From bf2d9dcd0526e2820933b2659a4026b17b8da9ce Mon Sep 17 00:00:00 2001 From: Ivailo Monev Date: Wed, 19 Nov 2014 02:23:05 +0000 Subject: [PATCH] import a bunch of important libraries and applications --- bluedevil/CMakeLists.txt | 23 + bluedevil/HACKING | 24 + bluedevil/README | 14 + bluedevil/bluedevil.kdev4 | 3 + bluedevil/cmake/CMakeLists.txt | 3 + bluedevil/cmake/modules/CMakeLists.txt | 8 + .../cmake/modules/FindLibBlueDevil.cmake | 38 + bluedevil/cmake/modules/PkgConfigGetVar.cmake | 32 + bluedevil/po/CMakeLists.txt | 63 + bluedevil/po/ar/CMakeLists.txt | 2 + bluedevil/po/ar/bluedevil.po | 919 + bluedevil/po/bs/CMakeLists.txt | 2 + bluedevil/po/bs/bluedevil.po | 964 + bluedevil/po/ca/CMakeLists.txt | 2 + bluedevil/po/ca/bluedevil.po | 914 + bluedevil/po/ca@valencia/CMakeLists.txt | 2 + bluedevil/po/ca@valencia/bluedevil.po | 914 + bluedevil/po/cs/CMakeLists.txt | 2 + bluedevil/po/cs/bluedevil.po | 907 + bluedevil/po/da/CMakeLists.txt | 2 + bluedevil/po/da/bluedevil.po | 1101 + bluedevil/po/de/CMakeLists.txt | 2 + bluedevil/po/de/bluedevil.po | 1042 + bluedevil/po/el/CMakeLists.txt | 2 + bluedevil/po/el/bluedevil.po | 917 + bluedevil/po/en_GB/CMakeLists.txt | 2 + bluedevil/po/en_GB/bluedevil.po | 1166 + bluedevil/po/eo/CMakeLists.txt | 2 + bluedevil/po/eo/bluedevil.po | 883 + bluedevil/po/es/CMakeLists.txt | 2 + bluedevil/po/es/bluedevil.po | 913 + bluedevil/po/et/CMakeLists.txt | 2 + bluedevil/po/et/bluedevil.po | 1330 + bluedevil/po/eu/CMakeLists.txt | 2 + bluedevil/po/eu/bluedevil.po | 1042 + bluedevil/po/fa/CMakeLists.txt | 2 + bluedevil/po/fa/bluedevil.po | 933 + bluedevil/po/fi/CMakeLists.txt | 2 + bluedevil/po/fi/bluedevil.po | 913 + bluedevil/po/fr/CMakeLists.txt | 2 + bluedevil/po/fr/bluedevil.po | 918 + bluedevil/po/ga/CMakeLists.txt | 2 + bluedevil/po/ga/bluedevil.po | 905 + bluedevil/po/gl/CMakeLists.txt | 2 + bluedevil/po/gl/bluedevil.po | 912 + bluedevil/po/hu/CMakeLists.txt | 2 + bluedevil/po/hu/bluedevil.po | 908 + bluedevil/po/it/CMakeLists.txt | 2 + bluedevil/po/it/bluedevil.po | 909 + bluedevil/po/ja/CMakeLists.txt | 2 + bluedevil/po/ja/bluedevil.po | 881 + bluedevil/po/kk/CMakeLists.txt | 2 + bluedevil/po/kk/bluedevil.po | 903 + bluedevil/po/km/CMakeLists.txt | 2 + bluedevil/po/km/bluedevil.po | 961 + bluedevil/po/ko/CMakeLists.txt | 2 + bluedevil/po/ko/bluedevil.po | 1055 + bluedevil/po/lt/CMakeLists.txt | 2 + bluedevil/po/lt/bluedevil.po | 918 + bluedevil/po/mai/CMakeLists.txt | 2 + bluedevil/po/mai/bluedevil.po | 968 + bluedevil/po/mr/CMakeLists.txt | 2 + bluedevil/po/mr/bluedevil.po | 900 + bluedevil/po/ms/CMakeLists.txt | 2 + bluedevil/po/ms/bluedevil.po | 980 + bluedevil/po/nb/CMakeLists.txt | 2 + bluedevil/po/nb/bluedevil.po | 1030 + bluedevil/po/nds/CMakeLists.txt | 2 + bluedevil/po/nds/bluedevil.po | 1115 + bluedevil/po/nl/CMakeLists.txt | 2 + bluedevil/po/nl/bluedevil.po | 908 + bluedevil/po/pa/CMakeLists.txt | 2 + bluedevil/po/pa/bluedevil.po | 1091 + bluedevil/po/pl/CMakeLists.txt | 2 + bluedevil/po/pl/bluedevil.po | 1033 + bluedevil/po/pt/CMakeLists.txt | 2 + bluedevil/po/pt/bluedevil.po | 899 + bluedevil/po/pt_BR/CMakeLists.txt | 2 + bluedevil/po/pt_BR/bluedevil.po | 908 + bluedevil/po/ro/CMakeLists.txt | 2 + bluedevil/po/ro/bluedevil.po | 1098 + bluedevil/po/ru/CMakeLists.txt | 2 + bluedevil/po/ru/bluedevil.po | 1182 + bluedevil/po/sk/CMakeLists.txt | 2 + bluedevil/po/sk/bluedevil.po | 908 + bluedevil/po/sl/CMakeLists.txt | 2 + bluedevil/po/sl/bluedevil.po | 914 + bluedevil/po/sr/CMakeLists.txt | 2 + bluedevil/po/sr/bluedevil.po | 1039 + bluedevil/po/sr@ijekavian/CMakeLists.txt | 2 + bluedevil/po/sr@ijekavian/bluedevil.po | 1039 + bluedevil/po/sr@ijekavianlatin/CMakeLists.txt | 2 + bluedevil/po/sr@ijekavianlatin/bluedevil.po | 1039 + bluedevil/po/sr@latin/CMakeLists.txt | 2 + bluedevil/po/sr@latin/bluedevil.po | 1039 + bluedevil/po/sv/CMakeLists.txt | 2 + bluedevil/po/sv/bluedevil.po | 901 + bluedevil/po/th/CMakeLists.txt | 2 + bluedevil/po/th/bluedevil.po | 1152 + bluedevil/po/tr/CMakeLists.txt | 2 + bluedevil/po/tr/bluedevil.po | 1060 + bluedevil/po/ug/CMakeLists.txt | 2 + bluedevil/po/ug/bluedevil.po | 905 + bluedevil/po/uk/CMakeLists.txt | 2 + bluedevil/po/uk/bluedevil.po | 912 + bluedevil/po/zh_CN/CMakeLists.txt | 2 + bluedevil/po/zh_CN/bluedevil.po | 999 + bluedevil/po/zh_TW/CMakeLists.txt | 2 + bluedevil/po/zh_TW/bluedevil.po | 1089 + bluedevil/src/CMakeLists.txt | 19 + bluedevil/src/Messages.sh | 4 + bluedevil/src/bluedevil-mime.xml | 43 + bluedevil/src/bluedevil.notifyrc | 476 + bluedevil/src/daemon/CMakeLists.txt | 2 + bluedevil/src/daemon/helpers/CMakeLists.txt | 4 + .../daemon/helpers/authorize/CMakeLists.txt | 12 + .../daemon/helpers/authorize/authorize.cpp | 84 + .../src/daemon/helpers/authorize/authorize.h | 63 + .../src/daemon/helpers/authorize/main.cpp | 41 + .../helpers/confirmmodechange/CMakeLists.txt | 12 + .../confirmmodechange/confirmmodechange.cpp | 72 + .../confirmmodechange/confirmmodechange.h | 58 + .../daemon/helpers/confirmmodechange/main.cpp | 41 + .../requestconfirmation/CMakeLists.txt | 12 + .../helpers/requestconfirmation/main.cpp | 40 + .../requestconfirmation.cpp | 68 + .../requestconfirmation/requestconfirmation.h | 58 + .../daemon/helpers/requestpin/CMakeLists.txt | 15 + .../daemon/helpers/requestpin/dialogWidget.ui | 92 + .../src/daemon/helpers/requestpin/main.cpp | 40 + .../daemon/helpers/requestpin/requestpin.cpp | 116 + .../daemon/helpers/requestpin/requestpin.h | 64 + bluedevil/src/daemon/kded/BlueDevilDaemon.cpp | 330 + bluedevil/src/daemon/kded/BlueDevilDaemon.h | 106 + bluedevil/src/daemon/kded/CMakeLists.txt | 39 + bluedevil/src/daemon/kded/bluedevil.desktop | 82 + bluedevil/src/daemon/kded/bluezagent.cpp | 196 + bluedevil/src/daemon/kded/bluezagent.h | 149 + .../daemon/kded/filereceiver/filereceiver.cpp | 57 + .../daemon/kded/filereceiver/filereceiver.h | 37 + .../daemon/kded/filereceiver/obexagent.cpp | 65 + .../src/daemon/kded/filereceiver/obexagent.h | 47 + .../org.bluez.obex.AgentManager1.xml | 12 + .../filereceiver/org.bluez.obex.Session1.xml | 14 + .../filereceiver/org.bluez.obex.Transfer1.xml | 15 + .../org.freedesktop.DBus.Properties.xml | 27 + .../kded/filereceiver/receivefilejob.cpp | 270 + .../daemon/kded/filereceiver/receivefilejob.h | 70 + .../src/fileitemactionplugin/CMakeLists.txt | 4 + .../bluedevilsendfile.desktop | 89 + .../sendfileitemaction.cpp | 121 + .../fileitemactionplugin/sendfileitemaction.h | 46 + bluedevil/src/kcmodule/CMakeLists.txt | 44 + bluedevil/src/kcmodule/bluedeviladapters.cpp | 380 + .../src/kcmodule/bluedeviladapters.desktop | 137 + bluedevil/src/kcmodule/bluedeviladapters.h | 128 + bluedevil/src/kcmodule/bluedevildevices.cpp | 683 + .../src/kcmodule/bluedevildevices.desktop | 142 + bluedevil/src/kcmodule/bluedevildevices.h | 93 + bluedevil/src/kcmodule/bluedeviltransfer.cpp | 155 + .../src/kcmodule/bluedeviltransfer.desktop | 136 + bluedevil/src/kcmodule/bluedeviltransfer.h | 59 + bluedevil/src/kcmodule/columnresizer.cpp | 202 + bluedevil/src/kcmodule/columnresizer.h | 41 + bluedevil/src/kcmodule/devicedetails.cpp | 96 + bluedevil/src/kcmodule/devicedetails.h | 58 + bluedevil/src/kcmodule/kded.cpp | 26 + bluedevil/src/kcmodule/kded.h | 120 + .../kcmodule/org.kde.BlueDevil.Service.xml | 10 + .../sharedfilesdialog/linkproxymodel.cpp | 38 + .../sharedfilesdialog/linkproxymodel.h | 31 + .../kcmodule/sharedfilesdialog/sharedfiles.ui | 55 + .../sharedfilesdialog/sharedfilesdialog.cpp | 132 + .../sharedfilesdialog/sharedfilesdialog.h | 44 + bluedevil/src/kcmodule/systemcheck.cpp | 324 + bluedevil/src/kcmodule/systemcheck.h | 89 + bluedevil/src/kcmodule/transfer.ui | 248 + bluedevil/src/kio/CMakeLists.txt | 2 + .../src/kio/bluetooth/.kdev4/bluetooth.kdev4 | 11 + bluedevil/src/kio/bluetooth/CMakeLists.txt | 19 + bluedevil/src/kio/bluetooth/bluetooth.kdev4 | 3 + .../src/kio/bluetooth/bluetooth.protocol | 16 + .../src/kio/bluetooth/kded_bluedevil.xml | 15 + bluedevil/src/kio/bluetooth/kiobluetooth.cpp | 240 + bluedevil/src/kio/bluetooth/kiobluetooth.h | 149 + bluedevil/src/kio/bluetooth/types.h | 11 + bluedevil/src/kio/obexftp/CMakeLists.txt | 35 + .../src/kio/obexftp/daemon/CMakeLists.txt | 26 + bluedevil/src/kio/obexftp/daemon/HACKING | 18 + .../src/kio/obexftp/daemon/ObexFtpDaemon.cpp | 200 + .../src/kio/obexftp/daemon/ObexFtpDaemon.h | 76 + .../kio/obexftp/daemon/createsessionjob.cpp | 101 + .../src/kio/obexftp/daemon/createsessionjob.h | 52 + .../kio/obexftp/daemon/obexftpdaemon.desktop | 79 + .../org.freedesktop.DBus.ObjectManager.xml | 14 + bluedevil/src/kio/obexftp/kded_obexftp.xml | 11 + bluedevil/src/kio/obexftp/kio_obexftp.cpp | 370 + bluedevil/src/kio/obexftp/kio_obexftp.h | 76 + bluedevil/src/kio/obexftp/obexdtypes.h | 35 + bluedevil/src/kio/obexftp/obexftp.protocol | 17 + .../obexftp/org.bluez.obex.FileTransfer1.xml | 41 + bluedevil/src/kio/obexftp/transferfilejob.cpp | 140 + bluedevil/src/kio/obexftp/transferfilejob.h | 59 + bluedevil/src/monolithic/CMakeLists.txt | 13 + .../monolithic/bluedevil-monolithic.desktop | 141 + bluedevil/src/monolithic/main.cpp | 42 + bluedevil/src/monolithic/monolithic.cpp | 457 + bluedevil/src/monolithic/monolithic.h | 94 + bluedevil/src/monolithic/org.bluez.Audio.xml | 15 + bluedevil/src/sendfile/CMakeLists.txt | 34 + .../src/sendfile/bluedevil-sendfile.desktop | 141 + bluedevil/src/sendfile/discover.ui | 35 + bluedevil/src/sendfile/discoverwidget.cpp | 136 + bluedevil/src/sendfile/discoverwidget.h | 68 + bluedevil/src/sendfile/main.cpp | 62 + .../src/sendfile/org.bluez.obex.Client1.xml | 15 + .../sendfile/org.bluez.obex.ObjectPush1.xml | 25 + .../src/sendfile/org.bluez.obex.Transfer1.xml | 38 + bluedevil/src/sendfile/pages/CMakeLists.txt | 1 + bluedevil/src/sendfile/pages/connecting.ui | 35 + .../src/sendfile/pages/connectingpage.cpp | 49 + bluedevil/src/sendfile/pages/connectingpage.h | 41 + .../pages/selectdeviceandfilespage.cpp | 106 + .../sendfile/pages/selectdeviceandfilespage.h | 56 + .../src/sendfile/pages/selectdevicepage.cpp | 73 + .../src/sendfile/pages/selectdevicepage.h | 54 + .../src/sendfile/pages/selectfilediscover.ui | 143 + .../src/sendfile/pages/selectfilespage.cpp | 62 + .../src/sendfile/pages/selectfilespage.h | 46 + bluedevil/src/sendfile/sendfilesjob.cpp | 226 + bluedevil/src/sendfile/sendfilesjob.h | 85 + bluedevil/src/sendfile/sendfilewizard.cpp | 168 + bluedevil/src/sendfile/sendfilewizard.h | 67 + bluedevil/src/settings/CMakeLists.txt | 1 + bluedevil/src/settings/filereceiver.kcfg | 74 + .../src/settings/filereceiversettings.kcfgc | 4 + bluedevil/src/settings/global.kcfg | 14 + bluedevil/src/settings/globalsettings.kcfgc | 4 + bluedevil/src/wizard/CMakeLists.txt | 31 + bluedevil/src/wizard/USER_FLOW.txt | 38 + bluedevil/src/wizard/bluedevil-wizard.desktop | 140 + bluedevil/src/wizard/bluewizard.cpp | 160 + bluedevil/src/wizard/bluewizard.h | 74 + bluedevil/src/wizard/main.cpp | 53 + bluedevil/src/wizard/pages/discover.ui | 136 + bluedevil/src/wizard/pages/discoverpage.cpp | 241 + bluedevil/src/wizard/pages/discoverpage.h | 58 + bluedevil/src/wizard/pages/fail.cpp | 66 + bluedevil/src/wizard/pages/fail.h | 54 + bluedevil/src/wizard/pages/fail.ui | 58 + .../src/wizard/pages/keyboardpairing.cpp | 91 + bluedevil/src/wizard/pages/keyboardpairing.h | 64 + bluedevil/src/wizard/pages/keyboardpairing.ui | 147 + bluedevil/src/wizard/pages/legacypairing.cpp | 91 + bluedevil/src/wizard/pages/legacypairing.h | 64 + bluedevil/src/wizard/pages/legacypairing.ui | 147 + .../wizard/pages/legacypairingdatabase.cpp | 77 + .../src/wizard/pages/legacypairingdatabase.h | 63 + bluedevil/src/wizard/pages/nopairing.cpp | 96 + bluedevil/src/wizard/pages/nopairing.h | 66 + bluedevil/src/wizard/pages/nopairing.ui | 74 + bluedevil/src/wizard/pages/ssppairing.cpp | 158 + bluedevil/src/wizard/pages/ssppairing.h | 70 + bluedevil/src/wizard/pages/ssppairing.ui | 156 + bluedevil/src/wizard/pin-code-database.xml | 168 + bluedevil/src/wizard/wizardagent.cpp | 194 + bluedevil/src/wizard/wizardagent.h | 70 + bluedevil/version.h.cmake | 4 + dragon/.gitignore | 2 + dragon/CMakeLists.txt | 24 + dragon/COPYING | 340 + dragon/COPYING.DOC | 397 + dragon/ChangeLog | 99 + dragon/HACKING | 302 + dragon/Messages.sh | 4 + dragon/README | 32 + dragon/TODO | 58 + dragon/config.h.cmake | 2 + dragon/doc/CMakeLists.txt | 2 + dragon/doc/index.docbook | 208 + dragon/doc/main.png | Bin 0 -> 141440 bytes dragon/doc/man-dragon.1.docbook | 124 + dragon/doc/playmedia.png | Bin 0 -> 50496 bytes dragon/misc/16x16_dragonplayer.svgz | Bin 0 -> 3573 bytes dragon/misc/32x32_dragonplayer.svgz | Bin 0 -> 16965 bytes dragon/misc/CMakeLists.txt | 8 + dragon/misc/dragonlogo.png | Bin 0 -> 47792 bytes dragon/misc/dragonplayer-opendvd.desktop | 58 + dragon/misc/dragonplayer.desktop | 109 + dragon/misc/dragonplayer_part.desktop | 107 + dragon/misc/dragonplayer_play_dvd.desktop | 59 + dragon/misc/dragonplayerrc | 14 + dragon/misc/dragonplayerui.rc | 38 + dragon/misc/hi128-app-dragonplayer.png | Bin 0 -> 17342 bytes dragon/misc/hi16-app-dragonplayer.png | Bin 0 -> 820 bytes dragon/misc/hi22-app-dragonplayer.png | Bin 0 -> 1241 bytes dragon/misc/hi32-app-dragonplayer.png | Bin 0 -> 2143 bytes dragon/misc/hi48-app-dragonplayer.png | Bin 0 -> 4091 bytes dragon/misc/hi64-app-dragonplayer.png | Bin 0 -> 6350 bytes dragon/misc/hisc-app-dragonplayer.svgz | Bin 0 -> 19630 bytes .../misc/ox16-actions-player-volume-muted.png | Bin 0 -> 747 bytes .../misc/ox22-actions-player-volume-muted.png | Bin 0 -> 1081 bytes .../misc/ox32-actions-player-volume-muted.png | Bin 0 -> 1700 bytes .../misc/ox48-actions-player-volume-muted.png | Bin 0 -> 2943 bytes .../oxsc-actions-player-volume-muted.svgz | Bin 0 -> 3600 bytes dragon/src/FAQ | 13 + dragon/src/app/CMakeLists.txt | 56 + dragon/src/app/actions.cpp | 72 + dragon/src/app/actions.h | 54 + dragon/src/app/adjustSizeButton.cpp | 160 + dragon/src/app/adjustSizeButton.h | 59 + dragon/src/app/analyzer/analyzerBase.cpp | 202 + dragon/src/app/analyzer/analyzerBase.h | 105 + dragon/src/app/analyzer/blockAnalyzer.cpp | 423 + dragon/src/app/analyzer/blockAnalyzer.h | 81 + dragon/src/app/analyzer/fht.cpp | 243 + dragon/src/app/analyzer/fht.h | 119 + dragon/src/app/audioView2.cpp | 105 + dragon/src/app/audioView2.h | 53 + dragon/src/app/audioView2.ui | 139 + dragon/src/app/discSelectionDialog.cpp | 138 + dragon/src/app/discSelectionDialog.h | 46 + dragon/src/app/extern.h | 38 + dragon/src/app/fullScreenToolBarHandler.cpp | 114 + dragon/src/app/fullScreenToolBarHandler.h | 47 + dragon/src/app/listView.cpp | 41 + dragon/src/app/loadView.cpp | 58 + dragon/src/app/loadView.h | 44 + dragon/src/app/loadView.ui | 84 + dragon/src/app/main.cpp | 56 + dragon/src/app/mainWindow.cpp | 981 + dragon/src/app/mainWindow.h | 164 + dragon/src/app/part.cpp | 137 + dragon/src/app/part.h | 69 + dragon/src/app/partToolBar.cpp | 60 + dragon/src/app/partToolBar.h | 37 + dragon/src/app/playDialog.cpp | 118 + dragon/src/app/playDialog.h | 53 + dragon/src/app/playerApplication.cpp | 56 + dragon/src/app/playerApplication.h | 45 + dragon/src/app/playlistFile.cpp | 143 + dragon/src/app/playlistFile.h | 55 + dragon/src/app/recentlyPlayedList.cpp | 129 + dragon/src/app/recentlyPlayedList.h | 47 + dragon/src/app/stateChange.cpp | 204 + dragon/src/app/textItem.cpp | 92 + dragon/src/app/textItem.h | 45 + dragon/src/app/theStream.cpp | 274 + dragon/src/app/theStream.h | 81 + dragon/src/app/timeLabel.cpp | 92 + dragon/src/app/timeLabel.h | 44 + dragon/src/app/videoSettingsWidget.ui | 158 + dragon/src/app/videoWindow.cpp | 844 + dragon/src/app/videoWindow.h | 189 + dragon/src/codeine.h | 42 + dragon/src/messageBox.h | 45 + dragon/src/mpris2/mediaplayer2.cpp | 124 + dragon/src/mpris2/mediaplayer2.h | 73 + dragon/src/mpris2/mediaplayer2player.cpp | 277 + dragon/src/mpris2/mediaplayer2player.h | 101 + dragon/src/mpris2/mpris2.cpp | 67 + dragon/src/mpris2/mpris2.h | 38 + ffmpegthumbs/CMakeLists.txt | 40 + ffmpegthumbs/COPYING | 339 + ffmpegthumbs/cmake/COPYING-CMAKE-SCRIPTS | 22 + ffmpegthumbs/cmake/FindFFmpeg.cmake | 148 + ffmpegthumbs/ffmpegthumbnailer.cpp | 56 + ffmpegthumbs/ffmpegthumbnailer.h | 39 + ffmpegthumbs/ffmpegthumbnailer/AUTHORS | 2 + ffmpegthumbs/ffmpegthumbnailer/ChangeLog | 105 + ffmpegthumbs/ffmpegthumbnailer/README | 7 + ffmpegthumbs/ffmpegthumbnailer/filmstrip.h | 575 + .../ffmpegthumbnailer/filmstripfilter.cpp | 97 + .../ffmpegthumbnailer/filmstripfilter.h | 34 + ffmpegthumbs/ffmpegthumbnailer/histogram.h | 40 + ffmpegthumbs/ffmpegthumbnailer/ifilter.h | 34 + .../ffmpegthumbnailer/imagewriter.cpp | 41 + ffmpegthumbs/ffmpegthumbnailer/imagewriter.h | 37 + .../ffmpegthumbnailer/moviedecoder.cpp | 367 + ffmpegthumbs/ffmpegthumbnailer/moviedecoder.h | 75 + ffmpegthumbs/ffmpegthumbnailer/videoframe.h | 43 + .../ffmpegthumbnailer/videothumbnailer.cpp | 224 + .../ffmpegthumbnailer/videothumbnailer.h | 81 + ffmpegthumbs/ffmpegthumbs.desktop | 57 + ffmpegthumbs/tests/CMakeLists.txt | 26 + ffmpegthumbs/tests/ffmpegthumbtest.cpp | 44 + gwenview/.gitignore | 2 + gwenview/.kateconfig | 1 + gwenview/.krazy | 1 + gwenview/.reviewboardrc | 4 + gwenview/CMakeLists.txt | 101 + gwenview/COPYING | 339 + gwenview/COPYING.DOC | 397 + gwenview/Messages.sh | 3 + gwenview/NEWS | 133 + gwenview/app/CMakeLists.txt | 107 + gwenview/app/abstractcontextmanageritem.cpp | 62 + gwenview/app/abstractcontextmanageritem.h | 53 + gwenview/app/advancedconfigpage.ui | 125 + gwenview/app/browsemainpage.cpp | 403 + gwenview/app/browsemainpage.h | 87 + gwenview/app/browsemainpage.ui | 157 + gwenview/app/configdialog.cpp | 132 + gwenview/app/configdialog.h | 51 + gwenview/app/documentinfoprovider.cpp | 121 + gwenview/app/documentinfoprovider.h | 60 + gwenview/app/fileoperations.cpp | 215 + gwenview/app/fileoperations.h | 46 + gwenview/app/fileopscontextmanageritem.cpp | 445 + gwenview/app/fileopscontextmanageritem.h | 74 + gwenview/app/filtercontroller.cpp | 615 + gwenview/app/filtercontroller.h | 135 + gwenview/app/folderviewcontextmanageritem.cpp | 302 + gwenview/app/folderviewcontextmanageritem.h | 62 + gwenview/app/fullscreenconfigwidget.ui | 213 + gwenview/app/fullscreencontent.cpp | 527 + gwenview/app/fullscreencontent.h | 86 + gwenview/app/generalconfigpage.ui | 113 + gwenview/app/gvcore.cpp | 373 + gwenview/app/gvcore.h | 87 + gwenview/app/gwenview.desktop | 184 + gwenview/app/gwenviewui.rc | 109 + gwenview/app/imagemetainfodialog.cpp | 156 + gwenview/app/imagemetainfodialog.h | 60 + gwenview/app/imageopscontextmanageritem.cpp | 313 + gwenview/app/imageopscontextmanageritem.h | 64 + gwenview/app/imageviewconfigpage.ui | 492 + gwenview/app/infocontextmanageritem.cpp | 391 + gwenview/app/infocontextmanageritem.h | 61 + gwenview/app/kipiexportaction.cpp | 132 + gwenview/app/kipiexportaction.h | 57 + gwenview/app/kipiimagecollectionselector.cpp | 91 + gwenview/app/kipiimagecollectionselector.h | 54 + gwenview/app/kipiinterface.cpp | 483 + gwenview/app/kipiinterface.h | 119 + gwenview/app/kipiuploadwidget.cpp | 56 + gwenview/app/kipiuploadwidget.h | 53 + gwenview/app/main.cpp | 146 + gwenview/app/mainwindow.cpp | 1610 + gwenview/app/mainwindow.h | 140 + gwenview/app/preloader.cpp | 121 + gwenview/app/preloader.h | 61 + gwenview/app/saveallhelper.cpp | 115 + gwenview/app/saveallhelper.h | 56 + gwenview/app/savebar.cpp | 375 + gwenview/app/savebar.h | 69 + .../app/semanticinfocontextmanageritem.cpp | 466 + gwenview/app/semanticinfocontextmanageritem.h | 66 + gwenview/app/semanticinfodialog.ui | 68 + gwenview/app/semanticinfosidebaritem.ui | 99 + gwenview/app/sidebar.cpp | 251 + gwenview/app/sidebar.h | 88 + gwenview/app/slideshow.desktop | 66 + gwenview/app/splitter.h | 76 + gwenview/app/startmainpage.cpp | 337 + gwenview/app/startmainpage.h | 74 + gwenview/app/startmainpage.ui | 190 + gwenview/app/thumbnailviewhelper.cpp | 117 + gwenview/app/thumbnailviewhelper.h | 61 + gwenview/app/viewmainpage.cpp | 793 + gwenview/app/viewmainpage.h | 151 + gwenview/cmake/FindLCMS2.cmake | 72 + gwenview/color-schemes/CMakeLists.txt | 4 + gwenview/color-schemes/fullscreen.colors | 95 + gwenview/config-gwenview.h.cmake | 5 + gwenview/cursors/CMakeLists.txt | 4 + gwenview/cursors/zoom.png | Bin 0 -> 1743 bytes gwenview/cursors/zoom.svg | 122 + gwenview/devdoc/CONTRIBUTING.md | 46 + gwenview/devdoc/ENVIRONMENT_VARIABLES.md | 30 + gwenview/doc/CMakeLists.txt | 4 + gwenview/doc/browse_mode.png | Bin 0 -> 293536 bytes gwenview/doc/fullscreen-browse.png | Bin 0 -> 366201 bytes gwenview/doc/fullscreen-view.png | Bin 0 -> 637698 bytes gwenview/doc/importer-picking-root-folder.png | Bin 0 -> 229186 bytes gwenview/doc/importer.png | Bin 0 -> 238160 bytes gwenview/doc/index.docbook | 713 + gwenview/doc/modified-bar.png | Bin 0 -> 8510 bytes gwenview/doc/start-page.png | Bin 0 -> 92553 bytes gwenview/doc/view_mode.png | Bin 0 -> 455362 bytes gwenview/icons/CMakeLists.txt | 1 + .../icons/hi128-actions-document-share.png | Bin 0 -> 7293 bytes gwenview/icons/hi128-apps-gwenview.png | Bin 0 -> 15726 bytes .../icons/hi16-actions-document-share.png | Bin 0 -> 674 bytes gwenview/icons/hi16-apps-gwenview.png | Bin 0 -> 864 bytes .../icons/hi22-actions-document-share.png | Bin 0 -> 1086 bytes gwenview/icons/hi22-apps-gwenview.png | Bin 0 -> 1298 bytes .../icons/hi32-actions-document-share.png | Bin 0 -> 1364 bytes gwenview/icons/hi32-apps-gwenview.png | Bin 0 -> 2258 bytes .../icons/hi48-actions-document-share.png | Bin 0 -> 2120 bytes gwenview/icons/hi48-apps-gwenview.png | Bin 0 -> 4064 bytes .../icons/hi64-actions-document-share.png | Bin 0 -> 2927 bytes gwenview/icons/hi64-apps-gwenview.png | Bin 0 -> 6077 bytes .../icons/hisc-actions-document-share.svgz | Bin 0 -> 4292 bytes gwenview/icons/hisc-apps-gwenview.svgz | Bin 0 -> 7642 bytes gwenview/images/CMakeLists.txt | 3 + gwenview/images/background.png | Bin 0 -> 46843 bytes gwenview/importer/CMakeLists.txt | 58 + gwenview/importer/dialogpage.cpp | 98 + gwenview/importer/dialogpage.h | 58 + gwenview/importer/dialogpage.ui | 116 + gwenview/importer/documentdirfinder.cpp | 114 + gwenview/importer/documentdirfinder.h | 77 + gwenview/importer/filenameformater.cpp | 114 + gwenview/importer/filenameformater.h | 66 + gwenview/importer/fileutils.cpp | 142 + gwenview/importer/fileutils.h | 63 + gwenview/importer/gwenview_importer.desktop | 65 + .../importer/gwenview_importer_camera.desktop | 64 + gwenview/importer/importdialog.cpp | 268 + gwenview/importer/importdialog.h | 59 + gwenview/importer/importer.cpp | 246 + gwenview/importer/importer.h | 92 + gwenview/importer/importerconfig.kcfg | 19 + gwenview/importer/importerconfig.kcfgc | 6 + gwenview/importer/importerconfigdialog.cpp | 102 + gwenview/importer/importerconfigdialog.h | 52 + gwenview/importer/importerconfigdialog.ui | 163 + gwenview/importer/main.cpp | 74 + gwenview/importer/progresspage.cpp | 55 + gwenview/importer/progresspage.h | 51 + gwenview/importer/progresspage.ui | 65 + gwenview/importer/serializedurlmap.cpp | 110 + gwenview/importer/serializedurlmap.h | 58 + gwenview/importer/thumbnailpage.cpp | 455 + gwenview/importer/thumbnailpage.h | 80 + gwenview/importer/thumbnailpage.ui | 285 + gwenview/lib/CMakeLists.txt | 238 + gwenview/lib/about.cpp | 50 + gwenview/lib/about.h | 41 + gwenview/lib/abstractimageoperation.cpp | 114 + gwenview/lib/abstractimageoperation.h | 89 + gwenview/lib/archiveutils.cpp | 87 + gwenview/lib/archiveutils.h | 61 + gwenview/lib/binder.cpp | 36 + gwenview/lib/binder.h | 125 + gwenview/lib/cms/cmsprofile.cpp | 249 + gwenview/lib/cms/cmsprofile.h | 79 + gwenview/lib/cms/cmsprofile_png.cpp | 115 + gwenview/lib/cms/cmsprofile_png.h | 49 + gwenview/lib/cms/iccjpeg.c | 270 + gwenview/lib/cms/iccjpeg.h | 99 + gwenview/lib/contextmanager.cpp | 317 + gwenview/lib/contextmanager.h | 96 + gwenview/lib/crop/cropimageoperation.cpp | 94 + gwenview/lib/crop/cropimageoperation.h | 54 + gwenview/lib/crop/croptool.cpp | 427 + gwenview/lib/crop/croptool.h | 83 + gwenview/lib/crop/cropwidget.cpp | 328 + gwenview/lib/crop/cropwidget.h | 70 + gwenview/lib/crop/cropwidget.ui | 198 + gwenview/lib/datewidget.cpp | 148 + gwenview/lib/datewidget.h | 64 + .../lib/disabledactionshortcutmonitor.cpp | 70 + gwenview/lib/disabledactionshortcutmonitor.h | 63 + .../lib/document/abstractdocumenteditor.h | 71 + .../lib/document/abstractdocumentimpl.cpp | 100 + gwenview/lib/document/abstractdocumentimpl.h | 123 + .../document/animateddocumentloadedimpl.cpp | 112 + .../lib/document/animateddocumentloadedimpl.h | 58 + gwenview/lib/document/document.cpp | 576 + gwenview/lib/document/document.h | 252 + gwenview/lib/document/document_p.h | 89 + gwenview/lib/document/documentfactory.cpp | 286 + gwenview/lib/document/documentfactory.h | 96 + gwenview/lib/document/documentjob.cpp | 95 + gwenview/lib/document/documentjob.h | 116 + gwenview/lib/document/documentloadedimpl.cpp | 124 + gwenview/lib/document/documentloadedimpl.h | 76 + gwenview/lib/document/emptydocumentimpl.cpp | 49 + gwenview/lib/document/emptydocumentimpl.h | 46 + .../lib/document/jpegdocumentloadedimpl.cpp | 91 + .../lib/document/jpegdocumentloadedimpl.h | 61 + gwenview/lib/document/loadingdocumentimpl.cpp | 547 + gwenview/lib/document/loadingdocumentimpl.h | 67 + gwenview/lib/document/loadingjob.cpp | 61 + gwenview/lib/document/loadingjob.h | 47 + gwenview/lib/document/savejob.cpp | 170 + gwenview/lib/document/savejob.h | 69 + .../lib/document/svgdocumentloadedimpl.cpp | 81 + gwenview/lib/document/svgdocumentloadedimpl.h | 58 + .../lib/document/videodocumentloadedimpl.cpp | 64 + .../lib/document/videodocumentloadedimpl.h | 54 + gwenview/lib/documentonlyproxymodel.cpp | 59 + gwenview/lib/documentonlyproxymodel.h | 56 + .../abstractdocumentviewadapter.cpp | 67 + .../abstractdocumentviewadapter.h | 187 + .../lib/documentview/abstractimageview.cpp | 533 + gwenview/lib/documentview/abstractimageview.h | 140 + .../abstractrasterimageviewtool.cpp | 56 + .../abstractrasterimageviewtool.h | 92 + gwenview/lib/documentview/birdeyeview.cpp | 295 + gwenview/lib/documentview/birdeyeview.h | 80 + gwenview/lib/documentview/documentview.cpp | 773 + gwenview/lib/documentview/documentview.h | 227 + .../documentview/documentviewcontainer.cpp | 339 + .../lib/documentview/documentviewcontainer.h | 99 + .../documentview/documentviewcontroller.cpp | 271 + .../lib/documentview/documentviewcontroller.h | 74 + .../documentview/documentviewsynchronizer.cpp | 142 + .../documentview/documentviewsynchronizer.h | 69 + .../lib/documentview/loadingindicator.cpp | 96 + gwenview/lib/documentview/loadingindicator.h | 64 + gwenview/lib/documentview/messageview.ui | 101 + .../lib/documentview/messageviewadapter.cpp | 130 + .../lib/documentview/messageviewadapter.h | 64 + gwenview/lib/documentview/rasterimageview.cpp | 541 + gwenview/lib/documentview/rasterimageview.h | 90 + .../documentview/rasterimageviewadapter.cpp | 149 + .../lib/documentview/rasterimageviewadapter.h | 90 + gwenview/lib/documentview/svgviewadapter.cpp | 181 + gwenview/lib/documentview/svgviewadapter.h | 105 + .../lib/documentview/videoviewadapter.cpp | 356 + gwenview/lib/documentview/videoviewadapter.h | 76 + gwenview/lib/eventwatcher.cpp | 55 + gwenview/lib/eventwatcher.h | 60 + gwenview/lib/exiv2imageloader.cpp | 86 + gwenview/lib/exiv2imageloader.h | 64 + gwenview/lib/flowlayout.cpp | 145 + gwenview/lib/flowlayout.h | 71 + gwenview/lib/fullscreenbar.cpp | 275 + gwenview/lib/fullscreenbar.h | 70 + gwenview/lib/graphicswidgetfloater.cpp | 173 + gwenview/lib/graphicswidgetfloater.h | 74 + gwenview/lib/gvdebug.h | 105 + gwenview/lib/gwenviewconfig.kcfg | 254 + gwenview/lib/gwenviewconfig.kcfgc | 8 + gwenview/lib/gwenviewlib_export.h | 36 + gwenview/lib/historymodel.cpp | 258 + gwenview/lib/historymodel.h | 61 + gwenview/lib/hud/hudbutton.cpp | 202 + gwenview/lib/hud/hudbutton.h | 70 + gwenview/lib/hud/hudbuttonbox.cpp | 127 + gwenview/lib/hud/hudbuttonbox.h | 67 + gwenview/lib/hud/hudcountdown.cpp | 93 + gwenview/lib/hud/hudcountdown.h | 60 + gwenview/lib/hud/hudlabel.cpp | 75 + gwenview/lib/hud/hudlabel.h | 57 + gwenview/lib/hud/hudmessagebubble.cpp | 89 + gwenview/lib/hud/hudmessagebubble.h | 61 + gwenview/lib/hud/hudslider.cpp | 389 + gwenview/lib/hud/hudslider.h | 82 + gwenview/lib/hud/hudtheme.cpp | 141 + gwenview/lib/hud/hudtheme.h | 75 + gwenview/lib/hud/hudwidget.cpp | 159 + gwenview/lib/hud/hudwidget.h | 80 + gwenview/lib/imageformats/imageformats.cpp | 54 + gwenview/lib/imageformats/imageformats.h | 43 + gwenview/lib/imageformats/jpeghandler.cpp | 537 + gwenview/lib/imageformats/jpeghandler.h | 60 + gwenview/lib/imageformats/jpegplugin.cpp | 72 + gwenview/lib/imageformats/jpegplugin.h | 45 + gwenview/lib/imagemetainfomodel.cpp | 498 + gwenview/lib/imagemetainfomodel.h | 73 + gwenview/lib/imagescaler.cpp | 214 + gwenview/lib/imagescaler.h | 66 + gwenview/lib/imageutils.cpp | 74 + gwenview/lib/imageutils.h | 39 + gwenview/lib/invisiblebuttongroup.cpp | 79 + gwenview/lib/invisiblebuttongroup.h | 102 + gwenview/lib/iodevicejpegsourcemanager.cpp | 115 + gwenview/lib/iodevicejpegsourcemanager.h | 51 + gwenview/lib/jpegcontent.cpp | 644 + gwenview/lib/jpegcontent.h | 92 + gwenview/lib/jpegerrormanager.h | 66 + gwenview/lib/kindproxymodel.cpp | 79 + gwenview/lib/kindproxymodel.h | 60 + gwenview/lib/libjpeg-62/README.jpeg | 385 + gwenview/lib/libjpeg-62/jinclude.h | 90 + gwenview/lib/libjpeg-62/jpegint.h | 389 + gwenview/lib/libjpeg-62/transupp.c | 930 + gwenview/lib/libjpeg-62/transupp.h | 138 + gwenview/lib/libjpeg-80/README.jpeg | 325 + gwenview/lib/libjpeg-80/jinclude.h | 90 + gwenview/lib/libjpeg-80/jpegint.h | 404 + gwenview/lib/libjpeg-80/transupp.c | 1585 + gwenview/lib/libjpeg-80/transupp.h | 206 + gwenview/lib/libjpeg-90/README.jpeg | 381 + gwenview/lib/libjpeg-90/jinclude.h | 91 + gwenview/lib/libjpeg-90/jpegint.h | 426 + gwenview/lib/libjpeg-90/transupp.c | 1763 + gwenview/lib/libjpeg-90/transupp.h | 219 + gwenview/lib/memoryutils.cpp | 171 + gwenview/lib/memoryutils.h | 56 + gwenview/lib/mimetypeutils.cpp | 206 + gwenview/lib/mimetypeutils.h | 70 + gwenview/lib/mimetypeutils_p.h | 71 + gwenview/lib/mousewheelbehavior.h | 44 + gwenview/lib/orientation.h | 57 + gwenview/lib/paintutils.cpp | 156 + gwenview/lib/paintutils.h | 74 + gwenview/lib/placetreemodel.cpp | 380 + gwenview/lib/placetreemodel.h | 72 + gwenview/lib/preferredimagemetainfomodel.cpp | 143 + gwenview/lib/preferredimagemetainfomodel.h | 64 + gwenview/lib/print/printhelper.cpp | 150 + gwenview/lib/print/printhelper.h | 53 + gwenview/lib/print/printoptionspage.cpp | 231 + gwenview/lib/print/printoptionspage.h | 75 + gwenview/lib/print/printoptionspage.ui | 362 + gwenview/lib/ramp.h | 65 + gwenview/lib/recursivedirmodel.cpp | 219 + gwenview/lib/recursivedirmodel.h | 69 + .../redeyereductionimageoperation.cpp | 166 + .../redeyereductionimageoperation.h | 56 + .../redeyereduction/redeyereductiontool.cpp | 192 + .../lib/redeyereduction/redeyereductiontool.h | 76 + .../redeyereduction/redeyereductionwidget.ui | 127 + gwenview/lib/resize/resizeimagedialog.cpp | 115 + gwenview/lib/resize/resizeimagedialog.h | 58 + gwenview/lib/resize/resizeimageoperation.cpp | 94 + gwenview/lib/resize/resizeimageoperation.h | 52 + gwenview/lib/resize/resizeimagewidget.ui | 153 + .../abstractsemanticinfobackend.cpp | 59 + .../abstractsemanticinfobackend.h | 104 + .../semanticinfo/baloosemanticinfobackend.cpp | 121 + .../semanticinfo/baloosemanticinfobackend.h | 71 + .../semanticinfo/fakesemanticinfobackend.cpp | 105 + .../semanticinfo/fakesemanticinfobackend.h | 71 + .../lib/semanticinfo/semanticinfodirmodel.cpp | 258 + .../lib/semanticinfo/semanticinfodirmodel.h | 84 + gwenview/lib/semanticinfo/sorteddirmodel.cpp | 300 + gwenview/lib/semanticinfo/sorteddirmodel.h | 133 + gwenview/lib/semanticinfo/tagitemdelegate.cpp | 151 + gwenview/lib/semanticinfo/tagitemdelegate.h | 67 + gwenview/lib/semanticinfo/tagmodel.cpp | 128 + gwenview/lib/semanticinfo/tagmodel.h | 83 + gwenview/lib/semanticinfo/tagwidget.cpp | 255 + gwenview/lib/semanticinfo/tagwidget.h | 65 + gwenview/lib/shadowfilter.cpp | 112 + gwenview/lib/shadowfilter.h | 68 + gwenview/lib/signalblocker.h | 53 + gwenview/lib/slidecontainer.cpp | 161 + gwenview/lib/slidecontainer.h | 100 + gwenview/lib/slideshow.cpp | 306 + gwenview/lib/slideshow.h | 81 + gwenview/lib/sorting.h | 50 + gwenview/lib/statusbartoolbutton.cpp | 111 + gwenview/lib/statusbartoolbutton.h | 62 + gwenview/lib/thumbnailgroup.h | 61 + .../thumbnailprovider/thumbnailgenerator.cpp | 314 + .../thumbnailprovider/thumbnailgenerator.h | 96 + .../thumbnailprovider/thumbnailprovider.cpp | 587 + .../lib/thumbnailprovider/thumbnailprovider.h | 182 + .../lib/thumbnailprovider/thumbnailwriter.cpp | 104 + .../lib/thumbnailprovider/thumbnailwriter.h | 64 + .../abstractdocumentinfoprovider.cpp | 38 + .../abstractdocumentinfoprovider.h | 66 + .../abstractthumbnailviewhelper.cpp | 34 + .../abstractthumbnailviewhelper.h | 55 + .../lib/thumbnailview/contextbarbutton.cpp | 108 + gwenview/lib/thumbnailview/contextbarbutton.h | 54 + .../lib/thumbnailview/dragpixmapgenerator.cpp | 125 + .../lib/thumbnailview/dragpixmapgenerator.h | 55 + gwenview/lib/thumbnailview/itemeditor.cpp | 88 + gwenview/lib/thumbnailview/itemeditor.h | 54 + .../lib/thumbnailview/previewitemdelegate.cpp | 955 + .../lib/thumbnailview/previewitemdelegate.h | 126 + .../lib/thumbnailview/thumbnailbarview.cpp | 533 + gwenview/lib/thumbnailview/thumbnailbarview.h | 89 + .../lib/thumbnailview/thumbnailslider.cpp | 74 + gwenview/lib/thumbnailview/thumbnailslider.h | 60 + gwenview/lib/thumbnailview/thumbnailview.cpp | 972 + gwenview/lib/thumbnailview/thumbnailview.h | 215 + gwenview/lib/thumbnailview/tooltipwidget.cpp | 107 + gwenview/lib/thumbnailview/tooltipwidget.h | 64 + gwenview/lib/timeutils.cpp | 159 + gwenview/lib/timeutils.h | 47 + gwenview/lib/transformimageoperation.cpp | 108 + gwenview/lib/transformimageoperation.h | 66 + gwenview/lib/urlutils.cpp | 118 + gwenview/lib/urlutils.h | 60 + gwenview/lib/version.h | 38 + gwenview/lib/widgetfloater.cpp | 169 + gwenview/lib/widgetfloater.h | 69 + gwenview/lib/zoomslider.cpp | 159 + gwenview/lib/zoomslider.h | 82 + gwenview/lib/zoomwidget.cpp | 213 + gwenview/lib/zoomwidget.h | 73 + gwenview/part/CMakeLists.txt | 18 + gwenview/part/gvbrowserextension.cpp | 64 + gwenview/part/gvbrowserextension.h | 51 + gwenview/part/gvpart.cpp | 189 + gwenview/part/gvpart.desktop | 71 + gwenview/part/gvpart.h | 63 + gwenview/part/gvpart.rc | 22 + gwenview/tests/CMakeLists.txt | 5 + gwenview/tests/auto/CMakeLists.txt | 52 + gwenview/tests/auto/README_REMOTE_TESTS | 6 + gwenview/tests/auto/cmsprofiletest.cpp | 96 + gwenview/tests/auto/cmsprofiletest.h | 44 + gwenview/tests/auto/contextmanagertest.cpp | 117 + gwenview/tests/auto/contextmanagertest.h | 38 + gwenview/tests/auto/documenttest.cpp | 839 + gwenview/tests/auto/documenttest.h | 135 + gwenview/tests/auto/historymodeltest.cpp | 119 + gwenview/tests/auto/historymodeltest.h | 36 + .../tests/auto/imagemetainfomodeltest.cpp | 58 + gwenview/tests/auto/imagemetainfomodeltest.h | 38 + gwenview/tests/auto/imagescalertest.cpp | 212 + gwenview/tests/auto/imagescalertest.h | 104 + gwenview/tests/auto/importertest.cpp | 248 + gwenview/tests/auto/importertest.h | 53 + gwenview/tests/auto/jpegcontenttest.cpp | 303 + gwenview/tests/auto/jpegcontenttest.h | 44 + gwenview/tests/auto/paintutilstest.cpp | 43 + gwenview/tests/auto/paintutilstest.h | 36 + gwenview/tests/auto/placetreemodeltest.cpp | 157 + gwenview/tests/auto/placetreemodeltest.h | 47 + gwenview/tests/auto/recursivedirmodeltest.cpp | 200 + gwenview/tests/auto/recursivedirmodeltest.h | 39 + .../tests/auto/semanticinfobackendtest.cpp | 137 + gwenview/tests/auto/semanticinfobackendtest.h | 78 + .../tests/auto/slidecontainerautotest.cpp | 145 + gwenview/tests/auto/slidecontainerautotest.h | 42 + gwenview/tests/auto/sorteddirmodeltest.cpp | 77 + gwenview/tests/auto/sorteddirmodeltest.h | 45 + gwenview/tests/auto/testutils.cpp | 196 + gwenview/tests/auto/testutils.h | 130 + gwenview/tests/auto/thumbnailprovidertest.cpp | 278 + gwenview/tests/auto/thumbnailprovidertest.h | 58 + gwenview/tests/auto/timeutilstest.cpp | 92 + gwenview/tests/auto/timeutilstest.h | 36 + .../auto/transformimageoperationtest.cpp | 69 + .../tests/auto/transformimageoperationtest.h | 39 + gwenview/tests/auto/urlutilstest.cpp | 57 + gwenview/tests/auto/urlutilstest.h | 35 + gwenview/tests/data/.gitignore | 3 + .../data/160216_no_size_before_decoding.eps | 338 + gwenview/tests/data/160382_corrupted.jpeg | Bin 0 -> 11069 bytes ..._1frame_with_graphic_control_extension.gif | Bin 0 -> 77 bytes gwenview/tests/data/188191_does_not_load.tga | Bin 0 -> 1013 bytes gwenview/tests/data/1frame.gif | Bin 0 -> 5600 bytes gwenview/tests/data/1x10k.jpg | Bin 0 -> 1269 bytes gwenview/tests/data/1x10k.png | Bin 0 -> 135 bytes gwenview/tests/data/289819_does_not_load.png | Bin 0 -> 15455 bytes .../tests/data/302350_exiv_0.23_exception.jpg | Bin 0 -> 122659 bytes gwenview/tests/data/40frames.gif | Bin 0 -> 4049 bytes gwenview/tests/data/4frames.gif | Bin 0 -> 434 bytes gwenview/tests/data/cms/Lower_Left.jpg | Bin 0 -> 43834 bytes gwenview/tests/data/cms/Lower_Right.jpg | Bin 0 -> 42800 bytes gwenview/tests/data/cms/Upper_Left.jpg | Bin 0 -> 30508 bytes gwenview/tests/data/cms/Upper_Right.jpg | Bin 0 -> 27862 bytes gwenview/tests/data/cms/colourTestFakeBRG.png | Bin 0 -> 8223 bytes gwenview/tests/data/cms/colourTestsRGB.png | Bin 0 -> 9009 bytes .../tests/data/date/exif-datetime-only.jpg | Bin 0 -> 8828 bytes .../tests/data/date/exif-datetimeoriginal.jpg | Bin 0 -> 11499 bytes gwenview/tests/data/embedded-thumbnail.jpg | Bin 0 -> 973 bytes gwenview/tests/data/empty.png | 0 gwenview/tests/data/fetch_testing_raw.sh | 26 + gwenview/tests/data/import/pict0001.jpg | Bin 0 -> 6811 bytes gwenview/tests/data/import/pict0002.jpg | Bin 0 -> 3080 bytes gwenview/tests/data/import/pict0003.jpg | Bin 0 -> 2745 bytes .../tests/data/jpg-with-gif-extension.gif | Bin 0 -> 9232 bytes gwenview/tests/data/orient1_vflip.jpg | Bin 0 -> 8582 bytes gwenview/tests/data/orient6-small.jpg | Bin 0 -> 4595 bytes gwenview/tests/data/orient6.jpg | Bin 0 -> 8828 bytes .../tests/data/png-with-jpeg-extension.jpg | Bin 0 -> 5612 bytes gwenview/tests/data/test.png | Bin 0 -> 5612 bytes gwenview/tests/data/test.svg | 73 + gwenview/tests/data/test.xcf | Bin 0 -> 27080 bytes gwenview/tests/manual/CMakeLists.txt | 36 + gwenview/tests/manual/browse.txt | 17 + gwenview/tests/manual/compare.txt | 10 + gwenview/tests/manual/imageloadbench.cpp | 61 + gwenview/tests/manual/slidecontainertest.cpp | 66 + gwenview/tests/manual/thumbnailgen.cpp | 121 + kactivities/.gitignore | 12 + kactivities/.videproject/project.conf | 30 + kactivities/.videproject/vimrc | 2 + kactivities/CMakeLists.txt | 32 + kactivities/MAINTAINER | 3 + kactivities/Mainpage.dox | 17 + kactivities/README | 6 + kactivities/README.developers | 112 + kactivities/README.packagers | 23 + kactivities/TODO | 11 + .../cmake/modules/CheckCxxFeatures.cmake | 113 + .../c++-test-override-attr-none-fail.cpp | 27 + .../modules/c++-test-override-attr-none.cpp | 27 + .../cmake/modules/c++11-test-auto-N2546.cpp | 20 + .../c++11-test-initializer-lists-N2672.cpp | 24 + .../cmake/modules/c++11-test-lambda-N2927.cpp | 25 + .../modules/c++11-test-nullptr-N2431-fail.cpp | 5 + .../modules/c++11-test-nullptr-N2431.cpp | 5 + .../c++11-test-override-N3206-fail.cpp | 27 + .../modules/c++11-test-override-N3206.cpp | 27 + .../modules/c++11-test-unique_ptr-none.cpp | 21 + .../c++11-test-variadic-templates-N2242.cpp | 20 + kactivities/scripts/commit.sh | 35 + kactivities/scripts/delete-activities.sh | 3 + kactivities/scripts/delete-stats.sh | 4 + kactivities/scripts/run-krazy.sh | 25 + kactivities/scripts/update-todo.sh | 3 + kactivities/scripts/ycm_extra_conf.py | 129 + kactivities/src/CMakeLists.txt | 101 + .../org.kde.ActivityManager.Activities.cpp | 76 + .../dbus/org.kde.ActivityManager.Activities.h | 45 + .../org.kde.ActivityManager.Activities.xml | 102 + .../dbus/org.kde.ActivityManager.Features.xml | 21 + .../org.kde.ActivityManager.Resources.xml | 24 + ...g.kde.ActivityManager.ResourcesLinking.xml | 46 + kactivities/src/config-features.h.cmake | 14 + kactivities/src/lib/CMakeLists.txt | 7 + kactivities/src/lib/core/CMakeLists.txt | 196 + .../src/lib/core/KActivitiesConfig.cmake.in | 24 + kactivities/src/lib/core/consumer.cpp | 210 + kactivities/src/lib/core/consumer.h | 198 + kactivities/src/lib/core/consumer_p.h | 74 + kactivities/src/lib/core/controller.cpp | 93 + kactivities/src/lib/core/controller.h | 116 + .../lib/core/includes/KActivities/Consumer | 1 + .../lib/core/includes/KActivities/Controller | 1 + .../src/lib/core/includes/KActivities/Info | 1 + .../includes/KActivities/ResourceInstance | 1 + .../src/lib/core/includes/KActivities/Version | 1 + kactivities/src/lib/core/info.cpp | 257 + kactivities/src/lib/core/info.h | 255 + kactivities/src/lib/core/info_p.h | 69 + kactivities/src/lib/core/kactivities_export.h | 52 + .../src/lib/core/libkactivities.pc.cmake | 12 + kactivities/src/lib/core/manager_p.cpp | 134 + kactivities/src/lib/core/manager_p.h | 71 + kactivities/src/lib/core/prettyheaders.sh | 4 + kactivities/src/lib/core/resourceinstance.cpp | 198 + kactivities/src/lib/core/resourceinstance.h | 223 + kactivities/src/lib/core/utils_p.h | 104 + kactivities/src/lib/core/version.cpp | 51 + kactivities/src/lib/core/version.h | 87 + kactivities/src/lib/models/CMakeLists.txt | 224 + .../models/KActivities-ModelsConfig.cmake.in | 24 + .../lib/models/activitiescomponentplugin.cpp | 45 + .../lib/models/activitiescomponentplugin.h | 35 + kactivities/src/lib/models/activitymodel.cpp | 302 + kactivities/src/lib/models/activitymodel.h | 84 + .../includes/KActivities/Models/ActivityModel | 1 + .../includes/KActivities/Models/ResourceModel | 1 + .../lib/models/kactivities_models_export.h | 52 + .../lib/models/libkactivities-models.pc.cmake | 12 + kactivities/src/lib/models/qmldir | 2 + kactivities/src/lib/models/resourcemodel.cpp | 591 + kactivities/src/lib/models/resourcemodel.h | 144 + kactivities/src/lib/models/utils_p.h | 60 + kactivities/src/ontologies/CMakeLists.txt | 9 + kactivities/src/ontologies/kao.ontology.in | 8 + kactivities/src/ontologies/kao.trig | 123 + kactivities/src/service/Activities.cpp | 502 + kactivities/src/service/Activities.h | 238 + kactivities/src/service/Activities_p.h | 96 + kactivities/src/service/Application.cpp | 238 + kactivities/src/service/Application.h | 56 + kactivities/src/service/CMakeLists.txt | 113 + kactivities/src/service/Event.cpp | 79 + kactivities/src/service/Event.h | 99 + kactivities/src/service/Features.cpp | 105 + kactivities/src/service/Features.h | 56 + kactivities/src/service/Messages.sh | 2 + kactivities/src/service/Module.cpp | 95 + kactivities/src/service/Module.h | 57 + kactivities/src/service/Plugin.cpp | 79 + kactivities/src/service/Plugin.h | 105 + kactivities/src/service/Resources.cpp | 339 + kactivities/src/service/Resources.h | 89 + kactivities/src/service/Resources_p.h | 73 + kactivities/src/service/common.h | 34 + .../files/activitymanager-plugin.desktop | 58 + .../service/files/kactivitymanagerd.desktop | 109 + kactivities/src/service/jobs/Job.cpp | 55 + kactivities/src/service/jobs/Job.h | 48 + kactivities/src/service/jobs/JobFactory.cpp | 97 + kactivities/src/service/jobs/JobFactory.h | 69 + .../src/service/jobs/activity/Start.cpp | 73 + kactivities/src/service/jobs/activity/Start.h | 71 + kactivities/src/service/jobs/activity/all.h | 26 + kactivities/src/service/jobs/general/Call.cpp | 96 + kactivities/src/service/jobs/general/Call.h | 80 + kactivities/src/service/jobs/general/all.h | 26 + .../src/service/jobs/ksmserver/KSMServer.cpp | 265 + .../src/service/jobs/ksmserver/KSMServer.h | 56 + .../src/service/jobs/ksmserver/KSMServer_p.h | 65 + .../src/service/jobs/schedulers/Abstract.cpp | 133 + .../src/service/jobs/schedulers/Abstract.h | 69 + .../src/service/jobs/schedulers/Abstract_p.h | 48 + .../src/service/jobs/schedulers/Fallible.cpp | 63 + .../src/service/jobs/schedulers/Fallible.h | 57 + .../src/service/jobs/schedulers/Given.cpp | 64 + .../src/service/jobs/schedulers/Given.h | 56 + .../src/service/jobs/schedulers/Ordered.cpp | 72 + .../src/service/jobs/schedulers/Ordered.h | 58 + .../src/service/jobs/schedulers/Retry.cpp | 75 + .../src/service/jobs/schedulers/Retry.h | 56 + .../src/service/jobs/schedulers/Switch.cpp | 71 + .../src/service/jobs/schedulers/Switch.h | 55 + .../src/service/jobs/schedulers/Test.cpp | 48 + .../src/service/jobs/schedulers/Test.h | 56 + kactivities/src/service/jobs/schedulers/all.h | 32 + .../src/service/plugins/CMakeLists.txt | 9 + .../plugins/activityranking/ActivityData.cpp | 87 + .../plugins/activityranking/ActivityData.h | 47 + .../activityranking/ActivityRanking.cpp | 584 + .../plugins/activityranking/ActivityRanking.h | 71 + .../activityranking/ActivityRankingPlugin.cpp | 79 + .../activityranking/ActivityRankingPlugin.h | 42 + .../plugins/activityranking/CMakeLists.txt | 44 + .../plugins/activityranking/Location.cpp | 121 + .../plugins/activityranking/Location.h | 58 + ...vitymanager-plugin-activityranking.desktop | 108 + ...rg.kde.ActivityManager.ActivityRanking.xml | 23 + .../org.kde.LocationManager.xml | 28 + .../plugins/globalshortcuts/CMakeLists.txt | 32 + .../globalshortcuts/GlobalShortcutsPlugin.cpp | 122 + .../globalshortcuts/GlobalShortcutsPlugin.h | 52 + .../plugins/globalshortcuts/Messages.sh | 2 + ...vitymanager-plugin-globalshortcuts.desktop | 109 + .../service/plugins/nepomuk/CMakeLists.txt | 49 + .../service/plugins/nepomuk/NepomukCommon.cpp | 134 + .../service/plugins/nepomuk/NepomukCommon.h | 61 + .../service/plugins/nepomuk/NepomukPlugin.cpp | 508 + .../service/plugins/nepomuk/NepomukPlugin.h | 81 + .../activitymanager-plugin-nepomuk.desktop | 104 + .../src/service/plugins/slc/CMakeLists.txt | 35 + .../src/service/plugins/slc/SlcPlugin.cpp | 116 + .../src/service/plugins/slc/SlcPlugin.h | 61 + .../slc/activitymanager-plugin-slc.desktop | 105 + .../slc/org.kde.ActivityManager.SLC.xml | 19 + .../src/service/plugins/sqlite/CMakeLists.txt | 41 + .../plugins/sqlite/DatabaseConnection.cpp | 335 + .../plugins/sqlite/DatabaseConnection.h | 62 + .../src/service/plugins/sqlite/Rankings.cpp | 249 + .../src/service/plugins/sqlite/Rankings.h | 127 + .../plugins/sqlite/ResourceScoreCache.cpp | 72 + .../plugins/sqlite/ResourceScoreCache.h | 44 + .../sqlite/ResourceScoreMaintainer.cpp | 140 + .../plugins/sqlite/ResourceScoreMaintainer.h | 46 + .../service/plugins/sqlite/StatsPlugin.cpp | 284 + .../src/service/plugins/sqlite/StatsPlugin.h | 79 + .../activitymanager-plugin-sqlite.desktop | 110 + .../org.kde.ActivityManager.Rankings.xml | 25 + ...org.kde.ActivityManager.RankingsClient.xml | 19 + ....kde.ActivityManager.Resources.Scoring.xml | 20 + .../virtualdesktopswitch/CMakeLists.txt | 32 + .../VirtualDesktopSwitchPlugin.cpp | 82 + .../VirtualDesktopSwitchPlugin.h | 44 + ...anager-plugin-virtualdesktopswitch.desktop | 108 + kactivities/src/utils/d_ptr.h | 52 + kactivities/src/utils/d_ptr_implementation.h | 55 + kactivities/src/utils/find_if_assoc.h | 85 + kactivities/src/utils/for_each_assoc.h | 81 + kactivities/src/utils/merge_into.h | 47 + kactivities/src/utils/nullptr.h | 30 + kactivities/src/utils/override.h | 35 + kactivities/src/utils/remove_if.h | 46 + kactivities/src/utils/val.h | 26 + kactivities/src/workspace/CMakeLists.txt | 29 + .../workspace/fileitemplugin/CMakeLists.txt | 23 + .../fileitemplugin/FileItemLinkingPlugin.cpp | 252 + .../fileitemplugin/FileItemLinkingPlugin.h | 46 + .../fileitemplugin/FileItemLinkingPlugin_p.h | 88 + .../src/workspace/fileitemplugin/Messages.sh | 2 + ...tymanagerd_fileitem_linking_plugin.desktop | 53 + kactivities/src/workspace/kio/CMakeLists.txt | 53 + .../src/workspace/kio/KioActivities.cpp | 445 + kactivities/src/workspace/kio/KioActivities.h | 101 + kactivities/src/workspace/kio/Messages.sh | 2 + .../src/workspace/kio/activities.protocol | 16 + .../settings/BlacklistedApplicationsModel.cpp | 228 + .../settings/BlacklistedApplicationsModel.h | 68 + .../src/workspace/settings/CMakeLists.txt | 47 + .../settings/MainConfigurationWidget.cpp | 286 + .../settings/MainConfigurationWidget.h | 66 + .../src/workspace/settings/Messages.sh | 3 + .../src/workspace/settings/kcm_activities.cpp | 20 + .../workspace/settings/kcm_activities.desktop | 107 + .../settings/qml/BlacklistApplicationView.qml | 120 + .../ui/MainConfigurationWidgetBase.ui | 221 + kcalc/CMakeLists.txt | 105 + kcalc/COPYING | 346 + kcalc/COPYING.LIB | 510 + kcalc/CTestConfig.cmake | 13 + kcalc/ChangeLog | 381 + kcalc/Mainpage.dox | 8 + kcalc/Messages.sh | 5 + kcalc/README | 8 + kcalc/TODO | 19 + kcalc/bitbutton.cpp | 65 + kcalc/bitbutton.h | 42 + kcalc/cmake/modules/FindMPFR.cmake | 23 + kcalc/colors.ui | 276 + kcalc/config-kcalc.h.cmake | 26 + kcalc/constants.ui | 174 + kcalc/doc/CMakeLists.txt | 4 + kcalc/doc/commands.docbook | 140 + kcalc/doc/index.docbook | 880 + kcalc/doc/kcalc_on_Aix.txt | 123 + kcalc/doc/kcalc_on_OSF.txt | 62 + kcalc/fonts.ui | 84 + kcalc/general.ui | 240 + kcalc/kcalc.cpp | 2328 + kcalc/kcalc.desktop | 154 + kcalc/kcalc.h | 281 + kcalc/kcalc.kcfg | 201 + kcalc/kcalc.ui | 1094 + kcalc/kcalc_bitset.cpp | 145 + kcalc/kcalc_bitset.h | 48 + kcalc/kcalc_button.cpp | 249 + kcalc/kcalc_button.h | 92 + kcalc/kcalc_const_button.cpp | 139 + kcalc/kcalc_const_button.h | 62 + kcalc/kcalc_const_menu.cpp | 150 + kcalc/kcalc_const_menu.h | 65 + kcalc/kcalc_core.cpp | 886 + kcalc/kcalc_core.h | 149 + kcalc/kcalc_settings.kcfgc | 6 + kcalc/kcalcdisplay.cpp | 968 + kcalc/kcalcdisplay.h | 156 + kcalc/kcalcrc.upd | 8 + kcalc/kcalcui.rc | 17 + kcalc/knumber/CMakeLists.txt | 2 + kcalc/knumber/knumber.cpp | 946 + kcalc/knumber/knumber.h | 175 + kcalc/knumber/knumber_base.h | 116 + kcalc/knumber/knumber_error.cpp | 714 + kcalc/knumber/knumber_error.h | 125 + kcalc/knumber/knumber_float.cpp | 1028 + kcalc/knumber/knumber_float.h | 135 + kcalc/knumber/knumber_fraction.cpp | 909 + kcalc/knumber/knumber_fraction.h | 137 + kcalc/knumber/knumber_integer.cpp | 888 + kcalc/knumber/knumber_integer.h | 123 + kcalc/knumber/knumber_operators.cpp | 296 + kcalc/knumber/knumber_operators.h | 67 + kcalc/knumber/tests/CMakeLists.txt | 8 + kcalc/knumber/tests/knumbertest.cpp | 857 + kcalc/scienceconstants.xml | 77 + kcalc/stats.cpp | 213 + kcalc/stats.h | 53 + kcalc/version.h | 21 + kcron/AUTHORS | 2 + kcron/CMakeLists.txt | 46 + kcron/COPYING | 340 + kcron/ChangeLog | 69 + kcron/Messages.sh | 2 + kcron/README | 12 + kcron/TODO | 5 + kcron/doc/CMakeLists.txt | 3 + kcron/doc/index.docbook | 443 + kcron/doc/kcronstart.png | Bin 0 -> 37391 bytes kcron/doc/newtask.png | Bin 0 -> 65045 bytes kcron/doc/newvariable.png | Bin 0 -> 25720 bytes kcron/kcron.lsm | 14 + kcron/src/CMakeLists.txt | 62 + kcron/src/crontabPrinter.cpp | 419 + kcron/src/crontabPrinter.h | 91 + kcron/src/crontabPrinterWidget.cpp | 65 + kcron/src/crontabPrinterWidget.h | 47 + kcron/src/crontabWidget.cpp | 430 + kcron/src/crontabWidget.h | 119 + kcron/src/crontablib/ctGlobalCron.cpp | 145 + kcron/src/crontablib/ctGlobalCron.h | 59 + kcron/src/crontablib/ctHelper.cpp | 33 + kcron/src/crontablib/ctHelper.h | 24 + .../src/crontablib/ctInitializationError.cpp | 12 + kcron/src/crontablib/ctInitializationError.h | 41 + kcron/src/crontablib/ctSaveStatus.cpp | 12 + kcron/src/crontablib/ctSaveStatus.h | 52 + kcron/src/crontablib/ctSystemCron.cpp | 72 + kcron/src/crontablib/ctSystemCron.h | 49 + kcron/src/crontablib/ctcron.cpp | 449 + kcron/src/crontablib/ctcron.h | 219 + kcron/src/crontablib/ctdom.cpp | 36 + kcron/src/crontablib/ctdom.h | 47 + kcron/src/crontablib/ctdow.cpp | 61 + kcron/src/crontablib/ctdow.h | 55 + kcron/src/crontablib/cthost.cpp | 249 + kcron/src/crontablib/cthost.h | 119 + kcron/src/crontablib/cthour.cpp | 37 + kcron/src/crontablib/cthour.h | 33 + kcron/src/crontablib/ctminute.cpp | 41 + kcron/src/crontablib/ctminute.h | 36 + kcron/src/crontablib/ctmonth.cpp | 35 + kcron/src/crontablib/ctmonth.h | 48 + kcron/src/crontablib/cttask.cpp | 462 + kcron/src/crontablib/cttask.h | 137 + kcron/src/crontablib/ctunit.cpp | 290 + kcron/src/crontablib/ctunit.h | 142 + kcron/src/crontablib/ctvariable.cpp | 143 + kcron/src/crontablib/ctvariable.h | 80 + kcron/src/crontablib/logging.h | 32 + kcron/src/genericListWidget.cpp | 192 + kcron/src/genericListWidget.h | 69 + kcron/src/kcmCron.cpp | 169 + kcron/src/kcmCron.h | 64 + kcron/src/kcm_cron.desktop | 172 + kcron/src/kcronHelper.cpp | 64 + kcron/src/kcronHelper.h | 32 + kcron/src/kcronIcons.cpp | 47 + kcron/src/kcronIcons.h | 40 + kcron/src/taskEditorDialog.cpp | 1114 + kcron/src/taskEditorDialog.h | 268 + kcron/src/taskWidget.cpp | 68 + kcron/src/taskWidget.h | 56 + kcron/src/tasksWidget.cpp | 358 + kcron/src/tasksWidget.h | 104 + kcron/src/variableEditorDialog.cpp | 225 + kcron/src/variableEditorDialog.h | 94 + kcron/src/variableWidget.cpp | 65 + kcron/src/variableWidget.h | 60 + kcron/src/variablesWidget.cpp | 274 + kcron/src/variablesWidget.h | 90 + kdenetwork-filesharing/CMakeLists.txt | 45 + kdenetwork-filesharing/COPYING | 347 + kdenetwork-filesharing/COPYING.DOC | 397 + kdenetwork-filesharing/COPYING.LIB | 510 + kdenetwork-filesharing/Messages.sh | 3 + kdenetwork-filesharing/samba/CMakeLists.txt | 1 + .../samba/filepropertiesplugin/CMakeLists.txt | 16 + .../samba/filepropertiesplugin/delegate.cpp | 80 + .../samba/filepropertiesplugin/delegate.h | 44 + .../samba/filepropertiesplugin/model.cpp | 178 + .../samba/filepropertiesplugin/model.h | 56 + .../sambausershareplugin.cpp | 238 + .../sambausershareplugin.desktop | 139 + .../sambausershareplugin.h | 60 + .../sambausershareplugin.ui | 114 + kfilemetadata/CMakeLists.txt | 57 + kfilemetadata/COPYING-CMAKE-SCRIPTS | 22 + kfilemetadata/COPYING.LGPL-2 | 481 + kfilemetadata/COPYING.LGPL-2.1 | 502 + kfilemetadata/COPYING.LGPL-3 | 165 + kfilemetadata/KFileMetaDataConfig.cmake.in | 18 + kfilemetadata/Messages.sh | 5 + kfilemetadata/autotests/CMakeLists.txt | 28 + .../autotests/indexerextractortests.cpp | 89 + .../autotests/indexerextractortests.h | 42 + .../indexerextractortestsconfig.h.in | 27 + kfilemetadata/autotests/propertyinfotest.cpp | 49 + kfilemetadata/autotests/propertyinfotest.h | 37 + kfilemetadata/autotests/samplefiles/README | 4 + .../autotests/samplefiles/plain_text_file.txt | 4 + kfilemetadata/autotests/simpleresult.cpp | 59 + kfilemetadata/autotests/simpleresult.h | 51 + kfilemetadata/cmake/modules/FindEPub.cmake | 35 + .../cmake/modules/FindPopplerQt4.cmake | 56 + kfilemetadata/src/CMakeLists.txt | 41 + kfilemetadata/src/extractionresult.cpp | 57 + kfilemetadata/src/extractionresult.h | 100 + kfilemetadata/src/extractorplugin.cpp | 144 + kfilemetadata/src/extractorplugin.h | 112 + kfilemetadata/src/extractorpluginmanager.cpp | 94 + kfilemetadata/src/extractorpluginmanager.h | 65 + kfilemetadata/src/extractors/CMakeLists.txt | 206 + .../src/extractors/epubextractor.cpp | 184 + kfilemetadata/src/extractors/epubextractor.h | 39 + .../src/extractors/exiv2extractor.cpp | 209 + kfilemetadata/src/extractors/exiv2extractor.h | 45 + .../src/extractors/ffmpegextractor.cpp | 182 + .../src/extractors/ffmpegextractor.h | 39 + .../kfilemetadata_epubextractor.desktop | 42 + .../kfilemetadata_exiv2extractor.desktop | 42 + .../kfilemetadata_ffmpegextractor.desktop | 42 + .../kfilemetadata_mobiextractor.desktop | 42 + .../kfilemetadata_odfextractor.desktop | 42 + .../kfilemetadata_office2007extractor.desktop | 42 + .../kfilemetadata_officeextractor.desktop | 42 + .../kfilemetadata_plaintextextractor.desktop | 42 + .../kfilemetadata_popplerextractor.desktop | 42 + .../kfilemetadata_taglibextractor.desktop | 42 + .../src/extractors/mobiextractor.cpp | 112 + kfilemetadata/src/extractors/mobiextractor.h | 39 + kfilemetadata/src/extractors/odfextractor.cpp | 138 + kfilemetadata/src/extractors/odfextractor.h | 41 + .../src/extractors/office2007extractor.cpp | 268 + .../src/extractors/office2007extractor.h | 46 + .../src/extractors/officeextractor.cpp | 118 + .../src/extractors/officeextractor.h | 50 + .../src/extractors/plaintextextractor.cpp | 60 + .../src/extractors/plaintextextractor.h | 40 + .../src/extractors/popplerextractor.cpp | 174 + .../src/extractors/popplerextractor.h | 43 + .../src/extractors/taglibextractor.cpp | 432 + .../src/extractors/taglibextractor.h | 41 + kfilemetadata/src/kfilemetadata_export.h | 39 + .../src/kfilemetadataextractor.desktop | 42 + kfilemetadata/src/properties.h | 126 + kfilemetadata/src/propertyinfo.cpp | 472 + kfilemetadata/src/propertyinfo.h | 81 + kfilemetadata/src/typeinfo.cpp | 101 + kfilemetadata/src/typeinfo.h | 58 + kfilemetadata/src/types.h | 82 + kgpg/.gitignore | 2 + kgpg/AUTHORS | 3 + kgpg/CMakeLists.txt | 187 + kgpg/COPYING | 346 + kgpg/CTestConfig.cmake | 13 + kgpg/Mainpage.dox | 17 + kgpg/Messages.sh | 5 + kgpg/TODO | 7 + kgpg/adduid.ui | 76 + kgpg/caff.cpp | 373 + kgpg/caff.h | 56 + kgpg/caff_p.h | 62 + kgpg/conf_decryption.ui | 73 + kgpg/conf_encryption.cpp | 45 + kgpg/conf_encryption.h | 39 + kgpg/conf_encryption.ui | 190 + kgpg/conf_gpg.ui | 188 + kgpg/conf_misc.ui | 386 + kgpg/conf_servers.ui | 105 + kgpg/conf_ui2.ui | 312 + kgpg/core/KGpgExpandableNode.cpp | 88 + kgpg/core/KGpgExpandableNode.h | 85 + kgpg/core/KGpgGroupMemberNode.cpp | 100 + kgpg/core/KGpgGroupMemberNode.h | 61 + kgpg/core/KGpgGroupNode.cpp | 277 + kgpg/core/KGpgGroupNode.h | 84 + kgpg/core/KGpgKeyNode.cpp | 369 + kgpg/core/KGpgKeyNode.h | 197 + kgpg/core/KGpgNode.cpp | 335 + kgpg/core/KGpgNode.h | 156 + kgpg/core/KGpgOrphanNode.cpp | 84 + kgpg/core/KGpgOrphanNode.h | 53 + kgpg/core/KGpgRefNode.cpp | 151 + kgpg/core/KGpgRefNode.h | 98 + kgpg/core/KGpgRootNode.cpp | 183 + kgpg/core/KGpgRootNode.h | 129 + kgpg/core/KGpgSignNode.cpp | 95 + kgpg/core/KGpgSignNode.h | 54 + kgpg/core/KGpgSignableNode.cpp | 98 + kgpg/core/KGpgSignableNode.h | 62 + kgpg/core/KGpgSubkeyNode.cpp | 99 + kgpg/core/KGpgSubkeyNode.h | 55 + kgpg/core/KGpgUatNode.cpp | 173 + kgpg/core/KGpgUatNode.h | 61 + kgpg/core/KGpgUidNode.cpp | 146 + kgpg/core/KGpgUidNode.h | 57 + kgpg/core/convert.cpp | 231 + kgpg/core/convert.h | 56 + kgpg/core/emailvalidator.cpp | 38 + kgpg/core/emailvalidator.h | 38 + kgpg/core/images.cpp | 106 + kgpg/core/images.h | 47 + kgpg/core/kgpgkey.cpp | 368 + kgpg/core/kgpgkey.h | 333 + kgpg/detailedconsole.cpp | 38 + kgpg/detailedconsole.h | 31 + kgpg/doc/CMakeLists.txt | 3 + kgpg/doc/editor.png | Bin 0 -> 53308 bytes kgpg/doc/index.docbook | 545 + kgpg/doc/keygen.png | Bin 0 -> 23121 bytes kgpg/doc/keymanage.png | Bin 0 -> 77957 bytes kgpg/doc/keyprop.png | Bin 0 -> 37313 bytes kgpg/doc/keys.png | Bin 0 -> 33672 bytes kgpg/doc/keyserver-search.png | Bin 0 -> 22155 bytes kgpg/doc/keyserver.png | Bin 0 -> 18552 bytes kgpg/doc/options.png | Bin 0 -> 47516 bytes kgpg/doc/select-secret-key.png | Bin 0 -> 24247 bytes kgpg/doc/systray.png | Bin 0 -> 8533 bytes kgpg/editor/kgpgeditor.cpp | 782 + kgpg/editor/kgpgeditor.h | 138 + kgpg/editor/kgpgeditor.rc | 29 + kgpg/editor/kgpgmd5widget.cpp | 101 + kgpg/editor/kgpgmd5widget.h | 38 + kgpg/editor/kgpgtextedit.cpp | 359 + kgpg/editor/kgpgtextedit.h | 78 + kgpg/encryptfile.desktop | 80 + kgpg/encryptfolder.desktop | 72 + kgpg/foldercompressjob.cpp | 184 + kgpg/foldercompressjob.h | 84 + kgpg/gpgproc.cpp | 371 + kgpg/gpgproc.h | 168 + kgpg/groupedit.cpp | 141 + kgpg/groupedit.h | 72 + kgpg/groupedit.ui | 184 + kgpg/hi16-app-kgpg.png | Bin 0 -> 1006 bytes kgpg/hi22-app-kgpg.png | Bin 0 -> 1245 bytes kgpg/hi32-app-kgpg.png | Bin 0 -> 2056 bytes kgpg/hi48-app-kgpg.png | Bin 0 -> 3399 bytes kgpg/icons/CMakeLists.txt | 1 + .../hi16-actions-document-export-key.png | Bin 0 -> 736 bytes .../hi16-actions-document-import-key.png | Bin 0 -> 1747 bytes .../hi16-actions-document-properties-key.png | Bin 0 -> 725 bytes kgpg/icons/hi16-status-key-group.png | Bin 0 -> 861 bytes kgpg/icons/hi16-status-key-orphan.png | Bin 0 -> 258 bytes kgpg/icons/hi16-status-key-pair.png | Bin 0 -> 703 bytes kgpg/icons/hi16-status-key-single.png | Bin 0 -> 635 bytes kgpg/icons/hi22-action-key-generate-pair.png | Bin 0 -> 337 bytes kgpg/icons/hi22-action-view-key-secret.png | Bin 0 -> 240 bytes .../hi22-actions-document-export-key.png | Bin 0 -> 1037 bytes .../hi22-actions-document-import-key.png | Bin 0 -> 2770 bytes .../hi22-actions-document-properties-key.png | Bin 0 -> 1085 bytes kgpg/icons/hi22-status-key-group.png | Bin 0 -> 1266 bytes kgpg/icons/hi22-status-key-pair.png | Bin 0 -> 1045 bytes kgpg/icons/hi22-status-key-single.png | Bin 0 -> 877 bytes .../hi32-actions-document-export-key.png | Bin 0 -> 1565 bytes .../hi32-actions-document-import-key.png | Bin 0 -> 4919 bytes .../hi32-actions-document-properties-key.png | Bin 0 -> 1676 bytes kgpg/icons/hi32-status-key-group.png | Bin 0 -> 2029 bytes kgpg/icons/hi32-status-key-pair.png | Bin 0 -> 1576 bytes kgpg/icons/hi32-status-key-single.png | Bin 0 -> 1307 bytes .../hi48-actions-document-export-key.png | Bin 0 -> 2513 bytes .../hi48-actions-document-import-key.png | Bin 0 -> 9282 bytes .../hi48-actions-document-properties-key.png | Bin 0 -> 2813 bytes kgpg/icons/hi48-status-key-group.png | Bin 0 -> 3297 bytes kgpg/icons/hi48-status-key-pair.png | Bin 0 -> 2456 bytes kgpg/icons/hi48-status-key-single.png | Bin 0 -> 2060 bytes .../hisc-actions-document-export-key.svgz | Bin 0 -> 20632 bytes .../hisc-actions-document-import-key.svgz | Bin 0 -> 20583 bytes .../hisc-actions-document-properties-key.svgz | Bin 0 -> 66516 bytes kgpg/icons/hisc-status-key-group.svgz | Bin 0 -> 63827 bytes kgpg/icons/hisc-status-key-pair.svgz | Bin 0 -> 62891 bytes kgpg/icons/hisc-status-key-single.svgz | Bin 0 -> 28667 bytes kgpg/keyexport.cpp | 52 + kgpg/keyexport.h | 36 + kgpg/keyexport.ui | 193 + kgpg/keyinfodialog.cpp | 392 + kgpg/keyinfodialog.h | 115 + kgpg/keyserver.ui | 315 + kgpg/keyservers.cpp | 374 + kgpg/keyservers.h | 133 + kgpg/keysmanager.cpp | 2861 + kgpg/keysmanager.h | 328 + kgpg/keysmanager.rc | 155 + kgpg/keytreeview.cpp | 231 + kgpg/keytreeview.h | 67 + kgpg/kgpg.appdata.xml | 111 + kgpg/kgpg.cpp | 188 + kgpg/kgpg.desktop | 220 + kgpg/kgpg.h | 63 + kgpg/kgpg.kcfg | 278 + kgpg/kgpgKeyInfo.ui | 468 + kgpg/kgpgchangekey.cpp | 188 + kgpg/kgpgchangekey.h | 187 + kgpg/kgpgexternalactions.cpp | 497 + kgpg/kgpgexternalactions.h | 99 + kgpg/kgpgfirstassistant.cpp | 375 + kgpg/kgpgfirstassistant.h | 97 + kgpg/kgpginterface.cpp | 417 + kgpg/kgpginterface.h | 39 + kgpg/kgpgkeygenerate.cpp | 250 + kgpg/kgpgkeygenerate.h | 62 + kgpg/kgpgkeygenerate.ui | 205 + kgpg/kgpgoptions.cpp | 608 + kgpg/kgpgoptions.h | 181 + kgpg/kgpgrevokewidget.cpp | 88 + kgpg/kgpgrevokewidget.h | 58 + kgpg/kgpgrevokewidget.ui | 154 + kgpg/kgpgsettings.kcfgc | 9 + kgpg/kgpgsettings_addons.h | 42 + kgpg/kgpgtextinterface.cpp | 110 + kgpg/kgpgtextinterface.h | 58 + kgpg/klinebufferedprocess.cpp | 128 + kgpg/klinebufferedprocess.h | 138 + kgpg/main.cpp | 54 + kgpg/model/gpgservermodel.cpp | 109 + kgpg/model/gpgservermodel.h | 56 + kgpg/model/groupeditproxymodel.cpp | 166 + kgpg/model/groupeditproxymodel.h | 55 + kgpg/model/keylistproxymodel.cpp | 595 + kgpg/model/keylistproxymodel.h | 92 + kgpg/model/kgpgitemmodel.cpp | 557 + kgpg/model/kgpgitemmodel.h | 99 + kgpg/model/kgpgitemnode.h | 35 + kgpg/model/kgpgsearchresultmodel.cpp | 391 + kgpg/model/kgpgsearchresultmodel.h | 63 + kgpg/model/selectkeyproxymodel.cpp | 272 + kgpg/model/selectkeyproxymodel.h | 76 + kgpg/newkey.cpp | 32 + kgpg/newkey.h | 36 + kgpg/newkey.ui | 221 + kgpg/org.kde.kgpg.Key.xml | 16 + kgpg/searchres.ui | 98 + kgpg/selectexpirydate.cpp | 82 + kgpg/selectexpirydate.h | 50 + kgpg/selectpublickeydialog.cpp | 243 + kgpg/selectpublickeydialog.h | 86 + kgpg/selectsecretkey.cpp | 139 + kgpg/selectsecretkey.h | 71 + kgpg/sourceselect.cpp | 37 + kgpg/sourceselect.h | 35 + kgpg/sourceselect.ui | 120 + kgpg/tips | 60 + kgpg/transactions/kgpgaddphoto.cpp | 60 + kgpg/transactions/kgpgaddphoto.h | 40 + kgpg/transactions/kgpgadduid.cpp | 83 + kgpg/transactions/kgpgadduid.h | 48 + kgpg/transactions/kgpgchangedisable.cpp | 55 + kgpg/transactions/kgpgchangedisable.h | 39 + kgpg/transactions/kgpgchangeexpire.cpp | 59 + kgpg/transactions/kgpgchangeexpire.h | 42 + kgpg/transactions/kgpgchangepass.cpp | 85 + kgpg/transactions/kgpgchangepass.h | 42 + kgpg/transactions/kgpgchangetrust.cpp | 63 + kgpg/transactions/kgpgchangetrust.h | 46 + kgpg/transactions/kgpgdecrypt.cpp | 141 + kgpg/transactions/kgpgdecrypt.h | 86 + kgpg/transactions/kgpgdelkey.cpp | 92 + kgpg/transactions/kgpgdelkey.h | 54 + kgpg/transactions/kgpgdelsign.cpp | 140 + kgpg/transactions/kgpgdelsign.h | 80 + kgpg/transactions/kgpgdeluid.cpp | 195 + kgpg/transactions/kgpgdeluid.h | 102 + kgpg/transactions/kgpgeditkeytransaction.cpp | 106 + kgpg/transactions/kgpgeditkeytransaction.h | 102 + kgpg/transactions/kgpgencrypt.cpp | 142 + kgpg/transactions/kgpgencrypt.h | 95 + kgpg/transactions/kgpgexport.cpp | 197 + kgpg/transactions/kgpgexport.h | 147 + kgpg/transactions/kgpggeneratekey.cpp | 266 + kgpg/transactions/kgpggeneratekey.h | 91 + kgpg/transactions/kgpggeneraterevoke.cpp | 126 + kgpg/transactions/kgpggeneraterevoke.h | 70 + kgpg/transactions/kgpgimport.cpp | 295 + kgpg/transactions/kgpgimport.h | 129 + .../kgpgkeyservergettransaction.cpp | 100 + .../kgpgkeyservergettransaction.h | 109 + .../kgpgkeyserversearchtransaction.cpp | 78 + .../kgpgkeyserversearchtransaction.h | 73 + .../transactions/kgpgkeyservertransaction.cpp | 103 + kgpg/transactions/kgpgkeyservertransaction.h | 90 + kgpg/transactions/kgpgprimaryuid.cpp | 43 + kgpg/transactions/kgpgprimaryuid.h | 53 + kgpg/transactions/kgpgsendkeys.cpp | 85 + kgpg/transactions/kgpgsendkeys.h | 72 + kgpg/transactions/kgpgsignkey.cpp | 87 + kgpg/transactions/kgpgsignkey.h | 60 + kgpg/transactions/kgpgsigntext.cpp | 86 + kgpg/transactions/kgpgsigntext.h | 86 + .../kgpgsigntransactionhelper.cpp | 144 + kgpg/transactions/kgpgsigntransactionhelper.h | 149 + kgpg/transactions/kgpgsignuid.cpp | 107 + kgpg/transactions/kgpgsignuid.h | 74 + .../kgpgtextorfiletransaction.cpp | 181 + kgpg/transactions/kgpgtextorfiletransaction.h | 109 + kgpg/transactions/kgpgtransaction.cpp | 746 + kgpg/transactions/kgpgtransaction.h | 460 + kgpg/transactions/kgpgtransactionjob.cpp | 83 + kgpg/transactions/kgpgtransactionjob.h | 77 + kgpg/transactions/kgpguidtransaction.cpp | 93 + kgpg/transactions/kgpguidtransaction.h | 84 + kgpg/transactions/kgpgverify.cpp | 201 + kgpg/transactions/kgpgverify.h | 90 + kgpg/viewdecrypted.desktop | 64 + kmix/AUTHORS | 12 + kmix/CMakeLists.txt | 252 + kmix/COPYING | 339 + kmix/COPYING.DOC | 397 + kmix/COPYING.LIB | 481 + kmix/ChangeLog | 55 + kmix/ConfigureChecks.cmake | 28 + kmix/Messages.sh | 4 + kmix/TODO | 91 + kmix/TestCases | 36 + kmix/apps/KMixApp.cpp | 141 + kmix/apps/KMixApp.h | 48 + kmix/apps/kmix.cpp | 1376 + kmix/apps/kmix.h | 173 + kmix/apps/kmixctrl.cpp | 86 + kmix/apps/kmixd.cpp | 322 + kmix/apps/kmixd.h | 98 + kmix/apps/kmixremote | 128 + kmix/apps/main.cpp | 79 + kmix/backends/kmix-backends.cpp | 118 + kmix/backends/mixer_alsa.h | 90 + kmix/backends/mixer_alsa9.cpp | 986 + kmix/backends/mixer_backend.cpp | 330 + kmix/backends/mixer_backend.h | 238 + kmix/backends/mixer_backend_i18n.cpp | 29 + kmix/backends/mixer_mpris2.cpp | 697 + kmix/backends/mixer_mpris2.h | 176 + kmix/backends/mixer_oss.cpp | 483 + kmix/backends/mixer_oss.h | 58 + kmix/backends/mixer_oss4.cpp | 799 + kmix/backends/mixer_oss4.h | 44 + kmix/backends/mixer_pulse.cpp | 1457 + kmix/backends/mixer_pulse.h | 95 + kmix/backends/mixer_sun.cpp | 495 + kmix/backends/mixer_sun.h | 55 + kmix/cmake/modules/COPYING-CMAKE-SCRIPTS | 22 + kmix/cmake/modules/FindCanberra.cmake | 29 + kmix/colorwidget.ui | 271 + kmix/config.h.cmake | 23 + kmix/core/ControlManager.cpp | 203 + kmix/core/ControlManager.h | 149 + kmix/core/ControlPool.cpp | 83 + kmix/core/ControlPool.h | 56 + kmix/core/GlobalConfig.cpp | 110 + kmix/core/GlobalConfig.h | 126 + kmix/core/MasterControl.cpp | 41 + kmix/core/MasterControl.h | 40 + kmix/core/MediaController.cpp | 88 + kmix/core/MediaController.h | 69 + kmix/core/kmixdevicemanager.cpp | 204 + kmix/core/kmixdevicemanager.h | 55 + kmix/core/mixdevice.cpp | 471 + kmix/core/mixdevice.h | 261 + kmix/core/mixdevicecomposite.cpp | 159 + kmix/core/mixdevicecomposite.h | 110 + kmix/core/mixer.cpp | 721 + kmix/core/mixer.h | 213 + kmix/core/mixertoolbox.cpp | 395 + kmix/core/mixertoolbox.h | 67 + kmix/core/mixset.cpp | 107 + kmix/core/mixset.h | 47 + kmix/core/version.h | 25 + kmix/core/volume.cpp | 381 + kmix/core/volume.h | 203 + kmix/dbus/dbuscontrolwrapper.cpp | 145 + kmix/dbus/dbuscontrolwrapper.h | 74 + kmix/dbus/dbusmixerwrapper.cpp | 145 + kmix/dbus/dbusmixerwrapper.h | 66 + kmix/dbus/dbusmixsetwrapper.cpp | 126 + kmix/dbus/dbusmixsetwrapper.h | 60 + kmix/dbus/org.kde.kmix.control.xml | 20 + kmix/dbus/org.kde.kmix.mixer.xml | 16 + kmix/dbus/org.kde.kmix.mixset.xml | 21 + kmix/doc/CMakeLists.txt | 2 + kmix/doc/ControlNames.txt | 84 + kmix/doc/index.docbook | 457 + kmix/doc/kmix-configure-general.png | Bin 0 -> 17571 bytes kmix/doc/kmix-configure-sound-menu.png | Bin 0 -> 14984 bytes kmix/doc/kmix-configure-start.png | Bin 0 -> 12697 bytes kmix/doc/kmix-file.png | Bin 0 -> 31944 bytes kmix/doc/kmix-master.png | Bin 0 -> 30169 bytes kmix/doc/kmix-options.png | Bin 0 -> 37882 bytes kmix/doc/kmix.png | Bin 0 -> 34647 bytes kmix/gui/dialogaddview.cpp | 243 + kmix/gui/dialogaddview.h | 69 + kmix/gui/dialogchoosebackends.cpp | 165 + kmix/gui/dialogchoosebackends.h | 66 + kmix/gui/dialogselectmaster.cpp | 218 + kmix/gui/dialogselectmaster.h | 60 + kmix/gui/dialogviewconfiguration.cpp | 460 + kmix/gui/dialogviewconfiguration.h | 145 + kmix/gui/guiprofile.cpp | 921 + kmix/gui/guiprofile.h | 244 + kmix/gui/kmixdockwidget.cpp | 388 + kmix/gui/kmixdockwidget.h | 80 + kmix/gui/kmixerwidget.cpp | 183 + kmix/gui/kmixerwidget.h | 87 + kmix/gui/kmixprefdlg.cpp | 428 + kmix/gui/kmixprefdlg.h | 129 + kmix/gui/kmixtoolbox.cpp | 91 + kmix/gui/kmixtoolbox.h | 44 + kmix/gui/ksmallslider.cpp | 441 + kmix/gui/ksmallslider.h | 88 + kmix/gui/mdwenum.cpp | 199 + kmix/gui/mdwenum.h | 80 + kmix/gui/mdwmoveaction.cpp | 48 + kmix/gui/mdwmoveaction.h | 46 + kmix/gui/mdwslider.cpp | 1303 + kmix/gui/mdwslider.h | 187 + kmix/gui/mdwswitch.cpp | 232 + kmix/gui/mdwswitch.h | 79 + kmix/gui/mixdevicewidget.cpp | 118 + kmix/gui/mixdevicewidget.h | 97 + kmix/gui/osdwidget.cpp | 220 + kmix/gui/osdwidget.h | 68 + kmix/gui/verticaltext.cpp | 76 + kmix/gui/verticaltext.h | 44 + kmix/gui/viewbase.cpp | 493 + kmix/gui/viewbase.h | 177 + kmix/gui/viewdockareapopup.cpp | 422 + kmix/gui/viewdockareapopup.h | 87 + kmix/gui/viewsliders.cpp | 442 + kmix/gui/viewsliders.h | 63 + kmix/gui/volumeslider.cpp | 30 + kmix/gui/volumeslider.h | 39 + kmix/gui/volumesliderextradata.h | 48 + kmix/kmix.desktop | 112 + kmix/kmix.notifyrc | 156 + kmix/kmix_autostart.desktop | 115 + kmix/kmixctrl_restore.desktop | 54 + kmix/kmixd.desktop | 111 + kmix/kmixui.rc | 26 + kmix/l10n/README | 11 + kmix/l10n/kmix-controls-de.po | 35 + kmix/l10n/kmix-controls-en.po | 116 + kmix/pics/CMakeLists.txt | 28 + kmix/pics/hi128-app-kmix.png | Bin 0 -> 10735 bytes kmix/pics/hi16-app-kmix.png | Bin 0 -> 906 bytes kmix/pics/hi32-app-kmix.png | Bin 0 -> 1865 bytes kmix/pics/hi48-app-kmix.png | Bin 0 -> 3107 bytes kmix/pics/hi64-app-kmix.png | Bin 0 -> 4231 bytes kmix/pics/kmixdocked.png | Bin 0 -> 1193 bytes kmix/pics/kmixdocked_error.png | Bin 0 -> 1165 bytes kmix/pics/kmixdocked_mute.png | Bin 0 -> 598 bytes kmix/pics/mixer-ac97.png | Bin 0 -> 453 bytes kmix/pics/mixer-capture.png | Bin 0 -> 317 bytes kmix/pics/mixer-cd.png | Bin 0 -> 358 bytes kmix/pics/mixer-digital.png | Bin 0 -> 186 bytes kmix/pics/mixer-front.png | Bin 0 -> 308 bytes kmix/pics/mixer-headset.png | Bin 0 -> 260 bytes kmix/pics/mixer-lfe.png | Bin 0 -> 308 bytes kmix/pics/mixer-line.png | Bin 0 -> 295 bytes kmix/pics/mixer-master.png | Bin 0 -> 304 bytes kmix/pics/mixer-microphone.png | Bin 0 -> 321 bytes kmix/pics/mixer-midi.png | Bin 0 -> 303 bytes kmix/pics/mixer-pcm-default.png | Bin 0 -> 311 bytes kmix/pics/mixer-pcm.png | Bin 0 -> 303 bytes kmix/pics/mixer-surround.png | Bin 0 -> 204 bytes kmix/pics/mixer-video.png | Bin 0 -> 158 bytes kmix/plasma/CMakeLists.txt | 1 + kmix/plasma/engine/CMakeLists.txt | 22 + kmix/plasma/engine/mixer.operations | 20 + kmix/plasma/engine/mixerengine.cpp | 410 + kmix/plasma/engine/mixerengine.h | 103 + kmix/plasma/engine/mixerservice.cpp | 70 + kmix/plasma/engine/mixerservice.h | 53 + .../plasma/engine/plasma-engine-mixer.desktop | 54 + kmix/profiles/ALSA.Sound_Fusion_CS46xx.xml | 97 + .../ALSA.TerraTec_DMX6Fire.1.default.xml | 59 + kmix/profiles/ALSA.capture.xml | 13 + kmix/profiles/ALSA.default.xml | 77 + kmix/profiles/ALSA.playback.xml | 13 + kmix/profiles/CMakeLists.txt | 8 + kmix/profiles/OSS.default.xml | 80 + kmix/restore_kmix_volumes.desktop | 57 + kmix/tests/CMakeLists.txt | 45 + kmix/tests/dialogtest.cpp | 72 + kmix/tests/dialogtest.h | 23 + kmix/tests/profiletest-wrong.xml | 98 + kmix/tests/profiletest.cpp | 34 + kmix/tests/profiletest.xml | 90 + kolourpaint/.krazy | 1 + kolourpaint/AUTHORS | 38 + kolourpaint/BUGS | 138 + kolourpaint/CMakeLists.txt | 580 + kolourpaint/COPYING | 139 + kolourpaint/COPYING.DOC | 397 + kolourpaint/COPYING.LIB | 481 + kolourpaint/ChangeLog | 15 + kolourpaint/Messages.sh | 3 + kolourpaint/NEWS | 383 + kolourpaint/README | 105 + kolourpaint/VERSION | 1 + .../effects/kpEffectBalanceCommand.cpp | 61 + .../imagelib/effects/kpEffectBalanceCommand.h | 56 + .../effects/kpEffectBlurSharpenCommand.cpp | 71 + .../effects/kpEffectBlurSharpenCommand.h | 58 + .../imagelib/effects/kpEffectClearCommand.cpp | 113 + .../imagelib/effects/kpEffectClearCommand.h | 62 + .../imagelib/effects/kpEffectCommandBase.cpp | 127 + .../imagelib/effects/kpEffectCommandBase.h | 67 + .../effects/kpEffectEmbossCommand.cpp | 58 + .../imagelib/effects/kpEffectEmbossCommand.h | 53 + .../effects/kpEffectFlattenCommand.cpp | 64 + .../imagelib/effects/kpEffectFlattenCommand.h | 59 + .../effects/kpEffectGrayscaleCommand.cpp | 61 + .../effects/kpEffectGrayscaleCommand.h | 57 + .../imagelib/effects/kpEffectHSVCommand.cpp | 50 + .../imagelib/effects/kpEffectHSVCommand.h | 51 + .../effects/kpEffectInvertCommand.cpp | 78 + .../imagelib/effects/kpEffectInvertCommand.h | 63 + .../effects/kpEffectReduceColorsCommand.cpp | 86 + .../effects/kpEffectReduceColorsCommand.h | 60 + .../effects/kpEffectToneEnhanceCommand.cpp | 56 + .../effects/kpEffectToneEnhanceCommand.h | 53 + .../imagelib/kpDocumentMetaInfoCommand.cpp | 89 + .../imagelib/kpDocumentMetaInfoCommand.h | 59 + .../transforms/kpTransformFlipCommand.cpp | 141 + .../transforms/kpTransformFlipCommand.h | 60 + .../kpTransformResizeScaleCommand.cpp | 486 + .../kpTransformResizeScaleCommand.h | 102 + .../transforms/kpTransformRotateCommand.cpp | 223 + .../transforms/kpTransformRotateCommand.h | 68 + .../transforms/kpTransformSkewCommand.cpp | 199 + .../transforms/kpTransformSkewCommand.h | 66 + kolourpaint/commands/kpCommand.cpp | 85 + kolourpaint/commands/kpCommand.h | 91 + kolourpaint/commands/kpCommandHistory.cpp | 128 + kolourpaint/commands/kpCommandHistory.h | 105 + kolourpaint/commands/kpCommandHistoryBase.cpp | 752 + kolourpaint/commands/kpCommandHistoryBase.h | 155 + kolourpaint/commands/kpCommandSize.cpp | 157 + kolourpaint/commands/kpCommandSize.h | 87 + kolourpaint/commands/kpMacroCommand.cpp | 148 + kolourpaint/commands/kpMacroCommand.h | 69 + kolourpaint/commands/kpNamedCommand.cpp | 45 + kolourpaint/commands/kpNamedCommand.h | 50 + .../commands/tools/flow/kpToolFlowCommand.cpp | 141 + .../commands/tools/flow/kpToolFlowCommand.h | 64 + .../tools/kpToolColorPickerCommand.cpp | 83 + .../commands/tools/kpToolColorPickerCommand.h | 59 + .../commands/tools/kpToolFloodFillCommand.cpp | 170 + .../commands/tools/kpToolFloodFillCommand.h | 68 + .../polygonal/kpToolPolygonalCommand.cpp | 145 + .../tools/polygonal/kpToolPolygonalCommand.h | 68 + .../rectangular/kpToolRectangularCommand.cpp | 134 + .../rectangular/kpToolRectangularCommand.h | 62 + .../kpAbstractSelectionContentCommand.cpp | 69 + .../kpAbstractSelectionContentCommand.h | 71 + ...pToolImageSelectionTransparencyCommand.cpp | 97 + .../kpToolImageSelectionTransparencyCommand.h | 56 + .../kpToolSelectionCreateCommand.cpp | 159 + .../selection/kpToolSelectionCreateCommand.h | 63 + .../kpToolSelectionDestroyCommand.cpp | 185 + .../selection/kpToolSelectionDestroyCommand.h | 61 + .../selection/kpToolSelectionMoveCommand.cpp | 229 + .../selection/kpToolSelectionMoveCommand.h | 74 + ...kpToolSelectionPullFromDocumentCommand.cpp | 144 + .../kpToolSelectionPullFromDocumentCommand.h | 59 + .../kpToolSelectionResizeScaleCommand.cpp | 277 + .../kpToolSelectionResizeScaleCommand.h | 107 + .../text/kpToolTextBackspaceCommand.cpp | 151 + .../text/kpToolTextBackspaceCommand.h | 64 + .../text/kpToolTextChangeStyleCommand.cpp | 99 + .../text/kpToolTextChangeStyleCommand.h | 55 + .../text/kpToolTextDeleteCommand.cpp | 139 + .../selection/text/kpToolTextDeleteCommand.h | 64 + .../selection/text/kpToolTextEnterCommand.cpp | 125 + .../selection/text/kpToolTextEnterCommand.h | 63 + .../text/kpToolTextGiveContentCommand.cpp | 154 + .../text/kpToolTextGiveContentCommand.h | 59 + .../text/kpToolTextInsertCommand.cpp | 108 + .../selection/text/kpToolTextInsertCommand.h | 56 + kolourpaint/cursors/kpCursorLightCross.cpp | 130 + kolourpaint/cursors/kpCursorLightCross.h | 39 + kolourpaint/cursors/kpCursorProvider.cpp | 47 + kolourpaint/cursors/kpCursorProvider.h | 43 + .../imagelib/effects/kpEffectsDialog.cpp | 366 + .../imagelib/effects/kpEffectsDialog.h | 92 + .../imagelib/kpDocumentMetaInfoDialog.cpp | 793 + .../imagelib/kpDocumentMetaInfoDialog.h | 114 + .../transforms/kpTransformPreviewDialog.cpp | 459 + .../transforms/kpTransformPreviewDialog.h | 144 + .../kpTransformResizeScaleDialog.cpp | 817 + .../transforms/kpTransformResizeScaleDialog.h | 124 + .../transforms/kpTransformRotateDialog.cpp | 315 + .../transforms/kpTransformRotateDialog.h | 93 + .../transforms/kpTransformSkewDialog.cpp | 296 + .../transforms/kpTransformSkewDialog.h | 90 + .../dialogs/kpColorSimilarityDialog.cpp | 156 + kolourpaint/dialogs/kpColorSimilarityDialog.h | 68 + .../kpDocumentSaveOptionsPreviewDialog.cpp | 249 + .../kpDocumentSaveOptionsPreviewDialog.h | 83 + kolourpaint/doc/CMakeLists.txt | 2 + kolourpaint/doc/KolourPaint.png | Bin 0 -> 55836 bytes kolourpaint/doc/brush_shapes.png | Bin 0 -> 242 bytes kolourpaint/doc/color_box.png | Bin 0 -> 1694 bytes kolourpaint/doc/eraser_shapes.png | Bin 0 -> 188 bytes kolourpaint/doc/fcc_std_text.png | Bin 0 -> 5756 bytes kolourpaint/doc/fcc_trans_text.png | Bin 0 -> 6789 bytes kolourpaint/doc/fill_color_similarity.png | Bin 0 -> 13479 bytes kolourpaint/doc/fill_style.png | Bin 0 -> 181 bytes kolourpaint/doc/image_balance.png | Bin 0 -> 49719 bytes kolourpaint/doc/image_emboss.png | Bin 0 -> 86303 bytes kolourpaint/doc/image_flatten.png | Bin 0 -> 35714 bytes kolourpaint/doc/image_invert.png | Bin 0 -> 43211 bytes kolourpaint/doc/image_reduce_colors.png | Bin 0 -> 34759 bytes kolourpaint/doc/image_resize_scale.png | Bin 0 -> 42746 bytes kolourpaint/doc/image_rotate.png | Bin 0 -> 43590 bytes kolourpaint/doc/image_skew.png | Bin 0 -> 34922 bytes kolourpaint/doc/image_soften_sharpen.png | Bin 0 -> 90875 bytes kolourpaint/doc/index.docbook | 1529 + kolourpaint/doc/line_width.png | Bin 0 -> 168 bytes kolourpaint/doc/lines_30_45_deg.png | Bin 0 -> 551 bytes kolourpaint/doc/lines_30_deg.png | Bin 0 -> 430 bytes kolourpaint/doc/lines_45_deg.png | Bin 0 -> 368 bytes kolourpaint/doc/rotate_image_30.png | Bin 0 -> 1202 bytes kolourpaint/doc/rotate_selection_30.png | Bin 0 -> 1508 bytes kolourpaint/doc/screenshot_acquiring.png | Bin 0 -> 15560 bytes .../doc/selections_opaque_transparent.png | Bin 0 -> 969 bytes kolourpaint/doc/spraycan_patterns.png | Bin 0 -> 406 bytes kolourpaint/doc/text_zoom_grid.png | Bin 0 -> 216 bytes kolourpaint/doc/tool_brush.png | Bin 0 -> 675 bytes kolourpaint/doc/tool_color_picker.png | Bin 0 -> 634 bytes kolourpaint/doc/tool_color_washer.png | Bin 0 -> 1007 bytes kolourpaint/doc/tool_curve.png | Bin 0 -> 478 bytes kolourpaint/doc/tool_ellipse.png | Bin 0 -> 807 bytes kolourpaint/doc/tool_elliptical_selection.png | Bin 0 -> 886 bytes kolourpaint/doc/tool_eraser.png | Bin 0 -> 798 bytes kolourpaint/doc/tool_flood_fill.png | Bin 0 -> 636 bytes kolourpaint/doc/tool_free_form_selection.png | Bin 0 -> 836 bytes kolourpaint/doc/tool_line.png | Bin 0 -> 390 bytes kolourpaint/doc/tool_pen.png | Bin 0 -> 572 bytes kolourpaint/doc/tool_polygon.png | Bin 0 -> 879 bytes kolourpaint/doc/tool_polyline.png | Bin 0 -> 542 bytes kolourpaint/doc/tool_polystar.png | Bin 0 -> 1308 bytes kolourpaint/doc/tool_rect_selection.png | Bin 0 -> 910 bytes kolourpaint/doc/tool_rectangle.png | Bin 0 -> 548 bytes kolourpaint/doc/tool_rectangles.png | Bin 0 -> 790 bytes kolourpaint/doc/tool_rounded_rectangle.png | Bin 0 -> 723 bytes kolourpaint/doc/tool_selections.png | Bin 0 -> 1562 bytes kolourpaint/doc/tool_spraycan.png | Bin 0 -> 776 bytes kolourpaint/doc/tool_text.png | Bin 0 -> 385 bytes kolourpaint/doc/view_thumbnails.png | Bin 0 -> 107638 bytes kolourpaint/document/kpDocument.cpp | 468 + kolourpaint/document/kpDocument.h | 362 + kolourpaint/document/kpDocumentPrivate.h | 47 + .../document/kpDocumentSaveOptions.cpp | 628 + kolourpaint/document/kpDocumentSaveOptions.h | 150 + kolourpaint/document/kpDocument_Open.cpp | 269 + kolourpaint/document/kpDocument_Save.cpp | 514 + kolourpaint/document/kpDocument_Selection.cpp | 342 + .../commands/kpCommandEnvironment.cpp | 103 + .../commands/kpCommandEnvironment.h | 80 + .../kpTransformDialogEnvironment.cpp | 43 + .../transforms/kpTransformDialogEnvironment.h | 53 + .../document/kpDocumentEnvironment.cpp | 213 + .../document/kpDocumentEnvironment.h | 72 + .../environments/kpEnvironmentBase.cpp | 121 + kolourpaint/environments/kpEnvironmentBase.h | 99 + .../environments/tools/kpToolEnvironment.cpp | 212 + .../environments/tools/kpToolEnvironment.h | 161 + .../selection/kpToolSelectionEnvironment.cpp | 97 + .../selection/kpToolSelectionEnvironment.h | 72 + kolourpaint/gen_cmake_include_dirs | 7 + kolourpaint/gen_cmake_srcs | 7 + .../generic/kpSetOverrideCursorSaver.cpp | 43 + .../generic/kpSetOverrideCursorSaver.h | 107 + kolourpaint/generic/kpWidgetMapper.cpp | 76 + kolourpaint/generic/kpWidgetMapper.h | 48 + .../widgets/kpResizeSignallingLabel.cpp | 68 + .../generic/widgets/kpResizeSignallingLabel.h | 56 + kolourpaint/generic/widgets/kpSubWindow.cpp | 35 + kolourpaint/generic/widgets/kpSubWindow.h | 57 + .../imagelib/effects/kpEffectBalance.cpp | 213 + .../imagelib/effects/kpEffectBalance.h | 53 + .../imagelib/effects/kpEffectBlurSharpen.cpp | 204 + .../imagelib/effects/kpEffectBlurSharpen.h | 57 + .../imagelib/effects/kpEffectEmboss.cpp | 93 + kolourpaint/imagelib/effects/kpEffectEmboss.h | 52 + .../imagelib/effects/kpEffectFlatten.cpp | 64 + .../imagelib/effects/kpEffectFlatten.h | 47 + .../imagelib/effects/kpEffectGrayscale.cpp | 74 + .../imagelib/effects/kpEffectGrayscale.h | 47 + kolourpaint/imagelib/effects/kpEffectHSV.cpp | 173 + kolourpaint/imagelib/effects/kpEffectHSV.h | 44 + .../imagelib/effects/kpEffectInvert.cpp | 90 + kolourpaint/imagelib/effects/kpEffectInvert.h | 62 + .../imagelib/effects/kpEffectReduceColors.cpp | 236 + .../imagelib/effects/kpEffectReduceColors.h | 51 + .../imagelib/effects/kpEffectToneEnhance.cpp | 283 + .../imagelib/effects/kpEffectToneEnhance.h | 61 + kolourpaint/imagelib/kpColor.cpp | 310 + kolourpaint/imagelib/kpColor.h | 156 + kolourpaint/imagelib/kpColor_Constants.cpp | 117 + kolourpaint/imagelib/kpDocumentMetaInfo.cpp | 283 + kolourpaint/imagelib/kpDocumentMetaInfo.h | 108 + kolourpaint/imagelib/kpFloodFill.cpp | 413 + kolourpaint/imagelib/kpFloodFill.h | 128 + kolourpaint/imagelib/kpImage.h | 38 + kolourpaint/imagelib/kpPainter.cpp | 600 + kolourpaint/imagelib/kpPainter.h | 188 + .../transforms/kpTransformAutoCrop.cpp | 769 + .../imagelib/transforms/kpTransformAutoCrop.h | 85 + .../imagelib/transforms/kpTransformCrop.cpp | 76 + .../imagelib/transforms/kpTransformCrop.h | 83 + .../transforms/kpTransformCropPrivate.h | 49 + .../kpTransformCrop_ImageSelection.cpp | 269 + .../kpTransformCrop_TextSelection.cpp | 76 + kolourpaint/kolourpaint.appdata.xml | 238 + kolourpaint/kolourpaint.cpp | 119 + kolourpaint/kolourpaint.desktop | 165 + kolourpaint/kolourpaintui.rc | 226 + kolourpaint/kpDefs.h | 152 + kolourpaint/kpThumbnail.cpp | 179 + kolourpaint/kpThumbnail.h | 76 + kolourpaint/kpViewScrollableContainer.cpp | 1181 + kolourpaint/kpViewScrollableContainer.h | 216 + .../image/kpAbstractImageSelection.cpp | 583 + .../image/kpAbstractImageSelection.h | 267 + .../image/kpEllipticalImageSelection.cpp | 183 + .../image/kpEllipticalImageSelection.h | 118 + .../image/kpFreeFormImageSelection.cpp | 389 + .../image/kpFreeFormImageSelection.h | 159 + .../image/kpImageSelectionTransparency.cpp | 208 + .../image/kpImageSelectionTransparency.h | 82 + .../image/kpRectangularImageSelection.cpp | 149 + .../image/kpRectangularImageSelection.h | 119 + .../layers/selections/kpAbstractSelection.cpp | 312 + .../layers/selections/kpAbstractSelection.h | 278 + .../layers/selections/kpSelectionDrag.cpp | 154 + .../layers/selections/kpSelectionDrag.h | 52 + .../layers/selections/kpSelectionFactory.cpp | 93 + .../layers/selections/kpSelectionFactory.h | 48 + .../layers/selections/text/kpPreeditText.cpp | 142 + .../layers/selections/text/kpPreeditText.h | 65 + .../selections/text/kpTextSelection.cpp | 346 + .../layers/selections/text/kpTextSelection.h | 294 + .../selections/text/kpTextSelectionPrivate.h | 47 + .../text/kpTextSelection_Cursor.cpp | 126 + .../selections/text/kpTextSelection_Paint.cpp | 269 + .../layers/selections/text/kpTextStyle.cpp | 269 + .../layers/selections/text/kpTextStyle.h | 107 + kolourpaint/layers/tempImage/kpTempImage.cpp | 217 + kolourpaint/layers/tempImage/kpTempImage.h | 110 + .../lgpl/generic/kpColorCollection.cpp | 537 + kolourpaint/lgpl/generic/kpColorCollection.h | 264 + kolourpaint/lgpl/generic/kpUrlFormatter.cpp | 58 + kolourpaint/lgpl/generic/kpUrlFormatter.h | 57 + .../lgpl/generic/widgets/kpColorCellsBase.cpp | 563 + .../lgpl/generic/widgets/kpColorCellsBase.h | 185 + kolourpaint/lgpl/kolourpaint_lgpl_export.h | 40 + kolourpaint/mainWindow/kpMainWindow.cpp | 975 + kolourpaint/mainWindow/kpMainWindow.h | 698 + kolourpaint/mainWindow/kpMainWindowPrivate.h | 428 + .../mainWindow/kpMainWindow_Colors.cpp | 454 + kolourpaint/mainWindow/kpMainWindow_Edit.cpp | 921 + kolourpaint/mainWindow/kpMainWindow_File.cpp | 1498 + kolourpaint/mainWindow/kpMainWindow_Image.cpp | 595 + .../mainWindow/kpMainWindow_Settings.cpp | 145 + .../mainWindow/kpMainWindow_StatusBar.cpp | 438 + kolourpaint/mainWindow/kpMainWindow_Text.cpp | 435 + kolourpaint/mainWindow/kpMainWindow_Tools.cpp | 807 + kolourpaint/mainWindow/kpMainWindow_View.cpp | 170 + .../kpMainWindow_View_Thumbnail.cpp | 477 + .../mainWindow/kpMainWindow_View_Zoom.cpp | 708 + .../patches/checkerboard-faster-render.diff | 147 + .../patches/linear-sharpen-effect.diff | 150 + kolourpaint/pics/CMakeLists.txt | 11 + kolourpaint/pics/action/CMakeLists.txt | 9 + .../pics/action/hi16-action-tool_brush.png | Bin 0 -> 675 bytes .../action/hi16-action-tool_color_eraser.png | Bin 0 -> 1007 bytes .../action/hi16-action-tool_color_picker.png | Bin 0 -> 634 bytes .../pics/action/hi16-action-tool_curve.png | Bin 0 -> 478 bytes .../pics/action/hi16-action-tool_ellipse.png | Bin 0 -> 807 bytes .../hi16-action-tool_elliptical_selection.png | Bin 0 -> 886 bytes .../pics/action/hi16-action-tool_eraser.png | Bin 0 -> 798 bytes .../action/hi16-action-tool_flood_fill.png | Bin 0 -> 636 bytes .../hi16-action-tool_free_form_selection.png | Bin 0 -> 836 bytes .../pics/action/hi16-action-tool_line.png | Bin 0 -> 390 bytes .../pics/action/hi16-action-tool_pen.png | Bin 0 -> 572 bytes .../pics/action/hi16-action-tool_polygon.png | Bin 0 -> 879 bytes .../pics/action/hi16-action-tool_polyline.png | Bin 0 -> 542 bytes .../hi16-action-tool_rect_selection.png | Bin 0 -> 910 bytes .../action/hi16-action-tool_rectangle.png | Bin 0 -> 548 bytes .../hi16-action-tool_rounded_rectangle.png | Bin 0 -> 723 bytes .../pics/action/hi16-action-tool_spraycan.png | Bin 0 -> 776 bytes .../pics/action/hi16-action-tool_text.png | Bin 0 -> 385 bytes .../pics/action/hi22-action-tool_brush.png | Bin 0 -> 965 bytes .../action/hi22-action-tool_color_eraser.png | Bin 0 -> 1493 bytes .../action/hi22-action-tool_color_picker.png | Bin 0 -> 941 bytes .../pics/action/hi22-action-tool_curve.png | Bin 0 -> 639 bytes .../pics/action/hi22-action-tool_ellipse.png | Bin 0 -> 1155 bytes .../hi22-action-tool_elliptical_selection.png | Bin 0 -> 1316 bytes .../pics/action/hi22-action-tool_eraser.png | Bin 0 -> 1218 bytes .../action/hi22-action-tool_flood_fill.png | Bin 0 -> 836 bytes .../hi22-action-tool_free_form_selection.png | Bin 0 -> 1241 bytes .../pics/action/hi22-action-tool_line.png | Bin 0 -> 395 bytes .../pics/action/hi22-action-tool_pen.png | Bin 0 -> 827 bytes .../pics/action/hi22-action-tool_polygon.png | Bin 0 -> 1303 bytes .../pics/action/hi22-action-tool_polyline.png | Bin 0 -> 772 bytes .../hi22-action-tool_rect_selection.png | Bin 0 -> 1263 bytes .../action/hi22-action-tool_rectangle.png | Bin 0 -> 795 bytes .../hi22-action-tool_rounded_rectangle.png | Bin 0 -> 1096 bytes .../pics/action/hi22-action-tool_spraycan.png | Bin 0 -> 1149 bytes .../pics/action/hi22-action-tool_text.png | Bin 0 -> 728 bytes .../pics/action/hi32-action-tool_brush.png | Bin 0 -> 1557 bytes .../action/hi32-action-tool_color_eraser.png | Bin 0 -> 2538 bytes .../action/hi32-action-tool_color_picker.png | Bin 0 -> 1422 bytes .../pics/action/hi32-action-tool_curve.png | Bin 0 -> 907 bytes .../pics/action/hi32-action-tool_ellipse.png | Bin 0 -> 1726 bytes .../hi32-action-tool_elliptical_selection.png | Bin 0 -> 1855 bytes .../pics/action/hi32-action-tool_eraser.png | Bin 0 -> 1939 bytes .../action/hi32-action-tool_flood_fill.png | Bin 0 -> 1229 bytes .../hi32-action-tool_free_form_selection.png | Bin 0 -> 1821 bytes .../pics/action/hi32-action-tool_line.png | Bin 0 -> 602 bytes .../pics/action/hi32-action-tool_pen.png | Bin 0 -> 1192 bytes .../pics/action/hi32-action-tool_polygon.png | Bin 0 -> 1930 bytes .../pics/action/hi32-action-tool_polyline.png | Bin 0 -> 1061 bytes .../hi32-action-tool_rect_selection.png | Bin 0 -> 2055 bytes .../action/hi32-action-tool_rectangle.png | Bin 0 -> 942 bytes .../hi32-action-tool_rounded_rectangle.png | Bin 0 -> 1514 bytes .../pics/action/hi32-action-tool_spraycan.png | Bin 0 -> 1912 bytes .../pics/action/hi32-action-tool_text.png | Bin 0 -> 1047 bytes .../pics/action/hi48-action-tool_brush.png | Bin 0 -> 2662 bytes .../action/hi48-action-tool_color_eraser.png | Bin 0 -> 4381 bytes .../action/hi48-action-tool_color_picker.png | Bin 0 -> 2422 bytes .../pics/action/hi48-action-tool_curve.png | Bin 0 -> 1310 bytes .../pics/action/hi48-action-tool_ellipse.png | Bin 0 -> 2590 bytes .../hi48-action-tool_elliptical_selection.png | Bin 0 -> 2964 bytes .../pics/action/hi48-action-tool_eraser.png | Bin 0 -> 3381 bytes .../action/hi48-action-tool_flood_fill.png | Bin 0 -> 1527 bytes .../hi48-action-tool_free_form_selection.png | Bin 0 -> 2940 bytes .../pics/action/hi48-action-tool_line.png | Bin 0 -> 802 bytes .../pics/action/hi48-action-tool_pen.png | Bin 0 -> 2005 bytes .../pics/action/hi48-action-tool_polygon.png | Bin 0 -> 2967 bytes .../pics/action/hi48-action-tool_polyline.png | Bin 0 -> 1394 bytes .../hi48-action-tool_rect_selection.png | Bin 0 -> 3509 bytes .../action/hi48-action-tool_rectangle.png | Bin 0 -> 1378 bytes .../hi48-action-tool_rounded_rectangle.png | Bin 0 -> 2237 bytes .../pics/action/hi48-action-tool_spraycan.png | Bin 0 -> 3253 bytes .../pics/action/hi48-action-tool_text.png | Bin 0 -> 1263 bytes .../pics/action/hisc-action-tool_brush.svgz | Bin 0 -> 8136 bytes .../action/hisc-action-tool_color_eraser.svgz | Bin 0 -> 7818 bytes .../action/hisc-action-tool_color_picker.svgz | Bin 0 -> 10939 bytes .../pics/action/hisc-action-tool_curve.svgz | Bin 0 -> 2380 bytes .../pics/action/hisc-action-tool_ellipse.svgz | Bin 0 -> 1994 bytes ...hisc-action-tool_elliptical_selection.svgz | Bin 0 -> 3597 bytes .../pics/action/hisc-action-tool_eraser.svgz | Bin 0 -> 6959 bytes .../action/hisc-action-tool_flood_fill.svgz | Bin 0 -> 2903 bytes .../hisc-action-tool_free_form_selection.svgz | Bin 0 -> 4661 bytes .../pics/action/hisc-action-tool_line.svgz | Bin 0 -> 1333 bytes .../pics/action/hisc-action-tool_pen.svgz | Bin 0 -> 4924 bytes .../pics/action/hisc-action-tool_polygon.svgz | Bin 0 -> 2276 bytes .../action/hisc-action-tool_polyline.svgz | Bin 0 -> 2098 bytes .../hisc-action-tool_rect_selection.svgz | Bin 0 -> 3819 bytes .../action/hisc-action-tool_rectangle.svgz | Bin 0 -> 1960 bytes .../hisc-action-tool_rounded_rectangle.svgz | Bin 0 -> 2220 bytes .../action/hisc-action-tool_spraycan.svgz | Bin 0 -> 8587 bytes .../pics/action/hisc-action-tool_text.svgz | Bin 0 -> 2666 bytes kolourpaint/pics/app/CMakeLists.txt | 9 + kolourpaint/pics/app/hi16-app-kolourpaint.png | Bin 0 -> 812 bytes kolourpaint/pics/app/hi22-app-kolourpaint.png | Bin 0 -> 1259 bytes kolourpaint/pics/app/hi32-app-kolourpaint.png | Bin 0 -> 2139 bytes kolourpaint/pics/app/hi48-app-kolourpaint.png | Bin 0 -> 3975 bytes .../pics/app/hisc-app-kolourpaint.svgz | Bin 0 -> 12874 bytes kolourpaint/pics/custom/CMakeLists.txt | 14 + .../pics/custom/color_transparent_26x26.png | Bin 0 -> 972 bytes .../pics/custom/colorbutton_swap_16x16.png | Bin 0 -> 119 bytes .../custom/image_rotate_anticlockwise.png | Bin 0 -> 203 bytes .../pics/custom/image_rotate_clockwise.png | Bin 0 -> 196 bytes .../pics/custom/image_skew_horizontal.png | Bin 0 -> 179 bytes .../pics/custom/image_skew_vertical.png | Bin 0 -> 237 bytes kolourpaint/pics/custom/option_opaque.png | Bin 0 -> 717 bytes .../pics/custom/option_transparent.png | Bin 0 -> 868 bytes kolourpaint/pics/custom/resize.png | Bin 0 -> 4009 bytes kolourpaint/pics/custom/scale.png | Bin 0 -> 1724 bytes kolourpaint/pics/custom/smooth_scale.png | Bin 0 -> 5097 bytes .../pics/custom/tool_spraycan_17x17.png | Bin 0 -> 130 bytes .../pics/custom/tool_spraycan_29x29.png | Bin 0 -> 210 bytes kolourpaint/pics/custom/tool_spraycan_9x9.png | Bin 0 -> 92 bytes kolourpaint/pixmapfx/kpPixmapFX.h | 281 + .../pixmapfx/kpPixmapFX_DrawShapes.cpp | 492 + .../pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp | 143 + .../pixmapfx/kpPixmapFX_Transforms.cpp | 674 + kolourpaint/stamp | 4 + kolourpaint/tests/45deg_line.png | Bin 0 -> 94 bytes kolourpaint/tests/4x4-transparent.png | Bin 0 -> 98 bytes kolourpaint/tests/5x5.png | Bin 0 -> 99 bytes kolourpaint/tests/depth1.bmp | Bin 0 -> 462 bytes kolourpaint/tests/dither.png | Bin 0 -> 859 bytes kolourpaint/tests/freeform-selection.png | Bin 0 -> 227 bytes kolourpaint/tests/kolourcircles.png | Bin 0 -> 4889 bytes kolourpaint/tests/paste_in_new_window.png | Bin 0 -> 248 bytes kolourpaint/tests/paste_in_new_window.txt | 15 + kolourpaint/tests/rotate.png | Bin 0 -> 79 bytes kolourpaint/tests/selections.txt | 12 + kolourpaint/tests/small16x16.png | Bin 0 -> 120 bytes kolourpaint/tests/tool_fill_xlimit.png | Bin 0 -> 119 bytes .../transforms-rotate-me-90-clockwise.png | Bin 0 -> 222 bytes kolourpaint/tests/transforms.png | Bin 0 -> 213 bytes kolourpaint/tests/transparent.png | Bin 0 -> 149 bytes kolourpaint/tests/transparent_selection.png | Bin 0 -> 226 bytes kolourpaint/tools/flow/kpToolBrush.cpp | 57 + kolourpaint/tools/flow/kpToolBrush.h | 50 + kolourpaint/tools/flow/kpToolColorEraser.cpp | 163 + kolourpaint/tools/flow/kpToolColorEraser.h | 65 + kolourpaint/tools/flow/kpToolEraser.cpp | 80 + kolourpaint/tools/flow/kpToolEraser.h | 55 + kolourpaint/tools/flow/kpToolFlowBase.cpp | 492 + kolourpaint/tools/flow/kpToolFlowBase.h | 120 + .../tools/flow/kpToolFlowPixmapBase.cpp | 87 + kolourpaint/tools/flow/kpToolFlowPixmapBase.h | 58 + kolourpaint/tools/flow/kpToolPen.cpp | 96 + kolourpaint/tools/flow/kpToolPen.h | 54 + kolourpaint/tools/flow/kpToolSpraycan.cpp | 264 + kolourpaint/tools/flow/kpToolSpraycan.h | 90 + kolourpaint/tools/kpTool.cpp | 264 + kolourpaint/tools/kpTool.h | 468 + kolourpaint/tools/kpToolAction.cpp | 70 + kolourpaint/tools/kpToolAction.h | 53 + kolourpaint/tools/kpToolColorPicker.cpp | 145 + kolourpaint/tools/kpToolColorPicker.h | 71 + kolourpaint/tools/kpToolFloodFill.cpp | 170 + kolourpaint/tools/kpToolFloodFill.h | 60 + kolourpaint/tools/kpToolPrivate.h | 83 + kolourpaint/tools/kpToolZoom.cpp | 252 + kolourpaint/tools/kpToolZoom.h | 66 + kolourpaint/tools/kpTool_Drawing.cpp | 418 + kolourpaint/tools/kpTool_KeyboardEvents.cpp | 412 + kolourpaint/tools/kpTool_MouseEvents.cpp | 337 + kolourpaint/tools/kpTool_OtherEvents.cpp | 166 + .../tools/kpTool_UserNotifications.cpp | 164 + kolourpaint/tools/kpTool_Utilities.cpp | 291 + kolourpaint/tools/polygonal/kpToolCurve.cpp | 182 + kolourpaint/tools/polygonal/kpToolCurve.h | 54 + kolourpaint/tools/polygonal/kpToolLine.cpp | 93 + kolourpaint/tools/polygonal/kpToolLine.h | 52 + kolourpaint/tools/polygonal/kpToolPolygon.cpp | 162 + kolourpaint/tools/polygonal/kpToolPolygon.h | 62 + .../tools/polygonal/kpToolPolygonalBase.cpp | 501 + .../tools/polygonal/kpToolPolygonalBase.h | 209 + .../tools/polygonal/kpToolPolyline.cpp | 114 + kolourpaint/tools/polygonal/kpToolPolyline.h | 59 + .../tools/rectangular/kpToolEllipse.cpp | 47 + kolourpaint/tools/rectangular/kpToolEllipse.h | 45 + .../tools/rectangular/kpToolRectangle.cpp | 48 + .../tools/rectangular/kpToolRectangle.h | 45 + .../rectangular/kpToolRectangularBase.cpp | 394 + .../tools/rectangular/kpToolRectangularBase.h | 98 + .../rectangular/kpToolRoundedRectangle.cpp | 46 + .../rectangular/kpToolRoundedRectangle.h | 45 + .../image/kpAbstractImageSelectionTool.cpp | 103 + .../image/kpAbstractImageSelectionTool.h | 106 + ...bstractImageSelectionTool_Transparency.cpp | 212 + .../image/kpToolEllipticalSelection.cpp | 82 + .../image/kpToolEllipticalSelection.h | 50 + .../image/kpToolFreeFormSelection.cpp | 141 + .../selection/image/kpToolFreeFormSelection.h | 50 + .../selection/image/kpToolRectSelection.cpp | 85 + .../selection/image/kpToolRectSelection.h | 50 + .../selection/kpAbstractSelectionTool.cpp | 631 + .../tools/selection/kpAbstractSelectionTool.h | 600 + .../kpAbstractSelectionToolPrivate.h | 92 + .../kpAbstractSelectionTool_Create.cpp | 305 + ...kpAbstractSelectionTool_KeyboardEvents.cpp | 103 + .../kpAbstractSelectionTool_Move.cpp | 401 + .../kpAbstractSelectionTool_ResizeScale.cpp | 454 + .../tools/selection/text/kpToolText.cpp | 221 + kolourpaint/tools/selection/text/kpToolText.h | 725 + .../tools/selection/text/kpToolTextPrivate.h | 51 + .../selection/text/kpToolText_Commands.cpp | 126 + .../selection/text/kpToolText_Create.cpp | 288 + .../selection/text/kpToolText_CursorCalc.cpp | 215 + .../text/kpToolText_InputMethodEvents.cpp | 96 + .../text/kpToolText_KeyboardEvents.cpp | 215 + ...oolText_KeyboardEvents_HandleArrowKeys.cpp | 216 + ...olText_KeyboardEvents_HandleTypingKeys.cpp | 198 + .../tools/selection/text/kpToolText_Move.cpp | 63 + .../selection/text/kpToolText_ResizeScale.cpp | 55 + .../selection/text/kpToolText_SelectText.cpp | 136 + .../selection/text/kpToolText_TextStyle.cpp | 331 + kolourpaint/views/kpThumbnailView.cpp | 99 + kolourpaint/views/kpThumbnailView.h | 90 + kolourpaint/views/kpUnzoomedThumbnailView.cpp | 218 + kolourpaint/views/kpUnzoomedThumbnailView.h | 106 + kolourpaint/views/kpView.cpp | 672 + kolourpaint/views/kpView.h | 599 + kolourpaint/views/kpViewPrivate.h | 78 + kolourpaint/views/kpView_Events.cpp | 272 + kolourpaint/views/kpView_Paint.cpp | 628 + kolourpaint/views/kpView_Selections.cpp | 362 + kolourpaint/views/kpZoomedThumbnailView.cpp | 140 + kolourpaint/views/kpZoomedThumbnailView.h | 95 + kolourpaint/views/kpZoomedView.cpp | 103 + kolourpaint/views/kpZoomedView.h | 96 + kolourpaint/views/manager/kpViewManager.cpp | 365 + kolourpaint/views/manager/kpViewManager.h | 256 + .../views/manager/kpViewManagerPrivate.h | 86 + .../manager/kpViewManager_TextCursor.cpp | 238 + .../manager/kpViewManager_ViewUpdates.cpp | 268 + .../kpColorSimilarityCubeRenderer.cpp | 234 + .../kpColorSimilarityCubeRenderer.h | 54 + .../kpColorSimilarityFrame.cpp | 78 + .../colorSimilarity/kpColorSimilarityFrame.h | 52 + .../kpColorSimilarityHolder.cpp | 189 + .../colorSimilarity/kpColorSimilarityHolder.h | 65 + .../kpColorSimilarityToolBarItem.cpp | 282 + .../kpColorSimilarityToolBarItem.h | 106 + .../effects/kpEffectBalanceWidget.cpp | 325 + .../imagelib/effects/kpEffectBalanceWidget.h | 85 + .../effects/kpEffectBlurSharpenWidget.cpp | 185 + .../effects/kpEffectBlurSharpenWidget.h | 72 + .../imagelib/effects/kpEffectEmbossWidget.cpp | 131 + .../imagelib/effects/kpEffectEmbossWidget.h | 66 + .../effects/kpEffectFlattenWidget.cpp | 187 + .../imagelib/effects/kpEffectFlattenWidget.h | 80 + .../imagelib/effects/kpEffectHSVWidget.cpp | 128 + .../imagelib/effects/kpEffectHSVWidget.h | 62 + .../imagelib/effects/kpEffectInvertWidget.cpp | 211 + .../imagelib/effects/kpEffectInvertWidget.h | 77 + .../effects/kpEffectReduceColorsWidget.cpp | 177 + .../effects/kpEffectReduceColorsWidget.h | 73 + .../effects/kpEffectToneEnhanceWidget.cpp | 144 + .../effects/kpEffectToneEnhanceWidget.h | 68 + .../imagelib/effects/kpEffectWidgetBase.cpp | 66 + .../imagelib/effects/kpEffectWidgetBase.h | 77 + kolourpaint/widgets/kpColorCells.cpp | 597 + kolourpaint/widgets/kpColorCells.h | 169 + kolourpaint/widgets/kpColorPalette.cpp | 125 + kolourpaint/widgets/kpColorPalette.h | 63 + .../widgets/kpDefaultColorCollection.cpp | 71 + .../widgets/kpDefaultColorCollection.h | 50 + .../widgets/kpDocumentSaveOptionsWidget.cpp | 769 + .../widgets/kpDocumentSaveOptionsWidget.h | 157 + kolourpaint/widgets/kpDualColorButton.cpp | 464 + kolourpaint/widgets/kpDualColorButton.h | 96 + kolourpaint/widgets/kpPrintDialogPage.cpp | 101 + kolourpaint/widgets/kpPrintDialogPage.h | 52 + .../widgets/kpTransparentColorCell.cpp | 129 + kolourpaint/widgets/kpTransparentColorCell.h | 66 + .../widgets/toolbars/kpColorToolBar.cpp | 329 + kolourpaint/widgets/toolbars/kpColorToolBar.h | 124 + .../widgets/toolbars/kpToolToolBar.cpp | 459 + kolourpaint/widgets/toolbars/kpToolToolBar.h | 123 + .../toolbars/options/kpToolWidgetBase.cpp | 723 + .../toolbars/options/kpToolWidgetBase.h | 114 + .../toolbars/options/kpToolWidgetBrush.cpp | 299 + .../toolbars/options/kpToolWidgetBrush.h | 81 + .../options/kpToolWidgetEraserSize.cpp | 188 + .../toolbars/options/kpToolWidgetEraserSize.h | 82 + .../options/kpToolWidgetFillStyle.cpp | 182 + .../toolbars/options/kpToolWidgetFillStyle.h | 79 + .../options/kpToolWidgetLineWidth.cpp | 90 + .../toolbars/options/kpToolWidgetLineWidth.h | 54 + .../kpToolWidgetOpaqueOrTransparent.cpp | 104 + .../options/kpToolWidgetOpaqueOrTransparent.h | 57 + .../options/kpToolWidgetSpraycanSize.cpp | 120 + .../options/kpToolWidgetSpraycanSize.h | 54 + ksnapshot/CMakeLists.txt | 95 + ksnapshot/COPYING | 339 + ksnapshot/COPYING.DOC | 397 + ksnapshot/COPYING.LIB | 481 + ksnapshot/Messages.sh | 3 + ksnapshot/config-ksnapshot.h.cmake | 8 + ksnapshot/doc/CMakeLists.txt | 4 + ksnapshot/doc/index.docbook | 687 + ksnapshot/doc/preview.png | Bin 0 -> 59204 bytes ksnapshot/doc/send-to-menu.png | Bin 0 -> 41025 bytes ksnapshot/doc/window.png | Bin 0 -> 94932 bytes ksnapshot/expblur.cpp | 156 + ksnapshot/freeregiongrabber.cpp | 317 + ksnapshot/freeregiongrabber.h | 71 + ksnapshot/hi16-app-ksnapshot.png | Bin 0 -> 846 bytes ksnapshot/hi22-app-ksnapshot.png | Bin 0 -> 1282 bytes ksnapshot/hi32-app-ksnapshot.png | Bin 0 -> 1935 bytes ksnapshot/hi48-app-ksnapshot.png | Bin 0 -> 3217 bytes ksnapshot/hisc-app-ksnapshot.svgz | Bin 0 -> 5050 bytes ksnapshot/kbackgroundsnapshot.cpp | 202 + ksnapshot/kbackgroundsnapshot.h | 51 + ksnapshot/kipiimagecollectionselector.cpp | 87 + ksnapshot/kipiimagecollectionselector.h | 45 + ksnapshot/kipiinterface.cpp | 94 + ksnapshot/kipiinterface.h | 59 + ksnapshot/ksnapshot.cpp | 939 + ksnapshot/ksnapshot.desktop | 161 + ksnapshot/ksnapshot.h | 153 + ksnapshot/ksnapshot_options.h | 38 + ksnapshot/ksnapshotimagecollectionshared.cpp | 54 + ksnapshot/ksnapshotimagecollectionshared.h | 49 + ksnapshot/ksnapshotinfoshared.cpp | 58 + ksnapshot/ksnapshotinfoshared.h | 41 + ksnapshot/ksnapshotobject.cpp | 186 + ksnapshot/ksnapshotobject.h | 63 + ksnapshot/ksnapshotpreview.cpp | 100 + ksnapshot/ksnapshotpreview.h | 48 + ksnapshot/ksnapshotwidget.ui | 270 + ksnapshot/main.cpp | 91 + ksnapshot/org.kde.ksnapshot.xml | 40 + ksnapshot/regiongrabber.cpp | 421 + ksnapshot/regiongrabber.h | 85 + ksnapshot/snapshottimer.cpp | 117 + ksnapshot/snapshottimer.h | 54 + ksnapshot/windowgrabber.cpp | 581 + ksnapshot/windowgrabber.h | 63 + ksystemlog/.cproject | 209 + ksystemlog/.project | 78 + .../org.eclipse.ltk.core.refactoring.prefs | 3 + ksystemlog/CMakeLists.txt | 71 + ksystemlog/COPYING | 339 + ksystemlog/Changelog | 217 + ksystemlog/Mainpage.dox | 5 + ksystemlog/Messages.sh | 4 + ksystemlog/build-package.sh | 35 + ksystemlog/doc/CMakeLists.txt | 3 + ksystemlog/doc/filter-process.png | Bin 0 -> 101472 bytes ksystemlog/doc/first-opening.png | Bin 0 -> 98133 bytes ksystemlog/doc/index.docbook | 317 + ksystemlog/doc/main-screen.png | Bin 0 -> 88483 bytes ksystemlog/src/CMakeLists.txt | 74 + ksystemlog/src/config/CMakeLists.txt | 16 + ksystemlog/src/config/dummyConfig.cpp | 20 + ksystemlog/src/config/ksystemlog.kcfg | 78 + ksystemlog/src/config/ksystemlogConfig.kcfgc | 4 + ksystemlog/src/configurationDialog.cpp | 200 + ksystemlog/src/configurationDialog.h | 65 + ksystemlog/src/detailDialog.cpp | 135 + ksystemlog/src/detailDialog.h | 59 + ksystemlog/src/detailDialogBase.ui | 190 + ksystemlog/src/generalConfigurationWidget.cpp | 149 + ksystemlog/src/generalConfigurationWidget.h | 58 + .../src/generalConfigurationWidgetBase.ui | 203 + ksystemlog/src/ksystemlog.desktop | 174 + ksystemlog/src/ksystemlog.lsm | 16 + ksystemlog/src/ksystemlogui.rc | 70 + ksystemlog/src/lib/CMakeLists.txt | 55 + ksystemlog/src/lib/analyzer.cpp | 256 + ksystemlog/src/lib/analyzer.h | 133 + ksystemlog/src/lib/defaults.h | 77 + ksystemlog/src/lib/globals.cpp | 225 + ksystemlog/src/lib/globals.h | 83 + ksystemlog/src/lib/kioLogFileReader.cpp | 172 + ksystemlog/src/lib/kioLogFileReader.h | 69 + ksystemlog/src/lib/levelPrintPage.cpp | 131 + ksystemlog/src/lib/levelPrintPage.h | 52 + ksystemlog/src/lib/loadingBar.cpp | 128 + ksystemlog/src/lib/loadingBar.h | 63 + ksystemlog/src/lib/localLogFileReader.cpp | 239 + ksystemlog/src/lib/localLogFileReader.h | 68 + ksystemlog/src/lib/logFile.cpp | 94 + ksystemlog/src/lib/logFile.h | 62 + ksystemlog/src/lib/logFileReader.cpp | 54 + ksystemlog/src/lib/logFileReader.h | 63 + ksystemlog/src/lib/logFileReaderPrivate.h | 37 + ksystemlog/src/lib/logLevel.cpp | 74 + ksystemlog/src/lib/logLevel.h | 52 + ksystemlog/src/lib/logLine.cpp | 218 + ksystemlog/src/lib/logLine.h | 92 + ksystemlog/src/lib/logManager.cpp | 218 + ksystemlog/src/lib/logManager.h | 91 + ksystemlog/src/lib/logMode.cpp | 82 + ksystemlog/src/lib/logMode.h | 112 + ksystemlog/src/lib/logModeAction.cpp | 56 + ksystemlog/src/lib/logModeAction.h | 65 + ksystemlog/src/lib/logModeConfiguration.cpp | 256 + ksystemlog/src/lib/logModeConfiguration.h | 52 + .../src/lib/logModeConfigurationWidget.cpp | 63 + .../src/lib/logModeConfigurationWidget.h | 58 + ksystemlog/src/lib/logModeFactory.cpp | 36 + ksystemlog/src/lib/logModeFactory.h | 51 + ksystemlog/src/lib/logModeItemBuilder.cpp | 105 + ksystemlog/src/lib/logModeItemBuilder.h | 51 + ksystemlog/src/lib/logViewColumn.cpp | 90 + ksystemlog/src/lib/logViewColumn.h | 59 + ksystemlog/src/lib/logViewColumns.cpp | 127 + ksystemlog/src/lib/logViewColumns.h | 64 + ksystemlog/src/lib/logViewExport.cpp | 274 + ksystemlog/src/lib/logViewExport.h | 61 + ksystemlog/src/lib/logViewFilterWidget.cpp | 167 + ksystemlog/src/lib/logViewFilterWidget.h | 87 + ksystemlog/src/lib/logViewModel.cpp | 255 + ksystemlog/src/lib/logViewModel.h | 95 + ksystemlog/src/lib/logViewSearchWidget.cpp | 336 + ksystemlog/src/lib/logViewSearchWidget.h | 86 + ksystemlog/src/lib/logViewSearchWidgetBase.ui | 129 + ksystemlog/src/lib/logViewWidget.cpp | 306 + ksystemlog/src/lib/logViewWidget.h | 87 + ksystemlog/src/lib/logViewWidgetItem.cpp | 78 + ksystemlog/src/lib/logViewWidgetItem.h | 57 + ksystemlog/src/lib/logging.h | 89 + ksystemlog/src/lib/multipleActions.cpp | 51 + ksystemlog/src/lib/multipleActions.h | 68 + .../src/lib/processOutputLogFileReader.cpp | 238 + .../src/lib/processOutputLogFileReader.h | 73 + ksystemlog/src/lib/simpleAction.cpp | 57 + ksystemlog/src/lib/simpleAction.h | 54 + ksystemlog/src/lib/view.cpp | 230 + ksystemlog/src/lib/view.h | 92 + ksystemlog/src/logModePluginsLoader.cpp | 96 + ksystemlog/src/logModePluginsLoader.h | 42 + ksystemlog/src/loggerDialog.cpp | 262 + ksystemlog/src/loggerDialog.h | 57 + ksystemlog/src/loggerDialogBase.ui | 381 + ksystemlog/src/main.cpp | 92 + ksystemlog/src/mainWindow.cpp | 814 + ksystemlog/src/mainWindow.h | 142 + ksystemlog/src/modes/acpid/CMakeLists.txt | 35 + ksystemlog/src/modes/acpid/acpidAnalyzer.cpp | 22 + ksystemlog/src/modes/acpid/acpidAnalyzer.h | 140 + .../src/modes/acpid/acpidConfiguration.cpp | 22 + .../src/modes/acpid/acpidConfiguration.h | 74 + .../modes/acpid/acpidConfigurationWidget.cpp | 22 + .../modes/acpid/acpidConfigurationWidget.h | 101 + ksystemlog/src/modes/acpid/acpidFactory.cpp | 48 + ksystemlog/src/modes/acpid/acpidFactory.h | 45 + .../src/modes/acpid/acpidItemBuilder.cpp | 22 + ksystemlog/src/modes/acpid/acpidItemBuilder.h | 68 + ksystemlog/src/modes/acpid/acpidLogMode.cpp | 63 + ksystemlog/src/modes/acpid/acpidLogMode.h | 57 + ksystemlog/src/modes/apache/CMakeLists.txt | 38 + .../src/modes/apache/apacheAccessAnalyzer.cpp | 22 + .../src/modes/apache/apacheAccessAnalyzer.h | 149 + .../modes/apache/apacheAccessItemBuilder.cpp | 22 + .../modes/apache/apacheAccessItemBuilder.h | 72 + .../src/modes/apache/apacheAccessLogMode.cpp | 64 + .../src/modes/apache/apacheAccessLogMode.h | 61 + .../src/modes/apache/apacheAnalyzer.cpp | 22 + ksystemlog/src/modes/apache/apacheAnalyzer.h | 183 + .../src/modes/apache/apacheConfiguration.cpp | 22 + .../src/modes/apache/apacheConfiguration.h | 87 + .../apache/apacheConfigurationWidget.cpp | 22 + .../modes/apache/apacheConfigurationWidget.h | 111 + ksystemlog/src/modes/apache/apacheFactory.cpp | 62 + ksystemlog/src/modes/apache/apacheFactory.h | 41 + .../src/modes/apache/apacheItemBuilder.cpp | 22 + .../src/modes/apache/apacheItemBuilder.h | 67 + ksystemlog/src/modes/apache/apacheLogMode.cpp | 64 + ksystemlog/src/modes/apache/apacheLogMode.h | 61 + .../src/modes/authentication/CMakeLists.txt | 33 + .../authentication/authenticationAnalyzer.cpp | 22 + .../authentication/authenticationAnalyzer.h | 84 + .../authenticationConfiguration.cpp | 22 + .../authenticationConfiguration.h | 92 + .../authenticationConfigurationWidget.cpp | 22 + .../authenticationConfigurationWidget.h | 115 + .../authentication/authenticationFactory.cpp | 46 + .../authentication/authenticationFactory.h | 45 + .../authentication/authenticationLogMode.cpp | 66 + .../authentication/authenticationLogMode.h | 57 + ksystemlog/src/modes/base/CMakeLists.txt | 36 + ksystemlog/src/modes/base/fileList.cpp | 233 + ksystemlog/src/modes/base/fileList.h | 84 + ksystemlog/src/modes/base/fileListBase.ui | 216 + ksystemlog/src/modes/base/fileListHelper.cpp | 172 + ksystemlog/src/modes/base/fileListHelper.h | 69 + .../src/modes/base/genericConfiguration.cpp | 112 + .../src/modes/base/genericConfiguration.h | 56 + .../src/modes/base/logLevelFileList.cpp | 193 + ksystemlog/src/modes/base/logLevelFileList.h | 66 + .../modes/base/logLevelSelectionDialog.cpp | 51 + .../src/modes/base/logLevelSelectionDialog.h | 41 + .../modes/base/logLevelSelectionDialogBase.ui | 112 + .../src/modes/base/multipleFileList.cpp | 432 + ksystemlog/src/modes/base/multipleFileList.h | 90 + .../src/modes/base/multipleFileListBase.ui | 188 + ksystemlog/src/modes/base/parsingHelper.cpp | 172 + ksystemlog/src/modes/base/parsingHelper.h | 70 + ksystemlog/src/modes/base/syslogAnalyzer.cpp | 212 + ksystemlog/src/modes/base/syslogAnalyzer.h | 58 + ksystemlog/src/modes/cron/CMakeLists.txt | 35 + ksystemlog/src/modes/cron/cronAnalyzer.cpp | 22 + ksystemlog/src/modes/cron/cronAnalyzer.h | 129 + .../src/modes/cron/cronConfiguration.cpp | 22 + ksystemlog/src/modes/cron/cronConfiguration.h | 88 + .../modes/cron/cronConfigurationWidget.cpp | 22 + .../src/modes/cron/cronConfigurationWidget.h | 159 + ksystemlog/src/modes/cron/cronFactory.cpp | 48 + ksystemlog/src/modes/cron/cronFactory.h | 45 + ksystemlog/src/modes/cron/cronItemBuilder.cpp | 22 + ksystemlog/src/modes/cron/cronItemBuilder.h | 70 + ksystemlog/src/modes/cron/cronLogMode.cpp | 64 + ksystemlog/src/modes/cron/cronLogMode.h | 56 + ksystemlog/src/modes/cups/CMakeLists.txt | 50 + .../src/modes/cups/cupsAccessAnalyzer.cpp | 22 + .../src/modes/cups/cupsAccessAnalyzer.h | 132 + .../src/modes/cups/cupsAccessItemBuilder.cpp | 22 + .../src/modes/cups/cupsAccessItemBuilder.h | 70 + .../src/modes/cups/cupsAccessLogMode.cpp | 66 + ksystemlog/src/modes/cups/cupsAccessLogMode.h | 61 + ksystemlog/src/modes/cups/cupsAnalyzer.cpp | 22 + ksystemlog/src/modes/cups/cupsAnalyzer.h | 149 + .../src/modes/cups/cupsConfiguration.cpp | 22 + ksystemlog/src/modes/cups/cupsConfiguration.h | 115 + .../modes/cups/cupsConfigurationWidget.cpp | 22 + .../src/modes/cups/cupsConfigurationWidget.h | 120 + ksystemlog/src/modes/cups/cupsFactory.cpp | 68 + ksystemlog/src/modes/cups/cupsFactory.h | 41 + ksystemlog/src/modes/cups/cupsItemBuilder.cpp | 22 + ksystemlog/src/modes/cups/cupsItemBuilder.h | 63 + ksystemlog/src/modes/cups/cupsLogMode.cpp | 64 + ksystemlog/src/modes/cups/cupsLogMode.h | 61 + .../src/modes/cups/cupsPageAnalyzer.cpp | 22 + ksystemlog/src/modes/cups/cupsPageAnalyzer.h | 112 + .../src/modes/cups/cupsPageItemBuilder.cpp | 22 + .../src/modes/cups/cupsPageItemBuilder.h | 71 + ksystemlog/src/modes/cups/cupsPageLogMode.cpp | 66 + ksystemlog/src/modes/cups/cupsPageLogMode.h | 61 + ksystemlog/src/modes/cups/cupsPdfAnalyzer.cpp | 22 + ksystemlog/src/modes/cups/cupsPdfAnalyzer.h | 127 + .../src/modes/cups/cupsPdfItemBuilder.cpp | 22 + .../src/modes/cups/cupsPdfItemBuilder.h | 66 + ksystemlog/src/modes/cups/cupsPdfLogMode.cpp | 66 + ksystemlog/src/modes/cups/cupsPdfLogMode.h | 61 + ksystemlog/src/modes/daemon/CMakeLists.txt | 32 + .../src/modes/daemon/daemonConfiguration.cpp | 22 + .../src/modes/daemon/daemonConfiguration.h | 72 + .../daemon/daemonConfigurationWidget.cpp | 22 + .../modes/daemon/daemonConfigurationWidget.h | 99 + ksystemlog/src/modes/daemon/daemonFactory.cpp | 44 + ksystemlog/src/modes/daemon/daemonFactory.h | 45 + ksystemlog/src/modes/daemon/daemonLogMode.cpp | 64 + ksystemlog/src/modes/daemon/daemonLogMode.h | 67 + ksystemlog/src/modes/kernel/CMakeLists.txt | 30 + .../src/modes/kernel/kernelAnalyzer.cpp | 22 + ksystemlog/src/modes/kernel/kernelAnalyzer.h | 166 + ksystemlog/src/modes/kernel/kernelFactory.cpp | 44 + ksystemlog/src/modes/kernel/kernelFactory.h | 45 + .../src/modes/kernel/kernelItemBuilder.cpp | 22 + .../src/modes/kernel/kernelItemBuilder.h | 68 + ksystemlog/src/modes/kernel/kernelLogMode.cpp | 64 + ksystemlog/src/modes/kernel/kernelLogMode.h | 58 + ksystemlog/src/modes/open/CMakeLists.txt | 29 + ksystemlog/src/modes/open/openAnalyzer.cpp | 22 + ksystemlog/src/modes/open/openAnalyzer.h | 51 + ksystemlog/src/modes/open/openFactory.cpp | 51 + ksystemlog/src/modes/open/openFactory.h | 48 + ksystemlog/src/modes/open/openLogMode.cpp | 86 + ksystemlog/src/modes/open/openLogMode.h | 63 + ksystemlog/src/modes/postfix/CMakeLists.txt | 33 + .../src/modes/postfix/postfixAnalyzer.cpp | 22 + .../src/modes/postfix/postfixAnalyzer.h | 68 + .../modes/postfix/postfixConfiguration.cpp | 22 + .../src/modes/postfix/postfixConfiguration.h | 55 + .../postfix/postfixConfigurationWidget.cpp | 22 + .../postfix/postfixConfigurationWidget.h | 105 + .../src/modes/postfix/postfixFactory.cpp | 49 + ksystemlog/src/modes/postfix/postfixFactory.h | 45 + .../src/modes/postfix/postfixLogMode.cpp | 63 + ksystemlog/src/modes/postfix/postfixLogMode.h | 56 + ksystemlog/src/modes/samba/CMakeLists.txt | 37 + ksystemlog/src/modes/samba/netbiosLogMode.cpp | 63 + ksystemlog/src/modes/samba/netbiosLogMode.h | 62 + .../src/modes/samba/sambaAccessLogMode.cpp | 63 + .../src/modes/samba/sambaAccessLogMode.h | 62 + ksystemlog/src/modes/samba/sambaAnalyzer.cpp | 22 + ksystemlog/src/modes/samba/sambaAnalyzer.h | 189 + .../src/modes/samba/sambaConfiguration.cpp | 22 + .../src/modes/samba/sambaConfiguration.h | 101 + .../modes/samba/sambaConfigurationWidget.cpp | 22 + .../modes/samba/sambaConfigurationWidget.h | 115 + ksystemlog/src/modes/samba/sambaFactory.cpp | 67 + ksystemlog/src/modes/samba/sambaFactory.h | 41 + .../src/modes/samba/sambaItemBuilder.cpp | 22 + ksystemlog/src/modes/samba/sambaItemBuilder.h | 69 + ksystemlog/src/modes/samba/sambaLogMode.cpp | 63 + ksystemlog/src/modes/samba/sambaLogMode.h | 62 + ksystemlog/src/modes/system/CMakeLists.txt | 33 + .../src/modes/system/systemAnalyzer.cpp | 22 + ksystemlog/src/modes/system/systemAnalyzer.h | 48 + .../src/modes/system/systemConfiguration.cpp | 22 + .../src/modes/system/systemConfiguration.h | 60 + .../system/systemConfigurationWidget.cpp | 22 + .../modes/system/systemConfigurationWidget.h | 105 + ksystemlog/src/modes/system/systemFactory.cpp | 44 + ksystemlog/src/modes/system/systemFactory.h | 45 + ksystemlog/src/modes/system/systemLogMode.cpp | 63 + ksystemlog/src/modes/system/systemLogMode.h | 56 + ksystemlog/src/modes/xorg/CMakeLists.txt | 35 + ksystemlog/src/modes/xorg/xorgAnalyzer.cpp | 22 + ksystemlog/src/modes/xorg/xorgAnalyzer.h | 144 + .../src/modes/xorg/xorgConfiguration.cpp | 22 + ksystemlog/src/modes/xorg/xorgConfiguration.h | 73 + .../modes/xorg/xorgConfigurationWidget.cpp | 22 + .../src/modes/xorg/xorgConfigurationWidget.h | 98 + ksystemlog/src/modes/xorg/xorgFactory.cpp | 47 + ksystemlog/src/modes/xorg/xorgFactory.h | 45 + ksystemlog/src/modes/xorg/xorgItemBuilder.cpp | 22 + ksystemlog/src/modes/xorg/xorgItemBuilder.h | 93 + ksystemlog/src/modes/xorg/xorgLogMode.cpp | 63 + ksystemlog/src/modes/xorg/xorgLogMode.h | 57 + ksystemlog/src/modes/xsession/CMakeLists.txt | 36 + .../src/modes/xsession/xsessionAnalyzer.cpp | 22 + .../src/modes/xsession/xsessionAnalyzer.h | 159 + .../modes/xsession/xsessionConfiguration.cpp | 22 + .../modes/xsession/xsessionConfiguration.h | 116 + .../xsession/xsessionConfigurationWidget.cpp | 22 + .../xsession/xsessionConfigurationWidget.h | 153 + .../xsessionConfigurationWidgetBase.ui | 92 + .../src/modes/xsession/xsessionFactory.cpp | 49 + .../src/modes/xsession/xsessionFactory.h | 45 + .../modes/xsession/xsessionItemBuilder.cpp | 22 + .../src/modes/xsession/xsessionItemBuilder.h | 93 + .../src/modes/xsession/xsessionLogMode.cpp | 66 + .../src/modes/xsession/xsessionLogMode.h | 57 + ksystemlog/src/statusBar.cpp | 143 + ksystemlog/src/statusBar.h | 64 + ksystemlog/src/tabLogManager.cpp | 87 + ksystemlog/src/tabLogManager.h | 62 + ksystemlog/src/tabLogViewsWidget.cpp | 421 + ksystemlog/src/tabLogViewsWidget.h | 116 + ksystemlog/tests/CMakeLists.txt | 53 + ksystemlog/tests/findIncompatibleKioTest.cpp | 295 + ksystemlog/tests/kernelAnalyzerTest.cpp | 178 + ksystemlog/tests/kioLogFileReaderTest.cpp | 85 + ksystemlog/tests/logModeFactoryTest.cpp | 82 + ksystemlog/tests/systemAnalyzerTest.cpp | 365 + ksystemlog/tests/testFiles/apache/access.log | 19 + ksystemlog/tests/testFiles/apache/error.log | 34 + ksystemlog/tests/testFiles/cups/access_log | 233 + ksystemlog/tests/testFiles/cups/cups-pdf_log | 26 + ksystemlog/tests/testFiles/cups/error_log | 4 + ksystemlog/tests/testFiles/cups/page_log | 3 + .../tests/testFiles/default/one-line.log | 1 + .../tests/testFiles/default/two-lines.log | 2 + ksystemlog/tests/testFiles/kernel/suse.dmesg | 23 + .../tests/testFiles/kernel/ubuntu.dmesg | 25 + .../tests/testFiles/logFileReader/file.txt | 15 + .../tests/testFiles/mysql/mysql-slow.log | 94 + ksystemlog/tests/testFiles/mysql/mysql.log | 194 + .../tests/testFiles/samba/log.hostname1 | 50 + ksystemlog/tests/testFiles/samba/log.ip2 | 4 + ksystemlog/tests/testFiles/samba/log.nmbd | 33 + ksystemlog/tests/testFiles/samba/log.smbd | 13 + .../system/delete-process-identifier.log | 2 + .../testFiles/system/duplicate-lines.log | 9 + .../tests/testFiles/system/max-lines.log | 7 + .../tests/testFiles/system/strange-lines.log | 8 + ksystemlog/tests/testFiles/system/system.log | 24 + ksystemlog/tests/testFiles/user/user.log | 88 + .../tests/testFiles/xsession/xsession-error | 46 + ksystemlog/tests/testResources.qrc | 19 + ksystemlog/tests/testUtil.cpp | 133 + ksystemlog/tests/testUtil.h | 89 + libbluedevil/CMakeLists.txt | 61 + libbluedevil/HACKING | 27 + libbluedevil/bluedevil/CMakeLists.txt | 46 + libbluedevil/bluedevil/bluedevil.h | 145 + libbluedevil/bluedevil/bluedevil.pc.in | 10 + libbluedevil/bluedevil/bluedevil_export.h | 30 + libbluedevil/bluedevil/bluedeviladapter.cpp | 288 + libbluedevil/bluedevil/bluedeviladapter.h | 241 + libbluedevil/bluedevil/bluedevildbustypes.h | 34 + libbluedevil/bluedevil/bluedevildevice.cpp | 288 + libbluedevil/bluedevil/bluedevildevice.h | 325 + libbluedevil/bluedevil/bluedevilmanager.cpp | 165 + libbluedevil/bluedevil/bluedevilmanager.h | 181 + libbluedevil/bluedevil/bluedevilmanager_p.cpp | 198 + libbluedevil/bluedevil/bluedevilmanager_p.h | 66 + libbluedevil/bluedevil/bluedevilutils.cpp | 111 + libbluedevil/bluedevil/bluedevilutils.h | 54 + .../bluedevil/bluez/org.bluez.Adapter1.xml | 25 + .../bluez/org.bluez.AgentManager1.xml | 16 + .../bluedevil/bluez/org.bluez.Device1.xml | 33 + .../org.freedesktop.DBus.ObjectManager.xml | 19 + .../bluez/org.freedesktop.DBus.Properties.xml | 28 + libbluedevil/bluedevil/test/CMakeLists.txt | 11 + libbluedevil/bluedevil/test/adaptertest.cpp | 104 + libbluedevil/bluedevil/test/adaptertest.h | 55 + libbluedevil/bluedevil/test/bluedeviltest.cpp | 116 + libbluedevil/bluedevil/test/bluedeviltest.h | 52 + libbluedevil/cmake/modules/FindQt4.cmake | 1250 + .../cmake/modules/MacroPushRequiredVars.cmake | 47 + .../modules/Qt4ConfigDependentSettings.cmake | 384 + libbluedevil/cmake/modules/Qt4Macros.cmake | 414 + libbluedevil/qt4.tag | 85910 ++++++++++++++++ okular/.krazy | 1 + okular/AUTHORS | 4 + okular/CMakeLists.txt | 239 + okular/COPYING | 339 + okular/COPYING.DOC | 397 + okular/COPYING.LIB | 481 + okular/Mainpage.dox | 864 + okular/Messages.sh | 4 + okular/OkularConfig.cmake | 24 + okular/OkularConfigureChecks.cmake | 18 + okular/README.internals.png | Bin 0 -> 33807 bytes okular/TODO | 274 + okular/VERSION | 1 + okular/aboutdata.h | 47 + okular/active/CMakeLists.txt | 10 + okular/active/app/CMakeLists.txt | 11 + okular/active/app/Messages.sh | 7 + .../active/app/active-documentviewer.desktop | 151 + .../app/package/contents/ui/Bookmarks.qml | 28 + .../app/package/contents/ui/Browser.qml | 253 + .../contents/ui/FullScreenDelegate.qml | 193 + .../package/contents/ui/TableOfContents.qml | 59 + .../app/package/contents/ui/Thumbnails.qml | 57 + .../package/contents/ui/ThumbnailsBase.qml | 103 + .../app/package/contents/ui/TreeDelegate.qml | 109 + .../app/package/contents/ui/bookmark.png | Bin 0 -> 3887 bytes .../app/package/contents/ui/bookmark.svgz | Bin 0 -> 2551 bytes .../active/app/package/contents/ui/main.qml | 64 + okular/active/app/package/metadata.desktop | 62 + okular/active/app/src/CMakeLists.txt | 21 + okular/active/app/src/main.cpp | 62 + okular/active/components/CMakeLists.txt | 41 + okular/active/components/Messages.sh | 3 + okular/active/components/documentitem.cpp | 257 + okular/active/components/documentitem.h | 185 + okular/active/components/okularplugin.cpp | 42 + okular/active/components/okularplugin.h | 36 + okular/active/components/pageitem.cpp | 412 + okular/active/components/pageitem.h | 174 + okular/active/components/qmldir | 2 + okular/active/components/thumbnailitem.cpp | 34 + okular/active/components/thumbnailitem.h | 35 + okular/cmake/modules/COPYING-CMAKE-SCRIPTS | 22 + okular/cmake/modules/FindCHM.cmake | 33 + okular/cmake/modules/FindDjVuLibre.cmake | 39 + okular/cmake/modules/FindEPub.cmake | 35 + okular/cmake/modules/FindLibSpectre.cmake | 67 + okular/cmake/modules/FindPoppler.cmake | 170 + okular/conf/dlgaccessibility.cpp | 47 + okular/conf/dlgaccessibility.h | 35 + okular/conf/dlgaccessibilitybase.ui | 386 + okular/conf/dlgannotations.cpp | 28 + okular/conf/dlgannotations.h | 22 + okular/conf/dlgannotationsbase.ui | 109 + okular/conf/dlgdebug.cpp | 33 + okular/conf/dlgdebug.h | 21 + okular/conf/dlgeditor.cpp | 71 + okular/conf/dlgeditor.h | 34 + okular/conf/dlgeditorbase.ui | 97 + okular/conf/dlggeneral.cpp | 49 + okular/conf/dlggeneral.h | 31 + okular/conf/dlggeneralbase.ui | 406 + okular/conf/dlgperformance.cpp | 58 + okular/conf/dlgperformance.h | 32 + okular/conf/dlgperformancebase.ui | 274 + okular/conf/dlgpresentation.cpp | 63 + okular/conf/dlgpresentation.h | 32 + okular/conf/dlgpresentationbase.ui | 340 + okular/conf/okular.kcfg | 283 + okular/conf/okular_core.kcfg | 95 + okular/conf/preferencesdialog.cpp | 68 + okular/conf/preferencesdialog.h | 55 + okular/conf/settings.kcfgc | 10 + okular/conf/settings_core.kcfgc | 8 + okular/conf/textdocumentsettings.ui | 13 + okular/conf/widgetannottools.cpp | 704 + okular/conf/widgetannottools.h | 108 + okular/config-okular.h.cmake | 5 + okular/core/action.cpp | 560 + okular/core/action.h | 567 + okular/core/annotations.cpp | 2937 + okular/core/annotations.h | 1653 + okular/core/annotations_p.h | 78 + okular/core/area.cpp | 493 + okular/core/area.h | 912 + okular/core/audioplayer.cpp | 259 + okular/core/audioplayer.h | 89 + okular/core/audioplayer_p.h | 50 + okular/core/bookmarkmanager.cpp | 745 + okular/core/bookmarkmanager.h | 212 + okular/core/chooseenginedialog.cpp | 46 + okular/core/chooseenginedialog_p.h | 32 + okular/core/chooseenginewidget.ui | 42 + okular/core/debug_p.h | 15 + okular/core/document.cpp | 4840 + okular/core/document.h | 1274 + okular/core/document_p.h | 279 + okular/core/documentcommands.cpp | 567 + okular/core/documentcommands_p.h | 259 + okular/core/fileprinter.cpp | 678 + okular/core/fileprinter.h | 187 + okular/core/fontinfo.cpp | 138 + okular/core/fontinfo.h | 162 + okular/core/form.cpp | 260 + okular/core/form.h | 347 + okular/core/form_p.h | 43 + okular/core/generator.cpp | 672 + okular/core/generator.h | 699 + okular/core/generator_p.cpp | 144 + okular/core/generator_p.h | 169 + okular/core/global.h | 83 + okular/core/misc.cpp | 89 + okular/core/misc.h | 82 + okular/core/movie.cpp | 155 + okular/core/movie.h | 149 + okular/core/observer.cpp | 54 + okular/core/observer.h | 121 + okular/core/okularGenerator.desktop | 72 + okular/core/okular_export.h | 33 + okular/core/page.cpp | 1018 + okular/core/page.h | 414 + okular/core/page_p.h | 151 + okular/core/pagecontroller.cpp | 52 + okular/core/pagecontroller_p.h | 45 + okular/core/pagesize.cpp | 116 + okular/core/pagesize.h | 83 + okular/core/pagetransition.cpp | 134 + okular/core/pagetransition.h | 158 + okular/core/rotationjob.cpp | 106 + okular/core/rotationjob_p.h | 59 + okular/core/script/executor_kjs.cpp | 113 + okular/core/script/executor_kjs_p.h | 35 + okular/core/script/kjs_app.cpp | 232 + okular/core/script/kjs_app_p.h | 30 + okular/core/script/kjs_console.cpp | 150 + okular/core/script/kjs_console_p.h | 28 + okular/core/script/kjs_data.cpp | 90 + okular/core/script/kjs_data_p.h | 30 + okular/core/script/kjs_document.cpp | 287 + okular/core/script/kjs_document_p.h | 30 + okular/core/script/kjs_field.cpp | 230 + okular/core/script/kjs_field_p.h | 32 + okular/core/script/kjs_fullscreen.cpp | 78 + okular/core/script/kjs_fullscreen_p.h | 28 + okular/core/script/kjs_spell.cpp | 42 + okular/core/script/kjs_spell_p.h | 28 + okular/core/script/kjs_util.cpp | 74 + okular/core/script/kjs_util_p.h | 28 + okular/core/scripter.cpp | 66 + okular/core/scripter.h | 43 + okular/core/sound.cpp | 117 + okular/core/sound.h | 127 + okular/core/sourcereference.cpp | 93 + okular/core/sourcereference.h | 64 + okular/core/sourcereference_p.h | 23 + okular/core/textdocumentgenerator.cpp | 567 + okular/core/textdocumentgenerator.h | 234 + okular/core/textdocumentgenerator_p.h | 202 + okular/core/textdocumentsettings.cpp | 77 + okular/core/textdocumentsettings.h | 123 + okular/core/textdocumentsettings_p.h | 42 + okular/core/texteditors_p.h | 39 + okular/core/textpage.cpp | 2036 + okular/core/textpage.h | 195 + okular/core/textpage_p.h | 79 + okular/core/tile.h | 56 + okular/core/tilesmanager.cpp | 709 + okular/core/tilesmanager_p.h | 207 + okular/core/utils.cpp | 402 + okular/core/utils.h | 92 + okular/core/utils_p.h | 27 + okular/core/version.h | 24 + okular/core/view.cpp | 77 + okular/core/view.h | 113 + okular/core/view_p.h | 33 + okular/doc/CMakeLists.txt | 5 + okular/doc/annotation-properties.png | Bin 0 -> 19290 bytes okular/doc/annotations.png | Bin 0 -> 109696 bytes okular/doc/bookmark-management.png | Bin 0 -> 46855 bytes okular/doc/configure-annotations.png | Bin 0 -> 28658 bytes okular/doc/configure-backends.png | Bin 0 -> 14549 bytes okular/doc/configure-editor.png | Bin 0 -> 17463 bytes okular/doc/configure.png | Bin 0 -> 26691 bytes okular/doc/embedded-files-bar.png | Bin 0 -> 4657 bytes okular/doc/enhance-lowcontrast.png | Bin 0 -> 3753 bytes okular/doc/enhance-shape.png | Bin 0 -> 4090 bytes okular/doc/enhance-solid.png | Bin 0 -> 6491 bytes okular/doc/enhance-thinline.png | Bin 0 -> 8225 bytes okular/doc/forms-bar.png | Bin 0 -> 4640 bytes okular/doc/index.docbook | 2425 + okular/doc/mainwindow.png | Bin 0 -> 52736 bytes okular/doc/man-okular.1.docbook | 164 + okular/doc/presentation.png | Bin 0 -> 6360 bytes okular/doc/rating.png | Bin 0 -> 744 bytes okular/doc/tool-ellipse-okular.png | Bin 0 -> 1100 bytes okular/doc/tool-highlighter-okular.png | Bin 0 -> 1536 bytes okular/doc/tool-ink-okular.png | Bin 0 -> 1710 bytes okular/doc/tool-line-okular.png | Bin 0 -> 849 bytes okular/doc/tool-note-inline-okular.png | Bin 0 -> 803 bytes okular/doc/tool-note-okular.png | Bin 0 -> 973 bytes okular/doc/tool-polygon-okular.png | Bin 0 -> 664 bytes okular/doc/tool-stamp-okular.png | Bin 0 -> 1074 bytes okular/doc/tool-underline-okular.png | Bin 0 -> 629 bytes okular/extensions.cpp | 124 + okular/extensions.h | 62 + okular/generators/CMakeLists.txt | 87 + okular/generators/chm/CMakeLists.txt | 33 + okular/generators/chm/Messages.sh | 2 + .../chm/active-documentviewer_chm.desktop | 201 + okular/generators/chm/generator_chm.cpp | 469 + okular/generators/chm/generator_chm.h | 72 + .../generators/chm/kio-msits/CMakeLists.txt | 23 + okular/generators/chm/kio-msits/msits.cpp | 309 + okular/generators/chm/kio-msits/msits.h | 71 + .../generators/chm/kio-msits/msits.protocol | 70 + okular/generators/chm/lib/bitfiddle.h | 157 + okular/generators/chm/lib/lchmurlhandler.cpp | 32 + okular/generators/chm/lib/lchmurlhandler.h | 36 + okular/generators/chm/lib/libchmfile.cpp | 127 + okular/generators/chm/lib/libchmfile.h | 280 + .../generators/chm/lib/libchmfile_search.cpp | 289 + okular/generators/chm/lib/libchmfileimpl.cpp | 1288 + okular/generators/chm/lib/libchmfileimpl.h | 300 + .../generators/chm/lib/libchmtextencoding.cpp | 375 + .../generators/chm/lib/libchmtextencoding.h | 37 + okular/generators/chm/lib/libchmtocimage.cpp | 2112 + okular/generators/chm/lib/libchmtocimage.h | 39 + okular/generators/chm/lib/libchmurlfactory.h | 123 + .../chm/libokularGenerator_chmlib.desktop | 127 + .../chm/okularApplication_chm.desktop | 181 + okular/generators/chm/okularChm.desktop | 66 + okular/generators/comicbook/CMakeLists.txt | 31 + okular/generators/comicbook/Messages.sh | 2 + .../active-documentviewer_comicbook.desktop | 201 + okular/generators/comicbook/directory.cpp | 67 + okular/generators/comicbook/directory.h | 55 + okular/generators/comicbook/document.cpp | 229 + okular/generators/comicbook/document.h | 58 + .../comicbook/generator_comicbook.cpp | 110 + .../comicbook/generator_comicbook.h | 39 + .../libokularGenerator_comicbook.desktop | 127 + .../okularApplication_comicbook.desktop | 181 + .../comicbook/okularComicbook.desktop | 66 + okular/generators/comicbook/qnatsort.cpp | 159 + okular/generators/comicbook/qnatsort.h | 54 + okular/generators/comicbook/unrar.cpp | 263 + okular/generators/comicbook/unrar.h | 81 + okular/generators/comicbook/unrarflavours.cpp | 73 + okular/generators/comicbook/unrarflavours.h | 55 + okular/generators/djvu/CMakeLists.txt | 28 + okular/generators/djvu/Messages.sh | 2 + okular/generators/djvu/TODO | 10 + .../djvu/active-documentviewer_djvu.desktop | 202 + okular/generators/djvu/generator_djvu.cpp | 472 + okular/generators/djvu/generator_djvu.h | 58 + okular/generators/djvu/kdjvu.cpp | 1158 + okular/generators/djvu/kdjvu.h | 297 + .../djvu/libokularGenerator_djvu.desktop | 128 + .../djvu/okularApplication_djvu.desktop | 182 + okular/generators/djvu/okularDjvu.desktop | 66 + okular/generators/dvi/CMakeLists.txt | 63 + okular/generators/dvi/Messages.sh | 2 + okular/generators/dvi/TeXFont.cpp | 8 + okular/generators/dvi/TeXFont.h | 48 + okular/generators/dvi/TeXFontDefinition.cpp | 248 + okular/generators/dvi/TeXFontDefinition.h | 150 + okular/generators/dvi/TeXFont_PFB.cpp | 291 + okular/generators/dvi/TeXFont_PFB.h | 41 + okular/generators/dvi/TeXFont_PK.cpp | 775 + okular/generators/dvi/TeXFont_PK.h | 41 + okular/generators/dvi/TeXFont_TFM.cpp | 159 + okular/generators/dvi/TeXFont_TFM.h | 38 + .../dvi/active-documentviewer_dvi.desktop | 202 + okular/generators/dvi/anchor.h | 61 + okular/generators/dvi/bigEndianByteReader.cpp | 112 + okular/generators/dvi/bigEndianByteReader.h | 63 + okular/generators/dvi/config.h | 2 + okular/generators/dvi/dvi.h | 68 + okular/generators/dvi/dviFile.cpp | 444 + okular/generators/dvi/dviFile.h | 153 + okular/generators/dvi/dviPageInfo.cpp | 33 + okular/generators/dvi/dviPageInfo.h | 48 + okular/generators/dvi/dviRenderer.cpp | 768 + okular/generators/dvi/dviRenderer.h | 332 + okular/generators/dvi/dviRenderer_dr.cpp | 49 + okular/generators/dvi/dviRenderer_draw.cpp | 711 + okular/generators/dvi/dviRenderer_prescan.cpp | 801 + okular/generators/dvi/dviexport.cpp | 326 + okular/generators/dvi/dviexport.h | 136 + okular/generators/dvi/dvisourcesplitter.cpp | 97 + okular/generators/dvi/dvisourcesplitter.h | 33 + okular/generators/dvi/fontEncoding.cpp | 95 + okular/generators/dvi/fontEncoding.h | 86 + okular/generators/dvi/fontEncodingPool.cpp | 41 + okular/generators/dvi/fontEncodingPool.h | 30 + okular/generators/dvi/fontMap.cpp | 173 + okular/generators/dvi/fontMap.h | 118 + okular/generators/dvi/fontpool.cpp | 483 + okular/generators/dvi/fontpool.h | 208 + okular/generators/dvi/generator_dvi.cpp | 605 + okular/generators/dvi/generator_dvi.h | 70 + okular/generators/dvi/glyph.cpp | 38 + okular/generators/dvi/glyph.h | 44 + okular/generators/dvi/hyperlink.h | 78 + okular/generators/dvi/kviewshell_export.h | 17 + okular/generators/dvi/kvs_debug.h | 21 + okular/generators/dvi/length.cpp | 77 + okular/generators/dvi/length.h | 200 + .../dvi/libokularGenerator_dvi.desktop | 128 + .../dvi/okularApplication_dvi.desktop | 182 + okular/generators/dvi/okularDvi.desktop | 66 + okular/generators/dvi/pageNumber.h | 63 + okular/generators/dvi/pageSize.cpp | 364 + okular/generators/dvi/pageSize.h | 284 + okular/generators/dvi/prebookmark.h | 50 + okular/generators/dvi/psgs.cpp | 353 + okular/generators/dvi/psgs.h | 108 + okular/generators/dvi/psheader.cpp | 128 + okular/generators/dvi/simplePageSize.cpp | 50 + okular/generators/dvi/simplePageSize.h | 168 + okular/generators/dvi/special.cpp | 706 + okular/generators/dvi/textBox.h | 66 + okular/generators/dvi/util.cpp | 114 + okular/generators/dvi/vf.cpp | 186 + okular/generators/dvi/xdvi.h | 24 + okular/generators/epub/CMakeLists.txt | 27 + okular/generators/epub/Messages.sh | 2 + okular/generators/epub/README | 10 + .../epub/active-documentviewer_epub.desktop | 201 + okular/generators/epub/converter.cpp | 454 + okular/generators/epub/converter.h | 41 + okular/generators/epub/data/CMakeLists.txt | 5 + .../epub/data/okular-epub-movie.png | Bin 0 -> 12616 bytes .../epub/data/okular-epub-sound-icon.png | Bin 0 -> 2194 bytes okular/generators/epub/epubdocument.cpp | 133 + okular/generators/epub/epubdocument.h | 49 + okular/generators/epub/generator_epub.cpp | 46 + okular/generators/epub/generator_epub.h | 24 + .../epub/libokularGenerator_epub.desktop | 123 + .../epub/okularApplication_epub.desktop | 181 + okular/generators/epub/okularEPub.desktop | 66 + okular/generators/fax/CMakeLists.txt | 20 + okular/generators/fax/Messages.sh | 2 + .../fax/active-documentviewer_fax.desktop | 152 + okular/generators/fax/faxdocument.cpp | 309 + okular/generators/fax/faxdocument.h | 63 + okular/generators/fax/faxexpand.cpp | 744 + okular/generators/fax/faxexpand.h | 120 + okular/generators/fax/faxinit.cpp | 343 + okular/generators/fax/generator_fax.cpp | 127 + okular/generators/fax/generator_fax.h | 40 + .../fax/libokularGenerator_fax.desktop | 123 + .../fax/okularApplication_fax.desktop | 132 + okular/generators/fax/okularFax.desktop | 66 + okular/generators/fictionbook/CMakeLists.txt | 28 + okular/generators/fictionbook/Messages.sh | 2 + .../active-documentviewer_fb.desktop | 201 + okular/generators/fictionbook/converter.cpp | 795 + okular/generators/fictionbook/converter.h | 75 + okular/generators/fictionbook/document.cpp | 91 + okular/generators/fictionbook/document.h | 41 + .../generators/fictionbook/generator_fb.cpp | 46 + okular/generators/fictionbook/generator_fb.h | 24 + .../fictionbook/hi16-app-okular-fb2.png | Bin 0 -> 969 bytes .../fictionbook/hi32-app-okular-fb2.png | Bin 0 -> 1045 bytes .../fictionbook/hi32-app-okular-fb2.svg | 200 + .../fictionbook/hi48-app-okular-fb2.png | Bin 0 -> 1395 bytes .../fictionbook/libokularGenerator_fb.desktop | 126 + .../fictionbook/okularApplication_fb.desktop | 181 + .../generators/fictionbook/okularFb.desktop | 66 + okular/generators/kimgio/CMakeLists.txt | 29 + okular/generators/kimgio/Messages.sh | 3 + .../active-documentviewer_kimgio.desktop | 152 + okular/generators/kimgio/generator_kimgio.cpp | 195 + okular/generators/kimgio/generator_kimgio.h | 47 + okular/generators/kimgio/gui.rc | 6 + .../kimgio/libokularGenerator_kimgio.desktop | 128 + .../kimgio/okularApplication_kimgio.desktop | 132 + okular/generators/kimgio/okularKimgio.desktop | 66 + .../tests/data/testExifOrientation-0.jpg | Bin 0 -> 447 bytes .../data/testExifOrientation-0mirror.jpg | Bin 0 -> 457 bytes .../tests/data/testExifOrientation-180.jpg | Bin 0 -> 463 bytes .../data/testExifOrientation-180mirror.jpg | Bin 0 -> 456 bytes .../tests/data/testExifOrientation-270.jpg | Bin 0 -> 456 bytes .../data/testExifOrientation-270mirror.jpg | Bin 0 -> 447 bytes .../tests/data/testExifOrientation-90.jpg | Bin 0 -> 456 bytes .../data/testExifOrientation-90mirror.jpg | Bin 0 -> 462 bytes .../tests/data/testExifOrientation-noexif.jpg | Bin 0 -> 347 bytes .../data/testExifOrientation-unspecified.jpg | Bin 0 -> 367 bytes okular/generators/kimgio/tests/kimgiotest.cpp | 101 + okular/generators/mobipocket/CMakeLists.txt | 20 + okular/generators/mobipocket/Messages.sh | 2 + okular/generators/mobipocket/converter.cpp | 106 + okular/generators/mobipocket/converter.h | 32 + .../generators/mobipocket/generator_mobi.cpp | 45 + okular/generators/mobipocket/generator_mobi.h | 23 + .../libokularGenerator_mobi.desktop | 110 + okular/generators/mobipocket/mobidocument.cpp | 116 + okular/generators/mobipocket/mobidocument.h | 42 + .../mobipocket/okularApplication_mobi.desktop | 176 + .../generators/mobipocket/okularMobi.desktop | 66 + okular/generators/ooo/CMakeLists.txt | 41 + okular/generators/ooo/Messages.sh | 2 + .../ooo/active-documentviewer_ooo.desktop | 152 + okular/generators/ooo/converter.cpp | 593 + okular/generators/ooo/converter.h | 56 + okular/generators/ooo/debug.h | 16 + okular/generators/ooo/document.cpp | 148 + okular/generators/ooo/document.h | 52 + okular/generators/ooo/formatproperty.cpp | 441 + okular/generators/ooo/formatproperty.h | 239 + okular/generators/ooo/generator_ooo.cpp | 54 + okular/generators/ooo/generator_ooo.h | 26 + .../ooo/libokularGenerator_ooo.desktop | 127 + okular/generators/ooo/manifest.cpp | 316 + okular/generators/ooo/manifest.h | 248 + .../ooo/okularApplication_ooo.desktop | 132 + okular/generators/ooo/okularOoo.desktop | 66 + okular/generators/ooo/styleinformation.cpp | 120 + okular/generators/ooo/styleinformation.h | 76 + okular/generators/ooo/styleparser.cpp | 500 + okular/generators/ooo/styleparser.h | 59 + okular/generators/plucker/CMakeLists.txt | 35 + okular/generators/plucker/Messages.sh | 2 + .../active-documentviewer_plucker.desktop | 201 + .../generators/plucker/generator_plucker.cpp | 210 + okular/generators/plucker/generator_plucker.h | 56 + .../libokularGenerator_plucker.desktop | 127 + .../plucker/okularApplication_plucker.desktop | 181 + .../generators/plucker/okularPlucker.desktop | 66 + okular/generators/plucker/unpluck/config.cpp | 406 + okular/generators/plucker/unpluck/image.cpp | 549 + okular/generators/plucker/unpluck/image.h | 20 + .../generators/plucker/unpluck/qunpluck.cpp | 1208 + okular/generators/plucker/unpluck/qunpluck.h | 85 + okular/generators/plucker/unpluck/unpluck.cpp | 1184 + okular/generators/plucker/unpluck/unpluck.h | 413 + .../generators/plucker/unpluck/unpluckint.h | 175 + okular/generators/plucker/unpluck/util.cpp | 262 + okular/generators/poppler/CMakeLists.txt | 56 + okular/generators/poppler/Messages.sh | 4 + .../poppler/active-documentviewer_pdf.desktop | 201 + okular/generators/poppler/annots.cpp | 424 + okular/generators/poppler/annots.h | 38 + okular/generators/poppler/conf/CMakeLists.txt | 3 + .../generators/poppler/conf/pdfsettings.kcfg | 18 + .../generators/poppler/conf/pdfsettings.kcfgc | 4 + .../poppler/conf/pdfsettingswidget.ui | 49 + .../poppler/config-okular-poppler.h.cmake | 17 + okular/generators/poppler/formfields.cpp | 306 + okular/generators/poppler/formfields.h | 105 + okular/generators/poppler/generator_pdf.cpp | 1899 + okular/generators/poppler/generator_pdf.h | 161 + .../libokularGenerator_poppler.desktop | 127 + .../poppler/okularApplication_pdf.desktop | 181 + .../generators/poppler/okularPoppler.desktop | 66 + .../generators/poppler/popplerembeddedfile.h | 60 + .../patches/00-disable-SYNCTEX_INLINE.diff | 16 + .../synctex/patches/01-fix-win32-define.diff | 42 + .../patches/04-gcc-specify-printf-format.diff | 28 + .../synctex/patches/05-fix-error-formats.diff | 13 + .../patches/06-mingw-_synctex_error.diff | 35 + ...anner_new_with_output_file-reset-mode.diff | 21 + .../synctex/patches/08-fix_cpp_comments.diff | 31 + .../patches/09-fix_path_comparison.diff | 22 + .../generators/poppler/synctex/patches/series | 8 + .../poppler/synctex/synctex_parser.c | 4251 + .../poppler/synctex/synctex_parser.h | 346 + .../poppler/synctex/synctex_parser_local.h | 45 + .../poppler/synctex/synctex_parser_readme.txt | 141 + .../poppler/synctex/synctex_parser_utils.c | 506 + .../poppler/synctex/synctex_parser_utils.h | 147 + .../synctex/synctex_parser_version.txt | 1 + okular/generators/spectre/CMakeLists.txt | 35 + okular/generators/spectre/DESIGN | 12 + okular/generators/spectre/Messages.sh | 4 + .../active-documentviewer_ghostview.desktop | 196 + okular/generators/spectre/conf/CMakeLists.txt | 3 + okular/generators/spectre/conf/empty.cpp | 0 .../generators/spectre/conf/gssettings.kcfg | 16 + .../generators/spectre/conf/gssettings.kcfgc | 4 + .../spectre/conf/gssettingswidget.ui | 61 + .../spectre/generator_ghostview.cpp | 321 + .../generators/spectre/generator_ghostview.h | 70 + .../generators/spectre/hi16-app-okular-gv.png | Bin 0 -> 160 bytes .../generators/spectre/hi32-app-okular-gv.png | Bin 0 -> 259 bytes .../libokularGenerator_ghostview.desktop | 127 + .../okularApplication_ghostview.desktop | 176 + .../spectre/okularGhostview.desktop | 67 + okular/generators/spectre/rendererthread.cpp | 138 + okular/generators/spectre/rendererthread.h | 80 + okular/generators/tiff/CMakeLists.txt | 25 + okular/generators/tiff/Messages.sh | 2 + .../tiff/active-documentviewer_tiff.desktop | 152 + okular/generators/tiff/generator_tiff.cpp | 447 + okular/generators/tiff/generator_tiff.h | 47 + .../tiff/libokularGenerator_tiff.desktop | 123 + .../tiff/okularApplication_tiff.desktop | 132 + okular/generators/tiff/okularTiff.desktop | 66 + okular/generators/txt/CMakeLists.txt | 25 + okular/generators/txt/Messages.sh | 2 + .../txt/active-documentviewer_txt.desktop | 197 + okular/generators/txt/converter.cpp | 41 + okular/generators/txt/converter.h | 28 + okular/generators/txt/document.cpp | 71 + okular/generators/txt/document.h | 24 + okular/generators/txt/generator_txt.cpp | 44 + okular/generators/txt/generator_txt.h | 27 + .../txt/libokularGenerator_txt.desktop | 97 + .../txt/okularApplication_txt.desktop | 177 + okular/generators/txt/okularTxt.desktop | 66 + okular/generators/xps/.emacs-dirvars | 12 + okular/generators/xps/CMakeLists.txt | 24 + okular/generators/xps/Messages.sh | 2 + .../xps/active-documentviewer_xps.desktop | 201 + okular/generators/xps/generator_xps.cpp | 2271 + okular/generators/xps/generator_xps.h | 327 + .../xps/libokularGenerator_xps.desktop | 123 + .../xps/okularApplication_xps.desktop | 181 + okular/generators/xps/okularXps.desktop | 66 + okular/interfaces/configinterface.h | 69 + okular/interfaces/guiinterface.h | 57 + okular/interfaces/printinterface.h | 60 + okular/interfaces/saveinterface.h | 85 + okular/interfaces/viewerinterface.h | 90 + okular/kdocumentviewer.h | 50 + okular/okular.upd | 25 + okular/okular_part.desktop | 66 + okular/okular_part_export.h | 33 + okular/part-viewermode.rc | 73 + okular/part.cpp | 3012 + okular/part.h | 372 + okular/part.rc | 106 + okular/shell/CMakeLists.txt | 38 + okular/shell/main.cpp | 67 + okular/shell/okular.desktop | 132 + okular/shell/okular_main.cpp | 168 + okular/shell/okular_main.h | 27 + okular/shell/shell.cpp | 660 + okular/shell/shell.h | 170 + okular/shell/shell.rc | 26 + okular/shell/shellutils.cpp | 134 + okular/shell/shellutils.h | 37 + okular/tests/CMakeLists.txt | 39 + okular/tests/addremoveannotationtest.cpp | 190 + okular/tests/annotationstest.cpp | 144 + okular/tests/data/contents.epub | Bin 0 -> 3158 bytes okular/tests/data/file1.pdf | Bin 0 -> 15605 bytes okular/tests/data/formSamples.pdf | Bin 0 -> 27196 bytes okular/tests/data/synctextest.tex | 104 + okular/tests/data/tocreload.pdf | Bin 0 -> 17245 bytes okular/tests/documenttest.cpp | 62 + okular/tests/editannotationcontentstest.cpp | 519 + okular/tests/editformstest.cpp | 431 + okular/tests/mainshelltest.cpp | 460 + .../tests/modifyannotationpropertiestest.cpp | 168 + okular/tests/parttest.cpp | 185 + okular/tests/searchtest.cpp | 434 + okular/tests/shelltest.cpp | 121 + okular/tests/testingutils.cpp | 54 + okular/tests/testingutils.h | 51 + okular/tests/translateannotationtest.cpp | 237 + okular/tests/urldetecttest.cpp | 57 + okular/ui/CMakeLists.txt | 2 + okular/ui/annotationmodel.cpp | 395 + okular/ui/annotationmodel.h | 55 + okular/ui/annotationpopup.cpp | 156 + okular/ui/annotationpopup.h | 73 + okular/ui/annotationpropertiesdialog.cpp | 185 + okular/ui/annotationpropertiesdialog.h | 49 + okular/ui/annotationproxymodels.cpp | 600 + okular/ui/annotationproxymodels.h | 145 + okular/ui/annotationtools.cpp | 234 + okular/ui/annotationtools.h | 107 + okular/ui/annotationwidgets.cpp | 762 + okular/ui/annotationwidgets.h | 256 + okular/ui/annotwindow.cpp | 411 + okular/ui/annotwindow.h | 66 + okular/ui/bookmarklist.cpp | 471 + okular/ui/bookmarklist.h | 62 + okular/ui/data/CMakeLists.txt | 51 + okular/ui/data/README.Icons | 13 + okular/ui/data/checkmark.png | Bin 0 -> 1303 bytes okular/ui/data/circle.png | Bin 0 -> 2036 bytes okular/ui/data/comment.png | Bin 0 -> 2205 bytes okular/ui/data/cross.png | Bin 0 -> 2200 bytes okular/ui/data/help.png | Bin 0 -> 1717 bytes okular/ui/data/icons/CMakeLists.txt | 1 + okular/ui/data/icons/hi128-apps-okular.png | Bin 0 -> 11390 bytes okular/ui/data/icons/hi16-apps-okular.png | Bin 0 -> 662 bytes okular/ui/data/icons/hi22-apps-okular.png | Bin 0 -> 1118 bytes okular/ui/data/icons/hi32-apps-okular.png | Bin 0 -> 1910 bytes okular/ui/data/icons/hi48-apps-okular.png | Bin 0 -> 3324 bytes okular/ui/data/icons/hi64-apps-okular.png | Bin 0 -> 4617 bytes okular/ui/data/icons/hisc-apps-okular.svgz | Bin 0 -> 14568 bytes .../ui/data/icons/small/hi16-apps-okular.svgz | Bin 0 -> 4116 bytes .../ui/data/icons/small/hi22-apps-okular.svgz | Bin 0 -> 10975 bytes okular/ui/data/insert.png | Bin 0 -> 1711 bytes okular/ui/data/key.png | Bin 0 -> 1967 bytes okular/ui/data/newparagraph.png | Bin 0 -> 1927 bytes okular/ui/data/note.png | Bin 0 -> 1114 bytes okular/ui/data/okular.knsrc | 2 + okular/ui/data/paperclip.png | Bin 0 -> 1741 bytes okular/ui/data/paragraph.png | Bin 0 -> 2384 bytes okular/ui/data/pushpin.png | Bin 0 -> 5138 bytes okular/ui/data/rightarrow.png | Bin 0 -> 1553 bytes okular/ui/data/rightpointer.png | Bin 0 -> 1573 bytes okular/ui/data/sources/checkmark.svgz | Bin 0 -> 1509 bytes okular/ui/data/sources/circle.svgz | Bin 0 -> 2173 bytes okular/ui/data/sources/comment.svgz | Bin 0 -> 3922 bytes okular/ui/data/sources/cross.svgz | Bin 0 -> 1918 bytes okular/ui/data/sources/ghns.svg | 3106 + okular/ui/data/sources/help.svgz | Bin 0 -> 3098 bytes okular/ui/data/sources/insert.svgz | Bin 0 -> 3551 bytes okular/ui/data/sources/key.svgz | Bin 0 -> 4252 bytes okular/ui/data/sources/newparagraph.svgz | Bin 0 -> 3652 bytes okular/ui/data/sources/note.svgz | Bin 0 -> 2424 bytes okular/ui/data/sources/paragraph.svgz | Bin 0 -> 4731 bytes okular/ui/data/sources/rightarrow.svgz | Bin 0 -> 2989 bytes okular/ui/data/sources/rightpointer.svgz | Bin 0 -> 3299 bytes okular/ui/data/sources/star.svgz | Bin 0 -> 4110 bytes okular/ui/data/sources/tool-base-okular.svgz | Bin 0 -> 10758 bytes .../ui/data/sources/tool-ellipse-okular.svgz | Bin 0 -> 18082 bytes .../tool-highlighter-okular-colorizable.svgz | Bin 0 -> 15084 bytes .../data/sources/tool-highlighter-okular.svgz | Bin 0 -> 22446 bytes .../sources/tool-ink-okular-colorizable.svgz | Bin 0 -> 5130 bytes okular/ui/data/sources/tool-ink-okular.svgz | Bin 0 -> 22334 bytes okular/ui/data/sources/tool-line-okular.svgz | Bin 0 -> 18071 bytes .../tool-note-inline-okular-colorizable.svgz | Bin 0 -> 5028 bytes .../data/sources/tool-note-inline-okular.svgz | Bin 0 -> 22703 bytes .../sources/tool-note-okular-colorizable.svgz | Bin 0 -> 13509 bytes okular/ui/data/sources/tool-note-okular.svgz | Bin 0 -> 30485 bytes .../ui/data/sources/tool-polygon-okular.svgz | Bin 0 -> 18142 bytes okular/ui/data/sources/tool-stamp-okular.svgz | Bin 0 -> 25597 bytes .../data/sources/tool-underline-okular.svgz | Bin 0 -> 18176 bytes okular/ui/data/sources/uparrow.svgz | Bin 0 -> 3070 bytes okular/ui/data/sources/upleftarrow.svgz | Bin 0 -> 3301 bytes okular/ui/data/stamps.svg | 538 + okular/ui/data/star.png | Bin 0 -> 2725 bytes okular/ui/data/tool-base-okular.png | Bin 0 -> 870 bytes .../tool-highlighter-okular-colorizable.png | Bin 0 -> 2148 bytes .../ui/data/tool-ink-okular-colorizable.png | Bin 0 -> 1770 bytes .../tool-note-inline-okular-colorizable.png | Bin 0 -> 515 bytes okular/ui/data/tool-note-inline.png | Bin 0 -> 1478 bytes .../ui/data/tool-note-okular-colorizable.png | Bin 0 -> 670 bytes okular/ui/data/tool-note.png | Bin 0 -> 2393 bytes okular/ui/data/tool_hl_orange.png | Bin 0 -> 1527 bytes okular/ui/data/tool_hl_pink.png | Bin 0 -> 1552 bytes okular/ui/data/tool_hl_yellow.png | Bin 0 -> 1552 bytes okular/ui/data/tool_ink_green.png | Bin 0 -> 1473 bytes okular/ui/data/tool_note.png | Bin 0 -> 1592 bytes okular/ui/data/tools.xml | 74 + okular/ui/data/uparrow.png | Bin 0 -> 1744 bytes okular/ui/data/upleftarrow.png | Bin 0 -> 1866 bytes okular/ui/embeddedfilesdialog.cpp | 132 + okular/ui/embeddedfilesdialog.h | 39 + okular/ui/fileprinterpreview.cpp | 167 + okular/ui/fileprinterpreview.h | 49 + okular/ui/findbar.cpp | 186 + okular/ui/findbar.h | 60 + okular/ui/formwidgets.cpp | 976 + okular/ui/formwidgets.h | 339 + okular/ui/guiutils.cpp | 273 + okular/ui/guiutils.h | 61 + okular/ui/ktreeviewsearchline.cpp | 653 + okular/ui/ktreeviewsearchline.h | 336 + okular/ui/latexrenderer.cpp | 200 + okular/ui/latexrenderer.h | 51 + okular/ui/magnifierview.cpp | 189 + okular/ui/magnifierview.h | 58 + okular/ui/minibar.cpp | 599 + okular/ui/minibar.h | 178 + okular/ui/pageitemdelegate.cpp | 79 + okular/ui/pageitemdelegate.h | 36 + okular/ui/pagepainter.cpp | 1005 + okular/ui/pagepainter.h | 82 + okular/ui/pagesizelabel.cpp | 36 + okular/ui/pagesizelabel.h | 40 + okular/ui/pageview.cpp | 5055 + okular/ui/pageview.h | 256 + okular/ui/pageviewannotator.cpp | 1250 + okular/ui/pageviewannotator.h | 121 + okular/ui/pageviewutils.cpp | 937 + okular/ui/pageviewutils.h | 218 + okular/ui/presentationsearchbar.cpp | 147 + okular/ui/presentationsearchbar.h | 47 + okular/ui/presentationwidget.cpp | 2208 + okular/ui/presentationwidget.h | 159 + okular/ui/priorities.h | 21 + okular/ui/propertiesdialog.cpp | 453 + okular/ui/propertiesdialog.h | 77 + okular/ui/searchlineedit.cpp | 321 + okular/ui/searchlineedit.h | 94 + okular/ui/searchwidget.cpp | 109 + okular/ui/searchwidget.h | 48 + okular/ui/side_reviews.cpp | 289 + okular/ui/side_reviews.h | 75 + okular/ui/sidebar.cpp | 768 + okular/ui/sidebar.h | 69 + okular/ui/snapshottaker.cpp | 46 + okular/ui/snapshottaker.h | 37 + okular/ui/thumbnaillist.cpp | 1001 + okular/ui/thumbnaillist.h | 100 + okular/ui/toc.cpp | 178 + okular/ui/toc.h | 65 + okular/ui/tocmodel.cpp | 436 + okular/ui/tocmodel.h | 62 + okular/ui/toolaction.cpp | 89 + okular/ui/toolaction.h | 41 + okular/ui/tts.cpp | 145 + okular/ui/tts.h | 39 + okular/ui/url_utils.h | 36 + okular/ui/videowidget.cpp | 450 + okular/ui/videowidget.h | 70 + partitionmanager/CHANGES | 300 + partitionmanager/CMakeLists.txt | 70 + partitionmanager/COPYING | 280 + partitionmanager/INSTALL | 65 + partitionmanager/README | 31 + partitionmanager/TODO | 52 + .../cmake/modules/FindLIBPARTED.cmake | 56 + .../cmake/modules/FindMSGFMT.cmake | 57 + partitionmanager/doc/CMakeLists.txt | 11 + partitionmanager/doc/ca/CMakeLists.txt | 1 + partitionmanager/doc/ca/appendix.docbook | 190 + partitionmanager/doc/ca/copyhowto.docbook | 54 + partitionmanager/doc/ca/credits.docbook | 18 + partitionmanager/doc/ca/faq.docbook | 152 + partitionmanager/doc/ca/glossary.docbook | 352 + partitionmanager/doc/ca/index.docbook | 142 + .../doc/ca/installoshowto.docbook | 152 + partitionmanager/doc/ca/introduction.docbook | 48 + .../doc/ca/referencemanual.docbook | 587 + partitionmanager/doc/ca/resizehowto.docbook | 220 + partitionmanager/doc/ca/usermanual.docbook | 97 + partitionmanager/doc/de/CMakeLists.txt | 1 + partitionmanager/doc/de/appendix.docbook | 206 + partitionmanager/doc/de/copyhowto.docbook | 64 + partitionmanager/doc/de/credits.docbook | 20 + partitionmanager/doc/de/faq.docbook | 154 + partitionmanager/doc/de/glossary.docbook | 354 + partitionmanager/doc/de/index.docbook | 156 + .../doc/de/installoshowto.docbook | 166 + partitionmanager/doc/de/introduction.docbook | 48 + .../doc/de/referencemanual.docbook | 591 + partitionmanager/doc/de/resizehowto.docbook | 236 + partitionmanager/doc/de/usermanual.docbook | 99 + partitionmanager/doc/en_US/CMakeLists.txt | 1 + partitionmanager/doc/en_US/appendix.docbook | 156 + partitionmanager/doc/en_US/copy_howto_1.png | Bin 0 -> 69028 bytes partitionmanager/doc/en_US/copy_howto_2.png | Bin 0 -> 73863 bytes partitionmanager/doc/en_US/copyhowto.docbook | 50 + partitionmanager/doc/en_US/credits.docbook | 19 + partitionmanager/doc/en_US/faq.docbook | 161 + .../doc/en_US/filesystemsupport.png | Bin 0 -> 71360 bytes partitionmanager/doc/en_US/glossary.docbook | 245 + partitionmanager/doc/en_US/index.docbook | 93 + .../doc/en_US/installos_howto_1.png | Bin 0 -> 73967 bytes .../doc/en_US/installos_howto_2.png | Bin 0 -> 62824 bytes .../doc/en_US/installos_howto_3.png | Bin 0 -> 33704 bytes .../doc/en_US/installos_howto_4.png | Bin 0 -> 35344 bytes .../doc/en_US/installos_howto_5.png | Bin 0 -> 27658 bytes .../doc/en_US/installos_howto_6.png | Bin 0 -> 27756 bytes .../doc/en_US/installos_howto_7.png | Bin 0 -> 93781 bytes .../doc/en_US/installoshowto.docbook | 131 + .../doc/en_US/introduction.docbook | 31 + partitionmanager/doc/en_US/mainwindow.png | Bin 0 -> 92661 bytes .../doc/en_US/referencemanual.docbook | 464 + partitionmanager/doc/en_US/resize_howto_1.png | Bin 0 -> 68722 bytes partitionmanager/doc/en_US/resize_howto_2.png | Bin 0 -> 27422 bytes partitionmanager/doc/en_US/resize_howto_3.png | Bin 0 -> 27125 bytes partitionmanager/doc/en_US/resize_howto_4.png | Bin 0 -> 77358 bytes partitionmanager/doc/en_US/resize_howto_5.png | Bin 0 -> 27431 bytes partitionmanager/doc/en_US/resize_howto_6.png | Bin 0 -> 77957 bytes partitionmanager/doc/en_US/resize_howto_7.png | Bin 0 -> 46701 bytes partitionmanager/doc/en_US/resize_howto_8.png | Bin 0 -> 76831 bytes partitionmanager/doc/en_US/resize_howto_9.png | Bin 0 -> 104208 bytes .../doc/en_US/resizehowto.docbook | 172 + partitionmanager/doc/en_US/usermanual.docbook | 89 + partitionmanager/doc/es/CMakeLists.txt | 1 + partitionmanager/doc/es/appendix.docbook | 192 + partitionmanager/doc/es/copyhowto.docbook | 64 + partitionmanager/doc/es/credits.docbook | 20 + partitionmanager/doc/es/faq.docbook | 154 + partitionmanager/doc/es/glossary.docbook | 354 + partitionmanager/doc/es/index.docbook | 184 + .../doc/es/installoshowto.docbook | 164 + partitionmanager/doc/es/introduction.docbook | 48 + .../doc/es/referencemanual.docbook | 587 + partitionmanager/doc/es/resizehowto.docbook | 236 + partitionmanager/doc/es/usermanual.docbook | 101 + partitionmanager/doc/fr/CMakeLists.txt | 1 + partitionmanager/doc/fr/appendix.docbook | 256 + partitionmanager/doc/fr/copyhowto.docbook | 66 + partitionmanager/doc/fr/credits.docbook | 18 + partitionmanager/doc/fr/faq.docbook | 172 + partitionmanager/doc/fr/glossary.docbook | 368 + partitionmanager/doc/fr/index.docbook | 142 + .../doc/fr/installoshowto.docbook | 180 + partitionmanager/doc/fr/introduction.docbook | 48 + .../doc/fr/referencemanual.docbook | 601 + partitionmanager/doc/fr/resizehowto.docbook | 256 + partitionmanager/doc/fr/usermanual.docbook | 109 + partitionmanager/doc/it/CMakeLists.txt | 1 + partitionmanager/doc/it/appendix.docbook | 194 + partitionmanager/doc/it/copyhowto.docbook | 64 + partitionmanager/doc/it/credits.docbook | 20 + partitionmanager/doc/it/faq.docbook | 154 + partitionmanager/doc/it/glossary.docbook | 354 + partitionmanager/doc/it/index.docbook | 156 + .../doc/it/installoshowto.docbook | 166 + partitionmanager/doc/it/introduction.docbook | 48 + .../doc/it/referencemanual.docbook | 595 + partitionmanager/doc/it/resizehowto.docbook | 238 + partitionmanager/doc/it/usermanual.docbook | 101 + partitionmanager/doc/nl/CMakeLists.txt | 1 + partitionmanager/doc/nl/appendix.docbook | 192 + partitionmanager/doc/nl/copyhowto.docbook | 64 + partitionmanager/doc/nl/credits.docbook | 16 + partitionmanager/doc/nl/faq.docbook | 154 + partitionmanager/doc/nl/glossary.docbook | 354 + partitionmanager/doc/nl/index.docbook | 142 + .../doc/nl/installoshowto.docbook | 166 + partitionmanager/doc/nl/introduction.docbook | 48 + .../doc/nl/referencemanual.docbook | 595 + partitionmanager/doc/nl/resizehowto.docbook | 238 + partitionmanager/doc/nl/usermanual.docbook | 101 + partitionmanager/doc/pt/CMakeLists.txt | 1 + partitionmanager/doc/pt/appendix.docbook | 192 + partitionmanager/doc/pt/copyhowto.docbook | 64 + partitionmanager/doc/pt/credits.docbook | 20 + partitionmanager/doc/pt/faq.docbook | 154 + partitionmanager/doc/pt/glossary.docbook | 354 + partitionmanager/doc/pt/index.docbook | 156 + .../doc/pt/installoshowto.docbook | 166 + partitionmanager/doc/pt/introduction.docbook | 48 + .../doc/pt/referencemanual.docbook | 595 + partitionmanager/doc/pt/resizehowto.docbook | 238 + partitionmanager/doc/pt/usermanual.docbook | 101 + partitionmanager/doc/pt_BR/CMakeLists.txt | 1 + partitionmanager/doc/pt_BR/appendix.docbook | 192 + partitionmanager/doc/pt_BR/copyhowto.docbook | 64 + partitionmanager/doc/pt_BR/credits.docbook | 20 + partitionmanager/doc/pt_BR/faq.docbook | 154 + partitionmanager/doc/pt_BR/glossary.docbook | 354 + partitionmanager/doc/pt_BR/index.docbook | 156 + .../doc/pt_BR/installoshowto.docbook | 166 + .../doc/pt_BR/introduction.docbook | 48 + .../doc/pt_BR/referencemanual.docbook | 595 + .../doc/pt_BR/resizehowto.docbook | 238 + partitionmanager/doc/pt_BR/usermanual.docbook | 101 + partitionmanager/doc/sv/CMakeLists.txt | 1 + partitionmanager/doc/sv/appendix.docbook | 192 + partitionmanager/doc/sv/copyhowto.docbook | 64 + partitionmanager/doc/sv/credits.docbook | 20 + partitionmanager/doc/sv/faq.docbook | 154 + partitionmanager/doc/sv/glossary.docbook | 354 + partitionmanager/doc/sv/index.docbook | 156 + .../doc/sv/installoshowto.docbook | 166 + partitionmanager/doc/sv/introduction.docbook | 48 + .../doc/sv/referencemanual.docbook | 595 + partitionmanager/doc/sv/resizehowto.docbook | 238 + partitionmanager/doc/sv/usermanual.docbook | 101 + partitionmanager/doc/uk/CMakeLists.txt | 1 + partitionmanager/doc/uk/appendix.docbook | 192 + partitionmanager/doc/uk/copyhowto.docbook | 64 + partitionmanager/doc/uk/credits.docbook | 20 + partitionmanager/doc/uk/faq.docbook | 154 + partitionmanager/doc/uk/glossary.docbook | 354 + partitionmanager/doc/uk/index.docbook | 156 + .../doc/uk/installoshowto.docbook | 166 + partitionmanager/doc/uk/introduction.docbook | 48 + .../doc/uk/referencemanual.docbook | 595 + partitionmanager/doc/uk/resizehowto.docbook | 238 + partitionmanager/doc/uk/usermanual.docbook | 101 + partitionmanager/icons/CMakeLists.txt | 1 + .../icons/hi128-apps-partitionmanager.png | Bin 0 -> 12893 bytes .../icons/hi16-apps-partitionmanager.png | Bin 0 -> 875 bytes .../icons/hi22-apps-partitionmanager.png | Bin 0 -> 1321 bytes .../icons/hi32-apps-partitionmanager.png | Bin 0 -> 2131 bytes .../icons/hi48-apps-partitionmanager.png | Bin 0 -> 3623 bytes .../icons/hi64-apps-partitionmanager.png | Bin 0 -> 5135 bytes partitionmanager/lib/CMakeLists.txt | 19 + .../lib/fatlabel/.kdev4/trunk.kdev4 | 11 + partitionmanager/lib/fatlabel/CMakeLists.txt | 50 + partitionmanager/lib/fatlabel/app/main.c | 57 + partitionmanager/lib/fatlabel/buffer.c | 392 + partitionmanager/lib/fatlabel/buffer.h | 26 + partitionmanager/lib/fatlabel/charsetConv.c | 325 + partitionmanager/lib/fatlabel/devices.c | 31 + partitionmanager/lib/fatlabel/devices.h | 51 + partitionmanager/lib/fatlabel/dirCache.c | 335 + partitionmanager/lib/fatlabel/dirCache.h | 60 + partitionmanager/lib/fatlabel/directory.c | 380 + partitionmanager/lib/fatlabel/directory.h | 82 + partitionmanager/lib/fatlabel/fat.c | 796 + partitionmanager/lib/fatlabel/fat.h | 30 + partitionmanager/lib/fatlabel/fatlabel.c | 212 + partitionmanager/lib/fatlabel/fatlabel.h | 33 + partitionmanager/lib/fatlabel/file.c | 537 + partitionmanager/lib/fatlabel/file.h | 29 + partitionmanager/lib/fatlabel/file_name.c | 112 + partitionmanager/lib/fatlabel/file_name.h | 50 + partitionmanager/lib/fatlabel/force_io.c | 60 + partitionmanager/lib/fatlabel/force_io.h | 31 + partitionmanager/lib/fatlabel/fs.h | 108 + partitionmanager/lib/fatlabel/htable.c | 234 + partitionmanager/lib/fatlabel/htable.h | 35 + partitionmanager/lib/fatlabel/init.c | 342 + partitionmanager/lib/fatlabel/init.h | 30 + partitionmanager/lib/fatlabel/llong.c | 57 + partitionmanager/lib/fatlabel/llong.h | 39 + partitionmanager/lib/fatlabel/match.c | 186 + partitionmanager/lib/fatlabel/match.h | 24 + partitionmanager/lib/fatlabel/msdos.h | 197 + partitionmanager/lib/fatlabel/mtools.h | 36 + partitionmanager/lib/fatlabel/nameclash.h | 55 + partitionmanager/lib/fatlabel/partition.h | 49 + partitionmanager/lib/fatlabel/plain_io.c | 425 + partitionmanager/lib/fatlabel/plain_io.h | 33 + partitionmanager/lib/fatlabel/stream.c | 95 + partitionmanager/lib/fatlabel/stream.h | 80 + partitionmanager/lib/fatlabel/vfat.c | 714 + partitionmanager/lib/fatlabel/vfat.h | 104 + partitionmanager/po/CMakeLists.txt | 49 + partitionmanager/po/ar/CMakeLists.txt | 2 + partitionmanager/po/ar/partitionmanager.po | 5251 + partitionmanager/po/bg/CMakeLists.txt | 2 + partitionmanager/po/bg/partitionmanager.po | 5624 + partitionmanager/po/bs/CMakeLists.txt | 2 + partitionmanager/po/bs/partitionmanager.po | 5542 + partitionmanager/po/ca/CMakeLists.txt | 2 + partitionmanager/po/ca/partitionmanager.po | 5548 + .../po/ca@valencia/CMakeLists.txt | 2 + .../po/ca@valencia/partitionmanager.po | 5548 + partitionmanager/po/cs/CMakeLists.txt | 2 + partitionmanager/po/cs/partitionmanager.po | 5091 + partitionmanager/po/da/CMakeLists.txt | 2 + partitionmanager/po/da/partitionmanager.po | 5427 + partitionmanager/po/de/CMakeLists.txt | 2 + partitionmanager/po/de/partitionmanager.po | 5830 ++ partitionmanager/po/el/CMakeLists.txt | 2 + partitionmanager/po/el/partitionmanager.po | 5629 + partitionmanager/po/en_GB/CMakeLists.txt | 2 + partitionmanager/po/en_GB/partitionmanager.po | 5460 + partitionmanager/po/es/CMakeLists.txt | 2 + partitionmanager/po/es/partitionmanager.po | 5494 + partitionmanager/po/et/CMakeLists.txt | 2 + partitionmanager/po/et/partitionmanager.po | 5551 + partitionmanager/po/fr/CMakeLists.txt | 2 + partitionmanager/po/fr/partitionmanager.po | 5557 + partitionmanager/po/gl/CMakeLists.txt | 2 + partitionmanager/po/gl/partitionmanager.po | 5460 + partitionmanager/po/it/CMakeLists.txt | 2 + partitionmanager/po/it/partitionmanager.po | 5602 + partitionmanager/po/lt/CMakeLists.txt | 2 + partitionmanager/po/lt/partitionmanager.po | 5103 + partitionmanager/po/nb/CMakeLists.txt | 2 + partitionmanager/po/nb/partitionmanager.po | 5374 + partitionmanager/po/nds/CMakeLists.txt | 2 + partitionmanager/po/nds/partitionmanager.po | 5844 ++ partitionmanager/po/nl/CMakeLists.txt | 2 + partitionmanager/po/nl/partitionmanager.po | 5616 + partitionmanager/po/pa/CMakeLists.txt | 2 + partitionmanager/po/pa/partitionmanager.po | 5376 + partitionmanager/po/pl/CMakeLists.txt | 2 + partitionmanager/po/pl/partitionmanager.po | 5608 + partitionmanager/po/pt/CMakeLists.txt | 2 + partitionmanager/po/pt/partitionmanager.po | 5471 + partitionmanager/po/pt_BR/CMakeLists.txt | 2 + partitionmanager/po/pt_BR/partitionmanager.po | 5464 + partitionmanager/po/ro/CMakeLists.txt | 2 + partitionmanager/po/ro/partitionmanager.po | 5619 + partitionmanager/po/ru/CMakeLists.txt | 2 + partitionmanager/po/ru/partitionmanager.po | 5726 + partitionmanager/po/sk/CMakeLists.txt | 2 + partitionmanager/po/sk/partitionmanager.po | 5374 + partitionmanager/po/sl/CMakeLists.txt | 2 + partitionmanager/po/sl/partitionmanager.po | 5454 + partitionmanager/po/sr/CMakeLists.txt | 2 + partitionmanager/po/sr/partitionmanager.po | 5536 + .../po/sr@ijekavian/CMakeLists.txt | 2 + .../po/sr@ijekavian/partitionmanager.po | 5542 + .../po/sr@ijekavianlatin/CMakeLists.txt | 2 + .../po/sr@ijekavianlatin/partitionmanager.po | 5551 + partitionmanager/po/sr@latin/CMakeLists.txt | 2 + .../po/sr@latin/partitionmanager.po | 5548 + partitionmanager/po/sv/CMakeLists.txt | 2 + partitionmanager/po/sv/partitionmanager.po | 5379 + partitionmanager/po/tr/CMakeLists.txt | 2 + partitionmanager/po/tr/partitionmanager.po | 5290 + partitionmanager/po/uk/CMakeLists.txt | 2 + partitionmanager/po/uk/partitionmanager.po | 5467 + partitionmanager/po/zh_CN/CMakeLists.txt | 2 + partitionmanager/po/zh_CN/partitionmanager.po | 5259 + partitionmanager/po/zh_TW/CMakeLists.txt | 2 + partitionmanager/po/zh_TW/partitionmanager.po | 5382 + .../scripts/partitionmanagerapp.rb | 39 + partitionmanager/scripts/release.rb | 30 + .../scripts/release/application.rb | 34 + .../scripts/release/releasebuilder.rb | 324 + .../scripts/release/releasecmd.rb | 130 + .../scripts/release/releasedialog.rb | 107 + .../scripts/release/releasedialog.ui | 469 + partitionmanager/scripts/release/tagger.rb | 94 + .../release/translationstatsbuilder.rb | 128 + partitionmanager/scripts/releasegui.rb | 32 + partitionmanager/src/CMakeLists.txt | 61 + partitionmanager/src/Messages.sh | 6 + partitionmanager/src/backend/corebackend.cpp | 70 + partitionmanager/src/backend/corebackend.h | 153 + .../src/backend/corebackenddevice.cpp | 26 + .../src/backend/corebackenddevice.h | 117 + .../src/backend/corebackendmanager.cpp | 81 + .../src/backend/corebackendmanager.h | 81 + .../src/backend/corebackendpartition.cpp | 25 + .../src/backend/corebackendpartition.h | 51 + .../src/backend/corebackendpartitiontable.cpp | 20 + .../src/backend/corebackendpartitiontable.h | 131 + partitionmanager/src/config.kcfg | 123 + partitionmanager/src/config.kcfgc | 8 + .../src/config/advancedpagewidget.cpp | 62 + .../src/config/advancedpagewidget.h | 50 + .../src/config/configureoptionsdialog.cpp | 161 + .../src/config/configureoptionsdialog.h | 62 + .../src/config/configurepageadvanced.ui | 145 + .../config/configurepagefilesystemcolors.ui | 649 + .../src/config/configurepagegeneral.ui | 211 + .../src/config/filesystemcolorspagewidget.cpp | 26 + .../src/config/filesystemcolorspagewidget.h | 34 + .../src/config/generalpagewidget.cpp | 62 + .../src/config/generalpagewidget.h | 50 + partitionmanager/src/core/copysource.cpp | 20 + partitionmanager/src/core/copysource.h | 57 + .../src/core/copysourcedevice.cpp | 119 + partitionmanager/src/core/copysourcedevice.h | 67 + partitionmanager/src/core/copysourcefile.cpp | 66 + partitionmanager/src/core/copysourcefile.h | 62 + partitionmanager/src/core/copysourceshred.cpp | 65 + partitionmanager/src/core/copysourceshred.h | 62 + partitionmanager/src/core/copytarget.cpp | 20 + partitionmanager/src/core/copytarget.h | 59 + .../src/core/copytargetdevice.cpp | 82 + partitionmanager/src/core/copytargetdevice.h | 65 + partitionmanager/src/core/copytargetfile.cpp | 58 + partitionmanager/src/core/copytargetfile.h | 60 + partitionmanager/src/core/device.cpp | 122 + partitionmanager/src/core/device.h | 98 + partitionmanager/src/core/devicescanner.cpp | 69 + partitionmanager/src/core/devicescanner.h | 57 + partitionmanager/src/core/mountentry.cpp | 46 + partitionmanager/src/core/mountentry.h | 49 + partitionmanager/src/core/operationrunner.cpp | 120 + partitionmanager/src/core/operationrunner.h | 79 + partitionmanager/src/core/operationstack.cpp | 550 + partitionmanager/src/core/operationstack.h | 95 + partitionmanager/src/core/partition.cpp | 404 + partitionmanager/src/core/partition.h | 222 + .../src/core/partitionalignment.cpp | 149 + .../src/core/partitionalignment.h | 53 + partitionmanager/src/core/partitionnode.cpp | 216 + partitionmanager/src/core/partitionnode.h | 77 + partitionmanager/src/core/partitionrole.cpp | 41 + partitionmanager/src/core/partitionrole.h | 69 + partitionmanager/src/core/partitiontable.cpp | 503 + partitionmanager/src/core/partitiontable.h | 167 + partitionmanager/src/core/smartattribute.cpp | 252 + partitionmanager/src/core/smartattribute.h | 86 + partitionmanager/src/core/smartstatus.cpp | 283 + partitionmanager/src/core/smartstatus.h | 118 + partitionmanager/src/fs/btrfs.cpp | 181 + partitionmanager/src/fs/btrfs.h | 89 + partitionmanager/src/fs/exfat.cpp | 122 + partitionmanager/src/fs/exfat.h | 89 + partitionmanager/src/fs/ext2.cpp | 157 + partitionmanager/src/fs/ext2.h | 88 + partitionmanager/src/fs/ext3.cpp | 44 + partitionmanager/src/fs/ext3.h | 54 + partitionmanager/src/fs/ext4.cpp | 44 + partitionmanager/src/fs/ext4.h | 54 + partitionmanager/src/fs/extended.cpp | 39 + partitionmanager/src/fs/extended.h | 67 + partitionmanager/src/fs/fat16.cpp | 172 + partitionmanager/src/fs/fat16.h | 88 + partitionmanager/src/fs/fat32.cpp | 70 + partitionmanager/src/fs/fat32.h | 58 + partitionmanager/src/fs/filesystem.cpp | 404 + partitionmanager/src/fs/filesystem.h | 188 + partitionmanager/src/fs/filesystemfactory.cpp | 185 + partitionmanager/src/fs/filesystemfactory.h | 56 + partitionmanager/src/fs/hfs.cpp | 97 + partitionmanager/src/fs/hfs.h | 76 + partitionmanager/src/fs/hfsplus.cpp | 86 + partitionmanager/src/fs/hfsplus.h | 71 + partitionmanager/src/fs/hpfs.cpp | 54 + partitionmanager/src/fs/hpfs.h | 79 + partitionmanager/src/fs/jfs.cpp | 189 + partitionmanager/src/fs/jfs.h | 82 + partitionmanager/src/fs/linuxswap.cpp | 160 + partitionmanager/src/fs/linuxswap.h | 89 + partitionmanager/src/fs/luks.cpp | 215 + partitionmanager/src/fs/luks.h | 98 + partitionmanager/src/fs/lvm2_pv.cpp | 114 + partitionmanager/src/fs/lvm2_pv.h | 88 + partitionmanager/src/fs/nilfs2.cpp | 184 + partitionmanager/src/fs/nilfs2.h | 90 + partitionmanager/src/fs/ntfs.cpp | 231 + partitionmanager/src/fs/ntfs.h | 91 + partitionmanager/src/fs/ocfs2.cpp | 157 + partitionmanager/src/fs/ocfs2.h | 88 + partitionmanager/src/fs/reiser4.cpp | 129 + partitionmanager/src/fs/reiser4.h | 75 + partitionmanager/src/fs/reiserfs.cpp | 172 + partitionmanager/src/fs/reiserfs.h | 91 + partitionmanager/src/fs/ufs.cpp | 32 + partitionmanager/src/fs/ufs.h | 58 + partitionmanager/src/fs/unformatted.cpp | 35 + partitionmanager/src/fs/unformatted.h | 58 + partitionmanager/src/fs/unknown.cpp | 28 + partitionmanager/src/fs/unknown.h | 46 + partitionmanager/src/fs/xfs.cpp | 197 + partitionmanager/src/fs/xfs.h | 83 + partitionmanager/src/fs/zfs.cpp | 104 + partitionmanager/src/fs/zfs.h | 84 + .../src/gui/applyprogressdetailswidget.cpp | 20 + .../src/gui/applyprogressdetailswidget.h | 42 + .../src/gui/applyprogressdetailswidgetbase.ui | 59 + .../src/gui/applyprogressdialog.cpp | 442 + .../src/gui/applyprogressdialog.h | 140 + .../src/gui/applyprogressdialogwidget.cpp | 20 + .../src/gui/applyprogressdialogwidget.h | 42 + .../src/gui/applyprogressdialogwidgetbase.ui | 133 + .../src/gui/createpartitiontabledialog.cpp | 70 + .../src/gui/createpartitiontabledialog.h | 56 + .../src/gui/createpartitiontablewidget.cpp | 32 + .../src/gui/createpartitiontablewidget.h | 47 + .../src/gui/createpartitiontablewidgetbase.ui | 105 + .../src/gui/decryptluksdialog.cpp | 39 + partitionmanager/src/gui/decryptluksdialog.h | 55 + .../src/gui/decryptluksdialogwidget.cpp | 26 + .../src/gui/decryptluksdialogwidget.h | 46 + .../src/gui/decryptluksdialogwidgetbase.ui | 78 + .../src/gui/devicepropsdialog.cpp | 166 + partitionmanager/src/gui/devicepropsdialog.h | 72 + .../src/gui/devicepropswidget.cpp | 34 + partitionmanager/src/gui/devicepropswidget.h | 63 + .../src/gui/devicepropswidgetbase.ui | 342 + .../src/gui/editmountoptionsdialog.cpp | 52 + .../src/gui/editmountoptionsdialog.h | 50 + .../src/gui/editmountoptionsdialogwidget.cpp | 32 + .../src/gui/editmountoptionsdialogwidget.h | 40 + .../gui/editmountoptionsdialogwidgetbase.ui | 31 + .../src/gui/editmountpointdialog.cpp | 63 + .../src/gui/editmountpointdialog.h | 55 + .../src/gui/editmountpointdialogwidget.cpp | 305 + .../src/gui/editmountpointdialogwidget.h | 85 + .../src/gui/editmountpointdialogwidgetbase.ui | 332 + .../src/gui/filesystemsupportdialog.cpp | 111 + .../src/gui/filesystemsupportdialog.h | 63 + .../src/gui/filesystemsupportdialogwidget.cpp | 26 + .../src/gui/filesystemsupportdialogwidget.h | 38 + .../gui/filesystemsupportdialogwidgetbase.ui | 165 + partitionmanager/src/gui/formattedspinbox.cpp | 64 + partitionmanager/src/gui/formattedspinbox.h | 39 + partitionmanager/src/gui/infopane.cpp | 185 + partitionmanager/src/gui/infopane.h | 61 + partitionmanager/src/gui/insertdialog.cpp | 69 + partitionmanager/src/gui/insertdialog.h | 53 + partitionmanager/src/gui/listdevices.cpp | 99 + partitionmanager/src/gui/listdevices.h | 74 + partitionmanager/src/gui/listdevicesbase.ui | 40 + partitionmanager/src/gui/listoperations.cpp | 59 + partitionmanager/src/gui/listoperations.h | 71 + .../src/gui/listoperationsbase.ui | 46 + partitionmanager/src/gui/mainwindow.cpp | 1077 + partitionmanager/src/gui/mainwindow.h | 175 + partitionmanager/src/gui/mainwindowbase.ui | 107 + partitionmanager/src/gui/newdialog.cpp | 193 + partitionmanager/src/gui/newdialog.h | 66 + .../src/gui/partitionmanagerui.rc | 84 + .../src/gui/partitionmanagerwidget.cpp | 783 + .../src/gui/partitionmanagerwidget.h | 132 + .../src/gui/partitionmanagerwidgetbase.ui | 142 + partitionmanager/src/gui/partpropsdialog.cpp | 373 + partitionmanager/src/gui/partpropsdialog.h | 104 + partitionmanager/src/gui/partpropswidget.h | 80 + .../src/gui/partpropswidgetbase.ui | 379 + .../src/gui/partresizerwidget.cpp | 506 + partitionmanager/src/gui/partresizerwidget.h | 159 + partitionmanager/src/gui/parttablewidget.cpp | 180 + partitionmanager/src/gui/parttablewidget.h | 81 + partitionmanager/src/gui/partwidget.cpp | 171 + partitionmanager/src/gui/partwidget.h | 68 + partitionmanager/src/gui/partwidgetbase.cpp | 158 + partitionmanager/src/gui/partwidgetbase.h | 64 + partitionmanager/src/gui/resizedialog.cpp | 128 + partitionmanager/src/gui/resizedialog.h | 73 + .../src/gui/scanprogressdialog.cpp | 46 + partitionmanager/src/gui/scanprogressdialog.h | 44 + .../src/gui/sizedetailswidget.cpp | 34 + partitionmanager/src/gui/sizedetailswidget.h | 50 + .../src/gui/sizedetailswidgetbase.ui | 177 + partitionmanager/src/gui/sizedialogbase.cpp | 398 + partitionmanager/src/gui/sizedialogbase.h | 109 + partitionmanager/src/gui/sizedialogwidget.cpp | 20 + partitionmanager/src/gui/sizedialogwidget.h | 65 + .../src/gui/sizedialogwidgetbase.ui | 335 + partitionmanager/src/gui/smartdialog.cpp | 241 + partitionmanager/src/gui/smartdialog.h | 68 + .../src/gui/smartdialogwidget.cpp | 136 + partitionmanager/src/gui/smartdialogwidget.h | 69 + .../src/gui/smartdialogwidgetbase.ui | 438 + partitionmanager/src/gui/treelog.cpp | 179 + partitionmanager/src/gui/treelog.h | 72 + partitionmanager/src/gui/treelogbase.ui | 76 + .../src/jobs/backupfilesystemjob.cpp | 80 + .../src/jobs/backupfilesystemjob.h | 63 + .../src/jobs/checkfilesystemjob.cpp | 57 + .../src/jobs/checkfilesystemjob.h | 51 + .../src/jobs/copyfilesystemjob.cpp | 106 + partitionmanager/src/jobs/copyfilesystemjob.h | 70 + .../src/jobs/createfilesystemjob.cpp | 92 + .../src/jobs/createfilesystemjob.h | 56 + .../src/jobs/createpartitionjob.cpp | 94 + .../src/jobs/createpartitionjob.h | 56 + .../src/jobs/createpartitiontablejob.cpp | 69 + .../src/jobs/createpartitiontablejob.h | 51 + .../src/jobs/deletefilesystemjob.cpp | 105 + .../src/jobs/deletefilesystemjob.h | 59 + .../src/jobs/deletepartitionjob.cpp | 94 + .../src/jobs/deletepartitionjob.h | 56 + partitionmanager/src/jobs/job.cpp | 241 + partitionmanager/src/jobs/job.h | 97 + .../src/jobs/movefilesystemjob.cpp | 95 + partitionmanager/src/jobs/movefilesystemjob.h | 63 + .../src/jobs/resizefilesystemjob.cpp | 156 + .../src/jobs/resizefilesystemjob.h | 69 + .../src/jobs/restorefilesystemjob.cpp | 117 + .../src/jobs/restorefilesystemjob.h | 63 + .../src/jobs/setfilesystemlabeljob.cpp | 68 + .../src/jobs/setfilesystemlabeljob.h | 58 + partitionmanager/src/jobs/setpartflagsjob.cpp | 123 + partitionmanager/src/jobs/setpartflagsjob.h | 65 + .../src/jobs/setpartgeometryjob.cpp | 92 + .../src/jobs/setpartgeometryjob.h | 68 + .../src/jobs/shredfilesystemjob.cpp | 89 + .../src/jobs/shredfilesystemjob.h | 60 + partitionmanager/src/main.cpp | 61 + partitionmanager/src/ops/backupoperation.cpp | 71 + partitionmanager/src/ops/backupoperation.h | 72 + partitionmanager/src/ops/checkoperation.cpp | 79 + partitionmanager/src/ops/checkoperation.h | 74 + partitionmanager/src/ops/copyoperation.cpp | 336 + partitionmanager/src/ops/copyoperation.h | 119 + .../src/ops/createfilesystemoperation.cpp | 98 + .../src/ops/createfilesystemoperation.h | 88 + .../src/ops/createpartitiontableoperation.cpp | 104 + .../src/ops/createpartitiontableoperation.h | 81 + partitionmanager/src/ops/deleteoperation.cpp | 121 + partitionmanager/src/ops/deleteoperation.h | 83 + partitionmanager/src/ops/newoperation.cpp | 135 + partitionmanager/src/ops/newoperation.h | 89 + partitionmanager/src/ops/operation.cpp | 181 + partitionmanager/src/ops/operation.h | 142 + partitionmanager/src/ops/resizeoperation.cpp | 400 + partitionmanager/src/ops/resizeoperation.h | 140 + partitionmanager/src/ops/restoreoperation.cpp | 237 + partitionmanager/src/ops/restoreoperation.h | 107 + .../src/ops/setfilesystemlabeloperation.cpp | 74 + .../src/ops/setfilesystemlabeloperation.h | 76 + .../src/ops/setpartflagsoperation.cpp | 78 + .../src/ops/setpartflagsoperation.h | 83 + .../src/org.kde.PartitionManager.xml | 6 + .../src/partitionmanager.appdata.xml | 133 + partitionmanager/src/partitionmanager.desktop | 158 + partitionmanager/src/plugins/CMakeLists.txt | 33 + .../src/plugins/dummy/CMakeLists.txt | 26 + .../src/plugins/dummy/dummybackend.cpp | 123 + .../src/plugins/dummy/dummybackend.h | 56 + .../src/plugins/dummy/dummydevice.cpp | 98 + .../src/plugins/dummy/dummydevice.h | 54 + .../src/plugins/dummy/dummypartition.cpp | 41 + .../src/plugins/dummy/dummypartition.h | 42 + .../src/plugins/dummy/dummypartitiontable.cpp | 129 + .../src/plugins/dummy/dummypartitiontable.h | 57 + .../dummy/pmdummybackendplugin.desktop | 99 + .../src/plugins/libparted/CMakeLists.txt | 37 + .../plugins/libparted/libpartedbackend.cpp | 607 + .../src/plugins/libparted/libpartedbackend.h | 76 + .../src/plugins/libparted/libparteddevice.cpp | 137 + .../src/plugins/libparted/libparteddevice.h | 62 + .../plugins/libparted/libpartedpartition.cpp | 57 + .../plugins/libparted/libpartedpartition.h | 50 + .../libparted/libpartedpartitiontable.cpp | 359 + .../libparted/libpartedpartitiontable.h | 68 + .../pmlibpartedbackendplugin.desktop | 99 + .../src/plugins/pmcorebackendplugin.desktop | 48 + partitionmanager/src/util/capacity.cpp | 152 + partitionmanager/src/util/capacity.h | 78 + partitionmanager/src/util/externalcommand.cpp | 186 + partitionmanager/src/util/externalcommand.h | 92 + partitionmanager/src/util/globallog.cpp | 44 + partitionmanager/src/util/globallog.h | 92 + partitionmanager/src/util/helpers.cpp | 270 + partitionmanager/src/util/helpers.h | 57 + partitionmanager/src/util/htmlreport.cpp | 97 + partitionmanager/src/util/htmlreport.h | 37 + .../src/util/libpartitionmanagerexport.h | 29 + partitionmanager/src/util/report.cpp | 174 + partitionmanager/src/util/report.h | 137 + print-manager/CMakeLists.txt | 35 + print-manager/COPYING | 339 + print-manager/Messages.sh | 8 + print-manager/README | 25 + print-manager/add-printer/AddPrinter.cpp | 112 + print-manager/add-printer/AddPrinter.h | 61 + .../add-printer/AddPrinterAssistant.cpp | 251 + .../add-printer/AddPrinterAssistant.h | 58 + print-manager/add-printer/CMakeLists.txt | 43 + print-manager/add-printer/ChooseLpd.cpp | 92 + print-manager/add-printer/ChooseLpd.h | 52 + print-manager/add-printer/ChooseLpd.ui | 68 + print-manager/add-printer/ChooseSamba.cpp | 134 + print-manager/add-printer/ChooseSamba.h | 51 + print-manager/add-printer/ChooseSamba.ui | 123 + print-manager/add-printer/ChooseSerial.cpp | 121 + print-manager/add-printer/ChooseSerial.h | 52 + print-manager/add-printer/ChooseSerial.ui | 100 + print-manager/add-printer/ChooseSocket.cpp | 90 + print-manager/add-printer/ChooseSocket.h | 49 + print-manager/add-printer/ChooseSocket.ui | 81 + print-manager/add-printer/ChooseUri.cpp | 177 + print-manager/add-printer/ChooseUri.h | 70 + print-manager/add-printer/ChooseUri.ui | 97 + print-manager/add-printer/DevicesModel.cpp | 383 + print-manager/add-printer/DevicesModel.h | 110 + print-manager/add-printer/GenericPage.cpp | 51 + print-manager/add-printer/GenericPage.h | 60 + print-manager/add-printer/PageAddPrinter.cpp | 176 + print-manager/add-printer/PageAddPrinter.h | 54 + print-manager/add-printer/PageAddPrinter.ui | 150 + print-manager/add-printer/PageChoosePPD.cpp | 211 + print-manager/add-printer/PageChoosePPD.h | 62 + print-manager/add-printer/PageChoosePPD.ui | 27 + .../add-printer/PageChoosePrinters.cpp | 91 + .../add-printer/PageChoosePrinters.h | 45 + .../add-printer/PageChoosePrinters.ui | 116 + .../add-printer/PageDestinations.cpp | 405 + print-manager/add-printer/PageDestinations.h | 83 + print-manager/add-printer/PageDestinations.ui | 130 + print-manager/add-printer/main.cpp | 78 + print-manager/cmake/modules/FindCUPS.cmake | 59 + print-manager/config.h.cmake | 7 + .../configure-printer/CMakeLists.txt | 34 + .../configure-printer/ConfigureDialog.cpp | 202 + .../configure-printer/ConfigureDialog.h | 50 + .../configure-printer/ConfigurePrinter.cpp | 56 + .../configure-printer/ConfigurePrinter.h | 40 + .../ConfigurePrinterInterface.cpp | 130 + .../ConfigurePrinterInterface.h | 47 + .../configure-printer/ModifyPrinter.cpp | 334 + .../configure-printer/ModifyPrinter.h | 78 + .../configure-printer/ModifyPrinter.ui | 185 + .../configure-printer/PrinterBehavior.cpp | 325 + .../configure-printer/PrinterBehavior.h | 63 + .../configure-printer/PrinterBehavior.ui | 170 + .../configure-printer/PrinterOptions.cpp | 823 + .../configure-printer/PrinterOptions.h | 77 + .../configure-printer/PrinterOptions.ui | 69 + .../configure-printer/PrinterPage.cpp | 38 + print-manager/configure-printer/PrinterPage.h | 44 + print-manager/configure-printer/main.cpp | 56 + .../org.kde.ConfigurePrinter.service.in | 3 + .../org.kde.ConfigurePrinter.xml | 8 + .../declarative-plugins/CMakeLists.txt | 13 + print-manager/declarative-plugins/qmldir | 1 + .../declarative-plugins/qmlplugins.cpp | 38 + .../declarative-plugins/qmlplugins.h | 31 + print-manager/libkcups/CMakeLists.txt | 29 + print-manager/libkcups/ClassListWidget.cpp | 189 + print-manager/libkcups/ClassListWidget.h | 70 + print-manager/libkcups/JobModel.cpp | 654 + print-manager/libkcups/JobModel.h | 151 + print-manager/libkcups/JobSortFilterModel.cpp | 132 + print-manager/libkcups/JobSortFilterModel.h | 58 + print-manager/libkcups/KCupsConnection.cpp | 878 + print-manager/libkcups/KCupsConnection.h | 404 + print-manager/libkcups/KCupsJob.cpp | 207 + print-manager/libkcups/KCupsJob.h | 76 + .../libkcups/KCupsPasswordDialog.cpp | 84 + print-manager/libkcups/KCupsPasswordDialog.h | 48 + print-manager/libkcups/KCupsPrinter.cpp | 197 + print-manager/libkcups/KCupsPrinter.h | 94 + print-manager/libkcups/KCupsRequest.cpp | 669 + print-manager/libkcups/KCupsRequest.h | 323 + print-manager/libkcups/KCupsServer.cpp | 98 + print-manager/libkcups/KCupsServer.h | 60 + print-manager/libkcups/KIppRequest.cpp | 273 + print-manager/libkcups/KIppRequest.h | 63 + print-manager/libkcups/KIppRequest_p.h | 66 + .../libkcups/NoSelectionRectDelegate.cpp | 38 + .../libkcups/NoSelectionRectDelegate.h | 35 + print-manager/libkcups/PPDModel.cpp | 129 + print-manager/libkcups/PPDModel.h | 60 + print-manager/libkcups/PrinterModel.cpp | 553 + print-manager/libkcups/PrinterModel.h | 117 + .../libkcups/PrinterSortFilterModel.cpp | 96 + .../libkcups/PrinterSortFilterModel.h | 55 + print-manager/libkcups/SelectMakeModel.cpp | 319 + print-manager/libkcups/SelectMakeModel.h | 79 + print-manager/libkcups/SelectMakeModel.ui | 174 + print-manager/plasmoid/CMakeLists.txt | 16 + .../plasmoid/KPrintManagerConfigPlugin.cpp | 75 + .../plasmoid/KPrintManagerConfigPlugin.h | 43 + .../plasmoid/package/contents/config/main.xml | 22 + .../contents/ui/CompactRepresentation.qml | 52 + .../plasmoid/package/contents/ui/JobItem.qml | 268 + .../package/contents/ui/NIHSwitch.qml | 208 + .../package/contents/ui/PrinterItem.qml | 181 + .../contents/ui/ScrollableListView.qml | 58 + .../package/contents/ui/StatusView.qml | 55 + .../plasmoid/package/contents/ui/config.ui | 68 + .../package/contents/ui/printmanager.qml | 238 + .../contents/ui/private/RoundShadow.qml | 134 + .../plasmoid/package/metadata.desktop | 113 + .../print-manager-kded/CMakeLists.txt | 21 + .../NewPrinterNotification.cpp | 298 + .../NewPrinterNotification.h | 55 + .../print-manager-kded/PrintManagerKded.cpp | 51 + .../print-manager-kded/PrintManagerKded.h | 43 + .../com.redhat.NewPrinterNotification.xml | 15 + .../print-manager-kded/printmanager.desktop | 98 + .../print-manager-kded/printmanager.notifyrc | 189 + .../printer-manager-kcm/CMakeLists.txt | 20 + .../printer-manager-kcm/PrintKCM.cpp | 394 + print-manager/printer-manager-kcm/PrintKCM.h | 70 + print-manager/printer-manager-kcm/PrintKCM.ui | 266 + .../printer-manager-kcm/PrinterDelegate.cpp | 185 + .../printer-manager-kcm/PrinterDelegate.h | 51 + .../PrinterDescription.cpp | 292 + .../printer-manager-kcm/PrinterDescription.h | 91 + .../printer-manager-kcm/PrinterDescription.ui | 320 + .../kcm_printer_manager.desktop | 108 + print-manager/printqueue/CMakeLists.txt | 22 + print-manager/printqueue/PrintQueue.cpp | 129 + print-manager/printqueue/PrintQueue.h | 43 + print-manager/printqueue/PrintQueueUi.cpp | 644 + print-manager/printqueue/PrintQueueUi.h | 93 + print-manager/printqueue/PrintQueueUi.ui | 340 + print-manager/printqueue/main.cpp | 56 + 4167 files changed, 871997 insertions(+) create mode 100644 bluedevil/CMakeLists.txt create mode 100644 bluedevil/HACKING create mode 100644 bluedevil/README create mode 100644 bluedevil/bluedevil.kdev4 create mode 100644 bluedevil/cmake/CMakeLists.txt create mode 100644 bluedevil/cmake/modules/CMakeLists.txt create mode 100644 bluedevil/cmake/modules/FindLibBlueDevil.cmake create mode 100644 bluedevil/cmake/modules/PkgConfigGetVar.cmake create mode 100644 bluedevil/po/CMakeLists.txt create mode 100644 bluedevil/po/ar/CMakeLists.txt create mode 100644 bluedevil/po/ar/bluedevil.po create mode 100644 bluedevil/po/bs/CMakeLists.txt create mode 100644 bluedevil/po/bs/bluedevil.po create mode 100644 bluedevil/po/ca/CMakeLists.txt create mode 100644 bluedevil/po/ca/bluedevil.po create mode 100644 bluedevil/po/ca@valencia/CMakeLists.txt create mode 100644 bluedevil/po/ca@valencia/bluedevil.po create mode 100644 bluedevil/po/cs/CMakeLists.txt create mode 100644 bluedevil/po/cs/bluedevil.po create mode 100644 bluedevil/po/da/CMakeLists.txt create mode 100644 bluedevil/po/da/bluedevil.po create mode 100644 bluedevil/po/de/CMakeLists.txt create mode 100644 bluedevil/po/de/bluedevil.po create mode 100644 bluedevil/po/el/CMakeLists.txt create mode 100644 bluedevil/po/el/bluedevil.po create mode 100644 bluedevil/po/en_GB/CMakeLists.txt create mode 100644 bluedevil/po/en_GB/bluedevil.po create mode 100644 bluedevil/po/eo/CMakeLists.txt create mode 100644 bluedevil/po/eo/bluedevil.po create mode 100644 bluedevil/po/es/CMakeLists.txt create mode 100644 bluedevil/po/es/bluedevil.po create mode 100644 bluedevil/po/et/CMakeLists.txt create mode 100644 bluedevil/po/et/bluedevil.po create mode 100644 bluedevil/po/eu/CMakeLists.txt create mode 100644 bluedevil/po/eu/bluedevil.po create mode 100644 bluedevil/po/fa/CMakeLists.txt create mode 100644 bluedevil/po/fa/bluedevil.po create mode 100644 bluedevil/po/fi/CMakeLists.txt create mode 100644 bluedevil/po/fi/bluedevil.po create mode 100644 bluedevil/po/fr/CMakeLists.txt create mode 100644 bluedevil/po/fr/bluedevil.po create mode 100644 bluedevil/po/ga/CMakeLists.txt create mode 100644 bluedevil/po/ga/bluedevil.po create mode 100644 bluedevil/po/gl/CMakeLists.txt create mode 100644 bluedevil/po/gl/bluedevil.po create mode 100644 bluedevil/po/hu/CMakeLists.txt create mode 100644 bluedevil/po/hu/bluedevil.po create mode 100644 bluedevil/po/it/CMakeLists.txt create mode 100644 bluedevil/po/it/bluedevil.po create mode 100644 bluedevil/po/ja/CMakeLists.txt create mode 100644 bluedevil/po/ja/bluedevil.po create mode 100644 bluedevil/po/kk/CMakeLists.txt create mode 100644 bluedevil/po/kk/bluedevil.po create mode 100644 bluedevil/po/km/CMakeLists.txt create mode 100644 bluedevil/po/km/bluedevil.po create mode 100644 bluedevil/po/ko/CMakeLists.txt create mode 100644 bluedevil/po/ko/bluedevil.po create mode 100644 bluedevil/po/lt/CMakeLists.txt create mode 100644 bluedevil/po/lt/bluedevil.po create mode 100644 bluedevil/po/mai/CMakeLists.txt create mode 100644 bluedevil/po/mai/bluedevil.po create mode 100644 bluedevil/po/mr/CMakeLists.txt create mode 100644 bluedevil/po/mr/bluedevil.po create mode 100644 bluedevil/po/ms/CMakeLists.txt create mode 100644 bluedevil/po/ms/bluedevil.po create mode 100644 bluedevil/po/nb/CMakeLists.txt create mode 100644 bluedevil/po/nb/bluedevil.po create mode 100644 bluedevil/po/nds/CMakeLists.txt create mode 100644 bluedevil/po/nds/bluedevil.po create mode 100644 bluedevil/po/nl/CMakeLists.txt create mode 100644 bluedevil/po/nl/bluedevil.po create mode 100644 bluedevil/po/pa/CMakeLists.txt create mode 100644 bluedevil/po/pa/bluedevil.po create mode 100644 bluedevil/po/pl/CMakeLists.txt create mode 100644 bluedevil/po/pl/bluedevil.po create mode 100644 bluedevil/po/pt/CMakeLists.txt create mode 100644 bluedevil/po/pt/bluedevil.po create mode 100644 bluedevil/po/pt_BR/CMakeLists.txt create mode 100644 bluedevil/po/pt_BR/bluedevil.po create mode 100644 bluedevil/po/ro/CMakeLists.txt create mode 100644 bluedevil/po/ro/bluedevil.po create mode 100644 bluedevil/po/ru/CMakeLists.txt create mode 100644 bluedevil/po/ru/bluedevil.po create mode 100644 bluedevil/po/sk/CMakeLists.txt create mode 100644 bluedevil/po/sk/bluedevil.po create mode 100644 bluedevil/po/sl/CMakeLists.txt create mode 100644 bluedevil/po/sl/bluedevil.po create mode 100644 bluedevil/po/sr/CMakeLists.txt create mode 100644 bluedevil/po/sr/bluedevil.po create mode 100644 bluedevil/po/sr@ijekavian/CMakeLists.txt create mode 100644 bluedevil/po/sr@ijekavian/bluedevil.po create mode 100644 bluedevil/po/sr@ijekavianlatin/CMakeLists.txt create mode 100644 bluedevil/po/sr@ijekavianlatin/bluedevil.po create mode 100644 bluedevil/po/sr@latin/CMakeLists.txt create mode 100644 bluedevil/po/sr@latin/bluedevil.po create mode 100644 bluedevil/po/sv/CMakeLists.txt create mode 100644 bluedevil/po/sv/bluedevil.po create mode 100644 bluedevil/po/th/CMakeLists.txt create mode 100644 bluedevil/po/th/bluedevil.po create mode 100644 bluedevil/po/tr/CMakeLists.txt create mode 100644 bluedevil/po/tr/bluedevil.po create mode 100644 bluedevil/po/ug/CMakeLists.txt create mode 100644 bluedevil/po/ug/bluedevil.po create mode 100644 bluedevil/po/uk/CMakeLists.txt create mode 100644 bluedevil/po/uk/bluedevil.po create mode 100644 bluedevil/po/zh_CN/CMakeLists.txt create mode 100644 bluedevil/po/zh_CN/bluedevil.po create mode 100644 bluedevil/po/zh_TW/CMakeLists.txt create mode 100644 bluedevil/po/zh_TW/bluedevil.po create mode 100644 bluedevil/src/CMakeLists.txt create mode 100755 bluedevil/src/Messages.sh create mode 100644 bluedevil/src/bluedevil-mime.xml create mode 100644 bluedevil/src/bluedevil.notifyrc create mode 100644 bluedevil/src/daemon/CMakeLists.txt create mode 100644 bluedevil/src/daemon/helpers/CMakeLists.txt create mode 100644 bluedevil/src/daemon/helpers/authorize/CMakeLists.txt create mode 100644 bluedevil/src/daemon/helpers/authorize/authorize.cpp create mode 100644 bluedevil/src/daemon/helpers/authorize/authorize.h create mode 100644 bluedevil/src/daemon/helpers/authorize/main.cpp create mode 100644 bluedevil/src/daemon/helpers/confirmmodechange/CMakeLists.txt create mode 100644 bluedevil/src/daemon/helpers/confirmmodechange/confirmmodechange.cpp create mode 100644 bluedevil/src/daemon/helpers/confirmmodechange/confirmmodechange.h create mode 100644 bluedevil/src/daemon/helpers/confirmmodechange/main.cpp create mode 100644 bluedevil/src/daemon/helpers/requestconfirmation/CMakeLists.txt create mode 100644 bluedevil/src/daemon/helpers/requestconfirmation/main.cpp create mode 100644 bluedevil/src/daemon/helpers/requestconfirmation/requestconfirmation.cpp create mode 100644 bluedevil/src/daemon/helpers/requestconfirmation/requestconfirmation.h create mode 100644 bluedevil/src/daemon/helpers/requestpin/CMakeLists.txt create mode 100644 bluedevil/src/daemon/helpers/requestpin/dialogWidget.ui create mode 100644 bluedevil/src/daemon/helpers/requestpin/main.cpp create mode 100644 bluedevil/src/daemon/helpers/requestpin/requestpin.cpp create mode 100644 bluedevil/src/daemon/helpers/requestpin/requestpin.h create mode 100644 bluedevil/src/daemon/kded/BlueDevilDaemon.cpp create mode 100644 bluedevil/src/daemon/kded/BlueDevilDaemon.h create mode 100644 bluedevil/src/daemon/kded/CMakeLists.txt create mode 100644 bluedevil/src/daemon/kded/bluedevil.desktop create mode 100644 bluedevil/src/daemon/kded/bluezagent.cpp create mode 100644 bluedevil/src/daemon/kded/bluezagent.h create mode 100644 bluedevil/src/daemon/kded/filereceiver/filereceiver.cpp create mode 100644 bluedevil/src/daemon/kded/filereceiver/filereceiver.h create mode 100644 bluedevil/src/daemon/kded/filereceiver/obexagent.cpp create mode 100644 bluedevil/src/daemon/kded/filereceiver/obexagent.h create mode 100644 bluedevil/src/daemon/kded/filereceiver/org.bluez.obex.AgentManager1.xml create mode 100644 bluedevil/src/daemon/kded/filereceiver/org.bluez.obex.Session1.xml create mode 100644 bluedevil/src/daemon/kded/filereceiver/org.bluez.obex.Transfer1.xml create mode 100644 bluedevil/src/daemon/kded/filereceiver/org.freedesktop.DBus.Properties.xml create mode 100644 bluedevil/src/daemon/kded/filereceiver/receivefilejob.cpp create mode 100644 bluedevil/src/daemon/kded/filereceiver/receivefilejob.h create mode 100644 bluedevil/src/fileitemactionplugin/CMakeLists.txt create mode 100644 bluedevil/src/fileitemactionplugin/bluedevilsendfile.desktop create mode 100644 bluedevil/src/fileitemactionplugin/sendfileitemaction.cpp create mode 100644 bluedevil/src/fileitemactionplugin/sendfileitemaction.h create mode 100644 bluedevil/src/kcmodule/CMakeLists.txt create mode 100644 bluedevil/src/kcmodule/bluedeviladapters.cpp create mode 100644 bluedevil/src/kcmodule/bluedeviladapters.desktop create mode 100644 bluedevil/src/kcmodule/bluedeviladapters.h create mode 100644 bluedevil/src/kcmodule/bluedevildevices.cpp create mode 100644 bluedevil/src/kcmodule/bluedevildevices.desktop create mode 100644 bluedevil/src/kcmodule/bluedevildevices.h create mode 100644 bluedevil/src/kcmodule/bluedeviltransfer.cpp create mode 100644 bluedevil/src/kcmodule/bluedeviltransfer.desktop create mode 100644 bluedevil/src/kcmodule/bluedeviltransfer.h create mode 100644 bluedevil/src/kcmodule/columnresizer.cpp create mode 100644 bluedevil/src/kcmodule/columnresizer.h create mode 100644 bluedevil/src/kcmodule/devicedetails.cpp create mode 100644 bluedevil/src/kcmodule/devicedetails.h create mode 100644 bluedevil/src/kcmodule/kded.cpp create mode 100644 bluedevil/src/kcmodule/kded.h create mode 100644 bluedevil/src/kcmodule/org.kde.BlueDevil.Service.xml create mode 100644 bluedevil/src/kcmodule/sharedfilesdialog/linkproxymodel.cpp create mode 100644 bluedevil/src/kcmodule/sharedfilesdialog/linkproxymodel.h create mode 100644 bluedevil/src/kcmodule/sharedfilesdialog/sharedfiles.ui create mode 100644 bluedevil/src/kcmodule/sharedfilesdialog/sharedfilesdialog.cpp create mode 100644 bluedevil/src/kcmodule/sharedfilesdialog/sharedfilesdialog.h create mode 100644 bluedevil/src/kcmodule/systemcheck.cpp create mode 100644 bluedevil/src/kcmodule/systemcheck.h create mode 100644 bluedevil/src/kcmodule/transfer.ui create mode 100644 bluedevil/src/kio/CMakeLists.txt create mode 100644 bluedevil/src/kio/bluetooth/.kdev4/bluetooth.kdev4 create mode 100644 bluedevil/src/kio/bluetooth/CMakeLists.txt create mode 100644 bluedevil/src/kio/bluetooth/bluetooth.kdev4 create mode 100644 bluedevil/src/kio/bluetooth/bluetooth.protocol create mode 100644 bluedevil/src/kio/bluetooth/kded_bluedevil.xml create mode 100644 bluedevil/src/kio/bluetooth/kiobluetooth.cpp create mode 100644 bluedevil/src/kio/bluetooth/kiobluetooth.h create mode 100644 bluedevil/src/kio/bluetooth/types.h create mode 100644 bluedevil/src/kio/obexftp/CMakeLists.txt create mode 100644 bluedevil/src/kio/obexftp/daemon/CMakeLists.txt create mode 100644 bluedevil/src/kio/obexftp/daemon/HACKING create mode 100644 bluedevil/src/kio/obexftp/daemon/ObexFtpDaemon.cpp create mode 100644 bluedevil/src/kio/obexftp/daemon/ObexFtpDaemon.h create mode 100644 bluedevil/src/kio/obexftp/daemon/createsessionjob.cpp create mode 100644 bluedevil/src/kio/obexftp/daemon/createsessionjob.h create mode 100644 bluedevil/src/kio/obexftp/daemon/obexftpdaemon.desktop create mode 100644 bluedevil/src/kio/obexftp/daemon/org.freedesktop.DBus.ObjectManager.xml create mode 100644 bluedevil/src/kio/obexftp/kded_obexftp.xml create mode 100644 bluedevil/src/kio/obexftp/kio_obexftp.cpp create mode 100644 bluedevil/src/kio/obexftp/kio_obexftp.h create mode 100644 bluedevil/src/kio/obexftp/obexdtypes.h create mode 100644 bluedevil/src/kio/obexftp/obexftp.protocol create mode 100644 bluedevil/src/kio/obexftp/org.bluez.obex.FileTransfer1.xml create mode 100644 bluedevil/src/kio/obexftp/transferfilejob.cpp create mode 100644 bluedevil/src/kio/obexftp/transferfilejob.h create mode 100644 bluedevil/src/monolithic/CMakeLists.txt create mode 100644 bluedevil/src/monolithic/bluedevil-monolithic.desktop create mode 100644 bluedevil/src/monolithic/main.cpp create mode 100644 bluedevil/src/monolithic/monolithic.cpp create mode 100644 bluedevil/src/monolithic/monolithic.h create mode 100644 bluedevil/src/monolithic/org.bluez.Audio.xml create mode 100644 bluedevil/src/sendfile/CMakeLists.txt create mode 100644 bluedevil/src/sendfile/bluedevil-sendfile.desktop create mode 100644 bluedevil/src/sendfile/discover.ui create mode 100644 bluedevil/src/sendfile/discoverwidget.cpp create mode 100644 bluedevil/src/sendfile/discoverwidget.h create mode 100644 bluedevil/src/sendfile/main.cpp create mode 100644 bluedevil/src/sendfile/org.bluez.obex.Client1.xml create mode 100644 bluedevil/src/sendfile/org.bluez.obex.ObjectPush1.xml create mode 100644 bluedevil/src/sendfile/org.bluez.obex.Transfer1.xml create mode 100644 bluedevil/src/sendfile/pages/CMakeLists.txt create mode 100644 bluedevil/src/sendfile/pages/connecting.ui create mode 100644 bluedevil/src/sendfile/pages/connectingpage.cpp create mode 100644 bluedevil/src/sendfile/pages/connectingpage.h create mode 100644 bluedevil/src/sendfile/pages/selectdeviceandfilespage.cpp create mode 100644 bluedevil/src/sendfile/pages/selectdeviceandfilespage.h create mode 100644 bluedevil/src/sendfile/pages/selectdevicepage.cpp create mode 100644 bluedevil/src/sendfile/pages/selectdevicepage.h create mode 100644 bluedevil/src/sendfile/pages/selectfilediscover.ui create mode 100644 bluedevil/src/sendfile/pages/selectfilespage.cpp create mode 100644 bluedevil/src/sendfile/pages/selectfilespage.h create mode 100644 bluedevil/src/sendfile/sendfilesjob.cpp create mode 100644 bluedevil/src/sendfile/sendfilesjob.h create mode 100644 bluedevil/src/sendfile/sendfilewizard.cpp create mode 100644 bluedevil/src/sendfile/sendfilewizard.h create mode 100644 bluedevil/src/settings/CMakeLists.txt create mode 100644 bluedevil/src/settings/filereceiver.kcfg create mode 100644 bluedevil/src/settings/filereceiversettings.kcfgc create mode 100644 bluedevil/src/settings/global.kcfg create mode 100644 bluedevil/src/settings/globalsettings.kcfgc create mode 100644 bluedevil/src/wizard/CMakeLists.txt create mode 100644 bluedevil/src/wizard/USER_FLOW.txt create mode 100644 bluedevil/src/wizard/bluedevil-wizard.desktop create mode 100644 bluedevil/src/wizard/bluewizard.cpp create mode 100644 bluedevil/src/wizard/bluewizard.h create mode 100644 bluedevil/src/wizard/main.cpp create mode 100644 bluedevil/src/wizard/pages/discover.ui create mode 100644 bluedevil/src/wizard/pages/discoverpage.cpp create mode 100644 bluedevil/src/wizard/pages/discoverpage.h create mode 100644 bluedevil/src/wizard/pages/fail.cpp create mode 100644 bluedevil/src/wizard/pages/fail.h create mode 100644 bluedevil/src/wizard/pages/fail.ui create mode 100644 bluedevil/src/wizard/pages/keyboardpairing.cpp create mode 100644 bluedevil/src/wizard/pages/keyboardpairing.h create mode 100644 bluedevil/src/wizard/pages/keyboardpairing.ui create mode 100644 bluedevil/src/wizard/pages/legacypairing.cpp create mode 100644 bluedevil/src/wizard/pages/legacypairing.h create mode 100644 bluedevil/src/wizard/pages/legacypairing.ui create mode 100644 bluedevil/src/wizard/pages/legacypairingdatabase.cpp create mode 100644 bluedevil/src/wizard/pages/legacypairingdatabase.h create mode 100644 bluedevil/src/wizard/pages/nopairing.cpp create mode 100644 bluedevil/src/wizard/pages/nopairing.h create mode 100644 bluedevil/src/wizard/pages/nopairing.ui create mode 100644 bluedevil/src/wizard/pages/ssppairing.cpp create mode 100644 bluedevil/src/wizard/pages/ssppairing.h create mode 100644 bluedevil/src/wizard/pages/ssppairing.ui create mode 100644 bluedevil/src/wizard/pin-code-database.xml create mode 100644 bluedevil/src/wizard/wizardagent.cpp create mode 100644 bluedevil/src/wizard/wizardagent.h create mode 100644 bluedevil/version.h.cmake create mode 100644 dragon/.gitignore create mode 100644 dragon/CMakeLists.txt create mode 100644 dragon/COPYING create mode 100644 dragon/COPYING.DOC create mode 100644 dragon/ChangeLog create mode 100644 dragon/HACKING create mode 100755 dragon/Messages.sh create mode 100644 dragon/README create mode 100644 dragon/TODO create mode 100644 dragon/config.h.cmake create mode 100644 dragon/doc/CMakeLists.txt create mode 100644 dragon/doc/index.docbook create mode 100644 dragon/doc/main.png create mode 100644 dragon/doc/man-dragon.1.docbook create mode 100644 dragon/doc/playmedia.png create mode 100644 dragon/misc/16x16_dragonplayer.svgz create mode 100644 dragon/misc/32x32_dragonplayer.svgz create mode 100644 dragon/misc/CMakeLists.txt create mode 100644 dragon/misc/dragonlogo.png create mode 100644 dragon/misc/dragonplayer-opendvd.desktop create mode 100755 dragon/misc/dragonplayer.desktop create mode 100644 dragon/misc/dragonplayer_part.desktop create mode 100644 dragon/misc/dragonplayer_play_dvd.desktop create mode 100644 dragon/misc/dragonplayerrc create mode 100644 dragon/misc/dragonplayerui.rc create mode 100644 dragon/misc/hi128-app-dragonplayer.png create mode 100644 dragon/misc/hi16-app-dragonplayer.png create mode 100644 dragon/misc/hi22-app-dragonplayer.png create mode 100644 dragon/misc/hi32-app-dragonplayer.png create mode 100644 dragon/misc/hi48-app-dragonplayer.png create mode 100644 dragon/misc/hi64-app-dragonplayer.png create mode 100644 dragon/misc/hisc-app-dragonplayer.svgz create mode 100644 dragon/misc/ox16-actions-player-volume-muted.png create mode 100644 dragon/misc/ox22-actions-player-volume-muted.png create mode 100644 dragon/misc/ox32-actions-player-volume-muted.png create mode 100644 dragon/misc/ox48-actions-player-volume-muted.png create mode 100644 dragon/misc/oxsc-actions-player-volume-muted.svgz create mode 100644 dragon/src/FAQ create mode 100644 dragon/src/app/CMakeLists.txt create mode 100644 dragon/src/app/actions.cpp create mode 100644 dragon/src/app/actions.h create mode 100644 dragon/src/app/adjustSizeButton.cpp create mode 100644 dragon/src/app/adjustSizeButton.h create mode 100644 dragon/src/app/analyzer/analyzerBase.cpp create mode 100644 dragon/src/app/analyzer/analyzerBase.h create mode 100644 dragon/src/app/analyzer/blockAnalyzer.cpp create mode 100644 dragon/src/app/analyzer/blockAnalyzer.h create mode 100644 dragon/src/app/analyzer/fht.cpp create mode 100644 dragon/src/app/analyzer/fht.h create mode 100644 dragon/src/app/audioView2.cpp create mode 100644 dragon/src/app/audioView2.h create mode 100644 dragon/src/app/audioView2.ui create mode 100644 dragon/src/app/discSelectionDialog.cpp create mode 100644 dragon/src/app/discSelectionDialog.h create mode 100644 dragon/src/app/extern.h create mode 100644 dragon/src/app/fullScreenToolBarHandler.cpp create mode 100644 dragon/src/app/fullScreenToolBarHandler.h create mode 100644 dragon/src/app/listView.cpp create mode 100644 dragon/src/app/loadView.cpp create mode 100644 dragon/src/app/loadView.h create mode 100644 dragon/src/app/loadView.ui create mode 100644 dragon/src/app/main.cpp create mode 100644 dragon/src/app/mainWindow.cpp create mode 100644 dragon/src/app/mainWindow.h create mode 100644 dragon/src/app/part.cpp create mode 100644 dragon/src/app/part.h create mode 100644 dragon/src/app/partToolBar.cpp create mode 100644 dragon/src/app/partToolBar.h create mode 100644 dragon/src/app/playDialog.cpp create mode 100644 dragon/src/app/playDialog.h create mode 100644 dragon/src/app/playerApplication.cpp create mode 100644 dragon/src/app/playerApplication.h create mode 100644 dragon/src/app/playlistFile.cpp create mode 100644 dragon/src/app/playlistFile.h create mode 100644 dragon/src/app/recentlyPlayedList.cpp create mode 100644 dragon/src/app/recentlyPlayedList.h create mode 100644 dragon/src/app/stateChange.cpp create mode 100644 dragon/src/app/textItem.cpp create mode 100644 dragon/src/app/textItem.h create mode 100644 dragon/src/app/theStream.cpp create mode 100644 dragon/src/app/theStream.h create mode 100644 dragon/src/app/timeLabel.cpp create mode 100644 dragon/src/app/timeLabel.h create mode 100644 dragon/src/app/videoSettingsWidget.ui create mode 100644 dragon/src/app/videoWindow.cpp create mode 100644 dragon/src/app/videoWindow.h create mode 100644 dragon/src/codeine.h create mode 100644 dragon/src/messageBox.h create mode 100644 dragon/src/mpris2/mediaplayer2.cpp create mode 100644 dragon/src/mpris2/mediaplayer2.h create mode 100644 dragon/src/mpris2/mediaplayer2player.cpp create mode 100644 dragon/src/mpris2/mediaplayer2player.h create mode 100644 dragon/src/mpris2/mpris2.cpp create mode 100644 dragon/src/mpris2/mpris2.h create mode 100644 ffmpegthumbs/CMakeLists.txt create mode 100644 ffmpegthumbs/COPYING create mode 100644 ffmpegthumbs/cmake/COPYING-CMAKE-SCRIPTS create mode 100644 ffmpegthumbs/cmake/FindFFmpeg.cmake create mode 100644 ffmpegthumbs/ffmpegthumbnailer.cpp create mode 100644 ffmpegthumbs/ffmpegthumbnailer.h create mode 100644 ffmpegthumbs/ffmpegthumbnailer/AUTHORS create mode 100644 ffmpegthumbs/ffmpegthumbnailer/ChangeLog create mode 100644 ffmpegthumbs/ffmpegthumbnailer/README create mode 100644 ffmpegthumbs/ffmpegthumbnailer/filmstrip.h create mode 100644 ffmpegthumbs/ffmpegthumbnailer/filmstripfilter.cpp create mode 100644 ffmpegthumbs/ffmpegthumbnailer/filmstripfilter.h create mode 100644 ffmpegthumbs/ffmpegthumbnailer/histogram.h create mode 100644 ffmpegthumbs/ffmpegthumbnailer/ifilter.h create mode 100644 ffmpegthumbs/ffmpegthumbnailer/imagewriter.cpp create mode 100644 ffmpegthumbs/ffmpegthumbnailer/imagewriter.h create mode 100644 ffmpegthumbs/ffmpegthumbnailer/moviedecoder.cpp create mode 100644 ffmpegthumbs/ffmpegthumbnailer/moviedecoder.h create mode 100644 ffmpegthumbs/ffmpegthumbnailer/videoframe.h create mode 100644 ffmpegthumbs/ffmpegthumbnailer/videothumbnailer.cpp create mode 100644 ffmpegthumbs/ffmpegthumbnailer/videothumbnailer.h create mode 100644 ffmpegthumbs/ffmpegthumbs.desktop create mode 100644 ffmpegthumbs/tests/CMakeLists.txt create mode 100644 ffmpegthumbs/tests/ffmpegthumbtest.cpp create mode 100644 gwenview/.gitignore create mode 100644 gwenview/.kateconfig create mode 100644 gwenview/.krazy create mode 100644 gwenview/.reviewboardrc create mode 100644 gwenview/CMakeLists.txt create mode 100644 gwenview/COPYING create mode 100644 gwenview/COPYING.DOC create mode 100644 gwenview/Messages.sh create mode 100644 gwenview/NEWS create mode 100644 gwenview/app/CMakeLists.txt create mode 100644 gwenview/app/abstractcontextmanageritem.cpp create mode 100644 gwenview/app/abstractcontextmanageritem.h create mode 100644 gwenview/app/advancedconfigpage.ui create mode 100644 gwenview/app/browsemainpage.cpp create mode 100644 gwenview/app/browsemainpage.h create mode 100644 gwenview/app/browsemainpage.ui create mode 100644 gwenview/app/configdialog.cpp create mode 100644 gwenview/app/configdialog.h create mode 100644 gwenview/app/documentinfoprovider.cpp create mode 100644 gwenview/app/documentinfoprovider.h create mode 100644 gwenview/app/fileoperations.cpp create mode 100644 gwenview/app/fileoperations.h create mode 100644 gwenview/app/fileopscontextmanageritem.cpp create mode 100644 gwenview/app/fileopscontextmanageritem.h create mode 100644 gwenview/app/filtercontroller.cpp create mode 100644 gwenview/app/filtercontroller.h create mode 100644 gwenview/app/folderviewcontextmanageritem.cpp create mode 100644 gwenview/app/folderviewcontextmanageritem.h create mode 100644 gwenview/app/fullscreenconfigwidget.ui create mode 100644 gwenview/app/fullscreencontent.cpp create mode 100644 gwenview/app/fullscreencontent.h create mode 100644 gwenview/app/generalconfigpage.ui create mode 100644 gwenview/app/gvcore.cpp create mode 100644 gwenview/app/gvcore.h create mode 100644 gwenview/app/gwenview.desktop create mode 100644 gwenview/app/gwenviewui.rc create mode 100644 gwenview/app/imagemetainfodialog.cpp create mode 100644 gwenview/app/imagemetainfodialog.h create mode 100644 gwenview/app/imageopscontextmanageritem.cpp create mode 100644 gwenview/app/imageopscontextmanageritem.h create mode 100644 gwenview/app/imageviewconfigpage.ui create mode 100644 gwenview/app/infocontextmanageritem.cpp create mode 100644 gwenview/app/infocontextmanageritem.h create mode 100644 gwenview/app/kipiexportaction.cpp create mode 100644 gwenview/app/kipiexportaction.h create mode 100644 gwenview/app/kipiimagecollectionselector.cpp create mode 100644 gwenview/app/kipiimagecollectionselector.h create mode 100644 gwenview/app/kipiinterface.cpp create mode 100644 gwenview/app/kipiinterface.h create mode 100644 gwenview/app/kipiuploadwidget.cpp create mode 100644 gwenview/app/kipiuploadwidget.h create mode 100644 gwenview/app/main.cpp create mode 100644 gwenview/app/mainwindow.cpp create mode 100644 gwenview/app/mainwindow.h create mode 100644 gwenview/app/preloader.cpp create mode 100644 gwenview/app/preloader.h create mode 100644 gwenview/app/saveallhelper.cpp create mode 100644 gwenview/app/saveallhelper.h create mode 100644 gwenview/app/savebar.cpp create mode 100644 gwenview/app/savebar.h create mode 100644 gwenview/app/semanticinfocontextmanageritem.cpp create mode 100644 gwenview/app/semanticinfocontextmanageritem.h create mode 100644 gwenview/app/semanticinfodialog.ui create mode 100644 gwenview/app/semanticinfosidebaritem.ui create mode 100644 gwenview/app/sidebar.cpp create mode 100644 gwenview/app/sidebar.h create mode 100644 gwenview/app/slideshow.desktop create mode 100644 gwenview/app/splitter.h create mode 100644 gwenview/app/startmainpage.cpp create mode 100644 gwenview/app/startmainpage.h create mode 100644 gwenview/app/startmainpage.ui create mode 100644 gwenview/app/thumbnailviewhelper.cpp create mode 100644 gwenview/app/thumbnailviewhelper.h create mode 100644 gwenview/app/viewmainpage.cpp create mode 100644 gwenview/app/viewmainpage.h create mode 100644 gwenview/cmake/FindLCMS2.cmake create mode 100644 gwenview/color-schemes/CMakeLists.txt create mode 100644 gwenview/color-schemes/fullscreen.colors create mode 100644 gwenview/config-gwenview.h.cmake create mode 100644 gwenview/cursors/CMakeLists.txt create mode 100644 gwenview/cursors/zoom.png create mode 100644 gwenview/cursors/zoom.svg create mode 100644 gwenview/devdoc/CONTRIBUTING.md create mode 100644 gwenview/devdoc/ENVIRONMENT_VARIABLES.md create mode 100644 gwenview/doc/CMakeLists.txt create mode 100644 gwenview/doc/browse_mode.png create mode 100644 gwenview/doc/fullscreen-browse.png create mode 100644 gwenview/doc/fullscreen-view.png create mode 100644 gwenview/doc/importer-picking-root-folder.png create mode 100644 gwenview/doc/importer.png create mode 100644 gwenview/doc/index.docbook create mode 100644 gwenview/doc/modified-bar.png create mode 100644 gwenview/doc/start-page.png create mode 100644 gwenview/doc/view_mode.png create mode 100644 gwenview/icons/CMakeLists.txt create mode 100644 gwenview/icons/hi128-actions-document-share.png create mode 100644 gwenview/icons/hi128-apps-gwenview.png create mode 100644 gwenview/icons/hi16-actions-document-share.png create mode 100644 gwenview/icons/hi16-apps-gwenview.png create mode 100644 gwenview/icons/hi22-actions-document-share.png create mode 100644 gwenview/icons/hi22-apps-gwenview.png create mode 100644 gwenview/icons/hi32-actions-document-share.png create mode 100644 gwenview/icons/hi32-apps-gwenview.png create mode 100644 gwenview/icons/hi48-actions-document-share.png create mode 100644 gwenview/icons/hi48-apps-gwenview.png create mode 100644 gwenview/icons/hi64-actions-document-share.png create mode 100644 gwenview/icons/hi64-apps-gwenview.png create mode 100644 gwenview/icons/hisc-actions-document-share.svgz create mode 100644 gwenview/icons/hisc-apps-gwenview.svgz create mode 100644 gwenview/images/CMakeLists.txt create mode 100644 gwenview/images/background.png create mode 100644 gwenview/importer/CMakeLists.txt create mode 100644 gwenview/importer/dialogpage.cpp create mode 100644 gwenview/importer/dialogpage.h create mode 100644 gwenview/importer/dialogpage.ui create mode 100644 gwenview/importer/documentdirfinder.cpp create mode 100644 gwenview/importer/documentdirfinder.h create mode 100644 gwenview/importer/filenameformater.cpp create mode 100644 gwenview/importer/filenameformater.h create mode 100644 gwenview/importer/fileutils.cpp create mode 100644 gwenview/importer/fileutils.h create mode 100644 gwenview/importer/gwenview_importer.desktop create mode 100644 gwenview/importer/gwenview_importer_camera.desktop create mode 100644 gwenview/importer/importdialog.cpp create mode 100644 gwenview/importer/importdialog.h create mode 100644 gwenview/importer/importer.cpp create mode 100644 gwenview/importer/importer.h create mode 100644 gwenview/importer/importerconfig.kcfg create mode 100644 gwenview/importer/importerconfig.kcfgc create mode 100644 gwenview/importer/importerconfigdialog.cpp create mode 100644 gwenview/importer/importerconfigdialog.h create mode 100644 gwenview/importer/importerconfigdialog.ui create mode 100644 gwenview/importer/main.cpp create mode 100644 gwenview/importer/progresspage.cpp create mode 100644 gwenview/importer/progresspage.h create mode 100644 gwenview/importer/progresspage.ui create mode 100644 gwenview/importer/serializedurlmap.cpp create mode 100644 gwenview/importer/serializedurlmap.h create mode 100644 gwenview/importer/thumbnailpage.cpp create mode 100644 gwenview/importer/thumbnailpage.h create mode 100644 gwenview/importer/thumbnailpage.ui create mode 100644 gwenview/lib/CMakeLists.txt create mode 100644 gwenview/lib/about.cpp create mode 100644 gwenview/lib/about.h create mode 100644 gwenview/lib/abstractimageoperation.cpp create mode 100644 gwenview/lib/abstractimageoperation.h create mode 100644 gwenview/lib/archiveutils.cpp create mode 100644 gwenview/lib/archiveutils.h create mode 100644 gwenview/lib/binder.cpp create mode 100644 gwenview/lib/binder.h create mode 100644 gwenview/lib/cms/cmsprofile.cpp create mode 100644 gwenview/lib/cms/cmsprofile.h create mode 100644 gwenview/lib/cms/cmsprofile_png.cpp create mode 100644 gwenview/lib/cms/cmsprofile_png.h create mode 100644 gwenview/lib/cms/iccjpeg.c create mode 100644 gwenview/lib/cms/iccjpeg.h create mode 100644 gwenview/lib/contextmanager.cpp create mode 100644 gwenview/lib/contextmanager.h create mode 100644 gwenview/lib/crop/cropimageoperation.cpp create mode 100644 gwenview/lib/crop/cropimageoperation.h create mode 100644 gwenview/lib/crop/croptool.cpp create mode 100644 gwenview/lib/crop/croptool.h create mode 100644 gwenview/lib/crop/cropwidget.cpp create mode 100644 gwenview/lib/crop/cropwidget.h create mode 100644 gwenview/lib/crop/cropwidget.ui create mode 100644 gwenview/lib/datewidget.cpp create mode 100644 gwenview/lib/datewidget.h create mode 100644 gwenview/lib/disabledactionshortcutmonitor.cpp create mode 100644 gwenview/lib/disabledactionshortcutmonitor.h create mode 100644 gwenview/lib/document/abstractdocumenteditor.h create mode 100644 gwenview/lib/document/abstractdocumentimpl.cpp create mode 100644 gwenview/lib/document/abstractdocumentimpl.h create mode 100644 gwenview/lib/document/animateddocumentloadedimpl.cpp create mode 100644 gwenview/lib/document/animateddocumentloadedimpl.h create mode 100644 gwenview/lib/document/document.cpp create mode 100644 gwenview/lib/document/document.h create mode 100644 gwenview/lib/document/document_p.h create mode 100644 gwenview/lib/document/documentfactory.cpp create mode 100644 gwenview/lib/document/documentfactory.h create mode 100644 gwenview/lib/document/documentjob.cpp create mode 100644 gwenview/lib/document/documentjob.h create mode 100644 gwenview/lib/document/documentloadedimpl.cpp create mode 100644 gwenview/lib/document/documentloadedimpl.h create mode 100644 gwenview/lib/document/emptydocumentimpl.cpp create mode 100644 gwenview/lib/document/emptydocumentimpl.h create mode 100644 gwenview/lib/document/jpegdocumentloadedimpl.cpp create mode 100644 gwenview/lib/document/jpegdocumentloadedimpl.h create mode 100644 gwenview/lib/document/loadingdocumentimpl.cpp create mode 100644 gwenview/lib/document/loadingdocumentimpl.h create mode 100644 gwenview/lib/document/loadingjob.cpp create mode 100644 gwenview/lib/document/loadingjob.h create mode 100644 gwenview/lib/document/savejob.cpp create mode 100644 gwenview/lib/document/savejob.h create mode 100644 gwenview/lib/document/svgdocumentloadedimpl.cpp create mode 100644 gwenview/lib/document/svgdocumentloadedimpl.h create mode 100644 gwenview/lib/document/videodocumentloadedimpl.cpp create mode 100644 gwenview/lib/document/videodocumentloadedimpl.h create mode 100644 gwenview/lib/documentonlyproxymodel.cpp create mode 100644 gwenview/lib/documentonlyproxymodel.h create mode 100644 gwenview/lib/documentview/abstractdocumentviewadapter.cpp create mode 100644 gwenview/lib/documentview/abstractdocumentviewadapter.h create mode 100644 gwenview/lib/documentview/abstractimageview.cpp create mode 100644 gwenview/lib/documentview/abstractimageview.h create mode 100644 gwenview/lib/documentview/abstractrasterimageviewtool.cpp create mode 100644 gwenview/lib/documentview/abstractrasterimageviewtool.h create mode 100644 gwenview/lib/documentview/birdeyeview.cpp create mode 100644 gwenview/lib/documentview/birdeyeview.h create mode 100644 gwenview/lib/documentview/documentview.cpp create mode 100644 gwenview/lib/documentview/documentview.h create mode 100644 gwenview/lib/documentview/documentviewcontainer.cpp create mode 100644 gwenview/lib/documentview/documentviewcontainer.h create mode 100644 gwenview/lib/documentview/documentviewcontroller.cpp create mode 100644 gwenview/lib/documentview/documentviewcontroller.h create mode 100644 gwenview/lib/documentview/documentviewsynchronizer.cpp create mode 100644 gwenview/lib/documentview/documentviewsynchronizer.h create mode 100644 gwenview/lib/documentview/loadingindicator.cpp create mode 100644 gwenview/lib/documentview/loadingindicator.h create mode 100644 gwenview/lib/documentview/messageview.ui create mode 100644 gwenview/lib/documentview/messageviewadapter.cpp create mode 100644 gwenview/lib/documentview/messageviewadapter.h create mode 100644 gwenview/lib/documentview/rasterimageview.cpp create mode 100644 gwenview/lib/documentview/rasterimageview.h create mode 100644 gwenview/lib/documentview/rasterimageviewadapter.cpp create mode 100644 gwenview/lib/documentview/rasterimageviewadapter.h create mode 100644 gwenview/lib/documentview/svgviewadapter.cpp create mode 100644 gwenview/lib/documentview/svgviewadapter.h create mode 100644 gwenview/lib/documentview/videoviewadapter.cpp create mode 100644 gwenview/lib/documentview/videoviewadapter.h create mode 100644 gwenview/lib/eventwatcher.cpp create mode 100644 gwenview/lib/eventwatcher.h create mode 100644 gwenview/lib/exiv2imageloader.cpp create mode 100644 gwenview/lib/exiv2imageloader.h create mode 100644 gwenview/lib/flowlayout.cpp create mode 100644 gwenview/lib/flowlayout.h create mode 100644 gwenview/lib/fullscreenbar.cpp create mode 100644 gwenview/lib/fullscreenbar.h create mode 100644 gwenview/lib/graphicswidgetfloater.cpp create mode 100644 gwenview/lib/graphicswidgetfloater.h create mode 100644 gwenview/lib/gvdebug.h create mode 100644 gwenview/lib/gwenviewconfig.kcfg create mode 100644 gwenview/lib/gwenviewconfig.kcfgc create mode 100644 gwenview/lib/gwenviewlib_export.h create mode 100644 gwenview/lib/historymodel.cpp create mode 100644 gwenview/lib/historymodel.h create mode 100644 gwenview/lib/hud/hudbutton.cpp create mode 100644 gwenview/lib/hud/hudbutton.h create mode 100644 gwenview/lib/hud/hudbuttonbox.cpp create mode 100644 gwenview/lib/hud/hudbuttonbox.h create mode 100644 gwenview/lib/hud/hudcountdown.cpp create mode 100644 gwenview/lib/hud/hudcountdown.h create mode 100644 gwenview/lib/hud/hudlabel.cpp create mode 100644 gwenview/lib/hud/hudlabel.h create mode 100644 gwenview/lib/hud/hudmessagebubble.cpp create mode 100644 gwenview/lib/hud/hudmessagebubble.h create mode 100644 gwenview/lib/hud/hudslider.cpp create mode 100644 gwenview/lib/hud/hudslider.h create mode 100644 gwenview/lib/hud/hudtheme.cpp create mode 100644 gwenview/lib/hud/hudtheme.h create mode 100644 gwenview/lib/hud/hudwidget.cpp create mode 100644 gwenview/lib/hud/hudwidget.h create mode 100644 gwenview/lib/imageformats/imageformats.cpp create mode 100644 gwenview/lib/imageformats/imageformats.h create mode 100644 gwenview/lib/imageformats/jpeghandler.cpp create mode 100644 gwenview/lib/imageformats/jpeghandler.h create mode 100644 gwenview/lib/imageformats/jpegplugin.cpp create mode 100644 gwenview/lib/imageformats/jpegplugin.h create mode 100644 gwenview/lib/imagemetainfomodel.cpp create mode 100644 gwenview/lib/imagemetainfomodel.h create mode 100644 gwenview/lib/imagescaler.cpp create mode 100644 gwenview/lib/imagescaler.h create mode 100644 gwenview/lib/imageutils.cpp create mode 100644 gwenview/lib/imageutils.h create mode 100644 gwenview/lib/invisiblebuttongroup.cpp create mode 100644 gwenview/lib/invisiblebuttongroup.h create mode 100644 gwenview/lib/iodevicejpegsourcemanager.cpp create mode 100644 gwenview/lib/iodevicejpegsourcemanager.h create mode 100644 gwenview/lib/jpegcontent.cpp create mode 100644 gwenview/lib/jpegcontent.h create mode 100644 gwenview/lib/jpegerrormanager.h create mode 100644 gwenview/lib/kindproxymodel.cpp create mode 100644 gwenview/lib/kindproxymodel.h create mode 100644 gwenview/lib/libjpeg-62/README.jpeg create mode 100644 gwenview/lib/libjpeg-62/jinclude.h create mode 100644 gwenview/lib/libjpeg-62/jpegint.h create mode 100644 gwenview/lib/libjpeg-62/transupp.c create mode 100644 gwenview/lib/libjpeg-62/transupp.h create mode 100644 gwenview/lib/libjpeg-80/README.jpeg create mode 100644 gwenview/lib/libjpeg-80/jinclude.h create mode 100644 gwenview/lib/libjpeg-80/jpegint.h create mode 100644 gwenview/lib/libjpeg-80/transupp.c create mode 100644 gwenview/lib/libjpeg-80/transupp.h create mode 100644 gwenview/lib/libjpeg-90/README.jpeg create mode 100644 gwenview/lib/libjpeg-90/jinclude.h create mode 100644 gwenview/lib/libjpeg-90/jpegint.h create mode 100644 gwenview/lib/libjpeg-90/transupp.c create mode 100644 gwenview/lib/libjpeg-90/transupp.h create mode 100644 gwenview/lib/memoryutils.cpp create mode 100644 gwenview/lib/memoryutils.h create mode 100644 gwenview/lib/mimetypeutils.cpp create mode 100644 gwenview/lib/mimetypeutils.h create mode 100644 gwenview/lib/mimetypeutils_p.h create mode 100644 gwenview/lib/mousewheelbehavior.h create mode 100644 gwenview/lib/orientation.h create mode 100644 gwenview/lib/paintutils.cpp create mode 100644 gwenview/lib/paintutils.h create mode 100644 gwenview/lib/placetreemodel.cpp create mode 100644 gwenview/lib/placetreemodel.h create mode 100644 gwenview/lib/preferredimagemetainfomodel.cpp create mode 100644 gwenview/lib/preferredimagemetainfomodel.h create mode 100644 gwenview/lib/print/printhelper.cpp create mode 100644 gwenview/lib/print/printhelper.h create mode 100644 gwenview/lib/print/printoptionspage.cpp create mode 100644 gwenview/lib/print/printoptionspage.h create mode 100644 gwenview/lib/print/printoptionspage.ui create mode 100644 gwenview/lib/ramp.h create mode 100644 gwenview/lib/recursivedirmodel.cpp create mode 100644 gwenview/lib/recursivedirmodel.h create mode 100644 gwenview/lib/redeyereduction/redeyereductionimageoperation.cpp create mode 100644 gwenview/lib/redeyereduction/redeyereductionimageoperation.h create mode 100644 gwenview/lib/redeyereduction/redeyereductiontool.cpp create mode 100644 gwenview/lib/redeyereduction/redeyereductiontool.h create mode 100644 gwenview/lib/redeyereduction/redeyereductionwidget.ui create mode 100644 gwenview/lib/resize/resizeimagedialog.cpp create mode 100644 gwenview/lib/resize/resizeimagedialog.h create mode 100644 gwenview/lib/resize/resizeimageoperation.cpp create mode 100644 gwenview/lib/resize/resizeimageoperation.h create mode 100644 gwenview/lib/resize/resizeimagewidget.ui create mode 100644 gwenview/lib/semanticinfo/abstractsemanticinfobackend.cpp create mode 100644 gwenview/lib/semanticinfo/abstractsemanticinfobackend.h create mode 100644 gwenview/lib/semanticinfo/baloosemanticinfobackend.cpp create mode 100644 gwenview/lib/semanticinfo/baloosemanticinfobackend.h create mode 100644 gwenview/lib/semanticinfo/fakesemanticinfobackend.cpp create mode 100644 gwenview/lib/semanticinfo/fakesemanticinfobackend.h create mode 100644 gwenview/lib/semanticinfo/semanticinfodirmodel.cpp create mode 100644 gwenview/lib/semanticinfo/semanticinfodirmodel.h create mode 100644 gwenview/lib/semanticinfo/sorteddirmodel.cpp create mode 100644 gwenview/lib/semanticinfo/sorteddirmodel.h create mode 100644 gwenview/lib/semanticinfo/tagitemdelegate.cpp create mode 100644 gwenview/lib/semanticinfo/tagitemdelegate.h create mode 100644 gwenview/lib/semanticinfo/tagmodel.cpp create mode 100644 gwenview/lib/semanticinfo/tagmodel.h create mode 100644 gwenview/lib/semanticinfo/tagwidget.cpp create mode 100644 gwenview/lib/semanticinfo/tagwidget.h create mode 100644 gwenview/lib/shadowfilter.cpp create mode 100644 gwenview/lib/shadowfilter.h create mode 100644 gwenview/lib/signalblocker.h create mode 100644 gwenview/lib/slidecontainer.cpp create mode 100644 gwenview/lib/slidecontainer.h create mode 100644 gwenview/lib/slideshow.cpp create mode 100644 gwenview/lib/slideshow.h create mode 100644 gwenview/lib/sorting.h create mode 100644 gwenview/lib/statusbartoolbutton.cpp create mode 100644 gwenview/lib/statusbartoolbutton.h create mode 100644 gwenview/lib/thumbnailgroup.h create mode 100644 gwenview/lib/thumbnailprovider/thumbnailgenerator.cpp create mode 100644 gwenview/lib/thumbnailprovider/thumbnailgenerator.h create mode 100644 gwenview/lib/thumbnailprovider/thumbnailprovider.cpp create mode 100644 gwenview/lib/thumbnailprovider/thumbnailprovider.h create mode 100644 gwenview/lib/thumbnailprovider/thumbnailwriter.cpp create mode 100644 gwenview/lib/thumbnailprovider/thumbnailwriter.h create mode 100644 gwenview/lib/thumbnailview/abstractdocumentinfoprovider.cpp create mode 100644 gwenview/lib/thumbnailview/abstractdocumentinfoprovider.h create mode 100644 gwenview/lib/thumbnailview/abstractthumbnailviewhelper.cpp create mode 100644 gwenview/lib/thumbnailview/abstractthumbnailviewhelper.h create mode 100644 gwenview/lib/thumbnailview/contextbarbutton.cpp create mode 100644 gwenview/lib/thumbnailview/contextbarbutton.h create mode 100644 gwenview/lib/thumbnailview/dragpixmapgenerator.cpp create mode 100644 gwenview/lib/thumbnailview/dragpixmapgenerator.h create mode 100644 gwenview/lib/thumbnailview/itemeditor.cpp create mode 100644 gwenview/lib/thumbnailview/itemeditor.h create mode 100644 gwenview/lib/thumbnailview/previewitemdelegate.cpp create mode 100644 gwenview/lib/thumbnailview/previewitemdelegate.h create mode 100644 gwenview/lib/thumbnailview/thumbnailbarview.cpp create mode 100644 gwenview/lib/thumbnailview/thumbnailbarview.h create mode 100644 gwenview/lib/thumbnailview/thumbnailslider.cpp create mode 100644 gwenview/lib/thumbnailview/thumbnailslider.h create mode 100644 gwenview/lib/thumbnailview/thumbnailview.cpp create mode 100644 gwenview/lib/thumbnailview/thumbnailview.h create mode 100644 gwenview/lib/thumbnailview/tooltipwidget.cpp create mode 100644 gwenview/lib/thumbnailview/tooltipwidget.h create mode 100644 gwenview/lib/timeutils.cpp create mode 100644 gwenview/lib/timeutils.h create mode 100644 gwenview/lib/transformimageoperation.cpp create mode 100644 gwenview/lib/transformimageoperation.h create mode 100644 gwenview/lib/urlutils.cpp create mode 100644 gwenview/lib/urlutils.h create mode 100644 gwenview/lib/version.h create mode 100644 gwenview/lib/widgetfloater.cpp create mode 100644 gwenview/lib/widgetfloater.h create mode 100644 gwenview/lib/zoomslider.cpp create mode 100644 gwenview/lib/zoomslider.h create mode 100644 gwenview/lib/zoomwidget.cpp create mode 100644 gwenview/lib/zoomwidget.h create mode 100644 gwenview/part/CMakeLists.txt create mode 100644 gwenview/part/gvbrowserextension.cpp create mode 100644 gwenview/part/gvbrowserextension.h create mode 100644 gwenview/part/gvpart.cpp create mode 100644 gwenview/part/gvpart.desktop create mode 100644 gwenview/part/gvpart.h create mode 100644 gwenview/part/gvpart.rc create mode 100644 gwenview/tests/CMakeLists.txt create mode 100644 gwenview/tests/auto/CMakeLists.txt create mode 100644 gwenview/tests/auto/README_REMOTE_TESTS create mode 100644 gwenview/tests/auto/cmsprofiletest.cpp create mode 100644 gwenview/tests/auto/cmsprofiletest.h create mode 100644 gwenview/tests/auto/contextmanagertest.cpp create mode 100644 gwenview/tests/auto/contextmanagertest.h create mode 100644 gwenview/tests/auto/documenttest.cpp create mode 100644 gwenview/tests/auto/documenttest.h create mode 100644 gwenview/tests/auto/historymodeltest.cpp create mode 100644 gwenview/tests/auto/historymodeltest.h create mode 100644 gwenview/tests/auto/imagemetainfomodeltest.cpp create mode 100644 gwenview/tests/auto/imagemetainfomodeltest.h create mode 100644 gwenview/tests/auto/imagescalertest.cpp create mode 100644 gwenview/tests/auto/imagescalertest.h create mode 100644 gwenview/tests/auto/importertest.cpp create mode 100644 gwenview/tests/auto/importertest.h create mode 100644 gwenview/tests/auto/jpegcontenttest.cpp create mode 100644 gwenview/tests/auto/jpegcontenttest.h create mode 100644 gwenview/tests/auto/paintutilstest.cpp create mode 100644 gwenview/tests/auto/paintutilstest.h create mode 100644 gwenview/tests/auto/placetreemodeltest.cpp create mode 100644 gwenview/tests/auto/placetreemodeltest.h create mode 100644 gwenview/tests/auto/recursivedirmodeltest.cpp create mode 100644 gwenview/tests/auto/recursivedirmodeltest.h create mode 100644 gwenview/tests/auto/semanticinfobackendtest.cpp create mode 100644 gwenview/tests/auto/semanticinfobackendtest.h create mode 100644 gwenview/tests/auto/slidecontainerautotest.cpp create mode 100644 gwenview/tests/auto/slidecontainerautotest.h create mode 100644 gwenview/tests/auto/sorteddirmodeltest.cpp create mode 100644 gwenview/tests/auto/sorteddirmodeltest.h create mode 100644 gwenview/tests/auto/testutils.cpp create mode 100644 gwenview/tests/auto/testutils.h create mode 100644 gwenview/tests/auto/thumbnailprovidertest.cpp create mode 100644 gwenview/tests/auto/thumbnailprovidertest.h create mode 100644 gwenview/tests/auto/timeutilstest.cpp create mode 100644 gwenview/tests/auto/timeutilstest.h create mode 100644 gwenview/tests/auto/transformimageoperationtest.cpp create mode 100644 gwenview/tests/auto/transformimageoperationtest.h create mode 100644 gwenview/tests/auto/urlutilstest.cpp create mode 100644 gwenview/tests/auto/urlutilstest.h create mode 100644 gwenview/tests/data/.gitignore create mode 100644 gwenview/tests/data/160216_no_size_before_decoding.eps create mode 100644 gwenview/tests/data/160382_corrupted.jpeg create mode 100644 gwenview/tests/data/185523_1frame_with_graphic_control_extension.gif create mode 100644 gwenview/tests/data/188191_does_not_load.tga create mode 100644 gwenview/tests/data/1frame.gif create mode 100644 gwenview/tests/data/1x10k.jpg create mode 100644 gwenview/tests/data/1x10k.png create mode 100644 gwenview/tests/data/289819_does_not_load.png create mode 100644 gwenview/tests/data/302350_exiv_0.23_exception.jpg create mode 100644 gwenview/tests/data/40frames.gif create mode 100644 gwenview/tests/data/4frames.gif create mode 100644 gwenview/tests/data/cms/Lower_Left.jpg create mode 100644 gwenview/tests/data/cms/Lower_Right.jpg create mode 100644 gwenview/tests/data/cms/Upper_Left.jpg create mode 100644 gwenview/tests/data/cms/Upper_Right.jpg create mode 100644 gwenview/tests/data/cms/colourTestFakeBRG.png create mode 100644 gwenview/tests/data/cms/colourTestsRGB.png create mode 100644 gwenview/tests/data/date/exif-datetime-only.jpg create mode 100644 gwenview/tests/data/date/exif-datetimeoriginal.jpg create mode 100644 gwenview/tests/data/embedded-thumbnail.jpg create mode 100644 gwenview/tests/data/empty.png create mode 100755 gwenview/tests/data/fetch_testing_raw.sh create mode 100644 gwenview/tests/data/import/pict0001.jpg create mode 100644 gwenview/tests/data/import/pict0002.jpg create mode 100644 gwenview/tests/data/import/pict0003.jpg create mode 100644 gwenview/tests/data/jpg-with-gif-extension.gif create mode 100644 gwenview/tests/data/orient1_vflip.jpg create mode 100644 gwenview/tests/data/orient6-small.jpg create mode 100644 gwenview/tests/data/orient6.jpg create mode 100644 gwenview/tests/data/png-with-jpeg-extension.jpg create mode 100644 gwenview/tests/data/test.png create mode 100644 gwenview/tests/data/test.svg create mode 100644 gwenview/tests/data/test.xcf create mode 100644 gwenview/tests/manual/CMakeLists.txt create mode 100644 gwenview/tests/manual/browse.txt create mode 100644 gwenview/tests/manual/compare.txt create mode 100644 gwenview/tests/manual/imageloadbench.cpp create mode 100644 gwenview/tests/manual/slidecontainertest.cpp create mode 100644 gwenview/tests/manual/thumbnailgen.cpp create mode 100644 kactivities/.gitignore create mode 100644 kactivities/.videproject/project.conf create mode 100644 kactivities/.videproject/vimrc create mode 100644 kactivities/CMakeLists.txt create mode 100644 kactivities/MAINTAINER create mode 100644 kactivities/Mainpage.dox create mode 100644 kactivities/README create mode 100644 kactivities/README.developers create mode 100644 kactivities/README.packagers create mode 100644 kactivities/TODO create mode 100644 kactivities/cmake/modules/CheckCxxFeatures.cmake create mode 100644 kactivities/cmake/modules/c++-test-override-attr-none-fail.cpp create mode 100644 kactivities/cmake/modules/c++-test-override-attr-none.cpp create mode 100644 kactivities/cmake/modules/c++11-test-auto-N2546.cpp create mode 100644 kactivities/cmake/modules/c++11-test-initializer-lists-N2672.cpp create mode 100644 kactivities/cmake/modules/c++11-test-lambda-N2927.cpp create mode 100644 kactivities/cmake/modules/c++11-test-nullptr-N2431-fail.cpp create mode 100644 kactivities/cmake/modules/c++11-test-nullptr-N2431.cpp create mode 100644 kactivities/cmake/modules/c++11-test-override-N3206-fail.cpp create mode 100644 kactivities/cmake/modules/c++11-test-override-N3206.cpp create mode 100644 kactivities/cmake/modules/c++11-test-unique_ptr-none.cpp create mode 100644 kactivities/cmake/modules/c++11-test-variadic-templates-N2242.cpp create mode 100755 kactivities/scripts/commit.sh create mode 100755 kactivities/scripts/delete-activities.sh create mode 100755 kactivities/scripts/delete-stats.sh create mode 100755 kactivities/scripts/run-krazy.sh create mode 100755 kactivities/scripts/update-todo.sh create mode 100644 kactivities/scripts/ycm_extra_conf.py create mode 100644 kactivities/src/CMakeLists.txt create mode 100644 kactivities/src/common/dbus/org.kde.ActivityManager.Activities.cpp create mode 100644 kactivities/src/common/dbus/org.kde.ActivityManager.Activities.h create mode 100644 kactivities/src/common/dbus/org.kde.ActivityManager.Activities.xml create mode 100644 kactivities/src/common/dbus/org.kde.ActivityManager.Features.xml create mode 100644 kactivities/src/common/dbus/org.kde.ActivityManager.Resources.xml create mode 100644 kactivities/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml create mode 100644 kactivities/src/config-features.h.cmake create mode 100644 kactivities/src/lib/CMakeLists.txt create mode 100644 kactivities/src/lib/core/CMakeLists.txt create mode 100644 kactivities/src/lib/core/KActivitiesConfig.cmake.in create mode 100644 kactivities/src/lib/core/consumer.cpp create mode 100644 kactivities/src/lib/core/consumer.h create mode 100644 kactivities/src/lib/core/consumer_p.h create mode 100644 kactivities/src/lib/core/controller.cpp create mode 100644 kactivities/src/lib/core/controller.h create mode 100644 kactivities/src/lib/core/includes/KActivities/Consumer create mode 100644 kactivities/src/lib/core/includes/KActivities/Controller create mode 100644 kactivities/src/lib/core/includes/KActivities/Info create mode 100644 kactivities/src/lib/core/includes/KActivities/ResourceInstance create mode 100644 kactivities/src/lib/core/includes/KActivities/Version create mode 100644 kactivities/src/lib/core/info.cpp create mode 100644 kactivities/src/lib/core/info.h create mode 100644 kactivities/src/lib/core/info_p.h create mode 100644 kactivities/src/lib/core/kactivities_export.h create mode 100644 kactivities/src/lib/core/libkactivities.pc.cmake create mode 100644 kactivities/src/lib/core/manager_p.cpp create mode 100644 kactivities/src/lib/core/manager_p.h create mode 100755 kactivities/src/lib/core/prettyheaders.sh create mode 100644 kactivities/src/lib/core/resourceinstance.cpp create mode 100644 kactivities/src/lib/core/resourceinstance.h create mode 100644 kactivities/src/lib/core/utils_p.h create mode 100644 kactivities/src/lib/core/version.cpp create mode 100644 kactivities/src/lib/core/version.h create mode 100644 kactivities/src/lib/models/CMakeLists.txt create mode 100644 kactivities/src/lib/models/KActivities-ModelsConfig.cmake.in create mode 100644 kactivities/src/lib/models/activitiescomponentplugin.cpp create mode 100644 kactivities/src/lib/models/activitiescomponentplugin.h create mode 100644 kactivities/src/lib/models/activitymodel.cpp create mode 100644 kactivities/src/lib/models/activitymodel.h create mode 100644 kactivities/src/lib/models/includes/KActivities/Models/ActivityModel create mode 100644 kactivities/src/lib/models/includes/KActivities/Models/ResourceModel create mode 100644 kactivities/src/lib/models/kactivities_models_export.h create mode 100644 kactivities/src/lib/models/libkactivities-models.pc.cmake create mode 100644 kactivities/src/lib/models/qmldir create mode 100644 kactivities/src/lib/models/resourcemodel.cpp create mode 100644 kactivities/src/lib/models/resourcemodel.h create mode 100644 kactivities/src/lib/models/utils_p.h create mode 100644 kactivities/src/ontologies/CMakeLists.txt create mode 100644 kactivities/src/ontologies/kao.ontology.in create mode 100644 kactivities/src/ontologies/kao.trig create mode 100644 kactivities/src/service/Activities.cpp create mode 100644 kactivities/src/service/Activities.h create mode 100644 kactivities/src/service/Activities_p.h create mode 100644 kactivities/src/service/Application.cpp create mode 100644 kactivities/src/service/Application.h create mode 100644 kactivities/src/service/CMakeLists.txt create mode 100644 kactivities/src/service/Event.cpp create mode 100644 kactivities/src/service/Event.h create mode 100644 kactivities/src/service/Features.cpp create mode 100644 kactivities/src/service/Features.h create mode 100644 kactivities/src/service/Messages.sh create mode 100644 kactivities/src/service/Module.cpp create mode 100644 kactivities/src/service/Module.h create mode 100644 kactivities/src/service/Plugin.cpp create mode 100644 kactivities/src/service/Plugin.h create mode 100644 kactivities/src/service/Resources.cpp create mode 100644 kactivities/src/service/Resources.h create mode 100644 kactivities/src/service/Resources_p.h create mode 100644 kactivities/src/service/common.h create mode 100644 kactivities/src/service/files/activitymanager-plugin.desktop create mode 100644 kactivities/src/service/files/kactivitymanagerd.desktop create mode 100644 kactivities/src/service/jobs/Job.cpp create mode 100644 kactivities/src/service/jobs/Job.h create mode 100644 kactivities/src/service/jobs/JobFactory.cpp create mode 100644 kactivities/src/service/jobs/JobFactory.h create mode 100644 kactivities/src/service/jobs/activity/Start.cpp create mode 100644 kactivities/src/service/jobs/activity/Start.h create mode 100644 kactivities/src/service/jobs/activity/all.h create mode 100644 kactivities/src/service/jobs/general/Call.cpp create mode 100644 kactivities/src/service/jobs/general/Call.h create mode 100644 kactivities/src/service/jobs/general/all.h create mode 100644 kactivities/src/service/jobs/ksmserver/KSMServer.cpp create mode 100644 kactivities/src/service/jobs/ksmserver/KSMServer.h create mode 100644 kactivities/src/service/jobs/ksmserver/KSMServer_p.h create mode 100644 kactivities/src/service/jobs/schedulers/Abstract.cpp create mode 100644 kactivities/src/service/jobs/schedulers/Abstract.h create mode 100644 kactivities/src/service/jobs/schedulers/Abstract_p.h create mode 100644 kactivities/src/service/jobs/schedulers/Fallible.cpp create mode 100644 kactivities/src/service/jobs/schedulers/Fallible.h create mode 100644 kactivities/src/service/jobs/schedulers/Given.cpp create mode 100644 kactivities/src/service/jobs/schedulers/Given.h create mode 100644 kactivities/src/service/jobs/schedulers/Ordered.cpp create mode 100644 kactivities/src/service/jobs/schedulers/Ordered.h create mode 100644 kactivities/src/service/jobs/schedulers/Retry.cpp create mode 100644 kactivities/src/service/jobs/schedulers/Retry.h create mode 100644 kactivities/src/service/jobs/schedulers/Switch.cpp create mode 100644 kactivities/src/service/jobs/schedulers/Switch.h create mode 100644 kactivities/src/service/jobs/schedulers/Test.cpp create mode 100644 kactivities/src/service/jobs/schedulers/Test.h create mode 100644 kactivities/src/service/jobs/schedulers/all.h create mode 100644 kactivities/src/service/plugins/CMakeLists.txt create mode 100644 kactivities/src/service/plugins/activityranking/ActivityData.cpp create mode 100644 kactivities/src/service/plugins/activityranking/ActivityData.h create mode 100644 kactivities/src/service/plugins/activityranking/ActivityRanking.cpp create mode 100644 kactivities/src/service/plugins/activityranking/ActivityRanking.h create mode 100644 kactivities/src/service/plugins/activityranking/ActivityRankingPlugin.cpp create mode 100644 kactivities/src/service/plugins/activityranking/ActivityRankingPlugin.h create mode 100644 kactivities/src/service/plugins/activityranking/CMakeLists.txt create mode 100644 kactivities/src/service/plugins/activityranking/Location.cpp create mode 100644 kactivities/src/service/plugins/activityranking/Location.h create mode 100644 kactivities/src/service/plugins/activityranking/activitymanager-plugin-activityranking.desktop create mode 100644 kactivities/src/service/plugins/activityranking/org.kde.ActivityManager.ActivityRanking.xml create mode 100644 kactivities/src/service/plugins/activityranking/org.kde.LocationManager.xml create mode 100644 kactivities/src/service/plugins/globalshortcuts/CMakeLists.txt create mode 100644 kactivities/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.cpp create mode 100644 kactivities/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.h create mode 100644 kactivities/src/service/plugins/globalshortcuts/Messages.sh create mode 100644 kactivities/src/service/plugins/globalshortcuts/activitymanager-plugin-globalshortcuts.desktop create mode 100644 kactivities/src/service/plugins/nepomuk/CMakeLists.txt create mode 100644 kactivities/src/service/plugins/nepomuk/NepomukCommon.cpp create mode 100644 kactivities/src/service/plugins/nepomuk/NepomukCommon.h create mode 100644 kactivities/src/service/plugins/nepomuk/NepomukPlugin.cpp create mode 100644 kactivities/src/service/plugins/nepomuk/NepomukPlugin.h create mode 100644 kactivities/src/service/plugins/nepomuk/activitymanager-plugin-nepomuk.desktop create mode 100644 kactivities/src/service/plugins/slc/CMakeLists.txt create mode 100644 kactivities/src/service/plugins/slc/SlcPlugin.cpp create mode 100644 kactivities/src/service/plugins/slc/SlcPlugin.h create mode 100644 kactivities/src/service/plugins/slc/activitymanager-plugin-slc.desktop create mode 100644 kactivities/src/service/plugins/slc/org.kde.ActivityManager.SLC.xml create mode 100644 kactivities/src/service/plugins/sqlite/CMakeLists.txt create mode 100644 kactivities/src/service/plugins/sqlite/DatabaseConnection.cpp create mode 100644 kactivities/src/service/plugins/sqlite/DatabaseConnection.h create mode 100644 kactivities/src/service/plugins/sqlite/Rankings.cpp create mode 100644 kactivities/src/service/plugins/sqlite/Rankings.h create mode 100644 kactivities/src/service/plugins/sqlite/ResourceScoreCache.cpp create mode 100644 kactivities/src/service/plugins/sqlite/ResourceScoreCache.h create mode 100644 kactivities/src/service/plugins/sqlite/ResourceScoreMaintainer.cpp create mode 100644 kactivities/src/service/plugins/sqlite/ResourceScoreMaintainer.h create mode 100644 kactivities/src/service/plugins/sqlite/StatsPlugin.cpp create mode 100644 kactivities/src/service/plugins/sqlite/StatsPlugin.h create mode 100644 kactivities/src/service/plugins/sqlite/activitymanager-plugin-sqlite.desktop create mode 100644 kactivities/src/service/plugins/sqlite/org.kde.ActivityManager.Rankings.xml create mode 100644 kactivities/src/service/plugins/sqlite/org.kde.ActivityManager.RankingsClient.xml create mode 100644 kactivities/src/service/plugins/sqlite/org.kde.ActivityManager.Resources.Scoring.xml create mode 100644 kactivities/src/service/plugins/virtualdesktopswitch/CMakeLists.txt create mode 100644 kactivities/src/service/plugins/virtualdesktopswitch/VirtualDesktopSwitchPlugin.cpp create mode 100644 kactivities/src/service/plugins/virtualdesktopswitch/VirtualDesktopSwitchPlugin.h create mode 100644 kactivities/src/service/plugins/virtualdesktopswitch/activitymanager-plugin-virtualdesktopswitch.desktop create mode 100644 kactivities/src/utils/d_ptr.h create mode 100644 kactivities/src/utils/d_ptr_implementation.h create mode 100644 kactivities/src/utils/find_if_assoc.h create mode 100644 kactivities/src/utils/for_each_assoc.h create mode 100644 kactivities/src/utils/merge_into.h create mode 100644 kactivities/src/utils/nullptr.h create mode 100644 kactivities/src/utils/override.h create mode 100644 kactivities/src/utils/remove_if.h create mode 100644 kactivities/src/utils/val.h create mode 100644 kactivities/src/workspace/CMakeLists.txt create mode 100644 kactivities/src/workspace/fileitemplugin/CMakeLists.txt create mode 100644 kactivities/src/workspace/fileitemplugin/FileItemLinkingPlugin.cpp create mode 100644 kactivities/src/workspace/fileitemplugin/FileItemLinkingPlugin.h create mode 100644 kactivities/src/workspace/fileitemplugin/FileItemLinkingPlugin_p.h create mode 100644 kactivities/src/workspace/fileitemplugin/Messages.sh create mode 100644 kactivities/src/workspace/fileitemplugin/kactivitymanagerd_fileitem_linking_plugin.desktop create mode 100644 kactivities/src/workspace/kio/CMakeLists.txt create mode 100644 kactivities/src/workspace/kio/KioActivities.cpp create mode 100644 kactivities/src/workspace/kio/KioActivities.h create mode 100644 kactivities/src/workspace/kio/Messages.sh create mode 100644 kactivities/src/workspace/kio/activities.protocol create mode 100644 kactivities/src/workspace/settings/BlacklistedApplicationsModel.cpp create mode 100644 kactivities/src/workspace/settings/BlacklistedApplicationsModel.h create mode 100644 kactivities/src/workspace/settings/CMakeLists.txt create mode 100644 kactivities/src/workspace/settings/MainConfigurationWidget.cpp create mode 100644 kactivities/src/workspace/settings/MainConfigurationWidget.h create mode 100644 kactivities/src/workspace/settings/Messages.sh create mode 100644 kactivities/src/workspace/settings/kcm_activities.cpp create mode 100644 kactivities/src/workspace/settings/kcm_activities.desktop create mode 100644 kactivities/src/workspace/settings/qml/BlacklistApplicationView.qml create mode 100644 kactivities/src/workspace/settings/ui/MainConfigurationWidgetBase.ui create mode 100644 kcalc/CMakeLists.txt create mode 100644 kcalc/COPYING create mode 100644 kcalc/COPYING.LIB create mode 100644 kcalc/CTestConfig.cmake create mode 100644 kcalc/ChangeLog create mode 100644 kcalc/Mainpage.dox create mode 100644 kcalc/Messages.sh create mode 100644 kcalc/README create mode 100644 kcalc/TODO create mode 100644 kcalc/bitbutton.cpp create mode 100644 kcalc/bitbutton.h create mode 100644 kcalc/cmake/modules/FindMPFR.cmake create mode 100644 kcalc/colors.ui create mode 100644 kcalc/config-kcalc.h.cmake create mode 100644 kcalc/constants.ui create mode 100644 kcalc/doc/CMakeLists.txt create mode 100644 kcalc/doc/commands.docbook create mode 100644 kcalc/doc/index.docbook create mode 100644 kcalc/doc/kcalc_on_Aix.txt create mode 100644 kcalc/doc/kcalc_on_OSF.txt create mode 100644 kcalc/fonts.ui create mode 100644 kcalc/general.ui create mode 100644 kcalc/kcalc.cpp create mode 100755 kcalc/kcalc.desktop create mode 100644 kcalc/kcalc.h create mode 100644 kcalc/kcalc.kcfg create mode 100644 kcalc/kcalc.ui create mode 100644 kcalc/kcalc_bitset.cpp create mode 100644 kcalc/kcalc_bitset.h create mode 100644 kcalc/kcalc_button.cpp create mode 100644 kcalc/kcalc_button.h create mode 100644 kcalc/kcalc_const_button.cpp create mode 100644 kcalc/kcalc_const_button.h create mode 100644 kcalc/kcalc_const_menu.cpp create mode 100644 kcalc/kcalc_const_menu.h create mode 100644 kcalc/kcalc_core.cpp create mode 100644 kcalc/kcalc_core.h create mode 100644 kcalc/kcalc_settings.kcfgc create mode 100644 kcalc/kcalcdisplay.cpp create mode 100644 kcalc/kcalcdisplay.h create mode 100644 kcalc/kcalcrc.upd create mode 100644 kcalc/kcalcui.rc create mode 100644 kcalc/knumber/CMakeLists.txt create mode 100644 kcalc/knumber/knumber.cpp create mode 100644 kcalc/knumber/knumber.h create mode 100644 kcalc/knumber/knumber_base.h create mode 100644 kcalc/knumber/knumber_error.cpp create mode 100644 kcalc/knumber/knumber_error.h create mode 100644 kcalc/knumber/knumber_float.cpp create mode 100644 kcalc/knumber/knumber_float.h create mode 100644 kcalc/knumber/knumber_fraction.cpp create mode 100644 kcalc/knumber/knumber_fraction.h create mode 100644 kcalc/knumber/knumber_integer.cpp create mode 100644 kcalc/knumber/knumber_integer.h create mode 100644 kcalc/knumber/knumber_operators.cpp create mode 100644 kcalc/knumber/knumber_operators.h create mode 100644 kcalc/knumber/tests/CMakeLists.txt create mode 100644 kcalc/knumber/tests/knumbertest.cpp create mode 100644 kcalc/scienceconstants.xml create mode 100644 kcalc/stats.cpp create mode 100644 kcalc/stats.h create mode 100644 kcalc/version.h create mode 100644 kcron/AUTHORS create mode 100644 kcron/CMakeLists.txt create mode 100644 kcron/COPYING create mode 100644 kcron/ChangeLog create mode 100755 kcron/Messages.sh create mode 100644 kcron/README create mode 100644 kcron/TODO create mode 100644 kcron/doc/CMakeLists.txt create mode 100644 kcron/doc/index.docbook create mode 100644 kcron/doc/kcronstart.png create mode 100644 kcron/doc/newtask.png create mode 100644 kcron/doc/newvariable.png create mode 100644 kcron/kcron.lsm create mode 100644 kcron/src/CMakeLists.txt create mode 100644 kcron/src/crontabPrinter.cpp create mode 100644 kcron/src/crontabPrinter.h create mode 100644 kcron/src/crontabPrinterWidget.cpp create mode 100644 kcron/src/crontabPrinterWidget.h create mode 100644 kcron/src/crontabWidget.cpp create mode 100644 kcron/src/crontabWidget.h create mode 100644 kcron/src/crontablib/ctGlobalCron.cpp create mode 100644 kcron/src/crontablib/ctGlobalCron.h create mode 100644 kcron/src/crontablib/ctHelper.cpp create mode 100644 kcron/src/crontablib/ctHelper.h create mode 100644 kcron/src/crontablib/ctInitializationError.cpp create mode 100644 kcron/src/crontablib/ctInitializationError.h create mode 100644 kcron/src/crontablib/ctSaveStatus.cpp create mode 100644 kcron/src/crontablib/ctSaveStatus.h create mode 100644 kcron/src/crontablib/ctSystemCron.cpp create mode 100644 kcron/src/crontablib/ctSystemCron.h create mode 100644 kcron/src/crontablib/ctcron.cpp create mode 100644 kcron/src/crontablib/ctcron.h create mode 100644 kcron/src/crontablib/ctdom.cpp create mode 100644 kcron/src/crontablib/ctdom.h create mode 100644 kcron/src/crontablib/ctdow.cpp create mode 100644 kcron/src/crontablib/ctdow.h create mode 100644 kcron/src/crontablib/cthost.cpp create mode 100644 kcron/src/crontablib/cthost.h create mode 100644 kcron/src/crontablib/cthour.cpp create mode 100644 kcron/src/crontablib/cthour.h create mode 100644 kcron/src/crontablib/ctminute.cpp create mode 100644 kcron/src/crontablib/ctminute.h create mode 100644 kcron/src/crontablib/ctmonth.cpp create mode 100644 kcron/src/crontablib/ctmonth.h create mode 100644 kcron/src/crontablib/cttask.cpp create mode 100644 kcron/src/crontablib/cttask.h create mode 100644 kcron/src/crontablib/ctunit.cpp create mode 100644 kcron/src/crontablib/ctunit.h create mode 100644 kcron/src/crontablib/ctvariable.cpp create mode 100644 kcron/src/crontablib/ctvariable.h create mode 100644 kcron/src/crontablib/logging.h create mode 100644 kcron/src/genericListWidget.cpp create mode 100644 kcron/src/genericListWidget.h create mode 100644 kcron/src/kcmCron.cpp create mode 100644 kcron/src/kcmCron.h create mode 100644 kcron/src/kcm_cron.desktop create mode 100644 kcron/src/kcronHelper.cpp create mode 100644 kcron/src/kcronHelper.h create mode 100644 kcron/src/kcronIcons.cpp create mode 100644 kcron/src/kcronIcons.h create mode 100644 kcron/src/taskEditorDialog.cpp create mode 100644 kcron/src/taskEditorDialog.h create mode 100644 kcron/src/taskWidget.cpp create mode 100644 kcron/src/taskWidget.h create mode 100644 kcron/src/tasksWidget.cpp create mode 100644 kcron/src/tasksWidget.h create mode 100644 kcron/src/variableEditorDialog.cpp create mode 100644 kcron/src/variableEditorDialog.h create mode 100644 kcron/src/variableWidget.cpp create mode 100644 kcron/src/variableWidget.h create mode 100644 kcron/src/variablesWidget.cpp create mode 100644 kcron/src/variablesWidget.h create mode 100644 kdenetwork-filesharing/CMakeLists.txt create mode 100644 kdenetwork-filesharing/COPYING create mode 100644 kdenetwork-filesharing/COPYING.DOC create mode 100644 kdenetwork-filesharing/COPYING.LIB create mode 100644 kdenetwork-filesharing/Messages.sh create mode 100644 kdenetwork-filesharing/samba/CMakeLists.txt create mode 100644 kdenetwork-filesharing/samba/filepropertiesplugin/CMakeLists.txt create mode 100644 kdenetwork-filesharing/samba/filepropertiesplugin/delegate.cpp create mode 100644 kdenetwork-filesharing/samba/filepropertiesplugin/delegate.h create mode 100644 kdenetwork-filesharing/samba/filepropertiesplugin/model.cpp create mode 100644 kdenetwork-filesharing/samba/filepropertiesplugin/model.h create mode 100644 kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.cpp create mode 100644 kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.desktop create mode 100644 kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.h create mode 100644 kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.ui create mode 100644 kfilemetadata/CMakeLists.txt create mode 100644 kfilemetadata/COPYING-CMAKE-SCRIPTS create mode 100644 kfilemetadata/COPYING.LGPL-2 create mode 100644 kfilemetadata/COPYING.LGPL-2.1 create mode 100644 kfilemetadata/COPYING.LGPL-3 create mode 100644 kfilemetadata/KFileMetaDataConfig.cmake.in create mode 100644 kfilemetadata/Messages.sh create mode 100644 kfilemetadata/autotests/CMakeLists.txt create mode 100644 kfilemetadata/autotests/indexerextractortests.cpp create mode 100644 kfilemetadata/autotests/indexerextractortests.h create mode 100644 kfilemetadata/autotests/indexerextractortestsconfig.h.in create mode 100644 kfilemetadata/autotests/propertyinfotest.cpp create mode 100644 kfilemetadata/autotests/propertyinfotest.h create mode 100644 kfilemetadata/autotests/samplefiles/README create mode 100644 kfilemetadata/autotests/samplefiles/plain_text_file.txt create mode 100644 kfilemetadata/autotests/simpleresult.cpp create mode 100644 kfilemetadata/autotests/simpleresult.h create mode 100644 kfilemetadata/cmake/modules/FindEPub.cmake create mode 100644 kfilemetadata/cmake/modules/FindPopplerQt4.cmake create mode 100644 kfilemetadata/src/CMakeLists.txt create mode 100644 kfilemetadata/src/extractionresult.cpp create mode 100644 kfilemetadata/src/extractionresult.h create mode 100644 kfilemetadata/src/extractorplugin.cpp create mode 100644 kfilemetadata/src/extractorplugin.h create mode 100644 kfilemetadata/src/extractorpluginmanager.cpp create mode 100644 kfilemetadata/src/extractorpluginmanager.h create mode 100644 kfilemetadata/src/extractors/CMakeLists.txt create mode 100644 kfilemetadata/src/extractors/epubextractor.cpp create mode 100644 kfilemetadata/src/extractors/epubextractor.h create mode 100644 kfilemetadata/src/extractors/exiv2extractor.cpp create mode 100644 kfilemetadata/src/extractors/exiv2extractor.h create mode 100644 kfilemetadata/src/extractors/ffmpegextractor.cpp create mode 100644 kfilemetadata/src/extractors/ffmpegextractor.h create mode 100644 kfilemetadata/src/extractors/kfilemetadata_epubextractor.desktop create mode 100644 kfilemetadata/src/extractors/kfilemetadata_exiv2extractor.desktop create mode 100644 kfilemetadata/src/extractors/kfilemetadata_ffmpegextractor.desktop create mode 100644 kfilemetadata/src/extractors/kfilemetadata_mobiextractor.desktop create mode 100644 kfilemetadata/src/extractors/kfilemetadata_odfextractor.desktop create mode 100644 kfilemetadata/src/extractors/kfilemetadata_office2007extractor.desktop create mode 100644 kfilemetadata/src/extractors/kfilemetadata_officeextractor.desktop create mode 100644 kfilemetadata/src/extractors/kfilemetadata_plaintextextractor.desktop create mode 100644 kfilemetadata/src/extractors/kfilemetadata_popplerextractor.desktop create mode 100644 kfilemetadata/src/extractors/kfilemetadata_taglibextractor.desktop create mode 100644 kfilemetadata/src/extractors/mobiextractor.cpp create mode 100644 kfilemetadata/src/extractors/mobiextractor.h create mode 100644 kfilemetadata/src/extractors/odfextractor.cpp create mode 100644 kfilemetadata/src/extractors/odfextractor.h create mode 100644 kfilemetadata/src/extractors/office2007extractor.cpp create mode 100644 kfilemetadata/src/extractors/office2007extractor.h create mode 100644 kfilemetadata/src/extractors/officeextractor.cpp create mode 100644 kfilemetadata/src/extractors/officeextractor.h create mode 100644 kfilemetadata/src/extractors/plaintextextractor.cpp create mode 100644 kfilemetadata/src/extractors/plaintextextractor.h create mode 100644 kfilemetadata/src/extractors/popplerextractor.cpp create mode 100644 kfilemetadata/src/extractors/popplerextractor.h create mode 100644 kfilemetadata/src/extractors/taglibextractor.cpp create mode 100644 kfilemetadata/src/extractors/taglibextractor.h create mode 100644 kfilemetadata/src/kfilemetadata_export.h create mode 100644 kfilemetadata/src/kfilemetadataextractor.desktop create mode 100644 kfilemetadata/src/properties.h create mode 100644 kfilemetadata/src/propertyinfo.cpp create mode 100644 kfilemetadata/src/propertyinfo.h create mode 100644 kfilemetadata/src/typeinfo.cpp create mode 100644 kfilemetadata/src/typeinfo.h create mode 100644 kfilemetadata/src/types.h create mode 100644 kgpg/.gitignore create mode 100644 kgpg/AUTHORS create mode 100644 kgpg/CMakeLists.txt create mode 100644 kgpg/COPYING create mode 100644 kgpg/CTestConfig.cmake create mode 100644 kgpg/Mainpage.dox create mode 100644 kgpg/Messages.sh create mode 100644 kgpg/TODO create mode 100644 kgpg/adduid.ui create mode 100644 kgpg/caff.cpp create mode 100644 kgpg/caff.h create mode 100644 kgpg/caff_p.h create mode 100644 kgpg/conf_decryption.ui create mode 100644 kgpg/conf_encryption.cpp create mode 100644 kgpg/conf_encryption.h create mode 100644 kgpg/conf_encryption.ui create mode 100644 kgpg/conf_gpg.ui create mode 100644 kgpg/conf_misc.ui create mode 100644 kgpg/conf_servers.ui create mode 100644 kgpg/conf_ui2.ui create mode 100644 kgpg/core/KGpgExpandableNode.cpp create mode 100644 kgpg/core/KGpgExpandableNode.h create mode 100644 kgpg/core/KGpgGroupMemberNode.cpp create mode 100644 kgpg/core/KGpgGroupMemberNode.h create mode 100644 kgpg/core/KGpgGroupNode.cpp create mode 100644 kgpg/core/KGpgGroupNode.h create mode 100644 kgpg/core/KGpgKeyNode.cpp create mode 100644 kgpg/core/KGpgKeyNode.h create mode 100644 kgpg/core/KGpgNode.cpp create mode 100644 kgpg/core/KGpgNode.h create mode 100644 kgpg/core/KGpgOrphanNode.cpp create mode 100644 kgpg/core/KGpgOrphanNode.h create mode 100644 kgpg/core/KGpgRefNode.cpp create mode 100644 kgpg/core/KGpgRefNode.h create mode 100644 kgpg/core/KGpgRootNode.cpp create mode 100644 kgpg/core/KGpgRootNode.h create mode 100644 kgpg/core/KGpgSignNode.cpp create mode 100644 kgpg/core/KGpgSignNode.h create mode 100644 kgpg/core/KGpgSignableNode.cpp create mode 100644 kgpg/core/KGpgSignableNode.h create mode 100644 kgpg/core/KGpgSubkeyNode.cpp create mode 100644 kgpg/core/KGpgSubkeyNode.h create mode 100644 kgpg/core/KGpgUatNode.cpp create mode 100644 kgpg/core/KGpgUatNode.h create mode 100644 kgpg/core/KGpgUidNode.cpp create mode 100644 kgpg/core/KGpgUidNode.h create mode 100644 kgpg/core/convert.cpp create mode 100644 kgpg/core/convert.h create mode 100644 kgpg/core/emailvalidator.cpp create mode 100644 kgpg/core/emailvalidator.h create mode 100644 kgpg/core/images.cpp create mode 100644 kgpg/core/images.h create mode 100644 kgpg/core/kgpgkey.cpp create mode 100644 kgpg/core/kgpgkey.h create mode 100644 kgpg/detailedconsole.cpp create mode 100644 kgpg/detailedconsole.h create mode 100644 kgpg/doc/CMakeLists.txt create mode 100644 kgpg/doc/editor.png create mode 100644 kgpg/doc/index.docbook create mode 100644 kgpg/doc/keygen.png create mode 100644 kgpg/doc/keymanage.png create mode 100644 kgpg/doc/keyprop.png create mode 100644 kgpg/doc/keys.png create mode 100644 kgpg/doc/keyserver-search.png create mode 100644 kgpg/doc/keyserver.png create mode 100644 kgpg/doc/options.png create mode 100644 kgpg/doc/select-secret-key.png create mode 100644 kgpg/doc/systray.png create mode 100644 kgpg/editor/kgpgeditor.cpp create mode 100644 kgpg/editor/kgpgeditor.h create mode 100644 kgpg/editor/kgpgeditor.rc create mode 100644 kgpg/editor/kgpgmd5widget.cpp create mode 100644 kgpg/editor/kgpgmd5widget.h create mode 100644 kgpg/editor/kgpgtextedit.cpp create mode 100644 kgpg/editor/kgpgtextedit.h create mode 100644 kgpg/encryptfile.desktop create mode 100644 kgpg/encryptfolder.desktop create mode 100644 kgpg/foldercompressjob.cpp create mode 100644 kgpg/foldercompressjob.h create mode 100644 kgpg/gpgproc.cpp create mode 100644 kgpg/gpgproc.h create mode 100644 kgpg/groupedit.cpp create mode 100644 kgpg/groupedit.h create mode 100644 kgpg/groupedit.ui create mode 100644 kgpg/hi16-app-kgpg.png create mode 100644 kgpg/hi22-app-kgpg.png create mode 100644 kgpg/hi32-app-kgpg.png create mode 100644 kgpg/hi48-app-kgpg.png create mode 100644 kgpg/icons/CMakeLists.txt create mode 100644 kgpg/icons/hi16-actions-document-export-key.png create mode 100644 kgpg/icons/hi16-actions-document-import-key.png create mode 100644 kgpg/icons/hi16-actions-document-properties-key.png create mode 100644 kgpg/icons/hi16-status-key-group.png create mode 100644 kgpg/icons/hi16-status-key-orphan.png create mode 100644 kgpg/icons/hi16-status-key-pair.png create mode 100644 kgpg/icons/hi16-status-key-single.png create mode 100644 kgpg/icons/hi22-action-key-generate-pair.png create mode 100644 kgpg/icons/hi22-action-view-key-secret.png create mode 100644 kgpg/icons/hi22-actions-document-export-key.png create mode 100644 kgpg/icons/hi22-actions-document-import-key.png create mode 100644 kgpg/icons/hi22-actions-document-properties-key.png create mode 100644 kgpg/icons/hi22-status-key-group.png create mode 100644 kgpg/icons/hi22-status-key-pair.png create mode 100644 kgpg/icons/hi22-status-key-single.png create mode 100644 kgpg/icons/hi32-actions-document-export-key.png create mode 100644 kgpg/icons/hi32-actions-document-import-key.png create mode 100644 kgpg/icons/hi32-actions-document-properties-key.png create mode 100644 kgpg/icons/hi32-status-key-group.png create mode 100644 kgpg/icons/hi32-status-key-pair.png create mode 100644 kgpg/icons/hi32-status-key-single.png create mode 100644 kgpg/icons/hi48-actions-document-export-key.png create mode 100644 kgpg/icons/hi48-actions-document-import-key.png create mode 100644 kgpg/icons/hi48-actions-document-properties-key.png create mode 100644 kgpg/icons/hi48-status-key-group.png create mode 100644 kgpg/icons/hi48-status-key-pair.png create mode 100644 kgpg/icons/hi48-status-key-single.png create mode 100644 kgpg/icons/hisc-actions-document-export-key.svgz create mode 100644 kgpg/icons/hisc-actions-document-import-key.svgz create mode 100644 kgpg/icons/hisc-actions-document-properties-key.svgz create mode 100644 kgpg/icons/hisc-status-key-group.svgz create mode 100644 kgpg/icons/hisc-status-key-pair.svgz create mode 100644 kgpg/icons/hisc-status-key-single.svgz create mode 100644 kgpg/keyexport.cpp create mode 100644 kgpg/keyexport.h create mode 100644 kgpg/keyexport.ui create mode 100644 kgpg/keyinfodialog.cpp create mode 100644 kgpg/keyinfodialog.h create mode 100644 kgpg/keyserver.ui create mode 100644 kgpg/keyservers.cpp create mode 100644 kgpg/keyservers.h create mode 100644 kgpg/keysmanager.cpp create mode 100644 kgpg/keysmanager.h create mode 100644 kgpg/keysmanager.rc create mode 100644 kgpg/keytreeview.cpp create mode 100644 kgpg/keytreeview.h create mode 100644 kgpg/kgpg.appdata.xml create mode 100644 kgpg/kgpg.cpp create mode 100755 kgpg/kgpg.desktop create mode 100644 kgpg/kgpg.h create mode 100644 kgpg/kgpg.kcfg create mode 100644 kgpg/kgpgKeyInfo.ui create mode 100644 kgpg/kgpgchangekey.cpp create mode 100644 kgpg/kgpgchangekey.h create mode 100644 kgpg/kgpgexternalactions.cpp create mode 100644 kgpg/kgpgexternalactions.h create mode 100644 kgpg/kgpgfirstassistant.cpp create mode 100644 kgpg/kgpgfirstassistant.h create mode 100644 kgpg/kgpginterface.cpp create mode 100644 kgpg/kgpginterface.h create mode 100644 kgpg/kgpgkeygenerate.cpp create mode 100644 kgpg/kgpgkeygenerate.h create mode 100644 kgpg/kgpgkeygenerate.ui create mode 100644 kgpg/kgpgoptions.cpp create mode 100644 kgpg/kgpgoptions.h create mode 100644 kgpg/kgpgrevokewidget.cpp create mode 100644 kgpg/kgpgrevokewidget.h create mode 100644 kgpg/kgpgrevokewidget.ui create mode 100644 kgpg/kgpgsettings.kcfgc create mode 100644 kgpg/kgpgsettings_addons.h create mode 100644 kgpg/kgpgtextinterface.cpp create mode 100644 kgpg/kgpgtextinterface.h create mode 100644 kgpg/klinebufferedprocess.cpp create mode 100644 kgpg/klinebufferedprocess.h create mode 100644 kgpg/main.cpp create mode 100644 kgpg/model/gpgservermodel.cpp create mode 100644 kgpg/model/gpgservermodel.h create mode 100644 kgpg/model/groupeditproxymodel.cpp create mode 100644 kgpg/model/groupeditproxymodel.h create mode 100644 kgpg/model/keylistproxymodel.cpp create mode 100644 kgpg/model/keylistproxymodel.h create mode 100644 kgpg/model/kgpgitemmodel.cpp create mode 100644 kgpg/model/kgpgitemmodel.h create mode 100644 kgpg/model/kgpgitemnode.h create mode 100644 kgpg/model/kgpgsearchresultmodel.cpp create mode 100644 kgpg/model/kgpgsearchresultmodel.h create mode 100644 kgpg/model/selectkeyproxymodel.cpp create mode 100644 kgpg/model/selectkeyproxymodel.h create mode 100644 kgpg/newkey.cpp create mode 100644 kgpg/newkey.h create mode 100644 kgpg/newkey.ui create mode 100644 kgpg/org.kde.kgpg.Key.xml create mode 100644 kgpg/searchres.ui create mode 100644 kgpg/selectexpirydate.cpp create mode 100644 kgpg/selectexpirydate.h create mode 100644 kgpg/selectpublickeydialog.cpp create mode 100644 kgpg/selectpublickeydialog.h create mode 100644 kgpg/selectsecretkey.cpp create mode 100644 kgpg/selectsecretkey.h create mode 100644 kgpg/sourceselect.cpp create mode 100644 kgpg/sourceselect.h create mode 100644 kgpg/sourceselect.ui create mode 100644 kgpg/tips create mode 100644 kgpg/transactions/kgpgaddphoto.cpp create mode 100644 kgpg/transactions/kgpgaddphoto.h create mode 100644 kgpg/transactions/kgpgadduid.cpp create mode 100644 kgpg/transactions/kgpgadduid.h create mode 100644 kgpg/transactions/kgpgchangedisable.cpp create mode 100644 kgpg/transactions/kgpgchangedisable.h create mode 100644 kgpg/transactions/kgpgchangeexpire.cpp create mode 100644 kgpg/transactions/kgpgchangeexpire.h create mode 100644 kgpg/transactions/kgpgchangepass.cpp create mode 100644 kgpg/transactions/kgpgchangepass.h create mode 100644 kgpg/transactions/kgpgchangetrust.cpp create mode 100644 kgpg/transactions/kgpgchangetrust.h create mode 100644 kgpg/transactions/kgpgdecrypt.cpp create mode 100644 kgpg/transactions/kgpgdecrypt.h create mode 100644 kgpg/transactions/kgpgdelkey.cpp create mode 100644 kgpg/transactions/kgpgdelkey.h create mode 100644 kgpg/transactions/kgpgdelsign.cpp create mode 100644 kgpg/transactions/kgpgdelsign.h create mode 100644 kgpg/transactions/kgpgdeluid.cpp create mode 100644 kgpg/transactions/kgpgdeluid.h create mode 100644 kgpg/transactions/kgpgeditkeytransaction.cpp create mode 100644 kgpg/transactions/kgpgeditkeytransaction.h create mode 100644 kgpg/transactions/kgpgencrypt.cpp create mode 100644 kgpg/transactions/kgpgencrypt.h create mode 100644 kgpg/transactions/kgpgexport.cpp create mode 100644 kgpg/transactions/kgpgexport.h create mode 100644 kgpg/transactions/kgpggeneratekey.cpp create mode 100644 kgpg/transactions/kgpggeneratekey.h create mode 100644 kgpg/transactions/kgpggeneraterevoke.cpp create mode 100644 kgpg/transactions/kgpggeneraterevoke.h create mode 100644 kgpg/transactions/kgpgimport.cpp create mode 100644 kgpg/transactions/kgpgimport.h create mode 100644 kgpg/transactions/kgpgkeyservergettransaction.cpp create mode 100644 kgpg/transactions/kgpgkeyservergettransaction.h create mode 100644 kgpg/transactions/kgpgkeyserversearchtransaction.cpp create mode 100644 kgpg/transactions/kgpgkeyserversearchtransaction.h create mode 100644 kgpg/transactions/kgpgkeyservertransaction.cpp create mode 100644 kgpg/transactions/kgpgkeyservertransaction.h create mode 100644 kgpg/transactions/kgpgprimaryuid.cpp create mode 100644 kgpg/transactions/kgpgprimaryuid.h create mode 100644 kgpg/transactions/kgpgsendkeys.cpp create mode 100644 kgpg/transactions/kgpgsendkeys.h create mode 100644 kgpg/transactions/kgpgsignkey.cpp create mode 100644 kgpg/transactions/kgpgsignkey.h create mode 100644 kgpg/transactions/kgpgsigntext.cpp create mode 100644 kgpg/transactions/kgpgsigntext.h create mode 100644 kgpg/transactions/kgpgsigntransactionhelper.cpp create mode 100644 kgpg/transactions/kgpgsigntransactionhelper.h create mode 100644 kgpg/transactions/kgpgsignuid.cpp create mode 100644 kgpg/transactions/kgpgsignuid.h create mode 100644 kgpg/transactions/kgpgtextorfiletransaction.cpp create mode 100644 kgpg/transactions/kgpgtextorfiletransaction.h create mode 100644 kgpg/transactions/kgpgtransaction.cpp create mode 100644 kgpg/transactions/kgpgtransaction.h create mode 100644 kgpg/transactions/kgpgtransactionjob.cpp create mode 100644 kgpg/transactions/kgpgtransactionjob.h create mode 100644 kgpg/transactions/kgpguidtransaction.cpp create mode 100644 kgpg/transactions/kgpguidtransaction.h create mode 100644 kgpg/transactions/kgpgverify.cpp create mode 100644 kgpg/transactions/kgpgverify.h create mode 100644 kgpg/viewdecrypted.desktop create mode 100644 kmix/AUTHORS create mode 100644 kmix/CMakeLists.txt create mode 100644 kmix/COPYING create mode 100644 kmix/COPYING.DOC create mode 100644 kmix/COPYING.LIB create mode 100644 kmix/ChangeLog create mode 100644 kmix/ConfigureChecks.cmake create mode 100644 kmix/Messages.sh create mode 100644 kmix/TODO create mode 100644 kmix/TestCases create mode 100644 kmix/apps/KMixApp.cpp create mode 100644 kmix/apps/KMixApp.h create mode 100644 kmix/apps/kmix.cpp create mode 100644 kmix/apps/kmix.h create mode 100644 kmix/apps/kmixctrl.cpp create mode 100644 kmix/apps/kmixd.cpp create mode 100644 kmix/apps/kmixd.h create mode 100755 kmix/apps/kmixremote create mode 100644 kmix/apps/main.cpp create mode 100644 kmix/backends/kmix-backends.cpp create mode 100644 kmix/backends/mixer_alsa.h create mode 100644 kmix/backends/mixer_alsa9.cpp create mode 100644 kmix/backends/mixer_backend.cpp create mode 100644 kmix/backends/mixer_backend.h create mode 100644 kmix/backends/mixer_backend_i18n.cpp create mode 100644 kmix/backends/mixer_mpris2.cpp create mode 100644 kmix/backends/mixer_mpris2.h create mode 100644 kmix/backends/mixer_oss.cpp create mode 100644 kmix/backends/mixer_oss.h create mode 100644 kmix/backends/mixer_oss4.cpp create mode 100644 kmix/backends/mixer_oss4.h create mode 100644 kmix/backends/mixer_pulse.cpp create mode 100644 kmix/backends/mixer_pulse.h create mode 100644 kmix/backends/mixer_sun.cpp create mode 100644 kmix/backends/mixer_sun.h create mode 100644 kmix/cmake/modules/COPYING-CMAKE-SCRIPTS create mode 100644 kmix/cmake/modules/FindCanberra.cmake create mode 100644 kmix/colorwidget.ui create mode 100644 kmix/config.h.cmake create mode 100644 kmix/core/ControlManager.cpp create mode 100644 kmix/core/ControlManager.h create mode 100644 kmix/core/ControlPool.cpp create mode 100644 kmix/core/ControlPool.h create mode 100644 kmix/core/GlobalConfig.cpp create mode 100644 kmix/core/GlobalConfig.h create mode 100644 kmix/core/MasterControl.cpp create mode 100644 kmix/core/MasterControl.h create mode 100644 kmix/core/MediaController.cpp create mode 100644 kmix/core/MediaController.h create mode 100644 kmix/core/kmixdevicemanager.cpp create mode 100644 kmix/core/kmixdevicemanager.h create mode 100644 kmix/core/mixdevice.cpp create mode 100644 kmix/core/mixdevice.h create mode 100644 kmix/core/mixdevicecomposite.cpp create mode 100644 kmix/core/mixdevicecomposite.h create mode 100644 kmix/core/mixer.cpp create mode 100644 kmix/core/mixer.h create mode 100644 kmix/core/mixertoolbox.cpp create mode 100644 kmix/core/mixertoolbox.h create mode 100644 kmix/core/mixset.cpp create mode 100644 kmix/core/mixset.h create mode 100644 kmix/core/version.h create mode 100644 kmix/core/volume.cpp create mode 100644 kmix/core/volume.h create mode 100644 kmix/dbus/dbuscontrolwrapper.cpp create mode 100644 kmix/dbus/dbuscontrolwrapper.h create mode 100644 kmix/dbus/dbusmixerwrapper.cpp create mode 100644 kmix/dbus/dbusmixerwrapper.h create mode 100644 kmix/dbus/dbusmixsetwrapper.cpp create mode 100644 kmix/dbus/dbusmixsetwrapper.h create mode 100644 kmix/dbus/org.kde.kmix.control.xml create mode 100644 kmix/dbus/org.kde.kmix.mixer.xml create mode 100644 kmix/dbus/org.kde.kmix.mixset.xml create mode 100644 kmix/doc/CMakeLists.txt create mode 100644 kmix/doc/ControlNames.txt create mode 100644 kmix/doc/index.docbook create mode 100644 kmix/doc/kmix-configure-general.png create mode 100644 kmix/doc/kmix-configure-sound-menu.png create mode 100644 kmix/doc/kmix-configure-start.png create mode 100644 kmix/doc/kmix-file.png create mode 100644 kmix/doc/kmix-master.png create mode 100644 kmix/doc/kmix-options.png create mode 100644 kmix/doc/kmix.png create mode 100644 kmix/gui/dialogaddview.cpp create mode 100644 kmix/gui/dialogaddview.h create mode 100644 kmix/gui/dialogchoosebackends.cpp create mode 100644 kmix/gui/dialogchoosebackends.h create mode 100644 kmix/gui/dialogselectmaster.cpp create mode 100644 kmix/gui/dialogselectmaster.h create mode 100644 kmix/gui/dialogviewconfiguration.cpp create mode 100644 kmix/gui/dialogviewconfiguration.h create mode 100644 kmix/gui/guiprofile.cpp create mode 100644 kmix/gui/guiprofile.h create mode 100644 kmix/gui/kmixdockwidget.cpp create mode 100644 kmix/gui/kmixdockwidget.h create mode 100644 kmix/gui/kmixerwidget.cpp create mode 100644 kmix/gui/kmixerwidget.h create mode 100644 kmix/gui/kmixprefdlg.cpp create mode 100644 kmix/gui/kmixprefdlg.h create mode 100644 kmix/gui/kmixtoolbox.cpp create mode 100644 kmix/gui/kmixtoolbox.h create mode 100644 kmix/gui/ksmallslider.cpp create mode 100644 kmix/gui/ksmallslider.h create mode 100644 kmix/gui/mdwenum.cpp create mode 100644 kmix/gui/mdwenum.h create mode 100644 kmix/gui/mdwmoveaction.cpp create mode 100644 kmix/gui/mdwmoveaction.h create mode 100644 kmix/gui/mdwslider.cpp create mode 100644 kmix/gui/mdwslider.h create mode 100644 kmix/gui/mdwswitch.cpp create mode 100644 kmix/gui/mdwswitch.h create mode 100644 kmix/gui/mixdevicewidget.cpp create mode 100644 kmix/gui/mixdevicewidget.h create mode 100644 kmix/gui/osdwidget.cpp create mode 100644 kmix/gui/osdwidget.h create mode 100644 kmix/gui/verticaltext.cpp create mode 100644 kmix/gui/verticaltext.h create mode 100644 kmix/gui/viewbase.cpp create mode 100644 kmix/gui/viewbase.h create mode 100644 kmix/gui/viewdockareapopup.cpp create mode 100644 kmix/gui/viewdockareapopup.h create mode 100644 kmix/gui/viewsliders.cpp create mode 100644 kmix/gui/viewsliders.h create mode 100644 kmix/gui/volumeslider.cpp create mode 100644 kmix/gui/volumeslider.h create mode 100644 kmix/gui/volumesliderextradata.h create mode 100755 kmix/kmix.desktop create mode 100644 kmix/kmix.notifyrc create mode 100755 kmix/kmix_autostart.desktop create mode 100644 kmix/kmixctrl_restore.desktop create mode 100644 kmix/kmixd.desktop create mode 100644 kmix/kmixui.rc create mode 100644 kmix/l10n/README create mode 100644 kmix/l10n/kmix-controls-de.po create mode 100644 kmix/l10n/kmix-controls-en.po create mode 100644 kmix/pics/CMakeLists.txt create mode 100644 kmix/pics/hi128-app-kmix.png create mode 100644 kmix/pics/hi16-app-kmix.png create mode 100644 kmix/pics/hi32-app-kmix.png create mode 100644 kmix/pics/hi48-app-kmix.png create mode 100644 kmix/pics/hi64-app-kmix.png create mode 100644 kmix/pics/kmixdocked.png create mode 100644 kmix/pics/kmixdocked_error.png create mode 100644 kmix/pics/kmixdocked_mute.png create mode 100644 kmix/pics/mixer-ac97.png create mode 100644 kmix/pics/mixer-capture.png create mode 100644 kmix/pics/mixer-cd.png create mode 100644 kmix/pics/mixer-digital.png create mode 100644 kmix/pics/mixer-front.png create mode 100644 kmix/pics/mixer-headset.png create mode 100644 kmix/pics/mixer-lfe.png create mode 100644 kmix/pics/mixer-line.png create mode 100644 kmix/pics/mixer-master.png create mode 100644 kmix/pics/mixer-microphone.png create mode 100644 kmix/pics/mixer-midi.png create mode 100644 kmix/pics/mixer-pcm-default.png create mode 100644 kmix/pics/mixer-pcm.png create mode 100644 kmix/pics/mixer-surround.png create mode 100644 kmix/pics/mixer-video.png create mode 100644 kmix/plasma/CMakeLists.txt create mode 100644 kmix/plasma/engine/CMakeLists.txt create mode 100644 kmix/plasma/engine/mixer.operations create mode 100644 kmix/plasma/engine/mixerengine.cpp create mode 100644 kmix/plasma/engine/mixerengine.h create mode 100644 kmix/plasma/engine/mixerservice.cpp create mode 100644 kmix/plasma/engine/mixerservice.h create mode 100644 kmix/plasma/engine/plasma-engine-mixer.desktop create mode 100644 kmix/profiles/ALSA.Sound_Fusion_CS46xx.xml create mode 100644 kmix/profiles/ALSA.TerraTec_DMX6Fire.1.default.xml create mode 100644 kmix/profiles/ALSA.capture.xml create mode 100644 kmix/profiles/ALSA.default.xml create mode 100644 kmix/profiles/ALSA.playback.xml create mode 100644 kmix/profiles/CMakeLists.txt create mode 100644 kmix/profiles/OSS.default.xml create mode 100755 kmix/restore_kmix_volumes.desktop create mode 100644 kmix/tests/CMakeLists.txt create mode 100644 kmix/tests/dialogtest.cpp create mode 100644 kmix/tests/dialogtest.h create mode 100644 kmix/tests/profiletest-wrong.xml create mode 100644 kmix/tests/profiletest.cpp create mode 100644 kmix/tests/profiletest.xml create mode 100644 kolourpaint/.krazy create mode 100644 kolourpaint/AUTHORS create mode 100644 kolourpaint/BUGS create mode 100644 kolourpaint/CMakeLists.txt create mode 100644 kolourpaint/COPYING create mode 100644 kolourpaint/COPYING.DOC create mode 100644 kolourpaint/COPYING.LIB create mode 100644 kolourpaint/ChangeLog create mode 100644 kolourpaint/Messages.sh create mode 100644 kolourpaint/NEWS create mode 100644 kolourpaint/README create mode 100644 kolourpaint/VERSION create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectBalanceCommand.cpp create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectBalanceCommand.h create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectBlurSharpenCommand.h create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectClearCommand.cpp create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectClearCommand.h create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectCommandBase.cpp create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectCommandBase.h create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectEmbossCommand.cpp create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectEmbossCommand.h create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectFlattenCommand.cpp create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectFlattenCommand.h create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectGrayscaleCommand.cpp create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectGrayscaleCommand.h create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectHSVCommand.cpp create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectHSVCommand.h create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectInvertCommand.cpp create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectInvertCommand.h create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectReduceColorsCommand.cpp create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectReduceColorsCommand.h create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectToneEnhanceCommand.cpp create mode 100644 kolourpaint/commands/imagelib/effects/kpEffectToneEnhanceCommand.h create mode 100644 kolourpaint/commands/imagelib/kpDocumentMetaInfoCommand.cpp create mode 100644 kolourpaint/commands/imagelib/kpDocumentMetaInfoCommand.h create mode 100644 kolourpaint/commands/imagelib/transforms/kpTransformFlipCommand.cpp create mode 100644 kolourpaint/commands/imagelib/transforms/kpTransformFlipCommand.h create mode 100644 kolourpaint/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp create mode 100644 kolourpaint/commands/imagelib/transforms/kpTransformResizeScaleCommand.h create mode 100644 kolourpaint/commands/imagelib/transforms/kpTransformRotateCommand.cpp create mode 100644 kolourpaint/commands/imagelib/transforms/kpTransformRotateCommand.h create mode 100644 kolourpaint/commands/imagelib/transforms/kpTransformSkewCommand.cpp create mode 100644 kolourpaint/commands/imagelib/transforms/kpTransformSkewCommand.h create mode 100644 kolourpaint/commands/kpCommand.cpp create mode 100644 kolourpaint/commands/kpCommand.h create mode 100644 kolourpaint/commands/kpCommandHistory.cpp create mode 100644 kolourpaint/commands/kpCommandHistory.h create mode 100644 kolourpaint/commands/kpCommandHistoryBase.cpp create mode 100644 kolourpaint/commands/kpCommandHistoryBase.h create mode 100644 kolourpaint/commands/kpCommandSize.cpp create mode 100644 kolourpaint/commands/kpCommandSize.h create mode 100644 kolourpaint/commands/kpMacroCommand.cpp create mode 100644 kolourpaint/commands/kpMacroCommand.h create mode 100644 kolourpaint/commands/kpNamedCommand.cpp create mode 100644 kolourpaint/commands/kpNamedCommand.h create mode 100644 kolourpaint/commands/tools/flow/kpToolFlowCommand.cpp create mode 100644 kolourpaint/commands/tools/flow/kpToolFlowCommand.h create mode 100644 kolourpaint/commands/tools/kpToolColorPickerCommand.cpp create mode 100644 kolourpaint/commands/tools/kpToolColorPickerCommand.h create mode 100644 kolourpaint/commands/tools/kpToolFloodFillCommand.cpp create mode 100644 kolourpaint/commands/tools/kpToolFloodFillCommand.h create mode 100644 kolourpaint/commands/tools/polygonal/kpToolPolygonalCommand.cpp create mode 100644 kolourpaint/commands/tools/polygonal/kpToolPolygonalCommand.h create mode 100644 kolourpaint/commands/tools/rectangular/kpToolRectangularCommand.cpp create mode 100644 kolourpaint/commands/tools/rectangular/kpToolRectangularCommand.h create mode 100644 kolourpaint/commands/tools/selection/kpAbstractSelectionContentCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/kpAbstractSelectionContentCommand.h create mode 100644 kolourpaint/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/kpToolImageSelectionTransparencyCommand.h create mode 100644 kolourpaint/commands/tools/selection/kpToolSelectionCreateCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/kpToolSelectionCreateCommand.h create mode 100644 kolourpaint/commands/tools/selection/kpToolSelectionDestroyCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/kpToolSelectionDestroyCommand.h create mode 100644 kolourpaint/commands/tools/selection/kpToolSelectionMoveCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/kpToolSelectionMoveCommand.h create mode 100644 kolourpaint/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.h create mode 100644 kolourpaint/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/kpToolSelectionResizeScaleCommand.h create mode 100644 kolourpaint/commands/tools/selection/text/kpToolTextBackspaceCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/text/kpToolTextBackspaceCommand.h create mode 100644 kolourpaint/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/text/kpToolTextChangeStyleCommand.h create mode 100644 kolourpaint/commands/tools/selection/text/kpToolTextDeleteCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/text/kpToolTextDeleteCommand.h create mode 100644 kolourpaint/commands/tools/selection/text/kpToolTextEnterCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/text/kpToolTextEnterCommand.h create mode 100644 kolourpaint/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/text/kpToolTextGiveContentCommand.h create mode 100644 kolourpaint/commands/tools/selection/text/kpToolTextInsertCommand.cpp create mode 100644 kolourpaint/commands/tools/selection/text/kpToolTextInsertCommand.h create mode 100644 kolourpaint/cursors/kpCursorLightCross.cpp create mode 100644 kolourpaint/cursors/kpCursorLightCross.h create mode 100644 kolourpaint/cursors/kpCursorProvider.cpp create mode 100644 kolourpaint/cursors/kpCursorProvider.h create mode 100644 kolourpaint/dialogs/imagelib/effects/kpEffectsDialog.cpp create mode 100644 kolourpaint/dialogs/imagelib/effects/kpEffectsDialog.h create mode 100644 kolourpaint/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp create mode 100644 kolourpaint/dialogs/imagelib/kpDocumentMetaInfoDialog.h create mode 100644 kolourpaint/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp create mode 100644 kolourpaint/dialogs/imagelib/transforms/kpTransformPreviewDialog.h create mode 100644 kolourpaint/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp create mode 100644 kolourpaint/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.h create mode 100644 kolourpaint/dialogs/imagelib/transforms/kpTransformRotateDialog.cpp create mode 100644 kolourpaint/dialogs/imagelib/transforms/kpTransformRotateDialog.h create mode 100644 kolourpaint/dialogs/imagelib/transforms/kpTransformSkewDialog.cpp create mode 100644 kolourpaint/dialogs/imagelib/transforms/kpTransformSkewDialog.h create mode 100644 kolourpaint/dialogs/kpColorSimilarityDialog.cpp create mode 100644 kolourpaint/dialogs/kpColorSimilarityDialog.h create mode 100644 kolourpaint/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp create mode 100644 kolourpaint/dialogs/kpDocumentSaveOptionsPreviewDialog.h create mode 100644 kolourpaint/doc/CMakeLists.txt create mode 100644 kolourpaint/doc/KolourPaint.png create mode 100644 kolourpaint/doc/brush_shapes.png create mode 100644 kolourpaint/doc/color_box.png create mode 100644 kolourpaint/doc/eraser_shapes.png create mode 100644 kolourpaint/doc/fcc_std_text.png create mode 100644 kolourpaint/doc/fcc_trans_text.png create mode 100644 kolourpaint/doc/fill_color_similarity.png create mode 100644 kolourpaint/doc/fill_style.png create mode 100644 kolourpaint/doc/image_balance.png create mode 100644 kolourpaint/doc/image_emboss.png create mode 100644 kolourpaint/doc/image_flatten.png create mode 100644 kolourpaint/doc/image_invert.png create mode 100644 kolourpaint/doc/image_reduce_colors.png create mode 100644 kolourpaint/doc/image_resize_scale.png create mode 100644 kolourpaint/doc/image_rotate.png create mode 100644 kolourpaint/doc/image_skew.png create mode 100644 kolourpaint/doc/image_soften_sharpen.png create mode 100644 kolourpaint/doc/index.docbook create mode 100644 kolourpaint/doc/line_width.png create mode 100644 kolourpaint/doc/lines_30_45_deg.png create mode 100644 kolourpaint/doc/lines_30_deg.png create mode 100644 kolourpaint/doc/lines_45_deg.png create mode 100644 kolourpaint/doc/rotate_image_30.png create mode 100644 kolourpaint/doc/rotate_selection_30.png create mode 100644 kolourpaint/doc/screenshot_acquiring.png create mode 100644 kolourpaint/doc/selections_opaque_transparent.png create mode 100644 kolourpaint/doc/spraycan_patterns.png create mode 100644 kolourpaint/doc/text_zoom_grid.png create mode 100644 kolourpaint/doc/tool_brush.png create mode 100644 kolourpaint/doc/tool_color_picker.png create mode 100644 kolourpaint/doc/tool_color_washer.png create mode 100644 kolourpaint/doc/tool_curve.png create mode 100644 kolourpaint/doc/tool_ellipse.png create mode 100644 kolourpaint/doc/tool_elliptical_selection.png create mode 100644 kolourpaint/doc/tool_eraser.png create mode 100644 kolourpaint/doc/tool_flood_fill.png create mode 100644 kolourpaint/doc/tool_free_form_selection.png create mode 100644 kolourpaint/doc/tool_line.png create mode 100644 kolourpaint/doc/tool_pen.png create mode 100644 kolourpaint/doc/tool_polygon.png create mode 100644 kolourpaint/doc/tool_polyline.png create mode 100644 kolourpaint/doc/tool_polystar.png create mode 100644 kolourpaint/doc/tool_rect_selection.png create mode 100644 kolourpaint/doc/tool_rectangle.png create mode 100644 kolourpaint/doc/tool_rectangles.png create mode 100644 kolourpaint/doc/tool_rounded_rectangle.png create mode 100644 kolourpaint/doc/tool_selections.png create mode 100644 kolourpaint/doc/tool_spraycan.png create mode 100644 kolourpaint/doc/tool_text.png create mode 100644 kolourpaint/doc/view_thumbnails.png create mode 100644 kolourpaint/document/kpDocument.cpp create mode 100644 kolourpaint/document/kpDocument.h create mode 100644 kolourpaint/document/kpDocumentPrivate.h create mode 100644 kolourpaint/document/kpDocumentSaveOptions.cpp create mode 100644 kolourpaint/document/kpDocumentSaveOptions.h create mode 100644 kolourpaint/document/kpDocument_Open.cpp create mode 100644 kolourpaint/document/kpDocument_Save.cpp create mode 100644 kolourpaint/document/kpDocument_Selection.cpp create mode 100644 kolourpaint/environments/commands/kpCommandEnvironment.cpp create mode 100644 kolourpaint/environments/commands/kpCommandEnvironment.h create mode 100644 kolourpaint/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.cpp create mode 100644 kolourpaint/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h create mode 100644 kolourpaint/environments/document/kpDocumentEnvironment.cpp create mode 100644 kolourpaint/environments/document/kpDocumentEnvironment.h create mode 100644 kolourpaint/environments/kpEnvironmentBase.cpp create mode 100644 kolourpaint/environments/kpEnvironmentBase.h create mode 100644 kolourpaint/environments/tools/kpToolEnvironment.cpp create mode 100644 kolourpaint/environments/tools/kpToolEnvironment.h create mode 100644 kolourpaint/environments/tools/selection/kpToolSelectionEnvironment.cpp create mode 100644 kolourpaint/environments/tools/selection/kpToolSelectionEnvironment.h create mode 100755 kolourpaint/gen_cmake_include_dirs create mode 100755 kolourpaint/gen_cmake_srcs create mode 100644 kolourpaint/generic/kpSetOverrideCursorSaver.cpp create mode 100644 kolourpaint/generic/kpSetOverrideCursorSaver.h create mode 100644 kolourpaint/generic/kpWidgetMapper.cpp create mode 100644 kolourpaint/generic/kpWidgetMapper.h create mode 100644 kolourpaint/generic/widgets/kpResizeSignallingLabel.cpp create mode 100644 kolourpaint/generic/widgets/kpResizeSignallingLabel.h create mode 100644 kolourpaint/generic/widgets/kpSubWindow.cpp create mode 100644 kolourpaint/generic/widgets/kpSubWindow.h create mode 100644 kolourpaint/imagelib/effects/kpEffectBalance.cpp create mode 100644 kolourpaint/imagelib/effects/kpEffectBalance.h create mode 100644 kolourpaint/imagelib/effects/kpEffectBlurSharpen.cpp create mode 100644 kolourpaint/imagelib/effects/kpEffectBlurSharpen.h create mode 100644 kolourpaint/imagelib/effects/kpEffectEmboss.cpp create mode 100644 kolourpaint/imagelib/effects/kpEffectEmboss.h create mode 100644 kolourpaint/imagelib/effects/kpEffectFlatten.cpp create mode 100644 kolourpaint/imagelib/effects/kpEffectFlatten.h create mode 100644 kolourpaint/imagelib/effects/kpEffectGrayscale.cpp create mode 100644 kolourpaint/imagelib/effects/kpEffectGrayscale.h create mode 100644 kolourpaint/imagelib/effects/kpEffectHSV.cpp create mode 100644 kolourpaint/imagelib/effects/kpEffectHSV.h create mode 100644 kolourpaint/imagelib/effects/kpEffectInvert.cpp create mode 100644 kolourpaint/imagelib/effects/kpEffectInvert.h create mode 100644 kolourpaint/imagelib/effects/kpEffectReduceColors.cpp create mode 100644 kolourpaint/imagelib/effects/kpEffectReduceColors.h create mode 100644 kolourpaint/imagelib/effects/kpEffectToneEnhance.cpp create mode 100644 kolourpaint/imagelib/effects/kpEffectToneEnhance.h create mode 100644 kolourpaint/imagelib/kpColor.cpp create mode 100644 kolourpaint/imagelib/kpColor.h create mode 100644 kolourpaint/imagelib/kpColor_Constants.cpp create mode 100644 kolourpaint/imagelib/kpDocumentMetaInfo.cpp create mode 100644 kolourpaint/imagelib/kpDocumentMetaInfo.h create mode 100644 kolourpaint/imagelib/kpFloodFill.cpp create mode 100644 kolourpaint/imagelib/kpFloodFill.h create mode 100644 kolourpaint/imagelib/kpImage.h create mode 100644 kolourpaint/imagelib/kpPainter.cpp create mode 100644 kolourpaint/imagelib/kpPainter.h create mode 100644 kolourpaint/imagelib/transforms/kpTransformAutoCrop.cpp create mode 100644 kolourpaint/imagelib/transforms/kpTransformAutoCrop.h create mode 100644 kolourpaint/imagelib/transforms/kpTransformCrop.cpp create mode 100644 kolourpaint/imagelib/transforms/kpTransformCrop.h create mode 100644 kolourpaint/imagelib/transforms/kpTransformCropPrivate.h create mode 100644 kolourpaint/imagelib/transforms/kpTransformCrop_ImageSelection.cpp create mode 100644 kolourpaint/imagelib/transforms/kpTransformCrop_TextSelection.cpp create mode 100644 kolourpaint/kolourpaint.appdata.xml create mode 100644 kolourpaint/kolourpaint.cpp create mode 100644 kolourpaint/kolourpaint.desktop create mode 100644 kolourpaint/kolourpaintui.rc create mode 100644 kolourpaint/kpDefs.h create mode 100644 kolourpaint/kpThumbnail.cpp create mode 100644 kolourpaint/kpThumbnail.h create mode 100644 kolourpaint/kpViewScrollableContainer.cpp create mode 100644 kolourpaint/kpViewScrollableContainer.h create mode 100644 kolourpaint/layers/selections/image/kpAbstractImageSelection.cpp create mode 100644 kolourpaint/layers/selections/image/kpAbstractImageSelection.h create mode 100644 kolourpaint/layers/selections/image/kpEllipticalImageSelection.cpp create mode 100644 kolourpaint/layers/selections/image/kpEllipticalImageSelection.h create mode 100644 kolourpaint/layers/selections/image/kpFreeFormImageSelection.cpp create mode 100644 kolourpaint/layers/selections/image/kpFreeFormImageSelection.h create mode 100644 kolourpaint/layers/selections/image/kpImageSelectionTransparency.cpp create mode 100644 kolourpaint/layers/selections/image/kpImageSelectionTransparency.h create mode 100644 kolourpaint/layers/selections/image/kpRectangularImageSelection.cpp create mode 100644 kolourpaint/layers/selections/image/kpRectangularImageSelection.h create mode 100644 kolourpaint/layers/selections/kpAbstractSelection.cpp create mode 100644 kolourpaint/layers/selections/kpAbstractSelection.h create mode 100644 kolourpaint/layers/selections/kpSelectionDrag.cpp create mode 100644 kolourpaint/layers/selections/kpSelectionDrag.h create mode 100644 kolourpaint/layers/selections/kpSelectionFactory.cpp create mode 100644 kolourpaint/layers/selections/kpSelectionFactory.h create mode 100644 kolourpaint/layers/selections/text/kpPreeditText.cpp create mode 100644 kolourpaint/layers/selections/text/kpPreeditText.h create mode 100644 kolourpaint/layers/selections/text/kpTextSelection.cpp create mode 100644 kolourpaint/layers/selections/text/kpTextSelection.h create mode 100644 kolourpaint/layers/selections/text/kpTextSelectionPrivate.h create mode 100644 kolourpaint/layers/selections/text/kpTextSelection_Cursor.cpp create mode 100644 kolourpaint/layers/selections/text/kpTextSelection_Paint.cpp create mode 100644 kolourpaint/layers/selections/text/kpTextStyle.cpp create mode 100644 kolourpaint/layers/selections/text/kpTextStyle.h create mode 100644 kolourpaint/layers/tempImage/kpTempImage.cpp create mode 100644 kolourpaint/layers/tempImage/kpTempImage.h create mode 100644 kolourpaint/lgpl/generic/kpColorCollection.cpp create mode 100644 kolourpaint/lgpl/generic/kpColorCollection.h create mode 100644 kolourpaint/lgpl/generic/kpUrlFormatter.cpp create mode 100644 kolourpaint/lgpl/generic/kpUrlFormatter.h create mode 100644 kolourpaint/lgpl/generic/widgets/kpColorCellsBase.cpp create mode 100644 kolourpaint/lgpl/generic/widgets/kpColorCellsBase.h create mode 100644 kolourpaint/lgpl/kolourpaint_lgpl_export.h create mode 100644 kolourpaint/mainWindow/kpMainWindow.cpp create mode 100644 kolourpaint/mainWindow/kpMainWindow.h create mode 100644 kolourpaint/mainWindow/kpMainWindowPrivate.h create mode 100644 kolourpaint/mainWindow/kpMainWindow_Colors.cpp create mode 100644 kolourpaint/mainWindow/kpMainWindow_Edit.cpp create mode 100644 kolourpaint/mainWindow/kpMainWindow_File.cpp create mode 100644 kolourpaint/mainWindow/kpMainWindow_Image.cpp create mode 100644 kolourpaint/mainWindow/kpMainWindow_Settings.cpp create mode 100644 kolourpaint/mainWindow/kpMainWindow_StatusBar.cpp create mode 100644 kolourpaint/mainWindow/kpMainWindow_Text.cpp create mode 100644 kolourpaint/mainWindow/kpMainWindow_Tools.cpp create mode 100644 kolourpaint/mainWindow/kpMainWindow_View.cpp create mode 100644 kolourpaint/mainWindow/kpMainWindow_View_Thumbnail.cpp create mode 100644 kolourpaint/mainWindow/kpMainWindow_View_Zoom.cpp create mode 100644 kolourpaint/patches/checkerboard-faster-render.diff create mode 100644 kolourpaint/patches/linear-sharpen-effect.diff create mode 100644 kolourpaint/pics/CMakeLists.txt create mode 100644 kolourpaint/pics/action/CMakeLists.txt create mode 100644 kolourpaint/pics/action/hi16-action-tool_brush.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_color_eraser.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_color_picker.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_curve.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_ellipse.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_elliptical_selection.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_eraser.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_flood_fill.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_free_form_selection.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_line.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_pen.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_polygon.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_polyline.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_rect_selection.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_rectangle.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_rounded_rectangle.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_spraycan.png create mode 100644 kolourpaint/pics/action/hi16-action-tool_text.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_brush.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_color_eraser.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_color_picker.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_curve.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_ellipse.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_elliptical_selection.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_eraser.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_flood_fill.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_free_form_selection.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_line.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_pen.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_polygon.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_polyline.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_rect_selection.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_rectangle.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_rounded_rectangle.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_spraycan.png create mode 100644 kolourpaint/pics/action/hi22-action-tool_text.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_brush.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_color_eraser.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_color_picker.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_curve.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_ellipse.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_elliptical_selection.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_eraser.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_flood_fill.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_free_form_selection.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_line.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_pen.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_polygon.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_polyline.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_rect_selection.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_rectangle.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_rounded_rectangle.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_spraycan.png create mode 100644 kolourpaint/pics/action/hi32-action-tool_text.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_brush.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_color_eraser.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_color_picker.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_curve.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_ellipse.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_elliptical_selection.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_eraser.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_flood_fill.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_free_form_selection.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_line.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_pen.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_polygon.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_polyline.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_rect_selection.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_rectangle.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_rounded_rectangle.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_spraycan.png create mode 100644 kolourpaint/pics/action/hi48-action-tool_text.png create mode 100644 kolourpaint/pics/action/hisc-action-tool_brush.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_color_eraser.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_color_picker.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_curve.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_ellipse.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_elliptical_selection.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_eraser.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_flood_fill.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_free_form_selection.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_line.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_pen.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_polygon.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_polyline.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_rect_selection.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_rectangle.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_rounded_rectangle.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_spraycan.svgz create mode 100644 kolourpaint/pics/action/hisc-action-tool_text.svgz create mode 100644 kolourpaint/pics/app/CMakeLists.txt create mode 100644 kolourpaint/pics/app/hi16-app-kolourpaint.png create mode 100644 kolourpaint/pics/app/hi22-app-kolourpaint.png create mode 100644 kolourpaint/pics/app/hi32-app-kolourpaint.png create mode 100644 kolourpaint/pics/app/hi48-app-kolourpaint.png create mode 100644 kolourpaint/pics/app/hisc-app-kolourpaint.svgz create mode 100644 kolourpaint/pics/custom/CMakeLists.txt create mode 100644 kolourpaint/pics/custom/color_transparent_26x26.png create mode 100644 kolourpaint/pics/custom/colorbutton_swap_16x16.png create mode 100644 kolourpaint/pics/custom/image_rotate_anticlockwise.png create mode 100644 kolourpaint/pics/custom/image_rotate_clockwise.png create mode 100644 kolourpaint/pics/custom/image_skew_horizontal.png create mode 100644 kolourpaint/pics/custom/image_skew_vertical.png create mode 100644 kolourpaint/pics/custom/option_opaque.png create mode 100644 kolourpaint/pics/custom/option_transparent.png create mode 100644 kolourpaint/pics/custom/resize.png create mode 100644 kolourpaint/pics/custom/scale.png create mode 100644 kolourpaint/pics/custom/smooth_scale.png create mode 100644 kolourpaint/pics/custom/tool_spraycan_17x17.png create mode 100644 kolourpaint/pics/custom/tool_spraycan_29x29.png create mode 100644 kolourpaint/pics/custom/tool_spraycan_9x9.png create mode 100644 kolourpaint/pixmapfx/kpPixmapFX.h create mode 100644 kolourpaint/pixmapfx/kpPixmapFX_DrawShapes.cpp create mode 100644 kolourpaint/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp create mode 100644 kolourpaint/pixmapfx/kpPixmapFX_Transforms.cpp create mode 100644 kolourpaint/stamp create mode 100644 kolourpaint/tests/45deg_line.png create mode 100644 kolourpaint/tests/4x4-transparent.png create mode 100644 kolourpaint/tests/5x5.png create mode 100644 kolourpaint/tests/depth1.bmp create mode 100644 kolourpaint/tests/dither.png create mode 100644 kolourpaint/tests/freeform-selection.png create mode 100644 kolourpaint/tests/kolourcircles.png create mode 100644 kolourpaint/tests/paste_in_new_window.png create mode 100644 kolourpaint/tests/paste_in_new_window.txt create mode 100644 kolourpaint/tests/rotate.png create mode 100644 kolourpaint/tests/selections.txt create mode 100644 kolourpaint/tests/small16x16.png create mode 100644 kolourpaint/tests/tool_fill_xlimit.png create mode 100644 kolourpaint/tests/transforms-rotate-me-90-clockwise.png create mode 100644 kolourpaint/tests/transforms.png create mode 100644 kolourpaint/tests/transparent.png create mode 100644 kolourpaint/tests/transparent_selection.png create mode 100644 kolourpaint/tools/flow/kpToolBrush.cpp create mode 100644 kolourpaint/tools/flow/kpToolBrush.h create mode 100644 kolourpaint/tools/flow/kpToolColorEraser.cpp create mode 100644 kolourpaint/tools/flow/kpToolColorEraser.h create mode 100644 kolourpaint/tools/flow/kpToolEraser.cpp create mode 100644 kolourpaint/tools/flow/kpToolEraser.h create mode 100644 kolourpaint/tools/flow/kpToolFlowBase.cpp create mode 100644 kolourpaint/tools/flow/kpToolFlowBase.h create mode 100644 kolourpaint/tools/flow/kpToolFlowPixmapBase.cpp create mode 100644 kolourpaint/tools/flow/kpToolFlowPixmapBase.h create mode 100644 kolourpaint/tools/flow/kpToolPen.cpp create mode 100644 kolourpaint/tools/flow/kpToolPen.h create mode 100644 kolourpaint/tools/flow/kpToolSpraycan.cpp create mode 100644 kolourpaint/tools/flow/kpToolSpraycan.h create mode 100644 kolourpaint/tools/kpTool.cpp create mode 100644 kolourpaint/tools/kpTool.h create mode 100644 kolourpaint/tools/kpToolAction.cpp create mode 100644 kolourpaint/tools/kpToolAction.h create mode 100644 kolourpaint/tools/kpToolColorPicker.cpp create mode 100644 kolourpaint/tools/kpToolColorPicker.h create mode 100644 kolourpaint/tools/kpToolFloodFill.cpp create mode 100644 kolourpaint/tools/kpToolFloodFill.h create mode 100644 kolourpaint/tools/kpToolPrivate.h create mode 100644 kolourpaint/tools/kpToolZoom.cpp create mode 100644 kolourpaint/tools/kpToolZoom.h create mode 100644 kolourpaint/tools/kpTool_Drawing.cpp create mode 100644 kolourpaint/tools/kpTool_KeyboardEvents.cpp create mode 100644 kolourpaint/tools/kpTool_MouseEvents.cpp create mode 100644 kolourpaint/tools/kpTool_OtherEvents.cpp create mode 100644 kolourpaint/tools/kpTool_UserNotifications.cpp create mode 100644 kolourpaint/tools/kpTool_Utilities.cpp create mode 100644 kolourpaint/tools/polygonal/kpToolCurve.cpp create mode 100644 kolourpaint/tools/polygonal/kpToolCurve.h create mode 100644 kolourpaint/tools/polygonal/kpToolLine.cpp create mode 100644 kolourpaint/tools/polygonal/kpToolLine.h create mode 100644 kolourpaint/tools/polygonal/kpToolPolygon.cpp create mode 100644 kolourpaint/tools/polygonal/kpToolPolygon.h create mode 100644 kolourpaint/tools/polygonal/kpToolPolygonalBase.cpp create mode 100644 kolourpaint/tools/polygonal/kpToolPolygonalBase.h create mode 100644 kolourpaint/tools/polygonal/kpToolPolyline.cpp create mode 100644 kolourpaint/tools/polygonal/kpToolPolyline.h create mode 100644 kolourpaint/tools/rectangular/kpToolEllipse.cpp create mode 100644 kolourpaint/tools/rectangular/kpToolEllipse.h create mode 100644 kolourpaint/tools/rectangular/kpToolRectangle.cpp create mode 100644 kolourpaint/tools/rectangular/kpToolRectangle.h create mode 100644 kolourpaint/tools/rectangular/kpToolRectangularBase.cpp create mode 100644 kolourpaint/tools/rectangular/kpToolRectangularBase.h create mode 100644 kolourpaint/tools/rectangular/kpToolRoundedRectangle.cpp create mode 100644 kolourpaint/tools/rectangular/kpToolRoundedRectangle.h create mode 100644 kolourpaint/tools/selection/image/kpAbstractImageSelectionTool.cpp create mode 100644 kolourpaint/tools/selection/image/kpAbstractImageSelectionTool.h create mode 100644 kolourpaint/tools/selection/image/kpAbstractImageSelectionTool_Transparency.cpp create mode 100644 kolourpaint/tools/selection/image/kpToolEllipticalSelection.cpp create mode 100644 kolourpaint/tools/selection/image/kpToolEllipticalSelection.h create mode 100644 kolourpaint/tools/selection/image/kpToolFreeFormSelection.cpp create mode 100644 kolourpaint/tools/selection/image/kpToolFreeFormSelection.h create mode 100644 kolourpaint/tools/selection/image/kpToolRectSelection.cpp create mode 100644 kolourpaint/tools/selection/image/kpToolRectSelection.h create mode 100644 kolourpaint/tools/selection/kpAbstractSelectionTool.cpp create mode 100644 kolourpaint/tools/selection/kpAbstractSelectionTool.h create mode 100644 kolourpaint/tools/selection/kpAbstractSelectionToolPrivate.h create mode 100644 kolourpaint/tools/selection/kpAbstractSelectionTool_Create.cpp create mode 100644 kolourpaint/tools/selection/kpAbstractSelectionTool_KeyboardEvents.cpp create mode 100644 kolourpaint/tools/selection/kpAbstractSelectionTool_Move.cpp create mode 100644 kolourpaint/tools/selection/kpAbstractSelectionTool_ResizeScale.cpp create mode 100644 kolourpaint/tools/selection/text/kpToolText.cpp create mode 100644 kolourpaint/tools/selection/text/kpToolText.h create mode 100644 kolourpaint/tools/selection/text/kpToolTextPrivate.h create mode 100644 kolourpaint/tools/selection/text/kpToolText_Commands.cpp create mode 100644 kolourpaint/tools/selection/text/kpToolText_Create.cpp create mode 100644 kolourpaint/tools/selection/text/kpToolText_CursorCalc.cpp create mode 100644 kolourpaint/tools/selection/text/kpToolText_InputMethodEvents.cpp create mode 100644 kolourpaint/tools/selection/text/kpToolText_KeyboardEvents.cpp create mode 100644 kolourpaint/tools/selection/text/kpToolText_KeyboardEvents_HandleArrowKeys.cpp create mode 100644 kolourpaint/tools/selection/text/kpToolText_KeyboardEvents_HandleTypingKeys.cpp create mode 100644 kolourpaint/tools/selection/text/kpToolText_Move.cpp create mode 100644 kolourpaint/tools/selection/text/kpToolText_ResizeScale.cpp create mode 100644 kolourpaint/tools/selection/text/kpToolText_SelectText.cpp create mode 100644 kolourpaint/tools/selection/text/kpToolText_TextStyle.cpp create mode 100644 kolourpaint/views/kpThumbnailView.cpp create mode 100644 kolourpaint/views/kpThumbnailView.h create mode 100644 kolourpaint/views/kpUnzoomedThumbnailView.cpp create mode 100644 kolourpaint/views/kpUnzoomedThumbnailView.h create mode 100644 kolourpaint/views/kpView.cpp create mode 100644 kolourpaint/views/kpView.h create mode 100644 kolourpaint/views/kpViewPrivate.h create mode 100644 kolourpaint/views/kpView_Events.cpp create mode 100644 kolourpaint/views/kpView_Paint.cpp create mode 100644 kolourpaint/views/kpView_Selections.cpp create mode 100644 kolourpaint/views/kpZoomedThumbnailView.cpp create mode 100644 kolourpaint/views/kpZoomedThumbnailView.h create mode 100644 kolourpaint/views/kpZoomedView.cpp create mode 100644 kolourpaint/views/kpZoomedView.h create mode 100644 kolourpaint/views/manager/kpViewManager.cpp create mode 100644 kolourpaint/views/manager/kpViewManager.h create mode 100644 kolourpaint/views/manager/kpViewManagerPrivate.h create mode 100644 kolourpaint/views/manager/kpViewManager_TextCursor.cpp create mode 100644 kolourpaint/views/manager/kpViewManager_ViewUpdates.cpp create mode 100644 kolourpaint/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp create mode 100644 kolourpaint/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.h create mode 100644 kolourpaint/widgets/colorSimilarity/kpColorSimilarityFrame.cpp create mode 100644 kolourpaint/widgets/colorSimilarity/kpColorSimilarityFrame.h create mode 100644 kolourpaint/widgets/colorSimilarity/kpColorSimilarityHolder.cpp create mode 100644 kolourpaint/widgets/colorSimilarity/kpColorSimilarityHolder.h create mode 100644 kolourpaint/widgets/colorSimilarity/kpColorSimilarityToolBarItem.cpp create mode 100644 kolourpaint/widgets/colorSimilarity/kpColorSimilarityToolBarItem.h create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectBalanceWidget.cpp create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectBalanceWidget.h create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectBlurSharpenWidget.cpp create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectBlurSharpenWidget.h create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectEmbossWidget.cpp create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectEmbossWidget.h create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectFlattenWidget.cpp create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectFlattenWidget.h create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectHSVWidget.cpp create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectHSVWidget.h create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectInvertWidget.cpp create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectInvertWidget.h create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectReduceColorsWidget.cpp create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectReduceColorsWidget.h create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectToneEnhanceWidget.cpp create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectToneEnhanceWidget.h create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectWidgetBase.cpp create mode 100644 kolourpaint/widgets/imagelib/effects/kpEffectWidgetBase.h create mode 100644 kolourpaint/widgets/kpColorCells.cpp create mode 100644 kolourpaint/widgets/kpColorCells.h create mode 100644 kolourpaint/widgets/kpColorPalette.cpp create mode 100644 kolourpaint/widgets/kpColorPalette.h create mode 100644 kolourpaint/widgets/kpDefaultColorCollection.cpp create mode 100644 kolourpaint/widgets/kpDefaultColorCollection.h create mode 100644 kolourpaint/widgets/kpDocumentSaveOptionsWidget.cpp create mode 100644 kolourpaint/widgets/kpDocumentSaveOptionsWidget.h create mode 100644 kolourpaint/widgets/kpDualColorButton.cpp create mode 100644 kolourpaint/widgets/kpDualColorButton.h create mode 100644 kolourpaint/widgets/kpPrintDialogPage.cpp create mode 100644 kolourpaint/widgets/kpPrintDialogPage.h create mode 100644 kolourpaint/widgets/kpTransparentColorCell.cpp create mode 100644 kolourpaint/widgets/kpTransparentColorCell.h create mode 100644 kolourpaint/widgets/toolbars/kpColorToolBar.cpp create mode 100644 kolourpaint/widgets/toolbars/kpColorToolBar.h create mode 100644 kolourpaint/widgets/toolbars/kpToolToolBar.cpp create mode 100644 kolourpaint/widgets/toolbars/kpToolToolBar.h create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetBase.cpp create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetBase.h create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetBrush.cpp create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetBrush.h create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetEraserSize.cpp create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetEraserSize.h create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetFillStyle.cpp create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetFillStyle.h create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetLineWidth.cpp create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetLineWidth.h create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.cpp create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetSpraycanSize.cpp create mode 100644 kolourpaint/widgets/toolbars/options/kpToolWidgetSpraycanSize.h create mode 100644 ksnapshot/CMakeLists.txt create mode 100644 ksnapshot/COPYING create mode 100644 ksnapshot/COPYING.DOC create mode 100644 ksnapshot/COPYING.LIB create mode 100644 ksnapshot/Messages.sh create mode 100644 ksnapshot/config-ksnapshot.h.cmake create mode 100644 ksnapshot/doc/CMakeLists.txt create mode 100644 ksnapshot/doc/index.docbook create mode 100644 ksnapshot/doc/preview.png create mode 100644 ksnapshot/doc/send-to-menu.png create mode 100644 ksnapshot/doc/window.png create mode 100644 ksnapshot/expblur.cpp create mode 100644 ksnapshot/freeregiongrabber.cpp create mode 100644 ksnapshot/freeregiongrabber.h create mode 100644 ksnapshot/hi16-app-ksnapshot.png create mode 100644 ksnapshot/hi22-app-ksnapshot.png create mode 100644 ksnapshot/hi32-app-ksnapshot.png create mode 100644 ksnapshot/hi48-app-ksnapshot.png create mode 100644 ksnapshot/hisc-app-ksnapshot.svgz create mode 100644 ksnapshot/kbackgroundsnapshot.cpp create mode 100644 ksnapshot/kbackgroundsnapshot.h create mode 100644 ksnapshot/kipiimagecollectionselector.cpp create mode 100644 ksnapshot/kipiimagecollectionselector.h create mode 100644 ksnapshot/kipiinterface.cpp create mode 100644 ksnapshot/kipiinterface.h create mode 100644 ksnapshot/ksnapshot.cpp create mode 100755 ksnapshot/ksnapshot.desktop create mode 100644 ksnapshot/ksnapshot.h create mode 100644 ksnapshot/ksnapshot_options.h create mode 100644 ksnapshot/ksnapshotimagecollectionshared.cpp create mode 100644 ksnapshot/ksnapshotimagecollectionshared.h create mode 100644 ksnapshot/ksnapshotinfoshared.cpp create mode 100644 ksnapshot/ksnapshotinfoshared.h create mode 100644 ksnapshot/ksnapshotobject.cpp create mode 100644 ksnapshot/ksnapshotobject.h create mode 100644 ksnapshot/ksnapshotpreview.cpp create mode 100644 ksnapshot/ksnapshotpreview.h create mode 100644 ksnapshot/ksnapshotwidget.ui create mode 100644 ksnapshot/main.cpp create mode 100644 ksnapshot/org.kde.ksnapshot.xml create mode 100644 ksnapshot/regiongrabber.cpp create mode 100644 ksnapshot/regiongrabber.h create mode 100644 ksnapshot/snapshottimer.cpp create mode 100644 ksnapshot/snapshottimer.h create mode 100644 ksnapshot/windowgrabber.cpp create mode 100644 ksnapshot/windowgrabber.h create mode 100644 ksystemlog/.cproject create mode 100644 ksystemlog/.project create mode 100644 ksystemlog/.settings/org.eclipse.ltk.core.refactoring.prefs create mode 100644 ksystemlog/CMakeLists.txt create mode 100644 ksystemlog/COPYING create mode 100644 ksystemlog/Changelog create mode 100644 ksystemlog/Mainpage.dox create mode 100755 ksystemlog/Messages.sh create mode 100755 ksystemlog/build-package.sh create mode 100644 ksystemlog/doc/CMakeLists.txt create mode 100644 ksystemlog/doc/filter-process.png create mode 100644 ksystemlog/doc/first-opening.png create mode 100644 ksystemlog/doc/index.docbook create mode 100644 ksystemlog/doc/main-screen.png create mode 100644 ksystemlog/src/CMakeLists.txt create mode 100644 ksystemlog/src/config/CMakeLists.txt create mode 100644 ksystemlog/src/config/dummyConfig.cpp create mode 100644 ksystemlog/src/config/ksystemlog.kcfg create mode 100644 ksystemlog/src/config/ksystemlogConfig.kcfgc create mode 100644 ksystemlog/src/configurationDialog.cpp create mode 100644 ksystemlog/src/configurationDialog.h create mode 100644 ksystemlog/src/detailDialog.cpp create mode 100644 ksystemlog/src/detailDialog.h create mode 100644 ksystemlog/src/detailDialogBase.ui create mode 100644 ksystemlog/src/generalConfigurationWidget.cpp create mode 100644 ksystemlog/src/generalConfigurationWidget.h create mode 100644 ksystemlog/src/generalConfigurationWidgetBase.ui create mode 100755 ksystemlog/src/ksystemlog.desktop create mode 100644 ksystemlog/src/ksystemlog.lsm create mode 100644 ksystemlog/src/ksystemlogui.rc create mode 100644 ksystemlog/src/lib/CMakeLists.txt create mode 100644 ksystemlog/src/lib/analyzer.cpp create mode 100644 ksystemlog/src/lib/analyzer.h create mode 100644 ksystemlog/src/lib/defaults.h create mode 100644 ksystemlog/src/lib/globals.cpp create mode 100644 ksystemlog/src/lib/globals.h create mode 100644 ksystemlog/src/lib/kioLogFileReader.cpp create mode 100644 ksystemlog/src/lib/kioLogFileReader.h create mode 100644 ksystemlog/src/lib/levelPrintPage.cpp create mode 100644 ksystemlog/src/lib/levelPrintPage.h create mode 100644 ksystemlog/src/lib/loadingBar.cpp create mode 100644 ksystemlog/src/lib/loadingBar.h create mode 100644 ksystemlog/src/lib/localLogFileReader.cpp create mode 100644 ksystemlog/src/lib/localLogFileReader.h create mode 100644 ksystemlog/src/lib/logFile.cpp create mode 100644 ksystemlog/src/lib/logFile.h create mode 100644 ksystemlog/src/lib/logFileReader.cpp create mode 100644 ksystemlog/src/lib/logFileReader.h create mode 100644 ksystemlog/src/lib/logFileReaderPrivate.h create mode 100644 ksystemlog/src/lib/logLevel.cpp create mode 100644 ksystemlog/src/lib/logLevel.h create mode 100644 ksystemlog/src/lib/logLine.cpp create mode 100644 ksystemlog/src/lib/logLine.h create mode 100644 ksystemlog/src/lib/logManager.cpp create mode 100644 ksystemlog/src/lib/logManager.h create mode 100644 ksystemlog/src/lib/logMode.cpp create mode 100644 ksystemlog/src/lib/logMode.h create mode 100644 ksystemlog/src/lib/logModeAction.cpp create mode 100644 ksystemlog/src/lib/logModeAction.h create mode 100644 ksystemlog/src/lib/logModeConfiguration.cpp create mode 100644 ksystemlog/src/lib/logModeConfiguration.h create mode 100644 ksystemlog/src/lib/logModeConfigurationWidget.cpp create mode 100644 ksystemlog/src/lib/logModeConfigurationWidget.h create mode 100644 ksystemlog/src/lib/logModeFactory.cpp create mode 100644 ksystemlog/src/lib/logModeFactory.h create mode 100644 ksystemlog/src/lib/logModeItemBuilder.cpp create mode 100644 ksystemlog/src/lib/logModeItemBuilder.h create mode 100644 ksystemlog/src/lib/logViewColumn.cpp create mode 100644 ksystemlog/src/lib/logViewColumn.h create mode 100644 ksystemlog/src/lib/logViewColumns.cpp create mode 100644 ksystemlog/src/lib/logViewColumns.h create mode 100644 ksystemlog/src/lib/logViewExport.cpp create mode 100644 ksystemlog/src/lib/logViewExport.h create mode 100644 ksystemlog/src/lib/logViewFilterWidget.cpp create mode 100644 ksystemlog/src/lib/logViewFilterWidget.h create mode 100644 ksystemlog/src/lib/logViewModel.cpp create mode 100644 ksystemlog/src/lib/logViewModel.h create mode 100644 ksystemlog/src/lib/logViewSearchWidget.cpp create mode 100644 ksystemlog/src/lib/logViewSearchWidget.h create mode 100644 ksystemlog/src/lib/logViewSearchWidgetBase.ui create mode 100644 ksystemlog/src/lib/logViewWidget.cpp create mode 100644 ksystemlog/src/lib/logViewWidget.h create mode 100644 ksystemlog/src/lib/logViewWidgetItem.cpp create mode 100644 ksystemlog/src/lib/logViewWidgetItem.h create mode 100644 ksystemlog/src/lib/logging.h create mode 100644 ksystemlog/src/lib/multipleActions.cpp create mode 100644 ksystemlog/src/lib/multipleActions.h create mode 100644 ksystemlog/src/lib/processOutputLogFileReader.cpp create mode 100644 ksystemlog/src/lib/processOutputLogFileReader.h create mode 100644 ksystemlog/src/lib/simpleAction.cpp create mode 100644 ksystemlog/src/lib/simpleAction.h create mode 100644 ksystemlog/src/lib/view.cpp create mode 100644 ksystemlog/src/lib/view.h create mode 100644 ksystemlog/src/logModePluginsLoader.cpp create mode 100644 ksystemlog/src/logModePluginsLoader.h create mode 100644 ksystemlog/src/loggerDialog.cpp create mode 100644 ksystemlog/src/loggerDialog.h create mode 100644 ksystemlog/src/loggerDialogBase.ui create mode 100644 ksystemlog/src/main.cpp create mode 100644 ksystemlog/src/mainWindow.cpp create mode 100644 ksystemlog/src/mainWindow.h create mode 100644 ksystemlog/src/modes/acpid/CMakeLists.txt create mode 100644 ksystemlog/src/modes/acpid/acpidAnalyzer.cpp create mode 100644 ksystemlog/src/modes/acpid/acpidAnalyzer.h create mode 100644 ksystemlog/src/modes/acpid/acpidConfiguration.cpp create mode 100644 ksystemlog/src/modes/acpid/acpidConfiguration.h create mode 100644 ksystemlog/src/modes/acpid/acpidConfigurationWidget.cpp create mode 100644 ksystemlog/src/modes/acpid/acpidConfigurationWidget.h create mode 100644 ksystemlog/src/modes/acpid/acpidFactory.cpp create mode 100644 ksystemlog/src/modes/acpid/acpidFactory.h create mode 100644 ksystemlog/src/modes/acpid/acpidItemBuilder.cpp create mode 100644 ksystemlog/src/modes/acpid/acpidItemBuilder.h create mode 100644 ksystemlog/src/modes/acpid/acpidLogMode.cpp create mode 100644 ksystemlog/src/modes/acpid/acpidLogMode.h create mode 100644 ksystemlog/src/modes/apache/CMakeLists.txt create mode 100644 ksystemlog/src/modes/apache/apacheAccessAnalyzer.cpp create mode 100644 ksystemlog/src/modes/apache/apacheAccessAnalyzer.h create mode 100644 ksystemlog/src/modes/apache/apacheAccessItemBuilder.cpp create mode 100644 ksystemlog/src/modes/apache/apacheAccessItemBuilder.h create mode 100644 ksystemlog/src/modes/apache/apacheAccessLogMode.cpp create mode 100644 ksystemlog/src/modes/apache/apacheAccessLogMode.h create mode 100644 ksystemlog/src/modes/apache/apacheAnalyzer.cpp create mode 100644 ksystemlog/src/modes/apache/apacheAnalyzer.h create mode 100644 ksystemlog/src/modes/apache/apacheConfiguration.cpp create mode 100644 ksystemlog/src/modes/apache/apacheConfiguration.h create mode 100644 ksystemlog/src/modes/apache/apacheConfigurationWidget.cpp create mode 100644 ksystemlog/src/modes/apache/apacheConfigurationWidget.h create mode 100644 ksystemlog/src/modes/apache/apacheFactory.cpp create mode 100644 ksystemlog/src/modes/apache/apacheFactory.h create mode 100644 ksystemlog/src/modes/apache/apacheItemBuilder.cpp create mode 100644 ksystemlog/src/modes/apache/apacheItemBuilder.h create mode 100644 ksystemlog/src/modes/apache/apacheLogMode.cpp create mode 100644 ksystemlog/src/modes/apache/apacheLogMode.h create mode 100644 ksystemlog/src/modes/authentication/CMakeLists.txt create mode 100644 ksystemlog/src/modes/authentication/authenticationAnalyzer.cpp create mode 100644 ksystemlog/src/modes/authentication/authenticationAnalyzer.h create mode 100644 ksystemlog/src/modes/authentication/authenticationConfiguration.cpp create mode 100644 ksystemlog/src/modes/authentication/authenticationConfiguration.h create mode 100644 ksystemlog/src/modes/authentication/authenticationConfigurationWidget.cpp create mode 100644 ksystemlog/src/modes/authentication/authenticationConfigurationWidget.h create mode 100644 ksystemlog/src/modes/authentication/authenticationFactory.cpp create mode 100644 ksystemlog/src/modes/authentication/authenticationFactory.h create mode 100644 ksystemlog/src/modes/authentication/authenticationLogMode.cpp create mode 100644 ksystemlog/src/modes/authentication/authenticationLogMode.h create mode 100644 ksystemlog/src/modes/base/CMakeLists.txt create mode 100644 ksystemlog/src/modes/base/fileList.cpp create mode 100644 ksystemlog/src/modes/base/fileList.h create mode 100644 ksystemlog/src/modes/base/fileListBase.ui create mode 100644 ksystemlog/src/modes/base/fileListHelper.cpp create mode 100644 ksystemlog/src/modes/base/fileListHelper.h create mode 100644 ksystemlog/src/modes/base/genericConfiguration.cpp create mode 100644 ksystemlog/src/modes/base/genericConfiguration.h create mode 100644 ksystemlog/src/modes/base/logLevelFileList.cpp create mode 100644 ksystemlog/src/modes/base/logLevelFileList.h create mode 100644 ksystemlog/src/modes/base/logLevelSelectionDialog.cpp create mode 100644 ksystemlog/src/modes/base/logLevelSelectionDialog.h create mode 100644 ksystemlog/src/modes/base/logLevelSelectionDialogBase.ui create mode 100644 ksystemlog/src/modes/base/multipleFileList.cpp create mode 100644 ksystemlog/src/modes/base/multipleFileList.h create mode 100644 ksystemlog/src/modes/base/multipleFileListBase.ui create mode 100644 ksystemlog/src/modes/base/parsingHelper.cpp create mode 100644 ksystemlog/src/modes/base/parsingHelper.h create mode 100644 ksystemlog/src/modes/base/syslogAnalyzer.cpp create mode 100644 ksystemlog/src/modes/base/syslogAnalyzer.h create mode 100644 ksystemlog/src/modes/cron/CMakeLists.txt create mode 100644 ksystemlog/src/modes/cron/cronAnalyzer.cpp create mode 100644 ksystemlog/src/modes/cron/cronAnalyzer.h create mode 100644 ksystemlog/src/modes/cron/cronConfiguration.cpp create mode 100644 ksystemlog/src/modes/cron/cronConfiguration.h create mode 100644 ksystemlog/src/modes/cron/cronConfigurationWidget.cpp create mode 100644 ksystemlog/src/modes/cron/cronConfigurationWidget.h create mode 100644 ksystemlog/src/modes/cron/cronFactory.cpp create mode 100644 ksystemlog/src/modes/cron/cronFactory.h create mode 100644 ksystemlog/src/modes/cron/cronItemBuilder.cpp create mode 100644 ksystemlog/src/modes/cron/cronItemBuilder.h create mode 100644 ksystemlog/src/modes/cron/cronLogMode.cpp create mode 100644 ksystemlog/src/modes/cron/cronLogMode.h create mode 100644 ksystemlog/src/modes/cups/CMakeLists.txt create mode 100644 ksystemlog/src/modes/cups/cupsAccessAnalyzer.cpp create mode 100644 ksystemlog/src/modes/cups/cupsAccessAnalyzer.h create mode 100644 ksystemlog/src/modes/cups/cupsAccessItemBuilder.cpp create mode 100644 ksystemlog/src/modes/cups/cupsAccessItemBuilder.h create mode 100644 ksystemlog/src/modes/cups/cupsAccessLogMode.cpp create mode 100644 ksystemlog/src/modes/cups/cupsAccessLogMode.h create mode 100644 ksystemlog/src/modes/cups/cupsAnalyzer.cpp create mode 100644 ksystemlog/src/modes/cups/cupsAnalyzer.h create mode 100644 ksystemlog/src/modes/cups/cupsConfiguration.cpp create mode 100644 ksystemlog/src/modes/cups/cupsConfiguration.h create mode 100644 ksystemlog/src/modes/cups/cupsConfigurationWidget.cpp create mode 100644 ksystemlog/src/modes/cups/cupsConfigurationWidget.h create mode 100644 ksystemlog/src/modes/cups/cupsFactory.cpp create mode 100644 ksystemlog/src/modes/cups/cupsFactory.h create mode 100644 ksystemlog/src/modes/cups/cupsItemBuilder.cpp create mode 100644 ksystemlog/src/modes/cups/cupsItemBuilder.h create mode 100644 ksystemlog/src/modes/cups/cupsLogMode.cpp create mode 100644 ksystemlog/src/modes/cups/cupsLogMode.h create mode 100644 ksystemlog/src/modes/cups/cupsPageAnalyzer.cpp create mode 100644 ksystemlog/src/modes/cups/cupsPageAnalyzer.h create mode 100644 ksystemlog/src/modes/cups/cupsPageItemBuilder.cpp create mode 100644 ksystemlog/src/modes/cups/cupsPageItemBuilder.h create mode 100644 ksystemlog/src/modes/cups/cupsPageLogMode.cpp create mode 100644 ksystemlog/src/modes/cups/cupsPageLogMode.h create mode 100644 ksystemlog/src/modes/cups/cupsPdfAnalyzer.cpp create mode 100644 ksystemlog/src/modes/cups/cupsPdfAnalyzer.h create mode 100644 ksystemlog/src/modes/cups/cupsPdfItemBuilder.cpp create mode 100644 ksystemlog/src/modes/cups/cupsPdfItemBuilder.h create mode 100644 ksystemlog/src/modes/cups/cupsPdfLogMode.cpp create mode 100644 ksystemlog/src/modes/cups/cupsPdfLogMode.h create mode 100644 ksystemlog/src/modes/daemon/CMakeLists.txt create mode 100644 ksystemlog/src/modes/daemon/daemonConfiguration.cpp create mode 100644 ksystemlog/src/modes/daemon/daemonConfiguration.h create mode 100644 ksystemlog/src/modes/daemon/daemonConfigurationWidget.cpp create mode 100644 ksystemlog/src/modes/daemon/daemonConfigurationWidget.h create mode 100644 ksystemlog/src/modes/daemon/daemonFactory.cpp create mode 100644 ksystemlog/src/modes/daemon/daemonFactory.h create mode 100644 ksystemlog/src/modes/daemon/daemonLogMode.cpp create mode 100644 ksystemlog/src/modes/daemon/daemonLogMode.h create mode 100644 ksystemlog/src/modes/kernel/CMakeLists.txt create mode 100644 ksystemlog/src/modes/kernel/kernelAnalyzer.cpp create mode 100644 ksystemlog/src/modes/kernel/kernelAnalyzer.h create mode 100644 ksystemlog/src/modes/kernel/kernelFactory.cpp create mode 100644 ksystemlog/src/modes/kernel/kernelFactory.h create mode 100644 ksystemlog/src/modes/kernel/kernelItemBuilder.cpp create mode 100644 ksystemlog/src/modes/kernel/kernelItemBuilder.h create mode 100644 ksystemlog/src/modes/kernel/kernelLogMode.cpp create mode 100644 ksystemlog/src/modes/kernel/kernelLogMode.h create mode 100644 ksystemlog/src/modes/open/CMakeLists.txt create mode 100644 ksystemlog/src/modes/open/openAnalyzer.cpp create mode 100644 ksystemlog/src/modes/open/openAnalyzer.h create mode 100644 ksystemlog/src/modes/open/openFactory.cpp create mode 100644 ksystemlog/src/modes/open/openFactory.h create mode 100644 ksystemlog/src/modes/open/openLogMode.cpp create mode 100644 ksystemlog/src/modes/open/openLogMode.h create mode 100644 ksystemlog/src/modes/postfix/CMakeLists.txt create mode 100644 ksystemlog/src/modes/postfix/postfixAnalyzer.cpp create mode 100644 ksystemlog/src/modes/postfix/postfixAnalyzer.h create mode 100644 ksystemlog/src/modes/postfix/postfixConfiguration.cpp create mode 100644 ksystemlog/src/modes/postfix/postfixConfiguration.h create mode 100644 ksystemlog/src/modes/postfix/postfixConfigurationWidget.cpp create mode 100644 ksystemlog/src/modes/postfix/postfixConfigurationWidget.h create mode 100644 ksystemlog/src/modes/postfix/postfixFactory.cpp create mode 100644 ksystemlog/src/modes/postfix/postfixFactory.h create mode 100644 ksystemlog/src/modes/postfix/postfixLogMode.cpp create mode 100644 ksystemlog/src/modes/postfix/postfixLogMode.h create mode 100644 ksystemlog/src/modes/samba/CMakeLists.txt create mode 100644 ksystemlog/src/modes/samba/netbiosLogMode.cpp create mode 100644 ksystemlog/src/modes/samba/netbiosLogMode.h create mode 100644 ksystemlog/src/modes/samba/sambaAccessLogMode.cpp create mode 100644 ksystemlog/src/modes/samba/sambaAccessLogMode.h create mode 100644 ksystemlog/src/modes/samba/sambaAnalyzer.cpp create mode 100644 ksystemlog/src/modes/samba/sambaAnalyzer.h create mode 100644 ksystemlog/src/modes/samba/sambaConfiguration.cpp create mode 100644 ksystemlog/src/modes/samba/sambaConfiguration.h create mode 100644 ksystemlog/src/modes/samba/sambaConfigurationWidget.cpp create mode 100644 ksystemlog/src/modes/samba/sambaConfigurationWidget.h create mode 100644 ksystemlog/src/modes/samba/sambaFactory.cpp create mode 100644 ksystemlog/src/modes/samba/sambaFactory.h create mode 100644 ksystemlog/src/modes/samba/sambaItemBuilder.cpp create mode 100644 ksystemlog/src/modes/samba/sambaItemBuilder.h create mode 100644 ksystemlog/src/modes/samba/sambaLogMode.cpp create mode 100644 ksystemlog/src/modes/samba/sambaLogMode.h create mode 100644 ksystemlog/src/modes/system/CMakeLists.txt create mode 100644 ksystemlog/src/modes/system/systemAnalyzer.cpp create mode 100644 ksystemlog/src/modes/system/systemAnalyzer.h create mode 100644 ksystemlog/src/modes/system/systemConfiguration.cpp create mode 100644 ksystemlog/src/modes/system/systemConfiguration.h create mode 100644 ksystemlog/src/modes/system/systemConfigurationWidget.cpp create mode 100644 ksystemlog/src/modes/system/systemConfigurationWidget.h create mode 100644 ksystemlog/src/modes/system/systemFactory.cpp create mode 100644 ksystemlog/src/modes/system/systemFactory.h create mode 100644 ksystemlog/src/modes/system/systemLogMode.cpp create mode 100644 ksystemlog/src/modes/system/systemLogMode.h create mode 100644 ksystemlog/src/modes/xorg/CMakeLists.txt create mode 100644 ksystemlog/src/modes/xorg/xorgAnalyzer.cpp create mode 100644 ksystemlog/src/modes/xorg/xorgAnalyzer.h create mode 100644 ksystemlog/src/modes/xorg/xorgConfiguration.cpp create mode 100644 ksystemlog/src/modes/xorg/xorgConfiguration.h create mode 100644 ksystemlog/src/modes/xorg/xorgConfigurationWidget.cpp create mode 100644 ksystemlog/src/modes/xorg/xorgConfigurationWidget.h create mode 100644 ksystemlog/src/modes/xorg/xorgFactory.cpp create mode 100644 ksystemlog/src/modes/xorg/xorgFactory.h create mode 100644 ksystemlog/src/modes/xorg/xorgItemBuilder.cpp create mode 100644 ksystemlog/src/modes/xorg/xorgItemBuilder.h create mode 100644 ksystemlog/src/modes/xorg/xorgLogMode.cpp create mode 100644 ksystemlog/src/modes/xorg/xorgLogMode.h create mode 100644 ksystemlog/src/modes/xsession/CMakeLists.txt create mode 100644 ksystemlog/src/modes/xsession/xsessionAnalyzer.cpp create mode 100644 ksystemlog/src/modes/xsession/xsessionAnalyzer.h create mode 100644 ksystemlog/src/modes/xsession/xsessionConfiguration.cpp create mode 100644 ksystemlog/src/modes/xsession/xsessionConfiguration.h create mode 100644 ksystemlog/src/modes/xsession/xsessionConfigurationWidget.cpp create mode 100644 ksystemlog/src/modes/xsession/xsessionConfigurationWidget.h create mode 100644 ksystemlog/src/modes/xsession/xsessionConfigurationWidgetBase.ui create mode 100644 ksystemlog/src/modes/xsession/xsessionFactory.cpp create mode 100644 ksystemlog/src/modes/xsession/xsessionFactory.h create mode 100644 ksystemlog/src/modes/xsession/xsessionItemBuilder.cpp create mode 100644 ksystemlog/src/modes/xsession/xsessionItemBuilder.h create mode 100644 ksystemlog/src/modes/xsession/xsessionLogMode.cpp create mode 100644 ksystemlog/src/modes/xsession/xsessionLogMode.h create mode 100644 ksystemlog/src/statusBar.cpp create mode 100644 ksystemlog/src/statusBar.h create mode 100644 ksystemlog/src/tabLogManager.cpp create mode 100644 ksystemlog/src/tabLogManager.h create mode 100644 ksystemlog/src/tabLogViewsWidget.cpp create mode 100644 ksystemlog/src/tabLogViewsWidget.h create mode 100644 ksystemlog/tests/CMakeLists.txt create mode 100644 ksystemlog/tests/findIncompatibleKioTest.cpp create mode 100644 ksystemlog/tests/kernelAnalyzerTest.cpp create mode 100644 ksystemlog/tests/kioLogFileReaderTest.cpp create mode 100644 ksystemlog/tests/logModeFactoryTest.cpp create mode 100644 ksystemlog/tests/systemAnalyzerTest.cpp create mode 100644 ksystemlog/tests/testFiles/apache/access.log create mode 100644 ksystemlog/tests/testFiles/apache/error.log create mode 100644 ksystemlog/tests/testFiles/cups/access_log create mode 100644 ksystemlog/tests/testFiles/cups/cups-pdf_log create mode 100644 ksystemlog/tests/testFiles/cups/error_log create mode 100644 ksystemlog/tests/testFiles/cups/page_log create mode 100644 ksystemlog/tests/testFiles/default/one-line.log create mode 100644 ksystemlog/tests/testFiles/default/two-lines.log create mode 100644 ksystemlog/tests/testFiles/kernel/suse.dmesg create mode 100644 ksystemlog/tests/testFiles/kernel/ubuntu.dmesg create mode 100644 ksystemlog/tests/testFiles/logFileReader/file.txt create mode 100644 ksystemlog/tests/testFiles/mysql/mysql-slow.log create mode 100644 ksystemlog/tests/testFiles/mysql/mysql.log create mode 100644 ksystemlog/tests/testFiles/samba/log.hostname1 create mode 100644 ksystemlog/tests/testFiles/samba/log.ip2 create mode 100644 ksystemlog/tests/testFiles/samba/log.nmbd create mode 100644 ksystemlog/tests/testFiles/samba/log.smbd create mode 100644 ksystemlog/tests/testFiles/system/delete-process-identifier.log create mode 100644 ksystemlog/tests/testFiles/system/duplicate-lines.log create mode 100644 ksystemlog/tests/testFiles/system/max-lines.log create mode 100644 ksystemlog/tests/testFiles/system/strange-lines.log create mode 100644 ksystemlog/tests/testFiles/system/system.log create mode 100644 ksystemlog/tests/testFiles/user/user.log create mode 100644 ksystemlog/tests/testFiles/xsession/xsession-error create mode 100644 ksystemlog/tests/testResources.qrc create mode 100644 ksystemlog/tests/testUtil.cpp create mode 100644 ksystemlog/tests/testUtil.h create mode 100644 libbluedevil/CMakeLists.txt create mode 100644 libbluedevil/HACKING create mode 100644 libbluedevil/bluedevil/CMakeLists.txt create mode 100644 libbluedevil/bluedevil/bluedevil.h create mode 100644 libbluedevil/bluedevil/bluedevil.pc.in create mode 100644 libbluedevil/bluedevil/bluedevil_export.h create mode 100644 libbluedevil/bluedevil/bluedeviladapter.cpp create mode 100644 libbluedevil/bluedevil/bluedeviladapter.h create mode 100644 libbluedevil/bluedevil/bluedevildbustypes.h create mode 100644 libbluedevil/bluedevil/bluedevildevice.cpp create mode 100644 libbluedevil/bluedevil/bluedevildevice.h create mode 100644 libbluedevil/bluedevil/bluedevilmanager.cpp create mode 100644 libbluedevil/bluedevil/bluedevilmanager.h create mode 100644 libbluedevil/bluedevil/bluedevilmanager_p.cpp create mode 100644 libbluedevil/bluedevil/bluedevilmanager_p.h create mode 100644 libbluedevil/bluedevil/bluedevilutils.cpp create mode 100644 libbluedevil/bluedevil/bluedevilutils.h create mode 100644 libbluedevil/bluedevil/bluez/org.bluez.Adapter1.xml create mode 100644 libbluedevil/bluedevil/bluez/org.bluez.AgentManager1.xml create mode 100644 libbluedevil/bluedevil/bluez/org.bluez.Device1.xml create mode 100644 libbluedevil/bluedevil/bluez/org.freedesktop.DBus.ObjectManager.xml create mode 100644 libbluedevil/bluedevil/bluez/org.freedesktop.DBus.Properties.xml create mode 100644 libbluedevil/bluedevil/test/CMakeLists.txt create mode 100644 libbluedevil/bluedevil/test/adaptertest.cpp create mode 100644 libbluedevil/bluedevil/test/adaptertest.h create mode 100644 libbluedevil/bluedevil/test/bluedeviltest.cpp create mode 100644 libbluedevil/bluedevil/test/bluedeviltest.h create mode 100644 libbluedevil/cmake/modules/FindQt4.cmake create mode 100644 libbluedevil/cmake/modules/MacroPushRequiredVars.cmake create mode 100644 libbluedevil/cmake/modules/Qt4ConfigDependentSettings.cmake create mode 100644 libbluedevil/cmake/modules/Qt4Macros.cmake create mode 100644 libbluedevil/qt4.tag create mode 100644 okular/.krazy create mode 100644 okular/AUTHORS create mode 100644 okular/CMakeLists.txt create mode 100644 okular/COPYING create mode 100644 okular/COPYING.DOC create mode 100644 okular/COPYING.LIB create mode 100644 okular/Mainpage.dox create mode 100644 okular/Messages.sh create mode 100644 okular/OkularConfig.cmake create mode 100644 okular/OkularConfigureChecks.cmake create mode 100644 okular/README.internals.png create mode 100644 okular/TODO create mode 100644 okular/VERSION create mode 100644 okular/aboutdata.h create mode 100644 okular/active/CMakeLists.txt create mode 100644 okular/active/app/CMakeLists.txt create mode 100644 okular/active/app/Messages.sh create mode 100755 okular/active/app/active-documentviewer.desktop create mode 100644 okular/active/app/package/contents/ui/Bookmarks.qml create mode 100644 okular/active/app/package/contents/ui/Browser.qml create mode 100644 okular/active/app/package/contents/ui/FullScreenDelegate.qml create mode 100644 okular/active/app/package/contents/ui/TableOfContents.qml create mode 100644 okular/active/app/package/contents/ui/Thumbnails.qml create mode 100644 okular/active/app/package/contents/ui/ThumbnailsBase.qml create mode 100644 okular/active/app/package/contents/ui/TreeDelegate.qml create mode 100644 okular/active/app/package/contents/ui/bookmark.png create mode 100644 okular/active/app/package/contents/ui/bookmark.svgz create mode 100644 okular/active/app/package/contents/ui/main.qml create mode 100644 okular/active/app/package/metadata.desktop create mode 100644 okular/active/app/src/CMakeLists.txt create mode 100644 okular/active/app/src/main.cpp create mode 100644 okular/active/components/CMakeLists.txt create mode 100644 okular/active/components/Messages.sh create mode 100644 okular/active/components/documentitem.cpp create mode 100644 okular/active/components/documentitem.h create mode 100644 okular/active/components/okularplugin.cpp create mode 100644 okular/active/components/okularplugin.h create mode 100644 okular/active/components/pageitem.cpp create mode 100644 okular/active/components/pageitem.h create mode 100644 okular/active/components/qmldir create mode 100644 okular/active/components/thumbnailitem.cpp create mode 100644 okular/active/components/thumbnailitem.h create mode 100644 okular/cmake/modules/COPYING-CMAKE-SCRIPTS create mode 100644 okular/cmake/modules/FindCHM.cmake create mode 100644 okular/cmake/modules/FindDjVuLibre.cmake create mode 100644 okular/cmake/modules/FindEPub.cmake create mode 100644 okular/cmake/modules/FindLibSpectre.cmake create mode 100644 okular/cmake/modules/FindPoppler.cmake create mode 100644 okular/conf/dlgaccessibility.cpp create mode 100644 okular/conf/dlgaccessibility.h create mode 100644 okular/conf/dlgaccessibilitybase.ui create mode 100644 okular/conf/dlgannotations.cpp create mode 100644 okular/conf/dlgannotations.h create mode 100644 okular/conf/dlgannotationsbase.ui create mode 100644 okular/conf/dlgdebug.cpp create mode 100644 okular/conf/dlgdebug.h create mode 100644 okular/conf/dlgeditor.cpp create mode 100644 okular/conf/dlgeditor.h create mode 100644 okular/conf/dlgeditorbase.ui create mode 100644 okular/conf/dlggeneral.cpp create mode 100644 okular/conf/dlggeneral.h create mode 100755 okular/conf/dlggeneralbase.ui create mode 100644 okular/conf/dlgperformance.cpp create mode 100644 okular/conf/dlgperformance.h create mode 100644 okular/conf/dlgperformancebase.ui create mode 100644 okular/conf/dlgpresentation.cpp create mode 100644 okular/conf/dlgpresentation.h create mode 100644 okular/conf/dlgpresentationbase.ui create mode 100644 okular/conf/okular.kcfg create mode 100644 okular/conf/okular_core.kcfg create mode 100644 okular/conf/preferencesdialog.cpp create mode 100644 okular/conf/preferencesdialog.h create mode 100644 okular/conf/settings.kcfgc create mode 100644 okular/conf/settings_core.kcfgc create mode 100644 okular/conf/textdocumentsettings.ui create mode 100644 okular/conf/widgetannottools.cpp create mode 100644 okular/conf/widgetannottools.h create mode 100644 okular/config-okular.h.cmake create mode 100644 okular/core/action.cpp create mode 100644 okular/core/action.h create mode 100644 okular/core/annotations.cpp create mode 100644 okular/core/annotations.h create mode 100644 okular/core/annotations_p.h create mode 100644 okular/core/area.cpp create mode 100644 okular/core/area.h create mode 100644 okular/core/audioplayer.cpp create mode 100644 okular/core/audioplayer.h create mode 100644 okular/core/audioplayer_p.h create mode 100644 okular/core/bookmarkmanager.cpp create mode 100644 okular/core/bookmarkmanager.h create mode 100644 okular/core/chooseenginedialog.cpp create mode 100644 okular/core/chooseenginedialog_p.h create mode 100644 okular/core/chooseenginewidget.ui create mode 100644 okular/core/debug_p.h create mode 100644 okular/core/document.cpp create mode 100644 okular/core/document.h create mode 100644 okular/core/document_p.h create mode 100644 okular/core/documentcommands.cpp create mode 100644 okular/core/documentcommands_p.h create mode 100644 okular/core/fileprinter.cpp create mode 100644 okular/core/fileprinter.h create mode 100644 okular/core/fontinfo.cpp create mode 100644 okular/core/fontinfo.h create mode 100644 okular/core/form.cpp create mode 100644 okular/core/form.h create mode 100644 okular/core/form_p.h create mode 100644 okular/core/generator.cpp create mode 100644 okular/core/generator.h create mode 100644 okular/core/generator_p.cpp create mode 100644 okular/core/generator_p.h create mode 100644 okular/core/global.h create mode 100644 okular/core/misc.cpp create mode 100644 okular/core/misc.h create mode 100644 okular/core/movie.cpp create mode 100644 okular/core/movie.h create mode 100644 okular/core/observer.cpp create mode 100644 okular/core/observer.h create mode 100644 okular/core/okularGenerator.desktop create mode 100644 okular/core/okular_export.h create mode 100644 okular/core/page.cpp create mode 100644 okular/core/page.h create mode 100644 okular/core/page_p.h create mode 100644 okular/core/pagecontroller.cpp create mode 100644 okular/core/pagecontroller_p.h create mode 100644 okular/core/pagesize.cpp create mode 100644 okular/core/pagesize.h create mode 100644 okular/core/pagetransition.cpp create mode 100644 okular/core/pagetransition.h create mode 100644 okular/core/rotationjob.cpp create mode 100644 okular/core/rotationjob_p.h create mode 100644 okular/core/script/executor_kjs.cpp create mode 100644 okular/core/script/executor_kjs_p.h create mode 100644 okular/core/script/kjs_app.cpp create mode 100644 okular/core/script/kjs_app_p.h create mode 100644 okular/core/script/kjs_console.cpp create mode 100644 okular/core/script/kjs_console_p.h create mode 100644 okular/core/script/kjs_data.cpp create mode 100644 okular/core/script/kjs_data_p.h create mode 100644 okular/core/script/kjs_document.cpp create mode 100644 okular/core/script/kjs_document_p.h create mode 100644 okular/core/script/kjs_field.cpp create mode 100644 okular/core/script/kjs_field_p.h create mode 100644 okular/core/script/kjs_fullscreen.cpp create mode 100644 okular/core/script/kjs_fullscreen_p.h create mode 100644 okular/core/script/kjs_spell.cpp create mode 100644 okular/core/script/kjs_spell_p.h create mode 100644 okular/core/script/kjs_util.cpp create mode 100644 okular/core/script/kjs_util_p.h create mode 100644 okular/core/scripter.cpp create mode 100644 okular/core/scripter.h create mode 100644 okular/core/sound.cpp create mode 100644 okular/core/sound.h create mode 100644 okular/core/sourcereference.cpp create mode 100644 okular/core/sourcereference.h create mode 100644 okular/core/sourcereference_p.h create mode 100644 okular/core/textdocumentgenerator.cpp create mode 100644 okular/core/textdocumentgenerator.h create mode 100644 okular/core/textdocumentgenerator_p.h create mode 100644 okular/core/textdocumentsettings.cpp create mode 100644 okular/core/textdocumentsettings.h create mode 100644 okular/core/textdocumentsettings_p.h create mode 100644 okular/core/texteditors_p.h create mode 100644 okular/core/textpage.cpp create mode 100644 okular/core/textpage.h create mode 100644 okular/core/textpage_p.h create mode 100644 okular/core/tile.h create mode 100644 okular/core/tilesmanager.cpp create mode 100644 okular/core/tilesmanager_p.h create mode 100644 okular/core/utils.cpp create mode 100644 okular/core/utils.h create mode 100644 okular/core/utils_p.h create mode 100644 okular/core/version.h create mode 100644 okular/core/view.cpp create mode 100644 okular/core/view.h create mode 100644 okular/core/view_p.h create mode 100644 okular/doc/CMakeLists.txt create mode 100644 okular/doc/annotation-properties.png create mode 100644 okular/doc/annotations.png create mode 100644 okular/doc/bookmark-management.png create mode 100644 okular/doc/configure-annotations.png create mode 100644 okular/doc/configure-backends.png create mode 100644 okular/doc/configure-editor.png create mode 100644 okular/doc/configure.png create mode 100644 okular/doc/embedded-files-bar.png create mode 100644 okular/doc/enhance-lowcontrast.png create mode 100644 okular/doc/enhance-shape.png create mode 100644 okular/doc/enhance-solid.png create mode 100644 okular/doc/enhance-thinline.png create mode 100644 okular/doc/forms-bar.png create mode 100644 okular/doc/index.docbook create mode 100644 okular/doc/mainwindow.png create mode 100644 okular/doc/man-okular.1.docbook create mode 100644 okular/doc/presentation.png create mode 100644 okular/doc/rating.png create mode 100644 okular/doc/tool-ellipse-okular.png create mode 100644 okular/doc/tool-highlighter-okular.png create mode 100644 okular/doc/tool-ink-okular.png create mode 100644 okular/doc/tool-line-okular.png create mode 100644 okular/doc/tool-note-inline-okular.png create mode 100644 okular/doc/tool-note-okular.png create mode 100644 okular/doc/tool-polygon-okular.png create mode 100644 okular/doc/tool-stamp-okular.png create mode 100644 okular/doc/tool-underline-okular.png create mode 100644 okular/extensions.cpp create mode 100644 okular/extensions.h create mode 100644 okular/generators/CMakeLists.txt create mode 100644 okular/generators/chm/CMakeLists.txt create mode 100644 okular/generators/chm/Messages.sh create mode 100644 okular/generators/chm/active-documentviewer_chm.desktop create mode 100644 okular/generators/chm/generator_chm.cpp create mode 100644 okular/generators/chm/generator_chm.h create mode 100644 okular/generators/chm/kio-msits/CMakeLists.txt create mode 100644 okular/generators/chm/kio-msits/msits.cpp create mode 100644 okular/generators/chm/kio-msits/msits.h create mode 100644 okular/generators/chm/kio-msits/msits.protocol create mode 100644 okular/generators/chm/lib/bitfiddle.h create mode 100644 okular/generators/chm/lib/lchmurlhandler.cpp create mode 100644 okular/generators/chm/lib/lchmurlhandler.h create mode 100644 okular/generators/chm/lib/libchmfile.cpp create mode 100644 okular/generators/chm/lib/libchmfile.h create mode 100644 okular/generators/chm/lib/libchmfile_search.cpp create mode 100644 okular/generators/chm/lib/libchmfileimpl.cpp create mode 100644 okular/generators/chm/lib/libchmfileimpl.h create mode 100644 okular/generators/chm/lib/libchmtextencoding.cpp create mode 100644 okular/generators/chm/lib/libchmtextencoding.h create mode 100644 okular/generators/chm/lib/libchmtocimage.cpp create mode 100644 okular/generators/chm/lib/libchmtocimage.h create mode 100644 okular/generators/chm/lib/libchmurlfactory.h create mode 100644 okular/generators/chm/libokularGenerator_chmlib.desktop create mode 100755 okular/generators/chm/okularApplication_chm.desktop create mode 100644 okular/generators/chm/okularChm.desktop create mode 100644 okular/generators/comicbook/CMakeLists.txt create mode 100755 okular/generators/comicbook/Messages.sh create mode 100644 okular/generators/comicbook/active-documentviewer_comicbook.desktop create mode 100644 okular/generators/comicbook/directory.cpp create mode 100644 okular/generators/comicbook/directory.h create mode 100644 okular/generators/comicbook/document.cpp create mode 100644 okular/generators/comicbook/document.h create mode 100644 okular/generators/comicbook/generator_comicbook.cpp create mode 100644 okular/generators/comicbook/generator_comicbook.h create mode 100644 okular/generators/comicbook/libokularGenerator_comicbook.desktop create mode 100755 okular/generators/comicbook/okularApplication_comicbook.desktop create mode 100644 okular/generators/comicbook/okularComicbook.desktop create mode 100644 okular/generators/comicbook/qnatsort.cpp create mode 100644 okular/generators/comicbook/qnatsort.h create mode 100644 okular/generators/comicbook/unrar.cpp create mode 100644 okular/generators/comicbook/unrar.h create mode 100644 okular/generators/comicbook/unrarflavours.cpp create mode 100644 okular/generators/comicbook/unrarflavours.h create mode 100644 okular/generators/djvu/CMakeLists.txt create mode 100644 okular/generators/djvu/Messages.sh create mode 100644 okular/generators/djvu/TODO create mode 100644 okular/generators/djvu/active-documentviewer_djvu.desktop create mode 100644 okular/generators/djvu/generator_djvu.cpp create mode 100644 okular/generators/djvu/generator_djvu.h create mode 100644 okular/generators/djvu/kdjvu.cpp create mode 100644 okular/generators/djvu/kdjvu.h create mode 100644 okular/generators/djvu/libokularGenerator_djvu.desktop create mode 100755 okular/generators/djvu/okularApplication_djvu.desktop create mode 100644 okular/generators/djvu/okularDjvu.desktop create mode 100644 okular/generators/dvi/CMakeLists.txt create mode 100644 okular/generators/dvi/Messages.sh create mode 100644 okular/generators/dvi/TeXFont.cpp create mode 100644 okular/generators/dvi/TeXFont.h create mode 100644 okular/generators/dvi/TeXFontDefinition.cpp create mode 100644 okular/generators/dvi/TeXFontDefinition.h create mode 100644 okular/generators/dvi/TeXFont_PFB.cpp create mode 100644 okular/generators/dvi/TeXFont_PFB.h create mode 100644 okular/generators/dvi/TeXFont_PK.cpp create mode 100644 okular/generators/dvi/TeXFont_PK.h create mode 100644 okular/generators/dvi/TeXFont_TFM.cpp create mode 100644 okular/generators/dvi/TeXFont_TFM.h create mode 100644 okular/generators/dvi/active-documentviewer_dvi.desktop create mode 100644 okular/generators/dvi/anchor.h create mode 100644 okular/generators/dvi/bigEndianByteReader.cpp create mode 100644 okular/generators/dvi/bigEndianByteReader.h create mode 100644 okular/generators/dvi/config.h create mode 100644 okular/generators/dvi/dvi.h create mode 100644 okular/generators/dvi/dviFile.cpp create mode 100644 okular/generators/dvi/dviFile.h create mode 100644 okular/generators/dvi/dviPageInfo.cpp create mode 100644 okular/generators/dvi/dviPageInfo.h create mode 100644 okular/generators/dvi/dviRenderer.cpp create mode 100644 okular/generators/dvi/dviRenderer.h create mode 100644 okular/generators/dvi/dviRenderer_dr.cpp create mode 100644 okular/generators/dvi/dviRenderer_draw.cpp create mode 100644 okular/generators/dvi/dviRenderer_prescan.cpp create mode 100644 okular/generators/dvi/dviexport.cpp create mode 100644 okular/generators/dvi/dviexport.h create mode 100644 okular/generators/dvi/dvisourcesplitter.cpp create mode 100644 okular/generators/dvi/dvisourcesplitter.h create mode 100644 okular/generators/dvi/fontEncoding.cpp create mode 100644 okular/generators/dvi/fontEncoding.h create mode 100644 okular/generators/dvi/fontEncodingPool.cpp create mode 100644 okular/generators/dvi/fontEncodingPool.h create mode 100644 okular/generators/dvi/fontMap.cpp create mode 100644 okular/generators/dvi/fontMap.h create mode 100644 okular/generators/dvi/fontpool.cpp create mode 100644 okular/generators/dvi/fontpool.h create mode 100644 okular/generators/dvi/generator_dvi.cpp create mode 100644 okular/generators/dvi/generator_dvi.h create mode 100644 okular/generators/dvi/glyph.cpp create mode 100644 okular/generators/dvi/glyph.h create mode 100644 okular/generators/dvi/hyperlink.h create mode 100644 okular/generators/dvi/kviewshell_export.h create mode 100644 okular/generators/dvi/kvs_debug.h create mode 100644 okular/generators/dvi/length.cpp create mode 100644 okular/generators/dvi/length.h create mode 100644 okular/generators/dvi/libokularGenerator_dvi.desktop create mode 100755 okular/generators/dvi/okularApplication_dvi.desktop create mode 100644 okular/generators/dvi/okularDvi.desktop create mode 100644 okular/generators/dvi/pageNumber.h create mode 100644 okular/generators/dvi/pageSize.cpp create mode 100644 okular/generators/dvi/pageSize.h create mode 100644 okular/generators/dvi/prebookmark.h create mode 100644 okular/generators/dvi/psgs.cpp create mode 100644 okular/generators/dvi/psgs.h create mode 100644 okular/generators/dvi/psheader.cpp create mode 100644 okular/generators/dvi/simplePageSize.cpp create mode 100644 okular/generators/dvi/simplePageSize.h create mode 100644 okular/generators/dvi/special.cpp create mode 100644 okular/generators/dvi/textBox.h create mode 100644 okular/generators/dvi/util.cpp create mode 100644 okular/generators/dvi/vf.cpp create mode 100644 okular/generators/dvi/xdvi.h create mode 100644 okular/generators/epub/CMakeLists.txt create mode 100755 okular/generators/epub/Messages.sh create mode 100644 okular/generators/epub/README create mode 100644 okular/generators/epub/active-documentviewer_epub.desktop create mode 100644 okular/generators/epub/converter.cpp create mode 100644 okular/generators/epub/converter.h create mode 100644 okular/generators/epub/data/CMakeLists.txt create mode 100644 okular/generators/epub/data/okular-epub-movie.png create mode 100644 okular/generators/epub/data/okular-epub-sound-icon.png create mode 100644 okular/generators/epub/epubdocument.cpp create mode 100644 okular/generators/epub/epubdocument.h create mode 100644 okular/generators/epub/generator_epub.cpp create mode 100644 okular/generators/epub/generator_epub.h create mode 100644 okular/generators/epub/libokularGenerator_epub.desktop create mode 100755 okular/generators/epub/okularApplication_epub.desktop create mode 100644 okular/generators/epub/okularEPub.desktop create mode 100644 okular/generators/fax/CMakeLists.txt create mode 100644 okular/generators/fax/Messages.sh create mode 100644 okular/generators/fax/active-documentviewer_fax.desktop create mode 100644 okular/generators/fax/faxdocument.cpp create mode 100644 okular/generators/fax/faxdocument.h create mode 100644 okular/generators/fax/faxexpand.cpp create mode 100644 okular/generators/fax/faxexpand.h create mode 100644 okular/generators/fax/faxinit.cpp create mode 100644 okular/generators/fax/generator_fax.cpp create mode 100644 okular/generators/fax/generator_fax.h create mode 100644 okular/generators/fax/libokularGenerator_fax.desktop create mode 100755 okular/generators/fax/okularApplication_fax.desktop create mode 100644 okular/generators/fax/okularFax.desktop create mode 100644 okular/generators/fictionbook/CMakeLists.txt create mode 100644 okular/generators/fictionbook/Messages.sh create mode 100644 okular/generators/fictionbook/active-documentviewer_fb.desktop create mode 100644 okular/generators/fictionbook/converter.cpp create mode 100644 okular/generators/fictionbook/converter.h create mode 100644 okular/generators/fictionbook/document.cpp create mode 100644 okular/generators/fictionbook/document.h create mode 100644 okular/generators/fictionbook/generator_fb.cpp create mode 100644 okular/generators/fictionbook/generator_fb.h create mode 100644 okular/generators/fictionbook/hi16-app-okular-fb2.png create mode 100644 okular/generators/fictionbook/hi32-app-okular-fb2.png create mode 100644 okular/generators/fictionbook/hi32-app-okular-fb2.svg create mode 100644 okular/generators/fictionbook/hi48-app-okular-fb2.png create mode 100644 okular/generators/fictionbook/libokularGenerator_fb.desktop create mode 100755 okular/generators/fictionbook/okularApplication_fb.desktop create mode 100644 okular/generators/fictionbook/okularFb.desktop create mode 100644 okular/generators/kimgio/CMakeLists.txt create mode 100644 okular/generators/kimgio/Messages.sh create mode 100755 okular/generators/kimgio/active-documentviewer_kimgio.desktop create mode 100644 okular/generators/kimgio/generator_kimgio.cpp create mode 100644 okular/generators/kimgio/generator_kimgio.h create mode 100644 okular/generators/kimgio/gui.rc create mode 100644 okular/generators/kimgio/libokularGenerator_kimgio.desktop create mode 100755 okular/generators/kimgio/okularApplication_kimgio.desktop create mode 100644 okular/generators/kimgio/okularKimgio.desktop create mode 100644 okular/generators/kimgio/tests/data/testExifOrientation-0.jpg create mode 100644 okular/generators/kimgio/tests/data/testExifOrientation-0mirror.jpg create mode 100644 okular/generators/kimgio/tests/data/testExifOrientation-180.jpg create mode 100644 okular/generators/kimgio/tests/data/testExifOrientation-180mirror.jpg create mode 100644 okular/generators/kimgio/tests/data/testExifOrientation-270.jpg create mode 100644 okular/generators/kimgio/tests/data/testExifOrientation-270mirror.jpg create mode 100644 okular/generators/kimgio/tests/data/testExifOrientation-90.jpg create mode 100644 okular/generators/kimgio/tests/data/testExifOrientation-90mirror.jpg create mode 100644 okular/generators/kimgio/tests/data/testExifOrientation-noexif.jpg create mode 100644 okular/generators/kimgio/tests/data/testExifOrientation-unspecified.jpg create mode 100644 okular/generators/kimgio/tests/kimgiotest.cpp create mode 100644 okular/generators/mobipocket/CMakeLists.txt create mode 100755 okular/generators/mobipocket/Messages.sh create mode 100644 okular/generators/mobipocket/converter.cpp create mode 100644 okular/generators/mobipocket/converter.h create mode 100644 okular/generators/mobipocket/generator_mobi.cpp create mode 100644 okular/generators/mobipocket/generator_mobi.h create mode 100644 okular/generators/mobipocket/libokularGenerator_mobi.desktop create mode 100644 okular/generators/mobipocket/mobidocument.cpp create mode 100644 okular/generators/mobipocket/mobidocument.h create mode 100755 okular/generators/mobipocket/okularApplication_mobi.desktop create mode 100644 okular/generators/mobipocket/okularMobi.desktop create mode 100644 okular/generators/ooo/CMakeLists.txt create mode 100644 okular/generators/ooo/Messages.sh create mode 100644 okular/generators/ooo/active-documentviewer_ooo.desktop create mode 100644 okular/generators/ooo/converter.cpp create mode 100644 okular/generators/ooo/converter.h create mode 100644 okular/generators/ooo/debug.h create mode 100644 okular/generators/ooo/document.cpp create mode 100644 okular/generators/ooo/document.h create mode 100644 okular/generators/ooo/formatproperty.cpp create mode 100644 okular/generators/ooo/formatproperty.h create mode 100644 okular/generators/ooo/generator_ooo.cpp create mode 100644 okular/generators/ooo/generator_ooo.h create mode 100644 okular/generators/ooo/libokularGenerator_ooo.desktop create mode 100644 okular/generators/ooo/manifest.cpp create mode 100644 okular/generators/ooo/manifest.h create mode 100755 okular/generators/ooo/okularApplication_ooo.desktop create mode 100644 okular/generators/ooo/okularOoo.desktop create mode 100644 okular/generators/ooo/styleinformation.cpp create mode 100644 okular/generators/ooo/styleinformation.h create mode 100644 okular/generators/ooo/styleparser.cpp create mode 100644 okular/generators/ooo/styleparser.h create mode 100644 okular/generators/plucker/CMakeLists.txt create mode 100644 okular/generators/plucker/Messages.sh create mode 100644 okular/generators/plucker/active-documentviewer_plucker.desktop create mode 100644 okular/generators/plucker/generator_plucker.cpp create mode 100644 okular/generators/plucker/generator_plucker.h create mode 100644 okular/generators/plucker/libokularGenerator_plucker.desktop create mode 100755 okular/generators/plucker/okularApplication_plucker.desktop create mode 100644 okular/generators/plucker/okularPlucker.desktop create mode 100644 okular/generators/plucker/unpluck/config.cpp create mode 100644 okular/generators/plucker/unpluck/image.cpp create mode 100644 okular/generators/plucker/unpluck/image.h create mode 100644 okular/generators/plucker/unpluck/qunpluck.cpp create mode 100644 okular/generators/plucker/unpluck/qunpluck.h create mode 100644 okular/generators/plucker/unpluck/unpluck.cpp create mode 100644 okular/generators/plucker/unpluck/unpluck.h create mode 100644 okular/generators/plucker/unpluck/unpluckint.h create mode 100644 okular/generators/plucker/unpluck/util.cpp create mode 100644 okular/generators/poppler/CMakeLists.txt create mode 100644 okular/generators/poppler/Messages.sh create mode 100644 okular/generators/poppler/active-documentviewer_pdf.desktop create mode 100644 okular/generators/poppler/annots.cpp create mode 100644 okular/generators/poppler/annots.h create mode 100644 okular/generators/poppler/conf/CMakeLists.txt create mode 100644 okular/generators/poppler/conf/pdfsettings.kcfg create mode 100644 okular/generators/poppler/conf/pdfsettings.kcfgc create mode 100644 okular/generators/poppler/conf/pdfsettingswidget.ui create mode 100644 okular/generators/poppler/config-okular-poppler.h.cmake create mode 100644 okular/generators/poppler/formfields.cpp create mode 100644 okular/generators/poppler/formfields.h create mode 100644 okular/generators/poppler/generator_pdf.cpp create mode 100644 okular/generators/poppler/generator_pdf.h create mode 100644 okular/generators/poppler/libokularGenerator_poppler.desktop create mode 100755 okular/generators/poppler/okularApplication_pdf.desktop create mode 100644 okular/generators/poppler/okularPoppler.desktop create mode 100644 okular/generators/poppler/popplerembeddedfile.h create mode 100644 okular/generators/poppler/synctex/patches/00-disable-SYNCTEX_INLINE.diff create mode 100644 okular/generators/poppler/synctex/patches/01-fix-win32-define.diff create mode 100644 okular/generators/poppler/synctex/patches/04-gcc-specify-printf-format.diff create mode 100644 okular/generators/poppler/synctex/patches/05-fix-error-formats.diff create mode 100644 okular/generators/poppler/synctex/patches/06-mingw-_synctex_error.diff create mode 100644 okular/generators/poppler/synctex/patches/07-synctex_scanner_new_with_output_file-reset-mode.diff create mode 100644 okular/generators/poppler/synctex/patches/08-fix_cpp_comments.diff create mode 100644 okular/generators/poppler/synctex/patches/09-fix_path_comparison.diff create mode 100644 okular/generators/poppler/synctex/patches/series create mode 100644 okular/generators/poppler/synctex/synctex_parser.c create mode 100644 okular/generators/poppler/synctex/synctex_parser.h create mode 100644 okular/generators/poppler/synctex/synctex_parser_local.h create mode 100644 okular/generators/poppler/synctex/synctex_parser_readme.txt create mode 100644 okular/generators/poppler/synctex/synctex_parser_utils.c create mode 100644 okular/generators/poppler/synctex/synctex_parser_utils.h create mode 100644 okular/generators/poppler/synctex/synctex_parser_version.txt create mode 100644 okular/generators/spectre/CMakeLists.txt create mode 100644 okular/generators/spectre/DESIGN create mode 100644 okular/generators/spectre/Messages.sh create mode 100644 okular/generators/spectre/active-documentviewer_ghostview.desktop create mode 100644 okular/generators/spectre/conf/CMakeLists.txt create mode 100644 okular/generators/spectre/conf/empty.cpp create mode 100644 okular/generators/spectre/conf/gssettings.kcfg create mode 100644 okular/generators/spectre/conf/gssettings.kcfgc create mode 100644 okular/generators/spectre/conf/gssettingswidget.ui create mode 100644 okular/generators/spectre/generator_ghostview.cpp create mode 100644 okular/generators/spectre/generator_ghostview.h create mode 100644 okular/generators/spectre/hi16-app-okular-gv.png create mode 100644 okular/generators/spectre/hi32-app-okular-gv.png create mode 100644 okular/generators/spectre/libokularGenerator_ghostview.desktop create mode 100755 okular/generators/spectre/okularApplication_ghostview.desktop create mode 100644 okular/generators/spectre/okularGhostview.desktop create mode 100644 okular/generators/spectre/rendererthread.cpp create mode 100644 okular/generators/spectre/rendererthread.h create mode 100644 okular/generators/tiff/CMakeLists.txt create mode 100644 okular/generators/tiff/Messages.sh create mode 100644 okular/generators/tiff/active-documentviewer_tiff.desktop create mode 100644 okular/generators/tiff/generator_tiff.cpp create mode 100644 okular/generators/tiff/generator_tiff.h create mode 100644 okular/generators/tiff/libokularGenerator_tiff.desktop create mode 100755 okular/generators/tiff/okularApplication_tiff.desktop create mode 100644 okular/generators/tiff/okularTiff.desktop create mode 100644 okular/generators/txt/CMakeLists.txt create mode 100644 okular/generators/txt/Messages.sh create mode 100644 okular/generators/txt/active-documentviewer_txt.desktop create mode 100644 okular/generators/txt/converter.cpp create mode 100644 okular/generators/txt/converter.h create mode 100644 okular/generators/txt/document.cpp create mode 100644 okular/generators/txt/document.h create mode 100644 okular/generators/txt/generator_txt.cpp create mode 100644 okular/generators/txt/generator_txt.h create mode 100644 okular/generators/txt/libokularGenerator_txt.desktop create mode 100644 okular/generators/txt/okularApplication_txt.desktop create mode 100644 okular/generators/txt/okularTxt.desktop create mode 100644 okular/generators/xps/.emacs-dirvars create mode 100644 okular/generators/xps/CMakeLists.txt create mode 100644 okular/generators/xps/Messages.sh create mode 100644 okular/generators/xps/active-documentviewer_xps.desktop create mode 100644 okular/generators/xps/generator_xps.cpp create mode 100644 okular/generators/xps/generator_xps.h create mode 100644 okular/generators/xps/libokularGenerator_xps.desktop create mode 100755 okular/generators/xps/okularApplication_xps.desktop create mode 100644 okular/generators/xps/okularXps.desktop create mode 100644 okular/interfaces/configinterface.h create mode 100644 okular/interfaces/guiinterface.h create mode 100644 okular/interfaces/printinterface.h create mode 100644 okular/interfaces/saveinterface.h create mode 100644 okular/interfaces/viewerinterface.h create mode 100644 okular/kdocumentviewer.h create mode 100644 okular/okular.upd create mode 100644 okular/okular_part.desktop create mode 100644 okular/okular_part_export.h create mode 100644 okular/part-viewermode.rc create mode 100644 okular/part.cpp create mode 100644 okular/part.h create mode 100644 okular/part.rc create mode 100644 okular/shell/CMakeLists.txt create mode 100644 okular/shell/main.cpp create mode 100755 okular/shell/okular.desktop create mode 100644 okular/shell/okular_main.cpp create mode 100644 okular/shell/okular_main.h create mode 100644 okular/shell/shell.cpp create mode 100644 okular/shell/shell.h create mode 100644 okular/shell/shell.rc create mode 100644 okular/shell/shellutils.cpp create mode 100644 okular/shell/shellutils.h create mode 100644 okular/tests/CMakeLists.txt create mode 100644 okular/tests/addremoveannotationtest.cpp create mode 100644 okular/tests/annotationstest.cpp create mode 100644 okular/tests/data/contents.epub create mode 100644 okular/tests/data/file1.pdf create mode 100644 okular/tests/data/formSamples.pdf create mode 100644 okular/tests/data/synctextest.tex create mode 100644 okular/tests/data/tocreload.pdf create mode 100644 okular/tests/documenttest.cpp create mode 100644 okular/tests/editannotationcontentstest.cpp create mode 100644 okular/tests/editformstest.cpp create mode 100644 okular/tests/mainshelltest.cpp create mode 100644 okular/tests/modifyannotationpropertiestest.cpp create mode 100644 okular/tests/parttest.cpp create mode 100644 okular/tests/searchtest.cpp create mode 100644 okular/tests/shelltest.cpp create mode 100644 okular/tests/testingutils.cpp create mode 100644 okular/tests/testingutils.h create mode 100644 okular/tests/translateannotationtest.cpp create mode 100644 okular/tests/urldetecttest.cpp create mode 100644 okular/ui/CMakeLists.txt create mode 100644 okular/ui/annotationmodel.cpp create mode 100644 okular/ui/annotationmodel.h create mode 100644 okular/ui/annotationpopup.cpp create mode 100644 okular/ui/annotationpopup.h create mode 100644 okular/ui/annotationpropertiesdialog.cpp create mode 100644 okular/ui/annotationpropertiesdialog.h create mode 100644 okular/ui/annotationproxymodels.cpp create mode 100644 okular/ui/annotationproxymodels.h create mode 100644 okular/ui/annotationtools.cpp create mode 100644 okular/ui/annotationtools.h create mode 100644 okular/ui/annotationwidgets.cpp create mode 100644 okular/ui/annotationwidgets.h create mode 100644 okular/ui/annotwindow.cpp create mode 100644 okular/ui/annotwindow.h create mode 100644 okular/ui/bookmarklist.cpp create mode 100644 okular/ui/bookmarklist.h create mode 100644 okular/ui/data/CMakeLists.txt create mode 100644 okular/ui/data/README.Icons create mode 100644 okular/ui/data/checkmark.png create mode 100644 okular/ui/data/circle.png create mode 100644 okular/ui/data/comment.png create mode 100644 okular/ui/data/cross.png create mode 100644 okular/ui/data/help.png create mode 100644 okular/ui/data/icons/CMakeLists.txt create mode 100644 okular/ui/data/icons/hi128-apps-okular.png create mode 100644 okular/ui/data/icons/hi16-apps-okular.png create mode 100644 okular/ui/data/icons/hi22-apps-okular.png create mode 100644 okular/ui/data/icons/hi32-apps-okular.png create mode 100644 okular/ui/data/icons/hi48-apps-okular.png create mode 100644 okular/ui/data/icons/hi64-apps-okular.png create mode 100644 okular/ui/data/icons/hisc-apps-okular.svgz create mode 100644 okular/ui/data/icons/small/hi16-apps-okular.svgz create mode 100644 okular/ui/data/icons/small/hi22-apps-okular.svgz create mode 100644 okular/ui/data/insert.png create mode 100644 okular/ui/data/key.png create mode 100644 okular/ui/data/newparagraph.png create mode 100644 okular/ui/data/note.png create mode 100644 okular/ui/data/okular.knsrc create mode 100644 okular/ui/data/paperclip.png create mode 100644 okular/ui/data/paragraph.png create mode 100644 okular/ui/data/pushpin.png create mode 100644 okular/ui/data/rightarrow.png create mode 100644 okular/ui/data/rightpointer.png create mode 100644 okular/ui/data/sources/checkmark.svgz create mode 100644 okular/ui/data/sources/circle.svgz create mode 100644 okular/ui/data/sources/comment.svgz create mode 100644 okular/ui/data/sources/cross.svgz create mode 100644 okular/ui/data/sources/ghns.svg create mode 100644 okular/ui/data/sources/help.svgz create mode 100644 okular/ui/data/sources/insert.svgz create mode 100644 okular/ui/data/sources/key.svgz create mode 100644 okular/ui/data/sources/newparagraph.svgz create mode 100644 okular/ui/data/sources/note.svgz create mode 100644 okular/ui/data/sources/paragraph.svgz create mode 100644 okular/ui/data/sources/rightarrow.svgz create mode 100644 okular/ui/data/sources/rightpointer.svgz create mode 100644 okular/ui/data/sources/star.svgz create mode 100644 okular/ui/data/sources/tool-base-okular.svgz create mode 100644 okular/ui/data/sources/tool-ellipse-okular.svgz create mode 100644 okular/ui/data/sources/tool-highlighter-okular-colorizable.svgz create mode 100644 okular/ui/data/sources/tool-highlighter-okular.svgz create mode 100644 okular/ui/data/sources/tool-ink-okular-colorizable.svgz create mode 100644 okular/ui/data/sources/tool-ink-okular.svgz create mode 100644 okular/ui/data/sources/tool-line-okular.svgz create mode 100644 okular/ui/data/sources/tool-note-inline-okular-colorizable.svgz create mode 100644 okular/ui/data/sources/tool-note-inline-okular.svgz create mode 100644 okular/ui/data/sources/tool-note-okular-colorizable.svgz create mode 100644 okular/ui/data/sources/tool-note-okular.svgz create mode 100644 okular/ui/data/sources/tool-polygon-okular.svgz create mode 100644 okular/ui/data/sources/tool-stamp-okular.svgz create mode 100644 okular/ui/data/sources/tool-underline-okular.svgz create mode 100644 okular/ui/data/sources/uparrow.svgz create mode 100644 okular/ui/data/sources/upleftarrow.svgz create mode 100644 okular/ui/data/stamps.svg create mode 100644 okular/ui/data/star.png create mode 100644 okular/ui/data/tool-base-okular.png create mode 100644 okular/ui/data/tool-highlighter-okular-colorizable.png create mode 100644 okular/ui/data/tool-ink-okular-colorizable.png create mode 100644 okular/ui/data/tool-note-inline-okular-colorizable.png create mode 100644 okular/ui/data/tool-note-inline.png create mode 100644 okular/ui/data/tool-note-okular-colorizable.png create mode 100644 okular/ui/data/tool-note.png create mode 100644 okular/ui/data/tool_hl_orange.png create mode 100644 okular/ui/data/tool_hl_pink.png create mode 100644 okular/ui/data/tool_hl_yellow.png create mode 100644 okular/ui/data/tool_ink_green.png create mode 100644 okular/ui/data/tool_note.png create mode 100644 okular/ui/data/tools.xml create mode 100644 okular/ui/data/uparrow.png create mode 100644 okular/ui/data/upleftarrow.png create mode 100644 okular/ui/embeddedfilesdialog.cpp create mode 100644 okular/ui/embeddedfilesdialog.h create mode 100644 okular/ui/fileprinterpreview.cpp create mode 100644 okular/ui/fileprinterpreview.h create mode 100644 okular/ui/findbar.cpp create mode 100644 okular/ui/findbar.h create mode 100644 okular/ui/formwidgets.cpp create mode 100644 okular/ui/formwidgets.h create mode 100644 okular/ui/guiutils.cpp create mode 100644 okular/ui/guiutils.h create mode 100644 okular/ui/ktreeviewsearchline.cpp create mode 100644 okular/ui/ktreeviewsearchline.h create mode 100644 okular/ui/latexrenderer.cpp create mode 100644 okular/ui/latexrenderer.h create mode 100644 okular/ui/magnifierview.cpp create mode 100644 okular/ui/magnifierview.h create mode 100644 okular/ui/minibar.cpp create mode 100644 okular/ui/minibar.h create mode 100644 okular/ui/pageitemdelegate.cpp create mode 100644 okular/ui/pageitemdelegate.h create mode 100644 okular/ui/pagepainter.cpp create mode 100644 okular/ui/pagepainter.h create mode 100644 okular/ui/pagesizelabel.cpp create mode 100644 okular/ui/pagesizelabel.h create mode 100644 okular/ui/pageview.cpp create mode 100644 okular/ui/pageview.h create mode 100644 okular/ui/pageviewannotator.cpp create mode 100644 okular/ui/pageviewannotator.h create mode 100644 okular/ui/pageviewutils.cpp create mode 100644 okular/ui/pageviewutils.h create mode 100644 okular/ui/presentationsearchbar.cpp create mode 100644 okular/ui/presentationsearchbar.h create mode 100644 okular/ui/presentationwidget.cpp create mode 100644 okular/ui/presentationwidget.h create mode 100644 okular/ui/priorities.h create mode 100644 okular/ui/propertiesdialog.cpp create mode 100644 okular/ui/propertiesdialog.h create mode 100644 okular/ui/searchlineedit.cpp create mode 100644 okular/ui/searchlineedit.h create mode 100644 okular/ui/searchwidget.cpp create mode 100644 okular/ui/searchwidget.h create mode 100644 okular/ui/side_reviews.cpp create mode 100644 okular/ui/side_reviews.h create mode 100644 okular/ui/sidebar.cpp create mode 100644 okular/ui/sidebar.h create mode 100644 okular/ui/snapshottaker.cpp create mode 100644 okular/ui/snapshottaker.h create mode 100644 okular/ui/thumbnaillist.cpp create mode 100644 okular/ui/thumbnaillist.h create mode 100644 okular/ui/toc.cpp create mode 100644 okular/ui/toc.h create mode 100644 okular/ui/tocmodel.cpp create mode 100644 okular/ui/tocmodel.h create mode 100644 okular/ui/toolaction.cpp create mode 100644 okular/ui/toolaction.h create mode 100644 okular/ui/tts.cpp create mode 100644 okular/ui/tts.h create mode 100644 okular/ui/url_utils.h create mode 100644 okular/ui/videowidget.cpp create mode 100644 okular/ui/videowidget.h create mode 100644 partitionmanager/CHANGES create mode 100644 partitionmanager/CMakeLists.txt create mode 100644 partitionmanager/COPYING create mode 100644 partitionmanager/INSTALL create mode 100644 partitionmanager/README create mode 100644 partitionmanager/TODO create mode 100644 partitionmanager/cmake/modules/FindLIBPARTED.cmake create mode 100644 partitionmanager/cmake/modules/FindMSGFMT.cmake create mode 100644 partitionmanager/doc/CMakeLists.txt create mode 100644 partitionmanager/doc/ca/CMakeLists.txt create mode 100644 partitionmanager/doc/ca/appendix.docbook create mode 100644 partitionmanager/doc/ca/copyhowto.docbook create mode 100644 partitionmanager/doc/ca/credits.docbook create mode 100644 partitionmanager/doc/ca/faq.docbook create mode 100644 partitionmanager/doc/ca/glossary.docbook create mode 100644 partitionmanager/doc/ca/index.docbook create mode 100644 partitionmanager/doc/ca/installoshowto.docbook create mode 100644 partitionmanager/doc/ca/introduction.docbook create mode 100644 partitionmanager/doc/ca/referencemanual.docbook create mode 100644 partitionmanager/doc/ca/resizehowto.docbook create mode 100644 partitionmanager/doc/ca/usermanual.docbook create mode 100644 partitionmanager/doc/de/CMakeLists.txt create mode 100644 partitionmanager/doc/de/appendix.docbook create mode 100644 partitionmanager/doc/de/copyhowto.docbook create mode 100644 partitionmanager/doc/de/credits.docbook create mode 100644 partitionmanager/doc/de/faq.docbook create mode 100644 partitionmanager/doc/de/glossary.docbook create mode 100644 partitionmanager/doc/de/index.docbook create mode 100644 partitionmanager/doc/de/installoshowto.docbook create mode 100644 partitionmanager/doc/de/introduction.docbook create mode 100644 partitionmanager/doc/de/referencemanual.docbook create mode 100644 partitionmanager/doc/de/resizehowto.docbook create mode 100644 partitionmanager/doc/de/usermanual.docbook create mode 100644 partitionmanager/doc/en_US/CMakeLists.txt create mode 100644 partitionmanager/doc/en_US/appendix.docbook create mode 100644 partitionmanager/doc/en_US/copy_howto_1.png create mode 100644 partitionmanager/doc/en_US/copy_howto_2.png create mode 100644 partitionmanager/doc/en_US/copyhowto.docbook create mode 100644 partitionmanager/doc/en_US/credits.docbook create mode 100644 partitionmanager/doc/en_US/faq.docbook create mode 100644 partitionmanager/doc/en_US/filesystemsupport.png create mode 100644 partitionmanager/doc/en_US/glossary.docbook create mode 100644 partitionmanager/doc/en_US/index.docbook create mode 100644 partitionmanager/doc/en_US/installos_howto_1.png create mode 100644 partitionmanager/doc/en_US/installos_howto_2.png create mode 100644 partitionmanager/doc/en_US/installos_howto_3.png create mode 100644 partitionmanager/doc/en_US/installos_howto_4.png create mode 100644 partitionmanager/doc/en_US/installos_howto_5.png create mode 100644 partitionmanager/doc/en_US/installos_howto_6.png create mode 100644 partitionmanager/doc/en_US/installos_howto_7.png create mode 100644 partitionmanager/doc/en_US/installoshowto.docbook create mode 100644 partitionmanager/doc/en_US/introduction.docbook create mode 100644 partitionmanager/doc/en_US/mainwindow.png create mode 100644 partitionmanager/doc/en_US/referencemanual.docbook create mode 100644 partitionmanager/doc/en_US/resize_howto_1.png create mode 100644 partitionmanager/doc/en_US/resize_howto_2.png create mode 100644 partitionmanager/doc/en_US/resize_howto_3.png create mode 100644 partitionmanager/doc/en_US/resize_howto_4.png create mode 100644 partitionmanager/doc/en_US/resize_howto_5.png create mode 100644 partitionmanager/doc/en_US/resize_howto_6.png create mode 100644 partitionmanager/doc/en_US/resize_howto_7.png create mode 100644 partitionmanager/doc/en_US/resize_howto_8.png create mode 100644 partitionmanager/doc/en_US/resize_howto_9.png create mode 100644 partitionmanager/doc/en_US/resizehowto.docbook create mode 100644 partitionmanager/doc/en_US/usermanual.docbook create mode 100644 partitionmanager/doc/es/CMakeLists.txt create mode 100644 partitionmanager/doc/es/appendix.docbook create mode 100644 partitionmanager/doc/es/copyhowto.docbook create mode 100644 partitionmanager/doc/es/credits.docbook create mode 100644 partitionmanager/doc/es/faq.docbook create mode 100644 partitionmanager/doc/es/glossary.docbook create mode 100644 partitionmanager/doc/es/index.docbook create mode 100644 partitionmanager/doc/es/installoshowto.docbook create mode 100644 partitionmanager/doc/es/introduction.docbook create mode 100644 partitionmanager/doc/es/referencemanual.docbook create mode 100644 partitionmanager/doc/es/resizehowto.docbook create mode 100644 partitionmanager/doc/es/usermanual.docbook create mode 100644 partitionmanager/doc/fr/CMakeLists.txt create mode 100644 partitionmanager/doc/fr/appendix.docbook create mode 100644 partitionmanager/doc/fr/copyhowto.docbook create mode 100644 partitionmanager/doc/fr/credits.docbook create mode 100644 partitionmanager/doc/fr/faq.docbook create mode 100644 partitionmanager/doc/fr/glossary.docbook create mode 100644 partitionmanager/doc/fr/index.docbook create mode 100644 partitionmanager/doc/fr/installoshowto.docbook create mode 100644 partitionmanager/doc/fr/introduction.docbook create mode 100644 partitionmanager/doc/fr/referencemanual.docbook create mode 100644 partitionmanager/doc/fr/resizehowto.docbook create mode 100644 partitionmanager/doc/fr/usermanual.docbook create mode 100644 partitionmanager/doc/it/CMakeLists.txt create mode 100644 partitionmanager/doc/it/appendix.docbook create mode 100644 partitionmanager/doc/it/copyhowto.docbook create mode 100644 partitionmanager/doc/it/credits.docbook create mode 100644 partitionmanager/doc/it/faq.docbook create mode 100644 partitionmanager/doc/it/glossary.docbook create mode 100644 partitionmanager/doc/it/index.docbook create mode 100644 partitionmanager/doc/it/installoshowto.docbook create mode 100644 partitionmanager/doc/it/introduction.docbook create mode 100644 partitionmanager/doc/it/referencemanual.docbook create mode 100644 partitionmanager/doc/it/resizehowto.docbook create mode 100644 partitionmanager/doc/it/usermanual.docbook create mode 100644 partitionmanager/doc/nl/CMakeLists.txt create mode 100644 partitionmanager/doc/nl/appendix.docbook create mode 100644 partitionmanager/doc/nl/copyhowto.docbook create mode 100644 partitionmanager/doc/nl/credits.docbook create mode 100644 partitionmanager/doc/nl/faq.docbook create mode 100644 partitionmanager/doc/nl/glossary.docbook create mode 100644 partitionmanager/doc/nl/index.docbook create mode 100644 partitionmanager/doc/nl/installoshowto.docbook create mode 100644 partitionmanager/doc/nl/introduction.docbook create mode 100644 partitionmanager/doc/nl/referencemanual.docbook create mode 100644 partitionmanager/doc/nl/resizehowto.docbook create mode 100644 partitionmanager/doc/nl/usermanual.docbook create mode 100644 partitionmanager/doc/pt/CMakeLists.txt create mode 100644 partitionmanager/doc/pt/appendix.docbook create mode 100644 partitionmanager/doc/pt/copyhowto.docbook create mode 100644 partitionmanager/doc/pt/credits.docbook create mode 100644 partitionmanager/doc/pt/faq.docbook create mode 100644 partitionmanager/doc/pt/glossary.docbook create mode 100644 partitionmanager/doc/pt/index.docbook create mode 100644 partitionmanager/doc/pt/installoshowto.docbook create mode 100644 partitionmanager/doc/pt/introduction.docbook create mode 100644 partitionmanager/doc/pt/referencemanual.docbook create mode 100644 partitionmanager/doc/pt/resizehowto.docbook create mode 100644 partitionmanager/doc/pt/usermanual.docbook create mode 100644 partitionmanager/doc/pt_BR/CMakeLists.txt create mode 100644 partitionmanager/doc/pt_BR/appendix.docbook create mode 100644 partitionmanager/doc/pt_BR/copyhowto.docbook create mode 100644 partitionmanager/doc/pt_BR/credits.docbook create mode 100644 partitionmanager/doc/pt_BR/faq.docbook create mode 100644 partitionmanager/doc/pt_BR/glossary.docbook create mode 100644 partitionmanager/doc/pt_BR/index.docbook create mode 100644 partitionmanager/doc/pt_BR/installoshowto.docbook create mode 100644 partitionmanager/doc/pt_BR/introduction.docbook create mode 100644 partitionmanager/doc/pt_BR/referencemanual.docbook create mode 100644 partitionmanager/doc/pt_BR/resizehowto.docbook create mode 100644 partitionmanager/doc/pt_BR/usermanual.docbook create mode 100644 partitionmanager/doc/sv/CMakeLists.txt create mode 100644 partitionmanager/doc/sv/appendix.docbook create mode 100644 partitionmanager/doc/sv/copyhowto.docbook create mode 100644 partitionmanager/doc/sv/credits.docbook create mode 100644 partitionmanager/doc/sv/faq.docbook create mode 100644 partitionmanager/doc/sv/glossary.docbook create mode 100644 partitionmanager/doc/sv/index.docbook create mode 100644 partitionmanager/doc/sv/installoshowto.docbook create mode 100644 partitionmanager/doc/sv/introduction.docbook create mode 100644 partitionmanager/doc/sv/referencemanual.docbook create mode 100644 partitionmanager/doc/sv/resizehowto.docbook create mode 100644 partitionmanager/doc/sv/usermanual.docbook create mode 100644 partitionmanager/doc/uk/CMakeLists.txt create mode 100644 partitionmanager/doc/uk/appendix.docbook create mode 100644 partitionmanager/doc/uk/copyhowto.docbook create mode 100644 partitionmanager/doc/uk/credits.docbook create mode 100644 partitionmanager/doc/uk/faq.docbook create mode 100644 partitionmanager/doc/uk/glossary.docbook create mode 100644 partitionmanager/doc/uk/index.docbook create mode 100644 partitionmanager/doc/uk/installoshowto.docbook create mode 100644 partitionmanager/doc/uk/introduction.docbook create mode 100644 partitionmanager/doc/uk/referencemanual.docbook create mode 100644 partitionmanager/doc/uk/resizehowto.docbook create mode 100644 partitionmanager/doc/uk/usermanual.docbook create mode 100644 partitionmanager/icons/CMakeLists.txt create mode 100644 partitionmanager/icons/hi128-apps-partitionmanager.png create mode 100644 partitionmanager/icons/hi16-apps-partitionmanager.png create mode 100644 partitionmanager/icons/hi22-apps-partitionmanager.png create mode 100644 partitionmanager/icons/hi32-apps-partitionmanager.png create mode 100644 partitionmanager/icons/hi48-apps-partitionmanager.png create mode 100644 partitionmanager/icons/hi64-apps-partitionmanager.png create mode 100644 partitionmanager/lib/CMakeLists.txt create mode 100644 partitionmanager/lib/fatlabel/.kdev4/trunk.kdev4 create mode 100644 partitionmanager/lib/fatlabel/CMakeLists.txt create mode 100644 partitionmanager/lib/fatlabel/app/main.c create mode 100644 partitionmanager/lib/fatlabel/buffer.c create mode 100644 partitionmanager/lib/fatlabel/buffer.h create mode 100644 partitionmanager/lib/fatlabel/charsetConv.c create mode 100644 partitionmanager/lib/fatlabel/devices.c create mode 100644 partitionmanager/lib/fatlabel/devices.h create mode 100644 partitionmanager/lib/fatlabel/dirCache.c create mode 100644 partitionmanager/lib/fatlabel/dirCache.h create mode 100644 partitionmanager/lib/fatlabel/directory.c create mode 100644 partitionmanager/lib/fatlabel/directory.h create mode 100644 partitionmanager/lib/fatlabel/fat.c create mode 100644 partitionmanager/lib/fatlabel/fat.h create mode 100644 partitionmanager/lib/fatlabel/fatlabel.c create mode 100644 partitionmanager/lib/fatlabel/fatlabel.h create mode 100644 partitionmanager/lib/fatlabel/file.c create mode 100644 partitionmanager/lib/fatlabel/file.h create mode 100644 partitionmanager/lib/fatlabel/file_name.c create mode 100644 partitionmanager/lib/fatlabel/file_name.h create mode 100644 partitionmanager/lib/fatlabel/force_io.c create mode 100644 partitionmanager/lib/fatlabel/force_io.h create mode 100644 partitionmanager/lib/fatlabel/fs.h create mode 100644 partitionmanager/lib/fatlabel/htable.c create mode 100644 partitionmanager/lib/fatlabel/htable.h create mode 100644 partitionmanager/lib/fatlabel/init.c create mode 100644 partitionmanager/lib/fatlabel/init.h create mode 100644 partitionmanager/lib/fatlabel/llong.c create mode 100644 partitionmanager/lib/fatlabel/llong.h create mode 100644 partitionmanager/lib/fatlabel/match.c create mode 100644 partitionmanager/lib/fatlabel/match.h create mode 100644 partitionmanager/lib/fatlabel/msdos.h create mode 100644 partitionmanager/lib/fatlabel/mtools.h create mode 100644 partitionmanager/lib/fatlabel/nameclash.h create mode 100644 partitionmanager/lib/fatlabel/partition.h create mode 100644 partitionmanager/lib/fatlabel/plain_io.c create mode 100644 partitionmanager/lib/fatlabel/plain_io.h create mode 100644 partitionmanager/lib/fatlabel/stream.c create mode 100644 partitionmanager/lib/fatlabel/stream.h create mode 100644 partitionmanager/lib/fatlabel/vfat.c create mode 100644 partitionmanager/lib/fatlabel/vfat.h create mode 100644 partitionmanager/po/CMakeLists.txt create mode 100644 partitionmanager/po/ar/CMakeLists.txt create mode 100644 partitionmanager/po/ar/partitionmanager.po create mode 100644 partitionmanager/po/bg/CMakeLists.txt create mode 100644 partitionmanager/po/bg/partitionmanager.po create mode 100644 partitionmanager/po/bs/CMakeLists.txt create mode 100644 partitionmanager/po/bs/partitionmanager.po create mode 100644 partitionmanager/po/ca/CMakeLists.txt create mode 100644 partitionmanager/po/ca/partitionmanager.po create mode 100644 partitionmanager/po/ca@valencia/CMakeLists.txt create mode 100644 partitionmanager/po/ca@valencia/partitionmanager.po create mode 100644 partitionmanager/po/cs/CMakeLists.txt create mode 100644 partitionmanager/po/cs/partitionmanager.po create mode 100644 partitionmanager/po/da/CMakeLists.txt create mode 100644 partitionmanager/po/da/partitionmanager.po create mode 100644 partitionmanager/po/de/CMakeLists.txt create mode 100644 partitionmanager/po/de/partitionmanager.po create mode 100644 partitionmanager/po/el/CMakeLists.txt create mode 100644 partitionmanager/po/el/partitionmanager.po create mode 100644 partitionmanager/po/en_GB/CMakeLists.txt create mode 100644 partitionmanager/po/en_GB/partitionmanager.po create mode 100644 partitionmanager/po/es/CMakeLists.txt create mode 100644 partitionmanager/po/es/partitionmanager.po create mode 100644 partitionmanager/po/et/CMakeLists.txt create mode 100644 partitionmanager/po/et/partitionmanager.po create mode 100644 partitionmanager/po/fr/CMakeLists.txt create mode 100644 partitionmanager/po/fr/partitionmanager.po create mode 100644 partitionmanager/po/gl/CMakeLists.txt create mode 100644 partitionmanager/po/gl/partitionmanager.po create mode 100644 partitionmanager/po/it/CMakeLists.txt create mode 100644 partitionmanager/po/it/partitionmanager.po create mode 100644 partitionmanager/po/lt/CMakeLists.txt create mode 100644 partitionmanager/po/lt/partitionmanager.po create mode 100644 partitionmanager/po/nb/CMakeLists.txt create mode 100644 partitionmanager/po/nb/partitionmanager.po create mode 100644 partitionmanager/po/nds/CMakeLists.txt create mode 100644 partitionmanager/po/nds/partitionmanager.po create mode 100644 partitionmanager/po/nl/CMakeLists.txt create mode 100644 partitionmanager/po/nl/partitionmanager.po create mode 100644 partitionmanager/po/pa/CMakeLists.txt create mode 100644 partitionmanager/po/pa/partitionmanager.po create mode 100644 partitionmanager/po/pl/CMakeLists.txt create mode 100644 partitionmanager/po/pl/partitionmanager.po create mode 100644 partitionmanager/po/pt/CMakeLists.txt create mode 100644 partitionmanager/po/pt/partitionmanager.po create mode 100644 partitionmanager/po/pt_BR/CMakeLists.txt create mode 100644 partitionmanager/po/pt_BR/partitionmanager.po create mode 100644 partitionmanager/po/ro/CMakeLists.txt create mode 100644 partitionmanager/po/ro/partitionmanager.po create mode 100644 partitionmanager/po/ru/CMakeLists.txt create mode 100644 partitionmanager/po/ru/partitionmanager.po create mode 100644 partitionmanager/po/sk/CMakeLists.txt create mode 100644 partitionmanager/po/sk/partitionmanager.po create mode 100644 partitionmanager/po/sl/CMakeLists.txt create mode 100644 partitionmanager/po/sl/partitionmanager.po create mode 100644 partitionmanager/po/sr/CMakeLists.txt create mode 100644 partitionmanager/po/sr/partitionmanager.po create mode 100644 partitionmanager/po/sr@ijekavian/CMakeLists.txt create mode 100644 partitionmanager/po/sr@ijekavian/partitionmanager.po create mode 100644 partitionmanager/po/sr@ijekavianlatin/CMakeLists.txt create mode 100644 partitionmanager/po/sr@ijekavianlatin/partitionmanager.po create mode 100644 partitionmanager/po/sr@latin/CMakeLists.txt create mode 100644 partitionmanager/po/sr@latin/partitionmanager.po create mode 100644 partitionmanager/po/sv/CMakeLists.txt create mode 100644 partitionmanager/po/sv/partitionmanager.po create mode 100644 partitionmanager/po/tr/CMakeLists.txt create mode 100644 partitionmanager/po/tr/partitionmanager.po create mode 100644 partitionmanager/po/uk/CMakeLists.txt create mode 100644 partitionmanager/po/uk/partitionmanager.po create mode 100644 partitionmanager/po/zh_CN/CMakeLists.txt create mode 100644 partitionmanager/po/zh_CN/partitionmanager.po create mode 100644 partitionmanager/po/zh_TW/CMakeLists.txt create mode 100644 partitionmanager/po/zh_TW/partitionmanager.po create mode 100755 partitionmanager/scripts/partitionmanagerapp.rb create mode 100755 partitionmanager/scripts/release.rb create mode 100644 partitionmanager/scripts/release/application.rb create mode 100644 partitionmanager/scripts/release/releasebuilder.rb create mode 100644 partitionmanager/scripts/release/releasecmd.rb create mode 100755 partitionmanager/scripts/release/releasedialog.rb create mode 100644 partitionmanager/scripts/release/releasedialog.ui create mode 100644 partitionmanager/scripts/release/tagger.rb create mode 100644 partitionmanager/scripts/release/translationstatsbuilder.rb create mode 100755 partitionmanager/scripts/releasegui.rb create mode 100644 partitionmanager/src/CMakeLists.txt create mode 100644 partitionmanager/src/Messages.sh create mode 100644 partitionmanager/src/backend/corebackend.cpp create mode 100644 partitionmanager/src/backend/corebackend.h create mode 100644 partitionmanager/src/backend/corebackenddevice.cpp create mode 100644 partitionmanager/src/backend/corebackenddevice.h create mode 100644 partitionmanager/src/backend/corebackendmanager.cpp create mode 100644 partitionmanager/src/backend/corebackendmanager.h create mode 100644 partitionmanager/src/backend/corebackendpartition.cpp create mode 100644 partitionmanager/src/backend/corebackendpartition.h create mode 100644 partitionmanager/src/backend/corebackendpartitiontable.cpp create mode 100644 partitionmanager/src/backend/corebackendpartitiontable.h create mode 100644 partitionmanager/src/config.kcfg create mode 100644 partitionmanager/src/config.kcfgc create mode 100644 partitionmanager/src/config/advancedpagewidget.cpp create mode 100644 partitionmanager/src/config/advancedpagewidget.h create mode 100644 partitionmanager/src/config/configureoptionsdialog.cpp create mode 100644 partitionmanager/src/config/configureoptionsdialog.h create mode 100644 partitionmanager/src/config/configurepageadvanced.ui create mode 100644 partitionmanager/src/config/configurepagefilesystemcolors.ui create mode 100644 partitionmanager/src/config/configurepagegeneral.ui create mode 100644 partitionmanager/src/config/filesystemcolorspagewidget.cpp create mode 100644 partitionmanager/src/config/filesystemcolorspagewidget.h create mode 100644 partitionmanager/src/config/generalpagewidget.cpp create mode 100644 partitionmanager/src/config/generalpagewidget.h create mode 100644 partitionmanager/src/core/copysource.cpp create mode 100644 partitionmanager/src/core/copysource.h create mode 100644 partitionmanager/src/core/copysourcedevice.cpp create mode 100644 partitionmanager/src/core/copysourcedevice.h create mode 100644 partitionmanager/src/core/copysourcefile.cpp create mode 100644 partitionmanager/src/core/copysourcefile.h create mode 100644 partitionmanager/src/core/copysourceshred.cpp create mode 100644 partitionmanager/src/core/copysourceshred.h create mode 100644 partitionmanager/src/core/copytarget.cpp create mode 100644 partitionmanager/src/core/copytarget.h create mode 100644 partitionmanager/src/core/copytargetdevice.cpp create mode 100644 partitionmanager/src/core/copytargetdevice.h create mode 100644 partitionmanager/src/core/copytargetfile.cpp create mode 100644 partitionmanager/src/core/copytargetfile.h create mode 100644 partitionmanager/src/core/device.cpp create mode 100644 partitionmanager/src/core/device.h create mode 100644 partitionmanager/src/core/devicescanner.cpp create mode 100644 partitionmanager/src/core/devicescanner.h create mode 100644 partitionmanager/src/core/mountentry.cpp create mode 100644 partitionmanager/src/core/mountentry.h create mode 100644 partitionmanager/src/core/operationrunner.cpp create mode 100644 partitionmanager/src/core/operationrunner.h create mode 100644 partitionmanager/src/core/operationstack.cpp create mode 100644 partitionmanager/src/core/operationstack.h create mode 100644 partitionmanager/src/core/partition.cpp create mode 100644 partitionmanager/src/core/partition.h create mode 100644 partitionmanager/src/core/partitionalignment.cpp create mode 100644 partitionmanager/src/core/partitionalignment.h create mode 100644 partitionmanager/src/core/partitionnode.cpp create mode 100644 partitionmanager/src/core/partitionnode.h create mode 100644 partitionmanager/src/core/partitionrole.cpp create mode 100644 partitionmanager/src/core/partitionrole.h create mode 100644 partitionmanager/src/core/partitiontable.cpp create mode 100644 partitionmanager/src/core/partitiontable.h create mode 100644 partitionmanager/src/core/smartattribute.cpp create mode 100644 partitionmanager/src/core/smartattribute.h create mode 100644 partitionmanager/src/core/smartstatus.cpp create mode 100644 partitionmanager/src/core/smartstatus.h create mode 100644 partitionmanager/src/fs/btrfs.cpp create mode 100644 partitionmanager/src/fs/btrfs.h create mode 100644 partitionmanager/src/fs/exfat.cpp create mode 100644 partitionmanager/src/fs/exfat.h create mode 100644 partitionmanager/src/fs/ext2.cpp create mode 100644 partitionmanager/src/fs/ext2.h create mode 100644 partitionmanager/src/fs/ext3.cpp create mode 100644 partitionmanager/src/fs/ext3.h create mode 100644 partitionmanager/src/fs/ext4.cpp create mode 100644 partitionmanager/src/fs/ext4.h create mode 100644 partitionmanager/src/fs/extended.cpp create mode 100644 partitionmanager/src/fs/extended.h create mode 100644 partitionmanager/src/fs/fat16.cpp create mode 100644 partitionmanager/src/fs/fat16.h create mode 100644 partitionmanager/src/fs/fat32.cpp create mode 100644 partitionmanager/src/fs/fat32.h create mode 100644 partitionmanager/src/fs/filesystem.cpp create mode 100644 partitionmanager/src/fs/filesystem.h create mode 100644 partitionmanager/src/fs/filesystemfactory.cpp create mode 100644 partitionmanager/src/fs/filesystemfactory.h create mode 100644 partitionmanager/src/fs/hfs.cpp create mode 100644 partitionmanager/src/fs/hfs.h create mode 100644 partitionmanager/src/fs/hfsplus.cpp create mode 100644 partitionmanager/src/fs/hfsplus.h create mode 100644 partitionmanager/src/fs/hpfs.cpp create mode 100644 partitionmanager/src/fs/hpfs.h create mode 100644 partitionmanager/src/fs/jfs.cpp create mode 100644 partitionmanager/src/fs/jfs.h create mode 100644 partitionmanager/src/fs/linuxswap.cpp create mode 100644 partitionmanager/src/fs/linuxswap.h create mode 100644 partitionmanager/src/fs/luks.cpp create mode 100644 partitionmanager/src/fs/luks.h create mode 100644 partitionmanager/src/fs/lvm2_pv.cpp create mode 100644 partitionmanager/src/fs/lvm2_pv.h create mode 100644 partitionmanager/src/fs/nilfs2.cpp create mode 100644 partitionmanager/src/fs/nilfs2.h create mode 100644 partitionmanager/src/fs/ntfs.cpp create mode 100644 partitionmanager/src/fs/ntfs.h create mode 100644 partitionmanager/src/fs/ocfs2.cpp create mode 100644 partitionmanager/src/fs/ocfs2.h create mode 100644 partitionmanager/src/fs/reiser4.cpp create mode 100644 partitionmanager/src/fs/reiser4.h create mode 100644 partitionmanager/src/fs/reiserfs.cpp create mode 100644 partitionmanager/src/fs/reiserfs.h create mode 100644 partitionmanager/src/fs/ufs.cpp create mode 100644 partitionmanager/src/fs/ufs.h create mode 100644 partitionmanager/src/fs/unformatted.cpp create mode 100644 partitionmanager/src/fs/unformatted.h create mode 100644 partitionmanager/src/fs/unknown.cpp create mode 100644 partitionmanager/src/fs/unknown.h create mode 100644 partitionmanager/src/fs/xfs.cpp create mode 100644 partitionmanager/src/fs/xfs.h create mode 100644 partitionmanager/src/fs/zfs.cpp create mode 100644 partitionmanager/src/fs/zfs.h create mode 100644 partitionmanager/src/gui/applyprogressdetailswidget.cpp create mode 100644 partitionmanager/src/gui/applyprogressdetailswidget.h create mode 100644 partitionmanager/src/gui/applyprogressdetailswidgetbase.ui create mode 100644 partitionmanager/src/gui/applyprogressdialog.cpp create mode 100644 partitionmanager/src/gui/applyprogressdialog.h create mode 100644 partitionmanager/src/gui/applyprogressdialogwidget.cpp create mode 100644 partitionmanager/src/gui/applyprogressdialogwidget.h create mode 100644 partitionmanager/src/gui/applyprogressdialogwidgetbase.ui create mode 100644 partitionmanager/src/gui/createpartitiontabledialog.cpp create mode 100644 partitionmanager/src/gui/createpartitiontabledialog.h create mode 100644 partitionmanager/src/gui/createpartitiontablewidget.cpp create mode 100644 partitionmanager/src/gui/createpartitiontablewidget.h create mode 100644 partitionmanager/src/gui/createpartitiontablewidgetbase.ui create mode 100644 partitionmanager/src/gui/decryptluksdialog.cpp create mode 100644 partitionmanager/src/gui/decryptluksdialog.h create mode 100644 partitionmanager/src/gui/decryptluksdialogwidget.cpp create mode 100644 partitionmanager/src/gui/decryptluksdialogwidget.h create mode 100644 partitionmanager/src/gui/decryptluksdialogwidgetbase.ui create mode 100644 partitionmanager/src/gui/devicepropsdialog.cpp create mode 100644 partitionmanager/src/gui/devicepropsdialog.h create mode 100644 partitionmanager/src/gui/devicepropswidget.cpp create mode 100644 partitionmanager/src/gui/devicepropswidget.h create mode 100644 partitionmanager/src/gui/devicepropswidgetbase.ui create mode 100644 partitionmanager/src/gui/editmountoptionsdialog.cpp create mode 100644 partitionmanager/src/gui/editmountoptionsdialog.h create mode 100644 partitionmanager/src/gui/editmountoptionsdialogwidget.cpp create mode 100644 partitionmanager/src/gui/editmountoptionsdialogwidget.h create mode 100644 partitionmanager/src/gui/editmountoptionsdialogwidgetbase.ui create mode 100644 partitionmanager/src/gui/editmountpointdialog.cpp create mode 100644 partitionmanager/src/gui/editmountpointdialog.h create mode 100644 partitionmanager/src/gui/editmountpointdialogwidget.cpp create mode 100644 partitionmanager/src/gui/editmountpointdialogwidget.h create mode 100644 partitionmanager/src/gui/editmountpointdialogwidgetbase.ui create mode 100644 partitionmanager/src/gui/filesystemsupportdialog.cpp create mode 100644 partitionmanager/src/gui/filesystemsupportdialog.h create mode 100644 partitionmanager/src/gui/filesystemsupportdialogwidget.cpp create mode 100644 partitionmanager/src/gui/filesystemsupportdialogwidget.h create mode 100644 partitionmanager/src/gui/filesystemsupportdialogwidgetbase.ui create mode 100644 partitionmanager/src/gui/formattedspinbox.cpp create mode 100644 partitionmanager/src/gui/formattedspinbox.h create mode 100644 partitionmanager/src/gui/infopane.cpp create mode 100644 partitionmanager/src/gui/infopane.h create mode 100644 partitionmanager/src/gui/insertdialog.cpp create mode 100644 partitionmanager/src/gui/insertdialog.h create mode 100644 partitionmanager/src/gui/listdevices.cpp create mode 100644 partitionmanager/src/gui/listdevices.h create mode 100644 partitionmanager/src/gui/listdevicesbase.ui create mode 100644 partitionmanager/src/gui/listoperations.cpp create mode 100644 partitionmanager/src/gui/listoperations.h create mode 100644 partitionmanager/src/gui/listoperationsbase.ui create mode 100644 partitionmanager/src/gui/mainwindow.cpp create mode 100644 partitionmanager/src/gui/mainwindow.h create mode 100644 partitionmanager/src/gui/mainwindowbase.ui create mode 100644 partitionmanager/src/gui/newdialog.cpp create mode 100644 partitionmanager/src/gui/newdialog.h create mode 100644 partitionmanager/src/gui/partitionmanagerui.rc create mode 100644 partitionmanager/src/gui/partitionmanagerwidget.cpp create mode 100644 partitionmanager/src/gui/partitionmanagerwidget.h create mode 100644 partitionmanager/src/gui/partitionmanagerwidgetbase.ui create mode 100644 partitionmanager/src/gui/partpropsdialog.cpp create mode 100644 partitionmanager/src/gui/partpropsdialog.h create mode 100644 partitionmanager/src/gui/partpropswidget.h create mode 100644 partitionmanager/src/gui/partpropswidgetbase.ui create mode 100644 partitionmanager/src/gui/partresizerwidget.cpp create mode 100644 partitionmanager/src/gui/partresizerwidget.h create mode 100644 partitionmanager/src/gui/parttablewidget.cpp create mode 100644 partitionmanager/src/gui/parttablewidget.h create mode 100644 partitionmanager/src/gui/partwidget.cpp create mode 100644 partitionmanager/src/gui/partwidget.h create mode 100644 partitionmanager/src/gui/partwidgetbase.cpp create mode 100644 partitionmanager/src/gui/partwidgetbase.h create mode 100644 partitionmanager/src/gui/resizedialog.cpp create mode 100644 partitionmanager/src/gui/resizedialog.h create mode 100644 partitionmanager/src/gui/scanprogressdialog.cpp create mode 100644 partitionmanager/src/gui/scanprogressdialog.h create mode 100644 partitionmanager/src/gui/sizedetailswidget.cpp create mode 100644 partitionmanager/src/gui/sizedetailswidget.h create mode 100644 partitionmanager/src/gui/sizedetailswidgetbase.ui create mode 100644 partitionmanager/src/gui/sizedialogbase.cpp create mode 100644 partitionmanager/src/gui/sizedialogbase.h create mode 100644 partitionmanager/src/gui/sizedialogwidget.cpp create mode 100644 partitionmanager/src/gui/sizedialogwidget.h create mode 100644 partitionmanager/src/gui/sizedialogwidgetbase.ui create mode 100644 partitionmanager/src/gui/smartdialog.cpp create mode 100644 partitionmanager/src/gui/smartdialog.h create mode 100644 partitionmanager/src/gui/smartdialogwidget.cpp create mode 100644 partitionmanager/src/gui/smartdialogwidget.h create mode 100644 partitionmanager/src/gui/smartdialogwidgetbase.ui create mode 100644 partitionmanager/src/gui/treelog.cpp create mode 100644 partitionmanager/src/gui/treelog.h create mode 100644 partitionmanager/src/gui/treelogbase.ui create mode 100644 partitionmanager/src/jobs/backupfilesystemjob.cpp create mode 100644 partitionmanager/src/jobs/backupfilesystemjob.h create mode 100644 partitionmanager/src/jobs/checkfilesystemjob.cpp create mode 100644 partitionmanager/src/jobs/checkfilesystemjob.h create mode 100644 partitionmanager/src/jobs/copyfilesystemjob.cpp create mode 100644 partitionmanager/src/jobs/copyfilesystemjob.h create mode 100644 partitionmanager/src/jobs/createfilesystemjob.cpp create mode 100644 partitionmanager/src/jobs/createfilesystemjob.h create mode 100644 partitionmanager/src/jobs/createpartitionjob.cpp create mode 100644 partitionmanager/src/jobs/createpartitionjob.h create mode 100644 partitionmanager/src/jobs/createpartitiontablejob.cpp create mode 100644 partitionmanager/src/jobs/createpartitiontablejob.h create mode 100644 partitionmanager/src/jobs/deletefilesystemjob.cpp create mode 100644 partitionmanager/src/jobs/deletefilesystemjob.h create mode 100644 partitionmanager/src/jobs/deletepartitionjob.cpp create mode 100644 partitionmanager/src/jobs/deletepartitionjob.h create mode 100644 partitionmanager/src/jobs/job.cpp create mode 100644 partitionmanager/src/jobs/job.h create mode 100644 partitionmanager/src/jobs/movefilesystemjob.cpp create mode 100644 partitionmanager/src/jobs/movefilesystemjob.h create mode 100644 partitionmanager/src/jobs/resizefilesystemjob.cpp create mode 100644 partitionmanager/src/jobs/resizefilesystemjob.h create mode 100644 partitionmanager/src/jobs/restorefilesystemjob.cpp create mode 100644 partitionmanager/src/jobs/restorefilesystemjob.h create mode 100644 partitionmanager/src/jobs/setfilesystemlabeljob.cpp create mode 100644 partitionmanager/src/jobs/setfilesystemlabeljob.h create mode 100644 partitionmanager/src/jobs/setpartflagsjob.cpp create mode 100644 partitionmanager/src/jobs/setpartflagsjob.h create mode 100644 partitionmanager/src/jobs/setpartgeometryjob.cpp create mode 100644 partitionmanager/src/jobs/setpartgeometryjob.h create mode 100644 partitionmanager/src/jobs/shredfilesystemjob.cpp create mode 100644 partitionmanager/src/jobs/shredfilesystemjob.h create mode 100644 partitionmanager/src/main.cpp create mode 100644 partitionmanager/src/ops/backupoperation.cpp create mode 100644 partitionmanager/src/ops/backupoperation.h create mode 100644 partitionmanager/src/ops/checkoperation.cpp create mode 100644 partitionmanager/src/ops/checkoperation.h create mode 100644 partitionmanager/src/ops/copyoperation.cpp create mode 100644 partitionmanager/src/ops/copyoperation.h create mode 100644 partitionmanager/src/ops/createfilesystemoperation.cpp create mode 100644 partitionmanager/src/ops/createfilesystemoperation.h create mode 100644 partitionmanager/src/ops/createpartitiontableoperation.cpp create mode 100644 partitionmanager/src/ops/createpartitiontableoperation.h create mode 100644 partitionmanager/src/ops/deleteoperation.cpp create mode 100644 partitionmanager/src/ops/deleteoperation.h create mode 100644 partitionmanager/src/ops/newoperation.cpp create mode 100644 partitionmanager/src/ops/newoperation.h create mode 100644 partitionmanager/src/ops/operation.cpp create mode 100644 partitionmanager/src/ops/operation.h create mode 100644 partitionmanager/src/ops/resizeoperation.cpp create mode 100644 partitionmanager/src/ops/resizeoperation.h create mode 100644 partitionmanager/src/ops/restoreoperation.cpp create mode 100644 partitionmanager/src/ops/restoreoperation.h create mode 100644 partitionmanager/src/ops/setfilesystemlabeloperation.cpp create mode 100644 partitionmanager/src/ops/setfilesystemlabeloperation.h create mode 100644 partitionmanager/src/ops/setpartflagsoperation.cpp create mode 100644 partitionmanager/src/ops/setpartflagsoperation.h create mode 100644 partitionmanager/src/org.kde.PartitionManager.xml create mode 100644 partitionmanager/src/partitionmanager.appdata.xml create mode 100644 partitionmanager/src/partitionmanager.desktop create mode 100644 partitionmanager/src/plugins/CMakeLists.txt create mode 100644 partitionmanager/src/plugins/dummy/CMakeLists.txt create mode 100644 partitionmanager/src/plugins/dummy/dummybackend.cpp create mode 100644 partitionmanager/src/plugins/dummy/dummybackend.h create mode 100644 partitionmanager/src/plugins/dummy/dummydevice.cpp create mode 100644 partitionmanager/src/plugins/dummy/dummydevice.h create mode 100644 partitionmanager/src/plugins/dummy/dummypartition.cpp create mode 100644 partitionmanager/src/plugins/dummy/dummypartition.h create mode 100644 partitionmanager/src/plugins/dummy/dummypartitiontable.cpp create mode 100644 partitionmanager/src/plugins/dummy/dummypartitiontable.h create mode 100644 partitionmanager/src/plugins/dummy/pmdummybackendplugin.desktop create mode 100644 partitionmanager/src/plugins/libparted/CMakeLists.txt create mode 100644 partitionmanager/src/plugins/libparted/libpartedbackend.cpp create mode 100644 partitionmanager/src/plugins/libparted/libpartedbackend.h create mode 100644 partitionmanager/src/plugins/libparted/libparteddevice.cpp create mode 100644 partitionmanager/src/plugins/libparted/libparteddevice.h create mode 100644 partitionmanager/src/plugins/libparted/libpartedpartition.cpp create mode 100644 partitionmanager/src/plugins/libparted/libpartedpartition.h create mode 100644 partitionmanager/src/plugins/libparted/libpartedpartitiontable.cpp create mode 100644 partitionmanager/src/plugins/libparted/libpartedpartitiontable.h create mode 100644 partitionmanager/src/plugins/libparted/pmlibpartedbackendplugin.desktop create mode 100644 partitionmanager/src/plugins/pmcorebackendplugin.desktop create mode 100644 partitionmanager/src/util/capacity.cpp create mode 100644 partitionmanager/src/util/capacity.h create mode 100644 partitionmanager/src/util/externalcommand.cpp create mode 100644 partitionmanager/src/util/externalcommand.h create mode 100644 partitionmanager/src/util/globallog.cpp create mode 100644 partitionmanager/src/util/globallog.h create mode 100644 partitionmanager/src/util/helpers.cpp create mode 100644 partitionmanager/src/util/helpers.h create mode 100644 partitionmanager/src/util/htmlreport.cpp create mode 100644 partitionmanager/src/util/htmlreport.h create mode 100644 partitionmanager/src/util/libpartitionmanagerexport.h create mode 100644 partitionmanager/src/util/report.cpp create mode 100644 partitionmanager/src/util/report.h create mode 100644 print-manager/CMakeLists.txt create mode 100644 print-manager/COPYING create mode 100644 print-manager/Messages.sh create mode 100644 print-manager/README create mode 100644 print-manager/add-printer/AddPrinter.cpp create mode 100644 print-manager/add-printer/AddPrinter.h create mode 100644 print-manager/add-printer/AddPrinterAssistant.cpp create mode 100644 print-manager/add-printer/AddPrinterAssistant.h create mode 100644 print-manager/add-printer/CMakeLists.txt create mode 100644 print-manager/add-printer/ChooseLpd.cpp create mode 100644 print-manager/add-printer/ChooseLpd.h create mode 100644 print-manager/add-printer/ChooseLpd.ui create mode 100644 print-manager/add-printer/ChooseSamba.cpp create mode 100644 print-manager/add-printer/ChooseSamba.h create mode 100644 print-manager/add-printer/ChooseSamba.ui create mode 100644 print-manager/add-printer/ChooseSerial.cpp create mode 100644 print-manager/add-printer/ChooseSerial.h create mode 100644 print-manager/add-printer/ChooseSerial.ui create mode 100644 print-manager/add-printer/ChooseSocket.cpp create mode 100644 print-manager/add-printer/ChooseSocket.h create mode 100644 print-manager/add-printer/ChooseSocket.ui create mode 100644 print-manager/add-printer/ChooseUri.cpp create mode 100644 print-manager/add-printer/ChooseUri.h create mode 100644 print-manager/add-printer/ChooseUri.ui create mode 100644 print-manager/add-printer/DevicesModel.cpp create mode 100644 print-manager/add-printer/DevicesModel.h create mode 100644 print-manager/add-printer/GenericPage.cpp create mode 100644 print-manager/add-printer/GenericPage.h create mode 100644 print-manager/add-printer/PageAddPrinter.cpp create mode 100644 print-manager/add-printer/PageAddPrinter.h create mode 100644 print-manager/add-printer/PageAddPrinter.ui create mode 100644 print-manager/add-printer/PageChoosePPD.cpp create mode 100644 print-manager/add-printer/PageChoosePPD.h create mode 100644 print-manager/add-printer/PageChoosePPD.ui create mode 100644 print-manager/add-printer/PageChoosePrinters.cpp create mode 100644 print-manager/add-printer/PageChoosePrinters.h create mode 100644 print-manager/add-printer/PageChoosePrinters.ui create mode 100644 print-manager/add-printer/PageDestinations.cpp create mode 100644 print-manager/add-printer/PageDestinations.h create mode 100644 print-manager/add-printer/PageDestinations.ui create mode 100644 print-manager/add-printer/main.cpp create mode 100644 print-manager/cmake/modules/FindCUPS.cmake create mode 100644 print-manager/config.h.cmake create mode 100644 print-manager/configure-printer/CMakeLists.txt create mode 100644 print-manager/configure-printer/ConfigureDialog.cpp create mode 100644 print-manager/configure-printer/ConfigureDialog.h create mode 100644 print-manager/configure-printer/ConfigurePrinter.cpp create mode 100644 print-manager/configure-printer/ConfigurePrinter.h create mode 100644 print-manager/configure-printer/ConfigurePrinterInterface.cpp create mode 100644 print-manager/configure-printer/ConfigurePrinterInterface.h create mode 100644 print-manager/configure-printer/ModifyPrinter.cpp create mode 100644 print-manager/configure-printer/ModifyPrinter.h create mode 100644 print-manager/configure-printer/ModifyPrinter.ui create mode 100644 print-manager/configure-printer/PrinterBehavior.cpp create mode 100644 print-manager/configure-printer/PrinterBehavior.h create mode 100644 print-manager/configure-printer/PrinterBehavior.ui create mode 100644 print-manager/configure-printer/PrinterOptions.cpp create mode 100644 print-manager/configure-printer/PrinterOptions.h create mode 100644 print-manager/configure-printer/PrinterOptions.ui create mode 100644 print-manager/configure-printer/PrinterPage.cpp create mode 100644 print-manager/configure-printer/PrinterPage.h create mode 100644 print-manager/configure-printer/main.cpp create mode 100644 print-manager/configure-printer/org.kde.ConfigurePrinter.service.in create mode 100644 print-manager/configure-printer/org.kde.ConfigurePrinter.xml create mode 100644 print-manager/declarative-plugins/CMakeLists.txt create mode 100644 print-manager/declarative-plugins/qmldir create mode 100644 print-manager/declarative-plugins/qmlplugins.cpp create mode 100644 print-manager/declarative-plugins/qmlplugins.h create mode 100644 print-manager/libkcups/CMakeLists.txt create mode 100644 print-manager/libkcups/ClassListWidget.cpp create mode 100644 print-manager/libkcups/ClassListWidget.h create mode 100644 print-manager/libkcups/JobModel.cpp create mode 100644 print-manager/libkcups/JobModel.h create mode 100644 print-manager/libkcups/JobSortFilterModel.cpp create mode 100644 print-manager/libkcups/JobSortFilterModel.h create mode 100644 print-manager/libkcups/KCupsConnection.cpp create mode 100644 print-manager/libkcups/KCupsConnection.h create mode 100644 print-manager/libkcups/KCupsJob.cpp create mode 100644 print-manager/libkcups/KCupsJob.h create mode 100644 print-manager/libkcups/KCupsPasswordDialog.cpp create mode 100644 print-manager/libkcups/KCupsPasswordDialog.h create mode 100644 print-manager/libkcups/KCupsPrinter.cpp create mode 100644 print-manager/libkcups/KCupsPrinter.h create mode 100644 print-manager/libkcups/KCupsRequest.cpp create mode 100644 print-manager/libkcups/KCupsRequest.h create mode 100644 print-manager/libkcups/KCupsServer.cpp create mode 100644 print-manager/libkcups/KCupsServer.h create mode 100644 print-manager/libkcups/KIppRequest.cpp create mode 100644 print-manager/libkcups/KIppRequest.h create mode 100644 print-manager/libkcups/KIppRequest_p.h create mode 100644 print-manager/libkcups/NoSelectionRectDelegate.cpp create mode 100644 print-manager/libkcups/NoSelectionRectDelegate.h create mode 100644 print-manager/libkcups/PPDModel.cpp create mode 100644 print-manager/libkcups/PPDModel.h create mode 100644 print-manager/libkcups/PrinterModel.cpp create mode 100644 print-manager/libkcups/PrinterModel.h create mode 100644 print-manager/libkcups/PrinterSortFilterModel.cpp create mode 100644 print-manager/libkcups/PrinterSortFilterModel.h create mode 100644 print-manager/libkcups/SelectMakeModel.cpp create mode 100644 print-manager/libkcups/SelectMakeModel.h create mode 100644 print-manager/libkcups/SelectMakeModel.ui create mode 100644 print-manager/plasmoid/CMakeLists.txt create mode 100644 print-manager/plasmoid/KPrintManagerConfigPlugin.cpp create mode 100644 print-manager/plasmoid/KPrintManagerConfigPlugin.h create mode 100644 print-manager/plasmoid/package/contents/config/main.xml create mode 100644 print-manager/plasmoid/package/contents/ui/CompactRepresentation.qml create mode 100644 print-manager/plasmoid/package/contents/ui/JobItem.qml create mode 100644 print-manager/plasmoid/package/contents/ui/NIHSwitch.qml create mode 100644 print-manager/plasmoid/package/contents/ui/PrinterItem.qml create mode 100644 print-manager/plasmoid/package/contents/ui/ScrollableListView.qml create mode 100644 print-manager/plasmoid/package/contents/ui/StatusView.qml create mode 100644 print-manager/plasmoid/package/contents/ui/config.ui create mode 100644 print-manager/plasmoid/package/contents/ui/printmanager.qml create mode 100644 print-manager/plasmoid/package/contents/ui/private/RoundShadow.qml create mode 100644 print-manager/plasmoid/package/metadata.desktop create mode 100644 print-manager/print-manager-kded/CMakeLists.txt create mode 100644 print-manager/print-manager-kded/NewPrinterNotification.cpp create mode 100644 print-manager/print-manager-kded/NewPrinterNotification.h create mode 100644 print-manager/print-manager-kded/PrintManagerKded.cpp create mode 100644 print-manager/print-manager-kded/PrintManagerKded.h create mode 100644 print-manager/print-manager-kded/com.redhat.NewPrinterNotification.xml create mode 100644 print-manager/print-manager-kded/printmanager.desktop create mode 100644 print-manager/print-manager-kded/printmanager.notifyrc create mode 100644 print-manager/printer-manager-kcm/CMakeLists.txt create mode 100644 print-manager/printer-manager-kcm/PrintKCM.cpp create mode 100644 print-manager/printer-manager-kcm/PrintKCM.h create mode 100644 print-manager/printer-manager-kcm/PrintKCM.ui create mode 100644 print-manager/printer-manager-kcm/PrinterDelegate.cpp create mode 100644 print-manager/printer-manager-kcm/PrinterDelegate.h create mode 100644 print-manager/printer-manager-kcm/PrinterDescription.cpp create mode 100644 print-manager/printer-manager-kcm/PrinterDescription.h create mode 100644 print-manager/printer-manager-kcm/PrinterDescription.ui create mode 100644 print-manager/printer-manager-kcm/kcm_printer_manager.desktop create mode 100644 print-manager/printqueue/CMakeLists.txt create mode 100644 print-manager/printqueue/PrintQueue.cpp create mode 100644 print-manager/printqueue/PrintQueue.h create mode 100644 print-manager/printqueue/PrintQueueUi.cpp create mode 100644 print-manager/printqueue/PrintQueueUi.h create mode 100644 print-manager/printqueue/PrintQueueUi.ui create mode 100644 print-manager/printqueue/main.cpp diff --git a/bluedevil/CMakeLists.txt b/bluedevil/CMakeLists.txt new file mode 100644 index 00000000..8f2d6e66 --- /dev/null +++ b/bluedevil/CMakeLists.txt @@ -0,0 +1,23 @@ +project(bluedevil) + +find_package(KDE4 REQUIRED) + +set(CMAKE_BLUEDEVIL_VERSION_MAJOR 2) +set(CMAKE_BLUEDEVIL_VERSION_MINOR 0) +set(CMAKE_BLUEDEVIL_VERSION_PATCH 0) +set(CMAKE_BLUEDEVIL_VERSION_STRING "${CMAKE_BLUEDEVIL_VERSION_MAJOR}.${CMAKE_BLUEDEVIL_VERSION_MINOR}.${CMAKE_BLUEDEVIL_VERSION_PATCH}") +configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h) + +include(KDE4Defaults) +include(MacroOptionalAddSubdirectory) + +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) +find_package(LibBlueDevil 2.0 REQUIRED) +find_package(SharedMimeInfo REQUIRED) + +include_directories(${KDE4_INCLUDES} ${LibBlueDevil_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +add_subdirectory(src) + +include(MacroOptionalAddSubdirectory) +macro_optional_add_subdirectory( po ) diff --git a/bluedevil/HACKING b/bluedevil/HACKING new file mode 100644 index 00000000..9bd088f5 --- /dev/null +++ b/bluedevil/HACKING @@ -0,0 +1,24 @@ +Please follow the bluedevil style before submitting patches. + +All copyright headers are unified with the following style: + +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 AuthorName Surname Surname * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ diff --git a/bluedevil/README b/bluedevil/README new file mode 100644 index 00000000..b701b77d --- /dev/null +++ b/bluedevil/README @@ -0,0 +1,14 @@ +BlueDevil runtime dependencies: + -obex-data-server + -Be able to "Browse File" aka kio_obexftp + -Be able to receive files + + -obexd-client + -Be able to Send files (bluedevil-sendfile) + + -If you're using Alsa: + The best way of having "automagical headset configuration" is having the bluetooth.conf hook + enabled + + NOTE: Be sure to install only obexd-client and not obexd-server, since the last will conflic + with obex-data-server. \ No newline at end of file diff --git a/bluedevil/bluedevil.kdev4 b/bluedevil/bluedevil.kdev4 new file mode 100644 index 00000000..3f9649ef --- /dev/null +++ b/bluedevil/bluedevil.kdev4 @@ -0,0 +1,3 @@ +[Project] +Manager=KDevCMakeManager +Name=bluedevil diff --git a/bluedevil/cmake/CMakeLists.txt b/bluedevil/cmake/CMakeLists.txt new file mode 100644 index 00000000..38a9971a --- /dev/null +++ b/bluedevil/cmake/CMakeLists.txt @@ -0,0 +1,3 @@ + +add_subdirectory(modules) + diff --git a/bluedevil/cmake/modules/CMakeLists.txt b/bluedevil/cmake/modules/CMakeLists.txt new file mode 100644 index 00000000..2bba4c03 --- /dev/null +++ b/bluedevil/cmake/modules/CMakeLists.txt @@ -0,0 +1,8 @@ +# install the cmake files + +set(cmakeFiles PkgConfigGetVar.cmake) + +set(module_install_dir ${DATA_INSTALL_DIR}/cmake/modules ) + +install( FILES ${cmakeFiles} DESTINATION ${module_install_dir} ) + diff --git a/bluedevil/cmake/modules/FindLibBlueDevil.cmake b/bluedevil/cmake/modules/FindLibBlueDevil.cmake new file mode 100644 index 00000000..9c43c06b --- /dev/null +++ b/bluedevil/cmake/modules/FindLibBlueDevil.cmake @@ -0,0 +1,38 @@ +# Find LibBlueDevil includes and library +# +# This module defines +# LibBlueDevil_INCLUDE_DIR +# LibBlueDevil_LIBRARIES, the libraries to link against to use LibBlueDevil. +# LibBlueDevil_FOUND, If false, do not try to use LibBlueDevil +# +# Copyright 2010, Aleix Pol Gonzalez +# +# Redistribution and use is allowed according to the terms of the BSD license. + +IF (LibBlueDevil_LIBRARIES AND LibBlueDevil_INCLUDE_DIR) + SET(LibBlueDevil_FIND_QUIETLY TRUE) # Already in cache, be silent +ENDIF (LibBlueDevil_LIBRARIES AND LibBlueDevil_INCLUDE_DIR) + +MESSAGE(STATUS "Looking for LibBlueDevil") +# find_path(LibBlueDevil_INCLUDE_DIR bluedevilmanager.h PATH_SUFFIXES bluedevil) +find_path(LibBlueDevil_INCLUDE_DIR bluedevil/bluedevilmanager.h) #apol's NOTE: the one above looks better but doesn't fit the way you work +find_library(LibBlueDevil_LIBRARIES bluedevil) + +MARK_AS_ADVANCED(LibBlueDevil_INCLUDE_DIR) +MARK_AS_ADVANCED(LibBlueDevil_LIBRARIES) + +IF (LibBlueDevil_INCLUDE_DIR AND LibBlueDevil_LIBRARIES) + SET(LibBlueDevil_FOUND TRUE) +ENDIF (LibBlueDevil_INCLUDE_DIR AND LibBlueDevil_LIBRARIES) + +IF (LibBlueDevil_FOUND) + IF (NOT LibBlueDevil_FIND_QUIETLY) + MESSAGE(STATUS "Found LibBlueDevil") + MESSAGE(STATUS " libraries : ${LibBlueDevil_LIBRARIES}") + MESSAGE(STATUS " includes : ${LibBlueDevil_INCLUDE_DIR}") + ENDIF (NOT LibBlueDevil_FIND_QUIETLY) +ELSE (LibBlueDevil_FOUND) + IF (LibBlueDevil_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find LibBlueDevil") + ENDIF (LibBlueDevil_FIND_REQUIRED) +ENDIF (LibBlueDevil_FOUND) diff --git a/bluedevil/cmake/modules/PkgConfigGetVar.cmake b/bluedevil/cmake/modules/PkgConfigGetVar.cmake new file mode 100644 index 00000000..7d03512e --- /dev/null +++ b/bluedevil/cmake/modules/PkgConfigGetVar.cmake @@ -0,0 +1,32 @@ +include(UsePkgConfig) + +MACRO(PKGCONFIG_GETVAR _package _var _output_variable) + SET(${_output_variable}) + + # if pkg-config has been found + IF(PKGCONFIG_EXECUTABLE) + + EXEC_PROGRAM(${PKGCONFIG_EXECUTABLE} ARGS ${_package} --exists RETURN_VALUE _return_VALUE OUTPUT_VARIABLE _pkgconfigDevNull ) + + # and if the package of interest also exists for pkg-config, then get the information + IF(NOT _return_VALUE) + + EXEC_PROGRAM(${PKGCONFIG_EXECUTABLE} ARGS ${_package} --variable ${_var} OUTPUT_VARIABLE ${_output_variable} ) + + ENDIF(NOT _return_VALUE) + + ENDIF(PKGCONFIG_EXECUTABLE) + +ENDMACRO(PKGCONFIG_GETVAR _package _var _output_variable) + +macro(dbus_add_activation_service _sources) + #PKGCONFIG_GETVAR(dbus-1 session_bus_services_dir _install_dir) + foreach (_i ${_sources}) + get_filename_component(_service_file ${_i} ABSOLUTE) + string(REGEX REPLACE "\\.service.*$" ".service" _output_file ${_i}) + set(_target ${CMAKE_CURRENT_BINARY_DIR}/${_output_file}) + configure_file(${_service_file} ${_target}) + install(FILES ${_target} DESTINATION ${DBUS_SERVICES_INSTALL_DIR} ) + #install(FILES ${_target} DESTINATION ${_install_dir}) + endforeach (_i ${ARGN}) +endmacro(dbus_add_activation_service _sources) diff --git a/bluedevil/po/CMakeLists.txt b/bluedevil/po/CMakeLists.txt new file mode 100644 index 00000000..35607893 --- /dev/null +++ b/bluedevil/po/CMakeLists.txt @@ -0,0 +1,63 @@ +# The pofiles macro creates in some versions same name targets +# which since cmake 2.8 leads to target clashes. +# Hence force the old policy for all po directories. +# http://public.kitware.com/Bug/view.php?id=12952 +cmake_policy(SET CMP0002 OLD) + +find_package(Gettext REQUIRED) +if (NOT GETTEXT_MSGMERGE_EXECUTABLE) +MESSAGE(FATAL_ERROR "Please install msgmerge binary") +endif (NOT GETTEXT_MSGMERGE_EXECUTABLE) +if (NOT GETTEXT_MSGFMT_EXECUTABLE) +MESSAGE(FATAL_ERROR "Please install msgmerge binary") +endif (NOT GETTEXT_MSGFMT_EXECUTABLE) +add_subdirectory(uk) +add_subdirectory(zh_CN) +add_subdirectory(zh_TW) +add_subdirectory(it) +add_subdirectory(da) +add_subdirectory(eo) +add_subdirectory(sr@latin) +add_subdirectory(pt) +add_subdirectory(pt_BR) +add_subdirectory(ca@valencia) +add_subdirectory(sk) +add_subdirectory(cs) +add_subdirectory(eu) +add_subdirectory(en_GB) +add_subdirectory(gl) +add_subdirectory(pl) +add_subdirectory(nds) +add_subdirectory(hu) +add_subdirectory(de) +add_subdirectory(et) +add_subdirectory(sr@ijekavian) +add_subdirectory(pa) +add_subdirectory(tr) +add_subdirectory(mai) +add_subdirectory(lt) +add_subdirectory(ru) +add_subdirectory(th) +add_subdirectory(ms) +add_subdirectory(nb) +add_subdirectory(sr@ijekavianlatin) +add_subdirectory(ug) +add_subdirectory(fr) +add_subdirectory(km) +add_subdirectory(sr) +add_subdirectory(ro) +add_subdirectory(ar) +add_subdirectory(mr) +add_subdirectory(fa) +add_subdirectory(bs) +add_subdirectory(ja) +add_subdirectory(es) +add_subdirectory(ko) +add_subdirectory(nl) +add_subdirectory(sv) +add_subdirectory(sl) +add_subdirectory(ca) +add_subdirectory(fi) +add_subdirectory(ga) +add_subdirectory(el) +add_subdirectory(kk) diff --git a/bluedevil/po/ar/CMakeLists.txt b/bluedevil/po/ar/CMakeLists.txt new file mode 100644 index 00000000..1d2d8ee8 --- /dev/null +++ b/bluedevil/po/ar/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(ar ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/ar/bluedevil.po b/bluedevil/po/ar/bluedevil.po new file mode 100644 index 00000000..af04376b --- /dev/null +++ b/bluedevil/po/ar/bluedevil.po @@ -0,0 +1,919 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# Zayed Al-Saidi , 2012. +# Abdalrahim G. Fakhouri , 2012. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2012-01-22 19:05+0300\n" +"Last-Translator: Abdalrahim G. Fakhouri \n" +"Language-Team: Arabic \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" +"X-Generator: Virtaal 0.7.0\n" +"X-Project-Style: kde\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "زايد السعيدي" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "zayed.alsaidi@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "يطلب %1 الوصول إلى هذا الحاسوب" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "ثقة وتصريح" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "تصريح فقط" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "امنع" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "نظام بلوتوث كدي" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "تغيير نمط بلوتوث إلى '%1'؟" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "تأكيد" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "امنع" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 يسأل إذا كان رمز PIN صحيح: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "رمز PIN صحيح" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "رمز PIN غير صحيح" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "رمز PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "رمز PIN مطلوب للاقتران ب %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "أدخل رمز PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"من أجل أن تقرن هذا الحاسوب مع %1 ، فستحتاج أن تدخل رمز PIN. الرجاء إدخاله في " +"الأسفل." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "أدخل رمز PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "مراقب بلوتوث" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "المشرف" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "أجهزة بلوتوث" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "يرسل لك %1 ملف %2 ؟" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "اقبل" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "ألغ" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "استقبال الملف عبر البلوتوث" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "من" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "إلى" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "أرسل بالبلوتوث" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "أعثر على جهاز..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "أخرى..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "مخفي" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "مرئي دائما" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "مرئي مؤقتا" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] " 0" +msgstr[1] "دقيقة واحدة" +msgstr[2] "دقيقتين" +msgstr[3] "%1 دقائق" +msgstr[4] "%1 دقيقة" +msgstr[5] "%1 دقيقة" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "الاسم" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "موصل بالطاقة" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "الرؤية" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "وقت الاكتشاف" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "المحول المبدئي: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "المحول: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] " 0" +msgstr[1] "دقيقة واحدة" +msgstr[2] "دقيقتين" +msgstr[3] "%1 دقائق" +msgstr[4] "%1 دقيقة" +msgstr[5] "%1 دقيقة" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "محولات البلوتوث" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "وحدة التحكم بمحولات البلوتوث" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "المطوّر و المسؤول عن الصيانة" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "لم يعثر على محول ، فضلاً صِل واحِداً." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "فعل تكامل البلوتوث وكدي" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "أجهزة بلوتوث" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "وحدة التحكم بأجهزة بلوتوث" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "التفاصيل" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "أزل" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "اتصل" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "اقطع الاتصال" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "أضف جهاز..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "اتصل" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "اختر كنية جديدة لـ %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "هل أنت متأكد من أنك تريد حذف الجهاز \"%1\" من قائمة الأجهزة المعروفة؟" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "إزالة جهاز" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "لم يضف أي جهاز بعيد " + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "انقر هنا لإضافة جهاز بعيد" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "مجهول" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "هاتف" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "مودم" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "حاسوب" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "شبكة" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "سماعات رأس" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "سماعات الأذن" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "صوت" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "لوحة المفاتيح" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "فأرة" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "آلة تصوير" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "طابعة" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "عصى ألعاب" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "حاسوب لوحي" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "النوع: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "نقل بلوتوث" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "وحدة التحكم بنقل بلوتوث" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "لا مطلقا" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "الأجهزة الموثوقة" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "جميع الأجهزة" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "لا تستعمل" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "دائما" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "قراءة فقط" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "تعديل و قراءة" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "الاسم" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "الكنية" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "العنوان" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "مقترن" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "محظور" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "موثوق" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "لم يعثر على أي محول بلوتوث." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "أصلح المشكلة" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "محول البلوتوث المبدئي غير مرئي للأجهزة البعيدة." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "التفاعل مع نظام البلوتوث غير ليس الأفضل." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "لم يتم تفعيل البلوتوث بشكل كامل." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "يستقبل" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "احفظ الملفات في:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "أقبل آليا:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "الملفات الواردة:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "مشاركة" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "شارك الملفات:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "اطلب PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "الأذون:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "الملفات المشتركة" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "مجلد مشترك" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "أرسل ملف" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "تصفح الملفات" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "جلب الخدمات..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "البحث عن الأجهزة الجديدة..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "مراقب ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "جلب المعلومات من الجهاز البعيد..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "يتصل بالجهاز" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "بلوتوث" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "المطور" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "تصفح الأجهزة" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "أرسل ملفات" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "بلوتوث مغلق" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "شغل بلوتوث" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "تصفح الأجهزة" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "الأجهزة المعروفة" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "أضف جهاز" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "اضبط البلوتوث" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "قابل للاكتشاف" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "أغلق البلوتوث" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "لم يتم توصيل شيء" +msgstr[1] "جهاز واحد متصل" +msgstr[2] "جهازين متصلين" +msgstr[3] "%1 أجهزة متصلة" +msgstr[4] "%1 جهازا متصلا" +msgstr[5] "%1 جهاز متصل" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "لم يعثر على محوّل" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "بلوتوث مشغل" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "بلوتوث مغلق" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "اتصل" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "مساعد إرسال الملفات بالبلوتوث" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "معرف الجهاز الذي ترغب أن ترسل الملفات له" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "الملفات التي سترسل" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "الاتصال بـ: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "جاري الاتصال بِــ %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "اختر ملفًّا أو أكثر:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "الملفات المحددة: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "اختر جهاز من القائمة:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "يبحث" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "إرسال الملفات عبر البلوتوث" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "إرسال الملفات بالبلوتوث" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "مكن أو عطل استقبال الملفات" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "احفظ الملفات الواردة في:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "السماح بتعديل الملفات الواردة" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "مكن أو عطل مشاركة الملفات" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "طلب رمز PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "السماح للأجهزة الخارجية بتعديل الملفات المشتركة" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "مكن أو عطل التكامل العام لبلوتوث كدي" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "معالج أجهزة بلوتوث" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "التالي" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "انهِ" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "معالج بلوتوث" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "الجهاز الذي سيقترن به" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "يبحث..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "رمز PIN يدوي:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "اختر جهاز" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "أعد تشغيل المعالج" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "أغلق" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "فشل في إعداد الجهاز" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "تثبيت %1 فشل" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "فضلا اكتب رمز PIN على لوحة المفاتيح عندما يظهر ، ثم اضغط زر الإدخال" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "فضلا اكتب رمز PIN على الجهاز عندما يظهر" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "جاري الاتصال بِــ %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "الاتصال بـ: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "المتطابقات" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "لم تتطابق" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "فضلا أكد أن الرمز PIN المعروض في \"%1\" يطابق الموجود في المعالج" \ No newline at end of file diff --git a/bluedevil/po/bs/CMakeLists.txt b/bluedevil/po/bs/CMakeLists.txt new file mode 100644 index 00000000..975b7406 --- /dev/null +++ b/bluedevil/po/bs/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(bs ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/bs/bluedevil.po b/bluedevil/po/bs/bluedevil.po new file mode 100644 index 00000000..6b0b998d --- /dev/null +++ b/bluedevil/po/bs/bluedevil.po @@ -0,0 +1,964 @@ +# Bosnian translation for bluedevil +# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010 +# This file is distributed under the same license as the bluedevil package. +# FIRST AUTHOR , 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2012-10-06 21:13+0000\n" +"Last-Translator: Samir Ribić \n" +"Language-Team: Bosnian \n" +"Language: bs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Launchpad-Export-Date: 2012-12-21 01:44+0000\n" +"X-Generator: Launchpad (build 16378)\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Marko Lalic,Samir Ribić" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "markolalic@etf.unsa.ba,samir.ribic@etf.unsa.ba" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 zahtijeva pristup ovom računaru" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Vjeruj i dozvoli" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Samo dozvoli" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Zabrani" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE Bluetooth System" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Promijeni Bluetooth režim u '%1'?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Potvrdi" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Odbij" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 pita da li je PIN tačan: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN je tačan" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN nije tačan" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN je potreban da bi se uparilo sa %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Uvedi PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Da biste povezali ovaj računar sa %1, morate unijeti PIN. Uradite to dolje." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Uvedi PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Bluetooth Daemon" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Održava" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetooth Uređaji" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 Vam šalje datoteku %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Prihvati" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Otkaži" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Prijem datoteka preko Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Od" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Za" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Šalji preko Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +#, fuzzy +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Dodaj uređaj..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +#, fuzzy +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Ostalo" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Skriven" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Uvijek vidljiv" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Privremeno vidljiv" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 minuta" +msgstr[1] "%1 minute" +msgstr[2] "%1 minuta" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Ime" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Napaja se" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Vidljivost" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Vrijeme za koje će biti moguće otkriti adapter" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Podrazumijevani adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 minuta" +msgstr[1] "%1 minute" +msgstr[2] "%1 minuta" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetooth adapteri" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Bluetooth Adapters Control Panel Module" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Programer i održavalac" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Ni jedan adapter nije pronađen. Molimo priključite jedan." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Omogući KDE bluetooth integraciju" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth Uređaji" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Bluetooth Devices Control Panel Module" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detalji" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Ukloni" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Poveži se" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Prekini vezu" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Dodaj uređaj..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Poveži se" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Odaberi novi alias za %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Jeste li sigurni da želite izbrisati uređaj \"%1\" iz liste poznatih uređaja?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Brisanje uređaja" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Nema dodanih udaljenih uređaja" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Kliknite ovdje da dodate udaljeni uređaj" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Nepoznat" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Računar" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Mreža" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Slušalice" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Slušalice" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Zvuk" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Tastatura" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Miš" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Štampač" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablet" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tip: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Transfer preko bluetootha" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Modul upravljačkeploče za Bluetooth prijenos" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Nikad" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Za uređaje kojim se vjeruje" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Za sve uređaje" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Nikada" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Uvijek" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Samo za čitanje" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Izmjena i čitanje" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Ime" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adresa" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Uparen" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blokiran" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Povjerljiv" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Ni jedan bluetooth adapter nije pronađen." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Popravi to" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Vaš podrazumijevani Bluetooth adapter nije vidljiv drugim uređajima." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Interakcija sa bluetooth sistemom nije optimalna." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth nije upotpunosti omogućen" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Prijem" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Snimi datoteke u:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Prihvati automatski:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Primi datoteke:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Dijeljenje" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Dijeli datoteke:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Zahtjevaj PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Dozvole:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Dijeljene datoteke" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Dijeljeni direktorij" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Pošalji datoteku" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Pregled datoteka" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Preuzimam usluge..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +#, fuzzy +msgid "Scanning for new devices..." +msgstr "Skeniram za druge uređaje..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp Daemon" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Preuzimam informacije od uređaja" + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Povezujem se s uređajem" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Programer" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Pregledaj uređaje" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Pošalji datoteke" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth je isključen" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Uključi Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Pregledaj uređaje" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Poznati uređaji" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Dodaj uređaj" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Konfiguriši Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Za otkrivanje" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Isključi Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 povezan uređaj" +msgstr[1] "%1 povezana uređaja" +msgstr[2] "%1 povezanih uređaja" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Ni jedan adapter nije pronađen" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth je uključen" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth je isključen" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Poveži se" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Pomoć za slanje datoteka preko bluetootha" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID uređaja gdje se datoteke šalju" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Datoteke koje se šalju" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Spajam se na: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Povezujem se na %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Odaberite jednu ili više datoteka" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Izabrane datoteke: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Izaberite uređaj iz liste:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Skeniranje" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Slanje datoteke preko Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Slanje datoteka preko bluetootha" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Omogući ili onemogući prijem datoteka" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Snimi primljene datoteke u" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Da li dopustiti modifikaciju dijeljenih datoteka" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Uključi ili isključi dijeljenje datoteka" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Da li zahtijevati PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Dozvolite spoljnim uređajima da mijenjaju dijeljene datoteke" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Omogućavanje ili onemogućavanje globalne KDE Bluetooth-integracije" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Čarobnjak za Bluetooth uređaje" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Sljedeće" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Završi" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Bluetooth čarobnjak" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Uređaj s kojim se treba upariti" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Pretražujem..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Ručni PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Izbor uređaja" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Ponovo pokreni čarobnjaka" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Zatvori" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Postavljanje uređaja nije uspjelo" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Podešavanje %1 neuspjelo" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Molim ubacite PIN u tastaturu kada se pojavi i pritisnite Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Molim ubacite PIN u uređaj kada se pojavi" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Povezujem se na %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Spajam se na: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Odgovara" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Ne slaže se" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Molim, potvrdite da PIN prikazan na \"%1\" odgovara onom na čarobnjaku." + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/ca/CMakeLists.txt b/bluedevil/po/ca/CMakeLists.txt new file mode 100644 index 00000000..bc9c07c4 --- /dev/null +++ b/bluedevil/po/ca/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(ca ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/ca/bluedevil.po b/bluedevil/po/ca/bluedevil.po new file mode 100644 index 00000000..b93a090a --- /dev/null +++ b/bluedevil/po/ca/bluedevil.po @@ -0,0 +1,914 @@ +# Translation of bluedevil.po to Catalan +# Copyright (C) 2010-2013 This_file_is_part_of_KDE +# This file is distributed under the license LGPL version 2 or later. +# +# Manuel Tortosa , 2010, 2011. +# Josep David Capdevila Cano , 2010. +# Josep Ma. Ferrer , 2010, 2011, 2013. +# Antoni Bella Pérez , 2013. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-10-19 21:49+0200\n" +"Last-Translator: Antoni Bella Pérez \n" +"Language-Team: Catalan \n" +"Language: ca\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Accelerator-Marker: &\n" +"X-Generator: Lokalize 1.5\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Manuel Tortosa Moreno" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "manutortosa@chakra-project.org" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 està sol·licitant accés a aquest ordinador" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Confia i autoritza" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Només autoritza" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Denega" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Sistema Bluetooth del KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Canvio el mode Bluetooth a «%1»?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Confirma" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Denega" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 està demanant si el PIN és correcte: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "El PIN és correcte" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "El PIN és incorrecte" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Fa falta un PIN per emparellar amb %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Introduïu el PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Per tal d'emparellar aquest ordinador amb %1 heu d'introduir un PIN, feu-ho " +"a sota." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Introduïu el PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Dimoni Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Mantenidor" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Dispositius Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 us està enviant el fitxer %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Accepta" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Cancel·la" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "S'està rebent un fitxer pel Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Des de" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "A" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Envia per Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Cerca dispositius..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Altres..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Ocult" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Sempre visible" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Temporalment visible" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minut" +msgstr[1] "%1 minuts" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Nom" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Endollat" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Visibilitat" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Temps de visibilitat" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Adaptador per omissió: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adaptador: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minut" +msgstr[1] "%1 minuts" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Adaptadors Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Mòdul del plafó de control pels adaptadors Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Desenvolupador i mantenidor" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "No s'ha trobat cap adaptador. Connecteu-ne un." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Habilita la integració Bluetooth amb el KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Dispositius Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Mòdul del plafó de control pels dispositius Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detalls" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Elimina" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Connecta" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Desconnecta" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Afegeix un dispositiu..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Connecta" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Seleccioneu un nou àlies per a %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Esteu segur que voleu eliminar el dispositiu «%1» de la llista de " +"dispositius coneguts?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Supressió de dispositiu" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "No s'ha afegit cap dispositiu remot" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Feu clic aquí per afegir un dispositiu remot" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Desconegut" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telèfon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Mòdem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Ordinador" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Xarxa" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Auriculars" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Cascs" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Àudio" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Teclat" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Ratolí" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Càmera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Impressora" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Comandament de joc" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tauleta" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tipus: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Transferència Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Mòdul del plafó de control per a les transferències Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Mai" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Dispositius de confiança" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Tots els dispositius" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Mai" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Sempre" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Només de lectura" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Modifica i llegeix" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Nom" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Àlies" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adreça" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Emparellat" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Bloquejat" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "De confiança" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "No s'ha trobat cap adaptador Bluetooth" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Arregla-ho" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"El vostre adaptador Bluetooth per omissió no és visible per als dispositius " +"remots." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "La interacció amb el sistema Bluetooth no és òptima." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "El Bluetooth no està habilitat completament." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Recepció" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Desa els fitxers a:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Accepta automàticament:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Rep fitxers:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Compartició" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Comparteix fitxers:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Requereix PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Permisos:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Fitxers compartits" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Carpeta compartida" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Envia fitxer" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Navega pels fitxers" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "S'estan obtenint els serveis..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "S'estan explorant per dispositius nous..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Dimoni ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "S'està recuperant la informació del dispositiu remot..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "S'està connectant al dispositiu" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Desenvolupador" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Navega pels dispositius" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Envia fitxers" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "El Bluetooth està apagat" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Engega el Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Navega pels dispositius" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Dispositius coneguts" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Afegeix un dispositiu" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Configura el Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Es pot descobrir" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Apaga el Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 dispositiu connectat" +msgstr[1] "%1 dispositius connectats" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "No s'ha trobat cap adaptador" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "El Bluetooth està engegat" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "El Bluetooth està apagat" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Connecta" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Assistent d'enviament de fitxer per Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID del dispositiu al que s'enviaran els fitxers" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Fitxers que s'enviaran" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "S'està connectant amb: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "S'està connectant a %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Seleccioneu un o més fitxers:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Fitxers seleccionats: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Seleccioneu un dispositiu de la llista:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "S'està explorant" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "S'està enviant un fitxer pel Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Envia fitxers per Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Habilita o deshabilita la recepció de fitxers" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Desa els fitxers rebuts a:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Si es permet modificar fitxers compartits o no" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Habilita o deshabilita la compartició de fitxers" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Si es requereix el PIN o no" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Permet a dispositius externs modificar els fitxers compartits" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Habilita o deshabilita la integració global del Bluetooth amb el KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Assistent de dispositiu Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Següent" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Acaba" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Assistent Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Dispositiu al que emparellar" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "S'està explorant..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN manual:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Seleccioneu un dispositiu" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Reinicia l'assistent" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Tanca" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Ha fallat l'arranjament del dispositiu" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "La fallat l'arranjament de %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Introduïu el PIN al vostre teclat en aparèixer i premeu Retorn" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Introduïu el PIN en aparèixer al vostre dispositiu" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "S'està connectant a %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "S'està connectant amb: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Coincideix" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "No coincideix" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "Confirmeu que el PIN mostrat a «%1» coincideix amb el de l'assistent." \ No newline at end of file diff --git a/bluedevil/po/ca@valencia/CMakeLists.txt b/bluedevil/po/ca@valencia/CMakeLists.txt new file mode 100644 index 00000000..756e315b --- /dev/null +++ b/bluedevil/po/ca@valencia/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(ca@valencia ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/ca@valencia/bluedevil.po b/bluedevil/po/ca@valencia/bluedevil.po new file mode 100644 index 00000000..8f29cda9 --- /dev/null +++ b/bluedevil/po/ca@valencia/bluedevil.po @@ -0,0 +1,914 @@ +# Translation of bluedevil.po to Catalan +# Copyright (C) 2010-2013 This_file_is_part_of_KDE +# This file is distributed under the license LGPL version 2 or later. +# +# Manuel Tortosa , 2010, 2011. +# Josep David Capdevila Cano , 2010. +# Josep Ma. Ferrer , 2010, 2011, 2013. +# Antoni Bella Pérez , 2013. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-10-19 21:49+0200\n" +"Last-Translator: Antoni Bella Pérez \n" +"Language-Team: Catalan \n" +"Language: ca@valencia\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Accelerator-Marker: &\n" +"X-Generator: Lokalize 1.5\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Manuel Tortosa Moreno" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "manutortosa@chakra-project.org" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 està sol·licitant accés a este ordinador" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Confia i autoritza" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Només autoritza" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Denega" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Sistema Bluetooth del KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Canvio el mode Bluetooth a «%1»?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Confirma" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Denega" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 està demanant si el PIN és correcte: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "El PIN és correcte" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "El PIN és incorrecte" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Fa falta un PIN per emparellar amb %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Introduïu el PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Per tal d'emparellar este ordinador amb %1 heu d'introduir un PIN, feu-ho a " +"sota." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Introduïu el PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Dimoni Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Mantenidor" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Dispositius Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 vos està enviant el fitxer %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Accepta" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Cancel·la" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "S'està rebent un fitxer pel Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Des de" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "A" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Envia per Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Cerca dispositius..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Altres..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Ocult" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Sempre visible" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Temporalment visible" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minut" +msgstr[1] "%1 minuts" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Nom" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Endollat" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Visibilitat" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Temps de visibilitat" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Adaptador per omissió: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adaptador: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minut" +msgstr[1] "%1 minuts" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Adaptadors Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Mòdul del plafó de control pels adaptadors Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Desenvolupador i mantenidor" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "No s'ha trobat cap adaptador. Connecteu-ne un." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Habilita la integració Bluetooth amb el KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Dispositius Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Mòdul del plafó de control pels dispositius Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detalls" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Elimina" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Connecta" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Desconnecta" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Afig un dispositiu..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Connecta" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Seleccioneu un nou àlies per a %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Esteu segur que voleu eliminar el dispositiu «%1» de la llista de " +"dispositius coneguts?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Supressió de dispositiu" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "No s'ha afegit cap dispositiu remot" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Feu clic ací per afegir un dispositiu remot" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Desconegut" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telèfon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Mòdem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Ordinador" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Xarxa" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Auriculars" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Cascs" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Àudio" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Teclat" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Ratolí" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Càmera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Impressora" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Comandament de joc" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tauleta" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tipus: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Transferència Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Mòdul del plafó de control per a les transferències Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Mai" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Dispositius de confiança" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Tots els dispositius" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Mai" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Sempre" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Només de lectura" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Modifica i llig" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Nom" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Àlies" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adreça" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Emparellat" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Bloquejat" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "De confiança" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "No s'ha trobat cap adaptador Bluetooth" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Arregla-ho" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"El vostre adaptador Bluetooth per omissió no és visible per als dispositius " +"remots." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "La interacció amb el sistema Bluetooth no és òptima." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "El Bluetooth no està habilitat completament." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Recepció" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Guarda els fitxers a:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Accepta automàticament:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Rep fitxers:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Compartició" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Comparteix fitxers:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Requereix PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Permisos:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Fitxers compartits" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Carpeta compartida" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Envia fitxer" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Navega pels fitxers" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "S'estan obtenint els serveis..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "S'estan explorant per dispositius nous..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Dimoni ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "S'està recuperant la informació del dispositiu remot..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "S'està connectant al dispositiu" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Desenvolupador" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Navega pels dispositius" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Envia fitxers" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "El Bluetooth està apagat" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Engega el Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Navega pels dispositius" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Dispositius coneguts" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Afig un dispositiu" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Configura el Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Es pot descobrir" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Apaga el Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 dispositiu connectat" +msgstr[1] "%1 dispositius connectats" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "No s'ha trobat cap adaptador" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "El Bluetooth està engegat" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "El Bluetooth està apagat" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Connecta" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Assistent d'enviament de fitxer per Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID del dispositiu al que s'enviaran els fitxers" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Fitxers que s'enviaran" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "S'està connectant amb: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "S'està connectant a %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Seleccioneu un o més fitxers:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Fitxers seleccionats: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Seleccioneu un dispositiu de la llista:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "S'està explorant" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "S'està enviant un fitxer pel Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Envia fitxers per Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Habilita o deshabilita la recepció de fitxers" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Guarda els fitxers rebuts a:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Si es permet modificar fitxers compartits o no" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Habilita o deshabilita la compartició de fitxers" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Si es requereix el PIN o no" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Permet a dispositius externs modificar els fitxers compartits" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Habilita o deshabilita la integració global del Bluetooth amb el KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Assistent de dispositiu Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Següent" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Acaba" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Assistent Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Dispositiu al que emparellar" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "S'està explorant..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN manual:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Seleccioneu un dispositiu" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Reinicia l'assistent" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Tanca" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Ha fallat l'arranjament del dispositiu" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "La fallat l'arranjament de %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Introduïu el PIN al vostre teclat en aparèixer i premeu Retorn" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Introduïu el PIN en aparèixer al vostre dispositiu" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "S'està connectant a %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "S'està connectant amb: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Coincideix" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "No coincideix" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "Confirmeu que el PIN mostrat a «%1» coincideix amb el de l'assistent." \ No newline at end of file diff --git a/bluedevil/po/cs/CMakeLists.txt b/bluedevil/po/cs/CMakeLists.txt new file mode 100644 index 00000000..88522fd5 --- /dev/null +++ b/bluedevil/po/cs/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(cs ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/cs/bluedevil.po b/bluedevil/po/cs/bluedevil.po new file mode 100644 index 00000000..fedd7934 --- /dev/null +++ b/bluedevil/po/cs/bluedevil.po @@ -0,0 +1,907 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# Vít Pelčák , 2010, 2011, 2013. +# Tomáš Chvátal , 2012, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-11-12 09:31+0100\n" +"Last-Translator: Vít Pelčák \n" +"Language-Team: Czech \n" +"Language: cs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"X-Generator: Lokalize 1.5\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Vít Pelčák, Tomáš Chvátal" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "vit@pelcak.org, tomas.chvatal@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 žádá o přístup k tomuto počítači" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Důvěřovat a udělit oprávnění" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Pouze udělit oprávnění" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Odepřít" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Systém Bluetooth KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Změnit režim Bluetooth na '%1'?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Potvrdit" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Odepřít" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 se táže, zda je je správný PIN: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN je správný" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN je nesprávný" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Je třeba zadat pin pro spárování s %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Zadejte PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "Pro připojení počítače s %1, musíte níže zadat PIN." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Zadejte PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Démon Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Správce" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Zařízení Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 vám posílá soubor %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Přijmout" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Zrušit" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Přijímám soubory přes Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Od" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Komu" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Poslat přes Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Najít zařízení..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Jiné..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Skryté" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Vždy viditelné" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Dočasně viditelné" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuta" +msgstr[1] "%1 minuty" +msgstr[2] "%1 minut" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Název" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Napájený" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Viditelný" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Čas odkrytí" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Výchozí adaptér: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adaptér: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuta" +msgstr[1] "%1 minuty" +msgstr[2] "%1 minut" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Adaptéry Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Modul ovládacího panelu pro Bluetooth adaptéry" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Vývojář a správce" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Adaptéry nenalezeny. Prosím připojte nějaký." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Povolit integraci Bluetooth" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Zařízení Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Modul ovládacího panelu pro Bluetooth zařízení" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Podrobnosti" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Odstranit" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Připojit se" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Odpojit" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Přidat zařízení..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Připojit se" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Vyberte nový alias pro %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Opravdu si přejete smazat zařízení \"%1\" ze seznamu známých zařízení?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Odstranit zařízení" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Vzdálená zařízení nepřidána" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Kliknutím zde přidáte vzdálené zařízení" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Neznámý" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Počítač" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Síť" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Headset" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Sluchátka" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Zvuk" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Klávesnice" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Myš" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Fotoaparát" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Tiskárna" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablet" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Typ: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth přenos" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Modul ovládacího panelu pro přenosy Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Nikdy" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Důvěryhodná zařízení" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Všechna zařízení" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Nikdy" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Vždy" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Pouze ke čtení" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Změnit a číst" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Název" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adresa" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Spárovaný" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blokovaný" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Důvěryhodný" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Bluetooth adaptéry nebyly nalezeny." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Opravit" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Váš výchozí Bluetooth adaptér není viditelný pro vzdálená zařízení." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Interakce se systémem Bluetooth není optimální." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth není kompletně povolen." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Přijímám" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Uložit soubory v:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Automaticky přijmout:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Přijmout soubory:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Sdílení" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Sdílet soubory:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Vyžadovat PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Práva:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Sdílené soubory" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Sdílená složka:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Poslat soubor" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Procházet soubory" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Přijímám služby..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Vyhledávám nová zařízení..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Démon ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Získávám informace ze vzdáleného zařízení..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Připojuji se k zařízení" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Vývojář" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Procházet zařízení" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Odeslat soubory" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth je vypnut" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Zapnout Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Procházet zařízení" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Známá zařízení" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Přidat zařízení" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Nastavit Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Objevitelné" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Vypnout Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 připojené zařízení" +msgstr[1] "%1 připojená zařízení" +msgstr[2] "%1 připojených zařízení" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Adaptéry nenalezeny" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth je zapnut" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth je vypnut" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Připojit se" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Pomocník pro posílání souborů přes Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID zařízení, kam budou soubory odeslány" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Soubory k odeslání" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Připojuji k: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Připojuji k %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Vyberte jeden nebo více souborů:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Vybrané soubory: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Vyberte zařízení ze seznamu:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Skenování" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Posílám soubor přes Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Odesílání souborů přes Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Povolit nebo zakázat příjem souborů" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Uložit příchozí soubory v:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Povolit změnu sdílených souborů" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Povolit nebo zakázat sdílení souborů" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Požadovat PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Povolit změnu sdílených souborů z externích zařízení" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Povolit nebo zakázat integraci Bluetooth pro KDE globálně" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Průvodce Bluetooth zařízením" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Následující" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Dokončit" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Průvodce Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Zařízení ke spárování" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Zkoumám..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Ruční PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Zvolte zařízení" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Restartovat průvodce" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Zavřít" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Selhalo nastavení zařízení" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Selhalo nastavení %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Až se ukáže dialog, prosím zadejte PIN na vaší klávesnici a stiskněte Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Až se ukáže dialog, prosím zadejte PIN na vašem zařízení" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Připojuji k %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Připojuji k: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Shodují se" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Neshodují se" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Prosím potvrďte, že PIN zobrazený na \"%1\" se shoduje s PIN v průvodci." \ No newline at end of file diff --git a/bluedevil/po/da/CMakeLists.txt b/bluedevil/po/da/CMakeLists.txt new file mode 100644 index 00000000..c870c516 --- /dev/null +++ b/bluedevil/po/da/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(da ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/da/bluedevil.po b/bluedevil/po/da/bluedevil.po new file mode 100644 index 00000000..4eff6f56 --- /dev/null +++ b/bluedevil/po/da/bluedevil.po @@ -0,0 +1,1101 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Martin Schlander , 2010, 2011, 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-11-16 17:12+0100\n" +"Last-Translator: Martin Schlander \n" +"Language-Team: Danish \n" +"Language: da\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.5\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Martin Schlander" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "mschlander@opensuse.org" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 anmoder om adgang til denne computer" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Betro og godkend" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Godkend kun" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Afvis" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE bluetooth-system" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Skift Bluetooth-tilstand til \"%1\"?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Bekræft" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Afvis" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 spørger om PIN-koden er korrekt: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "Korrekt PIN-kode" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "Forkert PIN-kode" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Der kræves PIN-kode for at parre med %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Introducér PIN-kode" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"For at parre denne computer med %1 skal du angive en PIN-kode. Gør det " +"venligst nedenfor." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Introducér PIN-kode" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Bluetooth-dæmon" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Vedligeholder" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetooth-enheder" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 sender dig filen %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Acceptér" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Annullér" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Modtager fil via Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Fra" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Til" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Send via Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Find enhed..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Andre..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Skjult" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Altid synlig" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Midlertidigt synlig" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minut" +msgstr[1] "%1 minutter" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Navn" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Strøm til" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Synlighed" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Opdagelsestid" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Standard-adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minut" +msgstr[1] "%1 minutter" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetooth-adaptere" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Kontrolpanelmodul til bluetooth-adaptere" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Udvikler og vedligeholder" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Ingen adaptere fundet. Tilslut venligst en." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Aktivér KDE bluetooth-integration" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth-enheder" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Kontrolpanelmodul til bluetooth-enheder" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detaljer" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Fjern" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Forbind" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Afbryd forbindelse" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Tilføj enhed..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Forbind" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Vælg et nyt alias til %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Vil du virkelig fjerne enheden \"%1\" fra listen over kendte enheder?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Fjernelse af enhed" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Ingen eksterne enheder er blevet tilføjet" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Klik her for at tilføje en ekstern enhed" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Ukendt" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Computer" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Netværk" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Headset" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Høretelefoner" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Lyd" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Tastatur" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Mus" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Printer" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tegneplade" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Type: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth-overførsel" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Kontrolpanelmodul til bluetooth-overførsel" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Aldrig" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Betroede enheder" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Alle enheder" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Aldrig" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Altid" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Skrivebeskyttet" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Ændr og læs" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Navn" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adresse" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Parret" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blokeret" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Betroet" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Ingen bluetooth-adaptere blev fundet." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Ret det" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Din standard bluetooth-adapter er ikke synlig for eksterne enheder." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Interaktion med bluetooth-systemet er ikke optimal." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth er ikke helt aktiveret." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Modtager" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Gem filer i:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Acceptér automatisk:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Modtag filer:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Deling" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Del filer:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Kræv PIN-kode:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Rettigheder:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Delte filer" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Delt mappe:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Send fil" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Gennemse filer" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Henter tjenester..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Scanner efter nye enheder..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp-dæmon" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Henter information fra ekstern enhed..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Forbinder til enheden" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Udvikler" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Gennemse enheder" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Send filer" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth er slået fra" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Slå Bluetooth til" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Gennemse enheder" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Kendte enheder" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Tilføj enhed" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Indstil Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Registrérbar" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Slå Bluetooth fra" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 forbundet enhed" +msgstr[1] "%1 forbundne enheder" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Ingen adaptere fundet" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth er slået til" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth er slået fra" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Forbind" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Hjælper til at sende filer med bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "Enheds-UUID som filerne bliver sendt til" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Filer der vil blive sendt" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Forbinder til: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Forbinder til %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Vælg én eller flere filer:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Valgte filer: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Vælg en enhed fra listen:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Scanner" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Sender fil via Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Send filer med bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Aktivér eller deaktivér modtagelse af filer" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Gem modtagne filer i:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Om ændring af delte filer skal tillades" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Aktivér eller deaktivér deling af filer" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Om PIN-kode skal kræves" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Tillad eksterne enheder at ændre de delte filer" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Aktivér eller deaktivér den globale Bluetooth-integration i KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Guide til bluetooth-enheder" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Næste" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Færdig" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Bluetooth-guide" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Enhed der skal parres med" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Scanner..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Manuel PIN-kode:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Vælg en enhed" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Genstart guiden" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Luk" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Opsætning af enheden mislykkedes" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Opsætning af %1 mislykkedes" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Indtast venligst PIN-koden på dit tastatur når den dukker op og tryk Retur" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Indtast venligst PIN-koden på din enhed når den dukker op" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Forbinder til %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Forbinder til: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Match" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Matcher ikke" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Bekræft venligst at den PIN-kode der vises på \"%1\" matcher den i guiden." + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/de/CMakeLists.txt b/bluedevil/po/de/CMakeLists.txt new file mode 100644 index 00000000..4a149a88 --- /dev/null +++ b/bluedevil/po/de/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(de ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/de/bluedevil.po b/bluedevil/po/de/bluedevil.po new file mode 100644 index 00000000..51fb3d09 --- /dev/null +++ b/bluedevil/po/de/bluedevil.po @@ -0,0 +1,1042 @@ +# Frederik Schwarzer , 2010, 2011, 2012. +# Panagiotis Papadopoulos , 2010, 2011. +# Johannes Obermayr , 2010. +# Kai Uwe Broulik , 2011. +# Burkhard Lück , 2011, 2012, 2013. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-12-21 11:39+0100\n" +"Last-Translator: Burkhard Lück \n" +"Language-Team: German \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.5\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Panagiots Papadopoulos,Frederik Schwarzer" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "pano_90@gmx.net,schwarzer@kde.org" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 beantragt Zugang zu diesem Rechner" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Vertrauen und autorisieren" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Nur autorisieren" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Verweigern" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Bluetooth-System für KDE" + +# Evtl. „Bluetooth-Modus nach %1 ändern“? +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Bluetooth-Modus zu %1 wechseln?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Bestätigen" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Verweigern" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 fragt ob die PIN korrekt ist: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN korrekt" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN nicht korrekt" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Für das Pairing mit %1 ist eine PIN erforderlich" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "PIN eingeben" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Für das Pairing des Rechners mit „%1“ muss eine PIN eingegeben werden. Bitte " +"tun Sie dies unten." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "PIN eingeben" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Bluetooth-Dienst" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Betreuer" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetooth-Gerät" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 sendet Ihnen die Datei %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Annehmen" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Abbrechen" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Datei wird über Bluetooth empfangen" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Von" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "An" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Über Bluetooth versenden" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Gerät suchen ..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Andere ..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Ausgeblendet" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Immer sichtbar" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Vorübergehend sichtbar" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 Minute" +msgstr[1] "%1 Minuten" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Name" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "In Betrieb" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Sichtbarkeit" + +# Könnte u.U. vom Platz her schmal werden neben dem Schieberegler. +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Sichtbarkeitszeit" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Standard-Adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 Minute" +msgstr[1] "%1 Minuten" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetooth-Adapter" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Systemeinstellungsmodul für Bluetooth-Adapter" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Entwickler und Betreuer" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Keine Adapter gefunden. Bitte schließen Sie einen an." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Bluetooth-Integration in KDE aktivieren" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth-Geräte" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Systemeinstellungsmodul für Bluetooth-Geräte" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Details" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Entfernen" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Verbinden" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Verbindung trennen" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Gerät hinzufügen ..." + +#: kcmodule/bluedevildevices.cpp:446 +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Wieder verbinden" + +# Alias? +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Wählen Sie einen neuen Alias für %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Sind Sie sicher, dass Sie das Gerät „%1“ von der Liste der bekannten Geräte " +"entfernen möchten?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Gerät entfernen" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Es wurden keine Geräte hinzugefügt" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Klicken Sie hier, um ein entferntes Gerät hinzuzufügen" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Unbekannt" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Computer" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Netzwerk" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Headset" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Kopfhörer" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Audio" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Tastatur" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Maus" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Drucker" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablet" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Art: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth-Übertragung" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Systemeinstellungsmodul für Bluetooth-Dateiübertragung" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Niemals" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Vertrauenswürdige Geräte" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Alle Geräte" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Niemals" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Immer" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Nur lesen" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Verändern und lesen" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Name" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adresse" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Verbunden (Paired)" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blockiert" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Vertrauenswürdig" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Es sind keine Bluetooth-Adapter gefunden worden." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Beheben" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"Ihr Standard-Bluetooth-Adapter ist für entfernte Geräte nicht sichtbar." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Zusammenspiel mit dem Bluetooth-System ist nicht optimal." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth ist nicht vollständig aktiviert." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Empfang" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Dateien speichern unter:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Automatisch annehmen:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Dateien empfangen:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Freigeben" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Dateien freigeben:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "PIN benötigt:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Berechtigungen:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Freigegebene Dateien" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Freigegebener Ordner:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Datei senden" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Dateien durchsuchen" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Erhalten der Dienste ..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Suchen nach neuen Geräten ..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp-Dienst" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Informationen vom entfernten Gerät werden abgerufen ..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Verbindung wird hergestellt" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Entwickler" + +#: monolithic/monolithic.cpp:42 +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Gerät durchsuchen" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Dateien senden" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth ist aus" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Bluetooth einschalten" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Geräte durchsuchen" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Bekannte Geräte" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Gerät hinzufügen" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Bluetooth einrichten" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Auffindbar" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Bluetooth ausschalten" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 verbundenes Gerät" +msgstr[1] "%1 verbundene Geräte" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Keine Adapter gefunden" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth ist an" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth ist aus" + +#: monolithic/monolithic.cpp:441 +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Verbinden" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Hilfsprogramm für Bluetooth-Dateiversand" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "Geräte-Kennung (UUID), an die Daten gesendet werden" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Dateien, die gesendet werden" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Verbindung herstellen zu: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Verbindung herstellen zu %1 ..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Wählen Sie eine oder mehrere Dateien:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Ausgewählte Dateien: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Wählen Sie ein Gerät aus der Liste:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Scannen" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Datei wird über Bluetooth versendet" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Bluetooth-Dateiversand" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Das Empfangen von Dateien aktivieren/deaktivieren" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Empfangene Dateien speichern unter:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Ob freigegebene Dateien verändert werden dürfen" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Dateifreigabe aktivieren/deaktivieren" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Ob eine PIN benötigt wird" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Externe Geräte dürfen freigegebene Dateien ändern" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Die Bluetooth-Integration in KDE aktivieren/deaktivieren" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Bluetooth-Geräte-Assistent" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Weiter" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Fertigstellen" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Bluetooth-Assistent" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Gerät für das Pairing" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Es wird gesucht ..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Manuelle PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Wählen Sie ein Gerät" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Assistenten neu starten" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Schließen" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Die Einrichtung des Gerätes ist fehlgeschlagen" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Die Einrichtung von %1 ist fehlgeschlagen" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Geben Sie bitte die PIN in ihr Gerät ein, sobald sie angezeigt wird und " +"drücken Sie Eingabe" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Geben Sie bitte die PIN in ihr Gerät ein, sobald sie angezeigt wird" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Verbindung zu %1 wird aufgebaut ..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Verbindung herstellen zu:" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Passt" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Passt nicht" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Bestätigen Sie, dass die PIN, die auf „%1“ angezeigt wird, zu der im " +"Assistenten passt." + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# Da hatten wir doch mal eine Übersetzung für? \ No newline at end of file diff --git a/bluedevil/po/el/CMakeLists.txt b/bluedevil/po/el/CMakeLists.txt new file mode 100644 index 00000000..52de24b9 --- /dev/null +++ b/bluedevil/po/el/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(el ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/el/bluedevil.po b/bluedevil/po/el/bluedevil.po new file mode 100644 index 00000000..345f3610 --- /dev/null +++ b/bluedevil/po/el/bluedevil.po @@ -0,0 +1,917 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Nikos Pantazis , 2011. +# George Stefanakis , 2011. +# Dimitrios Glentadakis , 2011. +# Stelios , 2011. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2011-11-13 15:59+0100\n" +"Last-Translator: Dimitrios Glentadakis \n" +"Language-Team: Greek \n" +"Language: el\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Lokalize 1.2\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Πανταζής Νίκος" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "pantazisnikolaos@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "Το %1 ζητά πρόσβαση σε αυτόν τον υπολογιστή" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Έμπιστο και εξουσιοδότηση" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Εξουσιοδότηση μόνο" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Άρνηση" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Σύστημα Bluetooth του KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Αλλαγή λειτουργίας Bluetooth σε «%1»;" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Επιβεβαίωση" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Άρνηση" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "Το %1 ρωτά αν το PIN είναι σωστό: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "Το PIN είναι σωστό" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "Το PIN είναι λάθος" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Χρειάζεται PIN για το σχηματισμό ζεύγους με το %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Εισαγωγή PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Για να σχηματίσει αυτός ο υπολογιστής ζεύγος με το %1, θα πρέπει να εισάγετε " +"ένα PIN. Παρακαλώ εισάγετε το παρακάτω." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Εισαγωγή PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Δαίμονας Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Συντηρητής" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Συσκευές Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "Το %1 σάς στέλνει το αρχείο %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Αποδοχή" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Ακύρωση" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Λήψη αρχείου μέσω Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Από" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Σε" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Αποστολή μέσω Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Εύρεση συσκευής..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Άλλη..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Κρυμμένο" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Πάντα ορατό" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Προσωρινά ορατό" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 λεπτό" +msgstr[1] "%1 λεπτά" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Όνομα" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Τροφοδοτούμενο" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Ορατότητα" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Χρόνος αποκάλυψης" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Προεπιλεγμένος προσαρμογέας: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Προσαρμογέας: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 λεπτό" +msgstr[1] "%1 λεπτά" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Προσαρμογείς Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Άρθρωμα πίνακα ελέγχου για προσαρμογείς Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Προγραμματιστής και συντηρητής" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Δεν βρέθηκαν προσαρμογείς. Παρακαλώ συνδέστε έναν." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Ενεργοποίηση ενσωμάτωσης Bluetooth στο KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Συσκευές Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Άρθρωμα πίνακα ελέγχου για συσκευές Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Λεπτομέρειες" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Απομάκρυνση" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Σύνδεση" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Αποσύνδεση" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Προσθήκη συσκευής..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Σύνδεση" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Επιλογή νέου ψευδώνυμου για το %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Επιθυμείτε πραγματικά την αφαίρεση της συσκευής «%1» από τη λίστα των " +"γνωστών συσκευών;" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Αφαίρεση συσκευής" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Καμία απομακρυσμένη συσκευή δεν προστέθηκε" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Κάντε κλικ εδώ για να προσθέσετε μία απομακρυσμένη συσκευή" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Άγνωστο" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Τηλέφωνο" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Μόντεμ" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Υπολογιστής" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Δίκτυο" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Ακουστικά με μικρόφωνο" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Ακουστικά" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Ήχος" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Πληκτρολόγιο" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Ποντίκι" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Κάμερα" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Εκτυπωτής" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Χειριστήριο Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablet" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Είδος: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Μεταφορά μέσω Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Άρθρωμα πίνακα ελέγχου για μεταφορά μέσω Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Ποτέ" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Έμπιστες συσκευές" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Όλες οι συσκευές" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Ποτέ" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Πάντα" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Ανάγνωση μόνο" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Τροποποίηση και ανάγνωση" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Όνομα" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Ψευδώνυμο" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Διεύθυνση" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Συζευγμένο" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Σε φραγή" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Έμπιστο" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Δεν βρέθηκαν προσαρμογείς Bluetooth." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Διόρθωση" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"Ο προεπιλεγμένος προσαρμογέας Bluetooth δεν είναι ορατός στις απομακρυσμένες " +"συσκευές." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Η αλληλεπίδραση με το σύστημα Bluetooth δεν είναι η βέλτιστη." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Το Bluetooth δεν είναι πλήρως ενεργοποιημένο." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Λήψη" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Αποθήκευση αρχείων στο:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Αυτόματη αποδοχή:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Λήψη αρχείων:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Κοινή χρήση" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Κοινή χρήση αρχείων:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Απαίτηση PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Δικαιώματα:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Κοινόχρηστα αρχεία" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Κοινόχρηστος φάκελος" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Αποστολή αρχείου" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Περιήγηση στα αρχεία" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Ανάκτηση υπηρεσιών..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Σάρωση για νέες συσκευές..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Δαίμονας ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Ανάκτηση πληροφοριών απομακρυσμένης συσκευής..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Σύνδεση στη συσκευή" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Προγραμματιστής" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Περιήγηση στις συσκευές" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Αποστολή αρχείων" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Το Bluetooth είναι ανενεργό" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Ενεργοποίηση Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Περιήγηση στις συσκευές" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Γνωστές συσκευές" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Προσθήκη συσκευής" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Διαμόρφωση Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Αποκαλυπτόμενο" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Απενεργοποίηση Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 συνδεδεμένη συσκευή" +msgstr[1] "%1 συνδεδεμένες συσκευές" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Δεν βρέθηκαν προσαρμογείς" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Το Bluetooth είναι ενεργό" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Το Bluetooth είναι ανενεργό" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Σύνδεση" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Βοηθός αποστολής αρχείου μέσω Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID συσκευής που θα σταλούν τα αρχεία" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Αρχεία που θα σταλούν" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Σύνδεση στο: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Σύνδεση στο %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Επιλέξτε ένα ή περισσότερα αρχεία:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Επιλεγμένα αρχεία: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Επιλέξτε συσκευή από τη λίστα:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Σάρωση" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Αποστολή αρχείου μέσω Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Αποστολή αρχείων μέσω Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Ενεργοποίηση ή απενεργοποίηση λήψης αρχείων" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Αποθήκευση ληφθέντων αρχείων στο:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Αν θα επιτρέπεται η τροποποίηση κοινόχρηστων αρχείων" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Ενεργοποίηση ή απενεργοποίηση κοινής χρήσης αρχείων" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Αν θα απαιτείται το PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Επίτρεψε σε εξωτερικές συσκευές να τροποποιούν τα κοινόχρηστα αρχεία" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "" +"Ενεργοποίηση ή απενεργοποίηση της καθολικής ενσωμάτωσης του Bluetooth στο KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Οδηγός συσκευής Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Επόμενο" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Τέλος" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Οδηγός Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Συσκευή για σχηματισμό ζεύγους" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Σάρωση..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Χειροκίνητο PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Επιλέξτε συσκευή" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Επανεκκίνηση του οδηγού" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Κλείσιμο" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Απέτυχε η ρύθμιση της συσκευής" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Απέτυχε η εγκατάσταση του %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Παρακαλώ εισαγάγετε το PIN στο πληκτρολόγιο σας, όταν αυτό εμφανιστεί και " +"πιέστε Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Παρακαλώ εισαγάγετε το PIN στη συσκευή σας όταν εμφανιστεί" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Σύνδεση στο %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Σύνδεση στο: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Ταιριάζει" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Δεν ταιριάζει" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Παρακαλώ, επιβεβαιώστε ότι το PIN που εμφανίζεται στο \"%1\" ταιριάζει με " +"αυτό του οδηγού." \ No newline at end of file diff --git a/bluedevil/po/en_GB/CMakeLists.txt b/bluedevil/po/en_GB/CMakeLists.txt new file mode 100644 index 00000000..b72a707a --- /dev/null +++ b/bluedevil/po/en_GB/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(en_GB ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/en_GB/bluedevil.po b/bluedevil/po/en_GB/bluedevil.po new file mode 100644 index 00000000..59bfa59f --- /dev/null +++ b/bluedevil/po/en_GB/bluedevil.po @@ -0,0 +1,1166 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Andrew Coles , 2010, 2011. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2011-02-05 17:25+0000\n" +"Last-Translator: Andrew Coles \n" +"Language-Team: British English \n" +"Language: en_GB\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.2\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Andrew Coles" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "andrew_coles@yahoo.co.uk" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 is requesting access to this computer" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Trust and Authorise" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Authorise Only" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Deny" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE Bluetooth System" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Change Bluetooth mode to '%1'?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Confirm" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Deny" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 is asking if the PIN is correct: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN correct" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN incorrect" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN needed to pair with %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Introduce PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Introduce PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Bluetooth Dæmon" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Maintainer" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetooth Devices" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 is sending you the file %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Accept" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Cancel" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Receiving file over Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "From" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "To" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Send via Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +#, fuzzy +#| msgid "Add Device..." +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Add Device..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +#, fuzzy +#| msgid "Other" +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Other" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Hidden" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Always visible" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Temporarily visible" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minute" +msgstr[1] "%1 minutes" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Name" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Powered" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Visibility" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Discover Time" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Default adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minute" +msgstr[1] "%1 minutes" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetooth Adapters" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Bluetooth Adapters Control Panel Module" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Developer and Maintainer" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "No adapters found. Please connect one." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Enable KDE Bluetooth Integration" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth Devices" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Bluetooth Devices Control Panel Module" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Remove" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Connect" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Disconnect" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Add Device..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Connect" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Pick a new alias for %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, fuzzy, kde-format +#| msgid "" +#| "Are you sure that you want to remove the device \"%1\" from the list of " +#| "known devices?" +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Are you sure that you want to remove the device \"%1\" from the list of " +"known devices?" + +#: kcmodule/bluedevildevices.cpp:508 +#, fuzzy +#| msgctxt "Title of window that asks for confirmation when removing a device" +#| msgid "Device Removal" +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Device Removal" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "No remote devices have been added" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Click here to add a remote device" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Unknown" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Phone" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Computer" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Network" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Headset" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Headphones" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Audio" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Keyboard" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Mouse" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Camera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Printer" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablet" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Type: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth Transfer" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Bluetooth Transfer Control Panel Module" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:75 +#, fuzzy +#| msgid "Browse devices" +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Browse devices" + +#: kcmodule/bluedeviltransfer.cpp:76 +#, fuzzy +#| msgid "Add Device" +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Add Device" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "" + +#: kcmodule/devicedetails.cpp:44 +#, fuzzy +#| msgctxt "Name of the adapter" +#| msgid "Name" +msgctxt "Name of the device" +msgid "Name" +msgstr "Name" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "" + +#: kcmodule/devicedetails.cpp:52 +#, fuzzy +#| msgctxt "Whether the adapter is powered or not" +#| msgid "Powered" +msgctxt "Device is paired" +msgid "Paired" +msgstr "Powered" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "" + +#: kcmodule/devicedetails.cpp:56 +#, fuzzy +#| msgctxt "Trust a device" +#| msgid "Trust" +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Trust" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "No Bluetooth adapters have been found." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Fix it" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Your default Bluetooth adapter is not visible for remote devices." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Interaction with Bluetooth system is not optimal." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth is not completely enabled." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +#, fuzzy +#| msgid "Save received files to:" +msgid "Save files in:" +msgstr "Save received files to:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +#, fuzzy +#| msgid "Save received files to:" +msgid "Receive files:" +msgstr "Save received files to:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +#, fuzzy +#| msgid "Send Files" +msgid "Share Files:" +msgstr "Send Files" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +#, fuzzy +#| msgid "Send Files" +msgid "Shared Files" +msgstr "Send Files" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Send Files" +msgid "Shared Folder:" +msgstr "Send Files" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Send File" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Browse Files" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Retrieving services..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +#, fuzzy +#| msgid "Scanning for remote devices..." +msgid "Scanning for new devices..." +msgstr "Scanning for remote devices..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp Dæmon" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Retrieving information from remote device..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Connecting to the device" + +#: monolithic/main.cpp:28 +#, fuzzy +#| msgid "kiobluetooth" +msgid "Bluetooth" +msgstr "kiobluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Developer" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Browse devices" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Send Files" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth is Off" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Turn Bluetooth On" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Browse devices" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Known Devices" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Add Device" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Configure Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Discoverable" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Turn Bluetooth Off" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 connected device" +msgstr[1] "%1 connected devices" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "No adapters found" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth is On" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth is Off" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Connect" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Bluetooth Send File Helper" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "Device UUID where the files will be sent" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Files that will be sent" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Connecting to: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Connecting to %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Select one or more files:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Selected files: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Select a device from the list:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Scanning" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Sending file over Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Bluetooth Send Files" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Enable or disable receiving files" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Save received files to:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +#, fuzzy +#| msgid "Enable or disable receiving files" +msgid "Enable or disable file sharing" +msgstr "Enable or disable receiving files" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Enable or disable the global KDE Bluetooth integration" + +#: wizard/bluewizard.cpp:41 +#, fuzzy +#| msgid "Bluetooth Remote Device Wizard" +msgid "Bluetooth Device Wizard" +msgstr "Bluetooth Remote Device Wizard" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Next" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Bluetooth Wizard" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Device to pair with" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +#, fuzzy +#| msgid "Scanning" +msgid "Scanning..." +msgstr "Scanning" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "" + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "" + +#: wizard/pages/discoverpage.cpp:39 +#, fuzzy +#| msgid "Select a device from the list:" +msgid "Select a device" +msgstr "Select a device from the list:" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Connecting to %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Connecting to: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/eo/CMakeLists.txt b/bluedevil/po/eo/CMakeLists.txt new file mode 100644 index 00000000..72983384 --- /dev/null +++ b/bluedevil/po/eo/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(eo ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/eo/bluedevil.po b/bluedevil/po/eo/bluedevil.po new file mode 100644 index 00000000..b24fc9d5 --- /dev/null +++ b/bluedevil/po/eo/bluedevil.po @@ -0,0 +1,883 @@ +# Translation of bluedevil into esperanto. +# Axel Rousseau , 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2010-06-20 20:01+0200\n" +"Last-Translator: Axel Rousseau \n" +"Language-Team: esperanto \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: pology\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Axel Rousseau" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "axel@esperanto-jeunes.org" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Prizorganto" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "" + +#: daemon/kded/bluezagent.cpp:192 +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "" +msgstr[1] "" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "" +msgstr[1] "" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +msgid "Connect" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:446 +msgid "Re-connect" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "" + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Kreinto" + +#: monolithic/monolithic.cpp:42 +msgid "Browse device" +msgstr "" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "" +msgstr[1] "" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:441 +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "" + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, kde-format +msgid "Connecting to %1..." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +msgid "Connecting to:" +msgstr "" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" \ No newline at end of file diff --git a/bluedevil/po/es/CMakeLists.txt b/bluedevil/po/es/CMakeLists.txt new file mode 100644 index 00000000..906dea91 --- /dev/null +++ b/bluedevil/po/es/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(es ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/es/bluedevil.po b/bluedevil/po/es/bluedevil.po new file mode 100644 index 00000000..2f5c9659 --- /dev/null +++ b/bluedevil/po/es/bluedevil.po @@ -0,0 +1,913 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Kira J. Fernandez , 2010. +# Cristina Yenyxe González García , 2010, 2011. +# Eloy Cuadra , 2011, 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-11-15 16:15+0100\n" +"Last-Translator: Eloy Cuadra \n" +"Language-Team: Spanish \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"com>\n" +"X-Generator: Lokalize 1.5\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Kira J. Fernández,Cristina Yenyxe González García" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "kirajfdez@gmail.com,the.blue.valkyrie@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 solicita acceso a este equipo" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Confiar y autorizar" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Solo autorizar" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Denegar" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Sistema Bluetooth para KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "¿Cambiar modo Bluetooth a «%1»?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Confirmar" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Denegar" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 pregunta si el PIN es correcto: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN correcto" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN incorrecto" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Se necesita un PIN para emparejarse con %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Introduzca el PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Para emparejar este equipo con %1, introduzca el PIN más abajo, por favor." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Introduzca el PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Demonio de Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Responsable" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Dispositivos Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 le está enviando el archivo %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Aceptar" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Cancelar" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Recibiendo archivo a través de Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "De" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "A" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Enviar a través de Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Encontrar dispositivo..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Otros..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Oculto" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Siempre visible" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Visible temporalmente" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuto" +msgstr[1] "%1 minutos" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Nombre" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Conectado" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Visibilidad" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Tiempo de descubrimiento" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Adaptador predeterminado: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adaptador: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuto" +msgstr[1] "%1 minutos" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Adaptadores Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Módulo de panel de control de adaptadores Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Desarrollador y encargado" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "No se encontró ningún adaptador. Conecte uno, por favor." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Activar integración de Bluetooth con KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Dispositivos Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Módulo de panel de control de dispositivos Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detalles" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Eliminar" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Conectar" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Desconectar" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Añadir dispositivo..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Conectar" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Obtener un nuevo alias para %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"¿Seguro que desea eliminar el dispositivo «%1» de la lista de dispositivos " +"conocidos?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Eliminación de dispositivo" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "No se ha añadido ningún dispositivo remoto" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Presione aquí para añadir un dispositivo remoto" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Desconocido" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Teléfono" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Módem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Ordenador" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Red" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Auricular" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Cascos" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Audio" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Teclado" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Ratón" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Cámara" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Impresora" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Mando" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tableta" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tipo: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Transferencia Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Módulo del panel de control de transferencias Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Nunca" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Dispositivos fiables" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Todos los dispositivos" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Nunca" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Siempre" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Solo lectura" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Modificar y leer" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Nombre" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Dirección" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Emparejado" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Bloqueado" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Fiable" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "No se ha encontrado ningún adaptador de Bluetooth." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Arreglarlo" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"Su adaptador de Bluetooth por defecto no es visible desde otros dispositivos " +"remotos." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "La interacción con el sistema Bluetooth no es la óptima." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "El Bluetooth no está activado completamente." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Recibiendo" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Guardar los archivos en:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Aceptar automáticamente:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Recibir archivos:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Compartir" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Compartir archivos:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Solicitar PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Permisos:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Archivos compartidos" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Carpeta compartida:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Enviar archivo" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Explorar archivos" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Consultando servicios..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Explorando nuevos dispositivos..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Demonio de ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Obteniendo información del dispositivo remoto..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Conectándose al dispositivo" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Desarrollador" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Explorar dispositivos" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Enviar archivos" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "El Bluetooth está apagado" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Encender Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Explorar dispositivos" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Dispositivos conocidos" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Añadir dispositivo" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Configurar Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Visible" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Apagar Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 dispositivo conectado" +msgstr[1] "%1 dispositivos conectados" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "No se encontró ningún adaptador" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "El Bluetooth está encendido" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "El Bluetooth está apagado" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Conectar" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Ayudante de envío de archivos por Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID del dispositivo al que se enviarán los archivos" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Archivos que se enviarán" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Conectándose a: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Conectándose a %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Seleccione uno o más archivos:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Archivos seleccionados: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Seleccione un dispositivo de la lista:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Buscando" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Enviando archivo a través de Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Envío de archivos mediante Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Activar o desactivar la recepción de archivos" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Guardar los archivos recibidos en:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Si se debe permitir modificar los archivos compartidos" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Activar o desactivar los archivos compartidos" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Si se debe solicitar el PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Permitir a archivos externos modificar los archivos compartidos" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Activar o desactivar la integración global de Bluetooth con KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Asistente de dispositivos Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Siguiente" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Finalizar" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Asistente Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Dispositivo con el que emparejarse" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Buscando..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN manual:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Seleccione un dispositivo" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Reiniciar el asistente" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Cerrar" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "La configuración del dispositivo ha fallado" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "La configuración de %1 ha fallado" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Por favor, introduzca el PIN en su teclado cuando se le indique y presione " +"Intro" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Por favor, introduzca el PIN en su dispositivo cuando se le indique" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Conectándose a %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Conectándose a: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Coincide" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "No coincide" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Por favor, confirme que el PIN mostrado en «%1» coincide con el del " +"asistente." \ No newline at end of file diff --git a/bluedevil/po/et/CMakeLists.txt b/bluedevil/po/et/CMakeLists.txt new file mode 100644 index 00000000..513e02a7 --- /dev/null +++ b/bluedevil/po/et/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(et ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/et/bluedevil.po b/bluedevil/po/et/bluedevil.po new file mode 100644 index 00000000..6e8d3155 --- /dev/null +++ b/bluedevil/po/et/bluedevil.po @@ -0,0 +1,1330 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Marek Laane , 2010, 2011, 2012. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2012-11-26 17:52+0200\n" +"Last-Translator: Marek Laane \n" +"Language-Team: Estonian \n" +"Language: et\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Lokalize 1.5\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Marek Laane" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "bald@smail.ee" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 soovib ligipääsu arvutile" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Usalda ja autendi" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Ainult autendi" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Keeldu" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE Bluetoothi süsteem" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Kas muuta Bluetoothi režiimiks \"%1\"?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Kinnita" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Keeldu" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 küsib, kas PIN on õige: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "Õige PIN" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "Vale PIN" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Paardumiseks seadmega %1 on vajalik PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "PIN-i sisestamine" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Arvuti paardumiseks seadmega %1 tuleb sisestada PIN. Palun tee seda allpool." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "PIN-i sisestamine" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Bluetoothi deemon" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010: UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Hooldaja" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetoothi seadmed" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 saadab sulle faili %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Nõustu" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Loobu" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Faili saamine Bluetoothi kaudu" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Lähtekoht" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Sihtkoht" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Saada Bluetoothi kaudu" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Otsi seadet..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Muu..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Peidetud" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Alati nähtav" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Ajutiselt nähtav" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minut" +msgstr[1] "%1 minutit" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Nimi" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Toitega" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Nähtavus" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Tuvastamisaeg" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Vaikeadapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minut" +msgstr[1] "%1 minutit" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetoothi adapterid" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Blutetoothi adapterite juhtimismoodul" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010: Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Arendaja ja hooldaja" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Adaptereid ei leitud. Palun ühenda adapter." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "KDE Bluetoothi lõimimise lubamine" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetoothi seadmed" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Blutetoothi seadmete juhtimismoodul" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Üksikasjad" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Eemalda" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Ühenda" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Katkesta ühendus" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Lisa seade..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Ühenda" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Uus alias seadmele %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Kas tõesti eemaldada seade \"%1\" tuntud seadmete nimekirjast?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Seadme eemaldamine" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Ühtegi võrguseadet pole lisatud" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Klõpsa siia võrguseadme lisamiseks" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Tundmatu" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Arvuti" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Võrk" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Peakomplekt" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Kõrvaklapid" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Heli" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Klaviatuur" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Hiir" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kaamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Printer" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Juhtpult" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tahvelarvuti" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tüüp: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetoothi ülekanne" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Bluetoothi ülekande juhtimismoodul" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Mitte kunagi" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Usaldatavad seadmed" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Kõik seadmed" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Mitte kunagi" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Alati" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Kirjutuskaitstud" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Lugemis- ja muutmisõigusega" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Nimi" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Aadress" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Paardunud" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blokeeritud" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Usaldatav" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Ühtegi Bluetoothi adapterit ei leitud." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Paranda probleem" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Sinu vaikimisi Bluetoothi adapter ei ole teistele seadmetele nähtav." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Suhtlemine Bluetoothi süsteemiga ei ole optimaalne." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth ei ole täielikult sisse lülitatud. " + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Vastuvõtmine" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Failide salvestamise asukoht:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Automaatne nõustumine" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Failide vastuvõtmine:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Jagamine" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Failide jagamine:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "PIN-i nõudmine:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Õigused:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Jagatud failid" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Jagatud kataloog" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Faili saatmine" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Failide sirvimine" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Teenuste hankimine..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Uute seadmete otsimine..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp deemon" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Teabe hankimine kaugseadmelt..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Ühendumine seadmega" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Arendaja" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Sirvi seadmeid" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Failide saatmine" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth ei tööta" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Lülita Bluetooth sisse" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Sirvi seadmeid" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Tuntud seadmed" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Lisa seade" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Bluetoothi seadistamine" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Tuvastatav" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Lülita Bluetooth välja" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 ühendatud seade" +msgstr[1] "%1 ühendatud seadet" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Ühtegi adapterit ei leitud" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth töötab" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth ei tööta" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Ühenda" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Bluetoothi failiedastuse abiline" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "Seadme UUID, kuhu failid saadetakse" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Saadetavad failid" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Ühenduse loomine: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Ühendumine seadmega %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Vali vähemalt üks fail:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Valitud failid: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Vali nimekirjast seade:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Uurimine" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Faili saatmine Bluetoothi kaudu" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Bluetoothi failiedastus" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Failide vastuvõtmise lubamine või keelamine" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Vastuvõetud failide salvestamise asukoht:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Kas lubada jagatud failide muutmist" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Failide jagamise lubamine või keelamine" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Kas nõuda PIN-i" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Välistel seadmetel lubatakse muuta jagatud faile" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Kogu KDE Bluetoothi lõimimise lubamine või keelamine" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Bluetoothi seadme nõustaja" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Edasi" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Lõpeta" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Bluetoothi nõustaja" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Seade paardumiseks" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Uurimine..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN käsitsi:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Seadme valimine" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Käivita nõustaja uuesti" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Sulge" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Seadme seadistamine nurjus" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Seadme %1 seadistamine nurjus" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Palun sisesta PIN klaviatuuril, kui see ilmub, ja vajuta klahvi Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Palun sisesta PIN oma seadmes, kui see ilmub" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Ühendumine seadmega %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Ühenduse loomine: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "On sama" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Ei ole sama" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Palun kinnita, et PIN, mida näitab \"%1\", on sama, mida näitab nõustaja." + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/eu/CMakeLists.txt b/bluedevil/po/eu/CMakeLists.txt new file mode 100644 index 00000000..98934e41 --- /dev/null +++ b/bluedevil/po/eu/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(eu ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/eu/bluedevil.po b/bluedevil/po/eu/bluedevil.po new file mode 100644 index 00000000..56406158 --- /dev/null +++ b/bluedevil/po/eu/bluedevil.po @@ -0,0 +1,1042 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Ignigo Salvador Azurmendi , 2010. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2010-08-27 16:18+0200\n" +"Last-Translator: Ignigo Salvador Azurmendi \n" +"Language-Team: Basque \n" +"Language: eu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.0\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Iñigo Salvador Azurmendi" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "xalba@euskalnet.net" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, fuzzy, kde-format +#| msgctxt "" +#| "Show a notification asking for authorize or deny access to this computer " +#| "from Bluetooth the %1 is the name of the bluetooth device" +#| msgid "%1 is requesting access to this computer" +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 konputagailu honetara sarbidea eskatzen ari da" + +#: daemon/helpers/authorize/authorize.cpp:47 +#, fuzzy +#| msgctxt "Button to authorize a bluetooth remote device to connect " +#| msgid "Authorize" +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Baimendu" + +#: daemon/helpers/authorize/authorize.cpp:48 +#, fuzzy +#| msgctxt "Button to authorize a bluetooth remote device to connect " +#| msgid "Authorize" +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Baimendu" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Ukatu" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDEren Bluetooth sistema" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, fuzzy, kde-format +#| msgctxt "" +#| "Showed in a notification when the Bluetooth mode is going to be changed " +#| "(for example to flight mode), the %1 is the name of the mode" +#| msgid "Change bluetooth mode to %1?" +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Aldatu bluetooth modua %1-era?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +#, fuzzy +#| msgctxt "Confirm the bluetooth mode change, showed in a notification button" +#| msgid "Confirm" +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Berretsi" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +#, fuzzy +#| msgctxt "Deny the bluetooth mdoe change, showed in a notification" +#| msgid "Deny" +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Ukatu" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, fuzzy, kde-format +#| msgctxt "" +#| "The text is showed in a knotification to know if the PIN is correct, %1 " +#| "is the remote bluetotoh device and %2 is the pin" +#| msgid "%1 is asking if the PIN is correct: %2" +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 PINa zuzena den galdetzen ari da: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, fuzzy, kde-format +#| msgctxt "" +#| "Showed in a notification to announce that a PIN is needed to acomplish a " +#| "pair action, %1 is the name of the bluetooth device" +#| msgid "Pin is needed to pair with %1" +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN behar da %1-ekin parekatzeko" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +#, fuzzy +#| msgctxt "Showed in the caption of a dialog where the user introduce the PIN" +#| msgid "Introduce the PIN" +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Sartu PINa" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, fuzzy, kde-format +#| msgctxt "" +#| "Showed in a dialog which ask to introduce a PIN that will be used tu pair " +#| "a bluetooth device, %1 is the name of the bluetooth device" +#| msgid "" +#| "In order to pair this computer with %1 you have to enter a PIN - please " +#| "do so below." +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Konputagailu hau %1-ekin parekatzeko PIN bat sartu behar duzu - Mesedez, " +"egin ezazu azpian" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +#, fuzzy +#| msgctxt "Showed in the caption of a dialog where the user introduce the PIN" +#| msgid "Introduce the PIN" +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Sartu PINa" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +#, fuzzy +#| msgid "KDE Bluetooth System" +msgid "Bluetooth Daemon" +msgstr "KDEren Bluetooth sistema" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Mantentzailea" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "BlueDevil" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "BlueDevil" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, fuzzy, kde-format +#| msgctxt "" +#| "Show a notification asking for authorize or deny an incoming file " +#| "transfer to this computer from a Bluetooth device." +#| msgid "%1 is sending you the file %2" +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 %2 fitxategia bidaltzen ari zaizu" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Onartu" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Utzi" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +#, fuzzy +#| msgid "KDE Bluetooth System" +msgid "Send via Bluetooth" +msgstr "KDEren Bluetooth sistema" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "" +msgstr[1] "" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "" +msgstr[1] "" + +#: kcmodule/bluedeviladapters.cpp:242 +#, fuzzy +#| msgid "KDE Bluetooth System" +msgid "Bluetooth Adapters" +msgstr "KDEren Bluetooth sistema" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:320 +#, fuzzy +#| msgid "KDE Bluetooth System" +msgid "Enable KDE Bluetooth Integration" +msgstr "KDEren Bluetooth sistema" + +#: kcmodule/bluedevildevices.cpp:325 +#, fuzzy +#| msgid "BlueDevil" +msgid "Bluetooth Devices" +msgstr "BlueDevil" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +msgid "Connect" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:446 +msgid "Re-connect" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:52 +#, fuzzy +#| msgid "KDE Bluetooth System" +msgid "Bluetooth Transfer" +msgstr "KDEren Bluetooth sistema" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "" + +#: kcmodule/devicedetails.cpp:56 +#, fuzzy +#| msgctxt "" +#| "Button to trust a bluetooth remote device and authorize it to connect" +#| msgid "Trust" +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Konfidatu" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +#, fuzzy +#| msgctxt "" +#| "Button to accept the incoming file transfer and show a SaveAs.. dialog " +#| "that will let the user choose where will the file be downloaded to" +#| msgid "Save as..." +msgid "Save files in:" +msgstr "Gorde honela" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +#, fuzzy +#| msgctxt "" +#| "Button to accept the incoming file transfer and show a SaveAs.. dialog " +#| "that will let the user choose where will the file be downloaded to" +#| msgid "Save as..." +msgid "Share Files:" +msgstr "Gorde honela" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +#, fuzzy +#| msgctxt "" +#| "Button to accept the incoming file transfer and show a SaveAs.. dialog " +#| "that will let the user choose where will the file be downloaded to" +#| msgid "Save as..." +msgid "Shared Files" +msgstr "Gorde honela" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgctxt "" +#| "Button to accept the incoming file transfer and show a SaveAs.. dialog " +#| "that will let the user choose where will the file be downloaded to" +#| msgid "Save as..." +msgid "Shared Folder:" +msgstr "Gorde honela" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "" + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "" + +#: monolithic/main.cpp:28 +#, fuzzy +#| msgid "BlueDevil" +msgid "Bluetooth" +msgstr "BlueDevil" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Garatzailea" + +#: monolithic/monolithic.cpp:42 +msgid "Browse device" +msgstr "" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +#, fuzzy +#| msgctxt "" +#| "Button to accept the incoming file transfer and show a SaveAs.. dialog " +#| "that will let the user choose where will the file be downloaded to" +#| msgid "Save as..." +msgid "Send Files" +msgstr "Gorde honela" + +#: monolithic/monolithic.cpp:128 +#, fuzzy +#| msgid "BlueDevil" +msgid "Bluetooth is Off" +msgstr "BlueDevil" + +#: monolithic/monolithic.cpp:134 +#, fuzzy +#| msgid "KDE Bluetooth System" +msgid "Turn Bluetooth On" +msgstr "KDEren Bluetooth sistema" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "" + +#: monolithic/monolithic.cpp:178 +#, fuzzy +#| msgid "KDE Bluetooth System" +msgid "Turn Bluetooth Off" +msgstr "KDEren Bluetooth sistema" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "" +msgstr[1] "" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "" + +#: monolithic/monolithic.cpp:389 +#, fuzzy +#| msgid "BlueDevil" +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "BlueDevil" + +#: monolithic/monolithic.cpp:391 +#, fuzzy +#| msgid "BlueDevil" +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "BlueDevil" + +#: monolithic/monolithic.cpp:441 +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "" + +#: sendfile/main.cpp:32 +#, fuzzy +#| msgid "Bluedevil helper" +msgid "Bluetooth Send File Helper" +msgstr "Bluedevil laguntzailea" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "" + +#: sendfile/sendfilewizard.cpp:60 +#, fuzzy +#| msgid "KDE Bluetooth System" +msgid "Bluetooth Send Files" +msgstr "KDEren Bluetooth sistema" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +#, fuzzy +#| msgid "KDE Bluetooth System" +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "KDEren Bluetooth sistema" + +#: wizard/bluewizard.cpp:41 +#, fuzzy +#| msgid "BlueDevil" +msgid "Bluetooth Device Wizard" +msgstr "BlueDevil" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "" + +#: wizard/main.cpp:39 +#, fuzzy +#| msgctxt "" +#| "Showed in a notification to announce that a PIN is needed to acomplish a " +#| "pair action, %1 is the name of the bluetooth device" +#| msgid "Pin is needed to pair with %1" +msgid "Device to pair with" +msgstr "PIN behar da %1-ekin parekatzeko" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "" + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, kde-format +msgid "Connecting to %1..." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +msgid "Connecting to:" +msgstr "" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" + +#, fuzzy + + + + +#, fuzzy + + + + +#, fuzzy + + + + +#, fuzzy + + + + + + + + + + +#, fuzzy + + + + + + + +#, fuzzy + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/fa/CMakeLists.txt b/bluedevil/po/fa/CMakeLists.txt new file mode 100644 index 00000000..8c26c772 --- /dev/null +++ b/bluedevil/po/fa/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(fa ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/fa/bluedevil.po b/bluedevil/po/fa/bluedevil.po new file mode 100644 index 00000000..1c681d67 --- /dev/null +++ b/bluedevil/po/fa/bluedevil.po @@ -0,0 +1,933 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Mohammad Reza Mirdamadi , 2012. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2012-02-29 00:17+0330\n" +"Last-Translator: Mohammad Reza Mirdamadi \n" +"Language-Team: Farsi (Persian) \n" +"Language: fa\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Lokalize 1.4\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "محمدرضا میردامادی" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "mohi@linuxshop.ir" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "انکار" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "تصدیق" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "انکار" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +#, fuzzy +msgid "Maintainer" +msgstr "نگه‌دارنده" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "بلوتوث" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +#, fuzzy +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "پذیرفتن" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +#, fuzzy +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "لغو" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +#, fuzzy +msgctxt "File transfer origin" +msgid "From" +msgstr "از:" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +#, fuzzy +msgctxt "File transfer destination" +msgid "To" +msgstr "به:" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +#, fuzzy +#| msgid "Add Device..." +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "افزودن دستگاه..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +#, fuzzy +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "غیره" + +#: kcmodule/bluedeviladapters.cpp:51 +#, fuzzy +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "(مخفی)" + +#: kcmodule/bluedeviladapters.cpp:52 +#, fuzzy +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "همیشه مرئی" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:93 +#, fuzzy, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "دقیقه:" +msgstr[1] "%1 دقیقه" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "نام" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, fuzzy, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "دقیقه:" +msgstr[1] "%1 دقیقه" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "جزئیات" + +#: kcmodule/bluedevildevices.cpp:363 +#, fuzzy +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "حذف" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "اتصال" + +#: kcmodule/bluedevildevices.cpp:367 +#, fuzzy +msgid "Disconnect" +msgstr "قطع کردن" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "افزودن دستگاه..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "اتصال" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "ناشناخته" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "تلفن" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "مودم" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "رایانه" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "شبکه" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "صوتی" + +#: kcmodule/bluedevildevices.cpp:642 +#, fuzzy +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "صفحه کلید" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "موشی" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "دوربین" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "چاپگر" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "نوع: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "هرگز" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "هرگز" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "همیشه" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "فقط خواندنی" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "نام" + +#: kcmodule/devicedetails.cpp:45 +#, fuzzy +msgctxt "Alias of the device" +msgid "Alias" +msgstr "نام‌گردان" + +#: kcmodule/devicedetails.cpp:49 +#, fuzzy +msgctxt "Physical address of the device" +msgid "Address" +msgstr "نشانی:" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "" + +#: kcmodule/devicedetails.cpp:54 +#, fuzzy +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "بازداشته‌شده" + +#: kcmodule/devicedetails.cpp:56 +#, fuzzy +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "معتمد:" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +#, fuzzy +msgid "Receiving" +msgstr "دریافت‌" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +#, fuzzy +msgid "Sharing" +msgstr "اشتراک" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +#, fuzzy +msgid "Share Files:" +msgstr "ارسال پرونده‌ها‌" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "مجوزها:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +#, fuzzy +msgid "Shared Files" +msgstr "ارسال پرونده‌ها‌" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +msgid "Shared Folder:" +msgstr "ارسال پرونده‌ها‌" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +#, fuzzy +msgid "Send File" +msgstr "ارسال &پرونده‌" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "" + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "بلوتوث" + +#: monolithic/main.cpp:32 +#, fuzzy +msgid "Developer" +msgstr "توسعه‌دهنده" + +#: monolithic/monolithic.cpp:42 +msgid "Browse device" +msgstr "" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +#, fuzzy +msgid "Send Files" +msgstr "ارسال پرونده‌ها‌" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "افزودن دستگاه" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "" +msgstr[1] "" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "اتصال" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, fuzzy, no-c-format, kde-format +#| msgid "Connecting..." +msgid "Connecting to: %1" +msgstr "اتصال..." + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "پویش" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "بعدی" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "پایان" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "پویش..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "" + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "بستن" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgid "Connecting..." +msgid "Connecting to %1..." +msgstr "اتصال..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting..." +msgid "Connecting to:" +msgstr "اتصال..." + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" + +#, fuzzy + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/fi/CMakeLists.txt b/bluedevil/po/fi/CMakeLists.txt new file mode 100644 index 00000000..846247c9 --- /dev/null +++ b/bluedevil/po/fi/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(fi ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/fi/bluedevil.po b/bluedevil/po/fi/bluedevil.po new file mode 100644 index 00000000..1ea9307e --- /dev/null +++ b/bluedevil/po/fi/bluedevil.po @@ -0,0 +1,913 @@ +# Finnish messages for bluedevil. +# Copyright © 2011 This_file_is_part_of_KDE +# This file is distributed under the same license as the extragear-base package. +# Tommi Nieminen , 2011. +# Jorma Karvonen , 2011. +# Lasse Liehu , 2011, 2012, 2013. +# +# KDE Finnish translation sprint participants: +# Author: Artnay +# Author: Lliehu +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-11-12 21:47+0200\n" +"Last-Translator: Lasse Liehu \n" +"Language-Team: Finnish \n" +"Language: fi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-POT-Import-Date: 2012-12-01 22:20:01+0000\n" +"X-Generator: Lokalize 1.5\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Tommi Nieminen,Jorma Karvonen" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "translator@legisign.org,karvonen.jorma@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 pyytää yhteyttä tähän tietokoneeseen" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Luota ja valtuuta" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Vain valtuuta" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Torju" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE:n Bluetooth-järjestelmä" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Vaihdetaanko Bluetooth-tilaan ’%1’ ?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Vahvista" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Torju" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 kysyy, onko PIN oikein: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN on oikein" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN ei ole oikea" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN tarvitaan parin muodostamiseksi laitteeseen %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Anna PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Parin muodostamiseksi tämän tietokoneen ja laitteen %1 välille sinun on " +"annettava PIN. Anna se alhaalla." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Anna PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Bluetooth-palvelu" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Ylläpitäjä" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetooth-laitteet" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 lähettää sinulle tiedoston %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Hyväksy" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Peru" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Vastaanotetaan tiedosto Bluetoothista" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Lähde" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Kohde" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Lähetä Bluetoothilla" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Löydä laite..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Muu..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Piilotettu" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Aina näkyvissä" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Väliaikaisesti näkyvissä" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuutti" +msgstr[1] "%1 minuuttia" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Nimi" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Virta kytketty" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Näkyvyys" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Löytämisaika" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Oletussovitin: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Sovitin: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuutti" +msgstr[1] "%1 minuuttia" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetooth-sovittimet" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Bluetooth-sovittimien asetusosio" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Kehittäjä ja ylläpitäjä" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Sovittimia ei löytynyt. Liitä jokin." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Käytä KDE:n Bluetooth-yhteensopivuutta" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth-laitteet" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Bluetooth-laitteiden asetusosio" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Lisätiedot" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Poista" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Yhdistä" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Katkaise yhteys" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Lisää laite..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Yhdistä" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Valitse uusi alias kohteelle %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Haluatko varmasti poistaa laitteen ”%1” tunnettujen laitteiden luettelosta?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Laitteen poisto" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Etälaitteita ei ole lisätty" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Lisää laite napsauttamalla tästä" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Tuntematon" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Puhelin" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modeemi" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Tietokone" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Verkko" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Kuulokkeet" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Kuulokkeet" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Ääni" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Näppäimistö" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Hiiri" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Tulostin" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Peliohjain" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Sormitietokone" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tyyppi: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth-siirto" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Bluetooth-siirron asetusosio" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Ei koskaan" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Luotetut laitteet" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Lisää laitteita" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Ei koskaan" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Aina" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Kirjoitussuojattu" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Muokkaa ja lue" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Nimi" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Osoite" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Kytketty pariksi" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Teljetty" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Luotettu" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Bluetooth-sovittimia ei löytynyt." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Korjaa" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Oletus-Bluetooth-sovitin ei näy etälaitteille." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Vuorovaikutus Bluetooth-järjestelmän kanssa ei ole optimaalista." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetoothia ei ole täysin otettu käyttöön." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Vastaanotto" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Tallenna tiedostot kohteeseen:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Hyväksy automaattisesti:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Vastaanota tiedostoja:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Jakaminen" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Jaa tiedostoja:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Pyydä PIN-tunnusta:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Käyttöoikeudet:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Jaetut tiedostot" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Jaettu kansio:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Lähetä tiedosto" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Selaa tiedostoja" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Etsitään laitteita..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Etsitään uusia laitteita..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp-palvelu" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Noudetaan tietoa etälaitteesta..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Yhdistetään laitteeseen" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Kehittäjä" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Selaa laitteita" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Lähetä tiedostoja" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth ei ole käytössä" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Ota Bluetooth käyttöön" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Selaa laitteita" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Tunnetut laitteet" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Lisää laite" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Bluetooth-asetukset" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Löydettävissä" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Ota Bluetooth pois käytöstä" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 yhdistetty laite" +msgstr[1] "%1 yhdistettyä laitetta" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Sovittimia ei löytynyt" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth on käytössä" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth ei ole käytössä" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Yhdistä" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Bluetooth-tiedostonlähetysavustaja" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "Kohdelaitteen UUID-tunniste" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Lähetettävät tiedostot" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Yhdistetään kohteeseen: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Yhdistetään laitteeseen %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Valitse yksi tai useampia tiedostoja:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Valitut tiedostot: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Valitse laite luettelosta:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Etsitään" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Lähetetään tiedosto Bluetoothilla" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Bluetooth-tiedostonlähetys" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Ota käyttöön tai poista käytöstä tiedostojen vastaanotto" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Tallenna vastaanotetut tiedostot kohteeseen:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Sallitaanko jaettujen tiedostojen muokkaus" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Ota käyttöön tai poista käytöstä tiedostojen jakaminen" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Pyydetäänkö PIN-tunnusta" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Salli ulkoisten laitteiden muokata jaettuja tiedostoja" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Ota käyttöön tai poista käytöstä yleinen KDE Bluetooth -yhdentyminen" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Opastettu Bluetooth-laitteen asetus" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Seuraava" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Lopeta" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Opastettu Bluetoothin asetus" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Laite, jonka kanssa muodostetaan pari" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Etsitään..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Manuaalinen PIN-tunniste:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Valitse laite" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Käynnistä opastettu toiminto uudelleen" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Sulje" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Laitteen asetus epäonnistui" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Kohteen %1 asetus epäonnistui" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Ota käyttöön PIN-tunniste näppäimistölläsi, kun se ilmaantuu näkyviin ja " +"paina sen jälkeen Enter-painiketta" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Ota käyttöön PIN-tunniste laitteessasi, kun se ilmaantuu näkyviin" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Yhdistetään laitteeseen %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Yhdistetään kohteeseen: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Täsmää" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Ei täsmää" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "Vahvista, että näytetty PIN kohteessa ”%1” vastaa tässä näytettyä." \ No newline at end of file diff --git a/bluedevil/po/fr/CMakeLists.txt b/bluedevil/po/fr/CMakeLists.txt new file mode 100644 index 00000000..5d2f6efc --- /dev/null +++ b/bluedevil/po/fr/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(fr ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/fr/bluedevil.po b/bluedevil/po/fr/bluedevil.po new file mode 100644 index 00000000..bd7b1b3f --- /dev/null +++ b/bluedevil/po/fr/bluedevil.po @@ -0,0 +1,918 @@ +# translation of bluedevil.po to Français +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# Joëlle Cornavin , 2010, 2011, 2012. +# Vincent PINON , 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-12-06 15:33+0100\n" +"Last-Translator: Vincent PINON \n" +"Language-Team: French \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Environment: kde\n" +"X-Accelerator-Marker: &\n" +"X-Text-Markup: kde4\n" +"X-Generator: Lokalize 1.5\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Joëlle Cornavin" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "jcorn@free.fr" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 demande l'accès à cet ordinateur" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Faire confiance et autoriser" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Autoriser seulement" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Refuser" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Système Bluetooth pour KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Changer le mode Bluetooth en %1 ?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Confirmer" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Refuser" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 demande si le code PIN est correct : %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN correct" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN incorrect" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN :" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Le code PIN est nécessaire pour l'appariement à %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Introduire le code PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Pour pouvoir apparier cet ordinateur à %1, vous devez saisir un code PIN. " +"Veuillez le faire ci-dessous." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Introduire le code PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Démon Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, Les codeurs UFO" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Mainteneur" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Périphériques Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 vous envoie en ce moment le fichier %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Accepter" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Annuler" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Réception d'un fichier via Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Depuis" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Vers" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Envoyer via Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Chercher un périphérique..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Autre..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Masqué" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Toujours visible" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Temporairement visible" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minute" +msgstr[1] "%1 minutes" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Nom" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "En cours de fonctionnement" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Visibilité" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Temps de découverte" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Adaptateur par défaut : %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adaptateur : %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minute" +msgstr[1] "%1 minutes" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Adaptateurs Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Module du Centre de Configuration pour les adaptateurs Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Développeur et mainteneur" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Aucun adaptateur n'a été trouvé. Veuillez en connecter un." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Activer l'intégration Bluetooth pour KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Périphériques Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Module du Centre de Configuration pour les périphériques Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Détails" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Supprimer" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Connecter" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Déconnecter" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Ajouter un périphérique..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Connecter" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Choisir un nouvel alias pour %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Voulez-vous vraiment supprimer le périphérique « %1 » de la liste des " +"périphériques connus ?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Suppression du périphérique" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Aucun périphérique distant n'a été ajouté" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Cliquez ici pour ajouter un périphérique distant" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Inconnu" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Téléphone" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Ordinateur" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Réseau" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Casque" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Écouteurs" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Audio" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Clavier" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Souris" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Appareil photo" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Imprimante" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablette" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Type : %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Transfert Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Module du Centre de Configuration pour les transferts Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Jamais" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Périphériques de confiance" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Tous les périphériques" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Jamais" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Toujours" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Lecture seule" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Modification et lecture" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Nom" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adresse" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Apparié" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Bloqué" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Fiable" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Aucun adaptateur Bluetooth n'a été trouvé. Veuillez en connecter un." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Corriger ce problème" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"Votre adaptateur Bluetooth par défaut n'est pas visible pour les " +"périphériques distants." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "L'interaction avec le système Bluetooth n'est pas optimale." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth n'est pas complètement activé." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Réception" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Enregistrer des fichiers dans :" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Accepter automatiquement :" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Réceptionner des fichiers :" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Partage" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Partager des fichiers :" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Exiger le PIN :" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Droits d'accès :" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Fichiers partagés" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Dossier partagé :" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Envoyer un fichier" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Explorer des fichiers" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Réception des services..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Recherche de nouveaux périphériques..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Démon ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Réception d'informations à partir du périphérique distant..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Connexion au périphérique" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Développeur" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Explorer les périphériques" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Envoyer des fichiers" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth est désactivé" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Activer Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Explorer les périphériques" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Périphériques connus" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Ajouter un périphérique" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Configurer Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Découvrable" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Désactiver Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 périphérique connecté" +msgstr[1] "%1 périphériques connectés" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Aucun adaptateur n'a été trouvé" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth est activé" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth est désactivé" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Connecter" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Assistant d'envoi de fichiers via Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID du périphérique où les fichiers seront envoyés" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Les fichiers à envoyer" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Connexion à : %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Connexion à %1 en cours..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Sélectionnez un ou plusieurs fichiers :" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Fichiers sélectionnés : %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Sélectionnez un périphérique dans la liste :" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Analyse" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Envoi d'un fichier via Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Envoi d'un fichier via Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Activer ou désactiver la réception de fichiers" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Enregistrer les fichiers reçus dans :" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "" +"Décider s'il faut ou non autoriser la modification de fichiers partagés" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Activer ou désactiver le partage de fichiers" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Décider s'il faut ou non exiger le PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" +"Autoriser les périphériques extérieurs à modifier les fichiers partagés" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Activer ou désactiver toute l'intégration Bluetooth pour KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Assistant de connexion de périphérique Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Suivant" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Terminer" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Assistant Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Périphérique avec lequel effectuer l'appariement" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Analyse en cours..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN manuel :" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Sélectionnez un périphérique" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Redémarrer l'assistant" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Fermer" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "La configuration du périphérique a échoué" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "La configuration de %1 a échoué" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Veuillez introduire le PIN dans votre clavier lorsqu'il apparaît et appuyer " +"sur Entrée" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Veuillez introduire le PIN dans votre périphérique lorsqu'il apparaît" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Connexion à %1 en cours..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Connexion à : %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Correspond" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Ne correspond pas" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Veuillez confirmer que le PIN affiché sur « %1 » correspond à celui de " +"l'assistant." \ No newline at end of file diff --git a/bluedevil/po/ga/CMakeLists.txt b/bluedevil/po/ga/CMakeLists.txt new file mode 100644 index 00000000..a7b9865a --- /dev/null +++ b/bluedevil/po/ga/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(ga ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/ga/bluedevil.po b/bluedevil/po/ga/bluedevil.po new file mode 100644 index 00000000..33b1e5d3 --- /dev/null +++ b/bluedevil/po/ga/bluedevil.po @@ -0,0 +1,905 @@ +# Irish translation of bluedevil +# Copyright (C) 2011 This_file_is_part_of_KDE +# This file is distributed under the same license as the bluedevil package. +# Kevin Scannell , 2011. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2011-12-28 12:28-0500\n" +"Last-Translator: Kevin Scannell \n" +"Language-Team: Irish \n" +"Language: ga\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n < 11 ? " +"3 : 4\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Kevin Scannell" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "kscanne@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Córas Bluetooth KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "UAP:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Cothaitheoir" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Cealaigh" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Ó" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Go" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Aimsigh Gléas..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Eile..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Folaithe" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "" +msgstr[1] "" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Ainm" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "" +msgstr[1] "" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Ceangail" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Dícheangail" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Cuir Gléas Leis..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Ceangail" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +#, fuzzy +#| msgid "Send Files" +msgid "Share Files:" +msgstr "Seol Comhaid" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Ceadanna:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +#, fuzzy +#| msgid "Send Files" +msgid "Shared Files" +msgstr "Seol Comhaid" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Comhfhillteán" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "" + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Forbróir" + +#: monolithic/monolithic.cpp:42 +msgid "Browse device" +msgstr "" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Seol Comhaid" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "" +msgstr[1] "" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Ceangail" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Comhaid le seoladh" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Ag dul i dteagmháil le: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Ag dul i dteagmháil le %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Roghnaigh comhad nó comhaid:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Comhaid roghnaithe: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Á Scanadh" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Comhad á sheoladh trí Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Á Scanadh..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Ag dul i dteagmháil le %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Ag dul i dteagmháil le: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" \ No newline at end of file diff --git a/bluedevil/po/gl/CMakeLists.txt b/bluedevil/po/gl/CMakeLists.txt new file mode 100644 index 00000000..83ff9254 --- /dev/null +++ b/bluedevil/po/gl/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(gl ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/gl/bluedevil.po b/bluedevil/po/gl/bluedevil.po new file mode 100644 index 00000000..88f15cf5 --- /dev/null +++ b/bluedevil/po/gl/bluedevil.po @@ -0,0 +1,912 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Marce Villarino , 2013. +# Adrian Chaves Fernandez , 2013. +# Miguel Branco , 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-11-14 22:43+0100\n" +"Last-Translator: Marce Villarino \n" +"Language-Team: Galician \n" +"Language: gl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Lokalize 1.5\n" +"X-Environment: kde\n" +"X-Accelerator-Marker: &\n" +"X-Text-Markup: kde4\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Marce Villarino" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "mvillarino@users.sourceforge.net" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 solicita acceso a este computador" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Validar e autorizar" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Só autorizar" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Denegar" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Sistema de Bluetooth de KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Cambiar o modo de Bluetooth a «%1»?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Confirmar" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Denegar" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 pregunta se o PIN é correcto: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN correcto" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN incorrecto" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Necesítase un PIN para emparellarse con %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Introduza o PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Para emparellar este computador con %1, ten que introducir un PIN. Fágao a " +"continuación." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Introduza o PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Daemon de Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010 Os desenvolvedores de UFO" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Mantedor" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Dispositivos de Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 está a enviarlle o ficheiro %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Aceptar" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Cancelar" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Recibindo un ficheiro por Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Desde" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "A" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Enviar por Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Atopar un dispositivo…" + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Outro…" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Agochado" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Sempre visíbel" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Temporalmente visíbel" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuto" +msgstr[1] "%1 minutos" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Nome" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Acendido" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Visibilidade" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Tempo de descubrimento" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Adaptador predeterminado: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adaptador: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuto" +msgstr[1] "%1 minutos" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Adaptadores de Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Módulo do panel de control dos adaptadores de Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Desenvolvedor e mantedor." + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Non se atopou ningún adaptador. Conecte un." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Activar a integración de Bluetooth con KDE." + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Dispositivos de Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Módulo do panel de control dos dispositivos de Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detalles" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Retirar" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Conectar" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Desconectar" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Engadir un dispositivo…" + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Conectar" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Escolla un novo alcume para %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Está seguro de que quere eliminar o dispositivo «%1» da lista de " +"dispositivos coñecidos?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Retirada do dispositivo" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Non se engadiu ningún dispositivo remoto." + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Prema aquí para engadir un dispositivo remoto." + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Descoñecido" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Teléfono" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Módem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Computador" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Rede" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Cascos" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Auriculares" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Son" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Teclado" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Rato" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Cámara" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Impresora" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Mando" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tableta" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tipo: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Transferencia por Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Módulo do panel de control das transferencias por Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Nunca" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Dispositivos validados" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Todos os dispositivos" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Nunca" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Sempre" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Só para ler" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Modificar e ler" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Nome" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alcume" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Enderezo" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Emparellado" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Bloqueado" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Validado" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Non se atopou ningún adaptador de Bluetooth." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Arranxalo" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"Os dispositivos remotos non poden ver o seu adaptador de Bluetooth " +"predeterminado." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "A interacción co sistema Bluetooth non é óptima." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "O sistema Bluetooth non está completamente activado." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Recibindo" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Gardar os ficheiros en:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Aceptar automaticamente:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Recibir os ficheiros:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Compartindo" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Compartir os ficheiros:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Esixir un PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Permisos:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Ficheiros compartidos" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Cartafol compartido:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Enviar un ficheiro" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Explorar os ficheiros" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Obtendo os servizos…" + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Buscando novos dispositivos…" + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Daemon de ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Obtendo información do dispositivo remoto…" + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Conectado co dispositivo" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Desenvolvedor" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Examinar os dispositivos" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Enviar os ficheiros" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "O sistema Bluetooth está desactivado." + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Activar o sistema Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Examinar os dispositivos" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Dispositivos coñecidos" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Engadir un dispositivo" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Configurar o sistema Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Visíbel" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Desactivar o sistema Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "Un dispositivo conectado." +msgstr[1] "%1 dispositivos conectados." + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Non se atopou ningún adaptador." + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "O sistema Bluetooth está activado" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "O sistema Bluetooth está desactivado" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Conectar" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Asistente de envío de ficheiros por Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID do dispositivo ao que se enviarán os ficheiros" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Ficheiros para enviar" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Conectando con: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Conectando con %1…" + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Seleccione un ou máis ficheiros:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Ficheiros seleccionados: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Escolla un dispositivo da lista:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Explorando" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Enviando o ficheiro por Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Envío de ficheiros por Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Activar ou desactivar a recepción de ficheiros" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Gardar os ficheiros recibidos en:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Se permitir ou non a modificación de ficheiros compartidos" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Activar ou desactivar a compartición de ficheiros." + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Se esixir un PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Permitir aos dispositivos externos modificar os ficheiros compartidos" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Activar ou desactivar a integración global de Bluetooth con KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Asistente para dispositivos de Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Seguinte" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Rematar" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Asistente de Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Dispositivo co que emparellarse" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Explorando…" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN manual:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Seleccione un dispositivo" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Reiniciar o asistente" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Pechar" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Non foi posíbel configurar o dispositivo" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Non foi posíbel configurar %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Introduza o PIN no seu teclado cando apareza, e prema Intro" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Introduza o PIN no seu dispositivo cando apareza." + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Conectando con %1…" + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Conectando con: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Coincide" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Non coincide" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "Confirme que o PIN amosado en «%1» coincide co do asistente." \ No newline at end of file diff --git a/bluedevil/po/hu/CMakeLists.txt b/bluedevil/po/hu/CMakeLists.txt new file mode 100644 index 00000000..71d49af7 --- /dev/null +++ b/bluedevil/po/hu/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(hu ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/hu/bluedevil.po b/bluedevil/po/hu/bluedevil.po new file mode 100644 index 00000000..3f929804 --- /dev/null +++ b/bluedevil/po/hu/bluedevil.po @@ -0,0 +1,908 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Kristóf Kiszel , 2010, 2011. +# Tamas Krutki , 2011. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2011-10-08 13:36+0200\n" +"Last-Translator: Kristóf Kiszel \n" +"Language-Team: Hungarian \n" +"Language: KDE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Lokalize 1.2\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Tóth Csanád,Kiszel Kristóf" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "toth.csanad@kde.hu,ulysses@kubuntu.org" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 engedélyt kér a hozzáférésre a számítógéphez" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Megbízhatónak jelölés és azonosítás" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Csak azonosítás" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Elutasítás" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE Bluetooth Rendszer" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "A(z) %1 Bluetooth-mód kerüljön beállításra?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Megerősítés" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Elutasítás" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 kéri az azonosító kód megerősítését: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "Helyes PIN" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "Hibás PIN" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "%1 azonosító kódot kér a társításhoz" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "PIN megadása" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "Adja meg lentebb a PIN-kódját a számítógép és a(z) %1 párosításához." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "PIN megadása" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Bluetooth Daemon" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© UFO Coders, 2010." + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Karbantartó" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetooth eszközök" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 küldi a következő fájlt: %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Jóváhagyás" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Mégsem" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Fájl fogadása Bluetoothon" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Forrás:" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Cél:" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Küldés bluetoothon" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Eszköz keresése…" + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Egyéb" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Rejtett" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Mindig látható" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Ideiglenesen látható" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 perc" +msgstr[1] "%1 perc" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Név" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Tápellátás" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Láthatóság" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Láthatósági időtartam" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Alapértelmezett adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 perc" +msgstr[1] "%1 perc" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetooth adapterek" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Bluetooth adapterek rendszerbeállítások modul" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(C) Rafael Fernández López, 2010." + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Fejlesztő és karbantartó" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Nem található adapter. Csatlakoztasson egyet!" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "KDE bluetooth integráció engedélyezése" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth eszközök" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Bluetooth rendszerbeállítások modul" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Részletek" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Eltávolítás" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Kapcsolódás" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Leválasztás" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Eszköz hozzáadása…" + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Kapcsolódás" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Adjon meg új nevet a következő eszköznek: %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Biztosan eltávolítja a(z) „%1” eszközt az ismert eszközök listájáról?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Eszközeltávolítás" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Nem kerültek hozzáadásra távoli eszközök" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Kattintson ide távoli eszköz hozzáadásához" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Ismeretlen" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Számítógép" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Hálózat" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Headset" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Fülhallgató" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Audio" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Billentyűzet" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Egér" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Nyomtató" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablet" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Típus: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth átvitel" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Bluetooth átvitel rendszerbeállítások modul" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Soha" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Megbízható eszközök" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Minden eszköz" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Soha" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Mindig" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Csak olvasható" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Módosítás és olvasás" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Név" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Álnév" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Cím" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Társítva" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blokkolva" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Megbízható" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Nem található bluetooth adapter." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Javítás" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"Az alapértelmezett bluetooth adapter nem látható távoli eszközök számára." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "A bluetooth rendszer elérése nem optimális." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "A Bluetooth nincs teljesen bekapcsolva." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Fogadás" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Fájlok mentése:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Automatikus elfogadás." + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Fájlok fogadása:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Megosztás" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Fájlok megosztása:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "PIN szükséges:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Jogosultságok:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Megosztott fájlok" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Megosztott mappa" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Fájl küldése" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Fájlok böngészése" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Szolgáltatások beolvasása…" + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Új eszközök keresése…" + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp Daemon" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Adatok beolvasása a távoli eszközről…" + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Kapcsolódás az eszközhöz" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Fejlesztő" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Eszközök böngészése" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Fájlok küldése" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "A Bluetooth ki van kapcsolva" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Bluetooth bekapcsolása" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Eszközök böngészése" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Ismert eszközök" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Eszköz hozzáadása" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Bluetooth beállítása" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Észlelhető" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Bluetooth kikapcsolása" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 csatlakoztatott eszköz" +msgstr[1] "%1 csatlakoztatott eszköz" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Nem található adapter" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "A Bluetooth be van kapcsolva" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "A Bluetooth ki van kapcsolva" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Kapcsolódás" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Bluetooth Fájlküldési Segítő" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "Az eszköz UUID-je ahova a fájlok el lesznek küldve" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Elküldendő fájlok" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Kapcsolódás: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Kapcsolódás: %1…" + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Válasszon ki egy vagy több fájlt:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Kiválasztott fájlok: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Válasszon ki egy eszközt a listából:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Keresés" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Fájl küldése Bluetoothon" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Bluetooth - fájlok küldése" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Fájlfogadás engedélyezése vagy letiltása" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Fogadott fájlok mentése ide:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Engedélyezve van-e a megosztott fájlok módosítása" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "A fájlmegosztás engedélyezése vagy letiltása" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Szükséges-e PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "A megosztott fájlok módosításának engedélyezése külső eszközök számára" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "KDE Bluetooth-integráció engedélyezése vagy letiltása" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Bluetooth eszköz varázsló" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Tovább" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Befejezés" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Bluetooth varázsló" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Társítandó eszköz" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Keresés…" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Kézi PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Válasszon ki egy eszközt" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Varázsló újraindítása" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Bezárás" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Az eszköz telepítése sikertelen volt" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "A telepítés sikertelen volt: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Kérem, írja be a billentyűzetén a megjelenő PIN kódot majd nyomjon Entert" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Kérem, írja be az eszközén a megjelenő PIN kódot" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Kapcsolódás: %1…" + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Kapcsolódás: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Találatok" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Nem illeszkedik" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Kérem, erősítse meg, hogy a(z) \"%1\" eszközön megjelenített PIN megegyezik " +"a varázslóban láthatóval." \ No newline at end of file diff --git a/bluedevil/po/it/CMakeLists.txt b/bluedevil/po/it/CMakeLists.txt new file mode 100644 index 00000000..ed46c753 --- /dev/null +++ b/bluedevil/po/it/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(it ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/it/bluedevil.po b/bluedevil/po/it/bluedevil.po new file mode 100644 index 00000000..2c085860 --- /dev/null +++ b/bluedevil/po/it/bluedevil.po @@ -0,0 +1,909 @@ +# translation of bluedevil.po to Italian +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Pino Toscano , 2010, 2011, 2013. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-11-13 14:14+0100\n" +"Last-Translator: Pino Toscano \n" +"Language-Team: Italian \n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: KBabel 1.11.4\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Pino Toscano" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "toscano.pino@tiscali.it" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 richiede l'accesso a questo computer" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Dai fiducia e autorizza" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Autorizza solamente" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Rifiuta" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Sistema Bluetooth per KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Cambiare la modalità Bluetooth in «%1»?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Conferma" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Rifiuta" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 chiede se il PIN è corretto: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN corretto" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN non corretto" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN richiesto per associare %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Introduci PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Per associare questo computer con %1 devi inserire un PIN. Per favore " +"inseriscilo sotto." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Introduzione PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Demone Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Responsabile" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Dispositivi Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 ti sta inviando il file %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Accetta" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Annulla" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Ricezione file via Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Da" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "A" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Invia tramite Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Cerca dispositivo..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Altro..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Nascosto" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Sempre visibile" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Visibile temporaneamente" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuto" +msgstr[1] "%1 minuti" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Nome" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Alimentato" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Visibilità" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Tempo di visibilità" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Adattatore predefinito: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adattatore: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuto" +msgstr[1] "%1 minuti" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Adattatori Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Modulo del pannello di controllo degli adattatori Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Sviluppatore e responsabile" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Nessun adattatore trovato. Connetterne uno." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Abilita l'integrazione del Bluetooth in KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Dispositivi Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Modulo del pannello di controllo dei dispositivi Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Dettagli" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Rimuovi" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Connetti" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Disconnetti" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Aggiungi dispositivo..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Connetti" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Scegli un nuovo alias per %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Sei sicuro di voler rimuovere il dispositivo «%1» dalla lista dei " +"dispositivi conosciuti?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Rimozione dispositivo" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Nessun dispositivo remoto è stato aggiunto" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Fai clic qui per aggiungere un dispositivo remoto" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Sconosciuto" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefono" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Computer" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Rete" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Cuffie con microfono" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Cuffie" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Audio" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Tastiera" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Mouse" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Fotocamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Stampante" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tavoletta grafica" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tipo: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Trasferimento Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Modulo del pannello di controllo del trasferimento Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Mai" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Dispositivi di fiducia" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Tutti i dispositivi" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Mai" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Sempre" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Sola lettura" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Lettura e modifica" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Nome" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Indirizzo" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Associato" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Bloccato" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Di fiducia" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Nessun adattatore Bluetooth trovato." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Risolvi il problema" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"L'adattatore Bluetooth predefinito non è visibile da dispositivi remoti." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "L'interazione con il sistema Bluetooth non è ottimale." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Il Bluetooth non è abilitato completamente." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Ricezione" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Salva i file in:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Accetta automaticamente:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Ricevi i file:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Condivisione" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Condividi i file:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Richiedi PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Permessi:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "File condivisi" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Cartella condivisa:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Invio file" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Sfoglia file" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Recupero servizi..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Ricerca di nuovi dispositivi..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Demone ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Recupero informazioni dal dispositivo remoto..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Connessione al dispositivo" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Sviluppatore" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Sfoglia dispositivi" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Invio file" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Il Bluetooth è disattivato" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Attiva il Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Sfoglia dispositivi" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Dispositivi conosciuti" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Aggiungi dispositivo" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Configura il Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Visibile" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Disattiva il Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 dispositivo connesso" +msgstr[1] "%1 dispositivi connessi" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Nessun adattatore trovato" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Il Bluetooth è attivo" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Il Bluetooth è disattivato" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Connetti" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Programma di supporto per l'invio di file via Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID del dispositivo al quale inviare i file" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "File da inviare" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Connessione a: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Connessione a %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Seleziona uno o più file:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "File selezionati: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Scegli un dispositivo dalla lista:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Scansione" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Invio di file via Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Invio file via Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Abilita o disabilita la ricezione di file" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Salva i file ricevuti in:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Se permettere la modifica di file condivisi" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Abilita o disabilita la condivisione di file" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Se richiedere il PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Permetti ai dispositivi esterni di modificare i file condivisi" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Abilita o disabilita l'integrazione del Bluetooth in KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Procedura guidata dei dispositivi Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Successivo" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Fine" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Procedura guidata Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Dispositivo da associare" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Scansione..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN manuale:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Scegli un dispositivo" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Riavvia la procedura guidata" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Chiudi" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "La configurazione del dispositivo non è riuscita" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "La configurazione di %1 non è riuscita" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Introduci il PIN dalla tastiera quando appare e premi Invio" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Introduci il PIN nel dispositivo quando appare" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Connessione a %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Connessione a: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Corrisponde" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Non corrisponde" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Per favore conferma se il PIN mostrato su \"%1\" corrisponde a quello della " +"procedura guidata." \ No newline at end of file diff --git a/bluedevil/po/ja/CMakeLists.txt b/bluedevil/po/ja/CMakeLists.txt new file mode 100644 index 00000000..c34b0ddb --- /dev/null +++ b/bluedevil/po/ja/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(ja ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/ja/bluedevil.po b/bluedevil/po/ja/bluedevil.po new file mode 100644 index 00000000..31266063 --- /dev/null +++ b/bluedevil/po/ja/bluedevil.po @@ -0,0 +1,881 @@ +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2010-04-16 22:26+0900\n" +"Last-Translator: Japanese KDE translation team \n" +"Language-Team: Japanese \n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Accelerator-Marker: &\n" +"X-Text-Markup: kde4\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "" + +#: daemon/kded/bluezagent.cpp:192 +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "" +msgstr[1] "" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "" +msgstr[1] "" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +msgid "Connect" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:446 +msgid "Re-connect" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "" + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "" + +#: monolithic/monolithic.cpp:42 +msgid "Browse device" +msgstr "" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "" +msgstr[1] "" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:441 +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "" + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, kde-format +msgid "Connecting to %1..." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +msgid "Connecting to:" +msgstr "" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" \ No newline at end of file diff --git a/bluedevil/po/kk/CMakeLists.txt b/bluedevil/po/kk/CMakeLists.txt new file mode 100644 index 00000000..6cc80446 --- /dev/null +++ b/bluedevil/po/kk/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(kk ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/kk/bluedevil.po b/bluedevil/po/kk/bluedevil.po new file mode 100644 index 00000000..3c7f31bb --- /dev/null +++ b/bluedevil/po/kk/bluedevil.po @@ -0,0 +1,903 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Sairan Kikkarin , 2012, 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-01-29 04:20+0600\n" +"Last-Translator: Sairan Kikkarin \n" +"Language-Team: Kazakh \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Lokalize 1.2\n" + +# +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Сайран Киккарин" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "sairan@computer.org" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 осы компьютеріне қатынауды сұрайды" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Сену және қуаттау" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Тек қуаттау" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Бас тарту" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE Bluetooth жүйесі" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Bluetooth режімін '%1' дегенге өзгертілсін бе?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Құптау" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Бас тарту" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 деген PIN коды дұрыс па деп сұрауда: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN дұрыс" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN дұрыс емес" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN коды:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "%1 дегенмен жұптасуға PIN коды керек" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "PIN кодын келтіру" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Осы компьютер мен %1 жұптасу үшін PIN кодын келтіру керек. Төменде " +"келтіріңіз." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "PIN кодын келтіру" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Bluetooth кезекшісі" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Жетілдірушісі" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetooth Devices құрылығылар" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 деген %2 файлын Сізге жіберуде" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Қабылдау" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Қайту" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Bluetooth арқылы файлды қабылдау" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Қайсыдан" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Қайсыға" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Bluetooth арқылы жіберу" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Құрылғыны табу..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Басқа..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Жасырын" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Әрқашанда көрінетін" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Уақытша көрінетін" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 минут" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Атауы" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Қосылған" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Көрінетіні" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Көрінетін уақыты" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Әдетті адаптері: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Адаптері: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 минут" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetooth адаптерлері" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Bluetooth адаптерлерін басқару панелінің модулі" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Құрастырушы және жетілдіруші" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Адаптерлер табылған жоқ. Біреуін қосыңыз." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "KDE Bluetooth біріктіруін рұқсат ету" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth Devices құрылығылар" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Bluetooth құрылғыларын басқару панелінің модулі" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Егжей-тегжейі" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Өшіру" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Қосылу" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Ажырату" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Құрылғыны қосу..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Қосылу" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "%1 үшін жаңа бүркеншік атауын беру" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "\"%1\" дегенді мәлім құрылғылар тізімінен шын өшірмексіз бе?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Құрылғыны кетіру" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Қашықтағы құрылғылар тізімге кірмеді" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Қашықтағы құрылғыны қосу үшін осында түртіңіз" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Беймәлім" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Телефон" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Модем" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Компьютер" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Желі" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Гарнитура" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Құлаққаптар" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Аудио" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Пернетақта" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Тышқан" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Фотокамера" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Принтер" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Ойын тізгіні" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Планшет" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Түрі: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth арқылы тасымалдау" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Bluetooth тасымалдауын басқару панелінің модулі" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Ешқашан" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Сенімді құрылғылар" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Бүкіл құрылғылар" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Ешқашан" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Әрқашан" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Тек оқу үшін" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Өзгерту мен оқу" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Атауы" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Бүркеншік атауы" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Адрес" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Жұпталған" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Бұғатталған" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Сенім артылған" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Bluetooth адаптерлері табылған жоқ." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Түзеу" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Әдетті bluetooth адаптері көрінбейді не қашықтағы құрылғы.." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Bluetooth жүйесімен әрекеттесу оңтайлы емес." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth толығымен рұқсат етілмеген." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Қабылдау" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Файлдарды мынада сақтау:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "А̀втоматты қабылдайтыны:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Қабылдайтын файлдар:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Ортактасу" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Ортақ файлдар:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "PIN коды:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Рұқсаттары:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Ортақ файлдар" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Ортақ қапшық" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Файлды жіберу" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Файлдарды шолу" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Қызметерін алып-шығу..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Жаңа құрылғыларды қарастыру..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp көмекші қызметі" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Қашық құрылғылардан мәліметті алу..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Серверімен байланысу" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Құрастырушы" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Құрылғыларды шолу" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Файлдарды жіберу" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth сөңдірілген" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Bluetooth-ді қосу" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Құрылғыларды шолу" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Мәлім құрылғылар" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Құрылғыны қосу" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Bluetooth баптауы" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Табылатын" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Bluetooth-ді сендіру" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 құрылғы байланыста" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Адаптерлер табылған жоқ" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth қосылған" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth сөңдірілген" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Қосылу" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Bluetooth файлды жіберу көмекшісі" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "файлдарды қабылдайтын құрылғының UUID-і" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Жіберілетін файлдар" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "%1 дегенге қосылу" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "%1 дегенге қосылу..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Жіберілетін бір не бірнеше файл:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Таңдалған файлдар: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Құрылғыны тізімінен таңдау:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Қарастыру" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Bluetooth арқылы файлды жіберу" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Bluetooth арқылы жіберілетін файлдар" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Файлдарды қабылдауын рұқсат ету не етпеу" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Қабылданған файлдарды мынада сақтау:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Ортақ файлдарды өзгертуге бола ма, жоқ па" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Файл ортақтастыруды рұқсат ету не етпеу" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "PIN коды керек па, жоқ па" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Ортақ файлдарды сыртқы құрылғыға өзгертуге рұқсат беру" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Жалпы жүйелік KDE Bluetooth біріктіруін рұқсат ету не етпеу" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Bluetooth құрылғы шебері" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Келесі" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Аяқтау" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Bluetooth шебері" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Қай құрлығымен жұптау" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Қарастыру..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN кодты теру:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr " 0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Құрылғыны таңдау" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Шеберін жаңадан жегу" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Жабу" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Құрылғыны баптау жаңылысы" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "%1 дегенді баптау жаңылысы" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "PIN коды көрсетілгенде оны пернетақтада теріп Enter пернесін басыңыз" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "құрылғы көрінгенде оның PIN кодын келтіріңіз" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "%1 дегенге қосылу..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "%1 дегенге қосылу" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Сәйкесті" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Сәйкесті емес" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "\"%1\" дегендегі PIN код шебердегімен сәйкестігін құптаңыз." \ No newline at end of file diff --git a/bluedevil/po/km/CMakeLists.txt b/bluedevil/po/km/CMakeLists.txt new file mode 100644 index 00000000..4bbd8579 --- /dev/null +++ b/bluedevil/po/km/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(km ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/km/bluedevil.po b/bluedevil/po/km/bluedevil.po new file mode 100644 index 00000000..1eabd2a4 --- /dev/null +++ b/bluedevil/po/km/bluedevil.po @@ -0,0 +1,961 @@ +# translation of bluedevil.po to Khmer +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# Khoem Sokhem , 2012. +# Seng Sutha , 2012. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2012-01-12 16:58+0700\n" +"Last-Translator: Seng Sutha \n" +"Language-Team: Khmer \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: WordForge 0.8 RC1\n" +"X-Language: km-KH\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "ខឹម សុខែម, ម៉ន ម៉េត, សេង សុត្ថា, ចាន់ សម្បត្តិរតនៈ, សុខ សុភា" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "" +"khoemsokhem@khmeros.info,​​mornmet@khmeros.info,sutha@khmeros.info," +"ratanak@khmeros.info,sophea@khmeros.info" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 កំពុង​ទាមទារ​ឲ្យ​ចូល​ដំណើរការ​​​​​នៅ​កុំព្យូទ័រ​នេះ" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "ជឿ​ទុកចិត្ត និង​អនុញ្ញាត" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "អនុញ្ញាត​តែប៉ុណ្ណោះ" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "បដិសេធ" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "ប្រព័ន្ធ​ប៊្លូធូស​របស់ KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "ផ្លាស់ប្ដូរ​របៀប​ប៊្លូធូស​ទៅ​ '%1' ?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "អះអាង" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "បដិសេធ" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 កំពុង​ស្នើ ប្រសិនបើ PIN គឺ​ត្រឹមត្រូវ ៖ %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN ត្រឹមត្រូវ" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN មិន​ត្រឹមត្រូវ" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN ចាំបាច់​ត្រូវ​គូ​ជាមួយ %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "ណែនាំ PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "ដើម្បី​​ដាក់​គូ​កុំព្យូទ័រ​ជាមួយ %1 អ្នក​ត្រូវ​បញ្ចូល PIN មួយ ។ សូម​បញ្ចូល​វា​នៅ​ខាងក្រោម ។" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "ណែនាំ​ PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "ដេមិន​របស់​ប៊្លូធូស" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "រក្សាសិទ្ធិ​ឆ្នាំ ២០១០ ដោយ UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "អ្នក​ថែទាំ" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "ឧបករណ៍​របស់​ប៊្លូធូស" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 កំពុង​ផ្ញើ​​ឯកសារ​ឲ្យ​អ្នក %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "ព្រម​ទទួល" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "បោះបង់" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "ទទួល​ឯកសារ​ពី​ប៊្លូធូស" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "ពី" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "ជូន​ចំពោះ​​" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "ផ្ញើ​តាម​ប៊្លូធូស" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +#, fuzzy +#| msgid "Add Device..." +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "បន្ថែម​ឧបករណ៍..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +#, fuzzy +#| msgid "Other" +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "ផ្សេង​ទៀត" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "ដែល​លាក់" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "អាច​មើល​ឃើញ​ជានិច្ច" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "អាច​មើល​ឃើញ​ជា​បណ្ដោះអាសន្ន" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "" +msgstr[1] "%1 នាទី" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "ឈ្មោះ" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "បាន​ផ្ដល់" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "ភាព​មើល​ឃើញ" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "ពេលវេលា​រក​ឃើញ" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "អាដាប់ទ័រ​លំនាំដើម ៖ %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "អាដាប់ទ័រ ៖ %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "" +msgstr[1] "%1 នាទី" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "អាដាប់ទ័រ​របស់​ប៊្លូធូស" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "ម៉ូឌុល​ផ្ទាំង​បញ្ជា​អាដាប់ទ័រ​របស់​ប៊្លូធូស" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "រក្សាសិទ្ធិ​ឆ្នាំ ២០១០ ដោយ Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "អ្នក​អភិវឌ្ឍន៍ និង​អ្នក​ថែទាំ" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "រក​មិន​ឃើញ​អាដាប់ទ័រ​ឡើយ ។ សូម​តភ្ជាប់ ។" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "បើក​ការ​រួមបញ្ចូល​ប៊្លូធូស​របស់ KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "ឧបករណ៍​របស់​ប៊្លូធូស" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "ម៉ូឌុល​ផ្ទាំង​បញ្ជា​ឧបករណ៍​របស់​ប៊្លូធូស" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "សេចក្ដី​លម្អិត" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "យកចេញ" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "តភ្ជាប់" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "ផ្ដាច់" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "បន្ថែម​ឧបករណ៍..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "តភ្ជាប់" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "រើស​ឈ្មោះ​ក្លែងក្លាយ​ថ្មី​​មួយ​​សម្រាប់ %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "តើ​អ្នក​ប្រាកដ​ជា​ចង់​យក​ឧបករណ៍​ \"%1\" ចេញ​ពី​បញ្ជី​នៃ​ឧបករណ៍​ដែល​មិន​ស្គាល់​ឬ ?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "ការ​យក​ឧបករណ៍​ចេញ" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "មិន​បាន​បន្ថែម​ឧបករណ៍​ពី​ចម្ងាយ​ឡើយ" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "ចុច​ទីនេះ​ដើម្បី​បន្ថែម​ឧបករណ៍​ពី​ចម្ងាយ" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "មិន​ស្គាល់" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "ទូរស័ព្ទ" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "ម៉ូដឹម" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "កុំព្យូទ័រ" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "បណ្ដាញ" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "ឧបករណ៍​សម្រាប់​ស្ដាប់" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "ឧបករណ៍​សម្រាប់​ស្ដាប់​ទូរស័ព្ទ" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "អូឌីយ៉ូ" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "ក្ដារចុច" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "កណ្ដុរ" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "ម៉ាស៊ីនថតរូប" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "ម៉ាស៊ីន​បោះពុម្ព" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablet" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "ប្រភេទ ៖ %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "ការ​ផ្ទេរ​ប៊្លូធូស" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "ម៉ូឌុល​ផ្ទាំង​បញ្ជា​អំពី​ការផ្ទេរ​ប៊្លូធូស" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "គ្មាន" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "ឧបករណ៍​ដែល​ជឿ​ទុកចិត្ត" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "ឧបករណ៍​ទាំងអស់" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "មិន" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "ជានិច្ច" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "បាន​តែអាន" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "កែប្រែ និង​អាន" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "ឈ្មោះ" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "ឈ្មោះ​ក្លែងក្លាយ" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "អាសយដ្ឋាន" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "ដាក់​ជា​គូរ" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "បាន​ទប់​ស្កាត់" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "ដែល​ជឿ​ទុកចិត្ត" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "រក​មិន​ឃើញ​អាដាប់ទ័រ​របស់​ប៊្លូធូស​ឡើយ ។" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "ជួសជុល​វា" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "អាដាប់ទ័រ​នៃ​ប៊្លូធូស​លំនាំដើម​របស់​អ្នក​មិន​អាច​មើល​ឃើញ​ឧបករណ៍​ពី​ចម្ងាយ​បាន​ទេ ។" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "អន្តរកម្ម​ជាមួយ​ប្រព័ន្ធ​របស់​ប៊្លូធូស​គឺ​មិន​ប្រសើរ​ឡើយ ។" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "ប៊្លូធូស​មិន​ត្រូវ​បាន​បើក​ដោយ​ពេញលេញ​​​ឡើយ ។" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "ទទួល" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "រក្សាទុក​ឯកសារ​ក្នុង ៖" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "ព្រម​ទទួល​ដោយ​ស្វ័យប្រវត្តិ ៖" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "ទទួល​ឯកសារ ៖" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "ការ​ចែករំលែក" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "ចែក​រំលែក​ឯកសារ ៖" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "ទាមទារ PIN ៖" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "សិទ្ធិ ៖" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "បាន​ចែករំលែក​ឯកសារ" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "បាន​ចែករំលែក​ថត" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "ផ្ញើ​ឯកសារ" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "រកមើល​ឯកសារ" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "កំពុង​ភ្ជាប់​សេវា..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +#, fuzzy +#| msgid "Scanning for remote devices..." +msgid "Scanning for new devices..." +msgstr "កំពុង​វិភាគ​រក​ឧបករណ៍​ពី​ចម្ងាយ..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ដេមិន​របស់ ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "កំពុង​យក​ព័ត៌មាន​ពី​ឧបករណ៍​ពី​ចម្ងាយ..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "ការ​តភ្ជាប់​ទៅកាន់​ឧបករណ៍" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "ប៊្លូធូស" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "អ្នក​អភិវឌ្ឍន៍" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "រកមើល​ឧបករណ៍" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "ផ្ញើ​ឯកសារ" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "បិទ​ប៊្លូធូស" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "បើក​ប៊្លូធូស" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "រកមើល​ឧបករណ៍" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "ឧបករណ៍​ដែល​មិន​ស្គាល់" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "បន្ថែម​ឧបករណ៍" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "កំណត់​រចនាសម្ព័ន្ធ​ប៊្លូធូស" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "ដែល​អាច​រក​ឃើញ" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "បិទ​ប៊្លូធូស" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "" +msgstr[1] "%1 ឧបករណ៍​ដែល​បាន​តភ្ជាប់" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "រក​មិន​ឃើញ​អាដាប់ទ័រ​ឡើយ" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "បើក​ប៊្លូធូស" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "បិទ​ប៊្លូធូស" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "តភ្ជាប់" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "អ្នក​ជំនួយ​ឯកសារ​​ផ្ញើ​របស់​ប៊្លូធូស" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "ឧបករណ៍​ UUID ដែល​ឯកសារ​នឹង​ត្រូវ​បាន​ផ្ញើ" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "ឯកសារ​ដែល​នឹង​​ត្រូវ​បាន​ផ្ញើ" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "តភ្ជាប់​ទៅកាន់ ៖ %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "កំពុង​តភ្ជាប់​ទៅកាន់ %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "ជ្រើស​ឯកសារ​មួយ ឬ​ច្រើន ៖" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "បាន​ជ្រើស​ឯកសារ ៖ %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "ជ្រើស​ឧបករណ៍​ពី​បញ្ជី ៖" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "ការ​វិភាគរក" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "ផ្ញើ​ឯកសារ​ទៅកាន់​ប៊្លូធូស" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "ផ្ញើ​ឯកសារ​របស់​ប៊្លូធូស" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "បិទ ឬ​បើក​ការ​ទទួល​ឯកសារ" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "រក្សាទុក​ឯកសារ​ដែល​បាន​ទទួល​ទៅកាន់ ៖" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "ទោះជា​អនុញ្ញាត​ឲ្យ​កែប្រែ​ឯកសារ​ដែល​បាន​ចែករំលែក​ក៏ដោយ" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "បើក ឬ​បិទ​ការ​ចែករំលែក​ឯកសារ" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "ទោះជា​​ទាមទារ PIN ក៏ដោយ" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "អនុញ្ញាត​ឲ្យ​ឧបករណ៍​​នៅ​ខាងក្រៅ​កែប្រែ​ឯកសារ​ដែល​បាន​ចែករំលែក" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "បើក ឬ​បិទ​ការ​រួមបញ្ចូល​ប៊្លូធូស​របស់ KDE សកល" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "អ្នក​ជំនួយការ​ឧបករណ៍​អំពី​ប៊្លូធូស" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "បន្ទាប់" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "បញ្ចប់" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "អ្នក​ជំនួយ​ការ​អំពី​ប៊្លូធូស" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "ឧបករណ៍​ត្រូវ​ដាក់​ជា​គូ​ជាមួយ" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "កំពុង​វិភាគ​រក..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN ដែល​ធ្វើ​ដោយ​ដៃ ៖" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "ជ្រើស​ឧបករណ៍" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "ចាប់ផ្ដើម​អ្នក​ជំនួយ​ការ​ឡើងវិញ" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "បិទ" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "បាន​បរាជ័យ​ក្នុង​ការ​រៀបចំ​ឧបករណ៍" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "បាន​បរាជ័យ​ក្នុង​ការ​រៀបចំ​ %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "សូម​ណែនាំ PIN នៅ​ក្នុង​ក្ដារចុច​របស់​អ្នក នៅ​ពេល​វា​លេចឡើង​ ហើយ​ចុច​បញ្ចូល" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "សូម​ណែនាំ​ PIN នៅ​​ក្នុង​ឧបករណ៍​របស់​អ្នក នៅ​ពេល​វា​លេចឡើង" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "កំពុង​តភ្ជាប់​ទៅកាន់ %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "តភ្ជាប់​ទៅកាន់ ៖ %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "ដំណូច" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "មិន​ផ្គូផ្គង" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "សូម​ បញ្ជាក់​ថា​ PIN ដែល​បាន​បង្ហាញ​នៅ​លើ​​ \"%1\" មាន​ការ​ផ្គូផ្គង​អ្នក​ជំនួយការ​ ។" + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/ko/CMakeLists.txt b/bluedevil/po/ko/CMakeLists.txt new file mode 100644 index 00000000..44ca541b --- /dev/null +++ b/bluedevil/po/ko/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(ko ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/ko/bluedevil.po b/bluedevil/po/ko/bluedevil.po new file mode 100644 index 00000000..59f7dfdb --- /dev/null +++ b/bluedevil/po/ko/bluedevil.po @@ -0,0 +1,1055 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Park Shinjo , 2010. +# Park Shinjo , 2011. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2011-02-27 02:04+0900\n" +"Last-Translator: Park Shinjo \n" +"Language-Team: Korean \n" +"Language: ko\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Lokalize 1.1\n" + +#, fuzzy +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Park Shinjo" + +#, fuzzy +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "kde@peremen.name" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1에서 이 컴퓨터에 접근하려고 하는 중" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "신뢰 및 인증" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "인증" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "거부" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE 블루투스 시스템" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, fuzzy, kde-format +#| msgctxt "" +#| "Showed in a notification when the Bluetooth mode is going to be changed " +#| "(for example to flight mode), the %1 is the name of the mode" +#| msgid "Change Bluetooth mode to %1 ?" +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "블루투스 모드를 %1(으)로 바꾸겠습니까?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "확인" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "거부" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1에서 PIN 코드를 확인하는 중: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN 올바름" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN 올바르지 않음" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "%1에 연결하기 위해서 PIN이 필요함" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "PIN 입력" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, fuzzy, kde-format +#| msgctxt "" +#| "Shown in a dialog which asks to introduce a PIN that will be used to pair " +#| "a bluetooth device, %1 is the name of the bluetooth device" +#| msgid "" +#| "In order to pair this computer with %1 you've to enter a PIN, do it below " +#| "please" +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"이 컴퓨터와 %1을(를) 연결하기 위해서 PIN이 필요한 경우 아래에 입력하십시오." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "PIN 입력" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "블루투스 데몬" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "관리자" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "블루투스 장치" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1에서 파일 %2을(를) 보내는 중" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "수락" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "취소" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +#, fuzzy +#| msgid "Receiving file over bluetooth" +msgid "Receiving file over Bluetooth" +msgstr "블루투스로 파일 받기" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "송신" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "수신" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +#, fuzzy +#| msgid "Sending file over bluetooth" +msgid "Send via Bluetooth" +msgstr "블루투스로 파일 보내기" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +#, fuzzy +#| msgid "Add Device..." +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "장치 추가..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "숨기기" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "항상 보이기" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "임시로 보이기" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1분" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "이름" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "전원 공급" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "가시성" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "발견 시간" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "기본 어댑터: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "어댑터: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1분" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "블루투스 어댑터" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "블루투스 어댑터 제어 모듈" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "개발자, 관리자" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "어댑터를 찾을 수 없습니다. 연결하십시오." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "KDE 블루투스 통합 사용하기" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "블루투스 장치" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "블루투스 장치 제어 모듈" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "삭제" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +msgid "Connect" +msgstr "연결" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "연결 끊기" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "장치 추가..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +msgid "Re-connect" +msgstr "연결" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "%1의 새 별명 추가" + +#: kcmodule/bluedevildevices.cpp:507 +#, fuzzy, kde-format +#| msgid "" +#| "Are you sure that you want to remove device \"%1\" from the list of known " +#| "devices?" +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "알려진 장치 목록에서 장치 \"%1\"을(를) 지우시겠습니까?" + +#: kcmodule/bluedevildevices.cpp:508 +#, fuzzy +#| msgctxt "Title of window that asks for confirmation when removing a device" +#| msgid "Device removal" +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "장치 삭제" + +#: kcmodule/bluedevildevices.cpp:577 +#, fuzzy +#| msgid "No Bluetooth adapters have been found." +msgid "No remote devices have been added" +msgstr "블루투스 어댑터가 없습니다." + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "알 수 없음" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "휴대폰" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "모뎀" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "컴퓨터" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "네트워크" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "헤드셋" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "헤드폰" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "오디오" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "키보드" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "마우스" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "카메라" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "프린터" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "조이스틱" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "태블릿" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "종류: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "블루투스 전송" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "블루투스 전송 제어 모듈" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:75 +#, fuzzy +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "장치 관리" + +#: kcmodule/bluedeviltransfer.cpp:76 +#, fuzzy +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "장치 추가..." + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "" + +#: kcmodule/devicedetails.cpp:44 +#, fuzzy +#| msgctxt "Name of the adapter" +#| msgid "Name" +msgctxt "Name of the device" +msgid "Name" +msgstr "이름" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "" + +#: kcmodule/devicedetails.cpp:52 +#, fuzzy +#| msgctxt "Whether the adapter is powered or not" +#| msgid "Powered" +msgctxt "Device is paired" +msgid "Paired" +msgstr "전원 공급" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "" + +#: kcmodule/devicedetails.cpp:56 +#, fuzzy +#| msgctxt "Trust a device" +#| msgid "Trust" +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "신뢰함" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "블루투스 어댑터가 없습니다." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "고치기" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "기본 블루투스 어댑터가 원격 장치에 보이지 않습니다." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "블루투스 시스템과의 통신이 최적화되지 않았습니다." + +#: kcmodule/systemcheck.cpp:215 +#, fuzzy +#| msgid "Bluetooth is not completely enabled. " +msgid "Bluetooth is not completely enabled." +msgstr "블루투스가 완전히 활성화되지 않았습니다." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +#, fuzzy +#| msgid "Device to send files to" +msgid "Save files in:" +msgstr "파일을 보낼 장치" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +#, fuzzy +#| msgid "Device to send files to" +msgid "Receive files:" +msgstr "파일을 보낼 장치" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +#, fuzzy +msgid "Share Files:" +msgstr "파일 보내기(&F)..." + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +#, fuzzy +msgid "Shared Files" +msgstr "파일 보내기(&F)..." + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +msgid "Shared Folder:" +msgstr "파일 보내기(&F)..." + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "파일 보내기" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "파일 탐색" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "서비스 가져오는 중..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +#, fuzzy +#| msgid "Scanning for remote devices..." +msgid "Scanning for new devices..." +msgstr "원격 장치 찾는 중..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp 데몬" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "원격 장치 정보 가져오는 중..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "장치에 연결하는 중" + +#: monolithic/main.cpp:28 +#, fuzzy +#| msgid "kiobluetooth" +msgid "Bluetooth" +msgstr "kiobluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "개발자" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse Files" +msgid "Browse device" +msgstr "파일 탐색" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +#, fuzzy +msgid "Send Files" +msgstr "파일 보내기(&F)..." + +#: monolithic/monolithic.cpp:128 +#, fuzzy +#| msgid "Bluetooth Devices" +msgid "Bluetooth is Off" +msgstr "블루투스 장치" + +#: monolithic/monolithic.cpp:134 +#, fuzzy +#| msgid "Bluetooth Daemon" +msgid "Turn Bluetooth On" +msgstr "블루투스 데몬" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "" + +#: monolithic/monolithic.cpp:161 +#, fuzzy +msgid "Add Device" +msgstr "장치 추가..." + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "" + +#: monolithic/monolithic.cpp:172 +#, fuzzy +#| msgctxt "How long the adapter will be discoverable" +#| msgid "Discover Time" +msgid "Discoverable" +msgstr "발견 시간" + +#: monolithic/monolithic.cpp:178 +#, fuzzy +#| msgid "Bluetooth Transfer" +msgid "Turn Bluetooth Off" +msgstr "블루투스 전송" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "" + +#: monolithic/monolithic.cpp:389 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "블루투스 장치" + +#: monolithic/monolithic.cpp:391 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "블루투스 장치" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "연결" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "블루투스 파일 전송 도우미" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "연결 대상: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, fuzzy, kde-format +#| msgctxt "Connecting to a bluetooth device" +#| msgid "Connecting to %1 ..." +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "%1에 연결 중..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +#, fuzzy +#| msgid "Connecting..." +msgid "Scanning" +msgstr "연결 중..." + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +#, fuzzy +#| msgid "Sending file over bluetooth" +msgid "Sending file over Bluetooth" +msgstr "블루투스로 파일 보내기" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "블루투스로 파일 보내기" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +#, fuzzy +#| msgid "Device to send files to" +msgid "Save received files to:" +msgstr "파일을 보낼 장치" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +#, fuzzy +#| msgid "Enable KDE Bluetooth Integration" +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "KDE 블루투스 통합 사용하기" + +#: wizard/bluewizard.cpp:41 +#, fuzzy +#| msgid "Bluetooth Devices" +msgid "Bluetooth Device Wizard" +msgstr "블루투스 장치" + +#: wizard/bluewizard.cpp:68 +#, fuzzy +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "다음" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +#, fuzzy +#| msgid "Connecting..." +msgid "Scanning..." +msgstr "연결 중..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "" + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a bluetooth device" +#| msgid "Connecting to %1 ..." +msgid "Connecting to %1..." +msgstr "%1에 연결 중..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "연결 대상: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + + + + + + + + +#, fuzzy + + + +#, fuzzy + + + + + + + + +#, fuzzy + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/lt/CMakeLists.txt b/bluedevil/po/lt/CMakeLists.txt new file mode 100644 index 00000000..ed0567c7 --- /dev/null +++ b/bluedevil/po/lt/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(lt ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/lt/bluedevil.po b/bluedevil/po/lt/bluedevil.po new file mode 100644 index 00000000..fafe0e35 --- /dev/null +++ b/bluedevil/po/lt/bluedevil.po @@ -0,0 +1,918 @@ +# Lithuanian translations for l package. +# Copyright (C) 2010 This_file_is_part_of_KDE +# This file is distributed under the same license as the l package. +# +# Andrius Štikonas , 2010. +# Donatas G. , 2010. +# Remigijus Jarmalavičius , 2011. +# Liudas Ališauskas , 2011. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2011-10-31 15:26+0000\n" +"Last-Translator: Liudas Ališauskas \n" +"Language-Team: Lithuanian \n" +"Language: lt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n%10>=2 && (n%100<10 || n" +"%100>=20) ? 1 : n%10==0 || (n%100>10 && n%100<20) ? 2 : 3);\n" +"X-Generator: Lokalize 1.2\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Andrius Štikonas, Liudas Ališauskas" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "stikonas@gmail.com, liudas@akmc.lt" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1: prašo prieigos prie šio kompiuterio" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Pasitikėti ir autorizuoti" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Tik autorizuoti" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Atmesti" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE Bluetooth sistema" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Pakeisti Bluetooth veikseną į „%1“?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Patvirtinti" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Atmesti" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 klausia, ar PIN yra teisingas: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN teisingas" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN neteisingas" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN, kuris reikalingas poravimui su %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Įveskite PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Norėdami suporuoti šį kompiuterį su %1 turite įvesti PIN. Padarykite tai " +"žemiau." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Įveskite PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Bluetooth tarnyba" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO programuotojai" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Prižiūrėtojas" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetooth įrenginiai" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 siunčia jums failą %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Priimti" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Atšaukti" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Gaunami failai per Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Iš" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Į" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Išsiųsti per Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Ieškoti įrenginių..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Kita..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Paslėptas" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Visuomet matomas" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Laikinai matomas" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 minutė" +msgstr[1] "%1 minutės" +msgstr[2] "%1 minučių" +msgstr[3] "%1 minutė" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Vardas" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Įgalintas" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Matomumas" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Aptikimo laikas" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Numatytasis adapteris: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adapteris: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 minutė" +msgstr[1] "%1 minutės" +msgstr[2] "%1 minučių" +msgstr[3] "%1 minutė" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetooth adapteris" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Bluetooth adapterio valdymo pulto modulis" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Programuotojas ir palaikytojas" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Adapterio rasti nepavyko. Prašome prijungti." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Įgalinti KDE Bluetooth integravimą" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth įrenginiai" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Bluetooth įrenginių valdymo pulto modulis" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Išsamiau" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Pašalinti" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Prisijungti" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Atsijungti" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Pridėti įrenginį..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Prisijungti" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Parinkite naują slapyvardį %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Ar tikrai norite pašalinti įrenginį „%1“ iš žinomų įrenginių sąrašo?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Įrenginio šalinimas" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Nepridėta jokių išorinių įrenginių" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Spragtelėkite čia norėdami pridėti įrenginį" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Nežinomas" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefonas" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modemas" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Kompiuteris" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Tinklas" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Ausinės" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Ausinės su mikrofonu" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Audio" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Klaviatūra" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Pelė" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Spausdintuvas" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Žaidimų pultas" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Bloknotas" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tipas: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth failų persiuntimas" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Bluetooth failų persiuntimo valdymo pulto modulis" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Niekada" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Patikimi įrenginiai" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Visi įrenginiai" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Niekada" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Visada" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Tik skaityti" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Skaityti ir rašyti" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Vardas" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alternatyvusis vardas" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adresas" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Suporuotas" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Užblokuotas" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Patikimi" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Nerasta Bluetooth adapterių" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Sutaisyti" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Numatytasis Bluetooth adapteris nėra matomas nutolusiems įrenginiams." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Sąveika su Bluetooth sistema nėra optimali." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth nėra pilnai įjungtas" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Gaunama" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Išsaugoti failus į:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Priimti automatiškai:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Failų gavimas:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Bendrinimas" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Bendrinti failus:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Reikalauti PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Teisės:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Bendrieji failai" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Bendrintas aplankas" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Siųsti failą" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Naršyti failus" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Gaunamas tarnybų sąrašas..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Ieškoma naujų įrenginių..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp tarnyba" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Gaunama informacija iš nutolusio įrenginio..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Jungiamasi prie įrenginio" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Programuotojas" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Naršyti įrenginių" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Siųsti failus" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth išjungtas" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Įjungti Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Naršyti įrenginių" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Žinomi įrenginiai" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Pridėti įrenginį" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Konfigūruoti Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Aptinkamas" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Išjungti Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 prijungtas įrenginys" +msgstr[1] "%1 prijungti įrenginiai" +msgstr[2] "%1 prijungtų įrenginių" +msgstr[3] "%1 prijungtas įrenginys" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Nepavyko rasti adapterio" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth įjungtas" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth išjungtas" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Prisijungti" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Bluetooth failų persiuntimo pagelbiklis" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "Įrenginio UUID kur bus siunčiami failai" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Failai kurie bus išsiųsti" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Jungiamasi prie %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Prisijungiama prie %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Pasirinkite vieną ar daugiau failų:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Pasirinkite failus: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Pasirinkite įrenginį iš sąrašo:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Ieškoma" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Failai siunčiami per Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Bluetooth failų siuntimas" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Failų gavimo įjungimas arba išjungimas" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Išsaugoti gautus failus į:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Ar leisti modifikuoti bendrintus failus" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Failų bendrinimo įjungimas arba išjungimas" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Ar reikalauti PIN kodo" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Leisti išoriniams įrenginiams modifikuoti bendrintus failus" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Įgalinti arba išjungti globalųjį KDE Bluetooth integravimą" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Bluetooth įrenginio vediklis" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Kitas" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Baigti" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Bluetooth vediklis" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Įrenginys, su kuriuo poruoti" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Ieškoma..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Rankinis PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Pasirinkite įrenginį" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Perkrauti vediklį" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Užverti" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Įrenginio diegimas nepavyko" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "%1 diegimas nepavyko" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Surinkite PIN kodą klaviatūroje, kai jo bus pareikalauta ir spauskite Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Įveskite PIN kodą, kai jis pasirodys ekrane" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Prisijungiama prie %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Jungiamasi prie %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Atitinka" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Neatitinka" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Patvirtinkite PIN kodą rodomą \"%1\" kad jis atitinką vediklyje įvestą." \ No newline at end of file diff --git a/bluedevil/po/mai/CMakeLists.txt b/bluedevil/po/mai/CMakeLists.txt new file mode 100644 index 00000000..669f8a52 --- /dev/null +++ b/bluedevil/po/mai/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(mai ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/mai/bluedevil.po b/bluedevil/po/mai/bluedevil.po new file mode 100644 index 00000000..3d9382cb --- /dev/null +++ b/bluedevil/po/mai/bluedevil.po @@ -0,0 +1,968 @@ +# translation of bluedevil.po to Maithili +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Rajesh Ranjan , 2010. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2010-09-24 12:01+0530\n" +"Last-Translator: Rajesh Ranjan \n" +"Language-Team: Maithili \n" +"Language: mai\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"\n" +"X-Generator: KBabel 1.11.4\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "संगीता कुमारी" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "sangeeta09@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "मना करू" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "संपुष्ट करू" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "मना करू" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "अनुरक्षक" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "एडयूर्डो रोब्लस एल्विरा" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Connecting..." +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "कनेक्ट कए रहल अछि...." + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "स्वीकारू" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "रद करू" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "सँ" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "केँ" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "नुकाएल" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "हरदम दृष्टिगोचर" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 मिनट" +msgstr[1] "%1 मिनट" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "नाम" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "दृश्यता" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 मिनट" +msgstr[1] "%1 मिनट" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "राफेल फर्नांडीज लोपेज" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "हटाबू" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "जोड़ू" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "डिसकनेक्ट करू" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "जोड़ू" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "अज्ञात" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "फोन" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "माडेम" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "कम्प्यूटर" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "नेटवर्क" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "हेडसेट" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "ऑडियो" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "कुँजीपटल" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "माउस" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "कैमरा" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "मुद्रक" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "प्रकार: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "" + +#: kcmodule/devicedetails.cpp:44 +#, fuzzy +#| msgctxt "Name of the adapter" +#| msgid "Name" +msgctxt "Name of the device" +msgid "Name" +msgstr "नाम" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +#, fuzzy +#| msgid "Send File" +msgid "Save files in:" +msgstr "फाइल पठाउ" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +#, fuzzy +#| msgid "Send File" +msgid "Share Files:" +msgstr "फाइल पठाउ" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +#, fuzzy +#| msgid "Send File" +msgid "Shared Files" +msgstr "फाइल पठाउ" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Send File" +msgid "Shared Folder:" +msgstr "फाइल पठाउ" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "फाइल पठाउ" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:154 +#, fuzzy +#| msgid "Connecting..." +msgid "Scanning for new devices..." +msgstr "कनेक्ट कए रहल अछि...." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:176 +#, fuzzy +#| msgid "Connecting..." +msgid "Connecting to the device" +msgstr "कनेक्ट कए रहल अछि...." + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "डेवलपर" + +#: monolithic/monolithic.cpp:42 +msgid "Browse device" +msgstr "" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +#, fuzzy +#| msgid "Send File" +msgid "Send Files" +msgstr "फाइल पठाउ" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "" +msgstr[1] "" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "जोड़ू" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, fuzzy, no-c-format, kde-format +#| msgid "Connecting..." +msgid "Connecting to: %1" +msgstr "कनेक्ट कए रहल अछि...." + +#: sendfile/pages/connectingpage.cpp:41 +#, fuzzy, kde-format +#| msgid "Connecting..." +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "कनेक्ट कए रहल अछि...." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +#, fuzzy +#| msgid "Connecting..." +msgid "Scanning" +msgstr "कनेक्ट कए रहल अछि...." + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "अगिला" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +#, fuzzy +#| msgid "Connecting..." +msgid "Scanning..." +msgstr "कनेक्ट कए रहल अछि...." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "" + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgid "Connecting..." +msgid "Connecting to %1..." +msgstr "कनेक्ट कए रहल अछि...." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting..." +msgid "Connecting to:" +msgstr "कनेक्ट कए रहल अछि...." + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" + + + + +#, fuzzy + + + + + +#, fuzzy + + + + +#, fuzzy + + + + + + + + + + + + +#, fuzzy + + + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/mr/CMakeLists.txt b/bluedevil/po/mr/CMakeLists.txt new file mode 100644 index 00000000..90b9f690 --- /dev/null +++ b/bluedevil/po/mr/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(mr ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/mr/bluedevil.po b/bluedevil/po/mr/bluedevil.po new file mode 100644 index 00000000..e3cd0bc2 --- /dev/null +++ b/bluedevil/po/mr/bluedevil.po @@ -0,0 +1,900 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Chetan Khona , 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-03-26 13:03+0530\n" +"Last-Translator: Chetan Khona \n" +"Language-Team: Marathi \n" +"Language: mr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Generator: Lokalize 1.5\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "चेतन खोना" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "chetan@kompkin.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "नकार" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "खात्री करा" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "नकार" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "योग्य पिन" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "अयोग्य पिन" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "पिन :" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "पालक" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Send Files" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "ब्लूटूथ फाईल्स पाठवा" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "स्वीकार करा" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "रद्द करा" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "पासून" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "पर्यंत" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "इतर..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "लपलेले" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "नेहमी दृश्यास्पद" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 मिनिट" +msgstr[1] "%1 मिनिटे" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "नाव" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "दृश्यता" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "अडॅप्टर : %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 मिनिट" +msgstr[1] "%1 मिनिटे" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "ब्लूटूथ अडाप्टर्स" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 राफेल फर्नांडेझ लोपेझ" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "राफेल फर्नांडेझ लोपेझ" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "विकासाकर्ता व पालक" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "तपशील" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "काढून टाका" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "जोडा" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "जुळवणी तोडा" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "जोडा" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "अपरिचीत" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "मोडेम" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "संगणक" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "संजाळ" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "ऑडिओ" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "कळफलक" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "माऊस" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "कॅमेरा" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "प्रिंटर" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "टाइप करा: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "कधीही नाही" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "सर्व साधने" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "कधीही नाही" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "नेहमी" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "फक्त वाचण्यासाठी" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "नाव" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "अलायस" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "पत्ता" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "शेअरींग" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "परवानगी :" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "सहभागीय फाईल्स" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "सहभागीय संचयीका" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "फाईल पाठवा" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "" + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "ब्लूटूथ" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "विकासकर्ता" + +#: monolithic/monolithic.cpp:42 +msgid "Browse device" +msgstr "" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "फाईल्स पाठवा" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "" +msgstr[1] "" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "जोडा" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "स्कॅन करत आहे" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "ब्लूटूथ द्वारे फाईल पाठवत आहे" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "ब्लूटूथ फाईल्स पाठवा" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "पुढील" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "संपले" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "स्कॅन करत आहे..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "" + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "बंद करा" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgid "Connecting..." +msgid "Connecting to %1..." +msgstr "जुळवत आहे..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting..." +msgid "Connecting to:" +msgstr "जुळवत आहे..." + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" \ No newline at end of file diff --git a/bluedevil/po/ms/CMakeLists.txt b/bluedevil/po/ms/CMakeLists.txt new file mode 100644 index 00000000..8efa6a32 --- /dev/null +++ b/bluedevil/po/ms/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(ms ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/ms/bluedevil.po b/bluedevil/po/ms/bluedevil.po new file mode 100644 index 00000000..6a495816 --- /dev/null +++ b/bluedevil/po/ms/bluedevil.po @@ -0,0 +1,980 @@ +# bluedevil Bahasa Melayu (Malay) (ms) +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Sharuzzaman Ahmat Raslan , 2010. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2010-11-14 22:52+0800\n" +"Last-Translator: Sharuzzaman Ahmat Raslan \n" +"Language-Team: Malay \n" +"Language: ms\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=1;\n" +"X-Generator: KBabel 1.11.4\n" + +#, fuzzy +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Sharuzzaman Ahmat Raslan" + +#, fuzzy +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "sharuzzaman@myrealbox.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:49 +#, fuzzy +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Halang" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +#, fuzzy +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Sah" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +#, fuzzy +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Halang" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +#, fuzzy +msgid "Maintainer" +msgstr "Penyelenggara" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Menyambung ke %1..." + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +#, fuzzy +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Terima" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +#, fuzzy +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Batal" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +#, fuzzy +msgctxt "File transfer origin" +msgid "From" +msgstr "Daripada" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +#, fuzzy +msgctxt "File transfer destination" +msgid "To" +msgstr "Kepada" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "" + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:51 +#, fuzzy +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Tersembunyi" + +#: kcmodule/bluedeviladapters.cpp:52 +#, fuzzy +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Sentiasa terampil" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:93 +#, fuzzy, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minit" +msgstr[1] "1 minit" + +#: kcmodule/bluedeviladapters.cpp:99 +#, fuzzy +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Nama" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:101 +#, fuzzy +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Keterampilan" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, fuzzy, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minit" +msgstr[1] "1 minit" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +#, fuzzy +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:363 +#, fuzzy +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Buang" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +msgid "Connect" +msgstr "Sambung" + +#: kcmodule/bluedevildevices.cpp:367 +#, fuzzy +msgid "Disconnect" +msgstr "Putus" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "" + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +msgid "Re-connect" +msgstr "Sambung" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +#, fuzzy +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Tidak diketahui" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:624 +#, fuzzy +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +#, fuzzy +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Komputer" + +#: kcmodule/bluedevildevices.cpp:630 +#, fuzzy +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Rangkaian" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:639 +#, fuzzy +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Audio" + +#: kcmodule/bluedevildevices.cpp:642 +#, fuzzy +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Papan Kekunci" + +#: kcmodule/bluedevildevices.cpp:645 +#, fuzzy +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Tetikus" + +#: kcmodule/bluedevildevices.cpp:648 +#, fuzzy +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:651 +#, fuzzy +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Pencetak" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:663 +#, fuzzy, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Jenis: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "" + +#: kcmodule/devicedetails.cpp:44 +#, fuzzy +msgctxt "Name of the device" +msgid "Name" +msgstr "Nama" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "" + +#: kcmodule/devicedetails.cpp:56 +#, fuzzy +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Amanah" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:154 +#, fuzzy +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Scanning for new devices..." +msgstr "Menyambung ke %1..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "" + +#: kio/obexftp/kio_obexftp.cpp:176 +#, fuzzy +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to the device" +msgstr "Menyambung ke %1..." + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Pemaju" + +#: monolithic/monolithic.cpp:42 +msgid "Browse device" +msgstr "" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "" +msgstr[1] "" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Sambung" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, fuzzy, no-c-format, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to: %1" +msgstr "Menyambung ke %1..." + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Menyambung ke %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +#, fuzzy +msgid "Scanning" +msgstr "Menyambung..." + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "" + +#: wizard/bluewizard.cpp:68 +#, fuzzy +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Maju" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +#, fuzzy +msgid "Scanning..." +msgstr "Menyambung..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "" + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Menyambung ke %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to:" +msgstr "Menyambung ke %1..." + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" + +#, fuzzy + + + +#, fuzzy + + + + + +#, fuzzy + + + + +#, fuzzy + + + +#, fuzzy + + + + +#, fuzzy + + + + + + + + +#, fuzzy + + + + +#, fuzzy + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/nb/CMakeLists.txt b/bluedevil/po/nb/CMakeLists.txt new file mode 100644 index 00000000..90d757ef --- /dev/null +++ b/bluedevil/po/nb/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(nb ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/nb/bluedevil.po b/bluedevil/po/nb/bluedevil.po new file mode 100644 index 00000000..68d20945 --- /dev/null +++ b/bluedevil/po/nb/bluedevil.po @@ -0,0 +1,1030 @@ +# Translation of bluedevil to Norwegian Bokmål +# +# Bjørn Steensrud , 2010, 2011. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-11-12 01:26+0000\n" +"PO-Revision-Date: 2011-08-06 13:26+0200\n" +"Last-Translator: Bjørn Steensrud \n" +"Language-Team: Norwegian Bokmål \n" +"Language: nb\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Lokalize 1.1\n" +"X-Environment: kde\n" +"X-Accelerator-Marker: &\n" +"X-Text-Markup: kde4\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Bjørn Steensrud" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "bjornst@skogkatt.homelinux.org" + +#: actionplugins/audio/audio.cpp:53 +#, kde-format +msgid "%1: audio service connection timeout" +msgstr "%1: tidsavbrudd på forbindelse til lydtjeneste" + +#: actionplugins/audio/audio.cpp:66 +#, kde-format +msgid "%1: audio service connected and configured" +msgstr "%1: lydtjeneste tilkoblet og satt opp" + +#: actionplugins/audio/helper/main.cpp:28 +msgid "Bluetooth Audio Helper" +msgstr "Lydhjelper for Bluetooth" + +#: actionplugins/audio/helper/main.cpp:29 +#: actionplugins/input/helper/main.cpp:29 +#: actionplugins/networkdun/helper/main.cpp:29 +#: actionplugins/networkpanu/helper/main.cpp:29 +#: actionplugins/sendfile/helper/main.cpp:33 +#: daemon/helpers/filereceiver/main.cpp:32 daemon/kded/BlueDevilDaemon.cpp:90 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:79 monolithic/main.cpp:29 +#: wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010 UFO.kodere" + +#: actionplugins/audio/helper/main.cpp:31 +#: actionplugins/input/helper/main.cpp:31 +#: actionplugins/networkdun/helper/main.cpp:31 +#: actionplugins/networkpanu/helper/main.cpp:31 +#: actionplugins/sendfile/helper/main.cpp:35 +#: daemon/helpers/filereceiver/main.cpp:34 daemon/kded/BlueDevilDaemon.cpp:93 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:82 monolithic/main.cpp:31 +#: wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: actionplugins/audio/helper/main.cpp:31 +#: actionplugins/input/helper/main.cpp:31 +#: actionplugins/networkdun/helper/main.cpp:31 +#: actionplugins/networkpanu/helper/main.cpp:31 +#: actionplugins/sendfile/helper/main.cpp:35 +#: daemon/helpers/filereceiver/main.cpp:34 daemon/kded/BlueDevilDaemon.cpp:93 +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/obexftpkded/ObexFtpDaemon.cpp:82 +#: monolithic/main.cpp:31 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Vedlikeholder" + +#: actionplugins/audio/helper/main.cpp:37 +#: actionplugins/input/helper/main.cpp:37 +#: actionplugins/networkdun/helper/main.cpp:37 +#: actionplugins/networkpanu/helper/main.cpp:37 +msgid "Device to connect" +msgstr "Enhet som skal kobles til" + +#: actionplugins/input/helper/main.cpp:28 +msgid "Bluetooth Input Helper" +msgstr "Inndata-hjelper for Bluetooth" + +#: actionplugins/input/input.cpp:54 +#, kde-format +msgid "%1: input service connection timeout" +msgstr "%1: tidsavbrudd på forbindelse til inndata-tjeneste" + +#: actionplugins/input/input.cpp:67 +#, kde-format +msgid "%1: input service connected and configured" +msgstr "%1: inndata-tjeneste tilkoblet og satt opp" + +#: actionplugins/networkdun/helper/main.cpp:28 +msgid "Bluetooth Network Helper" +msgstr "Nettverkshjelper for Bluetooth" + +#: actionplugins/networkdun/networkdun.cpp:65 +#: actionplugins/networkpanu/networkpanu.cpp:65 +#, kde-format +msgid "%1: Setting up..." +msgstr "%1: Setter opp …" + +#: actionplugins/networkpanu/helper/main.cpp:28 +msgid "Bluetooth Network PANU Helper" +msgstr "PANU nettverkshjelper for Bluetooth" + +#: actionplugins/sendfile/helper/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Filsende-hjelper for Bluetooth" + +#: actionplugins/sendfile/helper/main.cpp:42 +#: actionplugins/sendfile/helper/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "Enhets-UUID dit filene vil bli sendt" + +#: actionplugins/sendfile/helper/main.cpp:44 +msgid "Files that will be sent" +msgstr "Filer som vil bli sendt" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: actionplugins/sendfile/helper/pages/connecting.ui:17 +#: wizard/pages/nopairing.ui:51 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Oppkobling til: %1" + +#: actionplugins/sendfile/helper/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Kobler til %1 …" + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: actionplugins/sendfile/helper/pages/selectdeviceandfilespage.cpp:87 +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Velg en eller flere filer:" + +#: actionplugins/sendfile/helper/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Valgte filer:%1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Velg en enhet fra lista:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Skanner" + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +msgid "Sending file over Bluetooth" +msgstr "Sender fil over Bluetooth" + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgctxt "File transfer origin" +msgid "From" +msgstr "Fra" + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgctxt "File transfer destination" +msgid "To" +msgstr "Til" + +#: actionplugins/sendfile/helper/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Send filer med Bluetooth" + +#: actionplugins/sendfile/helper/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Send filer" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 ber om tilgang til denne datamaskinen" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Stol på og autoriser" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Bare autoriser" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Avvis" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE Bluetooth-system" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Endre Bluetooth-modus til «%1»?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Bekreft" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Nekt" + +#: daemon/helpers/filereceiver/main.cpp:31 +msgid "Bluetooth File Receiver Helper" +msgstr "Filmottak-hjelper for Bluetooth" + +#: daemon/helpers/filereceiver/main.cpp:36 monolithic/main.cpp:32 +msgid "Developer" +msgstr "Utvikler" + +#: daemon/helpers/filereceiver/main.cpp:36 daemon/kded/BlueDevilDaemon.cpp:96 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgid "Receiving file over Bluetooth" +msgstr "Mottar fil over Bluetooth" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:133 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 sender deg fila %2" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:136 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Avbryt" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:137 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Godta" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:138 +msgctxt "" +"Button to accept the incoming file transfer and show a Save as... dialog " +"that will let the user choose where will the file be downloaded to" +msgid "Save as..." +msgstr "Lagre som …" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 spør om PIN-et er riktig: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "Riktig PIN" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "Feil PIN" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN trengs for samvirke med %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Tast inn PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"For at denne datamaskinen skal samvirke med %1 må du taste inn et PIN, gjør " +"dette nedenfor." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Tast inn PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:86 daemon/kded/BlueDevilDaemon.cpp:88 +msgid "Bluetooth Daemon" +msgstr "Bluetooth-nisse" + +#: daemon/obexftpkded/ObexFtpDaemon.cpp:75 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:77 +msgid "ObexFtp Daemon" +msgstr "ObexFtp-nisse" + +#: fileitemactionplugin/sendfileitemaction.cpp:61 +msgid "Send via Bluetooth" +msgstr "Send over Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:80 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Finn enhet …" + +#: fileitemactionplugin/sendfileitemaction.cpp:83 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Andre …" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Skjult" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Alltid synlig" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Midlertidig synlig" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minutt" +msgstr[1] "%1 minutter" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Navn" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Slått på" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Synlighet" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Finnetid" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:206 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Standard adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:208 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:204 kcmodule/bluedeviladapters.cpp:229 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minutt" +msgstr[1] "%1 minutter" + +#: kcmodule/bluedeviladapters.cpp:241 +msgid "Bluetooth Adapters" +msgstr "Bluetooth-adaptere" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Kontrollpanelmodul for Bluetooth-adaptere" + +#: kcmodule/bluedeviladapters.cpp:243 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:245 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:245 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Utvikler og vedlikeholder" + +#: kcmodule/bluedeviladapters.cpp:322 +msgid "No adapters found. Please connect one." +msgstr "Fant ingen adaptere. Koble til et." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Slå på KDE Bluetopth-integrering" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth-enheter" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Kontrollpanelmodul for Bluetooth-enheter" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detaljer" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Fjern" + +#: kcmodule/bluedevildevices.cpp:365 +msgid "Disconnect" +msgstr "Koble fra" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Add Device..." +msgstr "Legg til enhet …" + +#: kcmodule/bluedevildevices.cpp:466 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Velg et nytt alias for %1" + +#: kcmodule/bluedevildevices.cpp:490 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Er du sikker på at du vil fjerne enhet «%1» fra lista over kjente enheter?" + +#: kcmodule/bluedevildevices.cpp:491 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Enhetsfjerning" + +#: kcmodule/bluedevildevices.cpp:555 +msgid "No remote devices have been added" +msgstr "Ingen fjernenheter er lagt till" + +#: kcmodule/bluedevildevices.cpp:557 +msgid "Click here to add a remote device" +msgstr "Klikk her for å legge til en fjernenhet" + +#: kcmodule/bluedevildevices.cpp:596 kcmodule/bluedevildevices.cpp:638 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Ukjent" + +#: kcmodule/bluedevildevices.cpp:599 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefon" + +#: kcmodule/bluedevildevices.cpp:602 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:605 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Datamaskin" + +#: kcmodule/bluedevildevices.cpp:608 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Nettverk" + +#: kcmodule/bluedevildevices.cpp:611 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Hodesett" + +#: kcmodule/bluedevildevices.cpp:614 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Hodetelefoner" + +#: kcmodule/bluedevildevices.cpp:617 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Lyd" + +#: kcmodule/bluedevildevices.cpp:620 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Tastatur" + +#: kcmodule/bluedevildevices.cpp:623 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Mus" + +#: kcmodule/bluedevildevices.cpp:626 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:629 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Skriver" + +#: kcmodule/bluedevildevices.cpp:632 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:635 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tegneplate" + +#: kcmodule/bluedevildevices.cpp:641 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Type: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth-overføring" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Kontrollpanelmodul for Bluetooth-overføring" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Aldri" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Tiltrodde enheter" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Alle enheter" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Aldri" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Alltid" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Skrivebeskyttet" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Endre og les" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Navn" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adresse" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Koblet" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blokkert" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Tiltrodd" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:101 +#: kio/bluetooth/kiobluetooth.cpp:206 +msgid "No Bluetooth adapters have been found." +msgstr "Fant ingen Bluetooth-adaptere." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Rett det" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Ditt standard Bluetooth-adapter er ikke synlig for ytre enheter." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Samvirke med Bluetooth-systemet er ikke best." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth er ikke fullstendig slått på." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Mottar" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Lagre filer til:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Godta automatisk" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Motta filer:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Deling" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Del filer" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Krev PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Rettigheter:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Delte filer" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:142 +msgid "Send File" +msgstr "Send fil" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Bla i filer" + +#: kio/bluetooth/kiobluetooth.cpp:78 +msgid "Human Interface Device" +msgstr "Enhet for menneskelig grensesnitt" + +#: kio/bluetooth/kiobluetooth.cpp:83 +msgid "Headset" +msgstr "Hodesett" + +#: kio/bluetooth/kiobluetooth.cpp:88 +msgid "Dial Up Network" +msgstr "Oppringt nettverk" + +#: kio/bluetooth/kiobluetooth.cpp:93 +msgid "Personal Area Network" +msgstr "Personlig områdenettverk (PANU – personal area network)" + +#: kio/bluetooth/kiobluetooth.cpp:124 +msgid "Retrieving services..." +msgstr "Henter tjenester …" + +#: kio/bluetooth/kiobluetooth.cpp:174 +msgid "Scanning for new devices..." +msgstr "Skanner etter nye enheter …" + +#: kio/obexftp/kio_obexftp.cpp:37 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:93 +msgid "Retrieving information from remote device..." +msgstr "Henter informasjon fra fjernenheten …" + +#: kio/obexftp/kio_obexftp.cpp:158 +msgid "Connecting to the device" +msgstr "Kobler til enheten" + +#: kio/obexftp/kio_obexftp.cpp:338 +msgid "Can't connect to the device" +msgstr "Kan ikke koble til enheten" + +#: kio/obexftp/kio_obexftp.cpp:340 +msgid "Connection closed" +msgstr "Tilkoblingen stengt" + +#: kio/obexftp/kio_obexftp.cpp:438 +msgid "The device is busy, waiting..." +msgstr "Enheten er opptatt, venter …" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/monolithic.cpp:130 +msgid "Bluetooth is Off" +msgstr "Bluetooth er Av." + +#: monolithic/monolithic.cpp:136 +msgid "Turn Bluetooth On" +msgstr "Slå på Bluetooth" + +#: monolithic/monolithic.cpp:146 +msgid "Browse devices" +msgstr "Bla i enheter" + +#: monolithic/monolithic.cpp:156 +msgid "Known Devices" +msgstr "Kjente enheter" + +#: monolithic/monolithic.cpp:197 monolithic/monolithic.cpp:537 +msgid "Browse device..." +msgstr "Bla i enhet …" + +#: monolithic/monolithic.cpp:206 monolithic/monolithic.cpp:545 +msgid "Send files..." +msgstr "Send filer …" + +#: monolithic/monolithic.cpp:224 monolithic/monolithic.cpp:248 +#: monolithic/monolithic.cpp:278 monolithic/monolithic.cpp:505 +#: monolithic/monolithic.cpp:512 +msgctxt "Action" +msgid "Disconnect" +msgstr "Koble fra" + +#: monolithic/monolithic.cpp:230 monolithic/monolithic.cpp:260 +#: monolithic/monolithic.cpp:290 monolithic/monolithic.cpp:497 +#: monolithic/monolithic.cpp:517 monolithic/monolithic.cpp:553 +#: monolithic/monolithic.cpp:566 monolithic/monolithic.cpp:579 +msgctxt "Action" +msgid "Connect" +msgstr "Koble til" + +#: monolithic/monolithic.cpp:254 monolithic/monolithic.cpp:284 +#: monolithic/monolithic.cpp:502 +msgid "Connecting..." +msgstr "Kobler til …" + +#: monolithic/monolithic.cpp:299 monolithic/monolithic.cpp:592 +msgid "No supported services found" +msgstr "Fant ingen støttede tjenester" + +#: monolithic/monolithic.cpp:314 +msgid "Add Device" +msgstr "Legg til enhet" + +#: monolithic/monolithic.cpp:318 +msgid "Configure Bluetooth" +msgstr "Sett opp Bluetooth" + +#: monolithic/monolithic.cpp:327 +msgid "Discoverable" +msgstr "Kan finnes" + +#: monolithic/monolithic.cpp:333 +msgid "Turn Bluetooth Off" +msgstr "Slå av Bluetooth" + +#: monolithic/monolithic.cpp:357 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 tilkoblet enhet" +msgstr[1] "%1 enheter tilkoblet" + +#: monolithic/monolithic.cpp:646 +msgid "No adapters found" +msgstr "Fant ingen adaptere" + +#: monolithic/monolithic.cpp:673 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth er På" + +#: monolithic/monolithic.cpp:675 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth er Av" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Slå på eller av mottak av filer" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Lagre mottatte filer til:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Om det tillates å endre delte filer" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Slå på eller av fildeling" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Om PIN skal kreves" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Tillat eksterne enheter å endre de delte filene" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Slå på eller av global KDE Bluetooth-integrering" + +#: wizard/bluewizard.cpp:44 +msgid "Bluetooth Device Wizard" +msgstr "Bluetooth enhetsveiviser" + +#: wizard/bluewizard.cpp:71 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Neste" + +#: wizard/bluewizard.cpp:72 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Fullfør" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Bluetooth-veiviser" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Enhet å samvirke med" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Skanner …" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Manuell PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Velg en enhet" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Start veiviseren på nytt" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Lukk" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Enhetsoppsettet mislyktes" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Oppsett av %1 mislyktes" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Tast inn PIN på tastaturet når det dukker opp og trykk Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:106 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Tast inn PIN på enheten din når det dukker opp" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/services.ui:19 +msgid "The following compatible services have been found:" +msgstr "Følgende kompatible tjenester er funnet:" + +#: wizard/pages/servicespage.cpp:32 +msgid "Service Selection" +msgstr "Tjenestevalg" + +#: wizard/pages/servicespage.cpp:76 +#, kde-format +msgid "%1 has been paired successfully" +msgstr "%1 er vellykket lagt til" + +#: wizard/pages/servicespage.cpp:87 +msgctxt "Do not initialize any service of the device" +msgid "None" +msgstr "Ingen" + +#: wizard/pages/servicespage.cpp:87 +msgid "Do not initialize any service" +msgstr "Ikke klargjør noen tjeneste" + +#: wizard/pages/ssppairing.cpp:82 +msgid "Matches" +msgstr "Stemmer overens" + +#: wizard/pages/ssppairing.cpp:84 +msgid "Does not match" +msgstr "Stemmer ikke overens" + +#: wizard/pages/ssppairing.cpp:98 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "Bekreft at PIN som vises på «%1» stemmer med veiviserens." + +#. i18n: ectx: property (text), widget (QLabel, confirmLbl) +#: wizard/pages/ssppairing.ui:117 +#, no-c-format, kde-format +msgid "Connecting to %1 ..." +msgstr "Kobler til %1 …" \ No newline at end of file diff --git a/bluedevil/po/nds/CMakeLists.txt b/bluedevil/po/nds/CMakeLists.txt new file mode 100644 index 00000000..2401745f --- /dev/null +++ b/bluedevil/po/nds/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(nds ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/nds/bluedevil.po b/bluedevil/po/nds/bluedevil.po new file mode 100644 index 00000000..02088e5c --- /dev/null +++ b/bluedevil/po/nds/bluedevil.po @@ -0,0 +1,1115 @@ +# translation of bluedevil.po to Low Saxon +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# Manfred Wiese , 2010, 2011, 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2012-02-08 22:21+0100\n" +"Last-Translator: Manfred Wiese \n" +"Language-Team: Low Saxon \n" +"Language: nds\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.0\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Manfred Wiese" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "m.j.wiese@web.de" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 fraagt na Togriep op dissen Reekner" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Vertroen un verlöven" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Bloots verlöven" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Afwiesen" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Bluetooth-System vun KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Bluetooth-Bedriefoort na \"%1\" wesseln?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Beglöven" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Afwiesen" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 fraagt na, wat de PIN gellen deit: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN gellt" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN gellt nich" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN deit för't Tokoppeln na %1 noot" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "PIN künnig maken" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Wullt Du mit dissen Reekner na \"%1\" tokoppeln, muttst Du nerrn en PIN " +"ingeven." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "PIN künnig maken" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Bluetooth-Dämoon" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010, UFO-Schrievers" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Pleger" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetooth-Reedschappen" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 stüert Di de Datei %2 to" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Annehmen" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Afbreken" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Datei warrt över Bluetooth afhaalt" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Vun" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Na" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Över Bluetooth loosstüern" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Reedschap söken..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Anner..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Versteken" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Jümmers sichtbor" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Tietwies sichtbor" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 Minuut" +msgstr[1] "%1 Minuten" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Naam" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Anmaakt" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Sichtborkeit" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Sööktiet" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Standardkoort: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Koort: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 Minuut" +msgstr[1] "%1 Minuten" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetooth-Koorten" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Stüerpaneelmoduul för Bluetooth-Koorten" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© 2010: Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Schriever un Pleger" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Keen Koorten funnen. Bitte een tokoppeln." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "KDE-Bluetooth-Inbinnen anmaken" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth-Reedschappen" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Stüerpaneelmoduul för Bluetooth-Reedschappen" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Enkelheiten" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Wegmaken" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Tokoppeln" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Afkoppeln" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Reedschap tofögen..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Tokoppeln" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Niegen Alias för %1 utsöken" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Wullt Du Reedschap \"%1\" redig ut de List vun begäng Reedschappen wegmaken?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Reedschap wegmaken" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Keen feern Reedschappen toföögt" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Klick hier, wenn Du en feern Reedschap tofögen wullt." + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Nich begäng" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefoon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Reekner" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Nettwark" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Snacksett" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Kopphörers" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Klang" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Tastatuur" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Muus" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Drucker" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Speel-Stüerreedschap" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablett" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Typ: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth-Överdregen" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Stüerpaneelmoduul för Bluetooth-Överdregen" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Nienich" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Troot Reedschappen" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "All Reedschappen" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Nienich" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Jümmers" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Bloots lesen" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Ännern un lesen" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Naam" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adress" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Tokoppelt" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blockeert" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Troot" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Keen Bluetooth-Koorten funnen" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Richt dat" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Dien Standard-Bluetooth-Koort is för feern Reedschappen nich sichtbor." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Tosamenspeel mit Bluetooth-System kunn beter wesen." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth is nich heel anmaakt." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Afhalen" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Dateien sekern na:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Automaatsch annehmen:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Afhalen vun Dateien:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Freegaav" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Dateien freegeven:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "PIN bruken:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Verlöven:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Freegeven Dateien" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Freegeven Orner" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "In-/Utgaavmoduul för Bluetooth-Överdregen" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Datei loosstüern" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Dateien dörkieken" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Deensten warrt haalt..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Na niege Reedschappen kieken…" + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp-Dämoon" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Informatschonen vun feern Reedschap warrt haalt." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "An't Tokoppeln na de Reedschap" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Programmschriever" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Reedschappen dörkieken" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Dateien loosstüern" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth is utmaakt" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Bluetooth anmaken" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Reedschappen dörkieken" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Begäng Reedschappen" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Reedschap tofögen" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Bluetooth instellen" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Opdeckbor" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Bluetooth utmaken" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 Reedschap tokoppelt" +msgstr[1] "%1 Reedschappen tokoppelt" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Keen Koorten funnen" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth is anmaakt" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth is utmaakt" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Tokoppeln" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Bluetooth-Hölper för't Loosstüern vun Dateien" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "Eenkennig UID vun de Reedschap, de Du Dateien tostüern wullt" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Dateien, de loosstüert warrt" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "An't Tokoppeln na: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "An't Tokoppeln na %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Een oder mehr Dateien utsöken:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Utsöchte Dateien: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "En Reedschap ut de List utsöken" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "An't Dörkieken" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Datei warrt över Bluetooth loosstüert" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Bluetooth - Dateien loosstüern" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Afhalen vun Dateien an- oder utmaken" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Kregen Dateien sekern na:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Ännern vun freegeven Dateien tolaten" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Dateifreegaven an- oder utmaken" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "PIN bruken" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Extern Reedschappen dörvt freegeven Dateien ännern" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Globaal Bluetooth-Inbinnen för KDE an- oder utmaken" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Bluetooth-Hölper för Reedschappen" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Nakamen" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Fardig maken" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Bluetooth-Hölper" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Reedschap, de Du tokoppeln wullt" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "An't Dörkieken..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN vun Hand:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Söök en Reedschap ut." + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Hölper nieg starten" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Tomaken" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "De Reedschap lett sik nich inrichten." + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "%1 lett sik nich inrichten." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Bitte de PIN ingeven wannehr se wiest warrt un denn de Ingaavtast drücken" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Bitte de PIN ingeven wannehr se wiest" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "An't Tokoppeln na %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "An't Tokoppeln na: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Passt" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Passt nich" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Bitte beglöven, de op \"%1\" wieste un de vun den Hölper angeven PIN passt" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/nl/CMakeLists.txt b/bluedevil/po/nl/CMakeLists.txt new file mode 100644 index 00000000..9081a95f --- /dev/null +++ b/bluedevil/po/nl/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(nl ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/nl/bluedevil.po b/bluedevil/po/nl/bluedevil.po new file mode 100644 index 00000000..46fd62ad --- /dev/null +++ b/bluedevil/po/nl/bluedevil.po @@ -0,0 +1,908 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Freek de Kruijf , 2010, 2011, 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-11-12 09:19+0100\n" +"Last-Translator: Freek de Kruijf \n" +"Language-Team: Dutch \n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.5\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Freek de Kruijf" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "freekdekruijf@kde.nl" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 verzoekt toegang tot deze computer" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Vertrouwen en autoriseren" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Alleen autoriseren" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Weigeren" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE Bluetooth systeem" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Bluetooth-modus wijzigen naar '%1'?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Bevestigen" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Weigeren" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 vraagt of de PIN juist is: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN juist" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN onjuist" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN is nodig om te koppelen met %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "PIN invoeren" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Om deze computer te koppelen met %1 dient u een PIN in te voeren, gaarne " +"hieronder." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "PIN invoeren" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Bluetooth-daemon" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Codeerders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Onderhouder" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetooth-apparaten" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 is bezig het bestand %2 naar u te zenden" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Accepteren" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Annuleren" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Bezig bestand te ontvangen over bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Vanaf" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Naar" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Verzenden over bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Apparaat zoeken..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Overig..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Verborgen" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Altijd zichtbaar" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Tijdelijk zichtbaar" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuut" +msgstr[1] "%1 minuten" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Naam" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Van energie voorzien" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Zichtbaarheid" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Zoektijd" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Standaard adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuut" +msgstr[1] "%1 minuten" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetooth adapters" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Controlepaneelmodule voor bluetooth-adapters" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Ontwikkelaar en onderhouder" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Geen adapters gevonden. Verbindt er een." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "KDE Bluetooth-integratie inschakelen" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth-apparaten" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Controlepaneelmodule voor bluetooth-apparaten" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Details" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Verwijderen" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Verbinden" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Verbinding verbreken" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Apparaat toevoegen..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Verbinden" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Kies een nieuwe alias voor %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Wilt u apparaat \"%1\" verwijderen uit de lijst van bekende apparaten?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Apparaat verwijderen" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Geen externe apparaten toegevoegd" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Hier klikken om een apparaat toe te voegen" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Onbekend" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefoon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Computer" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Netwerk" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Headset" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Hoofdtelefoons" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Geluid" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Toetsenbord" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Muis" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Camera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Printer" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablet" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Type: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth overdracht" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Paneelmodule voor bluetooth-overdracht" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Nooit" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Vertrouwde apparaten" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Alle apparaten" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Nooit" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Altijd" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Alleen-lezen" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Wijzigen en lezen" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Naam" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adres" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Gepaard" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Geblokkeerd" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Vertrouwd" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Er zijn geen bluetooth-adapters gevonden." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Repareer het" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"Uw standaard bluetooth-adapter is niet zichtbaar voor externe apparaten." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Interactie met bluetooth-systeem is niet optimaal." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth is niet volledig ingeschakeld." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Bezig met ontvangen" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Bestanden opslaan in:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Automatisch accepteren:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Bestanden ontvangen:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Delen" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Bestanden delen:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "PIN vereist:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Toegangsrechten:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Gedeelde bestanden" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Gedeelde map:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Bestand verzenden" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Door bestanden bladeren" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Services ophalen..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Naar nieuwe apparaten tasten..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp-daemon" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Ophalen van gegevens van extern apparaat..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Bezig te verbinden met apparaat" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Ontwikkelaar" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Apparaten langslopen" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Bestanden verzenden" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth staat uit" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Bluetooth inschakelen" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Apparaten langslopen" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Bekende apparaten" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Apparaat toevoegen" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Bluetooth instellen" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Vindbaar" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Bluetooth uitschakelen" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 verbonden apparaat" +msgstr[1] "%1 verbonden apparaten" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Geen adapters gevonden" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth staat aan" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth staat uit" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Verbinden" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Hulpje bij bestand verzenden met bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "Apparaat UUID waarnaar de bestanden verzonden zullen worden" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Bestanden die verzonden zullen worden" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Bezig te verbinden met: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Bezig verbinding te maken met %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Eén of meer bestanden selecteren:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Geselecteerde bestanden: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Een apparaat uit een lijst selecteren:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Bezig met scannen" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Bestand verzenden over bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Bestand verzenden met bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Het ontvangen van bestanden in- of uitschakelen" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Ontvangen bestanden opslaan naar:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Of het is toegestaan gedeelde bestanden te wijzigen" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Bestanden delen in- en uitschakelen" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Of de PIN is vereist" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Sta externe apparaten toe om gedeelde bestanden te wijzigen" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Alle globale KDE bluetooth-integratie in- of uitschakelen" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Assistent voor bluetooth-apparaat" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Volgende" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Voltooien" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Assistent voor bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Apparaat om mee te koppelen" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Scannen..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Handmatige PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Een apparaat selecteren" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Assistent opnieuw starten" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Sluiten" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Het instellen van het apparaat is mislukt" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Het instellen van %1 is mislukt" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Gaarne de PIN invoeren op uw toetsenbord wanneer het verschijnt en druk dan " +"op Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Gaarne de PIN invoeren in het apparaat wanneer het verschijnt" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Bezig verbinding te maken met %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Bezig te verbinden met: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Lucifers" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Komt niet overeen" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Gaarne bevestigen dat de PIN getoond op \"%1\" overeenkomt met die van de " +"assistent." \ No newline at end of file diff --git a/bluedevil/po/pa/CMakeLists.txt b/bluedevil/po/pa/CMakeLists.txt new file mode 100644 index 00000000..024080d9 --- /dev/null +++ b/bluedevil/po/pa/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(pa ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/pa/bluedevil.po b/bluedevil/po/pa/bluedevil.po new file mode 100644 index 00000000..91dcfb2e --- /dev/null +++ b/bluedevil/po/pa/bluedevil.po @@ -0,0 +1,1091 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# A S Alam , 2010, 2011. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2011-12-19 22:09+0530\n" +"Last-Translator: A S Alam \n" +"Language-Team: Punjabi/Panjabi \n" +"Language: pa\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Lokalize 1.2\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "ਅਮਨਪਰੀਤ ਸਿੰਘ ਆਲਮ (A S Alam)" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "aalam@users.sf.net" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 ਇਸ ਕੰਪਿਊਟਰ ਲਈ ਵਰਤੋਂ ਦੀ ਮੰਗ ਕਰ ਰਿਹਾ ਹੈ" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "ਭਰੋਸਾ ਅਤੇ ਪਰਮਾਣਕਿਤਾ" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "ਕੇਵਲ ਪਰਮਾਣਿਤ" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "ਪਾਬੰਦੀ" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE ਬਲਿਊਟੁੱਥ ਸਿਸਟਮ" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "ਬਲਿਊਟੁੱਥ ਮੋਡ '%1' ਲਈ ਬਦਲਣਾ ਹੈ?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "ਪੁਸ਼ਟੀ" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "ਪਾਬੰਦੀ" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 ਪੁੱਛ ਰਿਹਾ ਹੈ ਕਿ ਕੀ ਪਿੰਨ ਠੀਕ ਹੈ: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN ਠੀਕ ਹੈ" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN ਗਲਤ ਹੈ" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "ਪਿੰਨ:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "%1 ਨਾਲ ਪੇਅਰ ਕਰਨ ਲਈ ਪਿੰਨ ਦੀ ਲੋੜ ਹੈ" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "ਪਿੰਨ ਪਛਾਣ" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"ਇਹ ਕੰਪਿਊਟਰ ਨਾਲ %1 ਨੂੰ ਜੋੜਨ (ਪੇਅਰ ਕਰਨ) ਲਈ ਤੁਹਾਨੂੰ ਪਿੰਨ (PIN) ਦੇਣਾ ਪਵੇਗਾ। ਹੇਠਾਂ ਦਿਉ ਜੀ।" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "ਪਿੰਨ ਪਛਾਣ" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "ਬਲਿਊਟੁੱਥ ਡੈਮਨ" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "ਪਰਬੰਧਕ" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "ਬਲਿਊਟੁੱਥ ਜੰਤਰ" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 ਤੁਹਾਨੂੰ %2 ਫਾਇਲ ਭੇਜ ਰਿਹਾ ਹੈ" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "ਮਨਜ਼ੂਰ" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "ਰੱਦ ਕਰੋ" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "ਬਲਿਊਟੁੱਥ ਉੱਤੇ ਫਾਇਲ ਭੇਜੀ ਜਾ ਰਹੀ ਹੈ" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "ਤੋਂ" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "ਵੱਲ" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "ਬਲਿਊਟੁੱਥ ਰਾਹੀਂ ਭੇਜੋ" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "...ਜੰਤਰ ਲੱਭੋ" + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "ਹੋਰ..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "ਲੁਕਵਾਂ" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "ਹਮੇਸ਼ਾ ਵੇਖਾਉ" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "ਆਰਜ਼ੀ ਤੌਰ ਉੱਤੇ ਵੇਖਾਉ" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "੧ ਮਿੰਟ" +msgstr[1] "%1 ਮਿੰਟ" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "ਨਾਂ" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "ਪਾਵਰ" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "ਦਿੱਖ" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "ਖੋਜ ਸਮਾਂ" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "ਡਿਫਾਲਟ ਐਡਪਟਰ: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "ਐਡਪਟਰ: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "੧ ਮਿੰਟ" +msgstr[1] "%1 ਮਿੰਟ" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "ਬਲਿਊਟੁੱਥ ਐਡਪਟਰ" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "ਬਲਿਊਟੁੱਥ ਟਰਾਂਸਫਰ ਕੰਟਰੋਲ ਪੈਨਲ ਮੋਡੀਊਲ" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "ਡਿਵੈਲਪਰ ਤੇ ਪਰਬੰਧਕ" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "ਕੋਈ ਐਡਪਟਰ ਨਹੀਂ ਲੱਭਾ। ਪਹਿਲਾਂ ਇੱਕ ਕੁਨੈਕਟ ਕਰੋ।" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "KDE ਬਲਿਊਟੁੱਥ ਐਟੀਗਰੇਸ਼ਨ ਯੋਗ ਕਰੋ" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "ਬਲਿਊਟੁੱਥ ਜੰਤਰ" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "ਬਲਿਊਟੁੱਥ ਜੰਤਰ ਕੰਟਰੋਲ ਪੈਨਲ ਮੋਡੀਊਲ" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "ਵੇਰਵਾ" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "ਹਟਾਓ" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "ਕੁਨੈਕਟ ਕਰੋ" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "ਡਿਸ-ਕੁਨੈਕਟ ਕਰੋ" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "...ਜੰਤਰ ਸ਼ਾਮਲ" + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "ਕੁਨੈਕਟ ਕਰੋ" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "%1 ਲਈ ਨਵਾਂ ਨਾਂ ਚੁਣੋ" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "ਕੀ ਤੁਸੀਂ ਜਾਣ ਜੰਤਰਾਂ ਦੀ ਲਿਸਟ ਵਿੱਚੋਂ \"%1\" ਜੰਤਰ ਨੂੰ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "ਜੰਤਰ ਹਟਾਉਣਾ" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "ਕੋਈ ਰਿਮੋਟ ਜੰਤਰ ਸ਼ਾਮਲ ਨਹੀਂ ਕੀਤਾ ਗਿਆ" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "ਰਿਮੋਟ ਜੰਤਰ ਜੋੜਨ ਲਈ ਇੱਥੇ ਕਲਿੱਕ ਕਰੋ" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "ਅਣਜਾਣ" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "ਫੋਨ" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "ਮਾਡਮ" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "ਕੰਪਿਊਟਰ" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "ਨੈੱਟਵਰਕ" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "ਹੈੱਡਸੈੱਟ" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "ਹੈੱਡਫੋਨ" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "ਆਡੀਓ" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "ਕੀਬੋਰਡ" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "ਮਾਊਸ" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "ਕੈਮਰਾ" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "ਪਰਿੰਟਰ" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "ਜਾਏਪੈਡ" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "ਟੈਬਲੇਟ" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "ਕਿਸਮ: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "ਬਲਿਊਟੁੱਥ ਟਰਾਂਸਫਰ" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "ਬਲਿਊਟੁੱਥ ਟਰਾਂਸਫਰ ਕੰਟਰੋਲ ਪੈਨਲ ਮੋਡੀਊਲ" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "ਕਦੇ ਨਹੀਂ" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "ਭਰੋਸੇਯੋਗ ਜੰਤਰ" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "ਸਭ ਜੰਤਰ" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "ਕਦੇ ਨਹੀਂ" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "ਹਮੇਸ਼ਾਂ" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "ਕੇਵਲ ਪੜ੍ਹਨ" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "ਸੋਧ ਤੇ ਪੜ੍ਹਨ" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "ਨਾਂ" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "ਏਲੀਆਸ" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "ਐਡਰੈੱਸ" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "ਪੇਅਰ ਕੀਤੇ" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "ਪਾਬੰਦੀਸ਼ੁਦਾ" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "ਟਰੱਸਟ ਕੀਤੇ" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "ਕੋਈ ਬਲਿਊਟੁੱਥ ਐਡਪਟਰ ਨਹੀਂ ਲੱਭਾ।" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "ਠੀਕ ਕਰੋ" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "ਰਿਮੋਟ ਜੰਤਰਾਂ ਲਈ ਤੁਹਾਡਾ ਡਿਫਾਲਟ ਬਲਿਊਟੁੱਥ ਐਡਪਟਰ ਨਹੀਂ ਲੱਭਾ।" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "ਬਲਿਊਟੁੱਥ ਸਿਸਟਮ ਨਾਲ ਜੋੜਨਾ (ਐਂਟੀਗਰੇਸ਼ਨ) ਚੋਣਵਾਂ ਨਹੀਂ ਹੈ।" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "ਬਲਿਊਟੁੱਥ ਪੂਰੀ ਤਰ੍ਹਾਂ ਚਾਲੂ ਨਹੀਂ ਹੈ।" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "ਪ੍ਰਾਪਤੀ" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "ਫਾਇਲਾਂ ਸੰਭਾਲੋ:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "ਆਟੋਮੈਟਿਕ ਮਨਜ਼ੂਰ:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "ਪ੍ਰਾਪਤ ਫਾਇਲਾਂ:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "ਸਾਂਝ" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "ਫਾਇਲਾਂ ਸਾਂਝੀਆਂ:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "PIN ਚਾਹੀਦਾ:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "ਅਧਿਕਾਰ:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "ਸਾਂਝੀਆਂ ਕੀਤੀਆਂ ਫਾਇਲਾਂ" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "ਸਾਂਝਾ ਕੀਤਾ ਫੋਲਡਰ" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "ਫਾਇਲ ਭੇਜੋ" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "ਫਾਇਲਾਂ ਦੀ ਝਲਕ ਵੇਖੋ" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "...ਸਰਵਿਸਾਂ ਬਾਰੇ ਜਾਣਕਾਰੀ ਲਈ ਜਾ ਰਹੀ ਹੈ" + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "...ਨਵੇਂ ਜੰਤਰਾਂ ਵਾਸਤੇ ਸਕੈਨ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ" + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp ਡੈਮਨ" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "...ਰਿਮੋਟ ਜੰਤਰ ਤੋਂ ਜਾਣਕਾਰੀ ਲਈ ਜਾ ਰਹੀ ਹੈ" + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "ਜੰਤਰ ਨਾਲ ਕੁਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "ਬਲਿਊਟੁੱਥ" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "ਡਿਵੈਲਪਰ" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "ਜੰਤਰ ਝਲਕ ਵੇਖੋ" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "ਫਾਇਲਾਂ ਭੇਜੋ" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "ਬਲਿਊਟੁੱਥ ਬੰਦ ਹੈ" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "ਬਲਿਊਟੁੱਥ ਚਾਲੂ ਹੈ" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "ਜੰਤਰ ਝਲਕ ਵੇਖੋ" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "ਜਾਣੇ ਜੰਤਰ" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "ਜੰਤਰ ਸ਼ਾਮਲ" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "ਬਲਿਊਟੁੱਲ ਸੰਰਚਨਾ" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "ਖੋਜ ਹੋਣ ਯੋਗ" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "ਬਲਿਊਟੁੱਥ ਬੰਦ ਕਰੋ" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 ਕੁਨੈਕਟ ਹੋਇਆ ਜੰਤਰ" +msgstr[1] "%1 ਕੁਨੈਕਟ ਹੋਏ ਜੰਤਰ" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "ਕੋਈ ਐਡਪਟਰ ਨਹੀਂ ਲੱਭਾ" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "ਬਲਿਊਟੁੱਥ ਚਾਲੂ ਹੈ" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "ਬਲਿਊਟੁੱਥ ਬੰਦ ਹੈ" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "ਕੁਨੈਕਟ ਕਰੋ" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "ਬਲਿਊਟੁੱਥ ਫਾਇਲ ਭੇਜਣ ਸਹਾਇਕ" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "ਜੰਤਰ UUID, ਜਿਸ ਨੂੰ ਫਾਇਲਾਂ ਭੇਜੀਆਂ ਜਾਣੀਆਂ ਹਨ" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "ਫਾਇਲਾਂ, ਜੋ ਭੇਜੀਆਂ ਜਾਣਗੀਆਂ" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "ਕੁਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "%1 ਨਾਲ ਕੁਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ...." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "ਇੱਕ ਜਾਂ ਵੱਧ ਫਾਇਲਾਂ ਚੁਣੋ:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "ਫਾਇਲਾਂ ਚੁਣੋ: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "ਲਿਸਟ ਵਿੱਚੋਂ ਇੱਕ ਜੰਤਰ ਚੁਣੋ:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "ਸਕੈਨ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "ਬਲਿਊਟੁੱਥ ਰਾਹੀਂ ਫਾਇਲਾਂ ਭੇਜੀਆਂ ਜਾ ਰਹੀਆਂ ਹਨ" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "ਬਲਿਊਟੁੱਥ ਫਾਇਲਾਂ ਭੇਜੋ" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "ਫਾਇਲਾਂ ਪ੍ਰਾਪਤ ਕਰਨਾ ਯੋਗ ਜਾਂ ਬੰਦ" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "ਪ੍ਰਾਪਤ ਕੀਤੀਆਂ ਫਾਇਲਾਂ ਸੰਭਾਲੋ:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "ਕੀ ਸਾਂਝੀਆਂ ਕੀਤੀਆਂ ਫਾਇਲਾਂ ਸੋਧਣਾ ਮਨਜ਼ੂਰ" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "ਫਾਇਲ ਸ਼ੇਅਰਿੰਗ ਯੋਗ ਜਾਂ ਆਯੋਗ ਕਰੋ" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "ਕੀ ਪਿੰਨ (PIN) ਚਾਹੀਦਾ ਹੈ" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "ਬਾਹਰੀ ਜੰਤਰਾਂ ਨੂੰ ਸਾਂਝੀਆਂ ਕੀਤੀਆਂ ਫਾਇਲਾਂ ਸੋਧਣ ਦਿਉ" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "ਗਲੋਬਲ KDE ਬਲਿਊਟੁੱਥ ਐਂਟੀਗਰੇਸ਼ਨ ਚਾਲੂ ਜਾਂ ਬੰਦ ਕਰੋ" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "ਬਲਿਊਟੁੱਥ ਜੰਤਰ ਸਹਾਇਕ" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "ਅੱਗੇ" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "ਮੁਕੰਮਲ" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "ਬਲਿਊਟੁੱਥ ਸਹਾਇਕ" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "ਜੰਤਰ ਨਾਲ ਪੇਅਰ ਕਰੋ" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "...ਸਕੈਨ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "ਖੁਦ ਦਿੱਤਾ PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "ਜੰਤਰ ਚੁਣੋ" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "ਸਹਾਇਕ ਮੁੜ-ਚਾਲੂ ਕਰੋ" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "ਬੰਦ ਕਰੋ" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "ਜੰਤਰ ਲਈ ਸੈਟਅੱਪ ਫੇਲ੍ਹ ਹੋਇਆ" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "%1 ਲਈ ਸੈਟਅੱਪ ਫੇਲ੍ਹ ਹੋਇਆ" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "ਜਦੋਂ ਵੀ ਪਿੰਨ ਵੇਖਾਈ ਦੇਵੇ ਤਾਂ ਉਸ ਨੂੰ ਆਪਣੇ ਕੀਬੋਰਡ ਉਤੇ ਦਿਉ ਅਤੇ ਐਂਟਰ ਦੱਬੋ" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "ਜਦੋਂ ਵੀ ਪਿੰਨ (PIN) ਵੇਖਾਈ ਦੇਵੇ ਤਾਂ ਆਪਣੇ ਜੰਤਰ ਵਿੱਚ ਇਹ ਦਿਉ" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "%1 ਨਾਲ ਕੁਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ...." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "ਕੁਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "ਮੇਲ" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "ਨਹੀਂ ਮਿਲਦਾ" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "ਯਕੀਨੀ ਬਣਾਉ ਕਿ \"%1\" ਉੱਤੇ ਵੇਖਾਇਆ PIN ਸਹਾਇਕ ਨਾਲ ਮਿਲਦਾ ਹੈ।" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/pl/CMakeLists.txt b/bluedevil/po/pl/CMakeLists.txt new file mode 100644 index 00000000..1d48c2b0 --- /dev/null +++ b/bluedevil/po/pl/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(pl ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/pl/bluedevil.po b/bluedevil/po/pl/bluedevil.po new file mode 100644 index 00000000..d08a16e5 --- /dev/null +++ b/bluedevil/po/pl/bluedevil.po @@ -0,0 +1,1033 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Mateusz Stanisław Włodarski , 2010. +# Marta Rybczyńska , 2010. +# Łukasz Wojniłowicz , 2011, 2012. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2012-05-18 18:03+0200\n" +"Last-Translator: Łukasz Wojniłowicz \n" +"Language-Team: Polish \n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.4\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " +"|| n%100>=20) ? 1 : 2);\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Mateusz Włodarski, Łukasz Wojniłowicz" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "mikmach@wp.pl, lukasz@wojnilowicz@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 żąda dostępu do tego komputera" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Zaufaj i uwierzytelnij" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Tylko uwierzytelnij" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Odmów" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "System Bluetooth w KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Czy zmienić tryb Bluetooth na %1?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Potwierdź" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Odmów" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 pyta się czy numer PIN jest prawidłowy: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN poprawny" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN niepoprawny" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Numer PIN jest potrzebny do sparowania z %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Wprowadź numer PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"W celu sparowania tego komputera z %1, musisz wprowadzić kod PIN- proszę " +"zrobić to poniżej." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Wprowadź numer PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Demon Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, Koderzy UFO" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Opiekun" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Urządzenia Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 wysyła tobie plik %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Akceptuj" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Anuluj" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Otrzymywanie piliku przez Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Od" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Do" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Prześlij przez Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Znajdź urządzenie..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Inne..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Ukryty" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Zawsze widoczny" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Tymczasowo widoczny" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuta" +msgstr[1] "%1 minuty" +msgstr[2] "%1 minut" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Nazwa" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Zasilany" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Widoczność" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Czas odkrycia" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Domyślny adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuta" +msgstr[1] "%1 minuty" +msgstr[2] "%1 minut" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Adaptery Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Moduł panelu sterowania adapterów Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Programista i opiekun" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Nie znaleziono adapterów. Proszę podłączyć jakikolwiek." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Włącz integrację Bluetooth w KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Urządzenia Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Moduł panelu sterowania urządzeń Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Szczegóły" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Usuń" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Połącz" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Rozłącz" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Dodaj urządzenie..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Połącz" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Wybierz nowy alias dla %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Czy na pewno chcesz usunąć urządzenie \"%1\" z listy znanych urządzeń?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Usuwanie urządzenia" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Nie dodano żadnych urządzeń zdalnych" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Kliknij tutaj, aby dodać urządzenia zdalne" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Nieznane" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Komputer" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Sieć" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Zestaw słuchawkowy" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Słuchawki" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Dźwięk" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Klawiatura" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Mysz" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Aparat" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Drukarka" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablet" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Typ: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Przesyłanie Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Moduł panelu sterowania przesyłania Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Nigdy" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Zaufane urządzenia" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Wszystkie urządzenia" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Nigdy" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Zawsze" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Tylko do odczytu" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Modyfikuj i czytaj" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Nazwa" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adres" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Sparowane" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Zablokowane" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Zaufane" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Nie znaleziono adapterów Bluetooth." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Napraw to" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"Twój domyślny adapter Bluetooth nie jest widoczny dla urządzeń zdalnych." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Interakcja z systemem Bluetooth nie jest optymalna." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth nie jest całkowicie włączony." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Otrzymywanie" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Pliki zapisuj w:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Akceptuj samoczynnie:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Otrzymywanie plików:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Udostępnianie" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Udostępnianie plików:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Żądaj numeru PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Prawa dostępu:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Udostępniane pliki " + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Udostępniany katalog" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Wyślij plik" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Przeglądaj pliki" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Otrzymywanie usług..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Znajdywanie nowych urządzeń..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Demon ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Otrzymywanie informacji ze zdalnego urządzenia..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Łączenie do urządzenia" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Programista" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Przeglądaj urządzenia" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Wyślij pliki" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth jest wyłączone" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Włącz Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Przeglądaj urządzenia" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Znane urządzenia" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Dodaj urządzenie" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Konfiguruj Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Wykrywalne" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Wyłącz Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 podłączone urządzenie" +msgstr[1] "%1 podłączone urządzenia" +msgstr[2] "%1 podłączonych urządzeń" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Nie znaleziono adaptera" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth jest włączone" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth jest wyłączone" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Połącz" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Pomocnik wysyłania plików Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID urządzenia, do którego zostaną wysłane pliki" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Pliki, które zostaną wysłane" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Łączenie z: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Łączenie z %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Zaznacz jeden lub więcej plików:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Zaznaczone pliki: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Wybierz urządzenie z listy:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Przeszukiwanie" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Wysyłanie pliku przez Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Wysłanie plików Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Włącz lub wyłącz otrzymywanie plików" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Zapisuj otrzymywane pliki do:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Określa czy zezwolić na modyfikowanie udostępnionych plików" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Włącz lub wyłącz udostępnianie plików" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Określa czy wymagać numeru PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Pozwól zewnętrznym urządzeniom modyfikować udostępnione pliki" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Włącz lub wyłącz globalną integrację Bluetooth w KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Asystent urządzenia Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Dalej" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Zakończ" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Asystent Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Urządzenia do sparowania" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Przeszukiwanie..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Ręczny PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Wybierz urządzenie" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Uruchom ponownie asystenta" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Zamknij" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Ustawienie urządzenia nieudane" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Ustawienie %1 nieudane" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Proszę wprowadzić numer PIN na twojej klawiaturze, w momencie jego " +"pokazania, i nacisnąć Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" +"Proszę wprowadzić numer PIN na twoim urządzeniu, w momencie jego pokazania" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Łączenie z %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Łączenie z: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Pasuje" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Nie pasuje" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Proszę potwierdzić, że numer PIN wyświetlany na \"%1\" pasuje do tego z " +"asystenta." + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + +#, fuzzy + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/pt/CMakeLists.txt b/bluedevil/po/pt/CMakeLists.txt new file mode 100644 index 00000000..0cd4cec5 --- /dev/null +++ b/bluedevil/po/pt/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(pt ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/pt/bluedevil.po b/bluedevil/po/pt/bluedevil.po new file mode 100644 index 00000000..547ba623 --- /dev/null +++ b/bluedevil/po/pt/bluedevil.po @@ -0,0 +1,899 @@ +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-12-21 15:46+0000\n" +"Last-Translator: José Nuno Coelho Pires \n" +"Language-Team: Portuguese \n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-POFile-IgnoreConsistency: Form\n" +"X-POFile-SpellExtra: Bluedevil Alex PIN Fiestas TextLabel Alejandro Robles\n" +"X-POFile-SpellExtra: Sotware BlueDevil Artesanos pre GW kiobluetooth PnP\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-POFile-SpellExtra: Intercomunicador NAP MCSync wrap Fernández Coders\n" +"X-POFile-SpellExtra: space white HCR PANU AV GN WAP ObexFtp UFO UDITA\n" +"X-POFile-SpellExtra: López kioobexftp Gateway UDIMT UUID Enter\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "José Nuno Pires" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "zepires@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 está a pedir acesso a este computador" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Confiar e Autorizar" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Autorizar Apenas" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Negar" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Sistema de Bluetooth do KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Deseja mudar o modo do Bluetooth para '%1'?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Confirmar" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Negar" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 está a perguntar se o PIN está correcto: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN correcto" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN incorrecto" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "É necessário um PIN para emparelhar com %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Introduzir o PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Para emparelhar este computador com %1, terá de introduzir um PIN - faça-o " +"em baixo, por favor." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Introduzir o PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Servidor de Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Manutenção" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Dispositivo Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 está a enviar-lhe o ficheiro %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Aceitar" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Cancelar" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "A receber o ficheiro por Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "De" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Para" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Enviar por Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Procurar um Dispositivo..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Outro..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Escondido" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Sempre visível" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Visível temporariamente" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuto" +msgstr[1] "%1 minutos" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Nome" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Ligado à Corrente" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Visibilidade" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Tempo de Descoberta" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Adaptador predefinido: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adaptador: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "Um minuto" +msgstr[1] "%1 minutos" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Adaptadores Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Módulo do Painel de Controlo de Adaptadores Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Desenvolvimento e Manutenção" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Não foram encontrados adaptadores. Ligue um, por favor." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Activar a Integração de Bluetooth do KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Dispositivos Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Módulo do Painel de Controlo dos Dispositivos Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detalhes" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Remover" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Ligar" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Desligar" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Adicionar um Dispositivo..." + +#: kcmodule/bluedevildevices.cpp:446 +msgid "Re-connect" +msgstr "Ligar de novo" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Obter um novo nome alternativo para o '%1'" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Tem a certeza que deseja remover o dispositivo \"%1\" da lista de " +"dispositivos conhecidos?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Remoção do dispositivo" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Não foram adicionados quaisquer dispositivos remotos" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Carregue aqui para adicionar um dispositivo remoto" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Desconhecido" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefone" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Computador" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Rede" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Auricular" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Auscultadores" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Áudio" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Teclado" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Rato" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Câmara" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Impressora" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Comando de Jogos" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablete" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tipo: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Transferência por Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Módulo do Painel de Controlo de Transferências por Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Nunca" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Dispositivos fidedignos" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Todos os dispositivos" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Nunca" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Sempre" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Apenas para Leitura" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Modificação e Leitura" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Nome" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Nome Alternativo" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Endereço" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Emparelhado" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Bloqueado" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Fidedigno" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Não foram encontrados quaisquer adaptadores Bluetooth." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Corrigi-lo" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"O seu dispositivo Bluetooth predefinido não é visível para os dispositivos " +"remotos." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "A interacção com o sistema Bluetooth não é a adequada." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "O Bluetooth não está completamente activo." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "A Receber" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Gravar os ficheiros em:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Aceitar automaticamente:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Receber os ficheiros:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "A Partilhar" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Partilhar os Ficheiros:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Pedir o PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Permissões:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Ficheiros Partilhados" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Pasta Partilhada:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Enviar o Ficheiro" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Navegar pelos Ficheiros" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "A obter os serviços..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "À procura de dispositivos novos..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Servidor de ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "A obter a informação do dispositivo remoto..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "A efectuar a ligação ao dispositivo" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Desenvolvimento" + +#: monolithic/monolithic.cpp:42 +msgid "Browse device" +msgstr "Navegar pelo dispositivo" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Enviar os Ficheiros" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "O Bluetooth Está Desligado" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Activar o Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Navegar pelos dispositivos" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Dispositivos Conhecidos" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Adicionar um Dispositivo" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Configurar o Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Visível" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Desactivar o Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 dispositivo ligado" +msgstr[1] "%1 dispositivos ligados" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Não foram encontrados adaptadores" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "O Bluetooth Está Ligado" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "O Bluetooth Está Desligado" + +#: monolithic/monolithic.cpp:441 +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Ligar" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Utilitário de Envio de Ficheiros por Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "O UUID do dispositivo para onde serão enviados os ficheiros" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Os ficheiros que serão enviados" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "A efectuar a ligação a: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "A ligar a %1 ..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Seleccione um ou mais ficheiros:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Ficheiros seleccionados: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Seleccione um dispositivo na lista:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Digitalização" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "A enviar o ficheiro por Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Envio de Ficheiros por Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Activar ou desactivar a recepção de ficheiros" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Gravar os ficheiros recebidos em:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Se permitir a modificação dos ficheiros partilhados" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Activar ou desactivar a partilha de ficheiros" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Se pedir obrigatoriamente o PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Permitir aos dispositivos externos modificar os ficheiros partilhados" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Activar ou desactivar toda a integração de Bluetooth com o KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Assistente de Dispositivos Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Seguinte" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Terminar" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Assistente de Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Dispositivo com o qual emparelhar" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "A analisar..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN Manual:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Seleccione um dispositivo" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Reiniciar o assistente" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Fechar" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "A configuração do dispositivo foi mal-sucedida" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "A configuração de %1 foi mal-sucedida" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Introduza por favor o PIN no seu teclado quando aparecer, carregando depois " +"em Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Introduza por favor o PIN no seu dispositivo quando aparecer" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "A ligar a %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +msgid "Connecting to:" +msgstr "A ligar a:" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Corresponde" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Não corresponde" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Por favor confirme que o PIN apresentado em \"%1\" corresponde ao do " +"assistente." \ No newline at end of file diff --git a/bluedevil/po/pt_BR/CMakeLists.txt b/bluedevil/po/pt_BR/CMakeLists.txt new file mode 100644 index 00000000..9614c12c --- /dev/null +++ b/bluedevil/po/pt_BR/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(pt_BR ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/pt_BR/bluedevil.po b/bluedevil/po/pt_BR/bluedevil.po new file mode 100644 index 00000000..d68c5bbe --- /dev/null +++ b/bluedevil/po/pt_BR/bluedevil.po @@ -0,0 +1,908 @@ +# Translation of bluedevil.po to Brazilian Portuguese +# Copyright (C) 2010-2013 This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# André Marcelo Alvarenga , 2010, 2011, 2013. +# Luiz Fernando Ranghetti , 2010. +# Aracele Torres , 2010. +# Marcus Gama , 2011. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-12-21 01:30-0200\n" +"Last-Translator: André Marcelo Alvarenga \n" +"Language-Team: Brazilian Portuguese \n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.5\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "André Marcelo Alvarenga" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "alvarenga@kde.org" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 está solicitando acesso a este computador" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Confiar e autorizar" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Somente autorizar" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Recusar" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Sistema de Bluetooth do KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Alterar o modo do Bluetooth para %1?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Confirmar" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Recusar" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 está perguntando se o PIN está correto: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN correto" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN incorreto" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "É necessário um PIN para emparelhar com %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Introduzir o PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Para emparelhar este computador com %1, você precisa digitar um PIN no campo " +"abaixo." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Introduzir o PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Daemon de Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Mantenedor" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Dispositivo Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 está enviando-lhe o arquivo %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Aceitar" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Cancelar" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Recebendo o arquivo por Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "De" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Para" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Enviar por Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Localizar dispositivo..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Outro..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Oculto" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Sempre visível" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Visível temporariamente" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuto" +msgstr[1] "%1 minutos" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Nome" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Ligado" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Visibilidade" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Tempo de descoberta" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Adaptador padrão: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adaptador: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minuto" +msgstr[1] "%1 minutos" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Adaptadores Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Módulo do painel de controle de adaptadores Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Desenvolvedor e mantenedor" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Não foram encontrados adaptadores. Por favor, conecte um." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Habilitar a integração do Bluetooth do KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Dispositivos Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Módulo do painel de controle de dispositivos Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detalhes" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Remover" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Conectar" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Desconectar" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Adicionar dispositivo..." + +#: kcmodule/bluedevildevices.cpp:446 +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Reconectar" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Obter um novo nome alternativo para o %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Tem certeza de que deseja remover o dispositivo \"%1\" da lista de " +"dispositivos conhecidos?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Remoção do dispositivo" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Não foram adicionados dispositivos remotos" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Clique aqui para adicionar um dispositivo remoto" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Desconhecido" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefone" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Computador" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Rede" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Fone de ouvido com microfone" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Fone de ouvido" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Áudio" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Teclado" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Mouse" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Câmera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Impressora" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablet" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tipo: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Transferência por Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Módulo do painel de controle de transferências Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Nunca" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Dispositivos confiáveis" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Todos os dispositivos" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Nunca" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Sempre" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Somente leitura" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Leitura e modificação" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Nome" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Apelido" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Endereço" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Emparelhado" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Bloqueado" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Confiável" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Não foram encontrados adaptadores Bluetooth." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Corrigir" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"O seu dispositivo Bluetooth padrão não está visível para os dispositivos " +"remotos." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "A interação com o sistema Bluetooth não é a melhor." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "O Bluetooth não está completamente habilitado." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Recebendo" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Salvar os arquivos em:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Aceitar automaticamente:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Receber os arquivos:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Compartilhando" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Compartilhar os arquivos:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Solicitar o PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Permissões:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Arquivos compartilhados" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Pasta compartilhada:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Enviar arquivo" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Navegar pelos arquivos" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Obtendo serviços..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Procurando novos dispositivos..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Daemon do ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Recebendo informações do dispositivo remoto..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Conectando ao dispositivo" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Desenvolvedor" + +#: monolithic/monolithic.cpp:42 +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Navegar pelo dispositivo" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Enviar arquivos" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "O Bluetooth está desativado" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Ativar Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Navegar pelos dispositivos" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Dispositivos conhecidos" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Adicionar dispositivo" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Configurar o Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Visível" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Desativar Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 dispositivo conectado" +msgstr[1] "%1 dispositivos conectados" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Não foram encontrados adaptadores" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "O Bluetooth está ativado" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "O Bluetooth está desativado" + +#: monolithic/monolithic.cpp:441 +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Conectar" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Assistente de envio de arquivos por Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "O UUID do dispositivo para onde os arquivos serão enviados" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Arquivos que serão enviados" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Conectando a: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Conectando à %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Selecione um ou mais arquivos:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Arquivos selecionados: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Selecione um dispositivo na lista:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Procurando" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Enviando o arquivo por Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Envio de arquivos por Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Habilitar ou desabilitar a recepção de arquivos" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Salvar os arquivos recebidos em:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Se permitir a modificação dos arquivos compartilhados" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Habilita ou desabilita o compartilhamento de arquivos" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Se solicitar obrigatoriamente o PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" +"Permitir que os dispositivos externos modifiquem os arquivos compartilhados" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Habilitar ou desabilitar a integração global do Bluetooth com o KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Assistente de dispositivos Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Próximo" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Concluir" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Assistente de Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Dispositivo para emparelhamento" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Procurando..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN manual:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Selecione um dispositivo" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Reiniciar o assistente" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Fechar" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "A configuração do dispositivo falhou" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "A configuração de %1 falhou" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Introduza o PIN no seu teclado quando ele aparecer e pressione Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Introduza o PIN no seu dispositivo quando ele aparecer" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Conectando à %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Conectando a:" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Ocorrências" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Não corresponde" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Por favor, confirme que o PIN apresentado em \"%1\" corresponde ao do " +"dispositivo." \ No newline at end of file diff --git a/bluedevil/po/ro/CMakeLists.txt b/bluedevil/po/ro/CMakeLists.txt new file mode 100644 index 00000000..83a849f0 --- /dev/null +++ b/bluedevil/po/ro/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(ro ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/ro/bluedevil.po b/bluedevil/po/ro/bluedevil.po new file mode 100644 index 00000000..95ea57e2 --- /dev/null +++ b/bluedevil/po/ro/bluedevil.po @@ -0,0 +1,1098 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Sergiu Bivol , 2011, 2012. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2012-12-26 13:57+0200\n" +"Last-Translator: Sergiu Bivol \n" +"Language-Team: Romanian \n" +"Language: ro\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < " +"20)) ? 1 : 2;\n" +"X-Generator: Lokalize 1.5\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Sergiu Bivol" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "sergiu@ase.md" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 cere acces la acest calculator" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Acordă încredere și autorizează" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Numai autorizează" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Refuză" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Sistemul de Bluetooth KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Schimbați regimul Bluetooth la „%1”?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Confirmă" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Refuză" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 întreabă dacă PIN-ul este corect: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN corect" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN incorect" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Este nevoie de PIN pentru asociere cu %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Introducere PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Pentru a asocia acest calculator la %1, trebuie să introduceți un PIN. " +"Faceți aceasta mai jos." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Introducere PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Demon Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Responsabil" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Dispozitive Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 vă trimite fișierul %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Acceptă" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Renunță" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Se primește fișier prin Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "De la" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Către" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Expediază prin Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Căutare dispozitiv..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Altul..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Ascuns" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Întotdeauna vizibil" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Vizibil temporar" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minut" +msgstr[1] "%1 minute" +msgstr[2] "%1 de minute" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Denumire" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Alimentat" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Vizibilitate" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Timp de descoperire" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Adaptor implicit: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adaptor: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minut" +msgstr[1] "%1 minute" +msgstr[2] "%1 de minute" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Adaptoare Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Modul al Panoului de control pentru adaptoare Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Dezvoltator și responsabil" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Nu a fost găsit niciun adaptor. Conectați unul." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Activează integrarea cu Bluetooth KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Dispozitive Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Modul al Panoului de control pentru dispozitive Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detalii" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Elimină" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Conectează" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Deconectează" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Adaugă dispozitiv..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Conectează" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Alegeți un alias nou pentru %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Sigur eliminați dispozitivul „%1” din lista dispozitivelor cunoscute?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Eliminare dispozitiv" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Nu au fost adăugate dispozitive distante" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Apăsați aici pentru a adăuga un dispozitiv distant" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Necunoscut" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Calculator" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Rețea" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Căști cu microfon" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Căști" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Audio" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Tastatură" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Maus" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Cameră" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Imprimantă" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Joypad" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Planșetă" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tip: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Transfer Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Modul al Panoului de control pentru transfer Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Niciodată" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Dispozitive de încredere" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Toate dispozitivele" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Niciodată" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Întotdeauna" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Numai citire" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Modificare și citire" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Denumire" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adresă" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Asociat" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blocat" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "De încredere" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Nu au fost găsite adaptoare Bluetooth" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Repară" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Adaptorul Bluetooth implicit nu este vizibil pentru alte dispozitive." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Interacțiunea cu sistemul Bluetooth nu este optimă." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth nu este activat complet." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Se recepționează" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Salvează fișierele în:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Acceptă automat:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Primește fișiere:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Partajare" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Partajare fișiere:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Cere PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Permisiuni:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Fișiere partajate" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Dosar partajat" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Trimite fișier" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Răsfoiește fișiere" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Se obțin serviciile..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Se caută dispozitive noi..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Demon ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Se obțin informații de la dispozitivul distant..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Se conectează la dispozitiv" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Dezvoltator" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Răsfoiește dispozitive" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Trimite fișiere" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth este deconectat" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Conectează Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Răsfoiește dispozitive" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Dispozitive cunoscute" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Adaugă dispozitiv" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Configurează Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Vizibil" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Deconectează Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 dispozitiv conectat" +msgstr[1] "%1 dispozitive conectate" +msgstr[2] "%1 de dispozitive conectate" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Niciun adaptor găsit" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth este conectat" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth este deconectat" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Conectează" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Unealtă pentru expedierea fișierelor prin Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID-ul dispozitivului spre care se trimit fișierele" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Fișierele ce vor fi trimise" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Conectare la: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Conectare la %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Alegeți unul sau mai multe fișiere:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Fișiere alese: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Alegeți un dispozitiv din listă:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Se scanează" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Se expediază fișier prin Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Expediază fișiere prin Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Activează sau dezactivează primirea fișierelor" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Salvează fișierele primite la:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Dacă permiteți modificarea fișierelor partajate" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Activează sau dezactivează partajarea fișierelor" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Dacă se cere PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Permite dispozitivelor externe să modifice fișierele partajate" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Activează sau dezactivează integrarea globală Bluetooth în KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Expert dispozitive Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Înainte" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Finalizează" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Expert Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Dispozitiv de asociat" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Citire..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "PIN manual:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Alegeți un dispozitiv" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Repornește expertul" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Închide" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Configurarea dispozitivului a eșuat" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Configurarea %1 a eșuat" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Introduceți PIN-ul în tastatură cînd apare și apăsați Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Introduceți PIN-ul în dispozitiv cînd apare" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Conectare la %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Conectare la: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Se potrivește" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Nu se potrivește" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Confirmați că PIN-ul afișat pe „%1” se potrivește cu cel al expertului." + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/ru/CMakeLists.txt b/bluedevil/po/ru/CMakeLists.txt new file mode 100644 index 00000000..26b2f5e8 --- /dev/null +++ b/bluedevil/po/ru/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(ru ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/ru/bluedevil.po b/bluedevil/po/ru/bluedevil.po new file mode 100644 index 00000000..d566f235 --- /dev/null +++ b/bluedevil/po/ru/bluedevil.po @@ -0,0 +1,1182 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Vladimir Buharin , 2010. +# Alexander Potashev , 2010, 2011. +# Yuri Efremov , 2010, 2011. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2011-10-14 14:42+0400\n" +"Last-Translator: Yuri Efremov \n" +"Language-Team: Russian \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n" +"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Lokalize 1.2\n" +"X-Environment: kde\n" +"X-Accelerator-Marker: &\n" +"X-Text-Markup: kde4\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Владимир Бухарин" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "vovan2482@mail.ru" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 запрашивает доступ к этому компьютеру" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Сделать доверенным и авторизовать" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Только авторизовать" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Запретить" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Система KDE Bluetooth" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Изменить режим работы Bluetooth на %1?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Изменить" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Отмена" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 запрашивает, верно ли был введён PIN: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN верный" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN неверный" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Для сопряжения с %1 необходим PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Ввести PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Для сопряжения этого компьютера с %1 вы должны ввести PIN, введите его ниже." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Введите PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Служба Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© UFO Coders, 2010" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Сопровождающий" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Устройства Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 передаёт файл %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Принять" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Отмена" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Принимается файл по каналу Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "От" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "К" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Отправить по Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Поиск устройства..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Другое..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Скрытый" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Всегда видимый" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Временно видимый" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 минута" +msgstr[1] "%1 минуты" +msgstr[2] "%1 минут" +msgstr[3] "1 минута" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Название" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Подключён" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Видимый" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Продолжительность видимости" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Адаптер по умолчанию: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Адаптер: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 минута" +msgstr[1] "%1 минуты" +msgstr[2] "%1 минут" +msgstr[3] "1 минута" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Адаптеры Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Модуль настройки адаптеров Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© Rafael Fernández López, 2010" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Разработчик и сопровождающий" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Адаптеры не найдены. Включите адаптер." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Включить интеграцию с KDE Bluetooth" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Устройства Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Модуль настройки устройств Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Свойства" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Удалить" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Подключить" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Отключить" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Добавить устройство..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Подключить" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Выберите новое название для %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Удалить устройство «%1» из списка известных устройств?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Удаление устройства" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Удалённые устройства не добавлены" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Нажмите здесь, чтобы добавить удалённое устройство" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "неизвестно" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "телефон" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "модем" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "компьютер" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "сеть" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "гарнитура" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "наушники" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "аудиоустройство" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "клавиатура" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "мышь" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "камера" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "принтер" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "геймпад" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "планшет" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Тип: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Передача по каналу Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Модуль настройки передачи данных по каналу Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Никогда" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "От доверенных устройств" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "От всех устройств" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Никогда" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Всегда" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Только для чтения" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Чтение и изменение" + +# "Собственное" означает, что этот параметр принадлежит устройству, а не меняется пользователем. --aspotashev +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Собственное название" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Название" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Адрес" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Сопряжено" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Заблокировано" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Доверенное" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Адаптеры Bluetooth не найдены." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Исправить" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Ваш адаптер Bluetooth по умолчанию невидим для удалённых устройств." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Взаимодействие с системой Bluetooth неоптимально." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth включён не полностью." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Приём файлов" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Сохранять полученные файлы в:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Принимать автоматически:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Разрешить получение файлов:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Общий доступ к файлам" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Разрешить доступ к файлам:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Требовать PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Права доступа:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Общие файлы" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "Общая папка:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Передать файл" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Обзор файлов" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Получение списка служб..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Поиск новых устройств..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Служба ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Получение информации от удалённого устройства..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Подключение к устройству" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Разработчик" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Обзор устройств" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Передать файлы" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth отключён" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Включить Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Обзор устройств" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Известные устройства" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Добавить устройство" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Настроить Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Возможность обнаружения" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Отключить Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "Подключено %1 устройство" +msgstr[1] "Подключено %1 устройства" +msgstr[2] "Подключено %1 устройств" +msgstr[3] "Подключено одно устройство" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Адаптеры не найдены" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth включён" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth отключён" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Подключить" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Инструмент для передачи файлов по каналу Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID устройства, куда будут отправлены файлы" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Файлы, которые будут отправлены" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Подключение к: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Подключение к %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Выберите один или несколько файлов:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Выбранные файлы: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Выберите устройство из списка:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Сканирование" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Передаётся файл по каналу Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Файлы, передаваемые по каналу Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Включение или отключение приёма файлов" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Сохранить полученные файлы в:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Разрешить изменять файлы, открытые для общего доступа" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Включить или выключить общий доступ к файлам" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Требовать PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" +"Разрешить другим устройствам изменять файлы, открытые для общего доступа" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Включение или отключение всей интеграции с KDE Bluetooth" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Мастер устройств Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Далее" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Готово" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Мастер настройки Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Устройство для сопряжения" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Сканирование..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Ввести PIN вручную:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Выбор устройства" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Начать заново" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Закрыть" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Не удалось настроить устройство" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Не удалось настроить устройство %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Когда PIN будет показан, введите его на клавиатуре и нажмите Enter." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Когда PIN будет показан, введите его на устройстве." + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Подключение к %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Подключение к: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Совпадает" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Не совпадает" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Подтвердите, что PIN, показанный на устройстве «%1», совпадает с PIN, " +"указанным в мастере." + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + +# https://bugs.kde.org/show_bug.cgi?id=254464 + + + + + + + + + +# https://bugs.kde.org/show_bug.cgi?id=254463 +#, fuzzy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# https://bugs.kde.org/show_bug.cgi?id=254465 +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/sk/CMakeLists.txt b/bluedevil/po/sk/CMakeLists.txt new file mode 100644 index 00000000..7727a2e4 --- /dev/null +++ b/bluedevil/po/sk/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(sk ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/sk/bluedevil.po b/bluedevil/po/sk/bluedevil.po new file mode 100644 index 00000000..115d7472 --- /dev/null +++ b/bluedevil/po/sk/bluedevil.po @@ -0,0 +1,908 @@ +# translation of bluedevil.po to Slovak +# Richard Fric , 2010. +# Roman Paholík , 2013. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-11-12 18:41+0100\n" +"Last-Translator: Roman Paholík \n" +"Language-Team: Slovak \n" +"Language: sk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.5\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Roman Paholík" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "wizzardsk@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 žiada prístup k tomuto počítaču" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Dôverovať a autorizovať" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Iba autorizovať" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Odmietnuť" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE Bluetooth systém" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Zmeniť režim Bluetooth na '%1'?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Potvrdiť" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Odmietnuť" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 sa pýta, či PIN je správny: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "Správny PIN" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "Nesprávny PIN" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Potrebný PIN na spárovanie s %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Vložiť PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Na spárovanie vášho počítača s %1 musíte zadať PIN. Prosím, zadajte ho." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Vložiť PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Démon Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Správca" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Zariadenia Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 vám posiela súbor %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Prijať" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Zrušiť" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Prijímanie súboru cez Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Od" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Do" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Poslať cez Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Nájsť zariadenie..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Iné..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Skryté" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Vždy viditeľné" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Dočasne viditeľné" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minúta" +msgstr[1] "%1 minúty" +msgstr[2] "%1 minút" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Názov" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Napájaný" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Viditeľnosť" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Čas odkrytia" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Predvolený adaptér: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adaptér: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minúta" +msgstr[1] "%1 minúty" +msgstr[2] "%1 minút" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetooth adaptéry" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Modul ovládacieho panelu pre adaptéry Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Vývojár a správca" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Nenájdený žiadny adaptér. Prosím pripojte nejaký." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Povoliť KDE Bluetooth integráciu" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Zariadenia Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Modul ovládacieho panelu pre zariadenia Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detaily" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Odstrániť" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Pripojiť" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Odpojiť" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Pridať zariadenie..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Pripojiť" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Vyberte nový alias pre %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Ste si istý, že chcete odstrániť zariadenie \"%1\" zo zoznamu známych " +"zariadení?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Odstránenie zariadenia" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Neboli pridané žiadne vzdialené zariadenia" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Kliknite sem na pridanie vzdialeného zariadenia" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Neznáme" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefón" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Počítač" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Sieť" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Headset" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Slúchadlá" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Zvuk" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Klávesnica" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Myš" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Tlačiareň" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Herný ovládač" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablet" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Typ: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth prenos" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Modul ovládacieho panelu pre prenosy Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Nikdy" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Dôveryhodné zariadenia" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Všetky zariadenia" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Nikdy" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Vždy" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Iba na čítanie" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Čítať a zapisovať" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Názov" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Prezývka" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adresa" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Spárované" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blokované" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Dôveryhodné" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Nenájdené žiadne Bluetooth adaptéry." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Opraviť to" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"Váš predvolený Bluetooth adaptér nie je viditeľné pre vzdialené zariadenia." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Interakcie so systémom Bluetooth nie je optimálna." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth nie je úplne zapnutý." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Prijímanie" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Uložiť súbory v:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Prijať automaticky:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Prijať súbory:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Zdieľanie" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Zdieľať súbory:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Vyžadovať PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Prístupové práva:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Zdieľané súbory" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Zdieľaný priečinok:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Odoslať súbor" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Prehliadať súbory" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Vyberanie služieb..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Hľadanie nových zariadení..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Démon ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Získavanie informácie zo vzdialeného zariadenia..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Pripájanie k zariadeniu" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Vývojár" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Prehliadať zariadenia" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Odoslať súbory" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth je vypnutý" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Zapnúť Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Prehliadať zariadenia" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Známe zariadenia" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Pridať zariadenie" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Nastaviť Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Objaviteľné" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Vypnúť Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 pripojené zariadenie" +msgstr[1] "%1 pripojené zariadenia" +msgstr[2] "%1 pripojených zariadení" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Nenašli sa žiadne adaptéry" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth je zapnutý" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth je zapnutý" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Pripojiť" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Pomocník na posielanie súborov cez Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID zariadenia, kam sa pošlú súbory" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Súbory, ktoré sa odošlú" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Pripájanie k: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Pripájanie k %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Vyberte jeden alebo viac súborov:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Vybrané súbory: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Vyberte zariadenie zo zoznamu:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Skenovanie" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Posielanie súboru cez Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Posilať súbory cez Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Povoliť alebo zakázať prijímanie súborov" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Uložiť prijaté súbory do:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Či povoliť meniť zdieľané súbory" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Povoliť alebo zakázať zdieľanie súborov" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Či vyžadovať PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Povoliť externým zariadeniam meniť zdieľané súbory" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Povoliť alebo zakázať globálnu KDE integráciu Bluetooth" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Sprievodca Bluetooth zariadenia" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Ďalej" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Hotovo" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Sprievodca Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Zariadenie na spárovanie s" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Vyhľadávanie…" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Ručný PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Vyberte zariadenie" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Reštartovať sprievodcu" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Zatvoriť" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Nastavenie zariadenia zlyhalo" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Nastavenie %1 zlyhalo" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Prosím vložte PIN na vašej klávesnici, keď sa objaví dialóg, a stlačte Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Prosím vložte PIN do zariadenia, keď sa objaví dialóg" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Pripájanie k %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Pripájanie k: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Zhody" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Nezhoduje sa" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "Prosím, potvrďte, že PIN zobrazený na \"%1\" sa zhoduje s týmto." \ No newline at end of file diff --git a/bluedevil/po/sl/CMakeLists.txt b/bluedevil/po/sl/CMakeLists.txt new file mode 100644 index 00000000..6c34bb58 --- /dev/null +++ b/bluedevil/po/sl/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(sl ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/sl/bluedevil.po b/bluedevil/po/sl/bluedevil.po new file mode 100644 index 00000000..30e1c906 --- /dev/null +++ b/bluedevil/po/sl/bluedevil.po @@ -0,0 +1,914 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Jure Repinc , 2010. +# Andrej Mernik , 2012, 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-11-12 18:41+0100\n" +"Last-Translator: Andrej Mernik \n" +"Language-Team: Slovenian \n" +"Language: sl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n" +"%100==4 ? 3 : 0);\n" +"X-Generator: Lokalize 1.5\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Jure Repinc,Andrej Žnidaršič,Miha Gašperšič" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "jlp@holodeck1.com,andrej.znidarsic@gmail.com,mihec.gaspersic@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 želi dostopati do tega računalnika" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Zaupaj in odobri" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Samo odobri" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Zavrni" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE-jev sistem za Bluetooth" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Ali želite spremeniti način Bluetooth v '%1'?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Spremeni" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Ne spremeni" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 vprašuje, ali je PIN pravilen: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "Pravilen" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "Napačen" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Za povezovanje z %1 je potreben PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Vnos PIN-a" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Če se želite povezati z računalnikom %1, morate vnesti PIN. Vnesite ga " +"spodaj." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Vnos PIN-a" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Ozadnji program za Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Vzdrževalec" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Naprave Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 vam pošilja datoteko %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Sprejmi" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Zavrni" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Prejemanje datoteke preko Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Iz" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Na" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Pošlji prek povezave Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Najdi napravo ..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Drugo ..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Skrit" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Vedno viden" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Začasno viden" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 minut" +msgstr[1] "%1 minuto" +msgstr[2] "%1 minuti" +msgstr[3] "%1 minute" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Ime" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Vklopljen" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Vidnost" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Čas za odkritje" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Privzeti vmesnik: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Vmesnik: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 minut" +msgstr[1] "%1 minuto" +msgstr[2] "%1 minuti" +msgstr[3] "%1 minute" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Vmesniki Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Modul za vmesnike Bluetooth za Sistemske nastavitve" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Razvijalec in vzdrževalec" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Najdenega ni bilo nobenega vmesnika. Priključite ga." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Omogoči podporo KDE Bluetooth" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Naprave Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Modul za naprave Bluetooth za Sistemske nastavitve" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Podrobnosti" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Odstrani" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Poveži se" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Prekini povezavo" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Dodaj napravo ..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Poveži se" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Izberite nov vzdevek za %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Ali ste prepričani, da želite odstraniti napravo \"%1\" iz seznama znanih " +"naprav?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Odstranjevanje naprave" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Ni dodanih oddaljenih naprav" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Za dodajanje oddaljene naprave kliknite tukaj" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Neznana" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Računalnik" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Omrežje" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Slušalke z mikrofonom" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Slušalke" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Zvok" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Tipkovnica" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Miška" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Tiskalnik" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Igralna palica" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablica" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Vrsta: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Prenos prek Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Modul za prenos prek Bluetooth za Sistemske nastavitve" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Nikoli" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Zaupanja vredne naprave" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Vse naprave" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Nikoli" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Vedno" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Samo za branje" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Spreminjanje in branje" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Ime" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Vzdevek" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Naslov" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Seznanjeno" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blokirana" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Zaupanja vredna" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Najdenega ni bilo nobenega vmesnika Bluetooth." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Popravi" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Vaš vmesnik Bluetooth oddaljenim napravam ni viden." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Sodelovanje s sistemom Bluetooth ni optimalno." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth ni popolnoma omogočen" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Prejemanje" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Shrani datoteke v:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Samodejno sprejmi:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Prejmi datoteke:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Souporaba" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Souporaba datotek:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Zahtevaj PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Dovoljenja:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Datoteke v souporabi" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Mapa v souporabi:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Pošlji datoteko" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Brskanje po datotekah" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Pridobivanje storitev ..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Iskanje novih naprav ..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Ozadnji program obexftp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Pridobivanje podatkov od oddaljene naprave ..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Povezovanje z napravo" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Razvijalec" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Brskanje po napravah" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Pošlji datoteke" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth je izklopljen" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Vključi Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Brskanje po napravah" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Znane naprave" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Dodaj napravo" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Nastavi Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Viden" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Izklopi Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 povezanih naprav" +msgstr[1] "%1 povezana naprava" +msgstr[2] "%1 povezani napravi" +msgstr[3] "%1 povezane naprave" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Najdenega ni bilo nobenega vmesnika" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth je vklopljen" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth je izklopljen" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Poveži se" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Pomočnik za pošiljanje datotek prek Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID naprave, kamor bodo bile datoteke poslane" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Datoteke, ki bodo poslane" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Povezovanje z: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Povezovanje z %1 ..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Izberite eno ali več datotek:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Izbrane datoteke: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Izberite napravo s seznama:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Preiskovanje" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Pošiljanje datoteke preko Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Pošiljanje datotek prek Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Omogoči ali onemogoči prejemanje datotek" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Shrani prejete datoteke v:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Vedno dovoli spreminjanje datotek v souporabi" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Omogoči ali onemogoči souporabo datotek" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Vedno zahtevaj PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Dovoli zunanjim napravam, da urejajo datoteke v souporabi" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Omogoči ali onemogoči splošno vključitev KDE Bluetooth" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Čarovnik za naprave Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Naprej" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Končaj" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Čarovnik za Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Naprava za povezavo" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Preiskovanje ..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Ročni PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Izberite napravo" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Znova zaženi čarovnik" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Zapri" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Nastavitev naprave je spodletela" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Nastavitev %1 je spodletela" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Vpišite PIN na tipkovnici, ko se pojavi pogovorno okno, in pritisnite Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Vpišite PIN v svoji napravi, ko se pojavi pogovorno okno" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Povezovanje z %1 ..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Povezovanje z: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Ujemanja" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Se ne ujema" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "Potrdite, da se PIN prikazan na \"%1\" ujema s PIN čarovnika." \ No newline at end of file diff --git a/bluedevil/po/sr/CMakeLists.txt b/bluedevil/po/sr/CMakeLists.txt new file mode 100644 index 00000000..08e3986f --- /dev/null +++ b/bluedevil/po/sr/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(sr ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/sr/bluedevil.po b/bluedevil/po/sr/bluedevil.po new file mode 100644 index 00000000..b4880d36 --- /dev/null +++ b/bluedevil/po/sr/bluedevil.po @@ -0,0 +1,1039 @@ +# Translation of bluedevil.po into Serbian. +# Chusslove Illich , 2011, 2013. +# Dalibor Djuric , 2011. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-11-12 01:26+0000\n" +"PO-Revision-Date: 2013-11-17 17:00+0100\n" +"Last-Translator: Chusslove Illich \n" +"Language-Team: Serbian \n" +"Language: sr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n" +"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Accelerator-Marker: &\n" +"X-Text-Markup: kde4\n" +"X-Environment: kde\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Часлав Илић" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "caslav.ilic@gmx.net" + +#: actionplugins/audio/audio.cpp:53 +#, kde-format +msgid "%1: audio service connection timeout" +msgstr "%1: прековреме повезивања са аудио сервисом." + +#: actionplugins/audio/audio.cpp:66 +#, kde-format +msgid "%1: audio service connected and configured" +msgstr "%1: аудио сервис је повезан и подешен." + +#: actionplugins/audio/helper/main.cpp:28 +msgid "Bluetooth Audio Helper" +msgstr "Помоћник за блутут звук" + +#: actionplugins/audio/helper/main.cpp:29 +#: actionplugins/input/helper/main.cpp:29 +#: actionplugins/networkdun/helper/main.cpp:29 +#: actionplugins/networkpanu/helper/main.cpp:29 +#: actionplugins/sendfile/helper/main.cpp:33 +#: daemon/helpers/filereceiver/main.cpp:32 daemon/kded/BlueDevilDaemon.cpp:90 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:79 monolithic/main.cpp:29 +#: wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010, УФО кодерс" + +#: actionplugins/audio/helper/main.cpp:31 +#: actionplugins/input/helper/main.cpp:31 +#: actionplugins/networkdun/helper/main.cpp:31 +#: actionplugins/networkpanu/helper/main.cpp:31 +#: actionplugins/sendfile/helper/main.cpp:35 +#: daemon/helpers/filereceiver/main.cpp:34 daemon/kded/BlueDevilDaemon.cpp:93 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:82 monolithic/main.cpp:31 +#: wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Алехандро Фијестас Оливарес" + +#: actionplugins/audio/helper/main.cpp:31 +#: actionplugins/input/helper/main.cpp:31 +#: actionplugins/networkdun/helper/main.cpp:31 +#: actionplugins/networkpanu/helper/main.cpp:31 +#: actionplugins/sendfile/helper/main.cpp:35 +#: daemon/helpers/filereceiver/main.cpp:34 daemon/kded/BlueDevilDaemon.cpp:93 +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/obexftpkded/ObexFtpDaemon.cpp:82 +#: monolithic/main.cpp:31 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Одржавалац" + +#: actionplugins/audio/helper/main.cpp:37 +#: actionplugins/input/helper/main.cpp:37 +#: actionplugins/networkdun/helper/main.cpp:37 +#: actionplugins/networkpanu/helper/main.cpp:37 +msgid "Device to connect" +msgstr "Уређај није повезан." + +#: actionplugins/input/helper/main.cpp:28 +msgid "Bluetooth Input Helper" +msgstr "Помоћник за блутут унос" + +#: actionplugins/input/input.cpp:54 +#, kde-format +msgid "%1: input service connection timeout" +msgstr "%1: прековреме повезивања са сервисом уноса." + +#: actionplugins/input/input.cpp:67 +#, kde-format +msgid "%1: input service connected and configured" +msgstr "%1: сервис уноса повезан и подешен." + +#: actionplugins/networkdun/helper/main.cpp:28 +msgid "Bluetooth Network Helper" +msgstr "Помоћник за блутут мрежу" + +#: actionplugins/networkdun/networkdun.cpp:65 +#: actionplugins/networkpanu/networkpanu.cpp:65 +#, kde-format +msgid "%1: Setting up..." +msgstr "%1: Постављам..." + +#: actionplugins/networkpanu/helper/main.cpp:28 +msgid "Bluetooth Network PANU Helper" +msgstr "Помоћник за блутут ПАНУ мрежу" + +#: actionplugins/sendfile/helper/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Помоћник за слање фајлова преко блутута" + +#: actionplugins/sendfile/helper/main.cpp:42 +#: actionplugins/sendfile/helper/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "УУИД уређаја на који се шаљу фајлови" + +#: actionplugins/sendfile/helper/main.cpp:44 +msgid "Files that will be sent" +msgstr "Фајлови које треба послати" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: actionplugins/sendfile/helper/pages/connecting.ui:17 +#: wizard/pages/nopairing.ui:51 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Повезујем: %1" + +#: actionplugins/sendfile/helper/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Повезујем %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: actionplugins/sendfile/helper/pages/selectdeviceandfilespage.cpp:87 +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Изаберите један или више фајлова:" + +#: actionplugins/sendfile/helper/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Изабрани фајлови: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Изаберите уређај са списка:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Скенирам..." + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +msgid "Sending file over Bluetooth" +msgstr "Шаљем фајл преко блутута" + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgctxt "File transfer origin" +msgid "From" +msgstr "Са" + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgctxt "File transfer destination" +msgid "To" +msgstr "На" + +#: actionplugins/sendfile/helper/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Слање фајлова преко блутута" + +#: actionplugins/sendfile/helper/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Пошаљи фајлове" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 тражи приступ овом рачунару" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Веруј и овласти" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Само овласти" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Одбиј" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Блутут систем за КДЕ" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Променити режим блутута на „%1“?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Потврди" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Одбиј" + +#: daemon/helpers/filereceiver/main.cpp:31 +msgid "Bluetooth File Receiver Helper" +msgstr "Помоћник за примање фајлова преко блутута" + +#: daemon/helpers/filereceiver/main.cpp:36 monolithic/main.cpp:32 +msgid "Developer" +msgstr "Програмер" + +#: daemon/helpers/filereceiver/main.cpp:36 daemon/kded/BlueDevilDaemon.cpp:96 +msgid "Eduardo Robles Elvira" +msgstr "Едуардо Роблес Елвира" + +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgid "Receiving file over Bluetooth" +msgstr "Примам фајл преко блутута" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:133 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 хоће да пошаље фајл %2" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:136 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Одустани" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:137 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Прихвати" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:138 +msgctxt "" +"Button to accept the incoming file transfer and show a Save as... dialog " +"that will let the user choose where will the file be downloaded to" +msgid "Save as..." +msgstr "Сачувај као..." + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 пита да ли је ПИН исправан: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "ПИН је исправан" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "ПИН није исправан" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "ПИН:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Потребан ПИН за упаривање са %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Уведи ПИН" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Морате унети ПИН да бисте упарили овај рачунар са %1. Учините то испод." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Увођење ПИН‑а" + +#: daemon/kded/BlueDevilDaemon.cpp:86 daemon/kded/BlueDevilDaemon.cpp:88 +msgid "Bluetooth Daemon" +msgstr "Блутут демон" + +#: daemon/obexftpkded/ObexFtpDaemon.cpp:75 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:77 +msgid "ObexFtp Daemon" +msgstr "ОбексФТП‑ов демон" + +#: fileitemactionplugin/sendfileitemaction.cpp:61 +msgid "Send via Bluetooth" +msgstr "Пошаљи преко блутута" + +#: fileitemactionplugin/sendfileitemaction.cpp:80 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Нађи уређај..." + +#: fileitemactionplugin/sendfileitemaction.cpp:83 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Други..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "скривен" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "увек видљив" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "привремено видљив" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 минут" +msgstr[1] "%1 минута" +msgstr[2] "%1 минута" +msgstr[3] "1 минут" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Име" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "У погону" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Видљивост" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Време откривања" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:206 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Подразумевани адаптер: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:208 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Адаптер: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:204 kcmodule/bluedeviladapters.cpp:229 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 минут" +msgstr[1] "%1 минута" +msgstr[2] "%1 минута" +msgstr[3] "1 минут" + +#: kcmodule/bluedeviladapters.cpp:241 +msgid "Bluetooth Adapters" +msgstr "Блутут адаптери" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Контролни модул за блутут адаптере" + +#: kcmodule/bluedeviladapters.cpp:243 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© 2010, Рафел Фернандез Лопез" + +#: kcmodule/bluedeviladapters.cpp:245 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Рафел Фернандез Лопез" + +#: kcmodule/bluedeviladapters.cpp:245 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Програмер и одржавалац" + +#: kcmodule/bluedeviladapters.cpp:322 +msgid "No adapters found. Please connect one." +msgstr "Није нађен ниједан адаптер. Повежите неки." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Активирај сарадњу блутута са КДЕ‑ом" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Блутут уређаји" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Контролни модул за блутут уређаје" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Детаљи" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Уклони" + +#: kcmodule/bluedevildevices.cpp:365 +msgid "Disconnect" +msgstr "Прекини везу" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Add Device..." +msgstr "Додај уређај..." + +#: kcmodule/bluedevildevices.cpp:466 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Изаберите нови алијас за %1" + +#: kcmodule/bluedevildevices.cpp:490 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Желите ли заиста да уклоните уређај „%1“ са списка познатих уређаја?" + +#: kcmodule/bluedevildevices.cpp:491 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Уклањање уређаја" + +#: kcmodule/bluedevildevices.cpp:555 +msgid "No remote devices have been added" +msgstr "Није додат ниједан удаљени уређај" + +#: kcmodule/bluedevildevices.cpp:557 +msgid "Click here to add a remote device" +msgstr "Кликните овде да додате удаљени уређај" + +#: kcmodule/bluedevildevices.cpp:596 kcmodule/bluedevildevices.cpp:638 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "непознат" + +#: kcmodule/bluedevildevices.cpp:599 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "телефон" + +#: kcmodule/bluedevildevices.cpp:602 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "модем" + +#: kcmodule/bluedevildevices.cpp:605 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "рачунар" + +#: kcmodule/bluedevildevices.cpp:608 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "мрежа" + +#: kcmodule/bluedevildevices.cpp:611 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "слушалице са микрофоном" + +#: kcmodule/bluedevildevices.cpp:614 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "слушалице" + +#: kcmodule/bluedevildevices.cpp:617 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "аудио" + +#: kcmodule/bluedevildevices.cpp:620 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "тастатура" + +#: kcmodule/bluedevildevices.cpp:623 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "миш" + +#: kcmodule/bluedevildevices.cpp:626 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "фотоапарат" + +#: kcmodule/bluedevildevices.cpp:629 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "штампач" + +#: kcmodule/bluedevildevices.cpp:632 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "џојпед" + +#: kcmodule/bluedevildevices.cpp:635 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "таблица" + +#: kcmodule/bluedevildevices.cpp:641 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Тип: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Пренос преко блутута" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Контролни модул за пренос преко блутута" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "никад" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "поуздани уређаји" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "сви уређаји" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "никад" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Увек" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "само читање" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "читање и писање" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Име" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Алијас" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Адреса" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Упарен" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Блокиран" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Поуздан" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:101 +#: kio/bluetooth/kiobluetooth.cpp:206 +msgid "No Bluetooth adapters have been found." +msgstr "Није нађен ниједан блутут адаптер." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Поправи" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Подразумевани блутут адаптер није видљив удаљеним уређајима." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Сарадња са блутут системом није оптимална." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Блутут није потпуно активиран." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Пријем" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Уписуј фајлове у:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Прихватај аутоматски:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Примај фајлове:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Дељење" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Дели фајлове:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Захтевај ПИН:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Дозволе:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Дељени фајлови" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Дељена фасцикла:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:142 +msgid "Send File" +msgstr "Слање фајла" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Прегледање фајлова" + +#: kio/bluetooth/kiobluetooth.cpp:78 +msgid "Human Interface Device" +msgstr "Уређај људског сучеља" + +#: kio/bluetooth/kiobluetooth.cpp:83 +msgid "Headset" +msgstr "Слушалице с микрофоном" + +#: kio/bluetooth/kiobluetooth.cpp:88 +msgid "Dial Up Network" +msgstr "модемска мрежа" + +#: kio/bluetooth/kiobluetooth.cpp:93 +msgid "Personal Area Network" +msgstr "мрежа личног подручја (⁠ПАН)" + +#: kio/bluetooth/kiobluetooth.cpp:124 +msgid "Retrieving services..." +msgstr "Добављам сервисе..." + +#: kio/bluetooth/kiobluetooth.cpp:174 +msgid "Scanning for new devices..." +msgstr "Скенирам за новим уређајима..." + +#: kio/obexftp/kio_obexftp.cpp:37 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:93 +msgid "Retrieving information from remote device..." +msgstr "Добављам податке са удаљеног уређаја..." + +#: kio/obexftp/kio_obexftp.cpp:158 +msgid "Connecting to the device" +msgstr "Повезујем уређај..." + +#: kio/obexftp/kio_obexftp.cpp:338 +msgid "Can't connect to the device" +msgstr "Не могу да повежем уређај" + +#: kio/obexftp/kio_obexftp.cpp:340 +msgid "Connection closed" +msgstr "Веза прекинута" + +#: kio/obexftp/kio_obexftp.cpp:438 +msgid "The device is busy, waiting..." +msgstr "Уређај је заузет, чекам..." + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Блутут" + +#: monolithic/monolithic.cpp:130 +msgid "Bluetooth is Off" +msgstr "Блутут је искључен" + +#: monolithic/monolithic.cpp:136 +msgid "Turn Bluetooth On" +msgstr "Укључи блутут" + +#: monolithic/monolithic.cpp:146 +msgid "Browse devices" +msgstr "Прегледај уређаје" + +#: monolithic/monolithic.cpp:156 +msgid "Known Devices" +msgstr "Познати уређаји" + +#: monolithic/monolithic.cpp:197 monolithic/monolithic.cpp:537 +msgid "Browse device..." +msgstr "Прегледај уређај..." + +#: monolithic/monolithic.cpp:206 monolithic/monolithic.cpp:545 +msgid "Send files..." +msgstr "Пошаљи фајлове..." + +#: monolithic/monolithic.cpp:224 monolithic/monolithic.cpp:248 +#: monolithic/monolithic.cpp:278 monolithic/monolithic.cpp:505 +#: monolithic/monolithic.cpp:512 +msgctxt "Action" +msgid "Disconnect" +msgstr "Прекини везу" + +#: monolithic/monolithic.cpp:230 monolithic/monolithic.cpp:260 +#: monolithic/monolithic.cpp:290 monolithic/monolithic.cpp:497 +#: monolithic/monolithic.cpp:517 monolithic/monolithic.cpp:553 +#: monolithic/monolithic.cpp:566 monolithic/monolithic.cpp:579 +msgctxt "Action" +msgid "Connect" +msgstr "Повежи" + +#: monolithic/monolithic.cpp:254 monolithic/monolithic.cpp:284 +#: monolithic/monolithic.cpp:502 +msgid "Connecting..." +msgstr "Повезујем..." + +#: monolithic/monolithic.cpp:299 monolithic/monolithic.cpp:592 +msgid "No supported services found" +msgstr "Није нађен ниједан подржани сервис." + +#: monolithic/monolithic.cpp:314 +msgid "Add Device" +msgstr "Додај уређај" + +#: monolithic/monolithic.cpp:318 +msgid "Configure Bluetooth" +msgstr "Подеси блутут" + +#: monolithic/monolithic.cpp:327 +msgid "Discoverable" +msgstr "Видљив" + +#: monolithic/monolithic.cpp:333 +msgid "Turn Bluetooth Off" +msgstr "Искључи блутут" + +#: monolithic/monolithic.cpp:357 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 повезан уређај" +msgstr[1] "%1 повезана уређаја" +msgstr[2] "%1 повезаних уређаја" +msgstr[3] "%1 повезан уређај" + +#: monolithic/monolithic.cpp:646 +msgid "No adapters found" +msgstr "Није нађен ниједан адаптер." + +#: monolithic/monolithic.cpp:673 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Блутут је укључен" + +#: monolithic/monolithic.cpp:675 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Блутут је искључен" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Укључи или искључи пријем фајлова" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Уписуј примљене фајлове у:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Да ли дозволити мењање дељених фајлова" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Укључи или искључи дељење фајлова" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Да ли захтевати ПИН" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Дозволи спољашњим уређајима да мењају дељене фајлове" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Укључи или искључи глобалну сарадњу блутута са КДЕ‑ом" + +#: wizard/bluewizard.cpp:44 +msgid "Bluetooth Device Wizard" +msgstr "Чаробњак блутут уређаја" + +#: wizard/bluewizard.cpp:71 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Следеће" + +#: wizard/bluewizard.cpp:72 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Заврши" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Чаробњак блутута" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Уређај за упаривање" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Скенирам..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Ручни ПИН:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +# >> @title:window +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Избор уређаја" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Поново покрени чаробњак" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Затвори" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Постављање уређаја није успело" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Постављање уређаја %1 није успело" + +# ? Wtf? +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Уведите ПИН у своју тастатуру када се појави и притисните Enter." + +# ? Wtf? +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:106 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Уведите ПИН у свој уређај када се појави." + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/services.ui:19 +msgid "The following compatible services have been found:" +msgstr "Нађени су следећи сагласни сервиси:" + +#: wizard/pages/servicespage.cpp:32 +msgid "Service Selection" +msgstr "Избор сервиса" + +#: wizard/pages/servicespage.cpp:76 +#, kde-format +msgid "%1 has been paired successfully" +msgstr "%1 је успешно упарен" + +#: wizard/pages/servicespage.cpp:87 +msgctxt "Do not initialize any service of the device" +msgid "None" +msgstr "ниједан" + +#: wizard/pages/servicespage.cpp:87 +msgid "Do not initialize any service" +msgstr "Не припремај ниједан сервис" + +# >> @action:button +#: wizard/pages/ssppairing.cpp:82 +msgid "Matches" +msgstr "Поклапа се" + +# >> @action:button +#: wizard/pages/ssppairing.cpp:84 +msgid "Does not match" +msgstr "Не поклапа се" + +#: wizard/pages/ssppairing.cpp:98 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "Потврдите да ПИН приказан на „%1“ одговара овом у чаробњаку." + +#. i18n: ectx: property (text), widget (QLabel, confirmLbl) +#: wizard/pages/ssppairing.ui:117 +#, no-c-format, kde-format +msgid "Connecting to %1 ..." +msgstr "Повезујем %1..." \ No newline at end of file diff --git a/bluedevil/po/sr@ijekavian/CMakeLists.txt b/bluedevil/po/sr@ijekavian/CMakeLists.txt new file mode 100644 index 00000000..63804dac --- /dev/null +++ b/bluedevil/po/sr@ijekavian/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(sr@ijekavian ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/sr@ijekavian/bluedevil.po b/bluedevil/po/sr@ijekavian/bluedevil.po new file mode 100644 index 00000000..54f6df63 --- /dev/null +++ b/bluedevil/po/sr@ijekavian/bluedevil.po @@ -0,0 +1,1039 @@ +# Translation of bluedevil.po into Serbian. +# Chusslove Illich , 2011, 2013. +# Dalibor Djuric , 2011. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-11-12 01:26+0000\n" +"PO-Revision-Date: 2013-11-17 17:00+0100\n" +"Last-Translator: Chusslove Illich \n" +"Language-Team: Serbian \n" +"Language: sr@ijekavian\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n" +"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Accelerator-Marker: &\n" +"X-Text-Markup: kde4\n" +"X-Environment: kde\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Часлав Илић" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "caslav.ilic@gmx.net" + +#: actionplugins/audio/audio.cpp:53 +#, kde-format +msgid "%1: audio service connection timeout" +msgstr "%1: прековријеме повезивања са аудио сервисом." + +#: actionplugins/audio/audio.cpp:66 +#, kde-format +msgid "%1: audio service connected and configured" +msgstr "%1: аудио сервис је повезан и подешен." + +#: actionplugins/audio/helper/main.cpp:28 +msgid "Bluetooth Audio Helper" +msgstr "Помоћник за блутут звук" + +#: actionplugins/audio/helper/main.cpp:29 +#: actionplugins/input/helper/main.cpp:29 +#: actionplugins/networkdun/helper/main.cpp:29 +#: actionplugins/networkpanu/helper/main.cpp:29 +#: actionplugins/sendfile/helper/main.cpp:33 +#: daemon/helpers/filereceiver/main.cpp:32 daemon/kded/BlueDevilDaemon.cpp:90 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:79 monolithic/main.cpp:29 +#: wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010, УФО кодерс" + +#: actionplugins/audio/helper/main.cpp:31 +#: actionplugins/input/helper/main.cpp:31 +#: actionplugins/networkdun/helper/main.cpp:31 +#: actionplugins/networkpanu/helper/main.cpp:31 +#: actionplugins/sendfile/helper/main.cpp:35 +#: daemon/helpers/filereceiver/main.cpp:34 daemon/kded/BlueDevilDaemon.cpp:93 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:82 monolithic/main.cpp:31 +#: wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Алехандро Фијестас Оливарес" + +#: actionplugins/audio/helper/main.cpp:31 +#: actionplugins/input/helper/main.cpp:31 +#: actionplugins/networkdun/helper/main.cpp:31 +#: actionplugins/networkpanu/helper/main.cpp:31 +#: actionplugins/sendfile/helper/main.cpp:35 +#: daemon/helpers/filereceiver/main.cpp:34 daemon/kded/BlueDevilDaemon.cpp:93 +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/obexftpkded/ObexFtpDaemon.cpp:82 +#: monolithic/main.cpp:31 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Одржавалац" + +#: actionplugins/audio/helper/main.cpp:37 +#: actionplugins/input/helper/main.cpp:37 +#: actionplugins/networkdun/helper/main.cpp:37 +#: actionplugins/networkpanu/helper/main.cpp:37 +msgid "Device to connect" +msgstr "Уређај није повезан." + +#: actionplugins/input/helper/main.cpp:28 +msgid "Bluetooth Input Helper" +msgstr "Помоћник за блутут унос" + +#: actionplugins/input/input.cpp:54 +#, kde-format +msgid "%1: input service connection timeout" +msgstr "%1: прековријеме повезивања са сервисом уноса." + +#: actionplugins/input/input.cpp:67 +#, kde-format +msgid "%1: input service connected and configured" +msgstr "%1: сервис уноса повезан и подешен." + +#: actionplugins/networkdun/helper/main.cpp:28 +msgid "Bluetooth Network Helper" +msgstr "Помоћник за блутут мрежу" + +#: actionplugins/networkdun/networkdun.cpp:65 +#: actionplugins/networkpanu/networkpanu.cpp:65 +#, kde-format +msgid "%1: Setting up..." +msgstr "%1: Постављам..." + +#: actionplugins/networkpanu/helper/main.cpp:28 +msgid "Bluetooth Network PANU Helper" +msgstr "Помоћник за блутут ПАНУ мрежу" + +#: actionplugins/sendfile/helper/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Помоћник за слање фајлова преко блутута" + +#: actionplugins/sendfile/helper/main.cpp:42 +#: actionplugins/sendfile/helper/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "УУИД уређаја на који се шаљу фајлови" + +#: actionplugins/sendfile/helper/main.cpp:44 +msgid "Files that will be sent" +msgstr "Фајлови које треба послати" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: actionplugins/sendfile/helper/pages/connecting.ui:17 +#: wizard/pages/nopairing.ui:51 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Повезујем: %1" + +#: actionplugins/sendfile/helper/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Повезујем %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: actionplugins/sendfile/helper/pages/selectdeviceandfilespage.cpp:87 +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Изаберите један или више фајлова:" + +#: actionplugins/sendfile/helper/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Изабрани фајлови: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Изаберите уређај са списка:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Скенирам..." + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +msgid "Sending file over Bluetooth" +msgstr "Шаљем фајл преко блутута" + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgctxt "File transfer origin" +msgid "From" +msgstr "Са" + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgctxt "File transfer destination" +msgid "To" +msgstr "На" + +#: actionplugins/sendfile/helper/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Слање фајлова преко блутута" + +#: actionplugins/sendfile/helper/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Пошаљи фајлове" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 тражи приступ овом рачунару" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Вјеруј и овласти" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Само овласти" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Одбиј" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Блутут систем за КДЕ" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Промијенити режим блутута на „%1“?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Потврди" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Одбиј" + +#: daemon/helpers/filereceiver/main.cpp:31 +msgid "Bluetooth File Receiver Helper" +msgstr "Помоћник за примање фајлова преко блутута" + +#: daemon/helpers/filereceiver/main.cpp:36 monolithic/main.cpp:32 +msgid "Developer" +msgstr "Програмер" + +#: daemon/helpers/filereceiver/main.cpp:36 daemon/kded/BlueDevilDaemon.cpp:96 +msgid "Eduardo Robles Elvira" +msgstr "Едуардо Роблес Елвира" + +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgid "Receiving file over Bluetooth" +msgstr "Примам фајл преко блутута" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:133 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 хоће да пошаље фајл %2" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:136 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Одустани" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:137 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Прихвати" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:138 +msgctxt "" +"Button to accept the incoming file transfer and show a Save as... dialog " +"that will let the user choose where will the file be downloaded to" +msgid "Save as..." +msgstr "Сачувај као..." + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 пита да ли је ПИН исправан: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "ПИН је исправан" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "ПИН није исправан" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "ПИН:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Потребан ПИН за упаривање са %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Уведи ПИН" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Морате унијети ПИН да бисте упарили овај рачунар са %1. Учините то испод." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Увођење ПИН‑а" + +#: daemon/kded/BlueDevilDaemon.cpp:86 daemon/kded/BlueDevilDaemon.cpp:88 +msgid "Bluetooth Daemon" +msgstr "Блутут демон" + +#: daemon/obexftpkded/ObexFtpDaemon.cpp:75 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:77 +msgid "ObexFtp Daemon" +msgstr "ОбексФТП‑ов демон" + +#: fileitemactionplugin/sendfileitemaction.cpp:61 +msgid "Send via Bluetooth" +msgstr "Пошаљи преко блутута" + +#: fileitemactionplugin/sendfileitemaction.cpp:80 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Нађи уређај..." + +#: fileitemactionplugin/sendfileitemaction.cpp:83 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Други..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "скривен" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "увијек видљив" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "привремено видљив" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 минут" +msgstr[1] "%1 минута" +msgstr[2] "%1 минута" +msgstr[3] "1 минут" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Име" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "У погону" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Видљивост" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Вријеме откривања" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:206 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Подразумијевани адаптер: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:208 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Адаптер: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:204 kcmodule/bluedeviladapters.cpp:229 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 минут" +msgstr[1] "%1 минута" +msgstr[2] "%1 минута" +msgstr[3] "1 минут" + +#: kcmodule/bluedeviladapters.cpp:241 +msgid "Bluetooth Adapters" +msgstr "Блутут адаптери" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Контролни модул за блутут адаптере" + +#: kcmodule/bluedeviladapters.cpp:243 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© 2010, Рафел Фернандез Лопез" + +#: kcmodule/bluedeviladapters.cpp:245 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Рафел Фернандез Лопез" + +#: kcmodule/bluedeviladapters.cpp:245 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Програмер и одржавалац" + +#: kcmodule/bluedeviladapters.cpp:322 +msgid "No adapters found. Please connect one." +msgstr "Није нађен ниједан адаптер. Повежите неки." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Активирај сарадњу блутута са КДЕ‑ом" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Блутут уређаји" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Контролни модул за блутут уређаје" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Детаљи" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Уклони" + +#: kcmodule/bluedevildevices.cpp:365 +msgid "Disconnect" +msgstr "Прекини везу" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Add Device..." +msgstr "Додај уређај..." + +#: kcmodule/bluedevildevices.cpp:466 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Изаберите нови алијас за %1" + +#: kcmodule/bluedevildevices.cpp:490 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Желите ли заиста да уклоните уређај „%1“ са списка познатих уређаја?" + +#: kcmodule/bluedevildevices.cpp:491 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Уклањање уређаја" + +#: kcmodule/bluedevildevices.cpp:555 +msgid "No remote devices have been added" +msgstr "Није додат ниједан удаљени уређај" + +#: kcmodule/bluedevildevices.cpp:557 +msgid "Click here to add a remote device" +msgstr "Кликните овдје да додате удаљени уређај" + +#: kcmodule/bluedevildevices.cpp:596 kcmodule/bluedevildevices.cpp:638 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "непознат" + +#: kcmodule/bluedevildevices.cpp:599 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "телефон" + +#: kcmodule/bluedevildevices.cpp:602 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "модем" + +#: kcmodule/bluedevildevices.cpp:605 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "рачунар" + +#: kcmodule/bluedevildevices.cpp:608 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "мрежа" + +#: kcmodule/bluedevildevices.cpp:611 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "слушалице са микрофоном" + +#: kcmodule/bluedevildevices.cpp:614 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "слушалице" + +#: kcmodule/bluedevildevices.cpp:617 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "аудио" + +#: kcmodule/bluedevildevices.cpp:620 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "тастатура" + +#: kcmodule/bluedevildevices.cpp:623 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "миш" + +#: kcmodule/bluedevildevices.cpp:626 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "фотоапарат" + +#: kcmodule/bluedevildevices.cpp:629 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "штампач" + +#: kcmodule/bluedevildevices.cpp:632 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "џојпед" + +#: kcmodule/bluedevildevices.cpp:635 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "таблица" + +#: kcmodule/bluedevildevices.cpp:641 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Тип: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Пренос преко блутута" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Контролни модул за пренос преко блутута" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "никад" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "поуздани уређаји" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "сви уређаји" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "никад" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Увијек" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "само читање" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "читање и писање" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Име" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Алијас" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Адреса" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Упарен" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Блокиран" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Поуздан" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:101 +#: kio/bluetooth/kiobluetooth.cpp:206 +msgid "No Bluetooth adapters have been found." +msgstr "Није нађен ниједан блутут адаптер." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Поправи" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Подразумевани блутут адаптер није видљив удаљеним уређајима." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Сарадња са блутут системом није оптимална." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Блутут није потпуно активиран." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Пријем" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Уписуј фајлове у:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Прихватај аутоматски:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Примај фајлове:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Дијељење" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Дијели фајлове:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Захтијевај ПИН:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Дозволе:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Дијељени фајлови" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Дијељена фасцикла:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:142 +msgid "Send File" +msgstr "Слање фајла" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Прегледање фајлова" + +#: kio/bluetooth/kiobluetooth.cpp:78 +msgid "Human Interface Device" +msgstr "Уређај људског сучеља" + +#: kio/bluetooth/kiobluetooth.cpp:83 +msgid "Headset" +msgstr "Слушалице с микрофоном" + +#: kio/bluetooth/kiobluetooth.cpp:88 +msgid "Dial Up Network" +msgstr "модемска мрежа" + +#: kio/bluetooth/kiobluetooth.cpp:93 +msgid "Personal Area Network" +msgstr "мрежа личног подручја (⁠ПАН)" + +#: kio/bluetooth/kiobluetooth.cpp:124 +msgid "Retrieving services..." +msgstr "Добављам сервисе..." + +#: kio/bluetooth/kiobluetooth.cpp:174 +msgid "Scanning for new devices..." +msgstr "Скенирам за новим уређајима..." + +#: kio/obexftp/kio_obexftp.cpp:37 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:93 +msgid "Retrieving information from remote device..." +msgstr "Добављам податке са удаљеног уређаја..." + +#: kio/obexftp/kio_obexftp.cpp:158 +msgid "Connecting to the device" +msgstr "Повезујем уређај..." + +#: kio/obexftp/kio_obexftp.cpp:338 +msgid "Can't connect to the device" +msgstr "Не могу да повежем уређај" + +#: kio/obexftp/kio_obexftp.cpp:340 +msgid "Connection closed" +msgstr "Веза прекинута" + +#: kio/obexftp/kio_obexftp.cpp:438 +msgid "The device is busy, waiting..." +msgstr "Уређај је заузет, чекам..." + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Блутут" + +#: monolithic/monolithic.cpp:130 +msgid "Bluetooth is Off" +msgstr "Блутут је искључен" + +#: monolithic/monolithic.cpp:136 +msgid "Turn Bluetooth On" +msgstr "Укључи блутут" + +#: monolithic/monolithic.cpp:146 +msgid "Browse devices" +msgstr "Прегледај уређаје" + +#: monolithic/monolithic.cpp:156 +msgid "Known Devices" +msgstr "Познати уређаји" + +#: monolithic/monolithic.cpp:197 monolithic/monolithic.cpp:537 +msgid "Browse device..." +msgstr "Прегледај уређај..." + +#: monolithic/monolithic.cpp:206 monolithic/monolithic.cpp:545 +msgid "Send files..." +msgstr "Пошаљи фајлове..." + +#: monolithic/monolithic.cpp:224 monolithic/monolithic.cpp:248 +#: monolithic/monolithic.cpp:278 monolithic/monolithic.cpp:505 +#: monolithic/monolithic.cpp:512 +msgctxt "Action" +msgid "Disconnect" +msgstr "Прекини везу" + +#: monolithic/monolithic.cpp:230 monolithic/monolithic.cpp:260 +#: monolithic/monolithic.cpp:290 monolithic/monolithic.cpp:497 +#: monolithic/monolithic.cpp:517 monolithic/monolithic.cpp:553 +#: monolithic/monolithic.cpp:566 monolithic/monolithic.cpp:579 +msgctxt "Action" +msgid "Connect" +msgstr "Повежи" + +#: monolithic/monolithic.cpp:254 monolithic/monolithic.cpp:284 +#: monolithic/monolithic.cpp:502 +msgid "Connecting..." +msgstr "Повезујем..." + +#: monolithic/monolithic.cpp:299 monolithic/monolithic.cpp:592 +msgid "No supported services found" +msgstr "Није нађен ниједан подржани сервис." + +#: monolithic/monolithic.cpp:314 +msgid "Add Device" +msgstr "Додај уређај" + +#: monolithic/monolithic.cpp:318 +msgid "Configure Bluetooth" +msgstr "Подеси блутут" + +#: monolithic/monolithic.cpp:327 +msgid "Discoverable" +msgstr "Видљив" + +#: monolithic/monolithic.cpp:333 +msgid "Turn Bluetooth Off" +msgstr "Искључи блутут" + +#: monolithic/monolithic.cpp:357 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 повезан уређај" +msgstr[1] "%1 повезана уређаја" +msgstr[2] "%1 повезаних уређаја" +msgstr[3] "%1 повезан уређај" + +#: monolithic/monolithic.cpp:646 +msgid "No adapters found" +msgstr "Није нађен ниједан адаптер." + +#: monolithic/monolithic.cpp:673 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Блутут је укључен" + +#: monolithic/monolithic.cpp:675 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Блутут је искључен" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Укључи или искључи пријем фајлова" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Уписуј примљене фајлове у:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Да ли дозволити мијењање дељених фајлова" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Укључи или искључи дељење фајлова" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Да ли захтијевати ПИН" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Дозволи спољашњим уређајима да мијењају дијељене фајлове" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Укључи или искључи глобалну сарадњу блутута са КДЕ‑ом" + +#: wizard/bluewizard.cpp:44 +msgid "Bluetooth Device Wizard" +msgstr "Чаробњак блутут уређаја" + +#: wizard/bluewizard.cpp:71 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Сљедеће" + +#: wizard/bluewizard.cpp:72 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Заврши" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Чаробњак блутута" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Уређај за упаривање" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Скенирам..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Ручни ПИН:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +# >> @title:window +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Избор уређаја" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Поново покрени чаробњак" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Затвори" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Постављање уређаја није успјело" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Постављање уређаја %1 није успјело" + +# ? Wtf? +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Уведите ПИН у своју тастатуру када се појави и притисните Enter." + +# ? Wtf? +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:106 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Уведите ПИН у свој уређај када се појави." + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/services.ui:19 +msgid "The following compatible services have been found:" +msgstr "Нађени су сљедећи сагласни сервиси:" + +#: wizard/pages/servicespage.cpp:32 +msgid "Service Selection" +msgstr "Избор сервиса" + +#: wizard/pages/servicespage.cpp:76 +#, kde-format +msgid "%1 has been paired successfully" +msgstr "%1 је успјешно упарен" + +#: wizard/pages/servicespage.cpp:87 +msgctxt "Do not initialize any service of the device" +msgid "None" +msgstr "ниједан" + +#: wizard/pages/servicespage.cpp:87 +msgid "Do not initialize any service" +msgstr "Не припремај ниједан сервис" + +# >> @action:button +#: wizard/pages/ssppairing.cpp:82 +msgid "Matches" +msgstr "Поклапа се" + +# >> @action:button +#: wizard/pages/ssppairing.cpp:84 +msgid "Does not match" +msgstr "Не поклапа се" + +#: wizard/pages/ssppairing.cpp:98 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "Потврдите да ПИН приказан на „%1“ одговара овом у чаробњаку." + +#. i18n: ectx: property (text), widget (QLabel, confirmLbl) +#: wizard/pages/ssppairing.ui:117 +#, no-c-format, kde-format +msgid "Connecting to %1 ..." +msgstr "Повезујем %1..." \ No newline at end of file diff --git a/bluedevil/po/sr@ijekavianlatin/CMakeLists.txt b/bluedevil/po/sr@ijekavianlatin/CMakeLists.txt new file mode 100644 index 00000000..9d588b1d --- /dev/null +++ b/bluedevil/po/sr@ijekavianlatin/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(sr@ijekavianlatin ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/sr@ijekavianlatin/bluedevil.po b/bluedevil/po/sr@ijekavianlatin/bluedevil.po new file mode 100644 index 00000000..8c9a0a5c --- /dev/null +++ b/bluedevil/po/sr@ijekavianlatin/bluedevil.po @@ -0,0 +1,1039 @@ +# Translation of bluedevil.po into Serbian. +# Chusslove Illich , 2011, 2013. +# Dalibor Djuric , 2011. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-11-12 01:26+0000\n" +"PO-Revision-Date: 2013-11-17 17:00+0100\n" +"Last-Translator: Chusslove Illich \n" +"Language-Team: Serbian \n" +"Language: sr@ijekavianlatin\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n" +"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Accelerator-Marker: &\n" +"X-Text-Markup: kde4\n" +"X-Environment: kde\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Časlav Ilić" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "caslav.ilic@gmx.net" + +#: actionplugins/audio/audio.cpp:53 +#, kde-format +msgid "%1: audio service connection timeout" +msgstr "%1: prekovrijeme povezivanja sa audio servisom." + +#: actionplugins/audio/audio.cpp:66 +#, kde-format +msgid "%1: audio service connected and configured" +msgstr "%1: audio servis je povezan i podešen." + +#: actionplugins/audio/helper/main.cpp:28 +msgid "Bluetooth Audio Helper" +msgstr "Pomoćnik za bluetooth zvuk" + +#: actionplugins/audio/helper/main.cpp:29 +#: actionplugins/input/helper/main.cpp:29 +#: actionplugins/networkdun/helper/main.cpp:29 +#: actionplugins/networkpanu/helper/main.cpp:29 +#: actionplugins/sendfile/helper/main.cpp:33 +#: daemon/helpers/filereceiver/main.cpp:32 daemon/kded/BlueDevilDaemon.cpp:90 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:79 monolithic/main.cpp:29 +#: wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010, UFO Coders" + +#: actionplugins/audio/helper/main.cpp:31 +#: actionplugins/input/helper/main.cpp:31 +#: actionplugins/networkdun/helper/main.cpp:31 +#: actionplugins/networkpanu/helper/main.cpp:31 +#: actionplugins/sendfile/helper/main.cpp:35 +#: daemon/helpers/filereceiver/main.cpp:34 daemon/kded/BlueDevilDaemon.cpp:93 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:82 monolithic/main.cpp:31 +#: wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alehandro Fijestas Olivares" + +#: actionplugins/audio/helper/main.cpp:31 +#: actionplugins/input/helper/main.cpp:31 +#: actionplugins/networkdun/helper/main.cpp:31 +#: actionplugins/networkpanu/helper/main.cpp:31 +#: actionplugins/sendfile/helper/main.cpp:35 +#: daemon/helpers/filereceiver/main.cpp:34 daemon/kded/BlueDevilDaemon.cpp:93 +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/obexftpkded/ObexFtpDaemon.cpp:82 +#: monolithic/main.cpp:31 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Održavalac" + +#: actionplugins/audio/helper/main.cpp:37 +#: actionplugins/input/helper/main.cpp:37 +#: actionplugins/networkdun/helper/main.cpp:37 +#: actionplugins/networkpanu/helper/main.cpp:37 +msgid "Device to connect" +msgstr "Uređaj nije povezan." + +#: actionplugins/input/helper/main.cpp:28 +msgid "Bluetooth Input Helper" +msgstr "Pomoćnik za bluetooth unos" + +#: actionplugins/input/input.cpp:54 +#, kde-format +msgid "%1: input service connection timeout" +msgstr "%1: prekovrijeme povezivanja sa servisom unosa." + +#: actionplugins/input/input.cpp:67 +#, kde-format +msgid "%1: input service connected and configured" +msgstr "%1: servis unosa povezan i podešen." + +#: actionplugins/networkdun/helper/main.cpp:28 +msgid "Bluetooth Network Helper" +msgstr "Pomoćnik za bluetooth mrežu" + +#: actionplugins/networkdun/networkdun.cpp:65 +#: actionplugins/networkpanu/networkpanu.cpp:65 +#, kde-format +msgid "%1: Setting up..." +msgstr "%1: Postavljam..." + +#: actionplugins/networkpanu/helper/main.cpp:28 +msgid "Bluetooth Network PANU Helper" +msgstr "Pomoćnik za bluetooth PANU mrežu" + +#: actionplugins/sendfile/helper/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Pomoćnik za slanje fajlova preko Bluetootha" + +#: actionplugins/sendfile/helper/main.cpp:42 +#: actionplugins/sendfile/helper/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID uređaja na koji se šalju fajlovi" + +#: actionplugins/sendfile/helper/main.cpp:44 +msgid "Files that will be sent" +msgstr "Fajlovi koje treba poslati" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: actionplugins/sendfile/helper/pages/connecting.ui:17 +#: wizard/pages/nopairing.ui:51 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Povezujem: %1" + +#: actionplugins/sendfile/helper/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Povezujem %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: actionplugins/sendfile/helper/pages/selectdeviceandfilespage.cpp:87 +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Izaberite jedan ili više fajlova:" + +#: actionplugins/sendfile/helper/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Izabrani fajlovi: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Izaberite uređaj sa spiska:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Skeniram..." + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +msgid "Sending file over Bluetooth" +msgstr "Šaljem fajl preko Bluetootha" + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgctxt "File transfer origin" +msgid "From" +msgstr "Sa" + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgctxt "File transfer destination" +msgid "To" +msgstr "Na" + +#: actionplugins/sendfile/helper/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Slanje fajlova preko Bluetootha" + +#: actionplugins/sendfile/helper/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Pošalji fajlove" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 traži pristup ovom računaru" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Vjeruj i ovlasti" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Samo ovlasti" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Odbij" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Bluetooth sistem za KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Promijeniti režim Bluetootha na „%1“?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Potvrdi" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Odbij" + +#: daemon/helpers/filereceiver/main.cpp:31 +msgid "Bluetooth File Receiver Helper" +msgstr "Pomoćnik za primanje fajlova preko Bluetootha" + +#: daemon/helpers/filereceiver/main.cpp:36 monolithic/main.cpp:32 +msgid "Developer" +msgstr "Programer" + +#: daemon/helpers/filereceiver/main.cpp:36 daemon/kded/BlueDevilDaemon.cpp:96 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgid "Receiving file over Bluetooth" +msgstr "Primam fajl preko Bluetootha" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:133 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 hoće da pošalje fajl %2" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:136 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Odustani" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:137 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Prihvati" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:138 +msgctxt "" +"Button to accept the incoming file transfer and show a Save as... dialog " +"that will let the user choose where will the file be downloaded to" +msgid "Save as..." +msgstr "Sačuvaj kao..." + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 pita da li je PIN ispravan: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN je ispravan" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN nije ispravan" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Potreban PIN za uparivanje sa %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Uvedi PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Morate unijeti PIN da biste uparili ovaj računar sa %1. Učinite to ispod." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Uvođenje PIN‑a" + +#: daemon/kded/BlueDevilDaemon.cpp:86 daemon/kded/BlueDevilDaemon.cpp:88 +msgid "Bluetooth Daemon" +msgstr "Bluetooth demon" + +#: daemon/obexftpkded/ObexFtpDaemon.cpp:75 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:77 +msgid "ObexFtp Daemon" +msgstr "ObexFTP‑ov demon" + +#: fileitemactionplugin/sendfileitemaction.cpp:61 +msgid "Send via Bluetooth" +msgstr "Pošalji preko Bluetootha" + +#: fileitemactionplugin/sendfileitemaction.cpp:80 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Nađi uređaj..." + +#: fileitemactionplugin/sendfileitemaction.cpp:83 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Drugi..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "skriven" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "uvijek vidljiv" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "privremeno vidljiv" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 minut" +msgstr[1] "%1 minuta" +msgstr[2] "%1 minuta" +msgstr[3] "1 minut" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Ime" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "U pogonu" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Vidljivost" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Vrijeme otkrivanja" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:206 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Podrazumijevani adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:208 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:204 kcmodule/bluedeviladapters.cpp:229 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 minut" +msgstr[1] "%1 minuta" +msgstr[2] "%1 minuta" +msgstr[3] "1 minut" + +#: kcmodule/bluedeviladapters.cpp:241 +msgid "Bluetooth Adapters" +msgstr "Bluetooth adapteri" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Kontrolni modul za bluetooth adaptere" + +#: kcmodule/bluedeviladapters.cpp:243 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© 2010, Rafel Fernandez Lopez" + +#: kcmodule/bluedeviladapters.cpp:245 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafel Fernandez Lopez" + +#: kcmodule/bluedeviladapters.cpp:245 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Programer i održavalac" + +#: kcmodule/bluedeviladapters.cpp:322 +msgid "No adapters found. Please connect one." +msgstr "Nije nađen nijedan adapter. Povežite neki." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Aktiviraj saradnju Bluetootha sa KDE‑om" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth uređaji" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Kontrolni modul za bluetooth uređaje" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detalji" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Ukloni" + +#: kcmodule/bluedevildevices.cpp:365 +msgid "Disconnect" +msgstr "Prekini vezu" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Add Device..." +msgstr "Dodaj uređaj..." + +#: kcmodule/bluedevildevices.cpp:466 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Izaberite novi alijas za %1" + +#: kcmodule/bluedevildevices.cpp:490 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Želite li zaista da uklonite uređaj „%1“ sa spiska poznatih uređaja?" + +#: kcmodule/bluedevildevices.cpp:491 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Uklanjanje uređaja" + +#: kcmodule/bluedevildevices.cpp:555 +msgid "No remote devices have been added" +msgstr "Nije dodat nijedan udaljeni uređaj" + +#: kcmodule/bluedevildevices.cpp:557 +msgid "Click here to add a remote device" +msgstr "Kliknite ovdje da dodate udaljeni uređaj" + +#: kcmodule/bluedevildevices.cpp:596 kcmodule/bluedevildevices.cpp:638 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "nepoznat" + +#: kcmodule/bluedevildevices.cpp:599 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "telefon" + +#: kcmodule/bluedevildevices.cpp:602 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "modem" + +#: kcmodule/bluedevildevices.cpp:605 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "računar" + +#: kcmodule/bluedevildevices.cpp:608 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "mreža" + +#: kcmodule/bluedevildevices.cpp:611 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "slušalice sa mikrofonom" + +#: kcmodule/bluedevildevices.cpp:614 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "slušalice" + +#: kcmodule/bluedevildevices.cpp:617 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "audio" + +#: kcmodule/bluedevildevices.cpp:620 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "tastatura" + +#: kcmodule/bluedevildevices.cpp:623 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "miš" + +#: kcmodule/bluedevildevices.cpp:626 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "fotoaparat" + +#: kcmodule/bluedevildevices.cpp:629 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "štampač" + +#: kcmodule/bluedevildevices.cpp:632 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "džojped" + +#: kcmodule/bluedevildevices.cpp:635 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "tablica" + +#: kcmodule/bluedevildevices.cpp:641 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tip: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Prenos preko Bluetootha" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Kontrolni modul za prenos preko Bluetootha" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "nikad" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "pouzdani uređaji" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "svi uređaji" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "nikad" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Uvijek" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "samo čitanje" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "čitanje i pisanje" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Ime" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alijas" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adresa" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Uparen" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blokiran" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Pouzdan" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:101 +#: kio/bluetooth/kiobluetooth.cpp:206 +msgid "No Bluetooth adapters have been found." +msgstr "Nije nađen nijedan bluetooth adapter." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Popravi" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Podrazumevani bluetooth adapter nije vidljiv udaljenim uređajima." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Saradnja sa bluetooth sistemom nije optimalna." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth nije potpuno aktiviran." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Prijem" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Upisuj fajlove u:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Prihvataj automatski:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Primaj fajlove:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Dijeljenje" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Dijeli fajlove:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Zahtijevaj PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Dozvole:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Dijeljeni fajlovi" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Dijeljena fascikla:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:142 +msgid "Send File" +msgstr "Slanje fajla" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Pregledanje fajlova" + +#: kio/bluetooth/kiobluetooth.cpp:78 +msgid "Human Interface Device" +msgstr "Uređaj ljudskog sučelja" + +#: kio/bluetooth/kiobluetooth.cpp:83 +msgid "Headset" +msgstr "Slušalice s mikrofonom" + +#: kio/bluetooth/kiobluetooth.cpp:88 +msgid "Dial Up Network" +msgstr "modemska mreža" + +#: kio/bluetooth/kiobluetooth.cpp:93 +msgid "Personal Area Network" +msgstr "mreža ličnog područja (⁠PAN)" + +#: kio/bluetooth/kiobluetooth.cpp:124 +msgid "Retrieving services..." +msgstr "Dobavljam servise..." + +#: kio/bluetooth/kiobluetooth.cpp:174 +msgid "Scanning for new devices..." +msgstr "Skeniram za novim uređajima..." + +#: kio/obexftp/kio_obexftp.cpp:37 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:93 +msgid "Retrieving information from remote device..." +msgstr "Dobavljam podatke sa udaljenog uređaja..." + +#: kio/obexftp/kio_obexftp.cpp:158 +msgid "Connecting to the device" +msgstr "Povezujem uređaj..." + +#: kio/obexftp/kio_obexftp.cpp:338 +msgid "Can't connect to the device" +msgstr "Ne mogu da povežem uređaj" + +#: kio/obexftp/kio_obexftp.cpp:340 +msgid "Connection closed" +msgstr "Veza prekinuta" + +#: kio/obexftp/kio_obexftp.cpp:438 +msgid "The device is busy, waiting..." +msgstr "Uređaj je zauzet, čekam..." + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/monolithic.cpp:130 +msgid "Bluetooth is Off" +msgstr "Bluetooth je isključen" + +#: monolithic/monolithic.cpp:136 +msgid "Turn Bluetooth On" +msgstr "Uključi Bluetooth" + +#: monolithic/monolithic.cpp:146 +msgid "Browse devices" +msgstr "Pregledaj uređaje" + +#: monolithic/monolithic.cpp:156 +msgid "Known Devices" +msgstr "Poznati uređaji" + +#: monolithic/monolithic.cpp:197 monolithic/monolithic.cpp:537 +msgid "Browse device..." +msgstr "Pregledaj uređaj..." + +#: monolithic/monolithic.cpp:206 monolithic/monolithic.cpp:545 +msgid "Send files..." +msgstr "Pošalji fajlove..." + +#: monolithic/monolithic.cpp:224 monolithic/monolithic.cpp:248 +#: monolithic/monolithic.cpp:278 monolithic/monolithic.cpp:505 +#: monolithic/monolithic.cpp:512 +msgctxt "Action" +msgid "Disconnect" +msgstr "Prekini vezu" + +#: monolithic/monolithic.cpp:230 monolithic/monolithic.cpp:260 +#: monolithic/monolithic.cpp:290 monolithic/monolithic.cpp:497 +#: monolithic/monolithic.cpp:517 monolithic/monolithic.cpp:553 +#: monolithic/monolithic.cpp:566 monolithic/monolithic.cpp:579 +msgctxt "Action" +msgid "Connect" +msgstr "Poveži" + +#: monolithic/monolithic.cpp:254 monolithic/monolithic.cpp:284 +#: monolithic/monolithic.cpp:502 +msgid "Connecting..." +msgstr "Povezujem..." + +#: monolithic/monolithic.cpp:299 monolithic/monolithic.cpp:592 +msgid "No supported services found" +msgstr "Nije nađen nijedan podržani servis." + +#: monolithic/monolithic.cpp:314 +msgid "Add Device" +msgstr "Dodaj uređaj" + +#: monolithic/monolithic.cpp:318 +msgid "Configure Bluetooth" +msgstr "Podesi Bluetooth" + +#: monolithic/monolithic.cpp:327 +msgid "Discoverable" +msgstr "Vidljiv" + +#: monolithic/monolithic.cpp:333 +msgid "Turn Bluetooth Off" +msgstr "Isključi Bluetooth" + +#: monolithic/monolithic.cpp:357 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 povezan uređaj" +msgstr[1] "%1 povezana uređaja" +msgstr[2] "%1 povezanih uređaja" +msgstr[3] "%1 povezan uređaj" + +#: monolithic/monolithic.cpp:646 +msgid "No adapters found" +msgstr "Nije nađen nijedan adapter." + +#: monolithic/monolithic.cpp:673 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth je uključen" + +#: monolithic/monolithic.cpp:675 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth je isključen" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Uključi ili isključi prijem fajlova" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Upisuj primljene fajlove u:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Da li dozvoliti mijenjanje deljenih fajlova" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Uključi ili isključi deljenje fajlova" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Da li zahtijevati PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Dozvoli spoljašnjim uređajima da mijenjaju dijeljene fajlove" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Uključi ili isključi globalnu saradnju Bluetootha sa KDE‑om" + +#: wizard/bluewizard.cpp:44 +msgid "Bluetooth Device Wizard" +msgstr "Čarobnjak bluetooth uređaja" + +#: wizard/bluewizard.cpp:71 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Sljedeće" + +#: wizard/bluewizard.cpp:72 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Završi" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Čarobnjak Bluetootha" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Uređaj za uparivanje" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Skeniram..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Ručni PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +# >> @title:window +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Izbor uređaja" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Ponovo pokreni čarobnjak" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Zatvori" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Postavljanje uređaja nije uspjelo" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Postavljanje uređaja %1 nije uspjelo" + +# ? Wtf? +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Uvedite PIN u svoju tastaturu kada se pojavi i pritisnite Enter." + +# ? Wtf? +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:106 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Uvedite PIN u svoj uređaj kada se pojavi." + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/services.ui:19 +msgid "The following compatible services have been found:" +msgstr "Nađeni su sljedeći saglasni servisi:" + +#: wizard/pages/servicespage.cpp:32 +msgid "Service Selection" +msgstr "Izbor servisa" + +#: wizard/pages/servicespage.cpp:76 +#, kde-format +msgid "%1 has been paired successfully" +msgstr "%1 je uspješno uparen" + +#: wizard/pages/servicespage.cpp:87 +msgctxt "Do not initialize any service of the device" +msgid "None" +msgstr "nijedan" + +#: wizard/pages/servicespage.cpp:87 +msgid "Do not initialize any service" +msgstr "Ne pripremaj nijedan servis" + +# >> @action:button +#: wizard/pages/ssppairing.cpp:82 +msgid "Matches" +msgstr "Poklapa se" + +# >> @action:button +#: wizard/pages/ssppairing.cpp:84 +msgid "Does not match" +msgstr "Ne poklapa se" + +#: wizard/pages/ssppairing.cpp:98 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "Potvrdite da PIN prikazan na „%1“ odgovara ovom u čarobnjaku." + +#. i18n: ectx: property (text), widget (QLabel, confirmLbl) +#: wizard/pages/ssppairing.ui:117 +#, no-c-format, kde-format +msgid "Connecting to %1 ..." +msgstr "Povezujem %1..." \ No newline at end of file diff --git a/bluedevil/po/sr@latin/CMakeLists.txt b/bluedevil/po/sr@latin/CMakeLists.txt new file mode 100644 index 00000000..68979faa --- /dev/null +++ b/bluedevil/po/sr@latin/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(sr@latin ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/sr@latin/bluedevil.po b/bluedevil/po/sr@latin/bluedevil.po new file mode 100644 index 00000000..7709de63 --- /dev/null +++ b/bluedevil/po/sr@latin/bluedevil.po @@ -0,0 +1,1039 @@ +# Translation of bluedevil.po into Serbian. +# Chusslove Illich , 2011, 2013. +# Dalibor Djuric , 2011. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-11-12 01:26+0000\n" +"PO-Revision-Date: 2013-11-17 17:00+0100\n" +"Last-Translator: Chusslove Illich \n" +"Language-Team: Serbian \n" +"Language: sr@latin\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n" +"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Accelerator-Marker: &\n" +"X-Text-Markup: kde4\n" +"X-Environment: kde\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Časlav Ilić" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "caslav.ilic@gmx.net" + +#: actionplugins/audio/audio.cpp:53 +#, kde-format +msgid "%1: audio service connection timeout" +msgstr "%1: prekovreme povezivanja sa audio servisom." + +#: actionplugins/audio/audio.cpp:66 +#, kde-format +msgid "%1: audio service connected and configured" +msgstr "%1: audio servis je povezan i podešen." + +#: actionplugins/audio/helper/main.cpp:28 +msgid "Bluetooth Audio Helper" +msgstr "Pomoćnik za bluetooth zvuk" + +#: actionplugins/audio/helper/main.cpp:29 +#: actionplugins/input/helper/main.cpp:29 +#: actionplugins/networkdun/helper/main.cpp:29 +#: actionplugins/networkpanu/helper/main.cpp:29 +#: actionplugins/sendfile/helper/main.cpp:33 +#: daemon/helpers/filereceiver/main.cpp:32 daemon/kded/BlueDevilDaemon.cpp:90 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:79 monolithic/main.cpp:29 +#: wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010, UFO Coders" + +#: actionplugins/audio/helper/main.cpp:31 +#: actionplugins/input/helper/main.cpp:31 +#: actionplugins/networkdun/helper/main.cpp:31 +#: actionplugins/networkpanu/helper/main.cpp:31 +#: actionplugins/sendfile/helper/main.cpp:35 +#: daemon/helpers/filereceiver/main.cpp:34 daemon/kded/BlueDevilDaemon.cpp:93 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:82 monolithic/main.cpp:31 +#: wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alehandro Fijestas Olivares" + +#: actionplugins/audio/helper/main.cpp:31 +#: actionplugins/input/helper/main.cpp:31 +#: actionplugins/networkdun/helper/main.cpp:31 +#: actionplugins/networkpanu/helper/main.cpp:31 +#: actionplugins/sendfile/helper/main.cpp:35 +#: daemon/helpers/filereceiver/main.cpp:34 daemon/kded/BlueDevilDaemon.cpp:93 +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/obexftpkded/ObexFtpDaemon.cpp:82 +#: monolithic/main.cpp:31 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Održavalac" + +#: actionplugins/audio/helper/main.cpp:37 +#: actionplugins/input/helper/main.cpp:37 +#: actionplugins/networkdun/helper/main.cpp:37 +#: actionplugins/networkpanu/helper/main.cpp:37 +msgid "Device to connect" +msgstr "Uređaj nije povezan." + +#: actionplugins/input/helper/main.cpp:28 +msgid "Bluetooth Input Helper" +msgstr "Pomoćnik za bluetooth unos" + +#: actionplugins/input/input.cpp:54 +#, kde-format +msgid "%1: input service connection timeout" +msgstr "%1: prekovreme povezivanja sa servisom unosa." + +#: actionplugins/input/input.cpp:67 +#, kde-format +msgid "%1: input service connected and configured" +msgstr "%1: servis unosa povezan i podešen." + +#: actionplugins/networkdun/helper/main.cpp:28 +msgid "Bluetooth Network Helper" +msgstr "Pomoćnik za bluetooth mrežu" + +#: actionplugins/networkdun/networkdun.cpp:65 +#: actionplugins/networkpanu/networkpanu.cpp:65 +#, kde-format +msgid "%1: Setting up..." +msgstr "%1: Postavljam..." + +#: actionplugins/networkpanu/helper/main.cpp:28 +msgid "Bluetooth Network PANU Helper" +msgstr "Pomoćnik za bluetooth PANU mrežu" + +#: actionplugins/sendfile/helper/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Pomoćnik za slanje fajlova preko Bluetootha" + +#: actionplugins/sendfile/helper/main.cpp:42 +#: actionplugins/sendfile/helper/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID uređaja na koji se šalju fajlovi" + +#: actionplugins/sendfile/helper/main.cpp:44 +msgid "Files that will be sent" +msgstr "Fajlovi koje treba poslati" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: actionplugins/sendfile/helper/pages/connecting.ui:17 +#: wizard/pages/nopairing.ui:51 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Povezujem: %1" + +#: actionplugins/sendfile/helper/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Povezujem %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: actionplugins/sendfile/helper/pages/selectdeviceandfilespage.cpp:87 +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Izaberite jedan ili više fajlova:" + +#: actionplugins/sendfile/helper/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Izabrani fajlovi: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Izaberite uređaj sa spiska:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: actionplugins/sendfile/helper/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Skeniram..." + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +msgid "Sending file over Bluetooth" +msgstr "Šaljem fajl preko Bluetootha" + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgctxt "File transfer origin" +msgid "From" +msgstr "Sa" + +#: actionplugins/sendfile/helper/sendfilesjob.cpp:83 +#: actionplugins/sendfile/helper/sendfilesjob.cpp:115 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgctxt "File transfer destination" +msgid "To" +msgstr "Na" + +#: actionplugins/sendfile/helper/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Slanje fajlova preko Bluetootha" + +#: actionplugins/sendfile/helper/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Pošalji fajlove" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 traži pristup ovom računaru" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Veruj i ovlasti" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Samo ovlasti" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Odbij" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Bluetooth sistem za KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Promeniti režim Bluetootha na „%1“?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Potvrdi" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Odbij" + +#: daemon/helpers/filereceiver/main.cpp:31 +msgid "Bluetooth File Receiver Helper" +msgstr "Pomoćnik za primanje fajlova preko Bluetootha" + +#: daemon/helpers/filereceiver/main.cpp:36 monolithic/main.cpp:32 +msgid "Developer" +msgstr "Programer" + +#: daemon/helpers/filereceiver/main.cpp:36 daemon/kded/BlueDevilDaemon.cpp:96 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:102 +#: daemon/helpers/filereceiver/openobex/filetransferjob.cpp:141 +msgid "Receiving file over Bluetooth" +msgstr "Primam fajl preko Bluetootha" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:133 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 hoće da pošalje fajl %2" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:136 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Odustani" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:137 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Prihvati" + +#: daemon/helpers/filereceiver/openobex/serversession.cpp:138 +msgctxt "" +"Button to accept the incoming file transfer and show a Save as... dialog " +"that will let the user choose where will the file be downloaded to" +msgid "Save as..." +msgstr "Sačuvaj kao..." + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 pita da li je PIN ispravan: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN je ispravan" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN nije ispravan" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Potreban PIN za uparivanje sa %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Uvedi PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Morate uneti PIN da biste uparili ovaj računar sa %1. Učinite to ispod." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Uvođenje PIN‑a" + +#: daemon/kded/BlueDevilDaemon.cpp:86 daemon/kded/BlueDevilDaemon.cpp:88 +msgid "Bluetooth Daemon" +msgstr "Bluetooth demon" + +#: daemon/obexftpkded/ObexFtpDaemon.cpp:75 +#: daemon/obexftpkded/ObexFtpDaemon.cpp:77 +msgid "ObexFtp Daemon" +msgstr "ObexFTP‑ov demon" + +#: fileitemactionplugin/sendfileitemaction.cpp:61 +msgid "Send via Bluetooth" +msgstr "Pošalji preko Bluetootha" + +#: fileitemactionplugin/sendfileitemaction.cpp:80 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Nađi uređaj..." + +#: fileitemactionplugin/sendfileitemaction.cpp:83 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Drugi..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "skriven" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "uvek vidljiv" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "privremeno vidljiv" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 minut" +msgstr[1] "%1 minuta" +msgstr[2] "%1 minuta" +msgstr[3] "1 minut" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Ime" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "U pogonu" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Vidljivost" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Vreme otkrivanja" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:206 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Podrazumevani adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:208 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Adapter: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:204 kcmodule/bluedeviladapters.cpp:229 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 minut" +msgstr[1] "%1 minuta" +msgstr[2] "%1 minuta" +msgstr[3] "1 minut" + +#: kcmodule/bluedeviladapters.cpp:241 +msgid "Bluetooth Adapters" +msgstr "Bluetooth adapteri" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Kontrolni modul za bluetooth adaptere" + +#: kcmodule/bluedeviladapters.cpp:243 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© 2010, Rafel Fernandez Lopez" + +#: kcmodule/bluedeviladapters.cpp:245 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafel Fernandez Lopez" + +#: kcmodule/bluedeviladapters.cpp:245 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Programer i održavalac" + +#: kcmodule/bluedeviladapters.cpp:322 +msgid "No adapters found. Please connect one." +msgstr "Nije nađen nijedan adapter. Povežite neki." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Aktiviraj saradnju Bluetootha sa KDE‑om" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth uređaji" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Kontrolni modul za bluetooth uređaje" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detalji" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Ukloni" + +#: kcmodule/bluedevildevices.cpp:365 +msgid "Disconnect" +msgstr "Prekini vezu" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Add Device..." +msgstr "Dodaj uređaj..." + +#: kcmodule/bluedevildevices.cpp:466 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Izaberite novi alijas za %1" + +#: kcmodule/bluedevildevices.cpp:490 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Želite li zaista da uklonite uređaj „%1“ sa spiska poznatih uređaja?" + +#: kcmodule/bluedevildevices.cpp:491 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Uklanjanje uređaja" + +#: kcmodule/bluedevildevices.cpp:555 +msgid "No remote devices have been added" +msgstr "Nije dodat nijedan udaljeni uređaj" + +#: kcmodule/bluedevildevices.cpp:557 +msgid "Click here to add a remote device" +msgstr "Kliknite ovde da dodate udaljeni uređaj" + +#: kcmodule/bluedevildevices.cpp:596 kcmodule/bluedevildevices.cpp:638 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "nepoznat" + +#: kcmodule/bluedevildevices.cpp:599 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "telefon" + +#: kcmodule/bluedevildevices.cpp:602 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "modem" + +#: kcmodule/bluedevildevices.cpp:605 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "računar" + +#: kcmodule/bluedevildevices.cpp:608 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "mreža" + +#: kcmodule/bluedevildevices.cpp:611 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "slušalice sa mikrofonom" + +#: kcmodule/bluedevildevices.cpp:614 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "slušalice" + +#: kcmodule/bluedevildevices.cpp:617 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "audio" + +#: kcmodule/bluedevildevices.cpp:620 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "tastatura" + +#: kcmodule/bluedevildevices.cpp:623 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "miš" + +#: kcmodule/bluedevildevices.cpp:626 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "fotoaparat" + +#: kcmodule/bluedevildevices.cpp:629 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "štampač" + +#: kcmodule/bluedevildevices.cpp:632 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "džojped" + +#: kcmodule/bluedevildevices.cpp:635 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "tablica" + +#: kcmodule/bluedevildevices.cpp:641 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tip: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Prenos preko Bluetootha" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Kontrolni modul za prenos preko Bluetootha" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "nikad" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "pouzdani uređaji" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "svi uređaji" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "nikad" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Uvek" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "samo čitanje" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "čitanje i pisanje" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Ime" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alijas" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adresa" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Uparen" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blokiran" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Pouzdan" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:101 +#: kio/bluetooth/kiobluetooth.cpp:206 +msgid "No Bluetooth adapters have been found." +msgstr "Nije nađen nijedan bluetooth adapter." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Popravi" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Podrazumevani bluetooth adapter nije vidljiv udaljenim uređajima." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Saradnja sa bluetooth sistemom nije optimalna." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth nije potpuno aktiviran." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Prijem" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Upisuj fajlove u:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Prihvataj automatski:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Primaj fajlove:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Deljenje" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Deli fajlove:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Zahtevaj PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Dozvole:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Deljeni fajlovi" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Deljena fascikla:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:142 +msgid "Send File" +msgstr "Slanje fajla" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Pregledanje fajlova" + +#: kio/bluetooth/kiobluetooth.cpp:78 +msgid "Human Interface Device" +msgstr "Uređaj ljudskog sučelja" + +#: kio/bluetooth/kiobluetooth.cpp:83 +msgid "Headset" +msgstr "Slušalice s mikrofonom" + +#: kio/bluetooth/kiobluetooth.cpp:88 +msgid "Dial Up Network" +msgstr "modemska mreža" + +#: kio/bluetooth/kiobluetooth.cpp:93 +msgid "Personal Area Network" +msgstr "mreža ličnog područja (⁠PAN)" + +#: kio/bluetooth/kiobluetooth.cpp:124 +msgid "Retrieving services..." +msgstr "Dobavljam servise..." + +#: kio/bluetooth/kiobluetooth.cpp:174 +msgid "Scanning for new devices..." +msgstr "Skeniram za novim uređajima..." + +#: kio/obexftp/kio_obexftp.cpp:37 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:93 +msgid "Retrieving information from remote device..." +msgstr "Dobavljam podatke sa udaljenog uređaja..." + +#: kio/obexftp/kio_obexftp.cpp:158 +msgid "Connecting to the device" +msgstr "Povezujem uređaj..." + +#: kio/obexftp/kio_obexftp.cpp:338 +msgid "Can't connect to the device" +msgstr "Ne mogu da povežem uređaj" + +#: kio/obexftp/kio_obexftp.cpp:340 +msgid "Connection closed" +msgstr "Veza prekinuta" + +#: kio/obexftp/kio_obexftp.cpp:438 +msgid "The device is busy, waiting..." +msgstr "Uređaj je zauzet, čekam..." + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/monolithic.cpp:130 +msgid "Bluetooth is Off" +msgstr "Bluetooth je isključen" + +#: monolithic/monolithic.cpp:136 +msgid "Turn Bluetooth On" +msgstr "Uključi Bluetooth" + +#: monolithic/monolithic.cpp:146 +msgid "Browse devices" +msgstr "Pregledaj uređaje" + +#: monolithic/monolithic.cpp:156 +msgid "Known Devices" +msgstr "Poznati uređaji" + +#: monolithic/monolithic.cpp:197 monolithic/monolithic.cpp:537 +msgid "Browse device..." +msgstr "Pregledaj uređaj..." + +#: monolithic/monolithic.cpp:206 monolithic/monolithic.cpp:545 +msgid "Send files..." +msgstr "Pošalji fajlove..." + +#: monolithic/monolithic.cpp:224 monolithic/monolithic.cpp:248 +#: monolithic/monolithic.cpp:278 monolithic/monolithic.cpp:505 +#: monolithic/monolithic.cpp:512 +msgctxt "Action" +msgid "Disconnect" +msgstr "Prekini vezu" + +#: monolithic/monolithic.cpp:230 monolithic/monolithic.cpp:260 +#: monolithic/monolithic.cpp:290 monolithic/monolithic.cpp:497 +#: monolithic/monolithic.cpp:517 monolithic/monolithic.cpp:553 +#: monolithic/monolithic.cpp:566 monolithic/monolithic.cpp:579 +msgctxt "Action" +msgid "Connect" +msgstr "Poveži" + +#: monolithic/monolithic.cpp:254 monolithic/monolithic.cpp:284 +#: monolithic/monolithic.cpp:502 +msgid "Connecting..." +msgstr "Povezujem..." + +#: monolithic/monolithic.cpp:299 monolithic/monolithic.cpp:592 +msgid "No supported services found" +msgstr "Nije nađen nijedan podržani servis." + +#: monolithic/monolithic.cpp:314 +msgid "Add Device" +msgstr "Dodaj uređaj" + +#: monolithic/monolithic.cpp:318 +msgid "Configure Bluetooth" +msgstr "Podesi Bluetooth" + +#: monolithic/monolithic.cpp:327 +msgid "Discoverable" +msgstr "Vidljiv" + +#: monolithic/monolithic.cpp:333 +msgid "Turn Bluetooth Off" +msgstr "Isključi Bluetooth" + +#: monolithic/monolithic.cpp:357 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 povezan uređaj" +msgstr[1] "%1 povezana uređaja" +msgstr[2] "%1 povezanih uređaja" +msgstr[3] "%1 povezan uređaj" + +#: monolithic/monolithic.cpp:646 +msgid "No adapters found" +msgstr "Nije nađen nijedan adapter." + +#: monolithic/monolithic.cpp:673 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth je uključen" + +#: monolithic/monolithic.cpp:675 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth je isključen" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Uključi ili isključi prijem fajlova" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Upisuj primljene fajlove u:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Da li dozvoliti menjanje deljenih fajlova" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Uključi ili isključi deljenje fajlova" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Da li zahtevati PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Dozvoli spoljašnjim uređajima da menjaju deljene fajlove" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Uključi ili isključi globalnu saradnju Bluetootha sa KDE‑om" + +#: wizard/bluewizard.cpp:44 +msgid "Bluetooth Device Wizard" +msgstr "Čarobnjak bluetooth uređaja" + +#: wizard/bluewizard.cpp:71 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Sledeće" + +#: wizard/bluewizard.cpp:72 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Završi" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Čarobnjak Bluetootha" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Uređaj za uparivanje" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Skeniram..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Ručni PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +# >> @title:window +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Izbor uređaja" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Ponovo pokreni čarobnjak" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Zatvori" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Postavljanje uređaja nije uspelo" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Postavljanje uređaja %1 nije uspelo" + +# ? Wtf? +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Uvedite PIN u svoju tastaturu kada se pojavi i pritisnite Enter." + +# ? Wtf? +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:106 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Uvedite PIN u svoj uređaj kada se pojavi." + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/services.ui:19 +msgid "The following compatible services have been found:" +msgstr "Nađeni su sledeći saglasni servisi:" + +#: wizard/pages/servicespage.cpp:32 +msgid "Service Selection" +msgstr "Izbor servisa" + +#: wizard/pages/servicespage.cpp:76 +#, kde-format +msgid "%1 has been paired successfully" +msgstr "%1 je uspešno uparen" + +#: wizard/pages/servicespage.cpp:87 +msgctxt "Do not initialize any service of the device" +msgid "None" +msgstr "nijedan" + +#: wizard/pages/servicespage.cpp:87 +msgid "Do not initialize any service" +msgstr "Ne pripremaj nijedan servis" + +# >> @action:button +#: wizard/pages/ssppairing.cpp:82 +msgid "Matches" +msgstr "Poklapa se" + +# >> @action:button +#: wizard/pages/ssppairing.cpp:84 +msgid "Does not match" +msgstr "Ne poklapa se" + +#: wizard/pages/ssppairing.cpp:98 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "Potvrdite da PIN prikazan na „%1“ odgovara ovom u čarobnjaku." + +#. i18n: ectx: property (text), widget (QLabel, confirmLbl) +#: wizard/pages/ssppairing.ui:117 +#, no-c-format, kde-format +msgid "Connecting to %1 ..." +msgstr "Povezujem %1..." \ No newline at end of file diff --git a/bluedevil/po/sv/CMakeLists.txt b/bluedevil/po/sv/CMakeLists.txt new file mode 100644 index 00000000..186a388c --- /dev/null +++ b/bluedevil/po/sv/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(sv ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/sv/bluedevil.po b/bluedevil/po/sv/bluedevil.po new file mode 100644 index 00000000..4e60760a --- /dev/null +++ b/bluedevil/po/sv/bluedevil.po @@ -0,0 +1,901 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Stefan Asserhäll , 2010, 2013. +# Stefan Asserhall , 2010, 2011, 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-12-21 10:19+0100\n" +"Last-Translator: Stefan Asserhäll \n" +"Language-Team: Swedish \n" +"Language: sv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.4\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Stefan Asserhäll" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "stefan.asserhall@bredband.net" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 begär att få komma åt den här datorn" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Lita på och ge behörighet" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Ge bara behörighet" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Neka" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE:s Blåtandsystem" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Ändra Blåtandläge till '%1'?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Bekräfta" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Neka" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 frågar om PIN-koden är riktig: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "Riktig PIN-kod" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "Felaktig PIN-kod" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN-kod:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN-kod behövs för att skapa par med %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Skriv in PIN-kod" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"För att para ihop den här datorn med %1, måste du ange en PIN-kod. Gör det " +"nedan." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Skriv in PIN-kod" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Blåtanddemon" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© 2010, UFO-kodarna" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Underhåll" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Blåtandenhet" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 skickar filen %2 till dig" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Acceptera" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Avbryt" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Tar emot fil via Blåtand" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Från" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Till" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Sänd via Blåtand" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Sök enhet..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Annan..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Dold" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Alltid synlig" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Tillfälligt synlig" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minut" +msgstr[1] "%1 minuter" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Namn" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Påslagen" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Synlighet" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Upptäcktstid" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Standardanslutning: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Anslutning: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "1 minut" +msgstr[1] "%1 minuter" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Blåtandanslutningar" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Inställningsmodul för Blåtandanslutningar" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Utvecklare och underhåll" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Inga anslutningar hittades. Lägg till en." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Aktivera KDE:s Blåtandintegrering" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Blåtandenheter" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Inställningsmodul för Blåtandenheter" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Information" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Ta bort" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Anslut" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Koppla ner" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Lägg till enhet..." + +#: kcmodule/bluedevildevices.cpp:446 +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Återanslut" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Välj ett nytt alias för %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"Är du säker på att du vill ta bort enhet \"%1\" från listan med kända " +"enheter?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Borttagning av enhet" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Inga fjärrenheter har lagts till" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Klicka här för att lägga till fjärrenheter" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Okänd" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Dator" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Nätverk" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Headset" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Hörlurar" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Ljud" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Tangentbord" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Mus" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Skrivare" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Styrspak" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Ritplatta" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Typ: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Blåtandöverföring" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Inställningsmodul för Blåtandöverföring" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Aldrig" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Pålitliga enheter" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Alla enheter" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Aldrig" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Alltid" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Skrivskyddad" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Ändra och läs" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Namn" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Alias" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adress" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Ihopparad" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Blockerad" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Pålitlig" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Inga Blåtandanslutningar hittades." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Rätta det" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Standardanslutningen av Blåtand är inte synlig för fjärrenheter." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Interaktion med Blåtandsystemet är inte optimal." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Blåtand är inte fullständigt aktiverat." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Tar emot" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Spara filer i:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Acceptera automatiskt:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Ta emot filer:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Dela" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Dela filer:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Kräv PIN-kod:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Rättigheter:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Delade filer" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Delad katalog:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "Blåtand I/O-slav" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Skicka fil" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Bläddra bland filer" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Hämtar tjänster..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Söker efter nya enheter..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "OBEX FTP-demon" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "OBEX FTP I/O-slav" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Hämtar information från fjärrenhet..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Ansluter till enheten" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Blåtand" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Utvecklare" + +#: monolithic/monolithic.cpp:42 +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Bläddra i enhet" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Skicka filer" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Blåtand är av" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Sätt på Blåtand" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Bläddra bland enheter" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Kända enheter" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Lägg till enhet" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Anpassa Blåtand" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Upptäckningsbar" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Stäng av Blåtand" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 ansluten enhet" +msgstr[1] "%1 anslutna enheter" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Inga anslutningar hittades" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Blåtand är på" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Blåtand är av" + +#: monolithic/monolithic.cpp:441 +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Anslut" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Blåtand hjälpprogram för filsändning" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "Enhetens UUID dit filerna kommer att sändas" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Filer som kommer att sändas" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Ansluter till: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Ansluter till %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Markera en eller flera filer:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Markerade filer: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Välj en enhet i listan:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Söker" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Sänder fil via Blåtand" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Blåtand filsändning" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Aktivera eller inaktivera mottagning av filer" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Spara mottagna filer i:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Om ändring av delade filer ska tillåtas" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Aktivera eller inaktivera delade filer" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Om PIN-kod ska krävas" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Tillåt externa enheter att ändra delade filer" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Aktivera eller inaktivera global integrering med Blåtand i KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Blåtand enhetsguide" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Nästa" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Slutför" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Blåtandguide" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Enhet att skapa par med" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Söker..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Manuell PIN-kod:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Välj en enhet" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Starta om guiden" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Stäng" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Inställning av enheten har misslyckats" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Inställning av %1 har misslyckades" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Ange PIN-koden på tangentbordet när den visas och tryck på returtangenten" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Ange PIN-koden på enheten när den visas" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Ansluter till %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Ansluter till:" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Motsvarar" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Motsvarar inte" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "Bekräfta att PIN-koden som visas på \"%1\" motsvarar den i guiden." \ No newline at end of file diff --git a/bluedevil/po/th/CMakeLists.txt b/bluedevil/po/th/CMakeLists.txt new file mode 100644 index 00000000..1fbdef69 --- /dev/null +++ b/bluedevil/po/th/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(th ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/th/bluedevil.po b/bluedevil/po/th/bluedevil.po new file mode 100644 index 00000000..93246a85 --- /dev/null +++ b/bluedevil/po/th/bluedevil.po @@ -0,0 +1,1152 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Thanomsub Noppaburana , 2010. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2010-12-18 19:49+0700\n" +"Last-Translator: Thanomsub Noppaburana \n" +"Language-Team: Thai \n" +"Language: th\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Lokalize 1.1\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "ถนอมทรัพย์ นพบูรณ์" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "donga.nb@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 ร้องขอทำการเข้าใช้งานมายังคอมพิวเตอร์เครื่องนี้" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "เชื่อถือและให้สิทธิ์ในการเข้าใช้งาน" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "ให้สิทธิ์ในการเข้าใช้งานเท่านั้น" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "ปฏิเสธ" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "ระบบบลูทูทของ KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "เปลี่ยนโหมดของบลูทูทเป็นโหมด '%1' หรือไม่ ?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "ยืนยัน" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "ปฏิเสธ" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 ถามถึงรหัส PIN ว่าถูกต้องหรือไม่: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "รหัส PIN ถูกต้อง" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "รหัส PIN ไม่ถูกต้อง" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "รหัส PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "ต้องใช้รหัส PIN ในการจับคู่กับอุปกรณ์ %1" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "แนะนำรหัส PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"เพื่อทำการจับคู่คอมพิวเตอร์เครื่องนี้กับอุปกรณ์ %1 คุณจะต้องทำการป้อนรหัส PIN เสียก่อน " +"โปรดป้อนมันทางด้านล่างนี้" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "แนะนำรหัส PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "ดีมอนบลูทูท" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "สงวนลิขสิทธิ์ (c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "ผู้ดูแล" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "อุปกรณ์บลูทูท" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 กำลังส่งแฟ้ม %2 มาให้คุณ" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "ยอมรับ" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "ยกเลิก" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "กำลังรับแฟ้มผ่านทางบลูทูท" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "จาก" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "ไปยัง" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +#, fuzzy +#| msgid "Sending file over Bluetooth" +msgid "Send via Bluetooth" +msgstr "ส่งแฟ้มผ่านทางบลูทูท" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +#, fuzzy +#| msgid "Add Device..." +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "เพิ่มอุปกรณ์..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "ถูกซ่อน" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "มองเห็นเสมอ" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "มองเห็นได้ชั่วคราว" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 นาที" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "ชื่อ" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "ถูกเปิดอยู่" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "การมองเห็นได้" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "เวลาที่ให้ค้นพบ" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "อุปกรณ์ปริยาย: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "อุปกรณ์: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 นาที" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "อุปกรณ์บลูทูท" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "มอดูลบนพาเนลสำหรับควบคุมอุปกรณ์บลูทูท" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "สงวนลิขสิทธิ์ (c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "ผู้พัฒนาและผู้ดูแล" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "ไม่พบอุปกรณ์ โปรดทำการเชื่อมต่ออุปกรณ์ก่อน" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "เปิดใช้การผสานระบบบลูทูทเข้ากับระบบ KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "อุปกรณ์บลูทูท" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "มอดูลบนพาเนลสำหรับควบคุมอุปกรณ์บลูทูท" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "เอาออก" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "เชื่อมต่อ" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "เลิกการเชื่อมต่อ" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "เพิ่มอุปกรณ์..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "เชื่อมต่อ" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "เลือกชื่อที่ใช้แทน %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, fuzzy, kde-format +#| msgid "" +#| "Are you sure that you want to remove the device \"%1\" from the list of " +#| "known devices?" +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "คุณแน่ใจหรือว่าต้องการจะเอาอุปกรณ์ \"%1\" ออกจากรายการอุปกรณ์ที่รู้จัก ?" + +#: kcmodule/bluedevildevices.cpp:508 +#, fuzzy +#| msgctxt "Title of window that asks for confirmation when removing a device" +#| msgid "Device Removal" +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "การเอาอุปกรณ์ออก" + +#: kcmodule/bluedevildevices.cpp:577 +#, fuzzy +#| msgid "No Bluetooth adapters have been found." +msgid "No remote devices have been added" +msgstr "ไม่พบอุปกรณ์บลูทูทใดๆ" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "ไม่ทราบ" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "โทรศัพท์" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "โมเด็ม" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "คอมพิวเตอร์" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "เครือข่าย" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "ชุดไมค์และหูฟัง" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "ชุดหูฟัง" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "ระบบเสียง" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "แป้นพิมพ์" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "เมาส์" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "กล้อง" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "เครื่องพิมพ์" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "จอยแพด" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "แท็บเล็ต" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "ประเภท: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "การถ่ายโอนข้อมูลผ่านทางบลูทูท" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "มอดูลบนพาเนลสำหรับควบคุมการถ่ายโอนข้อมูลผ่านทางบลูทูท" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:75 +#, fuzzy +#| msgid "Browse devices" +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "เรียกดูอุปกรณ์บลูทูทต่างๆ" + +#: kcmodule/bluedeviltransfer.cpp:76 +#, fuzzy +#| msgid "Add Device" +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "เพิ่มอุปกรณ์" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "" + +#: kcmodule/devicedetails.cpp:44 +#, fuzzy +#| msgctxt "Name of the adapter" +#| msgid "Name" +msgctxt "Name of the device" +msgid "Name" +msgstr "ชื่อ" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "" + +#: kcmodule/devicedetails.cpp:52 +#, fuzzy +#| msgctxt "Whether the adapter is powered or not" +#| msgid "Powered" +msgctxt "Device is paired" +msgid "Paired" +msgstr "ถูกเปิดอยู่" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "" + +#: kcmodule/devicedetails.cpp:56 +#, fuzzy +#| msgctxt "Trust a device" +#| msgid "Trust" +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "เชื่อถือ" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "ไม่พบอุปกรณ์บลูทูทใดๆ" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "แก้ไข" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "อุปกรณ์บลูทูทของคุณไม่สามารถมองเห็นได้จากอุปกรณ์บลูทูทภายนอก" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "การโต้ตอบกับระบบบลูทูทไม่เหมาะสม" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "อุปกรณ์บลูทูทยังไม่ถูกเปิดใช้งานอย่างสมบูรณ์" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +#, fuzzy +#| msgid "Enable receiving files" +msgid "Save files in:" +msgstr "เปิดใช้งานการรับแฟ้มต่างๆ" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +#, fuzzy +#| msgid "Enable receiving files" +msgid "Receive files:" +msgstr "เปิดใช้งานการรับแฟ้มต่างๆ" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +#, fuzzy +#| msgid "Send File" +msgid "Share Files:" +msgstr "ส่งแฟ้ม" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +#, fuzzy +#| msgid "Send File" +msgid "Shared Files" +msgstr "ส่งแฟ้ม" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Send File" +msgid "Shared Folder:" +msgstr "ส่งแฟ้ม" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "ส่งแฟ้ม" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "เรียกดูแฟ้มต่างๆ" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "กำลังรับค่าบริการต่างๆ ..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +#, fuzzy +#| msgid "Scanning for remote devices..." +msgid "Scanning for new devices..." +msgstr "กำลังค้นหาอุปกรณ์บลูทูทภายนอก..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ดีมอน ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "กำลังรับข้อมูลรายละเอียดจากอุปกรณ์บลูทูทภายนอก..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "กำลังเชื่อมต่อไปยังอุปกรณ์" + +#: monolithic/main.cpp:28 +#, fuzzy +#| msgid "kiobluetooth" +msgid "Bluetooth" +msgstr "kiobluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "ผู้พัฒนา" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "เรียกดูอุปกรณ์บลูทูทต่างๆ" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +#, fuzzy +#| msgid "Send File" +msgid "Send Files" +msgstr "ส่งแฟ้ม" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "อุปกรณ์บลูทูทถูกปิด" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "เปิดอุปกรณ์บลูทูท" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "เรียกดูอุปกรณ์บลูทูทต่างๆ" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "อุปกรณ์บลูทูทที่รู้จัก" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "เพิ่มอุปกรณ์" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "ปรับแต่งอุปกรณ์บลูทูท" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "ไม่สามารถมองเห็นได้" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "ปิดอุปกรณ์บลูทูท" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "เชื่อมต่อกับอุปกรณ์อยู่ %1 อุปกรณ์" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "ไม่พบอุปกรณ์บลูทูทใดๆ" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "อุปกรณ์บลูทูทถูกเปิด" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "อุปกรณ์บลูทูทถูกปิด" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "เชื่อมต่อ" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "ตัวช่วยส่งแฟ้มผ่านทางบลูทูท" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "กำลังเชื่อมต่อไปยัง: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "กำลังเชื่อมต่อไปยัง %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +#, fuzzy +#| msgid "Untitled device" +msgid "Select a device from the list:" +msgstr "อุปกรณ์ที่ยังไม่กำหนดชื่อ" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +#, fuzzy +#| msgid "Connecting..." +msgid "Scanning" +msgstr "กำลังเชื่อมต่อ..." + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "ส่งแฟ้มผ่านทางบลูทูท" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "ส่งแฟ้มผ่านทางบลูทูท" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "เปิดใช้งานหรือปิดการใช้งานการรับแฟ้มต่างๆ" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +#, fuzzy +#| msgid "Enable receiving files" +msgid "Save received files to:" +msgstr "เปิดใช้งานการรับแฟ้มต่างๆ" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +#, fuzzy +#| msgid "Enable or disable receiving files" +msgid "Enable or disable file sharing" +msgstr "เปิดใช้งานหรือปิดการใช้งานการรับแฟ้มต่างๆ" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "เปิดใช้งานหรือปิดการใช้งานการผสานระบบบลูทูทเข้ากับระบบ KDE ทั้งระบบ" + +#: wizard/bluewizard.cpp:41 +#, fuzzy +#| msgid "Bluetooth Remote Device Wizard" +msgid "Bluetooth Device Wizard" +msgstr "ตัวช่วยจัดการอุปกรณ์บลูทูทภายนอก" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "ถัดไป" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "ตัวช่วยจัดการบลูทูท" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "อุปกรณ์ที่จะจับคู่ด้วย" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +#, fuzzy +#| msgid "Connecting..." +msgid "Scanning..." +msgstr "กำลังเชื่อมต่อ..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "" + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "" + +#: wizard/pages/discoverpage.cpp:39 +#, fuzzy +#| msgid "Untitled device" +msgid "Select a device" +msgstr "อุปกรณ์ที่ยังไม่กำหนดชื่อ" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "กำลังเชื่อมต่อไปยัง %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "กำลังเชื่อมต่อไปยัง: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + + +#, fuzzy + + + + + + + + +#, fuzzy + + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/tr/CMakeLists.txt b/bluedevil/po/tr/CMakeLists.txt new file mode 100644 index 00000000..c1344c9d --- /dev/null +++ b/bluedevil/po/tr/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(tr ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/tr/bluedevil.po b/bluedevil/po/tr/bluedevil.po new file mode 100644 index 00000000..bee646d2 --- /dev/null +++ b/bluedevil/po/tr/bluedevil.po @@ -0,0 +1,1060 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Serdar Soytetir , 2010. +# Serdar SOYTETİR , 2010, 2012. +# Ozan Çağlayan , 2011. +# H. İbrahim Güngör , 2011. +# Volkan Gezer , 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-11-29 03:24+0100\n" +"Last-Translator: Volkan Gezer\n" +"Language-Team: Turkish \n" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.5\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Serdar Soytetir, Ozan Çağlayan, Volkan Gezer" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "tulliana@gmail.com, ozan@pardus.org.tr, volkangezer@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 bu bilgisayara erişmek istiyor" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Güven ve Yetkilendir" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Sadece Yetkilendir" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Reddet" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE Bluetooth Sistemi" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Bluetooth kipi %1 olarak değiştirilsin mi?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Doğrula" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Reddet" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 aygıtı '%2' PIN kodunun doğru olup olmadığını soruyor" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN doğru" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN yanlış" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "%1 ile eşleşmek için PIN gerekli" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "PIN kodunu gir" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "Bu bilgisayarı %1 ile eşleştirmek için bir PIN kodu girmeniz gerekli." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "PIN kodunu gir" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Bluetooth Hizmeti" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Projeyi Yürüten" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Bluetooth Aygıtları" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 size %2 dosyasını gönderiyor" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Kabul Et" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "İptal" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Bluetooth üzerinden dosya alınıyor" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "Kimden" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "Kime" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Bluetooth üzerinden gönder" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Aygıt Bul..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Diğer..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Gizli" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Her zaman görünür" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Geçici olarak görünür" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 dakika" +msgstr[1] "%1 dakika" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "İsim" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Açık" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Görünürlük" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Keşif Süresi" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Öntanımlı bağdaştırıcı: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Bağdaştırıcı: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 dakika" +msgstr[1] "%1 dakika" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Bluetooth Bağdaştırıcıları" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Bluetooth Bağdaştırıcıları Yapılandırma Paneli" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Geliştirici ve projeyi yürüten" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "Bağdaştırıcı bulunamadı. Lütfen bir tane takın." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "KDE Bluetooth Bütünleştirmesini Etkinleştir" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Bluetooth Aygıtları" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Bluetooth Aygıtları Yapılandırma Paneli" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Detaylar" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Kaldır" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "Bağlan" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Bağlantıyı Kes" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Aygıt Ekle..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Bağlan" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "%1 için yeni bir takma ad seçin" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "" +"\"%1\" aygıtını bilinen aygıtlar listesinden kaldırmak istediğinize emin " +"misiniz?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Aygıt kaldırma" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Hiç uzak aygıt eklenmedi" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Uzak aygıt eklemek için tıklayın" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Bilinmeyen" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Telefon" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Modem" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Bilgisayar" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Ağ" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Mikrofonlu Kulaklık" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Kulaklık" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Ses" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Klavye" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Fare" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Kamera" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Yazıcı" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Oyun Çubuğu" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Tablet" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Tür: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Bluetooth Aktarımı" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Bluetooth Aktarımı Yapılandırma Paneli" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Asla" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Güvenilen aygıtlar" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Tüm aygıtlar" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Asla" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Her zaman" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Salt Okunur" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Değiştir ve Oku" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "İsim" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Takma Ad" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Adres" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Eşleşildi" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Engellendi" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Güvenilir" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Bluetooth bağdaştırıcı bulunamadı." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Düzelt" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "" +"Öntanımlı Bluetooth bağdaştırıcınız uzak aygıtlar tarafından görülemez." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Bluetooth desteği düzgün çalışmıyor" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth tamamen etkinleştirilmemiş." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Alma" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Dosyaları şuraya kaydet:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Otomatik kabul et:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Dosyaları al:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Paylaşma" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Dosya Paylaştır:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "PIN iste:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "İzinler:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Paylaşılan Dosyalar" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Paylaşılan Klasör:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Dosya Gönder" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Gözat" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Hizmet listesi alınıyor..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Yeni aygıtlar aranıyor..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp Hizmeti" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Uzak aygıttan bilgi alınıyor..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Aygıta bağlanılıyor" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Geliştirici" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Aygıtlara Gözat" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Dosya gönder" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Bluetooth Kapalı" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Bluetooth'u Aç" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Aygıtlara Gözat" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Bilinen Aygıtlar" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Aygıt Ekle" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Bluetooth'u Yapılandır" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Keşfedilebilir" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Bluetooth'u Kapat" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 bağlı aygıt" +msgstr[1] "%1 bağlı aygıt" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Bağdaştırıcı bulunamadı" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth Açık" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth Kapalı" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "Bağlan" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Bluetooth Dosya Gönderme Yardımcısı" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "Dosyaların gönderileceği aygıt UUID kimliği" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Gönderilecek dosyalar" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "Bağlanılıyor: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "%1 aygıtına bağlanılıyor..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Bir veya daha fazla dosya seçin:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Seçilen dosyalar: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Listeden bir aygıt seçin:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Taranıyor" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Bluetooth üzerinden dosya gönderiliyor" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Bluetooth ile Dosya Gönder" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Dosya alımını etkinleştir veya kapat" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Alınan dosyaları şuraya kaydet:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Paylaşılan dosyaların değiştirilmesine izin verilip verilmeyeceği" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Dosya paylaştırmayı etkinleştir veya kapat" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "PIN istenip istenmeyeceği" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "Harici aygıtların paylaşılan dosyaları değiştirmesine izin ver" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "KDE Bluetooth desteğini etkinleştir veya kapat" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Bluetooth Aygıt Sihirbazı" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "İleri" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Bitir" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Bluetooth Sihirbazı" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Eşleşilecek aygıt" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Taranıyor..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Elle PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Bir aygıt seçin" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Sihirbazı tekrar başlat" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Kapat" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Aygıtın yapılandırılamadı" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "%1 yapılandırılırken hata oluştu" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "Lütfen sorulduğunda klavyenizle PIN kodunu girin ve Enter tuşuna basın" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Lütfen aygıta PIN kodunu girin" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "%1 aygıtına bağlanılıyor..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "Bağlanılıyor: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Eşleşenler" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Eşleşmedi" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"\"%1\" üzerinde gösterilen PIN kodunun sihirbazdakiyle uyuştuğunu doğrulayın." + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy + + + + + + + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/ug/CMakeLists.txt b/bluedevil/po/ug/CMakeLists.txt new file mode 100644 index 00000000..0d33e417 --- /dev/null +++ b/bluedevil/po/ug/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(ug ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/ug/bluedevil.po b/bluedevil/po/ug/bluedevil.po new file mode 100644 index 00000000..558cbc92 --- /dev/null +++ b/bluedevil/po/ug/bluedevil.po @@ -0,0 +1,905 @@ +# Uyghur translation for bluedevil. +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# Sahran , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-09-08 07:04+0900\n" +"Last-Translator: Gheyret Kenji \n" +"Language-Team: Uyghur Computer Science Association \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "ئابدۇقادىر ئابلىز, غەيرەت كەنجى" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "sahran.ug@gmail.com, gheyret@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 بۇ كومپيۇتېرنى زىيارەت قىلىشنى ئىلتىماس قىلىۋاتىدۇ" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "پەقەتلا كىملىك دەلىللە" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "رەت قىل" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "ك د ئې(KDE) كۆكچىش سىستېمىسى" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "كۆكچىش ھالىتىنى «%1» غا ئۆزگەرتەمسىز؟" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "جەزملە" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "رەت قىل" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 PIN توغرىمۇ سوراۋاتىدۇ: %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN توغرا" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN ناتوغرا" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN %1 نىڭ بىلەن جۈپلىشىشى كېرەك" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "PIN تونۇشتۇرۇش" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"بۇ كومپيۇتېر بىلەن %1 نى جۈپلەش ئۈچۈن PIN كودىنى كىرگۈزۈشىڭىز كېرەك. بۇنى " +"تۆۋەندە ئېلىپ بېرىڭ." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "PIN تونۇشتۇرۇش" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "كۆكچىش مۇئەككىلى" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "مەسئۇل كىشى" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "كۆكچىش ئۈسكۈنىلىرى" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 سىزگە %2 دېگەن ھۆججەتنى يوللاۋاتىدۇ" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "قوشۇل" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "ئەمەلدىن قالدۇر" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "ھۆججەتنى كۆكچىش ئارقىلىق قوبۇل قىلىش" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "يوللىغۇچى" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "تاپشۇرۇپ ئالغۇچى" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "كۆكچىش بىلەن يوللاش" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "ئۈسكۈنە تېپىش…" + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "باشقا…" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "يوشۇرۇن" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "ھەمىشە كۆرۈنسۇن" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "ۋاقتىنچە كۆرۈنۈشچان" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 مىنۇت" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "ئاتى" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "كۆرۈشچانلىقى" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "بايقاش ۋاقتى" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "كۆڭۈلدىكى ماسلاشتۇرغۇچ: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "ماسلاشتۇرغۇچ: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 مىنۇت" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "كۆكچىش ماسلاشتۇرغۇچى" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "كۆكچىش ماسلاشتۇرغۇچى تىزگىن تاختا بۆلىكى" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "ئىجادىيەتچى ۋە مەسئۇل كىشى" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "ماسلاشتۇرغۇچ تېپىلمىدى. بىرنى چېتىڭ." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "ك د ئې(KDE) كۆكچىش يۈرۈشلەشتۈرۈشىنى ئىناۋەتلىك قىلىش" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "كۆكچىش ئۈسكۈنىلىرى" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "كۆكچىش ئۈسكۈنىسى تىزگىن تاختا بۆلىكى" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "تەپسىلاتلار" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "چىقىرىۋەت" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "باغلان" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "ئۈز" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "ئۈسكۈنە قوش" + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "باغلان" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "%1 نىڭ ئۈچۈن يېڭى لەقەم تاللاڭ" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "ئۈسكۈنە«%1» نى بىلىدىغان ئۈسكۈنە تىزىمىدىن راستلا چىقىرىۋېتەمسىز؟" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "ئۈسكۈنە چىقىرىۋېتىش" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "يىراقتىكى ئۈسكۈنە قوشۇلمىدى" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "يىراقتىكى ئۈسكىنىنى قوشۇش ئۈچۈن بۇ يەرنى چېكىڭ." + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "نامەلۇم" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "تېلېفون" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "مودېم" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "كومپيۇتېر" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "تور" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "تىڭشىغۇچ" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "تىڭشىغۇچ" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "ئۈن" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "ھەرپتاختا" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "چاشقىنەك" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "كامېرا" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "پرىنتېر" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "قول" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "سەزگۈر تاختا" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "تىپ: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "كۆكچىش ئۇزىتىشى" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "كۆكچىش يوللاش تىزگىن تاختا بۆلىكى" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "ھەرگىز" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "ئىشەنچلىك ئۈسكۈنىلەر" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "بارلىق ئۈسكۈنىلەر" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "ھەرگىز" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "ھەمىشە" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "ئوقۇشقىلا بولىدىغان" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "تۈزىتىش ۋە ئوقۇش" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "ئاتى" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "تەخەللۇس" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "ئادرېس" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "جۈپلەنگەن" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "توسۇلغان" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "ئىشەنچلىك" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "كۆكچىش ماسلاشتۇرغۇچىسى تېپىلمىدى." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "تۈزەت" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "كۆكچىش ماسلاشتۇرغۇچىسى يىراقتىكى ئۈسكىنىگە كۆرۈنمەيدۇ." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "كۆكچىش تولۇق ئىناۋەتلىك قىلىنمىدى." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "قوبۇل قىلىۋاتىدۇ" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "ھۆججەتلەرنى ساقلايدىغان ئورۇن:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "ئاپتوماتىك قوبۇل قىل:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "ھۆججەت قوبۇللاش" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "ھەمبەرلەش" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "ھۆججەت ھەمبەھىرلەش:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "PIN ئىلتىماس قىل:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "ھوقۇقى:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "ھەمبەھىرلەنگەن ھۆججەتلەر" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "ھەمبەھىرلەنگەن قىسقۇچ" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "ھۆججەت يوللاش" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "ھۆججەتلەرنى كۆرۈش" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "مۇلازىمەتلەرنى ئىزدەۋاتىدۇ…" + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "" + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp مۇئەككىلى" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "يىراقتىكى ئۈسكىنىدىن ئۆچۈر ئىزدەۋاتىدۇ…" + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "ئۈسكىنىگە باغلىنىۋاتىدۇ" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "كۆكچىش" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "ئىجادىيەتچى" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "ئۈسكۈنىلەرنى كۆرۈش" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "ھۆججەت يوللاش" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "كۆكچىش يېپىغلىق" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "كۆكچىشنى ئېچىش" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "ئۈسكۈنىلەرنى كۆرۈش" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "بىلىدىغان ئۈسكۈنىلەر" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "ئۈسكۈنە قوش" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "كۆكچىشنى سەپلەش" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "بايقىغىلى بولىدۇ" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "كۆكچىشنى تاقاش" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 باغلانغان ئۈسكۈنە" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "ماسلاشتۇرغۇچ تېپىلمىدى" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "كۆكچىش ئوچۇق" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "كۆكچىش يېپىغلىق" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "باغلان" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "كۆكچىش ھۆججەت يوللاش ياردەمچىسى" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "ھۆججەت يوللىنىدىغان يەرنىڭ UUID (كىملىكى)" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "يوللىنىدىغان ھۆججەتلەر" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "باغلىنىۋاتىدۇ: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "%1 غا بالىنىۋاتىدۇ…" + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "بىر ياكى بىر قانچە ھۆججەت تاللاڭ:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "تاللانغان ھۆججەتلەر: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "تىزىمدىن بىر ئۈسكۈنە تاللاڭ:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "تەكشۈرۈۋاتىدۇ" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "ھۆججەتنى كۆكچىش ئارقىلىق يوللاش" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "كۆكچىش ھۆججەت يوللاش" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "ھۆججەت قوبۇل قىلىشنى ئىناۋەتلىك ياكى ئىناۋەتسىز قىل" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "قوبۇل قىلغان ھۆججەتلەرنى ساقلايدىغان ئورۇن:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "ھەمبەھىرلەنگەن ھۆججەتلەرنى ئۆزگەرتىشكە ئىجازەت بەرسۇنمۇ" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "ھۆججەت ھەمبەھىر قىلىشنى ئىناۋەتلىك ياكى ئىناۋەتسىز قىل" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "PIN نى ئىلتىماس قىلسۇنمۇ" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" +"سىرتقى ئۈسكىنىلەرنىڭ ھەمبەھىرلەنگەن ھۆججەتلەرنى ئۆزگەرتىشكە ئىجازەت بەر" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "" +"ئومۇمىي ك د ئې(KDE) كۆكچىش يۈرۈشلەشتۈرۈشىنى ئىناۋەتلىك قىلىش ياكى ئىناۋەتسىز " +"قىلىش" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "كۆكچىش ئۈسكۈنىسى يېتەكچىسى" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "كېيىنكى" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "تامام" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "كۆكچىش يېتەكچىسى" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "جۈپلەشتۈرىدىغان ئۈسكۈنە" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "تەكشۈرۈۋاتىدۇ…" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "قول PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "ئۈسكۈنە تاللاڭ" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "يېتەكچىنى باشلاڭ" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "ياپ" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "ئۈسكىنەنى ئورنىتىش مەغلۇپ بولدى" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "%1 نى ئورنىتىش مەغلۇپ بولدى" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "%1 غا بالىنىۋاتىدۇ…" + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "باغلىنىۋاتىدۇ: %1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "ماسلاشتى" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "ماسلاشمىدى" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" \ No newline at end of file diff --git a/bluedevil/po/uk/CMakeLists.txt b/bluedevil/po/uk/CMakeLists.txt new file mode 100644 index 00000000..f0bb8958 --- /dev/null +++ b/bluedevil/po/uk/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(uk ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/uk/bluedevil.po b/bluedevil/po/uk/bluedevil.po new file mode 100644 index 00000000..4cab6c8d --- /dev/null +++ b/bluedevil/po/uk/bluedevil.po @@ -0,0 +1,912 @@ +# Ukrainian translation for bluedevil +# This file is distributed under the same license as the bluedevil package. +# +# Yuri Chornoivan , 2010, 2011, 2013. +msgid "" +msgstr "" +"Project-Id-Version: bluedevil\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-12-21 10:41+0200\n" +"Last-Translator: Yuri Chornoivan \n" +"Language-Team: Ukrainian \n" +"Language: uk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n" +"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Launchpad-Export-Date: 2010-09-09 05:40+0000\n" +"X-Generator: Lokalize 1.5\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Юрій Чорноіван" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "yurchor@gmail.com" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "Пристрій %1 надіслав запит щодо доступу до цього комп’ютера" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "Уповноважити і довіритися" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "Лише уповноважити" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "Відмовити" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "Система Bluetooth KDE" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "Змінити режим роботи Bluetooth на «%1»?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "Підтвердити" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "Відмовити" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "Запит від %1 щодо правильності PIN-коду %2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "Правильний PIN-код" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "Неправильний PIN-код" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "Для встановлення зв’язку з %1 потрібен PIN-код" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "Ввести PIN-код" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "" +"Щоб встановити зв’язок цього комп’ютера з %1, вам слід ввести PIN-код. Будь " +"ласка, введіть його у поле, розташоване нижче." + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "Ввести PIN-код" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "Фонова служба Bluetooth" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "© UFO Coders, 2010" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "Супровідник" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "Пристрій Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 надсилає вам файл %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "Прийняти" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "Скасувати" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "Отримання файлів за допомогою Bluetooth" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "З" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "До" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "Надсилання файлів Bluetooth" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "Знайти пристрій…" + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "Інше…" + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "Прихований" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "Завжди видимий" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "Тимчасово видимий" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 хвилина" +msgstr[1] "%1 хвилини" +msgstr[2] "%1 хвилин" +msgstr[3] "%1 хвилина" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "Назва" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "Живлення" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "Видимість" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "Тривалість виявлення" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "Типовий адаптер: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "Адаптер: %1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 хвилина" +msgstr[1] "%1 хвилини" +msgstr[2] "%1 хвилин" +msgstr[3] "%1 хвилина" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "Адаптери Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "Модуль панелі керування адаптерами Bluetooth" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "© Rafael Fernández López, 2010" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "Розробник та супроводжувач" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "" +"Не виявлено жодного адаптера. Будь ласка, з’єднайте з комп’ютером хоч один " +"адаптер." + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "Увімкнути інтеграцію Bluetooth з KDE" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "Пристрої Bluetooth" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "Модуль панелі керування пристроями Bluetooth" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "Параметри" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "Вилучити" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "З’єднати" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "Роз'єднати" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "Додати пристрій…" + +#: kcmodule/bluedevildevices.cpp:446 +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "Повторно з’єднати" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "Вкажіть нову альтернативну назву для %1" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "Ви справді бажаєте вилучити пристрій «%1» зі списку відомих пристроїв?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "Вилучення пристрою" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "Жодних віддалених пристроїв додано не було" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "Натисніть тут, щоб додати віддалений пристрій" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "Невідомий" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "Телефон" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "Модем" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "Комп'ютер" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "Мережевий пристрій" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "Гарнітура" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "Навушники" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "Звуковий" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "Клавіатура" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "Миша" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "Фотоапарат" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "Принтер" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "Ігровий пульт" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "Планшет" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "Тип: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "Передавання Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "Модуль панелі керування передаванням даних Bluetooth" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "Ніколи" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "Надійні пристрої" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "Всі пристрої" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "Ніколи" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "Завжди" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "Лише читання" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "Зміна і читання" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "Назва" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "Альт. назва" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "Адреса" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "Пов’язано" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "Заблоковано" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "Надійний" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "Не виявлено жодного адаптера Bluetooth." + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "Виправити" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "Ваш типовий адаптер Bluetooth є невидимим для віддалених пристроїв." + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "Взаємодія з системою Bluetooth не є оптимальною." + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "Bluetooth увімкнено не повністю." + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "Отримання" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "Зберігати файли до:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "Автоматично приймати:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "Отримання файлів:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "Спільне користування" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "Спільне користування файлами:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "Вимагати код:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "Права доступу:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "Спільні файли" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "Спільна тека:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "Надіслати файл" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "Переглянути файли" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "Отримання списку служб…" + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "Пошук нових пристроїв…" + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "Фонова служба ObexFtp" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "Отримання даних з віддаленого пристрою…" + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "Встановлення з’єднання з пристроєм" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "Розробник" + +#: monolithic/monolithic.cpp:42 +#| msgid "Browse devices" +msgid "Browse device" +msgstr "Перегляд пристрою" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "Надсилання файлів" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "Вимкнути Bluetooth" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "Увімкнути Bluetooth" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "Перегляд пристроїв" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "Відомі пристрої" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "Додавання пристрою" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "Налаштувати Bluetooth" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "Можливість виявлення" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "Вимкнути Bluetooth" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 з’єднаний пристрій" +msgstr[1] "%1 з’єднаних пристрої" +msgstr[2] "%1 з’єднаних пристроїв" +msgstr[3] "один з’єднаний пристрій" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "Адаптерів не виявлено" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "Bluetooth увімкнено" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "Bluetooth вимкнено" + +#: monolithic/monolithic.cpp:441 +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "З’єднатися" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "Допоміжна програма надсилання файлів Bluetooth" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "UUID пристрою, куди буде надіслано файли" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "Файли, які буде надіслано" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "З'єднання з: %1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "Встановлення з'єднання з %1…" + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "Виберіть один або декілька файлів:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "Вибрані файли: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "Виберіть пристрій зі списку:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "Сканування" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "Надсилання файлів Bluetooth" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "Надсилання файлів Bluetooth" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "Увімкнути або вимкнути отримання файлів" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "Зберегти отримані файли до:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "Внесення змін у файли спільного використання" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "Увімкнення або вимикання спільного доступу до файлів" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "Обов’язкове використання пін-коду" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "" +"Дозволити зовнішнім пристроям вносити зміни до файлів спільного використання" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "Увімкнути або вимкнути загальну інтеграцію Bluetooth з KDE" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "Майстер пристроїв Bluetooth" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "Далі" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "Завершити" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "Майстер Bluetooth" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "Пристрій, який слід прив’язати" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "Сканування…" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "Власний код:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "Виберіть пристрій" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "Перезапустити майстер" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "Закрити" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "Спроба налаштування пристрою завершилася невдало" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "Спроба налаштування %1 завершилася невдало" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "" +"Будь ласка, введіть PIN-код, який буде показано, з вашої клавіатури і " +"натисніть клавішу Enter." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "Будь ласка, введіть PIN-код, який буде показано, на вашому пристрої" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "Встановлення з’єднання з %1…" + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "З’єднуємося з:" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "Є відповідником" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "Не є відповідником" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "" +"Будь ласка, підтвердіть, що PIN-код, показаний на «%1», відповідає " +"показаному у майстрі." \ No newline at end of file diff --git a/bluedevil/po/zh_CN/CMakeLists.txt b/bluedevil/po/zh_CN/CMakeLists.txt new file mode 100644 index 00000000..d1c85c8e --- /dev/null +++ b/bluedevil/po/zh_CN/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(zh_CN ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/zh_CN/bluedevil.po b/bluedevil/po/zh_CN/bluedevil.po new file mode 100644 index 00000000..801c37ee --- /dev/null +++ b/bluedevil/po/zh_CN/bluedevil.po @@ -0,0 +1,999 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Feng Chao , 2011. +# Ni Hui , 2011, 2012. +# Wang Jiajun , 2012. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2012-12-28 01:29+0800\n" +"Last-Translator: Wang Jiajun \n" +"Language-Team: Chinese Simplified \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Lokalize 1.5\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "KDE 中国" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "kde-china@kde.org" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 请求访问此计算机" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "信任并授权" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "仅授权" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "拒绝" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE 蓝牙系统" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "将蓝牙模式更改为“%1”吗?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "确认" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "拒绝" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 询问 PIN 是否正确:%2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN 正确" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN 错误" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "为了与 %1 配对,需要一个 PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "输入 PIN" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "为了将此计算机与 %1 配对,您必须输入一个 PIN。请在下方填写。" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "输入 PIN" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "蓝牙守护进程" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "维护者" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "蓝牙设备" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 向您发送文件 %2" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "接受" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "取消" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "通过蓝牙接收文件" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "从" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "到" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "通过蓝牙发送" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "查找设备..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "其它..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "隐藏" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "总是可见" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "临时可见" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 分钟" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "名称" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "已开启" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "可见性" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "可见时间" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "默认适配器:%1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "适配器:%1 (%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 分钟" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "蓝牙适配器" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "蓝牙适配器控制面板模块" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "开发者和维护者" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "未找到适配器。请连接适配器。" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "启用 KDE 蓝牙集成" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "蓝牙设备" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "蓝牙设备控制面板模块" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "细节" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "移除" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "连接" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "断开" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "添加设备..." + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "连接" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "为 %1 取一个新别名" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "您确定要将“%1”从已知设备列表中移除吗?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "设备移除" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "尚未添加任何远程设备" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "点击此处添加远程设备" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "未知" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "电话" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "调制解调器" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "计算机" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "网络" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "耳机" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "耳麦" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "音频" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "键盘" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "鼠标" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "相机" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "打印机" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "手柄" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "平板" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "类型: %1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "蓝牙传输器" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "蓝牙传输器控制面板模块" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "从不" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "信任的设备" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "所有设备" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "从不" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "总是" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "只读" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "修改和读取" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "名称" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "别名" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "地址" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "已配对" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "被屏蔽" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "已信任" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "未找到蓝牙适配器。" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "修复" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "您的默认蓝牙适配器对于远程设备不可见。" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "与蓝牙系统的交互不理想" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "蓝牙未完全启用。" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "接收" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "将文件保存在:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "自动接受:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "接收文件: " + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "共享" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "共享文件:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "需要 PIN:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "权限:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "共享的文件" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +#, fuzzy +#| msgid "Shared Folder" +msgid "Shared Folder:" +msgstr "共享文件夹" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "发送文件" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "浏览文件" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "正在获取服务..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "正在扫描新设备..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp 守护进程" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "正在从远程设备获取信息..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "正在连接到设备" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "蓝牙" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "开发者" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "浏览设备" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "发送文件" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "蓝牙关闭" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "开启蓝牙" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "浏览设备" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "已知设备" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "添加设备" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "配置蓝牙" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "可发现" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "关闭蓝牙" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 个已连接的设备" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "未找到适配器" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "蓝牙开启" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "蓝牙关闭" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "连接" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "蓝牙发送文件辅助程序" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "文件要发送到的设备 UUID" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "要发送的文件" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "正在连接到:%1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "正在连接 %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "选择一个或多个文件:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "选中的文件: %1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "从列表中选择设备:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "正在扫描" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "通过蓝牙发送文件" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "蓝牙发送文件" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "启用或禁用接收文件" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "将接收的文件保存在:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "是否允许修改共享文件" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "启用或禁用文件共享" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "是否需要 PIN" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "允许外部设备修改共享的文件" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "启用或禁用全局 KDE 蓝牙集成" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "蓝牙设备向导" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "下一个" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "完成" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "蓝牙向导" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "要配对的设备:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "正在扫描..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "手工输入 PIN:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "选择设备" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "重新开始向导" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "关闭" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "设备设置失败" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "%1 的设置失败" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "请输入您键盘上的 PIN 并按回车" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "请输入您设备上的 PIN" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "正在连接 %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "正在连接到:%1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "匹配" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "不匹配" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "请确认“%1”上显示的 PIN 与向导中的匹配。" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/po/zh_TW/CMakeLists.txt b/bluedevil/po/zh_TW/CMakeLists.txt new file mode 100644 index 00000000..e26a305c --- /dev/null +++ b/bluedevil/po/zh_TW/CMakeLists.txt @@ -0,0 +1,2 @@ +file(GLOB _po_files *.po) +GETTEXT_PROCESS_PO_FILES(zh_TW ALL INSTALL_DESTINATION ${LOCALE_INSTALL_DIR} ${_po_files} ) diff --git a/bluedevil/po/zh_TW/bluedevil.po b/bluedevil/po/zh_TW/bluedevil.po new file mode 100644 index 00000000..354d561c --- /dev/null +++ b/bluedevil/po/zh_TW/bluedevil.po @@ -0,0 +1,1089 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Frank Weng (a.k.a. Franklin) , 2010. +# Franklin Weng , 2010. +# Franklin Weng , 2010, 2011, 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2013-12-21 01:22+0000\n" +"PO-Revision-Date: 2013-11-15 12:42+0800\n" +"Last-Translator: Franklin Weng \n" +"Language-Team: Chinese Traditional \n" +"Language: zh_TW\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.5\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Frank Weng (a.k.a. Franklin)" + +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "franklin@goodhorse.idv.tw" + +#: daemon/helpers/authorize/authorize.cpp:43 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny access to this computer from " +"Bluetooth. The %1 is the name of the bluetooth device" +msgid "%1 is requesting access to this computer" +msgstr "%1 正要求存取此電腦" + +#: daemon/helpers/authorize/authorize.cpp:47 +msgctxt "Button to trust a bluetooth remote device and authorize it to connect" +msgid "Trust and Authorize" +msgstr "信任與授權" + +#: daemon/helpers/authorize/authorize.cpp:48 +msgctxt "Button to authorize a bluetooth remote device to connect " +msgid "Authorize Only" +msgstr "只授權" + +#: daemon/helpers/authorize/authorize.cpp:49 +msgctxt "Deny access to a remote bluetooth device" +msgid "Deny" +msgstr "拒絕" + +#: daemon/helpers/authorize/main.cpp:32 +#: daemon/helpers/confirmmodechange/main.cpp:31 +#: daemon/helpers/requestconfirmation/main.cpp:30 +#: daemon/helpers/requestpin/main.cpp:30 +msgid "KDE Bluetooth System" +msgstr "KDE 藍牙系統" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:41 +#, kde-format +msgctxt "" +"Showed in a notification when the Bluetooth mode is going to be changed (for " +"example to flight mode), the %1 is the name of the mode" +msgid "Change Bluetooth mode to '%1'?" +msgstr "要變更藍牙模式為 %1 嗎?" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:45 +msgctxt "Confirm the bluetooth mode change, shown in a notification button" +msgid "Confirm" +msgstr "確認" + +#: daemon/helpers/confirmmodechange/confirmmodechange.cpp:46 +msgctxt "Deny the bluetooth mdoe change, shown in a notification" +msgid "Deny" +msgstr "拒絕" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:39 +#, kde-format +msgctxt "" +"The text is shown in a notification to know if the PIN is correct, %1 is the " +"remote bluetooth device and %2 is the pin" +msgid "%1 is asking if the PIN is correct: %2" +msgstr "%1 詢問 PIN 碼是否正確:%2" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:43 +msgctxt "Notification button to know if the pin is correct or not" +msgid "PIN correct" +msgstr "PIN 碼正確" + +#: daemon/helpers/requestconfirmation/requestconfirmation.cpp:44 +msgctxt "Notification button to say that the PIN is wrong" +msgid "PIN incorrect" +msgstr "PIN 碼不正確" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: daemon/helpers/requestpin/dialogWidget.ui:39 +msgid "PIN:" +msgstr "PIN 碼:" + +#: daemon/helpers/requestpin/requestpin.cpp:45 +#, kde-format +msgctxt "" +"Shown in a notification to announce that a PIN is needed to accomplish a " +"pair action, %1 is the name of the bluetooth device" +msgid "PIN needed to pair with %1" +msgstr "PIN 碼必須與 %1 配對" + +#: daemon/helpers/requestpin/requestpin.cpp:51 +msgctxt "" +"Notification button which once clicked, a dialog to introduce the PIN will " +"be shown" +msgid "Introduce PIN" +msgstr "介紹 PIN 碼" + +#: daemon/helpers/requestpin/requestpin.cpp:78 +#, kde-format +msgctxt "" +"Shown in a dialog which asks to introduce a PIN that will be used to pair a " +"Bluetooth device, %1 is the name of the Bluetooth device" +msgid "" +"In order to pair this computer with %1, you have to enter a PIN. Please do " +"it below." +msgstr "要將此電腦與 %1 配對,請在下方輸入 PIN 碼。" + +#: daemon/helpers/requestpin/requestpin.cpp:88 +msgctxt "Shown in the caption of a dialog where the user introduce the PIN" +msgid "Introduce PIN" +msgstr "介紹 PIN 碼" + +#: daemon/kded/BlueDevilDaemon.cpp:89 daemon/kded/BlueDevilDaemon.cpp:91 +msgid "Bluetooth Daemon" +msgstr "藍牙伺服程式" + +#: daemon/kded/BlueDevilDaemon.cpp:93 kio/obexftp/daemon/ObexFtpDaemon.cpp:67 +#: monolithic/main.cpp:29 sendfile/main.cpp:33 wizard/main.cpp:31 +msgid "(c) 2010, UFO Coders" +msgstr "(c) 2010, UFO Coders" + +#: daemon/kded/BlueDevilDaemon.cpp:96 kio/obexftp/daemon/ObexFtpDaemon.cpp:70 +#: monolithic/main.cpp:31 sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Alejandro Fiestas Olivares" +msgstr "Alejandro Fiestas Olivares" + +#: daemon/kded/BlueDevilDaemon.cpp:96 daemon/kded/BlueDevilDaemon.cpp:99 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:70 monolithic/main.cpp:31 +#: sendfile/main.cpp:35 wizard/main.cpp:33 +msgid "Maintainer" +msgstr "維護者" + +#: daemon/kded/BlueDevilDaemon.cpp:99 +msgid "Eduardo Robles Elvira" +msgstr "Eduardo Robles Elvira" + +#: daemon/kded/bluezagent.cpp:192 +#, fuzzy +#| msgid "Bluetooth Devices" +msgctxt "" +"User will see this as: Bluetooth device is asking if the pin is " +"correct It is mostly a fallback" +msgid "Bluetooth device" +msgstr "藍牙裝置" + +#: daemon/kded/filereceiver/receivefilejob.cpp:119 +#, kde-format +msgctxt "" +"Show a notification asking to authorize or deny an incoming file transfer to " +"this computer from a Bluetooth device." +msgid "%1 is sending you the file %2" +msgstr "%1 正在傳送檔案 %2 給您" + +#: daemon/kded/filereceiver/receivefilejob.cpp:123 +msgctxt "" +"Button to accept the incoming file transfer and download it in the default " +"download directory" +msgid "Accept" +msgstr "接受" + +#: daemon/kded/filereceiver/receivefilejob.cpp:124 +msgctxt "Deny the incoming file transfer" +msgid "Cancel" +msgstr "取消" + +#: daemon/kded/filereceiver/receivefilejob.cpp:195 +msgid "Receiving file over Bluetooth" +msgstr "透過藍牙接收檔案" + +#: daemon/kded/filereceiver/receivefilejob.cpp:196 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer origin" +msgid "From" +msgstr "從" + +#: daemon/kded/filereceiver/receivefilejob.cpp:198 +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgctxt "File transfer destination" +msgid "To" +msgstr "到" + +#: fileitemactionplugin/sendfileitemaction.cpp:62 +msgid "Send via Bluetooth" +msgstr "透過藍牙傳送" + +#: fileitemactionplugin/sendfileitemaction.cpp:81 +msgctxt "Find Bluetooth device" +msgid "Find Device..." +msgstr "尋找裝置..." + +#: fileitemactionplugin/sendfileitemaction.cpp:84 +msgctxt "Other Bluetooth device" +msgid "Other..." +msgstr "其他..." + +#: kcmodule/bluedeviladapters.cpp:51 +msgctxt "Radio widget to set if we want the adapter to be hidden" +msgid "Hidden" +msgstr "隱藏" + +#: kcmodule/bluedeviladapters.cpp:52 +msgctxt "Radio widget to set if we want the adapter to be always visible" +msgid "Always visible" +msgstr "永遠可見" + +#: kcmodule/bluedeviladapters.cpp:53 +msgctxt "Radio widget to set if we want the adapter to be temporarily visible" +msgid "Temporarily visible" +msgstr "暫時可見" + +#: kcmodule/bluedeviladapters.cpp:93 +#, kde-format +msgctxt "Discover time for the adapter" +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 分鐘" + +#: kcmodule/bluedeviladapters.cpp:99 +msgctxt "Name of the adapter" +msgid "Name" +msgstr "名稱" + +#: kcmodule/bluedeviladapters.cpp:100 +msgctxt "Whether the adapter is powered or not" +msgid "Powered" +msgstr "已供電" + +#: kcmodule/bluedeviladapters.cpp:101 +msgctxt "Whether the adapter is visible or not" +msgid "Visibility" +msgstr "可見度" + +#: kcmodule/bluedeviladapters.cpp:104 +msgctxt "How long the adapter will be discoverable" +msgid "Discover Time" +msgstr "發現時間" + +#: kcmodule/bluedeviladapters.cpp:121 kcmodule/bluedeviladapters.cpp:207 +#, kde-format +msgid "Default adapter: %1 (%2)" +msgstr "預設轉接器:%1(%2)" + +#: kcmodule/bluedeviladapters.cpp:123 kcmodule/bluedeviladapters.cpp:209 +#, kde-format +msgid "Adapter: %1 (%2)" +msgstr "轉接器:%1(%2)" + +#: kcmodule/bluedeviladapters.cpp:205 kcmodule/bluedeviladapters.cpp:230 +#, kde-format +msgid "1 minute" +msgid_plural "%1 minutes" +msgstr[0] "%1 分鐘" + +#: kcmodule/bluedeviladapters.cpp:242 +msgid "Bluetooth Adapters" +msgstr "藍牙轉接器" + +#: kcmodule/bluedeviladapters.cpp:243 +msgid "Bluetooth Adapters Control Panel Module" +msgstr "藍牙轉接器控制面板模組" + +#: kcmodule/bluedeviladapters.cpp:244 kcmodule/bluedevildevices.cpp:327 +#: kcmodule/bluedeviltransfer.cpp:54 +msgid "(c) 2010 Rafael Fernández López" +msgstr "(c) 2010 Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 monolithic/main.cpp:32 +msgid "Rafael Fernández López" +msgstr "Rafael Fernández López" + +#: kcmodule/bluedeviladapters.cpp:246 kcmodule/bluedevildevices.cpp:329 +#: kcmodule/bluedeviltransfer.cpp:56 +msgid "Developer and Maintainer" +msgstr "開發者和維護者" + +#: kcmodule/bluedeviladapters.cpp:323 +msgid "No adapters found. Please connect one." +msgstr "找不到轉接器。請連接一個。" + +#: kcmodule/bluedevildevices.cpp:320 +msgid "Enable KDE Bluetooth Integration" +msgstr "開啟 KDE 藍牙整合" + +#: kcmodule/bluedevildevices.cpp:325 +msgid "Bluetooth Devices" +msgstr "藍牙裝置" + +#: kcmodule/bluedevildevices.cpp:326 +msgid "Bluetooth Devices Control Panel Module" +msgstr "藍牙裝置控制面板模組" + +#: kcmodule/bluedevildevices.cpp:361 +msgctxt "Details of the device" +msgid "Details" +msgstr "詳細資料" + +#: kcmodule/bluedevildevices.cpp:363 +msgctxt "Remove a device from the list of known devices" +msgid "Remove" +msgstr "移除" + +#: kcmodule/bluedevildevices.cpp:365 kcmodule/bluedevildevices.cpp:448 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Connect" +msgstr "連線" + +#: kcmodule/bluedevildevices.cpp:367 +msgid "Disconnect" +msgstr "斷線" + +#: kcmodule/bluedevildevices.cpp:369 +msgid "Add Device..." +msgstr "新增裝置" + +#: kcmodule/bluedevildevices.cpp:446 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgid "Re-connect" +msgstr "連線" + +#: kcmodule/bluedevildevices.cpp:483 +#, kde-format +msgid "Pick a new alias for %1" +msgstr "請選擇 %1 的別名" + +#: kcmodule/bluedevildevices.cpp:507 +#, kde-format +msgid "" +"Are you sure that you want to remove device \"%1\" from the list of known " +"devices?" +msgstr "您確定要從已知裝置清單中移除裝置 %1 嗎?" + +#: kcmodule/bluedevildevices.cpp:508 +msgctxt "Title of window that asks for confirmation when removing a device" +msgid "Device removal" +msgstr "裝置移除" + +#: kcmodule/bluedevildevices.cpp:577 +msgid "No remote devices have been added" +msgstr "沒有新增遠端裝置" + +#: kcmodule/bluedevildevices.cpp:579 +msgid "Click here to add a remote device" +msgstr "點選這裡新增遠端裝置" + +#: kcmodule/bluedevildevices.cpp:618 kcmodule/bluedevildevices.cpp:660 +msgctxt "Type of device: could not be determined" +msgid "Unknown" +msgstr "未知" + +#: kcmodule/bluedevildevices.cpp:621 +msgctxt "This device is a Phone" +msgid "Phone" +msgstr "電話" + +#: kcmodule/bluedevildevices.cpp:624 +msgctxt "This device is a Modem" +msgid "Modem" +msgstr "數據機" + +#: kcmodule/bluedevildevices.cpp:627 +msgctxt "This device is a Computer" +msgid "Computer" +msgstr "電腦" + +#: kcmodule/bluedevildevices.cpp:630 +msgctxt "This device is of type Network" +msgid "Network" +msgstr "網路" + +#: kcmodule/bluedevildevices.cpp:633 +msgctxt "This device is a Headset" +msgid "Headset" +msgstr "耳機" + +#: kcmodule/bluedevildevices.cpp:636 +msgctxt "This device are Headphones" +msgid "Headphones" +msgstr "耳機電話" + +#: kcmodule/bluedevildevices.cpp:639 +msgctxt "This device is of type Audio" +msgid "Audio" +msgstr "音效" + +#: kcmodule/bluedevildevices.cpp:642 +msgctxt "This device is a Keyboard" +msgid "Keyboard" +msgstr "鍵盤" + +#: kcmodule/bluedevildevices.cpp:645 +msgctxt "This device is a Mouse" +msgid "Mouse" +msgstr "滑鼠" + +#: kcmodule/bluedevildevices.cpp:648 +msgctxt "This device is a Camera" +msgid "Camera" +msgstr "相機" + +#: kcmodule/bluedevildevices.cpp:651 +msgctxt "This device is a Printer" +msgid "Printer" +msgstr "印表機" + +#: kcmodule/bluedevildevices.cpp:654 +msgctxt "This device is a Joypad" +msgid "Joypad" +msgstr "搖桿" + +#: kcmodule/bluedevildevices.cpp:657 +msgctxt "This device is a Tablet" +msgid "Tablet" +msgstr "平板裝置" + +#: kcmodule/bluedevildevices.cpp:663 +#, kde-format +msgctxt "Type of remote device (e.g. Camera, Mouse, Headset...)" +msgid "Type: %1" +msgstr "類型:%1" + +#: kcmodule/bluedeviltransfer.cpp:52 +msgid "Bluetooth Transfer" +msgstr "藍牙傳輸" + +#: kcmodule/bluedeviltransfer.cpp:53 +msgid "Bluetooth Transfer Control Panel Module" +msgstr "藍牙傳輸控制面板模組" + +#: kcmodule/bluedeviltransfer.cpp:74 +msgctxt "'Auto accept' option value" +msgid "Never" +msgstr "永不" + +#: kcmodule/bluedeviltransfer.cpp:75 +msgctxt "'Auto accept' option value" +msgid "Trusted devices" +msgstr "信任的裝置" + +#: kcmodule/bluedeviltransfer.cpp:76 +msgctxt "'Auto accept' option value" +msgid "All devices" +msgstr "所有裝置" + +#: kcmodule/bluedeviltransfer.cpp:78 +msgctxt "'Require PIN' option value" +msgid "Never" +msgstr "永不" + +#: kcmodule/bluedeviltransfer.cpp:79 +msgctxt "'Require PIN' option value" +msgid "Always" +msgstr "總是" + +#: kcmodule/bluedeviltransfer.cpp:81 +msgctxt "'Permissions' option value" +msgid "Read Only" +msgstr "唯讀" + +#: kcmodule/bluedeviltransfer.cpp:82 +msgctxt "'Permissions' option value" +msgid "Modify and Read" +msgstr "變更及讀取" + +#: kcmodule/devicedetails.cpp:44 +msgctxt "Name of the device" +msgid "Name" +msgstr "名稱" + +#: kcmodule/devicedetails.cpp:45 +msgctxt "Alias of the device" +msgid "Alias" +msgstr "別名" + +#: kcmodule/devicedetails.cpp:49 +msgctxt "Physical address of the device" +msgid "Address" +msgstr "地址" + +#: kcmodule/devicedetails.cpp:52 +msgctxt "Device is paired" +msgid "Paired" +msgstr "無配對" + +#: kcmodule/devicedetails.cpp:54 +msgctxt "Device is blocked" +msgid "Blocked" +msgstr "已封鎖" + +#: kcmodule/devicedetails.cpp:56 +msgctxt "Device is trusted" +msgid "Trusted" +msgstr "信任:" + +#: kcmodule/systemcheck.cpp:156 kio/bluetooth/kiobluetooth.cpp:81 +#: kio/bluetooth/kiobluetooth.cpp:190 +msgid "No Bluetooth adapters have been found." +msgstr "找不到藍牙轉接器" + +#: kcmodule/systemcheck.cpp:164 kcmodule/systemcheck.cpp:171 +#: kcmodule/systemcheck.cpp:184 kcmodule/systemcheck.cpp:191 +#: kcmodule/systemcheck.cpp:204 kcmodule/systemcheck.cpp:211 +msgctxt "Action to fix a problem" +msgid "Fix it" +msgstr "修正它" + +#: kcmodule/systemcheck.cpp:175 +msgid "Your default Bluetooth adapter is not visible for remote devices." +msgstr "遠端裝置看不到您的預設藍牙轉接器" + +#: kcmodule/systemcheck.cpp:195 +msgid "Interaction with Bluetooth system is not optimal." +msgstr "與籃芽系統的互動未最佳化。" + +#: kcmodule/systemcheck.cpp:215 +msgid "Bluetooth is not completely enabled." +msgstr "藍牙並未完全開啟。" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: kcmodule/transfer.ui:17 +msgid "Receiving" +msgstr "接收中" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: kcmodule/transfer.ui:36 +msgid "Save files in:" +msgstr "儲存檔案到:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: kcmodule/transfer.ui:71 +msgid "Accept automatically:" +msgstr "自動接受:" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: kcmodule/transfer.ui:91 +msgid "Receive files:" +msgstr "接收檔案:" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: kcmodule/transfer.ui:107 +msgid "Sharing" +msgstr "分享中" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: kcmodule/transfer.ui:120 +msgid "Share Files:" +msgstr "分享檔案:" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: kcmodule/transfer.ui:140 +msgid "Require PIN:" +msgstr "需要 PIN 碼:" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: kcmodule/transfer.ui:153 +msgid "Permissions:" +msgstr "權限:" + +#. i18n: ectx: property (text), widget (KPushButton, sharedFiles) +#: kcmodule/transfer.ui:184 +msgid "Shared Files" +msgstr "分享的檔案" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: kcmodule/transfer.ui:212 +msgid "Shared Folder:" +msgstr "共享資料夾:" + +#: kio/bluetooth/kiobluetooth.cpp:44 +msgid "kiobluetooth" +msgstr "kiobluetooth" + +#: kio/bluetooth/kiobluetooth.cpp:68 monolithic/monolithic.cpp:140 +msgid "Send File" +msgstr "傳送檔案" + +#: kio/bluetooth/kiobluetooth.cpp:73 +msgid "Browse Files" +msgstr "瀏覽檔案" + +#: kio/bluetooth/kiobluetooth.cpp:104 +msgid "Retrieving services..." +msgstr "取得服務中..." + +#: kio/bluetooth/kiobluetooth.cpp:154 +msgid "Scanning for new devices..." +msgstr "掃描新裝置中..." + +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:63 +#: kio/obexftp/daemon/ObexFtpDaemon.cpp:65 +msgid "ObexFtp Daemon" +msgstr "ObexFtp 伺服程式" + +#: kio/obexftp/kio_obexftp.cpp:40 +msgid "kioobexftp" +msgstr "kioobexftp" + +#: kio/obexftp/kio_obexftp.cpp:97 +msgid "Retrieving information from remote device..." +msgstr "從遠端裝置取得資訊中..." + +#: kio/obexftp/kio_obexftp.cpp:176 +msgid "Connecting to the device" +msgstr "正在連線到裝置" + +#: monolithic/main.cpp:28 +msgid "Bluetooth" +msgstr "藍芽" + +#: monolithic/main.cpp:32 +msgid "Developer" +msgstr "開發者" + +#: monolithic/monolithic.cpp:42 +#, fuzzy +#| msgid "Browse devices" +msgid "Browse device" +msgstr "瀏覽裝置" + +#: monolithic/monolithic.cpp:43 sendfile/sendfilewizard.cpp:62 +msgid "Send Files" +msgstr "傳送檔案" + +#: monolithic/monolithic.cpp:128 +msgid "Bluetooth is Off" +msgstr "藍牙裝置已關閉" + +#: monolithic/monolithic.cpp:134 +msgid "Turn Bluetooth On" +msgstr "開啟藍牙裝置" + +#: monolithic/monolithic.cpp:144 +msgid "Browse devices" +msgstr "瀏覽裝置" + +#: monolithic/monolithic.cpp:151 +msgid "Known Devices" +msgstr "已知裝置" + +#: monolithic/monolithic.cpp:161 +msgid "Add Device" +msgstr "新增裝置" + +#: monolithic/monolithic.cpp:165 +msgid "Configure Bluetooth" +msgstr "設定藍牙裝置" + +#: monolithic/monolithic.cpp:172 +msgid "Discoverable" +msgstr "可發現" + +#: monolithic/monolithic.cpp:178 +msgid "Turn Bluetooth Off" +msgstr "關閉藍牙裝置" + +#: monolithic/monolithic.cpp:200 +#, kde-format +msgctxt "Number of Bluetooth connected devices" +msgid "%1 connected device" +msgid_plural "%1 connected devices" +msgstr[0] "%1 個已連線的裝置" + +#: monolithic/monolithic.cpp:359 +msgid "No adapters found" +msgstr "找不到轉接器" + +#: monolithic/monolithic.cpp:389 +msgctxt "When the bluetooth is enabled and powered" +msgid "Bluetooth is On" +msgstr "藍牙裝置已開啟" + +#: monolithic/monolithic.cpp:391 +msgctxt "When the bluetooth is disabled or not powered" +msgid "Bluetooth is Off" +msgstr "藍牙裝置已關閉" + +#: monolithic/monolithic.cpp:441 +#, fuzzy +#| msgctxt "Action" +#| msgid "Connect" +msgctxt "Connect to a bluetooth device" +msgid "Connect" +msgstr "連線" + +#: sendfile/main.cpp:32 +msgid "Bluetooth Send File Helper" +msgstr "藍牙傳送檔案協助程式" + +#: sendfile/main.cpp:42 sendfile/main.cpp:43 +msgid "Device UUID where the files will be sent" +msgstr "檔案傳送目的裝置的 UUID" + +#: sendfile/main.cpp:44 +msgid "Files that will be sent" +msgstr "要傳送的檔案" + +#. i18n: ectx: property (text), widget (QLabel, connLabel) +#: sendfile/pages/connecting.ui:17 +#, no-c-format, kde-format +msgid "Connecting to: %1" +msgstr "正在連接至:%1" + +#: sendfile/pages/connectingpage.cpp:41 +#, kde-format +msgctxt "Connecting to a Bluetooth device" +msgid "Connecting to %1..." +msgstr "連線到 %1..." + +#. i18n: ectx: property (text), widget (QLabel, selectLbl) +#: sendfile/pages/selectdeviceandfilespage.cpp:87 +#: sendfile/pages/selectfilediscover.ui:112 +msgid "Select one or more files:" +msgstr "請選擇一個或以上的檔案:" + +#: sendfile/pages/selectdeviceandfilespage.cpp:89 +#, kde-format +msgid "Selected files: %1" +msgstr "已選取檔案:%1" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: sendfile/pages/selectfilediscover.ui:29 +msgid "Select a device from the list:" +msgstr "從清單中選擇裝置:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: sendfile/pages/selectfilediscover.ui:92 +msgid "Scanning" +msgstr "掃瞄中" + +#: sendfile/sendfilesjob.cpp:74 sendfile/sendfilesjob.cpp:177 +msgid "Sending file over Bluetooth" +msgstr "透過藍牙傳送檔案" + +#: sendfile/sendfilewizard.cpp:60 +msgid "Bluetooth Send Files" +msgstr "藍牙傳送檔案" + +#. i18n: ectx: label, entry (enabled), group (General) +#: settings/filereceiver.kcfg:21 +msgid "Enable or disable receiving files" +msgstr "開啟或關閉接收檔案" + +#. i18n: ectx: label, entry (saveUrl), group (General) +#: settings/filereceiver.kcfg:25 +msgid "Save received files to:" +msgstr "儲存已接收檔案到:" + +#. i18n: ectx: label, entry (autoAccept), group (General) +#: settings/filereceiver.kcfg:29 +msgid "Whether allow to modify shared files" +msgstr "是否允許變更分享的檔案" + +#. i18n: ectx: label, entry (shareEnabled), group (General) +#: settings/filereceiver.kcfg:34 +msgid "Enable or disable file sharing" +msgstr "開啟或關閉檔案分享" + +#. i18n: ectx: label, entry (requirePin), group (General) +#: settings/filereceiver.kcfg:38 +msgid "Whether require the PIN" +msgstr "是否需要 PIN 碼" + +#. i18n: ectx: label, entry (allowWrite), group (General) +#: settings/filereceiver.kcfg:42 +msgid "Allow external devices to modify the shared files" +msgstr "允許外部裝置變更分享的檔案" + +#. i18n: ectx: label, entry (enableGlobalBluetooth), group (General) +#: settings/global.kcfg:10 +msgid "Enable or disable the global KDE Bluetooth integration" +msgstr "開啟或關閉所有的全域 KDE 藍牙整合" + +#: wizard/bluewizard.cpp:41 +msgid "Bluetooth Device Wizard" +msgstr "藍牙裝置精靈" + +#: wizard/bluewizard.cpp:68 +msgctxt "Action to go to the next page on the wizard" +msgid "Next" +msgstr "下一個" + +#: wizard/bluewizard.cpp:69 +msgctxt "Action to finish the wizard" +msgid "Finish" +msgstr "完成" + +#: wizard/main.cpp:30 +msgid "Bluetooth Wizard" +msgstr "藍牙精靈" + +#: wizard/main.cpp:39 +msgid "Device to pair with" +msgstr "要配對的裝置" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: wizard/pages/discover.ui:63 +msgid "Scanning..." +msgstr "掃瞄中..." + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/discover.ui:83 +msgid "Manual PIN:" +msgstr "手動 PIN 碼:" + +#. i18n: ectx: property (inputMask), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:118 +msgid "999999999; " +msgstr "999999999; " + +#. i18n: ectx: property (placeholderText), widget (QLineEdit, pinText) +#: wizard/pages/discover.ui:124 +msgid "0000" +msgstr "0000" + +#: wizard/pages/discoverpage.cpp:39 +msgid "Select a device" +msgstr "請選擇一個裝置" + +#: wizard/pages/fail.cpp:46 +msgctxt "" +"Button offered when the wizard fail. This button will restart the wizard" +msgid "Restart the wizard" +msgstr "重新啟動精靈" + +#: wizard/pages/fail.cpp:50 +msgctxt "Button that closes the wizard" +msgid "Close" +msgstr "關閉" + +#: wizard/pages/fail.cpp:61 +msgctxt "This string is shown when the wizard fail" +msgid "The setup of the device has failed" +msgstr "裝置的設定失敗" + +#. i18n: ectx: property (text), widget (QLabel, failLbl) +#: wizard/pages/fail.ui:35 +#, no-c-format, kde-format +msgid "The setup of %1 has failed" +msgstr "%1 的設定失敗" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/keyboardpairing.ui:108 +msgid "" +"Please introduce the PIN in your keyboard when it appears and press Enter" +msgstr "當它出現時,請在鍵盤上輸入 PIN 碼並按下 Enter" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: wizard/pages/legacypairing.ui:108 wizard/pages/ssppairing.cpp:98 +msgid "Please introduce the PIN in your device when it appears" +msgstr "當它出現時,請在裝置上輸入 PIN 碼" + +#: wizard/pages/legacypairingdatabase.cpp:48 wizard/pages/ssppairing.cpp:60 +#, fuzzy, kde-format +#| msgctxt "Connecting to a Bluetooth device" +#| msgid "Connecting to %1..." +msgid "Connecting to %1..." +msgstr "連線到 %1..." + +#. i18n: ectx: property (text), widget (QLabel, connecting) +#: wizard/pages/nopairing.ui:51 +#, fuzzy +#| msgid "Connecting to: %1" +msgid "Connecting to:" +msgstr "正在連接至:%1" + +#: wizard/pages/ssppairing.cpp:75 +msgid "Matches" +msgstr "火柴" + +#: wizard/pages/ssppairing.cpp:77 +msgid "Does not match" +msgstr "不符合" + +#: wizard/pages/ssppairing.cpp:90 +#, kde-format +msgid "" +"Please, confirm that the PIN displayed on \"%1\" matches the wizard one." +msgstr "請確定顯示在 \"%1\" 上的 PIN 碼與精靈上設定的符合。" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#, fuzzy \ No newline at end of file diff --git a/bluedevil/src/CMakeLists.txt b/bluedevil/src/CMakeLists.txt new file mode 100644 index 00000000..0ffaccf7 --- /dev/null +++ b/bluedevil/src/CMakeLists.txt @@ -0,0 +1,19 @@ +add_subdirectory(settings) +add_subdirectory(sendfile) +add_subdirectory(daemon) +add_subdirectory(kcmodule) +add_subdirectory(kio) +add_subdirectory(wizard) +add_subdirectory(monolithic) + +if(NOT KDE_VERSION VERSION_LESS "4.6.0") + add_subdirectory(fileitemactionplugin) +else(NOT KDE_VERSION VERSION_LESS "4.6.0") + message(WARNING "Upgrade to kde-4.6 if you want \"Send via bluetooth\" action in Dolphin/Konqueror") +endif(NOT KDE_VERSION VERSION_LESS "4.6.0") + +install(FILES bluedevil.notifyrc + DESTINATION ${DATA_INSTALL_DIR}/bluedevil) + +install(FILES bluedevil-mime.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) +update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) diff --git a/bluedevil/src/Messages.sh b/bluedevil/src/Messages.sh new file mode 100755 index 00000000..f3ab8ff1 --- /dev/null +++ b/bluedevil/src/Messages.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +$EXTRACTRC `find . -name \*.ui -o -name \*.rc -o -name \*.kcfg` >> rc.cpp +$XGETTEXT `find . -name \*.cpp` -o $podir/bluedevil.pot diff --git a/bluedevil/src/bluedevil-mime.xml b/bluedevil/src/bluedevil-mime.xml new file mode 100644 index 00000000..669147d2 --- /dev/null +++ b/bluedevil/src/bluedevil-mime.xml @@ -0,0 +1,43 @@ + + + + + + + + Known Device + + + + + Discovered Device + + + + + Service + + + Service + + + Service + + + Service + + + Service + + diff --git a/bluedevil/src/bluedevil.notifyrc b/bluedevil/src/bluedevil.notifyrc new file mode 100644 index 00000000..31ed1570 --- /dev/null +++ b/bluedevil/src/bluedevil.notifyrc @@ -0,0 +1,476 @@ +[Global] +IconName=preferences-system-bluetooth +Comment=Bluetooth system +Comment[ca]=Sistema Bluetooth +Comment[ca@valencia]=Sistema Bluetooth +Comment[cs]=Systém Bluetooth +Comment[da]=Bluetooth-system +Comment[de]=Bluetooth-System +Comment[el]=Σύστημα Bluetooth +Comment[es]=Sistema Bluetooth +Comment[fi]=Bluetooth-järjestelmä +Comment[fr]=Système Bluetooth +Comment[gl]=Sistema Bluetooth +Comment[hu]=Bluetooth rendszer +Comment[it]=Sistema Bluetooth +Comment[kk]=Bluetooth жүйесі +Comment[lt]=Bluetooth +Comment[mr]=ब्लूटूथ प्रणाली +Comment[nb]=Blåtann-system +Comment[nl]=Bluetooth-systeem +Comment[pa]=ਬਲਿਊਟੁੱਥ ਸਿਸਟਮ +Comment[pl]=System Bluetooth +Comment[pt]=Sistema Bluetooth +Comment[pt_BR]=Sistema Bluetooth +Comment[ro]=Sistem Bluetooth +Comment[ru]=Система Bluetooth +Comment[sk]=Systém Bluetooth +Comment[sl]=Sistem za Bluetooth +Comment[sr]=Блутут систем +Comment[sr@ijekavian]=Блутут систем +Comment[sr@ijekavianlatin]=Bluetooth sistem +Comment[sr@latin]=Bluetooth sistem +Comment[sv]=Blåtandsystem +Comment[tr]=Bluetooth sistemi +Comment[ug]=كۆكچىش سىستېمىسى +Comment[uk]=Система Bluetooth +Comment[x-test]=xxBluetooth systemxx +Comment[zh_TW]=藍牙系統 +Name=Bluetooth +Name[ca]=Bluetooth +Name[ca@valencia]=Bluetooth +Name[cs]=Bluetooth +Name[da]=Bluetooth +Name[de]=Bluetooth +Name[el]=Bluetooth +Name[es]=Bluetooth +Name[fi]=Bluetooth +Name[fr]=Bluetooth +Name[gl]=Bluetooth +Name[hu]=Bluetooth +Name[it]=Bluetooth +Name[kk]=Bluetooth +Name[lt]=Bluetooth +Name[mr]=ब्लूटूथ +Name[nb]=Blåtann +Name[nl]=Bluetooth +Name[pa]=ਬਲਿਊਟੁੱਥ +Name[pl]=Bluetooth +Name[pt]=Bluetooth +Name[pt_BR]=Bluetooth +Name[ro]=Bluetooth +Name[ru]=Bluetooth +Name[sk]=Bluetooth +Name[sl]=Bluetooth +Name[sr]=Блутут +Name[sr@ijekavian]=Блутут +Name[sr@ijekavianlatin]=Bluetooth +Name[sr@latin]=Bluetooth +Name[sv]=Blåtand +Name[tr]=Bluetooth +Name[ug]=كۆكچىش +Name[uk]=Bluetooth +Name[x-test]=xxBluetoothxx +Name[zh_TW]=藍牙 +Ignore=true + +[Event/bluedevilAuthorize] +Name=Authorization Requested +Name[ca]=Sol·licitud d'autorització +Name[ca@valencia]=Sol·licitud d'autorització +Name[cs]=Je vyžadováno udělení oprávnění +Name[da]=Godkendelse kræves +Name[de]=Autorisierung gefordert +Name[el]=Απαιτείται ταυτοποίηση +Name[es]=Se ha solicitado autorización +Name[fi]=Valtuutusta pyydetty +Name[fr]=Autorisation demandée +Name[gl]=Requírese autorización +Name[hu]=Felhatalmazás kérve +Name[it]=Richiesta autorizzazione +Name[kk]=Авторизация сұралды +Name[lt]=Prašoma prieigos teisių +Name[mr]=अधिप्रमाणाची विनंती केली +Name[nb]=Autorisasjon kreves +Name[nl]=Autorisatie gevraagd +Name[pa]=ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ +Name[pl]=Wymagane uwierzytelnienie +Name[pt]=Pedido de Autorização +Name[pt_BR]=Pedido de autorização +Name[ro]=Se cere autorizare +Name[ru]=Требуется авторизация +Name[sk]=Vyžaduje sa overenie +Name[sl]=Zahtevana je pooblastitev +Name[sr]=Неопходно овлашћивање +Name[sr@ijekavian]=Неопходно овлашћивање +Name[sr@ijekavianlatin]=Neophodno ovlašćivanje +Name[sr@latin]=Neophodno ovlašćivanje +Name[sv]=Behörighet begärd +Name[tr]=Yetkilendirme Gerekli +Name[ug]=كىملىك دەلىللەش تەلەپ قىلىندى +Name[uk]=Слід пройти розпізнавання +Name[x-test]=xxAuthorization Requestedxx +Name[zh_TW]=請求認證 +Comment=A device wants to connect +Comment[ca]=Un dispositiu es vol connectar +Comment[ca@valencia]=Un dispositiu es vol connectar +Comment[cs]=Zařízení se chce připojit +Comment[da]=En enhed prøver at forbinde +Comment[de]=Ein Gerät möchte sich verbinden +Comment[el]=Μία συσκευή επιθυμεί να συνδεθεί +Comment[es]=Un dispositivo desea conectarse +Comment[fi]=Laite haluaa ottaa yhteyden +Comment[fr]=Un périphérique veut se connecter +Comment[gl]=Un dispositivo quere conectarse +Comment[hu]=Egy eszköz szeretne csatlakozni +Comment[it]=Un dispositivo vuole connettersi +Comment[kk]=Бір құрылғы байланысуды қалайды +Comment[lt]=Įrenginys nori prijungti prie Jūsų +Comment[mr]=एक साधन जोडू इच्छिते +Comment[nb]=En enhet vil koble til +Comment[nl]=Een apparaat wil een verbinding maken +Comment[pa]=ਜੰਤਰ ਕੁਨੈਕਟ ਹੋਣਾ ਚਾਹੁੰਦਾ ਹੈ +Comment[pl]=Urządzenie chce się podłączyć +Comment[pt]=Um dispositivo deseja ligar-se +Comment[pt_BR]=Um dispositivo quer estabelecer conexão +Comment[ro]=Un dispozitiv vrea să se conecteze +Comment[ru]=Устройство запрашивает соединение +Comment[sk]=Zariadenie sa chce pripojiť +Comment[sl]=Naprava se želi povezati +Comment[sr]=Уређај жели да се повеже +Comment[sr@ijekavian]=Уређај жели да се повеже +Comment[sr@ijekavianlatin]=Uređaj želi da se poveže +Comment[sr@latin]=Uređaj želi da se poveže +Comment[sv]=En enhet vill ansluta +Comment[tr]=Bir aygıt bağlanmak istiyor +Comment[uk]=Отримано запит щодо з’єднання пристрою з комп’ютером +Comment[x-test]=xxA device wants to connectxx +Comment[zh_TW]=某個裝置希望能與您連線 +Action=Popup + +[Event/bluedevilConfirmModechange] +Name=Confirm Mode Change +Name[ca]=Confirmació del canvi de mode +Name[ca@valencia]=Confirmació del canvi de mode +Name[cs]=Potvrdit změnu režimu +Name[da]=Bekræft tilstandsændring +Name[de]=Moduswechsel bestätigen +Name[el]=Επιβεβαίωση αλλαγής λειτουργίας +Name[es]=Confirmar el cambio de modo +Name[fi]=Vahvistan tilan vaihto +Name[fr]=Confirmer le changement de mode +Name[gl]=Confirmación do cambio de modo +Name[hu]=Módváltoztatás megerősítése +Name[it]=Conferma cambio di modalità +Name[kk]=Күйін өзгертуді құптау +Name[lt]=Patvirtinti veiksenos pakeitimą +Name[mr]=पद्धत बदल खात्री करा +Name[nb]=Bekreft endring i kjøremåte +Name[nl]=Moduswisseling bevestigen +Name[pl]=Potwierdź zmianę trybu +Name[pt]=Confirmar a Mudança de Modo +Name[pt_BR]=Confirmar a mudança de modo +Name[ro]=Confirmă schimbarea de regim +Name[ru]=Подтвердите смену режима +Name[sk]=Potvrdiť zmenu režimu +Name[sl]=Potrdite spremembo načina +Name[sr]=Потврди промену режима +Name[sr@ijekavian]=Потврди промену режима +Name[sr@ijekavianlatin]=Potvrdi promenu režima +Name[sr@latin]=Potvrdi promenu režima +Name[sv]=Bekräfta lägesändring +Name[tr]=Kip Değişikliğini Onayla +Name[uk]=Підтвердження зміни режиму +Name[x-test]=xxConfirm Mode Changexx +Name[zh_TW]=確認模式變更 +Comment=Bluetooth mode is about to be changed (normal to flight for example) +Comment[ar]=نمط بلوتوث على وشك التغيير (على سبيل المثال من النمط العادي إلى نمط الطيران) +Comment[bs]=Režim blututa će biti promijenjen (npr. s normalnog na ljetni) +Comment[ca]=El mode Bluetooth és a punt de canviar (de mode normal a mode avió per exemple) +Comment[ca@valencia]=El mode Bluetooth és a punt de canviar (de mode normal a mode avió per exemple) +Comment[cs]=Bude změněn režim Bluetooth (např. Normální na V letadle) +Comment[da]=Bluetooth-tilstanden er ved at blive ændret (f.eks. normal til flyvning) +Comment[de]=Der Bluetooth-Modus ist im Begriff, sich zu ändern (z. B. von normal auf Flugzeugmodus) +Comment[el]=Η λειτουργία του Bluetooth πρόκειται να αλλάξει (π.χ. από κανονική σε πτήσης) +Comment[en_GB]=Bluetooth mode is about to be changed (normal to flight for example) +Comment[es]=Se va a cambiar el modo Bluetooth (por ejemplo, de normal a avión) +Comment[et]=BlueDevili režiimi muudetakse (näiteks tavalisest lennurežiimiks) +Comment[fi]=Bluetooth-tilaa muutetaan (esim. tavallisesta lentokonetilaksi) +Comment[fr]=Le mode Bluetooth est sur le point d'être modifié (par exemple, de « Normal » à « Survol » ) +Comment[gl]=Está a piques de cambiarse o modo de Bluetooth (por exemplo, de «normal» a «voo»). +Comment[hu]=Megváltozik a bluetooth üzemmód (pl. normál vagy repülési) +Comment[it]=La modalità del Bluetooth sta per essere cambiata (da «normale» a «flight», ad esempio) +Comment[kk]=Bluetooth режімі өзгертілмек (мысалы, кәдімгіден ұшқандағыға) +Comment[km]=របៀប​ប៊្លូធូស​ដែល​ហៀបនឹង​ផ្លាស់ប្ដូរ​ (​ឧទាហរណ៍​របៀប​ហោះហើរ​ធម្មតា​) +Comment[lt]=Bluetooth veiksena bus pakeista (pvz. iš normalaus į skrydžio) +Comment[mr]=ब्लूटूथ पद्धतीत बदल होत आहे (उदा. सामान्य ते फ्लाइट) +Comment[nb]=Blåtann-kjøremåte skal til å bli endret (f.eks. normal til fly) +Comment[nds]=Bluetooth-Bedrief warrt ännert (a.B. vun Normaal- na Flegerbedrief) +Comment[nl]=Bluetooth-modus staat op het punt te wijzigen (bijvoorbeeld: normaal naar vlucht) +Comment[pa]=ਬਲਿਊਟੁੱਥ ਮੋਡ ਬਦਲਣ ਵਾਲਾ ਹੈ (ਉਦਾਹਰਨ ਲਈ ਸਧਾਰਨ ਤੋਂ ਫਲਾਈਟ ਮੋਡ) +Comment[pl]=Tryb Bluetooth ma zamiar być zmienionym (normalny do np. w locie) +Comment[pt]=Está prestes a alterar o modo de Bluetooth (normal para voo, por exemplo) +Comment[pt_BR]=O modo do Bluetooth está para ser alterado (normal para voo, por exemplo) +Comment[ro]=Regimul Bluetooth este pe cale de a fi modificat (de ex., de la Normal la Zbor) +Comment[ru]=Режим работы Bluetooth будет изменён (например, с нормального на режим полёта) +Comment[sk]=Režim Bluetooth sa ide zmeniť (napríklad normálny na letecký) +Comment[sl]=Način Bluetooth se bo spremenil (npr. iz običajnega v način za letenje) +Comment[sr]=Режим блутута ће бити промењен (нпр. с нормалног на летни) +Comment[sr@ijekavian]=Режим блутута ће бити промијењен (нпр. с нормалног на љетни) +Comment[sr@ijekavianlatin]=Režim Bluetootha će biti promijenjen (npr. s normalnog na ljetni) +Comment[sr@latin]=Režim Bluetootha će biti promenjen (npr. s normalnog na letni) +Comment[sv]=Blåtandläget ska just ändras (exempelvis från normal till flygning) +Comment[th]=โหมดของบลูทูทจะถูกเปลี่ยนโหมด (เช่น จากโหมดปกติเป็นโหมดไฟลท์ เป็นต้น) +Comment[tr]=Bluetooth kipi değiştirilmek üzere (örn. normal -> uçuş modu) +Comment[ug]=كۆكچىش ھالىتى ئۆزگەرتىلدى(مەسىلەن: نورمال -› ئۈچۈشقا) +Comment[uk]=Зміна режиму роботи Bluetooth (наприклад, зі звичайного на автономний) +Comment[x-test]=xxBluetooth mode is about to be changed (normal to flight for example)xx +Comment[zh_CN]=蓝牙模式即将更改(例如从正常变到飞行模式) +Comment[zh_TW]=即將改變藍牙模式(例如從一般模式轉換為飛航模式) +Action=Popup + +[Event/bluedevilRequestConfirmation] +Name=Confirm PIN +Name[ca]=Confirmació del PIN +Name[ca@valencia]=Confirmació del PIN +Name[cs]=Potvrdit PIN +Name[da]=Bekræft PIN-kode +Name[de]=PIN bestätigen +Name[el]=Επιβεβαίωση PIN +Name[es]=Confirmar PIN +Name[fi]=Vahvista PIN +Name[fr]=Confirmer le code « PIN » +Name[gl]=Confirmación do PIN +Name[hu]=PIN megerősítése +Name[it]=Conferma PIN +Name[kk]=PIN-кодын құптау +Name[lt]=Patvirtinti PIN +Name[mr]=पिनची खात्री करा +Name[nb]=Bekreft PIN +Name[nl]=PIN bevestigen +Name[pa]=ਪਿੰਨ ਪੁਸ਼ਟੀ +Name[pl]=Potwierdź numer PIN +Name[pt]=Confirmar o PIN +Name[pt_BR]=Confirmar o PIN +Name[ro]=Confirmă PIN +Name[ru]=Подтвердите PIN +Name[sk]=Potvrdiť PIN +Name[sl]=Potrdite PIN +Name[sr]=Потврди ПИН +Name[sr@ijekavian]=Потврди ПИН +Name[sr@ijekavianlatin]=Potvrdi PIN +Name[sr@latin]=Potvrdi PIN +Name[sv]=Bekräfta PIN-kod +Name[tr]=PIN'i Onaylayın +Name[ug]=PIN جەزملەش +Name[uk]=Підтвердження PIN +Name[x-test]=xxConfirm PINxx +Name[zh_TW]=確認 PIN 碼 +Comment=Confirm a device request using matching PINs +Comment[ca]=Confirma una sol·licitud d'un dispositiu usant PIN que coincideixin +Comment[ca@valencia]=Confirma una sol·licitud d'un dispositiu usant PIN que coincidisquen +Comment[cs]=Potvrďte požadavek zařízení použitím odpovídajících PINů +Comment[da]=Bekræft en enhedsanmodning med brug af matchende PIN-koder +Comment[de]=Eine Geräteanforderung mit passenden PINs bestätigen +Comment[el]=Επιβεβαίωση αίτησης συσκευής που χρησιμοποιεί ταιριαστά PIN +Comment[es]=Confirmar la petición de un dispositivo usando PIN emparejados +Comment[fi]=Vahvista laitepyyntö käyttämällä samoja PIN-lukuja +Comment[fr]=Confirme une demande d'un périphérique utilisant une correspondance de codes « PIN » +Comment[gl]=Confirmar a solicitude dun dispositivo empregando PIN coincidentes +Comment[hu]=Eszközkérés megerősítése egyező PIN-ek használatával +Comment[it]=Conferma la richiesta di un dispositivo usando PIN corrispondenti +Comment[kk]=Сәйкесті PIN-дерді келтіріп құрылғының талабын құптау +Comment[lt]=Pavirtinti įrenginio užklausą naudojant atitinkančius PIN +Comment[mr]=एक साधन विनंती पिन जुळवणी वापरून खात्री करा +Comment[nb]=Bekreft en enhetsforespørsel med samsvarende PIN-er +Comment[nl]=Een apparaataanvraag bevestigen met overeenkomstige PIN's +Comment[pl]=Potwierdź żądanie urządzenia przy użyciu zgodnych numerów PIN +Comment[pt]=Confirmar o pedido de um dispositivo com PIN's correspondentes +Comment[pt_BR]=Confirma o pedido de um dispositivo usando os PINs correspondentes +Comment[ro]=Confirmați o cerere de dispozitiv folosind PIN-uri potrivite +Comment[ru]=Подтвердите запрос устройства соответствующим PIN-кодом +Comment[sk]=Potvrdiť požiadavku zariadenia pomocou zhodných PINov +Comment[sl]=Potrdite zahtevo naprave z uporabo ujemajočih se številk PIN +Comment[sr]=Потврдите захтев уређаја одговарајућим ПИН‑ом +Comment[sr@ijekavian]=Потврдите захтев уређаја одговарајућим ПИН‑ом +Comment[sr@ijekavianlatin]=Potvrdite zahtev uređaja odgovarajućim PIN‑om +Comment[sr@latin]=Potvrdite zahtev uređaja odgovarajućim PIN‑om +Comment[sv]=Bekräfta att en enhetsbegäran använder PIN-koder som stämmer +Comment[tr]=Eşleştirme PIN'i kullanarak bir aygıt isteğini onaylayın +Comment[uk]=Підтвердження запиту пристрою відповідними PIN-кодами +Comment[x-test]=xxConfirm a device request using matching PINsxx +Comment[zh_TW]=利用比對 PIN 碼確認裝置的要求 +Action=Popup + +[Event/bluedevilRequestPin] +Name=Request PIN +Name[ar]=اطلب PIN +Name[bs]=Zahtijevaj PIN +Name[ca]=Sol·licita el PIN +Name[ca@valencia]=Sol·licita el PIN +Name[cs]=Vyžádat PIN +Name[da]=nmod om PIN-kode +Name[de]=PIN anfordern +Name[el]=Αίτηση PIN +Name[en_GB]=Request PIN +Name[es]=Solicitud de PIN +Name[et]=PIN-i soov +Name[fi]=Pyydä PINiä +Name[fr]=Demande de code « PIN » +Name[gl]=PIN esixido +Name[hu]=PIN-kód kérés +Name[it]=Richiesta PIN +Name[kk]=PIN талабы +Name[km]=ស្នើ PIN +Name[lt]=Prašyti PIN +Name[mr]=PIN ची विनंती करा +Name[nb]=Be om PIN +Name[nds]=PIN anfragen +Name[nl]=Om PIN verzoeken +Name[pa]=ਪਿੰਨ ਦੀ ਮੰਗ +Name[pl]=Żąda numeru PIN +Name[pt]=Pedir o PIN +Name[pt_BR]=Solicitar o PIN +Name[ro]=Cere PIN +Name[ru]=Запрос PIN +Name[sk]=Vyžadovať PIN +Name[sl]=Zahteva po PIN-u +Name[sr]=Захтевај ПИН +Name[sr@ijekavian]=Захтијевај ПИН +Name[sr@ijekavianlatin]=Zahtijevaj PIN +Name[sr@latin]=Zahtevaj PIN +Name[sv]=Begär PIN-kod +Name[th]=ร้องขอรหัส PIN +Name[tr]=PIN iste +Name[ug]=PIN تەلەپ قىل +Name[uk]=Запит щодо PIN-коду +Name[x-test]=xxRequest PINxx +Name[zh_CN]=请求 PIN +Name[zh_TW]=需要 PIN 碼 +Comment=A PIN is needed +Comment[ar]=PIN مطلوب +Comment[bs]=Potreban je PIN +Comment[ca]=Es necessita un PIN +Comment[ca@valencia]=Es necessita un PIN +Comment[cs]=Je potřeba PIN +Comment[da]=PIN-kode kræves +Comment[de]=Eine PIN wird benötigt +Comment[el]=Απαιτείται PIN +Comment[en_GB]=A PIN is needed +Comment[es]=Debe introducir un PIN +Comment[et]=Vajalik on PIN-i sisestamine +Comment[fi]=PIN vaaditaan +Comment[fr]=Un code « PIN » est nécessaire +Comment[gl]=Necesítase un PIN. +Comment[hu]=PIN-kód szükséges +Comment[it]=Un PIN è richiesto +Comment[kk]=PIN -коды керек +Comment[km]=ត្រូវការ PIN +Comment[lt]=Reikia PIN +Comment[mr]=PIN ची गरज आहे +Comment[nb]=En PIN kreves +Comment[nds]=En PIN deit noot +Comment[nl]=Er is een PIN nodig +Comment[pa]=ਪਿੰਨ ਚਾਹੀਦਾ ਹੈ +Comment[pl]=Potrzebny jest numer PIN +Comment[pt]=É necessário um PIN +Comment[pt_BR]=É necessário um PIN +Comment[ro]=Este necesar un PIN +Comment[ru]=Необходим PIN +Comment[sk]=Vyžaduje sa PIN +Comment[sl]=Zahtevan je PIN +Comment[sr]=Потребан је ПИН +Comment[sr@ijekavian]=Потребан је ПИН +Comment[sr@ijekavianlatin]=Potreban je PIN +Comment[sr@latin]=Potreban je PIN +Comment[sv]=En PIN-kod behövs +Comment[th]=ต้องการรหัส PIN +Comment[tr]=PIN gerekiyor +Comment[ug]=PIN زۆرۈر +Comment[uk]=Потрібен PIN-код +Comment[x-test]=xxA PIN is neededxx +Comment[zh_CN]=需要 PIN +Comment[zh_TW]=需要 PIN 碼 +Action=Popup + +[Event/bluedevilIncomingFile] +Name=Incoming File +Name[ar]=الملف الوارد +Name[bs]=Dolazeća datoteka +Name[ca]=Fitxer entrant +Name[ca@valencia]=Fitxer entrant +Name[cs]=Příchozí soubor +Name[da]=Indkommende fil +Name[de]=Eingehende Datei +Name[el]=Εισερχόμενο αρχείο +Name[en_GB]=Incoming File +Name[es]=Archivo entrante +Name[et]=Sisenev fail +Name[fi]=Saapuva tiedosto +Name[fr]=Fichier entrant +Name[gl]=Ficheiro entrante +Name[hu]=Beérkező fájl +Name[it]=File in arrivo +Name[kk]=Файлды қабылдау +Name[km]=ឯកសារ​ចូល​ +Name[lt]=Atsiunčiamas failas +Name[mr]=आत येणारी फाईल +Name[nb]=Innkommende fil +Name[nds]=Datei kummt an +Name[nl]=Bestand aangeboden +Name[pa]=ਫਾਇਲ ਆ ਰਹੀ ਹੈ +Name[pl]=Przychodzący plik +Name[pt]=Ficheiro Recebido +Name[pt_BR]=Arquivo recebido +Name[ro]=Fișier de intrare +Name[ru]=Входящий файл +Name[sk]=Prichádzajúci súbor +Name[sl]=Prejem datoteke +Name[sr]=Долазни фајл +Name[sr@ijekavian]=Долазни фајл +Name[sr@ijekavianlatin]=Dolazni fajl +Name[sr@latin]=Dolazni fajl +Name[sv]=Inkommande fil +Name[th]=มีแฟ้มถูกส่งเข้ามา +Name[tr]=Gelen Dosya +Name[ug]=كىرىۋاتقان ھۆججەت +Name[uk]=Вхідний файл +Name[x-test]=xxIncoming Filexx +Name[zh_CN]=正在传入的文件 +Name[zh_TW]=要傳進來的檔案 +Comment=Incoming file transfer +Comment[ca]=Transferència de fitxer entrant +Comment[ca@valencia]=Transferència de fitxer entrant +Comment[cs]=Příchozí přenos souboru +Comment[da]=Indkommende filoverførsel +Comment[de]=Eingehende Dateiübertragung +Comment[el]=Μεταφορά εισερχόμενου αρχείου +Comment[es]=Transferencia de archivo entrante +Comment[fi]=Saapuva tiedostonsiirto +Comment[fr]=Transfert de fichier entrant +Comment[gl]=Transferencia de ficheiros entrante +Comment[hu]=Bejövő fájlátvitel +Comment[it]=Trasferimento di file in arrivo +Comment[kk]=Кіріс файл тасымалы +Comment[lt]=Gaunamo failo perdavimas +Comment[mr]=आत येणारी फाईल बदली +Comment[nb]=Innkommende filoverføring +Comment[nl]=Inkomende bestandsoverdracht +Comment[pa]=ਆ ਰਹੀ ਫਾਇਲ ਟਰਾਂਸਫਰ +Comment[pl]=Przychodzące przesyłanie pliku +Comment[pt]=Transferência de ficheiro recebida +Comment[pt_BR]=Transferência de arquivo recebida +Comment[ro]=Transfer de fișiere de intrare +Comment[ru]=Передача входящего файла +Comment[sk]=Prichádzajúci prenos súboru +Comment[sl]=Dohodni prenos datoteke +Comment[sr]=Долазни пренос фајла +Comment[sr@ijekavian]=Долазни пренос фајла +Comment[sr@ijekavianlatin]=Dolazni prenos fajla +Comment[sr@latin]=Dolazni prenos fajla +Comment[sv]=Inkommande filöverföring +Comment[tr]=Gelen dosya aktarımı +Comment[uk]=Вхідне перенесення файла +Comment[x-test]=xxIncoming file transferxx +Comment[zh_TW]=進來的檔案傳輸 +Action=Popup diff --git a/bluedevil/src/daemon/CMakeLists.txt b/bluedevil/src/daemon/CMakeLists.txt new file mode 100644 index 00000000..ae0959ba --- /dev/null +++ b/bluedevil/src/daemon/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(kded) +add_subdirectory(helpers) diff --git a/bluedevil/src/daemon/helpers/CMakeLists.txt b/bluedevil/src/daemon/helpers/CMakeLists.txt new file mode 100644 index 00000000..ca4d0fd9 --- /dev/null +++ b/bluedevil/src/daemon/helpers/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(confirmmodechange) +add_subdirectory(requestconfirmation) +add_subdirectory(requestpin) +add_subdirectory(authorize) diff --git a/bluedevil/src/daemon/helpers/authorize/CMakeLists.txt b/bluedevil/src/daemon/helpers/authorize/CMakeLists.txt new file mode 100644 index 00000000..d307c9b6 --- /dev/null +++ b/bluedevil/src/daemon/helpers/authorize/CMakeLists.txt @@ -0,0 +1,12 @@ +set(authorize_SRCS + main.cpp + authorize.cpp +) + +kde4_add_executable(bluedevil-authorize ${authorize_SRCS}) + +target_link_libraries(bluedevil-authorize + ${KDE4_KIO_LIBRARY} ${KDE4_KDEUI_LIBS} ${LibBlueDevil_LIBRARIES}) + +install(TARGETS bluedevil-authorize + DESTINATION ${LIBEXEC_INSTALL_DIR}) diff --git a/bluedevil/src/daemon/helpers/authorize/authorize.cpp b/bluedevil/src/daemon/helpers/authorize/authorize.cpp new file mode 100644 index 00000000..7566fff3 --- /dev/null +++ b/bluedevil/src/daemon/helpers/authorize/authorize.cpp @@ -0,0 +1,84 @@ +/*************************************************************************** + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "authorize.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include + +Authorize::Authorize() + : QObject() +{ + KNotification *notification = new KNotification("bluedevilAuthorize", + KNotification::Persistent, this); + + notification->setText(i18nc( + "Show a notification asking to authorize or deny access to this computer from Bluetooth. The %1 is the name of the bluetooth device", + "%1 is requesting access to this computer", qApp->arguments()[1]) + ); + + QStringList actions; + actions.append(i18nc("Button to trust a bluetooth remote device and authorize it to connect", "Trust and Authorize")); + actions.append(i18nc("Button to authorize a bluetooth remote device to connect ", "Authorize Only")); + actions.append(i18nc("Deny access to a remote bluetooth device", "Deny")); + + notification->setActions(actions); + + connect(notification, SIGNAL(action1Activated()),this, SLOT(trust())); + connect(notification, SIGNAL(action2Activated()),this, SLOT(authorize())); + connect(notification, SIGNAL(action3Activated()),this, SLOT(deny())); + connect(notification, SIGNAL(closed()), this, SLOT(deny())); + connect(notification, SIGNAL(ignored()), this, SLOT(deny())); + + notification->setPixmap(KIcon("preferences-system-bluetooth").pixmap(42, 42)); + + // We're using persistent notifications so we have to use our own timeout (10s) + QTimer::singleShot(10000, notification, SLOT(close())); + notification->sendEvent(); +} + +void Authorize::trust() +{ + qDebug() << "Trusted"; + BlueDevil::Manager::self()->usableAdapter()->deviceForUBI(qApp->arguments()[2])->setTrusted(true); + qApp->exit(0); +} + +void Authorize::authorize() +{ + qDebug() << "Accepted"; + qApp->exit(0); +} + +void Authorize::deny() +{ + qDebug() << "Rejected"; + qApp->exit(1); +} + diff --git a/bluedevil/src/daemon/helpers/authorize/authorize.h b/bluedevil/src/daemon/helpers/authorize/authorize.h new file mode 100644 index 00000000..d7a4eb19 --- /dev/null +++ b/bluedevil/src/daemon/helpers/authorize/authorize.h @@ -0,0 +1,63 @@ +/*************************************************************************** + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef AUTHORIZE_H +#define AUTHORIZE_H + +#include + +/** + * @short Small class which send a KNotificaton to know if the Bluetooth device is authorized or not + * A popup KNotification is send with 3 actions, trust accept and reject. + * Trust set the remote device as trusted (using libbluedevil remote device) and quits with 0 + * Authorize quits the app with 0 (which means authorized). + * Deny quits the app with 1 (which means denied) + * @internal + */ +class Authorize + : public QObject +{ + Q_OBJECT + +public: + /** + * Launch the KNotification which the respective actions, also makes the needed connection + * between those actions and the slots + */ + Authorize(); + +private Q_SLOTS: + /** + * Mark the remote device as trust and quit the application as success + */ + void trust(); + + /** + * Quits the application as success + */ + void authorize(); + + /** + * Quits the application as error + */ + void deny(); +}; +#endif diff --git a/bluedevil/src/daemon/helpers/authorize/main.cpp b/bluedevil/src/daemon/helpers/authorize/main.cpp new file mode 100644 index 00000000..3f889088 --- /dev/null +++ b/bluedevil/src/daemon/helpers/authorize/main.cpp @@ -0,0 +1,41 @@ +/************************************************************************************* + * Copyright (C) 2010-2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + + +#include "authorize.h" + +#include + +#include +#include +#include +#include +#include + +static const KLocalizedString description = ki18n("KDE Bluetooth System"); + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + KComponentData data("bluedevil", "bluedevilauthorizehelper"); + KGlobal::setActiveComponent(data); + Authorize auth; + return app.exec(); +} diff --git a/bluedevil/src/daemon/helpers/confirmmodechange/CMakeLists.txt b/bluedevil/src/daemon/helpers/confirmmodechange/CMakeLists.txt new file mode 100644 index 00000000..2ea391b4 --- /dev/null +++ b/bluedevil/src/daemon/helpers/confirmmodechange/CMakeLists.txt @@ -0,0 +1,12 @@ +set(confirmmodechange_SRCS + main.cpp + confirmmodechange.cpp +) + +kde4_add_executable(bluedevil-confirmmodechange ${confirmmodechange_SRCS}) + +target_link_libraries(bluedevil-confirmmodechange + ${KDE4_KIO_LIBRARY} ${KDE4_KDEUI_LIBS}) + +install(TARGETS bluedevil-confirmmodechange + DESTINATION ${LIBEXEC_INSTALL_DIR}) diff --git a/bluedevil/src/daemon/helpers/confirmmodechange/confirmmodechange.cpp b/bluedevil/src/daemon/helpers/confirmmodechange/confirmmodechange.cpp new file mode 100644 index 00000000..4ce9d8fb --- /dev/null +++ b/bluedevil/src/daemon/helpers/confirmmodechange/confirmmodechange.cpp @@ -0,0 +1,72 @@ +/*************************************************************************** + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "confirmmodechange.h" + +#include +#include +#include + +#include +#include +#include +#include + +ConfirmModeChange::ConfirmModeChange() + : QObject() +{ + KNotification *notification = new KNotification("bluedevilConfirmModechange", + KNotification::Persistent, this); + + notification->setText(i18nc( + "Showed in a notification when the Bluetooth mode is going to be changed (for example to flight mode), the %1 is the name of the mode", + "Change Bluetooth mode to '%1'?", qApp->arguments()[1]) + ); + + QStringList actions; + actions.append(i18nc("Confirm the bluetooth mode change, shown in a notification button", "Confirm")); + actions.append(i18nc("Deny the bluetooth mdoe change, shown in a notification", "Deny")); + + notification->setActions(actions); + + connect(notification, SIGNAL(action1Activated()),this, SLOT(confirm())); + connect(notification, SIGNAL(action2Activated()),this, SLOT(deny())); + connect(notification, SIGNAL(closed()), this, SLOT(deny())); + connect(notification, SIGNAL(ignored()), this, SLOT(deny())); + + // We're using persistent notifications so we have to use our own timeout (10s) + QTimer::singleShot(10000, notification, SLOT(close())); + notification->setPixmap(KIcon("preferences-system-bluetooth").pixmap(42, 42)); + notification->sendEvent(); +} + +void ConfirmModeChange::confirm() +{ + qDebug() << "confirmed"; + qApp->exit(0); +} + +void ConfirmModeChange::deny() +{ + qDebug() << "Denied"; + qApp->exit(1); +} + diff --git a/bluedevil/src/daemon/helpers/confirmmodechange/confirmmodechange.h b/bluedevil/src/daemon/helpers/confirmmodechange/confirmmodechange.h new file mode 100644 index 00000000..01bb2778 --- /dev/null +++ b/bluedevil/src/daemon/helpers/confirmmodechange/confirmmodechange.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef AUTHORIZE_H +#define AUTHORIZE_H + +#include + +/** + * @short Small class which send a KNotificaton to know if the mode change is authorized or not + * A popup KNotification is send with 3 actions, trust accept and reject. + * Trust set the remote device as trusted (using libbluedevil remote device) and quits with 0 + * Authorize quits the app with 0 (which means authorized). + * Deny quits the app with 1 (which means denied) + * @internal + */ +class ConfirmModeChange + : public QObject +{ + Q_OBJECT + +public: + /** + * Launch the KNotification which the respective actions, also makes the needed connection + * between those actions and the slots + */ + ConfirmModeChange(); + +private Q_SLOTS: + /** + * Quits the application as success + */ + void confirm(); + + /** + * Quits the application as error + */ + void deny(); +}; +#endif diff --git a/bluedevil/src/daemon/helpers/confirmmodechange/main.cpp b/bluedevil/src/daemon/helpers/confirmmodechange/main.cpp new file mode 100644 index 00000000..95de4f71 --- /dev/null +++ b/bluedevil/src/daemon/helpers/confirmmodechange/main.cpp @@ -0,0 +1,41 @@ +/************************************************************************************* + * Copyright (C) 2010-2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + + +#include "confirmmodechange.h" + +#include + +#include +#include +#include +#include + +static const KLocalizedString description = ki18n("KDE Bluetooth System"); + +int main(int argc, char *argv[]) +{ + QApplication app(argc,argv); + KComponentData data("bluedevil", "bluedevilconfirmmodechangehelper"); + KGlobal::setActiveComponent(data); + ConfirmModeChange confirmMode; + + return app.exec(); +} diff --git a/bluedevil/src/daemon/helpers/requestconfirmation/CMakeLists.txt b/bluedevil/src/daemon/helpers/requestconfirmation/CMakeLists.txt new file mode 100644 index 00000000..dafa25a9 --- /dev/null +++ b/bluedevil/src/daemon/helpers/requestconfirmation/CMakeLists.txt @@ -0,0 +1,12 @@ +set(requestconfirmation_SRCS + main.cpp + requestconfirmation.cpp +) + +kde4_add_executable(bluedevil-requestconfirmation ${requestconfirmation_SRCS}) + +target_link_libraries(bluedevil-requestconfirmation + ${KDE4_KIO_LIBRARY} ${KDE4_KDEUI_LIBS}) + +install(TARGETS bluedevil-requestconfirmation + DESTINATION ${LIBEXEC_INSTALL_DIR}) diff --git a/bluedevil/src/daemon/helpers/requestconfirmation/main.cpp b/bluedevil/src/daemon/helpers/requestconfirmation/main.cpp new file mode 100644 index 00000000..d2dca876 --- /dev/null +++ b/bluedevil/src/daemon/helpers/requestconfirmation/main.cpp @@ -0,0 +1,40 @@ +/************************************************************************************* + * Copyright (C) 2010-2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "requestconfirmation.h" + +#include + +#include +#include +#include +#include + +static const KLocalizedString description = ki18n("KDE Bluetooth System"); + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + KComponentData data("bluedevil", "bluedevilrequestconfirmationhelper"); + KGlobal::setActiveComponent(data); + RequestConfirmation confirmation; + + return app.exec(); +} diff --git a/bluedevil/src/daemon/helpers/requestconfirmation/requestconfirmation.cpp b/bluedevil/src/daemon/helpers/requestconfirmation/requestconfirmation.cpp new file mode 100644 index 00000000..8cb1d701 --- /dev/null +++ b/bluedevil/src/daemon/helpers/requestconfirmation/requestconfirmation.cpp @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "requestconfirmation.h" + +#include +#include + +#include +#include +#include +#include + +RequestConfirmation::RequestConfirmation() : QObject() +{ + KNotification *notification = new KNotification("bluedevilRequestConfirmation", + KNotification::Persistent, this); + + notification->setText(i18nc( + "The text is shown in a notification to know if the PIN is correct, %1 is the remote bluetooth device and %2 is the pin", + "%1 is asking if the PIN is correct: %2", qApp->arguments()[1], qApp->arguments()[2]) + ); + + QStringList actions; + actions.append(i18nc("Notification button to know if the pin is correct or not", "PIN correct")); + actions.append(i18nc("Notification button to say that the PIN is wrong", "PIN incorrect")); + + notification->setActions(actions); + + connect(notification, SIGNAL(action1Activated()),this, SLOT(pinCorrect())); + connect(notification, SIGNAL(action2Activated()),this, SLOT(pinWrong())); + connect(notification, SIGNAL(closed()), this, SLOT(pinWrong())); + connect(notification, SIGNAL(ignored()), this, SLOT(pinWrong())); + + //We're using persistent notifications so we have to use our own timeout (10s) + QTimer::singleShot(10000, notification, SLOT(close())); + notification->setPixmap(KIcon("preferences-system-bluetooth").pixmap(42,42)); + notification->sendEvent(); +} + +void RequestConfirmation::pinCorrect() +{ + qApp->exit(0); +} + +void RequestConfirmation::pinWrong() +{ + qApp->exit(1); +} + diff --git a/bluedevil/src/daemon/helpers/requestconfirmation/requestconfirmation.h b/bluedevil/src/daemon/helpers/requestconfirmation/requestconfirmation.h new file mode 100644 index 00000000..cc6af3f4 --- /dev/null +++ b/bluedevil/src/daemon/helpers/requestconfirmation/requestconfirmation.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef AUTHORIZE_H +#define AUTHORIZE_H + +#include + +/** + * @short Small class which send a KNotificaton to know if the Bluetooth device is authorized or not + * A popup KNotification is send with 3 actions, trust accept and reject. + * Trust set the remote device as trusted (using libbluedevil remote device) and quits with 0 + * Authorize quits the app with 0 (which means authorized). + * Deny quits the app with 1 (which means denied) + * @internal + */ +class RequestConfirmation + : public QObject +{ + Q_OBJECT + +public: + /** + * Launch the KNotification which the respective actions, also makes the needed connection + * between those actions and the slots + */ + RequestConfirmation(); + +private Q_SLOTS: + /** + * Quits the application as success + */ + void pinCorrect(); + + /** + * Quits the application as error + */ + void pinWrong(); +}; +#endif diff --git a/bluedevil/src/daemon/helpers/requestpin/CMakeLists.txt b/bluedevil/src/daemon/helpers/requestpin/CMakeLists.txt new file mode 100644 index 00000000..4c4a981f --- /dev/null +++ b/bluedevil/src/daemon/helpers/requestpin/CMakeLists.txt @@ -0,0 +1,15 @@ +set(requestpin_SRCS + main.cpp + requestpin.cpp +) + +kde4_add_ui_files(requestpin_UI + dialogWidget.ui) + +kde4_add_executable(bluedevil-requestpin ${requestpin_SRCS} ${requestpin_UI}) + +target_link_libraries(bluedevil-requestpin + ${KDE4_KIO_LIBRARY} ${KDE4_KDEUI_LIBS}) + +install(TARGETS bluedevil-requestpin + DESTINATION ${LIBEXEC_INSTALL_DIR}) diff --git a/bluedevil/src/daemon/helpers/requestpin/dialogWidget.ui b/bluedevil/src/daemon/helpers/requestpin/dialogWidget.ui new file mode 100644 index 00000000..6f6c5b90 --- /dev/null +++ b/bluedevil/src/daemon/helpers/requestpin/dialogWidget.ui @@ -0,0 +1,92 @@ + + + dialogWidget + + + + 0 + 0 + 273 + 95 + + + + + + 0 + 0 + 271 + 91 + + + + + + + + + + + + + + + + + + + + PIN: + + + + + + + + + 99999999; + + + 8 + + + false + + + true + + + + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + +
diff --git a/bluedevil/src/daemon/helpers/requestpin/main.cpp b/bluedevil/src/daemon/helpers/requestpin/main.cpp new file mode 100644 index 00000000..e66da816 --- /dev/null +++ b/bluedevil/src/daemon/helpers/requestpin/main.cpp @@ -0,0 +1,40 @@ +/************************************************************************************* + * Copyright (C) 2010-2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "requestpin.h" + +#include + +#include +#include +#include +#include + +static const KLocalizedString description = ki18n("KDE Bluetooth System"); + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + KComponentData data("bluedevil", "bluedevilrequestpinhelper"); + KGlobal::setActiveComponent(data); + RequestPin request; + + return app.exec(); +} diff --git a/bluedevil/src/daemon/helpers/requestpin/requestpin.cpp b/bluedevil/src/daemon/helpers/requestpin/requestpin.cpp new file mode 100644 index 00000000..96648f53 --- /dev/null +++ b/bluedevil/src/daemon/helpers/requestpin/requestpin.cpp @@ -0,0 +1,116 @@ +/*************************************************************************** + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "requestpin.h" +#include "ui_dialogWidget.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace std; +RequestPin::RequestPin() : QObject() +{ + m_notification = new KNotification("bluedevilRequestPin", + KNotification::Persistent, this); + + m_notification->setText(i18nc( + "Shown in a notification to announce that a PIN is needed to accomplish a pair action, %1 is the name of the bluetooth device", + "PIN needed to pair with %1",qApp->arguments()[1]) + ); + + QStringList actions; + actions.append(i18nc( + "Notification button which once clicked, a dialog to introduce the PIN will be shown", + "Introduce PIN") + ); + + m_notification->setActions(actions); + + connect(m_notification, SIGNAL(action1Activated()),this, SLOT(introducePin())); + connect(m_notification, SIGNAL(closed()), this, SLOT(quit())); + connect(m_notification, SIGNAL(ignored()), this, SLOT(quit())); + + //We're using persistent notifications so we have to use our own timeout (10s) + QTimer::singleShot(10000, m_notification, SLOT(close())); + m_notification->setPixmap(KIcon("preferences-system-bluetooth").pixmap(42,42)); + m_notification->sendEvent(); +} + +void RequestPin::introducePin() +{ + disconnect(m_notification, SIGNAL(closed()), this, SLOT(quit())); + disconnect(m_notification, SIGNAL(ignored()), this, SLOT(quit())); + + KIcon icon("preferences-system-bluetooth"); + + Ui::dialogWidget *dialogWidget = new Ui::dialogWidget; + QWidget *mainWidget = new QWidget(); + dialogWidget->setupUi(mainWidget); + dialogWidget->descLabel->setText(i18nc( + "Shown in a dialog which asks to introduce a PIN that will be used to pair a Bluetooth device, %1 is the name of the Bluetooth device", + "In order to pair this computer with %1, you have to enter a PIN. Please do it below.", + qApp->arguments()[1]) + ); + dialogWidget->pixmap->setPixmap(icon.pixmap(64,64)); + + KDialog *dialog = new KDialog(); + dialog->setMainWidget(mainWidget); + + dialog->setCaption(i18nc( + "Shown in the caption of a dialog where the user introduce the PIN", + "Introduce PIN" + )); + + QObject::connect(dialogWidget->pin, SIGNAL(returnPressed()), + dialog, SLOT(accept())); + + dialog->setButtons(KDialog::Ok | KDialog::Cancel); + dialog->setMinimumWidth(300); + dialog->setMinimumHeight(150); + dialog->setMaximumWidth(300); + dialog->setMaximumHeight(150); + + dialogWidget->pin->setFocus(Qt::ActiveWindowFocusReason); + + if (dialog->exec()) { + cout << dialogWidget->pin->text().toLatin1().data(); + flush(cout); + qApp->exit(0); + return; + } + + delete dialog; + qApp->exit(1); +} + +void RequestPin::quit() +{ + qApp->exit(1); +} diff --git a/bluedevil/src/daemon/helpers/requestpin/requestpin.h b/bluedevil/src/daemon/helpers/requestpin/requestpin.h new file mode 100644 index 00000000..32c02b34 --- /dev/null +++ b/bluedevil/src/daemon/helpers/requestpin/requestpin.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef REQUESTPIN_H +#define REQUESTPIN_H + +#include +class KNotification; + +/** + * @short Small class which send a KNotificaton to know if the Bluetooth device is authorized or not + * A popup KNotification is send with 3 actions, trust accept and reject. + * Trust set the remote device as trusted (using libbluedevil remote device) and quits with 0 + * Authorize quits the app with 0 (which means authorized). + * Deny quits the app with 1 (which means denied) + * @internal + */ +class RequestPin + : public QObject +{ + Q_OBJECT + +public: + /** + * Launch the KNotification which the respective actions, also makes the needed connection + * between those actions and the slots + */ + RequestPin(); + +private Q_SLOTS: + /** + * Show a dialog with widgetDialog as mainWidget where the user will write the PIN code. + * If the user click the button 1, the app print the PIN and quits the app as success + * In case of button 2, the app is quit as error + */ + void introducePin(); + + /** + * If the notification is ignored or closed, then we have to quit the helper + */ + void quit(); + +private: + KNotification *m_notification; +}; +#endif //REQUESTPIN_H diff --git a/bluedevil/src/daemon/kded/BlueDevilDaemon.cpp b/bluedevil/src/daemon/kded/BlueDevilDaemon.cpp new file mode 100644 index 00000000..32da7008 --- /dev/null +++ b/bluedevil/src/daemon/kded/BlueDevilDaemon.cpp @@ -0,0 +1,330 @@ +/************************************************************************************* + * Copyright (C) 2010-2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "BlueDevilDaemon.h" +#include "bluezagent.h" +#include "filereceiversettings.h" +#include "version.h" + +#include "filereceiver/filereceiver.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace BlueDevil; + +K_PLUGIN_FACTORY(BlueDevilFactory, + registerPlugin();) +K_EXPORT_PLUGIN(BlueDevilFactory("bluedevildaemon", "bluedevil")) + +Q_DECLARE_METATYPE(DeviceInfo) +Q_DECLARE_METATYPE(QMapDeviceInfo) + +struct BlueDevilDaemon::Private +{ + enum Status { + Online = 0, + Offline + } m_status; + + BluezAgent *m_bluezAgent; + KFilePlacesModel *m_placesModel; + Adapter *m_adapter; + QDBusServiceWatcher *m_monolithicWatcher; + FileReceiver *m_fileReceiver; + QList m_discovered; + QTimer m_timer; + KComponentData m_componentData; +}; + +BlueDevilDaemon::BlueDevilDaemon(QObject *parent, const QList&) + : KDEDModule(parent) + , d(new Private) +{ + qDBusRegisterMetaType (); + qDBusRegisterMetaType (); + + d->m_bluezAgent = 0; + d->m_adapter = 0; + d->m_placesModel = 0; + d->m_fileReceiver = 0; + d->m_monolithicWatcher = new QDBusServiceWatcher("org.kde.bluedevilmonolithic" + , QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration, this); + d->m_timer.setInterval(20000); + d->m_timer.setSingleShot(true); + + KAboutData aboutData( + "bluedevildaemon", + "bluedevil", + ki18n("Bluetooth Daemon"), + bluedevil_version, + ki18n("Bluetooth Daemon"), + KAboutData::License_GPL, + ki18n("(c) 2010, UFO Coders") + ); + + aboutData.addAuthor(ki18n("Alejandro Fiestas Olivares"), ki18n("Maintainer"), "afiestas@kde.org", + "http://www.afiestas.org"); + + aboutData.addAuthor(ki18n("Eduardo Robles Elvira"), ki18n("Maintainer"), "edulix@gmail.com", + "http://blog.edulix.es"); + + aboutData.setProgramIconName("preferences-system-bluetooth"); + d->m_componentData = KComponentData(aboutData); + connect(d->m_monolithicWatcher, SIGNAL(serviceUnregistered(const QString &)), SLOT(monolithicFinished(const QString &))); + + connect(Manager::self(), SIGNAL(usableAdapterChanged(Adapter*)), + this, SLOT(usableAdapterChanged(Adapter*))); + + connect(Manager::self()->usableAdapter(), SIGNAL(deviceFound(Device*)), this, SLOT(deviceFound(Device*))); + connect(&d->m_timer, SIGNAL(timeout()), Manager::self()->usableAdapter(), SLOT(stopDiscovery())); + + d->m_status = Private::Offline; + if (Manager::self()->usableAdapter()) { + onlineMode(); + } +} + +BlueDevilDaemon::~BlueDevilDaemon() +{ + if (d->m_status == Private::Online) { + offlineMode(); + } + + delete d; +} + +bool BlueDevilDaemon::isOnline() +{ + if (d->m_status == Private::Offline) { + return false; + } + return true; +} + +QMapDeviceInfo BlueDevilDaemon::knownDevices() +{ + QMapDeviceInfo devices; + + QList list = Manager::self()->usableAdapter()->devices(); + kDebug(dblue()) << "List: " << list.length(); + DeviceInfo info; + Q_FOREACH(Device *const device, list) { + info["name"] = device->friendlyName(); + info["icon"] = device->icon(); + info["address"] = device->address(); + info["UUIDs"] = device->UUIDs().join(","); + devices[device->address()] = info; + } + + if (!d->m_timer.isActive()) { + kDebug(dblue()) << "Start Discovery"; + Manager::self()->usableAdapter()->startStableDiscovery(); + d->m_discovered.clear(); + d->m_timer.start(); + } + + Q_FOREACH(const DeviceInfo& info, d->m_discovered) { + if (!devices.contains(info["address"])) { + devices[info["address"]] = info; + } + } + return devices; +} + +void BlueDevilDaemon::stopDiscovering() +{ + kDebug(dblue()) << "Stopping discovering"; + d->m_timer.stop(); + Manager::self()->usableAdapter()->stopDiscovery(); +} + +void BlueDevilDaemon::executeMonolithic() +{ + kDebug(dblue()); + + QProcess process; + if (!process.startDetached("bluedevil-monolithic")) { + kError() << "Could not start bluedevil-monolithic"; + } +} + +void BlueDevilDaemon::killMonolithic() +{ + kDebug(dblue()); + QDBusMessage msg = QDBusMessage::createMethodCall( + "org.kde.bluedevilmonolithic", + "/MainApplication", + "org.kde.KApplication", + "quit" + ); + QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(msg); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending); + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(monolithicQuit(QDBusPendingCallWatcher*))); +} + +void BlueDevilDaemon::onlineMode() +{ + kDebug(dblue()); + if (d->m_status == Private::Online) { + kDebug(dblue()) << "Already in onlineMode"; + return; + } + + d->m_bluezAgent = new BluezAgent(new QObject()); + connect(d->m_bluezAgent, SIGNAL(agentReleased()), this, SLOT(agentReleased())); + + d->m_adapter = Manager::self()->usableAdapter(); + + FileReceiverSettings::self()->readConfig(); + if (!d->m_fileReceiver && FileReceiverSettings::self()->enabled()) { + d->m_fileReceiver = new FileReceiver(d->m_componentData, this); + } + if (d->m_fileReceiver && !FileReceiverSettings::self()->enabled()) { + kDebug(dblue()) << "Stoppping server"; + delete d->m_fileReceiver; + d->m_fileReceiver = 0; + } + + if (!d->m_placesModel) { + d->m_placesModel = new KFilePlacesModel(); + } + + //Just in case kded4 was killed or crashed + QModelIndex index = d->m_placesModel->closestItem(KUrl("bluetooth:/")); + while (index.row() != -1) { + d->m_placesModel->removePlace(index); + index = d->m_placesModel->closestItem(KUrl("bluetooth:/")); + } + + d->m_placesModel->addPlace("Bluetooth", KUrl("bluetooth:/"), "preferences-system-bluetooth"); + + executeMonolithic(); + + d->m_status = Private::Online; +} + +void BlueDevilDaemon::monolithicFinished(const QString &) +{ + kDebug(dblue()); + + if (d->m_status == Private::Online) { + executeMonolithic(); + } +} + +void BlueDevilDaemon::offlineMode() +{ + kDebug(dblue()) << "Offline mode"; + if (d->m_status == Private::Offline) { + kDebug(dblue()) << "Already in offlineMode"; + return; + } + + d->m_adapter = 0; + + if (d->m_bluezAgent) { + delete d->m_bluezAgent->parent(); // we meed to delete the parent for not leaking it + d->m_bluezAgent = 0; + } + + if (d->m_fileReceiver) { + kDebug(dblue()) << "Stoppping server"; + delete d->m_fileReceiver; + d->m_fileReceiver = 0; + } + + //Just to be sure that online was called + if (d->m_placesModel) { + QModelIndex index = d->m_placesModel->closestItem(KUrl("bluetooth:/")); + d->m_placesModel->removePlace(index); + } + + killMonolithic(); + d->m_status = Private::Offline; +} + +/* + * The agent is released by another agents, for example if an user wants to use + * blueman agent in kde, we've to respect the user decision here, so ATM until we have + * the KCM, we should just delete the agent and be quiet + */ +void BlueDevilDaemon::agentReleased() +{ + //TODO think what to do +} + +void BlueDevilDaemon::usableAdapterChanged(Adapter *adapter) +{ + //if we have an adapter, remove it and offline the KDED for a moment + if (d->m_adapter) { + offlineMode(); + } + + //If the given adapter is not NULL, then set onlineMode again + if (adapter) { + d->m_adapter = adapter; + onlineMode(); + } +} + +void BlueDevilDaemon::deviceFound(Device *device) +{ + kDebug(dblue()) << "DeviceFound: " << device->name(); + d->m_discovered.append(deviceToInfo(device)); + org::kde::KDirNotify::emitFilesAdded("bluetooth:/"); +} + +void BlueDevilDaemon::monolithicQuit(QDBusPendingCallWatcher* watcher) +{ + kDebug(dblue()); + QDBusPendingReply reply = *watcher; + if (reply.isError()) { + qDebug() << "Error response: " << reply.error().message(); + killMonolithic(); + } +} + +DeviceInfo BlueDevilDaemon::deviceToInfo(Device *const device) const +{ + DeviceInfo info; + info["name"] = device->friendlyName(); + info["icon"] = device->icon(); + info["address"] = device->address(); + info["discovered"] = "true"; + info["UUIDs"] = device->UUIDs().join(","); + + return info; +} + +extern int dblue() { static int s_area = KDebug::registerArea("BlueDaemon", false); return s_area; } \ No newline at end of file diff --git a/bluedevil/src/daemon/kded/BlueDevilDaemon.h b/bluedevil/src/daemon/kded/BlueDevilDaemon.h new file mode 100644 index 00000000..be52e9fb --- /dev/null +++ b/bluedevil/src/daemon/kded/BlueDevilDaemon.h @@ -0,0 +1,106 @@ +/*************************************************************************** + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef BLUEDEVILDAEMON_H +#define BLUEDEVILDAEMON_H + +#include +#include +#include + +typedef QMap DeviceInfo; +typedef QMap QMapDeviceInfo; + +class QDBusPendingCallWatcher; +namespace BlueDevil { + class Adapter; + class Device; +}; +using namespace BlueDevil; + +class KDE_EXPORT BlueDevilDaemon + : public KDEDModule +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.BlueDevil") + +public: + /** + * Stablish basics connections with libbluedevil signals and calls online if interfaces are availables + */ + BlueDevilDaemon(QObject *parent, const QList&); + virtual ~BlueDevilDaemon(); + +public Q_SLOTS: + Q_SCRIPTABLE bool isOnline(); + + /** + * This slot will return a list of devices made of: configured and discovered devices. + * Going deeper, the first time that this slot is called a discovery of X seconds will start + * Then if this slot is consulted again it will return configured and discovered device. Once + * the discovery ends it won't start a new discovery until N seconds pass. + */ + Q_SCRIPTABLE QMapDeviceInfo knownDevices(); + + Q_SCRIPTABLE void stopDiscovering(); + +private: + /** + * Called by constructor or eventually by adapterAdded initialize all the helpers + * @see helpers + */ + void onlineMode(); + + /** + * Called eventually adapterRemoved shutdown all the helpers + * @see helpers + */ + void offlineMode(); + +private Q_SLOTS: + /** + * Called when the default adapter changes, re-initialize the kded with the new + * default adapter + */ + void usableAdapterChanged(Adapter *adapter); + + /** + * When the agent is released this is called to unload it + */ + void agentReleased(); + + void deviceFound(Device*); + void monolithicQuit(QDBusPendingCallWatcher* watcher); + void monolithicFinished(const QString &); + +private: + void executeMonolithic(); + void killMonolithic(); + + DeviceInfo deviceToInfo (Device *const device) const; + +private: + struct Private; + Private *d; +}; + +extern int dblue(); +#endif /*BLUEDEVILDAEMON_H*/ diff --git a/bluedevil/src/daemon/kded/CMakeLists.txt b/bluedevil/src/daemon/kded/CMakeLists.txt new file mode 100644 index 00000000..c3960c09 --- /dev/null +++ b/bluedevil/src/daemon/kded/CMakeLists.txt @@ -0,0 +1,39 @@ +set(kded_bluedevil_SRCS + BlueDevilDaemon.cpp + bluezagent.cpp + filereceiver/obexagent.cpp + filereceiver/filereceiver.cpp + filereceiver/receivefilejob.cpp +) + +qt4_add_dbus_interface(kded_bluedevil_SRCS + filereceiver/org.bluez.obex.AgentManager1.xml + obex_agent_manager) + +qt4_add_dbus_interface(kded_bluedevil_SRCS + filereceiver/org.bluez.obex.Transfer1.xml + obex_transfer) + +qt4_add_dbus_interface(kded_bluedevil_SRCS + filereceiver/org.bluez.obex.Session1.xml + obex_session) + +qt4_add_dbus_interface(kded_bluedevil_SRCS + filereceiver/org.freedesktop.DBus.Properties.xml + dbus_properties) + +kde4_add_kcfg_files(kded_bluedevil_SRCS ../../settings/filereceiversettings.kcfgc) + +kde4_add_plugin(kded_bluedevil + ${kded_bluedevil_SRCS} +) + +target_link_libraries(kded_bluedevil + ${KDE4_KDECORE_LIBS} + ${KDE4_KDEUI_LIBS} + ${KDE4_KFILE_LIBS} + ${LibBlueDevil_LIBRARIES} +) + +install(TARGETS kded_bluedevil DESTINATION ${PLUGIN_INSTALL_DIR}) +install(FILES bluedevil.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kded) \ No newline at end of file diff --git a/bluedevil/src/daemon/kded/bluedevil.desktop b/bluedevil/src/daemon/kded/bluedevil.desktop new file mode 100644 index 00000000..f871a180 --- /dev/null +++ b/bluedevil/src/daemon/kded/bluedevil.desktop @@ -0,0 +1,82 @@ +[Desktop Entry] +Type=Service +Icon=preferences-system-power-management +X-KDE-ServiceTypes=KDEDModule +X-KDE-Library=bluedevil +X-KDE-DBus-ModuleName=bluedevil +X-KDE-Kded-autoload=true +X-KDE-Kded-load-on-demand=false +X-KDE-Kded-phase=1 + +Name=Bluetooth +Name[ca]=Bluetooth +Name[ca@valencia]=Bluetooth +Name[cs]=Bluetooth +Name[da]=Bluetooth +Name[de]=Bluetooth +Name[el]=Bluetooth +Name[es]=Bluetooth +Name[fi]=Bluetooth +Name[fr]=Bluetooth +Name[gl]=Bluetooth +Name[hu]=Bluetooth +Name[it]=Bluetooth +Name[kk]=Bluetooth +Name[lt]=Bluetooth +Name[mr]=ब्लूटूथ +Name[nb]=Blåtann +Name[nl]=Bluetooth +Name[pa]=ਬਲਿਊਟੁੱਥ +Name[pl]=Bluetooth +Name[pt]=Bluetooth +Name[pt_BR]=Bluetooth +Name[ro]=Bluetooth +Name[ru]=Bluetooth +Name[sk]=Bluetooth +Name[sl]=Bluetooth +Name[sr]=Блутут +Name[sr@ijekavian]=Блутут +Name[sr@ijekavianlatin]=Bluetooth +Name[sr@latin]=Bluetooth +Name[sv]=Blåtand +Name[tr]=Bluetooth +Name[ug]=كۆكچىش +Name[uk]=Bluetooth +Name[x-test]=xxBluetoothxx +Name[zh_TW]=藍牙 + +Comment=Handles Bluetooth events +Comment[ca]=Gestiona els esdeveniments del Bluetooth +Comment[ca@valencia]=Gestiona els esdeveniments del Bluetooth +Comment[cs]=Ovládá události Bluetooth +Comment[da]=Håndterer Bluetooth-hændelser +Comment[de]=Verarbeitung von Bluetooth-Ereignissen +Comment[el]=Χειρίζεται γεγονότα Bluetooth +Comment[es]=Maneja eventos de Bluetooth +Comment[fi]=Käsittelee Bluetooth-tapahtumia +Comment[fr]=Gère les évènements Bluetooth +Comment[gl]=Xestiona acontecementos de Bluetooth +Comment[hu]=Kezeli a Bluetooth eseményeket +Comment[it]=Gestisce eventi Bluetooth +Comment[kk]=Bluetooth оқиғаларын өңдеу +Comment[lt]=Valdo Bluetooth įvykius +Comment[mr]=ब्लूटूथ घटना हाताळतो +Comment[nb]=Håndterer Blåtann-hendelser +Comment[nl]=Behandelt bluetooth-gebeurtenissen +Comment[pa]=ਬਲਿਊਟੁੱਥ ਘਟਨਾਵਾਂ ਕੰਟਰੋਲ ਕਰਨ ਲਈ +Comment[pl]=Obsługuje zdarzenia Bluetooth +Comment[pt]=Lida com os eventos de Bluetooth +Comment[pt_BR]=Lida com os eventos de Bluetooth +Comment[ro]=Manipulează evenimente Bluetooth +Comment[ru]=Обработчик событий Bluetooth +Comment[sk]=Spracúva udalosti Bluetooth +Comment[sl]=Obravnava dogodke za Bluetooth +Comment[sr]=Рукује блутут догађајима +Comment[sr@ijekavian]=Рукује блутут догађајима +Comment[sr@ijekavianlatin]=Rukuje bluetooth događajima +Comment[sr@latin]=Rukuje bluetooth događajima +Comment[sv]=Hanterar Blåtandhändelser +Comment[tr]=Bluetooth olaylarını ele alır +Comment[uk]=Обробляє події Bluetooth +Comment[x-test]=xxHandles Bluetooth eventsxx +Comment[zh_TW]=處理藍牙事件 diff --git a/bluedevil/src/daemon/kded/bluezagent.cpp b/bluedevil/src/daemon/kded/bluezagent.cpp new file mode 100644 index 00000000..4b604379 --- /dev/null +++ b/bluedevil/src/daemon/kded/bluezagent.cpp @@ -0,0 +1,196 @@ +/*************************************************************************** + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "bluezagent.h" + +#include +#include +#include + +#include +#include +#include + +#define AGENT_PATH "/blueDevil_agent" + +BluezAgent::BluezAgent(QObject *parent) + : QDBusAbstractAdaptor(parent) +{ + if (!QDBusConnection::systemBus().registerObject(AGENT_PATH, parent)) { + qDebug() << "The dbus object can't be registered"; + return; + } + + BlueDevil::Manager::self()->registerAgent(AGENT_PATH,BlueDevil::Manager::DisplayYesNo); + BlueDevil::Manager::self()->requestDefaultAgent(AGENT_PATH); + + m_process = new QProcess(this); + + qDebug() << "Agent registered"; +} + +void BluezAgent::unregister() +{ + qDebug() << "Unregistering object"; + BlueDevil::Manager::self()->unregisterAgent(AGENT_PATH); + QDBusConnection::systemBus().unregisterObject(AGENT_PATH); + parent()->deleteLater(); +} + +void BluezAgent::Release() +{ + qDebug() << "Agent Release"; + emit agentReleased(); +} + +void BluezAgent::AuthorizeService(const QDBusObjectPath &device, const QString& uuid, const QDBusMessage &msg) +{ + Q_UNUSED(uuid) + qDebug() << "Authorize called"; + + m_msg = msg; + m_msg.setDelayedReply(true); + m_currentHelper = "Authorize"; + + QStringList list; + list.append(deviceName(device.path())); + list.append(device.path()); + + connect(m_process, SIGNAL(finished(int)), this, SLOT(processClosedBool(int))); + m_process->start(KStandardDirs::findExe("bluedevil-authorize"), list); +} + +QString BluezAgent::RequestPinCode(const QDBusObjectPath &device, const QDBusMessage &msg) +{ + qDebug() << "AGENT-RequestPinCode " << device.path(); + m_msg = msg; + m_msg.setDelayedReply(true); + + QStringList list(deviceName(device.path())); + connect(m_process, SIGNAL(finished(int)), this, SLOT(processClosedPin(int))); + m_process->start(KStandardDirs::findExe("bluedevil-requestpin"), list); + + return QString(); +} + +quint32 BluezAgent::RequestPasskey(const QDBusObjectPath &device, const QDBusMessage &msg) +{ + qDebug() << "AGENT-RequestPasskey " << device.path(); + + m_msg = msg; + m_msg.setDelayedReply(true); + + QStringList list(deviceName(device.path())); + connect(m_process, SIGNAL(finished(int)), this, SLOT(processClosedPasskey(int))); + m_process->start(KStandardDirs::findExe("bluedevil-requestpin"), list); + + return 0; +} + +void BluezAgent::DisplayPasskey(const QDBusObjectPath &device, quint32 passkey) +{ + qDebug() << "AGENT-DisplayPasskey " << device.path() << ", " << QString::number(passkey); +} + +void BluezAgent::RequestConfirmation(const QDBusObjectPath &device, quint32 passkey, const QDBusMessage &msg) +{ + qDebug() << "AGENT-RequestConfirmation " << device.path() << ", " << QString::number(passkey); + + m_msg = msg; + m_msg.setDelayedReply(true); + m_currentHelper = "RequestConfirmation"; + + QStringList list; + list.append(deviceName(device.path())); + list.append(QString("%1").arg(passkey, 6, 10, QLatin1Char('0'))); + + connect(m_process, SIGNAL(finished(int)), this, SLOT(processClosedBool(int))); + m_process->start(KStandardDirs::findExe("bluedevil-requestconfirmation"), list); +} + +void BluezAgent::Cancel() +{ + qDebug() << "AGENT-Cancel"; +} + +void BluezAgent::processClosedBool(int exitCode) +{ + qDebug() << "ProcessClosed: " << exitCode; + disconnect(m_process, SIGNAL(finished(int)), this, SLOT(processClosedBool(int))); + + if (!exitCode) { + qDebug() << "Sending empty reply"; + QDBusConnection::systemBus().send(m_msg.createReply()); + return; + } + + qDebug() << "Sending error"; + sendBluezError(m_currentHelper, m_msg); +} + +void BluezAgent::processClosedPin(int exitCode) +{ + qDebug() << "ProcessClosedPin: " << exitCode; + disconnect(m_process, SIGNAL(finished(int)), this, SLOT(processClosedPin(int))); + + if (!exitCode) { + QVariant arg = QVariant::fromValue(m_process->readAllStandardOutput()); + QDBusMessage reply = m_msg.createReply(arg); + QDBusConnection::systemBus().send(reply); + return; + } + + QDBusMessage error = m_msg.createErrorReply("org.bluez.Error.Canceled", "Pincode request failed"); + QDBusConnection::systemBus().send(error); +} + +void BluezAgent::processClosedPasskey(int exitCode) +{ + disconnect(m_process, SIGNAL(finished(int)), this, SLOT(processClosedPasskey(int))); + + if (!exitCode) { + QVariant arg = QVariant::fromValue(m_process->readAllStandardOutput().toInt()); + QDBusMessage reply = m_msg.createReply(arg); + QDBusConnection::systemBus().send(reply); + return; + } + + QDBusMessage error = m_msg.createErrorReply("org.bluez.Error.Canceled", "Pincode request failed"); + QDBusConnection::systemBus().send(error); +} + +void BluezAgent::sendBluezError(const QString &helper, const QDBusMessage &msg) +{ + qDebug() << "Sending canceled msg to bluetooth" << helper; + QDBusMessage error = msg.createErrorReply("org.bluez.Error.Canceled", "Authorization canceled"); + QDBusConnection::systemBus().send(error); +} + +QString BluezAgent::deviceName(const QString& UBI) +{ + BlueDevil::Device *device = BlueDevil::Manager::self()->deviceForUBI(UBI); + if (!device || device->name().isEmpty()) { + return i18nc("User will see this as: Bluetooth device is asking if the pin is correct\ + It is mostly a fallback", "Bluetooth device"); + } + + return device->name(); +} \ No newline at end of file diff --git a/bluedevil/src/daemon/kded/bluezagent.h b/bluedevil/src/daemon/kded/bluezagent.h new file mode 100644 index 00000000..6c326887 --- /dev/null +++ b/bluedevil/src/daemon/kded/bluezagent.h @@ -0,0 +1,149 @@ +/*************************************************************************** + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + + +#ifndef BLUEZAGENT_H +#define BLUEZAGENT_H + +#include +#include +#include + +class QProcess; +namespace BlueDevil { + class Adapter; +}; + +/** + * @internal + * @short This class is only a delegate to be able to use agentlistener on a QThread (We can't inherit + * from 2 QObjects + * This class is only a delegate to be able to use agentlistener on a QThread (We can't inherit + * from 2 QObjects, so we had to create a new Class only to do the threading stuff + * @ref AgentListenerWorker + * @since 1.0 + */ +class BluezAgent + : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.bluez.Agent1") + +public: + /** + * Register the path and initialize the m_adapter + */ + BluezAgent(QObject *parent); + +public Q_SLOTS: + /** + * Called by bluez when another agent try to replace us as an agent + */ + void Release(); + + /** + * Called by bluez to ask for a device authoritation + */ + void AuthorizeService(const QDBusObjectPath &device, const QString& uuid, const QDBusMessage &msg); + + /** + * Called by bluez to ask for a PIN + */ + QString RequestPinCode(const QDBusObjectPath& device, const QDBusMessage& msg); + + /** + * Called by bluez to ask for a passkey, currently is a aslias of RequestPinCode + */ + quint32 RequestPasskey(const QDBusObjectPath &device, const QDBusMessage &msg); + + /** + * Called by bluez to display the passkey (Currently it's not implemented because we don't know + * what to do with it). + */ + void DisplayPasskey(const QDBusObjectPath &device, quint32 passkey); + + /** + * Called by bluez to ask for a request confirmation + */ + void RequestConfirmation(const QDBusObjectPath &device, quint32 passkey, const QDBusMessage &msg); + + /** + * Called by bluez to inform that a process has failed (for example when the pin is introduced + * too late and the device which ask for the pin is no longer listening). + * We do anything here, since is not needed + */ + void Cancel(); + + /** + * Slot for those calls that should return a Bool result + * + * This slot gets called when the helper process ends, and basically checks the exitCode + */ + void processClosedBool(int exitCode); + + /** + * Just like @processClosedBool but this instead returns a String (the PIN) + * + * This slot gets called when the RequestPin helper ends + */ + void processClosedPin(int exitCode); + + /** + * Just like @processClosedBool but this instead returns a quint32 (the PIN) + * + * This slot gets called when the RequestPasskey helper ends + */ + void processClosedPasskey(int exitCode); +Q_SIGNALS: + /** + * Emited to propagate the release call (so BlueDevil can decide what to do) + */ + void agentReleased(); + +public: + /** + * Called by agentListener just before delete. This is needed because ~QDBusAbstractAdaptor is + * not virtual + */ + void unregister(); + +private: + /** + * Unified method to return the bluez exception. + * @param helper Name of the helper + * @param msg The msg got from bluez + */ + void sendBluezError(const QString& helper, const QDBusMessage &msg); + + /** + * Returns the name of the device if it is registered on the bus + * + * @param UBI of the device + * @return the device->name() or "Bluetooth" if device doesn't exists; + */ + QString deviceName(const QString &UBI); + +private: + QProcess *m_process; + QDBusMessage m_msg; + QString m_currentHelper; +}; +#endif diff --git a/bluedevil/src/daemon/kded/filereceiver/filereceiver.cpp b/bluedevil/src/daemon/kded/filereceiver/filereceiver.cpp new file mode 100644 index 00000000..67db6aac --- /dev/null +++ b/bluedevil/src/daemon/kded/filereceiver/filereceiver.cpp @@ -0,0 +1,57 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "filereceiver.h" +#include "../BlueDevilDaemon.h" +#include "obexagent.h" +#include "obex_agent_manager.h" + +#include +#include +#include + +#include + +FileReceiver::FileReceiver(const KComponentData& componentData, QObject* parent) : QObject(parent) +{ + kDebug(dblue()); + qDBusRegisterMetaType(); + + new ObexAgent(componentData, this); + org::bluez::obex::AgentManager1 *agent = new org::bluez::obex::AgentManager1("org.bluez.obex", "/org/bluez/obex", QDBusConnection::sessionBus(), this); + + QDBusPendingReply r = agent->RegisterAgent(QDBusObjectPath("/BlueDevil_receiveAgent")); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(r, this); + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(agentRegistered(QDBusPendingCallWatcher*))); +} + +FileReceiver::~FileReceiver() +{ + +} + +void FileReceiver::agentRegistered(QDBusPendingCallWatcher* call) +{ + QDBusPendingReply r = *call; + kDebug(dblue()) << "Error: " << r.isError(); + if (r.isError()) { + kDebug(dblue()) << r.error().message(); + } + + call->deleteLater(); +} \ No newline at end of file diff --git a/bluedevil/src/daemon/kded/filereceiver/filereceiver.h b/bluedevil/src/daemon/kded/filereceiver/filereceiver.h new file mode 100644 index 00000000..9ac6b6f3 --- /dev/null +++ b/bluedevil/src/daemon/kded/filereceiver/filereceiver.h @@ -0,0 +1,37 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#ifndef FILE_RECEIVER_H +#define FILE_RECEIVER_H + +#include +#include + +class QDBusPendingCallWatcher; +class FileReceiver : public QObject +{ + Q_OBJECT + public: + explicit FileReceiver(const KComponentData &componentData, QObject* parent = 0); + virtual ~FileReceiver(); + + private Q_SLOTS: + void agentRegistered(QDBusPendingCallWatcher* call); +}; + +#endif //FILE_RECEIVER_H \ No newline at end of file diff --git a/bluedevil/src/daemon/kded/filereceiver/obexagent.cpp b/bluedevil/src/daemon/kded/filereceiver/obexagent.cpp new file mode 100644 index 00000000..91ade7d2 --- /dev/null +++ b/bluedevil/src/daemon/kded/filereceiver/obexagent.cpp @@ -0,0 +1,65 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "obexagent.h" +#include "receivefilejob.h" +#include "../BlueDevilDaemon.h" + +#include +#include + +#include + +ObexAgent::ObexAgent(const KComponentData &componentData, QObject* parent) + : QDBusAbstractAdaptor(parent) + , m_componentData(componentData) +{ + kDebug(dblue()); + if (!QDBusConnection::sessionBus().registerObject("/BlueDevil_receiveAgent", parent)) { + kDebug() << "The dbus object can't be registered"; + return; + } +} + +ObexAgent::~ObexAgent() +{ + +} + +QString ObexAgent::AuthorizePush(const QDBusObjectPath& path, const QDBusMessage &msg) +{ + kDebug(dblue()); + + msg.setDelayedReply(true); + + ReceiveFileJob *job = new ReceiveFileJob(msg, path.path(), m_componentData, this); + job->start(); + + return QString(); +} + +void ObexAgent::Cancel() +{ + kDebug(dblue()); +} + + +void ObexAgent::Release() +{ + kDebug(dblue()); +} \ No newline at end of file diff --git a/bluedevil/src/daemon/kded/filereceiver/obexagent.h b/bluedevil/src/daemon/kded/filereceiver/obexagent.h new file mode 100644 index 00000000..0edff42c --- /dev/null +++ b/bluedevil/src/daemon/kded/filereceiver/obexagent.h @@ -0,0 +1,47 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#ifndef OBEX_AGENT_H +#define OBEX_AGENT_H + +#include +#include +#include + +#include + +class QDBusMessage; +class ObexAgent : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.bluez.obex.Agent1") + + public: + explicit ObexAgent(const KComponentData &componentData, QObject* parent); + virtual ~ObexAgent(); + + public Q_SLOTS: + QString AuthorizePush(const QDBusObjectPath &path, const QDBusMessage &msg); + + void Release(); + void Cancel(); + private: + KComponentData m_componentData; +}; + +#endif //OBEX_AGENT_H \ No newline at end of file diff --git a/bluedevil/src/daemon/kded/filereceiver/org.bluez.obex.AgentManager1.xml b/bluedevil/src/daemon/kded/filereceiver/org.bluez.obex.AgentManager1.xml new file mode 100644 index 00000000..b88cab10 --- /dev/null +++ b/bluedevil/src/daemon/kded/filereceiver/org.bluez.obex.AgentManager1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/bluedevil/src/daemon/kded/filereceiver/org.bluez.obex.Session1.xml b/bluedevil/src/daemon/kded/filereceiver/org.bluez.obex.Session1.xml new file mode 100644 index 00000000..48cc7cc7 --- /dev/null +++ b/bluedevil/src/daemon/kded/filereceiver/org.bluez.obex.Session1.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bluedevil/src/daemon/kded/filereceiver/org.bluez.obex.Transfer1.xml b/bluedevil/src/daemon/kded/filereceiver/org.bluez.obex.Transfer1.xml new file mode 100644 index 00000000..0de04938 --- /dev/null +++ b/bluedevil/src/daemon/kded/filereceiver/org.bluez.obex.Transfer1.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bluedevil/src/daemon/kded/filereceiver/org.freedesktop.DBus.Properties.xml b/bluedevil/src/daemon/kded/filereceiver/org.freedesktop.DBus.Properties.xml new file mode 100644 index 00000000..1d101b49 --- /dev/null +++ b/bluedevil/src/daemon/kded/filereceiver/org.freedesktop.DBus.Properties.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bluedevil/src/daemon/kded/filereceiver/receivefilejob.cpp b/bluedevil/src/daemon/kded/filereceiver/receivefilejob.cpp new file mode 100644 index 00000000..8cc00220 --- /dev/null +++ b/bluedevil/src/daemon/kded/filereceiver/receivefilejob.cpp @@ -0,0 +1,270 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "receivefilejob.h" +#include "../BlueDevilDaemon.h" +#include "filereceiversettings.h" +#include "obex_transfer.h" +#include "obex_session.h" +#include "dbus_properties.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace BlueDevil; + +ReceiveFileJob::ReceiveFileJob(const QDBusMessage& msg, const QString &path, const KComponentData &componentData, QObject* parent) + : KJob(parent) + , m_speedBytes(0) + , m_path(path) + , m_msg(msg) + , m_componentData(componentData) +{ + setCapabilities(Killable); +} + +ReceiveFileJob::~ReceiveFileJob() +{ + +} + +void ReceiveFileJob::start() +{ + QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); +} + +bool ReceiveFileJob::doKill() +{ + kDebug(dblue()); + m_transfer->Cancel(); + return true; +} + +void ReceiveFileJob::init() +{ + m_transfer = new org::bluez::obex::Transfer1("org.bluez.obex", m_path, QDBusConnection::sessionBus(), this); + kDebug(dblue()) << m_transfer->name(); + kDebug(dblue()) << m_transfer->filename(); + kDebug(dblue()) << m_transfer->status(); + kDebug(dblue()) << m_transfer->type(); + kDebug(dblue()) << m_transfer->size(); + kDebug(dblue()) << m_transfer->transferred(); + + m_transferProps = new org::freedesktop::DBus::Properties("org.bluez.obex", m_path, QDBusConnection::sessionBus(), this); + connect(m_transferProps, + SIGNAL(PropertiesChanged(QString,QVariantMap,QStringList)), + SLOT(transferPropertiesChanged(QString,QVariantMap,QStringList))); + + m_session = new org::bluez::obex::Session1("org.bluez.obex", m_transfer->session().path(), QDBusConnection::sessionBus(), this); + kDebug(dblue()) << m_session->destination(); + + Device* device = Manager::self()->usableAdapter()->deviceForAddress(m_session->destination()); + kDebug(dblue()) << device; + + m_deviceName = m_session->destination(); + if (device) { + kDebug(dblue()) << device->name(); + m_deviceName = device->name(); + } + + FileReceiverSettings::self()->readConfig(); + kDebug(dblue()) << "Auto Accept: " << FileReceiverSettings::self()->autoAccept(); + if (FileReceiverSettings::self()->autoAccept() == 1 && device->isTrusted()) { + slotAccept(); + return; + } else if (FileReceiverSettings::self()->autoAccept() == 2) { + slotAccept(); + return; + } + + showNotification(); +} + +void ReceiveFileJob::showNotification() +{ + KNotification *m_notification = new KNotification("bluedevilIncomingFile", + KNotification::Persistent, this); + + m_notification->setText(i18nc( + "Show a notification asking to authorize or deny an incoming file transfer to this computer from a Bluetooth device.", + "%1 is sending you the file %2", m_deviceName, m_transfer->name())); + + QStringList actions; + + actions.append(i18nc("Button to accept the incoming file transfer and download it in the default download directory", "Accept")); + actions.append(i18nc("Deny the incoming file transfer", "Cancel")); + + m_notification->setActions(actions); + + connect(m_notification, SIGNAL(action1Activated()), SLOT(slotAccept())); + connect(m_notification, SIGNAL(action2Activated()), SLOT(slotCancel())); + connect(m_notification, SIGNAL(closed()), SLOT(slotCancel())); + + int size = IconSize(KIconLoader::Desktop); + m_notification->setPixmap(KIcon("preferences-system-bluetooth").pixmap(size, size)); + m_notification->setComponentData(KComponentData("bluedevil")); + m_notification->sendEvent(); +} + +void ReceiveFileJob::slotAccept() +{ + kDebug(dblue()); + KComponentData data = KGlobal::mainComponent(); + KGlobal::setActiveComponent(m_componentData); + KIO::getJobTracker()->registerJob(this); + KGlobal::setActiveComponent(data); + + m_tempPath = createTempPath(m_transfer->name()); + kDebug(dblue()) << m_tempPath; + QDBusMessage msg = m_msg.createReply(m_tempPath); + QDBusConnection::sessionBus().send(msg); +} + +void ReceiveFileJob::slotSaveAs() +{ + KTemporaryFile tmpFile; + tmpFile.open(); + tmpFile.close(); + + QDBusConnection::sessionBus().send(m_msg.createReply(tmpFile.fileName())); + kDebug(dblue()) << tmpFile.fileName(); +} + +void ReceiveFileJob::slotCancel() +{ + kDebug(dblue()); + QDBusMessage msg = m_msg.createErrorReply("org.bluez.obex.Error.Rejected", "org.bluez.obex.Error.Rejected"); + QDBusConnection::sessionBus().send(msg); +} + +void ReceiveFileJob::transferPropertiesChanged(const QString& interface, const QVariantMap& properties, const QStringList& invalidatedProperties) +{ + kDebug(dblue()) << interface; + kDebug(dblue()) << properties; + kDebug(dblue()) << invalidatedProperties; + + QStringList changedProps = properties.keys(); + Q_FOREACH(const QString &prop, changedProps) { + if (prop == QLatin1String("Status")) { + statusChanged(properties.value(prop)); + } else if (prop == QLatin1String("Transferred")) { + transferChanged(properties.value(prop)); + } + } +} + +void ReceiveFileJob::statusChanged(const QVariant& value) +{ + kDebug(dblue()) << value; + QString status = value.toString(); + + FileReceiverSettings::self()->readConfig(); + KUrl savePath = FileReceiverSettings::self()->saveUrl(); + savePath.setFileName(m_transfer->name()); + + if (status == QLatin1String("active")) { + emit description(this, i18n("Receiving file over Bluetooth"), + QPair(i18nc("File transfer origin", "From"), + QString(m_deviceName)), + QPair(i18nc("File transfer destination", "To"), savePath.path())); + + setTotalAmount(Bytes, m_transfer->size()); + setProcessedAmount(Bytes, 0); + m_time = QTime::currentTime(); + return; + } else if (status == QLatin1String("complete")) { + KIO::CopyJob* job = KIO::move(KUrl(m_tempPath), KUrl(savePath), KIO::HideProgressInfo); + job->setUiDelegate(0); + connect(job, SIGNAL(finished(KJob*)), SLOT(moveFinished(KJob*))); + return; + } else if (status == QLatin1String("error")) { + setError(KJob::UserDefinedError); + emitResult(); + return; + } + + kDebug(dblue()) << "Not implemented status: " << status; +} + +void ReceiveFileJob::transferChanged(const QVariant& value) +{ + kDebug(dblue()) << value; + bool ok = false; + qulonglong bytes = value.toULongLong(&ok); + if (!ok) { + kWarning(dblue()) << "Couldn't cast transferChanged value" << value; + return; + } + + //If a least 1 second has passed since last update + int secondsSinceLastTime = m_time.secsTo(QTime::currentTime()); + if (secondsSinceLastTime > 0) { + float speed = (bytes - m_speedBytes) / secondsSinceLastTime; + emitSpeed(speed); + + m_time = QTime::currentTime(); + m_speedBytes = bytes; + } + + setProcessedAmount(Bytes, bytes); +} + +void ReceiveFileJob::moveFinished(KJob* job) +{ + if (job->error()) { + kDebug(dblue()) << job->error(); + kDebug(dblue()) << job->errorText(); + setError(job->error()); + setErrorText("Error in KIO::move"); + } + + emitResult(); +} + +QString ReceiveFileJob::createTempPath(const QString &fileName) const +{ + QString xdgCacheHome = QLatin1String(qgetenv("XDG_CACHE_HOME")); + if (xdgCacheHome.isEmpty()) { + xdgCacheHome = QDir::homePath() + QLatin1String("/.cache"); + } + + xdgCacheHome.append(QLatin1String("/obexd/")); + QString path = xdgCacheHome + fileName; + int i = 0; + + while (QFile::exists(path)) { + path = xdgCacheHome + fileName + QString::number(i); + i++; + } + + return path; +} \ No newline at end of file diff --git a/bluedevil/src/daemon/kded/filereceiver/receivefilejob.h b/bluedevil/src/daemon/kded/filereceiver/receivefilejob.h new file mode 100644 index 00000000..655be8de --- /dev/null +++ b/bluedevil/src/daemon/kded/filereceiver/receivefilejob.h @@ -0,0 +1,70 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + + +#ifndef RECEIVE_FILE_JOB_H +#define RECEIVE_FILE_JOB_H + +#include +#include + +#include +#include + +class OrgBluezObexSession1Interface; +class OrgBluezObexTransfer1Interface; +class OrgFreedesktopDBusPropertiesInterface; +class ReceiveFileJob : public KJob +{ + Q_OBJECT + public: + explicit ReceiveFileJob(const QDBusMessage &msg, const QString &path, const KComponentData &componentData, QObject* parent = 0); + virtual ~ReceiveFileJob(); + + virtual void start(); + protected: + virtual bool doKill(); + + private Q_SLOTS: + void init(); + void showNotification(); + void slotCancel(); + void slotAccept(); + void slotSaveAs(); + void transferPropertiesChanged(const QString &interface, const QVariantMap &properties, const QStringList &invalidatedProperties); + void moveFinished(KJob* job); + + private: + void transferChanged(const QVariant &value); + void statusChanged(const QVariant &value); + QString createTempPath(const QString &fileName) const; + + QTime m_time; + qulonglong m_speedBytes; + QString m_path; + QString m_tempPath; + QString m_deviceName; + QDBusMessage m_msg; + KComponentData m_componentData; + OrgBluezObexSession1Interface *m_session; + OrgBluezObexTransfer1Interface *m_transfer; + OrgFreedesktopDBusPropertiesInterface *m_transferProps; +}; + +#endif //RECEIVE_FILE_JOB_H + diff --git a/bluedevil/src/fileitemactionplugin/CMakeLists.txt b/bluedevil/src/fileitemactionplugin/CMakeLists.txt new file mode 100644 index 00000000..27fce338 --- /dev/null +++ b/bluedevil/src/fileitemactionplugin/CMakeLists.txt @@ -0,0 +1,4 @@ +kde4_add_plugin(bluetoothfiletiemaction sendfileitemaction.cpp) +target_link_libraries(bluetoothfiletiemaction ${KDE4_KIO_LIBS} ${LibBlueDevil_LIBRARIES}) +install(TARGETS bluetoothfiletiemaction DESTINATION ${PLUGIN_INSTALL_DIR}) +install(FILES bluedevilsendfile.desktop DESTINATION ${SERVICES_INSTALL_DIR}) \ No newline at end of file diff --git a/bluedevil/src/fileitemactionplugin/bluedevilsendfile.desktop b/bluedevil/src/fileitemactionplugin/bluedevilsendfile.desktop new file mode 100644 index 00000000..ac4eb705 --- /dev/null +++ b/bluedevil/src/fileitemactionplugin/bluedevilsendfile.desktop @@ -0,0 +1,89 @@ +[Desktop Entry] +Type=Service +Name=Send file via Bluetooth +Name[ar]=أرسل ملف بالبلوتوث +Name[bs]=Šalji datoteku preko Bluetoots +Name[ca]=Envia un fitxer per Bluetooth +Name[ca@valencia]=Envia un fitxer per Bluetooth +Name[cs]=Poslat soubor přes Bluetooth +Name[da]=Send fil via Bluetooth +Name[de]=Datei über Bluetooth versenden +Name[el]=Αποστολή αρχείου μέσω Bluetooth +Name[es]=Enviar archivo por Bluetooth +Name[et]=Faili saatmine Bluetoothi kaudu +Name[fi]=Lähetä tiedosto Bluetoothin kautta +Name[fr]=Envoi de fichiers via Bluetooth +Name[gl]=Enviar o ficheiro por Bluetooth +Name[hu]=Fájl küldése Bluetoothon +Name[it]=Invia file via Bluetooth +Name[kk]=Bluetooth арқылы файлды жіберу +Name[km]=ផ្ញើ​ឯកសារ​តាម​ប៊្លូធូស +Name[lt]=Siųsti per Bluetooth +Name[mr]=ब्लूटूथ द्वारे फाईल पाठवा +Name[nb]=Send fil over Blåtann +Name[nds]=Datei över Bluetooth loosstüern +Name[nl]=Verzendt bestand over bluetooth +Name[pa]=ਫਾਇਲ ਬਲਿਊਟੁੱਥ ਰਾਹੀਂ ਭੇਜੋ +Name[pl]=Wyślij plik przez Bluetooth +Name[pt]=Enviar o ficheiro por Bluetooth +Name[pt_BR]=Enviar arquivo por Bluetooth +Name[ro]=Trimite fișier prin Bluetooth +Name[ru]=Отправить файл по Bluetooth +Name[sk]=Poslať súbor cez Bluetooth +Name[sl]=Pošlji datoteko preko Bluetooth-a +Name[sr]=Слање фајлова преко блутута +Name[sr@ijekavian]=Слање фајлова преко блутута +Name[sr@ijekavianlatin]=Slanje fajlova preko Bluetootha +Name[sr@latin]=Slanje fajlova preko Bluetootha +Name[sv]=Skicka fil via Blåtand +Name[tr]=Bluetooth üzerinden dosya gönder +Name[uk]=Надіслати файл за допомогою Bluetooth +Name[x-test]=xxSend file via Bluetoothxx +Name[zh_CN]=通过蓝牙发送文件 +Name[zh_TW]=透過藍牙傳送檔案 +X-KDE-Library=bluetoothfiletiemaction +X-KDE-Submenu=Bluetooth +X-KDE-Submenu[ar]=بلوتوث +X-KDE-Submenu[bs]=Bluetooth +X-KDE-Submenu[ca]=Bluetooth +X-KDE-Submenu[ca@valencia]=Bluetooth +X-KDE-Submenu[cs]=Bluetooth +X-KDE-Submenu[da]=Bluetooth +X-KDE-Submenu[de]=Bluetooth +X-KDE-Submenu[el]=Bluetooth +X-KDE-Submenu[es]=Bluetooth +X-KDE-Submenu[et]=Bluetooth +X-KDE-Submenu[fi]=Bluetooth +X-KDE-Submenu[fr]=Bluetooth +X-KDE-Submenu[gl]=Bluetooth +X-KDE-Submenu[hu]=Bluetooth +X-KDE-Submenu[it]=Bluetooth +X-KDE-Submenu[kk]=Bluetooth +X-KDE-Submenu[km]=ប៊្លូធូស +X-KDE-Submenu[lt]=Bluetooth +X-KDE-Submenu[mr]=ब्लूटूथ +X-KDE-Submenu[nb]=Blåtann +X-KDE-Submenu[nds]=Bluetooth +X-KDE-Submenu[nl]=Bluetooth +X-KDE-Submenu[pa]=ਬਲਿਊਟੁੱਥ +X-KDE-Submenu[pl]=Bluetooth +X-KDE-Submenu[pt]=Bluetooth +X-KDE-Submenu[pt_BR]=Bluetooth +X-KDE-Submenu[ro]=Bluetooth +X-KDE-Submenu[ru]=Bluetooth +X-KDE-Submenu[sk]=Bluetooth +X-KDE-Submenu[sl]=Bluetooth +X-KDE-Submenu[sr]=Блутут +X-KDE-Submenu[sr@ijekavian]=Блутут +X-KDE-Submenu[sr@ijekavianlatin]=Bluetooth +X-KDE-Submenu[sr@latin]=Bluetooth +X-KDE-Submenu[sv]=Blåtand +X-KDE-Submenu[tr]=Bluetooth +X-KDE-Submenu[ug]=كۆكچىش +X-KDE-Submenu[uk]=Bluetooth +X-KDE-Submenu[x-test]=xxBluetoothxx +X-KDE-Submenu[zh_CN]=蓝牙 +X-KDE-Submenu[zh_TW]=藍牙 +Icon=preferences-system-bluetooth +ServiceTypes=KFileItemAction/Plugin +MimeType=application/octet-stream; diff --git a/bluedevil/src/fileitemactionplugin/sendfileitemaction.cpp b/bluedevil/src/fileitemactionplugin/sendfileitemaction.cpp new file mode 100644 index 00000000..a7e47889 --- /dev/null +++ b/bluedevil/src/fileitemactionplugin/sendfileitemaction.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2011 Alejandro Fiestas Olivares + * Copyright (C) 2011 UFO Coders + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "sendfileitemaction.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +using namespace BlueDevil; +K_PLUGIN_FACTORY(SendFileItemActionFactory, registerPlugin();) +K_EXPORT_PLUGIN(SendFileItemActionFactory("SendFileItemAction", "bluedevil")) + +SendFileItemAction::SendFileItemAction(QObject* parent, const QVariantList& args): KFileItemActionPlugin(parent) +{ + Q_UNUSED(args) +} + +QList< QAction* > SendFileItemAction::actions(const KFileItemListProperties& fileItemInfos, QWidget* parentWidget) const +{ + Q_UNUSED(parentWidget) + QList< QAction* > list; + + SendFileItemAction *hack = const_cast(this); + hack->m_fileItemInfos = fileItemInfos; + + //If there is no adaptor, there is no bluetooth + if (!Manager::self()->usableAdapter()) { + return list; + } + Adapter *adapter = Manager::self()->usableAdapter(); + + QAction *menuAction = new QAction(KIcon("preferences-system-bluetooth"), i18n("Send via Bluetooth"), hack); + QMenu *menu = new QMenu(); + + //If we have configured devices, put them first + QList< Device * > devices = adapter->devices(); + if (!devices.isEmpty()) { + Q_FOREACH(Device *device, devices) { + if (device->UUIDs().contains("00001105-0000-1000-8000-00805F9B34FB", Qt::CaseInsensitive)) { + QAction *action = new QAction(KIcon(device->icon()), device->name(), hack); + connect(action, SIGNAL(triggered(bool)), this, SLOT(deviceTriggered())); + action->setData(device->UBI()); + menu->addAction(action); + } + } + } + + QAction *otherAction = new QAction(hack); + connect(otherAction, SIGNAL(triggered(bool)), this, SLOT(otherTriggered())); + if (menu->actions().isEmpty()) { + otherAction->setText(i18nc("Find Bluetooth device", "Find Device...")); + } else { + menu->addSeparator(); + otherAction->setText(i18nc("Other Bluetooth device", "Other...")); + } + menu->addAction(otherAction); + + menuAction->setMenu(menu); + list << menuAction; + return list; +} + +void SendFileItemAction::deviceTriggered() +{ + QStringList args; + args.append("-u" + static_cast(sender())->data().toString()); + + KUrl::List fileList = m_fileItemInfos.urlList(); + Q_FOREACH(const KUrl &url, fileList) { + args.append("-f" + url.path()); + } + kDebug() << args; + KProcess process; + process.setProgram("bluedevil-sendfile", args); + process.startDetached(); +} + +void SendFileItemAction::otherTriggered() +{ + kDebug(); + QStringList args; + + KUrl::List fileList = m_fileItemInfos.urlList(); + Q_FOREACH(const KUrl &url, fileList) { + args.append("-f" + url.path()); + } + + KProcess process; + process.setProgram("bluedevil-sendfile", args); + process.startDetached(); +} diff --git a/bluedevil/src/fileitemactionplugin/sendfileitemaction.h b/bluedevil/src/fileitemactionplugin/sendfileitemaction.h new file mode 100644 index 00000000..f441c81c --- /dev/null +++ b/bluedevil/src/fileitemactionplugin/sendfileitemaction.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 Alejandro Fiestas Olivares + * Copyright (C) 2011 UFO Coders + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef SENDFILEITEMACTION_H +#define SENDFILEITEMACTION_H + +#include +#include + +class QAction; +class KFileItemListProperties; +class QWidget; + +class SendFileItemAction : public KFileItemActionPlugin +{ +Q_OBJECT +public: + SendFileItemAction(QObject* parent, const QVariantList &args); + virtual QList< QAction* > actions(const KFileItemListProperties& fileItemInfos, QWidget* parentWidget) const; + +private Q_SLOTS: + void deviceTriggered(); + void otherTriggered(); + +private: + KFileItemListProperties m_fileItemInfos; +}; + +#endif // SENDFILEITEMACTION_H \ No newline at end of file diff --git a/bluedevil/src/kcmodule/CMakeLists.txt b/bluedevil/src/kcmodule/CMakeLists.txt new file mode 100644 index 00000000..04d9e7f1 --- /dev/null +++ b/bluedevil/src/kcmodule/CMakeLists.txt @@ -0,0 +1,44 @@ +set(kcm_bluedevildevices_PART_SRCS bluedevildevices.cpp systemcheck.cpp kded.cpp devicedetails.cpp) +set(kcm_bluedeviladapters_PART_SRCS bluedeviladapters.cpp systemcheck.cpp kded.cpp) +set(kcm_bluedeviltransfer_PART_SRCS + bluedeviltransfer.cpp + systemcheck.cpp + kded.cpp + columnresizer.cpp + sharedfilesdialog/sharedfilesdialog.cpp + sharedfilesdialog/linkproxymodel.cpp +) + +kde4_add_ui_files(kcm_bluedeviltransfer_PART_SRCS_UI transfer.ui sharedfilesdialog/sharedfiles.ui) + +kde4_add_kcfg_files(kcm_bluedeviltransfer_PART_SRCS + ../settings/filereceiversettings.kcfgc + ../settings/globalsettings.kcfgc) + +kde4_add_kcfg_files(kcm_bluedevildevices_PART_SRCS ../settings/globalsettings.kcfgc) + +kde4_add_kcfg_files(kcm_bluedeviladapters_PART_SRCS ../settings/globalsettings.kcfgc) + +qt4_add_dbus_interface(kcm_bluedeviltransfer_PART_SRCS + org.kde.BlueDevil.Service.xml + bluedevil_service) + +kde4_add_plugin(kcm_bluedevildevices ${kcm_bluedevildevices_PART_SRCS}) +kde4_add_plugin(kcm_bluedeviladapters ${kcm_bluedeviladapters_PART_SRCS}) +kde4_add_plugin(kcm_bluedeviltransfer ${kcm_bluedeviltransfer_PART_SRCS} ${kcm_bluedeviltransfer_PART_SRCS_UI}) + +qt4_automoc(${kcm_bluedevildevices} + ${kcm_bluedeviladapters} + ${kcm_bluedeviltransfer}) + +target_link_libraries(kcm_bluedevildevices ${KDE4_KIO_LIBS} ${QT_QTGUI_LIBRARY} ${LibBlueDevil_LIBRARIES}) +target_link_libraries(kcm_bluedeviladapters ${KDE4_KIO_LIBS} ${QT_QTGUI_LIBRARY} ${LibBlueDevil_LIBRARIES}) +target_link_libraries(kcm_bluedeviltransfer ${KDE4_KIO_LIBS} ${QT_QTGUI_LIBRARY} ${LibBlueDevil_LIBRARIES}) + +install(TARGETS kcm_bluedevildevices + kcm_bluedeviladapters + kcm_bluedeviltransfer DESTINATION ${PLUGIN_INSTALL_DIR}) + +install(FILES bluedevildevices.desktop + bluedeviladapters.desktop + bluedeviltransfer.desktop DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/bluedevil/src/kcmodule/bluedeviladapters.cpp b/bluedevil/src/kcmodule/bluedeviladapters.cpp new file mode 100644 index 00000000..ec2fff91 --- /dev/null +++ b/bluedevil/src/kcmodule/bluedeviladapters.cpp @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2010 Rafael Fernández López + * Copyright (C) 2010 UFO Coders + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "bluedeviladapters.h" +#include "systemcheck.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +K_PLUGIN_FACTORY(BlueDevilFactory, registerPlugin();) +K_EXPORT_PLUGIN(BlueDevilFactory("bluedeviladapters", "bluedevil")) + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +AdapterSettings::AdapterSettings(Adapter *adapter, KCModule *parent) + : QGroupBox(parent) + , m_adapter(adapter) + , m_name(new KLineEdit(this)) + , m_hidden(new QRadioButton(i18nc("Radio widget to set if we want the adapter to be hidden", "Hidden"), this)) + , m_alwaysVisible(new QRadioButton(i18nc("Radio widget to set if we want the adapter to be always visible", "Always visible"), this)) + , m_temporaryVisible(new QRadioButton(i18nc("Radio widget to set if we want the adapter to be temporarily visible", "Temporarily visible"), this)) + , m_discoverTime(new QSlider(Qt::Horizontal, this)) + , m_discoverTimeLabel(new QLabel(this)) + , m_discoverTimeWidget(new QWidget(this)) + , m_powered(new QCheckBox(this)) +{ + QButtonGroup *const buttonGroup = new QButtonGroup(this); + buttonGroup->addButton(m_hidden); + buttonGroup->addButton(m_alwaysVisible); + buttonGroup->addButton(m_temporaryVisible); + + m_name->setText(adapter->name()); + m_nameOrig = adapter->name(); + m_hiddenOrig = false; + m_alwaysVisibleOrig = false; + m_temporaryVisibleOrig = false; + if (!adapter->isDiscoverable()) { + m_hidden->setChecked(true); + m_hiddenOrig = true; + } else { + if (!adapter->discoverableTimeout()) { + m_alwaysVisible->setChecked(true); + m_alwaysVisibleOrig = true; + } else { + m_temporaryVisible->setChecked(true); + m_temporaryVisibleOrig = true; + } + } + m_discoverTime->setRange(1, 30); + m_discoverTime->setValue(adapter->discoverableTimeout() / 60); + m_discoverTime->setTickPosition(QSlider::TicksBelow); + m_discoverTime->setTickInterval(1); + m_discoverTimeOrig = qMax((quint32) 1, adapter->discoverableTimeout() / 60); + + QHBoxLayout *layout = new QHBoxLayout; + layout->addWidget(m_discoverTime); + layout->addWidget(m_discoverTimeLabel); + m_discoverTimeWidget->setLayout(layout); + m_discoverTimeWidget->setEnabled(m_temporaryVisibleOrig); + + m_discoverTimeLabel->setText(i18ncp("Discover time for the adapter", "1 minute", "%1 minutes", m_discoverTime->value())); + + m_powered->setChecked(adapter->isPowered()); + m_poweredOrig = adapter->isPowered(); + + m_layout = new QFormLayout; + m_layout->addRow(i18nc("Name of the adapter", "Name"), m_name); + m_layout->addRow(i18nc("Whether the adapter is powered or not", "Powered"), m_powered); + m_layout->addRow(i18nc("Whether the adapter is visible or not", "Visibility"), m_hidden); + m_layout->addWidget(m_alwaysVisible); + m_layout->addWidget(m_temporaryVisible); + m_layout->addRow(i18nc("How long the adapter will be discoverable", "Discover Time"), m_discoverTimeWidget); + setLayout(m_layout); + + m_layout->labelForField(m_discoverTimeWidget)->setEnabled(m_temporaryVisibleOrig); + + connect(m_adapter, SIGNAL(propertyChanged(QString,QVariant)), this, SLOT(readChanges())); + connect(m_name, SIGNAL(textEdited(QString)), this, SLOT(slotSettingsChanged())); + connect(m_hidden, SIGNAL(toggled(bool)), this, SLOT(visibilityChanged())); + connect(m_hidden, SIGNAL(toggled(bool)), this, SLOT(slotSettingsChanged())); + connect(m_alwaysVisible, SIGNAL(toggled(bool)), this, SLOT(visibilityChanged())); + connect(m_alwaysVisible, SIGNAL(toggled(bool)), this, SLOT(slotSettingsChanged())); + connect(m_temporaryVisible, SIGNAL(toggled(bool)), this, SLOT(visibilityChanged())); + connect(m_temporaryVisible, SIGNAL(toggled(bool)), this, SLOT(slotSettingsChanged())); + connect(m_discoverTime, SIGNAL(valueChanged(int)), this, SLOT(slotSettingsChanged())); + connect(m_powered, SIGNAL(stateChanged(int)), this, SLOT(slotSettingsChanged())); + + if (BlueDevil::Manager::self()->usableAdapter() == adapter) { + setTitle(i18n("Default adapter: %1 (%2)", adapter->name(), adapter->address())); + } else { + setTitle(i18n("Adapter: %1 (%2)", adapter->name(), adapter->address())); + } +} + +AdapterSettings::~AdapterSettings() +{ +} + +bool AdapterSettings::isModified() const +{ + return m_name->text() != m_nameOrig || m_hidden->isChecked() != m_hiddenOrig || + m_alwaysVisible->isChecked() != m_alwaysVisibleOrig || + m_temporaryVisible->isChecked() != m_temporaryVisibleOrig || + m_discoverTime->value() != m_discoverTimeOrig || m_powered->isChecked() != m_poweredOrig; +} + +void AdapterSettings::applyChanges() +{ + /* TODO: Find new way to set adapter name in bluez5 + if (m_name->text() != m_nameOrig) { + m_adapter->setName(m_name->text()); + } */ + + if (m_hidden->isChecked()) { + m_adapter->setDiscoverable(false); + } else if (m_alwaysVisible->isChecked()) { + m_adapter->setDiscoverable(true); + m_adapter->setDiscoverableTimeout(0); + } else { + m_adapter->setDiscoverable(true); + m_adapter->setDiscoverableTimeout(m_discoverTime->value() * 60); + } + + if (m_powered->isChecked() != m_poweredOrig) { + m_adapter->setPowered(m_powered->isChecked()); + } +} + +QString AdapterSettings::name() const +{ + return m_name->text(); +} + +AdapterSettings::DiscoverOptions AdapterSettings::discoverOptions() const +{ + if (m_hidden->isChecked()) { + return Hidden; + } + if (m_alwaysVisible->isChecked()) { + return AlwaysVisible; + } + return TemporaryVisible; +} + +quint32 AdapterSettings::discoverTime() const +{ + return m_discoverTime->value() * 60; +} + +bool AdapterSettings::powered() const +{ + return m_powered->isChecked(); +} + +void AdapterSettings::readChanges() +{ + blockSignals(true); + + m_nameOrig = m_adapter->name(); + m_hiddenOrig = !m_adapter->isDiscoverable(); + m_alwaysVisibleOrig = m_adapter->isDiscoverable() && !m_adapter->discoverableTimeout(); + m_temporaryVisibleOrig = m_adapter->isDiscoverable() && m_adapter->discoverableTimeout(); + m_discoverTimeOrig = qMax((quint32) 1, m_adapter->discoverableTimeout() / 60); + m_poweredOrig = m_adapter->isPowered(); + + m_name->setText(m_nameOrig); + m_hidden->setChecked(m_hiddenOrig); + m_alwaysVisible->setChecked(m_alwaysVisibleOrig); + m_temporaryVisible->setChecked(m_temporaryVisibleOrig); + m_discoverTime->setValue(m_discoverTimeOrig); + m_powered->setChecked(m_poweredOrig); + + m_discoverTimeLabel->setText(i18np("1 minute", "%1 minutes", m_discoverTime->value())); + if (BlueDevil::Manager::self()->usableAdapter() == m_adapter) { + setTitle(i18n("Default adapter: %1 (%2)", m_adapter->name(), m_adapter->address())); + } else { + setTitle(i18n("Adapter: %1 (%2)", m_adapter->name(), m_adapter->address())); + } + + blockSignals(false); + + emit settingsChanged(false); +} + +void AdapterSettings::visibilityChanged() +{ + QRadioButton *const sdr = static_cast(sender()); + if (!sdr->isChecked()) { + return; + } + const bool enabled = sender() == m_temporaryVisible; + m_discoverTimeWidget->setEnabled(enabled); + m_layout->labelForField(m_discoverTimeWidget)->setEnabled(enabled); +} + +void AdapterSettings::slotSettingsChanged() +{ + m_discoverTimeLabel->setText(i18np("1 minute", "%1 minutes", m_discoverTime->value())); + emit settingsChanged(isModified()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +KCMBlueDevilAdapters::KCMBlueDevilAdapters(QWidget *parent, const QVariantList&) + : KCModule(BlueDevilFactory::componentData(), parent) + , m_noAdaptersMessage(0) + , m_systemCheck(new SystemCheck(this)) +{ + KAboutData* ab = new KAboutData( + "kcmbluedeviladapters", "bluedevil", ki18n("Bluetooth Adapters"), "1.0", + ki18n("Bluetooth Adapters Control Panel Module"), + KAboutData::License_GPL, ki18n("(c) 2010 Rafael Fernández López")); + + ab->addAuthor(ki18n("Rafael Fernández López"), ki18n("Developer and Maintainer"), "ereslibre@kde.org"); + setAboutData(ab); + + connect(m_systemCheck, SIGNAL(updateInformationStateRequest()), + this, SLOT(updateInformationState())); + + QVBoxLayout *layout = new QVBoxLayout; + m_systemCheck->createWarnings(layout); + QScrollArea *mainArea = new QScrollArea(this); + QWidget *widget = new QWidget(mainArea); + m_layout = new QVBoxLayout; + widget->setLayout(m_layout); + mainArea->setWidget(widget); + mainArea->setWidgetResizable(true); + layout->addWidget(mainArea); + setLayout(layout); + + connect(BlueDevil::Manager::self(), SIGNAL(adapterAdded(Adapter*)), + this, SLOT(updateAdapters())); + connect(BlueDevil::Manager::self(), SIGNAL(adapterRemoved(Adapter*)), + this, SLOT(updateAdapters())); + connect(BlueDevil::Manager::self(), SIGNAL(usableAdapterChanged(Adapter*)), + this, SLOT(usableAdapterChanged(Adapter*))); + + BlueDevil::Adapter *const usableAdapter = BlueDevil::Manager::self()->usableAdapter(); + if (usableAdapter) { + connect(usableAdapter, SIGNAL(discoverableChanged(bool)), + this, SLOT(adapterDiscoverableChanged())); + } + + fillAdaptersInformation(); + updateInformationState(); +} + +KCMBlueDevilAdapters::~KCMBlueDevilAdapters() +{ +} + +void KCMBlueDevilAdapters::defaults() +{ +} + +void KCMBlueDevilAdapters::save() +{ + Q_FOREACH (AdapterSettings *const adapterSettings, m_adapterSettingsMap) { + adapterSettings->applyChanges(); + } + QTimer::singleShot(300, this, SLOT(updateInformationState())); +} + +void KCMBlueDevilAdapters::updateAdapters() +{ + fillAdaptersInformation(); + QTimer::singleShot(300, this, SLOT(updateInformationState())); +} + +void KCMBlueDevilAdapters::usableAdapterChanged(Adapter *adapter) +{ + if (adapter) { + connect(adapter, SIGNAL(discoverableChanged(bool)), + this, SLOT(adapterDiscoverableChanged())); + } + QTimer::singleShot(300, this, SLOT(updateInformationState())); +} + +void KCMBlueDevilAdapters::adapterDiscoverableChanged() +{ + QTimer::singleShot(300, this, SLOT(updateInformationState())); +} + +void KCMBlueDevilAdapters::generateNoAdaptersMessage() +{ + QGridLayout *layout = new QGridLayout; + m_noAdaptersMessage = new QWidget(this); + QLabel *label = new QLabel(m_noAdaptersMessage); + label->setPixmap(KIcon("dialog-information").pixmap(128, 128)); + layout->addWidget(label, 0, 1, Qt::AlignHCenter); + layout->addWidget(new QLabel(i18n("No adapters found. Please connect one."), m_noAdaptersMessage), + 1, 1, Qt::AlignHCenter); + layout->setRowStretch(2, 1); + layout->setColumnStretch(0, 1); + layout->setColumnStretch(2, 1); + m_noAdaptersMessage->setLayout(layout); + m_noAdaptersMessage->setVisible(false); +} + +void KCMBlueDevilAdapters::updateInformationState() +{ + m_systemCheck->updateInformationState(); +} + +void KCMBlueDevilAdapters::adapterConfigurationChanged(bool modified) +{ + if (modified) { + emit changed(true); + return; + } + Q_FOREACH (AdapterSettings *const adapterSettings, m_adapterSettingsMap) { + if (adapterSettings->isModified()) { + return; + } + } + emit changed(false); +} + +void KCMBlueDevilAdapters::fillAdaptersInformation() +{ + qDeleteAll(m_adapterSettingsMap); + m_adapterSettingsMap.clear(); + + for (int i = 0; i < m_layout->count(); ++i) { + m_layout->takeAt(0); + } + + if (BlueDevil::Manager::self()->adapters().isEmpty()) { + generateNoAdaptersMessage(); + m_layout->addWidget(m_noAdaptersMessage); + m_noAdaptersMessage->setVisible(true); + return; + } + + if (m_noAdaptersMessage) { + m_noAdaptersMessage->setVisible(false); + } + + Q_FOREACH (Adapter *const adapter, BlueDevil::Manager::self()->adapters()) { + AdapterSettings *const adapterSettings = new AdapterSettings(adapter, this); + connect(adapterSettings, SIGNAL(settingsChanged(bool)), + this, SLOT(adapterConfigurationChanged(bool))); + m_adapterSettingsMap.insert(adapter, adapterSettings); + m_layout->addWidget(adapterSettings); + } + + m_layout->addStretch(); +} diff --git a/bluedevil/src/kcmodule/bluedeviladapters.desktop b/bluedevil/src/kcmodule/bluedeviladapters.desktop new file mode 100644 index 00000000..700a4813 --- /dev/null +++ b/bluedevil/src/kcmodule/bluedeviladapters.desktop @@ -0,0 +1,137 @@ +[Desktop Entry] +Exec=kcmshell4 bluedeviladapters +Icon=audio-card +Type=Service + +X-KDE-ServiceTypes=KCModule + +X-KDE-Library=kcm_bluedeviladapters +X-KDE-ParentApp=kcontrol + +X-KDE-System-Settings-Parent-Category=bluetooth +X-KDE-Weight=100 + +Name=Adapters +Name[ar]=المحولات +Name[bs]=Adapteri +Name[ca]=Adaptadors +Name[ca@valencia]=Adaptadors +Name[cs]=Adaptéry +Name[da]=Adaptere +Name[de]=Adapter +Name[el]=Προσαρμογείς +Name[en_GB]=Adapters +Name[es]=Adaptadores +Name[et]=Adapterid +Name[fi]=Sovittimet +Name[fr]=Adaptateurs +Name[gl]=Adaptadores +Name[hu]=Adapterek +Name[it]=Adattatori +Name[kk]=Адаптерлері +Name[km]=អាដាប់ទ័រ +Name[lt]=Adapteriai +Name[mr]=एडाप्टर्स +Name[nb]=Adaptere +Name[nds]=Koorten +Name[nl]=Adapters +Name[pa]=ਐਡਪਟਰ +Name[pl]=Adaptery +Name[pt]=Adaptadores +Name[pt_BR]=Adaptadores +Name[ro]=Adaptoare +Name[ru]=Адаптеры +Name[sk]=Adaptéry +Name[sl]=Vmesniki +Name[sr]=Адаптери +Name[sr@ijekavian]=Адаптери +Name[sr@ijekavianlatin]=Adapteri +Name[sr@latin]=Adapteri +Name[sv]=Anslutningar +Name[th]=อุปกรณ์ +Name[tr]=Bağdaştırıcılar +Name[ug]=ماسلاشتۇرغۇچ +Name[uk]=Адаптери +Name[x-test]=xxAdaptersxx +Name[zh_CN]=适配器 +Name[zh_TW]=轉接器 + +Comment=Configure Bluetooth adapters +Comment[ca]=Configura els adaptadors Bluetooth +Comment[ca@valencia]=Configura els adaptadors Bluetooth +Comment[cs]=Nastavte adaptéry Bluetooth +Comment[da]=Indstil Bluetooth-adaptere +Comment[de]=Bluetooth-Adapter einrichten +Comment[el]=Διαμόρφωση προσαρμογέων Bluetooth +Comment[es]=Configurar adaptadores Bluetooth +Comment[fi]=Bluetooth-sovittimien asetukset +Comment[fr]=Configure les adaptateurs Bluetooth +Comment[gl]=Configurar os adaptadores de Bluetooth +Comment[hu]=Bluetooth adapterek beállítása +Comment[it]=Configura gli adattatori Bluetooth +Comment[kk]=Bluetooth адаптерлерін баптау +Comment[lt]=Konfigūruoti prieinamus Bluetooth adapterius +Comment[mr]=ब्लूटूथ एडाप्टर्स संयोजीत करा +Comment[nb]=Sett opp Blåtann-adaptere +Comment[nl]=Bluetooth-adapters instellen +Comment[pa]=ਬਲਿਊਟੁੱਥ ਐਡਪਟਰ ਸੰਰਚਨਾ +Comment[pl]=Konfiguruj adaptery Bluetooth +Comment[pt]=Configurar os adaptadores de Bluetooth +Comment[pt_BR]=Configura os adaptadores Bluetooth +Comment[ro]=Configurează adaptoare Bluetooth +Comment[ru]=Настройка адаптеров Bluetooth +Comment[sk]=Nastaviť adaptéry Bluetooth +Comment[sl]=Nastavite vmesnike Bluetooth +Comment[sr]=Подесите блутут адаптере +Comment[sr@ijekavian]=Подесите блутут адаптере +Comment[sr@ijekavianlatin]=Podesite bluetooth adaptere +Comment[sr@latin]=Podesite bluetooth adaptere +Comment[sv]=Anpassa Blåtandanslutningar +Comment[tr]=Bluetooth bağdaştırıcılarını yapılandır +Comment[uk]=Налаштування адаптерів Bluetooth +Comment[x-test]=xxConfigure Bluetooth adaptersxx +Comment[zh_TW]=設定藍牙轉接器 + +X-KDE-Keywords=Network,Connectivity,Bluetooth +X-KDE-Keywords[ar]=شبكة,توصيل,بلوتوث +X-KDE-Keywords[bs]=Network,Connectivity,Bluetooth +X-KDE-Keywords[ca]=Xarxa,Connectivitat,Bluetooth +X-KDE-Keywords[ca@valencia]=Xarxa,Connectivitat,Bluetooth +X-KDE-Keywords[cs]=Síť,Konektivita,Bluetooth +X-KDE-Keywords[da]=Netværk,forbindelse,Bluetooth +X-KDE-Keywords[de]=Netzwerk,Verbindungen,Bluetooth +X-KDE-Keywords[el]=Δίκτυο,Συνδεσιμότητα,Bluetooth +X-KDE-Keywords[es]=Red,Conectividad,Bluetooth +X-KDE-Keywords[et]=Võrk,Ühenduvus,Bluetooth +X-KDE-Keywords[fi]=Verkko,Yhteydet,Bluetooth +X-KDE-Keywords[fr]=Réseau, connectivité, Bluetooth +X-KDE-Keywords[gl]=Network,Connectivity,Bluetooth,Rede,Conectividade +X-KDE-Keywords[hu]=Hálózat,Kapcsolódás,Bluetooth +X-KDE-Keywords[it]=Rete,Connettività,Bluetooth +X-KDE-Keywords[kk]=Network,Connectivity,Bluetooth +X-KDE-Keywords[km]=បណ្ដាញ ការ​តភ្ជាប់ ប៊្លូធូស +X-KDE-Keywords[lt]=Tinklas,Junglumas,Bluetooth +X-KDE-Keywords[mr]=संजाळ,जुळवणी,ब्लूटूथ +X-KDE-Keywords[nb]=Nettverk,sammenkobling,blåtann +X-KDE-Keywords[nds]=Nettwark,Verbinnen,Bluetooth +X-KDE-Keywords[nl]=Netwerkverbinding,connectiviteit,bluetooth +X-KDE-Keywords[pa]=ਨੈੱਟਵਰਕ,ਕੁਨੈਕਟਵਿਟੀ,ਬਲਿਊਟੁੱਥ +X-KDE-Keywords[pl]=Sieć,Łączność,Bluetooth +X-KDE-Keywords[pt]=Rede,Conectividade,Bluetooth +X-KDE-Keywords[pt_BR]=rede,conectividade,bluetooth +X-KDE-Keywords[ro]=Rețea,Conectivitate,Bluetooth +X-KDE-Keywords[ru]=Сеть,Соединение,Bluetooth +X-KDE-Keywords[sk]=Sieť,Konektivita,Bluetooth +X-KDE-Keywords[sl]=Omrežje,Povezave,Bluetooth +X-KDE-Keywords[sr]=Network,Connectivity,Bluetooth,мрежа,повезивање,блутут +X-KDE-Keywords[sr@ijekavian]=Network,Connectivity,Bluetooth,мрежа,повезивање,блутут +X-KDE-Keywords[sr@ijekavianlatin]=Network,Connectivity,Bluetooth,mreža,povezivanje,Bluetooth +X-KDE-Keywords[sr@latin]=Network,Connectivity,Bluetooth,mreža,povezivanje,Bluetooth +X-KDE-Keywords[sv]=Nätverk,Anslutningar,Blåtand +X-KDE-Keywords[tr]=Ağ, Bağlanılabilirlik, Bluetooth +X-KDE-Keywords[uk]=Network,Connectivity,Bluetooth,мережа,з’єднання,з'єднання +X-KDE-Keywords[x-test]=xxNetwork,Connectivity,Bluetoothxx +X-KDE-Keywords[zh_CN]=Network,Connectivity,Bluetooth,网络,连接,蓝牙 +X-KDE-Keywords[zh_TW]=Network,Connectivity,Bluetooth + +Categories=Qt;KDE;X-KDE-settings-bluetooth; diff --git a/bluedevil/src/kcmodule/bluedeviladapters.h b/bluedevil/src/kcmodule/bluedeviladapters.h new file mode 100644 index 00000000..b96b2ea6 --- /dev/null +++ b/bluedevil/src/kcmodule/bluedeviladapters.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010 Rafael Fernández López + * Copyright (C) 2010 UFO Coders + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _BLUEDEVILADAPTERS_H +#define _BLUEDEVILADAPTERS_H + +#include + +#include + +class QVBoxLayout; +class QRadioButton; +class QSlider; +class QLabel; +class QCheckBox; +class QFormLayout; + +class KLineEdit; + +class SystemCheck; +class AdapterSettings; + +namespace BlueDevil { + class Adapter; +} + +typedef BlueDevil::Adapter Adapter; + +class AdapterSettings + : public QGroupBox +{ + Q_OBJECT + +public: + enum DiscoverOptions { + Hidden = 0, + AlwaysVisible, + TemporaryVisible + }; + + AdapterSettings(Adapter *adapter, KCModule *parent); + virtual ~AdapterSettings(); + + bool isModified() const; + void applyChanges(); + + QString name() const; + DiscoverOptions discoverOptions() const; + quint32 discoverTime() const; + bool powered() const; + +private Q_SLOTS: + void readChanges(); + void visibilityChanged(); + void slotSettingsChanged(); + +Q_SIGNALS: + void settingsChanged(bool changed); + +private: + Adapter *m_adapter; + KLineEdit *m_name; + QString m_nameOrig; + QRadioButton *m_hidden; + bool m_hiddenOrig; + QRadioButton *m_alwaysVisible; + bool m_alwaysVisibleOrig; + QRadioButton *m_temporaryVisible; + bool m_temporaryVisibleOrig; + QSlider *m_discoverTime; + QLabel *m_discoverTimeLabel; + QWidget *m_discoverTimeWidget; + int m_discoverTimeOrig; + QCheckBox *m_powered; + bool m_poweredOrig; + + QFormLayout *m_layout; +}; + +class KCMBlueDevilAdapters + : public KCModule +{ + Q_OBJECT + +public: + KCMBlueDevilAdapters(QWidget *parent, const QVariantList&); + virtual ~KCMBlueDevilAdapters(); + + virtual void defaults(); + virtual void save(); + +private Q_SLOTS: + void updateAdapters(); + void usableAdapterChanged(Adapter *adapter); + void adapterDiscoverableChanged(); + void generateNoAdaptersMessage(); + void updateInformationState(); + void adapterConfigurationChanged(bool modified); + +private: + void fillAdaptersInformation(); + +private: + QVBoxLayout *m_layout; + QMap m_adapterSettingsMap; + QWidget *m_noAdaptersMessage; + + SystemCheck *m_systemCheck; +}; + +#endif diff --git a/bluedevil/src/kcmodule/bluedevildevices.cpp b/bluedevil/src/kcmodule/bluedevildevices.cpp new file mode 100644 index 00000000..2eecf4c7 --- /dev/null +++ b/bluedevil/src/kcmodule/bluedevildevices.cpp @@ -0,0 +1,683 @@ +/* + * Copyright (C) 2010 Rafael Fernández López + * Copyright (C) 2010 UFO Coders + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "bluedevildevices.h" +#include "systemcheck.h" +#include "kded.h" +#include "globalsettings.h" +#include "devicedetails.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +K_PLUGIN_FACTORY(BlueDevilFactory, registerPlugin();) +K_EXPORT_PLUGIN(BlueDevilFactory("bluedevildevices", "bluedevil")) + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class BluetoothDevicesModel + : public QAbstractItemModel +{ +public: + enum ModelRoles { + IconModelRole = 0, + NameModelRole, + AliasModelRole, + DeviceTypeModelRole, + DeviceModelRole, + LastModelRole + }; + + BluetoothDevicesModel(QObject *parent = 0); + virtual ~BluetoothDevicesModel(); + + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role); + virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex &index) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + +private: + struct BluetoothDevice { + QPixmap m_icon; + QString m_deviceType; + Device *m_device; + }; + QList m_deviceList; +}; + +BluetoothDevicesModel::BluetoothDevicesModel(QObject *parent) + : QAbstractItemModel(parent) +{ +} + +BluetoothDevicesModel::~BluetoothDevicesModel() +{ +} + +int BluetoothDevicesModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return 1; +} + +QVariant BluetoothDevicesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= m_deviceList.count()) { + return QVariant(); + } + switch (role) { + case IconModelRole: + return m_deviceList[index.row()].m_icon; + case NameModelRole: + return m_deviceList[index.row()].m_device->name(); + case AliasModelRole: + return m_deviceList[index.row()].m_device->alias(); + case DeviceTypeModelRole: + return m_deviceList[index.row()].m_deviceType; + case DeviceModelRole: + return QVariant::fromValue(m_deviceList[index.row()].m_device); + default: + break; + } + return QVariant(); +} + +bool BluetoothDevicesModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= m_deviceList.count()) { + return false; + } + switch (role) { + case IconModelRole: + m_deviceList[index.row()].m_icon = value.value(); + break; + case DeviceTypeModelRole: + m_deviceList[index.row()].m_deviceType = value.toString(); + break; + case DeviceModelRole: { + Device *const device = static_cast(value.value()); + m_deviceList[index.row()].m_device = device; + connect(device, SIGNAL(propertyChanged(QString,QVariant)), + this, SIGNAL(layoutChanged())); + } + break; + default: + return false; + } + emit dataChanged(index, index); + return true; +} + +QModelIndex BluetoothDevicesModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + if (row < 0 || row >= m_deviceList.count() || column != 0) { + return QModelIndex(); + } + + return createIndex(row, column); +} + +QModelIndex BluetoothDevicesModel::parent(const QModelIndex &index) const +{ + Q_UNUSED(index); + + return QModelIndex(); +} + +int BluetoothDevicesModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return m_deviceList.count(); +} + +bool BluetoothDevicesModel::insertRows(int row, int count, const QModelIndex &parent) +{ + if (row < 0 || row > m_deviceList.count() || count < 1) { + return false; + } + beginInsertRows(parent, row, row + count - 1); + for (int i = row; i < row + count; ++i) { + m_deviceList.insert(i, BluetoothDevice()); + } + endInsertRows(); + return true; +} + +bool BluetoothDevicesModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (row < 0 || row > m_deviceList.count() || count < 1) { + return false; + } + beginRemoveRows(parent, row, row + count - 1); + for (int i = row; i < row + count; ++i) { + m_deviceList.removeAt(row); + } + endRemoveRows(); + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class BluetoothDevicesDelegate + : public QStyledItemDelegate +{ +public: + BluetoothDevicesDelegate(QObject *parent = 0); + virtual ~BluetoothDevicesDelegate(); + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + +private: + QPixmap m_blockedPixmap; + QPixmap m_trustedPixmap; + QPixmap m_untrustedPixmap; + QPixmap m_connectedPixmap; + QPixmap m_disconnectedPixmap; +}; + +BluetoothDevicesDelegate::BluetoothDevicesDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ + KIcon blockedIcon("dialog-cancel"); + m_blockedPixmap = blockedIcon.pixmap(22, 22); + KIcon trustedIcon("security-high"); + m_trustedPixmap = trustedIcon.pixmap(22, 22); + KIcon untrustedIcon("security-low"); + m_untrustedPixmap = untrustedIcon.pixmap(22, 22); + KIcon connectedIcon("user-online"); + m_connectedPixmap = connectedIcon.pixmap(22, 22); + KIcon disconnectedIcon("user-offline"); + m_disconnectedPixmap = disconnectedIcon.pixmap(22, 22); +} + +BluetoothDevicesDelegate::~BluetoothDevicesDelegate() +{ +} + +void BluetoothDevicesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyledItemDelegate::paint(painter, option, index); + + painter->save(); + + if (option.state & QStyle::State_Selected) { + painter->setPen(option.palette.highlightedText().color()); + } + +// Draw icon + const QModelIndex iconIndex = index.model()->index(index.row(), 0); + const QPixmap icon = iconIndex.data(BluetoothDevicesModel::IconModelRole).value(); + painter->drawPixmap(option.rect.left() + 5, option.rect.top() + 5, icon); + +// Draw alias and device type + const QModelIndex idx = index.model()->index(index.row(), 0); + QRect r = option.rect; + r.setTop(r.top() + 10); + r.setBottom(r.bottom() - 10); + r.setLeft(r.left() + KIconLoader::SizeLarge + 10); + QFont f = kapp->font(); + f.setBold(true); + painter->save(); + painter->setFont(f); + const QString name = idx.data(BluetoothDevicesModel::NameModelRole).toString(); + const QString alias = idx.data(BluetoothDevicesModel::AliasModelRole).toString(); + if (name == alias) { + painter->drawText(r, Qt::AlignLeft | Qt::AlignTop, name); + } else { + painter->drawText(r, Qt::AlignLeft | Qt::AlignTop, QString("%1 (%2)").arg(alias).arg(name)); + } + painter->restore(); + painter->drawText(r, Qt::AlignLeft | Qt::AlignBottom, idx.data(BluetoothDevicesModel::DeviceTypeModelRole).toString()); + +// Draw state + Device *const device = static_cast(index.data(BluetoothDevicesModel::DeviceModelRole).value()); + + r = option.rect; + r.setTop(r.top() + r.height() / 2 - 11); + r.setLeft(r.right() - 5 - 22); + r.setSize(QSize(22, 22)); + + if (!device->isBlocked()) { + if (device->isConnected()) { + painter->drawPixmap(r, m_connectedPixmap); + } else { + painter->drawPixmap(r, m_disconnectedPixmap); + } + + r.setLeft(r.right() - 5 - 22 - 22); + r.setSize(QSize(22, 22)); + + if (device->isTrusted()) { + painter->drawPixmap(r, m_trustedPixmap); + } else { + painter->drawPixmap(r, m_untrustedPixmap); + } + } else { + painter->drawPixmap(r, m_blockedPixmap); + } + +//restore + painter->restore(); +} + +QSize BluetoothDevicesDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + const QSize res = QStyledItemDelegate::sizeHint(option, index); + return QSize(res.width(), KIconLoader::SizeLarge + 10); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +KCMBlueDevilDevices::KCMBlueDevilDevices(QWidget *parent, const QVariantList&) + : KCModule(BlueDevilFactory::componentData(), parent) + , m_enable(new QCheckBox(i18n("Enable KDE Bluetooth Integration"), this)) + , m_systemCheck(new SystemCheck(this)) + , m_deviceDetails(0) +{ + KAboutData* ab = new KAboutData( + "kcmbluedevildevices", "bluedevil", ki18n("Bluetooth Devices"), "1.0", + ki18n("Bluetooth Devices Control Panel Module"), + KAboutData::License_GPL, ki18n("(c) 2010 Rafael Fernández López")); + + ab->addAuthor(ki18n("Rafael Fernández López"), ki18n("Developer and Maintainer"), "ereslibre@kde.org"); + setAboutData(ab); + + connect(m_systemCheck, SIGNAL(updateInformationStateRequest()), + this, SLOT(updateInformationState())); + + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget(m_enable); + + m_enable->setObjectName(QString::fromUtf8("kcfg_enableGlobalBluetooth")); + addConfig(GlobalSettings::self(), this); + + m_isEnabled = m_enable->isChecked(); + + m_systemCheck->createWarnings(layout); + +// Bluetooth device list + m_devicesModel = new BluetoothDevicesModel(this); + + m_devices = new QListView(this); + m_devices->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + m_devices->setItemDelegate(new BluetoothDevicesDelegate(this)); + m_devices->setModel(m_devicesModel); + + connect(m_devices->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(deviceSelectionChanged(QItemSelection))); + connect(m_devices, SIGNAL(doubleClicked(QModelIndex)), + this, SLOT(deviceDoubleClicked(QModelIndex))); + + layout->addWidget(m_devices); + +// Actions buttons + m_detailsDevice = new KPushButton(KIcon("document-properties"), i18nc("Details of the device", "Details")); + m_detailsDevice->setEnabled(false); + m_removeDevice = new KPushButton(KIcon("list-remove"), i18nc("Remove a device from the list of known devices", "Remove")); + m_removeDevice->setEnabled(false); + m_connectDevice = new KPushButton(KIcon("network-connect"), i18n("Connect")); + m_connectDevice->setEnabled(false); + m_disconnectDevice = new KPushButton(KIcon("network-disconnect"), i18n("Disconnect")); + m_disconnectDevice->setEnabled(false); + m_addDevice = new KPushButton(KIcon("list-add"), i18n("Add Device...")); + + connect(m_detailsDevice, SIGNAL(clicked()), this, SLOT(detailsDevice())); + connect(m_removeDevice, SIGNAL(clicked()), this, SLOT(removeDevice())); + connect(m_disconnectDevice, SIGNAL(clicked()), this, SLOT(disconnectDevice())); + connect(m_connectDevice, SIGNAL(clicked()), this, SLOT(connectDevice())); + connect(m_addDevice, SIGNAL(clicked()), this, SLOT(launchWizard())); + + QHBoxLayout *hLayout = new QHBoxLayout; + hLayout->addWidget(m_detailsDevice); + hLayout->addWidget(m_removeDevice); + hLayout->addWidget(m_connectDevice); + hLayout->addWidget(m_disconnectDevice); + hLayout->addStretch(); + hLayout->addWidget(m_addDevice); + layout->addLayout(hLayout); + + setLayout(layout); + +//Logic + connect(BlueDevil::Manager::self(), SIGNAL(usableAdapterChanged(Adapter*)), + this, SLOT(usableAdapterChanged(Adapter*))); + + BlueDevil::Adapter *const usableAdapter = BlueDevil::Manager::self()->usableAdapter(); + if (usableAdapter) { + connect(usableAdapter, SIGNAL(discoverableChanged(bool)), + this, SLOT(adapterDiscoverableChanged())); + connect(usableAdapter, SIGNAL(deviceChanged(Device*)), + this, SLOT(adapterDevicesChanged())); + connect(usableAdapter, SIGNAL(deviceRemoved(Device*)), + this, SLOT(adapterDevicesChanged())); + connect(usableAdapter, SIGNAL(deviceFound(Device*)), + this, SLOT(adapterDevicesChanged())); + } + + fillRemoteDevicesModelInformation(); + updateInformationState(); +} + +KCMBlueDevilDevices::~KCMBlueDevilDevices() +{ +} + +void KCMBlueDevilDevices::defaults() +{ +} + +void KCMBlueDevilDevices::save() +{ + KCModule::save(); + if (!m_isEnabled && m_enable->isChecked()) { + m_systemCheck->kded()->setModuleAutoloading("bluedevil", true); + m_systemCheck->kded()->loadModule("bluedevil"); + } else if (m_isEnabled && !m_enable->isChecked()) { + m_systemCheck->kded()->setModuleAutoloading("bluedevil", false); + m_systemCheck->kded()->unloadModule("bluedevil"); + } + m_isEnabled = m_enable->isChecked(); + updateInformationState(); +} + +void KCMBlueDevilDevices::deviceSelectionChanged(const QItemSelection &selection) +{ + const bool enable = !selection.isEmpty(); + m_detailsDevice->setEnabled(enable); + m_removeDevice->setEnabled(enable); + m_connectDevice->setEnabled(enable); + m_disconnectDevice->setEnabled(false); + + if (!m_devices->currentIndex().isValid()) { + return; + } + + Device *const device = static_cast(m_devices->currentIndex().data(BluetoothDevicesModel::DeviceModelRole).value()); + m_disconnectDevice->setEnabled(device->isConnected()); + + if (device->isConnected()) { + m_connectDevice->setText(i18n("Re-connect")); + } else { + m_connectDevice->setText(i18n("Connect")); + } +} + +void KCMBlueDevilDevices::deviceDoubleClicked(const QModelIndex &index) +{ + if (!index.isValid()) { + return; + } + + Device *const device = static_cast(index.data(BluetoothDevicesModel::DeviceModelRole).value()); + + m_deviceDetails = new DeviceDetails(device, this); + m_deviceDetails->exec(); + delete m_deviceDetails; + m_deviceDetails = 0; +} + +void KCMBlueDevilDevices::detailsDevice() +{ + Device *const device = static_cast(m_devices->currentIndex().data(BluetoothDevicesModel::DeviceModelRole).value()); + + m_deviceDetails = new DeviceDetails(device, this); + m_deviceDetails->exec(); + delete m_deviceDetails; + m_deviceDetails = 0; +} + + +void KCMBlueDevilDevices::renameAliasDevice() +{ + Device *const device = static_cast(m_devices->currentIndex().data(BluetoothDevicesModel::DeviceModelRole).value()); + KDialog *newAlias = new KDialog(this); + QWidget *widget = new QWidget(newAlias); + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget(new QLabel(i18n("Pick a new alias for %1", device->name()), widget)); + KLineEdit *lineEdit = new KLineEdit(widget); + lineEdit->setText(device->alias()); + lineEdit->selectAll(); + layout->addWidget(lineEdit); + widget->setLayout(layout); + newAlias->setMainWidget(widget); + newAlias->setButtons(KDialog::Ok | KDialog::Cancel); + if (newAlias->exec() == KDialog::Accepted) { + if (lineEdit->text().isEmpty()) { + device->setAlias(device->name()); + } else { + device->setAlias(lineEdit->text()); + } + } + delete newAlias; +} + +void KCMBlueDevilDevices::removeDevice() +{ + m_removeDevice->setEnabled(false); + Device *const device = static_cast(m_devices->currentIndex().data(BluetoothDevicesModel::DeviceModelRole).value()); + + QString ubi = device->UBI(); + if (KMessageBox::questionYesNo(this, i18n("Are you sure that you want to remove device \"%1\" from the list of known devices?", device->alias()), + i18nc("Title of window that asks for confirmation when removing a device", "Device removal")) == KMessageBox::Yes) { + QList deviceList = BlueDevil::Manager::self()->usableAdapter()->devices(); + Q_FOREACH(Device *item, deviceList) { + if (item->UBI() == ubi) { + BlueDevil::Manager::self()->usableAdapter()->removeDevice(device); + return; + } + } + } else { + m_removeDevice->setEnabled(true); + } +} + +void KCMBlueDevilDevices::connectDevice() +{ + Device *const device = static_cast(m_devices->currentIndex().data(BluetoothDevicesModel::DeviceModelRole).value()); + device->connectDevice(); +} + +void KCMBlueDevilDevices::disconnectDevice() +{ + m_disconnectDevice->setEnabled(false); + Device *const device = static_cast(m_devices->currentIndex().data(BluetoothDevicesModel::DeviceModelRole).value()); + device->disconnect(); +} + +void KCMBlueDevilDevices::launchWizard() +{ + KProcess wizard; + wizard.setProgram("bluedevil-wizard"); + wizard.startDetached(); +} + +void KCMBlueDevilDevices::usableAdapterChanged(Adapter *adapter) +{ + if (adapter) { + connect(adapter, SIGNAL(discoverableChanged(bool)), + this, SLOT(adapterDiscoverableChanged())); + connect(adapter, SIGNAL(devicesChanged(QList)), + this, SLOT(adapterDevicesChanged())); + } + fillRemoteDevicesModelInformation(); + QTimer::singleShot(300, this, SLOT(updateInformationState())); +} + +void KCMBlueDevilDevices::adapterDiscoverableChanged() +{ + QTimer::singleShot(300, this, SLOT(updateInformationState())); +} + +void KCMBlueDevilDevices::adapterDevicesChanged() +{ + if (m_deviceDetails) { + delete m_deviceDetails; + m_deviceDetails = 0; + } + fillRemoteDevicesModelInformation(); +} + +void KCMBlueDevilDevices::generateNoDevicesMessage() +{ + QGridLayout *layout = new QGridLayout; + m_noDevicesMessage = new QWidget(this); + m_noDevicesMessage->setMouseTracking(true); + m_noDevicesMessage->setBackgroundRole(QPalette::Base); + m_noDevicesMessage->setAutoFillBackground(true); + QLabel *label = new QLabel(m_noDevicesMessage); + label->setPixmap(KIcon("dialog-information").pixmap(128, 128)); + layout->addWidget(label, 0, 1, Qt::AlignHCenter); + layout->addWidget(new QLabel(i18n("No remote devices have been added"), m_noDevicesMessage), + 1, 1, Qt::AlignHCenter); + KPushButton *const addDevice = new KPushButton(KIcon("list-add"), i18n("Click here to add a remote device")); + connect(addDevice, SIGNAL(clicked()), this, SLOT(launchWizard())); + layout->addWidget(addDevice, 2, 1, Qt::AlignHCenter); + layout->setRowStretch(3, 1); + layout->setColumnStretch(0, 1); + layout->setColumnStretch(2, 1); + m_noDevicesMessage->setLayout(layout); + m_noDevicesMessage->setVisible(false); +} + +void KCMBlueDevilDevices::fillRemoteDevicesModelInformation() +{ + m_devicesModel->removeRows(0, m_devicesModel->rowCount()); + Adapter *usableAdapter = BlueDevil::Manager::self()->usableAdapter(); + QList deviceList; + if (usableAdapter) { + deviceList = usableAdapter->devices(); + } + if (deviceList.isEmpty()) { + generateNoDevicesMessage(); + m_devices->setViewport(m_noDevicesMessage); + m_noDevicesMessage->setVisible(true); + return; + } else if (m_devices->viewport() == m_noDevicesMessage) { + QWidget *viewport = new QWidget(this); + viewport->setMouseTracking(true); + viewport->setBackgroundRole(QPalette::Base); + viewport->setAutoFillBackground(true); + m_devices->setViewport(viewport); + } + m_devicesModel->insertRows(0, deviceList.count()); + int i = 0; + Q_FOREACH (Device *const device, deviceList) { + QModelIndex index = m_devicesModel->index(i, 0); + m_devicesModel->setData(index, KIcon(device->icon()).pixmap(48, 48), BluetoothDevicesModel::IconModelRole); + QString deviceType; + const quint32 type = BlueDevil::classToType(device->deviceClass()); + switch (type) { + case BlueDevil::BLUETOOTH_TYPE_ANY: + deviceType = i18nc("Type of device: could not be determined", "Unknown"); + break; + case BlueDevil::BLUETOOTH_TYPE_PHONE: + deviceType = i18nc("This device is a Phone", "Phone"); + break; + case BlueDevil::BLUETOOTH_TYPE_MODEM: + deviceType = i18nc("This device is a Modem", "Modem"); + break; + case BlueDevil::BLUETOOTH_TYPE_COMPUTER: + deviceType = i18nc("This device is a Computer", "Computer"); + break; + case BlueDevil::BLUETOOTH_TYPE_NETWORK: + deviceType = i18nc("This device is of type Network", "Network"); + break; + case BlueDevil::BLUETOOTH_TYPE_HEADSET: + deviceType = i18nc("This device is a Headset", "Headset"); + break; + case BlueDevil::BLUETOOTH_TYPE_HEADPHONES: + deviceType = i18nc("This device are Headphones", "Headphones"); + break; + case BlueDevil::BLUETOOTH_TYPE_OTHER_AUDIO: + deviceType = i18nc("This device is of type Audio", "Audio"); + break; + case BlueDevil::BLUETOOTH_TYPE_KEYBOARD: + deviceType = i18nc("This device is a Keyboard", "Keyboard"); + break; + case BlueDevil::BLUETOOTH_TYPE_MOUSE: + deviceType = i18nc("This device is a Mouse", "Mouse"); + break; + case BlueDevil::BLUETOOTH_TYPE_CAMERA: + deviceType = i18nc("This device is a Camera", "Camera"); + break; + case BlueDevil::BLUETOOTH_TYPE_PRINTER: + deviceType = i18nc("This device is a Printer", "Printer"); + break; + case BlueDevil::BLUETOOTH_TYPE_JOYPAD: + deviceType = i18nc("This device is a Joypad", "Joypad"); + break; + case BlueDevil::BLUETOOTH_TYPE_TABLET: + deviceType = i18nc("This device is a Tablet", "Tablet"); + break; + default: + deviceType = i18nc("Type of device: could not be determined", "Unknown"); + break; + } + m_devicesModel->setData(index, i18nc("Type of remote device (e.g. Camera, Mouse, Headset...)", "Type: %1", deviceType), BluetoothDevicesModel::DeviceTypeModelRole); + m_devicesModel->setData(index, QVariant::fromValue(device), BluetoothDevicesModel::DeviceModelRole); + ++i; + } +} + +void KCMBlueDevilDevices::updateInformationState() +{ + m_systemCheck->updateInformationState(); + + m_addDevice->setEnabled(false); + m_devices->setEnabled(false); + + if (m_isEnabled) { + BlueDevil::Adapter *const usableAdapter = BlueDevil::Manager::self()->usableAdapter(); + if (usableAdapter) { + m_addDevice->setEnabled(true); + m_devices->setEnabled(true); + } + } +} diff --git a/bluedevil/src/kcmodule/bluedevildevices.desktop b/bluedevil/src/kcmodule/bluedevildevices.desktop new file mode 100644 index 00000000..2ea1ede6 --- /dev/null +++ b/bluedevil/src/kcmodule/bluedevildevices.desktop @@ -0,0 +1,142 @@ +[Desktop Entry] +Exec=kcmshell4 bluedevildevices +Icon=input-mouse +Type=Service + +X-KDE-ServiceTypes=KCModule + +X-KDE-Library=kcm_bluedevildevices +X-KDE-ParentApp=kcontrol + +X-KDE-System-Settings-Parent-Category=bluetooth +X-KDE-Weight=50 + +Name=Devices +Name[ar]=الأجهزة +Name[bs]=uređaji +Name[ca]=Dispositius +Name[ca@valencia]=Dispositius +Name[cs]=Zařízení +Name[da]=Enheder +Name[de]=Geräte +Name[el]=Συσκευές +Name[en_GB]=Devices +Name[es]=Dispositivos +Name[et]=Seadmed +Name[fa]=دستگاهها +Name[fi]=Laitteet +Name[fr]=Périphériques +Name[ga]=Gléasanna +Name[gl]=Dispositivos +Name[hu]=Eszközök +Name[it]=Dispositivi +Name[kk]=Құрылғылар +Name[km]=ឧបករណ៍​ +Name[lt]=Įrenginiai +Name[mai]=डिवाइस +Name[mr]=साधने +Name[ms]=Peranti +Name[nb]=Enheter +Name[nds]=Reedschappen +Name[nl]=Apparaten +Name[pa]=ਜੰਤਰ +Name[pl]=Urządzenia +Name[pt]=Dispositivos +Name[pt_BR]=Dispositivos +Name[ro]=Dispozitive +Name[ru]=Устройства +Name[sk]=Zariadenia +Name[sl]=Naprave +Name[sr]=Уређаји +Name[sr@ijekavian]=Уређаји +Name[sr@ijekavianlatin]=Uređaji +Name[sr@latin]=Uređaji +Name[sv]=Enheter +Name[th]=อุปกรณ์ต่างๆ +Name[tr]=Aygıtlar +Name[ug]=ئۈسكۈنىلەر +Name[uk]=Пристрої +Name[x-test]=xxDevicesxx +Name[zh_CN]=设备 +Name[zh_TW]=裝置 + +Comment=Manage Bluetooth devices +Comment[ca]=Gestiona els dispositius Bluetooth +Comment[ca@valencia]=Gestiona els dispositius Bluetooth +Comment[cs]=Spravovat zařízení Bluetooth +Comment[da]=Håndtér Bluetooth-enheder +Comment[de]=Bluetooth-Geräte verwalten +Comment[el]=Διαχείριση συσκευών Bluetooth +Comment[es]=Gestión de dispositivos Bluetooth +Comment[fi]=Bluetooth-laitteiden asetukset +Comment[fr]=Gère les périphériques Bluetooth +Comment[gl]=Xestionar os dispositivos Bluetooth +Comment[hu]=Bluetooth eszközök kezelése +Comment[it]=Gestisci i dispositivi Bluetooth +Comment[kk]=Bluetooth құрылығыларын басқару +Comment[lt]=Konfigūruoti Bluetooth failų gavimą +Comment[mr]=ब्लूटूथ साधने व्यवस्थापीत करा +Comment[nb]=Håndter Blåtann-enheter +Comment[nl]=Bluetooth-apparaten beheren +Comment[pa]=ਬਲਿਊਟੁੱਥ ਜੰਤਰ ਪਰਬੰਧ +Comment[pl]=Zarządzaj urządzeniami Bluetooth +Comment[pt]=Gerir os dispositivos Bluetooth +Comment[pt_BR]=Gerencia os dispositivo Bluetooth +Comment[ro]=Gestionează dispozitivele Bluetooth +Comment[ru]=Управление устройствами Bluetooth +Comment[sk]=Spravovať Bluetooth zariadenia +Comment[sl]=Nastavite naprave Bluetooth +Comment[sr]=Управљајте блутут уређајима +Comment[sr@ijekavian]=Управљајте блутут уређајима +Comment[sr@ijekavianlatin]=Upravljajte bluetooth uređajima +Comment[sr@latin]=Upravljajte bluetooth uređajima +Comment[sv]=Hantera Blåtandenheter +Comment[tr]=Bluetooth aygıtlarını yönet +Comment[ug]=كۆكچىش ئۈسكۈنىلىرىنى باشقۇرۇش +Comment[uk]=Керування пристроями Bluetooth +Comment[x-test]=xxManage Bluetooth devicesxx +Comment[zh_TW]=管理藍牙裝置 + +X-KDE-Keywords=Network,Connectivity,Bluetooth +X-KDE-Keywords[ar]=شبكة,توصيل,بلوتوث +X-KDE-Keywords[bs]=Network,Connectivity,Bluetooth +X-KDE-Keywords[ca]=Xarxa,Connectivitat,Bluetooth +X-KDE-Keywords[ca@valencia]=Xarxa,Connectivitat,Bluetooth +X-KDE-Keywords[cs]=Síť,Konektivita,Bluetooth +X-KDE-Keywords[da]=Netværk,forbindelse,Bluetooth +X-KDE-Keywords[de]=Netzwerk,Verbindungen,Bluetooth +X-KDE-Keywords[el]=Δίκτυο,Συνδεσιμότητα,Bluetooth +X-KDE-Keywords[es]=Red,Conectividad,Bluetooth +X-KDE-Keywords[et]=Võrk,Ühenduvus,Bluetooth +X-KDE-Keywords[fi]=Verkko,Yhteydet,Bluetooth +X-KDE-Keywords[fr]=Réseau, connectivité, Bluetooth +X-KDE-Keywords[gl]=Network,Connectivity,Bluetooth,Rede,Conectividade +X-KDE-Keywords[hu]=Hálózat,Kapcsolódás,Bluetooth +X-KDE-Keywords[it]=Rete,Connettività,Bluetooth +X-KDE-Keywords[kk]=Network,Connectivity,Bluetooth +X-KDE-Keywords[km]=បណ្ដាញ ការ​តភ្ជាប់ ប៊្លូធូស +X-KDE-Keywords[lt]=Tinklas,Junglumas,Bluetooth +X-KDE-Keywords[mr]=संजाळ,जुळवणी,ब्लूटूथ +X-KDE-Keywords[nb]=Nettverk,sammenkobling,blåtann +X-KDE-Keywords[nds]=Nettwark,Verbinnen,Bluetooth +X-KDE-Keywords[nl]=Netwerkverbinding,connectiviteit,bluetooth +X-KDE-Keywords[pa]=ਨੈੱਟਵਰਕ,ਕੁਨੈਕਟਵਿਟੀ,ਬਲਿਊਟੁੱਥ +X-KDE-Keywords[pl]=Sieć,Łączność,Bluetooth +X-KDE-Keywords[pt]=Rede,Conectividade,Bluetooth +X-KDE-Keywords[pt_BR]=rede,conectividade,bluetooth +X-KDE-Keywords[ro]=Rețea,Conectivitate,Bluetooth +X-KDE-Keywords[ru]=Сеть,Соединение,Bluetooth +X-KDE-Keywords[sk]=Sieť,Konektivita,Bluetooth +X-KDE-Keywords[sl]=Omrežje,Povezave,Bluetooth +X-KDE-Keywords[sr]=Network,Connectivity,Bluetooth,мрежа,повезивање,блутут +X-KDE-Keywords[sr@ijekavian]=Network,Connectivity,Bluetooth,мрежа,повезивање,блутут +X-KDE-Keywords[sr@ijekavianlatin]=Network,Connectivity,Bluetooth,mreža,povezivanje,Bluetooth +X-KDE-Keywords[sr@latin]=Network,Connectivity,Bluetooth,mreža,povezivanje,Bluetooth +X-KDE-Keywords[sv]=Nätverk,Anslutningar,Blåtand +X-KDE-Keywords[tr]=Ağ, Bağlanılabilirlik, Bluetooth +X-KDE-Keywords[uk]=Network,Connectivity,Bluetooth,мережа,з’єднання,з'єднання +X-KDE-Keywords[x-test]=xxNetwork,Connectivity,Bluetoothxx +X-KDE-Keywords[zh_CN]=Network,Connectivity,Bluetooth,网络,连接,蓝牙 +X-KDE-Keywords[zh_TW]=Network,Connectivity,Bluetooth + +Categories=Qt;KDE;X-KDE-settings-bluetooth; diff --git a/bluedevil/src/kcmodule/bluedevildevices.h b/bluedevil/src/kcmodule/bluedevildevices.h new file mode 100644 index 00000000..8df7e106 --- /dev/null +++ b/bluedevil/src/kcmodule/bluedevildevices.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2010 Rafael Fernández López + * Copyright (C) 2010 UFO Coders + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _BLUEDEVILDEVICES_H +#define _BLUEDEVILDEVICES_H + +#include + +#include + +class SystemCheck; +class DeviceDetails; +class BluetoothDevicesModel; + +class QListView; +class QCheckBox; + +class KPushButton; + +namespace BlueDevil { + class Adapter; + class Device; +} + +typedef BlueDevil::Adapter Adapter; +typedef BlueDevil::Device Device; + +class KCMBlueDevilDevices + : public KCModule +{ + Q_OBJECT + +public: + KCMBlueDevilDevices(QWidget *parent, const QVariantList&); + virtual ~KCMBlueDevilDevices(); + + virtual void defaults(); + virtual void save(); + +private Q_SLOTS: + void deviceSelectionChanged(const QItemSelection &selection); + void deviceDoubleClicked(const QModelIndex &index); + void detailsDevice(); + void renameAliasDevice(); + void removeDevice(); + void connectDevice(); + void disconnectDevice(); + void launchWizard(); + + void usableAdapterChanged(Adapter *adapter); + void adapterDiscoverableChanged(); + void adapterDevicesChanged(); + + void updateInformationState(); + +private: + void generateNoDevicesMessage(); + void fillRemoteDevicesModelInformation(); + +private: + QCheckBox *m_enable; + KPushButton *m_detailsDevice; + KPushButton *m_removeDevice; + KPushButton *m_connectDevice; + KPushButton *m_disconnectDevice; + KPushButton *m_addDevice; + bool m_isEnabled; + BluetoothDevicesModel *m_devicesModel; + QListView *m_devices; + QWidget *m_noDevicesMessage; + + SystemCheck *m_systemCheck; + DeviceDetails *m_deviceDetails; +}; + +#endif diff --git a/bluedevil/src/kcmodule/bluedeviltransfer.cpp b/bluedevil/src/kcmodule/bluedeviltransfer.cpp new file mode 100644 index 00000000..1a4f4a6b --- /dev/null +++ b/bluedevil/src/kcmodule/bluedeviltransfer.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2010 Rafael Fernández López + * Copyright (C) 2010 UFO Coders + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "bluedeviltransfer.h" +#include "systemcheck.h" +#include "columnresizer.h" +#include "ui_transfer.h" +#include "filereceiversettings.h" +#include "bluedevil_service.h" +#include "sharedfilesdialog/sharedfilesdialog.h" + +#include + +#include + +#include + +#include +#include +#include +#include +#include + +K_PLUGIN_FACTORY(BlueDevilFactory, registerPlugin();) +K_EXPORT_PLUGIN(BlueDevilFactory("bluedeviltransfer", "bluedevil")) + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +KCMBlueDevilTransfer::KCMBlueDevilTransfer(QWidget *parent, const QVariantList&) + : KCModule(BlueDevilFactory::componentData(), parent) + , m_systemCheck(new SystemCheck(this)) + , m_restartNeeded(false) +{ + KAboutData* ab = new KAboutData( + "kcmbluedeviltransfer", "bluedevil", ki18n("Bluetooth Transfer"), "1.0", + ki18n("Bluetooth Transfer Control Panel Module"), + KAboutData::License_GPL, ki18n("(c) 2010 Rafael Fernández López")); + + ab->addAuthor(ki18n("Rafael Fernández López"), ki18n("Developer and Maintainer"), "ereslibre@kde.org"); + setAboutData(ab); + + connect(m_systemCheck, SIGNAL(updateInformationStateRequest()), + this, SLOT(updateInformationState())); + connect(this, SIGNAL(changed(bool)), this, SLOT(changed(bool))); + + QVBoxLayout *layout = new QVBoxLayout; + m_systemCheck->createWarnings(layout); + + QWidget *transfer = new QWidget(this); + m_uiTransfer = new Ui::Transfer(); + m_uiTransfer->setupUi(transfer); + layout->addWidget(transfer); + setLayout(layout); + + m_uiTransfer->kcfg_saveUrl->lineEdit()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + m_uiTransfer->kcfg_autoAccept->addItem(i18nc("'Auto accept' option value", "Never"), QVariant(0)); + m_uiTransfer->kcfg_autoAccept->addItem(i18nc("'Auto accept' option value", "Trusted devices"), QVariant(1)); + m_uiTransfer->kcfg_autoAccept->addItem(i18nc("'Auto accept' option value", "All devices"), QVariant(2)); + + m_uiTransfer->kcfg_requirePin->addItem(i18nc("'Require PIN' option value", "Never"), QVariant(false)); + m_uiTransfer->kcfg_requirePin->addItem(i18nc("'Require PIN' option value", "Always"), QVariant(true)); + + m_uiTransfer->kcfg_allowWrite->addItem(i18nc("'Permissions' option value", "Read Only"), QVariant(false)); + m_uiTransfer->kcfg_allowWrite->addItem(i18nc("'Permissions' option value", "Modify and Read"), QVariant(true)); + + addConfig(FileReceiverSettings::self(), transfer); + + connect(m_uiTransfer->sharedFiles, SIGNAL(clicked(bool)), this, SLOT(showSharedFilesDialog())); + connect(BlueDevil::Manager::self(), SIGNAL(usableAdapterChanged(Adapter*)), + this, SLOT(usableAdapterChanged(Adapter*))); + + BlueDevil::Adapter *const usableAdapter = BlueDevil::Manager::self()->usableAdapter(); + if (usableAdapter) { + connect(usableAdapter, SIGNAL(discoverableChanged(bool)), + this, SLOT(adapterDiscoverableChanged())); + } + + + updateInformationState(); + + ColumnResizer *resizer = new ColumnResizer(this); + resizer->addWidgetsFromFormLayout(m_uiTransfer->formLayout, QFormLayout::LabelRole); + resizer->addWidgetsFromFormLayout(m_uiTransfer->formLayout_2, QFormLayout::LabelRole); +} + +KCMBlueDevilTransfer::~KCMBlueDevilTransfer() +{ +} + +void KCMBlueDevilTransfer::save() +{ + if (!m_restartNeeded) { + return; + } + + KCModule::save(); + + org::kde::BlueDevil::Service *service = new org::kde::BlueDevil::Service( + "org.kde.BlueDevil.Service", + "/Service", + QDBusConnection::sessionBus(), this); + if (service->isRunning()) { + service->stopServer(); + } + + service->launchServer(); +} + +void KCMBlueDevilTransfer::usableAdapterChanged(Adapter *adapter) +{ + if (adapter) { + connect(adapter, SIGNAL(discoverableChanged(bool)), + this, SLOT(adapterDiscoverableChanged())); + } + QTimer::singleShot(300, this, SLOT(updateInformationState())); +} + +void KCMBlueDevilTransfer::adapterDiscoverableChanged() +{ + QTimer::singleShot(300, this, SLOT(updateInformationState())); +} + +void KCMBlueDevilTransfer::updateInformationState() +{ + m_systemCheck->updateInformationState(); +} + +void KCMBlueDevilTransfer::showSharedFilesDialog() +{ + SharedFilesDialog *d = new SharedFilesDialog(); + d->exec(); +} + +void KCMBlueDevilTransfer::changed(bool changed) +{ + m_restartNeeded = changed; +} diff --git a/bluedevil/src/kcmodule/bluedeviltransfer.desktop b/bluedevil/src/kcmodule/bluedeviltransfer.desktop new file mode 100644 index 00000000..d835c962 --- /dev/null +++ b/bluedevil/src/kcmodule/bluedeviltransfer.desktop @@ -0,0 +1,136 @@ +[Desktop Entry] +Exec=kcmshell4 bluedeviltransfer +Icon=folder-tar +Type=Service + +X-KDE-ServiceTypes=KCModule + +X-KDE-Library=kcm_bluedeviltransfer +X-KDE-ParentApp=kcontrol + +X-KDE-System-Settings-Parent-Category=bluetooth +X-KDE-Weight=60 + +Name=File Transfers +Name[ar]=نقل الملفات +Name[bs]=Transfer datoteke +Name[ca]=Transferències de fitxers +Name[ca@valencia]=Transferències de fitxers +Name[cs]=Přenosy souborů +Name[da]=Filoverførsler +Name[de]=Dateiübertragungen +Name[el]=Μεταφορές αρχείων +Name[es]=Transferencias de archivo +Name[et]=Failiedastused +Name[fi]=Tiedostonsiirto +Name[fr]=Transferts de fichiers +Name[gl]=Transferencias de ficheiros +Name[hu]=Fájlátvitelek +Name[it]=Trasferimenti di file +Name[kk]=Файл тасымалдаулары +Name[km]=ផ្ទេរ​ឯកសារ​​ +Name[lt]=Failų perdavimai +Name[mr]=फाईल देवाणघेवाण +Name[nb]=Filoverføringer +Name[nds]=Dateiöverdregen +Name[nl]=Bestandsoverdrachten +Name[pa]=ਫਾਇਲ ਟਰਾਂਸਫਰ +Name[pl]=Przesyłania plików +Name[pt]=Transferência de Ficheiros +Name[pt_BR]=Transferência de arquivos +Name[ro]=Transferuri de fișiere +Name[ru]=Приём файлов +Name[sk]=Prenosy súborov +Name[sl]=Prenosi datotek +Name[sr]=Преноси фајлова +Name[sr@ijekavian]=Преноси фајлова +Name[sr@ijekavianlatin]=Prenosi fajlova +Name[sr@latin]=Prenosi fajlova +Name[sv]=Filöverföringar +Name[tr]=Dosya Aktarımı +Name[ug]=ھۆججەت يوللاش +Name[uk]=Перенесення файла +Name[x-test]=xxFile Transfersxx +Name[zh_CN]=文件传送 +Name[zh_TW]=檔案傳輸 + +Comment=Configure Bluetooth file sharing and transfers +Comment[ca]=Configura la compartició i transferència de fitxers per Bluetooth +Comment[ca@valencia]=Configura la compartició i transferència de fitxers per Bluetooth +Comment[cs]=Nastavte sdílení souborů přes Bluetooth +Comment[da]=Indstil fildeling og -overførsler via Bluetooth +Comment[de]=Dateifreigaben und Dateiübertragung für Bluetooth einrichten +Comment[el]=Διαμόρφωση διαμοιρασμού και μεταφοράς αρχείων με Bluetooth +Comment[es]=Configurar compartición y transferencias de archivos por Bluetooth +Comment[fi]=Bluetooth-tiedostonjaon ja -siirron asetukset +Comment[fr]=Configure le partage et les transferts de fichiers par Bluetooth +Comment[gl]=Configurar a compartición e a transferencia de ficheiros por Bluetooth +Comment[hu]=Bluetooth fájlmegosztás és átvitelek beállítása +Comment[it]=Configura la condivisione e il trasferimento di file via Bluetooth +Comment[kk]=Bluetooth файл ортақтастыру және тасымалдауды баптау +Comment[lt]=Konfigūruoti Bluetooth failų gavimą +Comment[mr]=ब्लूटूथ फाईल शेअर करणे व बदली संयोजीत करा +Comment[nb]=Sett opp deling og overføring av filer over Blåtann +Comment[nl]=Bestandsdeling en -overdracht via bluetooth instellen +Comment[pa]=ਬਲਿਊਟੁੱਥ ਫਾਇਲ ਸਾਂਝ ਤੇ ਟਰਾਂਸਫਰ ਸੰਰਚਨਾ +Comment[pl]=Konfiguruj udostępnianie i przesyłanie plików przez Bluetooth +Comment[pt]=Configura a partilha e as transferências de ficheiros por Bluetooth +Comment[pt_BR]=Configura o compartilhamento e as transferências de arquivos por Bluetooth +Comment[ro]=Configurează partajarea și transferul de fișiere Bluetooth +Comment[ru]=Настройка доступа и передачи файлов через Bluetooth +Comment[sk]=Nastaviť zdieľanie súborov a prenosy Bluetooth +Comment[sl]=Nastavite souporabo in prenose datotek preko Bluetooth-a +Comment[sr]=Подесите преносе и дељење фајлова преко блутутом +Comment[sr@ijekavian]=Подесите преносе и дељење фајлова преко блутутом +Comment[sr@ijekavianlatin]=Podesite prenose i deljenje fajlova preko Bluetoothom +Comment[sr@latin]=Podesite prenose i deljenje fajlova preko Bluetoothom +Comment[sv]=Anpassa fildelning och överföringar med Blåtand +Comment[tr]=Bluetooth dosya paylaşımı ve transferlerini yapılandır +Comment[ug]=كۆكچىشتا ھۆججەت ھەمبەھىرلەش ۋە ئەۋەتىشنى سەپلەش +Comment[uk]=Налаштування надсилання та отримання файлів Bluetooth +Comment[x-test]=xxConfigure Bluetooth file sharing and transfersxx +Comment[zh_TW]=設定藍牙檔案共用與傳輸 + +X-KDE-Keywords=Network,Connectivity,Bluetooth +X-KDE-Keywords[ar]=شبكة,توصيل,بلوتوث +X-KDE-Keywords[bs]=Network,Connectivity,Bluetooth +X-KDE-Keywords[ca]=Xarxa,Connectivitat,Bluetooth +X-KDE-Keywords[ca@valencia]=Xarxa,Connectivitat,Bluetooth +X-KDE-Keywords[cs]=Síť,Konektivita,Bluetooth +X-KDE-Keywords[da]=Netværk,forbindelse,Bluetooth +X-KDE-Keywords[de]=Netzwerk,Verbindungen,Bluetooth +X-KDE-Keywords[el]=Δίκτυο,Συνδεσιμότητα,Bluetooth +X-KDE-Keywords[es]=Red,Conectividad,Bluetooth +X-KDE-Keywords[et]=Võrk,Ühenduvus,Bluetooth +X-KDE-Keywords[fi]=Verkko,Yhteydet,Bluetooth +X-KDE-Keywords[fr]=Réseau, connectivité, Bluetooth +X-KDE-Keywords[gl]=Network,Connectivity,Bluetooth,Rede,Conectividade +X-KDE-Keywords[hu]=Hálózat,Kapcsolódás,Bluetooth +X-KDE-Keywords[it]=Rete,Connettività,Bluetooth +X-KDE-Keywords[kk]=Network,Connectivity,Bluetooth +X-KDE-Keywords[km]=បណ្ដាញ ការ​តភ្ជាប់ ប៊្លូធូស +X-KDE-Keywords[lt]=Tinklas,Junglumas,Bluetooth +X-KDE-Keywords[mr]=संजाळ,जुळवणी,ब्लूटूथ +X-KDE-Keywords[nb]=Nettverk,sammenkobling,blåtann +X-KDE-Keywords[nds]=Nettwark,Verbinnen,Bluetooth +X-KDE-Keywords[nl]=Netwerkverbinding,connectiviteit,bluetooth +X-KDE-Keywords[pa]=ਨੈੱਟਵਰਕ,ਕੁਨੈਕਟਵਿਟੀ,ਬਲਿਊਟੁੱਥ +X-KDE-Keywords[pl]=Sieć,Łączność,Bluetooth +X-KDE-Keywords[pt]=Rede,Conectividade,Bluetooth +X-KDE-Keywords[pt_BR]=rede,conectividade,bluetooth +X-KDE-Keywords[ro]=Rețea,Conectivitate,Bluetooth +X-KDE-Keywords[ru]=Сеть,Соединение,Bluetooth +X-KDE-Keywords[sk]=Sieť,Konektivita,Bluetooth +X-KDE-Keywords[sl]=Omrežje,Povezave,Bluetooth +X-KDE-Keywords[sr]=Network,Connectivity,Bluetooth,мрежа,повезивање,блутут +X-KDE-Keywords[sr@ijekavian]=Network,Connectivity,Bluetooth,мрежа,повезивање,блутут +X-KDE-Keywords[sr@ijekavianlatin]=Network,Connectivity,Bluetooth,mreža,povezivanje,Bluetooth +X-KDE-Keywords[sr@latin]=Network,Connectivity,Bluetooth,mreža,povezivanje,Bluetooth +X-KDE-Keywords[sv]=Nätverk,Anslutningar,Blåtand +X-KDE-Keywords[tr]=Ağ, Bağlanılabilirlik, Bluetooth +X-KDE-Keywords[uk]=Network,Connectivity,Bluetooth,мережа,з’єднання,з'єднання +X-KDE-Keywords[x-test]=xxNetwork,Connectivity,Bluetoothxx +X-KDE-Keywords[zh_CN]=Network,Connectivity,Bluetooth,网络,连接,蓝牙 +X-KDE-Keywords[zh_TW]=Network,Connectivity,Bluetooth + +Categories=Qt;KDE;X-KDE-settings-bluetooth; diff --git a/bluedevil/src/kcmodule/bluedeviltransfer.h b/bluedevil/src/kcmodule/bluedeviltransfer.h new file mode 100644 index 00000000..87f205a4 --- /dev/null +++ b/bluedevil/src/kcmodule/bluedeviltransfer.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010 Rafael Fernández López + * Copyright (C) 2010 UFO Coders + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _BLUEDEVILTRANSFER_H +#define _BLUEDEVILTRANSFER_H + +#include + +class SystemCheck; +namespace Ui { + class Transfer; +} +namespace BlueDevil { + class Adapter; +} + +typedef BlueDevil::Adapter Adapter; + +class KCMBlueDevilTransfer + : public KCModule +{ + Q_OBJECT + +public: + KCMBlueDevilTransfer(QWidget *parent, const QVariantList&); + virtual ~KCMBlueDevilTransfer(); + + virtual void save(); +private Q_SLOTS: + void usableAdapterChanged(Adapter *adapter); + void adapterDiscoverableChanged(); + void updateInformationState(); + void showSharedFilesDialog(); + void changed(bool); + +private: + SystemCheck *m_systemCheck; + Ui::Transfer *m_uiTransfer; + bool m_restartNeeded; +}; + +#endif diff --git a/bluedevil/src/kcmodule/columnresizer.cpp b/bluedevil/src/kcmodule/columnresizer.cpp new file mode 100644 index 00000000..1a74f72b --- /dev/null +++ b/bluedevil/src/kcmodule/columnresizer.cpp @@ -0,0 +1,202 @@ +/* + * Copyright 2011 Aurélien Gâteau + * License: LGPL v2.1 or later (see COPYING) + */ +#include + +#include +#include +#include +#include +#include +#include + +class FormLayoutWidgetItem : public QWidgetItem +{ +public: + FormLayoutWidgetItem(QWidget* widget, QFormLayout* formLayout, QFormLayout::ItemRole itemRole) + : QWidgetItem(widget) + , m_width(-1) + , m_formLayout(formLayout) + , m_itemRole(itemRole) + {} + + QSize sizeHint() const + { + QSize size = QWidgetItem::sizeHint(); + if (m_width != -1) { + size.setWidth(m_width); + } + return size; + } + + QSize minimumSize() const + { + QSize size = QWidgetItem::minimumSize(); + if (m_width != -1) { + size.setWidth(m_width); + } + return size; + } + + QSize maximumSize() const + { + QSize size = QWidgetItem::maximumSize(); + if (m_width != -1) { + size.setWidth(m_width); + } + return size; + } + + void setWidth(int width) + { + if (width != m_width) { + m_width = width; + invalidate(); + } + } + + void setGeometry(const QRect& _rect) + { + QRect rect = _rect; + int width = widget()->sizeHint().width(); + if (m_itemRole == QFormLayout::LabelRole && m_formLayout->labelAlignment() & Qt::AlignRight) { + rect.setLeft(rect.right() - width); + } + QWidgetItem::setGeometry(rect); + } + + QFormLayout* formLayout() const + { + return m_formLayout; + } + +private: + int m_width; + QFormLayout* m_formLayout; + QFormLayout::ItemRole m_itemRole; +}; + +typedef QPair GridColumnInfo; + +class ColumnResizerPrivate +{ +public: + ColumnResizerPrivate(ColumnResizer* q_ptr) + : q(q_ptr) + , m_updateTimer(new QTimer(q)) + { + m_updateTimer->setSingleShot(true); + m_updateTimer->setInterval(0); + QObject::connect(m_updateTimer, SIGNAL(timeout()), q, SLOT(updateWidth())); + } + + void scheduleWidthUpdate() + { + m_updateTimer->start(); + } + + ColumnResizer* q; + QTimer* m_updateTimer; + QList m_widgets; + QList m_wrWidgetItemList; + QList m_gridColumnInfoList; +}; + +ColumnResizer::ColumnResizer(QObject* parent) +: QObject(parent) +, d(new ColumnResizerPrivate(this)) +{} + +ColumnResizer::~ColumnResizer() +{ + delete d; +} + +void ColumnResizer::addWidget(QWidget* widget) +{ + d->m_widgets.append(widget); + widget->installEventFilter(this); + d->scheduleWidthUpdate(); +} + +void ColumnResizer::updateWidth() +{ + int width = 0; + Q_FOREACH(QWidget* widget, d->m_widgets) { + width = qMax(widget->sizeHint().width(), width); + } + Q_FOREACH(FormLayoutWidgetItem* item, d->m_wrWidgetItemList) { + item->setWidth(width); + item->formLayout()->update(); + } + Q_FOREACH(GridColumnInfo info, d->m_gridColumnInfoList) { + info.first->setColumnMinimumWidth(info.second, width); + } +} + +bool ColumnResizer::eventFilter(QObject*, QEvent* event) +{ + if (event->type() == QEvent::Resize) { + d->scheduleWidthUpdate(); + } + return false; +} + +void ColumnResizer::addWidgetsFromLayout(QLayout* layout, int column) +{ + Q_ASSERT(column >= 0); + QGridLayout* gridLayout = qobject_cast(layout); + QFormLayout* formLayout = qobject_cast(layout); + if (gridLayout) { + addWidgetsFromGridLayout(gridLayout, column); + } else if (formLayout) { + if (column > QFormLayout::SpanningRole) { + qCritical() << "column should not be more than" << QFormLayout::SpanningRole << "for QFormLayout"; + return; + } + QFormLayout::ItemRole role = static_cast(column); + addWidgetsFromFormLayout(formLayout, role); + } else { + qCritical() << "Don't know how to handle layout" << layout; + } +} + +void ColumnResizer::addWidgetsFromGridLayout(QGridLayout* layout, int column) +{ + for (int row = 0; row < layout->rowCount(); ++row) { + QLayoutItem* item = layout->itemAtPosition(row, column); + if (!item) { + continue; + } + QWidget* widget = item->widget(); + if (!widget) { + continue; + } + addWidget(widget); + } + d->m_gridColumnInfoList << GridColumnInfo(layout, column); +} + +void ColumnResizer::addWidgetsFromFormLayout(QFormLayout* layout, QFormLayout::ItemRole role) +{ + for (int row = 0; row < layout->rowCount(); ++row) { + QLayoutItem* item = layout->itemAt(row, role); + if (!item) { + continue; + } + QWidget* widget = item->widget(); + if (!widget) { + continue; + } + layout->removeItem(item); + delete item; + FormLayoutWidgetItem* newItem = new FormLayoutWidgetItem(widget, layout, role); + layout->setItem(row, role, newItem); + addWidget(widget); + d->m_wrWidgetItemList << newItem; + } +} + +#include +// vi: ts=4 sw=4 et diff --git a/bluedevil/src/kcmodule/columnresizer.h b/bluedevil/src/kcmodule/columnresizer.h new file mode 100644 index 00000000..929b0fa4 --- /dev/null +++ b/bluedevil/src/kcmodule/columnresizer.h @@ -0,0 +1,41 @@ +/* + * Copyright 2011 Aurélien Gâteau + * License: LGPL v2.1 or later (see COPYING) + */ +#ifndef COLUMNRESIZER_H +#define COLUMNRESIZER_H + +#include + +#include +#include + +class QEvent; +class QGridLayout; +class QLayout; +class QWidget; + +class ColumnResizerPrivate; +class ColumnResizer : public QObject +{ + Q_OBJECT +public: + ColumnResizer(QObject* parent = 0); + ~ColumnResizer(); + + void addWidget(QWidget* widget); + void addWidgetsFromLayout(QLayout*, int column); + void addWidgetsFromGridLayout(QGridLayout*, int column); + void addWidgetsFromFormLayout(QFormLayout*, QFormLayout::ItemRole role); + +private Q_SLOTS: + void updateWidth(); + +protected: + bool eventFilter(QObject*, QEvent* event); + +private: + ColumnResizerPrivate* const d; +}; + +#endif /* COLUMNRESIZER_H */ diff --git a/bluedevil/src/kcmodule/devicedetails.cpp b/bluedevil/src/kcmodule/devicedetails.cpp new file mode 100644 index 00000000..46ec1154 --- /dev/null +++ b/bluedevil/src/kcmodule/devicedetails.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2010 Rafael Fernández López + * Copyright (C) 2010 UFO Coders + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "devicedetails.h" + +#include + +#include +#include +#include + +#include +#include +#include + +DeviceDetails::DeviceDetails(Device* device, QWidget* parent) + : KDialog(parent) + , m_device(device) + , m_alias(new KLineEdit(this)) + , m_blocked(new QCheckBox(this)) + , m_trusted(new QCheckBox(this)) +{ + m_alias->setClearButtonShown(true); + m_alias->setText(device->alias()); + + QFormLayout *layout = new QFormLayout; + layout->addRow(i18nc("Name of the device", "Name"), new QLabel(device->name())); + layout->addRow(i18nc("Alias of the device", "Alias"), m_alias); + QLineEdit *address = new QLineEdit(this); + address->setReadOnly(true); + address->setText(device->address()); + layout->addRow(i18nc("Physical address of the device", "Address"), address); + KLed *paired = new KLed(this); + paired->setState(device->isPaired() ? KLed::On : KLed::Off); + layout->addRow(i18nc("Device is paired" ,"Paired"), paired); + m_blocked->setChecked(device->isBlocked()); + layout->addRow(i18nc("Device is blocked", "Blocked"), m_blocked); + m_trusted->setChecked(device->isTrusted()); + layout->addRow(i18nc("Device is trusted", "Trusted"), m_trusted); + QWidget *widget = new QWidget(this); + widget->setLayout(layout); + setMainWidget(widget); + setButtons(Ok | Reset | Cancel); + + connect(m_blocked, SIGNAL(toggled(bool)), this, SLOT(blockToggled(bool))); + connect(this, SIGNAL(resetClicked()), this, SLOT(resetClickedSlot())); +} + +DeviceDetails::~DeviceDetails() +{ +} + +void DeviceDetails::slotButtonClicked(int button) +{ + if (button == KDialog::Ok) { + m_device->setAlias(m_alias->text()); + m_device->setTrusted(m_trusted->isChecked()); + m_device->setBlocked(m_blocked->isChecked()); + accept(); + } else { + KDialog::slotButtonClicked(button); + } +} + + +void DeviceDetails::resetClickedSlot() +{ + m_alias->setText(m_device->alias()); + m_blocked->setChecked(m_device->isBlocked()); + m_trusted->setChecked(m_device->isTrusted()); +} + +void DeviceDetails::blockToggled(bool checked) +{ + m_trusted->setEnabled(!checked); + if (checked) { + m_trusted->setChecked(false); + } +} diff --git a/bluedevil/src/kcmodule/devicedetails.h b/bluedevil/src/kcmodule/devicedetails.h new file mode 100644 index 00000000..e30200f6 --- /dev/null +++ b/bluedevil/src/kcmodule/devicedetails.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2010 Rafael Fernández López + * Copyright (C) 2010 UFO Coders + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _DEVICEDETAILS_H +#define _DEVICEDETAILS_H + +#include + +class QCheckBox; +class KLineEdit; + +namespace BlueDevil { + class Device; +} + +typedef BlueDevil::Device Device; + +class DeviceDetails + : public KDialog +{ + Q_OBJECT + +public: + DeviceDetails(Device *device, QWidget *parent = 0); + virtual ~DeviceDetails(); + +protected Q_SLOTS: + virtual void slotButtonClicked(int button); + +private Q_SLOTS: + void resetClickedSlot(); + void blockToggled(bool checked); + +private: + Device *m_device; + KLineEdit *m_alias; + QCheckBox *m_blocked; + QCheckBox *m_trusted; +}; + +#endif diff --git a/bluedevil/src/kcmodule/kded.cpp b/bluedevil/src/kcmodule/kded.cpp new file mode 100644 index 00000000..b1afbf22 --- /dev/null +++ b/bluedevil/src/kcmodule/kded.cpp @@ -0,0 +1,26 @@ +/* + * This file was generated by qdbusxml2cpp version 0.7 + * Command line was: qdbusxml2cpp -c KDED -p kded.h:kded.cpp /home/ereslibre/proyectos/kde/kdelibs/build/kded/org.kde.kded.xml + * + * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * This is an auto-generated file. + * This file may have been hand-edited. Look for HAND-EDIT comments + * before re-generating it. + */ + +#include "kded.h" + +/* + * Implementation of interface class KDED + */ + +KDED::KDED(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) + : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) +{ +} + +KDED::~KDED() +{ +} + diff --git a/bluedevil/src/kcmodule/kded.h b/bluedevil/src/kcmodule/kded.h new file mode 100644 index 00000000..4a5210f2 --- /dev/null +++ b/bluedevil/src/kcmodule/kded.h @@ -0,0 +1,120 @@ +/* + * This file was generated by qdbusxml2cpp version 0.7 + * Command line was: qdbusxml2cpp -c KDED -p kded.h:kded.cpp /home/ereslibre/proyectos/kde/kdelibs/build/kded/org.kde.kded.xml + * + * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * This is an auto-generated file. + * Do not edit! All changes made to it will be lost. + */ + +#ifndef KDED_H_1271348628 +#define KDED_H_1271348628 + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Proxy class for interface org.kde.kded + */ +class KDED: public QDBusAbstractInterface +{ + Q_OBJECT +public: + static inline const char *staticInterfaceName() + { return "org.kde.kded"; } + +public: + KDED(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0); + + ~KDED(); + +public Q_SLOTS: // METHODS + inline QDBusPendingReply isModuleAutoloaded(const QString &module) + { + QList argumentList; + argumentList << qVariantFromValue(module); + return asyncCallWithArgumentList(QLatin1String("isModuleAutoloaded"), argumentList); + } + + inline QDBusPendingReply isModuleLoadedOnDemand(const QString &module) + { + QList argumentList; + argumentList << qVariantFromValue(module); + return asyncCallWithArgumentList(QLatin1String("isModuleLoadedOnDemand"), argumentList); + } + + inline QDBusPendingReply loadModule(const QString &obj) + { + QList argumentList; + argumentList << qVariantFromValue(obj); + return asyncCallWithArgumentList(QLatin1String("loadModule"), argumentList); + } + + inline QDBusPendingReply<> loadSecondPhase() + { + QList argumentList; + return asyncCallWithArgumentList(QLatin1String("loadSecondPhase"), argumentList); + } + + inline QDBusPendingReply loadedModules() + { + QList argumentList; + return asyncCallWithArgumentList(QLatin1String("loadedModules"), argumentList); + } + + inline QDBusPendingReply<> quit() + { + QList argumentList; + return asyncCallWithArgumentList(QLatin1String("quit"), argumentList); + } + + inline QDBusPendingReply<> reconfigure() + { + QList argumentList; + return asyncCallWithArgumentList(QLatin1String("reconfigure"), argumentList); + } + + inline QDBusPendingReply<> registerWindowId(qlonglong windowId) + { + QList argumentList; + argumentList << qVariantFromValue(windowId); + return asyncCallWithArgumentList(QLatin1String("registerWindowId"), argumentList); + } + + inline QDBusPendingReply<> setModuleAutoloading(const QString &module, bool autoload) + { + QList argumentList; + argumentList << qVariantFromValue(module) << qVariantFromValue(autoload); + return asyncCallWithArgumentList(QLatin1String("setModuleAutoloading"), argumentList); + } + + inline QDBusPendingReply unloadModule(const QString &obj) + { + QList argumentList; + argumentList << qVariantFromValue(obj); + return asyncCallWithArgumentList(QLatin1String("unloadModule"), argumentList); + } + + inline QDBusPendingReply<> unregisterWindowId(qlonglong windowId) + { + QList argumentList; + argumentList << qVariantFromValue(windowId); + return asyncCallWithArgumentList(QLatin1String("unregisterWindowId"), argumentList); + } + +Q_SIGNALS: // SIGNALS +}; + +namespace org { + namespace kde { + typedef ::KDED kded; + } +} +#endif diff --git a/bluedevil/src/kcmodule/org.kde.BlueDevil.Service.xml b/bluedevil/src/kcmodule/org.kde.BlueDevil.Service.xml new file mode 100644 index 00000000..c2058275 --- /dev/null +++ b/bluedevil/src/kcmodule/org.kde.BlueDevil.Service.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/bluedevil/src/kcmodule/sharedfilesdialog/linkproxymodel.cpp b/bluedevil/src/kcmodule/sharedfilesdialog/linkproxymodel.cpp new file mode 100644 index 00000000..86db9d16 --- /dev/null +++ b/bluedevil/src/kcmodule/sharedfilesdialog/linkproxymodel.cpp @@ -0,0 +1,38 @@ +/*************************************************************************** + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "linkproxymodel.h" +#include +#include +#include + +bool LinkProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const +{ + QFileSystemModel *model = qobject_cast< QFileSystemModel *>(sourceModel()); + QModelIndex in = model->index(source_row, source_parent.column(), source_parent); + const QString path = in.data(QFileSystemModel::FilePathRole).toString(); + + if (path == model->rootPath()) { + return true; + } + + QFileInfo file(path); + return file.isSymLink(); +} diff --git a/bluedevil/src/kcmodule/sharedfilesdialog/linkproxymodel.h b/bluedevil/src/kcmodule/sharedfilesdialog/linkproxymodel.h new file mode 100644 index 00000000..a178e6bc --- /dev/null +++ b/bluedevil/src/kcmodule/sharedfilesdialog/linkproxymodel.h @@ -0,0 +1,31 @@ +/*************************************************************************** + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef LINKPROXYMODEL_H +#define LINKPROXYMODEL_H + +#include + +class LinkProxyModel : public QSortFilterProxyModel +{ + virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; +}; + +#endif // LINKPROXYMODEL_H diff --git a/bluedevil/src/kcmodule/sharedfilesdialog/sharedfiles.ui b/bluedevil/src/kcmodule/sharedfilesdialog/sharedfiles.ui new file mode 100644 index 00000000..0f62fe8d --- /dev/null +++ b/bluedevil/src/kcmodule/sharedfilesdialog/sharedfiles.ui @@ -0,0 +1,55 @@ + + + sharedFiles + + + + 0 + 0 + 400 + 300 + + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + KPushButton + QPushButton +
kpushbutton.h
+
+
+ + +
diff --git a/bluedevil/src/kcmodule/sharedfilesdialog/sharedfilesdialog.cpp b/bluedevil/src/kcmodule/sharedfilesdialog/sharedfilesdialog.cpp new file mode 100644 index 00000000..e97de278 --- /dev/null +++ b/bluedevil/src/kcmodule/sharedfilesdialog/sharedfilesdialog.cpp @@ -0,0 +1,132 @@ +/*************************************************************************** + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "sharedfilesdialog.h" +#include "ui_sharedfiles.h" +#include "linkproxymodel.h" +#include "filereceiversettings.h" + +#include +#include +#include + +#include +#include + +SharedFilesDialog::SharedFilesDialog(QWidget* parent, Qt::WFlags flags): KDialog(parent, flags) +{ + QWidget *sharedFiles = new QWidget(this); + m_ui = new Ui::sharedFiles(); + m_ui->setupUi(sharedFiles); + setMainWidget(sharedFiles); + m_ui->listView->setSelectionMode(QAbstractItemView::ExtendedSelection); + + QFileSystemModel *model = new QFileSystemModel(); + QModelIndex in = model->setRootPath(FileReceiverSettings::self()->rootFolder().path()); + + LinkProxyModel *proxy = new LinkProxyModel(); + proxy->setSourceModel(model); + + m_ui->listView->setModel(proxy); + m_ui->listView->setRootIndex(proxy->mapFromSource(in)); + + m_ui->addBtn->setIcon(KIcon("list-add")); + m_ui->removeBtn->setIcon(KIcon("list-remove")); + + connect(this, SIGNAL(finished(int)), this, SLOT(slotFinished(int))); + connect(m_ui->addBtn, SIGNAL(clicked(bool)), this, SLOT(addFiles())); + connect(m_ui->removeBtn, SIGNAL(clicked(bool)), this, SLOT(removeFiles())); +} + +void SharedFilesDialog::slotFinished(int result) +{ + if (result == 1) { + return; + } + + KUrl url; + QString baseDir = FileReceiverSettings::self()->rootFolder().path().append("/"); + if (!m_added.isEmpty()) { + Q_FOREACH(const QString &filePath, m_added) { + url.setPath(filePath); + QFile::remove(baseDir + url.fileName()); + } + } + if (!m_removed.isEmpty()) { + Q_FOREACH(const QString &filePath, m_removed) { + url.setPath(filePath); + QFile::link(filePath, baseDir + url.fileName()); + } + } +} + +void SharedFilesDialog::addFiles() +{ + KFileDialog *dialog = new KFileDialog(QDesktopServices::storageLocation(QDesktopServices::HomeLocation), "*", this); + dialog->setMode(KFile::Directory | KFile::Files | KFile::LocalOnly); + dialog->exec(); + + QFile fileExist; + KUrl url; + QString linkPath; + QString baseDir = FileReceiverSettings::self()->rootFolder().path().append("/"); + + QStringList files = dialog->selectedFiles(); + Q_FOREACH(const QString &filePath, files) { + url.setPath(filePath); + + linkPath = baseDir + url.fileName(); + fileExist.setFileName(linkPath); + if (fileExist.exists()) { + continue; + } + + QFile::link(filePath, linkPath); + if (m_removed.contains(filePath)) { + m_removed.removeOne(filePath); + continue; + } + if (!m_added.contains(filePath)) { + m_added.append(filePath); + } + } +} + +void SharedFilesDialog::removeFiles() +{ + QItemSelectionModel *select = m_ui->listView->selectionModel(); + QModelIndexList list = select->selectedIndexes(); + + QFile file; + QString linkPath; + Q_FOREACH(const QModelIndex &index, list) { + linkPath = index.data(QFileSystemModel::FilePathRole).toString(); + file.setFileName(linkPath); + if (m_added.contains(file.symLinkTarget())) { + m_added.removeOne(file.symLinkTarget()); + continue; + } + if (!m_removed.contains(file.symLinkTarget())) { + m_removed.append(file.symLinkTarget()); + } + + file.remove(); + } +} \ No newline at end of file diff --git a/bluedevil/src/kcmodule/sharedfilesdialog/sharedfilesdialog.h b/bluedevil/src/kcmodule/sharedfilesdialog/sharedfilesdialog.h new file mode 100644 index 00000000..f1c25b97 --- /dev/null +++ b/bluedevil/src/kcmodule/sharedfilesdialog/sharedfilesdialog.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef SHAREDFILESDIALOG_H +#define SHAREDFILESDIALOG_H + +#include + +class Ui_sharedFiles; +class SharedFilesDialog : public KDialog +{ + Q_OBJECT +public: + SharedFilesDialog(QWidget* parent = 0, Qt::WFlags flags = 0); + +private Q_SLOTS: + void slotFinished(int result); + void addFiles(); + void removeFiles(); + +private: + Ui_sharedFiles *m_ui; + QStringList m_removed; + QStringList m_added; +}; + +#endif // SHAREDFILESDIALOG_H diff --git a/bluedevil/src/kcmodule/systemcheck.cpp b/bluedevil/src/kcmodule/systemcheck.cpp new file mode 100644 index 00000000..44c47e53 --- /dev/null +++ b/bluedevil/src/kcmodule/systemcheck.cpp @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2010 Rafael Fernández López + * Copyright (C) 2010 UFO Coders + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "systemcheck.h" +#include "kded.h" +#include "globalsettings.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#if KDE_IS_VERSION(4,6,41) +#include +#else + +class ErrorWidget + : public QWidget +{ +public: + ErrorWidget(QWidget *parent = 0); + virtual ~ErrorWidget(); + + void setIcon(const QString &icon); + void setText(const QString &reason); + void addAction(KPushButton *action); + +protected: + virtual void paintEvent(QPaintEvent *event); + +private: + QLabel *m_icon; + QLabel *m_reason; + QHBoxLayout *m_actions; +}; + +ErrorWidget::ErrorWidget(QWidget *parent) + : QWidget(parent) + , m_icon(new QLabel(this)) + , m_reason(new QLabel(this)) + , m_actions(new QHBoxLayout) +{ + setAutoFillBackground(false); + + m_actions->addStretch(); + + QHBoxLayout *layout = new QHBoxLayout; + layout->addWidget(m_icon); + layout->addWidget(m_reason, 1); + + QVBoxLayout *outter = new QVBoxLayout; + outter->addLayout(layout); + outter->addLayout(m_actions); + + setLayout(outter); +} + +ErrorWidget::~ErrorWidget() +{ +} + +void ErrorWidget::setIcon(const QString &icon) +{ + m_icon->setPixmap(KIconLoader::global()->loadIcon(icon, KIconLoader::Dialog)); +} + +void ErrorWidget::setText(const QString &reason) +{ + m_reason->setText(reason); +} + +void ErrorWidget::addAction(KPushButton *action) +{ + action->setAutoFillBackground(false); + m_actions->addWidget(action); +} + +void ErrorWidget::paintEvent(QPaintEvent *event) +{ + const QRect r = event->rect(); + const KColorScheme colorScheme(QPalette::Active, KColorScheme::Window); + + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + QPainterPath path; + path.addRoundedRect(0, 0, r.width(), r.height(), 10, 10); + p.fillPath(path, colorScheme.background(KColorScheme::NegativeBackground)); + + QWidget::paintEvent(event); +} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SystemCheck::SystemCheck(QWidget *parent) + : QObject(parent) + , m_kded(new KDED("org.kde.kded", "/kded", QDBusConnection::sessionBus())) + , m_parent(parent) + , m_noAdaptersError(0) + , m_notDiscoverableAdapterError(0) + , m_disabledNotificationsError(0) +{ +} + +SystemCheck::~SystemCheck() +{ + m_noAdaptersError = 0; + m_notDiscoverableAdapterError = 0; + m_disabledNotificationsError = 0; + delete m_kded; +} + +void SystemCheck::createWarnings(QVBoxLayout *layout) +{ + if (m_noAdaptersError) { + return; + } + +#if KDE_IS_VERSION(4,6,41) + m_noAdaptersError = new KMessageWidget(m_parent); + m_noAdaptersError->setMessageType(KMessageWidget::Error); + m_noAdaptersError->setCloseButtonVisible(false); +#else + m_noAdaptersError = new ErrorWidget(m_parent); + m_noAdaptersError->setIcon("window-close"); +#endif + m_noAdaptersError->setText(i18n("No Bluetooth adapters have been found.")); + layout->addWidget(m_noAdaptersError); + +#if KDE_IS_VERSION(4,6,41) + m_notDiscoverableAdapterError = new KMessageWidget(m_parent); + m_notDiscoverableAdapterError->setMessageType(KMessageWidget::Warning); + m_notDiscoverableAdapterError->setCloseButtonVisible(false); + + KAction *fixNotDiscoverableAdapter = new KAction(KIcon("dialog-ok-apply"), i18nc("Action to fix a problem", "Fix it"), m_notDiscoverableAdapterError); + connect(fixNotDiscoverableAdapter, SIGNAL(triggered(bool)), this, SLOT(fixNotDiscoverableAdapterError())); + m_notDiscoverableAdapterError->addAction(fixNotDiscoverableAdapter); +#else + m_notDiscoverableAdapterError = new ErrorWidget(m_parent); + m_notDiscoverableAdapterError->setIcon("edit-find"); + + KPushButton *fixNotDiscoverableAdapter = new KPushButton(KIcon("dialog-ok-apply"), i18nc("Action to fix a problem", "Fix it"), m_notDiscoverableAdapterError); + connect(fixNotDiscoverableAdapter, SIGNAL(clicked()), this, SLOT(fixNotDiscoverableAdapterError())); + m_notDiscoverableAdapterError->addAction(fixNotDiscoverableAdapter); +#endif + m_notDiscoverableAdapterError->setText(i18n("Your default Bluetooth adapter is not visible for remote devices.")); + + layout->addWidget(m_notDiscoverableAdapterError); + +#if KDE_IS_VERSION(4,6,41) + m_disabledNotificationsError = new KMessageWidget(m_parent); + m_disabledNotificationsError->setMessageType(KMessageWidget::Warning); + m_disabledNotificationsError->setCloseButtonVisible(false); + + KAction *fixDisabledNotifications = new KAction(KIcon("dialog-ok-apply"), i18nc("Action to fix a problem", "Fix it"), m_disabledNotificationsError); + connect(fixDisabledNotifications, SIGNAL(triggered(bool)), this, SLOT(fixDisabledNotificationsError())); + m_disabledNotificationsError->addAction(fixDisabledNotifications); +#else + m_disabledNotificationsError = new ErrorWidget(m_parent); + m_disabledNotificationsError->setIcon("preferences-desktop-notification"); + + KPushButton *fixDisabledNotifications = new KPushButton(KIcon("dialog-ok-apply"), i18nc("Action to fix a problem", "Fix it"), m_disabledNotificationsError); + connect(fixDisabledNotifications, SIGNAL(clicked()), this, SLOT(fixDisabledNotificationsError())); + m_disabledNotificationsError->addAction(fixDisabledNotifications); +#endif + m_disabledNotificationsError->setText(i18n("Interaction with Bluetooth system is not optimal.")); + + layout->addWidget(m_disabledNotificationsError); + +#if KDE_IS_VERSION(4,6,41) + m_noKDEDRunning = new KMessageWidget(m_parent); + m_noKDEDRunning ->setMessageType(KMessageWidget::Warning); + m_noKDEDRunning->setCloseButtonVisible(false); + + KAction *fixNoKDEDRunning = new KAction(KIcon("dialog-ok-apply"), i18nc("Action to fix a problem", "Fix it"), m_noKDEDRunning); + connect(fixNoKDEDRunning, SIGNAL(triggered(bool)), this, SLOT(fixNoKDEDRunning())); + m_noKDEDRunning->addAction(fixNoKDEDRunning); +#else + m_noKDEDRunning = new ErrorWidget(m_parent); + m_noKDEDRunning->setIcon("dialog-warning"); + + KPushButton *fixNoKDEDRunning = new KPushButton(KIcon("dialog-ok-apply"), i18nc("Action to fix a problem", "Fix it"), m_noKDEDRunning); + connect(fixNoKDEDRunning, SIGNAL(clicked()), this, SLOT(fixNoKDEDRunning())); + m_noKDEDRunning->addAction(fixNoKDEDRunning); +#endif + m_noKDEDRunning->setText(i18n("Bluetooth is not completely enabled.")); + + layout->addWidget(m_noKDEDRunning); +} + +bool SystemCheck::checkKDEDModuleLoaded() +{ + const QStringList res = m_kded->loadedModules(); + bool moduleLoaded = false; + foreach (const QString &module, res) { + if (module == "bluedevil") { + moduleLoaded = true; + break; + } + } + return moduleLoaded; +} + +bool SystemCheck::checkNotificationsOK() +{ + KConfig config("bluedevil.notifyrc", KConfig::NoGlobals); + config.addConfigSources(KGlobal::dirs()->findAllResources("data", "bluedevil/bluedevil.notifyrc")); + + QStringList confList = config.groupList(); + QRegExp rx("^Event/([^/]*)$"); + confList = confList.filter(rx); + + Q_FOREACH (const QString &group , confList) { + KConfigGroup cg(&config, group); + const QString action = cg.readEntry("Action"); + if (!action.contains("Popup")) { + return false; + } + } + + return true; +} + +KDED *SystemCheck::kded() +{ + return m_kded; +} + +void SystemCheck::updateInformationState() +{ + m_noAdaptersError->setEnabled(true); + m_noAdaptersError->setVisible(false); + m_notDiscoverableAdapterError->setVisible(false); + m_disabledNotificationsError->setVisible(false); + m_noKDEDRunning->setVisible(false); + + if (!GlobalSettings::self()->enableGlobalBluetooth()) { + m_noAdaptersError->setEnabled(false); + return; + } + + BlueDevil::Adapter *const usableAdapter = BlueDevil::Manager::self()->usableAdapter(); + if (!usableAdapter) { + m_noAdaptersError->setVisible(true); + return; + } + if (!usableAdapter->isDiscoverable()) { + m_notDiscoverableAdapterError->setVisible(true); + return; + } + if (!checkNotificationsOK()) { + m_disabledNotificationsError->setVisible(true); + return; + } + if (!checkKDEDModuleLoaded()) { + m_noKDEDRunning->setVisible(true); + return; + } +} + +void SystemCheck::fixNoKDEDRunning() +{ + m_noKDEDRunning->setVisible(false); + m_kded->loadModule("bluedevil"); +} + +void SystemCheck::fixNotDiscoverableAdapterError() +{ + m_notDiscoverableAdapterError->setVisible(false); + BlueDevil::Manager::self()->usableAdapter()->setDiscoverable(true); + BlueDevil::Manager::self()->usableAdapter()->setDiscoverableTimeout(0); + // No need to call to updateInformationState, since we are changing this property, it will be + // triggered automatically. +} + +void SystemCheck::fixDisabledNotificationsError() +{ + m_disabledNotificationsError->setVisible(false); + + KConfig config("bluedevil.notifyrc", KConfig::NoGlobals); + config.addConfigSources(KGlobal::dirs()->findAllResources("data", "bluedevil/bluedevil.notifyrc")); + + QStringList confList = config.groupList(); + QRegExp rx("^Event/([^/]*)$"); + confList = confList.filter(rx); + + Q_FOREACH (const QString &group , confList) { + KConfigGroup cg(&config, group); + cg.writeEntry("Action", "Popup"); + } + + config.sync(); + + emit updateInformationStateRequest(); +} diff --git a/bluedevil/src/kcmodule/systemcheck.h b/bluedevil/src/kcmodule/systemcheck.h new file mode 100644 index 00000000..e03daf92 --- /dev/null +++ b/bluedevil/src/kcmodule/systemcheck.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2010 Rafael Fernández López + * Copyright (C) 2010 UFO Coders + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef BLUEDEVIL_SYSTEM_CHECK_H +#define BLUEDEVIL_SYSTEM_CHECK_H +#include + +#include + +class QVBoxLayout; + +class KDED; +#if KDE_IS_VERSION(4,6,41) +class KMessageWidget; +#else +class ErrorWidget; +#endif + +class SystemCheck + : public QObject +{ + Q_OBJECT + +public: + SystemCheck(QWidget *parent); + virtual ~SystemCheck(); + + struct SystemCheckResult { + enum Result { + NoWarnings = 0, + BluetoothDisabled, + NoAdapters, + NotificationsDisabled, + DefaultAdapterHidden + } result; + QWidget *warningWidget; + }; + + void createWarnings(QVBoxLayout *layout); + + bool checkKDEDModuleLoaded(); + bool checkNotificationsOK(); + KDED *kded(); + +Q_SIGNALS: + void updateInformationStateRequest(); + +public Q_SLOTS: + void updateInformationState(); + +private Q_SLOTS: + void fixNoKDEDRunning(); + void fixNotDiscoverableAdapterError(); + void fixDisabledNotificationsError(); + +private: + KDED *m_kded; + QWidget *m_parent; +#if KDE_IS_VERSION(4,6,41) + KMessageWidget *m_noAdaptersError; + KMessageWidget *m_noKDEDRunning; + KMessageWidget *m_notDiscoverableAdapterError; + KMessageWidget *m_disabledNotificationsError; +#else + ErrorWidget *m_noAdaptersError; + ErrorWidget *m_noKDEDRunning; + ErrorWidget *m_notDiscoverableAdapterError; + ErrorWidget *m_disabledNotificationsError; +#endif +}; + +#endif //BLUEDEVIL_SYSTEM_CHECK_H \ No newline at end of file diff --git a/bluedevil/src/kcmodule/transfer.ui b/bluedevil/src/kcmodule/transfer.ui new file mode 100644 index 00000000..6c6cf746 --- /dev/null +++ b/bluedevil/src/kcmodule/transfer.ui @@ -0,0 +1,248 @@ + + + Transfer + + + + 0 + 0 + 604 + 401 + + + + + + + <b>Receiving</b> + + + + + + + + 0 + 0 + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Save files in: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 400 + 0 + + + + + 16777215 + 16777215 + + + + KFile::Directory|KFile::ExistingOnly|KFile::LocalOnly + + + + + + + Accept automatically: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + + + + + + + Receive files: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + kcfg_enabled + + + + + + + + + + <b>Sharing</b> + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Share Files: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + kcfg_shareEnabled + + + + + + + + + + + + + + Require PIN: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Permissions: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 300 + 20 + + + + + + + + Shared Files + + + + + + + + + + 0 + 0 + + + + + 400 + 0 + + + + KFile::Directory|KFile::ExistingOnly|KFile::LocalOnly + + + + + + + Shared Folder: + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + KUrlRequester + QFrame +
kurlrequester.h
+
+ + KPushButton + QPushButton +
kpushbutton.h
+
+
+ + +
diff --git a/bluedevil/src/kio/CMakeLists.txt b/bluedevil/src/kio/CMakeLists.txt new file mode 100644 index 00000000..a6652be0 --- /dev/null +++ b/bluedevil/src/kio/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(bluetooth) +add_subdirectory(obexftp) diff --git a/bluedevil/src/kio/bluetooth/.kdev4/bluetooth.kdev4 b/bluedevil/src/kio/bluetooth/.kdev4/bluetooth.kdev4 new file mode 100644 index 00000000..3eb4f602 --- /dev/null +++ b/bluedevil/src/kio/bluetooth/.kdev4/bluetooth.kdev4 @@ -0,0 +1,11 @@ +[Buildset] +BuildItems=@Variant(\x00\x00\x00\t\x00\x00\x00\x00\x01\x00\x00\x00\x0b\x00\x00\x00\x00\x01\x00\x00\x00\x12\x00b\x00l\x00u\x00e\x00t\x00o\x00o\x00t\x00h) + +[CMake] +BuildDirs=/media/home/edulix/proyectos/kde4/src/build/bluetooth/kio/bluetooth/ +CMakeDir=/usr/share/cmake-2.8/Modules +Current CMake Binary=file:///usr/bin/cmake +CurrentBuildDir=file:///media/home/edulix/proyectos/kde4/src/build/bluetooth/kio/bluetooth/ +CurrentBuildType=Debug +CurrentInstallDir= +ProjectRootRelative=./ diff --git a/bluedevil/src/kio/bluetooth/CMakeLists.txt b/bluedevil/src/kio/bluetooth/CMakeLists.txt new file mode 100644 index 00000000..ef1f1e22 --- /dev/null +++ b/bluedevil/src/kio/bluetooth/CMakeLists.txt @@ -0,0 +1,19 @@ +set(kio_bluetooth_PART_SRCS + kiobluetooth.cpp) + +set(kded_bluedevil.xml ${CMAKE_CURRENT_SOURCE_DIR}/kded_bluedevil.xml) +set_source_files_properties(${kded_bluedevil.xml} PROPERTIES INCLUDE "types.h") +qt4_add_dbus_interface(kio_bluetooth_PART_SRCS ${kded_bluedevil.xml} kdedbluedevil) + +kde4_add_plugin(kio_bluetooth ${kio_bluetooth_PART_SRCS}) + +target_link_libraries(kio_bluetooth + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBRARY} + ${KDE4_KDEUI_LIBS} + ${LibBlueDevil_LIBRARIES} +) + +########### install files ############### +install(TARGETS kio_bluetooth DESTINATION ${PLUGIN_INSTALL_DIR}) +install(FILES bluetooth.protocol DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/bluedevil/src/kio/bluetooth/bluetooth.kdev4 b/bluedevil/src/kio/bluetooth/bluetooth.kdev4 new file mode 100644 index 00000000..08c69444 --- /dev/null +++ b/bluedevil/src/kio/bluetooth/bluetooth.kdev4 @@ -0,0 +1,3 @@ +[Project] +Manager=KDevCMakeManager +Name=bluetooth diff --git a/bluedevil/src/kio/bluetooth/bluetooth.protocol b/bluedevil/src/kio/bluetooth/bluetooth.protocol new file mode 100644 index 00000000..32f83254 --- /dev/null +++ b/bluedevil/src/kio/bluetooth/bluetooth.protocol @@ -0,0 +1,16 @@ +[Protocol] +exec=kio_bluetooth +protocol=bluetooth +input=none +output=filesystem +copyToFile=false +copyFromFile=false +listing=Name,Type,Access +reading=true +writing=false +makedir=false +deleting=false +moving=false +Icon=preferences-system-bluetooth +maxInstances=2 +X-DocPath=kioslave/bluetooth.html diff --git a/bluedevil/src/kio/bluetooth/kded_bluedevil.xml b/bluedevil/src/kio/bluetooth/kded_bluedevil.xml new file mode 100644 index 00000000..cb4d6fdd --- /dev/null +++ b/bluedevil/src/kio/bluetooth/kded_bluedevil.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bluedevil/src/kio/bluetooth/kiobluetooth.cpp b/bluedevil/src/kio/bluetooth/kiobluetooth.cpp new file mode 100644 index 00000000..4ac17a61 --- /dev/null +++ b/bluedevil/src/kio/bluetooth/kiobluetooth.cpp @@ -0,0 +1,240 @@ +/************************************************************************************* + * Copyright (C) 2010-2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2010 Eduardo Robles Elvira * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "kiobluetooth.h" +#include "kdedbluedevil.h" +#include "version.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace BlueDevil; + +extern "C" int KDE_EXPORT kdemain(int argc, char **argv) +{ + KAboutData about("kiobluetooth", "bluedevil", ki18n("kiobluetooth"), bluedevil_version); + KCmdLineArgs::init(&about); + + KApplication app; + + if (argc != 4) { + fprintf(stderr, "Usage: kio_bluetooth protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + KioBluetooth slave(argv[2], argv[3]); + slave.dispatchLoop(); + return 0; +} + +KioBluetooth::KioBluetooth(const QByteArray &pool, const QByteArray &app) + : SlaveBase("bluetooth", pool, app) +{ + qDBusRegisterMetaType (); + qDBusRegisterMetaType (); + + m_hasCurrentHost = false; + + Service s; + s.name = i18n("Send File"); + s.icon = "edit-copy"; + s.mimetype = "application/vnd.kde.bluedevil-sendfile"; + s.uuid = "00001105-0000-1000-8000-00805F9B34FB"; + m_supportedServices.insert("00001105-0000-1000-8000-00805F9B34FB", s); + s.name = i18n("Browse Files"); + s.icon = "edit-find"; + s.mimetype = ""; + s.uuid = "00001106-0000-1000-8000-00805F9B34FB"; + m_supportedServices.insert("00001106-0000-1000-8000-00805F9B34FB", s); + + if (!Manager::self()->usableAdapter()) { + kDebug() << "No available interface"; + infoMessage(i18n("No Bluetooth adapters have been found.")); + return; + } + + kDebug() << "Kio Bluetooth instanced!"; + m_kded = new org::kde::BlueDevil("org.kde.kded", "/modules/bluedevil", QDBusConnection::sessionBus(), 0); +} + +QList KioBluetooth::getSupportedServices(const QStringList &uuids) +{ + kDebug() << "supported services: " << uuids; + QList retValue; + Q_FOREACH (const QString &uuid, uuids) { + if (m_supportedServices.contains(uuid)) { + retValue << m_supportedServices[uuid]; + } + } + return retValue; +} + +void KioBluetooth::listRemoteDeviceServices() +{ + m_kded->stopDiscovering(); + infoMessage(i18n("Retrieving services...")); + + kDebug() << "Listing remote devices"; + m_currentHost = Manager::self()->usableAdapter()->deviceForAddress(m_currentHostname.replace('-', ':').toUpper()); + m_currentHostServices = getSupportedServices(m_currentHost->UUIDs()); + + kDebug() << "Num of supported services: " << m_currentHostServices.size(); + totalSize(m_currentHostServices.count()); + int i = 1; + Q_FOREACH (const Service &service, m_currentHostServices) { + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_NAME, service.uuid); + entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, service.name); + entry.insert(KIO::UDSEntry::UDS_ICON_NAME, service.icon); + + //If it is browse files, act as a folder + if (service.uuid == "00001106-0000-1000-8000-00805F9B34FB") { + KUrl obexUrl; + obexUrl.setProtocol("obexftp"); + obexUrl.setHost(m_currentHostname.replace(':', '-').toUpper()); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_URL, obexUrl.url()); + } else { + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IRWXO); + } + + if (service.mimetype.isEmpty()) { + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/vnd.kde.bluedevil.service"); + } else { + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, service.mimetype); + } + listEntry(entry, false); + processedSize(i++); + } + + listEntry(KIO::UDSEntry(), true); + infoMessage(""); + finished(); +} + +void KioBluetooth::listDevices() +{ + kDebug() << "Asking kded for devices"; + QMapDeviceInfo devices = m_kded->knownDevices().value(); + kDebug() << devices.keys(); + Q_FOREACH(const DeviceInfo device, devices) { + listDevice(device); + } + listEntry(KIO::UDSEntry(), true); + infoMessage(i18n("Scanning for new devices...")); + finished(); +} + +void KioBluetooth::listDevice(const DeviceInfo device) +{ + kDebug() << device; + if (getSupportedServices(device["UUIDs"].split(",")).isEmpty()) { + return; + } + const QString target = QString("bluetooth://").append(QString(device["address"]).replace(':', '-')); + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_URL, target); + entry.insert(KIO::UDSEntry::UDS_NAME, device["name"]); + entry.insert(KIO::UDSEntry::UDS_ICON_NAME, device["icon"]); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH); + if (device.contains("discovered") && device["discovered"] == "true") { + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/vnd.kde.bluedevil.device.discovered"); + } else { + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/vnd.kde.bluedevil.device"); + } + listEntry(entry, false); +} + +void KioBluetooth::listDir(const KUrl &url) +{ + kDebug() << "Listing..." << url; + + /// Url is not used here becuase all we could care about the url is the host, and that's already + /// handled in @p setHost + Q_UNUSED(url); + + // If we are not online (ie. there's no working bluetooth adapter), list an empty dir + kDebug() << m_kded->isOnline().value(); + if (!m_kded->isOnline().value()) { + infoMessage(i18n("No Bluetooth adapters have been found.")); + listEntry(KIO::UDSEntry(), true); + finished(); + return; + } + + if (!m_hasCurrentHost) { + listDevices(); + } else { + listRemoteDeviceServices(); + } +} + +void KioBluetooth::stat(const KUrl &url) +{ + kDebug() << "Stat: " << url; + finished(); +} + +void KioBluetooth::get(const KUrl &url) +{ + m_kded->stopDiscovering(); + kDebug() << "Get: " << url; + kDebug() << m_supportedServices.value(url.fileName()).mimetype; + mimeType(m_supportedServices.value(url.fileName()).mimetype); + finished(); +} + +void KioBluetooth::setHost(const QString &constHostname, quint16 port, const QString &user, + const QString &pass) +{ + kDebug() << "Setting host: " << constHostname; + + // In this kio only the hostname (constHostname) is used + Q_UNUSED(port) + Q_UNUSED(user) + Q_UNUSED(pass) + + QString hostname = constHostname; + hostname = hostname.replace('-', ':').toUpper(); + if (hostname.isEmpty()) { + m_hasCurrentHost = false; + } else { + m_hasCurrentHost = true; + m_currentHostname = constHostname; + m_currentHostServices.clear(); + } +} + + +#include "kiobluetooth.moc" diff --git a/bluedevil/src/kio/bluetooth/kiobluetooth.h b/bluedevil/src/kio/bluetooth/kiobluetooth.h new file mode 100644 index 00000000..33b6037c --- /dev/null +++ b/bluedevil/src/kio/bluetooth/kiobluetooth.h @@ -0,0 +1,149 @@ +/* This file is part of the KDE libraries + + Copyright (C) 2010 Eduardo Robles Elvira + Copyright (C) 2010 Rafael Fernández López + Copyright (C) 2010 UFO Coders + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KIOBLUETOOTH_H +#define KIOBLUETOOTH_H + +#include "kdedbluedevil.h" + +#include +#include + +/** + * @short This class implements a bluetooth kioslave that list devices and their services. + */ +class KioBluetoothPrivate; + +namespace BlueDevil { + class Device; + class Adapter; +} + +using namespace BlueDevil; + +class KioBluetooth : public QObject, public KIO::SlaveBase +{ + Q_OBJECT + +public: + KioBluetooth(const QByteArray &pool, const QByteArray &app); + + struct Service { + QString name; + QString icon; + QString mimetype; + QString uuid; + }; + + /** + * As our kio does not perform any service action, but just list devices and their services, the + * get function shall not do much other than setting a mimetype and returning some data that + * could be useful for the mimetype handler. + */ + void get(const KUrl &url); + + /** + * List current directory. There are two types of current directories in this kio: + * + * 1. First type, is the root dir, bluetooth:/. This directory is unique, and lists the remote + * devices that our default bluetooth adapter sees. + * 2. Remote device directory (something like bluetoth:/00_12_34_56_6d_34). This directory lists + * the services provided by the given remote device. + */ + void listDir(const KUrl &url); + + void stat(const KUrl &url); + + /** + * As at the momento we don't handle more than one level url paths, @p setHost has not much + * difference with @p listDir + * + */ + void setHost(const QString &constHostname, quint16 port, const QString &user, const QString &pass); + + /** + * Returns a list of supported service names corresponding to the given uuids list. If an uuid is + * not found in the uuids list, it is not added to the list of service names. + */ + QList getSupportedServices(const QStringList &uuids); + + /** + * Called by @p Bluetooth::listDir when listing root dir, bluetooth:/. + */ + void listDevices(); + + /** + * Called by @p Bluetooth::listDir when listing a remote device (something like + * bluetoth:/00_12_34_56_6d_34) services. + */ + void listRemoteDeviceServices(); + +public Q_SLOTS: + void listDevice(const DeviceInfo device); + +private: + + /** + * This is set to true when @p setHost is called to list a given remote device, like for example + * 00:2a:5E:8e:6e:f5. If listing the remote devices (bluetooth:/ uri), it's set back to false. + */ + bool m_hasCurrentHost; + + /** + * This is set in @p setHost when it's called to list a given remote device like for example + * 00:2a:5E:8e:6e:f5. We don't directly set @p currentHost in @p setHost because libbludevil might not + * have ready the remote bluetooth device yet ready at that time (it.s being created by the call + * to @p Solid::Control::BluetoothDevice::createBluetoothRemoteDevice . + */ + QString m_currentHostname; + + /** + * Represents the current host when @p hasCurrentHost is set to true. This is set in + * @p listRemoteDeviceServices function. + */ + Device *m_currentHost; + + /** + * When @p hasCurrentHost to true, this list holds the list of service names provided by the + * current host (which is a remote device we can connect to using those services). + */ + QList m_currentHostServices; + + /** + * This is an array containing as key the uuid and as value the name of the service that the + * given uuid represents. + */ + QMap m_serviceNames; + + /** + * This is an array containing as key the uuid and as value the name of the service that the + * given uuid represents, and a representative icon. It only contains the supported service names. + */ + QMap m_supportedServices; + + /** + * KDED DBus interface, used to communicate to the daemon since we need some status (like connected) + */ + org::kde::BlueDevil *m_kded; +}; + +#endif // KIOBLUETOOTH_H \ No newline at end of file diff --git a/bluedevil/src/kio/bluetooth/types.h b/bluedevil/src/kio/bluetooth/types.h new file mode 100644 index 00000000..19021467 --- /dev/null +++ b/bluedevil/src/kio/bluetooth/types.h @@ -0,0 +1,11 @@ +#ifndef KIOTYPES +#define KIOTYPES + +#include + +typedef QMap DeviceInfo; +typedef QMap QMapDeviceInfo; +Q_DECLARE_METATYPE(DeviceInfo) +Q_DECLARE_METATYPE(QMapDeviceInfo) + +#endif \ No newline at end of file diff --git a/bluedevil/src/kio/obexftp/CMakeLists.txt b/bluedevil/src/kio/obexftp/CMakeLists.txt new file mode 100644 index 00000000..a14b18db --- /dev/null +++ b/bluedevil/src/kio/obexftp/CMakeLists.txt @@ -0,0 +1,35 @@ +project(kio_obexftp) + +add_subdirectory(daemon) + +set(kio_obexftp_SRCS + kio_obexftp.cpp + transferfilejob.cpp + ) + +qt4_add_dbus_interface(kio_obexftp_SRCS kded_obexftp.xml kdedobexftp) + +set(FILE_TRANSFER_FILE org.bluez.obex.FileTransfer1.xml) +set_source_files_properties(${FILE_TRANSFER_FILE} PROPERTIES INCLUDE "obexdtypes.h") +qt4_add_dbus_interface(kio_obexftp_SRCS ${FILE_TRANSFER_FILE} obexd_file_transfer) + +set(obexftpfiletransfer_xml ${CMAKE_CURRENT_SOURCE_DIR}/obexftp/filetransfer.xml) + +set(SENDER_PLUGIN_PATH ${CMAKE_SOURCE_DIR}/src/sendfile/) +qt4_add_dbus_interface(kio_obexftp_SRCS ${SENDER_PLUGIN_PATH}org.bluez.obex.Transfer1.xml obexd_transfer) + +kde4_add_plugin(kio_obexftp ${kio_obexftp_SRCS}) + +target_link_libraries(kio_obexftp + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBRARY} + ${QT_QTDBUS_LIBRARY} +) + +QT4_AUTOMOC(${kio_obexftp_SRCS}) + +install(TARGETS kio_obexftp DESTINATION ${PLUGIN_INSTALL_DIR}) + +########### install files ############### + +install(FILES obexftp.protocol DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/bluedevil/src/kio/obexftp/daemon/CMakeLists.txt b/bluedevil/src/kio/obexftp/daemon/CMakeLists.txt new file mode 100644 index 00000000..00320a77 --- /dev/null +++ b/bluedevil/src/kio/obexftp/daemon/CMakeLists.txt @@ -0,0 +1,26 @@ +set(kded_obexftp_SRCS + ObexFtpDaemon.cpp + createsessionjob.cpp +) + +set(SENDER_PLUGIN_PATH ${CMAKE_SOURCE_DIR}/src/sendfile/) +qt4_add_dbus_interface(kded_obexftp_SRCS ${SENDER_PLUGIN_PATH}org.bluez.obex.Client1.xml obexd_client) + +set(OBJECT_MANAGER_FILE org.freedesktop.DBus.ObjectManager.xml) +set_source_files_properties(${OBJECT_MANAGER_FILE} PROPERTIES INCLUDE "../obexdtypes.h") +qt4_add_dbus_interface(kded_obexftp_SRCS org.freedesktop.DBus.ObjectManager.xml dbus_object_manager) + +kde4_add_plugin(kded_obexftpdaemon + ${kded_obexftp_SRCS} +) + +target_link_libraries(kded_obexftpdaemon + ${KDE4_KDECORE_LIBS} + ${KDE4_KDEUI_LIBS} + ${KDE4_KFILE_LIBS} + ${LibBlueDevil_LIBRARIES} +) + +install(TARGETS kded_obexftpdaemon DESTINATION ${PLUGIN_INSTALL_DIR}) +install(FILES obexftpdaemon.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kded) + diff --git a/bluedevil/src/kio/obexftp/daemon/HACKING b/bluedevil/src/kio/obexftp/daemon/HACKING new file mode 100644 index 00000000..cc351605 --- /dev/null +++ b/bluedevil/src/kio/obexftp/daemon/HACKING @@ -0,0 +1,18 @@ +When developing the kio_obexftp, we noticed that the only way to make play nice obexftp protocol with kio_obexftp +was developing a new KDED, this is why. + +KIO is designed to has atomic methods, to do that we need to connect/disconnect in each action becuase most of the +devices which implement obexftp only support 1 connection. + +So, what does this Daemon solve? + +This is the idea: + -On setHost, the kio send the addr to connect to. + -The connection (from now on known as session) is saved in a map, relationing ADDR<-->Session + -In each action (lsitdir, copy, etc...) the call is forwarded to the KDED, together with the current host (addr) + -The KDED, using the session perform the actual obexftp related work. + -In case of copy, it will emit signals when needed + +Sumary of needs: + -Share session between kio's + -Have an atomic kio_salve, as they should be. \ No newline at end of file diff --git a/bluedevil/src/kio/obexftp/daemon/ObexFtpDaemon.cpp b/bluedevil/src/kio/obexftp/daemon/ObexFtpDaemon.cpp new file mode 100644 index 00000000..7e716481 --- /dev/null +++ b/bluedevil/src/kio/obexftp/daemon/ObexFtpDaemon.cpp @@ -0,0 +1,200 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "ObexFtpDaemon.h" +#include "createsessionjob.h" +#include "dbus_object_manager.h" +#include "version.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include + +using namespace BlueDevil; +K_PLUGIN_FACTORY(ObexFtpFactory, + registerPlugin();) +K_EXPORT_PLUGIN(ObexFtpFactory("obexftpdaemon", "obexftpdaemon")) + +struct ObexFtpDaemon::Private +{ + enum Status { + Online = 0, + Offline + } m_status; + + QHash m_sessionMap; + QHash m_reverseSessionMap; + QHash m_wipSessions; + QDBusServiceWatcher *m_serviceWatcher; + OrgFreedesktopDBusObjectManagerInterface *m_interface; +}; + +ObexFtpDaemon::ObexFtpDaemon(QObject *parent, const QList&) + : KDEDModule(parent) + , d(new Private) +{ + d->m_status = Private::Offline; + + KAboutData aboutData( + "obexftpdaemon", + "bluedevil", + ki18n("ObexFtp Daemon"), + bluedevil_version, + ki18n("ObexFtp Daemon"), + KAboutData::License_GPL, + ki18n("(c) 2010, UFO Coders") + ); + + aboutData.addAuthor(ki18n("Alejandro Fiestas Olivares"), ki18n("Maintainer"), "afiestas@kde.org", + "http://www.afiestas.org"); + + connect(Manager::self(), SIGNAL(usableAdapterChanged(Adapter*)), + SLOT(usableAdapterChanged(Adapter*))); + + d->m_interface = new OrgFreedesktopDBusObjectManagerInterface("org.bluez.obex", "/", QDBusConnection::sessionBus(), this); + connect(d->m_interface, SIGNAL(InterfacesRemoved(QDBusObjectPath,QStringList)), + SLOT(interfaceRemoved(QDBusObjectPath,QStringList))); + d->m_serviceWatcher = new QDBusServiceWatcher("org.bluez.obex", QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForUnregistration, this); + + connect(d->m_serviceWatcher, SIGNAL(serviceUnregistered(QString)), SLOT(serviceUnregistered(QString))); + + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + //WARNING this blocks if org.bluez in system bus is dead + if (Manager::self()->usableAdapter()) { + onlineMode(); + } +} + +ObexFtpDaemon::~ObexFtpDaemon() +{ + if (d->m_status == Private::Online) { + offlineMode(); + } + delete d; +} + +void ObexFtpDaemon::onlineMode() +{ + kDebug(dobex()); + if (d->m_status == Private::Online) { + kDebug(dobex()) << "Already in onlineMode"; + return; + } + + d->m_status = Private::Online; +} + +void ObexFtpDaemon::offlineMode() +{ + kDebug(dobex()) << "Offline mode"; + if (d->m_status == Private::Offline) { + kDebug(dobex()) << "Already in offlineMode"; + return; + } + + d->m_sessionMap.clear(); + d->m_reverseSessionMap.clear(); + + d->m_status = Private::Offline; +} + +void ObexFtpDaemon::usableAdapterChanged(Adapter *adapter) +{ + if (!adapter) { + offlineMode(); + return; + } + + onlineMode(); +} + +QString ObexFtpDaemon::session(QString address, const QDBusMessage& msg) +{ + kDebug(dobex()) << address; + address.replace("-", ":"); + + if(d->m_sessionMap.contains(address)) { + return d->m_sessionMap[address]; + } + + //At this point we always want delayed reply + msg.setDelayedReply(true); + if (d->m_wipSessions.contains(address)) { + d->m_wipSessions[address]->addMessage(msg); + return QString(); + } + + CreateSessionJob *job = new CreateSessionJob(address, msg); + connect(job, SIGNAL(finished(KJob*)), SLOT(sessionCreated(KJob*))); + job->start(); + + d->m_wipSessions.insert(address, job); + return QString(); +} + +void ObexFtpDaemon::sessionCreated(KJob* job) +{ + CreateSessionJob* cJob = qobject_cast(job); + kDebug(dobex()) << cJob->path(); + + d->m_wipSessions.remove(cJob->address()); + d->m_sessionMap.insert(cJob->address(), cJob->path()); + d->m_reverseSessionMap.insert(cJob->path(), cJob->address()); + + const QList messages = cJob->messages(); + Q_FOREACH(const QDBusMessage &msg, messages) { + QDBusMessage reply = msg.createReply(cJob->path()); + QDBusConnection::sessionBus().asyncCall(reply); + } +} + +void ObexFtpDaemon::serviceUnregistered(const QString& service) +{ + if (service != QLatin1String("org.bluez.obex")) { + return; + } + + d->m_sessionMap.clear(); + d->m_reverseSessionMap.clear(); +} + +void ObexFtpDaemon::interfaceRemoved(const QDBusObjectPath &dbusPath, const QStringList& interfaces) +{ + kDebug(dobex()) << dbusPath.path() << interfaces; + const QString path = dbusPath.path(); + if (!d->m_reverseSessionMap.contains(path)) { + kDebug(dobex()) << d->m_reverseSessionMap; + return; + } + + QString address = d->m_reverseSessionMap.take(path); + kDebug(dobex()) << address; + kDebug(dobex()) << d->m_sessionMap.remove(address); +} + +extern int dobex() { static int s_area = KDebug::registerArea("ObexDaemon", false); return s_area; } \ No newline at end of file diff --git a/bluedevil/src/kio/obexftp/daemon/ObexFtpDaemon.h b/bluedevil/src/kio/obexftp/daemon/ObexFtpDaemon.h new file mode 100644 index 00000000..49f19e68 --- /dev/null +++ b/bluedevil/src/kio/obexftp/daemon/ObexFtpDaemon.h @@ -0,0 +1,76 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#ifndef OBEXFTPDAEMON_H +#define OBEXFTPDAEMON_H + +#include "../obexdtypes.h" + +#include +#include + +class KJob; +class QDBusMessage; +class QDBusPendingCallWatcher; + +namespace BlueDevil { + class Adapter; +}; +using namespace BlueDevil; + +class KDE_EXPORT ObexFtpDaemon + : public KDEDModule +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.ObexFtp") + +public: + /** + * Connects to usableAdapterChanged + */ + ObexFtpDaemon(QObject *parent, const QList&); + virtual ~ObexFtpDaemon(); + +public Q_SLOTS: + Q_SCRIPTABLE QString session(QString address, const QDBusMessage &msg); + +private Q_SLOTS: + void usableAdapterChanged(Adapter* adapter); + void sessionCreated(KJob* job); + void serviceUnregistered(const QString &service); + void interfaceRemoved(const QDBusObjectPath &path, const QStringList &interfaces); + +private: + /** + * Called by constructor or eventually by adapterAdded initialize all the helpers + * @see helpers + */ + void onlineMode(); + + /** + * Called eventually adapterRemoved shutdown all the helpers + * @see helpers + */ + void offlineMode(); + + struct Private; + Private *d; +}; + +extern int dobex(); +#endif /*OBEXFTPDAEMON_H*/ diff --git a/bluedevil/src/kio/obexftp/daemon/createsessionjob.cpp b/bluedevil/src/kio/obexftp/daemon/createsessionjob.cpp new file mode 100644 index 00000000..67733f5a --- /dev/null +++ b/bluedevil/src/kio/obexftp/daemon/createsessionjob.cpp @@ -0,0 +1,101 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "createsessionjob.h" +#include "obexd_client.h" +#include "ObexFtpDaemon.h" + +#include + +#include + +// class +CreateSessionJob::CreateSessionJob(const QString& address, const QDBusMessage& msg, QObject* parent) + : KJob(parent) + , m_address(address) + , m_client(0) +{ + m_messages.append(msg); +} + +void CreateSessionJob::start() +{ + QMetaObject::invokeMethod(this, "createSession", Qt::QueuedConnection); +} + +QString CreateSessionJob::path() +{ + return m_path; +} + +const QString CreateSessionJob::address() const +{ + return m_address; +} + +void CreateSessionJob::addMessage(const QDBusMessage& msg) +{ + m_messages.append(msg); +} + +const QList< QDBusMessage > CreateSessionJob::messages() const +{ + return m_messages; +} + +void CreateSessionJob::createSession() +{ + kDebug(dobex()); + QVariantMap args; + args["Target"] = "ftp"; +// args["Source"] = "00:02:72:D6:8F:2C"; + m_client = new OrgBluezObexClient1Interface("org.bluez.obex", + "/org/bluez/obex", + QDBusConnection::sessionBus(), this); + + QDBusPendingReply reply = m_client->CreateSession(m_address, args); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply); + + kDebug(dobex()) << "DROGUES"; + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(sessionCreated(QDBusPendingCallWatcher*))); +} + +void CreateSessionJob::sessionCreated(QDBusPendingCallWatcher* watcher) +{ + kDebug(dobex()); + QDBusPendingReply reply = *watcher; + watcher->deleteLater(); + if (reply.isError()) { + kDebug(dobex()) << "Error:"; + kDebug(dobex()) << reply.error().name(); + kDebug(dobex()) << reply.error().message(); +// Q_FOREACH(const QDBusMessage &msg, m_msgs) { +// kDebug(dobex()) << msg.service() << msg.path(); +// QDBusMessage errorMsg = msg.createErrorReply("org.kde.kded.Error", i18n("Can't stablish connection")); +// QDBusConnection::sessionBus().send(errorMsg); +// }C +// m_status = Error; + setError(reply.error().type()); + setErrorText(reply.error().message()); + emitResult(); + return; + } + + m_path = reply.value().path(); + emitResult(); +} \ No newline at end of file diff --git a/bluedevil/src/kio/obexftp/daemon/createsessionjob.h b/bluedevil/src/kio/obexftp/daemon/createsessionjob.h new file mode 100644 index 00000000..a457b028 --- /dev/null +++ b/bluedevil/src/kio/obexftp/daemon/createsessionjob.h @@ -0,0 +1,52 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#ifndef CREATE_SESSION_JOB_H +#define CREATE_SESSION_JOB_H + +#include + +#include + +class QDBusPendingCallWatcher; +class OrgBluezObexClient1Interface; +class CreateSessionJob : public KJob +{ + Q_OBJECT +public: + explicit CreateSessionJob(const QString &address, const QDBusMessage &msg, QObject* parent = 0); + + virtual void start(); + QString path(); + const QString address() const; + void addMessage(const QDBusMessage &msg); + const QList messages() const; + +private Q_SLOTS: + void createSession(); + void sessionCreated(QDBusPendingCallWatcher* watcher); + +private: + QString m_path; + QString m_address; + QList m_messages; + + OrgBluezObexClient1Interface* m_client; +}; + +#endif //CREATE_SESSION_JOB_H \ No newline at end of file diff --git a/bluedevil/src/kio/obexftp/daemon/obexftpdaemon.desktop b/bluedevil/src/kio/obexftp/daemon/obexftpdaemon.desktop new file mode 100644 index 00000000..4939c0cc --- /dev/null +++ b/bluedevil/src/kio/obexftp/daemon/obexftpdaemon.desktop @@ -0,0 +1,79 @@ +[Desktop Entry] +Type=Service +Icon=preferences-system-bluetooth +X-KDE-ServiceTypes=KDEDModule +X-KDE-Library=obexftpdaemon +X-KDE-DBus-ModuleName=obexftpdaemon +X-KDE-Kded-autoload=false +X-KDE-Kded-load-on-demand=true +X-KDE-Kded-phase=1 + +Name=Bluetooth File Transfer +Name[ca]=Transferència de fitxer per Bluetooth +Name[ca@valencia]=Transferència de fitxer per Bluetooth +Name[cs]=Přenos souborů Bluetooth +Name[da]=Bluetooth filoverførsel +Name[de]=Bluetooth-Dateiübertragung +Name[el]=Μεταφορά αρχείων με Bluetooth +Name[es]=Transferencia de archivo por Bluetooth +Name[fi]=Bluetooth-tiedostonsiirto +Name[fr]=Transfert de fichiers Bluetooth +Name[gl]=Transferencias de ficheiros mediante Bluetooth +Name[hu]=Bluetooth fájlátvitel +Name[it]=Trasferimento file via Bluetooth +Name[kk]=Bluetooth арқылы файлды тасымалдау +Name[lt]=Failų perdavimai +Name[mr]=ब्लूटूथ फाईल बदली +Name[nb]=Blåtann filoverføring +Name[nl]=Bestandsoverdracht via bluetooth +Name[pa]=ਬਲਿਊਟੁੱਥ ਫਾਇਲ ਟਰਾਂਸਫਰ +Name[pl]=Przesyłanie plików Bluetooth +Name[pt]=Transferência de Ficheiros por Bluetooth +Name[pt_BR]=Transferência de arquivos por Bluetooth +Name[ro]=Transfer de fișiere Bluetooth +Name[ru]=Передача файла по Bluetooth +Name[sk]=Prenosy súborov Bluetooth +Name[sl]=Prenos datotek prek Bluetooth +Name[sr]=Блутут пренос фајла +Name[sr@ijekavian]=Блутут пренос фајла +Name[sr@ijekavianlatin]=Bluetooth prenos fajla +Name[sr@latin]=Bluetooth prenos fajla +Name[sv]=Filöverföring med Blåtand +Name[tr]=Bluetooth Dosya Aktarımı +Name[uk]=Перенесення файла за допомогою Bluetooth +Name[x-test]=xxBluetooth File Transferxx +Name[zh_TW]=藍牙檔案傳輸 + +Comment=Supports Bluetooth file transfer using ObexFTP +Comment[ca]=Accepta transferència de fitxer per Bluetooth usant ObexFTP +Comment[ca@valencia]=Accepta transferència de fitxer per Bluetooth usant ObexFTP +Comment[cs]=Podporuje přenos souborů Bluetooth použitím ObexFTP +Comment[da]=Understøtter Bluetooth filoverførsel med ObexFTP +Comment[de]=Unterstützt Bluetooth-Datenübertragung mittels ObexFTP +Comment[el]=Υποστηρίζει μεταφορά αρχείων με Bluetooth με χρήση ObexFTP +Comment[es]=Permite transferencias de archivos por Bluetooth usando ObexFTP +Comment[fi]=Tukee Bluetooth-tiedostonsiirtoa ObexFTP:tä käyttäen +Comment[fr]=Prend en charge le transfert de fichiers Bluetooth à l'aide de « ObexFTP » +Comment[gl]=Permite a transferencia de ficheiros por Bluetooth empregando ObexFTP +Comment[hu]=Támogatja a Bluetooth fájlátvitelt az ObexFTP használatával +Comment[it]=Supporta i trasferimenti di file Bluetooth usando ObexFTP +Comment[kk]=ObexFTP-мен Bluetooth арқылы файлды тасымалдау +Comment[lt]=Palaiko Bluetooth failų perdavimą naudojant ObexFTP +Comment[mr]=ओबेक्स-एफटीपी वापरून ब्लूटूथ फाईल बदली समर्थीत करतो +Comment[nb]=Støtter Blåtann filoverføring vhja ObexFTP +Comment[nl]=Ondersteunt bestandsoverdracht via bluetooth met ObexFTP +Comment[pl]=Wspiera przesyłanie plików przez Bluetooth przy użyciu ObexFTP +Comment[pt]=Suporta a transferência de ficheiros por Bluetooth com o ObexFTP +Comment[pt_BR]=Suporte à transferência de arquivos por Bluetooth usando o ObexFTP +Comment[ru]=Поддерживает передачу файлов по Bluetooth на базе ObexFTP +Comment[sk]=Podporuje Bluetooth prenosy súborov pomocou ObexFTP +Comment[sl]=Podpira prenose datotek preko Bluetooth z uporabo ObexFTP +Comment[sr]=Подршка за блутут преносе фајлова помоћу ОбексФТП‑а +Comment[sr@ijekavian]=Подршка за блутут преносе фајлова помоћу ОбексФТП‑а +Comment[sr@ijekavianlatin]=Podrška za bluetooth prenose fajlova pomoću ObexFTP‑a +Comment[sr@latin]=Podrška za bluetooth prenose fajlova pomoću ObexFTP‑a +Comment[sv]=Stöder filöverföringar för Blåtand med ObexFTP +Comment[tr]=ObexFTP kullanarak Bluetooth dosya transferlerini destekler +Comment[uk]=Підтримує перенесення файлів Bluetooth за допомогою ObexFTP +Comment[x-test]=xxSupports Bluetooth file transfer using ObexFTPxx +Comment[zh_TW]=使用 ObexFTP 支援藍牙檔案傳輸 diff --git a/bluedevil/src/kio/obexftp/daemon/org.freedesktop.DBus.ObjectManager.xml b/bluedevil/src/kio/obexftp/daemon/org.freedesktop.DBus.ObjectManager.xml new file mode 100644 index 00000000..651c8f34 --- /dev/null +++ b/bluedevil/src/kio/obexftp/daemon/org.freedesktop.DBus.ObjectManager.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/bluedevil/src/kio/obexftp/kded_obexftp.xml b/bluedevil/src/kio/obexftp/kded_obexftp.xml new file mode 100644 index 00000000..24c6c5cc --- /dev/null +++ b/bluedevil/src/kio/obexftp/kded_obexftp.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/bluedevil/src/kio/obexftp/kio_obexftp.cpp b/bluedevil/src/kio/obexftp/kio_obexftp.cpp new file mode 100644 index 00000000..1f901c1e --- /dev/null +++ b/bluedevil/src/kio/obexftp/kio_obexftp.cpp @@ -0,0 +1,370 @@ +/************************************************************************************* + * Copyright (C) 2010-2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "kio_obexftp.h" +#include "obexd_transfer.h" +#include "obexd_file_transfer.h" +#include "kdedobexftp.h" +#include "version.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "obexdtypes.h" +#include "transferfilejob.h" + +#include + +extern "C" int KDE_EXPORT kdemain(int argc, char **argv) +{ + KAboutData about("kioobexftp", "bluedevil", ki18n("kioobexftp"), bluedevil_version); + KCmdLineArgs::init(&about); + + KApplication app; + + if (argc != 4) { + fprintf(stderr, "Usage: kio_obexftp protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + KioFtp slave(argv[2], argv[3]); + slave.dispatchLoop(); + return 0; +} + +KioFtp::KioFtp(const QByteArray &pool, const QByteArray &app) + : SlaveBase("obexftp", pool, app) + , m_transfer(0) +{ + m_settingHost = false; + + m_timer = new QTimer(); + m_timer->setInterval(100); + + qDBusRegisterMetaType(); + m_kded = new org::kde::ObexFtp("org.kde.kded", "/modules/obexftpdaemon", QDBusConnection::sessionBus(), 0); +} + +KioFtp::~KioFtp() +{ + delete m_kded; +} + +void KioFtp::launchProgressBar() +{ + connect(m_timer, SIGNAL(timeout()), this, SLOT(updateProcess())); + totalSize(50); + m_counter = 0; + m_timer->start(); +} + +void KioFtp::updateProcess() +{ + if (m_counter == 49) { + disconnect(m_timer, SIGNAL(timeout()), this, SLOT(updateProcess())); + m_timer->stop(); + return; + } + + processedSize(m_counter); + m_counter++; +} + +void KioFtp::listDir(const KUrl &url) +{ + kDebug() << "listdir: " << url; + + infoMessage(i18n("Retrieving information from remote device...")); + + kDebug() << "Asking for listFolder"; + + //TODO: Check if changeFolder fails + m_transfer->ChangeFolder(url.path()).waitForFinished(); + + QDBusPendingReply reply = m_transfer->ListFolder(); + reply.waitForFinished(); + + QVariantMapList folderList = reply.value(); + Q_FOREACH(const QVariantMap folder, folderList) { + KIO::UDSEntry entry = entryFromInfo(folder); + + KUrl statUrl(url); + statUrl.setFileName(folder["Name"].toString()); + if (!m_statMap.contains(statUrl.prettyUrl())) { + kDebug() << "Stat: " << statUrl.prettyUrl() << entry.numberValue(KIO::UDSEntry::UDS_SIZE); + m_statMap.insert(statUrl.prettyUrl(), entry); + } + + listEntry(entry, false); + } + + listEntry(KIO::UDSEntry(), true); + finished(); +} + +void KioFtp::copy(const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags) +{ + Q_UNUSED(permissions) + Q_UNUSED(flags) + + kDebug() << "copy: " << src.url() << " to " << dest.url(); + + copyHelper(src, dest); + + finished(); +} + +void KioFtp::rename(const KUrl& src, const KUrl& dest, KIO::JobFlags flags) +{ + Q_UNUSED(src) + Q_UNUSED(dest) + Q_UNUSED(flags) + + error(KIO::ERR_UNSUPPORTED_ACTION, src.prettyUrl()); + finished(); +} + +void KioFtp::get(const KUrl& url) +{ + KTemporaryFile tempFile; + tempFile.setSuffix(url.fileName()); + tempFile.open();//Create the file + kDebug() << tempFile.fileName(); + + copyHelper(url, KUrl(tempFile.fileName())); + + kDebug() << "Getting mimetype"; + KMimeType::Ptr mime = KMimeType::findByPath(tempFile.fileName()); + mimeType(mime->name()); + kDebug() << "Mime: " << mime->name(); + + kDebug() << tempFile.size(); + totalSize(tempFile.size()); + + data(tempFile.readAll()); + + finished(); +} + + +void KioFtp::setHost(const QString &host, quint16 port, const QString &user, const QString &pass) +{ + Q_UNUSED(port) + Q_UNUSED(user) + Q_UNUSED(pass) + + infoMessage(i18n("Connecting to the device")); + + kDebug() << "setHost: " << host; + + kDebug() << "Waiting to stablish the connection 2"; + QDBusPendingReply reply = m_kded->session(host); + reply.waitForFinished(); + + kDebug() << "AFTER" << reply.isError(); + if (reply.isError()) { + kDebug() << reply.error().message(); + kDebug() << reply.error().name(); + } + + kDebug() << "Got a path" << reply.value(); + + m_address = host; + m_sessionPath = reply.value(); + m_transfer = new org::bluez::obex::FileTransfer1("org.bluez.obex", m_sessionPath, QDBusConnection::sessionBus()); + m_statMap.clear(); +} + +void KioFtp::del(const KUrl& url, bool isfile) +{ + Q_UNUSED(isfile) + + kDebug() << "Del: " << url.url(); + m_transfer->ChangeFolder(url.directory()).waitForFinished(); + m_transfer->Delete(url.fileName()).waitForFinished(); + finished(); +} + +void KioFtp::mkdir(const KUrl& url, int permissions) +{ + Q_UNUSED(permissions) + + kDebug() << "MkDir: " << url.url(); + m_transfer->ChangeFolder(url.directory()).waitForFinished(); + m_transfer->CreateFolder(url.fileName()).waitForFinished(); + finished(); +} + +void KioFtp::stat(const KUrl &url) +{ + kDebug() << "Stat: " << url.url(); + kDebug() << "Stat Dir: " << url.directory(); + kDebug() << "Stat File: " << url.fileName(); + kDebug() << "Empty Dir: " << url.directory().isEmpty(); + + statHelper(url); + + kDebug() << "Finished"; + finished(); +} + +void KioFtp::copyHelper(const KUrl& src, const KUrl& dest) +{ + if (src.scheme() == "obexftp" && dest.scheme() == "obexftp") { + error(KIO::ERR_UNSUPPORTED_ACTION, src.prettyUrl()); + //TODO: with obexd this seems possible, we should at least try + return; + } + + if (src.scheme() == "obexftp") { + copyFromObexftp(src, dest); + return; + } + + if (dest.scheme() == "obexftp") { + copyToObexftp(src, dest); + return; + } + + kDebug() << "This shouldn't happen..."; + finished(); +} + +void KioFtp::copyFromObexftp(const KUrl& src, const KUrl& dest) +{ + kDebug() << "Source: " << src << "Dest:" << dest; + + //Just in case the url is not in the stat, some times happens... + if (!m_statMap.contains(src.prettyUrl())) { + kDebug() << "The url is not in the cache, stating it"; + statHelper(src); + } + + if (m_statMap.value(src.prettyUrl()).isDir()) { + kDebug() << "Skipping to copy: " << src.prettyUrl(); + //TODO: Check if dir copying works with obexd + error(KIO::ERR_IS_DIRECTORY, src.prettyUrl()); + return; + } + + kDebug() << "Changing dir:" << src.directory(); + m_transfer->ChangeFolder(src.directory()).waitForFinished(); + + QString dbusPath = m_transfer->GetFile(dest.path(), src.fileName()).value().path(); + kDebug() << "Path from GetFile:" << dbusPath; + + int size = m_statMap[src.prettyUrl()].numberValue(KIO::UDSEntry::UDS_SIZE); + TransferFileJob *getFile = new TransferFileJob(dbusPath, this); + getFile->setSize(size); + getFile->exec(); + + finished(); +} + +void KioFtp::copyToObexftp(const KUrl& src, const KUrl& dest) +{ + kDebug() << "Source:" << src << "Dest:" << dest; + + kDebug() << "Changing folder: " << dest.directory(); + m_transfer->ChangeFolder(dest.directory()); + QString dbusPath = m_transfer->PutFile(src.path(), dest.fileName()).value().path(); + kDebug() << "Path from PutFile: " << dbusPath; + + QFile file(src.path()); + TransferFileJob *putFile = new TransferFileJob(dbusPath, this); + putFile->setSize(file.size()); + putFile->exec(); + + finished(); +} + +void KioFtp::statHelper(const KUrl& url) +{ + kDebug() << url; + + if (m_statMap.contains(url.prettyUrl())) { + kDebug() << "statMap contains the url"; + statEntry(m_statMap[url.prettyUrl()]); + return; + } + + if ((url.directory() == "/" || url.directory().isEmpty()) && url.fileName().isEmpty()) { + kDebug() << "Url is root"; + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_NAME, QString::fromLatin1("/")); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700); + entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1( "inode/directory" ) ); + + kDebug() << "Adding stat cached: " << url.prettyUrl(); + m_statMap[url.prettyUrl()] = entry; + statEntry(entry); + + return; + } + + kDebug() << "statMap does NOT contains the url"; + //TODO: Check if changeFolder fails + m_transfer->ChangeFolder(url.directory()).waitForFinished(); + QVariantMapList folderList = m_transfer->ListFolder().value(); + kDebug() << url.directory() << folderList.count(); + Q_FOREACH(const QVariantMap folder, folderList) { + KIO::UDSEntry entry = entryFromInfo(folder); + + QString fileName = folder["Name"].toString(); + if (url.fileName() == fileName) { + statEntry(entry); + } + + //Most probably the client of the kio will stat each file + //so since we are on it, let's cache all of them. + KUrl statUrl(url); + statUrl.setFileName(fileName); + if (!m_statMap.contains(statUrl.prettyUrl())) { + kDebug() << "Stat: " << statUrl.prettyUrl() << entry.stringValue(KIO::UDSEntry::UDS_NAME) << entry.numberValue(KIO::UDSEntry::UDS_SIZE); + m_statMap.insert(statUrl.prettyUrl(), entry); + } + } + + kDebug() << "Finished"; +} + +KIO::UDSEntry KioFtp::entryFromInfo(const QVariantMap& info) +{ + kDebug() << info; + + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_NAME, info["Name"].toString()); + entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, info["Created"].toString()); + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700); + entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, info["Modified"].toString()); + + if (info["Type"].toString() == QLatin1String("folder")) { + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + } else { + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + entry.insert(KIO::UDSEntry::UDS_SIZE, info["Size"].toLongLong()); + } + + return entry; +} \ No newline at end of file diff --git a/bluedevil/src/kio/obexftp/kio_obexftp.h b/bluedevil/src/kio/obexftp/kio_obexftp.h new file mode 100644 index 00000000..e2d06571 --- /dev/null +++ b/bluedevil/src/kio/obexftp/kio_obexftp.h @@ -0,0 +1,76 @@ +/*************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KIO_OBEXFTP_H +#define KIO_OBEXFTP_H +#include "kdedobexftp.h" + +#include +#include +#include + +#include + +class OrgBluezObexFileTransfer1Interface; +class KioFtp + : public QObject + , public KIO::SlaveBase +{ + +Q_OBJECT +public: + KioFtp(const QByteArray &pool, const QByteArray &app); + virtual ~KioFtp(); + + virtual void copy(const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags); + virtual void listDir(const KUrl &url); + virtual void setHost(const QString &host, quint16 port, const QString &user, const QString &pass); + virtual void stat(const KUrl &url); + virtual void del(const KUrl &url, bool isfile); + virtual void mkdir(const KUrl&url, int permissions); + virtual void rename(const KUrl& src, const KUrl& dest, KIO::JobFlags flags); + virtual void get(const KUrl& url); + +private Q_SLOTS: + void updateProcess(); + + KIO::UDSEntry entryFromInfo(const QVariantMap &info); + void copyHelper(const KUrl &src, const KUrl &dest); + void copyFromObexftp(const KUrl &src, const KUrl &dest); + void copyToObexftp(const KUrl &src, const KUrl &dest); + void statHelper(const KUrl &url); + void launchProgressBar(); + +private: + int m_counter; + bool m_settingHost; + QEventLoop m_eventLoop; + QMap m_statMap; + QString m_address; + QString m_sessionPath; + QTimer *m_timer; + org::kde::ObexFtp *m_kded; + OrgBluezObexFileTransfer1Interface *m_transfer; + +}; + +#endif // KIO_OBEXFTP_H diff --git a/bluedevil/src/kio/obexftp/obexdtypes.h b/bluedevil/src/kio/obexftp/obexdtypes.h new file mode 100644 index 00000000..f3496f10 --- /dev/null +++ b/bluedevil/src/kio/obexftp/obexdtypes.h @@ -0,0 +1,35 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#ifndef OBEXD_TYPES_H +#define OBEXD_TYPES_H + +#include +#include +#include +#include + +typedef QList QVariantMapList; +Q_DECLARE_METATYPE(QVariantMapList); + +typedef QMap QVariantMapMap; +Q_DECLARE_METATYPE(QVariantMapMap) + +typedef QMap DBusManagerStruct; +Q_DECLARE_METATYPE(DBusManagerStruct) +#endif //OBEXD_TYPES_H diff --git a/bluedevil/src/kio/obexftp/obexftp.protocol b/bluedevil/src/kio/obexftp/obexftp.protocol new file mode 100644 index 00000000..5fa56671 --- /dev/null +++ b/bluedevil/src/kio/obexftp/obexftp.protocol @@ -0,0 +1,17 @@ +[Protocol] +exec=kio_obexftp +protocol=obexftp +input=none +output=filesystem +copyToFile=true +copyFromFile=true +listing=Name,Type,Access +reading=true +writing=true +makedir=true +deleting=true +moving=true +Icon=preferences-system-bluetooth +maxInstancesPerHost=1 +maxInstances=1 +X-DocPath=kioslave/obexftp.html diff --git a/bluedevil/src/kio/obexftp/org.bluez.obex.FileTransfer1.xml b/bluedevil/src/kio/obexftp/org.bluez.obex.FileTransfer1.xml new file mode 100644 index 00000000..cd3de92b --- /dev/null +++ b/bluedevil/src/kio/obexftp/org.bluez.obex.FileTransfer1.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bluedevil/src/kio/obexftp/transferfilejob.cpp b/bluedevil/src/kio/obexftp/transferfilejob.cpp new file mode 100644 index 00000000..cf479c33 --- /dev/null +++ b/bluedevil/src/kio/obexftp/transferfilejob.cpp @@ -0,0 +1,140 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "kio_obexftp.h" +#include "transferfilejob.h" +#include "obexd_transfer.h" + +#include + +#include +#include + +typedef OrgBluezObexTransfer1Interface TransferInterface; +typedef OrgFreedesktopDBusPropertiesInterface PropertiesInterface; + +TransferFileJob::TransferFileJob(const QString& path, KioFtp* parent) + : KJob(parent) + , m_path(path) + , m_speedBytes(0) + , m_parent(parent) +{ + +} + +TransferFileJob::~TransferFileJob() +{ + delete m_transfer; + delete m_properties; +} + +void TransferFileJob::start() +{ + QMetaObject::invokeMethod(this, "createObjects", Qt::QueuedConnection); +} + +bool TransferFileJob::doKill() +{ + QDBusPendingReply reply = m_transfer->Cancel(); + reply.waitForFinished(); + + return !reply.isError(); +} + +void TransferFileJob::setSize(int size) +{ + kDebug() << size; + m_parent->totalSize(size); +} + +void TransferFileJob::createObjects() +{ + m_transfer = new TransferInterface("org.bluez.obex", m_path, QDBusConnection::sessionBus()); + m_properties = new PropertiesInterface("org.bluez.obex", m_path, QDBusConnection::sessionBus()); + + connect(m_properties, SIGNAL(PropertiesChanged(QString,QVariantMap,QStringList)), SLOT(propertiesChanged(QString,QVariantMap,QStringList))); +} + +void TransferFileJob::propertiesChanged(const QString& interface, const QVariantMap& properties, const QStringList &invalidProps) +{ + Q_UNUSED(invalidProps) + kDebug() << properties; + if (interface != QLatin1String("org.bluez.obex.Transfer1")) { + return; + } + + QStringList changedProps = properties.keys(); + Q_FOREACH(const QString &prop, changedProps) { + if (prop == QLatin1String("Status")) { + statusChanged(properties.value(prop)); + } else if (prop == QLatin1String("Transferred")) { + transferChanged(properties.value(prop)); + } + } +} + +void TransferFileJob::statusChanged(const QVariant& value) +{ + kDebug() << value; + QString status = value.toString(); + + if (status == QLatin1String("active")) { + m_time = QTime::currentTime(); + return; + } else if (status == QLatin1String("complete")) { + m_parent->finished(); + emitResult(); + return; + } else if (status == QLatin1String("error")) { + setError(KJob::UserDefinedError); + emitResult(); + return; + } + + kDebug() << "Not implemented status: " << status; +} + +void TransferFileJob::transferChanged(const QVariant& value) +{ + kDebug() << "Transferred: " << value; + if (m_parent->wasKilled()) { + kDebug() << "Kio was killed, aborting task"; + m_transfer->Cancel().waitForFinished(); + emitResult(); + return; + } + + bool ok = false; + qulonglong bytes = value.toULongLong(&ok); + if (!ok) { + kWarning() << "Couldn't cast transferChanged value" << value; + return; + } + + //If a least 1 second has passed since last update + int secondsSinceLastTime = m_time.secsTo(QTime::currentTime()); + if (secondsSinceLastTime > 0) { + float speed = (bytes - m_speedBytes) / secondsSinceLastTime; + + m_parent->speed(speed); + m_time = QTime::currentTime(); + m_speedBytes = bytes; + } + + m_parent->processedSize(bytes); +} \ No newline at end of file diff --git a/bluedevil/src/kio/obexftp/transferfilejob.h b/bluedevil/src/kio/obexftp/transferfilejob.h new file mode 100644 index 00000000..d82cd307 --- /dev/null +++ b/bluedevil/src/kio/obexftp/transferfilejob.h @@ -0,0 +1,59 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#ifndef KIO_GET_FILE_JOB_H +#define KIO_GET_FILE_JOB_H + +#include +#include + +#include + +class KioFtp; +class OrgBluezObexTransfer1Interface; +class OrgFreedesktopDBusPropertiesInterface; + +class TransferFileJob : public KJob +{ + Q_OBJECT +public: + explicit TransferFileJob(const QString &path, KioFtp* parent = 0); + + virtual void start(); + virtual bool doKill(); + + virtual ~TransferFileJob(); + + void setSize(int size); +private Q_SLOTS: + void createObjects(); + void propertiesChanged(const QString &interface , const QVariantMap &properties , const QStringList &invalidProps); + void statusChanged(const QVariant& value); + void transferChanged(const QVariant& value); + +private: + QTime m_time; + const QString m_path; + qlonglong m_speedBytes; + KioFtp *m_parent; + OrgBluezObexTransfer1Interface *m_transfer; + OrgFreedesktopDBusPropertiesInterface *m_properties; + +}; + +#endif //KIO_GET_FILE_JOB_H \ No newline at end of file diff --git a/bluedevil/src/monolithic/CMakeLists.txt b/bluedevil/src/monolithic/CMakeLists.txt new file mode 100644 index 00000000..7cd40c54 --- /dev/null +++ b/bluedevil/src/monolithic/CMakeLists.txt @@ -0,0 +1,13 @@ +set(monolithic_SRCS + main.cpp + monolithic.cpp +) + +qt4_add_dbus_interface(monolithic_SRCS org.bluez.Audio.xml audio_interface) + +kde4_add_executable(bluedevil-monolithic ${monolithic_SRCS}) + +target_link_libraries(bluedevil-monolithic ${KDE4_KIO_LIBRARY} ${KDE4_KDEUI_LIBS} ${LibBlueDevil_LIBRARIES}) + +install(TARGETS bluedevil-monolithic ${INSTALL_TARGETS_DEFAULT_ARGS}) +install(FILES bluedevil-monolithic.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/bluedevil/src/monolithic/bluedevil-monolithic.desktop b/bluedevil/src/monolithic/bluedevil-monolithic.desktop new file mode 100644 index 00000000..9ffd94ab --- /dev/null +++ b/bluedevil/src/monolithic/bluedevil-monolithic.desktop @@ -0,0 +1,141 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=BlueDevil +Name[ar]=بلوديفل +Name[bs]=BlueDevil +Name[ca]=BlueDevil +Name[ca@valencia]=BlueDevil +Name[cs]=BlueDevil +Name[da]=BlueDevil +Name[de]=BlueDevil +Name[el]=BlueDevil +Name[en_GB]=BlueDevil +Name[es]=BlueDevil +Name[et]=BlueDevil +Name[fi]=BlueDevil +Name[fr]=BlueDevil +Name[ga]=BlueDevil +Name[gl]=BlueDevil +Name[hu]=BlueDevil +Name[it]=BlueDevil +Name[kk]=BlueDevil +Name[km]=BlueDevil +Name[lt]=BlueDevil +Name[mr]=ब्लु-डेव्हिल +Name[nb]=BlueDevil +Name[nds]=BlueDevil +Name[nl]=BlueDevil +Name[pa]=BlueDevil +Name[pl]=BlueDevil +Name[pt]=BlueDevil +Name[pt_BR]=BlueDevil +Name[ro]=BlueDevil +Name[ru]=BlueDevil +Name[sk]=BlueDevil +Name[sl]=BlueDevil +Name[sr]=Блудевил +Name[sr@ijekavian]=Блудевил +Name[sr@ijekavianlatin]=BlueDevil +Name[sr@latin]=BlueDevil +Name[sv]=Blådjävul +Name[th]=บลูดีวิล +Name[tr]=BlueDevil +Name[ug]=BlueDevil +Name[uk]=BlueDevil +Name[x-test]=xxBlueDevilxx +Name[zh_CN]=BlueDevil +Name[zh_TW]=BlueDevil +GenericName=BlueDevil +GenericName[ar]=بلوديفل +GenericName[bs]=Bludevil +GenericName[ca]=BlueDevil +GenericName[ca@valencia]=BlueDevil +GenericName[cs]=BlueDevil +GenericName[da]=BlueDevil +GenericName[de]=BlueDevil +GenericName[el]=BlueDevil +GenericName[en_GB]=BlueDevil +GenericName[es]=BlueDevil +GenericName[et]=BlueDevil +GenericName[fi]=BlueDevil +GenericName[fr]=BlueDevil +GenericName[ga]=BlueDevil +GenericName[gl]=BlueDevil +GenericName[hu]=BlueDevil +GenericName[it]=BlueDevil +GenericName[kk]=BlueDevil +GenericName[km]=BlueDevil +GenericName[lt]=BlueDevil +GenericName[mr]=ब्लु-डेव्हिल +GenericName[nb]=BlueDevil +GenericName[nds]=BlueDevil +GenericName[nl]=BlueDevil +GenericName[pa]=BlueDevil +GenericName[pl]=BlueDevil +GenericName[pt]=BlueDevil +GenericName[pt_BR]=BlueDevil +GenericName[ro]=BlueDevil +GenericName[ru]=BlueDevil +GenericName[sk]=BlueDevil +GenericName[sl]=BlueDevil +GenericName[sr]=Блудевил +GenericName[sr@ijekavian]=Блудевил +GenericName[sr@ijekavianlatin]=BlueDevil +GenericName[sr@latin]=BlueDevil +GenericName[sv]=Blådjävul +GenericName[th]=บลูดีวิล +GenericName[tr]=BlueDevil +GenericName[ug]=BlueDevil +GenericName[uk]=BlueDevil +GenericName[x-test]=xxBlueDevilxx +GenericName[zh_CN]=BlueDevil +GenericName[zh_TW]=BlueDevil +Exec=bluedevil-monolithic +Comment=KDE Bluetooth support +Comment[ar]=دعم بلوثوث لكدي +Comment[bs]=Podrška blututa u KDE‑u +Comment[ca]=Implementació Bluetooth del KDE +Comment[ca@valencia]=Implementació Bluetooth del KDE +Comment[cs]=Podpora Bluetooth v KDE +Comment[da]=KDE bluetooth-understøttelse +Comment[de]=KDE-Bluetooth-Unterstützung +Comment[el]=Υποστήριξη Bluetooth στο KDE +Comment[en_GB]=KDE Bluetooth support +Comment[es]=Soporte Bluetooth de KDE +Comment[et]=KDE Bluetoothi toetus +Comment[fi]=KDE:n Bluetooth-tuki +Comment[fr]=Prise en charge de Bluetooth pour KDE +Comment[gl]=Funcionalidades de Bluetooth para KDE. +Comment[hu]=KDE bluetooth támogatás +Comment[it]=Supporto Bluetooth per KDE +Comment[kk]=KDE Bluetooth қолдауы +Comment[km]=គាំទ្រ​ប៊្លូធូស​របស់​ KDE +Comment[lt]=KDE Bluetooth palaikymas +Comment[mr]=केडीई ब्लूटूथ समर्थन +Comment[nb]=KDE Blåtann-støtte +Comment[nds]=Bluetooth-Ünnerstütten vun KDE +Comment[nl]=KDE Bluetooth ondersteuning +Comment[pa]=KDE ਬਲਿਊਟੁੱਥ ਸਹਿਯੋਗ +Comment[pl]=Obsługa Bluetooth w KDE +Comment[pt]=Suporte para Bluetooth no KDE +Comment[pt_BR]=Suporte para Bluetooth do KDE +Comment[ro]=Suport Bluetooth pentru KDE +Comment[ru]=Поддержка Bluetooth в KDE +Comment[sk]=Podpora Bluetooth KDE +Comment[sl]=KDE-jeva podpora za Bluetooth +Comment[sr]=Подршка блутута у КДЕ‑у +Comment[sr@ijekavian]=Подршка блутута у КДЕ‑у +Comment[sr@ijekavianlatin]=Podrška Bluetootha u KDE‑u +Comment[sr@latin]=Podrška Bluetootha u KDE‑u +Comment[sv]=KDE:s Blåtandstöd +Comment[th]=ระบบบลูทูทของ KDE +Comment[tr]=KDE Bluetooth desteği +Comment[ug]=ك د ئې(KDE) كۆكچىش قوللىشى +Comment[uk]=Підтримка Bluetooth у KDE +Comment[x-test]=xxKDE Bluetooth supportxx +Comment[zh_CN]=KDE 蓝牙支持 +Comment[zh_TW]=KDE 藍牙支援 +Icon=preferences-system-bluetooth +Terminal=false +Categories=Qt;KDE;X-Bluetooth;Network; diff --git a/bluedevil/src/monolithic/main.cpp b/bluedevil/src/monolithic/main.cpp new file mode 100644 index 00000000..1cb61f3d --- /dev/null +++ b/bluedevil/src/monolithic/main.cpp @@ -0,0 +1,42 @@ +/************************************************************************************* + * Copyright (C) 2010-2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "monolithic.h" +#include "version.h" +#include +#include +#include + +int main(int argc, char *argv[]) +{ + KAboutData aboutData("bluedevilmonolithic", "bluedevil", ki18n("Bluetooth"), bluedevil_version, ki18n("Bluetooth"), + KAboutData::License_GPL, ki18n("(c) 2010, UFO Coders")); + + aboutData.addAuthor(ki18n("Alejandro Fiestas Olivares"), ki18n("Maintainer"), "afiestas@kde.org", "http://www.afiestas.org/"); + aboutData.addAuthor(ki18n("Rafael Fernández López"), ki18n("Developer"), "ereslibre@kde.org", "http://www.ereslibre.es/"); + aboutData.setProgramIconName("preferences-system-bluetooth"); + + KCmdLineArgs::init(argc, argv, &aboutData); + + KUniqueApplication app; + app.setQuitOnLastWindowClosed(false); + new Monolithic; + + return app.exec(); +} diff --git a/bluedevil/src/monolithic/monolithic.cpp b/bluedevil/src/monolithic/monolithic.cpp new file mode 100644 index 00000000..c7b2d7e4 --- /dev/null +++ b/bluedevil/src/monolithic/monolithic.cpp @@ -0,0 +1,457 @@ +/* + * Copyright (C) 2010 Alejandro Fiestas Olivares + * Copyright (C) 2010 Rafael Fernández López + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "monolithic.h" +#include "audio_interface.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace BlueDevil; + +Monolithic::Monolithic(QObject* parent) + : KStatusNotifierItem(parent) +{ + setCategory(KStatusNotifierItem::Hardware); + setIconByName("preferences-system-bluetooth"); + setToolTip("preferences-system-bluetooth", "Bluetooth", ""); + + m_supportedServices.insert(i18n("Browse device"), "00001106-0000-1000-8000-00805F9B34FB"); + m_supportedServices.insert(i18n("Send Files"), "00001105-0000-1000-8000-00805F9B34FB"); + + offlineMode(); + + if (Manager::self()->usableAdapter()) { + onlineMode(); + } + + connect(Manager::self(), SIGNAL(adapterRemoved(Adapter*)), this, SLOT(adapterChanged())); + connect(Manager::self(), SIGNAL(adapterAdded(Adapter*)), this, SLOT(adapterChanged())); + connect(Manager::self(), SIGNAL(usableAdapterChanged(Adapter*)), this, SLOT(adapterChanged())); + + setStandardActionsEnabled(false); + setAssociatedWidget(contextMenu()); +} + +void Monolithic::adapterChanged() +{ + offlineMode(); + + if (Manager::self()->usableAdapter()) { + onlineMode(); + } +} + +quint32 sortHelper(quint32 type) +{ + switch (type) { + case BLUETOOTH_TYPE_ANY: + return 100; + case BLUETOOTH_TYPE_PHONE: + return 0; + case BLUETOOTH_TYPE_MODEM: + return 99; + case BLUETOOTH_TYPE_COMPUTER: + return 1; + case BLUETOOTH_TYPE_NETWORK: + return 98; + case BLUETOOTH_TYPE_HEADSET: + return 2; + case BLUETOOTH_TYPE_HEADPHONES: + return 3; + case BLUETOOTH_TYPE_OTHER_AUDIO: + return 4; + case BLUETOOTH_TYPE_KEYBOARD: + return 5; + case BLUETOOTH_TYPE_MOUSE: + return 6; + case BLUETOOTH_TYPE_CAMERA: + return 7; + case BLUETOOTH_TYPE_PRINTER: + return 8; + case BLUETOOTH_TYPE_JOYPAD: + return 9; + case BLUETOOTH_TYPE_TABLET: + return 10; + default: + break; + } + return 1000; +} + +bool sortDevices(Device *device1, Device *device2) +{ + quint32 type1 = sortHelper(classToType(device1->deviceClass())); + quint32 type2 = sortHelper(classToType(device2->deviceClass())); + if (type1 != type2) { + return type1 < type2; + } + return device1->name().compare(device2->name(), Qt::CaseInsensitive) < 0; +} + +void Monolithic::regenerateDeviceEntries() +{ + KMenu *const menu = contextMenu(); + + qDeleteAll(m_interfaceMap); + m_interfaceMap.clear(); + qDeleteAll(m_actions); + m_actions.clear(); + qDeleteAll(menu->actions()); + menu->clear(); + + //If there are adapters (because we're in this function) but any of them is powered + if (!poweredAdapters()) { + menu->addTitle(i18n("Bluetooth is Off")); + + QAction *separator = new QAction(menu); + separator->setSeparator(true); + menu->addAction(separator); + + KAction *activeBluetooth = new KAction(i18n("Turn Bluetooth On"), menu); + connect(activeBluetooth, SIGNAL(triggered()), this, SLOT(toggleBluetooth())); + menu->addAction(activeBluetooth); + return; + } + + KAction *sendFile = new KAction(KIcon("edit-find-project"), i18n("Send File"), menu); + connect(sendFile, SIGNAL(triggered()), this, SLOT(sendFile())); + menu->addAction(sendFile); + + KAction *browseDevices = new KAction(KIcon("document-preview-archive"), i18n("Browse devices"), menu); + connect(browseDevices, SIGNAL(triggered()), this, SLOT(browseDevices())); + menu->addAction(browseDevices); + + QList adapters = Manager::self()->adapters(); + Q_FOREACH(Adapter* adapter, adapters) { + if (adapters.count() == 1) { + menu->addTitle(i18n("Known Devices")); + } else { + menu->addTitle(adapter->name()); + } + + menu->addActions(actionsForAdapter(adapter)); + } + + menu->addSeparator(); + + KAction *addDevice = new KAction(KIcon("edit-find-project"), i18n("Add Device"), menu); + connect(addDevice, SIGNAL(triggered()), this, SLOT(addDevice())); + menu->addAction(addDevice); + + KAction *configBluetooth = new KAction(KIcon("configure"), i18n("Configure Bluetooth"), menu); + connect(configBluetooth, SIGNAL(triggered(bool)), this, SLOT(configBluetooth())); + menu->addAction(configBluetooth); + +//Shortcut configuration actions, mainly checkables for discovering and powering + menu->addSeparator(); + + KAction *discoverable = new KAction(i18n("Discoverable"), menu); + discoverable->setCheckable(true); + discoverable->setChecked(Manager::self()->usableAdapter()->isDiscoverable()); + connect(discoverable, SIGNAL(toggled(bool)), this, SLOT(activeDiscoverable(bool))); + menu->addAction(discoverable); + + KAction *activeBluetooth = new KAction(i18n("Turn Bluetooth Off"), menu); + connect(activeBluetooth, SIGNAL(triggered()), this, SLOT(toggleBluetooth())); + menu->addAction(activeBluetooth); + + menu->addSeparator(); + +// menu->addAction(KStandardAction::quit(QCoreApplication::instance(), SLOT(quit()), menu)); +} + +void Monolithic::regenerateConnectedDevices() +{ + unsigned int connectedDevices = 0; + if (Manager::self()->usableAdapter()) { + QList devices = Manager::self()->devices(); + Q_FOREACH(Device* device, devices) { + if (device->isConnected()) { + ++connectedDevices; + } + } + } + if (connectedDevices > 0) { + setOverlayIconByName("emblem-link"); + setToolTipSubTitle(i18ncp("Number of Bluetooth connected devices", "%1 connected device", "%1 connected devices", int(connectedDevices))); + } else if(poweredAdapters()) { + setOverlayIconByName(QString()); + setToolTipSubTitle(""); + } +} + +void Monolithic::onlineMode() +{ + setStatus(KStatusNotifierItem::Active); + + QList adapters = Manager::self()->adapters(); + Q_FOREACH(Adapter *adapter, adapters) { + connect(adapter, SIGNAL(deviceFound(Device*)), SLOT(deviceCreated(Device*))); + connect(adapter, SIGNAL(deviceRemoved(Device*)), SLOT(regenerateDeviceEntries())); + connect(adapter, SIGNAL(poweredChanged(bool)), SLOT(poweredChanged())); + connect(adapter, SIGNAL(discoverableChanged(bool)), SLOT(regenerateDeviceEntries())); + } + + QList devices = Manager::self()->devices(); + Q_FOREACH(Device* device, devices) { + connect(device, SIGNAL(propertyChanged(QString,QVariant)), this, SLOT(regenerateConnectedDevices())); + } + + regenerateDeviceEntries(); + regenerateConnectedDevices(); + poweredChanged(); +} + +void Monolithic::actionTriggered() +{ + KAction *action = qobject_cast(sender()); + QString service = action->data().toString(); + Device *device = Manager::self()->deviceForUBI(action->property("UBI").toString()); + if (!device) { + return; + } + + if (service == "00001106-0000-1000-8000-00805F9B34FB") { + browseTriggered(device->address()); + } else if (service == "00001105-0000-1000-8000-00805F9B34FB") { + sendTriggered(device->UBI()); + } +} + +void Monolithic::sendFile() +{ + KProcess process; + process.setProgram("bluedevil-sendfile"); + process.startDetached(); +} + +void Monolithic::browseDevices() +{ + KUrl url("bluetooth://"); + KRun::runUrl(url, "inode/directory", new QWidget()); +} + +void Monolithic::addDevice() +{ + KProcess process; + process.setProgram("bluedevil-wizard"); + process.startDetached(); +} + +void Monolithic::configBluetooth() +{ + KProcess process; + QStringList args; + args << "bluedevildevices"; + args << "bluedeviltransfer"; + args << "bluedeviladapters"; + process.startDetached("kcmshell4", args); +} + +void Monolithic::toggleBluetooth() +{ + bool powered = false; + if (poweredAdapters()) { + powered = true; + } + + QList adapters = Manager::self()->adapters(); + if (!adapters.isEmpty()) { + Q_FOREACH(Adapter *adapter, adapters) { + adapter->setPowered(!powered);//If there were powered devices, unpower them. + } + } + + //We do not call regenerateDeviceEntries because we assume that the adapter proprety powered will change + //and then, poweredChange will be emitted +} + +void Monolithic::activeDiscoverable(bool active) +{ + QList adapters = Manager::self()->adapters(); + if (!adapters.isEmpty()) { + Q_FOREACH(Adapter *adapter, adapters) { + adapter->setDiscoverable(active); + } + } +} + + +void Monolithic::browseTriggered(QString address) +{ + KUrl url("obexftp:/"); + url.setHost(address.replace(':', '-')); + KRun::runUrl(url, "inode/directory", new QWidget()); +} + +void Monolithic::sendTriggered(const QString &UBI) +{ + KToolInvocation::kdeinitExec("bluedevil-sendfile", QStringList() << QString("-u%1").arg(UBI)); +} + +void Monolithic::UUIDsChanged(const QStringList &UUIDs) +{ + Q_UNUSED(UUIDs); + regenerateDeviceEntries(); +} + +void Monolithic::poweredChanged() +{ + if (!poweredAdapters()) { + setIconByName("preferences-system-bluetooth-inactive"); + setTooltipTitleStatus(false); + setToolTipSubTitle(""); + } else { + setIconByName("preferences-system-bluetooth"); + setTooltipTitleStatus(true); + } + setOverlayIconByName(QString()); + regenerateDeviceEntries(); + regenerateConnectedDevices(); +} + +void Monolithic::deviceCreated(Device *device) +{ + connect(device, SIGNAL(propertyChanged(QString,QVariant)), this, SLOT(regenerateConnectedDevices())); + connect(device, SIGNAL(UUIDsChanged(QStringList)), this, SLOT(UUIDsChanged(QStringList))); + regenerateDeviceEntries(); + regenerateConnectedDevices(); +} + +void Monolithic::offlineMode() +{ + setStatus(KStatusNotifierItem::Passive); + setTooltipTitleStatus(false); + + KMenu *const menu = contextMenu(); + + qDeleteAll(m_interfaceMap); + m_interfaceMap.clear(); + qDeleteAll(m_actions); + m_actions.clear(); + qDeleteAll(menu->actions()); + menu->clear(); + + KAction *noAdaptersFound = new KAction(i18n("No adapters found"), menu); + noAdaptersFound->setEnabled(false); + menu->addAction(noAdaptersFound); + + QAction *separator = new QAction(menu); + separator->setSeparator(true); + menu->addAction(separator); +// menu->addAction(KStandardAction::quit(QCoreApplication::instance(), SLOT(quit()), menu)); +} + +bool Monolithic::poweredAdapters() +{ + QList adapters = Manager::self()->adapters(); + + if (adapters.isEmpty()) { + return false; + } + + Q_FOREACH(Adapter* adapter, adapters) { + if (adapter->isPowered()) { + return true; + } + } + + return false; +} + +void Monolithic::setTooltipTitleStatus(bool status) +{ + if (status) { + setToolTipTitle(i18nc("When the bluetooth is enabled and powered","Bluetooth is On")); + } else { + setToolTipTitle(i18nc("When the bluetooth is disabled or not powered","Bluetooth is Off")); + } +} + +QList< QAction* > Monolithic::actionsForAdapter(Adapter* adapter) +{ + QList actions; + QList devices = adapter->devices(); + if (devices.isEmpty()) { + return actions; + } + + qStableSort(devices.begin(), devices.end(), sortDevices); + QAction *action = 0; + Device *lastDevice = 0; + Q_FOREACH (Device *device, devices) { + action = actionForDevice(device, lastDevice); + actions << action; + lastDevice = device; + } + + m_actions << actions; + + return actions; +} + +QAction* Monolithic::actionForDevice(Device* device, Device *lastDevice) +{ + // Create device entry + KAction *deviceAction = new KAction(device->name(), this); + deviceAction->setData(QVariant::fromValue(device)); + + //We only show the icon for the first device of the type, less UI clutter + if (!lastDevice || classToType(lastDevice->deviceClass()) != classToType(device->deviceClass())) { + deviceAction->setIcon(KIcon(device->icon())); + } + + // Create the submenu that will hang from this device menu entry + KMenu *const subMenu = new KMenu; + QStringList UUIDs = device->UUIDs(); + + QSet deviceServices = UUIDs.toSet().intersect(m_supportedServices.values().toSet()); + Q_FOREACH(QString service, deviceServices) { + KAction *action = new KAction(m_supportedServices.key(service), subMenu); + action->setData(service); + action->setProperty("UBI", device->UBI()); + connect(action, SIGNAL(triggered()), SLOT(actionTriggered())); + subMenu->addAction(action); + } + + KAction *connectAction = new KAction(i18nc("Connect to a bluetooth device", "Connect"), deviceAction); + connect(connectAction, SIGNAL(triggered()), device, SLOT(connectDevice())); + subMenu->addAction(connectAction); + +//Enable when we can know if we should show Connect or not +// if (deviceServices.isEmpty()) { +// KAction *_unknown = new KAction(i18n("No supported services found"), deviceAction); +// _unknown->setEnabled(false); +// subMenu->addAction(_unknown); +// } + + deviceAction->setMenu(subMenu); + + return deviceAction; +} + +Q_DECLARE_METATYPE(Device*) diff --git a/bluedevil/src/monolithic/monolithic.h b/bluedevil/src/monolithic/monolithic.h new file mode 100644 index 00000000..2d6790ab --- /dev/null +++ b/bluedevil/src/monolithic/monolithic.h @@ -0,0 +1,94 @@ +/* + Copyright (C) 2010 Alejandro Fiestas Olivares + Copyright (C) 2010 Rafael Fernández López + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#ifndef MONOLITHIC_H +#define MONOLITHIC_H + +#include + +#include + +namespace BlueDevil { + class Adapter; + class Device; +} + +class QAction; +class KAction; + +using namespace BlueDevil; + +class Monolithic + : public KStatusNotifierItem +{ + +Q_OBJECT +public: + Monolithic(QObject* parent = 0); + + struct EntryInfo { + Device *device; + QString service; + void *dbusService; + }; + +public Q_SLOTS: + void adapterChanged(); + + void regenerateDeviceEntries(); + /** + * Check if there are connected device, and if so updates tooltip and overlay + */ + void regenerateConnectedDevices(); + + void sendFile(); + void browseDevices(); + void addDevice(); + void configBluetooth(); + void toggleBluetooth(); + void activeDiscoverable(bool active); + +private Q_SLOTS: + void actionTriggered(); + void browseTriggered(QString address); + void sendTriggered(const QString &ubi); + void UUIDsChanged(const QStringList &UUIDs); + void poweredChanged(); + void deviceCreated(Device *device); + +private: + void onlineMode(); + void offlineMode(); + + /** + * Returns true or false wether there are powered adapters + */ + bool poweredAdapters(); + void setTooltipTitleStatus(bool); + QList actionsForAdapter(Adapter *adapter); + QAction* actionForDevice(Device *device, Device *lastDevice); +private: + QHash m_supportedServices; + QMap m_interfaceMap; + QList m_actions; +}; + +Q_DECLARE_METATYPE(Monolithic::EntryInfo) + +#endif // MONOLITHIC_H diff --git a/bluedevil/src/monolithic/org.bluez.Audio.xml b/bluedevil/src/monolithic/org.bluez.Audio.xml new file mode 100644 index 00000000..8ad6b67e --- /dev/null +++ b/bluedevil/src/monolithic/org.bluez.Audio.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/bluedevil/src/sendfile/CMakeLists.txt b/bluedevil/src/sendfile/CMakeLists.txt new file mode 100644 index 00000000..fa52d170 --- /dev/null +++ b/bluedevil/src/sendfile/CMakeLists.txt @@ -0,0 +1,34 @@ +include_directories( + ${CMAKE_SOURCE_DIR} + ${CMAKE_BINARY_DIR} + ${KDE4_INCLUDES} +) + +set(sendfilehelper_SRCS + main.cpp + discoverwidget.cpp + sendfilewizard.cpp + pages/selectdeviceandfilespage.cpp + pages/selectdevicepage.cpp + pages/selectfilespage.cpp + pages/connectingpage.cpp + sendfilesjob.cpp +) + +kde4_add_ui_files(sendfilehelper_SRCS + pages/connecting.ui + pages/selectfilediscover.ui + discover.ui +) + +qt4_add_dbus_interface(sendfilehelper_SRCS org.bluez.obex.Client1.xml obexd_client) +qt4_add_dbus_interface(sendfilehelper_SRCS org.bluez.obex.ObjectPush1.xml obexd_push) +qt4_add_dbus_interface(sendfilehelper_SRCS org.bluez.obex.Transfer1.xml obexd_transfer) + +kde4_add_executable(bluedevil-sendfile ${sendfilehelper_SRCS}) + +target_link_libraries(bluedevil-sendfile + ${KDE4_KIO_LIBRARY} ${KDE4_KDEUI_LIBS} ${KDE4_KFILE_LIBS} ${LibBlueDevil_LIBRARIES}) + +install(TARGETS bluedevil-sendfile DESTINATION ${INSTALL_TARGETS_DEFAULT_ARGS}) +install(FILES bluedevil-sendfile.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/bluedevil/src/sendfile/bluedevil-sendfile.desktop b/bluedevil/src/sendfile/bluedevil-sendfile.desktop new file mode 100644 index 00000000..62abe444 --- /dev/null +++ b/bluedevil/src/sendfile/bluedevil-sendfile.desktop @@ -0,0 +1,141 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=BlueDevil Send File +Name[ar]=بلوديفل يرسل ملف +Name[bs]=Bludevilovo slanje datoteke +Name[ca]=Envia fitxer amb BlueDevil +Name[ca@valencia]=Envia fitxer amb BlueDevil +Name[cs]=Posílání souborů BlueDevil +Name[da]=BlueDevil filafsendelse +Name[de]=BlueDevil-Dateiversand +Name[el]=Αποστολή αρχείου μέσω BlueDevil +Name[en_GB]=BlueDevil Send File +Name[es]=Envío de archivo BlueDevil +Name[et]=BlueDevili faili saatmine +Name[fi]=BlueDevil-tiedostonlähetys +Name[fr]=Envoi de fichiers par BlueDevil +Name[gl]=Envío de ficheiros por Bluetooth +Name[hu]=BlueDevil fájlküldés +Name[it]=Invio file di BlueDevil +Name[kk]=BlueDevil файл жіберуі +Name[km]=ផ្ញើ​ឯកសារ​ BlueDevil ​ +Name[lt]=BlueDevil siųsti failą +Name[mr]=ब्लु-डेव्हिल फाईल पाठवा +Name[nb]=BlueDevil send fil +Name[nds]=BlueDevil - Datei loosstüern +Name[nl]=BlueDevil bestand verzenden +Name[pa]=BlueDevil ਫਾਇਲ ਭੇਜੋ +Name[pl]=Wysłanie pliku BlueDevil +Name[pt]=Envio de Ficheiro do Bluetooth +Name[pt_BR]=Envio de arquivo do BlueDevil +Name[ro]=Expediere fișier BlueDevil +Name[ru]=Передача файлов с помощью BlueDevil +Name[sk]=Posielanie súborov BlueDevil +Name[sl]=BlueDevil – pošiljanje datotek +Name[sr]=Блудевилово слање фајла +Name[sr@ijekavian]=Блудевилово слање фајла +Name[sr@ijekavianlatin]=BlueDevilovo slanje fajla +Name[sr@latin]=BlueDevilovo slanje fajla +Name[sv]=Blådjävul skicka fil +Name[th]=ส่งแฟ้มด้วยบลูดีวิล +Name[tr]=BlueDevil Dosya Gönder +Name[ug]=BlueDevil ھۆججەت يوللاش +Name[uk]=Надсилання файла BlueDevil +Name[x-test]=xxBlueDevil Send Filexx +Name[zh_CN]=BlueDevil 发送文件 +Name[zh_TW]=BlueDevil 傳送檔案 +GenericName=BlueDevil Send File +GenericName[ar]=بلوديفل يرسل ملف +GenericName[bs]=Bludevilovo slanje datoteke +GenericName[ca]=Envia fitxers amb BlueDevil +GenericName[ca@valencia]=Envia fitxers amb BlueDevil +GenericName[cs]=Posílání souborů BlueDevil +GenericName[da]=BlueDevil filafsendelse +GenericName[de]=BlueDevil-Dateiversand +GenericName[el]=Αποστολή αρχείου μέσω BlueDevil +GenericName[en_GB]=BlueDevil Send File +GenericName[es]=Envío de archivo BlueDevil +GenericName[et]=BlueDevili faili saatmine +GenericName[fi]=BlueDevil-tiedostonlähetys +GenericName[fr]=Envoi de fichiers par BlueDevil +GenericName[gl]=Envío de ficheiros por Bluetooth +GenericName[hu]=BlueDevil fájlküldés +GenericName[it]=Invio file di BlueDevil +GenericName[kk]=BlueDevil файлды жіберуі +GenericName[km]=ផ្ញើ​ឯកសារ​ BlueDevil ​ +GenericName[lt]=BlueDevil siųsti failą +GenericName[mr]=ब्लु-डेव्हिल फाईल पाठवा +GenericName[nb]=BlueDevil send fil +GenericName[nds]=BlueDevil - Datei loosstüern +GenericName[nl]=BlueDevil bestand verzenden +GenericName[pa]=BlueDevil ਫਾਇਲ ਭੇਜੋ +GenericName[pl]=Wysłanie pliku BlueDevil +GenericName[pt]=Envio de Ficheiro do Bluetooth +GenericName[pt_BR]=Envio de arquivo do BlueDevil +GenericName[ro]=Expediere fișier BlueDevil +GenericName[ru]=Передача файлов с помощью BlueDevil +GenericName[sk]=Posielanie súborov BlueDevil +GenericName[sl]=BlueDevil – pošiljanje datotek +GenericName[sr]=Блудевилово слање фајла +GenericName[sr@ijekavian]=Блудевилово слање фајла +GenericName[sr@ijekavianlatin]=BlueDevilovo slanje fajla +GenericName[sr@latin]=BlueDevilovo slanje fajla +GenericName[sv]=Blådjävul skicka fil +GenericName[th]=ส่งแฟ้มด้วยบลูดีวิล +GenericName[tr]=BlueDevil Dosya Gönder +GenericName[ug]=BlueDevil ھۆججەت يوللاش +GenericName[uk]=Надсилання файла BlueDevil +GenericName[x-test]=xxBlueDevil Send Filexx +GenericName[zh_CN]=BlueDevil 发送文件 +GenericName[zh_TW]=BlueDevil 傳送檔案 +MimeType=application/vnd.kde.bluedevil-sendfile; +Exec=bluedevil-sendfile -k%U +Comment=BlueDevil Send File +Comment[ar]=بلوديفل يرسل ملف +Comment[bs]=Bludevilovo slanje datoteke +Comment[ca]=Envia fitxers amb BlueDevil +Comment[ca@valencia]=Envia fitxers amb BlueDevil +Comment[cs]=Posílání souborů BlueDevil +Comment[da]=BlueDevil filafsendelse +Comment[de]=BlueDevil-Dateiversand +Comment[el]=Αποστολή αρχείου μέσω BlueDevil +Comment[en_GB]=BlueDevil Send File +Comment[es]=Envío de archivo BlueDevil +Comment[et]=BlueDevili faili saatmine +Comment[fi]=BlueDevil-tiedostonlähetys +Comment[fr]=Envoi de fichiers par BlueDevil +Comment[gl]=Envío de ficheiros por Bluetooth. +Comment[hu]=BlueDevil fájlküldés +Comment[it]=Invio file di BlueDevil +Comment[kk]=BlueDevil файлды жіберуі +Comment[km]=ផ្ញើ​ឯកសារ BlueDevil +Comment[lt]=BlueDevil siųsti failą +Comment[mr]=ब्लु-डेव्हिल फाईल पाठवा +Comment[nb]=BlueDevil send fil +Comment[nds]=Datei loosstüern +Comment[nl]=BlueDevil bestand verzenden +Comment[pa]=BlueDevil ਫਾਇਲ ਭੇਜੋ +Comment[pl]=Wysłanie pliku BlueDevil +Comment[pt]=Envio de Ficheiro do Bluetooth +Comment[pt_BR]=Envio de arquivo do BlueDevil +Comment[ro]=Expediere fișier BlueDevil +Comment[ru]=Передача файлов с помощью BlueDevil +Comment[sk]=Posielanie súborov BlueDevil +Comment[sl]=BlueDevil – pošiljanje datotek +Comment[sr]=Блудевилово слање фајла +Comment[sr@ijekavian]=Блудевилово слање фајла +Comment[sr@ijekavianlatin]=BlueDevilovo slanje fajla +Comment[sr@latin]=BlueDevilovo slanje fajla +Comment[sv]=Blådjävul skicka fil +Comment[th]=ส่งแฟ้มด้วยบลูดีวิล +Comment[tr]=BlueDevil Dosya Gönder +Comment[ug]=BlueDevil ھۆججەت يوللاش +Comment[uk]=Надсилання файла BlueDevil +Comment[x-test]=xxBlueDevil Send Filexx +Comment[zh_CN]=BlueDevil 发送文件 +Comment[zh_TW]=BlueDevil 傳送檔案 +Icon=preferences-system-bluetooth +Terminal=false +Categories=Qt;KDE;X-Bluetooth;Network; +NoDisplay=true diff --git a/bluedevil/src/sendfile/discover.ui b/bluedevil/src/sendfile/discover.ui new file mode 100644 index 00000000..37ea1cec --- /dev/null +++ b/bluedevil/src/sendfile/discover.ui @@ -0,0 +1,35 @@ + + + Discover + + + + 0 + 0 + 571 + 525 + + + + + + + + + + 48 + 48 + + + + + + + + + + + + + + diff --git a/bluedevil/src/sendfile/discoverwidget.cpp b/bluedevil/src/sendfile/discoverwidget.cpp new file mode 100644 index 00000000..fb800541 --- /dev/null +++ b/bluedevil/src/sendfile/discoverwidget.cpp @@ -0,0 +1,136 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#include "discoverwidget.h" +#include "ui_discover.h" + +#include +#include +#include +#include +#include + +#include + +using namespace BlueDevil; + +DiscoverWidget::DiscoverWidget(QWidget* parent) : QWidget(parent) +{ + setupUi(this); + + connect(deviceList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, + SLOT(itemSelected(QListWidgetItem*))); + connect(Manager::self()->usableAdapter(), SIGNAL(deviceFound(QVariantMap)), this, + SLOT(deviceFound(QVariantMap))); + + startScan(); +} + +DiscoverWidget::~DiscoverWidget() +{ +} + +void DiscoverWidget::startScan() +{ + deviceList->clear(); + stopScan(); + + QList knownDevices = Manager::self()->usableAdapter()->devices(); + Q_FOREACH(Device *device, knownDevices) { + if (device->UUIDs().contains("00001105-0000-1000-8000-00805F9B34FB", Qt::CaseInsensitive)) { + deviceFound(device); + } + } + Manager::self()->usableAdapter()->startDiscovery(); +} + +void DiscoverWidget::stopScan() +{ + if (Manager::self()->usableAdapter()) { + Manager::self()->usableAdapter()->stopDiscovery(); + } +} + +void DiscoverWidget::deviceFound(const QVariantMap& deviceInfo) +{ + deviceFoundGeneric(deviceInfo["Address"].toString(), + deviceInfo["Name"].toString(), + deviceInfo["Icon"].toString(), + deviceInfo["Alias"].toString()); +} + +void DiscoverWidget::deviceFound(Device* device) +{ + deviceFoundGeneric(device->address(), device->name(), device->icon(), device->alias()); +} + +void DiscoverWidget::deviceFoundGeneric(QString address, QString name, QString icon, QString alias) +{ + qDebug() << "========================"; + qDebug() << "Address: " << address; + qDebug() << "Name: " << name; + qDebug() << "Alias: " << alias; + qDebug() << "Icon: " << icon; + qDebug() << "\n"; + + + bool origName = false; + if (!name.isEmpty()) { + origName = true; + } + + if (!alias.isEmpty() && alias != name && !name.isEmpty()) { + name = QString("%1 (%2)").arg(alias).arg(name); + } + + if (name.isEmpty()) { + name = address; + } + + if (icon.isEmpty()) { + icon.append("preferences-system-bluetooth"); + } + + if (m_itemRelation.contains(address)) { + m_itemRelation[address]->setText(name); + m_itemRelation[address]->setIcon(KIcon(icon)); + m_itemRelation[address]->setData(Qt::UserRole+1, origName); + + if (deviceList->currentItem() == m_itemRelation[address]) { + emit deviceSelected(Manager::self()->usableAdapter()->deviceForAddress(address)); + } + return; + } + + QListWidgetItem *item = new QListWidgetItem(KIcon(icon), name, deviceList); + + item->setData(Qt::UserRole, address); + item->setData(Qt::UserRole+1, origName); + + m_itemRelation.insert(address, item); +} + +void DiscoverWidget::itemSelected(QListWidgetItem* item) +{ + emit deviceSelected(Manager::self()->usableAdapter()->deviceForAddress(item->data(Qt::UserRole).toString())); +} \ No newline at end of file diff --git a/bluedevil/src/sendfile/discoverwidget.h b/bluedevil/src/sendfile/discoverwidget.h new file mode 100644 index 00000000..d1cc03bb --- /dev/null +++ b/bluedevil/src/sendfile/discoverwidget.h @@ -0,0 +1,68 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#ifndef DISCOVERWIDGET_H +#define DISCOVERWIDGET_H + +#include "ui_discover.h" + +#include + +class QTimer; +class BlueWizard; + +namespace BlueDevil { + class Device; +} +using namespace BlueDevil; + +class DiscoverWidget : public QWidget +, public Ui::Discover +{ +Q_OBJECT + +public: + DiscoverWidget(QWidget* parent = 0); + virtual ~DiscoverWidget(); + void stopScan(); + +public Q_SLOTS: + void startScan(); + +private Q_SLOTS: + void deviceFound(const QVariantMap &deviceInfo); + void deviceFound(Device* device); + void itemSelected(QListWidgetItem* item); + +private: + void deviceFoundGeneric(QString address, QString name, QString icon, QString alias); + +private: + QMap m_itemRelation; + BlueWizard *m_wizard; + +Q_SIGNALS: + void deviceSelected(Device *device); +}; + +#endif // DISCOVERWIDGET_H diff --git a/bluedevil/src/sendfile/main.cpp b/bluedevil/src/sendfile/main.cpp new file mode 100644 index 00000000..b1e61aa3 --- /dev/null +++ b/bluedevil/src/sendfile/main.cpp @@ -0,0 +1,62 @@ +/************************************************************************************* + * Copyright (C) 2010-2012 by Alejandro Fiestas Olivares * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "sendfilewizard.h" +#include "version.h" +#include +#include +#include +#include +#include + +using namespace BlueDevil; + +int main(int argc, char *argv[]) +{ + KAboutData aboutData("bluedevilsendfile", "bluedevil", ki18n("Bluetooth Send File Helper"), bluedevil_version, ki18n("Bluetooth Send File Helper"), + KAboutData::License_GPL, ki18n("(c) 2010, UFO Coders")); + + aboutData.addAuthor(ki18n("Alejandro Fiestas Olivares"), ki18n("Maintainer"), "afiestas@kde.org", + "http://www.afiestas.org/"); + aboutData.setProgramIconName("preferences-system-bluetooth"); + + KCmdLineArgs::init(argc, argv, &aboutData); + + KCmdLineOptions options; + options.add("k").add("kio ", ki18n("Device UUID where the files will be sent")); + options.add("u").add("ubi ", ki18n("Device UUID where the files will be sent")); + options.add("f").add("files ", ki18n("Files that will be sent")); + KCmdLineArgs::addCmdLineOptions( options ); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + KApplication app; + app.setQuitOnLastWindowClosed(false); + + QString deviceInfo = args->getOption("ubi"); + if (deviceInfo.isEmpty()) { + deviceInfo = args->getOption("kio"); + } + + SendFileWizard *sendFileWizard = new SendFileWizard(deviceInfo, args->getOptionList("files")); + + sendFileWizard->show(); + + return app.exec(); +} diff --git a/bluedevil/src/sendfile/org.bluez.obex.Client1.xml b/bluedevil/src/sendfile/org.bluez.obex.Client1.xml new file mode 100644 index 00000000..b520d019 --- /dev/null +++ b/bluedevil/src/sendfile/org.bluez.obex.Client1.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bluedevil/src/sendfile/org.bluez.obex.ObjectPush1.xml b/bluedevil/src/sendfile/org.bluez.obex.ObjectPush1.xml new file mode 100644 index 00000000..fb14e5eb --- /dev/null +++ b/bluedevil/src/sendfile/org.bluez.obex.ObjectPush1.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bluedevil/src/sendfile/org.bluez.obex.Transfer1.xml b/bluedevil/src/sendfile/org.bluez.obex.Transfer1.xml new file mode 100644 index 00000000..037ec352 --- /dev/null +++ b/bluedevil/src/sendfile/org.bluez.obex.Transfer1.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bluedevil/src/sendfile/pages/CMakeLists.txt b/bluedevil/src/sendfile/pages/CMakeLists.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/bluedevil/src/sendfile/pages/CMakeLists.txt @@ -0,0 +1 @@ + diff --git a/bluedevil/src/sendfile/pages/connecting.ui b/bluedevil/src/sendfile/pages/connecting.ui new file mode 100644 index 00000000..10d9d7ea --- /dev/null +++ b/bluedevil/src/sendfile/pages/connecting.ui @@ -0,0 +1,35 @@ + + + Connecting + + + + 0 + 0 + 400 + 299 + + + + + + + Connecting to: %1 + + + + + + + 0 + + + 574 + + + + + + + + diff --git a/bluedevil/src/sendfile/pages/connectingpage.cpp b/bluedevil/src/sendfile/pages/connectingpage.cpp new file mode 100644 index 00000000..8d18c3df --- /dev/null +++ b/bluedevil/src/sendfile/pages/connectingpage.cpp @@ -0,0 +1,49 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "connectingpage.h" +#include "../sendfilewizard.h" + +#include "klocalizedstring.h" + +#include + +using namespace BlueDevil; + +ConnectingPage::ConnectingPage(QWidget* parent): QWizardPage(parent) +{ + setupUi(this); +} + +void ConnectingPage::initializePage() +{ + Manager::self()->usableAdapter()->stopDiscovery(); + Device *device = static_cast(wizard())->device(); + connLabel->setText(i18nc("Connecting to a Bluetooth device", "Connecting to %1...", device->name())); + + static_cast(wizard())->startTransfer(); +} + +bool ConnectingPage::isComplete() const +{ + return false; +} diff --git a/bluedevil/src/sendfile/pages/connectingpage.h b/bluedevil/src/sendfile/pages/connectingpage.h new file mode 100644 index 00000000..89dffff3 --- /dev/null +++ b/bluedevil/src/sendfile/pages/connectingpage.h @@ -0,0 +1,41 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef CONNECTINGPAGE_H +#define CONNECTINGPAGE_H + +#include "ui_connecting.h" + +#include + +class ConnectingPage : public QWizardPage, +public Ui::Connecting +{ +Q_OBJECT +public: + ConnectingPage(QWidget* parent = 0); + + virtual void initializePage(); + virtual bool isComplete() const; +}; + +#endif // CONNECTINGPAGE_H diff --git a/bluedevil/src/sendfile/pages/selectdeviceandfilespage.cpp b/bluedevil/src/sendfile/pages/selectdeviceandfilespage.cpp new file mode 100644 index 00000000..081be44a --- /dev/null +++ b/bluedevil/src/sendfile/pages/selectdeviceandfilespage.cpp @@ -0,0 +1,106 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "selectdeviceandfilespage.h" +#include "discoverwidget.h" +#include "../sendfilewizard.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +using namespace BlueDevil; +SelectDeviceAndFilesPage::SelectDeviceAndFilesPage(QWidget* parent): QWizardPage(parent), m_dialog(0) +{ + setupUi(this); + + DiscoverWidget *widget = new DiscoverWidget(this); + widget->setContentsMargins(0, 0, 0, 0); + discoverLayout->addWidget(widget); + + KPixmapSequenceOverlayPainter *workingPainter = new KPixmapSequenceOverlayPainter(this); + workingPainter->setWidget(working); + workingPainter->start(); + + int buttonSize = selectBtn->sizeHint().height(); + selectBtn->setFixedSize(buttonSize, buttonSize); + selectBtn->setIcon(KIcon("document-open")); + + connect(widget, SIGNAL(deviceSelected(Device*)), this, SLOT(deviceSelected(Device*))); + connect(selectBtn, SIGNAL(clicked(bool)), this, SLOT(openFileDialog())); +} + + +void SelectDeviceAndFilesPage::deviceSelected(Device* device) +{ + if (!device->name().isEmpty()) { + static_cast(wizard())->setDevice(device); + } else { + static_cast(wizard())->setDevice(0); + } + emit completeChanged(); +} + +void SelectDeviceAndFilesPage::openFileDialog() +{ + //Don't worry MLaurent, I'm not going to check the pointer before delete it :) + delete m_dialog; + + m_dialog = new KFileDialog(KUrl(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)), "*", this); + m_dialog->setMode(KFile::Files); + + connect(m_dialog, SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); + + m_dialog->exec(); +} + +void SelectDeviceAndFilesPage::selectionChanged() +{ + if (m_dialog->selectedUrls().isEmpty()) { + selectLbl->setText(i18n("Select one or more files:")); + } else { + selectLbl->setText(i18n("Selected files: %1", m_dialog->selectedUrls().count())); + static_cast(wizard())->setFiles(m_dialog->selectedFiles()); + } + emit completeChanged(); +} + +bool SelectDeviceAndFilesPage::isComplete() const +{ + if (!static_cast(wizard())->device()) { + return false; + } + + if (!m_dialog || m_dialog->selectedUrls().isEmpty()) { + return false; + } + + return true; +} diff --git a/bluedevil/src/sendfile/pages/selectdeviceandfilespage.h b/bluedevil/src/sendfile/pages/selectdeviceandfilespage.h new file mode 100644 index 00000000..ceb06744 --- /dev/null +++ b/bluedevil/src/sendfile/pages/selectdeviceandfilespage.h @@ -0,0 +1,56 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef SELECTDEVICEANDFILESPAGE_H +#define SELECTDEVICEANDFILESPAGE_H + +#include "ui_selectfilediscover.h" + +#include + +class KUrl; +class QWizard; +class KFileDialog; +namespace BlueDevil { + class Device; +} +using namespace BlueDevil; + +class SelectDeviceAndFilesPage : public QWizardPage , + public Ui::SelectFileDiscover +{ +Q_OBJECT +public: + SelectDeviceAndFilesPage(QWidget* parent = 0); + + virtual bool isComplete() const; + +private Q_SLOTS: + void deviceSelected(Device*); + void openFileDialog(); + void selectionChanged(); + +private: + KFileDialog *m_dialog; +}; + +#endif // SELECTDEVICEANDFILESPAGE_H diff --git a/bluedevil/src/sendfile/pages/selectdevicepage.cpp b/bluedevil/src/sendfile/pages/selectdevicepage.cpp new file mode 100644 index 00000000..be13b05d --- /dev/null +++ b/bluedevil/src/sendfile/pages/selectdevicepage.cpp @@ -0,0 +1,73 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "selectdevicepage.h" +#include "discoverwidget.h" +#include "../sendfilewizard.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace BlueDevil; +SelectDevicePage::SelectDevicePage(QWidget* parent): QWizardPage(parent), m_dialog(0) +{ + setupUi(this); + + DiscoverWidget *widget = new DiscoverWidget(this); + widget->setContentsMargins(0, 0, 0, 0); + discoverLayout->addWidget(widget); + + KPixmapSequenceOverlayPainter *workingPainter = new KPixmapSequenceOverlayPainter(this); + workingPainter->setWidget(working); + workingPainter->start(); + + selectBtn->setHidden(true); + selectLbl->setHidden(true); + connect(widget, SIGNAL(deviceSelected(Device*)), this, SLOT(deviceSelected(Device*))); +} + +void SelectDevicePage::deviceSelected(Device* device) +{ + if (!device->name().isEmpty()) { + static_cast(wizard())->setDevice(device); + } else { + static_cast(wizard())->setDevice(0); + } + emit completeChanged(); +} + +bool SelectDevicePage::isComplete() const +{ + if (!static_cast(wizard())->device()) { + return false; + } + + return true; +} diff --git a/bluedevil/src/sendfile/pages/selectdevicepage.h b/bluedevil/src/sendfile/pages/selectdevicepage.h new file mode 100644 index 00000000..8c4b494a --- /dev/null +++ b/bluedevil/src/sendfile/pages/selectdevicepage.h @@ -0,0 +1,54 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef SELECTDEVICEPAGE_H +#define SELECTDEVICEPAGE_H + +#include "ui_selectfilediscover.h" + +#include + +class KUrl; +class QWizard; +class KFileDialog; +namespace BlueDevil { + class Device; +} +using namespace BlueDevil; + +class SelectDevicePage : public QWizardPage , + public Ui::SelectFileDiscover +{ +Q_OBJECT +public: + SelectDevicePage(QWidget* parent = 0); + + virtual bool isComplete() const; + +private Q_SLOTS: + void deviceSelected(Device*); + +private: + KFileDialog *m_dialog; +}; + +#endif // SELECTDEVICEPAGE_H diff --git a/bluedevil/src/sendfile/pages/selectfilediscover.ui b/bluedevil/src/sendfile/pages/selectfilediscover.ui new file mode 100644 index 00000000..b85e85c8 --- /dev/null +++ b/bluedevil/src/sendfile/pages/selectfilediscover.ui @@ -0,0 +1,143 @@ + + + SelectFileDiscover + + + + 0 + 0 + 658 + 396 + + + + + 0 + 0 + + + + + QLayout::SetMinimumSize + + + 0 + + + + + Select a device from the list: + + + + + + + + 0 + 0 + + + + + 0 + + + + + + + + + + + + 0 + 0 + + + + + 4 + + + + + + 24 + 24 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 4 + 20 + + + + + + + + Scanning + + + + + + + Qt::Horizontal + + + + 289 + 20 + + + + + + + + Select one or more files: + + + + + + + + + + true + + + true + + + + + + + + + + + KPushButton + QPushButton +
kpushbutton.h
+
+
+ + +
diff --git a/bluedevil/src/sendfile/pages/selectfilespage.cpp b/bluedevil/src/sendfile/pages/selectfilespage.cpp new file mode 100644 index 00000000..0b3d2cac --- /dev/null +++ b/bluedevil/src/sendfile/pages/selectfilespage.cpp @@ -0,0 +1,62 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "selectfilespage.h" +#include "../sendfilewizard.h" + +#include +#include +#include +#include + +#include +#include +#include + +SelectFilesPage::SelectFilesPage(QWidget* parent): QWizardPage(parent) +{ + m_files = new KFileWidget(KUrl(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)), this); + m_files->setMode(KFile::Files); + m_files->setContentsMargins(0, 0, 0, 0); + setContentsMargins(0, 0, 0, 0); + + connect(m_files, SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(m_files); +} + +void SelectFilesPage::selectionChanged() +{ + QStringList fileList; + KFileItemList itemList = m_files->dirOperator()->selectedItems(); + Q_FOREACH(const KFileItem &file, itemList) { + fileList << file.localPath(); + } + static_cast(wizard())->setFiles(fileList); + emit completeChanged(); +} + +bool SelectFilesPage::isComplete() const +{ + return !m_files->dirOperator()->selectedItems().isEmpty(); +} \ No newline at end of file diff --git a/bluedevil/src/sendfile/pages/selectfilespage.h b/bluedevil/src/sendfile/pages/selectfilespage.h new file mode 100644 index 00000000..a8022706 --- /dev/null +++ b/bluedevil/src/sendfile/pages/selectfilespage.h @@ -0,0 +1,46 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef SELECTFILESPAGE_H +#define SELECTFILESPAGE_H + +#include + +class QWizard; +class KFileWidget; + +class SelectFilesPage : public QWizardPage +{ +Q_OBJECT +public: + SelectFilesPage(QWidget* parent = 0); + + virtual bool isComplete() const; + +private Q_SLOTS: + void selectionChanged(); + +private: + KFileWidget *m_files; +}; + +#endif // SELECTFILESPAGE_H diff --git a/bluedevil/src/sendfile/sendfilesjob.cpp b/bluedevil/src/sendfile/sendfilesjob.cpp new file mode 100644 index 00000000..339c3a55 --- /dev/null +++ b/bluedevil/src/sendfile/sendfilesjob.cpp @@ -0,0 +1,226 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "sendfilesjob.h" +#include "obexd_client.h" +#include "obexd_push.h" +#include "obexd_transfer.h" + +#include +#include + +#include + +using namespace BlueDevil; +SendFilesJob::SendFilesJob(const QStringList& files, Device* device, QObject* parent) + : KJob(parent) + , m_progress(0) + , m_totalSize(0) + , m_speedBytes(0) + , m_device(device) + , m_currentFileSize(0) + , m_currentFileProgress(0) +{ + kDebug() << files; + m_filesToSend = files; + + Q_FOREACH(const QString &filePath, files) { + QFile file(filePath); + m_filesToSendSize << file.size(); + m_totalSize += file.size(); + } + + setCapabilities(Killable); +} + +bool SendFilesJob::doKill() +{ + return true; +} + +void SendFilesJob::start() +{ + QMetaObject::invokeMethod(this, "doStart", Qt::QueuedConnection); +} + +void SendFilesJob::doStart() +{ + kDebug(); + QVariantMap map; + map["Target"] = "opp"; + + setTotalAmount(Bytes, m_totalSize); + setProcessedAmount(Bytes, 0); + + emit description(this, i18n("Sending file over Bluetooth"), QPair(i18nc("File transfer origin", "From"), m_filesToSend.first()), QPair(i18nc("File transfer destination", "To"), m_device->name())); + + m_client = new OrgBluezObexClient1Interface("org.bluez.obex", "/org/bluez/obex", QDBusConnection::sessionBus(), this); + + QDBusPendingReply reply = m_client->CreateSession(m_device->address(), map); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply); + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(createSessionSlot(QDBusPendingCallWatcher*))); +} + +void SendFilesJob::createSessionSlot(QDBusPendingCallWatcher *call) +{ + const QDBusPendingReply reply = *call; + call->deleteLater(); + if (reply.isError()) { + kDebug() << "Error:"; + kDebug() << reply.error().name(); + kDebug() << reply.error().message(); + setError(-1); + emitResult(); + return; + } + + QString path = reply.value().path(); + m_push = new OrgBluezObexObjectPush1Interface("org.bluez.obex", path, QDBusConnection::sessionBus(), this); + + nextJob(); +} + +void SendFilesJob::sendFileSlot(QDBusPendingCallWatcher* watcher) +{ + const QDBusPendingReply reply = *watcher; + watcher->deleteLater(); + QString path = reply.value().path(); + + m_props = new OrgFreedesktopDBusPropertiesInterface("org.bluez.obex", path, QDBusConnection::sessionBus(), this); + connect(m_props, SIGNAL(PropertiesChanged(QString,QVariantMap,QStringList)), SLOT(propertiesChangedSlot(QString,QVariantMap,QStringList))); +} + +void SendFilesJob::propertiesChangedSlot(const QString& interface, const QVariantMap& props, const QStringList& invalidProps) +{ + kDebug() << interface; + kDebug() << props; + kDebug() << invalidProps; + + QStringList changedProps = props.keys(); + Q_FOREACH(const QString &prop, changedProps) { + if (prop == QLatin1String("Status")) { + statusChanged(props.value(prop)); + } else if (prop == QLatin1String("Transferred")) { + transferChanged(props.value(prop)); + } + } +} + +void SendFilesJob::statusChanged(const QVariant& value) +{ + kDebug() << value; + QString status = value.toString(); + + if (status == QLatin1String("active")) { + m_time = QTime::currentTime(); + return; + } else if (status == QLatin1String("complete")) { + jobDone(); + return; + } else if (status == QLatin1String("error")) { + setError(KJob::UserDefinedError); + emitResult(); + return; + } + + kDebug() << "Not implemented status: " << status; +} + +void SendFilesJob::transferChanged(const QVariant& value) +{ + kDebug() << value; + bool ok = false; + qulonglong bytes = value.toULongLong(&ok); + if (!ok) { + kWarning() << "Couldn't cast transferChanged value" << value; + return; + } + + //If a least 1 second has passed since last update + int secondsSinceLastTime = m_time.secsTo(QTime::currentTime()); + if (secondsSinceLastTime > 0) { + float speed = (bytes - m_speedBytes) / secondsSinceLastTime; + emitSpeed(speed); + + m_time = QTime::currentTime(); + m_speedBytes = bytes; + } + + progress(bytes); +} + +void SendFilesJob::nextJob() +{ + kDebug(); + m_currentFile = m_filesToSend.takeFirst(); + m_currentFileSize = m_filesToSendSize.takeFirst(); + + emit description(this, i18n("Sending file over Bluetooth"), QPair(i18nc("File transfer origin", "From"), m_currentFile), QPair(i18nc("File transfer destination", "To"), m_device->name())); + + QDBusPendingReply fileReply = m_push->SendFile(m_currentFile); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(fileReply); + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(sendFileSlot(QDBusPendingCallWatcher*))); +} + +void SendFilesJob::jobDone() +{ + kDebug(); + m_speedBytes = 0; + m_currentFileSize = 0; + m_currentFileProgress = 0; + + if (!m_filesToSend.isEmpty()) { + nextJob(); + return; + } + + emitResult(); +} + +void SendFilesJob::progress(quint64 transferBytes) +{ + kDebug(); + + quint64 toAdd = transferBytes - m_currentFileProgress; + m_currentFileProgress = transferBytes; + m_progress += toAdd; + setProcessedAmount(Bytes, m_progress); +} + +void SendFilesJob::error(const QDBusObjectPath& transfer, const QString& error) +{ + Q_UNUSED(transfer) + kDebug() << error; + + //if this is the last file, just emit error + if (m_filesToSend.isEmpty()) { + setError(KJob::UserDefinedError); + return; + } + + quint64 toAdd = m_currentFileSize - m_currentFileProgress; + m_progress += toAdd; + setProcessedAmount(Bytes, m_progress); + nextJob(); + +} diff --git a/bluedevil/src/sendfile/sendfilesjob.h b/bluedevil/src/sendfile/sendfilesjob.h new file mode 100644 index 00000000..e230a4ae --- /dev/null +++ b/bluedevil/src/sendfile/sendfilesjob.h @@ -0,0 +1,85 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef SENDFILESJOB_H +#define SENDFILESJOB_H + +#include +#include +#include + +#include +#include + +namespace BlueDevil +{ + class Device; +} + +class QDBusPendingCallWatcher; +class OrgBluezObexClient1Interface; +class OrgBluezObexObjectPush1Interface; +class OrgFreedesktopDBusPropertiesInterface; + +using namespace BlueDevil; +class SendFilesJob : public KJob +{ +Q_OBJECT +public: + SendFilesJob(const QStringList &files, BlueDevil::Device* device, QObject* parent = 0); + + virtual void start(); + virtual bool doKill(); + +private Q_SLOTS: + void doStart(); + void createSessionSlot(QDBusPendingCallWatcher *call); + void nextJob(); + void jobDone(); + void progress(quint64 transferBytes); + void error(const QDBusObjectPath& transfer, const QString& error); + void propertiesChangedSlot(const QString& interface, const QVariantMap &props, const QStringList &invalidProps); + void sendFileSlot(QDBusPendingCallWatcher* watcher); + +private: + void transferChanged(const QVariant &value); + void statusChanged(const QVariant &value); + + QTime m_time; + QStringList m_filesToSend; + QList m_filesToSendSize; + QString m_currentFile; + QDBusObjectPath m_currentFileDBusPath; + quint64 m_progress; + quint64 m_totalSize; + qulonglong m_speedBytes; + Device *m_device; + quint64 m_currentFileSize; + quint64 m_currentFileProgress; + + + OrgBluezObexClient1Interface *m_client; + OrgBluezObexObjectPush1Interface *m_push; + OrgFreedesktopDBusPropertiesInterface *m_props; +}; + +#endif // SENDFILESJOB_H diff --git a/bluedevil/src/sendfile/sendfilewizard.cpp b/bluedevil/src/sendfile/sendfilewizard.cpp new file mode 100644 index 00000000..5b2f6d33 --- /dev/null +++ b/bluedevil/src/sendfile/sendfilewizard.cpp @@ -0,0 +1,168 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "sendfilewizard.h" + +#include "pages/selectdeviceandfilespage.h" +#include "pages/selectdevicepage.h" +#include "pages/selectfilespage.h" +#include "pages/connectingpage.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace BlueDevil; + +SendFileWizard::SendFileWizard(const QString& deviceInfo, const QStringList& files) + : QWizard() + , m_device(0) + , m_job(0) +{ + if (!BlueDevil::Manager::self()->usableAdapter()) { + kDebug() << "No Adapters found"; + qApp->exit(); + return; + } + + kDebug() << "DeviceUbi: " << deviceInfo; + kDebug() << "Files"; + kDebug() << files; + + setWindowTitle(i18n("Bluetooth Send Files")); + setOption(NoCancelButton, false); + setButton(QWizard::NextButton, new KPushButton(KIcon("document-export"), i18n("Send Files"))); + setButton(QWizard::CancelButton, new KPushButton(KStandardGuiItem::cancel())); + setOption(QWizard::DisabledBackButtonOnLastPage); + setOption(QWizard::NoBackButtonOnStartPage); + + kDebug() << "DeviceUbi: " << deviceInfo; + kDebug() << "Files"; + kDebug() << files; + + setDevice(deviceInfo); + + if (!m_device || files.isEmpty()) { + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + setMinimumSize(680, 400); + updateGeometry(); + } + + if (!m_device && files.isEmpty()) { + addPage(new SelectDeviceAndFilesPage()); + } else if (!m_device) { + addPage(new SelectDevicePage()); + setFiles(files); + } else { + + if (files.isEmpty()) { + addPage(new SelectFilesPage()); + } else { + setFiles(files); + } + } + + addPage(new ConnectingPage()); +} + +SendFileWizard::~SendFileWizard() +{ + if (m_job) { + m_job->doKill(); + } +} + +void SendFileWizard::done(int result) +{ + QWizard::done(result); + if (!m_job) { + qApp->quit(); + } +} + +void SendFileWizard::setFiles(const QStringList& files) +{ + kDebug() << files; + m_files = files; +} + +void SendFileWizard::setDevice(Device* device) +{ + kDebug() << device; + m_device = device; +} + +void SendFileWizard::setDevice(QString deviceUrl) +{ + kDebug() << deviceUrl; + + BlueDevil::Device *device = 0; + if (deviceUrl.startsWith("bluetooth")) { + deviceUrl.remove("bluetooth:"); + deviceUrl.replace(":", "-"); + deviceUrl.prepend("bluetooth:"); + KUrl url(deviceUrl); + device = Manager::self()->usableAdapter()->deviceForAddress(url.host().replace("-", ":")); + } else { + device = Manager::self()->usableAdapter()->deviceForUBI(deviceUrl); + } + + m_device = device; +} + +Device* SendFileWizard::device() +{ + return m_device; +} + +void SendFileWizard::wizardDone() +{ + done(1); +} + +void SendFileWizard::startTransfer() +{ + if (m_files.isEmpty()) { + kDebug() << "No files to send"; + return; + } + if (!m_device) { + kDebug() << "No device selected"; + } + + m_job = new SendFilesJob(m_files, m_device); + connect(m_job, SIGNAL(destroyed(QObject*)), qApp, SLOT(quit())); + + KIO::getJobTracker()->registerJob(m_job); + m_job->start(); + + QTimer::singleShot(2000, this, SLOT(wizardDone())); +} diff --git a/bluedevil/src/sendfile/sendfilewizard.h b/bluedevil/src/sendfile/sendfilewizard.h new file mode 100644 index 00000000..9b425e2d --- /dev/null +++ b/bluedevil/src/sendfile/sendfilewizard.h @@ -0,0 +1,67 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010-2011 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef SENDFILEWIZARD_H +#define SENDFILEWIZARD_H + +#include +#include +#include +#include "discoverwidget.h" + +class WizardAgent; +class QStringList; +class SendFilesJob; +namespace BlueDevil { + class Device; +} +using namespace BlueDevil; + +class SendFileWizard : public QWizard +{ +Q_OBJECT + +public: + SendFileWizard(const QString &deviceUBI, const QStringList &files); + virtual ~SendFileWizard(); + + virtual void done(int result); + + void setFiles(const QStringList &files); + + void setDevice(Device *device); + void setDevice(QString deviceUrl); + Device* device(); + + void startTransfer(); + +private Q_SLOTS: + void wizardDone(); + +private: + QStringList m_files; + + Device *m_device; + SendFilesJob *m_job; +}; + +#endif // SENDFILEWIZARD_H diff --git a/bluedevil/src/settings/CMakeLists.txt b/bluedevil/src/settings/CMakeLists.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/bluedevil/src/settings/CMakeLists.txt @@ -0,0 +1 @@ + diff --git a/bluedevil/src/settings/filereceiver.kcfg b/bluedevil/src/settings/filereceiver.kcfg new file mode 100644 index 00000000..457506ad --- /dev/null +++ b/bluedevil/src/settings/filereceiver.kcfg @@ -0,0 +1,74 @@ + + + + QDesktopServices + + kglobalsettings.h + QFile + QDir + kdebug.h + KUrl + KGlobal + KConfig + kstandarddirs.h + + + + + + true + + + + QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation) + + + + 0 + + + + + true + + + + true + + + + false + + + + QString downloadPath; + const QString xdgUserDirs = KGlobal::dirs()->localxdgconfdir() + QLatin1String( "user-dirs.dirs" ); + if( QFile::exists( xdgUserDirs ) ) { + KConfig xdgUserConf( xdgUserDirs, KConfig::SimpleConfig ); + KConfigGroup g( &xdgUserConf, "" ); + downloadPath = g.readPathEntry( "XDG_PUBLICSHARE_DIR", downloadPath ).remove( '"' ); + if ( downloadPath.isEmpty() ) { + downloadPath = KStandardDirs().saveLocation("data", "bluedevil/shared_files/"); + } + } + + downloadPath = QDir::cleanPath( downloadPath ); + QDir dir(downloadPath); + if (!dir.exists()) { + KUrl url(downloadPath); + QString dirname = dir.dirName(); + dir.setPath(url.upUrl().path()); + if (!dir.mkdir(dirname)) { + downloadPath = KStandardDirs().saveLocation("data", "bluedevil/shared_files/"); + } + } + + KUrl rootFolderUrl(downloadPath); + + rootFolderUrl + + + diff --git a/bluedevil/src/settings/filereceiversettings.kcfgc b/bluedevil/src/settings/filereceiversettings.kcfgc new file mode 100644 index 00000000..6d6c0e4c --- /dev/null +++ b/bluedevil/src/settings/filereceiversettings.kcfgc @@ -0,0 +1,4 @@ +File=filereceiver.kcfg +ClassName=FileReceiverSettings +Singleton=true +Mutators=true diff --git a/bluedevil/src/settings/global.kcfg b/bluedevil/src/settings/global.kcfg new file mode 100644 index 00000000..389a53e8 --- /dev/null +++ b/bluedevil/src/settings/global.kcfg @@ -0,0 +1,14 @@ + + + + kglobalsettings.h + + + + true + + + diff --git a/bluedevil/src/settings/globalsettings.kcfgc b/bluedevil/src/settings/globalsettings.kcfgc new file mode 100644 index 00000000..0c65f173 --- /dev/null +++ b/bluedevil/src/settings/globalsettings.kcfgc @@ -0,0 +1,4 @@ +File=global.kcfg +ClassName=GlobalSettings +Singleton=true +Mutators=true diff --git a/bluedevil/src/wizard/CMakeLists.txt b/bluedevil/src/wizard/CMakeLists.txt new file mode 100644 index 00000000..847e6729 --- /dev/null +++ b/bluedevil/src/wizard/CMakeLists.txt @@ -0,0 +1,31 @@ +set(wizard_SRCS + main.cpp + bluewizard.cpp + wizardagent.cpp + + pages/discoverpage.cpp + pages/nopairing.cpp + pages/legacypairing.cpp + pages/legacypairingdatabase.cpp + pages/keyboardpairing.cpp + pages/ssppairing.cpp + pages/fail.cpp +) + +kde4_add_ui_files(wizard_SRCS + pages/discover.ui + pages/nopairing.ui + pages/legacypairing.ui + pages/keyboardpairing.ui + pages/ssppairing.ui + pages/fail.ui +) + +kde4_add_executable(bluedevil-wizard ${wizard_SRCS}) + +target_link_libraries(bluedevil-wizard + ${KDE4_KIO_LIBRARY} ${KDE4_KDEUI_LIBS} ${LibBlueDevil_LIBRARIES}) + +install(TARGETS bluedevil-wizard ${INSTALL_TARGETS_DEFAULT_ARGS}) +install(FILES bluedevil-wizard.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) +install(FILES pin-code-database.xml DESTINATION ${DATA_INSTALL_DIR}/bluedevilwizard) diff --git a/bluedevil/src/wizard/USER_FLOW.txt b/bluedevil/src/wizard/USER_FLOW.txt new file mode 100644 index 00000000..b43b9c41 --- /dev/null +++ b/bluedevil/src/wizard/USER_FLOW.txt @@ -0,0 +1,38 @@ ++-----------------------------+ +| Introduction | +| Page | ++--------------V--------------+ + | + | ++--------------V--------------+ +| Discovering | +| <------+ +| Page | | ++--------------V--------------+ | + | | + | | ++--------------V--------------+ | +| IF | | +| device is selected | | +| | | +| AND N------+ +| device name not empty | ++--------------Y--------------+ + | + | ++--------------V--------------+ +----------------------+ +| IF | +--------------------+ | IF | +| legacyPairing is false N-------> Pin Page >-------> AutomaticPin N-------+ +| (Means that SSP == true) | +--------------------+ | | | ++--------------Y--------------+ +-----------Y----------+ | +----------------------+ +-------------------+ + | | | | IF | | Pairing Page | + | | +-------> type == Keyboard N--------> show "Working | ++--------------V--------------+ +-----------V----------+ | | | | | +| Is PIN confirmed? N---> Exit | Try to get one | | +-----------Y----------+ +-------------------+ ++--------------V--------------+ | from the xml or | | | + | | generate a random >-------+ | +--------------------+ + | | one | | | Pairing Page | + +----------V----------+ | | +------------------> show "PIN" | + | Services Page | +----------------------+ | | + | End | +--------------------+ + +---------------------+ \ No newline at end of file diff --git a/bluedevil/src/wizard/bluedevil-wizard.desktop b/bluedevil/src/wizard/bluedevil-wizard.desktop new file mode 100644 index 00000000..2704b21b --- /dev/null +++ b/bluedevil/src/wizard/bluedevil-wizard.desktop @@ -0,0 +1,140 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=BlueDevil Wizard +Name[ar]=معالج بلوديفل +Name[bs]=Bludevilov čarobnjak +Name[ca]=Assistent del BlueDevil +Name[ca@valencia]=Assistent del BlueDevil +Name[cs]=Průvodce Bluedevil +Name[da]=Guide til BlueDevil +Name[de]=BlueDevil-Assistent +Name[el]=Οδηγός BlueDevil +Name[en_GB]=BlueDevil Wizard +Name[es]=Asistente de BlueDevil +Name[et]=BlueDevili nõustaja +Name[fi]=Opastettu BlueDevil-toiminto +Name[fr]=Assistant pour BlueDevil +Name[gl]=Asistente de BlueDevil +Name[hu]=BlueDevil varázsló +Name[it]=Procedura guidata di BlueDevil +Name[kk]=BlueDevil шебері +Name[km]=អ្នក​ជំនួយការ BlueDevil +Name[lt]=BlueDevil vedlys +Name[mr]=ब्लु-डेव्हिल विझार्ड +Name[nb]=BlueDevil veiviser +Name[nds]=BlueDevil-Hölper +Name[nl]=BlueDevil assistent +Name[pa]=BlueDevil ਸਹਾਇਕ +Name[pl]=Asystent BlueDevil +Name[pt]=Assistente BlueDevil +Name[pt_BR]=Assistente BlueDevil +Name[ro]=Asistent BlueDevil +Name[ru]=Мастер настройки BlueDevil +Name[sk]=Sprievodca BlueDevil +Name[sl]=BlueDevil – čarovnik +Name[sr]=Блудевилов чаробњак +Name[sr@ijekavian]=Блудевилов чаробњак +Name[sr@ijekavianlatin]=BlueDevilov čarobnjak +Name[sr@latin]=BlueDevilov čarobnjak +Name[sv]=Blådjävul guide +Name[th]=ตัวช่วยปรับแต่งบลูดีวิล +Name[tr]=BlueDevil Sihirbazı +Name[ug]=BlueDevil يېتەكچىسى +Name[uk]=Майстер BlueDevil +Name[x-test]=xxBlueDevil Wizardxx +Name[zh_CN]=BlueDevil 向导 +Name[zh_TW]=BlueDevil 精靈 +GenericName=BlueDevil Wizard +GenericName[ar]=معالج بلوديفل +GenericName[bs]=Bludevilov čarobnjak +GenericName[ca]=Assistent del BlueDevil +GenericName[ca@valencia]=Assistent del BlueDevil +GenericName[cs]=Průvodce Bluedevil +GenericName[da]=Guide til BlueDevil +GenericName[de]=BlueDevil-Assistent +GenericName[el]=Οδηγός BlueDevil +GenericName[en_GB]=BlueDevil Wizard +GenericName[es]=Asistente de BlueDevil +GenericName[et]=BlueDevili nõustaja +GenericName[fi]=Opastettu BlueDevil-toiminto +GenericName[fr]=Assistant pour BlueDevil +GenericName[gl]=Asistente de BlueDevil +GenericName[hu]=BlueDevil varázsló +GenericName[it]=Procedura guidata di BlueDevil +GenericName[kk]=BlueDevil шебері +GenericName[km]=អ្នកជំនួយការ​ BlueDevil +GenericName[lt]=BlueDevil vedlys +GenericName[mr]=ब्लु-डेव्हिल विझार्ड +GenericName[nb]=BlueDevil veiviser +GenericName[nds]=BlueDevil-Hölper +GenericName[nl]=BlueDevil assistent +GenericName[pa]=BlueDevil ਸਹਾਇਕ +GenericName[pl]=Asystent BlueDevil +GenericName[pt]=Assistente BlueDevil +GenericName[pt_BR]=Assistente BlueDevil +GenericName[ro]=Asistent BlueDevil +GenericName[ru]=Мастер настройки BlueDevil +GenericName[sk]=Sprievodca BlueDevil +GenericName[sl]=BlueDevil – čarovnik +GenericName[sr]=Блудевилов чаробњак +GenericName[sr@ijekavian]=Блудевилов чаробњак +GenericName[sr@ijekavianlatin]=BlueDevilov čarobnjak +GenericName[sr@latin]=BlueDevilov čarobnjak +GenericName[sv]=Blådjävul guide +GenericName[th]=ตัวช่วยปรับแต่งบลูดีวิล +GenericName[tr]=BlueDevil Sihirbazı +GenericName[ug]=BlueDevil يېتەكچىسى +GenericName[uk]=Майстер BlueDevil +GenericName[x-test]=xxBlueDevil Wizardxx +GenericName[zh_CN]=BlueDevil 向导 +GenericName[zh_TW]=BlueDevil 精靈 +Exec=bluedevil-wizard %U +Comment=BlueDevil Wizard +Comment[ar]=معالج بلوديفل +Comment[bs]=Bludevilov čarobnjak +Comment[ca]=Assistent del BlueDevil +Comment[ca@valencia]=Assistent del BlueDevil +Comment[cs]=BlueDevil +Comment[da]=Guide til BlueDevil +Comment[de]=BlueDevil-Assistent +Comment[el]=Οδηγός BlueDevil +Comment[en_GB]=BlueDevil Wizard +Comment[es]=Asistente de BlueDevil +Comment[et]=BlueDevili nõustaja +Comment[fi]=Opastettu BlueDevil-toiminto +Comment[fr]=Assistant pour BlueDevil +Comment[gl]=Asistente de BlueDevil. +Comment[hu]=BlueDevil varázsló +Comment[it]=Procedura guidata di BlueDevil +Comment[kk]=BlueDevil шебері +Comment[km]=អ្នក​ជំនួយការ​ BlueDevil +Comment[lt]=BlueDevil vedlys +Comment[mr]=ब्लु-डेव्हिल विझार्ड +Comment[nb]=BlueDevil veiviser +Comment[nds]=BlueDevil-Hölper +Comment[nl]=BlueDevil assistent +Comment[pa]=BlueDevil ਸਹਾਇਕ +Comment[pl]=Asystent BlueDevil +Comment[pt]=Assistente BlueDevil +Comment[pt_BR]=Assistente BlueDevil +Comment[ro]=Asistent BlueDevil +Comment[ru]=Мастер настройки BlueDevil +Comment[sk]=Sprievodca BlueDevil +Comment[sl]=BlueDevil – čarovnik +Comment[sr]=Блудевилов чаробњак +Comment[sr@ijekavian]=Блудевилов чаробњак +Comment[sr@ijekavianlatin]=BlueDevilov čarobnjak +Comment[sr@latin]=BlueDevilov čarobnjak +Comment[sv]=Blådjävul guide +Comment[th]=ตัวช่วยปรับแต่งบลูดีวิล +Comment[tr]=BlueDevil Sihirbazı +Comment[ug]=BlueDevil يېتەكچىسى +Comment[uk]=Майстер BlueDevil +Comment[x-test]=xxBlueDevil Wizardxx +Comment[zh_CN]=BlueDevil 向导 +Comment[zh_TW]=BlueDevil 精靈 +Icon=preferences-system-bluetooth +Terminal=false +Categories=Qt;KDE;X-Bluetooth;Network; +NoDisplay=true diff --git a/bluedevil/src/wizard/bluewizard.cpp b/bluedevil/src/wizard/bluewizard.cpp new file mode 100644 index 00000000..55c21d5a --- /dev/null +++ b/bluedevil/src/wizard/bluewizard.cpp @@ -0,0 +1,160 @@ +/* + Copyright (C) 2010 UFO Coders + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#include "bluewizard.h" +#include "wizardagent.h" +#include "pages/discoverpage.h" +#include "pages/nopairing.h" +#include "pages/legacypairing.h" +#include "pages/legacypairingdatabase.h" +#include "pages/keyboardpairing.h" +#include "pages/ssppairing.h" +#include "pages/fail.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +BlueWizard::BlueWizard(const KUrl &url) : QWizard(), m_device(0), m_manualPin(false) +{ + setWindowTitle(i18n("Bluetooth Device Wizard")); + + setOption(QWizard::IndependentPages, true); + + if (url.host().length() == 17) { + setPreselectedAddress(url.host().replace("-", ":").toLatin1()); + } + + if (url.fileName().length() == 36) { + setPreselectedUuid(url.fileName().toLatin1()); + } + + setPage(Discover, new DiscoverPage(this)); + setPage(NoPairing, new NoPairingPage(this)); + setPage(Connect, new NoPairingPage(this)); + setPage(LegacyPairing, new LegacyPairingPage(this)); + setPage(LegacyPairingDatabase, new LegacyPairingPageDatabase(this)); + setPage(KeyboardPairing, new KeyboardPairingPage(this)); + setPage(SSPPairing, new SSPPairingPage(this)); + setPage(Fail, new FailPage(this)); + + setButton(QWizard::BackButton, new KPushButton(KStandardGuiItem::back(KStandardGuiItem::UseRTL))); + setButton(QWizard::NextButton, new KPushButton(KStandardGuiItem::forward(KStandardGuiItem::UseRTL))); + setButton(QWizard::FinishButton, new KPushButton(KStandardGuiItem::apply())); + setButton(QWizard::CancelButton, new KPushButton(KStandardGuiItem::cancel())); + + //We do not want "Forward" as text + setButtonText(QWizard::NextButton, i18nc("Action to go to the next page on the wizard", "Next")); + setButtonText(QWizard::FinishButton, i18nc("Action to finish the wizard", "Finish")); + //First show, then do the rest + show(); + + if(!QDBusConnection::systemBus().registerObject("/wizardAgent", qApp)) { + qDebug() << "The dbus object can't be registered"; + } + + m_agent = new WizardAgent(qApp); +} + +void BlueWizard::done(int result) +{ + kDebug() << "Wizard done: " << result; + + QWizard::done(result); +} + +Device* BlueWizard::device() const +{ + return m_device; +} + +BlueWizard::~BlueWizard() +{ + +} + +void BlueWizard::setDeviceAddress(const QByteArray& address) +{ + kDebug() << "Device Address: " << address; + m_deviceAddress = address; + m_device = Manager::self()->usableAdapter()->deviceForAddress(m_deviceAddress); +} + +QByteArray BlueWizard::deviceAddress() const +{ + return m_deviceAddress; +} + +void BlueWizard::restartWizard() +{ + KProcess proc; + proc.setProgram("bluedevil-wizard"); + proc.startDetached(); + + qApp->quit(); +} + +void BlueWizard::setPin(const QByteArray& pinNum) +{ + kDebug() << "Setting pin: :" << pinNum; + m_pin = pinNum; +} + +void BlueWizard::setPin(const QString& pin) +{ + setPin(pin.toAscii()); +} + +QByteArray BlueWizard::pin() const +{ + return m_pin; +} + +void BlueWizard::setPreselectedUuid(const QByteArray& uuid) +{ + kDebug() << "Preselect UUID: " << uuid; + m_preselectedUuid = uuid; +} + +QByteArray BlueWizard::preselectedUuid() const +{ + return m_preselectedUuid; +} + +void BlueWizard::setPreselectedAddress(const QByteArray& address) +{ + kDebug() << "Preselected Address: " << address; + m_preselectedAddress = address; +} + +QByteArray BlueWizard::preselectedAddress() const +{ + return m_preselectedAddress; +} + + +WizardAgent* BlueWizard::agent() const +{ + return m_agent; +} diff --git a/bluedevil/src/wizard/bluewizard.h b/bluedevil/src/wizard/bluewizard.h new file mode 100644 index 00000000..fcb20152 --- /dev/null +++ b/bluedevil/src/wizard/bluewizard.h @@ -0,0 +1,74 @@ +/* + Copyright (C) 2010 UFO Coders + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#ifndef BLUEWIZARD_H +#define BLUEWIZARD_H + +#include +#include + +#include + +namespace BlueDevil { + class Device; +} + +class WizardAgent; +class BlueWizard : public QWizard +{ +Q_OBJECT + +public: + BlueWizard(const KUrl& url); + virtual ~BlueWizard(); + + QByteArray deviceAddress() const; + void setDeviceAddress(const QByteArray& address); + + BlueDevil::Device *device() const; + + QByteArray pin() const; + void setPin(const QByteArray& pin); + + QByteArray preselectedUuid() const; + void setPreselectedUuid(const QByteArray &uuid); + + QByteArray preselectedAddress() const; + void setPreselectedAddress(const QByteArray &uuid); + + WizardAgent* agent() const; + + enum {Discover, NoPairing, LegacyPairing, LegacyPairingDatabase, KeyboardPairing, SSPPairing, Fail, Connect}; + +public Q_SLOTS: + void restartWizard(); + void setPin(const QString& pin); + virtual void done(int result); + +private: + QByteArray m_deviceAddress; + BlueDevil::Device * m_device; + QByteArray m_pin; + QByteArray m_preselectedUuid; + QByteArray m_preselectedAddress; + WizardAgent *m_agent; + + bool m_manualPin; +}; + +#endif // BLUEWIZARD_H diff --git a/bluedevil/src/wizard/main.cpp b/bluedevil/src/wizard/main.cpp new file mode 100644 index 00000000..c7ea86f0 --- /dev/null +++ b/bluedevil/src/wizard/main.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2010-2013 Alejandro Fiestas Olivares * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "bluewizard.h" +#include "version.h" + +#include +#include +#include + +int main(int argc, char *argv[]) +{ + KAboutData aboutData("bluedevilwizard", "bluedevil", ki18n("Bluetooth Wizard"), bluedevil_version, ki18n("Bluetooth Wizard"), + KAboutData::License_GPL, ki18n("(c) 2010, UFO Coders")); + + aboutData.addAuthor(ki18n("Alejandro Fiestas Olivares"), ki18n("Maintainer"), "afiestas@kde.org", + "http://www.afiestas.org/"); + aboutData.setProgramIconName("preferences-system-bluetooth"); + + KCmdLineArgs::init(argc, argv, &aboutData); + KCmdLineOptions options; + options.add("+[URL]", ki18n("Device to pair with")); + KCmdLineArgs::addCmdLineOptions( options ); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + KUrl url; + if (args->count()) { + url = args->arg(0); + } + + KApplication app; + app.setQuitOnLastWindowClosed(false); + new BlueWizard(url); + + return app.exec(); +} diff --git a/bluedevil/src/wizard/pages/discover.ui b/bluedevil/src/wizard/pages/discover.ui new file mode 100644 index 00000000..c43f080e --- /dev/null +++ b/bluedevil/src/wizard/pages/discover.ui @@ -0,0 +1,136 @@ + + + Discover + + + + 0 + 0 + 571 + 444 + + + + + + + + + + 48 + 48 + + + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 4 + 20 + + + + + + + + Scanning... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Manual PIN: + + + manualPin + + + + + + + + + + false + + + + + + + false + + + + 100 + 0 + + + + + 50 + 40 + + + + 999999999; + + + false + + + 0000 + + + + + + + + + + + + diff --git a/bluedevil/src/wizard/pages/discoverpage.cpp b/bluedevil/src/wizard/pages/discoverpage.cpp new file mode 100644 index 00000000..3c0f48de --- /dev/null +++ b/bluedevil/src/wizard/pages/discoverpage.cpp @@ -0,0 +1,241 @@ +/* + Copyright (C) 2010 Alex Fiestas + Copyright (C) 2010 UFO Coders + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#include "discoverpage.h" +#include "ui_discover.h" +#include "../bluewizard.h" +#include "../wizardagent.h" + +#include +#include +#include +#include + +#include +#include + +#include + +using namespace BlueDevil; + +DiscoverPage::DiscoverPage(BlueWizard* parent): QWizardPage(parent), m_wizard(parent) +{ + setTitle(i18n("Select a device")); + setupUi(this); + + KPixmapSequenceOverlayPainter *workingPainter = new KPixmapSequenceOverlayPainter(this); + workingPainter->setWidget(working); + workingPainter->start(); + + connect(deviceList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, + SLOT(itemSelected(QListWidgetItem*))); +} + +DiscoverPage::~DiscoverPage() +{ +} + +void DiscoverPage::initializePage() +{ + kDebug() << "Initialize Page"; + + QList list; + list << QWizard::Stretch; + list << QWizard::NextButton; + list << QWizard::CancelButton; + m_wizard->setButtonLayout(list); + + connect(Manager::self()->usableAdapter(), SIGNAL(unpairedDeviceFound(Device*)), this, + SLOT(deviceFound(Device*))); + connect(manualPin, SIGNAL(toggled(bool)), pinText, SLOT(setEnabled(bool))); + connect(manualPin, SIGNAL(toggled(bool)), this, SIGNAL(completeChanged())); + connect(pinText, SIGNAL(textChanged(QString)), m_wizard, SLOT(setPin(QString))); + connect(pinText, SIGNAL(textChanged(QString)), this, SIGNAL(completeChanged())); + + QMetaObject::invokeMethod(this, "startScan", Qt::QueuedConnection); +} + +bool DiscoverPage::isComplete() const +{ + if (m_wizard->deviceAddress().isEmpty()) { + return false; + } + if (manualPin->isChecked() && pinText->text().isEmpty()) { + return false; + } + return true; +} + +void DiscoverPage::startScan() +{ + deviceList->clear(); + stopScan(); + + if (Manager::self()->usableAdapter()) { + Manager::self()->usableAdapter()->startDiscovery(); + QList devices = Manager::self()->usableAdapter()->devices(); + Q_FOREACH(Device *device, devices) { + deviceFound(device); + } + } +} + +void DiscoverPage::stopScan() +{ + if (Manager::self()->usableAdapter()) { + Manager::self()->usableAdapter()->stopDiscovery(); + } +} + +void DiscoverPage::deviceFound(Device* device) +{ + QString address = device->address(); + QString name = device->name(); + QString icon = device->icon(); + QString alias = device->alias(); + quint32 dClass = device->deviceClass(); + + bool origName = false; + if (!name.isEmpty()) { + origName = true; + } + + if (!alias.isEmpty() && alias != name && !name.isEmpty()) { + name = QString("%1 (%2)").arg(alias).arg(name); + } + + if (name.isEmpty()) { + name = address; + } + + if (icon.isEmpty()) { + icon.append("preferences-system-bluetooth"); + } + + if (m_itemRelation.contains(address)) { + m_itemRelation[address]->setText(name); + m_itemRelation[address]->setIcon(KIcon(icon)); + m_itemRelation[address]->setData(Qt::UserRole+1, origName); + + //If the device was selected but it didn't had a name, select it again + if (deviceList->currentItem() == m_itemRelation[address]) { + itemSelected(m_itemRelation[address]); + } + return; + } + + connect(device, SIGNAL(propertyChanged(QString,QVariant)), SLOT(devicePropertyChanged())); + + QListWidgetItem *item = new QListWidgetItem(KIcon(icon), name, deviceList); + + item->setData(Qt::UserRole, address); + item->setData(Qt::UserRole+1, origName); + + m_itemRelation.insert(address, item); + + if (!deviceList->currentItem() && BlueDevil::classToType(dClass) == BLUETOOTH_TYPE_MOUSE) { + deviceList->setCurrentItem(m_itemRelation[address]); + } + + //If the device has been preselected via arguments, select it + if (m_wizard->preselectedAddress() == address.toLower()) { + deviceList->setCurrentItem(m_itemRelation[address]); + } +} + +void DiscoverPage::itemSelected(QListWidgetItem* item) +{ + bool origName = item->data(Qt::UserRole+1).toBool(); + if (origName) { + QString address = item->data(Qt::UserRole).toString(); + m_wizard->setDeviceAddress(address.toAscii()); + } else { + m_wizard->setDeviceAddress(QByteArray()); + } + emit completeChanged(); +} + +void DiscoverPage::devicePropertyChanged() +{ + Device *device = qobject_cast(sender()); + if (device) { + deviceFound(device); + } +} + +int DiscoverPage::nextId() const +{ + kDebug(); + if (!isComplete()) { + return BlueWizard::Discover; + } + + if (!m_wizard) { + return BlueWizard::Discover; + } + + if (m_wizard->deviceAddress().isEmpty()) { + return BlueWizard::Discover; + } + + kDebug() << "Stopping scanning"; + + Manager::self()->usableAdapter()->stopDiscovery(); + Device *device = m_wizard->device(); + if (device->isPaired()) { + kDebug() << "Device is paired, jumping"; + return BlueWizard::Connect; + } + + QString pin; + if (manualPin->isChecked()) { + pin = m_wizard->pin(); + m_wizard->agent()->setPin(pin); + } else { + pin = m_wizard->agent()->getPin(device); + } + + kDebug() << "Class: " << classToType(device->deviceClass()); + kDebug() << "Legacy: " << device->hasLegacyPairing(); + kDebug() << "From DB: " << m_wizard->agent()->isFromDatabase(); + kDebug() << "PIN: " << m_wizard->agent()->pin(); + + //If keyboard no matter what, we go to the keyboard page. + if (classToType(device->deviceClass()) == BLUETOOTH_TYPE_KEYBOARD) { + kDebug() << "Keyboard Pairing"; + return BlueWizard::KeyboardPairing; + } + + //If pin == NULL means that not pairing is required + if (!device->hasLegacyPairing() && !m_wizard->agent()->isFromDatabase()) { + kDebug() << "Secure Pairing"; + return BlueWizard::SSPPairing; + } + + if (pin == "NULL") { + kDebug() << "NO Pairing"; + return BlueWizard::NoPairing; + } + + if (m_wizard->agent()->isFromDatabase()) { + return BlueWizard::LegacyPairingDatabase; + } else { + return BlueWizard::LegacyPairing; + } +} diff --git a/bluedevil/src/wizard/pages/discoverpage.h b/bluedevil/src/wizard/pages/discoverpage.h new file mode 100644 index 00000000..98f5352a --- /dev/null +++ b/bluedevil/src/wizard/pages/discoverpage.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2010 UFO Coders + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#ifndef DISCOVERPAGE_H +#define DISCOVERPAGE_H + +#include "ui_discover.h" +#include + +class BlueWizard; + +namespace BlueDevil { + class Device; +} +using namespace BlueDevil; + +class DiscoverPage : public QWizardPage +, public Ui::Discover +{ +Q_OBJECT + +public: + DiscoverPage(BlueWizard* parent = 0); + virtual ~DiscoverPage(); + + virtual void initializePage(); + virtual bool isComplete() const; + virtual int nextId() const; +private Q_SLOTS: + void startScan(); + void deviceFound(Device * device); + void itemSelected(QListWidgetItem* item); + void devicePropertyChanged(); +private: + void stopScan(); + +private: + QMap m_itemRelation; + Device *m_selectedDevice; + BlueWizard *m_wizard; +}; + +#endif // DISCOVERPAGE_H diff --git a/bluedevil/src/wizard/pages/fail.cpp b/bluedevil/src/wizard/pages/fail.cpp new file mode 100644 index 00000000..01251184 --- /dev/null +++ b/bluedevil/src/wizard/pages/fail.cpp @@ -0,0 +1,66 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#include "fail.h" +#include "bluewizard.h" + +#include +#include +#include + +#include +#include + +using namespace BlueDevil; + +FailPage::FailPage(BlueWizard* parent) : QWizardPage(parent) +, m_wizard(parent) +{ + setupUi(this); +} + +void FailPage::initializePage() +{ + kDebug(); + KPushButton *reset = new KPushButton(KStandardGuiItem::reset()); + reset->setText(i18nc("Button offered when the wizard fail. This button will restart the wizard","Restart the wizard")); + connect(reset, SIGNAL(clicked(bool)), m_wizard, SLOT(restartWizard())); + + m_wizard->setButton(QWizard::CustomButton3, reset); + m_wizard->setButtonText(QWizard::CancelButton, i18nc("Button that closes the wizard","Close")); + + QList list; + list << QWizard::Stretch; + list << QWizard::CustomButton3; + list << QWizard::CancelButton; + + m_wizard->setButtonLayout(list); + + QString deviceName = m_wizard->device()->name(); + if (deviceName.isEmpty()) { + failLbl->setText(i18nc("This string is shown when the wizard fail","The setup of the device has failed")); + } else { + failLbl->setText(failLbl->text().arg(deviceName)); + } + +} diff --git a/bluedevil/src/wizard/pages/fail.h b/bluedevil/src/wizard/pages/fail.h new file mode 100644 index 00000000..1964788a --- /dev/null +++ b/bluedevil/src/wizard/pages/fail.h @@ -0,0 +1,54 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#ifndef FAIL_H +#define FAIL_H + +#include "ui_fail.h" +#include + +class BlueWizard; +class KPixmapSequenceOverlayPainter; + +namespace BlueDevil { + class Device; + class Adapter; +} + +using namespace BlueDevil; + +class FailPage : public QWizardPage +, Ui::Fail +{ +Q_OBJECT + +public: + FailPage(BlueWizard* parent = 0); + + virtual void initializePage(); + +private: + BlueWizard *m_wizard; +}; + +#endif // FAIL_H \ No newline at end of file diff --git a/bluedevil/src/wizard/pages/fail.ui b/bluedevil/src/wizard/pages/fail.ui new file mode 100644 index 00000000..92d3289d --- /dev/null +++ b/bluedevil/src/wizard/pages/fail.ui @@ -0,0 +1,58 @@ + + + Fail + + + + 0 + 0 + 400 + 300 + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + The setup of %1 has failed + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/bluedevil/src/wizard/pages/keyboardpairing.cpp b/bluedevil/src/wizard/pages/keyboardpairing.cpp new file mode 100644 index 00000000..5ad02f94 --- /dev/null +++ b/bluedevil/src/wizard/pages/keyboardpairing.cpp @@ -0,0 +1,91 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#include "keyboardpairing.h" +#include "bluewizard.h" + +#include +#include + +#include +#include + +using namespace BlueDevil; + +KeyboardPairingPage::KeyboardPairingPage(BlueWizard* parent) : QWizardPage(parent) +, m_wizard(parent) +{ + setupUi(this); + m_working = new KPixmapSequenceOverlayPainter(this); + m_working->setWidget(pinNumber); + m_working->start(); + + QFont font(pinNumber->font()); + font.setPointSize(42); + font.setBold(true); + pinNumber->setFont(font); +} + +void KeyboardPairingPage::initializePage() +{ + kDebug(); + m_wizard->setButtonLayout(wizardButtonsLayout()); + + connect(m_wizard->agent(), SIGNAL(pinRequested(QString)), this, SLOT(pinRequested(QString))); + + Device *device = m_wizard->device(); + connect(device, SIGNAL(pairedChanged(bool)), this, SLOT(pairedChanged(bool))); + device->pair(); +} + +void KeyboardPairingPage::pinRequested(const QString& pin) +{ + m_working->stop(); + pinNumber->setText(pin); +} + +void KeyboardPairingPage::pairedChanged(bool paired) +{ + kDebug() << paired; + m_wizard->next(); +} + +bool KeyboardPairingPage::validatePage() +{ + return m_wizard->device()->isPaired(); +} + + +int KeyboardPairingPage::nextId() const +{ + return BlueWizard::Connect; +} + +QList KeyboardPairingPage::wizardButtonsLayout() const +{ + QList list; + list << QWizard::Stretch; + list << QWizard::CancelButton; + + return list; +} \ No newline at end of file diff --git a/bluedevil/src/wizard/pages/keyboardpairing.h b/bluedevil/src/wizard/pages/keyboardpairing.h new file mode 100644 index 00000000..9b06a9a5 --- /dev/null +++ b/bluedevil/src/wizard/pages/keyboardpairing.h @@ -0,0 +1,64 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#ifndef KEYBOARDPAIRING_H +#define KEYBOARDPAIRING_H + +#include "ui_keyboardpairing.h" +#include + +class BlueWizard; +class KPixmapSequenceOverlayPainter; + +namespace BlueDevil { + class Device; + class Adapter; +} + +using namespace BlueDevil; + +class KeyboardPairingPage : public QWizardPage +, Ui::KeyboardPairing +{ +Q_OBJECT + +public: + KeyboardPairingPage(BlueWizard* parent = 0); + + virtual void initializePage(); + virtual bool validatePage(); + virtual int nextId() const; + +public Q_SLOTS: + void pinRequested(const QString &pin); + void pairedChanged(bool paired); + +protected: + QList wizardButtonsLayout() const; + +private: + BlueWizard *m_wizard; + KPixmapSequenceOverlayPainter *m_working; +}; + +#endif // KEYBOARDPAIRING_H \ No newline at end of file diff --git a/bluedevil/src/wizard/pages/keyboardpairing.ui b/bluedevil/src/wizard/pages/keyboardpairing.ui new file mode 100644 index 00000000..1c8ad92b --- /dev/null +++ b/bluedevil/src/wizard/pages/keyboardpairing.ui @@ -0,0 +1,147 @@ + + + KeyboardPairing + + + + 0 + 0 + 400 + 300 + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 22 + 22 + + + + QFrame::NoFrame + + + QFrame::Raised + + + 0 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Please introduce the PIN in your keyboard when it appears and press Enter + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/bluedevil/src/wizard/pages/legacypairing.cpp b/bluedevil/src/wizard/pages/legacypairing.cpp new file mode 100644 index 00000000..870718b6 --- /dev/null +++ b/bluedevil/src/wizard/pages/legacypairing.cpp @@ -0,0 +1,91 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#include "legacypairing.h" +#include "bluewizard.h" + +#include +#include + +#include +#include +#include + +using namespace BlueDevil; + +LegacyPairingPage::LegacyPairingPage(BlueWizard* parent) : QWizardPage(parent) +, m_wizard(parent) +{ + setupUi(this); + m_working = new KPixmapSequenceOverlayPainter(this); + m_working->setWidget(pinNumber); + m_working->start(); + + QFont font(pinNumber->font()); + font.setPointSize(42); + font.setBold(true); + pinNumber->setFont(font); +} + +void LegacyPairingPage::initializePage() +{ + kDebug(); + m_wizard->setButtonLayout(wizardButtonsLayout()); + + Device *device = m_wizard->device(); + connect(m_wizard->agent(), SIGNAL(pinRequested(QString)), this, SLOT(pinRequested(QString))); + connect(device, SIGNAL(pairedChanged(bool)), this, SLOT(pairedChanged(bool))); + + device->pair(); +} + +void LegacyPairingPage::pinRequested(const QString& pin) +{ + m_working->stop(); + pinNumber->setText(pin); +} + +void LegacyPairingPage::pairedChanged(bool paired) +{ + kDebug() << paired; + m_wizard->next(); +} + +bool LegacyPairingPage::validatePage() +{ + return m_wizard->device()->isPaired(); +} + +int LegacyPairingPage::nextId() const +{ + return BlueWizard::Connect; +} + +QList LegacyPairingPage::wizardButtonsLayout() const +{ + QList list; + list << QWizard::Stretch; + list << QWizard::CancelButton; + + return list; +} \ No newline at end of file diff --git a/bluedevil/src/wizard/pages/legacypairing.h b/bluedevil/src/wizard/pages/legacypairing.h new file mode 100644 index 00000000..c822bd5c --- /dev/null +++ b/bluedevil/src/wizard/pages/legacypairing.h @@ -0,0 +1,64 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#ifndef LEGACYPAIRING_H +#define LEGACYPAIRING_H + +#include "ui_legacypairing.h" +#include + +class BlueWizard; +class KPixmapSequenceOverlayPainter; + +namespace BlueDevil { + class Device; + class Adapter; +} + +using namespace BlueDevil; + +class LegacyPairingPage : public QWizardPage +, Ui::LegacyPairing +{ +Q_OBJECT + +public: + LegacyPairingPage(BlueWizard* parent = 0); + + virtual void initializePage(); + virtual bool validatePage(); + virtual int nextId() const; + +public Q_SLOTS: + void pinRequested(const QString &pin); + void pairedChanged(bool paired); + +protected: + QList wizardButtonsLayout() const; + +private: + BlueWizard *m_wizard; + KPixmapSequenceOverlayPainter *m_working; +}; + +#endif // LEGACYPAIRING_H \ No newline at end of file diff --git a/bluedevil/src/wizard/pages/legacypairing.ui b/bluedevil/src/wizard/pages/legacypairing.ui new file mode 100644 index 00000000..4d909e54 --- /dev/null +++ b/bluedevil/src/wizard/pages/legacypairing.ui @@ -0,0 +1,147 @@ + + + LegacyPairing + + + + 0 + 0 + 400 + 300 + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 22 + 22 + + + + QFrame::NoFrame + + + QFrame::Raised + + + 0 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Please introduce the PIN in your device when it appears + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/bluedevil/src/wizard/pages/legacypairingdatabase.cpp b/bluedevil/src/wizard/pages/legacypairingdatabase.cpp new file mode 100644 index 00000000..5d4872f2 --- /dev/null +++ b/bluedevil/src/wizard/pages/legacypairingdatabase.cpp @@ -0,0 +1,77 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "legacypairingdatabase.h" +#include "bluewizard.h" +#include "../wizardagent.h" + +#include +#include + +#include + +using namespace BlueDevil; + +LegacyPairingPageDatabase::LegacyPairingPageDatabase(BlueWizard* parent) : QWizardPage(parent) +, m_wizard(parent) +{ + setupUi(this); + m_working = new KPixmapSequenceOverlayPainter(this); + m_working->setWidget(working); + m_working->start(); +} + +void LegacyPairingPageDatabase::initializePage() +{ + m_wizard->setButtonLayout(wizardButtonsLayout()); + + Device *device = m_wizard->device(); + connecting->setText(i18n("Connecting to %1...", device->name())); + + connect(device, SIGNAL(pairedChanged(bool)), this, SLOT(pairedChanged(bool))); + device->pair(); +} + +void LegacyPairingPageDatabase::pairedChanged(bool paired) +{ + kDebug() << paired; + m_wizard->next(); +} + +bool LegacyPairingPageDatabase::validatePage() +{ + return m_wizard->device()->isPaired(); +} + +int LegacyPairingPageDatabase::nextId() const +{ + return BlueWizard::Connect; +} + +QList< QWizard::WizardButton > LegacyPairingPageDatabase::wizardButtonsLayout() const +{ + QList list; + list << QWizard::Stretch; + list << QWizard::CancelButton; + + return list; +} \ No newline at end of file diff --git a/bluedevil/src/wizard/pages/legacypairingdatabase.h b/bluedevil/src/wizard/pages/legacypairingdatabase.h new file mode 100644 index 00000000..e9e13aaa --- /dev/null +++ b/bluedevil/src/wizard/pages/legacypairingdatabase.h @@ -0,0 +1,63 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#ifndef LEGACYPAIRINGDATABASE_H +#define LEGACYPAIRINGDATABASE_H + +#include "ui_nopairing.h" +#include + +class BlueWizard; +class KPixmapSequenceOverlayPainter; + +namespace BlueDevil { + class Device; + class Adapter; +} + +using namespace BlueDevil; + +class LegacyPairingPageDatabase : public QWizardPage +, Ui::NoPairing +{ +Q_OBJECT + +public: + LegacyPairingPageDatabase(BlueWizard* parent = 0); + + virtual void initializePage(); + virtual bool validatePage(); + virtual int nextId() const; + +public Q_SLOTS: + void pairedChanged(bool paired); + +protected: + QList wizardButtonsLayout() const; + +private: + BlueWizard *m_wizard; + KPixmapSequenceOverlayPainter *m_working; +}; + +#endif // LEGACYPAIRINGDATABASE_H \ No newline at end of file diff --git a/bluedevil/src/wizard/pages/nopairing.cpp b/bluedevil/src/wizard/pages/nopairing.cpp new file mode 100644 index 00000000..65be6535 --- /dev/null +++ b/bluedevil/src/wizard/pages/nopairing.cpp @@ -0,0 +1,96 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#include "nopairing.h" +#include "bluewizard.h" + +#include +#include + +#include +#include + +using namespace BlueDevil; + +NoPairingPage::NoPairingPage(BlueWizard* parent) : QWizardPage(parent) +, m_validPage(false) +, m_wizard(parent) +{ + setupUi(this); + m_working = new KPixmapSequenceOverlayPainter(this); + m_working->setWidget(working); + m_working->start(); +} + +void NoPairingPage::initializePage() +{ + kDebug(); + m_wizard->setButtonLayout(wizardButtonsLayout()); + + connecting->setText(connecting->text().append(m_wizard->device()->name())); + + //It can happen that the device is technically connected and trusted but we are not connected + //to the profile. We have no way to know if the profile was activated or not so we have to relay + //on a timeout (10s) + QTimer::singleShot(10000, this, SLOT(timeout())); + connect(m_wizard->device(), SIGNAL(connectedChanged(bool)), SLOT(connectedChanged(bool))); + connect(m_wizard->device(), SIGNAL(trustedChanged(bool)), SLOT(connectedChanged(bool))); + + m_wizard->device()->connectDevice(); + m_wizard->device()->setTrusted(true); +} + +void NoPairingPage::timeout() +{ + connectedChanged(true); +} + +void NoPairingPage::connectedChanged(bool connected) +{ + kDebug(); + + m_validPage = connected; + if (m_validPage) { + kDebug() << "Done"; + m_wizard->done(0); + } +} + +bool NoPairingPage::validatePage() +{ + return m_validPage; +} + +int NoPairingPage::nextId() const +{ + return -1; +} + +QList NoPairingPage::wizardButtonsLayout() const +{ + QList list; + list << QWizard::Stretch; + list << QWizard::CancelButton; + + return list; +} diff --git a/bluedevil/src/wizard/pages/nopairing.h b/bluedevil/src/wizard/pages/nopairing.h new file mode 100644 index 00000000..9b9f4bf4 --- /dev/null +++ b/bluedevil/src/wizard/pages/nopairing.h @@ -0,0 +1,66 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#ifndef NOPAIRING_H +#define NOPAIRING_H + +#include "ui_nopairing.h" +#include +#include + +class BlueWizard; +class KPixmapSequenceOverlayPainter; + +namespace BlueDevil { + class Device; + class Adapter; +} + +using namespace BlueDevil; + +class NoPairingPage : public QWizardPage +, Ui::NoPairing +{ +Q_OBJECT + +public: + NoPairingPage(BlueWizard* parent = 0); + + virtual void initializePage(); + virtual bool validatePage(); + virtual int nextId() const; + +protected: + QList wizardButtonsLayout() const; + +private Q_SLOTS: + void timeout(); + void connectedChanged(bool connected); + +private: + bool m_validPage; + BlueWizard *m_wizard; + KPixmapSequenceOverlayPainter *m_working; +}; + +#endif // NOPAIRING_H \ No newline at end of file diff --git a/bluedevil/src/wizard/pages/nopairing.ui b/bluedevil/src/wizard/pages/nopairing.ui new file mode 100644 index 00000000..e8d2855c --- /dev/null +++ b/bluedevil/src/wizard/pages/nopairing.ui @@ -0,0 +1,74 @@ + + + NoPairing + + + + 0 + 0 + 400 + 300 + + + + + + + + + + 0 + 0 + + + + + 22 + 22 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + Connecting to: + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/bluedevil/src/wizard/pages/ssppairing.cpp b/bluedevil/src/wizard/pages/ssppairing.cpp new file mode 100644 index 00000000..3962e147 --- /dev/null +++ b/bluedevil/src/wizard/pages/ssppairing.cpp @@ -0,0 +1,158 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#include "ssppairing.h" +#include "bluewizard.h" + +#include +#include +#include + +#include +#include + +using namespace BlueDevil; + +SSPPairingPage::SSPPairingPage(BlueWizard* parent) : QWizardPage(parent) +, m_buttonClicked(QWizard::NoButton) +, m_wizard(parent) +{ + setupUi(this); + m_working = new KPixmapSequenceOverlayPainter(this); + m_working->setWidget(pinNumber); + m_working->start(); + + QFont font(pinNumber->font()); + font.setPointSize(42); + font.setBold(true); + pinNumber->setFont(font); +} + +void SSPPairingPage::initializePage() +{ + kDebug(); + QList list; + list << QWizard::Stretch; + list << QWizard::CancelButton; + m_wizard->setButtonLayout(list); + + Device *device = m_wizard->device(); + confirmLbl->setText(i18n("Connecting to %1...", device->name())); + + connect(device, SIGNAL(pairedChanged(bool)), this, SLOT(pairedChanged(bool))); + connect(m_wizard->agent(), SIGNAL(confirmationRequested(quint32,QDBusMessage)), + this, SLOT(confirmationRequested(quint32,QDBusMessage))); + connect(m_wizard->agent(), SIGNAL(pinRequested(QString)), SLOT(pinRequested(QString))); + + device->pair(); +} + +void SSPPairingPage::confirmationRequested(quint32 passkey, const QDBusMessage& msg) +{ + m_msg = msg; + + KPushButton *matches = new KPushButton(KStandardGuiItem::apply()); + matches->setText(i18n("Matches")); + KPushButton *notMatch = new KPushButton(KStandardGuiItem::cancel()); + notMatch->setText(i18n("Does not match")); + + connect(matches, SIGNAL(clicked(bool)), this, SLOT(matchesClicked())); + connect(notMatch, SIGNAL(clicked(bool)), this, SLOT(notMatchClicked())); + + wizard()->setButton(QWizard::CustomButton1, matches); + wizard()->setButton(QWizard::CustomButton2, notMatch); + + wizard()->setButtonLayout(wizardButtonsLayout()); + + m_working->stop(); + pinNumber->setText(QString("%1").arg(passkey, 6, 10, QLatin1Char('0'))); + + confirmLbl->setText(i18n("Please, confirm that the PIN displayed on \"%1\" matches the wizard one.", m_wizard->device()->name())); + +} + +void SSPPairingPage::pinRequested(const QString& pin) +{ + m_working->stop(); + pinNumber->setText(pin); + confirmLbl->setText(i18n("Please introduce the PIN in your device when it appears")); +} + +void SSPPairingPage::pairedChanged(bool paired) +{ + kDebug() << paired; + + wizard()->next(); +} + +void SSPPairingPage::matchesClicked() +{ + wizard()->button(QWizard::CustomButton1)->setEnabled(false); + wizard()->button(QWizard::CustomButton2)->setEnabled(false); + + m_buttonClicked = QWizard::CustomButton1; + QDBusConnection::systemBus().send(m_msg.createReply()); + + wizard()->next(); +} + +void SSPPairingPage::notMatchClicked() +{ + m_buttonClicked = QWizard::CustomButton2; + + wizard()->next(); +} + +bool SSPPairingPage::validatePage() +{ + if (m_buttonClicked == QWizard::CustomButton2){ + return true; + } + if (m_wizard->device()->isPaired() && m_buttonClicked == QWizard::NoButton) { + return true; + } + if (m_wizard->device()->isPaired() && m_buttonClicked == QWizard::CustomButton1) { + return true; + } + + return false; +} + +int SSPPairingPage::nextId() const +{ + if (m_buttonClicked == QWizard::CustomButton2) { + return BlueWizard::Fail; + } + + return BlueWizard::Connect; +} + +QList SSPPairingPage::wizardButtonsLayout() const +{ + QList list; + list << QWizard::Stretch; + list << QWizard::CustomButton2; + list << QWizard::CustomButton1; + + return list; +} diff --git a/bluedevil/src/wizard/pages/ssppairing.h b/bluedevil/src/wizard/pages/ssppairing.h new file mode 100644 index 00000000..d8d0a79f --- /dev/null +++ b/bluedevil/src/wizard/pages/ssppairing.h @@ -0,0 +1,70 @@ +/***************************************************************************** + * This file is part of the KDE project * + * * + * Copyright (C) 2010 Alejandro Fiestas Olivares * + * Copyright (C) 2010-2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + + +#ifndef SSPPAIRINGPAGE_H +#define SSPPAIRINGPAGE_H + +#include "ui_ssppairing.h" +#include +#include + +class BlueWizard; +class KPixmapSequenceOverlayPainter; + +namespace BlueDevil { + class Device; + class Adapter; +} + +using namespace BlueDevil; + +class SSPPairingPage : public QWizardPage +, Ui::SSPPairing +{ +Q_OBJECT + +public: + SSPPairingPage(BlueWizard* parent = 0); + + virtual void initializePage(); + virtual int nextId() const; + virtual bool validatePage(); + +public Q_SLOTS: + void confirmationRequested(quint32 passkey, const QDBusMessage &msg); + void pairedChanged(bool paired); + void matchesClicked(); + void notMatchClicked(); + void pinRequested(const QString &pin); + +protected: + QList wizardButtonsLayout() const; + +private: + QDBusMessage m_msg; + QWizard::WizardButton m_buttonClicked; + BlueWizard *m_wizard; + KPixmapSequenceOverlayPainter *m_working; +}; + +#endif // SSPPAIRINGPAGE_H \ No newline at end of file diff --git a/bluedevil/src/wizard/pages/ssppairing.ui b/bluedevil/src/wizard/pages/ssppairing.ui new file mode 100644 index 00000000..673df77c --- /dev/null +++ b/bluedevil/src/wizard/pages/ssppairing.ui @@ -0,0 +1,156 @@ + + + SSPPairing + + + + 0 + 0 + 400 + 300 + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 22 + 22 + + + + QFrame::NoFrame + + + QFrame::Raised + + + 0 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + + 0 + 0 + + + + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/bluedevil/src/wizard/pin-code-database.xml b/bluedevil/src/wizard/pin-code-database.xml new file mode 100644 index 00000000..8239aa98 --- /dev/null +++ b/bluedevil/src/wizard/pin-code-database.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bluedevil/src/wizard/wizardagent.cpp b/bluedevil/src/wizard/wizardagent.cpp new file mode 100644 index 00000000..4194dfc2 --- /dev/null +++ b/bluedevil/src/wizard/wizardagent.cpp @@ -0,0 +1,194 @@ +/*************************************************************************** + * Copyright (C) 2010 Alex Fiestas * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "wizardagent.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace BlueDevil; + +WizardAgent::WizardAgent(QApplication* application) : QDBusAbstractAdaptor(application), m_fromDatabase(false) +{ + kDebug() << "AGENT registered !"; + BlueDevil::Manager::self()->registerAgent("/wizardAgent",BlueDevil::Manager::DisplayYesNo); +} + +WizardAgent::~WizardAgent() +{ + kDebug() << "Agent deleted"; + BlueDevil::Manager::self()->unregisterAgent("/wizardAgent"); +} + +void WizardAgent::Release() +{ + kDebug() << "Agent Release"; + emit agentReleased(); +} + +void WizardAgent::AuthorizeService(const QDBusObjectPath& device, const QString& uuid, const QDBusMessage& msg) +{ + Q_UNUSED(device); + Q_UNUSED(uuid); + Q_UNUSED(msg); + kDebug() << "AGENT-Authorize " << device.path() << " Service: " << uuid; +} + +quint32 WizardAgent::RequestPasskey(const QDBusObjectPath& device, const QDBusMessage& msg) +{ + Q_UNUSED(device); + Q_UNUSED(msg); + kDebug() << "AGENT-RequestPasskey " << device.path(); + return 0; +} + +void WizardAgent::DisplayPasskey(const QDBusObjectPath& device, quint32 passkey) +{ + Q_UNUSED(device); + Q_UNUSED(passkey); + kDebug() << "AGENT-DisplayPasskey " << device.path() << ", " << QString::number(passkey); +} + +void WizardAgent::DisplayPinCode(const QDBusObjectPath& device, const QString& pincode) +{ + Q_UNUSED(device); + Q_UNUSED(pincode); + kDebug() << "AGENT-DisplayPasskey " << device.path() << ", " << pincode; + emit pinRequested(pincode); +} + +void WizardAgent::RequestConfirmation(const QDBusObjectPath& device, quint32 passkey, const QDBusMessage& msg) +{ + Q_UNUSED(device); + Q_UNUSED(passkey); + Q_UNUSED(msg); + kDebug() << "AGENT-RequestConfirmation " << device.path() << ", " << QString::number(passkey); + emit confirmationRequested(passkey, msg); +} + +void WizardAgent::Cancel() +{ + kDebug() << "AGENT-Cancel"; +} + +QString WizardAgent::RequestPinCode(const QDBusObjectPath& device, const QDBusMessage& msg) +{ + Q_UNUSED(device); + Q_UNUSED(msg); + kDebug() << "AGENT-RequestPinCode " << device.path(); + + emit pinRequested(m_pin); + return m_pin; +} + +QString WizardAgent::getPin(Device *device) +{ + if(!m_pin.isEmpty()) { + return m_pin; + } + + m_pin = QString::number(KRandom::random()); + m_pin = m_pin.left(6); + + KComponentData data("bluedevilwizard"); + QString xmlPath = KStandardDirs::locate("appdata", "pin-code-database.xml", data); + + QFile file(xmlPath); + if(!file.open(QIODevice::ReadOnly)) { + kDebug() << "Can't open the device"; + return m_pin; + } + + if (!device) { + kDebug() << "could not found the device"; + return m_pin; + } + + m_device = device; + QXmlStreamReader m_xml(&file); + + int deviceType = classToType(device->deviceClass()); + int xmlType = 0; + + while(!m_xml.atEnd()) { + m_xml.readNext(); + if(m_xml.name() != "device") { + continue; + } + QXmlStreamAttributes attr = m_xml.attributes(); + + if(attr.count() == 0) { + continue; + } + + if(attr.hasAttribute("type") && attr.value("type") != "any") { + xmlType = stringToType(attr.value("type").toString()); + if(deviceType != xmlType) { + xmlType = 0; //This is not needed but I like restart the bucle in each interation + continue; + } + } + + if(attr.hasAttribute("oui")) { + if(!device->address().startsWith(attr.value("oui").toString())) { + continue; + } + } + + if(attr.hasAttribute("name")) { + if(device->name() != attr.value("name").toString()) { + continue; + } + } + + m_pin = attr.value("pin").toString(); + m_fromDatabase = true; + if (m_pin.startsWith("max:")) { + m_fromDatabase = false; + int num = m_pin.right(m_pin.length() - 4).toInt(); + m_pin = QString::number(KRandom::random()).left(num); + } + kDebug() << "PIN: " << m_pin; + return m_pin; + } + + return m_pin; +} + +void WizardAgent::setPin(const QString& pin) +{ + m_pin = pin; +} + +QString WizardAgent::pin() +{ + return m_pin; +} + +bool WizardAgent::isFromDatabase() +{ + return m_fromDatabase; +} diff --git a/bluedevil/src/wizard/wizardagent.h b/bluedevil/src/wizard/wizardagent.h new file mode 100644 index 00000000..05d577f2 --- /dev/null +++ b/bluedevil/src/wizard/wizardagent.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * Copyright (C) 2010 Alex Fiestas * + * Copyright (C) 2010 UFO Coders * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + + +#ifndef WIZARDAGENT_H +#define WIZARDAGENT_H + +#include +#include +#include + +namespace BlueDevil { + class Device; +} + +using namespace BlueDevil; +class WizardAgent : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.bluez.Agent1") + +public: + WizardAgent(QApplication* application); + ~WizardAgent(); + + void setPin(const QString& pin); + QString getPin(Device* device); + QString pin(); + bool isFromDatabase(); + +//D-Bus interface implementation +public slots: + void Release(); + void AuthorizeService(const QDBusObjectPath &device, const QString& uuid, const QDBusMessage &msg); + QString RequestPinCode(const QDBusObjectPath &device, const QDBusMessage &msg); + quint32 RequestPasskey(const QDBusObjectPath &device, const QDBusMessage &msg); + void DisplayPinCode(const QDBusObjectPath &device, const QString & pincode); + void DisplayPasskey(const QDBusObjectPath &device, quint32 passkey); + void RequestConfirmation(const QDBusObjectPath &device, quint32 passkey, const QDBusMessage &msg); + void Cancel(); + +private: + bool m_fromDatabase; + QString m_pin; + Device *m_device; + +Q_SIGNALS: + void pinRequested(const QString&); + void confirmationRequested(quint32 passkey, const QDBusMessage &msg); + void agentReleased(); +}; + +#endif diff --git a/bluedevil/version.h.cmake b/bluedevil/version.h.cmake new file mode 100644 index 00000000..67f5096e --- /dev/null +++ b/bluedevil/version.h.cmake @@ -0,0 +1,4 @@ +#ifndef BLUEDEVILVERSION_H +#define BLUEDEVILVERSION_H +static const char bluedevil_version[] = "${CMAKE_BLUEDEVIL_VERSION_STRING}"; +#endif \ No newline at end of file diff --git a/dragon/.gitignore b/dragon/.gitignore new file mode 100644 index 00000000..dee53c53 --- /dev/null +++ b/dragon/.gitignore @@ -0,0 +1,2 @@ +build +*~ diff --git a/dragon/CMakeLists.txt b/dragon/CMakeLists.txt new file mode 100644 index 00000000..5d970217 --- /dev/null +++ b/dragon/CMakeLists.txt @@ -0,0 +1,24 @@ +project(DragonPlayer) + +find_package(KDE4 REQUIRED) +include(KDE4Defaults) +include(MacroLibrary) + +include_directories(${KDE4_INCLUDES}) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR}/src + ${CMAKE_CURRENT_BINARY_DIR}/src/app + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src/app + ${CMAKE_CURRENT_BINARY_DIR} + ) + +include(CheckIncludeFiles) +check_include_files(unistd.h HAVE_UNISTD_H) + +configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +add_subdirectory( src/app ) +add_subdirectory( misc ) +add_subdirectory( doc ) diff --git a/dragon/COPYING b/dragon/COPYING new file mode 100644 index 00000000..5b6e7c66 --- /dev/null +++ b/dragon/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/dragon/COPYING.DOC b/dragon/COPYING.DOC new file mode 100644 index 00000000..4a0fe1c8 --- /dev/null +++ b/dragon/COPYING.DOC @@ -0,0 +1,397 @@ + GNU Free Documentation License + Version 1.2, November 2002 + + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/dragon/ChangeLog b/dragon/ChangeLog new file mode 100644 index 00000000..99287213 --- /dev/null +++ b/dragon/ChangeLog @@ -0,0 +1,99 @@ +2.1 + Added CheckBox for Mute in the right dockbar + Chapter +/- 1 (bugged, I think is a Phonon bug :S ) + Fixed a Mute bug + Adopted the KMix way to manage the volume + V for Volume + In the FullScreen mode, the volume disappear (bug fix) + Whishlist #158737. PageUP bring you 10% forward, PageDOWN 10% backward + Added a ratio to create the video as the window size + +2.0.1 + Removed the locking of the volumeSlider if the mute is active + +2.0.0 + Fixed issue where if the user ESCed the play media dialog it wouldn't reappear. + Pressing 'M' causes mute + Return to the logo when playback is explictly stopped + New Icon + Fixed crash on double click in KPart Bug #157579 + The volumeSlider is locked if the mute is active + +2.0-rc1 + Fix status bar title disappearing on window resize + Can turn off subtitles + Can revert to the 'auto' audio channel + Save subtitle and audio channel selection + New DBus API under /Player and /TrackList. A full implementation of MPRIS. + Fix pausing and then playing causing the window size to revert. + A new smart "Play Disc" button that plays whatever kind of media is inserted and + if more than one disc is inserted it lets you pick which disc to play. + As a result, Video CD's and Audio CD's are now supported. + +2.0-beta1 + Fix hang on close when paused + Restore cursor hiding over the playing video widget + Restore video settings widget (contrast, brightness), as a sidebar instead of a window. + Restore KPart + Save video settings + Audio channel selectable + Volume slider toolbar. Volume saved globally, not per-file. + Imported Amarok's runtime-selectable debug() system + +2.0-alpha1 + New maintainer: Ian Monroe + Renamed to Dragon Player + Ported to Qt4, KDE4 and Phonon. Many feature regressions. None of Codeine's bugs though... a whole new set! + +1.0.1 + Mute button for KPart + Play DVD entry for KDE 3.5 + media:/ when DVD inserted + DVD-Menu-Toggle is no longer a KToggleAction because I can't detect when DVD menus change, but it still acts as a toggle button + Made record work for systems other than mine! (hard-coded path) + Made record shortcut CTRL-R so it doesn't conflict with the DVD-Root-Menu toggle + videoWindow doesn't judder when toolbar appears in fullscreen anymore + dvd-toolbar is gone, instead root menu button appears when dvd is playing + toolbar in fullscreen mode shows on mouse move + toolbar in fullscreen mode respects user-positioning + media kioslave support + double-clicking the video toggles fullscreen + don't show part in K-Menu + a volume toolbar button, - available from the configure-toolbar dialog, it's not very good yet + +1.0-rc2 + Seek fixes + Improved error messages + KPart crash on exit fix + +1.0-beta6 + Frame capture function + Aspect Ratio setting + Snap for videoSettings dialog sliders + Polish to all dialogs, menus + Hide cursor in fullscreen mode bug fixed + Many other fixes and lots of polish + +1.0-beta3 + Made Codeine single-window + Removed Pause KAction, instead toggling play pauses + Made it remember all details about how you like to view videos (eg. + contrast, brightness, size) + Shows toolbar when mouse is a screen-top in fullscreen mode + Bug fixes + +1.0-beta2 + Fixed fullscreen not covering Kicker + Added stop KAction + Added "You must install!" message after make does linking + Set busy cursor during unresponsive init period + Made the GUI detect lack of a ui file and show a useful message + If you quit during playback, the track volume fades out + Various feel/feedback fixes + Recent-files list in initial definately doesn't have duplicates anymore, sort order is also corrected + Routed out some nasty freeze bugs + Automatic frame format change handling + Stream recording + Many little bug-fixes and improvements + +1.0-beta1 + Initial release diff --git a/dragon/HACKING b/dragon/HACKING new file mode 100644 index 00000000..99127215 --- /dev/null +++ b/dragon/HACKING @@ -0,0 +1,302 @@ +Dragon Video Player is a sister project to Amarok, its HACKING guidelines are the +same with minimal exception. + +Hacking on Amarok +----------------- + +Please respect these guidelines when coding for Amarok, thanks! + +* Where this document isn't clear, refer to Amarok code. + + +This C++ FAQ is a life saver in many situations, so you want to keep it handy: + + http://www.parashift.com/c++-faq-lite/ + + +Formatting +---------- +* Spaces, not tabs +* Indentation is 4 spaces +* Lines should be limited to 90 characters +* Spaces between brackets and argument functions +* For pointer and reference variable declarations put a space between the type + and the * or & and no space before the variable name. +* For if, else, while and similar statements put the brackets on the next line, + although brackets are not needed for single statements. +* Function and class definitions have their brackets on separate lines +* A function implementation's return type is on its own line. +* camelCase.{cpp,h} style file names. +* Qt 4 includes a foreach keyword which makes it very easy to iterate over all + elements of a container. + +Example: + + | bool + | MyClass::myMethod( QStringList list, const QString &name ) + | { + | if( list.isEmpty() ) + | return false; + | + | /* + | Define the temporary variable like this to restrict its scope + | when you do not need it outside the loop. Let the compiler + | optimise it. + | */ + | foreach( const QString &string, list ) + | { + | + | debug() << "Current string is " << string << endl; + | } + | } + +Header includes should be listed in the following order: + - Amarok includes + - KDE includes + - Qt includes + +They should also be sorted alphabetically, for ease of locating them. A small comment +if applicable is also helpful. + +Includes in a header file should be kept to the absolute minimum, as to keep compile times +low. This can be achieved by using "forward declarations" instead of includes, like +"class QListView;" Forward declarations work for pointers and const references. + +TIP: +Kate/KDevelop users can sort the headers automatically. Select the lines you want to sort, +then Tools -> Filter Selection Through Command -> "sort". + + +Example: + + | #include "amarok.h" + | #include "debug.h" + | #include "playlist.h" + | + | #include //baseclass + | #include //see function... + | + | #include + | #include + + +Comments +-------- +Comment your code. Don't comment what the code does, comment on the purpose of the code. It's +good for others reading your code, and ultimately it's good for you too. + +Comments are essential when adding a strange hack, like the following example: + + | /** Due to xine-lib, we have to make K3Process close all fds, otherwise we get "device is busy" messages + | * Used by AmarokProcIO and AmarokProcess, exploiting commSetupDoneC(), a virtual method that + | * happens to be called in the forked process + | * See bug #103750 for more information. + | */ + | class AmarokProcIO : public K3ProcIO + | { + | public: + | virtual int commSetupDoneC() { + | const int i = K3ProcIO::commSetupDoneC(); + | Amarok::closeOpenFiles(K3ProcIO::out[0],K3ProcIO::in[0],K3ProcIO::err[0]); + | return i; + | } + | }; + + +For headers, use the Doxygen syntax. See: http://www.stack.nl/~dimitri/doxygen/ + +Example: + + | /** + | * Start playback. + | * @param offset Start playing at @p msec position. + | * @return True for success. + | */ + | virtual bool play( uint offset = 0 ) = 0; + + +Header Formatting +----------------- +General rules apply here. Please keep header function definitions aligned nicely, +if possible. It helps greatly when looking through the code. Sorted methods, +either by name or by their function (ie, group all related methods together) is +great too. + + + | #ifndef AMAROK_QUEUEMANAGER_H + | #define AMAROK_QUEUEMANAGER_H + + | class QueueList : public K3ListView + | { + | Q_OBJECT + | + | public: + | Queuelist( QWidget *parent, const char *name = 0 ); + | ~QueueList() {}; + | + | public slots: + | void moveSelectedUp(); + | void moveSelectedDown(); + | }; + +#endif /* AMAROK_QUEUEMANAGER_H */ + + +0 vs NULL +--------- +The use of 0 to express a null pointer is preferred over the use of NULL. +0 is not a magic value, it's the defined value of the null pointer in C++. +NULL, on the other hand, is a preprocessor directive (#define) and not only is +it more typing than '0' but preprocessor directives are less elegant. + + | SomeClass *instance = 0; + + +Const Correctness +----------------- +Try to keep your code const correct. Declare methods const if they don't mutate the object, +and use const variables. It improves safety, and also makes it easier to understand the code. + +See: http://www.parashift.com/c++-faq-lite/const-correctness.html + + +Example: + + | bool + | MyClass::isValidFile( const QString& path ) const + | { + | const bool valid = QFile::exist( path ); + | + | return valid; + | } + + +Debugging +--------- +debug.h contains some handy functions for our debug console output. +Please use them instead of kDebug(). + +Usage: + + | #include "debug.h" + | + | debug() << "Something is happening" << endl; + | warning() << "Something bad may happen" << endl; + | error() << "Something bad did happen!" << endl; + +Additionally, there are some macros for debugging functions: + +DEBUG_BLOCK +DEBUG_FUNC_INFO +DEBUG_LINE_INFO +DEBUG_INDENT +DEBUG_UNINDENT + +AMAROK_NOTIMPLEMENTED +AMAROK_DEPRECATED + +threadweaver.h has two additional macros: +DEBUG_THREAD_FUNC_INFO outputs the memory address of the current QThread or 'none' + if its the original GUI thread. +SHOULD_BE_GUI outputs a warning message if it occurs in a thread that isn't in + the original "GUI Thread", otherwise it is silent. Useful for documenting + functions and to prevent problems in the future. + + +Usage of Amarok::config() +------------------------- +We provide this method for convenience, but it is important to use it properly. By +inspection, we can see that we may produce very obscure bugs in the wrong case: + + | KConfig + | *config( const QString &group ) + | { + | //Slightly more useful config() that allows setting the group simultaneously + | KGlobal::config()->setGroup( group ); + | return KGlobal::config(); + | } + +Take the following example: + + | void + | f1() + | { + | KConfig *config = Amarok::config( "Group 2" ); + | config->writeEntry( "Group 2 Variable", true ); + | } + | + | void + | doStuff() + | { + | KConfig *config = Amarok::config( "Group 1" ); + | f1(); + | config->writeEntry( "Group 1 Variable", true ); + | } + +We would expect the following results: + + | [Group 1] + | Group 1 Variable = true + | + | [Group 2] + | Group 2 Variable = true + +However because the config group is changed before writing the entry: + | [Group 1] + | + | [Group 2] + | Group 1 Variable = true + | Group 2 Variable = true + +Which is clearly incorrect. And hard to see when your wondering why f1() is not +working. So do not store a value of Amarok::config, make it a habit to just +always call writeEntry or readEntry directly. + +Correct: + | amarok::config( "Group 1" )->writeEntry( "Group 1 Variable", true ); + + +Errors & Asserts +---------------- +*Never use assert() or fatal(). There must be a better option than crashing a user's +application (its not uncommon for end-users to have debugging enabled). + +*KMessageBox is fine to use to prompt the user, but do not use it to display errors +or informational messages. Instead, KDE::StatusBar has a few handy methods. Refer to +amarok/src/statusbar/statusBarBase.h + + +Commenting Out Code +------------------- +Don't keep commented out code. It just causes confusion and makes the source +harder to read. Remember, the last revision before your change is always +availabe in SVN. Hence no need for leaving cruft in the source. + +Wrong: + | myWidget->show(); + | //myWidget->rise(); //what is this good for? + +Correct: + | myWidget->show(); + + +Tips & Tricks +------------- +A useful service is http://lxr.kde.org. Lxr is a cross reference of the entire +KDE SVN repository. You can for instance use it to search for example code +from other applications for a given KDElibs method. + + +Copyright +--------- +To comply with the GPL, add your name, email address & the year to the top of any file +that you edit. If you bring in code or files from elsewhere, make sure its +GPL-compatible and to put the authors name, email & copyright year to the top of +those files. + +Please note that it is not sufficient to write a pointer to the license (like a URL). +The complete license header needs to be written everytime. + + +Thanks, now have fun! + -- the Amarok developers diff --git a/dragon/Messages.sh b/dragon/Messages.sh new file mode 100755 index 00000000..7934b124 --- /dev/null +++ b/dragon/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` >> rc.cpp +$XGETTEXT `find . -name \*.cpp -o -name \*.h` -o $podir/dragonplayer.pot +rm -f rc.cpp diff --git a/dragon/README b/dragon/README new file mode 100644 index 00000000..4ae39cf4 --- /dev/null +++ b/dragon/README @@ -0,0 +1,32 @@ +INTRODUCTION + Dragon Player is a very simple Phonon-based media player. It was originally + developed by Max Howell and called Codeine. I ported it to KDE 4.0 and on + Max's suggestion renamed it to Video Player (probably, I might still + rename it.) + + Similar to Max, I make the following promises: + + * I will not add any substantial features after version 2.0.0 + * After then, improvements will only be in the realm of usability, bug + fixes, portability and allowing users to play their media. + + The aim of Dragon Player is to "just play my video." Precedence will be given + to short simple menus and being configuration-free over additional features. + + If you do have bugs, feature requests or (even better) features you want + removed, you can usually catch me in #amarok on irc.kde.org using a variation + of the nick eean. + + Thanks! + Ian Monroe + + +REQUIREMENTS + You will need at least: + + * kdelibs 4.x + * Phonon + * any Phonon backend +CMake is also required to build Dragon Player. + +Note that not all Phonon backends support DVD menus. \ No newline at end of file diff --git a/dragon/TODO b/dragon/TODO new file mode 100644 index 00000000..7ef7c264 --- /dev/null +++ b/dragon/TODO @@ -0,0 +1,58 @@ +Version 2.1+ + +_Certainly_: +*external subtitle support +*'o' in fullscreen mode shows OSD of length and elapsed time info, <-- emulate mplayer +*indiciate remote media somehow in PlayDialog +*DBus interface, subset of the VideoLAN Dbus API (done) +*resume DVD playback, probably requires remembering chapter and stuff which might need more Phonon API +*Look into using LiveConnectExtension to provide a WMP or RealPlayer-like JavaScript interface for the kpart. KMplayer apparently does this. + +_Thoughts_: +*a movie-like rolling credits instead of the about dialog +*PlayDialog optimization + *show an icon in the previously played list, indicating whether its audio or video. (done) + *allow filtering by audio/video? too confusing? +*improve volume control if needed (done) +*Chapter skipping (done, more testing needed) +*More feedback at playback end (done?) + ->Playback finished message (amaroK statusbar code?) + ->play next file button - be smart enough to know that 100 > 20 +*refactor: get rid of Engine::Status, just use Phonon's status +*use libkcddb, have the videoWindow display list of tracks in audio cd and highlight currently playing song. Have a button for the "edit cd" libkcddb dialog. + ->something similar for pls/m3u files? people would probably want to edit the playlist then... +*a super-awesome open file dialog or integrated widget + **it would indicate which files you've already watched, and for how long + **use dolphin-part? or from scratch. or creating a new view to a DirModel. or extending somthing else. +*nepomuk++ + **make use of tags, ratings, summaries. + **in file open dialog/widget + **and perhaps at the end of playback, let people rate and such + **feed back usage data into the social web stuff + +_Probably Not_: +*live previews of previously played videos + + +Before 2.0-rc1: +*add all the basic features to the KPart (done) +*Audio channel selection (done?) +*hide the videowidget when playing audio (done) +*have a volume somehow (done) +*app wide volume state saving (done) +*accurate file filter for open file dialog (done) +*fix: status bar looses title on resize (fixed) +*test the PLS code +*audio channel and subtitles... + *record their setting for between sessions (done?) + *subtitles needs an "off" option (done) + *does audio need an "auto" or is that the same as the first option? (not same, still confusing) + the deal is that "auto" for both subtitle and audio channel means what is picked by the user in the DVD menu + If the user picks something in the DVD menu, this does not change what xine says the subtitle or audio + channel is. This sucks! +*test VCDs, audio CDs -> add Solid magic, test with two drives (done, more testing needed) +*turn off screensaver when something is playing (done? testing needed) +*crash on Stop, then playing the same track (fixed) +*figure out what the volume value means in Phonon, adapt setVolume and volume DBus methods to be 0-100 range. (fixed) +*if a track ends and the user clicks play, they get the play media dialog again instead of it re-playing. (fixed) +*pause/unpause restarts (fixed) diff --git a/dragon/config.h.cmake b/dragon/config.h.cmake new file mode 100644 index 00000000..d820ea16 --- /dev/null +++ b/dragon/config.h.cmake @@ -0,0 +1,2 @@ +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H 1 diff --git a/dragon/doc/CMakeLists.txt b/dragon/doc/CMakeLists.txt new file mode 100644 index 00000000..cd512707 --- /dev/null +++ b/dragon/doc/CMakeLists.txt @@ -0,0 +1,2 @@ +kde4_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en/ SUBDIR dragonplayer) +kde4_create_manpage(man-dragon.1.docbook 1 INSTALL_DESTINATION ${MAN_INSTALL_DIR}) diff --git a/dragon/doc/index.docbook b/dragon/doc/index.docbook new file mode 100644 index 00000000..1a9622dd --- /dev/null +++ b/dragon/doc/index.docbook @@ -0,0 +1,208 @@ + + + Dragon Player"> + + + +]> + + + + +The &dragonplayer; Handbook + + + +Mike +Diehl + +
madpenguin8@yahoo.com
+
+
+ +Ian +Monroe + +
imonroe@kde.org
+
+
+ +
+ + +2004 +Mike Diehl + + +2007 +Ian Monroe + + +&FDLNotice; + +2013-05-11 +2.0 (&kde; 4.11) + + + +&dragonplayer; is a simple video player for &kde;. + + + + +KDE +Codeine +Phonon +Dragon +Player +Dragon Player +video +multi-media + + +
+ + +Introduction + + +&dragonplayer; Screenshot + + +Screenshot + + + + + +&dragonplayer; is a simple media player using &phonon; technology. &dragonplayer; is a no frills media player that excels at playing DVDs and movie files without getting in your way. + + + + + +Select a Media + +If you start &dragonplayer; from the application launcher or use Play Media +(&Ctrl;O) from the toolbar or from the Play +menu this window is displayed: + + +Play Media + + +Play Media + + + +There are three ways to play a video file: +Play File opens the file dialog and allows you to select a file to play. +Play Disc to play a &CD; or DVD. +Play Stream opens a dialog to enter an address of some multimedia stream. +Double click on an entry in the play list with recently opened files to start playing it. +The context menu of the play list shows actions to remove a selected entry or clear the whole play list. +Any file you played will be automatically added to the list. + + + +Features + + + + + +Full Screen Mode + + +While playing a movie, you can enter full screen mode. This uses the entire monitor to display the movie. +Moving the mouse shows a toolbar at the top of the screen in full screen mode. +Toggle the full screen mode using the Settings menu, the action in the toolbar, double click on the video or use the shortcut +F. + + + + + +Video Settings + +Select this action from the Settings menu to adjust Brightness, Contrast, Hue and Saturation. +These settings are stored together with the playback position for each video file. This enables you resume playing a video with the selected video settings and the position where you stopped playing. + + + + + +Navigation + +The shortcut PgUp brings you 10% forward, PgDown 10% backward. +Use Settings Menu Toggle (R) to switch to the video menu and select another chapter to play. +PlayPlay/Pause (Space) allows you to pause and resume playback if a movie is loaded. PlayStop (S) stops playback. +Go to the previous or next chapter of a video with Previous Chapter (,) or Next Chapter (.) from the Play menu. + + + + + +Volume control + +If you play videos with a hidden toolbar, you can use the shortcut M to mute or unmute the sound; and the shortcut V to toggle the display of the volume slider right to the video. + + + + + +Settings +Aspect Ratio + +This menu lets you pick the aspect ratio of the playing movie. The aspect ratio is width of movie compared to the height. + + + + +Subtitles and Audio Channels + + +These menu items from the Settings menu are active if there are any subtitles or additional audio channels (⪚ with different languages) available from the currently playing movie. Note that it may take a few seconds of playback before the subtitles or audio channels become available. + + + + + + + + + +Credits and License + + +&dragonplayer; + + +Program copyright 2004 Max B. Howell max.howell@methylblue.com +Program copyright 2007 Ian A. Monroe imonroe@kde.org + + +Documentation copyright 2004 Mike Diehl madpenguin8@yahoo.com + + +Documentation copyright 2007 Ian Monroe imonroe@kde.org + + + +&underFDL; +&underGPL; + + + +&documentation.index; +
+ diff --git a/dragon/doc/main.png b/dragon/doc/main.png new file mode 100644 index 0000000000000000000000000000000000000000..b9b47cf6c77b8b64a37014234cc76536d00df14d GIT binary patch literal 141440 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%0Dj$-i92Z?vJqQZl=n++ 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

STz(afyW9c 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 + +]> + + + +KDE User's Manual +PraveenArimbrathodiyil +Dragon Player man page. +pravi.a@gmail.com +2012-03-16 +K Desktop Environment + + + +dragon +1 + + + +dragon +a video player that has a usability focus + + + + + dragon Qt-options + KDE-options + URL + + + + +Description +dragon +player plays audio and video in different formats. + + + +Generic Options + + + + +Show help about options. + + + + + +Show Qt specific options. + + + + + +Show KDE specific options. + + + + + +Show all options. + + + + + +Show author information. + + + + + +Show version information. + + + + + +Show license information. + + + + + +End of options. + + + + + +Options + + + + +Play DVD Video. + + + + + +Feedback +On IRC, irc.freenode.net #dragonplayer or via email, imonroe@kde.org or on the web, <http://multimedia.kde.org> + + + +See Also +More detailed user documentation is available from help:/dragonplayer +(either enter this URL into &konqueror;, or run +khelpcenter +help:/dragonplayer). + + +Copyright +Copyright © 2006 Max Howell +Copyright © 2007 Ian Monroe +License: GNU General Public Version 2 <http://www.gnu.org/licenses/gpl-2.0.html> + + + diff --git a/dragon/doc/playmedia.png b/dragon/doc/playmedia.png new file mode 100644 index 0000000000000000000000000000000000000000..f691fe0d1b9e04edf52d41d0d5b5bea535cab6e2 GIT binary patch literal 50496 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 E0Lp!5{r~^~ literal 0 HcmV?d00001 diff --git a/dragon/misc/16x16_dragonplayer.svgz b/dragon/misc/16x16_dragonplayer.svgz new file mode 100644 index 0000000000000000000000000000000000000000..442bb02bf05eab38dc90a1edd2e6e8675bb6832b GIT binary patch literal 3573 zcmVv_OH`&*6sH5^>Ti>S@*Bb)|<0GfBB~`{%tz#{<7`o`+nKouJ&)c zuh-x27W3=A`}FO8e|^b6W0~M`jMphndsiSkJHe-SvFY?`8))^gWu({o7Y(%GY=8{cXQ` z^LBp(td_6Ne1%As+xx+X^T#a8iLebF3?C1%BpB;0H;dQvox3`FySeITtNnC0`+L7! z&1e5uE%rBC_wehh`5XWG?|!l0Y-e9DHtXH&tL^;FX5Ibca{jL0&U~KB`RmJmw!4~N zUd}-8K%Y(HYxZfDP4T+vC11&pV_!En`|F$izxum3aRHKWb!Zoj#X=fPYcZbZ%o zcyn>F>s>h?VY~gi%l=+^>ee^g^G|YK@M7_KgEpSHtNpukIO}FVJ(-=D41Q`drZRuu zWM9n2tehnACs}$w%Ys?PB);e@_Qa%5oc;Voh6jFpF7t)`y4iBFOck=!m#7}kT7N$} zDf90}CuRNhbbkJGX{1~}jK&vOf|@^orHl6Yr7S_k4`Qj5GB5H=S%S(RjU~?Ts~eyj zp$~6>^Ris_`9m9EOeVji0nY6;Jt6$3NQ}4nV@2aBNMKmo zn+Nc_^=j{jvzuMN{bw9-{&W4^u74)=?RLK2`DyCv)!Eg2zg^vZI>m&nVw0Rm38+q{ zkf<7{@MOvkNn+MQgTogmWiL+3 zZardYAw!i3nk6CCGG>@+l1lWH zVN8NhaviulDSI)JeVO5bosY$E>$5MNu*e28;GQN0ELrsteFx2dG!h*|(fzN9i3$yp zI7oi9u;q!#LHOQ8dEUQQvlbut6 znt?}bvm^$p2qU@&*^j^^6dD*{de8%e2kf~jOC zCU81Xk`OuzQ?q1PM0DH)uqMV@s*RceTT;rVL$#k2y%>oOEkF}h1)=(z<}PM=DR(jR zC@Zlf@DK4gD{IOGWaA!?Qz0rv^Qa39!XGV^(3-YKQPgVH2&j}IS!^=JM7iWla+J9K zdwAb(e>Qy{HMa<8m6RXiHe63YLXl!vhebAdF1b{ZgZyxP0jVljg>k0e`$ViqzYi|@ zzs+xUyVZRCAD1`b?@xO?oTHXs^*^lU`*v+W(HTic%@~`KQ(Yp4_gm!%Mgu7^;hk@6d?XuqF^?=44MkCu{|4&m|{s8tQ3AWdb8bnVou|p z#Fy2+4$u$Uw7tvs(7M({|ff zQ#(C=yIMO-(}xtc3hxm($ivMxO8Oy9{cdi*^4qh= zEWO_BR`)|<2`&DNil&(&spM(O*unFp-`reZZI=Cm`hmFX`Tp&n#=jJYdmk;8jkJ7z;l7`*H|xGV zO}96feiivczuqjD{ws33`Mw|SI@;@f9h~#wHTFRF zbpSDua7J{<5~5%!M;O0l+v{Vta7LOWuT&ih> zNl6*4cAyEYAgKdfNVRmpsS;ZtN)=u2n*rlgu-@knP|7TY%0yCD%my7mLd&JHL#$Z2 zb~U*O5(iYWNhLQ|6K7yhoYX{&RKi|(Q~`@QsK~W;oUHgT+r$lL0jbiF+bb*>5NFk} zH-MHT7gTn0)11`QO=~O`39Z!5%~)1rm;fdLG=wgvnzLDTrht+GVhO5ZM$J|yz^O>_ z*#ly*j5J|MRm0-3Hd`uM4XSHvBIiN9JgEAdN?JR_p!1^VJoM+C(h1wkn2% z1Y#B_m7>lEQ!+KM8W@lwa>o)5fkKl*F5o5ykkJJVw1i1bmI*2a8u_E#Nu2!KrHLD_ z1|^yW21Ss0!kn;jiIoS+rB=k&lyX+IK#QYn8A6|wE6EtuWQfM@q zVoeoLCPFDwsd@xK@feB$AL&NIgHeuonq)SkmWD;8vc5%YE6poCe z;_mx0%}F4uk()&k+j<5NAXpF#i=qhufhjCj;Tjgvp9nZAr9uYj1d^0o2prLD#n&8| zDXRM-PH#1`FqadSV(gfTy2^2AQY~7G9mg;k0aU&ZtUtJ+ScX)BFc*=FTp$O*(1;>? zs8-DCP?RU?QSzDltIfLsQmt)5WIZAav0aGp+6r*vL*l&;@ymVph2$N>8(lE?bE1z6qWRTl3 zVl@n_N(SWR8YrkyX*&dwkV|u_h-ylKmBL)O%RvGGSB*|%VkPVS{*uMii!2CZLb1RY zusWULvOwmx!2skOAm>EzFnRKCru+RxKynesT!<@{(2m|!ow|f1Z0ac#oWg?oW?usu)XWCIzqa0S!JgWoe8I`XH&GvEb1(K&fbvYNy;hS^5b1 z14{qH51Z|{|NL_8vp-y8$O&1fWP)Ixo#Y(V9l45VRAD0MBBf%$kuq478>$6MkrB!@ z35&2s-6m$Oq&3h=RKz+>3|K&DBEDR!%+|%JuQKEsSoOyedxRxIlZqC5KDmU6+jK+j zYF5|h1k8YOYOR$rQV3Sf!0ixh3Y?*dSp{yXKcJ8h#57ETMky1NK%)l&ckN7pT$?1L zsY2UxARB7Kw3>n2JVH6SGTXxjM^wQuP!>?BV-%7YRt{Nmz32b|A{S5`fv#_CRRgSv zYcZ5S+l}Z0N)-|61hX@VfDZR~Od~WrbA4^mj>kgLO(WO`P;y3N7sXVuSUZ_> z2CdpK%_0CDC8e5`HJp-4rrKdDNEIpshJdxvI-n4-U=$qX`ltj>RmSOKhQheloIE(x zx?C*u57!?k6*3XB3C0dUx#i4&vZE}-55X~#sv2m;!fG8jyJWR;9P_o1Y6sHNjBvu1 zAQu#LWyA!)2sKbOFyfAKLSvE=nB*L%iDtPfHhYMr1$IDDlel#ae+G55#9;2)Fil`m zicuLH3M`|buxk&VyG_kmm4_7u8#OC1onq+pmV_s_q(MR$@(bjHfcyK5e5qNUaZd)B zpK@8M6`~%wd`ZfRD`f_%8Hb0Fg_vih& z?$^2dI-MOI?LPDI`NQvPDS_LNo7cPdX=UNzVeR$vbY*aJeJ#72mm?s+>-k_y@_YNe z>6FgHyP@-^e!otEe(tT|V6VCM-=O-}@P`?XPJVu$08fTOfTN?{O!SqMzO8 z{%VMH!R)gSYwJ#XimT{efvkde0@B9o>{Udx2)~-Bh%lTkFVXI!4Ka%l=|0Y zYXbcquZNlA-SMCA*^l#=>}Z>T#e-;zWjJW~kGE+i-u@+GOWE&*lc@SXD0ZA}JiL6K zodNvtZg;a6+b?^IqqpyS`D+mppFY3du8t2swm1iW_HX<1exCkZg+wnm_X-f?fhz|?+WnmnS4yR7H3<{sc=N}9(3>3$+5g|bFMAF zVczW-e4Tw9jeeLCJd3WUb-z10c!0-IXa@Ltc)K@#UY>jE;qQ6qKi&Vh5d2Z`@p$}L zdkWz7<*>lS_!qVpi1^}o4d2%L?e38C>ZeuIV{F(>tFk_R6=G%kIsWHW zAU7aWJn(bbGQ;=YTIfhm6YPT;V{r`@<4>IEq=7s!)n2}+n%hbJf|zojKy-`wLFuUg zS~QL*iIjon;)l-MmpX)lyg^9G*mtyd$hP7JG zgMm!35^qKm^k0U(ChCE={iaVR&yH}PZ9epc`bMA6pCruC00BMIj(B?ar^nT<3B@kY z`Mo}EOzgwYMahEwSaFcw6IOOed9c~;C8-3&wpOMC0^8op0E1}^^4;j#$PL7A?b6|t zyK+}bPehzE!Q*Nxw4YJw2oH0mXWYh(<)br)xiuB;->Ym_|2(_>Jc#l{A5;k9USR>b z3g=|TlUmAN&#^UGl7Y4&ROzKaq|~~F_3XYMXJ=QYRM5Tr`uU$;<;!2c$Mh#RrbkN` z4gAx(g_MQFh|XdTF_0vqN8#BCV&e>6a}f%pZ(#C2{_R5kBrh9tz1;f({u`YXa4vtk z6=!Y=5cuUo0O6A2ePRM~E~RCM5KduzU23nZwM`Zbd0r&q&I7ojzyAs0*q2(l*YDx_ z=4W(saeUv91kC7om7dj2{;#G`uAnJMp%hrtvNZ>82{J{UB#v0wwt|Pwq=Ph9kbj)B zFaf27ea0Xj=*j79f~lb|mG(JZkr<~Ds9_iC!HOW9i!s_|ei|4(>L=|iGOv1fa%i#D zW4xflSjFO2f|8S=Mq#5BH^OsV5%?at_Op1hsLE3J`BV5B5}SM`zeGrZV`F|^pp$9B z{3#+-$R?^4NX2o;CX0<=8UIFaQkWwc-gTZatRPh1_VL|}<0iatlk$%*D=Zz9rWUuw3;M2>Z&<0%{1T|BdiNGFyqaI{G>rowrcB9zoN5kABa5=xStf0n!j=m9 zYq{g^#*WHlIJkv9HvM|-zuKmHh|O(`FQwFNzn10=xLMyEo6P7#rie~@Q&@b{{-s1A z5@VLTM9{T-Q!#|Y_JJ`Vs zz1@cL1bQ2^numeZQ-fe36=~oYULDsZml+8Nnhs8zqim_s4Q5jqLQ}ifri2-v6Wn@H zX%=&=OYByXP_!qV4k6JY#_j~OX$-KA3!GXU4_zJ;$){;~QE3O|QjaH!l7*HNDmrvC zOcGfRnoE-|SZu}-)p-|?f8I+(^OYtrEq4}4okuaG3CzIAGP2bHbZD~O8@ic@i^`W3 zb|Egv6a4ZR7$6!eDT^zTAuUH6f*etuH=EFA!{m_`YdV)UC6Ql#HJeG7W4#^F*Cp0s zZXLU<$aPn;4PCM%*THC6w(cAzQyR4KB-t<})j|ZKkhFrb3;zwdV56j6h0)k>wUm{B z5=P%^ZX7r&*}?9LJ{-1wUEZtUA3SEv>xX7(i!verlJB0ZU|0g4zZSf*nGS=8G{XCs zIV+TF_AGje^;VS3NZI+%)CMA_Qh@JUJHwOLn(kWZjNtwEdMA@qfWyPgYo$}53|=XH zF5!=!PcR*G>sWBgj0T?Ka1=z%YbG-$8uH9m9Pl74D#4~T?Uf-fQu5fsryZvw-+ z+E%P}BLhV0uH;OfUKag83(Nae5q}78Nqq&@o4m)_N?bB^7>hkvvAe%W;Y~xoX8zWV z&)vQ#Yn}e5U;Fpz=kw{Q_&fR6?W<4TV;yzPY-L!WCV+pde`9A$x-C-eRDYa6^(kxb zdRFAaUEB0|(>t3fM!Q4~J=b%AcuG8uTYI|HwlL&cLr11aGHL4vWNt6E6vWC>-mrj1 zme6O|F&0JD-0ce)#mK~Gpa>Cw4O~}aM*NnfkSKO~IwBTBMDrxwVFpw!S44b4z`7;f zI0hJD&YCjM3pJthq0%|0^y#kj;mcHYDcx9DsOCndiaZwp z*-u2Y(m>ih^+eyO88^7d;KtPfWzuS7Hy0{HH-=S1*G);R4O98Ml%64eZ=CP6EE|T~ z2L_k$*hUth+Lrt$$u13SB9%i@186(LX$Cpbl*B3Oo;tJd2sUDa8H3&hwm6N*V3OgE8T=`04_d{|Y> znk$%15$exw6suwM@nRD)o5y;h%P~1##=l)I#G-kpTcYXn1Ts!-G}5Yc zixkdjiKda+H{3rk!r2}SneZNmvxBi=0qW4)Vug2)5xC7`Gv{3nIE=a#InDID3;=#X@iXUrXStEH_~wdb|qOb9h%2hW3VVC`??6H zn#QUwSF#NzBr5Kku#dQ5U1XL=eaTnQUGWrYQ08ycB+Hkq4z6E*y)>!x}l!a=IoyhUySnIZq+c z`OG}fw^$d4I0bfU7AKo5K1D_Z1-#@=fs5QX?QtPuriBhojr)ioi?37Og_Y`-4FIa6 z6bU9PdAa)X~8wg=>7-$txT%Lm1@_bdK}gC>mX$(8_T7{ff@%OYb@pl zCe!drNj|cuJ`tNb;!C#0sx%|9G|R)2HCLPCQKb z+WqJ;BN293L|vXrE2-;3Jm`6c)DlPC+nzT_RIE88;U19uA1WbT%QhUTTCw!tkl0^f9+P7#q44v zdx*lMd4?wbH<~rxg>; zh{(uDOuYJqy*oBN5J57TA*LMg+L)k1Pm2;*A%Zr7Lv0`SaBYsKqKcD)F$e}YyoZ6E;c%%3xmZE%_q1)LX`BO>S)BcISdVMOF9*lB^2vs zH$FwGVaTAHAZyzH$|ttsxHU$U0o7rw@8#z_|5R>UuxYAjNBOOI^?SE%=a7%-d9Y`Y zR#_`eE5wba-M;}hmStXhP}WL$un*&vBE4CYo+VzQMj{mx+dVYb;~9v~-8!e!6{Dw)u|l~FQ3u1D;iP}^uaGoFZ3 zHFHaC2_&!*TZSXuQ6)~Hot$nJLm}t?rl`z#io5;ozHjZBpH>-p8cRkDhBHx)-Wu98 zf$Nsk25a12cKO9dl;(jHs&VfG@4}IOdx{knwK#2lm1CX?_kIe^s2D$$0-8SCXh{1j*4+jORvkVw^$5&>4M8>5}y*Pa|@I z!cM4qP)w~j()X^tod|f2a4e@`qRO(*G{+)NEstALONCx1DJ~lo+id9zQ=%6l5p_L6px z`fZT#f9z&3$SDf`V$P@^!#BTpG8xd{x`(q1O?El*AQiGxD@Oqw&xykkKv@r!5>*v1 z^y^F2INX{qSU8BH9KV32Ee{brruo@`q=xlfWSLd3&9|9TH8y~9=}ZA!6A(a8!MEPy8H~6ENZ@Vw1>) z)?j-Z=qt{vboH?R;D@MINibqm906y+52e@DLhdEJDWT6!YF|Y4Ue4swjt{a_SxR>u zawUW(a-GbPL6w(uMS;>Z4mfxK=OerPqM(9kpG>_(Sw})YBAs)>&y{CgzBGqPBaWic zUI)1)-t)0#Xsa2lXquj!%kyE~N?>E*%U$a{%isgEWS}9Z&r`^oN`-{L88>Fw+KAW| zFBF&`p!Pm1TC8~t5_buANvX;3!qvvC&<^XlDzF znG8V|6*7ZF4%zk*{|F#(u&a1MuecQ0w_1zFXPr!=^Jz>5Mq(*?c2*Yv9hQc>;VjQu zSgtI4`(PlY4u{lb)M$q^N`h?u4)l=m>Xiw~wSeI0-Iz{t1vWJ!e5eJeM-XM3U~OJFFLoEUVM+750@2^XF=p18h^cv$CM5BRVB zF(B|V=Rzy+xrR8Bg_ZgyA&nsGZ?>@?O4hOTRA=#1yd&GZ<*_E_s1U4yVomIZn3oc^ zsaUw~@z0yr1uJ{*Pe3{@Z1)dT`tj|D=7)z`Ys5ZP{J$L7=9qR)Y7V8Ogu*}u_$0lE z$6jL>EV9ao7cwuzld6p>f04d`t`D#8kIK*C{P?v$Z33*n&cD{B`#1giZw6U8r=L~7 zb_hMZd_21OdIU{B9C%w_SY|i5hh_!sbKY)v_5%Jt=D+vP!>scsgZ;UA@>>`(o^fXm z1^M=3`}w(YsyAy2FABo}wPooc0s$@`2QOOjN^kMe;f*H;7lWVetIyxEPu=k{QPw@5 zk0%$bInm8;9P;-E=cZX}w-Lp|(Hy88X!jnZdo}U;{h6!_Q9p4bE5rLXx-I(o3!lZq zFYh;?m*Zo=4w#LoD-Sg2-rap00z3U1`&H9q!j>P`OX2u^-aifj|H`X#4*&jr|K;NQ zykdu?4EjzlzbnU{vor4;4mmTEdu8zVcy?C~?tRKP_??e;h^H9sEAaMV zjdQ*_Ked*AgV;M~Fu(VwKEFOdKR<_nK)xT$$NgjddY&gA#)`TY2OWd7qC<;EZ`zU-GbUFO;`U@P0g;xj*7-u~y`^~g-V`-Vo2T&$Mu z*&B~7{BV&8QB>dX*2XX;EXxbwj!|Z;j!+y*IL~hrI5=*mgm`YePB^1zVa!Zcm=klk ztL&u9WtcYMOIt7%rQ1;4i~LYbZ$6?9bD&S5RaR6Oks!z`&lB4&IUEj&6%m*`vRj`` z24$APc!L*2kgT5~y9*jX7!_3yHbgQ?7n+R`X1~yqyS-H3$SH5G9lQ|R$vRPbuV^I% z7M`*WmW0%;ULJ^&%EJ;N7=|dXfqS1!lnyGXRo+4lXry=n4Sjt`ao~&tB?MVMp$&|L z4vZz&TgUF!NT0B?!Xg4Fql7W7rD5np8-vs|Q_E!W1_TlpY?cDdweXsivP_mw#>L5n zX69oB7Ip_NoO8CkEi5e9v@qPb0{_YIG*w#$(9&UDy{DUEy37g+3X6sjUr}hNi75gR zxDlsWD;`s{US7y;an7VLOwQP-m~_9lF)Uk2$Qt*TuDps5Nic66G}9HNv{cR!gH&=x zNwa}_@bRQ*R*D- zu3sLL>P+RjQFC;t71_=CbfQi5BulO7_8%Zs5O5%TagKs8Hn|Y=k6xmuHsuADb zLx1LS{cIt&-f8VN4;$)4IE<0lylj97mIP*5hTlxxeS&T@ST0k@NuNOjc}RFN5t_pW zOXviaB}C;kweIegTK~%)$y&BB-P`m6TZyu(c&kPSn|EF&fwN6|!;eF}_6H-Ru9lH% zS-lpP48(1#D!{1p2Ps=_Xp^lx##mx33XsAkMy;5_)I#WJniwZ!#pxh1HdN;BC*ty| z7w1>k%tc7Qf&%ukWNR6*!D?xv4+BEFR6BdoTL7b_)rdl%ze0+ zpik+~v7bnnfX^>|siytU7I&(eHgq@mU|OM?YxscYxT>8Jx}}V=%dqP*pVoqK3TBOS zh`wF%HvVf<3vTY3q&;j!fB|6`mbAoXrg2<33m>=nv1AyXn&pvDVr6liC3F5fvbj`S zL-zNYV>q?c2~W&aYN#I(D0hq--h#(OVA;y`@%;gFfpY@E1fO>6ech{$Y9(2O^&BAn za7ej=8RXe6BWZ@$B1N$N=dm+RJ zNKTb05=_9Bxlb9|;T>gf>@1ujwKCHJZ8jmGTsabgbG^ah_IhhOjhS${BApu%#>SJg z=1mz>j#ZfvZ3ktTA)Kwgx2CAXKLhX<(x|E*QU7&WHbQKZN@`!J|I z#aPW-Ab-4mLZdYz2uhwe3S$JDZ^SEPOiNaGP9gZqC?N}cgn>q9a>@61uMLALT~>Zu zzw60ed3{^l>&l!wl*UoVEQcCjWwR8Z2>E^Y_4W75@aOY!E&r0EUn@l?Y$f1BtSzwc z_kaXVj1spw4WWr0Hgt#7oE~spou}BLy)SU8T8S7)LEE zM)W(nmw6+Zgm?C#2D73DDiv`vjqK^f*zQLlj1}ui0h5A2_;8Wh?;|SirlHDkZS;nQ z%>pWWqG8mRwT+>axD&#*LT7b`IvC1a zGnkS=mCq?Ij58q|f+sf?dCmHqoM#q`Vdn_rRQP8;p@Jff$XJ!#$A}(=F`Q8m3pVK- zI+NGT+L}}%B=fH@BAy7mf0XHDI4j5ebW)yDmCnq zAvh9K3}8sCV&d2};zt-=As%9OoYUBc9HmggOD?`IY7|}zZ4ZnYtOb@!k1F$Jw`{NS zodo)B^eZZ#fHHpzip`M|&g=kQ|6c0TajM_?<^FIAmy7QUj6;}qTz4UzDtqqOD(jwY z>iqGC-;|qw^GAQMkl+rEl$zIv#=SziN=kP*BQ(68VHQ?J59RF|P&foELRr-*l2k7{ z$c)8V0A#eGnLB@rMv_rzt1|wPOKwqHpN{bFqO|me!SD%$7dJPU)Iv=T7wI`LJtNHG zQxlhk6}F_Vs`IRjG1|jHo@_lmx8ll|2u_N$I?c5d>Dn*oIipLmAR-b~XOG;Y$puUm zPGQe*Tr7%0S>QJoCyvUQb*R>PI+bv1$+a3Oq;;{>VvzS*!O5tnQQ$5h)DOAIF9o4L zC5p>ca)5ZFaImfRR6k|HqvbSuuR>prGh76-nY#ozk`3O? zJf-1_KM88twSrX0k``Af?cr`&P>}Swp!&Q>!NF)vJ`T^IC;#@N;2s46Ft zKhx4o+nqL$83&7G;HNmd)2HKe!~ULNvc?vBhU~f-v=#RbD z;%<`!K1xFT02txQZZ+T!d8rw?oi|gb>w3JpAKxv@KXwH~RoA`6+|plqYwpba0wh(} z`-3X#zME*$^*XPo1O!#rf3cMGGJ6ZXj4eG1*^-a8E%yCvQ(GlU_cJ20$W>oWir7_S zhzkizCu=x?uB;Fbl(KV1D*n>;M#VcD)l(>K#Hfcgm8BgnFhqIvbvpA)%KS4`swToE zrJ_f=w455iE@O=iXI3d2zFzc(aK(Y#l+W&#e2uk9@IpC)z-g_6?ri!8?e%NCbe5N@ z)-M!Lve)%P*}L$k*htfF=Uyfc*K??>o_Iaujys4!9&5%?7yPUPjt-I)YWtS;CUs@u zp738zB@EgEizHQi*}!R#DN{xa8yHl0S@II3!#kM{AHSvV_pgh$zgg z23lv4*B#m@miuU`9#W%7%SlrtX%w~?6P5Xh`sHr<_4nqEfm+U&t>rJv&nE=Si8DGw z9qExY^M4?L#xQkZ6OB3kY5MbT3sv?ZiXy|afzhIlA|p&csRHx_*z+^TDbaPWdP^Qc&fmMbfK7AIk=3sgLjXXtGB zd?x`^X8a7h#(@Z=atq!`$j##j$H6HJQMpP2Eo2e^VNxIh#C+3%I!>u-Sfe6ZQ!rCw z#v9uuX2%6Dfg z;=nc1R4EvlMJ*^%bI_><9iCbU4wq4YtF*Eker6*I&lbGP#w26f`J%PLsNzw))H5%WZ!y)MF}1yk;k+&^eUupfbuQ3GCh@5 zxT6ed;-gF^3yF=H5&mEeTN`C?`HXOW2|<@bq=zSoD#DIrjO;RHruA@YX26iEJNtHq z9pCo3htv&AqQfMwl4#Hnphv~%W_3xCni>qkXZk)@5t>+#)y~l)0&t>!6-}0gvqfEP zlp0!&s6x~1a{9>K-N*|(MfWlxRa;cRC6(dtFp*s6)MKvsgZys7zWYfeYwz&HktR*or zI`Xw!(CM#t%gco5KbT3eVZo%&j`u@so4T(<)S2X}qH!H6ku_tqOBx02#9BnzrR&WC zuU;AoSR%O=S_`h)vNteW88a51S0xYTV(^7-|6B&w>ZgXaW2cZczQavzh~*GXVRZS5 zu<0J#IHlsMVx@8Fsxc3ddV7RWc=KD+|L$h~*2Xq6o;4d4fUR=wYH*UdPm8r!KvmnhT+)pUTGdsH5Usl%7 z{F&chD(|?rbg?nSknY-9GEJsOf8i`8us|jiY9is?pdl__wnJWl&DiM7V0EjS-xecF z(?3Pw$DaxKlJLw>W7}8vI?AsHd#t)NQ2VZ~3h+9=T6;cokgFE=8?L=@xUYT||E7+; z@_#?kVwm^#TAOax&wEkHOZD$Y^rO;~zjikM--z3I8tW(aGWKj=U*WlEwo{w+Z-1QM z(!2Y2zE5s{30&0E_8sSN{#X8=BBrDK{N|)zJo1{0GS?|w^A^{-F_XV8H}Oq=9xw8G zz+}|)`@m#uCEKhIG#gU4e(b!crP<{AE35w!cI{Ns%BUMd$3`uXYVyKXoAMy~K>b7A z-&LLc=h63m{O{ON(JqmrqYuH||E=@ig#$iWtFF=#I+@=8z|Svb#i4HO`)1zrEjt#j zzJIbS=lkTE!6Ja`b9ytNs&4(lbuLdMm0x}N{i zj{(1is8O74?{{9fR;SC}Urg?UJ!qO#nB<6Lkf5DUI@fFzYg%ZNQR+Y%Q*jpCh~c+PAhn8qIbMOs0=H z%%`&qn#};V1v=ksI_meK1Rq&UM+al{hxOY{Rn(8ZiK~?H?CkU9y~*8f-YsvgQ2a3} zvBCCJH`8G9-v4^IXy`dKS=8aDzY#|6FxVp)$xe3!Nu7-Fr;G7fRXp`(ZE1ael>U#DztXZNXw-V8u>WpQjDwHPox|ztPTp@OW z8X6ON7v0l(!+^XL1Uby~Cp0YHp?QJ5nC-rGP{;_pBw6i~>(aux3+!?KXU{rM-?!=K zPjdgGe2_)&C(h`>@4q_-&#c2yz9>v1Cpa5(yl>ZLnZ93;9}4Qw@z3^nZy z6n8R}fm3%DP}mOf-V4K5_GqmF=Z#z*5;;0bNK4Uf+>%~tLcQV&;_o@;kr6O9#+S!9 z9-3l$70EYC>NCy{2p0~t2aBQXV-WI@w4i_5DkF;=AC0zeJgsG{`RE33#?jdh7DI!S z$;!7eJk7>5G}OrnJEbDZTqsYcO&3VvZOtipbnoxo)~ipSe5#MN4kq^>b{B4l)bM_V z1d~HwIXLjMbu+lMVHH|=P5JUv+O~i*z%ww51#JE1n-B{x6&9`GFo=#h8nO%Tsaclc zW-mhO0}{(p@RU<@XpmbncLs--*etH`!IYuaVk%O2CfQ`)F#W2u7kiGNV6ht9H-(aP zzEz5jY0VSuZ5oUn%-2Z5Dbg*c{PAW|f7$xW)x`^krR{GTt9t;CiGPnvt@P$b;uyhL z4;#+hMn}x$6ofIBg|0d}S?grkIX*ea`1rRn^img#Lv00T%hBAHa&@|6C)d1=jp)^B zDSU_daBNqSISL^~@*$7QymZq|9C^JVB8FylW}$86c8sRQnMY|r5|6zP%Qj>+Zgi>=x#Yigrxw%Cl5ytHfw`hq;Cf6gltLx zhe)WT_XzF%BVJDoUEh4(Y%vV-;G9Z2miC@7<;Y|5NQzP4-a<~QrNkH3Qm_s8{JYa( zT7dO*ho~?5e5cE(2ixCi=)o$?$(EDYj^ShnZf?y@v#?h56o7vN@ZS3%W!QA_q3iLP zI;~WHHoA!VFURL_{Y$y>VidA)F)I*=$fOja0Wlj&;Ee44;sb%dMHI5`5_yPo2%bJ@@I3G`yFJf)q;ZO zRpHlIV42}{k?PZb9501P;>M)C4yQ4qf5$s4TL6+d^nJJ_i4^GLmWEPO_ig& zU=q!Un(;b_LcDIwv^<$>pZn*umdB%+zGh!t`t8m}ART1_sXr6Jc!=)|dScT$FUZo) zYivin+b(uQVXvH5OP_^2{~hAO5NM0->M6w z(n%7?yhA7>uKhT|uC8-gLuzT0NI-VNhQ1&!r1y-$D!OD%ZOEBQuBiwCjV6E)EZioA`W)ibs)E!xb5fbmomCwG=@Pt zT{Rv2j1!r_#fM?a&Rf~N5=7XR?QjF8ZEt}~eNSE~rt}rql<|YeQ+|V@-C3K%b5%QN(pTT0*CYL;h2?LlX`$uN;fZOoE^iKp{zUdZGlxe6b)gQ$UQSf zg(hDYf+>PM15FJUD_Vi@a6_1Ixrka3W`m+3#nyVGhFlpus~o~qTqt85fr&eg9nCzA z5mTfO$zw5il{dhoUp4Iwun2J*Au69ZDoz3ooEy}S&6z;VQkw_l!chkHU$5Z z`KDQ{Lk{DXmg7li&%+lg8{UNeOiptKwIMeJFGPxXI=rSc>(gXN5MLeKjNcG}hB(>} zJycC-SgX2StpI`=;CUTUTM>YBYqDK4))M4zduN(x4VM--PrHRRE^i!aXGR~fgt$6&q-X?z9`jIp z6ln&w8h%)3qJsy4A#3}=xi2WDzcP>5yPTj*ZMiUP8d#ZB**L*&4LTy;4Z-=M@KY|gp<{nNj+)}t3@dEk6EXC6^Vp@pj)`3b$ z4&Txt@+@2hh6++qI)|!Vij&aY@n*V50H21qS%MI53zz)*m&=NQDhTs9#vXXuo~wrp zik1)8)T15dlIq5-V}CNE^zxuQh_(jD+IxV&ZYa~z$>X~~({PqV6lfZ5$$tbzFj0yH zkJE5)v}BN!U=Uh9J*(l55S*qX%?6CSV95b;JkYsfJGx?Li&#*28xK;HcVGsO%S|Cx zWC>5r@1U_E3L~O~DWr9YsIO?CA*phyVZw8zSk%e3B*L0G)-Sq4ma<4)N)5hjs}#&k zXU8>iKZp~oLN9m0EO^-EA7my<8|e4uiA`6}q=*2#jNg-s-(1Kx(pt0vr9~Uc(Y<5C z+f(LEf)Qp8-G5y%8E$&&c&%B}ihEfq7nr%mgk5vB5KoXn^F>-l62~l=W}0|Lb3|W1 zf&3&Oc5t@|5a>3G#p#n9n*#_;Ep9PatWFy2{IfT4Uy4z6U>DK}|6lk)KrKnu(Xj}C zyPbN5u(!)Sb}v^jJ9*XM7Zuo^7YK;(njLTS@6~Z0TddeVpDv)Q{V9S^iAt+Psj@M5cM3LshCPE0Kp;eCt z@{rK5Ym|mk6U>_yLzz*If|V6f#*Pt_!c21esGgVWGVdGBzPZ z8gh<~;xoAwVU{8~fj+s7JoyuK0Goc95-54gR>H_&(ojRUd)TBXS_?Uon1VAstCY$h zrg|k$Xu*NR#x@qDuWes7y){!J!?p3BHF&dHqVAY?Hh58=&$?_ul(wj+$s)=&w2x<) zv6-sS(J<5Jy+UOM{2

bM1PwbZLMQ7YUu5^bPVTh=6)gKNg_IKU5g&4rWQ}^D<#e zZakD>&yA!GdBj5qQ9FV~Xn3J7pnP$!i8yloLQ-s)S0g}e-Wvb`p6v4SpIxc;BT>GX z6viA-DX)HPXZTJD9L1-vg<`i7s4U&HXMf`JF3H0Uf7Wc0?3R>8?KDsrCR#|6wKm=>1Z0rrWQptE*?Ojqu9hezxr-WVN^8!_k9S&y!UoVRaXahc0 zGn%AHQC4ZwZxO|+i7myPCPo31tfQR|+rPU%H#XBA!(+!b%P0bnPPLRJ$f7BX{i(@% z&}DFnSL1iypO)BILOTwg`L7#M)i|~~IOhtEhP?=!bRn7bkBOei6qGLF&%XQlcPGW< z=F)gDN_WIkmlggz{jWJaMHv>f;T=d67p+?nrR8ssywa-8-p73!@!WvU zza$}Vmx<-lQ~(egQhUJCAxK{n1spt~X+8cRuuF*9bt?&jol=8zzTl3Zmv&vRYl%@6 zjCrOUJb4LDGMPh@$B^U}0u~Fb$KtNXU2AazsXgOuue&M^a#K9Gjg<0IwR?$X;)t2C zDf5+RiO-EBizdiAAB>hgn$JO{RXu*Go2u;Cf3yhg6B0Y_qJ(?2M-!r|q+&4zMkuX9SFt<(9b(wbMmK=Iepa^Y)`BJ5{NhctcEO_JmYe=tmr}FymQzD5q)IA&gdfciSTU4$2F!vZee!Tg;4F9R z{^nQ0E$?;~Njm-FY@E_9da+u$+nSi_)^x7+KBZ<>hf)#{1%h(Hy?uigkF-ou#=xLKe06br4CIBpTyIRElkc6&M390Da_(I?v zF)GNLsH-d25xE?vR z?8iI{om4y9E;Xu8Qe~(WdxKt7xAqtDa~McDuij_;J4biW^Kchh7g>!TC*0{?e=uk~asX?vj-YjPb3JuJcj zL~F>FSRfM|Y~tgn__EE9=Z5JPl;fzRf5%NRMHj^F<(U!o)+ zm~KSF=KH3$@&_aH-}0M3z?1$a20i{n@JvBh)5A*M--gtvsCV?;F_a0>slh zmNBZEb6(rDtBhM~ECwNFy^xOC<5 zH?T9v3xz`eoU?@~A#m@?oC(r3D%t_Y5RT?Z_RY9$EwYr)26YSCMc_cc8uO}5#<#ZA zEtD6Nh&YW0w@O1sN=UT)|H|%JO9~k!x&&gQotm;4tPUO9on7YMC?KaajvcsenAwdK zVB1U7QmtEm3gZ}hFIpmqx5!;BXx>IvbZ^wZsUOf)`+hNCiu&F!-UgKOuI_>g@c$J3 zY$L`;J}WSzBFcP4rDaS5b6jIjKSXwKjHms>X(~Iyl}YF|aGx>qtHvyhVVOBIB#uhz znNjM}KETd(3ZtQ58RdCx-)F&*ly2`H=+`AEy%RbJtf^xn6%lspppn>9W>akm$ILQa z%|NmaZ!FDv2$`EN=qj$y3O-`gVxs1<_}Jg{!(!nG`@5sRj@HU6aHPg4Fk)Q`S89Tu z43y`s*1h&DggW7J$Ew~LlM`hcS@O(snx1LM&C7S8(CMn%JNNx3en(24uEM4Tb+n?b z`RbZ!MM#IL{;5|@8$Bjo1vHwo{Q(pRq07+<*+MW7h}_F$pg9CM$SR7badEP<2a#&a zgr~xB9@=h$75*CvODWQ>&rWzWfyei7wwEVpwKd_j1>^%D)97_Ho0mWg_lR~j;l%Y$Ed zx|x|GXIeCsN?90+RIMDGpk{w5y3R@XB53tuniEqzwZpsxD^c!i7kgM_Qx{;h5CmkZ}MWF^1ki%?a5wyFbY!vyIUI`TSV{5r8U!OCMn8f2@lvGqH^ zvHXE+DEDrIpUF$q6>-aN;WorQz+W^SW&h9OE<4OHe%-mv=%NglY`$7qWh3l%-Y!y=LMUZOP>p*X{b$XG%3D6VfE>bbb)UM840HeUW~Y4*e%1d3b_W zA(a{&^s&=z9XpOc@S+0mtyH-jVLk`uYB~lb#Nphe!5EIR%d4G}h*Lq$-``m6$q%XY zPS+}{zI`qoE+Zoh0{m=}%MtP3|0*KlKl&x%mmUM?**a<;F1;cy1Ad%JG?-g&f;#JP z(o-=W+E5SJn!2T`aT;YrN5nkx3#}DnkXCVAHkc;^gE#EA+B|xDOQw53T*49Cq3Ts? z#Nt(6B9K0J-gNUlR#dZE@~O&UhKJ`5iExs@js5*h0T0?Dussyry2d8J3!LeaaO523 zFI%|TLfD*u!dWb)m&d$ejVB{q2LuI8ll4Nb0R*4HrQIx+Lo3wQTzLy_c3e20&_sr; zxv36*SMgk9)3mmS3lY1{^0Yg}aAEDM6WD0#dc`hM(4ky7=;$Wm_ld}=qy{c-ze<_9 zK3cuy2_2OxH-gV3$$eQ$HamrN;XBc}R#1`}G^fY<=$5741R}dO8mDGoK)QxrJIwJG z<~m`+nqaBO#UWwC3`N51Uy&*fCmFM|a+qT_dljw!3-tjC{$5o^NT%C3j`YmjF9dNZ zv*?M=XSSF6?fhA7w&y^=pLFL)L@QmlmWL_*gPaj-FPx&DE61hnP+yWGl{@~`uGth_ zQst-KAE4sASeT7K`T&-?M+|ky(#-ql<)I33Vp|dN+F|%o^{8FpCj#zG%*M0r@FiYZ zaAH@Ty-W|0=%JCEj&*Ef1t_f~T4OqhQBdCsn&wzNV=#$|CZ?uJtzA>DKoOHuYZ(P! zEo>sxNhAf+ZX;G<(HFY8A)ot4xa-6%*?vQDNrQWoXSVYN0IYHBSo8mm|`ZLp>8v+pdN4D>I`&_kG~++RKW1dZS8{n V=Wl=g=f9r+{2LX@@e_KM0RTV?=;Z(a literal 0 HcmV?d00001 diff --git a/dragon/misc/CMakeLists.txt b/dragon/misc/CMakeLists.txt new file mode 100644 index 00000000..59d9ee15 --- /dev/null +++ b/dragon/misc/CMakeLists.txt @@ -0,0 +1,8 @@ +install(PROGRAMS dragonplayer.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) +install(FILES dragonplayer-opendvd.desktop DESTINATION ${DATA_INSTALL_DIR}/solid/actions ) +install(FILES dragonplayer_part.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +install(FILES dragonplayerrc DESTINATION ${CONFIG_INSTALL_DIR}) +install(FILES dragonplayer_play_dvd.desktop DESTINATION ${SERVICES_INSTALL_DIR}/ServiceMenus) +install(FILES dragonplayerui.rc dragonlogo.png DESTINATION ${DATA_INSTALL_DIR}/dragonplayer) + +kde4_install_icons(${ICON_INSTALL_DIR}) diff --git a/dragon/misc/dragonlogo.png b/dragon/misc/dragonlogo.png new file mode 100644 index 0000000000000000000000000000000000000000..75532e801b8a4a46db7db858093bd366b73a7877 GIT binary patch literal 47792 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 Vg3y259eMx&002ovPDHLkV1ma5&P)IR literal 0 HcmV?d00001 diff --git a/dragon/misc/dragonplayer-opendvd.desktop b/dragon/misc/dragonplayer-opendvd.desktop new file mode 100644 index 00000000..5d4e1644 --- /dev/null +++ b/dragon/misc/dragonplayer-opendvd.desktop @@ -0,0 +1,58 @@ +[Desktop Entry] +X-KDE-Solid-Predicate=[ StorageVolume.ignored == false AND OpticalDisc.availableContent == 'Data|VideoDvd' ] +Type=Service +Actions=open; + +[Desktop Action open] +Name=Open with Video Player (Dragon Player) +Name[bg]=Отваряне с програма за видео (Dragon Player) +Name[bs]=Otvori programom Dragon Player +Name[ca]=Obre amb el reproductor de vídeo (Dragon Player) +Name[ca@valencia]=Obri amb el reproductor de vídeo (Dragon Player) +Name[cs]=Otevřít v přehrávači videa (Dragon) +Name[da]=Åbn med videoafspiller (Dragon Player) +Name[de]=Mit der Video-Wiedergabe öffnen (Dragon Player) +Name[el]=Άνοιγμα με πρόγραμμα αναπαραγωγής βίντεο (Dragon Player) +Name[en_GB]=Open with Video Player (Dragon Player) +Name[es]=Abrir con el reproductor de vídeo (Dragon Player) +Name[et]=Avamine videomängijas (Dragoni mängija) +Name[fi]=Avaa videosoittimeen (Dragon Player) +Name[fr]=Ouvrir avec le lecteur vidéo (Dragon Player) +Name[ga]=Oscail le seinnteoir físe (Dragon Player) +Name[gl]=Abrir co reprodutor de vídeo (Dragon Player) +Name[he]=פתח באמצעות נגן הוידאו (נגן Dragon) +Name[hu]=Megnyitás videolejátszóval (Dragon Player) +Name[ia]=Aperi con Video Player (Dragon Player) +Name[is]=Opna með vídeóspilara (Dragon spilarinn) +Name[it]=Apri con riproduttore video (Dragon Player) +Name[ja]=動画プレーヤー (Dragon Player) で開く +Name[kk]=Бейне (Dragon) ойнатқышында ашу +Name[km]=បើក​ជា​មួយ​កម្មវិធី​ចាក់​វីដេអូ (កម្មវិធី Dragon) +Name[ko]=동영상 재생기로 열기 (Dragon Player) +Name[lt]=Atverti su Video grotuvu (Dragon Player) +Name[mr]=व्हिडीओ प्लेयर मध्ये उघडा (ड्रेगोन प्लेयर) +Name[nb]=Åpne med filmspiller (Dragon Player) +Name[nds]=Mit Videoafspeler opmaken (Drakenspeler) +Name[nl]=Openen met videospeler (Dragon Player) +Name[nn]=Opna med filmspelar (Dragon Player) +Name[pa]=ਵਿਡੀਓ ਪਲੇਅਰ (ਡਰੈਗਨ ਪਲੇਅਰ) ਨਾਲ ਖੋਲ੍ਹੋ +Name[pl]=Otwórz w odtwarzaczu filmów (Dragon Player) +Name[pt]=Abrir com o Leitor de Vídeo (Dragon Player) +Name[pt_BR]=Abrir com o reprodutor de vídeo (Dragon Player) +Name[ro]=Deschide cu lectorul video (Dragon Player) +Name[ru]=Открыть в видеопроигрывателе (Dragon Player) +Name[sk]=Otvoriť pomocou prehrávača videa (Dragon) +Name[sl]=Odpri s predvajalnikom videa (Dragon Player) +Name[sr]=Отвори Змајевим плејером +Name[sr@ijekavian]=Отвори Змајевим плејером +Name[sr@ijekavianlatin]=Otvori Zmajevim plejerom +Name[sr@latin]=Otvori Zmajevim plejerom +Name[sv]=Öppna med videospelare (Dragon) +Name[tr]=Video Oynatıcı ile Aç (Dragon Player) +Name[ug]=سىن قويغۇچتا ئاچ (Dragon قويغۇچ) +Name[uk]=Відкрити у програвачі відео (Програвач Dragon) +Name[x-test]=xxOpen with Video Player (Dragon Player)xx +Name[zh_CN]=用视频播放器打开(Dragon Player) +Name[zh_TW]=以影像播放器(神龍播放器)開啟 +Exec=dragon --play-dvd +Icon=dragonplayer diff --git a/dragon/misc/dragonplayer.desktop b/dragon/misc/dragonplayer.desktop new file mode 100755 index 00000000..6c2edc5a --- /dev/null +++ b/dragon/misc/dragonplayer.desktop @@ -0,0 +1,109 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=Dragon Player +Name[bg]=Програма за видео Dragon +Name[bs]=Dragon Player +Name[ca]=Dragon Player +Name[ca@valencia]=Dragon Player +Name[cs]=Přehrávač Dragon +Name[da]=Dragon Player +Name[de]=Dragon Player +Name[el]=Dragon Player +Name[en_GB]=Dragon Player +Name[es]=Dragon Player +Name[et]=Dragoni mängija +Name[fi]=Dragon-soitin +Name[fr]=Dragon Player +Name[ga]=Dragon Player +Name[gl]=Dragon Player +Name[he]=נגן Dragon +Name[hu]=Dragon Player +Name[ia]=Dragon Player +Name[is]=Dragon spilari +Name[it]=Dragon Player +Name[ja]=Dragon Player +Name[kk]=Dragon ойнатқышы +Name[km]=កម្មវិធី​ចាក់ Dragon +Name[ko]=Dragon 플레이어 +Name[lt]=Dragon leistuvas +Name[mr]=ड्रेगोन प्लेयर +Name[nb]=Dragon Player +Name[nds]=Drakenspeler +Name[nl]=Dragon Player +Name[nn]=Dragon Player +Name[pa]=ਡਰੈਗਨ ਪਲੇਅਰ +Name[pl]=Dragon Player +Name[pt]=Dragon Player +Name[pt_BR]=Dragon Player +Name[ro]=Dragon Player +Name[ru]=Dragon Player +Name[sk]=Prehrávač Dragon +Name[sl]=Dragon Player +Name[sr]=Змајев плејер +Name[sr@ijekavian]=Змајев плејер +Name[sr@ijekavianlatin]=Zmajev plejer +Name[sr@latin]=Zmajev plejer +Name[sv]=Dragon videospelare +Name[tr]=Dragon Player +Name[ug]=Dragon قويغۇ +Name[uk]=Програвач Dragon +Name[x-test]=xxDragon Playerxx +Name[zh_CN]=Dragon Player +Name[zh_TW]=影音播放_神龍播放器 +GenericName=Video Player +GenericName[bg]=Възпроизвеждане на видео +GenericName[bs]=Video izvođač +GenericName[ca]=Reproductor de vídeo +GenericName[ca@valencia]=Reproductor de vídeo +GenericName[cs]=Přehrávač videa +GenericName[da]=Videoafspiller +GenericName[de]=Video-Wiedergabe +GenericName[el]=Πρόγραμμα αναπαραγωγής βίντεο +GenericName[en_GB]=Video Player +GenericName[es]=Reproductor de vídeo +GenericName[et]=Videomängija +GenericName[fi]=Videosoitin +GenericName[fr]=Lecteur vidéo +GenericName[ga]=Seinnteoir Físe +GenericName[gl]=Reprodutor de vídeo +GenericName[he]=נגן וידאו +GenericName[hu]=Videolejátszó +GenericName[ia]=Reproductor de Video +GenericName[is]=Vídeóspilari +GenericName[it]=Riproduttore video +GenericName[ja]=動画プレーヤー +GenericName[kk]=Бейне ойнатқышы +GenericName[km]=កម្មវិធី​ចាក់​វីដេអូ +GenericName[ko]=동영상 재생기 +GenericName[lt]=Video grotuvas +GenericName[mr]=व्हिडीओ प्लेयर +GenericName[nb]=Videospiller +GenericName[nds]=Videoafspeler +GenericName[nl]=Videospeler +GenericName[nn]=Filmspelar +GenericName[pa]=ਵਿਡੀਓ ਪਲੇਅਰ +GenericName[pl]=Odtwarzacz filmów +GenericName[pt]=Leitor de Vídeo +GenericName[pt_BR]=Reprodutor de vídeo +GenericName[ro]=Program de redare video +GenericName[ru]=Видеопроигрыватель +GenericName[sk]=Prehrávač videa +GenericName[sl]=Predvajalnik videov +GenericName[sr]=Видео плејер +GenericName[sr@ijekavian]=Видео плејер +GenericName[sr@ijekavianlatin]=Video plejer +GenericName[sr@latin]=Video plejer +GenericName[sv]=Videospelare +GenericName[tr]=Video Oynatıcı +GenericName[ug]=سىن قويۇش پروگراممىسى(سىن قويغۇ) +GenericName[uk]=Відеопрогравач +GenericName[x-test]=xxVideo Playerxx +GenericName[zh_CN]=视频播放器 +GenericName[zh_TW]=影像播放器 +Icon=dragonplayer +X-DocPath=dragonplayer/index.html +TryExec=dragon +Exec=dragon %u +MimeType=video/ogg;video/x-theora+ogg;video/x-ogm+ogg;video/x-ms-wmv;video/x-msvideo;video/x-ms-asf;video/x-matroska;video/mpeg;video/avi;video/quicktime;video/vnd.rn-realvideo;video/x-flic;video/mp4;video/x-flv;video/webm;application/x-cd-image; +Categories=Qt;KDE;AudioVideo;Player; diff --git a/dragon/misc/dragonplayer_part.desktop b/dragon/misc/dragonplayer_part.desktop new file mode 100644 index 00000000..8e0146ff --- /dev/null +++ b/dragon/misc/dragonplayer_part.desktop @@ -0,0 +1,107 @@ +[Desktop Entry] +Type=Service +Icon=dragonplayer +Name=Dragon Player Part +Name[bg]=Програма за видео Dragon +Name[bs]=Dio Dragon Player-a +Name[ca]=Part del Dragon Player +Name[ca@valencia]=Part del Dragon Player +Name[cs]=Komponenta přehrávače Dragon +Name[da]=Dragon Player-part +Name[de]=Dragon-Player-Komponente +Name[el]=Τμήμα Dragon Player +Name[en_GB]=Dragon Player Part +Name[es]=Parte de Dragon Player +Name[et]=Dragoni mängija komponent +Name[fi]=Dragon Player -osa +Name[fr]=Composant « Dragon Player » +Name[ga]=Comhpháirt Dragon Player +Name[gl]=Compoñente do Dragon Player +Name[he]=רכיב נגן Dragon +Name[hu]=Dragon Player objektum +Name[ia]=Parte de Dragon Player +Name[is]=Dragon spilaraeining +Name[it]=Componente Dragon Player +Name[ja]=Dragon Player コンポーネント +Name[kk]=Dragon ойнатқыш бөлшегі +Name[km]=ផ្នែក​នៃ​កម្មវិធី Dragon +Name[ko]=Dragon Player 부분 +Name[lt]=Dragon leistuvo dalis +Name[mr]=ड्रेगोन प्लेयर भाग +Name[nb]=Dragon Player-del +Name[nds]=Drakenspeler-Komponent +Name[nl]=Dragon Player-component +Name[nn]=Dragon Player-del +Name[pa]=ਡਰੈਗਨ ਪਲੇਅਰ ਭਾਗ +Name[pl]=Moduł odtwarzacza Dragon Player +Name[pt]=Componente do Dragon Player +Name[pt_BR]=Componente do Dragon Player +Name[ro]=Componentă Dragon Player +Name[ru]=Компонент проигрывателя Dragon Player +Name[sk]=Komponent prehrávača Dragon +Name[sl]=Sestavni del Dragon Player +Name[sr]=Змајев плејер део +Name[sr@ijekavian]=Змајев плејер дио +Name[sr@ijekavianlatin]=Zmajev plejer dio +Name[sr@latin]=Zmajev plejer deo +Name[sv]=Dragon videodelprogram +Name[tr]=Dragon Player Parçası +Name[ug]=Dragon قويغۇچ بۆلىكى +Name[uk]=Складова програвача Dragon +Name[x-test]=xxDragon Player Partxx +Name[zh_CN]=Dragon Player 部件 +Name[zh_TW]=神龍播放器部件 +Comment=Embeddable Video Player +Comment[bg]=Вградена програма за видео +Comment[bs]=Ugradivi video plejer +Comment[ca]=Reproductor de vídeo incrustat +Comment[ca@valencia]=Reproductor de vídeo incrustat +Comment[cs]=Vložený přehrávač videa +Comment[da]=Videoafspiller som kan indlejres +Comment[de]=Einbettbarer Videospieler +Comment[el]=Ενσωματώσιμο πρόγραμμα αναπαραγωγής βίντεο +Comment[en_GB]=Embeddable Video Player +Comment[es]=Reproductor de vídeo empotrable +Comment[et]=Põimitav videomängija +Comment[fi]=Upotettava videosoitin +Comment[fr]=Lecteur vidéo pouvant être embarqué +Comment[ga]=Seinnteoir Inleabaithe Físe +Comment[gl]=Reprodutor incrustábel de vídeo +Comment[he]=נגן וידאו מותמע +Comment[hu]=Beágyazható videolejátszó +Comment[ia]=Reproductor de Video Insertabile +Comment[is]=Ígræðanlegur vídeóspilari +Comment[it]=Lettore video integrabile +Comment[ja]=埋め込み可能な動画プレーヤー +Comment[kk]=Ендірілетін бейне ойнатқышы +Comment[km]=កម្មវិធី​ចាក់​វីដេអូ​ដែល​អាច​បង្កប់បាន +Comment[ko]=끼워넣을 수 있는 동영상 재생기 +Comment[lt]=Įdedamas video grotuvas +Comment[mr]=अंतर्भूत करण्यायोग्य व्हिडीओ प्लेयर +Comment[nb]=Innebyggbar filmspiller +Comment[nds]=Inbettbor Videoafspeler +Comment[nl]=Inbedbare videospeler +Comment[nn]=Innebygd filmspelar +Comment[pa]=ਇੰਬੈਡ ਕਰਨ ਯੋਗ ਵਿਡੀਓ ਪਲੇਅਰ +Comment[pl]=Osadzony odtwarzacz filmów +Comment[pt]=Leitor de Vídeo Incorporado +Comment[pt_BR]=Reprodutor de vídeo incorporado +Comment[ro]=Lector video încorporabil +Comment[ru]=Встраиваемый видеопроигрыватель +Comment[sk]=Vložiteľný prehrávač videa +Comment[sl]=Vgradni predvajalnik videov +Comment[sr]=Угнездиви видео плејер +Comment[sr@ijekavian]=Угњездиви видео плејер +Comment[sr@ijekavianlatin]=Ugnjezdivi video plejer +Comment[sr@latin]=Ugnezdivi video plejer +Comment[sv]=Inbäddningsbar videospelare +Comment[tr]=Gömülebilir Video Oynatıcı +Comment[ug]=سىڭدۈرۈشچان سىن قويغۇچ +Comment[uk]=Програвач відео +Comment[x-test]=xxEmbeddable Video Playerxx +Comment[zh_CN]=可嵌入的视频播放器 +Comment[zh_TW]=嵌入式影像播放器 +X-KDE-ServiceTypes=KParts/ReadOnlyPart +X-KDE-Library=dragonpart +InitialPreference=9 +MimeType=video/ogg;video/x-theora+ogg;video/x-ogm+ogg;video/x-ms-wmv;video/x-msvideo;video/x-ms-asf;video/x-matroska;video/mpeg;video/avi;video/quicktime;video/vnd.rn-realvideo;video/x-flic;video/mp4;video/x-flv;video/webm; diff --git a/dragon/misc/dragonplayer_play_dvd.desktop b/dragon/misc/dragonplayer_play_dvd.desktop new file mode 100644 index 00000000..1ea8da86 --- /dev/null +++ b/dragon/misc/dragonplayer_play_dvd.desktop @@ -0,0 +1,59 @@ +[Desktop Entry] +Type=Service +Actions=Play; +X-KDE-ServiceTypes=KonqPopupMenu/Plugin,media/dvdvideo +X-KDE-Priority=TopLevel + +[Desktop Action Play] +Name=Play DVD with Dragon Player +Name[bg]=Възпроизвеждане на DVD с Dragon +Name[bs]=Emituj DVD Dragon Playerom +Name[ca]=Reprodueix DVD amb el Dragon Player +Name[ca@valencia]=Reprodueix DVD amb el Dragon Player +Name[cs]=Přehrát DVD pomocí přehrávače Dragon +Name[da]=Afspil dvd med Dragon Player +Name[de]=DVD mit Dragon Player abspielen +Name[el]=Αναπαραγωγή DVD με Dragon Player +Name[en_GB]=Play DVD with Dragon Player +Name[es]=Reproducir DVD con Dragon Player +Name[et]=DVD esitamine Dragoni mängijaga +Name[fi]=Toista DVD Dragon-soittimessa +Name[fr]=Lire un DVD avec Dragon Player +Name[ga]=Seinn DVD le Dragon Player +Name[gl]=Reproducir o DVD co Dragon Player +Name[he]=נגן DVD באמצעות נגן Dragon +Name[hu]=DVD lejátszása a Dragon Playerrel +Name[ia]=Reproduce DVD con Dragon Player +Name[is]=Spila DVD með Dragon spilaranum +Name[it]=Riproduci DVD con Dragon Player +Name[ja]=Dragon Player で DVD を再生 +Name[kk]=DVD-ні Dragon ойнатқышында орындау +Name[km]=ចាក់​ឌីវីឌី​​ជា​មួយ​កម្មវិធី Dragon +Name[ko]=Dragon Player로 DVD 열기 +Name[lt]=Leisti DVD su Dragon Player +Name[mr]=ड्रेगोन प्लेयर वर DVD प्ले करा +Name[nb]=Spill DVD med Dragon Player +Name[nds]=DVD mit den Drakenspeler afspelen +Name[nl]=DVD met Dragon Player afspelen +Name[nn]=Spel DVD med Dragon Player +Name[pa]=ਡਰੈਗਨ ਪਲੇਅਰ ਨਾਲ ਡੀਵੀਡੀ ਚਲਾਓ +Name[pl]=Odtwórz DVD za pomocą Dragon Playera +Name[pt]=Reproduzir o DVD com o Dragon Player +Name[pt_BR]=Reproduzir o DVD com o Dragon Player +Name[ro]=Redare DVD cu Dragon player +Name[ru]=Воспроизведение DVD в Dragon Player +Name[sk]=Prehrať DVD pomocou prehrávača Dragon +Name[sl]=Predvajaj DVD v programu Dragon Player +Name[sr]=Пусти ДВД Змајевим плејером +Name[sr@ijekavian]=Пусти ДВД Змајевим плејером +Name[sr@ijekavianlatin]=Pusti DVD Zmajevim plejerom +Name[sr@latin]=Pusti DVD Zmajevim plejerom +Name[sv]=Spela dvd med Dragon videospelare +Name[tr]=DVD Ortamını Dragon Player ile Oynat +Name[ug]=DVD نى Dragon قويغۇچتا قويىدۇ +Name[uk]=Грати DVD програвачем Dragon +Name[x-test]=xxPlay DVD with Dragon Playerxx +Name[zh_CN]=用 Dragon Player 播放 DVD +Name[zh_TW]=用神龍播放器播放 DVD +Icon=dragonplayer +Exec=dragon --play-dvd %u diff --git a/dragon/misc/dragonplayerrc b/dragon/misc/dragonplayerrc new file mode 100644 index 00000000..481396fc --- /dev/null +++ b/dragon/misc/dragonplayerrc @@ -0,0 +1,14 @@ +[KFileDialog Settings] +ShowPreviews=false + +[MainWindow] +Height=400 +Width=600 + +[MainWindow Toolbar dvdToolBar] +Hidden=true +IconText=IconTextRight +Index=0 + +[MainWindow Toolbar mainToolBar] +Index=1 diff --git a/dragon/misc/dragonplayerui.rc b/dragon/misc/dragonplayerui.rc new file mode 100644 index 00000000..874951ed --- /dev/null +++ b/dragon/misc/dragonplayerui.rc @@ -0,0 +1,38 @@ + + + +

&Play + + + + + + + + + + + &Settings + + + + + + + + + + + + + + + +Main Toolbar + + + + + + + diff --git a/dragon/misc/hi128-app-dragonplayer.png b/dragon/misc/hi128-app-dragonplayer.png new file mode 100644 index 0000000000000000000000000000000000000000..86593552ab64c80e690810be77a810e84fe0006a GIT binary patch literal 17342 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 VL#EfDQ2GD>002ovPDHLkV1g)2`Yr$f literal 0 HcmV?d00001 diff --git a/dragon/misc/hi48-app-dragonplayer.png b/dragon/misc/hi48-app-dragonplayer.png new file mode 100644 index 0000000000000000000000000000000000000000..0d8a948bb629bd8def34a43ff3cb9cc4856adcff GIT binary patch literal 4091 zcmV(^b8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H14|qvLK~#9!&6o*roaK4P|L^-jq%;gYn3A?Lv?O&nLZ&4S#5Lu#!PwaNzHeF5 z>e%bM-m99SpYHzvjQsYBi(%|DNah-cKu{l*0d0nc8t}Lp!e>;uNAdN1C^I z#gFb<{@g#}i-~I2y!QJym%skjzrF|{1+D)xfXi=x@^7x5f8k>XvfekF7JRpG+4p~P zbmsg!#^8UJk#lo3hl~Ap?(Q78=JmH%%?3bDJ*Km$>y|t*N*VE$m&|IPz2?(H$QGEL zN!Qz6cdjA~y3PZz52u#+?$4gMmPV`MV`2^1e(cZ{*8^{y(+S-5*b9%(?wEeyj_WU) z&A5PLFvKhyR0(i*z(AyW)#L@=-Zf*!{3QOnk3rLDV>_YL@70L}wQ!Jv$Ywu0h*D~nf50I(J(>7u2J=R2coTgQzV4Mhm@ zu3G`Ea{_SR)Bo_Sq~^V=-d~G=rVx!+fq8wqD@P`JE9|HHFrB(%zj6xEV2rIr|;_QXLZf|e5q1CbUTTeNe%#^eScg#uz zo`*3;^qd`7*M9oSvrj;4&K(cO>pR-lO|EM- zYb}B>K_Dy&B?T1ZoQV@BikWxZaEX_ydC*b}GaAOB;0IuCkuAIL;r(ZIh3;SR^ETN! zZpOM-*QuWU2i_0z-p%V@eWDXXn!990WmEg8O(R;GTi*W5=g~NB47zO!C;_4YAi3Dn zo1Z9Z6E9a$YNVoZG*&la=fT4;d~YWJbyfim=lw;6k2Y@19^L=#^}qh}&vBBjxc3L` z#PF&>ZeQo0)X~^)}n`(FP(Pz+-EYGjQRIVzK3DCu8$hS1Z4rBILsJ@sPOZ7O-4;oY9skDcg;y;2Xze;2d3*)_tCZ`VSRo=o6Ap+} zNZ3(KX>3DLDCB#yi1{TcC0Tt=0p{Iv&y>b#Ggmd8cV6Ag&pZWE7yxlh7(U0z@(P6$ z-H%8>C^#^Xi6t?%`FtG6W+4XpU`x6F;blMUht@ecM(5u1z|#7OlRp-gRrmT+k3q5` zgku1w_=~rg%fnCs2p2H5Q%)2e6Q&~1*op3YMF6V2K4*ObQx-3d)uiiICYzhD+P-N6 zO8d4##BC_0V0i-6=a9AmX;PRT2O$K7TmeJ@2nC|VLQ}dPnNh9kaJGl2{$5y0fr6fW zfT=hC@x_UT`jy-?8g~B6O0bv(k#Nf4sT;x!Ig~*V#vKqLVfaFW7`?e{UyTA_+ZGyX zYUR{fb7)_wNZf2U;(iHQhg|o>33OIgh-gi_b@Ag?D*jIY@goggn?HnK9YK(Ez$#1# zDL@Sq913AFf^cXeOjltUy@Bh&u?^JK)nV3Umz78&j=bJ|W1w&7_p6^i`skzltN@I= z=9sDTQ`_~p%dO<_^Jqa)B@)m)V3g~0w_h8aLSXhiXp;E3q&dwu}#!x zGMGAj8q&?9D~kgI2)d8K5#`KTuf$)3))`-+_IdLzww%~+M6N3(Opc(YVmM%V$5HTL z83rT;l$7B5XFvkA z=FFK-rFxANN|n0Xy5ki(oKs#8VH==DAC?4&Wgt`%30DKw#mj(ZzY8-NL)xk4t!)W= z`n1Uy-B^!h=^B0Sp-GN>GRM!qX3g<%({*e{8CZk_`2(U8New6h=>WQ0~*D! zygI&q%{tN#S?QD~V5BP1dLTf$tr_iA^Y;yK>)Oy^;sewtm~D zOAZWVuV)pQP8w6mcXNj@;1+eb0MS68DxjE9GZNcr1nDu2D7h}&Vi8ahL`oP^a7XgH zhr^#S0A`w_M%eakO6#QHN$t$>)$iTAXI|&= z&RcesyxDz#(G})#%YraVj=AA%^oV{b6dOJ+`a-Ysbc66pZhUC^v|a(m{_4um9QMr;G6jP-##dxFi_>t z!%NP5)2v^^W?TRfSpK4Yn`sJRTPcK=x@Xr89oTT|zT{RVo zFQbZFsGw*O6f7P^SJ5qZoDu?sVJRpD%i_EWK<;czz|yWO6biEk!*6nfu|mqfe&`$5 zE(IVkq?>NK$%{Si+zA6CGPHT6pUi16MH}>_nF-7irCNf z8ASZ1&C-**t%`YpZxo_Z8wCV z3zyz_W5Iwz@3CztWDjCg>r}2-sVide#vR)>zbvHs z-TUvp|MHqOYxozv2IARlcKr4|yXQAlrLMj1^2;{>P&^dBabiw=rl}pH$IiI5Va#Nh zmV;qA6_x1Uw;#4sg^^=B00j_Apmdc&fQJD>!$9Yrbtv^8M@w@PoJbt$u@|E@(+Jay zXxpf78gE8yvoWH<8rr6R>%i{KxAgV(UjpDeUlc%ozI9VuZB5PXmtK6y`r$icz?iw< z8~2n0)s1Q^uf@r#sc%MKZy%0#9xexoS5Q>M;*d&$bB?12cY>NGSaATQ+yF9e4R-9> zi^;K(sHtr%ub2>0Tf+(hFHlf4f@o?q?di+UJ)Ki1KljqFKL^0mW*8FZ(pdA`S9JAo z;bn`EtgeM$%pn-j^UuX^0e5yaL_yG$0cj5S^cVJo?W^5y*8$xjb0uHLQbtMQ9azVc`fPzHe_Jc-J z3PDtczAODw1o=XQSO%qbp~wg!4wOKyP#SzwFztYGu#oHUtXx^3D`VwmKxio6)iAE7 z0^d?BG*u|13OK+*O1Xv*0VMdB2hgD?0At357Zagao*1EpLIJGbphcU|2$4`>pzLgx zgq$f(AbWZTC>H`!N^pMS;^f-wgmq3qSP+5-K7M z!puN@8GzyNmbRnAZB;}}Fl69R7a%fAd0d*x^t!qYj|-&*sQ@7bl+=|E!f;3?Ai1t37fQhZ zFgR6B!4wTR2-D`iB*NteclR?x92{z-(jZc4eNg~4WbEC$x4W^q`JL)&IZ3hNPUao! z5QcT7Z$~LhR4WsDe+N5da%FWM0b}KMop?uR5J{jPGziy4e?JK576$o1peXG-DhERw zJQ$$m(Wo>O_Vf?*{?Va)z83)bvLEUR07Ki>j%Y`k8IZmp?QHnLShHj;>d3X*0K zNV*Yemj<+Tu*K*I7}-oSF4Ice^z=CP*qg(EwkT>G)E-47X)uXE7ED4&NJ3Jnq>@^1 zRo&(6|Nr-Q&b?51>H+MDnq>N`y!G94&tJ>?e((4F=bpM@N-6vY3Z0OsXNTYtB;F)A z&Y`&Kru*8j{m5r;!RduY$P?yo+qG>Cx9Yc8~~Y;zv=k%RsVKY#d__|2gR-eUN7z^OosJ$_;kH$$)_z!QL!5F~&XV-Q?}3KSFosU(yXP$DSi z&5Me7gK%SfT|+v9LUR_4xg7dWoy z9}#|o?8(k=9xBXjv(vL$Av<#j8d8u<0ihfGvH2s;=DS~a2f6*GcX;{cW!Xd=EvY7W zegM~TP|)H(J7)w~`-N{jnroi+msO9m^t2_IJ!d%z?Hw5HJ6;vu=#`rv_{5B#th=Sf zt{llae*ZJoP8V*w4ndZdOhU1EOlYyscfa-OFW{88>~o*fOG)#Qw82<9kwSa20LyXV zxwil3pZrYkIU&Hk|6{|}_=?5vwP!TJp4tK=Q*a!i+!IH3%0zk2uJ1kg6zai`4NYlF zq~=eB1}7UpCN)UYfMgmJI8!u2=C8c`gQ!P&Z2y0oGHYR1LgQ$s>g!qpmTg1x-2JG> zS)BoQ|HF@-O)fw0E!C2Tm-qKT4tUb;JF=Bq<=dWq?8|#_N?g8n?X;HjySJzE`GUQF zAG-2!oJ=^-IiN`aMPQIhaTR^tTmI~QD`Jb6-8OXc5T>`cV_u;P7Fpl3t4IjB?W_y% zf!n^^Sh)CYD~~*X0_77Usyx{HBF2mF-?HH=FJe+$dFP!|r!Bf*M?*_<)32U-3ih*G z(Y^Xg9OD5DAprFn0;Ynt8*jW(t4>Y+bGuvOnrTq)HzJwHVR-ZeBsm}^++Al~04JWf zV{A~eBRjVrmsa`a?LT;66J95-yYueFvzIL2RA_H+{NX?S6Y$DGgi(3%0A|mhgX1p` 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 literal 0 HcmV?d00001 diff --git a/dragon/misc/ox22-actions-player-volume-muted.png b/dragon/misc/ox22-actions-player-volume-muted.png new file mode 100644 index 0000000000000000000000000000000000000000..d071eea3cf2003100595278b3ebb9da1083c95b5 GIT binary patch literal 1081 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 W?*4hz{HM~*KmP|hE#BrETL1tO65!|n literal 0 HcmV?d00001 diff --git a/dragon/src/FAQ b/dragon/src/FAQ new file mode 100644 index 00000000..5cefe5cd --- /dev/null +++ b/dragon/src/FAQ @@ -0,0 +1,13 @@ +================================================================================ +FAQ +================================================================================ +This FAQ regards the source code and its layout. + + +Q: Why reimplement code in the part rather than consolidate it with the app? +A: Mainly to make the application start faster, loading a part is not free. But + also to make the part start faster as it has much lower requirements. + I admit the maintainance consequences aren't pretty, but the part has very + little code, so theoretically it should be easy to keep bug fixes in sync. + +=========1=========2=========3=========4=========5=========6=========7=========8 \ No newline at end of file diff --git a/dragon/src/app/CMakeLists.txt b/dragon/src/app/CMakeLists.txt new file mode 100644 index 00000000..e94b6276 --- /dev/null +++ b/dragon/src/app/CMakeLists.txt @@ -0,0 +1,56 @@ +set(videoplayer_common_SRCS + theStream.cpp + actions.cpp + videoWindow.cpp ) + +set(videoplayer_app_SRCS + ${videoplayer_common_SRCS} + analyzer/analyzerBase.cpp + analyzer/blockAnalyzer.cpp + analyzer/fht.cpp + audioView2.cpp + stateChange.cpp + textItem.cpp + playDialog.cpp + discSelectionDialog.cpp + adjustSizeButton.cpp + fullScreenToolBarHandler.cpp + playlistFile.cpp + main.cpp + playerApplication.cpp + timeLabel.cpp + mainWindow.cpp + recentlyPlayedList.cpp + loadView.cpp + ../mpris2/mpris2.cpp + ../mpris2/mediaplayer2.cpp + ../mpris2/mediaplayer2player.cpp ) + +kde4_add_ui_files(videoplayer_app_SRCS videoSettingsWidget.ui loadView.ui audioView2.ui) + +kde4_add_executable(dragon ${videoplayer_app_SRCS} ) + +target_link_libraries(dragon + ${KDE4_KIO_LIBS} + ${KDE4_KDEUI_LIBS} + ${KDE4_PHONON_LIBS} + ${KDE4_SOLID_LIBS} + ${KDE4_PLASMA_LIBS} ) + +install(TARGETS dragon ${INSTALL_TARGETS_DEFAULT_ARGS}) + +set(videoplayer_part_SRCS + ${videoplayer_common_SRCS} + part.cpp + partToolBar.cpp ) + +kde4_add_plugin(dragonpart ${videoplayer_part_SRCS}) + +target_link_libraries(dragonpart + ${KDE4_KPARTS_LIBS} + ${KDE4_KIO_LIBS} + ${KDE4_KDEUI_LIBS} + ${KDE4_PHONON_LIBS} + ${KDE4_SOLID_LIBS}) + +install(TARGETS dragonpart DESTINATION ${PLUGIN_INSTALL_DIR} ) diff --git a/dragon/src/app/actions.cpp b/dragon/src/app/actions.cpp new file mode 100644 index 00000000..6e06f049 --- /dev/null +++ b/dragon/src/app/actions.cpp @@ -0,0 +1,72 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ +#include "actions.h" +#include "theStream.h" + +#include +#include +#include + +#include "videoWindow.h" + +Dragon::PlayAction::PlayAction( QObject *receiver, const char *slot, KActionCollection *ac ) + : KDualAction( ac ) +{ + setObjectName( QLatin1String( "play" ) ); + + setInactiveGuiItem( KGuiItem( i18n( "Play" ), KIcon( QLatin1String( "media-playback-start" ) ) ) ); + setActiveGuiItem( KGuiItem( i18n( "Pause" ), KIcon( QLatin1String( "media-playback-pause" ) ) ) ); + setAutoToggle( false ); + setShortcut( Qt::Key_Space ); + ac->addAction( objectName(), this ); + connect( this, SIGNAL(triggered(bool)), receiver, slot ); +} + +void +Dragon::PlayAction::setPlaying( bool playing ) +{ + setActive( playing ); +} + +///////////////////////////////////////////////////// +///Codeine::VolumeAction +//////////////////////////////////////////////////// +Dragon::VolumeAction::VolumeAction( QObject *receiver, const char *slot, KActionCollection *ac ) + : KToggleAction( i18nc( "Volume of sound output", "Volume"), ac ) +{ + setObjectName( QLatin1String( "volume" ) ); + setIcon( KIcon( QLatin1String( "player-volume" ) ) ); + setShortcut( Qt::Key_V ); + ac->addAction( objectName(), this ); + connect( this, SIGNAL(triggered(bool)), receiver, slot ); + connect( engine(), SIGNAL(mutedChanged(bool)), this, SLOT(mutedChanged(bool)) ); +} + +void +Dragon::VolumeAction::mutedChanged( bool mute ) +{ + if( mute ) + setIcon( KIcon( QLatin1String( "player-volume-muted" ) ) ); + else + setIcon( KIcon( QLatin1String( "player-volume" ) ) ); +} + +#include "actions.moc" diff --git a/dragon/src/app/actions.h b/dragon/src/app/actions.h new file mode 100644 index 00000000..47d87d2f --- /dev/null +++ b/dragon/src/app/actions.h @@ -0,0 +1,54 @@ +/*********************************************************************** + * Copyright 2004 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYERACTIONS_H +#define DRAGONPLAYERACTIONS_H + +#include //baseclass +#include //baseclass +#include //convenience + +namespace Dragon +{ + KActionCollection *actionCollection(); ///defined in mainWindow.cpp, part.cpp + QAction *action( const char* ); ///defined in mainWindow.cpp, part.cpp + inline KToggleAction *toggleAction( const char *name ) { return (KToggleAction*)action( name ); } + + class PlayAction : public KDualAction + { + Q_OBJECT + public: + PlayAction( QObject *receiver, const char *slot, KActionCollection* ); + void setPlaying( bool playing ); + }; + + class VolumeAction : public KToggleAction + { + Q_OBJECT + public: + VolumeAction( QObject *receiver, const char *slot, KActionCollection* ); + private slots: + void mutedChanged( bool ); + }; +} + +#endif + diff --git a/dragon/src/app/adjustSizeButton.cpp b/dragon/src/app/adjustSizeButton.cpp new file mode 100644 index 00000000..15011ef5 --- /dev/null +++ b/dragon/src/app/adjustSizeButton.cpp @@ -0,0 +1,160 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "adjustSizeButton.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" +#include "theStream.h" +#include "videoWindow.h" //videoWindow() + + +QString i18n( const char *text ); + +namespace Dragon +{ + AdjustSizeButton::AdjustSizeButton( QWidget *parent ) + : QFrame( parent ) + , m_counter( 0 ) + , m_stage( 1 ) + , m_offset( 0 ) + { + parent->installEventFilter( this ); + + setPalette( QApplication::palette() ); //videoWindow has different palette + setFrameStyle( QFrame::Plain | QFrame::Box ); + + m_preferred = new KPushButton( KGuiItem( i18n("Preferred Scale"), QLatin1String( "viewmag" ) ), this ); + connect( m_preferred, SIGNAL(clicked()), Dragon::mainWindow(), SLOT(adjustSize()) ); + connect( m_preferred, SIGNAL(clicked()), SLOT(deleteLater()) ); + + m_oneToOne = new KPushButton( KGuiItem( i18n("Scale 100%"), QLatin1String( "viewmag1" ) ), this ); + connect( m_oneToOne, SIGNAL(clicked()), (QObject*)videoWindow(), SLOT(resetZoom()) ); + connect( m_oneToOne, SIGNAL(clicked()), SLOT(deleteLater()) ); + + QBoxLayout *hbox = new QHBoxLayout( this ); + hbox->setMargin( 8 ); + hbox->setSpacing( 6 ); + QBoxLayout *vbox = new QVBoxLayout( this ); + hbox->addLayout( vbox ); + vbox->addWidget( new QLabel( i18n( "Adjust video scale?" ), this ) ); + vbox->addWidget( m_preferred ); + vbox->addWidget( m_oneToOne ); + hbox->addWidget( m_thingy = new QFrame( this ) ); + + m_thingy->setFixedWidth( fontMetrics().width( QLatin1String( "X" ) ) ); + m_thingy->setFrameStyle( QFrame::Plain | QFrame::Box ); + { + QPalette palette; + QPalette thisPalette = this->palette(); + palette.setColor( m_thingy->backgroundRole(), palette.color( QPalette::Window ).darker() ); + m_thingy->setPalette(palette); + } + + QEvent e( QEvent::Resize ); + eventFilter( 0, &e ); + + adjustSize(); + show(); + + m_timerId = startTimer( 5 ); + } + + void + AdjustSizeButton::timerEvent( QTimerEvent* ) + { + QFrame *&h = m_thingy; + + switch( m_stage ) + { + case 1: //raise + move(); + m_offset++; + + if( m_offset > height() ) + killTimer( m_timerId ), + m_timerId = startTimer( 40 ), + m_stage = 2; + + break; + + case 2: //fill in pause timer bar + if( m_counter < h->height() - 3 ) + QPainter( h ).fillRect( 2, 2, h->width() - 4, m_counter + , palette().color( QPalette::Active, QPalette::Highlight ) ); + + if( !testAttribute(Qt::WA_UnderMouse) ) + m_counter++; + + if( m_counter > h->height() + 5 ) //pause for 360ms before lowering + m_stage = 3, + killTimer( m_timerId ), + m_timerId = startTimer( 6 ); + + break; + + case 3: //lower + if( testAttribute(Qt::WA_UnderMouse) ) { + m_stage = 1; + m_counter = 0; + m_thingy->repaint(); + break; } + + m_offset--; + move(); + + if( m_offset < 0 ) + deleteLater(); + } + } + + bool + AdjustSizeButton::eventFilter( QObject */*o*/, QEvent *e ) + { + if( e->type() == QEvent::Resize ) { + const QSize preferredSize = TheStream::profile().readEntry( "Preferred Size", QSize() ); + const QSize defaultSize = TheStream::defaultVideoSize(); + const QSize parentSize = parentWidget()->size(); + + m_preferred->setEnabled( preferredSize.isValid() && parentSize != preferredSize && defaultSize != preferredSize ); + m_oneToOne->setEnabled( defaultSize != parentSize ); + + move(); + + if( !m_preferred->isEnabled() && !m_oneToOne->isEnabled() && m_counter == 0 ) + deleteLater(); + } + + return false; + } +} +#include "adjustSizeButton.moc" diff --git a/dragon/src/app/adjustSizeButton.h b/dragon/src/app/adjustSizeButton.h new file mode 100644 index 00000000..415ff545 --- /dev/null +++ b/dragon/src/app/adjustSizeButton.h @@ -0,0 +1,59 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_ADJUST_SIZE_BUTTON_H +#define DRAGONPLAYER_ADJUST_SIZE_BUTTON_H + +#include + +class QEvent; +class QTimerEvent; + +namespace Dragon +{ + class AdjustSizeButton : public QFrame + { + Q_OBJECT + int m_counter; + int m_stage; + int m_offset; + int m_timerId; + + QWidget *m_preferred; + QWidget *m_oneToOne; + + QFrame *m_thingy; + + public: + AdjustSizeButton( QWidget *parent ); + + private: + virtual void timerEvent( QTimerEvent* ); + virtual bool eventFilter( QObject*, QEvent* ); + + inline void move() + { + QWidget::move( parentWidget()->width() - width(), parentWidget()->height() - m_offset ); + } + }; +} + +#endif diff --git a/dragon/src/app/analyzer/analyzerBase.cpp b/dragon/src/app/analyzer/analyzerBase.cpp new file mode 100644 index 00000000..2273f74b --- /dev/null +++ b/dragon/src/app/analyzer/analyzerBase.cpp @@ -0,0 +1,202 @@ +/* + Copyright 2003 by Max Howell + Copyright 2009 by Martin Sandsmark + LOL GPL +*/ + +#include "analyzerBase.h" +#include //interpolate() + +#include //event() +#include + +#include + + +// INSTRUCTIONS Base2D +// 1. do anything that depends on height() in init(), Base2D will call it before you are shown +// 2. otherwise you can use the constructor to initialise things +// 3. reimplement analyze(), and paint to canvas(), Base2D will update the widget when you return control to it +// 4. if you want to manipulate the scope, reimplement transform() +// 5. for convenience are pre-included +// TODO make an INSTRUCTIONS file +//can't mod scope in analyze you have to use transform + + +Analyzer::Base::Base(QWidget *parent, uint scopeSize) + : QWidget(parent) + , m_fht(new FHT(scopeSize)) +{} + +void Analyzer::Base::transform(QVector &scope ) //virtual +{ + //this is a standard transformation that should give + //an FFT scope that has bands for pretty analyzers + + //NOTE resizing here is redundant as FHT routines only calculate FHT::size() values + //scope.resize( m_fht->size() ); + + float *front = static_cast( &scope.front() ); + + float* f = new float[ m_fht->size() ]; + m_fht->copy( &f[0], front ); + m_fht->logSpectrum( front, &f[0] ); + m_fht->scale( front, 1.0 / 20 ); + + scope.resize( m_fht->size() / 2 ); //second half of values are rubbish + delete [] f; +} + +void Analyzer::Base::drawFrame(const QMap > &thescope) +{ + if (thescope.isEmpty()) + return; + + static QVector scope( 512 ); + int i = 0; + + for( uint x = 0; (int)x < m_fht->size(); ++x ) + { + if (thescope.size() == 1) { // Mono + scope[x] = double(thescope[Phonon::AudioDataOutput::LeftChannel][x]); + } else { // Anything > Mono is treated as Stereo + scope[x] = double(thescope[Phonon::AudioDataOutput::LeftChannel][x] + + thescope[Phonon::AudioDataOutput::RightChannel][x]) + / (2*(1<<15)); // Average between the channels + } + i += 2; + } + + transform(scope); + analyze(scope); + + scope.resize( m_fht->size() ); + + update(); +} + +int Analyzer::Base::resizeExponent( int exp ) +{ + if ( exp < 3 ) + exp = 3; + else if ( exp > 9 ) + exp = 9; + + if ( exp != m_fht->sizeExp() ) { + delete m_fht; + m_fht = new FHT( exp ); + } + return exp; +} + +int Analyzer::Base::resizeForBands(int bands) +{ + int exp; + if ( bands <= 8 ) + exp = 4; + else if ( bands <= 16 ) + exp = 5; + else if ( bands <= 32 ) + exp = 6; + else if ( bands <= 64 ) + exp = 7; + else if ( bands <= 128 ) + exp = 8; + else + exp = 9; + + resizeExponent(exp); + return m_fht->size() / 2; +} + +void Analyzer::Base::paused() //virtual +{} + +void Analyzer::Base::demo() //virtual +{ + static int t = 201; //FIXME make static to namespace perhaps +// qDebug() << Q_FUNC_INFO << t; + + if( t > 300 ) t = 1; //0 = wasted calculations + if( t < 201 ) + { + QVector s( 512 ); + + const double dt = double(t) / 200; + for(int i = 0; i < s.size(); ++i) + s[i] = dt * (sin( M_PI + (i * M_PI) / s.size() ) + 1.0); + + analyze(s); + } + else analyze(QVector( 1, 0)); + + ++t; +} + + + +Analyzer::Base2D::Base2D(QWidget *parent, uint scopeSize) + : Base(parent, scopeSize) +{ + QTimer::singleShot(0, this, SLOT(init())); // needs to know the size + timer.setInterval(34); + timer.setSingleShot(false); + connect(&timer, SIGNAL(timeout()), this, SLOT(demo())); + timer.start(); +} + +void Analyzer::Base2D::resizeEvent(QResizeEvent *e) +{ + QWidget::resizeEvent(e); + + m_canvas = QPixmap(size()); + m_canvas.fill(Qt::transparent); + + eraseCanvas(); //this is necessary, no idea why. but I trust mxcl. +} + +void Analyzer::Base2D::paintEvent(QPaintEvent*) +{ + if(m_canvas.isNull()) + return; + + QPainter painter(this); + painter.drawPixmap(rect(), m_canvas); +} + +void Analyzer::interpolate( const QVector &inVec, QVector &outVec ) //static +{ + double pos = 0.0; + const double step = (double)inVec.size() / outVec.size(); + + for (int i = 0; i < outVec.size(); ++i, pos += step) + { + const double error = pos - std::floor( pos ); + const unsigned long offset = (unsigned long)pos; + + long indexLeft = offset + 0; + + if (indexLeft >= inVec.size()) + indexLeft = inVec.size() - 1; + + long indexRight = offset + 1; + + if (indexRight >= inVec.size()) + indexRight = inVec.size() - 1; + + outVec[i] = inVec[indexLeft ] * ( 1.0 - error ) + + inVec[indexRight] * error; + } +} + +void Analyzer::initSin( QVector &v, const uint size ) //static +{ + double step = ( M_PI * 2 ) / size; + double radian = 0; + + for ( uint i = 0; i < size; i++ ) + { + v.push_back( sin( radian ) ); + radian += step; + } +} diff --git a/dragon/src/app/analyzer/analyzerBase.h b/dragon/src/app/analyzer/analyzerBase.h new file mode 100644 index 00000000..594d62dd --- /dev/null +++ b/dragon/src/app/analyzer/analyzerBase.h @@ -0,0 +1,105 @@ +// Author: Max Howell , (C) 2004 +// Maintainer: Martin Sandsmark , (C) 2009 +// Copyright: See the COPYING file shipped with this distribution + +#ifndef ANALYZERBASE_H +#define ANALYZERBASE_H + +#ifdef __FreeBSD__ +#include +#endif + +#include "fht.h" //stack allocated and convenience +#include //stack allocated and convenience +#include //stack allocated +#include //baseclass +#include //included for convenience + +#include + +class QEvent; +class QPaintEvent; +class QResizeEvent; + + +namespace Analyzer { + +typedef std::vector Scope; + +class Base : public QWidget +{ + Q_OBJECT + +public slots: + void drawFrame(const QMap > &thescope); + +protected: + Base(QWidget*, uint = 7); + ~Base() { delete m_fht; } + + int resizeExponent(int); + int resizeForBands(int); + virtual void transform(QVector&); + virtual void analyze(const QVector&) = 0; + virtual void paused(); +public slots: + void demo(); +protected: + FHT *m_fht; +}; + + +class Base2D : public Base +{ +Q_OBJECT +public: + const QPixmap *canvas() const { return &m_canvas; } + +// private slots: +// void draw() { drawFrame(); bitBlt( this, 0, 0, canvas() ); } + + void enableDemo(bool enable) { enable ? timer.start() : timer.stop(); } + + +protected: + Base2D( QWidget*, uint scopeSize = 7); + + + QPixmap *canvas() { return &m_canvas; } + void eraseCanvas() { m_canvas.fill(Qt::transparent); } + + void paintEvent( QPaintEvent* ); + void resizeEvent( QResizeEvent* ); + + +protected slots: + virtual void init() {} + +private: + QPixmap m_canvas; + QTimer timer; +}; + +class Factory +{ + //Currently this is a rather small class, its only purpose + //to ensure that making changes to analyzers will not require + //rebuilding the world! + + //eventually it would be better to make analyzers pluggable + //but I can't be arsed, nor can I see much reason to do so + //yet! +public: + static QWidget* createAnalyzer(QWidget*); + static QWidget* createPlaylistAnalyzer(QWidget *); +}; + + +void interpolate(const QVector&, QVector&); +void initSin(QVector&, const uint = 6000); + +} //END namespace Analyzer + +using Analyzer::Scope; + +#endif diff --git a/dragon/src/app/analyzer/blockAnalyzer.cpp b/dragon/src/app/analyzer/blockAnalyzer.cpp new file mode 100644 index 00000000..d51dcfcc --- /dev/null +++ b/dragon/src/app/analyzer/blockAnalyzer.cpp @@ -0,0 +1,423 @@ +/**************************************************************************************** + * Copyright (c) 2003-2005 Max Howell * + * Copyright (c) 2005 Mark Kretschmann * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Pulic License for more details. * + * * + * You should have received a copy of the GNU General Public License along with * + * this program. If not, see . * + ****************************************************************************************/ + +#include "blockAnalyzer.h" + +#include + +#include +#include //paletteChange() +#include //mousePressEvent +#include //mousePressEvent +#include //mousePressEvent + +#include //paletteChange() +//Added by qt3to4: +#include +#include + +static inline uint myMax( uint v1, uint v2 ) { return v1 > v2 ? v1 : v2; } + +BlockAnalyzer::BlockAnalyzer( QWidget *parent ) + : Analyzer::Base2D( parent, 9 ) + , m_columns( 0 ) //uint + , m_rows( 0 ) //uint + , m_y( 0 ) //uint + , m_barPixmap( 1, 1 ) //null qpixmaps cause crashes + , m_topBarPixmap( WIDTH, HEIGHT ) + , m_scope( MIN_COLUMNS ) //Scope + , m_store( 1 << 8, 0 ) //vector + , m_fade_bars( FADE_SIZE ) //vector + , m_fade_pos( 1 << 8, 50 ) //vector + , m_fade_intensity( 1 << 8, 32 ) //vector +{ + setMinimumSize( MIN_COLUMNS*(WIDTH+1) -1, MIN_ROWS*(HEIGHT+1) -1 ); //-1 is padding, no drawing takes place there + setMaximumWidth( MAX_COLUMNS*(WIDTH+1) -1 ); + setMaximumHeight( MIN_ROWS*(HEIGHT+1) -1 ); + + // mxcl says null pixmaps cause crashes, so let's play it safe + for (int i = 0; i < FADE_SIZE; ++i) + m_fade_bars[i] = QPixmap( 1, 1 ); +} + +BlockAnalyzer::~BlockAnalyzer() +{ +} + +void +BlockAnalyzer::resizeEvent( QResizeEvent *e ) +{ + Analyzer::Base2D::resizeEvent( e ); + + const uint oldRows = m_rows; + + //all is explained in analyze().. + //+1 to counter -1 in maxSizes, trust me we need this! + m_columns = qMin( uint(double(width()+1) / (WIDTH+1)), MAX_COLUMNS ); + m_rows = uint(double(height()+1) / (HEIGHT+1)); + + //this is the y-offset for drawing from the top of the widget + m_y = (height() - (m_rows * (HEIGHT+1)) + 2) / 2; + + m_scope.resize( m_columns ); + + if( m_rows != oldRows ) { + m_barPixmap = QPixmap( WIDTH, m_rows*(HEIGHT+1) ); + + for (int i = 0; i < FADE_SIZE; ++i) + m_fade_bars[i] = QPixmap( WIDTH, m_rows*(HEIGHT+1) ); + + m_yscale.resize( m_rows + 1 ); + + const float PRE = 1, PRO = 1; //PRE and PRO allow us to restrict the range somewhat + + for( uint z = 0; z < m_rows; ++z ) + m_yscale[z] = 1 - (log10( PRE+z ) / log10( PRE+m_rows+PRO )); + + m_yscale[m_rows] = 0; + + determineStep(); + paletteChange( palette() ); + } + else if( width() > e->oldSize().width() || height() > e->oldSize().height() ) + drawBackground(); + + analyze( m_scope ); +} + +void +BlockAnalyzer::determineStep() +{ + // falltime is dependent on rowcount due to our digital resolution (ie we have boxes/blocks of pixels) + // I calculated the value 30 based on some trial and error + + const double fallTime = 30 * m_rows; + m_step = double(m_rows * 80) / fallTime; //80 = ~milliseconds between signals with audio data +} + +void +BlockAnalyzer::transform( QVector &s ) //pure virtual +{ + for(int x = 0; x < s.size(); ++x) + s[x] *= 2; + + float *front = static_cast( &s.front() ); + + m_fht->spectrum( front ); + m_fht->scale( front, 1.0 / 20 ); + + //the second half is pretty dull, so only show it if the user has a large analyzer + //by setting to m_scope.size() if large we prevent interpolation of large analyzers, this is good! + s.resize( m_scope.size() <= MAX_COLUMNS/2 ? MAX_COLUMNS/2 : m_scope.size() ); +} + +void +BlockAnalyzer::analyze( const QVector &s ) +{ + Analyzer::interpolate( s, m_scope ); + update(); +} + +void +BlockAnalyzer::paintEvent(QPaintEvent*) +{ + // y = 2 3 2 1 0 2 + // . . . . # . + // . . . # # . + // # . # # # # + // # # # # # # + // + // visual aid for how this analyzer works. + // y represents the number of blanks + // y starts from the top and increases in units of blocks + + // m_yscale looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 } + // if it contains 6 elements there are 5 rows in the analyzer + + + QPainter p( this ); + + // Paint the background + p.drawPixmap(0, 0, m_background); +// p.fillRect(rect(), palette().color( QPalette::Active, QPalette::Background )); + + uint y; + + for(int x = 0; x < m_scope.size(); ++x) + { + // determine y + for( y = 0; m_scope[x] < m_yscale[y]; ++y ) + ; + + // this is opposite to what you'd think, higher than y + // means the bar is lower than y (physically) + if( (float)y > m_store[x] ) + y = uint(m_store[x] += m_step); + else + m_store[x] = y; + + // if y is lower than m_fade_pos, then the bar has exceeded the height of the fadeout + // if the fadeout is quite faded now, then display the new one + if(y <= m_fade_pos[x] /*|| m_fade_intensity[x] < FADE_SIZE / 3*/) { + m_fade_pos[x] = y; + m_fade_intensity[x] = FADE_SIZE; + } + + if( m_fade_intensity[x] > 0 ) { + const uint offset = --m_fade_intensity[x]; + const uint y = m_y + (m_fade_pos[x] * (HEIGHT+1)); + p.drawPixmap( x*(WIDTH+1), y, m_fade_bars[offset], 0, 0, WIDTH, height() - y ); + } + + if( m_fade_intensity[x] == 0 ) + m_fade_pos[x] = m_rows; + + //REMEMBER: y is a number from 0 to m_rows, 0 means all blocks are glowing, m_rows means none are + p.drawPixmap( x*(WIDTH+1), y*(HEIGHT+1) + m_y, *bar(), 0, y*(HEIGHT+1), -1, -1 ); + } + + for( uint x = 0; x < m_store.size(); ++x ) + p.drawPixmap( x*(WIDTH+1), int(m_store[x])*(HEIGHT+1) + m_y, m_topBarPixmap ); +} + + + + + +static inline void +adjustToLimits( int &b, int &f, uint &amount ) +{ + // with a range of 0-255 and maximum adjustment of amount, + // maximise the difference between f and b + + if( b < f ) { + if( b > 255 - f ) { + amount -= f; + f = 0; + } else { + amount -= (255 - f); + f = 255; + } + } + else { + if( f > 255 - b ) { + amount -= f; + f = 0; + } else { + amount -= (255 - f); + f = 255; + } + } +} + +/** + * Clever contrast function + * + * It will try to adjust the foreground color such that it contrasts well with the background + * It won't modify the hue of fg unless absolutely necessary + * @return the adjusted form of fg + */ +QColor +ensureContrast( const QColor &bg, const QColor &fg, uint _amount = 150 ) +{ + class OutputOnExit { + public: + OutputOnExit( const QColor &color ) : c( color ) {} + ~OutputOnExit() { int h,s,v; c.getHsv( &h, &s, &v ); } + private: + const QColor &c; + }; + + // hack so I don't have to cast everywhere + #define amount static_cast(_amount) +// #define STAMP debug() << (QValueList() << fh << fs << fv); +// #define STAMP1( string ) debug() << string << ": " << (QValueList() << fh << fs << fv); +// #define STAMP2( string, value ) debug() << string << "=" << value << ": " << (QValueList() << fh << fs << fv); + + OutputOnExit allocateOnTheStack( fg ); + + int bh, bs, bv; + int fh, fs, fv; + + bg.getHsv( &bh, &bs, &bv ); + fg.getHsv( &fh, &fs, &fv ); + + int dv = abs( bv - fv ); + +// STAMP2( "DV", dv ); + + // value is the best measure of contrast + // if there is enough difference in value already, return fg unchanged + if( dv > amount ) + return fg; + + int ds = abs( bs - fs ); + +// STAMP2( "DS", ds ); + + // saturation is good enough too. But not as good. TODO adapt this a little + if( ds > amount ) + return fg; + + int dh = abs( bh - fh ); + +// STAMP2( "DH", dh ); + + if( dh > 120 ) { + // a third of the colour wheel automatically guarentees contrast + // but only if the values are high enough and saturations significant enough + // to allow the colours to be visible and not be shades of grey or black + + // check the saturation for the two colours is sufficient that hue alone can + // provide sufficient contrast + if( ds > amount / 2 && (bs > 125 && fs > 125) ) +// STAMP1( "Sufficient saturation difference, and hues are compliemtary" ); + return fg; + else if( dv > amount / 2 && (bv > 125 && fv > 125) ) +// STAMP1( "Sufficient value difference, and hues are compliemtary" ); + return fg; + +// STAMP1( "Hues are complimentary but we must modify the value or saturation of the contrasting colour" ); + + //but either the colours are two desaturated, or too dark + //so we need to adjust the system, although not as much + ///_amount /= 2; + } + + if( fs < 50 && ds < 40 ) { + // low saturation on a low saturation is sad + const int tmp = 50 - fs; + fs = 50; + if( amount > tmp ) + _amount -= tmp; + else + _amount = 0; + } + + // test that there is available value to honor our contrast requirement + if( 255 - dv < amount ) + { + // we have to modify the value and saturation of fg + //adjustToLimits( bv, fv, amount ); + +// STAMP + + // see if we need to adjust the saturation + if( amount > 0 ) + adjustToLimits( bs, fs, _amount ); + +// STAMP + + // see if we need to adjust the hue + if( amount > 0 ) + fh += amount; // cycles around; + +// STAMP + + return QColor::fromHsv( fh, fs, fv ); + } + +// STAMP + + if( fv > bv && bv > amount ) + return QColor::fromHsv( fh, fs, bv - amount ); + +// STAMP + + if( fv < bv && fv > amount ) + return QColor::fromHsv( fh, fs, fv - amount ); + +// STAMP + + if( fv > bv && (255 - fv > amount) ) + return QColor::fromHsv( fh, fs, fv + amount ); + +// STAMP + + if( fv < bv && (255 - bv > amount ) ) + return QColor::fromHsv( fh, fs, bv + amount ); + +// STAMP +// debug() << "Something went wrong!\n"; + + return Qt::blue; + + #undef amount +// #undef STAMP +} + +void +BlockAnalyzer::paletteChange( const QPalette& ) //virtual +{ + const QColor bg = palette().color(QPalette::Active, QPalette::Background); + const QColor fg = ensureContrast(bg, palette().color(QPalette::Active, QPalette::Foreground)); + + m_topBarPixmap.fill( fg ); + + const double dr = 15*double(bg.red() - fg.red()) / (m_rows*16); + const double dg = 15*double(bg.green() - fg.green()) / (m_rows*16); + const double db = 15*double(bg.blue() - fg.blue()) / (m_rows*16); + const int r = fg.red(), g = fg.green(), b = fg.blue(); + + bar()->fill( bg ); + + QPainter p( bar() ); + for( int y = 0; (uint)y < m_rows; ++y ) + //graduate the fg color + p.fillRect( 0, y*(HEIGHT+1), WIDTH, HEIGHT, QColor( r+int(dr*y), g+int(dg*y), b+int(db*y) ) ); + + { + const QColor bg = palette().color( QPalette::Active, QPalette::Background ).dark( 112 ); + + //make a complimentary fadebar colour + //TODO dark is not always correct, dumbo! + int h,s,v; palette().color( QPalette::Active, QPalette::Background ).dark( 150 ).getHsv( &h, &s, &v ); + const QColor fg = QColor::fromHsv( h + 60, s, v ); + + const double dr = fg.red() - bg.red(); + const double dg = fg.green() - bg.green(); + const double db = fg.blue() - bg.blue(); + const int r = bg.red(), g = bg.green(), b = bg.blue(); + + // Precalculate all fade-bar pixmaps + for(int y = 0; y < FADE_SIZE; ++y) { + m_fade_bars[y].fill( palette().color( QPalette::Active, QPalette::Background ) ); + QPainter f( &m_fade_bars[y] ); + for( int z = 0; (uint)z < m_rows; ++z ) { + const double Y = 1.0 - (log10( static_cast(FADE_SIZE) - y ) / log10( static_cast(FADE_SIZE) )); + f.fillRect( 0, z*(HEIGHT+1), WIDTH, HEIGHT, QColor( r+int(dr*Y), g+int(dg*Y), b+int(db*Y) ) ); + } + } + } + + drawBackground(); +} + +void +BlockAnalyzer::drawBackground() +{ + const QColor bg = palette().color( QPalette::Active, QPalette::Background); + const QColor bgdark = bg.dark( 112 ); + + m_background.fill( bg ); + + QPainter p( &m_background ); + for( int x = 0; (uint)x < m_columns; ++x ) + for( int y = 0; (uint)y < m_rows; ++y ) + p.fillRect( x*(WIDTH+1), y*(HEIGHT+1) + m_y, WIDTH, HEIGHT, bgdark ); + +} diff --git a/dragon/src/app/analyzer/blockAnalyzer.h b/dragon/src/app/analyzer/blockAnalyzer.h new file mode 100644 index 00000000..b36c228a --- /dev/null +++ b/dragon/src/app/analyzer/blockAnalyzer.h @@ -0,0 +1,81 @@ +/**************************************************************************************** + * Copyright (c) 2003-2005 Max Howell * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Pulic License for more details. * + * * + * You should have received a copy of the GNU General Public License along with * + * this program. If not, see . * + ****************************************************************************************/ + +#ifndef BLOCKANALYZER_H +#define BLOCKANALYZER_H + +#include "analyzerBase.h" +#include +//Added by qt3to4: +#include +#include +#include +#include + +class QResizeEvent; +class QMouseEvent; +class QPalette; + + +/** + * @author Max Howell + */ + +class BlockAnalyzer : public Analyzer::Base2D +{ +public: + BlockAnalyzer( QWidget* ); + ~BlockAnalyzer(); + + // Signed ints because most of what we compare them against are ints + static const int HEIGHT = 2; + static const int WIDTH = 4; + static const int MIN_ROWS = 48; //arbituary + static const int MIN_COLUMNS = 128; //arbituary + static const int MAX_COLUMNS = 128; //must be 2**n + static const int FADE_SIZE = 90; + +protected: + virtual void transform( QVector& ); + virtual void analyze( const QVector& ); + virtual void paintEvent( QPaintEvent* ); + virtual void resizeEvent( QResizeEvent* ); + virtual void paletteChange( const QPalette& ); + + void drawBackground(); + void determineStep(); + +private: + QPixmap* bar() { return &m_barPixmap; } + + uint m_columns, m_rows; //number of rows and columns of blocks + uint m_y; //y-offset from top of widget + QPixmap m_barPixmap; + QPixmap m_topBarPixmap; + QVector m_scope; //so we don't create a vector every frame + std::vector m_store; //current bar heights + std::vector m_yscale; + + //FIXME why can't I namespace these? c++ issue? + std::vector m_fade_bars; + std::vector m_fade_pos; + std::vector m_fade_intensity; + QPixmap m_background; + + float m_step; //rows to fall per frame +}; + +#endif diff --git a/dragon/src/app/analyzer/fht.cpp b/dragon/src/app/analyzer/fht.cpp new file mode 100644 index 00000000..41e912e5 --- /dev/null +++ b/dragon/src/app/analyzer/fht.cpp @@ -0,0 +1,243 @@ +// FHT - Fast Hartley Transform Class +// +// Copyright (C) 2004 Melchior FRANZ - mfranz@kde.org +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +// +// $Id: fht.cpp 871912 2008-10-16 02:27:10Z mitchell $ + + +#include "fht.h" + +#include +#include + +FHT::FHT(int n) : + m_buf(0), + m_tab(0), + m_log(0) +{ + if (n < 3) { + m_num = 0; + m_exp2 = -1; + return; + } + m_exp2 = n; + m_num = 1 << n; + if (n > 3) { + m_buf = new float[m_num]; + m_tab = new float[m_num * 2]; + makeCasTable(); + } +} + + +FHT::~FHT() +{ + delete[] m_buf; + delete[] m_tab; + delete[] m_log; +} + + +void FHT::makeCasTable(void) +{ + float d, *costab, *sintab; + int ul, ndiv2 = m_num / 2; + + for (costab = m_tab, sintab = m_tab + m_num / 2 + 1, ul = 0; ul < m_num; ul++) { + d = M_PI * ul / ndiv2; + *costab = *sintab = cos(d); + + costab += 2, sintab += 2; + if (sintab > m_tab + m_num * 2) + sintab = m_tab + 1; + } +} + + +float* FHT::copy(float *d, float *s) +{ + return (float *)memcpy(d, s, m_num * sizeof(float)); +} + + +float* FHT::clear(float *d) +{ + return (float *)memset(d, 0, m_num * sizeof(float)); +} + + +void FHT::scale(float *p, float d) +{ + for (int i = 0; i < (m_num / 2); i++) + *p++ *= d; +} + + +void FHT::ewma(float *d, float *s, float w) +{ + for (int i = 0; i < (m_num / 2); i++, d++, s++) + *d = *d * w + *s * (1 - w); +} + + +void FHT::logSpectrum(float *out, float *p) +{ + int n = m_num / 2, i, j, k, *r; + if (!m_log) { + m_log = new int[n]; + float f = n / log10((double)n); + for (i = 0, r = m_log; i < n; i++, r++) { + j = int(rint(log10(i + 1.0) * f)); + *r = j >= n ? n - 1 : j; + } + } + semiLogSpectrum(p); + *out++ = *p = *p / 100; + for (k = i = 1, r = m_log; i < n; ++i) { + j = *r++; + if (i == j) + *out++ = p[i]; + else { + float base = p[k - 1]; + float step = (p[j] - base) / (j - (k - 1)); + for (float corr = 0; k <= j; k++, corr += step) + *out++ = base + corr; + } + } +} + + +void FHT::semiLogSpectrum(float *p) +{ + float e; + power2(p); + for (int i = 0; i < (m_num / 2); i++, p++) { + e = 10.0 * log10(sqrt(*p * .5)); + *p = e < 0 ? 0 : e; + } +} + + +void FHT::spectrum(float *p) +{ + power2(p); + for (int i = 0; i < (m_num / 2); i++, p++) + *p = (float)sqrt(*p * .5); +} + + +void FHT::power(float *p) +{ + power2(p); + for (int i = 0; i < (m_num / 2); i++) + *p++ *= .5; +} + + +void FHT::power2(float *p) +{ + int i; + float *q; + _transform(p, m_num, 0); + + *p = (*p * *p), *p += *p, p++; + + for (i = 1, q = p + m_num - 2; i < (m_num / 2); i++, --q) + *p = (*p * *p) + (*q * *q), p++; +} + + +void FHT::transform(float *p) +{ + if (m_num == 8) + transform8(p); + else + _transform(p, m_num, 0); +} + + +void FHT::transform8(float *p) +{ + float a, b, c, d, e, f, g, h, b_f2, d_h2; + float a_c_eg, a_ce_g, ac_e_g, aceg, b_df_h, bdfh; + + a = *p++, b = *p++, c = *p++, d = *p++; + e = *p++, f = *p++, g = *p++, h = *p; + b_f2 = (b - f) * M_SQRT2; + d_h2 = (d - h) * M_SQRT2; + + a_c_eg = a - c - e + g; + a_ce_g = a - c + e - g; + ac_e_g = a + c - e - g; + aceg = a + c + e + g; + + b_df_h = b - d + f - h; + bdfh = b + d + f + h; + + *p = a_c_eg - d_h2; + *--p = a_ce_g - b_df_h; + *--p = ac_e_g - b_f2; + *--p = aceg - bdfh; + *--p = a_c_eg + d_h2; + *--p = a_ce_g + b_df_h; + *--p = ac_e_g + b_f2; + *--p = aceg + bdfh; +} + + +void FHT::_transform(float *p, int n, int k) +{ + if (n == 8) { + transform8(p + k); + return; + } + + int i, j, ndiv2 = n / 2; + float a, *t1, *t2, *t3, *t4, *ptab, *pp; + + for (i = 0, t1 = m_buf, t2 = m_buf + ndiv2, pp = &p[k]; i < ndiv2; i++) + *t1++ = *pp++, *t2++ = *pp++; + + memcpy(p + k, m_buf, sizeof(float) * n); + + _transform(p, ndiv2, k); + _transform(p, ndiv2, k + ndiv2); + + j = m_num / ndiv2 - 1; + t1 = m_buf; + t2 = t1 + ndiv2; + t3 = p + k + ndiv2; + ptab = m_tab; + pp = p + k; + + a = *ptab++ * *t3++; + a += *ptab * *pp; + ptab += j; + + *t1++ = *pp + a; + *t2++ = *pp++ - a; + + for (i = 1, t4 = p + k + n; i < ndiv2; i++, ptab += j) { + a = *ptab++ * *t3++; + a += *ptab * *--t4; + + *t1++ = *pp + a; + *t2++ = *pp++ - a; + } + memcpy(p + k, m_buf, sizeof(float) * n); +} + diff --git a/dragon/src/app/analyzer/fht.h b/dragon/src/app/analyzer/fht.h new file mode 100644 index 00000000..857be19b --- /dev/null +++ b/dragon/src/app/analyzer/fht.h @@ -0,0 +1,119 @@ +// FHT - Fast Hartley Transform Class +// +// Copyright (C) 2004 Melchior FRANZ - mfranz@kde.org +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +// +// $Id: fht.h 668973 2007-05-28 08:55:15Z mkossick $ + +#ifndef FHT_H +#define FHT_H + +/** + * Implementation of the Hartley Transform after Bracewell's discrete + * algorithm. The algorithm is subject to US patent No. 4,646,256 (1987) + * but was put into public domain by the Board of Trustees of Stanford + * University in 1994 and is now freely available[1]. + * + * [1] Computer in Physics, Vol. 9, No. 4, Jul/Aug 1995 pp 373-379 + */ +class FHT +{ + int m_exp2; + int m_num; + float *m_buf; + float *m_tab; + int *m_log; + + /** + * Create a table of "cas" (cosine and sine) values. + * Has only to be done in the constructor and saves from + * calculating the same values over and over while transforming. + */ + void makeCasTable(); + + /** + * Recursive in-place Hartley transform. For internal use only! + */ + void _transform(float *, int, int); + + public: + /** + * Prepare transform for data sets with @f$2^n@f$ numbers, whereby @f$n@f$ + * should be at least 3. Values of more than 3 need a trigonometry table. + * @see makeCasTable() + */ + FHT(int); + + ~FHT(); + inline int sizeExp() const { return m_exp2; } + inline int size() const { return m_num; } + float *copy(float *, float *); + float *clear(float *); + void scale(float *, float); + + /** + * Exponentially Weighted Moving Average (EWMA) filter. + * @param d is the filtered data. + * @param s is fresh input. + * @param w is the weighting factor. + */ + void ewma(float *d, float *s, float w); + + /** + * Logarithmic audio spectrum. Maps semi-logarithmic spectrum + * to logarithmic frequency scale, interpolates missing values. + * A logarithmic index map is calculated at the first run only. + * @param p is the input array. + * @param out is the spectrum. + */ + void logSpectrum(float *out, float *p); + + /** + * Semi-logarithmic audio spectrum. + */ + void semiLogSpectrum(float *); + + /** + * Fourier spectrum. + */ + void spectrum(float *); + + /** + * Calculates a mathematically correct FFT power spectrum. + * If further scaling is applied later, use power2 instead + * and factor the 0.5 in the final scaling factor. + * @see FHT::power2() + */ + void power(float *); + + /** + * Calculates an FFT power spectrum with doubled values as a + * result. The values need to be multiplied by 0.5 to be exact. + * Note that you only get @f$2^{n-1}@f$ power values for a data set + * of @f$2^n@f$ input values. This is the fastest transform. + * @see FHT::power() + */ + void power2(float *); + + /** + * Discrete Hartley transform of data sets with 8 values. + */ + void transform8(float *); + + void transform(float *); +}; + +#endif diff --git a/dragon/src/app/audioView2.cpp b/dragon/src/app/audioView2.cpp new file mode 100644 index 00000000..f2ddc44e --- /dev/null +++ b/dragon/src/app/audioView2.cpp @@ -0,0 +1,105 @@ +/*********************************************************************** + * Copyright 2012 Harald Sitter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "audioView2.h" +#include "ui_audioView2.h" + +#include +#include + +#include "theStream.h" +#include "videoWindow.h" + +namespace Dragon { + +AudioView2::AudioView2(QWidget *parent) : + QWidget(parent), + ui(new Ui::AudioView2) +{ + ui->setupUi(this); + + ui->m_analyzerFrame->setMaximumSize(ui->m_analyzer->maximumSize()); + ui->m_analyzerFrame->setMinimumSize(ui->m_analyzer->minimumSize()); + + QFont boldFont = KGlobalSettings::generalFont(); + boldFont.setBold(true); + ui->m_track->setFont(boldFont); + ui->m_artist->setFont(KGlobalSettings::generalFont()); + ui->m_album->setFont(KGlobalSettings::generalFont()); + + engine()->setupAnalyzer(ui->m_analyzer); + connect(engine(), SIGNAL(metaDataChanged()), this, SLOT(update())); +} + +AudioView2::~AudioView2() +{ + delete ui; +} + +void AudioView2::enableDemo(bool enable) +{ + ui->m_analyzer->enableDemo(enable); +} + +void AudioView2::update() +{ + ui->m_artist->setText( TheStream::metaData( Phonon::ArtistMetaData ) ); + ui->m_album->setText( TheStream::metaData( Phonon::AlbumMetaData ) ); + ui->m_track->setText( TheStream::metaData( Phonon::TitleMetaData ) ); +// { //somewhat of a longshot: try to find Amarok cover for the music +// QString imagePath = checkForAmarokImage( artist, album ); +// if(imagePath.isNull()) +// { +// delete m_image; +// m_image = 0; +// m_layout->setColumnFixedWidth(0, 0.0); +// } +// else +// { +// m_layout->setColumnFixedWidth(0, COVER_COLUMN_WIDTH); +// if(!m_image) +// { +// m_image = new QGraphicsPixmapItem(); +// scene()->addItem( m_image ); +// m_image->setPos( 3.0, 3.0 ); +// } +// QPixmap cover( imagePath ); +// m_image->setPixmap( cover ); +// qreal width = static_cast( cover.width() ); +// qreal scale = COVER_WIDTH / width; +// m_image->setScale( scale ); +// } +// } +} + + +void AudioView2::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +} // namespace Dragon diff --git a/dragon/src/app/audioView2.h b/dragon/src/app/audioView2.h new file mode 100644 index 00000000..6c04b475 --- /dev/null +++ b/dragon/src/app/audioView2.h @@ -0,0 +1,53 @@ +/*********************************************************************** + * Copyright 2012 Harald Sitter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGON_AUDIOVIEW2_H +#define DRAGON_AUDIOVIEW2_H + +#include + +namespace Dragon { + +namespace Ui { +class AudioView2; +} + +class AudioView2 : public QWidget +{ + Q_OBJECT + +public: + explicit AudioView2(QWidget *parent = 0); + ~AudioView2(); + +public slots: + void enableDemo(bool enable); + void update(); + +protected: + void changeEvent(QEvent *e); + +private: + Ui::AudioView2 *ui; +}; + + +} // namespace Dragon +#endif // DRAGON_AUDIOVIEW2_H diff --git a/dragon/src/app/audioView2.ui b/dragon/src/app/audioView2.ui new file mode 100644 index 00000000..110d485a --- /dev/null +++ b/dragon/src/app/audioView2.ui @@ -0,0 +1,139 @@ + + + Dragon::AudioView2 + + + + 0 + 0 + 794 + 622 + + + + + 0 + 0 + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 0 + 20 + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Sunken + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 0 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 0 + + + + + + + + + BlockAnalyzer + QFrame +
analyzer/blockAnalyzer.h
+ 1 +
+
+ + +
diff --git a/dragon/src/app/discSelectionDialog.cpp b/dragon/src/app/discSelectionDialog.cpp new file mode 100644 index 00000000..45de0846 --- /dev/null +++ b/dragon/src/app/discSelectionDialog.cpp @@ -0,0 +1,138 @@ +/*********************************************************************** + * Copyright 2008 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "discSelectionDialog.h" + +#include "codeine.h" +#include "videoWindow.h" + +#include +#include +#include + +#include +#include +#include +#include + + +class SolidListItem : public QListWidgetItem +{ + public: + SolidListItem( QListWidget* parent, const Solid::Device& device ) + : QListWidgetItem( parent ) + , m_device( device ) + { + const Solid::OpticalDisc* disc = device.as(); + if( disc ) + { + QString label = disc->label(); + if( label.isEmpty() ) + { + setText( contentTypesToString( disc->availableContent() ) ); + } + else + { + setText( i18nc( "%1 is the disc type, %2 is the name of the disc that the user can choose. Ex. 'DVD: OfficeSpace'" + , "%1: %2" + , contentTypesToString( disc->availableContent() ) + , label ) ); + } + if( disc->availableContent() & Solid::OpticalDisc::Audio ) + setIcon( KIcon( QLatin1String( "audio-x-generic" ) ) ); + else + setIcon( KIcon( QLatin1String( "video-x-generic" ) ) ); + } + } + + Solid::Device + device() const + { + return m_device; + } + + static QString + contentTypesToString( Solid::OpticalDisc::ContentTypes solidType ) + { + if( solidType & Solid::OpticalDisc::VideoDvd ) + return i18nc( "Digital Versatile Disc, but keep it short", "DVD" ); + else if( solidType & ( Solid::OpticalDisc::VideoCd | Solid::OpticalDisc::SuperVideoCd ) ) + return i18n( "Video CD" ); + else if( solidType & Solid::OpticalDisc::Audio ) + return i18n( "Audio CD" ); + else + return i18n( "Data CD" ); + } + + private: + const Solid::Device m_device; +}; + +DiscSelectionDialog::DiscSelectionDialog( QWidget* parent, const QList< Solid::Device >& deviceList ) + : KDialog( parent ) + , m_listWidget( new KListWidget() ) +{ + setButtons( KDialog::Ok | KDialog::Cancel ); + setCaption( i18n("Select a Disc") ); + + QLabel* questionLabel = new QLabel( i18n( "Select a disc to play." ) ); + foreach( const Solid::Device &device, deviceList ) + { + new SolidListItem( m_listWidget, device ); + } + + QVBoxLayout* layout = new QVBoxLayout(); + layout->addWidget( questionLabel ); + layout->addWidget( m_listWidget ); + QWidget* mainWidget = new QWidget( this ); + mainWidget->setLayout( layout ); + setMainWidget( mainWidget ); + connect( m_listWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(discItemSelected(QListWidgetItem*)) ); + connect( this, SIGNAL(okClicked()), this, SLOT(okClicked()) ); + connect( this, SIGNAL(cancelClicked()), this, SLOT(deleteLater()) ); + connect( this, SIGNAL(cancelClicked()), Dragon::mainWindow(), SLOT(playMedia()) ); + show(); +} + +void +DiscSelectionDialog::discItemSelected( QListWidgetItem *item ) +{ + openItem( item ); + deleteLater(); +} + +void +DiscSelectionDialog::okClicked() +{ + openItem( m_listWidget->currentItem() ); + deleteLater(); +} + +void +DiscSelectionDialog::openItem( QListWidgetItem *item ) +{ + if( item ) + { + const SolidListItem* solidItem = static_cast( item ); + Dragon::engine()->playDisc( solidItem->device() ); + } +} + +#include "discSelectionDialog.moc" diff --git a/dragon/src/app/discSelectionDialog.h b/dragon/src/app/discSelectionDialog.h new file mode 100644 index 00000000..15c922f4 --- /dev/null +++ b/dragon/src/app/discSelectionDialog.h @@ -0,0 +1,46 @@ +/*********************************************************************** + * Copyright 2008 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_DISCSELECTIONDIALOG_H +#define DRAGONPLAYER_DISCSELECTIONDIALOG_H + +#include + +#include +#include + +class QListWidget; +class QListWidgetItem; + +class DiscSelectionDialog : public KDialog +{ + Q_OBJECT + public: + DiscSelectionDialog( QWidget* parent, const QList< Solid::Device >& deviceList ); + private slots: + void discItemSelected( QListWidgetItem *item ); + void okClicked(); + private: + void openItem( QListWidgetItem *item ); + QListWidget* m_listWidget; + +}; + +#endif diff --git a/dragon/src/app/extern.h b/dragon/src/app/extern.h new file mode 100644 index 00000000..a9454ce5 --- /dev/null +++ b/dragon/src/app/extern.h @@ -0,0 +1,38 @@ +/************************************************************************ + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_EXTERN_H +#define DRAGONPLAYER_EXTERN_H + +#include +//I think this whole file is deprecated - Dave +class QWidget; + +namespace Dragon +{ + class VideoWindow; + + + void showVideoSettingsDialog( QWidget* ); + void insertAspectRatioMenuItems( QMenu* ); +} + +#endif diff --git a/dragon/src/app/fullScreenToolBarHandler.cpp b/dragon/src/app/fullScreenToolBarHandler.cpp new file mode 100644 index 00000000..c165e287 --- /dev/null +++ b/dragon/src/app/fullScreenToolBarHandler.cpp @@ -0,0 +1,114 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "fullScreenToolBarHandler.h" + +#include "videoWindow.h" +#include "mainWindow.h" + +#include +#include +#include + +#include +#include +#include + +Dragon::FullScreenToolBarHandler::FullScreenToolBarHandler( KMainWindow *parent ) + : QObject( parent ) + , m_timer_id( 0 ) + , m_parent(parent) +{ + parent->installEventFilter( this ); + + startTimer( Dragon::VideoWindow::CURSOR_HIDE_TIMEOUT ); // We want to hide automatically some time after fullscreening +} + +bool +Dragon::FullScreenToolBarHandler::eventFilter( QObject */*o*/, QEvent *e ) +{ + if (e->type() == QEvent::MouseMove) { + if (m_timer_id) { + kDebug() << "mouse move, killing timer"; + killTimer( m_timer_id ); + m_timer_id = 0; + } + + QMouseEvent const * const me = (QMouseEvent*)e; + + if (m_parent->toolBar()->geometry().contains(me->pos()) || + static_cast( Dragon::mainWindow() )->volumeContains(me->pos())) { + // no discussion here, mouse is in toolbar or volume slider area + kDebug() << "mouse in toolbar area, show toolbar"; + m_parent->toolBar()->show(); + static_cast( Dragon::mainWindow() )->showVolume( true ); + } + else if( m_parent->toolBar()->isHidden() ) { + kDebug() << "mouse moved while toolbar is hidden"; + if( m_home.isNull() ) + { + kDebug() << "set home"; + m_home = me->pos(); // store the position where the mouse was when we saw it + } + else if( ( m_home - me->pos() ).manhattanLength() > 6) + { + // then cursor has moved far enough to trigger show toolbar + kDebug() << "show toolbar"; + m_parent->toolBar()->show(); + static_cast( Dragon::mainWindow() )->showVolume( true ); + m_home = QPoint(); + } + else + { + kDebug() << "cursor hasn't moved far enough yet " << ( m_home - me->pos() ).manhattanLength(); + // cursor hasn't moved far enough yet + } + } + else { + // reset the hide timer + kDebug() << "mouse moved in video window while toolbar is shown, starting hide timer: " << Dragon::VideoWindow::CURSOR_HIDE_TIMEOUT; + m_timer_id = startTimer( Dragon::VideoWindow::CURSOR_HIDE_TIMEOUT ); + } + } + + else if (e->type() == QEvent::Resize) + { + //we aren't managed by mainWindow when at FullScreen + videoWindow()->move( 0, 0 ); + videoWindow()->resize( ((QWidget*)parent())->size() ); + videoWindow()->lower(); + } + + return false; +} + +void +Dragon::FullScreenToolBarHandler::timerEvent( QTimerEvent*e ) +{ + killTimer( e->timerId() ); // timers are NOT single-shot! + m_timer_id = 0; + + kDebug() << "hide timer triggered"; + static_cast( Dragon::mainWindow() )->showVolume( false ); + m_parent->toolBar()->hide(); +} + +#include "fullScreenToolBarHandler.moc" diff --git a/dragon/src/app/fullScreenToolBarHandler.h b/dragon/src/app/fullScreenToolBarHandler.h new file mode 100644 index 00000000..c51c8aa6 --- /dev/null +++ b/dragon/src/app/fullScreenToolBarHandler.h @@ -0,0 +1,47 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_FULLSCREENTOOLBARHANDLER_H +#define DRAGONPLAYER_FULLSCREENTOOLBARHANDLER_H + +#include +#include + +class KMainWindow; +class QTimerEvent; +class KToolBar; + +namespace Dragon +{ + class FullScreenToolBarHandler : QObject + { + Q_OBJECT + public: + FullScreenToolBarHandler( KMainWindow *parent ); + bool eventFilter( QObject *o, QEvent *e ); + void timerEvent( QTimerEvent* ); + private: + int m_timer_id; // 0 when timer is not running + QPoint m_home; + KMainWindow *m_parent; + }; +} +#endif diff --git a/dragon/src/app/listView.cpp b/dragon/src/app/listView.cpp new file mode 100644 index 00000000..9e3f9ab7 --- /dev/null +++ b/dragon/src/app/listView.cpp @@ -0,0 +1,41 @@ +/*********************************************************************** + * Copyright 2004 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYERLISTVIEW_CPP +#define DRAGONPLAYERLISTVIEW_CPP + +#include + +namespace Dragon +{ + class ListView : public KListWidget + { + public: + ListView( QWidget *parent ) + : KListWidget( parent ) + { + // setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ); + setAlternatingRowColors( true ); + } + }; +} + +#endif diff --git a/dragon/src/app/loadView.cpp b/dragon/src/app/loadView.cpp new file mode 100644 index 00000000..46a7878b --- /dev/null +++ b/dragon/src/app/loadView.cpp @@ -0,0 +1,58 @@ +/*********************************************************************** + * Copyright 2008 David Edmundson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "loadView.h" +#include "codeine.h" + +#include +#include +#include + + +namespace Dragon +{ + +LoadView::LoadView( QWidget *parent ) + : QWidget( parent ) +{ + setupUi( this ); + setStyleSheet( QLatin1String( "QPushButton { text-align: center; }" )); + m_playDiskButton->setIcon( KIcon( QLatin1String( "media-optical" ) ) ); + m_playDiskButton->setIconSize( QSize( KIconLoader::SizeMedium, KIconLoader::SizeMedium ) ); + m_playFileButton->setIcon( KIcon( QLatin1String( "folder" ) ) ); + m_playFileButton->setIconSize( QSize( KIconLoader::SizeMedium, KIconLoader::SizeMedium ) ); + m_playStreamButton->setIcon( KIcon( QLatin1String( "document-open-remote" ) ) ); + m_playStreamButton->setIconSize( QSize( KIconLoader::SizeMedium, KIconLoader::SizeMedium ) ); + + connect( m_playDiskButton, SIGNAL(released()), this, SIGNAL(openDVDPressed()) ); + connect( m_playFileButton, SIGNAL(released()), this, SIGNAL(openFilePressed()) ); + connect( m_playStreamButton, SIGNAL(released()), this, SIGNAL(openStreamPressed()) ); + connect( m_recentlyPlayed, SIGNAL(itemDoubleClicked(KUrl)), this, SIGNAL(loadUrl(KUrl)) ); +} + +void +LoadView::setThumbnail(QWidget *object) +{ + object->setParent(m_vThumb); + object->resize(m_vThumb->size()); + object->show(); +} + +} diff --git a/dragon/src/app/loadView.h b/dragon/src/app/loadView.h new file mode 100644 index 00000000..606851f8 --- /dev/null +++ b/dragon/src/app/loadView.h @@ -0,0 +1,44 @@ +/*********************************************************************** + * Copyright 2009 David Edmundson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ +#ifndef LOADVIEW_H +#define LOADVIEW_H +#include + +#include +#include "ui_loadView.h" + +namespace Dragon +{ + +class LoadView : public QWidget, private Ui_LoadView +{ + Q_OBJECT + public: + explicit LoadView(QWidget *parent); + void setThumbnail(QWidget *object); + signals: + void loadUrl(KUrl); + void openFilePressed(); + void openDVDPressed(); + void openStreamPressed(); +}; + +} +#endif diff --git a/dragon/src/app/loadView.ui b/dragon/src/app/loadView.ui new file mode 100644 index 00000000..491bb29c --- /dev/null +++ b/dragon/src/app/loadView.ui @@ -0,0 +1,84 @@ + + + LoadView + + + + 0 + 0 + 400 + 294 + + + + + + + + + Play File + + + + + + + Play Disc + + + + + + + Play Stream + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 80 + + + + false + + + + + + + + + + + + + KPushButton + QPushButton +
kpushbutton.h
+
+ + RecentlyPlayedList + QListWidget +
recentlyPlayedList.h
+
+
+ + +
diff --git a/dragon/src/app/main.cpp b/dragon/src/app/main.cpp new file mode 100644 index 00000000..c7215e1e --- /dev/null +++ b/dragon/src/app/main.cpp @@ -0,0 +1,56 @@ +/*********************************************************************** + * Copyright 2004 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ +#include "codeine.h" +#include "playerApplication.h" + +#include +#include +#include +#include + +static KAboutData aboutData( APP_NAME, 0, + ki18n("Dragon Player"), APP_VERSION, + ki18n("A video player that has a usability focus"), KAboutData::License_GPL_V2, + ki18n("Copyright 2006, Max Howell\nCopyright 2007, Ian Monroe"), ki18n("IRC:\nirc.freenode.net #dragonplayer\n\nFeedback:\nimonroe@kde.org"), + "http://multimedia.kde.org" ); + +int +main( int argc, char **argv ) +{ + aboutData.addCredit( ki18n("David Edmundson"), ki18n("Improvements and polish") ); + aboutData.addCredit( ki18n("Matthias Kretz"), ki18n("Creator of Phonon") ); + aboutData.addCredit( ki18n("Eugene Trounev"), ki18n("Dragon Player icon") ); + aboutData.addCredit( ki18n("Mike Diehl"), ki18n("Handbook") ); + aboutData.addCredit( ki18n("The Kaffeine Developers"), ki18n("Great reference code") ); + aboutData.addCredit( ki18n("Greenleaf"), ki18n("Yatta happened to be the only video on my laptop to test with. :)") ); + aboutData.addCredit( ki18n("Eike Hein"), ki18n("MPRIS v2 support") ); + + KCmdLineArgs::init( argc, argv, &aboutData ); + + KCmdLineOptions options; + options.add("+[URL]", ki18n( "Play 'URL'" )); + options.add("play-dvd", ki18n( "Play DVD Video" )); + KCmdLineArgs::addCmdLineOptions( options ); + KUniqueApplication::addCmdLineOptions(); + + Dragon::PlayerApplication application; + return application.exec(); +} diff --git a/dragon/src/app/mainWindow.cpp b/dragon/src/app/mainWindow.cpp new file mode 100644 index 00000000..1fe1672d --- /dev/null +++ b/dragon/src/app/mainWindow.cpp @@ -0,0 +1,981 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "mainWindow.h" +#include "timeLabel.h" + +#include +#include +#include +#include +#include +#include //::open() +#include +#include //::timerEvent() +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include //::stateChanged() +#include +#include +#include //ctor +#include +#include +#include +#include + +#include "actions.h" +#include "discSelectionDialog.h" +#include "mpris2/mpris2.h" +#include "extern.h" //dialog creation function definitions +#include "fullScreenToolBarHandler.h" +#include "messageBox.h" +#include "playDialog.h" //::play() +#include "playlistFile.h" +#include "theStream.h" +#include "ui_videoSettingsWidget.h" +#include "videoWindow.h" +#include "audioView2.h" +#include "loadView.h" + +#include +#include + +namespace Dragon { + + MainWindow *MainWindow::s_instance = 0; + /// @see codeine.h + QWidget* mainWindow() { return MainWindow::s_instance; } + +MainWindow::MainWindow() + : KXmlGuiWindow() + , m_mainView( 0 ) + , m_audioView( 0 ) + , m_loadView( new LoadView(this) ) + , m_currentWidget( new QWidget(this) ) + , m_leftDock( 0 ) + , m_positionSlider( 0 ) + , m_volumeSlider( 0 ) + , m_timeLabel( 0 ) + , m_titleLabel( new QLabel( this ) ) + , m_playDialog( 0 ) + , m_menuToggleAction( 0 ) + , m_stopScreenSaver( 0 ) + , m_stopSleepCookie( -1 ) + , m_stopScreenPowerMgmtCookie( -1 ) + , m_profileMaxDays(30) + , m_toolbarIsHidden(false) + , m_statusbarIsHidden(false) + , m_menuBarIsHidden(false) + , m_FullScreenHandler( 0 ) +{ + s_instance = this; + setMouseTracking( true ); + + m_mainView = new QStackedWidget(this); + m_mainView->setMouseTracking( true ); + + new VideoWindow( this ); + videoWindow()->setMouseTracking( true ); + + m_positionSlider = videoWindow()->newPositionSlider(); + + m_mainView->addWidget(m_loadView); + m_audioView = new AudioView2(this); + m_mainView->addWidget(m_audioView); + m_mainView->addWidget(videoWindow()); + m_mainView->setCurrentWidget(m_loadView); + + m_currentWidget = m_loadView; + + setCentralWidget( m_mainView ); + + setFocusProxy( videoWindow() ); // essential! See VideoWindow::event(), QEvent::FocusOut + + m_titleLabel->setMargin( 2 ); + m_titleLabel->setSizePolicy(QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed )); + + // work around a bug in KStatusBar + // sizeHint width of statusbar seems to get stupidly large quickly + statusBar()->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Maximum ); + + setupActions(); + + //setStandardToolBarMenuEnabled( false ); //bah to setupGUI()! + //toolBar()->show(); //it's possible it would be hidden, but we don't want that as no UI way to show it! + + { + KActionCollection* ac = actionCollection(); + KActionMenu *menuAction = 0; + #define make_menu( name, text ) \ + menuAction = new KActionMenu( text, this ); \ + menuAction->setObjectName( name ); \ + menuAction->setEnabled( false ); \ + connect( menuAction->menu(), SIGNAL(aboutToShow()), SLOT(aboutToShowMenu()) ); \ + ac->addAction( menuAction->objectName(), menuAction ); + make_menu( QLatin1String( "aspect_ratio_menu" ), i18n( "Aspect &Ratio" ) ); + make_menu( QLatin1String( "audio_channels_menu" ), i18n( "&Audio Channels" ) ); + make_menu( QLatin1String( "subtitle_channels_menu" ), i18n( "&Subtitles" ) ); + #undef make_menu + + { + m_aspectRatios = new QActionGroup( this ); + m_aspectRatios->setExclusive( true ); + #define make_ratio_action( text, objectname, aspectEnum ) \ + { \ + KAction* ratioAction = new KAction( this ); \ + ratioAction->setText( text ); \ + ratioAction->setCheckable( true ); \ + m_aspectRatios->addAction( ratioAction ); \ + TheStream::addRatio( aspectEnum, ratioAction ); \ + ac->addAction( objectname, ratioAction ); \ + connect( ratioAction, SIGNAL(triggered()), this, SLOT(streamSettingChange()) ); \ + } + make_ratio_action( i18n( "Determine &Automatically" ), QLatin1String( "ratio_auto" ), Phonon::VideoWidget::AspectRatioAuto ); + make_ratio_action( i18n( "&4:3" ), QLatin1String( "ratio_golden" ), Phonon::VideoWidget::AspectRatio4_3 ); + make_ratio_action( i18n( "Ana&morphic (16:9)" ), QLatin1String( "ratio_anamorphic" ), Phonon::VideoWidget::AspectRatio16_9 ); + make_ratio_action( i18n( "&Window Size" ), QLatin1String( "ratio_window" ), Phonon::VideoWidget::AspectRatioWidget ); + #undef make_ratio_action + ac->action( QLatin1String( "ratio_auto" ) )->setChecked( true ); + ac->action( QLatin1String( "aspect_ratio_menu" ) )->menu()->addActions( m_aspectRatios->actions() ); + } + } + + setupGUI(); //load xml dragonplayerui.rc file + //must be done after setupGUI: + { + toolBar()->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); + toolBar()->setFloatable( false ); + } + KXMLGUIClient::stateChanged( QLatin1String( "empty" ) ); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + if( args->count() || args->isSet( "play-dvd" ) || kapp->isSessionRestored() ) + //we need to resize the window, so we can't show the window yet + init(); + else + { + //"faster" startup + //TODO if we have a size stored for this video, do the "faster" route + QTimer::singleShot( 0, this, SLOT(init()) ); + QApplication::setOverrideCursor( Qt::WaitCursor ); + } +} + +void +MainWindow::init() +{ + //connect the stuff in loadView + connect( m_loadView, SIGNAL(openDVDPressed()), this, SLOT(playDisc()) ); + connect( m_loadView, SIGNAL(openFilePressed()), this, SLOT(openFileDialog()) ); + connect( m_loadView, SIGNAL(openStreamPressed()), this, SLOT(openStreamDialog()) ); + connect( m_loadView, SIGNAL(loadUrl(KUrl)), this, SLOT(open(KUrl)) ); + + //connect the video player + connect( engine(), SIGNAL(stateUpdated(Phonon::State,Phonon::State)), this, SLOT(engineStateChanged(Phonon::State)) ); + connect( engine(), SIGNAL(currentSourceChanged(Phonon::MediaSource)), this, SLOT(engineMediaChanged(Phonon::MediaSource)) ); + connect( engine(), SIGNAL(seekableChanged(bool)), this, SLOT(engineSeekableChanged(bool)) ); + connect( engine(), SIGNAL(metaDataChanged()), this, SLOT(engineMetaDataChanged()) ); + connect( engine(), SIGNAL(hasVideoChanged(bool)), this, SLOT(engineHasVideoChanged(bool)) ); + + connect( engine(), SIGNAL(subChannelsChanged(QList)), this, SLOT(subChannelsChanged(QList)) ); + connect( engine(), SIGNAL(audioChannelsChanged(QList)), this, SLOT(audioChannelsChanged(QList)) ); + connect( engine(), SIGNAL(mutedChanged(bool)), this, SLOT(mutedChanged(bool)) ); + + if( !engine()->init() ) { + KMessageBox::error( this, i18n( + "Phonon could not be successfully initialized. Dragon Player will now exit.") ); + QApplication::exit( 2 ); + } + + //would be dangerous for these to65535 happen before the videoWindow() is initialised + setAcceptDrops( true ); + connect( statusBar(), SIGNAL(messageChanged(QString)), engine(), SLOT(showOSD(QString)) ); + //statusBar()->insertPermanentItem( "hello world", 0, 0 ); + m_timeLabel = new TimeLabel( statusBar() ); + connect( videoWindow(), SIGNAL(tick(qint64)), m_timeLabel, SLOT(setCurrentTime(qint64)) ); + connect( videoWindow(), SIGNAL(totalTimeChanged(qint64)), m_timeLabel, SLOT(setTotalTime(qint64)) ); + statusBar()->addPermanentWidget( m_titleLabel, 100 ); + statusBar()->addPermanentWidget( m_timeLabel ); + + new Mpris2(this); + + QApplication::restoreOverrideCursor(); + engineStateChanged(Phonon::StoppedState);//set everything as it would be in stopped state + engineSeekableChanged(false); +} + +MainWindow::~MainWindow() +{ + hide(); //so we appear to have quit, and then sound fades out below + releasePowerSave(); + delete videoWindow(); //fades out sound in dtor +} + +void MainWindow::closeEvent (QCloseEvent *event) +{ + // Restore the state of these before closing + mainWindow()->setWindowState( Qt::WindowNoState ); + statusBar()->setHidden( m_statusbarIsHidden ); + toolBar()->setHidden( m_toolbarIsHidden ); + menuBar()->setHidden( m_menuBarIsHidden ); + + KMainWindow::closeEvent( event ); +} + +void MainWindow::wheelEvent (QWheelEvent *event) + { + if (event->delta() > 0) + engine()->increaseVolume(); + else + engine()->decreaseVolume(); + event->accept(); +} + +void +MainWindow::setupActions() +{ + + KActionCollection * const ac = actionCollection(); + + KStandardAction::quit( kapp, SLOT(closeAllWindows()), ac ); + + KStandardAction::open( this, SLOT(toggleLoadView()), ac )->setText( i18n("Play &Media...") ); + + #define addToAc( X ) ac->addAction( X->objectName(), X ); + + KToggleFullScreenAction* toggleFullScreen = new KToggleFullScreenAction( this, ac ); + toggleFullScreen->setObjectName( QLatin1String( "fullscreen" ) ); + toggleFullScreen->setShortcut( Qt::Key_F ); + toggleFullScreen->setAutoRepeat( false ); + connect( toggleFullScreen, SIGNAL(toggled(bool)), Dragon::mainWindow(), SLOT(setFullScreen(bool)) ); + addToAc( toggleFullScreen ); + + new PlayAction( this, SLOT(play()), ac ); + new VolumeAction( this, SLOT(toggleVolumeSlider(bool)), ac ); + + m_menuToggleAction = + static_cast(ac->addAction(KStandardAction::ShowMenubar, + menuBar(), + SLOT(setVisible(bool)))); + + KAction *action = new KAction(i18nc("@action", "Increase Volume"), ac); + action->setObjectName(QLatin1String("volume_inc")); + connect(action, SIGNAL(triggered()), engine(), SLOT(increaseVolume())); + addToAc(action); + + action = new KAction(i18nc("@action", "Decrease Volume"), ac); + action->setObjectName(QLatin1String("volume_dec")); + connect(action, SIGNAL(triggered()), engine(), SLOT(decreaseVolume())); + addToAc(action); + + KAction* playerStop = new KAction( KIcon(QLatin1String( "media-playback-stop" )), i18n("Stop"), ac ); + playerStop->setObjectName( QLatin1String( "stop" ) ); + playerStop->setShortcut( Qt::Key_S ); + connect( playerStop, SIGNAL(triggered()), this, SLOT(stop()) ); + addToAc( playerStop ) + + KToggleAction* mute = new KToggleAction( KIcon(QLatin1String( "player-volume-muted" )), i18nc( "Mute the sound output", "Mute"), ac ); + mute->setObjectName( QLatin1String( "mute" ) ); + mute->setShortcut( Qt::Key_M ); + connect( mute, SIGNAL(toggled(bool)), videoWindow(), SLOT(mute(bool)) ); + addToAc( mute ) + + KAction* resetZoom = new KAction( KIcon(QLatin1String( "zoom-fit-best" )), i18n("Reset Video Scale"), ac ); + resetZoom->setObjectName( QLatin1String( "reset_zoom" ) ); + resetZoom->setShortcut( Qt::Key_Equal ); + connect( resetZoom, SIGNAL(triggered()), videoWindow(), SLOT(resetZoom()) ); + addToAc( resetZoom ) + + KAction* dvdMenu = new KAction( KIcon(QLatin1String( "media-optical-video" )), i18n("Menu Toggle"), ac ); + dvdMenu->setObjectName( QLatin1String( "toggle_dvd_menu" ) ); + dvdMenu->setShortcut( Qt::Key_R ); + connect( dvdMenu, SIGNAL(triggered()), engine(), SLOT(toggleDVDMenu()) ); + addToAc( dvdMenu ) + + KAction* positionSlider = new KAction( i18n("Position Slider"), ac ); + positionSlider->setObjectName( QLatin1String( "position_slider" ) ); + positionSlider->setDefaultWidget( m_positionSlider ); + addToAc( positionSlider ) + + KAction* videoSettings = new KAction( i18n("Video Settings"), ac ); + videoSettings->setObjectName( QLatin1String( "video_settings" ) ); + videoSettings->setCheckable( true ); + connect( videoSettings, SIGNAL(toggled(bool)), this, SLOT(toggleVideoSettings(bool)) ); + addToAc( videoSettings ) + + KAction* uniqueToggle = + new KAction( i18nc("@action:inmenu Whether only one instance of dragon can be started" + " and will be reused when the user tries to play another file.", + "One Instance Only"), ac ); + uniqueToggle->setObjectName( QLatin1String( "unique" ) ); + uniqueToggle->setCheckable( true ); + uniqueToggle->setChecked( !KGlobal::config()->group("KDE").readEntry("MultipleInstances", QVariant(false)).toBool() ); + connect( uniqueToggle, SIGNAL(toggled(bool)), this, SLOT(toggleUnique(bool)) ); + addToAc( uniqueToggle ) + + KAction* prev_chapter = new KAction( KIcon(QLatin1String( "media-skip-backward" )), i18n("Previous Chapter"), ac ); + prev_chapter->setObjectName( QLatin1String( "prev_chapter" ) ); + prev_chapter->setShortcut( Qt::Key_Comma ); + connect( prev_chapter, SIGNAL(triggered()), engine(), SLOT(prevChapter()) ); + addToAc( prev_chapter ) + + KAction* next_chapter = new KAction( KIcon(QLatin1String( "media-skip-forward" )), i18n("Next Chapter"), ac ); + next_chapter->setObjectName( QLatin1String( "next_chapter" ) ); + next_chapter->setShortcut( Qt::Key_Period ); + connect( next_chapter, SIGNAL(triggered()), engine(), SLOT(nextChapter()) ); + addToAc( next_chapter ) + + // xgettext: no-c-format + KAction* tenPercentBack = new KAction( KIcon(QLatin1String( "media-seek-backward" )), i18n("Return 10% Back"), ac ); + tenPercentBack->setObjectName( QLatin1String( "ten_percent_back" ) ); + tenPercentBack->setShortcut( Qt::Key_PageUp ); + connect( tenPercentBack, SIGNAL(triggered()), engine(), SLOT(tenPercentBack()) ); + addToAc( tenPercentBack ) + + // xgettext: no-c-format + KAction* tenPercentForward = new KAction( KIcon(QLatin1String( "media-seek-forward" )), i18n("Go 10% Forward"), ac ); + tenPercentForward->setObjectName( QLatin1String( "ten_percent_forward" ) ); + tenPercentForward->setShortcut( Qt::Key_PageDown ); + connect( tenPercentForward, SIGNAL(triggered()), engine(), SLOT(tenPercentForward()) ); + addToAc( tenPercentForward ) + + KAction* tenSecondsBack = new KAction( KIcon(QLatin1String( "media-seek-backward" )), i18n("Return 10 Seconds Back"), ac ); + tenSecondsBack->setObjectName( QLatin1String( "ten_seconds_back" ) ); + tenSecondsBack->setShortcut( Qt::Key_Minus ); + connect( tenSecondsBack, SIGNAL(triggered()), engine(), SLOT(tenSecondsBack()) ); + addToAc( tenSecondsBack ) + + KAction* tenSecondsForward = new KAction( KIcon(QLatin1String( "media-seek-forward" )), i18n("Go 10 Seconds Forward"), ac ); + tenSecondsForward->setObjectName( QLatin1String( "ten_seconds_forward" ) ); + tenSecondsForward->setShortcut( Qt::Key_Plus ); + connect( tenSecondsForward, SIGNAL(triggered()), engine(), SLOT(tenSecondsForward()) ); + addToAc( tenSecondsForward ) + #undef addToAc +} + +void +MainWindow::toggleUnique( bool unique ) +{ + KGlobal::config()->group("KDE").writeEntry("MultipleInstances", !unique); + KGlobal::config()->sync(); +} + +void +MainWindow::toggleVideoSettings( bool show ) +{ + if( show ) + { + m_leftDock = new QDockWidget( this ); + m_leftDock->setObjectName( QLatin1String("left_dock" )); + m_leftDock->setFeatures( QDockWidget::NoDockWidgetFeatures ); + QWidget* videoSettingsWidget = new QWidget( m_leftDock ); + m_leftDock->setWidget( videoSettingsWidget ); + Ui::VideoSettingsWidget ui; + ui.setupUi( videoSettingsWidget ); + videoSettingsWidget->adjustSize(); + addDockWidget( Qt::LeftDockWidgetArea, m_leftDock ); + m_sliders.clear(); + m_sliders << ui.brightnessSlider << ui.contrastSlider << ui.hueSlider << ui.saturationSlider; + updateSliders(); + foreach( QSlider* slider, m_sliders ) + connect( slider, SIGNAL(valueChanged(int)), engine(), SLOT(settingChanged(int)) ); + + connect( ui.defaultsButton, SIGNAL(clicked(bool)), this, SLOT(restoreDefaultVideoSettings()) ); + connect( ui.closeButton, SIGNAL(clicked(bool)), action( "video_settings" ), SLOT(setChecked(bool)) ); + connect( ui.closeButton, SIGNAL(clicked(bool)), m_leftDock, SLOT(deleteLater()) ); + } + else + { + m_sliders.clear(); + delete m_leftDock; + } +} + +void +MainWindow::restoreDefaultVideoSettings() +{ + foreach( QSlider* slider, m_sliders ) + slider->setValue(0); +} + +void +MainWindow::toggleLoadView() +{ + if( engine()->state() == Phonon::PlayingState && TheStream::hasVideo() ) + { + engine()->playPause(); + } + if( m_mainView->currentWidget() == m_loadView ) + { + if( m_mainView->indexOf(m_currentWidget) == -1 ) + { + m_mainView->addWidget(m_currentWidget); + engine()->playPause(); + } + m_mainView->setCurrentWidget(m_currentWidget); + engine()->isPreview(false); + } + else if( m_currentWidget != m_audioView ) + { + kDebug() << "setting Thumbnail for video Widget"; + m_mainView->setCurrentWidget(m_loadView); + m_mainView->removeWidget(m_currentWidget); + engine()->isPreview(true); + m_loadView->setThumbnail(m_currentWidget); + } + else + { + m_mainView->setCurrentWidget(m_loadView); + } +} + +void +MainWindow::toggleVolumeSlider( bool show ) +{ + if( show ) + { + m_volumeSlider = engine()->newVolumeSlider(); + m_volumeSlider->setDisabled ( engine()->isMuted() ); + m_volumeSlider->setFocus(Qt::PopupFocusReason); + + m_muteCheckBox = new QCheckBox(); + m_muteCheckBox->setText( i18nc( "Mute the sound output", "Mute " ) ); + m_muteCheckBox->setChecked ( engine()->isMuted() ); + connect( m_muteCheckBox, SIGNAL(toggled(bool)), videoWindow(), SLOT(mute(bool)) ); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->addWidget(m_volumeSlider); + layout->addWidget(m_muteCheckBox); + + QWidget *dock = new QWidget; + dock->setLayout(layout); + + m_rightDock = new QDockWidget( this ); + m_rightDock->setFeatures( QDockWidget::NoDockWidgetFeatures ); + dock->setParent( m_rightDock ); + m_rightDock->setWidget( dock ); + addDockWidget( Qt::RightDockWidgetArea, m_rightDock ); + } + else + { + disconnect( m_muteCheckBox, SIGNAL(toggled(bool)), videoWindow(), SLOT(mute(bool)) ); + delete m_rightDock; // it's a QPointer, it will 0 itself + } +} + +void +MainWindow::mutedChanged( bool mute ) +{ + if( m_rightDock ) + { + m_volumeSlider->setDisabled ( mute ); + m_muteCheckBox->setChecked ( mute ); + } +} + +void MainWindow::stop() +{ + engine()->stop(); + m_mainView->setCurrentWidget(m_loadView); +} + +void +MainWindow::updateSliders() +{ + foreach( QSlider* slider, m_sliders ) + slider->setValue( engine()->videoSetting( slider->objectName() ) ); +} + +void +MainWindow::engineMessage( const QString &message ) +{ + statusBar()->showMessage( message, 3500 ); +} + +bool +MainWindow::open( const KUrl &url ) +{ + kDebug() << url; + + if( load( url ) ) { + const int offset = (TheStream::hasProfile() && isFresh()) + // adjust offset if we have session history for this video + ? TheStream::profile().readEntry( "Position", 0 ) + : 0; + kDebug() << "Initial offset is "<< offset; + engine()->loadSettings(); + updateSliders(); + return engine()->play( offset ); + } + + return false; +} + +bool +MainWindow::load( const KUrl &url ) +{ + //FileWatch the file that is opened + + if( url.isEmpty() ) { + MessageBox::sorry( i18n( "Dragon Player was asked to open an empty URL; it cannot." ) ); + return false; + } + + PlaylistFile playlist( url ); + if( playlist.isPlaylist() ) { + //TODO: problem is we return out of the function + //statusBar()->message( i18n("Parsing playlist file...") ); + + if( playlist.isValid() ) + return engine()->load( playlist.firstUrl() ); + else { + MessageBox::sorry( playlist.error() ); + return false; + } + } + + // local protocols like nepomuksearch:/ are not supported by xine + // check if an UDS_LOCAL_PATH is defined. + if (KProtocolInfo::protocolClass(url.protocol()) == QLatin1String(":local")) { + //#define UDS_LOCAL_PATH (72 | KIO::UDS_STRING) + KIO::UDSEntry e; + if (KIO::NetAccess::stat( url, e, 0 )) { + QString path = e.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH ); + if( !path.isEmpty() ) + return engine()->load( KUrl( path ) ); + } + } + + if( m_mainView->indexOf(engine()) == -1 ) + toggleLoadView(); + + //let xine handle invalid, etc, KUrlS + //TODO it handles non-existing files with bad error message + const bool ret = engine()->load( url ); + if( ret ) + { + if( TheStream::hasVideo() ) + m_currentWidget = engine(); + else { + m_currentWidget = m_audioView; + resize(m_currentWidget->minimumSize()); + } + m_mainView->setCurrentWidget(m_currentWidget); + } + return ret; +} + +void +MainWindow::play() +{ + switch( engine()->state() ) { + case Phonon::PlayingState: + engine()->pause(); + break; + case Phonon::PausedState: + engine()->resume(); + if( m_mainView->currentWidget() == m_loadView ) + toggleLoadView(); + break; + case Phonon::StoppedState: + if( TheStream::hasVideo() ) + m_currentWidget = engine(); + else { + m_currentWidget = m_audioView; + resize(m_currentWidget->minimumSize()); + } + engine()->play(); + m_mainView->setCurrentWidget(m_currentWidget); + break; + default: + break; + } +} + +void +MainWindow::openFileDialog() +{ + QStringList mimeFilter = Phonon::BackendCapabilities::availableMimeTypes(); + //temporary fixes for MimeTypes that Xine does support but it doesn't return - this is a Xine bug. + mimeFilter << QLatin1String( "audio/x-flac"); + mimeFilter << QLatin1String( "video/mp4" ); + mimeFilter << QLatin1String( "application/x-cd-image" ); // added for *.iso images + + static KUrl lastDirectory; + + KFileDialog dlg( KGlobalSettings::videosPath(), mimeFilter.join(QLatin1String( " " ) ), this ); + dlg.setCaption( i18n("Select File to Play") ); + dlg.setOperationMode( KFileDialog::Opening ); + + if( !lastDirectory.isEmpty() ) + dlg.setUrl( lastDirectory ); + + dlg.exec(); + + lastDirectory = dlg.baseUrl(); + const KUrl url = dlg.selectedFile(); + + if( url.isEmpty() ) + { + kDebug() << "URL empty in MainWindow::playDialogResult()"; + return; + } + else + { + open( url ); + } +} + +void +MainWindow::openStreamDialog() +{ + KUrl url(KInputDialog::getText( i18nc("@title:window", "Stream to Play"), i18n("Stream:") )); + + if( url.isEmpty() ) + { + kDebug() << "URL empty in MainWindow::openStreamDialog()"; + return; + } + else + { + open( url ); + } +} + +void +MainWindow::playDisc() +{ + QList< Solid::Device > playableDiscs; + { + QList< Solid::Device > deviceList = Solid::Device::listFromType( Solid::DeviceInterface::OpticalDisc ); + + foreach( const Solid::Device &device, deviceList ) + { + const Solid::OpticalDisc* disc = device.as(); + if( disc ) + { + if( disc->availableContent() & ( Solid::OpticalDisc::VideoDvd | Solid::OpticalDisc::VideoCd | Solid::OpticalDisc::SuperVideoCd | Solid::OpticalDisc::Audio ) ) + playableDiscs << device; + + } + } + } + if( !playableDiscs.isEmpty() ) + { + if( playableDiscs.size() > 1 ) //more than one disc, show user a selection box + { + kDebug() << "> 1 possible discs, showing dialog"; + new DiscSelectionDialog( this, playableDiscs ); + } + else //only one optical disc inserted, play whatever it is + { + bool status = engine()->playDisc( playableDiscs.first() ); + kDebug() << "playing disc" << status ; + } + } + else + { + engine()->playDvd(); + kDebug() << "no disc in drive or Solid isn't working"; + } + +} + +void +MainWindow::openRecentFile( const KUrl& url ) +{ + m_playDialog->deleteLater(); + m_playDialog = 0; + this->open( url ); +} + +void +MainWindow::parseArgs() +{ + KCmdLineArgs &args = *KCmdLineArgs::parsedArgs(); + if (args.isSet( "play-dvd" )) + playDisc(); + else if (args.count() > 0 ) { + open( args.url( 0 ) ); + args.clear(); + adjustSize(); //will resize us to reflect the videoWindow's sizeHint() + } +} + +void +MainWindow::setFullScreen( bool isFullScreen ) +{ + kDebug() << "Setting full screen to " << isFullScreen; + mainWindow()->setWindowState( (isFullScreen ? Qt::WindowFullScreen : Qt::WindowNoState )); + + if(isFullScreen) + { + m_statusbarIsHidden=statusBar()->isHidden(); + m_toolbarIsHidden=toolBar()->isHidden(); + m_menuBarIsHidden=menuBar()->isHidden(); + toolBar()->setHidden( false ); + statusBar()->setHidden( true ); + menuBar()->setHidden(true); + } + else + { + statusBar()->setHidden(m_statusbarIsHidden); + toolBar()->setHidden(m_toolbarIsHidden); + menuBar()->setHidden(m_menuBarIsHidden); + // In case someone hit the shortcut while being in fullscreen, the action + // would be out of sync. + m_menuToggleAction->setChecked(!m_menuBarIsHidden); + } + if( m_leftDock ) + m_leftDock->setHidden( isFullScreen ); + // the right dock is handled by the tool bar handler + + if( isFullScreen ) { + if (!m_FullScreenHandler) + m_FullScreenHandler = new FullScreenToolBarHandler( this ); + } + else + { + action( "fullscreen" )->setEnabled( videoWindow()->state() == Phonon::PlayingState || videoWindow()->state() == Phonon::PausedState); + delete m_FullScreenHandler; + m_FullScreenHandler = 0; + } +} + +void +MainWindow::showVolume( bool visible) +{ + if( m_rightDock ) + m_rightDock->setVisible( visible ); +} + +bool +MainWindow::volumeContains( QPoint mousePos ) +{ + if ( m_rightDock ) + return m_rightDock->geometry().contains(mousePos); + return false; +} + +void +MainWindow::aboutToShowMenu() +{ + TheStream::aspectRatioAction()->setChecked( true ); + { + int subId = TheStream::subtitleChannel(); + QList< QAction* > subs = action("subtitle_channels_menu")->menu()->actions(); + kDebug() << "subtitle #" << subId << " is going to be checked"; + foreach( QAction* subAction, subs ) + { + if( subAction->property( TheStream::CHANNEL_PROPERTY ).toInt() == subId ) + { + subAction->setChecked( true ); + break; + } + kDebug() << subAction->property( TheStream::CHANNEL_PROPERTY ).toInt() << " not checked."; + } + } + { + int audioId = TheStream::audioChannel(); + QList< QAction* > audios = action("audio_channels_menu")->menu()->actions(); + kDebug() << "audio #" << audioId << " is going to be checked"; + foreach( QAction* audioAction, audios ) + { + if( audioAction->property( TheStream::CHANNEL_PROPERTY ).toInt() == audioId ) + { + audioAction->setChecked( true ); + break; + } + } + } +} + +void +MainWindow::dragEnterEvent( QDragEnterEvent *e ) +{ + KUrl::List uriList = KUrl::List::fromMimeData( e->mimeData() ); + e->setAccepted( !uriList.isEmpty() ); +} + +void +MainWindow::dropEvent( QDropEvent *e ) +{ + KUrl::List uriList = KUrl::List::fromMimeData( e->mimeData() ); + if( !uriList.isEmpty() ) + this->open( uriList.first() ); + else + engineMessage( i18n("Sorry, no media was found in the drop") ); +} + +void +MainWindow::keyPressEvent( QKeyEvent *e ) +{ + switch( e->key() ) + { + case Qt::Key_Left: engine()->relativeSeek( -5000 ); break; + case Qt::Key_Right: engine()->relativeSeek( 5000 ); break; + case Qt::Key_Escape: action("fullscreen")->setChecked( false ); + default: ; + } + + #undef seek +} + +void +MainWindow::inhibitPowerSave() +{ + if (m_stopSleepCookie == -1) + m_stopSleepCookie = Solid::PowerManagement::beginSuppressingSleep(QLatin1String( "watching a film" )); + if (m_stopScreenPowerMgmtCookie == -1 && TheStream::hasVideo()) + m_stopScreenPowerMgmtCookie = Solid::PowerManagement::beginSuppressingScreenPowerManagement(QLatin1String( "watching a film" )); + if (!m_stopScreenSaver && TheStream::hasVideo()) + m_stopScreenSaver = new KNotificationRestrictions(KNotificationRestrictions::ScreenSaver); +} + +void +MainWindow::releasePowerSave() +{ + //stop supressing sleep + if (m_stopSleepCookie != -1) { + Solid::PowerManagement::stopSuppressingSleep(m_stopSleepCookie); + m_stopSleepCookie = -1; + } + + //stop supressing screen power management + if (m_stopScreenPowerMgmtCookie != -1) { + Solid::PowerManagement::stopSuppressingScreenPowerManagement(m_stopScreenPowerMgmtCookie); + m_stopScreenPowerMgmtCookie = -1; + } + + //stop disabling screensaver + delete m_stopScreenSaver; // It is always 0, I have been careful. + m_stopScreenSaver = 0; +} + +QMenu* +MainWindow::menu( const char *name ) +{ + // KXMLGUI is "really good". + return static_cast(factory()->container( QLatin1String( name ), this )); +} + +void +MainWindow::streamSettingChange() +{ + if( sender()->objectName().left( 5 ) == QLatin1String( "ratio" ) ) + { + TheStream::setRatio( dynamic_cast< QAction* > ( sender() ) ); + } +} + +void +MainWindow::updateTitleBarText() +{ + if( !TheStream::hasMedia() ) + { + m_titleLabel->setText( i18n("No media loaded") ); + } + else if( engine()->state() == Phonon::PausedState ) + { + m_titleLabel->setText( i18n("Paused") ); + } + else + { + m_titleLabel->setText( TheStream::prettyTitle() ); + } + kDebug() << "set titles "; +} + +#define CHANNELS_CHANGED( function, actionName ) \ +void \ +MainWindow::function( QList< QAction* > subActions ) \ +{ \ + if( subActions.size() <= 2 ) \ + action( actionName )->setEnabled( false ); \ + else \ + { \ + action( actionName )->menu()->addActions( subActions ); \ + action( actionName )->setEnabled( true ); \ + } \ +} + +CHANNELS_CHANGED( subChannelsChanged , "subtitle_channels_menu" ) +CHANNELS_CHANGED( audioChannelsChanged, "audio_channels_menu" ) +#undef CHANNELS_CHANGED + +/// Convenience class for other classes that need access to the actionCollection +KActionCollection* +actionCollection() +{ + return static_cast(mainWindow())->actionCollection(); +} + +/// Convenience class for other classes that need access to the actions +QAction* +action( const char *name ) +{ + KActionCollection *actionCollection = 0; + QAction *action = 0; + + if( mainWindow() ) + if( ( actionCollection = ((MainWindow*)mainWindow() )->actionCollection() ) ) + action = actionCollection->action(QLatin1String( name ) ); + if( !action ) + kDebug() << name; + Q_ASSERT( mainWindow() ); + Q_ASSERT( actionCollection ); + Q_ASSERT( action ); + + return action; +} + +bool MainWindow::isFresh() +{ + QDate date = QDate::fromString(TheStream::profile().readEntry( "Date", QDate::currentDate().toString("dd/MM/yyyy") ), "dd/MM/yyyy"); + + return (date.daysTo(QDate::currentDate()) < m_profileMaxDays) ? true : false; +} + + +} //namespace Dragon + +#include "mainWindow.moc" diff --git a/dragon/src/app/mainWindow.h b/dragon/src/app/mainWindow.h new file mode 100644 index 00000000..51ef72c1 --- /dev/null +++ b/dragon/src/app/mainWindow.h @@ -0,0 +1,164 @@ +/*********************************************************************** + * Copyright 2004 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYERMAINWINDOW_H +#define DRAGONPLAYERMAINWINDOW_H + +#include "codeine.h" +#include "timeLabel.h" +#include "loadView.h" + +#include +#include +#include +#include +#include +#include + +class KNotificationRestrictions; +class KToggleAction; +class KUrl; +class QActionGroup; +class QCloseEvent; +class QLabel; +class QMenu; +class QSlider; +class QCheckBox; + +class AudioView2; +#include "audioView2.h" + +namespace Dragon +{ + class PlayDialog; + class FullScreenToolBarHandler; + + class MainWindow : public KXmlGuiWindow + { + Q_OBJECT + + MainWindow(); + ~MainWindow(); + + static MainWindow *s_instance; + + friend class PlayerApplication; + friend QWidget* mainWindow(); + + public: + void openRecentFile( const KUrl& ); + void showVolume( bool ); + bool volumeContains( QPoint mousePos ); + + signals: + void fileChanged( QString ); + + public slots: + bool open( const KUrl& ); + void playDisc(); + void openFileDialog(); + void openStreamDialog(); + void play(); + void toggleVideoSettings( bool ); + void toggleVolumeSlider( bool ); + void restoreDefaultVideoSettings(); + void toggleLoadView(); + void parseArgs(); + + private slots: + void setFullScreen( bool full ); + void engineMessage( const QString& ); + void init(); + void aboutToShowMenu(); + void streamSettingChange(); + void subChannelsChanged( QList< QAction* > ); + void audioChannelsChanged( QList< QAction* > ); + void mutedChanged( bool ); + void stop(); + //in stateChange.cpp + void engineStateChanged( Phonon::State ); + void engineMediaChanged( Phonon::MediaSource ); + void engineSeekableChanged( bool ); + void engineMetaDataChanged(); + void engineHasVideoChanged( bool ); + void toggleUnique( bool ); + + private: + bool load( const KUrl& ); + void setupActions(); + void updateSliders(); + void updateTitleBarText(); + bool isFresh(); + + + QMenu *menu( const char *name ); + + virtual void dragEnterEvent( QDragEnterEvent* ); + virtual void dropEvent( QDropEvent* ); + virtual void keyPressEvent( QKeyEvent* ); + + void inhibitPowerSave(); + void releasePowerSave(); + +// virtual void saveProperties( KConfig* ); +// virtual void readProperties( KConfig* ); + + QStackedWidget *m_mainView; + AudioView2 *m_audioView; + LoadView *m_loadView; + QWidget *m_currentWidget; + + QPointer m_leftDock; + QPointer m_rightDock; + QWidget *m_positionSlider; + QPointer m_volumeSlider; + QCheckBox *m_muteCheckBox; + TimeLabel *m_timeLabel; + QLabel *m_titleLabel; + QList m_sliders; + QPointer m_playDialog; + + KToggleAction *m_menuToggleAction; + + KNotificationRestrictions *m_stopScreenSaver; + int m_screensaverDisableCookie; + int m_stopSleepCookie; + int m_stopScreenPowerMgmtCookie; + int m_profileMaxDays; + + bool m_toolbarIsHidden; + bool m_statusbarIsHidden; + bool m_menuBarIsHidden; + FullScreenToolBarHandler *m_FullScreenHandler; + + QActionGroup *m_aspectRatios; + Q_DISABLE_COPY(MainWindow) + + protected: + void closeEvent( QCloseEvent * event ); + void wheelEvent ( QWheelEvent * event ); + }; + +} + + + +#endif diff --git a/dragon/src/app/part.cpp b/dragon/src/app/part.cpp new file mode 100644 index 00000000..5c2cde1d --- /dev/null +++ b/dragon/src/app/part.cpp @@ -0,0 +1,137 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "part.h" + +#include "actions.h" +#include "codeine.h" +#include "partToolBar.h" +#include "videoWindow.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +K_PLUGIN_FACTORY(CodeineFactory, registerPlugin();) +K_EXPORT_PLUGIN(CodeineFactory("libdragon")) + +namespace Dragon +{ + Part::Part( QWidget* parentWidget, QObject* parent, const QList& /*args*/ ) + : ReadOnlyPart( parent ) + , m_statusBarExtension( new KParts::StatusBarExtension( this ) ) + , m_playPause( 0 ) + { + KActionCollection * const ac = actionCollection(); + + setWidget( new QWidget( parentWidget ) ); //, widgetName + QBoxLayout* layout = new QVBoxLayout(); + layout->setContentsMargins( 0, 0, 0, 0 ); + + KToolBar *toolBar = new MouseOverToolBar( widget() ); + layout->addWidget( toolBar ); + layout->addWidget( new VideoWindow( widget() ) ); + + m_playPause = new Dragon::PlayAction( videoWindow(), SLOT(playPause()), ac ); + toolBar->addAction( m_playPause ); + { + QWidget* slider = videoWindow()->newPositionSlider(); + KAction* sliderAction = new KAction( i18n("Position Slider"), ac ); + sliderAction->setObjectName( QLatin1String( "position_slider" ) ); + sliderAction->setDefaultWidget( slider ); + ac->addAction( sliderAction->objectName(), sliderAction ); + toolBar->addAction( sliderAction ); + } + connect( engine(), SIGNAL(stateChanged(Phonon::State)), this, SLOT(engineStateChanged(Phonon::State)) ); + videoWindow()->setContextMenuPolicy( Qt::CustomContextMenu ); + connect( videoWindow(), SIGNAL(customContextMenuRequested()), this, SLOT(videoContextMenu()) ); + + widget()->setLayout( layout ); + } + + void + Part::engineStateChanged( Phonon::State state ) + { + m_playPause->setChecked( state == Phonon::PlayingState ); + } + + bool + Part::openUrl( const KUrl &url ) + { + kDebug() << "playing " << url; + bool ret = videoWindow()->load( m_url = url ); + videoWindow()->play(); + return ret; + } + + bool + Part::closeUrl() + { + m_url = KUrl(); + videoWindow()->stop(); + return true; + } + + KAboutData* + Part::createAboutData() + { + // generic factory expects this on the heap + //return new KAboutData( APP_NAME, "Dragon Player", APP_VERSION ); + return new KAboutData( APP_NAME, 0, + ki18n("Dragon Player"), APP_VERSION, + ki18n("A video player that has a usability focus"), KAboutData::License_GPL_V2, + ki18n("Copyright 2006, Max Howell\nCopyright 2007, Ian Monroe"), KLocalizedString(), + "http://multimedia.kde.org", + "imonroe@kde.org" ); + } + + bool + Part::openFile() //pure virtual in base class + { + return false; + } + + void + Part::videoContextMenu( const QPoint & pos ) + { + KMenu menu; + menu.addAction( m_playPause ); + menu.exec( pos ); + } + + QAction* + action( const char* /*actionName*/ ) { return 0; } + ///fake mainWindow for VideoWindow + QWidget* + mainWindow() { return 0;} + +} + +#include "part.moc" diff --git a/dragon/src/app/part.h b/dragon/src/app/part.h new file mode 100644 index 00000000..eea09bb8 --- /dev/null +++ b/dragon/src/app/part.h @@ -0,0 +1,69 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_PART_H +#define DRAGONPLAYER_PART_H + +#include "codeine.h" + +#include + +#include +#include +#include +#include +class KAboutData; + + +namespace Dragon +{ + class PlayAction; + + class Part : public KParts::ReadOnlyPart + { + Q_OBJECT + public: + Part(QWidget* parentWidget, QObject* parent, const QList& /*args*/ ); + + virtual bool closeUrl(); + + static KAboutData *createAboutData(); + + public slots: + virtual bool openUrl( const KUrl& ); + + private slots: + void engineStateChanged( Phonon::State state ); + void videoContextMenu( const QPoint & pos ); + + protected: + virtual bool openFile(); + + private: + KUrl m_url; + KParts::StatusBarExtension *m_statusBarExtension; + Dragon::PlayAction* m_playPause; + + KStatusBar *statusBar() { return m_statusBarExtension->statusBar(); } + }; +} + +#endif diff --git a/dragon/src/app/partToolBar.cpp b/dragon/src/app/partToolBar.cpp new file mode 100644 index 00000000..a47be051 --- /dev/null +++ b/dragon/src/app/partToolBar.cpp @@ -0,0 +1,60 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "partToolBar.h" + +#include +#include +#include + +MouseOverToolBar::MouseOverToolBar( QWidget *parent ) + : KToolBar( parent ) +{ + parent->installEventFilter( this ); + // move( 0, 0 ); //TODO necessary? + hide(); + + setPalette( QApplication::palette() ); //videoWindow palette has a black background +} + +bool +MouseOverToolBar::eventFilter( QObject */*o*/, QEvent *e ) +{ + switch( e->type() ) + { + /*case QEvent::Resize: + resize( static_cast(e)->size().width(), sizeHint().height() ); + break;*/ + + case QEvent::Enter: + show(); + break; + + case QEvent::Leave: + hide(); + break; + + default: + ; + } + + return false; +} diff --git a/dragon/src/app/partToolBar.h b/dragon/src/app/partToolBar.h new file mode 100644 index 00000000..bd15e071 --- /dev/null +++ b/dragon/src/app/partToolBar.h @@ -0,0 +1,37 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + + +#ifndef DRAGONPLAYER_PARTTOOLBAR_H +#define DRAGONPLAYER_PARTTOOLBAR_H + +#include + + +class MouseOverToolBar : public KToolBar +{ + virtual bool eventFilter( QObject*, QEvent* ); + +public: + MouseOverToolBar( QWidget *parent ); +}; + +#endif diff --git a/dragon/src/app/playDialog.cpp b/dragon/src/app/playDialog.cpp new file mode 100644 index 00000000..7dc9971b --- /dev/null +++ b/dragon/src/app/playDialog.cpp @@ -0,0 +1,118 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "playDialog.h" + +#include "mainWindow.h" +#include "recentlyPlayedList.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Dragon { + +PlayDialog::PlayDialog( QWidget *parent, bool be_welcome_dialog ) + : KDialog( parent ) +{ + setWindowTitle( KDialog::makeStandardCaption( i18n("Play Media") ) ); + + QSignalMapper *mapper = new QSignalMapper( this ); + QWidget *o, *closeButton = new KPushButton( KStandardGuiItem::close(), this ); + QBoxLayout *vbox = new QVBoxLayout(); + //vbox->setMargin( 0 ); + vbox->setSpacing( 15 ); +// hbox->setMargin( 15 ); vbox->setMargin( 15 ); +// hbox->setSpacing( 20 ); vbox->setSpacing( 20 ); + + vbox->addWidget( new QLabel( i18n( "What media would you like to play?" ), this ) ); + + QGridLayout *grid = new QGridLayout(); + vbox->addLayout( grid ); + grid->setMargin( 0 ); + grid->setVerticalSpacing( 20 ); + + //TODO use the kguiItems from the actions + mapper->setMapping( o = new KPushButton( KGuiItem( i18n("Play File..."), QLatin1String( "document-open" ) ), this ), FILE ); + connect( o, SIGNAL(clicked()), mapper, SLOT(map()) ); + grid->addWidget( o, 0, 0 ); + + mapper->setMapping( o = new KPushButton( KGuiItem( i18n("Play Disc"), QLatin1String( "media-optical-video" ) ), this ), DVD ); + connect( o, SIGNAL(clicked()), mapper, SLOT(map()) ); + grid->addWidget( o, 0, 1 ); + + mapper->setMapping( closeButton, QDialog::Rejected ); + connect( closeButton, SIGNAL(clicked()), mapper, SLOT(map()) ); + + createRecentFileWidget( grid ); + + QBoxLayout *hbox = new QHBoxLayout(); + hbox->addItem( new QSpacerItem( 10, 10, QSizePolicy::Expanding ) ); + + if( be_welcome_dialog ) { + QWidget *w = new KPushButton( KStandardGuiItem::quit(), this ); + hbox->addWidget( w ); + connect( w, SIGNAL(clicked()), kapp, SLOT(closeAllWindows()) ); + } + + hbox->addWidget( closeButton ); + + connect( mapper, SIGNAL(mapped(int)), mainWindow(), SLOT(playDialogResult(int)) ); + vbox->addLayout( hbox ); + setLayout( vbox ); + setAttribute( Qt::WA_DeleteOnClose, true ); +} + +void +PlayDialog::createRecentFileWidget( QGridLayout *layout ) +{ + RecentlyPlayedList *lv = new RecentlyPlayedList( this ); + + + //delete list view widget if there are no items in it + if( lv->count() ) { + layout->addWidget( lv, 1, 0, 1, -1); + connect( lv, SIGNAL(executed(QListWidgetItem*)), this, SLOT(finished(QListWidgetItem*)) ); + } + else + delete lv; +} + +void +PlayDialog::finished( QListWidgetItem *item ) +{ + m_url = item->data( 0xdecade ).value(); + ((Dragon::MainWindow*) mainWindow() )->openRecentFile( m_url ); +} + +} + +#include "playDialog.moc" diff --git a/dragon/src/app/playDialog.h b/dragon/src/app/playDialog.h new file mode 100644 index 00000000..73fdc89a --- /dev/null +++ b/dragon/src/app/playDialog.h @@ -0,0 +1,53 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYERPLAYDIALOG_H +#define DRAGONPLAYERPLAYDIALOG_H + +#include +#include + +class QListWidgetItem; +class QGridLayout; + +namespace Dragon +{ + class PlayDialog : public KDialog + { + Q_OBJECT + public: + explicit PlayDialog( QWidget*, bool show_welcome_dialog = false ); + + const KUrl &url() const { return m_url; } + + enum DialogCode { FILE = QDialog::Accepted + 2, VCD, DVD, RECENT_FILE }; + + private slots: + virtual void finished( QListWidgetItem* ); + + private: + void createRecentFileWidget( QGridLayout* ); + + KUrl m_url; + }; +} + +#endif diff --git a/dragon/src/app/playerApplication.cpp b/dragon/src/app/playerApplication.cpp new file mode 100644 index 00000000..29e60008 --- /dev/null +++ b/dragon/src/app/playerApplication.cpp @@ -0,0 +1,56 @@ +/*********************************************************************** + * Copyright 2011 Geoffry Song + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "playerApplication.h" + +#include "mainWindow.h" + +namespace Dragon +{ + +PlayerApplication::PlayerApplication() + : KUniqueApplication(true, true) + , m_mainWindow(0) +{ +} + +PlayerApplication::~PlayerApplication() +{ + if (m_mainWindow) { + m_mainWindow = 0; + delete m_mainWindow; + } +} + +int PlayerApplication::newInstance() +{ + if (!m_mainWindow) + m_mainWindow = new Dragon::MainWindow; + + if (restoringSession()) + m_mainWindow->restore(1, false); + else + m_mainWindow->parseArgs(); + + m_mainWindow->show(); + return KUniqueApplication::newInstance(); +} + +} // namespace Dragon diff --git a/dragon/src/app/playerApplication.h b/dragon/src/app/playerApplication.h new file mode 100644 index 00000000..4357d6ce --- /dev/null +++ b/dragon/src/app/playerApplication.h @@ -0,0 +1,45 @@ +/*********************************************************************** + * Copyright 2011 Geoffry Song + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_PLAYERAPPLICATION_H +#define DRAGONPLAYER_PLAYERAPPLICATION_H + +#include + +namespace Dragon +{ +class MainWindow; + +class PlayerApplication : public KUniqueApplication +{ + Q_OBJECT +public: + PlayerApplication(); + ~PlayerApplication(); + + virtual int newInstance(); + +private: + MainWindow *m_mainWindow; +}; + +} // namespace Dragon + +#endif diff --git a/dragon/src/app/playlistFile.cpp b/dragon/src/app/playlistFile.cpp new file mode 100644 index 00000000..55f3e83c --- /dev/null +++ b/dragon/src/app/playlistFile.cpp @@ -0,0 +1,143 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + + +//TODO error messages that vary depending on if the file is remote or not + +#include "playlistFile.h" + +#include "codeine.h" +#include +#include +#include + +#include +#include + +PlaylistFile::PlaylistFile( const KUrl &url ) + : m_url( url ) + , m_isRemoteFile( !url.isLocalFile() ) + , m_isValid( false ) +{ + QApplication::setOverrideCursor( Qt::WaitCursor ); + + QString &path = m_path = url.path(); + + if( path.endsWith( QLatin1String(".pls"), Qt::CaseInsensitive ) ) + m_type = PLS; else + if( path.endsWith( QLatin1String(".m3u"), Qt::CaseInsensitive ) ) + m_type = M3U; + else { + m_type = Unknown; + m_error = i18n( "The file is not a playlist" ); + QApplication::restoreOverrideCursor(); + return; + } + + if( m_isRemoteFile ) { + path.clear(); + if( !KIO::NetAccess::download( url, path, Dragon::mainWindow() ) ) { + m_error = i18n( "Dragon Player could not download the remote playlist: %1", url.prettyUrl() ); + QApplication::restoreOverrideCursor(); + return; + } + } + + QFile file( path ); + if( file.open( QIODevice::ReadOnly ) ) { + QTextStream stream( &file ); + switch( m_type ) { + case M3U: parseM3uFile( stream ); break; + case PLS: parsePlsFile( stream ); break; + default: ; + } + + if( m_contents.isEmpty() ) + m_error = i18n( "The playlist, '%1', could not be interpreted. Perhaps it is empty?", path ), + m_isValid = false; + } + else + m_error = i18n( "Dragon Player could not open the file: %1", path ); + + QApplication::restoreOverrideCursor(); +} + + +PlaylistFile::~PlaylistFile() +{ + if( m_isRemoteFile ) + KIO::NetAccess::removeTempFile( m_path ); +} + + +void +PlaylistFile::parsePlsFile( QTextStream &stream ) +{ + + for( QString line = stream.readLine(); !line.isNull(); ) + { + if( line.startsWith( QLatin1String("File") ) ) { + const KUrl url = line.section( QLatin1Char( '=' ), -1 ); + const QString title = stream.readLine().section( QLatin1Char( '=' ), -1 ); + + kDebug() << url << endl << title; + + m_contents += url; + m_isValid = true; + + return; //TODO continue for all urls + } + line = stream.readLine(); + } +} + + +void +PlaylistFile::parseM3uFile( QTextStream &stream ) +{ + + for( QString line; !stream.atEnd(); ) + { + line = stream.readLine(); + + if( line.startsWith( QLatin1String("#EXTINF"), Qt::CaseInsensitive ) ) + continue; + + else if( !line.startsWith( QLatin1Char( '#' ) ) && !line.isEmpty() ) + { + KUrl url; + + // KUrl::isRelativeUrl() expects absolute URLs to start with a protocol, so prepend it if missing + if( line.startsWith( QLatin1Char( '/' ) ) ) + line.prepend( QLatin1String( "file://" ) ); + + if( KUrl::isRelativeUrl( line ) ) + url.setPath( m_url.directory() + QLatin1Char( '/' ) + line ); + else + url = KUrl( line ); + + m_contents += url; + m_isValid = true; + + return; + } + } +} diff --git a/dragon/src/app/playlistFile.h b/dragon/src/app/playlistFile.h new file mode 100644 index 00000000..608fa260 --- /dev/null +++ b/dragon/src/app/playlistFile.h @@ -0,0 +1,55 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_PLAYLIST_FILE_H +#define DRAGONPLAYER_PLAYLIST_FILE_H + +#include +#include + +class PlaylistFile +{ +public: + PlaylistFile( const KUrl &url ); + ~PlaylistFile(); + + enum FileFormat { M3U, PLS, Unknown, NotPlaylistFile = Unknown }; + + bool isPlaylist() const { return m_type != Unknown; } + bool isValid() const { return m_isValid; } + KUrl firstUrl() const { return m_contents.isEmpty() ? KUrl() : m_contents.first(); } + QString error() const { return m_error; } + +private: + /// both only return first url currently + void parsePlsFile( QTextStream& ); + void parseM3uFile( QTextStream& ); + + KUrl m_url; + bool m_isRemoteFile; + bool m_isValid; + QString m_error; + FileFormat m_type; + QString m_path; + KUrl::List m_contents; +}; + +#endif diff --git a/dragon/src/app/recentlyPlayedList.cpp b/dragon/src/app/recentlyPlayedList.cpp new file mode 100644 index 00000000..9de8d6e7 --- /dev/null +++ b/dragon/src/app/recentlyPlayedList.cpp @@ -0,0 +1,129 @@ +/*********************************************************************** + * Copyright 2008 David Edmundson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "recentlyPlayedList.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +//this is a widget for dispaying the rcently played items in a list. It is subclassed so that we can hook up a context menu +RecentlyPlayedList::RecentlyPlayedList(QWidget *parent) + :KListWidget(parent) +{ + connect(this,SIGNAL(itemDoubleClicked(QListWidgetItem*)),this, SLOT(itemDoubleClicked(QListWidgetItem*))); + setAlternatingRowColors( true ); + setSelectionMode(QAbstractItemView::SingleSelection); + + configGroup = new KConfigGroup( KGlobal::config(), "General" ); + loadEntries(); +} + +RecentlyPlayedList::~RecentlyPlayedList() +{ + delete configGroup; +} + +void +RecentlyPlayedList::loadEntries() +{ + clear(); + const QStringList entries = configGroup->readPathEntry( "Recent Urls", QStringList() ); + + QListIterator i(entries); + i.toBack(); + while(i.hasPrevious()) + { + KUrl url = KUrl(i.previous()); + QListWidgetItem* listItem = new QListWidgetItem( url.fileName().isEmpty() ? url.prettyUrl() : url.fileName() ); + listItem->setData( 0xdecade, QVariant::fromValue( url ) ); + + if(KConfigGroup( KGlobal::config(), url.prettyUrl()).readPathEntry( "IsVideo", QString() )==QLatin1String( "false" )) + listItem->setIcon( KIcon( QLatin1String( "audio-x-generic" ) ) ); + else + listItem->setIcon( KIcon( QLatin1String( "video-x-generic" ) ) ); + addItem( listItem ); + } +} + +void +RecentlyPlayedList::contextMenuEvent(QContextMenuEvent * event ) +{ + if (!currentItem()) + return; + KMenu menu; + kDebug() << "Loading Menu"; + menu.addAction(KIcon(QLatin1String( "list-remove" )),i18n("Remove Entry"),this,SLOT(removeEntry())); + menu.addAction(KIcon(QLatin1String( "list-remove" )),i18n("Clear List"),this,SLOT(clearList())); + menu.exec( event->globalPos() ); +} + +void +RecentlyPlayedList::removeEntry() +{ + QStringList list = configGroup->readPathEntry( "Recent Urls", QStringList() ); + KUrl toRemove = currentItem()->data(0xdecade).value(); + list.removeAll(toRemove.prettyUrl()); + configGroup->writePathEntry("Recent Urls",list.join( QLatin1String( "," ))); + loadEntries(); +} + +void +RecentlyPlayedList::clearList() +{ + configGroup->writePathEntry("Recent Urls",QString()); + loadEntries(); +} + +//send the url for the item clicked, not the item +void +RecentlyPlayedList::itemDoubleClicked(QListWidgetItem* item) +{ + KUrl url = item->data(0xdecade).value(); + + if( url.isLocalFile() ) + { + QFileInfo fileInfo( url.toLocalFile() ); + + if( !fileInfo.exists() ) + { + if( KMessageBox::questionYesNo( 0, + i18n( "This file could not be found. Would you like to remove it from the playlist?" ), + i18n( "File not found" ) ) == KMessageBox::Yes ) + { + removeEntry(); + } + + return; + } + } + + emit(itemDoubleClicked(url)); +} + +#include "recentlyPlayedList.moc" diff --git a/dragon/src/app/recentlyPlayedList.h b/dragon/src/app/recentlyPlayedList.h new file mode 100644 index 00000000..144727f8 --- /dev/null +++ b/dragon/src/app/recentlyPlayedList.h @@ -0,0 +1,47 @@ +/*********************************************************************** + * Copyright 2008 David Edmundson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef RECENTLYPLAYEDLIST_H +#define RECENTLYPLAYEDLIST_H + +#include +#include +#include + +class RecentlyPlayedList : public KListWidget +{ + Q_OBJECT + public: + explicit RecentlyPlayedList(QWidget*); + virtual ~RecentlyPlayedList(); + private: + virtual void contextMenuEvent(QContextMenuEvent*); + virtual void loadEntries(); + KConfigGroup* configGroup; + public slots: + virtual void removeEntry(); + virtual void clearList(); + virtual void itemDoubleClicked(QListWidgetItem*); + signals: + void itemDoubleClicked(KUrl); +}; + + +#endif diff --git a/dragon/src/app/stateChange.cpp b/dragon/src/app/stateChange.cpp new file mode 100644 index 00000000..f826fbde --- /dev/null +++ b/dragon/src/app/stateChange.cpp @@ -0,0 +1,204 @@ +/*********************************************************************** + * Copyright 2004 Max Howell + * 2007 Ian Monroe +* 2008 David Edmundson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "mainWindow.h" + +#include +#include +#include + +#include "actions.h" +#include "theStream.h" +#include "videoWindow.h" +#include "audioView2.h" + +#define QT_FATAL_ASSERT + +namespace Dragon { + +void +MainWindow::engineStateChanged( Phonon::State state ) +{ + bool const isFullScreen = toggleAction("fullscreen")->isChecked(); + bool const hasMedia = TheStream::hasMedia(); + + switch(state) + { + case Phonon::LoadingState: + kDebug() << "Loading state"; + break; + case Phonon::StoppedState: + kDebug() << "Stopped state"; + break; + case Phonon::PlayingState: + kDebug() << "Playing state"; + break; + case Phonon::BufferingState: + kDebug() << "Buffering state"; + break; + case Phonon::PausedState: + kDebug() << "Paused state"; + break; + case Phonon::ErrorState: + kDebug() << "Error state"; + break; + } + + bool enable = false; + if(state == Phonon::PlayingState || state == Phonon::PausedState || state == Phonon::BufferingState) + { + enable = true; + } + action("stop")->setEnabled(enable); + action("video_settings")->setEnabled(enable && TheStream::hasVideo()); + action("volume")->setEnabled(enable); + if( m_volumeSlider ) + m_volumeSlider->setEnabled(enable); + action("fullscreen")->setEnabled(enable || isFullScreen); + action("reset_zoom")->setEnabled(hasMedia && !isFullScreen); + + m_timeLabel->setVisible(enable); + m_audioView->enableDemo(!enable); + + kDebug() << "updated actions"; + + /// update menus + { + // the toolbar play button is always enabled, but the menu item + // is disabled if we are empty, this looks more sensible + PlayAction* playAction = static_cast( actionCollection()->action(QLatin1String( "play" )) ); + playAction->setEnabled( hasMedia ); + playAction->setPlaying( state == Phonon::PlayingState || state == Phonon::BufferingState ); + actionCollection()->action(QLatin1String( "aspect_ratio_menu" ))->setEnabled((enable) && TheStream::hasVideo() ); + + // set correct aspect ratio + if( state != Phonon::LoadingState ) + TheStream::aspectRatioAction()->setChecked( true ); + } + kDebug() << "updated menus"; + + /// turn off screensaver + if( state == Phonon::PlayingState ) + inhibitPowerSave(); + else if( state == Phonon::StoppedState || !TheStream::hasMedia() ) + releasePowerSave(); + + updateTitleBarText(); + + // enable/disable DVD specific buttons + QWidget *dvd_button = toolBar()->findChild< QWidget* >( QLatin1String( "toolbutton_toggle_dvd_menu" )); + if(videoWindow()->isDVD()) + { + if (dvd_button) + { + dvd_button->setVisible(true); + } + action("toggle_dvd_menu")->setEnabled( true ); + } + else + { + if (dvd_button) + { + dvd_button->setVisible(false); + } + action("toggle_dvd_menu")->setEnabled( false ); + } +}//engineStateChanged + + +void +MainWindow::engineMediaChanged(Phonon::MediaSource /*newSource*/) +{ + m_audioView->update(); + + // update recently played list + kDebug() << " update recent files list "; + + emit fileChanged( engine()->urlOrDisc() ); + //TODO fetch this from the Media source + KUrl const &url = TheStream::url(); + const QString url_string = url.url(); + + #ifndef NO_SKIP_PR0N + // ;-) + if( !(url_string.contains( QLatin1String( "porn" ), Qt::CaseInsensitive ) || url_string.contains( QLatin1String( "pr0n" ), Qt::CaseInsensitive )) ) + #endif + if( url.protocol() != QLatin1String( "dvd" ) && url.protocol() != QLatin1String( "vcd" ) && !url.prettyUrl().isEmpty()) + { + KConfigGroup config = KConfigGroup( KGlobal::config(), "General" ); + const QString prettyUrl = url.prettyUrl(); + QStringList urls = config.readPathEntry( "Recent Urls", QStringList() ); + urls.removeAll( prettyUrl ); + config.writePathEntry( "Recent Urls", urls << prettyUrl ); + } + +}//engineMediaChanged + +void MainWindow::engineSeekableChanged(bool canSeek) +{ + kDebug() << "seekable changed to " << canSeek; + m_positionSlider->setEnabled( canSeek ); + //TODO connect/disconnect the jump forward/back here. +}//engineSeekableChanged + + +void MainWindow::engineMetaDataChanged() +{ + kDebug() << "metaDataChanged"; + updateTitleBarText(); + m_audioView->update(); +} + +void MainWindow::engineHasVideoChanged( bool hasVideo ) +{ + Q_UNUSED(hasVideo); + + kDebug() << "hasVideo changed"; + if( TheStream::hasVideo() ) + { + if( m_mainView->indexOf(engine()) == -1 ) + m_mainView->addWidget(engine()); + m_mainView->setCurrentWidget(engine()); + m_currentWidget = engine(); + + // Fake change of state to trigger a re-evaluation of enabled actions. + // The video state might have changed *after* a state change (e.g. in Phonon-VLC) + // in which case the video related menu actions will not be enabled until + // a new state change occurs. By forcing a fake state change we can work around this. + engineStateChanged(videoWindow()->state()); + } + else + { + m_mainView->setCurrentWidget(m_audioView); + m_currentWidget = m_audioView; + } + + if (TheStream::hasVideo()) { + inhibitPowerSave(); + // Assumption: since we have no playlist the only way to release suppression + // is through going into stopped state. This also means that should there + // ever be a playlist playing video and then audio will possibly not + // release video specific inhibitions. + } +} + +}//namespace diff --git a/dragon/src/app/textItem.cpp b/dragon/src/app/textItem.cpp new file mode 100644 index 00000000..f2886e1c --- /dev/null +++ b/dragon/src/app/textItem.cpp @@ -0,0 +1,92 @@ +/*********************************************************************** +* Copyright 2010 Ian Monroe +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License or (at your option) version 3 or any later version +* accepted by the membership of KDE e.V. (or its successor approved +* by the membership of KDE e.V.), which shall act as a proxy +* defined in Section 14 of version 3 of the license. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +***********************************************************************/ + +#include "textItem.h" + +#include + +#include +#include +#include + +ExpandingTextItem::ExpandingTextItem(QGraphicsWidget* parent) + : QGraphicsTextItem(parent), QGraphicsLayoutItem() +{ + +} + +ExpandingTextItem::~ExpandingTextItem() +{ } + +QSizeF +ExpandingTextItem::sizeHint(Qt::SizeHint which, const QSizeF& constraint) const +{ + //credit to: psih128 + // http://www.qtcentre.org/threads/16533-Subclassing-QGraphicsTextItem-and-QGraphicsLayoutItem + switch (which) { + case Qt::MinimumSize: + return QSizeF(0, 0); + case Qt::PreferredSize: + return document()->size(); + case Qt::MaximumSize: + return QSizeF(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + default: + qWarning("TextItem::sizeHint(): Don't know how to handle the value of 'which'"); + break; + } + return constraint; + +} + +void +ExpandingTextItem::setGeometry(const QRectF& rect) +{ + QFont theFont = font(); + + int size = qMin(static_cast(rect.height()), 40); + + theFont.setPixelSize(size); + QFontMetricsF fm(theFont); + while(fm.width(toPlainText()) > rect.width()) + { + size -= 5; + theFont.setPixelSize(size); + fm = QFontMetricsF(theFont); + } + + setFont(theFont); + setPos(rect.topLeft()); +} + +void +ExpandingTextItem::updateGeometry() +{ + QGraphicsLayoutItem::updateGeometry(); +} + +void +ExpandingTextItem::setPlainText(const QString& text) +{ + QGraphicsTextItem::setPlainText(text); + updateGeometry(); +} + +#include "textItem.moc" + diff --git a/dragon/src/app/textItem.h b/dragon/src/app/textItem.h new file mode 100644 index 00000000..89c70fdf --- /dev/null +++ b/dragon/src/app/textItem.h @@ -0,0 +1,45 @@ +/*********************************************************************** +* Copyright 2010 Ian Monroe +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License or (at your option) version 3 or any later version +* accepted by the membership of KDE e.V. (or its successor approved +* by the membership of KDE e.V.), which shall act as a proxy +* defined in Section 14 of version 3 of the license. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +***********************************************************************/ + +#ifndef TEXTITEM_H +#define TEXTITEM_H + +#include +#include + +/** + * A QGV text widget which will automatically change its font size based on + * the space available. + **/ +class ExpandingTextItem : public QGraphicsTextItem, public QGraphicsLayoutItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsLayoutItem) + public: + ExpandingTextItem(QGraphicsWidget* parent = 0); + ~ExpandingTextItem(); + void setPlainText(const QString& text); + void setGeometry(const QRectF& rect); + protected: + virtual QSizeF sizeHint(Qt::SizeHint which, const QSizeF& constraint = QSizeF()) const; + virtual void updateGeometry(); +}; + +#endif // TEXTITEM_H diff --git a/dragon/src/app/theStream.cpp b/dragon/src/app/theStream.cpp new file mode 100644 index 00000000..8875189d --- /dev/null +++ b/dragon/src/app/theStream.cpp @@ -0,0 +1,274 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "theStream.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "videoWindow.h" + +namespace Dragon +{ + + const char* TheStream::CHANNEL_PROPERTY = "channel"; + + QHash TheStream::s_aspectRatioActions; + + KConfigGroup + TheStream::profile() + { + Phonon::MediaSource::Type current = videoWindow()->m_media->currentSource().type(); + if( current == Phonon::MediaSource::Disc ) + { + QList< Solid::Device > deviceList = Solid::Device::listFromType( Solid::DeviceInterface::OpticalDisc ); + if( !deviceList.isEmpty() ) + { + Solid::StorageVolume* disc = deviceList.first().as(); + if( disc ) + { + QString discLabel = QString::fromLatin1("disc:%1,%2").arg( disc->uuid(), disc->label() ); + return KConfigGroup( KGlobal::config(), discLabel ); + } + else + kDebug() << "profile: doesn't convert into Solid::StorageVolume"; + } + else + kDebug() << "profile: empty device list"; + } + //if not a disc, or Solid fails + return KConfigGroup( KGlobal::config(), url().prettyUrl() ); + } + + KUrl + TheStream::url() + { + return videoWindow()->m_media->currentSource().url(); + } + + bool + TheStream::canSeek() + { return videoWindow()->m_media->isSeekable(); } + + bool + TheStream::hasAudio() + { return true; } + + bool + TheStream::hasVideo() + { return videoWindow()->m_media->hasVideo(); } + + + bool + TheStream::hasMedia() + { + if(videoWindow()->m_media->currentSource().type() == Phonon::MediaSource::Invalid) + return false; + if(videoWindow()->m_media->currentSource().type() == Phonon::MediaSource::Empty) + return false; + //otherwise + return true; + } + + QSize + TheStream::defaultVideoSize() + { + return videoWindow()->m_vWidget->sizeHint(); + } + + int + TheStream::aspectRatio() + { + return engine()->m_vWidget->aspectRatio(); + } + + QAction* + TheStream::aspectRatioAction() + { + return s_aspectRatioActions[ engine()->m_vWidget->aspectRatio() ]; + } + + void + TheStream::addRatio( int aspectEnum, QAction* ratioAction ) + { + s_aspectRatioActions[aspectEnum] = ratioAction; + } + + int + TheStream::subtitleChannel() + { + return engine()->m_controller->currentSubtitle().index(); + } + + int + TheStream::audioChannel() + { + return engine()->m_controller->currentAudioChannel().index(); + } + + void + TheStream::setRatio( QAction* ratioAction ) + { + if( ratioAction ) + engine()->m_vWidget->setAspectRatio( (Phonon::VideoWidget::AspectRatio) s_aspectRatioActions.key( ratioAction ) ); + } + + QString + TheStream::prettyTitle() + { + const KUrl& url = videoWindow()->m_media->currentSource().url(); + QString artist, title; + + QStringList artists = videoWindow()->m_media->metaData(QLatin1String( "ARTIST" )); + if (!artists.isEmpty()) { + artist = artists.first(); + } + + QStringList titles = videoWindow()->m_media->metaData(QLatin1String( "TITLE" )); + if (!titles.isEmpty()) { + title = titles.first(); + } + + if (hasVideo() && !title.isEmpty()) + return title; + else if (!title.isEmpty() && !artist.isEmpty()) + return artist + QLatin1String( " - " ) + title; + else if (url.protocol() != QLatin1String( "http" ) && !url.fileName().isEmpty()) + { + const QString n = url.fileName(); + //toLatin1 sense fromPercentEncoding takes a QByteArray + //I'm not sure about this whole method though, should double check that titles make sense + //using QString::toLatin1() will display "????" in titlelabel. Should be QString::toUtf8(). patched by nihui, Jul.6th, 2008 + return QUrl::fromPercentEncoding( n.left( n.lastIndexOf( QLatin1Char( '.' ) ) ).replace( QLatin1Char( '_' ), QLatin1Char( ' ' ) ).toUtf8() ); //krazy:exclude-qclasses + + } + else + return url.prettyUrl(); + } + + QString + TheStream::fullTitle() + {/* + QString artist,album,title; + QStringList artists = m_mediaObject->metaData(Phonon::ArtistMetaData); + QStringList albums = m_mediaObject->metaData(Phonon::AlbumMetaData); + QStringList titles = m_mediaObject->metaData(Phonon::TitleMetaData) + title = (title ? titles.join( QLatin1String( " ") : "" ) ); + album = (albums ? albums.join( QLatin1String( " "): "" ) ); + artist = (artists ? artists.join( QLatin1String( " "): "" ) ); + + return title + "\n" + album + "\n" + artist;*/ + return QString(); + } + + bool + TheStream::hasProfile() + { + return KGlobal::config()->hasGroup( url().prettyUrl() ); + } + + QString + TheStream::metaData(Phonon::MetaData key) + { + QStringList values = videoWindow()->m_media->metaData(key); + kDebug() << values; + return (values.isEmpty()) ? QString() : values.join(QString(QLatin1Char( ' ' ))); + } + + +/* + static inline QString + entryHelper( const QString &plate, const QString &s1, const QString &s2 ) + { + return s2.isEmpty() ? s2 : plate.arg( s1 ).arg( s2 ); + } + + static inline QString + sectionHelper( const QString §ionTitle, const QStringList &entries ) + { + QString s; + + foreach( const QString& str, entries ) + if( !str.isEmpty() ) + s += str; + + return s.isEmpty() ? s : "

" + sectionTitle + "

" + s; + } + + QString + TheStream::information() + { + return QString(); +// #define meta( x ) xine_get_meta_info( VideoWindow::s_instance->m_stream, x ) +// #define info( x, y ) x.arg( xine_get_stream_info( VideoWindow::s_instance->m_stream, y ) ) +// #define simple( x ) QString::number( xine_get_stream_info( VideoWindow::s_instance->m_stream, x ) ) +// +// const QString plate = "

%1: %2

"; +// QString s; +// +// s += sectionHelper( i18n("Metadata"), +// QStringList() +// << entryHelper( plate, i18n("Title"), meta( XINE_META_INFO_TITLE ) ) +// << entryHelper( plate, i18n("Comment"), meta( XINE_META_INFO_COMMENT ) ) +// << entryHelper( plate, i18n("Artist"), meta( XINE_META_INFO_ARTIST ) ) +// << entryHelper( plate, i18n("Genre"), meta( XINE_META_INFO_GENRE ) ) +// << entryHelper( plate, i18n("Album"), meta( XINE_META_INFO_ALBUM ) ) +// << entryHelper( plate, i18n("Year"), meta( XINE_META_INFO_YEAR ) ) ); +// +// s += sectionHelper( i18n("Audio Properties"), +// QStringList() +// << entryHelper( plate, i18n("Bitrate"), info( i18n("%1 bps"), XINE_STREAM_INFO_AUDIO_BITRATE ) ) +// << entryHelper( plate, i18n("Sample-rate"), info( i18n("%1 Hz"), XINE_STREAM_INFO_AUDIO_SAMPLERATE ) ) ); +// +// s += sectionHelper( i18n("Technical Information"), +// QStringList() +// << entryHelper( plate, i18n("Video Codec"), meta( XINE_META_INFO_VIDEOCODEC ) ) +// << entryHelper( plate, i18n("Audio Codec"), meta( XINE_META_INFO_AUDIOCODEC ) ) +// << entryHelper( plate, i18n("System Layer"), meta( XINE_META_INFO_SYSTEMLAYER ) ) +// << entryHelper( plate, i18n("Input Plugin"), meta( XINE_META_INFO_INPUT_PLUGIN )) +// << entryHelper( plate, i18n("CDINDEX_DISCID"), meta( XINE_META_INFO_CDINDEX_DISCID ) ) ); +// +// QStringList texts; +// texts << "BITRATE" << "SEEKABLE" << "VIDEO_WIDTH" << "VIDEO_HEIGHT" << "VIDEO_RATIO" << "VIDEO_CHANNELS" << "VIDEO_STREAMS" << "VIDEO_BITRATE" << "VIDEO_FOURCC" << "VIDEO_HANDLED" << "FRAME_DURATION" << "AUDIO_CHANNELS" << "AUDIO_BITS" << "-AUDIO_SAMPLERATE" << "-AUDIO_BITRATE" << "AUDIO_FOURCC" << "AUDIO_HANDLED" << "HAS_CHAPTERS" << "HAS_VIDEO" << "HAS_AUDIO" << "-IGNORE_VIDEO" << "-IGNORE_AUDIO" << "-IGNORE_SPU" << "VIDEO_HAS_STILL" << "MAX_AUDIO_CHANNEL" << "MAX_SPU_CHANNEL" << "AUDIO_MODE" << "SKIPPED_FRAMES" << "DISCARDED_FRAMES"; +// +// s += "

Other

"; +// for( uint x = 0; x <= 28; ++x ) +// s += entryHelper( plate, texts[x], simple( x ) ); +// +// #undef meta +// #undef info +// #undef simple +// +// return s; + } +*/ +} + diff --git a/dragon/src/app/theStream.h b/dragon/src/app/theStream.h new file mode 100644 index 00000000..ee463266 --- /dev/null +++ b/dragon/src/app/theStream.h @@ -0,0 +1,81 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_THESTREAM_H +#define DRAGONPLAYER_THESTREAM_H + +#include +#include // larger :( but no macros at least +#include // small header +#include // small header +#include + +/// for purely static classes +#define DRAGONPLAYER_NO_EXPORT( T ) \ + T(); \ + ~T(); \ + T( const T& ); \ + T &operator=( const T& ); \ + bool operator==( const T& ); \ + bool operator!=( const T& ); + +class QAction; + +namespace Dragon +{ + class TheStream + { + DRAGONPLAYER_NO_EXPORT( TheStream ) + + public: + static KUrl url(); + + static bool canSeek(); + static bool hasAudio(); + static bool hasVideo(); + static bool hasMedia(); + + static QSize defaultVideoSize(); + + static int aspectRatio(); + static QAction* aspectRatioAction(); + static void setRatio( QAction* ); + static void addRatio( int, QAction* ); + + static const char* CHANNEL_PROPERTY; + static int subtitleChannel(); + static int audioChannel(); + + static QString prettyTitle(); + static QString fullTitle(); + + + static QString metaData(Phonon::MetaData key); + + static bool hasProfile(); + + static KConfigGroup profile(); + private: + static QHash s_aspectRatioActions; + }; +} + +#endif diff --git a/dragon/src/app/timeLabel.cpp b/dragon/src/app/timeLabel.cpp new file mode 100644 index 00000000..365119b8 --- /dev/null +++ b/dragon/src/app/timeLabel.cpp @@ -0,0 +1,92 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2008 David Edmundson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "timeLabel.h" +#include + +#include +#include +#include + +TimeLabel::TimeLabel( QWidget *parent ) + : QLabel( QLatin1String( " 0:00:00 " ), parent ) + , m_currentTime( 0 ) +{ + setFont( KGlobalSettings::fixedFont() ); + setAlignment( Qt::AlignCenter ); + setMinimumSize( sizeHint() ); + KConfigGroup config = KGlobal::config()->group( "General" ); + m_timeFormat = static_cast( config.readEntry( "TimeFormat", static_cast( SHOW_COMPLETED ) ) ); +} + +TimeLabel::~TimeLabel() +{ + KConfigGroup config = KGlobal::config()->group( "General" ); + config.writeEntry( "TimeFormat", static_cast( m_timeFormat ) ); +} + +void +TimeLabel::mousePressEvent( QMouseEvent * ) +{ + if( m_timeFormat == SHOW_REMAINING ) + m_timeFormat = SHOW_COMPLETED; + else + m_timeFormat = SHOW_REMAINING; + updateTime(); +} + +void +TimeLabel::updateTime() +{ + qint64 ms; +#define zeroPad( n ) n < 10 ? QString::fromLatin1("0%1").arg( n ) : QString::number( n ) + if( m_timeFormat == SHOW_REMAINING ) + ms = m_totalTime - m_currentTime; + else + ms = m_currentTime; + const int s = ms / 1000; + const int m = s / 60; + const int h = m / 60; + QString time = zeroPad( s % 60 ); //seconds + time.prepend( QLatin1Char( ':' ) ); + time.prepend( zeroPad( m % 60 ) ); //minutes + time.prepend( QLatin1Char( ':' ) ); + time.prepend( QString::number( h ) ); //hours + if( m_timeFormat == SHOW_REMAINING ) + time.prepend(QLatin1Char( '-' )); + setText( time ); +} + +void +TimeLabel::setCurrentTime( qint64 time ) +{ + m_currentTime = time; + updateTime(); +} + +void +TimeLabel::setTotalTime( qint64 time ) +{ + m_totalTime = time; + updateTime(); +} + +#include "timeLabel.moc" diff --git a/dragon/src/app/timeLabel.h b/dragon/src/app/timeLabel.h new file mode 100644 index 00000000..ca887967 --- /dev/null +++ b/dragon/src/app/timeLabel.h @@ -0,0 +1,44 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef TIMELABEL_H +#define TIMELABEL_H + +#include + +class TimeLabel : public QLabel +{ + Q_OBJECT + public: + explicit TimeLabel( QWidget *parent ); + virtual ~TimeLabel(); + virtual void mousePressEvent( QMouseEvent * ); + virtual void updateTime(); + enum TimeFormats { SHOW_REMAINING,SHOW_COMPLETED }; + public slots: + void setCurrentTime( qint64 ); + void setTotalTime( qint64 ); + private: + TimeFormats m_timeFormat; + qint64 m_currentTime; + qint64 m_totalTime; +}; + +#endif diff --git a/dragon/src/app/videoSettingsWidget.ui b/dragon/src/app/videoSettingsWidget.ui new file mode 100644 index 00000000..326a94f7 --- /dev/null +++ b/dragon/src/app/videoSettingsWidget.ui @@ -0,0 +1,158 @@ + + + Dragon::VideoSettingsWidget + + + + 0 + 0 + 203 + 148 + + + + + + + + + Brightness: + + + + + + + -100 + + + 100 + + + Qt::Horizontal + + + QSlider::NoTicks + + + + + + + Contrast: + + + + + + + -100 + + + 100 + + + Qt::Horizontal + + + QSlider::NoTicks + + + + + + + Hue: + + + + + + + -100 + + + 100 + + + Qt::Horizontal + + + QSlider::NoTicks + + + + + + + Saturation: + + + + + + + -100 + + + 100 + + + Qt::Horizontal + + + QSlider::NoTicks + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Restore Defaults + + + + + + + Close + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + diff --git a/dragon/src/app/videoWindow.cpp b/dragon/src/app/videoWindow.cpp new file mode 100644 index 00000000..0bd4fb10 --- /dev/null +++ b/dragon/src/app/videoWindow.cpp @@ -0,0 +1,844 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Christoph Pfister + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include + + +#include "videoWindow.h" +#include "timeLabel.h" + +#include "actions.h" //::seek() FIXME unfortunate +#include "theStream.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef Q_WS_WIN +#include +#endif + +#if defined(HAVE_UNISTD_H) +#include +#endif + +using Phonon::AudioOutput; +using Phonon::MediaObject; +using Phonon::VideoWidget; +using Phonon::SeekSlider; +using Phonon::VolumeSlider; +using Phonon::MediaController; + +namespace Dragon { + + +VideoWindow *VideoWindow::s_instance = 0; + +VideoWindow::VideoWindow( QWidget *parent ) + : QWidget( parent ) + , m_cursorTimer( new QTimer( this ) ) + , m_justLoaded( false ) + , m_adjustedSize( false) + , m_subLanguages( new QActionGroup( this ) ) + , m_audioLanguages( new QActionGroup( this ) ) + , m_logo( new QLabel( this ) ) + , m_initialOffset( 0 ) + , m_aDataOutput(0) +{ + + m_isPreview = false; + + s_instance = this; + setObjectName( QLatin1String( "VideoWindow" ) ); + + QVBoxLayout *box = new QVBoxLayout( this ); + box->setMargin(0); + box->setSpacing(0); + m_vWidget = new VideoWidget( this ); + m_vWidget->hide(); + box->addWidget( m_vWidget ); + m_aOutput = new AudioOutput( Phonon::VideoCategory, this ); + m_media = new MediaObject( this ); + m_controller = new MediaController( m_media ); + Phonon::createPath(m_media, m_vWidget); + m_audioPath = Phonon::createPath(m_media, m_aOutput); + m_media->setTickInterval( 1000 ); + connect( m_media, SIGNAL(tick(qint64)), this, SIGNAL(tick(qint64)) ); + connect( m_media, SIGNAL(currentSourceChanged(Phonon::MediaSource)), this, SIGNAL(currentSourceChanged(Phonon::MediaSource)) ); + connect( m_media, SIGNAL(totalTimeChanged(qint64)), this, SIGNAL(totalTimeChanged(qint64)) ); + connect( m_media, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool)) ); + connect( m_media, SIGNAL(metaDataChanged()), this, SIGNAL(metaDataChanged()) ); + connect( m_aOutput, SIGNAL(mutedChanged(bool)), this, SIGNAL(mutedChanged(bool)) ); + connect( m_aOutput, SIGNAL(volumeChanged(qreal)), this, SIGNAL(volumeChanged(qreal))); + connect( m_media, SIGNAL(hasVideoChanged(bool)), this, SIGNAL(hasVideoChanged(bool)) ); + connect( m_media, SIGNAL(hasVideoChanged(bool)), m_vWidget, SLOT(setVisible(bool)) ); //hide video widget if no video to show + connect( m_media, SIGNAL(hasVideoChanged(bool)), m_logo, SLOT(setHidden(bool)) ); + + connect( m_controller, SIGNAL(availableSubtitlesChanged()), this, SLOT(updateChannels()) ); + + { + m_subLanguages->setExclusive( true ); + QAction* turnOff = new QAction( i18n("&DVD Subtitle Selection"), m_subLanguages ); + turnOff->setCheckable( true ); + turnOff->setProperty( TheStream::CHANNEL_PROPERTY, -1 ); + connect( turnOff, SIGNAL(triggered()), this, SLOT(slotSetSubtitle()) ); + + QAction* separator = new QAction( m_subLanguages ); + separator->setSeparator( true ); + } + { + m_audioLanguages->setExclusive( true ); + QAction* autoLang = new QAction( i18n("&Auto"), m_audioLanguages ); + autoLang->setProperty( TheStream::CHANNEL_PROPERTY, -1 ); + autoLang->setCheckable( true ); + connect( autoLang, SIGNAL(triggered()), this, SLOT(slotSetAudio()) ); + + QAction* separator = new QAction( m_audioLanguages ); + separator->setSeparator( true ); + } + + connect( m_media, SIGNAL(stateChanged(Phonon::State,Phonon::State)), this, SLOT(stateChanged(Phonon::State,Phonon::State)) ); + connect( m_cursorTimer, SIGNAL(timeout()), this, SLOT(hideCursor()) ); + m_cursorTimer->setSingleShot( true ); + { + m_logo->setAutoFillBackground( true ); + QPalette pal; + pal.setColor( QPalette::Window, Qt::white ); + m_logo->setPalette( pal ); + QLayout* layout = new QVBoxLayout( m_logo ); + layout->setAlignment( Qt::AlignCenter ); + m_logo->setLayout( layout ); + box->addWidget( m_logo ); + m_logo->show(); + } + { + KConfigGroup config = KGlobal::config()->group( "General" ); + m_aOutput->setVolume( config.readEntry( "Volume", 1.0 ) ); + } +} + +VideoWindow::~VideoWindow() +{ + eject(); + KConfigGroup config = KGlobal::config()->group( "General" ); + config.writeEntry( "Volume", static_cast( m_aOutput->volume() ) ); +} + +bool +VideoWindow::init() +{ + return m_media->state() != Phonon::ErrorState; +} + +bool +VideoWindow::load( const KUrl &url ) +{ + QApplication::setOverrideCursor( Qt::WaitCursor ); + + eject(); + + KMimeType::Ptr mimeType = KMimeType::findByUrl( url ); + kDebug() << "detected mimetype: " << mimeType->name(); + if( mimeType->is( QLatin1String( "application/x-cd-image" ) ) || mimeType->is( QLatin1String( "inode/directory" ) ) ) + m_media->setCurrentSource( Phonon::MediaSource( Phonon::Dvd, url.path() ) ); + else + m_media->setCurrentSource( url ); + m_justLoaded = true; + m_adjustedSize=false; + + QApplication::restoreOverrideCursor(); + + return true; +} + +bool +VideoWindow::play( qint64 offset ) +{ + + QApplication::setOverrideCursor( Qt::WaitCursor ); + + m_justLoaded = false; + m_initialOffset = offset; + m_media->play(); + + QApplication::restoreOverrideCursor(); + + return true; +} + +bool +VideoWindow::resume() +{ + m_media->play(); + return true; +} + +bool +VideoWindow::playDvd() +{ + eject(); + m_media->setCurrentSource( Phonon::MediaSource( Phonon::Dvd ) ); + m_media->play(); + return true; +} + +bool +VideoWindow::playDisc(const Solid::Device& device ) +{ + QString devicePath; + { + const Solid::Block* block = device.as(); + if( block ) + devicePath = block->device(); + else + { + kDebug() << "device was not a block"; + return false; + } + } + const Solid::OpticalDisc* disc = device.as(); + if( disc ) + { + Phonon::DiscType phononType = Phonon::NoDisc; + { + Solid::OpticalDisc::ContentTypes solidType = disc->availableContent(); + if (solidType & Solid::OpticalDisc::VideoDvd) + phononType = Phonon::Dvd; + if (solidType & (Solid::OpticalDisc::VideoCd | Solid::OpticalDisc::SuperVideoCd)) + phononType = Phonon::Vcd; + if (solidType & Solid::OpticalDisc::Audio) + phononType = Phonon::Cd; + + // No change -> cannot play the disc.... should not really happen as + // mainWindow already preprocesses the type, so this would indicate + // bogus handling in one of the classes -> assertation. + Q_ASSERT(phononType != Phonon::NoDisc); + if (phononType == Phonon::NoDisc){ + kDebug() << "not a playable disc type: " << disc->availableContent() << " type"; + return false; + } + } + eject(); + m_media->setCurrentSource( Phonon::MediaSource( phononType, devicePath ) ); + kDebug() << "actually playing the disc at " << devicePath; + m_media->play(); + return true; + } + else + { + kDebug() << "device was not a disc"; + return false; + } +} + +bool +VideoWindow::isPreview(const bool &v) +{ + if( v ) + { + m_isPreview = v; + } + return m_isPreview; +} + +void +VideoWindow::relativeSeek( qint64 step ) +{ + kDebug() << "** relative seek"; + const qint64 new_pos = currentTime() + step; + if( ( new_pos >= 0 ) && ( new_pos < length() ) ) + { + seek( new_pos ); + play(); + } + else if( new_pos < 0 ) + { + seek( 0 ); + play(); + } +} + +void +VideoWindow::stop() +{ + kDebug() << "Stop called"; + eject(); + m_media->stop(); + m_media->setCurrentSource(Phonon::MediaSource()); //set the current source to Phonon::MediaSource::Empty + kDebug() << "Media source valid? "<< TheStream::hasMedia(); + m_vWidget->hide(); + m_logo->show(); +} + +void +VideoWindow::pause() +{ + m_media->pause(); +} + +void +VideoWindow::playPause() +{ + if(m_media->state() == Phonon::PlayingState) + pause(); + else + resume(); +} + +QString +VideoWindow::urlOrDisc() const +{ + Phonon::MediaSource source = m_media->currentSource(); + switch( source.type() ) + { + case Phonon::MediaSource::Invalid: + case Phonon::MediaSource::Empty: + return QLatin1String( "Invalid" ); //no i18n, used for DBus responses + break; + case Phonon::MediaSource::Url: + case Phonon::MediaSource::LocalFile: + return source.url().toString(); + break; + case Phonon::MediaSource::Disc: + return source.deviceName(); + break; + case Phonon::MediaSource::Stream: + return QLatin1String( "Data Stream" ); + break; + default: + break; + } + return QLatin1String( "Error" ); +} + +Phonon::MediaSource::Type +VideoWindow::mediaSourceType() const +{ + return m_media->currentSource().type(); +} + +QMultiMap +VideoWindow::metaData() const +{ + return m_media->metaData(); +} + +bool +VideoWindow::isSeekable() const +{ + return m_media->isSeekable(); +} + + +Phonon::State +VideoWindow::state() const +{ + return m_media->state(); +} + +qreal +VideoWindow::volume() const +{ + return m_aOutput->volume(); +} + +void +VideoWindow::setVolume( qreal vol ) +{ + m_aOutput->setVolume( vol ); +} + +void +VideoWindow::mute(bool muted) +{ + m_aOutput->setMuted( muted ); +} + +bool +VideoWindow::isMuted() +{ + return m_aOutput->isMuted(); +} + +void +VideoWindow::seek( qint64 pos ) +{ + m_media->seek( pos ); +} + +qint32 +VideoWindow::tickInterval() const +{ + return m_media->tickInterval(); +} + +void +VideoWindow::showOSD( const QString &/*message*/ ) +{ + return; +} + +void +VideoWindow::resetZoom() +{ + TheStream::profile().deleteEntry( "Preferred Size" ); + window()->adjustSize(); +} + +qint64 +VideoWindow::currentTime() const +{ + return m_media->currentTime(); +} + +qint64 +VideoWindow::length() const +{ + return m_media->totalTime(); +} + +bool +VideoWindow::setupAnalyzer(QObject* analyzer) +{ + if(!m_aDataOutput) + { + m_aDataOutput = new Phonon::AudioDataOutput(this); + m_audioDataPath = Phonon::createPath(m_media, m_aDataOutput); + connect(m_aDataOutput, SIGNAL(dataReady(QMap >)), + analyzer, SLOT(drawFrame(QMap >))); + } + + return m_audioDataPath.isValid(); +} + +bool +VideoWindow::isDVD() const +{ + return m_media->currentSource().discType() == Phonon::Dvd; +} + +QWidget* +VideoWindow::newPositionSlider() +{ + SeekSlider *seekSlider = new SeekSlider(); + seekSlider->setIconVisible( false ); + seekSlider->setMediaObject( m_media ); + seekSlider->setSingleStep( 5000 ); + return seekSlider; +} + +QWidget* +VideoWindow::newVolumeSlider() +{ + VolumeSlider *volumeSlider = new VolumeSlider(); + volumeSlider->setObjectName( QLatin1String( "volume" ) ); + volumeSlider->setAudioOutput( m_aOutput ); + volumeSlider->setMuteVisible( false ); + volumeSlider->setOrientation( Qt::Vertical ); + return volumeSlider; +} + +void +VideoWindow::stateChanged(Phonon::State currentState, Phonon::State oldstate) // slot +{ +kDebug() << "chapters: " << m_controller->availableChapters() << " titles: " << m_controller->availableTitles(); + QStringList states; + states << QLatin1String( "Loading" ) << QLatin1String( "Stopped" ) << QLatin1String( "Playing" ) << QLatin1String( "Buffering" ) << QLatin1String( "Paused" ) << QLatin1String( "Error" ); + kDebug() << "going from " << states.at(oldstate) << " to " << states.at(currentState); + + if( currentState == Phonon::PlayingState && m_initialOffset > 0) { + seek(m_initialOffset); + m_initialOffset = 0; + } + + if( currentState == Phonon::PlayingState && m_media->hasVideo() ) + { + m_logo->hide(); + m_vWidget->show(); + updateChannels(); + + if(m_adjustedSize==false) + { + if( mainWindow() ) + ( (QWidget*) mainWindow() )->adjustSize(); + m_adjustedSize=true; + kDebug() << "adjusting size to video resolution"; + } + } + emit stateUpdated( currentState, oldstate ); +} + +void +VideoWindow::settingChanged( int setting ) +{ + const QString name = sender()->objectName(); + const double dSetting = static_cast( setting ) * 0.01; + kDebug() << "setting " << name << " to " << dSetting; + if( name == QLatin1String( "brightnessSlider" ) ) + { + m_vWidget->setBrightness( dSetting ); + } + else if( name == QLatin1String( "contrastSlider" ) ) + { + m_vWidget->setContrast( dSetting ); + } + else if( name == QLatin1String( "hueSlider" ) ) + { + m_vWidget->setHue( dSetting ); + } + else if( name == QLatin1String( "saturationSlider" ) ) + { + m_vWidget->setSaturation( dSetting ); + } +} + +void +VideoWindow::loadSettings() +{ + if( TheStream::hasProfile() ) + { + KConfigGroup profile = TheStream::profile(); + m_vWidget->setBrightness( profile.readEntry( "Brightness", 0.0 ) ); + m_vWidget->setContrast( profile.readEntry( "Contrast", 0.0 ) ); + m_vWidget->setHue( profile.readEntry( "Hue", 0.0 ) ); + m_vWidget->setSaturation( profile.readEntry( "Saturation", 0.0 ) ); + setAudioChannel( profile.readEntry( "AudioChannel", -1 ) ); + setSubtitle( profile.readEntry( "Subtitle", -1 ) ); + } + else + { + m_vWidget->setBrightness( 0.0 ); + m_vWidget->setContrast( 0.0 ); + m_vWidget->setHue( 0.0 ); + m_vWidget->setSaturation( 0.0 ); + } +} + +template void +VideoWindow::updateActionGroup( QActionGroup* channelActions + , const QList& availableChannels + , const char* actionSlot ) +{ + { + QList subActions = channelActions->actions(); + while( 2 < subActions.size() ) + delete subActions.takeLast(); + } + foreach( const ChannelDescription &channel, availableChannels ) + { + QAction* lang = new QAction( channelActions ); + kDebug() << "the text is: \"" << channel.name() << "\" and index " << channel.index(); + lang->setCheckable( true ); + lang->setText( channel.name() ); + lang->setProperty( TheStream::CHANNEL_PROPERTY, channel.index() ); + connect( lang, SIGNAL(triggered()), this, actionSlot ); + } +} + +void +VideoWindow::updateChannels() +{ + updateActionGroup( m_subLanguages, m_controller->availableSubtitles(), SLOT(slotSetSubtitle()) ); + emit subChannelsChanged( m_subLanguages->actions() ); + updateActionGroup( m_audioLanguages, m_controller->availableAudioChannels(), SLOT(slotSetAudio()) ); + emit audioChannelsChanged( m_audioLanguages->actions() ); +} + +void +VideoWindow::hideCursor() +{ + if(m_media->hasVideo() && m_vWidget->underMouse() ) + kapp->setOverrideCursor( Qt::BlankCursor ); +} + +void +VideoWindow::setSubtitle( int channel ) +{ + Phonon::SubtitleDescription desc = Phonon::SubtitleDescription::fromIndex( channel ); + kDebug() << "using index: " << channel << " returned desc has index: " << desc.index(); + if(desc.isValid()) + m_controller->setCurrentSubtitle( desc ); +} + +void +VideoWindow::slotSetSubtitle() +{ + if( sender() && sender()->property( TheStream::CHANNEL_PROPERTY ).canConvert() ) + setSubtitle( sender()->property( TheStream::CHANNEL_PROPERTY ).toInt() ); +} + +void +VideoWindow::setAudioChannel( int channel ) +{ + Phonon::AudioChannelDescription desc = Phonon::AudioChannelDescription::fromIndex( channel ); + kDebug() << "using index: " << channel << " returned desc has index: " << desc.index(); + if(desc.isValid()) + m_controller->setCurrentAudioChannel( desc ); +} + +void +VideoWindow::slotSetAudio() +{ + if( sender() && sender()->property( TheStream::CHANNEL_PROPERTY ).canConvert() ) + setAudioChannel( sender()->property( TheStream::CHANNEL_PROPERTY ).toInt() ); +} + +void +VideoWindow::toggleDVDMenu() +{ +#if PHONON_VERSION >= PHONON_VERSION_CHECK(4, 5, 0) + m_controller->setCurrentMenu(MediaController::RootMenu); +#endif +} + +int +VideoWindow::videoSetting( const QString& setting ) +{ + double dValue = 0.0; + if( setting == QLatin1String( "brightnessSlider" ) ) + { + dValue = m_vWidget->brightness(); + } + else if( setting == QLatin1String( "contrastSlider" ) ) + { + dValue = m_vWidget->contrast(); + } + else if( setting == QLatin1String( "hueSlider" ) ) + { + dValue = m_vWidget->hue(); + } + else if( setting == QLatin1String( "saturationSlider" ) ) + { + dValue = m_vWidget->saturation(); + } + return static_cast( dValue * 100.0 ); +} + +void +VideoWindow::prevChapter() + { + m_controller->setCurrentChapter(m_controller->currentChapter() - 1); + } + +void +VideoWindow::nextChapter() + { + m_controller->setCurrentChapter(m_controller->currentChapter() + 1); + } + +void +VideoWindow::tenPercentBack() +{ + qint64 newTime = m_media->currentTime() - (m_media->totalTime() / 10); + if (newTime > 0) + m_media -> seek (newTime); + else + m_media -> seek ( 0 ) ; + } + +void +VideoWindow::tenPercentForward() +{ + qint64 newTime = m_media->currentTime() + (m_media->totalTime() / 10); + if (newTime < m_media->totalTime()) + m_media -> seek (newTime); +} + +void +VideoWindow::tenSecondsBack() +{ + relativeSeek( -10000 ); +} + +void +VideoWindow::tenSecondsForward() +{ + relativeSeek( 10000 ); +} + +void +VideoWindow::increaseVolume() +{ + m_aOutput->setVolume(qMin(qreal(1.0), volume() + qreal(0.10))); +} + +void +VideoWindow::decreaseVolume() +{ + m_aOutput->setVolume(qMax(qreal(0.0), volume() - qreal(0.10))); +} + +/////////// +///Protected +/////////// + +bool +VideoWindow::event( QEvent* event ) +{ + switch( event->type() ) + { + case QEvent::Leave: + m_cursorTimer->stop(); kDebug() << "stop cursorTimer"; + break; + case QEvent::FocusOut: + // if the user summons some dialog via a shortcut or whatever we need to ensure + // the mouse gets shown, because if it is modal, we won't get mouse events after + // it is shown! This works because we are always the focus widget. + // @see MainWindow::MainWindow where we setFocusProxy() + case QEvent::Enter: + case QEvent::MouseMove: + case QEvent::MouseButtonPress: + kapp->restoreOverrideCursor(); + m_cursorTimer->start( CURSOR_HIDE_TIMEOUT ); + break; + default: return QWidget::event( event ); + } + return false; +} + +void +VideoWindow::contextMenuEvent( QContextMenuEvent * event ) +{ + KMenu menu; + if( mainWindow() ) + { + menu.addAction( action( "play" ) ); + menu.addAction( action( "fullscreen" ) ); + menu.addAction( action( "reset_zoom" ) ); + if(isDVD()) + { + menu.addAction( action( "toggle_dvd_menu" ) ); + } + } + menu.exec( event->globalPos() ); +} + +void +VideoWindow::mouseDoubleClickEvent( QMouseEvent* ) +{ + if( mainWindow() ) //TODO: add full screen mode to kpart + action("fullscreen")->toggle(); +} + +QSize +VideoWindow::sizeHint() const //virtual +{ + QSize s = TheStream::profile().readEntry( "Preferred Size", QSize() ); + + if( !s.isValid() ) + s = TheStream::defaultVideoSize(); + + if( s.isValid() && !s.isNull() ) + return s; + + return QWidget::sizeHint(); +} + +/////////// +///Private +/////////// +void +VideoWindow::eject() +{ + if( m_media->currentSource().type() == Phonon::MediaSource::Invalid ) + return; + + if( m_media->currentSource().type() == Phonon::MediaSource::Empty ) + return; + + + KConfigGroup profile = TheStream::profile(); // the config profile for this video file + + Phonon::State state = m_media->state(); + if( ( ( state == Phonon::PlayingState || state == Phonon::PausedState ) ) + && ( m_media->remainingTime() > 5000 ) ) // if we are really close to the end, don't remember the position + profile.writeEntry( "Position", currentTime() ); + else + profile.deleteEntry( "Position" ); + + const QSize s = videoWindow()->size(); + const QSize defaultSize = TheStream::defaultVideoSize(); + if( defaultSize.isValid() && ( s.width() == defaultSize.width() || s.height() == defaultSize.height() ) ) + profile.deleteEntry( "Preferred Size" ); + else + profile.writeEntry( "Preferred Size", s ); + + profile.writeEntry( "Contrast", m_vWidget->contrast() ); + profile.writeEntry( "Brightness", m_vWidget->brightness() ); + profile.writeEntry( "Hue", m_vWidget->hue() ); + profile.writeEntry( "Saturation", m_vWidget->saturation() ); + profile.writeEntry( "IsVideo",m_media->hasVideo()); + { + //this if clause - is to prevent a crash from bug 162721 (a Phonon bug), remove when fixed + if(m_media->hasVideo()) + { + kDebug() << "trying to fetch subtitle information"; + const int subtitle = TheStream::subtitleChannel(); + const int audio = TheStream::audioChannel(); + kDebug() << "fetched subtitle information"; + + + if( subtitle != -1 ) + profile.writeEntry( "Subtitle", subtitle ); + else + profile.deleteEntry( "Subtitle" ); + + if( audio != -1 ) + profile.writeEntry( "AudioChannel", audio ); + else + profile.deleteEntry( "AudioChannel" ); + } + + } + profile.writeEntry( "Date", QDate::currentDate().toString("dd/MM/yyyy") ); + profile.sync(); +} + +} //namespace Dragon + +#include "videoWindow.moc" diff --git a/dragon/src/app/videoWindow.h b/dragon/src/app/videoWindow.h new file mode 100644 index 00000000..ab61ed0f --- /dev/null +++ b/dragon/src/app/videoWindow.h @@ -0,0 +1,189 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * 2007 Ian Monroe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_VIDEOWINDOW_H +#define DRAGONPLAYER_VIDEOWINDOW_H + +#include "codeine.h" + +#include +#include + +#include +#include +#include +#include +#include + +class QActionGroup; +class QTimer; + +namespace Phonon { + class VideoWidget; + class AudioOutput; + class MediaObject; + class MediaController; + class AudioDataOutput; +} + +#include "phonon/phononnamespace.h" //Phonon::State + +namespace Dragon +{ + class VideoWindow : public QWidget + { + Q_OBJECT + + public: + static VideoWindow *s_instance; + + private: + VideoWindow( const VideoWindow& ); //disable + VideoWindow &operator=( const VideoWindow& ); //disable + void eject(); + + QTimer* m_cursorTimer; + bool m_justLoaded; + bool m_adjustedSize; + QActionGroup* m_subLanguages; + QActionGroup* m_audioLanguages; + QWidget* m_logo; + bool m_isPreview; + quint64 m_initialOffset; + + Phonon::VideoWidget *m_vWidget; + Phonon::AudioOutput *m_aOutput; + Phonon::MediaObject *m_media; + Phonon::MediaController *m_controller; + Phonon::AudioDataOutput* m_aDataOutput; + Phonon::Path m_audioPath; + Phonon::Path m_audioDataPath; + + friend class TheStream; + + template + void updateActionGroup( QActionGroup* channelActions, const QList& availableChannels + , const char* actionSlot ); + + public: + VideoWindow( QWidget *parent ); + ~VideoWindow(); + + bool init(); + + bool load( const KUrl &url ); + bool play( qint64 = 0 ); + bool resume(); + bool playDvd(); + bool playDisc( const Solid::Device& ); + bool isMuted(); + bool isPreview(const bool &v = 0); + void relativeSeek( qint64 ); + + qint64 length() const; + bool isDVD() const; + + bool setupAnalyzer(QObject* analzyer); ///return whether setup was successful + + ///stuff for dbus: + qreal volume() const; + void setVolume( qreal ); + QString urlOrDisc() const; + QMultiMap metaData() const; + Phonon::MediaSource::Type mediaSourceType() const; + bool isSeekable() const; + qint32 tickInterval() const; + //} + + QWidget* newPositionSlider(); + QWidget* newVolumeSlider(); + void loadSettings(); + + Phonon::State state() const; + + /// Stuff to do with video and the video window/widget + static const uint CURSOR_HIDE_TIMEOUT = 2000; + + void becomePreferredSize(); + + enum { ExposeEvent = 3000 }; + + qint64 currentTime() const; + int videoSetting( const QString& ); + + public slots: + void pause(); + void playPause(); + void seek( qint64 ); + void stop(); + void stateChanged( Phonon::State, Phonon::State ); + void settingChanged( int ); + void mute( bool ); + + void toggleDVDMenu(); + void showOSD( const QString& ); + void slotSetSubtitle(); + void slotSetAudio(); + void resetZoom(); + + void prevChapter(); + void nextChapter(); + void tenPercentBack(); + void tenPercentForward(); + void tenSecondsBack(); + void tenSecondsForward(); + + void increaseVolume(); + void decreaseVolume(); + + protected: + virtual bool event( QEvent* e ); + virtual void contextMenuEvent( QContextMenuEvent * event ); + virtual void mouseDoubleClickEvent( QMouseEvent* ); + virtual QSize sizeHint() const; + Phonon::State state( Phonon::State state ) const; + void setSubtitle( int channel ); + void setAudioChannel( int channel ); + private slots: + void updateChannels(); + void hideCursor(); + signals: + void stateUpdated( const Phonon::State, const Phonon::State ); + void subChannelsChanged( QList< QAction* > ); + void audioChannelsChanged( QList< QAction* > ); + void tick( qint64 ); + void currentSourceChanged( const Phonon::MediaSource); + void totalTimeChanged( qint64 ); + void mutedChanged( bool ); + void seekableChanged( bool ); + void metaDataChanged(); + void hasVideoChanged( bool ); + void volumeChanged( qreal ); + }; + + //global function for general use by Dragon Player + + //rearranged from previous non-static functions due to compiler warning + static inline VideoWindow* engine() {return VideoWindow::s_instance;} + static inline VideoWindow* videoWindow() {return VideoWindow::s_instance; } +} + +#endif diff --git a/dragon/src/codeine.h b/dragon/src/codeine.h new file mode 100644 index 00000000..79b18fc4 --- /dev/null +++ b/dragon/src/codeine.h @@ -0,0 +1,42 @@ +/*********************************************************************** +* Copyright 2005 Max Howell +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License or (at your option) version 3 or any later version +* accepted by the membership of KDE e.V. (or its successor approved +* by the membership of KDE e.V.), which shall act as a proxy +* defined in Section 14 of version 3 of the license. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +***********************************************************************/ +#ifndef DRAGONPLAYER_H +#define DRAGONPLAYER_H + +// try to keep this file light. It gets included by +// practically every implementation and many headers + +namespace Engine +{} + +class QWidget; + +namespace Dragon +{ + QWidget *mainWindow(); //defined in mainWindow.cpp +} + +/// used by mainWindow.h +int main( int, char** ); + +#define APP_VERSION "2.0" +#define APP_NAME "dragonplayer" + +#endif diff --git a/dragon/src/messageBox.h b/dragon/src/messageBox.h new file mode 100644 index 00000000..6e6ecf8d --- /dev/null +++ b/dragon/src/messageBox.h @@ -0,0 +1,45 @@ +/*********************************************************************** + * Copyright 2005 Max Howell + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_MESSAGEBOX +#define DRAGONPLAYER_MESSAGEBOX + +#include +namespace Dragon { + static class VideoWindow* videoWindow(); + namespace MessageBox + { + static inline void error( const QString &message ) + { + KMessageBox::error( (QWidget*)videoWindow(), message ); + } + + static inline void sorry( const QString &message ) + { + KMessageBox::error( (QWidget*)videoWindow(), message ); + } + + static inline void information( const QString &message, const QString &title ) + { + KMessageBox::information( (QWidget*)videoWindow(), message, title ); + } + } +} +#endif diff --git a/dragon/src/mpris2/mediaplayer2.cpp b/dragon/src/mpris2/mediaplayer2.cpp new file mode 100644 index 00000000..51f1d8e7 --- /dev/null +++ b/dragon/src/mpris2/mediaplayer2.cpp @@ -0,0 +1,124 @@ +/*********************************************************************** + * Copyright 2012 Eike Hein + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "mediaplayer2.h" +#include "actions.h" +#include "codeine.h" +#include "mpris2.h" +#include "theStream.h" +#include "videoWindow.h" + + +#include +#include +#include +#include +#include +#include + +MediaPlayer2::MediaPlayer2(QObject* parent) : QDBusAbstractAdaptor(parent) +{ + connect(Dragon::action("fullscreen"), SIGNAL(toggled(bool)), this, SLOT(emitFullscreenChange(bool))); + connect(Dragon::videoWindow(), SIGNAL(hasVideoChanged(bool)), this, SLOT(emitFullscreenChange(bool))); +} + +MediaPlayer2::~MediaPlayer2() +{ +} + +bool MediaPlayer2::CanQuit() const +{ + return true; +} + +void MediaPlayer2::Quit() const +{ + kapp->closeAllWindows(); +} + +bool MediaPlayer2::CanRaise() const +{ + return true; +} + +void MediaPlayer2::Raise() const +{ + Dragon::mainWindow()->raise(); + KWindowSystem::forceActiveWindow(Dragon::mainWindow()->winId()); +} + +bool MediaPlayer2::Fullscreen() const +{ + return Dragon::action("fullscreen")->isChecked(); +} + +void MediaPlayer2::setFullscreen(bool fullscreen) const +{ + Dragon::action("fullscreen")->setChecked(fullscreen); +} + +void MediaPlayer2::emitFullscreenChange(bool fullscreen) const +{ + QVariantMap properties; + properties["Fullscreen"] = fullscreen; + properties["CanSetFullscreen"] = CanSetFullscreen(); + Mpris2::signalPropertiesChange(this, properties); +} + +bool MediaPlayer2::CanSetFullscreen() const +{ + return Dragon::TheStream::hasVideo(); +} + +bool MediaPlayer2::HasTrackList() const +{ + return false; +} + +QString MediaPlayer2::Identity() const +{ + return KCmdLineArgs::aboutData()->programName(); +} + +QString MediaPlayer2::DesktopEntry() const +{ + return "kde4-" + QString(APP_NAME); +} + +QStringList MediaPlayer2::SupportedUriSchemes() const +{ + QStringList protocols; + + foreach(const QString& protocol, KProtocolInfo::protocols()) + if (!KProtocolInfo::isHelperProtocol(protocol)) + protocols << protocol; + + return protocols; +} + +QStringList MediaPlayer2::SupportedMimeTypes() const +{ + KService::Ptr app = KService::serviceByDesktopName(APP_NAME); + + //if (app) + // return app->mimeTypes(); + + return QStringList(); +} diff --git a/dragon/src/mpris2/mediaplayer2.h b/dragon/src/mpris2/mediaplayer2.h new file mode 100644 index 00000000..445f4339 --- /dev/null +++ b/dragon/src/mpris2/mediaplayer2.h @@ -0,0 +1,73 @@ +/*********************************************************************** + * Copyright 2012 Eike Hein + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_MEDIAPLAYRER2_H +#define DRAGONPLAYER_MEDIAPLAYRER2_H + +#include +#include // Needed for automoc'ed cpp to compile + +class MediaPlayer2 : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2") // Docs: http://www.mpris.org/2.1/spec/Root_Node.html + + Q_PROPERTY(bool CanQuit READ CanQuit) + Q_PROPERTY(bool CanRaise READ CanRaise) + + Q_PROPERTY(bool Fullscreen READ Fullscreen WRITE setFullscreen) + Q_PROPERTY(bool CanSetFullscreen READ CanSetFullscreen) + + Q_PROPERTY(bool HasTrackList READ HasTrackList) + + Q_PROPERTY(QString Identity READ Identity) + Q_PROPERTY(QString DesktopEntry READ DesktopEntry) + + Q_PROPERTY(QStringList SupportedUriSchemes READ SupportedUriSchemes) + Q_PROPERTY(QStringList SupportedMimeTypes READ SupportedMimeTypes) + + public: + explicit MediaPlayer2(QObject* parent); + ~MediaPlayer2(); + + bool CanQuit() const; + bool CanRaise() const; + + bool Fullscreen() const; + void setFullscreen(bool fullscreen) const; + bool CanSetFullscreen() const; + + bool HasTrackList() const; + + QString Identity() const; + QString DesktopEntry() const; + + QStringList SupportedUriSchemes() const; + QStringList SupportedMimeTypes() const; + + public slots: + void Raise() const; + void Quit() const; + + private slots: + void emitFullscreenChange(bool fullscreen) const; +}; + +#endif diff --git a/dragon/src/mpris2/mediaplayer2player.cpp b/dragon/src/mpris2/mediaplayer2player.cpp new file mode 100644 index 00000000..11bb2be6 --- /dev/null +++ b/dragon/src/mpris2/mediaplayer2player.cpp @@ -0,0 +1,277 @@ +/*********************************************************************** + * Copyright 2012 Eike Hein + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "mediaplayer2player.h" +#include "mainWindow.h" +#include "mpris2.h" +#include "videoWindow.h" + +#include + +static QByteArray makeTrackId(const QString& source) +{ + return QByteArray("/org/kde/") + APP_NAME + "/tid_" + + QCryptographicHash::hash(source.toLocal8Bit(), QCryptographicHash::Sha1) + .toHex(); +} + +MediaPlayer2Player::MediaPlayer2Player(QObject* parent) : QDBusAbstractAdaptor(parent) +{ + connect(Dragon::engine(), SIGNAL(tick(qint64)), this, SLOT(tick(qint64))); + connect(Dragon::engine(), SIGNAL(currentSourceChanged(Phonon::MediaSource)), this, SLOT(currentSourceChanged())); + connect(Dragon::engine(), SIGNAL(metaDataChanged()), this, SLOT(emitMetadataChange())); + connect(Dragon::engine(), SIGNAL(stateUpdated(Phonon::State,Phonon::State)), this, SLOT(stateUpdated())); + connect(Dragon::engine(), SIGNAL(totalTimeChanged(qint64)), this, SLOT(emitMetadataChange())); + connect(Dragon::engine(), SIGNAL(seekableChanged(bool)), this, SLOT(seekableChanged(bool))); + connect(Dragon::engine(), SIGNAL(volumeChanged(qreal)), this, SLOT(volumeChanged())); +} + +MediaPlayer2Player::~MediaPlayer2Player() +{ +} + +bool MediaPlayer2Player::CanGoNext() const +{ + return false; +} + +void MediaPlayer2Player::Next() const +{ +} + +bool MediaPlayer2Player::CanGoPrevious() const +{ + return false; +} + +void MediaPlayer2Player::Previous() const +{ +} + +bool MediaPlayer2Player::CanPause() const +{ + return Dragon::engine()->state() != Phonon::ErrorState; +} + +void MediaPlayer2Player::Pause() const +{ + Dragon::engine()->pause(); +} + +void MediaPlayer2Player::PlayPause() const +{ + Dragon::engine()->playPause(); +} + +void MediaPlayer2Player::Stop() const +{ + Dragon::engine()->stop(); +} + +bool MediaPlayer2Player::CanPlay() const +{ + return true; +} + +void MediaPlayer2Player::Play() const +{ + Dragon::engine()->play(); +} + +void MediaPlayer2Player::SetPosition(const QDBusObjectPath& TrackId, qlonglong Position) const +{ + if (TrackId.path().toLocal8Bit() == makeTrackId(Dragon::engine()->urlOrDisc())) + Dragon::engine()->seek(Position / 1000); +} + +void MediaPlayer2Player::OpenUri(QString Uri) const +{ + static_cast(Dragon::mainWindow())->open(KUrl(Uri)); +} + +QString MediaPlayer2Player::PlaybackStatus() const +{ + switch (Dragon::engine()->state()) { + case (Phonon::PlayingState): + return "Playing"; + break; + case (Phonon::PausedState): + case (Phonon::BufferingState): + return "Paused"; + break; + case (Phonon::StoppedState): + return "Stopped"; + break; + default: + return "Stopped"; + break; + } +} + +QString MediaPlayer2Player::LoopStatus() const +{ + return "None"; +} + +void MediaPlayer2Player::setLoopStatus(const QString& loopStatus) const +{ + Q_UNUSED(loopStatus) +} + +double MediaPlayer2Player::Rate() const +{ + return 1.0; +} + +void MediaPlayer2Player::setRate(double rate) const +{ + Q_UNUSED(rate) +} + +bool MediaPlayer2Player::Shuffle() const +{ + return false; +} + +void MediaPlayer2Player::setShuffle(bool shuffle) const +{ + Q_UNUSED(shuffle) +} + +QVariantMap MediaPlayer2Player::Metadata() const +{ + QVariantMap metaData; + + switch (Dragon::engine()->mediaSourceType()) { + case Phonon::MediaSource::Invalid: + case Phonon::MediaSource::Empty: + break; + default: + metaData["mpris:trackid"] = QVariant::fromValue(QDBusObjectPath(makeTrackId(Dragon::engine()->urlOrDisc()).constData())); + metaData["mpris:length"] = Dragon::engine()->length() * 1000; + metaData["xesam:url"] = Dragon::engine()->urlOrDisc(); + } + + QMultiMap phononMetaData = Dragon::engine()->metaData(); + QMultiMap::const_iterator i = phononMetaData.constBegin(); + + while (i != phononMetaData.constEnd()) { + if (i.key() == "ALBUM" || i.key() == "TITLE") + metaData["xesam:" + i.key().toLower()] = i.value(); + else if (i.key() == "ARTIST" || i.key() == "GENRE") + metaData["xesam:" + i.key().toLower()] = QStringList(i.value()); + + ++i; + } + + return metaData; +} + +double MediaPlayer2Player::Volume() const +{ + return Dragon::engine()->volume(); +} + +void MediaPlayer2Player::setVolume(double volume) const +{ + Dragon::engine()->setVolume(qBound(qreal(0.0), qreal(volume), qreal(1.0))); +} + +qlonglong MediaPlayer2Player::Position() const +{ + return Dragon::engine()->currentTime() * 1000; +} + +double MediaPlayer2Player::MinimumRate() const +{ + return 1.0; +} + +double MediaPlayer2Player::MaximumRate() const +{ + return 1.0; +} + +bool MediaPlayer2Player::CanSeek() const +{ + return Dragon::engine()->isSeekable(); +} + +void MediaPlayer2Player::Seek(qlonglong Offset) const +{ + Dragon::engine()->seek(((Dragon::engine()->currentTime() * 1000) + Offset) / 1000); +} + +bool MediaPlayer2Player::CanControl() const +{ + return true; +} + +void MediaPlayer2Player::tick(qint64 newPos) +{ + if (newPos - oldPos > Dragon::engine()->tickInterval() + 250 || newPos < oldPos) + emit Seeked(newPos * 1000); + + oldPos = newPos; +} + +void MediaPlayer2Player::emitMetadataChange() const +{ + QVariantMap properties; + properties["Metadata"] = Metadata(); + Mpris2::signalPropertiesChange(this, properties); +} + +void MediaPlayer2Player::currentSourceChanged() const +{ + QVariantMap properties; + properties["Metadata"] = Metadata(); + properties["CanSeek"] = CanSeek(); + Mpris2::signalPropertiesChange(this, properties); +} + +void MediaPlayer2Player::stateUpdated() const +{ + QVariantMap properties; + properties["PlaybackStatus"] = PlaybackStatus(); + properties["CanPause"] = CanPause(); + Mpris2::signalPropertiesChange(this, properties); +} + +void MediaPlayer2Player::totalTimeChanged() const +{ + QVariantMap properties; + properties["Metadata"] = Metadata(); + Mpris2::signalPropertiesChange(this, properties); +} + +void MediaPlayer2Player::seekableChanged(bool seekable) const +{ + QVariantMap properties; + properties["CanSeek"] = seekable; + Mpris2::signalPropertiesChange(this, properties); +} + +void MediaPlayer2Player::volumeChanged() const +{ + QVariantMap properties; + properties["Volume"] = Volume(); + Mpris2::signalPropertiesChange(this, properties); +} diff --git a/dragon/src/mpris2/mediaplayer2player.h b/dragon/src/mpris2/mediaplayer2player.h new file mode 100644 index 00000000..2bc0bdd4 --- /dev/null +++ b/dragon/src/mpris2/mediaplayer2player.h @@ -0,0 +1,101 @@ +/*********************************************************************** + * Copyright 2012 Eike Hein + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_MEDIAPLAYRER2PLAYER_H +#define DRAGONPLAYER_MEDIAPLAYRER2PLAYER_H + +#include +#include + +#include + +class MediaPlayer2Player : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2.Player") // Docs: http://www.mpris.org/2.1/spec/Player_Node.html + + Q_PROPERTY(QString PlaybackStatus READ PlaybackStatus) + Q_PROPERTY(QString LoopStatus READ LoopStatus WRITE setLoopStatus) + Q_PROPERTY(double Rate READ Rate WRITE setRate) + Q_PROPERTY(bool Shuffle READ Shuffle WRITE setShuffle) + Q_PROPERTY(QVariantMap Metadata READ Metadata) + Q_PROPERTY(double Volume READ Volume WRITE setVolume) + Q_PROPERTY(qlonglong Position READ Position) + Q_PROPERTY(double MinimumRate READ MinimumRate) + Q_PROPERTY(double MaximumRate READ MaximumRate) + Q_PROPERTY(bool CanGoNext READ CanGoNext) + Q_PROPERTY(bool CanGoPrevious READ CanGoPrevious) + Q_PROPERTY(bool CanPlay READ CanPlay) + Q_PROPERTY(bool CanPause READ CanPause) + Q_PROPERTY(bool CanSeek READ CanSeek) + Q_PROPERTY(bool CanControl READ CanControl) + + public: + explicit MediaPlayer2Player(QObject* parent); + ~MediaPlayer2Player(); + + QString PlaybackStatus() const; + QString LoopStatus() const; + void setLoopStatus(const QString& loopStatus) const; + double Rate() const; + void setRate(double rate) const; + bool Shuffle() const; + void setShuffle(bool shuffle) const; + QVariantMap Metadata() const; + double Volume() const; + void setVolume(double volume) const; + qlonglong Position() const; + double MinimumRate() const; + double MaximumRate() const; + bool CanGoNext() const; + bool CanGoPrevious() const; + bool CanPlay() const; + bool CanPause() const; + bool CanSeek() const; + bool CanControl() const; + + signals: + void Seeked(qlonglong Position) const; + + public slots: + void Next() const; + void Previous() const; + void Pause() const; + void PlayPause() const; + void Stop() const; + void Play() const; + void Seek(qlonglong Offset) const; + void SetPosition(const QDBusObjectPath& TrackId, qlonglong Position) const; + void OpenUri(QString Uri) const; + + private slots: + void tick(qint64 newPos); + void emitMetadataChange() const; + void currentSourceChanged() const; + void stateUpdated() const; + void totalTimeChanged() const; + void seekableChanged(bool seekable) const; + void volumeChanged() const; + + private: + qint64 oldPos; +}; + +#endif diff --git a/dragon/src/mpris2/mpris2.cpp b/dragon/src/mpris2/mpris2.cpp new file mode 100644 index 00000000..3eaa7c62 --- /dev/null +++ b/dragon/src/mpris2/mpris2.cpp @@ -0,0 +1,67 @@ +/*********************************************************************** + * Copyright 2012 Eike Hein + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#include "mpris2.h" +#include "mediaplayer2.h" +#include "mediaplayer2player.h" +#include "codeine.h" + +#include +#include +#include +#include + +#include + +Mpris2::Mpris2(QObject* parent) : QObject(parent) +{ + QString mpris2Name("org.mpris.MediaPlayer2." + QLatin1String(APP_NAME)); + + bool success = QDBusConnection::sessionBus().registerService(mpris2Name); + + // If the above failed, it's likely because we're not the first instance + // and the name is already taken. In that event the MPRIS2 spec wants the + // following: + if (!success) + success = QDBusConnection::sessionBus().registerService(mpris2Name + ".instance" + QString::number(getpid())); + + if (success) + { + new MediaPlayer2(this); + new MediaPlayer2Player(this); + QDBusConnection::sessionBus().registerObject("/org/mpris/MediaPlayer2", this, QDBusConnection::ExportAdaptors); + } +} + +Mpris2::~Mpris2() +{ +} + +void Mpris2::signalPropertiesChange(const QObject* adaptor, const QVariantMap& properties) +{ + QDBusMessage msg = QDBusMessage::createSignal("/org/mpris/MediaPlayer2", + "org.freedesktop.DBus.Properties", "PropertiesChanged" ); + + msg << adaptor->metaObject()->classInfo(0).value(); + msg << properties; + msg << QStringList(); + + QDBusConnection::sessionBus().send(msg); +} diff --git a/dragon/src/mpris2/mpris2.h b/dragon/src/mpris2/mpris2.h new file mode 100644 index 00000000..bc28cd70 --- /dev/null +++ b/dragon/src/mpris2/mpris2.h @@ -0,0 +1,38 @@ +/*********************************************************************** + * Copyright 2012 Eike Hein + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ***********************************************************************/ + +#ifndef DRAGONPLAYER_MPRIS2_H +#define DRAGONPLAYER_MPRIS2_H + +#include +#include + +class Mpris2 : public QObject +{ + Q_OBJECT + + public: + explicit Mpris2(QObject* parent); + ~Mpris2(); + + static void signalPropertiesChange(const QObject* adaptor, const QVariantMap& properties); +}; + +#endif diff --git a/ffmpegthumbs/CMakeLists.txt b/ffmpegthumbs/CMakeLists.txt new file mode 100644 index 00000000..dd0974fe --- /dev/null +++ b/ffmpegthumbs/CMakeLists.txt @@ -0,0 +1,40 @@ +find_package(KDE4 REQUIRED) +include(KDE4Defaults) +include(MacroLibrary) + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +find_package(FFmpeg COMPONENTS AVCODEC AVFORMAT SWSCALE) + +include_directories( + ${KDE4_INCLUDES} + ${CMAKE_CURRENT_BINARY_DIR} + ${PC_AVCODEC_INCLUDEDIR} + ${PC_AVFORMAT_INCLUDEDIR} + ${FFMPEG_INCLUDE_DIR} + ) + +# Certain versions of FFMPEG need this to be defined +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D __STDC_CONSTANT_MACROS") + +set( ffmpegthumbs_PART_SRCS + ffmpegthumbnailer.cpp + ffmpegthumbnailer/filmstripfilter.cpp + ffmpegthumbnailer/moviedecoder.cpp + ffmpegthumbnailer/imagewriter.cpp + ffmpegthumbnailer/videothumbnailer.cpp +) + +kde4_add_plugin(ffmpegthumbs ${ffmpegthumbs_PART_SRCS}) + +target_link_libraries(ffmpegthumbs ${KDE4_KIO_LIBS} ${AVUTIL_LIBRARIES} ${AVFORMAT_LIBRARIES} ${AVCODEC_LIBRARIES} ${SWSCALE_LIBRARIES} ) + +install(TARGETS ffmpegthumbs DESTINATION ${PLUGIN_INSTALL_DIR}) + +########### install files ############### + +install(FILES ffmpegthumbs.desktop DESTINATION ${SERVICES_INSTALL_DIR}) + +if (KDE4_BUILD_TESTS) + add_subdirectory(tests) +endif (KDE4_BUILD_TESTS) diff --git a/ffmpegthumbs/COPYING b/ffmpegthumbs/COPYING new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/ffmpegthumbs/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/ffmpegthumbs/cmake/COPYING-CMAKE-SCRIPTS b/ffmpegthumbs/cmake/COPYING-CMAKE-SCRIPTS new file mode 100644 index 00000000..53b6b71e --- /dev/null +++ b/ffmpegthumbs/cmake/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ffmpegthumbs/cmake/FindFFmpeg.cmake b/ffmpegthumbs/cmake/FindFFmpeg.cmake new file mode 100644 index 00000000..27c6b16d --- /dev/null +++ b/ffmpegthumbs/cmake/FindFFmpeg.cmake @@ -0,0 +1,148 @@ +# vim: ts=2 sw=2 +# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) +# +# Once done this will define +# FFMPEG_FOUND - System has the all required components. +# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. +# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. +# +# For each of the components it will additionally set. +# - AVCODEC +# - AVDEVICE +# - AVFORMAT +# - AVUTIL +# - POSTPROCESS +# - SWSCALE +# the following variables will be defined +# _FOUND - System has +# _INCLUDE_DIRS - Include directory necessary for using the headers +# _LIBRARIES - Link these to use +# _DEFINITIONS - Compiler switches required for using +# _VERSION - The components version +# +# Copyright (c) 2006, Matthias Kretz, +# Copyright (c) 2008, Alexander Neundorf, +# Copyright (c) 2011, Michael Jansen, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindPackageHandleStandardArgs) + +# The default components were taken from a survey over other FindFFMPEG.cmake files +if (NOT FFmpeg_FIND_COMPONENTS) + set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL) +endif () + +# +### Macro: set_component_found +# +# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present. +# +macro(set_component_found _component ) + if (${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS) + # message(STATUS " - ${_component} found.") + set(${_component}_FOUND TRUE) + else () + # message(STATUS " - ${_component} not found.") + endif () +endmacro() + +# +### Macro: find_component +# +# Checks for the given component by invoking pkgconfig and then looking up the libraries and +# include directories. +# +macro(find_component _component _pkgconfig _library _header) + + if (NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(PC_${_component} ${_pkgconfig}) + endif () + endif (NOT WIN32) + + find_path(${_component}_INCLUDE_DIRS ${_header} + HINTS + ${PC_${_component}_INCLUDEDIR} + ${PC_${_component}_INCLUDE_DIRS} + PATH_SUFFIXES + ffmpeg + ) + + find_library(${_component}_LIBRARIES NAMES ${_library} + HINTS + ${PC_${_component}_LIBDIR} + ${PC_${_component}_LIBRARY_DIRS} + ) + + set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") + set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.") + + set_component_found(${_component}) + + mark_as_advanced( + ${_component}_INCLUDE_DIRS + ${_component}_LIBRARIES + ${_component}_DEFINITIONS + ${_component}_VERSION) + +endmacro() + + +# Check for cached results. If there are skip the costly part. +if (NOT FFMPEG_LIBRARIES) + + # Check for all possible component. + find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) + find_component(AVFORMAT libavformat avformat libavformat/avformat.h) + find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) + find_component(AVUTIL libavutil avutil libavutil/avutil.h) + find_component(SWSCALE libswscale swscale libswscale/swscale.h) + find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) + + # Check if the required components were found and add their stuff to the FFMPEG_* vars. + foreach (_component ${FFmpeg_FIND_COMPONENTS}) + if (${_component}_FOUND) + # message(STATUS "Required component ${_component} present.") + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES}) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS}) + list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) + else () + # message(STATUS "Required component ${_component} missing.") + endif () + endforeach () + + # Build the include path with duplicates removed. + if (FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) + endif () + + # cache the vars. + set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE) + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE) + + mark_as_advanced(FFMPEG_INCLUDE_DIRS + FFMPEG_LIBRARIES + FFMPEG_DEFINITIONS) + +endif () + +# Now set the noncached _FOUND vars for the components. +foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE) + set_component_found(${_component}) +endforeach () + +# Compile the list of required vars +set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) +foreach (_component ${FFmpeg_FIND_COMPONENTS}) + list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS) +endforeach () + +# Give a nice error message if some of the required vars are missing. +find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) diff --git a/ffmpegthumbs/ffmpegthumbnailer.cpp b/ffmpegthumbs/ffmpegthumbnailer.cpp new file mode 100644 index 00000000..03a40c36 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer.cpp @@ -0,0 +1,56 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "ffmpegthumbnailer.h" +#include +#include + +extern "C" +{ + KDE_EXPORT ThumbCreator* new_creator() + { + return new FFMpegThumbnailer(); + } +} + + +FFMpegThumbnailer::FFMpegThumbnailer() +{ + m_Thumbnailer.addFilter(&m_FilmStrip); +} + +FFMpegThumbnailer::~FFMpegThumbnailer() +{ +} + +bool FFMpegThumbnailer::create(const QString& path, int width, int /*heigth*/, QImage& img) +{ + m_Thumbnailer.setThumbnailSize(width); + // 20% seek inside the video to generate the preview + m_Thumbnailer.setSeekPercentage(20); + //Smart frame selection is very slow compared to the fixed detection + //TODO: Use smart detection if the image is single colored. + //m_Thumbnailer.setSmartFrameSelection(true); + m_Thumbnailer.generateThumbnail(path, img); + + return !img.isNull(); +} + +ThumbCreator::Flags FFMpegThumbnailer::flags() const +{ + return (Flags)(DrawFrame); +} + diff --git a/ffmpegthumbs/ffmpegthumbnailer.h b/ffmpegthumbs/ffmpegthumbnailer.h new file mode 100644 index 00000000..4f21dd47 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer.h @@ -0,0 +1,39 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef KFFMPEG_THUMBNAILER_H +#define KFFMPEG_THUMBNAILER_H + +#include +#include + +#include +#include + +class FFMpegThumbnailer : public QObject, public ThumbCreator +{ + Q_OBJECT +public: + FFMpegThumbnailer(); + virtual ~FFMpegThumbnailer(); + virtual bool create(const QString& path, int width, int height, QImage& img); + virtual Flags flags() const; +private: + ffmpegthumbnailer::VideoThumbnailer m_Thumbnailer; + ffmpegthumbnailer::FilmStripFilter m_FilmStrip; +}; + +#endif diff --git a/ffmpegthumbs/ffmpegthumbnailer/AUTHORS b/ffmpegthumbs/ffmpegthumbnailer/AUTHORS new file mode 100644 index 00000000..84a7eb56 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/AUTHORS @@ -0,0 +1,2 @@ +Dirk Vanden Boer +Andreas Scherf (KDE changes) diff --git a/ffmpegthumbs/ffmpegthumbnailer/ChangeLog b/ffmpegthumbs/ffmpegthumbnailer/ChangeLog new file mode 100644 index 00000000..89577de0 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/ChangeLog @@ -0,0 +1,105 @@ +FFmpegThumbnailer + +version 2.0.2 +- Fixed compilation error against latest ffmpeg +- Size of the filmstrip overlay is dependant on thumbnail size + +Version 2.0.1 +- Setting the thumbnail size to 0 will use the original video size (thanks to John Fremlin) +- Fix for video files containing lots of audio packets before a video packet +- Fixed libs in pkgconfig file (thanks to magnus.tuominen) + +version 2.0.0 +- Fixed some issues in package-config file (Thanks to ambrop7) +- C++ library has been put in a namespace +- C library functions have been renamed + +version 1.5.6 +- Fixed segmentation fault when seek in video file fails +- Command line option added to specify output format of the image regardless of the filename +- Fixed memory leak in MovieDecoder (thanks to Ulrich Völkel) + +version 1.5.5 +- Fixed build issue with certain versions of autoconf +- Fixed build issue with recent versions of gcc +- Fixed thumbnail generation for ogm files +- Fixed ffmpegthumbnailer hanging on certain filetypes caused by AVFMT_FLAG_GENPTS flag of video decoder + +version 1.5.4 +- Fixed support for large files + +version 1.5.3 +- Memory alignment fix causing segmentation in ffmpeg with altivec enabled + +version 1.5.2 +- Fixed soname version + +version 1.5.1 +- Optional compilation of jpeg and png support +- Fixed ffmpeg CFLAGS issue (causing compilation errors on Fedora) +- Fixed build error against latest ffmpeg (verified against ffmpeg revision 18769) +- Fixed missing include files for library usage (thanks to gustavo.boiko) + +version 1.5.0 +- Aspect ratio bug fix (Thanks to S. Eguchi) +- Support input files from stdin (will ignore seek time) +- Fixed build error against latest ffmpeg (verified against ffmpeg release 0.5) + +version 1.4.0 +- Filter support added that allows library users to implement filters that will be applied to the frame data +- Fixed compilation error with gcc 4.4 + +version 1.3.0 +- Option added to ignore aspect ratio and generate square thumbnails +- Quality option added for jpeg thumbnails +- Man page added (thanks to Lionel Le Folgoc) +- Added option to seek to absolute time in stead of percentage + +version 1.2.6 +- Fixed seeking in large files due to integer overflow (Thanks to AWaters1) +- Fixed possible linker error (Thanks to pressh) + +version 1.2.5 +- Fixed crash when thumbnailing raw avi files + +version 1.2.4 +- Fixed missing includes when compiling with gcc 4.3 (thanks to Samuli Suominen) + +version 1.2.3 +- ffmegthumbnailer now compiles against latest ffmpeg revisions (thanks to Alexis Ballier) + +version 1.2.2 +- Fixed linker errors when using certain ldflags + +version 1.2.1 +- Fixed compilation error in c interface when compiled with c compiler +- Error handling added to c interface + +version 1.2.0 +- Option to output files in jpeg +- ffmpegthumbnailer can now be accessed as a library (libffmpegthumbnailer) by other applications + +version 1.1.5 +- Support for large files (files larger than 2Gb) +- Seeking in h264 files is enabled again, use -w option from commandline to disable this if you have 100% cpu usage (using older versions of ffmpeg) +- Give up reading packets from a file after a number of attempts to avoid a hanging ffmpegthumbnailer for certain file types (noticed on h264 and ac3 in vob container) + +version 1.1.4 +- License information added to source files + +version 1.1.3 +- Use PkgConfig for ffmpeg to avoid build problems + +version 1.1.2 +- Fixed compilation error when using latest ffmpeg (thanks to Samuli Suominen) + +version 1.1.1 +- Fixed compilation error when using latest ffmpeg +- Disabled seeking in h264 files, because this causes 100% CPU usage in ffmpeglib (workaround) + +version 1.1 +- Updated command line argument parser +- Possibility to add a movie strip overlay + +version 1.0 +- Initial release of the FFmpegThumbnailer diff --git a/ffmpegthumbs/ffmpegthumbnailer/README b/ffmpegthumbs/ffmpegthumbnailer/README new file mode 100644 index 00000000..7e538a01 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/README @@ -0,0 +1,7 @@ +FFmpegthumbnailer is a lightweight video thumbnailer that can be used by file +managers to create thumbnails for your video files. The thumbnailer uses ffmpeg +o decode frames from the video files, so supported videoformats depend on the +configuration flags of ffmpeg. + +This thumbnailer was designed to be as fast and lightweight as possible. The +only dependencies are ffmpeglibraies and libswscale . diff --git a/ffmpegthumbs/ffmpegthumbnailer/filmstrip.h b/ffmpegthumbs/ffmpegthumbnailer/filmstrip.h new file mode 100644 index 00000000..12c6050e --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/filmstrip.h @@ -0,0 +1,575 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef FILMSTRIP_H +#define FILMSTRIP_H +#include + +static const quint32 SMALLEST_FILM_STRIP_WIDTH = 4; + +static const quint8 filmStrip4[4 * 4 * 3] = { + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0kkk\207\207\207777\0\0\0\237\237\237\303\303" + "\303RRR\0\0\0\0\0\0\0\0\0\0\0" +}; + +static const quint8 filmStrip8[8 * 8 * 3] = { + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\40\40\40""777::::::444\1\1\1\0\0\0\2" + "\2\2\205\205\205\320\320\320\333\333\333\333\333\333\313\313\313\32\32\32" + "\0\0\0\2\2\2\236\236\236\360\360\360\373\373\373\373\373\373\353\353\353" + "\37\37\37\0\0\0\0\0\0FFFsssyyyyyynnn\2\2\2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" +}; + +static const quint8 filmStrip16[16 * 16 * 3] = { + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\11\11\11\14\14\14" + "\15\15\15\15\15\15\15\15\15\15\15\15\15\15\15\15\15\15\4\4\4\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0""888YYYrrr|||\200\200\200\200\200\200\200\200\200" + "\200\200\200zzzmmm\23\23\23\0\0\0\0\0\0\0\0\0\0\0\0\11\11\11YYY\214\214\214" + "\257\257\257\276\276\276\302\302\302\302\302\302\302\302\302\301\301\301" + "\273\273\273\250\250\250@@@\0\0\0\0\0\0\0\0\0\0\0\0\14\14\14qqq\257\257\257" + "\326\326\326\347\347\347\353\353\353\354\354\354\354\354\354\353\353\353" + "\344\344\344\317\317\317PPP\0\0\0\0\0\0\0\0\0\0\0\0\15\15\15{{{\274\274\274" + "\345\345\345\365\365\365\372\372\372\373\373\373\373\373\373\371\371\371" + "\363\363\363\335\335\335VVV\0\0\0\0\0\0\0\0\0\0\0\0\14\14\14xxx\270\270\270" + "\340\340\340\361\361\361\365\365\365\366\366\366\366\366\366\365\365\365" + "\356\356\356\331\331\331UUU\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1ggg\240\240\240" + "\306\306\306\326\326\326\332\332\332\334\334\334\334\334\334\332\332\332" + "\324\324\324\277\277\277\"\"\"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\32\32" + "\32\40\40\40###$$$$$$$$$$$$###\12\12\12\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" +}; + +static const quint8 filmStrip32[32 * 32 * 3] = { + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\13\13\13\27\27\27" + "\34\34\34\40\40\40\"\"\"$$$%%%%%%&&&&&&&&&&&&&&&&&&%%%%%%###\35\35\35\3\3" + "\3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\34\34\34""666EEESSS]]]eeeiiilllmmmooonnnnnnnnnnnnnnnmmmkkkgggaaaXXX\15" + "\15\15\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\13\13" + "\13""666JJJ]]]nnn|||\205\205\205\213\213\213\217\217\217\220\220\220\221" + "\221\221\221\221\221\221\221\221\221\221\221\222\222\222\221\221\221\220" + "\220\220\215\215\215\210\210\210\201\201\201vvvXXX\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\27\27\27EEE]]]uuu\212\212\212\232" + "\232\232\245\245\245\254\254\254\260\260\260\262\262\262\263\263\263\263" + "\263\263\263\263\263\263\263\263\263\263\263\262\262\262\261\261\261\256" + "\256\256\252\252\252\241\241\241\222\222\222\200\200\200\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\34\34\34RRRnnn\212\212\212\242" + "\242\242\264\264\264\300\300\300\310\310\310\314\314\314\316\316\316\317" + "\317\317\317\317\317\317\317\317\317\317\317\317\317\317\317\317\317\315" + "\315\315\312\312\312\305\305\305\273\273\273\254\254\254\227\227\227\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\40\40\40\\\\\\|" + "||\232\232\232\264\264\264\307\307\307\324\324\324\334\334\334\341\341\341" + "\342\342\342\343\343\343\344\344\344\344\344\344\344\344\344\344\344\344" + "\343\343\343\342\342\342\337\337\337\331\331\331\317\317\317\277\277\277" + "\250\250\250\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\"\"\"ddd\206\206\206\245\245\245\300\300\300\324\324\324\342\342\342\352" + "\352\352\356\356\356\360\360\360\361\361\361\361\361\361\361\361\361\361" + "\361\361\361\361\361\361\361\361\360\360\360\354\354\354\346\346\346\334" + "\334\334\313\313\313\264\264\264\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0$$$hhh\212\212\212\253\253\253\307\307\307\333\333\333" + "\351\351\351\360\360\360\365\365\365\367\367\367\370\370\370\370\370\370" + "\370\370\370\370\370\370\370\370\370\367\367\367\366\366\366\363\363\363" + "\355\355\355\343\343\343\322\322\322\272\272\272\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0$$$iii\214\214\214\255\255\255\311\311" + "\311\336\336\336\353\353\353\363\363\363\370\370\370\372\372\372\373\373" + "\373\373\373\373\373\373\373\373\373\373\373\373\373\372\372\372\371\371" + "\371\366\366\366\360\360\360\345\345\345\324\324\324\274\274\274\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0$$$hhh\212\212\212\253" + "\253\253\307\307\307\333\333\333\351\351\351\360\360\360\365\365\365\367" + "\367\367\370\370\370\370\370\370\370\370\370\370\370\370\370\370\370\367" + "\367\367\366\366\366\363\363\363\355\355\355\343\343\343\322\322\322\272" + "\272\272\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\"\"" + "\"ddd\206\206\206\245\245\245\300\300\300\324\324\324\342\342\342\352\352" + "\352\356\356\356\360\360\360\361\361\361\361\361\361\361\361\361\361\361" + "\361\361\361\361\361\361\361\360\360\360\354\354\354\346\346\346\334\334" + "\334\313\313\313\264\264\264\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\23\23\23\\\\\\|||\232\232\232\264\264\264\310\310\310\324" + "\324\324\334\334\334\341\341\341\342\342\342\343\343\343\344\344\344\344" + "\344\344\344\344\344\344\344\344\343\343\343\342\342\342\337\337\337\331" + "\331\331\317\317\317\277\277\277\222\222\222\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0;;;nnn\212\212\212\242\242\242\264" + "\264\264\301\301\301\310\310\310\314\314\314\316\316\316\317\317\317\320" + "\320\320\320\320\320\320\320\320\320\320\320\317\317\317\315\315\315\313" + "\313\313\305\305\305\273\273\273\254\254\254\33\33\33\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\33\33\33:::EEEMMMSS" + "SVVVXXXYYYYYYZZZZZZZZZZZZYYYXXXWWWUUUFFF\10\10\10\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" +}; + +static const quint8 filmStrip64[64 * 64 * 3] = { + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\20\20\20\"\"" + "\"///666;;;@@@DDDHHHKKKMMMOOOQQQRRRRRRSSSSSSTTTTTTTTTTTTTTTTTTTTTTTTTTTT" + "TTTTTSSSSSSRRRRRRQQQOOOMMMHHH999\36\36\36\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\3\3\36\36\36...444;;;BB" + "BHHHNNNRRRWWWZZZ]]]___aaabbbcccddddddeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedd" + "ddddcccbbbaaa___]]]ZZZWWWRRR===\10\10\10\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\36\36\36///777???GGGNNNVVV\\\\\\bbbgg" + "gkkknnnppprrrtttuuuvvvvvvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvuuutttrr" + "rqqqnnnkkkgggbbb\\\\\\CCC\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\20\20\20...666@@@IIIRRR[[[cccjjjqqqvvv{{{~~~\201\201\201\203" + "\203\203\205\205\205\206\206\206\207\207\207\207\207\207\210\210\210\210" + "\210\210\210\210\210\210\210\210\210\210\210\210\210\210\210\210\210\210" + "\210\210\210\210\210\210\210\210\210\210\210\207\207\207\207\207\207\206" + "\206\206\205\205\205\203\203\203\201\201\201~~~{{{vvvqqqjjjccc)))\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\"\"\"444>>>IIISSS^^^hhhqq" + "qyyy\200\200\200\206\206\206\213\213\213\217\217\217\222\222\222\225\225" + "\225\226\226\226\230\230\230\230\230\230\231\231\231\231\231\231\232\232" + "\232\232\232\232\232\232\232\232\232\232\232\232\232\232\232\232\232\232" + "\232\232\232\232\232\232\232\231\231\231\231\231\231\231\231\231\230\230" + "\230\226\226\226\225\225\225\222\222\222\217\217\217\213\213\213\206\206" + "\206\200\200\200yyyqqqRRR\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0///;;;GGGRRR^^^iiittt~~~\207\207\207\217\217\217\226\226\226\233\233" + "\233\237\237\237\243\243\243\245\245\245\247\247\247\251\251\251\251\251" + "\251\252\252\252\252\252\252\253\253\253\253\253\253\253\253\253\253\253" + "\253\253\253\253\253\253\253\253\253\253\253\253\253\253\253\253\252\252" + "\252\252\252\252\251\251\251\251\251\251\247\247\247\245\245\245\243\243" + "\243\240\240\240\233\233\233\226\226\226\217\217\217\207\207\207~~~ooo\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0""666BBBNNN[[[hhhttt" + "\200\200\200\213\213\213\224\224\224\235\235\235\244\244\244\251\251\251" + "\256\256\256\262\262\262\264\264\264\266\266\266\267\267\267\270\270\270" + "\271\271\271\271\271\271\272\272\272\272\272\272\272\272\272\272\272\272" + "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272" + "\271\271\271\270\270\270\270\270\270\266\266\266\264\264\264\262\262\262" + "\256\256\256\252\252\252\244\244\244\235\235\235\224\224\224\213\213\213" + "\200\200\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0;;;HHH" + "UUUcccqqq~~~\213\213\213\226\226\226\241\241\241\251\251\251\261\261\261" + "\267\267\267\274\274\274\277\277\277\302\302\302\304\304\304\305\305\305" + "\306\306\306\307\307\307\307\307\307\310\310\310\310\310\310\310\310\310" + "\310\310\310\310\310\310\310\310\310\310\310\310\310\310\310\310\310\310" + "\310\310\310\307\307\307\306\306\306\306\306\306\304\304\304\302\302\302" + "\277\277\277\274\274\274\267\267\267\261\261\261\251\251\251\241\241\241" + "\226\226\226\213\213\213\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0@@@MMM\\\\\\jjjyyy\207\207\207\225\225\225\241\241\241\254\254\254" + "\265\265\265\275\275\275\303\303\303\310\310\310\314\314\314\317\317\317" + "\321\321\321\322\322\322\323\323\323\324\324\324\324\324\324\325\325\325" + "\325\325\325\325\325\325\325\325\325\325\325\325\325\325\325\325\325\325" + "\325\325\325\325\325\325\325\325\325\324\324\324\323\323\323\322\322\322" + "\321\321\321\317\317\317\314\314\314\310\310\310\303\303\303\275\275\275" + "\265\265\265\254\254\254\241\241\241\225\225\225\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0DDDRRRaaaqqq\200\200\200\217\217\217\235\235" + "\235\252\252\252\265\265\265\276\276\276\307\307\307\315\315\315\322\322" + "\322\326\326\326\331\331\331\333\333\333\334\334\334\335\335\335\336\336" + "\336\336\336\336\337\337\337\337\337\337\337\337\337\337\337\337\337\337" + "\337\337\337\337\337\337\337\337\337\337\337\337\337\337\337\337\336\336" + "\336\335\335\335\334\334\334\333\333\333\331\331\331\326\326\326\322\322" + "\322\315\315\315\307\307\307\277\277\277\265\265\265\252\252\252\235\235" + "\235\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0HHHWWWfffvvv\206" + "\206\206\226\226\226\244\244\244\261\261\261\275\275\275\306\306\306\317" + "\317\317\325\325\325\332\332\332\336\336\336\341\341\341\343\343\343\345" + "\345\345\345\345\345\346\346\346\347\347\347\347\347\347\347\347\347\347" + "\347\347\347\347\347\347\347\347\347\347\347\347\347\347\347\347\347\347" + "\347\347\347\347\347\346\346\346\346\346\346\345\345\345\343\343\343\341" + "\341\341\336\336\336\332\332\332\325\325\325\317\317\317\306\306\306\275" + "\275\275\261\261\261\244\244\244\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0KKKZZZjjj{{{\213\213\213\233\233\233\252\252\252\267\267\267" + "\303\303\303\315\315\315\325\325\325\334\334\334\341\341\341\345\345\345" + "\350\350\350\352\352\352\354\354\354\354\354\354\355\355\355\356\356\356" + "\356\356\356\356\356\356\356\356\356\356\356\356\356\356\356\356\356\356" + "\356\356\356\356\356\356\356\356\356\356\356\356\355\355\355\355\355\355" + "\354\354\354\352\352\352\350\350\350\345\345\345\341\341\341\334\334\334" + "\325\325\325\315\315\315\303\303\303\267\267\267\252\252\252\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0MMM]]]mmm~~~\217\217\217\237\237" + "\237\256\256\256\274\274\274\310\310\310\322\322\322\332\332\332\341\341" + "\341\346\346\346\352\352\352\355\355\355\357\357\357\361\361\361\362\362" + "\362\362\362\362\363\363\363\363\363\363\363\363\363\363\363\363\363\363" + "\363\363\363\363\363\363\363\363\363\363\363\363\363\363\363\363\363\363" + "\363\362\362\362\362\362\362\361\361\361\357\357\357\355\355\355\352\352" + "\352\346\346\346\341\341\341\332\332\332\322\322\322\310\310\310\274\274" + "\274\256\256\256\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0NN" + "N^^^ooo\200\200\200\221\221\221\242\242\242\261\261\261\277\277\277\313\313" + "\313\325\325\325\335\335\335\344\344\344\351\351\351\355\355\355\360\360" + "\360\362\362\362\364\364\364\365\365\365\365\365\365\366\366\366\366\366" + "\366\366\366\366\366\366\366\366\366\366\366\366\366\366\366\366\366\366" + "\366\366\366\366\366\366\366\366\366\366\365\365\365\365\365\365\364\364" + "\364\362\362\362\360\360\360\355\355\355\351\351\351\344\344\344\335\335" + "\335\325\325\325\313\313\313\277\277\277\261\261\261\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0PPP```qqq\202\202\202\223\223\223\244\244" + "\244\263\263\263\301\301\301\315\315\315\327\327\327\340\340\340\346\346" + "\346\354\354\354\357\357\357\362\362\362\364\364\364\366\366\366\367\367" + "\367\367\367\367\370\370\370\370\370\370\370\370\370\370\370\370\370\370" + "\370\370\370\370\370\370\370\370\370\370\370\370\370\370\370\370\370\370" + "\370\367\367\367\367\367\367\366\366\366\364\364\364\362\362\362\360\360" + "\360\354\354\354\346\346\346\340\340\340\327\327\327\315\315\315\301\301" + "\301\263\263\263\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0PP" + "P```qqq\203\203\203\224\224\224\245\245\245\264\264\264\302\302\302\316\316" + "\316\331\331\331\341\341\341\350\350\350\355\355\355\361\361\361\364\364" + "\364\366\366\366\370\370\370\371\371\371\371\371\371\372\372\372\372\372" + "\372\372\372\372\372\372\372\372\372\372\372\372\372\372\372\372\372\372" + "\372\372\372\372\372\372\372\372\372\372\371\371\371\371\371\371\370\370" + "\370\366\366\366\364\364\364\361\361\361\355\355\355\350\350\350\341\341" + "\341\331\331\331\316\316\316\302\302\302\264\264\264\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0PPP```qqq\203\203\203\224\224\224\245\245" + "\245\264\264\264\302\302\302\316\316\316\331\331\331\341\341\341\350\350" + "\350\355\355\355\361\361\361\364\364\364\366\366\366\370\370\370\371\371" + "\371\371\371\371\372\372\372\372\372\372\372\372\372\372\372\372\372\372" + "\372\372\372\372\372\372\372\372\372\372\372\372\372\372\372\372\372\372" + "\372\371\371\371\371\371\371\370\370\370\366\366\366\364\364\364\361\361" + "\361\355\355\355\350\350\350\341\341\341\331\331\331\316\316\316\302\302" + "\302\264\264\264\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0PP" + "P```qqq\202\202\202\223\223\223\244\244\244\263\263\263\301\301\301\315\315" + "\315\327\327\327\340\340\340\346\346\346\354\354\354\357\357\357\362\362" + "\362\364\364\364\366\366\366\367\367\367\367\367\367\370\370\370\370\370" + "\370\370\370\370\370\370\370\370\370\370\370\370\370\370\370\370\370\370" + "\370\370\370\370\370\370\370\370\370\370\367\367\367\367\367\367\366\366" + "\366\364\364\364\362\362\362\360\360\360\354\354\354\346\346\346\340\340" + "\340\327\327\327\315\315\315\301\301\301\263\263\263\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0NNN^^^ooo\200\200\200\221\221\221\242\242" + "\242\261\261\261\277\277\277\313\313\313\325\325\325\335\335\335\344\344" + "\344\351\351\351\355\355\355\360\360\360\362\362\362\364\364\364\365\365" + "\365\365\365\365\366\366\366\366\366\366\366\366\366\366\366\366\366\366" + "\366\366\366\366\366\366\366\366\366\366\366\366\366\366\366\366\366\366" + "\366\365\365\365\365\365\365\364\364\364\362\362\362\360\360\360\355\355" + "\355\351\351\351\344\344\344\335\335\335\325\325\325\313\313\313\277\277" + "\277\261\261\261\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0MM" + "M]]]mmm~~~\217\217\217\237\237\237\256\256\256\274\274\274\310\310\310\322" + "\322\322\332\332\332\341\341\341\346\346\346\352\352\352\355\355\355\357" + "\357\357\361\361\361\362\362\362\362\362\362\363\363\363\363\363\363\363" + "\363\363\363\363\363\363\363\363\363\363\363\363\363\363\363\363\363\363" + "\363\363\363\363\363\363\363\363\362\362\362\362\362\362\361\361\361\357" + "\357\357\355\355\355\352\352\352\346\346\346\341\341\341\332\332\332\322" + "\322\322\310\310\310\274\274\274\256\256\256\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0HHHZZZjjj{{{\213\213\213\233\233\233\252\252\252" + "\267\267\267\303\303\303\315\315\315\325\325\325\334\334\334\341\341\341" + "\345\345\345\350\350\350\352\352\352\354\354\354\354\354\354\355\355\355" + "\356\356\356\356\356\356\356\356\356\356\356\356\356\356\356\356\356\356" + "\356\356\356\356\356\356\356\356\356\356\356\356\356\356\356\355\355\355" + "\355\355\355\354\354\354\352\352\352\350\350\350\345\345\345\341\341\341" + "\334\334\334\325\325\325\315\315\315\303\303\303\267\267\267\243\243\243" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0""999WWWfffvvv\206" + "\206\206\226\226\226\244\244\244\261\261\261\275\275\275\306\306\306\317" + "\317\317\325\325\325\332\332\332\336\336\336\341\341\341\343\343\343\345" + "\345\345\345\345\345\346\346\346\347\347\347\347\347\347\347\347\347\347" + "\347\347\347\347\347\347\347\347\347\347\347\347\347\347\347\347\347\347" + "\347\347\347\347\347\346\346\346\346\346\346\345\345\345\343\343\343\341" + "\341\341\336\336\336\332\332\332\325\325\325\317\317\317\306\306\306\275" + "\275\275\261\261\261\201\201\201\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\36\36\36RRRaaaqqq\200\200\200\217\217\217\235\235\235\252" + "\252\252\265\265\265\277\277\277\307\307\307\315\315\315\322\322\322\326" + "\326\326\331\331\331\333\333\333\334\334\334\335\335\335\336\336\336\336" + "\336\336\337\337\337\337\337\337\337\337\337\337\337\337\337\337\337\337" + "\337\337\337\337\337\337\337\337\337\337\337\337\337\337\336\336\336\335" + "\335\335\335\335\335\333\333\333\331\331\331\326\326\326\322\322\322\315" + "\315\315\307\307\307\277\277\277\265\265\265\252\252\252GGG\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0===\\\\\\jjjyyy\207\207\207" + "\225\225\225\241\241\241\254\254\254\265\265\265\275\275\275\303\303\303" + "\310\310\310\314\314\314\317\317\317\321\321\321\322\322\322\323\323\323" + "\324\324\324\324\324\324\325\325\325\325\325\325\325\325\325\325\325\325" + "\325\325\325\325\325\325\325\325\325\325\325\325\325\325\325\325\325\325" + "\324\324\324\323\323\323\322\322\322\321\321\321\317\317\317\314\314\314" + "\310\310\310\303\303\303\275\275\275\265\265\265\254\254\254~~~\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10CCCcccq" + "qq~~~\213\213\213\227\227\227\241\241\241\252\252\252\261\261\261\270\270" + "\270\274\274\274\300\300\300\303\303\303\305\305\305\306\306\306\307\307" + "\307\310\310\310\310\310\310\311\311\311\311\311\311\311\311\311\311\311" + "\311\311\311\311\311\311\311\311\311\311\311\311\311\311\311\311\310\310" + "\310\310\310\310\307\307\307\306\306\306\305\305\305\303\303\303\300\300" + "\300\275\275\275\270\270\270\262\262\262\252\252\252~~~\22\22\22\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0))" + ")RRRooo\200\200\200\213\213\213\225\225\225\235\235\235\244\244\244\252\252" + "\252\257\257\257\262\262\262\265\265\265\267\267\267\270\270\270\271\271" + "\271\272\272\272\272\272\272\273\273\273\273\273\273\273\273\273\273\273" + "\273\273\273\273\273\273\273\273\273\273\273\273\273\273\273\273\272\272" + "\272\272\272\272\271\271\271\270\270\270\267\267\267\265\265\265\262\262" + "\262\257\257\257\243\243\243\201\201\201GGG\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" +}; + +#endif + \ No newline at end of file diff --git a/ffmpegthumbs/ffmpegthumbnailer/filmstripfilter.cpp b/ffmpegthumbs/ffmpegthumbnailer/filmstripfilter.cpp new file mode 100644 index 00000000..377ead07 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/filmstripfilter.cpp @@ -0,0 +1,97 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "filmstrip.h" +#include "filmstripfilter.h" + +namespace ffmpegthumbnailer +{ + +static const int FILMHOLE_WIDTH = 12; +static const int FILMHOLE_HEIGHT = 10; + +static const quint8* determineFilmStrip(quint32 videoWidth, quint32& filmStripWidth, quint32& filmStripHeight) +{ + if (videoWidth <= SMALLEST_FILM_STRIP_WIDTH * 2) + { + return NULL; + } + + if (videoWidth <= 96) + { + filmStripWidth = filmStripHeight = 4; + return filmStrip4; + } + + if (videoWidth <= 192) + { + filmStripWidth = filmStripHeight = 8; + return filmStrip8; + } + + if (videoWidth <= 384) + { + filmStripWidth = filmStripHeight = 16; + return filmStrip16; + } + + if (videoWidth <= 768) + { + filmStripWidth = filmStripHeight = 32; + return filmStrip32; + } + + filmStripWidth = filmStripHeight = 64; + return filmStrip64; +} + + + +void FilmStripFilter::process(VideoFrame& videoFrame) +{ + quint32 filmStripWidth; + quint32 filmStripHeight; + const quint8* filmHole = determineFilmStrip(videoFrame.width, filmStripWidth, filmStripHeight); + + if (!filmHole) + { + return; + } + + int frameIndex = 0; + int filmHoleIndex = 0; + int offset = (videoFrame.width * 3) - 3; + + for (quint32 i = 0; i < videoFrame.height; ++i) + { + for (quint32 j = 0; j < filmStripWidth * 3; j+=3) + { + int currentFilmHoleIndex = filmHoleIndex + j; + + videoFrame.frameData[frameIndex + j] = filmHole[currentFilmHoleIndex]; + videoFrame.frameData[frameIndex + j + 1] = filmHole[currentFilmHoleIndex + 1]; + videoFrame.frameData[frameIndex + j + 2] = filmHole[currentFilmHoleIndex + 2]; + + videoFrame.frameData[frameIndex + offset - j] = filmHole[currentFilmHoleIndex]; + videoFrame.frameData[frameIndex + offset - j + 1] = filmHole[currentFilmHoleIndex + 1]; + videoFrame.frameData[frameIndex + offset - j + 2] = filmHole[currentFilmHoleIndex + 2]; + } + frameIndex += videoFrame.lineSize; + filmHoleIndex = (i % filmStripHeight) * filmStripWidth * 3; + } +} + +} diff --git a/ffmpegthumbs/ffmpegthumbnailer/filmstripfilter.h b/ffmpegthumbs/ffmpegthumbnailer/filmstripfilter.h new file mode 100644 index 00000000..b27b3e0c --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/filmstripfilter.h @@ -0,0 +1,34 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef FILMSTRIPFILTER_H +#define FILMSTRIPFILTER_H + +#include "ifilter.h" + +namespace ffmpegthumbnailer +{ + +class FilmStripFilter : public IFilter +{ +public: + virtual ~FilmStripFilter() {} + virtual void process(VideoFrame& videoFrame); +}; + +} + +#endif diff --git a/ffmpegthumbs/ffmpegthumbnailer/histogram.h b/ffmpegthumbs/ffmpegthumbnailer/histogram.h new file mode 100644 index 00000000..045bc827 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/histogram.h @@ -0,0 +1,40 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef HISTOGRAM_H +#define HISTOGRAM_H + +#include + +namespace ffmpegthumbnailer +{ + +template +struct Histogram { + T r[256]; + T g[256]; + T b[256]; + + Histogram() { + memset(r, 0, 255 * sizeof(T)); + memset(g, 0, 255 * sizeof(T)); + memset(b, 0, 255 * sizeof(T)); + } +}; + +} + +#endif diff --git a/ffmpegthumbs/ffmpegthumbnailer/ifilter.h b/ffmpegthumbs/ffmpegthumbnailer/ifilter.h new file mode 100644 index 00000000..30333613 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/ifilter.h @@ -0,0 +1,34 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef IFILTER_H +#define IFILTER_H + +#include "videoframe.h" + +namespace ffmpegthumbnailer +{ + +class IFilter +{ +public: + virtual ~IFilter() {}; + virtual void process(VideoFrame& frameData) = 0; +}; + +} + +#endif diff --git a/ffmpegthumbs/ffmpegthumbnailer/imagewriter.cpp b/ffmpegthumbs/ffmpegthumbnailer/imagewriter.cpp new file mode 100644 index 00000000..7dae371b --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/imagewriter.cpp @@ -0,0 +1,41 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "imagewriter.h" +#include +#include + + +using namespace std; + +namespace ffmpegthumbnailer +{ + +ImageWriter::ImageWriter() +{ +} + +void ImageWriter::writeFrame(VideoFrame& frame, QImage& image) +{ + QImage previewImage(frame.width, frame.height, QImage::Format_RGB888); + for (quint32 y = 0; y < frame.height; y++) { + // Copy each line .. + memcpy(previewImage.scanLine(y), &frame.frameData[y*frame.lineSize], frame.width*3); + } + + image = previewImage; +} +} diff --git a/ffmpegthumbs/ffmpegthumbnailer/imagewriter.h b/ffmpegthumbs/ffmpegthumbnailer/imagewriter.h new file mode 100644 index 00000000..f73d564a --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/imagewriter.h @@ -0,0 +1,37 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef IMAGE_WRITER_H +#define IMAGE_WRITER_H +#include "videoframe.h" +#include +#include +#include +namespace ffmpegthumbnailer +{ + +class ImageWriter +{ +public: + ImageWriter(); + virtual ~ImageWriter() {} + + virtual void writeFrame(VideoFrame& frame, QImage& image); +}; + +} + +#endif diff --git a/ffmpegthumbs/ffmpegthumbnailer/moviedecoder.cpp b/ffmpegthumbs/ffmpegthumbnailer/moviedecoder.cpp new file mode 100644 index 00000000..d63cd4b2 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/moviedecoder.cpp @@ -0,0 +1,367 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "moviedecoder.h" + +#include +#include + +extern "C" { +#include +} + +using namespace std; + +namespace ffmpegthumbnailer +{ + +MovieDecoder::MovieDecoder(const QString& filename, AVFormatContext* pavContext) + : m_VideoStream(-1) + , m_pFormatContext(pavContext) + , m_pVideoCodecContext(NULL) + , m_pVideoCodec(NULL) + , m_pVideoStream(NULL) + , m_pFrame(NULL) + , m_pFrameBuffer(NULL) + , m_pPacket(NULL) + , m_FormatContextWasGiven(pavContext != NULL) + , m_AllowSeek(true) + , m_initialized(false) +{ + initialize(filename); +} + +MovieDecoder::~MovieDecoder() +{ + destroy(); +} + +void MovieDecoder::initialize(const QString& filename) +{ + av_register_all(); + avcodec_register_all(); + + QFileInfo fileInfo(filename); + + if ((!m_FormatContextWasGiven) && avformat_open_input(&m_pFormatContext, fileInfo.absoluteFilePath().toLocal8Bit().data(), NULL, NULL) != 0) { + kDebug() << "Could not open input file: " << fileInfo.absoluteFilePath(); + return; + } + + if (avformat_find_stream_info(m_pFormatContext, 0) < 0) { + kDebug() << "Could not find stream information"; + return; + } + + initializeVideo(); + m_pFrame = avcodec_alloc_frame(); + + if (m_pFrame) { + m_initialized=true; + } +} + +bool MovieDecoder::getInitialized() +{ + return m_initialized; +} + + +void MovieDecoder::destroy() +{ + if (m_pVideoCodecContext) { + avcodec_close(m_pVideoCodecContext); + m_pVideoCodecContext = NULL; + } + + if ((!m_FormatContextWasGiven) && m_pFormatContext) { + avformat_close_input(&m_pFormatContext); + m_pFormatContext = NULL; + } + + if (m_pPacket) { + av_free_packet(m_pPacket); + delete m_pPacket; + m_pPacket = NULL; + } + + if (m_pFrame) { + av_free(m_pFrame); + m_pFrame = NULL; + } + + if (m_pFrameBuffer) { + av_free(m_pFrameBuffer); + m_pFrameBuffer = NULL; + } +} + +QString MovieDecoder::getCodec() +{ + QString codecName; + if (m_pVideoCodec) { + codecName=QString::fromLatin1(m_pVideoCodec->name); + } + return codecName; +} + +void MovieDecoder::initializeVideo() +{ + for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++) { + if (m_pFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + m_pVideoStream = m_pFormatContext->streams[i]; + m_VideoStream = i; + break; + } + } + + if (m_VideoStream == -1) { + kDebug() << "Could not find video stream"; + return; + } + + m_pVideoCodecContext = m_pFormatContext->streams[m_VideoStream]->codec; + m_pVideoCodec = avcodec_find_decoder(m_pVideoCodecContext->codec_id); + + if (m_pVideoCodec == NULL) { + // set to NULL, otherwise avcodec_close(m_pVideoCodecContext) crashes + m_pVideoCodecContext = NULL; + kDebug() << "Video Codec not found"; + return; + } + + m_pVideoCodecContext->workaround_bugs = 1; + + if (avcodec_open2(m_pVideoCodecContext, m_pVideoCodec, 0) < 0) { + kDebug() << "Could not open video codec"; + } +} + +int MovieDecoder::getWidth() +{ + if (m_pVideoCodecContext) { + return m_pVideoCodecContext->width; + } + + return -1; +} + +int MovieDecoder::getHeight() +{ + if (m_pVideoCodecContext) { + return m_pVideoCodecContext->height; + } + + return -1; +} + +int MovieDecoder::getDuration() +{ + if (m_pFormatContext) { + return static_cast(m_pFormatContext->duration / AV_TIME_BASE); + } + + return 0; +} + +void MovieDecoder::seek(int timeInSeconds) +{ + if (!m_AllowSeek) { + return; + } + + qint64 timestamp = AV_TIME_BASE * static_cast(timeInSeconds); + + if (timestamp < 0) { + timestamp = 0; + } + + int ret = av_seek_frame(m_pFormatContext, -1, timestamp, 0); + if (ret >= 0) { + avcodec_flush_buffers(m_pFormatContext->streams[m_VideoStream]->codec); + } else { + kDebug() << "Seeking in video failed"; + return; + } + + int keyFrameAttempts = 0; + bool gotFrame = 0; + + do { + int count = 0; + gotFrame = 0; + + while (!gotFrame && count < 20) { + getVideoPacket(); + gotFrame = decodeVideoPacket(); + ++count; + } + + ++keyFrameAttempts; + } while ((!gotFrame || !m_pFrame->key_frame) && keyFrameAttempts < 200); + + if (gotFrame == 0) { + kDebug() << "Seeking in video failed"; + } +} + + +void MovieDecoder::decodeVideoFrame() +{ + bool frameFinished = false; + + while (!frameFinished && getVideoPacket()) { + frameFinished = decodeVideoPacket(); + } + + if (!frameFinished) { + kDebug() << "decodeVideoFrame() failed: frame not finished"; + return; + } +} + +bool MovieDecoder::decodeVideoPacket() +{ + if (m_pPacket->stream_index != m_VideoStream) { + return false; + } + + avcodec_get_frame_defaults(m_pFrame); + + int frameFinished = 0; + +#if LIBAVCODEC_VERSION_MAJOR < 53 + int bytesDecoded = avcodec_decode_video(m_pVideoCodecContext, m_pFrame, &frameFinished, m_pPacket->data, m_pPacket->size); +#else + int bytesDecoded = avcodec_decode_video2(m_pVideoCodecContext, m_pFrame, &frameFinished, m_pPacket); +#endif + + if (bytesDecoded < 0) { + kDebug() << "Failed to decode video frame: bytesDecoded < 0"; + } + + return (frameFinished > 0); +} + +bool MovieDecoder::getVideoPacket() +{ + bool framesAvailable = true; + bool frameDecoded = false; + + int attempts = 0; + + if (m_pPacket) { + av_free_packet(m_pPacket); + delete m_pPacket; + } + + m_pPacket = new AVPacket(); + + while (framesAvailable && !frameDecoded && (attempts++ < 1000)) { + framesAvailable = av_read_frame(m_pFormatContext, m_pPacket) >= 0; + if (framesAvailable) { + frameDecoded = m_pPacket->stream_index == m_VideoStream; + if (!frameDecoded) { + av_free_packet(m_pPacket); + } + } + } + + return frameDecoded; +} + +void MovieDecoder::getScaledVideoFrame(int scaledSize, bool maintainAspectRatio, VideoFrame& videoFrame) +{ + if (m_pFrame->interlaced_frame) { + avpicture_deinterlace((AVPicture*) m_pFrame, (AVPicture*) m_pFrame, m_pVideoCodecContext->pix_fmt, + m_pVideoCodecContext->width, m_pVideoCodecContext->height); + } + + int scaledWidth, scaledHeight; + convertAndScaleFrame(PIX_FMT_RGB24, scaledSize, maintainAspectRatio, scaledWidth, scaledHeight); + + videoFrame.width = scaledWidth; + videoFrame.height = scaledHeight; + videoFrame.lineSize = m_pFrame->linesize[0]; + + videoFrame.frameData.clear(); + videoFrame.frameData.resize(videoFrame.lineSize * videoFrame.height); + memcpy((&(videoFrame.frameData.front())), m_pFrame->data[0], videoFrame.lineSize * videoFrame.height); +} + +void MovieDecoder::convertAndScaleFrame(PixelFormat format, int scaledSize, bool maintainAspectRatio, int& scaledWidth, int& scaledHeight) +{ + calculateDimensions(scaledSize, maintainAspectRatio, scaledWidth, scaledHeight); + SwsContext* scaleContext = sws_getContext(m_pVideoCodecContext->width, m_pVideoCodecContext->height, + m_pVideoCodecContext->pix_fmt, scaledWidth, scaledHeight, + format, SWS_BICUBIC, NULL, NULL, NULL); + + if (NULL == scaleContext) { + kDebug() << "Failed to create resize context"; + return; + } + + AVFrame* convertedFrame = NULL; + uint8_t* convertedFrameBuffer = NULL; + + createAVFrame(&convertedFrame, &convertedFrameBuffer, scaledWidth, scaledHeight, format); + + sws_scale(scaleContext, m_pFrame->data, m_pFrame->linesize, 0, m_pVideoCodecContext->height, + convertedFrame->data, convertedFrame->linesize); + sws_freeContext(scaleContext); + + av_free(m_pFrame); + av_free(m_pFrameBuffer); + + m_pFrame = convertedFrame; + m_pFrameBuffer = convertedFrameBuffer; +} + +void MovieDecoder::calculateDimensions(int squareSize, bool maintainAspectRatio, int& destWidth, int& destHeight) +{ + if (!maintainAspectRatio) { + destWidth = squareSize; + destHeight = squareSize; + } else { + int srcWidth = m_pVideoCodecContext->width; + int srcHeight = m_pVideoCodecContext->height; + int ascpectNominator = m_pVideoCodecContext->sample_aspect_ratio.num; + int ascpectDenominator = m_pVideoCodecContext->sample_aspect_ratio.den; + + if (ascpectNominator != 0 && ascpectDenominator != 0) { + srcWidth = srcWidth * ascpectNominator / ascpectDenominator; + } + + if (srcWidth > srcHeight) { + destWidth = squareSize; + destHeight = static_cast(static_cast(squareSize) / srcWidth * srcHeight); + } else { + destWidth = static_cast(static_cast(squareSize) / srcHeight * srcWidth); + destHeight = squareSize; + } + } +} + +void MovieDecoder::createAVFrame(AVFrame** avFrame, quint8** frameBuffer, int width, int height, PixelFormat format) +{ + *avFrame = avcodec_alloc_frame(); + + int numBytes = avpicture_get_size(format, width, height); + *frameBuffer = reinterpret_cast(av_malloc(numBytes)); + avpicture_fill((AVPicture*) *avFrame, *frameBuffer, format, width, height); +} + +} diff --git a/ffmpegthumbs/ffmpegthumbnailer/moviedecoder.h b/ffmpegthumbs/ffmpegthumbnailer/moviedecoder.h new file mode 100644 index 00000000..28889268 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/moviedecoder.h @@ -0,0 +1,75 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef MOVIEDECODER_H +#define MOVIEDECODER_H + +#include "videoframe.h" +#include + +extern "C" { +#include +#include +} + +namespace ffmpegthumbnailer +{ + +class MovieDecoder +{ +public: + MovieDecoder(const QString& filename, AVFormatContext* pavContext = NULL); + ~MovieDecoder(); + + QString getCodec(); + void seek(int timeInSeconds); + void decodeVideoFrame(); + void getScaledVideoFrame(int scaledSize, bool maintainAspectRatio, VideoFrame& videoFrame); + + int getWidth(); + int getHeight(); + int getDuration(); + + void initialize(const QString& filename); + void destroy(); + bool getInitialized(); + +private: + void initializeVideo(); + + bool decodeVideoPacket(); + bool getVideoPacket(); + void convertAndScaleFrame(PixelFormat format, int scaledSize, bool maintainAspectRatio, int& scaledWidth, int& scaledHeight); + void createAVFrame(AVFrame** avFrame, quint8** frameBuffer, int width, int height, PixelFormat format); + void calculateDimensions(int squareSize, bool maintainAspectRatio, int& destWidth, int& destHeight); + +private: + int m_VideoStream; + AVFormatContext* m_pFormatContext; + AVCodecContext* m_pVideoCodecContext; + AVCodec* m_pVideoCodec; + AVStream* m_pVideoStream; + AVFrame* m_pFrame; + quint8* m_pFrameBuffer; + AVPacket* m_pPacket; + bool m_FormatContextWasGiven; + bool m_AllowSeek; + bool m_initialized; +}; + +} + +#endif diff --git a/ffmpegthumbs/ffmpegthumbnailer/videoframe.h b/ffmpegthumbs/ffmpegthumbnailer/videoframe.h new file mode 100644 index 00000000..ae238558 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/videoframe.h @@ -0,0 +1,43 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef VIDEOFRAME_H +#define VIDEOFRAME_H + +#include +#include +#include + +namespace ffmpegthumbnailer +{ + +struct VideoFrame { + VideoFrame() + : width(0), height(0), lineSize(0) {} + + VideoFrame(int width, int height, int lineSize) + : width(width), height(height), lineSize(lineSize) {} + + quint32 width; + quint32 height; + quint32 lineSize; + + std::vector frameData; +}; + +} + +#endif diff --git a/ffmpegthumbs/ffmpegthumbnailer/videothumbnailer.cpp b/ffmpegthumbs/ffmpegthumbnailer/videothumbnailer.cpp new file mode 100644 index 00000000..97218c89 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/videothumbnailer.cpp @@ -0,0 +1,224 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "videothumbnailer.h" + +#include "moviedecoder.h" +#include "filmstripfilter.h" +#include "imagewriter.h" + +#include +#include +#include +#include +#include +#include + + +using namespace std; + +namespace ffmpegthumbnailer +{ + +static const int SMART_FRAME_ATTEMPTS = 25; + +VideoThumbnailer::VideoThumbnailer() + : m_ThumbnailSize(128) + , m_SeekPercentage(10) + , m_OverlayFilmStrip(false) + , m_WorkAroundIssues(false) + , m_MaintainAspectRatio(true) + , m_SmartFrameSelection(false) +{ +} + +VideoThumbnailer::VideoThumbnailer(int thumbnailSize, bool workaroundIssues, bool maintainAspectRatio, bool smartFrameSelection) + : m_ThumbnailSize(thumbnailSize) + , m_SeekPercentage(10) + , m_WorkAroundIssues(workaroundIssues) + , m_MaintainAspectRatio(maintainAspectRatio) + , m_SmartFrameSelection(smartFrameSelection) +{ +} + +VideoThumbnailer::~VideoThumbnailer() +{ +} + +void VideoThumbnailer::setSeekPercentage(int percentage) +{ + m_SeekTime.clear(); + m_SeekPercentage = percentage > 95 ? 95 : percentage; +} + +void VideoThumbnailer::setSeekTime(const QString& seekTime) +{ + m_SeekTime = seekTime; +} + +void VideoThumbnailer::setThumbnailSize(int size) +{ + m_ThumbnailSize = size; +} + +void VideoThumbnailer::setWorkAroundIssues(bool workAround) +{ + m_WorkAroundIssues = workAround; +} + +void VideoThumbnailer::setMaintainAspectRatio(bool enabled) +{ + m_MaintainAspectRatio = enabled; +} + +void VideoThumbnailer::setSmartFrameSelection(bool enabled) +{ + m_SmartFrameSelection = enabled; +} + +int timeToSeconds(const QString& time) +{ + return QTime::fromString(time, QLatin1String("hh:mm:ss")).secsTo(QTime(0, 0, 0)); +} + +void VideoThumbnailer::generateThumbnail(const QString& videoFile, ImageWriter& imageWriter, QImage &image) +{ + MovieDecoder movieDecoder(videoFile, NULL); + if (movieDecoder.getInitialized()) { + movieDecoder.decodeVideoFrame(); //before seeking, a frame has to be decoded + + if ((!m_WorkAroundIssues) || (movieDecoder.getCodec() != QLatin1String("h264"))) { //workaround for bug in older ffmpeg (100% cpu usage when seeking in h264 files) + int secondToSeekTo = m_SeekTime.isEmpty() ? movieDecoder.getDuration() * m_SeekPercentage / 100 : timeToSeconds(m_SeekTime); + movieDecoder.seek(secondToSeekTo); + } + + VideoFrame videoFrame; + + if (m_SmartFrameSelection) { + generateSmartThumbnail(movieDecoder, videoFrame); + } else { + movieDecoder.getScaledVideoFrame(m_ThumbnailSize, m_MaintainAspectRatio, videoFrame); + } + + applyFilters(videoFrame); + imageWriter.writeFrame(videoFrame, image); + } +} + +void VideoThumbnailer::generateSmartThumbnail(MovieDecoder& movieDecoder, VideoFrame& videoFrame) +{ + vector videoFrames(SMART_FRAME_ATTEMPTS); + vector > histograms(SMART_FRAME_ATTEMPTS); + + for (int i = 0; i < SMART_FRAME_ATTEMPTS; ++i) { + movieDecoder.decodeVideoFrame(); + movieDecoder.getScaledVideoFrame(m_ThumbnailSize, m_MaintainAspectRatio, videoFrames[i]); + generateHistogram(videoFrames[i], histograms[i]); + } + + int bestFrame = getBestThumbnailIndex(videoFrames, histograms); + + Q_ASSERT(bestFrame != -1); + videoFrame = videoFrames[bestFrame]; +} + +void VideoThumbnailer::generateThumbnail(const QString& videoFile, QImage &image) +{ + ImageWriter* imageWriter = new ImageWriter(); + generateThumbnail(videoFile, *imageWriter, image); + delete imageWriter; +} + +void VideoThumbnailer::addFilter(IFilter* filter) +{ + m_Filters.push_back(filter); +} + +void VideoThumbnailer::removeFilter(IFilter* filter) +{ + for (vector::iterator iter = m_Filters.begin(); + iter != m_Filters.end(); + ++iter) { + if (*iter == filter) { + m_Filters.erase(iter); + break; + } + } +} + +void VideoThumbnailer::clearFilters() +{ + m_Filters.clear(); +} + +void VideoThumbnailer::applyFilters(VideoFrame& videoFrame) +{ + for (vector::iterator iter = m_Filters.begin(); + iter != m_Filters.end(); + ++iter) { + (*iter)->process(videoFrame); + } +} + +void VideoThumbnailer::generateHistogram(const VideoFrame& videoFrame, Histogram& histogram) +{ + for (quint32 i = 0; i < videoFrame.height; ++i) { + int pixelIndex = i * videoFrame.lineSize; + for (quint32 j = 0; j < videoFrame.width * 3; j += 3) { + ++histogram.r[videoFrame.frameData[pixelIndex + j]]; + ++histogram.g[videoFrame.frameData[pixelIndex + j + 1]]; + ++histogram.b[videoFrame.frameData[pixelIndex + j + 2]]; + } + } +} + +int VideoThumbnailer::getBestThumbnailIndex(vector& videoFrames, const vector >& histograms) +{ + Q_UNUSED(videoFrames); + Histogram avgHistogram; + for (size_t i = 0; i < histograms.size(); ++i) { + for (int j = 0; j < 255; ++j) { + avgHistogram.r[j] += static_cast(histograms[i].r[j]) / histograms.size(); + avgHistogram.g[j] += static_cast(histograms[i].g[j]) / histograms.size(); + avgHistogram.b[j] += static_cast(histograms[i].b[j]) / histograms.size(); + } + } + + int bestFrame = -1; + float minRMSE = FLT_MAX; + for (size_t i = 0; i < histograms.size(); ++i) { + //calculate root mean squared error + float rmse = 0.0; + for (int j = 0; j < 255; ++j) { + float error = fabsf(avgHistogram.r[j] - histograms[i].r[j]) + + fabsf(avgHistogram.g[j] - histograms[i].g[j]) + + fabsf(avgHistogram.b[j] - histograms[i].b[j]); + rmse += (error * error) / 255; + } + + rmse = sqrtf(rmse); + if (rmse < minRMSE) { + minRMSE = rmse; + bestFrame = i; + } + } +#ifdef DEBUG_MODE + cout << "Best frame was: " << bestFrame << "(RMSE: " << minRMSE << ")" << endl; +#endif + return bestFrame; +} + +} diff --git a/ffmpegthumbs/ffmpegthumbnailer/videothumbnailer.h b/ffmpegthumbs/ffmpegthumbnailer/videothumbnailer.h new file mode 100644 index 00000000..8d9a90b7 --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbnailer/videothumbnailer.h @@ -0,0 +1,81 @@ +// Copyright (C) 2010 Dirk Vanden Boer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef VIDEO_THUMBNAILER_H +#define VIDEO_THUMBNAILER_H + +#include +#include +#include +#include + +#include "ifilter.h" +#include "histogram.h" +#include +#include + + +namespace ffmpegthumbnailer +{ + +class VideoFrame; +class ImageWriter; +class MovieDecoder; + +class VideoThumbnailer +{ +public: + VideoThumbnailer(); + VideoThumbnailer(int thumbnailSize, bool workaroundIssues, bool maintainAspectRatio, bool smartFrameSelection); + ~VideoThumbnailer(); + + void generateThumbnail(const QString& videoFile, QImage &image); + + void setThumbnailSize(int size); + void setSeekPercentage(int percentage); + void setSeekTime(const QString& seekTime); + void setWorkAroundIssues(bool workAround); + void setMaintainAspectRatio(bool enabled); + void setSmartFrameSelection(bool enabled); + void addFilter(IFilter* filter); + void removeFilter(IFilter* filter); + void clearFilters(); + +private: + void generateThumbnail(const QString& videoFile, ImageWriter& imageWriter, QImage& image); + void generateSmartThumbnail(MovieDecoder& movieDecoder, VideoFrame& videoFrame); + + QString getMimeType(const QString& videoFile); + QString getExtension(const QString& videoFilename); + + void generateHistogram(const VideoFrame& videoFrame, Histogram& histogram); + int getBestThumbnailIndex(std::vector& videoFrames, const std::vector >& histograms); + void applyFilters(VideoFrame& frameData); + +private: + int m_ThumbnailSize; + quint16 m_SeekPercentage; + bool m_OverlayFilmStrip; + bool m_WorkAroundIssues; + bool m_MaintainAspectRatio; + bool m_SmartFrameSelection; + QString m_SeekTime; + std::vector m_Filters; +}; + +} + +#endif diff --git a/ffmpegthumbs/ffmpegthumbs.desktop b/ffmpegthumbs/ffmpegthumbs.desktop new file mode 100644 index 00000000..b27e9a6d --- /dev/null +++ b/ffmpegthumbs/ffmpegthumbs.desktop @@ -0,0 +1,57 @@ +[Desktop Entry] +Type=Service +Name=Video Files (ffmpegthumbs) +Name[bg]=Видео файлове (ffmpegthumbs) +Name[bs]=Video datoteke (ffmpegthumbs) +Name[ca]=Fitxers de vídeo (ffmpegthumbs) +Name[ca@valencia]=Fitxers de vídeo (ffmpegthumbs) +Name[cs]=Soubory videa (ffmpegthumbs) +Name[da]=Videofiler (ffmpegthumbs) +Name[de]=Videodateien (ffmpegthumbs) +Name[el]=Αρχεία βίντεο (ffmpegthumbs) +Name[en_GB]=Video Files (ffmpegthumbs) +Name[es]=Archivos de vídeo (ffmpegthumbs) +Name[et]=Videofailid (ffmpegthumbs) +Name[fi]=Videotiedostot (ffmpegthumbs) +Name[fr]=Fichiers vidéo (ffmpegthumbs) +Name[ga]=Comhaid Fhíse (ffmpegthumbs) +Name[gl]=Ficheiros de vídeo (ffmpegthumbs) +Name[hu]=Videofájlok (ffmpegthumbs) +Name[ia]=Files Video (ffmpegthumbs) +Name[is]=Vídeóskrár (ffmpegthumbs) +Name[it]=File video (ffmpegthumbs) +Name[ja]=動画ファイル (ffmpegthumbs) +Name[kk]=Видео файлдар (ffmpegthumbs) +Name[km]=ឯកសារ​វីដេអូ (ffmpegthumbs) +Name[ko]=동영상 파일 (ffmpegthumbs) +Name[lt]=Video failai (ffmpegthumbs) +Name[mr]=व्हिडीओ फाईल्स (ffmpegthumbs) +Name[nb]=Videofiler (ffmpegthumbs) +Name[nds]=Videodateien (ffmpeg-Vöransichten) +Name[nl]=Videobestanden (ffmpeg-miniaturen) +Name[pa]=ਵਿਡੀਓ ਫਾਇਲਾਂ (ffmpegthumbs) +Name[pl]=Pliki filmów (ffmpegthumbs) +Name[pt]=Ficheiros de Vídeo (ffmpegthumbs) +Name[pt_BR]=Arquivos de vídeo (ffmpegthumbs) +Name[ro]=Fișiere video (ffmpegthumbs) +Name[ru]=Видеофайлы (ffmpegthumbs) +Name[sk]=Video súbory (ffmpegthumbs) +Name[sl]=Datoteke z videom (ffmpegthumbs) +Name[sr]=Видео фајлови (сличице ФФмпегом) +Name[sr@ijekavian]=Видео фајлови (сличице ФФмпегом) +Name[sr@ijekavianlatin]=Video fajlovi (sličice FFmpegom) +Name[sr@latin]=Video fajlovi (sličice FFmpegom) +Name[sv]=Videofiler (ffmpeg miniatyrbilder) +Name[tr]=Video Dosyaları (ffmpegthumbs) +Name[ug]=سىن ھۆججەتلەر (ffmpegthumbs) +Name[uk]=Файли відео (ffmpegthumbs) +Name[x-test]=xxVideo Files (ffmpegthumbs)xx +Name[zh_CN]=视频文件(ffmpegthumbs) +Name[zh_TW]=影像檔 (ffmpegthumbs) +X-KDE-ServiceTypes=ThumbCreator +MimeType=video/*;application/x-flash-video;application/vnd.ms-asf;application/vnd.rn-realmedia; +X-KDE-Library=ffmpegthumbs +ServiceTypes=ThumbCreator +CacheThumbnail=true +ThumbnailerVersion=2 +IgnoreMaximumSize=true diff --git a/ffmpegthumbs/tests/CMakeLists.txt b/ffmpegthumbs/tests/CMakeLists.txt new file mode 100644 index 00000000..5525e00d --- /dev/null +++ b/ffmpegthumbs/tests/CMakeLists.txt @@ -0,0 +1,26 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +########### next target ############### + + +include_directories( .. ) + + +########### next target ############### +set(ffmpegthumbtest_SRCS ffmpegthumbtest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../ffmpegthumbnailer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../ffmpegthumbnailer/filmstripfilter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../ffmpegthumbnailer/moviedecoder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../ffmpegthumbnailer/imagewriter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../ffmpegthumbnailer/videothumbnailer.cpp +) + + +kde4_add_executable(ffmpegthumbtest ${ffmpegthumbtest_SRCS} ) + +target_link_libraries(ffmpegthumbtest ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} ${AVUTIL_LIBRARIES} ${AVFORMAT_LIBRARIES} ${AVCODEC_LIBRARIES} ${SWSCALE_LIBRARIES}) + + + + diff --git a/ffmpegthumbs/tests/ffmpegthumbtest.cpp b/ffmpegthumbs/tests/ffmpegthumbtest.cpp new file mode 100644 index 00000000..ca4fc9d1 --- /dev/null +++ b/ffmpegthumbs/tests/ffmpegthumbtest.cpp @@ -0,0 +1,44 @@ +// Copyright (C) 2010 Andreas Scherf +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "ffmpegthumbnailer.h" + +#include +#include +#include +#include + +using namespace std; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + QStringList arguments = app.arguments(); + + if (arguments.count() > 1) { + QString inputFilename(arguments.last()); + QImage image; + FFMpegThumbnailer *thumbnailer = new FFMpegThumbnailer(); + thumbnailer->create(inputFilename, 128, 128, image); + QFileInfo fileInfo(inputFilename); + image.save(fileInfo.baseName() + ".png"); + delete thumbnailer; + } else { + cout << "Usage:" << arguments.at(0).toLocal8Bit().data() << " filename" << endl; + } + return 0; +} + diff --git a/gwenview/.gitignore b/gwenview/.gitignore new file mode 100644 index 00000000..c3d76105 --- /dev/null +++ b/gwenview/.gitignore @@ -0,0 +1,2 @@ +.*.swp +tags diff --git a/gwenview/.kateconfig b/gwenview/.kateconfig new file mode 100644 index 00000000..39f52636 --- /dev/null +++ b/gwenview/.kateconfig @@ -0,0 +1 @@ +kate: tab-indents off; indent-width 4; replace-tabs on; diff --git a/gwenview/.krazy b/gwenview/.krazy new file mode 100644 index 00000000..bae7c8d8 --- /dev/null +++ b/gwenview/.krazy @@ -0,0 +1 @@ +SKIP /lib/libjpeg/ diff --git a/gwenview/.reviewboardrc b/gwenview/.reviewboardrc new file mode 100644 index 00000000..19f94440 --- /dev/null +++ b/gwenview/.reviewboardrc @@ -0,0 +1,4 @@ +REVIEWBOARD_URL = "https://git.reviewboard.kde.org" +REPOSITORY = "git://anongit.kde.org/gwenview" +BRANCH = "master" +TARGET_GROUPS = "gwenview" diff --git a/gwenview/CMakeLists.txt b/gwenview/CMakeLists.txt new file mode 100644 index 00000000..f463c839 --- /dev/null +++ b/gwenview/CMakeLists.txt @@ -0,0 +1,101 @@ +project(gwenview) + +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ) + +find_package(KDE4 4.6.41 REQUIRED) +include(KDE4Defaults) +include(MacroLibrary) +include(MacroOptionalAddSubdirectory) + +KDE4_NO_ENABLE_FINAL(gwenview) + +## CMake options +set(GWENVIEW_SEMANTICINFO_BACKEND_NONE OFF) +set(GWENVIEW_SEMANTICINFO_BACKEND_FAKE OFF) +set(GWENVIEW_SEMANTICINFO_BACKEND_BALOO OFF) + +set(GWENVIEW_SEMANTICINFO_BACKEND "Baloo" CACHE STRING "Semantic info backend for Gwenview (Baloo/Fake/None)") + +# Init GWENVIEW_SEMANTICINFO_BACKEND_* vars +if (GWENVIEW_SEMANTICINFO_BACKEND STREQUAL "None") + set(GWENVIEW_SEMANTICINFO_BACKEND_NONE ON) +elseif (GWENVIEW_SEMANTICINFO_BACKEND STREQUAL "Fake") + set(GWENVIEW_SEMANTICINFO_BACKEND_FAKE ON) +else() + set(GWENVIEW_SEMANTICINFO_BACKEND_BALOO ON) +endif() + +## Dependencies +find_package(JPEG) +macro_log_feature(JPEG_FOUND "libjpeg" "JPEG image manipulation support" "http://libjpeg.sourceforge.net/" TRUE) + +find_package(PNG) +macro_log_feature(PNG_FOUND "libpng" "PNG image manipulation support" "http://www.libpng.org" TRUE) + +find_package(Exiv2) +macro_log_feature(EXIV2_FOUND "Exiv2" "Image metadata support" "http://www.exiv2.org" TRUE "0.19") + +macro_optional_find_package(Kipi) +macro_log_feature(KIPI_FOUND "libkipi" "Provides various image manipulation and export features" "http://www.kipi-plugins.org" FALSE) + +find_package(LCMS2) +macro_log_feature(LCMS2_FOUND "LittleCMS" "Color management engine" "http://www.littlecms.com" TRUE "2.0") + +if (NOT GWENVIEW_SEMANTICINFO_BACKEND_NONE) + # We need Baloo for all backends but "None" + find_package(Baloo) + macro_log_feature(Baloo_FOUND "Baloo Core Libraries" "Desktop-wide semantic information support" "https://projects.kde.org/projects/kde/kdelibs/baloo" TRUE) + + find_package(KFileMetaData CONFIG) + macro_log_feature(KFileMetaData_FOUND "KFileMetaData Library" "A library for extracting file metadata" "https://projects.kde.org/kfilemetadata" TRUE) +endif() + +find_package(LibKonq) +macro_log_feature(LIBKONQ_FOUND "libkonq" "Standard file management features" "http://www.kde.org" TRUE) + +find_package(KActivities 6.1.0) +macro_log_feature(KActivities_FOUND "KActivities" "Activities interface library" "https://projects.kde.org/projects/kde/kdelibs/kactivities" TRUE "6.1.0") + +find_package(Kdcraw 2.4.2) +macro_log_feature(KDCRAW_FOUND "libkdcraw" "KDE Dcraw library" "http://www.kde.org" TRUE "2.4.2") + +## Global settings + +add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) +add_definitions( + ${QT_DEFINITIONS} + ${QT_QTDBUS_DEFINITIONS} + ${KDE4_DEFINITIONS} + ) + +include_directories( + ${QDBUS_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR} + ${CMAKE_BINARY_DIR} + ${KDE4_INCLUDES} + ${EXIV2_INCLUDE_DIR} + ${KDCRAW_INCLUDE_DIR} + ) + +if (NOT GWENVIEW_SEMANTICINFO_BACKEND_NONE) + include_directories( + ${BALOO_INCLUDE_DIR} + ${KFILEMETADATA_INCLUDE_DIR} + ) +endif() + +## dirs to build +add_subdirectory(lib) +add_subdirectory(app) +add_subdirectory(importer) +add_subdirectory(part) +add_subdirectory(tests) +add_subdirectory(icons) +add_subdirectory(images) +add_subdirectory(cursors) +add_subdirectory(color-schemes) +add_subdirectory(doc) + +configure_file(config-gwenview.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gwenview.h) + +macro_display_feature_log() diff --git a/gwenview/COPYING b/gwenview/COPYING new file mode 100644 index 00000000..8efe11aa --- /dev/null +++ b/gwenview/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/gwenview/COPYING.DOC b/gwenview/COPYING.DOC new file mode 100644 index 00000000..4a0fe1c8 --- /dev/null +++ b/gwenview/COPYING.DOC @@ -0,0 +1,397 @@ + GNU Free Documentation License + Version 1.2, November 2002 + + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/gwenview/Messages.sh b/gwenview/Messages.sh new file mode 100644 index 00000000..59456308 --- /dev/null +++ b/gwenview/Messages.sh @@ -0,0 +1,3 @@ +#! /bin/sh +$EXTRACTRC `find . -name "*.ui" -o -name "*.rc" -o -name "*.kcfg" ` >> rc.cpp +$XGETTEXT `find . -name "*.cpp" -o -name "*.h" | grep -v '/tests/'` -o $podir/gwenview.pot diff --git a/gwenview/NEWS b/gwenview/NEWS new file mode 100644 index 00000000..e8ab0b40 --- /dev/null +++ b/gwenview/NEWS @@ -0,0 +1,133 @@ +# 2.6.0 +## Improvements +- New shortcuts to go to first and last image (bug #174619), original patch by + Ivan Wagner. +- Image resize dialog now allows to set width and height (bug #225506) +- A new "Share" button has been added to the toolbar to make it easier to + export images to various systems. + +# 2.5.4 +## Fixes +- Make sure operations done after "save as" are applied to the new image + (bug #236577). + +# 2.5.2 +## Fixes +- Apply black-list extension filter to documents only (bug #249878). + +# 2.5.0 +## Improvements +- Gwenview no longer blocks the UI when modifying or saving images. +- Wrap around when panning images (bug #225811), original patch by + Ismael Barros. +- It is now possible to show image size and file size below thumbnails, + original patch by Aaron Seigo. +- Gwenview can now be started with multiple images. +- Added an option to disable video support (bug #135585). + +# 2.4.2 +## Improvements +- Improved browsing picture folders by caching EXIF thumbnails to disk. + +## Fixes +- Fixed lossless rotation when built with libjpeg-8 (bug #227313). + +# 2.4.1 +## Fixes +- Fixed behavior of image view scroll bar in RTL mode (bug #210058). +- Fixed wrong position of "save as" bubble. +- Fixed "File > Open" action (bug #227934). +- Prevent word-wrap in thumbnail tooltip. + +# 2.4.0 +## New features +- Added an importer tool to make it easy to get images from memory cards. +- Added renaming support (bug #190216). +- Added support for session restoring (bug #199669). +- Added support for clipboard operations (Cut/Copy/Paste) (bug #191084). + +## Improvements +- Reworked start page to give quick access to history and bookmarks. +- Show thumbnails over folder icons in thumbnail view and start page. +- Nicer thumbnail tooltips. +- Added support for drag'n'drop on location bar (bug #153381). +- Added buttons to the thumbnail view zoom slider (bug #196110). +- Hide empty plugin menu entries (bug #168915). +- Show thumbnails for dragged documents. +- "Save As" behavior has been improved: it now takes you to the new document, + with a link to go back to the original one. +- Do not show the save bar above the side bar (bug #209683). +- Improved speed of thumbnail generation when entering a folder for the first time. + +## Fixes +- Reloading a truncated document works now (bug #207056). +- Do not show semantic info in sidebar if Nepomuk is not enabled (bug #196019). +- Really quit when all images have been saved (bug #199531). +- Reloading a truncated document works now (bug #207056). +- Prevent the sidebar from getting larger when image information is too wide. +- Fix broken width of save bar. + +# 2.3.5 +## Fixes +- Make crop handles fully opaque, as they do not show for some reason + otherwise. + +# 2.3.2 +## Fixes +- Ensure crop widget is always visible (bug #201098). +- Ensure operations always apply to the current url (bug #185769). +- Make sure setting the crop ratio does not resize the crop rect to 0x0 + (bug #199946). +- Correctly enable or disable next and previous buttons when starting Gwenview + with an image as parameter (bug #205468). +- Do not crash if started with a comma as parameter (bug #197563). + +# 2.3.1 +## Fixes +- Ensure "Advanced Slideshow" plugin is always enabled. +- When trying to quit Gwenview with unsaved changes, really quit when all + images have been saved. + +# 2.3.0 +## New features +- Gwenview can now play videos (bug #174522). +- The sidebar has been reworked and now features a folder view. + +## Improvements +- New crop interface: less intrusive and with the ability to crop to screen + ratio. +- The thumbnail bar can now be shown vertically and show multiple rows/columns. +- Gwenview now remembers visited urls. +- History can now be disabled. +- Added file operations and "open with" menu to image view context menu. Patch + by Marian Kyral (bug #187819). +- Show XMP image information. +- Show compressed SVG files. +- Do not crash if image info is not available (bug #200494). + +## Fixes +- Restart slideshow timer when user changes current image (bug #186411). +- Scroll when dragging files to the top or bottom of the thumbnail view (bug + #189510). +- Correctly refresh view when reloading an SVG document (bug #189525). +- Fix crash when saving multiple modified images with "Save All" (bug #189992). +- Do not crash when clicking a rotate button from the thumbnail view speedbar. +- Disable screensaver in fullscreen mode (bug #185916). +- Forget modified documents if they are deleted or trashed. +- Fix date sorting. +- Make PageUp and PageDown scroll, not browse (bug #187774). + +# 2.2.3 +## Fixes +- Handle gif files containing one frame and a graphic control extension as + non-animated gif (bug #185523). + +# 2.2.2 +## Fixes +- Fix display of JPEG2000 images. +- Fix memory leak when rotating images. +- Fix crash when switching to parent directory when in View mode (bug #186187). +- Fix crop rect (bug #184876). + +## Improvements +- Give back memory as soon as modified images have been saved. diff --git a/gwenview/app/CMakeLists.txt b/gwenview/app/CMakeLists.txt new file mode 100644 index 00000000..7659ea6d --- /dev/null +++ b/gwenview/app/CMakeLists.txt @@ -0,0 +1,107 @@ +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${EXIV2_INCLUDE_DIR} + ${LIBKONQ_INCLUDE_DIR} + ${KACTIVITIES_INCLUDE_DIRS} + ) + +if (KIPI_FOUND) + include_directories(${KIPI_INCLUDE_DIR}) +endif() + +# For lib/gwenviewconfig.h and config-gwenview.h +include_directories( + ${CMAKE_CURRENT_BINARY_DIR}/.. + ) + +set(gwenview_SRCS + abstractcontextmanageritem.cpp + configdialog.cpp + gvcore.cpp + documentinfoprovider.cpp + viewmainpage.cpp + fileoperations.cpp + filtercontroller.cpp + folderviewcontextmanageritem.cpp + fullscreencontent.cpp + infocontextmanageritem.cpp + imagemetainfodialog.cpp + imageopscontextmanageritem.cpp + fileopscontextmanageritem.cpp + main.cpp + mainwindow.cpp + preloader.cpp + saveallhelper.cpp + savebar.cpp + sidebar.cpp + startmainpage.cpp + thumbnailviewhelper.cpp + browsemainpage.cpp + ) + +if (NOT GWENVIEW_SEMANTICINFO_BACKEND_NONE) + set (gwenview_SRCS + ${gwenview_SRCS} + semanticinfocontextmanageritem.cpp + ) +endif() + +if (KIPI_FOUND) + set (gwenview_SRCS + ${gwenview_SRCS} + kipiexportaction.cpp + kipiimagecollectionselector.cpp + kipiinterface.cpp + kipiuploadwidget.cpp + ) +endif() + +kde4_add_ui_files(gwenview_SRCS + advancedconfigpage.ui + fullscreenconfigwidget.ui + generalconfigpage.ui + imageviewconfigpage.ui + semanticinfodialog.ui + startmainpage.ui + browsemainpage.ui + ) + +if (NOT GWENVIEW_SEMANTICINFO_BACKEND_NONE) + kde4_add_ui_files(gwenview_SRCS + semanticinfosidebaritem.ui + ) +endif() + +kde4_add_app_icon(gwenview_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/../icons/hi*-apps-gwenview.png") + +kde4_add_executable(gwenview ${gwenview_SRCS}) + +target_link_libraries(gwenview + ${KDE4_KFILE_LIBS} + gwenviewlib + ${KDE4_KIO_LIBS} + ${LIBKONQ_LIBRARY} + ${QT_QTCORE_LIBRARY} + ${KACTIVITIES_LIBRARY} + ) + +target_link_libraries(gwenview LINK_INTERFACE_LIBRARIES ${KDE4_KIO_LIBS} ${QT_QTCORE_LIBRARY}) + +if (KIPI_FOUND) + target_link_libraries(gwenview ${KIPI_LIBRARIES}) +endif() + +if (NOT GWENVIEW_SEMANTICINFO_BACKEND_NONE) + target_link_libraries(gwenview ${BALOO_LIBRARIES}) +endif() + +install(TARGETS gwenview + ${INSTALL_TARGETS_DEFAULT_ARGS}) + +install(FILES gwenviewui.rc + DESTINATION ${DATA_INSTALL_DIR}/gwenview) + +install(PROGRAMS gwenview.desktop + DESTINATION ${XDG_APPS_INSTALL_DIR}) +install(FILES slideshow.desktop + DESTINATION ${SERVICES_INSTALL_DIR}/ServiceMenus) diff --git a/gwenview/app/abstractcontextmanageritem.cpp b/gwenview/app/abstractcontextmanageritem.cpp new file mode 100644 index 00000000..a0328ec5 --- /dev/null +++ b/gwenview/app/abstractcontextmanageritem.cpp @@ -0,0 +1,62 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "abstractcontextmanageritem.moc" + +// Local +#include + +namespace Gwenview +{ + +struct AbstractContextManagerItemPrivate +{ + ContextManager* mContextManager; + QWidget* mWidget; +}; + +AbstractContextManagerItem::AbstractContextManagerItem(ContextManager* manager) +: QObject(manager) +, d(new AbstractContextManagerItemPrivate) +{ + d->mContextManager = manager; + d->mWidget = 0; +} + +AbstractContextManagerItem::~AbstractContextManagerItem() +{ + delete d; +} + +ContextManager* AbstractContextManagerItem::contextManager() const +{ + return d->mContextManager; +} + +QWidget* AbstractContextManagerItem::widget() const +{ + return d->mWidget; +} + +void AbstractContextManagerItem::setWidget(QWidget* widget) +{ + d->mWidget = widget; +} + +} // namespace diff --git a/gwenview/app/abstractcontextmanageritem.h b/gwenview/app/abstractcontextmanageritem.h new file mode 100644 index 00000000..cb3793d2 --- /dev/null +++ b/gwenview/app/abstractcontextmanageritem.h @@ -0,0 +1,53 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#ifndef ABSTRACTCONTEXTMANAGERITEM_H +#define ABSTRACTCONTEXTMANAGERITEM_H + +// Qt +#include + +namespace Gwenview +{ + +class ContextManager; + +struct AbstractContextManagerItemPrivate; +class AbstractContextManagerItem : public QObject +{ + Q_OBJECT +public: + AbstractContextManagerItem(ContextManager*); + virtual ~AbstractContextManagerItem(); + + QWidget* widget() const; + + ContextManager* contextManager() const; + +protected: + void setWidget(QWidget* widget); + +private: + AbstractContextManagerItemPrivate * const d; +}; + +} // namespace + +#endif /* ABSTRACTCONTEXTMANAGERITEM_H */ diff --git a/gwenview/app/advancedconfigpage.ui b/gwenview/app/advancedconfigpage.ui new file mode 100644 index 00000000..dca1f4ba --- /dev/null +++ b/gwenview/app/advancedconfigpage.ui @@ -0,0 +1,125 @@ + + + AdvancedConfigPage + + + + 0 + 0 + 482 + 300 + + + + + + + Cache: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Delete thumbnail cache folder on exit + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Enable this option if you do not have a lot of disk space.<br/><br/><em>Be careful:</em> this will delete the folder named <filename>.thumbnails</filename> in your home folder, deleting all thumbnails previously generated by Gwenview and other applications. + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + History: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Remember folders and URLs + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 151 + + + + + + + + + diff --git a/gwenview/app/browsemainpage.cpp b/gwenview/app/browsemainpage.cpp new file mode 100644 index 00000000..9e8626f5 --- /dev/null +++ b/gwenview/app/browsemainpage.cpp @@ -0,0 +1,403 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "browsemainpage.moc" + +// Qt +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +inline Sorting::Enum sortingFromSortAction(const QAction* action) +{ + Q_ASSERT(action); + return Sorting::Enum(action->data().toInt()); +} + +struct BrowseMainPagePrivate : public Ui_BrowseMainPage +{ + BrowseMainPage* q; + GvCore* mGvCore; + KFilePlacesModel* mFilePlacesModel; + KUrlNavigator* mUrlNavigator; + SortedDirModel* mDirModel; + int mDocumentCount; + KActionCollection* mActionCollection; + FilterController* mFilterController; + KSelectAction* mSortAction; + QActionGroup* mThumbnailDetailsActionGroup; + PreviewItemDelegate* mDelegate; + + void setupWidgets() + { + setupUi(q); + q->layout()->setMargin(0); + + // mThumbnailView + mThumbnailView->setModel(mDirModel); + + mDelegate = new PreviewItemDelegate(mThumbnailView); + mThumbnailView->setItemDelegate(mDelegate); + mThumbnailView->setSelectionMode(QAbstractItemView::ExtendedSelection); + + // mUrlNavigator (use stupid layouting code because KUrlNavigator ctor + // can't be used directly from Designer) + mFilePlacesModel = new KFilePlacesModel(q); + mUrlNavigator = new KUrlNavigator(mFilePlacesModel, KUrl(), mUrlNavigatorContainer); + mUrlNavigatorContainer->setAutoFillBackground(true); + QVBoxLayout* layout = new QVBoxLayout(mUrlNavigatorContainer); + layout->setMargin(0); + layout->addWidget(mUrlNavigator); + QObject::connect(mUrlNavigator, SIGNAL(urlsDropped(KUrl,QDropEvent*)), + q, SLOT(slotUrlsDropped(KUrl,QDropEvent*))); + updateUrlNavigatorBackgroundColor(); + + // Thumbnail slider + QObject::connect(mThumbnailSlider, SIGNAL(valueChanged(int)), + mThumbnailView, SLOT(setThumbnailWidth(int))); + QObject::connect(mThumbnailView, SIGNAL(thumbnailWidthChanged(int)), + mThumbnailSlider, SLOT(setValue(int))); + } + + void setupActions(KActionCollection* actionCollection) + { + KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection); + KAction* action = view->addAction("edit_location", q, SLOT(editLocation())); + action->setText(i18nc("@action:inmenu Navigation Bar", "Edit Location")); + action->setShortcut(Qt::Key_F6); + + mSortAction = view->add("sort_order"); + mSortAction->setText(i18nc("@action:inmenu", "Sort By")); + action = mSortAction->addAction(i18nc("@addAction:inmenu", "Name")); + action->setData(QVariant(Sorting::Name)); + action = mSortAction->addAction(i18nc("@addAction:inmenu", "Date")); + action->setData(QVariant(Sorting::Date)); + action = mSortAction->addAction(i18nc("@addAction:inmenu", "Size")); + action->setData(QVariant(Sorting::Size)); + QObject::connect(mSortAction, SIGNAL(triggered(QAction*)), + q, SLOT(updateSortOrder())); + + mThumbnailDetailsActionGroup = new QActionGroup(q); + mThumbnailDetailsActionGroup->setExclusive(false); + KActionMenu* thumbnailDetailsAction = view->add("thumbnail_details"); + thumbnailDetailsAction->setText(i18nc("@action:inmenu", "Thumbnail Details")); +#define addAction(text, detail) \ + action = new KAction(q); \ + thumbnailDetailsAction->addAction(action); \ + action->setText(text); \ + action->setCheckable(true); \ + action->setChecked(GwenviewConfig::thumbnailDetails() & detail); \ + action->setData(QVariant(detail)); \ + mThumbnailDetailsActionGroup->addAction(action); \ + QObject::connect(action, SIGNAL(triggered(bool)), \ + q, SLOT(updateThumbnailDetails())); + addAction(i18nc("@action:inmenu", "Filename"), PreviewItemDelegate::FileNameDetail); + addAction(i18nc("@action:inmenu", "Date"), PreviewItemDelegate::DateDetail); + addAction(i18nc("@action:inmenu", "Image Size"), PreviewItemDelegate::ImageSizeDetail); + addAction(i18nc("@action:inmenu", "File Size"), PreviewItemDelegate::FileSizeDetail); +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + addAction(i18nc("@action:inmenu", "Rating"), PreviewItemDelegate::RatingDetail); +#endif +#undef addAction + + KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection); + action = file->addAction("add_folder_to_places", q, SLOT(addFolderToPlaces())); + action->setText(i18nc("@action:inmenu", "Add Folder to Places")); + } + + void setupFilterController() + { + QMenu* menu = new QMenu(mAddFilterButton); + mFilterController = new FilterController(mFilterFrame, mDirModel); + Q_FOREACH(QAction * action, mFilterController->actionList()) { + menu->addAction(action); + } + mAddFilterButton->setMenu(menu); + } + + void setupFullScreenToolBar() + { + mFullScreenToolBar->setIconDimensions(KIconLoader::SizeMedium); + mFullScreenToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); + #define addAction(name) mFullScreenToolBar->addAction(mActionCollection->action(name)) + addAction("browse"); + addAction("view"); + #undef addAction + + mFullScreenToolBar2->setIconDimensions(KIconLoader::SizeMedium); + mFullScreenToolBar2->setToolButtonStyle(Qt::ToolButtonIconOnly); + mFullScreenToolBar2->addAction(mActionCollection->action("leave_fullscreen")); + } + + void updateDocumentCountLabel() + { + QString text = i18ncp("@label", "%1 document", "%1 documents", mDocumentCount); + mDocumentCountLabel->setText(text); + } + + void setupDocumentCountConnections() + { + QObject::connect(mDirModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + q, SLOT(slotDirModelRowsInserted(QModelIndex,int,int))); + + QObject::connect(mDirModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + q, SLOT(slotDirModelRowsAboutToBeRemoved(QModelIndex,int,int))); + + QObject::connect(mDirModel, SIGNAL(modelReset()), + q, SLOT(slotDirModelReset())); + } + + int documentCountInIndexRange(const QModelIndex& parent, int start, int end) + { + int count = 0; + for (int row = start; row <= end; ++row) { + QModelIndex index = mDirModel->index(row, 0, parent); + KFileItem item = mDirModel->itemForIndex(index); + if (!ArchiveUtils::fileItemIsDirOrArchive(item)) { + ++count; + } + } + return count; + } + + void updateUrlNavigatorBackgroundColor() + { + QPalette pal(q->palette()); + pal.setColor(QPalette::Window, pal.color(QPalette::Window).dark(110)); + mUrlNavigatorContainer->setPalette(pal); + } +}; + +BrowseMainPage::BrowseMainPage(QWidget* parent, KActionCollection* actionCollection, GvCore* gvCore) +: QWidget(parent) +, d(new BrowseMainPagePrivate) +{ + d->q = this; + d->mGvCore = gvCore; + d->mDirModel = gvCore->sortedDirModel(); + d->mDocumentCount = 0; + d->mActionCollection = actionCollection; + d->setupWidgets(); + d->setupActions(actionCollection); + d->setupFilterController(); + d->setupDocumentCountConnections(); + loadConfig(); + updateSortOrder(); + updateThumbnailDetails(); +} + +BrowseMainPage::~BrowseMainPage() +{ + delete d; +} + +void BrowseMainPage::loadConfig() +{ + setPalette(d->mGvCore->palette(GvCore::NormalPalette)); + d->mThumbnailView->setPalette(d->mGvCore->palette(GvCore::NormalViewPalette)); + d->mUrlNavigator->setUrlEditable(GwenviewConfig::urlNavigatorIsEditable()); + d->mUrlNavigator->setShowFullPath(GwenviewConfig::urlNavigatorShowFullPath()); + + d->mThumbnailSlider->setValue(GwenviewConfig::thumbnailSize()); + d->mThumbnailSlider->updateToolTip(); + // If GwenviewConfig::thumbnailSize() returns the current value of + // mThumbnailSlider, it won't emit valueChanged() and the thumbnail view + // won't be updated. That's why we do it ourself. + d->mThumbnailView->setThumbnailAspectRatio(GwenviewConfig::thumbnailAspectRatio()); + d->mThumbnailView->setThumbnailWidth(GwenviewConfig::thumbnailSize()); + + Q_FOREACH(QAction * action, d->mSortAction->actions()) { + if (sortingFromSortAction(action) == GwenviewConfig::sorting()) { + d->mSortAction->setCurrentAction(action); + break; + } + } +} + +void BrowseMainPage::saveConfig() const +{ + GwenviewConfig::setUrlNavigatorIsEditable(d->mUrlNavigator->isUrlEditable()); + GwenviewConfig::setUrlNavigatorShowFullPath(d->mUrlNavigator->showFullPath()); + GwenviewConfig::setThumbnailSize(d->mThumbnailSlider->value()); + GwenviewConfig::setSorting(sortingFromSortAction(d->mSortAction->currentAction())); + GwenviewConfig::setThumbnailDetails(d->mDelegate->thumbnailDetails()); +} + +ThumbnailView* BrowseMainPage::thumbnailView() const +{ + return d->mThumbnailView; +} + +KUrlNavigator* BrowseMainPage::urlNavigator() const +{ + return d->mUrlNavigator; +} + +void BrowseMainPage::reload() +{ + QModelIndexList list = d->mThumbnailView->selectionModel()->selectedIndexes(); + Q_FOREACH(const QModelIndex & index, list) { + d->mThumbnailView->reloadThumbnail(index); + } + d->mDirModel->reload(); +} + +void BrowseMainPage::editLocation() +{ + d->mUrlNavigator->setUrlEditable(true); + d->mUrlNavigator->setFocus(); +} + +void BrowseMainPage::addFolderToPlaces() +{ + KUrl url = d->mUrlNavigator->locationUrl(); + QString text = url.fileName(); + if (text.isEmpty()) { + text = url.pathOrUrl(); + } + d->mFilePlacesModel->addPlace(text, url); +} + +void BrowseMainPage::slotDirModelRowsInserted(const QModelIndex& parent, int start, int end) +{ + int count = d->documentCountInIndexRange(parent, start, end); + if (count > 0) { + d->mDocumentCount += count; + d->updateDocumentCountLabel(); + } +} + +void BrowseMainPage::slotDirModelRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) +{ + int count = d->documentCountInIndexRange(parent, start, end); + if (count > 0) { + d->mDocumentCount -= count; + d->updateDocumentCountLabel(); + } +} + +void BrowseMainPage::slotDirModelReset() +{ + d->mDocumentCount = 0; + d->updateDocumentCountLabel(); +} + +void BrowseMainPage::updateSortOrder() +{ + const QAction* action = d->mSortAction->currentAction(); + GV_RETURN_IF_FAIL(action); + + // This works because for now Sorting::Enum maps to KDirModel::ModelColumns + d->mDirModel->setSortRole(sortingFromSortAction(action)); +} + +void BrowseMainPage::updateThumbnailDetails() +{ + PreviewItemDelegate::ThumbnailDetails details = 0; + Q_FOREACH(const QAction * action, d->mThumbnailDetailsActionGroup->actions()) { + if (action->isChecked()) { + details |= PreviewItemDelegate::ThumbnailDetail(action->data().toInt()); + } + } + d->mDelegate->setThumbnailDetails(details); +} + +void BrowseMainPage::setFullScreenMode(bool fullScreen) +{ + setPalette(d->mGvCore->palette(fullScreen ? GvCore::FullScreenPalette : GvCore::NormalPalette)); + d->mThumbnailView->setPalette(d->mGvCore->palette(fullScreen ? GvCore::FullScreenViewPalette : GvCore::NormalViewPalette)); + d->updateUrlNavigatorBackgroundColor(); + d->mUrlNavigatorContainer->setContentsMargins( + fullScreen ? 6 : 0, + 0, 0, 0); + PreviewItemDelegate::ContextBarActions actions = PreviewItemDelegate::SelectionAction | PreviewItemDelegate::RotateAction; + if (!fullScreen) { + actions |= PreviewItemDelegate::FullScreenAction; + } + d->mDelegate->setContextBarActions(actions); + + d->mFullScreenToolBar->setVisible(fullScreen); + d->mFullScreenToolBar2->setVisible(fullScreen); + if (fullScreen && d->mFullScreenToolBar->actions().isEmpty()) { + d->setupFullScreenToolBar(); + } +} + +void BrowseMainPage::slotUrlsDropped(const KUrl& destUrl, QDropEvent* event) +{ + const KUrl::List urlList = KUrl::List::fromMimeData(event->mimeData()); + if (urlList.isEmpty()) { + return; + } + event->acceptProposedAction(); + + // We can't call FileOperations::showMenuForDroppedUrls() directly because + // we need the slot to return so that the drop event is accepted. Otherwise + // the drop cursor is still visible when the menu is shown. + QMetaObject::invokeMethod(this, "showMenuForDroppedUrls", Qt::QueuedConnection, Q_ARG(KUrl::List, urlList), Q_ARG(KUrl, destUrl)); +} + +void BrowseMainPage::showMenuForDroppedUrls(const KUrl::List& urlList, const KUrl& destUrl) +{ + FileOperations::showMenuForDroppedUrls(d->mUrlNavigator, urlList, destUrl); +} + +QToolButton* BrowseMainPage::toggleSideBarButton() const +{ + return d->mToggleSideBarButton; +} + +} // namespace diff --git a/gwenview/app/browsemainpage.h b/gwenview/app/browsemainpage.h new file mode 100644 index 00000000..0bd206a1 --- /dev/null +++ b/gwenview/app/browsemainpage.h @@ -0,0 +1,87 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef BROWSEMAINPAGE_H +#define BROWSEMAINPAGE_H + +// Qt +#include + +// KDE +#include + +// Local + +class QDropEvent; +class QModelIndex; +class QToolButton; + +class KActionCollection; +class KUrlNavigator; + +namespace Gwenview +{ + +class GvCore; +class ThumbnailView; + +struct BrowseMainPagePrivate; +/** + * This class contains all the necessary widgets displayed in browse mode: + * the thumbnail view, the url navigator, the bottom bar. + */ +class BrowseMainPage : public QWidget +{ + Q_OBJECT +public: + BrowseMainPage(QWidget* parent, KActionCollection*, GvCore*); + ~BrowseMainPage(); + + void reload(); + + ThumbnailView* thumbnailView() const; + KUrlNavigator* urlNavigator() const; + + void loadConfig(); + void saveConfig() const; + + void setFullScreenMode(bool); + + QToolButton* toggleSideBarButton() const; + +private Q_SLOTS: + void editLocation(); + void addFolderToPlaces(); + + void slotDirModelRowsInserted(const QModelIndex& parent, int start, int end); + void slotDirModelRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); + void slotDirModelReset(); + void updateSortOrder(); + void updateThumbnailDetails(); + void slotUrlsDropped(const KUrl& destUrl, QDropEvent*); + void showMenuForDroppedUrls(const KUrl::List&, const KUrl& destUrl); + +private: + BrowseMainPagePrivate* const d; +}; + +} // namespace + +#endif /* BROWSEMAINPAGE_H */ diff --git a/gwenview/app/browsemainpage.ui b/gwenview/app/browsemainpage.ui new file mode 100644 index 00000000..0810d862 --- /dev/null +++ b/gwenview/app/browsemainpage.ui @@ -0,0 +1,157 @@ + + + BrowseMainPage + + + + 0 + 0 + 264 + 267 + + + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + + 0 + + + 0 + + + + + + + + Add Filter + + + QToolButton::InstantPopup + + + + + + + + 1 + 0 + + + + + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + + + + + + + + + Gwenview::ThumbnailView + QListView +
lib/thumbnailview/thumbnailview.h
+
+ + Gwenview::StatusBarToolButton + QToolButton +
lib/statusbartoolbutton.h
+
+ + Gwenview::ThumbnailSlider + QWidget +
lib/thumbnailview/thumbnailslider.h
+
+ + KToolBar + QWidget +
KToolBar
+ 1 +
+
+ + +
diff --git a/gwenview/app/configdialog.cpp b/gwenview/app/configdialog.cpp new file mode 100644 index 00000000..a582695a --- /dev/null +++ b/gwenview/app/configdialog.cpp @@ -0,0 +1,132 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "configdialog.h" + +// Qt + +// KDE +#include + +// Local +#include "ui_generalconfigpage.h" +#include "ui_imageviewconfigpage.h" +#include "ui_advancedconfigpage.h" +#include +#include + +namespace Gwenview +{ + +struct ConfigDialogPrivate +{ + InvisibleButtonGroup* mAlphaBackgroundModeGroup; + InvisibleButtonGroup* mWheelBehaviorGroup; + InvisibleButtonGroup* mAnimationMethodGroup; + InvisibleButtonGroup* mThumbnailBarOrientationGroup; + Ui_GeneralConfigPage mGeneralConfigPage; + Ui_ImageViewConfigPage mImageViewConfigPage; + Ui_AdvancedConfigPage mAdvancedConfigPage; +}; + +template +QWidget* setupPage(Ui& ui) +{ + QWidget* widget = new QWidget; + ui.setupUi(widget); + widget->layout()->setMargin(0); + return widget; +} + +ConfigDialog::ConfigDialog(QWidget* parent) +: KConfigDialog(parent, "Settings", GwenviewConfig::self()) +, d(new ConfigDialogPrivate) +{ + setFaceType(KPageDialog::List); + setButtons(KDialog::Ok | KDialog::Cancel | KDialog::Apply | KDialog::Default); + showButtonSeparator(true); + + QWidget* widget; + KPageWidgetItem* pageItem; + + // General + widget = setupPage(d->mGeneralConfigPage); + pageItem = addPage(widget, i18n("General")); + pageItem->setIcon(KIcon("gwenview")); + connect(d->mGeneralConfigPage.kcfg_ViewBackgroundValue, SIGNAL(valueChanged(int)), SLOT(updateViewBackgroundFrame())); + + // Image View + widget = setupPage(d->mImageViewConfigPage); + + d->mAlphaBackgroundModeGroup = new InvisibleButtonGroup(widget); + d->mAlphaBackgroundModeGroup->setObjectName(QLatin1String("kcfg_AlphaBackgroundMode")); + d->mAlphaBackgroundModeGroup->addButton(d->mImageViewConfigPage.checkBoardRadioButton, int(RasterImageView::AlphaBackgroundCheckBoard)); + d->mAlphaBackgroundModeGroup->addButton(d->mImageViewConfigPage.solidColorRadioButton, int(RasterImageView::AlphaBackgroundSolid)); + + d->mWheelBehaviorGroup = new InvisibleButtonGroup(widget); + d->mWheelBehaviorGroup->setObjectName(QLatin1String("kcfg_MouseWheelBehavior")); + d->mWheelBehaviorGroup->addButton(d->mImageViewConfigPage.mouseWheelScrollRadioButton, int(MouseWheelBehavior::Scroll)); + d->mWheelBehaviorGroup->addButton(d->mImageViewConfigPage.mouseWheelBrowseRadioButton, int(MouseWheelBehavior::Browse)); + + d->mAnimationMethodGroup = new InvisibleButtonGroup(widget); + d->mAnimationMethodGroup->setObjectName(QLatin1String("kcfg_AnimationMethod")); + d->mAnimationMethodGroup->addButton(d->mImageViewConfigPage.glAnimationRadioButton, int(DocumentView::GLAnimation)); + d->mAnimationMethodGroup->addButton(d->mImageViewConfigPage.softwareAnimationRadioButton, int(DocumentView::SoftwareAnimation)); + d->mAnimationMethodGroup->addButton(d->mImageViewConfigPage.noAnimationRadioButton, int(DocumentView::NoAnimation)); + + d->mThumbnailBarOrientationGroup = new InvisibleButtonGroup(widget); + d->mThumbnailBarOrientationGroup->setObjectName(QLatin1String("kcfg_ThumbnailBarOrientation")); + d->mThumbnailBarOrientationGroup->addButton(d->mImageViewConfigPage.horizontalRadioButton, int(Qt::Horizontal)); + d->mThumbnailBarOrientationGroup->addButton(d->mImageViewConfigPage.verticalRadioButton, int(Qt::Vertical)); + + pageItem = addPage(widget, i18n("Image View")); + pageItem->setIcon(KIcon("view-preview")); + + // Advanced + widget = setupPage(d->mAdvancedConfigPage); + pageItem = addPage(widget, i18n("Advanced")); + pageItem->setIcon(KIcon("preferences-other")); + d->mAdvancedConfigPage.cacheHelpLabel->setFont(KGlobalSettings::smallestReadableFont()); + + updateViewBackgroundFrame(); +} + +ConfigDialog::~ConfigDialog() +{ + delete d; +} + +void ConfigDialog::updateViewBackgroundFrame() +{ + QColor color = QColor::fromHsv(0, 0, d->mGeneralConfigPage.kcfg_ViewBackgroundValue->value()); + QString css = + QString( + "background-color: %1;" + "border-radius: 5px;" + "border: 1px solid %1;") + .arg(color.name()); + // When using Oxygen, setting the background color via palette causes the + // pixels outside the frame to be painted with the new background color as + // well. Using CSS works more like expected. + d->mGeneralConfigPage.backgroundValueFrame->setStyleSheet(css); +} + +} // namespace diff --git a/gwenview/app/configdialog.h b/gwenview/app/configdialog.h new file mode 100644 index 00000000..21e9346d --- /dev/null +++ b/gwenview/app/configdialog.h @@ -0,0 +1,51 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef CONFIGDIALOG_H +#define CONFIGDIALOG_H + +// Qt + +// KDE +#include + +// Local + +namespace Gwenview +{ + +struct ConfigDialogPrivate; +class ConfigDialog : public KConfigDialog +{ + Q_OBJECT +public: + ConfigDialog(QWidget* parent); + ~ConfigDialog(); + +private Q_SLOTS: + void updateViewBackgroundFrame(); + +private: + ConfigDialogPrivate* const d; +}; + +} // namespace + +#endif /* CONFIGDIALOG_H */ diff --git a/gwenview/app/documentinfoprovider.cpp b/gwenview/app/documentinfoprovider.cpp new file mode 100644 index 00000000..d1ff76ba --- /dev/null +++ b/gwenview/app/documentinfoprovider.cpp @@ -0,0 +1,121 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "documentinfoprovider.moc" + +// Qt +#include + +// KDE + +// Local +#include +#include + +namespace Gwenview +{ + +struct DocumentInfoProviderPrivate +{ + SortedDirModel* mDirModel; +}; + +DocumentInfoProvider::DocumentInfoProvider(SortedDirModel* model) +: AbstractDocumentInfoProvider(model) +, d(new DocumentInfoProviderPrivate) +{ + d->mDirModel = model; + connect(DocumentFactory::instance(), SIGNAL(documentBusyStateChanged(KUrl,bool)), + SLOT(emitBusyStateChanged(KUrl,bool))); + + connect(DocumentFactory::instance(), SIGNAL(documentChanged(KUrl)), + SLOT(emitDocumentChanged(KUrl))); +} + +DocumentInfoProvider::~DocumentInfoProvider() +{ + delete d; +} + +void DocumentInfoProvider::thumbnailForDocument(const KUrl& url, ThumbnailGroup::Enum group, QPixmap* outPix, QSize* outFullSize) const +{ + Q_ASSERT(outPix); + Q_ASSERT(outFullSize); + *outPix = QPixmap(); + *outFullSize = QSize(); + const int pixelSize = ThumbnailGroup::pixelSize(group); + + Document::Ptr doc = DocumentFactory::instance()->getCachedDocument(url); + if (!doc) { + return; + } + + if (doc->loadingState() != Document::Loaded) { + return; + } + + QImage image = doc->image(); + if (image.width() > pixelSize || image.height() > pixelSize) { + image = image.scaled(pixelSize, pixelSize, Qt::KeepAspectRatio); + } + *outPix = QPixmap::fromImage(image); + *outFullSize = doc->size(); +} + +bool DocumentInfoProvider::isModified(const KUrl& url) +{ + Document::Ptr doc = DocumentFactory::instance()->getCachedDocument(url); + if (doc) { + return doc->loadingState() == Document::Loaded && doc->isModified(); + } else { + return false; + } +} + +bool DocumentInfoProvider::isBusy(const KUrl& url) +{ + Document::Ptr doc = DocumentFactory::instance()->getCachedDocument(url); + if (doc) { + return doc->isBusy(); + } else { + return false; + } +} + +void DocumentInfoProvider::emitBusyStateChanged(const KUrl& url, bool busy) +{ + QModelIndex index = d->mDirModel->indexForUrl(url); + if (!index.isValid()) { + return; + } + busyStateChanged(index, busy); +} + +void DocumentInfoProvider::emitDocumentChanged(const KUrl& url) +{ + QModelIndex index = d->mDirModel->indexForUrl(url); + if (!index.isValid()) { + return; + } + documentChanged(index); +} + +} // namespace diff --git a/gwenview/app/documentinfoprovider.h b/gwenview/app/documentinfoprovider.h new file mode 100644 index 00000000..caee6a44 --- /dev/null +++ b/gwenview/app/documentinfoprovider.h @@ -0,0 +1,60 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENTINFOPROVIDER_H +#define DOCUMENTINFOPROVIDER_H + +// Qt + +// KDE + +// Local +#include + +namespace Gwenview +{ + +class SortedDirModel; + +struct DocumentInfoProviderPrivate; +class DocumentInfoProvider : public AbstractDocumentInfoProvider +{ + Q_OBJECT +public: + DocumentInfoProvider(SortedDirModel* model); + ~DocumentInfoProvider(); + + virtual bool isBusy(const KUrl& url); + + virtual bool isModified(const KUrl& url); + + virtual void thumbnailForDocument(const KUrl& url, ThumbnailGroup::Enum group, QPixmap* outPix, QSize* outFullSize) const; + +private Q_SLOTS: + void emitBusyStateChanged(const KUrl&, bool); + void emitDocumentChanged(const KUrl&); + +private: + DocumentInfoProviderPrivate* const d; +}; + +} // namespace + +#endif /* DOCUMENTINFOPROVIDER_H */ diff --git a/gwenview/app/fileoperations.cpp b/gwenview/app/fileoperations.cpp new file mode 100644 index 00000000..694407db --- /dev/null +++ b/gwenview/app/fileoperations.cpp @@ -0,0 +1,215 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "fileoperations.h" + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include +#include + +namespace Gwenview +{ + +namespace FileOperations +{ + +static void copyMoveOrLink(KonqOperations::Operation operation, const KUrl::List& urlList, QWidget* parent) +{ + Q_ASSERT(urlList.count() > 0); + + KFileDialog dialog( + KUrl("kfiledialog:///"), + QString() /* filter */, + parent); + dialog.setOperationMode(KFileDialog::Saving); + switch (operation) { + case KonqOperations::COPY: + dialog.setCaption(i18nc("@title:window", "Copy To")); + dialog.okButton()->setText(i18nc("@action:button", "Copy")); + break; + case KonqOperations::MOVE: + dialog.setCaption(i18nc("@title:window", "Move To")); + dialog.okButton()->setText(i18nc("@action:button", "Move")); + break; + case KonqOperations::LINK: + dialog.setCaption(i18nc("@title:window", "Link To")); + dialog.okButton()->setText(i18nc("@action:button", "Link")); + break; + default: + Q_ASSERT(0); + } + if (urlList.count() == 1) { + dialog.setMode(KFile::File); + dialog.setSelection(urlList[0].fileName()); + } else { + dialog.setMode(KFile::ExistingOnly | KFile::Directory); + } + if (!dialog.exec()) { + return; + } + + KUrl destUrl = dialog.selectedUrl(); + KonqOperations::copy(parent, operation, urlList, destUrl); +} + +static void delOrTrash(KonqOperations::Operation operation, const KUrl::List& urlList, QWidget* parent) +{ + Q_ASSERT(urlList.count() > 0); + + if (!KonqOperations::askDeleteConfirmation(urlList, operation, KonqOperations::DEFAULT_CONFIRMATION, parent)) { + return; + } + + KIO::Job* job = 0; + // KonqOperations::delOrTrash() handles the confirmation and does not provide + // a way to know if the deletion has been accepted. + // We need to know about the confirmation so that DocumentFactory can forget + // about the deleted urls. That's why we can't use KonqOperations::delOrTrash() + switch (operation) { + case KonqOperations::TRASH: + job = KIO::trash(urlList); + break; + + case KonqOperations::DEL: + job = KIO::del(urlList); + break; + + default: + kWarning() << "Unknown operation" << operation; + return; + } + Q_ASSERT(job); + job->ui()->setWindow(parent); + + Q_FOREACH(const KUrl & url, urlList) { + DocumentFactory::instance()->forget(url); + } +} + +void copyTo(const KUrl::List& urlList, QWidget* parent) +{ + copyMoveOrLink(KonqOperations::COPY, urlList, parent); +} + +void moveTo(const KUrl::List& urlList, QWidget* parent) +{ + copyMoveOrLink(KonqOperations::MOVE, urlList, parent); +} + +void linkTo(const KUrl::List& urlList, QWidget* parent) +{ + copyMoveOrLink(KonqOperations::LINK, urlList, parent); +} + +void trash(const KUrl::List& urlList, QWidget* parent) +{ + delOrTrash(KonqOperations::TRASH, urlList, parent); +} + +void del(const KUrl::List& urlList, QWidget* parent) +{ + delOrTrash(KonqOperations::DEL, urlList, parent); +} + +void showMenuForDroppedUrls(QWidget* parent, const KUrl::List& urlList, const KUrl& destUrl) +{ + if (urlList.isEmpty()) { + kWarning() << "urlList is empty!"; + return; + } + + if (!destUrl.isValid()) { + kWarning() << "destUrl is not valid!"; + return; + } + + KMenu menu(parent); + QAction* moveAction = menu.addAction( + KIcon("go-jump"), + i18n("Move Here")); + QAction* copyAction = menu.addAction( + KIcon("edit-copy"), + i18n("Copy Here")); + QAction* linkAction = menu.addAction( + KIcon("edit-link"), + i18n("Link Here")); + menu.addSeparator(); + menu.addAction( + KIcon("process-stop"), + i18n("Cancel")); + + QAction* action = menu.exec(QCursor::pos()); + + KIO::Job* job = 0; + if (action == moveAction) { + job = KIO::move(urlList, destUrl); + } else if (action == copyAction) { + job = KIO::copy(urlList, destUrl); + } else if (action == linkAction) { + job = KIO::link(urlList, destUrl); + } else { + return; + } + Q_ASSERT(job); + job->ui()->setWindow(parent); +} + +void rename(const KUrl& oldUrl, QWidget* parent) +{ + QString name = KInputDialog::getText( + i18nc("@title:window", "Rename") /* caption */, + i18n("Rename %1 to:", oldUrl.fileName()) /* label */, + oldUrl.fileName() /* value */, + 0 /* ok */, + parent + ); + if (name.isEmpty() || name == oldUrl.fileName()) { + return; + } + + KUrl newUrl = oldUrl; + newUrl.setFileName(name); + KIO::SimpleJob* job = KIO::rename(oldUrl, newUrl, KIO::HideProgressInfo); + if (!KIO::NetAccess::synchronousRun(job, parent)) { + job->ui()->showErrorMessage(); + return; + } + ThumbnailProvider::moveThumbnail(oldUrl, newUrl); +} + +} // namespace + +} // namespace diff --git a/gwenview/app/fileoperations.h b/gwenview/app/fileoperations.h new file mode 100644 index 00000000..2fd418eb --- /dev/null +++ b/gwenview/app/fileoperations.h @@ -0,0 +1,46 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef FILEOPERATIONS_H +#define FILEOPERATIONS_H + +#include + +class QWidget; + +namespace Gwenview +{ + +namespace FileOperations +{ + +void copyTo(const KUrl::List& urlList, QWidget* parent); +void moveTo(const KUrl::List& urlList, QWidget* parent); +void linkTo(const KUrl::List& urlList, QWidget* parent); +void trash(const KUrl::List& urlList, QWidget* parent); +void del(const KUrl::List& urlList, QWidget* parent); +void rename(const KUrl& url, QWidget* parent); + +void showMenuForDroppedUrls(QWidget* parent, const KUrl::List& urlList, const KUrl& destUrl); + +} // namespace + +} // namespace +#endif /* FILEOPERATIONS_H */ diff --git a/gwenview/app/fileopscontextmanageritem.cpp b/gwenview/app/fileopscontextmanageritem.cpp new file mode 100644 index 00000000..e08541a4 --- /dev/null +++ b/gwenview/app/fileopscontextmanageritem.cpp @@ -0,0 +1,445 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "fileopscontextmanageritem.h" + +// Qt +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// libkonq +#include +#include + +// Local +#include +#include +#include +#include "fileoperations.h" +#include "sidebar.h" + +namespace Gwenview +{ + +struct FileOpsContextManagerItemPrivate +{ + FileOpsContextManagerItem* q; + QListView* mThumbnailView; + KXMLGUIClient* mXMLGUIClient; + SideBarGroup* mGroup; + KAction* mCutAction; + KAction* mCopyAction; + KAction* mPasteAction; + KAction* mCopyToAction; + KAction* mMoveToAction; + KAction* mLinkToAction; + KAction* mRenameAction; + KAction* mTrashAction; + KAction* mDelAction; + KAction* mRestoreAction; + KAction* mShowPropertiesAction; + KAction* mCreateFolderAction; + KAction* mOpenWithAction; + QList mRegularFileActionList; + QList mTrashFileActionList; + KService::List mServiceList; + bool mInTrash; + + KUrl::List urlList() const + { + KUrl::List urlList; + + KFileItemList list = q->contextManager()->selectedFileItemList(); + if (list.count() > 0) { + urlList = list.urlList(); + } else { + KUrl url = q->contextManager()->currentUrl(); + Q_ASSERT(url.isValid()); + urlList << url; + } + return urlList; + } + + void updateServiceList() + { + // This code is inspired from + // kdebase/apps/lib/konq/konq_menuactions.cpp + + // Get list of all distinct mimetypes in selection + QStringList mimeTypes; + Q_FOREACH(const KFileItem & item, q->contextManager()->selectedFileItemList()) { + const QString mimeType = item.mimetype(); + if (!mimeTypes.contains(mimeType)) { + mimeTypes << mimeType; + } + } + + // Query trader + mServiceList = KFileItemActions::associatedApplications(mimeTypes, QString()); + } + + QMimeData* selectionMimeData() + { + QMimeData* mimeData = new QMimeData; + KFileItemList list = q->contextManager()->selectedFileItemList(); + list.urlList().populateMimeData(mimeData); + return mimeData; + } + + KUrl pasteTargetUrl() const + { + // If only one folder is selected, paste inside it, otherwise paste in + // current + KFileItemList list = q->contextManager()->selectedFileItemList(); + if (list.count() == 1 && list.first().isDir()) { + return list.first().url(); + } else { + return q->contextManager()->currentDirUrl(); + } + } +}; + +static QAction* createSeparator(QObject* parent) +{ + QAction* action = new KAction(parent); + action->setSeparator(true); + return action; +} + +FileOpsContextManagerItem::FileOpsContextManagerItem(ContextManager* manager, QListView* thumbnailView, KActionCollection* actionCollection, KXMLGUIClient* client) +: AbstractContextManagerItem(manager) +, d(new FileOpsContextManagerItemPrivate) +{ + d->q = this; + d->mThumbnailView = thumbnailView; + d->mXMLGUIClient = client; + d->mGroup = new SideBarGroup(i18n("File Operations")); + setWidget(d->mGroup); + EventWatcher::install(d->mGroup, QEvent::Show, this, SLOT(updateSideBarContent())); + + d->mInTrash = false; + + connect(contextManager(), SIGNAL(selectionChanged()), + SLOT(updateActions())); + connect(contextManager(), SIGNAL(currentDirUrlChanged(KUrl)), + SLOT(updateActions())); + + KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection); + KActionCategory* edit = new KActionCategory(i18nc("@title actions category", "Edit"), actionCollection); + + d->mCutAction = edit->addAction(KStandardAction::Cut, this, SLOT(cut())); + + // Copied from Dolphin: + // need to remove shift+del from cut action, else the shortcut for deletejob + // doesn't work + KShortcut cutShortcut = d->mCutAction->shortcut(); + cutShortcut.remove(Qt::SHIFT + Qt::Key_Delete, KShortcut::KeepEmpty); + d->mCutAction->setShortcut(cutShortcut); + + d->mCopyAction = edit->addAction(KStandardAction::Copy, this, SLOT(copy())); + d->mPasteAction = edit->addAction(KStandardAction::Paste, this, SLOT(paste())); + + d->mCopyToAction = file->addAction("file_copy_to", this, SLOT(copyTo())); + d->mCopyToAction->setText(i18nc("Verb", "Copy To...")); + d->mCopyToAction->setShortcut(Qt::Key_F7); + + d->mMoveToAction = file->addAction("file_move_to", this, SLOT(moveTo())); + d->mMoveToAction->setText(i18nc("Verb", "Move To...")); + d->mMoveToAction->setShortcut(Qt::Key_F8); + + d->mLinkToAction = file->addAction("file_link_to", this, SLOT(linkTo())); + d->mLinkToAction->setText(i18nc("Verb: create link to the file where user wants", "Link To...")); + d->mLinkToAction->setShortcut(Qt::Key_F9); + + d->mRenameAction = file->addAction("file_rename", this, SLOT(rename())); + d->mRenameAction->setText(i18nc("Verb", "Rename...")); + d->mRenameAction->setShortcut(Qt::Key_F2); + + d->mTrashAction = file->addAction("file_trash", this, SLOT(trash())); + d->mTrashAction->setText(i18nc("Verb", "Trash")); + d->mTrashAction->setIcon(KIcon("user-trash")); + d->mTrashAction->setShortcut(Qt::Key_Delete); + + d->mDelAction = file->addAction("file_delete", this, SLOT(del())); + d->mDelAction->setText(i18n("Delete")); + d->mDelAction->setIcon(KIcon("edit-delete")); + d->mDelAction->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_Delete)); + + d->mRestoreAction = file->addAction("file_restore", this, SLOT(restore())); + d->mRestoreAction->setText(i18n("Restore")); + + d->mShowPropertiesAction = file->addAction("file_show_properties", this, SLOT(showProperties())); + d->mShowPropertiesAction->setText(i18n("Properties")); + d->mShowPropertiesAction->setIcon(KIcon("document-properties")); + + d->mCreateFolderAction = file->addAction("file_create_folder", this, SLOT(createFolder())); + d->mCreateFolderAction->setText(i18n("Create Folder...")); + d->mCreateFolderAction->setIcon(KIcon("folder-new")); + + d->mOpenWithAction = file->addAction("file_open_with"); + d->mOpenWithAction->setText(i18n("Open With")); + QMenu* menu = new QMenu; + d->mOpenWithAction->setMenu(menu); + connect(menu, SIGNAL(aboutToShow()), + SLOT(populateOpenMenu())); + connect(menu, SIGNAL(triggered(QAction*)), + SLOT(openWith(QAction*))); + + d->mRegularFileActionList + << d->mRenameAction + << d->mTrashAction + << d->mDelAction + << createSeparator(this) + << d->mCopyToAction + << d->mMoveToAction + << d->mLinkToAction + << createSeparator(this) + << d->mOpenWithAction + << d->mShowPropertiesAction + << createSeparator(this) + << d->mCreateFolderAction + ; + + d->mTrashFileActionList + << d->mRestoreAction + << d->mDelAction + << createSeparator(this) + << d->mShowPropertiesAction + ; + + connect(QApplication::clipboard(), SIGNAL(dataChanged()), + SLOT(updatePasteAction())); + updatePasteAction(); + + // Delay action update because it must happen *after* main window has called + // createGUI(), otherwise calling d->mXMLGUIClient->plugActionList() will + // fail. + QMetaObject::invokeMethod(this, "updateActions", Qt::QueuedConnection); +} + +FileOpsContextManagerItem::~FileOpsContextManagerItem() +{ + delete d->mOpenWithAction->menu(); + delete d; +} + +void FileOpsContextManagerItem::updateActions() +{ + const int count = contextManager()->selectedFileItemList().count(); + const bool selectionNotEmpty = count > 0; + const bool urlIsValid = contextManager()->currentUrl().isValid(); + const bool dirUrlIsValid = contextManager()->currentDirUrl().isValid(); + + d->mInTrash = contextManager()->currentDirUrl().protocol() == "trash"; + + d->mCutAction->setEnabled(selectionNotEmpty); + d->mCopyAction->setEnabled(selectionNotEmpty); + d->mCopyToAction->setEnabled(selectionNotEmpty); + d->mMoveToAction->setEnabled(selectionNotEmpty); + d->mLinkToAction->setEnabled(selectionNotEmpty); + d->mTrashAction->setEnabled(selectionNotEmpty); + d->mRestoreAction->setEnabled(selectionNotEmpty); + d->mDelAction->setEnabled(selectionNotEmpty); + d->mOpenWithAction->setEnabled(selectionNotEmpty); + d->mRenameAction->setEnabled(count == 1); + + d->mCreateFolderAction->setEnabled(dirUrlIsValid); + d->mShowPropertiesAction->setEnabled(dirUrlIsValid || urlIsValid); + + d->mXMLGUIClient->unplugActionList("file_action_list"); + QList& list = d->mInTrash ? d->mTrashFileActionList : d->mRegularFileActionList; + d->mXMLGUIClient->plugActionList("file_action_list", list); + + updateSideBarContent(); +} + +void FileOpsContextManagerItem::updatePasteAction() +{ + QPair info = KonqOperations::pasteInfo(d->pasteTargetUrl()); + d->mPasteAction->setEnabled(info.first); + d->mPasteAction->setText(info.second); +} + +void FileOpsContextManagerItem::updateSideBarContent() +{ + if (!d->mGroup->isVisible()) { + return; + } + + d->mGroup->clear(); + QList& list = d->mInTrash ? d->mTrashFileActionList : d->mRegularFileActionList; + Q_FOREACH(QAction * action, list) { + if (action->isEnabled() && !action->isSeparator()) { + d->mGroup->addAction(action); + } + } +} + +void FileOpsContextManagerItem::showProperties() +{ + KFileItemList list = contextManager()->selectedFileItemList(); + if (list.count() > 0) { + KPropertiesDialog::showDialog(list, d->mGroup); + } else { + KUrl url = contextManager()->currentDirUrl(); + KPropertiesDialog::showDialog(url, d->mGroup); + } +} + +void FileOpsContextManagerItem::cut() +{ + QMimeData* mimeData = d->selectionMimeData(); + KonqMimeData::addIsCutSelection(mimeData, true); + QApplication::clipboard()->setMimeData(mimeData); +} + +void FileOpsContextManagerItem::copy() +{ + QMimeData* mimeData = d->selectionMimeData(); + KonqMimeData::addIsCutSelection(mimeData, false); + QApplication::clipboard()->setMimeData(mimeData); +} + +void FileOpsContextManagerItem::paste() +{ + const bool move = KonqMimeData::decodeIsCutSelection(QApplication::clipboard()->mimeData()); + KIO::pasteClipboard(d->pasteTargetUrl(), d->mGroup, move); +} + +void FileOpsContextManagerItem::trash() +{ + FileOperations::trash(d->urlList(), d->mGroup); +} + +void FileOpsContextManagerItem::del() +{ + FileOperations::del(d->urlList(), d->mGroup); +} + +void FileOpsContextManagerItem::restore() +{ + KonqOperations::restoreTrashedItems(d->urlList(), d->mGroup); +} + +void FileOpsContextManagerItem::copyTo() +{ + FileOperations::copyTo(d->urlList(), d->mGroup); +} + +void FileOpsContextManagerItem::moveTo() +{ + FileOperations::moveTo(d->urlList(), d->mGroup); +} + +void FileOpsContextManagerItem::linkTo() +{ + FileOperations::linkTo(d->urlList(), d->mGroup); +} + +void FileOpsContextManagerItem::rename() +{ + if (d->mThumbnailView->isVisible()) { + QModelIndex index = d->mThumbnailView->currentIndex(); + d->mThumbnailView->edit(index); + } else { + FileOperations::rename(d->urlList().first(), d->mGroup); + } +} + +void FileOpsContextManagerItem::createFolder() +{ + KUrl url = contextManager()->currentDirUrl(); + KonqOperations::newDir(d->mGroup, url); +} + +void FileOpsContextManagerItem::populateOpenMenu() +{ + QMenu* openMenu = d->mOpenWithAction->menu(); + qDeleteAll(openMenu->actions()); + + d->updateServiceList(); + + int idx = 0; + Q_FOREACH(const KService::Ptr & service, d->mServiceList) { + QString text = service->name().replace('&', "&&"); + QAction* action = openMenu->addAction(text); + action->setIcon(KIcon(service->icon())); + action->setData(idx); + ++idx; + } + + openMenu->addSeparator(); + QAction* action = openMenu->addAction(i18n("Other Application...")); + action->setData(-1); +} + +void FileOpsContextManagerItem::openWith(QAction* action) +{ + Q_ASSERT(action); + KService::Ptr service; + KUrl::List list = d->urlList(); + + bool ok; + int idx = action->data().toInt(&ok); + GV_RETURN_IF_FAIL(ok); + if (idx == -1) { + // Other Application... + KOpenWithDialog dlg(list, d->mGroup); + if (!dlg.exec()) { + return; + } + service = dlg.service(); + + if (!service) { + // User entered a custom command + Q_ASSERT(!dlg.text().isEmpty()); + KRun::run(dlg.text(), list, d->mGroup); + return; + } + } else { + service = d->mServiceList.at(idx); + } + + Q_ASSERT(service); + KRun::run(*service, list, d->mGroup); +} + +} // namespace diff --git a/gwenview/app/fileopscontextmanageritem.h b/gwenview/app/fileopscontextmanageritem.h new file mode 100644 index 00000000..4070950d --- /dev/null +++ b/gwenview/app/fileopscontextmanageritem.h @@ -0,0 +1,74 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef FILEOPSCONTEXTMANAGERITEM_H +#define FILEOPSCONTEXTMANAGERITEM_H + +// Qt + +// KDE + +// Local +#include "abstractcontextmanageritem.h" + +class QAction; +class QListView; +class KAction; +class KActionCollection; +class KXMLGUIClient; + +namespace Gwenview +{ + +struct FileOpsContextManagerItemPrivate; +class FileOpsContextManagerItem : public AbstractContextManagerItem +{ + Q_OBJECT +public: + FileOpsContextManagerItem(Gwenview::ContextManager* manager, QListView* thumbnailView, KActionCollection* actionCollection, KXMLGUIClient* client); + ~FileOpsContextManagerItem(); + +private Q_SLOTS: + void updateActions(); + void updatePasteAction(); + void updateSideBarContent(); + + void cut(); + void copy(); + void paste(); + void rename(); + void copyTo(); + void moveTo(); + void linkTo(); + void trash(); + void del(); + void restore(); + void showProperties(); + void createFolder(); + void populateOpenMenu(); + void openWith(QAction* action); + +private: + FileOpsContextManagerItemPrivate* const d; +}; + +} // namespace + +#endif /* FILEOPSCONTEXTMANAGERITEM_H */ diff --git a/gwenview/app/filtercontroller.cpp b/gwenview/app/filtercontroller.cpp new file mode 100644 index 00000000..618de89b --- /dev/null +++ b/gwenview/app/filtercontroller.cpp @@ -0,0 +1,615 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "filtercontroller.moc" + +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include + +// Local +#include +#include +#include +#include +#include + +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE +// KDE +#include + +// Local +#include +#include +#endif + +namespace Gwenview +{ + +/** + * An AbstractSortedDirModelFilter which filters on the file names + */ +class NameFilter : public AbstractSortedDirModelFilter +{ +public: + enum Mode { + Contains, + DoesNotContain + }; + NameFilter(SortedDirModel* model) + : AbstractSortedDirModelFilter(model) + , mText() + , mMode(Contains) + {} + + virtual bool needsSemanticInfo() const + { + return false; + } + + virtual bool acceptsIndex(const QModelIndex& index) const + { + if (mText.isEmpty()) { + return true; + } + switch (mMode) { + case Contains: + return index.data().toString().contains(mText, Qt::CaseInsensitive); + default: /*DoesNotContain:*/ + return !index.data().toString().contains(mText, Qt::CaseInsensitive); + } + } + + void setText(const QString& text) + { + mText = text; + model()->applyFilters(); + } + + void setMode(Mode mode) + { + mMode = mode; + model()->applyFilters(); + } + +private: + QString mText; + Mode mMode; +}; + +struct NameFilterWidgetPrivate +{ + QPointer mFilter; + KComboBox* mModeComboBox; + KLineEdit* mLineEdit; +}; + +NameFilterWidget::NameFilterWidget(SortedDirModel* model) +: d(new NameFilterWidgetPrivate) +{ + d->mFilter = new NameFilter(model); + + d->mModeComboBox = new KComboBox; + d->mModeComboBox->addItem(i18n("Name contains"), QVariant(NameFilter::Contains)); + d->mModeComboBox->addItem(i18n("Name does not contain"), QVariant(NameFilter::DoesNotContain)); + + d->mLineEdit = new KLineEdit; + + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(2); + layout->addWidget(d->mModeComboBox); + layout->addWidget(d->mLineEdit); + + QTimer* timer = new QTimer(this); + timer->setInterval(350); + timer->setSingleShot(true); + connect(timer, SIGNAL(timeout()), SLOT(applyNameFilter())); + + connect(d->mLineEdit, SIGNAL(textChanged(QString)), + timer, SLOT(start())); + + connect(d->mModeComboBox, SIGNAL(currentIndexChanged(int)), + SLOT(applyNameFilter())); + + QTimer::singleShot(0, d->mLineEdit, SLOT(setFocus())); +} + +NameFilterWidget::~NameFilterWidget() +{ + delete d->mFilter; + delete d; +} + +void NameFilterWidget::applyNameFilter() +{ + QVariant data = d->mModeComboBox->itemData(d->mModeComboBox->currentIndex()); + d->mFilter->setMode(NameFilter::Mode(data.toInt())); + d->mFilter->setText(d->mLineEdit->text()); +} + +/** + * An AbstractSortedDirModelFilter which filters on the file dates + */ +class DateFilter : public AbstractSortedDirModelFilter +{ +public: + enum Mode { + GreaterOrEqual, + Equal, + LessOrEqual + }; + DateFilter(SortedDirModel* model) + : AbstractSortedDirModelFilter(model) + , mMode(GreaterOrEqual) + {} + + virtual bool needsSemanticInfo() const + { + return false; + } + + virtual bool acceptsIndex(const QModelIndex& index) const + { + if (!mDate.isValid()) { + return true; + } + KFileItem fileItem = model()->itemForSourceIndex(index); + QDate date = TimeUtils::dateTimeForFileItem(fileItem).date(); + switch (mMode) { + case GreaterOrEqual: + return date >= mDate; + case Equal: + return date == mDate; + default: /* LessOrEqual */ + return date <= mDate; + } + } + + void setDate(const QDate& date) + { + mDate = date; + model()->applyFilters(); + } + + void setMode(Mode mode) + { + mMode = mode; + model()->applyFilters(); + } + +private: + QDate mDate; + Mode mMode; +}; + +struct DateFilterWidgetPrivate +{ + QPointer mFilter; + KComboBox* mModeComboBox; + DateWidget* mDateWidget; +}; + +DateFilterWidget::DateFilterWidget(SortedDirModel* model) +: d(new DateFilterWidgetPrivate) +{ + d->mFilter = new DateFilter(model); + + d->mModeComboBox = new KComboBox; + d->mModeComboBox->addItem(i18n("Date >="), DateFilter::GreaterOrEqual); + d->mModeComboBox->addItem(i18n("Date ="), DateFilter::Equal); + d->mModeComboBox->addItem(i18n("Date <="), DateFilter::LessOrEqual); + + d->mDateWidget = new DateWidget; + + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(0); + layout->addWidget(d->mModeComboBox); + layout->addWidget(d->mDateWidget); + + connect(d->mDateWidget, SIGNAL(dateChanged(QDate)), + SLOT(applyDateFilter())); + connect(d->mModeComboBox, SIGNAL(currentIndexChanged(int)), + SLOT(applyDateFilter())); + + applyDateFilter(); +} + +DateFilterWidget::~DateFilterWidget() +{ + delete d->mFilter; + delete d; +} + +void DateFilterWidget::applyDateFilter() +{ + QVariant data = d->mModeComboBox->itemData(d->mModeComboBox->currentIndex()); + d->mFilter->setMode(DateFilter::Mode(data.toInt())); + d->mFilter->setDate(d->mDateWidget->date()); +} + +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE +/** + * An AbstractSortedDirModelFilter which filters on file ratings + */ +class RatingFilter : public AbstractSortedDirModelFilter +{ +public: + enum Mode { + GreaterOrEqual, + Equal, + LessOrEqual + }; + + RatingFilter(SortedDirModel* model) + : AbstractSortedDirModelFilter(model) + , mRating(0) + , mMode(GreaterOrEqual) {} + + virtual bool needsSemanticInfo() const + { + return true; + } + + virtual bool acceptsIndex(const QModelIndex& index) const + { + SemanticInfo info = model()->semanticInfoForSourceIndex(index); + switch (mMode) { + case GreaterOrEqual: + return info.mRating >= mRating; + case Equal: + return info.mRating == mRating; + default: /* LessOrEqual */ + return info.mRating <= mRating; + } + } + + void setRating(int value) + { + mRating = value; + model()->applyFilters(); + } + + void setMode(Mode mode) + { + mMode = mode; + model()->applyFilters(); + } + +private: + int mRating; + Mode mMode; +}; + +struct RatingWidgetPrivate +{ + KComboBox* mModeComboBox; + KRatingWidget* mRatingWidget; + QPointer mFilter; +}; + +RatingFilterWidget::RatingFilterWidget(SortedDirModel* model) +: d(new RatingWidgetPrivate) +{ + d->mModeComboBox = new KComboBox; + d->mModeComboBox->addItem(i18n("Rating >="), RatingFilter::GreaterOrEqual); + d->mModeComboBox->addItem(i18n("Rating =") , RatingFilter::Equal); + d->mModeComboBox->addItem(i18n("Rating <="), RatingFilter::LessOrEqual); + + d->mRatingWidget = new KRatingWidget; + d->mRatingWidget->setHalfStepsEnabled(true); + d->mRatingWidget->setMaxRating(10); + + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(0); + layout->addWidget(d->mModeComboBox); + layout->addWidget(d->mRatingWidget); + + d->mFilter = new RatingFilter(model); + + QObject::connect(d->mModeComboBox, SIGNAL(currentIndexChanged(int)), + SLOT(updateFilterMode())); + + QObject::connect(d->mRatingWidget, SIGNAL(ratingChanged(int)), + SLOT(slotRatingChanged(int))); + + updateFilterMode(); +} + +RatingFilterWidget::~RatingFilterWidget() +{ + delete d->mFilter; + delete d; +} + +void RatingFilterWidget::slotRatingChanged(int value) +{ + d->mFilter->setRating(value); +} + +void RatingFilterWidget::updateFilterMode() +{ + QVariant data = d->mModeComboBox->itemData(d->mModeComboBox->currentIndex()); + d->mFilter->setMode(RatingFilter::Mode(data.toInt())); +} + +/** + * An AbstractSortedDirModelFilter which filters on associated tags + */ +class TagFilter : public AbstractSortedDirModelFilter +{ +public: + TagFilter(SortedDirModel* model) + : AbstractSortedDirModelFilter(model) + , mWantMatchingTag(true) + {} + + virtual bool needsSemanticInfo() const + { + return true; + } + + virtual bool acceptsIndex(const QModelIndex& index) const + { + if (mTag.isEmpty()) { + return true; + } + SemanticInfo info = model()->semanticInfoForSourceIndex(index); + if (mWantMatchingTag) { + return info.mTags.contains(mTag); + } else { + return !info.mTags.contains(mTag); + } + } + + void setTag(const SemanticInfoTag& tag) + { + mTag = tag; + model()->applyFilters(); + } + + void setWantMatchingTag(bool value) + { + mWantMatchingTag = value; + model()->applyFilters(); + } + +private: + SemanticInfoTag mTag; + bool mWantMatchingTag; +}; + +struct TagFilterWidgetPrivate +{ + KComboBox* mModeComboBox; + QComboBox* mTagComboBox; + QPointer mFilter; +}; + +TagFilterWidget::TagFilterWidget(SortedDirModel* model) +: d(new TagFilterWidgetPrivate) +{ + d->mFilter = new TagFilter(model); + + d->mModeComboBox = new KComboBox; + d->mModeComboBox->addItem(i18n("Tagged"), QVariant(true)); + d->mModeComboBox->addItem(i18n("Not Tagged"), QVariant(false)); + + d->mTagComboBox = new QComboBox; + + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(0); + layout->addWidget(d->mModeComboBox); + layout->addWidget(d->mTagComboBox); + + AbstractSemanticInfoBackEnd* backEnd = model->semanticInfoBackEnd(); + backEnd->refreshAllTags(); + TagModel* tagModel = TagModel::createAllTagsModel(this, backEnd); + + QCompleter* completer = new QCompleter(d->mTagComboBox); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setModel(tagModel); + d->mTagComboBox->setCompleter(completer); + d->mTagComboBox->setInsertPolicy(QComboBox::NoInsert); + d->mTagComboBox->setEditable(true); + d->mTagComboBox->setModel(tagModel); + d->mTagComboBox->setCurrentIndex(-1); + + connect(d->mTagComboBox, SIGNAL(currentIndexChanged(int)), SLOT(updateTagSetFilter())); + + connect(d->mModeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(updateTagSetFilter())); + + QTimer::singleShot(0, d->mTagComboBox, SLOT(setFocus())); +} + +TagFilterWidget::~TagFilterWidget() +{ + delete d->mFilter; + delete d; +} + +void TagFilterWidget::updateTagSetFilter() +{ + QModelIndex index = d->mTagComboBox->model()->index(d->mTagComboBox->currentIndex(), 0); + if (!index.isValid()) { + kWarning() << "Invalid index"; + return; + } + SemanticInfoTag tag = index.data(TagModel::TagRole).toString(); + d->mFilter->setTag(tag); + + bool wantMatchingTag = d->mModeComboBox->itemData(d->mModeComboBox->currentIndex()).toBool(); + d->mFilter->setWantMatchingTag(wantMatchingTag); +} +#endif + +/** + * A container for all filter widgets. It features a close button on the right. + */ +class FilterWidgetContainer : public QFrame +{ +public: + FilterWidgetContainer() + { + QPalette pal = palette(); + pal.setColor(QPalette::Window, pal.color(QPalette::Highlight)); + setPalette(pal); + } + + void setFilterWidget(QWidget* widget) + { + QToolButton* closeButton = new QToolButton; + closeButton->setIcon(KIcon("window-close")); + closeButton->setAutoRaise(true); + closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + int size = IconSize(KIconLoader::Small); + closeButton->setIconSize(QSize(size, size)); + connect(closeButton, SIGNAL(clicked()), SLOT(deleteLater())); + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(2); + layout->setSpacing(2); + layout->addWidget(widget); + layout->addWidget(closeButton); + } + +protected: + virtual void paintEvent(QPaintEvent*) + { + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + QPainterPath path = PaintUtils::roundedRectangle(QRectF(rect()).adjusted(0.5, 0.5, -0.5, -0.5), 6); + + QColor color = palette().color(QPalette::Highlight); + painter.fillPath(path, PaintUtils::alphaAdjustedF(color, 0.5)); + painter.setPen(color); + painter.drawPath(path); + } +}; + +struct FilterControllerPrivate +{ + FilterController* q; + QFrame* mFrame; + SortedDirModel* mDirModel; + QList mActionList; + + int mFilterWidgetCount; /**< How many filter widgets are in mFrame */ + + void addAction(const QString& text, const char* slot) + { + QAction* action = new QAction(text, q); + QObject::connect(action, SIGNAL(triggered()), q, slot); + mActionList << action; + } + + void addFilter(QWidget* widget) + { + if (mFrame->isHidden()) { + mFrame->show(); + } + FilterWidgetContainer* container = new FilterWidgetContainer; + container->setFilterWidget(widget); + mFrame->layout()->addWidget(container); + + mFilterWidgetCount++; + QObject::connect(container, SIGNAL(destroyed()), q, SLOT(slotFilterWidgetClosed())); + } +}; + +FilterController::FilterController(QFrame* frame, SortedDirModel* dirModel) +: QObject(frame) +, d(new FilterControllerPrivate) +{ + d->q = this; + d->mFrame = frame; + d->mDirModel = dirModel; + d->mFilterWidgetCount = 0; + + d->mFrame->hide(); + FlowLayout* layout = new FlowLayout(d->mFrame); + layout->setSpacing(2); + + d->addAction(i18nc("@action:inmenu", "Filter by Name"), SLOT(addFilterByName())); + d->addAction(i18nc("@action:inmenu", "Filter by Date"), SLOT(addFilterByDate())); +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + d->addAction(i18nc("@action:inmenu", "Filter by Rating"), SLOT(addFilterByRating())); + d->addAction(i18nc("@action:inmenu", "Filter by Tag"), SLOT(addFilterByTag())); +#endif +} + +FilterController::~FilterController() +{ + delete d; +} + +QList FilterController::actionList() const +{ + return d->mActionList; +} + +void FilterController::addFilterByName() +{ + d->addFilter(new NameFilterWidget(d->mDirModel)); +} + +void FilterController::addFilterByDate() +{ + d->addFilter(new DateFilterWidget(d->mDirModel)); +} + +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE +void FilterController::addFilterByRating() +{ + d->addFilter(new RatingFilterWidget(d->mDirModel)); +} + +void FilterController::addFilterByTag() +{ + d->addFilter(new TagFilterWidget(d->mDirModel)); +} +#endif + +void FilterController::slotFilterWidgetClosed() +{ + d->mFilterWidgetCount--; + if (d->mFilterWidgetCount == 0) { + d->mFrame->hide(); + } +} + +} // namespace diff --git a/gwenview/app/filtercontroller.h b/gwenview/app/filtercontroller.h new file mode 100644 index 00000000..2f6ef7bc --- /dev/null +++ b/gwenview/app/filtercontroller.h @@ -0,0 +1,135 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef FILTERCONTROLLER_H +#define FILTERCONTROLLER_H + +#include + +// Qt +#include +#include +#include + +// KDE + +// Local + +class QAction; +class QFrame; + +namespace Gwenview +{ + +class SortedDirModel; + +struct NameFilterWidgetPrivate; +class NameFilterWidget : public QWidget +{ + Q_OBJECT +public: + NameFilterWidget(SortedDirModel*); + ~NameFilterWidget(); + +private Q_SLOTS: + void applyNameFilter(); + +private: + NameFilterWidgetPrivate* const d; +}; + +struct DateFilterWidgetPrivate; +class DateFilterWidget : public QWidget +{ + Q_OBJECT +public: + DateFilterWidget(SortedDirModel*); + ~DateFilterWidget(); + +private Q_SLOTS: + void applyDateFilter(); + +private: + DateFilterWidgetPrivate* const d; +}; + +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE +struct RatingWidgetPrivate; +class RatingFilterWidget : public QWidget +{ + Q_OBJECT +public: + RatingFilterWidget(SortedDirModel*); + ~RatingFilterWidget(); + +private Q_SLOTS: + void slotRatingChanged(int value); + void updateFilterMode(); + +private: + RatingWidgetPrivate* const d; +}; + +struct TagFilterWidgetPrivate; +class TagFilterWidget : public QWidget +{ + Q_OBJECT +public: + TagFilterWidget(SortedDirModel*); + ~TagFilterWidget(); + +private Q_SLOTS: + void updateTagSetFilter(); + +private: + TagFilterWidgetPrivate* const d; +}; +#endif + +struct FilterControllerPrivate; +/** + * This class manages the filter widgets in the filter frame and assign the + * corresponding filters to the SortedDirModel + */ +class FilterController : public QObject +{ + Q_OBJECT +public: + FilterController(QFrame* filterFrame, SortedDirModel* model); + ~FilterController(); + + QList actionList() const; + +private Q_SLOTS: + void addFilterByName(); + void addFilterByDate(); +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + void addFilterByRating(); + void addFilterByTag(); +#endif + void slotFilterWidgetClosed(); + +private: + FilterControllerPrivate* const d; +}; + +} // namespace + +#endif /* FILTERCONTROLLER_H */ diff --git a/gwenview/app/folderviewcontextmanageritem.cpp b/gwenview/app/folderviewcontextmanageritem.cpp new file mode 100644 index 00000000..f306da10 --- /dev/null +++ b/gwenview/app/folderviewcontextmanageritem.cpp @@ -0,0 +1,302 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "folderviewcontextmanageritem.h" + +// Qt +#include +#include +#include + +// KDE +#include +#include + +// Local +#include +#include +#include "sidebar.h" +#include "fileoperations.h" + +#define USE_PLACETREE +#ifdef USE_PLACETREE +#include +#define MODEL_CLASS PlaceTreeModel +#else +#include +#include +#define MODEL_CLASS SortedDirModel +#endif + +namespace Gwenview +{ + +/** + * This treeview accepts url drops + */ +class UrlDropTreeView : public QTreeView +{ +public: + UrlDropTreeView(QWidget* parent = 0) + : QTreeView(parent) + {} + +protected: + void dragEnterEvent(QDragEnterEvent* event) + { + QAbstractItemView::dragEnterEvent(event); + setDirtyRegion(mDropRect); + if (event->mimeData()->hasUrls()) { + event->acceptProposedAction(); + } + } + + void dragMoveEvent(QDragMoveEvent* event) + { + QAbstractItemView::dragMoveEvent(event); + + QModelIndex index = indexAt(event->pos()); + + // This code has been copied from Dolphin + // (panels/folders/paneltreeview.cpp) + setDirtyRegion(mDropRect); + mDropRect = visualRect(index); + setDirtyRegion(mDropRect); + + if (index.isValid()) { + event->acceptProposedAction(); + } else { + event->ignore(); + } + } + + void dropEvent(QDropEvent* event) + { + const KUrl::List urlList = KUrl::List::fromMimeData(event->mimeData()); + const QModelIndex index = indexAt(event->pos()); + if (!index.isValid()) { + kWarning() << "Invalid index!"; + return; + } + const KUrl destUrl = static_cast(model())->urlForIndex(index); + FileOperations::showMenuForDroppedUrls(this, urlList, destUrl); + } + +private: + QRect mDropRect; +}; + +struct FolderViewContextManagerItemPrivate +{ + FolderViewContextManagerItem* q; + MODEL_CLASS* mModel; + QTreeView* mView; + + KUrl mUrlToSelect; + QPersistentModelIndex mExpandingIndex; + + void setupModel() + { + mModel = new MODEL_CLASS(q); + mView->setModel(mModel); +#ifndef USE_PLACETREE + for (int col = 1; col <= mModel->columnCount(); ++col) { + mView->header()->setSectionHidden(col, true); + } + mModel->dirLister()->openUrl(KUrl("/")); +#endif + QObject::connect(mModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + q, SLOT(slotRowsInserted(QModelIndex,int,int))); + } + + void setupView() + { + mView = new UrlDropTreeView; + mView->setEditTriggers(QAbstractItemView::NoEditTriggers); + mView->setAcceptDrops(true); + mView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + mView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + + // Necessary to get the drop target highlighted + mView->viewport()->setAttribute(Qt::WA_Hover); + + mView->setHeaderHidden(true); + + // This is tricky: QTreeView header has stretchLastSection set to true. + // In this configuration, the header gets quite wide and cause an + // horizontal scrollbar to appear. + // To avoid this, set stretchLastSection to false and resizeMode to + // Stretch (we still want the column to take the full width of the + // widget). + mView->header()->setStretchLastSection(false); + mView->header()->setResizeMode(QHeaderView::ResizeToContents); + + q->setWidget(mView); + QObject::connect(mView, SIGNAL(activated(QModelIndex)), + q, SLOT(slotActivated(QModelIndex))); + EventWatcher::install(mView, QEvent::Show, q, SLOT(expandToSelectedUrl())); + } + + QModelIndex findClosestIndex(const QModelIndex& parent, const KUrl& wantedUrl) + { + Q_ASSERT(mModel); + QModelIndex index = parent; + if (!index.isValid()) { + index = findRootIndex(wantedUrl); + if (!index.isValid()) { + return QModelIndex(); + } + } + + bool isParent; + KUrl url = mModel->urlForIndex(index); + QString relativePath = KUrl::relativePath(url.path(), wantedUrl.path(), &isParent); + if (!isParent) { + kWarning() << url << "is not a parent of" << wantedUrl << "!"; + return QModelIndex(); + } + + QModelIndex lastFoundIndex = index; + Q_FOREACH(const QString & pathPart, relativePath.mid(1).split('/', QString::SkipEmptyParts)) { + bool found = false; + for (int row = 0; row < mModel->rowCount(lastFoundIndex); ++row) { + QModelIndex index = mModel->index(row, 0, lastFoundIndex); + if (index.data().toString() == pathPart) { + // FIXME: Check encoding + found = true; + lastFoundIndex = index; + break; + } + } + if (!found) { + break; + } + } + return lastFoundIndex; + } + + QModelIndex findRootIndex(const KUrl& wantedUrl) + { + QModelIndex matchIndex; + int matchUrlLength = 0; + for (int row = 0; row < mModel->rowCount(); ++row) { + QModelIndex index = mModel->index(row, 0); + KUrl url = mModel->urlForIndex(index); + int urlLength = url.url().length(); + if (url.isParentOf(wantedUrl) && urlLength > matchUrlLength) { + matchIndex = index; + matchUrlLength = urlLength; + } + } + if (!matchIndex.isValid()) { + kWarning() << "Found no root index for" << wantedUrl; + } + return matchIndex; + } +}; + +FolderViewContextManagerItem::FolderViewContextManagerItem(ContextManager* manager) +: AbstractContextManagerItem(manager) +, d(new FolderViewContextManagerItemPrivate) +{ + d->q = this; + d->mModel = 0; + + d->setupView(); + + connect(contextManager(), SIGNAL(currentDirUrlChanged(KUrl)), + SLOT(slotCurrentDirUrlChanged(KUrl))); +} + +FolderViewContextManagerItem::~FolderViewContextManagerItem() +{ + delete d; +} + +void FolderViewContextManagerItem::slotCurrentDirUrlChanged(const KUrl& url) +{ + if (url.isValid() && d->mUrlToSelect != url) { + d->mUrlToSelect = url; + d->mUrlToSelect.cleanPath(); + d->mUrlToSelect.adjustPath(KUrl::RemoveTrailingSlash); + d->mExpandingIndex = QModelIndex(); + } + if (!d->mView->isVisible()) { + return; + } + + expandToSelectedUrl(); +} + +void FolderViewContextManagerItem::expandToSelectedUrl() +{ + if (!d->mUrlToSelect.isValid()) { + return; + } + + if (!d->mModel) { + d->setupModel(); + } + + QModelIndex index = d->findClosestIndex(d->mExpandingIndex, d->mUrlToSelect); + if (!index.isValid()) { + return; + } + d->mExpandingIndex = index; + + KUrl url = d->mModel->urlForIndex(d->mExpandingIndex); + if (d->mUrlToSelect == url) { + // We found our url + QItemSelectionModel* selModel = d->mView->selectionModel(); + selModel->setCurrentIndex(d->mExpandingIndex, QItemSelectionModel::ClearAndSelect); + d->mView->scrollTo(d->mExpandingIndex); + d->mUrlToSelect = KUrl(); + d->mExpandingIndex = QModelIndex(); + } else { + // We found a parent of our url + d->mView->setExpanded(d->mExpandingIndex, true); + } +} + +void FolderViewContextManagerItem::slotRowsInserted(const QModelIndex& parentIndex, int /*start*/, int /*end*/) +{ + // Can't trigger the case where parentIndex is invalid, but it most + // probably happen when root items are created. In this case we trigger + // expandToSelectedUrl without checking the url. + // See bug #191771 + if (!parentIndex.isValid() || d->mModel->urlForIndex(parentIndex).isParentOf(d->mUrlToSelect)) { + d->mExpandingIndex = parentIndex; + // Hack because otherwise indexes are not in correct order! + QMetaObject::invokeMethod(this, "expandToSelectedUrl", Qt::QueuedConnection); + } +} + +void FolderViewContextManagerItem::slotActivated(const QModelIndex& index) +{ + if (!index.isValid()) { + return; + } + + KUrl url = d->mModel->urlForIndex(index); + emit urlChanged(url); +} + +} // namespace diff --git a/gwenview/app/folderviewcontextmanageritem.h b/gwenview/app/folderviewcontextmanageritem.h new file mode 100644 index 00000000..057fb691 --- /dev/null +++ b/gwenview/app/folderviewcontextmanageritem.h @@ -0,0 +1,62 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef FOLDERVIEWCONTEXTMANAGERITEM_H +#define FOLDERVIEWCONTEXTMANAGERITEM_H + +// Qt + +// KDE + +// Local +#include "abstractcontextmanageritem.h" + +class QModelIndex; + +class KUrl; + +namespace Gwenview +{ + +struct FolderViewContextManagerItemPrivate; +class FolderViewContextManagerItem : public AbstractContextManagerItem +{ + Q_OBJECT +public: + FolderViewContextManagerItem(ContextManager*); + ~FolderViewContextManagerItem(); + +Q_SIGNALS: + void urlChanged(const KUrl&); + +private Q_SLOTS: + void slotCurrentDirUrlChanged(const KUrl&); + void expandToSelectedUrl(); + void slotRowsInserted(const QModelIndex&, int start, int end); + void slotActivated(const QModelIndex&); + +private: + friend struct FolderViewContextManagerItemPrivate; + FolderViewContextManagerItemPrivate* const d; +}; + +} // namespace + +#endif /* FOLDERVIEWCONTEXTMANAGERITEM_H */ diff --git a/gwenview/app/fullscreenconfigwidget.ui b/gwenview/app/fullscreenconfigwidget.ui new file mode 100644 index 00000000..7a1d4b1a --- /dev/null +++ b/gwenview/app/fullscreenconfigwidget.ui @@ -0,0 +1,213 @@ + + + FullScreenConfigWidget + + + + 0 + 0 + 258 + 312 + + + + + + + + + + Slideshow + + + true + + + + + + Interval: + + + mSlideShowIntervalSlider + + + + + + + + + 2 + + + 60 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 30 + 0 + + + + [N secs] + + + + + + + + + Loop + + + + + + + Random + + + + + + + + + + Image Information + + + true + + + + + + Select Image Information to Display... + + + + + + + + + + Thumbnails + + + true + + + + + + Show thumbnails + + + true + + + + + + + Height: + + + mHeightSlider + + + + + + + + 0 + 0 + + + + + 204 + 0 + + + + 70 + + + 256 + + + 70 + + + Qt::Horizontal + + + + + + + + + + mSlideShowIntervalSlider + mSlideShowLoopCheckBox + mSlideShowRandomCheckBox + mConfigureDisplayedInformationButton + mShowThumbnailsCheckBox + mHeightSlider + + + + + mShowThumbnailsCheckBox + toggled(bool) + mHeightSlider + setEnabled(bool) + + + 137 + 328 + + + 156 + 362 + + + + + mShowThumbnailsCheckBox + toggled(bool) + label_5 + setEnabled(bool) + + + 94 + 332 + + + 85 + 354 + + + + + diff --git a/gwenview/app/fullscreencontent.cpp b/gwenview/app/fullscreencontent.cpp new file mode 100644 index 00000000..577dfa78 --- /dev/null +++ b/gwenview/app/fullscreencontent.cpp @@ -0,0 +1,527 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "fullscreencontent.moc" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include + +// Local +#include "imagemetainfodialog.h" +#include "ui_fullscreenconfigwidget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +/** + * A widget which behaves more or less like a QToolBar, but which uses real + * widgets for the toolbar items. We need a real widget to be able to position + * the option menu. + */ +class FullScreenToolBar : public QWidget +{ +public: + FullScreenToolBar(QWidget* parent = 0) + : QWidget(parent) + , mLayout(new QHBoxLayout(this)) + { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + mLayout->setSpacing(0); + mLayout->setMargin(0); + } + + void addAction(QAction* action) + { + QToolButton* button = new QToolButton; + button->setDefaultAction(action); + button->setToolButtonStyle(Qt::ToolButtonIconOnly); + button->setAutoRaise(true); + const int extent = KIconLoader::SizeMedium; + button->setIconSize(QSize(extent, extent)); + mLayout->addWidget(button); + } + + void addSeparator() + { + mLayout->addSpacing(KDialog::spacingHint()); + } + + void addStretch() + { + mLayout->addStretch(); + } + + void setDirection(QBoxLayout::Direction direction) + { + mLayout->setDirection(direction); + } + +private: + QBoxLayout* mLayout; +}; + +class FullScreenConfigWidget : public QWidget, public Ui_FullScreenConfigWidget +{ +public: + FullScreenConfigWidget(QWidget* parent=0) + : QWidget(parent) + { + setupUi(this); + } +}; + +struct FullScreenContentPrivate +{ + FullScreenContent* q; + KActionCollection* mActionCollection; + FullScreenBar* mAutoHideContainer; + SlideShow* mSlideShow; + QWidget* mContent; + FullScreenToolBar* mToolBar; + FullScreenToolBar* mRightToolBar; + ThumbnailBarView* mThumbnailBar; + QLabel* mInformationLabel; + ShadowFilter* mToolBarShadow; + ShadowFilter* mRightToolBarShadow; + ShadowFilter* mInformationLabelShadow; + Document::Ptr mCurrentDocument; + QPointer mImageMetaInfoDialog; + QPointer mConfigWidget; + KAction* mOptionsAction; + + bool mFullScreenMode; + bool mViewPageVisible; + + void createOptionsAction() + { + // We do not use a KActionMenu because: + // + // - It causes the button to show a small down arrow on its right, + // which makes it wider + // + // - We can't control where the menu shows: in no-thumbnail-mode, the + // menu should not be aligned to the right edge of the screen because + // if the mode is changed to thumbnail-mode, we want the option button + // to remain visible + mOptionsAction = new KAction(q); + mOptionsAction->setPriority(QAction::LowPriority); + mOptionsAction->setIcon(KIcon("configure")); + mOptionsAction->setToolTip(i18nc("@info:tooltip", "Configure full screen mode")); + QObject::connect(mOptionsAction, SIGNAL(triggered()), q, SLOT(showOptionsMenu())); + } + + void updateContainerAppearance() + { + if (!mFullScreenMode || !mViewPageVisible) { + mAutoHideContainer->setActivated(false); + return; + } + + mThumbnailBar->setVisible(GwenviewConfig::showFullScreenThumbnails()); + mAutoHideContainer->adjustSize(); + mAutoHideContainer->setActivated(true); + } + + void updateLayout() + { + delete mContent->layout(); + + if (GwenviewConfig::showFullScreenThumbnails()) { + mRightToolBar->setDirection(QBoxLayout::TopToBottom); + + QHBoxLayout* layout = new QHBoxLayout(mContent); + layout->setMargin(0); + layout->setSpacing(0); + QVBoxLayout* vLayout; + + // First column + vLayout = new QVBoxLayout; + vLayout->addWidget(mToolBar); + vLayout->addWidget(mInformationLabel); + layout->addLayout(vLayout); + // Second column + layout->addSpacing(2); + layout->addWidget(mThumbnailBar); + layout->addSpacing(2); + // Third column + vLayout = new QVBoxLayout; + vLayout->addWidget(mRightToolBar); + layout->addLayout(vLayout); + + mThumbnailBar->setFixedHeight(GwenviewConfig::fullScreenBarHeight()); + mAutoHideContainer->setFixedHeight(GwenviewConfig::fullScreenBarHeight()); + + // Shadows + mToolBarShadow->reset(); + mToolBarShadow->setShadow(ShadowFilter::RightEdge, QColor(0, 0, 0, 64)); + mToolBarShadow->setShadow(ShadowFilter::BottomEdge, QColor(255, 255, 255, 8)); + + mInformationLabelShadow->reset(); + mInformationLabelShadow->setShadow(ShadowFilter::LeftEdge, QColor(0, 0, 0, 64)); + mInformationLabelShadow->setShadow(ShadowFilter::TopEdge, QColor(0, 0, 0, 64)); + mInformationLabelShadow->setShadow(ShadowFilter::RightEdge, QColor(0, 0, 0, 128)); + mInformationLabelShadow->setShadow(ShadowFilter::BottomEdge, QColor(255, 255, 255, 8)); + + mRightToolBarShadow->reset(); + mRightToolBarShadow->setShadow(ShadowFilter::LeftEdge, QColor(0, 0, 0, 64)); + mRightToolBarShadow->setShadow(ShadowFilter::BottomEdge, QColor(255, 255, 255, 8)); + } else { + mRightToolBar->setDirection(QBoxLayout::RightToLeft); + + QHBoxLayout* layout = new QHBoxLayout(mContent); + layout->setMargin(0); + layout->setSpacing(0); + layout->addWidget(mToolBar); + layout->addWidget(mInformationLabel); + layout->addWidget(mRightToolBar); + + mAutoHideContainer->setFixedHeight(mContent->sizeHint().height()); + + // Shadows + mToolBarShadow->reset(); + + mInformationLabelShadow->reset(); + mInformationLabelShadow->setShadow(ShadowFilter::LeftEdge, QColor(0, 0, 0, 64)); + mInformationLabelShadow->setShadow(ShadowFilter::RightEdge, QColor(0, 0, 0, 32)); + + mRightToolBarShadow->reset(); + mRightToolBarShadow->setShadow(ShadowFilter::LeftEdge, QColor(255, 255, 255, 8)); + } + } +}; + +FullScreenContent::FullScreenContent(QObject* parent) +: QObject(parent) +, d(new FullScreenContentPrivate) +{ + d->q = this; + d->mFullScreenMode = false; + d->mViewPageVisible = false; +} + +FullScreenContent::~FullScreenContent() +{ + delete d; +} + +void FullScreenContent::init(KActionCollection* actionCollection, QWidget* autoHideParentWidget, SlideShow* slideShow) +{ + d->mSlideShow = slideShow; + d->mActionCollection = actionCollection; + connect(actionCollection->action("view"), SIGNAL(toggled(bool)), + SLOT(slotViewModeActionToggled(bool))); + + // mAutoHideContainer + d->mAutoHideContainer = new FullScreenBar(autoHideParentWidget); + d->mAutoHideContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + QVBoxLayout* layout = new QVBoxLayout(d->mAutoHideContainer); + layout->setMargin(0); + layout->setSpacing(0); + + // mContent + d->mContent = new QWidget; + d->mContent->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + d->mContent->setAutoFillBackground(true); + EventWatcher::install(d->mContent, QEvent::Show, this, SLOT(updateCurrentUrlWidgets())); + EventWatcher::install(d->mContent, QEvent::PaletteChange, this, SLOT(slotPaletteChanged())); + layout->addWidget(d->mContent); + + d->createOptionsAction(); + + // mToolBar + d->mToolBar = new FullScreenToolBar(d->mContent); + + #define addAction(name) d->mToolBar->addAction(actionCollection->action(name)) + addAction("browse"); + addAction("view"); + d->mToolBar->addSeparator(); + addAction("go_previous"); + addAction("toggle_slideshow"); + addAction("go_next"); + d->mToolBar->addSeparator(); + addAction("rotate_left"); + addAction("rotate_right"); + #undef addAction + d->mToolBarShadow = new ShadowFilter(d->mToolBar); + + // mInformationLabel + d->mInformationLabel = new QLabel; + d->mInformationLabel->setWordWrap(true); + d->mInformationLabel->setContentsMargins(6, 0, 6, 0); + d->mInformationLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); + d->mInformationLabel->setAutoFillBackground(true); + d->mInformationLabelShadow = new ShadowFilter(d->mInformationLabel); + + // Thumbnail bar + d->mThumbnailBar = new ThumbnailBarView(d->mContent); + d->mThumbnailBar->setThumbnailScaleMode(ThumbnailView::ScaleToSquare); + ThumbnailBarItemDelegate* delegate = new ThumbnailBarItemDelegate(d->mThumbnailBar); + d->mThumbnailBar->setItemDelegate(delegate); + d->mThumbnailBar->setSelectionMode(QAbstractItemView::ExtendedSelection); + + // Right bar + d->mRightToolBar = new FullScreenToolBar(d->mContent); + d->mRightToolBar->addAction(d->mActionCollection->action("leave_fullscreen")); + d->mRightToolBar->addAction(d->mOptionsAction); + d->mRightToolBar->addStretch(); + d->mRightToolBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + d->mRightToolBarShadow = new ShadowFilter(d->mRightToolBar); + + d->updateLayout(); + + d->updateContainerAppearance(); +} + +ThumbnailBarView* FullScreenContent::thumbnailBar() const +{ + return d->mThumbnailBar; +} + +void FullScreenContent::setCurrentUrl(const KUrl& url) +{ + if (url.isEmpty()) { + d->mCurrentDocument = Document::Ptr(); + } else { + d->mCurrentDocument = DocumentFactory::instance()->load(url); + connect(d->mCurrentDocument.data(), SIGNAL(metaInfoUpdated()), + SLOT(updateCurrentUrlWidgets())); + } + updateCurrentUrlWidgets(); +} + +void FullScreenContent::updateInformationLabel() +{ + if (!d->mCurrentDocument) { + return; + } + + if (!d->mInformationLabel->isVisible()) { + return; + } + + ImageMetaInfoModel* model = d->mCurrentDocument->metaInfo(); + + QStringList valueList; + Q_FOREACH(const QString & key, GwenviewConfig::fullScreenPreferredMetaInfoKeyList()) { + const QString value = model->getValueForKey(key); + if (value.length() > 0) { + valueList << value; + } + } + QString text = valueList.join(i18nc("@item:intext fullscreen meta info separator", ", ")); + + d->mInformationLabel->setText(text); +} + +void FullScreenContent::slotPaletteChanged() +{ + QPalette pal = d->mContent->palette(); + pal.setColor(QPalette::Window, pal.color(QPalette::Window).dark(110)); + d->mInformationLabel->setPalette(pal); +} + +void FullScreenContent::updateCurrentUrlWidgets() +{ + updateInformationLabel(); + updateMetaInfoDialog(); +} + +void FullScreenContent::showImageMetaInfoDialog() +{ + if (!d->mImageMetaInfoDialog) { + d->mImageMetaInfoDialog = new ImageMetaInfoDialog(d->mInformationLabel); + // Do not let the fullscreen theme propagate to this dialog for now, + // it's already quite complicated to create a theme + d->mImageMetaInfoDialog->setStyle(QApplication::style()); + d->mImageMetaInfoDialog->setAttribute(Qt::WA_DeleteOnClose, true); + connect(d->mImageMetaInfoDialog, SIGNAL(preferredMetaInfoKeyListChanged(QStringList)), + SLOT(slotPreferredMetaInfoKeyListChanged(QStringList))); + connect(d->mImageMetaInfoDialog, SIGNAL(destroyed()), + SLOT(slotImageMetaInfoDialogClosed())); + } + if (d->mCurrentDocument) { + d->mImageMetaInfoDialog->setMetaInfo(d->mCurrentDocument->metaInfo(), GwenviewConfig::fullScreenPreferredMetaInfoKeyList()); + } + d->mAutoHideContainer->setAutoHidingEnabled(false); + d->mImageMetaInfoDialog->show(); +} + +void FullScreenContent::slotPreferredMetaInfoKeyListChanged(const QStringList& list) +{ + GwenviewConfig::setFullScreenPreferredMetaInfoKeyList(list); + GwenviewConfig::self()->writeConfig(); + updateInformationLabel(); +} + +void FullScreenContent::updateMetaInfoDialog() +{ + if (!d->mImageMetaInfoDialog) { + return; + } + ImageMetaInfoModel* model = d->mCurrentDocument ? d->mCurrentDocument->metaInfo() : 0; + d->mImageMetaInfoDialog->setMetaInfo(model, GwenviewConfig::fullScreenPreferredMetaInfoKeyList()); +} + +static QString formatSlideShowIntervalText(int value) +{ + return i18ncp("Slideshow interval in seconds", "%1 sec", "%1 secs", value); +} + +void FullScreenContent::updateSlideShowIntervalLabel() +{ + Q_ASSERT(d->mConfigWidget); + int value = d->mConfigWidget->mSlideShowIntervalSlider->value(); + QString text = formatSlideShowIntervalText(value); + d->mConfigWidget->mSlideShowIntervalLabel->setText(text); +} + +void FullScreenContent::setFullScreenBarHeight(int value) +{ + d->mThumbnailBar->setFixedHeight(value); + d->mAutoHideContainer->setFixedHeight(value); + GwenviewConfig::setFullScreenBarHeight(value); +} + +void FullScreenContent::showOptionsMenu() +{ + Q_ASSERT(!d->mConfigWidget); + + d->mConfigWidget = new FullScreenConfigWidget; + FullScreenConfigWidget* widget = d->mConfigWidget; + + // Put widget in a menu + QMenu menu; + QWidgetAction* action = new QWidgetAction(&menu); + action->setDefaultWidget(widget); + menu.addAction(action); + + // Slideshow checkboxes + widget->mSlideShowLoopCheckBox->setChecked(d->mSlideShow->loopAction()->isChecked()); + connect(widget->mSlideShowLoopCheckBox, SIGNAL(toggled(bool)), + d->mSlideShow->loopAction(), SLOT(trigger())); + + widget->mSlideShowRandomCheckBox->setChecked(d->mSlideShow->randomAction()->isChecked()); + connect(widget->mSlideShowRandomCheckBox, SIGNAL(toggled(bool)), + d->mSlideShow->randomAction(), SLOT(trigger())); + + // Interval slider + widget->mSlideShowIntervalSlider->setValue(int(GwenviewConfig::interval())); + connect(widget->mSlideShowIntervalSlider, SIGNAL(valueChanged(int)), + d->mSlideShow, SLOT(setInterval(int))); + connect(widget->mSlideShowIntervalSlider, SIGNAL(valueChanged(int)), + SLOT(updateSlideShowIntervalLabel())); + + // Interval label + QString text = formatSlideShowIntervalText(88); + int width = widget->mSlideShowIntervalLabel->fontMetrics().width(text); + widget->mSlideShowIntervalLabel->setFixedWidth(width); + updateSlideShowIntervalLabel(); + + // Image information + connect(widget->mConfigureDisplayedInformationButton, SIGNAL(clicked()), + SLOT(showImageMetaInfoDialog())); + + // Thumbnails + widget->mThumbnailGroupBox->setVisible(d->mViewPageVisible); + if (d->mViewPageVisible) { + widget->mShowThumbnailsCheckBox->setChecked(GwenviewConfig::showFullScreenThumbnails()); + widget->mHeightSlider->setMinimum(d->mRightToolBar->sizeHint().height()); + widget->mHeightSlider->setValue(d->mThumbnailBar->height()); + connect(widget->mShowThumbnailsCheckBox, SIGNAL(toggled(bool)), + SLOT(slotShowThumbnailsToggled(bool))); + connect(widget->mHeightSlider, SIGNAL(valueChanged(int)), + SLOT(setFullScreenBarHeight(int))); + } + + // Show menu below its button + QPoint pos; + QWidget* button = d->mOptionsAction->associatedWidgets().first(); + Q_ASSERT(button); + kWarning() << button << button->geometry(); + if (QApplication::isRightToLeft()) { + pos = button->mapToGlobal(button->rect().bottomLeft()); + } else { + pos = button->mapToGlobal(button->rect().bottomRight()); + pos.rx() -= menu.sizeHint().width(); + } + kWarning() << pos; + menu.exec(pos); +} + +void FullScreenContent::setFullScreenMode(bool fullScreenMode) +{ + d->mFullScreenMode = fullScreenMode; + d->updateContainerAppearance(); +} + +void FullScreenContent::setDistractionFreeMode(bool distractionFreeMode) +{ + d->mAutoHideContainer->setAutoHidingEnabled(!distractionFreeMode); +} + +void FullScreenContent::slotImageMetaInfoDialogClosed() +{ + d->mAutoHideContainer->setAutoHidingEnabled(true); +} + +void FullScreenContent::slotShowThumbnailsToggled(bool value) +{ + GwenviewConfig::setShowFullScreenThumbnails(value); + GwenviewConfig::self()->writeConfig(); + d->mThumbnailBar->setVisible(value); + d->updateLayout(); + d->mContent->adjustSize(); + d->mAutoHideContainer->adjustSize(); +} + +void FullScreenContent::slotViewModeActionToggled(bool value) +{ + d->mViewPageVisible = value; + d->updateContainerAppearance(); +} + +} // namespace diff --git a/gwenview/app/fullscreencontent.h b/gwenview/app/fullscreencontent.h new file mode 100644 index 00000000..f2f83f3c --- /dev/null +++ b/gwenview/app/fullscreencontent.h @@ -0,0 +1,86 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef FULLSCREENCONTENT_H +#define FULLSCREENCONTENT_H + +// Qt +#include + +// KDE + +// Local + +class QStringList; + +class KActionCollection; +class KUrl; + +namespace Gwenview +{ + +class FullScreenBar; +class SlideShow; + +class ThumbnailBarView; + +struct FullScreenContentPrivate; +/** + * The content of the fullscreen bar + */ +class FullScreenContent : public QObject +{ + Q_OBJECT +public: + FullScreenContent(QObject* parent); + ~FullScreenContent(); + + void init(KActionCollection*, QWidget* autoHideParentWidget, SlideShow*); + + ThumbnailBarView* thumbnailBar() const; + + void setDistractionFreeMode(bool); + + void setFullScreenMode(bool); + +public Q_SLOTS: + void setCurrentUrl(const KUrl&); + +private Q_SLOTS: + void updateCurrentUrlWidgets(); + void updateInformationLabel(); + void updateMetaInfoDialog(); + void showImageMetaInfoDialog(); + void slotPaletteChanged(); + void slotImageMetaInfoDialogClosed(); + void slotPreferredMetaInfoKeyListChanged(const QStringList& list); + void showOptionsMenu(); + void updateSlideShowIntervalLabel(); + void setFullScreenBarHeight(int value); + void slotShowThumbnailsToggled(bool value); + void slotViewModeActionToggled(bool value); + +private: + FullScreenContentPrivate* const d; +}; + +} // namespace + +#endif /* FULLSCREENCONTENT_H */ diff --git a/gwenview/app/generalconfigpage.ui b/gwenview/app/generalconfigpage.ui new file mode 100644 index 00000000..12d7830d --- /dev/null +++ b/gwenview/app/generalconfigpage.ui @@ -0,0 +1,113 @@ + + + GeneralConfigPage + + + + 0 + 0 + 470 + 338 + + + + + + + Background color: + + + + + + + + + + 256 + 0 + + + + 255 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 40 + 0 + + + + true + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Videos: + + + + + + + Show videos + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + diff --git a/gwenview/app/gvcore.cpp b/gwenview/app/gvcore.cpp new file mode 100644 index 00000000..0f0ba934 --- /dev/null +++ b/gwenview/app/gvcore.cpp @@ -0,0 +1,373 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "gvcore.h" + +// Qt +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +struct GvCorePrivate +{ + GvCore* q; + MainWindow* mMainWindow; + SortedDirModel* mDirModel; + HistoryModel* mRecentFoldersModel; + HistoryModel* mRecentUrlsModel; + QPalette mPalettes[4]; + + bool showSaveAsDialog(const KUrl& url, KUrl* outUrl, QByteArray* format) + { + KFileDialog dialog(url, QString(), mMainWindow); + dialog.setOperationMode(KFileDialog::Saving); + dialog.setSelection(url.fileName()); + dialog.setMimeFilter( + KImageIO::mimeTypes(KImageIO::Writing), // List + MimeTypeUtils::urlMimeType(url) // Default + ); + + // Show dialog + do { + if (!dialog.exec()) { + return false; + } + + const QString mimeType = dialog.currentMimeFilter(); + if (mimeType.isEmpty()) { + KMessageBox::sorry( + mMainWindow, + i18nc("@info", + "No image format selected.") + ); + continue; + } + + const QStringList typeList = KImageIO::typeForMime(mimeType); + if (typeList.count() > 0) { + *format = typeList[0].toAscii(); + break; + } + KMessageBox::sorry( + mMainWindow, + i18nc("@info", + "Gwenview cannot save images as %1.", mimeType) + ); + } while (true); + + *outUrl = dialog.selectedUrl(); + return true; + } + + void setupPalettes() + { + QPalette pal; + int value = GwenviewConfig::viewBackgroundValue(); + QColor fgColor = value > 128 ? Qt::black : Qt::white; + + // Normal + mPalettes[GvCore::NormalPalette] = KGlobalSettings::createApplicationPalette(); + + pal = mPalettes[GvCore::NormalPalette]; + pal.setColor(QPalette::Base, QColor::fromHsv(0, 0, value)); + pal.setColor(QPalette::Text, fgColor); + mPalettes[GvCore::NormalViewPalette] = pal; + + // Fullscreen + KSharedConfigPtr config; + QString name = GwenviewConfig::fullScreenColorScheme(); + if (name.isEmpty()) { + // Default color scheme + QString path = KStandardDirs::locate("data", "gwenview/color-schemes/fullscreen.colors"); + config = KSharedConfig::openConfig(path); + } else if (name.contains('/')) { + // Full path to a .colors file + config = KSharedConfig::openConfig(name); + } else { + // Standard KDE color scheme + config = KSharedConfig::openConfig(QString("color-schemes/%1.colors").arg(name), KConfig::FullConfig, "data"); + } + mPalettes[GvCore::FullScreenPalette] = KGlobalSettings::createApplicationPalette(config); + + pal = mPalettes[GvCore::FullScreenPalette]; + QString path = KStandardDirs::locate("data", "gwenview/images/background.png"); + QPixmap bgTexture(path); + pal.setBrush(QPalette::Base, bgTexture); + mPalettes[GvCore::FullScreenViewPalette] = pal; + } +}; + +GvCore::GvCore(MainWindow* mainWindow, SortedDirModel* dirModel) +: QObject(mainWindow) +, d(new GvCorePrivate) +{ + d->q = this; + d->mMainWindow = mainWindow; + d->mDirModel = dirModel; + d->mRecentFoldersModel = 0; + d->mRecentUrlsModel = 0; + + d->setupPalettes(); + + connect(GwenviewConfig::self(), SIGNAL(configChanged()), + SLOT(slotConfigChanged())); +} + +GvCore::~GvCore() +{ + delete d; +} + +QAbstractItemModel* GvCore::recentFoldersModel() const +{ + if (!d->mRecentFoldersModel) { + d->mRecentFoldersModel = new HistoryModel(const_cast(this), KStandardDirs::locateLocal("appdata", "recentfolders/")); + } + return d->mRecentFoldersModel; +} + +QAbstractItemModel* GvCore::recentUrlsModel() const +{ + if (!d->mRecentUrlsModel) { + d->mRecentUrlsModel = new HistoryModel(const_cast(this), KStandardDirs::locateLocal("appdata", "recenturls/")); + } + return d->mRecentUrlsModel; +} + +AbstractSemanticInfoBackEnd* GvCore::semanticInfoBackEnd() const +{ + return d->mDirModel->semanticInfoBackEnd(); +} + +SortedDirModel* GvCore::sortedDirModel() const +{ + return d->mDirModel; +} + +void GvCore::addUrlToRecentFolders(KUrl url) +{ + if (!GwenviewConfig::historyEnabled()) { + return; + } + if (!url.isValid()) { + return; + } + if (url.path() != "") { // This check is a workaraound for bug #312060 + url.adjustPath(KUrl::AddTrailingSlash); + } + recentFoldersModel(); + d->mRecentFoldersModel->addUrl(url); +} + +void GvCore::addUrlToRecentUrls(const KUrl& url) +{ + if (!GwenviewConfig::historyEnabled()) { + return; + } + recentUrlsModel(); + d->mRecentUrlsModel->addUrl(url); +} + +void GvCore::saveAll() +{ + SaveAllHelper helper(d->mMainWindow); + helper.save(); +} + +void GvCore::save(const KUrl& url) +{ + Document::Ptr doc = DocumentFactory::instance()->load(url); + QByteArray format = doc->format(); + const QStringList availableTypes = KImageIO::types(KImageIO::Writing); + if (availableTypes.contains(QString(format))) { + DocumentJob* job = doc->save(url, format); + connect(job, SIGNAL(result(KJob*)), SLOT(slotSaveResult(KJob*))); + } else { + // We don't know how to save in 'format', ask the user for a format we can + // write to. + KGuiItem saveUsingAnotherFormat = KStandardGuiItem::saveAs(); + saveUsingAnotherFormat.setText(i18n("Save using another format")); + int result = KMessageBox::warningContinueCancel( + d->mMainWindow, + i18n("Gwenview cannot save images in '%1' format.", QString(format)), + QString() /* caption */, + saveUsingAnotherFormat + ); + if (result == KMessageBox::Continue) { + saveAs(url); + } + } +} + +void GvCore::saveAs(const KUrl& url) +{ + QByteArray format; + KUrl saveAsUrl; + if (!d->showSaveAsDialog(url, &saveAsUrl, &format)) { + return; + } + + // Check for overwrite + if (KIO::NetAccess::exists(saveAsUrl, KIO::NetAccess::DestinationSide, d->mMainWindow)) { + int answer = KMessageBox::warningContinueCancel( + d->mMainWindow, + i18nc("@info", + "A file named %1 already exists.\n" + "Are you sure you want to overwrite it?", + saveAsUrl.fileName()), + QString(), + KStandardGuiItem::overwrite()); + if (answer == KMessageBox::Cancel) { + return; + } + } + + // Start save + Document::Ptr doc = DocumentFactory::instance()->load(url); + KJob* job = doc->save(saveAsUrl, format.data()); + if (!job) { + const QString name = saveAsUrl.fileName().isEmpty() ? saveAsUrl.pathOrUrl() : saveAsUrl.fileName(); + const QString msg = i18nc("@info", "Saving %1 failed:
%2", + name, doc->errorString()); + KMessageBox::sorry(QApplication::activeWindow(), msg); + } else { + connect(job, SIGNAL(result(KJob*)), SLOT(slotSaveResult(KJob*))); + } +} + +static void applyTransform(const KUrl& url, Orientation orientation) +{ + TransformImageOperation* op = new TransformImageOperation(orientation); + Document::Ptr doc = DocumentFactory::instance()->load(url); + op->applyToDocument(doc); +} + +void GvCore::slotSaveResult(KJob* _job) +{ + SaveJob* job = static_cast(_job); + KUrl oldUrl = job->oldUrl(); + KUrl newUrl = job->newUrl(); + + if (job->error()) { + QString name = newUrl.fileName().isEmpty() ? newUrl.pathOrUrl() : newUrl.fileName(); + QString msg = i18nc("@info", "Saving %1 failed:
%2", + name, job->errorString()); + + int result = KMessageBox::warningContinueCancel( + d->mMainWindow, msg, + QString() /* caption */, + KStandardGuiItem::saveAs()); + + if (result == KMessageBox::Continue) { + saveAs(newUrl); + } + return; + } + + if (oldUrl != newUrl) { + d->mMainWindow->goToUrl(newUrl); + + ViewMainPage* page = d->mMainWindow->viewMainPage(); + if (page->isVisible()) { + HudMessageBubble* bubble = new HudMessageBubble(); + bubble->setText(i18n("You are now viewing the new document.")); + KGuiItem item = KStandardGuiItem::back(); + item.setText(i18n("Go back to the original")); + HudButton* button = bubble->addButton(item); + + BinderRef::bind(button, SIGNAL(clicked()), d->mMainWindow, &MainWindow::goToUrl, oldUrl); + connect(button, SIGNAL(clicked()), + bubble, SLOT(deleteLater())); + + page->showMessageWidget(bubble); + } + } +} + +void GvCore::rotateLeft(const KUrl& url) +{ + applyTransform(url, ROT_270); +} + +void GvCore::rotateRight(const KUrl& url) +{ + applyTransform(url, ROT_90); +} + +void GvCore::setRating(const KUrl& url, int rating) +{ + QModelIndex index = d->mDirModel->indexForUrl(url); + if (!index.isValid()) { + kWarning() << "invalid index!"; + return; + } + d->mDirModel->setData(index, rating, SemanticInfoDirModel::RatingRole); +} + +static void clearModel(QAbstractItemModel* model) +{ + model->removeRows(0, model->rowCount()); +} + +void GvCore::slotConfigChanged() +{ + if (!GwenviewConfig::historyEnabled()) { + clearModel(recentFoldersModel()); + clearModel(recentUrlsModel()); + } + d->setupPalettes(); +} + +QPalette GvCore::palette(GvCore::PaletteType type) const +{ + return d->mPalettes[type]; +} + +} // namespace diff --git a/gwenview/app/gvcore.h b/gwenview/app/gvcore.h new file mode 100644 index 00000000..7974de6d --- /dev/null +++ b/gwenview/app/gvcore.h @@ -0,0 +1,87 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef GVCORE_H +#define GVCORE_H + +// Qt +#include + +// KDE + +// Local + +class KJob; +class KUrl; + +class QAbstractItemModel; +class QPalette; + +namespace Gwenview +{ + +class AbstractSemanticInfoBackEnd; +class MainWindow; +class SortedDirModel; + +struct GvCorePrivate; +class GvCore : public QObject +{ + Q_OBJECT +public: + GvCore(MainWindow* mainWindow, SortedDirModel*); + ~GvCore(); + + enum PaletteType { + NormalPalette = 0, + NormalViewPalette, + FullScreenPalette, + FullScreenViewPalette + }; + + QAbstractItemModel* recentFoldersModel() const; + QAbstractItemModel* recentUrlsModel() const; + SortedDirModel* sortedDirModel() const; + AbstractSemanticInfoBackEnd* semanticInfoBackEnd() const; + + void addUrlToRecentFolders(KUrl); + void addUrlToRecentUrls(const KUrl& url); + + QPalette palette(PaletteType type) const; + +public Q_SLOTS: + void saveAll(); + void save(const KUrl&); + void saveAs(const KUrl&); + void rotateLeft(const KUrl&); + void rotateRight(const KUrl&); + void setRating(const KUrl&, int); + +private Q_SLOTS: + void slotConfigChanged(); + void slotSaveResult(KJob*); + +private: + GvCorePrivate* const d; +}; + +} // namespace + +#endif /* GVCORE_H */ diff --git a/gwenview/app/gwenview.desktop b/gwenview/app/gwenview.desktop new file mode 100644 index 00000000..f3090c78 --- /dev/null +++ b/gwenview/app/gwenview.desktop @@ -0,0 +1,184 @@ +[Desktop Entry] +Name=Gwenview +Name[ar]=جوِينفيو +Name[ast]=Gwenview +Name[be]=Gwenview +Name[bg]=Gwenview +Name[bs]=Gwenview +Name[ca]=Gwenview +Name[ca@valencia]=Gwenview +Name[cs]=Gwenview +Name[da]=Gwenview +Name[de]=Gwenview +Name[el]=Gwenview +Name[en_GB]=Gwenview +Name[eo]=Gwenview +Name[es]=Gwenview +Name[et]=Gwenview +Name[eu]=Gwenview +Name[fi]=Gwenview +Name[fr]=Gwenview +Name[ga]=Gwenview +Name[gl]=Gwenview +Name[hi]=ग्वेन-व्यू +Name[hne]=ग्वेन-व्यू +Name[hr]=Gwenview +Name[hu]=Gwenview +Name[ia]=Gwenview +Name[is]=Gwenview +Name[it]=Gwenview +Name[ja]=Gwenview +Name[kk]=Gwenview +Name[km]=Gwenview +Name[ko]=Gwenview +Name[ku]=Gwenview +Name[lt]=Gwenview +Name[lv]=Gwenview +Name[mr]=ग्वेनव्यु +Name[nb]=Gwenview +Name[nds]=Gwenview +Name[ne]=जीवेनभ्यू +Name[nl]=Gwenview +Name[nn]=Gwenview +Name[oc]=Gwenview +Name[pa]=ਜੀਵੀਨ-ਵਿਊ +Name[pl]=Gwenview +Name[pt]=Gwenview +Name[pt_BR]=Gwenview +Name[ro]=Gwenview +Name[ru]=Gwenview +Name[si]=Gwenview +Name[sk]=Gwenview +Name[sl]=Gwenview +Name[sr]=Гвенвју +Name[sr@ijekavian]=Гвенвју +Name[sr@ijekavianlatin]=GwenView +Name[sr@latin]=GwenView +Name[sv]=Gwenview +Name[th]=เกวนวิว +Name[tr]=Gwenview +Name[ug]=Gwenview +Name[uk]=Gwenview +Name[vi]=Gwenview +Name[x-test]=xxGwenviewxx +Name[zh_CN]=Gwenview +Name[zh_TW]=影像檢視_Gwenview +GenericName=KDE Image Viewer +GenericName[ar]=عارض صور كدي +GenericName[bg]=Преглед на изображения в KDE +GenericName[bs]=KDE Prikazivač slika +GenericName[ca]=Visor d'imatges del KDE +GenericName[ca@valencia]=Visor d'imatges del KDE +GenericName[cs]=Prohlížeč obrázků KDE +GenericName[da]=KDE billedfremviser +GenericName[de]=KDE-Bildbetrachter +GenericName[el]=Προβολέας εικόνων του KDE +GenericName[en_GB]=KDE Image Viewer +GenericName[es]=Visor de imágenes de KDE +GenericName[et]=KDE pildinäitaja +GenericName[fi]=KDE:n kuvankatselin +GenericName[fr]=Afficheur d'images de KDE +GenericName[ga]=Amharcán Íomhánna KDE +GenericName[gl]=Visor de imaxes de KDE +GenericName[hu]=KDE képnézegető +GenericName[ia]=Visor de imagine de KDE +GenericName[is]=Myndskoðari fyrir KDE +GenericName[it]=Visore di immagini per KDE +GenericName[ja]=KDE 画像ビューア +GenericName[kk]=KDE кескін қарау құралы +GenericName[ko]=KDE 그림 뷰어 +GenericName[lt]=KDE Paveikslėlių žiūryklė +GenericName[mr]=केडीई प्रतिमा प्रदर्शक +GenericName[nb]=KDE bildeviser +GenericName[nds]=KDE-Bildkieker +GenericName[nl]=KDE afbeeldingenviewer +GenericName[pa]=ਕੇਡੀਈ ਚਿੱਤਰ ਦਰਸ਼ਕ +GenericName[pl]=Przeglądarka obrazów +GenericName[pt]=Visualizador de Imagens do KDE +GenericName[pt_BR]=Visualizador de imagens do KDE +GenericName[ro]=Vizualizor de imagini pentru KDE +GenericName[ru]=Программа просмотра изображений +GenericName[sk]=Prehliadač obrázkov KDE +GenericName[sl]=Pregledovalnik slik za KDE +GenericName[sr]=КДЕ приказивач слика +GenericName[sr@ijekavian]=КДЕ приказивач слика +GenericName[sr@ijekavianlatin]=KDE prikazivač slika +GenericName[sr@latin]=KDE prikazivač slika +GenericName[sv]=Bildvisare för KDE +GenericName[tr]=KDE Resim Gösterici +GenericName[uk]=Переглядач зображень KDE +GenericName[x-test]=xxKDE Image Viewerxx +GenericName[zh_CN]=KDE 图像查看器 +GenericName[zh_TW]=KDE 影像檢視程式 +Comment=A simple image viewer +Comment[ar]=عارض صور بسيط +Comment[ast]=Un visor d'imáxenes cenciellu +Comment[bg]=Програма за преглед на изображения +Comment[bs]=Jednostavan prikazivač slika +Comment[ca]=Un visualitzador d'imatges senzill +Comment[ca@valencia]=Un visualitzador d'imatges senzill +Comment[cs]=Jednoduchý prohlížeč obrázků +Comment[da]=Simpel billedfremviser +Comment[de]=Ein einfacher Bildbetrachter +Comment[el]=Ένας απλός προβολέας εικόνων +Comment[en_GB]=A simple image viewer +Comment[eo]=Simpla bildorigardilo +Comment[es]=Un visor de imágenes sencillo +Comment[et]=Lihtne pildinäitaja +Comment[eu]=Irudi ikustaile bakuna +Comment[fi]=Yksinkertainen kuvankatselin +Comment[fr]=Un afficheur simple d'images +Comment[ga]=Amharcán simplí íomhánna +Comment[gl]=Un visor de imaxes sinxelo +Comment[hi]=एक सरल चित्र प्रदर्शक +Comment[hne]=एक सरल फोटू प्रदर्सक +Comment[hr]=Jednostavni preglednik slika +Comment[hu]=Egyszerű képnézegető +Comment[ia]=Un simplice visor de imagine +Comment[is]=Einfaldur myndskoðari +Comment[it]=Un semplice visore di immagini +Comment[ja]=シンプルな画像ビューア +Comment[kk]=Қарапайым кескінді қарау құралы +Comment[km]=កម្មវិធី​មើល​រូបភាព​ធម្មតា +Comment[ko]=간단한 그림 뷰어 +Comment[ku]=Nîşanderê wêneyan yê hêsanî +Comment[lt]=Paprasta paveikslėlių žiūryklė +Comment[lv]=Vienkāršs attēlu skatītājs +Comment[mr]=एक सोपा प्रतिमा प्रदर्शक +Comment[nb]=En enkel bildeviser +Comment[nds]=En eenfach Bildkieker +Comment[nl]=Een eenvoudige afbeeldingenviewer +Comment[nn]=Ein enkel biletvisar +Comment[pa]=ਇੱਕ ਸਧਾਰਨ ਚਿੱਤਰ ਦਰਸ਼ਕ +Comment[pl]=Prosta przeglądarka obrazów +Comment[pt]=Um visualizador de imagens simples +Comment[pt_BR]=Um visualizador de imagens simples +Comment[ro]=Un vizualizator de imagini simplu +Comment[ru]=Просмотр изображений +Comment[si]=සරල පිංතූර දසුන +Comment[sk]=Jednoduchý prehliadač obrázkov +Comment[sl]=Preprost pregledovalnik slik +Comment[sr]=Једноставан приказивач слика +Comment[sr@ijekavian]=Једноставан приказивач слика +Comment[sr@ijekavianlatin]=Jednostavan prikazivač slika +Comment[sr@latin]=Jednostavan prikazivač slika +Comment[sv]=En enkel bildvisare +Comment[th]=เครื่องมือแสดงภาพแบบพื้นฐาน +Comment[tr]=Basit bir resim gösterici +Comment[ug]=ئاددىي سۈرەت كۆرگۈ +Comment[uk]=Простий переглядач зображень +Comment[vi]=Bộ xem ảnh đơn giản +Comment[x-test]=xxA simple image viewerxx +Comment[zh_CN]=简单图像查看器 +Comment[zh_TW]=一個簡單的影像檢視程式 +Exec=gwenview %U -caption %c %i +Terminal=false +Icon=gwenview +Type=Application +Categories=Qt;KDE;Graphics;Viewer;Photography; +MimeType=inode/directory;image/gif;image/jpeg;image/png;image/bmp;image/x-eps;image/x-ico;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-xbitmap;image/x-xpixmap;image/tiff;image/x-psd;image/x-webp; +X-DocPath=gwenview/index.html +# InitialPreference should be greater than Okular so that Gwenview is the +# primary application associated with images, but less than Konqueror or Dolphin +# so that Gwenview is not the primary applications for folders. +InitialPreference=8 diff --git a/gwenview/app/gwenviewui.rc b/gwenview/app/gwenviewui.rc new file mode 100644 index 00000000..0b1baad3 --- /dev/null +++ b/gwenview/app/gwenviewui.rc @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &Rating + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &Plugins + + + &Settings + + + + + + + + + + Main Toolbar + + + + + + + + + + + + + + + + + diff --git a/gwenview/app/imagemetainfodialog.cpp b/gwenview/app/imagemetainfodialog.cpp new file mode 100644 index 00000000..830c1287 --- /dev/null +++ b/gwenview/app/imagemetainfodialog.cpp @@ -0,0 +1,156 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "imagemetainfodialog.moc" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include + +// STL +#include + +// Local +#include + +namespace Gwenview +{ + +class MetaInfoDelegate : public QStyledItemDelegate +{ +public: + MetaInfoDelegate(QObject* parent) + : QStyledItemDelegate(parent) + {} + +protected: + virtual void paint(QPainter* painter, const QStyleOptionViewItem& _option, const QModelIndex& index) const + { + QStyleOptionViewItemV4 option = _option; + if (!index.parent().isValid()) { + option.displayAlignment = Qt::AlignCenter | Qt::AlignBottom; + option.font.setBold(true); + } + QStyledItemDelegate::paint(painter, option, index); + if (!index.parent().isValid()) { + painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight()); + } + } + + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const + { + QSize sh = QStyledItemDelegate::sizeHint(option, index); + if (!index.parent().isValid()) { + sh.setHeight(sh.height() * 3 / 2); + } + return sh; + } +}; + +/** + * A tree view which is always fully expanded + */ +class ExpandedTreeView : public QTreeView +{ +public: + ExpandedTreeView(QWidget* parent) + : QTreeView(parent) + {} + +protected: + virtual void rowsInserted(const QModelIndex& parent, int start, int end) + { + QTreeView::rowsInserted(parent, start, end); + if (!parent.isValid()) { + for (int row = start; row <= end; ++row) { + setUpRootIndex(row); + } + } + } + + virtual void reset() + { + QTreeView::reset(); + if (model()) { + for (int row = 0; row < model()->rowCount(); ++row) { + setUpRootIndex(row); + } + } + } + +private: + void setUpRootIndex(int row) + { + expand(model()->index(row, 0)); + setFirstColumnSpanned(row, QModelIndex(), true); + } +}; + +struct ImageMetaInfoDialogPrivate +{ + std::auto_ptr mModel; + QTreeView* mTreeView; +}; + +ImageMetaInfoDialog::ImageMetaInfoDialog(QWidget* parent) +: KDialog(parent) +, d(new ImageMetaInfoDialogPrivate) +{ + d->mTreeView = new ExpandedTreeView(this); + d->mTreeView->setRootIsDecorated(false); + d->mTreeView->setIndentation(0); + d->mTreeView->setItemDelegate(new MetaInfoDelegate(d->mTreeView)); + setMainWidget(d->mTreeView); + setCaption(i18nc("@title:window", "Image Information")); + setButtons(KDialog::Close); +} + +ImageMetaInfoDialog::~ImageMetaInfoDialog() +{ + delete d; +} + +void ImageMetaInfoDialog::setMetaInfo(ImageMetaInfoModel* model, const QStringList& list) +{ + if (model) { + d->mModel.reset(new PreferredImageMetaInfoModel(model, list)); + connect(d->mModel.get(), SIGNAL(preferredMetaInfoKeyListChanged(QStringList)), + this, SIGNAL(preferredMetaInfoKeyListChanged(QStringList))); + } else { + d->mModel.reset(0); + } + d->mTreeView->setModel(d->mModel.get()); + + d->mTreeView->header()->resizeSection(0, sizeHint().width() / 2 - KDialog::marginHint() * 2); +} + +QSize ImageMetaInfoDialog::sizeHint() const +{ + return QSize(400, 300); +} + +} // namespace diff --git a/gwenview/app/imagemetainfodialog.h b/gwenview/app/imagemetainfodialog.h new file mode 100644 index 00000000..36ec3a53 --- /dev/null +++ b/gwenview/app/imagemetainfodialog.h @@ -0,0 +1,60 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef IMAGEMETAINFODIALOG_H +#define IMAGEMETAINFODIALOG_H + +// Qt + +// KDE +#include + +// Local + +namespace Gwenview +{ + +class ImageMetaInfoModel; + +struct ImageMetaInfoDialogPrivate; +class ImageMetaInfoDialog : public KDialog +{ + Q_OBJECT +public: + ImageMetaInfoDialog(QWidget* parent); + ~ImageMetaInfoDialog(); + + /** + * Defines the image metainfo model and the preferred metainfo key list. + */ + void setMetaInfo(ImageMetaInfoModel*, const QStringList& list); + + virtual QSize sizeHint() const; + +Q_SIGNALS: + void preferredMetaInfoKeyListChanged(const QStringList&); + +private: + ImageMetaInfoDialogPrivate* const d; +}; + +} // namespace + +#endif /* IMAGEMETAINFODIALOG_H */ diff --git a/gwenview/app/imageopscontextmanageritem.cpp b/gwenview/app/imageopscontextmanageritem.cpp new file mode 100644 index 00000000..7635dbc0 --- /dev/null +++ b/gwenview/app/imageopscontextmanageritem.cpp @@ -0,0 +1,313 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "imageopscontextmanageritem.moc" + +// Qt +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "viewmainpage.h" +#include "gvcore.h" +#include "mainwindow.h" +#include "sidebar.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +struct ImageOpsContextManagerItem::Private +{ + ImageOpsContextManagerItem* q; + MainWindow* mMainWindow; + SideBarGroup* mGroup; + + KAction* mRotateLeftAction; + KAction* mRotateRightAction; + KAction* mMirrorAction; + KAction* mFlipAction; + KAction* mResizeAction; + KAction* mCropAction; + KAction* mRedEyeReductionAction; + QList mActionList; + + void setupActions() + { + KActionCollection* actionCollection = mMainWindow->actionCollection(); + KActionCategory* edit = new KActionCategory(i18nc("@title actions category - means actions changing image", "Edit"), actionCollection); + mRotateLeftAction = edit->addAction("rotate_left", q, SLOT(rotateLeft())); + mRotateLeftAction->setPriority(QAction::LowPriority); + mRotateLeftAction->setText(i18n("Rotate Left")); + mRotateLeftAction->setToolTip(i18nc("@info:tooltip", "Rotate image to the left")); + mRotateLeftAction->setIcon(KIcon("object-rotate-left")); + mRotateLeftAction->setShortcut(Qt::CTRL + Qt::Key_L); + + mRotateRightAction = edit->addAction("rotate_right", q, SLOT(rotateRight())); + mRotateRightAction->setPriority(QAction::LowPriority); + mRotateRightAction->setText(i18n("Rotate Right")); + mRotateRightAction->setToolTip(i18nc("@info:tooltip", "Rotate image to the right")); + mRotateRightAction->setIcon(KIcon("object-rotate-right")); + mRotateRightAction->setShortcut(Qt::CTRL + Qt::Key_R); + + mMirrorAction = edit->addAction("mirror", q, SLOT(mirror())); + mMirrorAction->setText(i18n("Mirror")); + mMirrorAction->setIcon(KIcon("object-flip-horizontal")); + + mFlipAction = edit->addAction("flip", q, SLOT(flip())); + mFlipAction->setText(i18n("Flip")); + mFlipAction->setIcon(KIcon("object-flip-vertical")); + + mResizeAction = edit->addAction("resize", q, SLOT(resizeImage())); + mResizeAction->setText(i18n("Resize")); + mResizeAction->setIcon(KIcon("transform-scale")); + mResizeAction->setShortcut(Qt::SHIFT + Qt::Key_R); + + mCropAction = edit->addAction("crop", q, SLOT(crop())); + mCropAction->setText(i18n("Crop")); + mCropAction->setIcon(KIcon("transform-crop-and-resize")); + mCropAction->setShortcut(Qt::SHIFT + Qt::Key_C); + + mRedEyeReductionAction = edit->addAction("red_eye_reduction", q, SLOT(startRedEyeReduction())); + mRedEyeReductionAction->setText(i18n("Red Eye Reduction")); + //mRedEyeReductionAction->setIcon(KIcon("transform-crop-and-resize")); + + mActionList + << mRotateLeftAction + << mRotateRightAction + << mMirrorAction + << mFlipAction + << mResizeAction + << mCropAction + << mRedEyeReductionAction + ; + } + + bool ensureEditable() + { + KUrl url = q->contextManager()->currentUrl(); + Document::Ptr doc = DocumentFactory::instance()->load(url); + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + if (doc->isEditable()) { + return true; + } + + KMessageBox::sorry( + QApplication::activeWindow(), + i18nc("@info", "Gwenview cannot edit this kind of image.") + ); + return false; + } +}; + +ImageOpsContextManagerItem::ImageOpsContextManagerItem(ContextManager* manager, MainWindow* mainWindow) +: AbstractContextManagerItem(manager) +, d(new Private) +{ + d->q = this; + d->mMainWindow = mainWindow; + d->mGroup = new SideBarGroup(i18n("Image Operations")); + setWidget(d->mGroup); + EventWatcher::install(d->mGroup, QEvent::Show, this, SLOT(updateSideBarContent())); + d->setupActions(); + updateActions(); + connect(contextManager(), SIGNAL(selectionChanged()), + SLOT(updateActions())); + connect(mainWindow, SIGNAL(viewModeChanged()), + SLOT(updateActions())); + connect(mainWindow->viewMainPage(), SIGNAL(completed()), + SLOT(updateActions())); +} + +ImageOpsContextManagerItem::~ImageOpsContextManagerItem() +{ + delete d; +} + +void ImageOpsContextManagerItem::updateSideBarContent() +{ + if (!d->mGroup->isVisible()) { + return; + } + + d->mGroup->clear(); + Q_FOREACH(KAction * action, d->mActionList) { + if (action->isEnabled() && action->priority() != QAction::LowPriority) { + d->mGroup->addAction(action); + } + } +} + +void ImageOpsContextManagerItem::updateActions() +{ + bool canModify = contextManager()->currentUrlIsRasterImage(); + bool viewMainPageIsVisible = d->mMainWindow->viewMainPage()->isVisible(); + if (!viewMainPageIsVisible) { + // Since we only support image operations on one image for now, + // disable actions if several images are selected and the document + // view is not visible. + if (contextManager()->selectedFileItemList().count() != 1) { + canModify = false; + } + } + + d->mRotateLeftAction->setEnabled(canModify); + d->mRotateRightAction->setEnabled(canModify); + d->mMirrorAction->setEnabled(canModify); + d->mFlipAction->setEnabled(canModify); + d->mResizeAction->setEnabled(canModify); + d->mCropAction->setEnabled(canModify && viewMainPageIsVisible); + d->mRedEyeReductionAction->setEnabled(canModify && viewMainPageIsVisible); + + updateSideBarContent(); +} + +void ImageOpsContextManagerItem::rotateLeft() +{ + TransformImageOperation* op = new TransformImageOperation(ROT_270); + applyImageOperation(op); +} + +void ImageOpsContextManagerItem::rotateRight() +{ + TransformImageOperation* op = new TransformImageOperation(ROT_90); + applyImageOperation(op); +} + +void ImageOpsContextManagerItem::mirror() +{ + TransformImageOperation* op = new TransformImageOperation(HFLIP); + applyImageOperation(op); +} + +void ImageOpsContextManagerItem::flip() +{ + TransformImageOperation* op = new TransformImageOperation(VFLIP); + applyImageOperation(op); +} + +void ImageOpsContextManagerItem::resizeImage() +{ + if (!d->ensureEditable()) { + return; + } + Document::Ptr doc = DocumentFactory::instance()->load(contextManager()->currentUrl()); + doc->startLoadingFullImage(); + ResizeImageDialog dialog(d->mMainWindow); + dialog.setOriginalSize(doc->size()); + if (!dialog.exec()) { + return; + } + ResizeImageOperation* op = new ResizeImageOperation(dialog.size()); + applyImageOperation(op); +} + +void ImageOpsContextManagerItem::crop() +{ + if (!d->ensureEditable()) { + return; + } + RasterImageView* imageView = d->mMainWindow->viewMainPage()->imageView(); + if (!imageView) { + kError() << "No ImageView available!"; + return; + } + CropTool* tool = new CropTool(imageView); + connect(tool, SIGNAL(imageOperationRequested(AbstractImageOperation*)), + SLOT(applyImageOperation(AbstractImageOperation*))); + connect(tool, SIGNAL(done()), + SLOT(restoreDefaultImageViewTool())); + + d->mMainWindow->setDistractionFreeMode(true); + imageView->setCurrentTool(tool); +} + +void ImageOpsContextManagerItem::startRedEyeReduction() +{ + if (!d->ensureEditable()) { + return; + } + RasterImageView* view = d->mMainWindow->viewMainPage()->imageView(); + if (!view) { + kError() << "No RasterImageView available!"; + return; + } + RedEyeReductionTool* tool = new RedEyeReductionTool(view); + connect(tool, SIGNAL(imageOperationRequested(AbstractImageOperation*)), + SLOT(applyImageOperation(AbstractImageOperation*))); + connect(tool, SIGNAL(done()), + SLOT(restoreDefaultImageViewTool())); + + d->mMainWindow->setDistractionFreeMode(true); + view->setCurrentTool(tool); +} + +void ImageOpsContextManagerItem::applyImageOperation(AbstractImageOperation* op) +{ + // For now, we only support operations on one image + KUrl url = contextManager()->currentUrl(); + + Document::Ptr doc = DocumentFactory::instance()->load(url); + op->applyToDocument(doc); +} + +void ImageOpsContextManagerItem::restoreDefaultImageViewTool() +{ + RasterImageView* imageView = d->mMainWindow->viewMainPage()->imageView(); + if (!imageView) { + kError() << "No RasterImageView available!"; + return; + } + + AbstractRasterImageViewTool* tool = imageView->currentTool(); + imageView->setCurrentTool(0); + tool->deleteLater(); + d->mMainWindow->setDistractionFreeMode(false); +} + +} // namespace diff --git a/gwenview/app/imageopscontextmanageritem.h b/gwenview/app/imageopscontextmanageritem.h new file mode 100644 index 00000000..23f37403 --- /dev/null +++ b/gwenview/app/imageopscontextmanageritem.h @@ -0,0 +1,64 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef IMAGEOPSCONTEXTMANAGERITEM_H +#define IMAGEOPSCONTEXTMANAGERITEM_H + +// Qt + +// KDE + +// Local +#include "abstractcontextmanageritem.h" + +namespace Gwenview +{ + +class AbstractImageOperation; +class MainWindow; + +class ImageOpsContextManagerItem : public AbstractContextManagerItem +{ + Q_OBJECT +public: + ImageOpsContextManagerItem(ContextManager*, MainWindow*); + ~ImageOpsContextManagerItem(); + +private Q_SLOTS: + void updateActions(); + void updateSideBarContent(); + void rotateLeft(); + void rotateRight(); + void mirror(); + void flip(); + void resizeImage(); + void crop(); + void startRedEyeReduction(); + void applyImageOperation(AbstractImageOperation*); + void restoreDefaultImageViewTool(); + +private: + struct Private; + Private* const d; +}; + +} // namespace + +#endif /* IMAGEOPSCONTEXTMANAGERITEM_H */ diff --git a/gwenview/app/imageviewconfigpage.ui b/gwenview/app/imageviewconfigpage.ui new file mode 100644 index 00000000..f5e1e5a2 --- /dev/null +++ b/gwenview/app/imageviewconfigpage.ui @@ -0,0 +1,492 @@ + + + ImageViewConfigPage + + + + 0 + 0 + 400 + 385 + + + + + + + Transparent background: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + checkBoardRadioButton + + + + + + + 6 + + + 0 + + + + + &Check board + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 6 + + + 0 + + + + + &Solid color: + + + + + + + false + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 207 + 17 + + + + + + + + Mouse wheel behavior: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + mouseWheelScrollRadioButton + + + + + + + + + Scroll + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Browse + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 207 + 17 + + + + + + + + + + Enlarge smaller images + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 204 + 17 + + + + + + + + Animations: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + glAnimationRadioButton + + + + + + + + + OpenGL + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Software + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + None + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + <b>Thumbnail Bar</b> + + + + + + + Orientation: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + horizontalRadioButton + + + + + + + + + Horizontal + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Row count: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + kcfg_ThumbnailBarRowCount + + + + + + + + + 1 + + + 10 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 62 + + + + + + + + + KColorButton + QPushButton +
kcolorbutton.h
+
+ + KIntSpinBox + QSpinBox +
knuminput.h
+
+
+ + checkBoardRadioButton + solidColorRadioButton + kcfg_AlphaBackgroundColor + mouseWheelScrollRadioButton + mouseWheelBrowseRadioButton + kcfg_EnlargeSmallerImages + glAnimationRadioButton + softwareAnimationRadioButton + noAnimationRadioButton + horizontalRadioButton + verticalRadioButton + kcfg_ThumbnailBarRowCount + + + + + solidColorRadioButton + toggled(bool) + kcfg_AlphaBackgroundColor + setEnabled(bool) + + + 195 + 49 + + + 292 + 56 + + + + +
diff --git a/gwenview/app/infocontextmanageritem.cpp b/gwenview/app/infocontextmanageritem.cpp new file mode 100644 index 00000000..b116db8d --- /dev/null +++ b/gwenview/app/infocontextmanageritem.cpp @@ -0,0 +1,391 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "infocontextmanageritem.moc" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include + +// Local +#include "imagemetainfodialog.h" +#include "sidebar.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +/** + * This widget is capable of showing multiple lines of key/value pairs. + */ +class KeyValueWidget : public QWidget +{ + struct Row + { + Row(QWidget* parent) + : keyLabel(new QLabel(parent)) + , valueLabel(new QLabel(parent)) + { + initLabel(keyLabel); + initLabel(valueLabel); + + QPalette pal = keyLabel->palette(); + QColor color = pal.color(QPalette::WindowText); + color.setAlphaF(0.65); + pal.setColor(QPalette::WindowText, color); + keyLabel->setPalette(pal); + + valueLabel->setContentsMargins(6, 0, 0, 6); + } + + ~Row() + { + delete keyLabel; + delete valueLabel; + } + + int setLabelGeometries(int rowY, int labelWidth) + { + int labelHeight = keyLabel->heightForWidth(labelWidth); + keyLabel->setGeometry(0, rowY, labelWidth, labelHeight); + rowY += labelHeight; + labelHeight = valueLabel->heightForWidth(labelWidth); + valueLabel->setGeometry(0, rowY, labelWidth, labelHeight); + rowY += labelHeight; + return rowY; + } + + int heightForWidth(int width) const + { + return keyLabel->heightForWidth(width) + valueLabel->heightForWidth(width); + } + + static void initLabel(QLabel* label) + { + label->setWordWrap(true); + label->show(); + label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); + } + + QLabel* keyLabel; + QLabel* valueLabel; + }; +public: + KeyValueWidget(QWidget* parent = 0) + : QWidget(parent) + { + QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Fixed); + policy.setHeightForWidth(true); + setSizePolicy(policy); + } + + QSize sizeHint() const + { + int width = 150; + int height = heightForWidth(width); + return QSize(width, height); + } + + int heightForWidth(int w) const + { + int height = 0; + Q_FOREACH(Row* row, mRows) { + height += row->heightForWidth(w); + } + return height; + } + + void clear() + { + qDeleteAll(mRows); + mRows.clear(); + updateGeometry(); + } + + void addRow(const QString& key, const QString& value) + { + Row* row = new Row(this); + row->keyLabel->setText(i18nc( + "@item:intext %1 is a key, we append a colon to it. A value is displayed after", + "%1:", key)); + row->valueLabel->setText(value); + mRows << row; + } + + static bool rowsLessThan(const Row* row1, const Row* row2) + { + return row1->keyLabel->text() < row2->keyLabel->text(); + } + + void finishAddRows() + { + qSort(mRows.begin(), mRows.end(), KeyValueWidget::rowsLessThan); + updateGeometry(); + } + + void layoutRows() + { + // Layout labels manually: I tried to use a QVBoxLayout but for some + // reason when using software animations the widget blinks when going + // from one image to another + int rowY = 0; + const int labelWidth = width(); + Q_FOREACH(Row* row, mRows) { + rowY = row->setLabelGeometries(rowY, labelWidth); + } + } + +protected: + void showEvent(QShowEvent* event) + { + QWidget::showEvent(event); + layoutRows(); + } + + void resizeEvent(QResizeEvent* event) + { + QWidget::resizeEvent(event); + layoutRows(); + } + +private: + QVector mRows; +}; + +struct InfoContextManagerItemPrivate +{ + InfoContextManagerItem* q; + SideBarGroup* mGroup; + + // One selection fields + QScrollArea* mOneFileWidget; + KeyValueWidget* mKeyValueWidget; + Document::Ptr mDocument; + + // Multiple selection fields + QLabel* mMultipleFilesLabel; + + QPointer mImageMetaInfoDialog; + + void updateMetaInfoDialog() + { + if (!mImageMetaInfoDialog) { + return; + } + ImageMetaInfoModel* model = mDocument ? mDocument->metaInfo() : 0; + mImageMetaInfoDialog->setMetaInfo(model, GwenviewConfig::preferredMetaInfoKeyList()); + } + + void setupGroup() + { + mOneFileWidget = new QScrollArea(); + mOneFileWidget->setFrameStyle(QFrame::NoFrame); + mOneFileWidget->setWidgetResizable(true); + + mKeyValueWidget = new KeyValueWidget; + + QLabel* moreLabel = new QLabel(mOneFileWidget); + moreLabel->setText(QString("%1").arg(i18nc("@action show more image meta info", "More..."))); + moreLabel->setAlignment(Qt::AlignRight); + + QWidget* content = new QWidget; + QVBoxLayout* layout = new QVBoxLayout(content); + layout->setMargin(2); + layout->setSpacing(2); + layout->addWidget(mKeyValueWidget); + layout->addWidget(moreLabel); + + mOneFileWidget->setWidget(content); + + mMultipleFilesLabel = new QLabel(); + + mGroup = new SideBarGroup(i18nc("@title:group", "Meta Information")); + q->setWidget(mGroup); + mGroup->addWidget(mOneFileWidget); + mGroup->addWidget(mMultipleFilesLabel); + + EventWatcher::install(mGroup, QEvent::Show, q, SLOT(updateSideBarContent())); + + QObject::connect(moreLabel, SIGNAL(linkActivated(QString)), + q, SLOT(showMetaInfoDialog())); + } + + void forgetCurrentDocument() + { + if (mDocument) { + QObject::disconnect(mDocument.data(), 0, q, 0); + // "Garbage collect" document + mDocument = 0; + } + } +}; + +InfoContextManagerItem::InfoContextManagerItem(ContextManager* manager) +: AbstractContextManagerItem(manager) +, d(new InfoContextManagerItemPrivate) +{ + d->q = this; + d->setupGroup(); + connect(contextManager(), SIGNAL(selectionChanged()), + SLOT(updateSideBarContent())); + connect(contextManager(), SIGNAL(selectionDataChanged()), + SLOT(updateSideBarContent())); +} + +InfoContextManagerItem::~InfoContextManagerItem() +{ + delete d; +} + +void InfoContextManagerItem::updateSideBarContent() +{ + LOG("updateSideBarContent"); + if (!d->mGroup->isVisible()) { + LOG("updateSideBarContent: not visible, not updating"); + return; + } + LOG("updateSideBarContent: really updating"); + + KFileItemList itemList = contextManager()->selectedFileItemList(); + if (itemList.count() == 0) { + d->forgetCurrentDocument(); + d->mOneFileWidget->hide(); + d->mMultipleFilesLabel->hide(); + d->updateMetaInfoDialog(); + return; + } + + KFileItem item = itemList.first(); + if (itemList.count() == 1 && !ArchiveUtils::fileItemIsDirOrArchive(item)) { + fillOneFileGroup(item); + } else { + fillMultipleItemsGroup(itemList); + } + d->updateMetaInfoDialog(); +} + +void InfoContextManagerItem::fillOneFileGroup(const KFileItem& item) +{ + d->mOneFileWidget->show(); + d->mMultipleFilesLabel->hide(); + + d->forgetCurrentDocument(); + d->mDocument = DocumentFactory::instance()->load(item.url()); + connect(d->mDocument.data(), SIGNAL(metaInfoUpdated()), + SLOT(updateOneFileInfo())); + + d->updateMetaInfoDialog(); + updateOneFileInfo(); +} + +void InfoContextManagerItem::fillMultipleItemsGroup(const KFileItemList& itemList) +{ + d->forgetCurrentDocument(); + + int folderCount = 0, fileCount = 0; + Q_FOREACH(const KFileItem & item, itemList) { + if (item.isDir()) { + folderCount++; + } else { + fileCount++; + } + } + + if (folderCount == 0) { + d->mMultipleFilesLabel->setText(i18ncp("@label", "%1 file selected", "%1 files selected", fileCount)); + } else if (fileCount == 0) { + d->mMultipleFilesLabel->setText(i18ncp("@label", "%1 folder selected", "%1 folders selected", folderCount)); + } else { + d->mMultipleFilesLabel->setText(i18nc("@label. The two parameters are strings like '2 folders' and '1 file'.", + "%1 and %2 selected", i18np("%1 folder", "%1 folders", folderCount), i18np("%1 file", "%1 files", fileCount))); + } + d->mOneFileWidget->hide(); + d->mMultipleFilesLabel->show(); +} + +void InfoContextManagerItem::updateOneFileInfo() +{ + if (!d->mDocument) { + return; + } + + ImageMetaInfoModel* metaInfoModel = d->mDocument->metaInfo(); + d->mKeyValueWidget->clear(); + Q_FOREACH(const QString & key, GwenviewConfig::preferredMetaInfoKeyList()) { + QString label; + QString value; + metaInfoModel->getInfoForKey(key, &label, &value); + if (!label.isEmpty() && !value.isEmpty()) { + d->mKeyValueWidget->addRow(label, value); + } + } + d->mKeyValueWidget->finishAddRows(); + d->mKeyValueWidget->layoutRows(); +} + +void InfoContextManagerItem::showMetaInfoDialog() +{ + if (!d->mImageMetaInfoDialog) { + d->mImageMetaInfoDialog = new ImageMetaInfoDialog(d->mOneFileWidget); + d->mImageMetaInfoDialog->setAttribute(Qt::WA_DeleteOnClose, true); + connect(d->mImageMetaInfoDialog, SIGNAL(preferredMetaInfoKeyListChanged(QStringList)), + SLOT(slotPreferredMetaInfoKeyListChanged(QStringList))); + } + d->mImageMetaInfoDialog->setMetaInfo(d->mDocument ? d->mDocument->metaInfo() : 0, GwenviewConfig::preferredMetaInfoKeyList()); + d->mImageMetaInfoDialog->show(); +} + +void InfoContextManagerItem::slotPreferredMetaInfoKeyListChanged(const QStringList& list) +{ + GwenviewConfig::setPreferredMetaInfoKeyList(list); + GwenviewConfig::self()->writeConfig(); + updateOneFileInfo(); +} + +} // namespace diff --git a/gwenview/app/infocontextmanageritem.h b/gwenview/app/infocontextmanageritem.h new file mode 100644 index 00000000..13918d03 --- /dev/null +++ b/gwenview/app/infocontextmanageritem.h @@ -0,0 +1,61 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef INFOCONTEXTMANAGERITEM_H +#define INFOCONTEXTMANAGERITEM_H + +// Qt + +// Local +#include "abstractcontextmanageritem.h" + +class QStringList; +class KFileItem; +class KFileItemList; + +namespace Gwenview +{ + +struct InfoContextManagerItemPrivate; + +class InfoContextManagerItem : public AbstractContextManagerItem +{ + Q_OBJECT +public: + InfoContextManagerItem(ContextManager*); + ~InfoContextManagerItem(); + +private Q_SLOTS: + void updateSideBarContent(); + void updateOneFileInfo(); + void showMetaInfoDialog(); + void slotPreferredMetaInfoKeyListChanged(const QStringList&); + +private: + void fillOneFileGroup(const KFileItem& item); + + void fillMultipleItemsGroup(const KFileItemList& itemList); + + friend struct InfoContextManagerItemPrivate; + InfoContextManagerItemPrivate* const d; +}; + +} // namespace + +#endif /* INFOCONTEXTMANAGERITEM_H */ diff --git a/gwenview/app/kipiexportaction.cpp b/gwenview/app/kipiexportaction.cpp new file mode 100644 index 00000000..1eeb0585 --- /dev/null +++ b/gwenview/app/kipiexportaction.cpp @@ -0,0 +1,132 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "kipiexportaction.moc" + +// Qt +#include + +// KDE +#include +#include +#include + +// Local +#include +#include "kipiinterface.h" + +namespace Gwenview +{ + +struct KIPIExportActionPrivate +{ + KIPIExportAction* q; + KIPIInterface* mKIPIInterface; + QAction* mDefaultAction; + QList mExportActionList; + + void updateMenu() + { + KMenu* menu = static_cast(q->menu()); + menu->clear(); + + if (mDefaultAction) { + menu->addTitle(i18n("Last Used Plugin")); + menu->addAction(mDefaultAction); + menu->addTitle(i18n("Other Plugins")); + } + Q_FOREACH(QAction * action, mExportActionList) { + action->setIconVisibleInMenu(true); + if (action != mDefaultAction) { + menu->addAction(action); + } + } + if (menu->isEmpty()) { + QAction* action = new QAction(menu); + action->setText(i18n("No Plugin Found")); + action->setEnabled(false); + menu->addAction(action); + } + } +}; + +KIPIExportAction::KIPIExportAction(QObject* parent) +: KToolBarPopupAction(KIcon("document-share"), i18nc("@action", "Share"), parent) +, d(new KIPIExportActionPrivate) +{ + d->q = this; + d->mKIPIInterface = 0; + d->mDefaultAction = 0; + setToolTip(i18nc("@info:tooltip", "Share images using various services")); + + setDelayed(false); + connect(menu(), SIGNAL(aboutToShow()), SLOT(init())); + connect(menu(), SIGNAL(triggered(QAction*)), SLOT(slotPluginTriggered(QAction*))); +} + +KIPIExportAction::~KIPIExportAction() +{ + delete d; +} + +void KIPIExportAction::setKIPIInterface(KIPIInterface* interface) +{ + d->mKIPIInterface = interface; +} + +void KIPIExportAction::init() +{ + d->mExportActionList = d->mKIPIInterface->pluginActions(KIPI::ExportPlugin); + if (d->mKIPIInterface->isLoadingFinished()) { + // Look for default action + QString defaultActionText = GwenviewConfig::defaultExportPluginText(); + Q_FOREACH(QAction* action, d->mExportActionList) { + if (action->text() == defaultActionText) { + setDefaultAction(action); + break; + } + } + // We are done, don't come back next time menu is shown + disconnect(menu(), SIGNAL(aboutToShow()), this, SLOT(init())); + } else { + // Loading is in progress, come back when it is done + connect(d->mKIPIInterface, SIGNAL(loadingFinished()), SLOT(init())); + } + d->updateMenu(); +} + +void KIPIExportAction::setDefaultAction(QAction* action) +{ + if (action == d->mDefaultAction) { + return; + } + d->mDefaultAction = action; + + GwenviewConfig::setDefaultExportPluginText(action->text()); +} + +void KIPIExportAction::slotPluginTriggered(QAction* action) +{ + setDefaultAction(action); + d->updateMenu(); +} + +} // namespace diff --git a/gwenview/app/kipiexportaction.h b/gwenview/app/kipiexportaction.h new file mode 100644 index 00000000..95da8ac3 --- /dev/null +++ b/gwenview/app/kipiexportaction.h @@ -0,0 +1,57 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef KIPIEXPORTACTION_H +#define KIPIEXPORTACTION_H + +// Qt + +// KDE +#include + +// Local + +namespace Gwenview +{ + +class KIPIInterface; + +struct KIPIExportActionPrivate; +class KIPIExportAction : public KToolBarPopupAction +{ + Q_OBJECT +public: + KIPIExportAction(QObject* parent); + ~KIPIExportAction(); + + void setKIPIInterface(KIPIInterface*); + +private Q_SLOTS: + void init(); + void setDefaultAction(QAction*); + void slotPluginTriggered(QAction*); + +private: + KIPIExportActionPrivate* const d; +}; + +} // namespace + +#endif /* KIPIEXPORTACTION_H */ diff --git a/gwenview/app/kipiimagecollectionselector.cpp b/gwenview/app/kipiimagecollectionselector.cpp new file mode 100644 index 00000000..c78279c6 --- /dev/null +++ b/gwenview/app/kipiimagecollectionselector.cpp @@ -0,0 +1,91 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "kipiimagecollectionselector.moc" + +// Qt +#include +#include + +// KDE +#include + +// Local + +namespace Gwenview +{ + +struct KIPIImageCollectionSelectorPrivate +{ + KIPIInterface* mInterface; + QListWidget* mListWidget; +}; + +KIPIImageCollectionSelector::KIPIImageCollectionSelector(KIPIInterface* interface, QWidget* parent) +: KIPI::ImageCollectionSelector(parent) +, d(new KIPIImageCollectionSelectorPrivate) +{ + d->mInterface = interface; + + d->mListWidget = new QListWidget; + QList list = interface->allAlbums(); + Q_FOREACH(const KIPI::ImageCollection & collection, list) { + QListWidgetItem* item = new QListWidgetItem(d->mListWidget); + QString name = collection.name(); + int imageCount = collection.images().size(); + QString title = i18ncp("%1 is collection name, %2 is image count in collection", + "%1 (%2 image)", "%1 (%2 images)", name, imageCount); + + item->setText(title); + item->setData(Qt::UserRole, name); + } + + connect(d->mListWidget, SIGNAL(currentRowChanged(int)), + SIGNAL(selectionChanged())); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->addWidget(d->mListWidget); + layout->setMargin(0); +} + +KIPIImageCollectionSelector::~KIPIImageCollectionSelector() +{ + delete d; +} + +QList KIPIImageCollectionSelector::selectedImageCollections() const +{ + QListWidgetItem* item = d->mListWidget->currentItem(); + QList selectedList; + if (item) { + QString name = item->data(Qt::UserRole).toString(); + QList list = d->mInterface->allAlbums(); + Q_FOREACH(const KIPI::ImageCollection & collection, list) { + if (collection.name() == name) { + selectedList << collection; + break; + } + } + } + return selectedList; +} + +} // namespace diff --git a/gwenview/app/kipiimagecollectionselector.h b/gwenview/app/kipiimagecollectionselector.h new file mode 100644 index 00000000..bad6412a --- /dev/null +++ b/gwenview/app/kipiimagecollectionselector.h @@ -0,0 +1,54 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef KIPIIMAGECOLLECTIONSELECTOR_H +#define KIPIIMAGECOLLECTIONSELECTOR_H + +// Qt + +// KDE + +// KIPI +#include +#include + +// Local +#include "kipiinterface.h" + +namespace Gwenview +{ + +struct KIPIImageCollectionSelectorPrivate; +class KIPIImageCollectionSelector : public KIPI::ImageCollectionSelector +{ + Q_OBJECT +public: + KIPIImageCollectionSelector(KIPIInterface*, QWidget* parent); + ~KIPIImageCollectionSelector(); + + virtual QList selectedImageCollections() const; + +private: + KIPIImageCollectionSelectorPrivate* const d; +}; + +} // namespace + +#endif /* KIPIIMAGECOLLECTIONSELECTOR_H */ diff --git a/gwenview/app/kipiinterface.cpp b/gwenview/app/kipiinterface.cpp new file mode 100644 index 00000000..43421485 --- /dev/null +++ b/gwenview/app/kipiinterface.cpp @@ -0,0 +1,483 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2000-2008 Aurélien Gâteau +Copyright 2008 Angelo Naselli + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#include "kipiinterface.moc" + +// Qt +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include + +// KIPI +#include +#include +#include +#include +#include +#include + +// local +#include "mainwindow.h" +#include "kipiimagecollectionselector.h" +#include "kipiuploadwidget.h" +#include +#include +#include +#include +#include + +namespace Gwenview +{ +#undef ENABLE_LOG +#undef LOG + +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +class KIPIImageInfo : public KIPI::ImageInfoShared +{ + static const QRegExp sExtensionRE; +public: + KIPIImageInfo(KIPI::Interface* interface, const KUrl& url) + : KIPI::ImageInfoShared(interface, url) + { + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url); + + mAttributes.insert("name", url.fileName()); + mAttributes.insert("comment", comment()); + mAttributes.insert("date", TimeUtils::dateTimeForFileItem(item).dateTime()); + mAttributes.insert("orientation", orientation()); + mAttributes.insert("title", prettyFileName()); + int size = item.size(); + if (size > 0) { + mAttributes.insert("filesize", size); + } + } + +#if (KIPI_VERSION < 0x020000) + QString name() + { + return mAttributes.value("name").toString(); + } + + QString description() + { + return mAttributes.value("comment").toString(); + } + + void setDescription(const QString&) + {} + + int angle() + { + return mAttributes.value("angle").toInt(); + } +#endif + + QMap attributes() { + return mAttributes; + } + + void delAttributes(const QStringList& attributeNames) + { + Q_FOREACH(const QString& name, attributeNames) { + mAttributes.remove(name); + } + } + + void clearAttributes() + { + mAttributes.clear(); + } + + void addAttributes(const QVariantMap& attributes) + { + QVariantMap::ConstIterator + it = attributes.constBegin(), + end = attributes.constEnd(); + for (; it != end; ++it) { + mAttributes.insert(it.key(), it.value()); + } + } + +private: + QString prettyFileName() const + { + QString txt = _url.fileName(); + txt.replace('_', ' '); + txt.remove(sExtensionRE); + return txt; + } + + QString comment() const + { + if (!_url.isLocalFile()) return QString(); + + JpegContent content; + bool ok = content.load(_url.toLocalFile()); + if (!ok) return QString(); + + return content.comment(); + } + + int orientation() const + { + KFileMetaInfo metaInfo(_url); + + if (!metaInfo.isValid()) { + return 0; + } + + const KFileMetaInfoItem& mii = metaInfo.item("http://freedesktop.org/standards/xesam/1.0/core#orientation"); + bool ok = false; + const Orientation orientation = (Orientation)mii.value().toInt(&ok); + if (!ok) { + return 0; + } + + switch (orientation) { + case NOT_AVAILABLE: + case NORMAL: + return 0; + + case ROT_90: + return 90; + + case ROT_180: + return 180; + + case ROT_270: + return 270; + + case HFLIP: + case VFLIP: + case TRANSPOSE: + case TRANSVERSE: + kWarning() << "Can't represent an orientation value of" << orientation << "as an angle (" << _url << ')'; + return 0; + } + + kWarning() << "Don't know how to handle an orientation value of" << orientation << '(' << _url << ')'; + return 0; + } + + QVariantMap mAttributes; +}; + +const QRegExp KIPIImageInfo::sExtensionRE("\\.[a-z0-9]+$", Qt::CaseInsensitive); + +struct MenuInfo +{ + QString mName; + QList mActions; + + MenuInfo() + {} + + MenuInfo(const QString& name) + : mName(name) + {} +}; +typedef QMap MenuInfoMap; + +struct KIPIInterfacePrivate +{ + KIPIInterface* q; + MainWindow* mMainWindow; + QMenu* mPluginMenu; + KIPI::PluginLoader* mPluginLoader; + KIPI::PluginLoader::PluginList mPluginQueue; + MenuInfoMap mMenuInfoMap; + KAction* mLoadingAction; + KAction* mNoPluginAction; + + void setupPluginsMenu() + { + mPluginMenu = static_cast( + mMainWindow->factory()->container("plugins", mMainWindow)); + QObject::connect(mPluginMenu, SIGNAL(aboutToShow()), + q, SLOT(loadPlugins())); + } + + KAction* createDummyPluginAction(const QString& text) + { + KAction* action = new KAction(q); + action->setText(text); + action->setShortcutConfigurable(false); + action->setEnabled(false); + return action; + } +}; + +KIPIInterface::KIPIInterface(MainWindow* mainWindow) +: KIPI::Interface(mainWindow) +, d(new KIPIInterfacePrivate) +{ + d->q = this; + d->mMainWindow = mainWindow; + d->mPluginLoader = 0; + d->mLoadingAction = d->createDummyPluginAction(i18n("Loading...")); + d->mNoPluginAction = d->createDummyPluginAction(i18n("No Plugin Found")); + + d->setupPluginsMenu(); + QObject::connect(d->mMainWindow->contextManager(), SIGNAL(selectionChanged()), + this, SLOT(slotSelectionChanged())); + QObject::connect(d->mMainWindow->contextManager(), SIGNAL(currentDirUrlChanged(KUrl)), + this, SLOT(slotDirectoryChanged())); +#if 0 +//TODO instead of delaying can we load them all at start-up to use actions somewhere else? +// delay a bit, so that it's called after loadPlugins() + QTimer::singleShot(0, this, SLOT(init())); +#endif +} + +KIPIInterface::~KIPIInterface() +{ + delete d; +} + +static bool actionLessThan(QAction* a1, QAction* a2) +{ + QString a1Text = a1->text().replace('&', QString()); + QString a2Text = a2->text().replace('&', QString()); + return QString::compare(a1Text, a2Text, Qt::CaseInsensitive) < 0; +} + +void KIPIInterface::loadPlugins() +{ + // Already done + if (d->mPluginLoader) { + return; + } + + d->mMenuInfoMap[KIPI::ImagesPlugin] = MenuInfo(i18nc("@title:menu", "Images")); + d->mMenuInfoMap[KIPI::ToolsPlugin] = MenuInfo(i18nc("@title:menu", "Tools")); + d->mMenuInfoMap[KIPI::ImportPlugin] = MenuInfo(i18nc("@title:menu", "Import")); + d->mMenuInfoMap[KIPI::ExportPlugin] = MenuInfo(i18nc("@title:menu", "Export")); + d->mMenuInfoMap[KIPI::BatchPlugin] = MenuInfo(i18nc("@title:menu", "Batch Processing")); + d->mMenuInfoMap[KIPI::CollectionsPlugin] = MenuInfo(i18nc("@title:menu", "Collections")); + +#if (KIPI_VERSION >= 0x020000) + d->mPluginLoader = new KIPI::PluginLoader(); + d->mPluginLoader->setInterface(this); + d->mPluginLoader->init(); +#else + d->mPluginLoader = new KIPI::PluginLoader(QStringList(), this); +#endif + d->mPluginQueue = d->mPluginLoader->pluginList(); + d->mPluginMenu->addAction(d->mLoadingAction); + loadOnePlugin(); +} + +void KIPIInterface::loadOnePlugin() +{ + while (!d->mPluginQueue.isEmpty()) { + KIPI::PluginLoader::Info* pluginInfo = d->mPluginQueue.takeFirst(); + if (!pluginInfo->shouldLoad()) { + continue; + } + + KIPI::Plugin* plugin = pluginInfo->plugin(); + if (!plugin) { + kWarning() << "Plugin from library" << pluginInfo->library() << "failed to load"; + continue; + } + + plugin->setup(d->mMainWindow); + QList actions = plugin->actions(); + Q_FOREACH(KAction * action, actions) { + KIPI::Category category = plugin->category(action); + + if (!d->mMenuInfoMap.contains(category)) { + kWarning() << "Unknown category '" << category; + continue; + } + + d->mMenuInfoMap[category].mActions << action; + } + // FIXME: Port + //plugin->actionCollection()->readShortcutSettings(); + + // If we reach this point, we just loaded one plugin. Go back to the + // event loop. We will come back to load the remaining plugins or create + // the menu later + QMetaObject::invokeMethod(this, "loadOnePlugin", Qt::QueuedConnection); + return; + } + + // If we reach this point, all plugins have been loaded. We can fill the + // menu + MenuInfoMap::Iterator + it = d->mMenuInfoMap.begin(), + end = d->mMenuInfoMap.end(); + for (; it != end; ++it) { + MenuInfo& info = it.value(); + if (!info.mActions.isEmpty()) { + QMenu* menu = d->mPluginMenu->addMenu(info.mName); + qSort(info.mActions.begin(), info.mActions.end(), actionLessThan); + Q_FOREACH(QAction * action, info.mActions) { + menu->addAction(action); + } + } + } + + d->mPluginMenu->removeAction(d->mLoadingAction); + if (d->mPluginMenu->isEmpty()) { + d->mPluginMenu->addAction(d->mNoPluginAction); + } + + loadingFinished(); +} + +QList KIPIInterface::pluginActions(KIPI::Category category) const +{ + const_cast(this)->loadPlugins(); + + if (isLoadingFinished()) { + QList list = d->mMenuInfoMap.value(category).mActions; + if (list.isEmpty()) { + list << d->mNoPluginAction; + } + return list; + } else { + return QList() << d->mLoadingAction; + } +} + +bool KIPIInterface::isLoadingFinished() const +{ + if (!d->mPluginLoader) { + // Not even started + return false; + } + return d->mPluginQueue.isEmpty(); +} + +void KIPIInterface::init() +{ + slotDirectoryChanged(); + slotSelectionChanged(); +} + +KIPI::ImageCollection KIPIInterface::currentAlbum() +{ + LOG(""); + const ContextManager* contextManager = d->mMainWindow->contextManager(); + const KUrl url = contextManager->currentDirUrl(); + const SortedDirModel* model = contextManager->dirModel(); + + KUrl::List list; + const int count = model->rowCount(); + for (int row = 0; row < count; ++row) { + const QModelIndex& index = model->index(row, 0); + const KFileItem item = model->itemForIndex(index); + if (MimeTypeUtils::fileItemKind(item) == MimeTypeUtils::KIND_RASTER_IMAGE) { + list << item.targetUrl(); + } + } + + return KIPI::ImageCollection(new ImageCollection(url, url.fileName(), list)); +} + +KIPI::ImageCollection KIPIInterface::currentSelection() +{ + LOG(""); + + KFileItemList fileList = d->mMainWindow->contextManager()->selectedFileItemList(); + KUrl::List list = fileList.urlList(); + KUrl url = d->mMainWindow->contextManager()->currentUrl(); + + return KIPI::ImageCollection(new ImageCollection(url, url.fileName(), list)); +} + +QList KIPIInterface::allAlbums() +{ + LOG(""); + QList list; + list << currentAlbum() << currentSelection(); + return list; +} + +KIPI::ImageInfo KIPIInterface::info(const KUrl& url) +{ + LOG(""); + return KIPI::ImageInfo(new KIPIImageInfo(this, url)); +} + +int KIPIInterface::features() const +{ + return KIPI::HostAcceptNewImages; +} + +/** + * KDirLister will pick up the image if necessary, so no updating is needed + * here, it is however necessary to discard caches if the plugin preserves timestamp + */ +bool KIPIInterface::addImage(const KUrl&, QString&) +{ +//TODO setContext(const KUrl& currentUrl, const KFileItemList& selection)? + //Cache::instance()->invalidate( url ); + return true; +} + +void KIPIInterface::delImage(const KUrl&) +{ +//TODO +} + +void KIPIInterface::refreshImages(const KUrl::List&) +{ +// TODO +} + +KIPI::ImageCollectionSelector* KIPIInterface::imageCollectionSelector(QWidget *parent) +{ + return new KIPIImageCollectionSelector(this, parent); +} + +KIPI::UploadWidget* KIPIInterface::uploadWidget(QWidget *parent) +{ + return (new KIPIUploadWidget(this, parent)); +} + +void KIPIInterface::slotSelectionChanged() +{ + emit selectionChanged(!d->mMainWindow->contextManager()->selectedFileItemList().isEmpty()); +} + +void KIPIInterface::slotDirectoryChanged() +{ + emit currentAlbumChanged(true); +} + +} //namespace diff --git a/gwenview/app/kipiinterface.h b/gwenview/app/kipiinterface.h new file mode 100644 index 00000000..45fb03ba --- /dev/null +++ b/gwenview/app/kipiinterface.h @@ -0,0 +1,119 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2000-2008 Aurélien Gâteau +Copyright 2008 Angelo Naselli + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef KIPIINTERFACE_H +#define KIPIINTERFACE_H + +// KIPI +#include +#include +#include + +class QAction; + +namespace Gwenview +{ + +struct KIPIInterfacePrivate; + +class MainWindow; + +class KIPIInterface : public KIPI::Interface +{ + Q_OBJECT + +public: + KIPIInterface(MainWindow*); + virtual ~KIPIInterface(); + + KIPI::ImageCollection currentAlbum(); + KIPI::ImageCollection currentSelection(); + QList allAlbums(); + KIPI::ImageInfo info(const KUrl& url); + int features() const; + virtual bool addImage(const KUrl&, QString& err); + virtual void delImage(const KUrl&); + virtual void refreshImages(const KUrl::List& urls); + + virtual KIPI::ImageCollectionSelector* imageCollectionSelector(QWidget *parent); + virtual KIPI::UploadWidget* uploadWidget(QWidget *parent); + + QList pluginActions(KIPI::Category) const; + + bool isLoadingFinished() const; + +Q_SIGNALS: + void loadingFinished(); + +public Q_SLOTS: + void loadPlugins(); + +private Q_SLOTS: + void slotSelectionChanged(); + void slotDirectoryChanged(); + void init(); + void loadOnePlugin(); + +private: + KIPIInterfacePrivate* const d; +}; + +class ImageCollection : public KIPI::ImageCollectionShared +{ +public: + ImageCollection(KUrl dirURL, const QString& name, const KUrl::List& images) + : KIPI::ImageCollectionShared() + , mDirURL(dirURL) + , mName(name) + , mImages(images) {} + + QString name() { + return mName; + } + QString comment() { + return QString(); + } + KUrl::List images() { + return mImages; + } + KUrl uploadRoot() { + return KUrl("/"); + } + KUrl uploadPath() { + return mDirURL; + } + QString uploadRootName() + { + return "/"; + } + bool isDirectory() { + return true; + } + +private: + KUrl mDirURL; + QString mName; + KUrl::List mImages; +}; + +} // namespace +#endif /* KIPIINTERFACE_H */ + diff --git a/gwenview/app/kipiuploadwidget.cpp b/gwenview/app/kipiuploadwidget.cpp new file mode 100644 index 00000000..55fec653 --- /dev/null +++ b/gwenview/app/kipiuploadwidget.cpp @@ -0,0 +1,56 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "kipiuploadwidget.moc" + +// Qt +#include +#include + +// KDE +#include + +// Local +#include "kipiinterface.h" + +namespace Gwenview +{ + +KIPIUploadWidget::KIPIUploadWidget(KIPIInterface* interface, QWidget* parent) +: KIPI::UploadWidget(parent) +, mInterface(interface) +{ + QLabel* label = new QLabel(this); + KUrl url = mInterface->currentAlbum().uploadPath(); + label->setText(i18n("Images will be uploaded here:\n%1", url.pathOrUrl())); + label->setWordWrap(true); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->addWidget(label); +} + +KIPI::ImageCollection KIPIUploadWidget::selectedImageCollection() const +{ + return mInterface->currentAlbum(); +} + +} // namespace diff --git a/gwenview/app/kipiuploadwidget.h b/gwenview/app/kipiuploadwidget.h new file mode 100644 index 00000000..59f4853e --- /dev/null +++ b/gwenview/app/kipiuploadwidget.h @@ -0,0 +1,53 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef KIPIUPLOADWIDGET_H +#define KIPIUPLOADWIDGET_H + +// Qt + +// KDE + +// KIPI +#include +#include + +// Local + +namespace Gwenview +{ + +class KIPIInterface; + +class KIPIUploadWidget : public KIPI::UploadWidget +{ + Q_OBJECT +public: + KIPIUploadWidget(KIPIInterface*, QWidget* parent); + + virtual KIPI::ImageCollection selectedImageCollection() const; + +private: + KIPIInterface* mInterface; +}; + +} // namespace + +#endif /* KIPIUPLOADWIDGET_H */ diff --git a/gwenview/app/main.cpp b/gwenview/app/main.cpp new file mode 100644 index 00000000..7d8d853d --- /dev/null +++ b/gwenview/app/main.cpp @@ -0,0 +1,146 @@ +/* +Gwenview: an image viewer +Copyright 2007-2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include +// STL +#include + +// Qt +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include +#include +#include "mainwindow.h" + +class StartHelper +{ +public: + StartHelper() + : mFullScreen(false) + , mSlideShow(false) + { + KCmdLineArgs* args = KCmdLineArgs::parsedArgs(); + if (args->count() > 0) { + parseArgs(args); + } + args->clear(); + } + + void parseArgs(KCmdLineArgs* args) + { + if (args->count() > 1) { + // Createa a temp dir containing links to url args + mMultipleUrlsDir.reset(new KTempDir); + mUrl = KUrl::fromPath(mMultipleUrlsDir->name()); + KUrl::List list; + for (int pos = 0; pos < args->count(); ++pos) { + list << args->url(pos); + } + KIO::CopyJob* job = KIO::link(list, mUrl); + job->exec(); + } else { + mUrl = args->url(0); + } + + if (mUrl.isValid() && (args->isSet("f") || args->isSet("s"))) { + mFullScreen = true; + if (args->isSet("s")) { + mSlideShow = true; + } + } + } + + void createMainWindow() + { + Gwenview::MainWindow* window = new Gwenview::MainWindow(); + if (mUrl.isValid()) { + window->setInitialUrl(mUrl); + } else { + window->showStartMainPage(); + } + + window->show(); + if (mFullScreen) { + window->actionCollection()->action("fullscreen")->trigger(); + } else { + window->show(); + } + + if (mSlideShow) { + window->startSlideShow(); + } + } + +private: + KUrl mUrl; + bool mFullScreen; + bool mSlideShow; + std::auto_ptr mMultipleUrlsDir; + std::auto_ptr mMainWindow; +}; + +int main(int argc, char *argv[]) +{ + QScopedPointer aboutData( + Gwenview::createAboutData( + "gwenview", /* appname */ + 0, /* catalogName */ + ki18n("Gwenview") /* programName */ + )); + aboutData->setShortDescription(ki18n("An Image Viewer")); + + KCmdLineArgs::init(argc, argv, aboutData.data()); + + KCmdLineOptions options; + options.add("f", ki18n("Start in fullscreen mode")); + options.add("s", ki18n("Start in slideshow mode")); + options.add("+[file or folder]", ki18n("A starting file or folder")); + KCmdLineArgs::addCmdLineOptions(options); + + KApplication app; + Gwenview::ImageFormats::registerPlugins(); + + // startHelper must live for the whole life of the application + StartHelper startHelper; + if (app.isSessionRestored()) { + kRestoreMainWindows(); + } else { + startHelper.createMainWindow(); + } + + // Workaround for QTBUG-38613 + // Another solution would be to port BalooSemanticInfoBackend::refreshAllTags + // to be async rather than using exec(). + qApp->sendPostedEvents(0, QEvent::DeferredDelete); + + return app.exec(); +} diff --git a/gwenview/app/mainwindow.cpp b/gwenview/app/mainwindow.cpp new file mode 100644 index 00000000..378971dd --- /dev/null +++ b/gwenview/app/mainwindow.cpp @@ -0,0 +1,1610 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "mainwindow.moc" +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "configdialog.h" +#include "documentinfoprovider.h" +#include "viewmainpage.h" +#include "fileopscontextmanageritem.h" +#include "folderviewcontextmanageritem.h" +#include "fullscreencontent.h" +#include "gvcore.h" +#include "imageopscontextmanageritem.h" +#include "infocontextmanageritem.h" +#ifdef KIPI_FOUND +#include "kipiexportaction.h" +#include "kipiinterface.h" +#endif +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE +#include "semanticinfocontextmanageritem.h" +#endif +#include "preloader.h" +#include "savebar.h" +#include "sidebar.h" +#include "splitter.h" +#include "startmainpage.h" +#include "thumbnailviewhelper.h" +#include "browsemainpage.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +static const int BROWSE_PRELOAD_DELAY = 1000; +static const int VIEW_PRELOAD_DELAY = 100; + +static const char* BROWSE_MODE_SIDE_BAR_GROUP = "SideBar-BrowseMode"; +static const char* VIEW_MODE_SIDE_BAR_GROUP = "SideBar-ViewMode"; +static const char* FULLSCREEN_MODE_SIDE_BAR_GROUP = "SideBar-FullScreenMode"; +static const char* SIDE_BAR_IS_VISIBLE_KEY = "IsVisible"; + +static const char* SESSION_CURRENT_PAGE_KEY = "Page"; +static const char* SESSION_URL_KEY = "Url"; + +enum MainPageId { + StartMainPageId, + BrowseMainPageId, + ViewMainPageId +}; + +struct MainWindowState +{ + bool mToolBarVisible; + Qt::WindowStates mWindowState; +}; + +/* + +Layout of the main window looks like this: + +.-mCentralSplitter-----------------------------. +|.-mSideBar--. .-mContentWidget---------------.| +|| | |.-mSaveBar-------------------.|| +|| | || ||| +|| | |'----------------------------'|| +|| | |.-mViewStackedWidget---------.|| +|| | || ||| +|| | || ||| +|| | || ||| +|| | || ||| +|| | |'----------------------------'|| +|'-----------' '------------------------------'| +'----------------------------------------------' + +*/ +struct MainWindow::Private +{ + GvCore* mGvCore; + MainWindow* q; + QSplitter* mCentralSplitter; + QWidget* mContentWidget; + ViewMainPage* mViewMainPage; + KUrlNavigator* mUrlNavigator; + ThumbnailView* mThumbnailView; + ThumbnailView* mActiveThumbnailView; + DocumentInfoProvider* mDocumentInfoProvider; + ThumbnailViewHelper* mThumbnailViewHelper; + QPointer mThumbnailProvider; + BrowseMainPage* mBrowseMainPage; + StartMainPage* mStartMainPage; + SideBar* mSideBar; + QStackedWidget* mViewStackedWidget; + FullScreenContent* mFullScreenContent; + SaveBar* mSaveBar; + bool mStartSlideShowWhenDirListerCompleted; + SlideShow* mSlideShow; + Preloader* mPreloader; + bool mPreloadDirectionIsForward; +#ifdef KIPI_FOUND + KIPIInterface* mKIPIInterface; +#endif + + QActionGroup* mViewModeActionGroup; + KRecentFilesAction* mFileOpenRecentAction; + KAction* mBrowseAction; + KAction* mViewAction; + KAction* mGoUpAction; + KAction* mGoToPreviousAction; + KAction* mGoToNextAction; + KAction* mGoToFirstAction; + KAction* mGoToLastAction; + KToggleAction* mToggleSideBarAction; + KToggleFullScreenAction* mFullScreenAction; + KAction* mToggleSlideShowAction; + KToggleAction* mShowMenuBarAction; +#ifdef KIPI_FOUND + KIPIExportAction* mKIPIExportAction; +#endif + + SortedDirModel* mDirModel; + DocumentOnlyProxyModel* mThumbnailBarModel; + KLinkItemSelectionModel* mThumbnailBarSelectionModel; + ContextManager* mContextManager; + + MainWindowState mStateBeforeFullScreen; + + QString mCaption; + + MainPageId mCurrentMainPageId; + + QDateTime mFullScreenLeftAt; + KNotificationRestrictions* mNotificationRestrictions; + + void setupContextManager() + { + mContextManager = new ContextManager(mDirModel, q); + connect(mContextManager, SIGNAL(selectionChanged()), + q, SLOT(slotSelectionChanged())); + connect(mContextManager, SIGNAL(currentDirUrlChanged(KUrl)), + q, SLOT(slotCurrentDirUrlChanged(KUrl))); + } + + void setupWidgets() + { + mFullScreenContent = new FullScreenContent(q); + connect(mContextManager, SIGNAL(currentUrlChanged(KUrl)), mFullScreenContent, SLOT(setCurrentUrl(KUrl))); + + mCentralSplitter = new Splitter(Qt::Horizontal, q); + q->setCentralWidget(mCentralSplitter); + + // Left side of splitter + mSideBar = new SideBar(mCentralSplitter); + EventWatcher::install(mSideBar, QList() << QEvent::Show << QEvent::Hide, + q, SLOT(updateToggleSideBarAction())); + + // Right side of splitter + mContentWidget = new QWidget(mCentralSplitter); + + mSaveBar = new SaveBar(mContentWidget, q->actionCollection()); + connect(mContextManager, SIGNAL(currentUrlChanged(KUrl)), mSaveBar, SLOT(setCurrentUrl(KUrl))); + mViewStackedWidget = new QStackedWidget(mContentWidget); + QVBoxLayout* layout = new QVBoxLayout(mContentWidget); + layout->addWidget(mSaveBar); + layout->addWidget(mViewStackedWidget); + layout->setMargin(0); + layout->setSpacing(0); + //// + + mStartSlideShowWhenDirListerCompleted = false; + mSlideShow = new SlideShow(q); + connect(mContextManager, SIGNAL(currentUrlChanged(KUrl)), mSlideShow, SLOT(setCurrentUrl(KUrl))); + + setupThumbnailView(mViewStackedWidget); + setupViewMainPage(mViewStackedWidget); + setupStartMainPage(mViewStackedWidget); + mViewStackedWidget->addWidget(mBrowseMainPage); + mViewStackedWidget->addWidget(mViewMainPage); + mViewStackedWidget->addWidget(mStartMainPage); + mViewStackedWidget->setCurrentWidget(mBrowseMainPage); + + mCentralSplitter->setStretchFactor(0, 0); + mCentralSplitter->setStretchFactor(1, 1); + + connect(mSaveBar, SIGNAL(requestSaveAll()), + mGvCore, SLOT(saveAll())); + connect(mSaveBar, SIGNAL(goToUrl(KUrl)), + q, SLOT(goToUrl(KUrl))); + + connect(mSlideShow, SIGNAL(goToUrl(KUrl)), + q, SLOT(goToUrl(KUrl))); + } + + void setupThumbnailView(QWidget* parent) + { + Q_ASSERT(mContextManager); + mBrowseMainPage = new BrowseMainPage(parent, q->actionCollection(), mGvCore); + + mThumbnailView = mBrowseMainPage->thumbnailView(); + mThumbnailView->setSelectionModel(mContextManager->selectionModel()); + mUrlNavigator = mBrowseMainPage->urlNavigator(); + + mDocumentInfoProvider = new DocumentInfoProvider(mDirModel); + mThumbnailView->setDocumentInfoProvider(mDocumentInfoProvider); + + mThumbnailViewHelper = new ThumbnailViewHelper(mDirModel, q->actionCollection()); + connect(mContextManager, SIGNAL(currentDirUrlChanged(KUrl)), + mThumbnailViewHelper, SLOT(setCurrentDirUrl(KUrl))); + mThumbnailView->setThumbnailViewHelper(mThumbnailViewHelper); + + mThumbnailBarSelectionModel = new KLinkItemSelectionModel(mThumbnailBarModel, mContextManager->selectionModel(), q); + + // Connect thumbnail view + connect(mThumbnailView, SIGNAL(indexActivated(QModelIndex)), + q, SLOT(slotThumbnailViewIndexActivated(QModelIndex))); + + // Connect delegate + QAbstractItemDelegate* delegate = mThumbnailView->itemDelegate(); + connect(delegate, SIGNAL(saveDocumentRequested(KUrl)), + mGvCore, SLOT(save(KUrl))); + connect(delegate, SIGNAL(rotateDocumentLeftRequested(KUrl)), + mGvCore, SLOT(rotateLeft(KUrl))); + connect(delegate, SIGNAL(rotateDocumentRightRequested(KUrl)), + mGvCore, SLOT(rotateRight(KUrl))); + connect(delegate, SIGNAL(showDocumentInFullScreenRequested(KUrl)), + q, SLOT(showDocumentInFullScreen(KUrl))); + connect(delegate, SIGNAL(setDocumentRatingRequested(KUrl,int)), + mGvCore, SLOT(setRating(KUrl,int))); + + // Connect url navigator + connect(mUrlNavigator, SIGNAL(urlChanged(KUrl)), + q, SLOT(openDirUrl(KUrl))); + } + + void setupViewMainPage(QWidget* parent) + { + mViewMainPage = new ViewMainPage(parent, mSlideShow, q->actionCollection(), mGvCore); + connect(mViewMainPage, SIGNAL(captionUpdateRequested(QString)), + q, SLOT(setCaption(QString))); + connect(mViewMainPage, SIGNAL(completed()), + q, SLOT(slotPartCompleted())); + connect(mViewMainPage, SIGNAL(previousImageRequested()), + q, SLOT(goToPrevious())); + connect(mViewMainPage, SIGNAL(nextImageRequested()), + q, SLOT(goToNext())); + + setupThumbnailBar(mViewMainPage->thumbnailBar()); + } + + void setupThumbnailBar(ThumbnailView* bar) + { + Q_ASSERT(mThumbnailBarModel); + Q_ASSERT(mThumbnailBarSelectionModel); + Q_ASSERT(mDocumentInfoProvider); + Q_ASSERT(mThumbnailViewHelper); + bar->setModel(mThumbnailBarModel); + bar->setSelectionModel(mThumbnailBarSelectionModel); + bar->setDocumentInfoProvider(mDocumentInfoProvider); + bar->setThumbnailViewHelper(mThumbnailViewHelper); + } + + void setupStartMainPage(QWidget* parent) + { + mStartMainPage = new StartMainPage(parent, mGvCore); + connect(mStartMainPage, SIGNAL(urlSelected(KUrl)), + q, SLOT(slotStartMainPageUrlSelected(KUrl))); + } + + void installDisabledActionShortcutMonitor(QAction* action, const char* slot) + { + DisabledActionShortcutMonitor* monitor = new DisabledActionShortcutMonitor(action, q); + connect(monitor, SIGNAL(activated()), q, slot); + } + + void setupActions() + { + KActionCollection* actionCollection = q->actionCollection(); + KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection); + KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection); + + file->addAction(KStandardAction::Save, q, SLOT(saveCurrent())); + file->addAction(KStandardAction::SaveAs, q, SLOT(saveCurrentAs())); + file->addAction(KStandardAction::Open, q, SLOT(openFile())); + mFileOpenRecentAction = KStandardAction::openRecent(q, SLOT(openUrl(KUrl)), q); + file->addAction("file_open_recent", mFileOpenRecentAction); + file->addAction(KStandardAction::Print, q, SLOT(print())); + file->addAction(KStandardAction::Quit, KApplication::kApplication(), SLOT(closeAllWindows())); + + KAction* action = file->addAction("reload", q, SLOT(reload())); + action->setText(i18nc("@action reload the currently viewed image", "Reload")); + action->setIcon(KIcon("view-refresh")); + action->setShortcut(Qt::Key_F5); + + mBrowseAction = view->addAction("browse"); + mBrowseAction->setText(i18nc("@action:intoolbar Switch to file list", "Browse")); + mBrowseAction->setToolTip(i18nc("@info:tooltip", "Browse folders for images")); + mBrowseAction->setCheckable(true); + mBrowseAction->setIcon(KIcon("view-list-icons")); + mBrowseAction->setShortcut(Qt::Key_Escape); + connect(mViewMainPage, SIGNAL(goToBrowseModeRequested()), + mBrowseAction, SLOT(trigger())); + + mViewAction = view->addAction("view"); + mViewAction->setText(i18nc("@action:intoolbar Switch to image view", "View")); + mViewAction->setToolTip(i18nc("@info:tooltip", "View selected images")); + mViewAction->setIcon(KIcon("view-preview")); + mViewAction->setCheckable(true); + + mViewModeActionGroup = new QActionGroup(q); + mViewModeActionGroup->addAction(mBrowseAction); + mViewModeActionGroup->addAction(mViewAction); + + connect(mViewModeActionGroup, SIGNAL(triggered(QAction*)), + q, SLOT(setActiveViewModeAction(QAction*))); + + mFullScreenAction = static_cast(view->addAction(KStandardAction::FullScreen, q, SLOT(toggleFullScreen(bool)))); + KShortcut shortcut = mFullScreenAction->shortcut(); + if (shortcut.alternate().isEmpty()) { + shortcut.setAlternate(Qt::Key_F11); + } + mFullScreenAction->setShortcut(shortcut); + connect(mViewMainPage, SIGNAL(toggleFullScreenRequested()), + mFullScreenAction, SLOT(trigger())); + + KAction* leaveFullScreenAction = view->addAction("leave_fullscreen", q, SLOT(leaveFullScreen())); + leaveFullScreenAction->setIcon(KIcon("view-restore")); + leaveFullScreenAction->setPriority(QAction::LowPriority); + leaveFullScreenAction->setText(i18nc("@action", "Leave Fullscreen Mode")); + + mGoToPreviousAction = view->addAction("go_previous", q, SLOT(goToPrevious())); + mGoToPreviousAction->setPriority(QAction::LowPriority); + mGoToPreviousAction->setIcon(KIcon("media-skip-backward")); + mGoToPreviousAction->setText(i18nc("@action Go to previous image", "Previous")); + mGoToPreviousAction->setToolTip(i18nc("@info:tooltip", "Go to previous image")); + mGoToPreviousAction->setShortcut(Qt::Key_Backspace); + installDisabledActionShortcutMonitor(mGoToPreviousAction, SLOT(showFirstDocumentReached())); + + mGoToNextAction = view->addAction("go_next", q, SLOT(goToNext())); + mGoToNextAction->setPriority(QAction::LowPriority); + mGoToNextAction->setIcon(KIcon("media-skip-forward")); + mGoToNextAction->setText(i18nc("@action Go to next image", "Next")); + mGoToNextAction->setToolTip(i18nc("@info:tooltip", "Go to next image")); + mGoToNextAction->setShortcut(Qt::Key_Space); + installDisabledActionShortcutMonitor(mGoToNextAction, SLOT(showLastDocumentReached())); + + mGoToFirstAction = view->addAction("go_first", q, SLOT(goToFirst())); + mGoToFirstAction->setPriority(QAction::LowPriority); + mGoToFirstAction->setText(i18nc("@action Go to first image", "First")); + mGoToFirstAction->setToolTip(i18nc("@info:tooltip", "Go to first image")); + mGoToFirstAction->setShortcut(Qt::Key_Home); + + mGoToLastAction = view->addAction("go_last", q, SLOT(goToLast())); + mGoToLastAction->setPriority(QAction::LowPriority); + mGoToLastAction->setText(i18nc("@action Go to last image", "Last")); + mGoToLastAction->setToolTip(i18nc("@info:tooltip", "Go to last image")); + mGoToLastAction->setShortcut(Qt::Key_End); + + mPreloadDirectionIsForward = true; + + mGoUpAction = view->addAction(KStandardAction::Up, q, SLOT(goUp())); + + action = view->addAction("go_start_page", q, SLOT(showStartMainPage())); + action->setPriority(QAction::LowPriority); + action->setIcon(KIcon("go-home")); + action->setText(i18nc("@action", "Start Page")); + action->setToolTip(i18nc("@info:tooltip", "Open the start page")); + + mToggleSideBarAction = view->add("toggle_sidebar"); + connect(mToggleSideBarAction, SIGNAL(toggled(bool)), + q, SLOT(toggleSideBar(bool))); + mToggleSideBarAction->setIcon(KIcon("view-sidetree")); + mToggleSideBarAction->setShortcut(Qt::Key_F4); + mToggleSideBarAction->setText(i18nc("@action", "Sidebar")); + connect(mBrowseMainPage->toggleSideBarButton(), SIGNAL(clicked()), + mToggleSideBarAction, SLOT(trigger())); + connect(mViewMainPage->toggleSideBarButton(), SIGNAL(clicked()), + mToggleSideBarAction, SLOT(trigger())); + + mToggleSlideShowAction = view->addAction("toggle_slideshow", q, SLOT(toggleSlideShow())); + q->updateSlideShowAction(); + connect(mSlideShow, SIGNAL(stateChanged(bool)), + q, SLOT(updateSlideShowAction())); + + mShowMenuBarAction = static_cast(view->addAction(KStandardAction::ShowMenubar, q, SLOT(toggleMenuBar()))); + + view->addAction(KStandardAction::KeyBindings, q->guiFactory(), + SLOT(configureShortcuts())); + + view->addAction(KStandardAction::Preferences, q, + SLOT(showConfigDialog())); + + view->addAction(KStandardAction::ConfigureToolbars, q, + SLOT(configureToolbars())); + +#ifdef KIPI_FOUND + mKIPIExportAction = new KIPIExportAction(q); + actionCollection->addAction("kipi_export", mKIPIExportAction); +#endif + } + + void setupUndoActions() + { + // There is no KUndoGroup similar to KUndoStack. This code basically + // does the same as KUndoStack, but for the KUndoGroup actions. + QUndoGroup* undoGroup = DocumentFactory::instance()->undoGroup(); + QAction* action; + KActionCollection* actionCollection = q->actionCollection(); + KActionCategory* edit = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "Edit"), actionCollection); + + action = undoGroup->createRedoAction(actionCollection); + action->setObjectName(KStandardAction::name(KStandardAction::Redo)); + action->setIcon(KIcon("edit-redo")); + action->setIconText(i18n("Redo")); + action->setShortcuts(KStandardShortcut::redo()); + edit->addAction(action->objectName(), action); + + action = undoGroup->createUndoAction(actionCollection); + action->setObjectName(KStandardAction::name(KStandardAction::Undo)); + action->setIcon(KIcon("edit-undo")); + action->setIconText(i18n("Undo")); + action->setShortcuts(KStandardShortcut::undo()); + edit->addAction(action->objectName(), action); + } + + void setupContextManagerItems() + { + Q_ASSERT(mContextManager); + KActionCollection* actionCollection = q->actionCollection(); + + // Create context manager items + FolderViewContextManagerItem* folderViewItem = new FolderViewContextManagerItem(mContextManager); + connect(folderViewItem, SIGNAL(urlChanged(KUrl)), + q, SLOT(openDirUrl(KUrl))); + + InfoContextManagerItem* infoItem = new InfoContextManagerItem(mContextManager); + +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + SemanticInfoContextManagerItem* semanticInfoItem = 0; + semanticInfoItem = new SemanticInfoContextManagerItem(mContextManager, actionCollection, mViewMainPage); +#endif + + ImageOpsContextManagerItem* imageOpsItem = + new ImageOpsContextManagerItem(mContextManager, q); + + FileOpsContextManagerItem* fileOpsItem = new FileOpsContextManagerItem(mContextManager, mThumbnailView, actionCollection, q); + + // Fill sidebar + SideBarPage* page; + page = new SideBarPage(i18n("Folders")); + page->setObjectName(QLatin1String("folders")); + page->addWidget(folderViewItem->widget()); + page->layout()->setMargin(0); + mSideBar->addPage(page); + + page = new SideBarPage(i18n("Information")); + page->setObjectName(QLatin1String("information")); + page->addWidget(infoItem->widget()); +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + if (semanticInfoItem) { + page->addWidget(semanticInfoItem->widget()); + } +#endif + mSideBar->addPage(page); + + page = new SideBarPage(i18n("Operations")); + page->setObjectName(QLatin1String("operations")); + page->addWidget(imageOpsItem->widget()); + page->addWidget(fileOpsItem->widget()); + page->addStretch(); + mSideBar->addPage(page); + } + + void initDirModel() + { + mDirModel->setKindFilter( + MimeTypeUtils::KIND_DIR + | MimeTypeUtils::KIND_ARCHIVE + | MimeTypeUtils::KIND_RASTER_IMAGE + | MimeTypeUtils::KIND_SVG_IMAGE + | MimeTypeUtils::KIND_VIDEO); + + connect(mDirModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + q, SLOT(slotDirModelNewItems())); + + connect(mDirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + q, SLOT(updatePreviousNextActions())); + connect(mDirModel, SIGNAL(modelReset()), + q, SLOT(updatePreviousNextActions())); + + connect(mDirModel->dirLister(), SIGNAL(completed()), + q, SLOT(slotDirListerCompleted())); + } + + void setupThumbnailBarModel() + { + mThumbnailBarModel = new DocumentOnlyProxyModel(q); + mThumbnailBarModel->setSourceModel(mDirModel); + } + + bool indexIsDirOrArchive(const QModelIndex& index) const + { + Q_ASSERT(index.isValid()); + KFileItem item = mDirModel->itemForIndex(index); + return ArchiveUtils::fileItemIsDirOrArchive(item); + } + + void goTo(const QModelIndex& index) + { + if (!index.isValid()) { + return; + } + mThumbnailView->setCurrentIndex(index); + mThumbnailView->scrollTo(index); + } + + void goTo(int offset) + { + mPreloadDirectionIsForward = offset > 0; + QModelIndex index = mContextManager->selectionModel()->currentIndex(); + index = mDirModel->index(index.row() + offset, 0); + if (index.isValid() && !indexIsDirOrArchive(index)) { + goTo(index); + } + } + + void goToFirstDocument() + { + QModelIndex index; + for (int row = 0;; ++row) { + index = mDirModel->index(row, 0); + if (!index.isValid()) { + return; + } + + if (!indexIsDirOrArchive(index)) { + break; + } + } + goTo(index); + } + + void goToLastDocument() + { + QModelIndex index = mDirModel->index(mDirModel->rowCount() - 1, 0); + goTo(index); + } + + void setupFullScreenContent() + { + mFullScreenContent->init(q->actionCollection(), mViewMainPage, mSlideShow); + setupThumbnailBar(mFullScreenContent->thumbnailBar()); + } + + inline void setActionEnabled(const char* name, bool enabled) + { + QAction* action = q->actionCollection()->action(name); + if (action) { + action->setEnabled(enabled); + } else { + kWarning() << "Action" << name << "not found"; + } + } + + void setActionsDisabledOnStartMainPageEnabled(bool enabled) + { + mBrowseAction->setEnabled(enabled); + mViewAction->setEnabled(enabled); + mToggleSideBarAction->setEnabled(enabled); + mFullScreenAction->setEnabled(enabled); + mToggleSlideShowAction->setEnabled(enabled); + + setActionEnabled("reload", enabled); + setActionEnabled("go_start_page", enabled); + setActionEnabled("add_folder_to_places", enabled); + } + + void updateActions() + { + bool isRasterImage = false; + bool canSave = false; + bool isModified = false; + const KUrl url = mContextManager->currentUrl(); + + if (url.isValid()) { + isRasterImage = mContextManager->currentUrlIsRasterImage(); + canSave = isRasterImage; + isModified = DocumentFactory::instance()->load(url)->isModified(); + if (mCurrentMainPageId != ViewMainPageId) { + // Saving only makes sense if exactly one image is selected + QItemSelection selection = mThumbnailView->selectionModel()->selection(); + QModelIndexList indexList = selection.indexes(); + if (indexList.count() != 1) { + canSave = false; + } + } + } + + KActionCollection* actionCollection = q->actionCollection(); + actionCollection->action("file_save")->setEnabled(canSave && isModified); + actionCollection->action("file_save_as")->setEnabled(canSave); + actionCollection->action("file_print")->setEnabled(isRasterImage); + } + + const char* sideBarConfigGroupName() const + { + const char* name = 0; + switch (mCurrentMainPageId) { + case StartMainPageId: + GV_WARN_AND_RETURN_VALUE(BROWSE_MODE_SIDE_BAR_GROUP, "mCurrentMainPageId == 'StartMainPageId'"); + break; + case BrowseMainPageId: + name = BROWSE_MODE_SIDE_BAR_GROUP; + break; + case ViewMainPageId: + name = mViewMainPage->isFullScreenMode() + ? FULLSCREEN_MODE_SIDE_BAR_GROUP + : VIEW_MODE_SIDE_BAR_GROUP; + break; + } + return name; + } + + void loadSideBarConfig() + { + static QMap defaultVisibility; + if (defaultVisibility.isEmpty()) { + defaultVisibility[BROWSE_MODE_SIDE_BAR_GROUP] = true; + defaultVisibility[VIEW_MODE_SIDE_BAR_GROUP] = true; + defaultVisibility[FULLSCREEN_MODE_SIDE_BAR_GROUP] = false; + } + + const char* name = sideBarConfigGroupName(); + KConfigGroup group(KGlobal::config(), name); + mSideBar->setVisible(group.readEntry(SIDE_BAR_IS_VISIBLE_KEY, defaultVisibility[name])); + mSideBar->setCurrentPage(GwenviewConfig::sideBarPage()); + q->updateToggleSideBarAction(); + } + + void saveSideBarConfig() const + { + KConfigGroup group(KGlobal::config(), sideBarConfigGroupName()); + group.writeEntry(SIDE_BAR_IS_VISIBLE_KEY, mSideBar->isVisible()); + GwenviewConfig::setSideBarPage(mSideBar->currentPage()); + } + + void setScreenSaverEnabled(bool enabled) + { + // Always delete mNotificationRestrictions, it does not hurt + delete mNotificationRestrictions; + if (!enabled) { + mNotificationRestrictions = new KNotificationRestrictions(KNotificationRestrictions::ScreenSaver, q); + } else { + mNotificationRestrictions = 0; + } + } + + void assignThumbnailProviderToThumbnailView(ThumbnailView* thumbnailView) + { + GV_RETURN_IF_FAIL(thumbnailView); + if (mActiveThumbnailView) { + mActiveThumbnailView->setThumbnailProvider(0); + } + thumbnailView->setThumbnailProvider(mThumbnailProvider); + mActiveThumbnailView = thumbnailView; + if (mActiveThumbnailView->isVisible()) { + mThumbnailProvider->stop(); + mActiveThumbnailView->generateThumbnailsForItems(); + } + } + + void autoAssignThumbnailProvider() + { + if (mCurrentMainPageId == ViewMainPageId) { + if (q->windowState() & Qt::WindowFullScreen) { + assignThumbnailProviderToThumbnailView(mFullScreenContent->thumbnailBar()); + } else { + assignThumbnailProviderToThumbnailView(mViewMainPage->thumbnailBar()); + } + } else if (mCurrentMainPageId == BrowseMainPageId) { + assignThumbnailProviderToThumbnailView(mThumbnailView); + } else if (mCurrentMainPageId == StartMainPageId) { + assignThumbnailProviderToThumbnailView(mStartMainPage->recentFoldersView()); + } + } +}; + +MainWindow::MainWindow() +: KXmlGuiWindow(), + d(new MainWindow::Private) +{ + d->q = this; + d->mCurrentMainPageId = StartMainPageId; + d->mDirModel = new SortedDirModel(this); + d->setupContextManager(); + d->setupThumbnailBarModel(); + d->mGvCore = new GvCore(this, d->mDirModel); + d->mPreloader = new Preloader(this); + d->mNotificationRestrictions = 0; + d->mThumbnailProvider = new ThumbnailProvider(); + d->mActiveThumbnailView = 0; + d->initDirModel(); + d->setupWidgets(); + d->setupActions(); + d->setupUndoActions(); + d->setupContextManagerItems(); + d->setupFullScreenContent(); + d->updateActions(); + updatePreviousNextActions(); + d->mSaveBar->initActionDependentWidgets(); + + createGUI(); + loadConfig(); + + connect(DocumentFactory::instance(), SIGNAL(modifiedDocumentListChanged()), + SLOT(slotModifiedDocumentListChanged())); + +#ifdef KIPI_FOUND + d->mKIPIInterface = new KIPIInterface(this); + d->mKIPIExportAction->setKIPIInterface(d->mKIPIInterface); +#endif + setAutoSaveSettings(); +} + +MainWindow::~MainWindow() +{ + if (GwenviewConfig::deleteThumbnailCacheOnExit()) { + const QString dir = ThumbnailProvider::thumbnailBaseDir(); + if (QFile::exists(dir)) { + KIO::NetAccess::del(KUrl::fromPath(dir), this); + } + } + delete d->mThumbnailProvider; + delete d; +} + +ContextManager* MainWindow::contextManager() const +{ + return d->mContextManager; +} + +ViewMainPage* MainWindow::viewMainPage() const +{ + return d->mViewMainPage; +} + +void MainWindow::setCaption(const QString& caption) +{ + // Keep a trace of caption to use it in slotModifiedDocumentListChanged() + d->mCaption = caption; + KXmlGuiWindow::setCaption(caption); +} + +void MainWindow::setCaption(const QString& caption, bool modified) +{ + d->mCaption = caption; + KXmlGuiWindow::setCaption(caption, modified); +} + +void MainWindow::slotModifiedDocumentListChanged() +{ + d->updateActions(); + + // Update caption + QList list = DocumentFactory::instance()->modifiedDocumentList(); + bool modified = list.count() > 0; + setCaption(d->mCaption, modified); +} + +void MainWindow::setInitialUrl(const KUrl& _url) +{ + Q_ASSERT(_url.isValid()); + KUrl url = UrlUtils::fixUserEnteredUrl(_url); + if (url.protocol() == "http" || url.protocol() == "https") { + d->mGvCore->addUrlToRecentUrls(url); + } + if (UrlUtils::urlIsDirectory(url)) { + d->mBrowseAction->trigger(); + openDirUrl(url); + } else { + d->mViewAction->trigger(); + d->mViewMainPage->openUrl(url); + d->mContextManager->setUrlToSelect(url); + } +} + +void MainWindow::startSlideShow() +{ + d->mViewAction->trigger(); + // We need to wait until we have listed all images in the dirlister to + // start the slideshow because the SlideShow objects needs an image list to + // work. + d->mStartSlideShowWhenDirListerCompleted = true; +} + +void MainWindow::setActiveViewModeAction(QAction* action) +{ + if (d->mCurrentMainPageId != StartMainPageId) { + d->saveSideBarConfig(); + } + if (action == d->mViewAction) { + d->mCurrentMainPageId = ViewMainPageId; + // Switching to view mode + d->mViewStackedWidget->setCurrentWidget(d->mViewMainPage); + if (d->mViewMainPage->isEmpty()) { + openSelectedDocuments(); + } + d->mPreloadDirectionIsForward = true; + QTimer::singleShot(VIEW_PRELOAD_DELAY, this, SLOT(preloadNextUrl())); + } else { + d->mCurrentMainPageId = BrowseMainPageId; + // Switching to browse mode + d->mViewStackedWidget->setCurrentWidget(d->mBrowseMainPage); + if (!d->mViewMainPage->isEmpty() + && KProtocolManager::supportsListing(d->mViewMainPage->url())) { + // Reset the view to spare resources, but don't do it if we can't + // browse the url, otherwise if the user starts Gwenview this way: + // gwenview http://example.com/example.png + // and switch to browse mode, switching back to view mode won't bring + // his image back. + d->mViewMainPage->reset(); + } + setCaption(QString()); + } + d->loadSideBarConfig(); + d->autoAssignThumbnailProvider(); + + emit viewModeChanged(); +} + +void MainWindow::slotThumbnailViewIndexActivated(const QModelIndex& index) +{ + if (!index.isValid()) { + return; + } + + KFileItem item = d->mDirModel->itemForIndex(index); + if (item.isDir()) { + // Item is a dir, open it + openDirUrl(item.url()); + } else { + QString protocol = ArchiveUtils::protocolForMimeType(item.mimetype()); + if (!protocol.isEmpty()) { + // Item is an archive, tweak url then open it + KUrl url = item.url(); + url.setProtocol(protocol); + openDirUrl(url); + } else { + // Item is a document, switch to view mode + d->mViewAction->trigger(); + } + } +} + +static bool indexRowLessThan(const QModelIndex& i1, const QModelIndex& i2) +{ + return i1.row() < i2.row(); +} + +void MainWindow::openSelectedDocuments() +{ + if (d->mCurrentMainPageId != ViewMainPageId) { + return; + } + + QModelIndex currentIndex = d->mThumbnailView->currentIndex(); + if (!currentIndex.isValid()) { + return; + } + + int count = 0; + + KUrl::List urls; + KUrl currentUrl; + QModelIndex firstDocumentIndex; + QModelIndexList list = d->mThumbnailView->selectionModel()->selectedIndexes(); + // Make 'list' follow the same order as 'mThumbnailView' + qSort(list.begin(), list.end(), indexRowLessThan); + + Q_FOREACH(const QModelIndex& index, list) { + KFileItem item = d->mDirModel->itemForIndex(index); + if (!item.isNull() && !ArchiveUtils::fileItemIsDirOrArchive(item)) { + KUrl url = item.url(); + if (!firstDocumentIndex.isValid()) { + firstDocumentIndex = index; + } + urls << url; + if (index == currentIndex) { + currentUrl = url; + } + ++count; + if (count == ViewMainPage::MaxViewCount) { + break; + } + } + } + if (urls.isEmpty()) { + // No image to display + return; + } + if (currentUrl.isEmpty()) { + // Current index is not selected, or it is not a document: set + // firstDocumentIndex as current + GV_RETURN_IF_FAIL(firstDocumentIndex.isValid()); + d->mThumbnailView->selectionModel()->setCurrentIndex(firstDocumentIndex, QItemSelectionModel::Current); + currentUrl = urls.first(); + } + + d->mViewMainPage->openUrls(urls, currentUrl); +} + +void MainWindow::goUp() +{ + if (d->mCurrentMainPageId == BrowseMainPageId) { + KUrl url = d->mContextManager->currentDirUrl(); + url = url.upUrl(); + openDirUrl(url); + } else { + d->mBrowseAction->trigger(); + } +} + +void MainWindow::showStartMainPage() +{ + if (d->mCurrentMainPageId != StartMainPageId) { + d->saveSideBarConfig(); + d->mCurrentMainPageId = StartMainPageId; + } + d->setActionsDisabledOnStartMainPageEnabled(false); + + d->mSideBar->hide(); + d->mViewStackedWidget->setCurrentWidget(d->mStartMainPage); + + d->updateActions(); + updatePreviousNextActions(); + d->mContextManager->setCurrentDirUrl(KUrl()); + d->mContextManager->setCurrentUrl(KUrl()); + + d->autoAssignThumbnailProvider(); +} + +void MainWindow::slotStartMainPageUrlSelected(const KUrl& url) +{ + d->setActionsDisabledOnStartMainPageEnabled(true); + + if (d->mBrowseAction->isChecked()) { + // Silently uncheck the action so that setInitialUrl() does the right thing + SignalBlocker blocker(d->mBrowseAction); + d->mBrowseAction->setChecked(false); + } + + setInitialUrl(url); +} + +void MainWindow::openDirUrl(const KUrl& url) +{ + const KUrl currentUrl = d->mContextManager->currentDirUrl(); + + if (url.equals(currentUrl, KUrl::CompareWithoutTrailingSlash)) { + return; + } + + if (url.isParentOf(currentUrl)) { + // Keep first child between url and currentUrl selected + // If currentPath is "/home/user/photos/2008/event" + // and wantedPath is "/home/user/photos" + // pathToSelect should be "/home/user/photos/2008" + + // To anyone considering using KUrl::toLocalFile() instead of + // KUrl::path() here. Please don't, using KUrl::path() is the right + // thing to do here. + const QString currentPath = QDir::cleanPath(currentUrl.path(KUrl::RemoveTrailingSlash)); + const QString wantedPath = QDir::cleanPath(url.path(KUrl::RemoveTrailingSlash)); + const QChar separator('/'); + const int slashCount = wantedPath.count(separator); + const QString pathToSelect = currentPath.section(separator, 0, slashCount + 1); + KUrl urlToSelect = url; + urlToSelect.setPath(pathToSelect); + d->mContextManager->setUrlToSelect(urlToSelect); + } + d->mThumbnailProvider->stop(); + d->mContextManager->setCurrentDirUrl(url); + d->mGvCore->addUrlToRecentFolders(url); + d->mViewMainPage->reset(); +} + +void MainWindow::toggleSideBar(bool on) +{ + d->mSideBar->setVisible(on); +} + +void MainWindow::updateToggleSideBarAction() +{ + SignalBlocker blocker(d->mToggleSideBarAction); + bool visible = d->mSideBar->isVisible(); + d->mToggleSideBarAction->setChecked(visible); + + QString text; + if (QApplication::isRightToLeft()) { + text = QString::fromUtf8(visible ? "▮→" : "▮←"); + } else { + text = QString::fromUtf8(visible ? "▮←" : "▮→"); + } + QString toolTip = visible ? i18nc("@info:tooltip", "Hide sidebar") : i18nc("@info:tooltip", "Show sidebar"); + + QList lst; + lst << d->mBrowseMainPage->toggleSideBarButton() + << d->mViewMainPage->toggleSideBarButton(); + Q_FOREACH(QToolButton * button, lst) { + button->setText(text); + button->setToolTip(toolTip); + } +} + +void MainWindow::slotPartCompleted() +{ + d->updateActions(); + KUrl url = d->mViewMainPage->url(); + // remember the opened file + if (!url.isEmpty()) { + d->mFileOpenRecentAction->addUrl(url); + } + if (!KProtocolManager::supportsListing(url)) { + return; + } + + KUrl dirUrl = url; + dirUrl.setFileName(QString()); + if (dirUrl.equals(d->mContextManager->currentDirUrl(), KUrl::CompareWithoutTrailingSlash)) { + QModelIndex index = d->mDirModel->indexForUrl(url); + QItemSelectionModel* selectionModel = d->mThumbnailView->selectionModel(); + if (index.isValid() && !selectionModel->isSelected(index)) { + // FIXME: QGV Reactivating this line prevents navigation to prev/next image + //selectionModel->select(index, QItemSelectionModel::SelectCurrent); + } + } else { + d->mContextManager->setCurrentDirUrl(dirUrl); + d->mGvCore->addUrlToRecentFolders(dirUrl); + } +} + +void MainWindow::slotSelectionChanged() +{ + if (d->mCurrentMainPageId == ViewMainPageId) { + // The user selected a new file in the thumbnail view, since the + // document view is visible, let's show it + openSelectedDocuments(); + } else { + // No document view, we need to load the document to set the undo group + // of document factory to the correct QUndoStack + QModelIndex index = d->mThumbnailView->currentIndex(); + KFileItem item; + if (index.isValid()) { + item = d->mDirModel->itemForIndex(index); + } + QUndoGroup* undoGroup = DocumentFactory::instance()->undoGroup(); + if (!item.isNull() && !ArchiveUtils::fileItemIsDirOrArchive(item)) { + KUrl url = item.url(); + Document::Ptr doc = DocumentFactory::instance()->load(url); + undoGroup->addStack(doc->undoStack()); + undoGroup->setActiveStack(doc->undoStack()); + } else { + undoGroup->setActiveStack(0); + } + } + + // Update UI + d->updateActions(); + updatePreviousNextActions(); + + // Start preloading + int preloadDelay = d->mCurrentMainPageId == ViewMainPageId ? VIEW_PRELOAD_DELAY : BROWSE_PRELOAD_DELAY; + QTimer::singleShot(preloadDelay, this, SLOT(preloadNextUrl())); +} + +void MainWindow::slotCurrentDirUrlChanged(const KUrl& url) +{ + if (url.isValid()) { + d->mUrlNavigator->setLocationUrl(url); + d->mGoUpAction->setEnabled(url.path() != "/"); + } else { + d->mGoUpAction->setEnabled(false); + } +} + +void MainWindow::slotDirModelNewItems() +{ + if (d->mThumbnailView->selectionModel()->hasSelection()) { + updatePreviousNextActions(); + } +} + +void MainWindow::slotDirListerCompleted() +{ + if (d->mStartSlideShowWhenDirListerCompleted) { + d->mStartSlideShowWhenDirListerCompleted = false; + QTimer::singleShot(0, d->mToggleSlideShowAction, SLOT(trigger())); + } + if (d->mThumbnailView->selectionModel()->hasSelection()) { + updatePreviousNextActions(); + } else if (!d->mContextManager->urlToSelect().isValid()) { + d->goToFirstDocument(); + } + d->mThumbnailView->scrollToSelectedIndex(); + d->mViewMainPage->thumbnailBar()->scrollToSelectedIndex(); +} + +void MainWindow::goToPrevious() +{ + d->goTo(-1); +} + +void MainWindow::goToNext() +{ + d->goTo(1); +} + +void MainWindow::goToFirst() +{ + d->goToFirstDocument(); +} + +void MainWindow::goToLast() +{ + d->goToLastDocument(); +} + +void MainWindow::goToUrl(const KUrl& url) +{ + if (d->mCurrentMainPageId == ViewMainPageId) { + d->mViewMainPage->openUrl(url); + } + KUrl dirUrl = url; + dirUrl.setFileName(""); + if (!dirUrl.equals(d->mContextManager->currentDirUrl(), KUrl::CompareWithoutTrailingSlash)) { + d->mContextManager->setCurrentDirUrl(dirUrl); + d->mGvCore->addUrlToRecentFolders(dirUrl); + } + d->mContextManager->setUrlToSelect(url); +} + +void MainWindow::updatePreviousNextActions() +{ + bool hasPrevious; + bool hasNext; + QModelIndex currentIndex = d->mContextManager->selectionModel()->currentIndex(); + if (currentIndex.isValid() && !d->indexIsDirOrArchive(currentIndex)) { + int row = currentIndex.row(); + QModelIndex prevIndex = d->mDirModel->index(row - 1, 0); + QModelIndex nextIndex = d->mDirModel->index(row + 1, 0); + hasPrevious = prevIndex.isValid() && !d->indexIsDirOrArchive(prevIndex); + hasNext = nextIndex.isValid() && !d->indexIsDirOrArchive(nextIndex); + } else { + hasPrevious = false; + hasNext = false; + } + + d->mGoToPreviousAction->setEnabled(hasPrevious); + d->mGoToNextAction->setEnabled(hasNext); + d->mGoToFirstAction->setEnabled(hasPrevious); + d->mGoToLastAction->setEnabled(hasNext); +} + +void MainWindow::leaveFullScreen() +{ + if (d->mFullScreenAction->isChecked()) { + d->mFullScreenAction->trigger(); + } +} + +void MainWindow::toggleFullScreen(bool checked) +{ + setUpdatesEnabled(false); + d->saveSideBarConfig(); + if (checked) { + // Save MainWindow config now, this way if we quit while in + // fullscreen, we are sure latest MainWindow changes are remembered. + saveMainWindowSettings(autoSaveConfigGroup()); + resetAutoSaveSettings(); + + // Save state + d->mStateBeforeFullScreen.mToolBarVisible = toolBar()->isVisible(); + d->mStateBeforeFullScreen.mWindowState = windowState(); + + // Go full screen + setWindowState(windowState() | Qt::WindowFullScreen); + menuBar()->hide(); + toolBar()->hide(); + + QApplication::setPalette(d->mGvCore->palette(GvCore::FullScreenPalette)); + d->mFullScreenContent->setFullScreenMode(true); + d->mBrowseMainPage->setFullScreenMode(true); + d->mViewMainPage->setFullScreenMode(true); + d->mSaveBar->setFullScreenMode(true); + d->setScreenSaverEnabled(false); + + // HACK: Only load sidebar config now, because it looks at + // ViewMainPage fullScreenMode property to determine the sidebar + // config group. + d->loadSideBarConfig(); + } else { + setAutoSaveSettings(); + + // Back to normal + QApplication::setPalette(d->mGvCore->palette(GvCore::NormalPalette)); + d->mFullScreenContent->setFullScreenMode(false); + d->mBrowseMainPage->setFullScreenMode(false); + d->mViewMainPage->setFullScreenMode(false); + d->mSlideShow->stop(); + d->mSaveBar->setFullScreenMode(false); + setWindowState(d->mStateBeforeFullScreen.mWindowState); + menuBar()->setVisible(d->mShowMenuBarAction->isChecked()); + toolBar()->setVisible(d->mStateBeforeFullScreen.mToolBarVisible); + + d->setScreenSaverEnabled(true); + + // Keep this after mViewMainPage->setFullScreenMode(false). + // See call to loadSideBarConfig() above. + d->loadSideBarConfig(); + + // See resizeEvent + d->mFullScreenLeftAt = QDateTime::currentDateTime(); + } + setUpdatesEnabled(true); + d->autoAssignThumbnailProvider(); +} + +void MainWindow::saveCurrent() +{ + d->mGvCore->save(d->mContextManager->currentUrl()); +} + +void MainWindow::saveCurrentAs() +{ + d->mGvCore->saveAs(d->mContextManager->currentUrl()); +} + +void MainWindow::reload() +{ + if (d->mCurrentMainPageId == ViewMainPageId) { + d->mViewMainPage->reload(); + } else { + d->mBrowseMainPage->reload(); + } +} + +void MainWindow::openFile() +{ + KUrl dirUrl = d->mContextManager->currentDirUrl(); + + KFileDialog dialog(dirUrl, QString(), this); + dialog.setCaption(i18nc("@title:window", "Open Image")); + const QStringList mimeFilter = MimeTypeUtils::imageMimeTypes(); + dialog.setMimeFilter(mimeFilter); + dialog.setOperationMode(KFileDialog::Opening); + if (!dialog.exec()) { + return; + } + + openUrl(dialog.selectedUrl()); +} + +void MainWindow::openUrl(const KUrl& url) +{ + d->setActionsDisabledOnStartMainPageEnabled(true); + d->mViewAction->trigger(); + d->mViewMainPage->openUrl(url); + d->mContextManager->setUrlToSelect(url); +} + +void MainWindow::showDocumentInFullScreen(const KUrl& url) +{ + d->mViewMainPage->openUrl(url); + d->mContextManager->setUrlToSelect(url); + d->mFullScreenAction->trigger(); +} + +void MainWindow::toggleSlideShow() +{ + if (d->mSlideShow->isRunning()) { + d->mSlideShow->stop(); + } else { + if (!d->mViewAction->isChecked()) { + d->mViewAction->trigger(); + } + if (!d->mFullScreenAction->isChecked()) { + d->mFullScreenAction->trigger(); + } + QList list; + for (int pos = 0; pos < d->mDirModel->rowCount(); ++pos) { + QModelIndex index = d->mDirModel->index(pos, 0); + KFileItem item = d->mDirModel->itemForIndex(index); + MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item); + switch (kind) { + case MimeTypeUtils::KIND_SVG_IMAGE: + case MimeTypeUtils::KIND_RASTER_IMAGE: + case MimeTypeUtils::KIND_VIDEO: + list << item.url(); + break; + default: + break; + } + } + d->mSlideShow->start(list); + } + updateSlideShowAction(); +} + +void MainWindow::updateSlideShowAction() +{ + if (d->mSlideShow->isRunning()) { + d->mToggleSlideShowAction->setText(i18n("Stop Slideshow")); + d->mToggleSlideShowAction->setIcon(KIcon("media-playback-pause")); + } else { + d->mToggleSlideShowAction->setText(i18n("Start Slideshow")); + d->mToggleSlideShowAction->setIcon(KIcon("media-playback-start")); + } +} + +bool MainWindow::queryClose() +{ + saveConfig(); + d->saveSideBarConfig(); + QList list = DocumentFactory::instance()->modifiedDocumentList(); + if (list.size() == 0) { + return true; + } + + KGuiItem yes(i18n("Save All Changes"), "document-save-all"); + KGuiItem no(i18n("Discard Changes")); + QString msg = i18np("One image has been modified.", "%1 images have been modified.", list.size()) + + '\n' + + i18n("If you quit now, your changes will be lost."); + int answer = KMessageBox::warningYesNoCancel( + this, + msg, + QString() /* caption */, + yes, + no); + + switch (answer) { + case KMessageBox::Yes: + d->mGvCore->saveAll(); + // We need to wait a bit because the DocumentFactory is notified about + // saved documents through a queued connection. + qApp->processEvents(); + return DocumentFactory::instance()->modifiedDocumentList().isEmpty(); + + case KMessageBox::No: + return true; + + default: // cancel + return false; + } +} + +void MainWindow::showConfigDialog() +{ + ConfigDialog dialog(this); + connect(&dialog, SIGNAL(settingsChanged(QString)), SLOT(loadConfig())); + dialog.exec(); +} + +void MainWindow::toggleMenuBar() +{ + if (!d->mFullScreenAction->isChecked()) { + menuBar()->setVisible(d->mShowMenuBarAction->isChecked()); + } +} + +void MainWindow::loadConfig() +{ + d->mDirModel->setBlackListedExtensions(GwenviewConfig::blackListedExtensions()); + d->mDirModel->adjustKindFilter(MimeTypeUtils::KIND_VIDEO, GwenviewConfig::listVideos()); + + d->mFileOpenRecentAction->loadEntries(KConfigGroup(KGlobal::config(), "Recent Files")); + d->mStartMainPage->loadConfig(); + d->mViewMainPage->loadConfig(); + d->mBrowseMainPage->loadConfig(); +} + +void MainWindow::saveConfig() +{ + d->mFileOpenRecentAction->saveEntries(KConfigGroup(KGlobal::config(), "Recent Files")); + d->mViewMainPage->saveConfig(); + d->mBrowseMainPage->saveConfig(); +} + +void MainWindow::print() +{ + if (!d->mContextManager->currentUrlIsRasterImage()) { + return; + } + + Document::Ptr doc = DocumentFactory::instance()->load(d->mContextManager->currentUrl()); + PrintHelper printHelper(this); + printHelper.print(doc); +} + +void MainWindow::preloadNextUrl() +{ + static bool disablePreload = qgetenv("GV_MAX_UNREFERENCED_IMAGES") == "0"; + if (disablePreload) { + kDebug() << "Preloading disabled"; + return; + } + QItemSelection selection = d->mThumbnailView->selectionModel()->selection(); + if (selection.size() != 1) { + return; + } + + QModelIndexList indexList = selection.indexes(); + if (indexList.isEmpty()) { + return; + } + + QModelIndex index = indexList.at(0); + if (!index.isValid()) { + return; + } + + if (d->mCurrentMainPageId == ViewMainPageId) { + // If we are in view mode, preload the next url, otherwise preload the + // selected one + int offset = d->mPreloadDirectionIsForward ? 1 : -1; + index = d->mDirModel->sibling(index.row() + offset, index.column(), index); + if (!index.isValid()) { + return; + } + } + + KFileItem item = d->mDirModel->itemForIndex(index); + if (!ArchiveUtils::fileItemIsDirOrArchive(item)) { + KUrl url = item.url(); + if (url.isLocalFile()) { + QSize size = d->mViewStackedWidget->size(); + d->mPreloader->preload(url, size); + } + } +} + +QSize MainWindow::sizeHint() const +{ + return KXmlGuiWindow::sizeHint().expandedTo(QSize(750, 500)); +} + +void MainWindow::showEvent(QShowEvent *event) +{ + // We need to delay initializing the action state until the menu bar has + // been initialized, that's why it's done only in the showEvent() + d->mShowMenuBarAction->setChecked(menuBar()->isVisible()); + KXmlGuiWindow::showEvent(event); +} + +void MainWindow::resizeEvent(QResizeEvent* event) +{ + KXmlGuiWindow::resizeEvent(event); + // This is a small hack to execute code after leaving fullscreen, and after + // the window has been resized back to its former size. + if (d->mFullScreenLeftAt.isValid() && d->mFullScreenLeftAt.secsTo(QDateTime::currentDateTime()) < 2) { + if (d->mCurrentMainPageId == BrowseMainPageId) { + d->mThumbnailView->scrollToSelectedIndex(); + } + d->mFullScreenLeftAt = QDateTime(); + } +} + +void MainWindow::setDistractionFreeMode(bool value) +{ + d->mFullScreenContent->setDistractionFreeMode(value); +} + +void MainWindow::saveProperties(KConfigGroup& group) +{ + group.writeEntry(SESSION_CURRENT_PAGE_KEY, int(d->mCurrentMainPageId)); + group.writeEntry(SESSION_URL_KEY, d->mContextManager->currentUrl()); +} + +void MainWindow::readProperties(const KConfigGroup& group) +{ + MainPageId pageId = MainPageId(group.readEntry(SESSION_CURRENT_PAGE_KEY, int(StartMainPageId))); + if (pageId == StartMainPageId) { + d->mCurrentMainPageId = StartMainPageId; + showStartMainPage(); + } else if (pageId == BrowseMainPageId) { + d->mBrowseAction->trigger(); + } else { + d->mViewAction->trigger(); + } + KUrl url = group.readEntry(SESSION_URL_KEY, KUrl()); + if (!url.isValid()) { + kWarning() << "Invalid url!"; + return; + } + goToUrl(url); +} + +void MainWindow::showFirstDocumentReached() +{ + if (d->mCurrentMainPageId != ViewMainPageId) { + return; + } + HudButtonBox* dlg = new HudButtonBox; + dlg->setText(i18n("You reached the first document, what do you want to do?")); + dlg->addButton(i18n("Stay There")); + dlg->addAction(d->mGoToLastAction, i18n("Go to the Last Document")); + dlg->addAction(d->mBrowseAction, i18n("Go Back to the Document List")); + dlg->addCountDown(15000); + d->mViewMainPage->showMessageWidget(dlg, Qt::AlignCenter); +} + +void MainWindow::showLastDocumentReached() +{ + if (d->mCurrentMainPageId != ViewMainPageId) { + return; + } + HudButtonBox* dlg = new HudButtonBox; + dlg->setText(i18n("You reached the last document, what do you want to do?")); + dlg->addButton(i18n("Stay There")); + dlg->addAction(d->mGoToFirstAction, i18n("Go to the First Document")); + dlg->addAction(d->mBrowseAction, i18n("Go Back to the Document List")); + dlg->addCountDown(15000); + d->mViewMainPage->showMessageWidget(dlg, Qt::AlignCenter); +} + +} // namespace diff --git a/gwenview/app/mainwindow.h b/gwenview/app/mainwindow.h new file mode 100644 index 00000000..8e058e37 --- /dev/null +++ b/gwenview/app/mainwindow.h @@ -0,0 +1,140 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +// Qt +#include + +// KDE +#include + +class QModelIndex; + +class KUrl; + +namespace Gwenview +{ + +class ViewMainPage; +class ContextManager; + +class MainWindow : public KXmlGuiWindow +{ + Q_OBJECT +public: + MainWindow(); + ~MainWindow(); + /** + * Defines the url to display when the window is shown for the first time. + */ + void setInitialUrl(const KUrl&); + + void startSlideShow(); + + ViewMainPage* viewMainPage() const; + + ContextManager* contextManager() const; + + void setDistractionFreeMode(bool); + +public Q_SLOTS: + void showStartMainPage(); + + /** + * Go to url, without changing current mode + */ + void goToUrl(const KUrl&); + +Q_SIGNALS: + void viewModeChanged(); + +public Q_SLOTS: + virtual void setCaption(const QString&); + + virtual void setCaption(const QString&, bool modified); + +protected: + virtual bool queryClose(); + virtual QSize sizeHint() const; + virtual void showEvent(QShowEvent*); + virtual void resizeEvent(QResizeEvent*); + virtual void saveProperties(KConfigGroup&); + virtual void readProperties(const KConfigGroup&); + +private Q_SLOTS: + void setActiveViewModeAction(QAction* action); + void openDirUrl(const KUrl&); + void slotThumbnailViewIndexActivated(const QModelIndex&); + + void slotStartMainPageUrlSelected(const KUrl&); + + void goUp(); + void toggleSideBar(bool visible); + void updateToggleSideBarAction(); + void slotModifiedDocumentListChanged(); + void slotPartCompleted(); + void slotDirModelNewItems(); + void slotDirListerCompleted(); + + void slotSelectionChanged(); + void slotCurrentDirUrlChanged(const KUrl& url); + + void goToPrevious(); + void goToNext(); + void goToFirst(); + void goToLast(); + void updatePreviousNextActions(); + + void leaveFullScreen(); + void toggleFullScreen(bool); + void toggleSlideShow(); + void updateSlideShowAction(); + + void saveCurrent(); + void saveCurrentAs(); + void openFile(); + void openUrl(const KUrl& url); + void reload(); + + void showDocumentInFullScreen(const KUrl&); + + void showConfigDialog(); + void loadConfig(); + void print(); + + void preloadNextUrl(); + + void toggleMenuBar(); + + void showFirstDocumentReached(); + void showLastDocumentReached(); + +private: + struct Private; + MainWindow::Private* const d; + + void openSelectedDocuments(); + void saveConfig(); +}; + +} // namespace + +#endif /* MAINWINDOW_H */ diff --git a/gwenview/app/preloader.cpp b/gwenview/app/preloader.cpp new file mode 100644 index 00000000..dca38b7e --- /dev/null +++ b/gwenview/app/preloader.cpp @@ -0,0 +1,121 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "preloader.moc" + +// Qt + +// KDE +#include + +// Local +#include + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +struct PreloaderPrivate +{ + Preloader* q; + Document::Ptr mDocument; + QSize mSize; + + void forgetDocument() + { + // Forget about the document. Keeping a reference to it would prevent it + // from being garbage collected. + QObject::disconnect(mDocument.data(), 0, q, 0); + mDocument = 0; + } +}; + +Preloader::Preloader(QObject* parent) +: QObject(parent) +, d(new PreloaderPrivate) +{ + d->q = this; +} + +Preloader::~Preloader() +{ + delete d; +} + +void Preloader::preload(const KUrl& url, const QSize& size) +{ + LOG("url=" << url); + if (d->mDocument) { + disconnect(d->mDocument.data(), 0, this, 0); + } + + d->mDocument = DocumentFactory::instance()->load(url); + d->mSize = size; + connect(d->mDocument.data(), SIGNAL(metaInfoUpdated()), + SLOT(doPreload())); + + if (d->mDocument->size().isValid()) { + LOG("size is already available"); + doPreload(); + } +} + +void Preloader::doPreload() +{ + if (!d->mDocument) { + return; + } + + if (d->mDocument->loadingState() == Document::LoadingFailed) { + LOG("loading failed"); + d->forgetDocument(); + return; + } + + if (!d->mDocument->size().isValid()) { + LOG("size not available yet"); + return; + } + + qreal zoom = qMin( + d->mSize.width() / qreal(d->mDocument->width()), + d->mSize.height() / qreal(d->mDocument->height()) + ); + + if (zoom < Document::maxDownSampledZoom()) { + LOG("preloading down sampled, zoom=" << zoom); + d->mDocument->prepareDownSampledImageForZoom(zoom); + } else { + LOG("preloading full image"); + d->mDocument->startLoadingFullImage(); + } + d->forgetDocument(); +} + +} // namespace diff --git a/gwenview/app/preloader.h b/gwenview/app/preloader.h new file mode 100644 index 00000000..29efda75 --- /dev/null +++ b/gwenview/app/preloader.h @@ -0,0 +1,61 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef PRELOADER_H +#define PRELOADER_H + +// Qt +#include + +// KDE + +// Local + +class QSize; + +class KUrl; + +namespace Gwenview +{ + +struct PreloaderPrivate; + +/** + * This class preloads a document to fit a specific size. + */ +class Preloader : public QObject +{ + Q_OBJECT +public: + Preloader(QObject* parent); + ~Preloader(); + + void preload(const KUrl&, const QSize&); + +private Q_SLOTS: + void doPreload(); + +private: + PreloaderPrivate* const d; +}; + +} // namespace + +#endif /* PRELOADER_H */ diff --git a/gwenview/app/saveallhelper.cpp b/gwenview/app/saveallhelper.cpp new file mode 100644 index 00000000..8cd027e5 --- /dev/null +++ b/gwenview/app/saveallhelper.cpp @@ -0,0 +1,115 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "saveallhelper.moc" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include + +// Local +#include +#include +#include + +namespace Gwenview +{ + +struct SaveAllHelperPrivate +{ + QWidget* mParent; + KProgressDialog* mProgressDialog; + QSet mJobSet; + QStringList mErrorList; +}; + +SaveAllHelper::SaveAllHelper(QWidget* parent) +: d(new SaveAllHelperPrivate) +{ + d->mParent = parent; + d->mProgressDialog = new KProgressDialog(parent); + connect(d->mProgressDialog, SIGNAL(cancelClicked()), SLOT(slotCanceled())); + d->mProgressDialog->setLabelText(i18nc("@info:progress saving all image changes", "Saving...")); + d->mProgressDialog->setButtonText(i18n("&Stop")); + d->mProgressDialog->progressBar()->setMinimum(0); +} + +SaveAllHelper::~SaveAllHelper() +{ + delete d; +} + +void SaveAllHelper::save() +{ + KUrl::List list = DocumentFactory::instance()->modifiedDocumentList(); + d->mProgressDialog->progressBar()->setRange(0, list.size()); + d->mProgressDialog->progressBar()->setValue(0); + Q_FOREACH(const KUrl & url, list) { + Document::Ptr doc = DocumentFactory::instance()->load(url); + DocumentJob* job = doc->save(url, doc->format()); + connect(job, SIGNAL(result(KJob*)), SLOT(slotResult(KJob*))); + d->mJobSet << job; + } + + d->mProgressDialog->exec(); + + // Done, show message if necessary + if (d->mErrorList.count() > 0) { + QString msg = i18ncp("@info", "One document could not be saved:", "%1 documents could not be saved:", d->mErrorList.count()); + msg += "
    "; + Q_FOREACH(const QString & item, d->mErrorList) { + msg += "
  • " + item + "
  • "; + } + msg += "
"; + KMessageBox::sorry(d->mParent, msg); + } +} + +void SaveAllHelper::slotCanceled() +{ + Q_FOREACH(DocumentJob * job, d->mJobSet) { + job->kill(); + } +} + +void SaveAllHelper::slotResult(KJob* _job) +{ + DocumentJob* job = static_cast(_job); + if (job->error()) { + KUrl url = job->document()->url(); + QString name = url.fileName().isEmpty() ? url.pathOrUrl() : url.fileName(); + d->mErrorList << i18nc("@info %1 is the name of the document which failed to save, %2 is the reason for the failure", + "%1: %2", name, job->errorString()); + } + d->mJobSet.remove(job); + QProgressBar* bar = d->mProgressDialog->progressBar(); + bar->setValue(bar->value() + 1); +} + +} // namespace diff --git a/gwenview/app/saveallhelper.h b/gwenview/app/saveallhelper.h new file mode 100644 index 00000000..fafd3cce --- /dev/null +++ b/gwenview/app/saveallhelper.h @@ -0,0 +1,56 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef SAVEALLHELPER_H +#define SAVEALLHELPER_H + +// Qt +#include + +// KDE + +// Local + +class KJob; + +namespace Gwenview +{ + +struct SaveAllHelperPrivate; +class SaveAllHelper : public QObject +{ + Q_OBJECT +public: + SaveAllHelper(QWidget* parent); + ~SaveAllHelper(); + + void save(); + +private Q_SLOTS: + void slotCanceled(); + void slotResult(KJob*); + +private: + SaveAllHelperPrivate* const d; +}; + +} // namespace + +#endif /* SAVEALLHELPER_H */ diff --git a/gwenview/app/savebar.cpp b/gwenview/app/savebar.cpp new file mode 100644 index 00000000..10a0cd88 --- /dev/null +++ b/gwenview/app/savebar.cpp @@ -0,0 +1,375 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "savebar.moc" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "lib/document/documentfactory.h" +#include "lib/gwenviewconfig.h" +#include "lib/memoryutils.h" +#include "lib/paintutils.h" + +namespace Gwenview +{ + +QToolButton* createToolButton() +{ + QToolButton* button = new QToolButton; + button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + button->hide(); + return button; +} + +struct SaveBarPrivate +{ + SaveBar* q; + KActionCollection* mActionCollection; + QWidget* mSaveBarWidget; + QWidget* mTopRowWidget; + QToolButton* mUndoButton; + QToolButton* mRedoButton; + QToolButton* mSaveCurrentUrlButton; + QToolButton* mSaveAsButton; + QToolButton* mSaveAllButton; + QToolButton* mSaveAllFullScreenButton; + QLabel* mMessageLabel; + QLabel* mActionsLabel; + QFrame* mTooManyChangesFrame; + KUrl mCurrentUrl; + bool mFullScreenMode; + + void createTooManyChangesFrame() + { + mTooManyChangesFrame = new QFrame; + + // Icon + QLabel* iconLabel = new QLabel; + QPixmap pix = KIconLoader::global()->loadIcon( + "dialog-warning", KIconLoader::Dialog, KIconLoader::SizeSmall); + iconLabel->setPixmap(pix); + + // Text label + QLabel* textLabel = new QLabel; + textLabel->setText( + i18n("You have modified many images. To avoid memory problems, you should save your changes.") + ); + + mSaveAllFullScreenButton = createToolButton(); + + // Layout + QHBoxLayout* layout = new QHBoxLayout(mTooManyChangesFrame); + layout->setMargin(0); + layout->addWidget(iconLabel); + layout->addWidget(textLabel); + layout->addWidget(mSaveAllFullScreenButton); + mTooManyChangesFrame->hide(); + + // CSS + KColorScheme scheme(mSaveBarWidget->palette().currentColorGroup(), KColorScheme::Window); + QColor warningBackgroundColor = scheme.background(KColorScheme::NegativeBackground).color(); + QColor warningBorderColor = PaintUtils::adjustedHsv(warningBackgroundColor, 0, 150, 0); + QColor warningColor = scheme.foreground(KColorScheme::NegativeText).color(); + + QString css = + ".QFrame {" + " background-color: %1;" + " border: 1px solid %2;" + " border-radius: 4px;" + " padding: 3px;" + "}" + ".QFrame QLabel {" + " color: %3;" + "}" + ; + css = css + .arg(warningBackgroundColor.name()) + .arg(warningBorderColor.name()) + .arg(warningColor.name()) + ; + mTooManyChangesFrame->setStyleSheet(css); + } + + void applyNormalStyleSheet() + { + QColor bgColor = QToolTip::palette().base().color(); + QColor borderColor = PaintUtils::adjustedHsv(bgColor, 0, 150, 0); + QColor fgColor = QToolTip::palette().text().color(); + + QString css = + "#saveBarWidget {" + " background-color: %1;" + " border-top: 1px solid %2;" + " border-bottom: 1px solid %2;" + " color: %3;" + "}" + ; + + css = css + .arg(bgColor.name()) + .arg(borderColor.name()) + .arg(fgColor.name()) + ; + mSaveBarWidget->setStyleSheet(css); + } + + void applyFullScreenStyleSheet() + { + QString css = + "#saveBarWidget {" + " background-color: #333;" + "}"; + mSaveBarWidget->setStyleSheet(css); + } + + void updateTooManyChangesFrame(const QList& list) + { + qreal maxPercentageOfMemoryUsage = GwenviewConfig::percentageOfMemoryUsageWarning(); + qulonglong maxMemoryUsage = MemoryUtils::getTotalMemory() * maxPercentageOfMemoryUsage; + qulonglong memoryUsage = 0; + Q_FOREACH(const KUrl & url, list) { + Document::Ptr doc = DocumentFactory::instance()->load(url); + memoryUsage += doc->memoryUsage(); + } + + mTooManyChangesFrame->setVisible(memoryUsage > maxMemoryUsage); + } + + void updateTopRowWidget(const QList& lst) + { + QStringList links; + QString message; + + if (lst.contains(mCurrentUrl)) { + message = i18n("Current image modified"); + + mUndoButton->show(); + mRedoButton->show(); + + if (lst.size() > 1) { + QString previous = i18n("Previous modified image"); + QString next = i18n("Next modified image"); + if (mCurrentUrl == lst[0]) { + links << previous; + } else { + links << QString("%1").arg(previous); + } + if (mCurrentUrl == lst[lst.size() - 1]) { + links << next; + } else { + links << QString("%1").arg(next); + } + } + } else { + mUndoButton->hide(); + mRedoButton->hide(); + + message = i18np("One image modified", "%1 images modified", lst.size()); + if (lst.size() > 1) { + links << QString("%1").arg(i18n("Go to first modified image")); + } else { + links << QString("%1").arg(i18n("Go to it")); + } + } + + mSaveCurrentUrlButton->setVisible(lst.contains(mCurrentUrl)); + mSaveAsButton->setVisible(lst.contains(mCurrentUrl)); + mSaveAllButton->setVisible(lst.size() >= 1); + + mMessageLabel->setText(message); + mActionsLabel->setText(links.join(" | ")); + } + + void updateWidgetSizes() + { + QVBoxLayout* layout = static_cast(mSaveBarWidget->layout()); + int topRowHeight = mFullScreenMode ? 0 : mTopRowWidget->height(); + int bottomRowHeight = mTooManyChangesFrame->isVisibleTo(mSaveBarWidget) ? mTooManyChangesFrame->sizeHint().height() : 0; + + int height = 2 * layout->margin() + topRowHeight + bottomRowHeight; + if (topRowHeight > 0 && bottomRowHeight > 0) { + height += layout->spacing(); + } + mSaveBarWidget->setFixedHeight(height); + } +}; + +SaveBar::SaveBar(QWidget* parent, KActionCollection* actionCollection) +: SlideContainer(parent) +, d(new SaveBarPrivate) +{ + d->q = this; + d->mFullScreenMode = false; + d->mActionCollection = actionCollection; + d->mSaveBarWidget = new QWidget(); + d->mSaveBarWidget->setObjectName(QLatin1String("saveBarWidget")); + d->applyNormalStyleSheet(); + + d->mMessageLabel = new QLabel; + d->mMessageLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + + d->mUndoButton = createToolButton(); + d->mRedoButton = createToolButton(); + d->mSaveCurrentUrlButton = createToolButton(); + d->mSaveAsButton = createToolButton(); + d->mSaveAllButton = createToolButton(); + + d->mActionsLabel = new QLabel; + d->mActionsLabel->setAlignment(Qt::AlignCenter); + d->mActionsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + d->createTooManyChangesFrame(); + + // Setup top row + d->mTopRowWidget = new QWidget; + QHBoxLayout* rowLayout = new QHBoxLayout(d->mTopRowWidget); + rowLayout->addWidget(d->mMessageLabel); + rowLayout->addWidget(d->mUndoButton); + rowLayout->addWidget(d->mRedoButton); + rowLayout->addWidget(d->mActionsLabel); + rowLayout->addWidget(d->mSaveCurrentUrlButton); + rowLayout->addWidget(d->mSaveAsButton); + rowLayout->addWidget(d->mSaveAllButton); + rowLayout->setMargin(0); + + // Setup bottom row + QHBoxLayout* bottomRowLayout = new QHBoxLayout; + bottomRowLayout->addStretch(); + bottomRowLayout->addWidget(d->mTooManyChangesFrame); + bottomRowLayout->addStretch(); + + // Gather everything together + QVBoxLayout* layout = new QVBoxLayout(d->mSaveBarWidget); + layout->addWidget(d->mTopRowWidget); + layout->addLayout(bottomRowLayout); + layout->setMargin(3); + layout->setSpacing(3); + + setContent(d->mSaveBarWidget); + + connect(DocumentFactory::instance(), SIGNAL(modifiedDocumentListChanged()), + SLOT(updateContent())); + + connect(d->mActionsLabel, SIGNAL(linkActivated(QString)), + SLOT(triggerAction(QString))); +} + +SaveBar::~SaveBar() +{ + delete d; +} + +void SaveBar::initActionDependentWidgets() +{ + d->mUndoButton->setDefaultAction(d->mActionCollection->action("edit_undo")); + d->mRedoButton->setDefaultAction(d->mActionCollection->action("edit_redo")); + d->mSaveCurrentUrlButton->setDefaultAction(d->mActionCollection->action("file_save")); + d->mSaveAsButton->setDefaultAction(d->mActionCollection->action("file_save_as")); + + // FIXME: Not using an action for now + d->mSaveAllButton->setText(i18n("Save All")); + d->mSaveAllButton->setIcon(KIcon("document-save-all")); + connect(d->mSaveAllButton, SIGNAL(clicked()), + SIGNAL(requestSaveAll())); + + d->mSaveAllFullScreenButton->setText(i18n("Save All")); + connect(d->mSaveAllFullScreenButton, SIGNAL(clicked()), + SIGNAL(requestSaveAll())); + + int height = d->mUndoButton->sizeHint().height(); + d->mTopRowWidget->setFixedHeight(height); + d->updateWidgetSizes(); +} + +void SaveBar::setFullScreenMode(bool value) +{ + d->mFullScreenMode = value; + d->mSaveAllFullScreenButton->setVisible(value); + if (value) { + d->applyFullScreenStyleSheet(); + } else { + d->applyNormalStyleSheet(); + } + updateContent(); +} + +void SaveBar::updateContent() +{ + QList lst = DocumentFactory::instance()->modifiedDocumentList(); + + if (d->mFullScreenMode) { + d->mTopRowWidget->hide(); + } else { + d->mTopRowWidget->show(); + d->updateTopRowWidget(lst); + } + + d->updateTooManyChangesFrame(lst); + + d->updateWidgetSizes(); + if (lst.isEmpty() || (d->mFullScreenMode && !d->mTooManyChangesFrame->isVisibleTo(d->mSaveBarWidget))) { + slideOut(); + } else { + slideIn(); + } +} + +void SaveBar::triggerAction(const QString& action) +{ + QList lst = DocumentFactory::instance()->modifiedDocumentList(); + if (action == "first") { + goToUrl(lst[0]); + } else if (action == "previous") { + int pos = lst.indexOf(d->mCurrentUrl); + --pos; + Q_ASSERT(pos >= 0); + goToUrl(lst[pos]); + } else if (action == "next") { + int pos = lst.indexOf(d->mCurrentUrl); + ++pos; + Q_ASSERT(pos < lst.size()); + goToUrl(lst[pos]); + } else { + kWarning() << "Unknown action: " << action ; + } +} + +void SaveBar::setCurrentUrl(const KUrl& url) +{ + d->mCurrentUrl = url; + updateContent(); +} + +} // namespace diff --git a/gwenview/app/savebar.h b/gwenview/app/savebar.h new file mode 100644 index 00000000..1116d513 --- /dev/null +++ b/gwenview/app/savebar.h @@ -0,0 +1,69 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef SAVEBAR_H +#define SAVEBAR_H + +// Qt + +// KDE + +// Local +#include + +class KActionCollection; +class KUrl; + +namespace Gwenview +{ + +struct SaveBarPrivate; +class SaveBar : public SlideContainer +{ + Q_OBJECT +public: + SaveBar(QWidget* parent, KActionCollection* collection); + ~SaveBar(); + + /** + * Init widgets which depend on an initialized actionCollection + */ + void initActionDependentWidgets(); + + void setFullScreenMode(bool); + +public Q_SLOTS: + void setCurrentUrl(const KUrl&); + +Q_SIGNALS: + void requestSaveAll(); + void goToUrl(const KUrl&); + +private: + SaveBarPrivate* const d; + +private Q_SLOTS: + void updateContent(); + void triggerAction(const QString& action); +}; + +} // namespace + +#endif /* SAVEBAR_H */ diff --git a/gwenview/app/semanticinfocontextmanageritem.cpp b/gwenview/app/semanticinfocontextmanageritem.cpp new file mode 100644 index 00000000..9f3435b6 --- /dev/null +++ b/gwenview/app/semanticinfocontextmanageritem.cpp @@ -0,0 +1,466 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "semanticinfocontextmanageritem.moc" + +// Qt +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "viewmainpage.h" +#include "sidebar.h" +#include "ui_semanticinfosidebaritem.h" +#include "ui_semanticinfodialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +static const int RATING_INDICATOR_HIDE_DELAY = 2000; + +struct SemanticInfoDialog : public KDialog, public Ui_SemanticInfoDialog +{ + SemanticInfoDialog(QWidget* parent) + : KDialog(parent) + { + setButtons(None); + QWidget* mainWidget = new QWidget; + setMainWidget(mainWidget); + setupUi(mainWidget); + mainWidget->layout()->setMargin(0); + setWindowTitle(mainWidget->windowTitle()); + + restoreDialogSize(configGroup()); + } + + ~SemanticInfoDialog() + { + KConfigGroup group = configGroup(); + saveDialogSize(group); + } + + KConfigGroup configGroup() const + { + KSharedConfigPtr config = KGlobal::config(); + return KConfigGroup(config, "SemanticInfoDialog"); + } +}; + +/** + * A QGraphicsPixmapItem-like class, but which inherits from QGraphicsWidget + */ +class GraphicsPixmapWidget : public QGraphicsWidget +{ +public: + void setPixmap(const QPixmap& pix) + { + mPix = pix; + setMinimumSize(pix.size()); + } + + void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) + { + painter->drawPixmap( + (size().width() - mPix.width()) / 2, + (size().height() - mPix.height()) / 2, + mPix); + } + +private: + QPixmap mPix; +}; + +class RatingIndicator : public HudWidget +{ +public: + RatingIndicator() + : HudWidget() + , mPixmapWidget(new GraphicsPixmapWidget) + , mDeleteTimer(new QTimer(this)) + { + updatePixmap(0); + setOpacity(0); + init(mPixmapWidget, OptionNone); + + mDeleteTimer->setInterval(RATING_INDICATOR_HIDE_DELAY); + mDeleteTimer->setSingleShot(true); + connect(mDeleteTimer, SIGNAL(timeout()), SLOT(fadeOut())); + connect(this, SIGNAL(fadedOut()), SLOT(deleteLater())); + } + + void setRating(int rating) + { + updatePixmap(rating); + update(); + mDeleteTimer->start(); + fadeIn(); + } + +private: + GraphicsPixmapWidget* mPixmapWidget; + QTimer* mDeleteTimer; + + void updatePixmap(int rating) + { + KRatingPainter ratingPainter; + const int iconSize = KIconLoader::global()->currentSize(KIconLoader::Small); + QPixmap pix(iconSize * 5 + ratingPainter.spacing() * 4, iconSize); + pix.fill(Qt::transparent); + { + QPainter painter(&pix); + ratingPainter.paint(&painter, pix.rect(), rating); + } + mPixmapWidget->setPixmap(pix); + } +}; + +struct SemanticInfoContextManagerItemPrivate : public Ui_SemanticInfoSideBarItem +{ + SemanticInfoContextManagerItem* q; + SideBarGroup* mGroup; + KActionCollection* mActionCollection; + ViewMainPage* mViewMainPage; + QPointer mSemanticInfoDialog; + TagInfo mTagInfo; + KAction* mEditTagsAction; + QSignalMapper* mRatingMapper; + /** A list of all actions, so that we can disable them when necessary */ + QList mActions; + QPointer mRatingIndicator; + + void setupGroup() + { + mGroup = new SideBarGroup(i18n("Semantic Information")); + q->setWidget(mGroup); + EventWatcher::install(mGroup, QEvent::Show, q, SLOT(update())); + + QWidget* container = new QWidget; + setupUi(container); + container->layout()->setMargin(0); + mGroup->addWidget(container); + + QObject::connect(mRatingWidget, SIGNAL(ratingChanged(int)), + q, SLOT(slotRatingChanged(int))); + QObject::connect(mRatingMapper, SIGNAL(mapped(int)), + mRatingWidget, SLOT(setRating(int))); + + mDescriptionTextEdit->installEventFilter(q); + + QObject::connect(mTagLabel, SIGNAL(linkActivated(QString)), + mEditTagsAction, SLOT(trigger())); + } + + void setupActions() + { + KActionCategory* edit = new KActionCategory(i18nc("@title actions category", "Edit"), mActionCollection); + + mEditTagsAction = edit->addAction("edit_tags"); + mEditTagsAction->setText(i18nc("@action", "Edit Tags")); + mEditTagsAction->setShortcut(Qt::CTRL | Qt::Key_T); + QObject::connect(mEditTagsAction, SIGNAL(triggered()), + q, SLOT(showSemanticInfoDialog())); + mActions << mEditTagsAction; + + mRatingMapper = new QSignalMapper(q); + for (int rating = 0; rating <= 5; ++rating) { + KAction* action = edit->addAction(QString("rate_%1").arg(rating)); + if (rating == 0) { + action->setText(i18nc("@action Rating value of zero", "Zero")); + } else { + action->setText(QString(rating, QChar(0x22C6))); /* 0x22C6 is the 'star' character */ + } + action->setShortcut(Qt::Key_0 + rating); + QObject::connect(action, SIGNAL(triggered()), mRatingMapper, SLOT(map())); + mRatingMapper->setMapping(action, rating * 2); + mActions << action; + } + QObject::connect(mRatingMapper, SIGNAL(mapped(int)), q, SLOT(slotRatingChanged(int))); + } + + void updateTagLabel() + { + if (q->contextManager()->selectedFileItemList().isEmpty()) { + mTagLabel->clear(); + return; + } + + AbstractSemanticInfoBackEnd* backEnd = q->contextManager()->dirModel()->semanticInfoBackEnd(); + + TagInfo::ConstIterator + it = mTagInfo.constBegin(), + end = mTagInfo.constEnd(); + QMap labelMap; + for (; it != end; ++it) { + SemanticInfoTag tag = it.key(); + QString label = backEnd->labelForTag(tag); + if (!it.value()) { + // Tag is not present for all urls + label += '*'; + } + labelMap[label.toLower()] = label; + } + QStringList labels(labelMap.values()); + + QString editLink = i18n("Edit"); + QString text = labels.join(", ") + QString(" %1").arg(editLink); + mTagLabel->setText(text); + } + + void updateSemanticInfoDialog() + { + mSemanticInfoDialog->mTagWidget->setEnabled(!q->contextManager()->selectedFileItemList().isEmpty()); + mSemanticInfoDialog->mTagWidget->setTagInfo(mTagInfo); + } +}; + +SemanticInfoContextManagerItem::SemanticInfoContextManagerItem(ContextManager* manager, KActionCollection* actionCollection, ViewMainPage* viewMainPage) +: AbstractContextManagerItem(manager) +, d(new SemanticInfoContextManagerItemPrivate) +{ + d->q = this; + d->mActionCollection = actionCollection; + d->mViewMainPage = viewMainPage; + + connect(contextManager(), SIGNAL(selectionChanged()), + SLOT(slotSelectionChanged())); + connect(contextManager(), SIGNAL(selectionDataChanged()), + SLOT(update())); + connect(contextManager(), SIGNAL(currentDirUrlChanged(KUrl)), + SLOT(update())); + + d->setupActions(); + d->setupGroup(); +} + +SemanticInfoContextManagerItem::~SemanticInfoContextManagerItem() +{ + delete d; +} + +inline int ratingForVariant(const QVariant& variant) +{ + if (variant.isValid()) { + return variant.toInt(); + } else { + return 0; + } +} + +void SemanticInfoContextManagerItem::slotSelectionChanged() +{ + update(); +} + +void SemanticInfoContextManagerItem::update() +{ + KFileItemList itemList = contextManager()->selectedFileItemList(); + + bool first = true; + int rating = 0; + QString description; + SortedDirModel* dirModel = contextManager()->dirModel(); + + // This hash stores for how many items the tag is present + // If you have 3 items, and only 2 have the "Holiday" tag, + // then tagHash["Holiday"] will be 2 at the end of the loop. + typedef QHash TagHash; + TagHash tagHash; + + Q_FOREACH(const KFileItem & item, itemList) { + QModelIndex index = dirModel->indexForItem(item); + + QVariant value = dirModel->data(index, SemanticInfoDirModel::RatingRole); + if (first) { + rating = ratingForVariant(value); + } else if (rating != ratingForVariant(value)) { + // Ratings aren't the same, reset + rating = 0; + } + + QString indexDescription = index.data(SemanticInfoDirModel::DescriptionRole).toString(); + if (first) { + description = indexDescription; + } else if (description != indexDescription) { + description.clear(); + } + + // Fill tagHash, incrementing the tag count if it's already there + TagSet tagSet = TagSet::fromVariant(index.data(SemanticInfoDirModel::TagsRole)); + Q_FOREACH(const QString & tag, tagSet) { + TagHash::Iterator it = tagHash.find(tag); + if (it == tagHash.end()) { + tagHash[tag] = 1; + } else { + ++it.value(); + } + } + + first = false; + } + { + SignalBlocker blocker(d->mRatingWidget); + d->mRatingWidget->setRating(rating); + } + d->mDescriptionTextEdit->setText(description); + + // Init tagInfo from tagHash + d->mTagInfo.clear(); + int itemCount = itemList.count(); + TagHash::ConstIterator + it = tagHash.constBegin(), + end = tagHash.constEnd(); + for (; it != end; ++it) { + QString tag = it.key(); + int count = it.value(); + d->mTagInfo[tag] = count == itemCount; + } + + bool enabled = !contextManager()->selectedFileItemList().isEmpty(); + Q_FOREACH(KAction * action, d->mActions) { + action->setEnabled(enabled); + } + d->updateTagLabel(); + if (d->mSemanticInfoDialog) { + d->updateSemanticInfoDialog(); + } +} + +void SemanticInfoContextManagerItem::slotRatingChanged(int rating) +{ + KFileItemList itemList = contextManager()->selectedFileItemList(); + + // Show rating indicator in view mode, and only if sidebar is not visible + if (d->mViewMainPage->isVisible() && !d->mRatingWidget->isVisible()) { + if (!d->mRatingIndicator.data()) { + d->mRatingIndicator = new RatingIndicator; + d->mViewMainPage->showMessageWidget(d->mRatingIndicator, Qt::AlignBottom | Qt::AlignHCenter); + } + d->mRatingIndicator->setRating(rating); + } + + SortedDirModel* dirModel = contextManager()->dirModel(); + Q_FOREACH(const KFileItem & item, itemList) { + QModelIndex index = dirModel->indexForItem(item); + dirModel->setData(index, rating, SemanticInfoDirModel::RatingRole); + } +} + +void SemanticInfoContextManagerItem::storeDescription() +{ + if (!d->mDescriptionTextEdit->document()->isModified()) { + return; + } + d->mDescriptionTextEdit->document()->setModified(false); + QString description = d->mDescriptionTextEdit->toPlainText(); + KFileItemList itemList = contextManager()->selectedFileItemList(); + + SortedDirModel* dirModel = contextManager()->dirModel(); + Q_FOREACH(const KFileItem & item, itemList) { + QModelIndex index = dirModel->indexForItem(item); + dirModel->setData(index, description, SemanticInfoDirModel::DescriptionRole); + } +} + +void SemanticInfoContextManagerItem::assignTag(const SemanticInfoTag& tag) +{ + KFileItemList itemList = contextManager()->selectedFileItemList(); + + SortedDirModel* dirModel = contextManager()->dirModel(); + Q_FOREACH(const KFileItem & item, itemList) { + QModelIndex index = dirModel->indexForItem(item); + TagSet tags = TagSet::fromVariant(dirModel->data(index, SemanticInfoDirModel::TagsRole)); + if (!tags.contains(tag)) { + tags << tag; + dirModel->setData(index, tags.toVariant(), SemanticInfoDirModel::TagsRole); + } + } +} + +void SemanticInfoContextManagerItem::removeTag(const SemanticInfoTag& tag) +{ + KFileItemList itemList = contextManager()->selectedFileItemList(); + + SortedDirModel* dirModel = contextManager()->dirModel(); + Q_FOREACH(const KFileItem & item, itemList) { + QModelIndex index = dirModel->indexForItem(item); + TagSet tags = TagSet::fromVariant(dirModel->data(index, SemanticInfoDirModel::TagsRole)); + if (tags.contains(tag)) { + tags.remove(tag); + dirModel->setData(index, tags.toVariant(), SemanticInfoDirModel::TagsRole); + } + } +} + +void SemanticInfoContextManagerItem::showSemanticInfoDialog() +{ + if (!d->mSemanticInfoDialog) { + d->mSemanticInfoDialog = new SemanticInfoDialog(d->mGroup); + d->mSemanticInfoDialog->setAttribute(Qt::WA_DeleteOnClose, true); + + connect(d->mSemanticInfoDialog->mPreviousButton, SIGNAL(clicked()), + d->mActionCollection->action("go_previous"), SLOT(trigger())); + connect(d->mSemanticInfoDialog->mNextButton, SIGNAL(clicked()), + d->mActionCollection->action("go_next"), SLOT(trigger())); + connect(d->mSemanticInfoDialog->mButtonBox, SIGNAL(rejected()), + d->mSemanticInfoDialog, SLOT(close())); + + AbstractSemanticInfoBackEnd* backEnd = contextManager()->dirModel()->semanticInfoBackEnd(); + d->mSemanticInfoDialog->mTagWidget->setSemanticInfoBackEnd(backEnd); + connect(d->mSemanticInfoDialog->mTagWidget, SIGNAL(tagAssigned(SemanticInfoTag)), + SLOT(assignTag(SemanticInfoTag))); + connect(d->mSemanticInfoDialog->mTagWidget, SIGNAL(tagRemoved(SemanticInfoTag)), + SLOT(removeTag(SemanticInfoTag))); + } + d->updateSemanticInfoDialog(); + d->mSemanticInfoDialog->show(); +} + +bool SemanticInfoContextManagerItem::eventFilter(QObject*, QEvent* event) +{ + if (event->type() == QEvent::FocusOut) { + storeDescription(); + } + return false; +} + +} // namespace diff --git a/gwenview/app/semanticinfocontextmanageritem.h b/gwenview/app/semanticinfocontextmanageritem.h new file mode 100644 index 00000000..bb0c2713 --- /dev/null +++ b/gwenview/app/semanticinfocontextmanageritem.h @@ -0,0 +1,66 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef SEMANTICINFOCONTEXTMANAGERITEM_H +#define SEMANTICINFOCONTEXTMANAGERITEM_H + +// Qt + +// KDE + +// Local +#include "abstractcontextmanageritem.h" +#include + +class KActionCollection; + +namespace Gwenview +{ + +class ViewMainPage; + +struct SemanticInfoContextManagerItemPrivate; +class SemanticInfoContextManagerItem : public AbstractContextManagerItem +{ + Q_OBJECT +public: + SemanticInfoContextManagerItem(ContextManager*, KActionCollection*, ViewMainPage* viewMainPage); + ~SemanticInfoContextManagerItem(); + +protected: + virtual bool eventFilter(QObject*, QEvent*); + +private Q_SLOTS: + void slotSelectionChanged(); + void update(); + void slotRatingChanged(int rating); + void storeDescription(); + void assignTag(const SemanticInfoTag&); + void removeTag(const SemanticInfoTag&); + void showSemanticInfoDialog(); + +private: + friend struct SemanticInfoContextManagerItemPrivate; + SemanticInfoContextManagerItemPrivate* const d; +}; + +} // namespace + +#endif /* SEMANTICINFOCONTEXTMANAGERITEM_H */ diff --git a/gwenview/app/semanticinfodialog.ui b/gwenview/app/semanticinfodialog.ui new file mode 100644 index 00000000..2ce5754f --- /dev/null +++ b/gwenview/app/semanticinfodialog.ui @@ -0,0 +1,68 @@ + + SemanticInfoDialog + + + + 0 + 0 + 323 + 355 + + + + Tag Editor + + + + + + + 0 + 0 + + + + + + + + + + Previous + + + + + + + Next + + + + + + + QDialogButtonBox::Close + + + + + + + + + + KDialogButtonBox + QDialogButtonBox +
kdialogbuttonbox.h
+
+ + Gwenview::TagWidget + QWidget +
lib/semanticinfo/tagwidget.h
+ 1 +
+
+ + +
diff --git a/gwenview/app/semanticinfosidebaritem.ui b/gwenview/app/semanticinfosidebaritem.ui new file mode 100644 index 00000000..8be311fe --- /dev/null +++ b/gwenview/app/semanticinfosidebaritem.ui @@ -0,0 +1,99 @@ + + + SemanticInfoSideBarItem + + + + 0 + 0 + 231 + 183 + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Rating: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Tags: + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + Qt::ClickFocus + + + false + + + Description + + + true + + + + + + + + KTextEdit + QTextEdit +
ktextedit.h
+
+ + KRatingWidget + QFrame +
kratingwidget.h
+
+
+ + +
diff --git a/gwenview/app/sidebar.cpp b/gwenview/app/sidebar.cpp new file mode 100644 index 00000000..3a679e30 --- /dev/null +++ b/gwenview/app/sidebar.cpp @@ -0,0 +1,251 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "sidebar.moc" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include + +// Local + +namespace Gwenview +{ + +/** + * A button which always leave room for an icon, even if there is none, so that + * all button texts are correctly aligned. + */ +class SideBarButton : public QToolButton +{ +protected: + virtual void paintEvent(QPaintEvent* event) + { + forceIcon(); + QToolButton::paintEvent(event); + } + + virtual QSize sizeHint() const + { + const_cast(this)->forceIcon(); + return QToolButton::sizeHint(); + } + +private: + void forceIcon() + { + if (!icon().isNull()) { + return; + } + + // Assign an empty icon to the button if there is no icon associated + // with the action so that all button texts are correctly aligned. + QSize wantedSize = iconSize(); + if (mEmptyIcon.isNull() || mEmptyIcon.actualSize(wantedSize) != wantedSize) { + QPixmap pix(wantedSize); + pix.fill(Qt::transparent); + mEmptyIcon.addPixmap(pix); + } + setIcon(mEmptyIcon); + } + + QIcon mEmptyIcon; +}; + +//- SideBarGroup --------------------------------------------------------------- +struct SideBarGroupPrivate +{ + QFrame* mContainer; + QLabel* mTitleLabel; +}; + +SideBarGroup::SideBarGroup(const QString& title) +: QFrame() +, d(new SideBarGroupPrivate) +{ + d->mContainer = 0; + d->mTitleLabel = new QLabel(this); + d->mTitleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + d->mTitleLabel->setFixedHeight(d->mTitleLabel->sizeHint().height() * 3 / 2); + QFont font(d->mTitleLabel->font()); + font.setBold(true); + d->mTitleLabel->setFont(font); + d->mTitleLabel->setText(title); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + + layout->addWidget(d->mTitleLabel); + + clear(); +} + +SideBarGroup::~SideBarGroup() +{ + delete d; +} + +void SideBarGroup::paintEvent(QPaintEvent* event) +{ + QFrame::paintEvent(event); + if (parentWidget()->layout()->indexOf(this) != 0) { + // Draw a separator, but only if we are not the first group + QPainter painter(this); + QPen pen(palette().mid().color()); + painter.setPen(pen); + painter.drawLine(rect().topLeft(), rect().topRight()); + } +} + +void SideBarGroup::addWidget(QWidget* widget) +{ + widget->setParent(d->mContainer); + d->mContainer->layout()->addWidget(widget); +} + +void SideBarGroup::clear() +{ + if (d->mContainer) { + d->mContainer->deleteLater(); + } + + d->mContainer = new QFrame(this); + QVBoxLayout* containerLayout = new QVBoxLayout(d->mContainer); + containerLayout->setMargin(0); + containerLayout->setSpacing(0); + + layout()->addWidget(d->mContainer); +} + +void SideBarGroup::addAction(QAction* action) +{ + int size = KIconLoader::global()->currentSize(KIconLoader::Small); + QToolButton* button = new SideBarButton(); + button->setFocusPolicy(Qt::NoFocus); + button->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + button->setAutoRaise(true); + button->setDefaultAction(action); + button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + button->setIconSize(QSize(size, size)); + if (action->menu()) { + button->setPopupMode(QToolButton::InstantPopup); + } + addWidget(button); +} + +//- SideBarPage ---------------------------------------------------------------- +struct SideBarPagePrivate +{ + QString mTitle; + QVBoxLayout* mLayout; +}; + +SideBarPage::SideBarPage(const QString& title) +: QWidget() +, d(new SideBarPagePrivate) +{ + d->mTitle = title; + + d->mLayout = new QVBoxLayout(this); + d->mLayout->setMargin(0); +} + +SideBarPage::~SideBarPage() +{ + delete d; +} + +const QString& SideBarPage::title() const +{ + return d->mTitle; +} + +void SideBarPage::addWidget(QWidget* widget) +{ + d->mLayout->addWidget(widget); +} + +void SideBarPage::addStretch() +{ + d->mLayout->addStretch(); +} + +//- SideBar -------------------------------------------------------------------- +struct SideBarPrivate +{ +}; + +SideBar::SideBar(QWidget* parent) +: QTabWidget(parent) +, d(new SideBarPrivate) +{ + setFont(KGlobalSettings::smallestReadableFont()); + tabBar()->setDocumentMode(true); + tabBar()->setUsesScrollButtons(false); + tabBar()->setFocusPolicy(Qt::NoFocus); + setTabPosition(QTabWidget::South); + setElideMode(Qt::ElideRight); +} + +SideBar::~SideBar() +{ + delete d; +} + +QSize SideBar::sizeHint() const +{ + return QSize(200, 200); +} + +void SideBar::addPage(SideBarPage* page) +{ + addTab(page, page->title()); +} + +QString SideBar::currentPage() const +{ + return currentWidget()->objectName(); +} + +void SideBar::setCurrentPage(const QString& name) +{ + for (int index = 0; index < count(); ++index) { + if (widget(index)->objectName() == name) { + setCurrentIndex(index); + } + } +} + +} // namespace diff --git a/gwenview/app/sidebar.h b/gwenview/app/sidebar.h new file mode 100644 index 00000000..f6404505 --- /dev/null +++ b/gwenview/app/sidebar.h @@ -0,0 +1,88 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef SIDEBAR_H +#define SIDEBAR_H + +// Qt +#include +#include + +namespace Gwenview +{ + +class SideBar; + +struct SideBarGroupPrivate; +class SideBarGroup : public QFrame +{ + Q_OBJECT +public: + SideBarGroup(const QString& title); + ~SideBarGroup(); + + void addWidget(QWidget*); + void addAction(QAction*); + void clear(); + +protected: + virtual void paintEvent(QPaintEvent*); + +private: + SideBarGroupPrivate* const d; +}; + +struct SideBarPagePrivate; +class SideBarPage : public QWidget +{ + Q_OBJECT +public: + SideBarPage(const QString& title); + ~SideBarPage(); + void addWidget(QWidget*); + void addStretch(); + + const QString& title() const; + +private: + SideBarPagePrivate* const d; +}; + +struct SideBarPrivate; +class SideBar : public QTabWidget +{ + Q_OBJECT +public: + SideBar(QWidget* parent); + ~SideBar(); + + void addPage(SideBarPage*); + + QString currentPage() const; + void setCurrentPage(const QString& name); + + virtual QSize sizeHint() const; + +private: + SideBarPrivate* const d; +}; + +} // namespace + +#endif /* SIDEBAR_H */ diff --git a/gwenview/app/slideshow.desktop b/gwenview/app/slideshow.desktop new file mode 100644 index 00000000..ec1cec05 --- /dev/null +++ b/gwenview/app/slideshow.desktop @@ -0,0 +1,66 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KonqPopupMenu/Plugin,inode/directory +Actions=slideshow; + +[Desktop Action slideshow] +Name=Start a Slideshow +Name[ar]=ابدأ عرض شرائح +Name[ast]=Entamar una presentación +Name[bg]=Пускане на прожекция +Name[bs]=Pokreni slajdšou +Name[ca]=Inicia una presentació amb diapositives +Name[ca@valencia]=Inicia una presentació amb diapositives +Name[cs]=Spustit promítání +Name[da]=Start et diasshow +Name[de]=Diaschau starten +Name[el]=Έναρξη προβολής σλάιντ +Name[en_GB]=Start a Slideshow +Name[es]=Comenzar una presentación +Name[et]=Käivita slaidiseanss +Name[eu]=Hasi diapositiba-aurkezpena +Name[fi]=Käynnistä diaesitys +Name[fr]=Démarrer un diaporama +Name[ga]=Rith Taispeántas Sleamhnán +Name[gl]=Iniciar unha presentación +Name[hne]=स्लाइड सो चालू करव +Name[hr]=Pokreni prezentaciju +Name[hu]=Diavetítés +Name[ia]=Starta un presentation (slideshow) +Name[is]=Hefja skyggnusýningu +Name[it]=Avvia una presentazione +Name[ja]=スライドショーを開始 +Name[kk]=Слайд көрсетілімін бастау +Name[km]=ចាប់ផ្ដើម​បញ្ចាំង​ស្លាយ +Name[ko]=슬라이드 쇼 시작 +Name[ku]=NîşandanaSlayt Bide Destpêkirin +Name[lt]=Pradėti skaidrių peržiūrą +Name[lv]=Sākt slīdrādi +Name[mr]=स्लाइडशो सुरु करा +Name[nb]=Start lysbildeframvisning +Name[nds]=En Diaschau starten +Name[nl]=Een diashow starten +Name[nn]=Start ei lysbiletframvising +Name[pa]=ਇੱਕ ਸਲਾਈਡ-ਸ਼ੋ ਸ਼ੁਰੂ +Name[pl]=Uruchom pokaz slajdów +Name[pt]=Iniciar uma Apresentação +Name[pt_BR]=Iniciar uma apresentação de slides +Name[ro]=Pornește o prezentare +Name[ru]=Запустить слайд-шоу +Name[si]=සලයිඩ දර්ශනය ආරඹන්න +Name[sk]=Spustiť prezentáciu +Name[sl]=Začni predstavitev +Name[sr]=Покрени слајдшоу +Name[sr@ijekavian]=Покрени слајдшоу +Name[sr@ijekavianlatin]=Pokreni slajdšou +Name[sr@latin]=Pokreni slajdšou +Name[sv]=Starta ett bildspel +Name[th]=เริ่มการนำเสนอ +Name[tr]=Bir Slayt Gösterisi Başlat +Name[ug]=تام تەسۋىرى باشلايدۇ +Name[uk]=Почати показ слайдів +Name[x-test]=xxStart a Slideshowxx +Name[zh_CN]=以幻灯模式启动 +Name[zh_TW]=開始投影秀 +Icon=gwenview +Exec=gwenview -s %u diff --git a/gwenview/app/splitter.h b/gwenview/app/splitter.h new file mode 100644 index 00000000..f4e90b16 --- /dev/null +++ b/gwenview/app/splitter.h @@ -0,0 +1,76 @@ +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef SPLITTER_H +#define SPLITTER_H + +// Qt +#include +#include + +namespace Gwenview +{ + +class SplitterHandle : public QSplitterHandle +{ +public: + SplitterHandle(Qt::Orientation orientation, QSplitter* parent) + : QSplitterHandle(orientation, parent) + {} + +protected: + virtual void paintEvent(QPaintEvent* event) + { + QSplitterHandle::paintEvent(event); + + QPainter painter(this); + painter.setPen(palette().mid().color()); + if (orientation() == Qt::Vertical) { + painter.drawLine(rect().topLeft(), rect().topRight()); + painter.drawLine(rect().bottomLeft(), rect().bottomRight()); + } else { + //painter.drawLine(rect().topLeft(), rect().bottomLeft()); + painter.drawLine(rect().topRight(), rect().bottomRight()); + } + } +}; + +/** + * Home made splitter to be able to define a custom handle which is border with + * "mid" colored lines. + */ +class Splitter : public QSplitter +{ +public: + Splitter(Qt::Orientation orientation, QWidget* parent) + : QSplitter(orientation, parent) + { + setHandleWidth(handleWidth() + 2); + } + +protected: + virtual QSplitterHandle* createHandle() + { + return new SplitterHandle(orientation(), this); + } +}; + +} // namespace + +#endif /* SPLITTER_H */ diff --git a/gwenview/app/startmainpage.cpp b/gwenview/app/startmainpage.cpp new file mode 100644 index 00000000..948fd48a --- /dev/null +++ b/gwenview/app/startmainpage.cpp @@ -0,0 +1,337 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "startmainpage.moc" + +#include + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include + +// Local +#include +#include +#include +#include +#include +#include + +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE +#include +#endif + +namespace Gwenview +{ + +class HistoryThumbnailViewHelper : public AbstractThumbnailViewHelper +{ +public: + HistoryThumbnailViewHelper(QObject* parent) + : AbstractThumbnailViewHelper(parent) + {} + + virtual void showContextMenu(QWidget*) + { + } + + virtual void showMenuForUrlDroppedOnViewport(QWidget*, const KUrl::List&) + { + } + + virtual void showMenuForUrlDroppedOnDir(QWidget*, const KUrl::List&, const KUrl&) + { + } +}; + +/** + * Inherit from QStyledItemDelegate to match KFilePlacesViewDelegate sizeHint + * height. + */ +class HistoryViewDelegate : public QStyledItemDelegate +{ +public: + HistoryViewDelegate(QObject* parent = 0) + : QStyledItemDelegate(parent) + {} + + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const + { + QSize sh = QStyledItemDelegate::sizeHint(option, index); + int iconSize = static_cast(parent())->iconSize().height(); + // Copied from KFilePlacesViewDelegate::sizeHint() + int height = option.fontMetrics.height() / 2 + qMax(iconSize, option.fontMetrics.height()); + sh.setHeight(qMax(sh.height(), height)); + return sh; + } +}; + +struct StartMainPagePrivate : public Ui_StartMainPage +{ + StartMainPage* q; + GvCore* mGvCore; + KFilePlacesModel* mBookmarksModel; + bool mSearchUiInitialized; + + void setupSearchUi() + { +#ifdef GWENVIEW_SEMANTICINFO_BACKEND_BALOO + mTagView->setModel(TagModel::createAllTagsModel(mTagView, mGvCore->semanticInfoBackEnd())); + mTagView->show(); + mTagLabel->hide(); +#else + mTagView->hide(); + mTagLabel->hide(); +#endif + } + + void updateHistoryTab() + { + mHistoryWidget->setVisible(GwenviewConfig::historyEnabled()); + mHistoryDisabledLabel->setVisible(!GwenviewConfig::historyEnabled()); + } +}; + +static void initViewPalette(QAbstractItemView* view, const QColor& fgColor) +{ + QWidget* viewport = view->viewport(); + QPalette palette = viewport->palette(); + palette.setColor(viewport->backgroundRole(), Qt::transparent); + palette.setColor(QPalette::WindowText, fgColor); + palette.setColor(QPalette::Text, fgColor); + + // QListView uses QStyledItemDelegate, which uses the view palette for + // foreground color, while KFilePlacesView uses the viewport palette. + viewport->setPalette(palette); + view->setPalette(palette); +} + +static bool styleIsGtkBased() +{ + const char* name = QApplication::style()->metaObject()->className(); + return qstrcmp(name, "QGtkStyle") == 0; +} + +StartMainPage::StartMainPage(QWidget* parent, GvCore* gvCore) +: QFrame(parent) +, d(new StartMainPagePrivate) +{ + d->q = this; + d->mGvCore = gvCore; + d->mSearchUiInitialized = false; + + d->setupUi(this); + if (styleIsGtkBased()) { + // Gtk-based styles do not apply the correct background color on tabs. + // As a workaround, use the Plastique style instead. + QStyle* fix = new QPlastiqueStyle(); + fix->setParent(this); + d->mHistoryWidget->tabBar()->setStyle(fix); + d->mPlacesTagsWidget->tabBar()->setStyle(fix); + } + setFrameStyle(QFrame::NoFrame); + + // Bookmark view + d->mBookmarksModel = new KFilePlacesModel(this); + + d->mBookmarksView->setModel(d->mBookmarksModel); + d->mBookmarksView->setAutoResizeItemsEnabled(false); + + connect(d->mBookmarksView, SIGNAL(urlChanged(KUrl)), + SIGNAL(urlSelected(KUrl))); + + // Tag view + connect(d->mTagView, SIGNAL(clicked(QModelIndex)), + SLOT(slotTagViewClicked(QModelIndex))); + + // Recent folder view + connect(d->mRecentFoldersView, SIGNAL(indexActivated(QModelIndex)), + SLOT(slotListViewActivated(QModelIndex))); + + connect(d->mRecentFoldersView, SIGNAL(customContextMenuRequested(QPoint)), + SLOT(showRecentFoldersViewContextMenu(QPoint))); + + // Url bag view + d->mRecentUrlsView->setItemDelegate(new HistoryViewDelegate(d->mRecentUrlsView)); + + connect(d->mRecentUrlsView, SIGNAL(customContextMenuRequested(QPoint)), + SLOT(showRecentFoldersViewContextMenu(QPoint))); + + if (KGlobalSettings::singleClick()) { + if (KGlobalSettings::changeCursorOverIcon()) { + d->mRecentUrlsView->setCursor(Qt::PointingHandCursor); + } + connect(d->mRecentUrlsView, SIGNAL(clicked(QModelIndex)), + SLOT(slotListViewActivated(QModelIndex))); + } else { + connect(d->mRecentUrlsView, SIGNAL(doubleClicked(QModelIndex)), + SLOT(slotListViewActivated(QModelIndex))); + } + + d->updateHistoryTab(); + connect(GwenviewConfig::self(), SIGNAL(configChanged()), + SLOT(loadConfig())); + + d->mRecentFoldersView->setFocus(); +} + +StartMainPage::~StartMainPage() +{ + delete d; +} + +void StartMainPage::slotTagViewClicked(const QModelIndex& index) +{ +#ifdef GWENVIEW_SEMANTICINFO_BACKEND_BALOO + if (!index.isValid()) { + return; + } + // FIXME: Check label encoding + const QString tag = index.data().toString(); + emit urlSelected(KUrl("tags:/" + tag)); +#endif +} + +void StartMainPage::applyPalette(const QPalette& newPalette) +{ + QColor fgColor = newPalette.text().color(); + + QPalette pal = palette(); + pal.setBrush(backgroundRole(), newPalette.base()); + pal.setBrush(QPalette::Button, newPalette.base()); + pal.setBrush(QPalette::WindowText, fgColor); + pal.setBrush(QPalette::ButtonText, fgColor); + pal.setBrush(QPalette::Text, fgColor); + setPalette(pal); + + initViewPalette(d->mBookmarksView, fgColor); + initViewPalette(d->mTagView, fgColor); + initViewPalette(d->mRecentFoldersView, fgColor); + initViewPalette(d->mRecentUrlsView, fgColor); +} + +void StartMainPage::slotListViewActivated(const QModelIndex& index) +{ + if (!index.isValid()) { + return; + } + QVariant data = index.data(KFilePlacesModel::UrlRole); + KUrl url = data.toUrl(); + + // Prevent dir lister error + if (!url.isValid()) { + kError() << "Tried to open an invalid url"; + return; + } + emit urlSelected(url); +} + +void StartMainPage::showEvent(QShowEvent* event) +{ + if (GwenviewConfig::historyEnabled()) { + if (!d->mRecentFoldersView->model()) { + d->mRecentFoldersView->setThumbnailViewHelper(new HistoryThumbnailViewHelper(d->mRecentFoldersView)); + d->mRecentFoldersView->setModel(d->mGvCore->recentFoldersModel()); + PreviewItemDelegate* delegate = new PreviewItemDelegate(d->mRecentFoldersView); + delegate->setContextBarActions(PreviewItemDelegate::NoAction); + delegate->setTextElideMode(Qt::ElideLeft); + d->mRecentFoldersView->setItemDelegate(delegate); + d->mRecentFoldersView->setThumbnailWidth(128); + d->mRecentFoldersView->setCreateThumbnailsForRemoteUrls(false); + QModelIndex index = d->mRecentFoldersView->model()->index(0, 0); + if (index.isValid()) { + d->mRecentFoldersView->setCurrentIndex(index); + } + } + if (!d->mRecentUrlsView->model()) { + d->mRecentUrlsView->setModel(d->mGvCore->recentUrlsModel()); + } + } + if (!d->mSearchUiInitialized) { + d->mSearchUiInitialized = true; + d->setupSearchUi(); + } + QFrame::showEvent(event); +} + +void StartMainPage::showRecentFoldersViewContextMenu(const QPoint& pos) +{ + QAbstractItemView* view = qobject_cast(sender()); + KUrl url; + QModelIndex index = view->indexAt(pos); + if (index.isValid()) { + QVariant data = index.data(KFilePlacesModel::UrlRole); + url = data.toUrl(); + } + + // Create menu + QMenu menu(this); + bool fromRecentUrls = view == d->mRecentUrlsView; + QAction* addToPlacesAction = fromRecentUrls ? 0 : menu.addAction(KIcon("bookmark-new"), i18n("Add to Places")); + QAction* removeAction = menu.addAction(KIcon("edit-delete"), fromRecentUrls ? i18n("Forget this URL") : i18n("Forget this Folder")); + menu.addSeparator(); + QAction* clearAction = menu.addAction(KIcon("edit-delete-all"), i18n("Forget All")); + + if (!index.isValid()) { + if (addToPlacesAction) { + addToPlacesAction->setEnabled(false); + } + removeAction->setEnabled(false); + } + + // Handle menu + QAction* action = menu.exec(view->mapToGlobal(pos)); + if (!action) { + return; + } + if (action == addToPlacesAction) { + QString text = url.fileName(); + if (text.isEmpty()) { + text = url.pathOrUrl(); + } + d->mBookmarksModel->addPlace(text, url); + } else if (action == removeAction) { + view->model()->removeRow(index.row()); + } else if (action == clearAction) { + view->model()->removeRows(0, view->model()->rowCount()); + } +} + +void StartMainPage::loadConfig() +{ + d->updateHistoryTab(); + applyPalette(d->mGvCore->palette(GvCore::NormalViewPalette)); +} + +ThumbnailView* StartMainPage::recentFoldersView() const +{ + return d->mRecentFoldersView; +} + +} // namespace diff --git a/gwenview/app/startmainpage.h b/gwenview/app/startmainpage.h new file mode 100644 index 00000000..f06aed24 --- /dev/null +++ b/gwenview/app/startmainpage.h @@ -0,0 +1,74 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef STARTMAINPAGE_H +#define STARTMAINPAGE_H + +// Qt +#include + +// KDE + +// Local + +class QModelIndex; +class QPalette; +class QShowEvent; + +class KUrl; + +namespace Gwenview +{ + +class GvCore; +class ThumbnailView; + +struct StartMainPagePrivate; +class StartMainPage : public QFrame +{ + Q_OBJECT +public: + StartMainPage(QWidget* parent, GvCore*); + ~StartMainPage(); + + void applyPalette(const QPalette&); + ThumbnailView* recentFoldersView() const; + +Q_SIGNALS: + void urlSelected(const KUrl& url); + +public Q_SLOTS: + void loadConfig(); + +protected: + virtual void showEvent(QShowEvent*); + +private Q_SLOTS: + void slotListViewActivated(const QModelIndex& index); + void showRecentFoldersViewContextMenu(const QPoint& pos); + void slotTagViewClicked(const QModelIndex& index); + +private: + StartMainPagePrivate* const d; +}; + +} // namespace + +#endif /* STARTMAINPAGE_H */ diff --git a/gwenview/app/startmainpage.ui b/gwenview/app/startmainpage.ui new file mode 100644 index 00000000..f85e738a --- /dev/null +++ b/gwenview/app/startmainpage.ui @@ -0,0 +1,190 @@ + + + StartMainPage + + + + 0 + 0 + 584 + 618 + + + + true + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + + + 0 + 0 + + + + History has been disabled. + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + 0 + + + true + + + + Recent Folders + + + + + + QAbstractItemView::NoEditTriggers + + + + + + + + Recent URLs + + + + + + Qt::CustomContextMenu + + + QFrame::NoFrame + + + QAbstractItemView::NoEditTriggers + + + + 32 + 32 + + + + + + + + + + + + + + + 0 + 0 + + + + 0 + + + true + + + + Places + + + + + + PointingHandCursor + + + QFrame::NoFrame + + + + 32 + 32 + + + + + + + + + Tags + + + + + + Browsing by tags is not available. Make sure Nepomuk is properly installed on your computer. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + PointingHandCursor + + + QFrame::NoFrame + + + + 32 + 32 + + + + + + + + + + + + + KTabWidget + QTabWidget +
ktabwidget.h
+ 1 +
+ + KFilePlacesView + QListView +
kfileplacesview.h
+
+ + Gwenview::ThumbnailView + QListView +
lib/thumbnailview/thumbnailview.h
+
+
+ + +
diff --git a/gwenview/app/thumbnailviewhelper.cpp b/gwenview/app/thumbnailviewhelper.cpp new file mode 100644 index 00000000..4c676ff3 --- /dev/null +++ b/gwenview/app/thumbnailviewhelper.cpp @@ -0,0 +1,117 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "thumbnailviewhelper.moc" + +#include + +// Qt +#include +#include + +// KDE +#include +#include +#include +#include + +// Local +#include +#include "fileoperations.h" + +namespace Gwenview +{ + +struct ThumbnailViewHelperPrivate +{ + KActionCollection* mActionCollection; + KUrl mCurrentDirUrl; + + void addActionToMenu(KMenu& popup, const char* name) + { + QAction* action = mActionCollection->action(name); + if (!action) { + kWarning() << "Unknown action" << name; + return; + } + if (action->isEnabled()) { + popup.addAction(action); + } + } +}; + +ThumbnailViewHelper::ThumbnailViewHelper(QObject* parent, KActionCollection* actionCollection) +: AbstractThumbnailViewHelper(parent) +, d(new ThumbnailViewHelperPrivate) +{ + d->mActionCollection = actionCollection; +} + +ThumbnailViewHelper::~ThumbnailViewHelper() +{ + delete d; +} + +void ThumbnailViewHelper::setCurrentDirUrl(const KUrl& url) +{ + d->mCurrentDirUrl = url; +} + +void ThumbnailViewHelper::showContextMenu(QWidget* parent) +{ + KMenu popup(parent); + if (d->mCurrentDirUrl.protocol() == "trash") { + d->addActionToMenu(popup, "file_restore"); + d->addActionToMenu(popup, "file_delete"); + popup.addSeparator(); + d->addActionToMenu(popup, "file_show_properties"); + } else { + d->addActionToMenu(popup, "file_create_folder"); + popup.addSeparator(); + d->addActionToMenu(popup, "file_rename"); + d->addActionToMenu(popup, "file_trash"); + d->addActionToMenu(popup, "file_delete"); + popup.addSeparator(); + d->addActionToMenu(popup, "file_copy_to"); + d->addActionToMenu(popup, "file_move_to"); + d->addActionToMenu(popup, "file_link_to"); + popup.addSeparator(); + d->addActionToMenu(popup, "file_open_with"); +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + d->addActionToMenu(popup, "edit_tags"); +#endif + popup.addSeparator(); + d->addActionToMenu(popup, "file_show_properties"); + } + popup.exec(QCursor::pos()); +} + +void ThumbnailViewHelper::showMenuForUrlDroppedOnViewport(QWidget* parent, const KUrl::List& lst) +{ + showMenuForUrlDroppedOnDir(parent, lst, d->mCurrentDirUrl); +} + +void ThumbnailViewHelper::showMenuForUrlDroppedOnDir(QWidget* parent, const KUrl::List& urlList, const KUrl& destUrl) +{ + FileOperations::showMenuForDroppedUrls(parent, urlList, destUrl); +} + +} // namespace diff --git a/gwenview/app/thumbnailviewhelper.h b/gwenview/app/thumbnailviewhelper.h new file mode 100644 index 00000000..c89ff9e8 --- /dev/null +++ b/gwenview/app/thumbnailviewhelper.h @@ -0,0 +1,61 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef THUMBNAILVIEWHELPER_H +#define THUMBNAILVIEWHELPER_H + +// Qt + +// KDE + +// Local +#include + +class KActionCollection; + +namespace Gwenview +{ + +class SortedDirModel; + +struct ThumbnailViewHelperPrivate; +class ThumbnailViewHelper : public AbstractThumbnailViewHelper +{ + Q_OBJECT +public: + ThumbnailViewHelper(QObject* parent, KActionCollection*); + ~ThumbnailViewHelper(); + + virtual void showContextMenu(QWidget* parent); + + virtual void showMenuForUrlDroppedOnViewport(QWidget* parent, const KUrl::List&); + + virtual void showMenuForUrlDroppedOnDir(QWidget* parent, const KUrl::List&, const KUrl&); + +public Q_SLOTS: + void setCurrentDirUrl(const KUrl&); + +private: + ThumbnailViewHelperPrivate* const d; +}; + +} // namespace + +#endif /* THUMBNAILVIEWHELPER_H */ diff --git a/gwenview/app/viewmainpage.cpp b/gwenview/app/viewmainpage.cpp new file mode 100644 index 00000000..7caf0992 --- /dev/null +++ b/gwenview/app/viewmainpage.cpp @@ -0,0 +1,793 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "viewmainpage.moc" + +// Qt +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "fileoperations.h" +#include +#include "splitter.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +const int ViewMainPage::MaxViewCount = 6; + +static QString rgba(const QColor &color) +{ + return QString::fromAscii("rgba(%1, %2, %3, %4)") + .arg(color.red()) + .arg(color.green()) + .arg(color.blue()) + .arg(color.alpha()); +} + +static QString gradient(Qt::Orientation orientation, const QColor &color, int value) +{ + int x2, y2; + if (orientation == Qt::Horizontal) { + x2 = 0; + y2 = 1; + } else { + x2 = 1; + y2 = 0; + } + QString grad = + "qlineargradient(x1:0, y1:0, x2:%1, y2:%2," + "stop:0 %3, stop: 1 %4)"; + return grad + .arg(x2) + .arg(y2) + .arg(rgba(PaintUtils::adjustedHsv(color, 0, 0, qMin(255 - color.value(), value / 2)))) + .arg(rgba(PaintUtils::adjustedHsv(color, 0, 0, -qMin(color.value(), value / 2)))) + ; +} + +/* + * Layout of mThumbnailSplitter is: + * + * +-mThumbnailSplitter------------------------------------------------+ + * |+-mAdapterContainer-----------------------------------------------+| + * ||+-mDocumentViewContainer----------------------------------------+|| + * |||+-DocumentView----------------++-DocumentView-----------------+||| + * |||| || |||| + * |||| || |||| + * |||| || |||| + * |||| || |||| + * |||| || |||| + * |||| || |||| + * |||+-----------------------------++------------------------------+||| + * ||+---------------------------------------------------------------+|| + * ||+-mToolContainer------------------------------------------------+|| + * ||| ||| + * ||+---------------------------------------------------------------+|| + * ||+-mStatusBarContainer-------------------------------------------+|| + * |||[mToggleSideBarButton][mToggleThumbnailBarButton] [mZoomWidget]||| + * ||+---------------------------------------------------------------+|| + * |+-----------------------------------------------------------------+| + * |===================================================================| + * |+-mThumbnailBar---------------------------------------------------+| + * || || + * || || + * |+-----------------------------------------------------------------+| + * +-------------------------------------------------------------------+ + */ +struct ViewMainPagePrivate +{ + ViewMainPage* q; + SlideShow* mSlideShow; + KActionCollection* mActionCollection; + GvCore* mGvCore; + KModelIndexProxyMapper* mDirModelToBarModelProxyMapper; + QSplitter *mThumbnailSplitter; + QWidget* mAdapterContainer; + DocumentViewController* mDocumentViewController; + QList mDocumentViews; + DocumentViewSynchronizer* mSynchronizer; + QToolButton* mToggleSideBarButton; + QToolButton* mToggleThumbnailBarButton; + ZoomWidget* mZoomWidget; + DocumentViewContainer* mDocumentViewContainer; + SlideContainer* mToolContainer; + QWidget* mStatusBarContainer; + ThumbnailBarView* mThumbnailBar; + KToggleAction* mToggleThumbnailBarAction; + KToggleAction* mSynchronizeAction; + QCheckBox* mSynchronizeCheckBox; + + // Activity Resource events reporting needs to be above KPart, + // in the shell itself, to avoid problems with other MDI applications + // that use this KPart + QHash mActivityResources; + + bool mFullScreenMode; + bool mCompareMode; + bool mThumbnailBarVisibleBeforeFullScreen; + + void setupThumbnailBar() + { + mThumbnailBar = new ThumbnailBarView; + ThumbnailBarItemDelegate* delegate = new ThumbnailBarItemDelegate(mThumbnailBar); + mThumbnailBar->setItemDelegate(delegate); + mThumbnailBar->setVisible(GwenviewConfig::thumbnailBarIsVisible()); + mThumbnailBar->setSelectionMode(QAbstractItemView::ExtendedSelection); + } + + void setupThumbnailBarStyleSheet() + { + QPalette pal = mGvCore->palette(GvCore::NormalViewPalette); + mThumbnailBar->setPalette(pal); + Qt::Orientation orientation = mThumbnailBar->orientation(); + QColor bgColor = pal.color(QPalette::Normal, QPalette::Base); + QColor bgSelColor = pal.color(QPalette::Normal, QPalette::Highlight); + + // Avoid dark and bright colors + bgColor.setHsv(bgColor.hue(), bgColor.saturation(), (127 + 3 * bgColor.value()) / 4); + + QColor leftBorderColor = PaintUtils::adjustedHsv(bgColor, 0, 0, qMin(20, 255 - bgColor.value())); + QColor rightBorderColor = PaintUtils::adjustedHsv(bgColor, 0, 0, -qMin(40, bgColor.value())); + QColor borderSelColor = PaintUtils::adjustedHsv(bgSelColor, 0, 0, -qMin(60, bgSelColor.value())); + + QString itemCss = + "QListView::item {" + " background-color: %1;" + " border-left: 1px solid %2;" + " border-right: 1px solid %3;" + "}"; + itemCss = itemCss.arg( + gradient(orientation, bgColor, 46), + gradient(orientation, leftBorderColor, 36), + gradient(orientation, rightBorderColor, 26)); + + QString itemSelCss = + "QListView::item:selected {" + " background-color: %1;" + " border-left: 1px solid %2;" + " border-right: 1px solid %2;" + "}"; + itemSelCss = itemSelCss.arg( + gradient(orientation, bgSelColor, 56), + rgba(borderSelColor)); + + QString css = itemCss + itemSelCss; + if (orientation == Qt::Vertical) { + css.replace("left", "top").replace("right", "bottom"); + } + + mThumbnailBar->setStyleSheet(css); + } + + void setupAdapterContainer() + { + mAdapterContainer = new QWidget; + + QVBoxLayout* layout = new QVBoxLayout(mAdapterContainer); + layout->setMargin(0); + layout->setSpacing(0); + mDocumentViewContainer = new DocumentViewContainer; + mDocumentViewContainer->setAutoFillBackground(true); + mDocumentViewContainer->setBackgroundRole(QPalette::Base); + layout->addWidget(mDocumentViewContainer); + layout->addWidget(mToolContainer); + layout->addWidget(mStatusBarContainer); + } + + void setupDocumentViewController() + { + mDocumentViewController = new DocumentViewController(mActionCollection, q); + mDocumentViewController->setZoomWidget(mZoomWidget); + mDocumentViewController->setToolContainer(mToolContainer); + mSynchronizer = new DocumentViewSynchronizer(&mDocumentViews, q); + } + + DocumentView* createDocumentView() + { + DocumentView* view = mDocumentViewContainer->createView(); + + // Connect context menu + // If you need to connect another view signal, make sure it is disconnected in deleteDocumentView + QObject::connect(view, SIGNAL(contextMenuRequested()), + q, SLOT(showContextMenu())); + + QObject::connect(view, SIGNAL(completed()), + q, SIGNAL(completed())); + QObject::connect(view, SIGNAL(previousImageRequested()), + q, SIGNAL(previousImageRequested())); + QObject::connect(view, SIGNAL(nextImageRequested()), + q, SIGNAL(nextImageRequested())); + QObject::connect(view, SIGNAL(captionUpdateRequested(QString)), + q, SIGNAL(captionUpdateRequested(QString))); + QObject::connect(view, SIGNAL(toggleFullScreenRequested()), + q, SIGNAL(toggleFullScreenRequested())); + QObject::connect(view, SIGNAL(focused(DocumentView*)), + q, SLOT(slotViewFocused(DocumentView*))); + QObject::connect(view, SIGNAL(hudTrashClicked(DocumentView*)), + q, SLOT(trashView(DocumentView*))); + QObject::connect(view, SIGNAL(hudDeselectClicked(DocumentView*)), + q, SLOT(deselectView(DocumentView*))); + + QObject::connect(view, SIGNAL(videoFinished()), + mSlideShow, SLOT(resumeAndGoToNextUrl())); + + mDocumentViews << view; + mActivityResources.insert(view, new KActivities::ResourceInstance(q->window()->winId(), view)); + + return view; + } + + void deleteDocumentView(DocumentView* view) + { + if (mDocumentViewController->view() == view) { + mDocumentViewController->setView(0); + } + + // Make sure we do not get notified about this view while it is going away. + // mDocumentViewController->deleteView() animates the view deletion so + // the view still exists for a short while when we come back to the + // event loop) + QObject::disconnect(view, 0, q, 0); + QObject::disconnect(view, 0, mSlideShow, 0); + + mDocumentViews.removeOne(view); + mActivityResources.remove(view); + mDocumentViewContainer->deleteView(view); + } + + void setupToolContainer() + { + mToolContainer = new SlideContainer; + } + + void setupStatusBar() + { + mStatusBarContainer = new QWidget; + mToggleSideBarButton = new StatusBarToolButton; + mToggleThumbnailBarButton = new StatusBarToolButton; + mZoomWidget = new ZoomWidget; + mSynchronizeCheckBox = new QCheckBox(i18n("Synchronize")); + mSynchronizeCheckBox->hide(); + + QHBoxLayout* layout = new QHBoxLayout(mStatusBarContainer); + layout->setMargin(0); + layout->setSpacing(0); + layout->addWidget(mToggleSideBarButton); + layout->addWidget(mToggleThumbnailBarButton); + layout->addStretch(); + layout->addWidget(mSynchronizeCheckBox); + layout->addStretch(); + layout->addWidget(mZoomWidget); + } + + void setupSplitter() + { + Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation(); + mThumbnailSplitter = new Splitter(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal, q); + mThumbnailBar->setOrientation(orientation); + mThumbnailBar->setThumbnailAspectRatio(GwenviewConfig::thumbnailAspectRatio()); + mThumbnailBar->setRowCount(GwenviewConfig::thumbnailBarRowCount()); + mThumbnailSplitter->addWidget(mAdapterContainer); + mThumbnailSplitter->addWidget(mThumbnailBar); + mThumbnailSplitter->setSizes(GwenviewConfig::thumbnailSplitterSizes()); + + QVBoxLayout* layout = new QVBoxLayout(q); + layout->setMargin(0); + layout->addWidget(mThumbnailSplitter); + } + + void saveSplitterConfig() + { + if (mThumbnailBar->isVisible()) { + GwenviewConfig::setThumbnailSplitterSizes(mThumbnailSplitter->sizes()); + } + } + + DocumentView* currentView() const + { + return mDocumentViewController->view(); + } + + void setCurrentView(DocumentView* view) + { + DocumentView* oldView = currentView(); + if (view == oldView) { + return; + } + if (oldView) { + oldView->setCurrent(false); + Q_ASSERT(mActivityResources.contains(oldView)); + mActivityResources.value(oldView)->notifyFocusedOut(); + } + view->setCurrent(true); + mDocumentViewController->setView(view); + mSynchronizer->setCurrentView(view); + + QModelIndex index = indexForView(view); + if (index.isValid()) { + // Index may be invalid when Gwenview is started as + // `gwenview /foo/image.png` because in this situation it loads image.png + // *before* listing /foo (because it matters less to the user) + mThumbnailBar->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Current); + } + + Q_ASSERT(mActivityResources.contains(view)); + mActivityResources.value(view)->notifyFocusedIn(); + } + + QModelIndex indexForView(DocumentView* view) const + { + KUrl url = view->url(); + if (!url.isValid()) { + kWarning() << "View does not display any document!"; + return QModelIndex(); + } + + SortedDirModel* dirModel = mGvCore->sortedDirModel(); + QModelIndex srcIndex = dirModel->indexForUrl(url); + if (!mDirModelToBarModelProxyMapper) { + // Delay the initialization of the mapper to its first use because + // mThumbnailBar->model() is not set after ViewMainPage ctor is + // done. + const_cast(this)->mDirModelToBarModelProxyMapper = new KModelIndexProxyMapper(dirModel, mThumbnailBar->model(), q); + } + QModelIndex index = mDirModelToBarModelProxyMapper->mapLeftToRight(srcIndex); + return index; + } + + void applyPalette(bool fullScreenMode) + { + mDocumentViewContainer->setPalette(mGvCore->palette(fullScreenMode ? GvCore::FullScreenViewPalette : GvCore::NormalViewPalette)); + setupThumbnailBarStyleSheet(); + } +}; + +ViewMainPage::ViewMainPage(QWidget* parent, SlideShow* slideShow, KActionCollection* actionCollection, GvCore* gvCore) +: QWidget(parent) +, d(new ViewMainPagePrivate) +{ + d->q = this; + d->mDirModelToBarModelProxyMapper = 0; // Initialized later + d->mSlideShow = slideShow; + d->mActionCollection = actionCollection; + d->mGvCore = gvCore; + d->mFullScreenMode = false; + d->mCompareMode = false; + d->mThumbnailBarVisibleBeforeFullScreen = false; + + QShortcut* goToBrowseModeShortcut = new QShortcut(this); + goToBrowseModeShortcut->setKey(Qt::Key_Return); + connect(goToBrowseModeShortcut, SIGNAL(activated()), SIGNAL(goToBrowseModeRequested())); + + d->setupToolContainer(); + d->setupStatusBar(); + + d->setupAdapterContainer(); + + d->setupThumbnailBar(); + + d->setupSplitter(); + + d->setupDocumentViewController(); + + KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection); + + d->mToggleThumbnailBarAction = view->add(QString("toggle_thumbnailbar")); + d->mToggleThumbnailBarAction->setText(i18n("Thumbnail Bar")); + d->mToggleThumbnailBarAction->setIcon(KIcon("folder-image")); + d->mToggleThumbnailBarAction->setShortcut(Qt::CTRL | Qt::Key_B); + d->mToggleThumbnailBarAction->setChecked(GwenviewConfig::thumbnailBarIsVisible()); + connect(d->mToggleThumbnailBarAction, SIGNAL(triggered(bool)), + this, SLOT(setThumbnailBarVisibility(bool))); + d->mToggleThumbnailBarButton->setDefaultAction(d->mToggleThumbnailBarAction); + + d->mSynchronizeAction = view->add("synchronize_views"); + d->mSynchronizeAction->setText(i18n("Synchronize")); + d->mSynchronizeAction->setShortcut(Qt::CTRL | Qt::Key_Y); + connect(d->mSynchronizeAction, SIGNAL(toggled(bool)), + d->mSynchronizer, SLOT(setActive(bool))); + // Ensure mSynchronizeAction and mSynchronizeCheckBox are in sync + connect(d->mSynchronizeAction, SIGNAL(toggled(bool)), + d->mSynchronizeCheckBox, SLOT(setChecked(bool))); + connect(d->mSynchronizeCheckBox, SIGNAL(toggled(bool)), + d->mSynchronizeAction, SLOT(setChecked(bool))); +} + +ViewMainPage::~ViewMainPage() +{ + delete d; +} + +void ViewMainPage::loadConfig() +{ + d->applyPalette(false /* fullScreenMode */); + + // FIXME: Not symetric with saveConfig(). Check if it matters. + Q_FOREACH(DocumentView * view, d->mDocumentViews) { + view->loadAdapterConfig(); + } + + Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation(); + d->mThumbnailSplitter->setOrientation(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal); + d->mThumbnailBar->setOrientation(orientation); + d->setupThumbnailBarStyleSheet(); + + int oldRowCount = d->mThumbnailBar->rowCount(); + int newRowCount = GwenviewConfig::thumbnailBarRowCount(); + if (oldRowCount != newRowCount) { + d->mThumbnailBar->setUpdatesEnabled(false); + int gridSize = d->mThumbnailBar->gridSize().width(); + + d->mThumbnailBar->setRowCount(newRowCount); + + // Adjust splitter to ensure thumbnail size remains the same + int delta = (newRowCount - oldRowCount) * gridSize; + QList sizes = d->mThumbnailSplitter->sizes(); + Q_ASSERT(sizes.count() == 2); + sizes[0] -= delta; + sizes[1] += delta; + d->mThumbnailSplitter->setSizes(sizes); + + d->mThumbnailBar->setUpdatesEnabled(true); + } + + if (GwenviewConfig::showLockZoomButton()) { + d->mZoomWidget->setZoomLocked(GwenviewConfig::lockZoom()); + } else { + d->mZoomWidget->setLockZoomButtonVisible(false); + } +} + +void ViewMainPage::saveConfig() +{ + d->saveSplitterConfig(); + GwenviewConfig::setThumbnailBarIsVisible(d->mToggleThumbnailBarAction->isChecked()); + if (GwenviewConfig::showLockZoomButton()) { + GwenviewConfig::setLockZoom(d->mZoomWidget->isZoomLocked()); + } +} + +void ViewMainPage::setThumbnailBarVisibility(bool visible) +{ + d->saveSplitterConfig(); + d->mThumbnailBar->setVisible(visible); +} + +int ViewMainPage::statusBarHeight() const +{ + return d->mStatusBarContainer->height(); +} + +void ViewMainPage::setFullScreenMode(bool fullScreenMode) +{ + d->mFullScreenMode = fullScreenMode; + d->mStatusBarContainer->setVisible(!fullScreenMode); + + if (fullScreenMode) { + d->mThumbnailBarVisibleBeforeFullScreen = d->mToggleThumbnailBarAction->isChecked(); + if (d->mThumbnailBarVisibleBeforeFullScreen) { + d->mToggleThumbnailBarAction->trigger(); + } + } else { + if (d->mThumbnailBarVisibleBeforeFullScreen) { + d->mToggleThumbnailBarAction->trigger(); + } + } + d->applyPalette(fullScreenMode); + d->mToggleThumbnailBarAction->setEnabled(!fullScreenMode); +} + +bool ViewMainPage::isFullScreenMode() const +{ + return d->mFullScreenMode; +} + +ThumbnailBarView* ViewMainPage::thumbnailBar() const +{ + return d->mThumbnailBar; +} + +inline void addActionToMenu(KMenu* menu, KActionCollection* actionCollection, const char* name) +{ + QAction* action = actionCollection->action(name); + if (action) { + menu->addAction(action); + } +} + +void ViewMainPage::showContextMenu() +{ + KMenu menu(this); + addActionToMenu(&menu, d->mActionCollection, "fullscreen"); + menu.addSeparator(); + addActionToMenu(&menu, d->mActionCollection, "go_previous"); + addActionToMenu(&menu, d->mActionCollection, "go_next"); + if (d->currentView()->canZoom()) { + menu.addSeparator(); + addActionToMenu(&menu, d->mActionCollection, "view_actual_size"); + addActionToMenu(&menu, d->mActionCollection, "view_zoom_to_fit"); + addActionToMenu(&menu, d->mActionCollection, "view_zoom_in"); + addActionToMenu(&menu, d->mActionCollection, "view_zoom_out"); + } + if (d->mCompareMode) { + menu.addSeparator(); + addActionToMenu(&menu, d->mActionCollection, "synchronize_views"); + } + + menu.addSeparator(); + addActionToMenu(&menu, d->mActionCollection, "file_copy_to"); + addActionToMenu(&menu, d->mActionCollection, "file_move_to"); + addActionToMenu(&menu, d->mActionCollection, "file_link_to"); + menu.addSeparator(); + addActionToMenu(&menu, d->mActionCollection, "file_open_with"); + menu.exec(QCursor::pos()); +} + +QSize ViewMainPage::sizeHint() const +{ + return QSize(400, 300); +} + +KUrl ViewMainPage::url() const +{ + GV_RETURN_VALUE_IF_FAIL(d->currentView(), KUrl()); + return d->currentView()->url(); +} + +Document::Ptr ViewMainPage::currentDocument() const +{ + if (!d->currentView()) { + LOG("!d->documentView()"); + return Document::Ptr(); + } + + return d->currentView()->document(); +} + +bool ViewMainPage::isEmpty() const +{ + return !currentDocument(); +} + +RasterImageView* ViewMainPage::imageView() const +{ + if (!d->currentView()) { + return 0; + } + return d->currentView()->imageView(); +} + +DocumentView* ViewMainPage::documentView() const +{ + return d->currentView(); +} + +void ViewMainPage::openUrl(const KUrl& url) +{ + openUrls(KUrl::List() << url, url); +} + +void ViewMainPage::openUrls(const KUrl::List& allUrls, const KUrl& currentUrl) +{ + DocumentView::Setup setup; + + QSet urls = allUrls.toSet(); + d->mCompareMode = urls.count() > 1; + + typedef QMap ViewForUrlMap; + ViewForUrlMap viewForUrlMap; + + if (!d->mDocumentViews.isEmpty()) { + d->mDocumentViewContainer->updateSetup(d->mDocumentViews.last()); + } + if (!d->mDocumentViews.isEmpty() && d->mZoomWidget->isZoomLocked()) { + setup = d->mDocumentViews.last()->setup(); + } else { + setup.valid = true; + setup.zoomToFit = true; + } + // Destroy views which show urls we don't care about, remove from "urls" the + // urls which already have a view. + Q_FOREACH(DocumentView * view, d->mDocumentViews) { + KUrl url = view->url(); + if (urls.contains(url)) { + // view displays an url we must display, keep it + urls.remove(url); + viewForUrlMap.insert(url, view); + } else { + // view url is not interesting, drop it + d->deleteDocumentView(view); + } + } + + // Create view for remaining urls + Q_FOREACH(const KUrl & url, urls) { + if (d->mDocumentViews.count() >= MaxViewCount) { + kWarning() << "Too many documents to show"; + break; + } + DocumentView* view = d->createDocumentView(); + viewForUrlMap.insert(url, view); + } + + // Set sortKey to match url order + int sortKey = 0; + Q_FOREACH(const KUrl& url, allUrls) { + viewForUrlMap[url]->setSortKey(sortKey); + ++sortKey; + } + + d->mDocumentViewContainer->updateLayout(); + + // Load urls for new views. Do it only now because the view must have the + // correct size before it starts loading its url. Do not do it later because + // view->url() needs to be set for the next loop. + ViewForUrlMap::ConstIterator + it = viewForUrlMap.constBegin(), + end = viewForUrlMap.constEnd(); + for (; it != end; ++it) { + KUrl url = it.key(); + DocumentView* view = it.value(); + DocumentView::Setup savedSetup = d->mDocumentViewContainer->savedSetup(url); + view->openUrl(url, savedSetup.valid ? savedSetup : setup); + d->mActivityResources.value(view)->setUri(url); + } + + // Init views + Q_FOREACH(DocumentView * view, d->mDocumentViews) { + view->setCompareMode(d->mCompareMode); + if (view->url() == currentUrl) { + d->setCurrentView(view); + } else { + view->setCurrent(false); + } + } + + d->mSynchronizeCheckBox->setVisible(d->mCompareMode); + if (d->mCompareMode) { + d->mSynchronizer->setActive(d->mSynchronizeCheckBox->isChecked()); + } else { + d->mSynchronizer->setActive(false); + } +} + +void ViewMainPage::reload() +{ + DocumentView *view = d->currentView(); + if (!view) { + return; + } + Document::Ptr doc = view->document(); + if (!doc) { + kWarning() << "!doc"; + return; + } + if (doc->isModified()) { + KGuiItem cont = KStandardGuiItem::cont(); + cont.setText(i18nc("@action:button", "Discard Changes and Reload")); + int answer = KMessageBox::warningContinueCancel(this, + i18nc("@info", "This image has been modified. Reloading it will discard all your changes."), + QString() /* caption */, + cont); + if (answer != KMessageBox::Continue) { + return; + } + } + doc->reload(); + // Call openUrl again because DocumentView may need to switch to a new + // adapter (for example because document was broken and it is not anymore) + d->currentView()->openUrl(doc->url(), d->currentView()->setup()); +} + +void ViewMainPage::reset() +{ + d->mDocumentViewController->setView(0); + d->mDocumentViewContainer->reset(); + d->mDocumentViews.clear(); +} + +void ViewMainPage::slotViewFocused(DocumentView* view) +{ + d->setCurrentView(view); +} + +void ViewMainPage::trashView(DocumentView* view) +{ + KUrl url = view->url(); + deselectView(view); + FileOperations::trash(KUrl::List() << url, this); +} + +void ViewMainPage::deselectView(DocumentView* view) +{ + DocumentView* newCurrentView = 0; + if (view == d->currentView()) { + // We need to find a new view to set as current + int idx = d->mDocumentViews.indexOf(view); + if (idx + 1 < d->mDocumentViews.count()) { + newCurrentView = d->mDocumentViews.at(idx + 1); + } else if (idx > 0) { + newCurrentView = d->mDocumentViews.at(idx - 1); + } else { + GV_WARN_AND_RETURN("No view found to set as current"); + } + } + + QModelIndex index = d->indexForView(view); + QItemSelectionModel* selectionModel = d->mThumbnailBar->selectionModel(); + selectionModel->select(index, QItemSelectionModel::Deselect); + + if (newCurrentView) { + d->setCurrentView(newCurrentView); + } +} + +QToolButton* ViewMainPage::toggleSideBarButton() const +{ + return d->mToggleSideBarButton; +} + +void ViewMainPage::showMessageWidget(QGraphicsWidget* widget, Qt::Alignment align) +{ + d->mDocumentViewContainer->showMessageWidget(widget, align); +} + +} // namespace diff --git a/gwenview/app/viewmainpage.h b/gwenview/app/viewmainpage.h new file mode 100644 index 00000000..6b103d5e --- /dev/null +++ b/gwenview/app/viewmainpage.h @@ -0,0 +1,151 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef VIEWMAINPAGE_H +#define VIEWMAINPAGE_H + +// Local +#include + +// KDE +#include + +// Qt +#include +#include + +class QGraphicsWidget; +class QPalette; + +class KActionCollection; + +namespace Gwenview +{ + +class DocumentView; +class GvCore; +class RasterImageView; +class SlideShow; +class ThumbnailBarView; + +struct ViewMainPagePrivate; + +/** + * Holds the active document view and associated widgetry. + */ +class ViewMainPage : public QWidget +{ + Q_OBJECT +public: + static const int MaxViewCount; + + ViewMainPage(QWidget* parent, SlideShow*, KActionCollection*, GvCore*); + ~ViewMainPage(); + + ThumbnailBarView* thumbnailBar() const; + + void loadConfig(); + + void saveConfig(); + + /** + * Reset the view + */ + void reset(); + + void setFullScreenMode(bool fullScreen); + + bool isFullScreenMode() const; + + int statusBarHeight() const; + + virtual QSize sizeHint() const; + + /** + * Returns the url of the current document, or an invalid url if unknown + */ + KUrl url() const; + + void openUrl(const KUrl& url); + + /** + * Opens up to MaxViewCount urls, and set currentUrl as the current one + */ + void openUrls(const KUrl::List& urls, const KUrl& currentUrl); + + void reload(); + + Document::Ptr currentDocument() const; + + bool isEmpty() const; + + /** + * Returns the image view, if the current adapter has one. + */ + RasterImageView* imageView() const; + + /** + * Returns the document view + */ + DocumentView* documentView() const; + + /** + * Sets a widget to show at the bottom of the panel + */ + void setToolWidget(QWidget* widget); + + QToolButton* toggleSideBarButton() const; + + void showMessageWidget(QGraphicsWidget*, Qt::Alignment align = Qt::AlignHCenter | Qt::AlignTop); + +Q_SIGNALS: + + /** + * Emitted when the part has finished loading + */ + void completed(); + + void previousImageRequested(); + + void nextImageRequested(); + + void toggleFullScreenRequested(); + + void goToBrowseModeRequested(); + + void captionUpdateRequested(const QString&); + +private Q_SLOTS: + void setThumbnailBarVisibility(bool visible); + + void showContextMenu(); + + void slotViewFocused(DocumentView*); + + void trashView(DocumentView*); + void deselectView(DocumentView*); + +private: + friend struct ViewMainPagePrivate; + ViewMainPagePrivate* const d; +}; + +} // namespace + +#endif /* VIEWMAINPAGE_H */ diff --git a/gwenview/cmake/FindLCMS2.cmake b/gwenview/cmake/FindLCMS2.cmake new file mode 100644 index 00000000..cc17e9f8 --- /dev/null +++ b/gwenview/cmake/FindLCMS2.cmake @@ -0,0 +1,72 @@ +# - Find LCMS2 +# Find the LCMS2 includes and library +# This module defines +# LCMS2_INCLUDE_DIR, where to find lcms.h +# LCMS2_LIBRARIES, the libraries needed to use LCMS2. +# LCMS2_VERSION, The value of LCMS_VERSION defined in lcms.h +# LCMS2_FOUND, If false, do not try to use LCMS2. + + +# Copyright (c) 2008, Adrian Page, +# Copyright (c) 2009, Cyrille Berger, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +# use pkg-config to get the directories and then use these values +# in the FIND_PATH() and FIND_LIBRARY() calls +if(NOT WIN32) + find_package(PkgConfig) + pkg_check_modules(PC_LCMS2 lcms2) + set(LCMS2_DEFINITIONS ${PC_LCMS2_CFLAGS_OTHER}) +endif() + +find_path(LCMS2_INCLUDE_DIR lcms2.h + PATHS + ${PC_LCMS2_INCLUDEDIR} + ${PC_LCMS2_INCLUDE_DIRS} + PATH_SUFFIXES lcms2 liblcms2 +) + +find_library(LCMS2_LIBRARIES NAMES lcms2 liblcms2 lcms-2 liblcms-2 + PATHS + ${PC_LCMS2_LIBDIR} + ${PC_LCMS2_LIBRARY_DIRS} + PATH_SUFFIXES lcms2 +) + +if(LCMS2_INCLUDE_DIR AND LCMS2_LIBRARIES) + set(LCMS2_FOUND TRUE) +else() + set(LCMS2_FOUND FALSE) +endif() + +if(LCMS2_FOUND) + file(READ ${LCMS2_INCLUDE_DIR}/lcms2.h LCMS2_VERSION_CONTENT) + string(REGEX MATCH "#define LCMS_VERSION[ ]*[0-9]*\n" LCMS2_VERSION_MATCH ${LCMS2_VERSION_CONTENT}) + if(LCMS2_VERSION_MATCH) + string(REGEX REPLACE "#define LCMS_VERSION[ ]*([0-9]*)\n" "\\1" LCMS2_VERSION ${LCMS2_VERSION_MATCH}) + if(NOT LCMS2_FIND_QUIETLY) + string(SUBSTRING ${LCMS2_VERSION} 0 1 LCMS2_MAJOR_VERSION) + string(SUBSTRING ${LCMS2_VERSION} 1 2 LCMS2_MINOR_VERSION) + message(STATUS "Found lcms version ${LCMS2_MAJOR_VERSION}.${LCMS2_MINOR_VERSION}, ${LCMS2_LIBRARIES}") + endif() + else() + if(NOT LCMS2_FIND_QUIETLY) + message(STATUS "Found lcms2 but failed to find version ${LCMS2_LIBRARIES}") + endif() + set(LCMS2_VERSION NOTFOUND) + endif() +else() + if(NOT LCMS2_FIND_QUIETLY) + if(LCMS2_FIND_REQUIRED) + message(FATAL_ERROR "Required package lcms2 NOT found") + else() + message(STATUS "lcms2 NOT found") + endif() + endif() +endif() + +mark_as_advanced(LCMS2_INCLUDE_DIR LCMS2_LIBRARIES LCMS2_VERSION) + diff --git a/gwenview/color-schemes/CMakeLists.txt b/gwenview/color-schemes/CMakeLists.txt new file mode 100644 index 00000000..c7e3bf8e --- /dev/null +++ b/gwenview/color-schemes/CMakeLists.txt @@ -0,0 +1,4 @@ +install( + FILES fullscreen.colors + DESTINATION ${DATA_INSTALL_DIR}/gwenview/color-schemes/ + ) diff --git a/gwenview/color-schemes/fullscreen.colors b/gwenview/color-schemes/fullscreen.colors new file mode 100644 index 00000000..0245ee18 --- /dev/null +++ b/gwenview/color-schemes/fullscreen.colors @@ -0,0 +1,95 @@ +[ColorEffects:Disabled] +ColorAmount=-0.8 +ColorEffect=0 +ContrastAmount=0.65 +ContrastEffect=1 +IntensityAmount=0.25 +IntensityEffect=2 + +[ColorEffects:Inactive] +Color=0,0,0 +ColorAmount=0.025 +ColorEffect=2 +ContrastAmount=0.4 +ContrastEffect=2 +IntensityAmount=0 +IntensityEffect=0 + +[Colors:Button] +BackgroundAlternate=66,65,64 +BackgroundNormal=64,63,62 +DecorationFocus=39,94,160 +DecorationHover=87,129,176 +ForegroundActive=150,191,240 +ForegroundInactive=120,119,117 +ForegroundLink=80,142,216 +ForegroundNegative=232,88,72 +ForegroundNeutral=192,162,95 +ForegroundNormal=232,230,227 +ForegroundPositive=120,183,83 +ForegroundVisited=142,121,165 + +[Colors:Selection] +BackgroundAlternate=22,68,120 +BackgroundNormal=24,72,128 +DecorationFocus=39,94,160 +DecorationHover=87,129,176 +ForegroundActive=150,191,240 +ForegroundInactive=81,119,166 +ForegroundLink=80,142,216 +ForegroundNegative=232,88,72 +ForegroundNeutral=192,162,95 +ForegroundNormal=255,255,255 +ForegroundPositive=120,183,83 +ForegroundVisited=142,121,165 + +[Colors:Tooltip] +BackgroundAlternate=17,51,86 +BackgroundNormal=16,48,80 +DecorationFocus=39,94,160 +DecorationHover=87,129,176 +ForegroundActive=150,191,240 +ForegroundInactive=120,119,117 +ForegroundLink=80,142,216 +ForegroundNegative=232,88,72 +ForegroundNeutral=192,162,95 +ForegroundNormal=196,209,224 +ForegroundPositive=120,183,83 +ForegroundVisited=142,121,165 + +[Colors:View] +BackgroundAlternate=36,35,35 +BackgroundNormal=32,31,31 +DecorationFocus=39,94,160 +DecorationHover=87,129,176 +ForegroundActive=150,191,240 +ForegroundInactive=120,119,117 +ForegroundLink=80,142,216 +ForegroundNegative=232,88,72 +ForegroundNeutral=192,162,95 +ForegroundNormal=212,210,207 +ForegroundPositive=120,183,83 +ForegroundVisited=142,121,165 + +[Colors:Window] +BackgroundAlternate=52,51,50 +BackgroundNormal=48,47,47 +DecorationFocus=39,94,160 +DecorationHover=87,129,176 +ForegroundActive=150,191,240 +ForegroundInactive=120,119,117 +ForegroundLink=80,142,216 +ForegroundNegative=232,88,72 +ForegroundNeutral=192,162,95 +ForegroundNormal=224,222,219 +ForegroundPositive=120,183,83 +ForegroundVisited=142,121,165 + +[General] +Name=Obsidian Coast + +[WM] +activeBackground=19,47,80 +activeForeground=255,255,255 +inactiveBackground=64,63,62 +inactiveForeground=128,127,125 diff --git a/gwenview/config-gwenview.h.cmake b/gwenview/config-gwenview.h.cmake new file mode 100644 index 00000000..1ae25a05 --- /dev/null +++ b/gwenview/config-gwenview.h.cmake @@ -0,0 +1,5 @@ +#cmakedefine Baloo_FOUND 1 +#cmakedefine GWENVIEW_SEMANTICINFO_BACKEND_NONE 1 +#cmakedefine GWENVIEW_SEMANTICINFO_BACKEND_FAKE 1 +#cmakedefine GWENVIEW_SEMANTICINFO_BACKEND_BALOO 1 +#cmakedefine KIPI_FOUND 1 diff --git a/gwenview/cursors/CMakeLists.txt b/gwenview/cursors/CMakeLists.txt new file mode 100644 index 00000000..85722f4b --- /dev/null +++ b/gwenview/cursors/CMakeLists.txt @@ -0,0 +1,4 @@ +install( + FILES zoom.png + DESTINATION ${DATA_INSTALL_DIR}/gwenview/cursors/ + ) diff --git a/gwenview/cursors/zoom.png b/gwenview/cursors/zoom.png new file mode 100644 index 0000000000000000000000000000000000000000..e7d0740615640d10190785ab84b38828b2778e98 GIT binary patch literal 1743 zcmV;=1~B=FP)iV=UviRre&^==J~{W~p2LU;s|8~$2B1yTv|XB}wVI~+ z3}cLInl`bxxHxFr_ICgVh-hKe2}%sM8h|nOlCJBgQ>m1gYHkkJ*VnVUx;k*qF+M(y zv9U248X78$T)%GLzkmM~BKq>N1w^#m0F*@{k-sJq3G;8m!^DXm+6KS+bG$3YJBji2gM>Nk@(zb$^penW0d~HVngVX=yRvICjkY&+Xg9 ziO5`9qOPtkTi5j;9|y7g5Q#*-=UMTFJ4ql(?qhSrZ77@`#U1)FLetD zjIo%m>!&|Cdsb5v1@FE4uAj|jbLn*YSR#?Q7Yqi)Kp-G$nkK2LD#@}e1%pAcrlw}L zrKRPK(UFnalP6D7C=|lSXU+t5UH^nJrk4x=(3VQ2#GN~LBAd;kr>DoQt*w1O5D18p zBnh%Ci>j)MvMh^=qAWj)q9|&bhGa7NPWREHPClQ+9mrx?})jjC0O^>Iecj=K=r}b1Q>>j{nbd&IM5v2>^WG z2V=}H8K7y}#Q69)0HD6U-kX`3NifEUb52A=p68M4y4<#H-?nYv^E~oAkBEqT-v{TM z?%usy)6mdB0C3~R4al-QUNT^DadB{LY>WUP)!eMi&(A;Wx-JnB_dJh#p2uz5_ASfu zRv?zgIOk+p)^j^|?Fs_G-$zHid-v`QmJG0M`@5l`p#lILIB-BL6bir1=kw*B=W)kz zxa+#U>$<*WS-xdiezEU34);8dPfbldW!rXY|Ni|90IpuWYIDwSlnekcFmnC6Jv20g zty{O^k8i&%Pft(3zO=Li-}je`JC5Tyj^h;}j^p@+LV@LSxx*ivI1#9*s6c;zKW^Q+ zwE&>x6)66S+bb(8&;5IPT2oaOd-v|OMn^_wlgZ>e)z#HAf*>$K5EuY(&WUqQQ&Uq< z<#M^hZEbC_znnX#E-Wk{SyNM(o11%)h_03jSUwaC!}zqTtIOs@DISjx7=~f>_Vw}N(cIC|;i{_2MNxeDagXDOuIoQ` zc6M3|i;KjGD0k-$_4M@c_V#wOyu7@yY15{qef##AZ@%>w|Ne&`h!fF&=jW-Tqr(k_ zLj3%N3lxjROvYG`q9~sOP)Y_A5#ez7?8c27m-;VXe&mJZ%S)H2tgLKFRaJie!Uf_) zboj^-(lm|I>9iFNhX)^pSUm~6sO$RN_Qpo@(4j-#$7jyaWHw9xoS2~AzCJp5@Sxk! z&~QH-4u21zSro;WW3iZ7EdJHN0J)w=nM}qs4C9-JAs#FPbO8G#N%})P9=~E3##|&4 zxm#9N)-MRc?*X&}2rgeN80#q(FRoB2h&A8g9@G>?`8=IYTdwE*;xny4e7zdti2(p8 z;c$2`lgXHmKm^tWP(&Dp@y%KgPZY2Q#FZlAi33(3zJ3@Y5{V1|2&@aBRD@v|m)8yO zGa~RHL~maok4B@T>k3$bxUv#)C!eRVVVD56uREZKh(sa-nM}s)?d#)V!!T7 + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/gwenview/devdoc/CONTRIBUTING.md b/gwenview/devdoc/CONTRIBUTING.md new file mode 100644 index 00000000..ffdb7f7e --- /dev/null +++ b/gwenview/devdoc/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# Intro + +Great to hear you want to contribute to Gwenview! Patches are always welcome. + +# Mailing list + +If you want to discuss development of Gwenview, you can subscribe to +gwenview-devel mailing list: +. + +# Bug tracker + +Gwenview bugs are tracked on KDE Bugzilla (). They are +assigned to a fake user by default: `gwenview-bugs-null@kde.org`. To get +notified when new bugs are filed, add this user to the list of users you follow. +You can do so from Bugzilla by editing your user preferences, then go in the +"Email Preferences" tab () + +# Code review + +Patches should be sent for review on . You will +get faster answers by posting them there rather than attaching them to a +Bugzilla bug report. + +# Commits for stable branch + +Commits to stable branch should be made to the stable branch first, then merged +back to master. + +Here is an example work-flow. + +First fix something in KDE/4.x: + + git checkout KDE/4.x + # Fix something, get it reviewed + git commit + git push + +Now merge the commit in master. Note the use of `--no-ff` in `git merge`. This +is required to make it easy to rollback the merge if need be. + + git checkout master + git merge --no-ff origin/KDE/4.x + # Check merge is correct + git push + diff --git a/gwenview/devdoc/ENVIRONMENT_VARIABLES.md b/gwenview/devdoc/ENVIRONMENT_VARIABLES.md new file mode 100644 index 00000000..0c1b11da --- /dev/null +++ b/gwenview/devdoc/ENVIRONMENT_VARIABLES.md @@ -0,0 +1,30 @@ +This document describe environment variables you can set to debug Gwenview + +# `GV_MAX_UNREFERENCED_IMAGES` + +How many unreferenced images (images which are not currently displayed and have +not been modified) should be kept in memory. + +Defaults to 3 + +# `GV_THUMBNAIL_DIR` + +Defines the dir where thumbnails should be generated. + +Defaults to $HOME/.thumbnails/ + +# `GV_REMOTE_TESTS_BASE_URL` + +Used by documenttest. Define a base url where documenttest will put images to +test loading from remote urls. + +If not set, remote url tests are skipped. + +Should be set to something like `sftp://localhost/tmp` + +# `GV_FATAL_FAILS` + +If set, when a `GV_RETURN*_IF_FAIL` method fails, then `kFatal()` will be +called, which makes it possible to stop at the place of the failure with the +debugger and also makes it possible for users to report backtraces if they +experiment those failures. diff --git a/gwenview/doc/CMakeLists.txt b/gwenview/doc/CMakeLists.txt new file mode 100644 index 00000000..78d0c66b --- /dev/null +++ b/gwenview/doc/CMakeLists.txt @@ -0,0 +1,4 @@ +########### install files ############### +# +# +kde4_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en SUBDIR gwenview) diff --git a/gwenview/doc/browse_mode.png b/gwenview/doc/browse_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..32743016e3403005c15a7d617341f3883556ad57 GIT binary patch literal 293536 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%0ARZa_D5xnpuJ<8CI&VnS+xbv8Olm#;+FnM8~ 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

Tq%qC})<{^7=*OoB4C&MZR`55z48FZCD= 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 literal 0 HcmV?d00001 diff --git a/gwenview/doc/fullscreen-browse.png b/gwenview/doc/fullscreen-browse.png new file mode 100644 index 0000000000000000000000000000000000000000..1b6734f4a9e47eda816b2fc4a27ff9ec29a6107a GIT binary patch literal 366201 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 z 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 z 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

sxTrVzy- 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�=OhXXm|w(?bvnu$94=r(NH60u~- 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


>21t70S)MBgla& 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 Dgiz?Hw4zod>p&G!z|9x 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�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 Cj5`Ma literal 0 HcmV?d00001 diff --git a/gwenview/doc/importer.png b/gwenview/doc/importer.png new file mode 100644 index 0000000000000000000000000000000000000000..d385282fbd8bdbe72d715cc8779508edf1989b4b GIT binary patch literal 238160 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 =~Q zkEeN!HnO8d?A;>C7B9dX&(tLkZ_ZZRs7)gxk~K_eP-V2d)fXt9ZV93-EXS``X4Lx} zeSam{(WcJyvhDWj3;^_;TQ4P#R+o;TxH-3%qrBcDREGfk)_&RXIIL!NvIbz4ZL^Co z@uT(m3Kw%)i;kfo#xm3wo=^yAS(*s{rIIc)LVC_quZVpA5BefK}1z z41DU%9f7=UORI&A_3O4Acd{zbxK!XjjLjuhtbdBrJt}6h*KimLZ`V^o^c%7Fe0L%{Mi9mSQ)|=cxd_CXH?Kd}k{V!a) zhcVS;DC{Mr4^W;D0Y%{`K=V*P3$Yw9w9Wd>*{~SA4Fxa>kcrDrRL;Rbs(urXafQ3H zB4IwDB_x75yK`KL#&p{A;hNOt^tUjw%SOD?Nx64h&5dGV7H3(Eg8bD_T&}H~y$V|Z zc-WRag8KhKKB{<+`vpFtSXjA(nrCG%mANt+=)C5`^ zHB0`orVjhB!})=#RruLI0Fe&H4Y=x_UNI2fEDAg?S-hI0JeG(}d@B{f-yVqev2|~R zDWk0JZnUA}x1hdx_rL&#(nGiOzrm09456}nx{3Dvx4-1Fc{y*mMIQZ^Z`&_jH>9av zbdI#_cWrn43vCu!qQDF@^;!zmSw6;j5Ev$!XXNx1~P8ewOX}kM^18y$HDn@Xl`= zgKZ`E3{5^Rc|M_4&;I~eFR|Z#6MDxZ(BepOo1BTBUL+3qo=rx-k~(9IvGVVbV)ljD zsTAwd<%~AG^e*0EOExO1EI~Nqno+e3()Uvt2^8d{B+Ix-ycS#A0`2n|Du_$b)*Gy5iod*wW9>Wqm7HaQR|p*8TY4-Lc-w4Rtk6#~SGf z>X1AW=DK@OFuA&qy4o|R*;pX;ZwWs+HEaSirkVwkuXJ3YF9zPe^7KLkK0YX{Y+rWV zN^WO*b4lNgKjJIU+U9ubjjmEp7CtWf9c^BZehkz+=s2_C-UR#*zG&w>4);s{1ZMQ` zx$s@cUZ9&K09md9@K%A*!xKIMs5gfgQ%#3LS2|!T0DI_J@A3E4>;7N3_#;Lq&EXyW z$HvoGPRQL|$D?>JxN6%av+rTIF!p(j=!HT1Y1WY*zfG3+NE6k^t{0NA_fG~`Pqn9H zObKFf~UeY+CnKx{8)$$3o0r1dkfSa7xxyKFIk>fC|4gvNR08#0d!Y&uKJfgV{xJ}I z`cDG_;2N6i0qN7zocmVJxgT{+;7gBP^9H9^a;IUwbop4MT-L)WoH~C`qqy5bY+w1Z z$l#5wtc>0+14F)DSkW7uEj5MhQg zjS4g-D};xI8HLpXeeUtc<^icoO4McCKL4Z;<=0qIF%s$iQsKkyZ zm`k?YM1faj5CHrB@)J;4Pmhtt7uyEx)*lsiKXCi6Vws%XM>YQIiXsC@lz<~Y8bC5& zuy9j%yY)NAdTllU#@Nq5tMNxx zpcVm%Ee5+6ln?Gm?~lptopBj7#=YDVuJk!xOF0mpuYZ3|vGpr`a$?Kr=B~ZtF4P%l z?UVS|i43G1V5KF{>Vvh*7m$^>2yW_|d|9-!c|#RQ5M*ITepE4hjFQ|wbyC&lk47xEdG(Oo_m1x%ueGrujS%OeiYuxS((_1EF-1_ z63~F0s;Vl(?aeudqqeKNZRuE;OaCixmwEdl*TKS6f%9AIN{gRJPw4>=6qq7wV>p0+ z1-Lhoc$(}|(Ue`kywrDEuhiftFuhyq84nCdIL&%K-p_ojvK~xOd1Wqq&;PDv^3R%6 z3yspKPal8d?pB8A!_|6*C!;SAbv*#lH0M4l`P>KCUI8+~RdZ8Q;#mHCk<+}r_3g)l z%R;r(lW6C_#HsQC*t2JT`c1g4c<;g)kV@c4UHwV% z@|@hxc{nSAZi}xBehj!C25`qGiG!N?Ny3L~AT;-G?vDFuoW*PpZd!GuYcpDOer`+M z8a~~{+;-;VkIwU0WYTkcDeZM^7!|g+Q{JbKI#rRD3jnAk6S*bKAfsa+*~R}6kssW=BUhNin{ykk1HAP zsZW~M9ML?iDvyZXJ~c>N`7DWzNNz9I*LU1)a z4l~tG2Sy`eryMa7Cj;dW74ejrwu?IdgR@B}@U0PDaZ1#IX^(fYkC_Q0eVP?s03pwP z^tUx~wp{w?s^1&H)BVFsD%vAgS3Tjev97lV4b!{O>tXW|dw}l-Xh4HDU*e)HUp4@~ z9`<7c5dZeRc|iw*miI5s#qZC=?(0@!0d)W2RQlob)0XJg)FX#0HP8`oGs$`2?AZ$J zP5`by(zKn~%?W55W_MTsGK$RnZjJPdhZ1N2AdUOHJ5FD5HUE=cK8`&~)6R*Fjh&oX zQQ{)41osj?iH(#@ui4FxG2cob2p~!ge}54y5xuXS{4!$L^0Wd1IS6p$-H`s6ZNIxw zKu`fl1q#=%?PsKyE?n0y0S-R|_@KnqTjgazOikHqG%p6FYWDdhb?up|x&f2P<+CBM zQnU?-Xu5F!2Ww%MU#rb6J>}zab@ZYWx_$?H;tmj{;l_w~&7t-7>v#9b7gVGb6-9M# ze;-aX?u;X384HwArSErNdM=Cx9J*h1k+ydN?CUI~Vd-k-gfwvX|t9*QZds@cd@ow6ENc`>U6G7|jS0et9+Iq&=dY{yAsT+7> zU*x{%+7FDXpUPcbCoE^;3_=YTz9=(cowBByb^ zi}4BGIBe8^i{CW5;qCpT22I1w@`1sB+-J)o2K%U{5R-%TKdzlDKD~Fcu0)(I1lziQ zvJF^AuR4a%lKRX8uA0N>*Rp2UP5wMQJbyNC(Sv_Bxqr?>#d4E7-6Gx^F?Lz%U-n=8 zL98i2?y{0|xK^h`BfN_on-7bo6n1;OzkG7V;`KgU{E%Lk0{n^ZV<6*Qk7*@k#R3}@ zbMrZpwN*`k@9x7+_X_44k?QB?QIB5D+Xrv|1>X$D>>b^`aHsA-4f1)MNm#hO-pw*Q z@VvAEIM}DgS4l0$`Pv9Spds@9Vj(s!FYm+O!NEWC>|B9APWF2ifwD zi^o|YRV^ttd8#sRtE`K%puczZP5IjGyIt(p!(MJicXw}cW(Xn%248TGP!A&A& zwV-0MxysUhwaw}XxmSI8T*(C>N$Nb$UrgL#mRLO^6LyIm$w$Oz`_G@2&u`?(F92&V z{YicV7+`u;Vk~F+lZ6>HeD9E!u~uS5cLZ4qmWrIY9^6+xN!>v_&wp$h%^a8m?Rh`X zKbEK5s$J$yLo>f`?^94vJbi&DU;B}Mtb@{!vdf6*c4go_8ek#R-L#!O?4EPG2!Tv^ zy@X)zg@+%%r`^2*YL*c|>{g;zwg%@P`4h_RyIL=_s}fr}YTS*B1Q6;`Lhp?;p8Rl` zX}zw-Qbl4iWX;z^6de>CVvJA!5{1sMi+)G{h)JhxiPvIr#Pr_$MF7YMkU1IFxQVW= zdshhrA`v>F6nVgCUjbr~tRVicE9$ z3`BGc=&8bW-a>EnAdiD6Wr9FY;N9wF$GXDfKQK`w4~Mfu&5tL+hEG51(Q9luGhiJ# z0c2Nxe^vQw6%gCI|2{uk!M*C{>;Suryoqud{7=R$;boUkt$^Q`7WNO%wYXPXv|M0E zxt--R+po~I_bjnr>-E#+e{_L_R=j9idP>HHJVK2Mwm3e2jxBTWrkg69*V4H??0Y@U zH+ss`=U6wb(jY|Z94g$ls{!GcwBDiaBikCy=)YOD1ZIs9j?@Mou%8Qf_D+TXA)5|- zpvD;A%!8oC@$^@E2YG4_yULT^bygIs}6imKMDG*K`K0(saAo?%(4s-gjk> zZj--yRnXB<(cH22ZUxk3#-}cQb93{w!ut4My9GYfP|hW7tc(D^iLy14-aV0(QoYC3 z0_blW%6<<52DPJU_Y(F`gOTkmkiyS71djvOc7Rt}$zevE<}}|i#7=)|7W3KudIKSD z+qaRH{ML=+?-M-U!c$!Vao4x6L!mKRYHjWEjm7thU1lsqvz3;}fL9~Sro>+Rk+Wc| zZx}OkEdmJM_)$_oT=Bxn%4%W$D>nK2SHLjS_(j6~rBXxN084R8MSw^+7m3;38*X@A zd!X!F(`t6XG4*5RvjGWQveT*|MvH1bHnc?|Iq)NRoI>1|bYquUv(~WY#`LsV=r^@T zd|zCeA|LnJ`6fT`tE_Sk8s_GoWNCAv3$e+ywOam|JUJl$Lh(}0H_JHhz1(9?XpVC7 z73&iSHcMvxrE~%1{97@TzsA>cis9%J@;igH+KAiwvbmm;lwMooVA}ka@m=|vsdFpH zCwsWiti>cC3_DRlQStw}(nCJKO8EYHI2o97!1NWo1*;RNEjR9H(N7GmE1>6X??(pi zr&exJ<5|9KjS)R=#z6>#w@xJl98DkmZh8*cHrAeePDLYjY7yc9-|Je>8pMkCzO=i*el%?Dq$IMR#I6Ej*ePgnm?_8aE)UuWrV zugF&^-}OmQe^|eFwPYYBSsaSuf_O~imk)F4tc=^=m@e#oZOg7K+SJ!qoet?rD{I~X z{r6(Iu-$$fr6~vI1S5yzfQmk2N#6LWLBzEB_;j-)LCW-wqr!X^spRh#Gw1-`^Oeg= z1OT22n!ky0XY`#=%Bnv?A=n z`v0?}J|Gm|zYL+mCd_;~_8fy{o4ZDbq9w4J zz1c@A?CfouJnsKKL+k}!V4VTxJ)0jMK2Riqe>%b$FnpaQ;dp2x4vBLUFti!*K10=xoXeB|6h-6KxWS}f%EESjjvs#F`_W}P&s3N zE<0CvUj`*I_95GJEj(l6-Y2PTlMC@uI$9Jfg(v>QIIw;*=U{}Zs7>3!PjmgU#luXv zgz59R%|*}8?56=3{eK=2&*arczjSk%MjDZqU92xxs#WkaH3GCrNfqa_5KW){rHi|@ z?%I&(ue< z{_Ll(MFE-@uaTIfG1g0r=e5&n@QRIFzb_Xoiy21=KoNC_Bvs3EPMN$o+u@XWNcMRl zU$LNESbESl)K2pBAM)k=^VnOiW9|bm#Ut4}QT^<~bvT&z?Pa zd4IRQg(hP8L`1&uJ$;y$63LchJ919FEf#OY_gzgHaEc6O`QZFJro9AnlZ-r+*2`aPr7@jSl!Ozm|vFzuZ;P3@6N z{@93IAz4lY2%LPFTN8F?P0szk{bX5^am2JubJf(t7@G~@eZl*$xD*S;O50~yoad^0 z5%fs2_4w+M^XLsXL&1th$pn+lKe{)#nzQ&RG^iVvec1sh{WS(G-%hbrBzpbIAx9)) zAZ1K(9X-Hc&#KJAUx5{kor04WEed~(%wJklH1fp_w)@sb5S{}Os8ma@zzSBUkQ7UR zs}UiBGsj1Fcm8wpb#5})WehNr^QKR2uq(hjuu>0-64D48r&~rn(Bn?0JR> z;_Z9WL_|lD?*jkMK%^GtHdk#w$zsWoG?Y@sS@^eZ5D7$O<QUm#37Dfx?xMvzLZP&jdFa^Sb%zM>E*9der zG;nPZFJlx%R75un#8JSMtNwg%8>33TkPfC;BB4h~H`Mdv*J%0ep&m_tYT&+S1nw_U zza)gN*}$`cVyh1eY}cI?&Tni z;CB|uU2LIt(MNt)MgVW&~w;wF#JycF+p&zpFDP`XK2k2iUj%h9D>4K@slvPJ?Qz0?eZPe zpv1%sMRsVV(L!m;oHdJe`Y-avM1h zj=$C<$pikp#k_YP_miZwS#s?i>L!JO?p9+KZ2hT@zNekb2b?4og|>frr&8yXENA-V zufWHg*IUul=Px9a-4?oPI0aXQ&Z#|>I@#Id>*@ERkN=j(AigfVU?^?tEr zsuJuofKXQ1X^ComVL{Uku_I$o#_xe9;CUoyz9YvS4;I2V2xSbP7G0~99|=m1!>FX2 z6;X^*glVxLX%1XRx*7hf)zl^EYj~53*~2DlPs@kjjxg9OC-3u_6+tPK=_J5gp!$xN zq)<#%H7%cCnZ&{_S4tf3=COE*LLAOQY3W8W4jtO$D|92*e4_<(n?WrbGN8BM{7!aY zCpKh+Df&{o38Y_cO8sXUZ1iGWH7}X|uOg)YW&yS;%FC^oFC!<@U4#f-NX<2Cb z7!lgxS;e!^CBUlL2#uq)(I80j0xZ!;0`u|r5T2NqB!yX~NFg9NMh}9nO1ubxX(jfR zOi^AB23B)i^1+r}Fs3(#S<4%NJQ^LU^_s}Grs$>O=L!k}o}1ZEMvsK8y#^aww&l1RTt4kGr@36{6Z@&lAf|M3sxkjoFnv}AZ2)+ zjR!dkyDkP;&`$2FT;vdxqZ>Tl7LPPGs5&_K7=4l;{D!1NfJNzpyfGDVJ>i5ZW5pvw zlK@51#NbOBI8p&ITQOjKPf`kd_};(o*-;jNeK<*bkd z*7CcWw+OI&r^2ttkq7xT?-u`@JDfT1_Eis_gG_R0W+Zt$=P&a(+wn|PpQI{R*s4;f=}N)Lbd8o z)N2X}9pxn>gGaC^;$wE_?iS<pj>q$jb?-lv4oK48M3TUKE zOWzV4GGB|y_8d9=u>q1kmV6vS>T~VlDI&RPu8qQ!IN=n*uug%!4`*XDeaZSmMt)cKe5oe`$ig%>`3mdKbAGXL2fp?_$jHn+1p5y(@pE3Zsh> zdfr|%=E_RT+ej%!(Zl2*uo0n(FFFdX>fNZ~-I*o${)`=8^>eip><_sbS8BIvJWmWr z9-iFK^V6R+CRY$4#&;-7%rV4LN{pBBHLz}EmGe;Mr-GP451S(qUKnFO&o|v*ZP9!x z%uRw2o9$EL1OM54mu2Nm>NiPU_{JQ1qOk?q5f#SI##xArP+1_0o*Mlu2iDOiV|Gby zA!>R5;NdXF+F)%82(~JF?c2Lq6=j)aeuG703{HzuJcnpoL6*=7%o1V)40+}}ZVCai zmkilDJ#z9^y=@Se87E@?@e$nn<#nEYY}o2&e*=k&qa5e3-7Ql$hEoqb62ZLC@> zV3SS?qcO2QmgpkXO$8rl=tN3p(SRQ&W#|S2xH1=jD(RV}_J(cxl4TsZ?}jZAkKW zgW?0%cw~3+4(u7iL_AP*(^7UO3FQr5i5ru10W$YT?k=PV^u+*WRPbJHY4}VqVCR7EmhC zx>8wM;qBooYH@|b!)@rQVB_WL_G7{BV&ZEb;4%*D%FR728F+#R;uW-qmI`3GIQCr0 z&v^7=Zv%ai2=dv;%f`5LxuNpsr3z6D8{=wyu`Nzfb-5g92LtJiSME00G6X4WYh{ek z3^Eylh0-KNhtb|t2XAb;6t^9WhG@E4uVN?q5T4^!^eS28ssx6S<$L9&&mlkiNHRhy z<+BxPWexL!x@foyh8eXYHNaYCK?!7qFJ*E@Tk{?ud@r{ntNxqHb_Dm%XLVlj}(uM2TCvbs+FA+(u9HN3!>~G7FM>2`GU7j z{#hPx4lod%fF!bF=n?5jkRCFwT@*wlXGGCq(nTbOi@{Lxzu>)2V$@2>6rsF@A8}{} z=Y$;RTFGM@l{!pP)Q%6HccYIo3(1!Mv7$GTrogC;)eb|wroxiwy@{vG=%MfHPLcE2 zm)n>my|P28qRl=YKt=UG$iyI{jih(WXN@)1yAMEZVh*b+bp0yvR>oqvRUxgn#VY0S zt-S~R@vKM_s(8WgAbv%A0nuL<0fsr}vz8Gz0GKMg=z=VMP&-*KWUp8Wn4X4(hd0bw zKkeXft#))!L>dA3B^@<2wIb?ZNn9hv{F`%#;Tn`!hJgt?>UAH~4m8y+?5;aII9I9s zak<}4jjRubNQ$JjDzG_e3O>cQ0=Z&rj^PjRsKcPxu5i`0uHksCNU;3SAj%L~1cYFO zfUsBA+DdqCJQzmLmtR|>&T#C+UhDdi-AvZ177O-|3e*7RH7bZk7tyS9K6%!oE&p|6 zZMx)b?0mc1aRo&*0aiWyh_$N{yT_-U{;dkfNDupWYl81Ap?06ExkwCHH>oiQCR7OW zk$DftIQm$UPGrbQM+!I;>9!^N^OJMOP=-Rqdl5`x0!A&mjbbl)JA@1B#%eWLmA1rhDp9F^>*lmZ@#>)`qd5tgv80xsSh43OKD*sdgnTe*F{COzWB~ zqAx4nBL;TB7<7mgadX|Ld?!x^9sJymwaZ5^P7^(@k5d(|MKLC?xXM_yL=4MHn|0`g z{;Wfg738h9lL)s)m8vHtGjO%>*&{+f%TTq970wzlXh($4Lc7ZtR4{savyle_bn$z6 zT?J*H$1FzCZ}Sp%kF)WpU}R#AflwM7_^L?!8Ob0KjIwl>2;I`1G6W)(EQwBOM&mUCL~ICQMs&Fjfmv158G;Ey@kymp5<^->8IUkp*%Q%(V%{TP~j(=Se=7DsBMYpzo@*}sy~Zyfj64qyEI;6I2A z1t+MuOm{E@eC0{#hlS&PiUb+`d$$brDLL*Xfh9JseC1L>P}!KUH1YZ{(awN2){>LU zrIc9@Op>x~$NJ==8efFy$!x@s!u?Y4_4~=9g}CII-gd!~Nc`8UCqq>7kqU`z{NdYq z{t6w$DZRm%#Ct;&D7N1_TQ_EwXP~LEy}PSU0;&^9ZjcH zUwFd97NKPN!{`TuG9f+R3COYT!j|Mdy~AVUv8#1U%zQ@>vwJ}f=XDBH#KVKpZOe?D znxHWhjvIKHRl`I>gP_+zGMbj9bUER&GGtZ3L`Wyiu>&u(D`Hcs{HP1#R;hsST;cid%>L(XqPMB&>2Y)e4>zawjxd+~{3L#T) zM8%IT3V`#;uac=^*&UeeG3OyMBuX!3lDj5KN^bw2im)=em6|so#grS-V2YOO)MV26 zApasebzh$(8hvebH!WndF`|foIbGmQM!vg$)bRVy7stABKC&U@cpref2|@$`pZ5fLk%nWgPyt^@c)t z9K<>{e_nTtoL{YwPG!Tbr~`f-oK?$M1n#S&8@hI+pjlvAZzN)LmP?AwTNBjliMQ|< z9v6*)@PWi$+~Q?eFU0q#mErAayG1g%E`mbjY1Q1R*>*mUyCOLZL}1a#HVMCyDP=t) zCEK~Q@JFExOG+FZEY=Frb4vKe2xa^Jdq|7hB)!!nUCJnezrrkv%`m>_x4jGUNSh?V zSKsm_f3%q(0%3C*#;;VaR?10OGo*XpV*mDVrH)LPx))gE7^&~`&9a1scg;v~Ief>O z8FGj^MpYj##GQspV4vOwgke&njzR~ps5j~uD~N2$$S?(?5M-oI;h8IS{pR=qj=m{Lu}@d zN%Xp);-iLGDAtW5%o!IhqaLHhQ5AjcjtF2RC1W$iQX8=B8BKARN;x zRie4Sdv|Uo*=@tjA%8%Ui>x4VpB2eYi>XPEk#}caLqu0q92}D|^*!m2eW*M` zqGrOPw_-uvQ4IU)u@T1*1!-b7T+YGtS2czKr6{qHmO+N=Sa45am!9 zjW_$4C-99OG4f}3gK7_{%~jA{ZceAphUCwy0A(VbNF_32HEY44OI9U(yMOLqIwyM& zHqHFm`4&`zh^k?f&FWudZcMRC)W@eAq#W|3NCTT%%Xo$7d#o>|6`hv-1+YvEsIvW# z^)=$tEx%R1!t*R{!Bb!OMr=5^!Tu`>nlojcPbvO&i1H?x?P5LhV zlTH$dVtM-rD-~*$Wb21}>O+pf5-VC1v3QVK0kOWmc-I@c*edOKH@g=As17e#MFJ&| z8nZS4ryNfV+CwtKMQMSeLzqK_O`>3FgP$>CW-*8|kviMfMC$!YK*L(hB$O-aP_TW3 z5ad?8mlVz-1LflAxs)J^@t=_j(yQkCC5Hm0Y|K3z!Eagk+RK`Nmbl_7&pWv zyp?sAuZ_UOA z9phi}%=L|rU4g6gs*hF6KBN~2xWjG~j^{;$m6MjDjE@k`tDb>^y0V0jLycha`@VOn|!m!Zhhq z(k>;HwZ*XshK0Y6;I-&yp4Q+zQPin1e+{FixWW6Ejrr~XRd zT;p1kMwh&nw4~wUkc&!a_^Pu{x!R2G;7UtYZUS>4V2CkI4B{6k7@Rb{M58;t(9jW| zK2q;t(VNFFHhc*xT$KMCX14iF1Z2jztJmDVC6M4tT9M9W>>BfygiqrzWv((U45KIh z8QO}l9U-AwNHhllyTJ1uSG;wJx8E`ul;Rk;7Qn+4(s*vDxc|oH!DYt#{XeRQcd6JV zmXsW$$T*2kpnXFC7G`BR5IOCTt}|ezfI+V!Yg$S_FmH;Cr)4D0rO~^)EperI%awr! zN^p==WLJng9aBNGGrvY*%E5*+)I^hD;}7-u1meVRtiC}?3KOArFDCKCVI1k$VU0df zUwb}%)!sLB6%tN#P`%DTZtR!@S0er#LWbGyUMeNX z7rQ#28!!G_t9q;V{XJRmR)cS}bGF0EqCKh+v-4X5F)o!G1^ml5?-S$V4V0HLbY=NC z#h`cug&6pk22AHO^2x2P|DwS^$DYZ#W5V=8^Z@avK?Q6un)lSBzMjAcvmyB&-H&I_f}W|!%jnu_ebEZZbj1k~sZC-4yFNa1zY7XV6QNdGq8K}fB={rYfJ zrar&^!>S3zd~!eXUUXIRESz9Q&Qu!Yc~X|$3^G%KoYl?kA>tK2zVm8!IECL=vJcrc z1Flpd-(uen`XZTj#JBfAH}xjkS13AW-fQgq8P?zRZSGFoW^-bE-Paq6o~=Z+mhvK& zBOgT~wA!qC2ei>T+n3+NS;d9O-9cPkgL}MjGG$G;KnXR;o6iOElZx-EBY_N z264lWCHI-hhsQsMgJ%Q6u3C9xuy! zYq8Twp-l0!bx6h;eC-<_n1?KB_%f?iPX|+$vS6L>9vZ7{O-_kbh3QB|7|{}JxT*b; zZH-}LxYvJ=;jBL?GVPd5+%Ccq&KJoNxznG|x!-D;hKkz#p7J#4oJ>Xb``QQ0(TGZk zct#kOVp$t0En?VshGAL|MLr zL=u{s^2#$Jf7&DVU@DeKvJ#LD51Ufp;j@KhodmH6gsKJLSz6K*%;S5%E$w9o{BA}rDc$4JD}zU+AZiX zLy`K@m|q!IL<*y{^Q*zJFZl`&z;B_ZT{$2ctr z4H?|9nw3$qk9qsrM!az2V_q8&GZG3Anu6d6E?;6d6`0~i>*5PhqQbH4IPRVvFDl(2 z;%*;N+7mXfs551olT0^;7omEX1>hvFrs+B%GD4A#+)tgC|0Ejrn~!cc+>m1y9a5*| z6R?T(7UzTqmt+~!C2H|IL-QKRuEu(c2}QU?Nk;hx!J1sH{gJt)vExk)I?5+7gW=8} z@L_Z}F8M;$Y7T;pM06ZdTW&;SuZz6a5JMtmVKW#wDxsVWi-Xa1=TIXhS5igdfW*NH zuD5Yd!r4wE@na^%(X+o1>KK=bd)3EY%^F@>HP! z`xBl#m8~6-sz>HP7+#;DPcTPTC>Rhbv2J5oOQc8I;F^e2oKUNp-)Hcts56Qv&~U(5 zYcnnwA&=0bF;drkpK9{ySfq=RosaAazA7<6j9yk(Pm!SSI14 zNTT!>g3m%abMJ!kQLs~ysIP7TCqydS*Z;_Mv4M45OFZY=%i-yCCY*Nei^r6bc2FELno@`z%?qgi@*Wq{ug33G2z_atsX(F@OGi z3WdUyCY8?i9;r-*YHe}r*u)GOdPS)mA+g|TT&9(Tt+;F@#-(?QEip01Pgd3xlMqO- zD#ipQP;LUF7YAtNGD^94>HtMPd7WGWLKM)KpNr8O+yYBt85xfWqX4b*KnSV~lz=2v zqERl5qITggzEOB+d@UI5a)@hBVFE=iSHSoEYE}%Tm5bL#Ty`5VNl1;$OAW@w+1E`H zb}1P_qg0OA7O`fGhyW2cNmHys#CO+Or4T}_im*1u#!fF{>K8E+w}_J@sT>7yDJBy_ zuZWYym2{$CB}6@P%fCzFiN{pSD#z&-o^uaQaqL1Ijdl}Kw`?gU>@XquDp}l!B;Maz z=Wk6W54%-Z4I-K-j_|xn))0*qO)RF`S5|D2-y6hko2A#aa#?G%wn?ZH)p%-^#EnE& zfmnhd$3S0>VmYF06*0-gb(O3TAQPc;*2S5)hq+kDYbYbE4gzxX<`He$%*MU~3&*!0 z8#fW+5k)bP(nzv0ZQXlKseN>-O~**2ER$Q%$D%zJGPgg^AU=67&&c{Y*wGP6 zW21y9$^^tJkM?~^b_1KoVEve;X!C3yE3;wUFj094eM5N)fx^q>$>j_5_4OlG7~3*R zDYC>1=b%yeSc1UE_X8FN1)h4)J{%mD_|=WSpuB06zMz1~xw$ZvRI=7sHii+SSmvud zLw$L~_nEiHGJ-w!A|6;OcSoPTu|2iGVR(Wd>1OY)1;CbGYoSZg3-A`ZUmZssL+HP)JOw-t%nAlfU%tRdO zL>|cP{kJk%l*=JG&m$Z!qO48Q8M^QX5tlbx8$lT%mbfPCh07FzfHfXsAuK0E+9h1U z(T_06G6E`gxx$f{T+UJ`7`Bu`lmvvKn`o+y3+UF$x=E~}Q7T4j7t(@q#K-qFIZxwh z=e^5$0TO%UMN=w|qtpn-XpHeu_$cMORXvHSsrj4--6kO@zKhLP$wW^yTKQ;W5GfKx zWsC`utSu!x<1)Tj8z)|FQ;jWmvEw>9e!<2r{a>^{rq+eVD5AJXXiF}0 z47nsVreGyRBytlS#mxn<))GeJiTxh2O_rJ0$qJ`7#!8rED1mahn4EP%tcSIRIQb`7 zv}lw#n@Uj`@LiaL4e|ZTgN7l(5>YdGGs(8%zHghWNWTs+#CR&nRAOT{VOFc^%$!IR zV!?|e`tkv}e8kvT=oa}=6PKLIG#Z^8Q=Eh?1YX#vlB`au*Q zUe3n5mNUM##D>iw;g}GI9+5SO?-Q~KPY>g3@RVXv-mth|U}zKmk^%#V@5698V*T(Y zw3!Q@;9FsQbQELCZtVq8^aXj8altu(@1cx`@qP0B{Vd+&=?vcWS4K)JC=_yN-$NUZ zzQD)x3T%!dA|26R&y5*`TUcPbW{j?^RoA|74^BoJLN^!H7Q73T)C||-g@cUu=BJm zFSXl^lZsVswic!8u2IIcw$Uis~L|W&^fdhVQoYl zTa*pmihxRCG{jL@N&oB?3-aBv_hESy88_%ap$EYm^bqBP3aG@AyOlqcTPw*0crbTh zKAW~|Ar38l`8)%ChQYc0F4kC4o$Tq-%O#VJ#pS9X@#q?3@q9PQ(ijh=Jtl$}+_@47 ztJzXC+D+1wN(hNg2>du;D3wANHmHot!;MSZSIJ~ii5ZWgsCwQ7MO1t1X-F$-BdPn{ z#&KMe|KC!KYBQy%n$HLlw|2M)in!&hXyxis31(4AyE@rU9#UG33nnr-X>cpECcjx1 z&t0)myO0~Hc!D3Z?cxv6GdzyU}mFtQd$4<7WG{d1~a4g0b!}u1>$l8cP|0sQfW90fRg#km5 z_sJJ>Xsrl>7zPZApqjl#lGSSG^etw5d^3;zZ9RXv?_T<2i=JOVjG|<%TP0WALPl{^ zBG56O_LDgc3dwtT{Tgpxz?`S-&Yb5zgWU%Le)q$huqDtc#@cZ=>C@!!OaZjQcrjLc z1SW?VjaI^dpCh+$A&)-r0HYf>f}ckjkAB~0&Om|w!T>gcIes7GN*K!bv0&~z`uEs_ z-2Mk5^Om6KCn}fmWDI}TJu!2iQoVLy06nn^$dhGW-}ZnVlBfgQ5s7_rpk1m+M=ol& zYnB93;F*cKmC3Rb!fq0jRw{`l)v560TI$+nDM%S$st$*?Z#ThA44Fv0NsM#;PgJ`VoedWsdRiMKD$f z-Bduvm14wrl&qy>W8#E(DOI_NDI|umv5;ahB$xNe&;T$u& zvyTxTbIx;BWeu!>>@Kp|1Jy*UTWC>0^PwRK0t5&RL4SrI=to;%Xh*v{9d>mPL@#kALD?VPC^k&UGz zPqR9Cb;si%wa(RrwO)@)8}PK& zfeThmqdFYxvlXY0PFN$bTrKIo8XR9^qlnDv;a9neaeT7&GJ+-Pg z8Z1o-X6#!)1So3$4S9lab2hIgu$rnO4`ur(RJXAC0ie^xp6oQJeL+w_j@R6oXuhvtdQDkv>`-?j(W;&NuUxCi!qy zIp~-mQk_6WtZApGI&T{wMJsIugHlN~wUS&Kfki=cO{#KeGXoNn|!Pb{HaI$vZfP&c5s!sHk_AssIQyvH-$?dSD)7e4U!mZlqr+HI=)y1r9_(x_|9;KBc(l){P!#8HY!71{%nv4pEvEg7=K;jQEb_ zP>6lUxXxVLnRC1>oUF%<{AJ>YSFc$?YvqI_nbQn+-+hnoELZHEthwVOhiB(R zh3J&sRB9K>ZC@Kd_~2Yl_@xq+9=APeE~cfCY`iIIjzJyALuN3Tq4*f;Fs-WzwU&fs zC9jmFfw@8qBhf<_Vct7-7Lk7LNxK<4pL71rGx|G%+wly$#(JJO+g%ZRqnn-4b&h^9 zqwgcL1%y81W&?eH&iURBi?~Bq2Ex7H;_Nqmk9kUH=b9YNW4@l(;zq<9!Oz(_xXRuu zukhVB-sJax^>;aWbk1mw`8*PxuPpo!=(|v*aG`6`8ROvSh=b338hP~-l)dM0VS$&3 ztj~z!F>e1RK3+pgEq;jtlYH;l`q0<2K>gYJP?p*EX&aZzH&=C!h0KpjLmq2^PN=TQ zHu}{%ZR+d)j5H*mDI6P=(az<5LV~aOwi;ifMDmjVd zk~Vu3@#O3f0%F!A9EHNV;n-@BD|kf4#$c|XzdDQwqNN7%%_;W+vnP%CQLEHr$@P3| z$4RCrc$24YRn(uDLva%oz0pD1_wC6f`SN~J)V8`kw*8!-xQ&_3W}sk88n)Ssk3H{{ zGHfRkvKh8CWMag(D*8pz|3n%1LtFE{5?@3Ej)-@2axt=IOh>vHaEk>8SFY3V?(rwz z`ZjAf;tE{f-DOA*S+%_>S`!X!Q>D{>g^CD0DT$Sfy!+^cbJt<#iM!pJmwmuta0L=-1E*rBlWrXiTIS8T&x! z1m_AJdvx&}U3Bby;R@Yn3x}@bqy&Z_HQ|TqX6y{J0k7cWKDmqN?j8p>UuI|Li1oSR zqtwB17w|D4&Lcj8514mE?+M=19bQFVd>Om?947nioT>@-;?+-Jd(UBVQ1P#>8K6z) z!`39LOoa~Fh&%jOkzXf1E5xKIAv<=^vz zoLsk`$35o0ajD8|_2zu>elGv+YU4g#=-Ss(O;x}hC@S>=;jEfz z6t(KimlDNSOL;%*HvNz~JMd&x>-%bZ?Q#-xSpv~+$W5C*9YfWpH~8%=;RL*kO@}R1 zo{De9<%DzEbn0%iSHvr>3$4Pgwd(B+BQvNXuIgVsh6|1&)uqv?R*rL4u)@yHg46YR z-5Xh0Ems^ZcB{FJ%cxA%X92N_3$)rLaV=nuzGEb#0?|cOCudt}d(XE-qVCCD>VRcY zC6~Gn>ZfX?Jk*a|*NBhRK3){d6IM?!S4S%unK17^DYxj%kxaj~x&W#xTK z(VKzX?QdP2dsTk2MSbCNQsBMES);NT7s&&4A3pAyd8sob(@^F1oW_I%s*b6_hdNly zHsbE-lqeUWvh=GRX+m{PKsH^Kph8w5<&jUm^gQe38F%kIhE2}jQUnX5dzM2 zjBDjP-+V~Oo>0JCCi;LSL2^M>4(DBc?x47saUHlA>EeQ!>*;#%tAzaN9qi_F9DeF! z++Kc<^ZXDWJx+~YJ2Y2pqZh$N4{l(%`!>7%EMvBQNL zuy?aa7mcoi(9Mvm*Wlnf=5`3$p;Bo_((e)a&s-c~=h&DNKYsL|$%j9*_0bR1?deq= zo-O{&j{Wx=>ij{@ao_J}pKurbK7+N7e;tDVzW=`cJdYDEw|}p2*%|MN=W)6@w5Cu> z4L&FO(9{}NRmO27=iJ<8jgo7XT?J+WFNpA}I0A_%!hwq%NT7F$l_^=N;pCokBoVHf zV~w#CIFZb`D92_b>HuRg&*+2`lrzn&X!l5~1FNROSPCf>)Eqmz@lsH8dHa0-{quuN zsMz}4rvJL8jeQy`NOjV5CaqoW8`ez-PVvrgu2dP8^VRsq6`XhYuDWy9K~7J$u~C`? zq@bcGZ2(bot+`r6Eg(32h%6Q}+`}_0*J}KFHG<4)l1D`ndLLRa>k+3oNr?IS2f=wE z+Q@ob6MVoGSBE{Z>I^zD@myt}I|NmalUU!sPpCr94wtD}oFq(}1f{fO+sj7QL?g9G z!}ocA;VKGTnmhTv1g=ft`h)h>_a8);CJ%b@aT5VX@U;E4oit9ZjemnB;_$9=u%*pn zJZ*Z$@?j&+lvX9iC{|}+Qb{TKJ^sb7{0fVmU0#0W6~6u_Zy;7UT=YDgMUF=! zO#|w!eObitX1Hc#rN9o#^>M(wu+&0a2V_)ou$mwhA!;Qh<(5pAJW_xxRqpk;bMn-M z+f>#?wyNq|gQ#skWdv_h2f7U&oDICN+oP^+VXgaCUF{}`_wD+*Jwz|H>!J!O3=bT~ zx0lSr0xM%1p{@iYXZ1cHTmPLXHV(n75F`=R;j*Fk@4)T*$d~>SHy*x2dHpLS-;)T; z>8k-(0PQ1qL(b1hNsxWv=(Sh)*%v>@>;L>WIeX{3#2A?MJzXD}&3d{nR!D$zlv%`I zyM}ZJn9VQ(PTRn<{D=>T{*cC!AN@f6+4_-OKPXV&{`%7J=)c0xUVL#;1%E%5yvXHy zulJV+OD|RDx3uE#7x-UF7TSR32ZXM~PDm*+3?sujvsw-0tmIT>AtCtYIH}~E*^U{e z0^p$!ydaJjMOXkx$Bx22j=343SZlpEHZs*UF$nYy78tu2hfc`elhtts+_%grRSvU@ zLw#fv;Y^h^Rcf)eQd{~4X&7L4{={drecZlZ)#Y(1%;U#YTbE>dp39p34}UpVn!Lpk z#BW>$ofyH_s%Z_<1?NHw)CKX11R;hFN5FaT;wh5JWyI5gMT&$v+^ZR}g5oi{s#Db! zi^Yr>9qW`333cz3g!60?1KJ+h$WAo0AVG0XC}RO9M43^nRM*YmTLM&*L31@B5i`7V zXiBxRC=tsYhDSodcfz7~6mxa|YDN7`&Yd|IvetyounoD#xp0XT^siN!lOOPp9~9#Y zEf8rEl#P6%KH+>eV;Dxpalrd`*p;_Q6Mm`c-W*JlApCD*|< zn|Q#jGvEY@G7~t$I5H!ekwu6S2vRUhwKDFk+0sa*YSp?ZxM?)7+L`yJ?J{i~nB%7F z8H49RN}PGec~O$WO$m&qoQv^53J=U!31sIerbMv~k9vF6tY>bnf z2xLJ^*C?J2)dZ3Wv1f)bpU+vYN3s=8M`5isD2U$UeGQJCb1eX?VS;HPdXGu9S1+X+ zE;*{y!-;EXMjG9v4dSU*NkfK`N3_(=j+ROWk&ju00o2wt8(!Nvr5eD*&*OWI69p&FwnW^!OW7riN!!qsTN1u%%Uh zO45O0wcPSdFiL6kA5~07oK{TYa-Faka6w2pk?|Z{>3HXzbA*caRD)m{vu0RjQin<@ zXfniVXQnr0SBzbad~-bKsE>TAWDbksqT|SncQoT-B&Ae4c-~V4FulxCZ&H`~k;qnf zSY0 z=Wq@sBh;k0(v75cSWA_JnG;k3NI-H;0E&VQBlh4f-Mu^Ped(u=*Z&Axy~W&S40*(< zC$Kg6_pvkb|3K zB0>fdW`&FqWWnVrk7w(nus-sE`m^<;vA$mg^aoYNFW<{ro7HU-Mr?yGoNkueto7}H zd?LTNEu7%{zTS30mP@Z=`#GQNgiAw2y6Hv?xKP~+RckP@Uaz_T;F$CC6=SQQSLjN+ zpi_sl)T*h7_~}kjTgkz}tX=dUSYZhq0f#=3#WS|oB(7GPd<$?*D)~jG3Xcdus6h{v zjF2&kD6;}H=NTn%5{#3Q88kEV!k!3M37i0Tt#B_Fq!b{wo8OZJpC_BC+uagrRWaQh z!wqlSDtc44e#Xq6YIk=3+CCye7rKhA^v!TYYC__CsEMQ)MMpvqV(b`(5eYRgbxxYG zj^JI7lQ}7`2u=y1!^|`0NXiKjqxXgtq3b$)?|A;XtIT(L4Dd0~@9nc#^n}inbE2aL z$f}j3BNTE8_&CE!VAiB4IgN;BtQ10s)w1190kcxfAwDt;17LJrq-ds;>Lyxpm2Cyz zp{1T@DGybX?*i+gv^k5d=WT3lE`@AWKBU=*AuDwC;P)n6_7>fCVIagg^0WA%a`V`h`SXQi}tFEmzDQlQc zRYH@9m_a@t*xL;(7LnVJ5@pq)osm{cV(=_8prBF>rUZ{OPoW;C02HauITz)Yj=bJ= z{JJV#2z=fpY+OSLghdAy@B)fbMxg7^GHxoqRYhLwz!z2E?O{^elC#?uou;{-N*%E- z2Jf7X9L+me7rYdhS5vTQaCk#PVj7Ycq|HpMP7|coJ>4XBS_v~!X;2$EPCE~*fE7i( zqE?7Sv5+bY-2<8#AKarmI$-bG4VK?}1D$vDvp_l<$U*6%68Ad#n^*9=`@HkkZT{e& z{4T2pr<5^sULs>3>3yW;sw;BrNJ~%e16D@F5Vs{`E^Z0N1Mmji~8gYD^_J4 z(#&VHWLC>14<0?^=c8tn`|_a^}TYDh*-T4bW%}^f|9aQoY4t*E!@$Jc?dWbD(r{Iu6Ouc zSWB(WdvO?-E9SZ>hu8I_{&)(swyT&ICQt7tG5DbGe;Dg0e(nokM(}~)16H8qHU|(Y zm%IjX3GvNtz61{3j9Nvlc*OS6ZY6egNY%`dax*k{l@62hNFG;pILv#l-@H<*i{@&8 zZHb~8Rio?s7Ias#i?oPC0SU2II?+E-q>8C}<1;@FE+Eql%*DFs68@UB*}ODSkk zy5PuJ5h6KRt3IY0Y)f_jtQgaZTP(Sr3meB$^B@_u6{Q%C8P0=h-cUVqsuuQU^|<-q z+OTKUxr?|9XKAg2jGAi|+xtota$fMhNATo4QspsIRl6oMAq2A46M1^R7~{pkTNje-(j7Woa(@pqwD0g5mQ1Wk(|S6ok7rcc$>a z!v}6lQhKa)WI%1KPfxwCsgxP_oO*WpL(Wc@ zeB&$M;NCaisv3XI^l`>q_nG^8zAXhh;Ajz>GqqlMolw_Gw>nshKvQXt%x6}oe^ z&3}4w>orlnU$hfH#kGflQUegbWKDJK~ArNur!t=FAGd3+(j^_FP~wBtBj;N1-FA zF(%K_lt-F4w!$gK!U{)4ITT?}AYz+AA-~lkc2@cG({NKHv`SFDt%TqhaSSC9RXK36 zI-6>l#479=+!V*1hJo}`oB5I^=m((`WuwR6KGq*JEc*CkKkPN86`Cu8G5DHhsAfzP zI?^g=6&L4R#LO`aXLQ|wvmWP^QWD~JL&aBHd&&*flu8YdEteT;VbX*^>6$s5AHpp zbCn3wcM(H)C?hKwIkrUcJx9*CaWF$Z{t9|{9W`G~`Aq9viL09YXX~T6ekcOc{4 zcT~A=g|!+9IO@Q+(pJW8$YWmIMuT4mrzY!p!_zeLR;{u-FI@3e%~;GgT$*E4m|I{G zBWq8k6*s=TZH}ot7L2r;c*Ck`3*;vRpX$V4ZowE$Gd+E-Oli>5Sza7C#W_fM4d!qz zfXvun9<0#%whs|gE{dZUiy#vcb?i4LK0%3VZcK~8f3y5K;nL#Q-K zhjZ1vb2giAhI6ZgrV&JE#xM*>vu!VEMe8vqN=I~p%1FtX0u*zY8cq`-IEn%(A(c>%s;j*BoG50j zB_Y}zF-v9qS2>Mi9F;Mx2qB;aQc5`IFLsMmgEQ|IsMbn;J-4guM{vX~7i2BEsno}= zCtIMT$pJZm%SaH!0%8$yhVO+juj(Esg0q@{h!0E@!#s?YF5|1Y&eK?0sxw39YQR2> z32QntXMt3NQL8*>ZD)*@IW%F{kIeRjpa0ShH;w|&-<RD1}hZOa&Mwn%Yz9ti*hMqpc z&PTdWzRFMihkup7_h0|lT+cnJ%V>#wd;L0p`t|RkVaB>BV=id#@u6cVJ)#cNHK7kI z65QxJ;=vv{2Gn~D#am%59Z?3n7E~%K^+&y;{(kq39|jQ24@IE!~%6_kA@HP z&4I45pw((++#}2pVsRvtL4<^3#7L@SXW%Y|b+@a9^#I8w{;{}*?7LJ|y!e4C6{>%C zzRHhmOX^}1@=c%4)pfNR0I80uTWoMAo){zIEAu>;T!ZF1fB->4uBS|nII8Iydf?xUQyr6t<%f$J*AkyhQ- z(gqN>9ZuOWx&g*GulM>Cr37U<7Lz8YKFD}%CT)b8u_ET8N zT$N~*irQ3!R0>HI)MMfsuHJJtWM&?QamB}9p7Xg+&pC`MED8HwxV9g-yUwJhbEZ|a zlY_NY*@ajQP&H<5&pU1}Iz}5PUO0`yS06m&jckl+h>sKmv&_NXoO$Qz)+=VE4G~kX zBeg4NNLGq-D27rPon1H@tF-rFSjnW83&&tYHLlO+>_v~IHC7bsjohxMAatU&@+=W^ zl`fOBGOWSpp6Ggd+av;6#{r zJ*VE2rw>V{A*}kPX{~rY{@G2(%--j&FaXs zgpEcj139O5I4;OUNDb&!npWK|q!>sxTDCV4hJ8QYP4%E()CwEJDvJEy>GczWjw- z_~kt$Muu$k<~iEwDz;QB;YAwtN~}7)j(~|VM91k)$NKOJ{q7Ot=^5*X56LN!x{m9+ zGoIVolhVXP9qNoqntig73%b8~b~!3%>bONC>M zK^&p$=;l4$tmE|Tgz@~CoC{~GHCMxoD6Y|YK&{UBWOX>F70Hq+-D;o^x`50*2d_o! z_DDG$s=t7P;_C#8sw3DIvGIwe6g@}GQDn}L9r(aJ3eL_EOO(5R!hU%RWzB6IKl%Jk zuJ3hx=l&^YdPU|r~~!C|F{3!r~MM1t$)R3Tgv54kn5(Z`TLs^U3`6^>-->{`Qz@R zo5#r%sJl(|bE@FVV|d0V2TrHjyKVlvU9G?DR9b`daU6Mce9FDM4_Td$_`c`!U;I3a zoLPV4bzUpNb0+j^^uQcXU#jcj=-|fB{y5M7>|bQ}@EVIySiSij;@Kl+ohP`Sg+$Pa zFky^2lT=Bn7=f`UqHCUWM(jJ@SS`^4M-mCs>#np%Y;sSuR&k|P0}Vr4a2>_rHi3E# z6BQ#nC|y7fc93o>;gM!*bMawsn8(v`v`e4orA^YvF*N?2UC0mY@*a3vYqcI42_)0S z$~WH-h)R`Vcmy8;ITtSAZVnf!e!0r0at)r%V;XB$Vyl}3zIA`9v?jV4sjNVTCSR$f z9kWn_L35Zotkj_01qb3QyBn>n46De)I!(6ufVhx`aAEG!g7N5L4Tjr5sJL8t>Q1S= zN!e8J+gViiCWh03P#8jDZUZkKXRa*=oKK88qC{Xuz?h9|+-k}EB(mxptNj`I$`M2G z1Q&<~Rw{E|@lEbhlar#_2&PR6p-rCl*Z}yH1gR?G>t|6K6^ADG*&GUyhPj;fZ>5>! zGB6nvRMfwI(ruh-C$y|do3RKT zQZhqX6H+FL^3s9ur4GlB;uyM~+dBt! zR$;v&r-9k-g59oTwzrGxW}KywlkdL8a(PZ3K?%edd7&%396Jt7xD_M&6c!Zre9uno z$P!2fXCChMk(2ozr*W5)e!(zXz^tcqLhzpFRx9p&{c9|5f0rbM^OQK8MFMre8x^F~ z`$lzRrr{y$Y{{v@-t!$_{L*#OZRM@sd6#@!v9*yIsb0m^L}&wv+@jD-ns?JGsl-N% zk>Uat9KHkY{ULer0`ZgoGvweOb9U#CdHv2ky6d0hwI6$h@BQj;fR#EUVuCkUhkv~z z^nq2$r1cV~GxlPHdB>?H9-f_YEd!G2yZTCzzRI#TEwyLsqqcr%1NBdS?h8-*B|KaI zTB}t2;8q24`n#I~c3_)}UNLv|yN_3|w>9n`M0uHhPkq+X0z{fdy;d#1U$Ae@AZFr# zPfT?0tDHtlCU&vCPGPkiIXOAw&U+6ishY;zym1x1cMpB<9zPa)_I1UP_e871ox!uI z#_Tvj?>}Vm@E(2^@OflTt+vib!_7UxcZjQm6;v6AVdG$`3fXyF@9-3kvvT;-OT7Eu zJIH!P6j(S`d5HF|j&^)jkqct>TLIv)fXHr?q_VFcL9ZqnY@s#m=i* zh1MzfgIvl+iw{CqDq}r{4wD|W88{(nCgl+?9l=MOuQ)g-5hF3?(~3Y;@S(DuiO_^nd1)MC@=}Lgad+fcCGsHp-;ia?$ zs3tEV)C9I@s>yzPPbrTe2^V5D(#Zucl~kkRSf?dE)R~mD@;Cw}t{z=0BvHhSF2p9K z`tX;TaM5dM%=adN5;5?el0A!lPREMReWIu51eG~OjCsx&9RWC)Ro;0DJC%gf8H-0J z?1^&L8JZny?^yMoC?Lyo+&Hphfg#Lzv|clwoTDE0;y&}xG0K{8tvD8Z0^_ggnole| zZZV_8p5?6Leh7T0Td?jAc;F&Ki0Eui4gp-KyRv?W%*7DP%x8P-?s{$>T_^qNuffB2 zIX}*naZT6P4qr~5nfSUNd<(k8lL_2kb^OWk9=GCxm!DG(Ke6DQhr;~j89EkRS>V>5 za=NB0Gh|PYSdGybiBerxgZK1(ChYApJKAIC${zDu2iS{ybT1uIUfZR+@fVSBgE)8W z?JZcXGhg|mZ)4%x6kj`9z9@K);>g~iPVqzHOg-Wg3vm#ucSF}l-u{zsv)KO^T>bGc zF@NqR#YV*DoBQRnK>ee+KKguQk5U{*vG5p?`Mkie){xFKi@Rv)AHUw z@pF&=&Wk@)v-&|vKn#I#EbV)qVV$^t|CoDsA21GeXjBaRyZ3p?c|O*87HP#^*D=$| z77hqOtX8^Rd+xWt$MSFgU3Px*r-?7$Vi;ER6evm@62&Kc7m>chbv=v491w=}!1{az zQdp$SGC7X^(qH1>XMc*F@4UgAzw#?Q_|Es(pLLk4#x`AP#w22l2Dw!t#Xa5vH7JHJ z#wdn`z`6_Ue)eO$_l>W!e6-|Xe}}&B8e)(d{8=R;yoHAhiT& z4Sw8(!=i{M_#SXzkyLDJAX}NuA}UsYPn+>5Ha5V7O%eEoM#-STp<7a_I9HkArAz~> zmnltVlWmr@px(7AckNV}Lh_DC;p+JrujDn0uqHT<5vlW(gE?hyj)fT|L~<0Y2&ki@pBxt0&@^x@nW9c6w@SE)VOlOD^Q=0% z)+D6>H3>GeW=2(MHKsy{3#QJHNkK&`J*T-*ni(Z4z8N~@Qu~R{d!oC*OJWXvO6D@( z?_5$GB@N6&4~+cGClaYH?)9?oV$ zuZ8}>F*{PYo}qW1Z{`(`d>y{moJl(1oul(TP7*o~EU)kI)q`t%d)6_88LMu=;3G(& zlR!Wy-snOPDKqOkTy%KLT-)En*5}MM@wv}@j@jXyoq5mOch32jkM43kM0VU;9N#ab z?AV*n=$(Q!?*TEKs|H5d1>U%~=54N$9~j?z_Y{9Pk_KhD9`PN>OqpNr+37p{tRwV2 zG4wSti&QyLoDqFTob}B1cag5cR0+Z3yEUXcxZ8pp{T0UJHz=$3I4_00Zsg{T13v%x zPxF;Oex38vCHwn_tOJ}6EB2j`oU$4+CPLSol6@-d_QGy(+#ij*-~JZ!9a!A>Ynbb+ zFM+rX$`$z$j@I9I-#`8P$`4u}{XqTM`jJ?dHR<2~M)CcsJf+TvAiqjekC1zx?AnxyQ<;7bRgX6c~X1u2? ze(uNNFZ~?L#SYiM_(gVZ-Q?}R`wuyN<1G&7J(d;5YUbe-f+|BPs45{uwo5BwU{+W= zWyCl)MGy8lStizx)|@R@jO&qGFWhKyp{Adn^w;fp{+nPGw^5BB1v3t_n($S`V*N%P zY^+I2(F9m?*W8xsIFKF8N~_Rju!3f-L&h?zI7S;O#S>$P>Imi$*HKCxVs&jeTCG$B zqQ!c=tvU=MO)4bPbmY}7Qf;h8MB=uFCKaR^$(V1W!_93HLl}YaQYT~j4Ad%VMK(9N zO6-}m@dUw*0|(0!o*xGeyd(RbuN+l|FYS@EE z@{lLiz5G9QR<2aI4IrIV1Y8cg*M6bp{2<**f zJP4kpDC!)pI2L}FS=S?rj;@QigATv5Pu$t(z4@G-Sx1aQ7aX0dd)oy^h{E~FiU*HQ zIJj~^SoG}70=;HrSaSaM8~o&#U*Uyo1OM<>zs7^IK*trkL*&ZZ;az~;ISs{yPr{>p9k~Ih^;r^ui0+dc`~U z9&*0Utj9!`J-6Zx_sg2&G~zlVc<|zJL*mLoU`|F5R?9VazxNJ{w_j)H)m=)QRkI`9 zk93Uw!@Qma>d)4PvbJR_j|bq}-v{0|kbb{_`l4#iMkX^+0XE;?`q^pF>~hcnaPi;i zwY2Znz(c0CU>(YI&QbDMt)y|Bo}P32y}P8Obp0Hk5}%44U-BK#i86P}jyYxtI$w=s ze1)YnBOvzZ(Bkqj`ra`!JYE9Hdx9yN6H#hAH7J;^7)s>}FRmTo7You$yIlRn zUnJeQM&C#dbN2!VU;YWc_sws!91}-U=!gq=E~yT5`sj7Lp46b2kXj z-Q;|oxP~Ko&w0+9&jV!hTp|#Bcz@}NOrw@|@xg7QIALQIKkbVr4U<0HrMW}Ape5n` zjFJkLEB=i}s@M#Yif@6jp_U15r~{=FPse%8X&WN-XlW#pb{HyNF;%2sh;3-Jl1n!D zOiNWMZPhJQ8n@X=Z2I;NF}LY%VAUL}O27=HCS~pEaIOZBnAO zAJ;r5@cDVq%@8=)KjNK-kLWa$x*5xU#-mxs?XKfd=C@{H>TH%ZGgPEXI6%_HH~E4=;y z?)=v4=)+@T7&-NDudF%z>RY_rkHo!>o!}W8`V7|@chyL z>;K8OfBTzUxq6+WQ24PQyUAx>-etYG&9zV5;%ENz|BP?E{yl#A|NVD)F?w!U;dHsG z@B)N!NUZWPi`gFYc*yxMaCNrBj(42ieZcAKzsvmEKJM`IsC3vg8}oE7tw7GB97<9aUkGJ2C16VUIIaMgTZpt-{Gd&IO@&Z zGgV}6WsS~-txW-$-mw~J!r^6|$>^c0IpQZc6$JmV#hJk*SIW4CgUAxK2 z_3NzP{T}a}oG^E=@WPIplM0yl8hli-bJPC{qZNiG!3aD1l;@t~OpWyD5!XdI@}7IH zI`#=wlbfAU)HaFI_v@t8K(GaeTZ9ZSBQian#t%=8*y2FuP2ZkShN{b*&KuV}HN6Mx+8}Humo=)15W8wUj(}9c zhq)T?%}-Ccetyc4In*NO$skt;)s+}7>n1C#s7gmMhYyjUQach|4SWw5!fJ%2W=4#& zWlc`{;Mh%uzq^JI8E13!aF=kfL$bn{GcJZUG)YZ3OLcJF>>rV>)G%+f|6Z!Yu+=jV zvy9|=eW|K0MUz%tz7=paf%DDeDC>Z^mRKn@QE(1%E%8x9bKY3bf7mOz4yS1&r|P{B z6jH90_E)Y6Kk>3>E=$nbX{m`$X~t)YSWhV)pV!=qj+I0{KHTS%WYVJJPmhlHqobSL ze(rfR?9hb;MnYXRU=t_ogoy7tT#R%+u;YcU3xpWS_uk_Gu|Q70lh^mzy|&9656*GE zqZDCW4cvWnhhZ4VrGRsEhgS$)kGja)x9=c8ALnG(;k&?H?>KXb{g-Oj z+$QD`Ka5-*3b%U4&=-#T#M+IN&|}h(bL}R0f!-xPwp-^r=7ZsF#NHZlF5zYlb|SKf zBykkWcn@74C@M%QA_(+U2i7hK17Nq6-LgZzzRROKE8hK+@A2}@9d@n+cH@%6 zopb0Gy!f%3+}gX!>3hG-t!st<ebiy&fVKQeD4lcM*M2YY`tbD%*eoy2ObOq4{b%jF^i7gSI0%sDS)Z2qsfG2 z>=+JqIUdi61^WBP_(3t>krB*Aa*$SChA=4!K42ff50XID-~ae*!c%o79TPFuD)c1v zC?z+pdDC}$*L42XUDL?K=y9$`EmO(}t{^QawuvHQR`G{rY-$dvQ@7?P3HjD>D-D0B zlq{94E`s+B*=V^XJ^4bM()8-y2d2)2mUA3!n6hGhwmtxdOoPEvQHR=C(cXEaC{Bdj z1w;aI%!n;HJblR3d#4=A4CnV4ijv2KWFu=ce=&#ihVzXhZ$bz&g6pfKLQ<84nGwX* z!hcbGsYueZ^*N<;?DRbg7wB@~UV zU4qdDgs!scS&}ti+Eta1tSO~x@9Xxf35peIG?-G^-mT-dU680U{#r>bs%=i=$-Vty zuAWsj-I6*dqbV^oW2V>kGe7t8oa=GKN~TDUs-p-%{0C zZ;J7a!7=JC{^|jl!cYX09YkQ>_bg@|v6&jh5G!xKnQO(g~ z!{6i^x8LQ_=^4vmKw_w!0OtvPM~DIMYbP(8(8qdB8qMQMJvTM(a5kJ1=X>0`K4ZLh zm!&xF4~cco9Hzj*%6Pta$i8Fcl(8FY@>v3%3s}kY&QLrhJJ{P{cYlC8fpuE=0A4C$#++*fA0>Te(#KR5za%VXy#-4`<$-VJWQEo z5u6r2-uEoliG1%iE)?YICy_9JDtqzS`oZg2p#E%qDC_ZH`*Hw%S^xU@&Fh17$w&$Ebg7W5To@z&oe|YEoVJYUSOqoLHBP#6UNnV==;Ffr*gB zu?REL!zCwgy~~wr`{?YLbiU&3jkmawGtnud3g$Y_qvK5BykwR+u`tKNJA4ZyDm23} zI#2i6Pf+5Fo!j>*$H%zUIU>qgR!TIyuLGkwmntxD_Qdecimy$s$k+^8t$X{~FIJ1WKtGe=>QVX~X(JydPtFj@0^V)EM zp)tRw<5c&1l@Te5waTxerBI4vHk)AvF~lZ0Dwoa$vx%frP+d0zrVdAQBi*1#1LAsc z8OW&56$R(!c-NtMq`0yEn-+G|I67Xj|L76BzE)sI9Y7L>k?2tsuoWhek|F2%e!PpD zbIyrkp-`wvtEPlBBQeVb(S()EI98&Ir}~%(($T>N?CRNJ0%%BRzrQDoIO9W*EShiI5#;ft3~> zl{Kq!PD-a7Ew16MV?iW3M~I-~P?7@K1j0o2(W$ zIJHn^9bHcJN3IAIo)FZ|3;arf*Y<(sc_ zs+pw1Xu|H0*k2b84>}e%Bi%J29M$La>~inXG3#6y)ELW(E7yewcbe0y_vGS8DPB%qyt2oYzw||Z>OcLT@ZSIMtDOGw|H$sAXFOQENA3#ZJdPf*fDavt zP|`>rLUpE|b@h4_d+2xAf1%?qeEBnc@4JOR{(Ha1-k6w&z>UsxtiriCmfmwN!YZB9 zz5XVPlQY7No5=o)6o2$o_TsbkgV(b_{n`3ZmTiXXCO-Mb{-fQ-@tWi~JW*l(uvXP47s^yYx3&jeO|4v${N!BlzQenYiSuHTsC@KL z&2_}#rN0q63I2Fr^9pv$u%(X07lNaIhfV~M(d z8K+fhHfiR)II`BypyaW34r~gaE<7WJ8a!$?ia=9U)L9C1Nb?x57Nn{flv*K=zK(@E z@pZOCCkc_O`hA9)Volh20Tp9gc=O?|jv|TdjDRrDE57vNj4!{?F=qwEAwCdYMO*sd z3C`ig(UDPw^J3)DkkoTNW{%gHd4Q8y}V^)6B3MHl|u? zwTdoQ894dfZxPo6FW%TA9vt!~-+YI$EJ@PQ&F09M2wjH@p3ntwhIfI0$3|t$1GqX6 zItxG%R(WJdC$+ohI_BMs#cal{fA8Pt5drzew^187cUs#7Sr{J*tMkm>oslb_T5$D? zH`%#)$k$*0I{Ch%j*K$8gpVCkAW;a-yh2^21dVf#?(A}V-f{Ac zZ}S`f=pS%U&gp;ZD)(nAq!gm{MDs{0%;&QT7ZykGRf6WFkk<*(SY>a{=nk$RyB*gL z3P1ny|CT@grGLs|eS$l^$B_uTiy05Z*x60^S;zUq6G9Ta3jVeC2=gmIz%(>fT50#q z>0)bJcj;&A!(1QvK>fm?`q}zXSQlLMt-rGspaZ^Dr#5WgEe*Ofv1uI`y)22?u8Ln& z&M$^WMJ}}FwPN@rE^=~Z^ew0?rI2&wfKSzR=NxCJOHR|8E@ytcoACwbIh4XY1m;o$ zVJ}s&J;`1sdu?aBsme*SHD|JRM=pxc&1dOCas$&a+~)fw*C3)4WX$+^LdG-FZ$9Gu zD}RE`cNm5<;-fnp=3IHsT5u+mR@N8q2%)yIu^l*n>Ep!AZwx4kt#@gt$6bS(QU0 zh zN+Ehr$qDa3VofN=F`=UPHoV(KA(YG&-*eRUbPoKYCivNw9H&#>yEpK?bz%QYUtp0F z;jP!vteh4>V`Q|Jwl8+VAn&^9zo;j4w)O4oix7Be=RRE0Lw{)1r>` z81N-i)(^o2I6WghdPEVy#eKT|lrQ|Xj}d3D@TdRX-=SZf5oF|6w}%lqJ6$pF9WD>7 zA3UNzKc^E%nLq$a{k%5&_UUzh{%x)w)>!f*AE-ZDKN{=%Nm8c4&1r21+w~@S8SMy{ zagP_sM5F7vt=~F1iaz;TwkzjTwS4<^B9-_uy_T-)=(_4eISd1j9vmZD_@wjv^kR<} zN@C_Cvk>W971K#ICfe{OHYy#rc~#P!Wvm6W8>&gGAWxO#OF{2+3^t}T7lf4YbX^1Q z&LP7?hO>KE9+*?HkSaAmFH((-ya>SrvCLKRbYaFjE^(|%?*r#zq}>^1ZP>jB%*M^Tc^io)Vt+np2tI9k;1&+*B=#b`jdO7$!@gN`GKiS3UzqYIjk!7 zHvt}vXI`t<)l{cSKxn~o!pNJ>klR$TrtG9uRrBkJziP;f}6S&Xyb$ zB@D{k_ec;{C6jYu91FuJ_!#JBkr*9vzCwJVq=b_W2$ddVxeXWBj!{7&E4e7k^-y`$ zp~DBk`GV?5&dO@cB&suICk`{ua6TeoVwvaqd_}j8)pXn@uD!J?zqP+l*CB((SQo7i z*Q{0AFNH+EQQh*@~mL01>Sq{pu$_xn}ICzBT;Q*m?{P=9nT98?pY=ZUCGJ67htC3+j@P}W0i`#$vfK&QvZ*RG}hN=);z)B*mff6wn@MawCMs6N0 z*johlclO!oB7OAqvyMLYbTQD)0&(6m-#OyjZ@$CteDxcwZgli%PPg=wb4OkaDGT_Ys%t8Sj!}=JC#9!MB7Qyk=xf6*AC8RA#KsW~_2RU1T<& zv%k9wcW!gE_a6O~1?in7T{KoDGn}lLNk`}cIgRYD!ES#Wz4ZdjZs9S^x!TS0b?&B# z_CDZ_`fUAKuV;b!v-P2@Ck=(R^ynLV`OA;J7|42Z;Pd?$=u>caL3e?i^ZUMr`n>Lv z?s;3k?p-sB@O~qSnf|1dIX_=>sE%LQyUKGI^F?6C9K8#85pdc9^;&@wwN1cn%>_rS zMYLNi!%4^5il9s?p%(MvT7ko+7;ZvPQroQFJA5&6PK?WupmkKRh~TV}KuU8ZEK;38 z9YP>78bINycMK(QuHa{T3bdJ)`D6WH2_j^76eVq|dkH|DIr{t0vpMJUq^k})jh2}X~30)E!X_y!nmeu{^;(zwkDKUY2~Wuj{IYNm2Y%A#ynU|Z`8n>e zoEHdnP)|*WC~iW;Sqo0Z*Y{jDScz%ItybJil>w;V8ln_;N&XY84m4(|E#gq>6R1oE znHdu!Ogd^aoC7b$mo`uGH&oHOSR*52W+Ebt4@ zo(Mx4>1I`z?ak0q-?yourfAV7$8KE(y{t=r+=yig+#l!cKdoWXJd6#8*fi-Z#bf4)-YTcuz)V28HoMT0(kkTV!brz82~jmd!a}XkO5<~jP&9)L zO(0l%H?bK230hg{7|sQ{F4QDLOEpLm!<9&JkX=F4G4HQ)>((vCZ-0f^?KfGJ0gtOT z<+(b3CLkUh5MFwLZhnn8CddQVkM>z-$9Qr=S4Mj27*dD&z%mb%kSRH_K2zR3gBw@& z$hxk_FN69>F(Z%Xm=5IPDCSuwW5~H06wM;$9pYwK_N<42WiE^%vJ^pFz~!3ko|iSj zJ6x*4_*yc7%oHHkJo(}lnDPo!4>*dhTc=`HuP0W}3{cvU4%Lm-Dr3InHt!PZoPp}p z4pW>DNT>!@s!Guogn4U;!iT?3AB^q1Y|<$lxm4L-(OUH`R%azjsn?5Q^?g}Tk{K=t zbLTl;C61GFY{F?O%)Mus){Gdd)66IHJ$9n-8>~5VbIzBAQr5V#CXYhGW3163Bhm1K zam6Vo9+s;mYJt%?d>26m3K?}hd$T#u??zs{8aY_>^j%=y8GRp!ArfPx-&xSlI=Vj4 zbsiraF?eDS@;I)2wKq#28m+5U^z@PP63E!^ygFu#h5Lsap^$K;sp zTzVG2|Cz2IjzIk*R#khp{`J=f5nbT1nv(ED<$U`Q8!F3I(7vr7f0D<3@zm2}r;7VF zLT#$XZwK&wA1S43dqec}ea~u@dHCQVSNooi`8n5u5N4j8t|tbgct;4kc&&qKa|h&v zj%jixua@M_kTM||Z6k6JH%S>LEp25k8&U@|-3MP0E8?-1*p-Wl_SC$bM%OCI<$^5Z z+A;8E^j2G*$pubTIWHq&*0WzAh_RB)ImRHhqQ3T#ovzy?OU}7X75(CP)?ec6vP0?i z@&4Bhyq^}<&ULY33Z)JKoA+qbE4z}h0^*F|0%Knwyt;2p$!Z!_9G*7t9-tBE{hX0u)5!44$~X;gGnQYwU^ z%$=jS$XZ8+vSuCj@u@?#a2^y-M#_jdK?zUrIIi7X6At&F<1HZR2Ndyju7hO zQ%XRkzFhoVu0yIeRqjD4P>iRPZ?ald9kN>OYUPwRGQ(16d!|l^Wv#6}?_K@;@i=je z>%?*_oMh!3VRXVUj)*%WNJgy=sbLv&sYjE7uG%@YQ*9QHqe#A z=p%QB6|P$_4oj@>DB0mcPe6EXPx$!rI~?uyI8lNZVsJGvb3zD?`Mj@=px$j}G0co% z94oCScvkDk(ZK<@9X=ZTBwAx!s{EwjLOjryTj z@Ao=?2wwXSN1*;}{m`!uGBf&tk8J@eTS5L*l?OH?7N~=WlkR*9;=7sAoU5$FIf5!bA#-lcW^}!PWHFlphnb>rJ`+~$9E zfVUdWC}u*BV-buqs(h!UQV069RutzF7)4n30Us|ugRbi?2iwPfL1|O6OzKdv zz~o}7Md}}|G#KB4lX?qnNT9|Hmqv4`fqAUKS>>M~BqCiUoQSZSN3K13z~RGtL{U~= zm@^|uAvq;`t0>W7L>`{7{G&f2E)F@E?~;}CK;ARlI87_9v8-5rOA}kXs!n&Wa zx^{*0xo38K!i|#gC1cJ}w5O=2XlBec$S=j3r-36jEY6TKgTaWf78o&-!dyU>D~2>+ zX+gMpNOrCvPai`QSUb1ig3?IKo;M_((zHgC5zK{#-I~_gv{c^#(}o+p7=*5;KsEC6 zu4~seqmvmKF1YGnrA5u(EfkMkaJQaf;^P}D#@!)jz4jb(8>8Vl#z z{1$Lvg*^gy1zzYJqZ>)Xz;opx|IvJ(e-L}VmuuH#WgTG^D6%4%;X}f8a33Yb$klnz z$vSZ_Ri^uHC%k&)I?o?=9L+MlbHsj!;5{LFV&~~P&ukV+X&^?A4w>0}zB%uH{nvk; zJ9qBz=}&)}pZM}m@Y2gK(M5RmOFzk1A1xuRNyEU-@&P5?<=kiX4;?$f!P=8g62~V> zv_M{uxbKY|bqAcv$k|+(E!NCmRd!$8XLV+9Hn6nFqh-YH?4deTET)XG*x@`Y!>W)< zp(JIo@Z>TuUlitxU5dZL`FM@&t}%Fs-dMOEaj_1P28Rm{2?6IjLP=*MpYrJ2usFoe zcY&^+6B~frN9#KMqqd#}>d)4PvfNbZ-l*KRRGJex0XEfTX_OErAiml+PQmo!G8kY} zC9it+i<{pR)Ni-ywueZk?>)(CcZdsk=bAHPg+gXcxRSXUdUm{{6OWHQd3OiqyIlFX zpW~DN<=gOlTIrHM}Jnl&Pw5sTr_OV$ttcnpQ zHOLkdO39>gU5PGuoUgi0*D#kvw+ZHisuQ0Q74KcuW-~SdP?f0E#7c0)$@sp8nr0VC znYCbVGmov*%lb4B?QuS~ied}&D_aRMM`4Z6k)084ERR{-yHmOO9e5Lr$~X6#u~P6( zAXMT^cRo_qcRA}HvfFzYGYrN!cv8r?P$*0ez(y*{=p5n36~gr^ES+$7omj2Uc@E!_ zQAHGT5yn&)#*D*PQI9xKPs+w>R8~n@14)eB0sYPO?%RW^syHBIhl_j!8?tYVFZ*NU6i+;;P=gGVJxK zX@0HA6~x!`--lY6bbKKgW z^Wt7)r*m|%$NR3b{kVQn@r5sZmQR2B(?~;RngxF07ycr-k!Bt} zc*wOIH&98ubn6wq^_dlS&qmHq&Nw>x4p;6LWH@8QIMyZIOo-mm^&R>sbFy}nH1guT zB{%y8-xwcYI|0{w*eUGo8nbSfuiy8KZwq@}&nmBo7@Y{K99U&X^n%kwPvU$vG8Bcl z&uYBE`LK`dUB!hxdLJqFV zEKq;8K9n_4>s*sXY_#W%9c|XEyIrNE$ma3&=26+u7sy0lGw3-X@0{}+sYTA27~>|m zo3M)8h9pzvd2Lo&;ry7v8@}t=6$fb~r$kN}FM6S(-OwW1 z6h0twq2-=SHv+x zS#*(A&J=@Dl@6@~iWyR_9fTUj)GAyZJ*>%(S!JU8rcqZ(jc#*c#A6c$hszY0$9d~d zWm!QH(rRe?wz_wkRqUW?-D{-`b$}R89Wa~%H)+F);9RIUzvc>HGPLuBi(Y%wR?{^TAxA?XnJZZ z3AHMi@@+tNnyaXv=Sx<_ z@y>d{^2n7~X(2&6oC;RPDkTaY7h)YO9WzCYtU`7jqbjRXC1G;|(So)a3@g;+sJ(6l zEYztx_2;1g-C9 zP>&WPllfz(cvHMra)y!}FZ65vQy=*ICVagJnpe0YbS2b;x1V!D;FSR4GOqLN+5&T# zy+uH5O&53Z9=hJ)oe{+ose{SC`CGrmY`)7+|I{yVoBn$WvejKdylb;&F93hLYMa$Ru74LH?VH6qJE#PKeP4G z57eJ^RDLAZ`_W!5D&sI!quvkSXl5Ikz$9mZN%q3V>2H!(#2D$i?vhc_^zYmH^eOlc zA&^qqJhyWW6QQUwive>`D6DKGTH!_4v&ar7FjvUVlZvqaxlbZ5-GCsBQi$dVVUFE= zfq3(IuKnU)Vg1(IeD`nvf3e^F1KfM>Fj}dDW)kT41MB5EU5ZF5l~t{|N>!rAyr5~k zs4h=fOW6wCAJ^nxd{vEtn$M!R%I0;Zs0dOD#d%zu$(56 zuXBAEP%VW0YOVh0e93pe@g|2?54n1DP+QOha><0~Q8J>FHMng$8q=_Et@2(#ziKjd z;WqnkqF?{BT}YG62CGCR%S9i=4}qm5`%iYcM<;1qQuxfGISvU*16 zS!(9osy9F|t;5t9Dpl(*A(BfSjGQo+vI)kYxI*D1wrf!a87^cm(t?<=YPb9s(a^VpjDqvfHUf(y|>n_1Z{gJD-?B%JNisIOdq=L9DiF9q*v2ME)RQQG&!;~VknW$zdej)##~I{07ok^koWobQYQi`0SX z2oJ`QSs!XZi!vkw6&AC|{$ft_TCuV|Hd8UT0p5P=Td(u^&wrLj$0z*iul);Ne)(m7 z`lr5((?S<}-u>=7+a@c3qXV2})+zIDN*s)9u1T)H z$45dGkj&bzDOX`FLVmXpnc*HRIoRu1EgVZN^drooGUT2ik1Tc;jOWM9$~k^EWAJkd z9p(&IYDdCLB|qU48V|8?pCS=;4(BuGW;lr(653R-NGV-NIaTi4Mn0V$NdyP;0@BeqCySKUfPyZ>W|NPepckaOIoN*ZF`#JOlQh}6VNI134 zJ>OZ_y!R8`t)kMUR-L4?U?Q zpz5}_6SZ3PoeZ92@_lO5jLX&Zi@^TrX6}ncN6|J+mzJ1T+0KxM${-g{2pvu!r4?2h zrjLq3bRsw(QO%^35K){PTL2hqWlJi7C%9RaM(F^q>e1C2yIm{h(ePNA&K1FVVLhyH zt_Cr|yE@z|4u>ZjtVah)0$wg4>!zvDQ9v8bxGO;OXt(D{LYtm)Lf1~l>TQ#vIk?(VE z<#ujKW8W7WC=`P05sURXS{J2y9aOz{EfUnh)0(_KC3=y0a>}HXF%x_UfY#$v26l7pav`JOjzV8B=wF|JYZ&-1 z7rXp-Iq|h&q>qB@dTuY3!$qvuc2m~1nZe$CMjt%A7pC;JcB5RaynDwdKKU_*l=+ok z`N!<-9P+I{d5hDNC4cQd{&&ea@u%PZCV4#NXMXyN{JnqhKl1ng(LdsE{>{HpB}phZ z59j>&r(fdsuYCpA&lprN-w_rIc6U6{4Om%|P00(-YElhEfzdldx}I1MSVvkU@mUD%)sV; zcIVC=u3fvv(a{ywXUD7`obVgJ`pWI>zmdV`$!fV95LeJlQD*O1)%X${5KU*Kl+Q>p>a;>bIOp`iH;Fw_ksqD_4*B5B~kX$}7)5 zN7p-MFWh41Z~hmQ|LI@n{13juyZ^ucf%R9uiq9iih1f^JT##{uVMMgD(W|w4br-cJ zOQ_o2>8P?XP!i`G?WG1W!5o=-1Fmcf*Xn%=9>y5Svp^o#>?{`W+Dm-tfAzoQ8_RRv ze&Y>xU%bKdzxXro=CASE=k_Qv^8EP@pSqR#-aYvCJBc^%-sO#Z5Bb<9Utw?GQ8YJ( ze5>M1t*#lmdBzFu>E-!TTvN=-zp1w*QcE6n;1?>Y=9nM&nwRm$aj#pzW%;52Z0L3&xt7xlP0 zUuPUtAt?oi^MSby?5copQ8cktL33Y+!Bi-H(xg_$V+wACyYu$Cut*~+fhjW;FM;9YwF(fNu9?coehDtp) z$sE}{gTb;SMj+scjaOb=4boLBqSP5=RVF34B2C>=zb;yt=x&;kajAAN%p)2x^O!Hq zysBNVq*7#@ca%~Ml$>+q<~%v^rj63}Y|B0`w)ZADuie0>6R4k^rzVZ6^CG1r#&L3D zt?$Fcfp;5vl(&kPEv2;4LHLo@Kk$CDC@a>om|Y~VlN z>3DcHa))YIw_X+kp&l&h8OjPDXY9;+LK%p&8Q!^OI#%ZZoR63|6yZ<4`3;Va_V~;H z&R^tj{jI;l@BRKC@#0Ij`0Qst!O7`4-}=^Dy#A-(WgL}nee(^5VWeNokq~(C>Jhiz zdXG*rj3~_cPKjpXRff$3*I_0 z#<&mbfx|vQHzTuyNvxG03y3DXXq8z7@HkR37LgyP)pmusU?ssNbF~0V-ukp=q ze1lg%_A0;d3qQ}jyLUJrmpnW^A*~-`GQw z7~`f|ydh^a-TCxy<2VvysFu;WqQ`s?LUgUNTdQ4FW}z*xPhhS|YK*J{^VuHjaY@mP zW|;Ro%;G8JLNTRNW38F_wW|z8@g10EL>&Le|HJ=@fAPM~ZLchw4=cl-m zaXF(ow*Xz+6z4>=Y1Zq`lcj>>YH(oqhS|#oNtFZ)eaHE{=kRBLmbbt975aNC=FW4P z3Omm|$B*@qlUujAb9{oV&+)VGaB$-U(ZFKA=kS{H-1W?>w=&;-;Q7|=k#!mvOGhaq zB5`8^#MW!6iH3WuVp+DTMVHz3^}Rjqq0$lOD+1BeGp=h@d`v66pVM`->KJO7)oM*C z(8W2X9^YItMJmg@?n|TV0zPz`WK44%9ClJR1AadG#^A2D>u|YCWz$JE-O#HpqY-{; z-LbfY>WbZA&9(C-`{RiBRR&Yqxe&#-Ak8RN+DGPErz5mVa7HbIxk`|c8n{~<6c%UI zN?j`+QcKmb_hF8njg;d%9EBybyk?knc&M_R#(q!K;Jg@=oXHr;gi)1Dm0#4ElsW+I zCd^58onCnV}S1>r_cITtW?#7V4X9BrUbW;|Re~O0KMV z0kj6Kb#IqC5NryrzUI>0Ve5M^Vu(*=7r%GzIbI3(mYzqW<18sdb5D%|`(Al&QMf)Y z%wnxxOEF4TR6J$NWqHClniZn+^v;nfh;Ky?7Z4XX6Zkzc*+o9dKrDsZ=b4vx9e-nY z&fghEj+3#7Ju?h(%4u=z-FS|x*PiFvo-yYUB`3^!oR!TijF#MnqlLS7Pk3k*rV1H{EFBqr?Q|} z-|mI=oW@4xeVtL7&lfDUXEaA}zJZ8Fv`M^*IV@*_3#^wbt{q+F?!ggnedjxT=ChyX z?0Cud-o3-_JmB3K!^vG1`?r|i`Z5LAjQ5nSN=UtK@WDZPeVAN+fUp1eOr8E`zn%r^ z&(?>swxulF71CNkgvVLwQ;@!mv)dl_454yvCppE0RxG7#hDcLNAmtKT{oE_04Z`_wW8P_D5es9^GeHo?#gMqGvw4 zLRu~hN5{{aG!kB8|SH+Qu23R!7oNNZp+NFZ?{?Ctk&U3Y}uNgbz;A7T5Ij$OgU%Xd8k`; zYgA2|hY}i*N(_yV40HpwX?M5jwgU?UbkJcphVHfr(+rjcwxe;YL6{-H76Ji9Nu`pi zq>^eL?of9g^5%TTz4uz{n_mC%t+mg|tCAobL1kSj--wJnd8RyP?QgIBe((1@zegKQ zQ_oOr<3mqO-LQr-bu}Spg)!xHC#F?ty0yU?gHnzG*gZ_D70y*fvu?_?T_{bl3{zuu z;@Eqvl*8~GGwTWdF;jiur(@7!Q6il3nM-}bIzdc^ObJoSQYPrSOz8oP7(^LVE{5Rp zvy^6CVF!b3`TphqE*f4n%VSLWq!UflsPp4pfq`8j@C| zkclFMEO;o>a#SHMsR6pUrW+~5I;1MPUa~5nq=B|5!+qpr@LEa(fn}U1-X=#G+LwV< zpL3CVeM~MhUS2r1ZzxL>nBTtrubu~(>b>edTxC7 zIh=J^C0xI817i%AE?uH&8oF+U(wZ0oecxfK2}vRct0nip=}o-#;V*OJNy`_X@hn4P zRtpcEt;j^2(QMBRZep3#P)!59Q}n)J|4QQQp2Ex(3!hNa8ItGFdFH1h*osw{k&6YC z7<4hx>cqV1dB@w%bLP@1zWAEs;JO-2>b3-E4WV&G##Pp6+qV3|FZ=>ZDO_WTF*2Js z=v3C|?%okQ(*?854RD&6Z(;8m|8A={!qs>~H0p1J3wgJG>v}!Md~C3dF+=|1ah=34 zzW#;aWTQVedncRzrfJqX_o|wZB@8j*tTpNYlgR{U9bMZ3xAVV^DR@0j5Mmg#?(?cD z(LsZbHYG)>Cv3dwAtDMdiCQV%^PczczW2Rvt>qnm4{HrtB3W6|kVa|-EvwcVi8b|O zZ|CBlc{Ba9FR=Tw|C;>y&!G=@3kO$$+uXp;XQaL-_dTljXdfV@5{^S@B9&5@0jRE} z9MeamQ5lkSM3Y%o7W4jhpx*sBhc^$HNJ+6=PUk%I`#(rFjwqo_ERXi6msdGA7wX!f zRfWx-#(7*_(kp|NT~$iWC>VXuzVq;0atBtt_E}oszOL9l5<~7rK(Vo ziVs8G&Hz7gJruSeA4j}i5zMSV$BHZD9Ua;3Jr`17Qfri*mc-F4=s}kpV+q(&xtz@C zJm|%B9Jt>ZZ9waaoGcIqWGDq=IG8p?mknu2SS{#9CAs(_RL13skRsO97%VY?AFfNx zsa%JV+pS95FPb5Fw`@x7pbz&dk=M8*g-@APjA&P3hH?7X$a7-TKQ`#=H6?k_v6n`u zgp286Q+A@se;qMqlxnR(sH$k)%di2ID%Yw=h9C}7u=3;FnRVQzh*hQ#Mo?r?7+uIT zDUTcK$U)G2{-EZWeb2qq%)>hs+sdz-bvcQ;M5s*Bs|T?qxJM|4=LYJ!MwBHQ%}aKR z`C^~u=zuz9wiZ1g3cs?3eD=Uqs;`;SHZuE`|tz$ze zZUQ-VY&0En-JtS>ILH7a5mAb|?m52$?|j=iE}W@I^wd=|tdX(?3lVErFQPEk5K|na zkjJlqT5HZkU^* zVnLnh3h%PZ+Q#MD+~!WaI_h$Q3}B7fpTrBAAg+fM;>AM>~rk?tDndJ%BP?|qKlqN zgle`y+ME#&kFZ@&@&R%t_L0!{)Ky&$CM6FO7)>pZC}_nMivCRxvh$taLOY${UVMdK z64kUJnwr@|kFW@ds3Kh(iCsr`bQ3rARCR?>#haH*1u=k!gEo+;m>7jhszkI8)5CFd zHeU0bWiVr!^-0EP9ENdocXA4!hFgJrC4b*nrD|8ETaqF%j7^?<-R8j#J_^CQA{!~j z&q#>nM8;Z&F^ZJ^5bCATMq#xgO2PcakPDA}kW!4-WqeUSHs>->U=%S9xY1$Kt(C?P z$)q)roo>N~$+-DhKxk8o)fyc#5{0JLgs>u|7D)m@7fh%mv`$4h(D zh@>BMw^AnRLc+Ql!4!f>^yPU=B;`ONl+&I88!AeZr8LrrM)I+78liX|B%xv=D+9`u zP%+7Pm5His{ZsNpF&L#FjV9P)=oKJK39TBpKxH})Ns7eAX;)68GDF_04AbZF`e&?D z2x-uv%OG`8!^UYel`6CuPPlUBxX+YzYVB9MtkfVwKoKXI{1n5o(u35gU@Dhro>{_+ z3(vh1;hxQgO_R|n;8eLfeJp1yb*(W1ZI_S?K4t2ZFj^P)a;iVqx+?1asdFoC`bco7034@f*VRFeK5LLo=oYCxT?l7Hgu<8(bcYAUz7F(k~S z;Tyl>TR46C9MyD#EG_NfEjG6=5~-*ppp`3zbS)?~l8}z?wPU^h^zIE`Z_r-y4bZ6H zt=|r%#wirCCb{HX=oX_9bgYvI#*>nXlEA3f(wDKRtEviX3+<(8YK+l@*cXJ=5QZ^o zQd3H#nATF6@%uaHZo@a`l!#j4oGDEeraW7WqD_I7L@IIEA&1*)TM{W_(W{Q^_oIU& z{^tMnZ}HKO{xUf!9)9>C{^S4jkMn_V|5kD?I(KVF%EZVp5JV}IXuN2uH@}%nk335M zy)Sb8XMYCy!WYSY$wU32t(7)NM*VbFIO|BMq?d|7T{lDcQ5vp+!l(wL97*}LxjMv?+Wowih2^2~Qz!KQ zc}VfJwkkPPLpoB^_iU<6c+D%)lBM!>AXQsYZm7-`_~tA(J=2jc4jq; zZN)WP?V?#YQcq0>i)CX5pRlYXmb@MlVFX!AE(Kt3j zF~+rQCZ&Y44reW~^;C6(m2xzm6?i2?T@n$k2*gC_I%p;YBREsxeWY>?|Km^nG(Y>Z zKL?_5uHnfip5?#%^Zzw})3DJ|Dk`sWgK9`(JZ4feDC>*XzUw?zDU7y7 z&XIe_7L_VsFsi4B)@6e&sNy%tqohk<_&r7JZJW`u9`2mi;}bKPIA&_$VwTQ1=V;LEv-yyBuW06K=w(*fI4GuOIP^m(K7B zuV_P$mPC-qL`Rz1BHDtoW%DJTBpM?XS^(w9N?{Nlf8;*4=HJV&{`x1GG!5^6@4KlD zNNjP|aQ4(D7rx~iQ99vLPZuLe8G>@$*zfq_uRO)(<~FmPGwjEGw$3^pe+PW_*FV8a zheuqv-f?bw!sbRtPJyPXSfxOo*L2MRHx7HutA>3^T<#J%*5ERg>aog#L{xUno#*td zghGX05t8uYzUQ!~Xoz0_$f#pF%Qy^r^wCGD>w35+7>JgXBfUGKax-#JbVs{XhkI;H z&m$_4H~{<*jy67S<#p(5Ap!cUT5o_R^J~6QzgxdO>h;aoNu%i4xQ#Wwe{8Ca(S#7z zRF{(+?(uDF4KYS?E@sjY0y$;sL3`f!{cTM4*R$Nmn_gYlrQt}KoHEnN1fw+BM?@Dx zX42rvq*0O)BZs^DY|J-F5qux8&azytc=E|7h_MGWF?m3fnDdwZ;(y2g=b!#GCuTt~ znBtU@!Aq}r?X|1C@ci?<_Z#1btsEA^^x?)-!cu zjmPzJ$D4G(SU*e~f!8H=jwBCb2PuWAP*`H@39-l7DbCr#Q1>2V)S6B-u0gE@A6g_S zVu~;rPDLGiH|n}3gs|S68dJU=l?oykx{p%T+MfJ<}YC*%JE}Wh@VU+jmmk z_b8injcjeQ^U&Kk*LrR|^+|4i_G9?V*N~hb_ZXYe#*!07k`i^9rVoKuIY*QVpnMt5 zWizC=Dk{5CblZdyY=Z_Bfmn3sQOH@N)dX#Xl{kQU4e{9@viZ-O-w8q*RkxU;l z$rJ;zjhTtD9ElKOLVQ5CD|!?w?U2+H+m2V1<^?TmDq*t=JgODd6A%_H_;S?o2qibx6ycGxt8s#ap*{<*Cne^;YKk4Vcyev#Ciw zv%A0K#v;Imr8|VYQnX9We&3^Xg*Ayvd8#}kNG9fttpby}006oOt5xRmA+$ha1E{ho zEH4uV21ft*dNr9$ZabwZry!pBW{0gBx?Vs+KxY+Vvz77($WUfHEx2VEK0;HdOV5OhQQ_W=kfOkB7`|ojG-+X_KeYf~;B& zo_hv+;T$tNr_&vkvMi4}o`3#DkRf7TYrg&4za4Vs$tS+ZwX0V-fBq~fl}$(tu>>Ab zC>%M%nM;@WnSb?R{`OD(J^s>P`rmWm^y!jfS=Y=z@B#FLZ)Wi?|2h7nzep7l#(~?} zKyA&D?G4hw0&eXhd5Or1+zI~lCFbAxo$ODiv`2@eE|9+bGI4pt63wNzy@jK`Cu>WJ zJz;S~A6j;=KgUD%GKa4@;^ryl+Yc~5|7I3Xf1HDZ=TInoo?+{4B=uy`B~U3RVhZc& zIq2e55W!VNdyXWGmD^55Bpo;KC#5qdO@LqRS}a?fsc^2rXge4l138BwCD9O5M+}jq z6wa0gymAh~ay|5Q8%DUAV97EMB>5dm19 zUu9YcITdtg}ICTH1x%iHK}!9?mwQ*sDg&AspaCeFX@T^zjh zEU$k2Bec&yHJE1=rmk_?(Y1og5#KLyt{6RiugEgN7(sL>M?=JQVyX;A#ez;TC3hgF zGG*6^F8B1SM2JE!g3pPZGD#_VELk4e6;qMzk@6R1#GsM=H~d zbjA~`bVX^h6fs25X=s6LnPd)Pk~>l-oAH0gT!NxH6PyYp3eqo0AvXodkQ1{?Q)|f1 z5<3MkuxS*#l2}Af4i&0O$g;(tNJ_D47yPQ7aKT#6gcZyEUH-s*6aMOrjw3V^U2%3> zBewV@QqFjUN;@nys2a(I`63}<Tq7dIWkgsFZP`>WZqn_;KW%MmF~$T4uw<~}RGLF)FITaWcx9&)tlsUX!b36Xqd%tet z$0_(oXGtkBolb#-F`Bxr>ADUPVKSKzV=P~9?7B%Gr|~)G;d27U=(S9yBt?$X`huV{ z!;3RgBnJ{bkG}alvUm*^LbQVId#?Q2r`dkbJBZHGP?0q>P0a%j+|S^L|K-2ThkpLU{NW${(KT+%+KRpXBbLi0o7FXn^a%Z=Br5uSdQOs1(T+s$KOild`_SX}4Ay*DszrtNRxkFHT| zpX0`j%N(u(Hx4Y>ZW6P@T1AvVh$S4Vsw#YlC4@&ALM+Lg<7VwEg&3VYh51@}L8xXk zL?EODeL#s&IZF~v^nq+j?0i)<#ke?_JX2B_-Jo@YGig16HwF-e(gv+eS$87TRZSWV z(@+eHO4ew=ED56(M(ZI9;47b6<$>Mp$ivL6oKggzm_2YFu`{|An5HnVUw!pujt&pG z@19F+Y|WWH{AS*I-)=j*?Lw6Q@$jdhL(AH1JWed^PQ zBz)-j@XvpU-~0PM$PfI`5AbWh_A6v9iCH8q*!I_v$O$yuM4230HOnnoT5>36CbChvZSMi}^_?*g}T)m7`XElSchm^76IP zs9RN)gw5DN&eT;6KuU$YQivV~V=7V#2x&m82}wHC$T?T3ATu*)V+Mn1TuWfQkHe{w zLm9P>n@5g%JNDS1K!&#wQ1stZJ zONSm*D-f*)j9+2JlS@OBl_n2zlaTZ92SOHl1)V8jSZUP}m15C*4)^w$Y|gpW_AK^x zX-I6&CQLSKTxGEaj4cK{li_gDa&WlB2TjU_U;mrjg@(rb*ko#qUjO?z^Fgmqee&>l zAVL(uXfS4MbdImTNE*mgcKDuoko_E+TfdfSeeyZ{8lx(44Plhy=b|oSS5(e4mBna9 zOl57WEJy-tG!9LS#m^%l5Yq?=0JOp*JYh9&){dFsce{^F-)Kk1tOiq|` zW&AT5&@<=++E`*z90tYiDzXfTmtMX>n<`keJ9vNW@`Dy<&IQvseY(W+Y8kgGXTcn?62SAf`fys!gWWH>pjF4p&J14OV@F zk{Xkt%N^C^6p|OjY*EXGymt+Kx}x9vGR@Z8(A9&GYZ8&nc#l$p1ABbrzc%i_zfLFj5pv>pAMxN7m-1xQmysu#+Y?eQ#a-FwJPYxQQlHl^>7ovifUg(G^V*AAM`Iq46kfpPnf6qIKvnf$!@R3!! zU~#bE=9Q;;K;NQU9??`9@c~JOu3vJvzvRkIL(iO`z&Zu7nCndCzz5QRq6{$>y{fRD zHalz4N(~dTQEOSgUX8&`Cq8y3n|9xJIH$37jpRh|p}+;TL7P0B#As41Z2ZCx*BDa~ z!U}8Zk|HaL7y?;@rY@u+a##n+6l`ZX#Zh91aHK-I%&AN}wQ|EXOo)w?#>-c&Z zW6?>If-k!F1Q|np>J&a@s=8s>t=Qk+<&XT4A7x`>1EV!Y7pZ~L6O=8w?#X?Zc;v!= z!1Dbc;OeJ7#`P~h$?9m2O7}2tO6W{b#8nR|B1+08BgM5ZA(!rqk|1D}% zrgxUUvZX7rn$Rcum`H8M%J(Ft2tLqQ&2&=HR8_$)Dj}+j(TbEUH}?XEM-VV5Q+f}j zkGbk6sZv_ofv_<~sVkHQr3+jBqyw|ubM%^9pFF$I=>{Ck!}SEGyTRZ;>q@Y+@R z*qy19L1MRWkj+588Q;C$cMK#Slq#%!3J+cl0Zvwx>k@NDVn#_W51$AVrHM*mvOp?w zCT$FrF_0`t61y3$O5~mnx`P$J_uf%v4#{OApCprjq%>_RhTgV=!!B?b z68kGplycfsSC+k_Lwv~Ct|ezrM$zx5@;>W0fBp%v^5G|;z@m6pO zbUkwSYjR9TTA&AX<_RL{I_cJ^K&=uRdY@_CvQc{~iI^y?!ai;6uwjNuk)>ad{XX&4 zOiD|VK27ag!qHP`J;nA1NdGa^eG^C*!E{95qm>0!Bt=@+KzZWaVc^#9=(^je->o}V zW0=tEr7v(Si5ZWXN9oImQ8dPsX?cv~R7{}JmxjJ}WvZT1#JR!(E|V-UM&Xd*ZdUcGjmFFf@GpZe8b z6!vt=k)QilAL84-{ag6Jw|xumd;h!mzyF<|!WoAVUl_rX*?Zx6s>6K{FlZ!rR4y`| z)y-R|RX}4|oS9R7hWo1YqF+r1b!%BBdb;re7 zM)yM2GH7=f+_)8a?FL-lQ+QL8bEeV?U0G5bBIvCp$5^-+YM`DB&Gu^5V@%OiTW!|n zQaElUj4|tDbA4i>c_&J{5{;fx!NOTvlOup82hc`iUGYqaXowo;48{mbHJ}uEFwGj{ za6_Y)(kKfWjkhBir)tqi)qqQ6-zh(h(cx{Qkg;*n% z9RhtKAuOq_=7T@@1LQ3D5Xxy%O3Y_dd?={XLK`&bX2RA3TQuj+bLqV|xctdqWA~{~ zquX5&k8KR-3Kc7oPbgnZuBePiEb@S)QG>BGN=C$zLLkS4Hx56YAx5DVEm{=47Q8h? z0p&Dp+oMB3TSG>X`hZMpOkIOYgp}AnT(MYrLe@l6p6^8>F_IGpgyguc$~FkC3*S6N zg{ByMwFTrvVAp_)RYS6_)@37=^3YwKc$@_{u;q)6+Zsd4l*CteXI_G}9ycQ+Z)#}7 zg!N`c7?LnY$+DF9iILK~N)}jGx1g%S2b56n{#S%cLiea%*WnXEu zMe7Qu6i=wYgVM9pwlud6dBA91NQwP6qh}2!RoGY)Zh6w~5zDk-5rrUz7>{2sthK1B z!i#`1gy`8jTG7XZt!nC~V$xKcKDA9XLV?g&r3z_FB04FyyGVU&p9`+za($Mo*3$Nd zGv_Yw^fOO$xD>i}!n9Vz90!VuBGj7YVb3BO?(r4z;)JkOV|6T?{w~w|07gg%PBOU- zoH}K26WHxKtcHmR%*_!ym1fgM=BC4$4x>Ovg(QQgpjbolpftYi$VSkv%nG{YA-0;J zQX=XWwYtK=^B>~$sU3QEi~982@bft)w3vPcwgCrX+&TbENg5W$-Rb(BsJo5&-TJE3 zNh9eosiiExu}N1-5o4sPs`b~Dx68DBY}iK{jEFF4njr{BXqsl-#1%qJ2|#krt-s%h zgB+*yqcL$how8c3*7BLtfvEqwY zu5#(yzKwgn?VD*&Th3lO!}s0yUHriJeLwqq2mH#%ewp(Z&XRND*=L{QQ=j^Ea*kNz z==+{}2w(EO=fD4}e~tIN^PNnmHNWSB-%AeV)F26v6aL@;y;z}#X;ErB%yLzbafb_1 z*U|Sq>FjA1TQw;O2e%F|2Zw}bpC#?>7k#a%$lBnPrP7&Izv8H0uzTw=;rg@0i#KVt zr?zttPmGD@UTS$|cS35;V8k+U8Q-;#p%zQ&Rr75oVmOu(R8=9EjE~Rw_$gHwm05jd zt@@pss%u@EM1`hy4ZvWO?2BA#(6|dgcCq=^Ml0zixjtuk7bBu;%o3L{yH8btsiu69Vy z#8?`8>ll)diRvaZM+RYEF*#$k6Pd6=Cl5JbmMiwIA9DR;pW@|%_le6T`x@lkUx%(H9NfH#N*EQBw#5ge230POw zpi+?-Gj+99nB+N{s|Zf9HQ9C-}^#zQAWb z{RRHB|MmZh#~ynap`@)A-v1EplOmOMsQd2c@gMmkEMEF1p8BN^v%LHqjuvbzNA?uv zc^U8*J$Me8lprWo8ucjjlPO_3C+CRnJ574ptpqRP@OZ6Qy}kCuf1baSaIp$-1PfYQYTj7DX}T#+29hF#uyrxKTM z#b`n4!9O9Hlp-+(l8*!*=%XfxC$ufhw;)GWeWspn()S%lUEow!G!x4-L6|@*i8M7F z^>BFTIdgNtbaRR{j@k)o5}EDHneS)zcRNDgld~!;bvb0x)Y!_A1cEJ39P12KWs58> z7ypJ*g3=MmEmc);xP_c%(Rz?IWymRzhKBprexkjyU}v+)mbmpS>cYFxI}agJGsF`P zsl5qNdL5AU+jEwF=dr*4w))y{)a6*qe7D}X^?H5rs0%%bW*qgNS}SI=8Ev~F=Sv_6ThIFG0Wk)n6&KH+=F+8${Llw~0ORz4 zS});8IS*#J7hd4{CqKK!nHV$KYTCY|KRPTE!07S&2l)L1OxWa^Yr?!*vAMZmHmP{|T4Et{^mJN6Z6uL$ z;Y%xPhJ@InQ63x6(E~6}&BsK}*H_MY(5N5l)5kS(CmVI89aUYEV=iGgwuJN)bD>A+ z6~d49#^csis!1(V*HuU0HH2TyjM!A5WSTO2cGrwNBNQEbCXyA`42y zn5im@wZd|>#Ar)ZTb!u~F(9f$g=6|NcM3`#41YSe|_1 zS$^>sKgxp--h*`(-xmY7wH8~|C>01Xuxb;t$&BWahj`n4e~A4reTmB-`zU_*GNE6h zD}&MwBZYqAwGg!;MIpyb@R1%(UmJoEh=Ps@l?#J=C5dI9aLJ>UEe6@pGjoQNOlds5 zAOyUR92_hO$+7AqSrj@KVvZV8Lr*TBQLhd|x+n(36c)M?tW~58A+i2GS`}luMio7_ ziR-Rc#uO%{Nm-B>5S4~zN1?4oqll8$8`iIORi;o=igiZ@LvoS!h$@?@Fx*24fw_az z8;+gEacRRat)O;-CKU=1fYziKxv*WaYBPQ%Sf%N+5K^Q^FwP)ZkrPM`R8HYTr0+V+ zWQy6)T$x7_1f}jFO zjD*3%p_M^tvremJxFejbuLJ=2TCcD4^S%*&gRlKY{cioXsFSjnvFREot4eXKIUmfT zecz#!D%$fHZ=1lMl))$kbyFk=Rb85r@#DrAx^~56GFeMp#wKrk-$;TPjihJKp5@l9 zTQp5w8mOv$bWa}`((+~iztnpsdHh(wIeuuO=xI3LL$`vkxK`6oE{ zP4DNyKlCGn>H+kmBIFL^D*oty{Kt65JKx1m{I#Fpr59i1=Jjj*0(KWu^8oCMtP8>BK0jftT;-6aNi|bN$f56V1JMH z>F3bPB|Vj;)o@>_^Xaa}9($&-A z2Z%*tl%r5NM+})1!(h0R^{K}w=Qw#ptp*d?ICVb`JSqv2C-vwjy$W}vv}M`0Sf#O* zMRG39W>Q#JBV@Wx(Av>7H6jro6Ot8^W`ec8NM%xKzJ*K(Eg?iS(~@LrBHii;YbIDT zBw$8GjKzFwtU)9JrQxY7hYvk{-{LBVs|2me#vw&T$_8aJ_uYGr{)@LTwjl-2;gy%+ zz6Y_h87e1y@GRR7WlHknTi^0HZ+Y9}eC*>N;hVqt{XFu>BWsU{4;?6oX+?~YfAG`) zgiDw1;k&=*+d*Yczw=$p&Yk7ipZ__Q&pb($g`_nYDd9lLAmYeL2tLzEML(I)*$N*M zCI(c@kaCfDaV)#&LO>aqA=qT_eIV5er!p>B`YzM?OeYy1^ANr@NI5XW+arRmlqimA z7iz%%<&4tDXga0MdQB=+VZtk2%<59~{)ItZC_5!>)fcG|iX^$1M~BI}5{IkqkVZWg zYDkt?Bv@K#VFioZfcqOyR_F++(@w(OI*< zT+y~Yy|Ntn%>GeF)E%kMXshUwaMUVp9Vm24C?dP7%#9`N?gbvquw@k9TlQ~88dg+| zVK#MCMsSmgjh!tfr_b=r(=V|+T2dK{7+o6lq!16ytifsp7!n3slDCG}vm&)x55aUA zHl$_pUP7T13WuXc!U9n}vb=?~6Z!^hT3}fsYKj^{yT*;>S7NB&t*>s~ZPf49SEasE z;L+G@7g<9&rW}*j`o3p#b8{_=7~|5FQe|pBHgx7#HZwNeh3Hc7jH3?yK3S!!#_b<7u3d`lZTz?Yr~jJ2|M&hrANtUT`PcvY-|*oNe}tV= zn}EUxPhHhTw>vPH^VJ*ZzC$FChiPG-1rvp|Iv}UOa#Eq*{TSZ2sG|eYGta;)mys0c z$wbP;GH1on<(Fwr&sZ*RF?E)yRkX1$O^jA}vGisWNdtyV!03ds8Snj?Vlh5G4A*^> zwTu&ulcyWU)AhW5oXez9gEGfbgAK{lELW`CC8v|D@}Be&7ec zk7dl{&GS6|UB8bnEPK+GS4)bi_Mj6w_NbJ}DdHv5P96Qc#t)CZjS&+vA!WQCnCen! zH%(1>{)Ipv6QVLkYoZjr`{63kM#~~((r`Uwpv-9f?NXDiNaDz+vtuXRDY z=7`A9`04U|3_g{$-$!yX^nC&i8`~4K%OvsKvuWwP=Ee$^7>o!i<#Hd*0VI;OVbS)~ zF|)a~LC7m|n6p(IuAaWYrK6tJV#UlyP!$@5OG2=T#whB}<3-R_O-#8AM}|6yIyI{3 z&XuIHQBaVFfd@))xC*4$v1~gQeMc7qk^~(B&!8}-p*JM*7Rd$oPT)hqj)X$&!E zR7`|W224t&B84fMEx-+bkN4U;$Nm4EUT=U#{f%%iaku_$b&_R1Hf(+07Y*)UtQwi- zqvYdbk`C_NEY2}YHhnhgr^J1v*^6Q#+Kzj`x!p> z%fHIG4}5@2-}_zcJpN|-E^_h0IsVk2`rq)-Ll5!y{_a1d>w5m|kN3G7P zZaYF9O~pznQZ7fh$CyF0{`$V}@5nkf!>LA$u>|=Tv!-Q8N;m_`3L$p*E@D(eZ5o6` z&H<$hIuL;v5=v{VX~?t#ov0Y8LyW}G56>6q`+y!Ak(44@RT!(uxokKQP^Oqdb4tWi zUhK6s7%Y?8p&QsdcbcQ;FPCP2_d2&e@*(2e-pSTG--ny+@Pps;y?ptzU*x4LSNY{n ze2ix1*x%dd!i7skD$sTO^}qIilx=qjYCNZCV%lmn7`QqF{241N(qXeO+TMSR9&P$8pzCT77~g{UG&5fNhW zBqBN`LZ%=vHCR!2$*huM>7k1Pt{@imn24)xKi&JS$~kwOrYo7zS~qUa^PqWGN)@DD zPKA#?8V^(n_7MzVi^)_54}waNGx`qEi?&SSkqeefrwwN}GutymWf%~jhL{s6WJnq% zW;l(=q?9>W^`wwUL5d+bX0}hy*xH#9Q;YNgrz014Dh|7rpbRr>5uJ%Bii$7cW1_Ab zkVI%#APHqUIIhjdYv5 zp=?-lkst|(Qs`Wa_&N7vfrG9?g-qyreDLVX4H(nRa(aqGV_2m?3<+V0mkJd-(lFrZ zvaq*`grLyIaBC&pY86o{Uh|39tm9nkxo2m}XI@*Ptsv7Du@2)}4sO0m=x=fH%qITg zMP7YnuMA>ZMZDS(TiDu}(_FG}-jF7v>|`*CiWH{2$|q!TLV2->#N^SYCTF;O^@!cw z1K#$ohiNWs(}i1@UL(ekl|9jln}P9f2HMM=S*Lfa)El5tzgxfEYLq!BrPd8R$hyfM z=`HK@QI$w3V~s)SLHbfTw3dOsPz+fkDP#QFoHOP`n3C3d*cc4F-{@&3HC?-+ZfbUR zHaS`x;VM&(co9Surfyx=A@_haP;8b7!~cRtp+C zBd{c{R;0s1AnH>KtVp?n)R811F_U$TxC#}DyrqsA>pfW&bYl#PR8?ThPFO34UwOKi zIJ82eHMJik6kl zEt z#Kq`2D=U7rzLz5$Do4r_&kcNdPfmJB$klW{5^_&2QDkI<5H&h@cCWsMRvKG3)RP%0 zl=XQejx2)Cf_O*-2p);C=)6+1#A7bHO5$NPpO3HqU^mQA{8kGI|&a1WJQ$%Ig>m zlDoV0)vmjZ`rZ1f)Jf^e`1YttFRXLcCecxcUP6vCanwIcCdZKsbDSbLPTa={{YY+U znxgAF+5C;u-tu)7-uIwNlfPIl25h7u#YpAc&;S)lP1kiyXEV@>%?sx_eB}lkwM9b2 z=^B$YhDcK2vKbi6ltz~!_BtiH<$`P#$|@RVaj&je{^P4W`3t|u^gZw8P5-k$h_FJI-16atA+HKdwln+X02TLi9NyTaz%-^2FfkF)iz$9da3 z-ooC|g4N+Z58QJPB;m_XJVW1Sl&&zQNP!LxBLCz6>!NEzap=J2o@>qx9%fB@!U5vh21SJ_`?SL%I zq#UV?8RRv3(74A!f3p zRc2$R5i~~6*4Ix-7>xghloVMBNhQvlo)E=z)GKrlB*H>xk}6kgLu)2l)00@V3pT7{ z*;d@zKFyWWXSi>7ALRqv#xijh>yJ=2Axhyzp;F-d!6A=L_jqPjb7bm5aESqYqRT?a z2Cp(n6q#c1jky?4MItt3R{MMG-@MN0Q)kE_;`0)f)8Hp5xfDTiIUUIf9X+{i*=Po= zuvTQ1iy;+**231#jPv(CNOkc7%ZvAL>&6TGN86eI;fpWxnHMtOR7duwOD1!Jb;6`l z%%(M`PES}K1lmlT3aTNi1}`hLc-mzL!v_v5Cg=obrga%|rBb?B{oNXes=9YU-q258ZPX5FqI|J%%^ z!{FeqBRmL&b7xceyl`nki zSJ-~fH*oLo`yt%dy`4^5>S1aVB;YEANs<2LSIMDAkqaAFXN;)wqN+0VMT0I6oWU+z zZhh*LOuIzoGfm@Ys&ezar37C(cal-qrY47g@BMIp9;qvd-bY+j<6}hYLU!@KNRg(~ zDc*;{>}l7Vt!3A(PfDRnqIdN0C2?=Wa((HMg7uoK7SYe#T!$XsjCj*LR*B`yf<)f?pIfcEBP zcE9)ucHZ-Tn)@GNvufBncL7l)sd7>|j4pV=oZ!#?_@Czf`!Dcw|KguvENpB}Fh-F> z#$s7qz6`4+NoVkZ7!@J*^dj^zv9ylXS$r093b?+v!gUit2j)FkbA1! zBfVm`o^fC_i`-L5;Qsj(IRa9qa|)X^c*v^b(ZhYN+Aa2?CFw#&QX(ug7-bz3YFGOstFsX z&T(}h`P#w{-0s?i%TJ?z<-S4<}ptTSwG z)=VdsP+77r8_TSOK4$78gFR?Deb1bq|MZeye)=`u{pN;8&oyjsP3Uy-v{c$*bd3Q* zobmjtJ)iyjWzL@7;^7C*uv+c1T3koXPvh==prppqks_ci8VkB4A`1qEEdgChuQLgT zoa7VFqc=jyzvKJ9w(5=1sDJHW$Gi1!sWlUtW9iFCXBnHo5#2YPPS?|K=c-|+Uv326 z`?WrOluUSk9BQ;!EU?znwk;r3!~Zj;TWYPD&*y`QwM^K&_iS!%a&&Y=N{OAF9on|V zIk(ojhY;wxj`@6!(u#zzur>4V|5md1_+R-XP4HypAeU(b8kE)R_-$7eDuJsjh>7eY zA!oW!P;`!jNt%B78tq4ao-h8|Cy<99HayQy||@Ji@Hpt*j?i@*G<80D!; zUOAEyHY>bVM3c#tEgJo%=EZ;gQMOgaTEmPAO9?#*DdKhFuy1+w>XK{&_dfP0+w(c8 z@3792Q)aa~ZVp93UDbFWKp)Fcx~>}--w{PQ;V{lo*L8V&(3p>(UmnZ6W;CBRv*7|d6+@M8`_|%m~IF}u$)isheA&eW>(&)=5-yqUdnXEF03(fX2 zusI;ug7!EuL>mBc>IeP}LG+J5y!$11R2|@Ge&wQSc zTb_9Gv%LN7zn|~@{_mvsM`(l)G7=R=6tY-hJY;JTFT|7x(h^X3BAu~B11bhw=$Umb z(f4>=5Ts5iPTLxrGP`|Gml6tvRR-+}`cp&*sWk0cG`7-Mrwg)C6%M*mW{C7TPUI|c z^kCQChkl)kJD#E)n@JVg*`ayONQ$Uo1F2Q<3K&e8zAHsqOS3YgHeM@@6vPpkjFvGQ z^$wI|jJ6o(5G`XzPF8Wq12E)4@|NxJSPs{(u_e7VMWOSFwuRMF2wu~N^7x0K@Ij$+ zh0_^h4K72L$TSEk=VA~>sB`9Q<=9_D{9=W!D*9V{?4OykTWikt!lnI|_ij2qJP{6h z!z4m$i<8#`&@0d3tY;x^Naw9kC^|a)!7UCC_Nixcs>zI$AO(-~9tlOpQc0$sPK#_w z=|R#GkrZ&KvT;GsB3MzJsvBZT93+^Z+TzrOGi+>~M!OkVTe2y1pb!kNyw)+9tT>y6 zYkGq%?O92~r>+F*Q-;mGmd&Q3ZZ_GdCTz?!oAX1`w&UQ+(ePMlw6#NiK;)5Amh^QE zzwpUJ{_fBAoZH^z9gj|U>m!?-*;1I=5wm911-|&@mwEA;VLIF4k%!N7YSXgZyGG0t zwjcdA^yb4@Am`QKkttmHl1iEiJw{oeLF6QDE4^|0^Yyy_cYNR1Sbfbm>c?=byY)t{ zHHG5DvF|t?9SvS1^<$K(lqtF*rsK5I(U7!)=Vc;L=w(enz~}OT2a@f`I*n> zw|)Kix3vx*N|Ww=*$ixLZIuQ~7mlz`MFP{VR>k}nB9a9+pK@4hys4-kxR0i4Sp4Eg z*bs;wCaS{O3Lbip``-QzUi-walWtyPVlrrp%pA#hjD{|Bn0}DAh)0XCTI^G|OQv5s zdn--SCpXZOorVD;ov?6jF9muV7IB6t;15n@)vE)q5>mQOu} zyZIVb2-x758cXd;FMO88i?6ctws*1f_IHxzQ?|DqfBH}Vr~KkCeVl*vkAIe*``KUM zrDv}&om&3PpZU+p5|C`jSRU=W-2{kMbj{D)`DnF9ugas#PUd(s_5$_KrR~#gF;(H@{uGU zSd6+eFTt~FQhwK*%c+4G79~=q&PriSg(xAkJ!A-Nrd=fVdWG*ZT`PnzH1HZ+Wl54* zwUOX0SuCATOebYC(DxoyX`Khi%7rZ zw3)DH4KGb9P6f}Tk7Q%;*3h?sWxpios>nWEqHi-kF0nZxp(E{eEcb5FE6qd{n_4&} z8I0l;0X?14<%ltfIu=u~vgL+uFohl?k<(i5PBWd%sE2T(2w*H2jj0?C zO)?3eHG60#_dUSk+8!w-uD5V`UvXi_vAOE0j}B-i3g!)Z_|x_`05vo8-)7~U{V+8gA@{0;Rr z->BcM-ySteT1NRwjPW*f;@EhNFSsKsyG+&HdV)T_f1J?gB+O^?LI13nPD{f+A{NIf zevFZ}ZK>;e-N>|E$80ubu~^&|+g?=_ecunXhw}4VSK`Ds=5xIF#8srK40}&Kjosd6 z^XL`v({aBn1f}7V$FOa3a||p*NAm zCq7?NR7EpEXev!*6`mCr-ggOkP}qu!`yQ)!^vaaaJ$J;@Pd?47uUzMwzxjR0aO|6M z;v`~mJf%RZ^#l?W#@M1^2jXDrh1)k-W7B@p6uQ<5k84Jk=4V_Zqx@-n-<>Gupso#V z+Ya)FNS4g1Ef}>R(6{Yc>SR$6Ulp{MdU+5M$|#r_w5jZ^RZ24;`H3%zrSZ~cb%@uoMunV)WQ# zW5YdS4@>lp6!V)ZPXw*?kg_?BeSYI6uZTLzDTE$ACzbT6!hX*{0_UpVDB%;q=(2gt zgOtS>g)YJN*%{kSSSu| zWNy3?INFVnKtv-lM9otYa_gy(8^O*iTxD>LWhx*j`Ys{LB377+W|kDaE&))6hjou=j>H@v_B59B2k0pkrfswqBdM2F&Mh>4J52@dPZ zb0p9)i-AX#HChluGh_ZUDtG7N6wk~d`?|eq)>jg*=)9!&!}mQeee|W z*_T;-;Y&1ceFSsgY5H5&a2~`~Wbu$Y4xacNPlUkx|HL0%qjxu5<=Y#$zAstB_N zV;n|XtWo5CMUDZH9zsG=UG$^6C;N^ZR_yP+$h5L_u_REcsEQt*!kW$^M1)QPu@$D0 z@i9_M#pa}DVjR6{*?D}M^Y1xL+>A_o$L!#UGaH&WT{z9N*A>6?r5jwodX=p+=RgPG zI8pYbTtzEY%vmX6%rU2ae7r=duece5qBkBlPNN)Zq`D|wj@n1*(n*@nosz%QbxE`B z+Ma3C6lqo{N5Dpx0HhJwr+^RxP);3y(y&`MT*pidgiyF+ws4{)*`W3V39I_&X zjI%|P-nSjjS)4PhhLav`G)m=?#+VdMGb3eBO1^}{B%zOy&I>79Bnu)ILr)UW)?zBh z#2In|-w#apu0;?+yvCJZ{uj6}{yJyg`Sr{mdk3dBCj3`_?tjbkFFw!XkG~C)3=Le7 zn=D?vfgB!@$`KC|6FCJuo`|8RB>Gl9lDod7u8KvFH3Bhd=yIkvrG>V-1h|Z)6ll#5 zm#)_N0$LThOCeSWO6PwoDM*Pb!8k)0QX1Ew9{ep?5kjD=b1)MpZN>(byxt_nS6&>bbzv&nJ3uOq}M@yx}ePZgJ)wOLHEA zSz_&JPQU%#xO*NW#|g^E5=<8qY-}RZFmltCQdG_s*^gvILP@?xo!IpH!Dn%o2J`Q; zzV;jSH|kl%-TDnRhK+#S7JwAvaYE?W;EgYU))p@J*dUHZ&{5wWR751x_oaCmB(}OWP2xSc~scuO+6pd zDGLEaawhtS)&rJP%4vcces8oQ^kJa77`!i(jLBqH=pqyoYTviTh-n?Q+bH-;$uw0> z8cvk#@cihyvgVc68 z#JFi>u#t`fb_FWy0o|jJA<#(4D6>*SGgi#O3=K*>LkU{zGrmi#mY%+gbZwyXnZ8%3P;>p|mVQrYlx3n7R`UAV#H2}5 zHYXaPu?mMFE;84jzRu|fwy1V0Iy|V7)@qGV8JHM|R=`s>iXv1frobvDqGU|$+0`?i z+o(9#Yc6zwixW-GfoL~)VQMic648jV=qL!0pdby2$2n!Ru}mjZoXQB9WzIzBSXC7( zT_LWbHi;Y-9O?=-Pchp(N4>dCnp8BkV`5FA4{3u=hRRv6iW*z|21=+aOQm6>shKwo z(@Ddu(oBrOX^XK5Q*X1aYmWAB@oOLd1y)hmTY6Sr)Ai8%z%11ym1rtW_JXYiJ%_5A zqJgA4v!JNGhMMI7~ z^ex&tq}f0xg_wkBK``aCL@K~yl|k@`coeP#en}7O=KXG?_V2U4_8axP_1mIEGRC;I z@iWFi2p((gFoo6YiS|hyxz@#$V^5eu3xC^|CVpt#tvxomq?Fm*+$IDM93ue7=BBF3 z@pau4GR$hVqN*y6j*gg4r^T3>h1s+;_bF%eprLM>GQrog33KDn?{xU5UPNBo$EHL! z3QI10YpqEnv@)ouIe76!KK*z8HkZEZck!M-^(WbX{zYE-C;tTh)DxKK%PC@InASCu zSdoIKYgYxTfPx50N>EH1P9&5nJlzC{D(0V@1CflL)JPwwf$-leQ6K*w02EkPybB?AL&G%EkMEQT51P6m#W6i~Tw7qQ!h5)kmdtZ!Et zlrnV79$QstRYHea<;bVcQ|;`qeD-B*cLi%BMm!QUBARR?N(;I&%xX)wtO#AAUj^<=*=8rzg_FLXYb?!bA7M(J3c#FmUHF6LtJH^XkS!b%OwC-QXb7Xb{C;MNq9#!(gT)lNmeR#=7>l`ATZ=Lp6(Y6L zNY)%&S#WxOrVw~k0y0`>c_u3f&& z#?B6pz3nZ$a``!Kyz(^H_BtNFI3)x{*C%4vg9BGrsICRoq0E2=9dN&j24yl?r-Dk= zj+ryeTwr?=ah0L2TCA?=`x<-d6f)Vw*_s?Xv~!R&##)Rsh$J$;$e_d^2C_0Gze5VD zQw^b4ioU#^JmiRygt|lYqTea?258hrZQ#EEWr< zv+6b$`dDq-GM~*zu{3r~({OZj#P;?!-GF!-lPs;ZOeT}HR((V<=3Mmbv)OD-CW$fP z29)4vf~@O$Z91LJW-M1Lj+RT#-+Pe&&|If`=2bSdB1*!DSu>Q!DZO*+NPQ0}Q;DLkCY-x?AMO4g=3>n~?|lHfqe(84V!|a|DA03FOHZ3TD9iTtDOR~6 z=Cqz-cU=dRiMRKj4C_;orfJq2A7e~O7e%iLJ>#Fzr=WG&fVFK~w4MXir!@5d>NsFV zskM(_{29L5RaFpUi}!)Lo(yz7i*uTo!oWbcYiUpjfu?CNri3GP{i>wM>hhu+e5S6a zwC#$ja<|cqP7;wO^#)35VoXCb4>5Xj9)AuQo?ixIs=@3Fu5twjn0t(_SSaD@s-xZv zaQ{72&#aipg3z~2lta&^*qs^OazQP+OpzTpqe$rx$pufCfh8Bw_{G7XLa=o;pz=0s-kUIs3b&5^pXg@CWn9`p;8_~j|Q?&dB{=LGMQ9ZtI#p9 z*Y_+#A`cpB#j!^Gw&#E@K8aD5@>@xyMkJ`xMJ}c&5|^wBo)02Q<)X<&t)(=`U^*Re zeM*=3_K^hRv@4_x(U3rE!B(>V+R^A+g51I#S;vb*=-Nn(1(u$oAqLH25$Js+_MUVk zR054wR0w8Z$PWh-XesiZG~`3n)}pN;hD7BYi&f9^sHfU?!!^uk%rL73BLYOSQY4*` zte9n?mXdg?yeI0O)>Rxda}K*BE@scY+3>Wg2?Gng0Bg#L4uBqrM^>xhk6=~VSoa~| zb0%wtuBV*3=K}LnXK+nJRX14YP}UCc{L-Xr6xL{rE@p4*3?NJ@Xl%u1V>!1q;lkFG z#v1CX!qp|qW3gOu<#5HK3#__;(wf6-w>W?CUf%h2@8Kg?ULXV^hC~XQ&_{gd(HNSl zrZpwUVtw5OR7zL@Oi2>es-zs>*p0k=ZNcuLV0Fh^-?YQDiA-h{6E&ktC8an=P+8&X zx_A_FMsykX=&VQg3eZzVKt)n1l1fln3aISM&!jbITMV>sgsJ))w3mDXH0pQjw?mCs zzQWor%~)O6w|#s>ERKI49UU>B&k38vQl!vK&A(5Otdv-0&m`*386q(P9 z*|hJ=WO#FPvrt5mUr%{Qxy5q1Tz{|mbjoT~41J@~(K*M_(Gf-~&faqmSFhZlnM^r) z^gML~{@GX93>uY25x`(XO`)VC`Z7scJo5!U^OMg~ZJlE0t?%N}x4#$jz?{9$ey&h5 z_VzJtkIf0Kgj|=V-z9Zh!+V>IL{SJ7Dx*}UQjQ1&iClW%9;#Wz^=r4d_nXe+9-NZu zB_(aNOoF{iWTj|>;quiLwtA42&xk2a_;R_t?OGL0x>-vIMx*IT|ACBYheI8s7tP=u z@N1(mFoanR8?2nkgMoGY+P?4C8@@YHN?CGFR8>V)S-P$(k}h3HBOl9Hf2GH}OXzyq-K$JyHL>@cI(v%c zq9+7_m`HwwHx?@puUtoN-emd3&!D$YBQaqQI+W9(dX!Y8Z1AZihFr{*k|5?Hi=l)X zX}~yzoha>-L_U^B*Ya>T~V2_+0Vn8 zF6gcdb!DoXQiv_TWr!CbDgKt^&Z=2CGeRh57wGQIa0ZK&!BYo~W)9WRd(#!Lxa z;OHolbHp0MsZ$&HCA@ld57WWSS{e;nB@($j&tcs(Y82X-0sl+GhV1CTv{D)f4AUG^$uTd1EKC8sO_(}EZQ#^K!+YQQDED7FkNB1p0$miIdFd)IJ^vium*;HAnTU`DoOOhi zN2@*F^p?kY@yl-_U3rQSa(Q0#knSRfspHtpjen1gUsY9j z@0m`IU&N-<>236oHQG_6pxjcVX=+xhWnoAQtX4~$b97yY_kO)u==*+XqBPp*^@Mjs zI#yLh=z5yTWIgSlPN(Zl#?Glzv~A03?|_RJFL34hHD=Qp)ngAM8=LrNE@N*V5>&>h zqQ6WeauTf4tccXvVA_t{y~h6as~r8(FHmC%5=lE7)B+)t8=z4NOlbzKE-!3j0Lg6) zt12&ADhbL8DfDQcadyJlH$6&P?z3_4Den2UcM<1{V%W-_J`}@aPMMqyH*WS^yOmMf z6M7MR*HKR<)B_t_=^|4}Qgpgn8KzCesw-3R$?&-40b}R;ktQ#bh#JGHKSQ zCVhXbO-F#uEu!f|hqE=H@u4J^)^$ZyO-Qj! zXoW=A#ep1BwAD(3$pWf`b6HoAfNi^=nM_bn^7SnY}rAQu@;%8_Na zU^1DP>zgx8RT5A$3hOFV)CA>mw+^WGj>y5&r-)V+nuN)L_GS;saPiG==V*F{)zxQ_ z2uTUmL}8i)y+W%Qp-;IMyb3(EwDn%?^Ytix}C|zc) zDG60=a8rwI3WXvjMQtpuN>~HkUMlNf4cONc&w-Klt&4+W*o+tcy+)%Y4-=tGg0Ul}k+r8KOQ9LrY_ZOj?-wI+*i;)m=<O_|oe0rKrp+Mp zBjcn(inlJ zs;RZ$w8Gd5t6*nlc=X=WyyZ>z@~*c&hC{e=^%hUPyvK7dTw&QoIv?qMPY@wt3ieb4 zXDvvkt}BuxmOk^yV~_CUYfs^$D^#S`(^sB)YH&($y4*t*#So1WB90gmPIa`&@+)8J zxx8C*`rLW$+uq>R>9fSeH6A!K=gg#LtJ%iZ8e2Pby@R*`&6#5H*BC=WlVe6yM4OUk zreZ+)zC3RR)3HFApldUrUvn`glX)XF>R%C~{sw3;-w=)Z-FoBKD0A`N(=^Tc1!~lq zk5Z0N+E9{36Mf%N)n*7Ck+n3XX$nJ{Vd}cIwYB~pz-`m{u_<)U4QZ!-Ad;vxN^7xL ztfets*D;^YSS^?9?>EXboO5(-i_StdsaUp4CesF^49m7-=fV4sQ`_u5`3$G8FY)^a zB&%?hMSLPDDR?~+S&EU$qH(DlF>0;pclSwvLk$KyK_?KMNW+Vz8C-H><6B;~3Ky9y zlnfKW_nvzncoeaQSD$~D^WSt2?tyb;JqOj2(gBtQDYhtMI67=OJc2&YY3ez~2=l2S zM5yWtZHkwGL53nC^j%LfX)RHSDHYm@);L$u_hCIHA0O+{AY9iKUDq<7&+)#Hd77pk zOshgQ=!A<_cxdRfp}9k^2}z2X(EA122ss%f*kRhL@u4Gxp636@-k+9qPMMgKm_$pHqtrJ<{Y|f;m@Mg( zYjUEHXmn|^8PS)Qk|?QWGSut0z2_UjNT~v(y106?E0mI)LzNU-JB;zwV`}7?=sImK zS(OCK_jQLf5>g_f(cK2WyUTZr?4+n3key#9h0KlmNulsS*3PR<>r7;?zqX7-mo zhr>+Df>5=(LuW?_itjx?@IU<44KGduf9>c03Xk^#uWk-}@x4#^=qmHk)yMSCVEjV2 zzeEikrPE|1(j6!=Y^nYaV-&e0(lVmLj7ZW`luAIq}fyvJ{xJQwdo_ZSwokFS_h#y!4d z_uu?gUjEkaF?^!9ycw}sCWq24Nba}-$ilT=lp?Bv%^8~;lR{xCX-2DxjZ<$};(ydM zPM1O^p^HXMfc9KGe#G4;-(%>H{LJ6{COD0AAERx-=GVm2HQr2&r{~-rUSWq0J6KR1 zVVMa;%)KPoW+Huq*X%_j9VNllcO6~V6H+9ljPnjkqqxL%LDd@jM)tXnyt|)7dY`Kg zZ?o6G?AmNKx3{5>%Q}1@A5MJYump*OMYFVP>=KS>{u{UI1&EmWh%fmP9TI zGk6wN_+UJpHU84{RO{xXxltq~rwFayLo} z=Cu4U$&;!fEJ@x!+OO0W(XZ(G-m1ZCT2+r{MKRV(ODXl1*Fd}y4r4{u(ym)wMfE93 z-b1N4$Wjv8NXU`O3RUU^+B%|wC1+wOZxYzQ(OwSVoLS?bm zaIqbj&Sz3gtK>yvp+vf&!+Oi@;lvUo(K9P0LkIvg)(Xb3l#EeEAbv(uyrx8mA+u=3 zVqq?dT!}myL3fHiI&uqq<~zO<&U`yBe3Qa+-4j$|36Y`{w%@RQe2E?Q^cPp?Zov8; z?R4E$(b!6wqB;@q4nDZ>eETbp`PRoDqH^Sa{Egq_v)2c%?`FV= zlm|^DlC@k1MvJ7aP{3$%3|N&p+`i=VFFxbb@BI$9FJGcdWN?F5XrX+^hA#|i< zndZc1=-70cIXX^D;foh%K7Db`^P2^i6X(+wjN@(zeDa^Z;PI~K=YRGo-~MKY*I9;0 z4xI1MR&FbB0^Qy%l$vYk@=7TIS^}lgE4Ml&#sH#)aK>v+6^yuC6$pPF!AI zR{gLT9770iQAI?nzFWb99;e^`g1yo;j#?x!LY#RrA*-SBREb7tpGpi^f&?&V5s6 zE6HXlq#8U!RP2Wx(~DP(x6j!9r49Ka63)*^+lbd2bbf++@(aw@|A|*Gf0v>?=Q)Ah z5|UxN-LotQsEeV#?^okzJ3h`@PRG+~AT5aDR9l%Q8ZDat){M9F+M4h4`Mm!9G)-7* zYZz8u3YXB9Z@8l?#N4KFde}v z#$~G8?}dfJ*(H+OF&rb0M#YuutIod26j?(~h3nt>J zrHc_)o@D?PYbz(+2tayHR;In6uKlnWI>^5*%4BB*TtfTW6Y_GsOL!TzT^X*H%{^2E$_ZM94_b9hu zJ4f&LkQQP*;__p(QngW6;uTqDR0@!S5Y}`NiCT!12&Nk_N+e7=7K%}14ca@j?a-Y_ z0eSUKF6Ez!`mt}+%f0_T)cad8#n{|ASKyCR$9|_o|qQVEvGjV@lwjFac_F@ zCr=);)HwT#i;Gnjb2uDGDXnsrVHhZ-tN}6BT24oq*r!}rmWXp!P;g3-t%9QQt`|cf zVpt7}kOua<9UnaXfM?G{pT5k?x&W{n(ZVig$H_a)U;c}1KmHo?Z~hMI^&N(uP!gU( z&XHo2AjFcA#{Qm1)$B_<;HIA62F(1bntxc73K+Z|b zan7z&@MWHbKc6K{GA3DEwR6zq3XO?B41*+`!nz0(Pl0S1qZKLURq8Vg1E22y3l=bR|%gti{g#Hg^gLzGz&W5t{p(@e^tl8D|cIOV+|li*Sc$K%X)yCtW> z92dNIq%dPB5@k<8SIl27uq=gPGZ5m$>2_kX*#nwV0)5?ml^ra|+GElnXgUVv=1dqb*u>bjFYkgis_M z(s+omvfd+PunJ}snKcr;R21E&XaC`j&D9pyDXh<=U>IL49B%@dE$5gBb=s%2m=M>1 zsZ|E!DsR0elPWo0V9BW=UTrmqYP7bi;nz7s&Jk^7vYU&>Xh+Nu3|Qwe$q?g2N+ZTP zyz`W($T6a==-ESv%uB>LkLw3%Z0E>4E||WT0G@e9m3K3Vy#Dk#s`Q)>hEKnDOE|(s z(YQI{tis4h8&wM4y4s-E$+!{2X3B{W1%a7>rJ4>hg{Yx)3BQ$vpYI1!R^+gVgbjtY znjS+ANgBXjaT8^mMWcv85mY8G6O&pvJIfKp>0J15|A_B=?OSvoJtFI#t>03#;`z;M zZ12I!wULwTq-dw;w8I%`(w)|LYjM`l>mpNdtuWf+y$k{75cvMf7laf@Im`83>Mmx{ zP&BDTtjdrI5e?d-OJuzHg7oSSI6VJ^!`*>xOgI~vH-;q_w1T~N7+tVRq12Y&dl7kc zyx}V!cpmLN^AtHAGhyk-c|&Jm=n~3gywhwqn$6(Y?|L3xZrE)*cDsRL?=c;0h6{Y} z!CDF$6%VBI7brdiWr*V$rXwbfs3bI}><5gs@_x|<)jM!r0*_*(qzq|B`;L-r-4$9e zX$GFWlWX};SLILmcRybBW8bLL0DMepR zjKE5ckwBv@<2VvS;PKSIkzh^q1t7aG^ z?8L~IZIyq-82SG9zmIcn4Nz*0sMUs43dge08Bf38ao1_g@E4fA|BT^#FX$&l$O-K& zHW#``-6}Q25ID>Wy%PgZs^-{?3~!90Y6a``vn^+n-6FN;3yWQ?Bg3D&JSrMAX7{+m|3%J^9-Ag77A*}lLcT(k!oCYDTn4Zq} zgm9+lKq(2O3cKBg@jQ_gqFatx%!ow{&+U3^I|Nwl$;Sok0KS#Tr#F)jMCUj_E4#yGqT&K+WFLgNS@l~ zlTwhiqF9A?kV+D=kXG{9r8U}=N-&XCR+5M;x~%DoCYVAlikO;2BT;hU59Yvc7I>`T z>&F`&T^tDg*Vy%2rYZ30XJ3$w5xq5v&Kr8~P`Y5O!WvU?jT);I&RVQ7bXKp6J$Peu zUzSCvJp%BT!2=nx_+*8XX4A3R4D5DWjFsfF z=q-LTfYqpEDCaY2`jnCaB}R|xej@7r7vueeN(slw^fe*0(P7$c8e7{KvxXrxt$J(j`mSRf$Mw$* z<=7-5hr?lQBJXRg8wwC);@_w*H#axLWnt+0Rg%;6)lH%iW90GU$6Q}u3qrCsN5e1> za;CGMy>=9%2`aH2b}Uh0e&LGvGhgHIo4?QIv)A+`6LpZLE`!dpAQ5tcqyVlPl?t*F zoqG~eGa50>iN#0A9HA(3(R70oqu&Oe{K6v^v(PImp*2Ye6yxnP&WBshw@1#4<9B{A zL;oF?oSElHnj;3!IG@+&bsxWYzsYV`!X{_&-ea9-oWptyz4veJYTVbXH#to+7{~NR z+S6oA_phOMQCt6{rr>H^2jh4n6DaM83pm}AVgwA%8BmFsMsfygHkBR!HsvU#bdQE) zC}fr;vfuBSXECR4`hlDR=kZ(#IV09-l!{E#Oy@g{afGnoJBzX6{ea9cc*b$W8cpXr zmL;H+#ahjj&ZzSp7u!o-9q%~!o}l%2H)ta;^3p01mOHd7%rD}>*s*SoB zHq+X`N)<9oo=7EDUNsbFuzgS04RoDjJM=6mFfB8;$C;1~#}HU@LH#MxJXK|`YyD<# zsy%P-{XG&@L7O7G8aZQ32dJ8+obcWXV>$<7%xGmP#gcO&s|AN4mn1`nnvkTFL2FyF zhLNHRDMYOGq!7qi;q1G)j&DB`I+WFL>5TXjmclY;;geVPcm}M}n4;?BSJ&XDKk8sh zU{x_1yuoyu1~*qozw3=5E77eRt{|y=#mLB^ITg#Q<`sg z6T{sn99};r4|_g)_MABvv>UKmvl+JZ#^Q{^8Zly;+Ne7%OmwR>_iPF4aG`w%f%qrL zB9%%ht%tas!X1)chb-e##~24Elt7#DOFQ*lubt>llg9**uqeJRzZ zuE5%G$@JBda$MQi%~0AJw`MNwx{i6C*S`nwPVjVHosu`ANLv)d7~g6nCCO8M_I{Ii zUoz8(FuXNs)9Y(!6ZNYnR~!z9^?8BtvjI9~iRYQ5*J9*YM#A1klaT^y<=Jy`mSdgN37Lsx4U(U-i}3^2)D+w z`3PFGopUCHNQeRN{9By$*3jR-Cfc!Y{sg7o9P@TgowcO=jvRQvIKM(Gi!ocqb0P#S zTly??uH!r&C>6mbbew|sJvkRbSkT7SiFv3jb}xqIVSrK?#}Tb9eZOa(3Px{n9ppGu zFbsW1PMH{r&`_386Hg1L)0@zwX&ULf0np6Tf-#P1oG96#G326|=YTaGc?Z|&jDNHx zEE}GU3y1TW$IHy45^C7V8#8k*5ygVH7pSO6Io4gVDAD3I`LP){OUdFlNFs4dITN#4 zy`9<``iqYJ$D=N3fwH2OAB9(866DaH1VBn&-wNx z2iI|Tx+9_4*p3_%IV5Zco6ccWK`Ckcof5>YMuL$jRB4N(n&!&RSIACg&KadGM(LW! z3MtLZ=M(XCgy%mXAD?6MiEZz&!RzNmev@haa!RF zI33w~OYf~T@1|oIdVJ^box?hZc22Oin+?u8eDAT|({&x%8%fg53uO$%WkIXDi)1Z& z7*O2~Qk9;j1u!J-$m6Y`NlPKb#IV0aVJKOlQbq}Ek`xw<$|$QOc**?uqZ!}ldJh`) zcNP}@P=AgULafc4WuB+HwY;Eo_m;Flwwpyp(Igbk)DOx6Lnj25d7c^ifl@NdGOv@# zoHN5PtW)>%`MgS9T9dfj?N-!cGp>I0(MKE(hqb}G%|~874}A7I^ZnNwj(QJn!G#F3VbgCpjMwyT2e5hzgoGjGz2|s5uHJ(X z!g}m?`%8>|b8gx(Y}3pZ40M0t)D}F;5>N%ab>t|?gE?h#O7wmIZmwf}!QeC@1(s=H zGwfv|UW%ky8pY5LXsrl2FbrGKvMw`9AJH0?kkHzIDnf&?h8Sm3lGH<`3iEuX?*>Ac zs-`!hwX5$VIbO>w=H-|ZsVsE;o)Bl4GDG)>l8=<^(5ffQBi?$EgJ=sXW4l6*8JjGF z+b~TNMk&gmxiy~Sn3)r7&L=iCI0@~^#R$K8K0=k2K&2XKh0d;$PlS9^lp&QsQ4n(w z34|%=Y(N=`H4M9+{l$ihVPHELLS8uC968>Ogwk_22bKiNCq&`=X3^J%09p$!P}j6k zr67cbtidP)MPZbwpDTsDJ{Q?tu*QQbxIr5CmW)f$qD{_6|g%iurcQ zXf2BsX9agxvOzDsFz=B>M{=PbcFfcAPOjsj)53<0TA1d_VC$l$L`_MFqO-iFBHHbo zqmYfFe0InxOH-sPlwY>sw+6d2XrCFJ!RuyD&ZLmAN@J}eXHYTM_qb-tidnMiab9x{ zi9{h4@$mGaP(tEt3dg&`V{-?`1Fzz9rt<>Eve+|DX;{wK;>8rJz$$~O;Z#&~ZLFV1 zB`4(cN&pifl89s_IR`33Oo|d`^7xwZ<@Z^xpVO5@uOqrE809eJFIi!n=lTmHHJ(K z36*m_B?^O*?}&}VbQ{nPf}BI8McP!tOo>MvU7fJkEC3QE&ud^+&XH0S)_34KN=1>D zcjUGB)7JZ`P=7BP^@sXNsFuW4>r5grHOnm?Pj^VF;m%Fb&W&imt<8S=|S95|KS&U4a zf-;LD!#{S?3GW9|cjK)+GS5}6k3pxj)&vYMN@p3+~pB+|Z zOe|zQ;k@SVF7bm;7Cyb}S^QJF{xLV>neDLUJkL0#tL#Ev%i(aq*U3%Or?)iMM&4;i zOfmeK{t z^1(WZuUC~#?~D=Dpb^bHBv1wGG$}=lanh9POpFW8iG- zh61I?R6dnN3KB{sew!qwb=TPWUw-MKha~|K9R+8Zbs-g@@3w!<1{*@ zw;G#L5~(D#({#pCY^p{^jq9yQxk@1y3E+}lwIskF#4DkTP>gaevV%w_7l=BF*T4v& zW#c=nnmA37n;H7CaCmXYbuZFC3Z!LWaWmuOnCFSz(BYlK)Lp;(UMIQU3mGHX6ICr= z)=9dAC#7&?Ib1Wp`V4)1jms0A$rx{NMZ&VQRu~0NQ?=Z(_;3cS7HZdK=ujn7aulp# znk1yjSe!BRPUF4CJB@W7=PZ3Ef0oJlfc1k+)J>|*f5H$^DU(tmO*L<#p)R$-Zw8!q zC?~sLB{qMUf}Ds;pyZk|z1^U+q2z=!nmmofByv6DIpG0~=qFD(>^5wE=0i@u|D5>ypE8Vw93!1pT>s{OB0jxh z^K;+k)srU-fBmmBfB$n*mQ>M()r(qG>q$Xx97m%W-BWH}E_|35Hv7n?A7m>!WWN8! z$g5G|9({{BZ&22X_A+NSmphJ!TRAWFOSbE})y#QcYSg~?hGAG2L89sRxb6)n9eAsu zZw>$bsd}4~rj*yF{yvVf$)w)NbzBnDIN{ttj0-W%bjA_qi0um@E<{Ox!TXL7W{jm~Kx@Ga=5RYsHdMz~7ScB2c=Zl{6;WWNB6kEA6GpE_6-8Ot~!r z3@ItRA4E=3cl(;ut6ir`DVCV3?%$DOrcfwF<7Dw>iG;%))Acjr-E-V>Vy}fBgZ5Ze zFePD34GXHjtd`%k0%sD=D!enYD@;I>Q7WTt!S!7=<;uT%Z|OTLM5e*xoThUcXJuDW zDUI(W6fIYXfB}e!l!83_A`~9uJbvgfU56V65;cJ{XCOjbqiT)Q7EA}mQB;PU1V4K^ zqO^F4jPn#NI(AieltsJtzBTFZXD|7FH0tl?v3;m}b>IBg0&a$(ujBx!*6cM2Oq;-|xA;zFzOsImdRp6;AqQ z%d#wM^Pf^;nkJ#n#K>l|<8(S!l8mmJTO&a@S>zehG_A6pd7cIBmjm14a9WeJBIL-$ zW{c?@Aw{N(E&S|LHlN*a{GIQij%OYnBjdmQx18>-i9hpoN?Dkl!7LU_VbL<_ie$1A zbKXtP+;m&g?(1k>c=_3A@}4|n5L0o*pQQG z%~x)E)t1L7ANVQDl>F{^q8~CtJ#M}qn5P+)3Mon;h(ZEJj8ZIBBGVf5Hr2h)x^L!7 zU^N!QyPK#_(-E*TO;!qLeVtNE^!zd|biK%Wa!Oe3*lcCTM(H5rbnUR(im8hQR1~&x zG3<6dDK&qDaJmx(YdkrDHKv+1GuHMJ?T!;2c`7V1;;f_O#5m1ZXUN4;kf0Y3ljkxo zl&r})U`mZ-*P1y5s7&)z6gd^P+K`5xyPP@EGiBIMGZ)Ngr_dpwRmXC1f%)!tNJ}C9 z?x%zwe2PuNB#K3*q$SDJ9-GKyY1lD1qi|M;E-_?YzPjUZDvUufhX`7eRYqHlP4`*+ z)oiVl&^=Va6wyU%BiE>_6Yw@wx3;U27gw*XY8us%X$r)sF~-VnK`bb5g@TbsoUI1Y z)Hv~V!AhlrToP-3n7pthtii#yHgct-P zn{!1UYIfUxO>0g?QLvO;Bz;q;!K40|p~Tr%J=b0@B@GA|QJA-rv3d z!9Ls0d7kslJXI`Rb`eJ4;L#ZYCIk`RFV*Qci6FgDS$E>qzdNnQ1q$sOVmb8(<#^%bcl z^`LKpo|R8rGKiKHAjg4~UbL3&e4wIrUiNGE$>zt~qb|;bW~p{W4IMvHw957JZZh&& zDN??q;Tp#0<>+l7xiFEds@-sS=%GIfo|F@g-7fcWx{9>Ycf3ihK3;>2!@zEJ%lT>! z{8Ua(PV9gkaep=3*HOamB_w@PQ)2p|b|LI&;-OS6c;PJ~Lb=n@Akd+uevs#Fe)R?e+!rRrYlnnXUv#i#rVXRK6cM? zEJPJ%`qaW?sL7kx{fHDG?T|b8eoMYe0*C7o~l@HB&RmdFzks3Z(!FH);vOy zz2g+)KE|pf9sEj22pKEDymOd*5T#Ml`d0(j&&=$88v%}YFPoA+pw9y@OyL)*id<} zFfGOB3*dGk+eCQk1WE*9yt3uVls~$Nug;kArNCuALub2hFmPOJ%#fYAF^>9dW3l4< zvBu`@;m02oRn*!PnkW_ELX=ii^);M+G8kI_+mcVzzR}Cs3o9}2g><*s(SBlehY5Ck zxtcDaW_HDBh74(({-~2W2c{#OYsb9g?Jrdjm_5;E(Fe1?97DgDt^@=+R+ZP`GvXhB zHg3SoaDG72)PUpFUuUDrTBbd}F_ZmNZ*`$L5>0Y`Q?!ifC!(>X7{!CmHP4+HysGyr zHFomTWw!J&ePk}WgdE77aX=1_z^#|^YeNo3+T@(n8}7>FN{oAVnOO5uTp5v};%zuX zMjt=WN&OvLs4^)Dkx!%)Sz2kNyTB)U-NM~~G$Qc0llTlquGQW)*~G#W%dk!>?-pya z3@}Dbx-Z_MjctcPs|#P*{Mjq=WLKFC4@Zl{?o;Yz#3#?8co&35eP0`11$Kb-;VYfR zTf0GH%A`=s8a{|ET_cMm$4B`AS3QEF;&%$Tg}u5kPn+%d*R%Y82n#)8iHfbjsxKuo zQlBZruZC1h>V;m@j0t1idBx#722|&pvXZ#LEYQ6!&Ub|WUYEW7r~H(!_W2uURZ;#g z(742ckeiRWoWsM<50%nRL;Ycv4!wdgK5He1?8LTuDOy|HNQF;PkUr**yI6%1q>0UX8u$O z#;;!~-RJT^S3ke;?cq3|+Xs@4)7Xw>gD=S;v$J}o@WX&%55a;cCLg7{RqXcjz;N#X z*@x`)uOGZ)Y0-);f?u4|5JeVHLq*~q7jw7wM7K~(X5cYebt-|U-|!q#c11ZVbLIN5 zf=37=>Qrr2l^JH0jB8|Q=pvkr8rO=*PJSdR=lu$9O8!imwm7|~txZouii+Jj$9n5( z&!3m5Y(C@q9>(FbyD5MLx#nRh)*-sO^!8^D|J# zMT@uKY08||q_j}eA*5%yXK73pUV0tbeZH>0lB^zKD)T~GnqDKSPhXmja4V#5UdHG} zY6?Izo#H^9yfTy@y?>(}uct0cg=f67u0*mT6tFfALsLlW&BSeG92qvwX}QAVr!rn& z`_vIDZ*=j;d$<_rpz>Q18&1x7v8UV1U_HBpmQ}XS~=N>*( zs*QhweRC%r*{(px7rGIl62?Zi9mX$c`!vM;qL|GHn+Tx-7!#FJR5}7Wds3>$^iQT1 zDP*h8OCRd{%G14d&?bgYMm1FC_tp@(M;Q>8lC;WO>xG@_!RCwGWQ9g5)$0ga*^}1q zwpK|mp7Iu*u@F(6l0fM+GM038LS-_;^N}w6&u+ji+kGX$89OZgSt)h3c!+#|xfT=E zMMJMw3Wq)%409)S{um3V&hwVQ$tsBqO#A5!(zb;JgV}AcL(=bmg}K~ec?K%=tVD(n z`%GgMayKV_Zse%w9CSIK(kB(b5c;P*uDWg$D!UofEAq?01e{RMQ&JJfx{q`6*kN$<=PZ2-) zdVcmRb1Faez8J-1E%ld$WyDNV^p@PTtBzc2(SrZ-11)Z+zkT?Ts(vlHZr>`saA-H( z_aqO^ENQlN(n*MP+*+C_dG~N-X$pwv?RtFTD(1g;OxlX#dlwPL;Fvbjjeh3%?THgFS>an^%qq*cVGg-;Y=^F#^HDTtAOkG$wpm$3qUd{Nu6daN9!!lz{#=O~_R>C3{ zQ}x)@mT1BYQ58t!;ko{L1z~Ft!q@c}5T*Ru*0EjP&f&}t2sd%F%@(9C%Ac1LOn&zd z|7!tXQKRZeh#(dOsoTgs0Eqv(ftgH-(J-4)`HnHcv}t>$x~1)m#TF0~GgEB0ZNCvk zfOcl}vmO*gPH(gj&HP=6JF@cHcajZ9+d%#m0PJ8PTYmy}WSgitNSIBO)2{tiJmk!JRMN+JnZp3>TyxSb_Mn*2eV0t;V) zX+jNEu=ka*Rbml+`eAjNJ4dW%hyo1KH*hK0s2onm9U_6}&q8=XZFZ-FS-MfY?R#$D z4UO`q$&2Nf+;i}L9){o%8R0qjC;zfx!%hQKW&DL>=_-}ImT|O;(+!cM@1S+-j+&)! zdKa(R%gk&D<({`#YyzTe4~(yb2qZpH@|i=243rLi2s$6OWXFeQF_RqlPvwyS!Xa-7 z--gd=GlJ^5nM)NR(Y*U4bU<X^o@bZnXX>6O)X(l#vg)!nHY*)1mXuSW1S_ zJYMTVC5D7+8AIz}d+~=DhCFR*BVbvg(Tjlf8r>oI7G*O}EW?l-i&UmuciEQ2K_%-jr;lWUFb33!Yy=rPai&XJya({F=8&TbuGKKkMTh(4z1IS!F1^iK zmR&_@kCj8op_yKt*&^x0`@(brsC|)xle4X@tyiCo=uTY@9me;w{8|N=K$g#%#m3Z- z9=v=Okri$!W>O`DGV8P~cO@Nri_#o)S|9jsCDtitqyr}4jKpsoMTO9Ni-1Zq^z1I# z%)97SgZ`Dt1=l(77bTRH3QA%>mbhZ0c{S$qwJfNu8wG5PSd}CR&~Cnpr+)!rR*`_G zDriM4Yu5;`CFCiJaj)wm!j}3j{qd3IG=enB_0w~)t~nz>tR_Zvsz9aY4ATZ{JnVVw zdvUg5b?SV!Ec1;;4>Lg}L&jPFfjWJ@Al0O2;|g#*Jw1;;a9{fC`uX(IgJS$TD*sif2?L9><3>r0L0{Uu|8XI2kg+C(plKS*=+TtZk-&1CEahEf^h)-|9%0 zGJZ?1AKBS|1Yw!MbS6TK1rI=JEsH1`^*3XND5dmwc_V}Q#WQ38(TNT%trcR*!>quz z!l@ub?s4Msl%>2V#k`oRXs6~u^waGyL}QL ziDpa*KZiqMfUP~E%U{@6lh|V((F+OU^5#|W0G<^((<4`Lg^V(dwlPJlwTSnI6)=86 z7{F>bg2`;uLm#Q9WTJP_b$;-Kp_zE|i48-on1ob8K0VRRi25C2lLd=>=S-f+B}}XzFz`M+VGMRn*?Nh2*3oc>J0~eOH?b?G{L@o z^vQ>ZZeiT~N?f`7mc_D9HgKp2!M<39y0QPWJqkDld_JK{g(7~XDUO=#N(j;?s>iUm%+U!$2tv&njcEYI_ z_Uj=q2)h}A`a(MrtTT+;R~H0hD;SH!;!4&@|LA8g=?$N*2G#igCdl=_!Zdk0(($?6 zLPtQU<76cgUrI$*bCBD=V;z#WplgA?dhh*5DmF9$^Uzkv)Yl|ZC~~}APOtuA#2sQ| ze;FV}r%-a)iPiG_YFVwT8R(Eawv)k|=n0wV!CX1kR`=B(i?+ug&Mh=6_n(8JeW zy&E(x40yx0@v8VWI9PkfPw;#5qiK~T^)Rar{#U1tnn#vyDB$zxXg$TRtTWQaW?gID zl(yM7^ldEaIOH8xgW8dyGKa<+DJ;eIh4f;V+7x;YUJaZS~X0l=kU{(k){h9*m?9M}-Wn zLiw_Jnbvq>h~^_pR_zpUc=RJ-&vAiFum-7y?c`sbQJ6JYroueR*lN9_-M~JU?v54^(zM@k&fze05Kzi!hRY@BFl&9m1qs;(Z%9nA+(KL`v+DyFS%P}z> zQ$$}DhaLy05Ood$D;3f&ex!w#)}H3e%6#6}&bWpKH}C{@~L!xINULfJ0)^=o{$_s64E>5i*@k4j5BjNb|-aU5!Ht zGnp9MlrTzLm^+~lIWZWzuQ>WVnJg2s1lal}7lokdkO?QJbs$er&IgI|iYeogqA!h#mTBo*_$60-(kteKyI{dVj%ywGFB?8PWkH6~hY!Sr9C@pLRR2Cc?i3@y7q3qOIa9-dPDaEJ}LpT?NYiX zTuE(vR?*!fAD=P*&qH|5h)|q=457eS{Aa#;II6G0BMqvCdhNAAA}YZH#0S1H>N#2B zY?zOShO!Ysy-GcU@MYt&Yo)TP;o&BQL16|-9b(*V=<9L z27Ad7k0z#E_6la-9)19u_8lN&O2=Y8k zbI>DgD=p=*Rpsn@ZH5V%vj~hTcize6A*}@Dt}~+Et($F$P98j&w=4CQ1jkSaO#aTU z0LJzC$^s>H$$i;wVhn$tzDn^ zRrX$(fu^|TeA_JAsZ@oGfa#R+E$c;!8I?Sh1(C01gQSLpF(*fX(J*O+4aGEB@hd&~ ze4~^QFsb!}FkO{W`fTvIrd7bxd4U8mO?`RHGTcr2Rq$9wFB@-=tn&52Rbr_Mhqs5P z^ges$s`gqc`1)v$&A6w#`nlW2t91NQp+r~Wp6#PT+{4>9wU;6D`EF0eYT=~x3myla zl$CRKA%>X>yb7#E_(E~m*`WyFUe*9k^x%O6lgo@=j?3`iVMrvAjD07`QX)<^kum^O z7FeyR5BDb49rrKTC+dF8h{^Ja!|o>aE&mZIuil=SF@5QC7M6g54Bb4lfB zk+f~X)E|Q4XBzUx1t~y_?Ai|OvD&2UylV=+H8VFx4GSIT3_ayn-d89U{s+sh+^(It z&xL=dOaDn}Mnjw@Z%}Nqr6mteg-|60wn2lJtE>3yf`3l9pOQXATuxRZ*Rrn5t$S2Z z0h^U|x9kZwRk{xl0^9c6VrAr;VFoNzaX9j@ksrP-c!g>tySmy{=)A+}ujRm7Dr!>- zIHzJ;-u5tRIcmzBC_GGz8}9Hloz5|N9i)!6e+YArbxm&*S{X%10Yiun9fuD+m{zde zd~Xg2IE_1X!3pK>hc)9Komv>?(PO28duD#G?@!(Z^zhG@A~8P&;Mk*-vKp$e?bbC} zKcn&bPS3aOO^v7Cyz4J+1qGBo+g}AGiy{%u$hSSwgv50&(eBDcEQGYyVoUIHdu@^+ z!R^joVbt-qYzff6{ZT&F!<&7sCXB)y&VAhyG5r@rl=rC~@?}Y0D-1xD|B5B^`F;11 zhVi^B>lx=(lOs3K?UO=8Wr0{(U`^aW-cWojAS+9hEhSJ093Gkf_I3VP`cXljZZCoG zhmDS$G+rd}PY2QYe%@>fFR^W(febu*Kh0cu^vdH=y&2oVPM8g0zq#yd`cyk?&nn`8 zbGE zvBNvb;#e{Cch$cwJ$NMKThd8;KS12j1<#t+=*IugqNLOrb z$%_(4%kT@f@Oj2tYheT`Q+bw<+(vwDe&g-AzT(&$I#Z7q%$KM}R#=pld5&6%Dx8pk zSzk3Gf`6GM;iZs0TL2S;$i$AJhFb>EnN0lxg})28uYqrJCWkgV`+d+^)jpKZjBQo+ zvnb)1HFB!`zGN}NUC&6f{I=Upd)^|rkRPB!bsC~wj}4e*wex)@SBPjI$%l_s>3Ggu z51Tj(y}F!l#q(<$_7DLVtY?pmQ$XmMg>+b%`!+MKqje_2k&iRqdP@IybW}eO@F`sj z(|95X`{-YW5K=IUd-7>mDRejhaH}?51gnS^=U3Ji6k9%Emr1Dx-XTdd!#XJxmQUY) z2%NZW+IX#V@@_*IrL*T~3wyiRZNBbPqSts_W$HLI&eUTY9i@&{inQfx*v;yd?e_Lk zSnxsYe^q4fA6ebZ=sx#(df-DraYKmlaRuW}@80mYfg`uKx9!h~bd->VI8xC?~=DlA@nxHw2^`U5i+-s+nZU6T=RSy)W)bqmpj|zJ} z;$)$&VyLWY(^dA?D&9=~d#6dL{Y~atC*S6srR1dOVqnQ&M@z{~;Fd!YUBDth>67gG z-Nd!jg2IX@Yu@3g{0wf2tEhIi^YEs>RYO3?ymV}~7Yk0Clv&mhSzDTFQ!_F5IQ7gD zPjfapk>M$jz2BLd)h|z(FnX+beg&(S{44KRz zCmfvQD%O3}6=3rIS@OU_hNy`HW1CFZ&OImQ&ZQg$B0-*=%*Y-sJn zdlRqxrl$Ol(Q-uB%(5*TI=zD^~aOSfzr_x2QeL7*&JDOCv7gmHwY9ZxxFr_kZOVO%^ zFLkXDX56-|4}gyz(|>27n;|c@YpA-I->Q;kS5tSZ_%kTPjxC`z-JA#dRyhqQ#1uqV zj{GxbtFZ1!9)|n$>C&iIchKtRR(4pH95}Di`sZ0K`Ox-&APrcN53>p1ZPf>aek>Z$ zL*i4~V8+x!noJaoUo}g<^JJ1(NX%^IRXj?{MKMAD9#$ZIZhV9XiFwAN-5p2iSOQm^ zi8?z15BjM`3}jM`)#rH-2vKY#nzaeJ-Cd#AP5c}Wdei;r?*E`%y^|a3`ycD2P{#k9 zgr}^iA^{@p(v|MpQs&3Q#;=Ecq3r?9tQ=x#9b%gCzS_auzfO;P|6r5c*a&p&=WVw7 zus;#lGwe5ovT~IEWjAV2`Yw=(TS7K@&#QzIEAmcxDC1ND0Yk2aR4W3y{g@~cz&1l0 zXI(Vg`{Cv=%0ZMG548?fPT6*>R@;`I-nT3u`?8j#+DCo$%a2hO&%rhkmtMlk$W)>eI;bUk&M~A<>LD6U0 z>8st_E>I0jzR-?kf_+hleTtyE8)NLJlP_v0l`vOzEr^KaYZIY2@>a|;O0dV&m~_T> zUvsjSXnxKmo3O3(r<))R3+pE%OQ(iskcnntQv%7ujZkf3nR}QVupla#EBk1Cq&S`c zdCog^BV8L!H)8g_B!n-lm|i9({zw8e5cQC+&F=VhC*=ga`W6jo65MJ?X# z9aFUHfB06i9bR8~|EnwljT@`&-$Md9SHEXcx&=;}#SkoEh^x!tw^t48e}&u-qMz?! zmyF!f>q6BzhV@-PU7(__f;8g5dPG(V<}yY5WQxmG(fvnjF+CACeHI@&f@iY8C}JM4 zI82YU34y;ivMJh7Bqc{!AI6H)C1W5vGTX_oM+6yRDaRm?_x)zGY1x846UxaG8&Zd{ zO}(VomKxLi>&p8s7{^CLtb3*m8;8xM zQ_En!s-n%AXYo5fe8k%q!4&23m5!?J-w5_t8bjTuGFr_(V07DKkFJ=5EuKU|LpQqN zqe+P*PdfaW>_I%LD0O-RY88BdnI>`9eT>4jro zSJ}fw_qiIRYSKRGqH*Zya+?{Y5}xZ^I@_Kpl+8kEtL%7`h>GE!1bI!ynU(y#P8KO8 zZFg%mci^e(_Gfm~-uN_uuYMGP4pB%6`Gxj5^j~CFB82@H#OFHxXsFq3{ z`{uOtfYSY!`53%xT8**P`+4ex+6-wsT&d4TL;qyD6g{+OT0I$Q`gH*TusL{QowkKy zp9Ee=JlE2wCYODsJ4oS~BVdC~aYqIXEB^{cGw;1PhQPc~+$OLW4m0hqm#QSO$dQob z5VW7^t`s52oJ4^RF4fit&BTPg6C`yhI``q%^y_*l>Cw@#Sw!I-sQWun2)}Q@=>ylK zEd19yFUjw2@3R-rInsx|^D5tw7GV^bmpzk~1Ny7s`q?ZHhY}VVh#Y|4e3yM%aa@ z*2H>M?@D+2yOH|s>4mAU@cs;&P7rqXXLE_EHrov{*vws#fkX^YI%7PmUX+x?HI0xm z=D68&fiA%_{~PqHwPuq%=WA`vWQh#s>Sn6$+uJuk@xlYTNJlRYS+@WH!Nz`N!cn%b}9V1r2KWi}I5?W^y8 z^9D&_Js3@wJ4&d(lhwJ&{nir>;|0holOn8#ya_q!7zf!xor9X~H-KHrjEs8o*(MNNIZ z6S_DU?)m=_9!8gu8P`5i?_WTQdnaz1Z0+^;!o(dkPUnkg<7D|WgDlhQ=H>nU_>Xx7 zheqb+<|w<7B{fQ^?==gpGP4B4{!C8dqG)1cs_iG^BKnS1r)*Jyj7WDpEPL}+Pi_)> zWCH)GWBW}r&oraJB-}?hTUSCCOR8!{ zu6LfMn;t(ONuIU2bcw2P4ji@CN3y8`CfF^nZIJK`_#K}sEr2M z{LN0up9j5VAIT{0DDflX)W=vmT}32?o&a1y(45^P1GZUt;Owa6JFWvsPg@c4<&tB} z#EY0eTNHB*kR6NsxHa=P(T6rD#61IUrbGvCy+}mSZw|h)vDh+nNlRI~PX%sD3zaPV z8Y2LFv%K?WMWmX9g?jnEMv{a$na}3|{2(cyTFJG(tKg4D9p|(?h9Oq7U|0PE-zgta z)0;0d#yhnCd0palPDkWT8$-t7iTHm4DhV+*J#yn@JI^B^;Hl8)is7AImJW^n6#N23 zG$KKh^h+TkNy7e9e+%I$2dH0)P=Sj__Q3aX2~7f4G@7bjL74VHn$(p2yP+4-(L&QU zW>EujxJtA124z9KVC9f@4eM;W-3Y!|I5*vpoz$en-)}^6`~FX`%u$NY>j5|m%Y$-Z zG840O+B?gyV45M86z>PlPQrgMt$JQ}Po1DFiq73_=2X$8H-<(|i;fs!PW`)o2~6$} zDntYPL!n}*M2w3q))HN% zH&?@0_n3<$Mk0~quL;!Eek-Y1Q6zLTEG(4M;uui}*QrAs>Vc%5* z*T#yT?kXB3+qSE`B9~tw0TsU^2}dArW4rHKm1)<7L+i@j_LZJ+jNTEyPEWMrO<3+Q z;LdRRNzhP5?X3~cxV7xsO!n0yVLzclO4a+&vm$RvNg6}avsWLS&f0Y~i>{&%^=26$ zmOd)t8>4LGVX+?t#=r3-|AC3eBRfJclD>VsfXd7{&#&4Yo$`;Vs0qNGgGyw{<6lL% zo-+X-N{iQ`WuC$*-q9Wn_ee44sFjcEB>I~U?HNxw=l@&d?IgUQuP0cnX9p~JyR3zD zX#Tx_Vw~llOn#|^DYta$<(yA>_xK+V#pXt!%e=|nTFMt6^X&gBE|ibqRBL&!mwDSq zl`?jU#fGl;r*!^UEOi}?W7v>$xvGXj{Au}GFAw{=gCrXV8jaGxZJ z)E@>&yH5c*?z*~Lneulfc1!y{d!mS&rTU`2R>gwo&jAd=e;C`n>`B7=gNEbYnpYVy z-#OZ?Grm`n_)+x!?*4X?bD9)?-gnQZtb--gR-dKV?DQtnKA-R4PVej-l^-iu<2ui! zA|X?!Uw3I1k3IJqM3@`FmvZU36 z0NqY}9H2ft#zupRtWe&2hB!48-zqHdYeBq(49Os&Rb>+K44h%LgzN#PZs>4FUG2Ph zBEX2gFBFB@?EBsgQ3O&Cp(b;~J840yG47H$gf$0U`=btWktXY@#N0+9i?}jEoAH1# z0tw1;cB}-2DGk_weG{pT_TOPu=r5E2r5C|7tPKOYGeHkhA$UbT)Qz3!GbYjDF(ihDhgGQ&a0!Rt+ z#ci)}Op`>KI9Wa6#J8bOp1qWz;`yvT#mB@53t#(#jvU18?KNo2FNTlQS`14XhiDvh z9*{({_KO9{lRcHiJUHm|>G(nTrn(3RFts2oLgw0u8*70hM_fpAk^&+b3 z@T5JqfAh;+2C7uP>9Bdex;_Ra*^$6+F6n~zB{8dL(*)X=2xLw^2seW5ul-DxS2O0q z#7=}wYYgml)MMw|mQzj^Z_ZA^PH&+0c6JQyaCMjY%bUZT%;k>TUw4PBxO9u8Y$*5x z(7Y07(GyqZfL+~8G}C&0`loqefss?`dX16uG>|I0^d4sk9H29sijuEa0_NY9BB1qi zmD0n8l;(OQ92|p&%bql-)JR+7U1cT6oG}y_>U1E_qV7DiM!1h@Ao*#}$Bdlcx$Xqe zrRAD~H9u+|=RV|1o*GJy)%`V%y_<~pkQ!}}-0g)taN@X)O?ru_%3@3>N*`L@IO|ET zvMMGtbBlEhu&6k12?4~ri|er;G{X=4rs_j{&vxs6bzpOcbuSDEszIy{pszppnnhxV zwzy+voJ>;6I`a!sOIy0B*=(n$jBq(TkE~Q7Xjdj3Tb@jThmik(Ql(f{i@eP6G1lGt z(txIsR`+ElCkxvYQ*eQ)W~8W@gPaOuUZ{mKTB9I`$VhU^B3$J)_4DZ+`lUy5dQ*Lu za~9t&HdL_C9`zxMzqf0zPOhk#Ut9jN=gsPOua50QB(p=y^4Rs;`<V7F)<@cEE+qzZ9L*eLInIh?tBH@&gH0xhqOJOPTjIJq+wq1JI z;_@RRq?HULoAcv;bW29LRXCD;I7Hl=1FfE=8QRq|v)xOz9+cpjP@;96ePbbj+Gc|x zKaXVCz^3+-T)LHP-qr^lc;|juO~lC;T@36&K3Su6VSqB27M~mo1g%nYbEZ&XF@~?| zlEYH~!=j5qe81dt=1ZvA@%gQ=+L!V*4lON?h|X`Ck`56hoq9^!5RywL-2|fDL_Gup zKq2;-UXd+;EGkFty6^BTHkH$OOMvF{<>x089?|wZ8$fqbl%zDtD2%KA!jH5359YtK z;mW}O5w_nR!52ye1-#x2@Nj;BV~lI2GFpX05#RbL+P(k#n!DBRADNNl8b$qRp z4vRQ3N*$||Q(*w=FjLFA7Mr^;j+dDFdW(&Bq}WO=xRt^>8ky(wKiA(F*~Kgzdi42C z9eU)&>jVVY!!l2b+4+sTe4EZFd6l3zu11x-T@NU7m&3njy(T)rYQte~B7XcqIFq93 zlxbyct@^W5$sCG1cDu}MEVi9oezE_CGd?4>o1NMWhEiZ?ZoY+}H7E!vk;bg2V)INo zmf~Dw@9<;GDOj(DyM&yFj`*Rw;yjx%=wp{RRf+A^(NQ7*5t6p(z?nW2s?JC^qpMIp zGSWtAZ<)wWxwFI1YR;NrGqJ!Yic$uQX4z3o+zdd zh))0e;EoO|<8JTK(p>*2epGXJHdNQ#`|lw8!c?`-LvK!8&#&hlZ>lReEiT zDeDQ|LPgp9*Ag?V?SUU{B^5){Jhf!4Z0YmI%BALmxmk=}GrGwOI?hk+!OIBWq)QxU zd|}OHCVXAQR%67*r3-_QXM?di!GaY7EOt1VYlQ*5-osoDFF!<;41^+&kNF%%Y-8rM zMG3*C9(0!ZI(qh_=19nrs&lJ_jONkFsJV+@j*13rwWPU#LM#1oo4wmH}NxP_jHm=SB1SqR&> zB@Vcl(+ygNN}xPcCI5Tou!clvb8GR;kK{I8*pf)Q1g)(Xwhi$8u_1pbM;gz|XmADa z1guw$=A$x8mFSlRsqsX8Z*2Ht__OlWXF*LDg<%#B*k7}FRB}Z1$j}@KY0dq|N7C#C zhc`Xo``lASSzrLqThd?4H2-aK?U$&jUQvPK=mIBY-v$i)2XaT)Ou6P*H!lalEtgP+ zMwCgbR3(>C0yXL1>t^nePXC}JH;3hQRrYI^qhzL1w&`}9ba{PLL}yQ5O7Q9F(3SUH z=YF>gqjKHszpL>?m%v%q-&ZC%?XN%&^2eIWoVz)5@9}Ozic@hS(-3##&yL;r*%q`z#a`Qw$zRODiF8 z#F-bau5rE$NM<>)KrFje7breJG2XRn3sTu;?}Kb|=G-lp4k{4LW<03j3rdjN(|h z(NmZGgE_p{6O|$rB^4D#O;SQ+CdWabvulYIjO_j?oMLIFhG`$_QV1vc+eE6Opqu~z z*%a2zo2m^!nYIb9ehv9{U;Pd)Rq?{1iaw{#tll?6aY=(D+6v_ttEaf(9R{)CXj9$Z zy@O6)JrgvA-5@*oGi#BT+%E-JY6sAKTi-3?&U@}?${}Hs!A66+@Z;L%Pl~3au8s~3 ztmHblHSz@~3JG&bBX6lN6&JEU*dpA4aJ!Trs0Fc~9FQix!{4mjKtfT-+sr+m&Ef4x z{(yH5s!rWZJm=S~0n|0vq`1+vRRgI>W<$Cmgdv%KP!#K#@H2a!6~%J)FNfalJEKX=e>Pry*f>O%5^6ZFKKMdA zR@S%iktIC;es9O^^34r(9@ZETI*6p)DOKv*8+ZG+nNh2h5)F}8*VH_p z&$Y!sUf&NmwNxQx8q~cYqra<%HPQC1*g_K0=$BW2_3Uu};YnR!kh-6Z0#`i@CEh?1 z5ByYKV>a?OfwLEJa)?p3U#Jluo`|9??F%mbx{mf8QCiooKDP8)6Jt^O3Ad9}M(mX| zR1|iWrv*CQnCritB-D+5B45Wfg;^y=@LJ)!{#W%x;UmLv3HNRhjo zs$toGv@{(Nso(-^vEQt18_6!*+SniP<9%@88p3`Pke-GRh&QvGMss5RHpv~m(uwl> zrd8MQl6LsoYbHyTMMV~|s1tu}X=+w3u2>k6(?%EMm1+G~uKJo@MKZ0pjS>8vH59B( z=~`@2ryg&VX_(|8&*#Ek2&YrwQsK!BctHJ`+24M?W%EDn_?+Oxknat#&6?kNVFS>k z*OrqXr^lQJNc9Q2Lfz5Uy_r+5RHXmkDa+J0Shjd;=Xj(J39&S;3sGX4;T^96c6N24 zn#D8tiuL9yK!uLd0z|w2kv!kmok7dQ?T4#WxFvKXK}~f|v++y}wk?t1tD8e)OWN8U zk+jM%LuzU&%7kBew|KLp^nz!K2jy7A<@)ZHbGcTH`y0i%K0IVhTJ>A!1~j{kWqP%Y zLiJy(Ce87lY_)XXvge5T4Gj(s3b#2c1!k5Dj3Ev^j7!&*Vv&q2&KDj&Vt;Y}{iHl* z5)!73tFUdcMV=pDp#xg=$W-HK#kNq>a@DEj{agFNAB~{#F*D~D=c#{2a0G@U4n>SQ z#XNlL+7I@NL9AT#TZVJk}x&T#E0-pKPQ<|lbq6?Q!^PZ&D z7~8B16hEd?ao(#9W!&^LI?^wG7NR0L2hOHazuUVrJ=Lb19=C303#Eb z*%9L7sW|tggM%-U{M+1yJVP>d1bKGQ3o2St5;M2vvw`^GkW9Z?C zROqI_j-}>H8i!iYpWG}O7TtvP5T)sX@!2tkhJ3xUW)lUbDwA2dA#K$yMYRC3cvcls5gR<~m z4IK`g9@{Gqi&U13$sARy&q%PB6MnQO0w_6DZ6$zTk_Zh^t<*|FDNjW}($&R@fZuGg zo3R4*k@3Qo9EMUF)uTHv+9E_tcBINg{Pne*6|~~>20s}B!#$6TCW}$N{X)L%pcwbr zqBm&selWh@-Dt$7S!w<3Kp99P$k3VwgW`QCZxB|V)Y5L}bOOAZI1P(YxBu)+>rvG4 z-+IZcn}H+-jHFDNRbJfyoSe(Co`9ZDY0JR0t4?HO8Cgh2Arb2tl*ATiq?YpiaxZM^ z!@sQ4UG(*Pw$It8uT&z%<#?7dk}+Ot1(+-D{*$eo8MvJAh7ND;vYk~sqt?E+rd_XM z9=>MYFz4$xSUD;%lMLkYZdbcmUb70__s_jQSn3g2l!4jqrkEHTS65zpW%AQ~zSESyYg`P6H5)aK#l(OaqbM@IJ&<)hV0r>VE`gU?Wg zQTumt6_1qXoE->v(-yU&-Ioy4L)NxFiP#XqgjxFMtV8t^&)07iQ6CG*aSc2E)9#XZ z{cbX|2^H$HCj`C`aU(@{NCG+XXSR^mq2NY}ZOEwSCac=^yq!8JPpL!%fv#4mJ;lCn zUt$CpjMKgK!LN;myH|T&wcad)UdGQ1e`V9lee}}%BUQ*sJ;-c>+PWT>g=mJK5UW0y+yaQ<{ zeawlpeNI>whO35!?0jA7r>808Th%#7L&>!`qNR9y4uAXrX5s>PM z3Oh%-TjW^ze~34KXU)zXr_T69JfmohPd_b^=n^n)_uk*PcP_N_t!Sp{_;v+0KUK_Z z5I)g(Vk8}xGINRTj)(AwvN#P)w@_Z1)K!0UO}&(Qd9C)4cj#sP^JTyHl+9*Qctqty zwOt&V0MYV#`UV_IAhun;1V%+jH=RGT`P=9{lerpQ&RHf zFL+K=@U*G>$ywX~ja~wz6(SrrqjEp1%FYMg{RyZ5|Xcdt3SH-4R^ zy!qF1zN7wEWB&1wDF79sbhpX%{>K}3ts{%Lr5((Mj&qb$ia_%FV~#E1wK*iAqC!DJ z_rNb*?%b=Rh4P3rB1zp)9>DHDOx{3f2m${0u4fwf)xdt%O(Dn0!7(2)F)!FMddsT> z8n0g~v0Yha?B**OudhX!bNswJHc`)!%uK3Zsi`4Zwb`;%T;wHO`CT%8lWV8od6!NF zkh?x^fn$C?d@xNxhXdQFK(w>7*47o2+0Y;Xnx7G|^KVRw&8V zf9U^_4V6w)a^5aHr);x5Ql^Yd4rLUVLB*4F7)wC*bUptvKjbqUe?(wu@B$TWFpFfV zBv~T?S&JqoZCzca9gApDwTxNUAq^*@P4-OtFMWFj2k%AeD7;w2cw(VAnL)0iYEcr$ zv2YKuU)b|{5>bL|d%dpdm;E0hK|&G0YthO2FHZGYgrmS2PYGvr=>(pEEIT}j5_kx| zKI-RLHl|mI@IW+7a8E8faJ~jduP8~fqF2(VQg%5lTNtg~x#Qq;;0_}xOp>6MG#Ow@ z{;qu|+R$j@((^?T$V`Z!`HBQbzS#ryd*e-5x8wvgx;k`Aa~%ExgZX#>&2MWHDi@)2_KJumr%w zl9vqPHHYjF)|C);!C$;gc-^LMg%w|sRjJ(6L|Ts>HOVC>T-Ng2`1gsvt>$PE%5xq2 zjhHI}Lo`|QcE7GbQ`%~)9Hf%Da- z>R*GOg~5r0v>WSe!!ACuQWR80NSU^=6^}APQp~NQR&-!I;r;B;VFq_B(T5F`PhoFC z&G@x;N&Qw*lwc_k7%w>_$SmXYeJe6NVf!$pBhw(~^JVaHrU7C*GJZ{|dN8+nZHoU% z%=X86G{e2>XNT-52|hj4?lIvkM>@P6iRf60;P){4lvBFoKOV3$EP%M~6rQFU!e5)V z%)r@uqv9ui5%b@kv^$<^f4zj0i7HNjtIrSHaHogSJLsR37&sH{TX{JUW?2<8$T(<$!8inYD$6K&~e)IhP-W z!_;`t9S|?^c}U`n7t>PTH+Eii2+D+r332W)Qfgg;hHx!}s_Fs-&u3eAU<7;hM_fbl zE-`L4?33b|Fg>=*+v;Q1jzzckamRdU8(mo%=AbfAt8pT`IVP$Q!;BK(YeXTfAH{o1 zK9e0zfhyf=*!bIQ0G4nH!`1I!T}{Ye2Tw)w%39{L=oa|jCknv7{luf8Luu|nR+Byj z=ORODigoBmeG~X*??T!#vWbYJ%P8&b!&$AdkImjOA9Tc!e~va4&0Rc)hT!X^2y|#q zKF)Au(YgW(@P9+0Hlcz1C0?4r3=)%5F-wv>-9U3jT3#{iC4Ee;N zqUABQT0lvUZD%{Luiww$itS&9+@bYbNz!esHrxFlroI9ysxNw12?aqIx_d}bBm_aa z8%b%TL8QB-ha4CNP`ac`=@w~_PN|_Ah8}8|H~!xHzxURq4SqmyX+eylMN`gDc^~wk{uq2_?6dGN>H& zxKJU95*&IUQXZ$-eQT1#8U7kFB>jp?x94@KPFJ&5J>{7waIj9T`X$E;wXz?&jgB?y z8}XG|r9)Z>i(OT7n`{aTbP_!p6)?p(2@(_6XJ>$hmN6;uT?5k?e0i~KN}=Y(1Z21y$9zZ9HKV=NlW-2W(3Gi@H7tKF-V&LssssJ1eYjnyPHSZ`g<(W1_v_0KLS0{HDh>}VPWMuW|++&6scNJ+BjQDKifKDfT^ zVE?Y)c(R2yF=^$#?%B5JEV{X(W^AfA1)J!|1rZ`=%AUR}r`rRY?9b>Z>xGOwQY+VS zVzpjcU(*SXN6YEEKdEod7J=4RO>tAKod2EpkRlqpq~dcval3IaK#QKF)Q0E1cmCx zfD?`iggH{6o+im z%Qv;&oHdK4XZtg6K72@1ox0cKbIxc=rPgJTuDFFKp+k1tG!CSl(~I7OtJYL9PiV3h zR9H~kMw)AX=kaQnS5CtHq))En6swf)tGQXoJn?A{+kF(pEh$gj>R-X3`(=1-}x;C8@ zl<;%wEf^N7HZ@C*;g(L_pdw@5{)@MCtMI9bo@ zsrA`)H0JRFmDD##^za{n`1ke4hMbwag^BwF=6M|9lN+?7gX*ERre3{#p7ZYqqjg}= zve-!J=XHK}z!%dQW4yXtLdcQMdf{-po>)M_@s`Sa&D@_;90+a5Z%Q6K43Ys>x0a6W zzqUF2FZmbCRm2~z8k=u-We$rdLJj4J`&mC$eBJO=rgUJH#*TFR0Z1v)ML6o2znS#_pD_{pXKaR$mXp;&sQLOBj|= zi{~fyiqan))r}VH_*okIox{KWdxZi^JpA{KemQXu3~YDnH!4{4G_lUi(bGN*G#GJe zv`l9czdz|l1E`P_SC+!yhy$HY0Pk#txl%l52Rfhc00wCh(7S()vsbityJ#sCh!9Y7 zAjx(&^@OW=O{OFI-aC)D;Vu6CEC{`Q+eg`PRbPF2wlz#f^p|a2Eq^lL@vNa14qtqE z^4-+hFJs>`zN8UnF(LFS$ftKi==g9c4QJ_mL3F}`af94ot3Yyg0#!jHtrJ!`hm>Ga zJ~3zMf@m1;I$lj*b)S*tA8K+>GlX*`tHKC`*wB-946hkj!JAnejr2lWi0AU&9QYeP zRaW052O(V4G7b<{$4SaqL?q;mFqPZq$lB2QNG(2e6e$1`iMEf#^L2}8J_LnZQ#LDU zVM7kqDMC6X?wK@~b)S~4Gq>SWkA54EG*U zI}w}x;v0qrcf#C7S0-;BTQ0Ypj0(>bRjNh4QFa79rY=*57%e*GU@dIE=TOOaPe^3i z2JykSUo^qIe4LwG7S6h+RGg~J_%w(%{8F2TxJF4@VGnW0y`G(n5MO84?-lXD??0O+ zfas|h0*44qQ!Q)e$}^oZ?w!mZqmV>6X4vbU#S7~1K+Q(V zm`VK?-82pXSt1lkl#G-D#kI+ooSaL)g~5IBNI40OS@v>NSU3S;_tL=D z!>)m9@G1WJ4XMH$45N1ax#kC9+hZ(^*(rIrtap*@J@63zklri&p+Z0 zu8ZsG6SifLie>h7A-WXE_k@=_25Ky@NyA{$OBXtcX>bcVqU;|GC(nk+gMB5#oxI7{ zLinwL=yb4B{mf;n07`6H!%OMn3Vfxqwk8r(!r&=p+)sF!{NAk3^WL@;QJ;2?W;S8i zN4~1OY+1fVKcHjwzf$1-cYm4o;LDBH3*V9`zyEK$)tvEvZ5{kCtUZtZ|J#D_55$r> z+McRp=bYWnm?3q8wmt+|BT;5|^=2C|WNR>D)-mW|CpbI!gz%xu@7)*YfDM}atj=@q z>*kz0;fneI)E#Wc3|SuB8+;E&oq_|~a!@xpJI=_;&R*}1bGp$T{?1dH&aP_IZFO~s zf->jBG5WUm>pTz4k5JpNdzyzE3Diz`^!|t7%MWvREvSHpLIlbddBbpZe>^Ag(2x2G z4??sCZ*JV%Ga#xTW;%y>P-i^nN6x_+Q+qwm58KXX2b~WGOUuj1=KFi(#+d?L4ujXy zL({?ja7h%p2N5^R)eOZ4*HL2C3|B1=0ti%8bvaJ~x)&s1JKmiu|4s$cqiXt58&&0q z+M%2icu?Cu+VN-D4=gvk?={4dvI(++#jLA;conc;qcS3hrbd=3@9Gd zR_7my8+H`dmrfJ{CH^l3t+V1dI2xI@gvz-q1(*XZS;PU2T9y{_M-E*1hN=)F}Ae>XULE!3z~PmGiyP%mZ59UelGH0K6Vh* zUL~=IZ2TvtRU6)}bTA3QU>&5A_!z1XffgTdaK?kUqS;V)HAA^s@bBA7*zL=QKG-$Q zK~C@*MNkMjL1Y~@wvy9loVsP(zUr*zFFN(EhgMj3)`}vsp)2t5n@XoAGGOD4I`ovM}tB$^I&*q^jH(fnz8`-{- z1lxs;^xJOnop>Q;w~9X%aU~+)3!A=gR|dB5`Z>SZU@_DVYTiHSLLnF$j3(51-gzC{ zIS?S;Z#e8w-QM?4O0LDgD1M1^{(zfdv!VkLy>c=Mgu6uljyR$@b`CYpa4U0vKZX)z zXJ^mM%se@lK%L*Oq5BZ#eFr5UEk%)4HJn6;O(jZ8k*8H$3x;_ z{2GwCo!nEaF@3Yyymt%_L+qh0avn&6_cf5Ev0T;?w*?YI|N8#n2O{9uJ!fMs=x9zH zB7Sx=7Mwk27K~g!9mx=4@W1b)adtW19swWzD=6|BjCzYYMc28QZ5mv*Y@dFnfZAo) zDo3Z{E8O1X+JI-C2&R=d2!a8j089I&r6&F>rbGgx1 z#SnabB?d-PuAqGGgE~;Y{q93k-jiu`;`EBfY=Q^=aJ*o*(P{aA)efMdk$S80oyGK&>yf zK7vmiA2pj+>fN_@A`s#~$DFG(X44lNozHuDf|9zsWgfOV@6%vc@N3|YgMNmpz_Cvf zLDgP?iRb_1*Bb-f+G`7lwyO=qJNnKXIBYGs`_kNi4!klopHaG)3R+P|8?`P6jd)IShKki(sQ1M&-~66Ek|ii6!`!MZ3h ze>tPBoFy)A=Qjb0;c4$d9}xffufYD7Gaq<}Ga7t4sSxnSWwogj_``DNhC5c8yIh9x#yd;={Fj@bTDbdFO38{I0Ei5gu?>kMO(Kw3q{TwxX4+{-8Z* z&Yv!L4$RPxmSR41{rWZ#w-0Cm53V*>nD)NzaoHKAd+3t5etQ2;Wz6o^DTt&`ZqZ#= z@MhZjzDMI8>zDRKc<1d06qm`|>Aw-%xY;eD^Rn@Q{1JMmvnTig-Q(+lX_sk>!DjS2 zD+kvMSLg7V4rdDZWxLt!WZHFU&Mjw^S?8!*^JZhvLF4rf>OLpfDhG9GhVp!vG`m$X z?hML0KX?CURnc{eF!O=!AboqP7$kzx1wJwDJm{uJ8KC!r5tkbXYB22J&f78tu(L~J6o-J4E>0krP|0H$=fsbht_xR_J;Yx zPHb+U64kV_4ESZ+FD^Yyv(=JH$g_$OEJa!@}A|kN3X#!sP~?xVq@Uxm199*7Scm zNx<+V5H{6-_Pi|uuAB#UaHVnTW~~h5A;26DcX7eP+i?O0_6}{t#J_%YUH7*ZzXqPr z0xp-o{OoRw0$?~UYR$d*XEjGOT7+RK*R8<_IL25IaBH!*ity+p>sNmu!_n<20Ss3( z?Yzrfk~b$XzoXW6X>BpwonVJH5Qce@^N@o|Q{aj5hU-YU>)VV{KQ-VoAWH9XbKI~0|)bC4V=e&GrgsEFk~-^CJ>ek#2syh4{;17 z2kxR>P>Jgh@lLTQ2GX=Klz*r9wf{T(<<%@OO9W%MBF=**iU@-VC@qO|x-Q8i4~5F$ ze)?X+oL~6sP7?pIg=|conKG58p|R>b6P9EywC(G!M#P_!2Q^p%BaWiv(3*Z)K_?OD zY&jV0mxJ9XF(BO0)Cqem80j6L)&rXF;KmZ~8L4BF{Ka!$j)M0GF^K&Iqpna7;K?XS zI2|)iVWNllxQdH%J{mpxgKZLOWCyRx>X}kV-_(#|+grzO3F#9ohS$=J09hu`=es8W zX4w!)l6-b~1t0;EUs-bEofHELlL*e^IR$EVeLtzGr^W}T#EBRIUjQevnQ=`3T&dNz z9_*0kA)x*>kkm(|JUg7NBRHw&PH+3Q2~ z5P=zhb^`r0?XsEc*~ifUP3<>g;v5zL5C@ZNq1N^8$qMt#_zO$Y?yqrJOz+Ko4rOy; z06H=jE!jwQmh9`rB`jtDMmQzUvFi_CFo-yx5eH;JiD^YEeq{Zt?CnxFxClpZL>gO; z6oabE=R~$*;g6AI+gB1s0B(k$!ei7N3egV(O9*6Zzq&ajxuo;D2KamqB?Gf*Gp(RL zp^lg_dF7>XD3X!=?J1)72{n$QIInOL0cZIy;aG5FWTEq5hrA-TmLr=9F3yF`1vx0z z9auhpH+Kk2StQauf)6gm_q2=|$4#7t`Y^LV^37b@+m zkhE3j7<=>%ok0UQS=kWNrr5OeJ7`3iWNWEhAVG z9|9obja0!K1gQgsUC-A*{0T~=hYC0xAgU~cK7(r(DINunb~-ASimfn8^A}>Q~ek&+G=rXrCCqu2#t-qay8Y z+fL`4u|O2lPO(Fobw9FoN3^MPbF()?Wpf z2DPMd!ZP#iIT37F43Sx>l&-VPA)lJ_5BxGcGeDgR%29yOHpN7(eCTZgw}*riek@qU z7D&K2LKF2w!EJu!&TfP5NSIM0En8b3E-92>&P)KCi)Sk^-11F+jrq1 zb^R)t{xZ{Yq;H#}eDa@(D8W%r*sYz5mg{{OjB*?CKwZFAhP&=u_!++(?sh^ z0T$~_X%xM=PbP5-U_K+-@XEM5yITn>i9NDBoo0R%DaGF>36+6j3Ii_jN`HO7>VGZ! z@EwXp!NYvI`$a%TR+%YDngzDEYH_OZXi*fDL#a?=DXS1dF4Ow%0QZYt%rw5HZWz-9q=cn^IQv7j6==So z@k0F(vs2-RjAgW*B=#_YYW`xAT3my*!aVjS`$pJ^X*IFd*}kq4UGMU0F_MIVRzX0` zb?ska-b4qvN0_r{CgVCm3v^42UA#6I9T}8=l7&OeGT^SJS+waJgO~crB*{T8-NuK4 z<0mZ<3-zFtNZ!ceNT?Q|fKg?5O-MxvKcs`CV)jK{Dyc!Al|&vb-Psu)e;ttz+~6f$ zx!kHlr+Zp*K}<_bcPNkuQ+OqtUE=UHR&z8&f%gxMYb_f35<%|jy24CEgnh<*`wF1- zC4f{D%quQmRq%-CRjGs}Z3iJhnuy8^J$q+pI>1U*)%AURV*L3!yEiP5)aT0n)?ISi zoA{DWoU86AH;05dfBFu!@k|WezLP%Q_lQ|lOraxqjA0py9xCB zMdkZaDWCXRYKNDW$$X#Q1vvi*)HtDEG@3nsqo%v&uL1Kf40qE>1mg1E#`gZ02Mi8( zeh`B?;Haod0u2u!k8ZQ!IJ;DNZE@pd+Y|?@K5WNhRcDDg_KnSpFJ5 z^C`kRbp*y_bkrR5L~o|Rg)&A;+L8VW7|&4gb9 z{8pEoqW!>$#Ccc*iB>q)iV)fye4|zJh*TRuI>BB*hD>-!VU%Mna|oSgu-=JR@|IpI zYl`fr5WQ#LpJLDninwvIzVeZV5l(opMh7u!wjZ4G@?}9mA6+u6v=3H#}qj|K2a|6@?ziG{56Erl3}3`) zQd)YY&w+7S90mEFgb;-qKdGy=ir!@8Z+rdnW_l*>h2?v{#3D*2Uc(Kc33n(^UJ3_; z^G^*60*lN~^-0rUS!Y{c9Ixr-LJ2#$vGQK*QYL^o=p8+#R1DLQ8t@G<2}=Y~VJ*=O zu$aTGPERMxRxo?@j$F4;Ql2>yR5yuRN{vC@&{!y9y<8`%Ux`CJl*nQ-w&oEDpqK4g z1i_iD&2jQrL}Gbk;;=H=t>4PCi;l{9JpD!%$1h{brixHTJM4)(_v5hv$a2D)6tf4B zVeFnn{5ctYi0pIXZ$3Yo!xO{U#IOj%N1A=^wz9!%bh!3btII*PRZB&=BENdo0J|n> zNnnPXjb}QCCvUR}^E+sYy{~az)006@7jKW1hIuiIcW+(l_(NiV&6^l0sIb2PCN#OD<>48P?Rjgqlcg5TJD z#U~${_OZ!sDjCxj^qA=>AIu*tNbHdyAdD03NyLdQEia3Y1&>NH2H`ojoO~wd=$5ut zx$yE4JhAflpd39*ZHdpN39@25G%S^>+F}{9s}rs$j)yR@pgDvodhf7!{U+d;7_1p?zZ3KIFor(fvlhFvD^Vznnds@#u>Dps&E z7&6UQiY{#4u7w51BVVw3!;DW;s+Ts_Tw|e;pzeMq{n~g6$1!c3kS~k_OzRI`IB@x| zf_RU{1Zf3)qMMtPo`XcamWJo7Ky?x=4P#q@Hv!7iIcTS`n}FeQ+{86t@C=%-^DDfp zOuu^0N1}(ix>3#2LhyKwJ02d`>YaXI?Q4L^c(xRKfvyv>lQwA!jx|nAe}ZAQ7HHM^L2JO zDTHIgJKm^2syBIJg`K?${E4|WAal4O5}ds$T?Vn?lrta(;jjX1^koH2ViO?(V}n2A z1XQB1HY)lJVtLa=dA=5LYD&&ET$qfV1%xOOS1q@7-5=621MQ{xI00JgSu8p6gJ-lm zbe=HH(V|>3Awq(a+0#A;SiUoLUjc9IWx9vp_t?braLQjegC*She<%bz)nHy8CL@J^ zc>k`-=R?~PrCd1w-ki3=PY;-SS>-r1!QI;0*1y@rfl z7XA&VSAltRSOb{9+s_G@M5$}Fc=o%kd^`*1z=)uzDA$f7J%p=iGVx_`o-uEvXat8; zBaewXugaZ&8sd(3znZOiOYKEtj4#uf7M48Pd($!yMt$4VIgYF~pcONBeg@tf#kAO6 zkTwQpito3isgeo$Vx^gpoF>DEVR-XImvkM)xfeB+970Li6D5Tl&noKc)D(1Q9#QfG zwVdeQNA^s7S+$oA`1ZE9=5HDJl(X`+rZe+~5u_g3*>l4r%`f{a%F%2`h}>||(o#st zD2$ffX~3XOmbfA8EQ0?zk;6;27eq>tu|yFv`L7n*SP#OUxE(xW;d!a^3OTBAiy^LX z%_zPMj~K=)7#v1 z`<`X12$+YO0iGuly0o2gJ#Tnd>K)s^awn?+PVz1|)sW-ueJB3K_GA3r&I85WMT6Xc zqy?5C?j*j2Y6wWjqPDbw>P~Q$-gQHzL(E?$f6JQswCTar-j}nJ-M9?z^y1s!%_39U zJKj)E49)r2c6oMIPmpY~YlETNR~leqIjKSnA2l2^Hl?7{LEcwB`%?uDM_$d3?#)oE z%#&3+sX2p3#?Dk^y?}P_q?rJ9Q%Y6)*P9nv1#ns|+*?4&q8FuvWw)J<3uV!gg!uQ` zRCO%FIi_-gmovF3A7XkaWoIlhVw4OLi-?J+gI1qbxg8Bbc>?zc*lkm%d}E(u&|p3W zQxFgV`G#Um7S-lxw;u~S@eVYe+W6kbnFU;}d%Bi(^s|blHX*OC87yQEZNlL=*_inq zD(uT^1znPoWS<3gYJOm{K#DjGQhBM<1k}3y#DLgJoQVVR?vVkXZJH}`0(CfSXpjV7 zR_0wDFAmWoe3nyL2`Jb2)Vp|ttQoTPry)tIw zrkId_JgN{r(X@)DDi0*&YrQ_)YH&ssLMf6Yh?vdKjY01QhrKc2@+Um(3ePyll|o0g zezr*#`hkvzn1r20vzb|U1z2CYL%3;hs%kScvmjfnvxc_~mZH=qI@QnyhKj$SkI%mi zDaQ5r3X#5)cDX+RZ86JmDru$)98lLLO$(Gin=z12=CzYaxAuALywW=SutINbI_|H+ z!>lQ-nQAP&RA5?JvaKTTuPuM4t$r+4Xj)}3OZRh6X)2t%2Mz*m|5#@8Av`)hk8qn! z_Qx&c`2Muo5tm6HCrFV7dN!f&B9;D>W=w%yUbNxb*Duh$G&dyi~A#7FI{4;ZMSg^{5Cq?u!#a+SJB@SD)O&GhvQ*NZd|?($3>ToET!r@-3znxH`WGTKzD z5Zj+cW5xq#Vm&n1Q7nbf~zM?^j0{+c- z*)eVbtmCvkZ_UYs-)WI5fD|>YNVmcpj?e}WOGr`miSOW)?B!2@1; zr#o2o^v$kFE+s)9#T^6nxhLsGJGD8%DIgX#?W?Lz*%K-`sy#i21p1||&E3bIwc8g~ z5zr?){`;QYR)#gpx%f<`_)A8RwSbR5%Dx_g0n&terW*LY6!eE@L`oFkq};9zp&l2a zk-ti2;-;c|dS~kJ!E=lhYAcKJ-qjVzHr0?_B4p1aR1>n`h4s>)j*MriWyDIv+%lv_OGsmP3jPX*7D-OJ=6 zlF~0fg_u-6{he)XS1Hk8HPxu{=fNrD$@>hhZeB(i;M=IU$AWs9X?N(EEs<;vA5x)a z{zZi}D+ z`SRtQfKJSy%-~8)gl@xXD5HNzu>kPuV|y_odtsKc@^$amWZkie{4TraD{k{$Rsw9Y zDl>Cmxy}E+8jK|+B7x{66+f9nDAF~1S^vHX=#t&etTgPn?BhizfLkX(Lv&uW5q<>r zVePlDXpmH-Fup7T6D-6K7V;TFs&n*rg!1AmZ%)PQ2sCQA&@gl>Zk7 zW4V@G6c9lBbwm|hU`WA)RZ}aPma-NTI zYnubAV; z)obUj7qR4FruwB)OT?DBjUgLRwxqR~bv24fU!XEwESvyormcp7c%ak66RKZ=Tntg2 zK7$#ER#064W!oa~T8d5aRA(kpMSGh~OP&(qON3!BQ1BNkxkyIZBo$CENfkCoQkWXo z$8wf0yBof>hg;BQm2R7v!vkW<6&j_q&URxR-fg9K6o{Q6PPPsIRHFEfKK`U9q{P3! ztLw2APT9FT+EPSlSm^u|TD0_S_!#8Xw_NMYST7BH@{RS&uSNQ{?!~;8 zQbc!XL#4koAkOBghi?4PGi4?m`J}I;VuSiwv{P?hcFCP-CJ1~2F^EXmWp5Q^P)Dt zFhus}+j7oWUS%$0v!o)b;^BmbJp;k)zlAoUe6ny}<${RS*-IH!!D%)-!zf5h(E+F{ zc^5<)V%l9I05a7g$Npf;`m~m}p*C|Y4eO(>?tpIZ3J%U=IBPPo)3Q(W#uUGJpg$NO zG3*?$2ZG(`$-wFOKHoy2-RhV!fq12n`JP~dXA*4p!QNx%j10JK^n+YNbAHMAv<6`Rpnx!_kh*rfIw>Sq8AXRy9Oa@QeuhT)I9Mt`b%&b2|Wt*ejcS}uf zDQOb!$$K7gqa>5S$4oaPJb~Y4_OCQLEcdWbJd1a{3ld!2ml8J$(*u zwmPRmLDv}p+7YyfyJ)a&HilSXtN$)5i&iFzVC3Ypd3s0cJ34X7n>+|=I2b@PUr^R* z=~SUH7E_ea)@o;`si=>0aD>8H>rkyU?S)<}cKOz`1@bk_BGqp^Xz~6SCr}EmYy2${>9xnq6x z2CiGB_SO)Nu{zPO3&{wmV%C0>y9^&#R*0VR z?Ce+q{Qe@z8KDQ^5KAP=|9z7#cTqtz+}=x_$10ask}+^&AC?-E@suiR3hOy{+6z^_ z^)7>W5;0{i2vDjeA?lojyt2QJanEi+c+UAxlPB_tH>{~Yg^x)y{iaNTLyb#7jtlfE zx1c8qgjZ7*74fA9!WQ%Nob}FHJ{`XUWuJ~85sFbbNV)hflm?=YSD*y_C^|aG9}jwN zKxDrN62$e^7~MF9Vr+(bC+WZOugzsz}iGW9kvinTYc^wU2RX2aAJl3El-@`4l#{ z9j=^RlOObM(lRgp{n4L{v}qaCgVqu7dX5S zV9XNf^h~;LJy2HR6Yx9=Z>Yj*`N@@~v~czheeQu6$SEGr$z^q>%h77$BF{?h0Dq<@ z&^OodrSBeSg3;>tLlrsPHsdw5t$D3q}Ia7A>?Wvn06 zff?X}!#b$IB0q(jtvE-B-8MN0niUoe*?+)PbwUf6L!&bz)Mh&pgF{3m$uQ4))kk<| z|I(q&#RrWfFv9cWN=z(dF9mZnAz`pK&Qo{dI@^xb#D!aZ4r`|-Py>cg?IUr5HAx9) zoNS?k$QC^1oasshGb>ZWWzQqvsG#HJK)jriyxbT(fgP0=L(S?`;N6`^J{v!7!D)Dd zqy0X$Bi+%k4*~yFnH4w~kr&$vs42K)iw+aT?C6T#(M9NF#N?Me ze{;_GyC8hDrZl11HFtUChA+mO4g=U7NVHXR;!X4!@b>$jT)6BSq$@~)GeVF$?}D*= zg2GLG>koaQP2awoLCCzY z7Uo>lA#YBj4-7xB)BD1)KjoBOm{aSi)fY-Ex&%@9jWg@tv&gYGcWgJOAANsFK57PUL=rb$zKYSqw zxa~bCbgK!+7k5ED1Ujv#4#Z~aE<(s4bFj8DD`n?&9m z!pD4&@y-xyVoT<^D%zjMD+Pu^yLBJiKh@kN0Cg!@pV~$;CUN|8V{N4yXAk1I=Ja^;o-^f$L@TD>td;f+Oqr!;${E<#h0XEO zeUkZU<$LKG2`V%+Vn+O1vp;*S39=40wNz7kqzAOl%Ym^r%WlZN=K(nA06?ZX<_u_N zby{6u|4z^tB8=2$r`dbwm8Q}{k+lHz#6mWD<{c;L59K)Z+$9HA!=A8&*iK~>ptTjyDW%yRLKh_K>OGDiyq-cI}tDDe32tUNrpe@vy#CI0wyX++ZCqL)8#}K@+0N)>RNJ9K#=&Et5>t5|pGe^rqX&2F!^_k-jiTbY$yXWU%~Iw{;X zrP6lB+pg!fqJmhGDjc!6e0KTnn_9eC+ih-+ggfT-6$aI`o>S}H&yp#L3!t6@(TwG>!7pf#*qNm5ru#mL_uPp7+kEf#LgA zLKAis{0X@PjZ{(5qno}AykolhI;qbw^p5oh84fM3M_LrV28(#8CYe8$oA+neZ%Slb zGaAb|6ft}r9}6dSh*tZk+bo%cTSXCq@t$J761H_2O!?B7lh{bW0k@K8{ca^d&yoVf z>9ty5-aDPlhf3NjS@4yW{eCwb?~pKPHRL-pS((t}k6>zaq9h<7(B!gie=EZl**?fr z!2I1o4=DeB=8b{PThYvu6dn`-uLpYRw!TU!ITP(WK#Aa(%0)7lN~}bu$Qxb!t14QC zM~}WdQj(R@@tbP&o|<2%6VtHT-1zmBWnbg?shJ^84AYHw381AE`gYnf82aZ$?|WgL zUx&AzneJiW`iZZi1^p!aa%MbChM8-dp+pwFSXV|M}EmP^p?iUq1 zDthHH9mk0nqsL1-yYa2GOKqbDd^;Bx;k^^Q)yAEp-K~hG%QLi-#8Z+ms?VcgP{N-W zYRHvOIvq$)A)$9ulxRqKKgKe^L8Q4M zbc=H&w2@8jLj{`P8{o#!bO@b&i`C^@7+<*CWn}cccP8|UC%(`3!DP}SW-X3%8ysr8 zf@mqCUKa*g3Z|1{EN|(z;rwg0GYR z{Qz^XaJBcxYQ*APb8!EugW0?7icdo1ok^oDYPgT2q9oHv9bY!l+iymQg@VrZsE&Vd zErcy5$AnhO6fe%+`#W(goRX5}UR$<5j~P;4r`dcO&!)C$L`3;cBz=7r|}9Y8zGfv}U+E;0EQsbR$= z4nR!H!6}l8d2pOBku`=ABk%)BoAfZW!O^7wypQlV!l}gxr600jm)|Im z8F0~b+2lj>ZM_zW+W;F0M5eAWy+@{_TKM?b1QMy#))IeZxDTOHfA5;8`2!~tU(1ch#Fjt}{-RpW^P9}8>&&fomJ0W90402^DX>Yw(*N^`YBk;D%NMfQ ziDioG-GITkq}7+@Tac|XW)*z7Y@!-)RRm>b+$*zAurlnSLMFBbH|fb{!{>k{JxTgL zX&&@?Sxr*6Tts|QJ@_pRM+|9Rq=k;-^3PB2v{)4*x|mL+EK+_eDP7RXD|vvhXonOh zm&RM^zqF~^Q+G`a#>j0Me8{F0lYbgO;p@`5wboKJpgkliH=wwvH2UF%^CXs1oUkaR1{BM#Ny$n!UqvC~-_qZ65ObbH1!kT|tXD}_@R$Vt)Q&e9SXUxnL}D{fv5j1!T420E!sw~|D3 zImicyL*rGA>1tTsr?uTe3teoz`mND0RtE=MEt2|QeQJ&g98bQDv{tZ0UEkZpwQhEI z6xcCHWd93rCSV{7g;Lm1D~m4>GdwHCj|gAiM#;=>+FQUQuPjdrh!Fei;k&!pNu(x6!M%`7ovJJ1>m36C;gg934t z9iO>;f5ODdr4prN_~k$ZBAc9o?YIm9;TYs3qydt=24njVeBdj{5L0!(BW_>sL&@G*6Zp-MW0s$`hYzf*Md~EMmX}TT4 zYx8o;N$WTfKF-N`E%NoXdt>4&Mq8FXUnyQH%gJyUa`MCOXqwOBb4^j8N2>F~)!f@Q zzZ6s476B7U!{%2;t|k=zy0JFgMJUhsyi)I3LRkOklFJ0 z`<|4MNynN8H;fTgklk&QDapImm}yXC{W1H~iTX2C)L1Vw$A5J62Uf8}heAwm;HUFwY5dun-86G~8AlhJO9+-0t<~@3VA47Zr~D z4@-eAJ+Y0>iO~ZODKevJWbJx|^6yDmR{Tx?Q0B4c@^UfKB%wX>@#o|oKr|;4?Lslf z3!JLrS0rdF?vf7S!c}(Pv64!1aH0;&4V$;DW4cyMPY@O};LA`4eES`h4NPIg?q}}@ zmUhW^h1JHm>A2i|?7LZydv}h#aY|nwdp|l66K3pw^f7XldeO+VEh)K(b5vx-@AZxm zpCr+s*O@;!B{_K_OV2u00_|39^l&H~vt7o>1g|ugRHHi6WR#zTffetPgY&hc{du#e!@?Ile`dW`5WN3kWb;}d7kN?#wi zxCxA?vbdZ&75(Dc!Acz2@w936+wn-#&AOvg`x&tfG&vR9M0cv<#c_vknh{U?z~8@A zwcRo?qQ#X)&ypDxdRN8s-^5UVzHJ>aez+90pUYWVaW{s6pR&D0$&m#S|?YHbTE4*ra zpW#oVbEx)ZC`(T!)}(Xo{lhggU!{6DKGlR+V~Zro`a$(5lP844oW+e@JQk)9kwaXw zYW*B3anA-+qd*D0ZRSu8;?f$v{<{H>%-Xc)oz_RB{oWc>DXhw&%D~ZCpi&Gub|ZqF z5$oKuE5lm@hl`I0cTQ7=@%K|%{OE7sO<dqy87jDQYAlnofpQUT3ewMAoIkCW)!uBlWeUifmVuCCD z#wxYz6dtRQ_S80jFkHWSaH%cgkw3ABoA#^62GecA>}9D7Prkl6N88_jIpOwu6;G=o z9$(B|t_8DAzypHxYeEy}|eD?QzAFqA&cR71_j0>KF ztNV-(_o%9h+lPCkSxPY)p_g-l5GX0>o0hg~acfHqfxYRB`N*RsztDMv^I(;HX0y?U94Z{B5jdX63!xJ06+icylHOk`EB&{`vmMkfX{ z86$c}>Krlp!Tl&v$YiMWD}^9&om&u`C(BA6u9i3{IX<)O zWr|7a=xpH5{fZmo3}X_y5Lq`Jx$2lrr&PVAu^j?E<7mCITH(R;vZ6 zDS6|YZ}5A6|9``)uUx~sju;e0p3z%R->x~S?(pI(w^>$8j0|7~2ZftAt`I|DwXUeD zno&8XU)SiUIoL1p8y&Nhk|fE8wu`NEjeb;t^lkn&zjX7vpVn4CR;7@S)|&8n#gjV? z+a%D-MO>_Nz;M3OB1cF`mS<>VNctXSG(LFxu1BgzPDG>8h{a-s+i0_sM5Ba6gh*~u z>g9^wS+2f#oBeB7sGUV6deE&_H3#DvwO`QL6$dx2vRKdAzj4U5S8sFmrJIA?BTFc+ z9+H;@i?eeGkt|Iaxod{+SS2=6iAbV@!pKpVo90j+S!@}f_0W?5~L7hNy=)uWHg-;L%29E zecKX)C-{I&49P~yl_m)>MpB*13lNc??$}wk)GdgFfy8t1ef@LzAV7rckNkOJ1i>gDn0h1|CRy|cWB(A1X zAhjo-D8}OwXFWa&l-4v&OK%`JaFHK6g)RswR>tNns4*y#vODV{c|? z`#>O2wSkywigb(?g1+fd+2H>_Dn?`|in72bp3~zwRhqK5cgUL$-egjaSg&icEWsGd z!}|*|t@-qgDK2PES2fN%jD+6xWLbt8DFQ8RSJ8BelX-S4 zf8~VN&m3J$5qN6nNWu^pP(t987_1$KI{}FxMn42nFYO%e@bTa|A?nY=nrDY+9RpF1 zgI4`*uqEw-`$f2rzHG_&;Qq4B$rKlab-k|e-qUp*t!pu4q-jQ2C#+Wvr4v+)I0sGB zQ)U^it+{pMfO~H|WN(t;UBg><&oI*i($vsb=d908QL!VqmU>ySI6GrDdV#uW0l{*) zL?<~$Np4<0z?kjggN@u}AnMLJP|5p>dR+LZi+7gEAmy;ov3eee&^$ zMYYC)FsVWj>xOU?vhu5xh?Ufg>TCxAq3+zp2lx4~B*(s~_ihJL9jmc{~CZ-N)_AAjlp zhlmQT6y8W4v`a)VjP}NS_Vb^oTCJI%opHXd3ErcWh7geBoLNzj8co-CEawZ_b)cS;HL0;pO;tAo;Yo&jC;0H#b$_=irq4R08c3Fif=!A&J_f9h z2&u5f5R!x-6(pdt3@HLp3D#XpjDmI7V6DTL6r&SXtD3Hd5QYf%x?V9JmEbKtM2t>Z zE!GIFSgb8g=h?pkZ7-OgwbWfii^$Ex3Ey~Fp@m?tOgTM0qawk(NLB}ei;Pmu%hwWa z9V#Z%3@H@edJ-*BTH}FrZzE?9Pid^6ZZzM#A8@O4KKt@jrbSBM z^_Wa@?aG+d%8)5RmM6sEakk@guiT~&fwTFNW!rIjwj?nLwmD;5?BV^8P&yewkq9)A zYqJSb1d3E6g5~Poh|Cz)Rf{x+WwpZ9Jyq2+o6T@UR;!MB-7%R=n9fR!G~`b5&4+jK z9W9Ry^!w%0hHvF0k z0~8>{A(9kBd_-h7dQCP(XfDe>E+WfPV4dTeuiv4q8*Y{ZYge zIZ6uNc;gPU{X^PGMN@ZlUB|WSH%U|Q;nD7Zl*6s@;hZBMm&~qRVRH2Vh2YMcZxXCW2Uyn) zghi z`So9>k{v<_T-P(s$4Cd0Y|L!3N82=1n^Nq-M@D-yX1A`BN(FUC6makKn8O!t@_XGM z;LE@I>-4J%r8Pw|*jA=lij4v998&8+R+OcvI8={}fJ{?lgub`*-B7eFNJU!YWNAvX zUek9i&f1H*30ukBU* zB-f;gpzkDv1g#}$k>H$U)xhGsVN|A!N<*SYxGvCk70yQp2H$sFyR}DpFyiR6;^=6B zFoxO>2Hmy~I0W-mi%B%6>z0t@sGtzeVO>Nk$t3q2j$m3Es;1-NS&ft#*RG8yqGvM7 zxOq5ZQLUNi5htgLW>mqP&| zBz@m;DvlXXCXBL-`}a@iTY>VDm#^=0x@z!RP?RaDiX6-et{je-jtlS>BP6LwQBpHs ztU0YaRN_#HK^V>cbV|Km6TIheHbP6u`fQHyQ2PceG%`z(jiu{anw5krQ?${fdgz;V z<7j(_ZVa9EEZPUm4)V8IE_WZ!B1uq^Ne)Y>oeDy6y zEmE2ga6S+`1SPQ%MDU0Z(L$kwL<_OA7WkO4BkDVR&tQ;s#0&j(j1ffK*fs_(4>uxS zBDHTv)CKqun9of7aK%j)A?JV9e$wpVmi;}htpautkx^9@2In(KgU?XLblW0NT zv|PV&z8Z*lv|gcwfHiJ>TGv`x#syAMHlvNWUXJO{H0&h{Y9Mf=RA zq`LPGDa3om3@#Io7~@mhnQuovz84WfxHy*)Hd$D5Akp3p7KqMaHwz@CH9AQMMABlA zCk+(?K^a4q=Q!tBHx0#jOw)BlC6S3nc~9FmXl-yI&^8Ty-*a$yz`^xv=scyfcH<6{ zus5B7^C%&i9?URl!ue{+%P(KyvtRgr=Bp((c%&Qb)sn>EvJBhRpam!EQ`*AQNKeRm z&<-^b2p@P*ov`)|JUGF)ghV79?oSC)u(yB6EF1IS=$Li1NNp&KoP2LONJIjWTM2V3SBL(CM^RDJ#wnri*)9Y7Qwl&$Pa zJh*n12XDMd7okftMzaY?nS-@-U5B(58$3p7@+2WGhAP34;U0D|(po!siX)knIkxMN zi2^4H-ecRx$@)Op#YJV~C)cg-R5a$9ftMFVoyd^*={&to&`PmxD$2 zrsIr5Jth?z5k1$h?o+pGobM4t_Qx5cJf*5C+O{ScMYJ){8GMLXXDNyV?|URI*Y^^# zCg-*LOYR<7j8e$xF>bKvOmK|V;9hmKYG|AzQ5j_>xV@KiFjf?~!J&`=<_pKNIpWsM zF;Q4jWl%yBgXijW%xqeMlDu_vOpF3z!}TkBoSo0{0Z!&~in2s&i4u{6@jh9SAdrOM z(JCPX#o5`gD7-eCVGbmH*HhauUw`w6#t)L7GL6jkabXmQMDZi#mRcf z;o$*I+talDFjfdpVj|NarqbU~^TrilP)X$SM-fzTFz)!J(E4dANqqYqkrQL_fI{wP3WDW zw>`ZNIH7Pp;0W|#Q&dTW=O8Bid}Bw{cle%x6bd2Z=1*R@USIyZ^yWMakW}$J|_Zq|&ikt_W8KAKv+42XAq9e8!t^Jme4m;UDHV{^#H3;n5jN=cH-Qm1{GU zObFu97JT3LgGJ-BND`i7gpH10s=?NBD1&Z>2`g;2gg4S5Wrk|6-g|rqNNZ`D2Jbz2 zK3JypeUFq9=RH+ZlNALnc$Co?ZE()vtflWdthH!knC|bP5<`rU^Wzhy(+T6z7~6F$ z&*zlmF^AW#l21m|)=~-2Tc<}zrO3;IY*b)FV7Xc`U(V@#!%=lioNB}*VJ&N-4;ZUR zNbq=i)6<0(83mJMN>QY!RI_(*z}3kfzxiMOD?Uc9T)#Ff^0Y!|$x#)JqwRXeMFOE?lxlnwuIt#JWprK7s-Cky%jtZib3G^zXf!Rq?{oWPzw?l)(saGTW}21ls1Z0XaUs%J zPah&KMnc%lGan~*M16>S^2kD293U9+j!K1ANB!obk zCM0P})mHSKBS{SFb%j!arnclEaO?IhUVrnLv-vrnx;5kQU`+7PSxcG-be7XqgQi>{ zkkR6u$4842o@z12x_~q(&h=O~XnK=mOz#~+39?+_Lj<{XXyKARdeEqgbf~&`c~?M6 zaX~gNo_`yw#>?(DQc6Pn-Y5$5wtYyeO;M>qhdykRhfQA@h`Z8icuq>BR&=h%$AGz% z17VCois8I!t;w%4V(jCYQMo7Z@8GG=_^Drbvx>bB*qUQnkUE_1AHg_i;2 z4T(;OYA7_O)UbGt0%Bq?;~Y^5Ufh3yJ70T^%61r)lBMlLKKtMN z5U+mW`*`ikzeClxbiq@OM??Xu#S$1?cY^msATJAqR*bTYwyv4iHF;5xjJgKvnUmd5n>2L83_T}9%>iyRZ9+N z87Xtg-ek<}gDJLc32g;Qfs%$iD+wXeyFrR%t)*=pb?tF>(78vSb3R`qbxS!iG!1l3 z%5|I69e=n~dw%_HabHuAy#vP$@zgk~Bd}2%)21&Cv+P zSxK2DtkxBo*4Rx>%5*$MOK7^5)pALQ9;r08?bmF6u19#wB+C%Kr*{IK=A15C7OOSa4<;muqRbU?Gp~)ul03V~!?#wP z9n}P@S#^pkfD6zE=)BteKjM7&kUesTcf<2R)OU-u9XEHkp1I~N$yg&%acs3;hUDwfjM_S}erc;oDx^1ZH8e6GK< zA$$A#Ear2%z8`e(i9r}e)znmNLtYdd?C;YzEw1mG9UgFa`v%3{gjHRmwWe)q^hA-3 zGP<@w$4HrG%$tgHDo7Q{K|voIyoiKE4l=M1P`SqS7F^iW9}pNcZC~@%ldq7w66GV~ zv}E2^jD+R-^#g>`g!`}a&FT%>*bO-s<87$U})!9!b1%3{L8IT~v@obJ;$gN39M4k05tPjI~@gds_ID>I9m zq~PEOy|Mi+G2;F#6jO51gCE-L2w3k4Q4p-;{9(mxFC}ChQY!)qRR*f2qB?bC zNsjl!XX@V4`pBxYoGmJB?YTOgk|@EtsabYCv6mz|!FPjIZCy7sPGM2l-clqPu5-+r zjwF?IU8LzF$|S68pvRIJ%?mTbXI|bXipW{zxN~HQqQfY~y7QDNT%Sg|)^fk@Nb{UL z4YanRT6pr=h$s}+_Du2utpr`u5`~~B3noP|=)Aj*&Ig1_D6$0UJjbW!BuP#T0U-lv zVwg+|mQ9Zhj;iz6u0dhE2opSXc=lEWYnnPj-Cm8P{ zy;by4(S}Hm`$YTm`^NK`OZw~+^`|5WJM8dnVjBrI#=vIc+(yT@(c*1y3&ab{WTiF1 z+NTI_jDft!HxbgIh|OxX#<`a0At_2;eDMXwlQE0s66Z8^)d8O4qa`oBFys8Z=H|gQ zgp4fD*6bZ>&QFdQ&kC|k;i6)Hui)(DjFZ#q!U<$N(R4m=?fMmz(t~N1QJdePNRo}@ z!oU3h8UZ#rv(Hwn@7c!InPn&@qAx5u#YXC)5`)qjlO`K|`cNzgA@F{vQfrJM5$Ukw zPh?$J=)|CuVlp0+7X{jAvOMGD!4YBO9CLW(kRV}xKBt_F2_aCHCBb{X@r`eA=6+S8m?ox9@(F*N$Gto56nEBn4V2qLz52uy}$J zcx#8Mk6A|W9_bxA7p!%|mzLjTG)g&~&Cs1d7h}5K)2`O!(|u5qYd5ZQe0}8U6}mEVx#5Yjq3;~FQz#)gov#2`uWJGdFB8@s+&}8spXPMI4wXx*hN8%6>JA+u zI!ReKmM9IVz6K2=DQ7$g5G#V@%F+RS|^XV5E5YW=8MMIbSN?I(7sZ zxq6r~HY2|B^?Mv%KjiwgFq(ceT=evP#AJ~WI`$?xR}VF|i}>DhFw0n;oB=K8i$L30KK05CiUfq#G;PcAk*6#R z#zo3>Qi4gaBCuSpXxcShSL5|ixGJ}XG)p+`)~vA{n3PFY(g%m{9RyFFX7nEBZQvWn zYj7mwe`l`i`pa z@KHf3Mf8CnKud)bgAJqiLoo;Mw`H_JOU;(T4ihg(sR+@7j6@d)|MMb8nqf<{?*d8< zQPd2RfnQwUAOE_XB-}+eChT3 zT%D9G*7L!YLJE#fS2!iAU7V+`>(GfI&GW$*-8&?aZnIF*S`+a2IQSBKB7JX}&SnTH zSg+TE3}mBeKRr3cD23}fbefWlN0=g`aXr_rUt_wzN7FVGMak;yoZGi=(_2erTTlu0 z+9N|`I+-vU6-*8evB3FyMdvC=9c$6h<GZ_s9zQ97buThNMN6zGk%+j_)?y!z;|&Z85< z(spE8VN{^f9lh&Ggl4KoEEWr7kc{&TsTB`TPRO&2q$s$5^pLZ&Q|kGO&~Y5_U8I4BteLv{XyN)zTNR`3%J=VLSD3vi7tq>t%yAId& zgXE*@A4#7s3ZKa*h;ERUHUk zk)@J*XDw~pA(duT)g)QO27$*=Xh|9(ePyw2q*||W!P0Ak^N|d;oiv-Oip18b{%Qc6@TRS-$cuZur)luPi7qG)>2-REXFkt>X1N6&$tfO)_#N(b15m8GYNas$qR@sd`Hr zdOCkfA0kF;4$37j?khh3(jHa>qDgt{;flk(lDJ+GTu+RWyy&?)%egiiQKXWQQOL63 z!O5B;k-VDZEY=I=y=NA3${-mhIlZ&YS2aRPtnFy6Lpec+0c#zL1op=XsaABAMFdEd zps@`_78#cruYT%~dw0%R%v+3BI1j0hc>kfh{(2?GC_YXPfO3_?;r^my1H}Fd#{ii`^H$(LBcx zkV?^8OY1C3X?oj(kmO}fUKB%u<+dcf7(Cb0EX6vDNfU~q7_Qw$?`y4v5E;#8?C{hxM!Q;3mIa;d>8%|qtx6zKL*XH5hC)yq$D|kow(p5Zk}M-8aMG+X zqnzc@83)-Ev9<{BDNIV!w~S{K<|ijO@3{5KOSFq6rw@;)&*p=4Lkgr(c;^tkMWpG* z|5-k=)D!|OB_c@&J$oFON81fHl6jG%$_&?Agc6KKCAR74>V{w~ebWv1+C?tMWrw84 zC1y`X+&>9;FL6H7+eqs?UI|9C5y8NNlM07|7^zMj;W*HB4YNs(6pn*QPL~QMlQE0s ziYNq0k#jna)Sc&>cUCOc=cFd3OcV-*N;Fm15yYlQQGir|ifc51E29!CB)|LmDG15B zid0^Jf|sro+&(n)ZO#5cMv!AVt5Io#(FwlSoXlIMlMJGxNCh`<&gkl#*hRDuq?tx) z$$Yt>syl2HM3v$r{J^JgFv<oEny{W8L=j5@agaFG_@fzUzrnaIf|(-Z>uNX~IgfVozqpdn7MT!d^qa=IN5n6RLonTbMs_RKgP3t4`zNHH-MUvqG ztu~dl)0ECbvuZGCibPPDl(q{7HM*!w|-&f}F!g_N7h4iRO{ zpiwUhgcR6z@X2)w+PXraNs^SKdv7sH4GO{OSw;842t+~CG!$7zUZkv67U3+{U%blE zxueJu?ms*tb}ftL659n%PUl>^c?HE#Sy87cSFh}EJdg+SZLPgtX^2;jVfC@9MUr3`xG8ezpCdoji!8RQqy z1!REUSvuP@nv93&u6IQ5hhkL`_C{k~zyB8b{*>u#M&EWkeDe;zZYk58^S{tB>4PJtiduMtQAiI-n$fzU3hR|?S6S5+#caxQ zxuofOQf<&>PFS~?AZVkbGoAvU~HstgZ~jv!GKlM*2$SWjr}rph98eNS{l@~+BKd~fl6kBAZ1+2MXs3fuLp;}H2I zLO@{hoG2vCxOvFc<*VQMK&E><2jo* zB;_7$XE~pDB*vg+&t!7I`N=71S@PAd&6ynpvPsVAa!K!&NS`py2K!6j_MFvlf7MV* z!Ieou<0J&|3J9J?c&aWkDHDW>RKXE+!qL2DI!drBOPLyO9+b3eN5GR{5h76eHT67@ z85pI3L@2C~JUCvFu2c3K!8A>n6b9#d)=h`X6v=2z8w`B|tEI(y&zr|{$|&)v0U>#C z@)lC3v?$)P=jc)miJDFkS|F7;6%is(Oe49;nZLES}f?rP(aFC zN18nD`VzKPmf$gI%qQiZq;$YIg_nZNK$52{g5}O>Ly;C7jy21=#mo#gKw0kd=KW)$ zm590`q;P-T;?FAf$A-gk$+`|W?-(TsrRs6?*bs=?u(FoEX&EJoG%M-7q_L58*U`8@ z>m@dL3N6W`LADuB6gEbZtf#nI^44o}bTkk=RSlqMy1-c-@I>-5VU$HoF%-=6(SwKtH8^$&K%|fv zL*oQ*p7ku7jzT+JKj-%KYfMupCyF2ptEL}56C*|s^U^3)c<1SjW;7~UH!Vq?(DZi5 zNfH4UEvj`SXcD7noJDzDdkA50CJ225GtnwCxk z(s4=EHXI)Avzjk3;|!e?v=8R=(s8;tMHz*S4NY4!-7g77k!FVVYQ=aoMoWQC4N0O{ zyAG2iteTocYmC;ou4jIJhR_O=rD&msD7S&rYEE8cjCF=i6OOATt?jus-lG_eNQxY* z1V8vgKg`2BZ{fA%2mY78AC2Hw{-^&5+jf)(GkQN*;l;im7Pih|twR}2-*${fC9C<8 zuyJ=1n?+dg0Vf<D?!JUr7%H zPsHPcqiq~9$r%+Rg7=)9pAnICt)jQ!Tu0q{dJhj4HP#xUj~vW2Ucu3sr|B%jp2JB= zrWGsg37z1y>WOVn&Y*|xVl#}fA%KuLk??TYV)5isvp>l=U#@AK#|KN}1GDLf{RH|> za<=N}L`PmEi0HueSR@h0;rF=7*YthFhaRm3F8Iyvs)Q&J#-PWF>GctJzj;DoMxa4PsBKFn zB;{U-NF-4NR*gd%jn)buAXtZ&fxWVz3l;gaBUI79{-v-|;W_+JC_Q z1%_`7_{M@^Shi$|G6|U?t7p~evx#=MtDWs=eKA)=<|!4yB6-NnRK^Dc5V`k`oomlM z=UCtP#``|6B*c;Ql!t^GJcU+7YblC~*`lEKHOTaqg7mGgztBtGUEgqie)&zms()#H zHyZVK^{##eB~GwoDfK9Wkz=g8Gz!v5)*vICl-weX=A+a5HKZ}v2u2tF*knnjvl&+} zuOSA!3#1UR)?$UAsw(!I4f86e-g}NsPo$vh8qUwphy(O}k1>X7GNW!CNhzH5D4p@( z@&T`3?HG0~)>-auw+t?0J)5A^_{JB!A18AuA>}B0kV3xI%0}@<#`3jm!PofCCuaPc zM&j)hT?nK$1ZPi@pHnzV_#W?_)2O8uX_SJD*BzpdZ>R0?G?|St;;nsq+M&cKp-6KO zT0sm%>qY~nb0jI~hk?RbX3GWE*)d=V!G5zvN9YfcUq(B-?KMZ8r5Tr!J zNRbz8c3ax6VKSY9l$bonX;0I)%!(;7Iupt)AHVQ&1$kjNWqI2&uK)>#FRK8Pki|~KmYkJXt#T= zZmv0Bt?8X3FAKV+Axc3pDM?bI%$O>fjok@D(~>EJZ8|ck=v+?{pw+mcPn`TKA)t*W zq(qP)wIC#cR{~uX1n)`SqvR+ziz%H5OD8>@NC?u{=cRAv#L77caYT$JG(#Uo#H`i` ztw^$DyJ^|&9o-NygGsy&1KDHl&kMtf6UBuwn)T2j1MZ>m9Z1+9qUE-q$B_Svfj*mZlfQ^ysn+>gq zEDOQI^99pVb2#*5N|9?r-+7L`W#12M4&$1-s05#Ua6X!%&lWV^^5f56^5F4fZijnP zS#Z1GqICv7kmaM?G0P1x2AuQbJtHJSf_EM*6^qN9{kGt6)gcAMWN>K8sUa^FLtmq^ z9Am}-Pw-;`v@sbmMfQiD%s`fFGM%F;&(lxVTs_+(J-E;__>9(Oe0kNdT13ug8A@r4 zNT}$L!9jG)<}>V<8-^|+Oo1|zzVmceP`45vJVGQ$Vl+uhkC%pHePFhjjXs*el9_^T zuw+J1Xo1KS2}zWRd}@%*X}A@>o2}#@{NX$4%de!;+nL0>dRPBuC3OY~wB~IiiYU1Wrqha)5|hb<>S!q!Gp?`i@OD5X%Y1c4V>Lt9Au%JJ;Bp08Ak#SA zO(`K_LWomyro~&X`bdHK<>vb9!AE?(SwDR)rg7RObT;yP(`c?uNxa1hejiIIO^PRS z$GF=ehD7vnoGM94mSxD3u~(ZB3FxdtoWiH-!-1?UD9eH_p3GZBtPfO0fpebQ?KR8U z9M^PX@QD=2+>ECrQlU;}=l4GTh+??}Z4gqEqGh+)^MhaeAp*gdUw%%03R5bk6)`$| zbgZ%|Q<2fifxIa2DKgjrQx%?2$b!_^kyGfHRq^zsQGe)s|Z=+A$jSK+|)a*ml) zEYFv04?AQs&R`VrY*V65dAQHd! z*M5VW*H=9M{29SH=4b2iRK!_a-xCH$f2iqWf^f=70FYwji%W@10VO0c1fm#&Z-fw- ze9UJ6O_5KD!}vX=&JarCV;rBSl-{1&@ymPTf9DmY1O-whk_&j7sQbtu1ZYRywn#n2 zc}F+&gd{O4!*!l2tB53gaJ;@da(k~CT%r$=-U|e2cR}vfXbnvL}R^_n$sM=#m$=8>;06pItXJ3bwu_YBjEP-XV$+uNk8t%QS9q zDuHYGe`F-o}{m1_?v&B2<%e#74-K9YvlY#lU8}rEVVcXuW2)*^mcEG#V)$vbo!F zeRazRvo&pVBy@YW+YR^oJw8h6qsJ5l@i?$tO~)y%Hlvm{CbF+3jJ`Qm_aTn2J9}pg z{Z-VP>pE_HPLtAj(xV%VIZ=04||rE7ZfI=sutXZ9X%1J5+Mn;yFDt)P`N=WHIk4*B=(-eeouRQAYC=2FE;cq zw+OB1Gl^Q3nBV?@`v~(%g{~VW$UUBbzP$c zK4ai9~GT})mKk~B^BhAMcgUB*CZRiBQtV{nkJcpB96HeY6mlBy1 zs1|HJPAZ0=&i3ky6azYyDUBstEay?+i#$Ohgtl;(aj>pS6k5&~@d4vx5 z2z6u8D#Hc1xj&%ujH=4%`j(gy7xSEt-usXkJ-tu7di|Q$cSl@+)>`%{;AzWrtT0?P)_o3BhWM) zha=@=#Ru=7@$%J%Z0k5~BQ7L@^AveTnOBrbQsfGy3_dtks{)e*)pW+)?tsyX?fyXT z62y|=Th7l)CTb)knFL0gk;(sEk-q$ey1TjI{NkPT@OSCY-K zbS;3G}l(RBmHM2wM0Eg78W?tV{RRtOBe^-N|Hl;(5>)ZgmvzcyL_WgO#Ix_2>%U;2J;C-vXM`nvfqo=*GTXqb&L=#wO>JSAcV@99$- zO~}E&mBN@Tr|UY7O+zuAAd$T3$m)j73^`H{*%U(1Q?c@9!z|g8opGRD|A9<`oatXKW4)zEm(TXkK44Uo7aH1q8tbQiOKb zVEaV-pM3f$e>VMN zKL6>@Xt(>(pK=^!3Qs$mBdk!oDqq|%s7v6w2Jo(Y^wcqbT~B}tD@9;ucn z6h*GkROA?xfG8Z6fET0m3mYJ5TAMLMK{Hso-r|y^86*cAX`Q3@(0b^jp%Z?TQuqid zk}HJ^79$h0T+yE<+2s%^hi*(P9U>1N&&ib{i8T5tniic;70W)bf#D@=R902NY;}_K|362-9T04JbX0cv+IVp8dEj;8g5K@wxjA})% z63pf^w)aP@btE~`J^Eo_Qk5LHd!&F&3AE0*+Z_0{Co3}TsWXj3Go3CN$hf=RP!>7U zNyhb!W3#R42hY{*3zmy1gN-Z~8A>TKla14KKZdvvziPnWm=xb2C#4j8edzIZH2PPX zbQn+O6GDC!-~#C_N^Uf(reFRB-=mU7BIU@Kj%mDqM*YHgaQoD-J`;eCX*y z{u4{=gGMmo`&J&d9?EHfJ-Ik`eRI|j(yBqq{F)z+&ZA&547|NtH;n-nqk0B%e zbVvP1uZXXY7$*=^Bn>hqqH{zfsW%jx#PXFT%?!#Wq!Q$MLZ4*_qYzEc{0RI1?@!Rr zIx=0tRE-8U1Z};gZw>?(Q3?L>fBg@6_WS<{|J(oI?<17t|MUm{FMJ$07KX)Q&HlCq zmuT7n22Z9nc~+5^Q_4J}%nF=!%vN*i{hkj$_y9Ky+-)`lt$6bB2efU=)t4_w&SR8f zvR)#pg6sW;Vp1`Cc+S(`_>||LeL*J!#e+3Z-hYpw?YR5$CDD0~bxpn7Gu-Xi>}x1< z9=-o`ly`WGN)f~x6M8~KM!C{kZsTd=`wdCRl1QVJPf7~O_)KSs6cRB>k}$-ifPDO2 z#}j5!2$T+_m{3Wa(4&H+1!UktLgWb(5+)N&tBl?SrXFUkqwh7HiyVET4si4e3k)8- zGouH_^;CuCy^AF>IY>DwBqs9=p(7D@noe7UP=uH;DkCMq-~&2F%1OaO3Z(K(XBAS8 zxYOp)<3nIREBWBVHTO+N-310KXq&+8u^}i$nHv;|qy&8!$fP6;5hR$hBDuuSJ5*8( zC)8w!11dq02TT@0XoO7TfG9~~3bbuMp1c`@3!cmfs;Nflh?E6+o|Aw|W@JLr9z4B< zyiAnSjMbT-Z6Yc~L~!&yyt)gx{>ZW@$cq_a@N~zT)kTFcir@M8jL*c5?NQQnmbvk~ z_tAn6KAf_eYm$$M5Giu_;Npyn)si3m{2HMOhPvf!Y8d(+L{8Mud(SXPssIKxhUUrd z=0tszzRbVrm+>#Hca8eH`rcHY=fn`kCNzq%$&wOvqIkS%Sl?g`-%QirH12O0Sf21rY1n+@_mJ%h#IosSd!laVx7he*7AZEvRP*4lryi}cI5XJ9nLy&;sm zp~|H1DG5d<&;o>1z}Se78<;?fk$TvlPGTfNo6|A>XfW<{q39J)K-`~AI(|5*#xow?`iv-f8QcCh(S@}gTyi|p@P2Pv!8 zaoE-1MjxIuRc^yx4x648Ole(jJAvj&eisHw+~R+PF4j_J?z?#N-SrDcMqFPngU1&< z5^Iq`icwXYwyi9^13mpoeov1q{r3as8}S_u4tOfI!z`sEDgMeO#AAWD{Y+*-cc|57 zo$~6v_ub;tal#h$O5+?A6&of_J_b#P)vgXVD$diMPj{YUV+Q6@d&IKo5PX$_zqj_m z34FrO+f<;l9c%V5{IWC#2K)e_{bJagN&B*D$scT+zsMlr)f(3u#6a6e=|@$}m@eYj z?!>lhdDYnB+9|$B$hOpU@;8(GxR{lQ5%4sy4|B6)eVU3PX8}>fPYLC+DGsNpIHgZh z&(X;;7f)^8R|xBx$zxo4YZ|3vgAk3h+mdTFV<+?tC?ng6LxP*cUK#(+HKv6)d6TnE)7GRdVh~i)-&VwHJfTNntxw%!EMkHbpLijIKA#~QC_jQ+YIMWp>?~lWrTu_xE&tS3-C@$QPZaT zd=~j(@$_(;wp@^OiXQ#rM;Md&dTXqm^8wTQ>7Z%eO*7-wXXPi}Uu!Eft@m?W+`0e?3n(3#nnLN)6D!6 zbVe)sF@ZVioO~%Cq2Nz+gffqpm|rqKMs51^_x1=jE+XdzU*`xJ;>T=PTGzs@BCJl) z$HsOx8~RnE_pBpn-u=fkvu7vaA`!`03<)aoMF~e?dI`_jW_CzX%PkEd1PSq@=t-1t zGRDZ`_Zsv~pS&Xpxa7E9>yWVUBQhys-uoMbPWNUoyDVWDM98=LwN<{pd(;&Yx^|YvhQShTv6TPxqw_(g%((h?(~kOt4*Kho~# zplP*9A)NZ&a3%5e?jjWk@|lG8YE!YLnh@Goq%+%|(C5|{f|oU!+wp}^pPtZ#N3&hY z*9;xYO)cbxCOK&UI!-!xWLpK#os}OV2T8?~ek5NNN%n_E&b3N4HO*=G#4u16rkeM= z@wy7MowBKv6bCSMrV=ucAax_xZ@GqE20o^djpMs~{Ej@_OVKo5J>P<(Y?A@DYBc#s z3{z4i@^wZi$jt>9I~2enk%x;o(e zPyW=oiE6*+6keyv_uUa9wY|D+K!V(*nwv?Zqc)L8bQ z=Fcd>kSrhM!gTi4|J@s*?AvSgj_Y7z4ms>s^31weDPgIU-?>99<#XB^$gH z3Vks%k%oJT>6pA%*BKRsTwKL3rCJ8Xvo$lxD8EpXD@bB*eR+jBhBL6MrCQA5t2rEu z@N-`*v{u^8Z(pSM+B)ln9UMi?`3i44hb5Exe-L>Hwz4L0t>1cqbWF9~cuZ8iI%6!fK$5a6GW2E29=)V7adEOJ1{eW2B)l;6+P zP=~z{nia?ki2u9wFaI){<2|qEeReu{7*KJJYBxPh`8mOAO~evZcmJssplm?}b8VtO z$!gcF&Tct`$EDg;n-*PmRUIpXU=y$w#2o*%47-y`^k_|8&aW>)VW9wR!h zI(KSYwq|VlHv8$^nqQ`M&i_+i&n z3U+yY+=LDr!iYe41os_(P(KN9jy-IRdipTx3M^p5gRqAVyBcodD6s7aE76~zC>mw7 zjsx_pMjLnkzH{e<^wh=0ccfnt#Tx5JU5XsIO;oAg*!@u%g!R4DWMGA5S;~Z>>E4{8 zBk`9tkde;B$`b!yocF5*@vKDqlJrDGWx+RdNz-q#gS!XW$r*yTM=8n;@vZJlu9z^B z_>#$DO&n5*QBqRAvhXL0y~}mT>L|H@*SBqiOfxwD4wNH9VqnqJvG{@5@zGjLB4mqU z6ZCAO6BEI75PkdR14n<@!n`)jpCE|$CDFe z>WjM&I^(4ma=ANxgnh%KDb?HMN@3FBK+I#Cqt4}BEny|q1I0fesjtJ-n@pdeiVP_p z8WCe2pI}*4G|58~*D<42OrIuAoIK=aoWIC zzTI!yAd7c#Uv>X9FmsYInlNd2Dr^aO7kF#@;_zV%92qet#t zXmJ`*`~JhOFSDpbH10oo0*9?R3`f%=XNf9hW3W|aqfcTlw-=wep0}@P?EkKIQ>@NZ zm#z%Rx81Odi|Dvg>XiJA0X^096;r|rdWj)pDSJ09%j7S*TRZD6y^ z7+_{%#)n*F5soF?`(4>$r6b)lAKe+0i|SsxTRv*O>~u)kWaBch_@^@^FsiD)-66IS zPo-|_pXF{Dv|sA7ew!dCJ(fo3hq64O+gXLlzhRaCjbrCxj~wPhaq-;JPHdJ4vkTSnJyR0g1gatQbUm>Aoil5bV8~i=fVhYM3HU8Y!QKtBkF-cP0HbM{ZC(S=1!ojb@AqeV0Y>jk^!g{7KT~>J%65%nn zL;OiJd?KGk+*OsS>?TqMw;nP4ec)dZ2shO@b?F*=XX~hJ#b73$&N&*qXce&$RNx+{ z?uFc*mHd@L?TX9JNyL|C7ER;B>Conuy?L;|%!C&%4SGCe$;G$|5BG^OrPJX*Jq`#Z2$UqIs}EKtvp5=d!D%v(OCsK{raoM=cm3KIQEvzMEWi|@8vd+ zD2QGV4HQ@m!Hy^+OJQZNsM{Pi3+g^Re3fKt$6k^F(>=~9rbB~HX;h>>9Hnc8xS^E| z1R$u5D^hxBkk*MC1SvIWRwREl(o74KFh8n$ZSNJAk;>D53V3*dn5o;-OdC)0sz!Af ze90z~Ll3`-)`&>`<}kS7oIu6pl9@)KaLmEn_7A2*`bJ= zZ#=U(W~Sy5DkS0t{oIhj+Nw+#$!uVis4uZTVDSh0-$zzC493N8`otXsl&a}Aai{eX z$`~GJ!)NfMnF0GKKG-j4EJ&n?UA<~Q>SYu7QR4@>`^Z8*QG;zyzhRWT)!^mDH)d#H z{;pO!6PYA^oBW-O*Z*Wtw*p*7g|-s-7jO6#8JX2Cf%y;;9V3SrX^AZ2ix*8e_FKt#2ddf&I%>hhXevl9Xa39(l@u zN7$IK?+sNB=GFzv%1v?cUaKOVRDCZ>5h2jr_7llPsAp~0C*^ozibl%)qdcbuh95Jb zW&tWfLP40+36+tffqF7tUa3K#A%W!=q#-jZAdjip+O|MQ+p|L6~! z4yg8x(1-4`_dh-}K(`k}J?KKs z_TLvhS9OSP=TND#+vc3MIV`QhQeClg@BcRRHHE%>Plu%rHqf)j40+Ioo;zK>jK743 zxW$ulhEc!IGdysR)M|3eAkXCK|D*5&bqwv;R|>P2&yaP&-js_&@J-{E!F+X7^^5f_ z76ZN2noIhmgTk3XcTYS2OlfuS$%%vd^Vhfh**Sy{0z(xjMfn46Adg^Q^>mv`+V|!U zVX9oaQSji!ngBn!R;jw=npI~4wOXgdk*}|J;)Gep=Y>^LQ^7$mpo?B=Dli!yoq5D& zcZ52?;edkv6>J`Ege%N#gxiJA6IlUPMi1{m1vnz;bmd{`{c4RdmpFAIgs^^rSoC7) z!|{T2^`K_ootIL9kSgW#dfp4>p4JP-9M00JtKV&Q9=@feyZ@L+i*@Uw#d-X%Du*QD zn{1rvUTT;%1{|F%$tX9se(?oqoyA^Z`UI0{{ZCtu$k{$+NL#Nc%49$O*MSb}nNFDhRPup0J0DJhYF4@Vz}KqHKA z-hv*RAh*wYsmb7d5YAH;y?RV9_A^=Nk=l;;Q@`C2RhM-}={OcbeS-yONmZ zs9ZO~(S6aSX4wB$NM<#U&W zvPQZta6bA<^=~W;$ICmyJjbni_I2c=v1}TcqVo@g{?(hb6C$&XNWIr=={E)}D{aYU z71i~BtuD=k55rGRh@YH%xZ&-5$!o)h11B!oymR7gE-IQ53ZQZ6???nG~X`-v#OLJF?g?5MZiI~_}PU2 zaOcPDnoL-7mFeRm$3zV>n|)*3lK#&7Sw znt9CA43ww!EzKmLxCap1|Koz-VL;w*Gug z%Ymg`B01Sd*8tYb4&llk>C2_vE{< z;`ETje;C?{Q@JO6=wlr<9_BNwbH@HXb)Q{eBX-j9n;Sl>ba%Z##&o9?e-VQI_emDf zpZ3WTNG`LnmxF$hGRRRWsU_Oh7}D)6Lq^s3Q*Fc-m*G~+jNXYneO}N5#`k&O9%K&~ z7lYMpIO@2{gc4m&v{gMiGE$vJC*Hw;#OUrrv~03nY8!-k&p)KTgvc}GtkA|S<<3!# zRZ@s=6w5lE*!~`$pM#H^>7jgUwc6$Na<+vguT7*0AbeZ49{0&33W(p{qRl8g7J{Nhgc%jXL3&nykZ0s1>*2G4y%SC*-47_ zh2zuTO@d&okgZ>x4mDOm>hG9TA+goGuQtBg^Q7P-yL){hM745Uxp@p5ne(jJL!m;x zngF8IP)-5;I!W4=))uxXS$!Df#{yjwMp5~V%6Xl2Eg*tGB6S88H0(<-#|rRoyX$gB zzYP)Y&G?!14yB3mB_OimvoonAOIFUksz;2wcTamE-+8Gas5R#{+BOFtv=)x84G)rAELu$EsBLIiS$OEA z7{#>CUx5UPU!|Y~Bb3YkSpxPnzd43Bwy0<-YEak48B2YIb!bz;!f~yO%4&m<4=gH( zh=icX_3=_{SQKKwp9NP)y=0hD*_AKG3IE2TpQ%Gap$vf%vKRGPzX4%P;z!7+Mxa~( z^_xe#t?*=K9d?YwBUAT4-#>SXGJg9E9tJmtWPT^qie3#m>x^6ih&DKA5qUi8tHpK;imYTu-ZvtOw?XmFp4b2z645RMhR_n1YOL>X+k!XIxR7#=1S(5;m*6rXSc0$sQa_I z+9HcQc?Jy0a$|5Bvr`=K3vKKKsN)MhhlZZV&$SLT(zr3Kr~Uq?TSNs`W1ToX-9rse z&rv)jSf_3o2VrSbN_@T8o=LnK(k{T47s#ZH1-l0?T14~>CPG;NA8DbTy##9?eSP#) z))Iu(zi)XNo{xP{vNe^XOXrBi#D9TAR?r!XEseOUVnfC6b`L(xe8V}$8hf5?n({VP*?A`*MdwoECVUM4(y~LW4GVBsspC05(%xGfuj9ru)Uda9V{tM$FMWv^hR% zC*03WMsTKt?wCIdMV}*y`L{-5V=F6edoOrv<6Z0G7UF+f6Z`jmKcGrnr0KjJ93S|! z)xFK41qZJ$)Y}PtW@2Ck5U5QzH$_ISwdRX|xomZhZvyv!*@l8Wd_t6=St}~mURH{J z;f)8NAuKw^(CZ>P!AoQYl+rX*BcuG-c=r4Zz1vb1jsyYm&!sjsuMVWV7t=mP;}f#5 z=nMBa4c8uv&C(brvEiyFCWPY0c!+fCp}M@kxa_&P8`KKzJ6cY!6KZ?u4%FL*f#L_TSz*z+IgQv`a~mMV;(^jtI`wb&BQN0(dH2<$Y`e%HG22g(&EcoYM4idKPad}S+Z^OuPBVsw8gy- z7DU6iI8!D!6vpoh3q9bCo{%q_QcFwJ{8&42ARB%geH^Yi5*MTM!w{84yh?9U-{hu9lU-5t4M9*PEn54GyRzaxL|g^wCWnOIRtjkxb*YF zk80VSAq_O_DQCVR!GnzTkGl(rFVFpRhf#`p6fSfV@{@t0QM+GIA4qesb8J!shNF3) z+v2*q6QaUn0i{6S3LQjFXNSUfk@CC1*v@O8MVjsa=KOG#3~UEwOkGpTM{{@n4QF#x zVrI;qwHVcUS0YW{KX8N9%c)11z57I={nbgAiPrnKGtt~&7U!t0jArq=)Kzl%&Z)A{ zlF8$TFxZqB|7+Y6Df!qYiSdjzzsi^uBHb?Z&-|1wLNd>rp=!I5Q0y)LsM^F*JbQme zg?m{IOi9_(80Y7YZ%1l)w710j?Dv0l-$iF>sG>k&@q@E_p~X6(^=BCTgmJ~kURhj*YSXx<~ zz6Q=}H}*e_a&j3oqSR|79eyfNe`6IiHCy!vZnEewtToA-_MsuuqN9%l4Bt#|R6GBX zODxouCpAR$w#c4rxv}{u{(cVo(e?01(3o%BVfHCp9aU4`KWMy%AMKBc7(q zE83%_L429IqLTZ+cJA*fLCkTz;Wz0?|0{cm#ydHL=Oo$N;$poL65MT!~#6S z%;su5Ya`{K#%rzrS8G?(vcT62Uqf0bF65)Ql@-rRHDI|BZCV`&1{WF=Ve;?)uwo>S z7!g1tBM(SIFHR-a$81E;%y^3`jp6ln13Fw!HeO!p=~WsgJBNayD!*>;u}NFk^0yjvHK z+kk(=Zggr1{#g)EW4(HGd#f{ca?ITMbVgYswcv$q6yVlaoJAINy?_&u2KM7Cad>y^ zq|QpjDk%2K;DJWpB3l2ATGmIa!u6iEEf1GniKL5^s(NCZ-u{Cenfv-Yop*!v@8Z-5 z)98&=ztU%`ViEB#4aNB2TML;|U|;2)PNNO1~ml&_31CMwV)2jVaLe zr7f_djQen3Wd7&cVv-0dGaB%d;9Qs$&ZU{BMQRyj7Z?o6N+@rg3Yu2G==`1gVsJO# z^u2=#aG?NcfSs%KF~&b27f)D+-^+~9{ivc;-zDiPxfOE`<;t20^tad$txggYRAp*r z5h?2=b^do|K%nV!V5|mFTVH=PO5;PigS3*pLJdK=@EGTZs*_u%8CdUr&N!@~Lahz& z5B&s(3Wh313|E@lKW7XI&*u+eVn3-mWv9??oPkXo!0Eba{!`)SB?&r)2I-7O7-_H-U*2IN^ny*XJ?njMxfR zdYYJ+*2z$>EW!T}-)#)@>ZrMcXX%wU`n5u?f&Q$V;Gn*RySss>F;BqUdDvxHyX6Ol z(k1H6L~c7KfJIDJm$YF-@gf>K>ZNH|1cbqhz&&@-tAE3J)EZ;S%ysw3z78f&Bwu`l zeqM<3N~wZeQ7fHL+3ETL`C&A@ZA1=}^gtL&MHS~eK+WQ?&gXWrg^mdTfh$o1>z4Ez4kkiOM#k4lUau^Ojf|#_Bol)p_{FE&x^QWf6Q*L<2=Ax^=29aq zMa-rfrDj=D2@id_C+$XJDS(rCgk7Ms`xXke?_$^;W2zk`0!11UIdQudgOO9uj;Zuv zg1^R;SHk98g#kUC?7>=yREjZT*7oP7GU8da)Qq=d6*b*^JQp@w8zUDCXM(5E+6f zo|xQ-Z~j#gs{R*(LK|{Q4;!GbqlA&uqajQqtbt%|R*`J7q>#(Qc#(fiRl@2QT!34s zX~i;E@%7-g+WB4&s*CG_727NQ z%=KVJ^(2whDfZZ*)3rKF|5dl2zT_>N*J3F|oBk##j+v`_U zVoG0p&bMvFj7Q>_Bz6sNU2X~%PbqB42z(+7Ol*7ZybWXsHMG=2@@bzY}s^IX4O>RlqHN| zgJfTk*}cBLp|T#Dr@aQWBe8dR7xy!qrXhDZ^}VSNi-B-(kXnk(;jGj1DPBlk)v+P` zhbmN?8<=WOw2?-bfy6;MMe?Xe>K}^W*hpJ2qT1LJ_$%`dH?Pv@IQ-_w(~eG54u6e5 z4Y?+egOb-Mj0ZWg*JC>nwqN+s#prl0-9Y%0w_|QrL*)+$L8$v7>NP}wg0tPu=EO?W zebr&no*|fRcUfm6pmV9vQ+q6aDDt@D8WgW6z=_=g@d}R9N;S9%y#B#5OxSs4J%}$+ zOriDBnsB#JK~+iL!F0LJS|r!K)-(kaCW3T;)Ob>UPPo`aPpq%M@ej8IwIytL1|>*y zO-S(^86**IFu-NiG2kCiYu(OX?y@l+>J#@fv0ioT;`!)r4w^;#UU4bv_ zXew3xBj_w72|s}-kJ^8k#TQck$9HRIuwUO>PPihb+nObQgWTsOR0>$o$;hFWg^Zun zP}bc{wVNg`TmSz-&`d(>1y?BJe;+^~VPK}&?qJe>!*sRnG*~DPvBY*Y+F(#DwziHg z`)`oQlDT+*&x^^Co0Hsm93k^0?r<7OPj2#`#jZrZSE^N@76!Z=7Z zouB|>FRV@Cxpb`ld>iT~XYT`R>3S=EHJNJp5#K%uIRIWn{ z&wG;Sje9W@i85nyF>$nNV&5#vE9Hm^JhOs-D{2-~`V_>F1Zj8;};1+&6fX zEiXjl5>EGgIu`>q9||A-oCNuJ#NGdE2bke92oJV_{;>r*M?gt=;){AU^O>ossVOHf zmp^a3cJ};!KempmcFZ#*WDKYoLy#fti!w(_C*s*4U&+YGt(x1Kw}94eNKBQ(Je`w9 zX1$Zs)Dw40`r~xy{rG9OCz#qE2sue9=@5P?{!$dm)T`LOs-R)5R`iK^F3OdCFgBSZ%+UzG4KI4jA#0?!ise zaH;?+F!y_OtZi%{A;9maYcRF9QOOX95jI_vi6)w>3Efevl)trDhVO>^X`Xezrad#l;ca|}UUl!&+ zP1<{fo?ENFVQCcb`z!BI%R3%+?SkA0@)*euP}7Ez$*|HeD>bPP!Y;OflT|~ultMH! zi?)$0M_4pPm3Q zAk!^4NuK^oz7v!04LWQB49?Rfolxs9IN1G1t+#e4aJndyQ@bd%8`OPg`MY+{eXy`t z7DZinbh3PH*dVYXvuI?kyJEvbhcQ96fWH@|(%NF zx~3k^=-1|2UKgDjn)i+F|G#FGi)sd7&_bC}k|Z>9R4CN_UQ3;=tmgy|Hps=+Wf99unHJbGD*x&qt zzL{^MQU3Q0DT47#JE@Ma1OiQPC1wYtTi^&p+Opofsta_h^KU-k^`kj>2CJ~32EQk2 z>%V)ItZ_l{ep)aO5wAxYra}LSS$U=JU(&w^3`=KwOF}E6F?%#%l8DFXD-T85h=FT} zeUfeOj_fuN8q%4^au7-nrc-|{7YMd=37v{@`gaeZMw)h%r$VY27-VA^U59wV@7>HL zvge~cV-?gP?n z(#jrI?BB86v*cYxsd#vK{qM>`!^g1smG3*sq(2er>wmg|S5yL{^0+6AvL|3A^dz|Q zXC&VTXk4K=IoFDR)rsrEq0iy5FYypPA@+MMalokL+h2Cl{`mwQO=ikmz$$~spwiY8 zH~8~w^r-HHl>~B%U+}Oq@tn7)-|qm7ZO!b|yNC3y%kA%9E5AAcLR?5|?>*3w(SH;b zH$!3U0Pnbg9fkBycw5U)9w$B4)4jwyN?Wy?UOcDuN0a;Cu7!8dEi7RI*BW+EZBN%s zEv?jli#WqT_pDf47(o0RtC=lnGiW_1hRe~M#N<%ZmKEH^(+ zr55*DsA^#43GpaF=R9?G9@hr5>SOHO;70{)oFp?|f0ea7KFwSefp3c8v3Wg>SOD>e1OW&u|K; z$^?@HMA*U16nOmcF*y=7PBgin!3G#65tN~isw|%9J?;#Jx(4bIjA{cdr`?f`o^}OEVdOJFFc> zm`7H|PS%~rQ1G(nDh_iB{!HuJ^Htm6hw9^-Y_yxIkWXJWqAjQav;4dpnpSKWF>?1q z6Da4b*oclTWaGyl6owj+kI`+n8=gL*l95SetY-Ly^nzINig}l-(rEbMeTkHJaaSlO z;J9JobC|%tE2$WGX`%^_tx5YY8~*Zn*LM{Hh?Ql;jBrz$zNFnROr^eyVp=l;;}BQ#=dZd#Wi0))j@K-8 zXgGoZ@LO~Nu?}+Sb|z-46Ai*?3)pl>*-weQX6=k6ii<*Gh~_q6#>sZ~4OB5o-}w=6ogA0K@SrnHucFk4hnSK`w#5VqMDl$aNuT|O@+2+W! z<^Pa8FYM}##vuTqjG@B@#i}`s7m@K(S-ph~YnEk8NiKTBOI;iiucGrC(#4ERD6*pL zY2F(#T3UwAZ=w$e{M^{T68?(E>vy%mQv3%!3C@={3e@HX2uYKoDy(`22dX!uR~`A^ z?-CqrhtCA{9{VW7ZVH|*@P>3B9dXz1!A8hMJ~R27ew?)u%fxRBxWY<7`F@2(hUBtX zBO$5qg|ZuxswV5o{hK^7cpQjHT@0%$YBcmya-%3r=i@Ieueen@HHcvMd<@K9JUwa5 z68cDxQCZwHAw-)A&I~JrYS>aBeR>^(kL*L*v7s>)3F^pW7t)7nLgxdQC)zE`+b#H=9cfX9sNqeR3aQ}mw(?1n2@{WlYouxopV zQ-%V8Zri|K=J6J=*?OfCd(Y^H;kpWZ)%A9{>>Sn1$q!VGpW#;1{M;Vb4_S<&5R!#M z$tnB{eHIYs!m=~pkhW&&7Eqn>;j?Id-Jc+xWTzer&$WK+U*K@PGe( z{7+R;PS5rtr?pk8svg@#6zwy+QtT^9F`zDkXI|%Cc55CLjm*E!)T0)@o=t<_-vF@# zeb2EtotLgL+`?8+I7e0nS!WU)e)BxZ!bLF2U(9l`Yh7lE*5$g(@T-|eYD4_hD1(cJ zWj=Wiq?HSm=*$w@$QQ1NfaKvk^6_kY8jLY~Z`L|r|LzEV{pF9}Uv7#Mq7!7OgT@zk zFg1SJpnE*Q4`KoNXk1Y-4Pl01f%_0yweHaa&Uihr|D~TZ-;r$Kx5-S_;>gKVX2NzF zzM+rA)S@au*w|HetF!DbVx7(Z{oFo2PV6?y@mvxRNtrd0zchhYXK0mzo5c_pyr$;o z@edD@ZT0Lw{DOm(Oi5w-gMnJ`ji|8J?03I{d#ojFewq@YwuL-pJ$>l!T@cSR3k{o! zt*gKQ71qZyvl#d`X;fh4qO?Gr6Gv~eBJ3J-7*YTYH(@Heqio>yL`=72bc%)pN=&H( z`$f(gva+MItGjeO+v^qGbe0tfkF>K0y60pI^4$mcs83vUOdWeaeMO^H2e506ooH(E! z5tU?&*ovJ@vNGvrkrPh7D@?CU=hQ}O9|Q=S2(uvZ=fhQ5)RVn;aS*Ie^t`CX^6*g| zWnEt!qjUmYfIoeN4hTt%s?JC$nBk}9*kjks3>az_4{&GH3uK+{k7mw7QC(nHITH4PfIn%T8<;k?RRj|Lz{sh` z;3GvVOm9Yy7paq{^`3B!nEkXC8v!?QqwhPD#m!17RlENP8CBbSBk`4s3-vA)%mb;T z8P5k*zA8%An7oZ#(}or#Y;3G&@yc}C2$YK*KIp<)Y(+S0e9U*>Et%K0BaUr{7w%w{ zL?iA` zY;bx$iclQfA;Gqe_i+iCR8b6hzW(*rY?X;u);#EQ>bD>I=PY7hqI^+Jp(~$VQ062# zq^C`Sib&$g(BP1ep|8gVp%|qXBrp_1rf*DEmw`PER7PT>vtH$z@usZ#2{`Tmo*25v zHS|NDcLFSf3BMQM4*F2?(IEbw*063P+Y50XRK}W?b~~sZ@KO=rF2jD`?iC`t>k)BdCSZaQw`VxjCiI z`Uk2xR3B0#{#WHbg#LE2p(%?u!x~6`3g&5I4o==kb)y4?rqU=OkDGM5q^K7Q@vD#P z)%@igRn^r40;2`xs-TH0WWwJ2jhse@j4=du55PpX4Gh(i#0LKgl1s82-Sybb&tOqtkRWeNqx79ZSG!VH=Sg+s4kPMkt<3P1*fq`x%U( zIMlNG6GiC9&t2x2t1t~aYu2J8aD+NL%Wfhv2vZ{+Afh>OLsfX}vWBlBx#B8gL5shv z?5W3o23`7%If@kRAulTQuYv3DV7*grzMbpa!; z^tlOM6cxVWPu2z-lK|X+S=NwyyYbttY4!AB zEGKTjiu_}(`6TmO{C4I}(9gBu0~~2e+}xt3#Eyl6hA#H71S}_HHCue5F*HbxF%O^R z*2oEZ9BFJ^Zz|L%$P0x+4A{#bXtSwveVGv{^3x(T*$L#?mz6u~cN8rtn}PBc$(gKH zFl!rDWQ%z^Nue;a(3kMVhp5oU&%PsL9s)QI?-eE}biz{z4#3KeUILIW4A^?pgj5}w zX$2?h?M{II`=)e7q`Yi`_Jk`kO+vDS==n5qPExphOg#KT(bZk2X7$K^5!aQQ@eerIkHZ(SG3n$|`7nlIY#Sqwkj9<*y!KMy7MJ_! zTl1i{UI-kbk`n_~tiI%{~Z^5I;!NBWI$B=xqaE9;@_H(vRKgvZXB zK~kh2DxRxzR9BI*eUY#j`p0)@7dN%IpMeO6*U~Kpf8E3nrC6Q)sh`}L(8~xF)fkb5 zasth78PQ`*f?;@J(kRl;S9DVWaaylLu^&&-WfZN8(C&M*q%AvFtzvV)!w~tJRXVqo zv@fWiOABtodPh8g6>w&TgeP8kayew2?rD-P#g5`c#vfAZNh{s_2dBmuKIz5HTW#jn zxoCABgdMskQR(}*fsj$*=3=Re06+q6Y(P`n=q~3CKSdj6$6|jsIe%a}cKsNyg zbruOxd217wR~1qwRFebzF zl9}TMVQpOgr@IV`qqdK^d)Oe74>iSUeVty3^r#oxId$yf@2c<4?*!2h|G8N(Kl>f9 zH>}6;cF;$5S&MAnT_CVE)vItQ$^W6{Qv9-5JAO{=b$(OAd{I9m>PhE%48n?-qELcQ zUu_C2$?K3F;;A&uS{sQ}k+H@8^%78GbhF5Bg7IH@sCOJCjbaK`LOzBSrn~9Stq=R-Tqiw3g1bXej=Wv_y-3 zpD}QrBhO4K!S65BJFGa@n8RJF!RonhVZKPCjhvBO&_*pKU5*I4%P4L9OYDprXfopN zpm?~W<|$sCp;=1Z+7*|LjQ905hqy&k-I8?1X+3Y%z&Unq_qNTLSL)e)#SX%3>Fn++ z9Gte52C87E_c4@#tNm3_m&7JU8`3DU9(!6QeNXDg>^%+LK{e7B7)Gd3$> z9#X~|G8bP07GQA&?yV2G4s9(81K?S#|TT>)Z_2Foy8!vvbHx4*P_l&AGK8vrH&3(ERK%H{6WtW_BM45EUmg< z%2tBSg5vvgJE&zX^qPvU$lYFw@*z5=?4(mg-mP=l{mwq&GSR?5vZ5aIDA%ectRJB( z;gtC{m7a(L4Y()+COkm%qA*dZdG$pV24&);O~A)GVPr>^OJ=?$mJ5$@&2s-FLNT<( zp2p-Y&8o;DtY%`V_&uJPhT$iKCV7RSvy8)=1ZT9Ws7R%&IqWQdCi<4Eln9KOLIs2@ z`qE0xWG|%%;*vDgY^SAV#9%S!H~ae;YB%Pw`YJyW?>sOqy{EEi7mAqG&mk| zi8$P+JfK##8>yiAUN6jR*2(BMK6(W(n)tanHLCb29>?%j5I&x=nqNmM!k_iW@9tPy zdt}qe8v2U&(EoRI5xdF?N$(Zz=1kMPI`^$YLpHv<;PK55yx$(CdmXO z0YENBGWdE83$yAzP1*v?K-3_aGj=ICVAxrt8;8cI^cJwz{MQC3u|gEfwcC|BQ#VpZ zIyzi)kB2F@6Hn@56$T_|!=%8Yxz>)O$>Gyk>&$v@*F3DoH)FHsMO*1;%4Wc?mgptp}3i zND&98Y*OvfF{RQ|!-~;~iiJ)|%#8lhrLxM1m{fNrU$H`YMla;Cvg!K!laA7vRE+um zK+J`Nx8^Z&&@GC%UP?(tG&_fTv;*SLrAq0P6htnP$rH>*$$I(6lqRPbG`AbaSqhwc zl7q|#?BBZ@C8xwx&r=%JoAddG^8Yl0)Oo3yL-Zh;9#`aSzaVzE3I!9I}Xx zUt9YtnQle?CuvtMnhul;U|Bm3t>cSb;w&CR3RpUC&QjS4e?ggle0$gJ-idhZ4sWBo z-P(hFU!<{9KJ7N@y#u6gM*j#5WV^I@A!>^Fb=zr=cB%pIu`8EhOa5+Dg?cTMvC_8C z0(*(!<*L4|)CtiwE`<^Q=lfWBJJ@t;-r#83eXbwHE}WKNYkYp-ClsHIgORtdG#}a{1e~z z>~Fk^TS)CWlNA)wvw7ncOZOC{r9Yzj;m@6|%^QudQrGf|Q~@Fl7Hnm6feBVS&&4{= z6XU_q?5iK0zfRhUPxcM;U6DU-WT0_C8Ck;cHxcsGWe5Dql~gMsPwY}>Z6Q~Vf1}^U z;r~CD&N3{j_6zr~fRuE14k<`W_W(1bbazX4gM{QbFashn4$`QI64D{vUBXC7mvoDq z{Xge?n~!^6*R1_KYpwhK-JI1EPV{X-;@0UQ%$)=iSe%_nKlZKr9Yi*-qPIE0ZoDpTC-EUcdWM{cK{8G;d&7G&D4b#10tUs(e2COcgbWE?w&KH*_?4>5!xd; z&$iC3litLpE8ZvcE-<{qQlYDna~*;e@v1k?u0ebR%lMlb8^Mt7TA_mV5>Cpz4Q#b+ z$1E0}m6oMsA30mjjD!+jo7D&{kk`OJa=12-qMc=jfI^r8!lFDWnL-vNcW%3e{Edy!Us8|0;MEJ{)IR;nF8q` z!$pG4&C2y({}LM5sGeR=a@w@DcSel!uttxh(F#@U=aFIcdnMHyKFcs8)s9gb^1J9tZe?-%NtH2^__b&#q|Yvq17RHIm=bPtO|ahwMKGm z)sZjHO~#1WV*Tb#+BQBPUo8_YmjB3`QiU0i2Xs>3h}y#AmpeY- zL&zEd0(LvY|E@;0Ic!;9ri!{&n@xmZ=VKzuj5{Oro*^{tt&2QJuOFd|4}vu=`*B|+ z(76Hafm(w2`5c1!hS$E+zRm@yh1Bz8999|>HVf1~U(-SNFlqwOt;kkM2M$^!6<3JF zxdKP!_O5?ld}-kj<+2O_9~x3JR|DMcQuWCK0D>Ijw4*CXlrJQ(`SJw-TG z)v*clr@}H>bbWRS(ND_nZM$4+VQhucVo}IpL#?{hs%@v1?GK*C?M5cz)K!UyzQ~ro zH#$DOz~#eAr=fS9mVO3m5B;vntLBWmtXL3GopZ)h;DcY|U@4i?+I5+_zj3|OnQ#W^ zT<;i+jp#@X1*J7LjwFWgZ*k!o!v!%-Tm-{bxY?O}+rv-d^yt=jT@7KAzgTgtw23-7 zuPo<``=!bN^fjFO3f*#HbI0Kty()+qmQ|}QgA`W5 z;1aNvM!><1VqH0>ais<6pRMt*o*XPZF&XB24!&GA3_;hR4EO=lKwjtXy}UR7@lkDW z8~RgbBsUCx`kjTmO~g4He%q&R{j>zgTLV4i*>F~~q2et?@Nh>YKU(X1^X8JJoJoZ* z;o5~eb|r9E&M_=nJ=;0UxHq^#)m>+PX6Mt!iN=xR`_0RLB%*Jo7!Z9e4m%N2T+95W zeZ&ZZ5pHTawHF!N)GggWOqa-e5S=ptuXbVI4-ljD0g2nkotD`AHC$FXO^>4g^qqjj z)aP8cR5=3kkzqmOv!jV6;zx>2tDQi4Dkr{g@apf#-Z~-kN?O2j{~2QmPm-T?>iXmR z%J9FRk+!>4u+{U&?vZJ>&`&TcThQdb#l?tAflxryab??^63xa~eJB zVp9DtzcV<~aC0s#RevURQUfmGTnuT9xHZ76V4I*WLy8vv5fH;Jl@x+r13hJ|r~0r*|iwoYVLmqB9s343WnHpc=J7bygl+YRbKw#kHQeSaN%uxo0CnC;o*qk z_WIU##tioAcB@)xrpDzU?V(hG&jW$#hAW)<5ccciN+&nmPh`>^IewlpmDC`yqG-b` zqLzVXqjR0{g-zJDq-@P#Lj1VjkYOnaJr*sgOO|;L27R*`I5Gl>z3@-eCQst#*ZCG} zu+7W9n-^=|#I}cOt{O}wn2qeRrj@=;-j{nl$6M?k1=@=P$>uEw#GF&f__+ymvrTNZ zOln4Jw3V#9R=;1;{+}F36W>o%lTd}sN8+c?a-$&hxB&y;n(+OY-x7UiP0itKZZVApx?aD8ae4)^r z@N^^Le3OGq3FE4d`6L8cZ_9GB1KB4+B=q^bg&|+Ud~Gs9DWgHUC$q&VyopLkhCw&F z$FURiGEWDG_+|rjZA=_%(at^#C(9AYs|t|5%<8OLN#Ga){Gjt7(}7rQATHzQJLB8^ zA7uOzz=v+XUp?}yUm2Y@6BDidRP)IUR7(G`)Y}bL{zv3~n8Xs<2lsPd-V~?moVQvB zYnUZ3MNF%3`antE7ZYqFE-iAs-&lXv<`L0{ixLrI4{~Y$xeDGL!pXX`Vjpf(>98Hn zE66%^(zqRzG@{-WF<<^G)i3!X#`d?u}*wrC2J`iABME-Y?DSL^!}D9J`ruucXsQgCH^JG^}3_4OoH{=-eMBUWl^exb3lQ;{+`gXMSAedrMJj zPsf;LQexia9aF-{Nv>?X#e}ivSI8u!6|kNb%{Mmch2>wM%e>69QjXo0|bWn6ludHr1Yq@5dqekGdX&S-y6fv-N6FBu112uw`{%hTe{)YKz( z=)A^X2B~A_VcvEf4Owc+f!L1cPD6k~3*sdu&BsPkVqri{Z5v}7@cql+>A-Gf$p8+| zFjHV}nxw4v9jBKEeM@3(TEOy`qhyPK79TSj`f}$~1B8)Q-`vUdq$P|vjN$w$$fIgPRdjr)-9$n}Q^>O9#mvlq!XeEEh67nL)#PiQDS16RN; znGk>P92jdIvFcEj>zp?LF?SLemj0ML{Y)3hM%MUKq=$vot{UYR6r}-sq{Bvy_{y>o zCl?@z9PmA*!Wt`{!4T=R z%O6Kt)z$cvnFz^2*{-_Mr$_4qZw)*fEKxxN16gzdLis1lhECF0THDy~$vpSbFSAcp z9%_Yr`!D?g_dN?25^5K<-1A1G5-4KIpSg|9W%%>Yzc{*#g;sF%K5o$If`Wz#nsig4q z<4|0c{D+X#q6#T_*>bCqW09hS)r+%t0^5e4U9t$3?M$dp)>bvvK7Yn32anosNGL`f zm~!7ykE5gdMlazU!&27ozgY=1wY51ntRS4}KhL6)HC*$0BQ>MUt4w}c}mT zMCkFC@0CNTqtSZCCa3}wMJvx2&FF{Ih`>Bw0B1FuXgjur5;T=^MWl3EM65*9^Q9qg zUJiP#*r&p_g>27Gdl`f8u)`!AC4}4)(L2~lhuIF&*qr)+(~qUm?0u1jp8TKVWy1zy zX!aVxFE2^EC2?qocPi8v_ajkKlq%*8?-%!@>9~IrKbbwiBXkzy)r@X<8U`@bHD8hq zznTy8olB2e@6~t+BsbvXg(0sp+EqM$^#v)AeY|U~ZcJCi9QtiY>QY#y-`h9YNc1W9 zAla)PM~ZkxtaZQe`$M{228|q@o=773G?2eVDX8z87ejLPY-Jax-{kMSSf|{~apf)Q zj+mzHUJNrRoCEHymrJjWbyR<3W-M*KM-XV_3|*-|@0Yg)L}=R`Qk+mGo^tB`#Zyd2 zq_PKI>olG7ARx|4S1k5mWD)XbQINN9l38on_SwZCYAmL12A0-Hj9D6`pyba*>jl*i z$2*f)Z8n^lK(vmJvKt`vcku*O~h_H zEX9bvnQb04qw8rK>7cNZkVI>q(C{+zEDA>Es4Uo@bT0HsI_1N8$}Q_Q)(+)Ag06@N zMZtm%_7es2$2~J9L*U=9$?&I^HgiCVX=Vn^*LE>oH+OY-gX6cfaM~9^*z5Zg}Fw%S}gMpab z!efESL$`!OKd)^6M|j=LObd2fl6WYKoEbaneIDes(}9M{e?3{t93@>jFoCZFr>Wv! z$riP%VVY4tFS+AVGatL`~pqtb=}xvvrU~hMjbeUhq_(4~7NjMB+eI zXg&yN)#pnluB2 znSGm$BNE41+|lQRJ)?~8k+Myer_Y?{9kx3nvy4w?zGm60X@n)i6IbSbj0K7#lS=?{y3O{>dOwo^J|7W> z5|EUJ1Ccqh=(Sx)Yc~mHEFz`PA}w`VaGr#7GOR~WJVh>;S!bIL1yYYix-{#4Xp&Rh z+*@Mq79PA^ZpD0nhFv<5c>Wkgh!pvboT4xnEFP3K0`@Dox4}-|c?WBdre6W2y|UD> zobk;2_#MCdSGDBDRQM@0!Z3njNdaSUCfn=YiXVmhjnd($fp1<($FXwxw>)|5Y_C{i8N?_X*_o7k0ye@8i+eB&MW*=c85Xxo(!S*v?n*+S9( zsG&9#n8eUEy71xP7W*Cfv|iNcTI&8q?DRC3rncpnx)zFYN754ggbdm@I8t1U<6d?- zi%*ea#NZ`5Rj0zkr}8ub32`s;FS%>=S*B%4;j1wq%;+I~c-Bi=QwZEOwFXXMtJoL8 zNl2s}v-h`9izyBbh%^DL#4eDfkV=!WW)tRV!%q3e$p58YAju)IXxBs#s?x+vHjV&2XvH%e3<{du9_+hC)YI5z3;7p$A-dN9Yx;oH#eD3O15Lu>oZ$HU2iZV}i> zdtoN)h=+TTv!3u{g!c}8TX!(LbznE2<_l=4+GPiKBq}U(zn?E@Nv|K5P3TFD=mT_Y zJ?UKlx?`#OkXZ$ytJN(a*;u$%Mc(Qqm;YXkD^K5r*SR8|6Ste=`zkdwPdJ#|UL51H zaqXg~sIpR@!@+)?s8P-pgbnnkbP{c6IE(|eyO9&2b;-PP&&}$C*g|77h||o~*X1Pl z;TN5m)Q_KovDmD&5DYeL)@Q4g-~=44o(teO?rnZWBmY)p1}_ap-+-YhBytd1VD-Ma z$V3|d{$}03bb+X{q;%Lg?_vWF7uQms-4KNAd)P!@MYz)sj1v3nN_DXsw62XkoZSPPH$;)chi{5~C<8zF zMs-8qayTXG@?Yv`WxG_KQs*xrz7UTLQ#tc=d7q*>HE*5tnejg7|wyeaJN^aee#-Lw#eQNqTCcz14i7*~_r?Ju(QoaRnLK=FVZEal%!_nDV3fItzyC>w z_*1?u>XoQ_;-h8nZ*zV%7ROtc1DC=XFICY`gZt&pB{Uu@>g=?wj<;Kg4ug`R?WZh? zZs$kk?GN<$ok`o8(mIo`4KmlBhdLZ9USMuA*(i?Q#1lB1Pu(*3tysxIC(F;CTErAY zQUEbILpP|*v?pV%R=T3_``C$eAa^sX$cxd)!R+f1q&7=noE-npoMnw%sW{+jjWa6A zweMJ|4MMjc4xFKGL!E5rv(BV*n(j`?sT z(;Czx&K7B>U@1H&sDi^{g_;x>B!n8%fMtaXj8UUot=C%-r=YcyUal1A&VkdZT!e2Y zhd|C-Y9X{D58=QT*Y%I^+O5;SS%9+eZasFZarRzmJ0PJUTH4AFPzk_bJEprw8_N zfr2gj62PrCZCtleHn>Es&%YhaKYC_r>HTL;z?JXt5B!Yh$(|%}zQ?$fMSHlxw-*v! z>UrTMR#`-$eeJz3jxR9spUBv#_!$zzKa5b9YWMfA%tN-6?96KaSu7 zSCVRcDk4m}KOF&oR9eU{+FW1kqfsP^gLAmE8#!#(2{GG17W$aUCatH3mi*Ir6>|!{ z&wP7M`LFs|;qm4@HV(V)#-5^1SS%|cG9h6gB6GLof&{;Dz9rq@Y|DEbI35$eD%egQ z&e6%q7?-GL@OEFcc(5qSJDOyh?f-egU*?}z_1&#B?EaM6_Wt+Nx|C3MVe>wRrd8h^ zcZT8WUC8&XP$`NbB)jg3i&s=hrOOCqw@b+CT?k*t%_$Y%QP7Px=spVbsY5Ry*JeDO zWI|DCZ-p~KO6`e7S}o`DKXLBH0tQD#?e9l(hOA|m`e7Zf2#OugAHCg%@V)4Ju>nj{ zzpKYq>{DeqH`1W3K=Hz2Vr?E#t>3WGGkt(Vt!ob;~ssgbB#M zzte)*wrdhPNq7arZqEr?mYh!8ma6m(kleuUSTZcHrPV}41;r(X z**w?Kg%8(l5xvS}n&03Q)@R?*O*blajv8@Eu(vyi2+k+2m2*XnS8F1(AD2!7%3u|{ z9boKKA=k;9PVtgouba}z4xdx?VOk&*udvCA8wAyRNzsLGiouQ8r~9(G=02E-$3x{j zR`sVM34Rj^J$~c_vd}o^EWR*YeESOYMqDE;Lpu1l(kG$^3svhBHWXS1nLj(0Qzbms z{U%Ei_=XFgt~YC@=8$0u)D4KwsrBJZQCf;%17hPBbX<@(+{pv^_vEZ{uMuR#FQ1ty z=-gc1{_rM)>{bxQn@D%By0_%r6#Z^Fvhg!;{+l0jFa5P6qT4BLl`<6rON zFc)7FGTeSYx0^pV)SuIy1iI%6ZI2j_X?{oL3a+$zfBVvD3S5I?CEJvy)*Oawg~t7s z23{6QHSsR|Vamrgd)nA^C@uRwAUd19nCBLvsE%TVOv6Y0^m3m`zt@_($QEKPsOAlr zOM>qJw5!Mc(Iw1s!~L$}LFrQr??Jx~JkCWQ9hv*UD@`6hha_n76!gZ6!Lq5`hoV=$ zLDuHN;5y?wg3GE*OpaL~bxx-qEYp^}QRNrh$eN6V7ZbECo;}4tFzEN@rm*Y%Z3J?!8_7>LD=W7rHt6cSE}~;gF#5 zSxUlCvokv62ON7GZH=Vmlbk;SG=i0qnc1cu$}1#p>|r-c4RV3+MzXhmEip^W%er=a z)lb=WnO5>GQLn`?lMUI;oHu+qs;?YlS$)5~RloVyX)N=Mh#dPwq3Gn^p(i3e!0>ZX zM**&0I_vJ$dY~05;M_~cmUU;3Gb+Go$D(g-T|0_kSx+>xs{8I0m4m1=;cI=uQl2S_ zw;X~2fryjiLC3|abwG%e_50vFOPyt)hRBhkxr$TJAAp~vqvgtuBG3v^LucFLPynju zJ`vUz91#ECn66&aQf2Pw6A~}gV|dJs(7(q=-rhGMF( z`G)l8&D0B*H`G{#)FQZq?>S*?5{YD3=Gkkl{g2bN4M_4Wk*gnB6jh+TkqX-{Tl(8t z%6OkV8yBKOZC+fhRPVoRzg-y59j9@6w)r7FaL!5pi_ z62g$x=8~BUzS&~=1h8zB5%qu1iNN$ZqwjFGp-5QA{Uu@XY?j$3XE(k96gHo;`mrUO zL-4;&YscL_axecP#7G+bnW$q2X7ZfnDTtGNjkC6E;=7QW`eBH1*f+z-GzUWAMG6uIGFqTHF5kzTtu^tQjno*DRpW+77iI; z>2fkL1BFd%YqetDl6Wdvt*sVkH`911^wzrSfZ0#@Bw``TG*2tOAql4v`@8OzGM`f|#iB zKV;Vn>(rC;QXaY6)Ma|P?|^_`@O(|)cyd&dKccqggSLF`SW~ltFbrA2xju2`7Nk=9 zIaf$i-J-9P^R=DH7Tsg7rD_aQSe?w~>^>rDAK@NH^(d@^k^QZ5pAv^W2YFA2Rizlh#FMO^lga^AxTd>nvZN9b03fIXa`4pRnh>E`pnDAJ&wb(c11w=$8 zAu||9x0V=+cxDqf7aUb!fBPS0(kitxpJQ zRQJKA`(>owL|eaX(%JNS_qZx38JJdYyX9v$SMBZzs#*ePr1y?r@L#u5p+&mps#2ep zJ4Ul>We%qqs?RyDR&vo0gN?u_J%kJXhl26)gNsqzSD*3-HRCd1>&XVG7vZDvNu((v zH5Olz4ZdNRg-3rleD6`ykiXoSdBGKTQwdIdb%@tzzwxf)`mv99732)&Z!(#lskNVVp$Xfv)0Sr4&5db`PDG1S@(V9 zEKt*jo}uQYMW~qv!k+(|c~$oG)fG;3%Mt*(J|Y3BsUI?bonvHC2n7~npUg=f_1QZG z)fCAyecTcQy8!-IHYVA{GUs!2Yo3T{9&n*S1qH5)?`A7u#36i>GKKrx3))~`KfkfB zF&7@sbjPmuJ8l?DCaFGwNNBt$7S|D7ZU)8p`NVI&8*(>43B=?{?#n0DmwjP`|G4SK zLz#-HAPqvWdn=vjaA}4=5=-2^3R%xOF9vasf14A9V_RcH3Pz)mXQ3Sm6NJi&{J|PY z1n_Yot)V7|Ky+bk-QkSWuQ##U{I{1sT+_3`5nX8*iS7xpVG5k={d+iw5BLWvik~}b z6;bxBB7oqGA1|}31-Op>Tpq}D76$MZCqp80Mw&{q1a#nDvJ(V{6NPo)r-Siw*qiuW zj!(WkKN-#L76jCXWjgb0<9wZ^s;` zl#Ypc&bSi3$A@50{6hR(ndN~v7Lm4BHW5;w&+rb{cQV2L15 z)^`?P`T>2Lxgl(jS;pdlcWFI3Tfeq)KPq^q{-Lgil!P>tc5pg53yYI0ccw&h6~ybc z^Rj<*slTcFHmU3X^{P(E+F`H{IRgy_e1D?rpY=q%zrBB^3&S}YoQ8qw`Q5o)CPAsRImBqI(&n{)3pO)Mn+W5smx{BeLTZSmFa@U=GA7Ep*Oe8Va9-Y zS)arIr(|+>Z0|yj@Ix6+w&COQ#gXkxvix{iDK~Q1^-N&q67_mS>RnuunLB|ylICeCXuXlLp zm7h({_o+IaC+tO~T~CzonI6S+^!zw=0*KcMAZP~O;7KSDrC>3AR3Dfx8%Ii|j(n%p zCp9RTfKQh>{hd=KkZO~uka|uH=^&aLq@%H-!*DaZmsRhjzxYk|kFPh7?C}mZHpO)H z)A@B4wF>yQ%GqRdrk@!{uSlYHHjdWEc6v?U82a{ImN}dP$uUl#5A?|un&(j`DY`^D z7z!0Oou~MOd}UbGpZEzx$ok10iSEKRGNSp1Cke?pJBf|V`>NlU)hxD}+PUup7z<6JO%z@^3B@F;oCFWP@7v*3XVb9FsgV=<>bYSQB9`t#U1rKS3xErwYc&0}=WF)po&dILDrSBsCWqm@b@;^yj4CKAVSa!=ydv!6HZddw-LI!ve+Tzk&s~69OJ?j- z$nx)b_x}tL&{Uoeikz1z$w{u7A%9QrKX2gtI7CvS`zK=Sg)veHZPc@KeQBN4LL=u# zuQw*4OvrSzVQtYs)OC73oS-mPVX1*EfiS3$;b+DNfsfqO#|5DH{236fmFHwMQhZ^) zewx!EfI9V!2p_5LHd8&eicE8RaeCebSq1jMBs(}~c`5)t@B!5JXBxxgAcvc=_1^=rxNdiHDk?~Iwcr60kyo5MmK8vt92nFrah z^1Wo6YyUz}CM=m04iZ5rwr~8tgzsOFh&{DGj#RKc9g@Bkp0bP&;#6;Ns>Q+l3A>;f z)6s-=C;cv7m0#A832@c;CFOAXDu{EAq|xugQAA&LfBHRD@fOp+d(gl8b-aG=(GZ86SMv>rx94EhsXzdqp%0zxUw{uZ8}2!wH}r@E@u3@0G1>oTK~| z3hK`NMghtfRorc8$zjAF61=YH@yKeBa`jh_Ky*=->dV(A1kk+54F4UFJ(C9NiMWmi zx_qXeQ=Koh=7&-3$464*^mi-;+ga{I_8r@{e{U2WqZ(W!&cxS+nH%WnF z8CItZnPKonbcf?Kv6H(>IaSoW2#GwYkkEk2#^sW%$(gkJQ-0nBPT9UHuG!RC ze`uceqHTzrLuB9Qm47q_-M{~$W_us!-=n)RiRKdBZ-7D`-*50KgLKhBknZnR%YOt@ z(UlKW{~a7Rm6IFIr52M}5E+7ZM?1yPWoU|6C@JMSSGHt#rhe=hV!1vTOnrA$FMgP; zDO!2e$P1k&^1;*noRw=pCtxi(5%EpGRu&FZo(i0ZP!=brWe!8HbtfB0dxew?Q5n0F zsjnYq*+}4|L%C%18z*bc(*twaC=oIL%xbn0I&Opei^$~6UH<;T5M%Tay>KwpyB@F< zuAWvp1T=01FTEJqZf#qtMhVilU>7KwlSK{AHm?Ua{=WKJ-}pf!#YrkyuC3ymMz7>b zNB8<9f1xm7#1+Ut6fEb_5-r@xi>(_m%gURSwXyaIJKdp@lv2q$e_yUmtfwnzD<{n! zEI8RUI7r|ZGaP>=)*cWN=rtbl)w?1UHTB9TVr+}rOOhh8#9dG$@#BU&la#!4Cp=qR zXD5kVx#TFKggjK_w?>kyt|I>J9I~9J@~h1XrLV*i=W7s$5+stSu1({~uz)ehAOiw* z)u1B0>7UZR)kxLTxS~{$0Y^hZ5DzI~=Xk}tq&pgEPY{z4s3%V9j&)nN-rbQRoO&}e z7I_RcGZXd9Kr|^z@M&V<_Q|xC_t)nMY`e_>j;*grf}?PH{KwSTeKb>c^n>2CH-jb|3;9ZaXe8_C`DMB@$Yw*ln@&oS#G!9Ax;7aQEDfaUH{F}8+ z%Cw({xe%Nq9XZ9CC*9!Y8Wc;6{vp$!Kkoz3GK&X|8b;^d0LOr~>=X&kuGC!sehRkTu|lnDl_*c|GSm{oiM znsjpTmN9gx&Imf@(pJhS^Mb-&T=^H{BqyLP-{X-^-w$SE73jFVFdU5i;d zV(4}AsK1L{41*9C2LZJ89Zn70#JS?k)Y}O%fsGrKqvG#KHYlZdoHidnWezpHa_Hs7 z!0PIf^>FZij9B0leTt7*rFjN^Q$2vmmF9CQPWOa#9)L5Rer%vspOkmw&}mKU24^mg zkKPHr6z&Y@%MThJ7kbQNK6EC@T>N6DADsZ>r*!jM-7XNS@TbGTHaON?mVEt!H8HqT zD*k3iyi-{*|Ax!0tPgLh;sc+%t}GXosz3(muz_*&JD)0%urbU#4VpBiBLC+YGz^mK z3}%H;zJcMga;R?;EuqbwecPO~-<=D>`VsxK)bWSpg|6lxut-fMRlU^O%Luk3oHPFJ zUIZN{6v?g=%gjj9!n_+#k;#3vX@Hfvbw0OJkj~5wP7pYrtFbu}uK6HfkZN_Z!BISe z+$p0o>7n8f{tiS(CcF<4cN`xSC`MwXX)va&uAd9s7apfn#B^z3vmZfk?m?ozcnY*r zbD-&e+WHqi(tSL_YpyB|LEp3%NzaH!f0A4AB!wv-50A4JCmV`r2@l}h&Q)8Dl^f(| zR4cMTMPJtcXaSWMf>1w|7rUUl6vPg*H`BK%;T)3E za%d}+Kk}bhL`*XG0_neKikO(lT1IPgx=%%KhRXL_tJvkY~d=Z`A zkiF&mLsIbL6>7}o+V2BBt`k~Bk|1|Vio?-&qYMa1*S^?y?~3lwa91NrrCo{9v&q8e z1LnI~wS;_J#`+X({ciEh>d6^y-D%q^we5mCZI09DM$Utqrlg_%VJS4o|8ae?(~{EZ zzHA>Z36efzeIb|~`SAC#=1ahoy)N}ZCoU*kX~VmA?e}S1D=%*#FoD(Z7!)SLXNOEt zfj73caQEdmTZ@JWBUh%MW0!f686a}0WG!^Z^xXDwQ9P2V(OgKFq87)jQ&&7 zry3bbjXETI{!ROdg(5|zt8_tTadlAtnpv&lp9QZFfwa^l+IRSm0IY;G3(4Q$Hg4*? zRr$}eFPoqDyy}`}3eK6-_X)w5)}g}G_sa}>>SyGB9KrvRVPP+;^@##S!Ta9#*DqvP zzvRQo*OZ|NR*zKi@|?G!KS~&mIH6`>VVNZ?^tq!A&R$=Q zyG+%Qz#M-@tN+^z~oj86W}H+F(`cFtp90tsRIH;beA~2Nf2{&NzDJ1&e17g z#~d{xWjQ*zOwr6-?yP$=%p}CzgGV)V+~5;(aEbS9obz*-@)9WoRKK4iC|1JSC}sU3 z1Rh0eR>7nQi#*vuuG{w)3173Y$eZ~_G_FsCv^GgkTu470nCb}wW~p#JR*o{G^M00X z4j&)fJR8622~e(m+t-CkA7QAnu-Ni}3=8_LlfLh-qo#HMN1ih+P8`fcZc+HA3_=x} zBp$bX2@BD1`3RbgQ_$WF6(QtcA>Z8SBqHH_Rq>Mvx3ANXez15J1&%~r#Vb-NkYDIs zyOBkM=5q+6mNkTR7Z2;bcJI*kbY+9_IpWO(2xWC$(*RuAZwoUA;j)`|V`2wx2tQ4n zkDpSjSd_rGos*=*3+C!C#ry5qz>V$g_CYtv-GyZq0%8AtORJ%4m;dm6rr75a)!ds~ zi%KJl8y&^bqA}r8{wJs1GjvQ8IU7!Xz&9wPq0YXV1twWh2T7Ca2{vaTh1+!Ywp=|$rNb=r=_q}kzIDH@5)W^2CwB(aZ-Lk%7jIKV_ zxz~97v|)3w9pc^jQgQ?NXdUr#J#+kbx{p6w+ni0ZXX>%Bob|~c2QI5Be&D#~B~YKh z)eHF>c9+x3xjco!NkwLEgcO^Wo2%{fKNsk0;$;lI|Yd8tmCxj1oA12LG3Nc9Hh9S^xN~!1bWPGGWk+RR&3+S$ci{Ik6)u zTH|}pN&eUD)_+@q_XkK9w+h+b+B&vTfq6+h|KbYR#&~-(D2Vt%q(+fs<8}XIjVNEa z@El}v@*(9*N?{%*MGVo72=@ zrU9~=GY;y)c_H}{>?J#@T1LXRN=5{d``JmfeE)-k{vgS4qL|`)#B+%qRH_*-dv6LY(r?>JN(@?T!Ta^Zn})qi-$ zTy}cMUcFRseQxXPX(1O|l!xHv5`Dvjmu^6yR`!!wc1Ux*nMP9TR5LR-tDICXEF!=z z0MO1#x5(Ii+4hv!#^f_>P<@tQi9lHd+^jMcm(O!LDXHt>)41XRk@0rXY+dy@0g=nDKGe>A#9~+1GTAym(MxX-I~#0t;A;ab8H?pD3UhRhyGBBimg-y zO2Dq0A~!bqUGNKW)+Uh(Xg1G}uHsXw1LExBsKX@-?X*Mgz@ZkUIK?76CC;_#o5akn ze%X(WS+XC}DHs(oS7((QOFy`h)qVHmMIM>_a4+iN_~DM!%F2pNWpy7A!PhQ1eo^In zGf5~|c3Qlp@ab&0Kpt2-h~>YX=^qq-nM8hQZ#-HKtwmN;6|yoo6`57)`WBb+il%au zAIU6MZ@aW@`AQvMpFS#(j~tn|jnT23$0^>yWSi@=zwLUHEjLcTNRmm1LKqpV<+>ce zHU2DnRtGyB$cTOvG4JzK(9qPG7vF$t(lN3BAY%gPxOe37;OkgpL=CUMrx;rXfUNZk zVVfi<-+r1UN+&2{!8ThLT0nDx^`&N4svaZN{4w|o-GP#W0Vun$ zcB57r6ZK{y?<^Ar2IF9Zwb1ywTVf!zb5)M1Rx{9kLdZ-EK)6Oj)ki-=Xq-jcfx;`~ zN@pG0S;Q^rNgLQVWgIi*bL9!JX}2)+u|zAp7lF%b^6o`%9O}dyF_6RMq@d5w+QYDGH|UDwhFX{Z$ty50gF_rvZ=FD2zevpF=ag6t==3zv zX(<{*z9dd@l?+Uf;+cJ|JnoJ4?=HYSq<9Ev$>TGAY{4 zSD&K9E@%A3lSg!sIOOur;K^*{G+8ge($IL0>8kFiolz&1H)uKm5h<|ms{CXO3T%xZ zJC{mV_9RmzQC!DdV9Uv(Hu}zG2v?72$gNb|tivNV3f&DJcnLD~M0ZMfxhQ)XQ0Y@g1w?CmeFh z^EC-mj;H`WMH>24IqR`PF(&~o#=ZVFeo-N>MH8L%kFR$h12P+2*;7E9*n4}(?1(8W zUR@fV7strAl=bBfHqzY1&Z(lEo!m8F61k(}Uws-{@O=J@gwy8Lclh+QVO&t%9X61w z*Hi)tm{VX=b&~#yKc;egkRMw+OMf;)Rt?Y>fd@ai+!VxI#QGhh(6+y}6Yw@7-d5!W zufZlEKtKzn3b-&?*8x^|#6Tf&_`>{%z*>tA?~`0V7U%KmZ`e&_XUIyVp0DEQ_KyD} zHFr9%9vMA0Wkyvn=l62@@VvuVvd6=jsXbDtBr+=VPx#fUMF@4kHu6;N6B)1#n#FPi zX;QbgE1Wj09GbDpyE~ZCLD8BN)cPC%ni5ADA8Wt-@kq6Mg=YK^7ep)jagBKS5fFbT z)`+P7b-Zn+mVuu^k(V!Omt)_Nb5Yyi+t@NEkLj1> z{VG8j<647L$DiQlir^??W>8iyY0i+m;(B8yR?nV_zJFnq_tlo=`tpK!{U=*(f`HDX z!y>|Ckr~PncJqh$#WgQ3}8*XFX3Isq~S`5$VqViE8=Iz2%7Y2YBq<$R9*>4KXeo00D+aB~dudR|z-O zY`yA89ZNAdz52@`2X&h*x#+*JmW$}-HD^8q&igVVlNy;QNH{-lEoyX$x)9?ZaSsQ- z%}OJZ6u;V36+GkLJ^;BI&Yzng2Z^ZUBI;Iebl#XqAFjH!prg;T95fqFv?-X?wO|xQ z;34lkGR@bm=?k~jBfWS@S3oUXv(hR@MNSwLC-i9O!D3qn!j4yw6*x=5Ni6EAa@`<8 zOh^J9B$LX~U<$-8lf*1IhKDmTD`Dd?RivV&yfRY~HnjH}7R{s#&K|yzCpbtk2i_o1 z(zL`JPKj`wwySe!GV=>-LUI2#KmZPSXfQ_bvj7hz#Y#G)kiN1vos*Qss%FVcT3%k6 zWPlt9CH3*Y2UM}EciZFcEx7Jw0Z#fR*U=!MCxO|bxZ^;0TNmE5v9S!z z2^L0vMD>Vk!omQig>iXl$*--gb1~LDn;llBz%oD)zxx{uX@)N{=Zw4jO@G*W=)*Ml z_^l5t^h(pxmXDmqZTW8W4$~&k(Jumior+Wn^1_h-U-a-U8`24gLGM8po~P>Aj*mT1P=L3OT|O4W(kEP*Xf#6|b*9FJAY|6){C>;}~=sorC@Z znc!EFLD-L%C$A7nJ-qQ(6pES?3?*GnuO@Lb@vX?t9ZM#p`@L3 zKQj3{?N=y^xS_<)zrQP%RfhOMqF54KW4wu+ltfID&==`#Tu88XrIpxmZHf z2UqIqk%gXWv*%2qO~TZ3OMd>$^iX9d-j*pcNFk=6OlQeUPr4Zh`n`dCnyUR7-_GRaDbiD{c7zx+H3D7z(-M@W!x7>WSZ!fQ*Pzn)0im zD&W#t`{P&6gUGjxjsETuYPFsBz|A`Su&-#WNwRcO=^R}8&n=K+vVexJkiq9Sd1xQ54tum&{W+!4klyZ`{*iBlBM(f6CoUpU;KOjc zIngOGMP;K6gYhBbIvjv!monP!#(Ojot^F4x_q@dN+QfCVmOMZ zH%+94rBWZO;KkN&m0`Og2>E)e$o?pwy+L8>PRC*`*^{&-r%Ljs)z0RwNQn+Iy_&i0 z@dy0vuApAn0rEK|m+6nar>$&~T2uOaF}-h$$L0x$bFUHN={4SeNH*9PETi;Lv+ddT z^9=$|(XpC2e}!oU)4YF&dkqsGugHOT`9*7GKEM1#^s!)!1aw4eVlY6P5Dt3^L6~j* z!VIp2?AJ%x61!8!;%GyxCEh3Y6!hEPr(BaQ%PQP9&wOu%0;=Hd>~sD&`2D61XHD()>ls81r6$20PVzPx?7O;veUEe#mFht7G0mgKSPsN8h=+uw{HTffx ze)E=j$2%?aj0%*%$8-SN_#kQZ(LB3I4c{=dSlPa=VWB_3*xU)6ownWxR7OUq-n77n zupU1pshcHvOC^m|)NE}tiN=gy3GoK7*}vI;3_7PN$tM>-`bW>r!y0S zkmTNdF8h1;51W~pPm}`sQnLpLpKma0%e>sm@-YthhZ6MIS{wB*w#G%;@2CK!fHlRe zwekCZ*14?I*&jLsF5zzCSN|qy8YT4qtkKUU$z}0=`tbB}Dh96LHstw^@9@scAxRcy zH#mHRbgX#yh`Nuo%TZ#d1Xt*(U*6`t=`vFrY|yV`Q)zjOu*HVzCB9LxXGzAwSHenC z$OZ2)t_#*0>_qd<>eG}dy@@n)ovj(gqyHSM&rF)rs}TGVmQN>t|NlzoQ&P0AKwt4n z#ZT_F;=nKY^^ZJuL3ua~Vbb*Y1h8L;q!POQkzbmF-A(g}e5KU9;iDq|DL<^iY{L^X zSDh#K^Ik(1Yiot245s<1NMgvK0`E;=sf>0!3|Mfbyw})zshM)1Vd~%M(3t}O!m$sG^9X z-$KV@P%IrAjnx9(`S^@|g#5x=auWPY3cc9DA8c17kDJLvqr8h`m5^tf&``I_{E%v^ z&r!YtK zyk4P1fl~}7c8{zw)9)G5MhTBvI)1-xluO$mBBLjoJN(~*Zx#W>3T$UOa_`1^dPqyC=)lnlUKE*} zz~a?)w2An(_z`ISJ+O7Vc;;roLQZxSi^xWuK3~#%$F9G{Z3CqYvQNpm;_OGDjl|$` z?#%N~&Ia1!*9fT>W|j~&6<=?tP*O}K@Un^(<1MQDU;zYTx-K+|ED(in?cSds45V<; zaJv52V3_K;`ZkIoY-ocX9E%`b!L2wv#@SR{yJ%;lXT#|fL9s|QDE~=IMMO0jf%cKA zv165aSq|i^V$J&ayf~-!nW-w$S{S)qsvs;Q8`m&J*R%-zHgL8hR4MLas zb9@||P0=+NpfV+kDE7PUrez5bx^)9>Nq^bQ{ji#T!$I@b{b2;7DPF1Yx@zJ>>>3>& zi>2#=whm+CzmVl)inA7q0n!Xky-wd-3_8X}_lY0>(q5zY*8>rw##&HxwtX9|8iDE? zGR9id9|ct8Rrj&-opZ6zU;emgY9&svcLzPgUPmVb`w^>Sb(QL|v>$-3AZd39`6u!3 zK6uuBU=qQF**l_&1~x9wmXQtEdOuP@;rMbJ!+nD{PJ@A6n^tW*!=aHUf<}m-GMmql z4)#x*Lm&6cie+7WJVwuh?m5RVfk|qH%cR>W2g$0fK-Slnc%$$4wk&khiabmB)dCZ3 zxR^l>oXV^-zczAmG85m@NGN0I*k4FZ%dg^4r=qAb8C3B`i2|PTxifS4s0$7kv&48-w15v#blP1ejQ?G0gA#3*Wgq4B@_w-x<0VoG zI`VqF{&_q!W|GBoXENwt%5jz$Vyf9N=Ig6yS}u4vjN0ke}K`j7$Px0L@Y z4Mdd(*RS95uMQz-h3ds83GYOeA7tnfuw!`51=#gn{2vy2NAkb!Fl|o%CVSRwTy+sK z;k=ZSi_O0l3Ob`&nKd zMX#>rSd^zK$b+y;G=J1ln3%uK9DR?XwyZ705b9&gE^^{uyi(X~?&HXP7(C6MqCNz{ z41E0?nCESGn=MJ38)A}uawdd zaTI(AR~!YRj2{pQ|zOk!6wTk?@)LsA7j6TiO%w4Mcb4 zapflKB`va*-fI*R8yMghDWN4^1&WYLY%dk0O0!1kk(Yo&X0SddG&w8l9FS*rml@`O zQ&5~1!ED^^TfXA32pB-kt|PazYKiI&jZq6c9Vek+fB-0ZnKY(Pib(y~7+)4bOv}!^ zi6#nD>A6()Vi?y0UL}A%0g?=;gGDizf{_wA9)IJ?&c$k+YY^X*G=#{p#?Ez>0%C($Kvc;fC7biZSW01y1GTZKS<~LpDFuuJL2`1bSw}$a z(W4vt(FeDKvyL4q=`H_VgshGeLp=lj#XthQ{d#Y+gaV#BejudWt-hs`eJOvCwd6eu z&&5!XrXO*wj4N2XAep6dHUzz&dE>RBNts15?t}3d*3@_Pm6cO6KWNbuUIZeg7w_!O z=Z@Qq?zy{w#!7ln>ON_=r<2ZcsxSNtyJsD!s95ji9TSHc?dO z5!h&_?;@(IImW9{DnX{5Y^@pC9*>r2N?QaFPQXmIF;NWn#6K73;PW;a+@PD}BM_g4 zR?-(aiNbUmjXw}YO7NhnR$tdFb2sdrVmVq&${MFUr2%B%>>;3D^ zkHW%@-wG2gq36z6PRALxNwK0G=^1pa!jVTa`#`B7wW z&0X9X6t`!QF?-8-Uc<55hq8a2Qh$7qnY%}Oc=QhUKNTyT>@gL`utweZX;w>@Hesng z5hXt1lm#sBe4s!@MyDPVtFO^laiWnk` z6zm7NS0F?*h=I3FY-o{_--r33rIU-bnPssRZeIP-ma`?}PX%H98>|f}8-`B@wny+% zvP6s%vrFF*w*B!ADn#Z?peGCc0L#@cO&h}Bv>1gRXmmqOv|321#lG(+j5;aMEuu@a z2|gh>iML21R2duZltb}o_DlMTGtcyA&wO~P_KY_a(vmB@q+ z&74LAN$3%uv9M^J2vV%A7}rZs|GT=*ySz9FIdxXDAyEIwj_7G}?WObg-${Vct^fs+ zlz;ovq!<=49F$r*uz|Mi@JJ2GJl+{}tF{8WyD+`FL>OXP^2??Bfo!q%)8+Dy7YfsE z-*C*3p_IGgarq{OS%0##?RV=z+$kxf)l#Zs+Y&Mwv2@d!Zj_hiyqH;!EJ|EYDfGKf zpJR(;*UIdkh`x7;HApRpe|`uvaQ1!l8?C?9CrKK)+&Ppx%8|3MJ$jeGg-G18%?g&q z-f5EoYQV^2d60#ght|&gGyUZK`#Dt7t;yqZFc#BMv1BpAvY#mEzkIky=hiC3pA`_0 zKXR5QlOdYIg_~cDK}7tB@EHF}YYSm@m2hAV)R&ETINvcar&^hoGzk_OspOJ3{BmtY zXQ6T%;S-HyLU_(w18!p=D{E`+ewlsUOnGGT{7+jD+^Fsz*1x+X-|Pyqi7D^I_7e1- zmS1;k(e3)jr(Lff;NO&y~;4 zT7ou!79Xhzu)+r;MVfdYf~M_63&WG%XT%EvFg$#Bg|^7+YY18q+3<>?mWs_!;TA`= z36ZIb9BZFF076Lz@i%gZvT!>{xplqXpM#2vapOq=VEO1r<5wBB20+0WW>vGNms}yU zkL+J_w~F-j249!R@D) zl87t;??{Qp%1l^X3L{gpWPmXL%2#$phyh2MZ3!@iX%@X>Ji-80&jGpdE~a6&1Fp7M z*@ZY6?b!_+Iu6IQx}AE)&K2DVV8Oh3}tvvaRq_U*d~ioN~6W{Jizyaxm&SXtpu=^!pD9pIX`KRl^NM*CcUj5s5> zol1)>Qq%p}Z?fMSBX(tlT3BikjmJ!W<1}VbJm3kh2<3e3R5-|Uc=d8f%|*oTttvRF z8<)dIq#`AIoN&)NL@U~o4^}zU7|Hy!^{s5Bv*T^5MYx42x+*tip>^BHP8s3E=q(JE~`mrDOVqVg^S#FGTY;bjRG1RG`b`jaE3`WKLwNKxYoweY)8Z1;~THX_&;dNi?NBYHF`)mjqnu)`m&dELZF&ZMuVZgYbX zQw!g{X_JuT<;!e%PvhY%NN1*_Pr0!WgS+4=c7f`UY!9sa@gf8GYMwSM^mb*{xZdyT ztAVarWIT6#Y<&+SSy4(#36lUif}Pj?mQ_=>Vg0PsM%9vKCbrL6Bezr=1Dyc!PO%JF zfIdAvCtwGkiDRf;@7ivJYeOIzV&uQd0!Cdgr<7Dc(v*_Qui-6#&M^Pi9`=}-SR0Zp*+<^6{{ZXHSlG#{VggjNr*qE`4ZsIF@aiUwMm6I3rEorYZrON!QqP0#St> zcTm;-=-6ITL~R`;Lf2B~jWT9hidlQhuR{k; zlmQL$BfGEPzRGN&DrEp{$?*Rp2(f6w=OmEFB5KPY((bNxsDq2Y4KvsFClyy#A>X&t zTB_f-Fds^hE<<@V8FbIU8%VN6`Ak^fQdGmK4EWo_L#^}yq2u|^{53?##XdzRNE6K}bF9bXja`W(?rp0^~9)d z4cyx9qEjTrJ~#r5>t0y_M{5e}K0Fj1 z{#r=E$YYp7P~2wG(Pnv4KqfKo*t5DCuVBE9^6n*(*WDeY6r zS0Xu$RFVT;20?5gm-YI%kRSh!*Xx&0r<4i5=AX@GhcTfhbmztYNH9zJg8O!VCDQKS zqr!_n{fi}%bM-T=i~9t=rE~?NDSZE9U-Fma3z4rYTMu&0QKr*sqcO4PG=a&6qaI9s zlY`PnV9;{#07aoKf1T<82dP9!F9x| zb&n(kzrP5QCv0qRDze3f_K0NCvTRv7W>4D4Zw{9U zbK&G55W;L|7~*Z2e%t|E_-rsHn#*km)vt#(E>IJUgc7?x-WG5EzWJr zx1@*~?bA~%iA&AD)vWlwX)U4%JirK)TB6+dZmKSm>~0y+;BEK&l@M`y79sVxpI5|B z*)H+p`IJ5IZda}nuOfm_)dQ>m(7iQe(K;&N`E_P%*k>bU#+ zTk%Yin)C@{t*pdR`Y1Imigy~%P&(+`AOZ`s^@yE}Jg)De>+MS2;!?guu%HXa!hMgq z!344Z<+t|=E@1heeBt*`w$U$cYd9}v|M#4_d!43bf00{{qUpw>bMr2w`4`6MN0QTjGHm?1z*k5s%?pL%P4F+$;_S*T6nrrpym!_HJclBE?+^Q??!Wt= z{`D_RD?5y|I}(ofbc)o87XHKPW!=~x&tO^i^duA{yWWhY55&Tkf!P4@S)#K8J+Yid z3o|bfjms*s?~)4vcfTPlCFNNRu&;XBly(+aJ^oERHzq>FHOP~zermkzP@m7?AVL_R z-!F0wHhaw%zd+DxR1D6}s{+OqsA4~EQ;XeTebz95+mWvik{vqN;Y7`+SuQBNQQvFpoCwT2KAT zQ<(B#MmcXd>$MtLyPkY`FUBkd7wAe8@5+N~?1w)16W-7S-pvNY+gJ}TVYUxw?U9o) zAZQ57sBIh~P45&bVEhbQ|9+F?T|V`zsHZVzSw<4z7v+!fmt>&b+UuDY_p{)EX`AT) z8v#~Lgm$?1hqTHgxP1iGOtR&2(sbNBTSY$!6P2fNsqF~F-!c?Th3NQoCg}XmZaE#+ z8S!pp9wkak+ea4Fk>Xv+YS!k|g!b5=@e3jRLwqOPTcLEnhFk(~6a5+Au*O(k}sM454QsGt^g;VjZAC+M8SI2o$dmZOLWYGGAWrfg)P?wdO zysavVhZu4H(*QPE9g|0Fq9$0)E)dDdreeSiWJ-v}&xVB7y9$o|idAJ+2CdX_iv15& zq=#7TJ?yJ;Mcd_KX#<5{*YnQvEw0XS=59&vq*rV3m+%{Q3;HQCh{$x>-`_9x%RIPuiUenNvanWSBvuJI zz?s>Po`c25>lTt&`U)Iuy>DOhx}*{sI698~dk`ai*Rphv3G!l?P9_SHBjD|aUL+oj zw|eN7vrBOo9$5o-4bN+a&T^L9-u_(>a#Kr3DP+Y2K{Rez+ELoX#5XEm>aaf$-PwOYFA>Jw86Zu=tY=5a~=H z;v~O)`!)vQ%Wg)imA5N2tr7oj?{rJoPg1x+|52xcnJ`IHMpq3pdxu z4iXdd=KMxV2IMvnE}nzksvOCl{_#=cPJTS= zY1J*Ce=q?{#K$d7>Ay6`If1@40=sjx`8x%5%HhLrL}K?g1a8+$i1u`-`{ zsS}2E`Vp?3Wsxar+jA{OM54~<9F)}h zAyU`e*GRu)l~xjc5UbDVZc=NFArNGcw$%<^cVL~SBVQ-%I;1-CbcWVpT&7-k+P%eW~Ja3+A#x3g2 zdkKOZGbgfv$P^ugD0Og3BFFuEmhHL$H>mg?%Kge{AB4tIFsHFW{1>_kPhR5Ay9K?E ztPPa0=62jxB9lY2vBqU_6sl<8k7@AfWU@6(Jvb^>Lu_Tbhnq*Tfi8EgGK}Bc(kX6r zm51N}`!QS8jo{SWMHIm(fH9*N1t$DnLk=!ufzZ8kc0dlX=p65wg#O2@PP5&nv{1pc zY%CmvU(FxLy>Vg*`8y|v>vlN9lv+{Yge|BldLg;QDH#5v_i+N#gjE4?+XbkwM&Y4X z4yRBRki9D%{>Mh zE^ktE=ywwjtpn}hn8^Bv>CJ%6g#*r*6p%DP>*K~R--geVUqplh%% zkNvwaCiQj32M(_L@{9&i%DPrDYnQq~6Xpc1xIstLKa4bYnqgs~WCV0skVe2_b^tq(`XS)yD7mJBCj@U_Nb zr}Smtrdl7_M+-$H4=$#w4xBiiYL0U%>3vKe^A@SESKi`K;oTk!O-S4ox&A{-%uuS?zE=pPST>lbY2<9+5YLkHStcR5EZ*(%sbu4`#EBL$pJFKWw*ANv zkSFWC%WfAaYJuH%^Cv}}StWr3krf$*5?oU@S%36$mcqEbDW(=1S~&!cxv%#5%lkriX~00MFB@ShZ8u>G6(i zp$Hk~hX8L}nl7y$$IcF!I4Kj*CM?eRSyevDyN+#bqH88GifF>-1>d*N;_H)RLL{O_ zMEb1dYT>uJJx2s-|9!IZ`b@zpS;wLYWh?n5Q4 z8^(eCvG@DQZh?x66r@3@$55qGAo()Aq$jZ_Un3J?jq$LV4N09QfY5x7joT%T_{LCa z@Hf}-#@~4Dx1xUxfBddEq-0S}1ylp}u0;Rl@NMUrdi#{qd-9W+rSKM4*Wd6Jp9g-W zj~`h^$i%#G9XKE~lA$~ie2|H{*49OkPDB@7bB&nLhegsBa!BZyQuLmfW-_2abET~` zI0;W*Tj+{z^)sZhP@0{%Ay%@AP1vQ+KJ`rr;Ja&3CMu@Jy475>j!DnaIMc8nVr4bV zs0=k(@*imFQHPgEDf1|dcY0UdXa`7a|LXE_d#U#T;B=~!7PF4%01T6gqvazeie(=Y z)9a`?8EYySQ^AD>w)e`7PE)v9Mvl~nZ zm6G{?OVwE62Rz>>qi~z0gq1#(p+U-(wVr0PRHfe$Po)H~$2;L!NM(4n(k?%7fPkZPHDGZ`BE3mXSNFNIkFYa&!(<0sesl zn?OC6)YYv_G0Efb>c6xWqEYs5R$!i0U0bRBCBuO=-{^ao6*S{|nUH!p)75jnv8n0T znN>8ArdX7EeqJ2Q=N|6i^hG2gE(5YV(jD}+GY`iUhUyZwJ)+bC-bUKf8FSuzYi+bu zV?wp-E@0R%F_ojwwg(Z?{3t(Z@fM2keYZo3CS@<}b~5NtkCNo%Ocrw@<^-O4SvSS*OZ00n}KD(vqn+ig4=f1zu@m{P7q7Upi=qlX5N zIj|RNhB>=1_95QVp6V`3gP3YT{InFMH!$ip_QP9uKd1uZ09J$?;`}%Ysah<{o&fpF z>A-k_d{x&Raqy+CCbgis{h5jMml=X*8R}wjC&krSFS^?oGsVvFr{;KKPCCo255{kL z>&2?bZV|jI7{Cfd&Ls2YL{C2;P3@zy*Gw9}TKxiyWmP1oiFyS@5KwMi^mze9@YES@ zCb-u3f!M#pGM-9GrUF@|NK&F2*kx&l@ZE_#2M+WP$9Bc|t4`)vLZR3hHi(qmW*f>_an05JkG7w(K zBFc)W=4G~E@7MjRZ{PkN**ZVnshH1iy@#InP-oAztfSBSmbAj&Y<%ggc&;*P`+`?E zTi06a(6C_cKT(OC1E>;US2(Q< zLzAILQD)EPvt`tZ!WhrZ#>LJneut}!!kPoau%n!l>D{u{-*1e|T`6M8oK2jfc46eR z{Laq_OwA{No3jB4d$^Oa!p)gDQn}}p!3GHx_PiC+;ls%n7^f(ul|vrbK$~zg6P5=4 z{grSDwtdo_5OMAF5!k&)wEMu7>(9N3^ci0p?coKze#`y4MA>9^m4#$uJ%JjWyIgA# z1`yvs2g+wo1Jhv{;Yw(@{Pj|i*pk7x$zWPVcF|%QEztm`Jkq|m_tMI@^3FmQdsTya zania*kBpv?FMgtscpC_0%${d|iDk2<1Um(`lQGbNZalrrVpwRnAvMIqjm|8v76dQ?X zwtzliCc@m%D;A5*EK*H808Jqf-qc^#e~Z4nY$U^;VI!Od1Higg(H)NEW~w4Ray#=9 z*~ZNh!P>LO`x{t&0q8E2Dk`7DE@I&k2UhaBdRYGN>>=DhJt0Bvz=TcLFC+Z>G-8q8 zuvCf|m2tsklp@|(CAC<{^7}_y&o5afH~taNv_;q}vNl~35v>o}oy|9o&(6C&1}cS! z+G={$w3@Z6adIpw6zhE#R(U zQVG#vBa$6CE9_`!=q=ixXlU#jzcL#tU$o?=~+6d5)ImHn=D#XO{uKJz!7TR?$MAOy4z#S_Q;8|$SgH+J;^?1I*y^f zbK11+2(da1)GieKemAjQVkB(p36D8-UyM3Q{0?7UxXaTbJ3ObB~h zMMX}Ya&vNsE>>~kR@_mhwcZ`zx~sA-QBg$ahG{r&ej~Fh)jzVkk}qloXu*u%-;-CM z%tQ{S`V{>8SSg^6jO)xUuBUQTdH9B10t_X>VGSAobo7RXL#f0qhcJzV0767~EFvbz zQI^@O;zx}NhCyig6i!2%n277d*fk9;b1dws+HPza69J^`>0=HAtdG8~BX8b(c>|J{ z(GkAZAO15!ZVE|v?@bC+w+nfV)yp*|i?5~GygG}GxsDE=$T-)Fg~ZEzs%s#M9*XRe zT(<7DA$nVNd7tm(%EdIV zek^7)=grv2u@ueWrNjX?{n8&jTM+CrO#n=SuCqI(e$ELXKAfI9O^AEL0n=#bvWw%t zQ%q|$*cfLUXcy5@C+pVtx7Lug^~|WCKa{N5swy%j%TlgGN;bdV#(B*E6uwRUWKDt{ z-r*SSb6sRi21aoG-QuE`yV?kPJnww!zxQ|@K;?Rh`7~CJ_}>CDw3!o<*@h=3K3MD4 zH#UwgEPO~4AVXnsp;FGg}Dl%(>&yNJII0i*x2n$}N zPb>f9>n7cJhiF7;5t-a|-Hy93+g6usI|}xRkW}%K7+X4{qzw`qbu8;BgilcmxSgDy zB%x`r33waxW-Vgz0#(CZxyQ6;#**xZ9z;Z^WysB`)QFQUy2iLQ942yzn}U>aJ4ztnxuN>9JU>0Y@e$1#73uG= zz|3iyVh(0jK1fEIhx~3GcuPK_85tTi=Gx6PteD;Lz2B5`cm~-;Ama-M?pAqqy`{~7 zjvfPeiSusggbM$PHBRB{BC#e0(lN&k3%VAxG=|ltp(d{Lv@kG7!#!mz%&Vvq9h`ieE-^>S66)qBvY8(;w zCt;HRjAifnV4X_vUG7|1DQws=mWWr_5DjFaXI0UoL%b~3F~-){>Ft=^9<%BC6`_N# zoElSbr~S5gG@H|^h94)K=!PXx(5v7jMU-|T25(~C=_4!0y?Xmb*MN=@5LH^K@;}6G zWp4%4k01q;aN0@3QCDGr!wK2);9HZu#kMJ)ChRTmLCe^RLu?N}!=jS>^w@tZ%u>M~ z@$>V#@zm=Ze-E|M3Np^se(RHmo&4^1vWc?No76vYXi#kwTruM%W5_PfoE%P$H3r@d z_)qcKW^k|Pa2r#*T^+bdO0cF+lu}Bx%VaVEM9b3dS#3}nD;4)50&~G1vNfHlGtENlK2}jeq@HQ>o|f{=j-tC5D8yfhx%TISPM3W* zKTpcgliJ9268^P_U{|J=2uk|xI1rAx5B<9tNtQ4OtU%a^#D}L1wH9%zbxEX01>YTr z8*l;5JG~0@`u(jM?Th8p`9&NMX&&N}eLJJGsuMbpuHUGe)jCzDI_;mr$cxn)e`B#) zljOH%3~`J){|>uf3ykLZuude(UR_=(f{^YrVz^*MeTa<^F%(U4Esg?ABjtn$pe%rN zGGVviCY?=6d#tsXJyH?BF6tVCjO$!lCy;=(?t4&V{lQ)UL-Ilt8^56nVK6ssmo$F1 zS;%zzPvIzHS3A=Nqj{`7SZA%P!uY8A&5%iWuMCn8XG!cmrT+k!f=;-34e@Nf6|J_-ZVD!e5Of;zyg)En)9Uao zN-u@p16g?DMt)1~CRp9P#lVofAZM`1!1S!1pLFvi_Rrj$7l=n-N0wXP$Xt+fDJMnM zhgp0-*OHO~+jsi<%)$%$g>`SN48?ytMC0ingX1}oZ|zi+yaW0~#20en6DyPr>vSw= zFdhcNABEDUqBS?pR)t0tm)c7aqeRc>OYBIlz(7OnTa0s6IK-!fHOCN%YAzuh!8R0?iPPak3i? zkHtf6o_jt8D0vR@JSeiR9#AN8aQxWkLTeHv014iy|$L1066bq6Ojo2&sv zCq4!=LKjO_$mDkxigz`zsU~-3OI7B+HsTe3{vOib$PP#l2h#A9Zxcj`Z{p=e03SF} z>||~0;H(5W;oG(x6JxAZ3Lu3-aEkI~V?2doo4Tx|0hCEkfr4k_>N z7z&fW#{*Ou#-4JC#jBQ!0Vc~Vz)n-t>CbMSXY||x;m_&Sb?0t8V{LNWu7HW-b?&E- zyty3R>@CAn3-->^KGSWla;oR9{2VJaW-BGS&O}s@9to#)9oopC7z_f=3gliP_rZx& zHq|i0-sXbg8lnFb`6WR2zERKQ(fa6b=qptF9CVzKIMcEmy+9PQ6BZYsZE- znRMqj%-hBG_?DM#tMyGj6$nv=Ax+2^c@7j;W+JJQ{K?$Tl9ffwE?BCZkao&tnPn^R zGd6xzV5?^vE9nofSt%NfFHw-3oSm4Ps0B5to+5`|)6FK`v^BN9hYrX=Ca2F4(JXI#?Ugqp}?p!S@Ipu>TfXz z(W>{&M_v~qlEpJ=B_$>A*oh2}r`%1^t~eOfaBM)!TPwp>Glw(g>ndWPkYo*`{CS(- zVopuC3KKQn@1NS_Jg9+Ibp~QVds^NtT=XS2cF>&73nawEDl?9O;VNJ#U>nUYmzQ&o zqfyogA#{bAVt_p=gK$00Grp6VvZ8)Q0am?@uSo)u+RAA7S&3LPj$W>9xf1zX!XL>> zInqzn$?9XXCuyhWO3kbwRW&$EHJA@Y_{YrZbt|(sB}@ua=eQ!?Tko4j?IN=wtH7-(=h zAzU`=Oa(N5BXhb~Q@0=ygSGs=Q;_m>a9Sii{8>3dtnh6h-5I5%+VcBv4z7}PQDi$X zTk*)-$Y$dB0XKyRET2j~0!BFt84!lH)rMhcTg-_C@AlI{XpL2r6##jb4ve$Oco zSgxj~`w=iwiGL@lheydMI2D~5#3*Z7#HJ(N=kV{Fw>ZKqIM1jFm&6La+P@xhdh;??63Ig zv|u?ZmX#uo&J)5O<_TPZ1`|=cDLo_PgF=ccpY3xz6YB(EH&@w>d18GfDuffJq}$m{ zATW$W0hG{}?+K*d{3A&hqFL{N8ynj4G9P&pI`zBs=&Zw&4dA-?lIxbT21RF(PfKc}g_0Ic$$XZ6m*# zX`U{*tVy1+!0jtm4NMr&!sZ%gw%W9iT*>+3*n!8P4;Bn>>^|ft$&91)Y(QkmE)B?h z@(MbjFdxUEFF58VE}X}JluMRNqDlM%IFY{`q(U| zzny$h7{lP>cED5<#XCdh$3GAV#jzSlBa)cLTuz$r_iyv=R;7^rgG0Z`$#7WLA9rt`gc&i#K57u|nSrQcp};c-g9>;rne{o4CPt>F zRLAS$3ml8AxU9ZZag%j~byn8(DECn~@J6^fvY{dNicS{fXk8v1!aok2#nWWqpMX~W zqddmeHE8JQ7-hhf8Y@Z=|K-T5ILhy7NQ}f~CYe>JgO(oy{jK_yVDCDFAA4PZRgTs~ zC`Xa78WECK!brW0JXYAqJbs;h=ot;JG~x+sTFjS zMOiq|&dYOt3d;szQdQ~^a+c?8to(9^S`4RJCSshK9@k6G-IT}~%E4IbB9dyj!NJ!= z{(M)a5i?|2Y4_<1@IWaBo`^r_Go6zQO#s4>L;b16c#c~Z2{XZJkoG7y;Szy&s|}(B z++J4k8Kjj)%ZDlo5zLuBw?6O;%L}dnw1uQve(pXY_HjEmVTj21HP`c$--2=K-pT5F zz?a~5m&EDqy$?*CIcFCBR8WjWQzH?DLseYuBkXc1nNAI?KbRrl-FFI6WK|1i)FP0X z%4==r?`Hi=T;07Ix!xt9Z!VS`GQ=nMf9kJK{tu?mdn z{zl7=pSr`q(bHE1D#zb?z_p8VB@)qvC;U}-qTL|;XmxGb`bq*MP9odz+Yem!4aT9W zQ|K(I^f_`E*u8W*3q)T8GceCXX*`lm=EwV2g=s;F?T7G#1ElM-hjWElwPxLpCeE=Q zhXfBK2S>sbuJ)d=$3GlqS-Zrt3?-J+y_ekPS<>v3Y5yN-Zy6LY$h^h^Qp)va^EYIA3y9Ip>M`%Q?S}*&-I@Df>bbk9urs`mi$ZHeyzJva z74W3ZTgTaI7i4Om;V*co&$iBdS>CvDTm61IQbDKO2X}s2=V1YaHFp(~1gDeoYCxIB}^Y1smCSm}p}C`|>^Ibs;9 zdRMBs^Kq!FK2E4a>Of|ps~&%+Hy0!9s21tzOey9Cs6$O7gFW+&lV)30HCRZ?i!lv3(5HmYVpf zQFNs7#&XO|+>=#YJbXHcsB@x zI74%nHu?sft8^HZVN|Y~!ze@LfsV89bF;^XC61A}F1pbNJ4z+0Ks?m~`-(O0(+r1yOc*21_C0C3B^RIA^Pk$m6YtN6uZ)Zpd(UPN! zC}X!VmX>k8n-Qw4q%5%?92sFZRf;Yyyu@WAtU`G~q*A%XSf&}isZdLi;PpJ$h7omE zh#9kcvlyom^76$oEaaG#?Y*!d(aEQ}E@mSnu)(r|dGkRPtF;cVgXCLgE7E>t3Mvvv zA4bQh-wXU@hB~E5lF!02^pfzpcDLs4UoY&E)awYjwZSF~Y1ny6&A2TgUcu#U6&2ja zE+!@mi|=WsJs8h35Iy`Zu_UU6+%q;ZV z?iepg8|4%~Z^Gqv_M+O6F6ip&g0_{hMjr{07}cn=0yFmIk!>>S0kAZF)F<0ns5^pQN4W5aF@Pxl~c`8O-#Y^&3{C zM`2|4Z*g~ya))ux1vPUn#=*}H2{lq_SmV_YdN)nqH*Av7m}5hlEhTQU(akZb8zh)N zrZqMP!`LKoT%AxL7rwS-o+1lo!*O0o!5@gMvVoz7+x=>OvYm1blQ_zx{uPsYu~fNN zNnqSdAzZ_Fsv&I|ixM2weq?>hBeC|MOWxG4m1FQfrlq|AXYno1NBBXZFl4UV1JjCM z`3G*k-hCxs659dS6S=xgu^c(UfiX5tezdRTy&Kb8zYyc$Agt#U%@GjYS)we%zU$>b zUGgJz&Tbl1nFgr4R@f7+sC=3UR1~GfZ{AUt*ZGvO*$PdGH*^oqXD@)7NEpSD7oqaH zbBs}g;t5UZ3MhfCqBTdwN3UV1`-nltAp zo=;utQ;T8bvQ+D~m4=+ap4#Y-OjL)pT)bSFOB-dz`nu9-Ctcv3V4pBoOwyGSBo^s}bSu2xbwyEq)P-|Z-E2Z9 zm@_8sJNjGBva!JJOJ< z2v5+_&?&ve=cgftevC(YJ9|!OKwKm0G$U?PvIWz$Fbpy%#BGeUChHjGjroEzD&n`Q zAL%l!;vcCga!zwd;b}bMQ|n&P_}gzzTXjO`713>x`IVyexxB*y%mdi9 z*q<#z%N=r0&Zlx2aqiJPnU$?oLT#H$o=?>`w0wtcE}5I{Fma5M->XfIMiQmHfNzVc z=;@=THbz&EN~pEAX2toP`zM?7SR$sv^W1>)q8C|08Q{sLM_)U|<6BInGASo=2!Y_d zQ56_-xU`Z}IZ+3HybGo9qHMG`0_s+YwZp#?(WHLM3KZd$l}lF26)2R{4&NVmxOBpv zaV2OCE~FWf-LU`jW;|A`i;q4fu_9TESxGVzQycQW86`;*6gHI1$gX7Bgswr!joiB& zW$yopX;i1_RLOzcHGjZZkdv!=0-jK351q1$5>%~+YJuK3%WoXW2cIx&+VN9XgNql( zKOXE4$)~G2#x>q1fDRZPY9X(l(}X*=lqt=2rxwS3x7Im_EkI>cbX_TBC##rbmK~|+ z4IlS`0|yCakr~9T-t23prdyNC{owvJdX~HPUP(*CZF!P*!;Z_X8@Cw^;O&okFZ+iv z0)42X9#3tm>BlRPBj$b!Qo5F`MHuy<{|a%<r*<=8!gL9o+wHk)_M|BZe<$xyLK!joZKp#|*AL&}4@kGz>P* zSNV@sRN#LIr4y>-p2`1GnkYEV2csw9Pl`!SGMYX9k{OAiP?21By3PqzPImg>Nn%8D z#W>TX#KQ-Ud!1N99;&HiN5SJ<4RBb^q{(xiXQnz33paaAnJ zjT3_n)@AJ+1o2#aONb@g_1&yk$soxgd>i(m)pB#Q7PNll-%{Cd2YlM&G)YQT?VJr7 zX*SJ=M3aL));RcKlw^f7u1!JI^-viUh`Tc~ytWUV-h6{={>6{J&2cscp{nhp;Np-R%%TYUSGb#^v23v1j)xaOT;B$e9 z#E%3qGtu2sV~R^U`a*^*jKBeDElDXavxWThb%a?aCp0=T~}{D*QM37vANor0TivjX5K(D`T1`7eB? zLp2kXT-0*)CIY+@G_tPGL0Q)hN?BmHcYfG!W+e z#5oqjaQCfuuPLP+J8+J<#YMjkiDH=}4X%91o~O8?9bo=Po+W4Xnolh05ut$6>)YEG z#G+HJl>xYnWRa8Ss;a6g`isn;+XuNlFVjnBz+;n^LzxHh%8l67gnJ|J0R-ZrgoF}X z+an6XNRJ@P6A5b>tyrg4D?YqDFF7|MPv~fOGa$I>@kZ4r5cUxS2Hbgf@ zFJsweY*9BFmQ+T423x3+aMsm5_mcvAood3F&>lY)A#|xYw=0*6%CW(f5%C1UF(n!7-$M zf;D59U!7am*2E$Ek7*U8!ja0t9g;>*e*XLuub2R;B|E{|i|1D5HlpFhRcst#tx^up z6sL9G8P2@b&@}o2`Rp!D1ZioYtp0-kv${8Gg3R9A)ML`{Rg`%NvTciP3Fhff54p0L z0Wu@QRHgTWDR{2~HhNV%S8Jp*wmOV|mwy={_om$y&|)Kf8{xYnnIj>Tvq-3?w-xIr zk$OhHS)~va&d71xH@$CuT?eai$pb(6HgVhhNtl)46l)H*6}yg31)pD9svQDli>KOIOvj=G4*TmJC~c2@aJ~ zVi!QUm0^t^B6qL&3zj-!Unk?eiv1{my8HpMDb%TNz{XmknYv{%Wj%*?$5ZRTlG>G!q*)wr3UvpzoVqW3eT;nnL=6k36^&};u@5EI*)=lTY`V)Iywvsdg6mjAr zNie(snAMsQ$2Uf0)|@BTe+DK5zs%v8;ALc__t`pg+2=R5cEprZFD^=uSsIuc0;$U= zAoD*d=dqO}kn2W+_H#5llExRAjcL4VJHJjX0lgB%r?Xl<#`n3EE*O+3_|%J%u{Ffe zpUQ|$vEyBnD0K0gCVH&ve#LybUcK0Wae@%cX1P^x{V!au=Tvxy9O$!VlPL8wy7FhV){BF?j7*UP>WuR+Ev zGVK|gmOVik;^hh473l+1$qOti$WAI{mQK}ZDw#|Bc=>7Ex+Ur8H6+@kM#bc zaBwUi%MGI6u~)CKO8kN`mY?{wqEC&p;P>y+CZw3}F=DK*v;AAVn1Z_{tTS~p4S7eH z3Kka_wAcni>rt5qw57~lryC?$$=P-`+4_rY5?So9G5ZS()*?{YLVt(X6+dT;M|wee zW5H=RVT7Sa=FfiKQ+InR`huikbu7I8)_+ZW3P#U$`3!xfwSvts4J|rlVJ=qk?4Kxc zSaIxBMT4(E`%`#BOJOj4=@%*Sj`f2zr+|*i`5>=&GO}R8I0tBFfa275Wkmroq zidYDV2-rd`2kmLbOfh0B9?Kt^k{O6NZ0&oj|WRfr^C; zCZ|FC`kY`<{bJN_!(PJ`^4et9c)3$;R&qt6_sA#bjFkIbp6&YZ^LVGK&k)8*f=*dX zWe`+LCE1#d9V5$a_Nxx(2-v(wb8f-hY{NbbZPK^MA5Ep1n;S?h3mH1DPqLap=r zExAIlN_Pq0pau5ZW?OEY<`dD)^1MO~!BVfcsO%8(jE^%CdfL72*~!Ww>C%FpD*RqC z$!+CX3|SP)yONL(BOq*=*vR5OUuAo3OdbPm-b&8qjB_bY#{oa)9hQ%#d%N3#Qwj}| zN}53$6>nJ$iHpRNMhHrd?U-0cF+&u}kHvI#$>Tc~;5Y2}YuOAtdY%yMcpF&u1pFCw zMf&$3*VxO>$tfFX*jWND$QKf7nP5;y?@S=#9Q?rxvOf~#xv|Ug7iibNo5DuwP5mWT zAjg9_jUh>_xs{IiJ}9Agf!!ACM}e!E4TObUso$hh>Zbg28Hb-VR^G30CiQRRA=4xY zimDMjilj>R#OY2D{LH~P#mdB@OI&u+g;*n7*6|~Lt!P2jAFqaXHs$%#=%=tXdmEs? zm_JYFe(HLSk??vgLXJJEJZz=f{e{FI5{k*FhTi47(1k`5e%n?H+P<`>Li3S9RdD~$pFt4ZDIrYx1D>fO6 zb8?5Of+S0VorpYrZ(Ut%)Mx1#869$i#rD`U3ol!=vp7e|us_D$FxWJb13Ul8Q8-X6 z_6w7~?YhRryPoV;>&}k76?@cDk6*emdcjbs-KhT0I=-O5RW3wW>!!}I89B?MnYeZx6?ybGq}2aly=r4r=MP6Qm2Q7+Fn{)ng==PEj6Lsnbwp_)} z&P+g~6;_tcv}ge5!dbHmUA57t)S@RsJLS3v(OKl}Lq{JtIX3D`gYVD<`*V>dT`Ed9San|@f@(qQ$L!K$ij`N(hq1WVo_wPb!pRl6J%ixjFe z_mL2&Sq51#SSbMP8=)SNPMp6x2Yfx~ESFW3s=%L^jqq-7= z0Jn(iP@$Z<;P998#b({g^Cs|bP*bz--*5SPV+h;ril%U&k1C z4xCD5nETfufI!uq{o(3@aw|lz_w5#&u&5!Wl#DRo$Vg)9I$&8{ZE?FLCH+{;AV6m# zN09vH*sushsX`?RZ|0Ey_TXKV^~rC$-%9_ZDXF-Yxm-f@DzS^!)LPd-35|u6ijp>C zIpKzVK||G~sg}f$i&z;_JeOm-_5kafAB{wnk`mLQ9vQ0K>RbY4S>kcd%z4`LtDN%3 z9V*122yOkGyVX6~;aqFO8pzKK+pe-gHz z&P5!PIm#7zo`HB4`j*)-<}Ci{3F!X`F$P-ll9AI&ovPx$ZZCIe%DulnWwz!kbMow~ z;pfCdt#YoSPl>(mT>Tj_`m5bM)lm4ASeZV7Mipyn3**StTzUlo86G1>-^ZA6d?X%u zO-Oe7SnsLYyJ~p$R!J0Ya;k!6&gu{65hMs>M+Q!_M2sfe{Uqk+heXapfy&T8LlaOe zKH0V=LAXG#@<+|sjco2vlMh^mW5>ngHE=6o?Ok(Q7&2#!3okTUe(~6DlK;7+Axqw% zuzDkNVN;2Hs7ncrv*Nd6c~weHZqsu^`xsdJbROb1aBHCRNA6)y5Jv~lXZMRq_^H1Z zQB_TiSNAd~9jsX6Oy{*b#T3hTa1>wP3|ZOtIN#B-V}5=+x{zH_ku}Z%Esz=Y6yLmx zvi$A+NAzBn7N7xpYZ$5VsdzVWU(QBo&X;B0K5Q!SP=hU+PQZ=i)w%pLs!A` z9{x-hEdIr`$d-QFoG9n8O)u?dp1rF3Jg(J;;-9>FyM#Yy^s(DDCJq)~TPNQm; zb;)sdddBbq84Lo4*mHQ>BTn?<)(2*vLT36zH9A7)^q7em<&Hi;>?jN6J@V^Fsu6jR zt+NJY4SY?v2m2<=LVd?y$wwM5U=F8QjvQT8z!pC~#qc0xIU^ z?Va=&!YQcE8ck3}YkSFTd-nsIMNKztM&fj&Q~K}6dGf8*H8T+GN~fLZYSG|zK5^0_LJJr%+Ro8S>CW6G zloQNTk++c^KjU6C_FTqrXP8%K-V;ZD5LX`dCkN{$m5g=6y65$y_=wSEdkS>Yz+d2Y zRh(y?k%;*Aih3CD0UqmU!{|e+>|XKs)tI716@z-%U{8gdn|-2%E*)^j&fttvIRz-W zj8&xseMn7cCE`6mUu5nOc2b2Jk2@vG=mX?w~7XQd^T}ep9?~T#$q8HCGU1@7Z=JK_3V6oeb>UC(`eG`3# zo3|+n=PP9%>vu>#6Bd+iSVa-FkY8uh63M?I=)`ULzWhRg+BmnNvl2tA;n0Uda-LzF zsx`1Mh{ebF)e-jKBGYH$0Xo{c)(&}osx_ij^TdQzKS8L*=;nxu<9AT{J#Ry6>ySCG-3DRlq~npuv}`z* zc{(GVWXGb!xgYud{{D=*O&ixk)qZw$HJ6^gzC@gFb}10%X_Vy!(2t+t7pjm`b{R`DY=l6I7a+>VeeKl3qNLY(Hh_ zklrgxz#lf5;GxLsGoJwX!Ba#~(xLO_>9*9M0N^7Xtm7h=j>0}!Bp8>0D3A&S&4^M~N>m2`vU$mz^Wnye#Gj(xRTXZ^l z_ZX7y)zHI3ovid!j-8;H_7~c&x=KDrm2x=;X~0Z`BD!k2csPwFObCb?8r%;K$9M+? zD6D`6VWnm!9gJqTg9BZsH|$<4Okigqhfz$FrfGoZ59P0=R^+N1>mt7(oy-L*;^!}$N z!Cu4n#pG_-+jZ)_Yvc^OWJX(21Lj$b3K4TtLx+8!csycMb)o)rIf&!3P>WQ3)Q+-! zT#S&tymN8xnTKdhQ&H>~5?KYBGllyoEW@fg66yT|7K>3hCy*Ytku#l2*VGc5& z>A?QbvHezepdUaly%*hp@c6R%${46KiyMEfZZr>Ixi&~GY*K89S$@#38Ey)K*VR(H zN-ZxcJ39I=xO80byDoVen)q?thTKy`D>}9fysTTQZLw<|YiSK>aN%F8RwxY26aM8& zZMdNPGW`%urI(xuS;|>+VQ+P@uz9IfPou%-&ILuAF;y`F#%7y)sywaJ9aTXx6> zE2Wd=3jJgs-E7#>-kdMD(55WO$R%Udh!uQ|CgpCHgkTic`SSK7y)j%6A!5|NnbbSXMk5j}Qq7TRAouNQZ9rG02#S^76BDZ2Pe+gD%mypN%hGC6!% z;~-Pht-EUvP9YLsU28Jj*P^byl%@Xu2Z!o;XAP|Hrhu7Rb9(4of`cA!N9+%bP>agM8Mj<)$cp9k%>tAtv zsn+u*=ZizJLzdj8ouBYSZ{a-!7Z+gsLeF(FONWsLI<4M!bzlhSr!mh>L95{jN7%XE z=>+Oz3`*>62-v-_-_HLw$o0d0l$KHMq6+&$tX0mT3zmTq&(6SKb_{>F;z?8{XZ7A< z91{uD1%zmxMv_W#crkmW##BQl#Fd5R(Ba3s`ju+-ks8M^4Z)^LVrLJ*Z7oADLM+?e zc#y6x<5)$Qv-F}D!7K@C>-+_P=7Y62He(xJtqM1MEuMr?AuEH9+sAXnOxa| znmozY>tDSs?mRtUYMab$O} z%uEl>%i8?&v%v&ZgVDV8VgMyJELA$h$0T5yko)s?hqHd&CNpw`>xzBZp;i@4j_VcAr)4;o~u zdrug?_#XSt#em{+J}3NgRXLeAOMMsN(MbvfNdh!*KI%7Q=eIsD=9hmLx1_7Qe1jQp zg|XoCurrDERw@6s)8A(~>hpng!$3>qn;@@*rQ+2czXH>7kChYM^b9&xRlmXH63pII z6(%xLOk#g~RK~YyukD_ypw(Xbl1HOkbM#E4>c**Qm%NSRsei5f1_YIjsEVmB5*A7i z=iJxH_*f!>4)fKvs2UC271D#oRZPuBwbwg>?9`G$bC6Oltv@ zsluoGci)7=7H#CV&yqd1p;~iHEE-hXS5fO5zVY5IZcjrSG8rgjtY3uEw(z0qx0=ZJ zPjw+$cH=ohBXT1v__5}v^&B=aYN%hBJB6JH_tQ8qsIjQ--rb7*ifiQ}aej7udF^bz zjJoZN&kaH9SvgQDR66gzFVrg<|4@ljKJ|&QD3f$-+hLO4N-<37m)5L@C*ez9>q$d8 ztDOUbRDp&T%8HQhwg#m9gZKwpT|`P?NA6x(eY3TIL{704)LrpA7fzK;#v5}F=5497 ziqZ^QG9xV^un<(q`^<4Z@iH2<~zRHSy%umS)j8HTeLOfgSPdBS%jSEF zk>Ur{vnS)iy9y;;>VIA&_E@21Vluv2?fk06%BN{OYJK!vr(b*P z$C>6tibK@d#}VZwmEMoBc`h9yH|_O6#^*4GqLvXiQ9X>9DJ)FQnc6-ZjPk0v5-5M$ zqS&;AC0E$jBu%Oc>d794T1J9PJ>DLVH5^%RF*^%{zi`ZXci8>xRR?EPb17%2zh>>6 zrD^I0$gl8g{p@Qa$BruZN-fuCL&6@j!m`+0u?+jsxQU6G?$*w=WyG})u&hhGKnix!gU9mMnE zdW{PcuHkCK%k0(Y69Bqch8U3LQ!Z5j;c3vMkd0 z1<_NBgrJ(8?Px)_%#nH#Z{x3T`SJuS1=+3sNM+lDEMhnQlvp)-I;GJ#jXBhac`;ft zWndG;ohHBaZ)zwh$0m@IOkPbS&W>QK!XoHf{ZNvP3yoVVvLj$jhbEIUbB z_vu@KG(fXN5o=Qe>iNNbX|;Y!qkmbtjnKpH138wKtD2C~2E`xEq=#~d5qRvzR zoWIm=bHr#L9G;Tik-J61nPAvY8*oE4P+($J&eH}z!Z|XT`i;oNanh1Kd@kzlEZ2{L zHz{u8_1WSUd@*NFaX3QI{-@v77!vP4YVUeORW zf+>a1x4l`VX=+VFC%jWC`b~Q3bvt-U3=6R?Icv^XWP#IPt;O@a;(20l-UYz?Nq9qO{U{lmMt7nLKI?+Ao`38jVkO=lLUi^n zfpe_?yO{&PqyceT*3<`EDt>+RMcm51HBDz4^qm^)9rh))3Wo}h^-mS*)XHnQ?bBJQ z?F3cmM3oDrWLXPWCTjCiF=C_U%cg(URI>$|q=j-ii1kVs?^6W>uGA{Ci!0`H(u}iV zYQ`foC9E>*=Y|P7qZnUt;!Yz1H-zeD(aU8i=~`y zmmG)OBz~BtI@njA=%UbMqs~tZ`-Z2YQS2z@2mNh0_7vR)flP{7M{KT1zlET?uv+U{ zP?N3CFK6)%7qPP2G=~0|;rD?mm#!<_JNJv%EKLp!~?a6xZ%XnTR0aKhCg`3({;PLyPx-+V)84OXlDUKwno_* zXdri@VM%;t$6F^=yp#MwIEI3eGC!z?)MOf9p7pXFBlbBt{JIiUC3Q$vk zN(P!QrP{NyUDI(?Jhz`z>dVl`r&K=NcvnFJzl|q2`jBT%8PL=|FI?vmcHXjDci-W7 zG^=Jz61-sEAtlY_Y&*%(vC)#JN6I33I3nLs>zrP{q+vo?qKOuLY;Muq`-vOhA?#i_ zr&BcRN&9Tt?ZRTOsZ39GjGQu-sd+I0zG$9IKGeiRmBBC$pwHvvb~ z!}zm)4xiB|Cu^p9MJemeTvt~D+TXQ>yu z;9wuL#=26iO;g@kMFU$BQ4_mk4HP%f(^9^KOHL{QEmj9cU*_t4X0LKZ2zcSyxV6Yw zijL}po{_jShgY5A`_ctohrNeHo6c^HV1Md|nw)e@)k_|Gnei`M#SAMTC1u#zNl3$Dt| z+GjiPqXL$5ZzN>Ar%fGqnY#uF-Xqlg^?@^T7=RkIt&sEJ8tq+JFg})PzD=rjp2GUP z8kKyKZF0_qRP?&&shaz?DW1#Y><0ial;6BlieGj3!P_8DC7;bOVgFK8hBC^M^;FG6 zp!>BO)$4qF-2RJIz2^xw8z9w6Ym|1RW`NM#$UI~F`l<7NW0dGe`f)*#b$_BEw~eeV zUSm8LVj$}+SqZ8#PLRBBdRQB``9l^hIsH2pu0=JJWSiapUg|6H?}re-+^g5pZwzy4 zl{U%=hBPdJcj^y2 z2@g9MW^f2kAe-5lkXe5qVrj@pc-@lrmJA-SEzu#oFNfZ}Y1AE2dlg;#WYs%p+uLWZ zV|kWXtLxC&b-$(gtBgqd{W#)!Zm~!4VN&Mib>}Jy&)2Pe$@#g!`BBqB%H?C!{M+B# z{COJJ(=zuu%hynEfIPC`{I(l*+p~OmdoS(kJ8I-}J!%$@$j1vh4(q&Kf_E3WfdxLT zU+pgSD7uun{SLBOxv5?mIm<^#Zk>ts+*J0AL`t6*Q7*vu1M%|hjF+(ghz9u~T$2{< zsmvUypxYkZ@Y`iu{mQz-|@{{PyWhP+{)^7!GoZE_UWKVcT&%Z z$OEd(rKJq4>jB8fzYE%l3_}0VbKm;-7udZt;BZ2J`L^ZpTKduNcJ6dfzo&hl)tkW> z4*n~AHlpY8KHxN^;Jet+zS*PS@(rx@v|r|~U*sI1@d)CoX}w3I@p)1$By;>s=E*mJ z9JTc{%Uhq}_-0$V;Zr1N^cL!8ML-Ew>{yhl)>rJx<>!3d@L7|f4*D%+6SS3;_ z%L1H5KzLm=e*G$oY)C)8Qos8&O%l;I-nBN@GYOX+t)>}@Ok)j<0_J2-r{E9IT}02s z{rzc1Pwv_FIIYi5k)U;v(`7_=#>&It3P8UU*MQ5!oro^@CIjs?I$+DItE(#q7j)*~Lk#i2 z47d(nU*Wj=Jc9>6-nw300}5fVp_j1UuL5+Ujib}vt)lq?nlJ%oyB&1B8+^us|KV?z zrw7m3D&+1{Javj7$O=k^LXDuJ#<1}>odS1 z>VL^8At51hcSm_mqu?T?bwl9$%1(gL>?*?SQ*-(cEB9Fp7hWk*(BqT84G6xg zA!t{Vz`c)JB*%fq#-$YiTkUvtV$}w_lv&v}bA8M|%iVr|b8a7WX&Zc59{6N#9)4*6 zKRi4E%t=q@+IDL}3@jUnJg;W@8!AcroxMq7Zw)2DtkOHK#~o;0Dg507=dY#rWKJJsjnvg^*f%^3$w?IrK_&VZ8Nw( zew)wQJxM#n->9)pO?k zfGcxCDAOB(McTd?Q~WpE6%3S*qlk81FhXLyp|0G+!m`z?FycY68nFm7)iHr{vz_gpsD2KGUg|0)+C=2iU=jl{7x2ZnFO z?Y#mZeZ;?|r|e#%yn>kBKv?c?=57IX!#nyA>$jn?g%-4mb`?T<72;?^zYN&sW3U+l z9B`cxECxWW0X$ud>3K2b!qHy~*L}Fy9Y_1HM%%shV;>Rxi13}xS^hpah)PLG*}1Fk z`UepNN4WU()VAK+`aLcf_<6+NzE9LTocQC@cvAtadaNJb8H&m|{YVQsIO6ZA@$&xGG zzrXh+0G7{@3b3cNTOnvP=7JP_6mdSIM$3J#Wo{-Zm;df7e?+B~0Q>S20QimX^rF!Q zOw|T=!FvzIcNf zmWS-yXFwL1*p;hQjCoX>)wIhSCCB{Rlo}+daU;cPv8N|b-P;?w^Q)_BF4OhrRvxHp z{YJAQrM)W?bV}B2gVr*nALA70MLwh^2j99ras}_Z-d7uSaU>1vFvqVCH-^~ui#|Zn zx(_QBHCE23y7#w(9=EqU7Bfck4i6o+Pd6Q0z2}Igm4&j@0!&DB`FL{Ku-5lfpraMq zE$PkG)}XBNT*{KB0|%@OYI6)Ro4LD{hvn0fvvI&&Mk%Dt7cL6)1*Cty zJDl%s0sCJMAv{U_qwDs+as9oU`rVBm)@1G?6O8D zxF!Gj%0(Us#{Toe-yZ?U**{(%@NaE6Q6YZ+@&T0?-6;ux65GG8HiZ9D8vgrg*jWkK z)Bbby|MM3A=Lh|M8q(e~D}n#-NBgF)!9U=i;iCWdt@{76=KNpZ_kS3`2EP04t-#>_ zZgd*}#V|bl5@6Bz*MNto$DC>Zd;TJm{^!X&`tVXJ`oF}EOpu`a@2SS6{hz1fe-QhB zJMWRW!Dvl;ldXPe`RpAMi>-d7xF`T)Eb#Z=i;m0W?No?+jI(l?4#Y9z4$Eh%GQpMJ zFaEXI8xsV8{{3gP|KA0TOk6Z8q4{4f{i3vH51}OYe=S;B4)Do;X&NrW|MTz_R`#er z{RRbWKU?woZ{dNR&1&Xz(tmVyv+;4e>mCg^q1-DV%>+7Cx= zNeY5Dow=58pVfjlo#$>IPmUaeGql|ge(?dv-Ook}{QEs{5BvV|9w?B$4>7F;qCI$2 z=+!tPFjqS8rnD>JKU201=xS{kAL@rz;4GKBo7zWlXq!=&sz_{Va_#*>?Pf>!e#e)J zJw3R_zctD^tmoMXon>LrGtuq8(M?2idI!8ahZG29SRskp(npVn3HX019&4G>LMk^o zV`N~4oe0tGnwxr<=|xX-`d=gK-L-ELunojV0?Cm7%(A2hAT6rBz&?*pFV+=a&k0&p z=Z-G=+7tOtAvrG%1bd>6((gOI_Z5Uj48=S{ocRvTBv-$=;|tyts$E@+q(%Lzg7}X) zXzPj!xdGcX{)!1XI$q8=3wyk~?LEwS&Hb_WLHw~b*ay#)^Dc1H@)*t4C?Hnm%I~lM z*3Y0bbh~`nvlcjc82ryz$5CI6j-J07y_+wS?)Mbm9$w%d?T_^{{k><>0Woka-_LFr z7Tr$NPG-zzRMu2k+FQEq9QF@{#nWt#13dgjMjf0yIAZnE#o5qorQLZ|?3n_jj{5q*JKJrnKK5M%w!s%K)!^zK1^2AimcLaaU(=@-~48mT~hOXmEZ4`&WlNLKEZ%SMV`V1cK}DZrsW|PvCT;UP9EQY1Y~TGhIia|{{r5}u z`v+CezF^NyNWz)3tKT!Qt`70XwpuZOm4vcZ0=f-#I^Cvy#OLY0B{N)^C*PnPFdT&* zaetl_GQEb>zLUX1w@-JwyK;-q6NL@|F4wQ+c?)ab>-doxp4#dY|KL;f+MuyYrRLHV zoz69`M*;X%6zx0SJ9=(7$=w`YPeO*wHhOuG*T<%DsP~2F-OQUu!SxevP`UuwIXUh} zAcZ=`$ux-}hkki^=>u@kwLL!EYN@#0%#=S{L=O=sn|n|vwWh_c7+AOMzwiBaXI^+j zDbcS&4G4NU-9og1yW9CzMGQ?jGYUdb(~+yp$~t(<~FrfRmT6`$#Pg; z6)|a+{&sq|dU?~93N9tl(Q*DtwR1<2r=(_NDDWW0^o~IKa`Lek7`kI+rFU%a_siRI8APR$ouM`UF=o@B*H;G6c zyD%@U&sR#>mg4TOp0I1yLhAo{`8G7@53LWfQs+C(!Jwp16!zE#k!SgWx&Wy)z~hNW zdo}ET9m6B3e#$vk*8()5cp(M@Ce5y5QmwDU6Nrnq1^_SyZNTI0&R@>yM?LrhSostP zi>VyT1nJ!vi)Tln|w* zTe=77Zjc<1lrBMOL^_5B>CT~>p=+pN-Wz}aXRT*FU*8Y2X4cHjxw-e*=j^?&YoEOd zOZUmcTU0Mh97Py)(obC=2aY{j$t z0Uji!3Bi)^G0~fe>~{!Pdg_#dUtQD6hFDHcXK33aM!<=uZ>z_ekWJ<;-4EtA0@GkM zE&ky}sT>D(8oAhb3syEZHbIRN!x?_yVyS0BersKX;+7JeKNWi{>07#hCRt#V@Vbg<%mK02QOa7D7?d+X=fwO^B6#Oa1v~cva&{5OpC|k_EOc*}C$i=k7 z7{eggXcNOZ)Zfc{LF15fsJ1lxjJLP`bS5q@YLw+K-K>6oHuyg;_0Z>!g7!c!LAAPv z$=fHHTiBF%q}IIFJ30N`RW)LZOd?a;Q|F|<6i!?H?m_TDR?if@npj@tOa#9 zN-Z)kHQ^<$PFno-oZ^Dfk@)*>pp6}F&6(-BrAD8{?Tz<6Hmx7h#IQM~BB@am&v>r7 zM8SJM1`WU`rVQJY6ut-lo(gby{6*G3EMV;|F=g})yo@%)>9g$laEB0(;tgaX^)@RAj4_rWLg6c`=g zF+}8{5-Z{jiZRkq7PuG_3MotMnVeHIcrE*{aQe>KW}RR5HZ?(AM_R1d8$gF*YD-Ij zjc}rnmAg=9sOt^K9?j?^KBJKTp%_jeyc|~V#|s*EKAmKZ&2Elz1~e~so8`rI(6(Kr zHJjUZn%&Pz`g=*_mFV^8t$*m;}>Ri#X|l2F62tpm&_EORf;b*zY1pRq%wc($7PfsPXvzboxU)- zu{mtmHas7i{Ha9_ln{3Yi$~T7-d}szQc2!~`|Ovl|1i3;ITRUR?*z(nddY!#%fI-+ z#UZgAoK-%{XO8pU4Rf@`Qi~!{HAE$JLUH4q^2*e|qTKIb@YZa|X)}`To_Z)~zHO#{ z`uWKo3L@uR2AV42%Q)dcn~^TGWubb$w77_QnTgT*ekh9V(zNQm`ai$OU4+wn_3S;v z9Up_=Hd5(@Rv)(S-mU-*Q#VcPxdzl58Fifi_Dw9yWS`cY&YG-^;nr^iWqqi-QCxZo z)Q0{jy#9bZ8eR2k@nkDj{E{Yl;AgJ;817ID%jf2xU0Rz7{_T2Q&PR>upTv9Q{ML~x z?sni+3_3JL)51&9tIfvF*>LeyRvG4`S+^EtK2m*crAC#$Q-!sqWH^4xH`|VCijxrq{&Ucg@>rS`Y^4 z15J7?Z&Fm~Kwd9!GHzmK=tU{`w6PUYL%Yw-b7)2J%? z?Mwe=eEQv`K$u_Jtxx^3D1!rhwTL|=9$)14Ulfp~s6RKkv(qEOaC1O>stADhK6|<0 z*;6wTKgeioVZb6JoA?_IzuWU)i{}Na4Q>g+SS+0B{WcygA+qlupXsz46e(9#)%q=n z&g+A_pVApy^Q}!VASnUxm7-ESlP6QWsd1dXF}l%tRj#)En*2b9B1&#i`la~$jy4qq zM)KVH1Y17AwfRK_xIY}b`WWpMI)!Cy|Cgka4@JT$*ynt!ZjWB6QP5eu%a4gybCozD z+5G1o3)5zZ_$4l1w_#rw3bsbLBm0{?x}yv|O5As;m`0pq8N_9i&GlQGyG<>Jk5`K5 zyR@S;BMl|DvnGmkjEqcOJwwGt94|N|nP}&gNL)G@o_xYm2?CB?_V47n=V}r=R6*;YtH| z?uU7+V~gv=&?RUSNwYgZ+q4lm5cYL)Hb8lER8B-?>js6$kv~DRkqX)&B)I`9^5`BwUuW;{= z4=E}N-04Fskt9Fs_OW+w*OGmOqw8Pg8&ZyJe(yAl_o7fV*|}d&=S;z<%BipxpXmO) zJ;8e)3VeTnb=OLbY7@Y>(W&_5X?d=>RX=F@Hs%hy1Ip*mE&i*3hcUViZ9PNYFgHN2RmM&00ltr!OdQ zE={y}*xkwBsBuLXv=U*!)%tYg5rFW&ynMl}7+P%PqeL+LzcCNB0mtXC8Q?s1zMt$5)8Q+SU;2~Z)(JkU)AnfleC(&O!f_`Z$8n7R zrpXrVV)mTne~`!Uh6?81QZV-uQ^jr0!{b@oKQyhs|BkI4{2>IFoAl1M#xi3JcqdsJ z9tg7Q{-pW=vu`8`aL3I5?2q;|ZDsMpv7e+B1%5?Ms%T` ztS7hJ;rriw-J){etO~1jW+rz|c=n=@;QAhtV@!(Vy>vuR1-u}M3WxP=3ct^l!r8pFH=s3BPF4kw{S91DROG5y?HAa}@9-eFyyjhAEjzpHifhZDDR5F*g2n71T98{uRhRkgHDu~@ z9ahamFx~HGO1do0*skBBpZK%IJi_-??OyY})j0{SGpb^55A2O#Gqm#CGQHvyLj0j? zV(v_$a{obAq?ENGikvsv^n@?EuiUd$XDoU9!680h;l3;Ab`FN0fg)@({!?0~jWeya z>GB3_`TpjW7w;;ZHV?E8mtzil*ISM*UCW^(e2T(u=PcFN8Hxlney22V;7Vr0 z=z48^4N)N1t?)Bv$Z-@7qqZ*f31)={=JNd(L^^9*dG8{tpWCn5_n*3NoUH}c+!tFH{PSmCYjsvGNzXI#v|JUFPzp<$dP}!2bKPBEiT_|E(GSb-`;SVqY{) z?Ig*6myJm5a=`G+rgfd2cocR~?sdTI6$3Cp35#IZNwOf4JPX-;dzI^d!s&TFNcAA) z4g{x%1i+>u@qvL`f{_`}U5YAKw7UhmYyAX_EY!w`cq^;mB!hqUN4ke;B&*jq{DW z<1MRzpx|}u5dv~DP5}uS0jN>P}!%-aNfmUbkcq3%GB)GZ%ky;`NJ>vz*l}T?3g44th+Fb(-bA2afU~ zZa4#806y{MEz6IG@2{fmk^QA>NvNZBExU>U;MKYj1^N0<+|cW@nMDg4lGYO%2ml86 z(zM=OG~bFNzXK2NXh8ndf1$257k-NZR98l5b@W%OI4g)f zQKwx0hw4LF!r4@M-IGIyg`1thY-Nw6a|+BPvLBkp=`G)AKIDQd?bRJ8f`)w!Q4iLg zS_U`v_VC(J=Xx&c>l<*}kf&he-@pY<{d$inQ?29_$qIyj9>SaF)=3;oQDq4sPU!+M zf~CF|JhHX6#tk?)I5=z_&`aqwO}m}%)nA(TnvWg193JL3GL5GZmRPn3AdL74n*nb>6wbrhXeS);&R2528Vuq-=W9g{GJUw^`fSx z=%U343T@uw&)Pq^?uVR{`&`=^4&__Yudn;x6g8h2KsrY5_C>S42w}$&p*?${(pNO`d?iBsclWBMa_%VJttMPD;?TW;o>nHN8!hUx`y0+i= zR*U>ji>|tuCE6)`Pbl*L15a)f%iTy`}`h? zQ+^_U0QZ`_Ktft>V-t!+kl6Ra+e*s<3$q4pDWNiCBxLxST`bx`c$ZeKW5#6OlXv@L zEI+S{BU)r2m)fc%dUj8Ps*F)dQQz$S_MI~f|$G3yT@KFpoWZ~=^1<(krqI1_ZreUHx!9X3LpzbzO7C;d9tjT#!T6XiKu8Z}E?l&i&_92v1~ew* z{CbYF7#B_=jWg%`+xZOiS5LB)%Br+NgJus_6E?3~)=)KubEEqwIr+)2k~i#>ICyUf zsikK;q^9_~CmS60^rr|Ih$&Z~@A8K*Y%ozC=eANm6N?c8@3=m3;~e;jpDG>ldskX2 z6|E45*wrePM89M7({n5|u8)r+tve3fOb?+AS56l;A2axH-I@p`AUHAmvzS!;98K0~ zI2dR@%gSU74YOQLET790V+D`8(kUt`9^WE>pJp3f+3@i2;()H?GOBGIf@XVm1dh@; zfJ%u21o zxY$a&;V+s~h$K^+81A3u&yOvDK9d{mv&(v-fdB9ID5n9}GjVZ3*1xPOb^<$%L=#+GU-XGw46wDxcETRv<+!3Z=5}(q zq!!MV+c9E%(vZ>6Aor2TPGS{Qd>qXUd#1^VffgwB^Q4m^ul8VR?ZT_fJj^8L*DqX& zY+`nH_MgSY($dmszkmN;`OzFb{7n-bit&yJpJH%v zaolQ@oH`G$>INOQdMXY^Fc)EGzn|^u$9faWvM?7i>73kLRC021kVvLx+yO}F=?}Y< z>)7ZmQrzD%liiML3I#<)?%qJD(S0d5Hvu(u_3(|#c;H?*j0Lvd207(>B6h`i;hm_D z4kgQBB9ziphXs>-v>xr4(>|SCIW)5MoJ|46)}#D{()+EHliO#xzkied{ri_oaiZ}W zcN^;U=b)IhA(fAZb38lKIkupnK=Z7WhEK*mAtNKYxcJrVf!p&JFI1OZA9Ntj#G(Rk zQZAY@Fzm*QMIRMCnCmTKia#BX5reEVfHY}S;NwL9Pnt$lMhqd-l?*25E*#sIv^47H zFJGeJV2+Kod%kE9V)1zHt9?i`Xi6*0JoM!P87W=6910dL+VBf1C^?w@2 zrHJU#6H|iXVz)+iQv#xJ^bBV* z3JNAkNq_n12Bim8m-ey*o|7qr1klRp3y>K-F4vPrpazXqH_ou|w}ga81X!w{K7~M` z`fLdfX%^WIhLe|{r{t3*cs}M^r?I?x6-0u@%E~$^m-#SHok4M=9egIhLBWB_xr3ER zv?awlV_}RQODG$Vo#6iM_NayUVXw@E_5JOaeQU~;mYxQvR$P31VD#WeGxHd!>94P$ zU)B{X7Y+_?f_QsY+?u#OPtEL>nyDo3ub9Ti$K$fIi2*UzEtKIaK#J z{9RIX)p+|}_0eR&GWCk-PwM%aBc>ioeGZD>)z$G?S+T6xN>x4FV6!%+!0JVZ@XpoB ztc6ado_8tcbqlK(UfkB>&o?$VbMx}hY^O`BZjzawKYz;1j15Qx)S#~^?AElnH^32s zV^B}cPpW8Y%GBTAKRQ1C8FLS)YS&Zmw7zoTwR++qa<^@wY;9*FYpuhYk%dtYZ92-M zzU~bv!COegctFWufeyL1nqNYVaI9MqI<}JB-;qNh|-*}ue@l>$)r1IZH4s1%> z*u28S$6tltuAW_wLZQ)_qV9PO4OCKAvps$HOq=gn^)UBP;t~@>1_wU^&#EIR{-tSg z`qG|LdodwRPq(t#gSn92l@Dln`dJP8LC9ToH;p29#|MHevCH)H&<}h%$N$eN=4T&?3WyU zDDFCQM?xIQMB{@}K9{(z{*B6byv$$1?FaD{)$ip~+;q$TQH6?f+*Oox<+5uz zd)1Dhb5PgNi2!o2CG`Dvn~75J72m2wQYb18$tx&O|IT0U25RIY5qEkK7-R9}ylVXp z3}Xwf7hjoa3D$cK!V5Mww6#JXZt3)jRhVl1a*L`?yMpPYnLEsQi(FS3N& zWZ4NNKRpm}uuvibdOm4MjoW!s-#Wiwv?~s5P1YW37GFM_B*mo_F@B zzM)cBT)X8)4kvyx-GwQ zBzT#jtwLD>b0Rh?mWfCbFP#1hW$bsi*DsYtNN$xgqDNBLVXkv(IP#36pR^*9lchWg z?Oe-pK35;KW^oFWgO~kqTea`VY>I7Fs$63yZ4GK`F&)AL zM!#QphzI~T?woO7_6VOJrVWJV`*Gq#)*Tr70!O5iQ&Tnb?s{WqvfJCM*OuAc@!^WysjAoF zJPVtI^7@>!16&4yEzR{fn zDQb;P&eCSl(BDu6_wE8P+npqfXkx8CEly9c6c8W14vPI;Ei4 zduBU7%YL=T9xOP`u7>NKX_o6pggQcEY9z}#D`G{7>K47CYW5>~={vWE(ofpu7*>P9 z5*Tu^B$g zIlTw(jegIvo6!)JWzc)BZuCiQp2K!|q81~d;vV662U}M@{yT0y+v-b`<+J7lEay3I zapc`u{Ynsi)riD;8|7OA1G=q|Z1DKF8c>!X!)@Lg*zB#Wt=oU37)JK=^t75!b_#~` zxe^k_#GrKOxD2CmB_?Z`c5czJ{Q4;?t0}=F7b~q~$f(96n^)K#UT8qBN!5vpxo6hq zbJG3gqv%$uraS5_tOfpzlaqlSjGbcWCOloCXUXpE{#Uf#xgBj|W8=ZfoEF>5jZRiD={%~1z?rx!eW!v z)&Qw=C`%$jhSCM$yEC)$0#F8m96v40VxY0d8RtT!$%N)>#Hna=!zqQ`_;{7hNSgpR znkW}(_odIL`regWYV2gh75cqis7Zoqk*ZN&z_rgD&!E+WvIsBlgx8^46X2ti&1h(7 zQ1TbId5L?STYb{hteuzml7J>xM3+i~DA|M5e}q~`=hS`v)Q=w|d{>CK|w%`P(( z4)N~}yMT>R-h*4}OiN=2hwLvJn?zg+nHOBAV(DtU<-F#TQFJp?f`WqFwt>C9Z^``r z?z;YFuVfV@RO+{;UZm8$KUY0AJBy<@u>ukT^-x(FU!QJX0f5yl-=0B%8Hh z9bGF+S$HB9is{Pkcgl2*Dsw&YkBcEvKlP%~o{}buX8M)-xZ}PwaMmqNx06zl%_P9- zN3+H@?lgOiMWW9(%_PqJNMCTWE=&|Q~jBskg?B_$;b2h|7I!1>i3+r``QLv(Ih zt8-6Qj-|QnGhhnMJW>T6=?%RP_)`lbjJdu7aocO{N)%jN+}V0-rt+3c>QW;JJx2;Y z{-JIF;Amd3=uBxAEa0h&U0pV1&Q?}o(Fjg`Uhkp4YtE&H2~M_+Vq%!)u2Sb0=5C{` z5!v;m;FSO2V8Pqd-^S+8wH zcZ+`ucF|B8CW_F-A9be-{+V-Yq%&tDPKL9-+pbt58AX0&B?+*!O^aM_7to2<4x65= z^bc_Mel2~i?=`o1b(Oz}=sed)1IcdX^s69I_h?Tpj>zHt2>*neML7zhKxQcS_(PuMg|+e&q+Rn+u@ zin6b<0so)-8QWu`Cb|zx2GLK9c)vKyeeS%;_BQY zXkhhAQ|jXG7a)e2*T;>BEl4B_7ZhLIRaA5>Dkzln9%kIuNJ^7L|We6 z?GPFP*gkAn9+sdLER4pX4LHab1E_bmV{@eH^)+3&B_&oZ1^H^?T(xSmDI3bl>W>Sd zPB)ig?*yo{B7J2G!@*oRhRX2VxzV)XK?#Z?%Oq^BS=)oSiUGNh|$)JW2>d* zW=P7^w`^X;sDgiT+GZ#wMs{>2ujbDmc>_Y5b}1D~n^BU+9`xcu*1};|_3dJdD~~pm z?U&T{FICu3QdKp+sg5vtWo{Kki|kg1-H4c(Z=;~JUY#yil`9pkH8{A~Jxo?!P!O6A z5&;+2t*dG+UvL%DQqfvQDHY(y~)hzmf)qCug5Pw_ztyX~n7zTCmf zkI~fMiBcAV)8?r`OWjhBc{9TDoSDv5!R%}O$N9ww;C_*%PXL$g~-a`W6wex0}`2B6j{;HL8FPd z@+TI?oBibS$Mv@^sC6%_FHU^Z2kw~nep4r0yL)+;TvOL1MydzZi(EP#BR zc=y(Tz1Hy_lkmdzxX?q?AjLU0vA~QhDcN|=@`F=bpgPx?mYnHUMXbXNa|`ptd?U5b z-CsFlwP$Rc*=Vl6;ZDMtD+E86YVZL}xT>7!;VIVQOwvG28I|Ejt7B9ty)n>z+E1S} zOs>b3$Drfm+rBSfynM#Q^jLUq4&(`~atQLvmC8K--nkmmxw`NF1{eTmuEeP)E-?e6 zs53VD&mT>}OBj2~Kxh{RF)as$2gTcWJ^sY5c$2sfH)ieF=5k$H8MYyaYd#?fWOMD2;|OK`_9PllO_zcXd$>c2u&z6H~mN+ zi|hTw(nnM&E&fC@@;8K6Ce?)3by$pVBTXnPg=zc#4up4-HwGJ(N>W%+`>QKXsOlJE zy`5-8JdB)s(W0;&we~%hV0WIT5V!lVt^VXO$?@$9`~u-tjiEwD(-V^&5CfZO=W>}P zdoiu?%&fvV^<|qd9v=SNY;#1ts{qv!narE^S!uglb6$dp{d4EWDY$7HHY9sVy=rvg zNJGR3-BzfZ*NNe$yQ@+0{}oXZGEHmHof5naUglI$5u%nXGeobLp@F@6^jpiy!J)_R z@ym`Q+d%6yKp!GZf$0C}jFFj>BHe%K>!gxI(e5p`1f_JaksA_t0$byYKa1f(^~95+ zrfDzJ($avfX>5F!1hz*&BRvt9xF(~n&I|ZTIRvXnX2 zZ*Q|F{?hppAM&3S-qPJCeF8Bf60=Dm7I$63-p}q$uqd}Opv$w=S2c< zC@8`*%r1xH$2%2uR7S>-udc3PHIPi{-#TOxt?2q7ElSApK`FF6g>bp$?{))QEG6@ z02H1vhWNs}0qYpiNt8aHe_)x(L&Jc8AtZ*FLgLE5zQHMOMO=CV_`FG##2@+@nHkr8 zs7_E=m~1M5Pjcp`;{nd}DyuP3 zA(uTy#JtghCxhRW!$--CTXP^G=Y3z?OKest%F-{``5bz+cEP>t?WhTG0MQ4qnxNB2 zJP&oR`w4^;onv#-M4d13FkD3paWj$7Xa}vw%?Nj(t3KJz=Jvb0QW~I4G=;6|>s5tX~%>Az*#6CH;f)IS3P2PaTm$P(Ye-r^nlHp_C&W&X&FTX1*xJ)Qx$Ayd7~QnUhtE3hfOF; z5^}bjN7f+xk++WxeNN01lao7O7i$2k(;mS*9PQfqSQ=~A*mXTB@fJZ`5}zVUN}i;# zl3@x|UB=5$TdsoRs3 zobqz|Xl^r0OTs5G!7~U|q-kq|cw4LuxaJe6b0zl^U zWs3fnM@znN-^uA|okc1oVOJuQm0;pz7u{v^Z2wp)-q_$o(hn)@y{)$g3)Z6D=M-^< z``$1nFr!HbMdasBWww;nozRNT_+-5Na{3zhBJp;vx^YKrPb4Xe8?zXbTGd5-Z{-M` z|KV>aooM9oaVL0XucV?Ykrg|ywG|bgqBUmSl9r;{ZP%8D5WDlK%F&{~mTB*kwQNpE zq`GouXhz1q+rYB-5qhan9X%W>&Hy<39+z|5C-b(BE5sBC`lFU69A_m-8sNQe-cjyM zqfzi<}v|QHD_V#EV zL?7|^vU=Ly&ZeUaH+OJ7HZPNfv!bo>G50dc~iv-0L3!a)>Nlw zUl!;b-C@aEpB}K!b@(E7S*YQ%Zu*$cS}RDw~9Mu?lga zGw%&?4K1zk?QCyqBYy-ty~hgUV5*Z?76RhG0;_;@pN+J94X3|JuQ{BrLAEmy14`_% zG1hxBtrrEl_uNLq@ufI~>e!Vbfhk)p1Ef+15g(cNTWlosSUG|-va=JS0I`sc2ePHx zptiakc69@DJlV-7MneJOFt5R;jb||5Ik$z}FRj#d4l-(9)C%`a)S(b@1~*5%^LgD* z2L6x7i8D}YN}r(CV9{tI0v=&rA6r=DIKieJ;8k4Et?=+B3`@(Hwwf7zb9*gK!&ogK zdl2TQORSy>IKSwiDUB2+Vc|IuW-Wau)+d~XzeR;Tr^q$GjPofrHyqo)HoC{WyGQYJ zKAaOmoX_Pxi6eDvY4LFXeSR$e+XZgjGBWJAhU1dALP^ks=X9oR{hyB7EZ#N8SI&{m+Top^0&q<2w639^7KN^Z zmT@&(-@t&3y@5f~0eSkkiGhfBG?h9EBZk2JZ%o_!v&XduEJ4M;rcd|Ms5iE^I}R%Y zox`9|+Q}(FmwNEwblI=6w|<5k`$VEbmcT%}zsnbsENdd{-O~xSfc?hXCa#q!FB^Gz zx7-Fe-#~c<94w6t|D}d$SY~&3*>uasT2x%xo{UW~1qITCqxI@@XpK*IB&XBgZVE6NFb*$295Wc?F4C`eqzC_M7pGd}Wz{aPryUhPU; zF!7+*x9Mm#H=Mp3xEdAZXL)%K|I9r^r9n+v0k=oH4r%W$$oWEe&g-CZ zA=f(Xq7mS-zf)07!%820FRO(B;qdWgo_i%|^%)RWuB=H8cDG|IMz~f#*r=?X{BqFJ zE^M@qw<%@Lo(NXcxfM_jo3=HW(c`SEGo+tiS~^T^6qQZSdn(CE0az<80`w$}so7Rj z=F3IP*8!uM!v0HFQ5ws!{V`%kKqK6ll}b^OGWCV)#gv&=MXa;t%dV`xpUfU&anjm? zNfCZ@UdsE1g2483AtGw~?kE0-Np}<2rcPNhI$#2In@sl(DN{I&0@wXqOKX;#AZf9i z16nITnTU=mqqQyUysI72rB3E+-o z3FW*WtwbuMO^b5AzUbc)M2wDBvE7l@w1edOuA}96XwQ=;ZL1&`@94!c+q#8EpU|iIKP*nw zES%2QM9jlX>8(yKvx<%W!FG=egrQ@r$w^iWA>Kv5PD@#ye&P}0So9`581|#q}O{)VIQR9I? zL6#4i4Mhuj-Lusy?$ms|#zi?`tpbp*ys|P3bPX7?lm3zl)85Nxf3AE`P=-BAz9guB zb4MBoN&o%Z2TPmRH8ThARMO{~?OvehGJn86xFZNOp@BD+b0WQumMi@bSU{W2o*voP zz-kH&H7(s_&fY$XB3HYyr~=H}$bUh1WrhK@W61O2(huYmLcYaL9r=D!AhhjVs#K;l5k#bRp< z&d^Y<=pUQ3*vNhZP?N#*>G_{eWGRV^Bsgt+idP6!GIDbF`sKrE#S1T0UFtn8fVv09 zYkDxLo-M3p^QC`EeN;wZDn80B$7_grKxX}Zn8M{fK1G1cWzxz+1!BUx8h!r~I! z<2!uL^__h1{;4?m%{}|>pK>F=Ye!n%1D0mQv@KA$NGUsv6N#9LJG@z=*2BR@Dbopd znC#GbjPap%-I-2kWm+%ZYwe>|O$`?Xgs{4um;-fYw!>k7PPCS!M5|)gp9|wQg2a~h)_+yjl!Jc>mpy$6&BZ&rv`{a8cuYOt9y?V9e2jg$9Ii8Zp&R#Xv zHk~#({OtNfa*<@O~obg37Nsm8Q#e? zKEwO!07rASdUjiN9OLgMQeofHxJT{0wOf5MK2WiSH1Nj59tbjm_xyA3S3+VySYX}HMFI~=VliBhi+WYnCQrp znkvm=?wjk@Y)MkI!>dCWlwU(cyP)z@)C^D<^#~yLn>843Z zSzDM-RsU3Dq!o7Vy8PMWc+>(+&(jcWY&8W~ptc z4oZX~wAa_^w6*Kp2%Xt=R_e5`8wnrT?$vW_oiEQ~)4Zc`-ZmU{Xxi=l*ck=HN9umQ zU+Q|RnJ+AsA9H4hk7)WuH1jb;^GGIsWR|cN;PPDBl+XC`)Mm+treE%;y{d#%-ZiH~ zzAYkU1m?!@0Eoixju7%nNhOCV?;_Rfdw1y4`RB_Jv&Z@S!j*e~0# zj791FhVuUZ<#{39FWa{didh`BP&>IQTYlmc4Q9ox(dn5k#-bP3*JBV%oKw>KZX9vX zA^$Hxvdp9!hcTGxT&EVjHyiX!!R-*h7G49@aWHR{p%qBX_&40e#sm^ZH&l3XU~9NA z9Ri{K+u6Ic#76k&4*$gB6OXOqH=|1BqU92Vu#pBEq$5rAs4Ij(lxNT7>6Ce5g7 z)ZqX6Wtic$_qzFfoNc<{U`I@DgjeygBd2ZV_@-xy2`?^m-`bZ=LuaipeA#tBe2AKy z%6US75t@rdm}@JRW0UrIPhZfr+$Ic+O#pJp8?!*GK?{fgDfb-nor>knAk7JOS)!dW zT`vehjZl@~Ky9reqx?Nbw(X;iVnF@~* z;@uVx(eM+^Y>$bBODd;9RUy#5&)x9~6J`2jP&`qXzPwOstO&}B2m!lsQZCnfS4!X- zaSs1})CBI$fWLIp_WwQ|!E1Hh#&LN|HyxWV)tV6RM%JTO1-COImNT3!!h3yzpFqG% z1nMqlPV5ck>h!eghgVy?Y7GR}yI~;%z+Yw7pfAh8(P5bj`z&^jhr^=2!*_Sm$%} zt^>FH+Vb*&kbaPY*@c`KP`Btg>U8#55--APP{adoTI@`$b-of+R*|qiuPr$_Z1C4( zZrJZAlMf#$&)1^`#jWAFqhRsFrdBb2%TrHR{wX@mj-#{upMNm)t5yThi?ju$jByaP zQWIIqs5!OeShh3emqpBSH0fNV&xxvYI7%@=}iLq01#p=y?(jp~VNC!Q=rw;(kp)rDbk87tthrji7`XD!I3 z8RsLwStMyJ#5K^M&|;zSI~FcUSG4GAZ2w0e^o>5k9Tv~R;7cFWR;7nt)E%+S;2CIy zmRpnL&Z<*4wwtlM42|mum=m#@>8N@=OOL;Yod<5jXqy*1`n?v-jZ68BJH_l}LLo^9 znHs2*tgsLkRtOVN)F9L#eC3y&&8n}X8Gk-N;g;fCN%J4^Kp?1lwHow`rty)T1NWT- zB)9mDy1K^O30L(ASCdp=m{e&T3!Vm1ivt0c6#GK^Z1~?5!;k$9AtWfN7^dHTedG~M zP@8z+EL>n>Q<%V;fGZ=fPN`Xn) zK;3A9friRHI&O#IE5Y;=>*Ewf-1y{=Q5S1GrbLYy#m>o(!>J*~@1Mk)=e{$a5@whz zy^+FfjaQI$E#4+2K`|ZgO+Bv+NF3zQu%IA1d@{HaB;3G7Nue*FK$GTR6**jA%w33) zS~mBxT{t9hoa-ag4*YGEYR-R&kvGG91nM()VXaogmlp-~qD$}|T& zYrG!Jb$u>=K8?^0n>+vnt#bt-6{Yw{8N?dNWgo^7+VMiGVNxhX ztdB0ki4H~gqsjkQ+SLZ7b!K5u5JdcHY(zyOOk%|vdIh2`F)qj&qXA=twoWPyC=!V# zL4|c=Vk@9@*SMASqkO@2AgO{I9h!t?#G193M7NnX_?e6*X0y}nGLZb( zAN{p|-aGTooH^$`_dL)0%$zg##w$*7y?3x|!Hx*CiZWWOiGI{hlCNYC^xyoEJ63LqQLqq(1O;0L9u~FY+k^b$ zxKT4FX1bF}twNOr&*bbdnc@!uRgk~x0Jb(kfgR#a!22k@$-RnU1Pc(rr^w!l4Db}6 z{qxqkY%3?hc=Y(CJefFD^oK^Fd_G zAkofiBT_On*YItRYFx0-0yH!kyQ73SNtU<)z4_$Ktb|S?^Nf@(!|6hL<~~j)!_7C4 z)i&Sx9a*ty6VOueXzYWpJQr#ri#~RDp$3~$)FPV^qXKOJ@T^XF9{cYU$J;Ooc~Vs#dl9&9Ao9VZnXebFLEGR z!~|d6a_nGyI01E!iC=58h12Vt*q%?%{8;FJ+lP**88UUJucxU@-eJK*kofS)S+Q0< zL%yT4jR{XKkktlFyN5B=?gHW0M!eK}w8o3UFQl0MEAq3aMwUO?4FNBS0pCw}MjpRHeAUJ`ymZK5b~sH}W_7_zLW)MkNmXw?9ImBv(M5V&30 zF-AEpm64k28#eK$uW5=w*iG64!ncrlC|R6+_BwLAAo9e_B#jbgvRZEd$;E$%7JD)d zZ7{}zo=aB;9~{j)cD(dEBV4ZnZKnVFNb_4pJ7O+TStKuThab8`Z}8?)tSb!jP6;n7sB?aWbdm zH7K#?^N$)5aN;j_aR6;tk5?vKfVd&w|7o32*8DF>RwkWh!Pz+G92P?kUT*HkJP$ke$ zqR=H*-ve8_kr;QmTD?zZLVQ?yv~+wW#|xhCd_Cik$U)7JL8w zn-^9aDX`u2lPSd=kX0(~@a30!mNLL(AtlByEgh}v&)gJhBy|1gpjEt6A1v=?!a{P2 z&wL~doh1i-Z6m_v5xAr%hO>?{%GdU zUAx;d8vgbEushqO%7|6<;^<)W%|9>R7z{*ehR2VdHG8_^s^@Oqa9YNE^JW3nl01L6e*SKTTyUgvcJNv93quVhaQZY(rK4h} zF8^=1VpvLPfD*5K>!F_SnATreKQ^QJY1(2AaMkY~GhK-z`r2&h_)~VQKMhy6+b&=d zpQ6#z_4flp=1Fv{^j86N1_JZ@|NoYdTR+2cs^4&2d4 literal 0 HcmV?d00001 diff --git a/gwenview/doc/index.docbook b/gwenview/doc/index.docbook new file mode 100644 index 00000000..d3266b8d --- /dev/null +++ b/gwenview/doc/index.docbook @@ -0,0 +1,713 @@ + +Gwenview"> + + + +]> + + +Gwenview User Manual + + + + +Aurélien +Gâteau +
agateau@kde.org
+
+ +ChristopherMartin +
chrsmrtn@debian.org
+
+ +Henry +de Valence +
hdevalence@gmail.com
+
+ +
+ +2005 +Aurélien Gâteau + + +2008 +Henry de Valence + +&FDLNotice; + +2014-03-06 +4.13 (&kde; 4.13) + +&kappname; is an image viewer for &kde;. + + +KDE +image +viewer +artist +photo +picture + +
+ + +Introduction + + +What is &kappname; + + +&kappname; is a fast and easy to use image and video viewer for KDE. + + + +Gwenview features two main modes: Browse and View. Both modes can be used +in a normal application window and Full Screen. Browse Mode +lets you navigate through your computer showing thumbnails of your images, View +Mode lets you view images one at a time, and Full Screen lets you make quick +slideshows. There is also a start screen that displays a list of recently opened +folders and &URL;s as well as your places and tags. + + +Image loading is handled by the Qt library, so &kappname; supports all +image formats your Qt installation supports. &kappname; correctly displays images +with an alpha channel (transparency) as well as animations. + +&kappname; supports the displaying and editing of EXIF comments in JPEG +images. Lossless JPEG transforms such as rotations and mirroring are also supported. + +&kappname; can read embedded color profiles from PNG and JPEG files. +It can use the image color profile together with the display color profile +to output correct colors on the screen. + + + + + +The Interface + +Start Page +The start page lists recently opened folders and &URL;s on the left side, +and your places and tags on the right side. + + + Start Page Screenshot + + + + + + + + +Image Operations +&kappname; has a few features which are available in both Browse, View, and +Full Screen view. &kappname; has the capability to do basic alteration of your +images. + +Rotate: A rotate operation will rotate the image either to the left +or to the right, depending on whether you do + + +&Ctrl;R + + Edit + Rotate Right + +or + + +&Ctrl;L + + Edit + Rotate Left + + + + + Edit + Mirror + : + This operation will reflect the image along the vertical +axis, as if you were seeing it in a mirror. + + + + Edit + Flip + : +This operation will reflect the image upside-down (a +reflection along the horizontal axis) + + + + + &Shift;R + + Edit + Resize + : +This operation will allow you to shrink or expand the image. Note that if you +increase the size of an image, it may appear +blurry and/or pixelated. + + + +These actions are also available on the Operations tab of the sidebar. + +If you have edited one or more images a bar with additional actions is displayed at the top of the image. +You can undo or redo your changes, go to the previous or next modified image and there are three options to save +the changed images. + + + + Actions bar for modified images + + + + + + + + + +If you have installed the Kipi Plugins, a Plugins +menu will be available that will allow you to perform many additional operations +on your images. For more information, see the +Kipi Plugins documentation. + + +Browse Mode + +When in Browse Mode, you can easily navigate through your files and folders. + The preview window shows thumbnails of the images in the current folder, as + well as subfolders. + + + Browse Mode Screenshot + + + + + + + + +Moving the mouse over an image shows buttons to select or rotate the image as well as +a button to enter Fullscreen Mode. +Modified images are indicated by an icon down right, click it to save the changed image. +Clicking on an image takes you into View +Mode. You may select multiple images and switch to View Mode to view them side-by-side. + +The slider at the bottom right allows you to change the size of the images. +You can also filter the images by filename, date, tag or rating using the box on the +lower left. The toolbar appears in both Browse mode as well as View +mode and contains the most commonly used actions. + +Start Page: Open the start page. +Browse: Switches to Browse Mode. +View: Switches to View Mode. +Full Screen: Switches to Full Screen Mode. +Previous: Clicking this icon will go +to the previous image in the folder. + +Next: Clicking this button will go to +the next image in the folder. + +Rotate Left/Right: Same as discussed in +Image Operations + + + + + +View Mode +View Mode displays full-size images. The same +sidebar available in Browse Mode is displayed on +the left. At the bottom, there is the Thumbnail Bar, which allows you to scroll +through the images in the current folder. The Thumbnail Bar can be minimized by +clicking on the Thumbnail Bar button. Clicking again will +restore it. To change the size of the thumbnails move the splitter with the &LMB;. + +View Mode supports viewing multiple images side-by-side. You may select +multiple images in Browse Mode before switching to View Mode, or you may click the ++ button that appears when hovering over images in the +Thumbnail Bar to add a pane displaying that image. A - will +then appear that will permit you to remove its pane. + +When multiple images are displayed, a small toolbar appears below each image +that permits you to delete the image or remove its pane. You may perform zoom operations +independently for each image, or synchronize them. Toggle this by checking the +Synchronize to the left of the zoom slider or by pressing +&Ctrl;Y. You can switch images +by clicking on their pane, or using your keyboard. To switch to the image on the +right, press . To switch to the image +on the left, press &Shift; . + + + View Mode Screenshot + + + + + + + +The slider at the bottom right controls the zoom of the image. The +Fit button and the 100% +button are next to the zoom slider and are two preset zoom levels. The +Fit button zooms the current image to fit the size +of the window, and the 100% button zooms the image to +the actual pixel size. The shortcut F toggles between both view modes. + +When an image is in zoom-to-fit mode, you can go to the previous and next +image with the arrow keys. When you zoom in, arrow keys are used to scroll the image. +This is very similar to the behavior provided by phones or digital cameras. + +When an image is zoomed in, a bird-eye view appears and lets you scroll +the image using the mouse and the arrow keys. The bird-eye view automatically hides +itself after a short delay, showing back only while zooming or scrolling. + +You can start directly in View mode by starting &kappname; from a +context menu like Open With in another program or by +launching it from the command line with an image as an argument. + +The following additional image operations are available only in View Mode: + + + + + + &Shift;C + + Edit + Crop + : + This operation lets you discard parts of the image you don't want. + + + You can access the advanced cropping parameters by ticking Advanced settings check box on the bottom popup pane. Use the corresponding fields to tune up the cropping operation. + + + It is also possible to adjust the cropped area by dragging the gray square handles on the borders of the image. You can move the cropped area by clicking and holding the &LMB; and drag it with the mouse pointer. + + + Press the Crop button to see the results when you are ready. Use the upper popup pane to save the results or undo/redo the operation. + + + + + Edit + Red Eye Reduction + : + This operation reduces the "red eye" effect commonly found in photographs taken + with a flash camera. + + + + + + +Full Screen Modes + +Access Full Screen by pressing the Full Screen +button on the toolbar, or by + + +&Ctrl;&Shift;F + + View + Full Screen Mode + . + +To leave this mode press the &Esc; key. + + +Browse Mode Full Screen + +In Browse Mode you can switch to fullscreen also by clicking on the button that +appears when you move the mouse over the thumbnails. + + + Full Screen View Mode Screenshot + + + + + + +Going fullscreen while browsing gives you a more immersive experience while +you go through your pictures. It is quite nice on your regular computer, but makes +even more sense when you connect your laptop to the big TV in the living room to show +pictures to your guests. + + + + +View Mode Full Screen +The full screen View Mode shows a slideshow of your images. Access Full Screen +Mode by clicking on the button that appears when you move the mouse over the +thumbnails in Browse Mode, by pressing the Full Screen +button on the taskbar. + + + Full Screen Browse Mode Screenshot + + + + + + + + +The top bar will hide itself automatically; to show it simply move the +mouse to the top of the screen. If the mouse cursor is over the top bar, it +will not autohide. Most of the buttons on the bar are the same as the ones on +the toolbar in Browse or View Modes, except for the Exit Full +Screen Mode button which returns you to the &kappname; window, the +Start/Stop Slideshow button, and the +Configure Full Screen Mode button which shows a +small settings dialog that allows you to easily and quickly configure the +slideshow. The slideshow controls there are: + + + The Interval slider controls how long + &kappname; will show an image before it move to the next one. + If the Loop check box is checked, when the + end of the slideshow is reached, it will continue from the beginning instead + of stopping. + If the Random check box is checked, + instead of progressing through the folder alphabetically, images will be + shown in random order. + Select Image Information to Display allows + you to define what metadata is displayed under the buttons on the toolbar. + + If the Show thumbnails check box is checked, + thumbnails for all images in the current folder will be displayed to the right of + the toolbar. + The Height slider changes the size of the + thumbnails displayed. + + +If enabled, an area that shows you the other images in the current folder will +be shown on the top bar. Clicking on one will display it. + + + + +Sidebar + + The sidebar on the left is available in the Browse and View modes, but does +not appear by default in Browse Mode. Its appearance can be toggled using + + + F4 + +View +Sidebar + +or using the ▮← / ▮→ button at the left side +of the statusbar. When clicked it collapses or expands the sidebar. +The sidebar contains several tabs: + + + + +Folders +Displays a list of all folders on your system permitting you to +switch between them. In Browse Mode thumbnails from the folder will be displayed, +while in View Mode the first image in the folder will appear, from which you can +browse through the folder using the Previous and +Next buttons or shortcuts. + + + +Information +Displays Meta Information like the filename +and size. The More... link permits you to view all available +metadata and select which data appear in the sidebar. + + + +Operations +This permits you to perform the +previously described global image operations +as well as those specific to View Mode. It also permits common file operations like +copying, renaming, deleting, and creating new folders. + + + + + + + + +&kappname; Importer + + +Introduction +The &kappname; Importer allows you to import images from a digital camera or +removable media. To launch it, select Download Photos with &kappname; +in the &kde; Device Notifier after connecting a supported device. + +When you plug in a device the &kappname; importer recursively lists all images and videos. + +This is not always what you expect, ⪚ plugging a smartphone you do not want to list all medias +of the device; but only the pictures you took, which are usually in a special subfolder. + + + + Root Folder Picking + + + + + + +It is possible to select the root folder to list, and &kappname; Importer will remember the +last root folder for each device. This way, next time you plug a device in, only the relevant +pictures and videos should be listed. + + + +Importing Images + + + &kappname; Importer Screenshot + + + + + + + +If you wish, you may select the images you want to import under +Select the documents to import by pressing the + +button that appears when hovering over an image. You may also select the folder +that images are imported to in the text box at the bottom of the window. When you are done, click the +Import Selected button to import only the images you have +selected, or click Import All to import all images found +on the device. + + + +Automatic Renaming +&kappname; Importer can rename your files according to a specified pattern. +To configure this, select the Settings in the lower left +corner. You may turn this feature on or off using the check box at the top. The +Rename Format supports several special parameters, which will +be replaced by metadata such as the date the image was created or its filename. +They are listed below the text box. You may either click on the parameter name to +enter it into the text box or type one manually. + + + + + + +Tips +Using the mouse + +Panning with the mouse + + Holding down the left mouse button on an image allows you to + scroll the image. + The mouse wheel will scroll the image up and + down. + + + + +Zooming with the mouse + + Clicking the middle mouse button will toggle the auto zoom + on/off. + Hold down the &Ctrl; key, then either use the mouse wheel to + zoom in and out or left click to zoom in and right click to zoom + out. + + The mouse wheel, used while holding down the &Alt; key, will + scroll the image horizontally. + + + + +Browsing with the mouse + + When in Browse mode, clicking an image switches into View mode and + shows that image. + When in Browse mode, scrolling the mouse wheel will scroll up + or down the thumbnail view area. + If the Mouse Behavior option in + SettingsConfigure + &kappname; + is set to Browse, scrolling the mouse wheel while in View Mode will + move you through the images in the folder. + + + + + Key bindings + +&kappname; comes with a range of keyboard shortcuts, all of which can be +viewed and remapped by selecting +SettingsConfigure +Shortcuts.... Note that in the Files and Folders +windows, all the normal KDE shortcuts are functional, unless otherwise +remapped. + +A few of the most useful default bindings are: + + +Space: Displays the next image +in the directory. + +&Backspace;: +Displays the previous image in the directory. + +&Alt;Up: +Moves to the parent folder of the current folder. + +&Ctrl;&Shift;F: +Switches into Full Screen Mode. + +&Esc;: Switches back to Browse Mode. + + +&Ctrl;M: +Show or hide the menubar. + +&Ctrl;B: +Show or hide the Thumbnail bar. + +F4: +Show or hide the Sidebar. + +F6: +Make the Location bar editable so that you can directly type in a file path. +You can return to the standard Location Bar by pressing the arrow at the +right. + +&Ctrl;R: +Rotate the current image to the right. + +&Ctrl;L: +Rotate the current image to the left. + +&Shift;R: +Resize the current image. + +&Shift;C: +Crop the current image. + +&Ctrl;Y: +When multiple images are displayed in View Mode, this synchronizes their views. + + + : +When multiple images are displayed in View Mode, this switched to the image to +the right of the currently selected image. + +&Shift; : +When multiple images are displayed in View Mode, this switched to the image to +the left of the currently selected image. + +&Ctrl;S: +Save any changes made to the image. + +Del: +Move the current image to the trash. + +&Shift;Del: +Permanently delete the image from the disk. Note that this operation is +irreversible and cannot be undone. + +&Ctrl;P: +Print the current image. + +&Ctrl;O: +Open an image using the standard file selection dialog. + +F: +Pressing this shortcut toggles zoom-to-fit on and off. + +P: +Viewing a video this shortcut toggles playback on and off. + +&Ctrl;T: +Edit tags. + +&Ctrl;F2: +Rename an image inline. + +Del: +Move an image to the trash. + +&Shift;Del: +Delete an image. + +&Ctrl;F7: +Copy an image. + +&Ctrl;F8: +Move an image. + +&Ctrl;F9: +Link an image. + + + + + + Advanced Configuration Options + + Some notes on hidden &kappname; options can be found on + this page. + + + + The options described on the above-mentioned page may help you + tune &kappname; for specific needs, but please keep in mind + there is no warranty they will continue working from one + version to another. + + + + + + +Credits and Copyright + +&kappname; is currently maintained by Aurélien Gâteau + +This document was written by Christopher Martin + +This document was updated for &kde; 4 by Henry de Valence + + + +&underFDL; +&underGPL; + + +
diff --git a/gwenview/doc/modified-bar.png b/gwenview/doc/modified-bar.png new file mode 100644 index 0000000000000000000000000000000000000000..80933f40db238c567a247b8f0e46f5faeb39d31a GIT binary patch literal 8510 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≥fKQ7q>1gB-bhj%+qZ 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 U{D${E@PtbH*1el$8V@7?2OH^v4*&oF literal 0 HcmV?d00001 diff --git a/gwenview/doc/view_mode.png b/gwenview/doc/view_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..b6ba11548f6ccbff9ebc7375216eb2339e03340b GIT binary patch literal 455362 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
wv&2empV=*HM6G^J{(b1_O;TQ;xH`H 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

?;} zP#WqQYAqrI&K7*ISktr4SAyT`Qkinjng8K4xB7OvUQv_rGu zv?z~dWi8-akCS~n64Bi*SNCe%QVOf7^s2PxdAMZOCLE1rEd?JVA@o=kq!o-YdvQT4 zzz*7L0xbx|fX7oQR4KGl5Mwdb;>7QH2Qe5e2s^;7)J&G0M(!M#8mP6B%t8lP)00wR z97dY2Xw00`iW*0A5wE+wbZZ-PQ!cqsZI2Vd=mzO~tyO84{at_bPyEwA`7+R_^LP8X zywf}V7e;a0_?54I{k#1%{!{DOxDaE-mO{Kfay^}SdVJ04^akfW*$Do4VC*NxFmb$n zfeby)cZf4=+l5jKu@zb|c<(ukNBXy3-sQUPb%+*JWuMRkFYYGh`HC@x_4FE#P}dcs z7Ar#4h;<$|wCzqAh8d_Ga*AppKN-S7!InN7+VZ^hy*bLKw z+BRB(QZujKyyp3{m(Utr*D<*PO69W5G#cG7GEE2KmKgexLZQ}3*L6FUwM4wPT$U>> zRz7|G8IMm7#O2E6@sYF?)EbV{9mYH2x?sfOMvr$MTld^SHHM@G=RHy@nhMXJzhqq_ z#Qk|xRa|puS&6MstV3I6TUM}^m^NA}RBccUc}v8Y_j*O$a|$UZ^7(=&BqPQ1;PRvH+`mSEm0Vd^O+v!%DKCn6Kg)Kl9VSfXySn`^SD4t!6^!sI4L)@OYXTLcd#C(r%3_C84d+YQzXwyE8znGiYr% z-|u__ANIOiE7Y3khhgveKaRM;^Zfpv4_@AK|MC{RGlaD7@hT{hBM`TX+-QjL^W zIE+1qyNRdAH#{FA-~Z8(f9v1*zw_BS^4|Ry`0*e80lxAh-^ZI*E8qI$ky5wa8=eH$ zJA{TciZg-}i$O`i(0RlgIzOP|--b>V@ApF`$Zqqn>|bkl+gvM(6|iFIyvG@b)qvI= z2xg3c4xaMM7whOTuuJTb0lv-_6Do1K4P4Bq$nJ|I%?onc3d>>m>`q*@WX z-^jOQC`KqKh01lgP<5wWR~+YchPseTCMH<60^;^eP|^MCoFXC6s-+YQ7HYOE2~L;C zom#Dear?(gNwl}t9Tz;o_4_tM3+I$c6_#A6+2OszcMfcaSV2VSbZ_ZG3$^68Ri|#J zCgXQHvk{uvEih%>$ZMoF*{h|e3EL?SA+LLzRBeSf^9e0VULrMS+~C-*Gm{IXvXZDc z*#`+R7h<)f+7Rnd_TOtuiV2RY4y~TGlsynlW~=6H-otP?XP%bA!;+YjVp5}zJHumB z#W_Q3m97RX1?!Zo25&4;-jcO3&}aG^gAI;5Hvk1|!rmn1_DqR0``;O~;>80(XZ@c2 zNd}V|^S1KY>sQ>}9dX9c8H1>TEp&FI&=3V@EY38VRBFp;-FYfOJl0!O6>u!e3R@=5 zD~q*&#Trkk9Z_MyP&T1$jine2JqF8{f8xi!+n4G8O!SjK{$p(24gbOG{N2eoywf|q z)AxqzC9S~MUViYwUZhtO)qpFNYLw-2L372KfbTmxYjMWz zK2`+l4s=7MszsWj0?vDa2$!b?S1cuFj>l(g>qa;9y9e6}w7rev@$|&ed5n0x2-XOp z3%IdI)j*5f-VQj~ySb|fTUrs&wi9b=L73{;hI)D8JKPkR0Ii*tE?ESxC%?XaLtccf%s zl+QrnbXMN$Kg5_wagnlRoH1;<;;kjM{bKsrt55N^!+XaPFVtr6VWc*tHyziKAZB{$ zk=iIFLQ~F{jV>G*+`yaVH73H%VI*yPL(NtNUjzf(A4ksT3x}J){Pe)(8aYlAMhjco z@U}yOqm+e0;bwXP-s7FJ%rg=?dTY^A*_Mqin4QXtCwarzdkB>^Uhz&4V|Z~4eDe52 zAB1t}DGHQA-3(oS%k8s{7&F&d5rOTp;B252Wh)DZ`x{8jg`Nc2r z8=pLoAFurU`tR}h>9_gO-}e>XfB6!p95MNwVSmf!bsb)Vq!x zH#+a}zN2}Asf|$_*AlTl;H`jhq?$P%M#e6n>>GsV&)%b`<-IYGQe{br>vG0!g}9!m zdBp(12R{G!bH4fYU*(Vd-M@oe3fU{in_Jd=VYnMO-gVsG-m%T#jKi3QbPcFDkv$Vr z9b0QyCr~1f=PSnD(|eDYLQmMMb)C{Eh-tXcledakA-Il`6PJFYB6QYKY9utzyw1=$ zn%OfN0+1EsgrW&Yw_Dko5?ds;NTY%7_BLz>Hh?y)C?R-ca2SL^JC?j6X5$#{nO&ll zM5z@w9oVj)qg8M!3}TS#NoAvTjo7Z(G7w8aOF(lZyUe;Ka30?ec+Vb8HkEEXu*J;Q zBFmDo>Fq?m%p?&)dAo^84PygqDLCB&!-huJIiw1)X0$WZVA)J#>>bv4Qo2GC3S$q4PqnJJs4C6k8cuB`4+K^@-*(;tY}e^m_lQJJxHyzR8a3I(g1_1V2m<|fV7=3UMigzqykN- z)!}VGTwqC8Y@2Zy@RP7zoTI@3x~-$qMJw25Kl;phgZ&DN8}E<*q+sn!!0? zTQ3AZfGTs^ISF0Y(W__OB2Q(b>pd@TKfu1dx!E3{h|9**LyU!G&N%BxZ!J20=&<5g z(;1T#t&Ly|rA3s+y3B-WAlQyz0~W*Q=LhC_rpEn7Nt8W!mnz~NQ<#`*!u1DSRZMQA zoLKUO92-JMUM_p}ZOe$WNN`9xv8mv#rwM3@#3mS3Eaq*7qj6m}&hryigpyWf3eRqz zqX^X)G{MHq+Ah>s8A8Xp?iAcDZBQV&2Ci{Foe&)B(ope?lV?*U6$?fv+Bl^vwh0A8 zFOGPQBv;AWn1aKkJ>0Aw)CH`7Hp9^b1{-L>@cQ%wx#64eU=}*(aoxcAoG=oQsiC2f zQzf*3rbG{%w@fLDA1u;?JHucc&z?PFZjtqBh^l<)2foB_etM#81wT5PFMRy@Z}9zZ z9N&2QjIVs(Pm=qLYUTFD3%YS)A4FQHLhv5zENb?=gm@vV!Kh%JvThqz3?h!I4dbEf z6%!1(79{Mi(NdIW&+ZAwiC_DrU*~$7G0pPu^m?~@t%=*)iHBujaF$LilMfgTeDLfC zx$O#hdBpdYS})w(G?0Z{g`w{myi!WR7>lT-Xk)U1YZeh9 zhZD;X2Ciw|r{%Pr8ZH&FiZcdOm}SHF2Hh&98cb8P8Jejy3-0Y?o4juoT(@r`iU?bb z1Rn_EQMGg?khasl^|Axj8K?=@*qf|!WSBbEn#o%ub7gXltr$cttyOAOat2c~y$qNn z)D6To2ujA`fBhw(D@i01C|-ytP;jdmcd} zje-gR2JXcLt0$MXIMr**7rFfVMrqR`&OJlZsTsRKQz9Nrq}FK^ybh zI6W?;=gVI6Z3mpO&@3tqwHc8CV>aqr3sloa)CNY_n^oJsVavklIy3f;W&Km1U6-D#Dxh!6>Wuk2hwkgdNR2p-QTrNG5EXEYdy6q(m z<~vS2_>Rc_u0Q%G{^`H#omJ_b-swB4?-i?3e*JZ#!tukGn5knf5pfMwA>14(ov^M6 zX9La(MLelC>ayP)hBn2V7mmY-I!ovSF<(KH(keECG)Hc`@4A{5wJ`NJjN`=3@i|%zUPg@TiBWe? z@pht=jhgeGacG5y^8+SVL@MeHgBe)nOf7W}?>fU_7^&LGHRDji;J9oTbj<`YT+U|< zj+QdpC6aPs*%sb>b|J_>^KQRc_8nax$T>2MBeg_qIN+^Eq{G#{Ey@H(*Y&7z`}DmT z5J3@A+AvrS{efC6;ykyv69uz}InBNyxyHtlHna+*G_Kb(aoP4xeJ|8fS+)q$NOdD# zXU5L*17CihumAeDFkaYN#-zrx`y+qkkNzIM@%68BobLF4{y%=6o{lAMeE7i!w3Z3M zW7wNs##_`3GVBzO~{^$9%U;hTCu6*+G=R7@Lc$!ZnDjf@d z@bCT!KKxt%}PQ+962RgO^C5XjUYnF z27_T+(;j>;3tj`x1aixi+Q?ghtOyWuWy>4t0AtA4yfXqq!^%J(1}qjM7V8`X9evw3 z4W<=L-Pl57iyOZ5J9N-CL>p4~_lJs-O~M9{rqs@2y#w-FqPeHlOsSQsa4C(o>_3A{ zqZ>z1aGk|@k2?-{Gcfogm$;EL*rXV{Z`v+#X0)EsI+}Ml0?WK&0${Y+tuLQd#gOHryDwI$M; zF;9h(3QOHMZD(?VT-$!2jg7V@PQ zwpPCKuM>JN;HPnnu!%`>w}%VVnk@p3dC$12=~oWax>HGx>65 zUiOKjX-aLC*Ka-}gr1w5o1Kz;%@`jj8aX{&IX=I~J4Y@B5sPnd7zWC^F<%O;RZzwE zBPPhs80Z|^968=hl$zMC3)XvF-_c4WZ5z&XsAN8Bdy*@B@N1Ab999=gu_#LceC853{&T+i; zY&pHPne4v@=Y``jVC}ZshMdK_h7@J+fq-Q(iV2pbHB8<(pU#Xo&*?+=wy|bLi!ykJ zSmo*ThM^zmZJ=sHrp8tZu?1Q!h&31)Pyt&qw|?Rwo-kQxDxgB}9fR+Q>lr7NW7qRu zZGa`Sv*yt`q#TBhSI|F!?}@8;+J>d$lc>?J?{tKTA%_{+cev*<7NU^{-t z@BAWn&+bqU51&0CRl!)i3QN3V#ep$AogVSNBj&sZ{k=tW_s0h_(mO{#3>*(5eK%lD zxPh`}sup5vD3!P+9?mnP8+kYSbire{ zLP{(29ZSk|e&7&#Hj~~~*cv1Y#U!qACB~gheDFQhCAPR>REfD!3~2G3*M;slAQ*xe zM%$qwW2z;duNXBLD|FL{>pF}GITcz~j5@XwsiL^vV-bwW)SM`#;#KiR$gMICM@lNR zW|`NOJ|tpI1S}na^Lpj9WiD;Qd%rU>N@LTwQ_V}F8w|B5MHI1S&+i%~G)HX~Zw%2i zYHd3e+_(JzzZO!4OBS{kr~*h&r$S6uns_c{!+A^0jZivV*tehA7!bIrmTjv9+u;f{ zC-?^10$9UpGNX5tqNH4rR0+-k%91nF6hP~qhtq*D)a-l_YZ|WZoC_uJ z={b>dL8)(9KZZ(2)9+1m^t*$@JH698{gxFDj@!-f`rBV;JlxXt7GDds&MdFr;0^;c z<=dY=;`$ENeQMWaC%?8FDNk?6%ZycF94AyOV)hNi!**tH9iU|KXl~5&6EqHZ6H*!u12)NO6JwL$tg7*f~JKD06=11aEaW~Jn#F<(OY02On z(=c#6KF73#IW2Uq;~ERDa}+Ij*CBv4LaT4HFVf!D(Dvw>!FqJMfns?&Ul41lvC)Ym z*dFg4*Dmc$0oOnb!3ZTM)-58Yk&?2N#*62kyYZIu;{!Go)&`^Rc>esBr^^#5gP6wM z{VmoSVr_JN;O=(7iYMG3xWp%J-+RX2`}h5B{_UUoOML0YGmH#;@~vOPq{fyDKl;NT z@iRa7(|qvk5A)#H9lA`R2Dt>x{P+35Ipa^iCBC68 zX1dg_H_qovF2QUMo+R!ef2SRUuZ(|4ohWZmA-^^R0B znS^bXgAL?fKn>eg80GR8u7AWRH&xVw6YN)3v((A))K4ke;dk1>+PQH(n{&g z-ni7=P`A1R-%7#i4oq9G5$l9!UC&ZBk})(9su+9-9J-NKB8yc_>#mL{-r}0zO~H2iCLs83XOArV)LxvYZjsjS z8tijoHCpemZla;Et&~=XW79P-3JzC$LRNfz3qNa(mMT}d;-nHnM`@Y7 zEF^7s7wCG=;4G$OmTTPmPmGx z?{4}0d_m(r>G!TfG?KO*lGV#VutIQ#tc6x0TfE{zPjEYFwl{FCx_foCl4-kLNDWpT zWli`nG92&N^5-;TxvVSBTXcg`99vze>q^Oy<{F(HId%ibn_JGyD*}N*jvQY&N-hW% z{BEzwxspvHw}n(HB75!Sk}?*{v+2OPT)7DohiN3Saeg?H({&Gh+rSbF*u4QMm=50> z$01;cBO*eHnd_U^RFkQpB5@;_Oby_Mk;n6Ev}EiMFlECu%P{o3d3?pPMsB-Xw5(JW zOssfOuFDzYEH!0JKOn%oZ5(yySy(INYG~Ec%1UP{F(t~DNjY)2nNXBY0ttA`8+lGU zo!ATgIFZ(sCC`jQN9Q_XE>sGlnd`FB>IE8val+OWO_|G*n0iYy`+=lWOO2UW6zeU| zpWmTsk?l1<`V&9K5B=DW@Gt%8KgWyXOKyfEfAQb@YaE{)(5IQv! zXhrLW#>CLy(7f`rE`%;{>pe?caBcs+$}|y6W@?tT7K$~r0I>=#^q88NyyKDzn>Jc( zoU>t$jmK2DY!M?nq|WM|190pice@k@dY-ljUa_@Ms-@YAp%RVIH^o+msh*S@gEy3- zh|6>$EMl-Kl$^2SK+Fx>Dd)1VwuCdo9%k-)T2(NGY7*X0h;chmP#U9HG%1b7AdX93 zDJJ8>o^u&}z={DfrtXAkdSBg<|?h%yiHZDy$ z5gDeDw9QO|qvgtp1*_lf3-oV~-W?p?>7Cx`x2)5-U{w(h#TINBx%udhr_UehO;10L z`$b0giTHRo;-{U6EH#lOV$qN44*p0i3&vTdAS67kDvCjC#QJ^* zS&3y{R`Ry6#e_;AjEYu^RL6Fi>BfPQuA~Or7U^ujOUK}ESo0O3u&_bs=t4)mVBK!XnR*oEO5$=^@OX~H9jQWT1u+R) zPj0Z~6=w_ugD2o(npiWt<6)C$gyIJ>e$ODH49-ZQTk;ye?K&<>uy>k&!B zb;A@xu%0Ohm$YD=qgBQA6TLQ)DR>D`p#=jbu`SB!dSbq;9H(a#t%&ucy0WGxmNnsq ziQogCz4JZiOwNs)>5iBS>h~V_wl!idaGfzYO5R($7HQn}H+WZB^FnKxm1|Z zz#0p`{7b*ax4-rYdM+$8JlZE7Klx3b=8hlzoj=UrJ+D9ejQ8LFBA4rhr{bCEd2b9H z!@#VO)8!3WD&>6Q<@@jR;>AlITtP!moENer@)q$K;+lEPSFT3(+RrZF#pA~wEs?Zk zx+#Df9#21ca(W0rwvnA zj-#df!RNes{fccd7_)D$3?6BXn_)z&p_EKo7vi>&o54s&tB}<}-7PSF=$Y<2=IeylU(C2bL_ii*QnM_dx;^J|XNfE{69dAw-UM-Q9l~gO$3aJUB4Zt3jK8BucTd_vSsS?HDi3|p8>1igBJHeEO+DM_G z)j=!JR=fpg3^6G}px}1JHvqGp2!Q%KOIyCv#!7E6@5su#Qpyt(bKW=;zj`wW6(Ini4gVN~RU?+8BbN zkf?bhmW?15Ntp=)((#$DtZ_kf2VklJaH#tpwkqvCweR#!@AOW8XZphJ#AiN%EnB$ccHH0+3HsT7U zRAlH#Wy7mdv*YMToGLk2w$?y|wPncrBvQnZ1ybASU5{$r5yRr3Bx+UGn25`M^U?=P z6DZd!TP@H$od`xOhD=jOH+XU`J1i_)Qq;ZnbK7ayV;I<(X_>AE64n3!fB;EEK~z>N zS(Fd%Zpk?@IL|UStU9imVCX2h65Gsp7@6*m{LUZ#A-?^skNLH){TjE&j<0<5A>aDs zGhW=>v6z{5RZh#wLx~j47!S{Hp7ZOUe1renU-*Ba()i)u^*gwKc1Q3~Qej;-VvQWX z{6$n74^LOpmZ>GPrp$|*_c`9*;(E{Vc*DbIC;r+m{R%(#*M5c1zxquM&+e(+fxrCA zzr^!F`0)OQKF@sg;gPO$q*{3I`EzQsJUa^7636FXB8QF7AKp-!@aw<&>-^B~`JEi5 z10^+VQ=)XtcEN=XgVMD`-5m3hh-<;DkVv#`rS~1CGt@LA#xWR2ONC|Ka6_Q)-EKY1 z!kQI7^yJE3WY8+93du(rl~Fv8xgaXw?0$1r_olA_r41FlP&Kp67u4)zUDe4!4flR_a_B#(hhOQPy>N zd*JX?>&OOduB7vc)ijo;1>1Gx7I4c(sRn=WOostuI@V?5n#+D5iW_2uR5nU0co#Sf z6WyA~Ez);A4Z($gi<#C9Z05j?x18ca&Sj@=OGm1PZHwdzpTB-!Sq)hNY8`@5Ld1!1 ztd$5jwRF{SjS02Tj8au;M!3Aa@wX=KOov(s5}4GGT4oW05BoN`8iO`N=aoz*vbP_p zD%)P#O|Ax$mGfg`KCe6<8)Y=uYPpdSD+V7t#b+vwt~sm@kd##95D#o+5>u z0d;6mVzXE=)Gd?QTj$g_)|&Q!y#pTusabj>U^45PX;Sdkl54-01~es#5KBh1qtwjU z0;WN=im?rEzPGGOfAg&HZsYJy@AOW;Wo;IY>dAWzF;MsTR+c66>}i%fjvL0M@dlLMt<+6>=6@F1&ZR1(z@cYF-#! zpcq5ejPV^U7lO&eWu-Pl&I^r3t%hDY#P+P~N-pqn43xHE#ByFwq`Gor?iuBPv3np; z>xK_^bT&}r2~xsn;4)u1x{fgPq_lEf7CI3I+cACdBbIe0t}DR_r3xQ?@hkgUy)}|= z0`tgT4y-VX9hc>G^`ht%Cem34>xQnGDa$c9oSkTwi!(p z?>d}^uIq@UkZeSoKq;tp1nb%Ij4CLWP%|xMoCz$kayZ;_J)d!6*jmAj2Yjl;VZ<6UWc}@-HI3 zvdkMl{9`}N7r*==OTCh7#kRd_IH$&|kAIm@zWE8S9zVmB%xCTq=?8ME^xedpPd?$R z-~Kwcw>{2meE!)tc=6#ge(=L*jNWm7nE2=iKji%QhF}AmT7L84a~@A;maXzbFYcJ* zEBdL&W7sa4U;UM@@)Q51pJeC_ukuFIN;8FSw1nVEu@S`LhMvQ*lD0jov0PTv1PF$4 zxWkkctfEw;8A?-fD#W?6hK(^;MA`EPMzGc~c7nu4s{t{V(h6gPOr-OUQiUx?j2*ew zhUUusb!6%V4H@kn$wa!|p|$LEawlvXh*MHknhIVmsbtVZx?GsTOGG-RVW75#81^cn zSXj0hQeBe;@`NA^-eLX1bSr#u zNqqF)bF?UzoESn+b%ikAu+0mGgcG|T9!f%_5~hwd3*HI`3^6Vs`y_w6CYoB}GUL<{ zTVl%_2P+&T5N)P4LoAL$rO{Xu6g7jQyqU|D&+R)W7FmW^43^JOKHh_N8W6ApJ6 zANK?JxHjw3{e{nk2k_tpqibJP+Oxj7DvNqC92Sxa(@_6E3|oU zCGz5kB-93`RQL8FV;L$$BG&X+8ka5ct%sG5UNlnNkZz#&0o8&jVChg%nkcDcN^3}~ z9MuzJqt!;K86=YD3leWAUT{q*0>tl(QLyS!{Im~Vi6B%YdE8nd?qfD;dq0$g3YpDkgO6I5Wjz|6T>yn zZ>Q*mIX>aGu-AKX+LT()tZQb(UzNnNUJ{dH z5FQ@hU^~k^FN|SiTUWkt_kz#!=ZL~OpXtKLjepMBUFpXT>tKr;*YnKK4a93CFNxwk zOUc;I;{1jU9@jZ~Kk|5ar1J-ErePQt1&aN3kp^5mUmChESC>uXr4tcdXe^2@K*fse&BX^2+E33@f=9N;Pz@p`|c{ zKrNY@+Z&PthokD-%1MFJ1e+_T^$7`%MOQox>kQ5mI`LfAOe_Wga*K2~mRbtM>>cb* z_N)vRf_Q4)r<&<{!n=;^n&^j~58r#iWqabU|Kcy=bYSd8mU7-y3NjqvY6rhzx=#}Owra9`8@i&2jP@4w|#z3lxF*r&)LkpNHP?VAj zA$U&h3?X1*A-78P4wDO6VXK+c6fc1H`ulh9km21kGJs8n=>$@i3KSjvJ|hf{|z^6Gp=i!$d%W9D!i82X+!uO3)i<~Vkw zwBW2GbQ4`aV5&j;0pfzGpk2gb$+hgk=Bhl*i7vQ(;}%BVoL3&_C!FcI^-_tso6&Aex97lrS4U|MZDhFJAG%^Ji!`VNC$BY?}7N zLxVmHv@lR@C0-KVb>yv(ji=-li*VjHE_I`?J-!nxiuD19Bc%3om z9BJOOW~DoHtnmu!xGry4a^hy1=;_d^Eb)Q31=I)+Z$814#_(RptJ7;rYy@*41!bF8 z2Gc=m9EP4tF3j5*ACC0512h<%XBa(Ox?+rSKFv(sg!qozFfdh~~hEu@k_mFxlMs3e*c zl2uAx>6}mzOdrs$3xry%oC^@yAmr*w2+PJ&F!8pUyn=2}cnJm6lkih-3 z19@xI1wM}#Xa?H=Q+c|shztDm&;1Pl{?Ghn#_32HC+ZfN*Av0Rx>b5HIPY-A^4X`K zp%sR{r_{o;JI{2O7{J@XE zxxcyJY1rO!97fW$vgV3)4#c7kdg(Zy6WJ6t)A;sv<+kf#$y}$6bF5^s6x*1_K(YlF zCY*5$eb2V82(Y9`XFJpftXPN%>ny%gY*A()OT!h#Hbe4I4b0Urs-<@g6+_7dTb1A( z);GTR@7F>lJ7bK=A2iY*NZl|n*u z#cIQL9nC{eM=eUQ5X+2dg=+Rp)y{bEl-6jqpvFQCsCpU@rC{QQ6v33VhwD;~nc=g$*lv3E%gbNOBjiSo&_<~X^A`WjHdecL!%*%!! zI;1wHe%L3xx=~8Qx*p>l=#|sst9|;sJyES^3_Y|M0tOQ!EoXZ(87IBJm9i0ozmop|6 z@IqQw`fi|9rL@RpHt1RyF^IL;04ZM)1WefBPc(`y#OScb(^V*IM7o}?8(G=|X1$P> z#PP**qF7#?pBS$WV-sS8L*J27#yQ!$-p7F+Pl^lP^>i(QB&dp%hPIWQ6SfmD7BL1% z1(ypF_DQ}GNGkjEc^bfVwE0R}SFCqL6yH3~8JuYhexS9sGdF4@U|6=jjgaYhIoyL!gafHycx)) zGW8uRLO*!6)Tl{u&JZkIb0OxHL)YO#M-}1jMW8w5M&85W$VM1^$LmiY=!V8{*OM+0 zZ3c|T)I9{fh*FKP&1cf`gq8h+ER&&@cOj!?1T6C+OK^T zBOM@E<8ancDj2P7tzd$ORVgXb1dQ=aox=yqTq`lI4EN8`T97sahHMo2eZy2@!JFM4 zGjAD5g{k*o4Aup1Y)7pP&6!dKBLR~l)(fretbs;HX^xT`Ds?~HSW9Y+vFoWhVcTvk zscoTRv8`d8!DwTe1}-HM#A7JX8f#oZm4>4lPs9Tv@lMFd0v>tBT@@p=Lk(0{T@s&sutO~UXSsSfXI@jn9%C^9(&rX~_n79prhGmqFU?SD+!RVw0XOtHAt!cHD93aOH zPolFOBc8TeqEwuZG|O9FhS@nKK!SacVj9_4+*oO`Fm6JUMp9*~jk#q|hcrL}CL4Mi z_}pg#J*g~6OpJp;wS2cP(Z4Nn0LGZ_*aQ9}|M;KezxyZu1b^rc{(=AK-~RH?{1yJC zKmBL;SO4Wd{T+Gyzn#7lkN-~Z^u44+m|NZnu?WV(mKIvG$k5}uz$LDj6zQCyl?c{ROTuU-$4stD z7(KV+J)b|m=Cp1chXL<}H9m4(&KyjS44!#e=)=JBbfM&ov(Qwz>2Il%qHo(la$QlI zalzr5p_Ig`nG!3uD&ufKjgaDkDf{Pi-GS4#qUS5WPt?e^UWu*I8jJ+S zJ`lG|Zh|_fP3dx>8H)^t<6$H$85;)Xn#eUno3UBYoY5tdYT=T0#)e|SjvNNhdv|vv zYtS4yI!l9{s%s4A`HIIe3lRv1|#0(8wHaZ=kgO zU@e)xa~zA?StX))D>#Ai?v7XI#G9uJr^h$^`Y->7eC2n3nP2$#e;pTKUMi}FhGnY@ zn7w*fOu!6=IVO@cq-CzD;zLL099uG6pPmT8lJbUBa3-L}P)w!uZr?0s*tX1Nxv;jx zv#tj*SPQ`gw0UC5#0Xs;=$mrY#<>*U>y=>?wyj`%Be;f%V5DQNg&{aFALPoyNW1Lz zl}-(&28!WteDhm~HAvn0J!%@FGfjjGWry4q zp$iVNaOgVHn#sxIoA7GMeCzSb7&>YQ)UHFVK@H@%GKG$u5=jely3%zXE3lLq@Nes! zdn5{n<3OuQbBdUR8plq-^jK494r(%F7F;W+Z6v3pT*xt@)nnA7EmKP2;0HwE)K+}1 zpnEPV*bZA{hhu&Z>2DqtzAGDtKmSud#UK0wzaL}vH2t6dsh|3eJ@)_S;ou+n$N%KJ z!@@hg(|-zCf6LH4qxr--UkIHrj5h@78N{<*ABpLL7)$F1idBYjA|r&uK-$i`ZA2;t zOPmYVSZ9 z&Y*e2T8j@ou_WB^7SGGx9@I*qr3?zjdA#p=SYI)}x-xVs+BwVZ z%@H+jum21^{k&ihFpee8^ug{BEtDpW>1Lo=hZ_#uWC*!2g^ttt5obKAh37XtGFh56 zT;CK-tvtI8Sn-TQ&+*`R-950L_l(6R%4VUn@cbt5;oU7m7kF|10ts&|LnSiWfydJf zY~*#u`hij#b1qDiGK2$GEko$o+Jf&r)fr+-j5g4Di=lE|SHwA-G5FT7oke`6M>!51 z>+11lLW<)!1#+u9kgIxPDU_5@8payD>)6(Wn1-_!zmHO(H7I{GG@4kJ$kKbnfvFW` zq3;Krddk*VO-57Qt1-RfbdFq2V)Bmn-@C(GfzB}K$ki2Woe|q(Q{=iu)|8mWK-W7; zs#M)O`<=PqRSy5i`oCFg>1j?9A1#uGqF>=!p+p9F{8z?rj0jAId~xeTPWnZ(kb*rHdi^H z67Ss}*vUFPW7#IC@_@3 zVhhy@PAk$Xts0)TGlyVNr?{%*(s=!P;mfy2N;i{@!G-{)C!$az76mEz-qBPrRhdGE zX%(e%IF4Mlne!ra))9;adT<31L#3g*VpgzCuxhCnr8Ps7gp`cSkV_+$NG?hUmZmGS zmAx?!%UV|`pki>X($)6ezC{1FXs=`CJ6p%_79T zXsz+T|5yKy{PM5-3NKzf=fCsc{%`SvU;grc;^*`q_n6P0J^POST;A!O{`02$<8u&* zOC(-aR#vPDXp6jl^(m+8iTj&p^wUVJ8=*O3S~*|epjXTBIMB8YqaDTta*Ir11RBeH z;wX2hZ}djV+X|x8xU!atmjOhG%M~+PhH1p&aBjePL(#pxN5gf8vDHSM7gDUe_x^j- zR6|_XWT5?uy!oa#lsu~W{#JtQH@jyjQ z#e@zK$7n2LuWYRlLXV?S=b5GrYXbB2LK9&M9mf}UJgp0_uPYJ^gSAXUPu~i*^Qduj zVMM*db&i}PwNy&m=sQoi38WNxdH+7A>wCnueClaye0Yi?JQGWCrJ%Pc!$= zZmBubjUI6UgW-kgS@OmQ@4w*tUVec$Z(fs)5~Xpxop|r=J(lx@J~*mT4#O=1w0y;e zK<`ExjwSEegVGB3xA)wd1Ub`VX==F68?~+sp=VAJ)Z)hx6@{#H^fY#MK}~1MH4{F# zrF5R-&4KxR;u^P|^z01X;Hk-Cy}_D2+&vJWWH1VnsW#A?z|ao}mTNjwXgDJbzNhv| ziVaIZO{LVvx+%Ti1NVarT3iUE!H-&t~f7@*3-6zGY*SjOQR`lR8rn}Di<)8FC2OdhBYZ^Dcp@m&LtvN8QlSs z8>?ngP&#Kxso|{9HpLjwvGT3;3hKFj`UKXXz?#o2+lA>caN`YQ*OLa>hc2*Vh@}4X6odS_w8Vn})G_%aapJt{Yu3q+IddAid?fZipCq zgn(mqY2OO6vtFnh-Wi4#C{nrB6;DqmjVcjKM-ZW^@br{Z#)rKy@8awoTl#aDjt%lzwq?%!mZCVt=V`@Q_dfA=r_ zN1yi>|J}dD@Bh8OhiRJl*Zz5myF?hWY6=Z(e^&R|E5# zM|9h`@9ycm+bcB($9lbRy!iBjfO#o0~hv!wu_Jsk!28 zV4QAfPB7*`E1qZ1UQml*t7AP!$^v(XmwV-8O0>C9m&j$=YcrRWSaV^Gjo=5G-#goz zZ!B>kgnplt)`o3`o5O@Lid9P=ZUKvg8;bSJ*Ol$k$eXgojcS$iy5ao9=sgZaTn}mq z;z+Hsv_i>-H*a2Xe*MVxv>yo8G=o&sMAnvAOQDy5=0^2~xi+#2*SEfO=Lg=bD-Um0 z4(@;#i%G)NAF$RV2DW+Oe3^NAypYZd@o^z-4G4_G4d}pfi5L}@R59MNZC6}^d@j2! z#yW5#!FPyPv~&35NYULAA_l}jOf${EbexE3#aQL#?SU^ozvatcc*&REf6k!~yu82V za65859-$X%Q1F(%56IY<)5iH)F)|XwklIY>9pXD;+NkpttC_B!Skp$%iQZY7Bw|`9 zDyR#1yIX*~v8-FBsnDEZIC!oxbGlZ({pka1h0axMZ&5F}zQg+g?|WKvSaFQg9m6da0^PAf{{ygV^43)fsJ z>Uq3weC=CL{K{8f@$FX+oX!ia7MvJ*49{DXpUx%XW#(mxVPY=91Y=!*>&29Y!k7 zS=O?VZKk~Xox9@Pe|AH&^C};qZwFgMy%P(15%(~1+y8PI-C)F z@thwr+u~TxnWqapoF56jCwNb;@ch{=L6qmWJzu=F+zbhE3tiWdohPYqrLolvwc;jE zDGNStI8$kzFdPp|{+6u@NtLS5TH|Ku>BMm72U=+KP8gb}Gy!jk9@bj8wv}vmFlCX* zUi@n*#u05ropRX{57(8aws3A&ma<}v!`cHmH$*c=6Rkwnb*9vfW(w!i2~Cwdv3nUs ztJrKHH*9URsKnGLTc)j**fO;?S`|!gn3_{Slr&d&d9vfA?p2@%%YI`XfK`w|x9pf9==!lmFO1$}sf& z1AqS?=AZZ{|3?7)eSgp2!$1Eo{)_wrfBzo_RsOzu0RH;l_(gvDXMTo% z`Cs|7z~6ap<8Swv@AOXZ^tYGR1vMRm_1MAj;_#f*RlfH6TX-G$;)^eFUap)UF7&10 zeZNoimK_GPoo1$+i5}Ve-32HH>c;3S#tKV3b6yubhE^|hoka*#v9vAWLttr-xT|8C zp;38u^Nca{yoobj9Ou)8B86c*0)@0(KrC~EZH2mS)HyMABcTs8(Y*t{DcM9W>x|LB z^JgzO$CVUUp5ON5Eiw%qF}^+Y*vhsRhSu$y1(Z|^HdVw0VmV<2x#+vd#_X%-*XW zPauYQy-;!klWA7z-GmpR3kQsKBx!6acnxUUxTY&!9byBQd8T(A*Ed&eU&(PntVgx6 zZ4GBU)>@hoj>jVpdBb_jS{A0`$TZ!OOJcB)i?YPVaqOTqL=4yK#@+or9=I%5YKn{! zNb{L#5`y1}u_8T1EuD8ez%LY}e`^P$uv}++@^}?$sbp1ReZ_H{_BIwL`2IjB5nGQO z?|a@{9tn1&T7$!qbHs#3D+wuXZxeD#=qEyoST(3^cyWjuan(_BBy>I5gXV@9MO(rd zM?+XH4bvLE>u4q8I!9+5ZOH@~2t8~*v6h61nHn?B7_>H2gjOr1Dh;r;?T0n1oLj+% zjvGI4ZHaK`IiH{S&98mTdU?Z_zx|tw>&kIl z`QjIj+>8OM2Iqwz_|ivgPbd0ez&S&jL0W_6ktR$|sANp%umoBa#Dg_ptm3R=y_`wg zOiq>Vek8|Ayl$krU|b-@MiWP=m32*E9d2~o+}v}44&Q_c#MFy%d#GLr_bitxX>A?hoI4+kn)6uhO<#Z854NDK# z^@&?Qpw067(>J{T@)>bco=x{m#!y}uroLy3@YFp4h4Tp>PBUY(Ontv^ttx08kYceZ za}#>1EeN~qEU_OPb#k}{Z7X!nGjx{PDyb>1b~rJ#Dm3e`HPIThDtkj+u~ZDk2oKl7 z_h*B}v8|DsD%DxE6%1f?#A>E$risI-QoSM45GM?MPn!$L7O0BGL}*GW8v~AN45ntZ zKy3|eiDs148nH!UGt8wTqG+gyNgUk(@j#R(e6>_H3?-3`rAXNW)Za^bdtd!sYgPJ( z|JWbnfA+ulQ~b~Wr~l*s#9#BJFMY&&FJJPrKl^hi%6l(g@}-YH0zei1g@5j!whaPJ`{}4+AMX`ol9$W#jSH=k(U#LPxU`H=|=+PH))=J!@|G&eBlO9&G_< zLDbN-Ks9#1Igw0@1wrt+A=;?YFx`RDDi|=@(^|yZfU%XN4U7;qvz$I*$yaXVp4uu# z9X527obY0y71C-5t_P>MLCJZ=>K%h0Ski*?Bc^(~?tn4En>Vjffvkqq1ZSZ5ouYi# z?Tt!nTX*o82^>3xR`4=0+}u#o#`$z*x$ezEgPTZcM@oghhp8J_ zi(yTPxMjvEuwG9rC1b3G2BRP7&B)qLduvpz40vjaJk^Yil^_n&7KY&&sa`NfaHe7h zLvAzuXz>~thd>uRY6`U~&tJUcvMk)b963F_qHn@o2;3Zdax1JT_xE?4Pfz;UM@Qs z*b2RPjPtnO;;qG*%5_`F;^>`2spOpTA&^TVn~F8is%4$8bi;^vhwnUN@6lB0!vw`q z^2Xpt$d$NdBxk<=2jAycf8{q37m&7L#i7Ob&9OAW8(O`mNe)@3m$(ny-1Ar@NmVyt68SNWqeK)sianI+>GiO!(e8_{Mme z`?{_lr2-9giFn_WOr^Dzk}F|Q#5rS(r|%nCB3guaK2fp-uL-uH%fOT~lGN5-wF}VT zbY1Yyqe`Lrz~kvc*KPHtIYo5yU^Trn8)LzEbaCV&oMb{PMNKG; zvLjSW(26Vt;~hu>EY5AVprE&wv8IG78r!z8ux^p+3R6)m)6Au69?ywL;rZUuTZhUW zB}UHcN^V>D{5(C;YlX>)+$(MlFVS(v=1gB#UOd0$jk@yk?l~=G9-kh0b~tjL8q@WP zm$qGkC{1t*)jXY1VEYgGo{Vh;yr&3MNf=4o*dFN|sVr#aIW~w1N@+OCmVql3rxYTE zH}4<0e9>_XBVsbMtY~j=&Z3oLE(^IXn6A;eo+(wV*OHHU7_k`|t1<{=$#{?wmwt`^@IU%L^4I^H|0X$S{`ddU|A~M1Z~cw`%+K@x z>pef}qdw{%ujYE*UuItH?npVZqzlve%x>%mon}6NLuV}eVMkkp@i-txkaZ=ll~giu zX()kQ3))+}Y*m>!SYkqXL)DdKTItx6>J^W0Emtt(<~1#ip>_13amq6;jHr@{*NJNh zG*x(Z`;0%QDOM24lw481;oBD8ZPHlMm0;lOhu0|8VX7qz z7UL97TWZX#snP(AuI6rOmuUQJiZlJB3?|WGi7f%mv#NCW8kq%az)X45nk( z2Y&YT*R0EP-hFi?JP*w46V~10oX4Ut+I;A`?^)Lw?*>|{ND~Gfkdl}rp<1Kl8L7gu zTt86A4UeY?8KgAUWuX9Fu$bP{8_&)bg7+Numh%(|$ByY*&~xSKX<@!joE$uxBd?z( zb~giuam1_z(L(+Z1RkA1+dxwds-Q$+jKwvmrIDp>_MMv1I&9ykoXH-lxfdQZ+mFb36WTWR_NYQQS{c+H%%@H zqYOD_EDmJ^A){I)REtuYVk}l)8MLq#MNk7-1Z%u6o7SkyCkx zZW$w)k`k#taC>`8JGU(~z8mR+LmGVc@<^P7pMCwnZVV)E(cW%hb}Y(hI#WnyVTz6z zBU)#)twfoTW~f+HDOeA?(QV;rD#lc*FMwr1lOw#n%)HoDtm#QCbaXglnMV|n)M*|)t!|AH6%?T>#hIS&8rzy5FWKmT9-ulYOw$^V7# ze+Vl7v3~P6euMwuKm0#@`1@b}lh^<4|JJ|7|Mq|PAMo$}_x}eB!@$4!-}tZp!rt?v zKI)_X@tUp|y8XyGPNY1sNX06J7DbK=G0s%iW4j*HuM{%NC38AoFeMR}8CL^h8pc3d z8&7MYYL9aor8U}Zn;ENjR0Y;3&dZ9)mfdcT@r`MTtWIdT()T;QdH04fj9A-Yyuql( zdVa)*5o;YvGW|F}PDm=0Fkt;auNBkdJ1&e-GCe94(p)L6GI&dEnb(i+@hUK#PtY`852)VamFGAfnOa17 zhq4;$8gX6eOkmd!l;Y?OT$U5o8H`r=!$3}npb|C;F*TyKBwg{!;H=Q%nItP&L)Q%) zb|ZO>-aS5$;zq-LcbXBk z0kMisbiO0UgztJRislq8Yxd)SRf;ScZ+0lH5wl{jWK~FOLbS%XfD#DX-8)fdy3S#W zCIm}PD+Eu9i4KkE6=fV6OBMm$pqY&>Y+>vMnl`AG2&y1eh*_aj+vvE~ATd*$Mv6w& z2ujmTL9An*X1bs$wbGiV)vXHCdygzaYnm73yh>dMpg z%>VU2{cZm3|IT0M)r&WrE-N++loVkr#AV6r7J?2MYNC|HwVWwZ&{{xMN@}DflU89) zkvXl<3g_#EwF+4a-C$`NZ14=dMW#eknrXRkdlOij(07?EE4OCgr(c~|n&NUgq23pU zca|Gt`26_`T61(CsLn2ELe~@1imr(`C)yghsDM_Ax@L41oYGwK3>vK8p=zVmigy;> zgqSMccnlM*Xmr_AL^F(yQiWL>Ni4_xftnH)%QRgnx*{d9Qx;R>)|^A5D-o1o^ha7- zIp>VVa=ET3yU=wbaZT7JC@d)nm%QMb#u>v*=2|w|^N=>WcJtfjGgpOGnFUbixZ_{>E8pSk%Om&KM6I5hD@h_g!)hvf zzo*ke?hGY6su&uAZzHW#imlXU(XG&~i6Jjjiarm~E=sy+xnI9bfN%g1ypRYfA z_Kbh;zxUt$$NoE`HUH!P(f{!O>-BLQ`FH>3fA$H(Kh1l7)JJ{PKVHEkTDGidr4?|> zkq{;+>?n9OptYm39fIL0K2cMo*A_93qBXa>Jt}yV&79W-28)r3@m0-~wVR>ZWeX{L89^A%eIr}I7CvjaIVgx>IUdfEuW&5-i8s7YzmqUfA( zxqnBue}Pw!FIO56Q^wmpqw7#ofR(HS*AGxC<6*?xj+HC2tn`DYI7^g5%W%C$ysIQ5 zEGhB)cmqwbQyoodUVirrOlL60pxt)UI$sx*vRtPJj=m#U!`UqCdkW<8~NRS!=;`&=BUO^69IW%=3acORXzLInrgqS&#A- zNCZvZF0C&kjBJj}uC-cxTB)u)af1 zXS~&T2g-OZ%Zh3RWelz#aX+0fCeftBbvxR!0EM(JlzAaH!y=WK z64U(~hQki0EJYf-n?2WSCXtz@8Rr5~7Ep>gXQX9RwVd*Vh!C`*m9+iBtc}zPS{t;r zbS5&_%IX`gC~VQhrqJ5pl|@>nnYvYcmd5G}vj`UO;t(22Zxrz?E2?T*7P_EOs$q{k zH@5@ja^(^;MQ!;V-4ryvwLK}rnk&5t$XXx>t|d@H!(zno)$hM0wMxG~U`>R0A&h~^ zH*UHBsq%wQp3~i5NjTPNp{<%JFVvEGXeYGxC`k-9&|;&xtqxczgI0x>Gre-mu8^`p zN!xVFb(k8Mrb0_#tfg8* zpll2lW9W3I>Pl>tbz?dr7LrQLGGUv+nSo|JPOGi`Y>rs(>Al8f&6BFoB2pa^;pyr8 zZ76|7h zP51l`KOD%VaD6;84tIR|>W2GwKc|L@5{>ILI!GbVJYS`h=%rJzeQsV4fzX<;t%2bb3d(JL1#K z?cq5^3tsh`zqnSyKG3Qor$SyYC}j|*h$`YtfUGyRkSeGTR*6ep80av{GAd1@GK7vf zEg+R;nYr6J)>t45w2Gw6zIROPf)vfXthCuM8jbgswJ3_QG|A`&NojJ;4BZjs0!<0y z(37(x3_ZQ=S!F?M!{t07^TO@j4vgP?!q-SJmay+oTDHcMW(h-}Sz8L^_7pB~6XSZkoR$Z!~mamBTY zgpuLyh~$|TSAq|$YsPs;Sr?R51gBV*h7X!B_5{}l9^e=u|L9Q=4ogX61-g zXj(xlsBNo!IV3%}+}wa^v|N?~=w7=tl|X5jL8!F~W(jwVoYCFT|14>S~=>&R=ySWj+3?|TrT z(i5EDDuuhAIYw%XsH6$5V#k5U>zQW2nSgw_%??U4rbkLXtN_$qLwq~cA7){4j zZwE#%Y6GV^LoR|UTb)gn#A+HM4XndyhgOPCKovzH6Q3f!!H<9N9WLiH*QYb5tKfnq z&6R8_msV)aA*CU*5K7y^+`)lv*i^X-JIo&5U7x5T=mOUY%T@4FIA5=P|BD+^%`D}L zYJ$59-0TMI+Hp~lnpRqBq@Y=1VlX`_HAKy5*HG5rttXu;O(~KRVm>j@6Uh*S%hEWV zXY8G&YaOFi-Ozt9pFDV)MefK9Qbgv{H!8cn`{0E_G(TPRP@O4?P_uog-pxMsz0x5L>+u9Z1nXjy1V;d@7If@p;UY*AR<;cXxl@U=0g8D-7JRH>RsA-RRo z+O0m*Wm*QzmWlX$cSDhhSQOUw?40L3ub56_l`!8w(VPce`Q+7scDizUTtVRJ%>(+g zmsBcM)MkyD5-HADZAmeqtR+fDM3d_lKrb|`_q1kN>$bVAtI&-jWfiWmfKH6V9ye%6 z6OPW{u zT~A#XnygSiScuw{Fk0fOm{Vf5!Y&uAQp8q2Oce`aGT92MZH-qlM|3kZQ6yC<3Yy+{ z4y_g(mRcdV?f08BLsOMCSN`EY{0U7OpTF88z7h7GQgWsr99cDLi)ak*-#sy>nZNgN;^oViNU9(iWiqa6q(pX3V_!Zemjx?| zN4Cn{`(+`ft+F+*kx3w{%4!oMfx$J>T3J;=Wkp&G?;cieZ*OQ?vE)coj%!-6qRFjN zL}5g+DhOCya$vUumXf+1Mq4Emg|&g^3_3PctrQW;I^(LMGq<}Vn#Q{~uW91xcO%yvd3ygqaE2_EufBP| zRj2~}*m1d>c=PTxIVYU$P||2p3Bv{q8^#^S+an@{n6BiU$Zg?tJrh*m#oZlm-+YDl z4pEw=28^S^xc3~mG_U| zP~uF=+by`!n*IJrE{&8{q&9M%32vK)G$mMPaKSPR2b}YaeH8bP-}LzBvdehk#wSl0!s z9Y~`Vuu5T2%%z~4B@8`PZPTb$E4mtD+5+iuTG@q;oeiW~w$IQqZ7?w!U2v2rq*PHN z6uS|NwF~5?$m@btjYtLG@$B#kWo|q?o%rI_o;79!sOuJ+DN4vjp{=2$g4P->2I^M7 zYMdsCLMuh2p~PW|p(tVT+r+2Vg4G%m3rZw-T4|(O zX-3E~p|!?4jgyAXplQrSkhu|*A!)@@U|kEvYKl~@*F>Bx)10Yu#t~@4o)WDNjwIQNei;$&|1Xdbr$#mWm(U_p{)}p<~ z(zYpa+a@SYx$WTM5=An-@iYzJ|K(r(wwLK&i2l;A{VJ*5b>e!yXSg|{jKw;I zv_dI{!)}L^i0?Gd`UB_tCziIL^oaF}R3gr5T8{kmw|^I_EYo};beY1>VlHrS*Q6*!;c&A@ zX+uol#vQNUyrJcV(lVyOKJ=tgaNaTuJ>*I)8Eu6sGl~&Y1I8+*c_P+^D7cmrNg`pl zL+>2x!?KZmodYXdnZF&e-r!Y+Dir1MzNfnFv$ifXopq!ok=L}{e40k*chsU0*Qi<} zy5XFo8+JrVh)L+?FeYqpC~N43j$Ui3z`8^_tI0c8Z`Hlne~ zjwFIMjv;hBoM+~1Ay1XjH=0an0@*`Fh!iXqDriOqKOp(KaRj_WrHqJTi5U<|5o!^X zQgmiuoo9+^=mJWwC{awyigjC~&(8G3HleX}J2;;%8^@yW`R35%{?S+O zP#Bh_Fi(-YeZU)!q>9Q7RR!Aib1p?AUQoJ0vtSyQ%7DeN8OCB-Nd?aHLU4}YHmixz ziY9_@P?BJE#q}0cN6Isbt@2gc%H83>`=@(c-)&i|H6j{X)<|yL9vn?OYSS2#>8zl& zCFw$^G^#AjwP1avHpBHAk(9`FdxpIa+}J=G3-h(HI$>=>BQjQtT`g-fxGj=EEEQ`M zO=SiHwJ4NQA1Z$%gAsD|8*y1}pSx-Tk%keWw~8uoKNl{4{|$ff%NP90_YeHe&+eI* z!sF?RvtWt_Q&BZ;`G0FDYzJ&B72_RMS8UQ-t*rJ)(U>TRsg&MQTVpDPTovkV=KOfZ zI>SUG=}1c%Z3L$qO*IbB_MBp2i3K-k^e~c=5Sv0I;f!V%9n-_g>D`4-p50KbY?lW` zC{=Jq(X2t%#(7@ocRjw$A1a51mI|x_y0W6dDzs<gtye};1wLZTZy>k^sfc^d&64Fv=k_8n1W(!5gV4E@B~u80C-9Jwsi zr-^G$7^O(dwlP-5p^T=LM9Wath*0Rmj(MGM*74c%=d7x1HHJoS`^;gWC`|*dF|oXV z!_Mtd7@8K0uIQFXDWhsnta|GrZ$hn+>D?o)G>l8E^OcrYj2_tC9GIrJEKdt0P<;>D zTuY6{J&`U^!n;IA<}& zuvdG0XGkTZ#bM3L@opejA?KNrdv?9z_PFP1IdhpNS}FM3BUnxI9jYq4+G;AL54==& zs8%q>(XhzkIiF`FzGv4bjxP?hx{$7oowgibeGg?ldA<ph`wlP)?y`q{^~Uz(cof z1f6G<1*uQJ zhrnDaLGRJFk+9TxVVcgHwM8ky;FzaG=SGSw7*Xij7(<6~mV@(%lI=5*3R*Yz$0OEt zG^tQBdw<}%o@hqV)W|g@wC?DPC)JDtbSX%VDBCbiAcJ-WX9J}~XocWAl(Ur8K!e~N zZ=PmqXLvCTEHP87LmNZQ8A%aSEW{O5BPk*2!f4zEi>noFH7L*P_xF5q8fp7GE@god z$WqWwa}$o#HKBq-D@%?UDGe)zysjvvkyP;7f!VB5^BgHsDC^AKe#g`G9%+?QDnsk2 zN>f%vTr<8~$VApTGmIU_&T=`;1Z~MFZ`=~sGhHTzp<_L-tP4CWk?#gijTur#H;ZYO zwQM!RIahqO)Ve?|IP10za)VHXx@1%^RFkkx(bYiIm8G5WD3pUz4PEf$n($%Z{yH<2 z8Lu_gI+AUqB)oY3Df8t-tr^{EEQT~Sq$IA<_WcQGxA6P( z&z|$suimgK$IkUQ+Y?N}R7;jEV*rI6LN%Ht8&usw&qir%g-8S42*zNtMk6Q#%_wTE zU^I)Vh-65O&>EVph(NMJ=|*BZe0gPQHt_eqdgSi?h2zff%fIxBXg=fb|MpK=?geKR zEpMA`<^>Zsav^0QxJ5cRnXevq_D<_W()hbT=`~Q=tje!9Mc-v+s2`H z6mL+*vd$N3glLjUC9MQ$h*&Th7X@QrQ649e zUGS8asj5+=5wQfl0T*@EXk$_9g4PzTgd)O4t%%a7W;r&?L)j)V_(oS8PjRBe%Geym z7o=9Ib1VW~ueV9361?d^C%Sq5wwLHX6@9QOIl6TDwx8vrKI;E(^ykH@6s}(}hLKtu zOKs?4Ar+KXT+XbdCy@OI9(Txhv!fm2XE;*Lyp_+Q%#Z7 zD$OdCNl<1~&Ey<$0;Lq9Dx@5@TlM1${I2I=y$=Avs_msXNJ&2vvlL0X};in zz}UvP+W=ilEfm$Lx?x&nKaNMz&Us9LCKV0PAY~y= z3zzemlnSm7q&CrXrSp!b>7FEt^}6!z@d6co_~|F~!CKh)mZPzRhZ)fb|dO)tw=f{t&t0)ITF{IaqMY1Qr1jK!jwT9 zgDHtQ3Qx<*JWr@p_~NrWn%*|yz3mB|Wr~kvQK;I8Y2~xezF?6nb4p}U^uh4u%exH* z=6ase!lg_s6;j+9!qzp#*A~0ScKIU z#KYrs#@4cd8GVn<3QfTciq@c0ic&H)7jzY7nfUz6FF9Rie7A!Jx--OFnX}-mBa7kT zDRKAgj%zIJLx3V!qabH;tL*oI7dJOFRcJO5#*VDimKA7~)j>23Z4|44#np|9j38KA z3uqmPwPKZKPywZ(7|?aA4Axp)t1^F-GO0#VCG$wpyXBceC5GK$bt6@>p z#gTGA=y1K^S~D*{{}JiDQ0Fs#bS$Rw$&1@9*N_Tdyn2Cg9uorhPmwc%a*n1nPH9xG zv?g?}!&;9MLwQ;$YQ^mhA$Wog=oYA=KxtUPnk}zV+Ct2RLPp6-a9aiQv3MTm8FVE$ z%fm8rofEBi+EP(PkfK>jL0X`q`O&Za+PA$-|3dVae(hI@e$Q|Hu~n(5k7lKh`lyfk zr&nd-DMo4nV>&MDm8utjiHetyG5KiSf$y>51`hhxuTK zIGrCT*T`;oPS%-1-GFiN9n}cZDO@$=Qpjb-sGh!a=vKENYZb{r z&5YeGxzQKs0H-;1!a+yJoC^r%`Je=R*uZF=Inytjr@XoXE z0;w#VA0KJ0;Pnoz0#+&By?=`{0d2MxqG?@Gcr;)Ji#0uBD@!hPXr4X)gvde#+}`4@ z6;&-a&+ce6tO?BJf^C(<;lR0B(!3Cc5$^)m<&1F#s)043X=qbvk~TlIb7%z9HX^gP zforuqt{3dQY+>M1dGoV(tk)S`654oN6Go%aW#RJlhzi0wujpn_rV*S$V`#~;EE6qn z-t4$W%+P`N=x*G4{ddpN-qKZr)e+M)%E1&@*7JpwBSYU4c1P;GqOGGJdRz#AVi`f(vME>8vN_gjI@a0I8U4@ZFK5GtH?jSEB|n2Coe3dL`G4H=67G zfFF8{HVngn3mwU9)*jV_TnpBE)@9v-xxs@4sfBgAVy&erh4*f=dysKPQxLL1ni9@> zN~?6$|AF<70wcT#jd(bLkTEPd6 zNagN!&rs_zN%4j>#dJC9muu=dCLH53>ZrcQxrGc^p7iept?=?uJ)QnV361x%H zTiI^FbEFAmQ3R`iM8OjJ9*ZN-mF6`2V5zN8L}(i7GBf&-ScEs{C+d9RJFi~x`HRok zkI#9nE<7$LN-K=Y;l$&tA)Cxv7SJJI72(U9k$noxapJs8sA9R@^|-EM z=z6}6->^n7&1`v-6gl>azUzo}r5Oc9Xe|;2@ERqBSSvv*Y-=>7a7K|@MoI<)O2A_% z3n-@vx&fNj;P<||5Zui3n~u+4?D+n(TmImyw}^tZMXc6ny)_U;Dy>;^YiKPPGDbBT zf~l5hHpW61EWR10wDR)qIel=ve{&%(TOeCWf!3HralI^{HGlNgH-vTJZd4rlp6~gd zOH8C9bj9+`yKngX)ib;|NQvkK+H0gWs&C9WG4?%ZwnMrSeAnS~q5xSbf@`!@5Q$8A z#fWE_BMpTyo?=wGlt zT9rQPqdw|CrRxwIozq{qMM!a>G6Mz^*?W@q33gBQc)w&Y@BC z!;V8ZAgxiPV5lf(@L@;Eg6lQvp>C6&si2~8)7`;Vq{QubOLZ_!6R!8D*09PDmq;xF zX-0KDQVrgDmg|M(@yvCeNGUS*BP|JYPB;z5Byvr3ZlLdau6e=Kl~4_}R8oy7tLb!L zStg`Z=5(gp-=dsDN?{1wd%4?N*d0L`42l>Zk+LAAVzbBSfYuT59`ylYRkaYDpuEBL zhB#NWF1X%PrDAoV3rA9ntji22ysa3?Xsu~FZGP>vvcyOj4p_61lNX6tZ%`tnb;YSI z>{_cP48!J~*N&<+%{Z)5NCx9Qh@;kprW(`v!cE)JX$?LwJxvYHu``_d1 zZ=T57;0pvdP)Y$5H5ZHlXFaK$Sxe*(|G^*dEC0%`P|AX}H)sQz&6Xo|gUJnTq*Ior z6tPxHifB_&sysYQ(JYyFkg2&^y+&;9DRz z4S0l1P6=ZgyDl)Li3F6Ua8{!%+ri#Bj^lyzc|n{|TP8C#vL`@I6gm& z96mpA47dD0fA0@@|L!NuO9rd>%YXSVu&guRynahA72P?8T=?wh(DBOP8`>%krlS=> znvkSmjK@@gTIhmBsme_chaZ2&yZ1BK#|3ob`0S2jA9#0tqCfQH4A=8a!67Z9=eS-Q&I&I--4UmWr>9C5wG}p0_}QBa-@Jd~%U3u2+An|3 z)BPhqd%e-pv5KZFA#F?*W7rABeI z{6h1VU^O9G(j2*{$b3=!{Q923^hl?$24+dDYvrXDOtW|zxvr=NtInt-xGvCIrWU|h z5}J4O!mjJ6394^MD%6soRBWqMCW@^jQPk9EDH3!enF!TlYhxi&R`5DdizWokDjK~C zVphBb(c2nPi%?wswwLHX6@553v~T-4KI)@B>R&+RB{66R5^-LWWCiVTz7nsAka`Ah zxn9nk&xM=eh*1zaN1PMhZnwyDS$OmI4R`$w-LoAJr+28LxIF~A4?f#zTF|}YI$v-V z_WO=C&*ZgIT1KisC-nV*X_nLV#Nlp_H;D>VqtKlpsfgd*;`@&4^-8=>7^mqwOW!o{ z10=~L!>VSUt|#0ua-J4Ce_%IQmO1nG%^Px_*cHX~;T=Zxs5)~z9(g#uXBY!P7fL5s z4v*QZBl4O4|<-*MO9V7rdD?_cwHy{Fq*ZufT_&V$`GtMBrDTt)io^a$YYCMasTAHnyv0k$ zt{YhLitk2D-{^aTtBvd0=(HhUCqn12UC&{-VRtwX^MavLqfkSk8BI^1mdf>V;xO(= zr81pY#2KmPGLKmxW#5VT(eHLTiUp4JD1B4Y~;F!xY6T zg-E73LzT+Vc}lG~F=!=(!LY6iT|Z)-P+BGCiq+tPWnEWZ-5g0dqM9bPnMn+#7QES- z$Bv<6NfufnX^$NOV>fa<_B0BA`17A~d1`dp@$$vS;LuI6%!S?Hx;FIq>=iyz$Shv;oO2HUQ-|dmIqCVKC)_mofPBa`AO;L)N7bq9TL&q22 z{gQQUSnc`Q@BfS?Ur=_lI`v-DS;x=b+@nomXAO^QWKEg7!-09d5)Mb6-Hbdv8LS)m zM_)e>+JIWGTdUZT$fH6iD5r7G;gurQg0%`~6;{J?xia(vk}IVOYf2dH03k}mwGNR$ zs|&hSL__>=Q|x2|#LbfN7UGgwt`|BJ2*)F9igdSle0Q@h0*ljS?fxIW!JQGm|Wok`QS~479e8 zmJ2mkg4-c!K^aZx223zKohDQjYHpk#pP)8ME@%T1EVVb--qTW|_Z?-q&;^b515F#2 zR0!PxWV8MRGmtUlHKLVex?C|?p-jUm;r(=?33PsBO&5Z8ROLYux))M9p=@P|r|tIB zH@XlA#^HL$Okx0nRXpF_VVz^0SBwjsu4m?!sY+peCD%%49a14R!G;~nl<03fXb`%yxSFKMAi zD~0bm20s#)2xx|JM@kb?x7)=U0?>9+;Pm3khd?q*-c9 zpcKYh#&Jh$c?;XO40VUdW@%}4MU3TI78+Zllaz#RnwA9Lw&_bG5r%=dUP#iYab^q_ zuQgdgs)?2oH5PClqb=5LjWvzJU_DPVFMrgvBqG1<$QYJGI*>A&pvz3>$hLy#bZojDT;A$ zR5H>uswv{UvdjyuDX5874K)k%ve27KKkWJDCtqQkAxb8yiN4EBd8KAcZxzGso*^lI z^t~VR&D(Ey7LH6)VV)EH?uPddPdM+f-GTXZ;fJ>eel8bYzpc~+aTWT}pa+MX&U9Kx z)lz_>Dq0lYds1nnx!|oMRam89OJj6_)C^5msIu89wPW9V)>df7V{9N;PnzBl3>Y71 zW@}HgOXkI}XRccd(sdE8B6PzJiefkh_TFNeqm`BZlLNo~llSy{%YLUhPY;C2Vnp$H zJ@NAPj`) zTRvqc4OI-j^Q2O-&cW!(R$Ak~##=IxARc24muW@$ z9@iq07o1a+mYGr}=zwjO(lWNewMJYQnBz3?ub*=DYC9 zI22sds0P|W6~)R*To=k!(TnDA6L8+L&Q}n{ZtOTcJ+Rglf9QC8dPkGZ%U#P#(~V)* zVNsNr=-6x&7=`UTd7aTlGj;>l`NC*C+Bk|X^tT<(I8s_!mdYZHbxFuPHgdQ3j#>k$R?a2j$#?_Hv{0hp z{1!Iu8>ps<&Y)GJ^8sTNah}P_KwS`JX=cQQjwXt#XB3T?7p~KlT{q&KMYjgIA`RwP zK{Z-laa}-J&H6Nxo3NfYlI>GG^T}tg$oT^;x^B1QPB&uC9ETkfg`?}S*jMwVqNGT0tj;o zW$br6EpMn5oN;{m>N%lrtd9v<3t~0PT+z;wSK+wxj8XCW-Iadlxpj?wu>8^|&v<># zh?y`{o*py1@yO8adHwdx;nwrnr_X6rF7F@t^!6E}x?@@jbdIt-p|#+o*N>K`F3fl*YZoI#qC~?JjPtA~H!3E2r(>Ar!8l|8n zP0*INmxOhSrYg4?bZyiO>%79y<8?z?Lo2XagVxlp(Ko|d3fiEZ)>x%c#&bKoplZv)JErr(tD7$w^-8J> zm5jd`Xe4^4umW~CQe&eV6w7tO_YOT2ob?13Sf!#nFxJ!JLf3ng5^B1zE*k4C%|LG) z)AfP0obcU%tPA}Z$T`tW#4C$Z4k}y!c}v*i4sQ**G>p|~>Bu=@dykG2#%R9lQR( znyxhE384`ekLx|&8N3~EL(jZSIHzbRY&K8})d)3fD#BnjqCk`)OT~3PE+|9_!S}4o z%-;9d=21kFG@Mq%^@?d0XFaX2C>05=K~t>anN#C1dOU_TPW0V?Q5tCjRoZq4XrP*4 zZAZyvs~KKanl^~;$+gn7Vz7Y{6D3aQ(CCXH)h*EMeTOt*JdUJ?h%N?W9SYFiaC^KV z#hJ1c=4+(%g7^)7sEk5tb;o^LNOR*SKlv%HgMP52v{8K((9HHTwBL;wYdD>s==vQJ zGqDKMvM@~(f+af`T%arq6LBLaG)GSdb7P7Zl2l4ol(=wnbWBA!FO4}i$};iE^ILl5 z33;Z-%I$8C>jv&bQ`WEf=5*oVa^=O%4pj;XLv0zY@(0p3=w_H=B&IF9Buoyp9xu)7KLJ4-2*oBe^V-*Z3B zB(w6vU;ZA`3SDr#{`zY^xgChhd*&Pooh1arTq08~#FW?t!>~UxmqIojm+8W>>xn7y z`1TQPJUbl-cZPn-{NR&2a*XJxw;T6kqK1ysHSzhYJJM3PJIxG7$MJ69>p#3_stK1M zt40w;FNWzVyl;uS+eXd8_g_Babe#C<&+mErnE2weBkxWZPS?6MVrfk$LNx`~cYJX> z@b;UpaGghDW>%U$c#290;F`lVyV0@Ric^VRIf_(5ajX)lS+G?xUm|OmY1U$kCQC$D zHb#%{F|`tGAg4mjhM*d*2~LePRmjR<0bSsU6|WSkD5QFt&K%vqCC)t56>T-CMvPKa z3*eY-25D$xm|NM}qL#*bdBXLcy>YBIf7?s+pNh<1{5SsQzyIqW$NZmF+yWO6fn_C{=y+@{mt_>>+t35_*&eIu%rRqw5JmOu4 zlEgGEIOFj~Smz5%O=Kse>q_W)asox5-}$Y9$aH-6`KPq9(rHUg8j2#<6+iCS?{BC@ z&{|>b=Fykt>4uS%C!Wra7`95oW(~d%xM29skA6fF=$xkMh7rrNo0sUS0ZsJ=--B}& z?*h^k!hNczD8@geKH;^SU3?n!DJDlmzp{KOUG{^1x8T?k8npdoB3uIX`hy9Kc z7p$_}zk5gMMq*Al41*5{k(5`~7!j*6-Z0u8=K@Z548xwJ>xWuK=-dt6U}zMK3!o#} zWDvnPvsp-V#VAb^i?#t}9lh=F+R{Qp)XIE4v8IWdGyDA>7YyEd6op!aKJ?rSdxqhL zW;{>#Z&{a#Z{EEo=9R1#n%WL*``*zF0l(W}`>h$tn;p6tbgj(!0y<;0-8|7ovBm|R z6aAnt=3MKy&t16eA% zX=DYDqAJDfhxZKqfN4AC`O5wEOvxHG4&)|SZ7^Ch#TD-Z&UUm0PsZYiQrqHxaQ zjOUUfen0Yfxe#+^iJ7NUVe1&Ln`K84sRCR$kp5=?^2u zZbnv06Aj+X3+S=E`!-Jl`J}KG+wZ z9u_WhYhC9Vl=DhHz(lf_ug;Vv-e!nnT$F=(eL zWUfo$o403v`7eIS&)>YGQ;i>e_Y=7GHp;hbS&(&1fw*Qo7>%z&uwj8DbH9 z7JRXkDrhArB_wAkPLoclL@Pom zE1AU5JC=AMX8~o{?{}O|1z(45SKr@S8t=+>uJcRKeT8Zg+#yTR?VMXX5!nvks*i zqG7F(RyE`E1HpMZ*P%>9fSNO^DwHzlrqRk0C4p8Pb|aLER3X-tR2E7WXq9=r64SbQXXlo^}g*tRJZ)qAzy3m=9SD$==a{<--b`vY$gU48n4 zsw7fLG?6X5S)tZKoL2-z&59Pv(-Lv5C*a9-Yd@NcpiD!d znWoGi{=wJuc(e;R7wF9g`<8{<{ee$kJj2C`iJ8&wuvt-?VmOS{xzHc>JjTq<_0*hk zz2!7b44ox6;nioi+oeMm-aniOy`f5B&6)FZ-Gch6GGEVh&fb4%((!mWZY!wM3Q~ zN~A0wDrI#cN@blYk-SY63n*8qrJ^t!G21v=Zy1zE7mL>otf#k*@BH{zzwKrE7ov^h zaQJ_79R5YpzwA%;)n~b7?GI6dI2-VVM#&YwXZ74tE6S@t^Mj%`&g-`x{bf zu=Cp{`?`?VLe7mLSib+AA9Bf=P77;M^urOaE!SL`(u@xlC6T2T#+xJi@dej;-ssXE z>a?=V2|HTi6!BfaFoO1UuF&tHdWXT2Q=+v<*mYRdu*RURp|=)g48<6#t)!M%^Tf^v zMl%rWOo@rAGASnp-7)shS?r2Yf=me|mY7ytry2KqL<`b@T(++Lrm;p7!a&P{7R|X% zsMgRvAhm6+L}jyjkQ3VN&}C&bo@_MN(_4m}MfZ-oXLme3T~Xe!+jZzgF}t{p_D$lj z+Y|Fl5y9C?)rG1+^aguUEn`j3yqsC*_l#Xn%?sLFu9pk_Fp{PTCt!_3cbY7QRxL^@ zZLJtRQrd-5GGW+55#D_B4KH83!dju`nbx-OWQv7W8@_E-x-oAd*Lj)fvqL+J$_gEN znu;trVthyFI?6gD)gorws89qK2E=aN_Zox8)1;8jGZPn_GqS(_$OPz?%joo4PI zpEw@&RFipmGm=FpWoB6xv{-UX9A7-+^?im^N$JF)DPBLF_;kPLi&xKhb9qOZ3r;(f z?Km7poEA!I$e>6ua=g1GipG0kt%Y0@nM{g8rxY5fk`XFya4adJs%AOAM|ojz9T-bv z>)wwgLDA$Xk){b6#FB|Mvz8TEDqnu_C8zT}pMLqAvA^N({@s5-DT;l8ykyqwi0O(~ ziem`OYvUN6aY>P(>j*|ThD|m6$vV>njH*~HILEa_s5372sAdqS5FC4V%jxupww5sjOx>&{jSa%)4?EV&nXm7^ z;<&rPw#xhK!q9t6S$KE7@bjNSoijJR!9T9lw*4#zzvtQQk!fAH+xPtZ{ym@UJ+f9l zz1#84>vyySzw6VWwEX1lmEqXa`2xA}>2b%+s~z)o=H0t1 z@7})WdtcsgeO#&4aO;Fx60t^p?`LoM{+F+~J6MLh?=r9NxW0L$#Y%0$5I_jbRao;( zN5ciP)kkA-S$Jx~WD0|4oA8}CC}E_bYQ`HwMWM9@TM+}aOh_x8>v@a|tt3j_7!=p3 z;(Cp<7Te%bD@}Fiyx?=AMo&qF)D|#;Qkts$#=j_;EQM+et~u5giE3l9bOr_ioh!Cz zj92K^kR;#s68)#5Kl6jbKWXFti#!ngJAdPE{S&|T@zC*6AN7lq(;dMm)*4Y3T2bg4 zx#@dwD`mRUqGoqIa;j%;!;ZEDR9~p;N@oXZTB*5EbD~M&+7jAmsxBNq{hZyf=X8F= zYR7V!P^;(mlacpnrWg&avGYe9M^t;yyY)4@;|^skS9NB0+=EVJEf_W6yFH_y@%B0Q z@7`}Ue8Z02&5@cbWtLDz&Fk9W0&dP;K+OAD#!C z4h+K$-B|f7IC5DLtw_tl&UV;-#G7$r9f*chF~Jk`wsCb<(@>n(3#}GREr>BJb)nK| z%2H^E2!df+W{k6>JQK9qCITX?(}Xk_+z4w!LM9j4HW{Ls=8U(Qw4M>QquTHR{o~oA z!c>FTps4HzFu_okm029FIn+`~OTw#0um-(UUiLR&5>Zzin$PcUxt>JY81xK)kyTNmIE1?_kgX7tYJ5KK&F|JWcB`*BI+ed!$ zH-3}({=&Y&JEM6$KXDj#Oc|cuzU6QjX=~zosdSx1rFRUS!&pQA3{t$Jm4ar7X#VgI zzv5=sGmM6QXHj+Hlb0{}y`Q|<>Q@_}ul&`&`Xhetr$48tKzXPbljwTS@7_Q1y&oL7 zDZ=&lPwWTHGB!+9bZVOn83kvR2oAh7dbJ=TbVft^f3f#xvDU47dLQ(A+tJQ;t@U-g zJIy{-r`l7t%fSv37Z?R&WMP`nDTIWOTtEa8L;-QdEmjf>6h%UjVhLn$xI;p56Oh2d zv570Ll&g|cb=tkp-uvrTH=EJDJr{E;vdslfQm$P-{+`RJwdR^bT64VP-#ouZQEP!u z5;tcmB@sDbW5$>s5O^{|20CL%MCKx(@(AfMa?LV(YL%&rVicZ3KGLKX5fxr2OzH4x zLI{m18f`OUaO5cPIWqZ7lHyY6^oeR^k&D7FGeQbdkca?T|1{?~l&?O=;dk?a{%?P; zcN2T#^FR| zEjbFdPv7JG_9eS^!#IqWHHQNG>nl=7s4SR=2`LP%Rm>Trxujd`l8Fg0nZ=F7lG$!o zjPry|1tSf%5l}Mgw8p5*np4h+Ys$y{g zqC?~iN>Q5{)DE@MEa!>7s~F~im?BHMpe|DZFi0XufiM+y*Au2hDv$Wc%?m&E2U4oq&LRy9RrwwsPmKYz`7 zw?dGpwd6EJ#`E!V-L4{-%;N58yB=HBtm^)^dsj`-#Bf1%2PRq*7=iXXlFf_=Xxnu%Zegv*^u8OB6>qF8)D z0NPw=Hl<`ru1O&yqrhrGsbDIR5+WuFmXxWrpiz=bfn6}7#uYFnV=FL9@zMJqGMvub z9Yz|Xh)FSf&(-dRxHy&s5l0$*004jhNklA3+9zl8Hz`WXLHHqGt((B?|(Qsx?$eKqYv#*F0}5 zv8i~Bn%mYw~4_~l>v zrN86n&*w9L=70CE@{j!&{uqDcKl6|LJ^hXQ`+NTNf8)>c$tR!kwXc1RfA*jGr@7ki zzv@eWr+4}XRuW;is8))3Ur>7|ush3^ckS zhKQDmm>lCW;bLOFYMH!8>Iz$}NN&Iy%^@67y28qioE?x5S(1>*ERa&+MMmfr;W}(x zaOa6$!rPah;>L--SD4irt9#C2ycpz0$D9JLwP;l^IzXz~ko=7!3)kDzgNK5H&Frl)3Eo z>!J{NEE*#_gtEA0B!qwu1ITC0Q{y63x}e9E>}T&-HPQGEL810TPC zg{lf6WKPS16_=HY0Z&_BI_=sDx=jz$z{7oDczH&Cr(p~eo$N@`bAFtehcnCKNGTFR zU>F`i78(t12|U`F|J*;xl8KI6Vfw z_Td$+GJLze<~W||bWMq8axSEz8O{rpT47^_l@se;UDif&AqhbvAgF>A5>Wzw;eHsw)a1sZyNc`Gnj&lNPe<+^ z9tkLl_k8;g{TzMOVk<{wJC3g(czAUnrA%FQ9M21)w#3QNcO6gPdjUG(Padq{91Gc3 zF(^k#Z)N$i5`jyDi(R=lsUs{4PcqzW1A-(`wjV zUExxpZEDbtkKWs}Oa;G8eEr3Hyt#d#sSF?g{0&C82v_mFAAW&18G=IfiQp6|D{3@G z2(kv1B&b3wG!vem{N#bn>Iq64a*mJ-A}eMuC{_|n0#hT3VICjYcLhH?v^RuOK%ekh zLRwMcK*@oS1u?hCdWA~?jG&~3PDX@3<5ar%iOR@&ZMoWZG*w3^g&QTgUR5L)7=2+F z+*f@>|9#OV$3gu5%W;S?@@M{~Kh1Cc=5O)m{`|lBcN7i}4-for|J8qu|N4LRPw~sY z^oxJb@A-fHKmB<={^mFNpZ@dzJpZ?U>)+zf{h$9l|C9gm|LCi})OUKPe_+*`6-h{H z1BcUzpfV;`AS-S@{07J4EkXK(oo2VPX;yhiw zrLJ3ISXh<`H%F8;A5)ToNfF1@sv#^B;iPL@Vwd>n~o!BmF8eCgr^ zM`tb%LEevecM19u6wZeOMHq?@#4zILiCXoj+A=I7ZaTAGZ_&jflVa8O=xswT1%JLE z9!p#}&5p`cXuS|jLKIL*uv)G0AtETmk~k$UlZ2cwrbZi!BICkDCXq07s~sWDsH{<3 zOu4FPF3_I>h z1u3CYm!wJ=f59H%!Z(V&t>DZ!i*A`4QC%%PyXX47?)6fiZI zpxJaQN?CYwI#Q1Y9W`1OeDIXyAO^;qFUH3lScU`7o`1wRK2l3XmWI=5p%g(a8+4MS z;P7t1yH~8*4yiQ}$@jkZDQ#_WAyC%=il(kwj7c00k6d4^ z>3YpHooTAfX>b&25p_i^Tc+WFvYyq>LKQhb&iu_^|1Dm8@SM6IP&6PK>ME1LDCb{r-Jma0C>MZM3PY5s_0w!r> z64R_&VO-CL~DRIMPBs7wuLK3caF z(eUek<3FUe61U9Ms={_P^Bj43JkT1$G0ap=;>G%kz9?!B$Ga29!-z9A#e0U~k^R@7 zar=10$eQRQ7Rfk|oJ!($II~^tIGdSR67z6oZR#({JPM%5jZ$5uTzi*)ke(OiC>DM(~ zQ!y?hcb~msb5(P*`X+I6qOCNlUh{s{^IJdq6qgg@IJ4XC8P5x=t|JtvWyk4ZV6&;& zub=XGcS}`Ela;zUJr`&Wj_jEG87ln)|~EROV{k zAoRfINl&8_+tyHoqBD|d9>C6AJ?rRPVL05;D@{;}b4uh`&^b{MPy{H2vY@TPrNqPA ziKjO$pa?7&VJ@MUG9pYzgC-V7a0|rtlJBrAxSWv~M9k!3AX?^FP(+HHh+d&$At{N- z357#hNhKjf0YTCy%ki<0Yfr5lO=TIE$UJ$L1ih)g>LdDhS6|i&zi%cEf4BbRfB8>f zjNuRc!9T#i_P_r-SB?HR|MI`WpZb%3@;_G50PwxVFx_(8aEp4+#6~l5Kn5Gk&!r~UBtO&7SxL^dc z63DiqueTJTXsqPk9Y{$sjSK5`O^y<4Evl>WAyLNp68uvNMk|EOk!~X`32RG9ntJZT_QVY9+zBiC%Be+A=Rkk`gE> z5gDw~AFHHf{5f8SbRp;D|Cz?B+k!-9VnK(tk?2m*z6HP#h3mSED z(}g(XJu(e6O^=`ulA>8PWH(V?ZIHTfJ}uPE1;06t6K!RAyuIh?^C#@z+w=BxPs%fg z+lgJVxZ^;|Bl9v~>pjtfY66Q3SYZ)KpjC}ZuuL;aXkIeyf}gy6gQ;62iIf9R)_d$$fmJ-dz31@uo?(V074L0UH2s>l$9rl~^J25%d^llL z$7%E&Suk<;~-XZ@#~Q=$MC=`_q|mDKt&OnMkc_v=BU$fz?&dFejo{crr0- zmXOg}vE)D!5!qPck_jQPU71V%O4cOd$TFdm0jnt~5F(5s6HP;ik~j}+>YB&%1ELBn zCh_^_N1oJ{lsx^m!@HT~G?1;t%8K27Mb!zW z)j!Jp-D|YXobKOn7>?vs!lj97BRDJrQ=E8zx8eH94cAYe@srP9(wIzf39%#|^u&9+ zHQ!#h2&;MZz1OtYJ?MhU6{T1vm(i)9JBzBzJ2> zZWS9Xcy%{%IKjGxC3-|oSXEJJfmiT&TIlT_(hN;%2WA0v_*19!Na;Bint? zN+|q%fj*=%e$_|x-xvM9EgXz707xk*f0qa2zw_Vx$N6)A{{Odc_|pmB)gf@;%NI8j6^0$B{M}vppc~C@$^VZn$=2jy9_8Ti87K? zqEQt|2`XzZ25I3~b!)OHcomt$LL)0y%8+EjTdzvbca4a(FsRfCHM zoF908bHg}{Y`d0a$rN3f;_=dc9fho8M=v(~IPJy{95jb%73eD}NG=gIXo#W*(4u2@Row4C_- z?Q6D1v1t^tB$m^O?bhIGN0~fVyAQa1yr+Lw^YT3K$=fqBCboOa>Sn`mKeA~zJl>tT zt~Pvh^9)%oyWd`GV#o*ur9`xbaU5uEPhLDqUDCdX^T7NtBbI^h{OZr~Yk%c;xW7Aa zo&w${nhva0H1(cJ!IKZKh^eqPPs!tVvC473IDGM(9&V|XvjKeDMSrjUs;Q=5vgIFw3Q6d!!!YrI@9#PiJK z?PuKIKGN4)#%ZBeiq)#-^S2K;;dmSb>!!o%a=9ODHW(53f?pO& zk;o)y+nS}Eh`z9Iw*)sbhDf6}SfjD)n#aRJks2uqvIx4iMk__}0>VsM3eHHnuE9vq zszFLYObf;+1oDy}Q8$;sFcL}#GzM~^u?>iVC=mjnD>M_{J4#lF2u5p!v1Fe>38D-% zRmEv|;D37gFew%Z%Rd@-<-!X?1YC~^@L4+V)dhL8fmpPvTYuUicVw6BGnT@S@ zJC2wC)x*GXctaRHLPQSpfYco&M&>jy&j;4^n(fWz;xDn9r&nvf`SHiRdHo9Xi1(L* zw$t#z(>+yHVXkY=VdTx5GiesIZAFO_In5LjBT9Y#jrx|}*c)Gpjd#`_l zN;R5=4?lVjZ6b|Sq@ge^m+wWT3d1~MR83!5V8J#O!{jlw;{Gr&jDc65yrjF@lQiT~ zSdM43^9->dh_Ff=lIN3`cTf~4MG-CAt2OcVp0s2-TcM3W02M*%zV9v`omc{pSl11r z(L`Y=OT54jvf|a7JNE02VO)rFpsfV9()f~i^0davnyRYUHj?9;iKsNkDKMwYcg+-m zPmv-DDjP(}bSsOHij*@-22N!mNQsmev$7V2>;qW{qW1XY5W1idJl(E&ID3|HWYxpx zcY$SIXquX<-7{VvM}!NUPbbD@WTO>+3XIbNm7%LU`nJMKxNbL?YK^EWbnxT?e({u) zkXf*4cAW1WG?K2}kkgsh$6KoPn&(fpv~`aPf=@pAF?D75`2A070m{zGdPYZK|x~u5CWQm}w z`I2H@DIO7D+QtJNz~$}FQNjtjYKjP>nQgK+(P}dtKKcS?=R*E?-q}!3kRE&PXrQt&Fi4DG*n3jQ6zou$B6p%fsV=s%t=Kd|GhJL}MyURdF{Q8AeB6@4R4G|(y&^g|=9!WQYORqH-aHOy zRa2>i-ByG#vFX=bcE&V!X9dnsDUGrfr`fZ-Ju{s=Km5(V%A7sF@`rzk$NNX#eD)b8 z9g*Ua03Az0NyX26`&-<%PV4f#7`vws!9&aaB8^tu7sq1TQ-#qg9JkeDZNXd)7We%Q5;)Cb!qg>+q zZ+=Xv6=jS(+jY#-$a^n7;?tLJ$WdY1p1adq%(~*{>I0U;BfISj?q1*VgV#U4WIHr9 zrd2%L9qFv#>H9s~u3NJPPJ7P&pizlZ@$d*(*oiQ|< z7MbDX65e@Su}op0H!!*d3KU_88ia*$IniK=Iq?18`7Xck?O)|)-|+T}Pr2UQkcwem zJcr?t)vD*muRde5+tIfjy;0OkG5U!)DSBJz`poN3Kc_V&s} z9Ef2cmEDE5RBIZuzNCJaOxBqy8ANHA^TH%x@(J^0)=JVVY7rTjiQ2OSMGy)nBD%`- zN+PA?G%ifRVP%b*97s!7!?RX1AiwG(`tOUxKmKR`Kjpvw?UwcM#aI1V-szqGq0#a1 z$p7^({Xf3ykMW;c+xRIZ31;WfT9I@n6^BuRtNr`*rXh@uP~zo*RKerpBdgsW=T9`N z4P^<8rvs@tmcg;oHB=H8B3;uGWMVi5x@JYYQUpH{7m%u@TX!TDqDz-hC=D?dmc`?y ziB#eRk$Ll!%4oC^Kqin`&I`^By!y!(eEs=zR@)Um1d?~8BpAJ8Iz3>m=6Zk4boONB zDD!3S->x)|^GIJkAqzoW3yMfI{hCq~jjSle5sSp=mZ~wxHl)3mND29*$2_nr@Rm~JfDil%}LUL3H zq7WBTbqG}YlEv}C*FGdHfzxTCuY2Cy-ShbR4JjQ78I*2l`i7i6wbYjt0-t$(e?aC; zWfbfEbDn+l4V20Jt$*+T&Au^g*E{YG54a@RJ%67{N|cIhcTbqzz!zV*tC|eS|f$!&8s&&9#6RJkycU_iBg&E zw#U?xy4G~M=6DYL`@j8)U;NHD_yfPP=0E?(evyCY-+9Ad{(t>|>3mP6GbT*rSTL)e zAYd6EdHtoJW?yno4*0%6WP-EUd2+}*w6 z%1CTuFgB92U|IyDTlm&DJ|elyX*^QZlHSyO@$i;m$}Gk5;~zirbhlz_48}@AQ79ph zT=1W^(U1iz(Xgb*96ZiNj8e!-Q<0$*Oxxj>g36L(nP}E4zV^|MU;huj%g=oDoY$Yc zgq(?@&{Qq=k7vxfB9sJ4QnEx8&=iW*q(vaqB|PoP)s~0j#A?&9-_^ur00brG%e^rQ zRIM4uk;B88{na&9qtGgIm=v|NNaa|!hN`Vug5!KY=66itHuhI8$$H#xP(+i+elaq^Doq z;B!KY%Oqo^ETcP7Z(I6i!)HJI2`L9u+cAYgYb)N~enH*VkR*+5aiU;qi!q8ac~qsD zy&Dermt8b$ifii%tSy3i7kfnywLRZg-9j@vIvB^+^@5QWJOQ9$NoM355GO3e&J93v;XT~xi(b({8fLhcY3FPXylfK-}&K>zv_?i zpITSu(&aMNaJ_p%g@FP__xQ7Cn9fXoA)W_rU%f(Ei%^mMRmbDw0gK}P?k8v^=&KH4 z3`sXM1WLiYlAikG=!Xy8Fap2A}LTSgHRnp7P2VJ(UangF8T7Blo;LM z(|}J4LP$cs;A?}=Xj!8W$atY2m0DibptNM&uc`Vq!Dte&s#hp0i8-OomP{aIkR%8R zsagt>5*^+h5sL#UP$?7VnPoa5NRSK6FiaG8<@_@B_p6=hrD$Azd(ym*^^CROlvT7QVTWH&s)A_{h7k89U zld~aLeZWOCcOJx;G!TZl&FpVR5S!k3%=7kVE zD99-igW|XZGN39$8WWj{)%uFAZ!i8ERY7Gy!~N;ZkAL(NK78?v936+lWhLY3en;Cj z1QUsIB=`~MJVrsuiAG9@`tp#r?oe4^+J-CyucsqzyXVQa;upX39^d=)bH4xmBbGvI zHKvX{dvDF-!-Lna9FHgV?TT?cGI@tpmaXj&QB#p< zjb#`EFK_~et9csF8%q=bab36&#fHxSdp^?pyYs(3s&&clJG zzvPV6rsW(?Xk}Qf42`N-=EPw>v)iw!ge2i{Dzd-1L7;j1wLSaYbKIlkINb7fxTiIO zl{QR+W1Jl0Ff$4ed8W1*r7Wsy>FO2EB}#Ia4!f;ryFF!DXx25S@PH7o=`11_WQ`aC>A(zPY^@`ooj;s=~ z$}Hyr>jag_BoXOWHKZA3Ep4(y6%dWY3Xf4UwKY^lB2q!;Osy*r0wV>;KoJ#fsK~*Q zzg%CcXHBaGl`w2)R>saQ4nRJ$V?CcBPxL(xzbpFAFaA8v zuKA6-a48(VbFHc06%Oz8PQPdRX(=2kcS~9>{PL6%MNn zx@)Rt4MMXdj~9WWEzX~zB#c%VC7Jwyn@*HmP*&2|Eml~&sLYBOBN;Af%Mp^B8N)~^f=XG6xR|Gls;F#zS>-SSsVhLEl|@mo zQe#a`){>MXDqjM^QU(DmF(AZ+%#^iaoQ|Y$WOVn;VSp5fenzH+7)MsU;559ZSc6|C z{BmFkGbtr>RkLa~q; z$;2=p@UtUlOV{t%byu8Df**YP8dWmRIi7#`j3Oh`GI9HG;MHeeppE3mpMK7!w$xT2 z)8#=wiJ9dT7#t@1X*yeTe7wgP!HXA9s2auX;Yg9N zyV~>o`BUZ?xt~sWAvq2wWN(osvhD;!a9rb!gQAk2^mwQK75e2wm;y4Z5 zp8|wP%97+G^|nJLxb8M63&(M$RhGIcEaS+=R@hqLQovT1?~)XfeP2^F^iQ7h*~dR)`tg1aICPFh-+f;qGuGkO`tt*%imv2Wo97bwMOW@CjWClXpl} zFtuPzmqcYL4BpC1q3a88PXj&$a?*%U7{{4vyse6p6FeM?C#7ITmHN5zlpALKU-(EQUzRhuXr+0d% z-~XDHiM(W#%J||DLEw_7#K_IfheQiU=UIZIX)Sft5mn|q3=HQ1V{6{ue~dAP!5v6` zVhIDIn@A;NcN=tp{nc|u2lFta8pF2U;z~qBMScANd8e2jk8HXgKi~1ei}yI69$0M} zOb6$1=5YJSZg+#~Yli7UUqU-l3X~Lqh^RUg&{Y;U2fRxlBq}(HfThfowxTv`x~?OM zgee-MEJ;LyFZ6W_HsO|qHf|UvPZL3!FWKYcnG!QlaA78*s7yr4%Y;E9DcPWvr7R1> z`3@^J0f)4PkQGuWgfKMnLar)OqGU}90a7H(3-3M@d+B1w30*ufOMFnk#n?TEg-p66 zRHulLip52w$oSJpB|1bEkqj7rxxQ2s!APE6Uo#x>Wude)8rEHme)*^uYG2;XK`9Q7mW2{p6`_;YnNb!_RMdI1hB2 z4r3*&P+0sCsnq>|L~t|Mn#wqe25B^>@c|#o#kb zwp)#DD~x=~>^$?dqN@teKKdGI&dle+I7hOw2qNe6nfbF3VFFFn@o-)s7gpUC7Z#8b zC16?RFEP9NLdWSE5-^8^Oi;;7_C}E&FAB-ckOWd@ECL~GQt-^jnYQlH7Fw$>X~A8h z+G~mn=meYnQ^fr}5{<1|YSmCwVO4iD{Zr28fpZ$kF|nK@tA0hC0=0xjwajF4(Y!r7 zq!65^BjFfm`ZaaeQnDZ{0Z}x<7epgC4-?rJ=6PhTHkct`NlZ!7uCAzT;e)ST@#8N( zr`fEC^TJAL#xXHS*wzgqR74+nvVBISYtHv?apOXzG@l+`qHW9K9JZ+#oyUoV-BrVG zv*q^HiF5QcYR%zri!v4~3{P&hsO*VzU}YK}&J*3LCAq}p7yQZ57fsh&Y@P^0P-J1% zZy3fiZc6A>;jbR~mv0^ee@xR6GX9p-8ImQ-)*xlB0B4Rl0e% zs`O6p^n0nFmWjjDZe+W^blw|T5NSamn8S<^6@9m22`7Y%XcV>TXzMjWWDJ^{Cr=P2 zGmS?q25U6AKqYH7+ovE3eopw=Aq^asBb7F&R2WWYWJtv1NhNc7JQ7?+h=N}(uc;{5 z)LY(v@pbBT!^=0H@p$`yn*&-(bn>|4%z2pEt*?+2mSKS;aUoEQ1yvx13oeyRV_Mp_ zB8x=I7fO<<8>Gr8618rSbwXN=t!tEuAT)KqV~G(d1j#SBypVH73WGM9#m&^xqNKU3 zQ&I5VVWdK6K@{?0cr6K4KxmCf<$^(!1|bT;&7A#+H8tn)h^;Ixdms{$Ku{w~ft;A) zKrS8;J%NaKGsodb3JWF#rg6rXi`lTy5JbjBhcb$fUVOy;!vja>Fsmo5`loc=hH*Nf zi{RDE&q-k+O%7u&RG!-Q6d~E|_ne0VK4~T=h#9bkxm=3Q`|~~8NSyNoQSe!kr@-Ot znCFE+rZXEhyBZ-DW;ZcAPfUU5&#oxKad`d6*PcD$cv`4b!+LEv4{==>A-Y8(@2F(n&V;kl0;>3p|F0k z=X*c;lwI3!y}iPiOKyQtio1sge1>UG%xz z!BM2g8VfY&oaq|FZo47R6FI~4_pVs2EDxsx(T|sDpV1_B=K89pscPnNq_r!Ofb+Bv zT}0YMRU2NtIdD3SZ2Jl+4Ju~*GH_ZFwX(GKlGNU{79@<(6H;QgUGeG5BV#Ir=xMr| zs;=laEjQ1va7*IhZa|oty3!a^v%lK2={sy)kp%?j+4b)ck|LFb+E`E)p)^qz9*zU| zb60filPg<%~NEm2{BWOB&I^64eP$8ZVPQwQL>}28){t>gTsh| zp%9YCG?JJcVM#Pt&P$+K)kq?Jy+&9;Dv{Iq5mgi+7j!ACsxL9s8ITE4DGCK2MwWac z=*#WCOGmTxLtwD;5fpc^d03~EfC>U)}Mq?UFBA|*w7j;Ql zPKjIuE+s-JloZJeh@wa$qtwMX9m~YxCsNEL?~qAh&1G7lq`?P|F&E@u5dxtU$bu+e z2Fyu?k~J|ztf?=)3kk>j*T|BwRgG#YWD&%osg2}%`;^hmJU*Uin-;k&Fip5=B*ntp z;{$CCN_bMpw06gN$xKVZrOY%s6b<*oNZ(1`d;Ww~*K;`CV`@W*f_2+7g~SpQnaue# z^7OrLvAx+N%msBBQ{e9Qk>mZ0m4*-_tz99jnr?T+nvsc%UkfrDL_-p$u#~Nl}4P z9EeQQZF%|0EB1X)T`Ah8LK%Y(j>pFnE@j3kU}Q$5LJ5&t2&5>uBrrx(ibg5JlHlPu z@npAS(?`Dj3*Y3||JLuIs6c>GhU7BqW{W}~lw=H!!|}|fT~o4Qm=~`5hG#b$&P(EO zI&!^TQBuOH#Hv<&^o6%XUUOOt*De?UNWgFxLiOLjBRMz4k3Y5 z5G>4o;xt5bEwMTwYKv8ZC)Yi-lGH}B+ix*SP=cr4RhUZRroigi4NqTeDS6^}9*_#$ zG9yDIFUw_ZsxA}?N*0F417Y^O_x=^rv@m(evzzA($Ax*AFjgRoM~Mbm^{l#z5N2Ww zY_C>i5zuJHdBHXo5j$RScfrdACULDN*MRvo9)fu^?L6)#U`PCilvNNz$k4T}?K z4N7SIl9+~(X^yOvrqlv!4Z?c_0aMqgBJlITs;=pb#*-)ov=&%t@V=0uB#)6&3J%Cg z^57B*L0mG8ZfHA&r6vc7&w)?^Dhp!PWSe=uX%I1SnjAVwR7%J!x%t*FeAUPF?}{#k z!;0UykM9bHcY3F`GcyqxBzajFPctQHBnd4ARb5|_pM%5biph=W9LPD+ zSc9%Bmguong)o}M&v@q%8iwVB6q<8n>J&VP-r%;M2l19C-5V38&#m z)wN`;*{nA_y}7}u3k}2%BM7LPicI1-9w}1OSWOBONjNZtBm}0`RJFt)Y1%ayONky+ zDaIVwT&<~FgV5Cl=cEKV)$~oz5*)@hBvFuBahgwrn20f4Rw!miXV;g9x*X9y;X+_Z z3uBzgv5-~4v=vHev{FPI5{D67(y_C_6p%Dzq0qKMS;Z0@qaRQzVzi;IYRu3-7p^_ry&wk!l-gF zYJX`^PGLZ>&{%`e37;bCx~HoeVu+}e>AIe6+wuBvCJW0qpY3QmNpK4f4`-^?hR4Ap z`v6 z`tpv?Z;zCE!!pn4)-cDyroY09j@!EjR<$Mh%;*Bsa#{D-?Qhs$-7vU?VVJ+{V0s?T zo)iVU{fgby6@TTg{w6xt-0W8?yjEtokk5RMf^YIZrMbDLm)# z$m@rZZrzh&q%$4st|qsMB}SIJGtwHeNWA>wEmM+gZ?>4C@MEUm_SmXNq=cIn{1Rxl z8Y)2y0xu$;ygYFnGD;d+StDbFA`mvy7lDeISPDwllq8U)(4bIK6NN^Ige(Q`FRyo1 zg_M`LwPl)#J|T-_^Z{9eREZpClr7vH2Er0?&T$;ih!|P-EvsJ9lIWCVUMAYrGj`V> zpiITCt-(2F=V{k_T*_?P9%5i}Gs!6Wb<1fQNd=bRsYF4EiOux}yri`~hvUFUAH2`Q z-HCA;dGF?mSFazKykic5)xJemk>fZMioj^WvJ_&0fFmz~?P`apDi$9(2an5zoGOfz zeEr1}tWe}wNMzbp^L(?Rm61w%yweOz=Jx!6At5p-Q3x)u%pMaW)9k2rJ$>C_v_%Pn z@sb=fAwiR%_yzA1o9&+K{hAlguPA_$HF}PO;uw~Rd7il&9+;Vkv*+z`p@@u(j`hl- zBqXb8nwI1JiCPJSkep9}tTb0Ip7Qo^pfQqF(=g|RCu3w`qcnA^`Rwt?zVFe-5JIL= z7R*9P9x#`6T9xs^lVW6w1$;z%MJk>wBXyUNd8R~<&H|01sUzJs@O*8M!E=gGW=+nL zvkoBO`NzNXRUgy8EBgN}9Ntlt-szowPxaGMICSZOC}xJ?h?~#IBB*7J5|?hJl7^=5 z8I}=IKp}8`pt6RHAPI?;irs3DZ5m3>gplak6{a%e0@hTJ^JN_cSmqg{FFUM0dx}z+ zTq6sJ0zzt5+bimJhpjsf!yUup2^}TM7%FR6+cg51mVs`yLTW=(w`{MTkW@sbNRgQ# z&*Ths)sS-nQ&6hm{U^`Sw#J|+L}V_MTtG^qUywpkvLpmS2mwh%6-@!s;uzzv_`2*+N%IO04Z@>k3N56hYH!&cm69 zSNCkUJ?G)b@$eS!Cgx#ArbHYZ%QSJgKj4R%mF`)s_so7FXP^{@;T{n^-FkzmJ6^v2 zg6JICc}!JNv_%TVzT2{Dwj>lGD{k)}zU(+lWFy%=*&#@m=}8j2xxJ@031tgzEQC4W zPa{?-vQ-F~c=~Kd(t+VLF;0oLw$#d^illA?Q#xVW3fWt#wkD-WaDm&`_xQ!L?JXil zd`={t>8zq@1Tjf$eF+XdO$$k6`o^%|ZJ6?z`|-r+XDE`5ZMnW#vD&P->ekG|z!E%P zoDK}j!mg>=t}EW#H0a=Hs+Qx?ar^e}LL9nUaXuc|T|LEln0>~(1*PC{yyfZBC#3nz z`Tl|EC91MKd-{ZV${g=cy#C_Iv)v6wD5S{rZ3_yPh!F~* zHPIC6wkJBpk{vNSy1qtOK^6idDzs2^ZHH2py6ISNw>*q9)w<&+FAqqQa7$n~EyN@_ zA4ld>pcG5D+v3Pv?;4t>!Y>n*a^zUJKS!Fn!U~WgQ5(&st_Tj!=SAu7$1)`ZOhHGYpl_nLdL~NbP;2qty;87O#X~tB9#_M z;W>^60t@SPPu3FW6EPL$X=XW%%=5zH6>gkJ;|U`pr|CprUFb`i%i55w+lx^;DM}Gc zDWHp{#xY69=EW6FyCN=ukR7Q6#`8o}&{Yy*MjA<@6_qv^Y4Gz*x3}aX5K@z6Lev^v z8>Zk{as~uZ3S7w)0@@T}PBg8dsx&GW8q?q@fJBMN^$xD<6@D%}x`<4Y6eAH&)hMJ$ zJp1OaeAUPF?~1d@X&Z?;UqA+ll~2bN)`c!dx(${42kj3hH0ACSf%jm3|Vgdn(?oID0W zESJ@yzS|>eMQt=CXVNrcyOuYnnKTAAP0JDsO}}E8Cx+!7V=Ya!0y2xwEW-(lzN{FT z6^jtK`9eBMa;9xtyt|+&t+gDE4?Njlk#oV%6MeOVR%3N0hJ$X)U@K28G$jU1Eg%bGmP}{QDNYz;3Bj?d6<9;t_6*)3Vn&M0?K06-H79rAY1Q)T zeBitYdM#LF!vhTr+^@AJhm@nj`<@4BL|EgzM@v;-=V@i{RpBb7Cb!@#qv6(TkaDRZ79@4x@S zC0%)$sdB(H3gISHRvaECQc-l9Ezh1`;|hFsdyiixj^`1LVb#{mX+oI>Wm;5#s>}3K zATNO|6Dej?(!?fG%S&E_wgsySZMVi$iYYB<3iFhC^Y+O3IMT0sUVr{T(=?n;Gt4?W+;VnGe75g1AgP9xs7@ zB?h#pS(L!dGgE>62OXwqDRH4|J!J@X*bkH)2ud(%cWD_)SC6W;qJ{Hhv9+t z<~^+2Q*~Qr9{JkG-zFYLzIgQoMt2;CBg~J2-tU;_Oy6~!9v`SR6eDj{*k#DrFn$z_(aV=fM51VZViq#t)3Ed%NK87vs%{>X0l5hh6USN zj3}tVF}a1+)e6(?INlxUH)~GAf?cnem&??1*VH@=CroQFeaDmS3zqu@Sv*#)S#7SE zrh(OUO+5+Z1bqNgTT0@RVUsjc2z0FoNwMAET(UhxL6kzJ6iZA@A(9gqb-8aUpQ*KE zN+3i}XdFd?js>m3b`|qEQpwCmPdAihRRA`cO98M=v23s}MReLcks*6do)(u9BO9xjPhT{QMGF{zK)jcsw zN+~oIRHi19(P)qbV=IP-dtz9~sbEoPEl}1VMPb|TX;q657t)aug2~NT47F~U<`aIJ z(8eMV5CzkDB72D|7qeoRBg^5DGMt!)f!0=_1VSj{?1(;a8XwRp(c7AsB#R5=oKXg@ zw$F&Z5K96xBQ?~mWV_w4#7w1DG;PP>;T5e`*c>T75=5X#MVcKkM_d??cvh{zn83K4 zE}hWnj7);Y)J)6B)9oHx6j~P6jX)4-OwHAH$9lD*-c(4FskCCdzG3zgE<~>PPq1~x z)&2!iHq_0Ys@}8eo}y*Le2)0>gb*I73RmkDKF(;VnZ4uj;T{nStFB^ubB$SV5w>IB zcBtAh`-R{mP?MD9YV(ZM`YB_Y$>ENbDKteA6MX;W9Z^f9YB;-0)mT>B4W}`1eYK(F z$Z>JB%?2$McZVZ&FByj;#VJHq2o0qqVi@T*Ep5|re>$VPnyYO`-?f}hnO4_4-EIga zqlCgri;f zyPwcngG-UVF;t=;@Vxl&3H#ksf^)3*EBZ}`tP0aKv8ik3(@0nKym@@&Je)BxqLSzS z{ti=BJm2rR9Y%Z>RMyb7E8MtHa==a@cWwA(_x@B|@Hy^Pov)*(Ua#|W_ zJHf;8z+KEhQDLn{ zW~izc3{gVsc<-5|w>7RvQW5yY1DQEGOf68lu-|XFdG?H|Yw*j0F_z<-2LyqxH+Ua7 z3@4Uxq^>*8=YU3$oa6pDAdA4e(*-!7G-lOvdpa}606EYp!FW1TH*1zKF$Ry%Q1ZgM z)2#ZMt9Ffa5|s;X9@w@W9z~}cuDX^eBBr*N>5fq30Qa|#y#LV$7$MozmYekxf>*R% zeaTK1nPE9$SDLn7@%Xsl-OTy^fF#iv$&>9Gp)$iVGI+-_N2D(_Ib((7c%HD?ay*=A z_Z>D@+`SIyPF&VlwWM2jXauzaZ6emREG|-1p|T3AGD&~}nqhl8s{TqfhvXs zsHCvU5=$bKh_RV%Utx=2x2p-$#Bni5RiRR53J#?Ou?Up4SfdcKV*jzmUM>sw(93L5O@lU)VF{N`xyURf@chM7q$~&t$|(HeC{d$>LTSn4c|b&iRTW=+eoJzhrfE6d zPn;eWPGck`kI0_r29Bo(qDw5ph%b?pXLi?H+~T;|Z&0u`bY2a*-!47 z&kc2T#k1|l{M^s|GUGH63J4L&c_Fx&I6IcZ9oPF6hx;QTOQt#Vv)}xH{Z+^5bfRl^ z$j0z+Tv+!v{NS@&A`5-jAWg;DO;p8lI3=#P9ruqj)8xr9Vy&R-I~M2ZnlRk^{gR3e2&K?fMP)BMd{r`YSO_s+%$`NkVG#mUWm$I}tIdXH)q!qs zLuQ;j<9KG@8h-ibuPIXUH~+ow6QW@8k#RWD^c4?F#wCdnEwA4ksC7k%nY+Wpc$~O> z7&%T0)8Rfp(<9a>6GXNR8xre706q?I+p;6O?GPGPLY$3)+5u-Y=s5A>`4 zf+v+NFK-{ffvqgp>kY#+Fp<8*2N$HOzr253?g>sfl2nv(`QFY1lFT3#)?7^gsK861H&?Kb|;?Ptk~{8#7&+^Af!Zap41jx8JPx0T~(JNOfGD08v5K~RYysZGr@1*tB{*0DsoRfSZ7Wptz@F|}a7?s&4@(l#|E z2L`|J_Uv@kV3UNpQo0wizuIDqRYL zk}j)pE;(dg5N?7{&_yDPVu=D-8<2-=Lv}Q#s z8nPJ?CJ}|==EI*-bM$wv40U){IK0z4{a)#(rErL+HnnNDYP zE)4F3Pl>v}5R%L?<3q$&6@^4n(9{hXfwh*RGFr+niA9%v*KrxKb;EAEMJmDQM}(@$ zT)N>w1SkccB1#ur4vgbKERJzG5keuQgsm#9wp6vn=Yj&_JYcN>Q*rPkAr4eZ^Yr-* z^KwK4f%hIEE(vd_Ny8t60;ASyx?2k{-gM5Vb!#Z?u-@!ouTP9zAO}> zDaBE@5p+gdfw2(6LQEIW%FUA(*sf(b-qCalQMDW%4{UcG?|<-3UcWqYd;5lcTO*_( zcf7sS^e7m>28yGvwNBOx@zew5ZIw|VGxas3_>&_Y(yyh#UHRjvK=y#6}BQIY$01< z31d-EMhFQbEQKw=fgyx}q7cszMNj5N&oL_YmKBVhq}er~3}+8c(9zKD4@Bc=b`4pc;)UY1SKgq^ zB}J8DOiP-CV+f=PoSmLAnaz>p^xc7zx~9q#q!dTp{m|i+WKt+*^Mv_nf)**a`;NoV zkro9eI8+R*rYl(`7{`4Ts*-tZ?`YpW){X-ztTJKYUDzr7lX6 zsz6Hct-Crm0D3C~q0Y4aA2!s@58cY6VUpoaeCLlh5XeOi~sJ$PA$bAwZc_jAj5LF$fZ!krXK^ zDM)03&S2jiDAE+ujd&GldPkNPBx#EKbRao;2*zLqs7w)pIp#EY0WQ#WJG`)1EY1wv z-EPn#r^+=#1eB1N!IGCbX<3pM8Nq7uL^Forn09RqiI&(1eeW22L}!xAmmh(*jLwiM z10iSU}`B&RBl#gNu1FYgYdx}+ZljO$t7 z-H<0aWnQq~w^$!B(Nd)uCzBagd7j^0(KkJlA_XDYHuu!?1>S6_grRG8Jbn5;gSRMk zOo&c$MOI1bQc+})AT6yo2%&lK_yM2X+_9LH)PjSG9?Pn!$*%e>@Blm#`W!nvQ`vXLfbgXNzQ6DW4r0d zk{s_XtFswRJF;(vPvu4_COXOjn$B@`yJvgwSR#wl6`QWb1Hq1fuoKs~Shg=pCc;4A!FrB+^q=8PQwT?Hx%H zD6$k4AQiB^+b~ZgX(2g({D{8w1RTqYGj7_J(L0vQDN0GUHwW5&WHDP|iPSU6>1={d zG!IYbm@rb76Hd?H;N|lj!h2%ks81{ACpEKa4MEUt0^USYa)gMuRMQU$hnp?dMb-yP zjDf{0Ww0GOuX%KFPEiW#g(96PMyrsqV!hrH<?rxG%Zwt=*$*9F1k7&7e3oM!h$LZucOdhMIx86l&(Ir0&{zZ6tVRh< zuuw*gN+qU;B8_A!qqh!`EASGh95@ZV!uOHUJ3{G6lH=JDg(J}tA1rASSxy3zOd({B zP86%Nf|Dv|?+o)r&BJMl4gsYUYqQ5HPc>PvyWFyR^IJ!%G^U>w4xiyOeADo)QaD_= z9V(Gz`5ditr1UsCR4EZcA!W)KN3sy{)-sGoZM@WxIF9%@(Ag29rARZBjO59LgRw{H zL2zVb1euX1iecAc&47rOzTcDOIZ;c3G}vGuC_=O-0(LN{OdrZ6jhE18w4J+Z%Mr7^Pm4BZCI0}nji(k(9mxO=F2JL&?Cr+QZVi|2*3>qPkZ8i_h*;WTpzjUi;EA8Qj9BMU!4mDjFt!Z+ z$Zi-(m_SSk+A=L7bzw1MOO{9wj_3qk=h&?q#+{`P4e6pL<|W3q?8lKJt=V-uf*8qk zjvyg80bY<#G_(1PbY77ZIm_8Yf`hgnSS%`L%PF_lJN5^O?=yD$1Dl%-!=?qNFhUYz zPL(e)ZF`h+C6ck9iI>x@GmVz`Y`~c(Faeg+T zDvl4a(FG3O9n%2ZKqJ5TlyP*Vy5#C+$Lj0?5iGMrkQa(bV10ds@`48s&iVWozYP}y zO@GgW^BD;NKdwonWZ(A4EMYK#=p}guk${j;WjUIh<$TF2uRbP7*dJO}^O_-AMjL3` z4bwzYAz02<)JehNFwhN-$!v~Rkx8DQLP9f)WJN+hY^ci=9UPm!C7)!h*FEcv=VCP@ z#EAC}5fvhlSj{oBAx5N(DD9{+&9p9vxh6r8B@&e=_SO&w!(rc2*K@|Mq3b&8#f;nA zdoG`E`07Wu41L4N=>(}QMXi}+8C};fJ1yy)<$h~GE0XjmiSal{VO7_>|MK!^S!)bs zrbx@2<}fgsCg?I{QZ0G8KOiR=t5r!=WXPy-qX+9L(A4t;r91+KP#GUS-=Toh$?8}f z*%3m(+dz&c#7_%cU7%Htvw=Pg$3k8NqBJ-KiB74~3MnI9*JFdhnmyLH1ZN3j%ee0t z27`bzvlqJw+N>o);`873=$kRkGpQ2Fc}mj_Osbl)(&X9^ zeI)rb7|w^D$w@*zUm_aM?&Up)?m(VTa3N#2-Gi3ASl@DSHf6aeXf{2j8)&zdZ#=sq zFEm}-kxWX;x?q-sUlFufL-h$L5$#GU9*AZPN2yJ2X#6dBPBSoh1K#&ES-XT=RVQ}2G zE$hQQBZ9$L4o$~AFIZ+1tZR;pf~?tMsEdT5IiQOinSgT^j3js9tz&B~K?m3(oC+v1Rrqu;EcP~JChz_p= zWC_-E_#sf$Ga}G-J^S4r=@ZJL=EchwB+76wErl+S0>-~>QP?*ewi|*o?AwO>^#ISB?$s0!jVl=nj}dOON;#b~t4k)OqH7!KvSdE5 zNRpcM!4O15$rKeWWtC!sA;~=_s{${N(xoJm_@SfD1S$&F+dWlLgGlKIOSPIY?Di~{ zOYW|=JblqJo#m8TllY_kY9cj3M7#naljCr1Jf;K(EFyta2(q*y3WZiBWtt;Jph!}j zISzI!;kme2k>{GewHyuuAxQExIT}4E#i6m>t@q64b8GXi={Fc@Qx6B%2KhM*9bh`cf9$=1s7-MxZZPnwP7%UG)t)$HBm&mW}rP7q!>9rS&&y6 z7ZR)qgkdB^%e1U8qamg#{b*P$YKk(aoTfZyB?)7cFvI0$IKe(Qf6sF+ibbn8&p;?t!K>2Iqf*&ya5lB#e_P^ zSsxm-gcv1CDayJeDK*_-sfsCOHN#ks&;?^-=!X#(BdcmkFpf+nBqbD8Mu-`)wHO~* zAC4@Ivkt8?*1I*`zGqrz)YA!72G$IeWsVy?Dpi=^5YeGE#PK);T_;EOD-w<$-!hOQ z>Aa7Nt)orS}x{%knRhF|w zVYytPr6fr-o81QGA&CN&ND`3{yrQh;1nH1|B)X#-F)8Amdqnu8;dHIs*WQvTEKtup}rm#4CwA4w7)&l28sxpJ%$b^E}QC2x+T@nct zT2L1WW9)DOL?|d{Gqfn^`@o1q#Yhqzv-#xcVei6mKb_Ft_6RGO&T97iJL)ngFH_Pw zCof7i_Zx<0$9}(OFeCSyJKAo~#cDyYmb7$(zgRm({g^Yq&FS2ItTx$)JjHNfK@#Q?YZ9#3?5{nSuJPu-Hv8wSWQl- zCMR6)_c$b3T2bYaLvtW{MLj(u&zC%X|0Ss|8HS$cm)8uVr%nZvLQ`runWu!lB^N1= z9>0Rpkby z2$nRLRMV34RZXy4AW+mL+ubcs9=}OFTN3(_-R3}+Rdl8!U~o3DJYR9YxngxP=Nn(U zk^$!q77d5-fDfRKw9OPAZho8=O1r4 zKRrWcOHAJrF=Q&DM8aqd_Biap#bU-APu@VKnlF9n4Q#h(yl-jRk-@^#Po81TfzQA3 zCY$cS*bOM9K%}%nV0wcH9w{??3~YBTi4+u*9FmMYo8a1!NRPLg=o171V+N!U^wwZP zU^<_Yr-9|9#5RKMW>2#@P$v~Z^*HH|tVG));>dEM*$cyAuy_*~%zz1yRT^2Q7&8z( z80YYS4Jn-)nCM6t9gTN%R`AhH!)#HJNl-~bm1%tNltoIKr9>5xLXZZH!6A_tYw3GS zbecTLP$;rACzyazmRXS@d`C03bW+g=$D>!433dkWJt9r$#{(k+Ar6e4qiuR} zdh%$gpM3i_`e&lR* z!tMP%T1cV@L==-*jYujc^(m&cV0xTu@LG}=_0eSp!}(c-BGOtAIwM4nl!82wRC$7k zid57LriZBUF5-g5OO0_2HVWE<=Fm8z75D)6yFJyUBww8JVDTzXpMF3ZGv5CEn}~_v z&^Op9aSlS%kmLxFQN)6=Yk7Hd!}4UwY&9i_j!dM?>Y7{&q!0|F!8lK*a-8j%Eo$(J zteUZ0&go5$NHSES>Bf%x^@cJ}v7;sWz&Lo?-Hu8NgcQf{EZ5>(N1{f2n(}h9=KSo8 z!R(QGL@7Z(Dh_Rj=^K{w6FzylN7_=!jORg1louW!XS!L8!fmad}6hw;%mP9MkEQ28>krL+|P1j?+#0-XD zB0>ZnUYwAt0;O`={(!Rs*LU}1c}mmv3^6bq_9RKlbTNh4aj~k%)JRp8OqLZ%Op!7r zxSn|#k+EYs&qx$h)r2ff$m`dY?VdC%>AD>%N}_Re z`;MYacyZhF;cZWnYbGjXl9eP{9213wAUaQs9wCqE*j5O$kRV1wp5%0mC3KN2FF3S2PU<-_BwSzJ5(4O$5C+&Bwv65rLrUmJN@Ma z{efIbGF3C5&oF-E=H+vAj0oY#ii9XjLQIhkuI{ebtQ)SbH(cLt72!|t$B@cQCrpzZ6$O1XBxOb!auTPQ%qA>OCUj$mE=p7iX_B(OAL#u+k(6}Svs^C7 zOF_Mw5rU@5B+FSzq9mw{t8GJzMM=dFkI9@;=a~NJgp?6Uk_=tst6@p_V$(!o_@&7mzMy1e(VdbZ8T_I_mC25xTmJiUCu zhgZ+};QbFUdr!0NXg3|#muqM&xz4ESoHUh8S5rm_MOs3da{9_+f)HRN0YyKIw1)$U z8QI=m;rbD&HPh7z#iXFla~KpuZ*Wl{vW%05kJy{Q2cJBrNCIuMAuCh9?Tc^m#w(90 z(vBq)GnjTnlOmD=qCjSVbmYoX z77is0A~g)cqSB1zq##Qp#_tIIo|K$%hy>@c)^mSwv_jCdmc?|%w9H8GR7pxQ7|b|e zgvMyWwq2tcn9dX0ZUadKJusVV=2hfkS#!Ef>H7_47}(w$#<8L(7l6|1f2-0FqLpOF<(KA~n_-K*i z$j9IK3Q7plQgeAX@QZ)(f1h9WkN--x;}##{(IKZ^k>t>;Te8!Fq?)t64~Q}4t=HZm zq%~=M%If3+U2l;ACe!TLHAWIedX%!%nk3Owx`H9YJHewzXJ9Ptwj->pMufK{L8Fx* zMDXEgSdgSCNl}n%2-eW|J-fj&DoMRu@XqHS@})1Y&{@m&uqUq#uf4j!uPwV6(xzVg*?@a*|5`+me2j|nNu2X7$Nf^qbCuNeo)Cr@9{?>7)zq-;2U@(SO$-Ep(- zXj(&1ibf3dz9B1M+ZYagLK_m=AR!TqVLTSdF4F8?a(Vj+hs_3^K_7Z#BG|PDn(hu2 z8}9cTigI#vv?Fj*r#RO!ZX3GYnsIE|Zf=>^3lb*0_;^FJHAp3icpfiLIZG?b#86HG z$;6ONG_O2-$UASn#Yn=eSsOUY3LA&E+o%Y-5%B+;QXXdw_W0gg;j zE(C+ybFdv_0I6!K)d|ZorxI|YHANioy+Y(UT~w$r<^JW!{$Lpa7rw;;;NtP0 zpLfRL|MLCc&;Q^z{Cd9a%U?ok&FRSrC#x0z`Jel9{POSl9^lyBx7PB1_yd2CAO4Xa z<%?hVJXKi&a5xvxyun}ii+_o?-~Jp=UVZhaScc#E zyM8xtWEuW8)9`2h)Bo&ewby6(41YI#t1u2PKl~ePVviF6trNEEmeC4cTwNlQh;;^Q zE#09f0rFh1Yg*dD5sJWkHA51>T87b*S2axujIvI#VjwRy4_6g$ zK6!)9?v~3>UXmw*7zA1?Hv4;=_e7tQ>N%35&81E(eRt2$H>7Efhy?%&jp!xF$mRY8 zI;4c)3BiCIaAF`ya;DXUDpOQiqLA!c0}u zX@O1xNg|jnXUtC~Jbdzivy%&kvBQiVxvW_%&S-nf`fAI2U;B{t-5$Fe7>yw+kg{Mn zbOblBKOE47#v#cwNxxZ>C7NlP(V3pSnsRe@%eZT~U2j+}r<^XA4Er7ZU}??BbhTi& z-y)SJSCVJ~MY&)Y4AU~fI}aKr%Ng}_POv+k{p3^LeeD*p@hV_BkPiAiNj-}VYH6U7{oa8+GC}t%-2!c3DzMA!L)Uv07{cep88mXYpHCb9Q^br#W7E?{z1~&IcZTQ)Pip$F_ zrrYzzs}Bied~&lRkw->BB3Uk0^i4;(T(TPr-uv)|QVE2N_-Jr`_|%5fBZZ_+jyAV4 z*LX7mkx5#i2xO@w3ldZy&5ko4{g|-X@2FKo3yG2zZ+e!?C2zl8@!+Dwv;$5#oKQS@ zG@)7*45nkWJ_#Esji3l1R!)K?s5{ICLDS&Px^-CoJX#%hifl>)$7;t%y(+M_ede<}JSKiJ`|dkDfAR8XTvJN%`+wiR$rr!ydH#d{@K65K^*{b6{v^NP zyT6Nn=lB2b`Axs^H}XgR=nwpib5==@LPWKZ{m;tv~@pYzqjA!Yo9U>?|uF2 z{MujhtNDqaJTe|%|Hk{g`_4N*oa_YZ)IdjMI8z%pE8<` zVy+qNJ03+jo2?{n&E%@;%m@ONu&0=YqStHN*Bmm1opd#UOoVE;NgK--&rSQ>_h=A!vWR^oVqxZ1hH+ZLLjpxPPp0*F%uJ@!e zqqheHBX2%Bkxg+}yt8U^WCX&}}z7T%O^5 zB=kMgEJZ}YVZX%=Ep?eOIa?tLL7t>kdBtlNPbg-mbc0})Wys#sY+95!GDrLU8iGLh zfb@aE35?9hCn@vQf{RDz^sYk!xl9OTWLb*WklVpzrXr~(Z2J*i6qv3U_Dih_DFXE2HoBNH8%WFXu~yXk4$j@dHd z<;!cDz9FdugY)R5<}mCKYCwquB{W_P>>H1#MkJnc8o&yaapXBTHDa-B?{|1NAeEsX zJ5;XeJ5Q08_|a0A31yaGakQqzj|2O*qv-}ZXIT#|R)Q)DT!=UUsZKFYVqIkDExW-G zqC)6`)4b4_#0^zuVAt5y6Czrqr{PU-E?~FlG!p%Nu8BytP;|QHs{> zn4M~7)4-SBJ*P|q_m`J+?T%PvOdgywspoX}maC7hux>=<0&+=8Msx2lA?N0PV3LE-G-VzT3ebXLita?Ga zAE_$M!;1y0WyyRsA*&PWMaIzGa2T$rXMqYZ`z=MOC@V=;Il69*4hdcB_{RHJ++1Ce z*N!*eI%BzDXV5Alcp{XYVL5aMU7 z`?bI3SCb?O-~CupOe?C%ly)mfN{vP$QhEGZ zm11pA7<+VDFf=#hGSciV6eY{$oG?bJw8BOpCM=4Y?fo_T%NriO`Z_L??Dp4q@93L` zAp{0%5i#P&!0G&y&JQ&04vAp9SyQJ8Z#{W~Ntr_jmoJ}jd%LAkib=gf<|!gcuu9Mh z$L?;=V>IV=iSdrAs!)SHIuso`uD1t70%f9)I`72cQp2I`!9*Ir=H%>EqH>r%P?R|; zcbv`VJb!+P856qRpi)VdOo-N@&A{nu%JzOsJKWJ698zUunE+?$ws%AY#cO9w>l36% z*f;l_oGb~_Q|40+J5N4G5=>9CKj5umJ9f-wOJowc9dWyxLHf zCATl`nM^9G#nG*sfuc zvlYPFr(B8DRP?o@i<5s1;(I1!Y$8 z^ln3pDZ_2cY+6xdfxOCD-#2{na?NZOd9awFgyj0}o=2~~PI_`iK19s6gAmB-gzLSf zu{|@_QK(4MH1yVyYK0P>(R#EG5F$G}5PgmYhPJ_B$yJS(igsuaRmnVGu-zZfGULOK zKENx7bpx|(LN`dJS&3Gj-EPOpc}aI@(J^H{nNX`7tu0;*q$=b7+As_qRbg>5K?ew} z=VX4yhcBKJOyK&a=k#JmS?AncZJ87c9xSIkdwI_wB7L{!@#72jO$VcZpeS=q*9Q`v zV}c_}OX~+Rogrn!|5WaRrW<>Lk0{|N^MuAa1{s;j3N0jdbgT~z(FW>rLYAh~B75A_2r_bZI^(WcBZcSmG-oiD5l<>2^NTYkX-&UxiCw^2$>P;>mL9BC?3$iK z(_y{9*+^MQmgh5`KYdA>Y?)>$hjqj_LsG!9s8~)jDlbV&MLz_lCrgx8Jpbq^j~+eZ z+ur$3e(cA;!g_PV{dz<^2rNs@EU(!12dY;W-0wQlvLGu<`k?Vxl*~C_u6XxzpW`Rq z|0-H*{HPec;qLyHR0)*Sq(zDI4d)Nf7<^_j62&Z^ zlDUCOW!&7~qJu+c3LQr>kzuW;lr`BTBP1h5J!QR%tj)liZ_c@Seoy8TR4Qp~$7E3= zI3}>GQ6e&hRvM+ibOXIJM+QHFejvOe320X8?mYBBZvhC3G8gzoU{c+yO2Ab;w zljVf6D7m`bB2tYsk&J}<*ppO}{jj4*lW+DB{SO4k!Xf_rZTR@nL%#O)uk&5s@f|;P z?Q38AI*%Vc{8_D!5q|&g`!|@+=YQ9)C&}?YN=iwL|MfrEyKleEU;Dwo&e_>1T5H~X z<8}V*pZoKF2OIMD!q0ZE&+r+(Rnc@kQU_cRBwBE~oMWBC80h)|DH24Mk}YdOx8rWz zP>7NsQU>YhOp9+vrYd6?N3wE?Q3H8V5xvJLjrSw!umzRk8_y6tMO7f9A!E`cC_z~2G^cMnszQV4D6M5uPN|kP&#s>_$xC`aa4-YWE5_l# zVj`H=nsJC6tYb2*nAauiyB+OeN3K(vy=SppL5SS=mb<4`S#M)D$~ z?FN!8#SRX8a6Fu!vOXLz41^&N@|27D8AQv&vz)ero9!MYC4~}HmBi(iLeDuIT9)N0 zO5`Bn^I!M^joa|-=|{|0Q;dytt|tjOSprJuR9V4lwW8~`^zOj!V9D!a=0ww9VsA8l ze9X7M^DDX9zR$;Bdxq#7Nu9EsEcx(*42HSPa7By*^;(bG&N>rBe{Ax=|jvabb zov=8YF}jgNN1lFi$!xkrW(Ax3HMe(ro}6FMG=|5oo+ER^EY%dU;uUbR#-}!+pbak#hNLhZdT4P>7@@(XeX=HtoQ^wInzm zoa&>4Rq9EEV75w|!3LnrBW z4g2*qb-iNS3lb|)(jj!9o)eB#mO4o}eR#(Fd?5G%GmhlT0@?%1WzE^@oW2<;vV?ux za}YhJ^C`|YbelDgW+xn)4fSHm5Jp~km|;6Z*9QoOD$7V^LEjzV(MLQfwT~6E{&|Xs36^CuZs}CNqD@JavZm}{)>%i^phPq1edgN+t2%Cge zC77%fvZ0MxLA9~hf%W^tL*ocUVvey1u%IE+9 zfB;EEK~w~DVCW30&hZXHsz_CVw1y}gIw{cdD2Ysj!1abAYI@}{F%p%cjtPm?2<;&a zgft~dU^R(E?Z{HF9;z~<8(KyZy2CAXS|hW_eshnir;I)jl0eiF83pa3|7IW2Kezbw zG56=4arh^G^{?jp|JV;uB^bvpjPyC1c&foFdf5z+n?4SRC@vDE;_woPy zvwx0v-u~QAUH`T(e~CZ&r~YsJGr##aVXfsq`cr?JfBtv>uK&jO@$Nfs^C$n~KgDnP z&A;iVzV8qG@jty z%h~h->m1L%@*dkh<&$>JbdoTJkvtW|?!YValB?%mXYpvxcJG+Z3$oZzO=d`?Df1Ob zgBJ;3eeV%;<2VMpOlLkSrGqKKSSb&1T0uPYH>n=^BOrLo@Q$ zTc;crdv5O?TBhV#%C>7!oyLofD$OX1g8U@Gw3e*Sh{}^*>k@$n0-RK{RGno@?=6HbFy4fWP;6R zV5}_roh8#X^LijlQtxqZe*H!t}m-~9{u<^R|l{N=y;|3P@k#~*xz>nzo* zrtb}dZMnI>qa7@>s%E~d(7Ta{VYGo51MU3{^3y@cpcOGd6cVWg-VAg$ko68FBR)n# zR7|QR-Oww1RPABT{AOYcUOxu$9E(IR2GEV;e*-1i-2k#ahJ zz-lq)<+Bfv(NR|=7bmCO-tE}k@0rdOfjzdDoGA&Q7fBvMMe?@$VY@Vxr)bzau!n(aSy3-KhFwn@3f9+qUfd7l^@i78zd(19&4*h)X}8Q4GrqBTiW3P+1ybRN z!ZDp@eCZ3X@$$tBzVO8_@X^h4uAlFjWtyvJAEAqq{o1lEB2fgKPZ3!lRS_);Cb^)V zUU0wJP%ln+sHe2;f$O`MjIE_!X3SSBf`s+`OOjk79dyxAR5Lc$2g(9`XbFj=&P%q3 z9rgSSFTk3XlhumR9HnFLys<?8Ug2sFTdMeXws6kq<*7x^cD{jcNuf8YnWySwM)WW_)E8-6`s{?gA(*Yn=< zKm9%b0uLWN;D7SZ|L&i<{@Z@bKg|#P2Y-Tp>3{hzQ7V+I|E=!hop;`*?K-~n z>Aqk7(w8vC@b0_s{9RxFec$`N{ICAi|22I-eCD?F831US79pUjs=w#8Z)NDZWR}mE z%t{7x%c5M6h?EcM@fI_T z+&3-u`j*M0!g#}CzND^dqUxy6Y8FMs^V=P*?MYL?>ufOO=={WT%gtX6ZJ65MUKOo|EPup`qcvuQy@ z;UmakiNl^{S#x>VQp_*d-d*$2i(3p9DJ)V8Ix^It$+MKr%NO*UJ#~=~Q^o!;P~-|Z z_MAO>$nDUvD(9>wfyPFjm(Z>c41;4{P3Y`^vI)I~EYtLR%f88|3XMn%dIlkmoSa?o z@%A0|y^Y`5<-_J(S5hEkS1 zuQ}|ulv#}rjtA!zx7&{x8VC6d1+?QxSsv{@N9%a=>;%&~e(10NRo-~*3;eo&;}LS&?!+o3Y&veC;PcAk!JuEN8u4^I-Xq`+iFjCB>v9M33;E zr~>;iGO0@xk>Cfc^@zNpSQZ@Y$ZTHn?D~>Audv2JyJg+^QWWTB0x(E-&9B5f#hTlx?@A8++dW+9lpdiZt^1FTBFDkFQ7em@X$c zCwThNbKZLLkh+@Cv>%ga0-JcwAFUYU8cE>sn{%{S@$&hWU1t~%hA0)6yAAVY&Fhn# zcJ281YnNk}``0L$QkMzOuWy-F3%oP< zOt4r?Q9jW1mfd1S4Jp#kxY06K z1t18izzRntQ$mQ0eT$45TMFC(l9*%t$e2okGjwCaD$T(-k~~3{8A*PW#91S7(Xw?t zt_@T!Wwn@NT#plxw94okhl?PkLa3C30plm%>?8Ug2*m&JkNoxc&%RqQZC?KUf483_ z{LbI?yFYt4{|x`oMX{OkYb|BiP)_ZGkXxBfGK&$YYl zhX4DY`wQRfAK~vC`yczW%*q8t1}}zN7WFA+FbEmwj70@Wmgi(DK}A76iO3>D?YdonoaW zZW(-o9~G|Yxw~r_d{0#*6j{R1OWF>cXt1r};{0{4K7P)8S#iDF5;Wup2n8+#9z9+V z@i+r+IBLT$77vLtjk8DR5rt;AZ5WK9J+!>?`V;QATdcAymS?P{GoHTxH9{1qM375E zo+`RN(6%iP9$YXOk4i0hGG{t}%(s8rcd^~y@U;(qn3L0U-g^68qD{DbamR;Gzrp7A z4jmkkK$L>!zGE_bNNe^iR~6CUQ`Iy2LDPB5YElrjra5fEjpRx*j*8wmc3nqZ<$UqW zzmN~!e~&VsQcOy|`u>kmWC|S>ho)gtO;}EAa4q{?Pd=&H?zX5zaxt5OE(i!BBd@;t znDyO(tLruOWJX=p>^B2%zxxJPcOP^0@l&*x#6nRe746>QLZo*+Z@uw4+uK{jXc$Jz zu6Jb9oPOJrs+7(h7zaUBrp%T#(Q7(qX%9QR1(nnwB9pv8(vw$;qNs7kaDRP^N>|84 zQe}bL?JZr~lcXuHJg8AJ65@1>Ov-*LOU6yyC&*Q$n9`eYJz!Vh79fk1r`zWH%l- zdAKB}Aju#kDZ8sZS{6hN+ry6OWKOOv`{o`IGRm?*E6o6;dBS3K#{%=_&K74} z-?a>V!1)7D9#5H1D}MYNS3JG$DRp343f_A6O&**+#P)%&fBkE8qhmUou^$JnpWPm_ zCq!UYFL-dWptEZpo-a^Ru)7D9X9RnXMsf&-OlroxrSXpY{hrsKOnCjt32(jn8ZTb% zn4X;SjrX1sY+!Tfu&u$l<7Z^o4ZQm5l+}DmmQ<|QcPuX^*xqut-Y{7v6qAzCBosR5 z<;QpQ{hrZ#4qaecODX^h7X4Z=mdiVziboiNcgL$~ARe&qCYg3eR!cMYRgeE!R?bGKb%n?0FGG0vcz zrPdXl69navsUQl)W^*8~b8dGBPUdH%sbZmWsyZW+iopdknP5E}e1kHcevIrJ4_Xnt zAw+>29b?;3Efq=XspfN(HKeH`)rz4T8QP9c4CFdz@DV92tL215R_!WBTU=zvDOmQ|zmU{L!!VM?2Q^S^A*M6(mv5MW->1h#KA)I2j zZOOD{)3-D>9ETA_ipo4Haa3tWnh8eNkTN2I<6^l)#>nHd2go#G>_>*fK+~)l$A%Yo z&+%HKqaaZMf`tg1VNadI5ITw|nQBc4117di%9KK8Y;O-tmnFuI$ONVV%L%W%vSL}xc=h2IdG_oR`ui>ODrdErllr6P_kQnKtd=}DS&*g*ooed5 zK$?JQ8*VOdxqNoR?snwmS8w_Hk9^F>Uw?*nhO6fv;<|w`c%HoTC0>8~i~R6ce;5-w z5+%rz1WbY?=g@c5vz$ap4%>T_vmCa2k~+r|@pdGYj^*-%=g(hqxZiPhen#7E$da5Q znJ}Nsu+CCy#mRKW;4JgilrdUPPtSS$Bw8ZS7&|ap%h}^6w9QE31&_~`-0e0Tnvpl(JY_TnmqdEgF?5l| zd_tn3>ny@~LMkvWVmv4*$y9=q25SRm^f)(A((fU!7d*ed;r8Z+X{HgX#D^4T1`?gHSvOd7AYeFsbjr=u z9uosG2%?B2QX-t7&?#LU8MP%9f{7O7Q6S9G?l(_sF7NO7c(=xdK=8*0>Bnbtu3qj~ zH|=po$~IizzT{+j#&TLQG!5-&Se)0amI;|qI2-92!_9in{A7ia1%LBzJST*}cYW8} zeECa{aHF9JDZ6#UhtF@Q@*K}dStguJE6O^hm?%E?_N$C$$1oh276Km}!9%mXBcz7Y z^T*uZ-jkGqvdqZxh^OUquTA;<>kmmq$xr;P4_WW`TwmWYot$9pK)pyfTNNx9HOrGZ zXAcV&r!!`=IU;(BQnQ>-sM4I1#e()=m{d!Q1!+N=NDz^1UQkV@RHbH8YFxAgJRv%w za)=0<-3F!M=IR;NK-)vV9+;>cV|wnl8>U&x>7*nWN1lKZiX>GmR+4F+;X{H8zsGuoXnT#6tXO3Fvpw| zNQi(I5CTUlRu9$>1RO>k+Y`M9Z#|+0r+Px?DHk)+3`(71oMpt476s8-l01h`vsA`@ zVCdKEx}H&{R5?TfD$STb{^H-~Df?>PB~t8n3rF{@@=(c!zWL@45C{rEp01A5j+xc_J8HASRknYR(@lIb9Sy zelRCZL8bwvEK-d~M#R{oZGyFeP1DkJ1E8s9r!?aLC>#|%l9Pvzn4ZkB_MUOrV|*Y} zfkhF?GKbRPC}prPhK57i;VB@=*zfwI^vMk&=em_Xq0vDK9^|WPLrL z$%s)ipPgW$r0aVgo)p9(KuC}}p{h!v7so_b0y;@C&f|k+HlOp#8}D%T;4v`j7-`3rAUa^cbpxFn z>4!Z^Nn|F;>zdGyeEjs9`XuAcx1Vr(y(W5z2?jElE((gQJkB@Nnjk$wW)yivq8gOx z$^7IGZzH)Tkt*swcF2L*nNQ_Z=n<4C4WB4392W z4E=$--3~t*N;J9FeDd+f^!pYxj8U-LMNVgD6dJBJcO+Fwl4}SlI;8jzX_|YQZikd9 zWieqoE7^C35F}_xSrjzwfyrb>ss*D7*fDT+I>-8vt{+%Vmt408rqz_MfASIA`yI>W zlq}D&ainWUR`YY3wjl&4(~2TZ(YhqjGcHa}@%}(Gmf3vCV!2{-KQeYBNs?jgz;ape z@ZlM|jX|mk-#G*&A71W3C{C7BT4QL3Bh#azL`o1+5b#(PX`ICykRoMTmxvh1(gfi( zd6lDeMsR^NFK9Lg#&O`#?^x)JN9T{Y++5P`_n2M7W_Mucdfs{G35h6(_L$zh*|c~^y6(v~5e%H_p2l6y!h?;Kb zar=?`tB$rASe_|*b0E**wZ|7|6Ug$6L}VlpPNx$9)j%r0$||Ew1Uf?71onMEXPP?C zuw7s_pOVT*v%BTNql#Ib@%Z$DyXy@vZtu9Wdm@rNn;?BaMUNJe2WKa=ZNuSUSkC95 z3O4t9Ztu5@Ch+wA7i@Y%Gg|I<9ryS5h|#fY42^+k0%LCoHXsp5ACS3ZI!&pvoPFP* z3Qd-!xVEE86e`vDn2}GDqa|=qU@eIj+_nd-uoQJpF{wF!{0O`xn1IMK%1J?QI-GGJ z0##X%C6PRjWJO6LGF%h{p)uAFWgt}*{np^4V>&I+N|7naVV{x~Il0!y#z~%{L_kV) z%si2XEGZa_#TknWme!8wM4^4gq3t>NKu9f5E*3}ISeX#4#SSA$HlavzVz6ZM2^ETV z@azu(Cj$5P9nuS=a`d(*7ocK_s#5B+pS|JEKg{?$3y0H$?B^)}?lXLbZxuLenN6p> z{rS)Hm;a0Zf`9q<{wq8@JEd*@f4`T$RSJi2^;L%8kJf{kAkP(-$FZa3 z?(!KbdTiLy4{LIrV1s4X?$A1+%E1JKiV~v3HXV|bw(H1?oT{4g`WtU@xxS}$9?Xu> z?#UClzTJ|Fgep&1ueVfL&Aci|yr*k>j2qc)N1};5zL*iBAy*{{ikBZhXB>9uOtPpm zatT3tBFFC8d{z?zxPBzhYA7_5bj8*4783;1B4w~0`+g+qNU9U^Y=TvgRT(PvfMiy! zKn9FIaQ2{NIjz`UuKDD{TL_UXNtjd>L(?&C8f1_VHT7&llm_3gabrMFSF8^`-b=df z!0Gvf*`mOX4SwudOiCuRoF`9S=j!sF7cZ{q+m`j+nwy(XIQW)bH}dA|uQQufXss}A zWN6n+CK}^f+P*`Gz$>r4icSlri;9>y+F?hL=Ex)`jVV<+Lnaw*zh%4M&~yVuQLvmZ z83#*N6fBohLKvvZl5ubt=c$r8!_YCCmwfJ>SNUcConOq;r{BPJf=%1ty=8s3B~yw4 zL#{k^syLlb+3)sDCk6e`g9{{ePBZk(<~1+x@9EnC)3sP9xVhVKwph?MEoSH$#_sqq z6bf$FdxVbI;eZUD>s`b1&4?053(q1s4kpS>;ta z0*lZNPh^^;EGL>QR|pZ=jeB-&M_ERq47k0;bq7SWoGxdq7E|zwwi%huS4XEOGjMWp z!R=*3+gf&w#~5*xp~Yhwc=VAd1*uge?)ZRboTB#wLIrwbXzY;{(VaoXgd)kQiW)O| zx~{=lgLNI=2BH~Ih2oPJcU;|$eEAEnGwfPE`0$GBn*q~1hW(yQN5W_*i-dGiFqv9T zXOSdxoIGA|@#KVDR%nyab{$tA-O{wjP`};%9ozk$moK(l-}XFs{D9sy2%WGW9I~nz zO+zpe(*}&OBx%9@wL_={H!nA|eZUQZ2PX^8S1Yb>*9^|GKio5$%;|f>$;lFt!;2SJ z5CrX}WA*TqXLq+4jtvIY4RqrUsZ%OhLJ0Jt$LayAK#;RIU@d}=N*2HF97 zCy`PilZ46ggwa|aFJ`1R&Lk_b>A9=gW6s4xp%&e*T3yMpKtPY28po- zs~u&b@kvH-CBqm&MIx5@bWTW0vMlAWxx?wm=ihyYED_wk_<(00zJQowwZQg)`Fut> z$ypX9xQNyT+igphmq;a<6e(JQ)`}#nc;%gU_@%$-SCUs1U;D~W@X6Cpu-zUv9=QDY zDck*;#Ux=i$q_-LghmTZrUe&|PZ;`+li7?WbdaV*A0avhZ!pnNq$$LLa$d9DT(aM9 zL2KTA>kSsO3|WBl0|5vEUfkSaJnYAj(~DEes%E`C5WHZvD!94ZuwFNOe0fc4I_k3G z(ZvP**nsa40@5sJIa{*bG;}VK3WXURw|70a_Z>F26osUZJ*Uf(#VjGua=Zl@71j=f z;E6HfqG7~R%~DqLDXw?iT;9?QEhnoPeQ+d6&dv2D&N!klSfQv6Y^A(ka2P{qn;I%<&+mMK4QChjx(NpF#e1PSmV0+h~L!_8U_J^L?WJ(eock3<9enhKdlI&_RXF8qm z^6DBPD&pYC3xzBavaCcY&3(H=iUNTmmiZv{Ia%pCEBk+RP_XvX=WD%_wAOZ z8L0A_$z(}g);zvAMF}`Nozl9VJTI74rzB~?`NIiimLt64*@rK21$23Q8eJ0V^Utf>stOHEmQ_r6|i1grj!>#c`fW z6Id z^Bk#=gT?2Pj04Z10aY zv{(06RC&tjbVk>A?AtA=wmewPc=G6i&0*wzyXUQUUg7k?1s^}VLU@f9usoR~MP!yn z3SCepCC{Jl$+D6_BnFQLPM0(4vgUT*aCg6EUKgC6&QVy3q~OKn6;&Qd1uTjJt-yK5 zq1n0C1Qe?dL_B$N#LU0*|#q&}fmC7Fn1WsR{N z#tyW7KqWQv#R(!9+~`mq(z>KxEP4I>HGcAM{wP(L$;k{oO;IXkQWfmlOH!e!tBR;{%K8L_W>QSC-jN^( zrUzr`nx3qXSaa;IFIT6WJ$w~iPZ27Cpt;*#k(8P*zWokGHfLI$(RFLacA&~nIJB0V z^@iDeN#9!%3W_2lN(jM{Bf&?MNHN|})(P{)oW<;f7cV~G&}@11>H~Bpa89t@wp3L` zp64hbX*L^-?J@n1hY!wpd9`MDwQ9_Cv?5z`6pL^L?wdR ztl)H2bFd><*Y|Y&$aXZSn1Ye`m_T#{@31x^r9w$TT}mdC5}jl?8<7Hf8ySM(hhB;%&f;BW0R!_T&{7XRmPc@_-a6WnMF#oimx7aCd!85;ZsXcWl;M z`nE$!czJU`M~yInoBRJSdv6|h*-;kwf2->3=kD*?dv`i}2s?^^AOf-o5tMz9Eo=fm zb<~;B(Q!~wMn@PN7ghi5U(GzsZ+I;4|M z;{EogKlh$fb?Q{rd#g@8=XsubMyziPS>NiDNtfA$-CTRa5#p@EVWK&5Y@4(fQDg~~ zg7eP0AJ^Ql#*H_w5(E}j-KDeGVi4yj36-f(!lyI>W!sc_Ng{G=2O542*LSG00t*RB zAu9z&V|$W?W|K;c=|>}^0BI@eHJAOnwy}5D9NLr=2wX4V@X8oz3(nfJ14M#JGn%SK z&1*3j6dYdPqMv8zydaKyl$OnA786Bt=Cp>#?wH8&oMtkuy6#%hK8!xy~nBUMq(q zOGBbjNOHA(Td14obMVLg7$CDo~jwjZzAokS8g^u_rzbr6Dg% z(mcW{1-_9eRpR)PG|$OJh3|Wlpo2w=I>7uD2YAB+JoNMXlkGsf?r) z_;XXiVak*#$Du0AJCy&vA~@umSF+mead_nr{lN;k88O^C%;?57mU=xbKShO5YKbT{ zwX9;su5c^^WkDVfsj`^Bap}|>R1_pp7iCID!%=YU_1|W7{V+yCAt9OPGMT%pWge{*&XO5*KYY5x0zP`#_r$w{XA=f3IE08E= z+uO*kg8raS-EI&K$9OfDzz?arHqF^4S{5XmL%hntwiSp9Q5J;tkZYE2WM*L&Pe|4` zdq~$oxM~vk%1(%NLTB4fY7HA>#%$jnV7nD7qb{EBQmec4Rt|IMdpB_4@L}9=qDg6Y z=CB+O&vmdASRMp+NfKofyY!6_o5_fA+9NFt6$QRGaT2YC4Ja#=G>EdmvqSo$m|;>e z+UT*>-9(xUR~pJP!LkkGQOusbXCkoZ4O2=ya^un)q{za-5-z5a)awoATXk+&KFrGc z7Twh~g3zTm=rSA(CT~*bwlR(?f}lfxI0E0n_5zgcQLEP(=o0NZ%+7CPar;gJyT$72 zGQxH7YYjZR#yHXVK|pRw*0;7m02J7@$%5UN9=%bH$PA9MNXvv#T;WIvtcuNzF-bha zH3~BTVO;W}?gST${QGtH}M_N zS&r{nNZV$0l;XHGw(r=%x30O4mF_C7K%w%Irqg7rn~@Y1ajdDd#FB!t)NDj4xmBS& zf%cq9tconf2#FRYN*hK+#W;^>cN*C3kcGM;5GAv|L&I~(MhQ#HeKvZ17CIsIP_h5a zZRjdz?MTG8{_T3|p@VBllvQ!n_pT-~G0hpp?p+LK7ef`Y*IXm}H6V5>Cj-`^sb z3y8B4%a;fx@IAp}e)OUI^FRMPc{0F}im+x++7y*o(&Es{ItvROY!5caW11b0g?0zu zt>d~jjw>0ZV^kz5MvBsL=?_Qj-@hFpMrf^wvx@dy$T-c}+>E&3g7dH~Fh$Ao)&^M; zv(O3X4>wpja+uND0Am}@e9*ZZS?^)#8Z{>%5e1Ixkz^GsaStKDRTf)CpRG~Ef$uJ{ za>F`8TO_5%t@#Xw3FD}sOkjO=lY@s2<5&*OPK!q9)2#VO8^--HMOL9zMXjc&dloab z5MfHZPIKb)PeC?LzzD3SMXEFn&qg|6j74R9#;Jz1Vy4z0&LGYzT+hb0T|^}&TchTN ztV-!;IpZQ_U|?V*BW=*SgsMVHi%`~31{_Nh_%?ORrsY}_2l))rv8L9j(@RGPqloj8 z*@f-wI&&vp=%GjX#M#>}afP5|0zy|(HqSWSkLgpwtp$gB*se-brrg)LuLur2*4SAV^NpU@gIIoCDeFkyNV3c8u zg-%O^NwK6LOAU_jsD&YUZWv_|g-$2Ya~(syt=O?+8||Qp6cXR}sj7^)%IVD4sJCjk zF3fj2)PsO}-NVL4m5Qy=7TG8wv;(p@;hO8e$AQ%)RyVeo>&&yZ)?@w9VPg6?KY%?WANLnIaL&?Trf z=*-PgZ`DvDM|v4j+W1WNNZ;(Pkn4!B<}+JsGVBd$dQIX{hPDK`Q{vhJ-ljRllz`=7Z+U+JrI%H|Z=IR=C-$LgF>+35VUOvRe=72KS zq~j53oY9Mij7MG4WRp&_#-SUxuC$P>koIG! zCW29E6~twRa191Q5+%5Pz&IJuc0KZ_B#uTXSgLVyib`eQQV& z_h|Zxs*0JLcNvZn(mbV8_YlUgy0U?=Bz1RUW+HS&^-2310l78vx=MPNzjSSWH9=4;H%)@Zm6j4N{b@2rQqCC`CAiGxpB2V|E9=Yje#ttBgkpuC4GbMO+F7qcH)B zGD~SM%(HakI_WTB-}c?KW@fnl#%l03T;5Zh6-JlsX$aIdOB2X?#lF|(7xPC|>N*bPpZz&2Jgj8rzk_bqv3Q>SG zntqW`35koqEhMsXiHd^pC?cz3q-9WnMO@{CwK{H~@!Xs|<xEj)t_#$&8_hGls(< zqENW0A_zi8qdrAcF-{BeOkgdXdAc9dr-TRG?|d@*9=xm4lqvUh?kj>r@6c5k$77t1 z(LmF7u^df3bZ9v)QC2X>HI@M{@G(YUvXU~>xXPs#`fQCW;zCh#LmKrKLRb{4Vo=0X zCZ|Xen)Q%I%fk;`q+?TxioIv~AtCUaSi(aoL0U!R zX^f|!j56{{BU}s5bIB_Wjzuqt$+8Hg6phv#>3EFabQtsobXqMEO3Eq&QL!}|(rC@H zyuL*;j%kN&8bOn0tMG@6*0#Iei$A9NvGn;VSEF&+YkkbP%f0Jdhezsz79 zQTIZWvT)oGAtj^kh%6e@@Ez9HHdtBfBW;VhogK!*5oIOOS`$Yxr~*?Mgb}29ge`26 zQBFHpV9%ay)ar^+Hm2R~kQNCt&FOEfBdn6qc*tNJqot-E2x`8fQ40uzkg6={b~llB z8(}Moyr76jB#EKa5-*r&E-GD86_P9)Q;L-N8ILka7^EfLaZ0!0rKkrTOf+JCTZ^5$7eH$o^#+Bhm|vX1c5Ph0!Dy6H^Xg>TkVA)7 zN%N94GPJxJjX+@wfj}UYMd&*?N?{6(7u1o)!4e87EUHvPS)yEv!sH+&!U1D6*B(5? zwFhs+#1-U%Jc}7d3BET&Xf;_rvc@|(B8(Kc zjy;L=T@=KHc#HpZ09H3FKBL&LF8{v5ZCyS}Z5oKACMdOKZv(;z4yFq0%wbm?_@3U*i zHjHP3?UAJ^MKLCEGZEM!QX0m&Mief4w#`$S2vd~Q z0*M_u>@+TQJ48D+Xo(+MnrQH zNsKar{%A;DN~)q@YqiI=UE9d5lHs6-AJ}A>pr4hv?H0z0z&0E>v`QIQc)p+(dPK3r z30?B6#Igi6>Czu16r!M;bUA$OGD<))&gryfu~i3c)@d!;Xl?MM%??$kyEP)~j;S1* z+*1e>qm3qwZQ^l{`I#0vPbe}$VQiwj1f@ul5yn^uk)TzHh@n4@aQ%>6S9lG`MT)d- z_HJ7w?rjn!31Jg#CuDuChvmB1R1C@i+s@g~o*lcXw|6ky*ksV{QI&?-+1-qiltN|{ zWVpV=Tx*7~)}mJL;Cd|%9y&~wt|4_w9HnS!2>b@4GNCfiKeS9cbcn|rbm}vdQ37R# zmKtfLC@(+>P})Zi3XXJp__Yve3l1Du#u)HIk3xZGNgUt9@@)dwr#)Z8ZPXbJBYb-S zuQo<`0Yy|{vYcikq>NL>!y#Rrk`^(uavNo)nXfMr)IDZe9acBG*w#cCYd9_&>&w(; zX7GbLNj9J-3|mP-t>YpLSVAB~i6|9CVIy1%v?eQZq_Qc;C6p=34sk7qN()@uBg!*+ z`GA6y(Rhor-^I6m&e%N%N>F7zHa1iG!vx7*}hwn+;TbmO~qxY}3YfS{zwkW`1#jEK4CB(F_%Ht&l-~gbZP8)T7#v=t#3xCI}^2 zPjj|zID~WzT52-gK$(Cv+QRoNqOFwv;VxTZ8R+ zq8!j_I_M~3dGi1jCAEe{S(dm;B9x6K!LbS|0dsD^V3;7SI(ebdqQG$!4H@7{`)CAb ziH)EzDP$U1*$mSZB}!zL;Rqi~Dk`Pu7NDgf7CB04>dIhii5^9a#)hcU6aNWg@O>Lk zN~AEXZuD`}99t@KUC;;|3X>8WO&RBi&^q0Z=u^r~vVywc=`VlX17>Z!sm5W-l)H`l zipIfB4ikowh8y79l6I)Dm1Z0#EceEfm<3uAx<09nv0ab4SEJ$i6jep3C4SAt4jr1U zfZ6#rJ9llzwOzJ0yWDtqowfBXhQl6}NlA(bOJ`I%M|u^GD{w6rVJV!tLw^_{jE%Ge z36p)vm2ny6V?fbvE|R1L{mnj&nH|h6EKsHed0NsP4alk3-0V^o6&vfD^rMI@8==!N z28)HoGswp&O(&$$uCqDpA&iYNin7cZCL^}**iM>cY_4rGin>Ur0!z~EZZO|zBFcie ztVr{mDjrje6Pm3$>%&dzwHl%AP>jZuRY{swjQcr7F4?hTfv_&|tOiwL$di&vrS$q+ zlu3oNfJ?! zv3z8inZRTB_62NJr`Fkq!s3Pl-@zoCkPj)+1Ys#`B_XdU(}JofSUPx+-r5F5mXW3f zPR(Xvv5n&e46>BXjRB38i|2%sU{SKdt@(Jhkomb8TJ3p^w6JU!O9TjENQVh|A;{Ab z&v8k{F`LVqw1O6M^$yuE$Fe;vuST3}R=NXfjzwh*-QEz=jKOfoc%0B3Y?0}Vz^jpE zkX92jinT+V>|9VZnhq;l3BzH|&Q2T0QaDQCxh}S}a2g7&KuA!fM&yRA(H6Ct0Ne7A zwnA7g6%)-wndbz`Mj=5I*iwK1-?OkhP?m+!hL#^vVu+2TKgzHJNuK45qJmzMp`;?+ zis|*o_>N?z?Ne`fG{QEbv`OuPoE*9%-RTbVY5(rP;Cw z0*l#BgJhVX^PGC{5cN_v(wwb0Bg-@nf}$+h-kzZoHfS_FYAv5!i>6D?sTI1mqU@u7j&4fd#27JONc@ z$kLp2T#_42Y6>bNkwT!fBB>lQ*TN|jDhKJ?ldX{Rf@qX6C@PY&A}r3spKu0C)*q3z?i5+xMLu%sR=Vp%Qv-Bo5gb;gB3 zlw#tzC_H9oIv4{*HVGs)%nK5eBZ`+x8H67LBHb?-`n41HY3qOxsk{-*4H`%x6 zOauyH3EDg6`0<~70=qj8=Gw2W@b{m&ob~nViK7*&vWx3k#2YcgQBKgDp~@wB6cgA` z6q?HRaqWPWyG9mk z$}FPU@vv-<>u*@Ys-PMA2nVt}V`HPs=CFq;p-4;C;}j!uLfgZ06rKfnF{a%P84Y5} zG{(}JOvj`}LaZfQRmAM}4#RQI_8r?8kB1aSlj;gD05^09{D5dQK#L5o?NORByLWmx zvO*^%qqt-c8(NJ3qcpy&a2+vmcy}E9z`=4Au4_}61lN^x+6!pQ!U&CRJ1C(LMv_;8 zQPjiHCH2q&V7S#sq0wbRW^!t_PbF<4osd@=r4+6zF|tHh4!d^jVQX^<$5vRrV0QNc z&4oI<_w8Zr$O^VFNXw>@Hjn?w$6#cF(i&SzdV`z;ht}EZkFYA6nlrKU@63cO&NYy> zpcmx~#tG5_sWi4_k!y*v6pkZt9ZB6$Gy@yErVxION(hWdu`LT18^^Jk>ol2PXfa=F z;(G>LRLD}If;x?MKoXBAq9J8GMv9Vpv&Qn$7X5CYGS2V<2Y+D>*Pi2s?_EvQ9aC#< zLtvp}u)-$3T}McZEGgKGVzS)B@h$4LCL0?g`hz}mb2Y}pE-LWY7(~R;n83E_ZH|yi zu{F*xwqehXIgHMz1$FwPh(bo3zyB=CSYVOk1QoM$HP$v#DxG6FHp2`y1|^Nq!gC$+ zJf|@;!?u>kaF7sH6`||k<_0p0?l?gNHf^WQ)z@vZ-d!b7n%R02OF86~AkPctf)0+? z1|bOSfTiVCswyOxB{C2Mj!lu|jIxxh5Nz}k(!y~4fi?0>aNx!b3LIh#_4YOz&Md=j zA0slfZLnP*;k!^8G={ZN%pisSFs4)ntptv8h+>6OHL7A_GGmm)P@!Cj=lM8}hj0wC z0#j5Zg=S+srm8fuswk_NQkN)OBBVr@C8^3OOi4vWd)A||;Nv(GU%V=-pwwt5Kopbt za70F#7gTA5#3m_n8jeMvY;>WiM1|0r(6aD$+;<{J=TpI9%9PW`eMN9k(X}|jqbSG7 zDnm($8yXg7LxNfzSy@OVahy|@1=+ZuRY-1XCXB}e zW;?SuzDF+^;n*RT1m7;PZG{w?Xw=1V1(s()sqw8z8Q6+EPZ5>?2RzTB+uOnjf$ey> zPLnvVSlU=YR}rIbm*M6b-QEUi5g}DcUKKdX#Z;Q4iU>oGX0t&pXyDl%vdk&75#zyt zY6zY$kWPhVYXw-Vw}g0DQ<^%le47WFW|@d9i=L^w8*3db?j!Wx5q zMo_D9_L=7p1WmFe!gU3qU&od$M&k(2tCJTQji!ffCU0kxq@X`e$cvI4yU(V#nKI~) zskIxV*%(jRSZ<9dF$8W6g~1dBO<^H{D$meG#om3}!PbPX#VGD#+zNxj@g@;;=VoWw zKEIodtsW~!2BfKEWf#;Rh#!_&>IXl@lAE1rbLYXBEQMxOE#N zC1sW4Iw64{vbH*)s4BD`vt#FOoJK%l1cUB?x)ZSHj5FElt>Ohfd1)xJgtbH`2!B>VR61Ixw=99+jDFLNpqs5B-m8AUOcr3gKn znzXSkiLErAt&pyR7r5A#PcvvirU`=pBMn6flme5bC`&Th9C5?J1IV(%WEMxZw%Azh z65wJ>o7sgq?7GijIG`L?NL%6tE+TZOEzDA8K3hkI^!r=r3P$6U_2o^%y5hQ}nB|QT zTceoPOdBXElLBcp(o|SVQN^0MMxDY)as#e!(`mLivYar}35bdWzvkk4HL6nM`vF=< zblNQnHirM-=Wi*r4cH8t3+2hPHBkaj4I2ixe`ZN zWLb{1a^@NiXUw)(IoxHGXS8OUbo?d<4|nMoU36CBT0UtqB+n8oyTRsWijsmX8zVzQ zSylv=jggA1FnFGaSF@>nfyiK7q?EQzlxPG2%S#)q^p{b7Oft$SQ-O^@3CW6q3ijA;_>v>4!f`M3;#G?YACR$2ra|;}}VF_VDYz%JLM0!3^ zmNPRygIBlc%+3%tYDB{khu2oHmBq&97OBp#gd%7LT}Q(kMVN7;jSL zF-jR)?GS}#)L$nWji}dr`stX`7>;Zl!K(~9Nr|!&%UJB3o1H{C)dt_IljIqBQ6epu zEXyfMiK9HcutqkHFjWCUvAH#-S?{2=p(tZIzM}4X6w06xG;0kIF-e}FtfM>6n1Z?4 zHnuSod5Wn@(%yhF8Iu(;Uc*7Vk~phK^MX#RjTHpcYi+`M4P_e;8rL!G-m}QQ{d-xM zX`@ZXdUr^bB$z6v+goKg+#*gk2|WlLkHR<@&qYXwER&2zBZOn21=!F)Yn`Nn0&8=LIhK7-?1Y^?QI-H2FQ8G+F#Pm&iAE(JUHE|BRAZG)}^0vo$- zVT`6x>(HK?rz#|tgM>GcBL_9XmL3!*$rUOI{X? z#$zmO{2-vw?yzgy`4q9wf#n-H(#t57!4@H|2tk#&w#N4yl;sdu9=_!ug+K~GRrrk- zt_s*#Tg7sjL?Mq8a%s?x&1MwSXf-IRjPB-$(Qw45H(;>YCkQ$SlcJ5`>7$pUcXV684F&?_m zq;ZOE`?OjC-E6@29WA!ab`W_&oF*JxAJFe5h`eIY?nR=g&tM#}vN@s_NVe9-$Qm@} zI;^cG4Eh816Q?o5QHC@nRW#YUd3Ajmokq;pTRiZ>A7y=Q9Y9-_4<}@g&%vvMOw0k!2MnHo^#^D5ICe)Uc>q zE~ydpq7kksu`9zU6If1wZ>Q9hjW7}9Z*scgl+P|qRJJeDH&HW!juG711AUw z7Vl$m9By)szCAX#y1d|}FC$O0$q51C=uQqo076W*!an+`Y@2t#{VmMT&7JPHPMI?0 zE~J4%Xu}}N@Mm3o&%%)gAzZSxiKDEh+zbMx~k;m~SySXk_!;}J7{15XKJog>;U z!g>R*5fYEbWXTYdmehS4*S7Fm0poN?X%vSJtst|EPNzXIS7$UHVOa^juSlbeq+1ef z4~RUErL`g37I%}$iml<0cx#01mUPnrI?LF;a~30Q@>0-gHYiI;o@PY7lC;Rt!k{Zb zWm_a7qp3WQH4bj9v(Rp%bdF`&)U7%j!vV2O`NqF~iNNk~?gc-I@gwHv*Vr0ta^<%+ z@Lii|9HCr~%{akmiSIUvCQaNUqzZ%i0Av5<_*io7a`HY3_|3nbP6 zT_{}JrrR44Hf9<2`gm?hnnS%kkL9!(MhVjQnC;9UbcG;iuH}FUD2ECmd{7BxnxSNc zDHJDMbUO5Bfdv?2)WB-xk}Xfy;;f@22^2Px@|8A58b9no-X zf>wy{c{pi_@)c{nB|5DDDos(0Nm9*n)~7NF9lwcD4%c>5!a2DswwJkQH;p7ec zC}y0O8V&NeqD)JKk`$q#mrcz1cFc8HT8&AfbwWR6zFB9m z)FpApv}*x1#~>RDlWA<)+F~Uwly0ZHhtnu5)EzE`vpmsBU&DV3i=09 z#Nh)pTQ0Sl&s?)0+}@^_kFmoJu2d{vw@F?mY}-AHz~a!6A)#t89QTN1L6R8)RWi3Y zM>Njqr%NnupCy+zbs2JGX_;s=fK;NLiX(#q)aM(_>}X<-ZKQx!;87MOGmA}%q(B#v zNNUDqj&e2IynsB7NrWQR23yrBvYe=_&^o5=JH(?5={HfKMUKL8Hw5k_IF3{h%46vlHXsuF|2vNbi!Vq6pyR*9z^qP$0l!U`pY z1f>gE-7+NuNopF!OZD?NZ$oawqu>MaP#qPX4>4`;`|FA zz-T;i*sHWbYmGJr0~lj4#!QZl!L}^E{%`-r!uooC8iJvBk`p}UXGD&#YQw>zS+UD3!>rHWFB!VOjc5J13K+ll;hGL3`wFst}rx0 zpCG6)Of#~oBpZ*RD2a;!en>jV<)1C{E9^|^9-Ki0eB5bFIr7G5Y zeX^p4RS&7PZL%st>IzGglvE^fLBBu5_AOfT4WyGZ?2iy=TrWUcHn#2JH*09EndvOz zcsApx%V?Yvge}JL5X+H#@5px;tgcYSnr)rEJn@;&Ag#q5Uf-nUG-%oa-}6`>M2vbv zvZ$acp;7NJP7)3;Ewg*?Zfx6SSPlrCIu;=ZS2swDiuu`DvV4rCDrBf}T$`n(HEb^+ zttw{PZGX5lT^D zlT`xc*jRRnq{Iwkvb>~{InBUleR%~}S#0)VmR7ddSRSJDfZke+G?FSCGS~1JZjH$* z#r(Dfs!Gv|Mm+Fg=dgWGjSJ4(!`bKWVeeVvv0*X>&yMobhfb=X{ zjT&pcgnm|03v84v2s{thQs7t=rNp;gJj=myB|?F1IS3)CFqD;;m{k-xWsxDO5FVA+Dd47Aq#V}?AX1H{b%k*xC-HcU)L-wwu#e>KshAI7$YRZ zVS$h?-C<0YI3NS^BtaJigN>N=Lp?Uu`-m#S6`CLUkw=ma1J-ZcVtwNP!?cT$DeYRF z!z&TG5C{xHNF1RM#sX!b6x8bhLMnW(hAvAa3MX(-mWPyzB<|6u1^AxDzO%N|**1e0 zXcChUST@nPATm%;FwUTmn%-bYZ*$DX+7=t#h|J_rMXaqx*tUx+4NIGAG@Bi2Za|g| z>2GA5xpR&rukb=aQA$)b>FYQd)2X`*2Ni=TW#^6s_U_xsu08YUazw8;#132*nsw@7 zh%tgHEATCsW~YPWc!V{dikw17hP{mWh3%~LHc7Q+oEKP?0$I|}a&pTMHWW!A*%)O+ ziDtEzV#=IGr-pCW@dL@8UE8sRWM#d_`Y1(X<2n*oN=#fJ9gBuvW7r>oQ49w$MVir` zZ;;6fO9;Zy!Wf9M3aJe3W(`p(hP{Znc9WVLQ0U2q4AO%<>w&ZpmW!%1wU&h?1jDr+ zRZ(JlHX_%IqXgF~85V|2+qglExStS3eS~XcY!^?tl$OD@6-qjoqmj@>X)!1=kTyzN zIEBDqD5W4(7FrjmTod3^DnlhLq?QDhi#7&n1dfzA%HRnbr9EWmGAv7?T!HJLs2Elm zS{jVeB*sQ43t70-9goJ|b5HkU`jqg1`<+kj*7?TwBS)WDaM;+~;2tz3ND##9Eo=1}RB zDl3td&HU~ij7md)9JAKlBF{#&g9cV8iDkjk;Vsgrq9{r{`5kT0|y6ScZ5sBoHo1l0YReW)dN^)@YDKU9?H51wO9rV6?{40%ahy46dh;rJ)*U z%rGXnTN?;nqAH8RfwkT`^PLv58j&VFhMVgg z`1WBI>*w%{pMEC8XdPwe6lF>nD2hBm=nPRxdTS%vtp$t{*rFs(A{w*v=+aOW5nioE z;DwaqA+`eL26!?cv|aL1N;|C63~J~iBTo7#X=BQQKo(d^P+OQ|Fxo^)MLlq#sEE4- z2#M|bP&(K`V2OmN%E?L?=P{jTlRPU}-`t>C^YI*o5D9bhA--$jIUYK<7>^Us7Q%5j zba)Ndb!fF5gs$jqL|8)OSsKR?_<_Ngio7aVUD-faP_NaQZH26?ty5JgfmFS9R9oE> zHi{H2#frNY*HRpU7WYz$7bt}mx8kH&f#U82r$Eu-7ATV9R@?~?iUqeoa`XP~TK9h6 ze@WKLS!bU;XZFl9&pbm?ORaCaMWa}d84*t z`4z;b-bNfhffkikz#bhvbp8?*$EG7S&)>96VIR|^rO)~3I8eLR=^^V@d`22Jrx-Sk zV=O+D{7o~wsn^r8N|uZ1>YwXR566GPrrG*5I{P$A_Fkv%ne?5s>7NNsie1IdxNdgzG zCIAjxFI}Quj~0o*HUk7>y#!YrB^_^zqG_`W*0v5xY(so zeG1N~%+4R>YE~3+9XvC21{5qlgTF2QY-`h7FLDgxt7%!3eCi;!Dmzum)Lj`IDsHEl zGW#K7=Ud-2^pwH_VC!$!yV@ePXNEcKr(mWSsu0WYR5_dWcfDez7OuVKTUzIm(+7+a z&b70opGoQ)5Q-FUj9p3FPxb@L^U*&x%BnD`abtR0EcfhO&!lGW5yYzL+F~B|-}O1^bc#5Bi|iElqJ5TDp5r{8-DjrvhWcR~BMkzP4o+Am zQ_Qd@Exj##&aH(FhsggIXB(EfM}+mqMVjZmb8kBhB`N<|RT9=e{sgzol~oK)YSTPx zt_z8bj5L2r%#0Y|^1qSUm@q21RH>@khJqd_5`)J|{_}SYdO_z@VZYxvW_gpSUOp_7#>Sr4DOw|1HcsK zPppvN5Z9Dw@B`-YD|2+n5Y;C0;qY7kjw)tZBq|5y@ zsuKPoqNjPQgT(y&;Fv>6e*UI5k6lvx6a@qQ7d*cU_}e_O4XCEgvJ^AWf%HmJXe`{H zVmErJJ*fkpv};#N*zgnArdu_KKHyQrnA5;D{)|P{lyKwfN0aTpKhjVG?Ha20#yNxe zUZn=Q1#jX?uxhlt)9jk|PL~N|`0?pjA8SN3-WXB~CO8?H%2)GQ-mi)DEGUeAU&?-Q zWtRVgIaO8a>TPTjH#cFpAO)Wm-(Pwb9)!~_QsuBZ@&%AUAFXG8t8U>JJ`SPoSLPYmHMdsK<({xAJh_W=f#FH_V{^pL={%6k1 zv6#8lWF+mV5^npUnb7_`-xE`Y^|sLZliAOB>YC_bIuco66N{4a9V?QXkSBgqGbe_h| zM|Y`YWxXD)SmmdUfBD)zu95HhXkE9HR)3E!|D8&A*jb@o8DHaw{#zUmLsCi1X<-M+ ziL-FrZX1xM15uQnSeT}A=iJaWM@z+@YC<8$X(AaFgAqpxukk+lkVOvfS1qIi#;SCb zL;-X)93dEu81$iVLaJqfID;e;$(e$}{;%g8(u!QP+scC@5bx^VqR@-Ain6RN!&i$KTx^&h1GZH*x; zUL3(DY*72#a4VoXm;1ZLc@6$y3@;^K!dJrjjACCOsdq_i9P8tqZY&j}Oz=GAA6EJmIi$bG^ZJb=&v5CBA9!t_ zR3SB?d_3ueGn{4I2I6BlVF}h1+`RYx!w)@pc_)xdo~^y)52RTY)Wz7WF~X<(N@Gn&u!tZ%7X1YA1@?{;_X zLtw4I*nn>0-ax>q;>0Q#WF2tqbobXmz8@j<$O692F<~C9OWPg?K7j8ocQY?SNvwv1 zy02ehbfIQO{q~ia#+-m#=@$NTa~9_&@ZB4$o0V37Q0`Mgz@M0rfCcOBOzWWWYAG)i z?Ij+zJaSl12PcZhm&S#tql!CrTM(RSzw%sXYnbSP=Rf{3?WdHy6W&#x@(VQ50N_s* z@j9oA%6)>zdV3Uw`ug#U6lzy5@L}fZm(v;R+n1$jkn=Srp^zoMdj8manJ=f?)W`>c znSzU-4&+bz8lXtTj(Mbi^Aup~f-30Qlx{=q)o5@Ca0ULTiuT;f2| z0)=kUJ+8Jh!AF9*xhDsDZI5?u=q6vTkD{N)G-MvK5@J7(X z^PsI*>z#C=&fBymn~`8pW0ihOzbULhja-i{dVP-D-ebNU<}v5nqhpE=-Wj#%;~@;v zo)MOvz6RAg(E2>rA!syxB7cJ$O!xEE^1s@NH0Bhmw31W`wnd0rDs zt?FQ&}^5?6aRka^xJ2 zcFj@9W^?=jKECQB89N~_uV1%4z>ZEBKvpAV^NvX?DpRr|Wbgl5qUhdVxeYx$t{jwW zY{0J=Yd6o`R}0To3B2-OjOS=2msXVO8XFr>;HVlDH^0`5aWO|jo;if#6*!Y5`(u+{ zb5{~G$Hb1}pPh3nQ`OFnbGA1Y8os6;L`+&-Ww!J_O8wRlbTq9#j@MSe>H_Pi%r)$%Ha<`kgEzKG=j#4Cj zB8$id`2I%7o)PG6wgO?k8Y!1=>5`wPH@m@^)^GO!MGNqk011ee^<{DA4#}(C9yMcQ zq9}IR4F}o%jhdTzf8FjSTS*3kx`jl!yaidmNc&j;Lc^`Zts=W>XhZS8q=lz z9`%8R&$#FY^7`ga@vDi|^`YkDSqaJE_jU=xL~ zUbv`TIEHqP%J>fpq2@%26!H7|!tsVL@~W!bEi)&rk)H%5;kD0#JlLZ4c#KYliSFfo zo8{9JGwoCe-P3*xUthC|dR^UL0YHjOEOy>x)xfvPIMEpNV+i-kMup`ik^Ef@xZV69 z#JYRojC(YN5B$jZWH_fIk9(->VfRHD)M$AO{?e~L5>QKnswjCeD?a7Fs)%hsACvu7< zf<=RT0)Pr?r0T8< zzKs*hnVi(Dx`OjrYDNSyTMxKBZ%@FdIUjEitKmY9#mWrfM#=nKF@c(ox=Z1uK%PA3ajUF+--`PXuoNt;%~9t zNR3n%t=9w&Ng?mE>|rvc(_082K`fq!P-m0(OWql&=e+w5-MdFO!lxLJ8@%BB z0Tco1uaLhI!6>-@Tfr=W+Re1?TL2?M*+8BnL7XR z>M65}jMkob0O|szWo+E_$Ozxrjc8nSY6%@n zGq($5s^-g?Abx7&qI+zGysbT9O!&`JNfEuXYhSip+UB(e_O!2r1-$Ce#C^%K4UC`)a5#ZCe0{tdKno zyQny5LVFj|y@)UU)~I`le>Vt7!p-qYAoU6@vvD@)O^^Bc(xg?Nn*6!MC0_9L@e0Jr z>Vg-Y-aPA_lzq(pZ?}3>k+~p5b_F06b@>20Z%71^LmK?Agt}6cqlhmgm=Wk!<}*Ij z9d$ceOCZq_F%7<>m%nb%Gc=}547n_6yDbxv7Z*W}&4Px7x(+B?O*7Hq-8y2Oq?LgxEJu>6;k4<}w{TGnF_d89_injkYG z7c5^HQsC*f^8qq9oaUwFdf)xu)%`F1wxe#sp~r7L)L&-BP9F2?EvNl6je6q#kTHVFG@1qo-InJXEsD0w-IHS7xqga9@bnNkiYC^3m zs;2o(&fTDD&+VX);nV&~aKFZT8RDlGUSpH_U9_fUTExDuNZbOt4${G6OE;ZKRorO% zlF!>frjC*tdaTWD?2QAF5RUf2<@;qGY6jOt<*39{C)8c$nJmfI|1yBPl}QvMhSTAp zg*(z8<6&^+1=gScR#CF{`JxbS{SJR#B8HHQ0wA3)4i8u{Z@W}xE+&OtoV0=|!=+kQ zszh+(`h>|CnB4pBZUX2hc#L%Mdfrf)|Z%3db$ z4pqXhwT?n+`A-IJpsn$-3LXBcJbDmt4& z97eh`jmkXQYDD8if{xsfITZk?Ddfpi=RvENnhsxLCTglkIjim{`Y33kQB6IW3k7S{z5Y$KkAEz|f3IiIRQ zuub894-R7w&yRf1qxPc%YBZ9m9fQfkl#NAXTE9Z2o6KtdjV`4YaudEaSxCaCYITvR zbV}hU2?bPdaF+~Gwx4hTM7zpivU?*v~m5LPCg`}04u3z}pf)g7-j^Nql>@MF!i zK5LalX9QnGzD**JVSc@l&yBk8?xbqF4nAX0?ZIM-qxFIgW*=Hn=hYQW#A?3A9WFWX z(semQoLOjc$L@gJA6F@*_`9BO+aT1P_3d9SKtKKZX&^eptk`Ixec_=M@~&^oxGrd^ z;rJPFuejnoIq_lqSg_YE&OZ7oo@6lmCt^{ zKAp+ziAEbhqTj?XkiVhN9l-5=19HC9GJXL<$%dR|hU|(T1{5Rx?qBu@{q!qwN7$L~?3{f%yH0$q5=l?Bz_)1SU+~kA+y9!i z@t2Mg8b$<(rHZRIe|+mz$H`D-`$&5;pOwE9XtaMxn8tS6ZJr&g z*Z#`b*k(oCyn9{aDY3o5CPCjv-?3-qiF~4YjS87KS`tiX_Fh~g_YoCziw@B`zIuEd zfb;_OAEFkR&(HKi2)|}$(;^{m4-y#2SufD2wbi*RhE>R!*6lt-zAsVssOS=by?zTs zK8e_*M5pv#Ky~Jw+_;Yw2cc*mjk#LX`axn=@?p??{dG=x*U>K6J$DIkzeTbh+6d1Z zDq3=8l7UMS`xk7m_OTKv$B971G9>_l-?yp!sj2o78HEJ->zn7gRqtaCJkg_mP77{& zK-hT13>VDFhDd$1pc}xG7Q?^bVu=r#;cIzr@~{p^PNw!uj*X3JIEt0e^mpgT2>-Vi zA#RZG@(I9cO99C|!uW8we;fpAXD|&ZA?txNj|LWBA~xVy>t`(hyv@@s_Mo#^)CdSU zoO?UI5wO-kqlJDiIUhh0kEcDaGaCQ-DStj8j{EV#3|B#$?#XLggkX0v$5eN!cvQBd zK;fU443KhZ0wYee0%FeQqIfNI+y5m09`UHwOB3$0GBz$Wbuu<}=(=|Ds^EC$8dvgK zJ`RJ2@JSNq5=YNBaqbP)Y5D##cs-O=uV-cDYC=nJ3legv<~NdBfUh0I@L$eyD+E3V zwQoLL_|2jU9CtG_GVcz+kMwSSkMMH@@K&vBA7+iwix5N~w!k%1mywSoucUVOWrem> ze%@3v9B&?DD9i57`feoFAB7Nt-dLlgKYo<gd7kg!m!b&J@J}a-iJG*lij?dHtB3nU3V*dk4)6Zll?r;t_K-(1i$Q5 zx2s4Jz=E-0<;&Rlp2Sc;q z{h)69$EcoGNnSqfSHx&Z9&E|vgN@aLTSU2LV$eY1^zk)%%TPigH@+dc!d(|KuB){1 z{V@~A8^HgaNbUQd^z4OcPHVN+y{er;PVb*Hf)7(}PM1x<=ZiPYz`rHBW}V69v^_AW zB=O-PeHSbk4zRJ*9fGh-g9U9FkKr*lArZ@`*-B5>7i+U!@o@T3Iz$_0ap zn);;a-$(g`#g2P=e*2der-gV8CCOWOl!m6xuc<(fr<{4+fhWWQJL!f`LML0%|M`N} zCo#?7da%T2t!IpyYxZHfK2@uCn6&y?nb?19xFh93(a`9fRV94~PORkp76ty@n)&J` zaEg8L73@OD`c4%2z}CMC1Sp|O+W?f(!BuE)RAYmnO86}#M!)q#bc zvuF2@n2C=u5RBIA;I>~3=-x7ta&$Pr1^8J>-fILp=P0;O<61lObgjNELFvBOV3fM~ zo(WEC?Kx%)+9_eKnEQNwSz$e3f;!%C^B5ar z{D*rmtH-Bk3=(){g}l{qD_?WE{Ao6>V#Ps06e`t_#{U+pVto1!xDJe~j|JhUPUi0;P zVhpRt1CzXzyVKuZlfTe|D&|;l>oOiM|LAyz|m9 zEhYT0;E{M<4;iMtj}UM+*$rH#{9Leoc9jCLw@icV?vnp zZfyxaUv1~LG}@gsll7jCHgL^ST=n*$vOaw!{Rv_>z~_p}ct z${$Pz-M<9cZd|JdO@h}%tEz16UDC1f+)AzLE|Q|;(qxB!Ozk(Sm{Dn}Gc4>@Ti+2H zP&A;SpcVmsUEep-&(iEe?Ux`4Q6*%ZlhRSubv=Z}>0{%okGeAz;RbDgI>DJbI_c$T zcdTBye|_IppqKFiGJX+PVXcxAf)bdOg01>fR>JU{U0v9A1(=lz4)rrQHFa(Xz!m5Z z2`_!VbxFKrG`Zn-bwbL}h6w*b3%L?m-vUv+s(OKR%y)mH+EM6hxOzL3QT)oM9cldV z=~KF1@Iz`|Aku%#Tl$@5GK?VC+}y(FYOd)(=gaYGpR>b9Ua`Cvu7YA1Vh298j}xu_ zy~<1@=v)&(e#>Oz4r^5cS9qlwI#+q1V=*z|{hJ>WIX`RaQqw6D11=L^9=IjH7|s`Z z_~QQf^{rC|S|=YBQcJcW~g3IBdX=?mV za|@5D=3{DhHu3St!>pG_X@SIA)rYc2WQm*S=&7FAA%6sP;W|Y9s>p-DC7rNvEq1fV zqNfN5lKK`cxotJh&d4b%-2N5P#N^YlB56M&m!Sw&`!)9>;XkvDdqZ^t?8W}){X)Dk zEXEsX66ETG274F43BupHWqiq2$BLF)Tt*t%G;B|H4|5Sua6_4&aP#o!+1{k{+-RR@ zK|jN@VXO9DvzCtpTm`5Tegmtm0OEO#CqA7qRaNT0;v^H)XX7+YxGPU(GgDA{{9R1_OEU^DvDE~-#gE7)r{O9F4KVZ!`+mowJD8AbFaaTsym-2)Hjm@6$ z7nN)h<3AG?F((Y)d>8jUH-dqc^L$;<9*Ot9b`34du?CC$mh1I9gX!JB3nRTC(Q@7- z!@{&d%Ks(~f`r;wI9%s;8ZF)Slv*ct)#M>=c~^KA$SVn9eBaZAohp=jSbwbb^>=eW zFvxj14b`U3`a|NNEn(cbLOEysiuFitvV;m(vcPv0�hmhl_sN`#&6iFxcM0(ho_( z9Di5Pdpr)Tc*^rc`_)sUlfJw+3{hwAQ#qK<;}0d0)vpC^W$$fBRM>?T-tXORzEbYF zJMU|7LjE&-enW5tA*V{TKFREPpp?J+H(B4(qg%0t&K;0qi2*z8y8i6&G26#IbKA86 z?=8S?tpXvMZP-@e+j_Jl6w$V@VycJconO`mBU)xHBV`7?k-4I~6Cz{`u68w|N426f zs%aV#DW~1^YHw5rhla?8!sWl1MsbDvRkIxR-AWKOOW+raAIaM-c#J+w_}1+xGv9Gj zgKs0zSsfsJE9t*<$Tw!w-65Nh(C9?BoCJ-0l&{W8Q6>!hSHhw8yd2ykctspOZr^j& zEacU{^(>9zD~rzShf65a*h|3xTqP}}D~JDM5j8nu_lwWiGMoSA`4Y_cxB!ozLXdZp z(6Eb1y!Rol37OqHgOC~PhengrE|_dCAM)Dk@;+G5AF@Id|N3FKbEAK51Hts`*UPR; zRiT}@V1%njJZaccHtm1F&SnU89#GrP{Q`ro)nsD0zPWi&X5EJXk7WJNdUv4z-Jp<7 zxz}N#5PfbgIuqEKI3FSWuUU4T?O5eX(h5YNH^In*y3>E7LSW@&K|bMNSaaT{5PvQtxh@rY>zIu z#Q5%iyXY&*2s6@sss=RV`is_~K8*yL7ok}$0jnRTIU;+x>v47lENJAiC2}3?UNMev zE1OV)Qh`Y2g=4A7fES9Vj{-A2Uc0_`GbTO1Wj1_fj|2A#5eNm z{dg^BAO(T?m1{0o1Pau(-J&V7=nByT2lB8Ebpy3RbUW4Fh4gUAC8L*w_}zaz2gO?f z{BJ9i>lSwZ3w0sP7w_I?5;NZfCJ$9~=KOc#&$S_UX|uxyeOtG>#6$R{7zvuSgg;zI zYFP33iEwam)^clq&u_Wh>l`ii_!!>ot#1w{rP2l;V)uKpdkSF@wxx& zHhvG^O44ALiN9H9`AVLoV6PAzGtcJ0#{Q=SL{r~X)D^#GQ*iya;uKxfB3bz&c{uj? zUy<~#a?<@w%gQ_Vstpx+18pP$M!*2DB?{HPDES8itE=ysm`T)91u%4N2wh-?xOwEm z1D%~+MI~ecJ9qQ=9YBek9rzd+UokXP6yNxuqu-Uuvo!Q$90dg8!1BDVJudRSu9qO^ z!DvJ@&pWXE0b+9i72d6VFNUsRba?-U|IYwkq7C3g*Yz0Md!~an!hJODV(%Ss|4oVT zK`v|;q4}+Gh9)?uZ|nC{!A)4f+5c>=v889?XpBfN@IVh#|69m!-OC@2C%C%+ya19u z!JG`)n`G1qJoyD02Z48MxXb*G^kny_7Ucf_0#@38=FG(p`*3#$17!Mk9g^4HuELgl z|4QBaJygKtqQHKKEA8m&7&zVQvhoMIo(cep+^m{2WKMq#s#i^q6M2nFDqC+QQ!0L- z)AhpTb7qp5JOG45=z({Nh;YN9<%vUf_mA=(3>$m0SIms8IEtiGpPPRyd`5>l&2_+V z2y(!#{4t0t>-u7?zX=3qLVvk&??Bu|(BZK@G2~6lQSPL(^fOPeGa*l*<&2YcEJVsySLaZ0GVm{ZNSvD7HY9-&H8xuiaD0PqljB$>Q#2gy|iXt#cSBpDcQ^6VHJ#>@SXI~sHu zNwS-()}rfse3Fd+qz-|g#|Zb9E!}010Ez6iiHLkJE?hWlXlUqDoxGKLYm%Yon|e}& z-*a9jWE9d-K5K1;~;aO&N) zz9)X2xlu>7A)!Uiu#w*98I-_Z13D?l7u{tVABR|`52c+&2mOT}E->5df&dY_(u*UC zJTXb+W&EXS_&lWe5lJixgfSv?!ACWrnArBl29#1}hj!36_#XVn<e)(_t%1Hx2jA&d=w(lzp4wI`CA9L85d@-xw{jA=1G% z`n1PqZjM4jDJ5>dNk;5YXNHY^(rZXVJ7X`avDYzFm{N(iG>t+vVIIp)40yVQQj0y-ru}P5#vm-#SO>ABVpJ)OAHjl$1&7Hw<57b{N@hz@lJXEl`OR|lQ5L5 zu=M?3X0k-`2+?;LIu5nrZ&EZ(-p#O-VZHRPDSk#%Go4c+W28+eG)lP8oQxHbec3Lq zjVu^qc(k@@%Fc?Sd-0`MJpz;U1fV%f$$ zg7NH#T3pzL)$LO`2p$yl0J`YDVosf-6|GjO%QymLB2=x=K4JqQs* zA&izMGl94HZ)y|Qv>bgu(wI|C-@oW|kal)_$6Pwr^f}K5{43f>#}|Y5XJ}K=Oz9`3 zoU)^3dF5(a{ilq?jBVczd+yvQJXA!Pl0R@>O)#wZ9;;rqyjyJUotY;SCl1LJ<)e8` z`}^}$b~dM@*it(R=}kq|hFk{1HcibQOL@dew8kpXUCh zdlmLA%87q!>Y%Bj@4UyZ*)VpR}Z{5ADSRR86X8|6{9@2SJb_ZuZy8$PV~G2-9&z=2r-qYjf3?a-DwZr+}=HA*zy zwqOdp6ibb*v08D{UDt5eU6)!Rvf$hkhdr*`I zLS1dhmOVb)1N<060`UaJy}VZNW%tL%Y?m;z2wT^SEs(qm3<>*Jfn(lOm2Vi$@vlJ* ziHEiK#7czU9(C@`bK7itzi_dV6Gydpei`?rajTtLZ;7g^;vSN#+M;@s^SX1e;9`

Zd_j{k_buR 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 OfP##Qbd98G=>Gx!)zJR{ literal 0 HcmV?d00001 diff --git a/gwenview/icons/hi128-apps-gwenview.png b/gwenview/icons/hi128-apps-gwenview.png new file mode 100644 index 0000000000000000000000000000000000000000..396c9d282a65672d13e28a818ed5049719834644 GIT binary patch literal 15726 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 literal 0 HcmV?d00001 diff --git a/gwenview/icons/hi16-actions-document-share.png b/gwenview/icons/hi16-actions-document-share.png new file mode 100644 index 0000000000000000000000000000000000000000..f1eea606ecff3545040f0ed2c0f5078b6de900ca GIT binary patch literal 674 zcmV;T0$u%yP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L00JTa00JTblc+KN00007bV*G`2ipM* z1`h~MRX7U(00JUOL_t(I%bk z-*XQ~j4{Z(He6ttCxO;7ug&Y!E3*MSCD`?w}I+i#rz=?avO zN&=t(N|h2tzxZGFZR-+sfpVqHsef9(?1*dXha1T^}MeA=B&A40a6y zFuOTR6h#n$ghE%mlS)*n3G9XBdw-DZ9c8S03_tRj+n%GZvyX{;69B~RIN6gdjyM#G z1=4$Iz8~k|FswTGd~(3Ew@;bcoT9C@jhG!HbvIS7cl~&s-Q!)B9xUNl4ubIV-HS#E zDrOoxn(fhTa;G_#_m}xlc#qOnlYpf_N%f>*_}VZt12e1^ zR&gwcjfWd3OObk$!u{={eT&PwwIQHI5DTyX1vx)Qwv=VEdlG<^{0a-77H|W%%CQ6` z)iw%`FkRhOaP1;~EPxikX#&8XCX^xODXs=>f4OR6_{(HH1xZ1A<_S zZj^94eh15|AW|cHwO!7}4hR}Cr8RgqaD*6RkjHYdaei*pU(hMjV<>?tr2qf`07*qo IM6N<$f_~s20{{R3 literal 0 HcmV?d00001 diff --git a/gwenview/icons/hi16-apps-gwenview.png b/gwenview/icons/hi16-apps-gwenview.png new file mode 100644 index 0000000000000000000000000000000000000000..b2c4ad662fabf9de147a4232ea35d4bfe72618a2 GIT binary patch literal 864 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 IM6N<$f*T`kQ2+n{ literal 0 HcmV?d00001 diff --git a/gwenview/icons/hi32-actions-document-share.png b/gwenview/icons/hi32-actions-document-share.png new file mode 100644 index 0000000000000000000000000000000000000000..34dc6aa60bb025a199f24cd0584accaaabb710b6 GIT binary patch literal 1364 zcmV-a1*`grP)tDjZ_f= z5n8HJTAwN?YF|WXL0<(Q6(1Dzq2xiaiYNw!27S^M+fu6a1HZO5sIfsCvzwS?lfAiL z(}%m++}-TPDxv9t!@bO%Isf0BIXgQeq?F90DKo16e*n-58-x&oFHwl0u?#USD%X+x zjl*d?S+M4l09sD0YOb6&r~RkrKQ|0ogZir}N%D{-2n6NJqjpPGOTzqObQ|z%aD)f~ ziLZqE+1H<6|8DuWOYS|sM?{ajntln(I2k^ezlgoR?7#Wr59gZXDd~=XE6jj;wd+RX z?{j}gRTM0DY+3*Sl6^_8KfdukpaLZT(um%^Ufp&rY;W4M0m#@H(t~M(bbejs11ta} zn9dl8>D?b}&q47NU!x?zFoKB0bj&FM4Ar2%vYzTlH3C83XdhkuU0AMFa*X5J6B;D} zAe({W00P14`KxJtx0Q&IU!Wt_2l)V)?~Y@S&o&4!o}H1GWdMooSh&KSXcEx2pe-kU?9MSR zKDme|{Ynk%VU|`eC1Yn8c89^sPBt(HM+V}R1gP}sY-wY=HcQ@ILP!sxC<<%eTMIz^ zuQ;9EohU+~Db@tQwccx7etJ1yRN{kppa_Mi5y5etoJQ#kNU&WS(>2-Jw3X_z>fEOR zbAa}{?O0eGTzQb1Xbk{8=^l1m-jT})5<+T-+S#?3mPs<5Wbc)|q^;qXWFUi}0up`q zK@%iW2~wF92rylf_IvFlog^#XT9Fe6sEO9F>YY{G>br&Q*ho*(KibdXTZhS*8E~LV zub9x7G6P7SrhDjf`dN8mC9`JDA`*^J9xBHO8K|mCqun@Z)Ya5gj&~hr+1zF9Yu-mh zi?I9S-8@S_WBsZ1Xqq}%syF~>s^YJq3Z^tMEt8a&0^4tU@ZbU48n@9n#|ODTc%PF` zPEr}Er1R5GV&PaWR`*ahsGuoov0q^c08RA^NX@#I_3XQ=x-7o1n6)*2VV>(bhpV~l zU%sDMIK~U>1zX}@$jL77^{m^a3y?G4nG z)seK4?7Y2`&A)BN6JGuVL^fuT190&gPzt!jgdci%h@;Pr(i&@}p}YYpC5fQ~@#k^U znRH&?DPxudkf4dFkAi6@&Dq|wkjgeVS)ao6635HpHBi`D20XjH65QI|Kzp!nP^iSD z^79f#MvbUfu7)Q92Hg!j$1B{eN$W{Oks08Vsn^pE=JqDEB(Q!yv@ zf^iQNWzK^8g^`5_c*rx*tqKgsxN8kqd5e{QDi%WM-{>FBtExUaWDhPJu|_6W&THHh z49)ODgzgLv4R7LmZUpZJqVR5*y_o1!q80veytajr4B&7YyYON%3E;oB8NVCOIKV$D Wzxvi&7q+1Q0000V*CGa3ZJA_Bcpp|tb{ZBKh{ zJ(u_T>=YU&5_F6|{Jweey#KuK_mk)M`{nn0gp`smQwm?QG|oo?G0ZywFIhd0rH?~$ zzFcZ@4m#%$Oo&bM0{CyWaj&;=t}(q%*!D@xtbu8nriFdL#BQ;$3d?M{f>-Oj-!+PnGqA8Ru|KNlZ^vL~bW%+-9gLA1Bx@X-WTBF|WPE*r1R%Vf|{bDF7QVYxYJxfyqIxoXxC-U+AF zmX3Dnbe!kYcwW0({w(u_056FZzFLZ3jyBaUtGHn%vN?gb-6YxQ=JyAV`i4h`$;Gn- z7t~WUw+7cVv65+wc#2Fc#l+AkMmCG%(U8$vGnn&^PLE5a$U?Dje)T=Ma`jmOwu=>7 z9QECby2|T(Sq<+9BMOYQ8#icVb!wxo}N>abo(uyX1Yjzcv*M$jsU3|JgRdclM-b`ZPAWN4nMbk6@fLU$t?Q5b`|QHx7oI7 zD?U*(0`@Cs4Z&+-`E3%p!G}l4Mt{1aE+5>K8szeeF666?HK64g%f?6<30&7fQ4~~F zMM_CiQxi*;EP*K{bI>1$&g0PV^80LmX|t@H5F>w=TQ1zd-q;x@@VfZrbQfM0ATkY} zRPfOoONP4lu*mlFmAUf(jbu89k#;!J(@tr`hhgN|vuDpqh_!3i5(86_i@J%^{ zx(6A4VF#tbvQ(S=u(@Ea07Cn0-0evQD6{K=iLzhZ9ulY+^%DTE4kbQtR<&b9FL1Mi?fPHYv=OiYc^W`J z_KyWz?N7{eopH*4 z*iGy7bau3RDEl05{sZU-jMI97ihZ9ziJIT}wryQmd-B)-CMnz6+Q{W{I9394Xe%XF z71Au>cv*^VU0cXY9fv+5H3BOmsR&k_{4UWmO7Qk0jLtNz(Y?L;v#fhDLAnh{N-13c z1z@%KkuKmd?Sjij+HYx`3@N$_ISU#a8xcaB%yf^gqGFLvjUYrGeVRht5Tr(XsSZL- ziHcWMm@{V%Lp_7|@9DuV6>j(54t*Uv-rP<5KA;!KpVp28M850MOFS)gvO0-MvG-K@ zj7@-4yewL@2mrs|4}jx1^!N31^w3fIjvhnU76Dg5PZSf7K^8B%jiQJ9(K3S8mR4Hn z-nWYN+kj3WB?~$Y0K^ZvLRRS=^JWp7fP;f@WEA4rQy?o>uB5K6Zqj%X;#AFB5_L!? z+pja;=V#<_HDkTy+&Jq77C!F76UXh`-zLZ0v z7@fdgAU2tJXKlv?h$mB|9y>tyi)jqAiMk~bLURL}&x({(n9)d7lvbB$o=8CV7^=_> z1(9)Z(w1c%nGjCS(oAf+89eq3Z*2J|+xG(>107T5&gTy#0yL1{lczc_0Z4 z06jn)uw+5Y#4`1MuBh^&XRenICq8!~dPn>*>fo*et`-&gnC8((x(B=Odyqf313KUV zd_Z98-3BIr3}6BVU`)ol_50h3D!aJjMElT<@k~P8z(e;-DXlLY$<~S$o*+f56vVyW z60aL=s5XqDiJtt4%xVX7-8DQi1gL-jB;WupAaiBgYt>nvdmN9rHaVOK4tDp=?_%Kn z2l>N8DU6%TNxBqW9aj8W7>`mW z{7P7ss!HB8jp6P=Jw7;I=^~vS9DMUpo_hv3Sn!%Sr=hd};d_F+7GKQl#|z1KsX!1Y zD<&K%B@_uu7uPh?dELkuijm2ai%lRLkVvagO}kg~uMI66Y6iN1v@GbH0Hz>xAapO^ z`sQWKxqBMrjTMAz6@()A?*qsTxv>t8wLZuC&4)Pl(Ufs)DktRseJ>CN0X`rMR8J#P zb2*Jo0ufeGy%NXRO~=vWjK`)7dw}t&p6{N=&FBme1<(NfGrRezM6?0(jM_hcfU}>D gJddAz>DTlB09J!K#}6Sd6#xJL07*qoM6N<$g7;WEc>n+a literal 0 HcmV?d00001 diff --git a/gwenview/icons/hi48-actions-document-share.png b/gwenview/icons/hi48-actions-document-share.png new file mode 100644 index 0000000000000000000000000000000000000000..46cdf53705bbcdf2c780eb9d8d2c14c029b9ad68 GIT binary patch literal 2120 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 S@l2=y0000Qzq?G(WLz!*^rW>~jm~I26 z8@CCVZUd$pno~g`gkTE-fbU1kTMY|HOlez?(&6+bvdIY{1W%z*jEe}l806qV$8(EC z&mabajG!=#n<4s%@)1ThBCe@Le93GKXl@=@1E}dO?N;(CF7RuEbSFSuo?IK zd+RnXtZ7`)Sk#C|_aZ%#;i@Q_syg-PXMtfzTsdwumr9?z7e%To{q^Vn(0#1?9kEUf zS`7v3A+QC3AFubm{6D$}a=Ftzmq8iA7xW?Rx?&ok3@R+FSV+lm3BGVEc}3nA=;H5e zVF-)(u_oYq1Uk8)@Ib|d3qRsl`~)K1H_P@Z!^y6bET6TU%NIKsibiG-C)aM)vQL6K z+PRB<@42B1)K}J`D7WV9myVbbBNB`9&;NYLkT#Tuj%JqztqJHsFb9?4gwuvJFpOI> z^v6S2G)g_CTnb$RiAvA)Mu39CX#R{DF%Z8D0wfX@1!5$8uz@`R@f35vBmxo`H9c-W z0cVeZ(d79{B0vIS%VS9CTk+`!^H z7Eh{OU0EGH^c=K_PqAp6_)v%hVBrlaYf1tbH}NmRtA zC;}vMG!Kk}NGm0*Z&;5=^D-Kbvh~l z#)ukEh_tx?x8|m{td^MtcW^awHBk{mA?JDs=ttl|q&mmQAYf7DA{LY{uzoTS8VPZr z{Qxmy$)10{{(08SU6UbKUs6wfNj*(%P3%0g(>nZ32q|%wP+wk8O;HVfLw)RR-OIbp zf8**e0M&dwS9&^=tmI>LWtzX1_8n0AYWYig07*i zq)|c<%*~&hm4NDkYVNDN50~P?kcnY;dEhcFy)C@wdyhakkctSizBNEkKd_yuy?`MN zg2O?GK_D97^@Fd20$!Jw!rVeUIUe#{dE~kB@akSvRmG*bP*oLGsA#H|F*{hQlm^>R zZ)ae5AmO9|H3c<1bJsJho40Oaa+Gkk_bl6wZch%ij0m`)m0(f}Xo{Lppk!X*c$n~D z7$Rxk3zJg7E4{C#~2Bz7diP7+J5C)Fl4r{8K>7)~v*lA(`)=jJ~*q4#&U?M~%~(aYEC%V?)-&fnW+ zCt#FBO;INHwiy{##H7hKbvDt|*);J;OeLB4mX*B2$rX21(%oCqKk(&ApAU;}7 zh=8dgU=+HdW_R8y5=;^zBC*3vBCSVj^K1EJnGbVx)lrVEI>t-$UP>;c#`410ct-(mj+ZwY-r)G}j#KI?&CtK=!Y+>Y z9?u?ilO(`l1jva=Fnt$jL58mC>|DB&s@y67?k%~OE%jS?xBXpC51i&)_#CQGS&+Yg z1^Ekjvg%1H-IWux9}OI3)3HtFx=d`))hQ)lY#oqV=~+5H?kdVDqB1wrp>kI_oA27} z*uaBb2YLL^+_WIInyD39J9~%N`icHJ2t7o`}ch0}#*qHJ7u>C_;|9dr|c*xRO zvrZ7ZFVk!w3g>FW*ozZ6Jd1K}1YA*?c%pq%4+UZHd6t4uwra>EHvKel6h-qZ0 zck)aNJM^0o0TYJ6WSlkY_4Fw%z23<33_IOPTb+89jR1+Epdf`zOf62FGz)csIRTlv zCK6yaG#u#Fgdq9OLE8WLz?!RKBr4J3oZ@I>Wg|1(CFrQuJz*B!W<8sp?G2NAt4J`5 z3=JCN|a*jEMiT-Ll_9I#YSRXg*gSB?j7N+C6A$xowsG{4a4M$ zo7|gyXV%t1@9RS4tzSRF7ye7QRX4_n(TU(14_Uv}wH^Xzki-ZKgs#0G8VUb76s-`a zI$~^H^m~G_V2ZkI8x*t8b{HR3R-aAoJtCKVt(WK?j1q}PkG(<( ze)+bHTD7w$KnTIJcz`)tf%dG+?OLyEdQ~tIbWQ5_!Z+D@03ie})fF*DjE>lF>;ofY z>;f+G4=jHu4Os{P5D38|J*`OeaCz3XTYs3f;Ajqc-OY9xY zNr%kznyr!I=O6002ovPDHLkV1k=2eQ*E( literal 0 HcmV?d00001 diff --git a/gwenview/icons/hi64-apps-gwenview.png b/gwenview/icons/hi64-apps-gwenview.png new file mode 100644 index 0000000000000000000000000000000000000000..9cbc080b8421d8c420e986935cc686c541ffdcfa GIT binary patch literal 6077 zcmV;u7eeTXP)|%=Ap(tGl|Z z>w50ro;f*BchS!{fCIAo$Xn09-*dXUs?Y!be(&Y0(=DWw{5G2cydl6F0=yx>Z?9>* zo+h@KHTLeeA%gJOt*&Nh1bJz|YiPgOE+FpD`}lA3k()ywA~-o9&_YiP zf)^iKq2J>ncT2q8;qj0pczPq}Z$%>izYt(n+`dA`{3{dn9R|iGA5VD^Sp$NUAkZOD zCBEh2HC=qy!T3H1t2VQGar}yl~?GIRPfb_q)LOp9cEJ3^X(J7+Di}N)EXx z4J%CoB*CPGH`2rp;f{tFY2Z5^3#%gfDqk!_^pFeB1^C-Mg7120*S=GJto*tPuw7i8 z(dhV;fpU=%QZ_W5^tr1r&gw6)Pd(4_}lAvlP!G9`_%*$ zNstTz-<865eGy3il?wBu9}3YTapMQ%CtI(n05^-9)n$~vZlHg{L?i#s739xZiz*Zd zBh8S7@>mW1fRAuqe4*u|93}=D)RZO-uSLC4r!iNfHdVoExc~@7plB*0hF-{EWOJx` z5>*;fYhV?}T|e3IGVNl-kJ<3}Ivc+AU*+HKdDR8DLwqIMfqA2j_6MeB(DUEclitvY z42jEK4cJ;oEBTmG$3Vj_NQT!}8CItl-m{PT2#8Dw4rMYzKp+o7B17-TK0ro~ zppZxD>X%u|nC~llexWIg0aCR@Y< z7m6;UMTK(F#LxJolQoioL;668jk)9bPwU^z@?`_40+bNC2AUE>1tFm6gs`DevzZ+l zWoFwRrXPQn`K{ZqY7Kl(;`<)5RSD_N>LujBPZbrbxTgBv*?cxYo_a(p;Jf0ol}W@C z23l`ApC(nz5{$IaT{VihE;P!gNPf2SLA-gJojI51x&@6wC{#90HWMSIlS>Mcnomj( zFm;J0B*!1Oo-@zf6`ojkLMpFyFdMfcUkG$wNd7!YJv7P&QKIgH! zFA0S#*<6~A5XH2hD-DHoK*|v4x{j)>r90Q&8Bb9e1F^)4H2jy&sWZuDxObZWbR@ZPwih{C+!W zGx9psQ#J12@et34eZ_`lvysc4#I(YEW%t9#wu{mV`KdMiN|>0##<2*T3v&!Q)Y~$gMY(^xctL1 zv{zgMLh@G~B+nA_Gm3oXglRD}GRk9XQ_Lq4tnD6PWmlfnohk!G6D^@(2R1FY6?zNL zcX50NV1cw@7?F1zXTksQhd<=pbI+x}zn`V1si69C4tkyG4<=!uF{Uo3RKJfAc_@20Ixj=rM zMT^LL>k`3}?oQ>cU%Z%^4wL7;@lDosrTNdx6RcU$Pd3*92>c+3ijiaa*exBau2ZeJ zsD>b!Hc?eU5Ck-vO-4pWqEtvE5?Gc6!1nFiS+;B$=bd*ROHEf6$A?I-5S@)0bFt)&nsIWkF)Y}N?L>Nt*#ZQD^z*tc(Al>g~; znnt4$rNcGXT*KLCpN(mnw40%Wu7au%A!&ef^CP79S}5cg@5;8aBc&B4=nfA&@W7!3P(croGXuQ8`y~l4je^2< zwbc|-q{b-iypOFrfhy2=*#c-NR{~uNa6dBT!JWS#gM#E1T*Cl zw$r3MXVS2f*q(-uie@GVJcUdyOWn0`6cyD_(RGbNAx|P{k{ipRr!*2B3D5ya^q$0B zV%xTD0Eg@brWWA-e2T7n=A-D(tjkDc_b*aDg4=oM?wI_1ybAoow!ygSuNLrOVpBSE zR-(|Iu&?+anY_-?-H?sbu~w_0D1zD9GLw@tRBD21Ge@8gpryJ9go&<$Zko7`hY(QH zyJ>bs&<*@#vdg}RM86_QWpqS64ZU7 zEi)o2lmM|9(eKv2UnX_^Gyo%S)@1b;`*ncFc>d=P^W1J=HeLZ%y!;O@fRFqP2m>Iu zW@BUWJtw5yl{wM|XzJpwERdi;)UU_J#-du_x^DCwev7~=5i~pkdy0hYBA@7CW_AiM z8HA{^Z9HIGSn#&(+sjxj%Y1zr*PS7oR8geFAwUg4wgONTG)0SwB7kk%Q97v4w@80t z1c2soN%lVVcqCMX+T`DJ(~siFm*eH{0`jnJ@CNa@eh?o8Fp`D&{NS-2&%R?NoeHQ? zwOt%Kg7~fS`8?%v`QSxGQORU7xUK`z!MEm6wK_`6M%J2ir3B-%2M~D7&Nlhl_x94$ zouRWsqWDcZk|NsIYbil90O<-WC5Xn^qmMp1ni9i-DM~pp=}`F4D7X@TmC3WC+k6qA zN@VaI?z*XkJqOGHb4!cg;a0$30||i=tXZ`=cl(ErLj<72eO6P5%nOKM04rCnjK~K; z@S{=$|C?bDw9x%xyz-`r#cAZ>8#dBRup&&GYr)oG- zMqOaTh7DA!)hNwG$)*2Krcg&+uaq*m8=Jol-RV+q1bFe>tqMr$uzB<5XhMtnb)1{wd6W)76tQrsi!R>B z#|%uAeg(x)uv$(0mW3KcUqcrjDN|r0Kuu6SnE!!>qNtpH`svX?#I(zb50}yQxX3<( z?cQ);mm4a;DARktz`xuc=lo22?!V-{L4o%vAeBljD?OLrmwkHEMqCN1h?hR#c+hY{ z*kHLtLUeR=L<343`Go;S)3k;Cy=~i(D3)zeo}QsJSz>x>hVtwzwq>Df8q#;sg~SjF zy04)*34*3TL+9A_$I{!|i#yd~`FS%KLoRgb3>jlVW1^}MF>TEJHQ)Z%7Ir0${4DX{ zBSSl^0QZZ_G>{(=5NKU1I@cZ3PfZ$8w7qznS+}9mg82{)XHfuv3of`IA{dK69QB0{ zJEW9RyYf9BLov}k6XEGdU&FQonk^U46L^k-+t88C9MwsQ=4Z%f3Y_uwGY~tPtUh}d zbC-uaD!~isNm)BkiTZmKQxA{oLBMYx(3SKe-U`IO#g6eIS>IpzkA_3wU&Sr| z_DhDw;;2u>A~X*Z{TJ5&2_FF=yMk1&Q?KJV78hAFd@Iov5jIqifFFQRh|ZIG{IOJs zoJzC`9(?dYo_OL30Hl;riwtvW;TS;r62(@K^D4p%a2o+FLm)JRfF#w1Mqq0=wok## z@x{`ISbm)ax}dh%VB~=v_yiGoi41PwC;xss5AOsjG5Hcu1)R9>z4V=-e-odd8u+7Q zdM57}YV>7Km{3;b2F=Ym{jp^$d8)UMj0v4-C}u*W+mIsih0q%s(K5gA!V6;&7ERN% zXpb+1h{TBR-=LTZnh`=(5P?8?;MzXE6YwtgWIkUzhunx7Rr=|CgSl;!3pYKQ*w+8% z<{NM1-tBR#8w2(OlR#}T`Y%I(Tf}AWHV4u_o*SPnDJVT4yw3Eh(R9Z^S8LGB)HbiD zbXqrBUMFP`LPkBi5R=cRh&=INbm^s+a>pHaw14E)ejEuB?d_F?aXM;$fhT!uWjzV)D#Vo4c5AefJTzkH(|!I4}ZCMCWnadqwvKe<&`$+r$0c z5|xMZpxW;g?`P`3g?#mU z!%WS^WG9!9w-?F3A_2a1w1B5vywnbcA9nD++DqY23uMkzP)q_2!DZ~<2Y>Hx|RFxyN{uvp(sV7Il)m`Ut7jWwY9vxxq*$= zD!hb*<5C0}h3XTNcvckgHu&;!c5JviYoHBn*^5fF)5ZP#Y4h3ZmxY2mWF2{qJFX_$qGx*)2RU47fluE_kIl>eE22 zo%|6C@K55O^j>Bj5O9ix%P-kgo1yZBe`d>VKo;m2pljeAoOsI7tT=HseaGj?bVlTf z#W;*NV=oYgB=6vQdzl=$hPxj8XYSq&_;J+hai1^6C-uTSIiU zaCn;C7Rb9pF+Ot-TP+|5bi_V;3dgSA%$rVHN$;v&@;%)Yy7Q#7?WC8Ap`b+MTi8w* z>5-?|v;SVU?z^3bMyoU(zyq9^WHlxqPkPJ)t+>?%ugTvC-6gJkH?Rdz96a`ju~s5u z+Lc=E&oANHEkF`T$H=rH1tfdO=fZU9KaRoP4$?`VAn@Yy=!S4`{V`^nck#%Ch2s;? zT5ZHzanvioT#RZgh_iHiD?$TAd@2vx5eWcrr?~QS3gS!gdorcYdD-Q8tui@t>!>|< z(HFUH3NQe(U62?lAQ3;W0~(+LN`yrV+K9XHa&N}uYruSb+>D8P?F3`u${DN9(032- zR_^5T(_2{exACz@UgjWlc>9s~@+AT;6S25;(WH1>pR7+;y;`aAkCV)OCESITVsd&c zMk0n8!-&x?f&k^_5lg!MkAWXN$Nszif^Rec76>nT+B|@4Ll8qA zg0v{5zu%KTan1Qkck;ddXe&4J%YDsxZ~g>{P{x^g^rKw)#c8U;af_@U@d{XK_lU3m zks!E6Ai5SA2>Op-#=r@yWPNy6)OXL~&sa?b?5oJhqmj*B`dl~ z|ErF6Mw}8H^hU~LU_;mlj~$Rf-4m_d75u#oltzGtHm9NN(9m~jsG(=KKy_Y3oFEOX z5-5YB$2?Z7FxQI#a~a)+Z%@_drgu(=((q)b^d;@~5ZiA1E57-CU@RtIZqNT$T>v43 znkS$AD(C;rAbp=wQ1nF^K-E?Hj#)pTA9Z-~vsYu&-aKlnSox8)GzJ%HCr009_4{(R2d{4P$u zsEhnt6oiN`F#^yV6h6vB4mN?5AZvu{sE`_=^#C)DhQ?h zvfI6?shIg>bS4A950JjZZ8!)?`zsDfeuDDwwfyMzTY2bNU^*^#lkt#bwYSOFQh;_5 zG#~}!fu7Bru;Coubk$NIl4tpFS$4rdv*~bZp z1SQxczyLu>>|ei`g{we(JF-pO=f^F)7w*o?VY`T4uhjDmF;XR~;bXUX*_%SV6x^x^71!Z5fglDJG}!Bbj31V3cIt*7xS z2|hlQ_`V0P|++%h~jLRCicyiiK-9n@*BN zvP`maJ)seujJoF2uK5&aPal(MzFg+n+V#xV-*}CS*}QFy+&ppDh+1nWgiRO=p+&gf zW@Y>w_PYX)LtPmmWCC?v%hOt2J}&@@gUq;Ee|CU>1^>4l8bY+rH^nrWL(e42l5+Cx z&)>F{A&F+?%p0abzc+7BC0V>o)~k4$tS1fK^?yodbhi%OtBt20=ZkUA5~M^+aw5NS;@DQHJw$kyP_f9`a=}i8n>ENRw>x z+bmf>x?lH2yn0Bd>&g97l0Bx$(DAigY{dl{u4B~ znkDl!HmtOPCE+Td3alxKi|>ng2K<&)qpDfIhBL0~KG17j=BuVHsG;00Kz1aC(|nN^ zm*33i$(_0TizDS=G-;B11n zvt3R4VxNhUQd*pW5+O1n8hGsh%C78{8Ih2$-gi<%3GQR`Fpon+#0YDF; z{n^g2C856@N}QgB64(C=lsNyID~`(- zqSV9#U}KR?Vf=XU6mQp~V=z*3mV3XOo0r2#*_rNrf3`DVq~hzrD8xa}c4FK*9Y!I{ z*Nc+iAjlV6vx1x#CAEC{nib^SD5>v=K4G?Dl*mh?Fo0y4};|Ly^F4@0$TQeYE>iz%HcYb^44OoQMc8&92jQi&?jcsF1u% zUgm>s=YwsF>ycoQlvWtk67Opce#z1j_K=%(Qv8Jbu)k-&tdq9YdIfvj*^f#2kR$KO z9Od5|S)|V&X{4>BgwipA|GH9WBdNKfO2H?OEZ36pi%uEYuOVOTzePLkp*0yDCA1Xj7 z)lINPDvt=zrA!%P_N)|~l(lcV z_`Hqz9}H9ony{B zsumH13@Y@AT6$Hncaq}0YTFZAL88lz_QZ5zOB6`cp6mln5fKt;W~`C#L3S_e8IoO| z%4E+lY{VGkL%MJ781WE|zIp7obqSRQtKr`H=<($V=`HMC6vagLP2oD#o?i>DR~23# zO`w(OC(zV_(^Fc@o>*Y~E>qS$u?&IDqgK}T#8N6U7PcqvVWAw4H_Xs;H?xN|kGnGP z8QPoiu<9-Bua%}+zM=G-5qqZezY}el0&8Y=OfOErOQV%=M)e7B2?bPRhHnDVN8SzVvvMPV+S_NA|s6C{q#dYDx-kh57+KQz}vCX|Jr*0$O0PiuXto}JS_oc&W%mamE8)$i0UUVN<>5}D*C5?18vb5 zAc3=rG5>1rgW)h3-Ky}G(0 zzxx${8LDZ)Lmu1NNHkc#eu!uJ6S%D1%FlTYZj*t}5Te_@bc%Tf=NDOPxoEcvUFEj6 zFfP>I!0o%KO=Y{Q=e_QmqQC}=c$)x-?pNcrx(94+KkpG+OIttWPlzg5K)h&`Rwb6f!maDp=Gp8VU zOxNihUO0M~LE9__;%tYy&f-vvU6C*UQ6xmDpOUf! zZaS|qD0g0@_h|+a5>J+jxa||q0i$sMxRhJp;4nH+<1JL?H}~k`YeRiC+4qKyzD&w^ z7MF478XHRU?(nLZ%`gA??RTxKpH46TJ1>6g+%qr9@`Ke47iyv?j@3OoeG%Xj&CpTyH z*+?;|#b`o^C*{?o!N8U7ch=}4zDpL@qg!-70o`4}_eH*0E%RAYySq_`Z0-IUSi$MH zI;?K;yY~SQ&=!{<>c_oY(bx-D>pdn|mqq?ta+&2>Qh!&z>ykG`90a(#1m3clPy97c zvpy9d1;+~Br@RymZ5HFp*dpHc=IC(?ohp92lKt%kH!Rocbd97*afuX?1cx(6Bp}pbWO$l1n-k zD&m+q^S&$mVVYA8Xl@TKXu)*7jrhv~yOh5oU=wJf)1r{+5 z@m&oIps9q>LlFsK18j>mTybbefh+8eV}(>l+IU4^)>xP^WD+>zV-qPRf{>GlYB~lJ z)sh8&3&1(T#w>Wm53Wt{5Wqk}8ITK@RMv^f4WPm->Nir#LcwU|IFuaG5CWSq{InE5 zVfzmS$_*A2;XY){FG#;&fDQajtI36XkH#djW~GM7RU|D97|6}r(&u0qfT?lN83UA= z!686dZd@1$02G2D5C9>kcrj)L7Az=-M~ch9i3lC2aGwglnYONA^%(EEwJQ)TAR1Tz z9l^i4>W=r|6#6f!cFYOHXf;3Qd<>GHR!|Z#2G$jb9y&?7A~J|Z5*mE|gcq|#_CMmWeV&SaTszbk_a(a8814Nf$L#^9hYfMvm8!J>>p%t0st|4I~u zk|cC8g36{~tduC%kOj2j8%zy^0(&v6Euh)K0tjlW3(@R&;H(Znac0NC>@0Kw=>8Q* z2^dnK82r;O2I~B=S&@Q*z)ppD?S+FP6ap*^#zx%h68?gvN5%@x5*U@1S40;0O<2&3 zQ&YogoTgMaf`wQOgw>YSblOhWlt1^ql6xCiKJea?*~xvA;Wrh z{s~5)VF4K_tQ{)}CZ!eFD+XUT?ieh_x^ZqCQ7X^> z>u$HM*m84I6Qj6jO21N5J z0#!u(fCWNahCmrNwnvi~z7&5g0C!pZe0XnV6h5&vsxf?V#PF5);~D;b)*aTb^&yyP zv~39c-W58b4E;6lQ-PrHzgF8oC_JWiUz_msn9ef(XiqnQDK-Iq9Im= zTdx>X^A&J%UyTnkKfAv zIT$#GVCoC_47KAkz>f(fmB9sRwW*MX6$>GmV~W*M;S2mQtD+%9YgpPo4VSY3NAoIP zV}MByp}m#BnG#GGeZ*bpy)&6NUk5L67nQegPJ=Q79ssa8;4TjOn$UMP-Y5srrCL!X z%`tHQut~`Ofz{xxb@S3Jq#Z+ffAdqj=P@VM`(EduQB>G63+4PXf*}~Cvh#K0=8bUc z6FZ9Ms5_$Ptve$4;bv@66`EJka z4~L_!$Uyo+k7X`O?A^D#hoC%oJbd8T^7vWj@VV&V5$f=1>*3Scg9p0ZeGr$2lv8iT zo%qAvYVdkY@Qc1i=$m;keDO4NXk4A|GNy+@hyQ>)q}{pg&Z%vC*5Ndpa#pd62! zsDBb5Bq+~xMBF1wZm^x`4g^}7@y-QWqHVY59#OWvyGd9xNyiLpcj>sw-C00OL$@L$ zB9-)rlo&UMH$z9VEqfi!_=g8IO>|w`31~Y(!w2L11B{gm@RU(J$1+41U{ayM zNDDNyT)_wf8jBQR6+<4CN031gH>SYHv>*l_Ylv@}YNqiV%rGr+OpO-=c<6yAUd)|% zfmnu{8X-LM0$+(#DJfOd8745KB+?*W8a&HIW5u~qoVxd|M+IBw-Iy&E#^l8C3J%zX mTW9T*Sgi(C_EIzE@7Ij$LtXdl3Llt!`tUyimUvq(PXGWQ2w&&` literal 0 HcmV?d00001 diff --git a/gwenview/icons/hisc-apps-gwenview.svgz b/gwenview/icons/hisc-apps-gwenview.svgz new file mode 100644 index 0000000000000000000000000000000000000000..24b50b215a7d9f5cebfdfbe4c37fa262f296b8ad GIT binary patch literal 7642 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 p$xZb`g%VJyqry~&dkpi$Mp38cuR+a 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 + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "dialogpage.moc" + +// Qt +#include +#include +#include +#include + +// KDE +#include + +// Local +#include + +namespace Gwenview +{ + +struct DialogPagePrivate : public Ui_DialogPage +{ + QVBoxLayout* mLayout; + QList mButtons; + QSignalMapper* mMapper; + QEventLoop* mEventLoop; +}; + +DialogPage::DialogPage(QWidget* parent) +: QWidget(parent) +, d(new DialogPagePrivate) +{ + d->setupUi(this); + d->mLayout = new QVBoxLayout(d->mButtonContainer); + d->mMapper = new QSignalMapper(this); + connect(d->mMapper, SIGNAL(mapped(int)), SLOT(slotMapped(int))); +} + +DialogPage::~DialogPage() +{ + delete d; +} + +void DialogPage::removeButtons() +{ + qDeleteAll(d->mButtons); + d->mButtons.clear(); +} + +void DialogPage::setText(const QString& text) +{ + d->mLabel->setText(text); +} + +int DialogPage::addButton(const KGuiItem& item) +{ + int id = d->mButtons.size(); + KPushButton* button = new KPushButton(item); + button->setFixedHeight(button->sizeHint().height() * 2); + + connect(button, SIGNAL(clicked()), d->mMapper, SLOT(map())); + d->mLayout->addWidget(button); + d->mMapper->setMapping(button, id); + d->mButtons << button; + return id; +} + +int DialogPage::exec() +{ + QEventLoop loop; + d->mEventLoop = &loop; + return loop.exec(); +} + +void DialogPage::slotMapped(int value) +{ + d->mEventLoop->exit(value); +} + +} // namespace diff --git a/gwenview/importer/dialogpage.h b/gwenview/importer/dialogpage.h new file mode 100644 index 00000000..3c4eb664 --- /dev/null +++ b/gwenview/importer/dialogpage.h @@ -0,0 +1,58 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DIALOGPAGE_H +#define DIALOGPAGE_H + +// Qt +#include + +// KDE + +// Local + +class KGuiItem; + +namespace Gwenview +{ + +struct DialogPagePrivate; +class DialogPage : public QWidget +{ + Q_OBJECT +public: + DialogPage(QWidget* parent = 0); + ~DialogPage(); + + void removeButtons(); + void setText(const QString&); + int addButton(const KGuiItem&); + int exec(); + +private Q_SLOTS: + void slotMapped(int); + +private: + DialogPagePrivate* const d; +}; + +} // namespace + +#endif /* DIALOGPAGE_H */ diff --git a/gwenview/importer/dialogpage.ui b/gwenview/importer/dialogpage.ui new file mode 100644 index 00000000..7d90f287 --- /dev/null +++ b/gwenview/importer/dialogpage.ui @@ -0,0 +1,116 @@ + + + DialogPage + + + + 0 + 0 + 400 + 239 + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 178 + 20 + + + + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 12 + + + + + + + + + + + + + Qt::Horizontal + + + + 177 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 55 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/gwenview/importer/documentdirfinder.cpp b/gwenview/importer/documentdirfinder.cpp new file mode 100644 index 00000000..e0daeb9c --- /dev/null +++ b/gwenview/importer/documentdirfinder.cpp @@ -0,0 +1,114 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "documentdirfinder.moc" + +// Qt + +// KDE +#include +#include + +// Local +#include + +namespace Gwenview +{ + +struct DocumentDirFinderPrivate +{ + KUrl mRootUrl; + KDirLister* mDirLister; + + KUrl mFoundDirUrl; +}; + +DocumentDirFinder::DocumentDirFinder(const KUrl& rootUrl) +: d(new DocumentDirFinderPrivate) +{ + d->mRootUrl = rootUrl; + d->mDirLister = new KDirLister(this); + connect(d->mDirLister, SIGNAL(itemsAdded(KUrl,KFileItemList)), + SLOT(slotItemsAdded(KUrl,KFileItemList))); + connect(d->mDirLister, SIGNAL(completed()), + SLOT(slotCompleted())); + d->mDirLister->openUrl(rootUrl); +} + +DocumentDirFinder::~DocumentDirFinder() +{ + delete d; +} + +void DocumentDirFinder::start() +{ + d->mDirLister->openUrl(d->mRootUrl); +} + +void DocumentDirFinder::slotItemsAdded(const KUrl& dir, const KFileItemList& list) +{ + Q_FOREACH(const KFileItem & item, list) { + MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item); + switch (kind) { + case MimeTypeUtils::KIND_DIR: + case MimeTypeUtils::KIND_ARCHIVE: + if (d->mFoundDirUrl.isValid()) { + // This is the second dir we find, stop now + finish(dir, MultipleDirsFound); + return; + } else { + // First dir + d->mFoundDirUrl = item.url(); + } + break; + + case MimeTypeUtils::KIND_RASTER_IMAGE: + case MimeTypeUtils::KIND_SVG_IMAGE: + case MimeTypeUtils::KIND_VIDEO: + finish(dir, DocumentDirFound); + return; + + case MimeTypeUtils::KIND_UNKNOWN: + case MimeTypeUtils::KIND_FILE: + break; + } + } +} + +void DocumentDirFinder::slotCompleted() +{ + if (d->mFoundDirUrl.isValid()) { + KUrl url = d->mFoundDirUrl; + d->mFoundDirUrl = KUrl(); + d->mDirLister->openUrl(url); + } else { + finish(d->mRootUrl, NoDocumentFound); + } +} + +void DocumentDirFinder::finish(const KUrl& url, DocumentDirFinder::Status status) +{ + disconnect(d->mDirLister, 0, this, 0); + emit done(url, status); + deleteLater(); +} + +} // namespace diff --git a/gwenview/importer/documentdirfinder.h b/gwenview/importer/documentdirfinder.h new file mode 100644 index 00000000..4c01e0cb --- /dev/null +++ b/gwenview/importer/documentdirfinder.h @@ -0,0 +1,77 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENTDIRFINDER_H +#define DOCUMENTDIRFINDER_H + +// Qt +#include + +// KDE +#include + +// Local + +namespace Gwenview +{ + +struct DocumentDirFinderPrivate; + +/** + * This class is a worker which tries to find the document dir given a root + * url. This is useful for digital camera cards, which often have a dir + * hierarchy like this: + * /DCIM + * /FOOBAR + * /PICT0001.JPG + * /PICT0002.JPG + * ... + * /PICTnnnn.JPG + */ +class DocumentDirFinder : public QObject +{ + Q_OBJECT +public: + enum Status { + NoDocumentFound, + DocumentDirFound, + MultipleDirsFound + }; + + DocumentDirFinder(const KUrl& rootUrl); + ~DocumentDirFinder(); + + void start(); + +Q_SIGNALS: + void done(const KUrl&, DocumentDirFinder::Status); + +private Q_SLOTS: + void slotItemsAdded(const KUrl&, const KFileItemList&); + void slotCompleted(); + +private: + DocumentDirFinderPrivate* const d; + void finish(const KUrl&, Status); +}; + +} // namespace + +#endif /* DOCUMENTDIRFINDER_H */ diff --git a/gwenview/importer/filenameformater.cpp b/gwenview/importer/filenameformater.cpp new file mode 100644 index 00000000..60805261 --- /dev/null +++ b/gwenview/importer/filenameformater.cpp @@ -0,0 +1,114 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "filenameformater.h" + +// Qt +#include + +// KDE +#include +#include +#include + +// Local + +namespace Gwenview +{ + +typedef QHash Dict; + +struct FileNameFormaterPrivate +{ + QString mFormat; +}; + +FileNameFormater::FileNameFormater(const QString& format) +: d(new FileNameFormaterPrivate) +{ + d->mFormat = format; +} + +FileNameFormater::~FileNameFormater() +{ + delete d; +} + +QString FileNameFormater::format(const KUrl& url, const KDateTime& dateTime) +{ + QFileInfo info(url.fileName()); + + // Keep in sync with helpMap() + Dict dict; + dict["date"] = dateTime.toString("%Y-%m-%d"); + dict["time"] = dateTime.toString("%H-%M-%S"); + dict["ext"] = info.suffix(); + dict["ext.lower"] = info.suffix().toLower(); + dict["name"] = info.completeBaseName(); + dict["name.lower"] = info.completeBaseName().toLower(); + + QString name; + int length = d->mFormat.length(); + for (int pos = 0; pos < length; ++pos) { + QChar ch = d->mFormat[pos]; + if (ch == '{') { + if (pos == length - 1) { + // We are at the end, ignore this + break; + } + if (d->mFormat[pos + 1] == '{') { + // This is an escaped '{', skip one + name += '{'; + ++pos; + continue; + } + int end = d->mFormat.indexOf('}', pos + 1); + if (end == -1) { + // No '}' found, stop here + return name; + } + // Replace keyword with its value + QString keyword = d->mFormat.mid(pos + 1, end - pos - 1); + name += dict.value(keyword); + pos = end; + } else { + name += ch; + } + } + return name; +} + +FileNameFormater::HelpMap FileNameFormater::helpMap() +{ + // Keep in sync with dict in format() + static HelpMap map; + if (map.isEmpty()) { + map["date"] = i18n("Shooting date"); + map["time"] = i18n("Shooting time"); + map["ext"] = i18n("Original extension"); + map["ext.lower"] = i18n("Original extension, in lower case"); + map["name"] = i18n("Original filename"); + map["name.lower"] = i18n("Original filename, in lower case"); + } + return map; +} + +} // namespace diff --git a/gwenview/importer/filenameformater.h b/gwenview/importer/filenameformater.h new file mode 100644 index 00000000..97205d74 --- /dev/null +++ b/gwenview/importer/filenameformater.h @@ -0,0 +1,66 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef FILENAMEFORMATER_H +#define FILENAMEFORMATER_H + +// Qt +#include + +// KDE + +// Local + +class QString; + +class KDateTime; +class KUrl; + +namespace Gwenview +{ + +struct FileNameFormaterPrivate; +class FileNameFormater +{ +public: + typedef QMap HelpMap; + + FileNameFormater(const QString& format); + ~FileNameFormater(); + + /** + * Given an url and its dateTime, returns a filename according to the + * format passed to the constructor + */ + QString format(const KUrl& url, const KDateTime& dateTime); + + /** + * Returns a map whose keys are the available keywords and values are the + * keyword help + */ + static HelpMap helpMap(); + +private: + FileNameFormaterPrivate* const d; +}; + +} // namespace + +#endif /* FILENAMEFORMATER_H */ diff --git a/gwenview/importer/fileutils.cpp b/gwenview/importer/fileutils.cpp new file mode 100644 index 00000000..5cc0c971 --- /dev/null +++ b/gwenview/importer/fileutils.cpp @@ -0,0 +1,142 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "fileutils.h" + +// Qt +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include + +// libc +#include +#include +#include + +namespace Gwenview +{ +namespace FileUtils +{ + +bool contentsAreIdentical(const KUrl& url1, const KUrl& url2, QWidget* authWindow) +{ + // FIXME: Support remote urls + QFile file1(KIO::NetAccess::mostLocalUrl(url1, authWindow).toLocalFile()); + if (!file1.open(QIODevice::ReadOnly)) { + // Can't read url1, assume it's different from url2 + kWarning() << "Can't read" << url1; + return false; + } + + QFile file2(KIO::NetAccess::mostLocalUrl(url2, authWindow).toLocalFile()); + if (!file2.open(QIODevice::ReadOnly)) { + // Can't read url2, assume it's different from url1 + kWarning() << "Can't read" << url2; + return false; + } + + const int CHUNK_SIZE = 4096; + while (!file1.atEnd() && !file2.atEnd()) { + QByteArray url1Array = file1.read(CHUNK_SIZE); + QByteArray url2Array = file2.read(CHUNK_SIZE); + + if (url1Array != url2Array) { + return false; + } + } + if (file1.atEnd() && file2.atEnd()) { + return true; + } else { + kWarning() << "One file ended before the other"; + return false; + } +} + +RenameResult rename(const KUrl& src, const KUrl& dst_, QWidget* authWindow) +{ + KUrl dst = dst_; + RenameResult result = RenamedOK; + int count = 1; + + QFileInfo fileInfo(dst.fileName()); + QString prefix = fileInfo.completeBaseName() + '_'; + QString suffix = '.' + fileInfo.suffix(); + + // Get src size + KIO::UDSEntry udsEntry; + KIO::NetAccess::stat(src, udsEntry, authWindow); + KFileItem item(udsEntry, src, true /* delayedMimeTypes */); + KIO::filesize_t srcSize = item.size(); + + // Find unique name + while (KIO::NetAccess::stat(dst, udsEntry, authWindow)) { + // File exists. If it's not the same, try to create a new name + item = KFileItem(udsEntry, dst, true /* delayedMimeTypes */); + KIO::filesize_t dstSize = item.size(); + + if (srcSize == dstSize && contentsAreIdentical(src, dst, authWindow)) { + // Already imported, skip it + KIO::Job* job = KIO::file_delete(src, KIO::HideProgressInfo); + if (job->ui()) { + job->ui()->setWindow(authWindow); + } + + return Skipped; + } + result = RenamedUnderNewName; + + dst.setFileName(prefix + QString::number(count) + suffix); + ++count; + } + + // Rename + KIO::Job* job = KIO::rename(src, dst, KIO::HideProgressInfo); + if (!KIO::NetAccess::synchronousRun(job, authWindow)) { + result = RenameFailed; + } + return result; +} + +QString createTempDir(const QString& baseDir, const QString& prefix, QString* errorMessage) +{ + Q_ASSERT(errorMessage); + + QByteArray name = QFile::encodeName(baseDir + '/' + prefix + "XXXXXX"); + + if (!mkdtemp(name.data())) { + // Failure + *errorMessage = QString::fromLocal8Bit(::strerror(errno)); + return QString(); + } + return QFile::decodeName(name + '/'); +} + +} // namespace +} // namespace diff --git a/gwenview/importer/fileutils.h b/gwenview/importer/fileutils.h new file mode 100644 index 00000000..e81c1db8 --- /dev/null +++ b/gwenview/importer/fileutils.h @@ -0,0 +1,63 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef FILEUTILS_H +#define FILEUTILS_H + +class QString; +class QWidget; +class KUrl; + +namespace Gwenview +{ + +namespace FileUtils +{ + +enum RenameResult { + RenamedOK, /** Renamed without problem */ + RenamedUnderNewName, /** Destination already existed, so rename() added a suffix to make the name unique */ + Skipped, /** Destination already existed and contained the same data as source, so rename() just removed the source */ + RenameFailed /** Rename failed */ +}; + +/** + * Compare content of two urls, returns whether they are the same + */ +bool contentsAreIdentical(const KUrl& url1, const KUrl& url2, QWidget* authWindow = 0); + +/** + * Rename src to dst, returns RenameResult + */ +RenameResult rename(const KUrl& src, const KUrl& dst, QWidget* authWindow = 0); + +/** + * Create a temp dir in baseDir, starting with prefix. If successful returns + * the created dir otherwise returns an empty string and writes the error message + * in errorMessage. + * + * errorMessage must be a valid pointer. + */ +QString createTempDir(const QString& baseDir, const QString& prefix, QString* errorMessage); + +} // namespace +} // namespace + +#endif /* FILEUTILS_H */ diff --git a/gwenview/importer/gwenview_importer.desktop b/gwenview/importer/gwenview_importer.desktop new file mode 100644 index 00000000..e20891c3 --- /dev/null +++ b/gwenview/importer/gwenview_importer.desktop @@ -0,0 +1,65 @@ +[Desktop Entry] +X-KDE-Solid-Predicate=[ StorageVolume.ignored == false AND StorageVolume.usage == 'FileSystem' ] +Type=Service +Actions=open; + +[Desktop Action open] +# %f gives StorageAccess.filePath, %d gives Block.device, %i gives UDI +Exec=gwenview_importer %f --udi %i +Icon=gwenview +Name=Download Photos with Gwenview +Name[ar]=نزل الصور بجوينفيو +Name[ast]=Descarga Semeyes con Gwenview +Name[bg]=Изтегляне на снимки с Gwenview +Name[bs]=Preuzimanje fotografija Gvenvjuom +Name[ca]=Descarrega fotos amb el Gwenview +Name[ca@valencia]=Descarrega fotos amb el Gwenview +Name[cs]=Stáhnout fotografie pomocí Gwenview +Name[da]=Download fotos med Gwenview +Name[de]=Fotos mit Gwenview herunterladen +Name[el]=Λήψη φωτογραφιών με το Gwenview +Name[en_GB]=Download Photos with Gwenview +Name[es]=Descargar fotos con Gwenview +Name[et]=Fotode allalaadimine Gwenview'ga +Name[eu]=Deskargatu argazkiak Gwenview-ekin +Name[fi]=Lataa valokuvat Gwenview’llä +Name[fr]=Télécharger des photos avec Gwenview +Name[ga]=Íosluchtaigh Grianghrafanna le Gwenview +Name[gl]=Descargar fotos co Gwenview +Name[hr]=Preuzmi fotografije Gwenviewom +Name[hu]=Fényképek letöltése a Gwenviewval +Name[ia]=Discarga photos con Gwenview +Name[is]=Hala niður myndum með Gwenview +Name[it]=Scarica foto con Gwenview +Name[ja]=Gwenview で写真をダウンロード +Name[kk]=Gwenview-де фотосуреттерді жүктеп алу +Name[km]=ទាញយក​រូបថត​​ដោយ​ប្រើ Gwenview +Name[ko]=Gwenview로 사진 가져오기 +Name[lt]=Atsisiųsti nuotraukas su Gwenview +Name[lv]=Ielādēt fotogrāfijas ar Gwenview +Name[mr]=ग्वेनव्युने फोटो डाउनलोड करा +Name[nb]=Last ned fotografier med Gwenview +Name[nds]=Fotos mit Gwenview daalladen +Name[nl]=Foto's met Gwenview downloaden +Name[nn]=Overfør bilete med Gwenview +Name[pa]=ਜੀਵੀਨ-ਵਿਊ ਨਾਲ ਫੋਟੋ ਡਾਊਨਲੋਡ ਕਰੋ +Name[pl]=Pobierz zdjęcia z Gwenview +Name[pt]=Obter as Fotografias com o Gwenview +Name[pt_BR]=Transferir as fotos com o Gwenview +Name[ro]=Descarcă fotografii cu Gwenview +Name[ru]=Скопировать фотографии с помощью Gwenview +Name[si]=Gwenview සමඟින් පිංතූර බාගන්න +Name[sk]=Stiahnuť fotografie pomocou Gwenview +Name[sl]=Prejmite fotografije z Gwenview +Name[sr]=Преузимање фотографија Гвенвјуом +Name[sr@ijekavian]=Преузимање фотографија Гвенвјуом +Name[sr@ijekavianlatin]=Preuzimanje fotografija GwenViewom +Name[sr@latin]=Preuzimanje fotografija GwenViewom +Name[sv]=Ladda ner foton med Gwenview +Name[th]=ดาวน์โหลดภาพถ่ายด้วยเกวนวิว +Name[tr]=Gwenview ile Fotoğrafları İndir +Name[ug]=سۈرەتلەرنى Gwenview بىلەن چۈشۈرۈش +Name[uk]=Звантажити фотографії за допомогою Gwenview +Name[x-test]=xxDownload Photos with Gwenviewxx +Name[zh_CN]=用 Gwenview 下载照片 +Name[zh_TW]=用 Gwenview 下載相片 diff --git a/gwenview/importer/gwenview_importer_camera.desktop b/gwenview/importer/gwenview_importer_camera.desktop new file mode 100644 index 00000000..1c316615 --- /dev/null +++ b/gwenview/importer/gwenview_importer_camera.desktop @@ -0,0 +1,64 @@ +[Desktop Entry] +X-KDE-Solid-Predicate=Camera.supportedDrivers == 'gphoto' +Type=Service +Actions=open; + +[Desktop Action open] +Exec=gwenview_importer camera:/ +Icon=gwenview +Name=Download Photos with Gwenview +Name[ar]=نزل الصور بجوينفيو +Name[ast]=Descarga Semeyes con Gwenview +Name[bg]=Изтегляне на снимки с Gwenview +Name[bs]=Preuzimanje fotografija Gvenvjuom +Name[ca]=Descarrega fotos amb el Gwenview +Name[ca@valencia]=Descarrega fotos amb el Gwenview +Name[cs]=Stáhnout fotografie pomocí Gwenview +Name[da]=Download fotos med Gwenview +Name[de]=Fotos mit Gwenview herunterladen +Name[el]=Λήψη φωτογραφιών με το Gwenview +Name[en_GB]=Download Photos with Gwenview +Name[es]=Descargar fotos con Gwenview +Name[et]=Fotode allalaadimine Gwenview'ga +Name[eu]=Deskargatu argazkiak Gwenview-ekin +Name[fi]=Lataa valokuvat Gwenview’llä +Name[fr]=Télécharger des photos avec Gwenview +Name[ga]=Íosluchtaigh Grianghrafanna le Gwenview +Name[gl]=Descargar fotos co Gwenview +Name[hr]=Preuzmi fotografije Gwenviewom +Name[hu]=Fényképek letöltése a Gwenviewval +Name[ia]=Discarga photos con Gwenview +Name[is]=Hala niður myndum með Gwenview +Name[it]=Scarica foto con Gwenview +Name[ja]=Gwenview で写真をダウンロード +Name[kk]=Gwenview-де фотосуреттерді жүктеп алу +Name[km]=ទាញយក​រូបថត​​ដោយ​ប្រើ Gwenview +Name[ko]=Gwenview로 사진 가져오기 +Name[lt]=Atsisiųsti nuotraukas su Gwenview +Name[lv]=Ielādēt fotogrāfijas ar Gwenview +Name[mr]=ग्वेनव्युने फोटो डाउनलोड करा +Name[nb]=Last ned fotografier med Gwenview +Name[nds]=Fotos mit Gwenview daalladen +Name[nl]=Foto's met Gwenview downloaden +Name[nn]=Overfør bilete med Gwenview +Name[pa]=ਜੀਵੀਨ-ਵਿਊ ਨਾਲ ਫੋਟੋ ਡਾਊਨਲੋਡ ਕਰੋ +Name[pl]=Pobierz zdjęcia z Gwenview +Name[pt]=Obter as Fotografias com o Gwenview +Name[pt_BR]=Transferir as fotos com o Gwenview +Name[ro]=Descarcă fotografii cu Gwenview +Name[ru]=Скопировать фотографии с помощью Gwenview +Name[si]=Gwenview සමඟින් පිංතූර බාගන්න +Name[sk]=Stiahnuť fotografie pomocou Gwenview +Name[sl]=Prejmite fotografije z Gwenview +Name[sr]=Преузимање фотографија Гвенвјуом +Name[sr@ijekavian]=Преузимање фотографија Гвенвјуом +Name[sr@ijekavianlatin]=Preuzimanje fotografija GwenViewom +Name[sr@latin]=Preuzimanje fotografija GwenViewom +Name[sv]=Ladda ner foton med Gwenview +Name[th]=ดาวน์โหลดภาพถ่ายด้วยเกวนวิว +Name[tr]=Gwenview ile Fotoğrafları İndir +Name[ug]=سۈرەتلەرنى Gwenview بىلەن چۈشۈرۈش +Name[uk]=Звантажити фотографії за допомогою Gwenview +Name[x-test]=xxDownload Photos with Gwenviewxx +Name[zh_CN]=用 Gwenview 下载照片 +Name[zh_TW]=用 Gwenview 下載相片 diff --git a/gwenview/importer/importdialog.cpp b/gwenview/importer/importdialog.cpp new file mode 100644 index 00000000..198cfc02 --- /dev/null +++ b/gwenview/importer/importdialog.cpp @@ -0,0 +1,268 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "importdialog.moc" + +// Qt +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "dialogpage.h" +#include "importer.h" +#include "importerconfig.h" +#include "progresspage.h" +#include "thumbnailpage.h" + +namespace Gwenview +{ + +class ImportDialogPrivate +{ +public: + ImportDialog* q; + QStackedWidget* mCentralWidget; + ThumbnailPage* mThumbnailPage; + ProgressPage* mProgressPage; + DialogPage* mDialogPage; + Importer* mImporter; + + void deleteImportedUrls() + { + KUrl::List importedUrls = mImporter->importedUrlList(); + KUrl::List skippedUrls = mImporter->skippedUrlList(); + int importedCount = importedUrls.count(); + int skippedCount = skippedUrls.count(); + + if (importedCount == 0 && skippedCount == 0) { + return; + } + + QStringList message; + message << i18np( + "One document has been imported.", + "%1 documents have been imported.", + importedCount); + if (skippedCount > 0) { + message << i18np( + "One document has been skipped because it had already been imported.", + "%1 documents have been skipped because they had already been imported.", + skippedCount); + } + + if (mImporter->renamedCount() > 0) { + message[0].append("*"); + message << "* " + i18np( + "One of them has been renamed because another document with the same name had already been imported.", + "%1 of them have been renamed because other documents with the same name had already been imported.", + mImporter->renamedCount()) + + ""; + } + + message << QString(); + if (skippedCount == 0) { + message << i18np( + "Delete the imported document from the device?", + "Delete the %1 imported documents from the device?", + importedCount); + } else if (importedCount == 0) { + message << i18np( + "Delete the skipped document from the device?", + "Delete the %1 skipped documents from the device?", + skippedCount); + } else { + message << i18ncp( + "Singular sentence is actually never used.", + "Delete the imported or skipped document from the device?", + "Delete the %1 imported and skipped documents from the device?", + importedCount + skippedCount); + } + + int answer = KMessageBox::questionYesNo(mCentralWidget, + "" + message.join("
") + "
", + i18nc("@title:window", "Import Finished"), + KStandardGuiItem::del(), + KGuiItem(i18n("Keep")) + ); + if (answer != KMessageBox::Yes) { + return; + } + KUrl::List urls = importedUrls + skippedUrls; + while (true) { + KIO::Job* job = KIO::del(urls); + if (KIO::NetAccess::synchronousRun(job, q)) { + break; + } + // Deleting failed + int answer = KMessageBox::warningYesNo(mCentralWidget, + i18np("Failed to delete the document:\n%2", + "Failed to delete documents:\n%2", + urls.count(), job->errorString()), + QString(), + KGuiItem(i18n("Retry")), + KGuiItem(i18n("Ignore")) + ); + if (answer != KMessageBox::Yes) { + // Ignore + break; + } + } + } + + void startGwenview() + { + KService::Ptr service = KService::serviceByDesktopName("gwenview"); + if (!service) { + kError() << "Could not find gwenview"; + } else { + KRun::run(*service, KUrl::List() << mThumbnailPage->destinationUrl(), 0 /* window */); + } + } + + void showWhatNext() + { + mCentralWidget->setCurrentWidget(mDialogPage); + mDialogPage->setText(i18n("What do you want to do now?")); + mDialogPage->removeButtons(); + int gwenview = mDialogPage->addButton(KGuiItem(i18n("View Imported Documents with Gwenview"), "gwenview")); + int importMore = mDialogPage->addButton(KGuiItem(i18n("Import more Documents"))); + mDialogPage->addButton(KGuiItem(i18n("Quit"), "dialog-cancel")); + + int answer = mDialogPage->exec(); + if (answer == gwenview) { + startGwenview(); + qApp->quit(); + } else if (answer == importMore) { + mCentralWidget->setCurrentWidget(mThumbnailPage); + } else { /* quit */ + qApp->quit(); + } + } +}; + +ImportDialog::ImportDialog() +: d(new ImportDialogPrivate) +{ + d->q = this; + d->mImporter = new Importer(this); + connect(d->mImporter, SIGNAL(error(QString)), + SLOT(showImportError(QString))); + d->mThumbnailPage = new ThumbnailPage; + + KUrl url = ImporterConfig::destinationUrl(); + if (!url.isValid()) { + url = KUrl::fromPath(KGlobalSettings::picturesPath()); + int year = QDate::currentDate().year(); + url.addPath(QString::number(year)); + } + d->mThumbnailPage->setDestinationUrl(url); + + d->mProgressPage = new ProgressPage(d->mImporter); + + d->mDialogPage = new DialogPage; + + d->mCentralWidget = new QStackedWidget; + setCentralWidget(d->mCentralWidget); + d->mCentralWidget->addWidget(d->mThumbnailPage); + d->mCentralWidget->addWidget(d->mProgressPage); + d->mCentralWidget->addWidget(d->mDialogPage); + + connect(d->mThumbnailPage, SIGNAL(importRequested()), + SLOT(startImport())); + connect(d->mThumbnailPage, SIGNAL(rejected()), + SLOT(close())); + connect(d->mImporter, SIGNAL(importFinished()), + SLOT(slotImportFinished())); + + d->mCentralWidget->setCurrentWidget(d->mThumbnailPage); + + setWindowIcon(KIcon("gwenview")); + setAutoSaveSettings(); +} + +ImportDialog::~ImportDialog() +{ + delete d; +} + +QSize ImportDialog::sizeHint() const +{ + return QSize(700, 500); +} + +void ImportDialog::setSourceUrl(const KUrl& url, const QString& deviceUdi) +{ + QString name, iconName; + if (deviceUdi.isEmpty()) { + name = url.pathOrUrl(); + iconName = KProtocolInfo::icon(url.protocol()); + if (iconName.isEmpty()) { + iconName = "folder"; + } + } else { + Solid::Device device(deviceUdi); + name = device.vendor() + " " + device.product(); + iconName = device.icon(); + } + d->mThumbnailPage->setSourceUrl(url, iconName, name); +} + +void ImportDialog::startImport() +{ + KUrl url = d->mThumbnailPage->destinationUrl(); + ImporterConfig::setDestinationUrl(url); + ImporterConfig::self()->writeConfig(); + + d->mCentralWidget->setCurrentWidget(d->mProgressPage); + d->mImporter->setAutoRenameFormat( + ImporterConfig::autoRename() + ? ImporterConfig::autoRenameFormat() + : QString()); + d->mImporter->start(d->mThumbnailPage->urlList(), url); +} + +void ImportDialog::slotImportFinished() +{ + d->deleteImportedUrls(); + d->showWhatNext(); +} + +void ImportDialog::showImportError(const QString& message) +{ + KMessageBox::sorry(this, message); + d->mCentralWidget->setCurrentWidget(d->mThumbnailPage); +} + +} // namespace diff --git a/gwenview/importer/importdialog.h b/gwenview/importer/importdialog.h new file mode 100644 index 00000000..f8f56d34 --- /dev/null +++ b/gwenview/importer/importdialog.h @@ -0,0 +1,59 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef IMPORTDIALOG_H +#define IMPORTDIALOG_H + +// Qt + +// KDE +#include +#include + +// Local + +namespace Gwenview +{ + +class ImportDialogPrivate; +class ImportDialog : public KMainWindow +{ + Q_OBJECT +public: + ImportDialog(); + ~ImportDialog(); + + virtual QSize sizeHint() const; + +public Q_SLOTS: + void setSourceUrl(const KUrl&, const QString& deviceUdi); + +private Q_SLOTS: + void startImport(); + void slotImportFinished(); + void showImportError(const QString&); + +private: + ImportDialogPrivate* const d; +}; + +} // namespace + +#endif /* IMPORTDIALOG_H */ diff --git a/gwenview/importer/importer.cpp b/gwenview/importer/importer.cpp new file mode 100644 index 00000000..a6b22a41 --- /dev/null +++ b/gwenview/importer/importer.cpp @@ -0,0 +1,246 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "importer.moc" + +// Qt + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// stdc++ +#include + +// Local +#include +#include +#include + +namespace Gwenview +{ + +struct ImporterPrivate +{ + Importer* q; + QWidget* mAuthWindow; + std::auto_ptr mFileNameFormater; + KUrl mTempImportDir; + + /* @defgroup reset Should be reset in start() + * @{ */ + KUrl::List mUrlList; + KUrl::List mImportedUrlList; + KUrl::List mSkippedUrlList; + int mRenamedCount; + int mProgress; + int mJobProgress; + /* @} */ + + KUrl mCurrentUrl; + + void emitError(const QString& message) + { + QMetaObject::invokeMethod(q, "error", Q_ARG(QString, message)); + } + + bool createImportDir(const KUrl url) + { + Q_ASSERT(url.isLocalFile()); + // FIXME: Support remote urls + + if (!KStandardDirs::makeDir(url.toLocalFile())) { + emitError(i18n("Could not create destination folder.")); + return false; + } + QString message; + mTempImportDir = FileUtils::createTempDir(url.toLocalFile(), ".gwenview_importer-", &message); + if (mTempImportDir.isEmpty()) { + emitError(i18n("Could not create temporary upload folder:\n%1", message)); + return false; + } + return true; + } + + void importNext() + { + if (mUrlList.empty()) { + q->finalizeImport(); + return; + } + mCurrentUrl = mUrlList.takeFirst(); + KUrl dst = mTempImportDir; + dst.addPath(mCurrentUrl.fileName()); + KIO::Job* job = KIO::copy(mCurrentUrl, dst, KIO::HideProgressInfo); + if (job->ui()) { + job->ui()->setWindow(mAuthWindow); + } + QObject::connect(job, SIGNAL(result(KJob*)), + q, SLOT(slotCopyDone(KJob*))); + QObject::connect(job, SIGNAL(percent(KJob*,ulong)), + q, SLOT(slotPercent(KJob*,ulong))); + } + + void renameImportedUrl(const KUrl& src) + { + KUrl dst = src; + dst.cd(".."); + QString fileName; + if (mFileNameFormater.get()) { + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, src, true /* delayedMimeTypes */); + // Get the document time, but do not cache the result because the + // 'src' url is temporary: if we import "foo/image.jpg" and + // "bar/image.jpg", both images will be temporarily saved in the + // 'src' url. + KDateTime dateTime = TimeUtils::dateTimeForFileItem(item, TimeUtils::SkipCache); + fileName = mFileNameFormater->format(src, dateTime); + } else { + fileName = src.fileName(); + } + dst.setFileName(fileName); + + FileUtils::RenameResult result = FileUtils::rename(src, dst, mAuthWindow); + switch (result) { + case FileUtils::RenamedOK: + mImportedUrlList << mCurrentUrl; + break; + case FileUtils::RenamedUnderNewName: + mRenamedCount++; + mImportedUrlList << mCurrentUrl; + break; + case FileUtils::Skipped: + mSkippedUrlList << mCurrentUrl; + break; + case FileUtils::RenameFailed: + kWarning() << "Rename failed for" << mCurrentUrl; + } + q->advance(); + importNext(); + } +}; + +Importer::Importer(QWidget* parent) +: QObject(parent) +, d(new ImporterPrivate) +{ + d->q = this; + d->mAuthWindow = parent; +} + +Importer::~Importer() +{ + delete d; +} + +void Importer::setAutoRenameFormat(const QString& format) +{ + if (format.isEmpty()) { + d->mFileNameFormater.reset(0); + } else { + d->mFileNameFormater.reset(new FileNameFormater(format)); + } +} + +void Importer::start(const KUrl::List& list, const KUrl& destination) +{ + d->mUrlList = list; + d->mImportedUrlList.clear(); + d->mSkippedUrlList.clear(); + d->mRenamedCount = 0; + d->mProgress = 0; + d->mJobProgress = 0; + + emitProgressChanged(); + maximumChanged(d->mUrlList.count() * 100); + + if (!d->createImportDir(destination)) { + kWarning() << "Could not create import dir"; + return; + } + d->importNext(); +} + +void Importer::slotCopyDone(KJob* _job) +{ + KIO::CopyJob* job = static_cast(_job); + KUrl url = job->destUrl(); + if (job->error()) { + kWarning() << "FIXME: What do we do with failed urls?"; + advance(); + d->importNext(); + return; + } + + d->renameImportedUrl(url); +} + +void Importer::finalizeImport() +{ + KIO::Job* job = KIO::del(d->mTempImportDir, KIO::HideProgressInfo); + if (job->ui()) { + job->ui()->setWindow(d->mAuthWindow); + } + importFinished(); +} + +void Importer::advance() +{ + ++d->mProgress; + d->mJobProgress = 0; + emitProgressChanged(); +} + +void Importer::slotPercent(KJob*, unsigned long percent) +{ + d->mJobProgress = percent; + emitProgressChanged(); +} + +void Importer::emitProgressChanged() +{ + progressChanged(d->mProgress * 100 + d->mJobProgress); +} + +KUrl::List Importer::importedUrlList() const +{ + return d->mImportedUrlList; +} + +KUrl::List Importer::skippedUrlList() const +{ + return d->mSkippedUrlList; +} + +int Importer::renamedCount() const +{ + return d->mRenamedCount; +} + +} // namespace diff --git a/gwenview/importer/importer.h b/gwenview/importer/importer.h new file mode 100644 index 00000000..0f175da7 --- /dev/null +++ b/gwenview/importer/importer.h @@ -0,0 +1,92 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef IMPORTER_H +#define IMPORTER_H + +// Qt +#include + +// KDE +#include + +// Local + +class KJob; + +namespace Gwenview +{ + +struct ImporterPrivate; +class Importer : public QObject +{ + Q_OBJECT +public: + Importer(QWidget* authWindow); + ~Importer(); + + /** + * Defines the auto-rename format applied to imported documents + * Set to QString() to reset + */ + void setAutoRenameFormat(const QString&); + + void start(const KUrl::List& list, const KUrl& destUrl); + + KUrl::List importedUrlList() const; + + /** + * Documents which have been skipped during import + */ + KUrl::List skippedUrlList() const; + + /** + * How many documents have been renamed during import + */ + int renamedCount() const; + +Q_SIGNALS: + void importFinished(); + + void progressChanged(int); + + void maximumChanged(int); + + /** + * An error has occurred and caused the whole process to stop without + * importing anything + */ + void error(const QString& message); + +private Q_SLOTS: + void slotCopyDone(KJob*); + void slotPercent(KJob*, unsigned long); + void emitProgressChanged(); + +private: + friend struct ImporterPrivate; + ImporterPrivate* const d; + void advance(); + void finalizeImport(); +}; + +} // namespace + +#endif /* IMPORTER_H */ diff --git a/gwenview/importer/importerconfig.kcfg b/gwenview/importer/importerconfig.kcfg new file mode 100644 index 00000000..36b2d920 --- /dev/null +++ b/gwenview/importer/importerconfig.kcfg @@ -0,0 +1,19 @@ + + + + + + + + + + true + + + {date}_{time}.{ext.lower} + + + diff --git a/gwenview/importer/importerconfig.kcfgc b/gwenview/importer/importerconfig.kcfgc new file mode 100644 index 00000000..41582b06 --- /dev/null +++ b/gwenview/importer/importerconfig.kcfgc @@ -0,0 +1,6 @@ +File=importerconfig.kcfg +ClassName=ImporterConfig +NameSpace=Gwenview +Singleton=true +Mutators=true +UseEnumTypes=true diff --git a/gwenview/importer/importerconfigdialog.cpp b/gwenview/importer/importerconfigdialog.cpp new file mode 100644 index 00000000..590e41f2 --- /dev/null +++ b/gwenview/importer/importerconfigdialog.cpp @@ -0,0 +1,102 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "importerconfigdialog.moc" + +// Qt +#include + +// KDE +#include + +// Local +#include "filenameformater.h" +#include "importerconfig.h" +#include "ui_importerconfigdialog.h" + +namespace Gwenview +{ + +static const QString PREVIEW_FILENAME = "PICT0012.JPG"; +static const KDateTime PREVIEW_DATETIME = KDateTime(QDate(2009, 10, 25), QTime(17, 51, 18)); + +struct ImporterConfigDialogPrivate : public Ui_ImporterConfigDialog +{ + ImporterConfigDialog* q; + + void setupHelpText() + { + QString helpText = "

"; + mRenameFormatHelpLabel->setText(helpText); + + QObject::connect(mRenameFormatHelpLabel, SIGNAL(linkActivated(QString)), + q, SLOT(slotHelpLinkActivated(QString))); + } +}; + +ImporterConfigDialog::ImporterConfigDialog(QWidget* parent) +: KConfigDialog(parent, "Importer Settings", ImporterConfig::self()) +, d(new ImporterConfigDialogPrivate) +{ + d->q = this; + QWidget* widget = new QWidget; + d->setupUi(widget); + setFaceType(KPageDialog::Plain); + // Hide help button until there is actual documentation to show + showButton(KDialog::Help, false); + showButtonSeparator(true); + addPage(widget, QString()); + + connect(d->kcfg_AutoRenameFormat, SIGNAL(textChanged(QString)), + SLOT(updatePreview())); + + d->setupHelpText(); + updatePreview(); +} + +ImporterConfigDialog::~ImporterConfigDialog() +{ + delete d; +} + +void ImporterConfigDialog::slotHelpLinkActivated(const QString& keyword) +{ + d->kcfg_AutoRenameFormat->insert(keyword); +} + +void ImporterConfigDialog::updatePreview() +{ + FileNameFormater formater(d->kcfg_AutoRenameFormat->text()); + d->mPreviewOutputLabel->setText(formater.format(KUrl::fromPath('/' + PREVIEW_FILENAME), PREVIEW_DATETIME)); +} + +} // namespace diff --git a/gwenview/importer/importerconfigdialog.h b/gwenview/importer/importerconfigdialog.h new file mode 100644 index 00000000..e2e50f2f --- /dev/null +++ b/gwenview/importer/importerconfigdialog.h @@ -0,0 +1,52 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef IMPORTERCONFIGDIALOG_H +#define IMPORTERCONFIGDIALOG_H + +// Qt + +// KDE +#include + +// Local + +namespace Gwenview +{ + +struct ImporterConfigDialogPrivate; +class ImporterConfigDialog : public KConfigDialog +{ + Q_OBJECT +public: + ImporterConfigDialog(QWidget*); + ~ImporterConfigDialog(); + +private Q_SLOTS: + void slotHelpLinkActivated(const QString& keyword); + void updatePreview(); + +private: + ImporterConfigDialogPrivate* const d; +}; + +} // namespace + +#endif /* IMPORTERCONFIGDIALOG_H */ diff --git a/gwenview/importer/importerconfigdialog.ui b/gwenview/importer/importerconfigdialog.ui new file mode 100644 index 00000000..cc47d418 --- /dev/null +++ b/gwenview/importer/importerconfigdialog.ui @@ -0,0 +1,163 @@ + + + ImporterConfigDialog + + + + 0 + 0 + 410 + 213 + + + + + + + Rename documents automatically + + + + + + + false + + + + 0 + 0 + + + + + 24 + + + + + Rename Format: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + [Output preview] + + + + + + + Preview: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + - {date}: shooting date +- {name}: original file name + + + true + + + Qt::LinksAccessibleByMouse + + + + + + + <i>Type text or click the items below to customize the format</i> + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 24 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 24 + + + + + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + + + kcfg_AutoRename + toggled(bool) + widget + setEnabled(bool) + + + 97 + 23 + + + 136 + 58 + + + + +
diff --git a/gwenview/importer/main.cpp b/gwenview/importer/main.cpp new file mode 100644 index 00000000..e09e84a3 --- /dev/null +++ b/gwenview/importer/main.cpp @@ -0,0 +1,74 @@ +/* +Gwenview: an image viewer +Copyright 2000-2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Qt +#include + +// KDE +#include +#include +#include +#include +#include +#include + +// Local +#include +#include +#include "importdialog.h" + +int main(int argc, char *argv[]) +{ + QScopedPointer aboutData( + Gwenview::createAboutData( + "gwenview_importer", /* appname */ + "gwenview", /* catalogName */ + ki18n("Gwenview Importer") /* programName */ + )); + aboutData->setShortDescription(ki18n("Photo Importer")); + + KCmdLineArgs::init(argc, argv, aboutData.data()); + + KCmdLineOptions options; + options.add("+folder", ki18n("Source folder")); + options.add("udi ", ki18n("Device UDI")); + KCmdLineArgs::addCmdLineOptions(options); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + KApplication app; + + if (args->count() != 1) { + KCmdLineArgs::usageError("Missing required source folder argument."); // FIXME 2.11 Add i18n() call + return 1; + } + KUrl url = args->url(0); + if (!url.isValid()) { + kError() << "Invalid source folder."; // FIXME 2.11 Add i18n() call + return 1; + } + QString deviceUdi = args->isSet("udi") ? args->getOption("udi") : QString(); + args->clear(); + + Gwenview::ImageFormats::registerPlugins(); + + Gwenview::ImportDialog* dialog = new Gwenview::ImportDialog(); + dialog->show(); + QMetaObject::invokeMethod(dialog, "setSourceUrl", Qt::QueuedConnection, Q_ARG(KUrl, url), Q_ARG(QString, deviceUdi)); + return app.exec(); +} diff --git a/gwenview/importer/progresspage.cpp b/gwenview/importer/progresspage.cpp new file mode 100644 index 00000000..f0288717 --- /dev/null +++ b/gwenview/importer/progresspage.cpp @@ -0,0 +1,55 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "progresspage.h" + +// Local +#include +#include "importer.h" + +namespace Gwenview +{ + +struct ProgressPagePrivate : public Ui_ProgressPage +{ + ProgressPage* q; + Importer* mImporter; +}; + +ProgressPage::ProgressPage(Importer* importer) +: d(new ProgressPagePrivate) +{ + d->q = this; + d->mImporter = importer; + d->setupUi(this); + + connect(d->mImporter, SIGNAL(progressChanged(int)), + d->mProgressBar, SLOT(setValue(int))); + connect(d->mImporter, SIGNAL(maximumChanged(int)), + d->mProgressBar, SLOT(setMaximum(int))); +} + +ProgressPage::~ProgressPage() +{ + delete d; +} + +} // namespace diff --git a/gwenview/importer/progresspage.h b/gwenview/importer/progresspage.h new file mode 100644 index 00000000..bd1856ad --- /dev/null +++ b/gwenview/importer/progresspage.h @@ -0,0 +1,51 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef PROGRESSPAGE_H +#define PROGRESSPAGE_H + +// Qt +#include + +// KDE +#include + +// Local + +namespace Gwenview +{ + +class Importer; + +struct ProgressPagePrivate; +class ProgressPage : public QWidget +{ + Q_OBJECT +public: + ProgressPage(Importer*); + ~ProgressPage(); + +private: + ProgressPagePrivate* const d; +}; + +} // namespace + +#endif /* PROGRESSPAGE_H */ diff --git a/gwenview/importer/progresspage.ui b/gwenview/importer/progresspage.ui new file mode 100644 index 00000000..6f859c96 --- /dev/null +++ b/gwenview/importer/progresspage.ui @@ -0,0 +1,65 @@ + + + ProgressPage + + + + 0 + 0 + 400 + 300 + + + + + + + Qt::Vertical + + + + 20 + 112 + + + + + + + + Importing documents... + + + + + + + 0 + + + + + + + Qt::Vertical + + + + 20 + 111 + + + + + + + + QDialogButtonBox::Cancel + + + + + + + + diff --git a/gwenview/importer/serializedurlmap.cpp b/gwenview/importer/serializedurlmap.cpp new file mode 100644 index 00000000..172761b4 --- /dev/null +++ b/gwenview/importer/serializedurlmap.cpp @@ -0,0 +1,110 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include + +// Local + +// KDE +#include +#include + +// Qt + +namespace Gwenview +{ + +static const char* KEY_SUFFIX = "key"; +static const char* VALUE_SUFFIX = "value"; + +static KUrl stripPass(const KUrl &url_) +{ + KUrl url = url_; + url.setPass(QString()); + return url; +} + +struct SerializedUrlMapPrivate +{ + KConfigGroup mGroup; + QMap mMap; + + void read() + { + mMap.clear(); + for (int idx=0;; ++idx) { + QString idxString = QString::number(idx); + QString key = idxString + QLatin1String(KEY_SUFFIX); + if (!mGroup.hasKey(key)) { + break; + } + QString keyUrl = mGroup.readEntry(key); + QString valueUrl = mGroup.readEntry(idxString + QLatin1String(VALUE_SUFFIX)); + mMap.insert(keyUrl, valueUrl); + } + } + + void write() + { + mGroup.deleteGroup(); + QMap::ConstIterator it = mMap.constBegin(), end = mMap.constEnd(); + int idx = 0; + for (; it != end; ++it, ++idx) { + QString idxString = QString::number(idx); + mGroup.writeEntry(idxString + QLatin1String(KEY_SUFFIX), it.key().url()); + mGroup.writeEntry(idxString + QLatin1String(VALUE_SUFFIX), it.value().url()); + } + mGroup.sync(); + } +}; + +SerializedUrlMap::SerializedUrlMap() +: d(new SerializedUrlMapPrivate) +{ +} + +SerializedUrlMap::~SerializedUrlMap() +{ + delete d; +} + +void SerializedUrlMap::setConfigGroup(const KConfigGroup& group) +{ + d->mGroup = group; + d->read(); +} + +KUrl SerializedUrlMap::value(const KUrl& key_) const +{ + QString pass = key_.pass(); + KUrl key = stripPass(key_); + KUrl url = d->mMap.value(key); + url.setPass(pass); + return url; +} + +void SerializedUrlMap::insert(const KUrl& key, const KUrl& value) +{ + d->mMap.insert(stripPass(key), stripPass(value)); + d->write(); +} + +} // namespace diff --git a/gwenview/importer/serializedurlmap.h b/gwenview/importer/serializedurlmap.h new file mode 100644 index 00000000..d15918db --- /dev/null +++ b/gwenview/importer/serializedurlmap.h @@ -0,0 +1,58 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef SERIALIZEDURLMAP_H +#define SERIALIZEDURLMAP_H + +// Local + +// KDE + +// Qt + +class KConfigGroup; +class KUrl; + +namespace Gwenview +{ + +struct SerializedUrlMapPrivate; +/** + * A map-like KUrl=>KUrl object, serialized in a KConfigGroup + */ +class SerializedUrlMap +{ +public: + SerializedUrlMap(); + ~SerializedUrlMap(); + + void setConfigGroup(const KConfigGroup&); + + KUrl value(const KUrl&) const; + + void insert(const KUrl& key, const KUrl& value); + +private: + SerializedUrlMapPrivate* const d; +}; + +} // namespace + +#endif /* SERIALIZEDURLMAP_H */ diff --git a/gwenview/importer/thumbnailpage.cpp b/gwenview/importer/thumbnailpage.cpp new file mode 100644 index 00000000..84b9375d --- /dev/null +++ b/gwenview/importer/thumbnailpage.cpp @@ -0,0 +1,455 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "thumbnailpage.moc" + +// Qt +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include + +// Local +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +static const int DEFAULT_THUMBNAIL_SIZE = 128; +static const qreal DEFAULT_THUMBNAIL_ASPECT_RATIO = 3. / 2.; + +static const char* URL_FOR_BASE_URL_GROUP = "UrlForBaseUrl"; + +class ImporterThumbnailViewHelper : public AbstractThumbnailViewHelper +{ +public: + ImporterThumbnailViewHelper(QObject* parent) + : AbstractThumbnailViewHelper(parent) + {} + + void showContextMenu(QWidget*) + {} + + void showMenuForUrlDroppedOnViewport(QWidget*, const KUrl::List&) + {} + + void showMenuForUrlDroppedOnDir(QWidget*, const KUrl::List&, const KUrl&) + {} +}; + +inline KFileItem itemForIndex(const QModelIndex& index) +{ + return index.data(KDirModel::FileItemRole).value(); +} + +struct ThumbnailPagePrivate : public Ui_ThumbnailPage +{ + ThumbnailPage* q; + SerializedUrlMap mUrlMap; + + KIcon mSrcBaseIcon; + QString mSrcBaseName; + KUrl mSrcBaseUrl; + KUrl mSrcUrl; + KModelIndexProxyMapper* mSrcUrlModelProxyMapper; + + RecursiveDirModel* mRecursiveDirModel; + QAbstractItemModel* mFinalModel; + + ThumbnailProvider mThumbnailProvider; + + QPushButton* mImportSelectedButton; + QPushButton* mImportAllButton; + KUrl::List mUrlList; + + void setupDirModel() + { + mRecursiveDirModel = new RecursiveDirModel(q); + + KindProxyModel* kindProxyModel = new KindProxyModel(q); + kindProxyModel->setKindFilter( + MimeTypeUtils::KIND_RASTER_IMAGE + | MimeTypeUtils::KIND_SVG_IMAGE + | MimeTypeUtils::KIND_VIDEO); + kindProxyModel->setSourceModel(mRecursiveDirModel); + + QSortFilterProxyModel *sortModel = new QSortFilterProxyModel(q); + sortModel->setDynamicSortFilter(true); + sortModel->setSourceModel(kindProxyModel); + sortModel->sort(0); + + mFinalModel = sortModel; + + QObject::connect( + mFinalModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + q, SLOT(updateImportButtons())); + QObject::connect( + mFinalModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + q, SLOT(updateImportButtons())); + QObject::connect( + mFinalModel, SIGNAL(modelReset()), + q, SLOT(updateImportButtons())); + } + + void setupIcons() + { + const KIconLoader::Group group = KIconLoader::NoGroup; + const int size = KIconLoader::SizeHuge; + mSrcIconLabel->setPixmap(KIconLoader::global()->loadIcon("camera-photo", group, size)); + mDstIconLabel->setPixmap(KIconLoader::global()->loadIcon("computer", group, size)); + } + + void setupSrcUrlWidgets() + { + mSrcUrlModelProxyMapper = 0; + QObject::connect(mSrcUrlButton, SIGNAL(clicked()), q, SLOT(setupSrcUrlTreeView())); + QObject::connect(mSrcUrlButton, SIGNAL(clicked()), q, SLOT(toggleSrcUrlTreeView())); + mSrcUrlTreeView->hide(); + KAcceleratorManager::setNoAccel(mSrcUrlButton); + } + + void setupDstUrlRequester() + { + mDstUrlRequester->setMode(KFile::Directory | KFile::LocalOnly); + } + + void setupThumbnailView() + { + mThumbnailView->setModel(mFinalModel); + + mThumbnailView->setSelectionMode(QAbstractItemView::ExtendedSelection); + mThumbnailView->setThumbnailViewHelper(new ImporterThumbnailViewHelper(q)); + + PreviewItemDelegate* delegate = new PreviewItemDelegate(mThumbnailView); + delegate->setThumbnailDetails(PreviewItemDelegate::FileNameDetail); + delegate->setContextBarActions(PreviewItemDelegate::SelectionAction); + mThumbnailView->setItemDelegate(delegate); + + // Colors + int value = GwenviewConfig::viewBackgroundValue(); + QColor bgColor = QColor::fromHsv(0, 0, value); + QColor fgColor = value > 128 ? Qt::black : Qt::white; + + QPalette pal = mThumbnailView->palette(); + pal.setColor(QPalette::Base, bgColor); + pal.setColor(QPalette::Text, fgColor); + + mThumbnailView->setPalette(pal); + + QObject::connect(mSlider, SIGNAL(valueChanged(int)), + mThumbnailView, SLOT(setThumbnailWidth(int))); + QObject::connect(mThumbnailView, SIGNAL(thumbnailWidthChanged(int)), + mSlider, SLOT(setValue(int))); + int thumbnailSize = DEFAULT_THUMBNAIL_SIZE; + mSlider->setValue(thumbnailSize); + mSlider->updateToolTip(); + mThumbnailView->setThumbnailAspectRatio(DEFAULT_THUMBNAIL_ASPECT_RATIO); + mThumbnailView->setThumbnailWidth(thumbnailSize); + mThumbnailView->setThumbnailProvider(&mThumbnailProvider); + + QObject::connect( + mThumbnailView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + q, SLOT(updateImportButtons())); + } + + void setupButtonBox() + { + QObject::connect(mConfigureButton, SIGNAL(clicked()), + q, SLOT(showConfigDialog())); + + mImportSelectedButton = mButtonBox->addButton( + i18n("Import Selected"), QDialogButtonBox::AcceptRole, + q, SLOT(slotImportSelected())); + + mImportAllButton = mButtonBox->addButton( + i18n("Import All"), QDialogButtonBox::AcceptRole, + q, SLOT(slotImportAll())); + + QObject::connect( + mButtonBox, SIGNAL(rejected()), + q, SIGNAL(rejected())); + } + + KUrl urlForBaseUrl() const + { + KUrl url = mUrlMap.value(mSrcBaseUrl); + if (!url.isValid()) { + return KUrl(); + } + + KIO::UDSEntry entry; + bool ok = KIO::NetAccess::stat(url, entry, q); + if (!ok) { + return KUrl(); + } + KFileItem item(entry, url, true /* delayedMimeTypes */); + return item.isDir() ? url : KUrl(); + } + + void rememberUrl(const KUrl& url) + { + mUrlMap.insert(mSrcBaseUrl, url); + } +}; + +ThumbnailPage::ThumbnailPage() +: d(new ThumbnailPagePrivate) +{ + d->q = this; + d->mUrlMap.setConfigGroup(KConfigGroup(KGlobal::config(), URL_FOR_BASE_URL_GROUP)); + d->setupUi(this); + d->setupIcons(); + d->setupDirModel(); + d->setupSrcUrlWidgets(); + d->setupDstUrlRequester(); + d->setupThumbnailView(); + d->setupButtonBox(); + updateImportButtons(); +} + +ThumbnailPage::~ThumbnailPage() +{ + delete d; +} + +void ThumbnailPage::setSourceUrl(const KUrl& srcBaseUrl, const QString& iconName, const QString& name) +{ + d->mSrcBaseIcon = KIcon(iconName); + d->mSrcBaseName = name; + + const int size = KIconLoader::SizeHuge; + d->mSrcIconLabel->setPixmap(d->mSrcBaseIcon.pixmap(size)); + + d->mSrcBaseUrl = srcBaseUrl; + d->mSrcBaseUrl.adjustPath(KUrl::AddTrailingSlash); + KUrl url = d->urlForBaseUrl(); + + if (url.isValid()) { + openUrl(url); + } else { + DocumentDirFinder* finder = new DocumentDirFinder(srcBaseUrl); + connect(finder, SIGNAL(done(KUrl,DocumentDirFinder::Status)), + SLOT(slotDocumentDirFinderDone(KUrl,DocumentDirFinder::Status))); + finder->start(); + } +} + +void ThumbnailPage::slotDocumentDirFinderDone(const KUrl& url, DocumentDirFinder::Status /*status*/) +{ + d->rememberUrl(url); + openUrl(url); +} + +void ThumbnailPage::openUrl(const KUrl& url) +{ + d->mSrcUrl = url; + QString path = KUrl::relativeUrl(d->mSrcBaseUrl, d->mSrcUrl); + QString text; + if (path.isEmpty() || path == "./") { + text = d->mSrcBaseName; + } else { + path = QUrl::fromPercentEncoding(path.toUtf8()); + path.replace("/", QString::fromUtf8(" › ")); + text = QString::fromUtf8("%1 › %2").arg(d->mSrcBaseName).arg(path); + } + d->mSrcUrlButton->setText(text); + d->mRecursiveDirModel->setUrl(url); +} + +KUrl::List ThumbnailPage::urlList() const +{ + return d->mUrlList; +} + +void ThumbnailPage::setDestinationUrl(const KUrl& url) +{ + d->mDstUrlRequester->setUrl(url); +} + +KUrl ThumbnailPage::destinationUrl() const +{ + return d->mDstUrlRequester->url(); +} + +void ThumbnailPage::slotImportSelected() +{ + importList(d->mThumbnailView->selectionModel()->selectedIndexes()); +} + +void ThumbnailPage::slotImportAll() +{ + QModelIndexList list; + QAbstractItemModel* model = d->mThumbnailView->model(); + for (int row = model->rowCount() - 1; row >= 0; --row) { + list << model->index(row, 0); + } + importList(list); +} + +void ThumbnailPage::importList(const QModelIndexList& list) +{ + d->mUrlList.clear(); + Q_FOREACH(const QModelIndex & index, list) { + KFileItem item = itemForIndex(index); + if (!ArchiveUtils::fileItemIsDirOrArchive(item)) { + d->mUrlList << item.url(); + } + // FIXME: Handle dirs (do we want to import recursively?) + } + emit importRequested(); +} + +void ThumbnailPage::updateImportButtons() +{ + d->mImportSelectedButton->setEnabled(d->mThumbnailView->selectionModel()->hasSelection()); + d->mImportAllButton->setEnabled(d->mThumbnailView->model()->rowCount(QModelIndex()) > 0); +} + +void ThumbnailPage::showConfigDialog() +{ + ImporterConfigDialog dialog(this); + dialog.exec(); +} + +/** + * This model allows only the url passed in the constructor to appear at the root + * level. This makes it possible to select the url, but not its siblings. + * It also provides custom role values for the root item. + */ +class OnlyBaseUrlProxyModel : public QSortFilterProxyModel +{ +public: + OnlyBaseUrlProxyModel(const KUrl& url, const KIcon& icon, const QString& name, QObject* parent) + : QSortFilterProxyModel(parent) + , mUrl(url) + , mIcon(icon) + , mName(name) + {} + + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const // reimp + { + if (sourceParent.isValid()) { + return true; + } + QModelIndex index = sourceModel()->index(sourceRow, 0); + KFileItem item = itemForIndex(index); + return item.url().equals(mUrl, KUrl::CompareWithoutTrailingSlash); + } + + QVariant data(const QModelIndex& index, int role) const // reimp + { + if (index.parent().isValid()) { + return QSortFilterProxyModel::data(index, role); + } + switch (role) { + case Qt::DisplayRole: + return mName; + case Qt::DecorationRole: + return mIcon; + case Qt::ToolTipRole: + return mUrl.pathOrUrl(); + default: + return QSortFilterProxyModel::data(index, role); + } + } + +private: + KUrl mUrl; + KIcon mIcon; + QString mName; +}; + +void ThumbnailPage::setupSrcUrlTreeView() +{ + if (d->mSrcUrlTreeView->model()) { + // Already initialized + return; + } + KDirModel* dirModel = new KDirModel(this); + dirModel->dirLister()->setDirOnlyMode(true); + dirModel->dirLister()->openUrl(d->mSrcBaseUrl.upUrl()); + + OnlyBaseUrlProxyModel* onlyBaseUrlModel = new OnlyBaseUrlProxyModel(d->mSrcBaseUrl, d->mSrcBaseIcon, d->mSrcBaseName, this); + onlyBaseUrlModel->setSourceModel(dirModel); + + QSortFilterProxyModel* sortModel = new QSortFilterProxyModel(this); + sortModel->setDynamicSortFilter(true); + sortModel->setSourceModel(onlyBaseUrlModel); + sortModel->sort(0); + + d->mSrcUrlModelProxyMapper = new KModelIndexProxyMapper(dirModel, sortModel, this); + + d->mSrcUrlTreeView->setModel(sortModel); + for(int i = 1; i < dirModel->columnCount(); ++i) { + d->mSrcUrlTreeView->hideColumn(i); + } + connect(d->mSrcUrlTreeView, SIGNAL(activated(QModelIndex)), SLOT(openUrlFromIndex(QModelIndex))); + connect(d->mSrcUrlTreeView, SIGNAL(clicked(QModelIndex)), SLOT(openUrlFromIndex(QModelIndex))); + + dirModel->expandToUrl(d->mSrcUrl); + connect(dirModel, SIGNAL(expand(QModelIndex)), SLOT(slotSrcUrlModelExpand(QModelIndex))); +} + +void ThumbnailPage::slotSrcUrlModelExpand(const QModelIndex& index) +{ + QModelIndex viewIndex = d->mSrcUrlModelProxyMapper->mapLeftToRight(index); + d->mSrcUrlTreeView->expand(viewIndex); + KFileItem item = itemForIndex(index); + if (item.url() == d->mSrcUrl) { + d->mSrcUrlTreeView->selectionModel()->select(viewIndex, QItemSelectionModel::ClearAndSelect); + } +} + +void ThumbnailPage::toggleSrcUrlTreeView() +{ + d->mSrcUrlTreeView->setVisible(!d->mSrcUrlTreeView->isVisible()); +} + +void ThumbnailPage::openUrlFromIndex(const QModelIndex& index) +{ + KFileItem item = itemForIndex(index); + if (item.isNull()) { + return; + } + KUrl url = item.url(); + d->rememberUrl(url); + openUrl(url); +} + +} // namespace diff --git a/gwenview/importer/thumbnailpage.h b/gwenview/importer/thumbnailpage.h new file mode 100644 index 00000000..4f74482a --- /dev/null +++ b/gwenview/importer/thumbnailpage.h @@ -0,0 +1,80 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef THUMBNAILPAGE_H +#define THUMBNAILPAGE_H + +// Qt +#include +#include + +// KDE +#include + +// Local +#include "documentdirfinder.h" + +namespace Gwenview +{ + +struct ThumbnailPagePrivate; +class ThumbnailPage : public QWidget +{ + Q_OBJECT +public: + ThumbnailPage(); + ~ThumbnailPage(); + + /** + * Returns the list of urls to import + * Only valid after importRequested() has been emitted + */ + KUrl::List urlList() const; + + KUrl destinationUrl() const; + void setDestinationUrl(const KUrl&); + + void setSourceUrl(const KUrl&, const QString& icon, const QString& label); + +Q_SIGNALS: + void importRequested(); + void rejected(); + +private Q_SLOTS: + void slotImportSelected(); + void slotImportAll(); + void updateImportButtons(); + void openUrl(const KUrl&); + void slotDocumentDirFinderDone(const KUrl& url, DocumentDirFinder::Status status); + void showConfigDialog(); + void openUrlFromIndex(const QModelIndex& index); + void setupSrcUrlTreeView(); + void toggleSrcUrlTreeView(); + void slotSrcUrlModelExpand(const QModelIndex& index); + +private: + friend struct ThumbnailPagePrivate; + ThumbnailPagePrivate* const d; + void importList(const QModelIndexList&); +}; + +} // namespace + +#endif /* THUMBNAILPAGE_H */ diff --git a/gwenview/importer/thumbnailpage.ui b/gwenview/importer/thumbnailpage.ui new file mode 100644 index 00000000..ae953aee --- /dev/null +++ b/gwenview/importer/thumbnailpage.ui @@ -0,0 +1,285 @@ + + + ThumbnailPage + + + + 0 + 0 + 437 + 487 + + + + QLabel[isTitle=true] { font-weight: bold } + +QLabel[isIcon=true] { + border: 1px solid palette(mid); + background-color: palette(base); + padding: 12px; +} + + + + + + + + + 0 + 0 + + + + [icon] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 13 + + + + + + + + + 0 + 0 + + + + [icon] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + + 0 + 0 + + + + + 0 + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + 1 + 0 + + + + QFrame::NoFrame + + + QAbstractItemView::NoEditTriggers + + + false + + + true + + + + + + 4 + 0 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + + + + + + + + + Select the documents to import + + + true + + + + + + + + + Settings... + + + + + + + QDialogButtonBox::Cancel + + + + + + + + + Enter the import destination + + + mDstUrlRequester + + + true + + + + + + + + + + + + + Listing content of: + + + mSrcUrlButton + + + + + + + + + + Qt::Horizontal + + + + 56 + 20 + + + + + + + + + + + + KDialogButtonBox + QDialogButtonBox +
kdialogbuttonbox.h
+
+ + KPushButton + QPushButton +
kpushbutton.h
+
+ + KUrlRequester + QFrame +
kurlrequester.h
+
+ + Gwenview::ThumbnailView + QListView +
lib/thumbnailview/thumbnailview.h
+
+ + Gwenview::ThumbnailSlider + QWidget +
lib/thumbnailview/thumbnailslider.h
+
+
+ + mSrcUrlButton + mSrcUrlTreeView + mThumbnailView + mDstUrlRequester + mConfigureButton + mButtonBox + + + +
diff --git a/gwenview/lib/CMakeLists.txt b/gwenview/lib/CMakeLists.txt new file mode 100644 index 00000000..a4ddb141 --- /dev/null +++ b/gwenview/lib/CMakeLists.txt @@ -0,0 +1,238 @@ +project(gwenviewlib) + +# Extract version of libjpeg so that we can use the appropriate dir +# See bug #227313 +message(STATUS "Looking for libjpeg version in ${JPEG_INCLUDE_DIR}/jpeglib.h") +file(READ "${JPEG_INCLUDE_DIR}/jpeglib.h" jpeglib_h_content) +string(REGEX MATCH "#define +JPEG_LIB_VERSION +([0-9]+)" "\\1" jpeglib_version "${jpeglib_h_content}") +set(jpeglib_version ${CMAKE_MATCH_1}) + +if ("${jpeglib_version}" STREQUAL "") + message(STATUS "No version defined in ${JPEG_INCLUDE_DIR}/jpeglib.h, looking for jconfig.h") + # libjpeg-turbo keeps JPEG_LIB_VERSION in jconfig.h, not jpeglib.h :/ + find_file(JCONFIG_H jconfig.h + PATHS "${JPEG_INCLUDE_DIR}" + PATH_SUFFIXES "${CMAKE_LIBRARY_ARCHITECTURE}" + ) + if (NOT EXISTS "${JCONFIG_H}") + message(FATAL_ERROR "Could not find jconfig.h. This file comes with libjpeg. You can specify its path with -DJCONFIG_H=/path/to/jconfig.h.") + endif() + message(STATUS "Found jconfig.h: ${JCONFIG_H}") + message(STATUS "Looking for libjpeg version in ${JCONFIG_H}") + file(READ "${JCONFIG_H}" jconfig_h_content) + string(REGEX MATCH "#define +JPEG_LIB_VERSION +([0-9]+)" "\\1" jpeglib_version "${jconfig_h_content}") + set(jpeglib_version ${CMAKE_MATCH_1}) +endif() + +if ("${jpeglib_version}" LESS 80) + set(GV_JPEG_DIR libjpeg-62) +endif() + +if ("${jpeglib_version}" EQUAL 80) + set(GV_JPEG_DIR libjpeg-80) +endif() + +if ("${jpeglib_version}" EQUAL 90) + set(GV_JPEG_DIR libjpeg-90) +endif() + +if ("${GV_JPEG_DIR}" STREQUAL "") + message(FATAL_ERROR "Unknown libjpeg version: ${jpeglib_version}") +endif() + +message(STATUS "libjpeg version: ${jpeglib_version}") + +add_definitions(-Dlibjpeg_EXPORTS) +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/${GV_JPEG_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_BINARY_DIR} + ${EXIV2_INCLUDE_DIR} + ${JPEG_INCLUDE_DIR} + ${PNG_INCLUDE_DIRS} + ) + +# For config-gwenview.h +include_directories( + ${CMAKE_CURRENT_BINARY_DIR}/.. + ) + +set(gwenviewlib_SRCS + cms/iccjpeg.c + cms/cmsprofile.cpp + cms/cmsprofile_png.cpp + contextmanager.cpp + crop/cropwidget.cpp + crop/cropimageoperation.cpp + crop/croptool.cpp + document/abstractdocumentimpl.cpp + document/documentjob.cpp + document/animateddocumentloadedimpl.cpp + document/document.cpp + document/documentfactory.cpp + document/documentloadedimpl.cpp + document/emptydocumentimpl.cpp + document/jpegdocumentloadedimpl.cpp + document/loadingdocumentimpl.cpp + document/loadingjob.cpp + document/savejob.cpp + document/svgdocumentloadedimpl.cpp + document/videodocumentloadedimpl.cpp + documentview/abstractdocumentviewadapter.cpp + documentview/abstractimageview.cpp + documentview/abstractrasterimageviewtool.cpp + documentview/birdeyeview.cpp + documentview/documentview.cpp + documentview/documentviewcontroller.cpp + documentview/documentviewsynchronizer.cpp + documentview/loadingindicator.cpp + documentview/messageviewadapter.cpp + documentview/rasterimageview.cpp + documentview/rasterimageviewadapter.cpp + documentview/svgviewadapter.cpp + documentview/videoviewadapter.cpp + about.cpp + abstractimageoperation.cpp + disabledactionshortcutmonitor.cpp + documentonlyproxymodel.cpp + documentview/documentviewcontainer.cpp + binder.cpp + eventwatcher.cpp + historymodel.cpp + archiveutils.cpp + datewidget.cpp + exiv2imageloader.cpp + flowlayout.cpp + fullscreenbar.cpp + hud/hudbutton.cpp + hud/hudbuttonbox.cpp + hud/hudcountdown.cpp + hud/hudlabel.cpp + hud/hudmessagebubble.cpp + hud/hudslider.cpp + hud/hudtheme.cpp + hud/hudwidget.cpp + graphicswidgetfloater.cpp + imageformats/imageformats.cpp + imageformats/jpegplugin.cpp + imageformats/jpeghandler.cpp + imagemetainfomodel.cpp + imagescaler.cpp + imageutils.cpp + invisiblebuttongroup.cpp + iodevicejpegsourcemanager.cpp + jpegcontent.cpp + kindproxymodel.cpp + semanticinfo/sorteddirmodel.cpp + memoryutils.cpp + mimetypeutils.cpp + paintutils.cpp + placetreemodel.cpp + preferredimagemetainfomodel.cpp + print/printhelper.cpp + print/printoptionspage.cpp + recursivedirmodel.cpp + shadowfilter.cpp + slidecontainer.cpp + slideshow.cpp + statusbartoolbutton.cpp + redeyereduction/redeyereductionimageoperation.cpp + redeyereduction/redeyereductiontool.cpp + resize/resizeimageoperation.cpp + resize/resizeimagedialog.cpp + thumbnailprovider/thumbnailgenerator.cpp + thumbnailprovider/thumbnailprovider.cpp + thumbnailprovider/thumbnailwriter.cpp + thumbnailview/abstractthumbnailviewhelper.cpp + thumbnailview/abstractdocumentinfoprovider.cpp + thumbnailview/contextbarbutton.cpp + thumbnailview/dragpixmapgenerator.cpp + thumbnailview/itemeditor.cpp + thumbnailview/previewitemdelegate.cpp + thumbnailview/thumbnailbarview.cpp + thumbnailview/thumbnailslider.cpp + thumbnailview/thumbnailview.cpp + thumbnailview/tooltipwidget.cpp + timeutils.cpp + transformimageoperation.cpp + urlutils.cpp + widgetfloater.cpp + zoomslider.cpp + zoomwidget.cpp + ${GV_JPEG_DIR}/transupp.c + ) + +if (NOT GWENVIEW_SEMANTICINFO_BACKEND_NONE) + set(gwenviewlib_SRCS + ${gwenviewlib_SRCS} + semanticinfo/abstractsemanticinfobackend.cpp + semanticinfo/semanticinfodirmodel.cpp + semanticinfo/tagitemdelegate.cpp + semanticinfo/tagmodel.cpp + semanticinfo/tagwidget.cpp + ) +endif() + +if (GWENVIEW_SEMANTICINFO_BACKEND_FAKE) + set(gwenviewlib_SRCS + ${gwenviewlib_SRCS} + semanticinfo/fakesemanticinfobackend.cpp + ) +endif() + +if (GWENVIEW_SEMANTICINFO_BACKEND_BALOO) + set(gwenviewlib_SRCS + ${gwenviewlib_SRCS} + semanticinfo/baloosemanticinfobackend.cpp + ) +endif() + +set_source_files_properties( + exiv2imageloader.cpp + imagemetainfomodel.cpp + timeutils.cpp + PROPERTIES + COMPILE_FLAGS "${KDE4_ENABLE_EXCEPTIONS}" + ) + +kde4_add_ui_files(gwenviewlib_SRCS + crop/cropwidget.ui + documentview/messageview.ui + print/printoptionspage.ui + redeyereduction/redeyereductionwidget.ui + resize/resizeimagewidget.ui + ) + +kde4_add_kcfg_files(gwenviewlib_SRCS + gwenviewconfig.kcfgc + ) + +kde4_add_library(gwenviewlib SHARED ${gwenviewlib_SRCS}) +set_target_properties(gwenviewlib PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION}) +if (WIN32) + set_target_properties(gwenviewlib PROPERTIES COMPILE_FLAGS -DJPEG_STATIC) +endif() + +target_link_libraries(gwenviewlib + ${KDE4_KFILE_LIBS} + ${KDE4_KIO_LIBS} + ${KDE4_PHONON_LIBS} + ${JPEG_LIBRARY} + ${EXIV2_LIBRARIES} + ${QT_QTOPENGL_LIBRARY} + ${X11_X11_LIB} + ${PNG_LIBRARIES} + ${LCMS2_LIBRARIES} + ${KDCRAW_LIBRARIES} + ) +if (WIN32) + target_link_libraries(gwenviewlib ${EXPAT_LIBRARIES}) +endif() + +if (NOT GWENVIEW_SEMANTICINFO_BACKEND_NONE) + target_link_libraries(gwenviewlib + ${BALOO_LIBRARIES} + ) +endif() + +install(TARGETS gwenviewlib ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/gwenview/lib/about.cpp b/gwenview/lib/about.cpp new file mode 100644 index 00000000..917b14ca --- /dev/null +++ b/gwenview/lib/about.cpp @@ -0,0 +1,50 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +// Self +#include + +// Local +#include + +// KDE +#include +#include + +// Qt + +namespace Gwenview +{ + +KAboutData* createAboutData(const QByteArray& appName, const QByteArray& catalogName, const KLocalizedString& programName) +{ + KAboutData* data = new KAboutData(appName, catalogName, programName, GWENVIEW_VERSION); + data->setLicense(KAboutData::License_GPL); + data->setCopyrightStatement(ki18n("Copyright 2000-2013 Gwenview authors")); + data->addAuthor( + ki18n("Aurélien Gâteau"), + ki18n("Main developer"), + "agateau@kde.org"); + data->addAuthor( + ki18n("Benjamin Löwe"), + ki18n("Developer"), + "benni@mytum.de"); + return data; +} + +} // namespace diff --git a/gwenview/lib/about.h b/gwenview/lib/about.h new file mode 100644 index 00000000..099b049e --- /dev/null +++ b/gwenview/lib/about.h @@ -0,0 +1,41 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2013 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef ABOUT_H +#define ABOUT_H + +#include + +// Local + +// KDE + +// Qt + +class KAboutData; +class QByteArray; +class KLocalizedString; + +namespace Gwenview +{ + +GWENVIEWLIB_EXPORT KAboutData* createAboutData(const QByteArray& appName, const QByteArray& catalogName, const KLocalizedString& programName); + +} // namespace + +#endif /* ABOUT_H */ diff --git a/gwenview/lib/abstractimageoperation.cpp b/gwenview/lib/abstractimageoperation.cpp new file mode 100644 index 00000000..cb2e5e4a --- /dev/null +++ b/gwenview/lib/abstractimageoperation.cpp @@ -0,0 +1,114 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "abstractimageoperation.moc" + +// Qt + +// KDE +#include +#include + +// Local +#include "document/documentfactory.h" +#include "document/documentjob.h" + +namespace Gwenview +{ + +class ImageOperationCommand : public QUndoCommand +{ +public: + ImageOperationCommand(AbstractImageOperation* op) + : mOp(op) + {} + + ~ImageOperationCommand() + { + delete mOp; + } + + virtual void undo() + { + mOp->undo(); + } + +private: + AbstractImageOperation* mOp; +}; + +struct AbstractImageOperationPrivate +{ + QString mText; + KUrl mUrl; +}; + +AbstractImageOperation::AbstractImageOperation() +: d(new AbstractImageOperationPrivate) +{ +} + +AbstractImageOperation::~AbstractImageOperation() +{ + delete d; +} + +void AbstractImageOperation::applyToDocument(Document::Ptr doc) +{ + d->mUrl = doc->url(); + redo(); +} + +Document::Ptr AbstractImageOperation::document() const +{ + Document::Ptr doc = DocumentFactory::instance()->load(d->mUrl); + doc->startLoadingFullImage(); + return doc; +} + +void AbstractImageOperation::finish(bool ok) +{ + if (ok) { + ImageOperationCommand* command = new ImageOperationCommand(this); + command->setText(d->mText); + document()->undoStack()->push(command); + } else { + deleteLater(); + } +} + +void AbstractImageOperation::finishFromKJob(KJob* job) +{ + finish(job->error() == KJob::NoError); +} + +void AbstractImageOperation::setText(const QString& text) +{ + d->mText = text; +} + +void AbstractImageOperation::redoAsDocumentJob(DocumentJob* job) +{ + connect(job, SIGNAL(result(KJob*)), SLOT(finishFromKJob(KJob*))); + document()->enqueueJob(job); +} + +} // namespace diff --git a/gwenview/lib/abstractimageoperation.h b/gwenview/lib/abstractimageoperation.h new file mode 100644 index 00000000..51b09530 --- /dev/null +++ b/gwenview/lib/abstractimageoperation.h @@ -0,0 +1,89 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef ABSTRACTIMAGEOPERATION_H +#define ABSTRACTIMAGEOPERATION_H + +#include + +// Qt +#include + +// KDE + +// Local +#include + +class KJob; + +namespace Gwenview +{ + +struct AbstractImageOperationPrivate; + +/** + * An operation to apply on a document. This class pushes internal instances of + * QUndoCommand to the document undo stack, but it only does so if the + * operation succeeded. + * + * Class inheriting from this class should: + * - Implement redo() and call finish() or finishFromKJob() when done + * - Implement undo() + * - Define the operation/command text with setText() + */ +class GWENVIEWLIB_EXPORT AbstractImageOperation : public QObject +{ + Q_OBJECT +public: + AbstractImageOperation(); + virtual ~AbstractImageOperation(); + + void applyToDocument(Document::Ptr); + Document::Ptr document() const; + +protected: + virtual void redo() = 0; + virtual void undo() + {} + void setText(const QString&); + + /** + * Convenience method which can be called from redo() if the operation is + * implemented as a job + */ + void redoAsDocumentJob(DocumentJob* job); + +protected Q_SLOTS: + void finish(bool ok); + + /** + * Convenience slot which call finish() correctly if job succeeded + */ + void finishFromKJob(KJob* job); + +private: + AbstractImageOperationPrivate* const d; + + friend class ImageOperationCommand; +}; + +} // namespace + +#endif /* ABSTRACTIMAGEOPERATION_H */ diff --git a/gwenview/lib/archiveutils.cpp b/gwenview/lib/archiveutils.cpp new file mode 100644 index 00000000..2de887a9 --- /dev/null +++ b/gwenview/lib/archiveutils.cpp @@ -0,0 +1,87 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab +/* +Gwenview - A simple image viewer for KDE +Copyright 2000-2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "archiveutils.h" + +// KDE +#include +#include +#include +#include + +namespace Gwenview +{ + +namespace ArchiveUtils +{ + +bool fileItemIsArchive(const KFileItem& item) +{ + KMimeType::Ptr mimeType = item.determineMimeType(); + if (!mimeType) { + kWarning() << "determineMimeType() returned a null pointer"; + return false; + } + return !ArchiveUtils::protocolForMimeType(mimeType->name()).isEmpty(); +} + +bool fileItemIsDirOrArchive(const KFileItem& item) +{ + return item.isDir() || fileItemIsArchive(item); +} + +QString protocolForMimeType(const QString& mimeType) +{ + static QHash cache; + QHash::ConstIterator it = cache.constFind(mimeType); + if (it != cache.constEnd()) { + return it.value(); + } + + if (mimeType == "image/svg+xml-compressed") { + // We don't want .svgz to be considered as archives because QtSvg knows + // how to decode gzip-ed svg files + cache.insert(mimeType, QString()); + return QString(); + } + + QString protocol = KProtocolManager::protocolForArchiveMimetype(mimeType); + if (protocol.isEmpty()) { + // No protocol, try with mimeType parents. This is useful for .cbz for + // example + KMimeType::Ptr ptr = KMimeType::mimeType(mimeType); + if (ptr) { + Q_FOREACH(const QString & parentMimeType, ptr->allParentMimeTypes()) { + protocol = KProtocolManager::protocolForArchiveMimetype(parentMimeType); + if (!protocol.isEmpty()) { + break; + } + } + } + } + + cache.insert(mimeType, protocol); + return protocol; +} + +} // namespace ArchiveUtils + +} // namespace Gwenview diff --git a/gwenview/lib/archiveutils.h b/gwenview/lib/archiveutils.h new file mode 100644 index 00000000..db797712 --- /dev/null +++ b/gwenview/lib/archiveutils.h @@ -0,0 +1,61 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab +/* +Gwenview - A simple image viewer for KDE +Copyright 2000-2004 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef ARCHIVEUTILS_H +#define ARCHIVEUTILS_H + +#include + +// Qt +#include + +class KFileItem; + +namespace Gwenview +{ + +/** + * Helper functions to deal with archives + */ +namespace ArchiveUtils +{ + +/** + * Returns true if @p item is an archive + */ +GWENVIEWLIB_EXPORT bool fileItemIsArchive(const KFileItem& item); + +/** + * Returns true if @p item is a dir or an archive + */ +GWENVIEWLIB_EXPORT bool fileItemIsDirOrArchive(const KFileItem& item); + +/** + * Returns the protocol for an archive mime type. Similar to + * KProtocolManager::protocolForArchiveMimetype(), except it tries parent + * mimetypes if it can't find anything (useful for .cbz and co) + */ +GWENVIEWLIB_EXPORT QString protocolForMimeType(const QString& mimeType); + +} // namespace ArchiveUtils + +} // namespace Gwenview +#endif + diff --git a/gwenview/lib/binder.cpp b/gwenview/lib/binder.cpp new file mode 100644 index 00000000..aaa6c3bf --- /dev/null +++ b/gwenview/lib/binder.cpp @@ -0,0 +1,36 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include + +namespace Gwenview +{ + +BinderInternal::BinderInternal(QObject* parent) +: QObject(parent) +{ +} + +BinderInternal::~BinderInternal() +{ +} + +} // namespace diff --git a/gwenview/lib/binder.h b/gwenview/lib/binder.h new file mode 100644 index 00000000..b5573ba7 --- /dev/null +++ b/gwenview/lib/binder.h @@ -0,0 +1,125 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef BINDER_H +#define BINDER_H + +#include + +// Qt +#include + +namespace Gwenview +{ + +/** + * @internal + * + * Necessary helper class because a QObject class cannot be a template + */ +class GWENVIEWLIB_EXPORT BinderInternal : public QObject +{ + Q_OBJECT +public: + explicit BinderInternal(QObject* parent); + ~BinderInternal(); + +protected Q_SLOTS: + virtual void callMethod() + {} +}; + +/** + * The Binder and BinderRef classes make it possible to "connect" a + * parameter-less signal with a slot which accepts one argument. The + * argument must be known at connection time. + * + * Example: + * + * Assuming a class like this: + * + * class Receiver + * { + * public: + * void doSomething(Param* p); + * }; + * + * This code: + * + * Binder::bind(emitter, SIGNAL(somethingHappened()), receiver, &Receiver::doSomething, p) + * + * Will result in receiver->doSomething(p) being called when emitter emits + * the somethingHappened() signal. + * + * Just like a regular QObject connection, the connection will last until + * either emitter or receiver are deleted. + * + * Using this system avoids creating an helper slot and adding a member to + * the Receiver class to store the argument of the method to call. + * + * To call a method which accept a pointer or a value argument, use Binder. + * To call a method which accept a const reference argument, use BinderRef. + * + * Note: the method does not need to be a slot. + */ +template +class BaseBinder : public BinderInternal +{ +public: + typedef void (Receiver::*Method)(MethodArg); + static void bind(QObject* emitter, const char* signal, Receiver* receiver, Method method, MethodArg arg) + { + BaseBinder* binder = new BaseBinder(emitter); + binder->mReceiver = receiver; + binder->mMethod = method; + binder->mArg = arg; + QObject::connect(emitter, signal, binder, SLOT(callMethod())); + QObject::connect(receiver, SIGNAL(destroyed(QObject*)), binder, SLOT(deleteLater())); + } + +protected: + void callMethod() + { + (mReceiver->*mMethod)(mArg); + } + +private: + BaseBinder(QObject* emitter) + : BinderInternal(emitter) + , mReceiver(0) + , mMethod(0) + {} + + Receiver* mReceiver; + Method mMethod; + Arg mArg; +}; + +template +class Binder : public BaseBinder +{}; + +template +class BinderRef : public BaseBinder +{}; + +} // namespace + +#endif /* BINDER_H */ diff --git a/gwenview/lib/cms/cmsprofile.cpp b/gwenview/lib/cms/cmsprofile.cpp new file mode 100644 index 00000000..205c21dd --- /dev/null +++ b/gwenview/lib/cms/cmsprofile.cpp @@ -0,0 +1,249 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "cmsprofile.h" + +// Local +#include +#include +#include +#include + +extern "C" { +#include +} + +// KDE +#include + +// Qt +#include + +// lcms +#include + +// X11 +#ifdef Q_WS_X11 +#include +#include +#include +#include +#endif + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +namespace Cms +{ + +//- JPEG ----------------------------------------------------------------------- +static cmsHPROFILE loadFromJpegData(const QByteArray& data) +{ + cmsHPROFILE profile = 0; + struct jpeg_decompress_struct srcinfo; + + JPEGErrorManager srcErrorManager; + srcinfo.err = &srcErrorManager; + jpeg_create_decompress(&srcinfo); + if (setjmp(srcErrorManager.jmp_buffer)) { + kError() << "libjpeg error in src\n"; + return 0; + } + + QBuffer buffer(const_cast(&data)); + buffer.open(QIODevice::ReadOnly); + IODeviceJpegSourceManager::setup(&srcinfo, &buffer); + + setup_read_icc_profile(&srcinfo); + jpeg_read_header(&srcinfo, true); + jpeg_start_decompress(&srcinfo); + + uchar* profile_data; + uint profile_len; + if (read_icc_profile(&srcinfo, &profile_data, &profile_len)) { + LOG("Found a profile, length:" << profile_len); + profile = cmsOpenProfileFromMem(profile_data, profile_len); + } + + jpeg_destroy_decompress(&srcinfo); + + return profile; +} + +//- Profile class -------------------------------------------------------------- +struct ProfilePrivate +{ + cmsHPROFILE mProfile; + + void reset() + { + if (mProfile) { + cmsCloseProfile(mProfile); + } + mProfile = 0; + } + + QString readInfo(cmsInfoType info) + { + GV_RETURN_VALUE_IF_FAIL(mProfile, QString()); + wchar_t buffer[1024]; + int size = cmsGetProfileInfo(mProfile, info, "en", "US", buffer, 1024); + return QString::fromWCharArray(buffer, size); + } +}; + +Profile::Profile() +: d(new ProfilePrivate) +{ + d->mProfile = 0; +} + +Profile::Profile(cmsHPROFILE hProfile) +: d(new ProfilePrivate) +{ + d->mProfile = hProfile; +} + +Profile::~Profile() +{ + d->reset(); + delete d; +} + +Profile::Ptr Profile::loadFromImageData(const QByteArray& data, const QByteArray& format) +{ + Profile::Ptr ptr; + cmsHPROFILE hProfile = 0; + if (format == "png") { + hProfile = loadFromPngData(data); + } + if (format == "jpeg") { + hProfile = loadFromJpegData(data); + } + if (hProfile) { + ptr = new Profile(hProfile); + } + return ptr; +} + +Profile::Ptr Profile::loadFromExiv2Image(const Exiv2::Image* image) +{ + Profile::Ptr ptr; + cmsHPROFILE hProfile = 0; + + const Exiv2::ExifData& exifData = image->exifData(); + Exiv2::ExifKey key("Exif.Image.InterColorProfile"); + Exiv2::ExifData::const_iterator it = exifData.findKey(key); + if (it == exifData.end()) { + LOG("No profile found"); + return ptr; + } + int size = it->size(); + LOG("size:" << size); + + QByteArray data; + data.resize(size); + it->copy(reinterpret_cast(data.data()), Exiv2::invalidByteOrder); + hProfile = cmsOpenProfileFromMem(data, size); + + if (hProfile) { + ptr = new Profile(hProfile); + } + return ptr; +} + +cmsHPROFILE Profile::handle() const +{ + return d->mProfile; +} + +QString Profile::copyright() const +{ + return d->readInfo(cmsInfoCopyright); +} + +QString Profile::description() const +{ + return d->readInfo(cmsInfoDescription); +} + +QString Profile::manufacturer() const +{ + return d->readInfo(cmsInfoManufacturer); +} + +QString Profile::model() const +{ + return d->readInfo(cmsInfoModel); +} + +Profile::Ptr Profile::getMonitorProfile() +{ + cmsHPROFILE hProfile = 0; + // Get the profile from you config file if the user has set it. + // if the user allows override through the atom, do this: +#ifdef Q_WS_X11 + + // get the current screen... + int screen = -1; + + Atom type; + int format; + unsigned long nitems; + unsigned long bytes_after; + quint8 *str; + + static Atom icc_atom = XInternAtom(QX11Info::display(), "_ICC_PROFILE", True); + + if (XGetWindowProperty(QX11Info::display(), + QX11Info::appRootWindow(screen), + icc_atom, + 0, + INT_MAX, + False, + XA_CARDINAL, + &type, + &format, + &nitems, + &bytes_after, + (unsigned char **) &str) == Success + ) { + hProfile = cmsOpenProfileFromMem((void*)str, nitems); + } +#endif + if (!hProfile) { + hProfile = cmsCreate_sRGBProfile(); + } + return Profile::Ptr(new Profile(hProfile)); +} + +} // namespace Cms + +} // namespace Gwenview diff --git a/gwenview/lib/cms/cmsprofile.h b/gwenview/lib/cms/cmsprofile.h new file mode 100644 index 00000000..014654cc --- /dev/null +++ b/gwenview/lib/cms/cmsprofile.h @@ -0,0 +1,79 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef CMSPROFILE_H +#define CMSPROFILE_H + +#include + +// Local + +// KDE +#include + +// Qt +#include + +// Exiv2 +#include + +class QByteArray; +class QString; + +typedef void* cmsHPROFILE; + +namespace Gwenview +{ + +namespace Cms +{ + +struct ProfilePrivate; +/** + * Wrapper for lcms color profile + */ +class GWENVIEWLIB_EXPORT Profile : public QSharedData +{ +public: + typedef KSharedPtr Ptr; + + Profile(); + ~Profile(); + + QString description() const; + QString manufacturer() const; + QString model() const; + QString copyright() const; + + cmsHPROFILE handle() const; + + static Profile::Ptr loadFromImageData(const QByteArray& data, const QByteArray& format); + static Profile::Ptr loadFromExiv2Image(const Exiv2::Image* image); + static Profile::Ptr getMonitorProfile(); + +private: + Profile(cmsHPROFILE); + ProfilePrivate* const d; +}; + +} // namespace Cms +} // namespace Gwenview + +#endif /* CMSPROFILE_H */ diff --git a/gwenview/lib/cms/cmsprofile_png.cpp b/gwenview/lib/cms/cmsprofile_png.cpp new file mode 100644 index 00000000..891e2b27 --- /dev/null +++ b/gwenview/lib/cms/cmsprofile_png.cpp @@ -0,0 +1,115 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "cmsprofile_png.h" + +// Local +#include + +// KDE +#include + +// Qt +#include + +// lcms +#include + +// libpng +#include + +namespace Gwenview +{ + +namespace Cms +{ + +static void readPngChunk(png_structp png_ptr, png_bytep data, png_size_t length) +{ + QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr); + + while (length) { + int nr = in->read((char*)data, length); + if (nr <= 0) { + png_error(png_ptr, "Read Error"); + return; + } + length -= nr; + } +} + +cmsHPROFILE loadFromPngData(const QByteArray& data) +{ + QBuffer buffer; + buffer.setBuffer(const_cast(&data)); + buffer.open(QIODevice::ReadOnly); + + // Initialize the internal structures + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); + GV_RETURN_VALUE_IF_FAIL(png_ptr, 0); + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); + kWarning() << "Could not create info_struct"; + return 0; + } + + png_infop end_info = png_create_info_struct(png_ptr); + if (!end_info) { + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + kWarning() << "Could not create info_struct2"; + return 0; + } + + // Catch errors + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + kWarning() << "Error decoding png file"; + return 0; + } + + // Initialize the special + png_set_read_fn(png_ptr, &buffer, readPngChunk); + + // read all PNG info up to image data + png_read_info(png_ptr, info_ptr); + + // Get profile + png_charp profile_name; +#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 + png_bytep profile_data; +#else + png_charp profile_data; +#endif + int compression_type; + png_uint_32 proflen; + + cmsHPROFILE profile = 0; + if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) { + profile = cmsOpenProfileFromMem(profile_data, proflen); + } + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + return profile; +} + +} // namespace Cms +} // namespace Gwenview diff --git a/gwenview/lib/cms/cmsprofile_png.h b/gwenview/lib/cms/cmsprofile_png.h new file mode 100644 index 00000000..9fa0a653 --- /dev/null +++ b/gwenview/lib/cms/cmsprofile_png.h @@ -0,0 +1,49 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef CMSPROFILE_PNG_H +#define CMSPROFILE_PNG_H + +// Local + +// KDE + +// Qt +#include + +typedef void* cmsHPROFILE; + +namespace Gwenview +{ + +namespace Cms +{ + +/** + * This code is in its own file because it cannot be compiled in the same .cpp + * file as jpeg code: libpng complains about setjmp being included twice. + */ + +cmsHPROFILE loadFromPngData(const QByteArray& data); + +} // namespace Cms +} // namespace Gwenview + +#endif /* CMSPROFILE_PNG_H */ diff --git a/gwenview/lib/cms/iccjpeg.c b/gwenview/lib/cms/iccjpeg.c new file mode 100644 index 00000000..1f6c4b10 --- /dev/null +++ b/gwenview/lib/cms/iccjpeg.c @@ -0,0 +1,270 @@ +/* + * Little cms + * Copyright (C) 1998-2004 Marti Maria + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * iccprofile.c + * + * This file provides code to read and write International Color Consortium + * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has + * defined a standard format for including such data in JPEG "APP2" markers. + * The code given here does not know anything about the internal structure + * of the ICC profile data; it just knows how to put the profile data into + * a JPEG file being written, or get it back out when reading. + * + * This code depends on new features added to the IJG JPEG library as of + * IJG release 6b; it will not compile or work with older IJG versions. + * + * NOTE: this code would need surgery to work on 16-bit-int machines + * with ICC profiles exceeding 64K bytes in size. If you need to do that, + * change all the "unsigned int" variables to "INT32". You'll also need + * to find a malloc() replacement that can allocate more than 64K. + */ + +#include "iccjpeg.h" +#include /* define malloc() */ + + +/* + * Since an ICC profile can be larger than the maximum size of a JPEG marker + * (64K), we need provisions to split it into multiple markers. The format + * defined by the ICC specifies one or more APP2 markers containing the + * following data: + * Identifying string ASCII "ICC_PROFILE\0" (12 bytes) + * Marker sequence number 1 for first APP2, 2 for next, etc (1 byte) + * Number of markers Total number of APP2's used (1 byte) + * Profile data (remainder of APP2 data) + * Decoders should use the marker sequence numbers to reassemble the profile, + * rather than assuming that the APP2 markers appear in the correct sequence. + */ + +#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ +#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ +#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ +#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) + + +/* + * This routine writes the given ICC profile data into a JPEG file. + * It *must* be called AFTER calling jpeg_start_compress() and BEFORE + * the first call to jpeg_write_scanlines(). + * (This ordering ensures that the APP2 marker(s) will appear after the + * SOI and JFIF or Adobe markers, but before all else.) + */ + +void +write_icc_profile (j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len) +{ + unsigned int num_markers; /* total number of markers we'll write */ + int cur_marker = 1; /* per spec, counting starts at 1 */ + unsigned int length; /* number of bytes to write in this marker */ + + /* Calculate the number of markers we'll need, rounding up of course */ + num_markers = icc_data_len / MAX_DATA_BYTES_IN_MARKER; + if (num_markers * MAX_DATA_BYTES_IN_MARKER != icc_data_len) + num_markers++; + + while (icc_data_len > 0) { + /* length of profile to put in this marker */ + length = icc_data_len; + if (length > MAX_DATA_BYTES_IN_MARKER) + length = MAX_DATA_BYTES_IN_MARKER; + icc_data_len -= length; + + /* Write the JPEG marker header (APP2 code and marker length) */ + jpeg_write_m_header(cinfo, ICC_MARKER, + (unsigned int) (length + ICC_OVERHEAD_LEN)); + + /* Write the marker identifying string "ICC_PROFILE" (null-terminated). + * We code it in this less-than-transparent way so that the code works + * even if the local character set is not ASCII. + */ + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x5F); + jpeg_write_m_byte(cinfo, 0x50); + jpeg_write_m_byte(cinfo, 0x52); + jpeg_write_m_byte(cinfo, 0x4F); + jpeg_write_m_byte(cinfo, 0x46); + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x4C); + jpeg_write_m_byte(cinfo, 0x45); + jpeg_write_m_byte(cinfo, 0x0); + + /* Add the sequencing info */ + jpeg_write_m_byte(cinfo, cur_marker); + jpeg_write_m_byte(cinfo, (int) num_markers); + + /* Add the profile data */ + while (length--) { + jpeg_write_m_byte(cinfo, *icc_data_ptr); + icc_data_ptr++; + } + cur_marker++; + } +} + + +/* + * Prepare for reading an ICC profile + */ + +void +setup_read_icc_profile (j_decompress_ptr cinfo) +{ + /* Tell the library to keep any APP2 data it may find */ + jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF); +} + + +/* + * Handy subroutine to test whether a saved marker is an ICC profile marker. + */ + +static boolean +marker_is_icc (jpeg_saved_marker_ptr marker) +{ + return + marker->marker == ICC_MARKER && + marker->data_length >= ICC_OVERHEAD_LEN && + /* verify the identifying string */ + GETJOCTET(marker->data[0]) == 0x49 && + GETJOCTET(marker->data[1]) == 0x43 && + GETJOCTET(marker->data[2]) == 0x43 && + GETJOCTET(marker->data[3]) == 0x5F && + GETJOCTET(marker->data[4]) == 0x50 && + GETJOCTET(marker->data[5]) == 0x52 && + GETJOCTET(marker->data[6]) == 0x4F && + GETJOCTET(marker->data[7]) == 0x46 && + GETJOCTET(marker->data[8]) == 0x49 && + GETJOCTET(marker->data[9]) == 0x4C && + GETJOCTET(marker->data[10]) == 0x45 && + GETJOCTET(marker->data[11]) == 0x0; +} + + +/* + * See if there was an ICC profile in the JPEG file being read; + * if so, reassemble and return the profile data. + * + * TRUE is returned if an ICC profile was found, FALSE if not. + * If TRUE is returned, *icc_data_ptr is set to point to the + * returned data, and *icc_data_len is set to its length. + * + * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc() + * and must be freed by the caller with free() when the caller no longer + * needs it. (Alternatively, we could write this routine to use the + * IJG library's memory allocator, so that the data would be freed implicitly + * at jpeg_finish_decompress() time. But it seems likely that many apps + * will prefer to have the data stick around after decompression finishes.) + * + * NOTE: if the file contains invalid ICC APP2 markers, we just silently + * return FALSE. You might want to issue an error message instead. + */ + +boolean +read_icc_profile (j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len) +{ + jpeg_saved_marker_ptr marker; + int num_markers = 0; + int seq_no; + JOCTET *icc_data; + unsigned int total_length; +#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */ + char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */ + unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */ + unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */ + + *icc_data_ptr = NULL; /* avoid confusion if FALSE return */ + *icc_data_len = 0; + + /* This first pass over the saved markers discovers whether there are + * any ICC markers and verifies the consistency of the marker numbering. + */ + + for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) + marker_present[seq_no] = 0; + + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + if (num_markers == 0) + num_markers = GETJOCTET(marker->data[13]); + else if (num_markers != GETJOCTET(marker->data[13])) + return FALSE; /* inconsistent num_markers fields */ + seq_no = GETJOCTET(marker->data[12]); + if (seq_no <= 0 || seq_no > num_markers) + return FALSE; /* bogus sequence number */ + if (marker_present[seq_no]) + return FALSE; /* duplicate sequence numbers */ + marker_present[seq_no] = 1; + data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN; + } + } + + if (num_markers == 0) + return FALSE; + + /* Check for missing markers, count total space needed, + * compute offset of each marker's part of the data. + */ + + total_length = 0; + for (seq_no = 1; seq_no <= num_markers; seq_no++) { + if (marker_present[seq_no] == 0) + return FALSE; /* missing sequence number */ + data_offset[seq_no] = total_length; + total_length += data_length[seq_no]; + } + + if (total_length <= 0) + return FALSE; /* found only empty markers? */ + + /* Allocate space for assembled data */ + icc_data = (JOCTET *) malloc(total_length * sizeof(JOCTET)); + if (icc_data == NULL) + return FALSE; /* oops, out of memory */ + + /* and fill it in */ + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + JOCTET FAR *src_ptr; + JOCTET *dst_ptr; + unsigned int length; + seq_no = GETJOCTET(marker->data[12]); + dst_ptr = icc_data + data_offset[seq_no]; + src_ptr = marker->data + ICC_OVERHEAD_LEN; + length = data_length[seq_no]; + while (length--) { + *dst_ptr++ = *src_ptr++; + } + } + } + + *icc_data_ptr = icc_data; + *icc_data_len = total_length; + + return TRUE; +} diff --git a/gwenview/lib/cms/iccjpeg.h b/gwenview/lib/cms/iccjpeg.h new file mode 100644 index 00000000..a3747b81 --- /dev/null +++ b/gwenview/lib/cms/iccjpeg.h @@ -0,0 +1,99 @@ +/* + * Little cms + * Copyright (C) 1998-2004 Marti Maria + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * iccprofile.h + * + * This file provides code to read and write International Color Consortium + * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has + * defined a standard format for including such data in JPEG "APP2" markers. + * The code given here does not know anything about the internal structure + * of the ICC profile data; it just knows how to put the profile data into + * a JPEG file being written, or get it back out when reading. + * + * This code depends on new features added to the IJG JPEG library as of + * IJG release 6b; it will not compile or work with older IJG versions. + * + * NOTE: this code would need surgery to work on 16-bit-int machines + * with ICC profiles exceeding 64K bytes in size. See iccprofile.c + * for details. + */ +#ifndef ICCJPEG +#define ICCJPEG + +#include /* needed to define "FILE", "NULL" */ +#include "jpeglib.h" + + +/* + * This routine writes the given ICC profile data into a JPEG file. + * It *must* be called AFTER calling jpeg_start_compress() and BEFORE + * the first call to jpeg_write_scanlines(). + * (This ordering ensures that the APP2 marker(s) will appear after the + * SOI and JFIF or Adobe markers, but before all else.) + */ + +extern void write_icc_profile JPP((j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len)); + + +/* + * Reading a JPEG file that may contain an ICC profile requires two steps: + * + * 1. After jpeg_create_decompress() but before jpeg_read_header(), + * call setup_read_icc_profile(). This routine tells the IJG library + * to save in memory any APP2 markers it may find in the file. + * + * 2. After jpeg_read_header(), call read_icc_profile() to find out + * whether there was a profile and obtain it if so. + */ + + +/* + * Prepare for reading an ICC profile + */ + +extern void setup_read_icc_profile JPP((j_decompress_ptr cinfo)); + + +/* + * See if there was an ICC profile in the JPEG file being read; + * if so, reassemble and return the profile data. + * + * true is returned if an ICC profile was found, false if not. + * If true is returned, *icc_data_ptr is set to point to the + * returned data, and *icc_data_len is set to its length. + * + * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc() + * and must be freed by the caller with free() when the caller no longer + * needs it. (Alternatively, we could write this routine to use the + * IJG library's memory allocator, so that the data would be freed implicitly + * at jpeg_finish_decompress() time. But it seems likely that many apps + * will prefer to have the data stick around after decompression finishes.) + */ + +extern boolean read_icc_profile JPP((j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len)); + +#endif diff --git a/gwenview/lib/contextmanager.cpp b/gwenview/lib/contextmanager.cpp new file mode 100644 index 00000000..91e688e1 --- /dev/null +++ b/gwenview/lib/contextmanager.cpp @@ -0,0 +1,317 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "contextmanager.moc" + +// Qt +#include +#include +#include + +// KDE +#include +#include +#include + +// Local +#include +#include +#include + +namespace Gwenview +{ + +struct ContextManagerPrivate +{ + SortedDirModel* mDirModel; + QItemSelectionModel* mSelectionModel; + KUrl mCurrentDirUrl; + KUrl mCurrentUrl; + + KUrl mUrlToSelect; + + bool mSelectedFileItemListNeedsUpdate; + QSet mQueuedSignals; + KFileItemList mSelectedFileItemList; + + QTimer* mQueuedSignalsTimer; + + void queueSignal(const QByteArray& signal) + { + mQueuedSignals << signal; + mQueuedSignalsTimer->start(); + } + + void updateSelectedFileItemList() + { + if (!mSelectedFileItemListNeedsUpdate) { + return; + } + mSelectedFileItemList.clear(); + QItemSelection selection = mSelectionModel->selection(); + Q_FOREACH(const QModelIndex & index, selection.indexes()) { + mSelectedFileItemList << mDirModel->itemForIndex(index); + } + + // At least add current url if it's valid (it may not be in + // the list if we are viewing a non-browsable url, for example + // using http protocol) + if (mSelectedFileItemList.isEmpty() && mCurrentUrl.isValid()) { + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, mCurrentUrl); + mSelectedFileItemList << item; + } + + mSelectedFileItemListNeedsUpdate = false; + } +}; + +ContextManager::ContextManager(SortedDirModel* dirModel, QObject* parent) +: QObject(parent) +, d(new ContextManagerPrivate) +{ + d->mQueuedSignalsTimer = new QTimer(this); + d->mQueuedSignalsTimer->setInterval(100); + d->mQueuedSignalsTimer->setSingleShot(true); + connect(d->mQueuedSignalsTimer, SIGNAL(timeout()), + SLOT(emitQueuedSignals())); + + d->mDirModel = dirModel; + connect(d->mDirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + SLOT(slotDirModelDataChanged(QModelIndex,QModelIndex))); + + /* HACK! In extended-selection mode, when the current index is removed, + * QItemSelectionModel selects the previous index if there is one, if not it + * selects the next index. This is not what we want: when the user removes + * an image, he expects to go to the next one, not the previous one. + * + * To overcome this, we must connect to the mDirModel.rowsAboutToBeRemoved() + * signal *before* QItemSelectionModel connects to it, so that our slot is + * called before QItemSelectionModel slot. This allows us to pick a new + * current index ourself, leaving QItemSelectionModel slot with nothing to + * do. + * + * This is the reason ContextManager creates a QItemSelectionModel itself: + * doing so ensures QItemSelectionModel cannot be connected to the + * mDirModel.rowsAboutToBeRemoved() signal before us. + */ + connect(d->mDirModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); + + connect(d->mDirModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + SLOT(slotRowsInserted())); + + connect(d->mDirModel->dirLister(), SIGNAL(redirection(KUrl)), + SLOT(slotDirListerRedirection(KUrl))); + + d->mSelectionModel = new QItemSelectionModel(d->mDirModel); + + connect(d->mSelectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + SLOT(slotSelectionChanged())); + connect(d->mSelectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + SLOT(slotCurrentChanged(QModelIndex))); + + d->mSelectedFileItemListNeedsUpdate = false; +} + +ContextManager::~ContextManager() +{ + delete d; +} + +QItemSelectionModel* ContextManager::selectionModel() const +{ + return d->mSelectionModel; +} + +void ContextManager::setCurrentUrl(const KUrl& currentUrl) +{ + if (d->mCurrentUrl == currentUrl) { + return; + } + + d->mCurrentUrl = currentUrl; + if (!d->mCurrentUrl.isEmpty()) { + Document::Ptr doc = DocumentFactory::instance()->load(currentUrl); + QUndoGroup* undoGroup = DocumentFactory::instance()->undoGroup(); + undoGroup->addStack(doc->undoStack()); + undoGroup->setActiveStack(doc->undoStack()); + } + + currentUrlChanged(currentUrl); +} + +KFileItemList ContextManager::selectedFileItemList() const +{ + d->updateSelectedFileItemList(); + return d->mSelectedFileItemList; +} + +void ContextManager::setCurrentDirUrl(const KUrl& url) +{ + if (url.equals(d->mCurrentDirUrl, KUrl::CompareWithoutTrailingSlash)) { + return; + } + d->mCurrentDirUrl = url; + if (url.isValid()) { + d->mDirModel->dirLister()->openUrl(url); + } + currentDirUrlChanged(url); +} + +KUrl ContextManager::currentDirUrl() const +{ + return d->mCurrentDirUrl; +} + +KUrl ContextManager::currentUrl() const +{ + return d->mCurrentUrl; +} + +SortedDirModel* ContextManager::dirModel() const +{ + return d->mDirModel; +} + +void ContextManager::slotDirModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // Data change can happen in the following cases: + // - items have been renamed + // - item bytes have been modified + // - item meta info has been retrieved or modified + // + // If a selected item is affected, schedule emission of a + // selectionDataChanged() signal. Don't emit it directly to avoid spamming + // the context items in case of a mass change. + QModelIndexList selectionList = d->mSelectionModel->selectedIndexes(); + if (selectionList.isEmpty()) { + return; + } + + QModelIndexList changedList; + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { + changedList << d->mDirModel->index(row, 0); + } + + QModelIndexList& shortList = selectionList; + QModelIndexList& longList = changedList; + if (shortList.length() > longList.length()) { + qSwap(shortList, longList); + } + Q_FOREACH(const QModelIndex & index, shortList) { + if (longList.contains(index)) { + d->mSelectedFileItemListNeedsUpdate = true; + d->queueSignal("selectionDataChanged"); + return; + } + } +} + +void ContextManager::slotSelectionChanged() +{ + d->mSelectedFileItemListNeedsUpdate = true; + if (!d->mSelectionModel->hasSelection()) { + setCurrentUrl(KUrl()); + } + d->queueSignal("selectionChanged"); +} + +void Gwenview::ContextManager::slotCurrentChanged(const QModelIndex& index) +{ + KUrl url = d->mDirModel->urlForIndex(index); + setCurrentUrl(url); +} + +void ContextManager::emitQueuedSignals() +{ + Q_FOREACH(const QByteArray & signal, d->mQueuedSignals) { + QMetaObject::invokeMethod(this, signal.data()); + } + d->mQueuedSignals.clear(); +} + +void Gwenview::ContextManager::slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end) +{ + QModelIndex oldCurrent = d->mSelectionModel->currentIndex(); + if (oldCurrent.row() < start || oldCurrent.row() > end) { + // currentIndex has not been removed + return; + } + QModelIndex newCurrent; + if (end + 1 < d->mDirModel->rowCount()) { + newCurrent = d->mDirModel->index(end + 1, 0); + } else if (start > 0) { + newCurrent = d->mDirModel->index(start - 1, 0); + } else { + // No index we can select, nothing to do + return; + } + d->mSelectionModel->select(oldCurrent, QItemSelectionModel::Deselect); + d->mSelectionModel->setCurrentIndex(newCurrent, QItemSelectionModel::Select); +} + +bool ContextManager::currentUrlIsRasterImage() const +{ + return MimeTypeUtils::urlKind(currentUrl()) == MimeTypeUtils::KIND_RASTER_IMAGE; +} + +KUrl ContextManager::urlToSelect() const +{ + return d->mUrlToSelect; +} + +void ContextManager::setUrlToSelect(const KUrl& url) +{ + GV_RETURN_IF_FAIL(url.isValid()); + d->mUrlToSelect = url; + setCurrentUrl(url); + selectUrlToSelect(); +} + +void ContextManager::slotRowsInserted() +{ + // We reach this method when rows have been inserted in the model, but views + // may not have been updated yet and thus do not have the matching items. + // Delay the selection of mUrlToSelect so that the view items exist. + // + // Without this, when Gwenview is started with an image as argument and the + // thumbnail bar is visible, the image will not be selected in the thumbnail + // bar. + if (d->mUrlToSelect.isValid()) { + QMetaObject::invokeMethod(this, "selectUrlToSelect", Qt::QueuedConnection); + } +} + +void ContextManager::selectUrlToSelect() +{ + GV_RETURN_IF_FAIL(d->mUrlToSelect.isValid()); + QModelIndex index = d->mDirModel->indexForUrl(d->mUrlToSelect); + if (index.isValid()) { + d->mSelectionModel->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + d->mUrlToSelect = KUrl(); + } +} + +void ContextManager::slotDirListerRedirection(const KUrl& newUrl) +{ + setCurrentDirUrl(newUrl); +} + + +} // namespace diff --git a/gwenview/lib/contextmanager.h b/gwenview/lib/contextmanager.h new file mode 100644 index 00000000..59c7226b --- /dev/null +++ b/gwenview/lib/contextmanager.h @@ -0,0 +1,96 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef CONTEXTMANAGER_H +#define CONTEXTMANAGER_H + +#include + +// Qt +#include + +// KDE +#include +#include + +class QItemSelectionModel; +class QModelIndex; + +namespace Gwenview +{ + +class SortedDirModel; + +struct ContextManagerPrivate; + +/** + * Manages the state of the application. + * TODO: Most of GvCore should be merged in this class + */ +class GWENVIEWLIB_EXPORT ContextManager : public QObject +{ + Q_OBJECT +public: + ContextManager(SortedDirModel*, QObject* parent); + + ~ContextManager(); + + KUrl currentUrl() const; + + void setCurrentDirUrl(const KUrl&); + + KUrl currentDirUrl() const; + + void setCurrentUrl(const KUrl& currentUrl); + + KFileItemList selectedFileItemList() const; + + SortedDirModel* dirModel() const; + + QItemSelectionModel* selectionModel() const; + + bool currentUrlIsRasterImage() const; + + KUrl urlToSelect() const; + + void setUrlToSelect(const KUrl&); + +Q_SIGNALS: + void currentDirUrlChanged(const KUrl&); + void currentUrlChanged(const KUrl&); + void selectionChanged(); + void selectionDataChanged(); + +private Q_SLOTS: + void slotDirModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + void slotSelectionChanged(); + void slotCurrentChanged(const QModelIndex&); + void emitQueuedSignals(); + void slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end); + void slotRowsInserted(); + void selectUrlToSelect(); + void slotDirListerRedirection(const KUrl&); + +private: + ContextManagerPrivate* const d; +}; + +} // namespace + +#endif /* CONTEXTMANAGER_H */ diff --git a/gwenview/lib/crop/cropimageoperation.cpp b/gwenview/lib/crop/cropimageoperation.cpp new file mode 100644 index 00000000..6dc4bdfe --- /dev/null +++ b/gwenview/lib/crop/cropimageoperation.cpp @@ -0,0 +1,94 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "cropimageoperation.h" + +// Qt +#include + +// KDE +#include +#include + +// Local +#include "document/document.h" +#include "document/documentjob.h" +#include "document/abstractdocumenteditor.h" + +namespace Gwenview +{ + +class CropJob : public ThreadedDocumentJob +{ +public: + CropJob(const QRect& rect) + : mRect(rect) + {} + + virtual void threadedStart() + { + if (!checkDocumentEditor()) { + return; + } + const QImage src = document()->image(); + const QImage dst = src.copy(mRect); + document()->editor()->setImage(dst); + setError(NoError); + } + +private: + QRect mRect; +}; + +struct CropImageOperationPrivate +{ + QRect mRect; + QImage mOriginalImage; +}; + +CropImageOperation::CropImageOperation(const QRect& rect) +: d(new CropImageOperationPrivate) +{ + d->mRect = rect; + setText(i18n("Crop")); +} + +CropImageOperation::~CropImageOperation() +{ + delete d; +} + +void CropImageOperation::redo() +{ + d->mOriginalImage = document()->image(); + redoAsDocumentJob(new CropJob(d->mRect)); +} + +void CropImageOperation::undo() +{ + if (!document()->editor()) { + kWarning() << "!document->editor()"; + return; + } + document()->editor()->setImage(d->mOriginalImage); +} + +} // namespace diff --git a/gwenview/lib/crop/cropimageoperation.h b/gwenview/lib/crop/cropimageoperation.h new file mode 100644 index 00000000..8ea26c10 --- /dev/null +++ b/gwenview/lib/crop/cropimageoperation.h @@ -0,0 +1,54 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef CROPIMAGEOPERATION_H +#define CROPIMAGEOPERATION_H + +#include + +// Qt + +// KDE + +// Local +#include + +class QRect; + +namespace Gwenview +{ + +struct CropImageOperationPrivate; +class GWENVIEWLIB_EXPORT CropImageOperation : public AbstractImageOperation +{ +public: + CropImageOperation(const QRect&); + ~CropImageOperation(); + + virtual void redo(); + virtual void undo(); + +private: + CropImageOperationPrivate* const d; +}; + +} // namespace + +#endif /* CROPIMAGEOPERATION_H */ diff --git a/gwenview/lib/crop/croptool.cpp b/gwenview/lib/crop/croptool.cpp new file mode 100644 index 00000000..fbfd30bd --- /dev/null +++ b/gwenview/lib/crop/croptool.cpp @@ -0,0 +1,427 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "croptool.moc" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include + +// Local +#include +#include "cropimageoperation.h" +#include "cropwidget.h" +#include "gwenviewconfig.h" + +static const int HANDLE_SIZE = 15; + +namespace Gwenview +{ + +enum CropHandleFlag { + CH_None, + CH_Top = 1, + CH_Left = 2, + CH_Right = 4, + CH_Bottom = 8, + CH_TopLeft = CH_Top | CH_Left, + CH_BottomLeft = CH_Bottom | CH_Left, + CH_TopRight = CH_Top | CH_Right, + CH_BottomRight = CH_Bottom | CH_Right, + CH_Content = 16 +}; + +Q_DECLARE_FLAGS(CropHandle, CropHandleFlag) + +} // namespace + +inline QPoint boundPointX(const QPoint& point, const QRect& rect) +{ + return QPoint( + qBound(rect.left(), point.x(), rect.right()), + point.y() + ); +} + +inline QPoint boundPointXY(const QPoint& point, const QRect& rect) +{ + return QPoint( + qBound(rect.left(), point.x(), rect.right()), + qBound(rect.top(), point.y(), rect.bottom()) + ); +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(Gwenview::CropHandle) + +namespace Gwenview +{ + +struct CropToolPrivate +{ + CropTool* q; + QRect mRect; + QList mCropHandleList; + CropHandle mMovingHandle; + QPoint mLastMouseMovePos; + double mCropRatio; + CropWidget* mCropWidget; + + QRect viewportCropRect() const + { + return q->imageView()->mapToView(mRect.adjusted(0, 0, 1, 1)); + } + + QRect handleViewportRect(CropHandle handle) + { + QSize viewportSize = q->imageView()->size().toSize(); + QRect rect = viewportCropRect(); + int left, top; + if (handle & CH_Top) { + top = rect.top(); + } else if (handle & CH_Bottom) { + top = rect.bottom() - HANDLE_SIZE; + } else { + top = rect.top() + (rect.height() - HANDLE_SIZE) / 2; + top = qBound(0, top, viewportSize.height() - HANDLE_SIZE); + } + + if (handle & CH_Left) { + left = rect.left(); + } else if (handle & CH_Right) { + left = rect.right() - HANDLE_SIZE; + } else { + left = rect.left() + (rect.width() - HANDLE_SIZE) / 2; + left = qBound(0, left, viewportSize.width() - HANDLE_SIZE); + } + + return QRect(left, top, HANDLE_SIZE, HANDLE_SIZE); + } + + CropHandle handleAt(const QPointF& pos) + { + Q_FOREACH(const CropHandle & handle, mCropHandleList) { + QRectF rect = handleViewportRect(handle); + if (rect.contains(pos)) { + return handle; + } + } + QRectF rect = viewportCropRect(); + if (rect.contains(pos)) { + return CH_Content; + } + return CH_None; + } + + void updateCursor(CropHandle handle, bool buttonDown) + { + Qt::CursorShape shape; + switch (handle) { + case CH_TopLeft: + case CH_BottomRight: + shape = Qt::SizeFDiagCursor; + break; + + case CH_TopRight: + case CH_BottomLeft: + shape = Qt::SizeBDiagCursor; + break; + + case CH_Left: + case CH_Right: + shape = Qt::SizeHorCursor; + break; + + case CH_Top: + case CH_Bottom: + shape = Qt::SizeVerCursor; + break; + + case CH_Content: + shape = buttonDown ? Qt::ClosedHandCursor : Qt::OpenHandCursor; + break; + + default: + shape = Qt::ArrowCursor; + break; + } + q->imageView()->setCursor(shape); + } + + void keepRectInsideImage() + { + const QSize imageSize = q->imageView()->documentSize().toSize(); + if (mRect.width() > imageSize.width() || mRect.height() > imageSize.height()) { + // This can happen when the crop ratio changes + QSize rectSize = mRect.size(); + rectSize.scale(imageSize, Qt::KeepAspectRatio); + mRect.setSize(rectSize); + } + + if (mRect.right() >= imageSize.width()) { + mRect.moveRight(imageSize.width() - 1); + } else if (mRect.left() < 0) { + mRect.moveLeft(0); + } + if (mRect.bottom() >= imageSize.height()) { + mRect.moveBottom(imageSize.height() - 1); + } else if (mRect.top() < 0) { + mRect.moveTop(0); + } + } + + void setupWidget() + { + RasterImageView* view = q->imageView(); + mCropWidget = new CropWidget(0, view, q); + QObject::connect(mCropWidget, SIGNAL(cropRequested()), + q, SLOT(slotCropRequested())); + QObject::connect(mCropWidget, SIGNAL(done()), + q, SIGNAL(done())); + } + + QRect computeVisibleImageRect() const + { + RasterImageView* view = q->imageView(); + const QRect imageRect = QRect(QPoint(0, 0), view->documentSize().toSize()); + const QRect viewportRect = view->mapToImage(view->rect().toRect()); + return imageRect & viewportRect; + } +}; + +CropTool::CropTool(RasterImageView* view) +: AbstractRasterImageViewTool(view) +, d(new CropToolPrivate) +{ + d->q = this; + d->mCropHandleList << CH_Left << CH_Right << CH_Top << CH_Bottom << CH_TopLeft << CH_TopRight << CH_BottomLeft << CH_BottomRight; + d->mMovingHandle = CH_None; + d->mCropRatio = 0.; + d->mRect = d->computeVisibleImageRect(); + d->setupWidget(); +} + +CropTool::~CropTool() +{ + // mCropWidget is a child of its container not of us, so it is not deleted automatically + delete d->mCropWidget; + delete d; +} + +void CropTool::setCropRatio(double ratio) +{ + d->mCropRatio = ratio; +} + +void CropTool::setRect(const QRect& rect) +{ + QRect oldRect = d->mRect; + d->mRect = rect; + d->keepRectInsideImage(); + if (d->mRect != oldRect) { + rectUpdated(d->mRect); + } + imageView()->update(); +} + +QRect CropTool::rect() const +{ + return d->mRect; +} + +void CropTool::paint(QPainter* painter) +{ + QRect rect = d->viewportCropRect(); + + QRect imageRect = imageView()->rect().toRect(); + + static const QColor outerColor = QColor::fromHsvF(0, 0, 0, 0.5); + // For some reason nothing gets drawn if borderColor is not fully opaque! + //static const QColor borderColor = QColor::fromHsvF(0, 0, 1.0, 0.66); + static const QColor borderColor = QColor::fromHsvF(0, 0, 1.0); + static const QColor fillColor = QColor::fromHsvF(0, 0, 0.75, 0.66); + + QRegion outerRegion = QRegion(imageRect) - QRegion(rect); + Q_FOREACH(const QRect & outerRect, outerRegion.rects()) { + painter->fillRect(outerRect, outerColor); + } + + painter->setPen(borderColor); + + rect.adjust(0, 0, -1, -1); + painter->drawRect(rect); + + if (d->mMovingHandle == CH_None) { + // Only draw handles when user is not resizing + painter->setBrush(fillColor); + Q_FOREACH(const CropHandle & handle, d->mCropHandleList) { + rect = d->handleViewportRect(handle); + painter->drawRect(rect); + } + } +} + +void CropTool::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + event->accept(); + if (event->buttons() != Qt::LeftButton) { + return; + } + d->mMovingHandle = d->handleAt(event->pos()); + d->updateCursor(d->mMovingHandle, true /* down */); + + if (d->mMovingHandle == CH_Content) { + d->mLastMouseMovePos = imageView()->mapToImage(event->pos().toPoint()); + } + + // Update to hide handles + imageView()->update(); +} + +void CropTool::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + event->accept(); + if (event->buttons() != Qt::LeftButton) { + return; + } + + const QSize imageSize = imageView()->document()->size(); + + QPoint point = imageView()->mapToImage(event->pos().toPoint()); + int posX = qBound(0, point.x(), imageSize.width() - 1); + int posY = qBound(0, point.y(), imageSize.height() - 1); + + if (d->mMovingHandle == CH_None) { + return; + } + + // Adjust edge + if (d->mMovingHandle & CH_Top) { + d->mRect.setTop(posY); + } else if (d->mMovingHandle & CH_Bottom) { + d->mRect.setBottom(posY); + } + if (d->mMovingHandle & CH_Left) { + d->mRect.setLeft(posX); + } else if (d->mMovingHandle & CH_Right) { + d->mRect.setRight(posX); + } + + // Normalize rect and handles (this is useful when user drag the right side + // of the crop rect to the left of the left side) + if (d->mRect.height() < 0) { + d->mMovingHandle = d->mMovingHandle ^(CH_Top | CH_Bottom); + } + if (d->mRect.width() < 0) { + d->mMovingHandle = d->mMovingHandle ^(CH_Left | CH_Right); + } + d->mRect = d->mRect.normalized(); + + // Enforce ratio + if (d->mCropRatio > 0.) { + if (d->mMovingHandle == CH_Top || d->mMovingHandle == CH_Bottom) { + // Top or bottom + int width = int(d->mRect.height() / d->mCropRatio); + d->mRect.setWidth(width); + } else if (d->mMovingHandle == CH_Left || d->mMovingHandle == CH_Right) { + // Left or right + int height = int(d->mRect.width() * d->mCropRatio); + d->mRect.setHeight(height); + } else if (d->mMovingHandle & CH_Top) { + // Top left or top right + int height = int(d->mRect.width() * d->mCropRatio); + d->mRect.setTop(d->mRect.bottom() - height); + } else if (d->mMovingHandle & CH_Bottom) { + // Bottom left or bottom right + int height = int(d->mRect.width() * d->mCropRatio); + d->mRect.setHeight(height); + } + } + + if (d->mMovingHandle == CH_Content) { + d->mRect.translate(point - d->mLastMouseMovePos); + d->mLastMouseMovePos = point; + } + + d->keepRectInsideImage(); + + imageView()->update(); + rectUpdated(d->mRect); +} + +void CropTool::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + event->accept(); + d->mMovingHandle = CH_None; + d->updateCursor(d->handleAt(event->lastPos()), false); + + // Update to show handles + imageView()->update(); +} + +void CropTool::hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + event->accept(); + // Make sure cursor is updated when moving over handles + CropHandle handle = d->handleAt(event->lastPos()); + d->updateCursor(handle, false /* buttonDown */); +} + +void CropTool::toolActivated() +{ + imageView()->setCursor(Qt::CrossCursor); + d->mCropWidget->setAdvancedSettingsEnabled(GwenviewConfig::cropAdvancedSettingsEnabled()); +} + +void CropTool::toolDeactivated() +{ + GwenviewConfig::setCropAdvancedSettingsEnabled(d->mCropWidget->advancedSettingsEnabled()); +} + +void CropTool::slotCropRequested() +{ + CropImageOperation* op = new CropImageOperation(d->mRect); + emit imageOperationRequested(op); + emit done(); +} + +QWidget* CropTool::widget() const +{ + return d->mCropWidget; +} + +void CropTool::onWidgetSlidedIn() +{ + setRect(d->computeVisibleImageRect()); +} + +} // namespace diff --git a/gwenview/lib/crop/croptool.h b/gwenview/lib/crop/croptool.h new file mode 100644 index 00000000..818f50d2 --- /dev/null +++ b/gwenview/lib/crop/croptool.h @@ -0,0 +1,83 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef CROPTOOL_H +#define CROPTOOL_H + +#include + +// Qt + +// KDE + +// Local +#include + +class QRect; + +namespace Gwenview +{ + +class AbstractImageOperation; +class ImageView; + +struct CropToolPrivate; +class GWENVIEWLIB_EXPORT CropTool : public AbstractRasterImageViewTool +{ + Q_OBJECT +public: + CropTool(RasterImageView* parent); + ~CropTool(); + + void setCropRatio(double ratio); + + void setRect(const QRect&); + QRect rect() const; + + virtual void paint(QPainter*); + + virtual void mousePressEvent(QGraphicsSceneMouseEvent*); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*); + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent*); + + virtual void toolActivated(); + virtual void toolDeactivated(); + + virtual QWidget* widget() const; + +public Q_SLOTS: + void onWidgetSlidedIn(); // reimp + +Q_SIGNALS: + void rectUpdated(const QRect&); + void done(); + void imageOperationRequested(AbstractImageOperation*); + +private Q_SLOTS: + void slotCropRequested(); + +private: + CropToolPrivate* const d; +}; + +} // namespace + +#endif /* CROPTOOL_H */ diff --git a/gwenview/lib/crop/cropwidget.cpp b/gwenview/lib/crop/cropwidget.cpp new file mode 100644 index 00000000..56e780b8 --- /dev/null +++ b/gwenview/lib/crop/cropwidget.cpp @@ -0,0 +1,328 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "cropwidget.moc" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include + +// Local +#include +#include "croptool.h" +#include "signalblocker.h" +#include "ui_cropwidget.h" + +namespace Gwenview +{ + +// Euclidean algorithm to compute the greatest common divisor of two integers. +// Found at: +// http://en.wikipedia.org/wiki/Euclidean_algorithm +static int gcd(int a, int b) +{ + return b == 0 ? a : gcd(b, a % b); +} + +static QSize screenRatio() +{ + const QRect rect = QApplication::desktop()->screenGeometry(); + const int width = rect.width(); + const int height = rect.height(); + const int divisor = gcd(width, height); + return QSize(width / divisor, height / divisor); +} + +struct CropWidgetPrivate : public Ui_CropWidget +{ + CropWidget* q; + + Document::Ptr mDocument; + CropTool* mCropTool; + bool mUpdatingFromCropTool; + + bool ratioIsConstrained() const + { + return cropRatio() > 0; + } + + double cropRatio() const + { + int index = ratioComboBox->currentIndex(); + if (index != -1 && ratioComboBox->currentText() == ratioComboBox->itemText(index)) { + // Get ratio from predefined value + // Note: We check currentText is itemText(currentIndex) because + // currentIndex is not reset to -1 when text is edited by hand. + QSizeF size = ratioComboBox->itemData(index).toSizeF(); + return size.height() / size.width(); + } + + // Not a predefined value, extract ratio from the combobox text + const QStringList lst = ratioComboBox->currentText().split(':'); + if (lst.size() != 2) { + return 0; + } + + bool ok; + const double width = lst[0].toDouble(&ok); + if (!ok) { + return 0; + } + const double height = lst[1].toDouble(&ok); + if (!ok) { + return 0; + } + + return height / width; + } + + void addRatioToComboBox(const QSizeF& size, const QString& label = QString()) + { + QString text = label.isEmpty() + ? QString("%1:%2").arg(size.width()).arg(size.height()) + : label; + ratioComboBox->addItem(text, QVariant(size)); + } + + void addSectionHeaderToComboBox(const QString& title) + { + // Insert a line + ratioComboBox->insertSeparator(ratioComboBox->count()); + + // Insert our section header + // This header is made of a separator with a text. We reset + // Qt::AccessibleDescriptionRole to the header text otherwise QComboBox + // delegate will draw a separator line instead of our text. + int index = ratioComboBox->count(); + ratioComboBox->insertSeparator(index); + ratioComboBox->setItemText(index, title); + ratioComboBox->setItemData(index, title, Qt::AccessibleDescriptionRole); + ratioComboBox->setItemData(index, Qt::AlignHCenter, Qt::TextAlignmentRole); + } + + void initRatioComboBox() + { + QList ratioList; + const qreal sqrt2 = qSqrt(2.); + ratioList + << QSizeF(7, 5) + << QSizeF(3, 2) + << QSizeF(4, 3) + << QSizeF(5, 4); + + addRatioToComboBox(QSizeF(1, 1), i18n("Square")); + addRatioToComboBox(screenRatio(), i18n("This Screen")); + addSectionHeaderToComboBox(i18n("Landscape")); + + Q_FOREACH(const QSizeF& size, ratioList) { + addRatioToComboBox(size); + } + addRatioToComboBox(QSizeF(sqrt2, 1), i18n("ISO Size (A4, A3...)")); + addRatioToComboBox(QSizeF(11, 8.5), i18n("US Letter")); + addSectionHeaderToComboBox(i18n("Portrait")); + Q_FOREACH(QSizeF size, ratioList) { + size.transpose(); + addRatioToComboBox(size); + } + addRatioToComboBox(QSizeF(1, sqrt2), i18n("ISO Size (A4, A3...)")); + addRatioToComboBox(QSizeF(8.5, 11), i18n("US Letter")); + + ratioComboBox->setMaxVisibleItems(ratioComboBox->count()); + ratioComboBox->setEditText(QString()); + + KLineEdit* edit = qobject_cast(ratioComboBox->lineEdit()); + Q_ASSERT(edit); + // Do not use i18n("%1:%2") because ':' should not be translated, it is + // used to parse the ratio string. + edit->setClickMessage(QString("%1:%2").arg(i18n("Width")).arg(i18n("Height"))); + } + + QRect cropRect() const + { + QRect rect( + leftSpinBox->value(), + topSpinBox->value(), + widthSpinBox->value(), + heightSpinBox->value() + ); + return rect; + } + + void initSpinBoxes() + { + QSize size = mDocument->size(); + leftSpinBox->setMaximum(size.width()); + widthSpinBox->setMaximum(size.width()); + topSpinBox->setMaximum(size.height()); + heightSpinBox->setMaximum(size.height()); + } + + void initDialogButtonBox() + { + QPushButton* cropButton = dialogButtonBox->button(QDialogButtonBox::Ok); + cropButton->setIcon(KIcon("transform-crop-and-resize")); + cropButton->setText(i18n("Crop")); + + QObject::connect(dialogButtonBox, SIGNAL(accepted()), + q, SIGNAL(cropRequested())); + QObject::connect(dialogButtonBox, SIGNAL(rejected()), + q, SIGNAL(done())); + } +}; + +CropWidget::CropWidget(QWidget* parent, RasterImageView* imageView, CropTool* cropTool) +: QWidget(parent) +, d(new CropWidgetPrivate) +{ + setWindowFlags(Qt::Tool); + d->q = this; + d->mDocument = imageView->document(); + d->mUpdatingFromCropTool = false; + d->mCropTool = cropTool; + d->setupUi(this); + setFont(KGlobalSettings::smallestReadableFont()); + layout()->setMargin(KDialog::marginHint()); + layout()->setSizeConstraint(QLayout::SetFixedSize); + + connect(d->advancedCheckBox, SIGNAL(toggled(bool)), + d->advancedWidget, SLOT(setVisible(bool))); + d->advancedWidget->setVisible(false); + d->advancedWidget->layout()->setMargin(0); + + d->initRatioComboBox(); + + connect(d->mCropTool, SIGNAL(rectUpdated(QRect)), + SLOT(setCropRect(QRect))); + + connect(d->leftSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotPositionChanged())); + connect(d->topSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotPositionChanged())); + connect(d->widthSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotWidthChanged())); + connect(d->heightSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotHeightChanged())); + + d->initDialogButtonBox(); + + connect(d->ratioComboBox, SIGNAL(editTextChanged(QString)), + SLOT(slotRatioComboBoxEditTextChanged())); + + // Don't do this before signals are connected, otherwise the tool won't get + // initialized + d->initSpinBoxes(); + + setCropRect(d->mCropTool->rect()); +} + +CropWidget::~CropWidget() +{ + delete d; +} + +void CropWidget::setAdvancedSettingsEnabled(bool enable) +{ + d->advancedCheckBox->setChecked(enable); +} + +bool CropWidget::advancedSettingsEnabled() const +{ + return d->advancedCheckBox->isChecked(); +} + +void CropWidget::setCropRect(const QRect& rect) +{ + d->mUpdatingFromCropTool = true; + d->leftSpinBox->setValue(rect.left()); + d->topSpinBox->setValue(rect.top()); + d->widthSpinBox->setValue(rect.width()); + d->heightSpinBox->setValue(rect.height()); + d->mUpdatingFromCropTool = false; +} + +void CropWidget::slotPositionChanged() +{ + const QSize size = d->mDocument->size(); + d->widthSpinBox->setMaximum(size.width() - d->leftSpinBox->value()); + d->heightSpinBox->setMaximum(size.height() - d->topSpinBox->value()); + + if (d->mUpdatingFromCropTool) { + return; + } + d->mCropTool->setRect(d->cropRect()); +} + +void CropWidget::slotWidthChanged() +{ + d->leftSpinBox->setMaximum(d->mDocument->width() - d->widthSpinBox->value()); + + if (d->mUpdatingFromCropTool) { + return; + } + if (d->ratioIsConstrained()) { + int height = int(d->widthSpinBox->value() * d->cropRatio()); + d->heightSpinBox->setValue(height); + } + d->mCropTool->setRect(d->cropRect()); +} + +void CropWidget::slotHeightChanged() +{ + d->topSpinBox->setMaximum(d->mDocument->height() - d->heightSpinBox->value()); + + if (d->mUpdatingFromCropTool) { + return; + } + if (d->ratioIsConstrained()) { + int width = int(d->heightSpinBox->value() / d->cropRatio()); + d->widthSpinBox->setValue(width); + } + d->mCropTool->setRect(d->cropRect()); +} + +void CropWidget::applyRatioConstraint() +{ + double ratio = d->cropRatio(); + d->mCropTool->setCropRatio(ratio); + + if (!d->ratioIsConstrained()) { + return; + } + QRect rect = d->cropRect(); + rect.setHeight(int(rect.width() * ratio)); + d->mCropTool->setRect(rect); +} + +void CropWidget::slotRatioComboBoxEditTextChanged() +{ + applyRatioConstraint(); +} + +} // namespace diff --git a/gwenview/lib/crop/cropwidget.h b/gwenview/lib/crop/cropwidget.h new file mode 100644 index 00000000..4615603c --- /dev/null +++ b/gwenview/lib/crop/cropwidget.h @@ -0,0 +1,70 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef CROPWIDGET_H +#define CROPWIDGET_H + +#include + +// Qt +#include + +// KDE + +// Local +#include + +namespace Gwenview +{ + +class CropTool; +class RasterImageView; + +struct CropWidgetPrivate; +class GWENVIEWLIB_EXPORT CropWidget : public QWidget +{ + Q_OBJECT +public: + CropWidget(QWidget* parent, RasterImageView*, CropTool*); + ~CropWidget(); + + void setAdvancedSettingsEnabled(bool enable); + bool advancedSettingsEnabled() const; + +Q_SIGNALS: + void cropRequested(); + void done(); + +private Q_SLOTS: + void slotPositionChanged(); + void slotWidthChanged(); + void slotHeightChanged(); + void setCropRect(const QRect& rect); + + void slotRatioComboBoxEditTextChanged(); + void applyRatioConstraint(); + +private: + CropWidgetPrivate* const d; +}; + +} // namespace + +#endif /* CROPWIDGET_H */ diff --git a/gwenview/lib/crop/cropwidget.ui b/gwenview/lib/crop/cropwidget.ui new file mode 100644 index 00000000..1d0fed89 --- /dev/null +++ b/gwenview/lib/crop/cropwidget.ui @@ -0,0 +1,198 @@ + + + CropWidget + + + + 0 + 0 + 790 + 66 + + + + + 0 + 0 + + + + Crop + + + + + + Qt::NoFocus + + + Advanced settings + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 12 + 20 + + + + + + + + Ratio: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + ratioComboBox + + + + + + + true + + + QComboBox::NoInsert + + + + + + + + 0 + 0 + + + + Position: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + leftSpinBox + + + + + + + + 1 + 0 + + + + + + + + + 1 + 0 + + + + + + + + Size: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + widthSpinBox + + + + + + + + 1 + 0 + + + + + + + + + 1 + 0 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + KDialogButtonBox + QDialogButtonBox +
kdialogbuttonbox.h
+
+ + KComboBox + QComboBox +
kcombobox.h
+
+ + KIntSpinBox + QSpinBox +
knuminput.h
+
+
+ + ratioComboBox + leftSpinBox + topSpinBox + widthSpinBox + heightSpinBox + + + +
diff --git a/gwenview/lib/datewidget.cpp b/gwenview/lib/datewidget.cpp new file mode 100644 index 00000000..036f4666 --- /dev/null +++ b/gwenview/lib/datewidget.cpp @@ -0,0 +1,148 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "datewidget.moc" + +// Qt +#include +#include + +// KDE +#include +#include +#include +#include + +// Local +#include + +namespace Gwenview +{ + +struct DateWidgetPrivate +{ + DateWidget* q; + + QDate mDate; + KDatePicker* mDatePicker; + StatusBarToolButton* mPreviousButton; + StatusBarToolButton* mDateButton; + StatusBarToolButton* mNextButton; + + void setupDatePicker() + { + mDatePicker = new KDatePicker; + /* Use Qt::Tool instead of Qt::Window so that the bubble does not appear in the task bar */ + //mDatePicker->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + mDatePicker->setWindowFlags(Qt::Popup); + mDatePicker->hide(); + mDatePicker->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); + + QObject::connect(mDatePicker, SIGNAL(dateEntered(QDate)), + q, SLOT(slotDatePickerModified(QDate))); + QObject::connect(mDatePicker, SIGNAL(dateSelected(QDate)), + q, SLOT(slotDatePickerModified(QDate))); + } + + void updateButton() + { + mDateButton->setText(KGlobal::locale()->formatDate(mDate, KLocale::ShortDate)); + } + + void adjustDate(int delta) + { + mDate = mDate.addDays(delta); + updateButton(); + q->dateChanged(mDate); + } +}; + +DateWidget::DateWidget(QWidget* parent) +: QWidget(parent) +, d(new DateWidgetPrivate) +{ + d->q = this; + + d->setupDatePicker(); + + d->mPreviousButton = new StatusBarToolButton; + d->mPreviousButton->setGroupPosition(StatusBarToolButton::GroupLeft); + // FIXME: RTL + d->mPreviousButton->setIcon(SmallIcon("go-previous")); + connect(d->mPreviousButton, SIGNAL(clicked()), SLOT(goToPrevious())); + + d->mDateButton = new StatusBarToolButton; + d->mDateButton->setGroupPosition(StatusBarToolButton::GroupCenter); + connect(d->mDateButton, SIGNAL(clicked()), SLOT(showDatePicker())); + + d->mNextButton = new StatusBarToolButton; + d->mNextButton->setGroupPosition(StatusBarToolButton::GroupRight); + d->mNextButton->setIcon(SmallIcon("go-next")); + connect(d->mNextButton, SIGNAL(clicked()), SLOT(goToNext())); + + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + layout->addWidget(d->mPreviousButton); + layout->addWidget(d->mDateButton); + layout->addWidget(d->mNextButton); +} + +DateWidget::~DateWidget() +{ + delete d->mDatePicker; + delete d; +} + +QDate DateWidget::date() const +{ + return d->mDate; +} + +void DateWidget::showDatePicker() +{ + d->mDatePicker->setDate(d->mDate); + d->mDatePicker->adjustSize(); + const QPoint pos = mapToGlobal(QPoint(0, -d->mDatePicker->height())); + d->mDatePicker->move(pos); + d->mDatePicker->show(); +} + +void DateWidget::slotDatePickerModified(const QDate& date) +{ + d->mDatePicker->hide(); + d->mDate = date; + emit dateChanged(date); + + d->updateButton(); +} + +void DateWidget::goToPrevious() +{ + d->adjustDate(-1); +} + +void DateWidget::goToNext() +{ + d->adjustDate(1); +} + +} // namespace diff --git a/gwenview/lib/datewidget.h b/gwenview/lib/datewidget.h new file mode 100644 index 00000000..6bd99409 --- /dev/null +++ b/gwenview/lib/datewidget.h @@ -0,0 +1,64 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DATEWIDGET_H +#define DATEWIDGET_H + +#include "gwenviewlib_export.h" + +// Qt +#include + +// KDE + +// Local + +class QDate; + +namespace Gwenview +{ + +struct DateWidgetPrivate; +class GWENVIEWLIB_EXPORT DateWidget : public QWidget +{ + Q_OBJECT +public: + DateWidget(QWidget* parent = 0); + ~DateWidget(); + + QDate date() const; + +Q_SIGNALS: + void dateChanged(const QDate&); + +private Q_SLOTS: + void showDatePicker(); + void slotDatePickerModified(const QDate& date); + void goToPrevious(); + void goToNext(); + +private: + friend struct DateWidgetPrivate; + DateWidgetPrivate* const d; +}; + +} // namespace + +#endif /* DATEWIDGET_H */ diff --git a/gwenview/lib/disabledactionshortcutmonitor.cpp b/gwenview/lib/disabledactionshortcutmonitor.cpp new file mode 100644 index 00000000..14130fbb --- /dev/null +++ b/gwenview/lib/disabledactionshortcutmonitor.cpp @@ -0,0 +1,70 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2014 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +// Self +#include + +// Local + +// KDE + +// Qt +#include +#include +#include + +namespace Gwenview +{ + +class DisabledActionShortcutMonitorPrivate +{ +public: + QShortcut* mShortcut; +}; + +DisabledActionShortcutMonitor::DisabledActionShortcutMonitor(QAction* action, QWidget* parent) +: QObject(parent) +, d(new DisabledActionShortcutMonitorPrivate) +{ + d->mShortcut = new QShortcut(parent); + connect(d->mShortcut, SIGNAL(activated()), SIGNAL(activated())); + action->installEventFilter(this); +} + +DisabledActionShortcutMonitor::~DisabledActionShortcutMonitor() +{ + delete d->mShortcut; + delete d; +} + +bool DisabledActionShortcutMonitor::eventFilter(QObject* object, QEvent* event) +{ + if (event->type() == QEvent::ActionChanged) { + QAction* action = static_cast(object); + if (action->isEnabled()) { + // Unset the shortcut otherwise we get a dialog complaining about + // ambiguous shortcuts when the user tries to trigger the action + d->mShortcut->setKey(QKeySequence()); + } else { + d->mShortcut->setKey(action->shortcut()); + } + } + return false; +} + +} // namespace diff --git a/gwenview/lib/disabledactionshortcutmonitor.h b/gwenview/lib/disabledactionshortcutmonitor.h new file mode 100644 index 00000000..42818887 --- /dev/null +++ b/gwenview/lib/disabledactionshortcutmonitor.h @@ -0,0 +1,63 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2014 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef DISABLEDACTIONSHORTCUTMONITOR_H +#define DISABLEDACTIONSHORTCUTMONITOR_H + +#include + +// Local + +// KDE + +// Qt +#include + +class QAction; + +namespace Gwenview +{ + +class DisabledActionShortcutMonitorPrivate; +/** + * Monitors an action and emits a signal if one tries to trigger the action + * using its shortcut while it is disabled. + */ +class GWENVIEWLIB_EXPORT DisabledActionShortcutMonitor : public QObject +{ + Q_OBJECT +public: + /** + * parent must be a widget because we need to create a QShortcut + */ + DisabledActionShortcutMonitor(QAction* action, QWidget* parent); + ~DisabledActionShortcutMonitor(); + +Q_SIGNALS: + void activated(); + +protected: + bool eventFilter(QObject* object, QEvent* event); + +private: + DisabledActionShortcutMonitorPrivate* const d; +}; + +} // namespace + +#endif /* DISABLEDACTIONSHORTCUTMONITOR_H */ diff --git a/gwenview/lib/document/abstractdocumenteditor.h b/gwenview/lib/document/abstractdocumenteditor.h new file mode 100644 index 00000000..0e3a482a --- /dev/null +++ b/gwenview/lib/document/abstractdocumenteditor.h @@ -0,0 +1,71 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef ABSTRACTDOCUMENTEDITOR_H +#define ABSTRACTDOCUMENTEDITOR_H + +#include + +// Qt + +// KDE + +// Local +#include + +class QImage; + +namespace Gwenview +{ + +/** + * An interface which can be returned by some implementations of + * AbstractDocumentImpl if they support edition. + */ +class GWENVIEWLIB_EXPORT AbstractDocumentEditor +{ +public: + virtual ~AbstractDocumentEditor() + {} + + /** + * Replaces the current image with image. + * Calling this while the document is loaded won't do anything. + * + * This method should only be called from a subclass of + * AbstractImageOperation and applied through Document::undoStack(). + */ + virtual void setImage(const QImage&) = 0; + + /** + * Apply a transformation to the document image. + * + * Transformations are handled by the Document class because it can be + * done in a lossless way by some Document implementations. + * + * This method should only be called from a subclass of + * AbstractImageOperation and applied through Document::undoStack(). + */ + virtual void applyTransformation(Orientation) = 0; +}; + +} // namespace + +#endif /* ABSTRACTDOCUMENTEDITOR_H */ diff --git a/gwenview/lib/document/abstractdocumentimpl.cpp b/gwenview/lib/document/abstractdocumentimpl.cpp new file mode 100644 index 00000000..d7077d29 --- /dev/null +++ b/gwenview/lib/document/abstractdocumentimpl.cpp @@ -0,0 +1,100 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "abstractdocumentimpl.moc" + +// Qt + +// KDE + +// Local +#include "document.h" + +namespace Gwenview +{ + +struct AbstractDocumentImplPrivate +{ + Document* mDocument; +}; + +AbstractDocumentImpl::AbstractDocumentImpl(Document* document) +: d(new AbstractDocumentImplPrivate) +{ + d->mDocument = document; +} + +AbstractDocumentImpl::~AbstractDocumentImpl() +{ + delete d; +} + +Document* AbstractDocumentImpl::document() const +{ + return d->mDocument; +} + +void AbstractDocumentImpl::switchToImpl(AbstractDocumentImpl* impl) +{ + d->mDocument->switchToImpl(impl); +} + +void AbstractDocumentImpl::setDocumentImage(const QImage& image) +{ + d->mDocument->setImageInternal(image); +} + +void AbstractDocumentImpl::setDocumentImageSize(const QSize& size) +{ + d->mDocument->setSize(size); +} + +void AbstractDocumentImpl::setDocumentFormat(const QByteArray& format) +{ + d->mDocument->setFormat(format); +} + +void AbstractDocumentImpl::setDocumentKind(MimeTypeUtils::Kind kind) +{ + d->mDocument->setKind(kind); +} + +void AbstractDocumentImpl::setDocumentExiv2Image(Exiv2::Image::AutoPtr image) +{ + d->mDocument->setExiv2Image(image); +} + +void AbstractDocumentImpl::setDocumentDownSampledImage(const QImage& image, int invertedZoom) +{ + d->mDocument->setDownSampledImage(image, invertedZoom); +} + +void AbstractDocumentImpl::setDocumentErrorString(const QString& string) +{ + d->mDocument->setErrorString(string); +} + +void AbstractDocumentImpl::setDocumentCmsProfile(Cms::Profile::Ptr profile) +{ + d->mDocument->setCmsProfile(profile); +} + +} // namespace diff --git a/gwenview/lib/document/abstractdocumentimpl.h b/gwenview/lib/document/abstractdocumentimpl.h new file mode 100644 index 00000000..de888677 --- /dev/null +++ b/gwenview/lib/document/abstractdocumentimpl.h @@ -0,0 +1,123 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef ABSTRACTDOCUMENTIMPL_H +#define ABSTRACTDOCUMENTIMPL_H + +// Qt +#include +#include + +// KDE + +// Local +#include +#include + +class QImage; +class QRect; + +namespace Gwenview +{ + +class Document; +class DocumentJob; +class AbstractDocumentEditor; + +struct AbstractDocumentImplPrivate; +class AbstractDocumentImpl : public QObject +{ + Q_OBJECT +public: + AbstractDocumentImpl(Document*); + virtual ~AbstractDocumentImpl(); + + /** + * This method is called by Document::switchToImpl after it has connected + * signals to the object + */ + virtual void init() = 0; + + virtual Document::LoadingState loadingState() const = 0; + + virtual DocumentJob* save(const KUrl&, const QByteArray& /*format*/) + { + return 0; + } + + virtual AbstractDocumentEditor* editor() + { + return 0; + } + + virtual QByteArray rawData() const + { + return QByteArray(); + } + + virtual bool isEditable() const + { + return false; + } + + virtual bool isAnimated() const + { + return false; + } + + virtual void startAnimation() + {} + + virtual void stopAnimation() + {} + + Document* document() const; + + virtual QSvgRenderer* svgRenderer() const + { + return 0; + } + +Q_SIGNALS: + void imageRectUpdated(const QRect&); + void metaInfoLoaded(); + void loaded(); + void loadingFailed(); + void isAnimatedUpdated(); + void editorUpdated(); + +protected: + void setDocumentImage(const QImage& image); + void setDocumentImageSize(const QSize& size); + void setDocumentKind(MimeTypeUtils::Kind); + void setDocumentFormat(const QByteArray& format); + void setDocumentExiv2Image(Exiv2::Image::AutoPtr); + void setDocumentDownSampledImage(const QImage&, int invertedZoom); + void setDocumentCmsProfile(Cms::Profile::Ptr profile); + void setDocumentErrorString(const QString&); + void switchToImpl(AbstractDocumentImpl* impl); + +private: + AbstractDocumentImplPrivate* const d; +}; + +} // namespace + +#endif /* ABSTRACTDOCUMENTIMPL_H */ diff --git a/gwenview/lib/document/animateddocumentloadedimpl.cpp b/gwenview/lib/document/animateddocumentloadedimpl.cpp new file mode 100644 index 00000000..e60c0f20 --- /dev/null +++ b/gwenview/lib/document/animateddocumentloadedimpl.cpp @@ -0,0 +1,112 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "animateddocumentloadedimpl.moc" + +// Qt +#include +#include +#include + +// KDE +#include + +// Local + +namespace Gwenview +{ + +struct AnimatedDocumentLoadedImplPrivate +{ + QByteArray mRawData; + QBuffer mMovieBuffer; + QMovie mMovie; +}; + +AnimatedDocumentLoadedImpl::AnimatedDocumentLoadedImpl(Document* document, const QByteArray& rawData) +: AbstractDocumentImpl(document) +, d(new AnimatedDocumentLoadedImplPrivate) +{ + d->mRawData = rawData; + + connect(&d->mMovie, SIGNAL(frameChanged(int)), SLOT(slotFrameChanged(int))); + + d->mMovieBuffer.setBuffer(&d->mRawData); + d->mMovieBuffer.open(QIODevice::ReadOnly); + d->mMovie.setDevice(&d->mMovieBuffer); +} + +AnimatedDocumentLoadedImpl::~AnimatedDocumentLoadedImpl() +{ + delete d; +} + +void AnimatedDocumentLoadedImpl::init() +{ + emit isAnimatedUpdated(); + if (!document()->image().isNull()) { + // We may reach this point without an image if the first frame got + // downsampled by LoadingDocumentImpl (unlikely for now because the gif + // io handler does not support the QImageIOHandler::ScaledSize option) + emit imageRectUpdated(document()->image().rect()); + emit loaded(); + } +} + +Document::LoadingState AnimatedDocumentLoadedImpl::loadingState() const +{ + return Document::Loaded; +} + +QByteArray AnimatedDocumentLoadedImpl::rawData() const +{ + return d->mRawData; +} + +void AnimatedDocumentLoadedImpl::slotFrameChanged(int /*frameNumber*/) +{ + QImage image = d->mMovie.currentImage(); + setDocumentImage(image); + emit imageRectUpdated(image.rect()); +} + +bool AnimatedDocumentLoadedImpl::isAnimated() const +{ + return true; +} + +void AnimatedDocumentLoadedImpl::startAnimation() +{ + d->mMovie.start(); + if (d->mMovie.state() == QMovie::NotRunning) { + // This is true with qt-copy as of 2008.08.23 + kDebug() << "QMovie didn't start. This can happen in some cases when starting for the second time."; + kDebug() << "Trying to start again, it usually fixes the bug."; + d->mMovie.start(); + } +} + +void AnimatedDocumentLoadedImpl::stopAnimation() +{ + d->mMovie.stop(); +} + +} // namespace diff --git a/gwenview/lib/document/animateddocumentloadedimpl.h b/gwenview/lib/document/animateddocumentloadedimpl.h new file mode 100644 index 00000000..f47711fe --- /dev/null +++ b/gwenview/lib/document/animateddocumentloadedimpl.h @@ -0,0 +1,58 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef ANIMATEDDOCUMENTLOADEDIMPL_H +#define ANIMATEDDOCUMENTLOADEDIMPL_H + +// Qt + +// KDE + +// Local +#include + +namespace Gwenview +{ + +struct AnimatedDocumentLoadedImplPrivate; +class AnimatedDocumentLoadedImpl : public AbstractDocumentImpl +{ + Q_OBJECT +public: + AnimatedDocumentLoadedImpl(Document*, const QByteArray&); + ~AnimatedDocumentLoadedImpl(); + + virtual void init(); + virtual Document::LoadingState loadingState() const; + virtual QByteArray rawData() const; + virtual bool isAnimated() const; + virtual void startAnimation(); + virtual void stopAnimation(); + +private Q_SLOTS: + void slotFrameChanged(int frameNumber); + +private: + AnimatedDocumentLoadedImplPrivate* const d; +}; + +} // namespace + +#endif /* ANIMATEDDOCUMENTLOADEDIMPL_H */ diff --git a/gwenview/lib/document/document.cpp b/gwenview/lib/document/document.cpp new file mode 100644 index 00000000..bcbd3b9c --- /dev/null +++ b/gwenview/lib/document/document.cpp @@ -0,0 +1,576 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "document.moc" +#include "document_p.moc" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include + +// Local +#include "documentjob.h" +#include "emptydocumentimpl.h" +#include "gvdebug.h" +#include "imagemetainfomodel.h" +#include "loadingdocumentimpl.h" +#include "loadingjob.h" +#include "savejob.h" + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +#ifdef ENABLE_LOG + +static void logQueue(DocumentPrivate* d) +{ +#define PREFIX " QUEUE: " + if (!d->mCurrentJob) { + Q_ASSERT(d->mJobQueue.isEmpty()); + qDebug(PREFIX "No current job, no pending jobs"); + return; + } + qDebug() << PREFIX "Current job:" << d->mCurrentJob.data(); + if (d->mJobQueue.isEmpty()) { + qDebug(PREFIX "No pending jobs"); + return; + } + qDebug(PREFIX "%d pending job(s):", d->mJobQueue.size()); + Q_FOREACH(DocumentJob* job, d->mJobQueue) { + Q_ASSERT(job); + qDebug() << PREFIX "-" << job; + } +#undef PREFIX +} + +#define LOG_QUEUE(msg, d) \ + LOG(msg); \ + logQueue(d) + +#else + +#define LOG_QUEUE(msg, d) + +#endif + +//- DocumentPrivate --------------------------------------- +void DocumentPrivate::scheduleImageLoading(int invertedZoom) +{ + LoadingDocumentImpl* impl = qobject_cast(mImpl); + Q_ASSERT(impl); + impl->loadImage(invertedZoom); +} + +void DocumentPrivate::scheduleImageDownSampling(int invertedZoom) +{ + LOG("invertedZoom=" << invertedZoom); + DownSamplingJob* job = qobject_cast(mCurrentJob.data()); + if (job && job->mInvertedZoom == invertedZoom) { + LOG("Current job is already doing it"); + return; + } + + // Remove any previously scheduled downsampling job + DocumentJobQueue::Iterator it; + for (it = mJobQueue.begin(); it != mJobQueue.end(); ++it) { + DownSamplingJob* job = qobject_cast(*it); + if (!job) { + continue; + } + if (job->mInvertedZoom == invertedZoom) { + // Already scheduled, nothing to do + LOG("Already scheduled"); + return; + } else { + LOG("Removing downsampling job"); + mJobQueue.erase(it); + delete job; + } + } + q->enqueueJob(new DownSamplingJob(invertedZoom)); +} + +void DocumentPrivate::downSampleImage(int invertedZoom) +{ + mDownSampledImageMap[invertedZoom] = mImage.scaled(mImage.size() / invertedZoom, Qt::KeepAspectRatio, Qt::FastTransformation); + if (mDownSampledImageMap[invertedZoom].size().isEmpty()) { + mDownSampledImageMap[invertedZoom] = mImage; + } + q->downSampledImageReady(); +}; + +//- DownSamplingJob --------------------------------------- +void DownSamplingJob::doStart() +{ + DocumentPrivate* d = document()->d; + d->downSampleImage(mInvertedZoom); + setError(NoError); + emitResult(); +} + +//- Document ---------------------------------------------- +qreal Document::maxDownSampledZoom() +{ + return 0.5; +} + +Document::Document(const KUrl& url) +: QObject() +, d(new DocumentPrivate) +{ + d->q = this; + d->mImpl = 0; + d->mUrl = url; + d->mKeepRawData = false; + connect(&d->mUndoStack, SIGNAL(indexChanged(int)), SLOT(slotUndoIndexChanged())); + + reload(); +} + +Document::~Document() +{ + // We do not want undo stack to emit signals, forcing us to emit signals + // ourself while we are being destroyed. + disconnect(&d->mUndoStack, 0, this, 0); + + delete d->mImpl; + delete d; +} + +void Document::reload() +{ + d->mSize = QSize(); + d->mImage = QImage(); + d->mDownSampledImageMap.clear(); + d->mExiv2Image.reset(); + d->mKind = MimeTypeUtils::KIND_UNKNOWN; + d->mFormat = QByteArray(); + d->mImageMetaInfoModel.setUrl(d->mUrl); + d->mUndoStack.clear(); + d->mErrorString.clear(); + d->mCmsProfile = 0; + + switchToImpl(new LoadingDocumentImpl(this)); +} + +const QImage& Document::image() const +{ + return d->mImage; +} + +/** + * invertedZoom is the biggest power of 2 for which zoom < 1/invertedZoom. + * Example: + * zoom = 0.4 == 1/2.5 => invertedZoom = 2 (1/2.5 < 1/2) + * zoom = 0.2 == 1/5 => invertedZoom = 4 (1/5 < 1/4) + */ +inline int invertedZoomForZoom(qreal zoom) +{ + int invertedZoom; + for (invertedZoom = 1; zoom < 1. / (invertedZoom * 4); invertedZoom *= 2) {} + return invertedZoom; +} + +const QImage& Document::downSampledImageForZoom(qreal zoom) const +{ + static const QImage sNullImage; + + int invertedZoom = invertedZoomForZoom(zoom); + if (invertedZoom == 1) { + return d->mImage; + } + + if (!d->mDownSampledImageMap.contains(invertedZoom)) { + if (!d->mImage.isNull()) { + // Special case: if we have the full image and the down sampled + // image would be too small, return the original image. + const QSize downSampledSize = d->mImage.size() / invertedZoom; + if (downSampledSize.isEmpty()) { + return d->mImage; + } + } + return sNullImage; + } + + return d->mDownSampledImageMap[invertedZoom]; +} + +Document::LoadingState Document::loadingState() const +{ + return d->mImpl->loadingState(); +} + +void Document::switchToImpl(AbstractDocumentImpl* impl) +{ + Q_ASSERT(impl); + LOG("old impl:" << d->mImpl << "new impl:" << impl); + if (d->mImpl) { + d->mImpl->deleteLater(); + } + d->mImpl = impl; + + connect(d->mImpl, SIGNAL(metaInfoLoaded()), + this, SLOT(emitMetaInfoLoaded())); + connect(d->mImpl, SIGNAL(loaded()), + this, SLOT(emitLoaded())); + connect(d->mImpl, SIGNAL(loadingFailed()), + this, SLOT(emitLoadingFailed())); + connect(d->mImpl, SIGNAL(imageRectUpdated(QRect)), + this, SIGNAL(imageRectUpdated(QRect))); + connect(d->mImpl, SIGNAL(isAnimatedUpdated()), + this, SIGNAL(isAnimatedUpdated())); + d->mImpl->init(); +} + +void Document::setImageInternal(const QImage& image) +{ + d->mImage = image; + d->mDownSampledImageMap.clear(); + + // If we didn't get the image size before decoding the full image, set it + // now + setSize(d->mImage.size()); +} + +KUrl Document::url() const +{ + return d->mUrl; +} + +QByteArray Document::rawData() const +{ + return d->mImpl->rawData(); +} + +bool Document::keepRawData() const +{ + return d->mKeepRawData; +} + +void Document::setKeepRawData(bool value) +{ + d->mKeepRawData = value; +} + +void Document::waitUntilLoaded() +{ + startLoadingFullImage(); + while (true) { + LoadingState state = loadingState(); + if (state == Loaded || state == LoadingFailed) { + return; + } + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); + } +} + +DocumentJob* Document::save(const KUrl& url, const QByteArray& format) +{ + waitUntilLoaded(); + DocumentJob* job = d->mImpl->save(url, format); + if (!job) { + kWarning() << "Implementation does not support saving!"; + setErrorString(i18nc("@info", "Gwenview cannot save this kind of documents.")); + return 0; + } + job->setProperty("oldUrl", d->mUrl); + job->setProperty("newUrl", url); + connect(job, SIGNAL(result(KJob*)), SLOT(slotSaveResult(KJob*))); + enqueueJob(job); + return job; +} + +void Document::slotSaveResult(KJob* job) +{ + if (job->error()) { + setErrorString(job->errorString()); + } else { + d->mUndoStack.setClean(); + SaveJob* saveJob = static_cast(job); + d->mUrl = saveJob->newUrl(); + d->mImageMetaInfoModel.setUrl(d->mUrl); + saved(saveJob->oldUrl(), d->mUrl); + } +} + +QByteArray Document::format() const +{ + return d->mFormat; +} + +void Document::setFormat(const QByteArray& format) +{ + d->mFormat = format; + emit metaInfoUpdated(); +} + +MimeTypeUtils::Kind Document::kind() const +{ + return d->mKind; +} + +void Document::setKind(MimeTypeUtils::Kind kind) +{ + d->mKind = kind; + emit kindDetermined(d->mUrl); +} + +QSize Document::size() const +{ + return d->mSize; +} + +bool Document::hasAlphaChannel() const +{ + if (d->mImage.isNull()) { + return false; + } else { + return d->mImage.hasAlphaChannel(); + } +} + +int Document::memoryUsage() const +{ + // FIXME: Take undo stack into account + int usage = d->mImage.numBytes(); + usage += rawData().length(); + return usage; +} + +void Document::setSize(const QSize& size) +{ + if (size == d->mSize) { + return; + } + d->mSize = size; + d->mImageMetaInfoModel.setImageSize(size); + emit metaInfoUpdated(); +} + +bool Document::isModified() const +{ + return !d->mUndoStack.isClean(); +} + +AbstractDocumentEditor* Document::editor() +{ + return d->mImpl->editor(); +} + +void Document::setExiv2Image(Exiv2::Image::AutoPtr image) +{ + d->mExiv2Image = image; + d->mImageMetaInfoModel.setExiv2Image(d->mExiv2Image.get()); + emit metaInfoUpdated(); +} + +void Document::setDownSampledImage(const QImage& image, int invertedZoom) +{ + Q_ASSERT(!d->mDownSampledImageMap.contains(invertedZoom)); + d->mDownSampledImageMap[invertedZoom] = image; + emit downSampledImageReady(); +} + +QString Document::errorString() const +{ + return d->mErrorString; +} + +void Document::setErrorString(const QString& string) +{ + d->mErrorString = string; +} + +ImageMetaInfoModel* Document::metaInfo() const +{ + return &d->mImageMetaInfoModel; +} + +void Document::startLoadingFullImage() +{ + LoadingState state = loadingState(); + if (state <= MetaInfoLoaded) { + // Schedule full image loading + LoadingJob* job = new LoadingJob; + job->uiDelegate()->setAutoWarningHandlingEnabled(false); + job->uiDelegate()->setAutoErrorHandlingEnabled(false); + enqueueJob(job); + d->scheduleImageLoading(1); + } else if (state == Loaded) { + return; + } else if (state == LoadingFailed) { + kWarning() << "Can't load full image: loading has already failed"; + } +} + +bool Document::prepareDownSampledImageForZoom(qreal zoom) +{ + if (zoom >= maxDownSampledZoom()) { + kWarning() << "No need to call prepareDownSampledImageForZoom if zoom >= " << maxDownSampledZoom(); + return true; + } + + int invertedZoom = invertedZoomForZoom(zoom); + if (d->mDownSampledImageMap.contains(invertedZoom)) { + LOG("downSampledImageForZoom=" << zoom << "invertedZoom=" << invertedZoom << "ready"); + return true; + } + + LOG("downSampledImageForZoom=" << zoom << "invertedZoom=" << invertedZoom << "not ready"); + if (loadingState() == LoadingFailed) { + kWarning() << "Image has failed to load, not doing anything"; + return false; + } else if (loadingState() == Loaded) { + d->scheduleImageDownSampling(invertedZoom); + return false; + } + + // Schedule down sampled image loading + d->scheduleImageLoading(invertedZoom); + + return false; +} + +void Document::emitMetaInfoLoaded() +{ + emit metaInfoLoaded(d->mUrl); +} + +void Document::emitLoaded() +{ + emit loaded(d->mUrl); +} + +void Document::emitLoadingFailed() +{ + emit loadingFailed(d->mUrl); +} + +QUndoStack* Document::undoStack() const +{ + return &d->mUndoStack; +} + +void Document::slotUndoIndexChanged() +{ + if (d->mUndoStack.isClean()) { + // If user just undid all his changes this does not really correspond + // to a save, but it's similar enough as far as Document users are + // concerned + saved(d->mUrl, d->mUrl); + } else { + modified(d->mUrl); + } +} + +bool Document::isEditable() const +{ + return d->mImpl->isEditable(); +} + +bool Document::isAnimated() const +{ + return d->mImpl->isAnimated(); +} + +void Document::startAnimation() +{ + return d->mImpl->startAnimation(); +} + +void Document::stopAnimation() +{ + return d->mImpl->stopAnimation(); +} + +void Document::enqueueJob(DocumentJob* job) +{ + LOG("job=" << job); + job->setDocument(Ptr(this)); + connect(job, SIGNAL(finished(KJob*)), + SLOT(slotJobFinished(KJob*))); + if (d->mCurrentJob) { + d->mJobQueue.enqueue(job); + } else { + d->mCurrentJob = job; + LOG("Starting first job"); + job->start(); + busyChanged(d->mUrl, true); + } + LOG_QUEUE("Job added", d); +} + +void Document::slotJobFinished(KJob* job) +{ + LOG("job=" << job); + GV_RETURN_IF_FAIL(job == d->mCurrentJob.data()); + + if (d->mJobQueue.isEmpty()) { + LOG("All done"); + d->mCurrentJob.clear(); + busyChanged(d->mUrl, false); + allTasksDone(); + } else { + LOG("Starting next job"); + d->mCurrentJob = d->mJobQueue.dequeue(); + GV_RETURN_IF_FAIL(d->mCurrentJob); + d->mCurrentJob.data()->start(); + } + LOG_QUEUE("Removed done job", d); +} + +bool Document::isBusy() const +{ + return !d->mJobQueue.isEmpty(); +} + +QSvgRenderer* Document::svgRenderer() const +{ + return d->mImpl->svgRenderer(); +} + +void Document::setCmsProfile(Cms::Profile::Ptr ptr) +{ + d->mCmsProfile = ptr; +} + +Cms::Profile::Ptr Document::cmsProfile() const +{ + return d->mCmsProfile; +} + +} // namespace diff --git a/gwenview/lib/document/document.h b/gwenview/lib/document/document.h new file mode 100644 index 00000000..2795fc10 --- /dev/null +++ b/gwenview/lib/document/document.h @@ -0,0 +1,252 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENT_H +#define DOCUMENT_H + +#include + +#include +#include + +// Qt +#include +#include +#include + +// KDE +#include + +// Local +#include +#include + +class QImage; +class QRect; +class QSize; +class QSvgRenderer; +class QUndoStack; + +class KJob; +class KUrl; + +namespace Gwenview +{ + +class AbstractDocumentEditor; +class AbstractDocumentImpl; +class DocumentJob; +class DocumentFactory; +struct DocumentPrivate; +class ImageMetaInfoModel; + +/** + * This class represents an image. + * + * It handles loading and saving the image, applying operations and maintaining + * the document undo stack. + * + * It is capable of loading down sampled versions of an image using + * prepareDownSampledImageForZoom() and downSampledImageForZoom(). Down sampled + * images load much faster than the full image but you need to load the full + * image to manipulate it( use startLoadingFullImage() to do so). + * + * To get a Document instance for url, ask for one with + * DocumentFactory::instance()->load(url); + */ +class GWENVIEWLIB_EXPORT Document : public QObject, public QSharedData +{ + Q_OBJECT +public: + /** + * Document won't produce down sampled images for any zoom value higher than maxDownSampledZoom(). + * + * Note: We can't use the enum {} trick to declare this constant, that's + * why it's defined as a static method + */ + static qreal maxDownSampledZoom(); + + enum LoadingState { + Loading, ///< Image is loading + KindDetermined, ///< Image is still loading, but kind has been determined + MetaInfoLoaded, ///< Image is still loading, but meta info has been loaded + Loaded, ///< Full image has been loaded + LoadingFailed ///< Image loading has failed + }; + + typedef KSharedPtr Ptr; + ~Document(); + + /** + * Returns a message for the last error which happened + */ + QString errorString() const; + + void reload(); + + void startLoadingFullImage(); + + /** + * Prepare a version of the image down sampled to be a bit bigger than + * size() * @a zoom. + * Do not ask for a down sampled image for @a zoom >= to MaxDownSampledZoom. + * + * @return true if the image is ready, false if not. In this case the + * downSampledImageReady() signal will be emitted. + */ + bool prepareDownSampledImageForZoom(qreal zoom); + + LoadingState loadingState() const; + + MimeTypeUtils::Kind kind() const; + + bool isModified() const; + + const QImage& image() const; + + const QImage& downSampledImageForZoom(qreal zoom) const; + + /** + * Returns an implementation of AbstractDocumentEditor if this document can + * be edited. + */ + AbstractDocumentEditor* editor(); + + KUrl url() const; + + DocumentJob* save(const KUrl& url, const QByteArray& format); + + QByteArray format() const; + + void waitUntilLoaded(); + + QSize size() const; + + int width() const + { + return size().width(); + } + + int height() const + { + return size().height(); + } + + bool hasAlphaChannel() const; + + ImageMetaInfoModel* metaInfo() const; + + QUndoStack* undoStack() const; + + void setKeepRawData(bool); + + bool keepRawData() const; + + /** + * Returns how much bytes the document is using + */ + int memoryUsage() const; + + /** + * Returns the compressed version of the document, if it is still + * available. + */ + QByteArray rawData() const; + + Cms::Profile::Ptr cmsProfile() const; + + /** + * Returns a QSvgRenderer which can be used to render this document if it is + * an SVG image. Returns a NULL pointer otherwise. + */ + QSvgRenderer* svgRenderer() const; + + /** + * Returns true if the image can be edited. + * You must ensure it has been fully loaded with startLoadingFullImage() first. + */ + bool isEditable() const; + + /** + * Returns true if the image is animated (eg: gif or mng format) + */ + bool isAnimated() const; + + /** + * Starts animation. Only sensible if isAnimated() returns true. + */ + void startAnimation(); + + /** + * Stops animation. Only sensible if isAnimated() returns true. + */ + void stopAnimation(); + + void enqueueJob(DocumentJob*); + + /** + * Returns true if there are queued tasks for this document. + */ + bool isBusy() const; + +Q_SIGNALS: + void downSampledImageReady(); + void imageRectUpdated(const QRect&); + void kindDetermined(const KUrl&); + void metaInfoLoaded(const KUrl&); + void loaded(const KUrl&); + void loadingFailed(const KUrl&); + void saved(const KUrl& oldUrl, const KUrl& newUrl); + void modified(const KUrl&); + void metaInfoUpdated(); + void isAnimatedUpdated(); + void busyChanged(const KUrl&, bool); + void allTasksDone(); + +private Q_SLOTS: + void emitMetaInfoLoaded(); + void emitLoaded(); + void emitLoadingFailed(); + void slotUndoIndexChanged(); + void slotSaveResult(KJob*); + void slotJobFinished(KJob*); + +private: + friend class AbstractDocumentImpl; + friend class DocumentFactory; + friend struct DocumentPrivate; + friend class DownSamplingJob; + + void setImageInternal(const QImage&); + void setKind(MimeTypeUtils::Kind); + void setFormat(const QByteArray&); + void setSize(const QSize&); + void setExiv2Image(Exiv2::Image::AutoPtr); + void setDownSampledImage(const QImage&, int invertedZoom); + void switchToImpl(AbstractDocumentImpl* impl); + void setErrorString(const QString&); + void setCmsProfile(Cms::Profile::Ptr); + + Document(const KUrl&); + DocumentPrivate * const d; +}; + +} // namespace + +#endif /* DOCUMENT_H */ diff --git a/gwenview/lib/document/document_p.h b/gwenview/lib/document/document_p.h new file mode 100644 index 00000000..075f554a --- /dev/null +++ b/gwenview/lib/document/document_p.h @@ -0,0 +1,89 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2013 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENT_P_H +#define DOCUMENT_P_H + +// Local +#include +#include + +// KDE +#include + +// Qt +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +typedef QQueue DocumentJobQueue; +struct DocumentPrivate +{ + Document* q; + AbstractDocumentImpl* mImpl; + KUrl mUrl; + bool mKeepRawData; + QWeakPointer mCurrentJob; + DocumentJobQueue mJobQueue; + + /** + * @defgroup imagedata should be reset in reload() + * @{ + */ + QSize mSize; + QImage mImage; + QMap mDownSampledImageMap; + Exiv2::Image::AutoPtr mExiv2Image; + MimeTypeUtils::Kind mKind; + QByteArray mFormat; + ImageMetaInfoModel mImageMetaInfoModel; + QUndoStack mUndoStack; + QString mErrorString; + Cms::Profile::Ptr mCmsProfile; + /** @} */ + + void scheduleImageLoading(int invertedZoom); + void scheduleImageDownSampling(int invertedZoom); + void downSampleImage(int invertedZoom); +}; + + +class DownSamplingJob : public DocumentJob +{ + Q_OBJECT +public: + DownSamplingJob(int invertedZoom) + : mInvertedZoom(invertedZoom) + {} + + void doStart(); // reimp + + int mInvertedZoom; +}; + + +} // namespace + +#endif /* DOCUMENT_P_H */ diff --git a/gwenview/lib/document/documentfactory.cpp b/gwenview/lib/document/documentfactory.cpp new file mode 100644 index 00000000..5a6a6211 --- /dev/null +++ b/gwenview/lib/document/documentfactory.cpp @@ -0,0 +1,286 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "documentfactory.moc" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include + +// Local +#include + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +inline int getMaxUnreferencedImages() +{ + int defaultValue = 3; + QByteArray ba = qgetenv("GV_MAX_UNREFERENCED_IMAGES"); + if (ba.isEmpty()) { + return defaultValue; + } + kDebug() << "Custom value for max unreferenced images:" << ba; + bool ok; + int value = ba.toInt(&ok); + return ok ? value : defaultValue; +} + +static const int MAX_UNREFERENCED_IMAGES = getMaxUnreferencedImages(); + +/** + * This internal structure holds the document and the last time it has been + * accessed. This access time is used to "garbage collect" the loaded + * documents. + */ +struct DocumentInfo +{ + Document::Ptr mDocument; + QDateTime mLastAccess; +}; + +/** + * Our collection of DocumentInfo instances. We keep them as pointers to avoid + * altering DocumentInfo::mDocument refcount, since we rely on it to garbage + * collect documents. + */ +typedef QMap DocumentMap; + +struct DocumentFactoryPrivate +{ + DocumentMap mDocumentMap; + QUndoGroup mUndoGroup; + + /** + * Removes items in a map if they are no longer referenced elsewhere + */ + void garbageCollect(DocumentMap& map) + { + // Build a map of all unreferenced images. We use a MultiMap because in + // rare cases documents may get accessed at the same millisecond. + // See https://bugs.kde.org/show_bug.cgi?id=296401 + typedef QMultiMap UnreferencedImages; + UnreferencedImages unreferencedImages; + + DocumentMap::Iterator it = map.begin(), end = map.end(); + for (; it != end; ++it) { + DocumentInfo* info = it.value(); + if (info->mDocument.count() == 1 && !info->mDocument->isModified()) { + unreferencedImages.insert(info->mLastAccess, it.key()); + } + } + + // Remove oldest unreferenced images. Since the map is sorted by key, + // the oldest one is always unreferencedImages.begin(). + for ( + UnreferencedImages::Iterator unreferencedIt = unreferencedImages.begin(); + unreferencedImages.count() > MAX_UNREFERENCED_IMAGES; + unreferencedIt = unreferencedImages.erase(unreferencedIt)) + { + KUrl url = unreferencedIt.value(); + LOG("Collecting" << url); + it = map.find(url); + Q_ASSERT(it != map.end()); + delete it.value(); + map.erase(it); + } + +#ifdef ENABLE_LOG + logDocumentMap(map); +#endif + } + + void logDocumentMap(const DocumentMap& map) + { + kDebug() << "map:"; + DocumentMap::ConstIterator + it = map.constBegin(), + end = map.constEnd(); + for (; it != end; ++it) { + kDebug() << "-" << it.key() + << "refCount=" << it.value()->mDocument.count() + << "lastAccess=" << it.value()->mLastAccess; + } + } + + QList mModifiedDocumentList; +}; + +DocumentFactory::DocumentFactory() +: d(new DocumentFactoryPrivate) +{ +} + +DocumentFactory::~DocumentFactory() +{ + qDeleteAll(d->mDocumentMap); + delete d; +} + +DocumentFactory* DocumentFactory::instance() +{ + static DocumentFactory factory; + return &factory; +} + +Document::Ptr DocumentFactory::getCachedDocument(const KUrl& url) const +{ + const DocumentInfo* info = d->mDocumentMap.value(url); + return info ? info->mDocument : Document::Ptr(); +} + +Document::Ptr DocumentFactory::load(const KUrl& url) +{ + GV_RETURN_VALUE_IF_FAIL(!url.isEmpty(), Document::Ptr()); + DocumentInfo* info = 0; + + DocumentMap::Iterator it = d->mDocumentMap.find(url); + + if (it != d->mDocumentMap.end()) { + LOG(url.fileName() << "url in mDocumentMap"); + info = it.value(); + info->mLastAccess = QDateTime::currentDateTime(); + return info->mDocument; + } + + // At this point we couldn't find the document in the map + + // Start loading the document + LOG(url.fileName() << "loading"); + Document* doc = new Document(url); + connect(doc, SIGNAL(loaded(KUrl)), + SLOT(slotLoaded(KUrl))); + connect(doc, SIGNAL(saved(KUrl,KUrl)), + SLOT(slotSaved(KUrl,KUrl))); + connect(doc, SIGNAL(modified(KUrl)), + SLOT(slotModified(KUrl))); + connect(doc, SIGNAL(busyChanged(KUrl,bool)), + SLOT(slotBusyChanged(KUrl,bool))); + + // Create DocumentInfo instance + info = new DocumentInfo; + Document::Ptr docPtr(doc); + info->mDocument = docPtr; + info->mLastAccess = QDateTime::currentDateTime(); + + // Place DocumentInfo in the map + d->mDocumentMap[url] = info; + + d->garbageCollect(d->mDocumentMap); + + return docPtr; +} + +QList DocumentFactory::modifiedDocumentList() const +{ + return d->mModifiedDocumentList; +} + +bool DocumentFactory::hasUrl(const KUrl& url) const +{ + return d->mDocumentMap.contains(url); +} + +void DocumentFactory::clearCache() +{ + qDeleteAll(d->mDocumentMap); + d->mDocumentMap.clear(); + d->mModifiedDocumentList.clear(); +} + +void DocumentFactory::slotLoaded(const KUrl& url) +{ + if (d->mModifiedDocumentList.contains(url)) { + d->mModifiedDocumentList.removeAll(url); + emit modifiedDocumentListChanged(); + emit documentChanged(url); + } +} + +void DocumentFactory::slotSaved(const KUrl& oldUrl, const KUrl& newUrl) +{ + bool oldIsNew = oldUrl == newUrl; + bool oldUrlWasModified = d->mModifiedDocumentList.removeOne(oldUrl); + bool newUrlWasModified = false; + if (!oldIsNew) { + newUrlWasModified = d->mModifiedDocumentList.removeOne(newUrl); + DocumentInfo* info = d->mDocumentMap.take(oldUrl); + d->mDocumentMap.insert(newUrl, info); + } + d->garbageCollect(d->mDocumentMap); + if (oldUrlWasModified || newUrlWasModified) { + emit modifiedDocumentListChanged(); + } + if (oldUrlWasModified) { + emit documentChanged(oldUrl); + } + if (!oldIsNew) { + emit documentChanged(newUrl); + } +} + +void DocumentFactory::slotModified(const KUrl& url) +{ + if (!d->mModifiedDocumentList.contains(url)) { + d->mModifiedDocumentList << url; + emit modifiedDocumentListChanged(); + } + emit documentChanged(url); +} + +void DocumentFactory::slotBusyChanged(const KUrl& url, bool busy) +{ + emit documentBusyStateChanged(url, busy); +} + +QUndoGroup* DocumentFactory::undoGroup() +{ + return &d->mUndoGroup; +} + +void DocumentFactory::forget(const KUrl& url) +{ + DocumentInfo* info = d->mDocumentMap.take(url); + if (!info) { + return; + } + delete info; + + if (d->mModifiedDocumentList.contains(url)) { + d->mModifiedDocumentList.removeAll(url); + emit modifiedDocumentListChanged(); + } +} + +} // namespace diff --git a/gwenview/lib/document/documentfactory.h b/gwenview/lib/document/documentfactory.h new file mode 100644 index 00000000..4d88f8e0 --- /dev/null +++ b/gwenview/lib/document/documentfactory.h @@ -0,0 +1,96 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENTFACTORY_H +#define DOCUMENTFACTORY_H + +// Qt +#include + +#include + +class QUndoGroup; + +class KUrl; + +namespace Gwenview +{ + +struct DocumentFactoryPrivate; + +/** + * This class holds all instances of Document. + * + * It keeps a cache of recently accessed documents to avoid reloading them. + * To do so it keeps a last-access timestamp, which is updated to the + * current time every time DocumentFactory::load() is called. + */ +class GWENVIEWLIB_EXPORT DocumentFactory : public QObject +{ + Q_OBJECT +public: + static DocumentFactory* instance(); + ~DocumentFactory(); + + /** + * Loads the document associated with url, or returns an already cached + * instance of Document::Ptr if there is any. + * This method updates the last-access timestamp. + */ + Document::Ptr load(const KUrl& url); + + /** + * Returns a document if it has already been loaded once with load(). + * This method does not update the last-access timestamp. + */ + Document::Ptr getCachedDocument(const KUrl&) const; + + QList modifiedDocumentList() const; + + bool hasUrl(const KUrl&) const; + + void clearCache(); + + QUndoGroup* undoGroup(); + + /** + * Do not keep document whose url is @url in cache even if it has been + * modified + */ + void forget(const KUrl& url); + +Q_SIGNALS: + void modifiedDocumentListChanged(); + void documentChanged(const KUrl&); + void documentBusyStateChanged(const KUrl&, bool); + +private Q_SLOTS: + void slotLoaded(const KUrl&); + void slotSaved(const KUrl&, const KUrl&); + void slotModified(const KUrl&); + void slotBusyChanged(const KUrl&, bool); + +private: + DocumentFactory(); + + DocumentFactoryPrivate* const d; +}; + +} // namespace +#endif /* DOCUMENTFACTORY_H */ diff --git a/gwenview/lib/document/documentjob.cpp b/gwenview/lib/document/documentjob.cpp new file mode 100644 index 00000000..bbcefa41 --- /dev/null +++ b/gwenview/lib/document/documentjob.cpp @@ -0,0 +1,95 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "documentjob.h" + +// Qt +#include +#include +#include + +// KDE +#include +#include +#include +#include + +// Local + +namespace Gwenview +{ + +struct DocumentJobPrivate +{ + Document::Ptr mDoc; +}; + +DocumentJob::DocumentJob() +: KCompositeJob(0) +, d(new DocumentJobPrivate) +{ + KDialogJobUiDelegate* delegate = new KDialogJobUiDelegate; + delegate->setWindow(KApplication::kApplication()->activeWindow()); + delegate->setAutoErrorHandlingEnabled(true); + setUiDelegate(delegate); +} + +DocumentJob::~DocumentJob() +{ + delete d; +} + +Document::Ptr DocumentJob::document() const +{ + return d->mDoc; +} + +void DocumentJob::setDocument(const Document::Ptr& doc) +{ + d->mDoc = doc; +} + +void DocumentJob::start() +{ + QMetaObject::invokeMethod(this, "doStart", Qt::QueuedConnection); +} + +bool DocumentJob::checkDocumentEditor() +{ + if (!document()->editor()) { + setError(NoDocumentEditorError); + setErrorText(i18nc("@info", "Gwenview cannot edit this kind of image.")); + return false; + } + return true; +} + +void ThreadedDocumentJob::doStart() +{ + QFuture future = QtConcurrent::run(this, &ThreadedDocumentJob::threadedStart); + QFutureWatcher* watcher = new QFutureWatcher(this); + connect(watcher, SIGNAL(finished()), SLOT(emitResult())); + watcher->setFuture(future); +} + +} // namespace + +#include diff --git a/gwenview/lib/document/documentjob.h b/gwenview/lib/document/documentjob.h new file mode 100644 index 00000000..ddfd93be --- /dev/null +++ b/gwenview/lib/document/documentjob.h @@ -0,0 +1,116 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENTJOB_H +#define DOCUMENTJOB_H + +#include + +// Qt + +// KDE +#include + +// Local +#include + +namespace Gwenview +{ + +struct DocumentJobPrivate; + +/** + * Represent an asynchronous task to be executed on a document + * The task must be created on the heap and passed to an instance of Document + * via Document::enqueueJob() + * + * The task behavior must be implemented in run() + * + * Tasks are always started from the GUI thread, and are never parallelized. + * You can of course use threading inside your task implementation to speed it + * up. + */ +class GWENVIEWLIB_EXPORT DocumentJob : public KCompositeJob +{ + Q_OBJECT +public: + enum { + NoDocumentEditorError = UserDefinedError + 1 + }; + DocumentJob(); + ~DocumentJob(); + + Document::Ptr document() const; + + virtual void start(); + +protected Q_SLOTS: + /** + * Implement this method to provide the task behavior. + * You *must* emit the result() signal when your work is finished, but it + * does not have to be finished when run() returns. + * If you are not emitting it from the GUI thread, then use a queued + * connection to emit it. + */ + virtual void doStart() = 0; + + /** + * slot-ification of emitResult() + */ + void emitResult() + { + KJob::emitResult(); + } + +protected: + /** + * Convenience method which checks document()->editor() is valid + * and set the job error properties if it's not. + * @return true if the editor is valid. + */ + bool checkDocumentEditor(); + +private: + void setDocument(const Document::Ptr&); + + DocumentJobPrivate* const d; + + friend class Document; +}; + +/** + * A document job whose action is started in a separate thread + */ +class ThreadedDocumentJob : public DocumentJob +{ +public: + /** + * Must be reimplemented to apply the action to the document. + * This method is never called from the GUI thread. + */ + virtual void threadedStart() = 0; + +protected: + virtual void doStart(); +}; + +} // namespace + +#endif /* DOCUMENTJOB_H */ diff --git a/gwenview/lib/document/documentloadedimpl.cpp b/gwenview/lib/document/documentloadedimpl.cpp new file mode 100644 index 00000000..5596656e --- /dev/null +++ b/gwenview/lib/document/documentloadedimpl.cpp @@ -0,0 +1,124 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "documentloadedimpl.h" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include + +// Local +#include "documentjob.h" +#include "imageutils.h" +#include "savejob.h" + +namespace Gwenview +{ + +struct DocumentLoadedImplPrivate +{ + QByteArray mRawData; + bool mQuietInit; +}; + +DocumentLoadedImpl::DocumentLoadedImpl(Document* document, const QByteArray& rawData, bool quietInit) +: AbstractDocumentImpl(document) +, d(new DocumentLoadedImplPrivate) +{ + if (document->keepRawData()) { + d->mRawData = rawData; + } + d->mQuietInit = quietInit; +} + +DocumentLoadedImpl::~DocumentLoadedImpl() +{ + delete d; +} + +void DocumentLoadedImpl::init() +{ + if (!d->mQuietInit) { + emit imageRectUpdated(document()->image().rect()); + emit loaded(); + } +} + +bool DocumentLoadedImpl::isEditable() const +{ + return true; +} + +Document::LoadingState DocumentLoadedImpl::loadingState() const +{ + return Document::Loaded; +} + +bool DocumentLoadedImpl::saveInternal(QIODevice* device, const QByteArray& format) +{ + QImageWriter writer(device, format); + bool ok = writer.write(document()->image()); + if (ok) { + setDocumentFormat(format); + } else { + setDocumentErrorString(writer.errorString()); + } + return ok; +} + +DocumentJob* DocumentLoadedImpl::save(const KUrl& url, const QByteArray& format) +{ + return new SaveJob(this, url, format); +} + +AbstractDocumentEditor* DocumentLoadedImpl::editor() +{ + return this; +} + +void DocumentLoadedImpl::setImage(const QImage& image) +{ + setDocumentImage(image); + imageRectUpdated(image.rect()); +} + +void DocumentLoadedImpl::applyTransformation(Orientation orientation) +{ + QImage image = document()->image(); + QMatrix matrix = ImageUtils::transformMatrix(orientation); + image = image.transformed(matrix); + setDocumentImage(image); + imageRectUpdated(image.rect()); +} + +QByteArray DocumentLoadedImpl::rawData() const +{ + return d->mRawData; +} + +} // namespace diff --git a/gwenview/lib/document/documentloadedimpl.h b/gwenview/lib/document/documentloadedimpl.h new file mode 100644 index 00000000..3259b8a9 --- /dev/null +++ b/gwenview/lib/document/documentloadedimpl.h @@ -0,0 +1,76 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENTLOADEDIMPL_H +#define DOCUMENTLOADEDIMPL_H + +// Qt + +// KDE + +// Local +#include +#include + +class QByteArray; +class QIODevice; + +class KUrl; + +namespace Gwenview +{ + +struct DocumentLoadedImplPrivate; +class DocumentLoadedImpl : public AbstractDocumentImpl, protected AbstractDocumentEditor +{ + Q_OBJECT +public: + /** + * @param quietInit set to true if init() should not emit any signal + */ + DocumentLoadedImpl(Document*, const QByteArray&, bool quietInit = false); + ~DocumentLoadedImpl(); + + // AbstractDocumentImpl + virtual void init(); + virtual Document::LoadingState loadingState() const; + virtual DocumentJob* save(const KUrl&, const QByteArray& format); + virtual AbstractDocumentEditor* editor(); + virtual QByteArray rawData() const; + virtual bool isEditable() const; + // + +protected: + virtual bool saveInternal(QIODevice* device, const QByteArray& format); + + // AbstractDocumentEditor + virtual void setImage(const QImage&); + virtual void applyTransformation(Orientation orientation); + // + +private: + DocumentLoadedImplPrivate* const d; + + friend class SaveJob; +}; + +} // namespace + +#endif /* DOCUMENTLOADEDIMPL_H */ diff --git a/gwenview/lib/document/emptydocumentimpl.cpp b/gwenview/lib/document/emptydocumentimpl.cpp new file mode 100644 index 00000000..cf3c9fd6 --- /dev/null +++ b/gwenview/lib/document/emptydocumentimpl.cpp @@ -0,0 +1,49 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "emptydocumentimpl.h" + +// Qt + +// KDE + +// Local + +namespace Gwenview +{ + +EmptyDocumentImpl::EmptyDocumentImpl(Document* document) +: AbstractDocumentImpl(document) +{} + +EmptyDocumentImpl::~EmptyDocumentImpl() +{} + +void EmptyDocumentImpl::init() +{ +} + +Document::LoadingState EmptyDocumentImpl::loadingState() const +{ + return Document::LoadingFailed; +} + +} // namespace diff --git a/gwenview/lib/document/emptydocumentimpl.h b/gwenview/lib/document/emptydocumentimpl.h new file mode 100644 index 00000000..bfed0049 --- /dev/null +++ b/gwenview/lib/document/emptydocumentimpl.h @@ -0,0 +1,46 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef EMPTYDOCUMENTIMPL_H +#define EMPTYDOCUMENTIMPL_H + +// Qt + +// KDE + +// Local +#include + +namespace Gwenview +{ + +class EmptyDocumentImpl : public AbstractDocumentImpl +{ +public: + EmptyDocumentImpl(Document*); + ~EmptyDocumentImpl(); + + virtual void init(); + virtual Document::LoadingState loadingState() const; +}; + +} // namespace + +#endif /* EMPTYDOCUMENTIMPL_H */ diff --git a/gwenview/lib/document/jpegdocumentloadedimpl.cpp b/gwenview/lib/document/jpegdocumentloadedimpl.cpp new file mode 100644 index 00000000..7fbacd31 --- /dev/null +++ b/gwenview/lib/document/jpegdocumentloadedimpl.cpp @@ -0,0 +1,91 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "jpegdocumentloadedimpl.h" + +// Qt +#include +#include + +// KDE + +// Local +#include "jpegcontent.h" + +namespace Gwenview +{ + +struct JpegDocumentLoadedImplPrivate +{ + JpegContent* mJpegContent; +}; + +JpegDocumentLoadedImpl::JpegDocumentLoadedImpl(Document* doc, JpegContent* jpegContent) +: DocumentLoadedImpl(doc, QByteArray() /* rawData */) +, d(new JpegDocumentLoadedImplPrivate) +{ + Q_ASSERT(jpegContent); + d->mJpegContent = jpegContent; +} + +JpegDocumentLoadedImpl::~JpegDocumentLoadedImpl() +{ + delete d->mJpegContent; + delete d; +} + +bool JpegDocumentLoadedImpl::saveInternal(QIODevice* device, const QByteArray& format) +{ + if (format == "jpeg") { + d->mJpegContent->resetOrientation(); + if (!d->mJpegContent->thumbnail().isNull()) { + QImage thumbnail = document()->image().scaled(128, 128, Qt::KeepAspectRatio); + d->mJpegContent->setThumbnail(thumbnail); + } + + bool ok = d->mJpegContent->save(device); + if (!ok) { + setDocumentErrorString(d->mJpegContent->errorString()); + } + return ok; + } else { + return DocumentLoadedImpl::saveInternal(device, format); + } +} + +void JpegDocumentLoadedImpl::setImage(const QImage& image) +{ + d->mJpegContent->setImage(image); + DocumentLoadedImpl::setImage(image); +} + +void JpegDocumentLoadedImpl::applyTransformation(Orientation orientation) +{ + DocumentLoadedImpl::applyTransformation(orientation); + d->mJpegContent->transform(orientation); +} + +QByteArray JpegDocumentLoadedImpl::rawData() const +{ + return d->mJpegContent->rawData(); +} + +} // namespace diff --git a/gwenview/lib/document/jpegdocumentloadedimpl.h b/gwenview/lib/document/jpegdocumentloadedimpl.h new file mode 100644 index 00000000..caedaaa6 --- /dev/null +++ b/gwenview/lib/document/jpegdocumentloadedimpl.h @@ -0,0 +1,61 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef JPEGDOCUMENTLOADEDIMPL_H +#define JPEGDOCUMENTLOADEDIMPL_H + +// Qt + +// KDE + +// Local +#include + +class QByteArray; + +namespace Gwenview +{ + +class JpegContent; + +struct JpegDocumentLoadedImplPrivate; +class JpegDocumentLoadedImpl : public DocumentLoadedImpl +{ + Q_OBJECT +public: + JpegDocumentLoadedImpl(Document*, JpegContent*); + ~JpegDocumentLoadedImpl(); + virtual QByteArray rawData() const; + +protected: + virtual bool saveInternal(QIODevice* device, const QByteArray& format); + + // AbstractDocumentEditor + virtual void setImage(const QImage&); + virtual void applyTransformation(Orientation orientation); + // + +private: + JpegDocumentLoadedImplPrivate* const d; +}; + +} // namespace + +#endif /* JPEGDOCUMENTLOADEDIMPL_H */ diff --git a/gwenview/lib/document/loadingdocumentimpl.cpp b/gwenview/lib/document/loadingdocumentimpl.cpp new file mode 100644 index 00000000..16ead28b --- /dev/null +++ b/gwenview/lib/document/loadingdocumentimpl.cpp @@ -0,0 +1,547 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "loadingdocumentimpl.moc" + +// STL +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "animateddocumentloadedimpl.h" +#include "cms/cmsprofile.h" +#include "document.h" +#include "documentloadedimpl.h" +#include "emptydocumentimpl.h" +#include "exiv2imageloader.h" +#include "gvdebug.h" +#include "imageutils.h" +#include "jpegcontent.h" +#include "jpegdocumentloadedimpl.h" +#include "orientation.h" +#include "svgdocumentloadedimpl.h" +#include "urlutils.h" +#include "videodocumentloadedimpl.h" +#include "gwenviewconfig.h" + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +const int MIN_PREV_SIZE = 1000; + +const int HEADER_SIZE = 256; + +struct LoadingDocumentImplPrivate +{ + LoadingDocumentImpl* q; + QPointer mTransferJob; + QFuture mMetaInfoFuture; + QFutureWatcher mMetaInfoFutureWatcher; + QFuture mImageDataFuture; + QFutureWatcher mImageDataFutureWatcher; + + // If != 0, this means we need to load an image at zoom = + // 1/mImageDataInvertedZoom + int mImageDataInvertedZoom; + + bool mMetaInfoLoaded; + bool mAnimated; + bool mDownSampledImageLoaded; + QByteArray mFormatHint; + QByteArray mData; + QByteArray mFormat; + QSize mImageSize; + Exiv2::Image::AutoPtr mExiv2Image; + std::auto_ptr mJpegContent; + QImage mImage; + Cms::Profile::Ptr mCmsProfile; + + /** + * Determine kind of document and switch to an implementation if it is not + * necessary to download more data. + * @return true if switched to another implementation. + */ + bool determineKind() + { + QString mimeType; + const KUrl& url = q->document()->url(); + if (KProtocolInfo::determineMimetypeFromExtension(url.protocol())) { + mimeType = KMimeType::findByNameAndContent(url.fileName(), mData)->name(); + } else { + mimeType = KMimeType::findByContent(mData)->name(); + } + MimeTypeUtils::Kind kind = MimeTypeUtils::mimeTypeKind(mimeType); + LOG("mimeType:" << mimeType); + LOG("kind:" << kind); + q->setDocumentKind(kind); + + switch (kind) { + case MimeTypeUtils::KIND_RASTER_IMAGE: + case MimeTypeUtils::KIND_SVG_IMAGE: + return false; + + case MimeTypeUtils::KIND_VIDEO: + q->switchToImpl(new VideoDocumentLoadedImpl(q->document())); + return true; + + default: + q->setDocumentErrorString( + i18nc("@info", "Gwenview cannot display documents of type %1.", mimeType) + ); + emit q->loadingFailed(); + q->switchToImpl(new EmptyDocumentImpl(q->document())); + return true; + } + } + + void startLoading() + { + Q_ASSERT(!mMetaInfoLoaded); + + switch (q->document()->kind()) { + case MimeTypeUtils::KIND_RASTER_IMAGE: + // The hint is used to: + // - Speed up loadMetaInfo(): QImageReader will try to decode the + // image using plugins matching this format first. + // - Avoid breakage: Because of a bug in Qt TGA image plugin, some + // PNG were incorrectly identified as PCX! See: + // https://bugs.kde.org/show_bug.cgi?id=289819 + // + mFormatHint = q->document()->url().fileName() + .section('.', -1).toAscii().toLower(); + mMetaInfoFuture = QtConcurrent::run(this, &LoadingDocumentImplPrivate::loadMetaInfo); + mMetaInfoFutureWatcher.setFuture(mMetaInfoFuture); + break; + + case MimeTypeUtils::KIND_SVG_IMAGE: + q->switchToImpl(new SvgDocumentLoadedImpl(q->document(), mData)); + break; + + case MimeTypeUtils::KIND_VIDEO: + break; + + default: + kWarning() << "We should not reach this point!"; + break; + } + } + + void startImageDataLoading() + { + LOG(""); + Q_ASSERT(mMetaInfoLoaded); + Q_ASSERT(mImageDataInvertedZoom != 0); + Q_ASSERT(!mImageDataFuture.isRunning()); + mImageDataFuture = QtConcurrent::run(this, &LoadingDocumentImplPrivate::loadImageData); + mImageDataFutureWatcher.setFuture(mImageDataFuture); + } + + bool loadMetaInfo() + { + LOG("mFormatHint" << mFormatHint); + QBuffer buffer; + buffer.setBuffer(&mData); + buffer.open(QIODevice::ReadOnly); + + if (KDcrawIface::KDcraw::rawFilesList().contains(QString(mFormatHint))) { + QByteArray previewData; + + // if the image is in format supported by dcraw, fetch its embedded preview + mJpegContent.reset(new JpegContent()); + + // use KDcraw for getting the embedded preview + // KDcraw functionality cloned locally (temp. solution) + bool ret = KDcrawIface::KDcraw::loadEmbeddedPreview(previewData, buffer); + + QImage originalImage; + if (!ret || !originalImage.loadFromData(previewData) || qMin(originalImage.width(), originalImage.height()) < MIN_PREV_SIZE) { + // if the embedded preview loading failed or gets just a small image, load + // half preview instead. That's slower but it works even for images containing + // small (160x120px) or none embedded preview. + if (!KDcrawIface::KDcraw::loadHalfPreview(previewData, buffer)) { + kWarning() << "unable to get half preview for " << q->document()->url().fileName(); + return false; + } + } + + buffer.close(); + + // now it's safe to replace mData with the jpeg data + mData = previewData; + + // need to fill mFormat so gwenview can tell the type when trying to save + mFormat = mFormatHint; + } else { + QImageReader reader(&buffer, mFormatHint); + mImageSize = reader.size(); + + if (!reader.canRead()) { + kWarning() << "QImageReader::read() using format hint" << mFormatHint << "failed:" << reader.errorString(); + if (buffer.pos() != 0) { + kWarning() << "A bad Qt image decoder moved the buffer to" << buffer.pos() << "in a call to canRead()! Rewinding."; + buffer.seek(0); + } + reader.setFormat(QByteArray()); + // Set buffer again, otherwise QImageReader won't restart from scratch + reader.setDevice(&buffer); + if (!reader.canRead()) { + kWarning() << "QImageReader::read() without format hint failed:" << reader.errorString(); + return false; + } + kWarning() << "Image format is actually" << reader.format() << "not" << mFormatHint; + } + + mFormat = reader.format(); + + if (mFormat == "jpg") { + // if mFormatHint was "jpg", then mFormat is "jpg", but the rest of + // Gwenview code assumes JPEG images have "jpeg" format. + mFormat = "jpeg"; + } + } + + LOG("mFormat" << mFormat); + GV_RETURN_VALUE_IF_FAIL(!mFormat.isEmpty(), false); + + Exiv2ImageLoader loader; + if (loader.load(mData)) { + mExiv2Image = loader.popImage(); + } + + if (mFormat == "jpeg" && mExiv2Image.get()) { + mJpegContent.reset(new JpegContent()); + } + + if (mJpegContent.get()) { + if (!mJpegContent->loadFromData(mData, mExiv2Image.get()) && + !mJpegContent->loadFromData(mData)) { + kWarning() << "Unable to use preview of " << q->document()->url().fileName(); + return false; + } + // Use the size from JpegContent, as its correctly transposed if the + // image has been rotated + mImageSize = mJpegContent->size(); + + mCmsProfile = Cms::Profile::loadFromExiv2Image(mExiv2Image.get()); + + } + + LOG("mImageSize" << mImageSize); + + if (!mCmsProfile) { + mCmsProfile = Cms::Profile::loadFromImageData(mData, mFormat); + } + + return true; + } + + void loadImageData() + { + QBuffer buffer; + buffer.setBuffer(&mData); + buffer.open(QIODevice::ReadOnly); + QImageReader reader(&buffer, mFormat); + + LOG("mImageDataInvertedZoom=" << mImageDataInvertedZoom); + if (mImageSize.isValid() + && mImageDataInvertedZoom != 1 + && reader.supportsOption(QImageIOHandler::ScaledSize) + ) { + // Do not use mImageSize here: QImageReader needs a non-transposed + // image size + QSize size = reader.size() / mImageDataInvertedZoom; + if (!size.isEmpty()) { + LOG("Setting scaled size to" << size); + reader.setScaledSize(size); + } else { + LOG("Not setting scaled size as it is empty" << size); + } + } + + bool ok = reader.read(&mImage); + if (!ok) { + LOG("QImageReader::read() failed"); + return; + } + + if (mJpegContent.get() && GwenviewConfig::applyExifOrientation()) { + Gwenview::Orientation orientation = mJpegContent->orientation(); + QMatrix matrix = ImageUtils::transformMatrix(orientation); + mImage = mImage.transformed(matrix); + } + + if (reader.supportsAnimation() + && reader.nextImageDelay() > 0 // Assume delay == 0 <=> only one frame + ) { + /* + * QImageReader is not really helpful to detect animated gif: + * - QImageReader::imageCount() returns 0 + * - QImageReader::nextImageDelay() may return something > 0 if the + * image consists of only one frame but includes a "Graphic + * Control Extension" (usually only present if we have an + * animation) (Bug #185523) + * + * Decoding the next frame is the only reliable way I found to + * detect an animated gif + */ + LOG("May be an animated image. delay:" << reader.nextImageDelay()); + QImage nextImage; + if (reader.read(&nextImage)) { + LOG("Really an animated image (more than one frame)"); + mAnimated = true; + } else { + kWarning() << q->document()->url() << "is not really an animated image (only one frame)"; + } + } + } +}; + +LoadingDocumentImpl::LoadingDocumentImpl(Document* document) +: AbstractDocumentImpl(document) +, d(new LoadingDocumentImplPrivate) +{ + d->q = this; + d->mMetaInfoLoaded = false; + d->mAnimated = false; + d->mDownSampledImageLoaded = false; + d->mImageDataInvertedZoom = 0; + + connect(&d->mMetaInfoFutureWatcher, SIGNAL(finished()), + SLOT(slotMetaInfoLoaded())); + + connect(&d->mImageDataFutureWatcher, SIGNAL(finished()), + SLOT(slotImageLoaded())); +} + +LoadingDocumentImpl::~LoadingDocumentImpl() +{ + LOG(""); + // Disconnect watchers to make sure they do not trigger further work + d->mMetaInfoFutureWatcher.disconnect(); + d->mImageDataFutureWatcher.disconnect(); + + d->mMetaInfoFutureWatcher.waitForFinished(); + d->mImageDataFutureWatcher.waitForFinished(); + + if (d->mTransferJob) { + d->mTransferJob->kill(); + } + delete d; +} + +void LoadingDocumentImpl::init() +{ + KUrl url = document()->url(); + + if (UrlUtils::urlIsFastLocalFile(url)) { + // Load file content directly + QFile file(url.toLocalFile()); + if (!file.open(QIODevice::ReadOnly)) { + setDocumentErrorString(i18nc("@info", "Could not open file %1", url.toLocalFile())); + emit loadingFailed(); + switchToImpl(new EmptyDocumentImpl(document())); + return; + } + d->mData = file.read(HEADER_SIZE); + if (d->determineKind()) { + return; + } + d->mData += file.readAll(); + d->startLoading(); + } else { + // Transfer file via KIO + d->mTransferJob = KIO::get(document()->url()); + connect(d->mTransferJob, SIGNAL(data(KIO::Job*,QByteArray)), + SLOT(slotDataReceived(KIO::Job*,QByteArray))); + connect(d->mTransferJob, SIGNAL(result(KJob*)), + SLOT(slotTransferFinished(KJob*))); + d->mTransferJob->start(); + } +} + +void LoadingDocumentImpl::loadImage(int invertedZoom) +{ + if (d->mImageDataInvertedZoom == invertedZoom) { + LOG("Already loading an image at invertedZoom=" << invertedZoom); + return; + } + if (d->mImageDataInvertedZoom == 1) { + LOG("Ignoring request: we are loading a full image"); + return; + } + d->mImageDataFutureWatcher.waitForFinished(); + d->mImageDataInvertedZoom = invertedZoom; + + if (d->mMetaInfoLoaded) { + // Do not test on mMetaInfoFuture.isRunning() here: it might not have + // started if we are downloading the image from a remote url + d->startImageDataLoading(); + } +} + +void LoadingDocumentImpl::slotDataReceived(KIO::Job* job, const QByteArray& chunk) +{ + d->mData.append(chunk); + if (document()->kind() == MimeTypeUtils::KIND_UNKNOWN && d->mData.length() >= HEADER_SIZE) { + if (d->determineKind()) { + job->kill(); + return; + } + } +} + +void LoadingDocumentImpl::slotTransferFinished(KJob* job) +{ + if (job->error()) { + setDocumentErrorString(job->errorString()); + emit loadingFailed(); + switchToImpl(new EmptyDocumentImpl(document())); + return; + } + d->startLoading(); +} + +bool LoadingDocumentImpl::isEditable() const +{ + return d->mDownSampledImageLoaded; +} + +Document::LoadingState LoadingDocumentImpl::loadingState() const +{ + if (!document()->image().isNull()) { + return Document::Loaded; + } else if (d->mMetaInfoLoaded) { + return Document::MetaInfoLoaded; + } else if (document()->kind() != MimeTypeUtils::KIND_UNKNOWN) { + return Document::KindDetermined; + } else { + return Document::Loading; + } +} + +void LoadingDocumentImpl::slotMetaInfoLoaded() +{ + LOG(""); + Q_ASSERT(!d->mMetaInfoFuture.isRunning()); + if (!d->mMetaInfoFuture.result()) { + setDocumentErrorString( + i18nc("@info", "Loading meta information failed.") + ); + emit loadingFailed(); + switchToImpl(new EmptyDocumentImpl(document())); + return; + } + + setDocumentFormat(d->mFormat); + setDocumentImageSize(d->mImageSize); + setDocumentExiv2Image(d->mExiv2Image); + setDocumentCmsProfile(d->mCmsProfile); + + d->mMetaInfoLoaded = true; + emit metaInfoLoaded(); + + // Start image loading if necessary + // We test if mImageDataFuture is not already running because code connected to + // metaInfoLoaded() signal could have called loadImage() + if (!d->mImageDataFuture.isRunning() && d->mImageDataInvertedZoom != 0) { + d->startImageDataLoading(); + } +} + +void LoadingDocumentImpl::slotImageLoaded() +{ + LOG(""); + if (d->mImage.isNull()) { + setDocumentErrorString( + i18nc("@info", "Loading image failed.") + ); + emit loadingFailed(); + switchToImpl(new EmptyDocumentImpl(document())); + return; + } + + if (d->mAnimated) { + if (d->mImage.size() == d->mImageSize) { + // We already decoded the first frame at the right size, let's show + // it + setDocumentImage(d->mImage); + } + + switchToImpl(new AnimatedDocumentLoadedImpl( + document(), + d->mData)); + + return; + } + + if (d->mImageDataInvertedZoom != 1 && d->mImage.size() != d->mImageSize) { + LOG("Loaded a down sampled image"); + d->mDownSampledImageLoaded = true; + // We loaded a down sampled image + setDocumentDownSampledImage(d->mImage, d->mImageDataInvertedZoom); + return; + } + + LOG("Loaded a full image"); + setDocumentImage(d->mImage); + DocumentLoadedImpl* impl; + if (d->mJpegContent.get()) { + impl = new JpegDocumentLoadedImpl( + document(), + d->mJpegContent.release()); + } else { + impl = new DocumentLoadedImpl( + document(), + d->mData); + } + switchToImpl(impl); +} + +} // namespace diff --git a/gwenview/lib/document/loadingdocumentimpl.h b/gwenview/lib/document/loadingdocumentimpl.h new file mode 100644 index 00000000..0523524f --- /dev/null +++ b/gwenview/lib/document/loadingdocumentimpl.h @@ -0,0 +1,67 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef LOADINGDOCUMENTIMPL_H +#define LOADINGDOCUMENTIMPL_H + +// Qt + +// KDE + +// Local +#include + +class KJob; +namespace KIO +{ +class Job; +} + +namespace Gwenview +{ + +struct LoadingDocumentImplPrivate; +class LoadingDocumentImpl : public AbstractDocumentImpl +{ + Q_OBJECT +public: + LoadingDocumentImpl(Document*); + ~LoadingDocumentImpl(); + + virtual void init(); + virtual Document::LoadingState loadingState() const; + virtual bool isEditable() const; + + void loadImage(int invertedZoom); + +private Q_SLOTS: + void slotMetaInfoLoaded(); + void slotImageLoaded(); + void slotDataReceived(KIO::Job*, const QByteArray&); + void slotTransferFinished(KJob*); + +private: + LoadingDocumentImplPrivate* const d; + friend struct LoadingDocumentImplPrivate; +}; + +} // namespace + +#endif /* LOADINGDOCUMENTIMPL_H */ diff --git a/gwenview/lib/document/loadingjob.cpp b/gwenview/lib/document/loadingjob.cpp new file mode 100644 index 00000000..f303253a --- /dev/null +++ b/gwenview/lib/document/loadingjob.cpp @@ -0,0 +1,61 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "loadingjob.moc" + +// Qt + +// KDE +#include +#include +#include + +// Local + +namespace Gwenview +{ + +void LoadingJob::doStart() +{ + Document::LoadingState state = document()->loadingState(); + if (state == Document::Loaded || state == Document::LoadingFailed) { + setError(NoError); + emitResult(); + } else { + connect(document().data(), SIGNAL(loaded(KUrl)), SLOT(slotLoaded())); + connect(document().data(), SIGNAL(loadingFailed(KUrl)), SLOT(slotLoadingFailed())); + } +} + +void LoadingJob::slotLoaded() +{ + setError(NoError); + emitResult(); +} + +void LoadingJob::slotLoadingFailed() +{ + setError(UserDefinedError + 1); + setErrorText(i18n("Could not load document %1", document()->url().pathOrUrl())); + emitResult(); +} + +} // namespace diff --git a/gwenview/lib/document/loadingjob.h b/gwenview/lib/document/loadingjob.h new file mode 100644 index 00000000..86acf30e --- /dev/null +++ b/gwenview/lib/document/loadingjob.h @@ -0,0 +1,47 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef LOADINGJOB_H +#define LOADINGJOB_H + +// Qt + +// KDE + +// Local +#include + +namespace Gwenview +{ + +class LoadingJob : public DocumentJob +{ + Q_OBJECT +protected: + void doStart(); + +private Q_SLOTS: + void slotLoaded(); + void slotLoadingFailed(); +}; + +} // namespace + +#endif /* LOADINGJOB_H */ diff --git a/gwenview/lib/document/savejob.cpp b/gwenview/lib/document/savejob.cpp new file mode 100644 index 00000000..1c281182 --- /dev/null +++ b/gwenview/lib/document/savejob.cpp @@ -0,0 +1,170 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "savejob.h" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "documentloadedimpl.h" + +namespace Gwenview +{ + +struct SaveJobPrivate +{ + DocumentLoadedImpl* mImpl; + KUrl mOldUrl; + KUrl mNewUrl; + QByteArray mFormat; + QScopedPointer mTemporaryFile; + QScopedPointer mSaveFile; + QScopedPointer > mInternalSaveWatcher; + + bool mKillReceived; +}; + +SaveJob::SaveJob(DocumentLoadedImpl* impl, const KUrl& url, const QByteArray& format) +: d(new SaveJobPrivate) +{ + d->mImpl = impl; + d->mOldUrl = impl->document()->url(); + d->mNewUrl = url; + d->mFormat = format; + d->mKillReceived = false; + setCapabilities(Killable); +} + +SaveJob::~SaveJob() +{ + delete d; +} + +void SaveJob::saveInternal() +{ + if (!d->mImpl->saveInternal(d->mSaveFile.data(), d->mFormat)) { + d->mSaveFile->abort(); + setError(UserDefinedError + 2); + setErrorText(d->mImpl->document()->errorString()); + } +} + +void SaveJob::doStart() +{ + if (d->mKillReceived) { + return; + } + QString fileName; + + if (d->mNewUrl.isLocalFile()) { + fileName = d->mNewUrl.toLocalFile(); + } else { + d->mTemporaryFile.reset(new KTemporaryFile); + d->mTemporaryFile->setAutoRemove(true); + d->mTemporaryFile->open(); + fileName = d->mTemporaryFile->fileName(); + } + + d->mSaveFile.reset(new KSaveFile(fileName)); + + if (!d->mSaveFile->open()) { + KUrl dirUrl = d->mNewUrl; + dirUrl.setFileName(QString()); + setError(UserDefinedError + 1); + setErrorText(i18nc("@info", "Could not open file for writing, check that you have the necessary rights in %1.", dirUrl.pathOrUrl())); + emitResult(); + return; + } + + QFuture future = QtConcurrent::run(this, &SaveJob::saveInternal); + d->mInternalSaveWatcher.reset(new QFutureWatcher(this)); + connect(d->mInternalSaveWatcher.data(), SIGNAL(finished()), SLOT(finishSave())); + d->mInternalSaveWatcher->setFuture(future); +} + +void SaveJob::finishSave() +{ + d->mInternalSaveWatcher.reset(0); + if (d->mKillReceived) { + return; + } + + if (error()) { + emitResult(); + return; + } + + if (!d->mSaveFile->finalize()) { + setErrorText(i18nc("@info", "Could not overwrite file, check that you have the necessary rights to write in %1.", d->mNewUrl.pathOrUrl())); + setError(UserDefinedError + 3); + return; + } + + if (d->mNewUrl.isLocalFile()) { + emitResult(); + } else { + KIO::Job* job = KIO::copy(KUrl::fromPath(d->mTemporaryFile->fileName()), d->mNewUrl); + job->ui()->setWindow(KApplication::kApplication()->activeWindow()); + addSubjob(job); + } +} + +void SaveJob::slotResult(KJob* job) +{ + DocumentJob::slotResult(job); + if (!error()) { + emitResult(); + } +} + +KUrl SaveJob::oldUrl() const +{ + return d->mOldUrl; +} + +KUrl SaveJob::newUrl() const +{ + return d->mNewUrl; +} + +bool SaveJob::doKill() +{ + d->mKillReceived = true; + if (d->mInternalSaveWatcher) { + d->mInternalSaveWatcher->waitForFinished(); + } + return true; +} + +} // namespace diff --git a/gwenview/lib/document/savejob.h b/gwenview/lib/document/savejob.h new file mode 100644 index 00000000..5ed86cbb --- /dev/null +++ b/gwenview/lib/document/savejob.h @@ -0,0 +1,69 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef SAVEJOB_H +#define SAVEJOB_H + +#include + +// Qt + +// KDE + +// Local +#include + +class QByteArray; +class KUrl; + +namespace Gwenview +{ + +class DocumentLoadedImpl; + +struct SaveJobPrivate; +class GWENVIEWLIB_EXPORT SaveJob : public DocumentJob +{ + Q_OBJECT +public: + SaveJob(DocumentLoadedImpl* impl, const KUrl& url, const QByteArray& format); + ~SaveJob(); + void saveInternal(); + + KUrl oldUrl() const; + KUrl newUrl() const; + +protected Q_SLOTS: + virtual void doStart(); + virtual void slotResult(KJob*); + +protected: + virtual bool doKill(); + +private Q_SLOTS: + void finishSave(); + +private: + SaveJobPrivate* const d; +}; + +} // namespace + +#endif /* SAVEJOB_H */ diff --git a/gwenview/lib/document/svgdocumentloadedimpl.cpp b/gwenview/lib/document/svgdocumentloadedimpl.cpp new file mode 100644 index 00000000..07b66f22 --- /dev/null +++ b/gwenview/lib/document/svgdocumentloadedimpl.cpp @@ -0,0 +1,81 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "svgdocumentloadedimpl.moc" + +// Qt +#include + +// KDE +#include + +// Local + +namespace Gwenview +{ + +struct SvgDocumentLoadedImplPrivate +{ + QByteArray mRawData; + QSvgRenderer* mRenderer; +}; + +SvgDocumentLoadedImpl::SvgDocumentLoadedImpl(Document* document, const QByteArray& data) +: AbstractDocumentImpl(document) +, d(new SvgDocumentLoadedImplPrivate) +{ + d->mRawData = data; + d->mRenderer = new QSvgRenderer(this); +} + +SvgDocumentLoadedImpl::~SvgDocumentLoadedImpl() +{ + delete d; +} + +void SvgDocumentLoadedImpl::init() +{ + d->mRenderer->load(d->mRawData); + setDocumentImageSize(d->mRenderer->defaultSize()); + emit loaded(); +} + +Document::LoadingState SvgDocumentLoadedImpl::loadingState() const +{ + return Document::Loaded; +} + +void SvgDocumentLoadedImpl::setImage(const QImage&) +{ + kWarning() << "Should not be called"; +} + +QByteArray SvgDocumentLoadedImpl::rawData() const +{ + return d->mRawData; +} + +QSvgRenderer* SvgDocumentLoadedImpl::svgRenderer() const +{ + return d->mRenderer; +} + +} // namespace diff --git a/gwenview/lib/document/svgdocumentloadedimpl.h b/gwenview/lib/document/svgdocumentloadedimpl.h new file mode 100644 index 00000000..f2c1e4f3 --- /dev/null +++ b/gwenview/lib/document/svgdocumentloadedimpl.h @@ -0,0 +1,58 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef SVGDOCUMENTLOADEDIMPL_H +#define SVGDOCUMENTLOADEDIMPL_H + +// Qt + +// KDE + +// Local +#include + +namespace Gwenview +{ + +struct SvgDocumentLoadedImplPrivate; +class SvgDocumentLoadedImpl : public AbstractDocumentImpl +{ + Q_OBJECT +public: + SvgDocumentLoadedImpl(Document*, const QByteArray&); + ~SvgDocumentLoadedImpl(); + + virtual void init(); + + virtual Document::LoadingState loadingState() const; + + virtual void setImage(const QImage&); + + QByteArray rawData() const; + + QSvgRenderer* svgRenderer() const; + +private: + SvgDocumentLoadedImplPrivate* const d; +}; + +} // namespace + +#endif /* SVGDOCUMENTLOADEDIMPL_H */ diff --git a/gwenview/lib/document/videodocumentloadedimpl.cpp b/gwenview/lib/document/videodocumentloadedimpl.cpp new file mode 100644 index 00000000..a302512f --- /dev/null +++ b/gwenview/lib/document/videodocumentloadedimpl.cpp @@ -0,0 +1,64 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "videodocumentloadedimpl.moc" + +// Qt + +// KDE +#include + +// Local + +namespace Gwenview +{ + +struct VideoDocumentLoadedImplPrivate +{ +}; + +VideoDocumentLoadedImpl::VideoDocumentLoadedImpl(Document* document) +: AbstractDocumentImpl(document) +, d(new VideoDocumentLoadedImplPrivate) +{ +} + +VideoDocumentLoadedImpl::~VideoDocumentLoadedImpl() +{ + delete d; +} + +void VideoDocumentLoadedImpl::init() +{ + emit loaded(); +} + +Document::LoadingState VideoDocumentLoadedImpl::loadingState() const +{ + return Document::Loaded; +} + +void VideoDocumentLoadedImpl::setImage(const QImage&) +{ + kWarning() << "Should not be called"; +} + +} // namespace diff --git a/gwenview/lib/document/videodocumentloadedimpl.h b/gwenview/lib/document/videodocumentloadedimpl.h new file mode 100644 index 00000000..b7ce5596 --- /dev/null +++ b/gwenview/lib/document/videodocumentloadedimpl.h @@ -0,0 +1,54 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef VIDEODOCUMENTLOADEDIMPL_H +#define VIDEODOCUMENTLOADEDIMPL_H + +// Qt + +// KDE + +// Local +#include + +namespace Gwenview +{ + +struct VideoDocumentLoadedImplPrivate; +class VideoDocumentLoadedImpl : public AbstractDocumentImpl +{ + Q_OBJECT +public: + VideoDocumentLoadedImpl(Document*); + ~VideoDocumentLoadedImpl(); + + virtual void init(); + + virtual Document::LoadingState loadingState() const; + + virtual void setImage(const QImage&); + +private: + VideoDocumentLoadedImplPrivate* const d; +}; + +} // namespace + +#endif /* VIDEODOCUMENTLOADEDIMPL_H */ diff --git a/gwenview/lib/documentonlyproxymodel.cpp b/gwenview/lib/documentonlyproxymodel.cpp new file mode 100644 index 00000000..de7130d7 --- /dev/null +++ b/gwenview/lib/documentonlyproxymodel.cpp @@ -0,0 +1,59 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2013 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include + +// Local +#include + +// KDE +#include + +// Qt + +namespace Gwenview +{ + +struct DocumentOnlyProxyModelPrivate +{ +}; + +DocumentOnlyProxyModel::DocumentOnlyProxyModel(QObject* parent) +: QSortFilterProxyModel(parent) +, d(new DocumentOnlyProxyModelPrivate) +{ + setDynamicSortFilter(true); +} + +DocumentOnlyProxyModel::~DocumentOnlyProxyModel() +{ + delete d; +} + +bool DocumentOnlyProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const +{ + const QModelIndex index = sourceModel()->index(row, 0, parent); + const KFileItem fileItem = index.data(KDirModel::FileItemRole).value(); + return !ArchiveUtils::fileItemIsDirOrArchive(fileItem); +} + + +} // namespace diff --git a/gwenview/lib/documentonlyproxymodel.h b/gwenview/lib/documentonlyproxymodel.h new file mode 100644 index 00000000..c7e513ec --- /dev/null +++ b/gwenview/lib/documentonlyproxymodel.h @@ -0,0 +1,56 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2013 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENTONLYPROXYMODEL_H +#define DOCUMENTONLYPROXYMODEL_H + +// Local +#include + +// KDE + +// Qt +#include + +namespace Gwenview +{ + +struct DocumentOnlyProxyModelPrivate; +/** + * A proxy model which lists items which are neither dirs nor archives. + * Only works with models which expose a KDirModel::FileItemRole. + */ +class GWENVIEWLIB_EXPORT DocumentOnlyProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + DocumentOnlyProxyModel(QObject* parent = 0); + ~DocumentOnlyProxyModel(); + +protected: + bool filterAcceptsRow(int row, const QModelIndex& parent) const; // reimp + +private: + DocumentOnlyProxyModelPrivate* const d; +}; + +} // namespace + +#endif /* DOCUMENTONLYPROXYMODEL_H */ diff --git a/gwenview/lib/documentview/abstractdocumentviewadapter.cpp b/gwenview/lib/documentview/abstractdocumentviewadapter.cpp new file mode 100644 index 00000000..2c463ad1 --- /dev/null +++ b/gwenview/lib/documentview/abstractdocumentviewadapter.cpp @@ -0,0 +1,67 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "abstractdocumentviewadapter.moc" + +// Qt +#include +#include + +// KDE + +// Local + +namespace Gwenview +{ + +AbstractDocumentViewAdapter::AbstractDocumentViewAdapter() +: mWidget(0) +{ +} + +AbstractDocumentViewAdapter::~AbstractDocumentViewAdapter() +{ + delete mWidget; +} + +QCursor AbstractDocumentViewAdapter::cursor() const +{ + return mWidget ? mWidget->cursor() : QCursor(); +} + +void AbstractDocumentViewAdapter::setCursor(const QCursor& cursor) +{ + if (mWidget) { + mWidget->setCursor(cursor); + } +} + +QRectF AbstractDocumentViewAdapter::visibleDocumentRect() const +{ + return mWidget ? mWidget->boundingRect() : QRectF(); +} + +EmptyAdapter::EmptyAdapter() +{ + setWidget(new QGraphicsWidget); +} + +} // namespace diff --git a/gwenview/lib/documentview/abstractdocumentviewadapter.h b/gwenview/lib/documentview/abstractdocumentviewadapter.h new file mode 100644 index 00000000..f3e9b2b1 --- /dev/null +++ b/gwenview/lib/documentview/abstractdocumentviewadapter.h @@ -0,0 +1,187 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef ABSTRACTDOCUMENTVIEWADAPTER_H +#define ABSTRACTDOCUMENTVIEWADAPTER_H + +#include + +// Qt +#include +#include + +// KDE + +// Local +#include + +class QCursor; +class QGraphicsWidget; +class QRectF; + +namespace Gwenview +{ + +class ImageView; +class RasterImageView; + +/** + * Classes inherit from this class so that they can be used inside the + * DocumentPanel. + */ +class GWENVIEWLIB_EXPORT AbstractDocumentViewAdapter : public QObject +{ + Q_OBJECT +public: + AbstractDocumentViewAdapter(); + virtual ~AbstractDocumentViewAdapter(); + + QGraphicsWidget* widget() const + { + return mWidget; + } + + virtual MimeTypeUtils::Kind kind() const = 0; + + virtual ImageView* imageView() const + { + return 0; + } + + virtual RasterImageView* rasterImageView() const + { + return 0; + } + + virtual QCursor cursor() const; + + virtual void setCursor(const QCursor&); + + /** + * @defgroup zooming functions + * @{ + */ + virtual bool canZoom() const + { + return false; + } + + // Implementation must emit zoomToFitChanged() + virtual void setZoomToFit(bool) + {} + + virtual bool zoomToFit() const + { + return false; + } + + virtual qreal zoom() const + { + return 0; + } + + virtual void setZoom(qreal /*zoom*/, const QPointF& /*center*/ = QPointF(-1, -1)) + {} + + virtual qreal computeZoomToFit() const + { + return 1.; + } + /** @} */ + + virtual Document::Ptr document() const = 0; + virtual void setDocument(Document::Ptr) = 0; + + virtual void loadConfig() + {} + + virtual QPointF scrollPos() const + { + return QPointF(0, 0); + } + virtual void setScrollPos(const QPointF& /*pos*/) + {} + + /** + * Rectangle within the item which is actually used to show the document. + * In item coordinates. + */ + virtual QRectF visibleDocumentRect() const; + +protected: + void setWidget(QGraphicsWidget* widget) + { + mWidget = widget; + } + +Q_SIGNALS: + /** + * @addgroup zooming functions + * @{ + */ + void zoomChanged(qreal); + + void zoomToFitChanged(bool); + + void zoomInRequested(const QPointF&); + + void zoomOutRequested(const QPointF&); + /** @} */ + + void scrollPosChanged(); + + /** + * Emitted when the adapter is done showing the document for the first time + */ + void completed(); + + void previousImageRequested(); + + void nextImageRequested(); + + void toggleFullScreenRequested(); + +private: + QGraphicsWidget* mWidget; +}; + +/** + * An empty adapter, used when no document is displayed + */ +class EmptyAdapter : public AbstractDocumentViewAdapter +{ + Q_OBJECT +public: + EmptyAdapter(); + virtual MimeTypeUtils::Kind kind() const + { + return MimeTypeUtils::KIND_UNKNOWN; + } + virtual Document::Ptr document() const + { + return Document::Ptr(); + } + virtual void setDocument(Document::Ptr) + {} +}; + +} // namespace + +#endif /* ABSTRACTDOCUMENTVIEWADAPTER_H */ diff --git a/gwenview/lib/documentview/abstractimageview.cpp b/gwenview/lib/documentview/abstractimageview.cpp new file mode 100644 index 00000000..55d5e6b2 --- /dev/null +++ b/gwenview/lib/documentview/abstractimageview.cpp @@ -0,0 +1,533 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "abstractimageview.moc" + +// Local + +// KDE +#include +#include +#include + +// Qt +#include +#include + +namespace Gwenview +{ + +static const int UNIT_STEP = 16; + +struct AbstractImageViewPrivate +{ + enum Verbosity { + Silent, + Notify + }; + AbstractImageView* q; + QCursor mZoomCursor; + Document::Ptr mDocument; + + bool mControlKeyIsDown; + bool mEnlargeSmallerImages; + + qreal mZoom; + bool mZoomToFit; + QPointF mImageOffset; + QPointF mScrollPos; + QPointF mLastDragPos; + + void adjustImageOffset(Verbosity verbosity = Notify) + { + QSizeF zoomedDocSize = q->documentSize() * mZoom; + QSizeF viewSize = q->boundingRect().size(); + QPointF offset( + qMax((viewSize.width() - zoomedDocSize.width()) / 2, qreal(0.)), + qMax((viewSize.height() - zoomedDocSize.height()) / 2, qreal(0.)) + ); + if (offset != mImageOffset) { + mImageOffset = offset; + if (verbosity == Notify) { + q->onImageOffsetChanged(); + } + } + } + + void adjustScrollPos(Verbosity verbosity = Notify) + { + setScrollPos(mScrollPos, verbosity); + } + + void setScrollPos(const QPointF& _newPos, Verbosity verbosity = Notify) + { + if (!mDocument) { + mScrollPos = _newPos; + return; + } + QSizeF zoomedDocSize = q->documentSize() * mZoom; + QSizeF viewSize = q->boundingRect().size(); + QPointF newPos( + qBound(qreal(0.), _newPos.x(), zoomedDocSize.width() - viewSize.width()), + qBound(qreal(0.), _newPos.y(), zoomedDocSize.height() - viewSize.height()) + ); + if (newPos != mScrollPos) { + QPointF oldPos = mScrollPos; + mScrollPos = newPos; + if (verbosity == Notify) { + q->onScrollPosChanged(oldPos); + } + // No verbosity test: we always notify the outside world about + // scrollPos changes + QMetaObject::invokeMethod(q, "scrollPosChanged"); + } + } + + void setupZoomCursor() + { + // We do not use "appdata" here because that does not work when this + // code is called from a KPart. + QString path = KStandardDirs::locate("data", "gwenview/cursors/zoom.png"); + QPixmap cursorPixmap = QPixmap(path); + mZoomCursor = QCursor(cursorPixmap, 11, 11); + } +}; + +AbstractImageView::AbstractImageView(QGraphicsItem* parent) +: QGraphicsWidget(parent) +, d(new AbstractImageViewPrivate) +{ + d->q = this; + d->mControlKeyIsDown = false; + d->mEnlargeSmallerImages = false; + d->mZoom = 1; + d->mZoomToFit = true; + d->mImageOffset = QPointF(0, 0); + d->mScrollPos = QPointF(0, 0); + setFocusPolicy(Qt::WheelFocus); + setFlag(ItemIsSelectable); + setAcceptHoverEvents(true); + d->setupZoomCursor(); + updateCursor(); +} + +AbstractImageView::~AbstractImageView() +{ + if (d->mDocument) { + d->mDocument->stopAnimation(); + } + delete d; +} + +Document::Ptr AbstractImageView::document() const +{ + return d->mDocument; +} + +void AbstractImageView::setDocument(Document::Ptr doc) +{ + if (d->mDocument) { + disconnect(d->mDocument.data(), 0, this, 0); + } + d->mDocument = doc; + loadFromDocument(); +} + +QSizeF AbstractImageView::documentSize() const +{ + return d->mDocument ? d->mDocument->size() : QSizeF(); +} + +qreal AbstractImageView::zoom() const +{ + return d->mZoom; +} + +void AbstractImageView::setZoom(qreal zoom, const QPointF& _center, AbstractImageView::UpdateType updateType) +{ + if (!d->mDocument) { + d->mZoom = zoom; + return; + } + if (updateType == UpdateIfNecessary && qFuzzyCompare(zoom, d->mZoom)) { + return; + } + qreal oldZoom = d->mZoom; + d->mZoom = zoom; + + QPointF center; + if (_center == QPointF(-1, -1)) { + center = boundingRect().center(); + } else { + center = _center; + } + + /* + We want to keep the point at viewport coordinates "center" at the same + position after zooming. The coordinates of this point in image coordinates + can be expressed like this: + + oldScroll + center + imagePointAtOldZoom = ------------------ + oldZoom + + scroll + center + imagePointAtZoom = --------------- + zoom + + So we want: + + imagePointAtOldZoom = imagePointAtZoom + + oldScroll + center scroll + center + <=> ------------------ = --------------- + oldZoom zoom + + zoom + <=> scroll = ------- (oldScroll + center) - center + oldZoom + */ + + /* + Compute oldScroll + It's useless to take the new offset in consideration because if a direction + of the new offset is not 0, we won't be able to center on a specific point + in that direction. + */ + QPointF oldScroll = scrollPos() - imageOffset(); + + QPointF scroll = (zoom / oldZoom) * (oldScroll + center) - center; + + d->adjustImageOffset(AbstractImageViewPrivate::Silent); + d->setScrollPos(scroll, AbstractImageViewPrivate::Silent); + onZoomChanged(); + zoomChanged(d->mZoom); +} + +bool AbstractImageView::zoomToFit() const +{ + return d->mZoomToFit; +} + +void AbstractImageView::setZoomToFit(bool on) +{ + d->mZoomToFit = on; + if (on) { + setZoom(computeZoomToFit()); + } + // We do not set zoom to 1 if zoomToFit is off, this is up to the code + // calling us. It may went to zoom to some other level and/or to zoom on + // a particular position + zoomToFitChanged(d->mZoomToFit); +} + +void AbstractImageView::resizeEvent(QGraphicsSceneResizeEvent* event) +{ + QGraphicsWidget::resizeEvent(event); + if (d->mZoomToFit) { + // setZoom() calls adjustImageOffset(), but only if the zoom changes. + // If the view is resized but does not cause a zoom change, we call + // adjustImageOffset() ourself. + const qreal newZoom = computeZoomToFit(); + if (qFuzzyCompare(zoom(), newZoom)) { + d->adjustImageOffset(AbstractImageViewPrivate::Notify); + } else { + setZoom(newZoom); + } + } else { + d->adjustImageOffset(); + d->adjustScrollPos(); + } +} + +qreal AbstractImageView::computeZoomToFit() const +{ + QSizeF docSize = documentSize(); + if (docSize.isEmpty()) { + return 1; + } + QSizeF viewSize = boundingRect().size(); + qreal fitWidth = viewSize.width() / docSize.width(); + qreal fitHeight = viewSize.height() / docSize.height(); + qreal fit = qMin(fitWidth, fitHeight); + if (!d->mEnlargeSmallerImages) { + fit = qMin(fit, qreal(1.)); + } + return fit; +} + +void AbstractImageView::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + QGraphicsItem::mousePressEvent(event); + if (event->button() == Qt::MiddleButton) { + bool value = !zoomToFit(); + setZoomToFit(value); + if (!value) { + setZoom(1.); + } + return; + } + + if (event->modifiers() & Qt::ControlModifier) { + if (event->button() == Qt::LeftButton) { + zoomInRequested(event->pos()); + return; + } else if (event->button() == Qt::RightButton) { + zoomOutRequested(event->pos()); + return; + } + } + + d->mLastDragPos = event->pos(); + updateCursor(); +} + +void AbstractImageView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + QGraphicsItem::mouseMoveEvent(event); + + QPointF mousePos = event->pos(); + QPointF newScrollPos = d->mScrollPos + d->mLastDragPos - mousePos; + + // Wrap mouse pos + qreal maxWidth = boundingRect().width(); + qreal maxHeight = boundingRect().height(); + // We need a margin because if the window is maximized, the mouse may not + // be able to go past the bounding rect. + // The mouse get placed 1 pixel before/after the margin to avoid getting + // considered as needing to wrap the other way in next mouseMoveEvent + // (because we don't check the move vector) + const int margin = 5; + if (mousePos.x() <= margin) { + mousePos.setX(maxWidth - margin - 1); + } else if (mousePos.x() >= maxWidth - margin) { + mousePos.setX(margin + 1); + } + if (mousePos.y() <= margin) { + mousePos.setY(maxHeight - margin - 1); + } else if (mousePos.y() >= maxHeight - margin) { + mousePos.setY(margin + 1); + } + + // Set mouse pos (Hackish translation to screen coords!) + QPointF screenDelta = event->screenPos() - event->pos(); + QCursor::setPos((mousePos + screenDelta).toPoint()); + + d->mLastDragPos = mousePos; + d->setScrollPos(newScrollPos); + +} + +void AbstractImageView::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + QGraphicsItem::mouseReleaseEvent(event); + if (!d->mLastDragPos.isNull()) { + d->mLastDragPos = QPointF(); + } + updateCursor(); +} + +void AbstractImageView::keyPressEvent(QKeyEvent* event) +{ + if (event->key() == Qt::Key_Control) { + d->mControlKeyIsDown = true; + updateCursor(); + return; + } + if (zoomToFit() || qFuzzyCompare(computeZoomToFit(), zoom())) { + if (event->modifiers() != Qt::NoModifier) { + return; + } + + switch (event->key()) { + case Qt::Key_Left: + case Qt::Key_Up: + previousImageRequested(); + break; + case Qt::Key_Right: + case Qt::Key_Down: + nextImageRequested(); + break; + default: + break; + } + return; + } + + QPointF delta(0, 0); + qreal pageStep = boundingRect().height(); + qreal unitStep; + + if (event->modifiers() & Qt::ShiftModifier) { + unitStep = pageStep / 2; + } else { + unitStep = UNIT_STEP; + } + switch (event->key()) { + case Qt::Key_Left: + delta.setX(-unitStep); + break; + case Qt::Key_Right: + delta.setX(unitStep); + break; + case Qt::Key_Up: + delta.setY(-unitStep); + break; + case Qt::Key_Down: + delta.setY(unitStep); + break; + case Qt::Key_PageUp: + delta.setY(-pageStep); + break; + case Qt::Key_PageDown: + delta.setY(pageStep); + break; + case Qt::Key_Home: + d->setScrollPos(QPointF(d->mScrollPos.x(), 0)); + return; + case Qt::Key_End: + d->setScrollPos(QPointF(d->mScrollPos.x(), documentSize().height() * zoom())); + return; + default: + return; + } + d->setScrollPos(d->mScrollPos + delta); +} + +void AbstractImageView::keyReleaseEvent(QKeyEvent* event) +{ + if (event->key() == Qt::Key_Control) { + d->mControlKeyIsDown = false; + updateCursor(); + } +} + +void AbstractImageView::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) +{ + if (event->modifiers() == Qt::NoModifier) { + toggleFullScreenRequested(); + } +} + +QPointF AbstractImageView::imageOffset() const +{ + return d->mImageOffset; +} + +QPointF AbstractImageView::scrollPos() const +{ + return d->mScrollPos; +} + +void AbstractImageView::setScrollPos(const QPointF& pos) +{ + d->setScrollPos(pos); +} + +QPointF AbstractImageView::mapToView(const QPointF& imagePos) const +{ + return imagePos * d->mZoom + d->mImageOffset - d->mScrollPos; +} + +QPoint AbstractImageView::mapToView(const QPoint& imagePos) const +{ + return mapToView(QPointF(imagePos)).toPoint(); +} + +QRectF AbstractImageView::mapToView(const QRectF& imageRect) const +{ + return QRectF( + mapToView(imageRect.topLeft()), + imageRect.size() * zoom() + ); +} + +QRect AbstractImageView::mapToView(const QRect& imageRect) const +{ + return QRect( + mapToView(imageRect.topLeft()), + imageRect.size() * zoom() + ); +} + +QPointF AbstractImageView::mapToImage(const QPointF& viewPos) const +{ + return (viewPos - d->mImageOffset + d->mScrollPos) / d->mZoom; +} + +QPoint AbstractImageView::mapToImage(const QPoint& viewPos) const +{ + return mapToImage(QPointF(viewPos)).toPoint(); +} + +QRectF AbstractImageView::mapToImage(const QRectF& viewRect) const +{ + return QRectF( + mapToImage(viewRect.topLeft()), + viewRect.size() / zoom() + ); +} + +QRect AbstractImageView::mapToImage(const QRect& viewRect) const +{ + return QRect( + mapToImage(viewRect.topLeft()), + viewRect.size() / zoom() + ); +} + +void AbstractImageView::setEnlargeSmallerImages(bool value) +{ + d->mEnlargeSmallerImages = value; + if (zoomToFit()) { + setZoom(computeZoomToFit()); + } +} + +void AbstractImageView::updateCursor() +{ + if (d->mControlKeyIsDown) { + setCursor(d->mZoomCursor); + } else { + if (d->mLastDragPos.isNull()) { + setCursor(Qt::OpenHandCursor); + } else { + setCursor(Qt::ClosedHandCursor); + } + } +} + +QSizeF AbstractImageView::visibleImageSize() const +{ + if (!document()) { + return QSizeF(); + } + QSizeF size = documentSize() * zoom(); + return size.boundedTo(boundingRect().size()); +} + +void AbstractImageView::applyPendingScrollPos() +{ + d->adjustImageOffset(); + d->adjustScrollPos(); +} + +} // namespace diff --git a/gwenview/lib/documentview/abstractimageview.h b/gwenview/lib/documentview/abstractimageview.h new file mode 100644 index 00000000..1b88c852 --- /dev/null +++ b/gwenview/lib/documentview/abstractimageview.h @@ -0,0 +1,140 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef ABSTRACTIMAGEVIEW_H +#define ABSTRACTIMAGEVIEW_H + +// Local +#include + +// KDE + +// Qt +#include + +namespace Gwenview +{ + +struct AbstractImageViewPrivate; +/** + * + */ +class AbstractImageView : public QGraphicsWidget +{ + Q_OBJECT +public: + enum UpdateType { + UpdateIfNecessary, + ForceUpdate + }; + AbstractImageView(QGraphicsItem* parent); + ~AbstractImageView(); + + qreal zoom() const; + + virtual void setZoom(qreal zoom, const QPointF& center = QPointF(-1, -1), UpdateType updateType = UpdateIfNecessary); + + bool zoomToFit() const; + + virtual void setZoomToFit(bool value); + + virtual void setDocument(Document::Ptr doc); + + Document::Ptr document() const; + + qreal computeZoomToFit() const; + + QSizeF documentSize() const; + + QSizeF visibleImageSize() const; + + /** + * If the image is smaller than the view, imageOffset is the distance from + * the topleft corner of the view to the topleft corner of the image. + * Neither x nor y can be negative. + */ + QPointF imageOffset() const; + + /** + * The scroll position, in zoomed image coordinates. + * x and y are always between 0 and (docsize * zoom - viewsize) + */ + QPointF scrollPos() const; + void setScrollPos(const QPointF& pos); + + QPointF mapToView(const QPointF& imagePos) const; + QPoint mapToView(const QPoint& imagePos) const; + QRectF mapToView(const QRectF& imageRect) const; + QRect mapToView(const QRect& imageRect) const; + + QPointF mapToImage(const QPointF& viewPos) const; + QPoint mapToImage(const QPoint& viewPos) const; + QRectF mapToImage(const QRectF& viewRect) const; + QRect mapToImage(const QRect& viewRect) const; + + void setEnlargeSmallerImages(bool value); + + void applyPendingScrollPos(); + +public Q_SLOTS: + void updateCursor(); + +Q_SIGNALS: + void zoomToFitChanged(bool); + void zoomChanged(qreal); + void zoomInRequested(const QPointF&); + void zoomOutRequested(const QPointF&); + void scrollPosChanged(); + void completed(); + void previousImageRequested(); + void nextImageRequested(); + void toggleFullScreenRequested(); + +protected: + virtual void loadFromDocument() = 0; + virtual void onZoomChanged() = 0; + /** + * Called when the offset changes. + * Note: to avoid multiple adjustments, this is not called if zoom changes! + */ + virtual void onImageOffsetChanged() = 0; + /** + * Called when the scrollPos changes. + * Note: to avoid multiple adjustments, this is not called if zoom changes! + */ + virtual void onScrollPosChanged(const QPointF& oldPos) = 0; + + void resizeEvent(QGraphicsSceneResizeEvent* event); + + void keyPressEvent(QKeyEvent* event); + void keyReleaseEvent(QKeyEvent* event); + void mousePressEvent(QGraphicsSceneMouseEvent* event); + void mouseMoveEvent(QGraphicsSceneMouseEvent* event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent* event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event); + +private: + friend struct AbstractImageViewPrivate; + AbstractImageViewPrivate* const d; +}; + +} // namespace + +#endif /* ABSTRACTIMAGEVIEW_H */ diff --git a/gwenview/lib/documentview/abstractrasterimageviewtool.cpp b/gwenview/lib/documentview/abstractrasterimageviewtool.cpp new file mode 100644 index 00000000..53daf8d2 --- /dev/null +++ b/gwenview/lib/documentview/abstractrasterimageviewtool.cpp @@ -0,0 +1,56 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "abstractrasterimageviewtool.moc" + +// Qt + +// KDE + +// Local +#include "rasterimageview.h" + +namespace Gwenview +{ + +struct AbstractRasterImageViewToolPrivate +{ + RasterImageView* mRasterImageView; +}; + +AbstractRasterImageViewTool::AbstractRasterImageViewTool(RasterImageView* view) +: QObject(view) +, d(new AbstractRasterImageViewToolPrivate) +{ + d->mRasterImageView = view; +} + +AbstractRasterImageViewTool::~AbstractRasterImageViewTool() +{ + delete d; +} + +RasterImageView* AbstractRasterImageViewTool::imageView() const +{ + return d->mRasterImageView; +} + +} // namespace diff --git a/gwenview/lib/documentview/abstractrasterimageviewtool.h b/gwenview/lib/documentview/abstractrasterimageviewtool.h new file mode 100644 index 00000000..9128c98a --- /dev/null +++ b/gwenview/lib/documentview/abstractrasterimageviewtool.h @@ -0,0 +1,92 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef ABSTRACTRASTERIMAGEVIEWTOOL_H +#define ABSTRACTRASTERIMAGEVIEWTOOL_H + +#include + +// Qt +#include + +// KDE + +// Local + +class QKeyEvent; +class QGraphicsSceneHoverEvent; +class QGraphicsSceneMouseEvent; +class QGraphicsSceneWheelEvent; +class QPainter; + +namespace Gwenview +{ + +class RasterImageView; + +struct AbstractRasterImageViewToolPrivate; +class GWENVIEWLIB_EXPORT AbstractRasterImageViewTool : public QObject +{ + Q_OBJECT +public: + AbstractRasterImageViewTool(RasterImageView* view); + virtual ~AbstractRasterImageViewTool(); + + RasterImageView* imageView() const; + + virtual void paint(QPainter*) + {} + + virtual void mousePressEvent(QGraphicsSceneMouseEvent*) + {} + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) + {} + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) + {} + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent*) + {} + virtual void wheelEvent(QGraphicsSceneWheelEvent*) + {} + virtual void keyPressEvent(QKeyEvent*) + {} + virtual void keyReleaseEvent(QKeyEvent*) + {} + + virtual void toolActivated() + {} + virtual void toolDeactivated() + {} + + virtual QWidget* widget() const + { + return 0; + } + +public Q_SLOTS: + virtual void onWidgetSlidedIn() + {} + +private: + AbstractRasterImageViewToolPrivate * const d; +}; + +} // namespace + +#endif /* ABSTRACTRASTERIMAGEVIEWTOOL_H */ diff --git a/gwenview/lib/documentview/birdeyeview.cpp b/gwenview/lib/documentview/birdeyeview.cpp new file mode 100644 index 00000000..4c9ec224 --- /dev/null +++ b/gwenview/lib/documentview/birdeyeview.cpp @@ -0,0 +1,295 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "birdeyeview.moc" + +// Local +#include +#include + +// KDE +#include + +// Qt +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +static qreal MIN_SIZE = 72; +static qreal VIEW_OFFSET = MIN_SIZE / 4; + +static int AUTOHIDE_DELAY = 2000; + +/** + * Returns a QRectF whose coordinates are rounded to completely contains rect + */ +inline QRectF alignedRectF(const QRectF& rect) +{ + return QRectF(rect.toAlignedRect()); +} + +struct BirdEyeViewPrivate +{ + BirdEyeView* q; + DocumentView* mDocView; + QPropertyAnimation* mOpacityAnim; + QTimer* mAutoHideTimer; + QRectF mVisibleRect; + QPointF mStartDragMousePos; + QPointF mStartDragViewPos; + + void updateCursor(const QPointF& pos) + { + q->setCursor(mVisibleRect.contains(pos) ? Qt::OpenHandCursor : Qt::ArrowCursor); + } + + void updateVisibility() + { + bool visible; + if (!mDocView->canZoom() || mDocView->zoomToFit()) { + // No need to show + visible = false; + } else if (mDocView->isAnimated()) { + // Do not show while animated + visible = false; + } else if (mVisibleRect == q->boundingRect()) { + // All of the image is visible + visible = false; + } else if (q->isUnderMouse() || !mStartDragMousePos.isNull()) { + // User is interacting or about to interact with birdeyeview + visible = true; + } else if (mAutoHideTimer->isActive()) { + // User triggered some activity recently (move mouse, scroll, zoom) + visible = true; + } else { + // No recent activity + visible = false; + } + qreal wantedOpacity = visible ? 1 : 0; + if (!qFuzzyCompare(wantedOpacity, q->opacity())) { + mOpacityAnim->setEndValue(wantedOpacity); + mOpacityAnim->start(); + } + + if (visible) { + mAutoHideTimer->start(); + } + } +}; + +BirdEyeView::BirdEyeView(DocumentView* docView) +: QGraphicsWidget(docView) +, d(new BirdEyeViewPrivate) +{ + d->q = this; + d->mDocView = docView; + setFlag(ItemIsSelectable); + setCursor(Qt::ArrowCursor); + setAcceptHoverEvents(true); + + d->mOpacityAnim = new QPropertyAnimation(this, "opacity", this); + + d->mAutoHideTimer = new QTimer(this); + d->mAutoHideTimer->setSingleShot(true); + d->mAutoHideTimer->setInterval(AUTOHIDE_DELAY); + connect(d->mAutoHideTimer, SIGNAL(timeout()), SLOT(slotAutoHideTimeout())); + + // Hide ourself by default, to avoid startup flashes (if we let updateOpacity + // update opacity, it will do so through an animation) + setOpacity(0); + slotZoomOrSizeChanged(); + + connect(docView->document().data(), SIGNAL(metaInfoUpdated()), SLOT(slotZoomOrSizeChanged())); + connect(docView, SIGNAL(zoomChanged(qreal)), SLOT(slotZoomOrSizeChanged())); + connect(docView, SIGNAL(zoomToFitChanged(bool)), SLOT(slotZoomOrSizeChanged())); + connect(docView, SIGNAL(positionChanged()), SLOT(slotPositionChanged())); +} + +BirdEyeView::~BirdEyeView() +{ + delete d; +} + +void BirdEyeView::adjustGeometry() +{ + if (!d->mDocView->canZoom() || d->mDocView->zoomToFit()) { + return; + } + QSizeF size = d->mDocView->document()->size(); + size.scale(MIN_SIZE, MIN_SIZE, Qt::KeepAspectRatioByExpanding); + QRectF docViewRect = d->mDocView->boundingRect(); + int maxBevHeight = docViewRect.height() - 2 * VIEW_OFFSET; + int maxBevWidth = docViewRect.width() - 2 * VIEW_OFFSET; + if (size.height() > maxBevHeight) { + size.scale(MIN_SIZE, maxBevHeight, Qt::KeepAspectRatio); + } + if (size.width() > maxBevWidth) { + size.scale(maxBevWidth, MIN_SIZE, Qt::KeepAspectRatio); + } + QRectF geom = QRectF( + QApplication::isRightToLeft() + ? docViewRect.left() + VIEW_OFFSET + : docViewRect.right() - VIEW_OFFSET - size.width(), + docViewRect.bottom() - VIEW_OFFSET - size.height(), + size.width(), + size.height() + ); + setGeometry(alignedRectF(geom)); + adjustVisibleRect(); +} + +void BirdEyeView::adjustVisibleRect() +{ + QSizeF docSize = d->mDocView->document()->size(); + qreal viewZoom = d->mDocView->zoom(); + qreal bevZoom; + if (docSize.height() > docSize.width()) { + bevZoom = size().height() / docSize.height(); + } else { + bevZoom = size().width() / docSize.width(); + } + + if (qFuzzyIsNull(viewZoom) || qFuzzyIsNull(bevZoom)) { + // Prevent divide-by-zero crashes + return; + } + + QRectF rect = QRectF( + QPointF(d->mDocView->position()) / viewZoom * bevZoom, + (d->mDocView->size() / viewZoom).boundedTo(docSize) * bevZoom); + d->mVisibleRect = rect; +} + +void BirdEyeView::slotAutoHideTimeout() +{ + d->updateVisibility(); +} + +void BirdEyeView::slotZoomOrSizeChanged() +{ + if (!d->mDocView->canZoom() || d->mDocView->zoomToFit()) { + d->updateVisibility(); + return; + } + adjustGeometry(); + update(); + d->mAutoHideTimer->start(); + d->updateVisibility(); +} + +void BirdEyeView::slotPositionChanged() +{ + adjustVisibleRect(); + update(); + d->mAutoHideTimer->start(); + d->updateVisibility(); +} + +inline void drawTransparentRect(QPainter* painter, const QRectF& rect, const QColor& color) +{ + QColor bg = color; + bg.setAlphaF(.33); + QColor fg = color; + fg.setAlphaF(.66); + painter->setPen(fg); + painter->setBrush(bg); + painter->drawRect(rect.adjusted(0, 0, -1, -1)); +} + +void BirdEyeView::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) +{ + static const QColor bgColor = QColor::fromHsvF(0, 0, .33); + drawTransparentRect(painter, boundingRect(), bgColor); + drawTransparentRect(painter, d->mVisibleRect, Qt::white); +} + +void BirdEyeView::onMouseMoved() +{ + d->mAutoHideTimer->start(); + d->updateVisibility(); +} + +void BirdEyeView::slotIsAnimatedChanged() +{ + d->updateVisibility(); +} + +void BirdEyeView::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + if (d->mVisibleRect.contains(event->pos())) { + setCursor(Qt::ClosedHandCursor); + d->mStartDragMousePos = event->pos(); + d->mStartDragViewPos = d->mDocView->position(); + } +} + +void BirdEyeView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + QGraphicsItem::mouseMoveEvent(event); + if (d->mStartDragMousePos.isNull()) { + // Do not drag if mouse was pressed outside visible rect + return; + } + qreal ratio = qMin(d->mDocView->boundingRect().height() / d->mVisibleRect.height(), + d->mDocView->boundingRect().width() / d->mVisibleRect.width()); + QPointF mousePos = event->pos(); + QPointF viewPos = d->mStartDragViewPos + (mousePos - d->mStartDragMousePos) * ratio; + + d->mDocView->setPosition(viewPos.toPoint()); +} + +void BirdEyeView::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + QGraphicsItem::mouseReleaseEvent(event); + if (d->mStartDragMousePos.isNull()) { + return; + } + d->updateCursor(event->pos()); + d->mStartDragMousePos = QPointF(); + d->mAutoHideTimer->start(); +} + +void BirdEyeView::hoverEnterEvent(QGraphicsSceneHoverEvent* /*event*/) +{ + d->mAutoHideTimer->stop(); + d->updateVisibility(); +} + +void BirdEyeView::hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + if (d->mStartDragMousePos.isNull()) { + d->updateCursor(event->pos()); + } +} + +void BirdEyeView::hoverLeaveEvent(QGraphicsSceneHoverEvent* /*event*/) +{ + d->mAutoHideTimer->start(); + d->updateVisibility(); +} + +} // namespace diff --git a/gwenview/lib/documentview/birdeyeview.h b/gwenview/lib/documentview/birdeyeview.h new file mode 100644 index 00000000..1f12fe23 --- /dev/null +++ b/gwenview/lib/documentview/birdeyeview.h @@ -0,0 +1,80 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef BIRDEYEVIEW_H +#define BIRDEYEVIEW_H + +// Local + +// KDE + +// Qt +#include + +namespace Gwenview +{ + +class DocumentView; + +struct BirdEyeViewPrivate; +/** + * Shows a bird-eye view of the current document. Makes it possible to scroll + * through the document. + */ +class BirdEyeView : public QGraphicsWidget +{ + Q_OBJECT +public: + BirdEyeView(DocumentView* docView); + ~BirdEyeView(); + + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); // reimp + + // Called by DocumentView when it detects mouse movements + // We cannot use a sceneEventFilter because QGraphicsSceneHoverEvent are not + // sent to parent items (unlike QHoverEvent). Therefore DocumentView has to + // do the work of event-filtering the actual document widget. + void onMouseMoved(); + +public Q_SLOTS: + void slotZoomOrSizeChanged(); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent* event); + void mouseMoveEvent(QGraphicsSceneMouseEvent* event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent* event); + void hoverEnterEvent(QGraphicsSceneHoverEvent* event); + void hoverMoveEvent(QGraphicsSceneHoverEvent* event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent* event); + +private Q_SLOTS: + void slotAutoHideTimeout(); + void slotPositionChanged(); + void slotIsAnimatedChanged(); + +private: + BirdEyeViewPrivate* const d; + void adjustVisibleRect(); + void adjustGeometry(); +}; + +} // namespace + +#endif /* BIRDEYEVIEW_H */ diff --git a/gwenview/lib/documentview/documentview.cpp b/gwenview/lib/documentview/documentview.cpp new file mode 100644 index 00000000..1ab8bbb1 --- /dev/null +++ b/gwenview/lib/documentview/documentview.cpp @@ -0,0 +1,773 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "documentview.moc" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include + +// Local +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +static const qreal REAL_DELTA = 0.001; +static const qreal MAXIMUM_ZOOM_VALUE = qreal(DocumentView::MaximumZoom); + +static const int COMPARE_MARGIN = 4; + +const int DocumentView::MaximumZoom = 16; +const int DocumentView::AnimDuration = 250; + +struct DocumentViewPrivate +{ + DocumentView* q; + int mSortKey; // Used to sort views when displayed in compare mode + HudWidget* mHud; + BirdEyeView* mBirdEyeView; + QWeakPointer mMoveAnimation; + QWeakPointer mFadeAnimation; + + LoadingIndicator* mLoadingIndicator; + + QScopedPointer mAdapter; + QList mZoomSnapValues; + Document::Ptr mDocument; + DocumentView::Setup mSetup; + bool mCurrent; + bool mCompareMode; + bool mEraseBorders; + + void setCurrentAdapter(AbstractDocumentViewAdapter* adapter) + { + Q_ASSERT(adapter); + mAdapter.reset(adapter); + + adapter->widget()->setParentItem(q); + resizeAdapterWidget(); + + if (adapter->canZoom()) { + QObject::connect(adapter, SIGNAL(zoomChanged(qreal)), + q, SLOT(slotZoomChanged(qreal))); + QObject::connect(adapter, SIGNAL(zoomInRequested(QPointF)), + q, SLOT(zoomIn(QPointF))); + QObject::connect(adapter, SIGNAL(zoomOutRequested(QPointF)), + q, SLOT(zoomOut(QPointF))); + QObject::connect(adapter, SIGNAL(zoomToFitChanged(bool)), + q, SIGNAL(zoomToFitChanged(bool))); + } + QObject::connect(adapter, SIGNAL(scrollPosChanged()), + q, SIGNAL(positionChanged())); + QObject::connect(adapter, SIGNAL(previousImageRequested()), + q, SIGNAL(previousImageRequested())); + QObject::connect(adapter, SIGNAL(nextImageRequested()), + q, SIGNAL(nextImageRequested())); + QObject::connect(adapter, SIGNAL(toggleFullScreenRequested()), + q, SIGNAL(toggleFullScreenRequested())); + QObject::connect(adapter, SIGNAL(completed()), + q, SLOT(slotCompleted())); + + adapter->loadConfig(); + + adapter->widget()->installSceneEventFilter(q); + if (mCurrent) { + adapter->widget()->setFocus(); + } + + if (mSetup.valid && adapter->canZoom()) { + adapter->setZoomToFit(mSetup.zoomToFit); + if (!mSetup.zoomToFit) { + adapter->setZoom(mSetup.zoom); + adapter->setScrollPos(mSetup.position); + } + } + q->adapterChanged(); + q->positionChanged(); + if (adapter->canZoom()) { + q->zoomToFitChanged(adapter->zoomToFit()); + } + if (adapter->rasterImageView()) { + QObject::connect(adapter->rasterImageView(), SIGNAL(currentToolChanged(AbstractRasterImageViewTool*)), + q, SIGNAL(currentToolChanged(AbstractRasterImageViewTool*))); + } + } + + void setupLoadingIndicator() + { + mLoadingIndicator = new LoadingIndicator(q); + GraphicsWidgetFloater* floater = new GraphicsWidgetFloater(q); + floater->setChildWidget(mLoadingIndicator); + } + + HudButton* createHudButton(const QString& text, const char* iconName, bool showText) + { + HudButton* button = new HudButton; + if (showText) { + button->setText(text); + } else { + button->setToolTip(text); + } + button->setIcon(KIcon(iconName)); + return button; + } + + void setupHud() + { + HudButton* trashButton = createHudButton(i18nc("@info:tooltip", "Trash"), "user-trash", false); + HudButton* deselectButton = createHudButton(i18nc("@action:button", "Deselect"), "list-remove", true); + + QGraphicsWidget* content = new QGraphicsWidget; + QGraphicsLinearLayout* layout = new QGraphicsLinearLayout(content); + layout->addItem(trashButton); + layout->addItem(deselectButton); + + mHud = new HudWidget(q); + mHud->init(content, HudWidget::OptionNone); + GraphicsWidgetFloater* floater = new GraphicsWidgetFloater(q); + floater->setChildWidget(mHud); + floater->setAlignment(Qt::AlignBottom | Qt::AlignHCenter); + + QObject::connect(trashButton, SIGNAL(clicked()), q, SLOT(emitHudTrashClicked())); + QObject::connect(deselectButton, SIGNAL(clicked()), q, SLOT(emitHudDeselectClicked())); + + mHud->hide(); + } + + void setupBirdEyeView() + { + if (mBirdEyeView) { + delete mBirdEyeView; + } + mBirdEyeView = new BirdEyeView(q); + mBirdEyeView->setZValue(1); + } + + void updateCaption() + { + QString caption; + + Document::Ptr doc = mAdapter->document(); + if (!doc) { + emit q->captionUpdateRequested(caption); + return; + } + + caption = doc->url().fileName(); + QSize size = doc->size(); + if (size.isValid()) { + caption += + QString(" - %1x%2") + .arg(size.width()) + .arg(size.height()); + if (mAdapter->canZoom()) { + int intZoom = qRound(mAdapter->zoom() * 100); + caption += QString(" - %1%") + .arg(intZoom); + } + } + emit q->captionUpdateRequested(caption); + } + + void uncheckZoomToFit() + { + if (mAdapter->zoomToFit()) { + mAdapter->setZoomToFit(false); + } + } + + void setZoom(qreal zoom, const QPointF& center = QPointF(-1, -1)) + { + uncheckZoomToFit(); + zoom = qBound(q->minimumZoom(), zoom, MAXIMUM_ZOOM_VALUE); + mAdapter->setZoom(zoom, center); + } + + void updateZoomSnapValues() + { + qreal min = q->minimumZoom(); + + mZoomSnapValues.clear(); + if (min < 1.) { + mZoomSnapValues << min; + for (qreal invZoom = 16.; invZoom > 1.; invZoom /= 2.) { + qreal zoom = 1. / invZoom; + if (zoom > min) { + mZoomSnapValues << zoom; + } + } + } + for (qreal zoom = 1; zoom <= MAXIMUM_ZOOM_VALUE ; zoom += 1.) { + mZoomSnapValues << zoom; + } + + q->minimumZoomChanged(min); + } + + void showLoadingIndicator() + { + if (!mLoadingIndicator) { + setupLoadingIndicator(); + } + mLoadingIndicator->show(); + mLoadingIndicator->setZValue(1); + } + + void hideLoadingIndicator() + { + if (!mLoadingIndicator) { + return; + } + mLoadingIndicator->hide(); + } + + void resizeAdapterWidget() + { + QRectF rect = QRectF(QPointF(0, 0), q->boundingRect().size()); + if (mCompareMode) { + rect.adjust(COMPARE_MARGIN, COMPARE_MARGIN, -COMPARE_MARGIN, -COMPARE_MARGIN); + } + mAdapter->widget()->setGeometry(rect); + } + + void fadeTo(qreal value) + { + if (mFadeAnimation.data()) { + qreal endValue = mFadeAnimation.data()->endValue().toReal(); + if (qFuzzyCompare(value, endValue)) { + // Same end value, don't change the actual animation + return; + } + } + // Create a new fade animation + QPropertyAnimation* anim = new QPropertyAnimation(q, "opacity"); + anim->setStartValue(q->opacity()); + anim->setEndValue(value); + if (qFuzzyCompare(value, 1)) { + QObject::connect(anim, SIGNAL(finished()), + q, SLOT(slotFadeInFinished())); + } + QObject::connect(anim, SIGNAL(finished()), q, SIGNAL(isAnimatedChanged())); + anim->setDuration(DocumentView::AnimDuration); + mFadeAnimation = anim; + q->isAnimatedChanged(); + anim->start(QAbstractAnimation::DeleteWhenStopped); + } +}; + +DocumentView::DocumentView(QGraphicsScene* scene) +: d(new DocumentViewPrivate) +{ + setFlag(ItemIsFocusable); + setFlag(ItemIsSelectable); + setFlag(ItemClipsChildrenToShape); + + d->q = this; + d->mLoadingIndicator = 0; + d->mBirdEyeView = 0; + d->mCurrent = false; + d->mCompareMode = false; + d->mEraseBorders = false; + + setOpacity(0); + + scene->addItem(this); + + d->setupHud(); + d->setCurrentAdapter(new EmptyAdapter); +} + +DocumentView::~DocumentView() +{ + delete d; +} + +void DocumentView::createAdapterForDocument() +{ + const MimeTypeUtils::Kind documentKind = d->mDocument->kind(); + if (d->mAdapter && documentKind == d->mAdapter->kind() && documentKind != MimeTypeUtils::KIND_UNKNOWN) { + // Do not reuse for KIND_UNKNOWN: we may need to change the message + LOG("Reusing current adapter"); + return; + } + AbstractDocumentViewAdapter* adapter = 0; + switch (documentKind) { + case MimeTypeUtils::KIND_RASTER_IMAGE: + adapter = new RasterImageViewAdapter; + break; + case MimeTypeUtils::KIND_SVG_IMAGE: + adapter = new SvgViewAdapter; + break; + case MimeTypeUtils::KIND_VIDEO: + adapter = new VideoViewAdapter; + connect(adapter, SIGNAL(videoFinished()), + SIGNAL(videoFinished())); + break; + case MimeTypeUtils::KIND_UNKNOWN: + adapter = new MessageViewAdapter; + static_cast(adapter)->setErrorMessage(i18n("Gwenview does not know how to display this kind of document")); + break; + default: + kWarning() << "should not be called for documentKind=" << documentKind; + adapter = new MessageViewAdapter; + break; + } + + d->setCurrentAdapter(adapter); +} + +void DocumentView::openUrl(const KUrl& url, const DocumentView::Setup& setup) +{ + if (d->mDocument) { + if (url == d->mDocument->url()) { + return; + } + disconnect(d->mDocument.data(), 0, this, 0); + } + d->mSetup = setup; + d->mDocument = DocumentFactory::instance()->load(url); + connect(d->mDocument.data(), SIGNAL(busyChanged(KUrl,bool)), SLOT(slotBusyChanged(KUrl,bool))); + + if (d->mDocument->loadingState() < Document::KindDetermined) { + MessageViewAdapter* messageViewAdapter = qobject_cast(d->mAdapter.data()); + if (messageViewAdapter) { + messageViewAdapter->setInfoMessage(QString()); + } + d->showLoadingIndicator(); + connect(d->mDocument.data(), SIGNAL(kindDetermined(KUrl)), + SLOT(finishOpenUrl())); + } else { + QMetaObject::invokeMethod(this, "finishOpenUrl", Qt::QueuedConnection); + } + d->setupBirdEyeView(); +} + +void DocumentView::finishOpenUrl() +{ + disconnect(d->mDocument.data(), SIGNAL(kindDetermined(KUrl)), + this, SLOT(finishOpenUrl())); + GV_RETURN_IF_FAIL(d->mDocument->loadingState() >= Document::KindDetermined); + + if (d->mDocument->loadingState() == Document::LoadingFailed) { + slotLoadingFailed(); + return; + } + createAdapterForDocument(); + + connect(d->mDocument.data(), SIGNAL(loadingFailed(KUrl)), + SLOT(slotLoadingFailed())); + d->mAdapter->setDocument(d->mDocument); + d->updateCaption(); +} + +void DocumentView::loadAdapterConfig() +{ + d->mAdapter->loadConfig(); +} + +RasterImageView* DocumentView::imageView() const +{ + return d->mAdapter->rasterImageView(); +} + +void DocumentView::slotCompleted() +{ + d->hideLoadingIndicator(); + d->updateCaption(); + d->updateZoomSnapValues(); + if (!d->mAdapter->zoomToFit()) { + qreal min = minimumZoom(); + if (d->mAdapter->zoom() < min) { + d->mAdapter->setZoom(min); + } + } + emit completed(); +} + +DocumentView::Setup DocumentView::setup() const +{ + Setup setup; + if (d->mAdapter->canZoom()) { + setup.valid = true; + setup.zoomToFit = zoomToFit(); + if (!setup.zoomToFit) { + setup.zoom = zoom(); + setup.position = position(); + } + } + return setup; +} + +void DocumentView::slotLoadingFailed() +{ + d->hideLoadingIndicator(); + MessageViewAdapter* adapter = new MessageViewAdapter; + adapter->setDocument(d->mDocument); + QString message = i18n("Loading %1 failed", d->mDocument->url().fileName()); + adapter->setErrorMessage(message, d->mDocument->errorString()); + d->setCurrentAdapter(adapter); + emit completed(); +} + +bool DocumentView::canZoom() const +{ + return d->mAdapter->canZoom(); +} + +void DocumentView::setZoomToFit(bool on) +{ + if (on == d->mAdapter->zoomToFit()) { + return; + } + d->mAdapter->setZoomToFit(on); + if (!on) { + d->mAdapter->setZoom(1.); + } +} + +bool DocumentView::zoomToFit() const +{ + return d->mAdapter->zoomToFit(); +} + +void DocumentView::zoomActualSize() +{ + d->uncheckZoomToFit(); + d->mAdapter->setZoom(1.); +} + +void DocumentView::zoomIn(const QPointF& center) +{ + qreal currentZoom = d->mAdapter->zoom(); + + Q_FOREACH(qreal zoom, d->mZoomSnapValues) { + if (zoom > currentZoom + REAL_DELTA) { + d->setZoom(zoom, center); + return; + } + } +} + +void DocumentView::zoomOut(const QPointF& center) +{ + qreal currentZoom = d->mAdapter->zoom(); + + QListIterator it(d->mZoomSnapValues); + it.toBack(); + while (it.hasPrevious()) { + qreal zoom = it.previous(); + if (zoom < currentZoom - REAL_DELTA) { + d->setZoom(zoom, center); + return; + } + } +} + +void DocumentView::slotZoomChanged(qreal zoom) +{ + d->updateCaption(); + zoomChanged(zoom); +} + +void DocumentView::setZoom(qreal zoom) +{ + d->setZoom(zoom); +} + +qreal DocumentView::zoom() const +{ + return d->mAdapter->zoom(); +} + +void DocumentView::wheelEvent(QGraphicsSceneWheelEvent* event) +{ + if (d->mAdapter->canZoom() && event->modifiers() & Qt::ControlModifier) { + // Ctrl + wheel => zoom in or out + if (event->delta() > 0) { + zoomIn(event->pos()); + } else { + zoomOut(event->pos()); + } + return; + } + if (GwenviewConfig::mouseWheelBehavior() == MouseWheelBehavior::Browse + && event->modifiers() == Qt::NoModifier) { + // Browse with mouse wheel + if (event->delta() > 0) { + previousImageRequested(); + } else { + nextImageRequested(); + } + return; + } + // Scroll + qreal dx = 0; + // 16 = pixels for one line + // 120: see QWheelEvent::delta() doc + qreal dy = -qApp->wheelScrollLines() * 16 * event->delta() / 120; + if (event->orientation() == Qt::Horizontal) { + qSwap(dx, dy); + } + d->mAdapter->setScrollPos(d->mAdapter->scrollPos() + QPointF(dx, dy)); +} + +void DocumentView::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) +{ + // Filter out context menu if Ctrl is down to avoid showing it when + // zooming out with Ctrl + Right button + if (event->modifiers() != Qt::ControlModifier) { + contextMenuRequested(); + } +} + +void DocumentView::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) +{ + QRectF visibleRect = mapRectFromItem(d->mAdapter->widget(), d->mAdapter->visibleDocumentRect()); + if (d->mEraseBorders) { + QRegion borders = QRegion(boundingRect().toRect()) + - QRegion(visibleRect.toRect()); + Q_FOREACH(const QRect& rect, borders.rects()) { + painter->eraseRect(rect); + } + } + + if (d->mCompareMode && d->mCurrent) { + painter->save(); + painter->setBrush(Qt::NoBrush); + painter->setPen(QPen(palette().highlight().color(), 2)); + painter->setRenderHint(QPainter::Antialiasing); + QRectF selectionRect = visibleRect.adjusted(-2, -2, 2, 2); + painter->drawRoundedRect(selectionRect, 3, 3); + painter->restore(); + } +} + +void DocumentView::slotBusyChanged(const KUrl&, bool busy) +{ + if (busy) { + d->showLoadingIndicator(); + } else { + d->hideLoadingIndicator(); + } +} + +qreal DocumentView::minimumZoom() const +{ + // There is no point zooming out less than zoomToFit, but make sure it does + // not get too small either + return qBound(qreal(0.001), d->mAdapter->computeZoomToFit(), qreal(1.)); +} + +void DocumentView::setCompareMode(bool compare) +{ + d->mCompareMode = compare; + d->resizeAdapterWidget(); + if (compare) { + d->mHud->show(); + d->mHud->setZValue(1); + } else { + d->mHud->hide(); + } +} + +void DocumentView::setCurrent(bool value) +{ + d->mCurrent = value; + if (value) { + d->mAdapter->widget()->setFocus(); + } + update(); +} + +bool DocumentView::isCurrent() const +{ + return d->mCurrent; +} + +QPoint DocumentView::position() const +{ + return d->mAdapter->scrollPos().toPoint(); +} + +void DocumentView::setPosition(const QPoint& pos) +{ + d->mAdapter->setScrollPos(pos); +} + +Document::Ptr DocumentView::document() const +{ + return d->mDocument; +} + +KUrl DocumentView::url() const +{ + Document::Ptr doc = d->mDocument; + return doc ? doc->url() : KUrl(); +} + +void DocumentView::emitHudDeselectClicked() +{ + hudDeselectClicked(this); +} + +void DocumentView::emitHudTrashClicked() +{ + hudTrashClicked(this); +} + +void DocumentView::emitFocused() +{ + focused(this); +} + +void DocumentView::setGeometry(const QRectF& rect) +{ + QGraphicsWidget::setGeometry(rect); + d->resizeAdapterWidget(); + if (d->mBirdEyeView) { + d->mBirdEyeView->slotZoomOrSizeChanged(); + } +} + +void DocumentView::moveTo(const QRect& rect) +{ + if (d->mMoveAnimation) { + d->mMoveAnimation.data()->setEndValue(rect); + } else { + setGeometry(rect); + } +} + +void DocumentView::moveToAnimated(const QRect& rect) +{ + QPropertyAnimation* anim = new QPropertyAnimation(this, "geometry"); + anim->setStartValue(geometry()); + anim->setEndValue(rect); + anim->setDuration(DocumentView::AnimDuration); + connect(anim, SIGNAL(finished()), SIGNAL(isAnimatedChanged())); + d->mMoveAnimation = anim; + isAnimatedChanged(); + anim->start(QAbstractAnimation::DeleteWhenStopped); +} + +QPropertyAnimation* DocumentView::fadeIn() +{ + d->fadeTo(1); + return d->mFadeAnimation.data(); +} + +void DocumentView::fadeOut() +{ + d->fadeTo(0); +} + +void DocumentView::slotFadeInFinished() +{ + fadeInFinished(this); +} + +bool DocumentView::isAnimated() const +{ + return d->mMoveAnimation || d->mFadeAnimation; +} + +bool DocumentView::sceneEventFilter(QGraphicsItem*, QEvent* event) +{ + if (event->type() == QEvent::GraphicsSceneMousePress) { + QMetaObject::invokeMethod(this, "emitFocused", Qt::QueuedConnection); + } else if (event->type() == QEvent::GraphicsSceneHoverMove) { + if (d->mBirdEyeView) { + d->mBirdEyeView->onMouseMoved(); + } + } + return false; +} + +AbstractRasterImageViewTool* DocumentView::currentTool() const +{ + return imageView() ? imageView()->currentTool() : 0; +} + +int DocumentView::sortKey() const +{ + return d->mSortKey; +} + +void DocumentView::setSortKey(int sortKey) +{ + d->mSortKey = sortKey; +} + +void DocumentView::setEraseBorders(bool value) +{ + d->mEraseBorders = value; +} + +void DocumentView::hideAndDeleteLater() +{ + hide(); + deleteLater(); +} + +} // namespace diff --git a/gwenview/lib/documentview/documentview.h b/gwenview/lib/documentview/documentview.h new file mode 100644 index 00000000..f0bb1dcd --- /dev/null +++ b/gwenview/lib/documentview/documentview.h @@ -0,0 +1,227 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENTVIEW_H +#define DOCUMENTVIEW_H + +#include + +// Qt +#include + +// KDE +#include + +// Local +#include + +class QPropertyAnimation; +class KUrl; + +namespace Gwenview +{ + +class AbstractRasterImageViewTool; +class RasterImageView; +class SlideShow; +class ZoomWidget; + +struct DocumentViewPrivate; + +/** + * This widget can display various documents, using an instance of + * AbstractDocumentViewAdapter + */ +class GWENVIEWLIB_EXPORT DocumentView : public QGraphicsWidget +{ + Q_OBJECT + Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged) + Q_PROPERTY(bool zoomToFit READ zoomToFit WRITE setZoomToFit NOTIFY zoomToFitChanged) + Q_PROPERTY(QPoint position READ position WRITE setPosition NOTIFY positionChanged) +public: + static const int MaximumZoom; + static const int AnimDuration; + + struct Setup { + Setup() + : valid(false) + , zoomToFit(false) + , zoom(0) + {} + bool valid:1; + bool zoomToFit:1; + qreal zoom; + QPointF position; + }; + + enum AnimationMethod { + NoAnimation, + SoftwareAnimation, + GLAnimation + }; + + /** + * Create a new view attached to scene. We need the scene to be able to + * install scene event filters. + */ + DocumentView(QGraphicsScene* scene); + ~DocumentView(); + + Document::Ptr document() const; + + KUrl url() const; + + void openUrl(const KUrl&, const Setup&); + + Setup setup() const; + + /** + * Tells the current adapter to load its config. Used when the user changed + * the config while the view was visible. + */ + void loadAdapterConfig(); + + bool canZoom() const; + + qreal minimumZoom() const; + + qreal zoom() const; + + bool isCurrent() const; + + void setCurrent(bool); + + void setCompareMode(bool); + + bool zoomToFit() const; + + QPoint position() const; + + /** + * Returns the RasterImageView of the current adapter, if it has one + */ + RasterImageView* imageView() const; + + AbstractRasterImageViewTool* currentTool() const; + + void moveTo(const QRect&); + void moveToAnimated(const QRect&); + QPropertyAnimation* fadeIn(); + void fadeOut(); + void fakeFadeOut(); + + void setGeometry(const QRectF& rect); // reimp + + int sortKey() const; + void setSortKey(int sortKey); + + /** + * If true, areas around the document will be painted with the default brush. + * If false they will be kept transparent. + */ + void setEraseBorders(bool); + + bool isAnimated() const; + +public Q_SLOTS: + void setZoom(qreal); + + void setZoomToFit(bool); + + void setPosition(const QPoint&); + + void hideAndDeleteLater(); + +Q_SIGNALS: + /** + * Emitted when the part has finished loading + */ + void completed(); + + void previousImageRequested(); + + void nextImageRequested(); + + void captionUpdateRequested(const QString&); + + void toggleFullScreenRequested(); + + void videoFinished(); + + void minimumZoomChanged(qreal); + + void zoomChanged(qreal); + + void adapterChanged(); + + void focused(DocumentView*); + + void zoomToFitChanged(bool); + + void positionChanged(); + + void hudTrashClicked(DocumentView*); + void hudDeselectClicked(DocumentView*); + + void fadeInFinished(DocumentView*); + + void contextMenuRequested(); + + void currentToolChanged(AbstractRasterImageViewTool*); + + void isAnimatedChanged(); + +protected: + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); + + void wheelEvent(QGraphicsSceneWheelEvent* event); + void contextMenuEvent(QGraphicsSceneContextMenuEvent* event); + bool sceneEventFilter(QGraphicsItem*, QEvent*); + +private Q_SLOTS: + void finishOpenUrl(); + void slotCompleted(); + void slotLoadingFailed(); + + void zoomActualSize(); + + void zoomIn(const QPointF& center = QPointF(-1, -1)); + void zoomOut(const QPointF& center = QPointF(-1, -1)); + + void slotZoomChanged(qreal); + + void slotBusyChanged(const KUrl&, bool); + + void emitHudTrashClicked(); + void emitHudDeselectClicked(); + void emitFocused(); + + void slotFadeInFinished(); + +private: + friend struct DocumentViewPrivate; + DocumentViewPrivate* const d; + + void createAdapterForDocument(); +}; + +} // namespace + +#endif /* DOCUMENTVIEW_H */ diff --git a/gwenview/lib/documentview/documentviewcontainer.cpp b/gwenview/lib/documentview/documentviewcontainer.cpp new file mode 100644 index 00000000..4fd7637a --- /dev/null +++ b/gwenview/lib/documentview/documentviewcontainer.cpp @@ -0,0 +1,339 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "documentviewcontainer.moc" + +// Local +#include +#include +#include +#include + +// KDE +#include +#include + +// Qt +#include +#include +#include +#include +#include + +// libc +#include + +namespace Gwenview +{ + +typedef QSet DocumentViewSet; +typedef QHash SetupForUrl; + +struct DocumentViewContainerPrivate +{ + DocumentViewContainer* q; + QGraphicsScene* mScene; + SetupForUrl mSetupForUrl; + DocumentViewSet mViews; + DocumentViewSet mAddedViews; + DocumentViewSet mRemovedViews; + QTimer* mLayoutUpdateTimer; + + void scheduleLayoutUpdate() + { + mLayoutUpdateTimer->start(); + } + + /** + * Remove view from set, move it to mRemovedViews so that it is later + * deleted. + */ + bool removeFromSet(DocumentView* view, DocumentViewSet* set) + { + DocumentViewSet::Iterator it = set->find(view); + if (it == set->end()) { + return false; + } + set->erase(it); + mRemovedViews << view; + scheduleLayoutUpdate(); + return true; + } + + void resetSet(DocumentViewSet* set) + { + qDeleteAll(*set); + set->clear(); + } +}; + +DocumentViewContainer::DocumentViewContainer(QWidget* parent) +: QGraphicsView(parent) +, d(new DocumentViewContainerPrivate) +{ + d->q = this; + d->mScene = new QGraphicsScene(this); + if (GwenviewConfig::animationMethod() == DocumentView::GLAnimation) { + QGLWidget* glWidget = new QGLWidget; + if (glWidget->isValid()) { + setViewport(glWidget); + } else { + kWarning() << "Failed to initialize OpenGL support!"; + delete glWidget; + } + } + setScene(d->mScene); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + setFrameStyle(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + d->mLayoutUpdateTimer = new QTimer(this); + d->mLayoutUpdateTimer->setInterval(0); + d->mLayoutUpdateTimer->setSingleShot(true); + connect(d->mLayoutUpdateTimer, SIGNAL(timeout()), SLOT(updateLayout())); + + connect(GwenviewConfig::self(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); +} + +DocumentViewContainer::~DocumentViewContainer() +{ + delete d; +} + +DocumentView* DocumentViewContainer::createView() +{ + DocumentView* view = new DocumentView(d->mScene); + d->mAddedViews << view; + view->show(); + connect(view, SIGNAL(fadeInFinished(DocumentView*)), + SLOT(slotFadeInFinished(DocumentView*))); + d->scheduleLayoutUpdate(); + return view; +} + +void DocumentViewContainer::deleteView(DocumentView* view) +{ + if (d->removeFromSet(view, &d->mViews)) { + return; + } + d->removeFromSet(view, &d->mAddedViews); +} + +DocumentView::Setup DocumentViewContainer::savedSetup(const KUrl& url) const +{ + return d->mSetupForUrl.value(url); +} + +void DocumentViewContainer::updateSetup(DocumentView* view) +{ + d->mSetupForUrl[view->url()] = view->setup(); +} + +void DocumentViewContainer::reset() +{ + d->resetSet(&d->mViews); + d->resetSet(&d->mAddedViews); + d->resetSet(&d->mRemovedViews); +} + +void DocumentViewContainer::showEvent(QShowEvent* event) +{ + QWidget::showEvent(event); + updateLayout(); +} + +void DocumentViewContainer::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + d->mScene->setSceneRect(rect()); + updateLayout(); +} + +static bool viewLessThan(DocumentView* v1, DocumentView* v2) +{ + return v1->sortKey() < v2->sortKey(); +} + +void DocumentViewContainer::updateLayout() +{ + // Stop update timer: this is useful if updateLayout() is called directly + // and not through scheduleLayoutUpdate() + d->mLayoutUpdateTimer->stop(); + QList views = (d->mViews | d->mAddedViews).toList(); + qSort(views.begin(), views.end(), viewLessThan); + + bool animated = GwenviewConfig::animationMethod() != DocumentView::NoAnimation; + bool crossFade = d->mAddedViews.count() == 1 && d->mRemovedViews.count() == 1; + + if (animated && crossFade) { + DocumentView* oldView = *d->mRemovedViews.begin(); + DocumentView* newView = *d->mAddedViews.begin(); + + newView->setGeometry(rect()); + newView->setEraseBorders(true); + QPropertyAnimation* anim = newView->fadeIn(); + + oldView->setZValue(-1); + connect(anim, SIGNAL(finished()), oldView, SLOT(hideAndDeleteLater())); + d->mRemovedViews.clear(); + + return; + } + + if (!views.isEmpty()) { + // Compute column count + int colCount; + switch (views.count()) { + case 1: + colCount = 1; + break; + case 2: + colCount = 2; + break; + case 3: + colCount = 3; + break; + case 4: + colCount = 2; + break; + case 5: + colCount = 3; + break; + case 6: + colCount = 3; + break; + default: + colCount = 3; + break; + } + + int rowCount = qCeil(views.count() / qreal(colCount)); + Q_ASSERT(rowCount > 0); + int viewWidth = width() / colCount; + int viewHeight = height() / rowCount; + + int col = 0; + int row = 0; + + Q_FOREACH(DocumentView * view, views) { + QRect rect; + rect.setLeft(col * viewWidth); + rect.setTop(row * viewHeight); + rect.setWidth(viewWidth); + rect.setHeight(viewHeight); + + if (animated) { + if (d->mViews.contains(view)) { + if (rect != view->geometry()) { + if (d->mAddedViews.isEmpty() && d->mRemovedViews.isEmpty()) { + // View moves because of a resize + view->moveTo(rect); + } else { + // View moves because the number of views changed, + // animate the change + view->moveToAnimated(rect); + } + } + } else { + view->setGeometry(rect); + view->fadeIn(); + } + } else { + // Not animated, set final geometry and opacity now + view->setGeometry(rect); + view->setOpacity(1); + } + + ++col; + if (col == colCount) { + col = 0; + ++row; + } + } + } + + // Handle removed views + if (animated) { + Q_FOREACH(DocumentView* view, d->mRemovedViews) { + view->fadeOut(); + QTimer::singleShot(DocumentView::AnimDuration, view, SLOT(deleteLater())); + } + } else { + Q_FOREACH(DocumentView* view, d->mRemovedViews) { + view->deleteLater(); + } + QMetaObject::invokeMethod(this, "pretendFadeInFinished", Qt::QueuedConnection); + } + d->mRemovedViews.clear(); +} + +void DocumentViewContainer::pretendFadeInFinished() +{ + // Animations are disabled. Pretend all fade ins are finished so that added + // views are moved to mViews + Q_FOREACH(DocumentView* view, d->mAddedViews) { + slotFadeInFinished(view); + } +} + +void DocumentViewContainer::slotFadeInFinished(DocumentView* view) +{ + if (!d->mAddedViews.contains(view)) { + // This can happen if user goes to next image then quickly goes to the + // next one before the animation is finished. + return; + } + d->mAddedViews.remove(view); + d->mViews.insert(view); + view->setEraseBorders(false); +} + +void DocumentViewContainer::slotConfigChanged() +{ + bool currentlyGL = qobject_cast(viewport()); + bool wantGL = GwenviewConfig::animationMethod() == DocumentView::GLAnimation; + if (currentlyGL != wantGL) { + setViewport(wantGL ? new QGLWidget() : new QWidget()); + } +} + +void DocumentViewContainer::showMessageWidget(QGraphicsWidget* widget, Qt::Alignment align) +{ + DocumentView* view = 0; + if (d->mViews.isEmpty()) { + GV_RETURN_IF_FAIL(!d->mAddedViews.isEmpty()); + view = *d->mAddedViews.begin(); + } else { + view = *d->mViews.begin(); + } + GV_RETURN_IF_FAIL(view); + + widget->setParentItem(view); + GraphicsWidgetFloater* floater = new GraphicsWidgetFloater(view); + floater->setChildWidget(widget); + floater->setAlignment(align); + widget->show(); + widget->setZValue(1); +} + +} // namespace diff --git a/gwenview/lib/documentview/documentviewcontainer.h b/gwenview/lib/documentview/documentviewcontainer.h new file mode 100644 index 00000000..67dff4ed --- /dev/null +++ b/gwenview/lib/documentview/documentviewcontainer.h @@ -0,0 +1,99 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENTVIEWCONTAINER_H +#define DOCUMENTVIEWCONTAINER_H + +#include + +// Local +#include + +// KDE +#include + +// Qt +#include + +namespace Gwenview +{ + +class DocumentView; + +struct DocumentViewContainerPrivate; +/** + * A container for DocumentViews which will arrange them to make best use of + * available space. + */ +class GWENVIEWLIB_EXPORT DocumentViewContainer : public QGraphicsView +{ + Q_OBJECT +public: + DocumentViewContainer(QWidget* parent = 0); + ~DocumentViewContainer(); + + /** + * Create a DocumentView in the DocumentViewContainer scene + */ + DocumentView* createView(); + + /** + * Delete view. Note that the view will first be faded to black before + * being destroyed. + */ + void deleteView(DocumentView* view); + + /** + * Immediately delete all views + */ + void reset(); + + /** + * Returns saved Setup configuration for a previously viewed document + */ + DocumentView::Setup savedSetup(const KUrl& url) const; + + /** + * Updates setupForUrl hash with latest setup values + */ + void updateSetup(DocumentView* view); + + void showMessageWidget(QGraphicsWidget*, Qt::Alignment); + +public Q_SLOTS: + void updateLayout(); + +protected: + void showEvent(QShowEvent*); + void resizeEvent(QResizeEvent*); + +private: + friend class ViewItem; + DocumentViewContainerPrivate* const d; + +private Q_SLOTS: + void slotFadeInFinished(DocumentView*); + void pretendFadeInFinished(); + void slotConfigChanged(); +}; + +} // namespace + +#endif /* DOCUMENTVIEWCONTAINER_H */ diff --git a/gwenview/lib/documentview/documentviewcontroller.cpp b/gwenview/lib/documentview/documentviewcontroller.cpp new file mode 100644 index 00000000..abf0b277 --- /dev/null +++ b/gwenview/lib/documentview/documentviewcontroller.cpp @@ -0,0 +1,271 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "documentviewcontroller.moc" + +// Local +#include "abstractdocumentviewadapter.h" +#include "documentview.h" +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include + +// Qt +#include +#include +#include + +namespace Gwenview +{ + +/** + * A simple container which: + * - Horizontally center the tool widget + * - Provide a darker background + */ +class ToolContainerContent : public QWidget +{ +public: + ToolContainerContent(QWidget* parent = 0) + : QWidget(parent) + , mLayout(new QHBoxLayout(this)) + { + mLayout->setMargin(0); + setAutoFillBackground(true); + QPalette pal = palette(); + pal.setColor(QPalette::Window, pal.color(QPalette::Window).dark(120)); + setPalette(pal); + } + + void setToolWidget(QWidget* widget) + { + mLayout->addWidget(widget, 0, Qt::AlignCenter); + setFixedHeight(widget->sizeHint().height()); + } + +private: + QHBoxLayout* mLayout; +}; + +struct DocumentViewControllerPrivate +{ + DocumentViewController* q; + KActionCollection* mActionCollection; + DocumentView* mView; + ZoomWidget* mZoomWidget; + SlideContainer* mToolContainer; + ToolContainerContent* mToolContainerContent; + + KAction* mZoomToFitAction; + KAction* mActualSizeAction; + KAction* mZoomInAction; + KAction* mZoomOutAction; + QList mActions; + + void setupActions() + { + KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), mActionCollection); + + mZoomToFitAction = view->addAction("view_zoom_to_fit"); + mZoomToFitAction->setShortcut(Qt::Key_F); + mZoomToFitAction->setCheckable(true); + mZoomToFitAction->setChecked(true); + mZoomToFitAction->setText(i18n("Zoom to Fit")); + mZoomToFitAction->setIcon(KIcon("zoom-fit-best")); + mZoomToFitAction->setIconText(i18nc("@action:button Zoom to fit, shown in status bar, keep it short please", "Fit")); + + mActualSizeAction = view->addAction(KStandardAction::ActualSize); + mActualSizeAction->setIcon(KIcon("zoom-original")); + mActualSizeAction->setIconText(i18nc("@action:button Zoom to original size, shown in status bar, keep it short please", "100%")); + + mZoomInAction = view->addAction(KStandardAction::ZoomIn); + mZoomOutAction = view->addAction(KStandardAction::ZoomOut); + + mActions << mZoomToFitAction << mActualSizeAction << mZoomInAction << mZoomOutAction; + } + + void connectZoomWidget() + { + if (!mZoomWidget || !mView) { + return; + } + + QObject::connect(mZoomWidget, SIGNAL(zoomChanged(qreal)), + mView, SLOT(setZoom(qreal))); + + QObject::connect(mView, SIGNAL(minimumZoomChanged(qreal)), + mZoomWidget, SLOT(setMinimumZoom(qreal))); + QObject::connect(mView, SIGNAL(zoomChanged(qreal)), + mZoomWidget, SLOT(setZoom(qreal))); + + mZoomWidget->setMinimumZoom(mView->minimumZoom()); + mZoomWidget->setZoom(mView->zoom()); + } + + void updateZoomWidgetVisibility() + { + if (!mZoomWidget) { + return; + } + mZoomWidget->setVisible(mView && mView->canZoom()); + } + + void updateActions() + { + const bool enabled = mView && mView->isVisible() && mView->canZoom(); + Q_FOREACH(QAction * action, mActions) { + action->setEnabled(enabled); + } + } +}; + +DocumentViewController::DocumentViewController(KActionCollection* actionCollection, QObject* parent) +: QObject(parent) +, d(new DocumentViewControllerPrivate) +{ + d->q = this; + d->mActionCollection = actionCollection; + d->mView = 0; + d->mZoomWidget = 0; + d->mToolContainer = 0; + d->mToolContainerContent = new ToolContainerContent; + + d->setupActions(); +} + +DocumentViewController::~DocumentViewController() +{ + delete d; +} + +void DocumentViewController::setView(DocumentView* view) +{ + // Forget old view + if (d->mView) { + disconnect(d->mView, 0, this, 0); + Q_FOREACH(QAction * action, d->mActions) { + disconnect(action, 0, d->mView, 0); + } + disconnect(d->mZoomWidget, 0, d->mView, 0); + } + + // Connect new view + d->mView = view; + if (!d->mView) { + return; + } + connect(d->mView, SIGNAL(adapterChanged()), + SLOT(slotAdapterChanged())); + connect(d->mView, SIGNAL(zoomToFitChanged(bool)), + SLOT(updateZoomToFitActionFromView())); + connect(d->mView, SIGNAL(currentToolChanged(AbstractRasterImageViewTool*)), + SLOT(updateTool())); + + connect(d->mZoomToFitAction, SIGNAL(toggled(bool)), + d->mView, SLOT(setZoomToFit(bool))); + connect(d->mActualSizeAction, SIGNAL(triggered()), + d->mView, SLOT(zoomActualSize())); + connect(d->mZoomInAction, SIGNAL(triggered()), + d->mView, SLOT(zoomIn())); + connect(d->mZoomOutAction, SIGNAL(triggered()), + d->mView, SLOT(zoomOut())); + + d->updateActions(); + updateZoomToFitActionFromView(); + updateTool(); + + // Sync zoom widget + d->connectZoomWidget(); + d->updateZoomWidgetVisibility(); +} + +DocumentView* DocumentViewController::view() const +{ + return d->mView; +} + +void DocumentViewController::setZoomWidget(ZoomWidget* widget) +{ + d->mZoomWidget = widget; + + d->mZoomWidget->setActions( + d->mZoomToFitAction, + d->mActualSizeAction, + d->mZoomInAction, + d->mZoomOutAction + ); + + d->mZoomWidget->setMaximumZoom(qreal(DocumentView::MaximumZoom)); + + d->connectZoomWidget(); + d->updateZoomWidgetVisibility(); +} + +ZoomWidget* DocumentViewController::zoomWidget() const +{ + return d->mZoomWidget; +} + +void DocumentViewController::slotAdapterChanged() +{ + d->updateActions(); + d->updateZoomWidgetVisibility(); +} + +void DocumentViewController::updateZoomToFitActionFromView() +{ + SignalBlocker blocker(d->mZoomToFitAction); + d->mZoomToFitAction->setChecked(d->mView->zoomToFit()); +} + +void DocumentViewController::updateTool() +{ + if (!d->mToolContainer) { + return; + } + AbstractRasterImageViewTool* tool = d->mView->currentTool(); + if (tool && tool->widget()) { + // Use a QueuedConnection to ensure the size of the view has been + // updated by the time the slot is called. + connect(d->mToolContainer, SIGNAL(slidedIn()), + tool, SLOT(onWidgetSlidedIn()), + Qt::QueuedConnection); + d->mToolContainerContent->setToolWidget(tool->widget()); + d->mToolContainer->slideIn(); + } else { + d->mToolContainer->slideOut(); + } +} + +void DocumentViewController::setToolContainer(SlideContainer* container) +{ + d->mToolContainer = container; + container->setContent(d->mToolContainerContent); +} + +} // namespace diff --git a/gwenview/lib/documentview/documentviewcontroller.h b/gwenview/lib/documentview/documentviewcontroller.h new file mode 100644 index 00000000..26e8f9c6 --- /dev/null +++ b/gwenview/lib/documentview/documentviewcontroller.h @@ -0,0 +1,74 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENTVIEWCONTROLLER_H +#define DOCUMENTVIEWCONTROLLER_H + +#include + +// Local + +// KDE + +// Qt +#include + +class KActionCollection; + +namespace Gwenview +{ + +class DocumentView; +class SlideContainer; +class ZoomWidget; + +struct DocumentViewControllerPrivate; + +/** + * Handles all DocumentView specific actions like zooming. Calls the + * corresponding code on its view, if any. + */ +class GWENVIEWLIB_EXPORT DocumentViewController : public QObject +{ + Q_OBJECT +public: + explicit DocumentViewController(KActionCollection*, QObject* parent = 0); + ~DocumentViewController(); + + DocumentView* view() const; + ZoomWidget* zoomWidget() const; + + void setView(DocumentView*); + void setZoomWidget(ZoomWidget* widget); + + void setToolContainer(SlideContainer* container); + +private Q_SLOTS: + void slotAdapterChanged(); + void updateZoomToFitActionFromView(); + void updateTool(); + +private: + DocumentViewControllerPrivate* const d; +}; + +} // namespace + +#endif /* DOCUMENTVIEWCONTROLLER_H */ diff --git a/gwenview/lib/documentview/documentviewsynchronizer.cpp b/gwenview/lib/documentview/documentviewsynchronizer.cpp new file mode 100644 index 00000000..76cb7675 --- /dev/null +++ b/gwenview/lib/documentview/documentviewsynchronizer.cpp @@ -0,0 +1,142 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "documentviewsynchronizer.moc" + +// Local +#include + +// KDE + +// Qt + +namespace Gwenview +{ + +struct DocumentViewSynchronizerPrivate +{ + DocumentViewSynchronizer* q; + const QList* mViews; + QWeakPointer mCurrentView; + bool mActive; + QPoint mOldPosition; + + DocumentViewSynchronizerPrivate(const QList* views) + : mViews(views) + {} + + void updateConnections() + { + if (!mCurrentView || !mActive) { + return; + } + + QObject::connect(mCurrentView.data(), SIGNAL(zoomChanged(qreal)), + q, SLOT(setZoom(qreal))); + QObject::connect(mCurrentView.data(), SIGNAL(zoomToFitChanged(bool)), + q, SLOT(setZoomToFit(bool))); + QObject::connect(mCurrentView.data(), SIGNAL(positionChanged()), + q, SLOT(updatePosition())); + + Q_FOREACH(DocumentView* view, *mViews) { + if (view == mCurrentView.data()) { + continue; + } + view->setZoom(mCurrentView.data()->zoom()); + view->setZoomToFit(mCurrentView.data()->zoomToFit()); + } + } + + void updateOldPosition() + { + if (!mCurrentView || !mActive) { + return; + } + mOldPosition = mCurrentView.data()->position(); + } +}; + +DocumentViewSynchronizer::DocumentViewSynchronizer(const QList* views, QObject* parent) +: QObject(parent) +, d(new DocumentViewSynchronizerPrivate(views)) +{ + d->q = this; + d->mActive = false; +} + +DocumentViewSynchronizer::~DocumentViewSynchronizer() +{ + delete d; +} + +void DocumentViewSynchronizer::setCurrentView(DocumentView* view) +{ + if (d->mCurrentView) { + disconnect(d->mCurrentView.data(), 0, this, 0); + } + d->mCurrentView = view; + d->updateOldPosition(); + d->updateConnections(); +} + +void DocumentViewSynchronizer::setActive(bool active) +{ + d->mActive = active; + d->updateOldPosition(); + d->updateConnections(); +} + +void DocumentViewSynchronizer::setZoom(qreal zoom) +{ + Q_FOREACH(DocumentView* view, *d->mViews) { + if (view == d->mCurrentView.data()) { + continue; + } + view->setZoom(zoom); + } + d->updateOldPosition(); +} + +void DocumentViewSynchronizer::setZoomToFit(bool fit) +{ + Q_FOREACH(DocumentView* view, *d->mViews) { + if (view == d->mCurrentView.data()) { + continue; + } + view->setZoomToFit(fit); + } + d->updateOldPosition(); +} + +void DocumentViewSynchronizer::updatePosition() +{ + QPoint pos = d->mCurrentView.data()->position(); + QPoint delta = pos - d->mOldPosition; + d->mOldPosition = pos; + Q_FOREACH(DocumentView* view, *d->mViews) { + if (view == d->mCurrentView.data()) { + continue; + } + view->setPosition(view->position() + delta); + } +} + +} // namespace diff --git a/gwenview/lib/documentview/documentviewsynchronizer.h b/gwenview/lib/documentview/documentviewsynchronizer.h new file mode 100644 index 00000000..0ade4ef2 --- /dev/null +++ b/gwenview/lib/documentview/documentviewsynchronizer.h @@ -0,0 +1,69 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENTVIEWSYNCHRONIZER_H +#define DOCUMENTVIEWSYNCHRONIZER_H + +#include + +// Local + +// KDE + +// Qt +#include + +class QPoint; + +namespace Gwenview +{ + +class DocumentView; + +struct DocumentViewSynchronizerPrivate; +/** + * A class to synchronize zoom and scroll of DocumentViews + */ +class GWENVIEWLIB_EXPORT DocumentViewSynchronizer : public QObject +{ + Q_OBJECT +public: + // We pass a pointer to the view list because we don't want to maintain + // a copy of the list itself + explicit DocumentViewSynchronizer(const QList* views, QObject* parent = 0); + ~DocumentViewSynchronizer(); + + void setCurrentView(DocumentView* view); + +public Q_SLOTS: + void setActive(bool); + +private Q_SLOTS: + void setZoom(qreal zoom); + void setZoomToFit(bool); + void updatePosition(); + +private: + DocumentViewSynchronizerPrivate* const d; +}; + +} // namespace + +#endif /* DOCUMENTVIEWSYNCHRONIZER_H */ diff --git a/gwenview/lib/documentview/loadingindicator.cpp b/gwenview/lib/documentview/loadingindicator.cpp new file mode 100644 index 00000000..bc0e15d9 --- /dev/null +++ b/gwenview/lib/documentview/loadingindicator.cpp @@ -0,0 +1,96 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "loadingindicator.moc" + +// Local + +// KDE +#include +#include + +// Qt +#include +#include + +namespace Gwenview +{ + +struct LoadingIndicatorPrivate +{ + LoadingIndicator* q; + KPixmapSequence mSequence; + int mIndex; + QTimer* mTimer; + + LoadingIndicatorPrivate(LoadingIndicator* qq) + : q(qq) + , mSequence("process-working", 22) + , mIndex(0) + , mTimer(new QTimer(qq)) + { + mTimer->setInterval(100); + QObject::connect(mTimer, SIGNAL(timeout()), q, SLOT(showNextFrame())); + } +}; + +LoadingIndicator::LoadingIndicator(QGraphicsItem* parent) +: QGraphicsWidget(parent) +, d(new LoadingIndicatorPrivate(this)) +{ +} + +LoadingIndicator::~LoadingIndicator() +{ + delete d; +} + +QRectF LoadingIndicator::boundingRect() const +{ + return QRectF(QPointF(0, 0), d->mSequence.frameSize()); +} + +void LoadingIndicator::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) +{ + painter->drawPixmap(0, 0, d->mSequence.frameAt(d->mIndex)); +} + +void LoadingIndicator::showNextFrame() +{ + if (d->mSequence.frameCount() > 0) { + d->mIndex = (d->mIndex + 1) % d->mSequence.frameCount(); + update(); + } +} + +QVariant LoadingIndicator::itemChange(GraphicsItemChange change, const QVariant& value) +{ + if (change == QGraphicsItem::ItemVisibleHasChanged) { + if (value.toBool()) { + d->mTimer->start(); + } else { + d->mTimer->stop(); + } + } + return QGraphicsWidget::itemChange(change, value); +} + +} // namespace diff --git a/gwenview/lib/documentview/loadingindicator.h b/gwenview/lib/documentview/loadingindicator.h new file mode 100644 index 00000000..8cd6ce0c --- /dev/null +++ b/gwenview/lib/documentview/loadingindicator.h @@ -0,0 +1,64 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef LOADINGINDICATOR_H +#define LOADINGINDICATOR_H + +// Local + +// KDE + +// Qt +#include + +namespace Gwenview +{ + +struct LoadingIndicatorPrivate; +/** + * A graphics widget which shows a spinner + */ +class LoadingIndicator : public QGraphicsWidget +{ + Q_OBJECT +public: + LoadingIndicator(QGraphicsItem* parent = 0); + ~LoadingIndicator(); + + // reimp + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); + + // reimp + QRectF boundingRect() const; + +private Q_SLOTS: + void showNextFrame(); + +protected: + // reimp + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + +private: + LoadingIndicatorPrivate* const d; +}; + +} // namespace + +#endif /* LOADINGINDICATOR_H */ diff --git a/gwenview/lib/documentview/messageview.ui b/gwenview/lib/documentview/messageview.ui new file mode 100644 index 00000000..a7504c1b --- /dev/null +++ b/gwenview/lib/documentview/messageview.ui @@ -0,0 +1,101 @@ + + + MessageView + + + + 0 + 0 + 400 + 300 + + + + + + + Qt::Vertical + + + + 20 + 125 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 120 + 20 + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 120 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 125 + + + + + + + + + KMessageWidget + QFrame +
kmessagewidget.h
+ 1 +
+
+ + +
diff --git a/gwenview/lib/documentview/messageviewadapter.cpp b/gwenview/lib/documentview/messageviewadapter.cpp new file mode 100644 index 00000000..c42f4a47 --- /dev/null +++ b/gwenview/lib/documentview/messageviewadapter.cpp @@ -0,0 +1,130 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "messageviewadapter.moc" + +// Qt +#include + +// KDE +#include +#include + +// Local +#include +#include + +namespace Gwenview +{ + +struct MessageViewAdapterPrivate : Ui_MessageView +{ + Document::Ptr mDocument; +}; + +MessageViewAdapter::MessageViewAdapter() +: d(new MessageViewAdapterPrivate) +{ + QWidget* widget = new QWidget; + widget->installEventFilter(this); + d->setupUi(widget); + d->mMessageWidget->setCloseButtonVisible(false); + d->mMessageWidget->setWordWrap(true); + + setInfoMessage(i18n("No document selected")); + + widget->setAutoFillBackground(true); + widget->setBackgroundRole(QPalette::Base); + widget->setForegroundRole(QPalette::Text); + + QGraphicsProxyWidget* proxy = new QGraphicsProxyWidget; + proxy->setWidget(widget); + setWidget(proxy); +} + +MessageViewAdapter::~MessageViewAdapter() +{ + delete d; +} + +void MessageViewAdapter::setErrorMessage(const QString& main, const QString& detail) +{ + if (main.isEmpty()) { + d->mMessageWidget->hide(); + return; + } + d->mMessageWidget->show(); + d->mMessageWidget->setMessageType(KMessageWidget::Error); + QString message; + if (detail.isEmpty()) { + message = main; + } else { + message = QString("%1
%2").arg(main).arg(detail); + } + d->mMessageWidget->setText(message); +} + +void MessageViewAdapter::setInfoMessage(const QString& message) +{ + if (message.isEmpty()) { + d->mMessageWidget->hide(); + return; + } + d->mMessageWidget->show(); + d->mMessageWidget->setMessageType(KMessageWidget::Information); + d->mMessageWidget->setText(message); +} + +Document::Ptr MessageViewAdapter::document() const +{ + return d->mDocument; +} + +void MessageViewAdapter::setDocument(Document::Ptr doc) +{ + d->mDocument = doc; +} + +bool MessageViewAdapter::eventFilter(QObject*, QEvent* ev) +{ + if (ev->type() == QEvent::KeyPress) { + QKeyEvent* event = static_cast(ev); + if (event->modifiers() != Qt::NoModifier) { + return false; + } + + switch (event->key()) { + case Qt::Key_Left: + case Qt::Key_Up: + previousImageRequested(); + break; + case Qt::Key_Right: + case Qt::Key_Down: + nextImageRequested(); + break; + default: + break; + } + } + return false; +} + +} // namespace diff --git a/gwenview/lib/documentview/messageviewadapter.h b/gwenview/lib/documentview/messageviewadapter.h new file mode 100644 index 00000000..5bd2af41 --- /dev/null +++ b/gwenview/lib/documentview/messageviewadapter.h @@ -0,0 +1,64 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef MESSAGEVIEWADAPTER_H +#define MESSAGEVIEWADAPTER_H + +// Qt + +// KDE + +// Local +#include + +namespace Gwenview +{ + +struct MessageViewAdapterPrivate; +class MessageViewAdapter : public AbstractDocumentViewAdapter +{ + Q_OBJECT +public: + MessageViewAdapter(); + ~MessageViewAdapter(); + + virtual MimeTypeUtils::Kind kind() const + { + return MimeTypeUtils::KIND_UNKNOWN; + } + + virtual Document::Ptr document() const; + + virtual void setDocument(Document::Ptr); + + void setInfoMessage(const QString&); + + void setErrorMessage(const QString& main, const QString& detail = QString()); + +protected: + bool eventFilter(QObject*, QEvent*); + +private: + MessageViewAdapterPrivate* const d; +}; + +} // namespace + +#endif /* MESSAGEVIEWADAPTER_H */ diff --git a/gwenview/lib/documentview/rasterimageview.cpp b/gwenview/lib/documentview/rasterimageview.cpp new file mode 100644 index 00000000..61de460d --- /dev/null +++ b/gwenview/lib/documentview/rasterimageview.cpp @@ -0,0 +1,541 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "rasterimageview.moc" + +// Local +#include +#include +#include +#include + +// KDE +#include + +// Qt +#include +#include +#include +#include + +// LCMS2 +#include + + +namespace Gwenview +{ + +struct RasterImageViewPrivate +{ + RasterImageView* q; + ImageScaler* mScaler; + QPixmap mBackgroundTexture; + bool mEmittedCompleted; + + // Config + RasterImageView::AlphaBackgroundMode mAlphaBackgroundMode; + QColor mAlphaBackgroundColor; + bool mEnlargeSmallerImages; + // /Config + + bool mBufferIsEmpty; + QPixmap mCurrentBuffer; + // The alternate buffer is useful when scrolling: existing content is copied + // to mAlternateBuffer and buffers are swapped. This avoids allocating a new + // QPixmap every time the image is scrolled. + QPixmap mAlternateBuffer; + + QTimer* mUpdateTimer; + + QWeakPointer mTool; + + bool mApplyDisplayTransform; // Defaults to true. Can be set to false if there is no need or no way to apply color profile + cmsHTRANSFORM mDisplayTransform; + + void updateDisplayTransform(QImage::Format format) + { + GV_RETURN_IF_FAIL(format != QImage::Format_Invalid); + mApplyDisplayTransform = false; + if (mDisplayTransform) { + cmsDeleteTransform(mDisplayTransform); + } + mDisplayTransform = 0; + + Cms::Profile::Ptr profile = q->document()->cmsProfile(); + if (!profile) { + return; + } + Cms::Profile::Ptr monitorProfile = Cms::Profile::getMonitorProfile(); + if (!monitorProfile) { + kWarning() << "Could not get monitor color profile"; + return; + } + + cmsUInt32Number cmsFormat = 0; + switch (format) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + cmsFormat = TYPE_BGRA_8; + break; + default: + kWarning() << "This image has a color profile, but Gwenview can only apply color profile on RGB32 or ARGB32 images"; + return; + } + mDisplayTransform = cmsCreateTransform(profile->handle(), cmsFormat, + monitorProfile->handle(), cmsFormat, + INTENT_PERCEPTUAL, cmsFLAGS_BLACKPOINTCOMPENSATION); + mApplyDisplayTransform = true; + } + + void createBackgroundTexture() + { + mBackgroundTexture = QPixmap(32, 32); + QPainter painter(&mBackgroundTexture); + painter.fillRect(mBackgroundTexture.rect(), QColor(128, 128, 128)); + QColor light = QColor(192, 192, 192); + painter.fillRect(0, 0, 16, 16, light); + painter.fillRect(16, 16, 16, 16, light); + } + + void setupUpdateTimer() + { + mUpdateTimer = new QTimer(q); + mUpdateTimer->setInterval(500); + mUpdateTimer->setSingleShot(true); + QObject::connect(mUpdateTimer, SIGNAL(timeout()), + q, SLOT(updateBuffer())); + } + + void startAnimationIfNecessary() + { + if (q->document() && q->isVisible()) { + q->document()->startAnimation(); + } + } + + QRectF mapViewportToZoomedImage(const QRectF& viewportRect) const + { + return QRectF( + viewportRect.topLeft() - q->imageOffset() + q->scrollPos(), + viewportRect.size() + ); + } + + void setScalerRegionToVisibleRect() + { + QRectF rect = mapViewportToZoomedImage(q->boundingRect()); + mScaler->setDestinationRegion(QRegion(rect.toRect())); + } + + void resizeBuffer() + { + QSize size = q->visibleImageSize().toSize(); + if (size == mCurrentBuffer.size()) { + return; + } + if (!size.isValid()) { + mAlternateBuffer = QPixmap(); + mCurrentBuffer = QPixmap(); + return; + } + + mAlternateBuffer = QPixmap(size); + mAlternateBuffer.fill(Qt::transparent); + { + QPainter painter(&mAlternateBuffer); + painter.drawPixmap(0, 0, mCurrentBuffer); + } + qSwap(mAlternateBuffer, mCurrentBuffer); + + mAlternateBuffer = QPixmap(); + } + + void drawAlphaBackground(QPainter* painter, const QRect& viewportRect, const QPoint& zoomedImageTopLeft) + { + if (mAlphaBackgroundMode == RasterImageView::AlphaBackgroundCheckBoard) { + QPoint textureOffset( + zoomedImageTopLeft.x() % mBackgroundTexture.width(), + zoomedImageTopLeft.y() % mBackgroundTexture.height() + ); + painter->drawTiledPixmap( + viewportRect, + mBackgroundTexture, + textureOffset); + } else { + painter->fillRect(viewportRect, mAlphaBackgroundColor); + } + } +}; + +RasterImageView::RasterImageView(QGraphicsItem* parent) +: AbstractImageView(parent) +, d(new RasterImageViewPrivate) +{ + d->q = this; + d->mEmittedCompleted = false; + d->mApplyDisplayTransform = true; + d->mDisplayTransform = 0; + + d->mAlphaBackgroundMode = AlphaBackgroundCheckBoard; + d->mAlphaBackgroundColor = Qt::black; + d->mEnlargeSmallerImages = false; + + d->mBufferIsEmpty = true; + d->mScaler = new ImageScaler(this); + connect(d->mScaler, SIGNAL(scaledRect(int,int,QImage)), + SLOT(updateFromScaler(int,int,QImage))); + + d->createBackgroundTexture(); + d->setupUpdateTimer(); +} + +RasterImageView::~RasterImageView() +{ + if (d->mDisplayTransform) { + cmsDeleteTransform(d->mDisplayTransform); + } + delete d; +} + +void RasterImageView::setAlphaBackgroundMode(AlphaBackgroundMode mode) +{ + d->mAlphaBackgroundMode = mode; + if (document() && document()->hasAlphaChannel()) { + d->mCurrentBuffer = QPixmap(); + updateBuffer(); + } +} + +void RasterImageView::setAlphaBackgroundColor(const QColor& color) +{ + d->mAlphaBackgroundColor = color; + if (document() && document()->hasAlphaChannel()) { + d->mCurrentBuffer = QPixmap(); + updateBuffer(); + } +} + +void RasterImageView::loadFromDocument() +{ + Document::Ptr doc = document(); + if (!doc) { + return; + } + + connect(doc.data(), SIGNAL(metaInfoLoaded(KUrl)), + SLOT(slotDocumentMetaInfoLoaded())); + connect(doc.data(), SIGNAL(isAnimatedUpdated()), + SLOT(slotDocumentIsAnimatedUpdated())); + + const Document::LoadingState state = doc->loadingState(); + if (state == Document::MetaInfoLoaded || state == Document::Loaded) { + slotDocumentMetaInfoLoaded(); + } +} + +void RasterImageView::slotDocumentMetaInfoLoaded() +{ + if (document()->size().isValid()) { + QMetaObject::invokeMethod(this, "finishSetDocument", Qt::QueuedConnection); + } else { + // Could not retrieve image size from meta info, we need to load the + // full image now. + connect(document().data(), SIGNAL(loaded(KUrl)), + SLOT(finishSetDocument())); + document()->startLoadingFullImage(); + } +} + +void RasterImageView::finishSetDocument() +{ + GV_RETURN_IF_FAIL(document()->size().isValid()); + + d->mScaler->setDocument(document()); + d->resizeBuffer(); + applyPendingScrollPos(); + + connect(document().data(), SIGNAL(imageRectUpdated(QRect)), + SLOT(updateImageRect(QRect))); + + if (zoomToFit()) { + // Force the update otherwise if computeZoomToFit() returns 1, setZoom() + // will think zoom has not changed and won't update the image + setZoom(computeZoomToFit(), QPointF(-1, -1), ForceUpdate); + } else { + updateBuffer(); + } + + d->startAnimationIfNecessary(); + update(); +} + +void RasterImageView::updateImageRect(const QRect& imageRect) +{ + QRectF viewRect = mapToView(imageRect); + if (!viewRect.intersects(boundingRect())) { + return; + } + + if (zoomToFit()) { + setZoom(computeZoomToFit()); + } + d->setScalerRegionToVisibleRect(); + update(); +} + +void RasterImageView::slotDocumentIsAnimatedUpdated() +{ + d->startAnimationIfNecessary(); +} + +void RasterImageView::updateFromScaler(int zoomedImageLeft, int zoomedImageTop, const QImage& image) +{ + if (d->mApplyDisplayTransform) { + if (!d->mDisplayTransform) { + d->updateDisplayTransform(image.format()); + } + if (d->mDisplayTransform) { + quint8 *bytes = const_cast(image.bits()); + cmsDoTransform(d->mDisplayTransform, bytes, bytes, image.width() * image.height()); + } + } + + d->resizeBuffer(); + int viewportLeft = zoomedImageLeft - scrollPos().x(); + int viewportTop = zoomedImageTop - scrollPos().y(); + d->mBufferIsEmpty = false; + { + QPainter painter(&d->mCurrentBuffer); + if (document()->hasAlphaChannel()) { + d->drawAlphaBackground( + &painter, QRect(viewportLeft, viewportTop, image.width(), image.height()), + QPoint(zoomedImageLeft, zoomedImageTop) + ); + } else { + painter.setCompositionMode(QPainter::CompositionMode_Source); + } + painter.drawImage(viewportLeft, viewportTop, image); + } + update(); + + if (!d->mEmittedCompleted) { + d->mEmittedCompleted = true; + completed(); + } +} + +void RasterImageView::onZoomChanged() +{ + // If we zoom more than twice, then assume the user wants to see the real + // pixels, for example to fine tune a crop operation + if (zoom() < 2.) { + d->mScaler->setTransformationMode(Qt::SmoothTransformation); + } else { + d->mScaler->setTransformationMode(Qt::FastTransformation); + } + if (!d->mUpdateTimer->isActive()) { + updateBuffer(); + } +} + +void RasterImageView::onImageOffsetChanged() +{ + update(); +} + +void RasterImageView::onScrollPosChanged(const QPointF& oldPos) +{ + QPointF delta = scrollPos() - oldPos; + + // Scroll existing + { + if (d->mAlternateBuffer.size() != d->mCurrentBuffer.size()) { + d->mAlternateBuffer = QPixmap(d->mCurrentBuffer.size()); + } + QPainter painter(&d->mAlternateBuffer); + painter.drawPixmap(-delta, d->mCurrentBuffer); + } + qSwap(d->mCurrentBuffer, d->mAlternateBuffer); + + // Scale missing parts + QRegion bufferRegion = QRegion(d->mCurrentBuffer.rect().translated(scrollPos().toPoint())); + QRegion updateRegion = bufferRegion - bufferRegion.translated(-delta.toPoint()); + updateBuffer(updateRegion); + update(); +} + +void RasterImageView::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) +{ + QPointF topLeft = imageOffset(); + if (zoomToFit()) { + // In zoomToFit mode, scale crudely the buffer to fit the screen. This + // provide an approximate rendered which will be replaced when the scheduled + // proper scale is ready. + QSizeF size = documentSize() * zoom(); + painter->drawPixmap(topLeft.x(), topLeft.y(), size.width(), size.height(), d->mCurrentBuffer); + } else { + painter->drawPixmap(topLeft, d->mCurrentBuffer); + } + + if (d->mTool) { + d->mTool.data()->paint(painter); + } + + // Debug +#if 0 + QSizeF visibleSize = documentSize() * zoom(); + painter->setPen(Qt::red); + painter->drawRect(topLeft.x(), topLeft.y(), visibleSize.width() - 1, visibleSize.height() - 1); + + painter->setPen(Qt::blue); + painter->drawRect(topLeft.x(), topLeft.y(), d->mCurrentBuffer.width() - 1, d->mCurrentBuffer.height() - 1); +#endif +} + +void RasterImageView::resizeEvent(QGraphicsSceneResizeEvent* event) +{ + // If we are in zoomToFit mode and have something in our buffer, delay the + // update: paint() will paint a scaled version of the buffer until resizing + // is done. This is much faster than rescaling the whole image for each + // resize event we receive. + // mUpdateTimer must be started before calling AbstractImageView::resizeEvent() + // because AbstractImageView::resizeEvent() will call onZoomChanged(), which + // will trigger an immediate update unless the mUpdateTimer is active. + if (zoomToFit() && !d->mBufferIsEmpty) { + d->mUpdateTimer->start(); + } + AbstractImageView::resizeEvent(event); + if (!zoomToFit()) { + // Only update buffer if we are not in zoomToFit mode: if we are + // onZoomChanged() will have already updated the buffer. + updateBuffer(); + } +} + +void RasterImageView::updateBuffer(const QRegion& region) +{ + d->mUpdateTimer->stop(); + d->mScaler->setZoom(zoom()); + if (region.isEmpty()) { + d->setScalerRegionToVisibleRect(); + } else { + d->mScaler->setDestinationRegion(region); + } +} + +void RasterImageView::setCurrentTool(AbstractRasterImageViewTool* tool) +{ + if (d->mTool) { + d->mTool.data()->toolDeactivated(); + d->mTool.data()->deleteLater(); + } + d->mTool = tool; + if (d->mTool) { + d->mTool.data()->toolActivated(); + } + updateCursor(); + currentToolChanged(tool); + update(); +} + +AbstractRasterImageViewTool* RasterImageView::currentTool() const +{ + return d->mTool.data(); +} + +void RasterImageView::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + if (d->mTool) { + d->mTool.data()->mousePressEvent(event); + if (event->isAccepted()) { + return; + } + } + AbstractImageView::mousePressEvent(event); +} + +void RasterImageView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + if (d->mTool) { + d->mTool.data()->mouseMoveEvent(event); + if (event->isAccepted()) { + return; + } + } + AbstractImageView::mouseMoveEvent(event); +} + +void RasterImageView::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + if (d->mTool) { + d->mTool.data()->mouseReleaseEvent(event); + if (event->isAccepted()) { + return; + } + } + AbstractImageView::mouseReleaseEvent(event); +} + +void RasterImageView::wheelEvent(QGraphicsSceneWheelEvent* event) +{ + if (d->mTool) { + d->mTool.data()->wheelEvent(event); + if (event->isAccepted()) { + return; + } + } + AbstractImageView::wheelEvent(event); +} + +void RasterImageView::keyPressEvent(QKeyEvent* event) +{ + if (d->mTool) { + d->mTool.data()->keyPressEvent(event); + if (event->isAccepted()) { + return; + } + } + AbstractImageView::keyPressEvent(event); +} + +void RasterImageView::keyReleaseEvent(QKeyEvent* event) +{ + if (d->mTool) { + d->mTool.data()->keyReleaseEvent(event); + if (event->isAccepted()) { + return; + } + } + AbstractImageView::keyReleaseEvent(event); +} + +void RasterImageView::hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + if (d->mTool) { + d->mTool.data()->hoverMoveEvent(event); + if (event->isAccepted()) { + return; + } + } + AbstractImageView::hoverMoveEvent(event); +} + +} // namespace diff --git a/gwenview/lib/documentview/rasterimageview.h b/gwenview/lib/documentview/rasterimageview.h new file mode 100644 index 00000000..407a79fe --- /dev/null +++ b/gwenview/lib/documentview/rasterimageview.h @@ -0,0 +1,90 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef RASTERIMAGEVIEW_H +#define RASTERIMAGEVIEW_H + +#include + +// Local +#include + +// KDE + +class QGraphicsSceneHoverEvent; + +namespace Gwenview +{ + +class AbstractRasterImageViewTool; + +struct RasterImageViewPrivate; +class GWENVIEWLIB_EXPORT RasterImageView : public AbstractImageView +{ + Q_OBJECT +public: + enum AlphaBackgroundMode { + AlphaBackgroundCheckBoard, + AlphaBackgroundSolid + }; + + RasterImageView(QGraphicsItem* parent = 0); + ~RasterImageView(); + + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); + + void setCurrentTool(AbstractRasterImageViewTool* tool); + AbstractRasterImageViewTool* currentTool() const; + + void setAlphaBackgroundMode(AlphaBackgroundMode mode); + void setAlphaBackgroundColor(const QColor& color); + +Q_SIGNALS: + void currentToolChanged(AbstractRasterImageViewTool*); + +protected: + void loadFromDocument(); + void onZoomChanged(); + void onImageOffsetChanged(); + void onScrollPosChanged(const QPointF& oldPos); + void resizeEvent(QGraphicsSceneResizeEvent* event); + void mousePressEvent(QGraphicsSceneMouseEvent* event); + void mouseMoveEvent(QGraphicsSceneMouseEvent* event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent* event); + void wheelEvent(QGraphicsSceneWheelEvent* event); + void keyPressEvent(QKeyEvent* event); + void keyReleaseEvent(QKeyEvent* event); + void hoverMoveEvent(QGraphicsSceneHoverEvent*); + +private Q_SLOTS: + void slotDocumentMetaInfoLoaded(); + void slotDocumentIsAnimatedUpdated(); + void finishSetDocument(); + void updateFromScaler(int, int, const QImage&); + void updateImageRect(const QRect& imageRect); + void updateBuffer(const QRegion& region = QRegion()); + +private: + RasterImageViewPrivate* const d; +}; + +} // namespace + +#endif /* RASTERIMAGEVIEW_H */ diff --git a/gwenview/lib/documentview/rasterimageviewadapter.cpp b/gwenview/lib/documentview/rasterimageviewadapter.cpp new file mode 100644 index 00000000..7a78e10d --- /dev/null +++ b/gwenview/lib/documentview/rasterimageviewadapter.cpp @@ -0,0 +1,149 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include + +// Local +#include +#include +#include + +// KDE +#include + +// Qt + +namespace Gwenview +{ + +//// RasterImageViewAdapter //// +struct RasterImageViewAdapterPrivate +{ + RasterImageViewAdapter* q; + RasterImageView* mView; +}; + +RasterImageViewAdapter::RasterImageViewAdapter() +: d(new RasterImageViewAdapterPrivate) +{ + d->q = this; + d->mView = new RasterImageView; + connect(d->mView, SIGNAL(zoomChanged(qreal)), SIGNAL(zoomChanged(qreal))); + connect(d->mView, SIGNAL(zoomToFitChanged(bool)), SIGNAL(zoomToFitChanged(bool))); + connect(d->mView, SIGNAL(zoomInRequested(QPointF)), SIGNAL(zoomInRequested(QPointF))); + connect(d->mView, SIGNAL(zoomOutRequested(QPointF)), SIGNAL(zoomOutRequested(QPointF))); + connect(d->mView, SIGNAL(scrollPosChanged()), SIGNAL(scrollPosChanged())); + connect(d->mView, SIGNAL(completed()), SIGNAL(completed())); + connect(d->mView, SIGNAL(previousImageRequested()), SIGNAL(previousImageRequested())); + connect(d->mView, SIGNAL(nextImageRequested()), SIGNAL(nextImageRequested())); + connect(d->mView, SIGNAL(toggleFullScreenRequested()), SIGNAL(toggleFullScreenRequested())); + setWidget(d->mView); +} + +RasterImageViewAdapter::~RasterImageViewAdapter() +{ + delete d; +} + +QCursor RasterImageViewAdapter::cursor() const +{ + return d->mView->cursor(); +} + +void RasterImageViewAdapter::setCursor(const QCursor& cursor) +{ + d->mView->setCursor(cursor); +} + +void RasterImageViewAdapter::setDocument(Document::Ptr doc) +{ + d->mView->setDocument(doc); + + connect(doc.data(), SIGNAL(loadingFailed(KUrl)), SLOT(slotLoadingFailed())); + if (doc->loadingState() == Document::LoadingFailed) { + slotLoadingFailed(); + } +} + +qreal RasterImageViewAdapter::zoom() const +{ + return d->mView->zoom(); +} + +void RasterImageViewAdapter::setZoomToFit(bool on) +{ + d->mView->setZoomToFit(on); +} + +bool RasterImageViewAdapter::zoomToFit() const +{ + return d->mView->zoomToFit(); +} + +void RasterImageViewAdapter::setZoom(qreal zoom, const QPointF& center) +{ + d->mView->setZoom(zoom, center); +} + +qreal RasterImageViewAdapter::computeZoomToFit() const +{ + return d->mView->computeZoomToFit(); +} + +Document::Ptr RasterImageViewAdapter::document() const +{ + return d->mView->document(); +} + +void RasterImageViewAdapter::slotLoadingFailed() +{ + d->mView->setDocument(Document::Ptr()); +} + +void RasterImageViewAdapter::loadConfig() +{ + d->mView->setAlphaBackgroundMode(GwenviewConfig::alphaBackgroundMode()); + d->mView->setAlphaBackgroundColor(GwenviewConfig::alphaBackgroundColor()); + d->mView->setEnlargeSmallerImages(GwenviewConfig::enlargeSmallerImages()); +} + +RasterImageView* RasterImageViewAdapter::rasterImageView() const +{ + return d->mView; +} + +QPointF RasterImageViewAdapter::scrollPos() const +{ + return d->mView->scrollPos(); +} + +void RasterImageViewAdapter::setScrollPos(const QPointF& pos) +{ + d->mView->setScrollPos(pos); +} + +QRectF RasterImageViewAdapter::visibleDocumentRect() const +{ + return QRectF(d->mView->imageOffset(), d->mView->visibleImageSize()); +} + + +} // namespace diff --git a/gwenview/lib/documentview/rasterimageviewadapter.h b/gwenview/lib/documentview/rasterimageviewadapter.h new file mode 100644 index 00000000..f9ea537e --- /dev/null +++ b/gwenview/lib/documentview/rasterimageviewadapter.h @@ -0,0 +1,90 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef RASTERIMAGEVIEWADAPTER_H +#define RASTERIMAGEVIEWADAPTER_H + +#include + +// Qt + +// KDE + +// Local +#include + +namespace Gwenview +{ + +struct RasterImageViewAdapterPrivate; +class GWENVIEWLIB_EXPORT RasterImageViewAdapter : public AbstractDocumentViewAdapter +{ + Q_OBJECT +public: + RasterImageViewAdapter(); + ~RasterImageViewAdapter(); + + virtual QCursor cursor() const; + + virtual void setCursor(const QCursor&); + + virtual MimeTypeUtils::Kind kind() const + { + return MimeTypeUtils::KIND_RASTER_IMAGE; + } + + virtual bool canZoom() const + { + return true; + } + + virtual void setZoomToFit(bool); + + virtual bool zoomToFit() const; + + virtual qreal zoom() const; + + virtual void setZoom(qreal zoom, const QPointF& center); + + virtual qreal computeZoomToFit() const; + + virtual Document::Ptr document() const; + + virtual void setDocument(Document::Ptr); + + virtual void loadConfig(); + + virtual RasterImageView* rasterImageView() const; + + virtual QPointF scrollPos() const; + virtual void setScrollPos(const QPointF& pos); + + virtual QRectF visibleDocumentRect() const; + +private Q_SLOTS: + void slotLoadingFailed(); + +private: + RasterImageViewAdapterPrivate* const d; +}; + +} // namespace + +#endif /* RASTERIMAGEVIEWADAPTER_H */ diff --git a/gwenview/lib/documentview/svgviewadapter.cpp b/gwenview/lib/documentview/svgviewadapter.cpp new file mode 100644 index 00000000..959aa06a --- /dev/null +++ b/gwenview/lib/documentview/svgviewadapter.cpp @@ -0,0 +1,181 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "svgviewadapter.moc" + +// Qt +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include + +// Local +#include "document/documentfactory.h" +#include +#include + +namespace Gwenview +{ + +/// SvgImageView //// +SvgImageView::SvgImageView(QGraphicsItem* parent) +: AbstractImageView(parent) +, mSvgItem(new QGraphicsSvgItem(this)) +{ +} + +void SvgImageView::loadFromDocument() +{ + Document::Ptr doc = document(); + GV_RETURN_IF_FAIL(doc); + + if (doc->loadingState() < Document::Loaded) { + connect(doc.data(), SIGNAL(loaded(KUrl)), + SLOT(finishLoadFromDocument())); + } else { + QMetaObject::invokeMethod(this, "finishLoadFromDocument", Qt::QueuedConnection); + } +} + +void SvgImageView::finishLoadFromDocument() +{ + QSvgRenderer* renderer = document()->svgRenderer(); + GV_RETURN_IF_FAIL(renderer); + mSvgItem->setSharedRenderer(renderer); + if (zoomToFit()) { + setZoom(computeZoomToFit(), QPointF(-1, -1), ForceUpdate); + } else { + mSvgItem->setScale(zoom()); + } + applyPendingScrollPos(); + completed(); +} + +void SvgImageView::onZoomChanged() +{ + mSvgItem->setScale(zoom()); + adjustItemPos(); +} + +void SvgImageView::onImageOffsetChanged() +{ + adjustItemPos(); +} + +void SvgImageView::onScrollPosChanged(const QPointF& /* oldPos */) +{ + adjustItemPos(); +} + +void SvgImageView::adjustItemPos() +{ + mSvgItem->setPos(imageOffset() - scrollPos()); +} + +//// SvgViewAdapter //// +struct SvgViewAdapterPrivate +{ + SvgImageView* mView; +}; + +SvgViewAdapter::SvgViewAdapter() +: d(new SvgViewAdapterPrivate) +{ + d->mView = new SvgImageView; + setWidget(d->mView); + connect(d->mView, SIGNAL(zoomChanged(qreal)), SIGNAL(zoomChanged(qreal))); + connect(d->mView, SIGNAL(zoomToFitChanged(bool)), SIGNAL(zoomToFitChanged(bool))); + connect(d->mView, SIGNAL(zoomInRequested(QPointF)), SIGNAL(zoomInRequested(QPointF))); + connect(d->mView, SIGNAL(zoomOutRequested(QPointF)), SIGNAL(zoomOutRequested(QPointF))); + connect(d->mView, SIGNAL(scrollPosChanged()), SIGNAL(scrollPosChanged())); + connect(d->mView, SIGNAL(completed()), SIGNAL(completed())); + connect(d->mView, SIGNAL(previousImageRequested()), SIGNAL(previousImageRequested())); + connect(d->mView, SIGNAL(nextImageRequested()), SIGNAL(nextImageRequested())); + connect(d->mView, SIGNAL(toggleFullScreenRequested()), SIGNAL(toggleFullScreenRequested())); +} + +SvgViewAdapter::~SvgViewAdapter() +{ + delete d; +} + +QCursor SvgViewAdapter::cursor() const +{ + return widget()->cursor(); +} + +void SvgViewAdapter::setCursor(const QCursor& cursor) +{ + widget()->setCursor(cursor); +} + +void SvgViewAdapter::setDocument(Document::Ptr doc) +{ + d->mView->setDocument(doc); +} + +Document::Ptr SvgViewAdapter::document() const +{ + return d->mView->document(); +} + +void SvgViewAdapter::setZoomToFit(bool on) +{ + d->mView->setZoomToFit(on); +} + +bool SvgViewAdapter::zoomToFit() const +{ + return d->mView->zoomToFit(); +} + +qreal SvgViewAdapter::zoom() const +{ + return d->mView->zoom(); +} + +void SvgViewAdapter::setZoom(qreal zoom, const QPointF& center) +{ + d->mView->setZoom(zoom, center); +} + +qreal SvgViewAdapter::computeZoomToFit() const +{ + return d->mView->computeZoomToFit(); +} + +QPointF SvgViewAdapter::scrollPos() const +{ + return d->mView->scrollPos(); +} + +void SvgViewAdapter::setScrollPos(const QPointF& pos) +{ + d->mView->setScrollPos(pos); +} + +} // namespace diff --git a/gwenview/lib/documentview/svgviewadapter.h b/gwenview/lib/documentview/svgviewadapter.h new file mode 100644 index 00000000..6fccdf3c --- /dev/null +++ b/gwenview/lib/documentview/svgviewadapter.h @@ -0,0 +1,105 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef SVGVIEWADAPTER_H +#define SVGVIEWADAPTER_H + +#include + +// Qt +#include + +// KDE + +// Local +#include +#include + +class QGraphicsSvgItem; + +namespace Gwenview +{ + +class SvgImageView : public AbstractImageView +{ + Q_OBJECT +public: + SvgImageView(QGraphicsItem* parent = 0); + +protected: + void loadFromDocument(); + void onZoomChanged(); + void onImageOffsetChanged(); + void onScrollPosChanged(const QPointF& oldPos); + +private Q_SLOTS: + void finishLoadFromDocument(); + +private: + QGraphicsSvgItem* mSvgItem; + void adjustItemPos(); +}; + +struct SvgViewAdapterPrivate; +class GWENVIEWLIB_EXPORT SvgViewAdapter : public AbstractDocumentViewAdapter +{ + Q_OBJECT +public: + SvgViewAdapter(); + ~SvgViewAdapter(); + + virtual QCursor cursor() const; + + virtual void setCursor(const QCursor&); + + virtual void setDocument(Document::Ptr); + + virtual Document::Ptr document() const; + + virtual MimeTypeUtils::Kind kind() const + { + return MimeTypeUtils::KIND_SVG_IMAGE; + } + + virtual bool canZoom() const + { + return true; + } + + virtual void setZoomToFit(bool); + + virtual bool zoomToFit() const; + + virtual qreal zoom() const; + + virtual void setZoom(qreal /*zoom*/, const QPointF& /*center*/ = QPointF(-1, -1)); + + virtual qreal computeZoomToFit() const; + + virtual QPointF scrollPos() const; + virtual void setScrollPos(const QPointF& pos); + +private: + SvgViewAdapterPrivate* const d; +}; + +} // namespace + +#endif /* SVGVIEWADAPTER_H */ diff --git a/gwenview/lib/documentview/videoviewadapter.cpp b/gwenview/lib/documentview/videoviewadapter.cpp new file mode 100644 index 00000000..fa1fab3d --- /dev/null +++ b/gwenview/lib/documentview/videoviewadapter.cpp @@ -0,0 +1,356 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "videoviewadapter.moc" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include + +// Local +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +struct VideoViewAdapterPrivate +{ + VideoViewAdapter* q; + Phonon::MediaObject* mMediaObject; + Phonon::VideoWidget* mVideoWidget; + Phonon::AudioOutput* mAudioOutput; + HudWidget* mHud; + GraphicsWidgetFloater* mFloater; + + HudSlider* mSeekSlider; + QTime mLastSeekSliderActionTime; + + QAction* mPlayPauseAction; + QAction* mMuteAction; + + HudSlider* mVolumeSlider; + QTime mLastVolumeSliderChangeTime; + + Document::Ptr mDocument; + + void setupActions() + { + mPlayPauseAction = new QAction(q); + mPlayPauseAction->setShortcut(Qt::Key_P); + QObject::connect(mPlayPauseAction, SIGNAL(triggered()), + q, SLOT(slotPlayPauseClicked())); + QObject::connect(mMediaObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)), + q, SLOT(updatePlayUi())); + + mMuteAction = new QAction(q); + mMuteAction->setShortcut(Qt::Key_M); + QObject::connect(mMuteAction, SIGNAL(triggered()), + q, SLOT(slotMuteClicked())); + QObject::connect(mAudioOutput, SIGNAL(mutedChanged(bool)), + q, SLOT(updateMuteAction())); + } + + void setupHud(QGraphicsWidget* parent) + { + // Play/Pause + HudButton* playPauseButton = new HudButton; + playPauseButton->setDefaultAction(mPlayPauseAction); + + // Seek + mSeekSlider = new HudSlider; + mSeekSlider->setPageStep(5000); + mSeekSlider->setSingleStep(200); + QObject::connect(mSeekSlider, SIGNAL(actionTriggered(int)), + q, SLOT(slotSeekSliderActionTriggered(int))); + QObject::connect(mMediaObject, SIGNAL(tick(qint64)), + q, SLOT(slotTicked(qint64))); + QObject::connect(mMediaObject, SIGNAL(totalTimeChanged(qint64)), + q, SLOT(updatePlayUi())); + QObject::connect(mMediaObject, SIGNAL(seekableChanged(bool)), + q, SLOT(updatePlayUi())); + + // Mute + HudButton* muteButton = new HudButton; + muteButton->setDefaultAction(mMuteAction); + + // Volume + mVolumeSlider = new HudSlider; + mVolumeSlider->setMinimumWidth(100); + mVolumeSlider->setRange(0, 100); + mVolumeSlider->setPageStep(5); + mVolumeSlider->setSingleStep(1); + QObject::connect(mVolumeSlider, SIGNAL(valueChanged(int)), + q, SLOT(slotVolumeSliderChanged(int))); + QObject::connect(mAudioOutput, SIGNAL(volumeChanged(qreal)), + q, SLOT(slotOutputVolumeChanged(qreal))); + + // Layout + QGraphicsWidget* hudContent = new QGraphicsWidget; + QGraphicsLinearLayout* layout = new QGraphicsLinearLayout(hudContent); + layout->addItem(playPauseButton); + layout->addItem(mSeekSlider); + layout->setStretchFactor(mSeekSlider, 5); + layout->addItem(muteButton); + layout->addItem(mVolumeSlider); + layout->setStretchFactor(mVolumeSlider, 1); + + // Create hud + mHud = new HudWidget(parent); + mHud->init(hudContent, HudWidget::OptionNone); + mHud->setZValue(1); + + // Init floater + mFloater = new GraphicsWidgetFloater(parent); + mFloater->setChildWidget(mHud); + mFloater->setAlignment(Qt::AlignJustify | Qt::AlignBottom); + } + + bool isPlaying() const + { + switch (mMediaObject->state()) { + case Phonon::PlayingState: + case Phonon::BufferingState: + return true; + default: + return false; + } + } + + void updateHudVisibility(int yPos) + { + const int floaterY = mVideoWidget->height() - mFloater->verticalMargin() - mHud->effectiveSizeHint(Qt::MinimumSize).height() * 3 / 2; + if (yPos < floaterY) { + mHud->fadeOut(); + } else { + mHud->fadeIn(); + } + } + + void keyPressEvent(QKeyEvent* event) + { + if (event->modifiers() != Qt::NoModifier) { + return; + } + + switch (event->key()) { + case Qt::Key_Left: + mSeekSlider->triggerAction(QAbstractSlider::SliderSingleStepSub); + break; + case Qt::Key_Right: + mSeekSlider->triggerAction(QAbstractSlider::SliderSingleStepAdd); + break; + case Qt::Key_Up: + q->previousImageRequested(); + break; + case Qt::Key_Down: + q->nextImageRequested(); + break; + default: + break; + } + } +}; + +/** + * This is a workaround for a bug in QGraphicsProxyWidget: it does not forward + * double-click events to the proxy-fied widget. + * + * QGraphicsProxyWidget::mouseDoubleClickEvent() correctly forwards the event + * to its QWidget, but it is never called. This is because for it to be called, + * the implementation of mousePressEvent() must call + * QGraphicsItem::mousePressEvent() but it does not. + */ +class DoubleClickableProxyWidget : public QGraphicsProxyWidget +{ +protected: + void mousePressEvent(QGraphicsSceneMouseEvent* event) + { + QGraphicsWidget::mousePressEvent(event); + } +}; + +VideoViewAdapter::VideoViewAdapter() +: d(new VideoViewAdapterPrivate) +{ + d->q = this; + d->mMediaObject = new Phonon::MediaObject(this); + d->mMediaObject->setTickInterval(350); + connect(d->mMediaObject, SIGNAL(finished()), SIGNAL(videoFinished())); + + d->mVideoWidget = new Phonon::VideoWidget; + d->mVideoWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + d->mVideoWidget->setAttribute(Qt::WA_Hover); + d->mVideoWidget->installEventFilter(this); + + Phonon::createPath(d->mMediaObject, d->mVideoWidget); + + d->mAudioOutput = new Phonon::AudioOutput(Phonon::VideoCategory, this); + Phonon::createPath(d->mMediaObject, d->mAudioOutput); + + QGraphicsProxyWidget* proxy = new DoubleClickableProxyWidget; + proxy->setFlag(QGraphicsItem::ItemIsSelectable); // Needed for doubleclick to work + proxy->setWidget(d->mVideoWidget); + proxy->setAcceptHoverEvents(true); + setWidget(proxy); + + d->setupActions(); + d->setupHud(proxy); + + updatePlayUi(); + updateMuteAction(); +} + +VideoViewAdapter::~VideoViewAdapter() +{ + // This prevents a memory leak that can occur after switching + // to the next/previous video. For details see: + // https://git.reviewboard.kde.org/r/108070/ + d->mMediaObject->stop(); + + delete d; +} + +void VideoViewAdapter::setDocument(Document::Ptr doc) +{ + d->mHud->show(); + d->mDocument = doc; + d->mMediaObject->setCurrentSource(d->mDocument->url()); + d->mMediaObject->play(); + // If we do not use a queued connection, the signal arrives too early, + // preventing the listing of the dir content when Gwenview is started with + // a video as an argument. + QMetaObject::invokeMethod(this, "completed", Qt::QueuedConnection); +} + +Document::Ptr VideoViewAdapter::document() const +{ + return d->mDocument; +} + +void VideoViewAdapter::slotPlayPauseClicked() +{ + if (d->isPlaying()) { + d->mMediaObject->pause(); + } else { + d->mMediaObject->play(); + } +} + +void VideoViewAdapter::slotMuteClicked() +{ + d->mAudioOutput->setMuted(!d->mAudioOutput->isMuted()); +} + +bool VideoViewAdapter::eventFilter(QObject*, QEvent* event) +{ + if (event->type() == QEvent::MouseMove) { + d->updateHudVisibility(static_cast(event)->y()); + } else if (event->type() == QEvent::KeyPress) { + d->keyPressEvent(static_cast(event)); + } else if (event->type() == QEvent::MouseButtonDblClick) { + if (static_cast(event)->modifiers() == Qt::NoModifier) { + toggleFullScreenRequested(); + } + } + return false; +} + +void VideoViewAdapter::updatePlayUi() +{ + if (d->isPlaying()) { + d->mPlayPauseAction->setIcon(KIcon("media-playback-pause")); + } else { + d->mPlayPauseAction->setIcon(KIcon("media-playback-start")); + } + + d->mLastSeekSliderActionTime.restart(); + d->mSeekSlider->setRange(0, d->mMediaObject->totalTime()); + + switch (d->mMediaObject->state()) { + case Phonon::PlayingState: + case Phonon::BufferingState: + case Phonon::PausedState: + d->mSeekSlider->setEnabled(true); + break; + case Phonon::StoppedState: + case Phonon::LoadingState: + case Phonon::ErrorState: + d->mSeekSlider->setEnabled(false); + d->mSeekSlider->setValue(0); + break; + } +} + +void VideoViewAdapter::updateMuteAction() +{ + d->mMuteAction->setIcon( + KIcon(d->mAudioOutput->isMuted() ? "player-volume-muted" : "player-volume") + ); +} + +void VideoViewAdapter::slotVolumeSliderChanged(int value) +{ + d->mLastVolumeSliderChangeTime.restart(); + d->mAudioOutput->setVolume(value / 100.); +} + +void VideoViewAdapter::slotOutputVolumeChanged(qreal value) +{ + if (d->mLastVolumeSliderChangeTime.isValid() && d->mLastVolumeSliderChangeTime.elapsed() < 2000) { + return; + } + d->mVolumeSlider->setValue(qRound(value * 100)); +} + +void VideoViewAdapter::slotSeekSliderActionTriggered(int /*action*/) +{ + d->mLastSeekSliderActionTime.restart(); + d->mMediaObject->seek(d->mSeekSlider->sliderPosition()); +} + +void VideoViewAdapter::slotTicked(qint64 value) +{ + if (d->mLastSeekSliderActionTime.isValid() && d->mLastSeekSliderActionTime.elapsed() < 2000) { + return; + } + if (!d->mSeekSlider->isSliderDown()) { + d->mSeekSlider->setValue(value); + } +} + +} // namespace diff --git a/gwenview/lib/documentview/videoviewadapter.h b/gwenview/lib/documentview/videoviewadapter.h new file mode 100644 index 00000000..a8a40c20 --- /dev/null +++ b/gwenview/lib/documentview/videoviewadapter.h @@ -0,0 +1,76 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef VIDEOVIEWADAPTER_H +#define VIDEOVIEWADAPTER_H + +#include + +// Qt + +// KDE + +// Local +#include + +namespace Gwenview +{ + +struct VideoViewAdapterPrivate; +class GWENVIEWLIB_EXPORT VideoViewAdapter : public AbstractDocumentViewAdapter +{ + Q_OBJECT +public: + VideoViewAdapter(); + ~VideoViewAdapter(); + + virtual MimeTypeUtils::Kind kind() const + { + return MimeTypeUtils::KIND_VIDEO; + } + + virtual Document::Ptr document() const; + + virtual void setDocument(Document::Ptr); + +Q_SIGNALS: + void videoFinished(); + +protected: + bool eventFilter(QObject*, QEvent*); + +private Q_SLOTS: + void slotPlayPauseClicked(); + void updatePlayUi(); + void slotMuteClicked(); + void updateMuteAction(); + void slotVolumeSliderChanged(int); + void slotOutputVolumeChanged(qreal); + void slotSeekSliderActionTriggered(int); + void slotTicked(qint64); + +private: + friend struct VideoViewAdapterPrivate; + VideoViewAdapterPrivate* const d; +}; + +} // namespace + +#endif /* VIDEOVIEWADAPTER_H */ diff --git a/gwenview/lib/eventwatcher.cpp b/gwenview/lib/eventwatcher.cpp new file mode 100644 index 00000000..68d8e078 --- /dev/null +++ b/gwenview/lib/eventwatcher.cpp @@ -0,0 +1,55 @@ +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "eventwatcher.moc" + +namespace Gwenview +{ + +EventWatcher::EventWatcher(QObject* watched, const QList& eventTypes) +: QObject(watched) +, mEventTypes(eventTypes) +{ + Q_ASSERT(watched); + watched->installEventFilter(this); +} + +EventWatcher* EventWatcher::install(QObject* watched, const QList& eventTypes, QObject* receiver, const char* slot) +{ + EventWatcher* watcher = new EventWatcher(watched, eventTypes); + connect(watcher, SIGNAL(eventTriggered(QEvent*)), receiver, slot); + return watcher; +} + +EventWatcher* EventWatcher::install(QObject* watched, QEvent::Type eventType, QObject* receiver, const char* slot) +{ + EventWatcher* watcher = new EventWatcher(watched, QList() << eventType); + connect(watcher, SIGNAL(eventTriggered(QEvent*)), receiver, slot); + return watcher; +} + +bool EventWatcher::eventFilter(QObject*, QEvent* event) +{ + if (mEventTypes.contains(event->type())) { + eventTriggered(event); + } + return false; +} + +} // namespace diff --git a/gwenview/lib/eventwatcher.h b/gwenview/lib/eventwatcher.h new file mode 100644 index 00000000..88fbdd2e --- /dev/null +++ b/gwenview/lib/eventwatcher.h @@ -0,0 +1,60 @@ +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef EVENTWATCHER_H +#define EVENTWATCHER_H + +// Qt +#include +#include +#include + +// Local +#include "gwenviewlib_export.h" + +namespace Gwenview +{ + +/** + * This class emits a signal when some events are triggered on a watched + * object. + */ +class GWENVIEWLIB_EXPORT EventWatcher : public QObject +{ + Q_OBJECT +public: + EventWatcher(QObject* watched, const QList& eventTypes); + + static EventWatcher* install(QObject* watched, const QList& eventTypes, QObject* receiver, const char* slot); + + static EventWatcher* install(QObject* watched, QEvent::Type eventType, QObject* receiver, const char* slot); + +Q_SIGNALS: + void eventTriggered(QEvent*); + +protected: + virtual bool eventFilter(QObject*, QEvent* event); + +private: + QList mEventTypes; +}; + +} + +#endif /* EVENTWATCHER_H */ diff --git a/gwenview/lib/exiv2imageloader.cpp b/gwenview/lib/exiv2imageloader.cpp new file mode 100644 index 00000000..37ac9fa9 --- /dev/null +++ b/gwenview/lib/exiv2imageloader.cpp @@ -0,0 +1,86 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "exiv2imageloader.h" + +// Qt +#include +#include + +// KDE + +// Exiv2 +#include +#include + +// Local + +namespace Gwenview +{ + +struct Exiv2ImageLoaderPrivate +{ + Exiv2::Image::AutoPtr mImage; + QString mErrorMessage; +}; + +Exiv2ImageLoader::Exiv2ImageLoader() +: d(new Exiv2ImageLoaderPrivate) +{ +} + +Exiv2ImageLoader::~Exiv2ImageLoader() +{ + delete d; +} + +bool Exiv2ImageLoader::load(const QByteArray& data) +{ + try { + d->mImage = Exiv2::ImageFactory::open((unsigned char*)data.constData(), data.size()); + d->mImage->readMetadata(); +#if EXIV2_VERSION >= EXIV2_MAKE_VERSION(0, 14, 0) + // For some unknown reason, trying to catch Exiv2::Error fails with Exiv2 + // >=0.14. For now, just catch std::exception. I would welcome any + // explanation. + } catch (const std::exception& error) { + d->mErrorMessage = error.what(); +#else + // In libexiv2 0.12, Exiv2::Error::what() returns an std::string. + } catch (const Exiv2::Error& error) { + d->mErrorMessage = error.what().c_str(); +#endif + return false; + } + return true; +} + +QString Exiv2ImageLoader::errorMessage() const +{ + return d->mErrorMessage; +} + +Exiv2::Image::AutoPtr Exiv2ImageLoader::popImage() +{ + return d->mImage; +} + +} // namespace diff --git a/gwenview/lib/exiv2imageloader.h b/gwenview/lib/exiv2imageloader.h new file mode 100644 index 00000000..2f1ced04 --- /dev/null +++ b/gwenview/lib/exiv2imageloader.h @@ -0,0 +1,64 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef EXIV2IMAGELOADER_H +#define EXIV2IMAGELOADER_H + +#include + +// Qt + +// KDE + +// Exiv2 +#include +#include + +// Local + +class QByteArray; +class QString; + +namespace Gwenview +{ + +struct Exiv2ImageLoaderPrivate; + +/** + * This helper class loads image using libexiv2, and takes care of exception + * handling for the different versions of libexiv2. + */ +class GWENVIEWLIB_EXPORT Exiv2ImageLoader +{ +public: + Exiv2ImageLoader(); + ~Exiv2ImageLoader(); + + bool load(const QByteArray&); + QString errorMessage() const; + Exiv2::Image::AutoPtr popImage(); + +private: + Exiv2ImageLoaderPrivate* const d; +}; + +} // namespace + +#endif /* EXIV2IMAGELOADER_H */ diff --git a/gwenview/lib/flowlayout.cpp b/gwenview/lib/flowlayout.cpp new file mode 100644 index 00000000..77f8234d --- /dev/null +++ b/gwenview/lib/flowlayout.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the example classes of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information +** to ensure GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#include "flowlayout.h" + +#include + +FlowLayout::FlowLayout(QWidget *parent, int margin, int spacing) +: QLayout(parent) +{ + setMargin(margin); + setSpacing(spacing); +} + +FlowLayout::FlowLayout(int spacing) +{ + setSpacing(spacing); +} + +FlowLayout::~FlowLayout() +{ + QLayoutItem *item; + while ((item = takeAt(0))) + delete item; +} + +void FlowLayout::addItem(QLayoutItem *item) +{ + itemList.append(item); +} + +int FlowLayout::count() const +{ + return itemList.size(); +} + +QLayoutItem *FlowLayout::itemAt(int index) const +{ + return itemList.value(index); +} + +QLayoutItem *FlowLayout::takeAt(int index) +{ + if (index >= 0 && index < itemList.size()) + return itemList.takeAt(index); + else + return 0; +} + +Qt::Orientations FlowLayout::expandingDirections() const +{ + return 0; +} + +bool FlowLayout::hasHeightForWidth() const +{ + return true; +} + +int FlowLayout::heightForWidth(int width) const +{ + int height = doLayout(QRect(0, 0, width, 0), true); + return height; +} + +void FlowLayout::setGeometry(const QRect &rect) +{ + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize FlowLayout::sizeHint() const +{ + return minimumSize(); +} + +QSize FlowLayout::minimumSize() const +{ + QSize size; + QLayoutItem *item; + foreach(item, itemList) + size = size.expandedTo(item->minimumSize()); + + size += QSize(2 * margin(), 2 * margin()); + return size; +} + +int FlowLayout::doLayout(const QRect &rect, bool testOnly) const +{ + int x = rect.x(); + int y = rect.y(); + int lineHeight = 0; + + QLayoutItem *item; + foreach(item, itemList) { + int nextX = x + item->sizeHint().width() + spacing(); + if (nextX - spacing() > rect.right() && lineHeight > 0) { + x = rect.x(); + y = y + lineHeight + spacing(); + nextX = x + item->sizeHint().width() + spacing(); + lineHeight = 0; + } + + if (!testOnly) + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + return y + lineHeight - rect.y(); +} diff --git a/gwenview/lib/flowlayout.h b/gwenview/lib/flowlayout.h new file mode 100644 index 00000000..f7d0c2bc --- /dev/null +++ b/gwenview/lib/flowlayout.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the example classes of the Qt Toolkit. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information +** to ensure GNU General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. In addition, as a special +** exception, Nokia gives you certain additional rights. These rights +** are described in the Nokia Qt GPL Exception version 1.3, included in +** the file GPL_EXCEPTION.txt in this package. +** +** Qt for Windows(R) Licensees +** As a special exception, Nokia, as the sole copyright holder for Qt +** Designer, grants users of the Qt/Eclipse Integration plug-in the +** right for the Qt/Eclipse Integration to link to functionality +** provided by Qt Designer and its related libraries. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ + +#ifndef FLOWLAYOUT_H +#define FLOWLAYOUT_H + +#include + +#include +#include +#include + +class GWENVIEWLIB_EXPORT FlowLayout : public QLayout +{ +public: + explicit FlowLayout(QWidget *parent, int margin = 0, int spacing = -1); + FlowLayout(int spacing = -1); + ~FlowLayout(); + + void addItem(QLayoutItem *item); + Qt::Orientations expandingDirections() const; + bool hasHeightForWidth() const; + int heightForWidth(int) const; + int count() const; + QLayoutItem *itemAt(int index) const; + QSize minimumSize() const; + void setGeometry(const QRect &rect); + QSize sizeHint() const; + QLayoutItem *takeAt(int index); + +private: + int doLayout(const QRect &rect, bool testOnly) const; + + QList itemList; +}; + +#endif diff --git a/gwenview/lib/fullscreenbar.cpp b/gwenview/lib/fullscreenbar.cpp new file mode 100644 index 00000000..526877d7 --- /dev/null +++ b/gwenview/lib/fullscreenbar.cpp @@ -0,0 +1,275 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "fullscreenbar.moc" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include + +// Local + +namespace Gwenview +{ + +static const int SLIDE_DURATION = 150; +static const int AUTO_HIDE_CURSOR_TIMEOUT = 1000; + +// How long before the bar slide out after switching to fullscreen +static const int INITIAL_HIDE_TIMEOUT = 2000; + +// Do not slide bar out if mouse is less than this amount of pixels below bar, to +// prevent accidental slide outs +static const int EXTRA_BAR_HEIGHT = 20; + +struct FullScreenBarPrivate +{ + FullScreenBar* q; + QTimeLine* mTimeLine; + QTimer* mAutoHideCursorTimer; + bool mAutoHidingEnabled; + QTimer* mInitialHideTimer; + + void startTimeLine() + { + if (mTimeLine->state() != QTimeLine::Running) { + mTimeLine->start(); + } + } + + void hideCursor() + { + QBitmap empty(32, 32); + empty.clear(); + QCursor blankCursor(empty, empty); + QApplication::setOverrideCursor(blankCursor); + } + + /** + * Returns the rectangle in which the mouse must enter to trigger bar + * sliding. The rectangle is in global coords. + */ + QRect slideInTriggerRect() const + { + QRect rect = QApplication::desktop()->screenGeometry(QApplication::desktop()->screenNumber(q->parentWidget())); + // Take parent widget position into account because it may not be at + // the top of the screen, for example when the save bar warning is + // shown. + rect.setHeight(q->parentWidget()->y() + q->height() + EXTRA_BAR_HEIGHT); + return rect; + } + + bool shouldHide() const + { + Q_ASSERT(q->parentWidget()); + + if (!mAutoHidingEnabled) { + return false; + } + if (slideInTriggerRect().contains(QCursor::pos())) { + return false; + } + if (QApplication::activePopupWidget()) { + return false; + } + // Do not hide if a button is down, which can happen when we are + // using a scroll bar. + if (QApplication::mouseButtons() != Qt::NoButton) { + return false; + } + return true; + } +}; + +FullScreenBar::FullScreenBar(QWidget* parent) +: QFrame(parent) +, d(new FullScreenBarPrivate) +{ + d->q = this; + d->mAutoHidingEnabled = true; + setObjectName(QLatin1String("fullScreenBar")); + + d->mTimeLine = new QTimeLine(SLIDE_DURATION, this); + connect(d->mTimeLine, SIGNAL(valueChanged(qreal)), SLOT(moveBar(qreal))); + + d->mAutoHideCursorTimer = new QTimer(this); + d->mAutoHideCursorTimer->setInterval(AUTO_HIDE_CURSOR_TIMEOUT); + d->mAutoHideCursorTimer->setSingleShot(true); + connect(d->mAutoHideCursorTimer, SIGNAL(timeout()), SLOT(slotAutoHideCursorTimeout())); + + d->mInitialHideTimer = new QTimer(this); + d->mInitialHideTimer->setInterval(INITIAL_HIDE_TIMEOUT); + d->mInitialHideTimer->setSingleShot(true); + connect(d->mInitialHideTimer, SIGNAL(timeout()), SLOT(slideOut())); + + hide(); +} + +FullScreenBar::~FullScreenBar() +{ + delete d; +} + +QSize FullScreenBar::sizeHint() const +{ + QSize sh = QFrame::sizeHint(); + if (!layout()) { + return sh; + } + + if (layout()->expandingDirections() & Qt::Horizontal) { + int width = QApplication::desktop()->screenGeometry(window()).width(); + sh.setWidth(width); + } + return sh; +} + +void FullScreenBar::moveBar(qreal value) +{ + move(0, -height() + int(value * height())); + + // For some reason, if Gwenview is started with command line options to + // start a slideshow, the bar might end up below the view. Calling raise() + // here fixes it. + raise(); +} + +void FullScreenBar::setActivated(bool activated) +{ + if (activated) { + // Delay installation of event filter because switching to fullscreen + // cause a few window adjustments, which seems to generate unwanted + // mouse events, which cause the bar to slide in. + QTimer::singleShot(500, this, SLOT(delayedInstallEventFilter())); + + adjustSize(); + + // Make sure the widget is visible on start + move(0, 0); + raise(); + show(); + } else { + qApp->removeEventFilter(this); + hide(); + d->mAutoHideCursorTimer->stop(); + QApplication::restoreOverrideCursor(); + } +} + +void FullScreenBar::delayedInstallEventFilter() +{ + qApp->installEventFilter(this); + if (d->shouldHide()) { + d->mInitialHideTimer->start(); + d->hideCursor(); + } +} + +void FullScreenBar::slotAutoHideCursorTimeout() +{ + if (d->shouldHide()) { + d->hideCursor(); + } else { + d->mAutoHideCursorTimer->start(); + } +} + +void FullScreenBar::slideOut() +{ + d->mInitialHideTimer->stop(); + d->mTimeLine->setDirection(QTimeLine::Backward); + d->startTimeLine(); +} + +void FullScreenBar::slideIn() +{ + d->mInitialHideTimer->stop(); + d->mTimeLine->setDirection(QTimeLine::Forward); + d->startTimeLine(); +} + +bool FullScreenBar::eventFilter(QObject* object, QEvent* event) +{ + if (event->type() == QEvent::MouseMove) { + QApplication::restoreOverrideCursor(); + d->mAutoHideCursorTimer->start(); + if (y() == 0) { + if (d->shouldHide()) { + slideOut(); + } + } else { + QMouseEvent* mouseEvent = static_cast(event); + if (mouseEvent->buttons() == 0 && d->slideInTriggerRect().contains(QCursor::pos())) { + slideIn(); + } + } + return false; + } + + if (event->type() == QEvent::MouseButtonRelease) { + // This can happen if user released the mouse after using a scrollbar + // in the content (the bar does not hide while a button is down) + if (y() == 0 && d->shouldHide()) { + slideOut(); + } + return false; + } + + // Filtering message on tooltip text for CJK to remove accelerators. + // Quoting ktoolbar.cpp: + // """ + // CJK languages use more verbose accelerator marker: they add a Latin + // letter in parenthesis, and put accelerator on that. Hence, the default + // removal of ampersand only may not be enough there, instead the whole + // parenthesis construct should be removed. Use KLocale's method to do this. + // """ + if (event->type() == QEvent::Show || event->type() == QEvent::Paint) { + QToolButton* button = qobject_cast(object); + if (button && !button->actions().isEmpty()) { + QAction* action = button->actions().first(); + QString toolTip = KGlobal::locale()->removeAcceleratorMarker(action->toolTip()); + // Filtering message requested by translators (scripting). + button->setToolTip(i18nc("@info:tooltip of custom toolbar button", "%1", toolTip)); + } + } + + return false; +} + +void FullScreenBar::setAutoHidingEnabled(bool value) +{ + d->mAutoHidingEnabled = value; +} + +} // namespace diff --git a/gwenview/lib/fullscreenbar.h b/gwenview/lib/fullscreenbar.h new file mode 100644 index 00000000..3ecbf468 --- /dev/null +++ b/gwenview/lib/fullscreenbar.h @@ -0,0 +1,70 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef FULLSCREENBAR_H +#define FULLSCREENBAR_H + +#include + +// Qt +#include + +// KDE + +// Local + +class QEvent; + +namespace Gwenview +{ + +struct FullScreenBarPrivate; +class GWENVIEWLIB_EXPORT FullScreenBar : public QFrame +{ + Q_OBJECT +public: + FullScreenBar(QWidget* parent); + ~FullScreenBar(); + + void setActivated(bool); + + void setAutoHidingEnabled(bool); + + virtual QSize sizeHint() const; + +public Q_SLOTS: + void slideIn(); + void slideOut(); + +private Q_SLOTS: + void slotAutoHideCursorTimeout(); + void moveBar(qreal); + void delayedInstallEventFilter(); + +protected: + virtual bool eventFilter(QObject*, QEvent*); + +private: + FullScreenBarPrivate* const d; +}; + +} // namespace + +#endif /* FULLSCREENBAR_H */ diff --git a/gwenview/lib/graphicswidgetfloater.cpp b/gwenview/lib/graphicswidgetfloater.cpp new file mode 100644 index 00000000..78c832ca --- /dev/null +++ b/gwenview/lib/graphicswidgetfloater.cpp @@ -0,0 +1,173 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "graphicswidgetfloater.moc" + +// Qt +#include +#include +#include + +// KDE +#include +#include + +// Local + +namespace Gwenview +{ + +struct GraphicsWidgetFloaterPrivate +{ + QGraphicsWidget* mParent; + QPointer mChild; + Qt::Alignment mAlignment; + + int mHorizontalMargin; + int mVerticalMargin; + bool mInsideUpdateChildGeometry; + + void updateChildGeometry() + { + if (!mChild) { + return; + } + if (mInsideUpdateChildGeometry) { + return; + } + + int posX, posY; + int childWidth, childHeight; + int parentWidth, parentHeight; + + childWidth = mChild->size().width(); + childHeight = mChild->size().height(); + + parentWidth = mParent->size().width(); + parentHeight = mParent->size().height(); + + if (parentWidth == 0 || parentHeight == 0) { + return; + } + + if (mAlignment & Qt::AlignLeft) { + posX = mHorizontalMargin; + } else if (mAlignment & Qt::AlignHCenter) { + posX = (parentWidth - childWidth) / 2; + } else if (mAlignment & Qt::AlignJustify) { + posX = mHorizontalMargin; + childWidth = parentWidth - 2 * mHorizontalMargin; + } else { + posX = parentWidth - childWidth - mHorizontalMargin; + } + + if (mAlignment & Qt::AlignTop) { + posY = mVerticalMargin; + } else if (mAlignment & Qt::AlignVCenter) { + posY = (parentHeight - childHeight) / 2; + } else { + posY = parentHeight - childHeight - mVerticalMargin; + } + + mInsideUpdateChildGeometry = true; + mChild->setGeometry(posX, posY, childWidth, childHeight); + mInsideUpdateChildGeometry = false; + } +}; + +GraphicsWidgetFloater::GraphicsWidgetFloater(QGraphicsWidget* parent) +: QObject(parent) +, d(new GraphicsWidgetFloaterPrivate) +{ + Q_ASSERT(parent); + d->mParent = parent; + d->mParent->installEventFilter(this); + d->mChild = 0; + d->mAlignment = Qt::AlignCenter; + d->mHorizontalMargin = KDialog::marginHint(); + d->mVerticalMargin = KDialog::marginHint(); + d->mInsideUpdateChildGeometry = false; +} + +GraphicsWidgetFloater::~GraphicsWidgetFloater() +{ + delete d; +} + +void GraphicsWidgetFloater::setChildWidget(QGraphicsWidget* child) +{ + if (d->mChild) { + d->mChild->removeEventFilter(this); + disconnect(d->mChild, 0, this, 0); + } + d->mChild = child; + d->mChild->setParent(d->mParent); + d->mChild->installEventFilter(this); + connect(d->mChild, SIGNAL(visibleChanged()), SLOT(slotChildVisibilityChanged())); + d->updateChildGeometry(); + //d->mChild->raise(); + d->mChild->show(); +} + +void GraphicsWidgetFloater::setAlignment(Qt::Alignment alignment) +{ + d->mAlignment = alignment; + d->updateChildGeometry(); +} + +bool GraphicsWidgetFloater::eventFilter(QObject*, QEvent* event) +{ + if (event->type() == QEvent::GraphicsSceneResize) { + d->updateChildGeometry(); + } + return false; +} + +void GraphicsWidgetFloater::slotChildVisibilityChanged() +{ + if (d->mChild->isVisible()) { + d->updateChildGeometry(); + } +} + +void GraphicsWidgetFloater::setHorizontalMargin(int value) +{ + d->mHorizontalMargin = value; + d->updateChildGeometry(); +} + +int GraphicsWidgetFloater::horizontalMargin() const +{ + return d->mHorizontalMargin; +} + +void GraphicsWidgetFloater::setVerticalMargin(int value) +{ + d->mVerticalMargin = value; + d->updateChildGeometry(); +} + +int GraphicsWidgetFloater::verticalMargin() const +{ + return d->mVerticalMargin; +} + +} // namespace diff --git a/gwenview/lib/graphicswidgetfloater.h b/gwenview/lib/graphicswidgetfloater.h new file mode 100644 index 00000000..705175e2 --- /dev/null +++ b/gwenview/lib/graphicswidgetfloater.h @@ -0,0 +1,74 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef GRAPHICSWIDGETFLOATER_H +#define GRAPHICSWIDGETFLOATER_H + +#include + +// Qt +#include + +// KDE + +// Local + +class QGraphicsWidget; + +namespace Gwenview +{ + +struct GraphicsWidgetFloaterPrivate; + +/** + * This helper object makes it possible to place a widget (the child) over + * another (the parent), ensuring the child remains aligned as specified by + * setAlignment() whenever either widget get resized. + */ +class GWENVIEWLIB_EXPORT GraphicsWidgetFloater : public QObject +{ + Q_OBJECT +public: + GraphicsWidgetFloater(QGraphicsWidget* parent); + ~GraphicsWidgetFloater(); + + void setChildWidget(QGraphicsWidget*); + + void setAlignment(Qt::Alignment); + + void setHorizontalMargin(int); + int horizontalMargin() const; + + void setVerticalMargin(int); + int verticalMargin() const; + +protected: + bool eventFilter(QObject*, QEvent*); + +private Q_SLOTS: + void slotChildVisibilityChanged(); + +private: + GraphicsWidgetFloaterPrivate* const d; +}; + +} // namespace + +#endif /* GRAPHICSWIDGETFLOATER_H */ diff --git a/gwenview/lib/gvdebug.h b/gwenview/lib/gvdebug.h new file mode 100644 index 00000000..e30e5c82 --- /dev/null +++ b/gwenview/lib/gvdebug.h @@ -0,0 +1,105 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef GVDEBUG_H +#define GVDEBUG_H + +#include + +/** + * Uses this macro if you want your code to abort when the GV_FATAL_FAILS + * environment variable is set. + * Most of the time you want to use the various GV_RETURN* macros instead, + * but this one can be handy in certain situations. For example in a switch + * case block: + * + * switch (state) { + * case State1: + * ... + * break; + * case State2: + * ... + * break; + * case State3: + * kWarning() << "state should not be State3"; + * GV_FATAL_FAILS; + * break; + * } + */ +#define GV_FATAL_FAILS \ + do { \ + if (!qgetenv("GV_FATAL_FAILS").isEmpty()) { \ + kFatal() << "Aborting because environment variable 'GV_FATAL_FAILS' is set"; \ + } \ + } while (0) + +#define GV_RETURN_IF_FAIL(cond) \ + do { \ + if (!(cond)) { \ + kWarning() << "Condition '" << #cond << "' failed"; \ + GV_FATAL_FAILS; \ + return; \ + } \ + } while (0) + +#define GV_RETURN_VALUE_IF_FAIL(cond, value) \ + do { \ + if (!(cond)) { \ + kWarning() << "Condition '" << #cond << "' failed."; \ + GV_FATAL_FAILS; \ + return value; \ + } \ + } while (0) + +#define GV_RETURN_IF_FAIL2(cond, msg) \ + do { \ + if (!(cond)) { \ + kWarning() << "Condition '" << #cond << "' failed" << msg; \ + GV_FATAL_FAILS; \ + return; \ + } \ + } while (0) + +#define GV_RETURN_VALUE_IF_FAIL2(cond, value, msg) \ + do { \ + if (!(cond)) { \ + kWarning() << "Condition '" << #cond << "' failed." << msg; \ + GV_FATAL_FAILS; \ + return value; \ + } \ + } while (0) + +#define GV_WARN_AND_RETURN(msg) \ + do { \ + kWarning() << msg; \ + GV_FATAL_FAILS; \ + return; \ + } while (0) + +#define GV_WARN_AND_RETURN_VALUE(value, msg) \ + do { \ + kWarning() << msg; \ + GV_FATAL_FAILS; \ + return value; \ + } while (0) + +#define GV_LOG(var) kDebug() << #var << '=' << (var) + +#endif // GVDEBUG_H diff --git a/gwenview/lib/gwenviewconfig.kcfg b/gwenview/lib/gwenviewconfig.kcfg new file mode 100644 index 00000000..92d39f77 --- /dev/null +++ b/gwenview/lib/gwenviewconfig.kcfg @@ -0,0 +1,254 @@ + + + + + lib/sorting.h + lib/mousewheelbehavior.h + lib/documentview/documentview.h + lib/documentview/rasterimageview.h + lib/print/printoptionspage.h + + + General.Name,General.ImageSize,Exif.Photo.ExposureTime,Exif.Photo.Flash + + + + + + 100 + + + + true + + + + 0.5 + The percentage of memory used by Gwenview before it + warns the user and suggest saving changes. + + + + new + A list of filename extensions Gwenview should not try to + load. We exclude *.new as well because this is the extension + used for temporary files by KSaveFile. + + + false + + + + + + + + + + Horizontal + + + + 1 + + + + false + + + false + + + + + + + information + + + + + + General.Name,Exif.Image.DateTime + + + + + + 75 + + + true + + + + + + + + + + RasterImageView::AlphaBackgroundCheckBoard + + + #ffffff + + + + + + + + MouseWheelBehavior::Scroll + + + + false + + + + true + + + + 350, 100 + + + + + + + + + DocumentView::SoftwareAnimation + + + + false + + + + false + Defines what happens when going to image B after having zoomed an area of image A. + When true: zoom and position is kept. When false: image B is zoomed out to fit the screen. + + + + + + 128 + + + + 3./2. + + + + false + + + + + + + + + Sorting::Name + + + + 1 + + + + + true + + + + + + + Qt::AlignHCenter | Qt::AlignVCenter + + + + + + + + + + PrintOptionsPage::ScaleToPage + + + + false + + + + 15.0 + + + + 10.0 + + + + + + + + + + PrintOptionsPage::Centimeters + + + + true + + + + + + + false + + + + true + + + + false + + + + false + + + + 5.0 + + + + + + 24 + + + + + + false + + + + + diff --git a/gwenview/lib/gwenviewconfig.kcfgc b/gwenview/lib/gwenviewconfig.kcfgc new file mode 100644 index 00000000..da0bb9d6 --- /dev/null +++ b/gwenview/lib/gwenviewconfig.kcfgc @@ -0,0 +1,8 @@ +File=gwenviewconfig.kcfg +ClassName=GwenviewConfig +NameSpace=Gwenview +Singleton=true +Mutators=true +UseEnumTypes=true +IncludeFiles=lib/gwenviewlib_export.h +Visibility=GWENVIEWLIB_EXPORT diff --git a/gwenview/lib/gwenviewlib_export.h b/gwenview/lib/gwenviewlib_export.h new file mode 100644 index 00000000..9498ec85 --- /dev/null +++ b/gwenview/lib/gwenviewlib_export.h @@ -0,0 +1,36 @@ +/* This file is part of the KDE project + Copyright (C) 2006 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef GWENVIEWLIB_EXPORT_H +#define GWENVIEWLIB_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include + +#ifndef GWENVIEWLIB_EXPORT +# if defined(MAKE_GWENVIEWLIB_LIB) +/* We are building this library */ +# define GWENVIEWLIB_EXPORT KDE_EXPORT +# else +/* We are using this library */ +# define GWENVIEWLIB_EXPORT KDE_IMPORT +# endif +#endif + +#endif diff --git a/gwenview/lib/historymodel.cpp b/gwenview/lib/historymodel.cpp new file mode 100644 index 00000000..9360c7b1 --- /dev/null +++ b/gwenview/lib/historymodel.cpp @@ -0,0 +1,258 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "historymodel.moc" + +// Qt +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include + +namespace Gwenview +{ + +struct HistoryItem : public QStandardItem +{ + void save() const + { + KConfig config(mConfigPath, KConfig::SimpleConfig); + KConfigGroup group(&config, "general"); + group.writeEntry("url", mUrl); + group.writeEntry("dateTime", mDateTime.toString(Qt::ISODate)); + config.sync(); + } + + static HistoryItem* create(const KUrl& url, const QDateTime& dateTime, const QString& storageDir) + { + if (!KStandardDirs::makeDir(storageDir, 0600)) { + kError() << "Could not create history dir" << storageDir; + return 0; + } + KTemporaryFile file; + file.setAutoRemove(false); + file.setPrefix(storageDir); + file.setSuffix("rc"); + if (!file.open()) { + kError() << "Could not create history file"; + return 0; + } + + HistoryItem* item = new HistoryItem(url, dateTime, file.fileName()); + item->save(); + return item; + } + + static HistoryItem* load(const QString& fileName) + { + KConfig config(fileName, KConfig::SimpleConfig); + KConfigGroup group(&config, "general"); + + KUrl url(group.readEntry("url")); + if (!url.isValid()) { + kError() << "Invalid url" << url; + return 0; + } + QDateTime dateTime = QDateTime::fromString(group.readEntry("dateTime"), Qt::ISODate); + if (!dateTime.isValid()) { + kError() << "Invalid dateTime" << dateTime; + return 0; + } + + return new HistoryItem(url, dateTime, fileName); + } + + KUrl url() const + { + return mUrl; + } + + QDateTime dateTime() const + { + return mDateTime; + } + + void setDateTime(const QDateTime& dateTime) + { + if (mDateTime != dateTime) { + mDateTime = dateTime; + save(); + } + } + + void unlink() + { + QFile::remove(mConfigPath); + } + +private: + KUrl mUrl; + QDateTime mDateTime; + QString mConfigPath; + + HistoryItem(const KUrl& url, const QDateTime& dateTime, const QString& configPath) + : mUrl(url) + , mDateTime(dateTime) + , mConfigPath(configPath) { + mUrl.cleanPath(); + KUrl urlForView = mUrl; + urlForView.adjustPath(KUrl::RemoveTrailingSlash); + setText(urlForView.pathOrUrl()); + + QString iconName = KMimeType::iconNameForUrl(mUrl); + setIcon(KIcon(iconName)); + + setData(QVariant(mUrl), KFilePlacesModel::UrlRole); + + KFileItem fileItem(KFileItem::Unknown, KFileItem::Unknown, mUrl); + setData(QVariant(fileItem), KDirModel::FileItemRole); + + QString date = KGlobal::locale()->formatDateTime(mDateTime, KLocale::FancyLongDate); + setData(QVariant(i18n("Last visited: %1", date)), Qt::ToolTipRole); + } + + bool operator<(const QStandardItem& other) const { + return mDateTime > static_cast(&other)->mDateTime; + } +}; + +struct HistoryModelPrivate +{ + HistoryModel* q; + QString mStorageDir; + int mMaxCount; + + QMap mHistoryItemForUrl; + + void load() + { + QDir dir(mStorageDir); + if (!dir.exists()) { + return; + } + Q_FOREACH(const QString & name, dir.entryList(QStringList() << "*rc")) { + HistoryItem* item = HistoryItem::load(dir.filePath(name)); + if (!item) { + continue; + } + + KUrl itemUrl = item->url(); + if (UrlUtils::urlIsFastLocalFile(itemUrl)) { + if (!QFile::exists(itemUrl.path())) { + kDebug() << "Removing" << itemUrl.path() << "from recent folders. It does not exist anymore"; + item->unlink(); + delete item; + continue; + } + } + + HistoryItem* existingItem = mHistoryItemForUrl.value(item->url()); + if (existingItem) { + // We already know this url(!) update existing item dateTime + // and get rid of duplicate + if (existingItem->dateTime() < item->dateTime()) { + existingItem->setDateTime(item->dateTime()); + } + item->unlink(); + delete item; + } else { + mHistoryItemForUrl.insert(item->url(), item); + q->appendRow(item); + } + } + q->sort(0); + } + + void garbageCollect() + { + while (q->rowCount() > mMaxCount) { + HistoryItem* item = static_cast(q->takeRow(q->rowCount() - 1).at(0)); + mHistoryItemForUrl.remove(item->url()); + item->unlink(); + delete item; + } + } +}; + +HistoryModel::HistoryModel(QObject* parent, const QString& storageDir, int maxCount) +: QStandardItemModel(parent) +, d(new HistoryModelPrivate) +{ + d->q = this; + d->mStorageDir = storageDir; + d->mMaxCount = maxCount; + d->load(); +} + +HistoryModel::~HistoryModel() +{ + delete d; +} + +void HistoryModel::addUrl(const KUrl& url, const QDateTime& _dateTime) +{ + QDateTime dateTime = _dateTime.isValid() ? _dateTime : QDateTime::currentDateTime(); + HistoryItem* historyItem = d->mHistoryItemForUrl.value(url); + if (historyItem) { + historyItem->setDateTime(dateTime); + sort(0); + } else { + historyItem = HistoryItem::create(url, dateTime, d->mStorageDir); + if (!historyItem) { + kError() << "Could not save history for url" << url; + return; + } + d->mHistoryItemForUrl.insert(url, historyItem); + appendRow(historyItem); + sort(0); + d->garbageCollect(); + } +} + +bool HistoryModel::removeRows(int start, int count, const QModelIndex& parent) +{ + Q_ASSERT(!parent.isValid()); + for (int row = start + count - 1; row >= start ; --row) { + HistoryItem* historyItem = static_cast(item(row, 0)); + Q_ASSERT(historyItem); + d->mHistoryItemForUrl.remove(historyItem->url()); + historyItem->unlink(); + } + return QStandardItemModel::removeRows(start, count, parent); +} + +} // namespace diff --git a/gwenview/lib/historymodel.h b/gwenview/lib/historymodel.h new file mode 100644 index 00000000..40568217 --- /dev/null +++ b/gwenview/lib/historymodel.h @@ -0,0 +1,61 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef HISTORYMODEL_H +#define HISTORYMODEL_H + +// Qt +#include +#include + +// KDE + +// Local +#include + +class KUrl; + +namespace Gwenview +{ + +struct HistoryModelPrivate; +/** + * A model which maintains a list of urls in the dir specified by the + * storageDir parameter of its ctor. + * Each url is stored in a separate KConfig file to avoid concurrency issues. + */ +class GWENVIEWLIB_EXPORT HistoryModel : public QStandardItemModel +{ + Q_OBJECT +public: + HistoryModel(QObject* parent, const QString& storageDir, int maxCount = 20); + ~HistoryModel(); + + void addUrl(const KUrl&, const QDateTime& dateTime = QDateTime()); + + virtual bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); + +private: + HistoryModelPrivate* const d; +}; + +} // namespace + +#endif /* HISTORYMODEL_H */ diff --git a/gwenview/lib/hud/hudbutton.cpp b/gwenview/lib/hud/hudbutton.cpp new file mode 100644 index 00000000..0a12d0fc --- /dev/null +++ b/gwenview/lib/hud/hudbutton.cpp @@ -0,0 +1,202 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "hud/hudbutton.moc" + +// Local +#include + +// KDE +#include +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +struct LayoutInfo +{ + QRect iconRect; + QRect textRect; + QSize size; +}; + +struct HudButtonPrivate +{ + HudButton* q; + QAction* mAction; + + QIcon mIcon; + QString mText; + + bool mIsDown; + + void initLayoutInfo(LayoutInfo* info, const QSizeF& constraint) + { + HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::ButtonWidget); + const int padding = renderInfo.padding; + QSize minInnerSize = constraint.toSize() - QSize(2 * padding, 2 * padding); + + if (!mIcon.isNull()) { + int size = KIconLoader::global()->currentSize(KIconLoader::Small); + info->iconRect = QRect(padding, padding, size, qMax(size, minInnerSize.height())); + minInnerSize.rwidth() -= size; + } + if (!mText.isEmpty()) { + QFont font = KGlobalSettings::generalFont(); + QFontMetrics fm(font); + QSize size = fm.size(0, mText).expandedTo(minInnerSize); + info->textRect = QRect(padding, padding, size.width(), size.height()); + if (!info->iconRect.isNull()) { + info->textRect.translate(info->iconRect.right(), 0); + } + } + + QRectF rect = info->iconRect | info->textRect; + info->size = QSize(rect.right() + padding, rect.bottom() + padding); + } + + void initFromAction() + { + Q_ASSERT(mAction); + q->setIcon(mAction->icon()); + q->setText(mAction->text()); + } +}; + +HudButton::HudButton(QGraphicsItem* parent) +: QGraphicsWidget(parent) +, d(new HudButtonPrivate) +{ + d->q = this; + d->mAction = 0; + d->mIsDown = false; + setCursor(Qt::ArrowCursor); + setAcceptHoverEvents(true); +} + +HudButton::~HudButton() +{ + delete d; +} + +void HudButton::setIcon(const QIcon& icon) +{ + d->mIcon = icon; + updateGeometry(); +} + +void HudButton::setText(const QString& text) +{ + d->mText = text; + updateGeometry(); +} + +void HudButton::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget*) +{ + HudTheme::State state; + if (option->state.testFlag(QStyle::State_MouseOver)) { + state = d->mIsDown ? HudTheme::DownState : HudTheme::MouseOverState; + } else { + state = HudTheme::NormalState; + } + HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::ButtonWidget, state); + + painter->setPen(renderInfo.borderPen); + painter->setBrush(renderInfo.bgBrush); + painter->setRenderHint(QPainter::Antialiasing); + painter->drawRoundedRect(boundingRect().adjusted(.5, .5, -.5, -.5), renderInfo.borderRadius, renderInfo.borderRadius); + + LayoutInfo info; + d->initLayoutInfo(&info, size()); + + if (!d->mIcon.isNull()) { + painter->drawPixmap( + info.iconRect.topLeft(), + d->mIcon.pixmap(info.iconRect.size()) + ); + } + if (!d->mText.isEmpty()) { + painter->setPen(renderInfo.textPen); + painter->drawText( + info.textRect, + Qt::AlignCenter, + d->mText); + } +} + +QSizeF HudButton::sizeHint(Qt::SizeHint which, const QSizeF& constraint) const +{ + LayoutInfo info; + d->initLayoutInfo(&info, constraint); + if (which == Qt::MinimumSize || which == Qt::PreferredSize) { + return info.size; + } else { + return constraint.expandedTo(info.size); + } +} + +void HudButton::mousePressEvent(QGraphicsSceneMouseEvent*) +{ + d->mIsDown = true; + update(); +} + +void HudButton::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + d->mIsDown = false; + update(); + if (boundingRect().contains(event->pos())) { + clicked(); + } +} + +void HudButton::setDefaultAction(QAction* action) +{ + if (action != d->mAction) { + d->mAction = action; + if (!actions().contains(action)) { + addAction(action); + } + d->initFromAction(); + connect(this, SIGNAL(clicked()), + d->mAction, SLOT(trigger())); + } +} + +bool HudButton::event(QEvent* event) +{ + if (event->type() == QEvent::ActionChanged) { + d->initFromAction(); + } + return QGraphicsWidget::event(event); +} + +} // namespace diff --git a/gwenview/lib/hud/hudbutton.h b/gwenview/lib/hud/hudbutton.h new file mode 100644 index 00000000..5bef2da8 --- /dev/null +++ b/gwenview/lib/hud/hudbutton.h @@ -0,0 +1,70 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef HUDBUTTON_H +#define HUDBUTTON_H + +// Local + +// KDE + +// Qt +#include + +class QIcon; + +namespace Gwenview +{ + +struct HudButtonPrivate; +/** + * + */ +class HudButton : public QGraphicsWidget +{ + Q_OBJECT +public: + HudButton(QGraphicsItem* parent = 0); + ~HudButton(); + + void setIcon(const QIcon&); + + void setText(const QString&); + + void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*); + + void setDefaultAction(QAction*); + +Q_SIGNALS: + void clicked(); + +protected: + bool event(QEvent* event); + QSizeF sizeHint(Qt::SizeHint which, const QSizeF& constraint = QSizeF()) const; + void mousePressEvent(QGraphicsSceneMouseEvent* event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent* event); + +private: + HudButtonPrivate* const d; +}; + +} // namespace + +#endif /* HUDBUTTON_H */ diff --git a/gwenview/lib/hud/hudbuttonbox.cpp b/gwenview/lib/hud/hudbuttonbox.cpp new file mode 100644 index 00000000..397e7524 --- /dev/null +++ b/gwenview/lib/hud/hudbuttonbox.cpp @@ -0,0 +1,127 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2014 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +// Self +#include + +// Local +#include +#include +#include +#include + +// KDE +#include + +// Qt +#include +#include +#include +#include + +namespace Gwenview +{ + +struct HudButtonBoxPrivate +{ + QGraphicsLinearLayout* mLayout; + HudLabel* mLabel; + QList mButtonList; + HudCountDown* mCountDown; + + void updateButtonWidths() + { + qreal minWidth = 0; + Q_FOREACH(HudButton* button, mButtonList) { + minWidth = qMax(minWidth, button->preferredWidth()); + } + Q_FOREACH(HudButton* button, mButtonList) { + button->setMinimumWidth(minWidth); + } + } +}; + +HudButtonBox::HudButtonBox(QGraphicsWidget* parent) +: HudWidget(parent) +, d(new HudButtonBoxPrivate) +{ + d->mCountDown = 0; + QGraphicsWidget* content = new QGraphicsWidget(); + d->mLayout = new QGraphicsLinearLayout(Qt::Vertical, content); + d->mLabel = new HudLabel(); + d->mLayout->addItem(d->mLabel); + d->mLayout->setItemSpacing(0, 24); + init(content, HudWidget::OptionNone); + + setContentsMargins(6, 6, 6, 6); + setAutoDeleteOnFadeout(true); +} + +HudButtonBox::~HudButtonBox() +{ + delete d; +} + +void HudButtonBox::addCountDown(qreal ms) +{ + Q_ASSERT(!d->mCountDown); + d->mCountDown = new HudCountDown(this); + connect(d->mCountDown, SIGNAL(timeout()), SLOT(fadeOut())); + + GraphicsWidgetFloater* floater = new GraphicsWidgetFloater(this); + floater->setChildWidget(d->mCountDown); + floater->setAlignment(Qt::AlignRight | Qt::AlignBottom); + floater->setHorizontalMargin(6); + floater->setVerticalMargin(6); + + d->mCountDown->start(ms); +} + +HudButton* HudButtonBox::addAction(QAction* action, const QString& overrideText) +{ + QString text = overrideText.isEmpty() ? action->text() : overrideText; + HudButton* button = addButton(text); + connect(button, SIGNAL(clicked()), action, SLOT(trigger())); + return button; +} + +HudButton* HudButtonBox::addButton(const QString& text) +{ + HudButton* button = new HudButton(); + connect(button, SIGNAL(clicked()), SLOT(fadeOut())); + button->setText(text); + d->mLayout->addItem(button); + d->mLayout->setAlignment(button, Qt::AlignCenter); + d->mButtonList += button; + + return button; +} + +void HudButtonBox::setText(const QString& msg) +{ + d->mLabel->setText(msg); +} + +void HudButtonBox::showEvent(QShowEvent* event) +{ + HudWidget::showEvent(event); + d->updateButtonWidths(); +} + + +} // namespace diff --git a/gwenview/lib/hud/hudbuttonbox.h b/gwenview/lib/hud/hudbuttonbox.h new file mode 100644 index 00000000..79fdc149 --- /dev/null +++ b/gwenview/lib/hud/hudbuttonbox.h @@ -0,0 +1,67 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2014 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef HUDBUTTONBOX_H +#define HUDBUTTONBOX_H + +#include + +// Local +#include + +// KDE + +// Qt + +class QAction; +class QGraphicsWidget; + +namespace Gwenview +{ + +class HudButton; + +struct HudButtonBoxPrivate; +/** + * A hud widget which shows a list of buttons + */ +class GWENVIEWLIB_EXPORT HudButtonBox : public HudWidget +{ + Q_OBJECT +public: + HudButtonBox(QGraphicsWidget* parent = 0); + ~HudButtonBox(); + + void setText(const QString& text); + + HudButton* addButton(const QString& text); + + HudButton* addAction(QAction* action, const QString& overrideText = QString()); + + void addCountDown(qreal ms); + +protected: + void showEvent(QShowEvent* event); + +private: + HudButtonBoxPrivate* const d; +}; + +} // namespace + +#endif /* HUDBUTTONBOX_H */ diff --git a/gwenview/lib/hud/hudcountdown.cpp b/gwenview/lib/hud/hudcountdown.cpp new file mode 100644 index 00000000..2e137faf --- /dev/null +++ b/gwenview/lib/hud/hudcountdown.cpp @@ -0,0 +1,93 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2014 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +// Self +#include + +// Local +#include + +// KDE + +// Qt +#include +#include + +namespace Gwenview +{ + +struct HudCountDownPrivate +{ + QTimeLine* mTimeLine; +}; + +HudCountDown::HudCountDown(QGraphicsWidget* parent) +: QGraphicsWidget(parent) +, d(new HudCountDownPrivate) +{ + d->mTimeLine = new QTimeLine(0, this); + d->mTimeLine->setDirection(QTimeLine::Backward); + connect(d->mTimeLine, SIGNAL(valueChanged(qreal)), SLOT(doUpdate())); + connect(d->mTimeLine, SIGNAL(finished()), SIGNAL(timeout())); + + // Use an odd value so that the vertical line is aligned to pixel + // boundaries + setMinimumSize(17, 17); +} + +HudCountDown::~HudCountDown() +{ + delete d; +} + +void HudCountDown::start(qreal ms) +{ + d->mTimeLine->setDuration(ms); + d->mTimeLine->start(); + update(); +} + +void HudCountDown::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) +{ + HudTheme::RenderInfo info = HudTheme::renderInfo(HudTheme::CountDown); + painter->setRenderHint(QPainter::Antialiasing); + const int circle = 5760; + const int start = circle / 4; // Start at 12h, not 3h + const int end = int(circle * d->mTimeLine->currentValue()); + painter->setBrush(info.bgBrush); + painter->setPen(info.borderPen); + + QRectF square = boundingRect().adjusted(.5, .5, -.5, -.5); + qreal width = square.width(); + qreal height = square.height(); + if (width < height) { + square.setHeight(width); + square.moveTop((height - width) / 2); + } else { + square.setWidth(height); + square.moveLeft((width - height) / 2); + } + painter->drawPie(square, start, end); +} + +void HudCountDown::doUpdate() +{ + update(); +} + +} // namespace diff --git a/gwenview/lib/hud/hudcountdown.h b/gwenview/lib/hud/hudcountdown.h new file mode 100644 index 00000000..fa05a0a4 --- /dev/null +++ b/gwenview/lib/hud/hudcountdown.h @@ -0,0 +1,60 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2014 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef HUDCOUNTDOWN_H +#define HUDCOUNTDOWN_H + +// Local + +// KDE + +// Qt +#include + +namespace Gwenview +{ + +struct HudCountDownPrivate; + +/** + * Displays a count-down pie-chart + */ +class HudCountDown : public QGraphicsWidget +{ + Q_OBJECT +public: + HudCountDown(QGraphicsWidget* parent = 0); + ~HudCountDown(); + + void start(qreal ms); + + void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*); + +Q_SIGNALS: + void timeout(); + +private Q_SLOTS: + void doUpdate(); + +private: + HudCountDownPrivate* const d; +}; + +} // namespace + +#endif /* HUDCOUNTDOWN_H */ diff --git a/gwenview/lib/hud/hudlabel.cpp b/gwenview/lib/hud/hudlabel.cpp new file mode 100644 index 00000000..f0d0745d --- /dev/null +++ b/gwenview/lib/hud/hudlabel.cpp @@ -0,0 +1,75 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "hud/hudlabel.moc" + +// Local +#include + +// KDE +#include +#include + +// Qt +#include +#include +#include +#include + +namespace Gwenview +{ + +struct HudLabelPrivate +{ + QString mText; +}; + +HudLabel::HudLabel(QGraphicsItem* parent) +: QGraphicsWidget(parent) +, d(new HudLabelPrivate) +{ + setCursor(Qt::ArrowCursor); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); +} + +HudLabel::~HudLabel() +{ + delete d; +} + +void HudLabel::setText(const QString& text) +{ + d->mText = text; + QFont font = KGlobalSettings::generalFont(); + QFontMetrics fm(font); + QSize minSize = fm.size(0, d->mText); + setMinimumSize(minSize); + setPreferredSize(minSize); +} + +void HudLabel::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) +{ + HudTheme::RenderInfo info = HudTheme::renderInfo(HudTheme::FrameWidget); + painter->setPen(info.textPen); + painter->drawText(boundingRect(), Qt::AlignCenter, d->mText); +} + +} // namespace diff --git a/gwenview/lib/hud/hudlabel.h b/gwenview/lib/hud/hudlabel.h new file mode 100644 index 00000000..3cf80ce7 --- /dev/null +++ b/gwenview/lib/hud/hudlabel.h @@ -0,0 +1,57 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef HUDLABEL_H +#define HUDLABEL_H + +#include + +// Local + +// KDE + +// Qt +#include + +namespace Gwenview +{ + +struct HudLabelPrivate; +/** + * + */ +class GWENVIEWLIB_EXPORT HudLabel : public QGraphicsWidget +{ + Q_OBJECT +public: + HudLabel(QGraphicsItem* parent = 0); + ~HudLabel(); + + void setText(const QString&); + + void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*); + +private: + HudLabelPrivate* const d; +}; + +} // namespace + +#endif /* HUDLABEL_H */ diff --git a/gwenview/lib/hud/hudmessagebubble.cpp b/gwenview/lib/hud/hudmessagebubble.cpp new file mode 100644 index 00000000..63048273 --- /dev/null +++ b/gwenview/lib/hud/hudmessagebubble.cpp @@ -0,0 +1,89 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "hudmessagebubble.moc" + +// Qt +#include + +// KDE +#include +#include + +// Local +#include +#include +#include +#include + +namespace Gwenview +{ + +static const int TIMEOUT = 10000; + +struct HudMessageBubblePrivate +{ + QGraphicsWidget* mWidget; + QGraphicsLinearLayout* mLayout; + HudCountDown* mCountDown; + HudLabel* mLabel; +}; + +HudMessageBubble::HudMessageBubble(QGraphicsWidget* parent) +: HudWidget(parent) +, d(new HudMessageBubblePrivate) +{ + d->mWidget = new QGraphicsWidget; + d->mCountDown = new HudCountDown; + d->mLabel = new HudLabel; + + connect(d->mCountDown, SIGNAL(timeout()), SLOT(fadeOut())); + connect(this, SIGNAL(fadedOut()), SLOT(deleteLater())); + + d->mLayout = new QGraphicsLinearLayout(d->mWidget); + d->mLayout->setContentsMargins(0, 0, 0, 0); + d->mLayout->addItem(d->mCountDown); + d->mLayout->addItem(d->mLabel); + + init(d->mWidget, HudWidget::OptionCloseButton); + d->mCountDown->start(TIMEOUT); +} + +HudMessageBubble::~HudMessageBubble() +{ + delete d; +} + +void HudMessageBubble::setText(const QString& text) +{ + d->mLabel->setText(text); +} + +HudButton* HudMessageBubble::addButton(const KGuiItem& guiItem) +{ + HudButton* button = new HudButton; + button->setText(guiItem.text()); + button->setIcon(guiItem.icon()); + d->mLayout->addItem(button); + return button; +} + +} // namespace diff --git a/gwenview/lib/hud/hudmessagebubble.h b/gwenview/lib/hud/hudmessagebubble.h new file mode 100644 index 00000000..a4bfab8b --- /dev/null +++ b/gwenview/lib/hud/hudmessagebubble.h @@ -0,0 +1,61 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef HUDMESSAGEBUBBLE_H +#define HUDMESSAGEBUBBLE_H + +#include + +// Qt + +// KDE + +// Local +#include + +class KGuiItem; + +namespace Gwenview +{ +class HudButton; + +struct HudMessageBubblePrivate; +/** + * Shows a bubble with a QLabel and optional buttons. + * Automatically goes away after a while. + */ +class GWENVIEWLIB_EXPORT HudMessageBubble : public HudWidget +{ + Q_OBJECT +public: + HudMessageBubble(QGraphicsWidget* parent = 0); + ~HudMessageBubble(); + + void setText(const QString& text); + + HudButton* addButton(const KGuiItem&); + +private: + HudMessageBubblePrivate* const d; +}; + +} // namespace + +#endif /* HUDMESSAGEBUBBLE_H */ diff --git a/gwenview/lib/hud/hudslider.cpp b/gwenview/lib/hud/hudslider.cpp new file mode 100644 index 00000000..82f65bbe --- /dev/null +++ b/gwenview/lib/hud/hudslider.cpp @@ -0,0 +1,389 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "hud/hudslider.moc" + +// Local +#include + +// KDE +#include +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +static const int FIRST_REPEAT_DELAY = 500; + +struct HudSliderPrivate +{ + HudSlider* q; + int mMin, mMax, mPageStep, mSingleStep; + int mSliderPosition; + int mRepeatX; + QAbstractSlider::SliderAction mRepeatAction; + int mValue; + bool mIsDown; + + QRectF mHandleRect; + + bool hasValidRange() const + { + return mMax > mMin; + } + + void updateHandleRect() + { + static const HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetHandle); + static const int radius = renderInfo.borderRadius; + + const QRectF sliderRect = q->boundingRect(); + const qreal posX = xForPosition(mSliderPosition) - radius; + const qreal posY = sliderRect.height() / 2 - radius; + mHandleRect = QRectF(posX, posY, radius * 2, radius * 2); + } + + int positionForX(qreal x) const + { + static const HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetHandle); + static const int radius = renderInfo.borderRadius; + + const qreal sliderWidth = q->boundingRect().width(); + + x -= radius; + if (QApplication::isRightToLeft()) { + x = sliderWidth - 2 * radius - x; + } + return mMin + int(x / (sliderWidth - 2 * radius) * (mMax - mMin)); + } + + qreal xForPosition(int pos) const + { + static const HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetHandle); + static const int radius = renderInfo.borderRadius; + + const qreal sliderWidth = q->boundingRect().width(); + qreal x = (qreal(pos - mMin) / (mMax - mMin)) * (sliderWidth - 2 * radius); + if (QApplication::isRightToLeft()) { + x = sliderWidth - 2 * radius - x; + } + return x + radius; + } +}; + +HudSlider::HudSlider(QGraphicsItem* parent) +: QGraphicsWidget(parent) +, d(new HudSliderPrivate) +{ + d->q = this; + d->mMin = 0; + d->mMax = 100; + d->mPageStep = 10; + d->mSingleStep = 1; + d->mSliderPosition = d->mValue = 0; + d->mIsDown = false; + d->mRepeatAction = QAbstractSlider::SliderNoAction; + d->updateHandleRect(); + setCursor(Qt::ArrowCursor); + setAcceptHoverEvents(true); + setFocusPolicy(Qt::WheelFocus); +} + +HudSlider::~HudSlider() +{ + delete d; +} + +void HudSlider::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget*) +{ + bool drawHandle = d->hasValidRange(); + HudTheme::State state; + if (drawHandle && option->state.testFlag(QStyle::State_MouseOver)) { + state = d->mIsDown ? HudTheme::DownState : HudTheme::MouseOverState; + } else { + state = HudTheme::NormalState; + } + painter->setRenderHint(QPainter::Antialiasing); + + const QRectF sliderRect = boundingRect(); + + // Groove + HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetGroove, state); + painter->setPen(renderInfo.borderPen); + painter->setBrush(renderInfo.bgBrush); + qreal centerY = d->mHandleRect.center().y(); + QRectF grooveRect = QRectF( + 0, centerY - renderInfo.borderRadius, + sliderRect.width(), + 2 * renderInfo.borderRadius + ); + + if (drawHandle) { + // Clip out handle + QPainterPath clipPath; + clipPath.addRect(QRectF(QPointF(0, 0), d->mHandleRect.bottomLeft()).adjusted(0, 0, 1, 0)); + clipPath.addRect(QRectF(d->mHandleRect.topRight(), sliderRect.bottomRight()).adjusted(-1, 0, 0, 0)); + painter->setClipPath(clipPath); + } + painter->drawRoundedRect(grooveRect.adjusted(.5, .5, -.5, -.5), renderInfo.borderRadius, renderInfo.borderRadius); + if (!drawHandle) { + return; + } + painter->setClipping(false); + + // Handle + renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetHandle, state); + painter->setPen(renderInfo.borderPen); + painter->setBrush(renderInfo.bgBrush); + painter->drawRoundedRect(d->mHandleRect.adjusted(.5, .5, -.5, -.5), renderInfo.borderRadius, renderInfo.borderRadius); +} + +void HudSlider::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + if (!d->hasValidRange()) { + return; + } + const int pos = d->positionForX(event->pos().x()); + if (d->mHandleRect.contains(event->pos())) { + switch (event->button()) { + case Qt::LeftButton: + d->mIsDown = true; + break; + case Qt::MiddleButton: + setSliderPosition(pos); + triggerAction(QAbstractSlider::SliderMove); + break; + default: + break; + } + } else { + d->mRepeatX = event->pos().x(); + d->mRepeatAction = pos < d->mSliderPosition + ? QAbstractSlider::SliderPageStepSub + : QAbstractSlider::SliderPageStepAdd; + doRepeatAction(FIRST_REPEAT_DELAY); + } + update(); +} + +void HudSlider::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + if (!d->hasValidRange()) { + return; + } + if (d->mIsDown) { + setSliderPosition(d->positionForX(event->pos().x())); + triggerAction(QAbstractSlider::SliderMove); + update(); + } +} + +void HudSlider::mouseReleaseEvent(QGraphicsSceneMouseEvent* /*event*/) +{ + if (!d->hasValidRange()) { + return; + } + d->mIsDown = false; + d->mRepeatAction = QAbstractSlider::SliderNoAction; + update(); +} + +void HudSlider::wheelEvent(QGraphicsSceneWheelEvent* event) +{ + if (!d->hasValidRange()) { + return; + } + int step = qMin(QApplication::wheelScrollLines() * d->mSingleStep, d->mPageStep); + if ((event->modifiers() & Qt::ControlModifier) || (event->modifiers() & Qt::ShiftModifier)) { + step = d->mPageStep; + } + setSliderPosition(d->mSliderPosition + event->delta() * step / 120); + triggerAction(QAbstractSlider::SliderMove); +} + +void HudSlider::keyPressEvent(QKeyEvent* event) +{ + if (!d->hasValidRange()) { + return; + } + bool rtl = QApplication::isRightToLeft(); + switch (event->key()) { + case Qt::Key_Left: + triggerAction(rtl ? QAbstractSlider::SliderSingleStepAdd : QAbstractSlider::SliderSingleStepSub); + break; + case Qt::Key_Right: + triggerAction(rtl ? QAbstractSlider::SliderSingleStepSub : QAbstractSlider::SliderSingleStepAdd); + break; + case Qt::Key_PageUp: + triggerAction(QAbstractSlider::SliderPageStepSub); + break; + case Qt::Key_PageDown: + triggerAction(QAbstractSlider::SliderPageStepAdd); + break; + case Qt::Key_Home: + triggerAction(QAbstractSlider::SliderToMinimum); + break; + case Qt::Key_End: + triggerAction(QAbstractSlider::SliderToMaximum); + break; + default: + event->ignore(); + break; + } +} + +void HudSlider::keyReleaseEvent(QKeyEvent* /*event*/) +{ + if (!d->hasValidRange()) { + return; + } + d->mRepeatAction = QAbstractSlider::SliderNoAction; +} + +void HudSlider::setRange(int min, int max) +{ + if (min == d->mMin && max == d->mMax) { + return; + } + d->mMin = min; + d->mMax = max; + setValue(d->mValue); // ensure value is within min and max + d->updateHandleRect(); + update(); +} + +void HudSlider::setPageStep(int step) +{ + d->mPageStep = step; +} + +void HudSlider::setSingleStep(int step) +{ + d->mSingleStep = step; +} + +void HudSlider::setValue(int value) +{ + value = qBound(d->mMin, value, d->mMax); + if (value != d->mValue) { + d->mValue = value; + setSliderPosition(value); + update(); + valueChanged(d->mValue); + } +} + +int HudSlider::sliderPosition() const +{ + return d->mSliderPosition; +} + +void HudSlider::setSliderPosition(int pos) +{ + pos = qBound(d->mMin, pos, d->mMax); + if (pos != d->mSliderPosition) { + d->mSliderPosition = pos; + d->updateHandleRect(); + update(); + } +} + +bool HudSlider::isSliderDown() const +{ + return d->mIsDown; +} + +void HudSlider::triggerAction(QAbstractSlider::SliderAction action) +{ + switch (action) { + case QAbstractSlider::SliderSingleStepAdd: + setSliderPosition(d->mValue + d->mSingleStep); + break; + case QAbstractSlider::SliderSingleStepSub: + setSliderPosition(d->mValue - d->mSingleStep); + break; + case QAbstractSlider::SliderPageStepAdd: + setSliderPosition(d->mValue + d->mPageStep); + break; + case QAbstractSlider::SliderPageStepSub: + setSliderPosition(d->mValue - d->mPageStep); + break; + case QAbstractSlider::SliderToMinimum: + setSliderPosition(d->mMin); + break; + case QAbstractSlider::SliderToMaximum: + setSliderPosition(d->mMax); + break; + case QAbstractSlider::SliderMove: + case QAbstractSlider::SliderNoAction: + break; + }; + actionTriggered(action); + setValue(d->mSliderPosition); +} + +void HudSlider::doRepeatAction(int time) +{ + int step = 0; + switch (d->mRepeatAction) { + case QAbstractSlider::SliderSingleStepAdd: + case QAbstractSlider::SliderSingleStepSub: + step = d->mSingleStep; + break; + case QAbstractSlider::SliderPageStepAdd: + case QAbstractSlider::SliderPageStepSub: + step = d->mPageStep; + break; + case QAbstractSlider::SliderToMinimum: + case QAbstractSlider::SliderToMaximum: + case QAbstractSlider::SliderMove: + kWarning() << "Not much point in repeating action of type" << d->mRepeatAction; + return; + case QAbstractSlider::SliderNoAction: + return; + } + + int pos = d->positionForX(d->mRepeatX); + if (qAbs(pos - d->mSliderPosition) >= step) { + // We are far enough from the position where the mouse button was held + // down to be able to repeat the action one more time + triggerAction(d->mRepeatAction); + QTimer::singleShot(time, this, SLOT(doRepeatAction())); + } else { + // We are too close to the held down position, reach the position and + // don't repeat + d->mRepeatAction = QAbstractSlider::SliderNoAction; + setSliderPosition(pos); + triggerAction(QAbstractSlider::SliderMove); + return; + } +} + +} // namespace diff --git a/gwenview/lib/hud/hudslider.h b/gwenview/lib/hud/hudslider.h new file mode 100644 index 00000000..9c211394 --- /dev/null +++ b/gwenview/lib/hud/hudslider.h @@ -0,0 +1,82 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef HUDSLIDER_H +#define HUDSLIDER_H + +// Local + +// KDE + +// Qt +#include +#include + +namespace Gwenview +{ + +struct HudSliderPrivate; +/** + * A QGraphicsView slider. + * Provides more-or-less the same API as QSlider. + */ +class HudSlider : public QGraphicsWidget +{ + Q_OBJECT +public: + HudSlider(QGraphicsItem* parent = 0); + ~HudSlider(); + + void setRange(int min, int max); + void setPageStep(int step); + void setSingleStep(int step); + void setValue(int value); + + int sliderPosition() const; + void setSliderPosition(int); + + bool isSliderDown() const; + + void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*); + + void triggerAction(QAbstractSlider::SliderAction); + +Q_SIGNALS: + void valueChanged(int); + void actionTriggered(int); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent* event); + void mouseMoveEvent(QGraphicsSceneMouseEvent* event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent* event); + void wheelEvent(QGraphicsSceneWheelEvent* event); + void keyPressEvent(QKeyEvent* event); + void keyReleaseEvent(QKeyEvent* event); + +private Q_SLOTS: + void doRepeatAction(int time = 50); + +private: + HudSliderPrivate* const d; +}; + +} // namespace + +#endif /* HUDSLIDER_H */ diff --git a/gwenview/lib/hud/hudtheme.cpp b/gwenview/lib/hud/hudtheme.cpp new file mode 100644 index 00000000..6704d8fa --- /dev/null +++ b/gwenview/lib/hud/hudtheme.cpp @@ -0,0 +1,141 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include + +// Qt +#include + +// KDE + +// Local + +namespace Gwenview +{ + +namespace HudTheme +{ + +struct RenderInfoSet +{ + QMap infos; +}; + +static QLinearGradient createGradient() +{ + QLinearGradient gradient(0, 0, 0, 1.); + gradient.setCoordinateMode(QGradient::ObjectBoundingMode); + return gradient; +} + +RenderInfo renderInfo(WidgetType widget, State state) +{ + static QMap renderInfoMap; + if (renderInfoMap.isEmpty()) { + QLinearGradient gradient; + + // Button, normal + RenderInfo button; + button.borderRadius = 5; + button.borderPen = QPen(QColor::fromHsvF(0, 0, .25, .8)); + gradient = createGradient(); + gradient.setColorAt(0, QColor::fromHsvF(0, 0, .34, .66)); + gradient.setColorAt(0.5, QColor::fromHsvF(0, 0, 0, .66)); + button.bgBrush = gradient; + button.padding = 6; + button.textPen = QPen(QColor("#ccc")); + renderInfoMap[ButtonWidget].infos[NormalState] = button; + + // Button, over + RenderInfo overButton = button; + overButton.bgBrush = gradient; + overButton.borderPen = QPen(QColor("#ccc")); + renderInfoMap[ButtonWidget].infos[MouseOverState] = overButton; + + // Button, down + RenderInfo downButton = button; + gradient = createGradient(); + gradient.setColorAt(0, QColor::fromHsvF(0, 0, .12)); + gradient.setColorAt(0.6, Qt::black); + downButton.bgBrush = gradient; + downButton.borderPen = QPen(QColor("#444")); + renderInfoMap[ButtonWidget].infos[DownState] = downButton; + + // Frame + RenderInfo frame; + gradient = createGradient(); + gradient.setColorAt(0, QColor::fromHsvF(0, 0, 0.1, .6)); + gradient.setColorAt(.6, QColor::fromHsvF(0, 0, 0, .6)); + frame.bgBrush = gradient; + frame.borderPen = QPen(QColor::fromHsvF(0, 0, .4, .6)); + frame.borderRadius = 8; + frame.textPen = QPen(QColor("#ccc")); + renderInfoMap[FrameWidget].infos[NormalState] = frame; + + // CountDown + RenderInfo countDownWidget; + countDownWidget.bgBrush = QColor::fromHsvF(0, 0, .5); + countDownWidget.borderPen = QPen(QColor::fromHsvF(0, 0, .8)); + renderInfoMap[CountDown].infos[NormalState] = countDownWidget; + + // SliderWidgetHandle + RenderInfo sliderWidgetHandle = button; + sliderWidgetHandle.borderPen = QPen(QColor("#666")); + sliderWidgetHandle.borderRadius = 7; + renderInfoMap[SliderWidgetHandle].infos[NormalState] = sliderWidgetHandle; + + // SliderWidgetHandle, over + sliderWidgetHandle = overButton; + sliderWidgetHandle.borderPen = QPen(QColor("#ccc")); + sliderWidgetHandle.borderRadius = 7; + renderInfoMap[SliderWidgetHandle].infos[MouseOverState] = sliderWidgetHandle; + + // SliderWidgetHandle, down + sliderWidgetHandle = downButton; + sliderWidgetHandle.borderRadius = 7; + renderInfoMap[SliderWidgetHandle].infos[DownState] = sliderWidgetHandle; + + // SliderWidgetGroove + RenderInfo sliderWidgetGroove = button; + sliderWidgetGroove.borderPen = QPen(QColor("#666")); + gradient = createGradient(); + gradient.setColorAt(0, Qt::black); + gradient.setColorAt(1, QColor("#444")); + sliderWidgetGroove.bgBrush = gradient; + sliderWidgetGroove.borderRadius = 3; + renderInfoMap[SliderWidgetGroove].infos[NormalState] = sliderWidgetGroove; + + // SliderWidgetGroove, over + RenderInfo overSliderWidgetGroove = sliderWidgetGroove; + overSliderWidgetGroove.borderPen = QPen(QColor("#ccc")); + renderInfoMap[SliderWidgetGroove].infos[MouseOverState] = overSliderWidgetGroove; + } + RenderInfo normalInfo = renderInfoMap[widget].infos.value(NormalState); + if (state == NormalState) { + return normalInfo; + } else { + return renderInfoMap[widget].infos.value(state, normalInfo); + } +} + +} // HudTheme namespace + +} // Gwenview namespace diff --git a/gwenview/lib/hud/hudtheme.h b/gwenview/lib/hud/hudtheme.h new file mode 100644 index 00000000..3af89f70 --- /dev/null +++ b/gwenview/lib/hud/hudtheme.h @@ -0,0 +1,75 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef HUDTHEME_H +#define HUDTHEME_H + +#include + +// Qt +#include +#include + +// KDE + +// Local + +namespace Gwenview +{ + +namespace HudTheme +{ + +struct RenderInfo +{ + RenderInfo() + : borderRadius(0) + , padding(0) + {} + qreal borderRadius; + QPen borderPen; + QBrush bgBrush; + qreal padding; + QPen textPen; +}; + +enum State +{ + NormalState, + MouseOverState, + DownState +}; + +enum WidgetType +{ + ButtonWidget, + FrameWidget, + CountDown, + SliderWidgetHandle, + SliderWidgetGroove +}; + +GWENVIEWLIB_EXPORT RenderInfo renderInfo(WidgetType, State = NormalState); + +} // HudTheme namespace + +} // Gwenview namespace + +#endif /* HUDTHEME_H */ diff --git a/gwenview/lib/hud/hudwidget.cpp b/gwenview/lib/hud/hudwidget.cpp new file mode 100644 index 00000000..0b600471 --- /dev/null +++ b/gwenview/lib/hud/hudwidget.cpp @@ -0,0 +1,159 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "hud/hudwidget.moc" + +// Qt +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include + +// Local +#include +#include + +namespace Gwenview +{ + +struct HudWidgetPrivate +{ + HudWidget* q; + QPropertyAnimation* mAnim; + QGraphicsWidget* mMainWidget; + HudButton* mCloseButton; + bool mAutoDeleteOnFadeout; + + void fadeTo(qreal value) + { + if (qFuzzyCompare(q->opacity(), value)) { + return; + } + mAnim->stop(); + mAnim->setStartValue(q->opacity()); + mAnim->setEndValue(value); + mAnim->start(); + } +}; + +HudWidget::HudWidget(QGraphicsWidget* parent) +: QGraphicsWidget(parent) +, d(new HudWidgetPrivate) +{ + d->q = this; + d->mAnim = new QPropertyAnimation(this, "opacity", this); + d->mMainWidget = 0; + d->mCloseButton = 0; + d->mAutoDeleteOnFadeout = false; + + connect(d->mAnim, SIGNAL(finished()), SLOT(slotFadeAnimationFinished())); +} + +HudWidget::~HudWidget() +{ + delete d; +} + +void HudWidget::init(QWidget* mainWidget, Options options) +{ + QGraphicsProxyWidget* proxy = new QGraphicsProxyWidget(this); + proxy->setWidget(mainWidget); + init(proxy, options); +} + +void HudWidget::init(QGraphicsWidget* mainWidget, Options options) +{ + if (options & OptionOpaque) { + setProperty("opaque", QVariant(true)); + } + + QGraphicsLinearLayout* layout = new QGraphicsLinearLayout(this); + layout->setContentsMargins(4, 4, 4, 4); + d->mMainWidget = mainWidget; + if (d->mMainWidget) { + if (d->mMainWidget->layout()) { + d->mMainWidget->layout()->setContentsMargins(0, 0, 0, 0); + } + layout->addItem(d->mMainWidget); + } + + if (options & OptionCloseButton) { + d->mCloseButton = new HudButton(this); + d->mCloseButton->setIcon(KIcon("window-close")); + d->mCloseButton->setToolTip(i18nc("@info:tooltip", "Close")); + + layout->addItem(d->mCloseButton); + layout->setAlignment(d->mCloseButton, Qt::AlignTop | Qt::AlignHCenter); + + connect(d->mCloseButton, SIGNAL(clicked()), SLOT(slotCloseButtonClicked())); + } +} + +void HudWidget::slotCloseButtonClicked() +{ + close(); + closed(); +} + +void HudWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) +{ + HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::FrameWidget); + painter->setPen(renderInfo.borderPen); + painter->setRenderHint(QPainter::Antialiasing); + painter->setBrush(renderInfo.bgBrush); + painter->drawRoundedRect(boundingRect().adjusted(.5, .5, -.5, -.5), renderInfo.borderRadius, renderInfo.borderRadius); +} + +void HudWidget::fadeIn() +{ + d->fadeTo(1.); +} + +void HudWidget::fadeOut() +{ + d->fadeTo(0.); +} + +void HudWidget::slotFadeAnimationFinished() +{ + if (qFuzzyCompare(opacity(), 1)) { + fadedIn(); + } else { + fadedOut(); + if (d->mAutoDeleteOnFadeout) { + deleteLater(); + } + } +} + +void HudWidget::setAutoDeleteOnFadeout(bool value) +{ + d->mAutoDeleteOnFadeout = value; +} + +} // namespace diff --git a/gwenview/lib/hud/hudwidget.h b/gwenview/lib/hud/hudwidget.h new file mode 100644 index 00000000..55ad323b --- /dev/null +++ b/gwenview/lib/hud/hudwidget.h @@ -0,0 +1,80 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef HUDWIDGET_H +#define HUDWIDGET_H + +#include + +// Qt +#include +#include + +// KDE + +// Local + +namespace Gwenview +{ + +struct HudWidgetPrivate; +class GWENVIEWLIB_EXPORT HudWidget : public QGraphicsWidget +{ + Q_OBJECT +public: + enum Option { + OptionNone = 0, + OptionCloseButton = 1 << 1, + OptionOpaque = 1 << 2 + }; + Q_DECLARE_FLAGS(Options, Option) + + HudWidget(QGraphicsWidget* parent = 0); + ~HudWidget(); + + void init(QWidget*, Options options); + void init(QGraphicsWidget*, Options options); + + void setAutoDeleteOnFadeout(bool autoDelete); + + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); + +public Q_SLOTS: + void fadeIn(); + void fadeOut(); + +Q_SIGNALS: + void closed(); + void fadedIn(); + void fadedOut(); + +private Q_SLOTS: + void slotCloseButtonClicked(); + void slotFadeAnimationFinished(); + +private: + HudWidgetPrivate* const d; +}; + +} // namespace + +Q_DECLARE_OPERATORS_FOR_FLAGS(Gwenview::HudWidget::Options) + +#endif /* HUDWIDGET_H */ diff --git a/gwenview/lib/imageformats/imageformats.cpp b/gwenview/lib/imageformats/imageformats.cpp new file mode 100644 index 00000000..27022c74 --- /dev/null +++ b/gwenview/lib/imageformats/imageformats.cpp @@ -0,0 +1,54 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "imageformats.h" + +// Qt +#include + +// Include QImageIOPlugin to get qRegisterStaticPluginInstanceFunction(), which +// does not come with QPluginLoader. +#include + +// KDE + +// Local + +namespace Gwenview +{ + +QObject* qt_plugin_instance_JpegPlugin(); + +namespace ImageFormats +{ + +static bool sPluginsRegistered = false; +void registerPlugins() +{ + if (sPluginsRegistered) { + return; + } + sPluginsRegistered = true; + qRegisterStaticPluginInstanceFunction(qt_plugin_instance_JpegPlugin); +} + +} // namespace +} // namespace diff --git a/gwenview/lib/imageformats/imageformats.h b/gwenview/lib/imageformats/imageformats.h new file mode 100644 index 00000000..af948f6e --- /dev/null +++ b/gwenview/lib/imageformats/imageformats.h @@ -0,0 +1,43 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef IMAGEFORMATS_H +#define IMAGEFORMATS_H + +#include + +// Qt + +// KDE + +// Local + +namespace Gwenview +{ + +namespace ImageFormats +{ + +GWENVIEWLIB_EXPORT void registerPlugins(); + +} // namespace +} // namespace + +#endif /* IMAGEFORMATS_H */ diff --git a/gwenview/lib/imageformats/jpeghandler.cpp b/gwenview/lib/imageformats/jpeghandler.cpp new file mode 100644 index 00000000..f3dd4928 --- /dev/null +++ b/gwenview/lib/imageformats/jpeghandler.cpp @@ -0,0 +1,537 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +-- + +The content of this file is based on qjpeghandler.cpp +Copyright (C) 1992-2008 Trolltech ASA. All rights reserved. + +*/ +// Self +#include "jpeghandler.h" + +// Qt +#include +#include +#include + +// KDE +#include + +// libjpeg +#include +#define XMD_H +extern "C" { +#include +} + +// Local +#include "../iodevicejpegsourcemanager.h" + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +struct JpegFatalError : public jpeg_error_mgr +{ + jmp_buf mJmpBuffer; + + static void handler(j_common_ptr cinfo) + { + JpegFatalError* error = static_cast(cinfo->err); + (error->output_message)(cinfo); + longjmp(error->mJmpBuffer, 1); + } +}; + +static void expand24to32bpp(QImage* image) +{ + for (int j = 0; j < image->height(); ++j) { + uchar *in = image->scanLine(j) + (image->width() - 1) * 3; + QRgb *out = (QRgb*)(image->scanLine(j)) + image->width() - 1; + + for (int i = image->width() - 1; i >= 0; --i, --out, in -= 3) { + *out = qRgb(in[0], in[1], in[2]); + } + } +} + +static void convertCmykToRgb(QImage* image) +{ + for (int j = 0; j < image->height(); ++j) { + uchar *in = image->scanLine(j) + image->width() * 4; + QRgb *out = (QRgb*)image->scanLine(j); + + for (int i = image->width() - 1; i >= 0; --i) { + in -= 4; + int k = in[3]; + out[i] = qRgb(k * in[0] / 255, k * in[1] / 255, k * in[2] / 255); + } + } +} + +static QSize getJpegSize(QIODevice* ioDevice) +{ + struct jpeg_decompress_struct cinfo; + QSize size; + + // Error handling + struct JpegFatalError jerr; + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = JpegFatalError::handler; + if (setjmp(jerr.mJmpBuffer)) { + jpeg_destroy_decompress(&cinfo); + return size; + } + + // Init decompression + jpeg_create_decompress(&cinfo); + Gwenview::IODeviceJpegSourceManager::setup(&cinfo, ioDevice); + jpeg_read_header(&cinfo, true); + + size = QSize(cinfo.image_width, cinfo.image_height); + jpeg_destroy_decompress(&cinfo); + return size; +} + +static bool loadJpeg(QImage* image, QIODevice* ioDevice, QSize scaledSize) +{ + struct jpeg_decompress_struct cinfo; + + // Error handling + struct JpegFatalError jerr; + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = JpegFatalError::handler; + if (setjmp(jerr.mJmpBuffer)) { + jpeg_destroy_decompress(&cinfo); + return false; + } + + // Init decompression + jpeg_create_decompress(&cinfo); + Gwenview::IODeviceJpegSourceManager::setup(&cinfo, ioDevice); + jpeg_read_header(&cinfo, true); + + // Compute scale value + cinfo.scale_num = 1; + if (!scaledSize.isEmpty()) { + // Use !scaledSize.isEmpty(), not scaledSize.isValid() because + // isValid() returns true if both the width and height is equal to or + // greater than 0, so it is possible to get a division by 0. + cinfo.scale_denom = qMin(cinfo.image_width / scaledSize.width(), + cinfo.image_height / scaledSize.height()); + if (cinfo.scale_denom < 2) { + cinfo.scale_denom = 1; + } else if (cinfo.scale_denom < 4) { + cinfo.scale_denom = 2; + } else if (cinfo.scale_denom < 8) { + cinfo.scale_denom = 4; + } else { + cinfo.scale_denom = 8; + } + } else { + cinfo.scale_denom = 1; + } + LOG("cinfo.scale_denom=" << cinfo.scale_denom); + + // Init image + jpeg_start_decompress(&cinfo); + switch (cinfo.output_components) { + case 3: + case 4: + *image = QImage(cinfo.output_width, cinfo.output_height, QImage::Format_RGB32); + break; + case 1: // B&W image + *image = QImage(cinfo.output_width, cinfo.output_height, QImage::Format_Indexed8); + image->setNumColors(256); + for (int i = 0; i < 256; ++i) { + image->setColor(i, qRgba(i, i, i, 255)); + } + break; + default: + jpeg_destroy_decompress(&cinfo); + return false; + } + + while (cinfo.output_scanline < cinfo.output_height) { + uchar *line = image->scanLine(cinfo.output_scanline); + jpeg_read_scanlines(&cinfo, &line, 1); + } + + switch (cinfo.out_color_space) { + case JCS_CMYK: + convertCmykToRgb(image); + break; + case JCS_RGB: + case JCS_GRAYSCALE: + break; + default: + kWarning() << "Unhandled JPEG colorspace" << cinfo.out_color_space; + break; + } + + if (cinfo.output_components == 3) { + expand24to32bpp(image); + } + + const QSize actualSize(cinfo.output_width, cinfo.output_height); + if (scaledSize.isValid() && actualSize != scaledSize) { + *image = image->scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + return true; +} + +/**************************************************************************** +This code is a copy of qjpeghandler.cpp because I can't find a way to fallback +to it for image writing. +BEGIN_COPY +****************************************************************************/ +struct my_error_mgr : public jpeg_error_mgr +{ + jmp_buf setjmp_buffer; +}; + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + + static void my_error_exit(j_common_ptr cinfo) + { + my_error_mgr* myerr = (my_error_mgr*) cinfo->err; + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + qWarning("%s", buffer); + longjmp(myerr->setjmp_buffer, 1); + } + +#if defined(Q_C_CALLBACKS) +} +#endif + +static const int max_buf = 4096; + +struct my_jpeg_destination_mgr : public jpeg_destination_mgr +{ + // Nothing dynamic - cannot rely on destruction over longjump + QIODevice *device; + JOCTET buffer[max_buf]; + +public: + my_jpeg_destination_mgr(QIODevice *); +}; + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + + static void qt_init_destination(j_compress_ptr) + { + } + + static boolean qt_empty_output_buffer(j_compress_ptr cinfo) + { + my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest; + + int written = dest->device->write((char*)dest->buffer, max_buf); + if (written == -1) + (*cinfo->err->error_exit)((j_common_ptr)cinfo); + + dest->next_output_byte = dest->buffer; + dest->free_in_buffer = max_buf; + +#if defined(Q_OS_UNIXWARE) + return B_TRUE; +#else + return true; +#endif + } + + static void qt_term_destination(j_compress_ptr cinfo) + { + my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest; + qint64 n = max_buf - dest->free_in_buffer; + + qint64 written = dest->device->write((char*)dest->buffer, n); + if (written == -1) + (*cinfo->err->error_exit)((j_common_ptr)cinfo); + } + +#if defined(Q_C_CALLBACKS) +} +#endif + +inline my_jpeg_destination_mgr::my_jpeg_destination_mgr(QIODevice *device) +{ + jpeg_destination_mgr::init_destination = qt_init_destination; + jpeg_destination_mgr::empty_output_buffer = qt_empty_output_buffer; + jpeg_destination_mgr::term_destination = qt_term_destination; + this->device = device; + next_output_byte = buffer; + free_in_buffer = max_buf; +} + +static bool write_jpeg_image(const QImage &sourceImage, QIODevice *device, int sourceQuality) +{ + bool success = false; + const QImage image = sourceImage; + const QVector cmap = image.colorTable(); + + struct jpeg_compress_struct cinfo; + JSAMPROW row_pointer[1]; + row_pointer[0] = 0; + + struct my_jpeg_destination_mgr *iod_dest = new my_jpeg_destination_mgr(device); + struct my_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = my_error_exit; + + if (!setjmp(jerr.setjmp_buffer)) { + // WARNING: + // this if loop is inside a setjmp/longjmp branch + // do not create C++ temporaries here because the destructor may never be called + // if you allocate memory, make sure that you can free it (row_pointer[0]) + jpeg_create_compress(&cinfo); + + cinfo.dest = iod_dest; + + cinfo.image_width = image.width(); + cinfo.image_height = image.height(); + + bool gray = false; + switch (image.depth()) { + case 1: + case 8: + gray = true; + for (int i = image.numColors(); gray && i--;) { + gray = gray & (qRed(cmap[i]) == qGreen(cmap[i]) && + qRed(cmap[i]) == qBlue(cmap[i])); + } + cinfo.input_components = gray ? 1 : 3; + cinfo.in_color_space = gray ? JCS_GRAYSCALE : JCS_RGB; + break; + case 32: + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + } + + jpeg_set_defaults(&cinfo); + + qreal diffInch = qAbs(image.dotsPerMeterX() * 2.54 / 100. - qRound(image.dotsPerMeterX() * 2.54 / 100.)) + + qAbs(image.dotsPerMeterY() * 2.54 / 100. - qRound(image.dotsPerMeterY() * 2.54 / 100.)); + qreal diffCm = (qAbs(image.dotsPerMeterX() / 100. - qRound(image.dotsPerMeterX() / 100.)) + + qAbs(image.dotsPerMeterY() / 100. - qRound(image.dotsPerMeterY() / 100.))) * 2.54; + if (diffInch < diffCm) { + cinfo.density_unit = 1; // dots/inch + cinfo.X_density = qRound(image.dotsPerMeterX() * 2.54 / 100.); + cinfo.Y_density = qRound(image.dotsPerMeterY() * 2.54 / 100.); + } else { + cinfo.density_unit = 2; // dots/cm + cinfo.X_density = (image.dotsPerMeterX() + 50) / 100; + cinfo.Y_density = (image.dotsPerMeterY() + 50) / 100; + } + + int quality = sourceQuality >= 0 ? qMin(sourceQuality, 100) : 75; +#if defined(Q_OS_UNIXWARE) + jpeg_set_quality(&cinfo, quality, B_TRUE /* limit to baseline-JPEG values */); + jpeg_start_compress(&cinfo, B_TRUE); +#else + jpeg_set_quality(&cinfo, quality, true /* limit to baseline-JPEG values */); + jpeg_start_compress(&cinfo, true); +#endif + + row_pointer[0] = new uchar[cinfo.image_width * cinfo.input_components]; + int w = cinfo.image_width; + while (cinfo.next_scanline < cinfo.image_height) { + uchar *row = row_pointer[0]; + switch (image.depth()) { + case 1: + if (gray) { + const uchar* data = image.scanLine(cinfo.next_scanline); + if (image.format() == QImage::Format_MonoLSB) { + for (int i = 0; i < w; i++) { + bool bit = !!(*(data + (i >> 3)) & (1 << (i & 7))); + row[i] = qRed(cmap[bit]); + } + } else { + for (int i = 0; i < w; i++) { + bool bit = !!(*(data + (i >> 3)) & (1 << (7 - (i & 7)))); + row[i] = qRed(cmap[bit]); + } + } + } else { + const uchar* data = image.scanLine(cinfo.next_scanline); + if (image.format() == QImage::Format_MonoLSB) { + for (int i = 0; i < w; i++) { + bool bit = !!(*(data + (i >> 3)) & (1 << (i & 7))); + *row++ = qRed(cmap[bit]); + *row++ = qGreen(cmap[bit]); + *row++ = qBlue(cmap[bit]); + } + } else { + for (int i = 0; i < w; i++) { + bool bit = !!(*(data + (i >> 3)) & (1 << (7 - (i & 7)))); + *row++ = qRed(cmap[bit]); + *row++ = qGreen(cmap[bit]); + *row++ = qBlue(cmap[bit]); + } + } + } + break; + case 8: + if (gray) { + const uchar* pix = image.scanLine(cinfo.next_scanline); + for (int i = 0; i < w; i++) { + *row = qRed(cmap[*pix]); + ++row; ++pix; + } + } else { + const uchar* pix = image.scanLine(cinfo.next_scanline); + for (int i = 0; i < w; i++) { + *row++ = qRed(cmap[*pix]); + *row++ = qGreen(cmap[*pix]); + *row++ = qBlue(cmap[*pix]); + ++pix; + } + } + break; + case 32: { + QRgb* rgb = (QRgb*)image.scanLine(cinfo.next_scanline); + for (int i = 0; i < w; i++) { + *row++ = qRed(*rgb); + *row++ = qGreen(*rgb); + *row++ = qBlue(*rgb); + ++rgb; + } + } + } + jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + success = true; + } else { + jpeg_destroy_compress(&cinfo); + success = false; + } + + delete iod_dest; + delete [] row_pointer[0]; + return success; +} +/**************************************************************************** +END_COPY +****************************************************************************/ + +struct JpegHandlerPrivate +{ + QSize mScaledSize; + int mQuality; +}; + +JpegHandler::JpegHandler() +: d(new JpegHandlerPrivate) +{ + d->mQuality = 75; +} + +JpegHandler::~JpegHandler() +{ + delete d; +} + +bool JpegHandler::canRead() const +{ + if (canRead(device())) { + setFormat("jpeg"); + return true; + } + return false; +} + +bool JpegHandler::canRead(QIODevice* device) +{ + if (!device) { + kWarning() << "called with no device"; + return false; + } + + return device->peek(2) == "\xFF\xD8"; +} + +bool JpegHandler::read(QImage* image) +{ + LOG(""); + if (!canRead()) { + return false; + } + return loadJpeg(image, device(), d->mScaledSize); +} + +bool JpegHandler::write(const QImage& image) +{ + LOG(""); + return write_jpeg_image(image, device(), d->mQuality); +} + +bool JpegHandler::supportsOption(ImageOption option) const +{ + return option == ScaledSize || option == Size || option == Quality; +} + +QVariant JpegHandler::option(ImageOption option) const +{ + if (option == ScaledSize) { + return d->mScaledSize; + } else if (option == Size) { + if (canRead() && !device()->isSequential()) { + qint64 pos = device()->pos(); + QSize size = getJpegSize(device()); + device()->seek(pos); + return size; + } + } else if (option == Quality) { + return d->mQuality; + } + return QVariant(); +} + +void JpegHandler::setOption(ImageOption option, const QVariant &value) +{ + if (option == ScaledSize) { + d->mScaledSize = value.toSize(); + } else if (option == Quality) { + d->mQuality = value.toInt(); + } +} + +} // namespace diff --git a/gwenview/lib/imageformats/jpeghandler.h b/gwenview/lib/imageformats/jpeghandler.h new file mode 100644 index 00000000..67ceb833 --- /dev/null +++ b/gwenview/lib/imageformats/jpeghandler.h @@ -0,0 +1,60 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef JPEGHANDLER_H +#define JPEGHANDLER_H + +// Qt +#include + +// KDE + +// Local + +namespace Gwenview +{ + +struct JpegHandlerPrivate; +/** + * A Jpeg handler which is more aggressive when loading down sampled images. + */ +class JpegHandler : public QImageIOHandler +{ +public: + JpegHandler(); + ~JpegHandler(); + + bool canRead() const; + bool read(QImage *image); + bool write(const QImage& image); + + static bool canRead(QIODevice *device); + + QVariant option(ImageOption option) const; + void setOption(ImageOption option, const QVariant &value); + bool supportsOption(ImageOption option) const; + +private: + JpegHandlerPrivate* const d; +}; + +} // namespace + +#endif /* JPEGHANDLER_H */ diff --git a/gwenview/lib/imageformats/jpegplugin.cpp b/gwenview/lib/imageformats/jpegplugin.cpp new file mode 100644 index 00000000..ceb9b1ce --- /dev/null +++ b/gwenview/lib/imageformats/jpegplugin.cpp @@ -0,0 +1,72 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#define QT_STATICPLUGIN + +// Self +#include "jpegplugin.h" + +// Qt +#include + +// Local +#include "jpeghandler.h" + +namespace Gwenview +{ + +QStringList JpegPlugin::keys() const +{ + return QStringList() << QLatin1String("jpeg") << QLatin1String("jpg"); +} + +QImageIOPlugin::Capabilities JpegPlugin::capabilities(QIODevice *device, const QByteArray &format) const +{ + if (format == "jpeg" || format == "jpg") { + return Capabilities(CanRead | CanWrite); + } + if (!format.isEmpty()) { + return 0; + } + if (!device->isOpen()) { + return 0; + } + + Capabilities cap; + if (device->isReadable() && JpegHandler::canRead(device)) { + cap |= CanRead; + } + if (device->isWritable()) { + cap |= CanWrite; + } + return cap; +} + +QImageIOHandler *JpegPlugin::create(QIODevice *device, const QByteArray &format) const +{ + QImageIOHandler *handler = new JpegHandler; + handler->setDevice(device); + handler->setFormat(format); + return handler; +} + +Q_EXPORT_STATIC_PLUGIN(JpegPlugin) + +} // namespace diff --git a/gwenview/lib/imageformats/jpegplugin.h b/gwenview/lib/imageformats/jpegplugin.h new file mode 100644 index 00000000..b1c20f2f --- /dev/null +++ b/gwenview/lib/imageformats/jpegplugin.h @@ -0,0 +1,45 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef JPEGPLUGIN_H +#define JPEGPLUGIN_H + +// Qt +#include +#include + +class QImageIOHandler; +class QIODevice; +class QStringList; + +namespace Gwenview +{ + +class JpegPlugin : public QImageIOPlugin +{ +public: + QStringList keys() const; + Capabilities capabilities(QIODevice *device, const QByteArray &format) const; + QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const; +}; + +} // namespace + +#endif /* JPEGPLUGIN_H */ diff --git a/gwenview/lib/imagemetainfomodel.cpp b/gwenview/lib/imagemetainfomodel.cpp new file mode 100644 index 00000000..caf6855c --- /dev/null +++ b/gwenview/lib/imagemetainfomodel.cpp @@ -0,0 +1,498 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "imagemetainfomodel.h" + +// Qt + +// KDE +#include +#include +#include +#include + +// Exiv2 +#include +#include +#include + +// Local + +namespace Gwenview +{ + +enum GroupRow { + NoGroupSpace = -2, + NoGroup = -1, + GeneralGroup, + ExifGroup, + IptcGroup, + XmpGroup +}; + +class MetaInfoGroup +{ +public: + enum { + InvalidRow = -1 + }; + + class Entry + { + public: + Entry(const QString& key, const QString& label, const QString& value) + : mKey(key), mLabel(label.trimmed()), mValue(value.trimmed()) + {} + + QString key() const + { + return mKey; + } + QString label() const + { + return mLabel; + } + + QString value() const + { + return mValue; + } + void setValue(const QString& value) + { + mValue = value.trimmed(); + } + + void appendValue(const QString& value) + { + if (mValue.length() > 0) { + mValue += '\n'; + } + mValue += value.trimmed(); + } + + private: + QString mKey; + QString mLabel; + QString mValue; + }; + + MetaInfoGroup(const QString& label) + : mLabel(label) + {} + + ~MetaInfoGroup() + { + qDeleteAll(mList); + } + + void clear() + { + qDeleteAll(mList); + mList.clear(); + mRowForKey.clear(); + } + + void addEntry(const QString& key, const QString& label, const QString& value) + { + addEntry(new Entry(key, label, value)); + } + + void addEntry(Entry* entry) + { + mList << entry; + mRowForKey[entry->key()] = mList.size() - 1; + } + + void getInfoForKey(const QString& key, QString* label, QString* value) const + { + Entry* entry = getEntryForKey(key); + if (entry) { + *label = entry->label(); + *value = entry->value(); + } + } + + QString getKeyAt(int row) const + { + Q_ASSERT(row < mList.size()); + return mList[row]->key(); + } + + QString getLabelForKeyAt(int row) const + { + Q_ASSERT(row < mList.size()); + return mList[row]->label(); + } + + QString getValueForKeyAt(int row) const + { + Q_ASSERT(row < mList.size()); + return mList[row]->value(); + } + + void setValueForKeyAt(int row, const QString& value) + { + Q_ASSERT(row < mList.size()); + mList[row]->setValue(value); + } + + int getRowForKey(const QString& key) const + { + return mRowForKey.value(key, InvalidRow); + } + + int size() const + { + return mList.size(); + } + + QString label() const + { + return mLabel; + } + + const QList& entryList() const { + return mList; + } + +private: + Entry* getEntryForKey(const QString& key) const + { + int row = getRowForKey(key); + if (row == InvalidRow) { + return 0; + } + return mList[row]; + } + + QList mList; + QHash mRowForKey; + QString mLabel; +}; + +struct ImageMetaInfoModelPrivate +{ + QVector mMetaInfoGroupVector; + ImageMetaInfoModel* q; + + void clearGroup(MetaInfoGroup* group, const QModelIndex& parent) + { + if (group->size() > 0) { + q->beginRemoveRows(parent, 0, group->size() - 1); + group->clear(); + q->endRemoveRows(); + } + } + + void setGroupEntryValue(GroupRow groupRow, const QString& key, const QString& value) + { + MetaInfoGroup* group = mMetaInfoGroupVector[groupRow]; + int entryRow = group->getRowForKey(key); + if (entryRow == MetaInfoGroup::InvalidRow) { + kWarning() << "No row for key" << key; + return; + } + group->setValueForKeyAt(entryRow, value); + QModelIndex groupIndex = q->index(groupRow, 0); + QModelIndex entryIndex = q->index(entryRow, 1, groupIndex); + emit q->dataChanged(entryIndex, entryIndex); + } + + QVariant displayData(const QModelIndex& index) const + { + if (index.internalId() == NoGroup) { + if (index.column() != 0) { + return QVariant(); + } + QString label = mMetaInfoGroupVector[index.row()]->label(); + return QVariant(label); + } + + if (index.internalId() == NoGroupSpace) { + return QVariant(QString()); + } + + MetaInfoGroup* group = mMetaInfoGroupVector[index.internalId()]; + if (index.column() == 0) { + return group->getLabelForKeyAt(index.row()); + } else { + return group->getValueForKeyAt(index.row()); + } + } + + void initGeneralGroup() + { + MetaInfoGroup* group = mMetaInfoGroupVector[GeneralGroup]; + group->addEntry("General.Name", i18nc("@item:intable Image file name", "Name"), QString()); + group->addEntry("General.Size", i18nc("@item:intable", "File Size"), QString()); + group->addEntry("General.Time", i18nc("@item:intable", "File Time"), QString()); + group->addEntry("General.ImageSize", i18nc("@item:intable", "Image Size"), QString()); + group->addEntry("General.Comment", i18nc("@item:intable", "Comment"), QString()); + } + + template + void fillExivGroup(const QModelIndex& parent, MetaInfoGroup* group, const Container& container) + { + // key aren't always unique (for example, "Iptc.Application2.Keywords" + // may appear multiple times) so we can't know how many rows we will + // insert before going through them. That's why we create a hash + // before. + typedef QHash EntryHash; + EntryHash hash; + + Iterator + it = container.begin(), + end = container.end(); + + for (; it != end; ++it) { + try { + // Skip metadatum if its tag is an hex number + if (it->tagName().substr(0, 2) == "0x") { + continue; + } + QString key = QString::fromUtf8(it->key().c_str()); + QString label = QString::fromLocal8Bit(it->tagLabel().c_str()); + std::ostringstream stream; + stream << *it; + QString value = QString::fromLocal8Bit(stream.str().c_str()); + + EntryHash::iterator hashIt = hash.find(key); + if (hashIt != hash.end()) { + hashIt.value()->appendValue(value); + } else { + hash.insert(key, new MetaInfoGroup::Entry(key, label, value)); + } + } catch (const Exiv2::Error& error) { + kWarning() << "Failed to read some meta info:" << error.what(); + } + } + + if (hash.isEmpty()) { + return; + } + q->beginInsertRows(parent, 0, hash.size() - 1); + Q_FOREACH(MetaInfoGroup::Entry * entry, hash) { + group->addEntry(entry); + } + q->endInsertRows(); + } +}; + +ImageMetaInfoModel::ImageMetaInfoModel() +: d(new ImageMetaInfoModelPrivate) +{ + d->q = this; + d->mMetaInfoGroupVector.resize(4); + d->mMetaInfoGroupVector[GeneralGroup] = new MetaInfoGroup(i18nc("@title:group General info about the image", "General")); + d->mMetaInfoGroupVector[ExifGroup] = new MetaInfoGroup("EXIF"); + d->mMetaInfoGroupVector[IptcGroup] = new MetaInfoGroup("IPTC"); + d->mMetaInfoGroupVector[XmpGroup] = new MetaInfoGroup("XMP"); + d->initGeneralGroup(); +} + +ImageMetaInfoModel::~ImageMetaInfoModel() +{ + qDeleteAll(d->mMetaInfoGroupVector); + delete d; +} + +void ImageMetaInfoModel::setUrl(const KUrl& url) +{ + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url); + QString sizeString = KGlobal::locale()->formatByteSize(item.size()); + + d->setGroupEntryValue(GeneralGroup, "General.Name", item.name()); + d->setGroupEntryValue(GeneralGroup, "General.Size", sizeString); + d->setGroupEntryValue(GeneralGroup, "General.Time", item.timeString()); +} + +void ImageMetaInfoModel::setImageSize(const QSize& size) +{ + QString imageSize; + if (size.isValid()) { + imageSize = i18nc( + "@item:intable %1 is image width, %2 is image height", + "%1x%2", size.width(), size.height()); + + double megaPixels = size.width() * size.height() / 1000000.; + if (megaPixels > 0.1) { + QString megaPixelsString = QString::number(megaPixels, 'f', 1); + imageSize += ' '; + imageSize += i18nc( + "@item:intable %1 is number of millions of pixels in image", + "(%1MP)", megaPixelsString); + } + } else { + imageSize = '-'; + } + d->setGroupEntryValue(GeneralGroup, "General.ImageSize", imageSize); +} + +void ImageMetaInfoModel::setExiv2Image(const Exiv2::Image* image) +{ + MetaInfoGroup* exifGroup = d->mMetaInfoGroupVector[ExifGroup]; + MetaInfoGroup* iptcGroup = d->mMetaInfoGroupVector[IptcGroup]; + MetaInfoGroup* xmpGroup = d->mMetaInfoGroupVector[XmpGroup]; + QModelIndex exifIndex = index(ExifGroup, 0); + QModelIndex iptcIndex = index(IptcGroup, 0); + QModelIndex xmpIndex = index(XmpGroup, 0); + d->clearGroup(exifGroup, exifIndex); + d->clearGroup(iptcGroup, iptcIndex); + d->clearGroup(xmpGroup, xmpIndex); + + if (!image) { + return; + } + + d->setGroupEntryValue(GeneralGroup, "General.Comment", QString::fromUtf8(image->comment().c_str())); + + if (image->checkMode(Exiv2::mdExif) & Exiv2::amRead) { + const Exiv2::ExifData& exifData = image->exifData(); + d->fillExivGroup(exifIndex, exifGroup, exifData); + } + + if (image->checkMode(Exiv2::mdIptc) & Exiv2::amRead) { + const Exiv2::IptcData& iptcData = image->iptcData(); + d->fillExivGroup(iptcIndex, iptcGroup, iptcData); + } + + if (image->checkMode(Exiv2::mdXmp) & Exiv2::amRead) { + const Exiv2::XmpData& xmpData = image->xmpData(); + d->fillExivGroup(xmpIndex, xmpGroup, xmpData); + } +} + +void ImageMetaInfoModel::getInfoForKey(const QString& key, QString* label, QString* value) const +{ + MetaInfoGroup* group; + if (key.startsWith(QLatin1String("General"))) { + group = d->mMetaInfoGroupVector[GeneralGroup]; + } else if (key.startsWith(QLatin1String("Exif"))) { + group = d->mMetaInfoGroupVector[ExifGroup]; + } else if (key.startsWith(QLatin1String("Iptc"))) { + group = d->mMetaInfoGroupVector[IptcGroup]; + } else if (key.startsWith(QLatin1String("Xmp"))) { + group = d->mMetaInfoGroupVector[XmpGroup]; + } else { + kWarning() << "Unknown metainfo key" << key; + return; + } + group->getInfoForKey(key, label, value); +} + +QString ImageMetaInfoModel::getValueForKey(const QString& key) const +{ + QString label, value; + getInfoForKey(key, &label, &value); + return value; +} + +QString ImageMetaInfoModel::keyForIndex(const QModelIndex& index) const +{ + if (index.internalId() == NoGroup) { + return QString(); + } + MetaInfoGroup* group = d->mMetaInfoGroupVector[index.internalId()]; + return group->getKeyAt(index.row()); +} + +QModelIndex ImageMetaInfoModel::index(int row, int col, const QModelIndex& parent) const +{ + if (col < 0 || col > 1) { + return QModelIndex(); + } + if (!parent.isValid()) { + // This is a group + if (row < 0 || row >= d->mMetaInfoGroupVector.size()) { + return QModelIndex(); + } + return createIndex(row, col, col == 0 ? NoGroup : NoGroupSpace); + } else { + // This is an entry + int group = parent.row(); + if (row < 0 || row >= d->mMetaInfoGroupVector[group]->size()) { + return QModelIndex(); + } + return createIndex(row, col, group); + } +} + +QModelIndex ImageMetaInfoModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) { + return QModelIndex(); + } + if (index.internalId() == NoGroup || index.internalId() == NoGroupSpace) { + return QModelIndex(); + } else { + return createIndex(index.internalId(), 0, NoGroup); + } +} + +int ImageMetaInfoModel::rowCount(const QModelIndex& parent) const +{ + if (!parent.isValid()) { + return d->mMetaInfoGroupVector.size(); + } else if (parent.internalId() == NoGroup) { + return d->mMetaInfoGroupVector[parent.row()]->size(); + } else { + return 0; + } +} + +int ImageMetaInfoModel::columnCount(const QModelIndex& /*parent*/) const +{ + return 2; +} + +QVariant ImageMetaInfoModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + switch (role) { + case Qt::DisplayRole: + return d->displayData(index); + default: + return QVariant(); + } +} + +QVariant ImageMetaInfoModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical || role != Qt::DisplayRole) { + return QVariant(); + } + + QString caption; + if (section == 0) { + caption = i18nc("@title:column", "Property"); + } else if (section == 1) { + caption = i18nc("@title:column", "Value"); + } else { + kWarning() << "Unknown section" << section; + } + + return QVariant(caption); +} + +} // namespace diff --git a/gwenview/lib/imagemetainfomodel.h b/gwenview/lib/imagemetainfomodel.h new file mode 100644 index 00000000..9017bf04 --- /dev/null +++ b/gwenview/lib/imagemetainfomodel.h @@ -0,0 +1,73 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef IMAGEMETAINFO_H +#define IMAGEMETAINFO_H + +#include + +// Qt +#include + +// KDE + +// Local + +class KUrl; + +namespace Exiv2 +{ +class Image; +} + +namespace Gwenview +{ + +struct ImageMetaInfoModelPrivate; +class GWENVIEWLIB_EXPORT ImageMetaInfoModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ImageMetaInfoModel(); + ~ImageMetaInfoModel(); + + void setUrl(const KUrl&); + void setImageSize(const QSize&); + void setExiv2Image(const Exiv2::Image*); + + QString keyForIndex(const QModelIndex&) const; + void getInfoForKey(const QString& key, QString* label, QString* value) const; + QString getValueForKey(const QString& key) const; + + virtual QModelIndex index(int row, int col, const QModelIndex& parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex&) const; + virtual int rowCount(const QModelIndex& = QModelIndex()) const; + virtual int columnCount(const QModelIndex& = QModelIndex()) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + virtual QVariant data(const QModelIndex&, int role = Qt::DisplayRole) const; + +private: + ImageMetaInfoModelPrivate* const d; + friend struct ImageMetaInfoModelPrivate; +}; + +} // namespace + +#endif /* IMAGEMETAINFO_H */ diff --git a/gwenview/lib/imagescaler.cpp b/gwenview/lib/imagescaler.cpp new file mode 100644 index 00000000..373d826e --- /dev/null +++ b/gwenview/lib/imagescaler.cpp @@ -0,0 +1,214 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "imagescaler.moc" + +// Qt +#include +#include + +// KDE +#include + +// Local +#include +#include + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +namespace Gwenview +{ + +// Amount of pixels to keep so that smooth scale is correct +static const int SMOOTH_MARGIN = 3; + +struct ImageScalerPrivate +{ + Qt::TransformationMode mTransformationMode; + Document::Ptr mDocument; + qreal mZoom; + QRegion mRegion; +}; + +ImageScaler::ImageScaler(QObject* parent) +: QObject(parent) +, d(new ImageScalerPrivate) +{ + d->mTransformationMode = Qt::FastTransformation; + d->mZoom = 0; +} + +ImageScaler::~ImageScaler() +{ + delete d; +} + +void ImageScaler::setDocument(Document::Ptr document) +{ + if (d->mDocument) { + disconnect(d->mDocument.data(), 0, this, 0); + } + d->mDocument = document; + // Used when scaler asked for a down-sampled image + connect(d->mDocument.data(), SIGNAL(downSampledImageReady()), + SLOT(doScale())); + // Used when scaler asked for a full image + connect(d->mDocument.data(), SIGNAL(loaded(KUrl)), + SLOT(doScale())); +} + +void ImageScaler::setZoom(qreal zoom) +{ + d->mZoom = zoom; +} + +void ImageScaler::setTransformationMode(Qt::TransformationMode mode) +{ + d->mTransformationMode = mode; +} + +void ImageScaler::setDestinationRegion(const QRegion& region) +{ + LOG(region); + d->mRegion = region; + if (d->mRegion.isEmpty()) { + return; + } + + if (d->mDocument && d->mZoom > 0) { + doScale(); + } +} + +void ImageScaler::doScale() +{ + if (d->mZoom < Document::maxDownSampledZoom()) { + if (!d->mDocument->prepareDownSampledImageForZoom(d->mZoom)) { + LOG("Asked for a down sampled image"); + return; + } + } else if (d->mDocument->image().isNull()) { + LOG("Asked for the full image"); + d->mDocument->startLoadingFullImage(); + return; + } + + LOG("Starting"); + Q_FOREACH(const QRect & rect, d->mRegion.rects()) { + LOG(rect); + scaleRect(rect); + } + LOG("Done"); +} + +void ImageScaler::scaleRect(const QRect& rect) +{ + const qreal REAL_DELTA = 0.001; + if (qAbs(d->mZoom - 1.0) < REAL_DELTA) { + QImage tmp = d->mDocument->image().copy(rect); + tmp = tmp.convertToFormat(QImage::Format_ARGB32_Premultiplied); + scaledRect(rect.left(), rect.top(), tmp); + return; + } + + QImage image; + qreal zoom; + if (d->mZoom < Document::maxDownSampledZoom()) { + image = d->mDocument->downSampledImageForZoom(d->mZoom); + Q_ASSERT(!image.isNull()); + qreal zoom1 = qreal(image.width()) / d->mDocument->width(); + zoom = d->mZoom / zoom1; + } else { + image = d->mDocument->image(); + zoom = d->mZoom; + } + // If rect contains "half" pixels, make sure sourceRect includes them + QRectF sourceRectF( + rect.left() / zoom, + rect.top() / zoom, + rect.width() / zoom, + rect.height() / zoom); + + sourceRectF = sourceRectF.intersected(image.rect()); + QRect sourceRect = PaintUtils::containingRect(sourceRectF); + if (sourceRect.isEmpty()) { + return; + } + + // Compute smooth margin + bool needsSmoothMargins = d->mTransformationMode == Qt::SmoothTransformation; + + int sourceLeftMargin, sourceRightMargin, sourceTopMargin, sourceBottomMargin; + int destLeftMargin, destRightMargin, destTopMargin, destBottomMargin; + if (needsSmoothMargins) { + sourceLeftMargin = qMin(sourceRect.left(), SMOOTH_MARGIN); + sourceTopMargin = qMin(sourceRect.top(), SMOOTH_MARGIN); + sourceRightMargin = qMin(image.rect().right() - sourceRect.right(), SMOOTH_MARGIN); + sourceBottomMargin = qMin(image.rect().bottom() - sourceRect.bottom(), SMOOTH_MARGIN); + sourceRect.adjust( + -sourceLeftMargin, + -sourceTopMargin, + sourceRightMargin, + sourceBottomMargin); + destLeftMargin = int(sourceLeftMargin * zoom); + destTopMargin = int(sourceTopMargin * zoom); + destRightMargin = int(sourceRightMargin * zoom); + destBottomMargin = int(sourceBottomMargin * zoom); + } else { + sourceLeftMargin = sourceRightMargin = sourceTopMargin = sourceBottomMargin = 0; + destLeftMargin = destRightMargin = destTopMargin = destBottomMargin = 0; + } + + // destRect is almost like rect, but it contains only "full" pixels + QRectF destRectF = QRectF( + sourceRect.left() * zoom, + sourceRect.top() * zoom, + sourceRect.width() * zoom, + sourceRect.height() * zoom + ); + QRect destRect = PaintUtils::containingRect(destRectF); + + QImage tmp; + tmp = image.copy(sourceRect); + tmp = tmp.convertToFormat(QImage::Format_ARGB32_Premultiplied); + tmp = tmp.scaled( + destRect.width(), + destRect.height(), + Qt::IgnoreAspectRatio, // Do not use KeepAspectRatio, it can lead to skipped rows or columns + d->mTransformationMode); + + if (needsSmoothMargins) { + tmp = tmp.copy( + destLeftMargin, destTopMargin, + destRect.width() - (destLeftMargin + destRightMargin), + destRect.height() - (destTopMargin + destBottomMargin) + ); + } + + scaledRect(destRect.left() + destLeftMargin, destRect.top() + destTopMargin, tmp); +} + +} // namespace diff --git a/gwenview/lib/imagescaler.h b/gwenview/lib/imagescaler.h new file mode 100644 index 00000000..62be2b6d --- /dev/null +++ b/gwenview/lib/imagescaler.h @@ -0,0 +1,66 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef IMAGESCALER_H +#define IMAGESCALER_H + +// Qt +#include + +// KDE +#include + +#include + +class QImage; +class QRect; +class QRegion; + +namespace Gwenview +{ + +class Document; + +struct ImageScalerPrivate; +class GWENVIEWLIB_EXPORT ImageScaler : public QObject +{ + Q_OBJECT +public: + ImageScaler(QObject* parent = 0); + ~ImageScaler(); + void setDocument(KSharedPtr); + void setZoom(qreal); + void setDestinationRegion(const QRegion&); + + void setTransformationMode(Qt::TransformationMode); + +Q_SIGNALS: + void scaledRect(int left, int top, const QImage&); + +private: + ImageScalerPrivate * const d; + void scaleRect(const QRect&); + +private Q_SLOTS: + void doScale(); +}; + +} // namespace + +#endif /* IMAGESCALER_H */ diff --git a/gwenview/lib/imageutils.cpp b/gwenview/lib/imageutils.cpp new file mode 100644 index 00000000..de591840 --- /dev/null +++ b/gwenview/lib/imageutils.cpp @@ -0,0 +1,74 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "imageutils.h" + +// Qt +#include + +namespace Gwenview +{ +namespace ImageUtils +{ + +QMatrix transformMatrix(Orientation orientation) +{ + QMatrix matrix; + switch (orientation) { + case NOT_AVAILABLE: + case NORMAL: + break; + + case HFLIP: + matrix.scale(-1, 1); + break; + + case ROT_180: + matrix.rotate(180); + break; + + case VFLIP: + matrix.scale(1, -1); + break; + + case TRANSPOSE: + matrix.scale(-1, 1); + matrix.rotate(90); + break; + + case ROT_90: + matrix.rotate(90); + break; + + case TRANSVERSE: + matrix.scale(1, -1); + matrix.rotate(90); + break; + + case ROT_270: + matrix.rotate(270); + break; + } + + return matrix; +} + +} // namespace +} // namespace diff --git a/gwenview/lib/imageutils.h b/gwenview/lib/imageutils.h new file mode 100644 index 00000000..bdccdc82 --- /dev/null +++ b/gwenview/lib/imageutils.h @@ -0,0 +1,39 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef IMAGEUTILS_H +#define IMAGEUTILS_H + +#include +#include + +class QMatrix; + +namespace Gwenview +{ +namespace ImageUtils +{ + +GWENVIEWLIB_EXPORT QMatrix transformMatrix(Orientation); + +} // namespace +} // namespace + +#endif /* IMAGEUTILS_H */ diff --git a/gwenview/lib/invisiblebuttongroup.cpp b/gwenview/lib/invisiblebuttongroup.cpp new file mode 100644 index 00000000..7271ca18 --- /dev/null +++ b/gwenview/lib/invisiblebuttongroup.cpp @@ -0,0 +1,79 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "invisiblebuttongroup.moc" + +// Qt +#include +#include + +// KDE +#include + +// Local + +namespace Gwenview +{ + +struct InvisibleButtonGroupPrivate +{ + QButtonGroup* mGroup; +}; + +InvisibleButtonGroup::InvisibleButtonGroup(QWidget* parent) +: QWidget(parent) +, d(new InvisibleButtonGroupPrivate) +{ + hide(); + d->mGroup = new QButtonGroup(this); + d->mGroup->setExclusive(true); + connect(d->mGroup, SIGNAL(buttonClicked(int)), SIGNAL(selectionChanged(int))); + const QString name = metaObject()->className(); + if (!KConfigDialogManager::propertyMap()->contains(name)) { + KConfigDialogManager::propertyMap()->insert(name, "current"); + KConfigDialogManager::changedMap()->insert(name, SIGNAL(selectionChanged(int))); + } +} + +InvisibleButtonGroup::~InvisibleButtonGroup() +{ + delete d; +} + +int InvisibleButtonGroup::selected() const +{ + return d->mGroup->checkedId(); +} + +void InvisibleButtonGroup::addButton(QAbstractButton* button, int id) +{ + d->mGroup->addButton(button, id); +} + +void InvisibleButtonGroup::setSelected(int id) +{ + QAbstractButton* button = d->mGroup->button(id); + if (button) { + button->setChecked(true); + } +} + +} // namespace diff --git a/gwenview/lib/invisiblebuttongroup.h b/gwenview/lib/invisiblebuttongroup.h new file mode 100644 index 00000000..5d4b7f85 --- /dev/null +++ b/gwenview/lib/invisiblebuttongroup.h @@ -0,0 +1,102 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef INVISIBLEBUTTONGROUP_H +#define INVISIBLEBUTTONGROUP_H + +#include + +// Qt +#include + +// KDE + +// Local + +class QAbstractButton; + +namespace Gwenview +{ + +struct InvisibleButtonGroupPrivate; +/** + * This class makes it possible to create radio buttons without having to put + * them in a dedicated QGroupBox or KButtonGroup. This is useful when you do not + * want to add visual frames to create a set of radio buttons. + * + * It is a QWidget so that it can support KConfigXT: it is completely + * invisible and does not require the radio buttons to be its children. + * The most common way to use it is to create your dialog with Designer, + * including the radio buttons, and to instantiate an instance of + * InvisibleButtonGroup from your code. + * + * Example: + * + * We assume "config" is a KConfigSkeleton object which contains a + * "ViewMode" key. This key is an int where 1 means "list" and 2 means + * "detail". + * We also assume "ui" has been created with Designer and contains two + * QRadioButton named "listRadioButton" and "detailRadioButton". + * + * @code + * // Prepare the config dialog + * KConfigDialog* dialog(parent, "Settings", config); + * + * // Create a widget in the dialog + * QWidget* pageWidget = new QWidget; + * ui->setupUi(pageWidget); + * dialog->addPage(pageWidget, i18n("Page Title")); + * @endcode + * + * Now we can setup an InvisibleButtonGroup to handle both radio + * buttons and ensure they follow the "ViewMode" config key. + * + * @code + * InvisibleButtonGroup* group = new InvisibleButtonGroup(pageWidget); + * group->setObjectName( QLatin1String("kcfg_ViewMode" )); + * group->addButton(ui->listRadioButton, 1); + * group->addButton(ui->detailRadioButton, 2); + * @endcode + */ +class GWENVIEWLIB_EXPORT InvisibleButtonGroup : public QWidget +{ + Q_OBJECT + Q_PROPERTY(int current READ selected WRITE setSelected) +public: + explicit InvisibleButtonGroup(QWidget* parent = 0); + ~InvisibleButtonGroup(); + + int selected() const; + + void addButton(QAbstractButton* button, int id); + +public Q_SLOTS: + void setSelected(int id); + +Q_SIGNALS: + void selectionChanged(int id); + +private: + InvisibleButtonGroupPrivate* const d; +}; + +} // namespace + +#endif /* INVISIBLEBUTTONGROUP_H */ diff --git a/gwenview/lib/iodevicejpegsourcemanager.cpp b/gwenview/lib/iodevicejpegsourcemanager.cpp new file mode 100644 index 00000000..062e6b90 --- /dev/null +++ b/gwenview/lib/iodevicejpegsourcemanager.cpp @@ -0,0 +1,115 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "iodevicejpegsourcemanager.h" + +// Qt +#include + +// KDE +#include + +// libjpeg +#include +#define XMD_H +extern "C" { +#include +} + +// Local + +namespace Gwenview +{ +namespace IODeviceJpegSourceManager +{ + +#define SOURCE_MANAGER_BUFFER_SIZE 4096 +struct IODeviceJpegSourceManager : public jpeg_source_mgr +{ + QIODevice* mIODevice; + JOCTET mBuffer[SOURCE_MANAGER_BUFFER_SIZE]; +}; + +static boolean fill_input_buffer(j_decompress_ptr cinfo) +{ + IODeviceJpegSourceManager* src = static_cast(cinfo->src); + Q_ASSERT(src->mIODevice); + int readSize = src->mIODevice->read((char*)src->mBuffer, SOURCE_MANAGER_BUFFER_SIZE); + if (readSize > 0) { + src->next_input_byte = src->mBuffer; + src->bytes_in_buffer = readSize; + } else { + /** + * JPEG file is broken. We feed the decoder with fake EOI, as specified + * in the libjpeg documentation. + */ + static JOCTET fakeEOI[2] = { JOCTET(0xFF), JOCTET(JPEG_EOI)}; + kWarning() << "Image is incomplete"; + cinfo->src->next_input_byte = fakeEOI; + cinfo->src->bytes_in_buffer = 2; + } + return true; +} + +static void init_source(j_decompress_ptr cinfo) +{ + fill_input_buffer(cinfo); +} + +static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) +{ + if (num_bytes > 0) { + while (num_bytes > (long) cinfo->src->bytes_in_buffer) { + num_bytes -= (long) cinfo->src->bytes_in_buffer; + fill_input_buffer(cinfo); + /** + * we assume that fill_input_buffer will never return FALSE, so + * suspension need not be handled. + */ + } + cinfo->src->next_input_byte += (size_t) num_bytes; + cinfo->src->bytes_in_buffer -= (size_t) num_bytes; + } +} + +static void term_source(j_decompress_ptr) +{ +} + +void setup(j_decompress_ptr cinfo, QIODevice* ioDevice) +{ + Q_ASSERT(!cinfo->src); + IODeviceJpegSourceManager* src = (IODeviceJpegSourceManager*) + (*cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(IODeviceJpegSourceManager)); + cinfo->src = src; + + src->init_source = init_source; + src->fill_input_buffer = fill_input_buffer; + src->skip_input_data = skip_input_data; + src->resync_to_restart = jpeg_resync_to_restart; + src->term_source = term_source; + + src->mIODevice = ioDevice; +} + +} // IODeviceJpegSourceManager namespace +} // Gwenview namespace diff --git a/gwenview/lib/iodevicejpegsourcemanager.h b/gwenview/lib/iodevicejpegsourcemanager.h new file mode 100644 index 00000000..c99a0210 --- /dev/null +++ b/gwenview/lib/iodevicejpegsourcemanager.h @@ -0,0 +1,51 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef IODEVICEJPEGSOURCEMANAGER_H +#define IODEVICEJPEGSOURCEMANAGER_H + +// Qt + +// KDE + +// Local + +class QIODevice; +struct jpeg_decompress_struct; + +namespace Gwenview +{ + +/** + * This namespace provides a function which makes it possible to decode JPEG + * files with libjpeg from a QIODevice instance. + * + * To use it, simply call setup() to initialize your jpeg_decompress_struct + * with QIODevice-ready callbacks. The device should be opened for reading. + */ +namespace IODeviceJpegSourceManager +{ + +void setup(jpeg_decompress_struct* cinfo, QIODevice* ioDevice); + +} // namespace +} // namespace + +#endif /* IODEVICEJPEGSOURCEMANAGER_H */ diff --git a/gwenview/lib/jpegcontent.cpp b/gwenview/lib/jpegcontent.cpp new file mode 100644 index 00000000..f575c2dc --- /dev/null +++ b/gwenview/lib/jpegcontent.cpp @@ -0,0 +1,644 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "jpegcontent.h" + +// System +#include +#include +#include +#include +extern "C" { +#include +#include "transupp.h" +} + +// Qt +#include +#include +#include +#include +#include + +// KDE +#include +#include + +// Exiv2 +#include +#include + +// Local +#include "jpegerrormanager.h" +#include "iodevicejpegsourcemanager.h" +#include "exiv2imageloader.h" +#include "gwenviewconfig.h" + +namespace Gwenview +{ + +const int INMEM_DST_DELTA = 4096; + +//----------------------------------------------- +// +// In-memory data destination manager for libjpeg +// +//----------------------------------------------- +struct inmem_dest_mgr : public jpeg_destination_mgr +{ + QByteArray* mOutput; + + void dump() + { + kDebug() << "dest_mgr:\n"; + kDebug() << "- next_output_byte: " << next_output_byte; + kDebug() << "- free_in_buffer: " << free_in_buffer; + kDebug() << "- output size: " << mOutput->size(); + } +}; + +void inmem_init_destination(j_compress_ptr cinfo) +{ + inmem_dest_mgr* dest = (inmem_dest_mgr*)(cinfo->dest); + if (dest->mOutput->size() == 0) { + dest->mOutput->resize(INMEM_DST_DELTA); + } + dest->free_in_buffer = dest->mOutput->size(); + dest->next_output_byte = (JOCTET*)(dest->mOutput->data()); +} + +boolean inmem_empty_output_buffer(j_compress_ptr cinfo) +{ + inmem_dest_mgr* dest = (inmem_dest_mgr*)(cinfo->dest); + dest->mOutput->resize(dest->mOutput->size() + INMEM_DST_DELTA); + dest->next_output_byte = (JOCTET*)(dest->mOutput->data() + dest->mOutput->size() - INMEM_DST_DELTA); + dest->free_in_buffer = INMEM_DST_DELTA; + + return true; +} + +void inmem_term_destination(j_compress_ptr cinfo) +{ + inmem_dest_mgr* dest = (inmem_dest_mgr*)(cinfo->dest); + int finalSize = dest->next_output_byte - (JOCTET*)(dest->mOutput->data()); + Q_ASSERT(finalSize >= 0); + dest->mOutput->resize(finalSize); +} + +//--------------------- +// +// JpegContent::Private +// +//--------------------- +struct JpegContent::Private +{ + // JpegContent usually stores the image pixels as compressed JPEG data in + // mRawData. However if the image is set with setImage() because the user + // performed a lossy image manipulation, mRawData is cleared and the image + // pixels are kept in mImage until updateRawDataFromImage() is called. + QImage mImage; + QByteArray mRawData; + QSize mSize; + QString mComment; + bool mPendingTransformation; + QMatrix mTransformMatrix; + Exiv2::ExifData mExifData; + QString mErrorString; + + Private() + { + mPendingTransformation = false; + } + + void setupInmemDestination(j_compress_ptr cinfo, QByteArray* outputData) + { + Q_ASSERT(!cinfo->dest); + inmem_dest_mgr* dest = (inmem_dest_mgr*) + (*cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(inmem_dest_mgr)); + cinfo->dest = (struct jpeg_destination_mgr*)(dest); + + dest->init_destination = inmem_init_destination; + dest->empty_output_buffer = inmem_empty_output_buffer; + dest->term_destination = inmem_term_destination; + + dest->mOutput = outputData; + } + bool readSize() + { + struct jpeg_decompress_struct srcinfo; + + // Init JPEG structs + JPEGErrorManager errorManager; + + // Initialize the JPEG decompression object + srcinfo.err = &errorManager; + jpeg_create_decompress(&srcinfo); + if (setjmp(errorManager.jmp_buffer)) { + kError() << "libjpeg fatal error\n"; + return false; + } + + // Specify data source for decompression + QBuffer buffer(&mRawData); + buffer.open(QIODevice::ReadOnly); + IODeviceJpegSourceManager::setup(&srcinfo, &buffer); + + // Read the header + jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL); + int result = jpeg_read_header(&srcinfo, true); + if (result != JPEG_HEADER_OK) { + kError() << "Could not read jpeg header\n"; + jpeg_destroy_decompress(&srcinfo); + return false; + } + mSize = QSize(srcinfo.image_width, srcinfo.image_height); + + jpeg_destroy_decompress(&srcinfo); + return true; + } + + bool updateRawDataFromImage() + { + QBuffer buffer; + QImageWriter writer(&buffer, "jpeg"); + if (!writer.write(mImage)) { + mErrorString = writer.errorString(); + return false; + } + mRawData = buffer.data(); + mImage = QImage(); + return true; + } +}; + +//------------ +// +// JpegContent +// +//------------ +JpegContent::JpegContent() +{ + d = new JpegContent::Private(); +} + +JpegContent::~JpegContent() +{ + delete d; +} + +bool JpegContent::load(const QString& path) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + kError() << "Could not open '" << path << "' for reading\n"; + return false; + } + return loadFromData(file.readAll()); +} + +bool JpegContent::loadFromData(const QByteArray& data) +{ + Exiv2::Image::AutoPtr image; + Exiv2ImageLoader loader; + if (!loader.load(data)) { + kError() << "Could not load image with Exiv2, reported error:" << loader.errorMessage(); + } + image = loader.popImage(); + + return loadFromData(data, image.get()); +} + +bool JpegContent::loadFromData(const QByteArray& data, Exiv2::Image* exiv2Image) +{ + d->mPendingTransformation = false; + d->mTransformMatrix.reset(); + + d->mRawData = data; + if (d->mRawData.size() == 0) { + kError() << "No data\n"; + return false; + } + + if (!d->readSize()) return false; + + d->mExifData = exiv2Image->exifData(); + d->mComment = QString::fromUtf8(exiv2Image->comment().c_str()); + + if (!GwenviewConfig::applyExifOrientation()) { + return true; + } + + // Adjust the size according to the orientation + switch (orientation()) { + case TRANSPOSE: + case ROT_90: + case TRANSVERSE: + case ROT_270: + d->mSize.transpose(); + break; + default: + break; + } + + return true; +} + +QByteArray JpegContent::rawData() const +{ + return d->mRawData; +} + +Orientation JpegContent::orientation() const +{ + Exiv2::ExifKey key("Exif.Image.Orientation"); + Exiv2::ExifData::iterator it = d->mExifData.findKey(key); + + // We do the same checks as in libexiv2's src/crwimage.cpp: + // http://dev.exiv2.org/projects/exiv2/repository/entry/trunk/src/crwimage.cpp?rev=2681#L1336 + if (it == d->mExifData.end() || it->count() == 0 || it->typeId() != Exiv2::unsignedShort) { + return NOT_AVAILABLE; + } + return Orientation(it->toLong()); +} + +int JpegContent::dotsPerMeterX() const +{ + return dotsPerMeter("XResolution"); +} + +int JpegContent::dotsPerMeterY() const +{ + return dotsPerMeter("YResolution"); +} + +int JpegContent::dotsPerMeter(const QString& keyName) const +{ + Exiv2::ExifKey keyResUnit("Exif.Image.ResolutionUnit"); + Exiv2::ExifData::iterator it = d->mExifData.findKey(keyResUnit); + if (it == d->mExifData.end()) { + return 0; + } + int res = it->toLong(); + QString keyVal = "Exif.Image." + keyName; + Exiv2::ExifKey keyResolution(keyVal.toAscii().data()); + it = d->mExifData.findKey(keyResolution); + if (it == d->mExifData.end()) { + return 0; + } + // The unit for measuring XResolution and YResolution. The same unit is used for both XResolution and YResolution. + // If the image resolution in unknown, 2 (inches) is designated. + // Default = 2 + // 2 = inches + // 3 = centimeters + // Other = reserved + const float INCHESPERMETER = (100. / 2.54); + switch (res) { + case 3: // dots per cm + return int(it->toLong() * 100); + default: // dots per inch + return int(it->toLong() * INCHESPERMETER); + } + + return 0; +} + +void JpegContent::resetOrientation() +{ + Exiv2::ExifKey key("Exif.Image.Orientation"); + Exiv2::ExifData::iterator it = d->mExifData.findKey(key); + if (it == d->mExifData.end()) { + return; + } + + *it = uint16_t(NORMAL); +} + +QSize JpegContent::size() const +{ + return d->mSize; +} + +QString JpegContent::comment() const +{ + return d->mComment; +} + +void JpegContent::setComment(const QString& comment) +{ + d->mComment = comment; +} + +static QMatrix createRotMatrix(int angle) +{ + QMatrix matrix; + matrix.rotate(angle); + return matrix; +} + +static QMatrix createScaleMatrix(int dx, int dy) +{ + QMatrix matrix; + matrix.scale(dx, dy); + return matrix; +} + +struct OrientationInfo +{ + OrientationInfo() + : orientation(NOT_AVAILABLE) + , jxform(JXFORM_NONE) + {} + + OrientationInfo(Orientation o, QMatrix m, JXFORM_CODE j) + : orientation(o), matrix(m), jxform(j) + {} + + Orientation orientation; + QMatrix matrix; + JXFORM_CODE jxform; +}; +typedef QList OrientationInfoList; + +static const OrientationInfoList& orientationInfoList() +{ + static OrientationInfoList list; + if (list.size() == 0) { + QMatrix rot90 = createRotMatrix(90); + QMatrix hflip = createScaleMatrix(-1, 1); + QMatrix vflip = createScaleMatrix(1, -1); + + list + << OrientationInfo() + << OrientationInfo(NORMAL, QMatrix(), JXFORM_NONE) + << OrientationInfo(HFLIP, hflip, JXFORM_FLIP_H) + << OrientationInfo(ROT_180, createRotMatrix(180), JXFORM_ROT_180) + << OrientationInfo(VFLIP, vflip, JXFORM_FLIP_V) + << OrientationInfo(TRANSPOSE, hflip * rot90, JXFORM_TRANSPOSE) + << OrientationInfo(ROT_90, rot90, JXFORM_ROT_90) + << OrientationInfo(TRANSVERSE, vflip * rot90, JXFORM_TRANSVERSE) + << OrientationInfo(ROT_270, createRotMatrix(270), JXFORM_ROT_270) + ; + } + return list; +} + +void JpegContent::transform(Orientation orientation) +{ + if (orientation != NOT_AVAILABLE && orientation != NORMAL) { + d->mPendingTransformation = true; + OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end()); + for (; it != end; ++it) { + if ((*it).orientation == orientation) { + d->mTransformMatrix = (*it).matrix * d->mTransformMatrix; + break; + } + } + if (it == end) { + kWarning() << "Could not find matrix for orientation\n"; + } + } +} + +#if 0 +static void dumpMatrix(const QMatrix& matrix) +{ + kDebug() << "matrix | " << matrix.m11() << ", " << matrix.m12() << " |\n"; + kDebug() << " | " << matrix.m21() << ", " << matrix.m22() << " |\n"; + kDebug() << " ( " << matrix.dx() << ", " << matrix.dy() << " )\n"; +} +#endif + +static bool matricesAreSame(const QMatrix& m1, const QMatrix& m2, double tolerance) +{ + return fabs(m1.m11() - m2.m11()) < tolerance + && fabs(m1.m12() - m2.m12()) < tolerance + && fabs(m1.m21() - m2.m21()) < tolerance + && fabs(m1.m22() - m2.m22()) < tolerance + && fabs(m1.dx() - m2.dx()) < tolerance + && fabs(m1.dy() - m2.dy()) < tolerance; +} + +static JXFORM_CODE findJxform(const QMatrix& matrix) +{ + OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end()); + for (; it != end; ++it) { + if (matricesAreSame((*it).matrix, matrix, 0.001)) { + return (*it).jxform; + } + } + kWarning() << "findJxform: failed\n"; + return JXFORM_NONE; +} + +void JpegContent::applyPendingTransformation() +{ + if (d->mRawData.size() == 0) { + kError() << "No data loaded\n"; + return; + } + + // The following code is inspired by jpegtran.c from the libjpeg + + // Init JPEG structs + struct jpeg_decompress_struct srcinfo; + struct jpeg_compress_struct dstinfo; + jvirt_barray_ptr * src_coef_arrays; + jvirt_barray_ptr * dst_coef_arrays; + + // Initialize the JPEG decompression object + JPEGErrorManager srcErrorManager; + srcinfo.err = &srcErrorManager; + jpeg_create_decompress(&srcinfo); + if (setjmp(srcErrorManager.jmp_buffer)) { + kError() << "libjpeg error in src\n"; + return; + } + + // Initialize the JPEG compression object + JPEGErrorManager dstErrorManager; + dstinfo.err = &dstErrorManager; + jpeg_create_compress(&dstinfo); + if (setjmp(dstErrorManager.jmp_buffer)) { + kError() << "libjpeg error in dst\n"; + return; + } + + // Specify data source for decompression + QBuffer buffer(&d->mRawData); + buffer.open(QIODevice::ReadOnly); + IODeviceJpegSourceManager::setup(&srcinfo, &buffer); + + // Enable saving of extra markers that we want to copy + jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL); + + (void) jpeg_read_header(&srcinfo, true); + + // Init transformation + jpeg_transform_info transformoption; + memset(&transformoption, 0, sizeof(jpeg_transform_info)); + transformoption.transform = findJxform(d->mTransformMatrix); + jtransform_request_workspace(&srcinfo, &transformoption); + + /* Read source file as DCT coefficients */ + src_coef_arrays = jpeg_read_coefficients(&srcinfo); + + /* Initialize destination compression parameters from source values */ + jpeg_copy_critical_parameters(&srcinfo, &dstinfo); + + /* Adjust destination parameters if required by transform options; + * also find out which set of coefficient arrays will hold the output. + */ + dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo, + src_coef_arrays, + &transformoption); + + /* Specify data destination for compression */ + QByteArray output; + output.resize(d->mRawData.size()); + d->setupInmemDestination(&dstinfo, &output); + + /* Start compressor (note no image data is actually written here) */ + jpeg_write_coefficients(&dstinfo, dst_coef_arrays); + + /* Copy to the output file any extra markers that we want to preserve */ + jcopy_markers_execute(&srcinfo, &dstinfo, JCOPYOPT_ALL); + + /* Execute image transformation, if any */ + jtransform_execute_transformation(&srcinfo, &dstinfo, + src_coef_arrays, + &transformoption); + + /* Finish compression and release memory */ + jpeg_finish_compress(&dstinfo); + jpeg_destroy_compress(&dstinfo); + (void) jpeg_finish_decompress(&srcinfo); + jpeg_destroy_decompress(&srcinfo); + + // Set rawData to our new JPEG + d->mRawData = output; +} + +QImage JpegContent::thumbnail() const +{ + QImage image; + if (!d->mExifData.empty()) { +#if(EXIV2_TEST_VERSION(0,17,91)) + Exiv2::ExifThumbC thumb(d->mExifData); + Exiv2::DataBuf thumbnail = thumb.copy(); +#else + Exiv2::DataBuf thumbnail = d->mExifData.copyThumbnail(); +#endif + image.loadFromData(thumbnail.pData_, thumbnail.size_); + } + return image; +} + +void JpegContent::setThumbnail(const QImage& thumbnail) +{ + if (d->mExifData.empty()) { + return; + } + + QByteArray array; + QBuffer buffer(&array); + buffer.open(QIODevice::WriteOnly); + QImageWriter writer(&buffer, "JPEG"); + if (!writer.write(thumbnail)) { + kError() << "Could not write thumbnail\n"; + return; + } + +#if (EXIV2_TEST_VERSION(0,17,91)) + Exiv2::ExifThumb thumb(d->mExifData); + thumb.setJpegThumbnail((unsigned char*)array.data(), array.size()); +#else + d->mExifData.setJpegThumbnail((unsigned char*)array.data(), array.size()); +#endif +} + +bool JpegContent::save(const QString& path) +{ + QFile file(path); + if (!file.open(QIODevice::WriteOnly)) { + d->mErrorString = i18nc("@info", "Could not open file for writing."); + return false; + } + + return save(&file); +} + +bool JpegContent::save(QIODevice* device) +{ + if (!d->mImage.isNull()) { + if (!d->updateRawDataFromImage()) { + return false; + } + } + + if (d->mRawData.size() == 0) { + d->mErrorString = i18nc("@info", "No data to store."); + return false; + } + + if (d->mPendingTransformation) { + applyPendingTransformation(); + d->mPendingTransformation = false; + } + + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((unsigned char*)d->mRawData.data(), d->mRawData.size()); + + // Store Exif info + image->setExifData(d->mExifData); + image->setComment(d->mComment.toUtf8().data()); + image->writeMetadata(); + + // Update mRawData + Exiv2::BasicIo& io = image->io(); + d->mRawData.resize(io.size()); + io.read((unsigned char*)d->mRawData.data(), io.size()); + + QDataStream stream(device); + stream.writeRawData(d->mRawData.data(), d->mRawData.size()); + + // Make sure we are up to date + loadFromData(d->mRawData); + return true; +} + +QString JpegContent::errorString() const +{ + return d->mErrorString; +} + +void JpegContent::setImage(const QImage& image) +{ + d->mRawData.clear(); + d->mImage = image; + d->mSize = image.size(); + d->mExifData["Exif.Photo.PixelXDimension"] = image.width(); + d->mExifData["Exif.Photo.PixelYDimension"] = image.height(); + resetOrientation(); + + d->mPendingTransformation = false; + d->mTransformMatrix = QMatrix(); +} + +} // namespace diff --git a/gwenview/lib/jpegcontent.h b/gwenview/lib/jpegcontent.h new file mode 100644 index 00000000..cdac51c0 --- /dev/null +++ b/gwenview/lib/jpegcontent.h @@ -0,0 +1,92 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef JPEGCONTENT_H +#define JPEGCONTENT_H + +// Local +#include +#include + +class QImage; +class QSize; +class QString; +class QIODevice; + +namespace Exiv2 +{ +class Image; +} + +namespace Gwenview +{ + +class GWENVIEWLIB_EXPORT JpegContent +{ +public: + JpegContent(); + ~JpegContent(); + + Orientation orientation() const; + void resetOrientation(); + + int dotsPerMeterX() const; + int dotsPerMeterY() const; + + QSize size() const; + + QString comment() const; + void setComment(const QString&); + + void transform(Orientation); + + QImage thumbnail() const; + void setThumbnail(const QImage&); + + // Recreate raw data to represent image + // Note: thumbnail must be updated separately + void setImage(const QImage& image); + + bool load(const QString& file); + bool loadFromData(const QByteArray& rawData); + /** + * Use this version of loadFromData if you already have an Exiv2::Image* + */ + bool loadFromData(const QByteArray& rawData, Exiv2::Image*); + bool save(const QString& file); + bool save(QIODevice*); + + QByteArray rawData() const; + + QString errorString() const; + +private: + struct Private; + Private *d; + + JpegContent(const JpegContent&); + void operator=(const JpegContent&); + void applyPendingTransformation(); + int dotsPerMeter(const QString& keyName) const; +}; + +} // namespace + +#endif /* JPEGCONTENT_H */ diff --git a/gwenview/lib/jpegerrormanager.h b/gwenview/lib/jpegerrormanager.h new file mode 100644 index 00000000..a520f3f3 --- /dev/null +++ b/gwenview/lib/jpegerrormanager.h @@ -0,0 +1,66 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef JPEGERRORMANAGER_H +#define JPEGERRORMANAGER_H + +#include + +extern "C" { +#define XMD_H +#include +#undef const +} + +namespace Gwenview +{ + +/** + * A simple error manager which overrides jpeg_error_mgr.error_exit to avoid + * calls to exit(). It uses setjmp, which I don't like, but I don't fill like + * introducing exceptions to the code base for now. + * + * In order to use it, give an instance of it to jpeg_decompress_struct.err, + * then call setjmp(errorManager.jmp_buffer) + */ +struct JPEGErrorManager : public jpeg_error_mgr +{ + JPEGErrorManager() + : jpeg_error_mgr() + { + jpeg_std_error(this); + error_exit = errorExitCallBack; + } + + jmp_buf jmp_buffer; + + static void errorExitCallBack(j_common_ptr cinfo) + { + JPEGErrorManager* myerr = static_cast(cinfo->err); + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + kWarning() << buffer ; + longjmp(myerr->jmp_buffer, 1); + } +}; + +} // namespace + +#endif /* JPEGERRORMANAGER_H */ diff --git a/gwenview/lib/kindproxymodel.cpp b/gwenview/lib/kindproxymodel.cpp new file mode 100644 index 00000000..705eb398 --- /dev/null +++ b/gwenview/lib/kindproxymodel.cpp @@ -0,0 +1,79 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "kindproxymodel.moc" + +// Local + +// KDE +#include +#include +#include + +// Qt + +namespace Gwenview +{ + +struct KindProxyModelPrivate +{ + MimeTypeUtils::Kinds mKindFilter; +}; + +KindProxyModel::KindProxyModel(QObject* parent) +: QSortFilterProxyModel(parent) +, d(new KindProxyModelPrivate) +{ +} + +KindProxyModel::~KindProxyModel() +{ + delete d; +} + +void KindProxyModel::setKindFilter(MimeTypeUtils::Kinds filter) +{ + if (d->mKindFilter != filter) { + d->mKindFilter = filter; + invalidateFilter(); + } +} + +MimeTypeUtils::Kinds KindProxyModel::kindFilter() const +{ + return d->mKindFilter; +} + +bool KindProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const +{ + if (d->mKindFilter == MimeTypeUtils::Kinds()) { + return true; + } + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + KFileItem fileItem = index.data(KDirModel::FileItemRole).value(); + if (fileItem.isNull()) { + return false; + } + MimeTypeUtils::Kinds kind = MimeTypeUtils::fileItemKind(fileItem); + return d->mKindFilter & kind; +} + +} // namespace diff --git a/gwenview/lib/kindproxymodel.h b/gwenview/lib/kindproxymodel.h new file mode 100644 index 00000000..693fb470 --- /dev/null +++ b/gwenview/lib/kindproxymodel.h @@ -0,0 +1,60 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef KINDPROXYMODEL_H +#define KINDPROXYMODEL_H + +#include + +// Local +#include + +// KDE + +// Qt +#include + +namespace Gwenview +{ + +struct KindProxyModelPrivate; +/** + * A simple proxy model allowing only objects of a certain kind + */ +class GWENVIEWLIB_EXPORT KindProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + KindProxyModel(QObject* parent = 0); + ~KindProxyModel(); + + void setKindFilter(MimeTypeUtils::Kinds); + MimeTypeUtils::Kinds kindFilter() const; + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const; // reimp + +private: + KindProxyModelPrivate* const d; +}; + +} // namespace + +#endif /* KINDPROXYMODEL_H */ diff --git a/gwenview/lib/libjpeg-62/README.jpeg b/gwenview/lib/libjpeg-62/README.jpeg new file mode 100644 index 00000000..86cc2066 --- /dev/null +++ b/gwenview/lib/libjpeg-62/README.jpeg @@ -0,0 +1,385 @@ +The Independent JPEG Group's JPEG software +========================================== + +README for release 6b of 27-Mar-1998 +==================================== + +This distribution contains the sixth public release of the Independent JPEG +Group's free JPEG software. You are welcome to redistribute this software and +to use it for any purpose, subject to the conditions under LEGAL ISSUES, below. + +Serious users of this software (particularly those incorporating it into +larger programs) should contact IJG at jpeg-info@uunet.uu.net to be added to +our electronic mailing list. Mailing list members are notified of updates +and have a chance to participate in technical discussions, etc. + +This software is the work of Tom Lane, Philip Gladstone, Jim Boucher, +Lee Crocker, Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi, +Guido Vollbeding, Ge' Weijers, and other members of the Independent JPEG +Group. + +IJG is not affiliated with the official ISO JPEG standards committee. + + +DOCUMENTATION ROADMAP +===================== + +This file contains the following sections: + +OVERVIEW General description of JPEG and the IJG software. +LEGAL ISSUES Copyright, lack of warranty, terms of distribution. +REFERENCES Where to learn more about JPEG. +ARCHIVE LOCATIONS Where to find newer versions of this software. +RELATED SOFTWARE Other stuff you should get. +FILE FORMAT WARS Software *not* to get. +TO DO Plans for future IJG releases. + +Other documentation files in the distribution are: + +User documentation: + install.doc How to configure and install the IJG software. + usage.doc Usage instructions for cjpeg, djpeg, jpegtran, + rdjpgcom, and wrjpgcom. + *.1 Unix-style man pages for programs (same info as usage.doc). + wizard.doc Advanced usage instructions for JPEG wizards only. + change.log Version-to-version change highlights. +Programmer and internal documentation: + libjpeg.doc How to use the JPEG library in your own programs. + example.c Sample code for calling the JPEG library. + structure.doc Overview of the JPEG library's internal structure. + filelist.doc Road map of IJG files. + coderules.doc Coding style rules --- please read if you contribute code. + +Please read at least the files install.doc and usage.doc. Useful information +can also be found in the JPEG FAQ (Frequently Asked Questions) article. See +ARCHIVE LOCATIONS below to find out where to obtain the FAQ article. + +If you want to understand how the JPEG code works, we suggest reading one or +more of the REFERENCES, then looking at the documentation files (in roughly +the order listed) before diving into the code. + + +OVERVIEW +======== + +This package contains C software to implement JPEG image compression and +decompression. JPEG (pronounced "jay-peg") is a standardized compression +method for full-color and gray-scale images. JPEG is intended for compressing +"real-world" scenes; line drawings, cartoons and other non-realistic images +are not its strong suit. JPEG is lossy, meaning that the output image is not +exactly identical to the input image. Hence you must not use JPEG if you +have to have identical output bits. However, on typical photographic images, +very good compression levels can be obtained with no visible change, and +remarkably high compression levels are possible if you can tolerate a +low-quality image. For more details, see the references, or just experiment +with various compression settings. + +This software implements JPEG baseline, extended-sequential, and progressive +compression processes. Provision is made for supporting all variants of these +processes, although some uncommon parameter settings aren't implemented yet. +For legal reasons, we are not distributing code for the arithmetic-coding +variants of JPEG; see LEGAL ISSUES. We have made no provision for supporting +the hierarchical or lossless processes defined in the standard. + +We provide a set of library routines for reading and writing JPEG image files, +plus two sample applications "cjpeg" and "djpeg", which use the library to +perform conversion between JPEG and some other popular image file formats. +The library is intended to be reused in other applications. + +In order to support file conversion and viewing software, we have included +considerable functionality beyond the bare JPEG coding/decoding capability; +for example, the color quantization modules are not strictly part of JPEG +decoding, but they are essential for output to colormapped file formats or +colormapped displays. These extra functions can be compiled out of the +library if not required for a particular application. We have also included +"jpegtran", a utility for lossless transcoding between different JPEG +processes, and "rdjpgcom" and "wrjpgcom", two simple applications for +inserting and extracting textual comments in JFIF files. + +The emphasis in designing this software has been on achieving portability and +flexibility, while also making it fast enough to be useful. In particular, +the software is not intended to be read as a tutorial on JPEG. (See the +REFERENCES section for introductory material.) Rather, it is intended to +be reliable, portable, industrial-strength code. We do not claim to have +achieved that goal in every aspect of the software, but we strive for it. + +We welcome the use of this software as a component of commercial products. +No royalty is required, but we do ask for an acknowledgement in product +documentation, as described under LEGAL ISSUES. + + +LEGAL ISSUES +============ + +In plain English: + +1. We don't promise that this software works. (But if you find any bugs, + please let us know!) +2. You can use this software for whatever you want. You don't have to pay us. +3. You may not pretend that you wrote this software. If you use it in a + program, you must acknowledge somewhere in your documentation that + you've used the IJG code. + +In legalese: + +The authors make NO WARRANTY or representation, either express or implied, +with respect to this software, its quality, accuracy, merchantability, or +fitness for a particular purpose. This software is provided "AS IS", and you, +its user, assume the entire risk as to its quality and accuracy. + +This software is copyright (C) 1991-1998, Thomas G. Lane. +All Rights Reserved except as specified below. + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +Permission is NOT granted for the use of any IJG author's name or company name +in advertising or publicity relating to this software or products derived from +it. This software may be referred to only as "the Independent JPEG Group's +software". + +We specifically permit and encourage the use of this software as the basis of +commercial products, provided that all warranty or liability claims are +assumed by the product vendor. + + +ansi2knr.c is included in this distribution by permission of L. Peter Deutsch, +sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA. +ansi2knr.c is NOT covered by the above copyright and conditions, but instead +by the usual distribution terms of the Free Software Foundation; principally, +that you must include source code if you redistribute it. (See the file +ansi2knr.c for full details.) However, since ansi2knr.c is not needed as part +of any program generated from the IJG code, this does not limit you more than +the foregoing paragraphs do. + +The Unix configuration script "configure" was produced with GNU Autoconf. +It is copyright by the Free Software Foundation but is freely distributable. +The same holds for its supporting scripts (config.guess, config.sub, +ltconfig, ltmain.sh). Another support script, install-sh, is copyright +by M.I.T. but is also freely distributable. + +It appears that the arithmetic coding option of the JPEG spec is covered by +patents owned by IBM, AT&T, and Mitsubishi. Hence arithmetic coding cannot +legally be used without obtaining one or more licenses. For this reason, +support for arithmetic coding has been removed from the free JPEG software. +(Since arithmetic coding provides only a marginal gain over the unpatented +Huffman mode, it is unlikely that very many implementations will support it.) +So far as we are aware, there are no patent restrictions on the remaining +code. + +The IJG distribution formerly included code to read and write GIF files. +To avoid entanglement with the Unisys LZW patent, GIF reading support has +been removed altogether, and the GIF writer has been simplified to produce +"uncompressed GIFs". This technique does not use the LZW algorithm; the +resulting GIF files are larger than usual, but are readable by all standard +GIF decoders. + +We are required to state that + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + + +REFERENCES +========== + +We highly recommend reading one or more of these references before trying to +understand the innards of the JPEG software. + +The best short technical introduction to the JPEG compression algorithm is + Wallace, Gregory K. "The JPEG Still Picture Compression Standard", + Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44. +(Adjacent articles in that issue discuss MPEG motion picture compression, +applications of JPEG, and related topics.) If you don't have the CACM issue +handy, a PostScript file containing a revised version of Wallace's article is +available at ftp://ftp.uu.net/graphics/jpeg/wallace.ps.gz. The file (actually +a preprint for an article that appeared in IEEE Trans. Consumer Electronics) +omits the sample images that appeared in CACM, but it includes corrections +and some added material. Note: the Wallace article is copyright ACM and IEEE, +and it may not be used for commercial purposes. + +A somewhat less technical, more leisurely introduction to JPEG can be found in +"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by +M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides +good explanations and example C code for a multitude of compression methods +including JPEG. It is an excellent source if you are comfortable reading C +code but don't know much about data compression in general. The book's JPEG +sample code is far from industrial-strength, but when you are ready to look +at a full implementation, you've got one here... + +The best full description of JPEG is the textbook "JPEG Still Image Data +Compression Standard" by William B. Pennebaker and Joan L. Mitchell, published +by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1. Price US$59.95, 638 pp. +The book includes the complete text of the ISO JPEG standards (DIS 10918-1 +and draft DIS 10918-2). This is by far the most complete exposition of JPEG +in existence, and we highly recommend it. + +The JPEG standard itself is not available electronically; you must order a +paper copy through ISO or ITU. (Unless you feel a need to own a certified +official copy, we recommend buying the Pennebaker and Mitchell book instead; +it's much cheaper and includes a great deal of useful explanatory material.) +In the USA, copies of the standard may be ordered from ANSI Sales at (212) +642-4900, or from Global Engineering Documents at (800) 854-7179. (ANSI +doesn't take credit card orders, but Global does.) It's not cheap: as of +1992, ANSI was charging $95 for Part 1 and $47 for Part 2, plus 7% +shipping/handling. The standard is divided into two parts, Part 1 being the +actual specification, while Part 2 covers compliance testing methods. Part 1 +is titled "Digital Compression and Coding of Continuous-tone Still Images, +Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS +10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of +Continuous-tone Still Images, Part 2: Compliance testing" and has document +numbers ISO/IEC IS 10918-2, ITU-T T.83. + +Some extensions to the original JPEG standard are defined in JPEG Part 3, +a newer ISO standard numbered ISO/IEC IS 10918-3 and ITU-T T.84. IJG +currently does not support any Part 3 extensions. + +The JPEG standard does not specify all details of an interchangeable file +format. For the omitted details we follow the "JFIF" conventions, revision +1.02. A copy of the JFIF spec is available from: + Literature Department + C-Cube Microsystems, Inc. + 1778 McCarthy Blvd. + Milpitas, CA 95035 + phone (408) 944-6300, fax (408) 944-6314 +A PostScript version of this document is available by FTP at +ftp://ftp.uu.net/graphics/jpeg/jfif.ps.gz. There is also a plain text +version at ftp://ftp.uu.net/graphics/jpeg/jfif.txt.gz, but it is missing +the figures. + +The TIFF 6.0 file format specification can be obtained by FTP from +ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation scheme +found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems. +IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6). +Instead, we recommend the JPEG design proposed by TIFF Technical Note #2 +(Compression tag 7). Copies of this Note can be obtained from ftp.sgi.com or +from ftp://ftp.uu.net/graphics/jpeg/. It is expected that the next revision +of the TIFF spec will replace the 6.0 JPEG design with the Note's design. +Although IJG's own code does not support TIFF/JPEG, the free libtiff library +uses our library to implement TIFF/JPEG per the Note. libtiff is available +from ftp://ftp.sgi.com/graphics/tiff/. + + +ARCHIVE LOCATIONS +================= + +The "official" archive site for this software is ftp.uu.net (Internet +address 192.48.96.9). The most recent released version can always be found +there in directory graphics/jpeg. This particular version will be archived +as ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz. If you don't have +direct Internet access, UUNET's archives are also available via UUCP; contact +help@uunet.uu.net for information on retrieving files that way. + +Numerous Internet sites maintain copies of the UUNET files. However, only +ftp.uu.net is guaranteed to have the latest official version. + +You can also obtain this software in DOS-compatible "zip" archive format from +the SimTel archives (ftp://ftp.simtel.net/pub/simtelnet/msdos/graphics/), or +on CompuServe in the Graphics Support forum (GO CIS:GRAPHSUP), library 12 +"JPEG Tools". Again, these versions may sometimes lag behind the ftp.uu.net +release. + +The JPEG FAQ (Frequently Asked Questions) article is a useful source of +general information about JPEG. It is updated constantly and therefore is +not included in this distribution. The FAQ is posted every two weeks to +Usenet newsgroups comp.graphics.misc, news.answers, and other groups. +It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/ +and other news.answers archive sites, including the official news.answers +archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/. +If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu +with body + send usenet/news.answers/jpeg-faq/part1 + send usenet/news.answers/jpeg-faq/part2 + + +RELATED SOFTWARE +================ + +Numerous viewing and image manipulation programs now support JPEG. (Quite a +few of them use this library to do so.) The JPEG FAQ described above lists +some of the more popular free and shareware viewers, and tells where to +obtain them on Internet. + +If you are on a Unix machine, we highly recommend Jef Poskanzer's free +PBMPLUS software, which provides many useful operations on PPM-format image +files. In particular, it can convert PPM images to and from a wide range of +other formats, thus making cjpeg/djpeg considerably more useful. The latest +version is distributed by the NetPBM group, and is available from numerous +sites, notably ftp://wuarchive.wustl.edu/graphics/graphics/packages/NetPBM/. +Unfortunately PBMPLUS/NETPBM is not nearly as portable as the IJG software is; +you are likely to have difficulty making it work on any non-Unix machine. + +A different free JPEG implementation, written by the PVRG group at Stanford, +is available from ftp://havefun.stanford.edu/pub/jpeg/. This program +is designed for research and experimentation rather than production use; +it is slower, harder to use, and less portable than the IJG code, but it +is easier to read and modify. Also, the PVRG code supports lossless JPEG, +which we do not. (On the other hand, it doesn't do progressive JPEG.) + + +FILE FORMAT WARS +================ + +Some JPEG programs produce files that are not compatible with our library. +The root of the problem is that the ISO JPEG committee failed to specify a +concrete file format. Some vendors "filled in the blanks" on their own, +creating proprietary formats that no one else could read. (For example, none +of the early commercial JPEG implementations for the Macintosh were able to +exchange compressed files.) + +The file format we have adopted is called JFIF (see REFERENCES). This format +has been agreed to by a number of major commercial JPEG vendors, and it has +become the de facto standard. JFIF is a minimal or "low end" representation. +We recommend the use of TIFF/JPEG (TIFF revision 6.0 as modified by TIFF +Technical Note #2) for "high end" applications that need to record a lot of +additional data about an image. TIFF/JPEG is fairly new and not yet widely +supported, unfortunately. + +The upcoming JPEG Part 3 standard defines a file format called SPIFF. +SPIFF is interoperable with JFIF, in the sense that most JFIF decoders should +be able to read the most common variant of SPIFF. SPIFF has some technical +advantages over JFIF, but its major claim to fame is simply that it is an +official standard rather than an informal one. At this point it is unclear +whether SPIFF will supersede JFIF or whether JFIF will remain the de-facto +standard. IJG intends to support SPIFF once the standard is frozen, but we +have not decided whether it should become our default output format or not. +(In any case, our decoder will remain capable of reading JFIF indefinitely.) + +Various proprietary file formats incorporating JPEG compression also exist. +We have little or no sympathy for the existence of these formats. Indeed, +one of the original reasons for developing this free software was to help +force convergence on common, open format standards for JPEG files. Don't +use a proprietary file format! + + +TO DO +===== + +The major thrust for v7 will probably be improvement of visual quality. +The current method for scaling the quantization tables is known not to be +very good at low Q values. We also intend to investigate block boundary +smoothing, "poor man's variable quantization", and other means of improving +quality-vs-file-size performance without sacrificing compatibility. + +In future versions, we are considering supporting some of the upcoming JPEG +Part 3 extensions --- principally, variable quantization and the SPIFF file +format. + +As always, speeding things up is of great interest. + +Please send bug reports, offers of help, etc. to jpeg-info@uunet.uu.net. diff --git a/gwenview/lib/libjpeg-62/jinclude.h b/gwenview/lib/libjpeg-62/jinclude.h new file mode 100644 index 00000000..0285f8f4 --- /dev/null +++ b/gwenview/lib/libjpeg-62/jinclude.h @@ -0,0 +1,90 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + +/* Include auto-config file to find out which system include files we need. */ + +#include "jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/gwenview/lib/libjpeg-62/jpegint.h b/gwenview/lib/libjpeg-62/jpegint.h new file mode 100644 index 00000000..0c827f7c --- /dev/null +++ b/gwenview/lib/libjpeg-62/jpegint.h @@ -0,0 +1,389 @@ +/* + * jpegint.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README.jpeg file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* perhaps this should be an array??? */ + JMETHOD(void, forward_DCT, (j_compress_ptr cinfo, + jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); + /* These routines are exported to allow insertion of extra markers */ + /* Probably only COM and APPn markers should be written this way */ + JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker, + unsigned int datalen)); + JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val)); +}; + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); + + /* This is here to share code between baseline and progressive decoders; */ + /* other modules probably should not use it */ + boolean insufficient_data; /* set TRUE after emitting warning */ +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_phuff_encoder jIPHEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_phuff_decoder jIPHDecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Compression module initialization routines */ +EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_phuff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_phuff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN(long) jdiv_round_up JPP((long a, long b)); +EXTERN(long) jround_up JPP((long a, long b)); +EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +#if 0 /* This table is not actually needed in v6a */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +#endif +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { + long dummy; +}; +struct jvirt_barray_control { + long dummy; +}; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/gwenview/lib/libjpeg-62/transupp.c b/gwenview/lib/libjpeg-62/transupp.c new file mode 100644 index 00000000..6ccdc948 --- /dev/null +++ b/gwenview/lib/libjpeg-62/transupp.c @@ -0,0 +1,930 @@ +/* + * transupp.c + * + * Copyright (C) 1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains image transformation routines and other utility code + * used by the jpegtran sample application. These are NOT part of the core + * JPEG library. But we keep these routines separate from jpegtran.c to + * ease the task of maintaining jpegtran-like programs that have other user + * interfaces. + */ + +/* Although this file really shouldn't have access to the library internals, + * it's helpful to let it call jround_up() and jcopy_block_row(). + */ +#define JPEG_INTERNALS + +#include "jinclude.h" +#include "jpeglib.h" +#include "transupp.h" /* My own external interface */ + + +#if TRANSFORMS_SUPPORTED + +/* + * Lossless image transformation routines. These routines work on DCT + * coefficient arrays and thus do not require any lossy decompression + * or recompression of the image. + * Thanks to Guido Vollbeding for the initial design and code of this feature. + * + * Horizontal flipping is done in-place, using a single top-to-bottom + * pass through the virtual source array. It will thus be much the + * fastest option for images larger than main memory. + * + * The other routines require a set of destination virtual arrays, so they + * need twice as much memory as jpegtran normally does. The destination + * arrays are always written in normal scan order (top to bottom) because + * the virtual array manager expects this. The source arrays will be scanned + * in the corresponding order, which means multiple passes through the source + * arrays for most of the transforms. That could result in much thrashing + * if the image is larger than main memory. + * + * Some notes about the operating environment of the individual transform + * routines: + * 1. Both the source and destination virtual arrays are allocated from the + * source JPEG object, and therefore should be manipulated by calling the + * source's memory manager. + * 2. The destination's component count should be used. It may be smaller + * than the source's when forcing to grayscale. + * 3. Likewise the destination's sampling factors should be used. When + * forcing to grayscale the destination's sampling factors will be all 1, + * and we may as well take that as the effective iMCU size. + * 4. When "trim" is in effect, the destination's dimensions will be the + * trimmed values but the source's will be untrimmed. + * 5. All the routines assume that the source and destination buffers are + * padded out to a full iMCU boundary. This is true, although for the + * source buffer it is an undocumented property of jdcoefct.c. + * Notes 2,3,4 boil down to this: generally we should use the destination's + * dimensions and ignore the source's. + */ + + +LOCAL(void) +do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays) +/* Horizontal flip; done in-place, so no separate dest array is required */ +{ + JDIMENSION MCU_cols, comp_width, blk_x, blk_y; + int ci, k, offset_y; + JBLOCKARRAY buffer; + JCOEFPTR ptr1, ptr2; + JCOEF temp1, temp2; + jpeg_component_info *compptr; + + /* Horizontal mirroring of DCT blocks is accomplished by swapping + * pairs of blocks in-place. Within a DCT block, we perform horizontal + * mirroring by changing the signs of odd-numbered columns. + * Partial iMCUs at the right edge are left untouched. + */ + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + for (blk_y = 0; blk_y < compptr->height_in_blocks; + blk_y += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) { + ptr1 = buffer[offset_y][blk_x]; + ptr2 = buffer[offset_y][comp_width - blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + temp1 = *ptr1; /* swap even column */ + temp2 = *ptr2; + *ptr1++ = temp2; + *ptr2++ = temp1; + temp1 = *ptr1; /* swap odd column with sign change */ + temp2 = *ptr2; + *ptr1++ = -temp2; + *ptr2++ = -temp1; + } + } + } + } + } +} + + +LOCAL(void) +do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Vertical flip */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* We output into a separate array because we can't touch different + * rows of the source virtual array simultaneously. Otherwise, this + * is a pretty straightforward analog of horizontal flip. + * Within a DCT block, vertical mirroring is done by changing the signs + * of odd-numbered rows. + * Partial iMCUs at the bottom edge are copied verbatim. + */ + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge blocks will be copied verbatim. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + /* copy even row */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + /* copy odd row with sign change */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Just copy row verbatim. */ + jcopy_block_row(src_buffer[offset_y], dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } + } +} + + +LOCAL(void) +do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transpose source into destination */ +{ + JDIMENSION dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Transposing pixels within a block just requires transposing the + * DCT coefficients. + * Partial iMCUs at the edges require no special treatment; we simply + * process all the available DCT blocks for every component. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } +} + + +LOCAL(void) +do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 90 degree rotation is equivalent to + * 1. Transposing the image; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) right edge properly. They just get transposed and + * not mirrored. + */ + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + if (dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* Edge blocks are transposed but not mirrored. */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 270 degree rotation is equivalent to + * 1. Horizontal mirroring; + * 2. Transposing the image. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) bottom edge properly. They just get transposed and + * not mirrored. + */ + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (dst_blk_y < comp_height) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[offset_x] + [comp_height - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 180 degree rotation is equivalent to + * 1. Vertical mirroring; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (dst_blk_y < comp_height) { + /* Row is within the vertically mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge rows are only mirrored horizontally. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + /* Process the blocks that can be mirrored both ways. */ + for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE; i += 2) { + /* For even row, negate every odd column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + /* For odd row, negate every even column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = *src_ptr++; + } + } + } + /* Any remaining right-edge blocks are only mirrored vertically. */ + for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Remaining rows are just mirrored horizontally. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[offset_y]; + /* Process the blocks that can be mirrored. */ + for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE2; i += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + } + /* Any remaining right-edge blocks are only copied. */ + for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE2; i++) + *dst_ptr++ = *src_ptr++; + } + } + } + } + } +} + + +LOCAL(void) +do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transverse transpose is equivalent to + * 1. 180 degree rotation; + * 2. Transposition; + * or + * 1. Horizontal mirroring; + * 2. Transposition; + * 3. Horizontal mirroring. + * These steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + if (dst_blk_y < comp_height) { + src_ptr = src_buffer[offset_x] + [comp_height - dst_blk_y - offset_y - 1]; + if (dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + i++; + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Right-edge blocks are mirrored in y only */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } + } else { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + if (dst_blk_x < comp_width) { + /* Bottom-edge blocks are mirrored in x only */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* At lower right corner, just transpose, no mirroring */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } + } +} + + +/* Request any required workspace. + * + * We allocate the workspace virtual arrays from the source decompression + * object, so that all the arrays (both the original data and the workspace) + * will be taken into account while making memory management decisions. + * Hence, this routine must be called after jpeg_read_header (which reads + * the image dimensions) and before jpeg_read_coefficients (which realizes + * the source's virtual arrays). + */ + +GLOBAL(void) +jtransform_request_workspace (j_decompress_ptr srcinfo, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *coef_arrays = NULL; + jpeg_component_info *compptr; + int ci; + + if (info->force_grayscale && + srcinfo->jpeg_color_space == JCS_YCbCr && + srcinfo->num_components == 3) { + /* We'll only process the first component */ + info->num_components = 1; + } else { + /* Process all the components */ + info->num_components = srcinfo->num_components; + } + + switch (info->transform) { + case JXFORM_NONE: + case JXFORM_FLIP_H: + /* Don't need a workspace array */ + break; + case JXFORM_FLIP_V: + case JXFORM_ROT_180: + /* Need workspace arrays having same dimensions as source image. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) compptr->v_samp_factor); + } + break; + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + /* Need workspace arrays having transposed dimensions. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) compptr->h_samp_factor); + } + break; + } + info->workspace_coef_arrays = coef_arrays; +} + + +/* Transpose destination image parameters */ + +LOCAL(void) +transpose_critical_parameters (j_compress_ptr dstinfo) +{ + int tblno, i, j, ci, itemp; + jpeg_component_info *compptr; + JQUANT_TBL *qtblptr; + JDIMENSION dtemp; + UINT16 qtemp; + + /* Transpose basic image dimensions */ + dtemp = dstinfo->image_width; + dstinfo->image_width = dstinfo->image_height; + dstinfo->image_height = dtemp; + + /* Transpose sampling factors */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + itemp = compptr->h_samp_factor; + compptr->h_samp_factor = compptr->v_samp_factor; + compptr->v_samp_factor = itemp; + } + + /* Transpose quantization tables */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + qtblptr = dstinfo->quant_tbl_ptrs[tblno]; + if (qtblptr != NULL) { + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < i; j++) { + qtemp = qtblptr->quantval[i*DCTSIZE+j]; + qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i]; + qtblptr->quantval[j*DCTSIZE+i] = qtemp; + } + } + } + } +} + + +/* Trim off any partial iMCUs on the indicated destination edge */ + +LOCAL(void) +trim_right_edge (j_compress_ptr dstinfo) +{ + int ci, max_h_samp_factor; + JDIMENSION MCU_cols; + + /* We have to compute max_h_samp_factor ourselves, + * because it hasn't been set yet in the destination + * (and we don't want to use the source's value). + */ + max_h_samp_factor = 1; + for (ci = 0; ci < dstinfo->num_components; ci++) { + int h_samp_factor = dstinfo->comp_info[ci].h_samp_factor; + max_h_samp_factor = MAX(max_h_samp_factor, h_samp_factor); + } + MCU_cols = dstinfo->image_width / (max_h_samp_factor * DCTSIZE); + if (MCU_cols > 0) /* can't trim to 0 pixels */ + dstinfo->image_width = MCU_cols * (max_h_samp_factor * DCTSIZE); +} + +LOCAL(void) +trim_bottom_edge (j_compress_ptr dstinfo) +{ + int ci, max_v_samp_factor; + JDIMENSION MCU_rows; + + /* We have to compute max_v_samp_factor ourselves, + * because it hasn't been set yet in the destination + * (and we don't want to use the source's value). + */ + max_v_samp_factor = 1; + for (ci = 0; ci < dstinfo->num_components; ci++) { + int v_samp_factor = dstinfo->comp_info[ci].v_samp_factor; + max_v_samp_factor = MAX(max_v_samp_factor, v_samp_factor); + } + MCU_rows = dstinfo->image_height / (max_v_samp_factor * DCTSIZE); + if (MCU_rows > 0) /* can't trim to 0 pixels */ + dstinfo->image_height = MCU_rows * (max_v_samp_factor * DCTSIZE); +} + + +/* Adjust output image parameters as needed. + * + * This must be called after jpeg_copy_critical_parameters() + * and before jpeg_write_coefficients(). + * + * The return value is the set of virtual coefficient arrays to be written + * (either the ones allocated by jtransform_request_workspace, or the + * original source data arrays). The caller will need to pass this value + * to jpeg_write_coefficients(). + */ + +GLOBAL(jvirt_barray_ptr *) +jtransform_adjust_parameters (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + srcinfo = srcinfo; /* avoid unsued parameter warning */ + /* If force-to-grayscale is requested, adjust destination parameters */ + if (info->force_grayscale) { + /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed + * properly. Among other things, the target h_samp_factor & v_samp_factor + * will get set to 1, which typically won't match the source. + * In fact we do this even if the source is already grayscale; that + * provides an easy way of coercing a grayscale JPEG with funny sampling + * factors to the customary 1,1. (Some decoders fail on other factors.) + */ + if ((dstinfo->jpeg_color_space == JCS_YCbCr && + dstinfo->num_components == 3) || + (dstinfo->jpeg_color_space == JCS_GRAYSCALE && + dstinfo->num_components == 1)) { + /* We have to preserve the source's quantization table number. */ + int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no; + jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE); + dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no; + } else { + /* Sorry, can't do it */ + ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL); + } + } + + /* Correct the destination's image dimensions etc if necessary */ + switch (info->transform) { + case JXFORM_NONE: + /* Nothing to do */ + break; + case JXFORM_FLIP_H: + if (info->trim) + trim_right_edge(dstinfo); + break; + case JXFORM_FLIP_V: + if (info->trim) + trim_bottom_edge(dstinfo); + break; + case JXFORM_TRANSPOSE: + transpose_critical_parameters(dstinfo); + /* transpose does NOT have to trim anything */ + break; + case JXFORM_TRANSVERSE: + transpose_critical_parameters(dstinfo); + if (info->trim) { + trim_right_edge(dstinfo); + trim_bottom_edge(dstinfo); + } + break; + case JXFORM_ROT_90: + transpose_critical_parameters(dstinfo); + if (info->trim) + trim_right_edge(dstinfo); + break; + case JXFORM_ROT_180: + if (info->trim) { + trim_right_edge(dstinfo); + trim_bottom_edge(dstinfo); + } + break; + case JXFORM_ROT_270: + transpose_critical_parameters(dstinfo); + if (info->trim) + trim_bottom_edge(dstinfo); + break; + } + + /* Return the appropriate output data set */ + if (info->workspace_coef_arrays != NULL) + return info->workspace_coef_arrays; + return src_coef_arrays; +} + + +/* Execute the actual transformation, if any. + * + * This must be called *after* jpeg_write_coefficients, because it depends + * on jpeg_write_coefficients to have computed subsidiary values such as + * the per-component width and height fields in the destination object. + * + * Note that some transformations will modify the source data arrays! + */ + +GLOBAL(void) +jtransform_execute_transformation (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays; + + switch (info->transform) { + case JXFORM_NONE: + break; + case JXFORM_FLIP_H: + do_flip_h(srcinfo, dstinfo, src_coef_arrays); + break; + case JXFORM_FLIP_V: + do_flip_v(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSPOSE: + do_transpose(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSVERSE: + do_transverse(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_90: + do_rot_90(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_180: + do_rot_180(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_270: + do_rot_270(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + } +} + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* Setup decompression object to save desired markers in memory. + * This must be called before jpeg_read_header() to have the desired effect. + */ + +GLOBAL(void) +jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option) +{ +#ifdef SAVE_MARKERS_SUPPORTED + int m; + + /* Save comments except under NONE option */ + if (option != JCOPYOPT_NONE) { + jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF); + } + /* Save all types of APPn markers iff ALL option */ + if (option == JCOPYOPT_ALL) { + for (m = 0; m < 16; m++) + jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF); + } +#endif /* SAVE_MARKERS_SUPPORTED */ +} + +/* Copy markers saved in the given source object to the destination object. + * This should be called just after jpeg_start_compress() or + * jpeg_write_coefficients(). + * Note that those routines will have written the SOI, and also the + * JFIF APP0 or Adobe APP14 markers if selected. + */ + +GLOBAL(void) +jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option) +{ + option = option; /* avoid unsued parameter warning */ + jpeg_saved_marker_ptr marker; + + /* In the current implementation, we don't actually need to examine the + * option flag here; we just copy everything that got saved. + * But to avoid confusion, we do not output JFIF and Adobe APP14 markers + * if the encoder library already wrote one. + */ + for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) { + if (dstinfo->write_JFIF_header && + marker->marker == JPEG_APP0 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x4A && + GETJOCTET(marker->data[1]) == 0x46 && + GETJOCTET(marker->data[2]) == 0x49 && + GETJOCTET(marker->data[3]) == 0x46 && + GETJOCTET(marker->data[4]) == 0) + continue; /* reject duplicate JFIF */ + if (dstinfo->write_Adobe_marker && + marker->marker == JPEG_APP0+14 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x41 && + GETJOCTET(marker->data[1]) == 0x64 && + GETJOCTET(marker->data[2]) == 0x6F && + GETJOCTET(marker->data[3]) == 0x62 && + GETJOCTET(marker->data[4]) == 0x65) + continue; /* reject duplicate Adobe */ +#ifdef NEED_FAR_POINTERS + /* We could use jpeg_write_marker if the data weren't FAR... */ + { + unsigned int i; + jpeg_write_m_header(dstinfo, marker->marker, marker->data_length); + for (i = 0; i < marker->data_length; i++) + jpeg_write_m_byte(dstinfo, marker->data[i]); + } +#else + jpeg_write_marker(dstinfo, marker->marker, + marker->data, marker->data_length); +#endif + } +} diff --git a/gwenview/lib/libjpeg-62/transupp.h b/gwenview/lib/libjpeg-62/transupp.h new file mode 100644 index 00000000..39e1fbbf --- /dev/null +++ b/gwenview/lib/libjpeg-62/transupp.h @@ -0,0 +1,138 @@ +/* + * transupp.h + * + * Copyright (C) 1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for image transformation routines and + * other utility code used by the jpegtran sample application. These are + * NOT part of the core JPEG library. But we keep these routines separate + * from jpegtran.c to ease the task of maintaining jpegtran-like programs + * that have other user interfaces. + * + * NOTE: all the routines declared here have very specific requirements + * about when they are to be executed during the reading and writing of the + * source and destination files. See the comments in transupp.c, or see + * jpegtran.c for an example of correct usage. + */ + +#ifndef TRANSUPP_H +#define TRANSUPP_H + +/* If you happen not to want the image transform support, disable it here */ +#ifndef TRANSFORMS_SUPPORTED +#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */ +#endif + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jtransform_request_workspace jTrRequest +#define jtransform_adjust_parameters jTrAdjust +#define jtransform_execute_transformation jTrExec +#define jcopy_markers_setup jCMrkSetup +#define jcopy_markers_execute jCMrkExec +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* + * Codes for supported types of image transformations. + */ + +typedef enum { + JXFORM_NONE, /* no transformation */ + JXFORM_FLIP_H, /* horizontal flip */ + JXFORM_FLIP_V, /* vertical flip */ + JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */ + JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */ + JXFORM_ROT_90, /* 90-degree clockwise rotation */ + JXFORM_ROT_180, /* 180-degree rotation */ + JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */ +} JXFORM_CODE; + +/* + * Although rotating and flipping data expressed as DCT coefficients is not + * hard, there is an asymmetry in the JPEG format specification for images + * whose dimensions aren't multiples of the iMCU size. The right and bottom + * image edges are padded out to the next iMCU boundary with junk data; but + * no padding is possible at the top and left edges. If we were to flip + * the whole image including the pad data, then pad garbage would become + * visible at the top and/or left, and real pixels would disappear into the + * pad margins --- perhaps permanently, since encoders & decoders may not + * bother to preserve DCT blocks that appear to be completely outside the + * nominal image area. So, we have to exclude any partial iMCUs from the + * basic transformation. + * + * Transpose is the only transformation that can handle partial iMCUs at the + * right and bottom edges completely cleanly. flip_h can flip partial iMCUs + * at the bottom, but leaves any partial iMCUs at the right edge untouched. + * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched. + * The other transforms are defined as combinations of these basic transforms + * and process edge blocks in a way that preserves the equivalence. + * + * The "trim" option causes untransformable partial iMCUs to be dropped; + * this is not strictly lossless, but it usually gives the best-looking + * result for odd-size images. Note that when this option is active, + * the expected mathematical equivalences between the transforms may not hold. + * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim + * followed by -rot 180 -trim trims both edges.) + * + * We also offer a "force to grayscale" option, which simply discards the + * chrominance channels of a YCbCr image. This is lossless in the sense that + * the luminance channel is preserved exactly. It's not the same kind of + * thing as the rotate/flip transformations, but it's convenient to handle it + * as part of this package, mainly because the transformation routines have to + * be aware of the option to know how many components to work on. + */ + +typedef struct { + /* Options: set by caller */ + JXFORM_CODE transform; /* image transform operator */ + boolean trim; /* if TRUE, trim partial MCUs as needed */ + boolean force_grayscale; /* if TRUE, convert color image to grayscale */ + + /* Internal workspace: caller should not touch these */ + int num_components; /* # of components in workspace */ + jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */ +} jpeg_transform_info; + +#if TRANSFORMS_SUPPORTED + +/* Request any required workspace */ +EXTERN(void) jtransform_request_workspace +JPP((j_decompress_ptr srcinfo, jpeg_transform_info *info)); +/* Adjust output image parameters */ +EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters +JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); +/* Execute the actual transformation, if any */ +EXTERN(void) jtransform_execute_transformation +JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); + +#endif /* TRANSFORMS_SUPPORTED */ + +/* + * Support for copying optional markers from source to destination file. + */ + +typedef enum { + JCOPYOPT_NONE, /* copy no optional markers */ + JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */ + JCOPYOPT_ALL /* copy all optional markers */ +} JCOPY_OPTION; + +#define JCOPYOPT_DEFAULT JCOPYOPT_COMMENTS /* recommended default */ + +/* Setup decompression object to save desired markers in memory */ +EXTERN(void) jcopy_markers_setup +JPP((j_decompress_ptr srcinfo, JCOPY_OPTION option)); +/* Copy markers saved in the given source object to the destination object */ +EXTERN(void) jcopy_markers_execute +JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option)); + +#endif + diff --git a/gwenview/lib/libjpeg-80/README.jpeg b/gwenview/lib/libjpeg-80/README.jpeg new file mode 100644 index 00000000..7bc588f0 --- /dev/null +++ b/gwenview/lib/libjpeg-80/README.jpeg @@ -0,0 +1,325 @@ +The Independent JPEG Group's JPEG software +========================================== + +README for release 8 of 10-Jan-2010 +=================================== + +This distribution contains the eighth public release of the Independent JPEG +Group's free JPEG software. You are welcome to redistribute this software and +to use it for any purpose, subject to the conditions under LEGAL ISSUES, below. + +This software is the work of Tom Lane, Guido Vollbeding, Philip Gladstone, +Bill Allombert, Jim Boucher, Lee Crocker, Bob Friesenhahn, Ben Jackson, +Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi, Ge' Weijers, +and other members of the Independent JPEG Group. + +IJG is not affiliated with the official ISO JPEG standards committee. + + +DOCUMENTATION ROADMAP +===================== + +This file contains the following sections: + +OVERVIEW General description of JPEG and the IJG software. +LEGAL ISSUES Copyright, lack of warranty, terms of distribution. +REFERENCES Where to learn more about JPEG. +ARCHIVE LOCATIONS Where to find newer versions of this software. +ACKNOWLEDGMENTS Special thanks. +FILE FORMAT WARS Software *not* to get. +TO DO Plans for future IJG releases. + +Other documentation files in the distribution are: + +User documentation: + install.txt How to configure and install the IJG software. + usage.txt Usage instructions for cjpeg, djpeg, jpegtran, + rdjpgcom, and wrjpgcom. + *.1 Unix-style man pages for programs (same info as usage.txt). + wizard.txt Advanced usage instructions for JPEG wizards only. + change.log Version-to-version change highlights. +Programmer and internal documentation: + libjpeg.txt How to use the JPEG library in your own programs. + example.c Sample code for calling the JPEG library. + structure.txt Overview of the JPEG library's internal structure. + filelist.txt Road map of IJG files. + coderules.txt Coding style rules --- please read if you contribute code. + +Please read at least the files install.txt and usage.txt. Some information +can also be found in the JPEG FAQ (Frequently Asked Questions) article. See +ARCHIVE LOCATIONS below to find out where to obtain the FAQ article. + +If you want to understand how the JPEG code works, we suggest reading one or +more of the REFERENCES, then looking at the documentation files (in roughly +the order listed) before diving into the code. + + +OVERVIEW +======== + +This package contains C software to implement JPEG image encoding, decoding, +and transcoding. JPEG (pronounced "jay-peg") is a standardized compression +method for full-color and gray-scale images. + +This software implements JPEG baseline, extended-sequential, and progressive +compression processes. Provision is made for supporting all variants of these +processes, although some uncommon parameter settings aren't implemented yet. +We have made no provision for supporting the hierarchical or lossless +processes defined in the standard. + +We provide a set of library routines for reading and writing JPEG image files, +plus two sample applications "cjpeg" and "djpeg", which use the library to +perform conversion between JPEG and some other popular image file formats. +The library is intended to be reused in other applications. + +In order to support file conversion and viewing software, we have included +considerable functionality beyond the bare JPEG coding/decoding capability; +for example, the color quantization modules are not strictly part of JPEG +decoding, but they are essential for output to colormapped file formats or +colormapped displays. These extra functions can be compiled out of the +library if not required for a particular application. + +We have also included "jpegtran", a utility for lossless transcoding between +different JPEG processes, and "rdjpgcom" and "wrjpgcom", two simple +applications for inserting and extracting textual comments in JFIF files. + +The emphasis in designing this software has been on achieving portability and +flexibility, while also making it fast enough to be useful. In particular, +the software is not intended to be read as a tutorial on JPEG. (See the +REFERENCES section for introductory material.) Rather, it is intended to +be reliable, portable, industrial-strength code. We do not claim to have +achieved that goal in every aspect of the software, but we strive for it. + +We welcome the use of this software as a component of commercial products. +No royalty is required, but we do ask for an acknowledgement in product +documentation, as described under LEGAL ISSUES. + + +LEGAL ISSUES +============ + +In plain English: + +1. We don't promise that this software works. (But if you find any bugs, + please let us know!) +2. You can use this software for whatever you want. You don't have to pay us. +3. You may not pretend that you wrote this software. If you use it in a + program, you must acknowledge somewhere in your documentation that + you've used the IJG code. + +In legalese: + +The authors make NO WARRANTY or representation, either express or implied, +with respect to this software, its quality, accuracy, merchantability, or +fitness for a particular purpose. This software is provided "AS IS", and you, +its user, assume the entire risk as to its quality and accuracy. + +This software is copyright (C) 1991-2010, Thomas G. Lane, Guido Vollbeding. +All Rights Reserved except as specified below. + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +Permission is NOT granted for the use of any IJG author's name or company name +in advertising or publicity relating to this software or products derived from +it. This software may be referred to only as "the Independent JPEG Group's +software". + +We specifically permit and encourage the use of this software as the basis of +commercial products, provided that all warranty or liability claims are +assumed by the product vendor. + + +ansi2knr.c is included in this distribution by permission of L. Peter Deutsch, +sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA. +ansi2knr.c is NOT covered by the above copyright and conditions, but instead +by the usual distribution terms of the Free Software Foundation; principally, +that you must include source code if you redistribute it. (See the file +ansi2knr.c for full details.) However, since ansi2knr.c is not needed as part +of any program generated from the IJG code, this does not limit you more than +the foregoing paragraphs do. + +The Unix configuration script "configure" was produced with GNU Autoconf. +It is copyright by the Free Software Foundation but is freely distributable. +The same holds for its supporting scripts (config.guess, config.sub, +ltmain.sh). Another support script, install-sh, is copyright by X Consortium +but is also freely distributable. + +The IJG distribution formerly included code to read and write GIF files. +To avoid entanglement with the Unisys LZW patent, GIF reading support has +been removed altogether, and the GIF writer has been simplified to produce +"uncompressed GIFs". This technique does not use the LZW algorithm; the +resulting GIF files are larger than usual, but are readable by all standard +GIF decoders. + +We are required to state that + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + + +REFERENCES +========== + +We recommend reading one or more of these references before trying to +understand the innards of the JPEG software. + +The best short technical introduction to the JPEG compression algorithm is + Wallace, Gregory K. "The JPEG Still Picture Compression Standard", + Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44. +(Adjacent articles in that issue discuss MPEG motion picture compression, +applications of JPEG, and related topics.) If you don't have the CACM issue +handy, a PostScript file containing a revised version of Wallace's article is +available at http://www.ijg.org/files/wallace.ps.gz. The file (actually +a preprint for an article that appeared in IEEE Trans. Consumer Electronics) +omits the sample images that appeared in CACM, but it includes corrections +and some added material. Note: the Wallace article is copyright ACM and IEEE, +and it may not be used for commercial purposes. + +A somewhat less technical, more leisurely introduction to JPEG can be found in +"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by +M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides +good explanations and example C code for a multitude of compression methods +including JPEG. It is an excellent source if you are comfortable reading C +code but don't know much about data compression in general. The book's JPEG +sample code is far from industrial-strength, but when you are ready to look +at a full implementation, you've got one here... + +The best currently available description of JPEG is the textbook "JPEG Still +Image Data Compression Standard" by William B. Pennebaker and Joan L. +Mitchell, published by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1. +Price US$59.95, 638 pp. The book includes the complete text of the ISO JPEG +standards (DIS 10918-1 and draft DIS 10918-2). +Although this is by far the most detailed and comprehensive exposition of +JPEG publicly available, we point out that it is still missing an explanation +of the most essential properties and algorithms of the underlying DCT +technology. +If you think that you know about DCT-based JPEG after reading this book, +then you are in delusion. The real fundamentals and corresponding potential +of DCT-based JPEG are not publicly known so far, and that is the reason for +all the mistaken developments taking place in the image coding domain. + +The original JPEG standard is divided into two parts, Part 1 being the actual +specification, while Part 2 covers compliance testing methods. Part 1 is +titled "Digital Compression and Coding of Continuous-tone Still Images, +Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS +10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of +Continuous-tone Still Images, Part 2: Compliance testing" and has document +numbers ISO/IEC IS 10918-2, ITU-T T.83. +IJG JPEG 8 introduces an implementation of the JPEG SmartScale extension +which is specified in a contributed document at ITU and ISO with title "ITU-T +JPEG-Plus Proposal for Extending ITU-T T.81 for Advanced Image Coding", April +2006, Geneva, Switzerland. The latest version of the document is Revision 3. + +The JPEG standard does not specify all details of an interchangeable file +format. For the omitted details we follow the "JFIF" conventions, revision +1.02. JFIF 1.02 has been adopted as an Ecma International Technical Report +and thus received a formal publication status. It is available as a free +download in PDF format from +http://www.ecma-international.org/publications/techreports/E-TR-098.htm. +A PostScript version of the JFIF document is available at +http://www.ijg.org/files/jfif.ps.gz. There is also a plain text version at +http://www.ijg.org/files/jfif.txt.gz, but it is missing the figures. + +The TIFF 6.0 file format specification can be obtained by FTP from +ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation scheme +found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems. +IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6). +Instead, we recommend the JPEG design proposed by TIFF Technical Note #2 +(Compression tag 7). Copies of this Note can be obtained from +http://www.ijg.org/files/. It is expected that the next revision +of the TIFF spec will replace the 6.0 JPEG design with the Note's design. +Although IJG's own code does not support TIFF/JPEG, the free libtiff library +uses our library to implement TIFF/JPEG per the Note. + + +ARCHIVE LOCATIONS +================= + +The "official" archive site for this software is www.ijg.org. +The most recent released version can always be found there in +directory "files". This particular version will be archived as +http://www.ijg.org/files/jpegsrc.v8.tar.gz, and in Windows-compatible +"zip" archive format as http://www.ijg.org/files/jpegsr8.zip. + +The JPEG FAQ (Frequently Asked Questions) article is a source of some +general information about JPEG. +It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/ +and other news.answers archive sites, including the official news.answers +archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/. +If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu +with body + send usenet/news.answers/jpeg-faq/part1 + send usenet/news.answers/jpeg-faq/part2 + + +ACKNOWLEDGMENTS +=============== + +Thank to Juergen Bruder for providing me with a copy of the common DCT +algorithm article, only to find out that I had come to the same result +in a more direct and comprehensible way with a more generative approach. + +Thank to Istvan Sebestyen and Joan L. Mitchell for inviting me to the +ITU JPEG (Study Group 16) meeting in Geneva, Switzerland. + +Thank to Thomas Wiegand and Gary Sullivan for inviting me to the +Joint Video Team (MPEG & ITU) meeting in Geneva, Switzerland. + +Thank to John Korejwa and Massimo Ballerini for inviting me to +fruitful consultations in Boston, MA and Milan, Italy. + +Thank to Hendrik Elstner, Roland Fassauer, Simone Zuck, Guenther +Maier-Gerber, and Walter Stoeber for corresponding business development. + +Thank to Nico Zschach and Dirk Stelling of the technical support team +at the Digital Images company in Halle for providing me with extra +equipment for configuration tests. + +Thank to Richard F. Lyon (then of Foveon Inc.) for fruitful +communication about JPEG configuration in Sigma Photo Pro software. + +Thank to Andrew Finkenstadt for hosting the ijg.org site. + +Last but not least special thank to Thomas G. Lane for the original +design and development of this singular software package. + + +FILE FORMAT WARS +================ + +The ISO JPEG standards committee actually promotes different formats like +"JPEG 2000" or "JPEG XR" which are incompatible with original DCT-based +JPEG and which are based on faulty technologies. IJG therefore does not +and will not support such momentary mistakes (see REFERENCES). +We have little or no sympathy for the promotion of these formats. Indeed, +one of the original reasons for developing this free software was to help +force convergence on common, interoperable format standards for JPEG files. +Don't use an incompatible file format! +(In any case, our decoder will remain capable of reading existing JPEG +image files indefinitely.) + + +TO DO +===== + +Version 8.0 is the first release of a new generation JPEG standard +to overcome the limitations of the original JPEG specification. +More features are being prepared for coming releases... + +Please send bug reports, offers of help, etc. to jpeg-info@uc.ag. diff --git a/gwenview/lib/libjpeg-80/jinclude.h b/gwenview/lib/libjpeg-80/jinclude.h new file mode 100644 index 00000000..0285f8f4 --- /dev/null +++ b/gwenview/lib/libjpeg-80/jinclude.h @@ -0,0 +1,90 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + +/* Include auto-config file to find out which system include files we need. */ + +#include "jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/gwenview/lib/libjpeg-80/jpegint.h b/gwenview/lib/libjpeg-80/jpegint.h new file mode 100644 index 00000000..987cce8e --- /dev/null +++ b/gwenview/lib/libjpeg-80/jpegint.h @@ -0,0 +1,404 @@ +/* + * jpegint.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 1997-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +typedef JMETHOD(void, forward_DCT_ptr, + (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); + +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* It is useful to allow each component to have a separate FDCT method. */ + forward_DCT_ptr forward_DCT[MAX_COMPONENTS]; +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); + /* These routines are exported to allow insertion of extra markers */ + /* Probably only COM and APPn markers should be written this way */ + JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker, + unsigned int datalen)); + JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val)); +}; + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_arith_encoder jIAEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_arith_decoder jIADecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#define jpeg_natural_order7 jZAGTable7 +#define jpeg_natural_order6 jZAGTable6 +#define jpeg_natural_order5 jZAGTable5 +#define jpeg_natural_order4 jZAGTable4 +#define jpeg_natural_order3 jZAGTable3 +#define jpeg_natural_order2 jZAGTable2 +#define jpeg_aritab jAriTab +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Compression module initialization routines */ +EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_arith_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_arith_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN(long) jdiv_round_up JPP((long a, long b)); +EXTERN(long) jround_up JPP((long a, long b)); +EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +#if 0 /* This table is not actually needed in v6a */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +#endif +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ +extern const int jpeg_natural_order7[]; /* zz to natural order for 7x7 block */ +extern const int jpeg_natural_order6[]; /* zz to natural order for 6x6 block */ +extern const int jpeg_natural_order5[]; /* zz to natural order for 5x5 block */ +extern const int jpeg_natural_order4[]; /* zz to natural order for 4x4 block */ +extern const int jpeg_natural_order3[]; /* zz to natural order for 3x3 block */ +extern const int jpeg_natural_order2[]; /* zz to natural order for 2x2 block */ + +/* Arithmetic coding probability estimation tables in jaricom.c */ +extern const INT32 jpeg_aritab[]; + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { + long dummy; +}; +struct jvirt_barray_control { + long dummy; +}; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/gwenview/lib/libjpeg-80/transupp.c b/gwenview/lib/libjpeg-80/transupp.c new file mode 100644 index 00000000..99306622 --- /dev/null +++ b/gwenview/lib/libjpeg-80/transupp.c @@ -0,0 +1,1585 @@ +/* + * transupp.c + * + * Copyright (C) 1997-2009, Thomas G. Lane, Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains image transformation routines and other utility code + * used by the jpegtran sample application. These are NOT part of the core + * JPEG library. But we keep these routines separate from jpegtran.c to + * ease the task of maintaining jpegtran-like programs that have other user + * interfaces. + */ + +/* Although this file really shouldn't have access to the library internals, + * it's helpful to let it call jround_up() and jcopy_block_row(). + */ +#define JPEG_INTERNALS + +#include "jinclude.h" +#include "jpeglib.h" +#include "transupp.h" /* My own external interface */ +#include /* to declare isdigit() */ + +#undef EXTERN +#define EXTERN(type) type + +#if TRANSFORMS_SUPPORTED + +/* + * Lossless image transformation routines. These routines work on DCT + * coefficient arrays and thus do not require any lossy decompression + * or recompression of the image. + * Thanks to Guido Vollbeding for the initial design and code of this feature, + * and to Ben Jackson for introducing the cropping feature. + * + * Horizontal flipping is done in-place, using a single top-to-bottom + * pass through the virtual source array. It will thus be much the + * fastest option for images larger than main memory. + * + * The other routines require a set of destination virtual arrays, so they + * need twice as much memory as jpegtran normally does. The destination + * arrays are always written in normal scan order (top to bottom) because + * the virtual array manager expects this. The source arrays will be scanned + * in the corresponding order, which means multiple passes through the source + * arrays for most of the transforms. That could result in much thrashing + * if the image is larger than main memory. + * + * If cropping or trimming is involved, the destination arrays may be smaller + * than the source arrays. Note it is not possible to do horizontal flip + * in-place when a nonzero Y crop offset is specified, since we'd have to move + * data from one block row to another but the virtual array manager doesn't + * guarantee we can touch more than one row at a time. So in that case, + * we have to use a separate destination array. + * + * Some notes about the operating environment of the individual transform + * routines: + * 1. Both the source and destination virtual arrays are allocated from the + * source JPEG object, and therefore should be manipulated by calling the + * source's memory manager. + * 2. The destination's component count should be used. It may be smaller + * than the source's when forcing to grayscale. + * 3. Likewise the destination's sampling factors should be used. When + * forcing to grayscale the destination's sampling factors will be all 1, + * and we may as well take that as the effective iMCU size. + * 4. When "trim" is in effect, the destination's dimensions will be the + * trimmed values but the source's will be untrimmed. + * 5. When "crop" is in effect, the destination's dimensions will be the + * cropped values but the source's will be uncropped. Each transform + * routine is responsible for picking up source data starting at the + * correct X and Y offset for the crop region. (The X and Y offsets + * passed to the transform routines are measured in iMCU blocks of the + * destination.) + * 6. All the routines assume that the source and destination buffers are + * padded out to a full iMCU boundary. This is true, although for the + * source buffer it is an undocumented property of jdcoefct.c. + */ + + +EXTERN(void) +do_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Crop. This is only used when no rotate/flip is requested with the crop. */ +{ + JDIMENSION dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + jpeg_component_info *compptr; + + /* We simply have to copy the right amount of data (the destination's + * image size) starting at the given X and Y offsets in the source. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, + dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } +} + + +EXTERN(void) +do_flip_h_no_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, + jvirt_barray_ptr *src_coef_arrays) +/* Horizontal flip; done in-place, so no separate dest array is required. + * NB: this only works when y_crop_offset is zero. + */ +{ + JDIMENSION MCU_cols, comp_width, blk_x, blk_y, x_crop_blocks; + int ci, k, offset_y; + JBLOCKARRAY buffer; + JCOEFPTR ptr1, ptr2; + JCOEF temp1, temp2; + jpeg_component_info *compptr; + + /* Horizontal mirroring of DCT blocks is accomplished by swapping + * pairs of blocks in-place. Within a DCT block, we perform horizontal + * mirroring by changing the signs of odd-numbered columns. + * Partial iMCUs at the right edge are left untouched. + */ + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + for (blk_y = 0; blk_y < compptr->height_in_blocks; + blk_y += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + /* Do the mirroring */ + for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) { + ptr1 = buffer[offset_y][blk_x]; + ptr2 = buffer[offset_y][comp_width - blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + temp1 = *ptr1; /* swap even column */ + temp2 = *ptr2; + *ptr1++ = temp2; + *ptr2++ = temp1; + temp1 = *ptr1; /* swap odd column with sign change */ + temp2 = *ptr2; + *ptr1++ = -temp2; + *ptr2++ = -temp1; + } + } + if (x_crop_blocks > 0) { + /* Now left-justify the portion of the data to be kept. + * We can't use a single jcopy_block_row() call because that routine + * depends on memcpy(), whose behavior is unspecified for overlapping + * source and destination areas. Sigh. + */ + for (blk_x = 0; blk_x < compptr->width_in_blocks; blk_x++) { + jcopy_block_row(buffer[offset_y] + blk_x + x_crop_blocks, + buffer[offset_y] + blk_x, + (JDIMENSION) 1); + } + } + } + } + } +} + + +EXTERN(void) +do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Horizontal flip in general cropping case */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, k, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Here we must output into a separate array because we can't touch + * different rows of a single virtual array simultaneously. Otherwise, + * this is essentially the same as the routine above. + */ + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[offset_y]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Do the mirrorable blocks */ + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + *dst_ptr++ = *src_ptr++; /* copy even column */ + *dst_ptr++ = - *src_ptr++; /* copy odd column with sign change */ + } + } else { + /* Copy last partial block(s) verbatim */ + jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks, + dst_row_ptr + dst_blk_x, + (JDIMENSION) 1); + } + } + } + } + } +} + + +EXTERN(void) +do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Vertical flip */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* We output into a separate array because we can't touch different + * rows of the source virtual array simultaneously. Otherwise, this + * is a pretty straightforward analog of horizontal flip. + * Within a DCT block, vertical mirroring is done by changing the signs + * of odd-numbered rows. + * Partial iMCUs at the bottom edge are copied verbatim. + */ + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - y_crop_blocks - dst_blk_y - + (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge blocks will be copied verbatim. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + src_row_ptr += x_crop_blocks; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + /* copy even row */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + /* copy odd row with sign change */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Just copy row verbatim. */ + jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, + dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } + } +} + + +EXTERN(void) +do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transpose source into destination */ +{ + JDIMENSION dst_blk_x, dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Transposing pixels within a block just requires transposing the + * DCT coefficients. + * Partial iMCUs at the edges require no special treatment; we simply + * process all the available DCT blocks for every component. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } +} + + +EXTERN(void) +do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 90 degree rotation is equivalent to + * 1. Transposing the image; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) right edge properly. They just get transposed and + * not mirrored. + */ + MCU_cols = srcinfo->output_height / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_width - x_crop_blocks - dst_blk_x - + (JDIMENSION) compptr->h_samp_factor, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } else { + /* Edge blocks are transposed but not mirrored. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +EXTERN(void) +do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 270 degree rotation is equivalent to + * 1. Horizontal mirroring; + * 2. Transposing the image. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) bottom edge properly. They just get transposed and + * not mirrored. + */ + MCU_rows = srcinfo->output_width / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[offset_x] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +EXTERN(void) +do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 180 degree rotation is equivalent to + * 1. Vertical mirroring; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the vertically mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - y_crop_blocks - dst_blk_y - + (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge rows are only mirrored horizontally. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + dst_row_ptr = dst_buffer[offset_y]; + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Process the blocks that can be mirrored both ways. */ + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE; i += 2) { + /* For even row, negate every odd column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + /* For odd row, negate every even column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = *src_ptr++; + } + } + } else { + /* Any remaining right-edge blocks are only mirrored vertically. */ + src_ptr = src_row_ptr[x_crop_blocks + dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } + } else { + /* Remaining rows are just mirrored horizontally. */ + src_row_ptr = src_buffer[offset_y]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Process the blocks that can be mirrored. */ + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE2; i += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + } else { + /* Any remaining right-edge blocks are only copied. */ + jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks, + dst_row_ptr + dst_blk_x, + (JDIMENSION) 1); + } + } + } + } + } + } +} + + +EXTERN(void) +do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transverse transpose is equivalent to + * 1. 180 degree rotation; + * 2. Transposition; + * or + * 1. Horizontal mirroring; + * 2. Transposition; + * 3. Horizontal mirroring. + * These steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_height / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_width / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_width - x_crop_blocks - dst_blk_x - + (JDIMENSION) compptr->h_samp_factor, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } else { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (y_crop_blocks + dst_blk_y < comp_height) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + i++; + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Right-edge blocks are mirrored in y only */ + src_ptr = src_buffer[offset_x] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } + } else { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Bottom-edge blocks are mirrored in x only */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* At lower right corner, just transpose, no mirroring */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } + } +} + + +/* Parse an unsigned integer: subroutine for jtransform_parse_crop_spec. + * Returns TRUE if valid integer found, FALSE if not. + * *strptr is advanced over the digit string, and *result is set to its value. + */ + +EXTERN(boolean) +jt_read_integer (const char ** strptr, JDIMENSION * result) +{ + const char * ptr = *strptr; + JDIMENSION val = 0; + + for (; isdigit(*ptr); ptr++) { + val = val * 10 + (JDIMENSION) (*ptr - '0'); + } + *result = val; + if (ptr == *strptr) + return FALSE; /* oops, no digits */ + *strptr = ptr; + return TRUE; +} + + +/* Parse a crop specification (written in X11 geometry style). + * The routine returns TRUE if the spec string is valid, FALSE if not. + * + * The crop spec string should have the format + * x{+-}{+-} + * where width, height, xoffset, and yoffset are unsigned integers. + * Each of the elements can be omitted to indicate a default value. + * (A weakness of this style is that it is not possible to omit xoffset + * while specifying yoffset, since they look alike.) + * + * This code is loosely based on XParseGeometry from the X11 distribution. + */ + +EXTERN(boolean) +jtransform_parse_crop_spec (jpeg_transform_info *info, const char *spec) +{ + info->crop = FALSE; + info->crop_width_set = JCROP_UNSET; + info->crop_height_set = JCROP_UNSET; + info->crop_xoffset_set = JCROP_UNSET; + info->crop_yoffset_set = JCROP_UNSET; + + if (isdigit(*spec)) { + /* fetch width */ + if (! jt_read_integer(&spec, &info->crop_width)) + return FALSE; + info->crop_width_set = JCROP_POS; + } + if (*spec == 'x' || *spec == 'X') { + /* fetch height */ + spec++; + if (! jt_read_integer(&spec, &info->crop_height)) + return FALSE; + info->crop_height_set = JCROP_POS; + } + if (*spec == '+' || *spec == '-') { + /* fetch xoffset */ + info->crop_xoffset_set = (*spec == '-') ? JCROP_NEG : JCROP_POS; + spec++; + if (! jt_read_integer(&spec, &info->crop_xoffset)) + return FALSE; + } + if (*spec == '+' || *spec == '-') { + /* fetch yoffset */ + info->crop_yoffset_set = (*spec == '-') ? JCROP_NEG : JCROP_POS; + spec++; + if (! jt_read_integer(&spec, &info->crop_yoffset)) + return FALSE; + } + /* We had better have gotten to the end of the string. */ + if (*spec != '\0') + return FALSE; + info->crop = TRUE; + return TRUE; +} + + +/* Trim off any partial iMCUs on the indicated destination edge */ + +EXTERN(void) +trim_right_edge (jpeg_transform_info *info, JDIMENSION full_width) +{ + JDIMENSION MCU_cols; + + MCU_cols = info->output_width / info->iMCU_sample_width; + if (MCU_cols > 0 && info->x_crop_offset + MCU_cols == + full_width / info->iMCU_sample_width) + info->output_width = MCU_cols * info->iMCU_sample_width; +} + +EXTERN(void) +trim_bottom_edge (jpeg_transform_info *info, JDIMENSION full_height) +{ + JDIMENSION MCU_rows; + + MCU_rows = info->output_height / info->iMCU_sample_height; + if (MCU_rows > 0 && info->y_crop_offset + MCU_rows == + full_height / info->iMCU_sample_height) + info->output_height = MCU_rows * info->iMCU_sample_height; +} + + +/* Request any required workspace. + * + * This routine figures out the size that the output image will be + * (which implies that all the transform parameters must be set before + * it is called). + * + * We allocate the workspace virtual arrays from the source decompression + * object, so that all the arrays (both the original data and the workspace) + * will be taken into account while making memory management decisions. + * Hence, this routine must be called after jpeg_read_header (which reads + * the image dimensions) and before jpeg_read_coefficients (which realizes + * the source's virtual arrays). + * + * This function returns FALSE right away if -perfect is given + * and transformation is not perfect. Otherwise returns TRUE. + */ + +EXTERN(boolean) +jtransform_request_workspace (j_decompress_ptr srcinfo, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *coef_arrays; + boolean need_workspace, transpose_it; + jpeg_component_info *compptr; + JDIMENSION xoffset, yoffset; + JDIMENSION width_in_iMCUs, height_in_iMCUs; + JDIMENSION width_in_blocks, height_in_blocks; + int ci, h_samp_factor, v_samp_factor; + + /* Determine number of components in output image */ + if (info->force_grayscale && + srcinfo->jpeg_color_space == JCS_YCbCr && + srcinfo->num_components == 3) + /* We'll only process the first component */ + info->num_components = 1; + else + /* Process all the components */ + info->num_components = srcinfo->num_components; + + /* Compute output image dimensions and related values. */ + jpeg_core_output_dimensions(srcinfo); + + /* Return right away if -perfect is given and transformation is not perfect. + */ + if (info->perfect) { + if (info->num_components == 1) { + if (!jtransform_perfect_transform(srcinfo->output_width, + srcinfo->output_height, + srcinfo->min_DCT_h_scaled_size, + srcinfo->min_DCT_v_scaled_size, + info->transform)) + return FALSE; + } else { + if (!jtransform_perfect_transform(srcinfo->output_width, + srcinfo->output_height, + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size, + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size, + info->transform)) + return FALSE; + } + } + + /* If there is only one output component, force the iMCU size to be 1; + * else use the source iMCU size. (This allows us to do the right thing + * when reducing color to grayscale, and also provides a handy way of + * cleaning up "funny" grayscale images whose sampling factors are not 1x1.) + */ + switch (info->transform) { + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + info->output_width = srcinfo->output_height; + info->output_height = srcinfo->output_width; + if (info->num_components == 1) { + info->iMCU_sample_width = srcinfo->min_DCT_v_scaled_size; + info->iMCU_sample_height = srcinfo->min_DCT_h_scaled_size; + } else { + info->iMCU_sample_width = + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size; + info->iMCU_sample_height = + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size; + } + break; + default: + info->output_width = srcinfo->output_width; + info->output_height = srcinfo->output_height; + if (info->num_components == 1) { + info->iMCU_sample_width = srcinfo->min_DCT_h_scaled_size; + info->iMCU_sample_height = srcinfo->min_DCT_v_scaled_size; + } else { + info->iMCU_sample_width = + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size; + info->iMCU_sample_height = + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size; + } + break; + } + + /* If cropping has been requested, compute the crop area's position and + * dimensions, ensuring that its upper left corner falls at an iMCU boundary. + */ + if (info->crop) { + /* Insert default values for unset crop parameters */ + if (info->crop_xoffset_set == JCROP_UNSET) + info->crop_xoffset = 0; /* default to +0 */ + if (info->crop_yoffset_set == JCROP_UNSET) + info->crop_yoffset = 0; /* default to +0 */ + if (info->crop_xoffset >= info->output_width || + info->crop_yoffset >= info->output_height) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + if (info->crop_width_set == JCROP_UNSET) + info->crop_width = info->output_width - info->crop_xoffset; + if (info->crop_height_set == JCROP_UNSET) + info->crop_height = info->output_height - info->crop_yoffset; + /* Ensure parameters are valid */ + if (info->crop_width <= 0 || info->crop_width > info->output_width || + info->crop_height <= 0 || info->crop_height > info->output_height || + info->crop_xoffset > info->output_width - info->crop_width || + info->crop_yoffset > info->output_height - info->crop_height) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + /* Convert negative crop offsets into regular offsets */ + if (info->crop_xoffset_set == JCROP_NEG) + xoffset = info->output_width - info->crop_width - info->crop_xoffset; + else + xoffset = info->crop_xoffset; + if (info->crop_yoffset_set == JCROP_NEG) + yoffset = info->output_height - info->crop_height - info->crop_yoffset; + else + yoffset = info->crop_yoffset; + /* Now adjust so that upper left corner falls at an iMCU boundary */ + info->output_width = + info->crop_width + (xoffset % info->iMCU_sample_width); + info->output_height = + info->crop_height + (yoffset % info->iMCU_sample_height); + /* Save x/y offsets measured in iMCUs */ + info->x_crop_offset = xoffset / info->iMCU_sample_width; + info->y_crop_offset = yoffset / info->iMCU_sample_height; + } else { + info->x_crop_offset = 0; + info->y_crop_offset = 0; + } + + /* Figure out whether we need workspace arrays, + * and if so whether they are transposed relative to the source. + */ + need_workspace = FALSE; + transpose_it = FALSE; + switch (info->transform) { + case JXFORM_NONE: + if (info->x_crop_offset != 0 || info->y_crop_offset != 0) + need_workspace = TRUE; + /* No workspace needed if neither cropping nor transforming */ + break; + case JXFORM_FLIP_H: + if (info->trim) + trim_right_edge(info, srcinfo->output_width); + if (info->y_crop_offset != 0) + need_workspace = TRUE; + /* do_flip_h_no_crop doesn't need a workspace array */ + break; + case JXFORM_FLIP_V: + if (info->trim) + trim_bottom_edge(info, srcinfo->output_height); + /* Need workspace arrays having same dimensions as source image. */ + need_workspace = TRUE; + break; + case JXFORM_TRANSPOSE: + /* transpose does NOT have to trim anything */ + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_TRANSVERSE: + if (info->trim) { + trim_right_edge(info, srcinfo->output_height); + trim_bottom_edge(info, srcinfo->output_width); + } + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_ROT_90: + if (info->trim) + trim_right_edge(info, srcinfo->output_height); + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_ROT_180: + if (info->trim) { + trim_right_edge(info, srcinfo->output_width); + trim_bottom_edge(info, srcinfo->output_height); + } + /* Need workspace arrays having same dimensions as source image. */ + need_workspace = TRUE; + break; + case JXFORM_ROT_270: + if (info->trim) + trim_bottom_edge(info, srcinfo->output_width); + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + } + + /* Allocate workspace if needed. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + if (need_workspace) { + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + width_in_iMCUs = (JDIMENSION) + jdiv_round_up((long) info->output_width, + (long) info->iMCU_sample_width); + height_in_iMCUs = (JDIMENSION) + jdiv_round_up((long) info->output_height, + (long) info->iMCU_sample_height); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + if (info->num_components == 1) { + /* we're going to force samp factors to 1x1 in this case */ + h_samp_factor = v_samp_factor = 1; + } else if (transpose_it) { + h_samp_factor = compptr->v_samp_factor; + v_samp_factor = compptr->h_samp_factor; + } else { + h_samp_factor = compptr->h_samp_factor; + v_samp_factor = compptr->v_samp_factor; + } + width_in_blocks = width_in_iMCUs * h_samp_factor; + height_in_blocks = height_in_iMCUs * v_samp_factor; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + width_in_blocks, height_in_blocks, (JDIMENSION) v_samp_factor); + } + info->workspace_coef_arrays = coef_arrays; + } else + info->workspace_coef_arrays = NULL; + + return TRUE; +} + + +/* Transpose destination image parameters */ + +EXTERN(void) +transpose_critical_parameters (j_compress_ptr dstinfo) +{ + int tblno, i, j, ci, itemp; + jpeg_component_info *compptr; + JQUANT_TBL *qtblptr; + JDIMENSION jtemp; + UINT16 qtemp; + + /* Transpose image dimensions */ + jtemp = dstinfo->image_width; + dstinfo->image_width = dstinfo->image_height; + dstinfo->image_height = jtemp; + itemp = dstinfo->min_DCT_h_scaled_size; + dstinfo->min_DCT_h_scaled_size = dstinfo->min_DCT_v_scaled_size; + dstinfo->min_DCT_v_scaled_size = itemp; + + /* Transpose sampling factors */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + itemp = compptr->h_samp_factor; + compptr->h_samp_factor = compptr->v_samp_factor; + compptr->v_samp_factor = itemp; + } + + /* Transpose quantization tables */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + qtblptr = dstinfo->quant_tbl_ptrs[tblno]; + if (qtblptr != NULL) { + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < i; j++) { + qtemp = qtblptr->quantval[i*DCTSIZE+j]; + qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i]; + qtblptr->quantval[j*DCTSIZE+i] = qtemp; + } + } + } + } +} + + +/* Adjust Exif image parameters. + * + * We try to adjust the Tags ExifImageWidth and ExifImageHeight if possible. + */ + +EXTERN(void) +adjust_exif_parameters (JOCTET FAR * data, unsigned int length, + JDIMENSION new_width, JDIMENSION new_height) +{ + boolean is_motorola; /* Flag for byte order */ + unsigned int number_of_tags, tagnum; + unsigned int firstoffset, offset; + JDIMENSION new_value; + + if (length < 12) return; /* Length of an IFD entry */ + + /* Discover byte order */ + if (GETJOCTET(data[0]) == 0x49 && GETJOCTET(data[1]) == 0x49) + is_motorola = FALSE; + else if (GETJOCTET(data[0]) == 0x4D && GETJOCTET(data[1]) == 0x4D) + is_motorola = TRUE; + else + return; + + /* Check Tag Mark */ + if (is_motorola) { + if (GETJOCTET(data[2]) != 0) return; + if (GETJOCTET(data[3]) != 0x2A) return; + } else { + if (GETJOCTET(data[3]) != 0) return; + if (GETJOCTET(data[2]) != 0x2A) return; + } + + /* Get first IFD offset (offset to IFD0) */ + if (is_motorola) { + if (GETJOCTET(data[4]) != 0) return; + if (GETJOCTET(data[5]) != 0) return; + firstoffset = GETJOCTET(data[6]); + firstoffset <<= 8; + firstoffset += GETJOCTET(data[7]); + } else { + if (GETJOCTET(data[7]) != 0) return; + if (GETJOCTET(data[6]) != 0) return; + firstoffset = GETJOCTET(data[5]); + firstoffset <<= 8; + firstoffset += GETJOCTET(data[4]); + } + if (firstoffset > length - 2) return; /* check end of data segment */ + + /* Get the number of directory entries contained in this IFD */ + if (is_motorola) { + number_of_tags = GETJOCTET(data[firstoffset]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[firstoffset+1]); + } else { + number_of_tags = GETJOCTET(data[firstoffset+1]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[firstoffset]); + } + if (number_of_tags == 0) return; + firstoffset += 2; + + /* Search for ExifSubIFD offset Tag in IFD0 */ + for (;;) { + if (firstoffset > length - 12) return; /* check end of data segment */ + /* Get Tag number */ + if (is_motorola) { + tagnum = GETJOCTET(data[firstoffset]); + tagnum <<= 8; + tagnum += GETJOCTET(data[firstoffset+1]); + } else { + tagnum = GETJOCTET(data[firstoffset+1]); + tagnum <<= 8; + tagnum += GETJOCTET(data[firstoffset]); + } + if (tagnum == 0x8769) break; /* found ExifSubIFD offset Tag */ + if (--number_of_tags == 0) return; + firstoffset += 12; + } + + /* Get the ExifSubIFD offset */ + if (is_motorola) { + if (GETJOCTET(data[firstoffset+8]) != 0) return; + if (GETJOCTET(data[firstoffset+9]) != 0) return; + offset = GETJOCTET(data[firstoffset+10]); + offset <<= 8; + offset += GETJOCTET(data[firstoffset+11]); + } else { + if (GETJOCTET(data[firstoffset+11]) != 0) return; + if (GETJOCTET(data[firstoffset+10]) != 0) return; + offset = GETJOCTET(data[firstoffset+9]); + offset <<= 8; + offset += GETJOCTET(data[firstoffset+8]); + } + if (offset > length - 2) return; /* check end of data segment */ + + /* Get the number of directory entries contained in this SubIFD */ + if (is_motorola) { + number_of_tags = GETJOCTET(data[offset]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[offset+1]); + } else { + number_of_tags = GETJOCTET(data[offset+1]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[offset]); + } + if (number_of_tags < 2) return; + offset += 2; + + /* Search for ExifImageWidth and ExifImageHeight Tags in this SubIFD */ + do { + if (offset > length - 12) return; /* check end of data segment */ + /* Get Tag number */ + if (is_motorola) { + tagnum = GETJOCTET(data[offset]); + tagnum <<= 8; + tagnum += GETJOCTET(data[offset+1]); + } else { + tagnum = GETJOCTET(data[offset+1]); + tagnum <<= 8; + tagnum += GETJOCTET(data[offset]); + } + if (tagnum == 0xA002 || tagnum == 0xA003) { + if (tagnum == 0xA002) + new_value = new_width; /* ExifImageWidth Tag */ + else + new_value = new_height; /* ExifImageHeight Tag */ + if (is_motorola) { + data[offset+2] = 0; /* Format = unsigned long (4 octets) */ + data[offset+3] = 4; + data[offset+4] = 0; /* Number Of Components = 1 */ + data[offset+5] = 0; + data[offset+6] = 0; + data[offset+7] = 1; + data[offset+8] = 0; + data[offset+9] = 0; + data[offset+10] = (JOCTET)((new_value >> 8) & 0xFF); + data[offset+11] = (JOCTET)(new_value & 0xFF); + } else { + data[offset+2] = 4; /* Format = unsigned long (4 octets) */ + data[offset+3] = 0; + data[offset+4] = 1; /* Number Of Components = 1 */ + data[offset+5] = 0; + data[offset+6] = 0; + data[offset+7] = 0; + data[offset+8] = (JOCTET)(new_value & 0xFF); + data[offset+9] = (JOCTET)((new_value >> 8) & 0xFF); + data[offset+10] = 0; + data[offset+11] = 0; + } + } + offset += 12; + } while (--number_of_tags); +} + + +/* Adjust output image parameters as needed. + * + * This must be called after jpeg_copy_critical_parameters() + * and before jpeg_write_coefficients(). + * + * The return value is the set of virtual coefficient arrays to be written + * (either the ones allocated by jtransform_request_workspace, or the + * original source data arrays). The caller will need to pass this value + * to jpeg_write_coefficients(). + */ + +EXTERN(jvirt_barray_ptr *) +jtransform_adjust_parameters (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + /* If force-to-grayscale is requested, adjust destination parameters */ + if (info->force_grayscale) { + /* First, ensure we have YCbCr or grayscale data, and that the source's + * Y channel is full resolution. (No reasonable person would make Y + * be less than full resolution, so actually coping with that case + * isn't worth extra code space. But we check it to avoid crashing.) + */ + if (((dstinfo->jpeg_color_space == JCS_YCbCr && + dstinfo->num_components == 3) || + (dstinfo->jpeg_color_space == JCS_GRAYSCALE && + dstinfo->num_components == 1)) && + srcinfo->comp_info[0].h_samp_factor == srcinfo->max_h_samp_factor && + srcinfo->comp_info[0].v_samp_factor == srcinfo->max_v_samp_factor) { + /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed + * properly. Among other things, it sets the target h_samp_factor & + * v_samp_factor to 1, which typically won't match the source. + * We have to preserve the source's quantization table number, however. + */ + int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no; + jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE); + dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no; + } else { + /* Sorry, can't do it */ + ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL); + } + } else if (info->num_components == 1) { + /* For a single-component source, we force the destination sampling factors + * to 1x1, with or without force_grayscale. This is useful because some + * decoders choke on grayscale images with other sampling factors. + */ + dstinfo->comp_info[0].h_samp_factor = 1; + dstinfo->comp_info[0].v_samp_factor = 1; + } + + /* Correct the destination's image dimensions as necessary + * for rotate/flip, resize, and crop operations. + */ + dstinfo->jpeg_width = info->output_width; + dstinfo->jpeg_height = info->output_height; + + /* Transpose destination image parameters */ + switch (info->transform) { + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + transpose_critical_parameters(dstinfo); + break; + default: + break; + } + + /* Adjust Exif properties */ + if (srcinfo->marker_list != NULL && + srcinfo->marker_list->marker == JPEG_APP0+1 && + srcinfo->marker_list->data_length >= 6 && + GETJOCTET(srcinfo->marker_list->data[0]) == 0x45 && + GETJOCTET(srcinfo->marker_list->data[1]) == 0x78 && + GETJOCTET(srcinfo->marker_list->data[2]) == 0x69 && + GETJOCTET(srcinfo->marker_list->data[3]) == 0x66 && + GETJOCTET(srcinfo->marker_list->data[4]) == 0 && + GETJOCTET(srcinfo->marker_list->data[5]) == 0) { + /* Suppress output of JFIF marker */ + dstinfo->write_JFIF_header = FALSE; + /* Adjust Exif image parameters */ + if (dstinfo->jpeg_width != srcinfo->image_width || + dstinfo->jpeg_height != srcinfo->image_height) + /* Align data segment to start of TIFF structure for parsing */ + adjust_exif_parameters(srcinfo->marker_list->data + 6, + srcinfo->marker_list->data_length - 6, + dstinfo->jpeg_width, dstinfo->jpeg_height); + } + + /* Return the appropriate output data set */ + if (info->workspace_coef_arrays != NULL) + return info->workspace_coef_arrays; + return src_coef_arrays; +} + + +/* Execute the actual transformation, if any. + * + * This must be called *after* jpeg_write_coefficients, because it depends + * on jpeg_write_coefficients to have computed subsidiary values such as + * the per-component width and height fields in the destination object. + * + * Note that some transformations will modify the source data arrays! + */ + +EXTERN(void) +jtransform_execute_transform (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays; + + /* Note: conditions tested here should match those in switch statement + * in jtransform_request_workspace() + */ + switch (info->transform) { + case JXFORM_NONE: + if (info->x_crop_offset != 0 || info->y_crop_offset != 0) + do_crop(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_FLIP_H: + if (info->y_crop_offset != 0) + do_flip_h(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + else + do_flip_h_no_crop(srcinfo, dstinfo, info->x_crop_offset, + src_coef_arrays); + break; + case JXFORM_FLIP_V: + do_flip_v(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSPOSE: + do_transpose(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSVERSE: + do_transverse(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_90: + do_rot_90(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_180: + do_rot_180(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_270: + do_rot_270(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + } +} + +/* jtransform_perfect_transform + * + * Determine whether lossless transformation is perfectly + * possible for a specified image and transformation. + * + * Inputs: + * image_width, image_height: source image dimensions. + * MCU_width, MCU_height: pixel dimensions of MCU. + * transform: transformation identifier. + * Parameter sources from initialized jpeg_struct + * (after reading source header): + * image_width = cinfo.image_width + * image_height = cinfo.image_height + * MCU_width = cinfo.max_h_samp_factor * cinfo.block_size + * MCU_height = cinfo.max_v_samp_factor * cinfo.block_size + * Result: + * TRUE = perfect transformation possible + * FALSE = perfect transformation not possible + * (may use custom action then) + */ + +EXTERN(boolean) +jtransform_perfect_transform(JDIMENSION image_width, JDIMENSION image_height, + int MCU_width, int MCU_height, + JXFORM_CODE transform) +{ + boolean result = TRUE; /* initialize TRUE */ + + switch (transform) { + case JXFORM_FLIP_H: + case JXFORM_ROT_270: + if (image_width % (JDIMENSION) MCU_width) + result = FALSE; + break; + case JXFORM_FLIP_V: + case JXFORM_ROT_90: + if (image_height % (JDIMENSION) MCU_height) + result = FALSE; + break; + case JXFORM_TRANSVERSE: + case JXFORM_ROT_180: + if (image_width % (JDIMENSION) MCU_width) + result = FALSE; + if (image_height % (JDIMENSION) MCU_height) + result = FALSE; + break; + default: + break; + } + + return result; +} + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* Setup decompression object to save desired markers in memory. + * This must be called before jpeg_read_header() to have the desired effect. + */ + +EXTERN(void) +jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option) +{ +#ifdef SAVE_MARKERS_SUPPORTED + int m; + + /* Save comments except under NONE option */ + if (option != JCOPYOPT_NONE) { + jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF); + } + /* Save all types of APPn markers iff ALL option */ + if (option == JCOPYOPT_ALL) { + for (m = 0; m < 16; m++) + jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF); + } +#endif /* SAVE_MARKERS_SUPPORTED */ +} + +/* Copy markers saved in the given source object to the destination object. + * This should be called just after jpeg_start_compress() or + * jpeg_write_coefficients(). + * Note that those routines will have written the SOI, and also the + * JFIF APP0 or Adobe APP14 markers if selected. + */ + +EXTERN(void) +jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option) +{ + jpeg_saved_marker_ptr marker; + + /* In the current implementation, we don't actually need to examine the + * option flag here; we just copy everything that got saved. + * But to avoid confusion, we do not output JFIF and Adobe APP14 markers + * if the encoder library already wrote one. + */ + for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) { + if (dstinfo->write_JFIF_header && + marker->marker == JPEG_APP0 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x4A && + GETJOCTET(marker->data[1]) == 0x46 && + GETJOCTET(marker->data[2]) == 0x49 && + GETJOCTET(marker->data[3]) == 0x46 && + GETJOCTET(marker->data[4]) == 0) + continue; /* reject duplicate JFIF */ + if (dstinfo->write_Adobe_marker && + marker->marker == JPEG_APP0+14 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x41 && + GETJOCTET(marker->data[1]) == 0x64 && + GETJOCTET(marker->data[2]) == 0x6F && + GETJOCTET(marker->data[3]) == 0x62 && + GETJOCTET(marker->data[4]) == 0x65) + continue; /* reject duplicate Adobe */ +#ifdef NEED_FAR_POINTERS + /* We could use jpeg_write_marker if the data weren't FAR... */ + { + unsigned int i; + jpeg_write_m_header(dstinfo, marker->marker, marker->data_length); + for (i = 0; i < marker->data_length; i++) + jpeg_write_m_byte(dstinfo, marker->data[i]); + } +#else + jpeg_write_marker(dstinfo, marker->marker, + marker->data, marker->data_length); +#endif + } +} diff --git a/gwenview/lib/libjpeg-80/transupp.h b/gwenview/lib/libjpeg-80/transupp.h new file mode 100644 index 00000000..30acc432 --- /dev/null +++ b/gwenview/lib/libjpeg-80/transupp.h @@ -0,0 +1,206 @@ +/* + * transupp.h + * + * Copyright (C) 1997-2009, Thomas G. Lane, Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for image transformation routines and + * other utility code used by the jpegtran sample application. These are + * NOT part of the core JPEG library. But we keep these routines separate + * from jpegtran.c to ease the task of maintaining jpegtran-like programs + * that have other user interfaces. + * + * NOTE: all the routines declared here have very specific requirements + * about when they are to be executed during the reading and writing of the + * source and destination files. See the comments in transupp.c, or see + * jpegtran.c for an example of correct usage. + */ + +/* If you happen not to want the image transform support, disable it here */ +#ifndef TRANSFORMS_SUPPORTED +#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */ +#endif + +/* + * Although rotating and flipping data expressed as DCT coefficients is not + * hard, there is an asymmetry in the JPEG format specification for images + * whose dimensions aren't multiples of the iMCU size. The right and bottom + * image edges are padded out to the next iMCU boundary with junk data; but + * no padding is possible at the top and left edges. If we were to flip + * the whole image including the pad data, then pad garbage would become + * visible at the top and/or left, and real pixels would disappear into the + * pad margins --- perhaps permanently, since encoders & decoders may not + * bother to preserve DCT blocks that appear to be completely outside the + * nominal image area. So, we have to exclude any partial iMCUs from the + * basic transformation. + * + * Transpose is the only transformation that can handle partial iMCUs at the + * right and bottom edges completely cleanly. flip_h can flip partial iMCUs + * at the bottom, but leaves any partial iMCUs at the right edge untouched. + * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched. + * The other transforms are defined as combinations of these basic transforms + * and process edge blocks in a way that preserves the equivalence. + * + * The "trim" option causes untransformable partial iMCUs to be dropped; + * this is not strictly lossless, but it usually gives the best-looking + * result for odd-size images. Note that when this option is active, + * the expected mathematical equivalences between the transforms may not hold. + * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim + * followed by -rot 180 -trim trims both edges.) + * + * We also offer a lossless-crop option, which discards data outside a given + * image region but losslessly preserves what is inside. Like the rotate and + * flip transforms, lossless crop is restricted by the JPEG format: the upper + * left corner of the selected region must fall on an iMCU boundary. If this + * does not hold for the given crop parameters, we silently move the upper left + * corner up and/or left to make it so, simultaneously increasing the region + * dimensions to keep the lower right crop corner unchanged. (Thus, the + * output image covers at least the requested region, but may cover more.) + * + * We also provide a lossless-resize option, which is kind of a lossless-crop + * operation in the DCT coefficient block domain - it discards higher-order + * coefficients and losslessly preserves lower-order coefficients of a + * sub-block. + * + * Rotate/flip transform, resize, and crop can be requested together in a + * single invocation. The crop is applied last --- that is, the crop region + * is specified in terms of the destination image after transform/resize. + * + * We also offer a "force to grayscale" option, which simply discards the + * chrominance channels of a YCbCr image. This is lossless in the sense that + * the luminance channel is preserved exactly. It's not the same kind of + * thing as the rotate/flip transformations, but it's convenient to handle it + * as part of this package, mainly because the transformation routines have to + * be aware of the option to know how many components to work on. + */ + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jtransform_parse_crop_spec jTrParCrop +#define jtransform_request_workspace jTrRequest +#define jtransform_adjust_parameters jTrAdjust +#define jtransform_execute_transform jTrExec +#define jtransform_perfect_transform jTrPerfect +#define jcopy_markers_setup jCMrkSetup +#define jcopy_markers_execute jCMrkExec +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* + * Codes for supported types of image transformations. + */ + +typedef enum { + JXFORM_NONE, /* no transformation */ + JXFORM_FLIP_H, /* horizontal flip */ + JXFORM_FLIP_V, /* vertical flip */ + JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */ + JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */ + JXFORM_ROT_90, /* 90-degree clockwise rotation */ + JXFORM_ROT_180, /* 180-degree rotation */ + JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */ +} JXFORM_CODE; + +/* + * Codes for crop parameters, which can individually be unspecified, + * positive, or negative. (Negative width or height makes no sense, though.) + */ + +typedef enum { + JCROP_UNSET, + JCROP_POS, + JCROP_NEG +} JCROP_CODE; + +/* + * Transform parameters struct. + * NB: application must not change any elements of this struct after + * calling jtransform_request_workspace. + */ + +typedef struct { + /* Options: set by caller */ + JXFORM_CODE transform; /* image transform operator */ + boolean perfect; /* if TRUE, fail if partial MCUs are requested */ + boolean trim; /* if TRUE, trim partial MCUs as needed */ + boolean force_grayscale; /* if TRUE, convert color image to grayscale */ + boolean crop; /* if TRUE, crop source image */ + + /* Crop parameters: application need not set these unless crop is TRUE. + * These can be filled in by jtransform_parse_crop_spec(). + */ + JDIMENSION crop_width; /* Width of selected region */ + JCROP_CODE crop_width_set; + JDIMENSION crop_height; /* Height of selected region */ + JCROP_CODE crop_height_set; + JDIMENSION crop_xoffset; /* X offset of selected region */ + JCROP_CODE crop_xoffset_set; /* (negative measures from right edge) */ + JDIMENSION crop_yoffset; /* Y offset of selected region */ + JCROP_CODE crop_yoffset_set; /* (negative measures from bottom edge) */ + + /* Internal workspace: caller should not touch these */ + int num_components; /* # of components in workspace */ + jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */ + JDIMENSION output_width; /* cropped destination dimensions */ + JDIMENSION output_height; + JDIMENSION x_crop_offset; /* destination crop offsets measured in iMCUs */ + JDIMENSION y_crop_offset; + int iMCU_sample_width; /* destination iMCU size */ + int iMCU_sample_height; +} jpeg_transform_info; + +#if TRANSFORMS_SUPPORTED + +/* Parse a crop specification (written in X11 geometry style) */ +EXTERN(boolean) jtransform_parse_crop_spec +JPP((jpeg_transform_info *info, const char *spec)); +/* Request any required workspace */ +EXTERN(boolean) jtransform_request_workspace +JPP((j_decompress_ptr srcinfo, jpeg_transform_info *info)); +/* Adjust output image parameters */ +EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters +JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); +/* Execute the actual transformation, if any */ +EXTERN(void) jtransform_execute_transform +JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); +/* Determine whether lossless transformation is perfectly + * possible for a specified image and transformation. + */ +EXTERN(boolean) jtransform_perfect_transform +JPP((JDIMENSION image_width, JDIMENSION image_height, + int MCU_width, int MCU_height, + JXFORM_CODE transform)); + +/* jtransform_execute_transform used to be called + * jtransform_execute_transformation, but some compilers complain about + * routine names that long. This macro is here to avoid breaking any + * old source code that uses the original name... + */ +#define jtransform_execute_transformation jtransform_execute_transform + +#endif /* TRANSFORMS_SUPPORTED */ + +/* + * Support for copying optional markers from source to destination file. + */ + +typedef enum { + JCOPYOPT_NONE, /* copy no optional markers */ + JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */ + JCOPYOPT_ALL /* copy all optional markers */ +} JCOPY_OPTION; + +#define JCOPYOPT_DEFAULT JCOPYOPT_COMMENTS /* recommended default */ + +/* Setup decompression object to save desired markers in memory */ +EXTERN(void) jcopy_markers_setup +JPP((j_decompress_ptr srcinfo, JCOPY_OPTION option)); +/* Copy markers saved in the given source object to the destination object */ +EXTERN(void) jcopy_markers_execute +JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option)); diff --git a/gwenview/lib/libjpeg-90/README.jpeg b/gwenview/lib/libjpeg-90/README.jpeg new file mode 100644 index 00000000..014ad301 --- /dev/null +++ b/gwenview/lib/libjpeg-90/README.jpeg @@ -0,0 +1,381 @@ +The Independent JPEG Group's JPEG software +========================================== + +README for release 9a of 19-Jan-2014 +==================================== + +This distribution contains the ninth public release of the Independent JPEG +Group's free JPEG software. You are welcome to redistribute this software and +to use it for any purpose, subject to the conditions under LEGAL ISSUES, below. + +This software is the work of Tom Lane, Guido Vollbeding, Philip Gladstone, +Bill Allombert, Jim Boucher, Lee Crocker, Bob Friesenhahn, Ben Jackson, +Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi, Ge' Weijers, +and other members of the Independent JPEG Group. + +IJG is not affiliated with the ISO/IEC JTC1/SC29/WG1 standards committee +(previously known as JPEG, together with ITU-T SG16). + + +DOCUMENTATION ROADMAP +===================== + +This file contains the following sections: + +OVERVIEW General description of JPEG and the IJG software. +LEGAL ISSUES Copyright, lack of warranty, terms of distribution. +REFERENCES Where to learn more about JPEG. +ARCHIVE LOCATIONS Where to find newer versions of this software. +ACKNOWLEDGMENTS Special thanks. +FILE FORMAT WARS Software *not* to get. +TO DO Plans for future IJG releases. + +Other documentation files in the distribution are: + +User documentation: + install.txt How to configure and install the IJG software. + usage.txt Usage instructions for cjpeg, djpeg, jpegtran, + rdjpgcom, and wrjpgcom. + *.1 Unix-style man pages for programs (same info as usage.txt). + wizard.txt Advanced usage instructions for JPEG wizards only. + change.log Version-to-version change highlights. +Programmer and internal documentation: + libjpeg.txt How to use the JPEG library in your own programs. + example.c Sample code for calling the JPEG library. + structure.txt Overview of the JPEG library's internal structure. + filelist.txt Road map of IJG files. + coderules.txt Coding style rules --- please read if you contribute code. + +Please read at least the files install.txt and usage.txt. Some information +can also be found in the JPEG FAQ (Frequently Asked Questions) article. See +ARCHIVE LOCATIONS below to find out where to obtain the FAQ article. + +If you want to understand how the JPEG code works, we suggest reading one or +more of the REFERENCES, then looking at the documentation files (in roughly +the order listed) before diving into the code. + + +OVERVIEW +======== + +This package contains C software to implement JPEG image encoding, decoding, +and transcoding. JPEG (pronounced "jay-peg") is a standardized compression +method for full-color and gray-scale images. + +This software implements JPEG baseline, extended-sequential, and progressive +compression processes. Provision is made for supporting all variants of these +processes, although some uncommon parameter settings aren't implemented yet. +We have made no provision for supporting the hierarchical or lossless +processes defined in the standard. + +We provide a set of library routines for reading and writing JPEG image files, +plus two sample applications "cjpeg" and "djpeg", which use the library to +perform conversion between JPEG and some other popular image file formats. +The library is intended to be reused in other applications. + +In order to support file conversion and viewing software, we have included +considerable functionality beyond the bare JPEG coding/decoding capability; +for example, the color quantization modules are not strictly part of JPEG +decoding, but they are essential for output to colormapped file formats or +colormapped displays. These extra functions can be compiled out of the +library if not required for a particular application. + +We have also included "jpegtran", a utility for lossless transcoding between +different JPEG processes, and "rdjpgcom" and "wrjpgcom", two simple +applications for inserting and extracting textual comments in JFIF files. + +The emphasis in designing this software has been on achieving portability and +flexibility, while also making it fast enough to be useful. In particular, +the software is not intended to be read as a tutorial on JPEG. (See the +REFERENCES section for introductory material.) Rather, it is intended to +be reliable, portable, industrial-strength code. We do not claim to have +achieved that goal in every aspect of the software, but we strive for it. + +We welcome the use of this software as a component of commercial products. +No royalty is required, but we do ask for an acknowledgement in product +documentation, as described under LEGAL ISSUES. + + +LEGAL ISSUES +============ + +In plain English: + +1. We don't promise that this software works. (But if you find any bugs, + please let us know!) +2. You can use this software for whatever you want. You don't have to pay us. +3. You may not pretend that you wrote this software. If you use it in a + program, you must acknowledge somewhere in your documentation that + you've used the IJG code. + +In legalese: + +The authors make NO WARRANTY or representation, either express or implied, +with respect to this software, its quality, accuracy, merchantability, or +fitness for a particular purpose. This software is provided "AS IS", and you, +its user, assume the entire risk as to its quality and accuracy. + +This software is copyright (C) 1991-2014, Thomas G. Lane, Guido Vollbeding. +All Rights Reserved except as specified below. + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +Permission is NOT granted for the use of any IJG author's name or company name +in advertising or publicity relating to this software or products derived from +it. This software may be referred to only as "the Independent JPEG Group's +software". + +We specifically permit and encourage the use of this software as the basis of +commercial products, provided that all warranty or liability claims are +assumed by the product vendor. + + +The Unix configuration script "configure" was produced with GNU Autoconf. +It is copyright by the Free Software Foundation but is freely distributable. +The same holds for its supporting scripts (config.guess, config.sub, +ltmain.sh). Another support script, install-sh, is copyright by X Consortium +but is also freely distributable. + +The IJG distribution formerly included code to read and write GIF files. +To avoid entanglement with the Unisys LZW patent (now expired), GIF reading +support has been removed altogether, and the GIF writer has been simplified +to produce "uncompressed GIFs". This technique does not use the LZW +algorithm; the resulting GIF files are larger than usual, but are readable +by all standard GIF decoders. + +We are required to state that + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + + +REFERENCES +========== + +We recommend reading one or more of these references before trying to +understand the innards of the JPEG software. + +The best short technical introduction to the JPEG compression algorithm is + Wallace, Gregory K. "The JPEG Still Picture Compression Standard", + Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44. +(Adjacent articles in that issue discuss MPEG motion picture compression, +applications of JPEG, and related topics.) If you don't have the CACM issue +handy, a PostScript file containing a revised version of Wallace's article is +available at http://www.ijg.org/files/wallace.ps.gz. The file (actually +a preprint for an article that appeared in IEEE Trans. Consumer Electronics) +omits the sample images that appeared in CACM, but it includes corrections +and some added material. Note: the Wallace article is copyright ACM and IEEE, +and it may not be used for commercial purposes. + +A somewhat less technical, more leisurely introduction to JPEG can be found in +"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by +M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides +good explanations and example C code for a multitude of compression methods +including JPEG. It is an excellent source if you are comfortable reading C +code but don't know much about data compression in general. The book's JPEG +sample code is far from industrial-strength, but when you are ready to look +at a full implementation, you've got one here... + +The best currently available description of JPEG is the textbook "JPEG Still +Image Data Compression Standard" by William B. Pennebaker and Joan L. +Mitchell, published by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1. +Price US$59.95, 638 pp. The book includes the complete text of the ISO JPEG +standards (DIS 10918-1 and draft DIS 10918-2). +Although this is by far the most detailed and comprehensive exposition of +JPEG publicly available, we point out that it is still missing an explanation +of the most essential properties and algorithms of the underlying DCT +technology. +If you think that you know about DCT-based JPEG after reading this book, +then you are in delusion. The real fundamentals and corresponding potential +of DCT-based JPEG are not publicly known so far, and that is the reason for +all the mistaken developments taking place in the image coding domain. + +The original JPEG standard is divided into two parts, Part 1 being the actual +specification, while Part 2 covers compliance testing methods. Part 1 is +titled "Digital Compression and Coding of Continuous-tone Still Images, +Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS +10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of +Continuous-tone Still Images, Part 2: Compliance testing" and has document +numbers ISO/IEC IS 10918-2, ITU-T T.83. +IJG JPEG 8 introduced an implementation of the JPEG SmartScale extension +which is specified in two documents: A contributed document at ITU and ISO +with title "ITU-T JPEG-Plus Proposal for Extending ITU-T T.81 for Advanced +Image Coding", April 2006, Geneva, Switzerland. The latest version of this +document is Revision 3. And a contributed document ISO/IEC JTC1/SC29/WG1 N +5799 with title "Evolution of JPEG", June/July 2011, Berlin, Germany. +IJG JPEG 9 introduces a reversible color transform for improved lossless +compression which is described in a contributed document ISO/IEC JTC1/SC29/ +WG1 N 6080 with title "JPEG 9 Lossless Coding", June/July 2012, Paris, +France. + +The JPEG standard does not specify all details of an interchangeable file +format. For the omitted details we follow the "JFIF" conventions, revision +1.02. JFIF 1.02 has been adopted as an Ecma International Technical Report +and thus received a formal publication status. It is available as a free +download in PDF format from +http://www.ecma-international.org/publications/techreports/E-TR-098.htm. +A PostScript version of the JFIF document is available at +http://www.ijg.org/files/jfif.ps.gz. There is also a plain text version at +http://www.ijg.org/files/jfif.txt.gz, but it is missing the figures. + +The TIFF 6.0 file format specification can be obtained by FTP from +ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation scheme +found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems. +IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6). +Instead, we recommend the JPEG design proposed by TIFF Technical Note #2 +(Compression tag 7). Copies of this Note can be obtained from +http://www.ijg.org/files/. It is expected that the next revision +of the TIFF spec will replace the 6.0 JPEG design with the Note's design. +Although IJG's own code does not support TIFF/JPEG, the free libtiff library +uses our library to implement TIFF/JPEG per the Note. + + +ARCHIVE LOCATIONS +================= + +The "official" archive site for this software is www.ijg.org. +The most recent released version can always be found there in +directory "files". This particular version will be archived as +http://www.ijg.org/files/jpegsrc.v9a.tar.gz, and in Windows-compatible +"zip" archive format as http://www.ijg.org/files/jpegsr9a.zip. + +The JPEG FAQ (Frequently Asked Questions) article is a source of some +general information about JPEG. +It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/ +and other news.answers archive sites, including the official news.answers +archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/. +If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu +with body + send usenet/news.answers/jpeg-faq/part1 + send usenet/news.answers/jpeg-faq/part2 + + +ACKNOWLEDGMENTS +=============== + +Thank to Juergen Bruder for providing me with a copy of the common DCT +algorithm article, only to find out that I had come to the same result +in a more direct and comprehensible way with a more generative approach. + +Thank to Istvan Sebestyen and Joan L. Mitchell for inviting me to the +ITU JPEG (Study Group 16) meeting in Geneva, Switzerland. + +Thank to Thomas Wiegand and Gary Sullivan for inviting me to the +Joint Video Team (MPEG & ITU) meeting in Geneva, Switzerland. + +Thank to Thomas Richter and Daniel Lee for inviting me to the +ISO/IEC JTC1/SC29/WG1 (previously known as JPEG, together with ITU-T SG16) +meeting in Berlin, Germany. + +Thank to John Korejwa and Massimo Ballerini for inviting me to +fruitful consultations in Boston, MA and Milan, Italy. + +Thank to Hendrik Elstner, Roland Fassauer, Simone Zuck, Guenther +Maier-Gerber, Walter Stoeber, Fred Schmitz, and Norbert Braunagel +for corresponding business development. + +Thank to Nico Zschach and Dirk Stelling of the technical support team +at the Digital Images company in Halle for providing me with extra +equipment for configuration tests. + +Thank to Richard F. Lyon (then of Foveon Inc.) for fruitful +communication about JPEG configuration in Sigma Photo Pro software. + +Thank to Andrew Finkenstadt for hosting the ijg.org site. + +Last but not least special thank to Thomas G. Lane for the original +design and development of this singular software package. + + +FILE FORMAT WARS +================ + +The ISO/IEC JTC1/SC29/WG1 standards committee (previously known as JPEG, +together with ITU-T SG16) currently promotes different formats containing +the name "JPEG" which is misleading because these formats are incompatible +with original DCT-based JPEG and are based on faulty technologies. +IJG therefore does not and will not support such momentary mistakes +(see REFERENCES). +There exist also distributions under the name "OpenJPEG" promoting such +kind of formats which is misleading because they don't support original +JPEG images. +We have no sympathy for the promotion of inferior formats. Indeed, one of +the original reasons for developing this free software was to help force +convergence on common, interoperable format standards for JPEG files. +Don't use an incompatible file format! +(In any case, our decoder will remain capable of reading existing JPEG +image files indefinitely.) + +The ISO committee pretends to be "responsible for the popular JPEG" in their +public reports which is not true because they don't respond to actual +requirements for the maintenance of the original JPEG specification. +Furthermore, the ISO committee pretends to "ensure interoperability" with +their standards which is not true because their "standards" support only +application-specific and proprietary use cases and contain mathematically +incorrect code. + +There are currently different distributions in circulation containing the +name "libjpeg" which is misleading because they don't have the features and +are incompatible with formats supported by actual IJG libjpeg distributions. +One of those fakes is released by members of the ISO committee and just uses +the name of libjpeg for misdirection of people, similar to the abuse of the +name JPEG as described above, while having nothing in common with actual IJG +libjpeg distributions and containing mathematically incorrect code. +The other one claims to be a "derivative" or "fork" of the original libjpeg, +but violates the license conditions as described under LEGAL ISSUES above +and violates basic C programming properties. +We have no sympathy for the release of misleading, incorrect and illegal +distributions derived from obsolete code bases. +Don't use an obsolete code base! + +According to the UCC (Uniform Commercial Code) law, IJG has the lawful and +legal right to foreclose on certain standardization bodies and other +institutions or corporations that knowingly perform substantial and +systematic deceptive acts and practices, fraud, theft, and damaging of the +value of the people of this planet without their knowing, willing and +intentional consent. +The titles, ownership, and rights of these institutions and all their assets +are now duly secured and held in trust for the free people of this planet. +People of the planet, on every country, may have a financial interest in +the assets of these former principals, agents, and beneficiaries of the +foreclosed institutions and corporations. +IJG asserts what is: that each man, woman, and child has unalienable value +and rights granted and deposited in them by the Creator and not any one of +the people is subordinate to any artificial principality, corporate fiction +or the special interest of another without their appropriate knowing, +willing and intentional consent made by contract or accommodation agreement. +IJG expresses that which already was. +The people have already determined and demanded that public administration +entities, national governments, and their supporting judicial systems must +be fully transparent, accountable, and liable. +IJG has secured the value for all concerned free people of the planet. + +A partial list of foreclosed institutions and corporations ("Hall of Shame") +is currently prepared and will be published later. + + +TO DO +===== + +Version 9 is the second release of a new generation JPEG standard +to overcome the limitations of the original JPEG specification, +and is the first true source reference JPEG codec. +More features are being prepared for coming releases... + +Please send bug reports, offers of help, etc. to jpeg-info@jpegclub.org. diff --git a/gwenview/lib/libjpeg-90/jinclude.h b/gwenview/lib/libjpeg-90/jinclude.h new file mode 100644 index 00000000..0a4f1514 --- /dev/null +++ b/gwenview/lib/libjpeg-90/jinclude.h @@ -0,0 +1,91 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +/* Include auto-config file to find out which system include files we need. */ + +#include "jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/gwenview/lib/libjpeg-90/jpegint.h b/gwenview/lib/libjpeg-90/jpegint.h new file mode 100644 index 00000000..18bb8879 --- /dev/null +++ b/gwenview/lib/libjpeg-90/jpegint.h @@ -0,0 +1,426 @@ +/* + * jpegint.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 1997-2013 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +typedef JMETHOD(void, forward_DCT_ptr, + (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); + +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* It is useful to allow each component to have a separate FDCT method. */ + forward_DCT_ptr forward_DCT[MAX_COMPONENTS]; +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); + /* These routines are exported to allow insertion of extra markers */ + /* Probably only COM and APPn markers should be written this way */ + JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker, + unsigned int datalen)); + JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val)); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_arith_encoder jIAEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_arith_decoder jIADecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jzero_far jZeroFar +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#define jpeg_natural_order7 jZAG7Table +#define jpeg_natural_order6 jZAG6Table +#define jpeg_natural_order5 jZAG5Table +#define jpeg_natural_order4 jZAG4Table +#define jpeg_natural_order3 jZAG3Table +#define jpeg_natural_order2 jZAG2Table +#define jpeg_aritab jAriTab +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* On normal machines we can apply MEMCOPY() and MEMZERO() to sample arrays + * and coefficient-block arrays. This won't work on 80x86 because the arrays + * are FAR and we're assuming a small-pointer memory model. However, some + * DOS compilers provide far-pointer versions of memcpy() and memset() even + * in the small-model libraries. These will be used if USE_FMEM is defined. + * Otherwise, the routines in jutils.c do it the hard way. + */ + +#ifndef NEED_FAR_POINTERS /* normal case, same as regular macro */ +#define FMEMZERO(target,size) MEMZERO(target,size) +#else /* 80x86 case */ +#ifdef USE_FMEM +#define FMEMZERO(target,size) _fmemset((void FAR *)(target), 0, (size_t)(size)) +#else +EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero)); +#define FMEMZERO(target,size) jzero_far(target, size) +#endif +#endif + + +/* Compression module initialization routines */ +EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_arith_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_arith_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN(long) jdiv_round_up JPP((long a, long b)); +EXTERN(long) jround_up JPP((long a, long b)); +EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +/* Constant tables in jutils.c */ +#if 0 /* This table is not actually needed in v6a */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +#endif +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ +extern const int jpeg_natural_order7[]; /* zz to natural order for 7x7 block */ +extern const int jpeg_natural_order6[]; /* zz to natural order for 6x6 block */ +extern const int jpeg_natural_order5[]; /* zz to natural order for 5x5 block */ +extern const int jpeg_natural_order4[]; /* zz to natural order for 4x4 block */ +extern const int jpeg_natural_order3[]; /* zz to natural order for 3x3 block */ +extern const int jpeg_natural_order2[]; /* zz to natural order for 2x2 block */ + +/* Arithmetic coding probability estimation tables in jaricom.c */ +extern const INT32 jpeg_aritab[]; + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/gwenview/lib/libjpeg-90/transupp.c b/gwenview/lib/libjpeg-90/transupp.c new file mode 100644 index 00000000..525932a3 --- /dev/null +++ b/gwenview/lib/libjpeg-90/transupp.c @@ -0,0 +1,1763 @@ +/* + * transupp.c + * + * Copyright (C) 1997-2013, Thomas G. Lane, Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains image transformation routines and other utility code + * used by the jpegtran sample application. These are NOT part of the core + * JPEG library. But we keep these routines separate from jpegtran.c to + * ease the task of maintaining jpegtran-like programs that have other user + * interfaces. + */ + +/* Although this file really shouldn't have access to the library internals, + * it's helpful to let it call jround_up() and jcopy_block_row(). + */ +#define JPEG_INTERNALS + +#include "jinclude.h" +#include "jpeglib.h" +#include "transupp.h" /* My own external interface */ +#include /* to declare isdigit() */ + + +#if TRANSFORMS_SUPPORTED + +/* + * Lossless image transformation routines. These routines work on DCT + * coefficient arrays and thus do not require any lossy decompression + * or recompression of the image. + * Thanks to Guido Vollbeding for the initial design and code of this feature, + * and to Ben Jackson for introducing the cropping feature. + * + * Horizontal flipping is done in-place, using a single top-to-bottom + * pass through the virtual source array. It will thus be much the + * fastest option for images larger than main memory. + * + * The other routines require a set of destination virtual arrays, so they + * need twice as much memory as jpegtran normally does. The destination + * arrays are always written in normal scan order (top to bottom) because + * the virtual array manager expects this. The source arrays will be scanned + * in the corresponding order, which means multiple passes through the source + * arrays for most of the transforms. That could result in much thrashing + * if the image is larger than main memory. + * + * If cropping or trimming is involved, the destination arrays may be smaller + * than the source arrays. Note it is not possible to do horizontal flip + * in-place when a nonzero Y crop offset is specified, since we'd have to move + * data from one block row to another but the virtual array manager doesn't + * guarantee we can touch more than one row at a time. So in that case, + * we have to use a separate destination array. + * + * Some notes about the operating environment of the individual transform + * routines: + * 1. Both the source and destination virtual arrays are allocated from the + * source JPEG object, and therefore should be manipulated by calling the + * source's memory manager. + * 2. The destination's component count should be used. It may be smaller + * than the source's when forcing to grayscale. + * 3. Likewise the destination's sampling factors should be used. When + * forcing to grayscale the destination's sampling factors will be all 1, + * and we may as well take that as the effective iMCU size. + * 4. When "trim" is in effect, the destination's dimensions will be the + * trimmed values but the source's will be untrimmed. + * 5. When "crop" is in effect, the destination's dimensions will be the + * cropped values but the source's will be uncropped. Each transform + * routine is responsible for picking up source data starting at the + * correct X and Y offset for the crop region. (The X and Y offsets + * passed to the transform routines are measured in iMCU blocks of the + * destination.) + * 6. All the routines assume that the source and destination buffers are + * padded out to a full iMCU boundary. This is true, although for the + * source buffer it is an undocumented property of jdcoefct.c. + */ + + +LOCAL(void) +do_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Crop. This is only used when no rotate/flip is requested with the crop. */ +{ + JDIMENSION dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + jpeg_component_info *compptr; + + /* We simply have to copy the right amount of data (the destination's + * image size) starting at the given X and Y offsets in the source. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, + dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } +} + + +LOCAL(void) +do_crop_ext (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Crop. This is only used when no rotate/flip is requested with the crop. + * Extension: If the destination size is larger than the source, we fill in + * the extra area with zero (neutral gray). Note we also have to zero partial + * iMCUs at the right and bottom edge of the source image area in this case. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height; + JDIMENSION dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (dstinfo->jpeg_height > srcinfo->output_height) { + if (dst_blk_y < y_crop_blocks || + dst_blk_y >= comp_height + y_crop_blocks) { + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + FMEMZERO(dst_buffer[offset_y], + compptr->width_in_blocks * SIZEOF(JBLOCK)); + } + continue; + } + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y - y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (dstinfo->jpeg_width > srcinfo->output_width) { + if (x_crop_blocks > 0) { + FMEMZERO(dst_buffer[offset_y], + x_crop_blocks * SIZEOF(JBLOCK)); + } + jcopy_block_row(src_buffer[offset_y], + dst_buffer[offset_y] + x_crop_blocks, + comp_width); + if (compptr->width_in_blocks > comp_width + x_crop_blocks) { + FMEMZERO(dst_buffer[offset_y] + + comp_width + x_crop_blocks, + (compptr->width_in_blocks - + comp_width - x_crop_blocks) * SIZEOF(JBLOCK)); + } + } else { + jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, + dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } + } +} + + +LOCAL(void) +do_wipe (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + JDIMENSION drop_width, JDIMENSION drop_height) +/* Wipe - drop content of specified area, fill with zero (neutral gray) */ +{ + JDIMENSION comp_width, comp_height; + JDIMENSION blk_y, x_wipe_blocks, y_wipe_blocks; + int ci, offset_y; + JBLOCKARRAY buffer; + jpeg_component_info *compptr; + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = drop_width * compptr->h_samp_factor; + comp_height = drop_height * compptr->v_samp_factor; + x_wipe_blocks = x_crop_offset * compptr->h_samp_factor; + y_wipe_blocks = y_crop_offset * compptr->v_samp_factor; + for (blk_y = 0; blk_y < comp_height; blk_y += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y + y_wipe_blocks, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + FMEMZERO(buffer[offset_y] + x_wipe_blocks, + comp_width * SIZEOF(JBLOCK)); + } + } + } +} + + +LOCAL(void) +do_flip_h_no_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, + jvirt_barray_ptr *src_coef_arrays) +/* Horizontal flip; done in-place, so no separate dest array is required. + * NB: this only works when y_crop_offset is zero. + */ +{ + JDIMENSION MCU_cols, comp_width, blk_x, blk_y, x_crop_blocks; + int ci, k, offset_y; + JBLOCKARRAY buffer; + JCOEFPTR ptr1, ptr2; + JCOEF temp1, temp2; + jpeg_component_info *compptr; + + /* Horizontal mirroring of DCT blocks is accomplished by swapping + * pairs of blocks in-place. Within a DCT block, we perform horizontal + * mirroring by changing the signs of odd-numbered columns. + * Partial iMCUs at the right edge are left untouched. + */ + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + for (blk_y = 0; blk_y < compptr->height_in_blocks; + blk_y += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + /* Do the mirroring */ + for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) { + ptr1 = buffer[offset_y][blk_x]; + ptr2 = buffer[offset_y][comp_width - blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + temp1 = *ptr1; /* swap even column */ + temp2 = *ptr2; + *ptr1++ = temp2; + *ptr2++ = temp1; + temp1 = *ptr1; /* swap odd column with sign change */ + temp2 = *ptr2; + *ptr1++ = -temp2; + *ptr2++ = -temp1; + } + } + if (x_crop_blocks > 0) { + /* Now left-justify the portion of the data to be kept. + * We can't use a single jcopy_block_row() call because that routine + * depends on memcpy(), whose behavior is unspecified for overlapping + * source and destination areas. Sigh. + */ + for (blk_x = 0; blk_x < compptr->width_in_blocks; blk_x++) { + jcopy_block_row(buffer[offset_y] + blk_x + x_crop_blocks, + buffer[offset_y] + blk_x, + (JDIMENSION) 1); + } + } + } + } + } +} + + +LOCAL(void) +do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Horizontal flip in general cropping case */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, k, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Here we must output into a separate array because we can't touch + * different rows of a single virtual array simultaneously. Otherwise, + * this is essentially the same as the routine above. + */ + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[offset_y]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Do the mirrorable blocks */ + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + *dst_ptr++ = *src_ptr++; /* copy even column */ + *dst_ptr++ = - *src_ptr++; /* copy odd column with sign change */ + } + } else { + /* Copy last partial block(s) verbatim */ + jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks, + dst_row_ptr + dst_blk_x, + (JDIMENSION) 1); + } + } + } + } + } +} + + +LOCAL(void) +do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Vertical flip */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* We output into a separate array because we can't touch different + * rows of the source virtual array simultaneously. Otherwise, this + * is a pretty straightforward analog of horizontal flip. + * Within a DCT block, vertical mirroring is done by changing the signs + * of odd-numbered rows. + * Partial iMCUs at the bottom edge are copied verbatim. + */ + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - y_crop_blocks - dst_blk_y - + (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge blocks will be copied verbatim. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + src_row_ptr += x_crop_blocks; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + /* copy even row */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + /* copy odd row with sign change */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Just copy row verbatim. */ + jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, + dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } + } +} + + +LOCAL(void) +do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transpose source into destination */ +{ + JDIMENSION dst_blk_x, dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Transposing pixels within a block just requires transposing the + * DCT coefficients. + * Partial iMCUs at the edges require no special treatment; we simply + * process all the available DCT blocks for every component. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } +} + + +LOCAL(void) +do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 90 degree rotation is equivalent to + * 1. Transposing the image; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) right edge properly. They just get transposed and + * not mirrored. + */ + MCU_cols = srcinfo->output_height / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_width - x_crop_blocks - dst_blk_x - + (JDIMENSION) compptr->h_samp_factor, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } else { + /* Edge blocks are transposed but not mirrored. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 270 degree rotation is equivalent to + * 1. Horizontal mirroring; + * 2. Transposing the image. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) bottom edge properly. They just get transposed and + * not mirrored. + */ + MCU_rows = srcinfo->output_width / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[offset_x] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 180 degree rotation is equivalent to + * 1. Vertical mirroring; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the vertically mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - y_crop_blocks - dst_blk_y - + (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge rows are only mirrored horizontally. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + dst_row_ptr = dst_buffer[offset_y]; + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Process the blocks that can be mirrored both ways. */ + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE; i += 2) { + /* For even row, negate every odd column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + /* For odd row, negate every even column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = *src_ptr++; + } + } + } else { + /* Any remaining right-edge blocks are only mirrored vertically. */ + src_ptr = src_row_ptr[x_crop_blocks + dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } + } else { + /* Remaining rows are just mirrored horizontally. */ + src_row_ptr = src_buffer[offset_y]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Process the blocks that can be mirrored. */ + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE2; i += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + } else { + /* Any remaining right-edge blocks are only copied. */ + jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks, + dst_row_ptr + dst_blk_x, + (JDIMENSION) 1); + } + } + } + } + } + } +} + + +LOCAL(void) +do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transverse transpose is equivalent to + * 1. 180 degree rotation; + * 2. Transposition; + * or + * 1. Horizontal mirroring; + * 2. Transposition; + * 3. Horizontal mirroring. + * These steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_height / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_width / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_width - x_crop_blocks - dst_blk_x - + (JDIMENSION) compptr->h_samp_factor, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } else { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (y_crop_blocks + dst_blk_y < comp_height) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + i++; + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Right-edge blocks are mirrored in y only */ + src_ptr = src_buffer[offset_x] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } + } else { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Bottom-edge blocks are mirrored in x only */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* At lower right corner, just transpose, no mirroring */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } + } +} + + +/* Parse an unsigned integer: subroutine for jtransform_parse_crop_spec. + * Returns TRUE if valid integer found, FALSE if not. + * *strptr is advanced over the digit string, and *result is set to its value. + */ + +LOCAL(boolean) +jt_read_integer (const char ** strptr, JDIMENSION * result) +{ + const char * ptr = *strptr; + JDIMENSION val = 0; + + for (; isdigit(*ptr); ptr++) { + val = val * 10 + (JDIMENSION) (*ptr - '0'); + } + *result = val; + if (ptr == *strptr) + return FALSE; /* oops, no digits */ + *strptr = ptr; + return TRUE; +} + + +/* Parse a crop specification (written in X11 geometry style). + * The routine returns TRUE if the spec string is valid, FALSE if not. + * + * The crop spec string should have the format + * [f]x[f]{+-}{+-} + * where width, height, xoffset, and yoffset are unsigned integers. + * Each of the elements can be omitted to indicate a default value. + * (A weakness of this style is that it is not possible to omit xoffset + * while specifying yoffset, since they look alike.) + * + * This code is loosely based on XParseGeometry from the X11 distribution. + */ + +GLOBAL(boolean) +jtransform_parse_crop_spec (jpeg_transform_info *info, const char *spec) +{ + info->crop = FALSE; + info->crop_width_set = JCROP_UNSET; + info->crop_height_set = JCROP_UNSET; + info->crop_xoffset_set = JCROP_UNSET; + info->crop_yoffset_set = JCROP_UNSET; + + if (isdigit(*spec)) { + /* fetch width */ + if (! jt_read_integer(&spec, &info->crop_width)) + return FALSE; + if (*spec == 'f' || *spec == 'F') { + spec++; + info->crop_width_set = JCROP_FORCE; + } else + info->crop_width_set = JCROP_POS; + } + if (*spec == 'x' || *spec == 'X') { + /* fetch height */ + spec++; + if (! jt_read_integer(&spec, &info->crop_height)) + return FALSE; + if (*spec == 'f' || *spec == 'F') { + spec++; + info->crop_height_set = JCROP_FORCE; + } else + info->crop_height_set = JCROP_POS; + } + if (*spec == '+' || *spec == '-') { + /* fetch xoffset */ + info->crop_xoffset_set = (*spec == '-') ? JCROP_NEG : JCROP_POS; + spec++; + if (! jt_read_integer(&spec, &info->crop_xoffset)) + return FALSE; + } + if (*spec == '+' || *spec == '-') { + /* fetch yoffset */ + info->crop_yoffset_set = (*spec == '-') ? JCROP_NEG : JCROP_POS; + spec++; + if (! jt_read_integer(&spec, &info->crop_yoffset)) + return FALSE; + } + /* We had better have gotten to the end of the string. */ + if (*spec != '\0') + return FALSE; + info->crop = TRUE; + return TRUE; +} + + +/* Trim off any partial iMCUs on the indicated destination edge */ + +LOCAL(void) +trim_right_edge (jpeg_transform_info *info, JDIMENSION full_width) +{ + JDIMENSION MCU_cols; + + MCU_cols = info->output_width / info->iMCU_sample_width; + if (MCU_cols > 0 && info->x_crop_offset + MCU_cols == + full_width / info->iMCU_sample_width) + info->output_width = MCU_cols * info->iMCU_sample_width; +} + +LOCAL(void) +trim_bottom_edge (jpeg_transform_info *info, JDIMENSION full_height) +{ + JDIMENSION MCU_rows; + + MCU_rows = info->output_height / info->iMCU_sample_height; + if (MCU_rows > 0 && info->y_crop_offset + MCU_rows == + full_height / info->iMCU_sample_height) + info->output_height = MCU_rows * info->iMCU_sample_height; +} + + +/* Request any required workspace. + * + * This routine figures out the size that the output image will be + * (which implies that all the transform parameters must be set before + * it is called). + * + * We allocate the workspace virtual arrays from the source decompression + * object, so that all the arrays (both the original data and the workspace) + * will be taken into account while making memory management decisions. + * Hence, this routine must be called after jpeg_read_header (which reads + * the image dimensions) and before jpeg_read_coefficients (which realizes + * the source's virtual arrays). + * + * This function returns FALSE right away if -perfect is given + * and transformation is not perfect. Otherwise returns TRUE. + */ + +GLOBAL(boolean) +jtransform_request_workspace (j_decompress_ptr srcinfo, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *coef_arrays; + boolean need_workspace, transpose_it; + jpeg_component_info *compptr; + JDIMENSION xoffset, yoffset; + JDIMENSION width_in_iMCUs, height_in_iMCUs; + JDIMENSION width_in_blocks, height_in_blocks; + int ci, h_samp_factor, v_samp_factor; + + /* Determine number of components in output image */ + if (info->force_grayscale && + (srcinfo->jpeg_color_space == JCS_YCbCr || + srcinfo->jpeg_color_space == JCS_BG_YCC) && + srcinfo->num_components == 3) + /* We'll only process the first component */ + info->num_components = 1; + else + /* Process all the components */ + info->num_components = srcinfo->num_components; + + /* Compute output image dimensions and related values. */ + jpeg_core_output_dimensions(srcinfo); + + /* Return right away if -perfect is given and transformation is not perfect. + */ + if (info->perfect) { + if (info->num_components == 1) { + if (!jtransform_perfect_transform(srcinfo->output_width, + srcinfo->output_height, + srcinfo->min_DCT_h_scaled_size, + srcinfo->min_DCT_v_scaled_size, + info->transform)) + return FALSE; + } else { + if (!jtransform_perfect_transform(srcinfo->output_width, + srcinfo->output_height, + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size, + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size, + info->transform)) + return FALSE; + } + } + + /* If there is only one output component, force the iMCU size to be 1; + * else use the source iMCU size. (This allows us to do the right thing + * when reducing color to grayscale, and also provides a handy way of + * cleaning up "funny" grayscale images whose sampling factors are not 1x1.) + */ + switch (info->transform) { + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + info->output_width = srcinfo->output_height; + info->output_height = srcinfo->output_width; + if (info->num_components == 1) { + info->iMCU_sample_width = srcinfo->min_DCT_v_scaled_size; + info->iMCU_sample_height = srcinfo->min_DCT_h_scaled_size; + } else { + info->iMCU_sample_width = + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size; + info->iMCU_sample_height = + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size; + } + break; + default: + info->output_width = srcinfo->output_width; + info->output_height = srcinfo->output_height; + if (info->num_components == 1) { + info->iMCU_sample_width = srcinfo->min_DCT_h_scaled_size; + info->iMCU_sample_height = srcinfo->min_DCT_v_scaled_size; + } else { + info->iMCU_sample_width = + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size; + info->iMCU_sample_height = + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size; + } + break; + } + + /* If cropping has been requested, compute the crop area's position and + * dimensions, ensuring that its upper left corner falls at an iMCU boundary. + */ + if (info->crop) { + /* Insert default values for unset crop parameters */ + if (info->crop_xoffset_set == JCROP_UNSET) + info->crop_xoffset = 0; /* default to +0 */ + if (info->crop_yoffset_set == JCROP_UNSET) + info->crop_yoffset = 0; /* default to +0 */ + if (info->crop_width_set == JCROP_UNSET) { + if (info->crop_xoffset >= info->output_width) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + info->crop_width = info->output_width - info->crop_xoffset; + } else { + /* Check for crop extension */ + if (info->crop_width > info->output_width) { + /* Crop extension does not work when transforming! */ + if (info->transform != JXFORM_NONE || + info->crop_xoffset >= info->crop_width || + info->crop_xoffset > info->crop_width - info->output_width) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + } else { + if (info->crop_xoffset >= info->output_width || + info->crop_width <= 0 || + info->crop_xoffset > info->output_width - info->crop_width) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + } + } + if (info->crop_height_set == JCROP_UNSET) { + if (info->crop_yoffset >= info->output_height) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + info->crop_height = info->output_height - info->crop_yoffset; + } else { + /* Check for crop extension */ + if (info->crop_height > info->output_height) { + /* Crop extension does not work when transforming! */ + if (info->transform != JXFORM_NONE || + info->crop_yoffset >= info->crop_height || + info->crop_yoffset > info->crop_height - info->output_height) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + } else { + if (info->crop_yoffset >= info->output_height || + info->crop_height <= 0 || + info->crop_yoffset > info->output_height - info->crop_height) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + } + } + /* Convert negative crop offsets into regular offsets */ + if (info->crop_xoffset_set != JCROP_NEG) + xoffset = info->crop_xoffset; + else if (info->crop_width > info->output_width) /* crop extension */ + xoffset = info->crop_width - info->output_width - info->crop_xoffset; + else + xoffset = info->output_width - info->crop_width - info->crop_xoffset; + if (info->crop_yoffset_set != JCROP_NEG) + yoffset = info->crop_yoffset; + else if (info->crop_height > info->output_height) /* crop extension */ + yoffset = info->crop_height - info->output_height - info->crop_yoffset; + else + yoffset = info->output_height - info->crop_height - info->crop_yoffset; + /* Now adjust so that upper left corner falls at an iMCU boundary */ + if (info->transform == JXFORM_WIPE) { + /* Ensure the effective wipe region will cover the requested */ + info->drop_width = (JDIMENSION) jdiv_round_up + ((long) (info->crop_width + (xoffset % info->iMCU_sample_width)), + (long) info->iMCU_sample_width); + info->drop_height = (JDIMENSION) jdiv_round_up + ((long) (info->crop_height + (yoffset % info->iMCU_sample_height)), + (long) info->iMCU_sample_height); + } else { + /* Ensure the effective crop region will cover the requested */ + if (info->crop_width_set == JCROP_FORCE || + info->crop_width > info->output_width) + info->output_width = info->crop_width; + else + info->output_width = + info->crop_width + (xoffset % info->iMCU_sample_width); + if (info->crop_height_set == JCROP_FORCE || + info->crop_height > info->output_height) + info->output_height = info->crop_height; + else + info->output_height = + info->crop_height + (yoffset % info->iMCU_sample_height); + } + /* Save x/y offsets measured in iMCUs */ + info->x_crop_offset = xoffset / info->iMCU_sample_width; + info->y_crop_offset = yoffset / info->iMCU_sample_height; + } else { + info->x_crop_offset = 0; + info->y_crop_offset = 0; + } + + /* Figure out whether we need workspace arrays, + * and if so whether they are transposed relative to the source. + */ + need_workspace = FALSE; + transpose_it = FALSE; + switch (info->transform) { + case JXFORM_NONE: + if (info->x_crop_offset != 0 || info->y_crop_offset != 0 || + info->output_width > srcinfo->output_width || + info->output_height > srcinfo->output_height) + need_workspace = TRUE; + /* No workspace needed if neither cropping nor transforming */ + break; + case JXFORM_FLIP_H: + if (info->trim) + trim_right_edge(info, srcinfo->output_width); + if (info->y_crop_offset != 0) + need_workspace = TRUE; + /* do_flip_h_no_crop doesn't need a workspace array */ + break; + case JXFORM_FLIP_V: + if (info->trim) + trim_bottom_edge(info, srcinfo->output_height); + /* Need workspace arrays having same dimensions as source image. */ + need_workspace = TRUE; + break; + case JXFORM_TRANSPOSE: + /* transpose does NOT have to trim anything */ + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_TRANSVERSE: + if (info->trim) { + trim_right_edge(info, srcinfo->output_height); + trim_bottom_edge(info, srcinfo->output_width); + } + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_ROT_90: + if (info->trim) + trim_right_edge(info, srcinfo->output_height); + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_ROT_180: + if (info->trim) { + trim_right_edge(info, srcinfo->output_width); + trim_bottom_edge(info, srcinfo->output_height); + } + /* Need workspace arrays having same dimensions as source image. */ + need_workspace = TRUE; + break; + case JXFORM_ROT_270: + if (info->trim) + trim_bottom_edge(info, srcinfo->output_width); + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_WIPE: + break; + } + + /* Allocate workspace if needed. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + if (need_workspace) { + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + width_in_iMCUs = (JDIMENSION) + jdiv_round_up((long) info->output_width, + (long) info->iMCU_sample_width); + height_in_iMCUs = (JDIMENSION) + jdiv_round_up((long) info->output_height, + (long) info->iMCU_sample_height); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + if (info->num_components == 1) { + /* we're going to force samp factors to 1x1 in this case */ + h_samp_factor = v_samp_factor = 1; + } else if (transpose_it) { + h_samp_factor = compptr->v_samp_factor; + v_samp_factor = compptr->h_samp_factor; + } else { + h_samp_factor = compptr->h_samp_factor; + v_samp_factor = compptr->v_samp_factor; + } + width_in_blocks = width_in_iMCUs * h_samp_factor; + height_in_blocks = height_in_iMCUs * v_samp_factor; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + width_in_blocks, height_in_blocks, (JDIMENSION) v_samp_factor); + } + info->workspace_coef_arrays = coef_arrays; + } else + info->workspace_coef_arrays = NULL; + + return TRUE; +} + + +/* Transpose destination image parameters */ + +LOCAL(void) +transpose_critical_parameters (j_compress_ptr dstinfo) +{ + int tblno, i, j, ci, itemp; + jpeg_component_info *compptr; + JQUANT_TBL *qtblptr; + JDIMENSION jtemp; + UINT16 qtemp; + + /* Transpose image dimensions */ + jtemp = dstinfo->image_width; + dstinfo->image_width = dstinfo->image_height; + dstinfo->image_height = jtemp; + itemp = dstinfo->min_DCT_h_scaled_size; + dstinfo->min_DCT_h_scaled_size = dstinfo->min_DCT_v_scaled_size; + dstinfo->min_DCT_v_scaled_size = itemp; + + /* Transpose sampling factors */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + itemp = compptr->h_samp_factor; + compptr->h_samp_factor = compptr->v_samp_factor; + compptr->v_samp_factor = itemp; + } + + /* Transpose quantization tables */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + qtblptr = dstinfo->quant_tbl_ptrs[tblno]; + if (qtblptr != NULL) { + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < i; j++) { + qtemp = qtblptr->quantval[i*DCTSIZE+j]; + qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i]; + qtblptr->quantval[j*DCTSIZE+i] = qtemp; + } + } + } + } +} + + +/* Adjust Exif image parameters. + * + * We try to adjust the Tags ExifImageWidth and ExifImageHeight if possible. + */ + +LOCAL(void) +adjust_exif_parameters (JOCTET FAR * data, unsigned int length, + JDIMENSION new_width, JDIMENSION new_height) +{ + boolean is_motorola; /* Flag for byte order */ + unsigned int number_of_tags, tagnum; + unsigned int firstoffset, offset; + JDIMENSION new_value; + + if (length < 12) return; /* Length of an IFD entry */ + + /* Discover byte order */ + if (GETJOCTET(data[0]) == 0x49 && GETJOCTET(data[1]) == 0x49) + is_motorola = FALSE; + else if (GETJOCTET(data[0]) == 0x4D && GETJOCTET(data[1]) == 0x4D) + is_motorola = TRUE; + else + return; + + /* Check Tag Mark */ + if (is_motorola) { + if (GETJOCTET(data[2]) != 0) return; + if (GETJOCTET(data[3]) != 0x2A) return; + } else { + if (GETJOCTET(data[3]) != 0) return; + if (GETJOCTET(data[2]) != 0x2A) return; + } + + /* Get first IFD offset (offset to IFD0) */ + if (is_motorola) { + if (GETJOCTET(data[4]) != 0) return; + if (GETJOCTET(data[5]) != 0) return; + firstoffset = GETJOCTET(data[6]); + firstoffset <<= 8; + firstoffset += GETJOCTET(data[7]); + } else { + if (GETJOCTET(data[7]) != 0) return; + if (GETJOCTET(data[6]) != 0) return; + firstoffset = GETJOCTET(data[5]); + firstoffset <<= 8; + firstoffset += GETJOCTET(data[4]); + } + if (firstoffset > length - 2) return; /* check end of data segment */ + + /* Get the number of directory entries contained in this IFD */ + if (is_motorola) { + number_of_tags = GETJOCTET(data[firstoffset]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[firstoffset+1]); + } else { + number_of_tags = GETJOCTET(data[firstoffset+1]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[firstoffset]); + } + if (number_of_tags == 0) return; + firstoffset += 2; + + /* Search for ExifSubIFD offset Tag in IFD0 */ + for (;;) { + if (firstoffset > length - 12) return; /* check end of data segment */ + /* Get Tag number */ + if (is_motorola) { + tagnum = GETJOCTET(data[firstoffset]); + tagnum <<= 8; + tagnum += GETJOCTET(data[firstoffset+1]); + } else { + tagnum = GETJOCTET(data[firstoffset+1]); + tagnum <<= 8; + tagnum += GETJOCTET(data[firstoffset]); + } + if (tagnum == 0x8769) break; /* found ExifSubIFD offset Tag */ + if (--number_of_tags == 0) return; + firstoffset += 12; + } + + /* Get the ExifSubIFD offset */ + if (is_motorola) { + if (GETJOCTET(data[firstoffset+8]) != 0) return; + if (GETJOCTET(data[firstoffset+9]) != 0) return; + offset = GETJOCTET(data[firstoffset+10]); + offset <<= 8; + offset += GETJOCTET(data[firstoffset+11]); + } else { + if (GETJOCTET(data[firstoffset+11]) != 0) return; + if (GETJOCTET(data[firstoffset+10]) != 0) return; + offset = GETJOCTET(data[firstoffset+9]); + offset <<= 8; + offset += GETJOCTET(data[firstoffset+8]); + } + if (offset > length - 2) return; /* check end of data segment */ + + /* Get the number of directory entries contained in this SubIFD */ + if (is_motorola) { + number_of_tags = GETJOCTET(data[offset]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[offset+1]); + } else { + number_of_tags = GETJOCTET(data[offset+1]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[offset]); + } + if (number_of_tags < 2) return; + offset += 2; + + /* Search for ExifImageWidth and ExifImageHeight Tags in this SubIFD */ + do { + if (offset > length - 12) return; /* check end of data segment */ + /* Get Tag number */ + if (is_motorola) { + tagnum = GETJOCTET(data[offset]); + tagnum <<= 8; + tagnum += GETJOCTET(data[offset+1]); + } else { + tagnum = GETJOCTET(data[offset+1]); + tagnum <<= 8; + tagnum += GETJOCTET(data[offset]); + } + if (tagnum == 0xA002 || tagnum == 0xA003) { + if (tagnum == 0xA002) + new_value = new_width; /* ExifImageWidth Tag */ + else + new_value = new_height; /* ExifImageHeight Tag */ + if (is_motorola) { + data[offset+2] = 0; /* Format = unsigned long (4 octets) */ + data[offset+3] = 4; + data[offset+4] = 0; /* Number Of Components = 1 */ + data[offset+5] = 0; + data[offset+6] = 0; + data[offset+7] = 1; + data[offset+8] = 0; + data[offset+9] = 0; + data[offset+10] = (JOCTET)((new_value >> 8) & 0xFF); + data[offset+11] = (JOCTET)(new_value & 0xFF); + } else { + data[offset+2] = 4; /* Format = unsigned long (4 octets) */ + data[offset+3] = 0; + data[offset+4] = 1; /* Number Of Components = 1 */ + data[offset+5] = 0; + data[offset+6] = 0; + data[offset+7] = 0; + data[offset+8] = (JOCTET)(new_value & 0xFF); + data[offset+9] = (JOCTET)((new_value >> 8) & 0xFF); + data[offset+10] = 0; + data[offset+11] = 0; + } + } + offset += 12; + } while (--number_of_tags); +} + + +/* Adjust output image parameters as needed. + * + * This must be called after jpeg_copy_critical_parameters() + * and before jpeg_write_coefficients(). + * + * The return value is the set of virtual coefficient arrays to be written + * (either the ones allocated by jtransform_request_workspace, or the + * original source data arrays). The caller will need to pass this value + * to jpeg_write_coefficients(). + */ + +GLOBAL(jvirt_barray_ptr *) +jtransform_adjust_parameters (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + /* If force-to-grayscale is requested, adjust destination parameters */ + if (info->force_grayscale) { + /* First, ensure we have YCC or grayscale data, and that the source's + * Y channel is full resolution. (No reasonable person would make Y + * be less than full resolution, so actually coping with that case + * isn't worth extra code space. But we check it to avoid crashing.) + */ + if ((((dstinfo->jpeg_color_space == JCS_YCbCr || + dstinfo->jpeg_color_space == JCS_BG_YCC) && + dstinfo->num_components == 3) || + (dstinfo->jpeg_color_space == JCS_GRAYSCALE && + dstinfo->num_components == 1)) && + srcinfo->comp_info[0].h_samp_factor == srcinfo->max_h_samp_factor && + srcinfo->comp_info[0].v_samp_factor == srcinfo->max_v_samp_factor) { + /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed + * properly. Among other things, it sets the target h_samp_factor & + * v_samp_factor to 1, which typically won't match the source. + * We have to preserve the source's quantization table number, however. + */ + int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no; + jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE); + dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no; + } else { + /* Sorry, can't do it */ + ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL); + } + } else if (info->num_components == 1) { + /* For a single-component source, we force the destination sampling factors + * to 1x1, with or without force_grayscale. This is useful because some + * decoders choke on grayscale images with other sampling factors. + */ + dstinfo->comp_info[0].h_samp_factor = 1; + dstinfo->comp_info[0].v_samp_factor = 1; + } + + /* Correct the destination's image dimensions as necessary + * for rotate/flip, resize, and crop operations. + */ + dstinfo->jpeg_width = info->output_width; + dstinfo->jpeg_height = info->output_height; + + /* Transpose destination image parameters */ + switch (info->transform) { + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + transpose_critical_parameters(dstinfo); + break; + default: + break; + } + + /* Adjust Exif properties */ + if (srcinfo->marker_list != NULL && + srcinfo->marker_list->marker == JPEG_APP0+1 && + srcinfo->marker_list->data_length >= 6 && + GETJOCTET(srcinfo->marker_list->data[0]) == 0x45 && + GETJOCTET(srcinfo->marker_list->data[1]) == 0x78 && + GETJOCTET(srcinfo->marker_list->data[2]) == 0x69 && + GETJOCTET(srcinfo->marker_list->data[3]) == 0x66 && + GETJOCTET(srcinfo->marker_list->data[4]) == 0 && + GETJOCTET(srcinfo->marker_list->data[5]) == 0) { + /* Suppress output of JFIF marker */ + dstinfo->write_JFIF_header = FALSE; + /* Adjust Exif image parameters */ + if (dstinfo->jpeg_width != srcinfo->image_width || + dstinfo->jpeg_height != srcinfo->image_height) + /* Align data segment to start of TIFF structure for parsing */ + adjust_exif_parameters(srcinfo->marker_list->data + 6, + srcinfo->marker_list->data_length - 6, + dstinfo->jpeg_width, dstinfo->jpeg_height); + } + + /* Return the appropriate output data set */ + if (info->workspace_coef_arrays != NULL) + return info->workspace_coef_arrays; + return src_coef_arrays; +} + + +/* Execute the actual transformation, if any. + * + * This must be called *after* jpeg_write_coefficients, because it depends + * on jpeg_write_coefficients to have computed subsidiary values such as + * the per-component width and height fields in the destination object. + * + * Note that some transformations will modify the source data arrays! + */ + +GLOBAL(void) +jtransform_execute_transform (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays; + + /* Note: conditions tested here should match those in switch statement + * in jtransform_request_workspace() + */ + switch (info->transform) { + case JXFORM_NONE: + if (info->output_width > srcinfo->output_width || + info->output_height > srcinfo->output_height) + do_crop_ext(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + else if (info->x_crop_offset != 0 || info->y_crop_offset != 0) + do_crop(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_FLIP_H: + if (info->y_crop_offset != 0) + do_flip_h(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + else + do_flip_h_no_crop(srcinfo, dstinfo, info->x_crop_offset, + src_coef_arrays); + break; + case JXFORM_FLIP_V: + do_flip_v(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSPOSE: + do_transpose(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSVERSE: + do_transverse(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_90: + do_rot_90(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_180: + do_rot_180(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_270: + do_rot_270(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_WIPE: + do_wipe(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, info->drop_width, info->drop_height); + break; + } +} + +/* jtransform_perfect_transform + * + * Determine whether lossless transformation is perfectly + * possible for a specified image and transformation. + * + * Inputs: + * image_width, image_height: source image dimensions. + * MCU_width, MCU_height: pixel dimensions of MCU. + * transform: transformation identifier. + * Parameter sources from initialized jpeg_struct + * (after reading source header): + * image_width = cinfo.image_width + * image_height = cinfo.image_height + * MCU_width = cinfo.max_h_samp_factor * cinfo.block_size + * MCU_height = cinfo.max_v_samp_factor * cinfo.block_size + * Result: + * TRUE = perfect transformation possible + * FALSE = perfect transformation not possible + * (may use custom action then) + */ + +GLOBAL(boolean) +jtransform_perfect_transform(JDIMENSION image_width, JDIMENSION image_height, + int MCU_width, int MCU_height, + JXFORM_CODE transform) +{ + boolean result = TRUE; /* initialize TRUE */ + + switch (transform) { + case JXFORM_FLIP_H: + case JXFORM_ROT_270: + if (image_width % (JDIMENSION) MCU_width) + result = FALSE; + break; + case JXFORM_FLIP_V: + case JXFORM_ROT_90: + if (image_height % (JDIMENSION) MCU_height) + result = FALSE; + break; + case JXFORM_TRANSVERSE: + case JXFORM_ROT_180: + if (image_width % (JDIMENSION) MCU_width) + result = FALSE; + if (image_height % (JDIMENSION) MCU_height) + result = FALSE; + break; + default: + break; + } + + return result; +} + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* Setup decompression object to save desired markers in memory. + * This must be called before jpeg_read_header() to have the desired effect. + */ + +GLOBAL(void) +jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option) +{ +#ifdef SAVE_MARKERS_SUPPORTED + int m; + + /* Save comments except under NONE option */ + if (option != JCOPYOPT_NONE) { + jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF); + } + /* Save all types of APPn markers iff ALL option */ + if (option == JCOPYOPT_ALL) { + for (m = 0; m < 16; m++) + jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF); + } +#endif /* SAVE_MARKERS_SUPPORTED */ +} + +/* Copy markers saved in the given source object to the destination object. + * This should be called just after jpeg_start_compress() or + * jpeg_write_coefficients(). + * Note that those routines will have written the SOI, and also the + * JFIF APP0 or Adobe APP14 markers if selected. + */ + +GLOBAL(void) +jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option) +{ + jpeg_saved_marker_ptr marker; + + /* In the current implementation, we don't actually need to examine the + * option flag here; we just copy everything that got saved. + * But to avoid confusion, we do not output JFIF and Adobe APP14 markers + * if the encoder library already wrote one. + */ + for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) { + if (dstinfo->write_JFIF_header && + marker->marker == JPEG_APP0 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x4A && + GETJOCTET(marker->data[1]) == 0x46 && + GETJOCTET(marker->data[2]) == 0x49 && + GETJOCTET(marker->data[3]) == 0x46 && + GETJOCTET(marker->data[4]) == 0) + continue; /* reject duplicate JFIF */ + if (dstinfo->write_Adobe_marker && + marker->marker == JPEG_APP0+14 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x41 && + GETJOCTET(marker->data[1]) == 0x64 && + GETJOCTET(marker->data[2]) == 0x6F && + GETJOCTET(marker->data[3]) == 0x62 && + GETJOCTET(marker->data[4]) == 0x65) + continue; /* reject duplicate Adobe */ +#ifdef NEED_FAR_POINTERS + /* We could use jpeg_write_marker if the data weren't FAR... */ + { + unsigned int i; + jpeg_write_m_header(dstinfo, marker->marker, marker->data_length); + for (i = 0; i < marker->data_length; i++) + jpeg_write_m_byte(dstinfo, marker->data[i]); + } +#else + jpeg_write_marker(dstinfo, marker->marker, + marker->data, marker->data_length); +#endif + } +} diff --git a/gwenview/lib/libjpeg-90/transupp.h b/gwenview/lib/libjpeg-90/transupp.h new file mode 100644 index 00000000..eee69314 --- /dev/null +++ b/gwenview/lib/libjpeg-90/transupp.h @@ -0,0 +1,219 @@ +/* + * transupp.h + * + * Copyright (C) 1997-2013, Thomas G. Lane, Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for image transformation routines and + * other utility code used by the jpegtran sample application. These are + * NOT part of the core JPEG library. But we keep these routines separate + * from jpegtran.c to ease the task of maintaining jpegtran-like programs + * that have other user interfaces. + * + * NOTE: all the routines declared here have very specific requirements + * about when they are to be executed during the reading and writing of the + * source and destination files. See the comments in transupp.c, or see + * jpegtran.c for an example of correct usage. + */ + +/* If you happen not to want the image transform support, disable it here */ +#ifndef TRANSFORMS_SUPPORTED +#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */ +#endif + +/* + * Although rotating and flipping data expressed as DCT coefficients is not + * hard, there is an asymmetry in the JPEG format specification for images + * whose dimensions aren't multiples of the iMCU size. The right and bottom + * image edges are padded out to the next iMCU boundary with junk data; but + * no padding is possible at the top and left edges. If we were to flip + * the whole image including the pad data, then pad garbage would become + * visible at the top and/or left, and real pixels would disappear into the + * pad margins --- perhaps permanently, since encoders & decoders may not + * bother to preserve DCT blocks that appear to be completely outside the + * nominal image area. So, we have to exclude any partial iMCUs from the + * basic transformation. + * + * Transpose is the only transformation that can handle partial iMCUs at the + * right and bottom edges completely cleanly. flip_h can flip partial iMCUs + * at the bottom, but leaves any partial iMCUs at the right edge untouched. + * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched. + * The other transforms are defined as combinations of these basic transforms + * and process edge blocks in a way that preserves the equivalence. + * + * The "trim" option causes untransformable partial iMCUs to be dropped; + * this is not strictly lossless, but it usually gives the best-looking + * result for odd-size images. Note that when this option is active, + * the expected mathematical equivalences between the transforms may not hold. + * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim + * followed by -rot 180 -trim trims both edges.) + * + * We also offer a lossless-crop option, which discards data outside a given + * image region but losslessly preserves what is inside. Like the rotate and + * flip transforms, lossless crop is restricted by the current JPEG format: the + * upper left corner of the selected region must fall on an iMCU boundary. If + * this does not hold for the given crop parameters, we silently move the upper + * left corner up and/or left to make it so, simultaneously increasing the + * region dimensions to keep the lower right crop corner unchanged. (Thus, the + * output image covers at least the requested region, but may cover more.) + * The adjustment of the region dimensions may be optionally disabled. + * + * A complementary lossless-wipe option is provided to discard (gray out) data + * inside a given image region while losslessly preserving what is outside. + * + * We also provide a lossless-resize option, which is kind of a lossless-crop + * operation in the DCT coefficient block domain - it discards higher-order + * coefficients and losslessly preserves lower-order coefficients of a + * sub-block. + * + * Rotate/flip transform, resize, and crop can be requested together in a + * single invocation. The crop is applied last --- that is, the crop region + * is specified in terms of the destination image after transform/resize. + * + * We also offer a "force to grayscale" option, which simply discards the + * chrominance channels of a YCbCr image. This is lossless in the sense that + * the luminance channel is preserved exactly. It's not the same kind of + * thing as the rotate/flip transformations, but it's convenient to handle it + * as part of this package, mainly because the transformation routines have to + * be aware of the option to know how many components to work on. + */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jtransform_parse_crop_spec jTrParCrop +#define jtransform_request_workspace jTrRequest +#define jtransform_adjust_parameters jTrAdjust +#define jtransform_execute_transform jTrExec +#define jtransform_perfect_transform jTrPerfect +#define jcopy_markers_setup jCMrkSetup +#define jcopy_markers_execute jCMrkExec +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * Codes for supported types of image transformations. + */ + +typedef enum { + JXFORM_NONE, /* no transformation */ + JXFORM_FLIP_H, /* horizontal flip */ + JXFORM_FLIP_V, /* vertical flip */ + JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */ + JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */ + JXFORM_ROT_90, /* 90-degree clockwise rotation */ + JXFORM_ROT_180, /* 180-degree rotation */ + JXFORM_ROT_270, /* 270-degree clockwise (or 90 ccw) */ + JXFORM_WIPE /* wipe */ +} JXFORM_CODE; + +/* + * Codes for crop parameters, which can individually be unspecified, + * positive or negative for xoffset or yoffset, + * positive or forced for width or height. + */ + +typedef enum { + JCROP_UNSET, + JCROP_POS, + JCROP_NEG, + JCROP_FORCE +} JCROP_CODE; + +/* + * Transform parameters struct. + * NB: application must not change any elements of this struct after + * calling jtransform_request_workspace. + */ + +typedef struct { + /* Options: set by caller */ + JXFORM_CODE transform; /* image transform operator */ + boolean perfect; /* if TRUE, fail if partial MCUs are requested */ + boolean trim; /* if TRUE, trim partial MCUs as needed */ + boolean force_grayscale; /* if TRUE, convert color image to grayscale */ + boolean crop; /* if TRUE, crop or wipe source image */ + + /* Crop parameters: application need not set these unless crop is TRUE. + * These can be filled in by jtransform_parse_crop_spec(). + */ + JDIMENSION crop_width; /* Width of selected region */ + JCROP_CODE crop_width_set; /* (forced disables adjustment) */ + JDIMENSION crop_height; /* Height of selected region */ + JCROP_CODE crop_height_set; /* (forced disables adjustment) */ + JDIMENSION crop_xoffset; /* X offset of selected region */ + JCROP_CODE crop_xoffset_set; /* (negative measures from right edge) */ + JDIMENSION crop_yoffset; /* Y offset of selected region */ + JCROP_CODE crop_yoffset_set; /* (negative measures from bottom edge) */ + + /* Internal workspace: caller should not touch these */ + int num_components; /* # of components in workspace */ + jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */ + JDIMENSION output_width; /* cropped destination dimensions */ + JDIMENSION output_height; + JDIMENSION x_crop_offset; /* destination crop offsets measured in iMCUs */ + JDIMENSION y_crop_offset; + JDIMENSION drop_width; /* drop/wipe dimensions measured in iMCUs */ + JDIMENSION drop_height; + int iMCU_sample_width; /* destination iMCU size */ + int iMCU_sample_height; +} jpeg_transform_info; + + +#if TRANSFORMS_SUPPORTED + +/* Parse a crop specification (written in X11 geometry style) */ +EXTERN(boolean) jtransform_parse_crop_spec + JPP((jpeg_transform_info *info, const char *spec)); +/* Request any required workspace */ +EXTERN(boolean) jtransform_request_workspace + JPP((j_decompress_ptr srcinfo, jpeg_transform_info *info)); +/* Adjust output image parameters */ +EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); +/* Execute the actual transformation, if any */ +EXTERN(void) jtransform_execute_transform + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); +/* Determine whether lossless transformation is perfectly + * possible for a specified image and transformation. + */ +EXTERN(boolean) jtransform_perfect_transform + JPP((JDIMENSION image_width, JDIMENSION image_height, + int MCU_width, int MCU_height, + JXFORM_CODE transform)); + +/* jtransform_execute_transform used to be called + * jtransform_execute_transformation, but some compilers complain about + * routine names that long. This macro is here to avoid breaking any + * old source code that uses the original name... + */ +#define jtransform_execute_transformation jtransform_execute_transform + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* + * Support for copying optional markers from source to destination file. + */ + +typedef enum { + JCOPYOPT_NONE, /* copy no optional markers */ + JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */ + JCOPYOPT_ALL /* copy all optional markers */ +} JCOPY_OPTION; + +#define JCOPYOPT_DEFAULT JCOPYOPT_COMMENTS /* recommended default */ + +/* Setup decompression object to save desired markers in memory */ +EXTERN(void) jcopy_markers_setup + JPP((j_decompress_ptr srcinfo, JCOPY_OPTION option)); +/* Copy markers saved in the given source object to the destination object */ +EXTERN(void) jcopy_markers_execute + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option)); diff --git a/gwenview/lib/memoryutils.cpp b/gwenview/lib/memoryutils.cpp new file mode 100644 index 00000000..4e3aec12 --- /dev/null +++ b/gwenview/lib/memoryutils.cpp @@ -0,0 +1,171 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau +Copyright (C) 2004-2005 by Enrico Ros +Copyright (C) 2004-2007 by Albert Astals Cid + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "memoryutils.h" + +// Qt +#include +#include +#include + +// System +#ifdef Q_OS_WIN +#define _WIN32_WINNT 0x0500 +#include +#elif defined(Q_OS_FREEBSD) +#include +#include +#include +#endif + +namespace Gwenview +{ + +namespace MemoryUtils +{ + +// This code has been copied from okular/core/document.cpp +qulonglong getTotalMemory() +{ + static qulonglong cachedValue = 0; + if ( cachedValue ) + return cachedValue; + +#if defined(Q_OS_LINUX) + // if /proc/meminfo doesn't exist, return 128MB + QFile memFile( "/proc/meminfo" ); + if ( !memFile.open( QIODevice::ReadOnly ) ) + return (cachedValue = 134217728); + + QTextStream readStream( &memFile ); + while ( true ) + { + QString entry = readStream.readLine(); + if ( entry.isNull() ) break; + if ( entry.startsWith( "MemTotal:" ) ) + return (cachedValue = (Q_UINT64_C(1024) * entry.section( ' ', -2, -2 ).toULongLong())); + } +#elif defined(Q_OS_FREEBSD) + qulonglong physmem; + int mib[] = {CTL_HW, HW_PHYSMEM}; + size_t len = sizeof( physmem ); + if ( sysctl( mib, 2, &physmem, &len, NULL, 0 ) == 0 ) + return (cachedValue = physmem); +#elif defined(Q_OS_WIN) + MEMORYSTATUSEX stat; + stat.dwLength = sizeof(stat); + GlobalMemoryStatusEx (&stat); + + return ( cachedValue = stat.ullTotalPhys ); +#endif + return (cachedValue = 134217728); +} + +qulonglong getFreeMemory() +{ + static QTime lastUpdate = QTime::currentTime().addSecs(-3); + static qulonglong cachedValue = 0; + + if ( qAbs( lastUpdate.secsTo( QTime::currentTime() ) ) <= 2 ) + return cachedValue; + +#if defined(Q_OS_LINUX) + // if /proc/meminfo doesn't exist, return MEMORY FULL + QFile memFile( "/proc/meminfo" ); + if ( !memFile.open( QIODevice::ReadOnly ) ) + return 0; + + // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers' + // and 'Cached' fields. consider swapped memory as used memory. + qulonglong memoryFree = 0; + QString entry; + QTextStream readStream( &memFile ); + static const int nElems = 5; + QString names[nElems] = { "MemFree:", "Buffers:", "Cached:", "SwapFree:", "SwapTotal:" }; + qulonglong values[nElems] = { 0, 0, 0, 0, 0 }; + bool foundValues[nElems] = { false, false, false, false, false }; + while ( true ) + { + entry = readStream.readLine(); + if ( entry.isNull() ) break; + for ( int i = 0; i < nElems; ++i ) + { + if ( entry.startsWith( names[i] ) ) + { + values[i] = entry.section( ' ', -2, -2 ).toULongLong( &foundValues[i] ); + } + } + } + memFile.close(); + bool found = true; + for ( int i = 0; found && i < nElems; ++i ) + found = found && foundValues[i]; + if ( found ) + { + memoryFree = values[0] + values[1] + values[2] + values[3]; + if ( values[4] > memoryFree ) + memoryFree = 0; + else + memoryFree -= values[4]; + } + + lastUpdate = QTime::currentTime(); + + return ( cachedValue = (Q_UINT64_C(1024) * memoryFree) ); +#elif defined(Q_OS_FREEBSD) + qulonglong cache, inact, free, psize; + size_t cachelen, inactlen, freelen, psizelen; + cachelen = sizeof( cache ); + inactlen = sizeof( inact ); + freelen = sizeof( free ); + psizelen = sizeof( psize ); + // sum up inactive, cached and free memory + if ( sysctlbyname( "vm.stats.vm.v_cache_count", &cache, &cachelen, NULL, 0 ) == 0 && + sysctlbyname( "vm.stats.vm.v_inactive_count", &inact, &inactlen, NULL, 0 ) == 0 && + sysctlbyname( "vm.stats.vm.v_free_count", &free, &freelen, NULL, 0 ) == 0 && + sysctlbyname( "vm.stats.vm.v_page_size", &psize, &psizelen, NULL, 0 ) == 0 ) + { + lastUpdate = QTime::currentTime(); + return (cachedValue = (cache + inact + free) * psize); + } + else + { + return 0; + } +#elif defined(Q_OS_WIN) + MEMORYSTATUSEX stat; + stat.dwLength = sizeof(stat); + GlobalMemoryStatusEx (&stat); + + lastUpdate = QTime::currentTime(); + + return ( cachedValue = stat.ullAvailPhys ); +#else + // tell the memory is full.. will act as in LOW profile + return 0; +#endif +} + +} // MemoryUtils namespace + +} // Gwenview namespace diff --git a/gwenview/lib/memoryutils.h b/gwenview/lib/memoryutils.h new file mode 100644 index 00000000..4dfebf1b --- /dev/null +++ b/gwenview/lib/memoryutils.h @@ -0,0 +1,56 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef MEMORYUTILS_H +#define MEMORYUTILS_H + +#include + +// Qt +#include + +// KDE + +// Local + +namespace Gwenview +{ + +namespace MemoryUtils +{ + +/** + * This function returns the amount of total memory installed on the system + * FIXME: It lacks *BSD and MacOSX support! + */ +GWENVIEWLIB_EXPORT qulonglong getTotalMemory(); + +/** + * This function returns the amount of available free memory installed on the + * system + * FIXME: It lacks *BSD and MacOSX support! + */ +GWENVIEWLIB_EXPORT qulonglong getFreeMemory(); + +} // namespace + +} // namespace + +#endif /* MEMORYUTILS_H */ diff --git a/gwenview/lib/mimetypeutils.cpp b/gwenview/lib/mimetypeutils.cpp new file mode 100644 index 00000000..17e30ca5 --- /dev/null +++ b/gwenview/lib/mimetypeutils.cpp @@ -0,0 +1,206 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab +/* +Gwenview - A simple image viewer for KDE +Copyright 2006 Aurelien Gateau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "mimetypeutils.h" +#include "mimetypeutils_p.moc" + +// Qt +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Local +#include +#include + +namespace Gwenview +{ + +namespace MimeTypeUtils +{ + +static inline QString resolveAlias(const QString& name) +{ + KMimeType::Ptr ptr = KMimeType::mimeType(name, KMimeType::ResolveAliases); + return ptr.isNull() ? name : ptr->name(); +} + +static void resolveAliasInList(QStringList* list) +{ + QStringList::Iterator + it = list->begin(), + end = list->end(); + for (; it != end; ++it) { + *it = resolveAlias(*it); + } +} + +static void addRawMimeTypes(QStringList* list) +{ + // need to invent more intelligent way to whitelist raws + *list += "image/x-nikon-nef"; + *list += "image/x-nikon-nrw"; + *list += "image/x-canon-cr2"; + *list += "image/x-canon-crw"; + *list += "image/x-pentax-pef"; + *list += "image/x-adobe-dng"; + *list += "image/x-sony-arw"; + *list += "image/x-minolta-mrw"; + *list += "image/x-panasonic-raw"; + *list += "image/x-panasonic-raw2"; + *list += "image/x-samsung-srw"; + *list += "image/x-olympus-orf"; + *list += "image/x-fuji-raf"; + *list += "image/x-kodak-dcr"; + *list += "image/x-sigma-x3f"; +} + +const QStringList& rasterImageMimeTypes() +{ + static QStringList list; + if (list.isEmpty()) { + list = KImageIO::mimeTypes(KImageIO::Reading); + resolveAliasInList(&list); + // We don't want svg images to be considered as raster images + Q_FOREACH(const QString& mimeType, svgImageMimeTypes()) { + list.removeOne(mimeType); + } + addRawMimeTypes(&list); + } + return list; +} + +const QStringList& svgImageMimeTypes() +{ + static QStringList list; + if (list.isEmpty()) { + list << "image/svg+xml" << "image/svg+xml-compressed"; + resolveAliasInList(&list); + } + return list; +} + +const QStringList& imageMimeTypes() +{ + static QStringList list; + if (list.isEmpty()) { + list = rasterImageMimeTypes(); + list += svgImageMimeTypes(); + } + + return list; +} + +QString urlMimeType(const KUrl& url) +{ + if (url.isEmpty()) { + return "unknown"; + } + // Try a simple guess, using extension for remote urls + QString mimeType = KMimeType::findByUrl(url)->name(); + if (mimeType == "application/octet-stream") { + kDebug() << "KMimeType::findByUrl() failed to find mimetype for" << url << ". Falling back to KIO::NetAccess::mimetype()."; + // No luck, look deeper. This can happens with http urls if the filename + // does not provide any extension. + mimeType = KIO::NetAccess::mimetype(url, KApplication::kApplication()->activeWindow()); + } + return mimeType; +} + +QString urlMimeTypeByContent(const KUrl& url) +{ + const int HEADER_SIZE = 30; + if (url.isLocalFile()) { + return KMimeType::findByFileContent(url.toLocalFile())->name(); + } + + KIO::TransferJob* job = KIO::get(url); + DataAccumulator accumulator(job); + while (!accumulator.finished() && accumulator.data().size() < HEADER_SIZE) { + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); + } + return KMimeType::findByContent(accumulator.data())->name(); +} + +Kind mimeTypeKind(const QString& mimeType) +{ + if (rasterImageMimeTypes().contains(mimeType)) { + return KIND_RASTER_IMAGE; + } + if (svgImageMimeTypes().contains(mimeType)) { + return KIND_SVG_IMAGE; + } + if (mimeType.startsWith(QLatin1String("video/"))) { + return KIND_VIDEO; + } + if (mimeType.startsWith(QLatin1String("inode/directory"))) { + return KIND_DIR; + } + if (!ArchiveUtils::protocolForMimeType(mimeType).isEmpty()) { + return KIND_ARCHIVE; + } + + return KIND_FILE; +} + +Kind fileItemKind(const KFileItem& item) +{ + GV_RETURN_VALUE_IF_FAIL(!item.isNull(), KIND_UNKNOWN); + return mimeTypeKind(item.mimetype()); +} + +Kind urlKind(const KUrl& url) +{ + return mimeTypeKind(urlMimeType(url)); +} + +DataAccumulator::DataAccumulator(KIO::TransferJob* job) +: QObject() +, mFinished(false) +{ + connect(job, SIGNAL(data(KIO::Job*,QByteArray)), + SLOT(slotDataReceived(KIO::Job*,QByteArray))); + connect(job, SIGNAL(result(KJob*)), + SLOT(slotFinished())); +} + +void DataAccumulator::slotDataReceived(KIO::Job*, const QByteArray& data) +{ + mData += data; +} + +void DataAccumulator::slotFinished() +{ + mFinished = true; +} + +} // namespace MimeTypeUtils +} // namespace Gwenview diff --git a/gwenview/lib/mimetypeutils.h b/gwenview/lib/mimetypeutils.h new file mode 100644 index 00000000..319bbfd6 --- /dev/null +++ b/gwenview/lib/mimetypeutils.h @@ -0,0 +1,70 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab +/* +Gwenview - A simple image viewer for KDE +Copyright 2006 Aurelien Gateau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef MIMETYPEUTILS_H +#define MIMETYPEUTILS_H + +#include + +// Local +class QStringList; + +class KFileItem; +class KUrl; + +namespace Gwenview +{ + +namespace MimeTypeUtils +{ + +GWENVIEWLIB_EXPORT const QStringList& rasterImageMimeTypes(); +GWENVIEWLIB_EXPORT const QStringList& svgImageMimeTypes(); +GWENVIEWLIB_EXPORT const QStringList& imageMimeTypes(); + +GWENVIEWLIB_EXPORT QString urlMimeType(const KUrl&); + +/** + * Finds mimetype by downloading the beginning of the url + */ +GWENVIEWLIB_EXPORT QString urlMimeTypeByContent(const KUrl&); + +enum Kind { + KIND_UNKNOWN = 0, + KIND_DIR = 1, + KIND_ARCHIVE = 1 << 2, + KIND_FILE = 1 << 3, + KIND_RASTER_IMAGE = 1 << 4, + KIND_SVG_IMAGE = 1 << 5, + KIND_VIDEO = 1 << 6 +}; +Q_DECLARE_FLAGS(Kinds, Kind) + +GWENVIEWLIB_EXPORT Kind fileItemKind(const KFileItem&); +GWENVIEWLIB_EXPORT Kind urlKind(const KUrl&); +GWENVIEWLIB_EXPORT Kind mimeTypeKind(const QString& mimeType); + +} // namespace MimeTypeUtils + +} // namespace Gwenview + +Q_DECLARE_OPERATORS_FOR_FLAGS(Gwenview::MimeTypeUtils::Kinds) + +#endif /* MIMETYPEUTILS_H */ diff --git a/gwenview/lib/mimetypeutils_p.h b/gwenview/lib/mimetypeutils_p.h new file mode 100644 index 00000000..09294be4 --- /dev/null +++ b/gwenview/lib/mimetypeutils_p.h @@ -0,0 +1,71 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef MIMETYPEUTILS_P_H +#define MIMETYPEUTILS_P_H + +// Qt +#include +#include + +namespace KIO +{ +class Job; +class TransferJob; +} + +namespace Gwenview +{ + +namespace MimeTypeUtils +{ +/** + * A simple helper class used to determine mime type in some extreme cases + * where we need to download the url header + */ +class DataAccumulator : public QObject +{ + Q_OBJECT +public: + DataAccumulator(KIO::TransferJob* job); + + const QByteArray& data() const { + return mData; + } + + bool finished() const + { + return mFinished; + } + +private Q_SLOTS: + void slotDataReceived(KIO::Job*, const QByteArray& data); + void slotFinished(); + +private: + QByteArray mData; + bool mFinished; +}; + +} // namespace MimeTypeUtils + +} // namespace Gwenview + +#endif /* MIMETYPEUTILS_P_H */ diff --git a/gwenview/lib/mousewheelbehavior.h b/gwenview/lib/mousewheelbehavior.h new file mode 100644 index 00000000..c91029a9 --- /dev/null +++ b/gwenview/lib/mousewheelbehavior.h @@ -0,0 +1,44 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef MOUSEWHEELBEHAVIOR_H +#define MOUSEWHEELBEHAVIOR_H + +// Qt + +// KDE + +// Local + +namespace Gwenview +{ + +namespace MouseWheelBehavior +{ +enum Enum { + Scroll, + Browse +}; + +} // namespace MouseWheelBehavior + +} // namespace Gwenview + +#endif /* MOUSEWHEELBEHAVIOR_H */ diff --git a/gwenview/lib/orientation.h b/gwenview/lib/orientation.h new file mode 100644 index 00000000..6a1d1d60 --- /dev/null +++ b/gwenview/lib/orientation.h @@ -0,0 +1,57 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef ORIENTATION_H +#define ORIENTATION_H + +namespace Gwenview +{ + +/* Explanation extracted from http://sylvana.net/jpegcrop/exif_orientation.html + + For convenience, here is what the letter F would look like if it were tagged +correctly and displayed by a program that ignores the orientation tag (thus +showing the stored image): + + 1 2 3 4 5 6 7 8 + +888888 888888 88 88 8888888888 88 88 8888888888 +88 88 88 88 88 88 88 88 88 88 88 88 +8888 8888 8888 8888 88 8888888888 8888888888 88 +88 88 88 88 +88 88 888888 888888 + +*/ + +enum Orientation { + NOT_AVAILABLE = 0, + NORMAL = 1, + HFLIP = 2, + ROT_180 = 3, + VFLIP = 4, + TRANSPOSE = 5, + ROT_90 = 6, + TRANSVERSE = 7, + ROT_270 = 8 +}; + +} + +#endif diff --git a/gwenview/lib/paintutils.cpp b/gwenview/lib/paintutils.cpp new file mode 100644 index 00000000..2902bcaf --- /dev/null +++ b/gwenview/lib/paintutils.cpp @@ -0,0 +1,156 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "paintutils.h" + +#include + +// Qt +#include +#include +#include +#include + +namespace Gwenview +{ + +namespace PaintUtils +{ + +// Copied from KFileItemDelegate +QPainterPath roundedRectangle(const QRectF &rect, qreal radius) +{ + QPainterPath path(QPointF(rect.left(), rect.top() + radius)); + path.quadTo(rect.left(), rect.top(), rect.left() + radius, rect.top()); // Top left corner + path.lineTo(rect.right() - radius, rect.top()); // Top side + path.quadTo(rect.right(), rect.top(), rect.right(), rect.top() + radius); // Top right corner + path.lineTo(rect.right(), rect.bottom() - radius); // Right side + path.quadTo(rect.right(), rect.bottom(), rect.right() - radius, rect.bottom()); // Bottom right corner + path.lineTo(rect.left() + radius, rect.bottom()); // Bottom side + path.quadTo(rect.left(), rect.bottom(), rect.left(), rect.bottom() - radius); // Bottom left corner + path.closeSubpath(); + + return path; +} + +QPixmap generateFuzzyRect(const QSize& size, const QColor& color, int radius) +{ + QPixmap pix(size); + const QColor transparent(0, 0, 0, 0); + pix.fill(transparent); + + QPainter painter(&pix); + painter.setRenderHint(QPainter::Antialiasing, true); + + // Fill middle + painter.fillRect(pix.rect().adjusted(radius, radius, -radius, -radius), color); + + // Corners + QRadialGradient gradient; + gradient.setColorAt(0, color); + gradient.setColorAt(1, transparent); + gradient.setRadius(radius); + QPoint center; + + // Top Left + center = QPoint(radius, radius); + gradient.setCenter(center); + gradient.setFocalPoint(center); + painter.fillRect(0, 0, radius, radius, gradient); + + // Top right + center = QPoint(size.width() - radius, radius); + gradient.setCenter(center); + gradient.setFocalPoint(center); + painter.fillRect(center.x(), 0, radius, radius, gradient); + + // Bottom left + center = QPoint(radius, size.height() - radius); + gradient.setCenter(center); + gradient.setFocalPoint(center); + painter.fillRect(0, center.y(), radius, radius, gradient); + + // Bottom right + center = QPoint(size.width() - radius, size.height() - radius); + gradient.setCenter(center); + gradient.setFocalPoint(center); + painter.fillRect(center.x(), center.y(), radius, radius, gradient); + + // Borders + QLinearGradient linearGradient; + linearGradient.setColorAt(0, color); + linearGradient.setColorAt(1, transparent); + + // Top + linearGradient.setStart(0, radius); + linearGradient.setFinalStop(0, 0); + painter.fillRect(radius, 0, size.width() - 2 * radius, radius, linearGradient); + + // Bottom + linearGradient.setStart(0, size.height() - radius); + linearGradient.setFinalStop(0, size.height()); + painter.fillRect(radius, int(linearGradient.start().y()), size.width() - 2 * radius, radius, linearGradient); + + // Left + linearGradient.setStart(radius, 0); + linearGradient.setFinalStop(0, 0); + painter.fillRect(0, radius, radius, size.height() - 2 * radius, linearGradient); + + // Right + linearGradient.setStart(size.width() - radius, 0); + linearGradient.setFinalStop(size.width(), 0); + painter.fillRect(int(linearGradient.start().x()), radius, radius, size.height() - 2 * radius, linearGradient); + return pix; +} + +QColor adjustedHsv(const QColor& color, int deltaH, int deltaS, int deltaV) +{ + int hue, saturation, value; + color.getHsv(&hue, &saturation, &value); + return QColor::fromHsv( + qBound(0, hue + deltaH, 359), + qBound(0, saturation + deltaS, 255), + qBound(0, value + deltaV, 255) + ); +} + +QColor alphaAdjustedF(const QColor& color, qreal alphaF) +{ + QColor tmp = color; + tmp.setAlphaF(alphaF); + return tmp; +} + +QRect containingRect(const QRectF& rectF) +{ + return QRect( + QPoint( + qRound(floor(rectF.left())), + qRound(floor(rectF.top())) + ), + QPoint( + qRound(ceil(rectF.right() - 1.)), + qRound(ceil(rectF.bottom() - 1.)) + ) + ); + // Note: QRect::right = left + width - 1, while QRectF::right = left + width +} + +} // namespace +} // namespace diff --git a/gwenview/lib/paintutils.h b/gwenview/lib/paintutils.h new file mode 100644 index 00000000..97ec0d5e --- /dev/null +++ b/gwenview/lib/paintutils.h @@ -0,0 +1,74 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef PAINTUTILS_H +#define PAINTUTILS_H + +#include + +class QColor; +class QPainterPath; +class QPixmap; +class QRect; +class QRectF; +class QSize; + +namespace Gwenview +{ + +/** + * A collection of independent painting functions + */ +namespace PaintUtils +{ + +/** + * Returns a rounded-corner version of @rect. Corner radius is @p radius. + * (Copied from KFileItemDelegate) + */ +GWENVIEWLIB_EXPORT QPainterPath roundedRectangle(const QRectF& rect, qreal radius); + +/** + * Generates a pixmap of size @p size, filled with @p color, whose borders have + * been blurred by @p radius pixels. + */ +GWENVIEWLIB_EXPORT QPixmap generateFuzzyRect(const QSize& size, const QColor& color, int radius); + +/** + * Returns a modified version of @p color, where hue, saturation and value have + * been adjusted according to @p deltaH, @p deltaS and @p deltaV. + */ +GWENVIEWLIB_EXPORT QColor adjustedHsv(const QColor& color, int deltaH, int deltaS, int deltaV); + +/** + * Returns a modified version of @p color, where alpha has been set to @p + * alphaF. + */ +GWENVIEWLIB_EXPORT QColor alphaAdjustedF(const QColor& color, qreal alphaF); + +/** + * Returns the smallest QRect which contains @p rectF + */ +GWENVIEWLIB_EXPORT QRect containingRect(const QRectF& rectF); + +} // namespace + +} // namespace + +#endif /* PAINTUTILS_H */ diff --git a/gwenview/lib/placetreemodel.cpp b/gwenview/lib/placetreemodel.cpp new file mode 100644 index 00000000..b036fa56 --- /dev/null +++ b/gwenview/lib/placetreemodel.cpp @@ -0,0 +1,380 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "placetreemodel.moc" + +// Qt + +// KDE +#include +#include +#include + +// Local +#include + +namespace Gwenview +{ + +/** + * Here is how the mapping work: + * + * Place1 Node(dirModel1, KUrl()) + * Photos Node(dirModel1, place1Url) + * 2008 Node(dirModel1, place1Url/Photos) + * 2009 Node(dirModel1, place1Url/Photos) + * + * Place2 Node(dirModel2, KUrl()) + * ... + * Node contains the parent url, not the url of the node itself, because + * for some unknown reason when accessing rows from a slot connected to + * rowsInserted(), the rows are unsorted, they appear in KDirModel natural + * order. Further access to the rows are correctly sorted! This confuses + * QTreeView a lot (symptoms are mixed tooltips, filled nodes appearing + * empty on first expand) + * + * I could not determine whether it's a bug or not, and if it's in my model + * code, in QSortFilterProxyModel or somewhere else. + */ + +struct Node +{ + Node() + : model(0) + {} + + Node(SortedDirModel* _model, const KUrl& _parentUrl) + : model(_model) + , parentUrl(_parentUrl) + {} + + SortedDirModel* model; + KUrl parentUrl; + + bool isPlace() const + { + return !parentUrl.isValid(); + } +}; + +typedef QHash NodeHash; +typedef QMap NodeHashMap; + +struct PlaceTreeModelPrivate +{ + PlaceTreeModel* q; + KFilePlacesModel* mPlacesModel; + QList mDirModels; + mutable NodeHashMap mNodes; + + Node nodeForIndex(const QModelIndex& index) const + { + Q_ASSERT(index.isValid()); + Q_ASSERT(index.internalPointer()); + return *static_cast(index.internalPointer()); + } + + Node* createNode(SortedDirModel* dirModel, const KUrl& parentUrl) const + { + NodeHashMap::iterator nhmIt = mNodes.find(dirModel); + if (nhmIt == mNodes.end()) { + nhmIt = mNodes.insert(dirModel, new NodeHash); + } + NodeHash* nodeHash = nhmIt.value(); + + NodeHash::iterator nhIt = nodeHash->find(parentUrl); + if (nhIt == nodeHash->end()) { + nhIt = nodeHash->insert(parentUrl, new Node(dirModel, parentUrl)); + } + return nhIt.value(); + } + + QModelIndex createIndexForDir(SortedDirModel* dirModel, const KUrl& url) const + { + QModelIndex dirIndex = dirModel->indexForUrl(url); + QModelIndex parentDirIndex = dirIndex.parent(); + KUrl parentUrl; + if (parentDirIndex.isValid()) { + parentUrl = dirModel->urlForIndex(parentDirIndex); + } else { + parentUrl = dirModel->dirLister()->url(); + } + return createIndexForDirChild(dirModel, parentUrl, dirIndex.row(), dirIndex.column()); + } + + QModelIndex createIndexForDirChild(SortedDirModel* dirModel, const KUrl& parentUrl, int row, int column) const + { + Q_ASSERT(parentUrl.isValid()); + Node* node = createNode(dirModel, parentUrl); + return q->createIndex(row, column, node); + } + + QModelIndex createIndexForPlace(SortedDirModel* dirModel) const + { + int row = mDirModels.indexOf(dirModel); + Q_ASSERT(row != -1); + Node* node = createNode(dirModel, KUrl()); + return q->createIndex(row, 0, node); + } + + QModelIndex dirIndexForNode(const Node& node, const QModelIndex& index) const + { + if (node.isPlace()) { + return QModelIndex(); + } + Q_ASSERT(node.parentUrl.isValid()); + const QModelIndex parentDirIndex = node.model->indexForUrl(node.parentUrl); + return node.model->index(index.row(), index.column(), parentDirIndex); + } +}; + +PlaceTreeModel::PlaceTreeModel(QObject* parent) +: QAbstractItemModel(parent) +, d(new PlaceTreeModelPrivate) +{ + d->q = this; + + d->mPlacesModel = new KFilePlacesModel(this); + connect(d->mPlacesModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + SLOT(slotPlacesRowsInserted(QModelIndex,int,int))); + connect(d->mPlacesModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + SLOT(slotPlacesRowsAboutToBeRemoved(QModelIndex,int,int))); + + // Bootstrap + slotPlacesRowsInserted(QModelIndex(), 0, d->mPlacesModel->rowCount() - 1); +} + +PlaceTreeModel::~PlaceTreeModel() +{ + Q_FOREACH(NodeHash * nodeHash, d->mNodes) { + qDeleteAll(*nodeHash); + } + qDeleteAll(d->mNodes); + delete d; +} + +int PlaceTreeModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +QVariant PlaceTreeModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + QVariant value; + const Node node = d->nodeForIndex(index); + if (node.isPlace()) { + const QModelIndex placesIndex = d->mPlacesModel->index(index.row(), index.column()); + value = d->mPlacesModel->data(placesIndex, role); + } else { + const QModelIndex dirIndex = d->dirIndexForNode(node, index); + value = node.model->data(dirIndex, role); + } + return value; +} + +QModelIndex PlaceTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + if (column != 0) { + return QModelIndex(); + } + if (!parent.isValid()) { + // User wants to create a places index + if (0 <= row && row < d->mDirModels.size()) { + SortedDirModel* dirModel = d->mDirModels[row]; + return d->createIndexForPlace(dirModel); + } else { + return QModelIndex(); + } + } + + Node parentNode = d->nodeForIndex(parent); + QModelIndex parentDirIndex = d->dirIndexForNode(parentNode, parent); + + SortedDirModel* dirModel = parentNode.model; + KUrl parentUrl = dirModel->urlForIndex(parentDirIndex); + if (!parentUrl.isValid()) { + // parent is a place + parentUrl = dirModel->dirLister()->url(); + if (!parentUrl.isValid()) { + return QModelIndex(); + } + } + return d->createIndexForDirChild(dirModel, parentUrl, row, column); +} + +QModelIndex PlaceTreeModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) { + return QModelIndex(); + } + const Node node = d->nodeForIndex(index); + if (node.isPlace()) { + return QModelIndex(); + } + if (node.parentUrl == node.model->dirLister()->url()) { + // index is a direct child of a place + return d->createIndexForPlace(node.model); + } + return d->createIndexForDir(node.model, node.parentUrl); +} + +int PlaceTreeModel::rowCount(const QModelIndex& index) const +{ + if (!index.isValid()) { + // index is the invisible root item + return d->mDirModels.size(); + } + + // index is a place or a dir + const Node node = d->nodeForIndex(index); + const QModelIndex dirIndex = d->dirIndexForNode(node, index); + return node.model->rowCount(dirIndex); +} + +bool PlaceTreeModel::hasChildren(const QModelIndex& index) const +{ + if (!index.isValid()) { + return true; + } + const Node node = d->nodeForIndex(index); + if (node.isPlace()) { + return true; + } + const QModelIndex dirIndex = d->dirIndexForNode(node, index); + return node.model->hasChildren(dirIndex); +} + +bool PlaceTreeModel::canFetchMore(const QModelIndex& parent) const +{ + if (!parent.isValid()) { + return d->mPlacesModel->canFetchMore(QModelIndex()); + } + const Node node = d->nodeForIndex(parent); + if (!node.model->dirLister()->url().isValid()) { + // Special case to avoid calling openUrl on all places at startup + return true; + } + const QModelIndex dirIndex = d->dirIndexForNode(node, parent); + return node.model->canFetchMore(dirIndex); +} + +void PlaceTreeModel::fetchMore(const QModelIndex& parent) +{ + if (!parent.isValid()) { + d->mPlacesModel->fetchMore(QModelIndex()); + return; + } + const Node node = d->nodeForIndex(parent); + if (!node.model->dirLister()->url().isValid()) { + QModelIndex placeIndex = d->mPlacesModel->index(parent.row(), parent.column()); + KUrl url = d->mPlacesModel->url(placeIndex); + node.model->dirLister()->openUrl(url, KDirLister::Keep); + return; + } + const QModelIndex dirIndex = d->dirIndexForNode(node, parent); + node.model->fetchMore(dirIndex); +} + +void PlaceTreeModel::slotPlacesRowsInserted(const QModelIndex& /*parent*/, int start, int end) +{ + beginInsertRows(QModelIndex(), start, end); + for (int row = start; row <= end; ++row) { + SortedDirModel* dirModel = new SortedDirModel(this); + connect(dirModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + SLOT(slotDirRowsAboutToBeInserted(QModelIndex,int,int))); + connect(dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + SLOT(slotDirRowsInserted(QModelIndex,int,int))); + connect(dirModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + SLOT(slotDirRowsAboutToBeRemoved(QModelIndex,int,int))); + connect(dirModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + SLOT(slotDirRowsRemoved(QModelIndex,int,int))); + + d->mDirModels.insert(row, dirModel); + KDirLister* lister = dirModel->dirLister(); + lister->setDirOnlyMode(true); + } + endInsertRows(); +} + +void PlaceTreeModel::slotPlacesRowsAboutToBeRemoved(const QModelIndex&, int start, int end) +{ + beginRemoveRows(QModelIndex(), start, end); + for (int row = end; row >= start; --row) { + SortedDirModel* dirModel = d->mDirModels.takeAt(row); + delete d->mNodes.take(dirModel); + delete dirModel; + } + endRemoveRows(); +} + +void PlaceTreeModel::slotDirRowsAboutToBeInserted(const QModelIndex& parentDirIndex, int start, int end) +{ + SortedDirModel* dirModel = static_cast(sender()); + QModelIndex parentIndex; + if (parentDirIndex.isValid()) { + KUrl url = dirModel->urlForIndex(parentDirIndex); + parentIndex = d->createIndexForDir(dirModel, url); + } else { + parentIndex = d->createIndexForPlace(dirModel); + } + beginInsertRows(parentIndex, start, end); +} + +void PlaceTreeModel::slotDirRowsInserted(const QModelIndex&, int, int) +{ + endInsertRows(); +} + +void PlaceTreeModel::slotDirRowsAboutToBeRemoved(const QModelIndex& parentDirIndex, int start, int end) +{ + SortedDirModel* dirModel = static_cast(sender()); + QModelIndex parentIndex; + if (parentDirIndex.isValid()) { + KUrl url = dirModel->urlForIndex(parentDirIndex); + parentIndex = d->createIndexForDir(dirModel, url); + } else { + parentIndex = d->createIndexForPlace(dirModel); + } + beginRemoveRows(parentIndex, start, end); +} + +void PlaceTreeModel::slotDirRowsRemoved(const QModelIndex&, int, int) +{ + endRemoveRows(); +} + +KUrl PlaceTreeModel::urlForIndex(const QModelIndex& index) const +{ + const Node node = d->nodeForIndex(index); + if (node.isPlace()) { + QModelIndex placeIndex = d->mPlacesModel->index(index.row(), 0); + return d->mPlacesModel->url(placeIndex); + } + + const QModelIndex parentDirIndex = node.model->indexForUrl(node.parentUrl); + const QModelIndex dirIndex = node.model->index(index.row(), index.column(), parentDirIndex); + return node.model->urlForIndex(dirIndex); +} + +} // namespace diff --git a/gwenview/lib/placetreemodel.h b/gwenview/lib/placetreemodel.h new file mode 100644 index 00000000..7ae6eb88 --- /dev/null +++ b/gwenview/lib/placetreemodel.h @@ -0,0 +1,72 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef PLACETREEMODEL_H +#define PLACETREEMODEL_H + +#include + +// Qt +#include + +// KDE + +// Local + +class KUrl; + +namespace Gwenview +{ + +struct PlaceTreeModelPrivate; +class GWENVIEWLIB_EXPORT PlaceTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + PlaceTreeModel(QObject*); + ~PlaceTreeModel(); + + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex& index) const; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; + virtual bool hasChildren(const QModelIndex& parent) const; + virtual bool canFetchMore(const QModelIndex& parent) const; + virtual void fetchMore(const QModelIndex& parent); + + KUrl urlForIndex(const QModelIndex&) const; + +private Q_SLOTS: + void slotPlacesRowsInserted(const QModelIndex&, int start, int end); + void slotPlacesRowsAboutToBeRemoved(const QModelIndex&, int start, int end); + void slotDirRowsAboutToBeInserted(const QModelIndex&, int start, int end); + void slotDirRowsInserted(const QModelIndex&, int start, int end); + void slotDirRowsAboutToBeRemoved(const QModelIndex&, int start, int end); + void slotDirRowsRemoved(const QModelIndex&, int start, int end); + +private: + friend struct PlaceTreeModelPrivate; + PlaceTreeModelPrivate* const d; +}; + +} // namespace + +#endif /* PLACETREEMODEL_H */ diff --git a/gwenview/lib/preferredimagemetainfomodel.cpp b/gwenview/lib/preferredimagemetainfomodel.cpp new file mode 100644 index 00000000..216b148c --- /dev/null +++ b/gwenview/lib/preferredimagemetainfomodel.cpp @@ -0,0 +1,143 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "preferredimagemetainfomodel.h" + +// Qt +#include + +// KDE +#include + +namespace Gwenview +{ + +struct PreferredImageMetaInfoModelPrivate +{ + const ImageMetaInfoModel* mModel; + QStringList mPreferredMetaInfoKeyList; + + QVariant checkStateData(const QModelIndex& sourceIndex) const + { + if (sourceIndex.parent().isValid() && sourceIndex.column() == 0) { + QString key = mModel->keyForIndex(sourceIndex); + bool checked = mPreferredMetaInfoKeyList.contains(key); + return QVariant(checked ? Qt::Checked : Qt::Unchecked); + } else { + return QVariant(); + } + } + + void sortPreferredMetaInfoKeyList() + { + QStringList sortedList; + int groupCount = mModel->rowCount(); + for (int groupRow = 0; groupRow < groupCount; ++groupRow) { + QModelIndex groupIndex = mModel->index(groupRow, 0); + int keyCount = mModel->rowCount(groupIndex); + for (int keyRow = 0; keyRow < keyCount; ++keyRow) { + QModelIndex keyIndex = mModel->index(keyRow, 0, groupIndex); + QString key = mModel->keyForIndex(keyIndex); + if (mPreferredMetaInfoKeyList.contains(key)) { + sortedList << key; + } + } + } + mPreferredMetaInfoKeyList = sortedList; + } +}; + +PreferredImageMetaInfoModel::PreferredImageMetaInfoModel(ImageMetaInfoModel* model, const QStringList& list) +: d(new PreferredImageMetaInfoModelPrivate) +{ + d->mModel = model; + setSourceModel(model); + sort(0); + setDynamicSortFilter(true); + d->mPreferredMetaInfoKeyList = list; +} + +PreferredImageMetaInfoModel::~PreferredImageMetaInfoModel() +{ + delete d; +} + +Qt::ItemFlags PreferredImageMetaInfoModel::flags(const QModelIndex& index) const +{ + QModelIndex sourceIndex = mapToSource(index); + Qt::ItemFlags fl = d->mModel->flags(sourceIndex); + if (sourceIndex.parent().isValid() && sourceIndex.column() == 0) { + fl |= Qt::ItemIsUserCheckable; + } + return fl; +} + +QVariant PreferredImageMetaInfoModel::data(const QModelIndex& index, int role) const +{ + QModelIndex sourceIndex = mapToSource(index); + if (!sourceIndex.isValid()) { + return QVariant(); + } + + switch (role) { + case Qt::CheckStateRole: + return d->checkStateData(sourceIndex); + + default: + return d->mModel->data(sourceIndex, role); + } +} + +bool PreferredImageMetaInfoModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + QModelIndex sourceIndex = mapToSource(index); + if (role != Qt::CheckStateRole) { + return false; + } + + if (!sourceIndex.parent().isValid()) { + return false; + } + + QString key = d->mModel->keyForIndex(sourceIndex); + if (value == Qt::Checked) { + d->mPreferredMetaInfoKeyList << key; + d->sortPreferredMetaInfoKeyList(); + } else { + d->mPreferredMetaInfoKeyList.removeAll(key); + } + emit preferredMetaInfoKeyListChanged(d->mPreferredMetaInfoKeyList); + emit dataChanged(index, index); + return true; +} + +bool PreferredImageMetaInfoModel::lessThan(const QModelIndex& left, const QModelIndex& right) const +{ + if (!left.parent().isValid()) { + // Keep root entries in insertion order + return left.row() < right.row(); + } else { + // Sort leaf entries alphabetically + return QSortFilterProxyModel::lessThan(left, right); + } +} + +} // namespace diff --git a/gwenview/lib/preferredimagemetainfomodel.h b/gwenview/lib/preferredimagemetainfomodel.h new file mode 100644 index 00000000..7bd9be67 --- /dev/null +++ b/gwenview/lib/preferredimagemetainfomodel.h @@ -0,0 +1,64 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef PREFERREDIMAGEMETAINFOMODEL_H +#define PREFERREDIMAGEMETAINFOMODEL_H + +#include + +// Qt +#include + +// Local +#include + +namespace Gwenview +{ + +/** + * This model uses an instance of ImageMetaInfoModel to make it possible to + * select your preferred image metainfo keys by checking them. + */ +struct PreferredImageMetaInfoModelPrivate; +class GWENVIEWLIB_EXPORT PreferredImageMetaInfoModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + PreferredImageMetaInfoModel(ImageMetaInfoModel* model, const QStringList& list); + ~PreferredImageMetaInfoModel(); + + virtual QVariant data(const QModelIndex&, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role); + virtual Qt::ItemFlags flags(const QModelIndex& index) const; + +Q_SIGNALS: + void preferredMetaInfoKeyListChanged(const QStringList&); + +protected: + bool lessThan(const QModelIndex& left, const QModelIndex& right) const; + +private: + PreferredImageMetaInfoModelPrivate* const d; + friend struct PreferredImageMetaInfoModelPrivate; +}; + +} // namespace + +#endif /* PREFERREDIMAGEMETAINFOMODEL_H */ diff --git a/gwenview/lib/print/printhelper.cpp b/gwenview/lib/print/printhelper.cpp new file mode 100644 index 00000000..0afe86eb --- /dev/null +++ b/gwenview/lib/print/printhelper.cpp @@ -0,0 +1,150 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "printhelper.h" + +// STD +#include + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include + +// Local +#include "printoptionspage.h" + +namespace Gwenview +{ + +struct PrintHelperPrivate +{ + QWidget* mParent; + + QSize adjustSize(PrintOptionsPage* optionsPage, Document::Ptr doc, int printerResolution, const QSize & viewportSize) + { + QSize size = doc->size(); + PrintOptionsPage::ScaleMode scaleMode = optionsPage->scaleMode(); + if (scaleMode == PrintOptionsPage::ScaleToPage) { + bool imageBiggerThanPaper = + size.width() > viewportSize.width() + || size.height() > viewportSize.height(); + + if (imageBiggerThanPaper || optionsPage->enlargeSmallerImages()) { + size.scale(viewportSize, Qt::KeepAspectRatio); + } + + } else if (scaleMode == PrintOptionsPage::ScaleToCustomSize) { + double wImg = optionsPage->scaleWidth(); + double hImg = optionsPage->scaleHeight(); + size.setWidth(int(wImg * printerResolution)); + size.setHeight(int(hImg * printerResolution)); + + } else { + // No scale + const double INCHES_PER_METER = 100. / 2.54; + int dpmX = doc->image().dotsPerMeterX(); + int dpmY = doc->image().dotsPerMeterY(); + if (dpmX > 0 && dpmY > 0) { + double wImg = double(size.width()) / double(dpmX) * INCHES_PER_METER; + double hImg = double(size.height()) / double(dpmY) * INCHES_PER_METER; + size.setWidth(int(wImg * printerResolution)); + size.setHeight(int(hImg * printerResolution)); + } + } + return size; + } + + QPoint adjustPosition(PrintOptionsPage* optionsPage, const QSize& imageSize, const QSize & viewportSize) + { + Qt::Alignment alignment = optionsPage->alignment(); + int posX, posY; + + if (alignment & Qt::AlignLeft) { + posX = 0; + } else if (alignment & Qt::AlignHCenter) { + posX = (viewportSize.width() - imageSize.width()) / 2; + } else { + posX = viewportSize.width() - imageSize.width(); + } + + if (alignment & Qt::AlignTop) { + posY = 0; + } else if (alignment & Qt::AlignVCenter) { + posY = (viewportSize.height() - imageSize.height()) / 2; + } else { + posY = viewportSize.height() - imageSize.height(); + } + + return QPoint(posX, posY); + } +}; + +PrintHelper::PrintHelper(QWidget* parent) +: d(new PrintHelperPrivate) +{ + d->mParent = parent; +} + +PrintHelper::~PrintHelper() +{ + delete d; +} + +void PrintHelper::print(Document::Ptr doc) +{ + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + QPrinter printer; + + PrintOptionsPage* optionsPage = new PrintOptionsPage(doc->size()); + optionsPage->loadConfig(); + + std::auto_ptr dialog( + KdePrint::createPrintDialog(&printer, + QList() << optionsPage, + d->mParent) + ); + dialog->setWindowTitle(i18n("Print Image")); + bool wantToPrint = dialog->exec(); + + optionsPage->saveConfig(); + if (!wantToPrint) { + return; + } + + QPainter painter(&printer); + QRect rect = painter.viewport(); + QSize size = d->adjustSize(optionsPage, doc, printer.resolution(), rect.size()); + QPoint pos = d->adjustPosition(optionsPage, size, rect.size()); + painter.setViewport(pos.x(), pos.y(), size.width(), size.height()); + + QImage image = doc->image(); + painter.setWindow(image.rect()); + painter.drawImage(0, 0, image); +} + +} // namespace diff --git a/gwenview/lib/print/printhelper.h b/gwenview/lib/print/printhelper.h new file mode 100644 index 00000000..d6710af3 --- /dev/null +++ b/gwenview/lib/print/printhelper.h @@ -0,0 +1,53 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef PRINTHELPER_H +#define PRINTHELPER_H + +#include + +// Qt + +// KDE + +// Local +#include + +class QWidget; + +namespace Gwenview +{ + +struct PrintHelperPrivate; +class GWENVIEWLIB_EXPORT PrintHelper +{ +public: + PrintHelper(QWidget* parent); + ~PrintHelper(); + + void print(Document::Ptr); + +private: + PrintHelperPrivate* const d; +}; + +} // namespace + +#endif /* PRINTHELPER_H */ diff --git a/gwenview/lib/print/printoptionspage.cpp b/gwenview/lib/print/printoptionspage.cpp new file mode 100644 index 00000000..c2c451d3 --- /dev/null +++ b/gwenview/lib/print/printoptionspage.cpp @@ -0,0 +1,231 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "printoptionspage.moc" + +// Qt +#include +#include +#include + +// KDE +#include + +// Local +#include +#include +#include + +namespace Gwenview +{ + +static inline double unitToInches(PrintOptionsPage::Unit unit) +{ + if (unit == PrintOptionsPage::Inches) { + return 1.; + } else if (unit == PrintOptionsPage::Centimeters) { + return 1 / 2.54; + } else { // Millimeters + return 1 / 25.4; + } +} + +struct PrintOptionsPagePrivate : public Ui_PrintOptionsPage +{ + QSize mImageSize; + QButtonGroup mScaleGroup; + QButtonGroup mPositionGroup; + KConfigDialogManager* mConfigDialogManager; + + void initPositionFrame() + { + mPositionFrame->setStyleSheet( + "QFrame {" + " background-color: palette(mid);" + " border: 1px solid palette(dark);" + "}" + "QToolButton {" + " border: none;" + " background: palette(base);" + "}" + "QToolButton:hover {" + " background: palette(alternate-base);" + " border: 1px solid palette(highlight);" + "}" + "QToolButton:checked {" + " background-color: palette(highlight);" + "}" + ); + + QGridLayout* layout = new QGridLayout(mPositionFrame); + layout->setMargin(0); + layout->setSpacing(1); + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) { + QToolButton* button = new QToolButton(mPositionFrame); + button->setFixedSize(40, 40); + button->setCheckable(true); + layout->addWidget(button, row, col); + + Qt::Alignment alignment; + if (row == 0) { + alignment = Qt::AlignTop; + } else if (row == 1) { + alignment = Qt::AlignVCenter; + } else { + alignment = Qt::AlignBottom; + } + if (col == 0) { + alignment |= Qt::AlignLeft; + } else if (col == 1) { + alignment |= Qt::AlignHCenter; + } else { + alignment |= Qt::AlignRight; + } + + mPositionGroup.addButton(button, int(alignment)); + } + } + } +}; + +PrintOptionsPage::PrintOptionsPage(const QSize& imageSize) +: QWidget() +, d(new PrintOptionsPagePrivate) +{ + d->setupUi(this); + d->mImageSize = imageSize; + d->mConfigDialogManager = new KConfigDialogManager(this, GwenviewConfig::self()); + + d->initPositionFrame(); + + d->mScaleGroup.addButton(d->mNoScale, NoScale); + d->mScaleGroup.addButton(d->mScaleToPage, ScaleToPage); + d->mScaleGroup.addButton(d->mScaleTo, ScaleToCustomSize); + + connect(d->kcfg_PrintWidth, SIGNAL(valueChanged(double)), + SLOT(adjustHeightToRatio())); + + connect(d->kcfg_PrintHeight, SIGNAL(valueChanged(double)), + SLOT(adjustWidthToRatio())); + + connect(d->kcfg_PrintKeepRatio, SIGNAL(toggled(bool)), + SLOT(adjustHeightToRatio())); +} + +PrintOptionsPage::~PrintOptionsPage() +{ + delete d; +} + +Qt::Alignment PrintOptionsPage::alignment() const +{ + int id = d->mPositionGroup.checkedId(); + kDebug() << "alignment=" << id; + return Qt::Alignment(id); +} + +PrintOptionsPage::ScaleMode PrintOptionsPage::scaleMode() const +{ + return PrintOptionsPage::ScaleMode(d->mScaleGroup.checkedId()); +} + +bool PrintOptionsPage::enlargeSmallerImages() const +{ + return d->kcfg_PrintEnlargeSmallerImages->isChecked(); +} + +PrintOptionsPage::Unit PrintOptionsPage::scaleUnit() const +{ + return PrintOptionsPage::Unit(d->kcfg_PrintUnit->currentIndex()); +} + +double PrintOptionsPage::scaleWidth() const +{ + return d->kcfg_PrintWidth->value() * unitToInches(scaleUnit()); +} + +double PrintOptionsPage::scaleHeight() const +{ + return d->kcfg_PrintHeight->value() * unitToInches(scaleUnit()); +} + +void PrintOptionsPage::adjustWidthToRatio() +{ + if (!d->kcfg_PrintKeepRatio->isChecked()) { + return; + } + double width = d->mImageSize.width() * d->kcfg_PrintHeight->value() / d->mImageSize.height(); + + SignalBlocker blocker(d->kcfg_PrintWidth); + d->kcfg_PrintWidth->setValue(width ? width : 1.); +} + +void PrintOptionsPage::adjustHeightToRatio() +{ + if (!d->kcfg_PrintKeepRatio->isChecked()) { + return; + } + double height = d->mImageSize.height() * d->kcfg_PrintWidth->value() / d->mImageSize.width(); + + SignalBlocker blocker(d->kcfg_PrintHeight); + d->kcfg_PrintHeight->setValue(height ? height : 1.); +} + +void PrintOptionsPage::loadConfig() +{ + QAbstractButton* button; + + button = d->mPositionGroup.button(GwenviewConfig::printPosition()); + if (button) { + button->setChecked(true); + } else { + kWarning() << "Unknown button for position group"; + } + + button = d->mScaleGroup.button(GwenviewConfig::printScaleMode()); + if (button) { + button->setChecked(true); + } else { + kWarning() << "Unknown button for scale group"; + } + + d->mConfigDialogManager->updateWidgets(); + + if (d->kcfg_PrintKeepRatio->isChecked()) { + adjustHeightToRatio(); + } +} + +void PrintOptionsPage::saveConfig() +{ + int position = d->mPositionGroup.checkedId(); + GwenviewConfig::setPrintPosition(position); + + ScaleMode scaleMode = ScaleMode(d->mScaleGroup.checkedId()); + GwenviewConfig::setPrintScaleMode(scaleMode); + + d->mConfigDialogManager->updateSettings(); + + GwenviewConfig::self()->writeConfig(); +} + +} // namespace diff --git a/gwenview/lib/print/printoptionspage.h b/gwenview/lib/print/printoptionspage.h new file mode 100644 index 00000000..1f6c9d22 --- /dev/null +++ b/gwenview/lib/print/printoptionspage.h @@ -0,0 +1,75 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef PRINTOPTIONSPAGE_H +#define PRINTOPTIONSPAGE_H + +// Qt +#include + +// KDE + +// Local + +namespace Gwenview +{ + +struct PrintOptionsPagePrivate; +class PrintOptionsPage : public QWidget +{ + Q_OBJECT +public: + enum ScaleMode { + NoScale, + ScaleToPage, + ScaleToCustomSize + }; + + // Order should match the content of the unit combbox in the ui file + enum Unit { + Millimeters, + Centimeters, + Inches + }; + + PrintOptionsPage(const QSize& imageSize); + ~PrintOptionsPage(); + + Qt::Alignment alignment() const; + ScaleMode scaleMode() const; + bool enlargeSmallerImages() const; + Unit scaleUnit() const; + double scaleWidth() const; + double scaleHeight() const; + + void loadConfig(); + void saveConfig(); + +private Q_SLOTS: + void adjustWidthToRatio(); + void adjustHeightToRatio(); + +private: + PrintOptionsPagePrivate* const d; +}; + +} // namespace + +#endif /* PRINTOPTIONSPAGE_H */ diff --git a/gwenview/lib/print/printoptionspage.ui b/gwenview/lib/print/printoptionspage.ui new file mode 100644 index 00000000..3759843f --- /dev/null +++ b/gwenview/lib/print/printoptionspage.ui @@ -0,0 +1,362 @@ + + + PrintOptionsPage + + + + 0 + 0 + 511 + 252 + + + + Image Settings + + + + + + Image Position + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Scaling + + + + + + &No scaling + + + true + + + + + + + &Fit image to page + + + false + + + + + + + 6 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + false + + + Enlarge smaller images + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 24 + 21 + + + + + + + + + + &Scale to: + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + false + + + 15.000000000000000 + + + + + + + x + + + false + + + + + + + false + + + 10.000000000000000 + + + + + + + false + + + 1 + + + + Millimeters + + + + + Centimeters + + + + + Inches + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 24 + 20 + + + + + + + + + + false + + + Keep ratio + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 16 + + + + + + + + + KComboBox + QComboBox +
kcombobox.h
+
+
+ + kcombobox.h + knuminput.h + knuminput.h + kcombobox.h + + + + + mScaleTo + toggled(bool) + kcfg_PrintUnit + setEnabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + mScaleTo + toggled(bool) + kcfg_PrintKeepRatio + setEnabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + mScaleToPage + toggled(bool) + kcfg_PrintEnlargeSmallerImages + setEnabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + mScaleTo + toggled(bool) + kcfg_PrintWidth + setEnabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + mScaleTo + toggled(bool) + kcfg_PrintHeight + setEnabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + +
diff --git a/gwenview/lib/ramp.h b/gwenview/lib/ramp.h new file mode 100644 index 00000000..fc03302f --- /dev/null +++ b/gwenview/lib/ramp.h @@ -0,0 +1,65 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef RAMP_H +#define RAMP_H + +namespace Gwenview +{ + +/** + * This class maps values on a linear ramp. + * It's useful to do mappings like this: + * + * x | -oo x1 x2 +oo + * --+-------------------------------------- + * y | y1 y1 (linear ramp) y2 y2 + * + * Note that y1 can be greater than y2 if necessary + */ +class Ramp +{ +public: + Ramp(qreal x1, qreal x2, qreal y1, qreal y2) + : mX1(x1) + , mX2(x2) + , mY1(y1) + , mY2(y2) { + mK = (y2 - y1) / (x2 - x1); + } + + qreal operator()(qreal x) const + { + if (x < mX1) { + return mY1; + } + if (x > mX2) { + return mY2; + } + return mY1 + (x - mX1) * mK; + } + +private: + qreal mX1, mX2, mY1, mY2, mK; +}; + +} // namespace + +#endif /* RAMP_H */ diff --git a/gwenview/lib/recursivedirmodel.cpp b/gwenview/lib/recursivedirmodel.cpp new file mode 100644 index 00000000..d1d15c88 --- /dev/null +++ b/gwenview/lib/recursivedirmodel.cpp @@ -0,0 +1,219 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "recursivedirmodel.moc" + +// Local +#include + +// KDE +#include +#include +#include + +// Qt + +namespace Gwenview +{ + +struct RecursiveDirModelPrivate { + KDirLister* mDirLister; + + int rowForUrl(const KUrl &url) const + { + return mRowForUrl.value(url, -1); + } + + void removeAt(int row) + { + KFileItem item = mList.takeAt(row); + mRowForUrl.remove(item.url()); + + // Decrease row value for all urls after the one we removed + // ("row" now points to the item after the one we removed since we used takeAt) + const int count = mList.count(); + for (; row < count; ++row) { + KUrl url = mList.at(row).url(); + mRowForUrl[url]--; + } + } + + void addItem(const KFileItem& item) + { + mRowForUrl.insert(item.url(), mList.count()); + mList.append(item); + } + + void clear() + { + mRowForUrl.clear(); + mList.clear(); + } + + // RecursiveDirModel can only access mList through this read-only getter. + // This ensures it cannot introduce inconsistencies between mList and mRowForUrl. + const KFileItemList& list() const + { + return mList; + } + +private: + KFileItemList mList; + QHash mRowForUrl; +}; + +RecursiveDirModel::RecursiveDirModel(QObject* parent) +: QAbstractListModel(parent) +, d(new RecursiveDirModelPrivate) +{ + d->mDirLister = new KDirLister(this); + connect(d->mDirLister, SIGNAL(itemsAdded(KUrl, KFileItemList)), + SLOT(slotItemsAdded(KUrl, KFileItemList))); + connect(d->mDirLister, SIGNAL(itemsDeleted(KFileItemList)), + SLOT(slotItemsDeleted(KFileItemList))); + connect(d->mDirLister, SIGNAL(completed()), + SIGNAL(completed())); + connect(d->mDirLister, SIGNAL(clear()), + SLOT(slotCleared())); + connect(d->mDirLister, SIGNAL(clear(KUrl)), + SLOT(slotDirCleared(KUrl))); +} + +RecursiveDirModel::~RecursiveDirModel() +{ + delete d; +} + +KUrl RecursiveDirModel::url() const +{ + return d->mDirLister->url(); +} + +void RecursiveDirModel::setUrl(const KUrl& url) +{ + beginResetModel(); + d->clear(); + endResetModel(); + d->mDirLister->openUrl(url); +} + +int RecursiveDirModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } else { + return d->list().count(); + } +} + +QVariant RecursiveDirModel::data(const QModelIndex& index, int role) const +{ + if (index.parent().isValid()) { + return QVariant(); + } + KFileItem item = d->list().value(index.row()); + if (item.isNull()) { + kWarning() << "Invalid row" << index.row(); + return QVariant(); + } + switch (role) { + case Qt::DisplayRole: + return item.text(); + case Qt::DecorationRole: + return item.iconName(); + case KDirModel::FileItemRole: + return QVariant(item); + default: + kWarning() << "Unhandled role" << role; + break; + } + return QVariant(); +} + +void RecursiveDirModel::slotItemsAdded(const KUrl&, const KFileItemList& newList) +{ + QList dirUrls; + KFileItemList fileList; + Q_FOREACH(const KFileItem& item, newList) { + if (item.isFile()) { + if (d->rowForUrl(item.url()) == -1) { + fileList << item; + } + } else { + dirUrls << item.url(); + } + } + + if (!fileList.isEmpty()) { + beginInsertRows(QModelIndex(), d->list().count(), d->list().count() + fileList.count()); + Q_FOREACH(const KFileItem& item, fileList) { + d->addItem(item); + } + endInsertRows(); + } + + Q_FOREACH(const KUrl& url, dirUrls) { + d->mDirLister->openUrl(url, KDirLister::Keep); + } +} + +void RecursiveDirModel::slotItemsDeleted(const KFileItemList& list) +{ + Q_FOREACH(const KFileItem& item, list) { + if (item.isDir()) { + continue; + } + int row = d->rowForUrl(item.url()); + if (row == -1) { + kWarning() << "Received itemsDeleted for an unknown item: this should not happen!"; + GV_FATAL_FAILS; + continue; + } + beginRemoveRows(QModelIndex(), row, row); + d->removeAt(row); + endRemoveRows(); + } +} + +void RecursiveDirModel::slotCleared() +{ + if (d->list().isEmpty()) { + return; + } + beginResetModel(); + d->clear(); + endResetModel(); +} + +void RecursiveDirModel::slotDirCleared(const KUrl& dirUrl) +{ + int row; + for (row = d->list().count() - 1; row >= 0; --row) { + const KUrl url = d->list().at(row).url(); + if (dirUrl.isParentOf(url)) { + beginRemoveRows(QModelIndex(), row, row); + d->removeAt(row); + endRemoveRows(); + } + } +} + +} // namespace diff --git a/gwenview/lib/recursivedirmodel.h b/gwenview/lib/recursivedirmodel.h new file mode 100644 index 00000000..05008b3b --- /dev/null +++ b/gwenview/lib/recursivedirmodel.h @@ -0,0 +1,69 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef RECURSIVEDIRMODEL_H +#define RECURSIVEDIRMODEL_H + +// Local +#include + +// KDE +#include + +// Qt +#include + +class KUrl; + +namespace Gwenview +{ + +struct RecursiveDirModelPrivate; +/** + * Recursively list content of a dir + */ +class GWENVIEWLIB_EXPORT RecursiveDirModel : public QAbstractListModel +{ + Q_OBJECT +public: + RecursiveDirModel(QObject* parent = 0); + ~RecursiveDirModel(); + + KUrl url() const; + void setUrl(const KUrl&); + + int rowCount(const QModelIndex&) const; // reimp + QVariant data(const QModelIndex&, int role = Qt::DisplayRole) const; // reimp + +Q_SIGNALS: + void completed(); + +private Q_SLOTS: + void slotItemsAdded(const KUrl& dirUrl, const KFileItemList&); + void slotItemsDeleted(const KFileItemList&); + void slotDirCleared(const KUrl&); + void slotCleared(); +private: + RecursiveDirModelPrivate* const d; +}; + +} // namespace + +#endif /* RECURSIVEDIRMODEL_H */ diff --git a/gwenview/lib/redeyereduction/redeyereductionimageoperation.cpp b/gwenview/lib/redeyereduction/redeyereductionimageoperation.cpp new file mode 100644 index 00000000..3ce45533 --- /dev/null +++ b/gwenview/lib/redeyereduction/redeyereductionimageoperation.cpp @@ -0,0 +1,166 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "redeyereductionimageoperation.h" + +// Stdc +#include + +// Qt +#include +#include + +// KDE +#include +#include + +// Local +#include "ramp.h" +#include "document/document.h" +#include "document/documentjob.h" +#include "document/abstractdocumenteditor.h" +#include "paintutils.h" + +namespace Gwenview +{ + +class RedEyeReductionJob : public ThreadedDocumentJob +{ +public: + RedEyeReductionJob(const QRectF& rectF) + : mRectF(rectF) + {} + + void threadedStart() + { + if (!checkDocumentEditor()) { + return; + } + QImage img = document()->image(); + RedEyeReductionImageOperation::apply(&img, mRectF); + document()->editor()->setImage(img); + setError(NoError); + } + +private: + QRectF mRectF; +}; + +struct RedEyeReductionImageOperationPrivate +{ + QRectF mRectF; + QImage mOriginalImage; +}; + +RedEyeReductionImageOperation::RedEyeReductionImageOperation(const QRectF& rectF) +: d(new RedEyeReductionImageOperationPrivate) +{ + d->mRectF = rectF; + setText(i18n("RedEyeReduction")); +} + +RedEyeReductionImageOperation::~RedEyeReductionImageOperation() +{ + delete d; +} + +void RedEyeReductionImageOperation::redo() +{ + QImage img = document()->image(); + QRect rect = PaintUtils::containingRect(d->mRectF); + d->mOriginalImage = img.copy(rect); + redoAsDocumentJob(new RedEyeReductionJob(d->mRectF)); +} + +void RedEyeReductionImageOperation::undo() +{ + if (!document()->editor()) { + kWarning() << "!document->editor()"; + return; + } + QImage img = document()->image(); + { + QPainter painter(&img); + painter.setCompositionMode(QPainter::CompositionMode_Source); + QRect rect = PaintUtils::containingRect(d->mRectF); + painter.drawImage(rect.topLeft(), d->mOriginalImage); + } + document()->editor()->setImage(img); +} + +/** + * This code is inspired from code found in a Paint.net plugin: + * http://paintdotnet.forumer.com/viewtopic.php?f=27&t=26193&p=205954&hilit=red+eye#p205954 + */ +inline qreal computeRedEyeAlpha(const QColor& src) +{ + int hue, sat, value; + src.getHsv(&hue, &sat, &value); + + qreal axs = 1.0; + if (hue > 259) { + static const Ramp ramp(30, 35, 0., 1.); + axs = ramp(sat); + } else { + const Ramp ramp(hue * 2 + 29, hue * 2 + 40, 0., 1.); + axs = ramp(sat); + } + + return qBound(qreal(0.), src.alphaF() * axs, qreal(1.)); +} + +void RedEyeReductionImageOperation::apply(QImage* img, const QRectF& rectF) +{ + const QRect rect = PaintUtils::containingRect(rectF); + const qreal radius = rectF.width() / 2; + const qreal centerX = rectF.x() + radius; + const qreal centerY = rectF.y() + radius; + const Ramp radiusRamp( + qMin(qreal(radius * 0.7), qreal(radius - 1)), radius, + qreal(1.), qreal(0.)); + + uchar* line = img->scanLine(rect.top()) + rect.left() * 4; + for (int y = rect.top(); y < rect.bottom(); ++y, line += img->bytesPerLine()) { + QRgb* ptr = (QRgb*)line; + + for (int x = rect.left(); x < rect.right(); ++x, ++ptr) { + const qreal currentRadius = sqrt(pow(y - centerY, 2) + pow(x - centerX, 2)); + qreal alpha = radiusRamp(currentRadius); + if (qFuzzyCompare(alpha, 0)) { + continue; + } + + const QColor src(*ptr); + alpha *= computeRedEyeAlpha(src); + int r = src.red(); + int g = src.green(); + int b = src.blue(); + QColor dst; + // Replace red with green, and blend according to alpha + dst.setRed(int((1 - alpha) * r + alpha * g)); + dst.setGreen(g); + dst.setBlue(b); + *ptr = dst.rgba(); + } + } +} + +} // namespace diff --git a/gwenview/lib/redeyereduction/redeyereductionimageoperation.h b/gwenview/lib/redeyereduction/redeyereductionimageoperation.h new file mode 100644 index 00000000..5c8ab42e --- /dev/null +++ b/gwenview/lib/redeyereduction/redeyereductionimageoperation.h @@ -0,0 +1,56 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef REDEYEREDUCTIONIMAGEOPERATION_H +#define REDEYEREDUCTIONIMAGEOPERATION_H + +#include + +// Qt + +// KDE + +// Local +#include + +class QRectF; + +namespace Gwenview +{ + +struct RedEyeReductionImageOperationPrivate; +class GWENVIEWLIB_EXPORT RedEyeReductionImageOperation : public AbstractImageOperation +{ +public: + RedEyeReductionImageOperation(const QRectF&); + ~RedEyeReductionImageOperation(); + + virtual void redo(); + virtual void undo(); + + static void apply(QImage* img, const QRectF& rectF); + +private: + RedEyeReductionImageOperationPrivate* const d; +}; + +} // namespace + +#endif /* REDEYEREDUCTIONIMAGEOPERATION_H */ diff --git a/gwenview/lib/redeyereduction/redeyereductiontool.cpp b/gwenview/lib/redeyereduction/redeyereductiontool.cpp new file mode 100644 index 00000000..e1dd474e --- /dev/null +++ b/gwenview/lib/redeyereduction/redeyereductiontool.cpp @@ -0,0 +1,192 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "redeyereductiontool.moc" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include + +// Local +#include +#include "gwenviewconfig.h" +#include "paintutils.h" +#include "redeyereductionimageoperation.h" +#include "ui_redeyereductionwidget.h" + +namespace Gwenview +{ + +struct RedEyeReductionWidget : public QWidget, public Ui_RedEyeReductionWidget +{ + RedEyeReductionWidget() + { + setupUi(this); + } + + void showNotSetPage() + { + dialogButtonBox->button(QDialogButtonBox::Ok)->hide(); + stackedWidget->setCurrentWidget(notSetPage); + } + + void showMainPage() + { + dialogButtonBox->button(QDialogButtonBox::Ok)->show(); + stackedWidget->setCurrentWidget(mainPage); + } +}; + +struct RedEyeReductionToolPrivate +{ + RedEyeReductionTool* q; + RedEyeReductionTool::Status mStatus; + QPointF mCenter; + int mDiameter; + RedEyeReductionWidget* mToolWidget; + + void setupToolWidget() + { + mToolWidget = new RedEyeReductionWidget; + mToolWidget->showNotSetPage(); + QObject::connect(mToolWidget->diameterSpinBox, SIGNAL(valueChanged(int)), + q, SLOT(setDiameter(int))); + QObject::connect(mToolWidget->dialogButtonBox, SIGNAL(accepted()), + q, SLOT(slotApplyClicked())); + QObject::connect(mToolWidget->dialogButtonBox, SIGNAL(rejected()), + q, SIGNAL(done())); + } + + QRectF rectF() const + { + if (mStatus == RedEyeReductionTool::NotSet) { + return QRectF(); + } + return QRectF(mCenter.x() - mDiameter / 2, mCenter.y() - mDiameter / 2, mDiameter, mDiameter); + } +}; + +RedEyeReductionTool::RedEyeReductionTool(RasterImageView* view) +: AbstractRasterImageViewTool(view) +, d(new RedEyeReductionToolPrivate) +{ + d->q = this; + d->mDiameter = GwenviewConfig::redEyeReductionDiameter(); + d->mStatus = NotSet; + d->setupToolWidget(); + + view->document()->startLoadingFullImage(); +} + +RedEyeReductionTool::~RedEyeReductionTool() +{ + GwenviewConfig::setRedEyeReductionDiameter(d->mDiameter); + delete d->mToolWidget; + delete d; +} + +void RedEyeReductionTool::paint(QPainter* painter) +{ + if (d->mStatus == NotSet) { + return; + } + QRectF docRectF = d->rectF(); + imageView()->document()->waitUntilLoaded(); + + QRect docRect = PaintUtils::containingRect(docRectF); + QImage img = imageView()->document()->image().copy(docRect); + QRectF imgRectF( + docRectF.left() - docRect.left(), + docRectF.top() - docRect.top(), + docRectF.width(), + docRectF.height() + ); + RedEyeReductionImageOperation::apply(&img, imgRectF); + + const QRectF viewRectF = imageView()->mapToView(docRectF); + painter->drawImage(viewRectF, img, imgRectF); +} + +void RedEyeReductionTool::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + event->accept(); + if (d->mStatus == NotSet) { + d->mToolWidget->diameterSpinBox->setValue(d->mDiameter); + d->mToolWidget->showMainPage(); + d->mStatus = Adjusting; + } + d->mCenter = imageView()->mapToImage(event->pos()); + imageView()->update(); +} + +void RedEyeReductionTool::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + event->accept(); + if (event->buttons() == Qt::NoButton) { + return; + } + d->mCenter = imageView()->mapToImage(event->pos()); + imageView()->update(); +} + +void RedEyeReductionTool::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + // Just prevent the event from reaching the image view + event->accept(); +} + +void RedEyeReductionTool::toolActivated() +{ + imageView()->setCursor(Qt::CrossCursor); +} + +void RedEyeReductionTool::slotApplyClicked() +{ + QRectF docRectF = d->rectF(); + if (!docRectF.isValid()) { + kWarning() << "invalid rect"; + return; + } + RedEyeReductionImageOperation* op = new RedEyeReductionImageOperation(docRectF); + emit imageOperationRequested(op); + + d->mStatus = NotSet; + d->mToolWidget->showNotSetPage(); +} + +void RedEyeReductionTool::setDiameter(int value) +{ + d->mDiameter = value; + imageView()->update(); +} + +QWidget* RedEyeReductionTool::widget() const +{ + return d->mToolWidget; +} + +} // namespace diff --git a/gwenview/lib/redeyereduction/redeyereductiontool.h b/gwenview/lib/redeyereduction/redeyereductiontool.h new file mode 100644 index 00000000..98187d93 --- /dev/null +++ b/gwenview/lib/redeyereduction/redeyereductiontool.h @@ -0,0 +1,76 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef REDEYEREDUCTIONTOOL_H +#define REDEYEREDUCTIONTOOL_H + +#include + +// Qt + +// KDE + +// Local +#include + +namespace Gwenview +{ + +class AbstractImageOperation; +class RasterImageView; + +struct RedEyeReductionToolPrivate; +class GWENVIEWLIB_EXPORT RedEyeReductionTool : public AbstractRasterImageViewTool +{ + Q_OBJECT +public: + enum Status { + NotSet, + Adjusting + }; + + RedEyeReductionTool(RasterImageView* parent); + ~RedEyeReductionTool(); + + virtual void paint(QPainter*); + + virtual void mousePressEvent(QGraphicsSceneMouseEvent*); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*); + + virtual void toolActivated(); + + QWidget* widget() const; + +Q_SIGNALS: + void done(); + void imageOperationRequested(AbstractImageOperation*); + +private Q_SLOTS: + void setDiameter(int); + void slotApplyClicked(); + +private: + RedEyeReductionToolPrivate* const d; +}; + +} // namespace + +#endif /* REDEYEREDUCTIONTOOL_H */ diff --git a/gwenview/lib/redeyereduction/redeyereductionwidget.ui b/gwenview/lib/redeyereduction/redeyereductionwidget.ui new file mode 100644 index 00000000..151e4e77 --- /dev/null +++ b/gwenview/lib/redeyereduction/redeyereductionwidget.ui @@ -0,0 +1,127 @@ + + + RedEyeReductionWidget + + + + 0 + 0 + 372 + 51 + + + + + + + 0 + + + + + + + Size + + + diameterSlider + + + + + + + 2 + + + 40 + + + Qt::Horizontal + + + + + + + 2 + + + + + diameterSpinBox + diameterSlider + diameterSpinBox + label + diameterSlider + + + + + + + Click on the red eye you want to fix + + + + + + + + + + + QDialogButtonBox::Close|QDialogButtonBox::Ok + + + + + + + + KDialogButtonBox + QDialogButtonBox +
kdialogbuttonbox.h
+
+ + KIntSpinBox + QSpinBox +
knuminput.h
+
+
+ + + + diameterSlider + sliderMoved(int) + diameterSpinBox + setValue(int) + + + 120 + 22 + + + 175 + 21 + + + + + diameterSpinBox + valueChanged(int) + diameterSlider + setValue(int) + + + 153 + 7 + + + 124 + 7 + + + + +
diff --git a/gwenview/lib/resize/resizeimagedialog.cpp b/gwenview/lib/resize/resizeimagedialog.cpp new file mode 100644 index 00000000..bff51565 --- /dev/null +++ b/gwenview/lib/resize/resizeimagedialog.cpp @@ -0,0 +1,115 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "resizeimagedialog.moc" + +// Qt + +// KDE + +// Local +#include + +namespace Gwenview +{ + +struct ResizeImageDialogPrivate : public Ui_ResizeImageWidget +{ + bool mUpdateFromRatio; + QSize mOriginalSize; +}; + +ResizeImageDialog::ResizeImageDialog(QWidget* parent) +: KDialog(parent) +, d(new ResizeImageDialogPrivate) +{ + d->mUpdateFromRatio = false; + + QWidget* content = new QWidget(this); + d->setupUi(content); + content->layout()->setMargin(0); + setMainWidget(content); + showButtonSeparator(true); + setButtonGuiItem(Ok, KGuiItem(i18n("Resize"), "transform-scale")); + setWindowTitle(content->windowTitle()); + d->mWidthSpinBox->setFocus(); + + connect(d->mWidthSpinBox, SIGNAL(valueChanged(int)), SLOT(slotWidthChanged(int))); + connect(d->mHeightSpinBox, SIGNAL(valueChanged(int)), SLOT(slotHeightChanged(int))); + connect(d->mKeepAspectCheckBox, SIGNAL(toggled(bool)), SLOT(slotKeepAspectChanged(bool))); +} + +ResizeImageDialog::~ResizeImageDialog() +{ + delete d; +} + +void ResizeImageDialog::setOriginalSize(const QSize& size) +{ + d->mOriginalSize = size; + d->mOriginalWidthLabel->setText(QString::number(size.width())); + d->mOriginalHeightLabel->setText(QString::number(size.height())); + d->mWidthSpinBox->setValue(size.width()); + d->mHeightSpinBox->setValue(size.height()); +} + +QSize ResizeImageDialog::size() const +{ + return QSize( + d->mWidthSpinBox->value(), + d->mHeightSpinBox->value() + ); +} + +void ResizeImageDialog::slotWidthChanged(int width) +{ + if (!d->mKeepAspectCheckBox->isChecked()) { + return; + } + if (d->mUpdateFromRatio) { + return; + } + d->mUpdateFromRatio = true; + d->mHeightSpinBox->setValue(d->mOriginalSize.height() * width / d->mOriginalSize.width()); + d->mUpdateFromRatio = false; +} + +void ResizeImageDialog::slotHeightChanged(int height) +{ + if (!d->mKeepAspectCheckBox->isChecked()) { + return; + } + if (d->mUpdateFromRatio) { + return; + } + d->mUpdateFromRatio = true; + d->mWidthSpinBox->setValue(d->mOriginalSize.width() * height / d->mOriginalSize.height()); + d->mUpdateFromRatio = false; +} + +void ResizeImageDialog::slotKeepAspectChanged(bool value) +{ + if (value) { + slotWidthChanged(d->mWidthSpinBox->value()); + } +} + +} // namespace diff --git a/gwenview/lib/resize/resizeimagedialog.h b/gwenview/lib/resize/resizeimagedialog.h new file mode 100644 index 00000000..c4a5e7e7 --- /dev/null +++ b/gwenview/lib/resize/resizeimagedialog.h @@ -0,0 +1,58 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef RESIZEIMAGEDIALOG_H +#define RESIZEIMAGEDIALOG_H + +#include + +// Qt + +// KDE +#include + +// Local + +namespace Gwenview +{ + +struct ResizeImageDialogPrivate; +class GWENVIEWLIB_EXPORT ResizeImageDialog : public KDialog +{ + Q_OBJECT +public: + ResizeImageDialog(QWidget *parent); + ~ResizeImageDialog(); + + void setOriginalSize(const QSize&); + QSize size() const; + +private Q_SLOTS: + void slotWidthChanged(int); + void slotHeightChanged(int); + void slotKeepAspectChanged(bool); + +private: + ResizeImageDialogPrivate* const d; +}; + +} // namespace + +#endif /* RESIZEIMAGEDIALOG_H */ diff --git a/gwenview/lib/resize/resizeimageoperation.cpp b/gwenview/lib/resize/resizeimageoperation.cpp new file mode 100644 index 00000000..744746f2 --- /dev/null +++ b/gwenview/lib/resize/resizeimageoperation.cpp @@ -0,0 +1,94 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "resizeimageoperation.h" + +// Qt +#include + +// KDE +#include +#include + +// Local +#include "document/abstractdocumenteditor.h" +#include "document/document.h" +#include "document/documentjob.h" + +namespace Gwenview +{ + +struct ResizeImageOperationPrivate +{ + QSize mSize; + QImage mOriginalImage; +}; + +class ResizeJob : public ThreadedDocumentJob +{ +public: + ResizeJob(const QSize& size) + : mSize(size) + {} + + virtual void threadedStart() + { + if (!checkDocumentEditor()) { + return; + } + QImage image = document()->image(); + image = image.scaled(mSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + document()->editor()->setImage(image); + setError(NoError); + } + +private: + QSize mSize; +}; + +ResizeImageOperation::ResizeImageOperation(const QSize& size) +: d(new ResizeImageOperationPrivate) +{ + d->mSize = size; + setText(i18nc("(qtundo-format)", "Resize")); +} + +ResizeImageOperation::~ResizeImageOperation() +{ + delete d; +} + +void ResizeImageOperation::redo() +{ + d->mOriginalImage = document()->image(); + redoAsDocumentJob(new ResizeJob(d->mSize)); +} + +void ResizeImageOperation::undo() +{ + if (!document()->editor()) { + kWarning() << "!document->editor()"; + return; + } + document()->editor()->setImage(d->mOriginalImage); +} + +} // namespace diff --git a/gwenview/lib/resize/resizeimageoperation.h b/gwenview/lib/resize/resizeimageoperation.h new file mode 100644 index 00000000..f6a78afc --- /dev/null +++ b/gwenview/lib/resize/resizeimageoperation.h @@ -0,0 +1,52 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef RESIZEIMAGEOPERATION_H +#define RESIZEIMAGEOPERATION_H + +#include + +// Qt + +// KDE + +// Local +#include + +namespace Gwenview +{ + +struct ResizeImageOperationPrivate; +class GWENVIEWLIB_EXPORT ResizeImageOperation : public AbstractImageOperation +{ +public: + ResizeImageOperation(const QSize& size); + ~ResizeImageOperation(); + + virtual void redo(); + virtual void undo(); + +private: + ResizeImageOperationPrivate* const d; +}; + +} // namespace + +#endif /* RESIZEIMAGEOPERATION_H */ diff --git a/gwenview/lib/resize/resizeimagewidget.ui b/gwenview/lib/resize/resizeimagewidget.ui new file mode 100644 index 00000000..21ea19a4 --- /dev/null +++ b/gwenview/lib/resize/resizeimagewidget.ui @@ -0,0 +1,153 @@ + + + ResizeImageWidget + + + + 0 + 0 + 248 + 155 + + + + Image Resizing + + + + + + Enter the new size for this image. + + + + + + + + true + + + + Current size: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + true + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + true + + + + + + + + + + + + true + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + New Size: + + + mWidthSpinBox + + + + + + + + + false + + + QAbstractSpinBox::UpDownArrows + + + 1 + + + 100000 + + + + + + + + + + + + + + 1 + + + 100000 + + + + + + + + + Keep aspect ratio + + + true + + + + + + + + KIntSpinBox + QSpinBox +
knuminput.h
+
+
+ + +
diff --git a/gwenview/lib/semanticinfo/abstractsemanticinfobackend.cpp b/gwenview/lib/semanticinfo/abstractsemanticinfobackend.cpp new file mode 100644 index 00000000..5db53a96 --- /dev/null +++ b/gwenview/lib/semanticinfo/abstractsemanticinfobackend.cpp @@ -0,0 +1,59 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "abstractsemanticinfobackend.moc" + +// Qt +#include +#include + +// KDE + +// Local + +namespace Gwenview +{ + +TagSet::TagSet() +: QSet() {} + +TagSet::TagSet(const QSet& set) +: QSet(set) {} + +QVariant TagSet::toVariant() const +{ + QStringList lst = toList(); + return QVariant(lst); +} + +TagSet TagSet::fromVariant(const QVariant& variant) +{ + QStringList lst = variant.toStringList(); + return TagSet::fromList(lst); +} + +AbstractSemanticInfoBackEnd::AbstractSemanticInfoBackEnd(QObject* parent) +: QObject(parent) +{ + qRegisterMetaType("SemanticInfo"); +} + +} // namespace diff --git a/gwenview/lib/semanticinfo/abstractsemanticinfobackend.h b/gwenview/lib/semanticinfo/abstractsemanticinfobackend.h new file mode 100644 index 00000000..18a393b1 --- /dev/null +++ b/gwenview/lib/semanticinfo/abstractsemanticinfobackend.h @@ -0,0 +1,104 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef ABSTRACTSEMANTICINFOBACKEND_H +#define ABSTRACTSEMANTICINFOBACKEND_H + +#include + +// Qt +#include +#include + +// KDE + +// Local + +class KUrl; + +namespace Gwenview +{ + +typedef QString SemanticInfoTag; + +/** + * This class represents the set of tags associated to an url. + * + * It provides convenience methods to convert to and from QVariant, which are + * useful to communicate with SemanticInfoDirModel. + */ +class GWENVIEWLIB_EXPORT TagSet : public QSet +{ +public: + TagSet(); + TagSet(const QSet&); + + QVariant toVariant() const; + static TagSet fromVariant(const QVariant&); +}; + +/** + * A POD struct used by AbstractSemanticInfoBackEnd to store the metadata + * associated to an url. + */ +struct SemanticInfo +{ + int mRating; + QString mDescription; + TagSet mTags; +}; + +/** + * An abstract class, used by SemanticInfoDirModel to store and retrieve metadata. + */ +class AbstractSemanticInfoBackEnd : public QObject +{ + Q_OBJECT +public: + AbstractSemanticInfoBackEnd(QObject* parent); + + virtual TagSet allTags() const = 0; + + virtual void refreshAllTags() = 0; + + virtual void storeSemanticInfo(const KUrl&, const SemanticInfo&) = 0; + + virtual void retrieveSemanticInfo(const KUrl&) = 0; + + virtual QString labelForTag(const SemanticInfoTag&) const = 0; + + /** + * Return a tag for a label. Will emit tagAdded() if the tag had to be + * created. + */ + virtual SemanticInfoTag tagForLabel(const QString&) = 0; + +Q_SIGNALS: + void semanticInfoRetrieved(const KUrl&, const SemanticInfo&); + + /** + * Emitted whenever a new tag is added to allTags() + */ + void tagAdded(const SemanticInfoTag&, const QString& label); +}; + +} // namespace + +#endif /* ABSTRACTSEMANTICINFOBACKEND_H */ diff --git a/gwenview/lib/semanticinfo/baloosemanticinfobackend.cpp b/gwenview/lib/semanticinfo/baloosemanticinfobackend.cpp new file mode 100644 index 00000000..62655086 --- /dev/null +++ b/gwenview/lib/semanticinfo/baloosemanticinfobackend.cpp @@ -0,0 +1,121 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau +Copyright 2014 Vishesh Handa + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "baloosemanticinfobackend.h" + +// Local +#include + +// Qt +#include + +// KDE +#include +#include + +// Baloo +#include +#include +#include +#include + +namespace Gwenview +{ + +struct BalooSemanticInfoBackend::Private +{ + TagSet mAllTags; +}; + +BalooSemanticInfoBackend::BalooSemanticInfoBackend(QObject* parent) +: AbstractSemanticInfoBackEnd(parent) +, d(new BalooSemanticInfoBackend::Private) +{ +} + +BalooSemanticInfoBackend::~BalooSemanticInfoBackend() +{ + delete d; +} + +TagSet BalooSemanticInfoBackend::allTags() const +{ + if (d->mAllTags.empty()) { + const_cast(this)->refreshAllTags(); + } + return d->mAllTags; +} + +void BalooSemanticInfoBackend::refreshAllTags() +{ + Baloo::TagListJob* job = new Baloo::TagListJob(); + job->exec(); + + d->mAllTags.clear(); + Q_FOREACH(const QString& tag, job->tags()) { + d->mAllTags << tag; + } +} + +void BalooSemanticInfoBackend::storeSemanticInfo(const KUrl& url, const SemanticInfo& semanticInfo) +{ + Baloo::File file(url.toLocalFile()); + file.setRating(semanticInfo.mRating); + file.setUserComment(semanticInfo.mDescription); + file.setTags(semanticInfo.mTags.toList()); + + Baloo::FileModifyJob* job = new Baloo::FileModifyJob(file); + job->start(); +} + +void BalooSemanticInfoBackend::retrieveSemanticInfo(const KUrl& url) +{ + Baloo::FileFetchJob* job = new Baloo::FileFetchJob(url.toLocalFile()); + connect(job, SIGNAL(finished(KJob*)), this, SLOT(slotFetchFinished(KJob*))); + + job->start(); +} + +void BalooSemanticInfoBackend::slotFetchFinished(KJob* job) +{ + Baloo::FileFetchJob* fjob = static_cast(job); + Baloo::File file = fjob->file(); + + SemanticInfo si; + si.mRating = file.rating(); + si.mDescription = file.userComment(); + si.mTags = file.tags().toSet(); + + emit semanticInfoRetrieved(KUrl::fromLocalFile(file.url()), si); +} + +QString BalooSemanticInfoBackend::labelForTag(const SemanticInfoTag& uriString) const +{ + return uriString; +} + +SemanticInfoTag BalooSemanticInfoBackend::tagForLabel(const QString& label) +{ + return label; +} + +} // namespace diff --git a/gwenview/lib/semanticinfo/baloosemanticinfobackend.h b/gwenview/lib/semanticinfo/baloosemanticinfobackend.h new file mode 100644 index 00000000..6e1de00b --- /dev/null +++ b/gwenview/lib/semanticinfo/baloosemanticinfobackend.h @@ -0,0 +1,71 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau +Copyright 2014 Vishesh Handa + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef BALOOSEMANTICINFOBACKEND_H +#define BALOOSEMANTICINFOBACKEND_H + +#include + +// Qt + +// KDE + +// Local +#include "abstractsemanticinfobackend.h" + +class KJob; + +namespace Gwenview +{ + +/** + * A real metadata backend using Baloo to store and retrieve metadata. + */ +class GWENVIEWLIB_EXPORT BalooSemanticInfoBackend : public AbstractSemanticInfoBackEnd +{ + Q_OBJECT +public: + BalooSemanticInfoBackend(QObject* parent); + ~BalooSemanticInfoBackend(); + + virtual TagSet allTags() const; + + virtual void refreshAllTags(); + + virtual void storeSemanticInfo(const KUrl&, const SemanticInfo&); + + virtual void retrieveSemanticInfo(const KUrl&); + + virtual QString labelForTag(const SemanticInfoTag&) const; + + virtual SemanticInfoTag tagForLabel(const QString&); + +private Q_SLOTS: + void slotFetchFinished(KJob* job); + +private: + struct Private; + Private* const d; +}; + +} // namespace + +#endif /* BALOOSEMANTICINFOBACKEND_H */ diff --git a/gwenview/lib/semanticinfo/fakesemanticinfobackend.cpp b/gwenview/lib/semanticinfo/fakesemanticinfobackend.cpp new file mode 100644 index 00000000..5471108c --- /dev/null +++ b/gwenview/lib/semanticinfo/fakesemanticinfobackend.cpp @@ -0,0 +1,105 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "fakesemanticinfobackend.moc" + +// Qt +#include + +// KDE +#include + +// Local + +namespace Gwenview +{ + +FakeSemanticInfoBackEnd::FakeSemanticInfoBackEnd(QObject* parent, InitializeMode mode) +: AbstractSemanticInfoBackEnd(parent) +, mInitializeMode(mode) +{ + mAllTags + << tagForLabel("beach") + << tagForLabel("mountains") + << tagForLabel("wallpaper") + ; +} + +void FakeSemanticInfoBackEnd::storeSemanticInfo(const KUrl& url, const SemanticInfo& semanticInfo) +{ + mSemanticInfoForUrl[url] = semanticInfo; + mergeTagsWithAllTags(semanticInfo.mTags); +} + +void FakeSemanticInfoBackEnd::mergeTagsWithAllTags(const TagSet& set) +{ + int size = mAllTags.size(); + mAllTags |= set; + if (mAllTags.size() > size) { + //emit allTagsUpdated(); + } +} + +TagSet FakeSemanticInfoBackEnd::allTags() const +{ + return mAllTags; +} + +void FakeSemanticInfoBackEnd::refreshAllTags() +{ +} + +void FakeSemanticInfoBackEnd::retrieveSemanticInfo(const KUrl& url) +{ + if (!mSemanticInfoForUrl.contains(url)) { + QString urlString = url.url(); + SemanticInfo semanticInfo; + if (mInitializeMode == InitializeRandom) { + semanticInfo.mRating = int(urlString.length()) % 6; + semanticInfo.mDescription = url.fileName(); + QStringList lst = url.path().split('/'); + Q_FOREACH(const QString & token, lst) { + if (!token.isEmpty()) { + semanticInfo.mTags << '#' + token.toLower(); + } + } + semanticInfo.mTags << QString("#length-%1").arg(url.fileName().length()); + + mergeTagsWithAllTags(semanticInfo.mTags); + } else { + semanticInfo.mRating = 0; + } + mSemanticInfoForUrl[url] = semanticInfo; + } + emit semanticInfoRetrieved(url, mSemanticInfoForUrl.value(url)); +} + +QString FakeSemanticInfoBackEnd::labelForTag(const SemanticInfoTag& tag) const +{ + return tag[1].toUpper() + tag.mid(2); +} + +SemanticInfoTag FakeSemanticInfoBackEnd::tagForLabel(const QString& label) +{ + return '#' + label.toLower(); +} + +} // namespace diff --git a/gwenview/lib/semanticinfo/fakesemanticinfobackend.h b/gwenview/lib/semanticinfo/fakesemanticinfobackend.h new file mode 100644 index 00000000..0e97e242 --- /dev/null +++ b/gwenview/lib/semanticinfo/fakesemanticinfobackend.h @@ -0,0 +1,71 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef FAKESEMANTICINFOBACKEND_H +#define FAKESEMANTICINFOBACKEND_H + +#include + +// Qt +#include + +// KDE +#include + +// Local +#include "abstractsemanticinfobackend.h" + +namespace Gwenview +{ + +/** + * A fake metadata backend, useful to test the ui layer. + * It provides fake rating values based on the image url. + */ +class GWENVIEWLIB_EXPORT FakeSemanticInfoBackEnd : public AbstractSemanticInfoBackEnd +{ + Q_OBJECT +public: + enum InitializeMode { InitializeEmpty, InitializeRandom }; + FakeSemanticInfoBackEnd(QObject* parent, InitializeMode initializeMode); + + virtual TagSet allTags() const; + + virtual void refreshAllTags(); + + virtual void storeSemanticInfo(const KUrl&, const SemanticInfo&); + + virtual void retrieveSemanticInfo(const KUrl&); + + virtual QString labelForTag(const SemanticInfoTag&) const; + + virtual SemanticInfoTag tagForLabel(const QString&); + +private: + void mergeTagsWithAllTags(const TagSet&); + + QHash mSemanticInfoForUrl; + InitializeMode mInitializeMode; + TagSet mAllTags; +}; + +} // namespace + +#endif /* FAKESEMANTICINFOBACKEND_H */ diff --git a/gwenview/lib/semanticinfo/semanticinfodirmodel.cpp b/gwenview/lib/semanticinfo/semanticinfodirmodel.cpp new file mode 100644 index 00000000..69ae1a2e --- /dev/null +++ b/gwenview/lib/semanticinfo/semanticinfodirmodel.cpp @@ -0,0 +1,258 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "semanticinfodirmodel.moc" +#include + +// Qt +#include + +// KDE +#include + +// Local +#include "abstractsemanticinfobackend.h" +#include "../archiveutils.h" + +#ifdef GWENVIEW_SEMANTICINFO_BACKEND_FAKE +#include "fakesemanticinfobackend.h" + +#elif defined(GWENVIEW_SEMANTICINFO_BACKEND_BALOO) +#include "baloosemanticinfobackend.h" + +#else +#ifdef __GNUC__ +#error No metadata backend defined +#endif +#endif + +namespace Gwenview +{ + +struct SemanticInfoCacheItem +{ + SemanticInfoCacheItem() + : mValid(false) + {} + QPersistentModelIndex mIndex; + bool mValid; + SemanticInfo mInfo; +}; + +typedef QHash SemanticInfoCache; + +struct SemanticInfoDirModelPrivate +{ + SemanticInfoCache mSemanticInfoCache; + AbstractSemanticInfoBackEnd* mBackEnd; +}; + +SemanticInfoDirModel::SemanticInfoDirModel(QObject* parent) +: KDirModel(parent) +, d(new SemanticInfoDirModelPrivate) +{ +#ifdef GWENVIEW_SEMANTICINFO_BACKEND_FAKE + d->mBackEnd = new FakeSemanticInfoBackEnd(this, FakeSemanticInfoBackEnd::InitializeRandom); +#elif defined(GWENVIEW_SEMANTICINFO_BACKEND_BALOO) + d->mBackEnd = new BalooSemanticInfoBackend(this); +#endif + + connect(d->mBackEnd, SIGNAL(semanticInfoRetrieved(KUrl,SemanticInfo)), + SLOT(slotSemanticInfoRetrieved(KUrl,SemanticInfo)), + Qt::QueuedConnection); + + connect(this, SIGNAL(modelAboutToBeReset()), + SLOT(slotModelAboutToBeReset())); + + connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); +} + +SemanticInfoDirModel::~SemanticInfoDirModel() +{ + delete d; +} + +void SemanticInfoDirModel::clearSemanticInfoCache() +{ + d->mSemanticInfoCache.clear(); +} + +bool SemanticInfoDirModel::semanticInfoAvailableForIndex(const QModelIndex& index) const +{ + if (!index.isValid()) { + return false; + } + KFileItem item = itemForIndex(index); + if (item.isNull()) { + return false; + } + SemanticInfoCache::const_iterator it = d->mSemanticInfoCache.constFind(item.targetUrl()); + if (it == d->mSemanticInfoCache.constEnd()) { + return false; + } + return it.value().mValid; +} + +SemanticInfo SemanticInfoDirModel::semanticInfoForIndex(const QModelIndex& index) const +{ + if (!index.isValid()) { + kWarning() << "invalid index"; + return SemanticInfo(); + } + KFileItem item = itemForIndex(index); + if (item.isNull()) { + kWarning() << "no item for index"; + return SemanticInfo(); + } + return d->mSemanticInfoCache.value(item.targetUrl()).mInfo; +} + +void SemanticInfoDirModel::retrieveSemanticInfoForIndex(const QModelIndex& index) +{ + if (!index.isValid()) { + return; + } + KFileItem item = itemForIndex(index); + if (item.isNull()) { + kWarning() << "invalid item"; + return; + } + if (ArchiveUtils::fileItemIsDirOrArchive(item)) { + return; + } + SemanticInfoCacheItem cacheItem; + cacheItem.mIndex = QPersistentModelIndex(index); + d->mSemanticInfoCache[item.targetUrl()] = cacheItem; + d->mBackEnd->retrieveSemanticInfo(item.targetUrl()); +} + +QVariant SemanticInfoDirModel::data(const QModelIndex& index, int role) const +{ + if (role == RatingRole || role == DescriptionRole || role == TagsRole) { + KFileItem item = itemForIndex(index); + if (item.isNull()) { + return QVariant(); + } + SemanticInfoCache::ConstIterator it = d->mSemanticInfoCache.constFind(item.targetUrl()); + if (it != d->mSemanticInfoCache.constEnd()) { + if (!it.value().mValid) { + return QVariant(); + } + const SemanticInfo& info = it.value().mInfo; + if (role == RatingRole) { + return info.mRating; + } else if (role == DescriptionRole) { + return info.mDescription; + } else if (role == TagsRole) { + return info.mTags.toVariant(); + } else { + // We should never reach this part + Q_ASSERT(0); + return QVariant(); + } + } else { + const_cast(this)->retrieveSemanticInfoForIndex(index); + return QVariant(); + } + } else { + return KDirModel::data(index, role); + } +} + +bool SemanticInfoDirModel::setData(const QModelIndex& index, const QVariant& data, int role) +{ + if (role == RatingRole || role == DescriptionRole || role == TagsRole) { + KFileItem item = itemForIndex(index); + if (item.isNull()) { + kWarning() << "no item found for this index"; + return false; + } + KUrl url = item.targetUrl(); + SemanticInfoCache::iterator it = d->mSemanticInfoCache.find(url); + if (it == d->mSemanticInfoCache.end()) { + kWarning() << "No index for" << url; + return false; + } + if (!it.value().mValid) { + kWarning() << "Semantic info cache for" << url << "is invalid"; + return false; + } + SemanticInfo& semanticInfo = it.value().mInfo; + if (role == RatingRole) { + semanticInfo.mRating = data.toInt(); + } else if (role == DescriptionRole) { + semanticInfo.mDescription = data.toString(); + } else if (role == TagsRole) { + semanticInfo.mTags = TagSet::fromVariant(data); + } else { + // We should never reach this part + Q_ASSERT(0); + } + emit dataChanged(index, index); + + d->mBackEnd->storeSemanticInfo(url, semanticInfo); + return true; + } else { + return KDirModel::setData(index, data, role); + } +} + +void SemanticInfoDirModel::slotSemanticInfoRetrieved(const KUrl& url, const SemanticInfo& semanticInfo) +{ + SemanticInfoCache::iterator it = d->mSemanticInfoCache.find(url); + if (it == d->mSemanticInfoCache.end()) { + kWarning() << "No index for" << url; + return; + } + SemanticInfoCacheItem& cacheItem = it.value(); + if (!cacheItem.mIndex.isValid()) { + kWarning() << "Index for" << url << "is invalid"; + return; + } + cacheItem.mInfo = semanticInfo; + cacheItem.mValid = true; + emit dataChanged(cacheItem.mIndex, cacheItem.mIndex); +} + +void SemanticInfoDirModel::slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) +{ + for (int pos = start; pos <= end; ++pos) { + QModelIndex idx = index(pos, 0, parent); + KFileItem item = itemForIndex(idx); + if (item.isNull()) { + continue; + } + d->mSemanticInfoCache.remove(item.targetUrl()); + } +} + +void SemanticInfoDirModel::slotModelAboutToBeReset() +{ + d->mSemanticInfoCache.clear(); +} + +AbstractSemanticInfoBackEnd* SemanticInfoDirModel::semanticInfoBackEnd() const +{ + return d->mBackEnd; +} + +} // namespace diff --git a/gwenview/lib/semanticinfo/semanticinfodirmodel.h b/gwenview/lib/semanticinfo/semanticinfodirmodel.h new file mode 100644 index 00000000..252db5e7 --- /dev/null +++ b/gwenview/lib/semanticinfo/semanticinfodirmodel.h @@ -0,0 +1,84 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef SEMANTICINFODIRMODEL_H +#define SEMANTICINFODIRMODEL_H + +// Qt + +// KDE +#include + +// Local + +class KUrl; + +namespace Gwenview +{ + +class AbstractSemanticInfoBackEnd; +struct SemanticInfo; +struct SemanticInfoDirModelPrivate; +/** + * Extends KDirModel by providing read/write access to image metadata such as + * rating, tags and descriptions. + */ +class SemanticInfoDirModel : public KDirModel +{ + Q_OBJECT +public: + enum { + RatingRole = 0x21a43a51, + DescriptionRole = 0x26FB33FA, + TagsRole = 0x0462F0A8 + }; + SemanticInfoDirModel(QObject* parent); + ~SemanticInfoDirModel(); + + void clearSemanticInfoCache(); + + bool semanticInfoAvailableForIndex(const QModelIndex&) const; + + void retrieveSemanticInfoForIndex(const QModelIndex&); + + SemanticInfo semanticInfoForIndex(const QModelIndex&) const; + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + + bool setData(const QModelIndex& index, const QVariant& data, int role = Qt::EditRole); + + AbstractSemanticInfoBackEnd* semanticInfoBackEnd() const; + +Q_SIGNALS: + void semanticInfoRetrieved(const KUrl&, const SemanticInfo&); + +private: + SemanticInfoDirModelPrivate* const d; + +private Q_SLOTS: + void slotSemanticInfoRetrieved(const KUrl& url, const SemanticInfo&); + + void slotRowsAboutToBeRemoved(const QModelIndex&, int, int); + void slotModelAboutToBeReset(); +}; + +} // namespace + +#endif /* SEMANTICINFODIRMODEL_H */ diff --git a/gwenview/lib/semanticinfo/sorteddirmodel.cpp b/gwenview/lib/semanticinfo/sorteddirmodel.cpp new file mode 100644 index 00000000..267dede1 --- /dev/null +++ b/gwenview/lib/semanticinfo/sorteddirmodel.cpp @@ -0,0 +1,300 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "sorteddirmodel.moc" +#include + +// Qt + +// KDE +#include +#include +#include + +// Local +#include +#include +#ifdef GWENVIEW_SEMANTICINFO_BACKEND_NONE +#include +#else +#include "abstractsemanticinfobackend.h" +#include "semanticinfodirmodel.h" +#endif + +namespace Gwenview +{ + +AbstractSortedDirModelFilter::AbstractSortedDirModelFilter(SortedDirModel* model) +: QObject(model) +, mModel(model) +{ + if (mModel) { + mModel->addFilter(this); + } +} + +AbstractSortedDirModelFilter::~AbstractSortedDirModelFilter() +{ + if (mModel) { + mModel->removeFilter(this); + } +} + +struct SortedDirModelPrivate +{ +#ifdef GWENVIEW_SEMANTICINFO_BACKEND_NONE + KDirModel* mSourceModel; +#else + SemanticInfoDirModel* mSourceModel; +#endif + QStringList mBlackListedExtensions; + QList mFilters; + QTimer mDelayedApplyFiltersTimer; + MimeTypeUtils::Kinds mKindFilter; +}; + +SortedDirModel::SortedDirModel(QObject* parent) +: KDirSortFilterProxyModel(parent) +, d(new SortedDirModelPrivate) +{ +#ifdef GWENVIEW_SEMANTICINFO_BACKEND_NONE + d->mSourceModel = new KDirModel(this); +#else + d->mSourceModel = new SemanticInfoDirModel(this); +#endif + setSourceModel(d->mSourceModel); + d->mDelayedApplyFiltersTimer.setInterval(0); + d->mDelayedApplyFiltersTimer.setSingleShot(true); + connect(&d->mDelayedApplyFiltersTimer, SIGNAL(timeout()), SLOT(doApplyFilters())); +} + +SortedDirModel::~SortedDirModel() +{ + delete d; +} + +MimeTypeUtils::Kinds SortedDirModel::kindFilter() const +{ + return d->mKindFilter; +} + +void SortedDirModel::setKindFilter(MimeTypeUtils::Kinds kindFilter) +{ + if (d->mKindFilter == kindFilter) { + return; + } + d->mKindFilter = kindFilter; + applyFilters(); +} + +void SortedDirModel::adjustKindFilter(MimeTypeUtils::Kinds kinds, bool set) +{ + MimeTypeUtils::Kinds kindFilter = d->mKindFilter; + if (set) { + kindFilter |= kinds; + } else { + kindFilter &= ~kinds; + } + setKindFilter(kindFilter); +} + +void SortedDirModel::addFilter(AbstractSortedDirModelFilter* filter) +{ + d->mFilters << filter; + applyFilters(); +} + +void SortedDirModel::removeFilter(AbstractSortedDirModelFilter* filter) +{ + d->mFilters.removeAll(filter); + applyFilters(); +} + +KDirLister* SortedDirModel::dirLister() const +{ + return d->mSourceModel->dirLister(); +} + +void SortedDirModel::reload() +{ +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + d->mSourceModel->clearSemanticInfoCache(); +#endif + dirLister()->updateDirectory(dirLister()->url()); +} + +void SortedDirModel::setBlackListedExtensions(const QStringList& list) +{ + d->mBlackListedExtensions = list; +} + +KFileItem SortedDirModel::itemForIndex(const QModelIndex& index) const +{ + if (!index.isValid()) { + return KFileItem(); + } + + QModelIndex sourceIndex = mapToSource(index); + return d->mSourceModel->itemForIndex(sourceIndex); +} + +KUrl SortedDirModel::urlForIndex(const QModelIndex& index) const +{ + KFileItem item = itemForIndex(index); + return item.isNull() ? KUrl() : item.url(); +} + +KFileItem SortedDirModel::itemForSourceIndex(const QModelIndex& sourceIndex) const +{ + if (!sourceIndex.isValid()) { + return KFileItem(); + } + return d->mSourceModel->itemForIndex(sourceIndex); +} + +QModelIndex SortedDirModel::indexForItem(const KFileItem& item) const +{ + if (item.isNull()) { + return QModelIndex(); + } + + QModelIndex sourceIndex = d->mSourceModel->indexForItem(item); + return mapFromSource(sourceIndex); +} + +QModelIndex SortedDirModel::indexForUrl(const KUrl& url) const +{ + if (!url.isValid()) { + return QModelIndex(); + } + QModelIndex sourceIndex = d->mSourceModel->indexForUrl(url); + return mapFromSource(sourceIndex); +} + +bool SortedDirModel::filterAcceptsRow(int row, const QModelIndex& parent) const +{ + QModelIndex index = d->mSourceModel->index(row, 0, parent); + KFileItem fileItem = d->mSourceModel->itemForIndex(index); + + MimeTypeUtils::Kinds kind = MimeTypeUtils::fileItemKind(fileItem); + if (d->mKindFilter != MimeTypeUtils::Kinds() && !(d->mKindFilter & kind)) { + return false; + } + + if (kind != MimeTypeUtils::KIND_DIR && kind != MimeTypeUtils::KIND_ARCHIVE) { + int dotPos = fileItem.name().lastIndexOf('.'); + if (dotPos >= 1) { + QString extension = fileItem.name().mid(dotPos + 1).toLower(); + if (d->mBlackListedExtensions.contains(extension)) { + return false; + } + } +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + if (!d->mSourceModel->semanticInfoAvailableForIndex(index)) { + Q_FOREACH(const AbstractSortedDirModelFilter * filter, d->mFilters) { + // Make sure we have semanticinfo, otherwise retrieve it and + // return false, we will be called again later when it is + // there. + if (filter->needsSemanticInfo()) { + d->mSourceModel->retrieveSemanticInfoForIndex(index); + return false; + } + } + } +#endif + + Q_FOREACH(const AbstractSortedDirModelFilter * filter, d->mFilters) { + if (!filter->acceptsIndex(index)) { + return false; + } + } + } + return KDirSortFilterProxyModel::filterAcceptsRow(row, parent); +} + +AbstractSemanticInfoBackEnd* SortedDirModel::semanticInfoBackEnd() const +{ +#ifdef GWENVIEW_SEMANTICINFO_BACKEND_NONE + return 0; +#else + return d->mSourceModel->semanticInfoBackEnd(); +#endif +} + +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE +SemanticInfo SortedDirModel::semanticInfoForSourceIndex(const QModelIndex& sourceIndex) const +{ + return d->mSourceModel->semanticInfoForIndex(sourceIndex); +} +#endif + +void SortedDirModel::applyFilters() +{ + d->mDelayedApplyFiltersTimer.start(); +} + +void SortedDirModel::doApplyFilters() +{ + QSortFilterProxyModel::invalidateFilter(); +} + +bool SortedDirModel::lessThan(const QModelIndex& left, const QModelIndex& right) const +{ + const KFileItem leftItem = itemForSourceIndex(left); + const KFileItem rightItem = itemForSourceIndex(right); + + const bool leftIsDirOrArchive = ArchiveUtils::fileItemIsDirOrArchive(leftItem); + const bool rightIsDirOrArchive = ArchiveUtils::fileItemIsDirOrArchive(rightItem); + + if (leftIsDirOrArchive != rightIsDirOrArchive) { + return leftIsDirOrArchive; + } + + if (sortRole() != KDirModel::ModifiedTime) { + return KDirSortFilterProxyModel::lessThan(left, right); + } + + const KDateTime leftDate = TimeUtils::dateTimeForFileItem(leftItem); + const KDateTime rightDate = TimeUtils::dateTimeForFileItem(rightItem); + + return leftDate < rightDate; +} + +bool SortedDirModel::hasDocuments() const +{ + const int count = rowCount(); + if (count == 0) { + return false; + } + for (int row = 0; row < count; ++row) { + const QModelIndex idx = index(row, 0); + const KFileItem item = itemForIndex(idx); + if (!ArchiveUtils::fileItemIsDirOrArchive(item)) { + return true; + } + } + return false; +} + +void SortedDirModel::setDirLister(KDirLister* dirLister) +{ + d->mSourceModel->setDirLister(dirLister); +} + +} //namespace diff --git a/gwenview/lib/semanticinfo/sorteddirmodel.h b/gwenview/lib/semanticinfo/sorteddirmodel.h new file mode 100644 index 00000000..d42b369c --- /dev/null +++ b/gwenview/lib/semanticinfo/sorteddirmodel.h @@ -0,0 +1,133 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef SORTEDDIRMODEL_H +#define SORTEDDIRMODEL_H + +#include + +// Qt +#include + +// KDE +#include + +// Local +#include +#include + +class KDirLister; +class KFileItem; +class KUrl; + +namespace Gwenview +{ + +class AbstractSemanticInfoBackEnd; +struct SortedDirModelPrivate; + +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE +struct SemanticInfo; +#endif + +class SortedDirModel; +class GWENVIEWLIB_EXPORT AbstractSortedDirModelFilter : public QObject +{ +public: + AbstractSortedDirModelFilter(SortedDirModel* model); + ~AbstractSortedDirModelFilter(); + SortedDirModel* model() const + { + return mModel; + } + + virtual bool needsSemanticInfo() const = 0; + /** + * Returns true if index should be accepted. + * Warning: index is a source index of SortedDirModel + */ + virtual bool acceptsIndex(const QModelIndex& index) const = 0; + +private: + QPointer mModel; +}; + +/** + * This model makes it possible to show all images in a folder. + * It can filter images based on name and metadata. + */ +class GWENVIEWLIB_EXPORT SortedDirModel : public KDirSortFilterProxyModel +{ + Q_OBJECT +public: + SortedDirModel(QObject* parent = 0); + ~SortedDirModel(); + KDirLister* dirLister() const; + /** + * Redefines the dir lister, useful for debugging + */ + void setDirLister(KDirLister*); + KFileItem itemForIndex(const QModelIndex& index) const; + KUrl urlForIndex(const QModelIndex& index) const; + KFileItem itemForSourceIndex(const QModelIndex& sourceIndex) const; + QModelIndex indexForItem(const KFileItem& item) const; + QModelIndex indexForUrl(const KUrl& url) const; + + void setKindFilter(MimeTypeUtils::Kinds); + MimeTypeUtils::Kinds kindFilter() const; + + void adjustKindFilter(MimeTypeUtils::Kinds, bool set); + + /** + * A list of file extensions we should skip + */ + void setBlackListedExtensions(const QStringList& list); + + void addFilter(AbstractSortedDirModelFilter*); + + void removeFilter(AbstractSortedDirModelFilter*); + + void reload(); + + AbstractSemanticInfoBackEnd* semanticInfoBackEnd() const; + +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + SemanticInfo semanticInfoForSourceIndex(const QModelIndex& sourceIndex) const; +#endif + + bool hasDocuments() const; + +public Q_SLOTS: + void applyFilters(); + +protected: + bool filterAcceptsRow(int row, const QModelIndex& parent) const; + bool lessThan(const QModelIndex& left, const QModelIndex& right) const; + +private Q_SLOTS: + void doApplyFilters(); + +private: + friend struct SortedDirModelPrivate; + SortedDirModelPrivate * const d; +}; + +} // namespace + +#endif /* SORTEDDIRMODEL_H */ diff --git a/gwenview/lib/semanticinfo/tagitemdelegate.cpp b/gwenview/lib/semanticinfo/tagitemdelegate.cpp new file mode 100644 index 00000000..8e559ab6 --- /dev/null +++ b/gwenview/lib/semanticinfo/tagitemdelegate.cpp @@ -0,0 +1,151 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include + +// Qt +#include +#include +#include + +// KDE +#include +#include +#include +#include + +// Local +#include + +namespace Gwenview +{ + +TagItemDelegate::TagItemDelegate(QAbstractItemView* view) +: KWidgetItemDelegate(view, view) +{ +#define pm(x) view->style()->pixelMetric(QStyle::x) + mMargin = pm(PM_ToolBarItemMargin); + mSpacing = pm(PM_ToolBarItemSpacing); +#undef pm + const int iconSize = KIconLoader::global()->currentSize(KIconLoader::Toolbar); + const QSize sz = view->style()->sizeFromContents(QStyle::CT_ToolButton, 0, QSize(iconSize, iconSize)); + mButtonSize = qMax(sz.width(), sz.height()); +} + +QList TagItemDelegate::createItemWidgets() const +{ + +#define initButton(x) \ + (x)->setAutoRaise(true); \ + setBlockedEventTypes((x), QList() \ + << QEvent::MouseButtonPress \ + << QEvent::MouseButtonRelease \ + << QEvent::MouseButtonDblClick); + + QToolButton* assignToAllButton = new QToolButton; + initButton(assignToAllButton); + assignToAllButton->setIcon(KIcon("fill-color")); /* FIXME: Probably not the appropriate icon */ + assignToAllButton->setToolTip(i18nc("@info:tooltip", "Assign this tag to all selected images")); + connect(assignToAllButton, SIGNAL(clicked()), SLOT(slotAssignToAllButtonClicked())); + + QToolButton* removeButton = new QToolButton; + initButton(removeButton); + removeButton->setIcon(KIcon("list-remove")); + connect(removeButton, SIGNAL(clicked()), SLOT(slotRemoveButtonClicked())); + +#undef initButton + + return QList() << removeButton << assignToAllButton; +} + +void TagItemDelegate::updateItemWidgets(const QList widgets, const QStyleOptionViewItem& option, const QPersistentModelIndex& index) const +{ + const bool fullyAssigned = index.data(TagModel::AssignmentStatusRole).toInt() == int(TagModel::FullyAssigned); + + QToolButton* removeButton = static_cast(widgets[0]); + QToolButton* assignToAllButton = static_cast(widgets[1]); + + QSize buttonSize(mButtonSize, option.rect.height() - 2 * mMargin); + + removeButton->resize(buttonSize); + assignToAllButton->resize(buttonSize); + + removeButton->move(option.rect.width() - mButtonSize - mMargin, mMargin); + + if (fullyAssigned) { + assignToAllButton->hide(); + } else { + assignToAllButton->move(removeButton->x() - mButtonSize - mSpacing, mMargin); + } +} + +void TagItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + if (!index.isValid()) { + return; + } + const bool selected = option.state & QStyle::State_Selected; + const bool fullyAssigned = index.data(TagModel::AssignmentStatusRole).toInt() == int(TagModel::FullyAssigned); + + itemView()->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, 0); + + QRect textRect = option.rect; + textRect.setLeft(textRect.left() + mMargin); + textRect.setWidth(textRect.width() - mButtonSize - mMargin - mSpacing); + if (!fullyAssigned) { + textRect.setWidth(textRect.width() - mButtonSize - mSpacing); + } + + painter->setPen(option.palette.color(QPalette::Normal, + selected + ? QPalette::HighlightedText + : QPalette::Text)); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, index.data().toString()); +} + +QSize TagItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + const int width = option.fontMetrics.width(index.data().toString()); + const int height = qMax(mButtonSize, option.fontMetrics.height()); + return QSize(width + 2 * mMargin, height + 2 * mMargin); +} + +void TagItemDelegate::slotRemoveButtonClicked() +{ + const QModelIndex index = focusedIndex(); + if (!index.isValid()) { + kWarning() << "!index.isValid()"; + return; + } + emit removeTagRequested(index.data(TagModel::TagRole).toString()); +} + +void TagItemDelegate::slotAssignToAllButtonClicked() +{ + const QModelIndex index = focusedIndex(); + if (!index.isValid()) { + kWarning() << "!index.isValid()"; + return; + } + emit assignTagToAllRequested(index.data(TagModel::TagRole).toString()); +} + +} // namespace diff --git a/gwenview/lib/semanticinfo/tagitemdelegate.h b/gwenview/lib/semanticinfo/tagitemdelegate.h new file mode 100644 index 00000000..3f1e879a --- /dev/null +++ b/gwenview/lib/semanticinfo/tagitemdelegate.h @@ -0,0 +1,67 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef TAGITEMDELEGATE_H +#define TAGITEMDELEGATE_H + +// KDE +#include + +namespace Gwenview +{ + +typedef QString SemanticInfoTag; + +class TagItemDelegate : public KWidgetItemDelegate +{ + Q_OBJECT +public: + TagItemDelegate(QAbstractItemView* view); + +protected: + virtual QList createItemWidgets() const; + + virtual void updateItemWidgets(const QList widgets, + const QStyleOptionViewItem& option, + const QPersistentModelIndex& /*index*/) const; + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + virtual QSize sizeHint(const QStyleOptionViewItem &/*option*/, + const QModelIndex &/*index*/) const; + +Q_SIGNALS: + void removeTagRequested(const SemanticInfoTag& tag); + void assignTagToAllRequested(const SemanticInfoTag& tag); + +private Q_SLOTS: + void slotRemoveButtonClicked(); + void slotAssignToAllButtonClicked(); + +private: + int mButtonSize; + int mMargin; + int mSpacing; +}; + +} // namespace + +#endif /* TAGITEMDELEGATE_H */ diff --git a/gwenview/lib/semanticinfo/tagmodel.cpp b/gwenview/lib/semanticinfo/tagmodel.cpp new file mode 100644 index 00000000..e0b28d67 --- /dev/null +++ b/gwenview/lib/semanticinfo/tagmodel.cpp @@ -0,0 +1,128 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "tagmodel.moc" + +// Qt + +// KDE +#include +#include + +// Local +#include "abstractsemanticinfobackend.h" + +namespace Gwenview +{ + +struct TagModelPrivate +{ + AbstractSemanticInfoBackEnd* mBackEnd; +}; + +static QStandardItem* createItem(const SemanticInfoTag& tag, const QString& label, TagModel::AssignmentStatus status) +{ + QStandardItem* item = new QStandardItem(label); + item->setData(tag, TagModel::TagRole); + item->setData(label.toLower(), TagModel::SortRole); + item->setData(status, TagModel::AssignmentStatusRole); + item->setData(KIcon("mail-tagged.png"), Qt::DecorationRole); + return item; +} + +TagModel::TagModel(QObject* parent) +: QStandardItemModel(parent) +, d(new TagModelPrivate) +{ + d->mBackEnd = 0; + setSortRole(SortRole); +} + +TagModel::~TagModel() +{ + delete d; +} + +void TagModel::setSemanticInfoBackEnd(AbstractSemanticInfoBackEnd* backEnd) +{ + d->mBackEnd = backEnd; +} + +void TagModel::setTagSet(const TagSet& set) +{ + clear(); + Q_FOREACH(const SemanticInfoTag & tag, set) { + QString label = d->mBackEnd->labelForTag(tag); + QStandardItem* item = createItem(tag, label, TagModel::FullyAssigned); + appendRow(item); + } + sort(0); +} + +void TagModel::addTag(const SemanticInfoTag& tag, const QString& _label, TagModel::AssignmentStatus status) +{ + int row; + QString label = _label.isEmpty() ? d->mBackEnd->labelForTag(tag) : _label; + + const QString sortLabel = label.toLower(); + // This is not optimal, implement dichotomic search if necessary + for (row = 0; row < rowCount(); ++row) { + const QModelIndex idx = index(row, 0); + if (idx.data(SortRole).toString().compare(sortLabel) > 0) { + break; + } + } + if (row > 0) { + QStandardItem* _item = item(row - 1); + Q_ASSERT(_item); + if (_item->data(TagRole).toString() == tag) { + // Update, do not add + _item->setData(label.toLower(), SortRole); + _item->setData(status, AssignmentStatusRole); + return; + } + } + QStandardItem* _item = createItem(tag, label, status); + insertRow(row, _item); +} + +void TagModel::removeTag(const SemanticInfoTag& tag) +{ + // This is not optimal, implement dichotomic search if necessary + for (int row = 0; row < rowCount(); ++row) { + if (index(row, 0).data(TagRole).toString() == tag) { + removeRow(row); + return; + } + } +} + +TagModel* TagModel::createAllTagsModel(QObject* parent, AbstractSemanticInfoBackEnd* backEnd) +{ + TagModel* tagModel = new TagModel(parent); + tagModel->setSemanticInfoBackEnd(backEnd); + tagModel->setTagSet(backEnd->allTags()); + connect(backEnd, SIGNAL(tagAdded(SemanticInfoTag,QString)), + tagModel, SLOT(addTag(SemanticInfoTag,QString))); + return tagModel; +} + +} // namespace diff --git a/gwenview/lib/semanticinfo/tagmodel.h b/gwenview/lib/semanticinfo/tagmodel.h new file mode 100644 index 00000000..8c7ae8eb --- /dev/null +++ b/gwenview/lib/semanticinfo/tagmodel.h @@ -0,0 +1,83 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef TAGMODEL_H +#define TAGMODEL_H + +#include + +// Qt +#include + +// KDE + +// Local + +namespace Gwenview +{ + +typedef QString SemanticInfoTag; + +class AbstractSemanticInfoBackEnd; +class TagSet; + +struct TagModelPrivate; +class GWENVIEWLIB_EXPORT TagModel : public QStandardItemModel +{ + Q_OBJECT +public: + TagModel(QObject*); + ~TagModel(); + + enum { + TagRole = Qt::UserRole, + SortRole, + AssignmentStatusRole + }; + + enum AssignmentStatus { + PartiallyAssigned, + FullyAssigned + }; + + void setSemanticInfoBackEnd(AbstractSemanticInfoBackEnd*); + void setTagSet(const TagSet& set); + + /** + * Convenience method to create a TagModel showing all tags available in + * AbstractSemanticInfoBackEnd + */ + static TagModel* createAllTagsModel(QObject* parent, AbstractSemanticInfoBackEnd*); + +public Q_SLOTS: + /** + * Add a new tag. If label is empty, backend will be queried for it + */ + void addTag(const SemanticInfoTag& tag, const QString& label = QString(), AssignmentStatus status = FullyAssigned); + + void removeTag(const SemanticInfoTag& tag); + +private: + TagModelPrivate* const d; +}; + +} // namespace + +#endif /* TAGMODEL_H */ diff --git a/gwenview/lib/semanticinfo/tagwidget.cpp b/gwenview/lib/semanticinfo/tagwidget.cpp new file mode 100644 index 00000000..4f416260 --- /dev/null +++ b/gwenview/lib/semanticinfo/tagwidget.cpp @@ -0,0 +1,255 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "tagwidget.moc" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include + +// Local +#include +#include + +namespace Gwenview +{ + +class TagCompleterModel : public QSortFilterProxyModel +{ +public: + TagCompleterModel(QObject* parent) + : QSortFilterProxyModel(parent) + { + } + + void setTagInfo(const TagInfo& tagInfo) + { + mExcludedTagSet.clear(); + TagInfo::ConstIterator + it = tagInfo.begin(), + end = tagInfo.end(); + for (; it != end; ++it) { + if (it.value()) { + mExcludedTagSet << it.key(); + } + } + invalidate(); + } + + void setSemanticInfoBackEnd(AbstractSemanticInfoBackEnd* backEnd) + { + setSourceModel(TagModel::createAllTagsModel(this, backEnd)); + } + +protected: + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const + { + QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent); + SemanticInfoTag tag = sourceIndex.data(TagModel::TagRole).toString(); + return !mExcludedTagSet.contains(tag); + } + +private: + TagSet mExcludedTagSet; +}; + +/** + * A simple class to eat return keys. We use it to avoid propagating the return + * key from our KLineEdit to a dialog using TagWidget. + * We can't use KLineEdit::setTrapReturnKey() because it does not play well + * with QCompleter, it only deals with KCompletion. + */ +class ReturnKeyEater : public QObject +{ +public: + ReturnKeyEater(QObject* parent = 0) + : QObject(parent) + {} + +protected: + virtual bool eventFilter(QObject*, QEvent* event) + { + if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { + QKeyEvent* keyEvent = static_cast(event); + switch (keyEvent->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + return true; + default: + return false; + } + } + return false; + } +}; + +struct TagWidgetPrivate +{ + TagWidget* q; + TagInfo mTagInfo; + QListView* mListView; + QComboBox* mComboBox; + KPushButton* mAddButton; + AbstractSemanticInfoBackEnd* mBackEnd; + TagCompleterModel* mTagCompleterModel; + TagModel* mAssignedTagModel; + + void setupWidgets() + { + mListView = new QListView; + TagItemDelegate* delegate = new TagItemDelegate(mListView); + QObject::connect(delegate, SIGNAL(removeTagRequested(SemanticInfoTag)), + q, SLOT(removeTag(SemanticInfoTag))); + QObject::connect(delegate, SIGNAL(assignTagToAllRequested(SemanticInfoTag)), + q, SLOT(assignTag(SemanticInfoTag))); + mListView->setItemDelegate(delegate); + mListView->setModel(mAssignedTagModel); + + mComboBox = new QComboBox; + mComboBox->setEditable(true); + mComboBox->setInsertPolicy(QComboBox::NoInsert); + + mTagCompleterModel = new TagCompleterModel(q); + QCompleter* completer = new QCompleter(q); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setModel(mTagCompleterModel); + mComboBox->setCompleter(completer); + + mComboBox->setModel(mTagCompleterModel); + + mAddButton = new KPushButton; + mAddButton->setIcon(KIcon("list-add")); + mAddButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + QObject::connect(mAddButton, SIGNAL(clicked()), + q, SLOT(addTagFromComboBox())); + + QVBoxLayout* layout = new QVBoxLayout(q); + layout->setMargin(0); + layout->addWidget(mListView); + + QHBoxLayout* hLayout = new QHBoxLayout; + hLayout->addWidget(mComboBox); + hLayout->addWidget(mAddButton); + layout->addLayout(hLayout); + + q->setTabOrder(mComboBox, mListView); + } + + void fillTagModel() + { + Q_ASSERT(mBackEnd); + + mAssignedTagModel->clear(); + TagInfo::ConstIterator + it = mTagInfo.constBegin(), + end = mTagInfo.constEnd(); + for (; it != end; ++it) { + mAssignedTagModel->addTag( + it.key(), + QString(), + it.value() ? TagModel::FullyAssigned : TagModel::PartiallyAssigned); + } + } + + void updateCompleterModel() + { + mTagCompleterModel->setTagInfo(mTagInfo); + } +}; + +TagWidget::TagWidget(QWidget* parent) +: QWidget(parent) +, d(new TagWidgetPrivate) +{ + d->q = this; + d->mBackEnd = 0; + d->mAssignedTagModel = new TagModel(this); + d->setupWidgets(); + installEventFilter(new ReturnKeyEater(this)); + + connect(d->mComboBox->lineEdit(), SIGNAL(returnPressed()), + SLOT(addTagFromComboBox())); +} + +TagWidget::~TagWidget() +{ + delete d; +} + +void TagWidget::setSemanticInfoBackEnd(AbstractSemanticInfoBackEnd* backEnd) +{ + d->mBackEnd = backEnd; + d->mAssignedTagModel->setSemanticInfoBackEnd(backEnd); + d->mTagCompleterModel->setSemanticInfoBackEnd(backEnd); +} + +void TagWidget::setTagInfo(const TagInfo& tagInfo) +{ + d->mTagInfo = tagInfo; + d->fillTagModel(); + d->updateCompleterModel(); +} + +void TagWidget::addTagFromComboBox() +{ + Q_ASSERT(d->mBackEnd); + QString label = d->mComboBox->currentText(); + if (label.isEmpty()) { + return; + } + assignTag(d->mBackEnd->tagForLabel(label.trimmed())); + + // Use a QTimer because if the tag is new, it will be inserted in the model + // and QComboBox will sometimes select it. At least it does so when the + // model is empty. + QTimer::singleShot(0, d->mComboBox, SLOT(clearEditText())); +} + +void TagWidget::assignTag(const SemanticInfoTag& tag) +{ + d->mTagInfo[tag] = true; + d->mAssignedTagModel->addTag(tag); + d->updateCompleterModel(); + + emit tagAssigned(tag); +} + +void TagWidget::removeTag(const SemanticInfoTag& tag) +{ + d->mTagInfo.remove(tag); + d->mAssignedTagModel->removeTag(tag); + d->updateCompleterModel(); + + emit tagRemoved(tag); +} + +} // namespace diff --git a/gwenview/lib/semanticinfo/tagwidget.h b/gwenview/lib/semanticinfo/tagwidget.h new file mode 100644 index 00000000..ad8fb76d --- /dev/null +++ b/gwenview/lib/semanticinfo/tagwidget.h @@ -0,0 +1,65 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef TAGWIDGET_H +#define TAGWIDGET_H + +#include + +// Qt +#include +#include + +// KDE + +// Local +#include + +namespace Gwenview +{ + +typedef QMap TagInfo; + +struct TagWidgetPrivate; +class GWENVIEWLIB_EXPORT TagWidget : public QWidget +{ + Q_OBJECT +public: + TagWidget(QWidget* parent = 0); + ~TagWidget(); + void setTagInfo(const TagInfo&); + void setSemanticInfoBackEnd(AbstractSemanticInfoBackEnd*); + +Q_SIGNALS: + void tagAssigned(const SemanticInfoTag&); + void tagRemoved(const SemanticInfoTag&); + +private Q_SLOTS: + void addTagFromComboBox(); + void assignTag(const SemanticInfoTag& tag); + void removeTag(const SemanticInfoTag&); + +private: + TagWidgetPrivate* const d; +}; + +} // namespace + +#endif /* TAGWIDGET_H */ diff --git a/gwenview/lib/shadowfilter.cpp b/gwenview/lib/shadowfilter.cpp new file mode 100644 index 00000000..0ca38115 --- /dev/null +++ b/gwenview/lib/shadowfilter.cpp @@ -0,0 +1,112 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "shadowfilter.moc" + +// Local + +// KDE + +// Qt +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +struct ShadowFilterPrivate { + QWidget* mWidget; + QHash mShadows; + + void paintShadow(QPainter* painter, const QColor& color, QPoint origin, int dx, int dy) + { + const int gradientSize = 12; + + if (color == Qt::transparent) { + return; + } + QLinearGradient gradient; + gradient.setColorAt(0, color); + gradient.setColorAt(1, Qt::transparent); + gradient.setStart(origin); + gradient.setFinalStop(origin.x() + dx * gradientSize, origin.y() + dy * gradientSize); + painter->fillRect(mWidget->rect(), gradient); + } + + void paint() + { + QPainter painter(mWidget); + QRect rect = mWidget->rect(); + QColor color; + + color = mShadows.value(ShadowFilter::LeftEdge, Qt::transparent); + paintShadow(&painter, color, rect.topLeft(), 1, 0); + + color = mShadows.value(ShadowFilter::TopEdge, Qt::transparent); + paintShadow(&painter, color, rect.topLeft(), 0, 1); + + color = mShadows.value(ShadowFilter::RightEdge, Qt::transparent); + paintShadow(&painter, color, rect.topRight(), -1, 0); + + color = mShadows.value(ShadowFilter::BottomEdge, Qt::transparent); + paintShadow(&painter, color, rect.bottomLeft(), 0, -1); + } +}; + +ShadowFilter::ShadowFilter(QWidget* widget) +: QObject(widget) +, d(new ShadowFilterPrivate) +{ + d->mWidget = widget; + widget->installEventFilter(this); +} + +ShadowFilter::~ShadowFilter() +{ + delete d; +} + +void ShadowFilter::setShadow(ShadowFilter::WidgetEdge edge, const QColor& color) +{ + d->mShadows[edge] = color; +} + +void ShadowFilter::reset() +{ + d->mShadows.clear(); +} + +bool ShadowFilter::eventFilter(QObject* obj, QEvent* event) +{ + if (event->type() == QEvent::Paint) { + obj->removeEventFilter(this); + QCoreApplication::sendEvent(obj, event); + d->paint(); + obj->installEventFilter(this); + return true; + } + return false; +} + +} // namespace diff --git a/gwenview/lib/shadowfilter.h b/gwenview/lib/shadowfilter.h new file mode 100644 index 00000000..950082e8 --- /dev/null +++ b/gwenview/lib/shadowfilter.h @@ -0,0 +1,68 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef SHADOWFILTER_H +#define SHADOWFILTER_H + +#include + +// Local + +// KDE + +// Qt +#include + +class QColor; +class QWidget; + +namespace Gwenview +{ + +struct ShadowFilterPrivate; +/** + * Paint shadows on widget edges + */ +class GWENVIEWLIB_EXPORT ShadowFilter : public QObject +{ + Q_OBJECT +public: + enum WidgetEdge { + LeftEdge, + TopEdge, + RightEdge, + BottomEdge + }; + explicit ShadowFilter(QWidget* parent); + ~ShadowFilter(); + + void setShadow(WidgetEdge edge, const QColor& color); + void reset(); + +protected: + bool eventFilter(QObject*, QEvent*); // reimp + +private: + ShadowFilterPrivate* const d; +}; + +} // namespace + +#endif /* SHADOWFILTER_H */ diff --git a/gwenview/lib/signalblocker.h b/gwenview/lib/signalblocker.h new file mode 100644 index 00000000..58e48532 --- /dev/null +++ b/gwenview/lib/signalblocker.h @@ -0,0 +1,53 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef SIGNALBLOCKER_H +#define SIGNALBLOCKER_H + +#include + +namespace Gwenview +{ + +/** + * An RAII class to block and unblock signals from a QObject instance + */ +class SignalBlocker +{ +public: + SignalBlocker(QObject* object) + { + mObject = object; + mWasBlocked = object->blockSignals(true); + } + + ~SignalBlocker() + { + mObject->blockSignals(mWasBlocked); + } + +private: + QObject* mObject; + bool mWasBlocked; +}; + +} // namespace + +#endif /* SIGNALBLOCKER_H */ diff --git a/gwenview/lib/slidecontainer.cpp b/gwenview/lib/slidecontainer.cpp new file mode 100644 index 00000000..6eda64e9 --- /dev/null +++ b/gwenview/lib/slidecontainer.cpp @@ -0,0 +1,161 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "slidecontainer.moc" + +// KDE +#include + +// Qt +#include +#include +#include + +namespace Gwenview +{ + +static const int SLIDE_DURATION = 250; + +SlideContainer::SlideContainer(QWidget* parent) +: QFrame(parent) +{ + mContent = 0; + mSlidingOut = false; + setFixedHeight(0); +} + +QWidget* SlideContainer::content() const +{ + return mContent; +} + +void SlideContainer::setContent(QWidget* content) +{ + if (mContent) { + mContent->setParent(0); + mContent->removeEventFilter(this); + } + mContent = content; + if (mContent) { + mContent->setParent(this); + mContent->installEventFilter(this); + mContent->hide(); + } +} + +void SlideContainer::animTo(int newHeight) +{ + delete mAnim.data(); + QPropertyAnimation* anim = new QPropertyAnimation(this, "slideHeight", this); + anim->setDuration(SLIDE_DURATION); + anim->setStartValue(slideHeight()); + anim->setEndValue(newHeight); + anim->start(QAbstractAnimation::DeleteWhenStopped); + connect(anim, SIGNAL(finished()), SLOT(slotAnimFinished())); + mAnim = anim; +} + +void SlideContainer::slideIn() +{ + mSlidingOut = false; + mContent->show(); + mContent->adjustSize(); + delete mAnim.data(); + if (height() == mContent->height()) { + return; + } + animTo(mContent->height()); +} + +void SlideContainer::slideOut() +{ + if (height() == 0) { + return; + } + mSlidingOut = true; + animTo(0); +} + +QSize SlideContainer::sizeHint() const +{ + if (mContent) { + return mContent->sizeHint(); + } else { + return QSize(); + } +} + +QSize SlideContainer::minimumSizeHint() const +{ + if (mContent) { + return mContent->minimumSizeHint(); + } else { + return QSize(); + } +} + +void SlideContainer::resizeEvent(QResizeEvent* event) +{ + if (mContent) { + if (event->oldSize().width() != width()) { + adjustContentGeometry(); + } + } +} + +void SlideContainer::adjustContentGeometry() +{ + if (mContent) { + mContent->setGeometry(0, height() - mContent->height(), width(), mContent->height()); + } +} + +bool SlideContainer::eventFilter(QObject*, QEvent* event) +{ + if (event->type() == QEvent::Resize) { + if (!mSlidingOut && height() != 0) { + animTo(mContent->height()); + } + } + return false; +} + +int SlideContainer::slideHeight() const +{ + return isVisible() ? height() : 0; +} + +void SlideContainer::setSlideHeight(int value) +{ + setFixedHeight(value); + adjustContentGeometry(); +} + +void SlideContainer::slotAnimFinished() +{ + if (height() == 0) { + mSlidingOut = false; + slidedOut(); + } else { + slidedIn(); + } +} + +} // namespace diff --git a/gwenview/lib/slidecontainer.h b/gwenview/lib/slidecontainer.h new file mode 100644 index 00000000..d7ec0a9b --- /dev/null +++ b/gwenview/lib/slidecontainer.h @@ -0,0 +1,100 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef SLIDECONTAINER_H +#define SLIDECONTAINER_H + +// Qt +#include +#include + +#include + +class QPropertyAnimation; + +namespace Gwenview +{ + +/** + * This widget is design to contain one child widget, the "content" widget. + * It will start hidden by default. Calling slideIn() will slide in the content + * widget from the top border. Calling slideOut() will slide it out. + */ +class GWENVIEWLIB_EXPORT SlideContainer : public QFrame +{ + Q_OBJECT + Q_PROPERTY(int slideHeight READ slideHeight WRITE setSlideHeight) +public: + SlideContainer(QWidget* parent = 0); + + /** + * Returns the content widget + */ + QWidget* content() const; + + /** + * Defines the content widget + */ + void setContent(QWidget* content); + + virtual QSize sizeHint() const; + + virtual QSize minimumSizeHint() const; + + int slideHeight() const; + + Q_INVOKABLE void setSlideHeight(int height); + +public Q_SLOTS: + /** + * Slides the content widget in. + * Calling it multiple times won't cause the animation to be replayed. + */ + void slideIn(); + + /** + * Slides the content widget out. + * Calling it multiple times won't cause the animation to be replayed. + */ + void slideOut(); + +Q_SIGNALS: + void slidedIn(); + void slidedOut(); + +protected: + void resizeEvent(QResizeEvent*); + bool eventFilter(QObject*, QEvent* event); + +private Q_SLOTS: + void slotAnimFinished(); + +private: + QWidget* mContent; + QWeakPointer mAnim; + bool mSlidingOut; + + void adjustContentGeometry(); + + void animTo(int height); +}; + +} /* namespace */ + +#endif /* SLIDECONTAINER_H */ diff --git a/gwenview/lib/slideshow.cpp b/gwenview/lib/slideshow.cpp new file mode 100644 index 00000000..c4aee66f --- /dev/null +++ b/gwenview/lib/slideshow.cpp @@ -0,0 +1,306 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "slideshow.moc" + +// libc +#include + +// STL +#include + +// Qt +#include +#include + +// KDE +#include +#include +#include +#include + +// Local +#include +#include + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +enum State { + Stopped, + Started, + WaitForEndOfUrl +}; + +/** + * This class generate random numbers which are not the same between two runs + * of Gwenview. See bug #132334 + */ +class RandomNumberGenerator +{ +public: + RandomNumberGenerator() + : mSeed(time(0)) + { + } + + int operator()(int n) + { + return rand_r(&mSeed) % n; + } + +private: + unsigned int mSeed; +}; + +struct SlideShowPrivate +{ + QTimer* mTimer; + State mState; + QVector mUrls; + QVector mShuffledUrls; + QVector::ConstIterator mStartIt; + KUrl mCurrentUrl; + KUrl mLastShuffledUrl; + + QAction* mLoopAction; + QAction* mRandomAction; + + KUrl findNextUrl() + { + if (GwenviewConfig::random()) { + return findNextRandomUrl(); + } else { + return findNextOrderedUrl(); + } + } + + KUrl findNextOrderedUrl() + { + QVector::ConstIterator it = qFind(mUrls.constBegin(), mUrls.constEnd(), mCurrentUrl); + GV_RETURN_VALUE_IF_FAIL2(it != mUrls.constEnd(), KUrl(), "Current url not found in list."); + + ++it; + if (GwenviewConfig::loop()) { + // Looping, if we reach the end, start again + if (it == mUrls.constEnd()) { + it = mUrls.constBegin(); + } + } else { + // Not looping, have we reached the end? + // FIXME: stopAtEnd + if (/*(it==mUrls.end() && GwenviewConfig::stopAtEnd()) ||*/ it == mStartIt) { + it = mUrls.constEnd(); + } + } + + if (it != mUrls.constEnd()) { + return *it; + } else { + return KUrl(); + } + } + + void initShuffledUrls() + { + mShuffledUrls = mUrls; + RandomNumberGenerator generator; + std::random_shuffle(mShuffledUrls.begin(), mShuffledUrls.end(), generator); + // Ensure the first url is different from the previous last one, so that + // last url does not stay visible twice longer than usual + if (mLastShuffledUrl == mShuffledUrls.first() && mShuffledUrls.count() > 1) { + qSwap(mShuffledUrls[0], mShuffledUrls[1]); + } + mLastShuffledUrl = mShuffledUrls.last(); + } + + KUrl findNextRandomUrl() + { + if (mShuffledUrls.empty()) { + if (GwenviewConfig::loop()) { + initShuffledUrls(); + } else { + return KUrl(); + } + } + + KUrl url = mShuffledUrls.last(); + mShuffledUrls.pop_back(); + + return url; + } + + void updateTimerInterval() + { + mTimer->setInterval(int(GwenviewConfig::interval() * 1000)); + } + + void doStart() + { + if (MimeTypeUtils::urlKind(mCurrentUrl) == MimeTypeUtils::KIND_VIDEO) { + LOG("mState = WaitForEndOfUrl"); + // Just in case + mTimer->stop(); + mState = WaitForEndOfUrl; + } else { + LOG("mState = Started"); + mTimer->start(); + mState = Started; + } + } +}; + +SlideShow::SlideShow(QObject* parent) +: QObject(parent) +, d(new SlideShowPrivate) +{ + d->mState = Stopped; + + d->mTimer = new QTimer(this); + connect(d->mTimer, SIGNAL(timeout()), + this, SLOT(goToNextUrl())); + + d->mLoopAction = new QAction(this); + d->mLoopAction->setText(i18nc("@item:inmenu toggle loop in slideshow", "Loop")); + d->mLoopAction->setCheckable(true); + connect(d->mLoopAction, SIGNAL(triggered()), SLOT(updateConfig())); + + d->mRandomAction = new QAction(this); + d->mRandomAction->setText(i18nc("@item:inmenu toggle random order in slideshow", "Random")); + d->mRandomAction->setCheckable(true); + connect(d->mRandomAction, SIGNAL(toggled(bool)), SLOT(slotRandomActionToggled(bool))); + connect(d->mRandomAction, SIGNAL(triggered()), SLOT(updateConfig())); + + d->mLoopAction->setChecked(GwenviewConfig::loop()); + d->mRandomAction->setChecked(GwenviewConfig::random()); +} + +SlideShow::~SlideShow() +{ + GwenviewConfig::self()->writeConfig(); + delete d; +} + +QAction* SlideShow::loopAction() const +{ + return d->mLoopAction; +} + +QAction* SlideShow::randomAction() const +{ + return d->mRandomAction; +} + +void SlideShow::start(const QList& urls) +{ + d->mUrls.resize(urls.size()); + qCopy(urls.begin(), urls.end(), d->mUrls.begin()); + + d->mStartIt = qFind(d->mUrls.constBegin(), d->mUrls.constEnd(), d->mCurrentUrl); + if (d->mStartIt == d->mUrls.constEnd()) { + kWarning() << "Current url not found in list, aborting.\n"; + return; + } + + if (GwenviewConfig::random()) { + d->initShuffledUrls(); + } + + d->updateTimerInterval(); + d->mTimer->setSingleShot(false); + d->doStart(); + stateChanged(true); +} + +void SlideShow::setInterval(int intervalInSeconds) +{ + GwenviewConfig::setInterval(double(intervalInSeconds)); + d->updateTimerInterval(); +} + +void SlideShow::stop() +{ + LOG("Stopping timer"); + d->mTimer->stop(); + d->mState = Stopped; + stateChanged(false); +} + +void SlideShow::resumeAndGoToNextUrl() +{ + LOG(""); + if (d->mState == WaitForEndOfUrl) { + goToNextUrl(); + } +} + +void SlideShow::goToNextUrl() +{ + LOG(""); + KUrl url = d->findNextUrl(); + LOG("url:" << url); + if (!url.isValid()) { + stop(); + return; + } + goToUrl(url); +} + +void SlideShow::setCurrentUrl(const KUrl& url) +{ + LOG(url); + if (d->mCurrentUrl == url) { + return; + } + d->mCurrentUrl = url; + // Restart timer to avoid showing new url for the remaining time of the old + // url + if (d->mState != Stopped) { + d->doStart(); + } +} + +bool SlideShow::isRunning() const +{ + return d->mState != Stopped; +} + +void SlideShow::updateConfig() +{ + GwenviewConfig::setLoop(d->mLoopAction->isChecked()); + GwenviewConfig::setRandom(d->mRandomAction->isChecked()); +} + +void SlideShow::slotRandomActionToggled(bool on) +{ + if (on && d->mState != Stopped) { + d->initShuffledUrls(); + } +} + +} // namespace diff --git a/gwenview/lib/slideshow.h b/gwenview/lib/slideshow.h new file mode 100644 index 00000000..33e66b41 --- /dev/null +++ b/gwenview/lib/slideshow.h @@ -0,0 +1,81 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef SLIDESHOW_H +#define SLIDESHOW_H + +#include + +// Qt +#include + +// KDE +#include + +class QAction; + +namespace Gwenview +{ + +struct SlideShowPrivate; +class GWENVIEWLIB_EXPORT SlideShow : public QObject +{ + Q_OBJECT +public: + SlideShow(QObject* parent); + virtual ~SlideShow(); + + void start(const QList& urls); + void stop(); + + QAction* loopAction() const; + QAction* randomAction() const; + + /** @return true if the slideshow is running */ + bool isRunning() const; + +public Q_SLOTS: + void setInterval(int); + void setCurrentUrl(const KUrl& url); + + /** + * Resume slideshow and go to next url. + */ + void resumeAndGoToNextUrl(); + +Q_SIGNALS: + void goToUrl(const KUrl&); + /** + * Slideshow has been started or stopped + */ + void stateChanged(bool running); + +private Q_SLOTS: + void goToNextUrl(); + void updateConfig(); + void slotRandomActionToggled(bool on); + +private: + SlideShowPrivate* const d; +}; + +} // namespace + +#endif // SLIDESHOW_H diff --git a/gwenview/lib/sorting.h b/gwenview/lib/sorting.h new file mode 100644 index 00000000..bdca12bc --- /dev/null +++ b/gwenview/lib/sorting.h @@ -0,0 +1,50 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef SORTING_H +#define SORTING_H + +// Qt + +// KDE + +// Local + +namespace Gwenview +{ + +namespace Sorting +{ +/** + * This enum represents the different sorting orders. + * For now it maps with KDirModel::ModelColumns, but in the future it may + * be possible to add custom sort orders like Rating. + */ +enum Enum { + Name, + Size, + Date +}; + +} // namespace Sorting + +} // namespace Gwenview + +#endif /* SORTING_H */ diff --git a/gwenview/lib/statusbartoolbutton.cpp b/gwenview/lib/statusbartoolbutton.cpp new file mode 100644 index 00000000..a3c14d5d --- /dev/null +++ b/gwenview/lib/statusbartoolbutton.cpp @@ -0,0 +1,111 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include + +// Qt +#include +#include +#include +#include + +// KDE +#include + +namespace Gwenview +{ + +StatusBarToolButton::StatusBarToolButton(QWidget* parent) +: QToolButton(parent) +, mGroupPosition(NotGrouped) +{ + setToolButtonStyle(Qt::ToolButtonTextOnly); + setFocusPolicy(Qt::NoFocus); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); +} + +void StatusBarToolButton::setGroupPosition(StatusBarToolButton::GroupPosition groupPosition) +{ + mGroupPosition = groupPosition; +} + +void StatusBarToolButton::paintEvent(QPaintEvent* event) +{ + if (mGroupPosition == NotGrouped) { + QToolButton::paintEvent(event); + return; + } + QStylePainter painter(this); + QStyleOptionToolButton opt; + initStyleOption(&opt); + QStyleOptionToolButton panelOpt = opt; + + // Panel + QRect& panelRect = panelOpt.rect; + switch (mGroupPosition) { + case GroupLeft: + panelRect.setWidth(panelRect.width() * 2); + break; + case GroupCenter: + panelRect.setLeft(panelRect.left() - panelRect.width()); + panelRect.setWidth(panelRect.width() * 3); + break; + case GroupRight: + panelRect.setLeft(panelRect.left() - panelRect.width()); + break; + case NotGrouped: + Q_ASSERT(0); + } + painter.drawPrimitive(QStyle::PE_PanelButtonTool, panelOpt); + + // Separator + const int y1 = opt.rect.top() + 6; + const int y2 = opt.rect.bottom() - 6; + if (mGroupPosition & GroupRight) { + const int x = opt.rect.left(); + painter.setPen(opt.palette.color(QPalette::Light)); + painter.drawLine(x, y1, x, y2); + } + if (mGroupPosition & GroupLeft) { + const int x = opt.rect.right(); + painter.setPen(opt.palette.color(QPalette::Mid)); + painter.drawLine(x, y1, x, y2); + } + + // Text + painter.drawControl(QStyle::CE_ToolButtonLabel, opt); + + // Filtering message on tooltip text for CJK to remove accelerators. + // Quoting ktoolbar.cpp: + // """ + // CJK languages use more verbose accelerator marker: they add a Latin + // letter in parenthesis, and put accelerator on that. Hence, the default + // removal of ampersand only may not be enough there, instead the whole + // parenthesis construct should be removed. Provide these filtering i18n + // messages so that translators can use Transcript for custom removal. + // """ + if (!actions().isEmpty()) { + QAction* action = actions().first(); + setToolTip(i18nc("@info:tooltip of custom toolbar button", "%1", action->toolTip())); + } +} + +} // namespace diff --git a/gwenview/lib/statusbartoolbutton.h b/gwenview/lib/statusbartoolbutton.h new file mode 100644 index 00000000..2d7f4a59 --- /dev/null +++ b/gwenview/lib/statusbartoolbutton.h @@ -0,0 +1,62 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef STATUSBARTOOLBUTTON_H +#define STATUSBARTOOLBUTTON_H + +#include + +// Qt +#include + +namespace Gwenview +{ + +/** + * A thin tool button which can be grouped with another and look like one solid + * bar: + * + * ( button1 | button2 ) + */ +class GWENVIEWLIB_EXPORT StatusBarToolButton : public QToolButton +{ + Q_OBJECT +public: + enum GroupPosition { + NotGrouped = 0, + GroupLeft = 1, + GroupRight = 2, + GroupCenter = 3 + }; + + StatusBarToolButton(QWidget* parent = 0); + + void setGroupPosition(StatusBarToolButton::GroupPosition groupPosition); + +protected: + virtual void paintEvent(QPaintEvent* event); + +private: + GroupPosition mGroupPosition; +}; + +} // namespace + +#endif /* STATUSBARTOOLBUTTON_H */ diff --git a/gwenview/lib/thumbnailgroup.h b/gwenview/lib/thumbnailgroup.h new file mode 100644 index 00000000..b95c44f6 --- /dev/null +++ b/gwenview/lib/thumbnailgroup.h @@ -0,0 +1,61 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef THUMBNAILGROUP_H +#define THUMBNAILGROUP_H + +// Qt + +// KDE + +// Local + +namespace Gwenview +{ + +namespace ThumbnailGroup +{ +enum Enum { + Normal, + Large +}; + +inline int pixelSize(Enum value) +{ + if (value == Normal) { + return 128; + } else { + return 256; + } +} + +inline Enum fromPixelSize(int value) +{ + if (value <= 128) { + return Normal; + } else { + return Large; + } +} +} // namespace ThumbnailGroup + +} // namespace Gwenview + +#endif /* THUMBNAILGROUP_H */ diff --git a/gwenview/lib/thumbnailprovider/thumbnailgenerator.cpp b/gwenview/lib/thumbnailprovider/thumbnailgenerator.cpp new file mode 100644 index 00000000..f8963990 --- /dev/null +++ b/gwenview/lib/thumbnailprovider/thumbnailgenerator.cpp @@ -0,0 +1,314 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include + +// Local +#include "imageutils.h" +#include "jpegcontent.h" +#include "gwenviewconfig.h" +#include "exiv2imageloader.h" + +// KDE +#include +#include + +// Qt +#include +#include +#include + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +const int MIN_PREV_SIZE = 1000; + +//------------------------------------------------------------------------ +// +// ThumbnailContext +// +//------------------------------------------------------------------------ +bool ThumbnailContext::load(const QString &pixPath, int pixelSize) +{ + mImage = QImage(); + mNeedCaching = true; + Orientation orientation = NORMAL; + QImage originalImage; + QSize originalSize; + + QByteArray formatHint = pixPath.section('.', -1).toAscii().toLower(); + QImageReader reader(pixPath); + + JpegContent content; + QByteArray format; + QByteArray data; + QBuffer buffer; + int previewRatio = 1; + + // raw images deserve special treatment + if (KDcrawIface::KDcraw::rawFilesList().contains(QString(formatHint))) { + // use KDCraw to extract the preview + bool ret = KDcrawIface::KDcraw::loadEmbeddedPreview(data, pixPath); + + // We need QImage. Loading JpegContent from QImage - exif lost + // Loading QImage from JpegContent - unimplemented, would go with loadFromData + if (!ret || !originalImage.loadFromData(data) || qMin(originalImage.width(), originalImage.height()) < MIN_PREV_SIZE) { + // if the emebedded preview loading failed or gets just a small image, load + // half preview instead. That's slower... + if (!KDcrawIface::KDcraw::loadHalfPreview(data, pixPath)) { + kWarning() << "unable to get preview for " << pixPath.toUtf8().constData(); + return false; + } + previewRatio = 2; + } + + // And we need JpegContent too because of EXIF (orientation!). + if (!content.loadFromData(data)) { + kWarning() << "unable to load preview for " << pixPath.toUtf8().constData(); + return false; + } + + buffer.setBuffer(&data); + buffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + reader.setFormat(formatHint); + } else { + if (!reader.canRead()) { + reader.setDecideFormatFromContent(true); + // Set filename again, otherwise QImageReader won't restart from scratch + reader.setFileName(pixPath); + } + + if (reader.format() == "jpeg" && GwenviewConfig::applyExifOrientation()) { + content.load(pixPath); + } + } + + // If there's jpeg content (from jpg or raw files), try to load an embedded thumbnail, if available. + // If applyExifOrientation is not set, don't use the + // embedded thumbnail since it might be rotated differently + // than the actual image + if (!content.rawData().isEmpty() && GwenviewConfig::applyExifOrientation()) { + QImage thumbnail = content.thumbnail(); + orientation = content.orientation(); + + if (qMax(thumbnail.width(), thumbnail.height()) >= pixelSize) { + mImage = thumbnail; + if (orientation != NORMAL && orientation != NOT_AVAILABLE) { + QMatrix matrix = ImageUtils::transformMatrix(orientation); + mImage = mImage.transformed(matrix); + } + mOriginalWidth = content.size().width(); + mOriginalHeight = content.size().height(); + return true; + } + } + + // Generate thumbnail from full image + originalSize = reader.size(); + if (originalSize.isValid() && reader.supportsOption(QImageIOHandler::ScaledSize)) { + QSizeF scaledSize = originalSize; + scaledSize.scale(pixelSize, pixelSize, Qt::KeepAspectRatio); + if (!scaledSize.isEmpty()) { + reader.setScaledSize(scaledSize.toSize()); + } + } + + // format() is empty after QImageReader::read() is called + format = reader.format(); + if (!reader.read(&originalImage)) { + return false; + } + + if (!originalSize.isValid()) { + originalSize = originalImage.size(); + } + mOriginalWidth = originalSize.width() * previewRatio; + mOriginalHeight = originalSize.height() * previewRatio; + + if (qMax(mOriginalWidth, mOriginalHeight) <= pixelSize) { + mImage = originalImage; + mNeedCaching = format != "png"; + } else { + mImage = originalImage.scaled(pixelSize, pixelSize, Qt::KeepAspectRatio); + } + + // Rotate if necessary + if (orientation != NORMAL && orientation != NOT_AVAILABLE && GwenviewConfig::applyExifOrientation()) { + QMatrix matrix = ImageUtils::transformMatrix(orientation); + mImage = mImage.transformed(matrix); + + switch (orientation) { + case TRANSPOSE: + case ROT_90: + case TRANSVERSE: + case ROT_270: + qSwap(mOriginalWidth, mOriginalHeight); + break; + default: + break; + } + } + return true; +} + +//------------------------------------------------------------------------ +// +// ThumbnailGenerator +// +//------------------------------------------------------------------------ +ThumbnailGenerator::ThumbnailGenerator() +: mCancel(false) +{} + +void ThumbnailGenerator::load( + const QString& originalUri, time_t originalTime, KIO::filesize_t originalFileSize, const QString& originalMimeType, + const QString& pixPath, + const QString& thumbnailPath, + ThumbnailGroup::Enum group) +{ + QMutexLocker lock(&mMutex); + Q_ASSERT(mPixPath.isNull()); + + mOriginalUri = originalUri; + mOriginalTime = originalTime; + mOriginalFileSize = originalFileSize; + mOriginalMimeType = originalMimeType; + mPixPath = pixPath; + mThumbnailPath = thumbnailPath; + mThumbnailGroup = group; + if (!isRunning()) start(); + mCond.wakeOne(); +} + +QString ThumbnailGenerator::originalUri() const +{ + return mOriginalUri; +} + +time_t ThumbnailGenerator::originalTime() const +{ + return mOriginalTime; +} + +KIO::filesize_t ThumbnailGenerator::originalFileSize() const +{ + return mOriginalFileSize; +} + +QString ThumbnailGenerator::originalMimeType() const +{ + return mOriginalMimeType; +} + +bool ThumbnailGenerator::testCancel() +{ + QMutexLocker lock(&mMutex); + return mCancel; +} + +void ThumbnailGenerator::cancel() +{ + QMutexLocker lock(&mMutex); + mCancel = true; + mCond.wakeOne(); +} + +void ThumbnailGenerator::run() +{ + LOG(""); + while (!testCancel()) { + QString pixPath; + int pixelSize; + { + QMutexLocker lock(&mMutex); + // empty mPixPath means nothing to do + LOG("Waiting for mPixPath"); + if (mPixPath.isNull()) { + LOG("mPixPath.isNull"); + mCond.wait(&mMutex); + } + } + if (testCancel()) { + return; + } + { + QMutexLocker lock(&mMutex); + pixPath = mPixPath; + pixelSize = ThumbnailGroup::pixelSize(mThumbnailGroup); + } + + Q_ASSERT(!pixPath.isNull()); + LOG("Loading" << pixPath); + ThumbnailContext context; + bool ok = context.load(pixPath, pixelSize); + + { + QMutexLocker lock(&mMutex); + if (ok) { + mImage = context.mImage; + mOriginalWidth = context.mOriginalWidth; + mOriginalHeight = context.mOriginalHeight; + if (context.mNeedCaching) { + cacheThumbnail(); + } + } else { + kWarning() << "Could not generate thumbnail for file" << mOriginalUri; + } + mPixPath.clear(); // done, ready for next + } + if (testCancel()) { + return; + } + { + QSize size(mOriginalWidth, mOriginalHeight); + LOG("emitting done signal, size=" << size); + QMutexLocker lock(&mMutex); + done(mImage, size); + LOG("Done"); + } + } + LOG("Ending thread"); +} + +void ThumbnailGenerator::cacheThumbnail() +{ + mImage.setText("Thumb::URI" , 0, mOriginalUri); + mImage.setText("Thumb::MTime" , 0, QString::number(mOriginalTime)); + mImage.setText("Thumb::Size" , 0, QString::number(mOriginalFileSize)); + mImage.setText("Thumb::Mimetype" , 0, mOriginalMimeType); + mImage.setText("Thumb::Image::Width" , 0, QString::number(mOriginalWidth)); + mImage.setText("Thumb::Image::Height", 0, QString::number(mOriginalHeight)); + mImage.setText("Software" , 0, "Gwenview"); + + emit thumbnailReadyToBeCached(mThumbnailPath, mImage); +} + +} // namespace diff --git a/gwenview/lib/thumbnailprovider/thumbnailgenerator.h b/gwenview/lib/thumbnailprovider/thumbnailgenerator.h new file mode 100644 index 00000000..e81c75f2 --- /dev/null +++ b/gwenview/lib/thumbnailprovider/thumbnailgenerator.h @@ -0,0 +1,96 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef THUMBNAILGENERATOR_H +#define THUMBNAILGENERATOR_H + +// Local +#include + +// KDE +#include + +// Qt +#include +#include +#include +#include + +namespace Gwenview +{ + +struct ThumbnailContext { + QImage mImage; + int mOriginalWidth; + int mOriginalHeight; + bool mNeedCaching; + + bool load(const QString &pixPath, int pixelSize); +}; + +class ThumbnailGenerator : public QThread +{ + Q_OBJECT +public: + ThumbnailGenerator(); + + void load( + const QString& originalUri, + time_t originalTime, + KIO::filesize_t originalFileSize, + const QString& originalMimeType, + const QString& pixPath, + const QString& thumbnailPath, + ThumbnailGroup::Enum group); + + void cancel(); + + QString originalUri() const; + time_t originalTime() const; + KIO::filesize_t originalFileSize() const; + QString originalMimeType() const; +protected: + virtual void run(); + +Q_SIGNALS: + void done(const QImage&, const QSize&); + void thumbnailReadyToBeCached(const QString& thumbnailPath, const QImage&); + +private: + bool testCancel(); + void cacheThumbnail(); + QImage mImage; + QString mPixPath; + QString mThumbnailPath; + QString mOriginalUri; + time_t mOriginalTime; + KIO::filesize_t mOriginalFileSize; + QString mOriginalMimeType; + int mOriginalWidth; + int mOriginalHeight; + QMutex mMutex; + QWaitCondition mCond; + ThumbnailGroup::Enum mThumbnailGroup; + bool mCancel; +}; + +} // namespace + +#endif /* THUMBNAILGENERATOR_H */ diff --git a/gwenview/lib/thumbnailprovider/thumbnailprovider.cpp b/gwenview/lib/thumbnailprovider/thumbnailprovider.cpp new file mode 100644 index 00000000..b7a3c206 --- /dev/null +++ b/gwenview/lib/thumbnailprovider/thumbnailprovider.cpp @@ -0,0 +1,587 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* Gwenview - A simple image viewer for KDE + Copyright 2000-2007 Aurélien Gâteau + This class is based on the ImagePreviewJob class from Konqueror. +*/ +/* This file is part of the KDE project + Copyright (C) 2000 David Faure + 2000 Carsten Pfeiffer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +#include "thumbnailprovider.moc" + +#include +#include +#include + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "mimetypeutils.h" +#include "thumbnailwriter.h" +#include "thumbnailgenerator.h" +#include "urlutils.h" + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +K_GLOBAL_STATIC(ThumbnailWriter, sThumbnailWriter) + +static QString generateOriginalUri(const KUrl& url_) +{ + KUrl url = url_; + // Don't include the password if any + url.setPass(QString::null); //krazy:exclude=nullstrassign for old broken gcc + return url.url(); +} + +static QString generateThumbnailPath(const QString& uri, ThumbnailGroup::Enum group) +{ + KMD5 md5(QFile::encodeName(uri)); + QString baseDir = ThumbnailProvider::thumbnailBaseDir(group); + return baseDir + QString(QFile::encodeName(md5.hexDigest())) + ".png"; +} + +//------------------------------------------------------------------------ +// +// ThumbnailProvider static methods +// +//------------------------------------------------------------------------ +static QString sThumbnailBaseDir; +QString ThumbnailProvider::thumbnailBaseDir() +{ + if (sThumbnailBaseDir.isEmpty()) { + const QByteArray customDir = qgetenv("GV_THUMBNAIL_DIR"); + if (customDir.isEmpty()) { + sThumbnailBaseDir = QDir::homePath() + "/.thumbnails/"; + } else { + sThumbnailBaseDir = QString::fromLocal8Bit(customDir.constData()) + '/'; + } + } + return sThumbnailBaseDir; +} + +void ThumbnailProvider::setThumbnailBaseDir(const QString& dir) +{ + sThumbnailBaseDir = dir; +} + +QString ThumbnailProvider::thumbnailBaseDir(ThumbnailGroup::Enum group) +{ + QString dir = thumbnailBaseDir(); + switch (group) { + case ThumbnailGroup::Normal: + dir += "normal/"; + break; + case ThumbnailGroup::Large: + dir += "large/"; + break; + } + return dir; +} + +void ThumbnailProvider::deleteImageThumbnail(const KUrl& url) +{ + QString uri = generateOriginalUri(url); + QFile::remove(generateThumbnailPath(uri, ThumbnailGroup::Normal)); + QFile::remove(generateThumbnailPath(uri, ThumbnailGroup::Large)); +} + +static void moveThumbnailHelper(const QString& oldUri, const QString& newUri, ThumbnailGroup::Enum group) +{ + QString oldPath = generateThumbnailPath(oldUri, group); + QString newPath = generateThumbnailPath(newUri, group); + QImage thumb; + if (!thumb.load(oldPath)) { + return; + } + thumb.setText("Thumb::URI", 0, newUri); + thumb.save(newPath, "png"); + QFile::remove(QFile::encodeName(oldPath)); +} + +void ThumbnailProvider::moveThumbnail(const KUrl& oldUrl, const KUrl& newUrl) +{ + QString oldUri = generateOriginalUri(oldUrl); + QString newUri = generateOriginalUri(newUrl); + moveThumbnailHelper(oldUri, newUri, ThumbnailGroup::Normal); + moveThumbnailHelper(oldUri, newUri, ThumbnailGroup::Large); +} + +//------------------------------------------------------------------------ +// +// ThumbnailProvider implementation +// +//------------------------------------------------------------------------ +ThumbnailProvider::ThumbnailProvider() +: KIO::Job() +, mState(STATE_NEXTTHUMB) +, mOriginalTime(0) +{ + LOG(this); + + // Make sure we have a place to store our thumbnails + QString thumbnailDirNormal = ThumbnailProvider::thumbnailBaseDir(ThumbnailGroup::Normal); + QString thumbnailDirLarge = ThumbnailProvider::thumbnailBaseDir(ThumbnailGroup::Large); + KStandardDirs::makeDir(thumbnailDirNormal, 0700); + KStandardDirs::makeDir(thumbnailDirLarge, 0700); + + // Look for images and store the items in our todo list + mCurrentItem = KFileItem(); + mThumbnailGroup = ThumbnailGroup::Large; + createNewThumbnailGenerator(); +} + +ThumbnailProvider::~ThumbnailProvider() +{ + LOG(this); + abortSubjob(); + mThumbnailGenerator->cancel(); + disconnect(mThumbnailGenerator, 0, this, 0); + disconnect(mThumbnailGenerator, 0, sThumbnailWriter, 0); + connect(mThumbnailGenerator, SIGNAL(finished()), mThumbnailGenerator, SLOT(deleteLater())); + if (mPreviousThumbnailGenerator) { + disconnect(mPreviousThumbnailGenerator, 0, sThumbnailWriter, 0); + } + sThumbnailWriter->wait(); +} + +void ThumbnailProvider::stop() +{ + // Clear mItems and create a new ThumbnailGenerator if mThumbnailGenerator is running, + // but also make sure that at most two ThumbnailGenerators are running. + // startCreatingThumbnail() will take care that these two threads won't work on the same item. + mItems.clear(); + abortSubjob(); + if (mThumbnailGenerator->isRunning() && !mPreviousThumbnailGenerator) { + mPreviousThumbnailGenerator = mThumbnailGenerator; + mPreviousThumbnailGenerator->cancel(); + disconnect(mPreviousThumbnailGenerator, 0, this, 0); + connect(mPreviousThumbnailGenerator, SIGNAL(finished()), mPreviousThumbnailGenerator, SLOT(deleteLater())); + createNewThumbnailGenerator(); + mCurrentItem = KFileItem(); + } +} + +const KFileItemList& ThumbnailProvider::pendingItems() const +{ + return mItems; +} + +void ThumbnailProvider::setThumbnailGroup(ThumbnailGroup::Enum group) +{ + mThumbnailGroup = group; +} + +void ThumbnailProvider::appendItems(const KFileItemList& items) +{ + if (!mItems.isEmpty()) { + QSet itemSet; + Q_FOREACH(const KFileItem & item, mItems) { + itemSet.insert(item.url().url()); + } + + Q_FOREACH(const KFileItem & item, items) { + if (!itemSet.contains(item.url().url())) { + mItems.append(item); + } + } + } else { + mItems = items; + } + + if (mCurrentItem.isNull()) { + determineNextIcon(); + } +} + +void ThumbnailProvider::removeItems(const KFileItemList& itemList) +{ + if (mItems.isEmpty()) { + return; + } + Q_FOREACH(const KFileItem & item, itemList) { + // If we are removing the next item, update to be the item after or the + // first if we removed the last item + mItems.removeAll(item); + + if (item == mCurrentItem) { + abortSubjob(); + } + } + + // No more current item, carry on to the next remaining item + if (mCurrentItem.isNull()) { + determineNextIcon(); + } +} + +void ThumbnailProvider::removePendingItems() +{ + mItems.clear(); +} + +bool ThumbnailProvider::isRunning() const +{ + return !mCurrentItem.isNull(); +} + +//-Internal-------------------------------------------------------------- +void ThumbnailProvider::createNewThumbnailGenerator() +{ + mThumbnailGenerator = new ThumbnailGenerator; + connect(mThumbnailGenerator, SIGNAL(done(QImage,QSize)), + SLOT(thumbnailReady(QImage,QSize)), + Qt::QueuedConnection); + + connect(mThumbnailGenerator, SIGNAL(thumbnailReadyToBeCached(QString,QImage)), + sThumbnailWriter, SLOT(queueThumbnail(QString,QImage)), + Qt::QueuedConnection); +} + +void ThumbnailProvider::abortSubjob() +{ + if (hasSubjobs()) { + LOG("Killing subjob"); + KJob* job = subjobs().first(); + job->kill(); + removeSubjob(job); + mCurrentItem = KFileItem(); + } +} + +void ThumbnailProvider::determineNextIcon() +{ + LOG(this); + mState = STATE_NEXTTHUMB; + + // No more items ? + if (mItems.isEmpty()) { + LOG("No more items. Nothing to do"); + mCurrentItem = KFileItem(); + finished(); + return; + } + + mCurrentItem = mItems.takeFirst(); + LOG("mCurrentItem.url=" << mCurrentItem.url()); + + // First, stat the orig file + mState = STATE_STATORIG; + mCurrentUrl = mCurrentItem.url(); + mCurrentUrl.cleanPath(); + mOriginalFileSize = mCurrentItem.size(); + + // Do direct stat instead of using KIO if the file is local (faster) + bool directStatOk = false; + if (UrlUtils::urlIsFastLocalFile(mCurrentUrl)) { + KDE_struct_stat buff; + if (KDE::stat(mCurrentUrl.toLocalFile(), &buff) == 0) { + directStatOk = true; + mOriginalTime = buff.st_mtime; + QMetaObject::invokeMethod(this, "checkThumbnail", Qt::QueuedConnection); + } + } + if (!directStatOk) { + KIO::Job* job = KIO::stat(mCurrentUrl, KIO::HideProgressInfo); + job->ui()->setWindow(KApplication::kApplication()->activeWindow()); + LOG("KIO::stat orig" << mCurrentUrl.url()); + addSubjob(job); + } + LOG("/determineNextIcon" << this); +} + +void ThumbnailProvider::slotResult(KJob * job) +{ + LOG(mState); + removeSubjob(job); + Q_ASSERT(subjobs().isEmpty()); // We should have only one job at a time + + switch (mState) { + case STATE_NEXTTHUMB: + Q_ASSERT(false); + determineNextIcon(); + return; + + case STATE_STATORIG: { + // Could not stat original, drop this one and move on to the next one + if (job->error()) { + emitThumbnailLoadingFailed(); + determineNextIcon(); + return; + } + + // Get modification time of the original file + KIO::UDSEntry entry = static_cast(job)->statResult(); + mOriginalTime = entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1); + checkThumbnail(); + return; + } + + case STATE_DOWNLOADORIG: + if (job->error()) { + emitThumbnailLoadingFailed(); + LOG("Delete temp file" << mTempPath); + QFile::remove(mTempPath); + mTempPath.clear(); + determineNextIcon(); + } else { + startCreatingThumbnail(mTempPath); + } + return; + + case STATE_PREVIEWJOB: + determineNextIcon(); + return; + } +} + +void ThumbnailProvider::thumbnailReady(const QImage& _img, const QSize& _size) +{ + QImage img = _img; + QSize size = _size; + if (!img.isNull()) { + emitThumbnailLoaded(img, size); + } else { + emitThumbnailLoadingFailed(); + } + if (!mTempPath.isEmpty()) { + LOG("Delete temp file" << mTempPath); + QFile::remove(mTempPath); + mTempPath.clear(); + } + determineNextIcon(); +} + +QImage ThumbnailProvider::loadThumbnailFromCache() const +{ + QImage image = sThumbnailWriter->value(mThumbnailPath); + if (!image.isNull()) { + return image; + } + + image = QImage(mThumbnailPath); + if (image.isNull() && mThumbnailGroup == ThumbnailGroup::Normal) { + // If there is a large-sized thumbnail, generate the normal-sized version from it + QString largeThumbnailPath = generateThumbnailPath(mOriginalUri, ThumbnailGroup::Large); + QImage largeImage(largeThumbnailPath); + if (largeImage.isNull()) { + return image; + } + int size = ThumbnailGroup::pixelSize(ThumbnailGroup::Normal); + image = largeImage.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + Q_FOREACH(const QString& key, largeImage.textKeys()) { + QString text = largeImage.text(key); + image.setText(key, text); + } + sThumbnailWriter->queueThumbnail(mThumbnailPath, image); + } + + return image; +} + +void ThumbnailProvider::checkThumbnail() +{ + if (mCurrentItem.isNull()) { + // This can happen if current item has been removed by removeItems() + determineNextIcon(); + return; + } + + // If we are in the thumbnail dir, just load the file + if (mCurrentUrl.isLocalFile() + && mCurrentUrl.directory().startsWith(thumbnailBaseDir())) { + QImage image(mCurrentUrl.toLocalFile()); + emitThumbnailLoaded(image, image.size()); + determineNextIcon(); + return; + } + + mOriginalUri = generateOriginalUri(mCurrentUrl); + mThumbnailPath = generateThumbnailPath(mOriginalUri, mThumbnailGroup); + + LOG("Stat thumb" << mThumbnailPath); + + QImage thumb = loadThumbnailFromCache(); + KIO::filesize_t fileSize = thumb.text("Thumb::Size", 0).toULongLong(); + if (!thumb.isNull()) { + if (thumb.text("Thumb::URI", 0) == mOriginalUri && + thumb.text("Thumb::MTime", 0).toInt() == mOriginalTime && + (fileSize == 0 || fileSize == mOriginalFileSize)) { + int width = 0, height = 0; + QSize size; + bool ok; + + width = thumb.text("Thumb::Image::Width", 0).toInt(&ok); + if (ok) height = thumb.text("Thumb::Image::Height", 0).toInt(&ok); + if (ok) { + size = QSize(width, height); + } else { + kWarning() << "Thumbnail for" << mOriginalUri << "does not contain correct image size information"; + // Don't try to determine the size of a video, it probably won't work and + // will cause high I/O usage with big files (bug #307007). + if (MimeTypeUtils::urlKind(mCurrentUrl) == MimeTypeUtils::KIND_VIDEO) { + emitThumbnailLoaded(thumb, QSize()); + determineNextIcon(); + return; + } + KFileMetaInfo fmi(mCurrentUrl); + if (fmi.isValid()) { + KFileMetaInfoItem item = fmi.item("Dimensions"); + if (item.isValid()) { + size = item.value().toSize(); + } else { + kWarning() << "KFileMetaInfoItem for" << mOriginalUri << "did not get image size information"; + } + } else { + kWarning() << "Could not get a valid KFileMetaInfo instance for" << mOriginalUri; + } + } + emitThumbnailLoaded(thumb, size); + determineNextIcon(); + return; + } + } + + // Thumbnail not found or not valid + if (MimeTypeUtils::fileItemKind(mCurrentItem) == MimeTypeUtils::KIND_RASTER_IMAGE) { + if (mCurrentUrl.isLocalFile()) { + // Original is a local file, create the thumbnail + startCreatingThumbnail(mCurrentUrl.toLocalFile()); + } else { + // Original is remote, download it + mState = STATE_DOWNLOADORIG; + + KTemporaryFile tempFile; + tempFile.setAutoRemove(false); + if (!tempFile.open()) { + kWarning() << "Couldn't create temp file to download " << mCurrentUrl.prettyUrl(); + emitThumbnailLoadingFailed(); + determineNextIcon(); + return; + } + mTempPath = tempFile.fileName(); + + KUrl url; + url.setPath(mTempPath); + KIO::Job* job = KIO::file_copy(mCurrentUrl, url, -1, KIO::Overwrite | KIO::HideProgressInfo); + job->ui()->setWindow(KApplication::kApplication()->activeWindow()); + LOG("Download remote file" << mCurrentUrl.prettyUrl() << "to" << url.pathOrUrl()); + addSubjob(job); + } + } else { + // Not a raster image, use a KPreviewJob + LOG("Starting a KPreviewJob for" << mCurrentItem.url()); + mState = STATE_PREVIEWJOB; + KFileItemList list; + list.append(mCurrentItem); + const int pixelSize = ThumbnailGroup::pixelSize(mThumbnailGroup); + if (mPreviewPlugins.isEmpty()) { + mPreviewPlugins = KIO::PreviewJob::availablePlugins(); + } + KIO::Job* job = KIO::filePreview(list, QSize(pixelSize, pixelSize), &mPreviewPlugins); + //job->ui()->setWindow(KApplication::kApplication()->activeWindow()); + connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)), + this, SLOT(slotGotPreview(KFileItem,QPixmap))); + connect(job, SIGNAL(failed(KFileItem)), + this, SLOT(emitThumbnailLoadingFailed())); + addSubjob(job); + } +} + +void ThumbnailProvider::startCreatingThumbnail(const QString& pixPath) +{ + LOG("Creating thumbnail from" << pixPath); + // If mPreviousThumbnailGenerator is already working on our current item + // its thumbnail will be passed to sThumbnailWriter when ready. So we + // connect mPreviousThumbnailGenerator's signal "finished" to determineNextIcon + // which will load the thumbnail from sThumbnailWriter or from disk + // (because we re-add mCurrentItem to mItems). + if (mPreviousThumbnailGenerator && mPreviousThumbnailGenerator->isRunning() && + mOriginalUri == mPreviousThumbnailGenerator->originalUri() && + mOriginalTime == mPreviousThumbnailGenerator->originalTime() && + mOriginalFileSize == mPreviousThumbnailGenerator->originalFileSize() && + mCurrentItem.mimetype() == mPreviousThumbnailGenerator->originalMimeType()) { + connect(mPreviousThumbnailGenerator, SIGNAL(finished()), SLOT(determineNextIcon())); + mItems.prepend(mCurrentItem); + return; + } + mThumbnailGenerator->load(mOriginalUri, mOriginalTime, mOriginalFileSize, + mCurrentItem.mimetype(), pixPath, mThumbnailPath, mThumbnailGroup); +} + +void ThumbnailProvider::slotGotPreview(const KFileItem& item, const QPixmap& pixmap) +{ + if (mCurrentItem.isNull()) { + // This can happen if current item has been removed by removeItems() + return; + } + LOG(mCurrentItem.url()); + QSize size; + emit thumbnailLoaded(item, pixmap, size, mOriginalFileSize); +} + +void ThumbnailProvider::emitThumbnailLoaded(const QImage& img, const QSize& size) +{ + if (mCurrentItem.isNull()) { + // This can happen if current item has been removed by removeItems() + return; + } + LOG(mCurrentItem.url()); + QPixmap thumb = QPixmap::fromImage(img); + emit thumbnailLoaded(mCurrentItem, thumb, size, mOriginalFileSize); +} + +void ThumbnailProvider::emitThumbnailLoadingFailed() +{ + if (mCurrentItem.isNull()) { + // This can happen if current item has been removed by removeItems() + return; + } + LOG(mCurrentItem.url()); + emit thumbnailLoadingFailed(mCurrentItem); +} + +bool ThumbnailProvider::isThumbnailWriterEmpty() +{ + return sThumbnailWriter->isEmpty(); +} + +} // namespace diff --git a/gwenview/lib/thumbnailprovider/thumbnailprovider.h b/gwenview/lib/thumbnailprovider/thumbnailprovider.h new file mode 100644 index 00000000..b5f8b187 --- /dev/null +++ b/gwenview/lib/thumbnailprovider/thumbnailprovider.h @@ -0,0 +1,182 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab +/* Gwenview - A simple image viewer for KDE + Copyright 2000-2004 Aurélien Gâteau + This class is based on the ImagePreviewJob class from Konqueror. +*/ +/* This file is part of the KDE project + Copyright (C) 2000 David Faure + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef THUMBNAILPROVIDER_H +#define THUMBNAILPROVIDER_H + +#include + +// Qt +#include +#include +#include + +// KDE +#include +#include + +// Local +#include + +namespace Gwenview +{ + +class ThumbnailGenerator; +class ThumbnailWriter; + +/** + * A job that determines the thumbnails for the images in the current directory + */ +class GWENVIEWLIB_EXPORT ThumbnailProvider : public KIO::Job +{ + Q_OBJECT +public: + ThumbnailProvider(); + virtual ~ThumbnailProvider(); + + void stop(); + + /** + * To be called whenever items are removed from the view + */ + void removeItems(const KFileItemList& itemList); + + /** + * Remove all pending items + */ + void removePendingItems(); + + /** + * Returns the list of items waiting for a thumbnail + */ + const KFileItemList& pendingItems() const; + + /** + * Add items to the job + */ + void appendItems(const KFileItemList& items); + + /** + * Defines size of thumbnails to generate + */ + void setThumbnailGroup(ThumbnailGroup::Enum); + + bool isRunning() const; + + /** + * Returns the thumbnail base dir, independent of the thumbnail size + */ + static QString thumbnailBaseDir(); + + /** + * Sets the thumbnail base dir, useful for unit-testing + */ + static void setThumbnailBaseDir(const QString&); + + /** + * Returns the thumbnail base dir, for the @p group + */ + static QString thumbnailBaseDir(ThumbnailGroup::Enum group); + + /** + * Delete the thumbnail for the @p url + */ + static void deleteImageThumbnail(const KUrl& url); + + /** + * Move a thumbnail to match a file move + */ + static void moveThumbnail(const KUrl& oldUrl, const KUrl& newUrl); + + /** + * Returns true if all thumbnails have been written to disk. Useful for + * unit-testing. + */ + static bool isThumbnailWriterEmpty(); + +Q_SIGNALS: + /** + * Emitted when the thumbnail for the @p item has been loaded + */ + void thumbnailLoaded(const KFileItem& item, const QPixmap&, const QSize&, qulonglong); + + void thumbnailLoadingFailed(const KFileItem& item); + + /** + * Queue is empty + */ + void finished(); + +protected: + virtual void slotResult(KJob *job); + +private Q_SLOTS: + void determineNextIcon(); + void slotGotPreview(const KFileItem&, const QPixmap&); + void checkThumbnail(); + void thumbnailReady(const QImage&, const QSize&); + void emitThumbnailLoadingFailed(); + +private: + enum { STATE_STATORIG, STATE_DOWNLOADORIG, STATE_PREVIEWJOB, STATE_NEXTTHUMB } mState; + + KFileItemList mItems; + KFileItem mCurrentItem; + + // The Url of the current item (always equivalent to m_items.first()->item()->url()) + KUrl mCurrentUrl; + + // The Uri of the original image (might be different from mCurrentUrl.url()) + QString mOriginalUri; + + // The modification time of the original image + time_t mOriginalTime; + + // The file size of the original image + KIO::filesize_t mOriginalFileSize; + + // The thumbnail path + QString mThumbnailPath; + + // The temporary path for remote urls + QString mTempPath; + + // Thumbnail group + ThumbnailGroup::Enum mThumbnailGroup; + + ThumbnailGenerator* mThumbnailGenerator; + QPointer mPreviousThumbnailGenerator; + + QStringList mPreviewPlugins; + + void createNewThumbnailGenerator(); + void abortSubjob(); + void startCreatingThumbnail(const QString& path); + + void emitThumbnailLoaded(const QImage& img, const QSize& size); + + QImage loadThumbnailFromCache() const; +}; + +} // namespace +#endif /* THUMBNAILPROVIDER_H */ diff --git a/gwenview/lib/thumbnailprovider/thumbnailwriter.cpp b/gwenview/lib/thumbnailprovider/thumbnailwriter.cpp new file mode 100644 index 00000000..8b1442b0 --- /dev/null +++ b/gwenview/lib/thumbnailprovider/thumbnailwriter.cpp @@ -0,0 +1,104 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include + +// Local + +// KDE +#include +#include +#include + +// Qt +#include + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +static void storeThumbnailToDiskCache(const QString& path, const QImage& image) +{ + LOG(path); + KTemporaryFile tmp; + tmp.setPrefix(path + ".gwenview.tmp"); + tmp.setSuffix(".png"); + if (!tmp.open()) { + kWarning() << "Could not create a temporary file."; + return; + } + + if (!image.save(tmp.fileName(), "png")) { + kWarning() << "Could not save thumbnail"; + return; + } + + KDE_rename(QFile::encodeName(tmp.fileName()), QFile::encodeName(path)); +} + +void ThumbnailWriter::queueThumbnail(const QString& path, const QImage& image) +{ + LOG(path); + QMutexLocker locker(&mMutex); + mCache.insert(path, image); + start(); +} + +void ThumbnailWriter::run() +{ + QMutexLocker locker(&mMutex); + while (!mCache.isEmpty()) { + Cache::ConstIterator it = mCache.constBegin(); + const QString path = it.key(); + const QImage image = it.value(); + + // This part of the thread is the most time consuming but it does not + // depend on mCache so we can unlock here. This way other thumbnails + // can be added or queried + locker.unlock(); + storeThumbnailToDiskCache(path, image); + locker.relock(); + + mCache.remove(path); + } +} + +QImage ThumbnailWriter::value(const QString& path) const +{ + QMutexLocker locker(&mMutex); + return mCache.value(path); +} + +bool ThumbnailWriter::isEmpty() const +{ + QMutexLocker locker(&mMutex); + return mCache.isEmpty(); +} + +} // namespace diff --git a/gwenview/lib/thumbnailprovider/thumbnailwriter.h b/gwenview/lib/thumbnailprovider/thumbnailwriter.h new file mode 100644 index 00000000..942d9d01 --- /dev/null +++ b/gwenview/lib/thumbnailprovider/thumbnailwriter.h @@ -0,0 +1,64 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef THUMBNAILWRITER_H +#define THUMBNAILWRITER_H + +// Local + +// KDE + +// Qt +#include +#include +#include + +class QImage; + +namespace Gwenview +{ + +/** + * Store thumbnails to disk when done generating them + */ +class ThumbnailWriter : public QThread +{ + Q_OBJECT +public: + // Return thumbnail if it has still not been stored + QImage value(const QString&) const; + + bool isEmpty() const; + +public Q_SLOTS: + void queueThumbnail(const QString&, const QImage&); + +protected: + void run(); + +private: + typedef QHash Cache; + Cache mCache; + mutable QMutex mMutex; +}; + +} // namespace + +#endif /* THUMBNAILWRITER_H */ diff --git a/gwenview/lib/thumbnailview/abstractdocumentinfoprovider.cpp b/gwenview/lib/thumbnailview/abstractdocumentinfoprovider.cpp new file mode 100644 index 00000000..ceac3ef9 --- /dev/null +++ b/gwenview/lib/thumbnailview/abstractdocumentinfoprovider.cpp @@ -0,0 +1,38 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "abstractdocumentinfoprovider.moc" + +// Qt + +// KDE + +// Local + +namespace Gwenview +{ + +AbstractDocumentInfoProvider::AbstractDocumentInfoProvider(QObject* parent) +: QObject(parent) +{ +} + +} // namespace diff --git a/gwenview/lib/thumbnailview/abstractdocumentinfoprovider.h b/gwenview/lib/thumbnailview/abstractdocumentinfoprovider.h new file mode 100644 index 00000000..7e1d618f --- /dev/null +++ b/gwenview/lib/thumbnailview/abstractdocumentinfoprovider.h @@ -0,0 +1,66 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef ABSTRACTDOCUMENTINFOPROVIDER_H +#define ABSTRACTDOCUMENTINFOPROVIDER_H + +#include + +// Qt +#include + +// KDE + +// Local +#include + +class QModelIndex; +class QPixmap; +class QSize; + +class KUrl; + +namespace Gwenview +{ + +class GWENVIEWLIB_EXPORT AbstractDocumentInfoProvider : public QObject +{ + Q_OBJECT +public: + AbstractDocumentInfoProvider(QObject* parent = 0); + + /** + * Returns true if the document is currently busy (loading, saving, + * rotating...) + */ + virtual bool isBusy(const KUrl& url) = 0; + + virtual bool isModified(const KUrl& url) = 0; + + virtual void thumbnailForDocument(const KUrl& url, ThumbnailGroup::Enum, QPixmap* outPix, QSize* outFullSize) const = 0; + +Q_SIGNALS: + void busyStateChanged(const QModelIndex&, bool); + void documentChanged(const QModelIndex&); +}; + +} // namespace + +#endif /* ABSTRACTDOCUMENTINFOPROVIDER_H */ diff --git a/gwenview/lib/thumbnailview/abstractthumbnailviewhelper.cpp b/gwenview/lib/thumbnailview/abstractthumbnailviewhelper.cpp new file mode 100644 index 00000000..e702a06d --- /dev/null +++ b/gwenview/lib/thumbnailview/abstractthumbnailviewhelper.cpp @@ -0,0 +1,34 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "abstractthumbnailviewhelper.moc" + +namespace Gwenview +{ + +AbstractThumbnailViewHelper::AbstractThumbnailViewHelper(QObject* parent) +: QObject(parent) +{ +} + +AbstractThumbnailViewHelper::~AbstractThumbnailViewHelper() +{ +} + +} // namespace diff --git a/gwenview/lib/thumbnailview/abstractthumbnailviewhelper.h b/gwenview/lib/thumbnailview/abstractthumbnailviewhelper.h new file mode 100644 index 00000000..921f495f --- /dev/null +++ b/gwenview/lib/thumbnailview/abstractthumbnailviewhelper.h @@ -0,0 +1,55 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef ABSTRACTTHUMBNAILVIEWHELPER_H +#define ABSTRACTTHUMBNAILVIEWHELPER_H + +#include + +// Qt +#include + +// KDE +#include + +// Local + +namespace Gwenview +{ + +/** + * This class is used by the ThumbnailView to request various things. + */ +class GWENVIEWLIB_EXPORT AbstractThumbnailViewHelper : public QObject +{ + Q_OBJECT +public: + AbstractThumbnailViewHelper(QObject* parent); + virtual ~AbstractThumbnailViewHelper(); + + virtual void showContextMenu(QWidget* parent) = 0; + + virtual void showMenuForUrlDroppedOnViewport(QWidget* parent, const KUrl::List&) = 0; + + virtual void showMenuForUrlDroppedOnDir(QWidget* parent, const KUrl::List&, const KUrl&) = 0; +}; + +} // namespace + +#endif /* ABSTRACTTHUMBNAILVIEWHELPER_H */ diff --git a/gwenview/lib/thumbnailview/contextbarbutton.cpp b/gwenview/lib/thumbnailview/contextbarbutton.cpp new file mode 100644 index 00000000..f9e6c642 --- /dev/null +++ b/gwenview/lib/thumbnailview/contextbarbutton.cpp @@ -0,0 +1,108 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "contextbarbutton.moc" + +// Local +#include "paintutils.h" + +// KDE +#include + +// Qt +#include +#include + +namespace Gwenview +{ + +/** How lighter is the border of context bar buttons */ +const int CONTEXTBAR_BORDER_LIGHTNESS = 140; + +/** How darker is the background of context bar buttons */ +const int CONTEXTBAR_BACKGROUND_DARKNESS = 170; + +/** How lighter are context bar buttons when under mouse */ +const int CONTEXTBAR_MOUSEOVER_LIGHTNESS = 115; + +/** Radius of ContextBarButtons */ +const int CONTEXTBAR_RADIUS = 5; + +struct ContextBarButtonPrivate +{ +}; + +ContextBarButton::ContextBarButton(const QString& iconName, QWidget* parent) +: QToolButton(parent) +, d(new ContextBarButtonPrivate) +{ + const int size = KIconLoader::global()->currentSize(KIconLoader::Small); + setIconSize(QSize(size, size)); + setAutoRaise(true); + setIcon(SmallIcon(iconName)); +} + +ContextBarButton::~ContextBarButton() +{ + delete d; +} + +void Gwenview::ContextBarButton::paintEvent(QPaintEvent*) +{ + QStylePainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + QStyleOptionToolButton opt; + initStyleOption(&opt); + + const QColor bgColor = palette().color(backgroundRole()); + QColor color = bgColor.dark(CONTEXTBAR_BACKGROUND_DARKNESS); + QColor borderColor = bgColor.light(CONTEXTBAR_BORDER_LIGHTNESS); + + if (opt.state & QStyle::State_MouseOver && opt.state & QStyle::State_Enabled) { + color = color.light(CONTEXTBAR_MOUSEOVER_LIGHTNESS); + borderColor = borderColor.light(CONTEXTBAR_MOUSEOVER_LIGHTNESS); + } + + const QRectF rectF = QRectF(opt.rect).adjusted(0.5, 0.5, -0.5, -0.5); + const QPainterPath path = PaintUtils::roundedRectangle(rectF, CONTEXTBAR_RADIUS); + + // Background + painter.fillPath(path, color); + + // Top shadow + QLinearGradient gradient(rectF.topLeft(), rectF.topLeft() + QPoint(0, 5)); + gradient.setColorAt(0, QColor::fromHsvF(0, 0, 0, .3)); + gradient.setColorAt(1, Qt::transparent); + painter.fillPath(path, gradient); + + // Left shadow + gradient.setFinalStop(rectF.topLeft() + QPoint(5, 0)); + painter.fillPath(path, gradient); + + // Border + painter.setPen(borderColor); + painter.drawPath(path); + + // Content + painter.drawControl(QStyle::CE_ToolButtonLabel, opt); +} + +} // namespace diff --git a/gwenview/lib/thumbnailview/contextbarbutton.h b/gwenview/lib/thumbnailview/contextbarbutton.h new file mode 100644 index 00000000..0a4d82bf --- /dev/null +++ b/gwenview/lib/thumbnailview/contextbarbutton.h @@ -0,0 +1,54 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef CONTEXTBARBUTTON_H +#define CONTEXTBARBUTTON_H + +// Local + +// KDE + +// Qt +#include + +namespace Gwenview +{ + +struct ContextBarButtonPrivate; +/** + * A button with a special look, appears when hovering over thumbnails + */ +class ContextBarButton : public QToolButton +{ + Q_OBJECT +public: + explicit ContextBarButton(const QString& iconName, QWidget* parent = 0); + ~ContextBarButton(); + +protected: + void paintEvent(QPaintEvent*); + +private: + ContextBarButtonPrivate* const d; +}; + +} // namespace + +#endif /* CONTEXTBARBUTTON_H */ diff --git a/gwenview/lib/thumbnailview/dragpixmapgenerator.cpp b/gwenview/lib/thumbnailview/dragpixmapgenerator.cpp new file mode 100644 index 00000000..6efd476e --- /dev/null +++ b/gwenview/lib/thumbnailview/dragpixmapgenerator.cpp @@ -0,0 +1,125 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include + +// Local +#include + +// KDE +#include +#include + +// Qt +#include +#include +#include +#include +#include + +namespace Gwenview +{ + +namespace DragPixmapGenerator +{ + +const int DRAG_THUMB_SIZE = KIconLoader::SizeHuge; +const int DRAG_THUMB_SPACING = 4; +const int SPREAD_ANGLE = 30; + +DragPixmap generate(const QList& pixmaps, int totalCount) +{ + DragPixmap out; + + const int extraSpace = DRAG_THUMB_SIZE / 4; + const int maxHeight = qSqrt(qPow(DRAG_THUMB_SIZE + extraSpace, 2) + qPow(DRAG_THUMB_SIZE / 2, 2)); + out.pix = QPixmap(maxHeight * 2, maxHeight); + out.pix.fill(Qt::transparent); + + QPainter painter(&out.pix); + painter.translate(out.pix.width() / 2, out.pix.height()); + + const int count = pixmaps.count(); + qreal delta = 0; + if (count > 1) { + painter.rotate(-SPREAD_ANGLE / 2); + delta = SPREAD_ANGLE / (count - 1); + } + + painter.setRenderHint(QPainter::Antialiasing); + painter.setRenderHint(QPainter::SmoothPixmapTransform); + //int index = 0; + int maxX = 0; + Q_FOREACH(const QPixmap& pix, pixmaps) { + QPixmap pix2 = pix.scaled(DRAG_THUMB_SIZE - 2, DRAG_THUMB_SIZE - 2, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QRect rect(-pix2.width() / 2, -pix2.height() - extraSpace, pix2.width(), pix2.height()); + + if (!pix2.hasAlphaChannel()) { + // Draw a thin white border around fully opaque pictures to give them a photo-like appearance + painter.fillRect(rect.adjusted(-1, -1, 1, 1), Qt::white); + } + painter.drawPixmap(rect.topLeft(), pix2); + + QPoint topRight = painter.transform().map(rect.topRight()); + maxX = qMax(topRight.x(), maxX); + /* + painter.drawRect(-pix2.width() / 2, -pix2.height() - extraSpace, pix2.width(), pix2.height()); + painter.drawText(-pix2.width() / 2, -pix2.height() - extraSpace, pix2.width(), pix2.height(), Qt::AlignTop | Qt::AlignLeft, QString::number(index)); + index++; + */ + painter.rotate(delta); + } + out.hotSpot = QPoint(out.pix.width() / 2, -extraSpace); + + if (count < totalCount) { + painter.resetTransform(); + QString text = QString::number(totalCount); + QRect rect = painter.fontMetrics().boundingRect(text); + if (rect.width() < rect.height()) { + rect.setWidth(rect.height()); + } + + const int padding = 1; + rect.moveRight(maxX - padding - 1); + rect.moveBottom(out.pix.height() - padding - 1); + + // Background + QColor bg1 = QApplication::palette().color(QPalette::Highlight); + QColor bg2 = PaintUtils::adjustedHsv(bg1, 0, 0, -50); + QLinearGradient gradient(0, rect.top(), 0, rect.bottom()); + gradient.setColorAt(0, bg1); + gradient.setColorAt(1, bg2); + painter.setBrush(gradient); + painter.setPen(bg1); + painter.translate(-.5, -.5); + painter.drawRoundedRect(rect.adjusted(-padding, -padding, padding, padding), padding, padding); + + // Foreground + painter.setPen(QApplication::palette().color(QPalette::HighlightedText)); + painter.drawText(rect, Qt::AlignCenter, text); + } + + return out; +} + +} // DragPixmapGenerator namespace + +} // namespace diff --git a/gwenview/lib/thumbnailview/dragpixmapgenerator.h b/gwenview/lib/thumbnailview/dragpixmapgenerator.h new file mode 100644 index 00000000..ad6caf22 --- /dev/null +++ b/gwenview/lib/thumbnailview/dragpixmapgenerator.h @@ -0,0 +1,55 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DRAGPIXMAPGENERATOR_H +#define DRAGPIXMAPGENERATOR_H + +// Local + +// KDE + +// Qt +#include +#include +#include + +namespace Gwenview +{ + +/** + * Generate a nice pixmap to represent dragged images + */ +namespace DragPixmapGenerator +{ + +enum { MaxCount = 6 }; + +struct DragPixmap { + QPixmap pix; + QPoint hotSpot; +}; + +DragPixmap generate(const QList& pixmaps, int totalCount); + +} // DragPixmapGenerator namespace + +} // namespace + +#endif /* DRAGPIXMAPGENERATOR_H */ diff --git a/gwenview/lib/thumbnailview/itemeditor.cpp b/gwenview/lib/thumbnailview/itemeditor.cpp new file mode 100644 index 00000000..205260da --- /dev/null +++ b/gwenview/lib/thumbnailview/itemeditor.cpp @@ -0,0 +1,88 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "itemeditor.moc" + +// Qt +#include +#include + +// KDE +#include +#include + +// Local + +namespace Gwenview +{ + +struct ItemEditorPrivate +{ + QPoint mCenter; +}; + +ItemEditor::ItemEditor(QWidget* parent) +: KLineEdit(parent) +, d(new ItemEditorPrivate) +{ + setPalette(QApplication::palette()); + connect(this, SIGNAL(textChanged(QString)), SLOT(resizeToContents())); + setTrapReturnKey(true); +} + +ItemEditor::~ItemEditor() +{ + delete d; +} + +void ItemEditor::showEvent(QShowEvent* event) +{ + // We can't do this in PreviewItemDelegate::updateEditorGeometry() because QAbstractItemView outsmarts us by calling selectAll() on the editor if it is a QLineEdit + const QString extension = KMimeType::extractKnownExtension(text()); + if (!extension.isEmpty()) { + // The filename contains an extension. Assure that only the filename + // gets selected. + const int selectionLength = text().length() - extension.length() - 1; + setSelection(0, selectionLength); + } + KLineEdit::showEvent(event); +} + +void ItemEditor::resizeToContents() +{ + if (d->mCenter.isNull()) { + d->mCenter = geometry().center(); + } + int textWidth = fontMetrics().width(" " + text() + " "); + QRect rect = geometry(); + rect.setWidth(textWidth); + rect.moveCenter(d->mCenter); + if (rect.right() > parentWidget()->width()) { + rect.setRight(parentWidget()->width()); + } + if (rect.left() < 0) { + rect.setLeft(0); + } + setGeometry(rect); + +} + +} // namespace diff --git a/gwenview/lib/thumbnailview/itemeditor.h b/gwenview/lib/thumbnailview/itemeditor.h new file mode 100644 index 00000000..a8d95f50 --- /dev/null +++ b/gwenview/lib/thumbnailview/itemeditor.h @@ -0,0 +1,54 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef ITEMEDITOR_H +#define ITEMEDITOR_H + +// Qt + +// KDE +#include + +// Local + +namespace Gwenview +{ + +struct ItemEditorPrivate; +class ItemEditor : public KLineEdit +{ + Q_OBJECT +public: + ItemEditor(QWidget* parent = 0); + ~ItemEditor(); + +protected: + virtual void showEvent(QShowEvent*); + +private Q_SLOTS: + void resizeToContents(); + +private: + ItemEditorPrivate* const d; +}; + +} // namespace + +#endif /* ITEMEDITOR_H */ diff --git a/gwenview/lib/thumbnailview/previewitemdelegate.cpp b/gwenview/lib/thumbnailview/previewitemdelegate.cpp new file mode 100644 index 00000000..2eb18a64 --- /dev/null +++ b/gwenview/lib/thumbnailview/previewitemdelegate.cpp @@ -0,0 +1,955 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "previewitemdelegate.moc" +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE +#include +#endif + +// Local +#include "archiveutils.h" +#include "contextbarbutton.h" +#include "itemeditor.h" +#include "paintutils.h" +#include "thumbnailview.h" +#include "timeutils.h" +#include "tooltipwidget.h" +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE +#include "../semanticinfo/semanticinfodirmodel.h" +#endif + +// Define this to be able to fine tune the rendering of the selection +// background through a config file +//#define FINETUNE_SELECTION_BACKGROUND +#ifdef FINETUNE_SELECTION_BACKGROUND +#include +#include +#endif + +//#define DEBUG_DRAW_BORDER +//#define DEBUG_DRAW_CURRENT + +namespace Gwenview +{ + +/** + * Space between the item outer rect and the content, and between the + * thumbnail and the caption + */ +const int ITEM_MARGIN = 5; + +/** How darker is the border line around selection */ +const int SELECTION_BORDER_DARKNESS = 140; + +/** Radius of the selection rounded corners, in pixels */ +const int SELECTION_RADIUS = 5; + +/** Space between the item outer rect and the context bar */ +const int CONTEXTBAR_MARGIN = 1; + +/** How dark is the shadow, 0 is invisible, 255 is as dark as possible */ +const int SHADOW_STRENGTH = 128; + +/** How many pixels around the thumbnail are shadowed */ +const int SHADOW_SIZE = 4; + +static KFileItem fileItemForIndex(const QModelIndex& index) +{ + Q_ASSERT(index.isValid()); + QVariant data = index.data(KDirModel::FileItemRole); + return qvariant_cast(data); +} + +static KUrl urlForIndex(const QModelIndex& index) +{ + KFileItem item = fileItemForIndex(index); + return item.url(); +} + +struct PreviewItemDelegatePrivate +{ + /** + * Maps full text to elided text. + */ + mutable QHash mElidedTextCache; + + // Key is height * 1000 + width + typedef QHash ShadowCache; + mutable ShadowCache mShadowCache; + + PreviewItemDelegate* q; + ThumbnailView* mView; + QWidget* mContextBar; + QToolButton* mSaveButton; + QPixmap mSaveButtonPixmap; + + QToolButton* mToggleSelectionButton; + QToolButton* mFullScreenButton; + QToolButton* mRotateLeftButton; + QToolButton* mRotateRightButton; +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + KRatingPainter mRatingPainter; +#endif + + QPersistentModelIndex mIndexUnderCursor; + QSize mThumbnailSize; + PreviewItemDelegate::ThumbnailDetails mDetails; + PreviewItemDelegate::ContextBarActions mContextBarActions; + Qt::TextElideMode mTextElideMode; + + QPointer mToolTip; + QScopedPointer mToolTipAnimation; + + void initSaveButtonPixmap() + { + if (!mSaveButtonPixmap.isNull()) { + return; + } + // Necessary otherwise we won't see the save button itself + mSaveButton->adjustSize(); + + mSaveButtonPixmap = QPixmap(mSaveButton->sizeHint()); + mSaveButtonPixmap.fill(Qt::transparent); + mSaveButton->render(&mSaveButtonPixmap, QPoint(), QRegion(), QWidget::DrawChildren); + } + + void showContextBar(const QRect& rect, const QPixmap& thumbnailPix) + { + if (mContextBarActions == PreviewItemDelegate::NoAction) { + return; + } + mContextBar->adjustSize(); + // Center bar, except if only showing SelectionAction. + const int posX = mContextBarActions == PreviewItemDelegate::SelectionAction + ? 0 + : (rect.width() - mContextBar->width()) / 2; + const int posY = qMax(CONTEXTBAR_MARGIN, mThumbnailSize.height() - thumbnailPix.height() - mContextBar->height()); + mContextBar->move(rect.topLeft() + QPoint(posX, posY)); + mContextBar->show(); + } + + void initToolTip() + { + mToolTip = new ToolTipWidget(mView->viewport()); + mToolTip->setOpacity(0); + mToolTip->show(); + } + + bool hoverEventFilter(QHoverEvent* event) + { + QModelIndex index = mView->indexAt(event->pos()); + if (index != mIndexUnderCursor) { + updateHoverUi(index); + } else { + // Same index, nothing to do, but repaint anyway in case we are + // over the rating row + mView->update(mIndexUnderCursor); + } + return false; + } + + void updateHoverUi(const QModelIndex& index) + { + QModelIndex oldIndex = mIndexUnderCursor; + mIndexUnderCursor = index; + mView->update(oldIndex); + + if (KGlobalSettings::singleClick() && KGlobalSettings::changeCursorOverIcon()) { + mView->setCursor(mIndexUnderCursor.isValid() ? Qt::PointingHandCursor : Qt::ArrowCursor); + } + + if (mIndexUnderCursor.isValid()) { + updateToggleSelectionButton(); + updateImageButtons(); + + const QRect rect = mView->visualRect(mIndexUnderCursor); + const QPixmap thumbnailPix = mView->thumbnailForIndex(index); + showContextBar(rect, thumbnailPix); + if (mView->isModified(mIndexUnderCursor)) { + showSaveButton(rect); + } else { + mSaveButton->hide(); + } + + showToolTip(index); + mView->update(mIndexUnderCursor); + + } else { + mContextBar->hide(); + mSaveButton->hide(); + hideToolTip(); + } + } + + QRect ratingRectFromIndexRect(const QRect& rect) const + { + return QRect( + rect.left(), + rect.bottom() - ratingRowHeight() - ITEM_MARGIN, + rect.width(), + ratingRowHeight()); + } + +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + int ratingFromCursorPosition(const QRect& ratingRect) const + { + const QPoint pos = mView->viewport()->mapFromGlobal(QCursor::pos()); + return mRatingPainter.ratingFromPosition(ratingRect, pos); + } +#endif + + bool mouseButtonEventFilter(QEvent::Type type) + { +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + const QRect rect = ratingRectFromIndexRect(mView->visualRect(mIndexUnderCursor)); + const int rating = ratingFromCursorPosition(rect); + if (rating == -1) { + return false; + } + if (type == QEvent::MouseButtonRelease) { + q->setDocumentRatingRequested(urlForIndex(mIndexUnderCursor) , rating); + } + return true; +#else + return false; +#endif + } + + QPoint saveButtonPosition(const QRect& itemRect) const + { + QSize buttonSize = mSaveButton->sizeHint(); + int posX = itemRect.right() - buttonSize.width(); + int posY = itemRect.top() + mThumbnailSize.height() + 2 * ITEM_MARGIN - buttonSize.height(); + + return QPoint(posX, posY); + } + + void showSaveButton(const QRect& itemRect) const + { + mSaveButton->move(saveButtonPosition(itemRect)); + mSaveButton->show(); + } + + void drawBackground(QPainter* painter, const QRect& rect, const QColor& bgColor, const QColor& borderColor) const + { + int bgH, bgS, bgV; + int borderH, borderS, borderV, borderMargin; +#ifdef FINETUNE_SELECTION_BACKGROUND + QSettings settings(QDir::homePath() + "/colors.ini", QSettings::IniFormat); + bgH = settings.value("bg/h").toInt(); + bgS = settings.value("bg/s").toInt(); + bgV = settings.value("bg/v").toInt(); + borderH = settings.value("border/h").toInt(); + borderS = settings.value("border/s").toInt(); + borderV = settings.value("border/v").toInt(); + borderMargin = settings.value("border/margin").toInt(); +#else + bgH = 0; + bgS = -20; + bgV = 43; + borderH = 0; + borderS = -100; + borderV = 60; + borderMargin = 1; +#endif + painter->setRenderHint(QPainter::Antialiasing); + + QRectF rectF = QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5); + + QPainterPath path = PaintUtils::roundedRectangle(rectF, SELECTION_RADIUS); + + QLinearGradient gradient(rectF.topLeft(), rectF.bottomLeft()); + gradient.setColorAt(0, PaintUtils::adjustedHsv(bgColor, bgH, bgS, bgV)); + gradient.setColorAt(1, bgColor); + painter->fillPath(path, gradient); + + painter->setPen(borderColor); + painter->drawPath(path); + + painter->setPen(PaintUtils::adjustedHsv(borderColor, borderH, borderS, borderV)); + rectF = rectF.adjusted(borderMargin, borderMargin, -borderMargin, -borderMargin); + path = PaintUtils::roundedRectangle(rectF, SELECTION_RADIUS); + painter->drawPath(path); + } + + void drawShadow(QPainter* painter, const QRect& rect) const + { + const QPoint shadowOffset(-SHADOW_SIZE, -SHADOW_SIZE + 1); + + int key = rect.height() * 1000 + rect.width(); + + ShadowCache::Iterator it = mShadowCache.find(key); + if (it == mShadowCache.end()) { + QSize size = QSize(rect.width() + 2 * SHADOW_SIZE, rect.height() + 2 * SHADOW_SIZE); + QColor color(0, 0, 0, SHADOW_STRENGTH); + QPixmap shadow = PaintUtils::generateFuzzyRect(size, color, SHADOW_SIZE); + it = mShadowCache.insert(key, shadow); + } + painter->drawPixmap(rect.topLeft() + shadowOffset, it.value()); + } + + void drawText(QPainter* painter, const QRect& rect, const QColor& fgColor, const QString& fullText) const + { + QFontMetrics fm = mView->fontMetrics(); + + // Elide text + QString text; + QHash::const_iterator it = mElidedTextCache.constFind(fullText); + if (it == mElidedTextCache.constEnd()) { + text = fm.elidedText(fullText, mTextElideMode, rect.width()); + mElidedTextCache[fullText] = text; + } else { + text = it.value(); + } + + // Compute x pos + int posX; + if (text.length() == fullText.length()) { + // Not elided, center text + posX = (rect.width() - fm.width(text)) / 2; + } else { + // Elided, left align + posX = 0; + } + + // Draw text + painter->setPen(fgColor); + painter->drawText(rect.left() + posX, rect.top() + fm.ascent(), text); + } + + void drawRating(QPainter* painter, const QRect& rect, const QVariant& value) + { +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + const int rating = value.toInt(); + const QRect ratingRect = ratingRectFromIndexRect(rect); + const int hoverRating = ratingFromCursorPosition(ratingRect); + mRatingPainter.paint(painter, ratingRect, rating, hoverRating); +#endif + } + + bool isTextElided(const QString& text) const + { + QHash::const_iterator it = mElidedTextCache.constFind(text); + if (it == mElidedTextCache.constEnd()) { + return false; + } + return it.value().length() < text.length(); + } + + /** + * Show a tooltip only if the item has been elided. + * This function places the tooltip over the item text. + */ + void showToolTip(const QModelIndex& index) + { + if (mDetails == 0 || mDetails == PreviewItemDelegate::RatingDetail) { + // No text to display + return; + } + + // Gather tip text + QStringList textList; + bool elided = false; + if (mDetails & PreviewItemDelegate::FileNameDetail) { + const QString text = index.data().toString(); + elided |= isTextElided(text); + textList << text; + } + + // FIXME: Duplicated from drawText + const KFileItem fileItem = fileItemForIndex(index); + const bool isDirOrArchive = ArchiveUtils::fileItemIsDirOrArchive(fileItem); + if (mDetails & PreviewItemDelegate::DateDetail) { + if (!ArchiveUtils::fileItemIsDirOrArchive(fileItem)) { + const KDateTime dt = TimeUtils::dateTimeForFileItem(fileItem); + const QString text = KGlobal::locale()->formatDateTime(dt); + elided |= isTextElided(text); + textList << text; + } + } + + if (!isDirOrArchive && (mDetails & PreviewItemDelegate::ImageSizeDetail)) { + QSize fullSize; + QPixmap thumbnailPix = mView->thumbnailForIndex(index, &fullSize); + if (fullSize.isValid()) { + const QString text = QString("%1x%2").arg(fullSize.width()).arg(fullSize.height()); + elided |= isTextElided(text); + textList << text; + } + } + + if (!isDirOrArchive && (mDetails & PreviewItemDelegate::FileSizeDetail)) { + const KIO::filesize_t size = fileItem.size(); + if (size > 0) { + const QString text = KIO::convertSize(size); + elided |= isTextElided(text); + textList << text; + } + } + + if (!elided) { + hideToolTip(); + return; + } + + bool newTipLabel = !mToolTip; + if (!mToolTip) { + initToolTip(); + } + mToolTip->setText(textList.join("\n")); + QSize tipSize = mToolTip->sizeHint(); + + // Compute tip position + QRect rect = mView->visualRect(index); + const int textY = ITEM_MARGIN + mThumbnailSize.height() + ITEM_MARGIN; + const int spacing = 1; + QRect geometry( + QPoint(rect.topLeft() + QPoint((rect.width() - tipSize.width()) / 2, textY + spacing)), + tipSize + ); + if (geometry.left() < 0) { + geometry.moveLeft(0); + } else if (geometry.right() > mView->viewport()->width()) { + geometry.moveRight(mView->viewport()->width()); + } + + // Show tip + QParallelAnimationGroup* anim = new QParallelAnimationGroup(); + QPropertyAnimation* fadeIn = new QPropertyAnimation(mToolTip, "opacity"); + fadeIn->setStartValue(mToolTip->opacity()); + fadeIn->setEndValue(1.); + anim->addAnimation(fadeIn); + + if (newTipLabel) { + mToolTip->setGeometry(geometry); + } else { + QPropertyAnimation* move = new QPropertyAnimation(mToolTip, "geometry"); + move->setStartValue(mToolTip->geometry()); + move->setEndValue(geometry); + anim->addAnimation(move); + } + + mToolTipAnimation.reset(anim); + mToolTipAnimation->start(); + } + + void hideToolTip() + { + if (!mToolTip) { + return; + } + QSequentialAnimationGroup* anim = new QSequentialAnimationGroup(); + anim->addPause(500); + QPropertyAnimation* fadeOut = new QPropertyAnimation(mToolTip, "opacity"); + fadeOut->setStartValue(mToolTip->opacity()); + fadeOut->setEndValue(0.); + anim->addAnimation(fadeOut); + mToolTipAnimation.reset(anim); + mToolTipAnimation->start(); + QObject::connect(anim, SIGNAL(finished()), mToolTip, SLOT(deleteLater())); + } + + int itemWidth() const + { + return mThumbnailSize.width() + 2 * ITEM_MARGIN; + } + + int ratingRowHeight() const + { +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + return qMax(mView->fontMetrics().ascent(), int(KIconLoader::SizeSmall)); +#endif + return 0; + } + + int itemHeight() const + { + const int lineHeight = mView->fontMetrics().height(); + int textHeight = 0; + if (mDetails & PreviewItemDelegate::FileNameDetail) { + textHeight += lineHeight; + } + if (mDetails & PreviewItemDelegate::DateDetail) { + textHeight += lineHeight; + } + if (mDetails & PreviewItemDelegate::ImageSizeDetail) { + textHeight += lineHeight; + } + if (mDetails & PreviewItemDelegate::FileSizeDetail) { + textHeight += lineHeight; + } + if (mDetails & PreviewItemDelegate::RatingDetail) { + textHeight += ratingRowHeight(); + } + if (textHeight == 0) { + // Keep at least one row of text, so that we can show folder names + textHeight = lineHeight; + } + return mThumbnailSize.height() + textHeight + 3 * ITEM_MARGIN; + } + + void selectIndexUnderCursorIfNoMultiSelection() + { + if (mView->selectionModel()->selectedIndexes().size() <= 1) { + mView->setCurrentIndex(mIndexUnderCursor); + } + } + + void updateToggleSelectionButton() + { + mToggleSelectionButton->setIcon(SmallIcon( + mView->selectionModel()->isSelected(mIndexUnderCursor) ? "list-remove" : "list-add" + )); + } + + void updateImageButtons() + { + const KFileItem item = fileItemForIndex(mIndexUnderCursor); + const bool isImage = !ArchiveUtils::fileItemIsDirOrArchive(item); + mFullScreenButton->setEnabled(isImage); + mRotateLeftButton->setEnabled(isImage); + mRotateRightButton->setEnabled(isImage); + } + + void updateContextBar() + { + if (mContextBarActions == PreviewItemDelegate::NoAction) { + mContextBar->hide(); + return; + } + const int width = itemWidth(); + const int buttonWidth = mRotateRightButton->sizeHint().width(); + mFullScreenButton->setVisible(mContextBarActions & PreviewItemDelegate::FullScreenAction); + bool rotate = mContextBarActions & PreviewItemDelegate::RotateAction; + mRotateLeftButton->setVisible(rotate && width >= 3 * buttonWidth); + mRotateRightButton->setVisible(rotate && width >= 4 * buttonWidth); + mContextBar->adjustSize(); + } + + void updateViewGridSize() + { + mView->setGridSize(QSize(itemWidth(), itemHeight())); + } +}; + +PreviewItemDelegate::PreviewItemDelegate(ThumbnailView* view) +: QItemDelegate(view) +, d(new PreviewItemDelegatePrivate) +{ + d->q = this; + d->mView = view; + view->viewport()->installEventFilter(this); + + // Set this attribute so that the viewport receives QEvent::HoverMove and + // QEvent::HoverLeave events. We use these events in the event filter + // installed on the viewport. + // Some styles set this attribute themselves (Oxygen and Skulpture do) but + // others do not (Plastique, Cleanlooks...) + view->viewport()->setAttribute(Qt::WA_Hover); + + d->mThumbnailSize = view->thumbnailSize(); + d->mDetails = FileNameDetail; + d->mContextBarActions = SelectionAction | FullScreenAction | RotateAction; + d->mTextElideMode = Qt::ElideRight; + + connect(view, SIGNAL(rowsRemovedSignal(QModelIndex,int,int)), + SLOT(slotRowsChanged())); + connect(view, SIGNAL(rowsInsertedSignal(QModelIndex,int,int)), + SLOT(slotRowsChanged())); + +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + d->mRatingPainter.setAlignment(Qt::AlignHCenter | Qt::AlignBottom); + d->mRatingPainter.setLayoutDirection(view->layoutDirection()); + d->mRatingPainter.setMaxRating(10); +#endif + + connect(view, SIGNAL(thumbnailSizeChanged(QSize)), + SLOT(setThumbnailSize(QSize))); + + // Button frame + d->mContextBar = new QWidget(d->mView->viewport()); + d->mContextBar->hide(); + + d->mToggleSelectionButton = new ContextBarButton("list-add"); + connect(d->mToggleSelectionButton, SIGNAL(clicked()), + SLOT(slotToggleSelectionClicked())); + + d->mFullScreenButton = new ContextBarButton("view-fullscreen"); + connect(d->mFullScreenButton, SIGNAL(clicked()), + SLOT(slotFullScreenClicked())); + + d->mRotateLeftButton = new ContextBarButton("object-rotate-left"); + connect(d->mRotateLeftButton, SIGNAL(clicked()), + SLOT(slotRotateLeftClicked())); + + d->mRotateRightButton = new ContextBarButton("object-rotate-right"); + connect(d->mRotateRightButton, SIGNAL(clicked()), + SLOT(slotRotateRightClicked())); + + QHBoxLayout* layout = new QHBoxLayout(d->mContextBar); + layout->setMargin(2); + layout->setSpacing(2); + layout->addWidget(d->mToggleSelectionButton); + layout->addWidget(d->mFullScreenButton); + layout->addWidget(d->mRotateLeftButton); + layout->addWidget(d->mRotateRightButton); + + // Save button + d->mSaveButton = new ContextBarButton("document-save", d->mView->viewport()); + d->mSaveButton->hide(); + connect(d->mSaveButton, SIGNAL(clicked()), + SLOT(slotSaveClicked())); +} + +PreviewItemDelegate::~PreviewItemDelegate() +{ + delete d; +} + +QSize PreviewItemDelegate::sizeHint(const QStyleOptionViewItem & /*option*/, const QModelIndex & /*index*/) const +{ + return d->mView->gridSize(); +} + +bool PreviewItemDelegate::eventFilter(QObject* object, QEvent* event) +{ + if (object == d->mView->viewport()) { + switch (event->type()) { + case QEvent::ToolTip: + return true; + + case QEvent::HoverMove: + case QEvent::HoverLeave: + return d->hoverEventFilter(static_cast(event)); + + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + return d->mouseButtonEventFilter(event->type()); + + default: + return false; + } + } else { + // Necessary for the item editor to work correctly (especially closing + // the editor with the Escape key) + return QItemDelegate::eventFilter(object, event); + } +} + +void PreviewItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const +{ + int thumbnailHeight = d->mThumbnailSize.height(); + QSize fullSize; + QPixmap thumbnailPix = d->mView->thumbnailForIndex(index, &fullSize); + const KFileItem fileItem = fileItemForIndex(index); + const bool opaque = !thumbnailPix.hasAlphaChannel(); + const bool isDirOrArchive = ArchiveUtils::fileItemIsDirOrArchive(fileItem); + QRect rect = option.rect; + const bool selected = option.state & QStyle::State_Selected; + const bool underMouse = option.state & QStyle::State_MouseOver; + const QWidget* viewport = d->mView->viewport(); + +#ifdef DEBUG_DRAW_BORDER + painter->setPen(Qt::red); + painter->setBrush(Qt::NoBrush); + painter->drawRect(rect); +#endif + + // Select color group + QPalette::ColorGroup cg; + + if ((option.state & QStyle::State_Enabled) && (option.state & QStyle::State_Active)) { + cg = QPalette::Normal; + } else if ((option.state & QStyle::State_Enabled)) { + cg = QPalette::Inactive; + } else { + cg = QPalette::Disabled; + } + + // Select colors + QColor bgColor, borderColor, fgColor; + if (selected || underMouse) { + bgColor = option.palette.color(cg, QPalette::Highlight); + borderColor = bgColor.dark(SELECTION_BORDER_DARKNESS); + } else { + bgColor = viewport->palette().color(viewport->backgroundRole()); + borderColor = bgColor.light(200); + } + fgColor = viewport->palette().color(viewport->foregroundRole()); + + // Compute thumbnailRect + QRect thumbnailRect = QRect( + rect.left() + (rect.width() - thumbnailPix.width()) / 2, + rect.top() + (thumbnailHeight - thumbnailPix.height()) + ITEM_MARGIN, + thumbnailPix.width(), + thumbnailPix.height()); + + // Draw background + const QRect backgroundRect = thumbnailRect.adjusted(-ITEM_MARGIN, -ITEM_MARGIN, ITEM_MARGIN, ITEM_MARGIN); + if (selected) { + d->drawBackground(painter, backgroundRect, bgColor, borderColor); + } else if (underMouse) { + painter->setOpacity(0.2); + d->drawBackground(painter, backgroundRect, bgColor, borderColor); + painter->setOpacity(1.); + } else if (opaque) { + d->drawShadow(painter, thumbnailRect); + } + + // Draw thumbnail + if (opaque) { + painter->setPen(borderColor); + painter->setRenderHint(QPainter::Antialiasing, false); + QRect borderRect = thumbnailRect.adjusted(-1, -1, 0, 0); + painter->drawRect(borderRect); + } + painter->drawPixmap(thumbnailRect.left(), thumbnailRect.top(), thumbnailPix); + + // Draw modified indicator + bool isModified = d->mView->isModified(index); + if (isModified) { + // Draws a pixmap of the save button frame, as an indicator that + // the image has been modified + QPoint framePosition = d->saveButtonPosition(rect); + d->initSaveButtonPixmap(); + painter->drawPixmap(framePosition, d->mSaveButtonPixmap); + } + + // Draw busy indicator + if (d->mView->isBusy(index)) { + QPixmap pix = d->mView->busySequenceCurrentPixmap(); + painter->drawPixmap( + thumbnailRect.left() + (thumbnailRect.width() - pix.width()) / 2, + thumbnailRect.top() + (thumbnailRect.height() - pix.height()) / 2, + pix); + } + + if (index == d->mIndexUnderCursor) { + // Show bar again: if the thumbnail has changed, we may need to update + // its position. Don't do it if we are over rotate buttons, though: it + // would not be nice to move the button now, the user may want to + // rotate the image one more time. + // The button will get moved when the mouse leaves. + if (!d->mRotateLeftButton->underMouse() && !d->mRotateRightButton->underMouse()) { + d->showContextBar(rect, thumbnailPix); + } + if (isModified) { + // If we just rotated the image with the buttons from the + // button frame, we need to show the save button frame right now. + d->showSaveButton(rect); + } else { + d->mSaveButton->hide(); + } + } + + QRect textRect( + rect.left() + ITEM_MARGIN, + rect.top() + 2 * ITEM_MARGIN + thumbnailHeight, + rect.width() - 2 * ITEM_MARGIN, + d->mView->fontMetrics().height()); + if (isDirOrArchive || (d->mDetails & PreviewItemDelegate::FileNameDetail)) { + d->drawText(painter, textRect, fgColor, index.data().toString()); + textRect.moveTop(textRect.bottom()); + } + + if (!isDirOrArchive && (d->mDetails & PreviewItemDelegate::DateDetail)) { + const KDateTime dt = TimeUtils::dateTimeForFileItem(fileItem); + d->drawText(painter, textRect, fgColor, KGlobal::locale()->formatDateTime(dt)); + textRect.moveTop(textRect.bottom()); + } + + if (!isDirOrArchive && (d->mDetails & PreviewItemDelegate::ImageSizeDetail)) { + if (fullSize.isValid()) { + const QString text = QString("%1x%2").arg(fullSize.width()).arg(fullSize.height()); + d->drawText(painter, textRect, fgColor, text); + textRect.moveTop(textRect.bottom()); + } + } + + if (!isDirOrArchive && (d->mDetails & PreviewItemDelegate::FileSizeDetail)) { + const KIO::filesize_t size = fileItem.size(); + if (size > 0) { + const QString st = KIO::convertSize(size); + d->drawText(painter, textRect, fgColor, st); + textRect.moveTop(textRect.bottom()); + } + } + + if (!isDirOrArchive && (d->mDetails & PreviewItemDelegate::RatingDetail)) { +#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE + d->drawRating(painter, rect, index.data(SemanticInfoDirModel::RatingRole)); +#endif + } + +#ifdef DEBUG_DRAW_CURRENT + if (d->mView->currentIndex() == index) { + painter->fillRect(rect.left(), rect.top(), 12, 12, Qt::red); + } +#endif +} + +void PreviewItemDelegate::setThumbnailSize(const QSize& value) +{ + d->mThumbnailSize = value; + d->updateViewGridSize(); + d->updateContextBar(); + d->mElidedTextCache.clear(); +} + +void PreviewItemDelegate::slotSaveClicked() +{ + saveDocumentRequested(urlForIndex(d->mIndexUnderCursor)); +} + +void PreviewItemDelegate::slotRotateLeftClicked() +{ + d->selectIndexUnderCursorIfNoMultiSelection(); + rotateDocumentLeftRequested(urlForIndex(d->mIndexUnderCursor)); +} + +void PreviewItemDelegate::slotRotateRightClicked() +{ + d->selectIndexUnderCursorIfNoMultiSelection(); + rotateDocumentRightRequested(urlForIndex(d->mIndexUnderCursor)); +} + +void PreviewItemDelegate::slotFullScreenClicked() +{ + showDocumentInFullScreenRequested(urlForIndex(d->mIndexUnderCursor)); +} + +void PreviewItemDelegate::slotToggleSelectionClicked() +{ + d->mView->selectionModel()->select(d->mIndexUnderCursor, QItemSelectionModel::Toggle); + d->updateToggleSelectionButton(); +} + +PreviewItemDelegate::ThumbnailDetails PreviewItemDelegate::thumbnailDetails() const +{ + return d->mDetails; +} + +void PreviewItemDelegate::setThumbnailDetails(PreviewItemDelegate::ThumbnailDetails details) +{ + d->mDetails = details; + d->updateViewGridSize(); + d->mView->scheduleDelayedItemsLayout(); +} + +PreviewItemDelegate::ContextBarActions PreviewItemDelegate::contextBarActions() const +{ + return d->mContextBarActions; +} + +void PreviewItemDelegate::setContextBarActions(PreviewItemDelegate::ContextBarActions actions) +{ + d->mContextBarActions = actions; + d->updateContextBar(); +} + +Qt::TextElideMode PreviewItemDelegate::textElideMode() const +{ + return d->mTextElideMode; +} + +void PreviewItemDelegate::setTextElideMode(Qt::TextElideMode mode) +{ + if (d->mTextElideMode == mode) { + return; + } + d->mTextElideMode = mode; + d->mElidedTextCache.clear(); + d->mView->viewport()->update(); +} + +void PreviewItemDelegate::slotRowsChanged() +{ + // We need to update hover ui because the current index may have + // disappeared: for example if the current image is removed with "del". + QPoint pos = d->mView->viewport()->mapFromGlobal(QCursor::pos()); + QModelIndex index = d->mView->indexAt(pos); + d->updateHoverUi(index); +} + +QWidget * PreviewItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const +{ + return new ItemEditor(parent); +} + +void PreviewItemDelegate::setEditorData(QWidget* widget, const QModelIndex& index) const +{ + ItemEditor* edit = qobject_cast(widget); + if (!edit) { + return; + } + edit->setText(index.data().toString()); +} + +void PreviewItemDelegate::updateEditorGeometry(QWidget* widget, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + ItemEditor* edit = qobject_cast(widget); + if (!edit) { + return; + } + QString text = index.data().toString(); + int textWidth = edit->fontMetrics().width(" " + text + " "); + QRect textRect( + option.rect.left() + (option.rect.width() - textWidth) / 2, + option.rect.top() + 2 * ITEM_MARGIN + d->mThumbnailSize.height(), + textWidth, + edit->sizeHint().height()); + + edit->setGeometry(textRect); +} + +void PreviewItemDelegate::setModelData(QWidget* widget, QAbstractItemModel* model, const QModelIndex& index) const +{ + ItemEditor* edit = qobject_cast(widget); + if (!edit) { + return; + } + if (index.data().toString() != edit->text()) { + model->setData(index, edit->text(), Qt::EditRole); + } +} + +} // namespace diff --git a/gwenview/lib/thumbnailview/previewitemdelegate.h b/gwenview/lib/thumbnailview/previewitemdelegate.h new file mode 100644 index 00000000..3b11969f --- /dev/null +++ b/gwenview/lib/thumbnailview/previewitemdelegate.h @@ -0,0 +1,126 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef PREVIEWITEMDELEGATE_H +#define PREVIEWITEMDELEGATE_H + +#include + +// Qt +#include + +// KDE + +// Local + +class KUrl; + +namespace Gwenview +{ + +class ThumbnailView; + +struct PreviewItemDelegatePrivate; + +/** + * An ItemDelegate which generates thumbnails for images. It also makes sure + * all items are of the same size. + */ +class GWENVIEWLIB_EXPORT PreviewItemDelegate : public QItemDelegate +{ + Q_OBJECT +public: + PreviewItemDelegate(ThumbnailView*); + ~PreviewItemDelegate(); + + enum ContextBarAction { + NoAction = 0, + SelectionAction = 1, + FullScreenAction = 2, + RotateAction = 4 + }; + Q_DECLARE_FLAGS(ContextBarActions, ContextBarAction) + + enum ThumbnailDetail { + FileNameDetail = 1, + DateDetail = 2, + RatingDetail = 4, + ImageSizeDetail = 8, + FileSizeDetail = 16 + }; + // FIXME: Find out why this cause problems with Qt::Alignment in + // PreviewItemDelegate! + Q_DECLARE_FLAGS(ThumbnailDetails, ThumbnailDetail) + + /** + * Returns which thumbnail details are shown + */ + ThumbnailDetails thumbnailDetails() const; + + void setThumbnailDetails(ThumbnailDetails); + + ContextBarActions contextBarActions() const; + + void setContextBarActions(ContextBarActions); + + Qt::TextElideMode textElideMode() const; + + void setTextElideMode(Qt::TextElideMode); + + virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual void setEditorData(QWidget* editor, const QModelIndex& index) const; + virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const; + virtual void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const; + + virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const; + virtual QSize sizeHint(const QStyleOptionViewItem & /*option*/, const QModelIndex & /*index*/) const; + +Q_SIGNALS: + void saveDocumentRequested(const KUrl&); + void rotateDocumentLeftRequested(const KUrl&); + void rotateDocumentRightRequested(const KUrl&); + void showDocumentInFullScreenRequested(const KUrl&); + void setDocumentRatingRequested(const KUrl&, int rating); + +private Q_SLOTS: + void setThumbnailSize(const QSize&); + + void slotSaveClicked(); + void slotRotateLeftClicked(); + void slotRotateRightClicked(); + void slotFullScreenClicked(); + void slotToggleSelectionClicked(); + void slotRowsChanged(); + +protected: + virtual bool eventFilter(QObject*, QEvent*); + +private: + PreviewItemDelegatePrivate* const d; + friend struct PreviewItemDelegatePrivate; +}; + +} // namespace + +// See upper +Q_DECLARE_OPERATORS_FOR_FLAGS(Gwenview::PreviewItemDelegate::ThumbnailDetails) +Q_DECLARE_OPERATORS_FOR_FLAGS(Gwenview::PreviewItemDelegate::ContextBarActions) + +#endif /* PREVIEWITEMDELEGATE_H */ diff --git a/gwenview/lib/thumbnailview/thumbnailbarview.cpp b/gwenview/lib/thumbnailview/thumbnailbarview.cpp new file mode 100644 index 00000000..5863f728 --- /dev/null +++ b/gwenview/lib/thumbnailview/thumbnailbarview.cpp @@ -0,0 +1,533 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau +Copyright 2008 Ilya Konkov + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "thumbnailbarview.moc" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include + +// Local +#include "lib/hud/hudtheme.h" +#include "lib/paintutils.h" +#include "lib/thumbnailview/abstractthumbnailviewhelper.h" +#include "lib/thumbnailview/contextbarbutton.h" + +namespace Gwenview +{ + +/** + * Duration in ms of the smooth scroll + */ +const int SMOOTH_SCROLL_DURATION = 250; + +/** + * Space between the item outer rect and the content, and between the + * thumbnail and the caption + */ +const int ITEM_MARGIN = 5; + +/** How dark is the shadow, 0 is invisible, 255 is as dark as possible */ +const int SHADOW_STRENGTH = 127; + +/** How many pixels around the thumbnail are shadowed */ +const int SHADOW_SIZE = 4; + +struct ThumbnailBarItemDelegatePrivate +{ + // Key is height * 1000 + width + typedef QMap ShadowCache; + mutable ShadowCache mShadowCache; + + ThumbnailBarItemDelegate* q; + ThumbnailView* mView; + ContextBarButton* mToggleSelectionButton; + + QColor mBorderColor; + QPersistentModelIndex mIndexUnderCursor; + + void setupToggleSelectionButton() + { + mToggleSelectionButton = new ContextBarButton("list-add", mView->viewport()); + mToggleSelectionButton->hide(); + QObject::connect(mToggleSelectionButton, SIGNAL(clicked(bool)), q, SLOT(toggleSelection())); + } + + void showToolTip(QHelpEvent* helpEvent) + { + QModelIndex index = mView->indexAt(helpEvent->pos()); + if (!index.isValid()) { + return; + } + QString fullText = index.data().toString(); + QPoint pos = QCursor::pos(); + QToolTip::showText(pos, fullText, mView); + } + + void drawShadow(QPainter* painter, const QRect& rect) const + { + const QPoint shadowOffset(-SHADOW_SIZE, -SHADOW_SIZE + 1); + + int key = rect.height() * 1000 + rect.width(); + + ShadowCache::Iterator it = mShadowCache.find(key); + if (it == mShadowCache.end()) { + QSize size = QSize(rect.width() + 2 * SHADOW_SIZE, rect.height() + 2 * SHADOW_SIZE); + QColor color(0, 0, 0, SHADOW_STRENGTH); + QPixmap shadow = PaintUtils::generateFuzzyRect(size, color, SHADOW_SIZE); + it = mShadowCache.insert(key, shadow); + } + painter->drawPixmap(rect.topLeft() + shadowOffset, it.value()); + } + + bool hoverEventFilter(QHoverEvent* event) + { + QModelIndex index = mView->indexAt(event->pos()); + if (index != mIndexUnderCursor) { + updateHoverUi(index); + } + return false; + } + + void updateHoverUi(const QModelIndex& index) + { + QModelIndex oldIndex = mIndexUnderCursor; + mIndexUnderCursor = index; + + if (mIndexUnderCursor.isValid()) { + updateToggleSelectionButton(); + + const QRect rect = mView->visualRect(mIndexUnderCursor); + mToggleSelectionButton->move(rect.topLeft() + QPoint(2, 2)); + mToggleSelectionButton->show(); + } else { + mToggleSelectionButton->hide(); + } + } + + void updateToggleSelectionButton() + { + bool isSelected = mView->selectionModel()->isSelected(mIndexUnderCursor); + mToggleSelectionButton->setIcon(SmallIcon(isSelected ? "list-remove" : "list-add")); + } +}; + +ThumbnailBarItemDelegate::ThumbnailBarItemDelegate(ThumbnailView* view) +: QAbstractItemDelegate(view) +, d(new ThumbnailBarItemDelegatePrivate) +{ + d->q = this; + d->mView = view; + d->setupToggleSelectionButton(); + view->viewport()->installEventFilter(this); + + // Set this attribute so that the viewport receives QEvent::HoverMove and + // QEvent::HoverLeave events. We use these events in the event filter + // installed on the viewport. + // Some styles set this attribute themselves (Oxygen and Skulpture do) but + // others do not (Plastique, Cleanlooks...) + view->viewport()->setAttribute(Qt::WA_Hover); + + d->mBorderColor = PaintUtils::alphaAdjustedF(QColor(Qt::white), 0.65); +} + +QSize ThumbnailBarItemDelegate::sizeHint(const QStyleOptionViewItem & /*option*/, const QModelIndex & index) const +{ + QSize size; + if (d->mView->thumbnailScaleMode() == ThumbnailView::ScaleToFit) { + size = d->mView->gridSize(); + } else { + QPixmap thumbnailPix = d->mView->thumbnailForIndex(index); + size = thumbnailPix.size(); + size.rwidth() += ITEM_MARGIN * 2; + size.rheight() += ITEM_MARGIN * 2; + } + return size; +} + +bool ThumbnailBarItemDelegate::eventFilter(QObject*, QEvent* event) +{ + switch (event->type()) { + case QEvent::ToolTip: + d->showToolTip(static_cast(event)); + return true; + case QEvent::HoverMove: + case QEvent::HoverLeave: + return d->hoverEventFilter(static_cast(event)); + default: + break; + } + + return false; +} + +void ThumbnailBarItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const +{ + bool isSelected = option.state & QStyle::State_Selected; + bool isCurrent = d->mView->selectionModel()->currentIndex() == index; + QPixmap thumbnailPix = d->mView->thumbnailForIndex(index); + QRect rect = option.rect; + + QStyleOptionViewItemV4 opt = option; + const QWidget* widget = opt.widget; + QStyle* style = widget ? widget->style() : QApplication::style(); + if (isSelected && !isCurrent) { + // Draw selected but not current item backgrounds with some transparency + // so that the current item stands out. + painter->setOpacity(.33); + } + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget); + painter->setOpacity(1); + + // Draw thumbnail + if (!thumbnailPix.isNull()) { + QRect thumbnailRect = QRect( + rect.left() + (rect.width() - thumbnailPix.width()) / 2, + rect.top() + (rect.height() - thumbnailPix.height()) / 2 - 1, + thumbnailPix.width(), + thumbnailPix.height()); + + if (!thumbnailPix.hasAlphaChannel()) { + d->drawShadow(painter, thumbnailRect); + painter->setPen(d->mBorderColor); + painter->setRenderHint(QPainter::Antialiasing, false); + QRect borderRect = thumbnailRect.adjusted(-1, -1, 0, 0); + painter->drawRect(borderRect); + } + painter->drawPixmap(thumbnailRect.left(), thumbnailRect.top(), thumbnailPix); + + // Draw busy indicator + if (d->mView->isBusy(index)) { + QPixmap pix = d->mView->busySequenceCurrentPixmap(); + painter->drawPixmap( + thumbnailRect.left() + (thumbnailRect.width() - pix.width()) / 2, + thumbnailRect.top() + (thumbnailRect.height() - pix.height()) / 2, + pix); + } + } +} + +void ThumbnailBarItemDelegate::toggleSelection() +{ + d->mView->selectionModel()->select(d->mIndexUnderCursor, QItemSelectionModel::Toggle); + d->updateToggleSelectionButton(); +} + +ThumbnailBarItemDelegate::~ThumbnailBarItemDelegate() +{ + delete d; +} + +/** + * This proxy style makes it possible to override the value returned by + * styleHint() which leads to not-so-nice results with some styles. + * + * We cannot use QProxyStyle because it takes ownership of the base style, + * which causes crash when user change styles. + */ +class ProxyStyle : public QWindowsStyle +{ +public: + ProxyStyle() : QWindowsStyle() + { + } + + void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w = 0) const + { + QApplication::style()->drawPrimitive(pe, opt, p, w); + } + + void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w = 0) const + { + QApplication::style()->drawControl(element, opt, p, w); + } + + void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *w = 0) const + { + QApplication::style()->drawComplexControl(cc, opt, p, w); + } + + int styleHint(StyleHint sh, const QStyleOption *opt = 0, const QWidget *w = 0, QStyleHintReturn *shret = 0) const + { + switch (sh) { + case SH_ItemView_ShowDecorationSelected: + // We want the highlight to cover our thumbnail + return true; + case SH_ScrollView_FrameOnlyAroundContents: + // Ensure the frame does not include the scrollbar. This ensure the + // scrollbar touches the edge of the window and thus can touch the + // edge of the screen when maximized + return false; + default: + return QApplication::style()->styleHint(sh, opt, w, shret); + } + } + + void polish(QApplication* application) + { + QApplication::style()->polish(application); + } + + void polish(QPalette& palette) + { + QApplication::style()->polish(palette); + } + + void polish(QWidget* widget) + { + QApplication::style()->polish(widget); + } + + void unpolish(QWidget* widget) + { + QApplication::style()->unpolish(widget); + } + + void unpolish(QApplication* application) + { + QApplication::style()->unpolish(application); + } + + int pixelMetric(PixelMetric pm, const QStyleOption* opt, const QWidget* widget) const + { + switch (pm) { + case PM_MaximumDragDistance: + // Ensure the fullscreen thumbnailbar does not go away while + // dragging the scrollbar if the mouse cursor is too far away from + // the widget + return -1; + default: + return QApplication::style()->pixelMetric(pm, opt, widget); + } + } +}; + +typedef int (QSize::*QSizeDimension)() const; + +struct ThumbnailBarViewPrivate +{ + ThumbnailBarView* q; + QStyle* mStyle; + QTimeLine* mTimeLine; + + Qt::Orientation mOrientation; + int mRowCount; + + QScrollBar* scrollBar() const + { + return mOrientation == Qt::Horizontal ? q->horizontalScrollBar() : q->verticalScrollBar(); + } + + QSizeDimension mainDimension() const + { + return mOrientation == Qt::Horizontal ? &QSize::width : &QSize::height; + } + + QSizeDimension oppositeDimension() const + { + return mOrientation == Qt::Horizontal ? &QSize::height : &QSize::width; + } + + void smoothScrollTo(const QModelIndex& index) + { + if (!index.isValid()) { + return; + } + + const QRect rect = q->visualRect(index); + + int oldValue = scrollBar()->value(); + int newValue = scrollToValue(rect); + if (mTimeLine->state() == QTimeLine::Running) { + mTimeLine->stop(); + } + mTimeLine->setFrameRange(oldValue, newValue); + mTimeLine->start(); + } + + int scrollToValue(const QRect& rect) + { + // This code is a much simplified version of + // QListViewPrivate::horizontalScrollToValue() + const QRect area = q->viewport()->rect(); + int value = scrollBar()->value(); + + if (mOrientation == Qt::Horizontal) { + if (q->isRightToLeft()) { + value += (area.width() - rect.width()) / 2 - rect.left(); + } else { + value += rect.left() - (area.width() - rect.width()) / 2; + } + } else { + value += rect.top() - (area.height() - rect.height()) / 2; + } + return value; + } + + void updateMinMaxSizes() + { + QSizeDimension dimension = oppositeDimension(); + int scrollBarSize = (scrollBar()->sizeHint().*dimension)(); + QSize minSize(0, mRowCount * 48 + scrollBarSize); + QSize maxSize(QWIDGETSIZE_MAX, mRowCount * 256 + scrollBarSize); + if (mOrientation == Qt::Vertical) { + minSize.transpose(); + maxSize.transpose(); + } + q->setMinimumSize(minSize); + q->setMaximumSize(maxSize); + } + + void updateThumbnailSize() + { + QSizeDimension dimension = oppositeDimension(); + int scrollBarSize = (scrollBar()->sizeHint().*dimension)(); + int widgetSize = (q->size().*dimension)(); + + if (mRowCount > 1) { + // Decrease widgetSize because otherwise the view sometimes wraps at + // mRowCount-1 instead of mRowCount. Probably because gridSize * + // mRowCount is too close to widgetSize. + --widgetSize; + } + + int gridWidth, gridHeight; + if (mOrientation == Qt::Horizontal) { + gridHeight = (widgetSize - scrollBarSize - 2 * q->frameWidth()) / mRowCount; + gridWidth = qRound(gridHeight * q->thumbnailAspectRatio()); + } else { + gridWidth = (widgetSize - scrollBarSize - 2 * q->frameWidth()) / mRowCount; + gridHeight = qRound(gridWidth / q->thumbnailAspectRatio()); + } + if (q->thumbnailScaleMode() == ThumbnailView::ScaleToFit) { + q->setGridSize(QSize(gridWidth, gridHeight)); + } + q->setThumbnailWidth(gridWidth - ITEM_MARGIN * 2); + } +}; + +ThumbnailBarView::ThumbnailBarView(QWidget* parent) +: ThumbnailView(parent) +, d(new ThumbnailBarViewPrivate) +{ + d->q = this; + d->mTimeLine = new QTimeLine(SMOOTH_SCROLL_DURATION, this); + connect(d->mTimeLine, SIGNAL(frameChanged(int)), + SLOT(slotFrameChanged(int))); + + d->mRowCount = 1; + d->mOrientation = Qt::Vertical; // To pass value-has-changed check in setOrientation() + setOrientation(Qt::Horizontal); + + setObjectName(QLatin1String("thumbnailBarView")); + setWrapping(true); + + d->mStyle = new ProxyStyle; + setStyle(d->mStyle); +} + +ThumbnailBarView::~ThumbnailBarView() +{ + delete d->mStyle; + delete d; +} + +Qt::Orientation ThumbnailBarView::orientation() const +{ + return d->mOrientation; +} + +void ThumbnailBarView::setOrientation(Qt::Orientation orientation) +{ + if (d->mOrientation == orientation) { + return; + } + d->mOrientation = orientation; + + if (d->mOrientation == Qt::Vertical) { + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + setFlow(LeftToRight); + } else { + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setFlow(TopToBottom); + } + + d->updateMinMaxSizes(); +} + +void ThumbnailBarView::slotFrameChanged(int value) +{ + d->scrollBar()->setValue(value); +} + +void ThumbnailBarView::resizeEvent(QResizeEvent *event) +{ + ThumbnailView::resizeEvent(event); + d->updateThumbnailSize(); +} + +void ThumbnailBarView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + QListView::selectionChanged(selected, deselected); + + QModelIndexList oldList = deselected.indexes(); + QModelIndexList newList = selected.indexes(); + // Only scroll the list if the user went from one image to another. If the + // user just unselected one image from a set of two, he might want to + // reselect it again, scrolling the thumbnails would prevent him from + // reselecting it by clicking again without moving the mouse. + if (oldList.count() == 1 && newList.count() == 1 && isVisible()) { + d->smoothScrollTo(newList.first()); + } +} + +void ThumbnailBarView::wheelEvent(QWheelEvent* event) +{ + d->scrollBar()->setValue(d->scrollBar()->value() - event->delta()); +} + +int ThumbnailBarView::rowCount() const +{ + return d->mRowCount; +} + +void ThumbnailBarView::setRowCount(int rowCount) +{ + Q_ASSERT(rowCount > 0); + d->mRowCount = rowCount; + d->updateMinMaxSizes(); + d->updateThumbnailSize(); +} + +} // namespace diff --git a/gwenview/lib/thumbnailview/thumbnailbarview.h b/gwenview/lib/thumbnailview/thumbnailbarview.h new file mode 100644 index 00000000..db3e414b --- /dev/null +++ b/gwenview/lib/thumbnailview/thumbnailbarview.h @@ -0,0 +1,89 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau +Copyright 2008 Ilya Konkov + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef THUMBNAILBARVIEW_H +#define THUMBNAILBARVIEW_H + +#include + +// Qt +#include + +// KDE + +// Local +#include + +namespace Gwenview +{ + +struct ThumbnailBarItemDelegatePrivate; + +class GWENVIEWLIB_EXPORT ThumbnailBarItemDelegate : public QAbstractItemDelegate +{ + Q_OBJECT +public: + ThumbnailBarItemDelegate(ThumbnailView*); + ~ThumbnailBarItemDelegate(); + + virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const; + virtual QSize sizeHint(const QStyleOptionViewItem & /*option*/, const QModelIndex & /*index*/) const; + +protected: + virtual bool eventFilter(QObject*, QEvent*); + +private Q_SLOTS: + void toggleSelection(); + +private: + ThumbnailBarItemDelegatePrivate* const d; + friend struct ThumbnailBarItemDelegatePrivate; +}; + +struct ThumbnailBarViewPrivate; +class GWENVIEWLIB_EXPORT ThumbnailBarView : public ThumbnailView +{ + Q_OBJECT +public: + ThumbnailBarView(QWidget* = 0); + ~ThumbnailBarView(); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation); + + int rowCount() const; + void setRowCount(int); + +protected: + virtual void resizeEvent(QResizeEvent * event); + virtual void wheelEvent(QWheelEvent* event); + virtual void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + +private Q_SLOTS: + void slotFrameChanged(int); + +private: + ThumbnailBarViewPrivate* const d; +}; + +} // namespace + +#endif /* THUMBNAILBARVIEW_H */ diff --git a/gwenview/lib/thumbnailview/thumbnailslider.cpp b/gwenview/lib/thumbnailview/thumbnailslider.cpp new file mode 100644 index 00000000..fdf75a31 --- /dev/null +++ b/gwenview/lib/thumbnailview/thumbnailslider.cpp @@ -0,0 +1,74 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "thumbnailslider.moc" + +// Local +#include + +// Qt +#include +#include + +// KDE + +namespace Gwenview +{ + +struct ThumbnailSliderPrivate +{ +}; + +ThumbnailSlider::ThumbnailSlider(QWidget* parent) +: ZoomSlider(parent) +, d(new ThumbnailSliderPrivate) +{ + connect(slider(), SIGNAL(actionTriggered(int)), + SLOT(slotActionTriggered(int))); + slider()->setRange(ThumbnailView::MinThumbnailSize, ThumbnailView::MaxThumbnailSize); +} + +ThumbnailSlider::~ThumbnailSlider() +{ + delete d; +} + +void ThumbnailSlider::slotActionTriggered(int actionTriggered) +{ + updateToolTip(); + + if (actionTriggered != QAbstractSlider::SliderNoAction) { + // If we are updating because of a direct action on the slider, show + // the tooltip immediately. + const QPoint pos = slider()->mapToGlobal(QPoint(0, slider()->height() / 2)); + QToolTip::showText(pos, slider()->toolTip(), slider()); + } +} + +void ThumbnailSlider::updateToolTip() +{ + // FIXME: i18n? + const int size = slider()->sliderPosition(); + const QString text = QString("%1 x %2").arg(size).arg(size); + slider()->setToolTip(text); +} + +} // namespace diff --git a/gwenview/lib/thumbnailview/thumbnailslider.h b/gwenview/lib/thumbnailview/thumbnailslider.h new file mode 100644 index 00000000..07ed2cfd --- /dev/null +++ b/gwenview/lib/thumbnailview/thumbnailslider.h @@ -0,0 +1,60 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef THUMBNAILSLIDER_H +#define THUMBNAILSLIDER_H + +#include + +// Qt +#include + +// KDE + +// Local +#include + +namespace Gwenview +{ + +struct ThumbnailSliderPrivate; +/** + * A zoom slider which shows the thumbnail size as a tooltip when it is + * adjusted + */ +class GWENVIEWLIB_EXPORT ThumbnailSlider : public ZoomSlider +{ + Q_OBJECT +public: + ThumbnailSlider(QWidget* parent = 0); + ~ThumbnailSlider(); + + void updateToolTip(); + +private Q_SLOTS: + void slotActionTriggered(int actionTriggered); + +private: + ThumbnailSliderPrivate* const d; +}; + +} // namespace + +#endif /* THUMBNAILSLIDER_H */ diff --git a/gwenview/lib/thumbnailview/thumbnailview.cpp b/gwenview/lib/thumbnailview/thumbnailview.cpp new file mode 100644 index 00000000..0e0fe594 --- /dev/null +++ b/gwenview/lib/thumbnailview/thumbnailview.cpp @@ -0,0 +1,972 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "thumbnailview.moc" + +// Std +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include + +// Local +#include "abstractdocumentinfoprovider.h" +#include "abstractthumbnailviewhelper.h" +#include "archiveutils.h" +#include "dragpixmapgenerator.h" +#include "mimetypeutils.h" +#include "urlutils.h" +#include +#include + +namespace Gwenview +{ + +#undef ENABLE_LOG +#undef LOG +//#define ENABLE_LOG +#ifdef ENABLE_LOG +#define LOG(x) kDebug() << x +#else +#define LOG(x) ; +#endif + +/** How many msec to wait before starting to smooth thumbnails */ +const int SMOOTH_DELAY = 500; + +const int WHEEL_ZOOM_MULTIPLIER = 4; + +static KFileItem fileItemForIndex(const QModelIndex& index) +{ + if (!index.isValid()) { + LOG("Invalid index"); + return KFileItem(); + } + QVariant data = index.data(KDirModel::FileItemRole); + return qvariant_cast(data); +} + +static KUrl urlForIndex(const QModelIndex& index) +{ + KFileItem item = fileItemForIndex(index); + return item.isNull() ? KUrl() : item.url(); +} + +struct Thumbnail +{ + Thumbnail(const QPersistentModelIndex& index_, const KDateTime& mtime) + : mIndex(index_) + , mModificationTime(mtime) + , mFileSize(0) + , mRough(true) + , mWaitingForThumbnail(true) {} + + Thumbnail() + : mFileSize(0) + , mRough(true) + , mWaitingForThumbnail(true) {} + + /** + * Init the thumbnail based on a icon + */ + void initAsIcon(const QPixmap& pix) + { + mGroupPix = pix; + int largeGroupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::Large); + mFullSize = QSize(largeGroupSize, largeGroupSize); + } + + bool isGroupPixAdaptedForSize(int size) const + { + if (mWaitingForThumbnail) { + return false; + } + if (mGroupPix.isNull()) { + return false; + } + const int groupSize = qMax(mGroupPix.width(), mGroupPix.height()); + if (groupSize >= size) { + return true; + } + + // groupSize is less than size, but this may be because the full image + // is the same size as groupSize + return groupSize == qMax(mFullSize.width(), mFullSize.height()); + } + + void prepareForRefresh(const KDateTime& mtime) + { + mModificationTime = mtime; + mFileSize = 0; + mGroupPix = QPixmap(); + mAdjustedPix = QPixmap(); + mFullSize = QSize(); + mRealFullSize = QSize(); + mRough = true; + mWaitingForThumbnail = true; + } + + QPersistentModelIndex mIndex; + KDateTime mModificationTime; + /// The pix loaded from .thumbnails/{large,normal} + QPixmap mGroupPix; + /// Scaled version of mGroupPix, adjusted to ThumbnailView::thumbnailSize + QPixmap mAdjustedPix; + /// Size of the full image + QSize mFullSize; + /// Real size of the full image, invalid unless the thumbnail + /// represents a raster image (not an icon) + QSize mRealFullSize; + /// File size of the full image + KIO::filesize_t mFileSize; + /// Whether mAdjustedPix represents has been scaled using fast or smooth + /// transformation + bool mRough; + /// Set to true if mGroupPix should be replaced with a real thumbnail + bool mWaitingForThumbnail; +}; + +typedef QHash ThumbnailForUrl; +typedef QQueue UrlQueue; +typedef QSet PersistentModelIndexSet; + +struct ThumbnailViewPrivate +{ + ThumbnailView* q; + ThumbnailView::ThumbnailScaleMode mScaleMode; + QSize mThumbnailSize; + qreal mThumbnailAspectRatio; + AbstractDocumentInfoProvider* mDocumentInfoProvider; + AbstractThumbnailViewHelper* mThumbnailViewHelper; + ThumbnailForUrl mThumbnailForUrl; + QTimer mScheduledThumbnailGenerationTimer; + + UrlQueue mSmoothThumbnailQueue; + QTimer mSmoothThumbnailTimer; + + QPixmap mWaitingThumbnail; + QPointer mThumbnailProvider; + + PersistentModelIndexSet mBusyIndexSet; + KPixmapSequence mBusySequence; + QTimeLine* mBusyAnimationTimeLine; + + bool mCreateThumbnailsForRemoteUrls; + + void setupBusyAnimation() + { + mBusySequence = KPixmapSequence("process-working", 22); + mBusyAnimationTimeLine = new QTimeLine(100 * mBusySequence.frameCount(), q); + mBusyAnimationTimeLine->setCurveShape(QTimeLine::LinearCurve); + mBusyAnimationTimeLine->setEndFrame(mBusySequence.frameCount() - 1); + mBusyAnimationTimeLine->setLoopCount(0); + QObject::connect(mBusyAnimationTimeLine, SIGNAL(frameChanged(int)), + q, SLOT(updateBusyIndexes())); + } + + void scheduleThumbnailGeneration() + { + if (mThumbnailProvider) { + mThumbnailProvider->removePendingItems(); + } + mSmoothThumbnailQueue.clear(); + mScheduledThumbnailGenerationTimer.start(); + } + + void updateThumbnailForModifiedDocument(const QModelIndex& index) + { + Q_ASSERT(mDocumentInfoProvider); + KFileItem item = fileItemForIndex(index); + KUrl url = item.url(); + ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(mThumbnailSize.width()); + QPixmap pix; + QSize fullSize; + mDocumentInfoProvider->thumbnailForDocument(url, group, &pix, &fullSize); + mThumbnailForUrl[url] = Thumbnail(QPersistentModelIndex(index), KDateTime::currentLocalDateTime()); + q->setThumbnail(item, pix, fullSize, 0); + } + + void appendItemsToThumbnailProvider(const KFileItemList& list) + { + if (mThumbnailProvider) { + ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(mThumbnailSize.width()); + mThumbnailProvider->setThumbnailGroup(group); + mThumbnailProvider->appendItems(list); + } + } + + void roughAdjustThumbnail(Thumbnail* thumbnail) + { + const QPixmap& mGroupPix = thumbnail->mGroupPix; + const int groupSize = qMax(mGroupPix.width(), mGroupPix.height()); + const int fullSize = qMax(thumbnail->mFullSize.width(), thumbnail->mFullSize.height()); + if (fullSize == groupSize && mGroupPix.height() <= mThumbnailSize.height() && mGroupPix.width() <= mThumbnailSize.width()) { + thumbnail->mAdjustedPix = mGroupPix; + thumbnail->mRough = false; + } else { + thumbnail->mAdjustedPix = scale(mGroupPix, Qt::FastTransformation); + thumbnail->mRough = true; + } + } + + void initDragPixmap(QDrag* drag, const QModelIndexList& indexes) + { + const int thumbCount = qMin(indexes.count(), int(DragPixmapGenerator::MaxCount)); + QList lst; + for (int row = 0; row < thumbCount; ++row) { + const KUrl url = urlForIndex(indexes[row]); + lst << mThumbnailForUrl.value(url).mAdjustedPix; + } + DragPixmapGenerator::DragPixmap dragPixmap = DragPixmapGenerator::generate(lst, indexes.count()); + drag->setPixmap(dragPixmap.pix); + drag->setHotSpot(dragPixmap.hotSpot); + } + + QPixmap scale(const QPixmap& pix, Qt::TransformationMode transformationMode) + { + switch (mScaleMode) { + case ThumbnailView::ScaleToFit: + return pix.scaled(mThumbnailSize.width(), mThumbnailSize.height(), Qt::KeepAspectRatio, transformationMode); + break; + case ThumbnailView::ScaleToSquare: { + int minSize = qMin(pix.width(), pix.height()); + QPixmap pix2 = pix.copy((pix.width() - minSize) / 2, (pix.height() - minSize) / 2, minSize, minSize); + return pix2.scaled(mThumbnailSize.width(), mThumbnailSize.height(), Qt::KeepAspectRatio, transformationMode); + } + case ThumbnailView::ScaleToHeight: + return pix.scaledToHeight(mThumbnailSize.height(), transformationMode); + break; + case ThumbnailView::ScaleToWidth: + return pix.scaledToWidth(mThumbnailSize.width(), transformationMode); + break; + } + // Keep compiler happy + Q_ASSERT(0); + return QPixmap(); + } +}; + +ThumbnailView::ThumbnailView(QWidget* parent) +: QListView(parent) +, d(new ThumbnailViewPrivate) +{ + d->q = this; + d->mScaleMode = ScaleToFit; + d->mThumbnailViewHelper = 0; + d->mDocumentInfoProvider = 0; + d->mThumbnailProvider = 0; + // Init to some stupid value so that the first call to setThumbnailSize() + // is not ignored (do not use 0 in case someone try to divide by + // mThumbnailSize...) + d->mThumbnailSize = QSize(1, 1); + d->mThumbnailAspectRatio = 1; + d->mCreateThumbnailsForRemoteUrls = true; + + setFrameShape(QFrame::NoFrame); + setViewMode(QListView::IconMode); + setResizeMode(QListView::Adjust); + setDragEnabled(true); + setAcceptDrops(true); + setDropIndicatorShown(true); + setUniformItemSizes(true); + setEditTriggers(QAbstractItemView::EditKeyPressed); + + d->setupBusyAnimation(); + + setVerticalScrollMode(ScrollPerPixel); + setHorizontalScrollMode(ScrollPerPixel); + + d->mScheduledThumbnailGenerationTimer.setSingleShot(true); + d->mScheduledThumbnailGenerationTimer.setInterval(500); + connect(&d->mScheduledThumbnailGenerationTimer, SIGNAL(timeout()), + SLOT(generateThumbnailsForItems())); + + d->mSmoothThumbnailTimer.setSingleShot(true); + connect(&d->mSmoothThumbnailTimer, SIGNAL(timeout()), + SLOT(smoothNextThumbnail())); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(QPoint)), + SLOT(showContextMenu())); + + if (KGlobalSettings::singleClick()) { + connect(this, SIGNAL(clicked(QModelIndex)), + SLOT(emitIndexActivatedIfNoModifiers(QModelIndex))); + } else { + connect(this, SIGNAL(doubleClicked(QModelIndex)), + SLOT(emitIndexActivatedIfNoModifiers(QModelIndex))); + } +} + +ThumbnailView::~ThumbnailView() +{ + delete d; +} + +ThumbnailView::ThumbnailScaleMode ThumbnailView::thumbnailScaleMode() const +{ + return d->mScaleMode; +} + +void ThumbnailView::setThumbnailScaleMode(ThumbnailScaleMode mode) +{ + d->mScaleMode = mode; + setUniformItemSizes(mode == ScaleToFit || mode == ScaleToSquare); +} + +void ThumbnailView::setModel(QAbstractItemModel* newModel) +{ + if (model()) { + disconnect(model(), 0, this, 0); + } + QListView::setModel(newModel); + connect(model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), + SIGNAL(rowsRemovedSignal(QModelIndex,int,int))); +} + +void ThumbnailView::setThumbnailProvider(ThumbnailProvider* thumbnailProvider) +{ + GV_RETURN_IF_FAIL(d->mThumbnailProvider != thumbnailProvider); + if (thumbnailProvider) { + connect(thumbnailProvider, SIGNAL(thumbnailLoaded(KFileItem,QPixmap,QSize,qulonglong)), + SLOT(setThumbnail(KFileItem,QPixmap,QSize,qulonglong))); + connect(thumbnailProvider, SIGNAL(thumbnailLoadingFailed(KFileItem)), + SLOT(setBrokenThumbnail(KFileItem))); + } else { + disconnect(d->mThumbnailProvider, 0 , this, 0); + } + d->mThumbnailProvider = thumbnailProvider; +} + +void ThumbnailView::updateThumbnailSize() +{ + QSize value = d->mThumbnailSize; + // mWaitingThumbnail + int waitingThumbnailSize; + if (value.width() > 64) { + waitingThumbnailSize = 48; + } else { + waitingThumbnailSize = 32; + } + QPixmap icon = DesktopIcon("chronometer", waitingThumbnailSize); + QPixmap pix(value); + pix.fill(Qt::transparent); + QPainter painter(&pix); + painter.setOpacity(0.5); + painter.drawPixmap((value.width() - icon.width()) / 2, (value.height() - icon.height()) / 2, icon); + painter.end(); + d->mWaitingThumbnail = pix; + + // Stop smoothing + d->mSmoothThumbnailTimer.stop(); + d->mSmoothThumbnailQueue.clear(); + + // Clear adjustedPixes + ThumbnailForUrl::iterator + it = d->mThumbnailForUrl.begin(), + end = d->mThumbnailForUrl.end(); + for (; it != end; ++it) { + it.value().mAdjustedPix = QPixmap(); + } + + thumbnailSizeChanged(value); + thumbnailWidthChanged(value.width()); + if (d->mScaleMode != ScaleToFit) { + scheduleDelayedItemsLayout(); + } + d->scheduleThumbnailGeneration(); +} + +void ThumbnailView::setThumbnailWidth(int width) +{ + if(d->mThumbnailSize.width() == width) { + return; + } + int height = round((qreal)width / d->mThumbnailAspectRatio); + d->mThumbnailSize = QSize(width, height); + updateThumbnailSize(); +} + +void ThumbnailView::setThumbnailAspectRatio(qreal ratio) +{ + if(d->mThumbnailAspectRatio == ratio) { + return; + } + d->mThumbnailAspectRatio = ratio; + int width = d->mThumbnailSize.width(); + int height = round((qreal)width / d->mThumbnailAspectRatio); + d->mThumbnailSize = QSize(width, height); + updateThumbnailSize(); +} + +qreal ThumbnailView::thumbnailAspectRatio() const +{ + return d->mThumbnailAspectRatio; +} + +QSize ThumbnailView::thumbnailSize() const +{ + return d->mThumbnailSize; +} + +void ThumbnailView::setThumbnailViewHelper(AbstractThumbnailViewHelper* helper) +{ + d->mThumbnailViewHelper = helper; +} + +AbstractThumbnailViewHelper* ThumbnailView::thumbnailViewHelper() const +{ + return d->mThumbnailViewHelper; +} + +void ThumbnailView::setDocumentInfoProvider(AbstractDocumentInfoProvider* provider) +{ + d->mDocumentInfoProvider = provider; + if (provider) { + connect(provider, SIGNAL(busyStateChanged(QModelIndex,bool)), + SLOT(updateThumbnailBusyState(QModelIndex,bool))); + connect(provider, SIGNAL(documentChanged(QModelIndex)), + SLOT(updateThumbnail(QModelIndex))); + } +} + +AbstractDocumentInfoProvider* ThumbnailView::documentInfoProvider() const +{ + return d->mDocumentInfoProvider; +} + +void ThumbnailView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) +{ + QListView::rowsAboutToBeRemoved(parent, start, end); + + // Remove references to removed items + KFileItemList itemList; + for (int pos = start; pos <= end; ++pos) { + QModelIndex index = model()->index(pos, 0, parent); + KFileItem item = fileItemForIndex(index); + if (item.isNull()) { + kDebug() << "Skipping invalid item!" << index.data().toString(); + continue; + } + + QUrl url = item.url(); + d->mThumbnailForUrl.remove(url); + d->mSmoothThumbnailQueue.removeAll(url); + + itemList.append(item); + } + + if (d->mThumbnailProvider) { + d->mThumbnailProvider->removeItems(itemList); + } + + // Removing rows might make new images visible, make sure their thumbnail + // is generated + d->mScheduledThumbnailGenerationTimer.start(); +} + +void ThumbnailView::rowsInserted(const QModelIndex& parent, int start, int end) +{ + QListView::rowsInserted(parent, start, end); + d->mScheduledThumbnailGenerationTimer.start(); + rowsInsertedSignal(parent, start, end); +} + +void ThumbnailView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + QListView::dataChanged(topLeft, bottomRight); + bool thumbnailsNeedRefresh = false; + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { + QModelIndex index = model()->index(row, 0); + KFileItem item = fileItemForIndex(index); + if (item.isNull()) { + kWarning() << "Invalid item for index" << index << ". This should not happen!"; + GV_FATAL_FAILS; + continue; + } + + ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(item.url()); + if (it != d->mThumbnailForUrl.end()) { + // All thumbnail views are connected to the model, so + // ThumbnailView::dataChanged() is called for all of them. As a + // result this method will also be called for views which are not + // currently visible, and do not yet have a thumbnail for the + // modified url. + KDateTime mtime = item.time(KFileItem::ModificationTime); + if (it->mModificationTime != mtime || it->mFileSize != item.size()) { + // dataChanged() is called when the file changes but also when + // the model fetched additional data such as semantic info. To + // avoid needless refreshes, we only trigger a refresh if the + // modification time changes. + thumbnailsNeedRefresh = true; + it->prepareForRefresh(mtime); + } + } + } + if (thumbnailsNeedRefresh) { + d->mScheduledThumbnailGenerationTimer.start(); + } +} + +void ThumbnailView::showContextMenu() +{ + d->mThumbnailViewHelper->showContextMenu(this); +} + +void ThumbnailView::emitIndexActivatedIfNoModifiers(const QModelIndex& index) +{ + if (QApplication::keyboardModifiers() == Qt::NoModifier) { + emit indexActivated(index); + } +} + +void ThumbnailView::setThumbnail(const KFileItem& item, const QPixmap& pixmap, const QSize& size, qulonglong fileSize) +{ + ThumbnailForUrl::iterator it = d->mThumbnailForUrl.find(item.url()); + if (it == d->mThumbnailForUrl.end()) { + return; + } + Thumbnail& thumbnail = it.value(); + thumbnail.mGroupPix = pixmap; + thumbnail.mAdjustedPix = QPixmap(); + int largeGroupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::Large); + thumbnail.mFullSize = size.isValid() ? size : QSize(largeGroupSize, largeGroupSize); + thumbnail.mRealFullSize = size; + thumbnail.mWaitingForThumbnail = false; + thumbnail.mFileSize = fileSize; + + update(thumbnail.mIndex); + if (d->mScaleMode != ScaleToFit) { + scheduleDelayedItemsLayout(); + } +} + +void ThumbnailView::setBrokenThumbnail(const KFileItem& item) +{ + ThumbnailForUrl::iterator it = d->mThumbnailForUrl.find(item.url()); + if (it == d->mThumbnailForUrl.end()) { + return; + } + Thumbnail& thumbnail = it.value(); + MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item); + if (kind == MimeTypeUtils::KIND_VIDEO) { + // Special case for videos because our kde install may come without + // support for video thumbnails so we show the mimetype icon instead of + // a broken image icon + ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(d->mThumbnailSize.height()); + QPixmap pix = item.pixmap(ThumbnailGroup::pixelSize(group)); + thumbnail.initAsIcon(pix); + } else if (kind == MimeTypeUtils::KIND_DIR) { + // Special case for folders because ThumbnailProvider does not return a + // thumbnail if there is no images + thumbnail.mWaitingForThumbnail = false; + return; + } else { + thumbnail.initAsIcon(DesktopIcon("image-missing", 48)); + thumbnail.mFullSize = thumbnail.mGroupPix.size(); + } + update(thumbnail.mIndex); +} + +QPixmap ThumbnailView::thumbnailForIndex(const QModelIndex& index, QSize* fullSize) +{ + KFileItem item = fileItemForIndex(index); + if (item.isNull()) { + LOG("Invalid item"); + if (fullSize) { + *fullSize = QSize(); + } + return QPixmap(); + } + KUrl url = item.url(); + + // Find or create Thumbnail instance + ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(url); + if (it == d->mThumbnailForUrl.end()) { + Thumbnail thumbnail = Thumbnail(QPersistentModelIndex(index), item.time(KFileItem::ModificationTime)); + it = d->mThumbnailForUrl.insert(url, thumbnail); + } + Thumbnail& thumbnail = it.value(); + + // If dir or archive, generate a thumbnail from fileitem pixmap + MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item); + if (kind == MimeTypeUtils::KIND_ARCHIVE || kind == MimeTypeUtils::KIND_DIR) { + int groupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::fromPixelSize(d->mThumbnailSize.height())); + if (thumbnail.mGroupPix.isNull() || thumbnail.mGroupPix.height() < groupSize) { + QPixmap pix = item.pixmap(groupSize); + thumbnail.initAsIcon(pix); + if (kind == MimeTypeUtils::KIND_ARCHIVE) { + // No thumbnails for archives + thumbnail.mWaitingForThumbnail = false; + } else if (!d->mCreateThumbnailsForRemoteUrls && !UrlUtils::urlIsFastLocalFile(url)) { + // If we don't want thumbnails for remote urls, use + // "folder-remote" icon for remote folders, so that they do + // not look like regular folders + thumbnail.mWaitingForThumbnail = false; + thumbnail.initAsIcon(DesktopIcon("folder-remote", groupSize)); + } else { + // set mWaitingForThumbnail to true (necessary in the case + // 'thumbnail' already existed before, but with a too small + // mGroupPix) + thumbnail.mWaitingForThumbnail = true; + } + } + } + + if (thumbnail.mGroupPix.isNull()) { + if (fullSize) { + *fullSize = QSize(); + } + return d->mWaitingThumbnail; + } + + // Adjust thumbnail + if (thumbnail.mAdjustedPix.isNull()) { + d->roughAdjustThumbnail(&thumbnail); + } + if (thumbnail.mRough && !d->mSmoothThumbnailQueue.contains(url)) { + d->mSmoothThumbnailQueue.enqueue(url); + if (!d->mSmoothThumbnailTimer.isActive()) { + d->mSmoothThumbnailTimer.start(SMOOTH_DELAY); + } + } + if (fullSize) { + *fullSize = thumbnail.mRealFullSize; + } + return thumbnail.mAdjustedPix; +} + +bool ThumbnailView::isModified(const QModelIndex& index) const +{ + if (!d->mDocumentInfoProvider) { + return false; + } + KUrl url = urlForIndex(index); + return d->mDocumentInfoProvider->isModified(url); +} + +bool ThumbnailView::isBusy(const QModelIndex& index) const +{ + if (!d->mDocumentInfoProvider) { + return false; + } + KUrl url = urlForIndex(index); + return d->mDocumentInfoProvider->isBusy(url); +} + +void ThumbnailView::startDrag(Qt::DropActions supportedActions) +{ + QModelIndexList indexes = selectionModel()->selectedIndexes(); + if (indexes.isEmpty()) { + return; + } + QDrag* drag = new QDrag(this); + drag->setMimeData(model()->mimeData(indexes)); + d->initDragPixmap(drag, indexes); + drag->exec(supportedActions, Qt::CopyAction); +} + +void ThumbnailView::dragEnterEvent(QDragEnterEvent* event) +{ + QAbstractItemView::dragEnterEvent(event); + if (event->mimeData()->hasUrls()) { + event->acceptProposedAction(); + } +} + +void ThumbnailView::dragMoveEvent(QDragMoveEvent* event) +{ + // Necessary, otherwise we don't reach dropEvent() + QAbstractItemView::dragMoveEvent(event); + event->acceptProposedAction(); +} + +void ThumbnailView::dropEvent(QDropEvent* event) +{ + const KUrl::List urlList = KUrl::List::fromMimeData(event->mimeData()); + if (urlList.isEmpty()) { + return; + } + + QModelIndex destIndex = indexAt(event->pos()); + if (destIndex.isValid()) { + KFileItem item = fileItemForIndex(destIndex); + if (item.isDir()) { + KUrl destUrl = item.url(); + d->mThumbnailViewHelper->showMenuForUrlDroppedOnDir(this, urlList, destUrl); + return; + } + } + + d->mThumbnailViewHelper->showMenuForUrlDroppedOnViewport(this, urlList); + + event->acceptProposedAction(); +} + +void ThumbnailView::keyPressEvent(QKeyEvent* event) +{ + QListView::keyPressEvent(event); + if (event->key() == Qt::Key_Return) { + const QModelIndex index = selectionModel()->currentIndex(); + if (index.isValid() && selectionModel()->selectedIndexes().count() == 1) { + emit indexActivated(index); + } + } +} + +void ThumbnailView::resizeEvent(QResizeEvent* event) +{ + QListView::resizeEvent(event); + d->scheduleThumbnailGeneration(); +} + +void ThumbnailView::showEvent(QShowEvent* event) +{ + QListView::showEvent(event); + d->scheduleThumbnailGeneration(); + QTimer::singleShot(0, this, SLOT(scrollToSelectedIndex())); +} + +void ThumbnailView::wheelEvent(QWheelEvent* event) +{ + // If we don't adjust the single step, the wheel scroll exactly one item up + // and down, giving the impression that the items do not move but only + // their label changes. + // For some reason it is necessary to set the step here: setting it in + // setThumbnailSize() does not work + //verticalScrollBar()->setSingleStep(d->mThumbnailSize / 5); + if (event->modifiers() == Qt::ControlModifier) { + int width = d->mThumbnailSize.width() + (event->delta() > 0 ? 1 : -1) * WHEEL_ZOOM_MULTIPLIER; + width = qMax(int(MinThumbnailSize), qMin(width, int(MaxThumbnailSize))); + setThumbnailWidth(width); + } else { + QListView::wheelEvent(event); + } +} + +void ThumbnailView::scrollToSelectedIndex() +{ + QModelIndexList list = selectedIndexes(); + if (list.count() >= 1) { + scrollTo(list.first(), PositionAtCenter); + } +} + +void ThumbnailView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + QListView::selectionChanged(selected, deselected); + emit selectionChangedSignal(selected, deselected); +} + +void ThumbnailView::scrollContentsBy(int dx, int dy) +{ + QListView::scrollContentsBy(dx, dy); + d->scheduleThumbnailGeneration(); +} + +void ThumbnailView::generateThumbnailsForItems() +{ + if (!isVisible() || !model()) { + return; + } + const QRect visibleRect = viewport()->rect(); + const int visibleSurface = visibleRect.width() * visibleRect.height(); + const QPoint origin = visibleRect.center(); + + // distance => item + QMultiMap itemMap; + + for (int row = 0; row < model()->rowCount(); ++row) { + QModelIndex index = model()->index(row, 0); + KFileItem item = fileItemForIndex(index); + QUrl url = item.url(); + + // Filter out remote items if necessary + if (!d->mCreateThumbnailsForRemoteUrls && !url.isLocalFile()) { + continue; + } + + // Filter out archives + MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item); + if (kind == MimeTypeUtils::KIND_ARCHIVE) { + continue; + } + + // Immediately update modified items + if (d->mDocumentInfoProvider && d->mDocumentInfoProvider->isModified(url)) { + d->updateThumbnailForModifiedDocument(index); + continue; + } + + // Filter out items which already have a thumbnail + ThumbnailForUrl::ConstIterator it = d->mThumbnailForUrl.constFind(url); + if (it != d->mThumbnailForUrl.constEnd() && it.value().isGroupPixAdaptedForSize(d->mThumbnailSize.height())) { + continue; + } + + // Compute distance + int distance; + const QRect itemRect = visualRect(index); + const qreal itemSurface = itemRect.width() * itemRect.height(); + const QRect visibleItemRect = visibleRect.intersected(itemRect); + qreal visibleItemFract = 0; + if (itemSurface > 0) { + visibleItemFract = visibleItemRect.width() * visibleItemRect.height() / itemSurface; + } + if (visibleItemFract > 0.7) { + // Item is visible, order thumbnails from left to right, top to bottom + // Distance is computed so that it is between 0 and visibleSurface + distance = itemRect.top() * visibleRect.width() + itemRect.left(); + // Make sure directory thumbnails are generated after image thumbnails: + // Distance is between visibleSurface and 2 * visibleSurface + if (kind == MimeTypeUtils::KIND_DIR) { + distance = distance + visibleSurface; + } + } else { + // Item is not visible, order thumbnails according to distance + // Start at 2 * visibleSurface to ensure invisible thumbnails are + // generated *after* visible thumbnails + distance = 2 * visibleSurface + (itemRect.center() - origin).manhattanLength(); + } + + // Add the item to our map + itemMap.insert(distance, item); + + // Insert the thumbnail in mThumbnailForUrl, so that + // setThumbnail() can find the item to update + if (it == d->mThumbnailForUrl.constEnd()) { + Thumbnail thumbnail = Thumbnail(QPersistentModelIndex(index), item.time(KFileItem::ModificationTime)); + d->mThumbnailForUrl.insert(url, thumbnail); + } + } + + if (!itemMap.isEmpty()) { + d->appendItemsToThumbnailProvider(itemMap.values()); + } +} + +void ThumbnailView::updateThumbnail(const QModelIndex& index) +{ + KFileItem item = fileItemForIndex(index); + KUrl url = item.url(); + if (d->mDocumentInfoProvider && d->mDocumentInfoProvider->isModified(url)) { + d->updateThumbnailForModifiedDocument(index); + } else { + KFileItemList list; + list << item; + d->appendItemsToThumbnailProvider(list); + } +} + +void ThumbnailView::updateThumbnailBusyState(const QModelIndex& _index, bool busy) +{ + QPersistentModelIndex index(_index); + if (busy && !d->mBusyIndexSet.contains(index)) { + d->mBusyIndexSet << index; + update(index); + if (d->mBusyAnimationTimeLine->state() != QTimeLine::Running) { + d->mBusyAnimationTimeLine->start(); + } + } else if (!busy && d->mBusyIndexSet.remove(index)) { + update(index); + if (d->mBusyIndexSet.isEmpty()) { + d->mBusyAnimationTimeLine->stop(); + } + } +} + +void ThumbnailView::updateBusyIndexes() +{ + Q_FOREACH(const QPersistentModelIndex & index, d->mBusyIndexSet) { + update(index); + } +} + +QPixmap ThumbnailView::busySequenceCurrentPixmap() const +{ + return d->mBusySequence.frameAt(d->mBusyAnimationTimeLine->currentFrame()); +} + +void ThumbnailView::smoothNextThumbnail() +{ + if (d->mSmoothThumbnailQueue.isEmpty()) { + return; + } + + if (d->mThumbnailProvider && d->mThumbnailProvider->isRunning()) { + // give mThumbnailProvider priority over smoothing + d->mSmoothThumbnailTimer.start(SMOOTH_DELAY); + return; + } + + KUrl url = d->mSmoothThumbnailQueue.dequeue(); + ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(url); + GV_RETURN_IF_FAIL2(it != d->mThumbnailForUrl.end(), url << "not in mThumbnailForUrl."); + + Thumbnail& thumbnail = it.value(); + thumbnail.mAdjustedPix = d->scale(thumbnail.mGroupPix, Qt::SmoothTransformation); + thumbnail.mRough = false; + + GV_RETURN_IF_FAIL2(thumbnail.mIndex.isValid(), "index for" << url << "is invalid."); + update(thumbnail.mIndex); + + if (!d->mSmoothThumbnailQueue.isEmpty()) { + d->mSmoothThumbnailTimer.start(0); + } +} + +void ThumbnailView::reloadThumbnail(const QModelIndex& index) +{ + KUrl url = urlForIndex(index); + if (!url.isValid()) { + kWarning() << "Invalid url for index" << index; + return; + } + ThumbnailProvider::deleteImageThumbnail(url); + ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(url); + if (it == d->mThumbnailForUrl.end()) { + return; + } + d->mThumbnailForUrl.erase(it); + generateThumbnailsForItems(); +} + +void ThumbnailView::setCreateThumbnailsForRemoteUrls(bool createRemoteThumbs) +{ + d->mCreateThumbnailsForRemoteUrls = createRemoteThumbs; +} + +} // namespace diff --git a/gwenview/lib/thumbnailview/thumbnailview.h b/gwenview/lib/thumbnailview/thumbnailview.h new file mode 100644 index 00000000..d5894834 --- /dev/null +++ b/gwenview/lib/thumbnailview/thumbnailview.h @@ -0,0 +1,215 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef THUMBNAILVIEW_H +#define THUMBNAILVIEW_H + +#include + +// Qt +#include + +// KDE +#include + +class KFileItem; +class QDragEnterEvent; +class QDragMoveEvent; +class QDropEvent; +class QPixmap; + +namespace Gwenview +{ + +class AbstractDocumentInfoProvider; +class AbstractThumbnailViewHelper; +class ThumbnailProvider; + +struct ThumbnailViewPrivate; +class GWENVIEWLIB_EXPORT ThumbnailView : public QListView +{ + Q_OBJECT +public: + enum { + MinThumbnailSize = 48, + MaxThumbnailSize = 256 + }; + + enum ThumbnailScaleMode { + ScaleToSquare, + ScaleToHeight, + ScaleToWidth, + ScaleToFit + }; + ThumbnailView(QWidget* parent); + ~ThumbnailView(); + + void setThumbnailViewHelper(AbstractThumbnailViewHelper* helper); + + AbstractThumbnailViewHelper* thumbnailViewHelper() const; + + void setDocumentInfoProvider(AbstractDocumentInfoProvider* provider); + + AbstractDocumentInfoProvider* documentInfoProvider() const; + + ThumbnailScaleMode thumbnailScaleMode() const; + + void setThumbnailScaleMode(ThumbnailScaleMode); + + /** + * Returns the thumbnail size. + */ + QSize thumbnailSize() const; + + /** + * Returns the aspect ratio of the thumbnail. + */ + qreal thumbnailAspectRatio() const; + + QPixmap thumbnailForIndex(const QModelIndex&, QSize* fullSize = 0); + + /** + * Returns true if the document pointed by the index has been modified + * inside Gwenview. + */ + bool isModified(const QModelIndex&) const; + + /** + * Returns true if the document pointed by the index is currently busy + * (loading, saving, rotating...) + */ + bool isBusy(const QModelIndex& index) const; + + virtual void setModel(QAbstractItemModel* model); + + void setThumbnailProvider(ThumbnailProvider* thumbnailProvider); + + /** + * Publish this method so that delegates can call it. + */ + using QListView::scheduleDelayedItemsLayout; + + /** + * Returns the current pixmap to paint when drawing a busy index. + */ + QPixmap busySequenceCurrentPixmap() const; + + void reloadThumbnail(const QModelIndex&); + + void updateThumbnailSize(); + + void setCreateThumbnailsForRemoteUrls(bool createRemoteThumbs); + +Q_SIGNALS: + /** + * It seems we can't use the 'activated()' signal for now because it does + * not know about KDE single vs doubleclick settings. The indexActivated() + * signal replaces it. + */ + void indexActivated(const QModelIndex&); + void urlListDropped(const KUrl::List& lst, const KUrl& destination); + + void thumbnailSizeChanged(const QSize&); + void thumbnailWidthChanged(int); + + /** + * Emitted whenever selectionChanged() is called. + * This signal is suffixed with "Signal" because + * QAbstractItemView::selectionChanged() is a slot. + */ + void selectionChangedSignal(const QItemSelection&, const QItemSelection&); + + /** + * Forward some signals from model, so that the delegate can use them + */ + void rowsRemovedSignal(const QModelIndex& parent, int start, int end); + + void rowsInsertedSignal(const QModelIndex& parent, int start, int end); + +public Q_SLOTS: + /** + * Sets the thumbnail's width, in pixels. Keeps aspect ratio unchanged. + */ + void setThumbnailWidth(int width); + + /** + * Sets the thumbnail's aspect ratio. Keeps width unchanged. + */ + void setThumbnailAspectRatio(qreal ratio); + + void scrollToSelectedIndex(); + + void generateThumbnailsForItems(); + +protected: + virtual void dragEnterEvent(QDragEnterEvent*); + + virtual void dragMoveEvent(QDragMoveEvent*); + + virtual void dropEvent(QDropEvent*); + + virtual void keyPressEvent(QKeyEvent*); + + virtual void resizeEvent(QResizeEvent*); + + virtual void scrollContentsBy(int dx, int dy); + + virtual void showEvent(QShowEvent*); + + virtual void wheelEvent(QWheelEvent*); + + virtual void startDrag(Qt::DropActions); + +protected Q_SLOTS: + virtual void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); + virtual void rowsInserted(const QModelIndex& parent, int start, int end); + virtual void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + +private Q_SLOTS: + void showContextMenu(); + void emitIndexActivatedIfNoModifiers(const QModelIndex&); + void setThumbnail(const KFileItem&, const QPixmap&, const QSize&, qulonglong fileSize); + void setBrokenThumbnail(const KFileItem&); + + /** + * Generate thumbnail for @a index. + */ + void updateThumbnail(const QModelIndex& index); + + /** + * Tells the view the busy state of the document pointed by the index has changed. + */ + void updateThumbnailBusyState(const QModelIndex& index, bool); + + /* + * Cause a repaint of all busy indexes + */ + void updateBusyIndexes(); + + void smoothNextThumbnail(); + +private: + friend struct ThumbnailViewPrivate; + ThumbnailViewPrivate * const d; +}; + +} // namespace + +#endif /* THUMBNAILVIEW_H */ diff --git a/gwenview/lib/thumbnailview/tooltipwidget.cpp b/gwenview/lib/thumbnailview/tooltipwidget.cpp new file mode 100644 index 00000000..90210c9b --- /dev/null +++ b/gwenview/lib/thumbnailview/tooltipwidget.cpp @@ -0,0 +1,107 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "tooltipwidget.moc" + +// Qt +#include +#include +#include + +// KDE +#include +#include + +// Local +#include + +static const int RADIUS = 5; +static const int HMARGIN = 2; + +namespace Gwenview +{ + +struct ToolTipWidgetPrivate +{ + QString mText; + qreal mOpacity; +}; + +ToolTipWidget::ToolTipWidget(QWidget* parent) +: QWidget(parent) +, d(new ToolTipWidgetPrivate) +{ + d->mOpacity = 1.; + setAttribute(Qt::WA_NoSystemBackground); +} + +ToolTipWidget::~ToolTipWidget() +{ + delete d; +} + +qreal ToolTipWidget::opacity() const +{ + return d->mOpacity; +} + +void ToolTipWidget::setOpacity(qreal opacity) +{ + d->mOpacity = opacity; + update(); +} + +QString ToolTipWidget::text() const +{ + return d->mText; +} + +void ToolTipWidget::setText(const QString& text) +{ + d->mText = text; + update(); +} + +QSize ToolTipWidget::sizeHint() const +{ + QSize sh = fontMetrics().size(0 /* flags */, d->mText); + return QSize(sh.width() + 2 * HMARGIN, sh.height()); +} + +void ToolTipWidget::paintEvent(QPaintEvent*) +{ + QColor bg2Color = palette().color(QPalette::Highlight); + QColor bg1Color = KColorScheme::shade(bg2Color, KColorScheme::LightShade, 0.2); + + QLinearGradient gradient(QPointF(0.0, 0.0), QPointF(0.0, height())); + gradient.setColorAt(0.0, bg1Color); + gradient.setColorAt(1.0, bg2Color); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + painter.setOpacity(d->mOpacity); + QPainterPath path = PaintUtils::roundedRectangle(rect(), RADIUS); + painter.fillPath(path, gradient); + painter.setPen(palette().color(QPalette::HighlightedText)); + painter.drawText(rect().adjusted(HMARGIN, 0, -HMARGIN, 0), 0 /* flags */, d->mText); +} + +} // namespace diff --git a/gwenview/lib/thumbnailview/tooltipwidget.h b/gwenview/lib/thumbnailview/tooltipwidget.h new file mode 100644 index 00000000..32a612e3 --- /dev/null +++ b/gwenview/lib/thumbnailview/tooltipwidget.h @@ -0,0 +1,64 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef TOOLTIPWIDGET_H +#define TOOLTIPWIDGET_H + +// Qt +#include + +// KDE + +// Local + +namespace Gwenview +{ + +struct ToolTipWidgetPrivate; + +/** + * A label which uses tooltip colors and can be faded + */ +class ToolTipWidget : public QWidget +{ + Q_OBJECT + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) +public: + ToolTipWidget(QWidget* parent = 0); + ~ToolTipWidget(); + + qreal opacity() const; + void setOpacity(qreal); + + QString text() const; + void setText(const QString& text); + + virtual QSize sizeHint() const; + +protected: + virtual void paintEvent(QPaintEvent*); + +private: + ToolTipWidgetPrivate* const d; +}; + +} // namespace + +#endif /* TOOLTIPWIDGET_H */ diff --git a/gwenview/lib/timeutils.cpp b/gwenview/lib/timeutils.cpp new file mode 100644 index 00000000..607ff476 --- /dev/null +++ b/gwenview/lib/timeutils.cpp @@ -0,0 +1,159 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "timeutils.h" + +// Qt +#include + +// KDE +#include +#include +#include + +// Exiv2 +#include +#include + +// Local +#include +#include + +namespace Gwenview +{ + +namespace TimeUtils +{ + +static Exiv2::ExifData::const_iterator findDateTimeKey(const Exiv2::ExifData& exifData) +{ + // Ordered list of keys to try + static QList lst = QList() + << Exiv2::ExifKey("Exif.Photo.DateTimeOriginal") + << Exiv2::ExifKey("Exif.Image.DateTimeOriginal") + << Exiv2::ExifKey("Exif.Photo.DateTimeDigitized") + << Exiv2::ExifKey("Exif.Image.DateTime"); + + Exiv2::ExifData::const_iterator it, end = exifData.end(); + Q_FOREACH(const Exiv2::ExifKey& key, lst) { + it = exifData.findKey(key); + if (it != end) { + return it; + } + } + return end; +} + +struct CacheItem +{ + KDateTime fileMTime; + KDateTime realTime; + + void update(const KFileItem& fileItem) + { + KDateTime time = fileItem.time(KFileItem::ModificationTime); + if (fileMTime == time) { + return; + } + + fileMTime = time; + + if (!updateFromExif(fileItem.url())) { + realTime = time; + } + } + + bool updateFromExif(const KUrl& url) + { + if (!UrlUtils::urlIsFastLocalFile(url)) { + return false; + } + QString path = url.path(); + Exiv2ImageLoader loader; + QByteArray header; + { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + kWarning() << "Could not open" << path << "for reading"; + return false; + } + header = file.read(65536); // FIXME: Is this big enough? + } + + if (!loader.load(header)) { + return false; + } + Exiv2::Image::AutoPtr img = loader.popImage(); + try { + Exiv2::ExifData exifData = img->exifData(); + if (exifData.empty()) { + return false; + } + Exiv2::ExifData::const_iterator it = findDateTimeKey(exifData); + if (it == exifData.end()) { + kWarning() << "No date in exif header of" << path; + return false; + } + + std::ostringstream stream; + stream << *it; + QString value = QString::fromLocal8Bit(stream.str().c_str()); + + KDateTime dt = KDateTime::fromString(value, "%Y:%m:%d %H:%M:%S"); + if (!dt.isValid()) { + kWarning() << "Invalid date in exif header of" << path; + return false; + } + + realTime = dt; + return true; + } catch (const Exiv2::Error& error) { + kWarning() << "Failed to read date from exif header of" << path << ". Error:" << error.what(); + return false; + } + } +}; + +typedef QHash Cache; + +KDateTime dateTimeForFileItem(const KFileItem& fileItem, CachePolicy cachePolicy) +{ + if (cachePolicy == SkipCache) { + CacheItem item; + item.update(fileItem); + return item.realTime; + } + + static Cache cache; + const KUrl url = fileItem.targetUrl(); + + Cache::iterator it = cache.find(url); + if (it == cache.end()) { + it = cache.insert(url, CacheItem()); + } + + it.value().update(fileItem); + return it.value().realTime; +} + +} // namespace + +} // namespace diff --git a/gwenview/lib/timeutils.h b/gwenview/lib/timeutils.h new file mode 100644 index 00000000..198e48be --- /dev/null +++ b/gwenview/lib/timeutils.h @@ -0,0 +1,47 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef TIMEUTILS_H +#define TIMEUTILS_H + +// Local +#include + +class KFileItem; +class KDateTime; + +namespace Gwenview +{ + +namespace TimeUtils +{ + +enum CachePolicy +{ + SkipCache, + UseCache +}; + +GWENVIEWLIB_EXPORT KDateTime dateTimeForFileItem(const KFileItem&, CachePolicy cachePolicy = UseCache); + +} // namespace + +} // namespace +#endif /* TIMEUTILS_H */ diff --git a/gwenview/lib/transformimageoperation.cpp b/gwenview/lib/transformimageoperation.cpp new file mode 100644 index 00000000..857b6e0b --- /dev/null +++ b/gwenview/lib/transformimageoperation.cpp @@ -0,0 +1,108 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include "transformimageoperation.h" + +// Qt + +// KDE +#include +#include + +// Local +#include "document/abstractdocumenteditor.h" + +namespace Gwenview +{ + +struct TransformImageOperationPrivate +{ + Orientation mOrientation; +}; + +TransformJob::TransformJob(Orientation orientation) +: mOrientation(orientation) +{ + +} + +void TransformJob::threadedStart() +{ + if (!checkDocumentEditor()) { + return; + } + document()->editor()->applyTransformation(mOrientation); + setError(NoError); +} + +TransformImageOperation::TransformImageOperation(Orientation orientation) +: d(new TransformImageOperationPrivate) +{ + d->mOrientation = orientation; + switch (d->mOrientation) { + case ROT_90: + setText(i18nc("(qtundo-format)", "Rotate Right")); + break; + case ROT_270: + setText(i18nc("(qtundo-format)", "Rotate Left")); + break; + case HFLIP: + setText(i18nc("(qtundo-format)", "Mirror")); + break; + case VFLIP: + setText(i18nc("(qtundo-format)", "Flip")); + break; + default: + // We should not get there because the transformations listed above are + // the only one available from the UI. Define a default text nevertheless. + setText(i18nc("(qtundo-format)", "Transform")); + break; + } +} + +TransformImageOperation::~TransformImageOperation() +{ + delete d; +} + +void TransformImageOperation::redo() +{ + redoAsDocumentJob(new TransformJob(d->mOrientation)); +} + +void TransformImageOperation::undo() +{ + Orientation orientation; + switch (d->mOrientation) { + case ROT_90: + orientation = ROT_270; + break; + case ROT_270: + orientation = ROT_90; + break; + default: + orientation = d->mOrientation; + break; + } + document()->enqueueJob(new TransformJob(orientation)); +} + +} // namespace diff --git a/gwenview/lib/transformimageoperation.h b/gwenview/lib/transformimageoperation.h new file mode 100644 index 00000000..acc194ad --- /dev/null +++ b/gwenview/lib/transformimageoperation.h @@ -0,0 +1,66 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef TRANSFORMIMAGEOPERATION_H +#define TRANSFORMIMAGEOPERATION_H + +#include + +// Qt + +// KDE + +// Local +#include +#include + +#include + +namespace Gwenview +{ + +class TransformJob : public ThreadedDocumentJob +{ + Q_OBJECT +public: + TransformJob(Orientation orientation); + void threadedStart(); // reimp + +private: + Orientation mOrientation; +}; + +struct TransformImageOperationPrivate; +class GWENVIEWLIB_EXPORT TransformImageOperation : public AbstractImageOperation +{ +public: + TransformImageOperation(Orientation); + ~TransformImageOperation(); + + virtual void redo(); + virtual void undo(); + +private: + TransformImageOperationPrivate* const d; +}; + +} // namespace + +#endif /* TRANSFORMIMAGEOPERATION_H */ diff --git a/gwenview/lib/urlutils.cpp b/gwenview/lib/urlutils.cpp new file mode 100644 index 00000000..29550186 --- /dev/null +++ b/gwenview/lib/urlutils.cpp @@ -0,0 +1,118 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "urlutils.h" + +// Qt +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include + +// Local +#include +#include + +namespace Gwenview +{ + +namespace UrlUtils +{ + +bool urlIsFastLocalFile(const KUrl& url) +{ + if (!url.isLocalFile()) { + return false; + } + + KMountPoint::List list = KMountPoint::currentMountPoints(); + KMountPoint::Ptr mountPoint = list.findByPath(url.toLocalFile()); + if (!mountPoint) { + // We couldn't find a mount point for the url. We are probably in a + // chroot. Assume everything is fast then. + return true; + } + + return !mountPoint->probablySlow(); +} + +bool urlIsDirectory(const KUrl& url) +{ + if (url.fileName(KUrl::ObeyTrailingSlash).isEmpty()) { + return true; // file:/somewhere/ + } + + // Do direct stat instead of using KIO if the file is local (faster) + if (UrlUtils::urlIsFastLocalFile(url)) { + KDE_struct_stat buff; + if (KDE_stat(QFile::encodeName(url.toLocalFile()), &buff) == 0) { + return S_ISDIR(buff.st_mode); + } + } + + QWidgetList list = QApplication::topLevelWidgets(); + QWidget* parent; + if (list.count() > 0) { + parent = list[0]; + } else { + parent = 0; + } + KIO::UDSEntry entry; + if (KIO::NetAccess::stat(url, entry, parent)) { + return entry.isDir(); + } + return false; +} + +KUrl fixUserEnteredUrl(const KUrl& in) +{ + if (!in.isRelative() && !in.isLocalFile()) { + return in; + } + + QFileInfo info(in.toLocalFile()); + QString path = info.absoluteFilePath(); + + KUrl out = KUrl::fromPath(path); + QString mimeType = MimeTypeUtils::urlMimeType(out); + + const QString protocol = ArchiveUtils::protocolForMimeType(mimeType); + + if (!protocol.isEmpty()) { + KUrl tmp = out; + tmp.setProtocol(protocol); + if (KProtocolManager::supportsListing(tmp)) { + out = tmp; + } + } + return out; +} + +} // namespace + +} // namespace diff --git a/gwenview/lib/urlutils.h b/gwenview/lib/urlutils.h new file mode 100644 index 00000000..94ad0f3a --- /dev/null +++ b/gwenview/lib/urlutils.h @@ -0,0 +1,60 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef URLUTILS_H +#define URLUTILS_H + +#include + +// Qt + +// KDE + +// Local + +class KUrl; + +namespace Gwenview +{ + +namespace UrlUtils +{ + +/** + * Returns whether the url is a local file, and it's not on a slow device like + * an nfs export. + */ +GWENVIEWLIB_EXPORT bool urlIsFastLocalFile(const KUrl& url); + +/** + * Returns whether the url is a directory. + */ +GWENVIEWLIB_EXPORT bool urlIsDirectory(const KUrl& url); + +/** + * Returns a fixed version of a user entered url + */ +GWENVIEWLIB_EXPORT KUrl fixUserEnteredUrl(const KUrl& url); + +} // namespace + +} // namespace + +#endif /* URLUTILS_H */ diff --git a/gwenview/lib/version.h b/gwenview/lib/version.h new file mode 100644 index 00000000..3654bd3c --- /dev/null +++ b/gwenview/lib/version.h @@ -0,0 +1,38 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2010-2013 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef VERSION_H +#define VERSION_H + +/* + +For stable releases, GWENVIEW_VERSION should be "$major.$minor.$patch", +matching KDE SC versions. + +For unstable releases, it should be "$major.$minor.$patch $suffix", where +suffix is one of "pre", "alpha$N", "beta$N" or "rc$N". + +When you change GWENVIEW_VERSION, add the new version in Bugzilla as well: +https://bugs.kde.org/editversions.cgi?product=gwenview + +*/ +#define GWENVIEW_VERSION "4.14.0 pre" + +#endif /* VERSION_H */ diff --git a/gwenview/lib/widgetfloater.cpp b/gwenview/lib/widgetfloater.cpp new file mode 100644 index 00000000..0a836ce7 --- /dev/null +++ b/gwenview/lib/widgetfloater.cpp @@ -0,0 +1,169 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "widgetfloater.moc" + +// Qt +#include +#include +#include + +// KDE +#include +#include + +// Local + +namespace Gwenview +{ + +struct WidgetFloaterPrivate +{ + QWidget* mParent; + QPointer mChild; + Qt::Alignment mAlignment; + + int mHorizontalMargin; + int mVerticalMargin; + bool mInsideUpdateChildGeometry; + + void updateChildGeometry() + { + if (!mChild) { + return; + } + if (mInsideUpdateChildGeometry) { + return; + } + mInsideUpdateChildGeometry = true; + + int posX, posY; + int childWidth, childHeight; + int parentWidth, parentHeight; + + childWidth = mChild->width(); + childHeight = mChild->height(); + + parentWidth = mParent->width(); + parentHeight = mParent->height(); + + if (mAlignment & Qt::AlignLeft) { + posX = mHorizontalMargin; + } else if (mAlignment & Qt::AlignHCenter) { + posX = (parentWidth - childWidth) / 2; + } else if (mAlignment & Qt::AlignJustify) { + posX = mHorizontalMargin; + childWidth = parentWidth - 2 * mHorizontalMargin; + QRect childGeometry = mChild->geometry(); + childGeometry.setWidth(childWidth); + mChild->setGeometry(childGeometry); + } else { + posX = parentWidth - childWidth - mHorizontalMargin; + } + + if (mAlignment & Qt::AlignTop) { + posY = mVerticalMargin; + } else if (mAlignment & Qt::AlignVCenter) { + posY = (parentHeight - childHeight) / 2; + } else { + posY = parentHeight - childHeight - mVerticalMargin; + } + + mChild->move(posX, posY); + + mInsideUpdateChildGeometry = false; + } +}; + +WidgetFloater::WidgetFloater(QWidget* parent) +: QObject(parent) +, d(new WidgetFloaterPrivate) +{ + Q_ASSERT(parent); + d->mParent = parent; + d->mParent->installEventFilter(this); + d->mChild = 0; + d->mAlignment = Qt::AlignCenter; + d->mHorizontalMargin = KDialog::marginHint(); + d->mVerticalMargin = KDialog::marginHint(); + d->mInsideUpdateChildGeometry = false; +} + +WidgetFloater::~WidgetFloater() +{ + delete d; +} + +void WidgetFloater::setChildWidget(QWidget* child) +{ + if (d->mChild) { + d->mChild->removeEventFilter(this); + } + d->mChild = child; + d->mChild->setParent(d->mParent); + d->mChild->installEventFilter(this); + d->updateChildGeometry(); + d->mChild->raise(); + d->mChild->show(); +} + +void WidgetFloater::setAlignment(Qt::Alignment alignment) +{ + d->mAlignment = alignment; + d->updateChildGeometry(); +} + +bool WidgetFloater::eventFilter(QObject*, QEvent* event) +{ + switch (event->type()) { + case QEvent::Resize: + case QEvent::Show: + d->updateChildGeometry(); + break; + default: + break; + } + return false; +} + +void WidgetFloater::setHorizontalMargin(int value) +{ + d->mHorizontalMargin = value; + d->updateChildGeometry(); +} + +int WidgetFloater::horizontalMargin() const +{ + return d->mHorizontalMargin; +} + +void WidgetFloater::setVerticalMargin(int value) +{ + d->mVerticalMargin = value; + d->updateChildGeometry(); +} + +int WidgetFloater::verticalMargin() const +{ + return d->mVerticalMargin; +} + +} // namespace diff --git a/gwenview/lib/widgetfloater.h b/gwenview/lib/widgetfloater.h new file mode 100644 index 00000000..2f91b597 --- /dev/null +++ b/gwenview/lib/widgetfloater.h @@ -0,0 +1,69 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef WIDGETFLOATER_H +#define WIDGETFLOATER_H + +#include + +// Qt +#include + +// KDE + +// Local + +namespace Gwenview +{ + +struct WidgetFloaterPrivate; + +/** + * This helper object makes it possible to place a widget (the child) over + * another (the parent), ensuring the child remains aligned as specified by + * setAlignment() whenever either widget get resized. + */ +class GWENVIEWLIB_EXPORT WidgetFloater : public QObject +{ + Q_OBJECT +public: + WidgetFloater(QWidget* parent); + ~WidgetFloater(); + + void setChildWidget(QWidget*); + + void setAlignment(Qt::Alignment); + + void setHorizontalMargin(int); + int horizontalMargin() const; + + void setVerticalMargin(int); + int verticalMargin() const; + +protected: + bool eventFilter(QObject*, QEvent*); + +private: + WidgetFloaterPrivate* const d; +}; + +} // namespace + +#endif /* WIDGETFLOATER_H */ diff --git a/gwenview/lib/zoomslider.cpp b/gwenview/lib/zoomslider.cpp new file mode 100644 index 00000000..d7f57d01 --- /dev/null +++ b/gwenview/lib/zoomslider.cpp @@ -0,0 +1,159 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "zoomslider.moc" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include + +// Local + +namespace Gwenview +{ + +struct ZoomSliderPrivate +{ + QToolButton* mZoomOutButton; + QToolButton* mZoomInButton; + QSlider* mSlider; + QAction* mZoomInAction; + QAction* mZoomOutAction; + + void updateButtons() + { + mZoomOutButton->setEnabled(mSlider->value() > mSlider->minimum()); + mZoomInButton->setEnabled(mSlider->value() < mSlider->maximum()); + } +}; + +static QToolButton* createZoomButton(const char* iconName) +{ + QToolButton* button = new QToolButton; + button->setIcon(KIcon(iconName)); + button->setAutoRaise(true); + button->setAutoRepeat(true); + return button; +} + +ZoomSlider::ZoomSlider(QWidget* parent) +: QWidget(parent) +, d(new ZoomSliderPrivate) +{ + d->mZoomInButton = createZoomButton("zoom-in"); + d->mZoomOutButton = createZoomButton("zoom-out"); + d->mZoomInAction = 0; + d->mZoomOutAction = 0; + + d->mSlider = new QSlider; + d->mSlider->setOrientation(Qt::Horizontal); + + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + layout->addWidget(d->mZoomOutButton); + layout->addWidget(d->mSlider); + layout->addWidget(d->mZoomInButton); + + connect(d->mSlider, SIGNAL(actionTriggered(int)), + SLOT(slotActionTriggered(int))); + connect(d->mSlider, SIGNAL(valueChanged(int)), + SIGNAL(valueChanged(int))); + + connect(d->mZoomOutButton, SIGNAL(clicked()), + SLOT(zoomOut())); + connect(d->mZoomInButton, SIGNAL(clicked()), + SLOT(zoomIn())); +} + +ZoomSlider::~ZoomSlider() +{ + delete d; +} + +int ZoomSlider::value() const +{ + return d->mSlider->value(); +} + +void ZoomSlider::setValue(int value) +{ + d->mSlider->setValue(value); + d->updateButtons(); +} + +void ZoomSlider::setMinimum(int value) +{ + d->mSlider->setMinimum(value); + d->updateButtons(); +} + +void ZoomSlider::setMaximum(int value) +{ + d->mSlider->setMaximum(value); + d->updateButtons(); +} + +void ZoomSlider::setZoomInAction(QAction* action) +{ + d->mZoomInAction = action; +} + +void ZoomSlider::setZoomOutAction(QAction* action) +{ + d->mZoomOutAction = action; +} + +void ZoomSlider::slotActionTriggered(int) +{ + d->updateButtons(); +} + +void ZoomSlider::zoomOut() +{ + if (d->mZoomOutAction) { + d->mZoomOutAction->trigger(); + } else { + d->mSlider->triggerAction(QAbstractSlider::SliderPageStepSub); + } +} + +void ZoomSlider::zoomIn() +{ + if (d->mZoomInAction) { + d->mZoomInAction->trigger(); + } else { + d->mSlider->triggerAction(QAbstractSlider::SliderPageStepAdd); + } +} + +QSlider* ZoomSlider::slider() const +{ + return d->mSlider; +} + +} // namespace diff --git a/gwenview/lib/zoomslider.h b/gwenview/lib/zoomslider.h new file mode 100644 index 00000000..b8958ea3 --- /dev/null +++ b/gwenview/lib/zoomslider.h @@ -0,0 +1,82 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef ZOOMSLIDER_H +#define ZOOMSLIDER_H + +#include + +// Qt +#include + +// KDE + +// Local + +class QAction; +class QSlider; + +namespace Gwenview +{ + +struct ZoomSliderPrivate; +/** + * A widget featuring an horizontal slider and zoom in/out buttons. + * By default zoom in/out buttons will trigger SliderPageStep{Add,Sub}. + * Their behavior can be changed with setZoomInAction() and + * setZoomOutAction(). + */ +class GWENVIEWLIB_EXPORT ZoomSlider : public QWidget +{ + Q_OBJECT +public: + ZoomSlider(QWidget* parent = 0); + ~ZoomSlider(); + + int value() const; + + QSlider* slider() const; + + void setZoomInAction(QAction*); + + void setZoomOutAction(QAction*); + +public Q_SLOTS: + void setValue(int); + + void setMinimum(int); + + void setMaximum(int); + +Q_SIGNALS: + int valueChanged(int); + +private Q_SLOTS: + void slotActionTriggered(int actionTriggered); + void zoomOut(); + void zoomIn(); + +private: + ZoomSliderPrivate* const d; +}; + +} // namespace + +#endif /* ZOOMSLIDER_H */ diff --git a/gwenview/lib/zoomwidget.cpp b/gwenview/lib/zoomwidget.cpp new file mode 100644 index 00000000..da66a56f --- /dev/null +++ b/gwenview/lib/zoomwidget.cpp @@ -0,0 +1,213 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "zoomwidget.moc" + +// stdc++ +#include + +// Qt +#include +#include +#include +#include +#include + +// KDE +#include +#include + +// Local +#include "zoomslider.h" +#include "signalblocker.h" +#include "statusbartoolbutton.h" + +namespace Gwenview +{ + +static const qreal MAGIC_K = 1.04; +static const qreal MAGIC_OFFSET = 16.; +static const qreal PRECISION = 100.; +inline int sliderValueForZoom(qreal zoom) +{ + return int(PRECISION * (log(zoom) / log(MAGIC_K) + MAGIC_OFFSET)); +} + +inline qreal zoomForSliderValue(int sliderValue) +{ + return pow(MAGIC_K, sliderValue / PRECISION - MAGIC_OFFSET); +} + +struct ZoomWidgetPrivate +{ + ZoomWidget* q; + + StatusBarToolButton* mZoomToFitButton; + StatusBarToolButton* mActualSizeButton; + QLabel* mZoomLabel; + ZoomSlider* mZoomSlider; + QAction* mZoomToFitAction; + QAction* mActualSizeAction; + + QToolButton* mLockZoomButton; + + bool mZoomUpdatedBySlider; + + void emitZoomChanged() + { + // Use QSlider::sliderPosition(), not QSlider::value() because when we are + // called from slotZoomSliderActionTriggered(), QSlider::value() has not + // been updated yet. + qreal zoom = zoomForSliderValue(mZoomSlider->slider()->sliderPosition()); + mZoomUpdatedBySlider = true; + emit q->zoomChanged(zoom); + mZoomUpdatedBySlider = false; + } +}; + +ZoomWidget::ZoomWidget(QWidget* parent) +: QFrame(parent) +, d(new ZoomWidgetPrivate) +{ + d->q = this; + d->mZoomUpdatedBySlider = false; + + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + + d->mZoomToFitButton = new StatusBarToolButton; + d->mActualSizeButton = new StatusBarToolButton; + + if (QApplication::isLeftToRight()) { + d->mZoomToFitButton->setGroupPosition(StatusBarToolButton::GroupLeft); + d->mActualSizeButton->setGroupPosition(StatusBarToolButton::GroupRight); + } else { + d->mActualSizeButton->setGroupPosition(StatusBarToolButton::GroupLeft); + d->mZoomToFitButton->setGroupPosition(StatusBarToolButton::GroupRight); + } + + d->mZoomLabel = new QLabel; + d->mZoomLabel->setFixedWidth(d->mZoomLabel->fontMetrics().width(" 1000% ")); + d->mZoomLabel->setAlignment(Qt::AlignCenter); + + d->mZoomSlider = new ZoomSlider; + d->mZoomSlider->setMinimumWidth(150); + d->mZoomSlider->slider()->setSingleStep(int(PRECISION)); + d->mZoomSlider->slider()->setPageStep(3 * int(PRECISION)); + connect(d->mZoomSlider->slider(), SIGNAL(actionTriggered(int)), SLOT(slotZoomSliderActionTriggered())); + + d->mLockZoomButton = new QToolButton; + d->mLockZoomButton->setAutoRaise(true); + d->mLockZoomButton->setCheckable(true); + updateLockZoomButton(); + connect(d->mLockZoomButton, SIGNAL(toggled(bool)), SLOT(updateLockZoomButton())); + + // Layout + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + layout->addWidget(d->mZoomToFitButton); + layout->addWidget(d->mActualSizeButton); + layout->addWidget(d->mZoomSlider); + layout->addWidget(d->mZoomLabel); + layout->addWidget(d->mLockZoomButton); +} + +ZoomWidget::~ZoomWidget() +{ + delete d; +} + +void ZoomWidget::setActions(QAction* zoomToFitAction, QAction* actualSizeAction, QAction* zoomInAction, QAction* zoomOutAction) +{ + d->mZoomToFitAction = zoomToFitAction; + d->mActualSizeAction = actualSizeAction; + + d->mZoomToFitButton->setDefaultAction(zoomToFitAction); + d->mActualSizeButton->setDefaultAction(actualSizeAction); + + d->mZoomSlider->setZoomInAction(zoomInAction); + d->mZoomSlider->setZoomOutAction(zoomOutAction); + + // Adjust sizes + int width = qMax(d->mZoomToFitButton->sizeHint().width(), d->mActualSizeButton->sizeHint().width()); + d->mZoomToFitButton->setFixedWidth(width); + d->mActualSizeButton->setFixedWidth(width); +} + +void ZoomWidget::slotZoomSliderActionTriggered() +{ + // The slider value changed because of the user (not because of range + // changes). In this case disable zoom and apply slider value. + d->emitZoomChanged(); +} + +void ZoomWidget::setZoom(qreal zoom) +{ + int intZoom = qRound(zoom * 100); + d->mZoomLabel->setText(QString("%1%").arg(intZoom)); + + // Don't change slider value if we come here because the slider change, + // avoids choppy sliding scroll. + if (!d->mZoomUpdatedBySlider) { + QSlider* slider = d->mZoomSlider->slider(); + SignalBlocker blocker(slider); + int value = sliderValueForZoom(zoom); + + if (value < slider->minimum()) { + // It is possible that we are called *before* setMinimumZoom() as + // been called. In this case, define the minimum ourself. + d->mZoomSlider->setMinimum(value); + } + d->mZoomSlider->setValue(value); + } +} + +void ZoomWidget::setMinimumZoom(qreal minimumZoom) +{ + d->mZoomSlider->setMinimum(sliderValueForZoom(minimumZoom)); +} + +void ZoomWidget::setMaximumZoom(qreal zoom) +{ + d->mZoomSlider->setMaximum(sliderValueForZoom(zoom)); +} + +bool ZoomWidget::isZoomLocked() const +{ + return d->mLockZoomButton->isVisible() && d->mLockZoomButton->isChecked(); +} + +void ZoomWidget::setZoomLocked(bool locked) +{ + d->mLockZoomButton->setChecked(locked); +} + +void ZoomWidget::setLockZoomButtonVisible(bool visible) +{ + d->mLockZoomButton->setVisible(visible); +} + +void ZoomWidget::updateLockZoomButton() +{ + d->mLockZoomButton->setIcon(KIcon(d->mLockZoomButton->isChecked() ? "object-locked" : "object-unlocked")); +} + +} // namespace diff --git a/gwenview/lib/zoomwidget.h b/gwenview/lib/zoomwidget.h new file mode 100644 index 00000000..b181f5ac --- /dev/null +++ b/gwenview/lib/zoomwidget.h @@ -0,0 +1,73 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef ZOOMWIDGET_H +#define ZOOMWIDGET_H + +#include + +// Qt +#include + +// KDE + +// Local + +class QAction; + +namespace Gwenview +{ + +struct ZoomWidgetPrivate; +class GWENVIEWLIB_EXPORT ZoomWidget : public QFrame +{ + Q_OBJECT +public: + ZoomWidget(QWidget* parent = 0); + ~ZoomWidget(); + + void setActions(QAction* zoomToFitAction, QAction* actualSizeAction, QAction* zoomInAction, QAction* zoomOutAction); + + bool isZoomLocked() const; + void setZoomLocked(bool); + + void setLockZoomButtonVisible(bool); + +public Q_SLOTS: + void setZoom(qreal zoom); + + void setMinimumZoom(qreal zoom); + void setMaximumZoom(qreal zoom); + +Q_SIGNALS: + void zoomChanged(qreal); + +private Q_SLOTS: + void slotZoomSliderActionTriggered(); + void updateLockZoomButton(); + +private: + friend struct ZoomWidgetPrivate; + ZoomWidgetPrivate* const d; +}; + +} // namespace + +#endif /* ZOOMWIDGET_H */ diff --git a/gwenview/part/CMakeLists.txt b/gwenview/part/CMakeLists.txt new file mode 100644 index 00000000..dc9dfe55 --- /dev/null +++ b/gwenview/part/CMakeLists.txt @@ -0,0 +1,18 @@ +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${EXIV2_INCLUDE_DIR} + ) + +set(gvpart_SRCS + gvbrowserextension.cpp + gvpart.cpp + ) + +kde4_add_plugin(gvpart ${gvpart_SRCS}) + +target_link_libraries(gvpart ${KDE4_KPARTS_LIBS} gwenviewlib) + +install(TARGETS gvpart DESTINATION ${PLUGIN_INSTALL_DIR}) +install(FILES gvpart.desktop DESTINATION ${SERVICES_INSTALL_DIR}) +install(FILES gvpart.rc + DESTINATION ${DATA_INSTALL_DIR}/gvpart) diff --git a/gwenview/part/gvbrowserextension.cpp b/gwenview/part/gvbrowserextension.cpp new file mode 100644 index 00000000..dacaf1f6 --- /dev/null +++ b/gwenview/part/gvbrowserextension.cpp @@ -0,0 +1,64 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "gvbrowserextension.moc" + +// Qt + +// KDE +#include +#include + +// Local +#include "../lib/document/documentfactory.h" +#include "../lib/print/printhelper.h" + +namespace Gwenview +{ + +struct GVBrowserExtensionPrivate +{ + KParts::ReadOnlyPart* mPart; +}; + +GVBrowserExtension::GVBrowserExtension(KParts::ReadOnlyPart* part) +: KParts::BrowserExtension(part) +, d(new GVBrowserExtensionPrivate) +{ + d->mPart = part; + emit enableAction("print", true); + QString iconPath = KIconLoader::global()->iconPath("image-x-generic", KIconLoader::SizeSmall); + emit setIconUrl(KUrl::fromPath(iconPath)); +} + +GVBrowserExtension::~GVBrowserExtension() +{ + delete d; +} + +void GVBrowserExtension::print() +{ + Document::Ptr doc = DocumentFactory::instance()->load(d->mPart->url()); + PrintHelper printHelper(d->mPart->widget()); + printHelper.print(doc); +} + +} // namespace diff --git a/gwenview/part/gvbrowserextension.h b/gwenview/part/gvbrowserextension.h new file mode 100644 index 00000000..da94c882 --- /dev/null +++ b/gwenview/part/gvbrowserextension.h @@ -0,0 +1,51 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef GVBROWSEREXTENSION_H +#define GVBROWSEREXTENSION_H + +// Qt + +// KDE +#include + +// Local + +namespace Gwenview +{ + +struct GVBrowserExtensionPrivate; +class GVBrowserExtension : public KParts::BrowserExtension +{ + Q_OBJECT +public: + GVBrowserExtension(KParts::ReadOnlyPart*); + ~GVBrowserExtension(); + +private Q_SLOTS: + void print(); + +private: + GVBrowserExtensionPrivate* const d; +}; + +} // namespace + +#endif /* GVBROWSEREXTENSION_H */ diff --git a/gwenview/part/gvpart.cpp b/gwenview/part/gvpart.cpp new file mode 100644 index 00000000..64f39ace --- /dev/null +++ b/gwenview/part/gvpart.cpp @@ -0,0 +1,189 @@ +/* +Gwenview: an image viewer +Copyright 2007-2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "gvpart.moc" + +// Qt + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "../lib/about.h" +#include "../lib/document/document.h" +#include "../lib/document/documentfactory.h" +#include "../lib/documentview/documentview.h" +#include "../lib/documentview/documentviewcontainer.h" +#include "../lib/documentview/documentviewcontroller.h" +#include "../lib/imageformats/imageformats.h" +#include "../lib/urlutils.h" +#include "../lib/zoomwidget.h" +#include "gvbrowserextension.h" + +//Factory Code +K_PLUGIN_FACTORY(GVPartFactory, registerPlugin();) +K_EXPORT_PLUGIN(GVPartFactory) + +namespace Gwenview +{ + +GVPart::GVPart(QWidget* parentWidget, QObject* parent, const QVariantList& /*args*/) +: KParts::ReadOnlyPart(parent) +{ + KGlobal::locale()->insertCatalog("gwenview"); + DocumentViewContainer* container = new DocumentViewContainer(parentWidget); + setWidget(container); + mDocumentView = container->createView(); + + connect(mDocumentView, SIGNAL(captionUpdateRequested(QString)), + SIGNAL(setWindowCaption(QString))); + connect(mDocumentView, SIGNAL(completed()), + SIGNAL(completed())); + + connect(mDocumentView, SIGNAL(contextMenuRequested()), + SLOT(showContextMenu())); + + // Necessary to have zoom actions + DocumentViewController* documentViewController = new DocumentViewController(actionCollection(), this); + documentViewController->setView(mDocumentView); + + KAction* action = new KAction(actionCollection()); + action->setText(i18nc("@action", "Properties")); + connect(action, SIGNAL(triggered()), SLOT(showProperties())); + actionCollection()->addAction("file_show_properties", action); + + KStandardAction::saveAs(this, SLOT(saveAs()), actionCollection()); + + Gwenview::ImageFormats::registerPlugins(); + new GVBrowserExtension(this); + + setXMLFile("gvpart/gvpart.rc"); +} + +void GVPart::showProperties() +{ + KPropertiesDialog::showDialog(url(), widget()); +} + +bool GVPart::openFile() +{ + return false; +} + +bool GVPart::openUrl(const KUrl& url) +{ + if (!url.isValid()) { + return false; + } + setUrl(url); + Document::Ptr doc = DocumentFactory::instance()->load(url); + if (arguments().reload()) { + doc->reload(); + } + if (!UrlUtils::urlIsFastLocalFile(url)) { + // Keep raw data of remote files to avoid downloading them again in + // saveAs() + doc->setKeepRawData(true); + } + DocumentView::Setup setup; + setup.zoomToFit = true; + mDocumentView->openUrl(url, setup); + return true; +} + +KAboutData* GVPart::createAboutData() +{ + KAboutData* aboutData = Gwenview::createAboutData( + "gvpart", /* appname */ + "gwenview", /* catalogName */ + ki18n("Gwenview KPart") /* programName */ + ); + aboutData->setShortDescription(ki18n("An Image Viewer")); + return aboutData; +} + +inline void addActionToMenu(KMenu* menu, KActionCollection* actionCollection, const char* name) +{ + QAction* action = actionCollection->action(name); + if (action) { + menu->addAction(action); + } +} + +void GVPart::showContextMenu() +{ + KMenu menu(widget()); + addActionToMenu(&menu, actionCollection(), "file_save_as"); + menu.addSeparator(); + addActionToMenu(&menu, actionCollection(), "view_actual_size"); + addActionToMenu(&menu, actionCollection(), "view_zoom_to_fit"); + addActionToMenu(&menu, actionCollection(), "view_zoom_in"); + addActionToMenu(&menu, actionCollection(), "view_zoom_out"); + menu.addSeparator(); + addActionToMenu(&menu, actionCollection(), "file_show_properties"); + menu.exec(QCursor::pos()); +} + +void GVPart::saveAs() +{ + KUrl srcUrl = url(); + KUrl dstUrl = KFileDialog::getSaveUrl(srcUrl.fileName(), QString(), widget()); + if (!dstUrl.isValid()) { + return; + } + + KIO::Job* job; + Document::Ptr doc = DocumentFactory::instance()->load(srcUrl); + QByteArray rawData = doc->rawData(); + if (rawData.length() > 0) { + job = KIO::storedPut(rawData, dstUrl, -1); + } else { + job = KIO::file_copy(srcUrl, dstUrl); + } + connect(job, SIGNAL(result(KJob*)), + this, SLOT(showJobError(KJob*))); +} + +void GVPart::showJobError(KJob* job) +{ + if (job->error() != 0) { + KIO::JobUiDelegate* ui = static_cast(job)->ui(); + if (!ui) { + kError() << "Saving failed. job->ui() is null."; + return; + } + ui->setWindow(widget()); + ui->showErrorMessage(); + } +} + +} // namespace diff --git a/gwenview/part/gvpart.desktop b/gwenview/part/gvpart.desktop new file mode 100644 index 00000000..51c3e104 --- /dev/null +++ b/gwenview/part/gvpart.desktop @@ -0,0 +1,71 @@ +[Desktop Entry] +Type=Service +Name=Gwenview Image Viewer +Name[ar]=جوِينفيو مستعرض الصورة +Name[ast]=Visor d'imáxenes Gwenview +Name[bg]=Програма за снимки Gwenview +Name[bs]=Gvenview prikazivač slika +Name[ca]=Visualitzador d'imatges Gwenview +Name[ca@valencia]=Visualitzador d'imatges Gwenview +Name[cs]=Prohlížeč obrázků Gwenview +Name[da]=Gwenview billedfremviser +Name[de]=Gwenview Bildbetrachter +Name[el]=Προβολέας εικόνων Gwenview +Name[en_GB]=Gwenview Image Viewer +Name[eo]=Gwenview bildorigardilo +Name[es]=Visor de imágenes Gwenview +Name[et]=Gwenview pildinäitaja +Name[eu]=Gwenview irudi ikustailea +Name[fa]=مشاهده‌گر تصویر Gwenview +Name[fi]=Gwenview-kuvankatselin +Name[fr]=Afficheur d'images Gwenview +Name[ga]=Amharcán Íomhánna Gwenview +Name[gl]=Visor de imaxes Gwenview +Name[hi]=ग्वेन-व्यू छवि प्रदर्शक +Name[hne]=ग्वेन-व्यू फोटू प्रदर्सक +Name[hr]=Preglednik slika Gwenview +Name[hu]=Gwenview képnézegető +Name[ia]=Visor de imagine Gwenview +Name[is]=Gwenview myndskoðari +Name[it]=Visore di immagini Gwenview +Name[ja]=Gwenview 画像ビューア +Name[kk]=Gwenview кескінді қарау құралы +Name[km]=កម្មវិធី​មើល​រូបភាព​របស់ Gwenview +Name[ko]=Gwenview 그림 뷰어 +Name[ku]=Gwenview Nîşanderê Wêneyan +Name[lt]=Gwenview paveikslėlių žiūryklė +Name[lv]=Gwenview attēlu skatītājs +Name[mr]=ग्वेनव्यु प्रतिमा प्रदर्शक +Name[nb]=Bildeviseren Gwenview +Name[nds]=Bildkieker Gwenview +Name[ne]=जीवेनभ्यू छवि दर्शक +Name[nl]=GWenview Afbeeldingenviewer +Name[nn]=Gwenview biletvisar +Name[pa]=ਜੀਵੀਨ-ਵਿਊ ਚਿੱਤਰ ਦਰਸ਼ਕ +Name[pl]=Przeglądarka obrazów Gwenview +Name[pt]=Visualizador de Imagens Gwenview +Name[pt_BR]=Visualizador de imagens do Gwenview +Name[ro]=Vizualizator de imagini KView +Name[ru]=Просмотр изображений в Gwenview +Name[si]=Gwenview පිංතූර දසුන +Name[sk]=Prehliadač obrázkov Gwenview +Name[sl]=Pregledovalnik slik Gwenview +Name[sr]=Гвенвју приказивач слика +Name[sr@ijekavian]=Гвенвју приказивач слика +Name[sr@ijekavianlatin]=GwenView prikazivač slika +Name[sr@latin]=GwenView prikazivač slika +Name[sv]=Gwenview bildvisare +Name[th]=เกวนวิว - เครื่องมือแสดงภาพ +Name[tr]=Gwenview Resim Gösterici +Name[ug]=Gwenview سۈرەت كۆرگۈ +Name[uk]=Переглядач зображень Gwenview +Name[vi]=Bộ xem ảnh Gwenview +Name[x-test]=xxGwenview Image Viewerxx +Name[zh_CN]=Gwenview 图像查看器 +Name[zh_TW]=Gwenview 影像檢視程式 +# TODO: what is image/x-krl? Please register this mimetype at freedesktop.org if you need it +MimeType=image/gif;image/jpeg;image/jp2;image/png;image/bmp;image/x-eps;image/x-ico;image/x-portable-bitmap;image/x-portable-pixmap;image/x-xbitmap;image/x-xpixmap;image/x-webp; +X-KDE-ServiceTypes=KParts/ReadOnlyPart +X-KDE-Library=gvpart +InitialPreference=12 +Icon=gwenview diff --git a/gwenview/part/gvpart.h b/gwenview/part/gvpart.h new file mode 100644 index 00000000..647d42ad --- /dev/null +++ b/gwenview/part/gvpart.h @@ -0,0 +1,63 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef GVPART_H +#define GVPART_H + +// Qt +#include + +// KDE +#include + +// Local +#include + +class KAboutData; + +namespace Gwenview +{ + +class DocumentView; + +class GVPart : public KParts::ReadOnlyPart +{ + Q_OBJECT +public: + GVPart(QWidget* parentWidget, QObject* parent, const QVariantList&); + + static KAboutData* createAboutData(); + +protected: + virtual bool openFile(); + virtual bool openUrl(const KUrl&); + +private Q_SLOTS: + void showContextMenu(); + void showProperties(); + void saveAs(); + void showJobError(KJob*); + +private: + DocumentView* mDocumentView; +}; + +} // namespace + +#endif /* GVPART_H */ diff --git a/gwenview/part/gvpart.rc b/gwenview/part/gvpart.rc new file mode 100644 index 00000000..35703a79 --- /dev/null +++ b/gwenview/part/gvpart.rc @@ -0,0 +1,22 @@ + + + + &File + + + + &View + + + + + + +Main Toolbar + + + + + + + diff --git a/gwenview/tests/CMakeLists.txt b/gwenview/tests/CMakeLists.txt new file mode 100644 index 00000000..a8a2913c --- /dev/null +++ b/gwenview/tests/CMakeLists.txt @@ -0,0 +1,5 @@ +add_subdirectory(auto) +add_subdirectory(manual) + +add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --verbose) +add_dependencies(check buildtests) diff --git a/gwenview/tests/auto/CMakeLists.txt b/gwenview/tests/auto/CMakeLists.txt new file mode 100644 index 00000000..7231ec7b --- /dev/null +++ b/gwenview/tests/auto/CMakeLists.txt @@ -0,0 +1,52 @@ +macro(gv_add_unit_test _test) + set(_src ${_test}.cpp ${ARGN}) + qt4_automoc(${_src}) + kde4_add_unit_test(${_test} ${_src}) + target_link_libraries(${_test} + ${BALOO_LIBRARIES} + ${KDE4_KIO_LIBS} + ${KDE4_KDEUI_LIBS} + ${KDE4_KDECORE_LIBS} + ${QT_QTTEST_LIBRARY} + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${KDCRAW_LIBRARIES} + gwenviewlib) +endmacro(gv_add_unit_test) + +include_directories( + ${gwenview_SOURCE_DIR} + ${importer_SOURCE_DIR} + ) + +# For config-gwenview.h +include_directories( + ${gwenview_BINARY_DIR} + ) + +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) + +gv_add_unit_test(imagescalertest testutils.cpp) +gv_add_unit_test(paintutilstest) +gv_add_unit_test(documenttest testutils.cpp) +gv_add_unit_test(transformimageoperationtest) +gv_add_unit_test(jpegcontenttest) +gv_add_unit_test(thumbnailprovidertest testutils.cpp) +if (NOT GWENVIEW_SEMANTICINFO_BACKEND_NONE) + gv_add_unit_test(semanticinfobackendtest) +endif() +gv_add_unit_test(timeutilstest) +gv_add_unit_test(placetreemodeltest testutils.cpp) +gv_add_unit_test(urlutilstest) +gv_add_unit_test(historymodeltest) +gv_add_unit_test(importertest + ${importer_SOURCE_DIR}/importer.cpp + ${importer_SOURCE_DIR}/fileutils.cpp + ${importer_SOURCE_DIR}/filenameformater.cpp + ) +gv_add_unit_test(sorteddirmodeltest testutils.cpp) +gv_add_unit_test(slidecontainerautotest slidecontainerautotest.cpp) +gv_add_unit_test(imagemetainfomodeltest testutils.cpp) +gv_add_unit_test(cmsprofiletest testutils.cpp) +gv_add_unit_test(recursivedirmodeltest testutils.cpp) +gv_add_unit_test(contextmanagertest testutils.cpp) diff --git a/gwenview/tests/auto/README_REMOTE_TESTS b/gwenview/tests/auto/README_REMOTE_TESTS new file mode 100644 index 00000000..93ccd640 --- /dev/null +++ b/gwenview/tests/auto/README_REMOTE_TESTS @@ -0,0 +1,6 @@ +Some of the unit tests in this dir can test code which access images on remote +urls. + +To enable these tests, set the environment variable GV_REMOTE_TESTS_BASE_URL +to something like "sftp://user@host/tmp". The tests will create a dir named +"gwenview-remote-tests" in the supplied url. diff --git a/gwenview/tests/auto/cmsprofiletest.cpp b/gwenview/tests/auto/cmsprofiletest.cpp new file mode 100644 index 00000000..120d0b0f --- /dev/null +++ b/gwenview/tests/auto/cmsprofiletest.cpp @@ -0,0 +1,96 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "cmsprofiletest.moc" + +// Local +#include +#include +#include + +// KDE +#include + +// Qt + +QTEST_KDEMAIN(CmsProfileTest, GUI) + +using namespace Gwenview; + +void CmsProfileTest::testLoadFromImageData() +{ + QFETCH(QString, fileName); + QFETCH(QByteArray, format); + QByteArray data; + { + QString path = pathForTestFile(fileName); + QFile file(path); + QVERIFY(file.open(QIODevice::ReadOnly)); + data = file.readAll(); + } + Cms::Profile::Ptr ptr = Cms::Profile::loadFromImageData(data, format); + QVERIFY(!ptr.isNull()); +} + +#define NEW_ROW(fileName, format) QTest::newRow(fileName) << fileName << QByteArray(format) +void CmsProfileTest::testLoadFromImageData_data() +{ + QTest::addColumn("fileName"); + QTest::addColumn("format"); + NEW_ROW("cms/colourTestFakeBRG.png", "png"); + NEW_ROW("cms/colourTestsRGB.png", "png"); + NEW_ROW("cms/Upper_Left.jpg", "jpeg"); + NEW_ROW("cms/Upper_Right.jpg", "jpeg"); + NEW_ROW("cms/Lower_Left.jpg", "jpeg"); + NEW_ROW("cms/Lower_Right.jpg", "jpeg"); +} +#undef NEW_ROW + +#if 0 + +void CmsProfileTest::testLoadFromExiv2Image() +{ + QFETCH(QString, fileName); + Exiv2::Image::AutoPtr image; + { + QByteArray data; + QString path = pathForTestFile(fileName); + kWarning() << path; + QFile file(path); + QVERIFY(file.open(QIODevice::ReadOnly)); + data = file.readAll(); + + Exiv2ImageLoader loader; + QVERIFY(loader.load(data)); + image = loader.popImage(); + } + Cms::Profile::Ptr ptr = Cms::Profile::loadFromExiv2Image(image.get()); + QVERIFY(!ptr.isNull()); +} + +#define NEW_ROW(fileName) QTest::newRow(fileName) << fileName +void CmsProfileTest::testLoadFromExiv2Image_data() +{ + QTest::addColumn("fileName"); +} +#undef NEW_ROW + +#endif diff --git a/gwenview/tests/auto/cmsprofiletest.h b/gwenview/tests/auto/cmsprofiletest.h new file mode 100644 index 00000000..9062466f --- /dev/null +++ b/gwenview/tests/auto/cmsprofiletest.h @@ -0,0 +1,44 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef CMSPROFILETEST_H +#define CMSPROFILETEST_H + +// Local + +// KDE + +// Qt +#include + +class CmsProfileTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testLoadFromImageData(); + void testLoadFromImageData_data(); +#if 0 // Need some test data + void testLoadFromExiv2Image(); + void testLoadFromExiv2Image_data(); +#endif +}; + +#endif /* CMSPROFILETEST_H */ diff --git a/gwenview/tests/auto/contextmanagertest.cpp b/gwenview/tests/auto/contextmanagertest.cpp new file mode 100644 index 00000000..3856905d --- /dev/null +++ b/gwenview/tests/auto/contextmanagertest.cpp @@ -0,0 +1,117 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2013 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include + +// Local +#include +#include +#include + +// Qt +#include +#include + +// KDE +#include +#include +#include + +using namespace Gwenview; +using namespace TestUtils; + +QTEST_KDEMAIN(ContextManagerTest, GUI) + +void ContextManagerTest::testRemove() +{ + // When the current image is removed Gwenview must go to the next image if + // there is any, otherwise to the previous image. + + SandBoxDir sandBox; + sandBox.fill(QStringList() << "a" << "b" << "c"); + KUrl dirUrl = KUrl::fromPath(sandBox.absolutePath()); + + SortedDirModel dirModel; + { + QEventLoop loop; + connect(dirModel.dirLister(), SIGNAL(completed()), &loop, SLOT(quit())); + dirModel.dirLister()->openUrl(dirUrl); + loop.exec(); + } + + QCOMPARE(dirModel.rowCount(), 3); + + ContextManager manager(&dirModel, 0); + // Select second row + manager.selectionModel()->setCurrentIndex(dirModel.index(1, 0), QItemSelectionModel::Select); + + // Remove "b", `manager` should select "c" + sandBox.remove("b"); + dirModel.dirLister()->updateDirectory(dirUrl); + while (dirModel.rowCount() == 3) { + QTest::qWait(100); + } + + QModelIndex currentIndex = manager.selectionModel()->currentIndex(); + QCOMPARE(currentIndex.row(), 1); + QCOMPARE(currentIndex.data(Qt::DisplayRole).toString(), QString("c")); + + // Remove "c", `manager` should select "a" + sandBox.remove("c"); + dirModel.dirLister()->updateDirectory(dirUrl); + while (dirModel.rowCount() == 2) { + QTest::qWait(100); + } + + currentIndex = manager.selectionModel()->currentIndex(); + QCOMPARE(currentIndex.row(), 0); + QCOMPARE(currentIndex.data(Qt::DisplayRole).toString(), QString("a")); +} + +void ContextManagerTest::testInvalidDirUrl() +{ + class DirLister : public KDirLister + { + public: + DirLister() + : mOpenUrlCalled(false) + { + setAutoErrorHandlingEnabled(false, 0); + } + + bool openUrl(const KUrl& url, OpenUrlFlags flags = NoFlags) + { + mOpenUrlCalled = true; + return KDirLister::openUrl(url, flags); + } + + bool mOpenUrlCalled; + }; + + SortedDirModel dirModel; + DirLister* dirLister = new DirLister; + dirModel.setDirLister(dirLister); + ContextManager manager(&dirModel, 0); + + manager.setCurrentDirUrl(KUrl()); + QVERIFY(!dirLister->mOpenUrlCalled); +} + diff --git a/gwenview/tests/auto/contextmanagertest.h b/gwenview/tests/auto/contextmanagertest.h new file mode 100644 index 00000000..c870458c --- /dev/null +++ b/gwenview/tests/auto/contextmanagertest.h @@ -0,0 +1,38 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2013 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef CONTEXTMANAGERTEST_H +#define CONTEXTMANAGERTEST_H + +// Local +#include + +// Qt +#include + +class ContextManagerTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testRemove(); + void testInvalidDirUrl(); +}; + +#endif /* CONTEXTMANAGERTEST_H */ diff --git a/gwenview/tests/auto/documenttest.cpp b/gwenview/tests/auto/documenttest.cpp new file mode 100644 index 00000000..f401e39f --- /dev/null +++ b/gwenview/tests/auto/documenttest.cpp @@ -0,0 +1,839 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Qt +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include + +// Local +#include "../lib/abstractimageoperation.h" +#include "../lib/document/abstractdocumenteditor.h" +#include "../lib/document/documentjob.h" +#include "../lib/document/documentfactory.h" +#include "../lib/imagemetainfomodel.h" +#include "../lib/imageutils.h" +#include "../lib/transformimageoperation.h" +#include "testutils.h" + +#include + +#include "documenttest.moc" + +QTEST_KDEMAIN(DocumentTest, GUI) + +using namespace Gwenview; + +static void waitUntilMetaInfoLoaded(Document::Ptr doc) +{ + while (doc->loadingState() < Document::MetaInfoLoaded) { + QTest::qWait(100); + } +} + +static bool waitUntilJobIsDone(DocumentJob* job) +{ + JobWatcher watcher(job); + watcher.wait(); + return watcher.error() == KJob::NoError; +} + +void DocumentTest::initTestCase() +{ + qRegisterMetaType("KUrl"); +} + +void DocumentTest::init() +{ + DocumentFactory::instance()->clearCache(); +} + +void DocumentTest::testLoad() +{ + QFETCH(QString, fileName); + QFETCH(QByteArray, expectedFormat); + QFETCH(int, expectedKindInt); + QFETCH(bool, expectedIsAnimated); + QFETCH(QImage, expectedImage); + QFETCH(int, maxHeight); // number of lines to test. -1 to test all lines + + MimeTypeUtils::Kind expectedKind = MimeTypeUtils::Kind(expectedKindInt); + + KUrl url = urlForTestFile(fileName); + + // testing RAW loading. For raw, QImage directly won't work -> load it using KDCRaw + QByteArray mFormatHint = url.fileName().section('.', -1).toAscii().toLower(); + if (KDcrawIface::KDcraw::rawFilesList().contains(QString(mFormatHint))) { + if (!KDcrawIface::KDcraw::loadEmbeddedPreview(expectedImage, url.toLocalFile())) { + QSKIP("Not running this test: failed to get expectedImage. Try running ./fetch_testing_raw.sh\ + in the tests/data directory and then rerun the tests.", SkipSingle); + } + } + + if (expectedKind != MimeTypeUtils::KIND_SVG_IMAGE) { + if (expectedImage.isNull()) { + QSKIP("Not running this test: QImage failed to load the test image", SkipSingle); + } + } + + Document::Ptr doc = DocumentFactory::instance()->load(url); + QSignalSpy spy(doc.data(), SIGNAL(isAnimatedUpdated())); + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + QCOMPARE(doc->loadingState(), Document::Loaded); + + QCOMPARE(doc->kind(), expectedKind); + QCOMPARE(doc->isAnimated(), expectedIsAnimated); + QCOMPARE(spy.count(), doc->isAnimated() ? 1 : 0); + if (doc->kind() == MimeTypeUtils::KIND_RASTER_IMAGE) { + QImage image = doc->image(); + if (maxHeight > -1) { + QRect poiRect(0, 0, image.width(), maxHeight); + image = image.copy(poiRect); + expectedImage = expectedImage.copy(poiRect); + } + QCOMPARE(image, expectedImage); + QCOMPARE(QString(doc->format()), QString(expectedFormat)); + } +} + +static void testLoad_newRow( + const char* fileName, + const QByteArray& format, + MimeTypeUtils::Kind kind = MimeTypeUtils::KIND_RASTER_IMAGE, + bool isAnimated = false, + int maxHeight = -1 + ) +{ + QTest::newRow(fileName) + << fileName + << QByteArray(format) + << int(kind) + << isAnimated + << QImage(pathForTestFile(fileName), format) + << maxHeight; +} + +void DocumentTest::testLoad_data() +{ + QTest::addColumn("fileName"); + QTest::addColumn("expectedFormat"); + QTest::addColumn("expectedKindInt"); + QTest::addColumn("expectedIsAnimated"); + QTest::addColumn("expectedImage"); + QTest::addColumn("maxHeight"); + + testLoad_newRow("test.png", "png"); + testLoad_newRow("160216_no_size_before_decoding.eps", "eps"); + testLoad_newRow("160382_corrupted.jpeg", "jpeg", MimeTypeUtils::KIND_RASTER_IMAGE, false, 55); + testLoad_newRow("1x10k.png", "png"); + testLoad_newRow("1x10k.jpg", "jpeg"); + testLoad_newRow("test.xcf", "xcf"); + testLoad_newRow("188191_does_not_load.tga", "tga"); + testLoad_newRow("289819_does_not_load.png", "png"); + testLoad_newRow("png-with-jpeg-extension.jpg", "png"); + testLoad_newRow("jpg-with-gif-extension.gif", "jpeg"); + + // RAW preview + testLoad_newRow("CANON-EOS350D-02.CR2", "cr2", MimeTypeUtils::KIND_RASTER_IMAGE, false); + testLoad_newRow("dsc_0093.nef", "nef", MimeTypeUtils::KIND_RASTER_IMAGE, false); + + // SVG + testLoad_newRow("test.svg", "", MimeTypeUtils::KIND_SVG_IMAGE); + // FIXME: Test svgz + + // Animated + testLoad_newRow("4frames.gif", "gif", MimeTypeUtils::KIND_RASTER_IMAGE, true); + testLoad_newRow("1frame.gif", "gif", MimeTypeUtils::KIND_RASTER_IMAGE, false); + testLoad_newRow("185523_1frame_with_graphic_control_extension.gif", + "gif", MimeTypeUtils::KIND_RASTER_IMAGE, false); +} + +void DocumentTest::testLoadTwoPasses() +{ + KUrl url = urlForTestFile("test.png"); + QImage image; + bool ok = image.load(url.toLocalFile()); + QVERIFY2(ok, "Could not load 'test.png'"); + Document::Ptr doc = DocumentFactory::instance()->load(url); + waitUntilMetaInfoLoaded(doc); + QVERIFY2(doc->image().isNull(), "Image shouldn't have been loaded at this time"); + QCOMPARE(doc->format().data(), "png"); + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + QCOMPARE(image, doc->image()); +} + +void DocumentTest::testLoadEmpty() +{ + KUrl url = urlForTestFile("empty.png"); + Document::Ptr doc = DocumentFactory::instance()->load(url); + while (doc->loadingState() <= Document::KindDetermined) { + QTest::qWait(100); + } + QCOMPARE(doc->loadingState(), Document::LoadingFailed); +} + +#define NEW_ROW(fileName) QTest::newRow(fileName) << fileName +void DocumentTest::testLoadDownSampled_data() +{ + QTest::addColumn("fileName"); + + NEW_ROW("orient6.jpg"); + NEW_ROW("1x10k.jpg"); +} +#undef NEW_ROW + +void DocumentTest::testLoadDownSampled() +{ + // Note: for now we only support down sampling on jpeg, do not use test.png + // here + QFETCH(QString, fileName); + KUrl url = urlForTestFile(fileName); + QImage image; + bool ok = image.load(url.toLocalFile()); + QVERIFY2(ok, "Could not load test image"); + Document::Ptr doc = DocumentFactory::instance()->load(url); + + QSignalSpy downSampledImageReadySpy(doc.data(), SIGNAL(downSampledImageReady())); + QSignalSpy loadingFailedSpy(doc.data(), SIGNAL(loadingFailed(KUrl))); + QSignalSpy loadedSpy(doc.data(), SIGNAL(loaded(KUrl))); + bool ready = doc->prepareDownSampledImageForZoom(0.2); + QVERIFY2(!ready, "There should not be a down sampled image at this point"); + + while (downSampledImageReadySpy.count() == 0 && loadingFailedSpy.count() == 0 && loadedSpy.count() == 0) { + QTest::qWait(100); + } + QImage downSampledImage = doc->downSampledImageForZoom(0.2); + QVERIFY2(!downSampledImage.isNull(), "Down sampled image should not be null"); + + QSize expectedSize = doc->size() / 2; + if (expectedSize.isEmpty()) { + expectedSize = image.size(); + } + QCOMPARE(downSampledImage.size(), expectedSize); +} + +/** + * Down sampling is not supported on png. We should get a complete image + * instead. + */ +void DocumentTest::testLoadDownSampledPng() +{ + KUrl url = urlForTestFile("test.png"); + QImage image; + bool ok = image.load(url.toLocalFile()); + QVERIFY2(ok, "Could not load test image"); + Document::Ptr doc = DocumentFactory::instance()->load(url); + + LoadingStateSpy stateSpy(doc); + connect(doc.data(), SIGNAL(loaded(KUrl)), &stateSpy, SLOT(readState())); + + bool ready = doc->prepareDownSampledImageForZoom(0.2); + QVERIFY2(!ready, "There should not be a down sampled image at this point"); + + doc->waitUntilLoaded(); + + QCOMPARE(stateSpy.mCallCount, 1); + QCOMPARE(stateSpy.mState, Document::Loaded); +} + +void DocumentTest::testLoadRemote() +{ + KUrl url = setUpRemoteTestDir("test.png"); + if (!url.isValid()) { + return; + } + url.addPath("test.png"); + + QVERIFY2(KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0), "test url not found"); + + Document::Ptr doc = DocumentFactory::instance()->load(url); + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + QImage image = doc->image(); + QCOMPARE(image.width(), 150); + QCOMPARE(image.height(), 100); +} + +void DocumentTest::testLoadAnimated() +{ + KUrl srcUrl = urlForTestFile("40frames.gif"); + Document::Ptr doc = DocumentFactory::instance()->load(srcUrl); + QSignalSpy spy(doc.data(), SIGNAL(imageRectUpdated(QRect))); + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + QVERIFY(doc->isAnimated()); + + // Test we receive only one imageRectUpdated() until animation is started + // (the imageRectUpdated() is triggered by the loading of the first image) + QTest::qWait(1000); + QCOMPARE(spy.count(), 1); + + // Test we now receive some imageRectUpdated() + doc->startAnimation(); + QTest::qWait(1000); + int count = spy.count(); + doc->stopAnimation(); + QVERIFY2(count > 0, "No imageRectUpdated() signal received"); + + // Test we do not receive imageRectUpdated() anymore + QTest::qWait(1000); + QCOMPARE(count, spy.count()); + + // Start again, we should receive imageRectUpdated() again + doc->startAnimation(); + QTest::qWait(1000); + QVERIFY2(spy.count() > count, "No imageRectUpdated() signal received after restarting"); +} + +void DocumentTest::testPrepareDownSampledAfterFailure() +{ + KUrl url = urlForTestFile("empty.png"); + Document::Ptr doc = DocumentFactory::instance()->load(url); + + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + QCOMPARE(doc->loadingState(), Document::LoadingFailed); + + bool ready = doc->prepareDownSampledImageForZoom(0.25); + QVERIFY2(!ready, "Down sampled image should not be ready"); +} + +void DocumentTest::testSaveRemote() +{ + KUrl dstUrl = setUpRemoteTestDir(); + if (!dstUrl.isValid()) { + return; + } + + KUrl srcUrl = urlForTestFile("test.png"); + Document::Ptr doc = DocumentFactory::instance()->load(srcUrl); + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + + dstUrl.addPath("testSaveRemote.png"); + QVERIFY(waitUntilJobIsDone(doc->save(dstUrl, "png"))); +} + +/** + * Check that deleting a document while it is loading does not crash + */ +void DocumentTest::testDeleteWhileLoading() +{ + { + KUrl url = urlForTestFile("test.png"); + QImage image; + bool ok = image.load(url.toLocalFile()); + QVERIFY2(ok, "Could not load 'test.png'"); + Document::Ptr doc = DocumentFactory::instance()->load(url); + } + DocumentFactory::instance()->clearCache(); + // Wait two seconds. If the test fails we will get a segfault while waiting + QTest::qWait(2000); +} + +void DocumentTest::testLoadRotated() +{ + KUrl url = urlForTestFile("orient6.jpg"); + QImage image; + bool ok = image.load(url.toLocalFile()); + QVERIFY2(ok, "Could not load 'orient6.jpg'"); + QMatrix matrix = ImageUtils::transformMatrix(ROT_90); + image = image.transformed(matrix); + + Document::Ptr doc = DocumentFactory::instance()->load(url); + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + QCOMPARE(image, doc->image()); + + // RAW preview on rotated image + url = urlForTestFile("dsd_1838.nef"); + if (!KDcrawIface::KDcraw::loadEmbeddedPreview(image, url.toLocalFile())) { + QSKIP("Not running this test: failed to get image. Try running ./fetch_testing_raw.sh\ + in the tests/data directory and then rerun the tests.", SkipSingle); + } + matrix = ImageUtils::transformMatrix(ROT_270); + image = image.transformed(matrix); + + doc = DocumentFactory::instance()->load(url); + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + QCOMPARE(image, doc->image()); +} + +/** + * Checks that asking the DocumentFactory the same document twice in a row does + * not load it twice + */ +void DocumentTest::testMultipleLoads() +{ + KUrl url = urlForTestFile("orient6.jpg"); + Document::Ptr doc1 = DocumentFactory::instance()->load(url); + Document::Ptr doc2 = DocumentFactory::instance()->load(url); + + QCOMPARE(doc1.data(), doc2.data()); +} + +void DocumentTest::testSaveAs() +{ + KUrl url = urlForTestFile("orient6.jpg"); + DocumentFactory* factory = DocumentFactory::instance(); + Document::Ptr doc = factory->load(url); + QSignalSpy savedSpy(doc.data(), SIGNAL(saved(KUrl,KUrl))); + QSignalSpy modifiedDocumentListChangedSpy(factory, SIGNAL(modifiedDocumentListChanged())); + QSignalSpy documentChangedSpy(factory, SIGNAL(documentChanged(KUrl))); + doc->startLoadingFullImage(); + + KUrl destUrl = urlForTestOutputFile("result.png"); + QVERIFY(waitUntilJobIsDone(doc->save(destUrl, "png"))); + QCOMPARE(doc->format().data(), "png"); + QCOMPARE(doc->url(), destUrl); + QCOMPARE(doc->metaInfo()->getValueForKey("General.Name"), destUrl.fileName()); + + QVERIFY2(doc->loadingState() == Document::Loaded, + "Document is supposed to finish loading before saving" + ); + + QTest::qWait(100); // saved() is emitted asynchronously + QCOMPARE(savedSpy.count(), 1); + QVariantList args = savedSpy.takeFirst(); + QCOMPARE(args.at(0).value(), url); + QCOMPARE(args.at(1).value(), destUrl); + + QImage image("result.png", "png"); + QCOMPARE(doc->image(), image); + + QVERIFY(!DocumentFactory::instance()->hasUrl(url)); + QVERIFY(DocumentFactory::instance()->hasUrl(destUrl)); + + QCOMPARE(modifiedDocumentListChangedSpy.count(), 0); // No changes were made + + QCOMPARE(documentChangedSpy.count(), 1); + args = documentChangedSpy.takeFirst(); + QCOMPARE(args.at(0).value(), destUrl); +} + +void DocumentTest::testLosslessSave() +{ + KUrl url1 = urlForTestFile("orient6.jpg"); + Document::Ptr doc = DocumentFactory::instance()->load(url1); + doc->startLoadingFullImage(); + + KUrl url2 = urlForTestOutputFile("orient1.jpg"); + QVERIFY(waitUntilJobIsDone(doc->save(url2, "jpeg"))); + + QImage image1; + QVERIFY(image1.load(url1.toLocalFile())); + + QImage image2; + QVERIFY(image2.load(url2.toLocalFile())); + + QCOMPARE(image1, image2); +} + +void DocumentTest::testLosslessRotate() +{ + // Generate test image + QImage image1(200, 96, QImage::Format_RGB32); + { + QPainter painter(&image1); + QConicalGradient gradient(QPointF(100, 48), 100); + gradient.setColorAt(0, Qt::white); + gradient.setColorAt(1, Qt::blue); + painter.fillRect(image1.rect(), gradient); + } + + KUrl url1 = urlForTestOutputFile("lossless1.jpg"); + QVERIFY(image1.save(url1.toLocalFile(), "jpeg")); + + // Load it as a Gwenview document + Document::Ptr doc = DocumentFactory::instance()->load(url1); + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + + // Rotate one time + QVERIFY(doc->editor()); + doc->editor()->applyTransformation(ROT_90); + + // Save it + KUrl url2 = urlForTestOutputFile("lossless2.jpg"); + waitUntilJobIsDone(doc->save(url2, "jpeg")); + + // Load the saved image + doc = DocumentFactory::instance()->load(url2); + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + + // Rotate the other way + QVERIFY(doc->editor()); + doc->editor()->applyTransformation(ROT_270); + waitUntilJobIsDone(doc->save(url2, "jpeg")); + + // Compare the saved images + QVERIFY(image1.load(url1.toLocalFile())); + QImage image2; + QVERIFY(image2.load(url2.toLocalFile())); + + QCOMPARE(image1, image2); +} + +void DocumentTest::testModifyAndSaveAs() +{ + QVariantList args; + class TestOperation : public AbstractImageOperation + { + public: + void redo() + { + QImage image(10, 10, QImage::Format_ARGB32); + image.fill(QColor(Qt::white).rgb()); + document()->editor()->setImage(image); + finish(true); + } + }; + KUrl url = urlForTestFile("orient6.jpg"); + DocumentFactory* factory = DocumentFactory::instance(); + Document::Ptr doc = factory->load(url); + + QSignalSpy savedSpy(doc.data(), SIGNAL(saved(KUrl,KUrl))); + QSignalSpy modifiedDocumentListChangedSpy(factory, SIGNAL(modifiedDocumentListChanged())); + QSignalSpy documentChangedSpy(factory, SIGNAL(documentChanged(KUrl))); + + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + QVERIFY(!doc->isModified()); + QCOMPARE(modifiedDocumentListChangedSpy.count(), 0); + + // Modify image + QVERIFY(doc->editor()); + TestOperation* op = new TestOperation; + op->applyToDocument(doc); + QVERIFY(doc->isModified()); + QCOMPARE(modifiedDocumentListChangedSpy.count(), 1); + modifiedDocumentListChangedSpy.clear(); + QList lst = factory->modifiedDocumentList(); + QCOMPARE(lst.count(), 1); + QCOMPARE(lst.first(), url); + QCOMPARE(documentChangedSpy.count(), 1); + args = documentChangedSpy.takeFirst(); + QCOMPARE(args.at(0).value(), url); + + // Save it under a new name + KUrl destUrl = urlForTestOutputFile("modify.png"); + QVERIFY(waitUntilJobIsDone(doc->save(destUrl, "png"))); + + // Wait a bit because save() will clear the undo stack when back to the + // event loop + QTest::qWait(100); + QVERIFY(!doc->isModified()); + + QVERIFY(!factory->hasUrl(url)); + QVERIFY(factory->hasUrl(destUrl)); + QCOMPARE(modifiedDocumentListChangedSpy.count(), 1); + QVERIFY(DocumentFactory::instance()->modifiedDocumentList().isEmpty()); + + QCOMPARE(documentChangedSpy.count(), 2); + KUrl::List modifiedUrls = KUrl::List() << url << destUrl; + QVERIFY(modifiedUrls.contains(url)); + QVERIFY(modifiedUrls.contains(destUrl)); +} + +void DocumentTest::testMetaInfoJpeg() +{ + KUrl url = urlForTestFile("orient6.jpg"); + Document::Ptr doc = DocumentFactory::instance()->load(url); + + // We cleared the cache, so the document should not be loaded + Q_ASSERT(doc->loadingState() <= Document::KindDetermined); + + // Wait until we receive the metaInfoUpdated() signal + QSignalSpy metaInfoUpdatedSpy(doc.data(), SIGNAL(metaInfoUpdated())); + while (metaInfoUpdatedSpy.count() == 0) { + QTest::qWait(100); + } + + // Extract an exif key + QString value = doc->metaInfo()->getValueForKey("Exif.Image.Make"); + QCOMPARE(value, QString::fromUtf8("Canon")); +} + +void DocumentTest::testMetaInfoBmp() +{ + KUrl url = urlForTestOutputFile("metadata.bmp"); + const int width = 200; + const int height = 100; + QImage image(width, height, QImage::Format_ARGB32); + image.fill(Qt::black); + image.save(url.toLocalFile(), "BMP"); + + Document::Ptr doc = DocumentFactory::instance()->load(url); + QSignalSpy metaInfoUpdatedSpy(doc.data(), SIGNAL(metaInfoUpdated())); + waitUntilMetaInfoLoaded(doc); + + Q_ASSERT(metaInfoUpdatedSpy.count() >= 1); + + QString value = doc->metaInfo()->getValueForKey("General.ImageSize"); + QString expectedValue = QString("%1x%2").arg(width).arg(height); + QCOMPARE(value, expectedValue); +} + +void DocumentTest::testForgetModifiedDocument() +{ + QSignalSpy spy(DocumentFactory::instance(), SIGNAL(modifiedDocumentListChanged())); + DocumentFactory::instance()->forget(KUrl("file://does/not/exist.png")); + QCOMPARE(spy.count(), 0); + + // Generate test image + QImage image1(200, 96, QImage::Format_RGB32); + { + QPainter painter(&image1); + QConicalGradient gradient(QPointF(100, 48), 100); + gradient.setColorAt(0, Qt::white); + gradient.setColorAt(1, Qt::blue); + painter.fillRect(image1.rect(), gradient); + } + + KUrl url = urlForTestOutputFile("testForgetModifiedDocument.png"); + QVERIFY(image1.save(url.toLocalFile(), "png")); + + // Load it as a Gwenview document + Document::Ptr doc = DocumentFactory::instance()->load(url); + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + + // Modify it + TransformImageOperation* op = new TransformImageOperation(ROT_90); + op->applyToDocument(doc); + QTest::qWait(100); + + QCOMPARE(spy.count(), 1); + + QList lst = DocumentFactory::instance()->modifiedDocumentList(); + QCOMPARE(lst.length(), 1); + QCOMPARE(lst.first(), url); + + // Forget it + DocumentFactory::instance()->forget(url); + + QCOMPARE(spy.count(), 2); + lst = DocumentFactory::instance()->modifiedDocumentList(); + QVERIFY(lst.isEmpty()); +} + +void DocumentTest::testModifiedAndSavedSignals() +{ + TransformImageOperation* op; + + KUrl url = urlForTestFile("orient6.jpg"); + Document::Ptr doc = DocumentFactory::instance()->load(url); + QSignalSpy modifiedSpy(doc.data(), SIGNAL(modified(KUrl))); + QSignalSpy savedSpy(doc.data(), SIGNAL(saved(KUrl,KUrl))); + doc->startLoadingFullImage(); + doc->waitUntilLoaded(); + + QCOMPARE(modifiedSpy.count(), 0); + QCOMPARE(savedSpy.count(), 0); + + op = new TransformImageOperation(ROT_90); + op->applyToDocument(doc); + QTest::qWait(100); + QCOMPARE(modifiedSpy.count(), 1); + + op = new TransformImageOperation(ROT_90); + op->applyToDocument(doc); + QTest::qWait(100); + QCOMPARE(modifiedSpy.count(), 2); + + doc->undoStack()->undo(); + QCOMPARE(modifiedSpy.count(), 3); + + doc->undoStack()->undo(); + QCOMPARE(savedSpy.count(), 1); +} + +class TestJob : public DocumentJob +{ +public: + TestJob(QString* str, char ch) + : mStr(str) + , mCh(ch) + {} + +protected: + virtual void doStart() + { + *mStr += mCh; + emitResult(); + } + +private: + QString* mStr; + char mCh; +}; + +void DocumentTest::testJobQueue() +{ + KUrl url = urlForTestFile("orient6.jpg"); + Document::Ptr doc = DocumentFactory::instance()->load(url); + QSignalSpy spy(doc.data(), SIGNAL(busyChanged(KUrl,bool))); + + QString str; + doc->enqueueJob(new TestJob(&str, 'a')); + doc->enqueueJob(new TestJob(&str, 'b')); + doc->enqueueJob(new TestJob(&str, 'c')); + QVERIFY(doc->isBusy()); + QEventLoop loop; + connect(doc.data(), SIGNAL(allTasksDone()), + &loop, SLOT(quit())); + loop.exec(); + QVERIFY(!doc->isBusy()); + QCOMPARE(spy.count(), 2); + QVariantList row = spy.takeFirst(); + QCOMPARE(row.at(0).value(), url); + QVERIFY(row.at(1).toBool()); + row = spy.takeFirst(); + QCOMPARE(row.at(0).value(), url); + QVERIFY(!row.at(1).toBool()); + QCOMPARE(str, QString("abc")); +} + +class TestCheckDocumentEditorJob : public DocumentJob +{ +public: + TestCheckDocumentEditorJob(int* hasEditor) + : mHasEditor(hasEditor) + { + *mHasEditor = -1; + } + +protected: + virtual void doStart() + { + document()->waitUntilLoaded(); + *mHasEditor = checkDocumentEditor() ? 1 : 0; + emitResult(); + } + +private: + int* mHasEditor; +}; + +class TestUiDelegate : public KJobUiDelegate +{ +public: + TestUiDelegate(bool* showErrorMessageCalled) + : mShowErrorMessageCalled(showErrorMessageCalled) + { + setAutoErrorHandlingEnabled(true); + *mShowErrorMessageCalled = false; + } + + virtual void showErrorMessage() + { + kDebug(); + *mShowErrorMessageCalled = true; + } + +private: + bool* mShowErrorMessageCalled; +}; + +/** + * Test that an error is reported when a DocumentJob fails because there is no + * document editor available + */ +void DocumentTest::testCheckDocumentEditor() +{ + int hasEditor; + bool showErrorMessageCalled; + QEventLoop loop; + Document::Ptr doc; + TestCheckDocumentEditorJob* job; + + doc = DocumentFactory::instance()->load(urlForTestFile("orient6.jpg")); + + job = new TestCheckDocumentEditorJob(&hasEditor); + job->setUiDelegate(new TestUiDelegate(&showErrorMessageCalled)); + doc->enqueueJob(job); + connect(doc.data(), SIGNAL(allTasksDone()), &loop, SLOT(quit())); + loop.exec(); + QVERIFY(!showErrorMessageCalled); + QCOMPARE(hasEditor, 1); + + doc = DocumentFactory::instance()->load(urlForTestFile("test.svg")); + + job = new TestCheckDocumentEditorJob(&hasEditor); + job->setUiDelegate(new TestUiDelegate(&showErrorMessageCalled)); + doc->enqueueJob(job); + connect(doc.data(), SIGNAL(allTasksDone()), &loop, SLOT(quit())); + loop.exec(); + QVERIFY(showErrorMessageCalled); + QCOMPARE(hasEditor, 0); +} + +/** + * An operation should only pushed to the document undo stack if it succeed + */ +void DocumentTest::testUndoStackPush() +{ + class SuccessOperation : public AbstractImageOperation + { + protected: + virtual void redo() + { + QMetaObject::invokeMethod(this, "finish", Qt::QueuedConnection, Q_ARG(bool, true)); + } + }; + + class FailureOperation : public AbstractImageOperation + { + protected: + virtual void redo() + { + QMetaObject::invokeMethod(this, "finish", Qt::QueuedConnection, Q_ARG(bool, false)); + } + }; + + AbstractImageOperation* op; + Document::Ptr doc = DocumentFactory::instance()->load(urlForTestFile("orient6.jpg")); + + // A successful operation should be added to the undo stack + op = new SuccessOperation; + op->applyToDocument(doc); + QTest::qWait(100); + QVERIFY(!doc->undoStack()->isClean()); + + // Reset + doc->undoStack()->undo(); + QVERIFY(doc->undoStack()->isClean()); + + // A failed operation should not be added to the undo stack + op = new FailureOperation; + op->applyToDocument(doc); + QTest::qWait(100); + QVERIFY(doc->undoStack()->isClean()); +} diff --git a/gwenview/tests/auto/documenttest.h b/gwenview/tests/auto/documenttest.h new file mode 100644 index 00000000..c08dcd25 --- /dev/null +++ b/gwenview/tests/auto/documenttest.h @@ -0,0 +1,135 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef DOCUMENTTEST_H +#define DOCUMENTTEST_H + +// Qt +#include + +// KDE +#include + +// Local +#include "../lib/document/document.h" + +class LoadingStateSpy : public QObject +{ + Q_OBJECT +public: + LoadingStateSpy(const Gwenview::Document::Ptr& doc) + : mDocument(doc) + , mCallCount(0) { + } + +public Q_SLOTS: + void readState() + { + mCallCount++; + mState = mDocument->loadingState(); + } + +public: + Gwenview::Document::Ptr mDocument; + int mCallCount; + Gwenview::Document::LoadingState mState; +}; + +class JobWatcher : public QObject +{ + Q_OBJECT +public: + JobWatcher(KJob* job) + : mJob(job) + , mDone(false) + , mError(0) { + connect(job, SIGNAL(result(KJob*)), + SLOT(slotResult(KJob*))); + connect(job, SIGNAL(destroyed(QObject*)), + SLOT(slotDestroyed())); + } + + void wait() + { + while (!mDone) { + QApplication::processEvents(); + } + } + + int error() const + { + return mError; + } + +private Q_SLOTS: + void slotResult(KJob* job) + { + mError = job->error(); + mDone = true; + } + + void slotDestroyed() + { + kWarning() << "Destroyed"; + mError = -1; + mDone = true; + } + +private: + KJob* mJob; + bool mDone; + int mError; +}; + +class DocumentTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testLoad(); + void testLoad_data(); + void testLoadTwoPasses(); + void testLoadEmpty(); + void testLoadDownSampled(); + void testLoadDownSampled_data(); + void testLoadDownSampledPng(); + void testLoadRemote(); + void testLoadAnimated(); + void testPrepareDownSampledAfterFailure(); + void testDeleteWhileLoading(); + void testLoadRotated(); + void testMultipleLoads(); + void testSaveAs(); + void testSaveRemote(); + void testLosslessSave(); + void testLosslessRotate(); + void testModifyAndSaveAs(); + void testMetaInfoJpeg(); + void testMetaInfoBmp(); + void testForgetModifiedDocument(); + void testModifiedAndSavedSignals(); + void testJobQueue(); + void testCheckDocumentEditor(); + void testUndoStackPush(); + + void initTestCase(); + void init(); +}; + +#endif // DOCUMENTTEST_H diff --git a/gwenview/tests/auto/historymodeltest.cpp b/gwenview/tests/auto/historymodeltest.cpp new file mode 100644 index 00000000..c437949b --- /dev/null +++ b/gwenview/tests/auto/historymodeltest.cpp @@ -0,0 +1,119 @@ +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "historymodeltest.moc" + +// Qt +#include + +// KDE +#include +#include +#include +#include + +// Local +#include "../lib/historymodel.h" + +QTEST_KDEMAIN(HistoryModelTest, GUI) + +using namespace Gwenview; + +void testModel(const HistoryModel& model, const KUrl& u1, const KUrl& u2) +{ + QModelIndex index; + KUrl url; + QCOMPARE(model.rowCount(), 2); + + index = model.index(0, 0); + QVERIFY(index.isValid()); + url = model.data(index, KFilePlacesModel::UrlRole).value(); + QCOMPARE(url, u1); + + index = model.index(1, 0); + QVERIFY(index.isValid()); + url = model.data(index, KFilePlacesModel::UrlRole).value(); + QCOMPARE(url, u2); + +} + +void HistoryModelTest::testAddUrl() +{ + KUrl u1 = KUrl::fromPath("/home"); + QDateTime d1 = QDateTime::fromString("2008-02-03T12:34:56", Qt::ISODate); + KUrl u2 = KUrl::fromPath("/root"); + QDateTime d2 = QDateTime::fromString("2009-01-29T23:01:47", Qt::ISODate); + KTempDir dir; + { + HistoryModel model(0, dir.name()); + model.addUrl(u1, d1); + model.addUrl(u2, d2); + testModel(model, u2, u1); + } + + HistoryModel model(0, dir.name()); + testModel(model, u2, u1); + + // Make u1 the most recent + QDateTime d3 = QDateTime::fromString("2009-03-24T22:42:15", Qt::ISODate); + model.addUrl(u1, d3); + testModel(model, u1, u2); +} + +void HistoryModelTest::testGarbageCollect() +{ + KUrl u1 = KUrl::fromPath("/home"); + QDateTime d1 = QDateTime::fromString("2008-02-03T12:34:56", Qt::ISODate); + KUrl u2 = KUrl::fromPath("/root"); + QDateTime d2 = QDateTime::fromString("2009-01-29T23:01:47", Qt::ISODate); + KUrl u3 = KUrl::fromPath("/usr"); + QDateTime d3 = QDateTime::fromString("2009-03-24T22:42:15", Qt::ISODate); + + KTempDir dir; + { + HistoryModel model(0, dir.name(), 2); + model.addUrl(u1, d1); + model.addUrl(u2, d2); + testModel(model, u2, u1); + model.addUrl(u3, d3); + } + + // Create a model with a larger history so that if garbage collecting fails + // to remove the collected url, the size of the model won't pass + // testModel() + HistoryModel model(0, dir.name(), 10); + testModel(model, u3, u2); +} + +void HistoryModelTest::testRemoveRows() +{ + KUrl u1 = KUrl::fromPath("/home"); + QDateTime d1 = QDateTime::fromString("2008-02-03T12:34:56", Qt::ISODate); + KUrl u2 = KUrl::fromPath("/root"); + QDateTime d2 = QDateTime::fromString("2009-01-29T23:01:47", Qt::ISODate); + + KTempDir dir; + HistoryModel model(0, dir.name(), 2); + model.addUrl(u1, d1); + model.addUrl(u2, d2); + model.removeRows(0, 1); + QCOMPARE(model.rowCount(), 1); + QDir qDir(dir.name()); + QCOMPARE(qDir.entryList(QDir::Files | QDir::NoDotAndDotDot).count(), 1); +} diff --git a/gwenview/tests/auto/historymodeltest.h b/gwenview/tests/auto/historymodeltest.h new file mode 100644 index 00000000..15da2776 --- /dev/null +++ b/gwenview/tests/auto/historymodeltest.h @@ -0,0 +1,36 @@ +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef HISTORYMODELTEST_H +#define HISTORYMODELTEST_H + +// Qt +#include + +class HistoryModelTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testAddUrl(); + void testGarbageCollect(); + void testRemoveRows(); +}; + +#endif /* HISTORYMODELTEST_H */ diff --git a/gwenview/tests/auto/imagemetainfomodeltest.cpp b/gwenview/tests/auto/imagemetainfomodeltest.cpp new file mode 100644 index 00000000..e1a6b7f1 --- /dev/null +++ b/gwenview/tests/auto/imagemetainfomodeltest.cpp @@ -0,0 +1,58 @@ +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Qt + +// KDE +#include +#include + +// Local +#include "../lib/exiv2imageloader.h" +#include "../lib/imagemetainfomodel.h" +#include "testutils.h" + +#include + +#include "imagemetainfomodeltest.moc" + +QTEST_KDEMAIN(ImageMetaInfoModelTest, GUI) + +using namespace Gwenview; + +void ImageMetaInfoModelTest::testCatchExiv2Errors() +{ + QByteArray data; + { + QString path = pathForTestFile("302350_exiv_0.23_exception.jpg"); + QFile file(path); + QVERIFY(file.open(QIODevice::ReadOnly)); + data = file.readAll(); + } + + Exiv2::Image::AutoPtr image; + { + Exiv2ImageLoader loader; + QVERIFY(loader.load(data)); + image = loader.popImage(); + } + + ImageMetaInfoModel model; + model.setExiv2Image(image.get()); +} diff --git a/gwenview/tests/auto/imagemetainfomodeltest.h b/gwenview/tests/auto/imagemetainfomodeltest.h new file mode 100644 index 00000000..bf386646 --- /dev/null +++ b/gwenview/tests/auto/imagemetainfomodeltest.h @@ -0,0 +1,38 @@ +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef IMAGEMETAINFOMODELTEST_H +#define IMAGEMETAINFOMODELTEST_H + +// Qt +#include + +// KDE + +// Local + +class ImageMetaInfoModelTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testCatchExiv2Errors(); +}; + +#endif // IMAGEMETAINFOMODELTEST_H diff --git a/gwenview/tests/auto/imagescalertest.cpp b/gwenview/tests/auto/imagescalertest.cpp new file mode 100644 index 00000000..940242fa --- /dev/null +++ b/gwenview/tests/auto/imagescalertest.cpp @@ -0,0 +1,212 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include + +#include "imagescalertest.moc" + +#include "../lib/imagescaler.h" +#include "../lib/document/documentfactory.h" + +#include "testutils.h" + +QTEST_KDEMAIN(ImageScalerTest, GUI) + +using namespace Gwenview; + +/** + * Scale whole image in one pass + */ +void ImageScalerTest::testScaleFullImage() +{ + const qreal zoom = 2; + KUrl url = urlForTestFile("test.png"); + Document::Ptr doc = DocumentFactory::instance()->load(url); + + // Wait for meta info because we need the document size + while (doc->loadingState() < Document::MetaInfoLoaded) { + QTest::qWait(500); + } + + ImageScaler scaler; + ImageScalerClient client(&scaler); + scaler.setDocument(doc); + scaler.setZoom(zoom); + + scaler.setDestinationRegion(QRect(QPoint(0, 0), doc->size() * zoom)); + + bool ok = QTest::kWaitForSignal(&scaler, SIGNAL(scaledRect(int,int,QImage)), 30); + QVERIFY2(ok, "ImageScaler did not emit scaledRect() signal in time"); + + // Document should be fully loaded by the time image scaler is done + QCOMPARE(doc->loadingState(), Document::Loaded); + + QImage scaledImage = client.createFullImage(); + + QImage expectedImage = doc->image().scaled(doc->size() * zoom); + QVERIFY(TestUtils::imageCompare(scaledImage, expectedImage)); +} + +#if 0 +/** + * Scale parts of an image + * In this test, the result image should be missing its bottom-right corner + */ +void ImageScalerTest::testScalePartialImage() +{ + QImage image(10, 10, QImage::Format_ARGB32); + const int zoom = 2; + { + QPainter painter(&image); + painter.fillRect(image.rect(), Qt::white); + painter.drawText(0, image.height(), "X"); + } + + Gwenview::ImageScaler scaler; + ImageScalerClient client(&scaler); + scaler.setImage(&image); + scaler.setZoom(zoom); + QRegion region; + region |= QRect( + 0, 0, + image.width() * zoom / 2, image.height() * zoom); + + region |= QRect( + 0, 0, + image.width() * zoom, image.height() * zoom / 2); + scaler.setDestinationRegion(region); + + QImage expectedImage(image.size() * zoom, image.format()); + expectedImage.fill(0); + { + QPainter painter(&expectedImage); + QImage tmp; + tmp = image.copy(0, 0, + expectedImage.width() / zoom / 2, + expectedImage.height() / zoom); + painter.drawImage(0, 0, tmp.scaled(tmp.size() * zoom)); + tmp = image.copy(0, 0, + expectedImage.width() / zoom, + expectedImage.height() / zoom / 2); + painter.drawImage(0, 0, tmp.scaled(tmp.size() * zoom)); + } + + QImage scaledImage = client.createFullImage(); + + QCOMPARE(scaledImage, expectedImage); +} + +/** + * Scale whole image in two passes, not using exact pixel boundaries + */ +void ImageScalerTest::testScaleFullImageTwoPasses() +{ + QFETCH(qreal, zoom); + QImage image(10, 10, QImage::Format_ARGB32); + { + QPainter painter(&image); + painter.fillRect(image.rect(), Qt::white); + painter.drawLine(0, 0, image.width(), image.height()); + } + + Gwenview::ImageScaler scaler; + ImageScalerClient client(&scaler); + + scaler.setImage(&image); + scaler.setZoom(zoom); + int zWidth = int(image.width() * zoom); + int zHeight = int(image.width() * zoom); + int partialZWidth = zWidth / 3; + scaler.setDestinationRegion( + QRect( + 0, 0, + partialZWidth, zHeight) + ); + + scaler.setDestinationRegion( + QRect( + partialZWidth, 0, + zWidth - partialZWidth, zHeight) + ); + + QImage expectedImage = image.scaled(image.size() * zoom); + + QImage scaledImage = client.createFullImage(); + QCOMPARE(expectedImage, scaledImage); +} + +void ImageScalerTest::testScaleFullImageTwoPasses_data() +{ + QTest::addColumn("zoom"); + + QTest::newRow("0.5") << 0.5; + QTest::newRow("2.0") << 2.0; + QTest::newRow("4.0") << 4.0; +} + +/** + * When zooming out, make sure that we don't crash when scaling an area which + * have one dimension smaller than one pixel in the destination. + */ +void ImageScalerTest::testScaleThinArea() +{ + QImage image(10, 10, QImage::Format_ARGB32); + image.fill(0); + + Gwenview::ImageScaler scaler; + + const qreal zoom = 0.25; + scaler.setImage(&image); + scaler.setZoom(zoom); + scaler.setDestinationRegion(QRect(0, 0, image.width(), 2)); +} + +/** + * Test instantiating a scaler without setting an image won't crash + */ +void ImageScalerTest::testDontCrashWithoutImage() +{ + Gwenview::ImageScaler scaler; + scaler.setZoom(1.0); + scaler.setDestinationRegion(QRect(0, 0, 10, 10)); +} + +/** + * Test that scaling down a big image (==bigger than MAX_CHUNK_AREA) does not + * produce any gap + */ +void ImageScalerTest::testScaleDownBigImage() +{ + QImage image(1704, 2272, QImage::Format_RGB32); + image.fill(255); + + Gwenview::ImageScaler scaler; + ImageScalerClient client(&scaler); + + const qreal zoom = 0.28125; + scaler.setImage(&image); + scaler.setZoom(zoom); + scaler.setDestinationRegion(QRect(QPoint(0, 0), image.size() * zoom)); + + QImage scaledImage = client.createFullImage(); + + QImage expectedImage = image.scaled(scaledImage.size()); + QCOMPARE(expectedImage, scaledImage); +} +#endif diff --git a/gwenview/tests/auto/imagescalertest.h b/gwenview/tests/auto/imagescalertest.h new file mode 100644 index 00000000..7e71b489 --- /dev/null +++ b/gwenview/tests/auto/imagescalertest.h @@ -0,0 +1,104 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef IMAGESCALERTEST_H +#define IMAGESCALERTEST_H + +#include "../lib/imagescaler.h" + +// Qt +#include +#include + +// KDE +#include + +class ImageScalerClient : public QObject +{ + Q_OBJECT +public: + ImageScalerClient(Gwenview::ImageScaler* scaler) + { + connect(scaler, SIGNAL(scaledRect(int, int, const QImage&)), + SLOT(slotScaledRect(int, int, const QImage&))); + } + + struct ImageInfo + { + int left; + int top; + QImage image; + }; + QVector mImageInfoList; + + QImage createFullImage() + { + Q_ASSERT(mImageInfoList.size() > 0); + QImage::Format format = mImageInfoList[0].image.format(); + + int imageWidth = 0; + int imageHeight = 0; + Q_FOREACH(const ImageInfo & info, mImageInfoList) { + int right = info.left + info.image.width(); + int bottom = info.top + info.image.height(); + imageWidth = qMax(imageWidth, right); + imageHeight = qMax(imageHeight, bottom); + } + + QImage image(imageWidth, imageHeight, format); + image.fill(0); + QPainter painter(&image); + Q_FOREACH(const ImageInfo & info, mImageInfoList) { + painter.drawImage(info.left, info.top, info.image); + } + return image; + } + +public Q_SLOTS: + void slotScaledRect(int left, int top, const QImage& image) + { + ImageInfo info; + info.left = left; + info.top = top; + info.image = image; + + mImageInfoList.append(info); + } +}; + +class ImageScalerTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testScaleFullImage(); + + // FIXME Disabled for now, does not compile since ImageScaler::setImage() has + // been replaced with ImageScaler::setDocument() +#if 0 + void testScalePartialImage(); + void testScaleFullImageTwoPasses(); + void testScaleFullImageTwoPasses_data(); + void testScaleThinArea(); + void testDontCrashWithoutImage(); + void testScaleDownBigImage(); +#endif +}; + +#endif // IMAGESCALERTEST_H diff --git a/gwenview/tests/auto/importertest.cpp b/gwenview/tests/auto/importertest.cpp new file mode 100644 index 00000000..1939cbb0 --- /dev/null +++ b/gwenview/tests/auto/importertest.cpp @@ -0,0 +1,248 @@ +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "importertest.moc" + +// stdlib +#include + +// Qt +#include + +// KDE +#include +#include +#include + +// Local +#include "../importer/fileutils.h" +#include "../importer/importer.h" +#include "../importer/filenameformater.h" +#include "testutils.h" + +QTEST_KDEMAIN(ImporterTest, GUI) + +using namespace Gwenview; + +void ImporterTest::init() +{ + mDocumentList = KUrl::List() + << urlForTestFile("import/pict0001.jpg") + << urlForTestFile("import/pict0002.jpg") + << urlForTestFile("import/pict0003.jpg") + ; + + mTempDir.reset(new KTempDir()); +} + +void ImporterTest::testContentsAreIdentical() +{ + QVERIFY(!FileUtils::contentsAreIdentical(mDocumentList[0], mDocumentList[1])); + QVERIFY(FileUtils::contentsAreIdentical(mDocumentList[0], mDocumentList[0])); + + KUrl url1 = mDocumentList[0]; + KUrl url2 = urlForTestOutputFile("foo"); + + // Test on a copy of a file + QFile::remove(url2.toLocalFile()); + QFile::copy(url1.toLocalFile(), url2.toLocalFile()); + + QVERIFY(FileUtils::contentsAreIdentical(url1, url2)); + + // Alter one byte of the copy and test again + QFile file(url2.toLocalFile()); + QVERIFY(file.open(QIODevice::ReadOnly)); + QByteArray data = file.readAll(); + file.close(); + data[data.size() / 2] = 255 - data[data.size() / 2]; + + file.open(QIODevice::WriteOnly); + file.write(data); + file.close(); + + QVERIFY(!FileUtils::contentsAreIdentical(url1, url2)); +} + +void ImporterTest::testSuccessfulImport() +{ + KUrl destUrl = KUrl::fromPath(mTempDir->name() + "/foo"); + + Importer importer(0); + QSignalSpy maximumChangedSpy(&importer, SIGNAL(maximumChanged(int))); + QSignalSpy errorSpy(&importer, SIGNAL(error(QString))); + + KUrl::List list = mDocumentList; + + QEventLoop loop; + connect(&importer, SIGNAL(importFinished()), &loop, SLOT(quit())); + importer.start(list, destUrl); + loop.exec(); + + QCOMPARE(maximumChangedSpy.count(), 1); + QCOMPARE(maximumChangedSpy.takeFirst().at(0).toInt(), list.count() * 100); + QCOMPARE(errorSpy.count(), 0); + + QCOMPARE(importer.importedUrlList().count(), list.count()); + QCOMPARE(importer.importedUrlList(), list); + QCOMPARE(importer.skippedUrlList().count(), 0); + QCOMPARE(importer.renamedCount(), 0); + + Q_FOREACH(const KUrl & src, list) { + KUrl dst = destUrl; + dst.addPath(src.fileName()); + QVERIFY(FileUtils::contentsAreIdentical(src, dst)); + } +} + +void ImporterTest::testSkippedUrlList() +{ + KUrl destUrl = KUrl::fromPath(mTempDir->name() + "/foo"); + + Importer importer(0); + + KUrl::List list = mDocumentList.mid(0, 1); + + QEventLoop loop; + connect(&importer, SIGNAL(importFinished()), &loop, SLOT(quit())); + importer.start(list, destUrl); + loop.exec(); + + QCOMPARE(importer.importedUrlList().count(), 1); + QCOMPARE(importer.importedUrlList(), list); + + list = mDocumentList; + KUrl::List expectedImportedList = mDocumentList.mid(1); + KUrl::List expectedSkippedList = mDocumentList.mid(0, 1); + importer.start(list, destUrl); + loop.exec(); + + QCOMPARE(importer.importedUrlList().count(), 2); + QCOMPARE(importer.importedUrlList(), expectedImportedList); + QCOMPARE(importer.skippedUrlList(), expectedSkippedList); + QCOMPARE(importer.renamedCount(), 0); +} + +void ImporterTest::testRenamedCount() +{ + KUrl destUrl = KUrl::fromPath(mTempDir->name() + "/foo"); + + Importer importer(0); + + KUrl::List list; + list << mDocumentList.first(); + + QEventLoop loop; + connect(&importer, SIGNAL(importFinished()), &loop, SLOT(quit())); + importer.start(list, destUrl); + loop.exec(); + + QCOMPARE(importer.importedUrlList().count(), 1); + QCOMPARE(importer.importedUrlList(), list); + + // Modify imported document so that next import does not skip it + { + KUrl url = destUrl; + url.addPath(mDocumentList.first().fileName()); + QFile file(url.toLocalFile()); + QVERIFY(file.open(QIODevice::Append)); + file.write("foo"); + } + + list = mDocumentList; + importer.start(list, destUrl); + loop.exec(); + + QCOMPARE(importer.importedUrlList().count(), 3); + QCOMPARE(importer.importedUrlList(), mDocumentList); + QCOMPARE(importer.skippedUrlList().count(), 0); + QCOMPARE(importer.renamedCount(), 1); +} + +void ImporterTest::testFileNameFormater() +{ + QFETCH(QString, fileName); + QFETCH(QString, dateTime); + QFETCH(QString, format); + QFETCH(QString, expected); + + KUrl url = KUrl("file://foo/bar/" + fileName); + FileNameFormater fileNameFormater(format); + QCOMPARE(fileNameFormater.format(url, KDateTime::fromString(dateTime)), expected); +} + +#define NEW_ROW(fileName, dateTime, format, expected) QTest::newRow(fileName) << fileName << dateTime << format << expected +void ImporterTest::testFileNameFormater_data() +{ + QTest::addColumn("fileName"); + QTest::addColumn("dateTime"); + QTest::addColumn("format"); + QTest::addColumn("expected"); + + NEW_ROW("PICT0001.JPG", "20091024T225049", "{date}_{time}.{ext}", "2009-10-24_22-50-49.JPG"); + NEW_ROW("PICT0001.JPG", "20091024T225049", "{date}_{time}.{ext.lower}", "2009-10-24_22-50-49.jpg"); + NEW_ROW("2009.10.24.JPG", "20091024T225049", "{date}_{time}.{ext.lower}", "2009-10-24_22-50-49.jpg"); + NEW_ROW("PICT0001.JPG", "20091024T225049", "{name}.{ext}", "PICT0001.JPG"); + NEW_ROW("PICT0001.JPG", "20091024T225049", "{name.lower}.{ext.lower}", "pict0001.jpg"); + NEW_ROW("iLikeCurlies", "20091024T225049", "{{{name}}", "{iLikeCurlies}"); + NEW_ROW("UnknownKeyword", "20091024T225049", "foo{unknown}bar", "foobar"); + NEW_ROW("MissingClosingCurly", "20091024T225049", "foo{date", "foo"); +} + +void ImporterTest::testAutoRenameFormat() +{ + QStringList dates = QStringList() + << "1979-02-23_10-20-00" + << "2006-04-01_11-30-15" + << "2009-10-01_21-15-27"; + QCOMPARE(dates.count(), mDocumentList.count()); + + KUrl destUrl = KUrl::fromPath(mTempDir->name() + "foo"); + + Importer importer(0); + importer.setAutoRenameFormat("{date}_{time}.{ext}"); + KUrl::List list = mDocumentList; + + QEventLoop loop; + connect(&importer, SIGNAL(importFinished()), &loop, SLOT(quit())); + importer.start(list, destUrl); + loop.exec(); + + QCOMPARE(importer.importedUrlList().count(), list.count()); + QCOMPARE(importer.importedUrlList(), list); + + for (int pos = 0; pos < dates.count(); ++pos) { + KUrl src = list[pos]; + KUrl dst = destUrl; + dst.addPath(dates[pos] + ".jpg"); + QVERIFY(FileUtils::contentsAreIdentical(src, dst)); + } +} + +void ImporterTest::testReadOnlyDestination() +{ + KUrl destUrl = KUrl::fromPath(mTempDir->name() + "/foo"); + chmod(QFile::encodeName(mTempDir->name()), 0555); + + Importer importer(0); + QSignalSpy errorSpy(&importer, SIGNAL(error(QString))); + importer.start(mDocumentList, destUrl); + + QCOMPARE(errorSpy.count(), 1); + QVERIFY(importer.importedUrlList().isEmpty()); +} diff --git a/gwenview/tests/auto/importertest.h b/gwenview/tests/auto/importertest.h new file mode 100644 index 00000000..a12529f0 --- /dev/null +++ b/gwenview/tests/auto/importertest.h @@ -0,0 +1,53 @@ +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef IMPORTERTEST_H +#define IMPORTERTEST_H + +// stdc++ +#include + +// Qt +#include + +// KDE +#include +#include + +class ImporterTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void init(); + void testContentsAreIdentical(); + void testSuccessfulImport(); + void testAutoRenameFormat(); + void testReadOnlyDestination(); + void testFileNameFormater(); + void testFileNameFormater_data(); + void testSkippedUrlList(); + void testRenamedCount(); + +private: + std::auto_ptr mTempDir; + KUrl::List mDocumentList; +}; + +#endif /* IMPORTERTEST_H */ diff --git a/gwenview/tests/auto/jpegcontenttest.cpp b/gwenview/tests/auto/jpegcontenttest.cpp new file mode 100644 index 00000000..d9273edc --- /dev/null +++ b/gwenview/tests/auto/jpegcontenttest.cpp @@ -0,0 +1,303 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "jpegcontenttest.moc" +#include + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include + +// Local +#include "../lib/orientation.h" +#include "../lib/jpegcontent.h" +#include "testutils.h" + +using namespace std; + +const char* ORIENT6_FILE = "orient6.jpg"; +const char* ORIENT1_VFLIP_FILE = "orient1_vflip.jpg"; +const char* CUT_FILE = "cut.jpg"; +const char* TMP_FILE = "tmp.jpg"; +const char* THUMBNAIL_FILE = "test_thumbnail.jpg"; + +const int ORIENT6_WIDTH = 128; // This size is the size *after* orientation +const int ORIENT6_HEIGHT = 256; // has been applied +const QString ORIENT6_COMMENT = "a comment"; + +QTEST_KDEMAIN(JpegContentTest, GUI) + +void JpegContentTest::initTestCase() +{ + bool result; + QFile in(pathForTestFile(ORIENT6_FILE)); + result = in.open(QIODevice::ReadOnly); + QVERIFY(result); + + QFileInfo info(in); + int size = info.size() / 2; + + char* data = new char[size]; + int readSize = in.read(data, size); + QCOMPARE(size, readSize); + + QFile out(CUT_FILE); + result = out.open(QIODevice::WriteOnly); + QVERIFY(result); + + int wroteSize = out.write(data, size); + QCOMPARE(size, wroteSize); + delete []data; +} + +void JpegContentTest::cleanupTestCase() +{ + QDir::current().remove(CUT_FILE); +} + +typedef QMap MetaInfoMap; + +MetaInfoMap getMetaInfo(const QString& path) +{ + KFileMetaInfo fmi(path); + QStringList list = fmi.supportedKeys(); + QStringList::ConstIterator it = list.constBegin(); + MetaInfoMap map; + + for (; it != list.constEnd(); ++it) { + KFileMetaInfoItem item = fmi.item(*it); + map[*it] = item.value().toString(); + } + + return map; +} + +void compareMetaInfo(const QString& path1, const QString& path2, const QStringList& ignoredKeys) +{ + MetaInfoMap mim1 = getMetaInfo(path1); + MetaInfoMap mim2 = getMetaInfo(path2); + + QCOMPARE(mim1.keys(), mim2.keys()); + QList keys = mim1.keys(); + QList::ConstIterator it = keys.constBegin(); + for (; it != keys.constEnd(); ++it) { + QString key = *it; + if (ignoredKeys.contains(key)) continue; + + QString msg = + QString("Meta info differs for key '%1': v1=%2 v2=%3") + .arg(key) + .arg(mim1[key]) + .arg(mim2[key]); + + QVERIFY2(mim1[key] == mim2[key], msg.toUtf8()); + } +} + +void JpegContentTest::testResetOrientation() +{ + Gwenview::JpegContent content; + bool result; + + // Test resetOrientation without transform + result = content.load(pathForTestFile(ORIENT6_FILE)); + QVERIFY(result); + + content.resetOrientation(); + + result = content.save(TMP_FILE); + QVERIFY(result); + + result = content.load(TMP_FILE); + QVERIFY(result); + QCOMPARE(content.orientation(), Gwenview::NORMAL); + + // Test resetOrientation with transform + result = content.load(pathForTestFile(ORIENT6_FILE)); + QVERIFY(result); + + content.resetOrientation(); + content.transform(Gwenview::ROT_90); + + result = content.save(TMP_FILE); + QVERIFY(result); + + result = content.load(TMP_FILE); + QVERIFY(result); + QCOMPARE(content.orientation(), Gwenview::NORMAL); +} + +/** + * This function tests JpegContent::transform() by applying a ROT_90 + * transformation, saving, reloading and applying a ROT_270 to undo the ROT_90. + * Saving and reloading are necessary because lossless transformation only + * happens in JpegContent::save() + */ +void JpegContentTest::testTransform() +{ + bool result; + QImage finalImage, expectedImage; + + Gwenview::JpegContent content; + result = content.load(pathForTestFile(ORIENT6_FILE)); + QVERIFY(result); + + content.transform(Gwenview::ROT_90); + result = content.save(TMP_FILE); + QVERIFY(result); + + result = content.load(TMP_FILE); + QVERIFY(result); + content.transform(Gwenview::ROT_270); + result = content.save(TMP_FILE); + QVERIFY(result); + + result = finalImage.load(TMP_FILE); + QVERIFY(result); + + result = expectedImage.load(pathForTestFile(ORIENT6_FILE)); + QVERIFY(result); + + QCOMPARE(finalImage , expectedImage); +} + +void JpegContentTest::testSetComment() +{ + QString comment = "test comment"; + Gwenview::JpegContent content; + bool result; + result = content.load(pathForTestFile(ORIENT6_FILE)); + QVERIFY(result); + + content.setComment(comment); + QCOMPARE(content.comment() , comment); + result = content.save(TMP_FILE); + QVERIFY(result); + + result = content.load(TMP_FILE); + QVERIFY(result); + QCOMPARE(content.comment() , comment); +} + +void JpegContentTest::testReadInfo() +{ + Gwenview::JpegContent content; + bool result = content.load(pathForTestFile(ORIENT6_FILE)); + QVERIFY(result); + QCOMPARE(int(content.orientation()), 6); + QCOMPARE(content.comment() , ORIENT6_COMMENT); + QCOMPARE(content.size() , QSize(ORIENT6_WIDTH, ORIENT6_HEIGHT)); +} + +void JpegContentTest::testThumbnail() +{ + Gwenview::JpegContent content; + bool result = content.load(pathForTestFile(ORIENT6_FILE)); + QVERIFY(result); + QImage thumbnail = content.thumbnail(); + result = thumbnail.save(THUMBNAIL_FILE, "JPEG"); + QVERIFY(result); +} + +void JpegContentTest::testMultipleRotations() +{ + // Test that rotating a file a lot of times does not cause findJxform() to fail + Gwenview::JpegContent content; + bool result = content.load(pathForTestFile(ORIENT6_FILE)); + QVERIFY(result); + result = content.load(pathForTestFile(ORIENT6_FILE)); + QVERIFY(result); + + // 12*4 + 1 is the same as 1, since rotating four times brings you back + for (int loop = 0; loop < 12 * 4 + 1; ++loop) { + content.transform(Gwenview::ROT_90); + } + result = content.save(TMP_FILE); + QVERIFY(result); + + result = content.load(TMP_FILE); + QVERIFY(result); + + QCOMPARE(content.size() , QSize(ORIENT6_HEIGHT, ORIENT6_WIDTH)); + + // Check the other meta info are still here + QStringList ignoredKeys; + ignoredKeys << "Orientation" << "Comment"; + compareMetaInfo(pathForTestFile(ORIENT6_FILE), pathForTestFile(ORIENT1_VFLIP_FILE), ignoredKeys); +} + +void JpegContentTest::testLoadTruncated() +{ + // Test that loading and manipulating a truncated file does not crash + Gwenview::JpegContent content; + bool result = content.load(CUT_FILE); + QVERIFY(result); + QCOMPARE(int(content.orientation()), 6); + QCOMPARE(content.comment() , ORIENT6_COMMENT); + content.transform(Gwenview::VFLIP); + kWarning() << "# Next function should output errors about incomplete image" ; + content.save(TMP_FILE); + kWarning() << "#" ; +} + +void JpegContentTest::testRawData() +{ + Gwenview::JpegContent content; + bool result = content.load(pathForTestFile(ORIENT6_FILE)); + QVERIFY(result); + + QByteArray fileData; + QFile file(pathForTestFile(ORIENT6_FILE)); + result = file.open(QIODevice::ReadOnly); + QVERIFY(result); + fileData = file.readAll(); + + QCOMPARE(content.rawData(), fileData); +} + +void JpegContentTest::testSetImage() +{ + Gwenview::JpegContent content; + bool result = content.load(pathForTestFile(ORIENT6_FILE)); + QVERIFY(result); + + QImage image = QImage(400, 300, QImage::Format_RGB32); + image.fill(Qt::red); + + content.setImage(image); + + result = content.save(TMP_FILE); + QVERIFY(result); + + result = content.load(TMP_FILE); + QVERIFY(result); + + QCOMPARE(content.size(), image.size()); + + QStringList ignoredKeys; + ignoredKeys << "Orientation"; + compareMetaInfo(pathForTestFile(ORIENT6_FILE), pathForTestFile(TMP_FILE), ignoredKeys); +} diff --git a/gwenview/tests/auto/jpegcontenttest.h b/gwenview/tests/auto/jpegcontenttest.h new file mode 100644 index 00000000..fd4ae0ca --- /dev/null +++ b/gwenview/tests/auto/jpegcontenttest.h @@ -0,0 +1,44 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef JPEGCONTENTTEST_H +#define JPEGCONTENTTEST_H + +// Qt +#include + +class JpegContentTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void testReadInfo(); + void testThumbnail(); + void testResetOrientation(); + void testTransform(); + void testSetComment(); + void testMultipleRotations(); + void testLoadTruncated(); + void testRawData(); + void testSetImage(); +}; + +#endif // JPEGCONTENTTEST_H diff --git a/gwenview/tests/auto/paintutilstest.cpp b/gwenview/tests/auto/paintutilstest.cpp new file mode 100644 index 00000000..5390773c --- /dev/null +++ b/gwenview/tests/auto/paintutilstest.cpp @@ -0,0 +1,43 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include + +#include "../lib/paintutils.h" + +#include "paintutilstest.moc" + +QTEST_KDEMAIN(PaintUtilsTest, GUI) + +void PaintUtilsTest::testScaledRect_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expected"); + + QTest::newRow("overflow right") << QRectF(1.0, 1.0, 2.7, 3.2) << QRect(1, 1, 3, 4); + QTest::newRow("overflow left") << QRectF(0.5, 1.0, 2.0, 3.2) << QRect(0, 1, 3, 4); + QTest::newRow("overflow both") << QRectF(0.5, 1.0, 2.6, 3.2) << QRect(0, 1, 4, 4); +} + +void PaintUtilsTest::testScaledRect() +{ + QFETCH(QRectF, input); + QFETCH(QRect, expected); + QCOMPARE(Gwenview::PaintUtils::containingRect(input), expected); +} diff --git a/gwenview/tests/auto/paintutilstest.h b/gwenview/tests/auto/paintutilstest.h new file mode 100644 index 00000000..9938c6fa --- /dev/null +++ b/gwenview/tests/auto/paintutilstest.h @@ -0,0 +1,36 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef PAINTUTILSTEST_H +#define PAINTUTILSTEST_H + +// Qt + +// KDE + +class PaintUtilsTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testScaledRect(); + void testScaledRect_data(); +}; + +#endif // PAINTUTILSTEST_H diff --git a/gwenview/tests/auto/placetreemodeltest.cpp b/gwenview/tests/auto/placetreemodeltest.cpp new file mode 100644 index 00000000..66b79e43 --- /dev/null +++ b/gwenview/tests/auto/placetreemodeltest.cpp @@ -0,0 +1,157 @@ +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "placetreemodeltest.moc" + +// Qt +#include +#include + +// KDE +#include +#include +#include + +// Local +#include "../lib/placetreemodel.h" +#include "testutils.h" + +//#define KEEP_TEMP_DIR + +QTEST_KDEMAIN(PlaceTreeModelTest, GUI) + +using namespace Gwenview; + +const char* BOOKMARKS_XML = + "" + "" + "" + " " + " url1" + " " + " " + " " + " " + " " + " 1214343736/0" + " true" + " " + " " + " " + " " + " url2" + " " + " " + " " + " " + " " + " 1214343736/1" + " true" + " " + " " + " " + ""; + +void PlaceTreeModelTest::initTestCase() +{ + Q_ASSERT(mTempDir.exists()); + QDir dir(mTempDir.name()); + + const bool dir1created = dir.mkdir("url1"); + Q_ASSERT(dir1created); + Q_UNUSED(dir1created); + mUrl1 = KUrl::fromPath(dir.filePath("url1")); + + const bool dir2created = dir.mkdir("url2"); + Q_ASSERT(dir2created); + Q_UNUSED(dir2created); + mUrl2 = KUrl::fromPath(dir.filePath("url2")); + + mUrl1Dirs << "aaa" << "zzz" << "bbb"; + Q_FOREACH(const QString & dirName, mUrl1Dirs) { + dir.mkdir("url1/" + dirName); + } + +#ifdef KEEP_TEMP_DIR + mTempDir.setAutoRemove(false); + kDebug() << "mTempDir:" << mTempDir.name(); +#endif +} + +void PlaceTreeModelTest::init() +{ + TestUtils::purgeUserConfiguration(); + + QFile bookmark(KStandardDirs::locateLocal("data", "kfileplaces/bookmarks.xml")); + const bool bookmarkOpened = bookmark.open(QIODevice::WriteOnly); + Q_ASSERT(bookmarkOpened); + Q_UNUSED(bookmarkOpened); + + QString xml = QString(BOOKMARKS_XML) + .arg(mUrl1.toLocalFile()) + .arg(mUrl2.toLocalFile()) + ; + bookmark.write(xml.toUtf8()); + +#ifdef KEEP_TEMP_DIR + mTempDir.setAutoRemove(false); + kDebug() << "mTempDir:" << mTempDir.name(); +#endif +} + +void PlaceTreeModelTest::testListPlaces() +{ + PlaceTreeModel model(0); + QCOMPARE(model.rowCount(), 2); + + QModelIndex index; + index = model.index(0, 0); + QCOMPARE(model.urlForIndex(index), mUrl1); + index = model.index(1, 0); + QCOMPARE(model.urlForIndex(index), mUrl2); +} + +void PlaceTreeModelTest::testListUrl1() +{ + PlaceTreeModel model(0); + + QModelIndex index = model.index(0, 0); + QCOMPARE(model.urlForIndex(index), mUrl1); + + // We should not have fetched content yet + QCOMPARE(model.rowCount(index), 0); + QVERIFY(model.canFetchMore(index)); + + while (model.canFetchMore(index)) { + model.fetchMore(index); + } + QTest::qWait(1000); + QCOMPARE(model.rowCount(index), mUrl1Dirs.length()); + + QStringList dirs = mUrl1Dirs; + dirs.sort(); + + for (int row = 0; row < dirs.count(); ++row) { + QModelIndex subIndex = model.index(row, 0, index); + QVERIFY(subIndex.isValid()); + + QString dirName = model.data(subIndex).toString(); + QCOMPARE(dirName, dirs.value(row)); + } +} diff --git a/gwenview/tests/auto/placetreemodeltest.h b/gwenview/tests/auto/placetreemodeltest.h new file mode 100644 index 00000000..4a163572 --- /dev/null +++ b/gwenview/tests/auto/placetreemodeltest.h @@ -0,0 +1,47 @@ +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef PLACETREEMODELTEST_H +#define PLACETREEMODELTEST_H + +// Qt +#include +#include + +// KDE +#include +#include + +class PlaceTreeModelTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void init(); + void initTestCase(); + void testListPlaces(); + void testListUrl1(); + +private: + KUrl mUrl1, mUrl2; + QStringList mUrl1Dirs; + KTempDir mTempDir; +}; + +#endif /* PLACETREEMODELTEST_H */ diff --git a/gwenview/tests/auto/recursivedirmodeltest.cpp b/gwenview/tests/auto/recursivedirmodeltest.cpp new file mode 100644 index 00000000..ccf47c23 --- /dev/null +++ b/gwenview/tests/auto/recursivedirmodeltest.cpp @@ -0,0 +1,200 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include + +// Local +#include +#include + +// Qt + +// KDE +#include +#include + +using namespace Gwenview; + +QTEST_KDEMAIN(RecursiveDirModelTest, GUI) + +void RecursiveDirModelTest::testBasic_data() +{ + QTest::addColumn("initialFiles"); + QTest::addColumn("addedFiles"); + QTest::addColumn("removedFiles"); +#define NEW_ROW(name, initialFiles, addedFiles, removedFiles) QTest::newRow(name) << (initialFiles) << (addedFiles) << (removedFiles) + NEW_ROW("empty_dir", + QStringList(), + QStringList() + << "new.jpg", + QStringList() + << "new.jpg" + ); + NEW_ROW("images_only", + QStringList() + << "pict01.jpg" + << "pict02.jpg" + << "pict03.jpg", + QStringList() + << "pict04.jpg", + QStringList() + << "pict02.jpg" + ); + NEW_ROW("images_in_two_dirs", + QStringList() + << "d1/pict101.jpg" + << "d1/pict102.jpg" + << "d2/pict201.jpg", + QStringList() + << "d1/pict103.jpg" + << "d2/pict202.jpg", + QStringList() + << "d2/pict202.jpg" + ); + NEW_ROW("images_in_two_dirs_w_same_names", + QStringList() + << "d1/a.jpg" + << "d1/b.jpg" + << "d2/a.jpg" + << "d2/b.jpg", + QStringList() + << "d3/a.jpg" + << "d3/b.jpg", + QStringList() + << "d1/a.jpg" + << "d2/a.jpg" + << "d3/a.jpg" + ); +#undef NEW_ROW +} + +static QList listModelUrls(QAbstractItemModel* model) +{ + QList out; + for (int row = 0; row < model->rowCount(QModelIndex()); ++row) { + QModelIndex index = model->index(row, 0); + KFileItem item = index.data(KDirModel::FileItemRole).value(); + out << item.url(); + } + qSort(out); + return out; +} + +static QList listExpectedUrls(const QDir& dir, const QStringList& files) +{ + QList lst; + Q_FOREACH(const QString &name, files) { + KUrl url(dir.absoluteFilePath(name)); + lst << url; + } + qSort(lst); + return lst; +} + +void logLst(const QList& lst) +{ + Q_FOREACH(const KUrl& url, lst) { + kWarning() << url.fileName(); + } +} + +void RecursiveDirModelTest::testBasic() +{ + QFETCH(QStringList, initialFiles); + QFETCH(QStringList, addedFiles); + QFETCH(QStringList, removedFiles); + TestUtils::SandBoxDir sandBoxDir; + + RecursiveDirModel model; + TestUtils::TimedEventLoop loop; + connect(&model, SIGNAL(completed()), &loop, SLOT(quit())); + + // Test initial files + sandBoxDir.fill(initialFiles); + model.setUrl(sandBoxDir.absolutePath()); + loop.exec(); + + QList out = listModelUrls(&model); + QList expected = listExpectedUrls(sandBoxDir, initialFiles); + QCOMPARE(out, expected); + + // Test adding new files + sandBoxDir.fill(addedFiles); + loop.exec(); + + out = listModelUrls(&model); + expected = listExpectedUrls(sandBoxDir, initialFiles + addedFiles); + QCOMPARE(out, expected); + +# if 0 + /* FIXME: This part of the test is not reliable :/ Sometimes some tests pass, + * sometimes they don't. It feels like KDirLister::itemsDeleted() is not + * always emitted. + */ + + // Test removing files + Q_FOREACH(const QString &name, removedFiles) { + bool ok = sandBoxDir.remove(name); + Q_ASSERT(ok); + expected.removeOne(KUrl(sandBoxDir.absoluteFilePath(name))); + } + QTime chrono; + chrono.start(); + while (chrono.elapsed() < 2000) { + waitForDeferredDeletes(); + } + + out = listModelUrls(&model); + if (out != expected) { + kWarning() << "out:"; + logLst(out); + kWarning() << "expected:"; + logLst(expected); + } + QCOMPARE(out, expected); +#endif +} + +void RecursiveDirModelTest::testSetNewUrl() +{ + TestUtils::SandBoxDir sandBoxDir; + sandBoxDir.fill( + QStringList() + << "d1/a.jpg" + << "d1/b.jpg" + << "d1/c.jpg" + << "d1/d.jpg" + << "d2/e.jpg" + << "d2/f.jpg" + ); + + RecursiveDirModel model; + TestUtils::TimedEventLoop loop; + connect(&model, SIGNAL(completed()), &loop, SLOT(quit())); + + model.setUrl(sandBoxDir.absoluteFilePath("d1")); + loop.exec(); + QCOMPARE(model.rowCount(QModelIndex()), 4); + + model.setUrl(sandBoxDir.absoluteFilePath("d2")); + loop.exec(); + QCOMPARE(model.rowCount(QModelIndex()), 2); +} diff --git a/gwenview/tests/auto/recursivedirmodeltest.h b/gwenview/tests/auto/recursivedirmodeltest.h new file mode 100644 index 00000000..08b46978 --- /dev/null +++ b/gwenview/tests/auto/recursivedirmodeltest.h @@ -0,0 +1,39 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef RECURSIVEDIRMODELTEST_H +#define RECURSIVEDIRMODELTEST_H + +// Local +#include + +// Qt +#include + +class RecursiveDirModelTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testBasic_data(); + void testBasic(); + void testSetNewUrl(); +}; + +#endif /* RECURSIVEDIRMODELTEST_H */ diff --git a/gwenview/tests/auto/semanticinfobackendtest.cpp b/gwenview/tests/auto/semanticinfobackendtest.cpp new file mode 100644 index 00000000..2221d381 --- /dev/null +++ b/gwenview/tests/auto/semanticinfobackendtest.cpp @@ -0,0 +1,137 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Local +#include "semanticinfobackendtest.moc" + +// Qt +#include + +// KDE +#include +#include +#include +#include + +// Local +#include "testutils.h" +#include + +#ifdef GWENVIEW_SEMANTICINFO_BACKEND_FAKE +#include + +#elif defined(GWENVIEW_SEMANTICINFO_BACKEND_BALOO) +#include + +#else +#ifdef __GNUC__ +#error No metadata backend defined +#endif +#endif + +QTEST_KDEMAIN(Gwenview::SemanticInfoBackEndTest, GUI) + +namespace Gwenview +{ + +SemanticInfoBackEndClient::SemanticInfoBackEndClient(AbstractSemanticInfoBackEnd* backEnd) +: mBackEnd(backEnd) +{ + connect(backEnd, SIGNAL(semanticInfoRetrieved(KUrl,SemanticInfo)), + SLOT(slotSemanticInfoRetrieved(KUrl,SemanticInfo))); +} + +void SemanticInfoBackEndClient::slotSemanticInfoRetrieved(const KUrl& url, const SemanticInfo& semanticInfo) +{ + mSemanticInfoForUrl[url] = semanticInfo; +} + +void SemanticInfoBackEndTest::initTestCase() +{ + qRegisterMetaType("KUrl"); + qRegisterMetaType("SemanticInfoTag"); +} + +void SemanticInfoBackEndTest::init() +{ +#ifdef GWENVIEW_SEMANTICINFO_BACKEND_FAKE + mBackEnd = new FakeSemanticInfoBackEnd(0, FakeSemanticInfoBackEnd::InitializeEmpty); +#elif defined(GWENVIEW_SEMANTICINFO_BACKEND_BALOO) + mBackEnd = new BalooSemanticInfoBackend(0); +#endif +} + +void SemanticInfoBackEndTest::cleanup() +{ + delete mBackEnd; + mBackEnd = 0; +} + +/** + * Get and set the rating of a temp file + */ +void SemanticInfoBackEndTest::testRating() +{ + KTemporaryFile temp; + temp.setSuffix(".metadatabackendtest"); + QVERIFY(temp.open()); + + KUrl url; + url.setPath(temp.fileName()); + + SemanticInfoBackEndClient client(mBackEnd); + QSignalSpy spy(mBackEnd, SIGNAL(semanticInfoRetrieved(KUrl,SemanticInfo))); + mBackEnd->retrieveSemanticInfo(url); + QVERIFY(waitForSignal(spy)); + + SemanticInfo semanticInfo = client.semanticInfoForUrl(url); + QCOMPARE(semanticInfo.mRating, 0); + + semanticInfo.mRating = 5; + mBackEnd->storeSemanticInfo(url, semanticInfo); +} + +#if 0 +// Disabled because Baloo does not work like Nepomuk: it does not create tags +// independently of files. +void SemanticInfoBackEndTest::testTagForLabel() +{ + QSignalSpy spy(mBackEnd, SIGNAL(tagAdded(SemanticInfoTag,QString))); + + TagSet oldAllTags = mBackEnd->allTags(); + QString label = "testTagForLabel-" + KRandom::randomString(5); + SemanticInfoTag tag1 = mBackEnd->tagForLabel(label); + QVERIFY(!tag1.isEmpty()); + QVERIFY(!oldAllTags.contains(tag1)); + QVERIFY(mBackEnd->allTags().contains(tag1)); + + // This is a new tag, we should receive a signal + QCOMPARE(spy.count(), 1); + + SemanticInfoTag tag2 = mBackEnd->tagForLabel(label); + QCOMPARE(tag1, tag2); + // This is not a new tag, we should not receive a signal + QCOMPARE(spy.count(), 1); + + QString label2 = mBackEnd->labelForTag(tag2); + QCOMPARE(label, label2); +} +#endif + +} // namespace diff --git a/gwenview/tests/auto/semanticinfobackendtest.h b/gwenview/tests/auto/semanticinfobackendtest.h new file mode 100644 index 00000000..aaa85807 --- /dev/null +++ b/gwenview/tests/auto/semanticinfobackendtest.h @@ -0,0 +1,78 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef SEMANTICINFOBACKENDTEST_H +#define SEMANTICINFOBACKENDTEST_H + +// Qt +#include +#include + +// KDE +#include + +// Local +#include + +namespace Gwenview +{ + +/** + * Helper class which gathers the metadata retrieved when + * AbstractSemanticInfoBackEnd::retrieveSemanticInfo() is called. + */ +class SemanticInfoBackEndClient : public QObject +{ + Q_OBJECT +public: + SemanticInfoBackEndClient(AbstractSemanticInfoBackEnd*); + + SemanticInfo semanticInfoForUrl(const KUrl& url) const + { + return mSemanticInfoForUrl.value(url); + } + +private Q_SLOTS: + void slotSemanticInfoRetrieved(const KUrl&, const SemanticInfo&); + +private: + QHash mSemanticInfoForUrl; + AbstractSemanticInfoBackEnd* mBackEnd; +}; + +class SemanticInfoBackEndTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testRating(); + #if 0 + void testTagForLabel(); + #endif + +private: + AbstractSemanticInfoBackEnd* mBackEnd; +}; + +} // namespace + +#endif // SEMANTICINFOBACKENDTEST_H diff --git a/gwenview/tests/auto/slidecontainerautotest.cpp b/gwenview/tests/auto/slidecontainerautotest.cpp new file mode 100644 index 00000000..9a50b1c0 --- /dev/null +++ b/gwenview/tests/auto/slidecontainerautotest.cpp @@ -0,0 +1,145 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "slidecontainerautotest.moc" + +// Local +#include + +// KDE +#include +#include + +// Qt +#include +#include + +QTEST_KDEMAIN(SlideContainerAutoTest, GUI) + +using namespace Gwenview; + +struct TestWindow : public QWidget +{ + explicit TestWindow(QWidget* parent = 0) + : QWidget(parent) + , mContainer(new SlideContainer) + , mContent(0) { + createContent(); + + mMainWidget = new QTextEdit(); + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setSpacing(0); + layout->setMargin(0); + layout->addWidget(mMainWidget); + layout->addWidget(mContainer); + } + + void createContent() + { + mContent = new QTextEdit; + mContent->setFixedSize(100, 40); + mContainer->setContent(mContent); + } + + SlideContainer* mContainer; + QWidget* mMainWidget; + QWidget* mContent; +}; + +void SlideContainerAutoTest::testInit() +{ + // Even with content, a SlideContainer should be invisible until slideIn() + // is called + TestWindow window; + window.show(); + + QTest::qWait(500); + QCOMPARE(window.mMainWidget->height(), window.height()); +} + +void SlideContainerAutoTest::testSlideIn() +{ + TestWindow window; + QSignalSpy inSpy(window.mContainer, SIGNAL(slidedIn())); + QSignalSpy outSpy(window.mContainer, SIGNAL(slidedOut())); + window.show(); + + window.mContainer->slideIn(); + while (window.mContainer->slideHeight() != window.mContent->height()) { + QTest::qWait(100); + } + QCOMPARE(window.mContainer->height(), window.mContent->height()); + QCOMPARE(inSpy.count(), 1); + QCOMPARE(outSpy.count(), 0); +} + +void SlideContainerAutoTest::testSlideOut() +{ + TestWindow window; + window.show(); + + window.mContainer->slideIn(); + while (window.mContainer->slideHeight() != window.mContent->height()) { + QTest::qWait(100); + } + + QSignalSpy inSpy(window.mContainer, SIGNAL(slidedIn())); + QSignalSpy outSpy(window.mContainer, SIGNAL(slidedOut())); + window.mContainer->slideOut(); + while (window.mContainer->slideHeight() != 0) { + QTest::qWait(100); + } + QCOMPARE(window.mContainer->height(), 0); + QCOMPARE(inSpy.count(), 0); + QCOMPARE(outSpy.count(), 1); +} + +void SlideContainerAutoTest::testSlideInDeleteSlideOut() +{ + // If content is deleted while visible, slideOut() should still produce an + // animation + TestWindow window; + window.show(); + + window.mContainer->slideIn(); + while (window.mContainer->slideHeight() != window.mContent->height()) { + QTest::qWait(100); + } + window.mContent->deleteLater(); + window.mContainer->slideOut(); + while (window.mContainer->slideHeight() != 0) { + QTest::qWait(100); + } + QCOMPARE(window.mContainer->height(), 0); +} + +void SlideContainerAutoTest::testHiddenContentResize() +{ + // Resizing content should not trigger a slide if it is not visible. + TestWindow window; + window.show(); + QTest::qWaitForWindowShown(&window); + + window.mContent->show(); + window.mContent->setFixedSize(150, 80); + QTest::qWait(500); + QCOMPARE(window.mContainer->height(), 0); +} diff --git a/gwenview/tests/auto/slidecontainerautotest.h b/gwenview/tests/auto/slidecontainerautotest.h new file mode 100644 index 00000000..63199f3d --- /dev/null +++ b/gwenview/tests/auto/slidecontainerautotest.h @@ -0,0 +1,42 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef SLIDECONTAINERAUTOTEST_H +#define SLIDECONTAINERAUTOTEST_H + +// Local + +// KDE + +// Qt +#include + +class SlideContainerAutoTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testInit(); + void testSlideIn(); + void testSlideOut(); + void testSlideInDeleteSlideOut(); + void testHiddenContentResize(); +}; + +#endif /* SLIDECONTAINERAUTOTEST_H */ diff --git a/gwenview/tests/auto/sorteddirmodeltest.cpp b/gwenview/tests/auto/sorteddirmodeltest.cpp new file mode 100644 index 00000000..198a0c88 --- /dev/null +++ b/gwenview/tests/auto/sorteddirmodeltest.cpp @@ -0,0 +1,77 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Self +#include + +// Local +#include +#include + +// Qt + +// KDE +#include +#include +#include + +using namespace Gwenview; + +QTEST_KDEMAIN(SortedDirModelTest, GUI) + +void SortedDirModelTest::initTestCase() +{ + mSandBoxDir.mkdir("empty_dir"); + mSandBoxDir.mkdir("dirs_only"); + mSandBoxDir.mkdir("dirs_only/dir1"); + mSandBoxDir.mkdir("dirs_only/dir2"); + mSandBoxDir.mkdir("dirs_and_docs"); + mSandBoxDir.mkdir("dirs_and_docs/dir"); + createEmptyFile(mSandBoxDir.absoluteFilePath("dirs_and_docs/file.png")); + mSandBoxDir.mkdir("docs_only"); + createEmptyFile(mSandBoxDir.absoluteFilePath("docs_only/file.png")); +} + +void SortedDirModelTest::testHasDocuments_data() +{ + QTest::addColumn("dir"); + QTest::addColumn("hasDocuments"); +#define NEW_ROW(dir, hasDocuments) \ + QTest::newRow(QString(dir).toLocal8Bit().data()) << mSandBoxDir.absoluteFilePath(dir) << hasDocuments + NEW_ROW("empty_dir", false); + NEW_ROW("dirs_only", false); + NEW_ROW("dirs_and_docs", true); + NEW_ROW("docs_only", true); +#undef NEW_ROW +} + +void SortedDirModelTest::testHasDocuments() +{ + QFETCH(QString, dir); + QFETCH(bool, hasDocuments); + KUrl url = KUrl::fromPath(dir); + + SortedDirModel model; + QEventLoop loop; + connect(model.dirLister(), SIGNAL(completed()), &loop, SLOT(quit())); + model.dirLister()->openUrl(url); + loop.exec(); + QCOMPARE(model.hasDocuments(), hasDocuments); +} diff --git a/gwenview/tests/auto/sorteddirmodeltest.h b/gwenview/tests/auto/sorteddirmodeltest.h new file mode 100644 index 00000000..1201f60f --- /dev/null +++ b/gwenview/tests/auto/sorteddirmodeltest.h @@ -0,0 +1,45 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2011 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef SORTEDDIRMODELTEST_H +#define SORTEDDIRMODELTEST_H + +// Local +#include + +// Qt +#include + +// std c++ +#include + +class SortedDirModelTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void testHasDocuments_data(); + void testHasDocuments(); + +private: + TestUtils::SandBoxDir mSandBoxDir; +}; + +#endif /* SORTEDDIRMODELTEST_H */ diff --git a/gwenview/tests/auto/testutils.cpp b/gwenview/tests/auto/testutils.cpp new file mode 100644 index 00000000..9f5ed6cb --- /dev/null +++ b/gwenview/tests/auto/testutils.cpp @@ -0,0 +1,196 @@ +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include + +// Qt +#include +#include + +// KDE +#include +#include +#include + +KUrl setUpRemoteTestDir(const QString& testFile) +{ + QWidget* authWindow = 0; + bool ok; + if (qgetenv("GV_REMOTE_TESTS_BASE_URL").isEmpty()) { + kWarning() << "Environment variable GV_REMOTE_TESTS_BASE_URL not set: remote tests disabled"; + return KUrl(); + } + + KUrl baseUrl = QString::fromLocal8Bit(qgetenv("GV_REMOTE_TESTS_BASE_URL")); + baseUrl.addPath("gwenview-remote-tests"); + + if (KIO::NetAccess::exists(baseUrl, KIO::NetAccess::DestinationSide, authWindow)) { + KIO::NetAccess::del(baseUrl, authWindow); + } + ok = KIO::NetAccess::mkdir(baseUrl, authWindow); + if (!ok) { + kFatal() << "Could not create dir" << baseUrl << ":" << KIO::NetAccess::lastErrorString(); + return KUrl(); + } + + if (!testFile.isEmpty()) { + KUrl dstUrl = baseUrl; + dstUrl.addPath(testFile); + ok = KIO::NetAccess::file_copy(urlForTestFile(testFile), dstUrl, authWindow); + if (!ok) { + kFatal() << "Could not copy" << testFile << "to" << dstUrl << ":" << KIO::NetAccess::lastErrorString(); + return KUrl(); + } + } + + return baseUrl; +} + +void createEmptyFile(const QString& path) +{ + QVERIFY(!QFile::exists(path)); + QFile file(path); + bool ok = file.open(QIODevice::WriteOnly); + QVERIFY(ok); +} + +void waitForDeferredDeletes() +{ + while (QCoreApplication::hasPendingEvents()) { + QCoreApplication::sendPostedEvents(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + } +} + +namespace TestUtils +{ + +void purgeUserConfiguration() +{ + QString confDir = qgetenv("KDEHOME"); + QVERIFY(confDir.endsWith(".kde-unit-test")); // Better safe than sorry + if (QFileInfo(confDir).isDir()) { + bool ok = KIO::NetAccess::del(KUrl::fromPath(confDir), 0); + QVERIFY(ok); + } + + QFile kdebugrc(KStandardDirs::locateLocal("config", "kdebugrc")); + QVERIFY(kdebugrc.open(QIODevice::WriteOnly)); + kdebugrc.write( + "DisableAll=true\n" + "InfoOutput=4\n" + "[gwenview]\n" + "InfoOutput=2\n" + "[gwenview_importer]\n" + "InfoOutput=2\n" + ); + kClearDebugConfig(); +} + +static QImage simplifyFormats(const QImage& img) +{ + switch (img.format()) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32_Premultiplied: + return img.convertToFormat(QImage::Format_ARGB32); + default: + return img; + } +} + +inline bool fuzzyColorComponentCompare(int c1, int c2, int delta) +{ + return qAbs(c1 - c2) < delta; +} + +bool fuzzyImageCompare(const QImage& img1_, const QImage& img2_, int delta) +{ + if (img1_.size() != img2_.size()) { + kWarning() << "Different sizes" << img1_.size() << "!=" << img2_.size(); + return false; + } + QImage img1 = simplifyFormats(img1_); + QImage img2 = simplifyFormats(img2_); + if (img1.format() != img2.format()) { + kWarning() << "Different formats" << img1.format() << "!=" << img2.format(); + return false; + } + + for (int posY = 0; posY < img1.height(); ++posY) { + for (int posX = 0; posX < img2.width(); ++posX) { + QColor col1 = img1.pixel(posX, posY); + QColor col2 = img2.pixel(posX, posY); + bool ok = + fuzzyColorComponentCompare(col1.red(), col2.red(), delta) + && fuzzyColorComponentCompare(col1.green(), col2.green(), delta) + && fuzzyColorComponentCompare(col1.blue(), col2.blue(), delta) + && fuzzyColorComponentCompare(col1.alpha(), col2.alpha(), delta); + if (!ok) { + kWarning() << "Different at" << QPoint(posX, posY) << col1.name() << "!=" << col2.name(); + return false; + } + } + } + return true; +} + +bool imageCompare(const QImage& img1, const QImage& img2) +{ + return fuzzyImageCompare(img1, img2, 1); +} + +SandBoxDir::SandBoxDir() +: mTempDir(QDir::currentPath() + "/sandbox-") +{ + setPath(mTempDir.name()); +} + +void SandBoxDir::fill(const QStringList& filePaths) +{ + Q_FOREACH(const QString& filePath, filePaths) { + QFileInfo info(*this, filePath); + mkpath(info.absolutePath()); + createEmptyFile(info.absoluteFilePath()); + } +} + +TimedEventLoop::TimedEventLoop(int maxDuration) +: mTimer(new QTimer(this)) +{ + mTimer->setSingleShot(true); + mTimer->setInterval(maxDuration * 1000); + connect(mTimer, SIGNAL(timeout()), SLOT(fail())); +} + +int TimedEventLoop::exec(ProcessEventsFlags flags) +{ + mTimer->start(); + return QEventLoop::exec(flags); +} + +void TimedEventLoop::fail() +{ + if (isRunning()) { + qFatal("TimedEventLoop has been running for %d seconds. Aborting.", mTimer->interval() / 1000); + exit(1); + } +} + +} // namespace TestUtils diff --git a/gwenview/tests/auto/testutils.h b/gwenview/tests/auto/testutils.h new file mode 100644 index 00000000..a549560f --- /dev/null +++ b/gwenview/tests/auto/testutils.h @@ -0,0 +1,130 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef TESTUTILS_H +#define TESTUTILS_H + +#include + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include + +/* + * This file contains simple helpers to access test files + */ + +inline QString pathForTestFile(const QString& name) +{ + return QDir::cleanPath(QString("%1/../data/%2").arg(KDESRCDIR).arg(name)); +} + +inline KUrl urlForTestFile(const QString& name) +{ + KUrl url; + url.setPath(pathForTestFile(name)); + return url; +} + +inline QString pathForTestOutputFile(const QString& name) +{ + return QString("%1/%2").arg(QDir::currentPath()).arg(name); +} + +inline KUrl urlForTestOutputFile(const QString& name) +{ + KUrl url; + url.setPath(pathForTestOutputFile(name)); + return url; +} + +inline bool waitForSignal(const QSignalSpy& spy, int timeout = 5) +{ + for (int x = 0; x < timeout; ++x) { + if (spy.count() > 0) { + return true; + } + QTest::qWait(1000); + } + return false; +} + +void createEmptyFile(const QString& path); + +/** + * Returns the url of the remote url dir if remote test dir was successfully + * set up. + * If testFile is valid, it is copied into the test dir. + */ +KUrl setUpRemoteTestDir(const QString& testFile = QString()); + +/** + * Make sure all objects on which deleteLater() have been called have been + * destroyed. + */ +void waitForDeferredDeletes(); + +// FIXME: Top-level functions should move to the TestUtils namespace +namespace TestUtils +{ + +bool fuzzyImageCompare(const QImage& img1, const QImage& img2, int delta = 2); + +bool imageCompare(const QImage& img1, const QImage& img2); + +void purgeUserConfiguration(); + +class SandBoxDir : public QDir +{ +public: + SandBoxDir(); + void fill(const QStringList& files); + +private: + KTempDir mTempDir; +}; + +/** + * An event loop which stops itself after a predefined duration + */ +class TimedEventLoop : public QEventLoop +{ + Q_OBJECT +public: + TimedEventLoop(int maxDurationInSeconds = 60); + + int exec(ProcessEventsFlags flags = AllEvents); + +private Q_SLOTS: + void fail(); + +private: + QTimer *mTimer; +}; + +} // namespace + +#endif /* TESTUTILS_H */ diff --git a/gwenview/tests/auto/thumbnailprovidertest.cpp b/gwenview/tests/auto/thumbnailprovidertest.cpp new file mode 100644 index 00000000..a37517cf --- /dev/null +++ b/gwenview/tests/auto/thumbnailprovidertest.cpp @@ -0,0 +1,278 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "thumbnailprovidertest.moc" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include + +// Local +#include "../lib/imageformats/imageformats.h" +#include "../lib/thumbnailprovider/thumbnailprovider.h" +#include "testutils.h" + +// libc +#include +#include + +using namespace Gwenview; + +QTEST_KDEMAIN(ThumbnailProviderTest, GUI) + +SandBox::SandBox() +: mPath(QDir::currentPath() + "/sandbox") +{} + +void SandBox::initDir() +{ + KIO::Job* job; + QDir dir(mPath); + if (dir.exists()) { + KUrl sandBoxUrl("file://" + mPath); + job = KIO::del(sandBoxUrl); + QVERIFY2(job->exec(), "Couldn't delete sandbox"); + } + dir.mkpath("."); +} + +void SandBox::fill() +{ + initDir(); + createTestImage("red.png", 300, 200, Qt::red); + createTestImage("blue.png", 200, 300, Qt::blue); + createTestImage("small.png", 50, 50, Qt::green); + + copyTestImage("orient6.jpg", 128, 256); + copyTestImage("orient6-small.jpg", 32, 64); +} + +void SandBox::copyTestImage(const QString& testFileName, int width, int height) +{ + QString testPath = pathForTestFile(testFileName); + KIO::Job* job = KIO::copy(testPath, KUrl(mPath + '/' + testFileName)); + QVERIFY2(job->exec(), "Couldn't copy test image"); + mSizeHash.insert(testFileName, QSize(width, height)); +} + +static QImage createColoredImage(int width, int height, const QColor& color) +{ + QImage image(width, height, QImage::Format_RGB32); + QPainter painter(&image); + painter.fillRect(image.rect(), color); + return image; +} + +void SandBox::createTestImage(const QString& name, int width, int height, const QColor& color) +{ + QImage image = createColoredImage(width, height, color); + image.save(mPath + '/' + name, "png"); + mSizeHash.insert(name, QSize(width, height)); +} + +void ThumbnailProviderTest::initTestCase() +{ + qRegisterMetaType("KFileItem"); + Gwenview::ImageFormats::registerPlugins(); +} + +void ThumbnailProviderTest::init() +{ + ThumbnailProvider::setThumbnailBaseDir(mSandBox.mPath + "/thumbnails/"); + mSandBox.fill(); +} + +static void syncRun(ThumbnailProvider *provider) +{ + QEventLoop loop; + QObject::connect(provider, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); +} + +void ThumbnailProviderTest::testLoadLocal() +{ + QDir dir(mSandBox.mPath); + + // Create a list of items which will be thumbnailed + KFileItemList list; + Q_FOREACH(const QFileInfo & info, dir.entryInfoList(QDir::Files)) { + KUrl url("file://" + info.absoluteFilePath()); + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url); + list << item; + } + + // Generate the thumbnails + ThumbnailProvider provider; + provider.setThumbnailGroup(ThumbnailGroup::Normal); + provider.appendItems(list); + QSignalSpy spy(&provider, SIGNAL(thumbnailLoaded(KFileItem,QPixmap,QSize,qulonglong))); + syncRun(&provider); + while (!ThumbnailProvider::isThumbnailWriterEmpty()) { + QTest::qWait(100); + } + + // Check we generated the correct number of thumbnails + QDir thumbnailDir = ThumbnailProvider::thumbnailBaseDir(ThumbnailGroup::Normal); + // There should be one file less because small.png is a png and is too + // small to have a thumbnail + QStringList entryList = thumbnailDir.entryList(QStringList("*.png")); + QCOMPARE(entryList.count(), mSandBox.mSizeHash.size() - 1); + + // Check thumbnail keys + QHash > thumbnailHash; + Q_FOREACH(const QString& name, entryList) { + QImage thumb; + QVERIFY(thumb.load(thumbnailDir.filePath(name))); + + KUrl url(thumb.text("Thumb::URI")); + KFileItem item = list.findByUrl(url); + QVERIFY(!item.isNull()); + + QSize originalSize = mSandBox.mSizeHash.value(item.url().fileName()); + uint mtime = item.time(KFileItem::ModificationTime).toTime_t(); + + if (mtime == uint(-1)) { + // This happens from time to time on build.kde.org, but I haven't + // been able to reproduce it locally, so let's try to gather more + // information. + kWarning() << "mtime == -1 for url" << url << ". This should not happen!"; + kWarning() << "errno:" << errno << "message:" << strerror(errno); + kWarning() << "QFile::exists(" << url.toLocalFile() << "):" << QFile::exists(url.toLocalFile()); + kWarning() << "Recalculating mtime" << item.time(KFileItem::ModificationTime).toTime_t(); + QFAIL("Invalid time for test KFileItem"); + } + + QCOMPARE(thumb.text("Thumb::Image::Width"), QString::number(originalSize.width())); + QCOMPARE(thumb.text("Thumb::Image::Height"), QString::number(originalSize.height())); + QCOMPARE(thumb.text("Thumb::Mimetype"), item.mimetype()); + QCOMPARE(thumb.text("Thumb::Size"), QString::number(item.size())); + QCOMPARE(thumb.text("Thumb::MTime"), QString::number(mtime)); + } + + // Check what was in the thumbnailLoaded() signals + QCOMPARE(spy.count(), mSandBox.mSizeHash.size()); + QSignalSpy::ConstIterator it = spy.constBegin(), + end = spy.constEnd(); + for (; it != end; ++it) { + const QVariantList args = *it; + const KFileItem item = qvariant_cast(args.at(0)); + const QSize size = args.at(2).toSize(); + const QSize expectedSize = mSandBox.mSizeHash.value(item.url().fileName()); + QCOMPARE(size, expectedSize); + } +} + +void ThumbnailProviderTest::testUseEmbeddedOrNot() +{ + QImage expectedThumbnail; + QPixmap thumbnailPix; + SandBox sandBox; + sandBox.initDir(); + // This image is red (0xfe0000) and 256x128 but contains a white 128x64 thumbnail + sandBox.copyTestImage("embedded-thumbnail.jpg", 256, 128); + + KFileItemList list; + KUrl url("file://" + QDir(sandBox.mPath).absoluteFilePath("embedded-thumbnail.jpg")); + list << KFileItem(KFileItem::Unknown, KFileItem::Unknown, url); + + // Loading a normal thumbnail should bring the white one + { + ThumbnailProvider provider; + provider.setThumbnailGroup(ThumbnailGroup::Normal); + provider.appendItems(list); + QSignalSpy spy(&provider, SIGNAL(thumbnailLoaded(KFileItem,QPixmap,QSize,qulonglong))); + syncRun(&provider); + + QCOMPARE(spy.count(), 1); + expectedThumbnail = createColoredImage(128, 64, Qt::white); + thumbnailPix = qvariant_cast(spy.at(0).at(1)); + QVERIFY(TestUtils::imageCompare(expectedThumbnail, thumbnailPix.toImage())); + } + + // Loading a large thumbnail should bring the red one + { + ThumbnailProvider provider; + provider.setThumbnailGroup(ThumbnailGroup::Large); + provider.appendItems(list); + QSignalSpy spy(&provider, SIGNAL(thumbnailLoaded(KFileItem,QPixmap,QSize,qulonglong))); + syncRun(&provider); + + QCOMPARE(spy.count(), 1); + expectedThumbnail = createColoredImage(256, 128, QColor(254, 0, 0)); + thumbnailPix = qvariant_cast(spy.at(0).at(1)); + QVERIFY(TestUtils::imageCompare(expectedThumbnail, thumbnailPix.toImage())); + } +} + +void ThumbnailProviderTest::testLoadRemote() +{ + KUrl url = setUpRemoteTestDir("test.png"); + if (!url.isValid()) { + return; + } + url.addPath("test.png"); + + KFileItemList list; + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url); + list << item; + + ThumbnailProvider provider; + provider.setThumbnailGroup(ThumbnailGroup::Normal); + provider.appendItems(list); + syncRun(&provider); + while (!ThumbnailProvider::isThumbnailWriterEmpty()) { + QTest::qWait(100); + } + + QDir thumbnailDir = ThumbnailProvider::thumbnailBaseDir(ThumbnailGroup::Normal); + QStringList entryList = thumbnailDir.entryList(QStringList("*.png")); + QCOMPARE(entryList.count(), 1); +} + +void ThumbnailProviderTest::testRemoveItemsWhileGenerating() +{ + QDir dir(mSandBox.mPath); + + // Create a list of items which will be thumbnailed + KFileItemList list; + Q_FOREACH(const QFileInfo & info, dir.entryInfoList(QDir::Files)) { + KUrl url("file://" + info.absoluteFilePath()); + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url); + list << item; + } + + // Start generating thumbnails for items + ThumbnailProvider provider; + provider.setThumbnailGroup(ThumbnailGroup::Normal); + provider.appendItems(list); + QEventLoop loop; + connect(&provider, SIGNAL(finished()), &loop, SLOT(quit())); + + // Remove items, it should not crash + provider.removeItems(list); + loop.exec(); +} diff --git a/gwenview/tests/auto/thumbnailprovidertest.h b/gwenview/tests/auto/thumbnailprovidertest.h new file mode 100644 index 00000000..f5512f95 --- /dev/null +++ b/gwenview/tests/auto/thumbnailprovidertest.h @@ -0,0 +1,58 @@ +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef THUMBNAILPROVIDERTEST_H +#define THUMBNAILPROVIDERTEST_H + +// Qt +#include +#include +#include +#include + +class SandBox +{ +public: + SandBox(); + void initDir(); + void fill(); + void createTestImage(const QString& name, int width, int height, const QColor& color); + void copyTestImage(const QString& name, int width, int height); + + QHash mSizeHash; + QString mPath; +}; + +class ThumbnailProviderTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void init(); + void initTestCase(); + void testLoadLocal(); + void testLoadRemote(); + void testUseEmbeddedOrNot(); + void testRemoveItemsWhileGenerating(); + +private: + SandBox mSandBox; +}; + +#endif // THUMBNAILPROVIDERTEST_H diff --git a/gwenview/tests/auto/timeutilstest.cpp b/gwenview/tests/auto/timeutilstest.cpp new file mode 100644 index 00000000..9ac3f097 --- /dev/null +++ b/gwenview/tests/auto/timeutilstest.cpp @@ -0,0 +1,92 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#include "timeutilstest.moc" + +// libc +#include + +// KDE +#include +#include +#include + +// Local +#include "../lib/timeutils.h" + +#include "testutils.h" + +QTEST_KDEMAIN(TimeUtilsTest, GUI) + +using namespace Gwenview; + +static void touchFile(const QString& path) +{ + utime(QFile::encodeName(path).data(), 0); +} + +#define NEW_ROW(fileName, dateTime) QTest::newRow(fileName) << fileName << dateTime +void TimeUtilsTest::testBasic_data() +{ + QTest::addColumn("fileName"); + QTest::addColumn("expectedDateTime"); + + NEW_ROW("date/exif-datetimeoriginal.jpg", KDateTime::fromString("2003-03-10T17:45:21")); + NEW_ROW("date/exif-datetime-only.jpg", KDateTime::fromString("2003-03-25T02:02:21")); + + KUrl url = urlForTestFile("test.png"); + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url); + NEW_ROW("test.png", item.time(KFileItem::ModificationTime)); +} + +void TimeUtilsTest::testBasic() +{ + QFETCH(QString, fileName); + QFETCH(KDateTime, expectedDateTime); + KDateTime dateTime; + KUrl url = urlForTestFile(fileName); + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url); + + dateTime = TimeUtils::dateTimeForFileItem(item); + QCOMPARE(dateTime, expectedDateTime); + + dateTime = TimeUtils::dateTimeForFileItem(item, TimeUtils::SkipCache); + QCOMPARE(dateTime, expectedDateTime); +} + +void TimeUtilsTest::testCache() +{ + KTemporaryFile tempFile; + QVERIFY(tempFile.open()); + KUrl url = KUrl::fromLocalFile(tempFile.fileName()); + KFileItem item1(KFileItem::Unknown, KFileItem::Unknown, url); + KDateTime dateTime1 = TimeUtils::dateTimeForFileItem(item1); + QCOMPARE(dateTime1, item1.time(KFileItem::ModificationTime)); + + QTest::qWait(1200); + touchFile(url.toLocalFile()); + + KFileItem item2(KFileItem::Unknown, KFileItem::Unknown, url); + KDateTime dateTime2 = TimeUtils::dateTimeForFileItem(item2); + + QVERIFY(dateTime1 != dateTime2); + + QCOMPARE(dateTime2, item2.time(KFileItem::ModificationTime)); +} diff --git a/gwenview/tests/auto/timeutilstest.h b/gwenview/tests/auto/timeutilstest.h new file mode 100644 index 00000000..33bb88a0 --- /dev/null +++ b/gwenview/tests/auto/timeutilstest.h @@ -0,0 +1,36 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef TIMEUTILSTEST_H +#define TIMEUTILSTEST_H + +// Qt +#include + +class TimeUtilsTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testBasic(); + void testBasic_data(); + void testCache(); +}; + +#endif /* TIMEUTILSTEST_H */ diff --git a/gwenview/tests/auto/transformimageoperationtest.cpp b/gwenview/tests/auto/transformimageoperationtest.cpp new file mode 100644 index 00000000..7528a5ad --- /dev/null +++ b/gwenview/tests/auto/transformimageoperationtest.cpp @@ -0,0 +1,69 @@ +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +// Qt +#include +#include + +// KDE +#include +#include + +// Local +#include "../lib/document/documentfactory.h" +#include "../lib/imageutils.h" +#include "../lib/transformimageoperation.h" +#include "testutils.h" + +#include "transformimageoperationtest.moc" + +QTEST_KDEMAIN(TransformImageOperationTest, GUI) + +using namespace Gwenview; + +void TransformImageOperationTest::initTestCase() +{ + qRegisterMetaType("KUrl"); +} + +void TransformImageOperationTest::init() +{ + DocumentFactory::instance()->clearCache(); +} + +void TransformImageOperationTest::testRotate90() +{ + KUrl url = urlForTestFile("test.png"); + QImage image; + + bool ok = image.load(url.toLocalFile()); + QVERIFY2(ok, "Could not load 'test.png'"); + QMatrix matrix = ImageUtils::transformMatrix(ROT_90); + image = image.transformed(matrix); + + Document::Ptr doc = DocumentFactory::instance()->load(url); + + TransformImageOperation* op = new TransformImageOperation(ROT_90); + QEventLoop loop; + connect(doc.data(), SIGNAL(allTasksDone()), &loop, SLOT(quit())); + op->applyToDocument(doc); + loop.exec(); + + QCOMPARE(image, doc->image()); +} diff --git a/gwenview/tests/auto/transformimageoperationtest.h b/gwenview/tests/auto/transformimageoperationtest.h new file mode 100644 index 00000000..14aecfc7 --- /dev/null +++ b/gwenview/tests/auto/transformimageoperationtest.h @@ -0,0 +1,39 @@ +/* +Gwenview: an image viewer +Copyright 2010 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef TRANSFORMIMAGEOPERATIONTEST_H +#define TRANSFORMIMAGEOPERATIONTEST_H + +// Qt +#include + +// Local +#include "../lib/document/document.h" + +class TransformImageOperationTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testRotate90(); + void initTestCase(); + void init(); +}; + +#endif // TRANSFORMIMAGEOPERATIONTEST_H diff --git a/gwenview/tests/auto/urlutilstest.cpp b/gwenview/tests/auto/urlutilstest.cpp new file mode 100644 index 00000000..4cee4801 --- /dev/null +++ b/gwenview/tests/auto/urlutilstest.cpp @@ -0,0 +1,57 @@ +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#include "urlutilstest.moc" + +// Qt + +// KDE +#include +#include + +// Local +#include "../lib/urlutils.h" + +QTEST_KDEMAIN(UrlUtilsTest, GUI) + +using namespace Gwenview; + +void UrlUtilsTest::testFixUserEnteredUrl() +{ + QFETCH(KUrl, in); + QFETCH(KUrl, expected); + KUrl out = UrlUtils::fixUserEnteredUrl(in); + QCOMPARE(out.url(), expected.url()); +} + +#define NEW_ROW(in, expected) QTest::newRow(QString(in).toLocal8Bit().data()) << KUrl(in) << KUrl(expected) +void UrlUtilsTest::testFixUserEnteredUrl_data() +{ + QTest::addColumn("in"); + QTest::addColumn("expected"); + + QString pwd = QDir::currentPath(); + + NEW_ROW("http://example.com", "http://example.com"); + NEW_ROW("file://" + pwd + "/example.zip", "zip:" + pwd + "/example.zip"); + NEW_ROW("file://" + pwd + "/example.cbz", "zip:" + pwd + "/example.cbz"); + NEW_ROW("file://" + pwd + "/example.jpg", "file://" + pwd + "/example.jpg"); + // Check it does not get turned into gzip://... + NEW_ROW("file://" + pwd + "/example.svgz", "file://" + pwd + "/example.svgz"); +} diff --git a/gwenview/tests/auto/urlutilstest.h b/gwenview/tests/auto/urlutilstest.h new file mode 100644 index 00000000..bdcd14ec --- /dev/null +++ b/gwenview/tests/auto/urlutilstest.h @@ -0,0 +1,35 @@ +/* +Gwenview: an image viewer +Copyright 2009 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ +#ifndef URLUTILSTEST_H +#define URLUTILSTEST_H + +// Qt +#include + +class UrlUtilsTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testFixUserEnteredUrl(); + void testFixUserEnteredUrl_data(); +}; + +#endif /* URLUTILSTEST_H */ diff --git a/gwenview/tests/data/.gitignore b/gwenview/tests/data/.gitignore new file mode 100644 index 00000000..3210aa92 --- /dev/null +++ b/gwenview/tests/data/.gitignore @@ -0,0 +1,3 @@ +# Skip raw data +*.nef +*.CR2 diff --git a/gwenview/tests/data/160216_no_size_before_decoding.eps b/gwenview/tests/data/160216_no_size_before_decoding.eps new file mode 100644 index 00000000..1a019190 --- /dev/null +++ b/gwenview/tests/data/160216_no_size_before_decoding.eps @@ -0,0 +1,338 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: GIMP PostScript file plugin V 1.17 by Peter Kirchgessner +%%Title: GwenviewCrashSample.eps +%%CreationDate: Tue Apr 1 11:29:17 2008 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%Pages: 1 +%%BoundingBox: 14 14 115 115 +%%EndComments +%%BeginProlog +% Use own dictionary to avoid conflicts +10 dict begin +%%EndProlog +%%Page: 1 1 +% Translate for offset +14.173228346456694 14.173228346456694 translate +% Translate to begin of first scanline +0 99.999999999999986 translate +99.999999999999986 -99.999999999999986 scale +% Image geometry +100 100 8 +% Transformation matrix +[ 100 0 0 100 0 0 ] +% Strings to hold RGB-samples per scanline +/rstr 100 string def +/gstr 100 string def +/bstr 100 string def +{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop} +{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop} +{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop} +true 3 +%%BeginData: 2111 ASCII Bytes +colorimage +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +SH+0~> +SH+0~> +Sc=3~> +%%EndData +showpage +%%Trailer +end +%%EOF diff --git a/gwenview/tests/data/160382_corrupted.jpeg b/gwenview/tests/data/160382_corrupted.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..cf04e82aba185f007112634d3798587ccedabf9b GIT binary patch literal 11069 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 MmwBvxpZE6v3rBNqBLDyZ literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/185523_1frame_with_graphic_control_extension.gif b/gwenview/tests/data/185523_1frame_with_graphic_control_extension.gif new file mode 100644 index 0000000000000000000000000000000000000000..a98e327c41a14bc0d96825954a5b837041065ece GIT binary patch literal 77 zcmZ?wbhEHb6k*_J_`tyM|Nnmm1_s4{g3d*$i6yBi3gww484B*6z5xu1KUo-T82&Tp f02MHRlru2#xAd<({g!|6oGrJyH{aXCz+epkkdqkZ literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/188191_does_not_load.tga b/gwenview/tests/data/188191_does_not_load.tga new file mode 100644 index 0000000000000000000000000000000000000000..57c19c8928ebf00291f630d12b65ee764dd1c3e9 GIT binary patch literal 1013 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 literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/1x10k.jpg b/gwenview/tests/data/1x10k.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aec11c176ba7434d699308265c9d740e2efc6834 GIT binary patch literal 1269 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<8LvnOK-vSy@`H|qMvW5}awt1(JSZA;@q>zSQc)8pmzcPOq?D?fx`w8fiK&^ng{76Vi>sTv zho@I?NN8AiL}XNQN@`kqMrKxVNoiSmMP*fUOKV$uM`zch$y26In?7UatVN5LEM2yI z#mZHiHgDOwZTpU$yAB;ba`f2o6DLnyx_ss8wd*%--g@}x@sp>|p1*kc>f@)+U%r0( i{^RGb|F;-8K>o33`1!9(W6>xY4S~@R80;bN|0VznvkKS% literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/1x10k.png b/gwenview/tests/data/1x10k.png new file mode 100644 index 0000000000000000000000000000000000000000..ca7dd26a712a0c8da108e5f8c39179e10365bc1e GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^j0_Cw0*uT+)^qQMXF!TMz$e5NNH4Fly#=H=3p^r= z85sDEfH31!Z9ZwBpq8hLV@SoVw+9V@+8GWh{9Yc%%bx&bqJtO?KLrNHWtTs6gJe8i L{an^LB{Ts5^SK++ literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/289819_does_not_load.png b/gwenview/tests/data/289819_does_not_load.png new file mode 100644 index 0000000000000000000000000000000000000000..fb7b57539e8ea640fbac59de8ecce95c2be78273 GIT binary patch literal 15455 zcmeI&v2DUY6b9gDBP3+WO_YjIWDpvVBXDCOlmRHyXM-pW4FwB$11~{GLB|l8AwrIC zjL6?#+`Zy8|JU9fcg?0O=0!x5^|snaq%rbYKAbP7r|WQKpZm=^y4&I>;x+1O_5Lx< z^5x@_^?jeVg#ZBp1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7{<%PUB$n|BwBPMK zVo|2Q0OM<~F#!Su2oNAZfB*pk1PBlyK!5-N0t5&UAV7csfe{E4=SjAF{=KKF^;=VQ H>%-R{?wJ?R literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/302350_exiv_0.23_exception.jpg b/gwenview/tests/data/302350_exiv_0.23_exception.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e3e94c34a0710736e3edf614cc580d379fbb783b GIT binary patch literal 122659 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�r2KAFFbMc@S11{N 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 literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/40frames.gif b/gwenview/tests/data/40frames.gif new file mode 100644 index 0000000000000000000000000000000000000000..cf866b92b35254da873b34db504ca809b3110042 GIT binary patch literal 4049 zcmZ?wbhEHb)L@WfSjfl#1pi?`@jthpYe=xOV}PrXo&hsRQt>AX0~f=81|5(>kQN3e zi=O_Kr{D4~p0njv_vU*76>^mfH)@wC&s(ulFZ1;#gHPwSJ7#EB=o zwWgn0_Bm4nY#1XK0~4}g&cqqk&cPB@wtqq44@0fy6BmnIylnkl=6&rto3%a0g%RXP zpt;P*<_4mg`{Nbk1NYX&5tAm*T(nwi<9g}C6JsuKzx$DY-{r3@_Vbx5I5ejpVKX|= zV6AbpR2Dh|Fb&mJ|SbaYH|1Viyuquq;)gBI(M0MU+M2Ip3!t1YD*UrHm{6YG8$Z< xjP_$Rb&cke(P9O&tRF2PM+?Z&0&=t=G1~6BKH7pDZEz!X)kbT^kx(;Q0|24dE&c!i literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/4frames.gif b/gwenview/tests/data/4frames.gif new file mode 100644 index 0000000000000000000000000000000000000000..927fb54d8298a83df1f7f4f7972d7bfb49a0a034 GIT binary patch literal 434 zcmZ?wbhEHb)L@WfSjfl#1pi?`@jthpYe=xOV}PrXo&hsRQt_Xlb5UwyNotBhd1gt5 zg1e`00E6OB76vYc{|q`H-5_%qm@InwSDt>$zj)4;Tiu)Q2~@~cGTf+LqC9WKO1;e2 zn+!gk+mgdxy6*9}dCdQ6j1woG@Yb4sX4&UV4WJD_Ss1w(n2-%~CeE;S4wk60{R;|z z7-}`2xLD-kW$W)U?`zlDtnD!_j8Jo#klZ(k*h%YVdUftH T>%P+8T|A@dIMkLdCI)K&bz`^! literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/cms/Lower_Left.jpg b/gwenview/tests/data/cms/Lower_Left.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0984e9b3fd734a28f5b2d1698aab2f39d774c91a GIT binary patch literal 43834 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%0A}QtUYdUZ;O^dPzcyIj=(2od3atinPS5 z)&t73_&-#P(w3{iPXD{hPjc_`d84_K2mQu;B6*3)Bp{n8sysM{7wjoCTcR9$Hu7Mf z-@^<`>z6J#D5YO2UhorN$BJ6}(dOm`@jazxEhsEu+Nm`##0DL6de(vjOl^#3Y~J-d zH1O$!<)MQ9$K2dq&=ziD!X)2pVO#Mi39vC;GreuT%R>7$6N{#AX(KTbHPnb6TJ%oN zzL!)Fr({|`#~)EzCUQTb&Hv2s!99Hgwy-jEX=zmYCd*;uEii?%y@sxt3we`Od2G}3 zJc0IQ*n3msi2K-$^5O^{ec^$VmW@NTr#2SZ#h6ARHM45kDAdD`?e9>!jPd;mn5F?< zh;yzi>dDoRLnyUcbYV?~Uqb6{Xp>07rx?XvtUe&Oze-B_Fk&;r z9BUp&Q(1kGgWFg0a$#Tj=H+zfDa$6s_RZ5XJ)VksA1_wi;T<=La5{rvL?;jr@v}st!jKjo6qppaK#IhxF2<2MMEfr(2n#d zTiT2x+U%5EdJtf@GHxn%$Q}H|n`z>6Sd|}iE-e``c`3AT&}`M4h_LPF6wB^v*`@h_ zY1DQ2V^raIMb(nZ!W zt}2u^8oPpT*Lu>rJdCjH(DZufQ^G-}Ob)t_ESVE0WVA$Mx~mrcRo}iB9?< z5f{lt&Bbs%%yovxKi2im8XXugbqi(XL2v_J9v-JT|iCy zzPvw|oKRZ^pB@@vJz=>fsYG9zln~&L_MOHz9vb!-C#=K`O@*~MtS42a;Byw1&x1uaHqR~McOB-oxd#L88tmec4%xojS+K5ies zzqISl+x}OQCoMru6z=7Caz15VZhYL#!;?wnMP})4i@t!EDM_=v2(R-48&zWX4`!?V z$OR8{@;n`ri2Pi4#s@pk>ka#j(kMXyGgN(Q*Mof^JOdQvo z8u;Sp>_SP~ncmiPDO?{Q0oGj;ddxIE8-jKfu{N8DYddu)7rZ+i#Bqlt*E#zuD+l^Y zQ>RK4>zfb>;=f_?TjURatodMM=;HMW#pz>XJT|Nyk#Ce`w|uF@_T}SjWfA}-0lGx3 z6@LIt7X740jO+4)%Qb3CBtWlW#p<_Cb1}rw6lOWTTLK7hn(@YGRMwzw zMsusW2s3HRRgPS>tn&`Ug8fW!)ScDW80T3&i(#u}73S#!B;cy{Q?dIZEvUS#kXft9 zJzZkR3tj&5F5}LRr+Yu%8r+OqKHy_P97f+{K2~q79ZY|9E!1khcvcpyQ@b{(Fp0LO zBwQJ@{12=)Xk)HyR9(U%)MWJ>8Bg{CCR|ef2i|6&QGdAQ5c(kQK~;WMg^!DEPS9-C%Q9HkOD;J=y<} z=GyaTd3~=2wfdgLNf*t(%;#TtIhJU%8JSxxo7R1=05P^|iSqwF!Z~-o5pYdP#KZ9$ zxaHF;*?IIAb z+!atMbitMlZ!&il58PH5=)h*(4|Ce&S+!oWBvfeUp^{M}qCOi{qo=$4>^7M{)|k6i z66bO&M+w)&TtXv?ru`4nTiY#yT{5-JVXM&t`Dsq3OD=v~rW%N=)oJxAjR}_Yk8juz zjn>g`%*(xt%KUu8J2+@5Xgb!2c-^(?m1>Pcj;!@w>%06p$I?)DIFGOMvGL5#;{u0k zmVFlot%zbo)Mp&NAPu!|%>E~Do7YEZUTd|wa7k8}KJp}q1TC0gq78?LhD znza?{slJx09>32!#Evv$?^$cE4Zgw4M_}eRNkF@aa(wL{)5CT}uQDp-jdI?G&Myy^ zb#Ho5Ej}ki?yO@*$(?|)L2Ka4tHmQty)_^C9tZ8CCrq_)ul5tVsQW67a#|DbwEO0q zo?hJG2}sGAgH_yytcX}=>fG=!v+4~4F7e$!uIxS;nJ`*!1(Iy?+J^ZoeT@EXJ*epq4TAqoB88}F_ zns{*)tn;Q1ZHO+Nnh#%GS5@?q3{$0Vs&a_lOSm!bJSED}!`USJI4?GiqW`ggf1ke9 z{7`!*q19<)^KM=MeG#E>L8fp)Yiw0yMXk$KJ-H80$eBf-CiUifE*Zf9}G?ky@yX&qBZ9{(W2F$hQcf}b6IZ2jfI(DS$fWO8QY(9lM5_R600fK$8% z$T;fGxeHq5Nv#k4)@(9Lz=GydzKcrrH`>4hIebAJQ~F&g$`wx?F# zBlxPLme=t0Bw*O7e)pr2Fg_jq()9o$3nQ@@v(p`wmuU1xUcTPW^{8XK7=0TX8}KhE=yC6=*hd*3(| z(APd|+j`CSK4K#!eX}y#+&e)mtu}F#5UVsXIS#*p9r75MJ~=lSNdk_iWPVzl*$~__A1GpQ()s2f6<3!iVH3^-j!;>phO0G*9(MzSy;{iSj9~ z!gh>T4X@A9q#>>uzbKwlzm8+hdgtX$=tt;g+km5487ppJ?H$^dhL+=O@u#smnKL^w ze>@*S&nu@q)~E*7FnRdKobJ9$k$%bZQOZuY!~6m<&&Oyov9oH*#G|g#2Q2uGV-8}J zTNXToGx#u#?k^uz=Z)4Z`O6fI#|$4tdnJzeLTA;C=ku6GZDk+FZlq*_!($wI#&7Rj zk*|K7XemIs0rSeMnr!#li~>hvgK`xW*Ur3__oE+~%dGMrf3y~G<)NWr|D)H0s+WO&;ZCoZxB?&A)l8J(8%2`q7;X4G zo)u<3j$D(m>{rZ#}M|JWSIA8$&aUt4FK_b6JXaqMUS}8os8- z!qfL;oRg+i4ezvU(0b|N5ns%jvhy_24X392_5@D)fmpr2#N8~+x@KL(Sh~-;zJGH1 zNc)spy`ReIsSd!q*ILeFZ)9KT&?<$^Ty-8$%H7e4QkzUz?8a>HeK4Btqp!?+(c+`c zm8cyVtO2XI7N(lBiTeoF)k$l4%2Joaa>^eJU+E*>KIGv7s~Vmy^O;ZXTig)+fIPK% z86A~ZYOO6#V-;Zo8KuPf_sd|H6Pjw4Q`$t;p}7y_#pjp<#LA3yR9dkm(TL$vAB5L-OtTOb#>OZw0d~|D53S!dUeWOCqq`5q2;%HwXR+Q_7?;@LGw1iPujXP+!&i;Q<4u^n_hG$zDjJGdcQ{Zb$t55 z!@{w|_ro7o*TGjHINw|-V(bbpGu->+>6>rzuy3v*eC3C2IA@1W;|vx?KP~2!fkX0n z0^AIbl{3~G+u)K{{@AD}B`DBx)~CPlqCbmfh^>mKyxYcZ%qW5cyu>Fi zmx@D#T9qWYWgp_-CBIxoZWPYj7v%*J7I7jqIuEh;*49zJ;P*{c5B?J;!SF!=b(|Z2 znBV2j;mFo1yJedy&!wIlue?8TB=g1+Mf3d7Vhrx$P-G;RPKQB7&2y*m7n``v(sWZ! z`osv!tA`{C+mzk>qCV(iM@Gj}z@h8@bIkX$h z2kD7(#bQ(hq{Jl!U`PxC>*$JcRuM2a(-Bh;P*dG;gkQ@@)6B|P8|Lojit~aQn;$iR zYr+J?BqTK4-QAEdO)T2o+Y9L_A)#fa1v55)o0`EuViFSC#|2;lE?!>lN)i&jzP{oP zWOQ)^7EMOPNf>)#-I1PN=M6xrVqgSuM=wVKFul*o{KOXsb#z5|sqUaVf3RW+@p5o8LE?@`knzE2U!wnYd@ol73h5<|@I*Rz zVLiXbp*w=}0?9l7IXMu7cEEs1f|y@0cF_Hju&Wmmt>NkEaQ-`K2eKDr42<}dwZEb9 zA7_qC1Z3S!RbKjt#Fm3h_m>MmYi%-O) zq{O5Y%p}1jD2hr_3f4y?wu1gcesNwP1syjB=O3wpMUMnaK~?gI#8>B*KqbCP^oYb) zF@x#+yZU3~NfwQR7x+?qP3&6{{iXK(-=jT$ku|cOd=;M5&vM*WAiDo81u{iAxPf)V z|7*(gr)+(zbU>}yu5-TCFaLWgV*$2A8MDv5UUvtu7k=6m;!zX8k3|F!Na z{tVvM-K`JS&!4;B>g<1{yI}Fb!MYk8=rAT%+{Ycz-@mlK*JgjI_+P7qf3vm~cSnmG zV{u-l2v1jcuuA_v*73_X_&;Cc{wj-PZT-Rs#zD*t=>-lv{lwa@E3JPS_xMKb&tpn| zpZQ;>@T+Q3QcO}_Oj^NAT3Sg~UP)5sr{RxZ3ia33;=g>j|7TUp9OLjm)1v<$pku$t z>)%w3+s*o44d4AI^!LB0S^r*d{8*+S^uMuP|36HD|D4&s(cr&n#s3Dp-ME931HZ0; z|BE%)-QDs3hX(%dviUK2$5BR-jfJLCb6LNBJ!yzTe6I;KE>UG}zhhFPnPd$ONdS5DZv#o)OAK3af z-0`b4{xzFlJC1+O<*%sxMJA7Y%j2&i{+PwD3IDl|`F#xK&sqGM!y^(~v!SXxw&s4J zvs<&e=eBlSZ|#N#=YYp3)JY4Z1t>Lz3DlWtAN14KUh%J?0~GtV_ES58F~MEk(5?OG zcoOI)@3Q`OxzGZ8A<XW z;7)Y#f_#Cb-A?!hk=}w^gU_!xvRe?b?~tuLegORd;12FK|H}(HLk|ASAku#(Y)dk< zWZD2ZuV4vLsDHk{NRbzW0$cm%Kg)-c?1jR?$LIWzy#RS{K7<|o-vJl^Sil!7Ay42m z=tcrgfENh=spQEK!0QLtR@|@q`#*>NjPMQig-Nm>bTEc%OoiwzTJmoMR4tWo3KToFi| zg_{A6O#i2JsJ6mDFdKN&Y!@$4)?)*c&J>AT-EzQ8Bz6h5r z{GTC)IA`Qf5NZcEFEa<{pI}S~Bp45Q&I^vyGczAe_zVVP(l{cW9K7AgI!5OMPHFuGI(ayM3;Z)W{TXLXtQ*$z2jQ}QrhioT zTL2gZIF7}TWliUWbvN?%!XbZDoE>hUBK-@L;SAObRH%P}(}P9q@@Mp|H*g<0A%FqA zA!_fo+@IgJom+0Q3V*Kc$QS@0`5h#SEfu`afzAm4cnLZ|ixzu8dlcIiEVeCJOs#HP zu-LX>v2DR(+k(Zm1&eJ97TXpqwk=p}Td>%+V6kn%V%vhnwgroA3l`fJEVeCJY+JC{ zwqUVs!D8Ej#kK{DZ3`CL7A&?cSZrIc*tTG?ZNXyOg2n&k7A&@A)A_;4Z~!m`Z*UzC z05Ag@fFrmp1pXhC2^hCO9XC!3lz?ZL#5M`7PQ{*>$zt$}O1C z4%k*wT1ed1J`Ql%AB>!|1Gq}ZOGVV%+tpD?Qc^)m+QCs#Ov>SmENG&^Q9;Z>TKW3(j@?R3zit?1>hw2?Mh^LbX|h-82jdpx<+A?*Bdg06&=TBbk9G4V8~9L_l9ZD8iu@z|8?x2k zApZ#ehWs7Z9OLSx3R+{?O7KtM{~6(d_1#~W`qzq(tc0*5pifDYydexpw%PLCMvJ_e zlq9%ANeMLBBB=mY5Jx2bg!whHA=c5=>HNP${&Kp|kCA`C{1zFkIKe%m4*yp4A924D z*kT=AHU?I4rugNeXvy&hK5R7ScErN_j$p!R6n;b zj#va(MSp(>6{H;Hq~#Dua%C;^H$?KA<3nJ18W_rFMK;9l$^GVPN}-0 z!F}%%XWfyY(UP>QPl zY|`sWjvB zd79`Rg;`oz!)V&Ut-Ms=WDYGr<$%DsYZx0Ffb%Z^=JWPUvh(W&PWr6&ecq|sEk>sG zHQ?X;-+^%VB=0Ew1l~f9;4B6>%>x77r+vNLK{zB3bniTa+H&6{yFJZJG(q@1(9Puh z*dAdLFcN zh{nO-7=$?RnIAG1K&gAwWXthi`5?>sGrgq~TQY=jy!a0Lo)W^_)8~s1LU9-X{v-c) z8fW4Fpezgk_6&Z938jGZbQb}j^vQP^HWl214(_#wM*roR>ulf==`2jUSXg%Pva_=D z3Ucn+#VIJTn|ulUJ4dENcCxc`?B(Fvy_@SlbLM(Gg#AxvuCr~Qx&B{z=K5?5TsX4F zOen@U=4OoXO`%Mon?j%q4my&AZs~)Mn?>+0xY?ISUjW8Er@pfNu~4+UOGZbEP`N?VK$R!c zo0}PgzW9O|{W}W+>Y6*LZ&1DFJ;%59l+QZ0#$~9Vm($ist=&{OABYX%{KI4i1h1#Z zG9hxlBTJ?r#ltU9noEh|dI_MH*C;DB|7zVuRG>86-_u&V-F_*lu54!IFDx+9wTJRsp#W>d#|P^lYGAByq1o(Vo;GWQF`9J`qj13zE_mRrs-xX{73%d@!U*bw%~YZ^ij zqPC2WCo#-iwxZX}CN%~gvLig;y)<0X;C!wkGqYcF&ePwkEcK{4@%5Cm7w6I{E0!^I^#Q=!J*dXE$WKZM)^nhyXxv9g46 zm0)!#n$yIy)^e4%(GLji=csM~EOIl(`EHDFg)>t<`BU4cvk#qfKm;k%iZkBkZ@%mX z9v5fJ&E3S9E zZ%ozNB&QJkM7o3Wx@zmK;5vCIg|3m`j)9r3{ z=d>4;Ls0P@dSZ|;qYq{)M@DF}hH2dv#G^a;+5(6@MWv7APcQ~|@B-lZFjVS((sZFE z91zM{aaaa0m&%eDk|C|f9ebNmXrDEw&ZJ}QA02{@t|w5|wA|FuDsB6W+#sq0@2{t7 zMVN*4sM5@$rz`c8jpDdI!gm} zx3s31cxxVg>L!h1gPsWN;9e{9ZYwtWXE2$w$TKumCwbaDdTu35{QwIwkImtlf&UA!_b=X+RzdI8N`*FcwZ)V2(P zlqksYQVYnC9-D@wtf@qhfWc}Y68;d>LL*AX6Ej(i_srDUrf>HIGIiYwwA5E0GmvUv zWpr7~sc5Ir)Pg<(G)$2h!7vKh!?eshOs$p=BkpK8NK#)^X!^wI^Xetx(tGiR56X2G zuv@A>rVk7r)oF*4clGEGY9AiyT~#OAe5 zps4$O;|U%C8A=ypX)d4y*=F zHF(gNQc?9-tZ_U^@Pb898f|Q-K3&nItpd+$QXoP=^uXkhOiGZ z1k?4JyG?BcF9l#xx8L$BU9zA@p(9?_A5F;B7`zO>kx2)t3jF9X6Zlb3eT43=6-J2r zg{Z$yVg+uf?~e(2n_mbnBsjw6U|N>jF`g!|6i%-!D0%ALsV;jL2ym2e_2!*$Y2FdF z>3kOOY`_h8gz{NfcyB{v?ycKv3ejkoR&8i1W5}nyeC+inP9^t6NoyUeiHd8292H#e zLb5>9p?-8i0|sl<__>{i*F@FV*v_brP)Ny^P)reu0lFl!K%l9J;YLmh@sf%->gqvJ z!?q;q>mQJ4_YY`V8g*roMU9;bKtLenfdGe||{aja-}yi@MY#-0XI= zjU%8By&M>D)xmZp9wHvZn!3L;elD0^;-p4avQiFBxCmVkjL8IjP0$^P+DjYUXrK<9 z&kma0r7zuIO6&Sz_Q27lBRuD?r|y?~Zg87Iu~$zuj*iI{+R8?Ql=2=4Vt-X4B5|Zy zYJy+yaekZY$873q`(Wk-Zo#|8iyd#+C!^0vLTK8b?j&SCSu~s}uJwP(xlgzJTt@cl zg-8yAn|Jj?8f8yULiATBP}nVCLYj9sS{E)BDm&fgEHyi!REqaZy>J(ESK4rQ0w~@8qPtHNb1A@I{#)+49 zDxTL14lvWCQzo9eJ?hM|h~V}O^ofzd1zHN;|Gl)n+mS`X-6un;Cb?)Xf3Bq^e27j= zrA`Z(Xsw^o?kT8A0&Z+t7jv^jbkr;!*4?Rnr7RWON0Zbp=IX#w%S=VRS>r*7aA1JN z=q?5pasL661Uevqi=rlFiRB-Vf@VsdhNdaJ1E2FA`T7(L)cOL4_xhbRiakPswQ%U^ z-}8gQaGHC8F0_)}I3j7&n2zAiWFRJ)*o zfe@~nBNUbSc99bH65^=v9u@`>KO0IeZs*HEe9JozKu2hU4Qd6;k2NYpPZiu?(Nl$6 zB<#z%hJsL5G|WJgM9l=A*9lqJ%CiF1UHJYrRz~K4`H5LdI!@O0EAtqDWr9JEa!==h zPI13WFbYmiS^>Qnd%T!o)Pq`gNCqu`TD^IQo!=RVpfLlFv9?c0sv8fCOt_?+&m4lqOsk7a zYvsf#ROdqy!*E;|Nq~cL3xT)8NTtqD>hetD*%M*HPk(PLLSE#l#Ghb)dv4m~)a+Z^ z6PkBg)qzM+tz`P9P8jb*-OWI#da$>;yNCK}NsPDj^q!mYFS|}z?~#R8&_untn-DpO zvSs$96L&Y9d2~dH+gY>8{_+kDCu%W=jFYf~Ic++UhS2wVid4)8c35OQh23#>xv6Rx5 zaqC>O5vgbT>B3XUN;x4m+J294o<(KU35*3yO!#SrLNHv!ziMOdHlxA{G0!VzvZI6l z`PBu#tCuD1*aQm#Ho~El4Nk$Juf9Vpu}t=2un@*$;w}eaW!^7bnkkyj z@JeIiA$Ll}hQ;f(d~d4R3AdV73m+6Fp0yW>F-URo67fHoC|6Jd$eyQxtF_|A_UKUt zQQn}O@zTKOzENqAONBr+E5?TnMN_rH2M#}L;;6_Ah?Nrxbd=3rl20>hDqI<|rQK&Qa533m}@I>6a@-V$X9MfT`PN@eN@vXS# zRLtYTyEgW6Db4vRaD~yBwYQ4wzE@3RJVTl!;~Dv6`+G1jc&J#rA_J^nH`<2ESeNi zA9=s;RnLTM({Y40rf+bo)@EMNl_wD*Pg7pFwh1{dd;{%Q2 z+3&jD2N+Qa^zh*G{p);&2lgWl@VAb@MNbg;_ghsOy6hQ@u~c^luBS>4UVK}z*qc@x z1<)U3AB2k+t|)Q(-0qMm#|LYRH=)HuG}S{j_?zjtoBov>X_j_`og>TlyIO-U`#&22}aI#Wswbm^z6zc;Yvrd9dS z!Pc`Wz-R*SbjUTT)a&a(?VQc!-I!6F3GTk6sd#UE^1hYP#lNxdx&)jfn>OYT})hhTbrJb1Wba$3}>UAIHd$3Qjj zK(&Qmug{Z!HnsZHldiu>LW=Hk)*6+^ZwNnLu}8bOxJPTSb@J(f9k+JP;Q|d2=~Mjn zf#*!7?M*Opz)*^1TpxetV#+&OEi*kv3NBXmd49Um8>o=0N%lg)3z|lo8p@ZE75@ynubQX8H)NxoW2bgD7SYkYS;( zkxCi1(1&W^>TW!Px{uS9C{bFx7Jp(o2pzCi2N=^)#`oO4>&i-~Wny@RDAb93w`)ki zv23{`7;k$8LR~)0Fa48%eWRVdhpV4@Z$oq)&C=et>RnyhE=f`MtQ!ls4i;I{v2Y3E zi6o#t#_SV3GjEao%-7N6mJ4t|Xbo|Jt`?Pa| zevE-J)>eQ5g#(4GZz`)233$zZ5;fMb8cgFa#BzfEMH~x*C&H4hpR;lY z71y4Ft9@OxEOh)`9pU2VTq)P9j^;{pAEpE*UQ%AK)qd_r4-JfAMnL%aJ7x0@@8zHn zVpruC{XKR@_u7Ztc<>Na4B-XcZ5aFJg!spmJXUE__p%32#@I_!f~ z%8gdDDTF?<@S7C$wP?7rc~Qez@N=vKBfb=z13(>RGQRzoba5rU{i)H$L1l1UQ>Exw z{4>-G=;2pWfvy<^!TlONfs+*UVh`+QVhODaOuR=$#pgqo`mydV(^Bf$>Cqe>682aJeV5 z;fmYrWXwZ}cZ=K?V#cVyTS7l&FnyoZy8eRotwAG< zMlx^`;uWl}W^}=K$81htk*!^-hcs*_a|~m~1GkF)J+9(H7*P}E;$;Kk**Bbx1pp_U zt+vaNt!Pe>>dxK~N(yF29Xr(N5;;l=Ustv`KzS#F{T#DJt&={y5Ik_6T8K4GXRi@Z z7}?W#KV>`SkH5Wi;~@kJB@CuthK0{-fX^h6d}NzMP4OvxJ6mdt6(e%xsB< z(msV|FMZ1y7r};oON%@UZqZhiL1mZ1Ly&<>xl0Y{lCcc$O`ye!0B@(VQe={SQP#IWyM*hAZM@fBi#n+}MlI0I-F z5DF~ym|$$wLHA_Gv?ZtFcx7WKZrzCK3)Ixt$y0K<3=8~?-EP2f^U_2C8^d1C7Z)#R z;5J@nW~fuXGjTc`h*^tKkB~7=Rs5KBopFHf@ZszYYgU?Ymk@${GbG)TTb+fXwWX*J z{JgZI(17`FW-o}btsYUVW#zl0rMDweM;$RVD+w1DJwf+J>_sX{v!W)Bfe(z5>V3yb z)}aP0bes)LJ42{4R1%o9ZYW(k%^&3=;g!FSz3Gx z4D@L+1^fz?Wst(LJeun4*AhLWQ3OKguL))p3ljY zvW96bw9Fg5<3>*`tL6mgCa31(+*-lv;e~l&2+U!*m8zKM5ODX zsJrSU;F)Ma^fW-{PGPS{VO%((KcbTWOzI?%fawhQ%w3-)+efNRJhk)uwLE%5SM71a z4HP~hFxPt|;4y!1;eurU?jxZ$GTJwmx-6{bWShJ=SfEqf*3QnGI}(r<7Pn6bKd2q% zo~A&)I6|Q=w$UKki5USx0Lpfl^-bf?X2M#99ACxN2Pc79Zer(PDj@#tYDcG_JT3GP$Yk>jRL*CPSh z8yDStiYBJRAmt$ifqKxv!j$qT-d;b6S3Tx$8&Zxni^WXP`O>%^aDVR|!A5s3OsnkX zN7=m+E<|;GCvWTm-tOQ5$gS#&abu$Ow3}#N{$rE7vYpbD7yM>}>a7$I@RZrQzaY5F(i%B*gOL5P z=M_xjzO9q2+nI4peFxkz5H51`QA-l+(SjpD_x_eH_oG*eZB(hsC$CEzP2VeBeZ&R_ z7_;jkdMrAb0^xm0YGx&jjgIlGY5U(45Ch9hD62~J%r1+&3efKX#FLKac0>2n!a2ZC z%Ona{p4j+#!yv>I9EO9lA4{l$^uX=h&ZGs(HTn1 z6^xZ~J!sBb5TguLUrV{Gar96V$_`y%$y|mFzL4Yp7FR9J*_{I`(c9-#uxDx>wzp?4 zT%=T;D#FT~5YwS?ZX&?tW}CZ*n7D|w9*f{v!$yX}e4%G`@r1Y|hV zknWrWD#sW-CVN03+{Fc3GZ6ZGc7^KIV5tuv>LEIto-p>m0)r5I@53%Kb&zx~go6dO zhY=6s!T?1q*jgMcBo>W8CxHo`TwGqz9LKoG6YU@|35)_-H4XN-yOAFM0DtwoU=yoTKsUv28XSdIvJ`zeS@f^n`B;FIpjONQo_K-@J}X|MEcE@*S2WP&#l`~LvfQ*n7MV001f1P^FLo%Cd}sR5$q76~B1 za3o0~+lqycb6(a%n#Q;Y1K+Rj{{UTSJXc#99S{VDktCH?>VxuH^64&cI|zW?J)c1i zB?)MJkcK_9SPP+>p@a=XoKdO9ePy;XTtsdYx=OQiSSva-05=c@;r(>g*;5+v1UL`~ zAht*!%l-7yPer7j$?I<8B#=SSg9O0;0KcG?*Xq|}3tJ?#FzQ%G_$1=s_c}(x(gYnE zH2wbo&q<79A0){jkO&|-G>)rC(!&tTsj%we%RvB=3mZT1Z2Ef>Q%Gf&GPaf(mJmz5 zGaF+>H8fcoa*-7Hswzl#=NE*$;Twp-{!fdQ}xumoQTO*$Bjhin>R_OpZ zISy;aqF!_)Nd`aiAIMOtk8?u|1^`JT={kL^V$rv!T>8fH83Trr@X`C9>-t-5Oz$@4j{J(n zO(Fw9kkij953sp`%n)RdTuhn#^x{AoQu81Hs%FaG;2r_hq6r{~2lM{`sE+5iYfGXd zS^Vh=7`M$vu4mdDG$c7qnC~RZT-HQs$dutN7sP@ru&&M0-6lXXK%Eg8VJwyxG!jHK zql#(-g4hB;CzlcL-~Rxes8?Is8ViVF1nwi;0ZozVOJr{JY*UhJskJrEheF zxM`g1Ft#&^5w31o@4I0eB{{X+EQ(o7GfdU4nUi%SAYY2Hdf!s?;`hLWp zxu!#iBuOE#NF)6kG!(+ZMU87u(tsyef`lz@;=*JCOF_`>B@|ns8=!yyag7N`(NQu) zn1J)*KYcANLb@$YO>+RWhL8y$4`}}YU!a}Et$6h{c_snVXdxfc0BOkrT>@Oz{{T6? z3_Xl@wh1RqKqSxZC8D%IOq(EktRTsj?F}FI^k-j4U;u(iItd-WF$_C#A;Mk(CB$uH z$}Lqnto`a~1?h#-(f zO_wpche&Yt{W`v%u@u}K`3)dR0GXKm^gNaHT#F4s<-|GKf&>PFR#sy{dGvsFhv^Oh zB>w=T{Qm&_^tE1Lgb|>b0GR<(7-q|hrOja_#sJ(*Y-i~b7N8Ec;y#>>=hPa`u>dPI zv>m>&h5!Ui6ANY7?Xta=)+0hhlJ`QWaI5uN)_^K;I|G4{uf027%YZGm3ig1;>?RBV z0U9Ztt&&M>0W_l7Y93!!;V|HIj}(#tN*)*-2`zhugm5;L3y5GY zFD=|aAn7e~jn)SigCMcMn2_VDh#l_8?}?{Nhx1wtToOXhLHk&rBujmCCzgh z;||>NB%O{v^s=59=9ni?ULfraQ>Mqg`#>-tu)ErJf8Ww1Ad$0BQILLvLzrAk9pftV z9$0r;*synyNrH^l29Wk2V?mkFGCyXv^A>s|( zyvSog0ie#ZHZ%UMKrmZf)UeCT3z$DRy0iZPd`OOz$ZG4wo>Pl}`G3Fk5w&S?;0B2ROpuc?S{qvfWtA3@KH@gJ)!+XBgQa+_yMuvi z++Y@%f_J2fJ<%JBiEM~@5hsuby%2_i2mopT1k9IzuJkZLurxRqG!PmFrKkQ6{s}eD zYiTbs!q8wJq$K`&N*)XBcsochVH%J|@K5uP{KQ9FVW<-X#1T5~Kh#xda192NHMExQ zGC_R)fB0Y0s}FG=;7I~I?F}h@C_#0>+n8EF+qey3tq1A<0AWB2hB&c+kW4(ghR^+4 zR=hC!@LvP~0Mk?X=&##xYr!U#w3mQ(jmP{K{Aw4X1vH*tOMzoZbK~FiKp_1;rmogn z<~Y5_a5_Pe6Zw^@`53g49Oni!?S~RiAGnTf3=aD{Lzo~EHFH|uX2yTQq!U`$Og39+ zFECmrB>VJdzx{vGD^eOs2WSu^=-CS_-Dj;^ESA1EJb@dB?tdXWt+9?ehcV29I_Q1> z0QeF&-7blH9o(}>1d?a@Y*A~t2c0KUeM4C2bt`D?~3bhdy9>j_U^DWiH=tNG+(e0|sZ*`h-AWncZBAj2f zu#pUKAQB9R2;Pw@mhMB`(BjYx7$b?{USpUX1EHbd7M=@b8JM(7s{={ZTZMtT?&O1j*f}&`}s}b!>Aa+Vebt zEdd^^GTTE7$N&QX?VaDwevuKnK{k(lpS@obz~Lop?4!?^v#~fl@ z2@o20#1D6>i6>?LRGzHXT|ueGXlqDeJOJP-fEq*gU@K5+9Lm`2rdxr9LgSc-$OZ>pif*kDx?nPsZoZ}L~Ee!fiFn>qAQ>574!&e5l$!L<|jlTCWzMEXq z95|RD{WqaoEjHbyl{5#ou**w8Ab{4k6dvbWxvU@%L=XsXxkqcOce&-xaRk6Fa6ZrV ztsw2tNO%FqXd_S0{{TISD*Ki%7FV-tiFD55VWJ9I6uzq5S_#}fnRbxx^EO(H9LBwy z{mvkV5)SzN?@dMaZfFI&S*!>JrN7rowN{qp-Y&l$|uKmtJ^f1-c+h?x21yIFGq zYo;90x9L0dx4&{Hr;GvI=DDCxJ6|7~&*jtq0HTxKQMImhuWK3sHHi&whi}mT07L%( zpFf>RRpU)ulKbxlrx&zp2@yZk{<@Wph8D;q2_|(X`3Q{;HU`N6NF~3`lu9ec4iZ7K zMzRAYe>2{J-$Zq4m1{=@0r3&qk6vTX4C?(j0gYAcL>+(>l7zm<7abB1VDAHb@fU?b@~) z0sghF{{YmA0pw&k!KZlTqD-=1{Mt$xRadV4_9hy)2y%Jj45fDHT6A+7oEoRn(NdOXL9Uvl>4ehsR4geD7nD-#a zY`6el;q-^{zx5(0bA)LvBZwpq>$~?609_Q4817@sgIsi$f(EZDEN0gW9qy}a5Cj5L zSXgfxhma%N{clYb>@K0rE)6;W;}H||k>R~UuWeeawA9xJ{Q^MLZjt%w_Bm+;GujA) zIvrI?>#|6Dm>AMG5M|DH{30y4x!Xhz<)huF^}l^I#PvZP(*w1RYg@DvB#sWh_wR;`Y)CS*X3d(l)~r1!T+2W-BCA3W&o>;M3UhQK}m0H^_K2n#?Ch9KY{0O0^Aw!#2_hj4xmdqVbn#UTT6 zBmwXn;08Z_5UH*34ltY)6u%W_1jDyOodGEL3sM9cz|Rc$0ZZSCZ~bbbT~A`*CQh#I zu0$tS0$j@!ZfK8k#*?Cel!Sz|lBA@Pq&!?wUP($?Nk$fsR8&-i8`!%M;RjsZ96?e5 zc`D=!Pn(+nkV^KgJP1W9`L{68MnVKHN(lK^{waT!5u)@%M#zDmWd!y0m4C<~vhTEc zK71hu=z+f^*|vT+f7P=d3A#B8&~3?hLdy_>1HR@Pn2bX(WgpD&T$e?#bLY3Uj0nHU(Db}%q7>>zzH?D&#} z`ClQh*#Iz80|7uPIfNe|V}_73LpFy%v1v9NA?hHP6p*b8WSDf3gGL0UproRP(a?gX z@@palAS3^l$OJ&35OOjo83h$3H3d1HG?>Ut4&`H!q|iKU&&q!~P>OO#^v&V}0tmJv z_fAU7xCOOqQ3=MFF1&n)BFeI-l>`fEUok@;yr1qax3k0i73O{Exd)vduRrJrU(GmQ zcCE4N&0=Qx!|u0B77m^vu~`*OJ!8xAxs4Qb+|x?;vq-?@0TCG~(n-x1bm;Q#KS)=A9rVi2*7h+4~(J6X`qA2hHW& zF|WR}@aG%||6pOWAD|=ODw7#d2LwkP>N_xVvLW2F_Mudasn*M}E`4uL6(+6@A0L=x z8*Z1GaXRDLaVygOb7JdA>&zyQmD_!B+7>91_Vsx*fT)s*HV-dd#Mcear>}JsSFOi3 z^{h~tikx1>WT|@%EGFjJ_=Q}WDTrL6FeDB=ePC17{Jvb<6M18|WMhp#Wv+4IYH{*% z4J{UxQD$(XwAKu(^{$XNgRGKTR<=mqXcIWRI;Ul06udqrwC024i67?B8B_8tk$3fM z-RQE;%jk2H>(h$wk-?j?cCFAPL-K2?NsDfju-2TrQGkEtK+W%; zKeFE-;d0uJy?+lD&P3o!!@~2;F-{Ket5Fv*xas;>ML_#aP+&;bsBacXb87 zB{(thHOThM&M|rpT@Y!$eWrNSXws!1Udj`zmcD;z@x&%zk#_u{6lY>Z?%ZVfa_s8O;7J#CJzuyB zU-E3>D#Ku!a}(;p2=UEit>;*H-9|)@A|wtjCk*#_QQpi&{ZcZF`&NnDyM73`{Eg=to5}0q#~#ykH^6-81AvYvWnV>xhqL>fFy42gRD##^=QS zf(IJBsw?oThvxS`f5Up&?de4-{q;R5a&fYcUSAayk&ukcIVK>M{2CYa1_#b@9@41iU_& z4`-u}=^t;FiG~K~GCw$`{zA*gZMKm=-?NvPRDidmc*BVAV9c%?d6%aaaV^R%MgKj| zu3jcB4&Q#Qt@Tz*Qb0 zO3~zQkj@iSHDEdUVF+9#nUhy|5B6+o8%B!r0(&#K6-;* zfxH`UP#k{L`YbygtzwyCjWYffYR<#IU%x9y-Hx+=RNp9FZu+*oJ@V`~Gt6V^Tc!H(lT>etVv zP)UkNzhX!AsCU~xf7&cVXGq@RUo%S$XUTgMzxI>9 zj=uBvZ$2x1$=aGX?8HKZ6pVWMSb!H4`QcRU?_6Q#-uJ3ltX|&$^xJ6V( zs!}%FW%-U=U4%5x4U=Y@TG#p9*L9U;LKi%9ANC z1D}gQQO(K>&9dR59>!0Y#;JT&2Xt>Ay;qUsEx^Hbez!Dp+wAFhY8xmyz6@x~WB z;0^hf-kRyhKVU>5QI~zRjz3>5Q}HVsxCQoRyNjZBEz5RTs$X{|41L~M&&5rh!TD!T zH86{xSsT``lkaK0zHwzjSRVDJHG!ilfsv?nTuAPYOaHvS>#FBQOc~CDW;Ck*=!rY5 zow3t=vxTjNT?&=;7r8%{4bMI+nDHC69h1gIKee5Iev!>AwYZQrXX3wYU9L#Xi^9(Mv1fq@Fn6 z>NS_kkS!w7uGKHM@Df30Jh8$%uA1AuZX?ewz}zDd#W+nrGPlKN*h|6@&$Ept@@jns4%R=HS?96V6VmYUTussa5S>@m zr@ayO@Ls_Zhoye%omHc?@yfM_)-%J&9rH&sFx~bmu6ZuWJj<(|eZ8{9pYpAfUhssU zVMQ}D&|@Pbnq~?rHUZRP7)L?(%&frew-!PS_2N^1nyKz_Mcag&Acgeb)O#wA&}h=+_ls7S1&rNoij%Sn zuAD5t?K-y!bXo{JQQLofw}9knITfz31B7dOhi6V~c9h1y=bfBE%wp*vGjOh$LtVsD7yCSctqBN=0ruFKcjnxuWe;b*Mt8CLc9p=Sf1HyWu%`^1||YV}X$ z+AcG_S~2!N{C331Uw!SNCFQ%=T9zKhhmRilaJ>2~7uSH$2rcrLS&tl<-#Gp}t3aBp zU`ejHip3UI{2-X;dW!2ZPo8x8Nv)MQ+i9L@j?dN|S{LXr;foK{6K~y(tN*|rRceP# znuQ11+HeN@%hvJ?MXrrmfrFAnO~Vyu#m2LA44xk)9!X4!CyykojgRMcmuHO^pHrPn zF^l0GFQdOaa-!PCPrB6=ubS;u6|gLi6cZS-c>c`4`m^YPJhJ}E5tPkU4azJ^O?F86 zfV|7V>?-fw#)C6uEly5R*EXI&e1ZY2B~pZbV7|ZUuH5JBPh|oZKkw#AH_9x4OFISD zaNS${WK{G*|w zSKzU3Q=g~2cFSQ9gs0&-jo_oHf?tUEGta;e(rij~pN7?T}Ba!wUO(nX*1FMp?lYVZlFwPKEBImKm_R8hvp4sxb&JP?0 z5QIOb{!xE&eQ_l*sUoy*bbJ>@W}~)JiSON!#Zv{n5@?#+l(jYz-mx3L!PvV&5vm_6 z$)4Cw;us0Srum*%XVo9O7g8$?bUbj*TAFzo|NOv&BkI#ND(0RN3&Vl~ry7`H*-)KW|#kY^lq2ztw~# zoQT}DU?tedyK?siZ!zJiR>q4vW3VjzKaTFEN$}Hfe9Twwp-~n9~jN%*N<+BB*L7Db+u00=Fvr z5D4C%AadN-=`Lr#FFmycxHt^{9SuVEc3N z_WJrV89&#hj^;esZiPMpmq?MtkvS3a7d&~M>XZB`opTRd7~88oA_xj??>qROb!n{H zV7=b^g`c`4amvG}u%8!WSN*WUmz7^maTY6bGO%Lx`m$OrML00;+00sdpQA@n;2Ks# zw4fn|7KPQXcEV5BT`paHl+4%7ukbeK!d{|8v3PDx2`9dPfE+t$K;+5p99Z;!$#lB% zm36b^%5`0`9?0>_%rn@g*_l;!;|}dol>kl;`B$^ca|GsQa*djuP_(Dt`?ROePKQBcu(D6&$w)6|LIcy+7EX_ zW|O+LqT3m?h}fFke*fe%OB`II-o7^0J?bA$l#eWBSl_nuNqo|G0#evduAiiq$2mVx zj~%EpxYO68cH6*8=cOi&J3`>oP`6FxWQtcg_W=XDv!~A4pB$q)%Z_^csxHJ3@8T)j z!Fyto!#8<;>_+L@&Po3p#;%5wOG;VRpTnpYjrol^Yh_!#IyM2_I+5QD&e~EyK2}g3 z45lHA zJ&+Bmflu}NCpmdmOP)RsXfIN{W4y#QKjn8es<44;AI05tElXR!%U2ftuGDKc6>kEc z-koU9Q)?3q|2Pve;BbGe{Qj}p@LJ!y-3(Ir*eDsdnrNRSn*sZj)XCF=MSdxztMy1; z%ImsES`yE3#UjftDBF67iE<{-yN(w$AHCl1*qv3ubld?J;IH1fE;R4DW?d+DYvxRG z{wksu2k2ku>)!}Z@*OdDbI)APJzt$pe_QAdK@4auAcW_Kw-hHmUzfq*q#j^7o%kDg zCu_*vHv!ne2#49)x&cw_+1uHi}$Y!HZ{e@49oA1ByIxA{lU@E7;{=JAfG)95vRS= zay@wr>U%Z!B6zspuYy z_T5=mxq9=m=UA*!DtqYBRh{V*<%K2wH`W@JOOM&aPjhiJ(`S5Sog1EiGr5>BZ_qnp z!he6)#_^u_FNP{tTQ{CWm|MIlDK?S7QPMqyDi&6{wiOqy@^-CDIa3wsRqQ(BiA#Ef zU(wB&(-ZCAU3Ev6BnkDI3rrP)l1cJ3Nw=a}i(iq|W;3DU4?3tf=RG zwJRs`R1I6#1!i5rcK%+^<<;5cA{BX5aHCHPBYyp1ee&~hCb+YCyRHZ3#ntK746-)C z+fGvIvaO(IQ0p=b+1-UMGk8f~ohO&_tLHAhO?U3+nl1Dj_dje|IL+x&aEGH0`n2lx zWO@E3K=FBC6YxEndvU)<{v)z>M3;Jdl@mDZ*|2DQCpHBQ9*cgl8{b&C+&7lUF$z8h z`{#`N*!B$A_-ibedQJGwFLq2kuYP0p^q8q`Rpms?v9-S1kyYz~jqt+ZJi3qRGhzh0 zLJ_6s>UY-V>zJ35_S@Z|~xLT6?(Otsmb#v$p5~1f>ux1+q5qsSXTIUeo4v zOEM`(O33@#XMIEaM%sJ#B@JQ=Ypr{%n^}4-xhCkTY8_GXih$!;_SLIOcmiKrPgO&} zyuItE&t30I`=8qGw()x&UC_1AzCk}6GC#H+dRD`WQ(RH}HeIAi=L;gvC#PyaOf2_i zdazMQana|BxuNRZr^e|=`}gOtDp6UEL#pCrmtB2c%s8%bgl3+Wq<1M)8=wrh7hd5s z(&~mQOo-uhuWV<7;onu)BKya>%ZE1s*D*!@OA|t4VfS2k{e!a>cILpg=Hv*@8#qK!!IQjQW_Gw@$6sli7%o@l>?Hb_poLW}Z z(7iZNj4xE4!_Lq%8+|P2GpcFyjM! z%c%g`H>~#LSb-z+b}Rme>2EJJl#d_LbSRdbqxT!jT}qs5E?E^UEW){rarvGrFBq8h zy(Zhbq&~4HX%`n~z0+wrAPAOlD-Bh13K6kqdd(FC{?*Z!iu~}Jml>qW%Jcak$22^e2immYFHI-Dl zouY#%RAxl(b5n4vnZ5}GrSV4_9rs;+{l=pA==9m!gqyLfq5< z+K4!`K9>*hQXS)P=j3_b_W4-ty1*tN>4>(8Ou9-)av0#4x1=nKrL<^P%m?3(WG>Zy z>NVY9>^chEZL-FyUBJGu!NaH8WEU?hcdsIwlxTGxN<6B|ind(t*Efil=qa^+Ffv`E>1b*rT)RHP8)r;r?_iw zX@A#Q!T!qr>0RI(==xR{uUm`i?V0ABX1|R6A6YRc#e)hlW0F0?CO3s=732)i&D1QG+10tNvDd zri^Ogp{k=tQI9z*YPjv@1;7?=WLf@Gxm&@7dIZfjG@-9|d&*t2X_r|#NlZP=lpH@!vO9F=$(#9F-b~nq5 z(^8v?W^bhvl&$^!C+msz(rf%d)*~~PO*3&)4ZYG1>zd^Ha#$N*|JM3-Ho0|*=da!l z_hL+^LMtB2#GrKM3SPE)eLNPs5$CH?x}PM*aTEjiO)m^0f$`Qg zo!E$6ttWy1KxLua!sOb$0@ot zl=8$Qc@tP;mddxNNW=}Ve_~6T7LzR>F0j){rQP#rxpt`5v7}-Tx~s)bB%L0-(K$|7 z(Gzl5_7${x_NF0O0>=a%^@bEJ1dFT0H&y*!W1dcnx9?^}qfd*+oj+q#mjGT^}N zdB^&l>8mvh1w4_9s-kt4Cu{!sA~@@jlXCg!wLPIK9<;ceBAK1BAyU_U$+`2cf3~R@ zNgX*j9Qi^0(=4uLIt6twc|{?%H{HX3yq4%M@49{r%_Np9Sw7%XSZ@r@2?b*ANg&Y3aencPtP`Ex?-T52YQ=PHn5^?4XgcPD~dFhN- zC+bDzqW^%!dufsCpdrpO;J9yQ)3KV8kp=OQPvA)NqK1dZm_pS6!8a){zeTZ-5?{(y zJ23INj@?(?klDs>um9$Y%~>sxACvdeViJJD0i+?6G-VC|99-b?05X7#i_9Hh_;ucX z3rlbdi-}HAe~hk0_`Dna1!E`qXVs)M@+ndpcY%t!w+jyLiE$%3gDPfWyGGTwMqr z?ie?5acwhgxQPMM)C>+16Bj>d#1H3pa(5>viHm!Ad5PJR(8W-$E+j;vxQUx90psR= z#sH)$3T6;PyQBHR^1c-F3tu1HA*zUz@WE_fv;T2^cV`q9<1U7B!`QpKx_!$7Qz5#8E>pC<_BqeQWRti%=nG9KcVr@g(DFGS;wi#OR0!&1xR%N zk|Pw(8H0EK&JKwDcVzu*+AgkWZF_gj&&1JST#dBiL{w5zR8qlA0$g{Zs3a+8ts=ga z^iTOEx`PySaQ2QrQw6IY1J;6?go^mL;FdzgziCuO{F|D=a{g8OF?J)V#@-#gDZUl< zy^8+O`u^Xso<9(cWGCO0C;6)$w+#sPUos$5lsyh?BmTD;&+o+g-spfgM-SgMIW_Z7}Sq}<##Lm>%qkD2Ht;j zH2PPgvVR! z^zT$Dco+PCVYQEtuzuu1>%uH!H7;QxM$ z`&S~8to17+czaPC#vOd|^b2c$-Dv&8dyntbe*aGC&xQZ%68@@Lln|AW7nM>llaf-B zl~4aR2X`mO0-3KhvZCUtnW@A?u$sjoaP&AHBZ&Z`kj@sayZi zZv0%QAoPE*U;n?%fPXLSpIGpp^x}U4-tOGN$$`Iaf&a}GOdz2Df9T-4YNej2s@@zfJItM|1*H`{tq@e^C$Uw8af8UHBew}IpDrTi-@e;}mF zcNzaG>*zRr-ezIrb*kT0kS@_JzJZo(O?AJ%1yqt3Eh7a43oB2f4`ilf!&a( zttk9E@E-zOv&XYrV5YBI%}HC{Nt7X5v(7?WX$(Iyss{jHX07SBV5GTgo-NGXzsAYG zZ2E?6LBTJ`ms~VkambgLFw<6y<~!$GWs)y#LG=J}2L~_(2Y!Rw&%q1w6>>8n@jFCn z3vLa5f5Va8f{6ZrY{~c$i~|4x++zNR7x@G!`45ve|2t`0nxQ7q21v4kHAJTIw|kjvsJ$b@>|KV&jOT)7SJZ)+mWMXB38LfiobI=>Jj<#a0>! zW&uA`TM?x8Pqe8V-CR8gzkn#g6$6gWcnsdd1nk$N7i1EIl!A0|go8i-)AEyhxZ%tWTAG1HeN`?=`M*L8iH?|GAe8nv zcQbp(UtkO?nKPbhC_n`4e1}CbSGWiL4*1SI4xMkPQORrx(D}=3IgcB4@7-oEBxhtBe~%C zV!~hAI}!%KOLE3RQd=tc2>^qm06@wFI+GFQ$APJsh}Dfj``?*ITJpb4PC4>(@S8`xXRrn_#8D7$2;L#i6 zB#$2A5>nz)QlcP+DDe#Fk-%FNPZR)4+hW7n@_V*l#C5jZk`^q82EJ94Hio#he*;|Z z2PYM653bE|R~7c~a7HUhpe5umGLkZ)lJ+NMLDvju1yOq``IDk(DSHVCc?pyh+Cg5J z2L82340xo;9~VkW`Bp3(u0o0`ZLQfb1vQRQlaW%CQIwOHQj}E@|9u)r{R_T{vo{83 zsqO57!IPFIs!7R#YaX|-f1mPe7OS7KNPt_Geq{M61=NK&N%)_X@jsv-!cTSiOQnDb zpn9am!CLvxF!Jrlmp`s9!nfp~>g+pANe^_#;_7xr4P1Ko4evYbKf}Jev%{Yl8Y$%` zrb$typPX8dmdXADjASlbKud6Uy(`Xx(tH9rZ|1-)1+q*xm^=}O!$q3;pU`$Div=IzL^40RgH;cTPqy)G#NeOh-BB7up z0WJpq9p<;phOTI5hco|>`Rh?aKWF|4^Lu8n;RN@J+W$-0f5!brV2gEdu^8Ccs%;(m z^C!m~{lxxP?EhFT7#jX{J-E1RwFg^OVQ8qN?TYdM+dg-sw%V8e1?`Fgo9G|+puB^O zjJ*_Ek?@hA0>2mWN4?t@7&y`Z#~k9{M=@VmAhGd3d2Px0KUn~Y_I4qEOT*jh+E&-!(!k$B z-nOo7b^R?3{4L~d>)KY=-_pR}Lf*EnZFT)E4g4+SZR^@r*Wc2>-$LHDu5ESwJq`Sr z2f^UM$qz4ZmSgi3nFdlz>x9V>V;!Wy!L11!G6_xX0|&{N0Kf(B?si1?0Nm2b8cy{b z+-XeOLk?c{C?Y}A#KeFU#(&wANeX_uz)7Fwo-g}TJ4H#fz9syNzceU<8)+Zu8u*by zgR>amG>-%r#(KFEKse+&7~XLbyA>`Xh26}KXo2uXFwEfiCEP^{pZpSjLkhcrLV!4n zBz(uMFd1pT^^TKY!d#@VJGdVf`~|dJ31_z6u)w!o%6xgvXnF?pbLc{ZBSA;$ZUonF zdxF34N6G~-e?cT^Y!8@GVec07-}ptUhcDzl9Ny9|guUel>_!-RF$kG70Q|G~ z4;t5Z0f2EPxY2d(2TUd$+~6t#&f{JC0TTi@bTf$qz}@SAc=$RCcvdppLee z2wj5t*>|=*F=OTPtWXvN^lc+kPduVA%JYJDR&Qw8U41l@5ARw~SaMBqb{Fc=j@bFP zW42rx8`gVo72JB+0m%Gr9ka7Mh~(lna`Y^pr@cg$39P9;?b4~ zi-$ik$b7GVU4aSuL@tf&c}`7K6Y$<(YEJl5qk{zpz?wP-onAjk*`>+%EMbN$8ci67 z%~B6nrVTR>rst^aWDlhlvb3X$Nt}6bIOu-gn<^$j9xmC_Lodnr=v&1d8eS?*y_DW- zsTsz}^0=CXr0M$ll~f;tx?8@YpC#2~GTk)TfZs&;VO>PEw#Ls^-q zD5R_qh6zaTb2&b1qDtw6LNL#`o)&u*j5DQmN#l#|21Yv^cEBBH7DVuFBS4@v{ksH&)s}lBPb4ked zSH-rl!gsAbgafF^c6by_g0t*q+*2*B3?^-6F?dS$RE`ZS43#HXBQrs3l*Z;@!S^<^ zAym8NvC(T3K!m*jVioWytMeO%Mxl6n_(sl(0!t7qy+>&lJlvsmvn7ZjW0y{w zPQ{hZ{Aqg0R0(gB9Y=WdoUS~EPTh!Oh7@{*BBFM*Mk*;pQF&=zz2Y0sG3gWskb6+V zE*SM3vAcknmNL1%0AsORfV{bSKaciu*goL$@2@&v#k#N!~<72}PP+bXD1bw)~J% zC#4vp(oYW$uCP}ZP$A(lGJFrU3QA-4Jw5pGlTj*dAv8cPs>70oU0E!j>9bd*247}L zd3Zpn28>^C^t^R`h)op}7c4*n8pI-(s`HTTe6f|3+=QkRc=KGbNb$wv$l>91`>HLh z%7Xe>fd#=Qap_UV$K(&3K{&lIg5WYV+eI5FRa?7?Gh|b>YnY1PpP20Fw0Rn=A*Fm@ z@m}35BYVJt+Bqif>{acCy0S!ENFO4`gHrvax+)8WQ-r|T^WhQB%5|v$C*_TocDzV1 z!6w3>*qkoeeZ`i`F*qea29o+j@Z&N~u;nOR_YN!Xr+z5I6w5VMeN(U8KKntW#3Eax#DUE32}uTn~UY%Qo1B zH<7E(Poafow3uqy=~U*yfGdUAa}#aK4b-kckPs{~D3Hav0TP2w=961QXLR3*4P}K# zjKYpAy=h5&e=|eG+mEGgavp28)oLn6GU=5$^?#G!JB}l(e)r~aRJ{myV%DG z>Q%S;?qlKf$Fhgb63orYq>4Oc$Q+Lx@>2*b{vyas19s&QN1^FASRW^E|4!{ zcX0kSN1m5X{M4dzK6&vUn#}ez86sawLPvI(YG?B7Ltb5OEE9wsFu8WiB*q)L-*mG> z(F5HW@Y~`(!_!X7@mx``po`A-Z^&uHZWStp9kx-Vx6O`~=xfstqjyINW~$=sfQ#rT z7(aQS#eklZvS8_GQir!RV$DORS(`BJUTPO$`xyQR8l)A(dCTGb>@zVTqms76`n*iA z*U>i*_r1)B`mN*rzMEZQ3*1(Af~6VZ_T~!Z)kz|!5f`A;5G&cL6@k&DPA{VLtEhc& z?gz)OV{du$x4jh7gi;^Xd2mZ9S@&e>{xecs9v?Lh7ss-*vp_E}jGM8U$ze-(D0O1w zcFM$Pz_~McQxjgsk5*1_Lg3sQ1k-Hp?xIUDrQlg8?HOkkZyTp@b<)BEMDi3{g~xD%O(6c-jzN4< zc~Fn+?NBvYe$+wQ;unOl{<&vuQ9*+-uQcq1NA1}n-8G$g{r8Fi7U8rKI5IA+^bY~> z`*Qm&(P5G|2^TC@!h(AB<`j?-^f7HVDh(VNhuUf2K^?`<25oy%oCEnelJhEei3YQ< z#obM#79%3zS6J`Uw+y~ouBy?7WMzu(RZDfb;InHE9Syyp6qRKFoC)kBJ5+dj_!S`W z62Xe#oz8a2ccv#mr*nj_=9NIpyG!odLI_Y;AQWK`ra}+l4q_cDBh$LT?^u%rc)k#f zaF1$s(xcv$m}`qrE}{3)U{KDW&#tCvg2lXo!Ka`xl(}``L~A(y9hC{0#6)?Sjfw?<=L_4BFQ4D`wZ?C*y zIK{(f2qI7gQ-5Td$8(7TS67=41vC){WcwE5E4$n$r?vQ2hKheHj^(J<)CsN7_dB>ldK$#U2j%YU5=>dh*JeJ{T_k;6 zgN|rx%rJ^@6KRWSCg-Q-xSu+vR;h^%zy%mYI)Mz!1~gSKRmC7XD{|VbVV^l&p2Gd* z+8An>m_xLlk_X!$d|7X=%N}X6ZF0eK+lz_;u&|BOpZma5vk$u%VZ+gPX4{ydLWOvx zrwA>6L=-%fO#Sw!i=vOE8L&DvK{}&sRz3#Gr#YOEQH;y1su@xC8DZfWVN{G7_j@*%@;xHR7}{Pl8Lw)GCQ{e!?eZ0Ir>qK4 zHkxH(fW{n*5z^p??#rgk(i1C-&}0z2?|+(hrUIYnTEY-m3TN_<4Ud?RgRU5fk`;NCGsARk-;3g;K_nWO<1K;0NVQoS#n7bi5LqBg2QPYS2THo%++()q zDczaEaSf%S#!2<$!n5TMj7GIuB$nMOcQ+llhdo3obiomZh&lTNJa$>N5E9rJ1!K~< zW?NS%T?36md;1@=@;N?DIrZ31Is@0Q?yI?29iU~a{fLYUdY<~dqwvE^dRXXsS; zqxa1KdE=CZ$}VbG@7!J5NNCugb)G}NucrQS%mO?Ba2)^Ku@8#l496I=w85efdZsev zL0Uq_ZeVkl^7&%HfiY@MdMR?QSDMS4Ku=EqIy^O~!SMi^_d|zj4AaF2S{$4eTy)5V z!$n#v!O8}jY62gaOL#bK$S(tvapa}W`>NRH7@in}e*sK6QCfY1!Sz0^g^(9ui9@TQ7NVgI=x zv|b=bGfftzMOu#q&Vt#uM~Y4hLI&f@LKxLvO>982%o!vrn&mxqaiv2rQ6sp`2i*oV zJzvA4;mwlOf_5>Djn0V-F_PWiYILK1_bh ze6Mr*zW2cclp1q7=ML>1)CI?JMir8_Q8h zs2|OpyVTvKx8e7bc-n>vyMkG?5gCzo#phUHUBSEusmXeKXu}qt7X<@xew)DD-eB9u z0CQH3MO(8K^-r#|mh! zQO~Ewn^S7nP*YL|sdQeCWzmRIq2}W-j|dO2ZnJI*coDxKC_RlS(YHPZB`5dH(RrCw zK;Cjcsya9hP-vTo3%X-j$&f}4Z;*mXAEGa`}FJ`ymlnpAS>hIg_+K7Twcjzz-5R5SL}450cuj2ll0ug++s?6?r# zp`snv;0%yaRk2wvjwwEDbtj`+LF{FX`pltaXyCjOqm%xbQI|MPKG{@)2s&II0v*tE z?@<7zsR>F<&;zA9(vpwq5usRUIJMC<2letJ;UjtrCLWiC-S6>JGmAm^O?P1nBo%Q> z@KXxH4e3#RQ~@3{CYDbiVpt7?=Sw4P;Jytu{K`$8Zl5pv0g}Z)RhvudijF z5zXP2O@BcQ;q_9USL4Qg>X-Wu2c8-TSkO;^LnI#$-U_i~=#w&dJX4-uqUnr?5kS7l z^Z>~esvt(Z>VDA0&j77Qb(3i+X1X|(0D%B$L?+? z4?Mg!miySo+5J=P+4i38^Fs2GQ~di+R(4eu9Htn!e7f+?p6aDoTJqH8WRp6Mx%>cn zG3O``fOqh0{DU%S?2tf=PPW+|OOvZLlzv4oYP3&FDI#fS7+;hUPdWyxPk)*eJZ_VB z@woVA&wl-2L|_>Ksd|N~3?{_L_1@&x9gc=GP#DLQf_aH8WLaT1qJg{1qkvsY(t`6D z;)3(XD9v~+kHGZPrvb*mYsU)wvyee6+JJXJ-W?k&0zuh_MFx=u(a!F@S2a@^Le`2{ z;eky(VGtX-btQkFW~6Ko_zs2NUMDVSp;PoUR8xu}Wu-ATEQujJPJ)b982Ke zfH27f(ooDpW#XeWrQwGHA-5&yjEQfq@gJ#6YI%Z65(CQb1j*2_Tb-v`bRmY4$w~$E zM(M@Cm<}0<7bmw-b08y33a8JtO3GV^i_t>@bUG0f?rK@nWvn-u^vKwqGV_1yqxHGa z#gwl#gw^5{jiLsqsiU(3nG<@CYBP<}J%Y0c_Y3MN$bBckZL?%X0Fr~}t!yC!KrKp1{q8)+3sd8=37KF{ z%>!!|3A9Bl3g&}M$Qv+L7*&U`R#psBBHp4oXadTEaEh0040)iX5zP^%$5i#mOOM{c zAjS_>5>=d80(-4DV~+DgyB0C=Yr`Zk%F_UF3K4+pnmwm6b5rt!Svb8|4Dv$TJs7*h z4gJeEe38V+)F_Mom z13o9uH3mxfM07?9%61Q3>LO>f23ROAYSKwZ<3rKfT^m{tSMH8{OP#>dnB!4Gj`fW;<5a zv2c7Z_L645MnA?VyT}P}rV7m1k)In(U5jrW^G;Y^h!J~NBX=PzVJ=3=Qi46ck=2(2Rvx#=>I6k@I5SuZ)3q^t=1K~j|{d+kE7@+-9KQJc%$aJ~V(aua%^|5hnav$Y8H zk$?tY;LZJ~XR((CY%Y*#lQFy|z01iJTeP%=MKGLaQNEzj zdw{0$jtpf;g;8pBnuCQD--oiY-4QYx`qpV_O66Q!9fFxN5*@nEG*2&aT8dD#0Dwxn zXGj=8MxB6QITX*PKqlRmY56>k;F2AqH~^Q5>X8tZHVWDE9VX1l`-hR z#+yEm;ghd3ybPr{q35z*@$0d|b;l2!kBe%T^4div!xZRY!f}e?VQOpu8ovqDr`3;G zVGXK+v@e9}PP7T=3$ot}NgD^;3DB_clC1I43GE6*4#&;Q2*3uelg39giS+_BlXX&` zKq;(skMA07(_qcm^_JD!nD1JGPR5OZL-*Q&{d5?JuMV zk>!{9$R=k?i-;!E5PodI;BwL$7U45t8q_PJr1-`&CQ2!1heLy{9z0Z2lm{-*AQM+y zfFNdoPq>f7M5AN~(sK-(0BqxIV97n3JbQW}yZH9UJq)?f;hATKB2t!Z?=_$>L010d ziqz@dWwreBWVA_MN|ijA#eaW9jLCF{6HC&llv zQNP?BqLyBs9Pf?K9RGmccl^rw0n0WBD?r8KxDix+d09sPk-ZQtN*8Kq4rg`ddU74E zugAjZl+vO}@Zl?jhO&5tD{lfBI*M0|-)2=d#KS}8)VmHost$_!D0rKt^F;>VS!V2G zDw7^3AG@HoN1cNPDYG}xXtgMqRIeWS)uUYlrzlh1-;gP4_%u)yvM2{J7l=A1^w;c7 zFb3eEZ12S|fc>gIohVSO|qnGux$c+A|{t$+7CqEr6b%#9uUf10-)zg!(qz(blAx>scGT%6Y2pHz&|dq@UHEP` zlrbR4=fRg_c|}#KX=q;s>4tTVbCDDXc1sa{I#wXh(6^Nk_ zV_9f(Y;llyAP}QgP-$=t4h_k{r-U14!ILlkRiz zcM}hg3mQGK`{)cUAQu>T5+Df>nq{c2%4Klc{jD2-0G%KSRqV8}!L1`{(20TxM0tGf z-8_>#`@ixEKWC-;ONYI>k|b_xe=!YZU=-eVIsyit>#+&14tCQX&@~*wM2i*KN+dLE1RV_cs0$q80R%yUeAJ&%B)AEkMC%GA zyMTg5ypD88Rf{NGSYgg1Xlal~G7y{HNU_8w6WSPXmeaO=1fWd0L&4KgqaWo06^5}qy=ME03E>~4H5)Z0HRoe zMvVlfDw{WmCPuK~pxo38 zt9i0oIn?+GMFFgS*67kC5Fmm=U!-#bjLDzP8eSa0OB?}kjT@26U220h$0ICAQ&Ka37pVeLr8-XCT3NE4JE)#8G;c>rQ|p?$kY)N zDFeMIc$YWRyMEoLXB_Lc5?6Qrm(`DSM%^Y7M*NjSzgPd!s{a6R0!Sng91w!*Dx^K_ z-M!#$0L+2xuB8XQ$CB_2IDo+sqJ{Lm#iD_^+U{}o%Uo1+i)}UbIJK{m&;c=_4ru)v zE6s!#G?TYjADrk_s0F}>I5o^W2%SkAJwK&1j6#5Uh^7)jcFpsR&|Ls)jP#z`FLsbYdGFbi7e5*lEKNA4?PzyqEP z5JUoFlt?y!kUY8IID!a}2ReV$Ay#a4rLsW-LUaZY&6hc)!KNHE00{&l)oBEm>%F2$ z@b~`!)Fc4Cf{94eVRq*M1^_zl2{3|O;z?=H$SnYWAx6hbWv+rC$Q`HXC;@HMxt0Le z@MNK4>XJ6LIaZcFw|1BW83YAtsTBt}kW7$HhgDSLTXi+=AUT8+Be4E)TXPw(vQHqi za6^DV)rv)^SRCL1f;G}45{P}k88FiZK?W6HNU(d1K1+!m!2SOK-&O<608EZSCs|02 zp-VlDwS)~PP+$iH7l7Dngpr{natg8>>4HHKAotKAT3Yf0xH^J6LLg~sYrF;)hHyrM zl@Q!=zM3PFL)f(N%iOzMSU z1{eSZ+V=zih&w2zhQvm`ADoho%_DJ`gp#q{Pyf=O%1gt5aReD*6Ccz;x#t*$v=UB^ z6B|iCr2hahICaIsnw;AJA_ymbz?E3;vF#27nB3<$`*#ocH5nRFKuw6!Yi*8b4kWyg zG;nw4t2a5~qC^q_kO3!O>LG@(*jNdM0{}}v1x?l(zyJ^kjl)pbAD)^{sz&o*AUU9y z07)7Rqu%uvI;T7q9i@#R3H;E1?}?u9<~hX642ftC+E1}%()%mQhwu*22;2+=fW6e2 z*FL+pz7F>X2glNDe?4f2uz(t5yHY2aJJkk|ZJTFnL=s~{M*W}k)wUYLn0E(~4#V64 z6Ql;3DU-cWM375nbb>)b4=>!%ARQPEx&i+HrBvTz-D_K1mbytZ%YuPx81M|s$hZ#*I)vB1XT+r>l*ET`oLG~bI9s>z3EiEAT+N`zZj@V)yz#;$v3Zllk#=L?< zzMF&yDFSz;Q&iaET3|PL2C@NcgZYTV*g7+X2Q}NRmR$HvzrZ2Ra-;XyKr% z6usi6faAQ4Hxn`b4K}sCRM-v%PthR8qfHR(ZD0ZnwCM!F)jJ*z&030AV|{0uOQ!nP%cQdr8n~B*H0UK_^{~5(gTDGT=amZ6xYQ)JOeVbViyX zggv$wfJ;de@=}q@44q69;-p&OK^mAo5LIW|OSXm`#$X?A@BD&5YNpf4Ye@oNNbw2Y zSU{3Ph|)VkuGbcrcqF(<16ck?^3fPKlUu;r;9=T=Wg}J;5;5Z1gMrjWnsA3}ppfDm zIgz876zize1#OsV(2@lI0OZwt))u>P(13pC`_ioxpVX>naq*eD&POptkwWag8*>kh}TqzwZ5vEq&+`=wj7+N9-%Po{ zv^LoAAGn{Tja7Zt*_UxFsf5(<2@m$n5A?790KO|CyK%*Du!HR_fHi0z%lyS*p|n_W z+D^~|m|SL9NB!|DLAsYT4IU&JgxOuu3S?dYBmhAGfCmmC1YFA=Sm*>y$s$@0(@w%m zT;4zMa->XaKe#K8iyXp1W10h>%SmuY0JH{x z2m@?us!wpx`DipUg=NkbG*;643=#&O)Wds_pv{l4Ffq;47<07XetK=dHb{_lF|NP9 zoR(4pOScojnGg!3+=f2aG!ozmfB#NC)X^Q;w0*0BYygU54RVX>kG>GIo>u z>qfV*4YC>S~bHYa<%&|3lF?g67gtdqc$LIEZW z5uuSBf7PM3Sis;!j>Zmzq6de(fFK?wAcRiJ0g0@};$(sd06~dG45`2XV77Jr(on;; z0z4oCJRl2+c@YE>-M|wr{{X+F2U=D1N?rj0!b}|r*Po_^k!wt}G`NUtPt)9@{Qxd9 z_<-@Gr)`_T4{6z01Ue7WDFf1!)|2Y`POhc zT0s=FXw_;_(JCIbD)p$K;;rC~2Sr7^s`<}u2$x9fzsLXke_xj0?!Gr~-n^N4v-94* zH%rtP)U}93$Y^vlLeL{1Lij_}b;K8e>7oP#2@6ApAP8cN7!#}zeMk}D4Sq$&pD?rBsn5m683U$YDIbCE_0C^CY`@G8yhnh9~0Y<>f^V<;fLf zj#Qoqq!9fz1PxDWHG-_s)3!$-tow$%V7Ozl;dgaMJqKe@^&`X-mvORx7(!4tAZB#yWJ4nLwKT<#mYXKi`AX>3vu7`}UKVC%7S?8FX4crn%vw{$ z@{bTuA3!XP5jnC(pJ0#ZSrYUu3F@mLwu$;M!56qRB;W@yj2-%5Llx@78uc(X)Hij9LQ8#vK~`>teq(tg`)QvtdRQ+h+Qu17vEd$! zclVHIRrwn^sG=I{9`hB$d#~O8nPWh1v>@=%I^~Ey)iF1OPrlFo@l5K?r=y+niszOs zJ$&|7Q^}5>&fR`CE@ArT%hvBaQgf%-GiZEb+84_=>^ge>?sH2-PY<{?z_!9i|SqdO(OPM)RmkFwz_JRUprz!)%c9Db-8m{`|G5EwH; zDZJ6j!uvUJYGXlt2{F~j+q6V{k#AHh$87%2HcE^PnHW`K92-<6DLs-q;AODqNp{tZ zx!){|U(G`D3^qq^k1LgonPhb%#%&SX#l`Z*N~;~*N~gv3FGe4VEiNnw`B*l$RFJ!x zn=Kw3qly@aaz=f#$WDFQ>_B>iWwA@pO20iPwv--Wn-YT!eJVr}BDXJLV`eRWiw4C@KlS$LKG8QWoi<4ivEI4c zfqsc}fDjSyw?~Z_-xZ z5V1f~A#%(b>2koqio^-2`uwP4)8{=v zj+EAoBsP~!-Ra^Y8Iu%pc9$}*sDkMKiA9+k8b;Y1I{aat_rVY&L490lww=M6(&W0Q z;Y&B>`|l+^8iJ~JP2I4M$joeFCF>asU$TPQo9eQZi{J4C|hHbPkGxEk^R#6werl(t%XMM8{ zwfJs&XkTJ>c~)swNQ!9^C0}2)cHs4O6Rte*_n%o=Yx#2Huq&rnV;L(hOg66{Q@Pxn zmOCt)Pb#guv31nbVJl)5pEb7I`$*3xzv=ABhRiae;;jXSQDXOtLt~Z|%-YN^P5!KTx!sz(o{>JQRz=wf_gDOKe@GKT&Dgm8=8he^>b)*c zY}(n==iBMeXRA)uY`SP)@h`&@B5IGpSNvz4l1;x}^m=1K*oED_QUeb)=fvJWs4w%L zJ$LDzeOoRkmb~))CDzws=~MT{q8Hv*=4^@UAG5;FH_GH{!NV`O$NN@aI^OJ&GV_Za zTV@X950OZo`HnvS>z-d<3|Nphwt&w^$(B)Q!v5VQj}%jzmM$YKNVznbU&s>?wonIt z&DgnP(TLX*ntX!3Z`!z{Jk^(cM{sk-PVR^sIp@6&>DH(a6Djan14qgXTuk+x_JmZ8z*bJmBCOBk@@Vd-;xM3ule@e(U{q=OzW~ zA^p{CabdY{!z$KR7uMUY%k1`Df4zAP(ctyG{;Jc)c9(L_ky7ZY6r|SxhqdgP&EM`m z{4@FDsEVD79=Rr8aeB$#u;Ec)uaaM`xaFhMRLO)R9@8rx?Qgzua`|DeEM?BTM}5C~ zy&=Tc{Puyf{l-ri^Y+=!-)7Kc>8T@rMI;$!hHFl37EOQdIIGViMeNC(L34lbA24|O zb^fmLP1A>DJq&9wKU%WWy*{f(pR`M1TO*l~kos|0UD=tOJF6$~dvVI`_T6W14NAK+ zW5Ig#XE|fZ_teYNzA)wY74?1CcVe#U{ytXP_U7{0OMg7`tZz;I6n@`3nVyOF)JVXV zoy~>l$wAL9oPV1?d(o@l@c|%;?n)pz>^EjqQd0ImhE0vE#_bT;ohrx zvA)7xgblv7Bg>ZWJAIelaQW5L{fcu}j{2Ty%CU+5$@%!=6UQFB8LzH6Z}-4upf`<# zD$V?6ZFaaZ-+s<(en$9g)niJ|5ZW%gb#E$P+tu#%qgY;TzJBth!^{0M9#d4~6EB^; zv^S7qR%u6nYX0pr>D{TXGEZe@zp`7^l(}4ulv@3ECo@)!3~x%$w0pfxjTrA;;czP9 zWyzgM%j_tbd+a9MpB$PQ{EAG|@PXRoH}-}~xrruV%!Y~TCK^NLqdDst1F`cy33 z|Me?7bpOfBtA*F!##P&e?LR5Y%zv9$cuI}TRU>26h}qsH4z_9}=;SBUUsY_&z5H_j z$-DJ$^qYK_zuw{CFO|qrl|ng<77!gkjtoVkqRBvvMhgtLC)+0}l`3^tHxAtsZ< z4AeD}mna-9l}mV#3=5XZMKh!+N}iZ2RIq3mK2&R|-gSMYh@UJ}y7J{ho>D5;)*-SK zN+6%uxj6tycqz~%V76eG5W6KTQVJ!0ayc)fjWiF-0%K57%i4Qr{JwA)5nx@+_GGYV zcmku_Ek}H@NSLB*We1QyMAq(UOQeDTo>JJJxB&8O>?MKf=0*1uuA8h-1<4 zqTl6Lp#%z}#k|D!RH5sI&6KD3Z-c^%A#r24DI|;`-zZas^KZ&q0lzg+C^Pj z-~S)$=|VKjPPEE%>!?Sa0TKUK1{B5TiNQzwrx{Ntv09xDSdGr-wECC-oym-YaZm(i z^B*{3bs<`->FCC*zhJ!5D!9}0`JXaoy+^!O8`8NSOcmPc%^!9@iq!aM&8%0(gIV}w zQA&bzvSudp#|Ewr(^5ad)KrC1D8PVRA-`>6*=mK|PbNA|ygzU@+TE<|-KjT`rJWRF ziqlRNvGni0TD(_Gtadx9ki&;Wx+_dg+Xlzq0eiZ8bcH?Kb3_Ml+nfYi?V4%rjI{G4 z{{!{fJ@t2|`Jl}|GSBap=dLFDZ8OwPGyQgU-_G#cK|iER;VJm9Fx}2_zQ=aqHLZi$ zwV3pKz@DC6+pbqfu+Dbz7go*dZmZA!AlrrRL%~p%NCJIKS&GfpPN5bCQ zba>wjmGYC@2qo~u3Ly>K*y^sTGDFsG*4p*f6YT1>IP_?Ip`smIt;-$VwehZ)+KFRl zDZ8W6g^;XP8M~|4UPNufo#&WsYbc#X)Jn*r;k%)16MXMizaHP!O~)hS@TfJ|1HPs= zO5LDt(C?wg)lW2Z*8dHU>C={8&~wJ)-ULt+qVM$axWP;{q_JqY)+5On=|{-Hv*cFb zI|+RE_z@1aXo(KQq7*U81bo-o5ic`qtLQri(d=6H!eQ9n^*|i6U&p-uQ#?<^uW%t~ zYMJ19f+pu=f#*zGImg@7e}+SY5n4h5l!)O95rc3LS|CL$R<=SII6MxnTAblHgxUtd zWqbj71d%~hV3&vfJ*>FPBK4ms!!=`!(MB*?p@;NFY0efc4g@HI$2n^B(;v&~qj1s4 zgJlschLS*ny9p8lGRUBZFp1?;QpIAp zVPU2{;-#r60!2jpR6gWd>T&r>H^^ZIfJ8(1s92hU$An_}P+@|yeV(I~N?P(b8{EXC zmU5i69tyFDFI0>Zhbo{lM6b1L#}F?Auoav}cm@qV3NXm`;-xC3LBbTF99*3M07x|Wt`=ky4KfD7N>E868E${K{Q9YKadcp8 zG&I$sTuk{LAz_L{VFySLo>&>pOY8tM;|rmlFkOi%f}_Ji9D2M1v<8zkApj+_l!^Umx`tGcFMKX(B}lT z7C;pwT$+OEny8e@B2twKVSB?d5rZMU12v78D#4)Mf%k+iPU?(~KY`9zLx?Gy1m8wH zt$Et8m}!j(YrGwXffyfx3#PU;aLOTBiy-F?0DL{t7vhF>k;b}6<2%i|NMl{3u`beB z7ip}EG}c8L{~JacXiC^+GL3qi~gKSY2?k$8A*AS01T5~P)ID)1D%8o?C1 zdkiw-uOX+IJ!#%hjIeLNuol#H{g^GW!6^z?9`jo(wU}BbTGh$O-kY$=J&@YG^=F6mfhLR%F#FNkcw8^EiyD*;{5FyY%N<8M%q ztX*Gr=oBab^|;fZSN<7Bv|m+r-CZ(mal1Zig)xI6d|oQgV8c?87Oxfd&#$b1l4zsl~I8KP|;r*fN?QyjPaMt1V9-J*3|E~5uUlO-tzXkgr z>xHne?)yO^!TkZ=6=7k_04YBee4i2xU~9$~fs_v}y6qYCOz`mFF$D3Lv-Wt8v&O~k zNbDbzmy0cbD7u#!Um{Vo$*rlw4CBd@g#vc8JQY^)!QMK8{}~Ac{8rXwupowy!U8@` zC>CP9q5!Ys*21@d&rHBS4q}TW5T8h!DicC*ej60CQOIdz#U3`zK{N7X<#tZfH?=Pbrz#x0b}Fu zJa9Sx&IT~rx<)=o!#Z{8)b&9c_#kARb?MagK^piVWSw>C)b&9c_#kARb?MagK^piV zWSw>C)b&9c_#kARb?MagVH#-L2N9;g=0_Ura;R?%@Id|jCr5H4Mx&vD_=e2@ieCUH z5R0#pq$uUwAPzZpLLAws8lu3k|L+F}kFSvVMMj2da_?MW)2F#c6H#=AiqYb#@@|dS z8*6#__}~hQO;)$wsnHS8lP{A)4Hx8GGQ?Z~k z?7%j$Oob^i*p3z#PFvWHl0r0G21MzIkVM$VQo_a-1EIqo15)ibv!Vb?0p&tO0(UZk zA}I(TYRjQMcIDw^!2NsL!yAW-Z`eL68H}Bz_z+hKk2@BEThr1i%Tp)*Sp) z`SjmQBgl+X2qFeNXRr(QdIYJ2_)bd_P=)^z^wD_8lho?{E7j_K!`13zciU(ZfHWal zuiC)fFsH{oV|N4lPv7JH&iB9eQJwAe0T&0J zvfEGIG$_|TZSXjXn}e~#CC6`^4b;eteAcEb9^6G_jf zIwgNBUMe{w?Jb)$ZIk>rWmM{xG_!O?#Zev83{$HsUaHkUf2{LGI$xyoMLJ*9*%#^jg3d2!{epz!M~0V{uHKjO0eq#+RuJTU zX+#fyU&Y914n4wAZ4$pjIeZs37*1O_{ohBYJzNj|U48ol%Ox~of8j5ZmTX$y=f&Yq zCmukMBZJo@snwoNQ&aC5jXbv1cAHUN`Q*SA=F8&NJ#3?gy_fyl9g3mbKsgxcy97_0EIk>-MDY=wUU$OgK)LiIuG5XS?%ZIK!y+*!{)=KM^-1zS1 ziCedCzq;G&-hlcc_dOo49{N4fdVNJOpc% z(QWqk{xMZI{^`a)t&RQ__coi;&4qMxA?+TB&KCaLZK2EG^s<7V;Ic5Yv9PciNU|gi z9Ngc=rvG63KGQ z*5;|GbBHBJmEEW^sY(hV6|0tI-waTpAt*()!-dkCXv{&!K+Xkq3RUH)&^m`<@KaOT zl=7g13#DGgRiTu;VijuRt3F+(LWefjaM3zvzv%5~9VKsj)it zNJkxpSZV9D5!WySoMF_QQHMC3u7(3=pv(giLrky|48>esT@Fp}J4#V=`VpHS{JntxCqLjHslzK{uDqDLgVgb2mpyYz9F>fX4GLNC5E9NCibE00rF<4S@?*KEkn0o^#GLphPrDl~LTi3v(sPv>S8ue9Rg4aHeKTQw(`uu7n&kvL&7t%nt+Knl^{4U6 zM5L0yeBM_RCnjgkY-CZ2etpblef3)wTG0?*-@NXk`aI6m9%v$`7|eDY%EWmB#R>9? z7|^~GY8rJ&sp6gn9ZW;%Z{a(>pud}JDNM4Z8jY5k8Za5&LFNBNoi znwUz8YQg~o>DRkKL|JiW5;53aT3()Ga4>A&Vtuj2@Pa6-(*b33g8N%#jeV7txt`6M z{^qc1!QKH!uGLdEs83f9qU;XeGQ0*?Y6$5Ns!?)TEU<}L81zqN9o|WpxY(nEJxsaa z98gL<;Npo!)n}6~UNbA6MnWR)7W>JNGE2sk)s|xfr&b!Pau^1dJ>vCBbMLSvdlRc! zOOC9}xp?|icG12sa~_pEwlJvQxavXjk@Hyu=D{ppu-i|!8w?0*&T~n9wh#BS6_J~( zOh5OS^D+FqB|SCJ)i#)PYOmqs>W$BjXMZlzN6}$#>|O+=Kk-6C`n}%VSVyr2qpUgu zUgYM#1i2-|xZWtsffxry1J|WQ4oWTo!$w`;q?Ev;q*@9m{s6QYjl&(~ixTSNF!y;^ zibt*fl#A9*_sxp*4*kvIIq$0MC)ZC4B`(XW<#yiJ4>TqXRwz^QX8Az%N{Q4!{d-$li=U$8c2z%%P}}vf|)P z4xCD4s9))6vydr`Q_4u}5HpHuiiJ0clF17gxx_JO31LIvqzts0Ixk+Idu;cC!^thu(TJLXhLqg;hRNzP=f(uyaTzK}J5z>c zEI?hFZY|F*RuzV8GH*yUH`I__xHd2&3(DSD4LxCnMr>F=DP9#a%ZKsQS`}4ac|M<_ zDvWr+F{~&i=0CZeu_wfKe_iGk_Zyd+G8?W3Sr(saICG-dG3dvbyF}-Hg(2Q+rX5YO z_@b0~w1?BdjOyUhrdiXUKdLjKU1c|TPh;$@r&z!0H{bj2B6|n4VAm?{1(<2IVRzH(l*qt_&v$|v&GxPp&g%2iLJmjqn^ Hi@5kdG577h literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/cms/Upper_Right.jpg b/gwenview/tests/data/cms/Upper_Right.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c9854116fc4e0eee95ba531711efa2d22aa0225 GIT binary patch literal 27862 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 literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/cms/colourTestFakeBRG.png b/gwenview/tests/data/cms/colourTestFakeBRG.png new file mode 100644 index 0000000000000000000000000000000000000000..09efd6b1e5dbc000829d4b5a706f8bc42e6cfebb GIT binary patch literal 8223 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 aMFL>gV+@$M$v$v++|_%aTYme=%l`m$pvG?i literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/cms/colourTestsRGB.png b/gwenview/tests/data/cms/colourTestsRGB.png new file mode 100644 index 0000000000000000000000000000000000000000..66fd72232faafae05978352364848e0ef514b0b4 GIT binary patch literal 9009 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 literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/date/exif-datetime-only.jpg b/gwenview/tests/data/date/exif-datetime-only.jpg new file mode 100644 index 0000000000000000000000000000000000000000..db1002bfc359f38a48dd132bcb148e40abc82d73 GIT binary patch literal 8828 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 literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/embedded-thumbnail.jpg b/gwenview/tests/data/embedded-thumbnail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f6579c7d869e44e627229c3330d8be741e3ab3f9 GIT binary patch literal 973 zcmex=#fdwj(!^8w*GbRGX82)2($v*~R z=c3falGGH1^30M91$R&1fd97{oI#GI2!ITs$N-S${|6WZIT#!m8kiZC7?=bZnFSgD zA7Ky$Iu7I^B)|m}77;)fWdMm{s0E7(VyeBxz{AW4w1rua!Jc7%1Cr^)Fy;T>WcWW6 nLbHL9flz32;tWkOP^_Q^2PZh}3593(16AvKRE/dev/null && DOWNLOADER="wget -O " +[ "$DOWNLOADER" == "" ] && which curl &>/dev/null && DOWNLOADER="curl -o " + +if [ "$DOWNLOADER" != "" ] ; then + echo "Fetching CANON-EOS350D-02.CR2 (1/3)" + $DOWNLOADER CANON-EOS350D-02.CR2 http://digikam3rdparty.free.fr/TEST_IMAGES/RAW/HORIZONTAL/CANON-EOS350D-02.CR2 + echo "Fetching dsc_0093.nef (2/3)" + $DOWNLOADER dsc_0093.nef http://digikam3rdparty.free.fr/TEST_IMAGES/RAW/HORIZONTAL/dsc_0093.nef + echo "Fetching dsd_1838.nef (3/3)" + $DOWNLOADER dsd_1838.nef http://digikam3rdparty.free.fr/TEST_IMAGES/RAW/VERTICAL/dsd_1838.nef +else + echo "Unable to detect downloader. Please install one of wget/curl and try again or" + echo "fetch manually the following files:" + echo " http://digikam3rdparty.free.fr/TEST_IMAGES/RAW/HORIZONTAL/CANON-EOS350D-02.CR2" + echo " http://digikam3rdparty.free.fr/TEST_IMAGES/RAW/HORIZONTAL/dsc_0093.nef" + echo " http://digikam3rdparty.free.fr/TEST_IMAGES/RAW/VERTICAL/dsd_1838.nef" + echo "in the tests/data directory." + exit 1 +fi diff --git a/gwenview/tests/data/import/pict0001.jpg b/gwenview/tests/data/import/pict0001.jpg new file mode 100644 index 0000000000000000000000000000000000000000..90d9eb19618182f4f5166f21787953fa69dd3919 GIT binary patch literal 6811 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 literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/import/pict0003.jpg b/gwenview/tests/data/import/pict0003.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1627facbed1d51ba411a04ab4fd89b02ae55f512 GIT binary patch literal 2745 zcmb7GUuaup6#s7iht+lWHtl9y+bq3-q+*wZ46!fTbcsnyp}RH(i)~TcVhDY3lZHLM+6@*@+RJIQUL*&uGun zoSW$BdmRY<1b&GPzVo^cb-a^Ue9Q52baYIOM1>p zbUdf$Ir$s%G*%9ar(KW@&TIiu*Ri_ypuiCwnoxsU6TOa@P3WzQZ#LoOd!$Vdro5gf zFOtU#j>}o=#b3YtT8b3Lm%^EXn2JYSR(mTr#-w6`NuL<5;sW`F?e5c6qJUS-NLA)vvoT*w`%bd?`m`3!@h5hZ6%zk*9nKO9(D%Ua{kH8C-ARw#!0 zB9-h=e_mQ%^@zgG5QWc*0);nMXi8kOOIqNNAq-sI2f|sJ-K7bZ-B!@Wavu!dl^zDv zkbbw?is_M`3VW4os6Q_RV122Gfu|7S`cjdG(+nY4cfLug*)mrRwyIPLP+Jwkx~Gy? z&wu(H7VDw|inVAfzoBeWt4`gXgS3?4jLQZcRlts_EhVe0lCL~=6i5$XZ5y{BE4{Si;UUfj>qS)K^@PEM|lVbl9Sp zfIaPB%UM+?1uG387+&AOk`1KR$6_8RCI+5~l^y67+Meor;wtojx#XdXh7P0{145Ky zTh>5ShRsqbY!)EhJ1R4XQiw!_HBtx+T6^Fl78np~nUZ7EsSshK%6yOo?W{q{*JRxQCY8sjLZCiV<< z1MoJ3*J&>vG9C?wNSkbFLtIZsD%c;l4F_X+cfMMBw4K4hy6w6@d8dN3bXgml9$AWQ zeZYYxc$P}BkC77gv3jW5Misubl7j7}!dCLfDWR&2`nLz}Rj=SVQg9R%hVS=zu-KsZszeO)@$QYg8n!7{4i zv37{Ea_BYKst~0km4)Q=cYj?_4?pG1*_o`~5O9pZ1Z$CRtyWQ1Nmh+aJ{s-I6)B;B UjEivgVc#C|SK{E0#*F;Kzc)L33;+NC literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/jpg-with-gif-extension.gif b/gwenview/tests/data/jpg-with-gif-extension.gif new file mode 100644 index 0000000000000000000000000000000000000000..3c1bbdd6e9bd6f9bee9c7b8bfbac8cd2ac14b852 GIT binary patch literal 9232 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 literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/orient1_vflip.jpg b/gwenview/tests/data/orient1_vflip.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c4c9e021da17b34659752e61241466bfc597090a GIT binary patch literal 8582 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 OnaX8Q(~`;r?SBWK%X7*A literal 0 HcmV?d00001 diff --git a/gwenview/tests/data/orient6.jpg b/gwenview/tests/data/orient6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..db1002bfc359f38a48dd132bcb148e40abc82d73 GIT binary patch literal 8828 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 + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/gwenview/tests/data/test.xcf b/gwenview/tests/data/test.xcf new file mode 100644 index 0000000000000000000000000000000000000000..56c0067893afc476c82a64ec5923ac9d32df157e GIT binary patch literal 27080 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 literal 0 HcmV?d00001 diff --git a/gwenview/tests/manual/CMakeLists.txt b/gwenview/tests/manual/CMakeLists.txt new file mode 100644 index 00000000..19758336 --- /dev/null +++ b/gwenview/tests/manual/CMakeLists.txt @@ -0,0 +1,36 @@ +include_directories( + ${gwenview_SOURCE_DIR} + ) +# SlideContainer +set(slidecontainertest_SRCS + slidecontainertest.cpp + ) + +kde4_add_executable(slidecontainertest TEST ${slidecontainertest_SRCS}) + +target_link_libraries(slidecontainertest ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} gwenviewlib) + +# imageloadbench +set(imageloadbench_SRCS + imageloadbench.cpp + ) + +kde4_add_executable(imageloadbench TEST ${imageloadbench_SRCS}) + +target_link_libraries(imageloadbench ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} gwenviewlib) + +# thumbnailgen +set(thumbnailgen_SRCS + thumbnailgen.cpp + ../auto/testutils.cpp # FIXME: Move testutils.cpp to test/ + ) + +kde4_add_executable(thumbnailgen TEST ${thumbnailgen_SRCS}) + +target_link_libraries(thumbnailgen + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${QT_QTTEST_LIBRARY} + ${KDE4_KIO_LIBS} + ${KDE4_KDEUI_LIBS} + gwenviewlib) diff --git a/gwenview/tests/manual/browse.txt b/gwenview/tests/manual/browse.txt new file mode 100644 index 00000000..8f0355b8 --- /dev/null +++ b/gwenview/tests/manual/browse.txt @@ -0,0 +1,17 @@ +name: browse/startup_down_sampled +description: + 1. Start Gwenview in a folder with raster images large enough to make use of down-sampling + => thumbnails show + 2. Click one image + => Gwenview switches to View mode and show the image, zoomed-to-fit + 3. Ctrl + left click + => Image zooms in + +name: browse/startup_svg +description: + 1. Start Gwenview in a folder with an svg + => thumbnails show + 2. Click the svg + => Gwenview switches to View mode and show the image, zoomed-to-fit + 3. Ctrl + left click + => Image zooms in \ No newline at end of file diff --git a/gwenview/tests/manual/compare.txt b/gwenview/tests/manual/compare.txt new file mode 100644 index 00000000..d8e6eb7f --- /dev/null +++ b/gwenview/tests/manual/compare.txt @@ -0,0 +1,10 @@ +name: compare/resize +description: + 1. Select two images => images are shown next to each others + 2. Go to fullscreen => images resize to fit the new screen size + +name: compare/view-mode +description: + 1. Select one image in view mode + 2. Show the thumbnail bar + 3. Select another image => images are shown next to each others diff --git a/gwenview/tests/manual/imageloadbench.cpp b/gwenview/tests/manual/imageloadbench.cpp new file mode 100644 index 00000000..a7759917 --- /dev/null +++ b/gwenview/tests/manual/imageloadbench.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +const int ITERATIONS = 2; +const QSize SCALED_SIZE(1280, 800); + +static void bench(QIODevice* device, const QString& outputName) +{ + QTime chrono; + chrono.start(); + for (int iteration = 0; iteration < ITERATIONS; ++iteration) { + qDebug() << "Iteration:" << iteration; + + device->open(QIODevice::ReadOnly); + QImageReader reader(device); + QSize size = reader.size(); + size.scale(SCALED_SIZE, Qt::KeepAspectRatio); + reader.setScaledSize(size); + QImage img = reader.read(); + device->close(); + + if (iteration == ITERATIONS - 1) { + qDebug() << "time:" << chrono.elapsed(); + img.save(outputName, "png"); + } + } +} + +int main(int argc, char** argv) +{ + QCoreApplication app(argc, argv); + if (argc != 2) { + qDebug() << "Usage: imageloadbench "; + return 1; + } + + QString fileName = QString::fromUtf8(argv[1]); + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << QString("Could not open '%1'").arg(fileName); + return 2; + } + QByteArray data = file.readAll(); + QBuffer buffer(&data); + + qDebug() << "Using Qt loader"; + bench(&buffer, "qt.png"); + Gwenview::ImageFormats::registerPlugins(); + qDebug() << "Using Gwenview loader"; + bench(&buffer, "gv.png"); + + return 0; +} diff --git a/gwenview/tests/manual/slidecontainertest.cpp b/gwenview/tests/manual/slidecontainertest.cpp new file mode 100644 index 00000000..190b1ef7 --- /dev/null +++ b/gwenview/tests/manual/slidecontainertest.cpp @@ -0,0 +1,66 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Qt +#include +#include +#include +#include + +// Local +#include + +using namespace Gwenview; + +class Window : public QWidget +{ +public: + Window() + : QWidget() + { + SlideContainer* container = new SlideContainer(this); + + QPushButton* inButton = new QPushButton(this); + inButton->setText("Slide &In"); + connect(inButton, SIGNAL(clicked()), container, SLOT(slideIn())); + + QPushButton* outButton = new QPushButton(this); + outButton->setText("Slide &Out"); + connect(outButton, SIGNAL(clicked()), container, SLOT(slideOut())); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->addWidget(inButton); + layout->addWidget(outButton); + layout->addWidget(container); + + QLineEdit* content = new QLineEdit(container); + content->setText("Some long text. Some long text. Some long text. Some long text."); + container->setContent(content); + } +}; + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + Window window; + + window.show(); + return app.exec(); +} diff --git a/gwenview/tests/manual/thumbnailgen.cpp b/gwenview/tests/manual/thumbnailgen.cpp new file mode 100644 index 00000000..1c9f7737 --- /dev/null +++ b/gwenview/tests/manual/thumbnailgen.cpp @@ -0,0 +1,121 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2012 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Local +#include +#include <../auto/testutils.h> + +// KDE +#include +#include +#include +#include + +// Qt +#include +#include + +using namespace Gwenview; + +int main(int argc, char** argv) +{ + KAboutData aboutData( + "thumbnailgen", // appName + 0, // catalogName + ki18n("thumbnailgen"), // programName + "0.0.0"); + + // Parser init + KCmdLineArgs::init(argc, argv, &aboutData); + + KCmdLineOptions options; + options.add("+image-dir", ki18n("Image dir to open")); + options.add("+size", ki18n("What size of thumbnails to generate. Can be either 'normal' or 'large'")); + options.add("t").add("thumbnail-dir

", ki18n("Use instead of ~/.thumbnails to store thumbnails")); + KCmdLineArgs::addCmdLineOptions(options); + + KApplication app; + + // Read cmdline options + KCmdLineArgs* args = KCmdLineArgs::parsedArgs(); + if (args->count() != 2) { + kFatal() << "Wrong number of arguments"; + return 1; + } + QString imageDirName = args->arg(0); + ThumbnailGroup::Enum group = ThumbnailGroup::Normal; + if (args->arg(1) == "large") { + group = ThumbnailGroup::Large; + } else if (args->arg(1) == "normal") { + // group is already set to the right value + } else { + kFatal() << "Invalid thumbnail size:" << args->arg(1); + } + QString thumbnailBaseDirName = args->isSet("thumbnail-dir") ? args->getOption("thumbnail-dir") : QString(); + + // Set up thumbnail base dir + if (!thumbnailBaseDirName.isEmpty()) { + QDir dir = QDir(thumbnailBaseDirName); + thumbnailBaseDirName = dir.absolutePath(); + if (!dir.exists()) { + bool ok = QDir::root().mkpath(thumbnailBaseDirName); + if (!ok) { + kFatal() << "Could not create" << thumbnailBaseDirName; + return 1; + } + } + if (!thumbnailBaseDirName.endsWith("/")) { + thumbnailBaseDirName += "/"; + } + ThumbnailProvider::setThumbnailBaseDir(thumbnailBaseDirName); + } + + // List dir + QDir dir(imageDirName); + KFileItemList list; + Q_FOREACH(const QString &name, dir.entryList()) { + KUrl url = KUrl::fromLocalFile(dir.absoluteFilePath(name)); + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url); + list << item; + } + kWarning() << "Generating thumbnails for" << list.count() << "files"; + + // Start the job + QTime chrono; + ThumbnailProvider job; + job.setThumbnailGroup(group); + job.appendItems(list); + + chrono.start(); + + QEventLoop loop; + QObject::connect(&job, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + + kWarning() << "Time to generate thumbnails:" << chrono.restart(); + + waitForDeferredDeletes(); + while (!ThumbnailProvider::isThumbnailWriterEmpty()) { + QCoreApplication::processEvents(); + } + kWarning() << "Time to save pending thumbnails:" << chrono.restart(); + + return 0; +} diff --git a/kactivities/.gitignore b/kactivities/.gitignore new file mode 100644 index 00000000..1431d03b --- /dev/null +++ b/kactivities/.gitignore @@ -0,0 +1,12 @@ +.debug +tags +_tests +.videproject/ctags +*swp +*~ +.kdev4 +.cmake-params +.ycm_extra_conf.py +.ycm_extra_conf.pyc +.clang_complete +kactivities.kdev4 diff --git a/kactivities/.videproject/project.conf b/kactivities/.videproject/project.conf new file mode 100644 index 00000000..fb52c7bd --- /dev/null +++ b/kactivities/.videproject/project.conf @@ -0,0 +1,30 @@ +[Project] +Name=KDE Activities +Location=/opt/kde/src/libs/kactivities + +MakeCommand='OBJ_REPLACEMENT="s:/opt/kde/src/core/libs/kactivities:/opt/kde/build-clang/core/libs/kactivities:" makeobj' + +RunLocation=./ +RunCommand=./ + +[General] +OverrideVimCommands=1 +AutoInsertModeOnOpen=0 +Tags= +SourcePaths=. + +[KeyBindings] +SidePanelOpen='' +MakeProject='' +RunProject='' + +[Grep] +Root=. + +[Find] +Root=. + +[QuickBrowser] +iNotifyEnabled=0 +Root=. +TagsSystem=ctags diff --git a/kactivities/.videproject/vimrc b/kactivities/.videproject/vimrc new file mode 100644 index 00000000..a8dbe5c1 --- /dev/null +++ b/kactivities/.videproject/vimrc @@ -0,0 +1,2 @@ + +set tags+=/opt/kde4trunk/ctags/libQt.tags diff --git a/kactivities/CMakeLists.txt b/kactivities/CMakeLists.txt new file mode 100644 index 00000000..eef09b8d --- /dev/null +++ b/kactivities/CMakeLists.txt @@ -0,0 +1,32 @@ +project (KACTIVITIES) + +find_package (KDE4 REQUIRED) + +include (GenerateExportHeader) +include (KDE4Defaults) +include (MacroLibrary) +include (MacroOptionalAddSubdirectory) +include (FindPackageHandleStandardArgs) + +# Until this is set by FindQt, we are defining the +# QT_NO_DEBUG_OUTPUT if this is not a debug build + +string (TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_TOLOWER) +if (NOT CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") + add_definitions(-DQT_NO_DEBUG_OUTPUT) +endif() + + +set ( + CMAKE_MODULE_PATH + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules + ${CMAKE_MODULE_PATH} + ) + +if (KAMD_PEDANTIC_COMPILATION) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic") +endif () + +add_subdirectory (src) + +macro_display_feature_log () diff --git a/kactivities/MAINTAINER b/kactivities/MAINTAINER new file mode 100644 index 00000000..51d257d4 --- /dev/null +++ b/kactivities/MAINTAINER @@ -0,0 +1,3 @@ + +Current kactivities maintainer is: Ivan Čukić + diff --git a/kactivities/Mainpage.dox b/kactivities/Mainpage.dox new file mode 100644 index 00000000..3f08354f --- /dev/null +++ b/kactivities/Mainpage.dox @@ -0,0 +1,17 @@ +/** @mainpage KActivities + +@authors +See the copyright notices on the individual files. + +@maintainers +See the MAINTAINER file + +@licenses +@lgpl + +*/ + +// DOXYGEN_SET_PROJECT_NAME = KActivities +// DOXYGEN_SET_RECURSIVE = YES +// DOXYGEN_EXCLUDE_PATTERNS = *_p.h */private/* */tests/* */ontologies/* */service/* */scripts/* */workspace/* +// vim:ts=4:sw=4:expandtab:filetype=doxygen diff --git a/kactivities/README b/kactivities/README new file mode 100644 index 00000000..43a57c6f --- /dev/null +++ b/kactivities/README @@ -0,0 +1,6 @@ +See README.developers and README.packagers (the former is also for users). + +In order to properly display the files, use the GNU man command. + +man README.developers +man README.packagers diff --git a/kactivities/README.developers b/kactivities/README.developers new file mode 100644 index 00000000..7e4758b8 --- /dev/null +++ b/kactivities/README.developers @@ -0,0 +1,112 @@ + + +.\" " " " " " " " " " " " " " " " " " " " " " " " " " " " +.\" " +.\" It is best to view this file with the man tool: " +.\" man ./README.developers " +.\" " +.\" " " " " " " " " " " " " " " " " " " " " " " " " " " " + + +.TH README KAMD 2012-08-29 "KDE" "KActivities Developers" + +.SH COMMIT POLICY + +Every non-trivial patch must go through the review before it goes into the +master branch. + + http://git.reviewboard.kde.org + repository: kactivities + groups: plasma + people: Ivan Cukic + +If you don't have an account for identity.kde.org, you can send smaller +patches to the plasma-devel@kde.org mailing list, or (please don't) directly +to the repository maintainer (see MAINTAINER file). + + +.SH CODE POLICY + +The code needs to follow KDElibs coding style in *all* parts of the project, +not only the library. You can find more information about the style here: +http://techbase.kde.org/Policies/Kdelibs_Coding_Style + +Macros in CMakeLists.txt should be lowercase throughout the project, +and indentation should be 4, with the closing parenthesis in the same level +as the content. + +.SH COMPILER COMPATIBILITY + +The library (src/lib) needs to be compilable with all compilers that the +latest version of Qt (4.x) supports. + +Other parts require modern compilers. You can (and should) use more modern +C++ coding practices. Including auto, lambdas, smart pointers etc. You can +use anything that GCC 4.5 can compile. + +These are the compilers you need to test your patches against: + - GCC 4.5 + - GCC 4.7 + - LLVM/Clang 3.1 + +When you set up different builds alongside the main one, you can use +scripts/commit.sh to build them all before committing. The script +calls git commit if all builds finished successfully. See the script +for more info. + + +.SH FILE NAMING + +The library files are lower-case, apart from the pretty headers. +The service, and the rest of the repository should be in camel-case +(with the exception of source files that don't have corresponding +headers, or vice-versa). + + +.SH CONVENIENCE MACROS AND METHODS + +There are some convenience macros and methods defined in the headers placed +in the service/utils/ directory. + +.TP +.B nullptr +nullptr.h defines nullptr macro if the compiler doesn't support it. It is +defined as 0 which is far from perfect, but works good enough on obsolete +compilers. + +.TP +.B _override +override.h defines _override to express explicit virtual overrides. Will +expand to nothing on obsolete compilers. + +.TP +.B val +val.h defines a macro 'val' that expands to 'const auto'. People tend not +to use the const keyword even when they are defining a variable that will +not change. This way, it is easier to do so. For example, instead of writing: + + const QString path = KStandardDirs::locateLocal("data", ...); + const int id = object->id(); + +you can (and should) write this: + + val path = KStandardDirs::locateLocal("data", ...); + val id = object->id(); + +This should be always last included header since other parts of the project +or upstream libraries could have used val as a variable name. + +.TP +.B D_PTR +d_ptr.h and d_ptr_implementation.h define a smart pointer way of doing +the d-ptr (aka pimpl) idiom. + +.TP +.B remove_if +remove_if.h is a generic implementation of the erase-remove idiom + +.TP +.B for_each_assoc, find_if_assoc +for_each_assoc.h and find_if_assoc.h define the for_each and find_if +algorithms for associative containers. Works with both Qt and STL containers. + diff --git a/kactivities/README.packagers b/kactivities/README.packagers new file mode 100644 index 00000000..6b7155b7 --- /dev/null +++ b/kactivities/README.packagers @@ -0,0 +1,23 @@ + + +.\" " " " " " " " " " " " " " " " " " " " " " " " " " " " +.\" " +.\" It is best to view this file with the man tool: " +.\" man ./README.packagers " +.\" " +.\" " " " " " " " " " " " " " " " " " " " " " " " " " " " + + +.\" Best to view this file with the man tool +.TH README KAMD 2012-08-29 "KDE" "KActivities Packagers" + +.SH LIBRARY + +The library can be used even if the service is not running nor installed. +The dependencies are minimal since it is only a thin wrapper for the d-bus +service. + +.SH SERVICE + +.TP + diff --git a/kactivities/TODO b/kactivities/TODO new file mode 100644 index 00000000..1422fddc --- /dev/null +++ b/kactivities/TODO @@ -0,0 +1,11 @@ +Add focus and modification (src/service/plugins/sqlite/StatsPlugin.cpp:176) +A possible problem is that theoretically the dangling ones can be (src/service/plugins/activityranking/ActivityRanking.cpp:316) +Move this to job-based execution (src/service/Activities.cpp:140) +Remove (src/service/Event.h:57) +Test whether necessary services are running (src/workspace/kio/KioActivities.cpp:245) +This should be also done from time to time, (src/service/plugins/sqlite/StatsPlugin.cpp:111) +This should be removed as soon as we get rid of the ResourcesLinkedToActivity method (src/service/plugins/nepomuk/NepomukPlugin.cpp:478) +This should be tested - something is wrong here (src/service/plugins/activityranking/ActivityRanking.cpp:541) +update the service info (src/lib/core/resourceinstance.cpp:152) +update the service info (src/lib/core/resourceinstance.cpp:162) +We should move away from any GUI code (src/service/Application.cpp:107) diff --git a/kactivities/cmake/modules/CheckCxxFeatures.cmake b/kactivities/cmake/modules/CheckCxxFeatures.cmake new file mode 100644 index 00000000..a4a0f11f --- /dev/null +++ b/kactivities/cmake/modules/CheckCxxFeatures.cmake @@ -0,0 +1,113 @@ +# Macro that tests and returns whether a C++ feature is present in the +# current compiler + +set(CXX_CHECK_FEATURE_MODULE_DIR "${CMAKE_SOURCE_DIR}/cmake/modules") +set(CXX_FEATURES_SUPPORTED "") +set(CXX_FEATURES_UNSUPPORTED "") + +macro(CXX_PERFORM_TEST TEST_SOURCE_FILE TEST_TEST_BINARY_DIR EXPECTED_RESULT RESULT COMPILE_DEFINITIONS) + + try_run( + RUN_RESULT_VAR COMPILE_RESULT_VAR + "${TEST_BINARY_DIR}" "${TEST_SOURCE_FILE}" + COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS}" + COMPILE_OUTPUT_VARIABLE COMPILE_OUT + RUN_OUTPUT_VARIABLE RUN_OUT + ) + + set(RESULT_VAR FALSE) + + if (COMPILE_RESULT_VAR AND NOT RUN_RESULT_VAR) + set(RESULT_VAR TRUE) + endif (COMPILE_RESULT_VAR AND NOT RUN_RESULT_VAR) + + if (NOT ("${RESULT_VAR}" STREQUAL "${EXPECTED_RESULT}")) + # message ("Got ${RESULT_VAR} as a result, but ${EXPECTED_RESULT} expected") + + if (NOT ${COMPILE_RESULT_VAR}) + # message("------ compilation output ------") + # message("${COMPILE_OUT}") + endif (NOT ${COMPILE_RESULT_VAR}) + + if (${RUN_RESULT_VAR}) + # message("---------- run output ----------") + # message("${RUN_OUT}") + # message("Process returned: ${RUN_RESULT_VAR}") + endif (${RUN_RESULT_VAR}) + + # message("--------------------------------") + + set (${RESULT} FALSE) + + else () + set (${RESULT} TRUE) + + endif () + + + +endmacro(CXX_PERFORM_TEST TEST_SOURCE EXPECTED_RESULT RESULT) + + + +macro(CXX_CHECK_FEATURE CXX_VERSION FEATURE_NAME FEATURE_NUMBER RESULT_VAR COMPILE_DEFINITIONS) + + # Testing whether we have previously set the variable + if(NOT DEFINED ${RESULT_VAR}) + + set(TEST_BINARY_DIR + "${CMAKE_CURRENT_BINARY_DIR}/cxx-check-feature/cxx_${FEATURE_NUMBER}" + ) + + set(TEST_SOURCE_BASE + "${CXX_CHECK_FEATURE_MODULE_DIR}/${CXX_VERSION}-test-${FEATURE_NAME}-${FEATURE_NUMBER}" + ) + + set(TEST_SOURCE_FILE "${TEST_SOURCE_BASE}.cpp") + set(FAILTEST_SOURCE_FILE "${TEST_SOURCE_BASE}-fail.cpp") + + set(FEATURE_NAME + "'${FEATURE_NAME}' (${CXX_VERSION} N${FEATURE_NUMBER})" + ) + + message(STATUS "Checking C++ support for ${FEATURE_NAME}") + + string (COMPARE EQUAL "${CMAKE_CXX_COMPILER_ID}" "Clang" CMAKE_COMPILER_IS_CLANG) + string (COMPARE EQUAL "${CMAKE_CXX_COMPILER_ID}" "GNU" CMAKE_COMPILER_IS_GCC) + + set (ADD_COMPILE_DEFINITIONS "") + + if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG OR CMAKE_COMPILER_IS_GCC) + set (ADD_COMPILE_DEFINITIONS "-std=c++0x") + endif () + + if (EXISTS ${TEST_SOURCE_FILE}) + CXX_PERFORM_TEST(${TEST_SOURCE_FILE} ${TEST_BINARY_DIR} TRUE ${RESULT_VAR} "${COMPILE_DEFINITIONS} ${ADD_COMPILE_DEFINITIONS}") + endif () + + if (${RESULT_VAR} AND EXISTS ${FAILTEST_SOURCE_FILE}) + CXX_PERFORM_TEST(${FAILTEST_SOURCE_FILE} ${TEST_BINARY_DIR} FALSE ${RESULT_VAR} "${COMPILE_DEFINITIONS} ${ADD_COMPILE_DEFINITIONS}") + endif () + + if (${RESULT_VAR}) + message(STATUS "Checking C++ support for ${FEATURE_NAME} -- works") + set (CXX_FEATURES_SUPPORTED + "${CXX_FEATURES_SUPPORTED} ${FEATURE_NAME} (${FEATURE_NUMBER})," + ) + + else () + message(STATUS "Checking C++ support for ${FEATURE_NAME} -- not supported") + set (CXX_FEATURES_UNSUPPORTED + "${CXX_FEATURES_UNSUPPORTED} ${FEATURE_NAME} (${FEATURE_NUMBER})," + ) + + endif () + + # This would break the feature reporting on second call of cmake + # TODO: Fix? + # set(${RESULT_VAR} ${${RESULT_VAR}} CACHE INTERNAL "C++ support for ${FEATURE_NAME}") + + endif(NOT DEFINED ${RESULT_VAR}) + +endmacro(CXX_CHECK_FEATURE) + diff --git a/kactivities/cmake/modules/c++-test-override-attr-none-fail.cpp b/kactivities/cmake/modules/c++-test-override-attr-none-fail.cpp new file mode 100644 index 00000000..0e533d3c --- /dev/null +++ b/kactivities/cmake/modules/c++-test-override-attr-none-fail.cpp @@ -0,0 +1,27 @@ +#include + +class A { + public: + virtual int fn(int arg) { + return 10 * arg; + }; + +}; + +class B: public A { + public: + virtual int fn(long arg) __attribute__((override)) { + return 20 * arg; + }; + +}; + +int main() +{ + A * a = new A(); + A * b = new B(); + + int result = a->fn(2) - b->fn(1); + + return 0; +} diff --git a/kactivities/cmake/modules/c++-test-override-attr-none.cpp b/kactivities/cmake/modules/c++-test-override-attr-none.cpp new file mode 100644 index 00000000..0e533d3c --- /dev/null +++ b/kactivities/cmake/modules/c++-test-override-attr-none.cpp @@ -0,0 +1,27 @@ +#include + +class A { + public: + virtual int fn(int arg) { + return 10 * arg; + }; + +}; + +class B: public A { + public: + virtual int fn(long arg) __attribute__((override)) { + return 20 * arg; + }; + +}; + +int main() +{ + A * a = new A(); + A * b = new B(); + + int result = a->fn(2) - b->fn(1); + + return 0; +} diff --git a/kactivities/cmake/modules/c++11-test-auto-N2546.cpp b/kactivities/cmake/modules/c++11-test-auto-N2546.cpp new file mode 100644 index 00000000..31bfd004 --- /dev/null +++ b/kactivities/cmake/modules/c++11-test-auto-N2546.cpp @@ -0,0 +1,20 @@ +#include + +int main() +{ + auto i = 5; + auto f = 3.14159f; + auto d = 3.14159; + auto l = 3l; + + bool checkFloats = (sizeof(f) < sizeof(d)); + bool checkInts = (sizeof(i) == sizeof(int)); + bool checkLongs = (sizeof(l) == sizeof(long)); + + std::cout + << "Float sizes correct: " << checkFloats << std::endl + << "Integer size correct: " << checkFloats << std::endl + << "Long sizes correct: " << checkFloats << std::endl; + + return (checkFloats && checkInts && checkLongs) ? 0 : 1; +} diff --git a/kactivities/cmake/modules/c++11-test-initializer-lists-N2672.cpp b/kactivities/cmake/modules/c++11-test-initializer-lists-N2672.cpp new file mode 100644 index 00000000..f58c0aa0 --- /dev/null +++ b/kactivities/cmake/modules/c++11-test-initializer-lists-N2672.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +struct test { + int i; + int j; + double k; + std::string s; +}; + +int main() +{ + test t { 1, 2, 4, "asdf" }; + std::vector v { 1, 2, 3, 4 }; + + return ( + t.i == v[0] + && t.j == v[1] + && t.k > v[2] + && t.s.size() == v[3] + ) + ? 0 : 1; +} diff --git a/kactivities/cmake/modules/c++11-test-lambda-N2927.cpp b/kactivities/cmake/modules/c++11-test-lambda-N2927.cpp new file mode 100644 index 00000000..16fa55ee --- /dev/null +++ b/kactivities/cmake/modules/c++11-test-lambda-N2927.cpp @@ -0,0 +1,25 @@ +#include + +int main() +{ + int ref = 10; + int pass = 2; + + ([&](int mul) { + ref *= mul; + })(pass); + + bool checkRefNoref = (ref == 10 * pass); + + int result = ([=](int mul) { + return ref * mul; + })(pass); + + bool checkReturn = (result == 10 * pass * pass); + + std::cout + << "Capture by reference: " << checkRefNoref << std::endl + << "Return a value: " << checkReturn << std::endl; + + return (checkRefNoref && checkReturn) ? 0 : 1; +} diff --git a/kactivities/cmake/modules/c++11-test-nullptr-N2431-fail.cpp b/kactivities/cmake/modules/c++11-test-nullptr-N2431-fail.cpp new file mode 100644 index 00000000..607095ad --- /dev/null +++ b/kactivities/cmake/modules/c++11-test-nullptr-N2431-fail.cpp @@ -0,0 +1,5 @@ +int main() +{ + int i = nullptr; + return 1; +} diff --git a/kactivities/cmake/modules/c++11-test-nullptr-N2431.cpp b/kactivities/cmake/modules/c++11-test-nullptr-N2431.cpp new file mode 100644 index 00000000..ca298d2a --- /dev/null +++ b/kactivities/cmake/modules/c++11-test-nullptr-N2431.cpp @@ -0,0 +1,5 @@ +int main() +{ + int * test = nullptr; + return test ? 1 : 0; +} diff --git a/kactivities/cmake/modules/c++11-test-override-N3206-fail.cpp b/kactivities/cmake/modules/c++11-test-override-N3206-fail.cpp new file mode 100644 index 00000000..0919d5eb --- /dev/null +++ b/kactivities/cmake/modules/c++11-test-override-N3206-fail.cpp @@ -0,0 +1,27 @@ +#include + +class A { + public: + virtual int fn(int arg) { + return 10 * arg; + }; + +}; + +class B: public A { + public: + virtual int fn(long arg) override { + return 20 * arg; + }; + +}; + +int main() +{ + A * a = new A(); + A * b = new B(); + + int result = a->fn(2) - b->fn(1); + + return 0; +} diff --git a/kactivities/cmake/modules/c++11-test-override-N3206.cpp b/kactivities/cmake/modules/c++11-test-override-N3206.cpp new file mode 100644 index 00000000..10d8e207 --- /dev/null +++ b/kactivities/cmake/modules/c++11-test-override-N3206.cpp @@ -0,0 +1,27 @@ +#include + +class A { + public: + virtual int fn(int arg) { + return 10 * arg; + }; + +}; + +class B: public A { + public: + virtual int fn(int arg) override { + return 20 * arg; + }; + +}; + +int main() +{ + A * a = new A(); + A * b = new B(); + + int result = a->fn(2) - b->fn(1); + + return 0; +} diff --git a/kactivities/cmake/modules/c++11-test-unique_ptr-none.cpp b/kactivities/cmake/modules/c++11-test-unique_ptr-none.cpp new file mode 100644 index 00000000..17107751 --- /dev/null +++ b/kactivities/cmake/modules/c++11-test-unique_ptr-none.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +struct Question { + long answer; + std::string description; +}; + +int main() +{ + std::unique_ptr < Question > node_original(new Question()); + + node_original->answer = 42; + node_original->description = "The Answer to the Ultimate Question of Life, the Universe, and Everything"; + + std::unique_ptr < Question > node_second(std::move(node_original)); + + return (!node_original && (node_second->answer == 42))?0:1; +} diff --git a/kactivities/cmake/modules/c++11-test-variadic-templates-N2242.cpp b/kactivities/cmake/modules/c++11-test-variadic-templates-N2242.cpp new file mode 100644 index 00000000..a9ef03d5 --- /dev/null +++ b/kactivities/cmake/modules/c++11-test-variadic-templates-N2242.cpp @@ -0,0 +1,20 @@ +#include + +template +int addall(T value) +{ + return value; +} + +template +int addall(T value, Args ... args) +{ + return value + addall(args...); +} + +int main() +{ + int v1 = addall(1, 2, 3, 4, 5); // 15 + int v2 = addall(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4); // 40 + return ((v1 == 15) && (v2 == 40)) ? 0 : 1; +} diff --git a/kactivities/scripts/commit.sh b/kactivities/scripts/commit.sh new file mode 100755 index 00000000..3377ff37 --- /dev/null +++ b/kactivities/scripts/commit.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# The script finds build directories for the current +# src directory and builds them +# +# For example, for the source dir: +# /some/path/kde/src/project/whatever +# It finds: +# /some/path/kde/build*/project/whatever + +current_dir=`pwd` + +all_root_dir=`pwd | sed 's#/src/.*##'` +src_root_dir=$all_root_dir/src + +echo "src: $src_root_dir" + +for build_root_dir in $all_root_dir/build*; do + echo "building in $build_root_dir" + + cd $current_dir + current_dir_log=`OBJ_REPLACEMENT=s#$src_root_dir#$build_root_dir# makeobj` + if [ "$?" = "0" ] + then + echo "... success" + else + echo "... FAILED" + echo $current_dir_log + exit + fi + +done + +git commit + diff --git a/kactivities/scripts/delete-activities.sh b/kactivities/scripts/delete-activities.sh new file mode 100755 index 00000000..4c29c46d --- /dev/null +++ b/kactivities/scripts/delete-activities.sh @@ -0,0 +1,3 @@ +#!/bin/zsh +alias nepomukcmd="sopranocmd --socket `kde4-config --path socket`nepomuk-socket --model main --nrl" +for res in `nepomukcmd --foo query 'select ?r { ?r a kao:Activity . }'`; nepomukcmd rm $res diff --git a/kactivities/scripts/delete-stats.sh b/kactivities/scripts/delete-stats.sh new file mode 100755 index 00000000..b35e3f6c --- /dev/null +++ b/kactivities/scripts/delete-stats.sh @@ -0,0 +1,4 @@ +#!/bin/zsh +alias nepomukcmd="sopranocmd --socket `kde4-config --path socket`nepomuk-socket --model main --nrl" +for res in `nepomukcmd --foo query 'select ?r { ?r a kao:ResourceScoreCache . }'`; nepomukcmd rm $res +for res in `nepomukcmd --foo query 'select ?r { ?r a nuao:DesktopEvent . }'`; nepomukcmd rm $res diff --git a/kactivities/scripts/run-krazy.sh b/kactivities/scripts/run-krazy.sh new file mode 100755 index 00000000..d0783fde --- /dev/null +++ b/kactivities/scripts/run-krazy.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +if [ ! -f "scripts/run-krazy.sh" ]; +then + echo "This script needs to be started from KAMD's root directory" + exit +fi + +DIRS=$1 + +if [ ! -n "$1" ]; +then + DIRS="lib service utils workspace" +fi + + + +echo $DIRS +CURRENT_DIRECTORY=$PWD + +for dir in $DIRS; +do + echo "Running krazy2 on $dir ..." + cd $CURRENT_DIRECTORY/src/$dir && krazy2all --exclude license > /tmp/$dir.krazy +done diff --git a/kactivities/scripts/update-todo.sh b/kactivities/scripts/update-todo.sh new file mode 100755 index 00000000..ea42e3d3 --- /dev/null +++ b/kactivities/scripts/update-todo.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +grep -n -I -r TODO src | sed 's/^\([^:]*:[^:]*\):[- \/#]*\(.*\)/\2\t(\1)/' | sed 's/^TODO: //' | sed 's/(TODO)//' | sed 's/BLOCKER/!!! BLOCKER !!!/' | sort | tee TODO diff --git a/kactivities/scripts/ycm_extra_conf.py b/kactivities/scripts/ycm_extra_conf.py new file mode 100644 index 00000000..7693730d --- /dev/null +++ b/kactivities/scripts/ycm_extra_conf.py @@ -0,0 +1,129 @@ +import os +import ycm_core +from clang_helpers import PrepareClangFlags + +# Set this to the absolute path to the folder (NOT the file!) containing the +# compile_commands.json file to use that instead of 'flags'. See here for +# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html +# Most projects will NOT need to set this to anything; you can just change the +# 'flags' list of compilation flags. Notice that YCM itself uses that approach. +compilation_database_folder = '' + +# These are the compilation flags that will be used in case there's no +# compilation database set. +flags = [ +'-Wall', +'-Wextra', +'-Werror', +'-Wc++98-compat', +'-Wno-long-long', +'-Wno-variadic-macros', +'-DUSE_CLANG_COMPLETER', +'-std=c++11', +'-x', +'c++', +'-I/opt/kde/build/core/libs/kactivities/src', +'-I/opt/kde/src/core/libs/kactivities/src', +'-I/opt/kde/usr/kde/include', +'-I/opt/kde/usr/kde/include/KDE', +'-I/usr/include', +'-I/usr/include/qt4', +'-I/usr/include/qt4/Qt', +'-I/usr/include/qt4/Qt3Support', +'-I/usr/include/qt4/QtCore', +'-I/usr/include/qt4/QtDBus', +'-I/usr/include/qt4/QtDeclarative', +'-I/usr/include/qt4/QtDesigner', +'-I/usr/include/qt4/QtDesigner', +'-I/usr/include/qt4/QtGui', +'-I/usr/include/qt4/QtHelp', +'-I/usr/include/qt4/QtNetwork', +'-I/usr/include/qt4/QtOpenGL', +'-I/usr/include/qt4/QtScript', +'-I/usr/include/qt4/QtScriptTools', +'-I/usr/include/qt4/QtSql', +'-I/usr/include/qt4/QtSvg', +'-I/usr/include/qt4/QtTest', +'-I/usr/include/qt4/QtUiTools', +'-I/usr/include/qt4/QtWebKit', +'-I/usr/include/qt4/QtXml', +'-I/usr/include/qt4/QtXmlPatterns', +'-I/usr/share/qt4/mkspecs/default', +'-D_BSD_SOURCE', +'-DQT_NO_DEBUG_OUTPUT', +'-DQT_USE_FAST_CONCATENATION', +'-DQT_USE_FAST_OPERATOR_PLUS', +'-D_XOPEN_SOURCE=500', +'-D_BSD_SOURCE', +'-DQT_NO_STL', +'-DQT_NO_CAST_TO_ASCII', +'-D_REENTRANT', +'-DKDE_DEPRECATED_WARNINGS', +'-DKDE4_CMAKE_TOPLEVEL_DIR_LENGTH=22' +] + +if compilation_database_folder: + database = ycm_core.CompilationDatabase( compilation_database_folder ) +else: + database = None + + +def DirectoryOfThisScript(): + return os.path.dirname( os.path.abspath( __file__ ) ) + + +def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): + if not working_directory: + return flags + new_flags = [] + make_next_absolute = False + path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] + for flag in flags: + new_flag = flag + + if make_next_absolute: + make_next_absolute = False + if not flag.startswith( '/' ): + new_flag = os.path.join( working_directory, flag ) + + for path_flag in path_flags: + if flag == path_flag: + make_next_absolute = True + break + + if flag.startswith( path_flag ): + path = flag[ len( path_flag ): ] + new_flag = path_flag + os.path.join( working_directory, path ) + break + + if new_flag: + new_flags.append( new_flag ) + return new_flags + + +def FlagsForFile( filename ): + if database: + # Bear in mind that compilation_info.compiler_flags_ does NOT return a + # python list, but a "list-like" StringVec object + compilation_info = database.GetCompilationInfoForFile( filename ) + final_flags = PrepareClangFlags( + MakeRelativePathsInFlagsAbsolute( + compilation_info.compiler_flags_, + compilation_info.compiler_working_dir_ ), + filename ) + + # NOTE: This is just for YouCompleteMe; it's highly likely that your project + # does NOT need to remove the stdlib flag. DO NOT USE THIS IN YOUR + # ycm_extra_conf IF YOU'RE NOT 100% YOU NEED IT. + try: + final_flags.remove( '-stdlib=libc++' ) + except ValueError: + pass + else: + relative_to = DirectoryOfThisScript() + final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) + + return { + 'flags': final_flags, + 'do_cache': True + } diff --git a/kactivities/src/CMakeLists.txt b/kactivities/src/CMakeLists.txt new file mode 100644 index 00000000..bd345a76 --- /dev/null +++ b/kactivities/src/CMakeLists.txt @@ -0,0 +1,101 @@ + +set (CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) + +# Installation directory for ontologies. Needed here because used in both ontology/ and lib/ sub +# directories. + +set (KACTIVITIES_ONTOLOGIES_DIR ${CMAKE_INSTALL_PREFIX}/share/ontology/kde) + +add_definitions (-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) + +# Checking for Nepomuk +macro_optional_find_package (NepomukCore) +macro_log_feature ( + NepomukCore_FOUND + "Nepomuk Core" "Nepomuk Core" "https://projects.kde.org/nepomuk-core" FALSE "" + "STRONGLY_RECOMMENDED: Nepomuk is needed for some activity-related info" + ) + +if (NepomukCore_FOUND) + find_package (Soprano) + macro_log_feature ( + Soprano_FOUND + "Soprano" "Semantic Desktop Storing" "http://soprano.sourceforge.net" TRUE "" + "Soprano is needed to build and use the Nepomuk related functionalities" + ) + + include (SopranoAddOntology) +endif () + +if (NepomukCore_FOUND AND Soprano_FOUND) + set (HAVE_NEPOMUK 1) +endif () + +# Testing for C++0x/C++11 features + +include (CheckCxxFeatures) + +cxx_check_feature ("c++11" "auto" "N2546" HAVE_CXX11_AUTO "${ADDITIONAL_DEFINITIONS}") +cxx_check_feature ("c++11" "nullptr" "N2431" HAVE_CXX11_NULLPTR "${ADDITIONAL_DEFINITIONS}") +cxx_check_feature ("c++11" "lambda" "N2927" HAVE_CXX11_LAMBDA "${ADDITIONAL_DEFINITIONS}") +cxx_check_feature ("c++11" "override" "N3206" HAVE_CXX11_OVERRIDE "${ADDITIONAL_DEFINITIONS}") +cxx_check_feature ("c++11" "unique_ptr" "none" HAVE_CXX11_UNIQUE_PTR "${ADDITIONAL_DEFINITIONS}") +cxx_check_feature ("c++11" "variadic-templates" "N2242" HAVE_CXX11_VARIADIC_TEMPLATES "${ADDITIONAL_DEFINITIONS}") +cxx_check_feature ("c++11" "initializer-lists" "N2672" HAVE_CXX11_INITIALIZER_LISTS "${ADDITIONAL_DEFINITIONS}") + +add_subdirectory (lib) + +# config file + +set (KAMD_DATA_DIR "${DATA_INSTALL_DIR}/activitymanager/") + +configure_file (config-features.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-features.h) + +# main part + +option (KACTIVITIES_LIBRARY_ONLY "If true, compiles only the KActivities library, without the service and other modules." OFF) + +if (NOT KACTIVITIES_LIBRARY_ONLY) + string (COMPARE EQUAL "${CXX_FEATURES_UNSUPPORTED}" "" CXX_COMPILER_IS_MODERN) + + if (NOT HAVE_CXX11_AUTO OR NOT HAVE_CXX11_LAMBDA OR NOT HAVE_CXX11_UNIQUE_PTR OR NOT HAVE_CXX11_VARIADIC_TEMPLATES OR NOT HAVE_CXX11_INITIALIZER_LISTS) + + macro_log_feature (FALSE + "C++11 enabled compiler" + "GCC >=4.5 or Clang 3.1" + "http://www.open-std.org/jtc1/sc22/wg21/" FALSE "" + "REQUIRED: You need a more modern compiler in order to build the Activity Manager daemon" + ) + + else () + + if (CXX_COMPILER_IS_MODERN) + macro_log_feature ( + CXX_COMPILER_IS_MODERN + "C++11 enabled compiler" + "Your compiler is state-of-the-art" + "http://www.open-std.org/jtc1/sc22/wg21/" FALSE "" + "Your compiler doesn't support the following features: ${CXX_FEATURES_UNSUPPORTED} but + the list of the supported ones is sufficient for the build: ${CXX_FEATURES_SUPPORTED}" + ) + else () + macro_log_feature ( + CXX_COMPILER_IS_MODERN + "C++11 enabled compiler" + "Having to use the latest versions of GCC and Clang would be awesome" + "http://www.open-std.org/jtc1/sc22/wg21/" FALSE "" + "Your compiler doesn't support the following features: ${CXX_FEATURES_UNSUPPORTED} but + the list of the supported ones is sufficient for the build: ${CXX_FEATURES_SUPPORTED}" + ) + endif () + + add_subdirectory (service) + + # No point in having workspace addons without the service + add_subdirectory (workspace) + + endif () +endif() + +add_subdirectory (ontologies) + diff --git a/kactivities/src/common/dbus/org.kde.ActivityManager.Activities.cpp b/kactivities/src/common/dbus/org.kde.ActivityManager.Activities.cpp new file mode 100644 index 00000000..40e99952 --- /dev/null +++ b/kactivities/src/common/dbus/org.kde.ActivityManager.Activities.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2010, 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "org.kde.ActivityManager.Activities.h" + +#include +#include + +namespace details { + +class ActivityInfoStaticInit { +public: + ActivityInfoStaticInit() + { + qDBusRegisterMetaType < ActivityInfo > (); + qDBusRegisterMetaType < ActivityInfoList > (); + } + + static ActivityInfoStaticInit _instance; + +}; + +ActivityInfoStaticInit ActivityInfoStaticInit::_instance; + +} // namespace details + +QDBusArgument & operator << (QDBusArgument & arg, const ActivityInfo r) +{ + arg.beginStructure(); + + arg << r.id; + arg << r.name; + arg << r.icon; + arg << r.state; + + arg.endStructure(); + + return arg; +} + +const QDBusArgument & operator >> (const QDBusArgument & arg, ActivityInfo & r) +{ + arg.beginStructure(); + + arg >> r.id; + arg >> r.name; + arg >> r.icon; + arg >> r.state; + + arg.endStructure(); + + return arg; +} + +QDebug operator << (QDebug dbg, const ActivityInfo & r) +{ + dbg << "ActivityInfo(" << r.id << r.name << ")"; + return dbg.space(); +} + diff --git a/kactivities/src/common/dbus/org.kde.ActivityManager.Activities.h b/kactivities/src/common/dbus/org.kde.ActivityManager.Activities.h new file mode 100644 index 00000000..7c598bf2 --- /dev/null +++ b/kactivities/src/common/dbus/org.kde.ActivityManager.Activities.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2010, 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KAMD_ACTIVITIES_DBUS_H +#define KAMD_ACTIVITIES_DBUS_H + +#include +#include +#include +#include + +struct ActivityInfo { + QString id; + QString name; + QString icon; + int state; +}; + +typedef QList ActivityInfoList; + +Q_DECLARE_METATYPE(ActivityInfo) +Q_DECLARE_METATYPE(ActivityInfoList) + +QDBusArgument & operator << (QDBusArgument & arg, const ActivityInfo); +const QDBusArgument & operator >> (const QDBusArgument & arg, ActivityInfo & rec); + +QDebug operator << (QDebug dbg, const ActivityInfo & r); + +#endif // KAMD_ACTIVITIES_DBUS_H diff --git a/kactivities/src/common/dbus/org.kde.ActivityManager.Activities.xml b/kactivities/src/common/dbus/org.kde.ActivityManager.Activities.xml new file mode 100644 index 00000000..659f2d37 --- /dev/null +++ b/kactivities/src/common/dbus/org.kde.ActivityManager.Activities.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kactivities/src/common/dbus/org.kde.ActivityManager.Features.xml b/kactivities/src/common/dbus/org.kde.ActivityManager.Features.xml new file mode 100644 index 00000000..64f8ffba --- /dev/null +++ b/kactivities/src/common/dbus/org.kde.ActivityManager.Features.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/kactivities/src/common/dbus/org.kde.ActivityManager.Resources.xml b/kactivities/src/common/dbus/org.kde.ActivityManager.Resources.xml new file mode 100644 index 00000000..ca3ee24c --- /dev/null +++ b/kactivities/src/common/dbus/org.kde.ActivityManager.Resources.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kactivities/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml b/kactivities/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml new file mode 100644 index 00000000..8a079adc --- /dev/null +++ b/kactivities/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kactivities/src/config-features.h.cmake b/kactivities/src/config-features.h.cmake new file mode 100644 index 00000000..f0ce3f7d --- /dev/null +++ b/kactivities/src/config-features.h.cmake @@ -0,0 +1,14 @@ +#ifndef CONFIG_FEATURES_H_ +#define CONFIG_FEATURES_H_ + +#cmakedefine01 HAVE_NEPOMUK + +#cmakedefine KAMD_DATA_DIR "@KAMD_DATA_DIR@" + +#cmakedefine01 HAVE_CXX11_AUTO +#cmakedefine01 HAVE_CXX11_NULLPTR +#cmakedefine01 HAVE_CXX11_LAMBDA +#cmakedefine01 HAVE_CXX11_OVERRIDE +#cmakedefine01 HAVE_CXX_OVERRIDE_ATTR + +#endif diff --git a/kactivities/src/lib/CMakeLists.txt b/kactivities/src/lib/CMakeLists.txt new file mode 100644 index 00000000..5cd53844 --- /dev/null +++ b/kactivities/src/lib/CMakeLists.txt @@ -0,0 +1,7 @@ + +add_subdirectory (core) + +if (NepomukCore_FOUND) + add_subdirectory (models) +endif () + diff --git a/kactivities/src/lib/core/CMakeLists.txt b/kactivities/src/lib/core/CMakeLists.txt new file mode 100644 index 00000000..f230d2ee --- /dev/null +++ b/kactivities/src/lib/core/CMakeLists.txt @@ -0,0 +1,196 @@ +project (kactivities) + +find_package (Qt4 REQUIRED) +find_package (KDE4 REQUIRED) +include (KDE4Defaults) +include (MacroLibrary) +include (MacroOptionalAddSubdirectory) +include (FindPackageHandleStandardArgs) + +# ======================================================= +# Information to update before to release this library. + +# Library version history: +# API ABI +# 0.1.0 => 0.1.0 +# 0.1.1 => 0.1.1 +# 0.2.0 => 0.2.0 +# 6.1.0 => 6.1.0 +# 6.2.0 => 6.2.0 + +# Library API version +set (KACTIVITIES_LIB_MAJOR_VERSION "6") +set (KACTIVITIES_LIB_MINOR_VERSION "2") +set (KACTIVITIES_LIB_PATCH_VERSION "0") + +# Suffix to add at end of version string. Usual values are: +# "-git" : alpha code unstable from git. Do not use in production +# "-beta1" : beta1 release. +# "-beta2" : beta2 release. +# "-beta3" : beta3 release. +# "-rc" : release candidate. +# "" : final relase. Can be used in production. +set (KACTIVITIES_LIB_SUFFIX_VERSION "") + +# Library ABI version used by linker. +# For details : http://www.gnu.org/software/libtool/manual/libtool.html#Updating-version-info +set (KACTIVITIES_LIB_SO_CUR_VERSION "6") +set (KACTIVITIES_LIB_SO_REV_VERSION "2") +set (KACTIVITIES_LIB_SO_AGE_VERSION "0") + +# ======================================================= +# Set env. variables accordinly. + +set (KACTIVITIES_INCLUDE_DIR + "${CMAKE_CURRENT_SOURCE_DIR}/.." + "${CMAKE_CURRENT_SOURCE_DIR}/" + CACHE STRING + "Location of libkactivities headers" FORCE) +set (KACTIVITIES_LIBS + "kactivities" + CACHE STRING + "Location of libkactivities binary" FORCE) + +set (KACTIVITIES_LIB_VERSION_STRING "${KACTIVITIES_LIB_MAJOR_VERSION}.${KACTIVITIES_LIB_MINOR_VERSION}.${KACTIVITIES_LIB_PATCH_VERSION}${KACTIVITIES_LIB_SUFFIX_VERSION}") +set (KACTIVITIES_LIB_VERSION_ID "0x0${KACTIVITIES_LIB_MAJOR_VERSION}0${KACTIVITIES_LIB_MINOR_VERSION}0${KACTIVITIES_LIB_PATCH_VERSION}") +set (KACTIVITIES_LIB_SO_VERSION_STRING "${KACTIVITIES_LIB_SO_CUR_VERSION}.${KACTIVITIES_LIB_SO_REV_VERSION}.${KACTIVITIES_LIB_SO_AGE_VERSION}") + +add_definitions (${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS} ${KDE4_DEFINITIONS}) + +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${QDBUS_INCLUDE_DIRS} + ${KDE4_INCLUDES} + ${KDE4_KIO_INCLUDES} + ) + +set ( + kactivities_LIB_SRCS + + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.cpp + consumer.cpp + controller.cpp + info.cpp + manager_p.cpp + resourceinstance.cpp + version.cpp + ) + +set_source_files_properties ( + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.xml + PROPERTIES + INCLUDE ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.h + ) + +qt4_add_dbus_interface ( + kactivities_LIB_SRCS + + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.xml + activities_interface + ) + +qt4_add_dbus_interface ( + kactivities_LIB_SRCS + + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Resources.xml + resources_interface + ) + +qt4_add_dbus_interface ( + kactivities_LIB_SRCS + + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Features.xml + features_interface + ) + +qt4_add_dbus_interface ( + kactivities_LIB_SRCS + + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml + resources_linking_interface + ) + +kde4_add_library ( + kactivities SHARED + ${kactivities_LIB_SRCS} + ) + +set_target_properties ( + kactivities + PROPERTIES + VERSION ${KACTIVITIES_LIB_SO_VERSION_STRING} + SOVERSION ${KACTIVITIES_LIB_SO_CUR_VERSION} + ) + +target_link_libraries ( + kactivities + ${KDE4_KDECORE_LIBS} + ) + +## install + +set ( + kactivities_LIB_HEADERS + consumer.h + controller.h + info.h + resourceinstance.h + version.h + kactivities_export.h + ) + +set ( + kactivities_LIB_PRETTY_HEADERS + includes/KActivities/Consumer + includes/KActivities/Controller + includes/KActivities/Info + includes/KActivities/ResourceInstance + includes/KActivities/Version + ) + +install ( + FILES ${kactivities_LIB_HEADERS} + DESTINATION ${INCLUDE_INSTALL_DIR}/kactivities + COMPONENT Devel + ) + +install ( + FILES ${kactivities_LIB_PRETTY_HEADERS} + DESTINATION ${INCLUDE_INSTALL_DIR}/KDE/KActivities + COMPONENT Devel + ) + +install ( + TARGETS kactivities + EXPORT kactivitiesLibraryTargets + ${INSTALL_TARGETS_DEFAULT_ARGS} + ) + +install ( + EXPORT kactivitiesLibraryTargets + DESTINATION ${LIB_INSTALL_DIR}/cmake/KActivities + FILE KActivitiesLibraryTargets.cmake + ) + +if (NOT WIN32) + configure_file (${CMAKE_CURRENT_SOURCE_DIR}/libkactivities.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libkactivities.pc) + install (FILES ${CMAKE_CURRENT_BINARY_DIR}/libkactivities.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig ) +endif () + +configure_file (KActivitiesConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/KActivitiesConfig.cmake @ONLY) +macro_write_basic_cmake_version_file ( + ${CMAKE_CURRENT_BINARY_DIR}/KActivitiesConfigVersion.cmake + ${KACTIVITIES_LIB_MAJOR_VERSION} + ${KACTIVITIES_LIB_MINOR_VERSION} + ${KACTIVITIES_LIB_PATCH_VERSION} + ) + +install ( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/KActivitiesConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/KActivitiesConfigVersion.cmake + DESTINATION + ${LIB_INSTALL_DIR}/cmake/KActivities +) + diff --git a/kactivities/src/lib/core/KActivitiesConfig.cmake.in b/kactivities/src/lib/core/KActivitiesConfig.cmake.in new file mode 100644 index 00000000..f70e798b --- /dev/null +++ b/kactivities/src/lib/core/KActivitiesConfig.cmake.in @@ -0,0 +1,24 @@ +get_filename_component(myDir ${CMAKE_CURRENT_LIST_FILE} PATH) # get the directory where I myself am +get_filename_component(rootDir ${myDir}/@relInstallDir@ ABSOLUTE) # get the chosen install prefix + +# set the version of myself +set(KACTIVITIES_VERSION_MAJOR @KACTIVITIES_LIB_MAJOR_VERSION@) +set(KACTIVITIES_VERSION_MINOR @KACTIVITIES_LIB_MINOR_VERSION@) +set(KACTIVITIES_VERSION_PATCH @KACTIVITIES_LIB_PATCH_VERSION@) +set(KACTIVITIES_VERSION @KACTIVITIES_LIB_MAJOR_VERSION@.@KACTIVITIES_LIB_MINOR_VERSION@.@KACTIVITIES_LIB_PATCH_VERSION@) +set(KACTIVITIES_VERSION_STRING "@KACTIVITIES_LIB_MAJOR_VERSION@.@KACTIVITIES_LIB_MINOR_VERSION@.@KACTIVITIES_LIB_PATCH_VERSION@") + +# what is my include directory +# we have _DIRS for compat with existing usage +SET(KACTIVITIES_INCLUDE_DIRS "@INCLUDE_INSTALL_DIR@" "@INCLUDE_INSTALL_DIR@/KDE" CACHE PATH "Include path for the KActivities library") +set(KACTIVITIES_INCLUDES "${rootDir}/@INCLUDE_INSTALL_DIR@" "@INCLUDE_INSTALL_DIR@/KDE") +set(KACTIVITIES_ONTOLOGIES_DIR "@KACTIVITIES_ONTOLOGIES_DIR@") + +# import the exported targets +include(${myDir}/KActivitiesLibraryTargets.cmake) + +# set the expected library variable +# XXX: only KACTIVITIES_LIBRARIES should be used according to CMake's conventions, +# KACTIVITIES_LIBRARY is kept for compatibility with all the places using it. +set(KACTIVITIES_LIBRARY kactivities) +set(KACTIVITIES_LIBRARIES kactivities) diff --git a/kactivities/src/lib/core/consumer.cpp b/kactivities/src/lib/core/consumer.cpp new file mode 100644 index 00000000..ff54b7fe --- /dev/null +++ b/kactivities/src/lib/core/consumer.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2010, 2011, 2012 Ivan Cukic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "consumer.h" +#include "consumer_p.h" +#include "manager_p.h" + +#include + +namespace KActivities { + +static QString nulluuid = "00000000-0000-0000-0000-000000000000"; +ConsumerPrivate * ConsumerPrivate::s_instance = 0; + +ConsumerPrivate * ConsumerPrivate::self(QObject * consumer) +{ + if (!s_instance) { + s_instance = new ConsumerPrivate(); + } + + s_instance->consumers << consumer; + + return s_instance; +} + +void ConsumerPrivate::free(QObject * consumer) +{ + consumers.remove(consumer); + + if (consumers.isEmpty()) { + s_instance = 0; + deleteLater(); + } +} + +KAMD_RETRIEVE_REMOTE_VALUE_HANDLER(QString, ConsumerPrivate, currentActivity, nulluuid) +KAMD_RETRIEVE_REMOTE_VALUE_HANDLER(QStringList, ConsumerPrivate, listActivities, QStringList()) +KAMD_RETRIEVE_REMOTE_VALUE_HANDLER(QStringList, ConsumerPrivate, runningActivities, QStringList()) + +ConsumerPrivate::ConsumerPrivate() + : currentActivityCallWatcher(0), + listActivitiesCallWatcher(0), + runningActivitiesCallWatcher(0) +{ + connect(Manager::activities(), SIGNAL(CurrentActivityChanged(const QString &)), + this, SLOT(setCurrentActivity(const QString &))); + connect(Manager::activities(), SIGNAL(ActivityAdded(QString)), + this, SLOT(addActivity(QString))); + connect(Manager::activities(), SIGNAL(ActivityRemoved(QString)), + this, SLOT(removeActivity(QString))); + connect(Manager::activities(), SIGNAL(ActivityStateChanged(QString,int)), + this, SLOT(setActivityState(QString, int))); + + connect(Manager::self(), SIGNAL(servicePresenceChanged(bool)), + this, SLOT(setServicePresent(bool))); + + qDebug() << "We are checking whether the service is present" << + Manager::isServicePresent(); + + if (Manager::isServicePresent()) { + initializeCachedData(); + } +} + +void ConsumerPrivate::setServicePresent(bool present) +{ + emit serviceStatusChanged( + present ? Consumer::Running : Consumer::NotRunning + ); + + if (present) { + initializeCachedData(); + } +} + +void ConsumerPrivate::initializeCachedData() +{ + KAMD_RETRIEVE_REMOTE_VALUE(currentActivity, CurrentActivity(), this); + KAMD_RETRIEVE_REMOTE_VALUE(listActivities, ListActivities(), this); + KAMD_RETRIEVE_REMOTE_VALUE(runningActivities, ListActivities(Info::Running), this); +} + +void ConsumerPrivate::setCurrentActivity(const QString & activity) +{ + qDebug() << "current activity is" << activity; + currentActivity = activity; + + emit currentActivityChanged(activity); +} + +void ConsumerPrivate::addActivity(const QString & activity) +{ + qDebug() << "new activity added" << activity; + if (!listActivities.contains(activity)) { + listActivities << activity; + runningActivities << activity; + } + + emit activityAdded(activity); +} + +void ConsumerPrivate::removeActivity(const QString & activity) +{ + qDebug() << "activity removed added" << activity; + listActivities.removeAll(activity); + runningActivities.removeAll(activity); + + emit activityRemoved(activity); +} + +void ConsumerPrivate::setActivityState(const QString & activity, int state) +{ + if (!listActivities.contains(activity)) { + qWarning("trying to alter state of unknown activity!!"); + return; // denied + } + + if (state == Info::Running) { + if (!runningActivities.contains(activity)) + runningActivities << activity; + + } else { + runningActivities.removeAll(activity); + } +} + +Consumer::Consumer(QObject * parent) + : QObject(parent), d(ConsumerPrivate::self(this)) +{ + connect(d, SIGNAL(serviceStatusChanged(KActivities::Consumer::ServiceStatus)), + this, SIGNAL(serviceStatusChanged(KActivities::Consumer::ServiceStatus))); + connect(d, SIGNAL(currentActivityChanged(QString)), + this, SIGNAL(currentActivityChanged(QString))); + connect(d, SIGNAL(activityAdded(QString)), + this, SIGNAL(activityAdded(QString))); + connect(d, SIGNAL(activityRemoved(QString)), + this, SIGNAL(activityRemoved(QString))); + +} + +Consumer::~Consumer() +{ + d->free(this); +} + +KAMD_REMOTE_VALUE_GETTER(QString, Consumer, currentActivity, nulluuid) +KAMD_REMOTE_VALUE_GETTER(QStringList, Consumer, listActivities, QStringList(nulluuid)) + +QStringList Consumer::listActivities(Info::State state) const +{ + if (state == Info::Running) { + if (!Manager::isServicePresent()) return QStringList(nulluuid); + + waitForCallFinished(d->runningActivitiesCallWatcher, &d->runningActivitiesMutex); + + qDebug() << "Returning the running activities" << d->runningActivities; + + return d->runningActivities; + } + + KAMD_RETRIEVE_REMOTE_VALUE_SYNC( + QStringList, activities, ListActivities(state), QStringList(nulluuid) + ); +} + +void Consumer::linkResourceToActivity(const QUrl & uri, const QString & activity) +{ + if (Manager::isServicePresent()) + Manager::resourcesLinking()->LinkResourceToActivity(uri.toString(), activity); +} + +void Consumer::unlinkResourceFromActivity(const QUrl & uri, const QString & activity) +{ + if (Manager::isServicePresent()) + Manager::resourcesLinking()->UnlinkResourceFromActivity(uri.toString(), activity); +} + +bool Consumer::isResourceLinkedToActivity(const QUrl & uri, const QString & activity) const +{ + KAMD_RETRIEVE_REMOTE_VALUE_SYNC( + bool, resourcesLinking, IsResourceLinkedToActivity(uri.toString(), activity), false + ); +} + +Consumer::ServiceStatus Consumer::serviceStatus() +{ + if (!Manager::isServicePresent()) { + return NotRunning; + } + + return Running; +} + +} // namespace KActivities + diff --git a/kactivities/src/lib/core/consumer.h b/kactivities/src/lib/core/consumer.h new file mode 100644 index 00000000..82245743 --- /dev/null +++ b/kactivities/src/lib/core/consumer.h @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2010, 2011, 2012 Ivan Cukic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef ACTIVITIES_CONSUMER_H +#define ACTIVITIES_CONSUMER_H + +#include +#include +#include +#include + +#include "info.h" + +#include +#include "kactivities_export.h" + +namespace KActivities { + +class ConsumerPrivate; + +/** + * Contextual information can be, from the user's point of view, divided + * into three aspects - "who am I?", "where am I?" (what are my surroundings?) + * and "what am I doing?". + * + * Activities deal with the last one - "what am I doing?". The current activity + * refers to what the user is doing at the moment, while the other activities represent + * things that he/she was doing before, and probably will be doing again. + * + * Activity is an abstract concept whose meaning can differ from one user to another. + * Typical examples of activities are "developing a KDE project", "studying the + * 19th century art", "composing music", "lazing on a Sunday afternoon" etc. + * + * Every activity can have applications, documents, or other types of resources + * assigned to it. + * + * Consumer provides an entry-level API for supporting activities in an + * application - to react to the changes to the current activity as well as + * registering the resources with its windows. + * + * Resource can be anything that is identifiable by an URI (for example, + * a local file or a web page) + * + * The API of the class is synchronous, but the most used properties + * are pre-fetched and cached. This means that, in order to get the least + * amount of d-bus related locks, you should declare long-lived instances + * of this class. + * + * For example, this is wrong (works, but blocks): + * @code + * void someMethod() { + * Consumer c; + * doSomethingWith(c.listActivities()); + * } + * @endcode + * + * The methods that are cached are marked as 'pre-fetched and cached'. + * Methods that will block until the response from the service is returned + * are marked as 'blocking'. + * + * @since 4.5 + */ +class KACTIVITIES_EXPORT Consumer: public QObject { + Q_OBJECT + + Q_PROPERTY(QString currentActivity READ currentActivity NOTIFY currentActivityChanged) + Q_PROPERTY(QStringList activities READ listActivities) + +public: + /** + * Different states of the activities service + */ + enum ServiceStatus { + NotRunning, ///< Service is not running + BareFunctionality, ///< @deprecated Service is running without a sane backend. + FullFunctionality, ///< @deprecated Service is running, and a backend is available + Running + }; + + explicit Consumer(QObject * parent = 0 /*nullptr*/); + + ~Consumer(); + + /** + * @returns the id of the current activity + * @note Activity ID is a UUID-formatted string. If the activities + * service is not running, or there was some error, the + * method will return null UUID. The ID can also be an empty + * string in the case there is no current activity. + * @note This method is pre-fetched and cached + */ + QString currentActivity() const; + + /** + * @returns the list of activities filtered by state + * @param state state of the activity + * @note If the activities service is not running, only a null + * activity will be returned. + * @note This method is pre-fetched and cached only for the Info::Running state + */ + QStringList listActivities(Info::State state) const; + + /** + * @returns the list of all existing activities + * @note If the activities service is not running, only a null + * activity will be returned. + * @note This method is pre-fetched and cached + */ + QStringList listActivities() const; + + /** + * @returns status of the activities service + */ + static ServiceStatus serviceStatus(); + + /** + * Links a resource to the activity + * @param uri URI of the resource + * @param activity id of the activity to link to. If empty, the + * resource is linked to the current activity. + * @note This method is asynchronous + * @deprecated use Info::linkResource + */ + KDE_DEPRECATED + void linkResourceToActivity(const QUrl & uri, const QString & activity = QString()); + + /** + * Unlinks a resource from the activity + * @param uri URI of the resource + * @param activity id of the activity to unlink from. If empty, the + * resource is unlinked from the current activity. + * @note This method is asynchronous + * @deprecated use Info::unlinkResource + */ + KDE_DEPRECATED + void unlinkResourceFromActivity(const QUrl & uri, const QString & activity = QString()); + + /** + * @returns whether the resource is linket to the specified activity + * @param uri URI of the resource + * @param activity id of the activity. If empty, the current activity is used. + * @note This method is blocking + * @deprecated use Info::isResourceLinked + */ + KDE_DEPRECATED + bool isResourceLinkedToActivity(const QUrl & uri, const QString & activity = QString()) const; + +Q_SIGNALS: + /** + * This signal is emitted when the global + * activity is changed + * @param id id of the new current activity + */ + void currentActivityChanged(const QString & id); + + /** + * This signal is emitted when the activity service + * goes online or offline + * @param status new status of the service + */ + void serviceStatusChanged(KActivities::Consumer::ServiceStatus status); + + /** + * This signal is emitted when a new activity is added + * @param id id of the new activity + */ + void activityAdded(const QString & id); + + /** + * This signal is emitted when the activity + * is removed + * @param id id of the removed activity + */ + void activityRemoved(const QString & id); + +private: + ConsumerPrivate * const d; +}; + +} // namespace KActivities + +#endif // ACTIVITIES_CONSUMER_H + diff --git a/kactivities/src/lib/core/consumer_p.h b/kactivities/src/lib/core/consumer_p.h new file mode 100644 index 00000000..4ff78622 --- /dev/null +++ b/kactivities/src/lib/core/consumer_p.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2010, 2011, 2012 Ivan Cukic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef ACTIVITIES_CONSUMER_P_H +#define ACTIVITIES_CONSUMER_P_H + +#include "consumer.h" + +#include + +#include "utils_p.h" + +class QDBusPendingCallWatcher; + +namespace KActivities { + +class ConsumerPrivate: public QObject { + Q_OBJECT + +public: + static ConsumerPrivate * self(QObject * consumer); + void free(QObject * consumer); + +public Q_SLOTS: + void setServicePresent(bool present); + void initializeCachedData(); + + void currentActivityCallFinished(QDBusPendingCallWatcher * call); + void listActivitiesCallFinished(QDBusPendingCallWatcher * call); + void runningActivitiesCallFinished(QDBusPendingCallWatcher * call); + + void setCurrentActivity(const QString & activity); + void addActivity(const QString & activity); + void removeActivity(const QString & activity); + void setActivityState(const QString & activity, int state); + +Q_SIGNALS: + void serviceStatusChanged(KActivities::Consumer::ServiceStatus status); + + void currentActivityChanged(const QString & id); + void activityAdded(const QString & id); + void activityRemoved(const QString & id); + +public: + KAMD_REMOTE_VALUE_CUSTOM_HANDLER(QString, currentActivity); + KAMD_REMOTE_VALUE_CUSTOM_HANDLER(QStringList, listActivities); + KAMD_REMOTE_VALUE_CUSTOM_HANDLER(QStringList, runningActivities); + + QSet consumers; + +private: + ConsumerPrivate(); + static ConsumerPrivate * s_instance; +}; + +} // namespace KActivities + +#endif // ACTIVITIES_CONSUMER_P_H + diff --git a/kactivities/src/lib/core/controller.cpp b/kactivities/src/lib/core/controller.cpp new file mode 100644 index 00000000..83a04128 --- /dev/null +++ b/kactivities/src/lib/core/controller.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2010, 2011, 2012 Ivan Cukic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "controller.h" +#include "consumer_p.h" +#include "manager_p.h" + +#include +#include + +namespace KActivities { + +class ControllerPrivate: public QObject { +public: + ControllerPrivate(Controller * parent) + : q(parent) + { + } + +private: + Controller * const q; +}; + +Controller::Controller(QObject * parent) + : Consumer(parent), d(new ControllerPrivate(this)) +{ +} + +Controller::~Controller() +{ + delete d; +} + +void Controller::setActivityName(const QString & id, const QString & name) +{ + Manager::activities()->SetActivityName(id, name); +} + +void Controller::setActivityIcon(const QString & id, const QString & icon) +{ + Manager::activities()->SetActivityIcon(id, icon); +} + +void Controller::setActivityEncrypted(const QString & id, bool encrypted) +{ + Q_UNUSED(id) + Q_UNUSED(encrypted) + // Manager::activities()->SetActivityEncrypted(id, encrypted); +} + +bool Controller::setCurrentActivity(const QString & id) +{ + return Manager::activities()->SetCurrentActivity(id); +} + +QString Controller::addActivity(const QString & name) +{ + return Manager::activities()->AddActivity(name); +} + +void Controller::removeActivity(const QString & id) +{ + Manager::activities()->RemoveActivity(id); +} + +void Controller::stopActivity(const QString & id) +{ + Manager::activities()->StopActivity(id); +} + +void Controller::startActivity(const QString & id) +{ + Manager::activities()->StartActivity(id); +} + +} // namespace KActivities + +#include "controller.moc" diff --git a/kactivities/src/lib/core/controller.h b/kactivities/src/lib/core/controller.h new file mode 100644 index 00000000..9d22ac1e --- /dev/null +++ b/kactivities/src/lib/core/controller.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2010, 2011, 2012 Ivan Cukic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef ACTIVITIES_CONTROLLER_H +#define ACTIVITIES_CONTROLLER_H + +#include +#include +#include +#include + +#include "consumer.h" + +#include +#include "kactivities_export.h" + +namespace KActivities { + +class ControllerPrivate; + +/** + * This class provides methods for controlling and managing + * the activities. + * + * @see Consumer for info about activities + * + * @since 4.5 + */ +class KACTIVITIES_EXPORT Controller: public Consumer +{ + Q_OBJECT + + Q_PROPERTY(QString currentActivity READ currentActivity WRITE setCurrentActivity) + +public: + explicit Controller(QObject * parent = 0 /*nullptr*/); + + ~Controller(); + + /** + * Sets the name of the specified activity + * @param id id of the activity + * @param name name to be set + */ + void setActivityName(const QString & id, const QString & name); + + /** + * Sets the icon of the specified activity + * @param id id of the activity + * @param icon icon to be set - freedesktop.org name or file path + */ + void setActivityIcon(const QString & id, const QString & icon); + + /** + * Sets whether the activity should be encrypted + * @param id id of the activity + * @param encrypted should the activity be encrypted + */ + KDE_DEPRECATED + void setActivityEncrypted(const QString & id, bool encrypted); + + /** + * Sets the current activity + * @param id id of the activity to make current + * @returns true if successful + */ + bool setCurrentActivity(const QString & id); + + /** + * Adds a new activity + * @param name name of the activity + * @returns id of the newly created activity + */ + QString addActivity(const QString & name); + + /** + * Removes the specified activity + * @param id id of the activity to delete + */ + void removeActivity(const QString & id); + + /** + * Stops the activity + * @param id id of the activity to stop + */ + void stopActivity(const QString & id); + + /** + * Starts the activity + * @param id id of the activity to start + */ + void startActivity(const QString & id); + +private: + ControllerPrivate * const d; +}; + +} // namespace KActivities + +#endif // ACTIVITIES_CONTROLLER_H + diff --git a/kactivities/src/lib/core/includes/KActivities/Consumer b/kactivities/src/lib/core/includes/KActivities/Consumer new file mode 100644 index 00000000..066d6dde --- /dev/null +++ b/kactivities/src/lib/core/includes/KActivities/Consumer @@ -0,0 +1 @@ +#include "../../kactivities/consumer.h" diff --git a/kactivities/src/lib/core/includes/KActivities/Controller b/kactivities/src/lib/core/includes/KActivities/Controller new file mode 100644 index 00000000..e51ff610 --- /dev/null +++ b/kactivities/src/lib/core/includes/KActivities/Controller @@ -0,0 +1 @@ +#include "../../kactivities/controller.h" diff --git a/kactivities/src/lib/core/includes/KActivities/Info b/kactivities/src/lib/core/includes/KActivities/Info new file mode 100644 index 00000000..fdafd5f4 --- /dev/null +++ b/kactivities/src/lib/core/includes/KActivities/Info @@ -0,0 +1 @@ +#include "../../kactivities/info.h" diff --git a/kactivities/src/lib/core/includes/KActivities/ResourceInstance b/kactivities/src/lib/core/includes/KActivities/ResourceInstance new file mode 100644 index 00000000..92ec053e --- /dev/null +++ b/kactivities/src/lib/core/includes/KActivities/ResourceInstance @@ -0,0 +1 @@ +#include "../../kactivities/resourceinstance.h" diff --git a/kactivities/src/lib/core/includes/KActivities/Version b/kactivities/src/lib/core/includes/KActivities/Version new file mode 100644 index 00000000..6d55e35e --- /dev/null +++ b/kactivities/src/lib/core/includes/KActivities/Version @@ -0,0 +1 @@ +#include "../../kactivities/version.h" diff --git a/kactivities/src/lib/core/info.cpp b/kactivities/src/lib/core/info.cpp new file mode 100644 index 00000000..11513860 --- /dev/null +++ b/kactivities/src/lib/core/info.cpp @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2010, 2011, 2012 Ivan Cukic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "info.h" +#include "info_p.h" +#include "manager_p.h" + +#include + +namespace KActivities { + +// Private common + +InfoPrivateCommon * InfoPrivateCommon::s_instance = 0; + +InfoPrivateCommon * InfoPrivateCommon::self() +{ + if (!s_instance) { + s_instance = new InfoPrivateCommon(); + } + + return s_instance; +} + +InfoPrivateCommon::InfoPrivateCommon() +{ +} + +InfoPrivateCommon::~InfoPrivateCommon() +{ +} + +// Private + +InfoPrivate::InfoPrivate(Info *info, const QString &activity) + : q(info), + state(Info::Invalid), + nameCallWatcher(0), + iconCallWatcher(0), + id(activity) +{ + if (Manager::isServicePresent()) { + initializeCachedData(); + } +} + +// Filters out signals for only this activity +#define IMPLEMENT_SIGNAL_HANDLER(ORIGINAL, INTERNAL) \ + void InfoPrivate::INTERNAL(const QString & _id) const \ + { \ + if (id == _id) emit q->INTERNAL(); \ + } + +IMPLEMENT_SIGNAL_HANDLER(ActivityAdded, added) +IMPLEMENT_SIGNAL_HANDLER(ActivityRemoved, removed) +IMPLEMENT_SIGNAL_HANDLER(ActivityStarted, started) +IMPLEMENT_SIGNAL_HANDLER(ActivityStopped, stopped) +IMPLEMENT_SIGNAL_HANDLER(ActivityChanged, infoChanged) + +#undef IMPLEMENT_SIGNAL_HANDLER + +KAMD_RETRIEVE_REMOTE_VALUE_HANDLER(QString, InfoPrivate, name, QString()) +KAMD_RETRIEVE_REMOTE_VALUE_HANDLER(QString, InfoPrivate, icon, QString()) + +void InfoPrivate::nameChanged(const QString & _id, const QString & _name) const +{ + if (id == _id) { + name = _name; + emit q->nameChanged(name); + } +} + +void InfoPrivate::iconChanged(const QString & _id, const QString & _icon) const +{ + if (id == _id) { + icon = _icon; + emit q->iconChanged(icon); + } +} + + +void InfoPrivate::setServicePresent(bool present) +{ + if (present) { + initializeCachedData(); + } +} + +void InfoPrivate::activityStateChanged(const QString & idChanged, int newState) +{ + if (idChanged == id) { + state = static_cast(newState); + emit q->stateChanged(state); + } +} + +void InfoPrivate::initializeCachedData() +{ + KAMD_RETRIEVE_REMOTE_VALUE(name, ActivityName(id), q); + KAMD_RETRIEVE_REMOTE_VALUE(icon, ActivityIcon(id), q); +} + +// Info +Info::Info(const QString &activity, QObject *parent) + : QObject(parent), + d(new InfoPrivate(this, activity)) +{ + connect(Manager::activities(), SIGNAL(ActivityStateChanged(QString, int)), + this, SLOT(activityStateChanged(QString, int))); + + connect(Manager::activities(), SIGNAL(ActivityChanged(QString)), + this, SLOT(infoChanged(QString))); + + connect(Manager::activities(), SIGNAL(ActivityNameChanged(QString, QString)), + this, SLOT(nameChanged(QString, QString))); + + connect(Manager::activities(), SIGNAL(ActivityIconChanged(QString, QString)), + this, SLOT(iconChanged(QString, QString))); + + connect(Manager::activities(), SIGNAL(ActivityAdded(QString)), + this, SLOT(added(QString))); + + connect(Manager::activities(), SIGNAL(ActivityRemoved(QString)), + this, SLOT(removed(QString))); + + connect(Manager::activities(), SIGNAL(ActivityStarted(QString)), + this, SLOT(started(QString))); + + connect(Manager::activities(), SIGNAL(ActivityStopped(QString)), + this, SLOT(stopped(QString))); +} + +Info::~Info() +{ + delete d; +} + +bool Info::isValid() const +{ + return (state() != Invalid); +} + +KUrl Info::uri() const +{ + return KUrl("activities://" + d->id); +} + +KUrl Info::resourceUri() const +{ + return KUrl(); +} + +QString Info::id() const +{ + return d->id; +} + +KAMD_REMOTE_VALUE_GETTER(QString, Info, name, + i18nc("The name of the main activity - when the activity manager is not running", "Main")) + +KAMD_REMOTE_VALUE_GETTER(QString, Info, icon, "preferences-activities") + +Info::State Info::state() const +{ + if (d->state == Invalid) { + QDBusReply < int > dbusReply = Manager::activities()->ActivityState(d->id); + + if (dbusReply.isValid()) { + d->state = (State)(dbusReply.value()); + } + } + + return d->state; +} + +QString Info::name(const QString & id) +{ + KAMD_RETRIEVE_REMOTE_VALUE_SYNC( + QString, activities, ActivityName(id), + i18nc("The name of the main activity - when the activity manager is not running", "Main") + ); +} + +bool Info::isEncrypted() const +{ + return false; +} + +Info::Availability Info::availability() const +{ + Availability result = Nothing; + + if (!Manager::isServicePresent()) { + return result; + } + + if (Manager::activities()->ListActivities().value().contains(d->id)) { + result = BasicInfo; + + if (Manager::features()->IsFeatureOperational("org.kde.ActivityManager.Nepomuk/linking")) { + result = Everything; + } + } + + return result; +} + +KUrl::List Info::linkedResources() const +{ + KUrl::List result; + + QDBusReply < QStringList > dbusReply = Manager::resourcesLinking()->ResourcesLinkedToActivity(d->id); + + if (dbusReply.isValid()) { + foreach (const QString & uri, dbusReply.value()) { + result << KUrl(uri); + } + } + + return result; +} + +void Info::linkResource(const KUrl & resourceUri) +{ + Manager::resourcesLinking()->LinkResourceToActivity(resourceUri.url(), d->id); +} + +void Info::unlinkResource(const KUrl & resourceUri) +{ + Manager::resourcesLinking()->UnlinkResourceFromActivity(resourceUri.url(), d->id); +} + +bool Info::isResourceLinked(const KUrl & resourceUri) +{ + return Manager::resourcesLinking()->IsResourceLinkedToActivity(resourceUri.url(), d->id); +} + +} // namespace KActivities + +#include "info_p.moc" +#include "info.moc" + diff --git a/kactivities/src/lib/core/info.h b/kactivities/src/lib/core/info.h new file mode 100644 index 00000000..1ae90635 --- /dev/null +++ b/kactivities/src/lib/core/info.h @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2010, 2011, 2012 Ivan Cukic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef ACTIVITIES_INFO_H +#define ACTIVITIES_INFO_H + +#include +#include +#include +#include + +#include +#include + +#include "kactivities_export.h" + +namespace KActivities { + +class InfoPrivate; + +/** + * This class provides info about an activity. Most methods in it + * require a Nepomuk backend running. + * + * This class is not thread-safe + * + * @see Consumer for info about activities + * + * The API of the class is synchronous, but the most used properties + * are pre-fetched and cached. This means that, in order to get the least + * amount of d-bus related locks, you should declare long-lived instances + * of this class. + * + * For example, this is wrong (works, but blocks): + * @code + * void someMethod(const QString & activity) { + * Info info(activity); + * doSomethingWith(info.name()); + * } + * @endcode + * + * The methods that are cached are marked as 'pre-fetched and cached'. + * Methods that will block until the response from the service is returned + * are marked as 'blocking'. + * + * @since 4.5 + */ +class KACTIVITIES_EXPORT Info: public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString id READ id) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString icon READ icon NOTIFY iconChanged) + +public: + explicit Info(const QString & activity, QObject * parent = 0 /*nullptr*/); + ~Info(); + + /** + * @return true if the activity represented by this object exists and is valid + */ + bool isValid() const; + + /** + * Specifies which parts of this class are functional + */ + enum Availability { + Nothing = 0, ///< No activity info provided (isValid is false) + BasicInfo = 1, ///< Basic info is provided + Everything = 2 ///< Everything is available + }; + + /** + * State of the activity + */ + enum State { + Invalid = 0, + Running = 2, + Starting = 3, + Stopped = 4, + Stopping = 5 + }; + + /** + * @returns what info is provided by this instance of Info + */ + Availability availability() const; + + /** + * @returns the URI of this activity. The same URI is used by + * activities KIO slave. + */ + KUrl uri() const; + + /** + * @deprecated we don't guarantee that nepomuk is the backend + * @returns the Nepomuk resource URI of this activity + * @note Functional only when availability is Everything + */ + KDE_DEPRECATED + KUrl resourceUri() const; + + /** + * @returns the id of the activity + */ + QString id() const; + + /** + * @returns the name of the activity + * @note Functional when availability is BasicInfo or Everything + * @note This method is pre-fetched and cached + */ + QString name() const; + + /** + * @returns the icon of the activity. Icon can be a + * freedesktop.org name or a file path. Or empty if + * no icon is set. + * @note Functional only when availability is Everything + * @note This method is pre-fetched and cached + */ + QString icon() const; + + /** + * @returns the state of the activity + * @note This method is cached + */ + State state() const; + + /** + * @returns true if encrypted + * @since 4.8 + */ + KDE_DEPRECATED + bool isEncrypted() const; + + /** + * This function is provided for convenience. + * @returns the name of the specified activity + * @param id id of the activity + * @note This method is blocking, you should use Info::name() + */ + static QString name(const QString & id); + + /** + * Links the specified resource to the activity + * @param resourceUri resource URI + * @note This method is asynchronous + */ + void linkResource(const KUrl & resourceUri); + + + /** + * Unlinks the specified resource from the activity + * @param resourceUri resource URI + * @note This method is asynchronous + */ + void unlinkResource(const KUrl & resourceUri); + + + /** + * @returns the list of linked resources + * @note This method is blocking + */ + KDE_DEPRECATED + KUrl::List linkedResources() const; + + + /** + * @returns whether a resource is linked to this activity + * @note this method is blocking + * @since 4.11 + */ + bool isResourceLinked(const KUrl & resourceUri); + +Q_SIGNALS: + /** + * Emitted when the activity's name, icon or some custom property is changed + */ + void infoChanged(); + + /** + * Emitted when the name is changed + */ + void nameChanged(const QString & name); + + /** + * Emitted when the icon was changed + */ + void iconChanged(const QString & icon); + + /** + * Emitted when the activity is added + */ + void added(); + + /** + * Emitted when the activity is removed + */ + void removed(); + + /** + * Emitted when the activity is started + */ + void started(); + + /** + * Emitted when the activity is stopped + */ + void stopped(); + + /** + * Emitted when the activity changes state + * @param state new state of the activity + */ + void stateChanged(KActivities::Info::State state); + +private: + InfoPrivate * const d; + + Q_PRIVATE_SLOT(d, void activityStateChanged(const QString &, int)) + Q_PRIVATE_SLOT(d, void added(const QString &)) + Q_PRIVATE_SLOT(d, void removed(const QString &)) + Q_PRIVATE_SLOT(d, void started(const QString &)) + Q_PRIVATE_SLOT(d, void stopped(const QString &)) + Q_PRIVATE_SLOT(d, void infoChanged(const QString &)) + Q_PRIVATE_SLOT(d, void nameChanged(const QString &, const QString &)) + Q_PRIVATE_SLOT(d, void iconChanged(const QString &, const QString &)) + Q_PRIVATE_SLOT(d, void setServicePresent(bool)) + Q_PRIVATE_SLOT(d, void nameCallFinished(QDBusPendingCallWatcher*)) + Q_PRIVATE_SLOT(d, void iconCallFinished(QDBusPendingCallWatcher*)) + + friend class InfoPrivate; +}; + +} // namespace KActivities + +#endif // ACTIVITIES_INFO_H + diff --git a/kactivities/src/lib/core/info_p.h b/kactivities/src/lib/core/info_p.h new file mode 100644 index 00000000..2b78333f --- /dev/null +++ b/kactivities/src/lib/core/info_p.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2010, 2011, 2012 Ivan Cukic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KACTIVITIESINFO_P_H +#define KACTIVITIESINFO_P_H + +#include "info.h" +#include + +#include "utils_p.h" + +namespace KActivities { + +class InfoPrivateCommon: public QObject { + Q_OBJECT +public: + static InfoPrivateCommon * self(); + + InfoPrivateCommon(); + virtual ~InfoPrivateCommon(); + +private: + static InfoPrivateCommon * s_instance; +}; + +class InfoPrivate { +public: + InfoPrivate(Info * info, const QString & activity); + + void activityStateChanged(const QString &, int); + + void added(const QString &) const; + void removed(const QString &) const; + void started(const QString &) const; + void stopped(const QString &) const; + void infoChanged(const QString &) const; + void nameChanged(const QString &, const QString &) const; + void iconChanged(const QString &, const QString &) const; + void setServicePresent(bool present); + + void initializeCachedData(); + + Info *q; + Info::State state; + + KAMD_REMOTE_VALUE(QString, name); + KAMD_REMOTE_VALUE(QString, icon); + + const QString id; +}; + +} // namespace KActivities + +#endif // ACTIVITIES_INFO_P_H diff --git a/kactivities/src/lib/core/kactivities_export.h b/kactivities/src/lib/core/kactivities_export.h new file mode 100644 index 00000000..b6b04f59 --- /dev/null +++ b/kactivities/src/lib/core/kactivities_export.h @@ -0,0 +1,52 @@ +/**************************************************************************************** + * Copyright (c) 2012 Patrick von Reth * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU Lesser General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License along with * + * this program. If not, see . * + ****************************************************************************************/ + +#ifndef KACTIVITIES_EXPORT_H +#define KACTIVITIES_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include + +#ifndef KACTIVITIES_EXPORT +# ifdef MAKE_KACTIVITIES_LIB + /* We are building this library */ +# define KACTIVITIES_EXPORT KDE_EXPORT + +# if defined(DEBUG) +# define KACTIVITIES_EXPORT_TESTS KDE_EXPORT +# else +# define KACTIVITIES_EXPORT_TESTS +# endif + +# else + /* We are using this library */ +# define KACTIVITIES_EXPORT KDE_IMPORT + +# if defined(DEBUG) +# define KACTIVITIES_EXPORT_TESTS KDE_IMPORT +# else +# define KACTIVITIES_EXPORT_TESTS +# endif + +# endif//MAKE_KACTIVITIES_LIB +#endif// KACTIVITIES_EXPORT + +# ifndef KACTIVITIES_EXPORT_DEPRECATED +# define KACTIVITIES_EXPORT_DEPRECATED KDE_DEPRECATED KACTIVITIES_EXPORT +# endif + +#endif + diff --git a/kactivities/src/lib/core/libkactivities.pc.cmake b/kactivities/src/lib/core/libkactivities.pc.cmake new file mode 100644 index 00000000..af5aa969 --- /dev/null +++ b/kactivities/src/lib/core/libkactivities.pc.cmake @@ -0,0 +1,12 @@ +prefix=${CMAKE_INSTALL_PREFIX} +exec_prefix=${BIN_INSTALL_DIR} +libdir=${LIB_INSTALL_DIR} +includedir=${INCLUDE_INSTALL_DIR} + +Name: libkkactivities +Description: libkkactivities is a C++ library for using KDE activities +URL: http://www.kde.org +Requires: +Version: ${KACTIVITIES_LIB_VERSION_STRING} +Libs: -L${LIB_INSTALL_DIR} -lkactivities +Cflags: -I${INCLUDE_INSTALL_DIR} diff --git a/kactivities/src/lib/core/manager_p.cpp b/kactivities/src/lib/core/manager_p.cpp new file mode 100644 index 00000000..f9e10083 --- /dev/null +++ b/kactivities/src/lib/core/manager_p.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2010, 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "manager_p.h" + +#include +#include + +#include +#include + +namespace KActivities { + +Manager * Manager::s_instance = 0 /*nullptr*/; + +#define ACTIVITY_MANAGER_DBUS_PATH "org.kde.ActivityManager" +#define ACTIVITY_MANAGER_DBUS_OBJECT "/ActivityManager" + +Manager::Manager() + : QObject(), + m_activities( + new org::kde::ActivityManager::Activities( + ACTIVITY_MANAGER_DBUS_PATH, + ACTIVITY_MANAGER_DBUS_OBJECT "/Activities", + KDBusConnectionPool::threadConnection(), + this + )), + m_resources( + new org::kde::ActivityManager::Resources( + ACTIVITY_MANAGER_DBUS_PATH, + ACTIVITY_MANAGER_DBUS_OBJECT "/Resources", + KDBusConnectionPool::threadConnection(), + this + )), + m_resourcesLinking( + new org::kde::ActivityManager::ResourcesLinking( + ACTIVITY_MANAGER_DBUS_PATH, + ACTIVITY_MANAGER_DBUS_OBJECT "/Resources/Linking", + KDBusConnectionPool::threadConnection(), + this + )), + m_features( + new org::kde::ActivityManager::Features( + ACTIVITY_MANAGER_DBUS_PATH, + ACTIVITY_MANAGER_DBUS_OBJECT "/Features", + KDBusConnectionPool::threadConnection(), + this + )) +{ + connect(&m_watcher, SIGNAL(serviceOwnerChanged(const QString &, const QString &, const QString &)), + this, SLOT(serviceOwnerChanged(const QString &, const QString &, const QString &))); +} + +Manager * Manager::self() +{ + if (!s_instance) { + // check if the activity manager is already running + if (!isServicePresent()) { + + // not running, trying to launch it + QString error; + + int ret = KToolInvocation::startServiceByDesktopPath("kactivitymanagerd.desktop", QStringList(), &error); + if (ret > 0) { + qDebug() << "Activity: Couldn't start kactivitymanagerd: " << error << endl; + QProcess::startDetached("kactivitymanagerd"); + } + + if (!KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ACTIVITY_MANAGER_DBUS_PATH)) { + qDebug() << "Activity: The kactivitymanagerd service is still not registered"; + } else { + qDebug() << "Activity: The kactivitymanagerd service has been registered"; + } + } + + // creating a new instance of the class + s_instance = new Manager(); + } + + return s_instance; +} + +bool Manager::isServicePresent() +{ + return KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ACTIVITY_MANAGER_DBUS_PATH); +} + +void Manager::serviceOwnerChanged(const QString & serviceName, const QString & oldOwner, const QString & newOwner) +{ + Q_UNUSED(oldOwner) + + if (serviceName == ACTIVITY_MANAGER_DBUS_PATH) { + emit servicePresenceChanged(!newOwner.isEmpty()); + } +} + +Service::Activities * Manager::activities() +{ + return self()->m_activities; +} + +Service::Resources * Manager::resources() +{ + return self()->m_resources; +} + +Service::ResourcesLinking * Manager::resourcesLinking() +{ + return self()->m_resourcesLinking; +} + +Service::Features * Manager::features() +{ + return self()->m_features; +} + +} // namespace KActivities + diff --git a/kactivities/src/lib/core/manager_p.h b/kactivities/src/lib/core/manager_p.h new file mode 100644 index 00000000..e1ab74e6 --- /dev/null +++ b/kactivities/src/lib/core/manager_p.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010, 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef ACTIVITIES_MANAGER_P +#define ACTIVITIES_MANAGER_P + +#include + +#include "activities_interface.h" +#include "resources_interface.h" +#include "resources_linking_interface.h" +#include "features_interface.h" + +#include + +namespace Service = org::kde::ActivityManager; + +namespace KActivities { + +class Manager: public QObject { + Q_OBJECT + +public: + static Manager * self(); + + static bool isServicePresent(); + + static Service::Activities * activities(); + static Service::Resources * resources(); + static Service::ResourcesLinking * resourcesLinking(); + static Service::Features * features(); + +public Q_SLOTS: + void serviceOwnerChanged(const QString & serviceName, const QString & oldOwner, const QString & newOwner); + +Q_SIGNALS: + void servicePresenceChanged(bool present); + +private: + Manager(); + + QDBusServiceWatcher m_watcher; + + static Manager * s_instance; + + Service::Activities * const m_activities; + Service::Resources * const m_resources; + Service::ResourcesLinking * const m_resourcesLinking; + Service::Features * const m_features; +}; + +} // namespace KActivities + +#endif // ACTIVITIES_MANAGER_P + diff --git a/kactivities/src/lib/core/prettyheaders.sh b/kactivities/src/lib/core/prettyheaders.sh new file mode 100755 index 00000000..469be8f8 --- /dev/null +++ b/kactivities/src/lib/core/prettyheaders.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +grep KDE_EXPORT *.h | sed 's/:.*KDE_EXPORT//' | sed 's/:.*//g' | sed 's/\([^ ]*\) \(.*\)/echo "#include \\"..\/..\/kactivities\/\1\\"" > includes\/Activities\/\2/g' + diff --git a/kactivities/src/lib/core/resourceinstance.cpp b/kactivities/src/lib/core/resourceinstance.cpp new file mode 100644 index 00000000..e2e02ed8 --- /dev/null +++ b/kactivities/src/lib/core/resourceinstance.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "resourceinstance.h" +#include "manager_p.h" + +#include +#include + +namespace KActivities { + +#ifdef Q_OS_WIN64 // krazy:skip +__inline int toInt(WId wid) +{ + return (int)((__int64)wid); +} + +#else +__inline int toInt(WId wid) +{ + return (int)wid; +} +#endif + +class ResourceInstancePrivate { +public: + WId wid; + ResourceInstance::AccessReason reason; + QUrl uri; + QString mimetype; + QString title; + QString application; + + void closeResource(); + void openResource(); + + enum Type { + Accessed = 0, + Opened = 1, + Modified = 2, + Closed = 3, + FocusedIn = 4, + FocusedOut = 5 + }; + + static void registerResourceEvent(const QString &application, WId wid, const QUrl &uri, Type event, ResourceInstance::AccessReason reason) + { + Manager::resources()->RegisterResourceEvent(application, toInt(wid), uri.toString(), uint(event), uint(reason)); + } +}; + +void ResourceInstancePrivate::closeResource() +{ + registerResourceEvent(application, wid, uri, Closed, reason); +} + +void ResourceInstancePrivate::openResource() +{ + registerResourceEvent(application, wid, uri, Opened, reason); +} + +ResourceInstance::ResourceInstance(WId wid, QObject *parent) + : QObject(parent), d(new ResourceInstancePrivate()) +{ + qDebug() << "Creating ResourceInstance: empty for now"; + d->wid = wid; + d->reason = User; + d->application = QCoreApplication::instance()->applicationName(); +} + +ResourceInstance::ResourceInstance(WId wid, AccessReason reason, const QString &application, QObject *parent) + : QObject(parent), d(new ResourceInstancePrivate()) +{ + qDebug() << "Creating ResourceInstance: empty for now"; + d->wid = wid; + d->reason = reason; + d->application = application.isEmpty() ? QCoreApplication::instance()->applicationName() : application; +} + +ResourceInstance::ResourceInstance(WId wid, QUrl resourceUri, const QString &mimetype, + const QString &title, AccessReason reason, const QString &application, QObject *parent) + : QObject(parent), d(new ResourceInstancePrivate()) +{ + qDebug() << "Creating ResourceInstance: " << resourceUri; + d->wid = wid; + d->reason = reason; + d->uri = resourceUri; + d->application = application.isEmpty() ? QCoreApplication::instance()->applicationName() : application; + + d->openResource(); + + setTitle(title); + setMimetype(mimetype); +} + +ResourceInstance::~ResourceInstance() +{ + d->closeResource(); + delete d; +} + +void ResourceInstance::notifyModified() +{ + d->registerResourceEvent(d->application, d->wid, d->uri, ResourceInstancePrivate::Modified, d->reason); +} + +void ResourceInstance::notifyFocusedIn() +{ + d->registerResourceEvent(d->application, d->wid, d->uri, ResourceInstancePrivate::FocusedIn, d->reason); +} + +void ResourceInstance::notifyFocusedOut() +{ + d->registerResourceEvent(d->application, d->wid, d->uri, ResourceInstancePrivate::FocusedOut, d->reason); +} + +void ResourceInstance::setUri(const QUrl &newUri) +{ + if (d->uri == newUri) + return; + + if (!d->uri.isEmpty()) { + d->closeResource(); + } + + d->uri = newUri; + + d->openResource(); +} + +void ResourceInstance::setMimetype(const QString &mimetype) +{ + if (mimetype.isEmpty()) return; + + d->mimetype = mimetype; + // TODO: update the service info + Manager::resources()->RegisterResourceMimeType(d->uri.toString(), mimetype); +} + +void ResourceInstance::setTitle(const QString &title) +{ + qDebug() << "Setting the title: " << title; + if (title.isEmpty()) return; + + d->title = title; + // TODO: update the service info + Manager::resources()->RegisterResourceTitle(d->uri.toString(), title); +} + +QUrl ResourceInstance::uri() const +{ + return d->uri; +} + +QString ResourceInstance::mimetype() const +{ + return d->mimetype; +} + +QString ResourceInstance::title() const +{ + return d->title; +} + +WId ResourceInstance::winId() const +{ + return d->wid; +} + +ResourceInstance::AccessReason ResourceInstance::accessReason() const +{ + return d->reason; +} + +void ResourceInstance::notifyAccessed(const QUrl &uri, const QString &application) +{ + ResourceInstancePrivate::registerResourceEvent( + application.isEmpty() ? QCoreApplication::instance()->applicationName() : application, + 0, uri, ResourceInstancePrivate::Accessed, User); +} + +} // namespace KActivities diff --git a/kactivities/src/lib/core/resourceinstance.h b/kactivities/src/lib/core/resourceinstance.h new file mode 100644 index 00000000..e3f39962 --- /dev/null +++ b/kactivities/src/lib/core/resourceinstance.h @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef ACTIVITIES_RESOURCEINSTANCE_H +#define ACTIVITIES_RESOURCEINSTANCE_H + +#include +#include +#include + +#include "kactivities_export.h" + +namespace KActivities { + +class ResourceInstancePrivate; + +/** + * This class is used to notify the system that a file, web page + * or some other resource has been accessed. + * + * It provides methods to notify the system when the resource was + * opened, modified and closed, along with in what window the + * resource is shown. + * + * You should create an instance of this class for every resource + * you open. + * + * "The system" in this case can be the backend for tracking + * and automatically scoring files that are being accessed, the + * system to show the open files per window in the taskbar, + * the share-like-connect, etc. + * + * The user of this class shouldn't care about the backend + * systems - everything is done under-the-hood automatically. + * + */ +class KACTIVITIES_EXPORT ResourceInstance: public QObject +{ + Q_OBJECT + + Q_PROPERTY(QUrl uri READ uri WRITE setUri) + Q_PROPERTY(QString mimetype READ mimetype WRITE setMimetype) + Q_PROPERTY(QString title READ title WRITE setTitle) + Q_PROPERTY(WId winId READ winId) + Q_PROPERTY(AccessReason accessReason READ accessReason) + +public: + /*** + * The reason for opening the resource + */ + enum AccessReason { + User = 0, ///< Due to an explicit user request + Scheduled = 1, ///< As a result of a user-scheduled action + Heuristic = 2, ///< Deduced from user's activity, or indirectly requested + System = 3, ///< Due to a system event + World = 4 ///< Due to an action performed by an external entity + }; + Q_ENUMS(AccessReason) + + /** + * Creates a new resource instance + * @param wid id of the window that will show the resource + * @param parent pointer to the parent object + * @since 4.10 + */ + explicit ResourceInstance(WId wid, QObject *parent = 0 /*nullptr*/); + + /** + * Creates a new resource instance + * @param wid id of the window that will show the resource + * @param reason reason for opening the resource + * @param application application's name (the name used for the .desktop file). + * If not specified, QCoreApplication::applicationName is used + * @param parent pointer to the parent object + */ + explicit ResourceInstance(WId wid, AccessReason reason = User, const QString &application = QString(), QObject * parent = 0 /*nullptr*/); + + /** + * Creates a new resource instance and automatically + * notifies the system that it was opened. + * + * In some special cases, where the URI of the resource is + * being constantly changed (for example, in the world globe, + * street map applications) you have two options: + * - to pass an empty resourceUri while passing the mimetype + * - to update the uri from time to time (in the example of + * the world map - to send URIs for major objects - cities + * or main streets. + * and in both cases reimplementing the currentUri() method + * which will return the exact URI shown at that specific moment. + * + * @param wid window id in which the resource is shown + * @param resourceUri URI of the resource that is shown + * @param mimetype the mime type of the resource + * @param title the title of the resource + * @param reason reason for opening the resource + * @param application application's name (the name used for the .desktop file). + * If not specified, QCoreApplication::applicationName is used + * @param parent pointer to the parent object + */ + ResourceInstance(WId wid, QUrl resourceUri, const QString &mimetype = QString(), const QString &title = QString(), AccessReason reason = User, const QString &application = QString(), QObject *parent = 0 /*nullptr*/); + + /** + * Destroys the ResourceInstance and notifies the system + * that the resource has been closed + */ + ~ResourceInstance(); + +public Q_SLOTS: + /** + * Call this method to notify the system that you modified + * (the contents of) the resource + */ + void notifyModified(); + + /** + * Call this method to notify the system that the resource + * has the focus in your application + * @note You only need to call this in MDI applications + */ + void notifyFocusedIn(); + + /** + * Call this method to notify the system that the resource + * lost the focus in your application + * @note You only need to call this in MDI applications + */ + void notifyFocusedOut(); + + /** + * This is a convenience method that sets the new URI. + * This is usually handled by sending the close event for + * the previous URI, and an open event for the new one. + */ + void setUri(const QUrl &newUri); + + /** + * Sets the mimetype for this resource + */ + void setMimetype(const QString &mimetype); + + /** + * Sets the title for this resource + */ + void setTitle(const QString &title); + +Q_SIGNALS: + /** + * Emitted when the system wants to show the resource + * represented by this ResourceInstance. + * + * You should listen to this signal if you have multiple + * resources shown in one window (MDI). On catching it, show + * the resource and give it focus. + */ + void requestsFocus(); + +public: + /** + * @returns the current uri + * The default implementation returns the URI that was passed + * to the constructor. + * You need to reimplement it only for the applications with + * frequently updated URIs. + */ + virtual QUrl uri() const; + + /** + * @returns mimetype of the resource + */ + QString mimetype() const; + + /** + * @returns title of the resource + */ + QString title() const; + + /** + * @returns the window id + */ + WId winId() const; + + /** + * @returns the reason for accessing the resource + */ + AccessReason accessReason() const; + + /** + * If there's no way to tell for how long an application is keeping + * the resource open, you can just call this static method - it + * will notify the system that the application has accessed the + * resource + * @param uri URI of the resource + * @param application application's name (the name used for the .desktop file). + * If not specified, QCoreApplication::applicationName is used + * + */ + static void notifyAccessed(const QUrl &uri, const QString &application = QString()); + +private: + ResourceInstancePrivate * const d; +}; + +} + +#endif // ACTIVITIES_RESOURCEINSTANCE_H + diff --git a/kactivities/src/lib/core/utils_p.h b/kactivities/src/lib/core/utils_p.h new file mode 100644 index 00000000..402beaa9 --- /dev/null +++ b/kactivities/src/lib/core/utils_p.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef UTILS_P_H +#define UTILS_P_H + +#include + +#include +#include + +#include + +// Creates an async call to retrieve a value from the dbus service +// and initializes the call watcher +#define KAMD_RETRIEVE_REMOTE_VALUE(Variable, MethodToCall, Target) \ + Variable##Mutex.lock(); \ + const QDBusPendingCall & Variable##Call = Manager::activities()->MethodToCall; \ + Variable##CallWatcher = new QDBusPendingCallWatcher(Variable##Call, Target); \ + \ + QObject::connect(Variable##CallWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), \ + Target, SLOT(Variable##CallFinished(QDBusPendingCallWatcher*))) + +// Defines a variable and handlers for a variable on a dbus service +// without a handler for when a call is finished +#define KAMD_REMOTE_VALUE_CUSTOM_HANDLER(Type, Name) \ + mutable Type Name; \ + QDBusPendingCallWatcher * Name##CallWatcher; \ + QMutex Name##Mutex + +// Defines a variable and handlers for a variable on a dbus service +#define KAMD_REMOTE_VALUE(Type, Name) \ + KAMD_REMOTE_VALUE_CUSTOM_HANDLER(Type, Name); \ + void Name##CallFinished(QDBusPendingCallWatcher * call) + +// macro defines a shorthand for validating and returning a d-bus result +// @param TYPE type of the result +// @param METHOD invocation of the d-bus method +// @param DEFAULT value to be used if the reply was not valid +#define KAMD_RETRIEVE_REMOTE_VALUE_SYNC(TYPE, OBJECT, METHOD, DEFAULT) \ + if (!Manager::isServicePresent()) return DEFAULT; \ + \ + QDBusReply < TYPE > dbusReply = Manager::OBJECT()->METHOD; \ + if (dbusReply.isValid()) { \ + return dbusReply.value(); \ + } else { \ + qDebug() << "d-bus reply was invalid" \ + << dbusReply.value() \ + << dbusReply.error(); \ + return DEFAULT; \ + } + +// Defines a handler for pre-fetching of the activity info +#define KAMD_RETRIEVE_REMOTE_VALUE_HANDLER(ReturnType, Namespace, Variable, DefaultValue) \ + void Namespace::Variable##CallFinished(QDBusPendingCallWatcher * call) \ + { \ + QDBusPendingReply reply = * call; \ + \ + Variable = reply.isError() \ + ? DefaultValue \ + : reply.argumentAt<0>(); \ + \ + Variable##CallWatcher = 0; \ + Variable##Mutex.unlock(); \ + call->deleteLater(); \ + } + +// Implements a value getter +#define KAMD_REMOTE_VALUE_GETTER(ReturnType, Namespace, Variable, Default) \ + ReturnType Namespace::Variable() const \ + { \ + if (!Manager::isServicePresent()) return Default; \ + waitForCallFinished(d->Variable##CallWatcher, &d->Variable##Mutex); \ + return d->Variable; \ + } + +static inline void waitForCallFinished(QDBusPendingCallWatcher * watcher, QMutex * mutex) +{ + if (watcher) { + watcher->waitForFinished(); + + mutex->lock(); + mutex->unlock(); + } +} + +#endif // UTILS_P_H + diff --git a/kactivities/src/lib/core/version.cpp b/kactivities/src/lib/core/version.cpp new file mode 100644 index 00000000..926e2b25 --- /dev/null +++ b/kactivities/src/lib/core/version.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2008 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "version.h" + +namespace KActivities +{ + +unsigned int version() +{ + return KACTIVITIES_VERSION; +} + +unsigned int versionMajor() +{ + return KACTIVITIES_VERSION_MAJOR; +} + +unsigned int versionMinor() +{ + return KACTIVITIES_VERSION_MINOR; +} + +unsigned int versionRelease() +{ + return KACTIVITIES_VERSION_RELEASE; +} + +const char *versionString() +{ + return KACTIVITIES_VERSION_STRING; +} + +} // KActivities namespace + diff --git a/kactivities/src/lib/core/version.h b/kactivities/src/lib/core/version.h new file mode 100644 index 00000000..afe5f48d --- /dev/null +++ b/kactivities/src/lib/core/version.h @@ -0,0 +1,87 @@ +/* + * Copyright 2008 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KACTIVITIES_VERSION_H +#define KACTIVITIES_VERSION_H + +/** @file version.h */ + +#include "kactivities_export.h" + +/** + * String version of libkactivities version, suitable for use in + * file formats or network protocols + */ +#define KACTIVITIES_VERSION_STRING "6.2.0" + +/// @brief Major version of libkactivities, at compile time +#define KACTIVITIES_VERSION_MAJOR 6 +/// @brief Minor version of libkactivities, at compile time +#define KACTIVITIES_VERSION_MINOR 2 +/// @brief Release version of libkactivities, at compile time +#define KACTIVITIES_VERSION_RELEASE 0 + +#define KACTIVITIES_MAKE_VERSION(a,b,c) (((a) << 16) | ((b) << 8) | (c)) + +/** + * Compile time macro for the version number of libkactivities + */ +#define KACTIVITIES_VERSION \ + KACTIVITIES_MAKE_VERSION(KACTIVITIES_VERSION_MAJOR, KACTIVITIES_VERSION_MINOR, KACTIVITIES_VERSION_RELEASE) + +/** + * Compile-time macro for checking the kactivities version. Not useful for + * detecting the version of libkactivities at runtime. + */ +#define KACTIVITIES_IS_VERSION(a,b,c) (KACTIVITIES_VERSION >= KACTIVITIES_MAKE_VERSION(a,b,c)) + +/** + * Namespace for everything in libkactivities + */ +namespace KActivities +{ + +/** + * The runtime version of libkactivities + */ +KACTIVITIES_EXPORT unsigned int version(); + +/** + * The runtime major version of libkactivities + */ +KACTIVITIES_EXPORT unsigned int versionMajor(); + +/** + * The runtime major version of libkactivities + */ +KACTIVITIES_EXPORT unsigned int versionMinor(); + +/** + * The runtime major version of libkactivities + */ +KACTIVITIES_EXPORT unsigned int versionRelease(); + +/** + * The runtime version string of libkactivities + */ +KACTIVITIES_EXPORT const char *versionString(); + +} // KActivities namespace + +#endif // multiple inclusion guard diff --git a/kactivities/src/lib/models/CMakeLists.txt b/kactivities/src/lib/models/CMakeLists.txt new file mode 100644 index 00000000..00bf06ed --- /dev/null +++ b/kactivities/src/lib/models/CMakeLists.txt @@ -0,0 +1,224 @@ +project (kactivities-models) + +find_package (KDE4 REQUIRED) + +include (KDE4Defaults) +include (MacroLibrary) +include (MacroOptionalAddSubdirectory) +include (FindPackageHandleStandardArgs) + +# ======================================================= +# Information to update before to release this library. + +# Library version history: +# API ABI +# 0.1.0 => 0.1.0 +# 0.1.1 => 0.1.1 +# 0.2.0 => 0.2.0 + +# Library API version +set (KACTIVITIES_MODELS_LIB_MAJOR_VERSION "1") +set (KACTIVITIES_MODELS_LIB_MINOR_VERSION "0") +set (KACTIVITIES_MODELS_LIB_PATCH_VERSION "0") + +# Suffix to add at end of version string. Usual values are: +# "-git" : alpha code unstable from git. Do not use in production +# "-beta1" : beta1 release. +# "-beta2" : beta2 release. +# "-beta3" : beta3 release. +# "-rc" : release candidate. +# "" : final relase. Can be used in production. +set (KACTIVITIES_MODELS_LIB_SUFFIX_VERSION "-git") + +# Library ABI version used by linker. +# For details : http://www.gnu.org/software/libtool/manual/libtool.html#Updating-version-info +set (KACTIVITIES_MODELS_LIB_SO_CUR_VERSION "1") +set (KACTIVITIES_MODELS_LIB_SO_REV_VERSION "0") +set (KACTIVITIES_MODELS_LIB_SO_AGE_VERSION "0") + +# ======================================================= +# Set env. variables accordinly. + +set (KACTIVITIES_MODELS_INCLUDE_DIR + "${CMAKE_CURRENT_SOURCE_DIR}/.." + "${CMAKE_CURRENT_SOURCE_DIR}/" + CACHE STRING + "Location of libkactivities-models headers" FORCE) +set (KACTIVITIES_MODELS_LIBS + "kactivities-models" + CACHE STRING + "Location of libkactivities-models binary" FORCE) + +set (KACTIVITIES_MODELS_LIB_VERSION_STRING "${KACTIVITIES_MODELS_LIB_MAJOR_VERSION}.${KACTIVITIES_MODELS_LIB_MINOR_VERSION}.${KACTIVITIES_MODELS_LIB_PATCH_VERSION}${KACTIVITIES_MODELS_LIB_SUFFIX_VERSION}") +set (KACTIVITIES_MODELS_LIB_VERSION_ID "0x0${KACTIVITIES_MODELS_LIB_MAJOR_VERSION}0${KACTIVITIES_MODELS_LIB_MINOR_VERSION}0${KACTIVITIES_MODELS_LIB_PATCH_VERSION}") +set (KACTIVITIES_MODELS_LIB_SO_VERSION_STRING "${KACTIVITIES_MODELS_LIB_SO_CUR_VERSION}.${KACTIVITIES_MODELS_LIB_SO_REV_VERSION}.${KACTIVITIES_MODELS_LIB_SO_AGE_VERSION}") + +add_definitions (${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS} ${KDE4_DEFINITIONS}) + +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${QDBUS_INCLUDE_DIRS} + ${KDE4_INCLUDES} + ${KDE4_KIO_INCLUDES} + ${NEPOMUK_CORE_INCLUDE_DIR} + ${SOPRANO_INCLUDE_DIR} + ) + +set ( + kactivities-models_LIB_SRCS + + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.cpp + activitymodel.cpp + resourcemodel.cpp + ../core/manager_p.cpp + ) + +set ( + kactivities-models-component_LIB_SRCS + activitiescomponentplugin.cpp + ) + +set_source_files_properties ( + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.xml + PROPERTIES + INCLUDE ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.h + ) + +qt4_add_dbus_interface ( + kactivities-models_LIB_SRCS + + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.xml + activities_interface + ) + +qt4_add_dbus_interface ( + kactivities-models_LIB_SRCS + + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Resources.xml + resources_interface + ) + +qt4_add_dbus_interface ( + kactivities-models_LIB_SRCS + + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Features.xml + features_interface + ) + +qt4_add_dbus_interface ( + kactivities-models_LIB_SRCS + + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml + resources_linking_interface + ) + +kde4_add_library ( + kactivities-models SHARED + ${kactivities-models_LIB_SRCS} + ) + +kde4_add_library ( + kactivities-models-component-plugin SHARED + ${kactivities-models-component_LIB_SRCS} + ) + +set_target_properties ( + kactivities-models + PROPERTIES + VERSION ${KACTIVITIES_MODELS_LIB_SO_VERSION_STRING} + SOVERSION ${KACTIVITIES_MODELS_LIB_SO_CUR_VERSION} + ) + +target_link_libraries ( + kactivities-models + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${NEPOMUK_CORE_LIBRARY} + ${SOPRANO_LIBRARIES} + ) + +target_link_libraries ( + kactivities-models-component-plugin + kactivities-models + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${NEPOMUK_CORE_LIBRARY} + ${SOPRANO_LIBRARIES} + ${QT_QTDECLARATIVE_LIBRARY} + ) + +## install + +set ( + kactivities-models_LIB_HEADERS + + activitymodel.h + resourcemodel.h + kactivities_models_export.h + ) + +set ( + kactivities-models_LIB_PRETTY_HEADERS + includes/KActivities/Models/ActivityModel + includes/KActivities/Models/ResourceModel + ) + +install ( + FILES ${kactivities-models_LIB_HEADERS} + DESTINATION ${INCLUDE_INSTALL_DIR}/kactivities-models + COMPONENT Devel + ) + +install ( + FILES ${kactivities-models_LIB_PRETTY_HEADERS} + DESTINATION ${INCLUDE_INSTALL_DIR}/KDE/KActivities/Models + COMPONENT Devel + ) + +install ( + TARGETS kactivities-models + EXPORT kactivities-modelsLibraryTargets + ${INSTALL_TARGETS_DEFAULT_ARGS} + ) + +install ( + EXPORT kactivities-modelsLibraryTargets + DESTINATION ${LIB_INSTALL_DIR}/cmake/KActivities-Models + FILE KActivitiesModelsLibraryTargets.cmake + ) + +install ( + TARGETS + kactivities-models-component-plugin + DESTINATION + ${IMPORTS_INSTALL_DIR}/org/kde/activities/models + ) + +install ( + FILES + qmldir + DESTINATION + ${IMPORTS_INSTALL_DIR}/org/kde/activities/models + ) + +if (NOT WIN32) + configure_file (${CMAKE_CURRENT_SOURCE_DIR}/libkactivities-models.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libkactivities-models.pc) + install (FILES ${CMAKE_CURRENT_BINARY_DIR}/libkactivities-models.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig ) +endif (NOT WIN32) + +configure_file (KActivities-ModelsConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/KActivities-ModelsConfig.cmake @ONLY) + +macro_write_basic_cmake_version_file ( + ${CMAKE_CURRENT_BINARY_DIR}/KActivities-ModelsConfigVersion.cmake + ${KACTIVITIES_MODELS_LIB_MAJOR_VERSION} + ${KACTIVITIES_MODELS_LIB_MINOR_VERSION} + ${KACTIVITIES_MODELS_LIB_PATCH_VERSION} + ) + +install ( + FILES ${CMAKE_CURRENT_BINARY_DIR}/KActivities-ModelsConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/KActivities-ModelsConfigVersion.cmake + DESTINATION ${LIB_INSTALL_DIR}/cmake/KActivities-Models + ) + diff --git a/kactivities/src/lib/models/KActivities-ModelsConfig.cmake.in b/kactivities/src/lib/models/KActivities-ModelsConfig.cmake.in new file mode 100644 index 00000000..54f62673 --- /dev/null +++ b/kactivities/src/lib/models/KActivities-ModelsConfig.cmake.in @@ -0,0 +1,24 @@ +get_filename_component(myDir ${CMAKE_CURRENT_LIST_FILE} PATH) # get the directory where I myself am +get_filename_component(rootDir ${myDir}/@relInstallDir@ ABSOLUTE) # get the chosen install prefix + +# set the version of myself +set(KACTIVITIES_MODELS_VERSION_MAJOR @KACTIVITIES_MODELS_LIB_MAJOR_VERSION@) +set(KACTIVITIES_MODELS_VERSION_MINOR @KACTIVITIES_MODELS_LIB_MINOR_VERSION@) +set(KACTIVITIES_MODELS_VERSION_PATCH @KACTIVITIES_MODELS_LIB_PATCH_VERSION@) +set(KACTIVITIES_MODELS_VERSION @KACTIVITIES_MODELS_LIB_MAJOR_VERSION@.@KACTIVITIES_MODELS_LIB_MINOR_VERSION@.@KACTIVITIES_MODELS_LIB_PATCH_VERSION@) +set(KACTIVITIES_MODELS_VERSION_STRING "@KACTIVITIES_MODELS_LIB_MAJOR_VERSION@.@KACTIVITIES_MODELS_LIB_MINOR_VERSION@.@KACTIVITIES_MODELS_LIB_PATCH_VERSION@") + +# what is my include directory +# we have _DIRS for compat with existing usage +SET(KACTIVITIES_MODELS_INCLUDE_DIRS "@INCLUDE_INSTALL_DIR@" "@INCLUDE_INSTALL_DIR@/KDE" CACHE PATH "Include path for the KActivities-Models library") +set(KACTIVITIES_MODELS_INCLUDES "${rootDir}/@INCLUDE_INSTALL_DIR@" "@INCLUDE_INSTALL_DIR@/KDE") +set(KACTIVITIES_MODELS_ONTOLOGIES_DIR "@KACTIVITIES_MODELS_ONTOLOGIES_DIR@") + +# import the exported targets +include(${myDir}/KActivitiesModelsLibraryTargets.cmake) + +# set the expected library variable +# XXX: only KACTIVITIES_MODELS_LIBRARIES should be used according to CMake's conventions, +# KACTIVITIES_MODELS_LIBRARY is kept for compatibility with all the places using it. +set(KACTIVITIES_MODELS_LIBRARY kactivities-models) +set(KACTIVITIES_MODELS_LIBRARIES kactivities-models) diff --git a/kactivities/src/lib/models/activitiescomponentplugin.cpp b/kactivities/src/lib/models/activitiescomponentplugin.cpp new file mode 100644 index 00000000..a280ea04 --- /dev/null +++ b/kactivities/src/lib/models/activitiescomponentplugin.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "activitiescomponentplugin.h" + +#include + +#include "resourcemodel.h" +#include "activitymodel.h" + +#include + +#define REGISTER_MODEL(Title, Icon, Type) \ + QML_REGISTER_TYPE(Type); \ + AvailableModels::addModel(Title, QIcon::fromTheme(Icon), #Type) + + +void ActivitiesComponentDataPlugin::registerTypes(const char * uri) +{ + qDebug() << "###########"; + Q_ASSERT(uri == QLatin1String("org.kde.activities.models")); + + qmlRegisterType < KActivities::Models::ResourceModel > (uri, 0, 1, "ResourceModel"); + qmlRegisterType < KActivities::Models::ActivityModel > (uri, 0, 1, "ActivityModel"); + +} + +#include "activitiescomponentplugin.moc" + diff --git a/kactivities/src/lib/models/activitiescomponentplugin.h b/kactivities/src/lib/models/activitiescomponentplugin.h new file mode 100644 index 00000000..f166713e --- /dev/null +++ b/kactivities/src/lib/models/activitiescomponentplugin.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011, 2012 by Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef ACTIVITIES_COMPONENT_PLUGIN_H +#define ACTIVITIES_COMPONENT_PLUGIN_H + +#include + +class ActivitiesComponentDataPlugin: public QDeclarativeExtensionPlugin { + Q_OBJECT + +public: + void registerTypes(const char * uri); +}; + +Q_EXPORT_PLUGIN2(activitiescomponentdataplugin, ActivitiesComponentDataPlugin) + +#endif // ACTIVITIES_COMPONENT_DATA_PLUGIN_H + diff --git a/kactivities/src/lib/models/activitymodel.cpp b/kactivities/src/lib/models/activitymodel.cpp new file mode 100644 index 00000000..a14f3961 --- /dev/null +++ b/kactivities/src/lib/models/activitymodel.cpp @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "activitymodel.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +// from libkactivities (core) +#include "../core/manager_p.h" +#include "../core/utils_p.h" + +#include "utils_p.h" + +namespace KActivities { +namespace Models { + +static bool activityInfoLessThen(const ActivityInfo & left, const ActivityInfo & right) +{ + return left.name.toLower() < right.name.toLower(); +} + +class ActivityModel::Private { +public: + DECLARE_RAII_MODEL_UPDATERS(ActivityModel) + + Private(ActivityModel * parent) + : q(parent), valid(false) + { + qDebug() << "Manager isServicePresent" << Manager::isServicePresent(); + if (Manager::isServicePresent()) + fetchActivityList(); + + connect(Manager::self(), SIGNAL(servicePresenceChanged(bool)), + q, SLOT(servicePresenceChanged(bool))); + + connect(Manager::activities(), SIGNAL(ActivityAdded(QString)), + q, SLOT(activityAdded(QString))); + connect(Manager::activities(), SIGNAL(ActivityRemoved(QString)), + q, SLOT(activityRemoved(QString))); + connect(Manager::activities(), SIGNAL(ActivityNameChanged(QString, QString)), + q, SLOT(activityNameChanged(QString, QString))); + connect(Manager::activities(), SIGNAL(ActivityIconChanged(QString, QString)), + q, SLOT(activityIconChanged(QString, QString))); + connect(Manager::activities(), SIGNAL(ActivityStateChanged(QString, int)), + q, SLOT(activityStateChanged(QString, int))); + } + + void fetchActivityList(); + void fetchActivityInfo(const QString & activity); + + void listActivitiesCallFinished(QDBusPendingCallWatcher * watcher); + void activityInfoCallFinished(QDBusPendingCallWatcher * watcher); + QMutex listActivitiesMutex; + QMutex activityInfoMutex; + + void activityNameChanged(const QString & activity, const QString & name); + void activityIconChanged(const QString & activity, const QString & icon); + void activityStateChanged(const QString & activity, int state); + + void activityAdded(const QString & activity); + void activityRemoved(const QString & activity); + + void servicePresenceChanged(bool present); + + QList < ActivityInfo > activities; + QHash < QString, int > activityIndex; + + ActivityModel * const q; + + QDBusPendingCallWatcher * listActivitiesCallWatcher; + + bool valid : 1; +}; + +void ActivityModel::Private::servicePresenceChanged(bool present) +{ + Q_UNUSED(present) + + model_reset m(q); + + valid = false; + + if (valid) fetchActivityList(); +} + +void ActivityModel::Private::fetchActivityList() +{ + qDebug() << "getting the list of activities"; + KAMD_RETRIEVE_REMOTE_VALUE(listActivities, ListActivitiesWithInformation(), q); +} + +void ActivityModel::Private::fetchActivityInfo(const QString & activity) +{ + QDBusPendingCallWatcher * activityInfoCallWatcher; + qDebug() << "getting info for " << activity; + KAMD_RETRIEVE_REMOTE_VALUE(activityInfo, ActivityInformation(activity), q); +} + +void ActivityModel::Private::listActivitiesCallFinished(QDBusPendingCallWatcher * watcher) +{ + qDebug() << "got the activities"; + model_reset m(q); + + QDBusPendingReply reply = * watcher; + + if (reply.isError()) { + valid = false; + qDebug() << "we got some kind of error" << reply.error(); + return; + } + + activities = reply.argumentAt<0>(); + + qSort(activities.begin(), activities.end(), activityInfoLessThen); + + for (int i = 0; i < activities.size(); i++) { + activityIndex[activities[i].id] = i; + } + + valid = true; + + qDebug() << activities.size(); + + watcher->deleteLater(); +} + +void ActivityModel::Private::activityInfoCallFinished(QDBusPendingCallWatcher * watcher) +{ + qDebug() << "got the activities"; + + QDBusPendingReply reply = * watcher; + + if (reply.isError()) { + valid = false; + qDebug() << "we got some kind of error" << reply.error(); + return; + } + + ActivityInfo info = reply.argumentAt<0>(); + + QList < ActivityInfo > ::iterator insertAt + = qLowerBound(activities.begin(), activities.end(), info, activityInfoLessThen); + + insertAt = activities.insert(insertAt, info); + + int index = insertAt - activities.begin(); + model_insert m(q, QModelIndex(), index, index); + + QMutableHashIterator < QString, int > i(activityIndex); + while (i.hasNext()) { + i.next(); + + const int value = i.value(); + if (value >= index) + i.setValue(value + 1); + } + + activityIndex[info.id] = index; + + watcher->deleteLater(); +} + +#define PROPERTY_CHANGED_HANDLER(Name, StrName, Type) \ + void ActivityModel::Private::Name##Changed(const QString & activity, Type StrName) \ + { \ + if (!activityIndex.contains(activity)) return; \ + \ + const int index = activityIndex[activity]; \ + activities[index].StrName = StrName; \ + \ + QModelIndex i = q->index(index); \ + q->dataChanged(i, i); \ + } + +PROPERTY_CHANGED_HANDLER(activityName, name, const QString &) +PROPERTY_CHANGED_HANDLER(activityIcon, icon, const QString &) +PROPERTY_CHANGED_HANDLER(activityState, state, int) + +#undef PROPERTY_CHANGED_HANDLER + +void ActivityModel::Private::activityAdded(const QString & activity) +{ + fetchActivityInfo(activity); +} + +void ActivityModel::Private::activityRemoved(const QString & activity) +{ + if (!activityIndex.contains(activity)) return; + + int index = activityIndex[activity]; + model_remove m(q, QModelIndex(), index, index); + + activities.removeAt(index); + activityIndex.remove(activity); + + QMutableHashIterator < QString, int > i(activityIndex); + while (i.hasNext()) { + i.next(); + + const int value = i.value(); + if (value > index) + i.setValue(value - 1); + } +} + + +ActivityModel::ActivityModel(QObject * parent) + : QAbstractListModel(parent), d(new Private(this)) +{ + qDebug() << "################"; + d->valid = false; + + QHash roles; + + roles[Qt::DisplayRole] = "name"; + roles[Qt::DecorationRole] = "icon"; + roles[ActivityState] = "state"; + roles[ActivityId] = "id"; + + setRoleNames(roles); +} + +ActivityModel::~ActivityModel() +{ + delete d; +} + +int ActivityModel::rowCount(const QModelIndex & parent) const +{ + Q_UNUSED(parent); + + if (!d->valid) return 0; + + return d->activities.size(); +} + +QVariant ActivityModel::data(const QModelIndex & index, int role) const +{ + if (!d->valid) return QVariant(); + + const int row = index.row(); + + switch (role) { + case Qt::DisplayRole: + return d->activities[row].name; + + case Qt::DecorationRole: + return KIcon(d->activities[row].icon); + + case ActivityId: + return d->activities[row].id; + + case ActivityState: + return d->activities[row].state; + + default: + return QVariant(); + } +} + +QVariant ActivityModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(orientation) + + if (section == 0 && role == Qt::DisplayRole) { + return i18nc("Header title for activity data model", "Activity"); + } + + return QVariant(); +} + +} // namespace Models +} // namespace KActivities + +#include "activitymodel.moc" diff --git a/kactivities/src/lib/models/activitymodel.h b/kactivities/src/lib/models/activitymodel.h new file mode 100644 index 00000000..4d6abbf7 --- /dev/null +++ b/kactivities/src/lib/models/activitymodel.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KACTIVITIES_MODELS_ACTIVITY_MODEL_H +#define KACTIVITIES_MODELS_ACTIVITY_MODEL_H + +#include + +#include "kactivities_models_export.h" + +class QModelIndex; + +namespace KActivities { +namespace Models { + +/** + * ActivityModel + */ + +class KACTIVITIES_MODELS_EXPORT ActivityModel: public QAbstractListModel { + Q_OBJECT + +public: + ActivityModel(QObject * parent = 0); + virtual ~ActivityModel(); + + virtual int rowCount(const QModelIndex & parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + enum Roles { + ActivityId = Qt::UserRole, + ActivityState = Qt::UserRole + 1 + }; + + enum State { + Invalid = 0, + Running = 2, + Starting = 3, + Stopped = 4, + Stopping = 5 + }; + + +private: + Q_PRIVATE_SLOT(d, void listActivitiesCallFinished(QDBusPendingCallWatcher*)) + Q_PRIVATE_SLOT(d, void activityInfoCallFinished(QDBusPendingCallWatcher*)) + + Q_PRIVATE_SLOT(d, void activityNameChanged(const QString &, const QString &)) + Q_PRIVATE_SLOT(d, void activityIconChanged(const QString &, const QString &)) + Q_PRIVATE_SLOT(d, void activityStateChanged(const QString &, int)) + + Q_PRIVATE_SLOT(d, void activityAdded(const QString &)) + Q_PRIVATE_SLOT(d, void activityRemoved(const QString &)) + + Q_PRIVATE_SLOT(d, void servicePresenceChanged(bool)) + + friend class Private; + + class Private; + Private * const d; +}; + +} // namespace Models +} // namespace KActivities + +#endif // KACTIVITIES_MODELS_ACTIVITY_MODEL_H + diff --git a/kactivities/src/lib/models/includes/KActivities/Models/ActivityModel b/kactivities/src/lib/models/includes/KActivities/Models/ActivityModel new file mode 100644 index 00000000..13cc3cf6 --- /dev/null +++ b/kactivities/src/lib/models/includes/KActivities/Models/ActivityModel @@ -0,0 +1 @@ +#include "../../../kactivities-models/activitymodel.h" diff --git a/kactivities/src/lib/models/includes/KActivities/Models/ResourceModel b/kactivities/src/lib/models/includes/KActivities/Models/ResourceModel new file mode 100644 index 00000000..17ef3666 --- /dev/null +++ b/kactivities/src/lib/models/includes/KActivities/Models/ResourceModel @@ -0,0 +1 @@ +#include "../../../kactivities-models/resourcemodel.h" diff --git a/kactivities/src/lib/models/kactivities_models_export.h b/kactivities/src/lib/models/kactivities_models_export.h new file mode 100644 index 00000000..a2c46e8c --- /dev/null +++ b/kactivities/src/lib/models/kactivities_models_export.h @@ -0,0 +1,52 @@ +/**************************************************************************************** + * Copyright (c) 2012 Patrick von Reth * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU Lesser General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License along with * + * this program. If not, see . * + ****************************************************************************************/ + +#ifndef KACTIVITIES_MODELS_EXPORT_H +#define KACTIVITIES_MODELS_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include + +#ifndef KACTIVITIES_MODELS_EXPORT +# ifdef MAKE_KACTIVITIES_MODELS_LIB + /* We are building this library */ +# define KACTIVITIES_MODELS_EXPORT KDE_EXPORT + +# if defined(DEBUG) +# define KACTIVITIES_MODELS_EXPORT_TESTS KDE_EXPORT +# else +# define KACTIVITIES_MODELS_EXPORT_TESTS +# endif + +# else + /* We are using this library */ +# define KACTIVITIES_MODELS_EXPORT KDE_IMPORT + +# if defined(DEBUG) +# define KACTIVITIES_MODELS_EXPORT_TESTS KDE_IMPORT +# else +# define KACTIVITIES_MODELS_EXPORT_TESTS +# endif + +# endif//MAKE_KACTIVITIES_MODELS_LIB +#endif// KACTIVITIES_MODELS_EXPORT + +# ifndef KACTIVITIES_MODELS_EXPORT_DEPRECATED +# define KACTIVITIES_MODELS_EXPORT_DEPRECATED KDE_DEPRECATED KACTIVITIES_MODELS_EXPORT +# endif + +#endif + diff --git a/kactivities/src/lib/models/libkactivities-models.pc.cmake b/kactivities/src/lib/models/libkactivities-models.pc.cmake new file mode 100644 index 00000000..7e0cdb06 --- /dev/null +++ b/kactivities/src/lib/models/libkactivities-models.pc.cmake @@ -0,0 +1,12 @@ +prefix=${CMAKE_INSTALL_PREFIX} +exec_prefix=${BIN_INSTALL_DIR} +libdir=${LIB_INSTALL_DIR} +includedir=${INCLUDE_INSTALL_DIR} + +Name: libkkactivities-models +Description: libkkactivities is a C++ library that provides KDE activity-related item models +URL: http://www.kde.org +Requires: +Version: ${KACTIVITIES_MODELS_LIB_VERSION_STRING} +Libs: -L${LIB_INSTALL_DIR} -lkactivities-models +Cflags: -I${INCLUDE_INSTALL_DIR} diff --git a/kactivities/src/lib/models/qmldir b/kactivities/src/lib/models/qmldir new file mode 100644 index 00000000..b159b300 --- /dev/null +++ b/kactivities/src/lib/models/qmldir @@ -0,0 +1,2 @@ +plugin kactivities-models-component-plugin + diff --git a/kactivities/src/lib/models/resourcemodel.cpp b/kactivities/src/lib/models/resourcemodel.cpp new file mode 100644 index 00000000..db3e442e --- /dev/null +++ b/kactivities/src/lib/models/resourcemodel.cpp @@ -0,0 +1,591 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "resourcemodel.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include + +// from libkactivities (core) +#include "../core/manager_p.h" +#include "../core/utils_p.h" + +#include "utils_p.h" + +namespace Nepomuk = Nepomuk2; +namespace NQuery = Nepomuk2::Query; + +using namespace Soprano::Vocabulary; +using namespace Nepomuk::Vocabulary; + +namespace KActivities { +namespace Models { + +template +inline short sign(T value) +{ + return value >= 0 ? 1 : -1; +} + +template +inline T abs(T value) +{ + return value >= 0 ? value : -value; +} + +struct ResourceInfo { + QUrl resource; + QString url; + QString title; + QString icon; + double score; + + bool operator >= (const ResourceInfo & other) const { + short this_sign = sign(score); + short other_sign = sign(other.score); + + if (this_sign != other_sign) { + return (other_sign == -1); + } + + double this_abs = abs(score); + double other_abs = abs(other.score); + + if (this_abs < other_abs) return true; + + return title < other.title; + } + + bool operator < (const ResourceInfo & other) const { + return !(*this >= other); + } +}; + +typedef QList < ResourceInfo > ResourceInfoList; + +class ResourceModel::Private { +public: + DECLARE_RAII_MODEL_UPDATERS(ResourceModel) + + Private(ResourceModel * parent) + : limit(10), service(0), q(parent), valid(false), showCurrentActivity(true) + { + servicePresenceChanged(Manager::isServicePresent()); + + connect(Manager::self(), SIGNAL(servicePresenceChanged(bool)), + q, SLOT(servicePresenceChanged(bool))); + } + + void reload(); + void servicePresenceChanged(bool present); + void resourceScoreUpdated(const QString & activity, const QString & client, const QString & resource, double score); + + void newEntries(const QList < NQuery::Result > & entries); + void entriesRemoved(const QList < QUrl > & entries); + void error(const QString & errorMessage); + + static + ResourceInfo infoFromResult(const NQuery::Result & result); + + void loadFromQuery(const QString & query); + void loadLinked(); + void loadTopRated(); + void loadRecent(); + + QString activityToShowN3() const; + void setCurrentActivity(const QString & activity); + + QString activity; + QString currentActivity; + QString application; + int limit; + ResourceModel::ContentMode contentMode; + + QSet < QString > resourceSet; + ResourceInfoList resources; + QList < NQuery::QueryServiceClient * > queries; + + QDBusInterface * service; + + ResourceModel * const q; + bool valid : 1; + bool showCurrentActivity : 1; +}; + +void ResourceModel::Private::reload() +{ + servicePresenceChanged(Manager::isServicePresent()); +} + +void ResourceModel::Private::resourceScoreUpdated(const QString & activity, const QString & client, const QString & resource, double score) +{ + qDebug() << activity << client << resource << score; +} + +void ResourceModel::Private::servicePresenceChanged(bool present) +{ + qDebug() << present; + model_reset m(q); + + resources.clear(); + resourceSet.clear(); + + valid = present; + + if (service) { + delete service; + service = 0; + } + + if (!valid) return; + + if (showCurrentActivity && currentActivity.isEmpty()) { + // we need to show the current activity, but don't know which it is + + bool res = Manager::activities()->callWithCallback("CurrentActivity", QVariantList(), q, SLOT(setCurrentActivity(QString))); + qDebug() << "CALLING" << res; + + connect(Manager::activities(), SIGNAL(CurrentActivityChanged(QString)), + q, SLOT(setCurrentActivity(QString))); + + return; + } + + service = new QDBusInterface( + "org.kde.ActivityManager", + "/ActivityManager/Resources/Scoring", + "org.kde.ActivityManager.Resources.Scoring" + ); + + // connect(service, SIGNAL(resourceScoreUpdated(QString, QString, QString, double)), + // q, SLOT(resourceScoreUpdated(QString, QString, QString, double))); + + qDeleteAll(queries); + queries.clear(); + + contentMode = Recent; + + switch (contentMode) { + case Favorites: + loadTopRated(); + loadLinked(); + break; + + case Linked: + loadLinked(); + break; + + case TopRated: + loadTopRated(); + break; + + case Recent: + loadRecent(); + break; + } +} + +void ResourceModel::Private::loadFromQuery(const QString & query) +{ + qDebug() << query; + + NQuery::QueryServiceClient * queryClient = new NQuery::QueryServiceClient(q); + + NQuery::RequestPropertyMap requestPropertyMap; + requestPropertyMap.insert("url", NIE::url()); + requestPropertyMap.insert("title", NAO::prefLabel()); + requestPropertyMap.insert("score", NAO::numericRating()); + requestPropertyMap.insert("icon", NAO::iconName()); + + queryClient->sparqlQuery(query, requestPropertyMap); + + connect(queryClient, SIGNAL(newEntries(QList)), + q, SLOT(newEntries(QList))); + connect(queryClient, SIGNAL(entriesRemoved(QList)), + q, SLOT(entriesRemoved(QList))); + connect(queryClient, SIGNAL(error(QString)), + q, SLOT(error(QString))); + + queries << queryClient; +} + +void ResourceModel::Private::loadLinked() +{ + static const QString & _query = QString::fromLatin1( + "select distinct ?r, ?url, -1 as ?score, ?title, ?icon where { " + "?activity nao:isRelated ?r . " + "?activity kao:activityIdentifier %1. " + "?r nie:url ?url . " + "OPTIONAL { ?r nao:prefLabel ?title } . " + "OPTIONAL { ?r nao:iconName ?icon } . " + "%2 " + "}" + ); + + static const QString & _applicationFilter = QString::fromLatin1( + "?scoreCache a kao:ResourceScoreCache . " + "?scoreCache kao:usedActivity ?activity . " + "?scoreCache kao:targettedResource ?r . " + "?scoreCache kao:initiatingAgent ?agent . " + "?agent nao:identifier %1 ." + ); + + loadFromQuery(_query.arg( + activityToShowN3(), + (application.isEmpty() ? + QString() : + _applicationFilter.arg(Soprano::Node::literalToN3(application)) + ) + )); +} + +void ResourceModel::Private::loadRecent() +{ + static const QString & _query = QString::fromLatin1( + "select distinct ?r, ?url, " + // "(bif:datediff ('second', \"1970-01-01\"^^, ?lastModified)) as ?score, " + "(bif:datediff ('second', \"1970-01-01\"^^, ?lastModified)) as ?score, " + "?title, ?icon where { " + "?scoreCache a kao:ResourceScoreCache . " + "?scoreCache kao:usedActivity ?activity . " + "?activity kao:activityIdentifier %1. " + "?scoreCache kao:targettedResource ?r . " + "?scoreCache nao:lastModified ?lastModified . " + "?r nie:url ?url . " + "OPTIONAL { ?r nao:prefLabel ?title } . " + "OPTIONAL { ?r nao:iconName ?icon } . " + "%2 " + "} order by desc(?score) limit %3" + ); + + static const QString & _applicationFilter = QString::fromLatin1( + "?scoreCache kao:initiatingAgent ?agent . " + "?agent nao:identifier %1 ." + ); + + qDebug() << Soprano::Node::literalToN3(QDate(1970,1,1)); + + loadFromQuery(_query.arg( + activityToShowN3(), + (application.isEmpty() ? + QString() : + _applicationFilter.arg(Soprano::Node::literalToN3(application)) + ), + QString::number(limit) + )); +} + +void ResourceModel::Private::loadTopRated() +{ + static const QString & _query = QString::fromLatin1( + "select distinct ?r, ?url, ?score, ?title, ?icon where { " + "?scoreCache a kao:ResourceScoreCache . " + "?scoreCache kao:usedActivity ?activity . " + "?activity kao:activityIdentifier %1. " + "?scoreCache kao:targettedResource ?r . " + "?scoreCache kao:cachedScore ?score . " + "?r nie:url ?url . " + "OPTIONAL { ?r nao:prefLabel ?title } . " + "OPTIONAL { ?r nao:iconName ?icon } . " + "%2 " + "} order by desc(?score) limit %3" + ); + + static const QString & _applicationFilter = QString::fromLatin1( + "?scoreCache kao:initiatingAgent ?agent . " + "?agent nao:identifier %1 ." + ); + + loadFromQuery(_query.arg( + activityToShowN3(), + (application.isEmpty() ? + QString() : + _applicationFilter.arg(Soprano::Node::literalToN3(application)) + ), + QString::number(limit) + )); +} + +QString ResourceModel::Private::activityToShowN3() const +{ + return Soprano::Node::literalToN3( + activity.isEmpty() ? + currentActivity : + activity + ); +} + +ResourceInfo ResourceModel::Private::infoFromResult(const NQuery::Result & result) +{ + ResourceInfo info; + info.resource = result.resource().uri(); + + QHash < Nepomuk::Types::Property, Soprano::Node > props = result.requestProperties(); + + info.url = props[NIE::url()].toString(); + info.title = props[NAO::prefLabel()].toString(); + info.icon = props[NAO::iconName()].toString(); + info.score = props[NAO::numericRating()].toString().toDouble(); + + qDebug() + << info.url + << info.title + << info.icon + << info.score; + + if (info.title.isEmpty() /*&& info.url.startsWith("file://")*/) { + KFileItem fileItem(KFileItem::Unknown, KFileItem::Unknown, info.url); + + if (fileItem.isFile() || fileItem.isDir()) { + info.title = fileItem.text(); + info.icon = fileItem.iconName(); + + qDebug() << "## 1 ##" << info.title << info.icon; + + } else { + info.title = info.url; + + } + } + + return info; +} + +void ResourceModel::Private::newEntries(const QList < NQuery::Result > & entries) +{ + // model_insert m(q, QModelIndex(), 0, entries.size()); + model_reset m(q); + + ResourceInfoList newEntries; + + foreach (const NQuery::Result & result, entries) { + const ResourceInfo & entry = infoFromResult(result); + + if (entry.title.isEmpty() + || resourceSet.contains(entry.url) + || entry.url.startsWith(QLatin1String("filex://"))) + continue; + + resourceSet << entry.url; + newEntries << entry; + + } + + kamd::utils::merge_into(resources, newEntries); + + valid = 1; +} + +void ResourceModel::Private::entriesRemoved(const QList < QUrl > & entries) +{ + model_reset m(q); + + foreach (const QUrl & entry, entries) { + qDebug() << "Removing: " << entry; + + ResourceInfoList::iterator start = resources.begin(); + ResourceInfoList::iterator end = resources.end(); + + while (start != end) { + if (start->resource == entry) { + start = resources.erase(start); + end = resources.end(); + + } else { + ++start; + + } + } + } +} + +void ResourceModel::Private::setCurrentActivity(const QString & activity) +{ + if (currentActivity == activity) return; + + currentActivity = activity; + + reload(); +} + +void ResourceModel::Private::error(const QString & errorMessage) +{ + qDebug() << errorMessage; +} + +ResourceModel::ResourceModel(QObject * parent) + : QAbstractListModel(parent), d(new Private(this)) +{ + d->valid = false; + + QHash roles; + + roles[Qt::DisplayRole] = "name"; + roles[Qt::DecorationRole] = "icon"; + + setRoleNames(roles); +} + +ResourceModel::~ResourceModel() +{ + delete d; +} + +int ResourceModel::rowCount(const QModelIndex & parent) const +{ + Q_UNUSED(parent); + + if (!d->valid) return 0; + + return qMin(d->limit, d->resources.size()); +} + +QVariant ResourceModel::data(const QModelIndex & index, int role) const +{ + if (!d->valid) return QVariant(); + + const int row = index.row(); + + if (row >= d->resources.size()) return QVariant(); + + const ResourceInfo & info = d->resources[row]; + + switch (role) { + case Qt::DisplayRole: + return info.title; + // return QString(info.title + " " + QString::number(info.score)); + + case Qt::DecorationRole: + return KIcon(info.icon); + + case ResourceUrl: + return info.url; + + case ResourceIconName: + return info.icon; + + case ResourceScore: + return info.score; + + default: + return QVariant(); + } +} + +QVariant ResourceModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(orientation) + + if (section == 0 && role == Qt::DisplayRole) { + return i18nc("Header title for resource data model", "Resource"); + } + + return QVariant(); +} + +void ResourceModel::setActivity(const QString & activity) +{ + if (d->activity == activity) return; + + d->activity = activity; + + d->showCurrentActivity = d->activity.isEmpty(); + + emit activityChanged(activity); + + d->reload(); +} + +QString ResourceModel::activity() const +{ + return d->activity; +} + +void ResourceModel::setApplication(const QString & application) +{ + if (d->application == application) return; + + qDebug() << "Setting the application to:" << application; + + d->application = application; + + emit applicationChanged(application); + + d->reload(); +} + +QString ResourceModel::application() const +{ + return d->application; +} + +void ResourceModel::setLimit(int count) +{ + if (d->limit == count) return; + + d->limit = count; + + emit limitChanged(count); + + d->reload(); +} + +int ResourceModel::limit() const +{ + return d->limit; +} + +void ResourceModel::setContentMode(ResourceModel::ContentMode mode) +{ + if (d->contentMode == mode) return; + + d->contentMode = mode; + d->reload(); +} + +ResourceModel::ContentMode ResourceModel::contentMode() const +{ + return d->contentMode; +} + +} // namespace Models +} // namespace KActivities + +#include "resourcemodel.moc" diff --git a/kactivities/src/lib/models/resourcemodel.h b/kactivities/src/lib/models/resourcemodel.h new file mode 100644 index 00000000..ce39b15a --- /dev/null +++ b/kactivities/src/lib/models/resourcemodel.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KACTIVITIES_MODELS_RESOURCE_MODEL_H +#define KACTIVITIES_MODELS_RESOURCE_MODEL_H + +#include + +#include "kactivities_models_export.h" + +class QModelIndex; + +namespace KActivities { +namespace Models { + +/** + * ResourceModel + */ + +class KACTIVITIES_MODELS_EXPORT ResourceModel: public QAbstractListModel { + Q_OBJECT + + Q_PROPERTY(QString activity READ activity WRITE setActivity NOTIFY activityChanged) + Q_PROPERTY(QString application READ application WRITE setApplication NOTIFY applicationChanged) + Q_PROPERTY(int limit READ limit WRITE setLimit NOTIFY limitChanged) + +public: + ResourceModel(QObject * parent = 0); + virtual ~ResourceModel(); + + /** + * What should the model display? + */ + enum ContentMode { + Favorites, // Show linked resources first, then the top rated (default) + Linked, // Show only linked resources + TopRated, // Show only top rated resources + Recent // Show recently used resources + }; + + enum Roles { + ResourceUrl = Qt::UserRole, + ResourceScore, + ResourceIconName + }; + + virtual int rowCount(const QModelIndex & parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + +public Q_SLOTS: + /** + * Shows resources related to specific activity + * @param activity activity id. + * @note if the activity id is empty, the model + * will display resources linked to the current activity + * (default) + */ + void setActivity(const QString & activity); + + /** + * @returns which activity the model displays + */ + QString activity() const; + + /** + * Shows resources related to a specific application + * @param application application id + * @note if empty, shows resources for all applications + * aggregated (default) + */ + void setApplication(const QString & application); + + /** + * @returns for which application the resources are shown + */ + QString application() const; + + /** + * Limit the number of items to show + * @param count number of items to show + * @note default is 10, increasing it significantely + * can lead to performance degradation + */ + void setLimit(int count); + + /** + * @returns the item count limit + */ + int limit() const; + + /** + * Sets the list mode + */ + void setContentMode(ContentMode mode); + + /** + * @returns display mode + */ + ContentMode contentMode() const; + +Q_SIGNALS: + void applicationChanged(const QString & application); + void activityChanged(const QString & activity); + void limitChanged(int limit); + + +private: + Q_PRIVATE_SLOT(d, void servicePresenceChanged(bool)) + Q_PRIVATE_SLOT(d, void resourceScoreUpdated(QString, QString, QString, double)) + + Q_PRIVATE_SLOT(d, void newEntries(QList)) + Q_PRIVATE_SLOT(d, void entriesRemoved(QList)) + Q_PRIVATE_SLOT(d, void error(QString)) + + Q_PRIVATE_SLOT(d, void setCurrentActivity(QString)) + + friend class Private; + + class Private; + Private * const d; +}; + +} // namespace Models +} // namespace KActivities + +#endif // KACTIVITIES_MODELS_RESOURCE_MODEL_H + diff --git a/kactivities/src/lib/models/utils_p.h b/kactivities/src/lib/models/utils_p.h new file mode 100644 index 00000000..af44febf --- /dev/null +++ b/kactivities/src/lib/models/utils_p.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KACTIVITIES_MODELS_UTILS_P_H +#define KACTIVITIES_MODELS_UTILS_P_H + +// ----------------------------------------- +// RAII classes for model updates ---------- +// ----------------------------------------- + +#define DECLARE_RAII_MODEL_UPDATERS(Class) \ +template \ +class _model_reset { \ + T * model; \ +public: \ + _model_reset(T * m) : model(m) { model->beginResetModel(); } \ + ~_model_reset() { model->endResetModel(); } \ +}; \ + \ +template \ +class _model_insert { \ + T * model; \ +public: \ + _model_insert(T * m, const QModelIndex & parent, int first, int last) \ + : model(m) { model->beginInsertRows(parent, first, last); } \ + ~_model_insert() { model->endInsertRows(); } \ +}; \ + \ +template \ +class _model_remove { \ + T * model; \ +public: \ + _model_remove(T * m, const QModelIndex & parent, int first, int last) \ + : model(m) { model->beginRemoveRows(parent, first, last); } \ + ~_model_remove() { model->endRemoveRows(); } \ +}; \ + \ +typedef _model_reset model_reset; \ +typedef _model_remove model_remove; \ +typedef _model_insert model_insert; + +// ----------------------------------------- + +#endif // KACTIVITIES_MODELS_UTILS_P_H diff --git a/kactivities/src/ontologies/CMakeLists.txt b/kactivities/src/ontologies/CMakeLists.txt new file mode 100644 index 00000000..16c6befc --- /dev/null +++ b/kactivities/src/ontologies/CMakeLists.txt @@ -0,0 +1,9 @@ +configure_file (kao.ontology.in ${CMAKE_CURRENT_BINARY_DIR}/kao.ontology) + +install ( + FILES + kao.trig + ${CMAKE_CURRENT_BINARY_DIR}/kao.ontology + DESTINATION + ${KACTIVITIES_ONTOLOGIES_DIR} + ) diff --git a/kactivities/src/ontologies/kao.ontology.in b/kactivities/src/ontologies/kao.ontology.in new file mode 100644 index 00000000..1d733ad9 --- /dev/null +++ b/kactivities/src/ontologies/kao.ontology.in @@ -0,0 +1,8 @@ +[Ontology] +Version=1.0 +Name=KDE Activities Ontology +Comment=KDE Activities Ontology contains classes that are related to the Activity Manager system +Namespace=http://nepomuk.kde.org/ontologies/2012/02/29/kao# +Path=${CMAKE_INSTALL_PREFIX}/share/ontology/kde/kao.trig +MimeType=application/x-trig +Type=Data diff --git a/kactivities/src/ontologies/kao.trig b/kactivities/src/ontologies/kao.trig new file mode 100644 index 00000000..a16448ee --- /dev/null +++ b/kactivities/src/ontologies/kao.trig @@ -0,0 +1,123 @@ +# +# Copyright (c) 2011-2012 Ivan Cukic +# Copyright (c) 2010-2011 Sebastian Trueg +# +# All rights reserved, licensed under either CC-BY or BSD. +# +# You are free: +# * to Share - to copy, distribute and transmit the work +# * to Remix - to adapt the work +# Under the following conditions: +# * Attribution - You must attribute the work in the manner specified by the author +# or licensor (but not in any way that suggests that they endorse you or your use +# of the work). +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this +# list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +# * Neither the names of the authors nor the names of contributors may +# be used to endorse or promote products derived from this ontology without +# specific prior written permission. +# +# THIS ONTOLOGY IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS ONTOLOGY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +@prefix xsd: . +@prefix rdf: . +@prefix rdfs: . + +@prefix nao: . +@prefix nrl: . +@prefix nfo: . + +@prefix kao: . + +kao: { + kao:Activity + a rdfs:Class ; + rdfs:subClassOf rdfs:Resource ; + rdfs:label "activity" ; + rdfs:comment "An abstract concept to handle various user's activities." . + + kao:usedActivity + a rdf:Property ; + rdfs:label "used activity" ; + rdfs:comment "The activity that was active when resource was created. This is mostly used for graphs or resource events." ; + rdfs:domain rdfs:Resource ; + rdfs:range kao:Activity ; + nrl:maxCardinality 1 ; + nao:userVisible false . + + kao:activityIdentifier + a rdf:Property ; + rdfs:subPropertyOf nao:identifier ; + rdfs:label "activity identifier" ; + rdfs:comment "The unique ID of the activity as used outside of Nepomuk. This is a UUID. Anything else can be considered invalid." ; + rdfs:domain kao:Activity ; + rdfs:range xsd:string ; + nrl:cardinality 1 ; + nao:userVisible false . + + kao:ResourceScoreCache + a rdfs:Class ; + rdfs:subClassOf rdfs:Resource ; + rdfs:label "Resource score cache" ; + rdfs:comment "For storing the automatically calculated score based on the usage statistics" ; + nao:userVisible false . + + kao:targettedResource + a rdf:Property ; + rdfs:comment "Resource for which the score is calculated." ; + rdfs:domain kao:ResourceScoreCache ; + rdfs:label "resource" ; + rdfs:range rdfs:Resource ; + nrl:maxCardinality "1" . + + kao:initiatingAgent + a rdf:Property ; + rdfs:comment "Relates the score to the agent initiating the events." ; + rdfs:domain kao:ResourceScoreCache ; + rdfs:label "involved agent" ; + rdfs:range nao:Agent ; + nrl:maxCardinality "1" . + + kao:cachedScore + a rdf:Property ; + rdfs:subPropertyOf nao:score ; + rdfs:comment "The automatically calculated score" ; + rdfs:domain kao:ResourceScoreCache ; + rdfs:label "calculated score" ; + rdfs:range xsd:float ; + nrl:maxCardinality "1" . +} + + { + + a nrl:GraphMetadata ; + nrl:coreGraphMetadataFor kao: . + + kao: + a nrl:Ontology , nrl:DocumentGraph ; + nao:prefLabel "KDE Activities Ontology" ; + nao:hasDefaultNamespace "http://nepomuk.kde.org/ontologies/2012/02/29/kao#" ; + nao:hasDefaultNamespaceAbbreviation "kao" ; + nao:lastModified "2012-03-31T20:32:00Z" ; + nao:serializationLanguage "TriG" ; + nao:status "Unstable" ; + nrl:updatable "0" ; + nao:version "3" . +} + diff --git a/kactivities/src/service/Activities.cpp b/kactivities/src/service/Activities.cpp new file mode 100644 index 00000000..18ec3394 --- /dev/null +++ b/kactivities/src/service/Activities.cpp @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2010, 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Activities.h" +#include "Activities_p.h" + +#include "activitiesadaptor.h" + +#include +#include +#include + +#include +#include +#include + +#include "jobs/activity/all.h" +#include "jobs/general/all.h" +#include "jobs/schedulers/all.h" +#include "jobs/ksmserver/KSMServer.h" + +#include "common.h" + +#include +#include +#include +#include + +// Private + +Activities::Private::Private(Activities * parent) + : config("activitymanagerrc"), + q(parent) +{ +} + +Activities::Private::~Private() +{ + configSync(); +} + +// Main + +Activities::Activities(QObject * parent) + : Module("activities", parent), d(this) +{ + qDebug() << "\n\n-------------------------------------------------------"; + qDebug() << "Starting the KDE Activity Manager daemon" << QDateTime::currentDateTime(); + qDebug() << "-------------------------------------------------------"; + + // Basic initialization ////////////////////////////////////////////////////////////////////////////////// + + // Initializing D-Bus service + + new ActivitiesAdaptor(this); + KDBusConnectionPool::threadConnection().registerObject( + ACTIVITY_MANAGER_OBJECT_PATH(Activities), this); + + // Initializing config + + d->connect(&d->configSyncTimer, SIGNAL(timeout()), + SLOT(configSync())); + + d->configSyncTimer.setSingleShot(true); + + d->ksmserver = new KSMServer(this); + d->connect(d->ksmserver, SIGNAL(activitySessionStateChanged(QString, int)), + SLOT(activitySessionStateChanged(QString, int))); + + // Activity initialization /////////////////////////////////////////////////////////////////////////////// + + // Reading activities from the config file + + foreach (val & activity, d->activitiesConfig().keyList()) { + d->activities[activity] = Activities::Stopped; + } + + val & runningActivities = d->mainConfig().readEntry("runningActivities", d->activities.keys()); + + foreach (val & activity, runningActivities) { + if (d->activities.contains(activity)) { + d->activities[activity] = Activities::Running; + } + } + + d->loadLastActivity(); +} + +Activities::~Activities() +{ +} + +QString Activities::CurrentActivity() const +{ + return d->currentActivity; +} + +bool Activities::SetCurrentActivity(const QString & activity) +{ + // Public method can not put us in a limbo state + if (activity.isEmpty()) { + return false; + } + + return d->setCurrentActivity(activity); +} + +bool Activities::Private::setCurrentActivity(const QString & activity) +{ + using namespace Jobs; + using namespace Jobs::General; + + // If the activity is empty, this means we are entering a limbo state + if (activity.isEmpty()) { + currentActivity.clear(); + emit q->CurrentActivityChanged(currentActivity); + return true; + } + + // Sanity checks + if (!activities.contains(activity)) return false; + if (currentActivity == activity) return true; + + // Start activity + // TODO: Move this to job-based execution + q->StartActivity(activity); + + // - change the current activity and signal the change + emitCurrentActivityChanged(activity); + + return true; +} + +void Activities::Private::loadLastActivity() +{ + // If there are no public activities, try to load the last used activity + val & lastUsedActivity = mainConfig().readEntry("currentActivity", QString()); + + setCurrentActivity( + (lastUsedActivity.isEmpty() && activities.size() > 0) + ? activities.keys().at(0) + : lastUsedActivity + ); +} + +void Activities::Private::emitCurrentActivityChanged(const QString & activity) +{ + // Saving the current activity, and notifying + // clients of the change + + currentActivity = activity; + mainConfig().writeEntry("currentActivity", activity); + + scheduleConfigSync(); + + emit q->CurrentActivityChanged(activity); +} + +QString Activities::AddActivity(const QString & name) +{ + if (!KAuthorized::authorize("plasma-desktop/add_activities")) { + return QString(); + } + + if (name.isEmpty()) { + Q_ASSERT(!name.isEmpty()); + return QString(); + } + + QString activity; + + // Ensuring a new Uuid. The loop should usually end after only + // one iteration + + val & existingActivities = d->activities.keys(); + while (activity.isEmpty() || existingActivities.contains(activity)) { + activity = QUuid::createUuid(); + activity.replace(QRegExp("[{}]"), QString()); + } + + // Saves the activity info to the config + + d->activities[activity] = Invalid; + d->setActivityState(activity, Running); + + SetActivityName(activity, name); + + emit ActivityAdded(activity); + + d->scheduleConfigSync(true); + return activity; +} + +void Activities::RemoveActivity(const QString & activity) +{ + if (!KAuthorized::authorize("plasma-desktop/add_activities")) { + return; + } + + // Sanity checks + if (!d->activities.contains(activity)) { + return; + } + + d->removeActivity(activity); +} + +void Activities::Private::removeActivity(const QString & activity) +{ + qDebug() << activities << activity; + Q_ASSERT(!activity.isEmpty()); + Q_ASSERT(activities.contains(activity)); + + // If the activity is running, stash it + q->StopActivity(activity); + + setActivityState(activity, Activities::Invalid); + + // Removing the activity + activities.remove(activity); + activitiesConfig().deleteEntry(activity); + + // If the removed activity was the current one, + // set another activity as current + if (currentActivity == activity) { + ensureCurrentActivityIsRunning(); + } + + emit q->ActivityRemoved(activity); + configSync(); +} + + +KConfigGroup Activities::Private::activityIconsConfig() +{ + return KConfigGroup(&config, "activities-icons"); +} + +KConfigGroup Activities::Private::activitiesConfig() +{ + return KConfigGroup(&config, "activities"); +} + +KConfigGroup Activities::Private::mainConfig() +{ + return KConfigGroup(&config, "main"); +} + +QString Activities::Private::activityName(const QString & activity) +{ + return activitiesConfig().readEntry(activity, QString()); +} + +QString Activities::Private::activityIcon(const QString & activity) +{ + return activityIconsConfig().readEntry(activity, QString()); +} + +void Activities::Private::scheduleConfigSync(const bool soon) +{ + static const auto shortInterval = 1000; + static const auto longInterval = 2 * 60 * 1000; + + // If the timer is not running, or has a longer interval than we need, + // start it + if ((soon && configSyncTimer.interval() > shortInterval) + || !configSyncTimer.isActive()) { + + QMetaObject::invokeMethod( + &configSyncTimer, "start", Qt::QueuedConnection, + Q_ARG(int, soon ? shortInterval : longInterval)); + } +} + +void Activities::Private::configSync() +{ + QMetaObject::invokeMethod(&configSyncTimer, "stop", Qt::QueuedConnection); + config.sync(); +} + +QStringList Activities::ListActivities() const +{ + qDebug() << "This is the current thread id for Activities" << QThread::currentThreadId() << QThread::currentThread(); + return d->activities.keys(); +} + +QStringList Activities::ListActivities(int state) const +{ + return d->activities.keys((State)state); +} + +QList Activities::ListActivitiesWithInformation() const +{ + QList result; + + foreach (const QString & activity, ListActivities()) { + result << ActivityInformation(activity); + } + + return result; +} + +ActivityInfo Activities::ActivityInformation(const QString & activity) const +{ + if (!d->activities.contains(activity)) return ActivityInfo(); + + ActivityInfo activityInfo; + activityInfo.id = activity; + activityInfo.name = ActivityName(activity); + activityInfo.icon = ActivityIcon(activity); + activityInfo.state = ActivityState(activity); + return activityInfo; +} + +QString Activities::ActivityName(const QString & activity) const +{ + if (!d->activities.contains(activity)) return QString(); + + return d->activityName(activity); +} + +void Activities::SetActivityName(const QString & activity, const QString & name) +{ + if (!d->activities.contains(activity)) return; + + if (name == d->activityName(activity)) return; + + d->activitiesConfig().writeEntry(activity, name); + + d->scheduleConfigSync(true); + + emit ActivityNameChanged(activity, name); + emit ActivityChanged(activity); +} + +QString Activities::ActivityIcon(const QString & activity) const +{ + if (!d->activities.contains(activity)) return QString(); + + return d->activityIcon(activity); +} + +void Activities::SetActivityIcon(const QString & activity, const QString & icon) +{ + if (!d->activities.contains(activity)) return; + + d->activityIconsConfig().writeEntry(activity, icon); + + d->scheduleConfigSync(); + + emit ActivityIconChanged(activity, icon); + emit ActivityChanged(activity); +} + +void Activities::Private::setActivityState(const QString & activity, Activities::State state) +{ + qDebug() << activities << activity; + Q_ASSERT(activities.contains(activity)); + + if (activities.value(activity) == state) return; + + // Treating 'Starting' as 'Running', and 'Stopping' as 'Stopped' + // as far as the config file is concerned + bool configNeedsUpdating = ((activities[activity] & 4) != (state & 4)); + + activities[activity] = state; + + switch (state) { + case Activities::Running: + emit q->ActivityStarted(activity); + break; + + case Activities::Stopped: + emit q->ActivityStopped(activity); + break; + + default: + break; + } + + emit q->ActivityStateChanged(activity, state); + + if (configNeedsUpdating) { + mainConfig().writeEntry("runningActivities", + activities.keys(Activities::Running) + + activities.keys(Activities::Starting)); + scheduleConfigSync(); + } +} + +void Activities::Private::ensureCurrentActivityIsRunning() +{ + // If the current activity is not running, + // make some other activity current + + val & runningActivities = q->ListActivities(Activities::Running); + + if (!runningActivities.contains(currentActivity)) { + if (runningActivities.size() > 0) { + qDebug() << "Somebody called ensureCurrentActivityIsRunning?"; + setCurrentActivity(runningActivities.first()); + } + } +} + +// Main + +void Activities::StartActivity(const QString & activity) +{ + if (!d->activities.contains(activity) || + d->activities[activity] != Stopped) { + return; + } + + qDebug() << "Starting the session"; + d->setActivityState(activity, Starting); + d->ksmserver->startActivitySession(activity); +} + +void Activities::StopActivity(const QString & activity) +{ + if (!d->activities.contains(activity) || + d->activities[activity] == Stopped) { + return; + } + + qDebug() << "Stopping the session"; + d->setActivityState(activity, Stopping); + d->ksmserver->stopActivitySession(activity); +} + +void Activities::Private::activitySessionStateChanged(const QString & activity, int status) +{ + if (!activities.contains(activity)) return; + + switch (status) { + case KSMServer::Started: + case KSMServer::FailedToStop: + setActivityState(activity, Activities::Running); + break; + + case KSMServer::Stopped: + setActivityState(activity, Activities::Stopped); + + if (currentActivity == activity) { + ensureCurrentActivityIsRunning(); + } + + break; + } + + configSync(); +} + +int Activities::ActivityState(const QString & activity) const +{ + return d->activities.contains(activity) ? d->activities[activity] : Invalid; +} + +bool Activities::isFeatureOperational(const QStringList & feature) const +{ + Q_UNUSED(feature) + + return false; +} + +bool Activities::isFeatureEnabled(const QStringList & feature) const +{ + Q_UNUSED(feature) + + return false; +} + +void Activities::setFeatureEnabled(const QStringList & feature, bool value) +{ + Q_UNUSED(feature) + Q_UNUSED(value) +} + +QStringList Activities::listFeatures(const QStringList & feature) const +{ + Q_UNUSED(feature) + + return QStringList(); +} + diff --git a/kactivities/src/service/Activities.h b/kactivities/src/service/Activities.h new file mode 100644 index 00000000..bca89283 --- /dev/null +++ b/kactivities/src/service/Activities.h @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2010, 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef ACTIVITIES_H +#define ACTIVITIES_H + +#include +#include + +#include + +#include +#include + +#include + +/** + * Service for tracking the user actions and managing the + * activities + */ +class Activities: public Module { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.ActivityManager.Activities") + Q_PROPERTY(QString CurrentActivity READ CurrentActivity WRITE SetCurrentActivity NOTIFY CurrentActivityChanged) + +public: + /** + * Activity state + * @note: Do not change the values, needed for bit-operations + */ + enum State { + Invalid = 0, + Running = 2, + Starting = 3, + Stopped = 4, + Stopping = 5 + }; + + /** + * The event type + */ + enum EventType { + Accessed = 1, + Opened = 2, + Modified = 3, + Closed = 4, + FocussedIn = 5, + FocussedOut = 6 + }; + + /** + * Creates new Activities object + */ + Activities(QObject * parent = nullptr); + + /** + * Destroys this interface + */ + virtual ~Activities(); + +// workspace activities control +public Q_SLOTS: + /** + * @returns the id of the current activity, empty string if none + */ + QString CurrentActivity() const; + + /** + * Sets the current activity + * @param activity id of the activity to make current + */ + bool SetCurrentActivity(const QString & activity); + + /** + * Adds a new activity + * @param name name of the activity + * @returns id of the newly created activity + */ + QString AddActivity(const QString & name); + + /** + * Starts the specified activity + * @param activity id of the activity to stash + */ + void StartActivity(const QString & activity); + + /** + * Stops the specified activity + * @param activity id of the activity to stash + */ + void StopActivity(const QString & activity); + + /** + * @returns the state of the activity + * @param activity id of the activity + */ + int ActivityState(const QString & activity) const; + + /** + * Removes the specified activity + * @param activity id of the activity to delete + */ + void RemoveActivity(const QString & activity); + + /** + * @returns the list of all existing activities + */ + QStringList ListActivities() const; + + /** + * @returns the list of activities with the specified state + * @param state state + */ + QStringList ListActivities(int state) const; + + /** + * @returns the name of the specified activity + * @param activity id of the activity + */ + QString ActivityName(const QString & activity) const; + + /** + * Sets the name of the specified activity + * @param activity id of the activity + * @param name name to be set + */ + void SetActivityName(const QString & activity, const QString & name); + + /** + * @returns the icon of the specified activity + * @param activity id of the activity + */ + QString ActivityIcon(const QString & activity) const; + + /** + * Sets the icon of the specified activity + * @param activity id of the activity + * @param icon icon to be set + */ + void SetActivityIcon(const QString & activity, const QString & icon); + +public Q_SLOTS: + /** + * @returns a list of activities with basic info about them + */ + ActivityInfoList ListActivitiesWithInformation() const; + + /** + * @returns the info about an activity + */ + ActivityInfo ActivityInformation(const QString & activity) const; + +Q_SIGNALS: + /** + * This signal is emitted when the global + * activity is changed + * @param activity id of the new current activity + */ + void CurrentActivityChanged(const QString & activity); + + /** + * This signal is emitted when a new activity is created + * @param activity id of the activity + */ + void ActivityAdded(const QString & activity); + + /** + * This signal is emitted when an activity is started + * @param activity id of the activity + */ + void ActivityStarted(const QString & activity); + + /** + * This signal is emitted when an activity is stashed + * @param activity id of the activity + */ + void ActivityStopped(const QString & activity); + + /** + * This signal is emitted when an activity is deleted + * @param activity id of the activity + */ + void ActivityRemoved(const QString & activity); + + /** + * Emitted when an activity name is changed + * @param activity id of the changed activity + * @param name name of the changed activity + */ + void ActivityNameChanged(const QString & activity, const QString & name); + + /** + * Emitted when an activity icon is changed + * @param activity id of the changed activity + * @param icon name of the changed activity + */ + void ActivityIconChanged(const QString & activity, const QString & icon); + + /** + * Emitted when an activity is changed (name, icon, or some other property) + * @param activity id of the changed activity + */ + void ActivityChanged(const QString & activity); + + /** + * Emitted when the state of activity is changed + */ + void ActivityStateChanged(const QString & activity, int state); + + +public: + virtual bool isFeatureOperational(const QStringList & feature) const _override; + virtual bool isFeatureEnabled(const QStringList & feature) const _override; + virtual void setFeatureEnabled(const QStringList & feature, bool value) _override; + virtual QStringList listFeatures(const QStringList & feature) const _override; + +private: + D_PTR; +}; + + +#endif // ACTIVITIES_H diff --git a/kactivities/src/service/Activities_p.h b/kactivities/src/service/Activities_p.h new file mode 100644 index 00000000..5f3304d6 --- /dev/null +++ b/kactivities/src/service/Activities_p.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2010, 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef ACTIVITIES_P_H +#define ACTIVITIES_P_H + +#include +#include + +#include +#include + +#include "Activities.h" + +class KSMServer; + +class QDBusInterface; +class KJob; + +class Activities::Private: public QObject { + Q_OBJECT + +public: + Private(Activities * parent); + ~Private(); + + // Loads the last activity + // the user has used + void loadLastActivity(); + + // If the current activity is not running, + // make some other activity current + void ensureCurrentActivityIsRunning(); + +public Q_SLOTS: + bool setCurrentActivity(const QString & activity); + +public: + void setActivityState(const QString & activity, Activities::State state); + QHash < QString, Activities::State > activities; + + // Current activity + QString currentActivity; + + // Configuration + QTimer configSyncTimer; + KConfig config; + + // Interface to the session management + KSMServer * ksmserver; + +public: + KConfigGroup activitiesConfig(); + KConfigGroup activityIconsConfig(); + KConfigGroup mainConfig(); + QString activityName(const QString & activity); + QString activityIcon(const QString & activity); + + +public Q_SLOTS: + // Schedules config syncing to be done after + // a predefined time interval + // if soon == true, the syncing is performed + // after a few seconds, otherwise a few minutes + void scheduleConfigSync(const bool soon = false); + + // Immediately syncs the configuration file + void configSync(); + + void removeActivity(const QString & activity); + void activitySessionStateChanged(const QString & activity, int state); + + void emitCurrentActivityChanged(const QString & activity); + +private: + Activities * const q; +}; + +#endif // ACTIVITIES_P_H + diff --git a/kactivities/src/service/Application.cpp b/kactivities/src/service/Application.cpp new file mode 100644 index 00000000..ac4978b4 --- /dev/null +++ b/kactivities/src/service/Application.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2010, 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace { + QList < QThread * > s_moduleThreads; +} + +// Runs a QObject inside a QThread + +template +T * runInQThread() +{ + T * object = new T(); + + class Thread: public QThread { + public: + Thread(T * ptr = nullptr) + : QThread(), object(ptr) + { + } + + void run() _override + { + std::unique_ptr o(object); + exec(); + } + + private: + T * object; + + } * thread = new Thread(object); + + s_moduleThreads << thread; + + object->moveToThread(thread); + thread->start(); + + return object; +} + +class Application::Private { +public: + Private() + : resources (runInQThread ()), + activities (runInQThread ()), + features (runInQThread ()) + { + } + + Resources * resources; + Activities * activities; + Features * features; + + QList < Plugin * > plugins; + + static Application * s_instance; + +}; + +Application * Application::Private::s_instance = nullptr; + +Application::Application() + : KUniqueApplication(), d() +{ + // TODO: We should move away from any GUI code + setQuitOnLastWindowClosed(false); + + if (!KDBusConnectionPool::threadConnection().registerService("org.kde.ActivityManager")) { + exit(0); + } + + // KAMD is a daemon, if it crashes it is not a problem as + // long as it restarts properly + // NOTE: We have a custom crash handler + KCrash::setFlags(KCrash::AutoRestart); + + QMetaObject::invokeMethod(this, "loadPlugins", Qt::QueuedConnection); +} + +void Application::loadPlugins() +{ + val offers = KServiceTypeTrader::self()->query("ActivityManager/Plugin"); + + val config = KSharedConfig::openConfig("activitymanagerrc"); + auto disabledPlugins = config->group("Global").readEntry("disabledPlugins", QStringList()); + + val pluginsGroup = config->group("Plugins"); + foreach (const QString & plugin, pluginsGroup.keyList()) { + if (!pluginsGroup.readEntry(plugin, true)) + disabledPlugins << plugin; + } + + // Adding overridden plugins into the list of disabled ones + + foreach (val & service, offers) { + if (!disabledPlugins.contains(service->library())) { + disabledPlugins.append( + service->property("X-ActivityManager-PluginOverrides", QVariant::StringList).toStringList() + ); + } + } + + qDebug() << "These are the disabled plugins:" << disabledPlugins; + + // Loading plugins and initializing them + foreach (val & service, offers) { + if (disabledPlugins.contains(service->library()) || + disabledPlugins.contains(service->property("X-KDE-PluginInfo-Name").toString() + "Enabled")) { + continue; + } + + val factory = KPluginLoader(service->library()).factory(); + + if (!factory) { + continue; + } + + val plugin = factory->create < Plugin > (this); + + if (plugin) { + qDebug() << "Got the plugin: " << service->library(); + d->plugins << plugin; + } + } + + foreach (Plugin * plugin, d->plugins) { + plugin->init(Module::get()); + } +} + +Application::~Application() +{ + foreach (val plugin, d->plugins) { + delete plugin; + } + + foreach (val thread, s_moduleThreads) { + thread->quit(); + thread->wait(); + + delete thread; + } + + Private::s_instance = nullptr; +} + +int Application::newInstance() +{ + //We don't want to show the mainWindow() + return 0; +} + +Activities & Application::activities() const +{ + return *d->activities; +} + +Resources & Application::resources() const +{ + return *d->resources; +} + +Application * Application::self() +{ + if (!Private::s_instance) { + Private::s_instance = new Application(); + } + + return Private::s_instance; +} + +void Application::quit() +{ + if (Private::s_instance) { + Private::s_instance->exit(); + delete Private::s_instance; + } +} + + +// Leaving object oriented world :) + +int main(int argc, char ** argv) +{ + KAboutData about("kactivitymanagerd", nullptr, ki18n("KDE Activity Manager"), "3.0", + ki18n("KDE Activity Management Service"), + KAboutData::License_GPL, + ki18n("(c) 2010, 2011, 2012 Ivan Cukic"), KLocalizedString(), + "http://www.kde.org/"); + + KCmdLineArgs::init(argc, argv, &about); + + return Application::self()->exec(); +} + diff --git a/kactivities/src/service/Application.h b/kactivities/src/service/Application.h new file mode 100644 index 00000000..2e84b27f --- /dev/null +++ b/kactivities/src/service/Application.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef APPLICATION_H +#define APPLICATION_H + +#include +#include + +class Resources; +class Activities; +class Features; + +/** + * Main application object + */ +class Application: public KUniqueApplication { + Q_OBJECT + +public: + Application(); + virtual ~Application(); + + virtual int newInstance(); + + static Application * self(); + static void quit(); + + Resources & resources() const; + Activities & activities() const; + Features & features() const; + +private Q_SLOTS: + void loadPlugins(); + +private: + D_PTR; +}; + +#endif // APPLICATION_H diff --git a/kactivities/src/service/CMakeLists.txt b/kactivities/src/service/CMakeLists.txt new file mode 100644 index 00000000..5c93e5d7 --- /dev/null +++ b/kactivities/src/service/CMakeLists.txt @@ -0,0 +1,113 @@ +project (ActivityManager) + +# C++11 + +string (COMPARE EQUAL "${CMAKE_CXX_COMPILER_ID}" "Clang" CMAKE_COMPILER_IS_CLANG) + +if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG) + message (STATUS "We have GNU or Clang, adding -std=c++0x flag") + add_definitions ("-std=c++0x") + set (ADDITIONAL_DEFINITIONS "-std=c++0x") +endif () + +# General + +find_package(KDeclarative) + +set (ADDITIONAL_LINK_LIBS) + +set (sdo_SRCS) + +# Standard stuff + +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${KDE4_INCLUDES} + ) + +set (plugin_implementation_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/Plugin.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Module.cpp + ) + +add_subdirectory (plugins) + +set (activity_manager_SRCS + Application.cpp + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.cpp + + Activities.cpp + Resources.cpp + Features.cpp + + ${plugin_implementation_SRCS} + + Event.cpp + + jobs/Job.cpp + jobs/JobFactory.cpp + + jobs/schedulers/Abstract.cpp + jobs/schedulers/Ordered.cpp + jobs/schedulers/Fallible.cpp + jobs/schedulers/Given.cpp + jobs/schedulers/Retry.cpp + jobs/schedulers/Switch.cpp + jobs/schedulers/Test.cpp + + jobs/general/Call.cpp + + jobs/ksmserver/KSMServer.cpp + + ${sdo_SRCS} + ) + +qt4_add_dbus_adaptor ( + activity_manager_SRCS + ../common/dbus/org.kde.ActivityManager.Activities.xml + Activities.h Activities + ) + +qt4_add_dbus_adaptor ( + activity_manager_SRCS + ../common/dbus/org.kde.ActivityManager.Resources.xml + Resources.h Resources + ) + +qt4_add_dbus_adaptor ( + activity_manager_SRCS + ../common/dbus/org.kde.ActivityManager.Features.xml + Features.h Features + ) + +kde4_add_executable (activity-manager ${activity_manager_SRCS}) + +target_link_libraries (activity-manager + ${KDE4_KDECORE_LIBS} + ${KDE4_KDEUI_LIBS} # KUniqueApplication + ${ADDITIONAL_LINK_LIBS} + ) + +set_target_properties (activity-manager + PROPERTIES OUTPUT_NAME kactivitymanagerd + ) + +########### install application ############### + +install (FILES + files/kactivitymanagerd.desktop + DESTINATION ${SERVICES_INSTALL_DIR} + ) + +install (TARGETS + activity-manager ${INSTALL_TARGETS_DEFAULT_ARGS} + ) + +install (FILES + files/activitymanager-plugin.desktop + DESTINATION ${SERVICETYPES_INSTALL_DIR} + ) + diff --git a/kactivities/src/service/Event.cpp b/kactivities/src/service/Event.cpp new file mode 100644 index 00000000..add5b9fa --- /dev/null +++ b/kactivities/src/service/Event.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Event.h" + +#include +#include + + +Event::Event() + : wid(0), type(Accessed), reason(User), timestamp(QDateTime::currentDateTime()) +{ +} + +Event::Event(const QString & vApplication, WId vWid, const QString & vUri, int vType, int vReason) + : application(vApplication), wid(vWid), uri(vUri), type(vType), reason(vReason), timestamp(QDateTime::currentDateTime()) +{ + Q_ASSERT(!vApplication.isEmpty()); + Q_ASSERT(!vUri.isEmpty()); +} + +Event Event::deriveWithType(Type type) const +{ + Event result(*this); + result.type = type; + return result; +} + +bool Event::operator == (const Event & other) const +{ + return + application == other.application && + wid == other.wid && + uri == other.uri && + type == other.type && + reason == other.reason && + timestamp == other.timestamp; +} + +QString Event::typeName() const +{ + switch (type) { + case Accessed: return "Accessed"; + case Opened: return "Opened"; + case Modified: return "Modified"; + case Closed: return "Closed"; + case FocussedIn: return "FocussedIn"; + case FocussedOut: return "FocussedOut"; + default: return "Other"; + } + +} + +QDebug operator << (QDebug dbg, const Event & e) +{ +#ifndef QT_NO_DEBUG_OUTPUT + dbg << "Event(" << e.application << e.wid << e.typeName() << e.uri << ":" << e.timestamp << ")"; +#else + Q_UNUSED(e) +#endif + return dbg.space(); +} + diff --git a/kactivities/src/service/Event.h b/kactivities/src/service/Event.h new file mode 100644 index 00000000..d4a33ee5 --- /dev/null +++ b/kactivities/src/service/Event.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef EVENT_H +#define EVENT_H + +#include +#include +#include +#include + +/** + * + */ +class Event { +public: + + enum Type { + Accessed = 0, ///< resource was accessed, but we don't know for how long it will be open/used + + Opened = 1, ///< resource was opened + Modified = 2, ///< previously opened resource was modified + Closed = 3, ///< previously opened resource was closed + + FocussedIn = 4, ///< resource get the keyboard focus + FocussedOut = 5, ///< resource lost the focus + + LastEventType = 5, + UserEventType = 32 + + }; + + // These events can't come outside of the activity manager daemon, + // they are intended to provide some additional functionality + // to the daemon plugins + enum UserType { + UpdateScore = UserEventType + 1 + + }; + + // TODO: Remove + // Was introduced for better cooperation with Zeitgeist + // We don't use it + enum Reason { + User = 0, + Scheduled = 1, + Heuristic = 2, + System = 3, + World = 4, + + LastEventReason = 4, + UserEventReason = 32 + }; + + Event(); + + explicit Event(const QString & application, WId wid, const QString & uri, + int type = Accessed, int reason = User); + + Event deriveWithType(Type type) const; + + bool operator == (const Event & other) const; + +public: + QString application; + WId wid; + QString uri; + int type; + int reason; + QDateTime timestamp; + + QString typeName() const; +}; + +QDebug operator << (QDebug dbg, const Event & e); + +typedef QList EventList; + +Q_DECLARE_METATYPE(Event) +Q_DECLARE_METATYPE(EventList) + +#endif // EVENT_H + diff --git a/kactivities/src/service/Features.cpp b/kactivities/src/service/Features.cpp new file mode 100644 index 00000000..5ec79937 --- /dev/null +++ b/kactivities/src/service/Features.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include "featuresadaptor.h" + +#include "common.h" +#include + +#include +#include + +class Features::Private { + +}; + +Features::Features(QObject * parent) + : Module("features", parent), d() +{ + new FeaturesAdaptor(this); + KDBusConnectionPool::threadConnection().registerObject( + ACTIVITY_MANAGER_OBJECT_PATH(Features), this); +} + +Features::~Features() +{ +} + +// Features object is just a gateway to the other KAMD modules. +// This is a convenience method to pass the request down to the module + +template +static RetType passToModule(const QString & feature, RetType defaultResult, Function f) +{ + val params = feature.split('/'); + val module = Module::get(params.first()); + + if (!module) return defaultResult; + + return f(static_cast(module), params.mid(1)); +} + +#define FEATURES_PASS_TO_MODULE(RetType, DefaultResult, What) \ + passToModule(feature, DefaultResult, \ + [=] (Module * module, const QStringList & params) -> RetType { \ + What \ + }); + +bool Features::IsFeatureOperational(const QString & feature) const +{ + if (feature.isEmpty()) return false; + + return FEATURES_PASS_TO_MODULE(bool, false, + return module->isFeatureOperational(params); + ); +} + +bool Features::IsFeatureEnabled(const QString & feature) const +{ + if (feature.isEmpty()) return false; + + return FEATURES_PASS_TO_MODULE(bool, false, + return module->isFeatureEnabled(params); + ); +} + +void Features::SetFeatureEnabled(const QString & feature, bool value) +{ + if (feature.isEmpty()) return; + + FEATURES_PASS_TO_MODULE(bool, false, + module->setFeatureEnabled(params, value); + return true; + ); +} + +QStringList Features::ListFeatures(const QString & feature) const +{ + if (feature.isEmpty()) { + return Module::get().keys(); + } + + return FEATURES_PASS_TO_MODULE(QStringList, QStringList(), + return module->listFeatures(params); + ); +} + +#undef FEATURES_PASS_TO_MODULE + diff --git a/kactivities/src/service/Features.h b/kactivities/src/service/Features.h new file mode 100644 index 00000000..64a58618 --- /dev/null +++ b/kactivities/src/service/Features.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2010, 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FEATURES_H +#define FEATURES_H + +#include +#include + +#include + +#include +#include + +/** + * Features object provides one interface for clients + * to access other objects' features + */ +class Features: public Module { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.ActivityManager.Features") + +public: + Features(QObject * parent = nullptr); + virtual ~Features(); + +public Q_SLOTS: + bool IsFeatureOperational(const QString & feature) const; + + bool IsFeatureEnabled(const QString & feature) const; + + void SetFeatureEnabled(const QString & feature, bool value); + + QStringList ListFeatures(const QString & module) const; + +private: + D_PTR; +}; + +#endif // FEATURES_H diff --git a/kactivities/src/service/Messages.sh b/kactivities/src/service/Messages.sh new file mode 100644 index 00000000..9f50300c --- /dev/null +++ b/kactivities/src/service/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp -o $podir/kactivitymanagerd.pot diff --git a/kactivities/src/service/Module.cpp b/kactivities/src/service/Module.cpp new file mode 100644 index 00000000..107c90dc --- /dev/null +++ b/kactivities/src/service/Module.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Module.h" + +#include +#include +#include +#include + +#include + +class Module::Private { +public: + static QHash < QString, QObject * > s_modules; + +}; + +QHash < QString, QObject * > Module::Private::s_modules; + +Module::Module(const QString & name, QObject * parent) + : QObject(parent), d() +{ + registerModule(name, this); +} + +void Module::registerModule(const QString & name, QObject * module) { + if (!name.isEmpty()) { + Private::s_modules[name] = module; + qDebug() << "Module " << name << "is registered"; + } +} + +Module::~Module() +{ +} + +QObject * Module::get(const QString & name) +{ + Q_ASSERT(!name.isEmpty()); + + if (Private::s_modules.contains(name)) { + qDebug() << "Returning a valid module object for:" << name; + return Private::s_modules[name]; + } + + qDebug() << "The requested module doesn't exist:" << name; + return nullptr; +} + +const QHash < QString, QObject * > Module::get() +{ + return Private::s_modules; +} + +bool Module::isFeatureEnabled(const QStringList & feature) const +{ + Q_UNUSED(feature) + return false; +} + +bool Module::isFeatureOperational(const QStringList & feature) const +{ + Q_UNUSED(feature) + return false; +} + +void Module::setFeatureEnabled(const QStringList & feature, bool value) +{ + Q_UNUSED(feature) + Q_UNUSED(value) +} + +QStringList Module::listFeatures(const QStringList & feature) const +{ + Q_UNUSED(feature) + return QStringList(); +} + diff --git a/kactivities/src/service/Module.h b/kactivities/src/service/Module.h new file mode 100644 index 00000000..bdd5b105 --- /dev/null +++ b/kactivities/src/service/Module.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MODULE_H +#define MODULE_H + +#include +#include +#include + +#include +#include +#include + +/** + * Module + */ +class Module: public QObject { + Q_OBJECT + +public: + explicit Module(const QString & name, QObject * parent = nullptr); + virtual ~Module(); + + static QObject * get(const QString & name); + static const QHash < QString, QObject * > get(); + + virtual bool isFeatureOperational(const QStringList & feature) const; + virtual bool isFeatureEnabled(const QStringList & feature) const; + virtual void setFeatureEnabled(const QStringList & feature, bool value); + virtual QStringList listFeatures(const QStringList & feature) const; + +protected: + static void registerModule(const QString & name, QObject * module); + +private: + D_PTR; +}; + +#endif // MODULE_H + diff --git a/kactivities/src/service/Plugin.cpp b/kactivities/src/service/Plugin.cpp new file mode 100644 index 00000000..de904f2d --- /dev/null +++ b/kactivities/src/service/Plugin.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Plugin.h" +#include + +#include +#include + +class Plugin::Private { +public: + Private() + : config(nullptr) + { + } + + QString name; + KSharedConfig::Ptr config; +}; + +Plugin::Plugin(QObject * parent) + : Module(QString(), parent), d() +{ +} + +Plugin::~Plugin() +{ +} + +bool Plugin::init(const QHash < QString, QObject * > & modules) +{ + Q_UNUSED(modules) + return true; +} + +KConfigGroup Plugin::config() +{ + if (d->name.isEmpty()) { + qWarning() << "The plugin needs a name in order to have a config section"; + return KConfigGroup(); + } + + if (!d->config) { + d->config = KSharedConfig::openConfig("activitymanager-pluginsrc"); + } + + return d->config->group("Plugin-" + d->name); +} + +void Plugin::setName(const QString & name) +{ + Q_ASSERT_X(d->name.isEmpty(), "Plugin::setName", "The name can not be set twice"); + Q_ASSERT_X(!name.isEmpty(), "Plugin::setName", "The name can not be empty"); + + d->name = name; + registerModule(name, this); +} + +QString Plugin::name() const +{ + return d->name; +} + diff --git a/kactivities/src/service/Plugin.h b/kactivities/src/service/Plugin.h new file mode 100644 index 00000000..edbadab4 --- /dev/null +++ b/kactivities/src/service/Plugin.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PLUGIN_H +#define PLUGIN_H + +#include + +#include +#include + +#include +#include + +#include "Event.h" +#include "Module.h" + +#include + +#define KAMD_EXPORT_PLUGIN(ClassName, AboutData) \ + K_PLUGIN_FACTORY(ClassName##Factory, registerPlugin();) \ + K_EXPORT_PLUGIN(ClassName##Factory(AboutData)) + + +/** + * + */ +class KDE_EXPORT Plugin: public Module { + Q_OBJECT + +public: + Plugin(QObject * parent); + virtual ~Plugin(); + + /** + * Initializes the plugin. + * @arg modules Activities, Resources and Features manager objects + * @returns the plugin needs to return whether it has + * successfully been initialized + */ + virtual bool init(const QHash < QString, QObject * > & modules); + + /** + * Returns the config group for the plugin. + * In order to use it, you need to set the plugin name. + */ + KConfigGroup config(); + QString name() const; + + /** + * Convenience meta-method to provide prettier invocation of QMetaObject::invokeMethod + */ + template + static ReturnType callOn(QObject * object, const char * method, const char * returnTypeName) + { + ReturnType result; + + QMetaObject::invokeMethod( + object, method, connection, + QReturnArgument < ReturnType > (returnTypeName, result) + ); + + return result; + } + + template + static ReturnType callOnWithArgs(QObject * object, const char * method, const char * returnTypeName, Args ... args) + { + ReturnType result; + + QMetaObject::invokeMethod( + object, method, connection, + QReturnArgument < ReturnType > (returnTypeName, result), + args... + ); + + return result; + } + +protected: + void setName(const QString & name); + +private: + D_PTR; + +}; + +#endif // PLUGIN_H + diff --git a/kactivities/src/service/Resources.cpp b/kactivities/src/service/Resources.cpp new file mode 100644 index 00000000..e4cc1133 --- /dev/null +++ b/kactivities/src/service/Resources.cpp @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2010, 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Resources.h" +#include "Resources_p.h" +#include "resourcesadaptor.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include "common.h" + +#include +#include + +Resources::Private::Private(Resources * parent) + : QThread(parent), focussedWindow(0), q(parent) +{ +} + +namespace { + EventList events; + QMutex events_mutex; +} + +void Resources::Private::run() +{ + forever { + // initial delay before processing the events + sleep(5); + + EventList currentEvents; + + { + QMutexLocker locker(& events_mutex); + + if (events.count() == 0) { + // qDebug() << "No more events to process, exiting."; + return; + } + + currentEvents = events; + events.clear(); + } + + emit q->ProcessedResourceEvents(currentEvents); + } +} + +void Resources::Private::insertEvent(const Event & newEvent) +{ + if (lastEvent == newEvent) return; + lastEvent = newEvent; + + { + QMutexLocker locker(& events_mutex); + events << newEvent; + } + + emit q->RegisteredResourceEvent(newEvent); +} + +void Resources::Private::addEvent(const QString & application, WId wid, const QString & uri, + int type, int reason) +{ + Event newEvent(application, wid, uri, type, reason); + addEvent(newEvent); +} + +void Resources::Private::addEvent(const Event & newEvent) +{ + // And now, for something completely delayed + { + QMutexLocker locker(& events_mutex); + + // Deleting previously registered Accessed events if + // the current one has the same application and uri + if (newEvent.type != Event::Accessed) { + kamd::utils::remove_if(events, [&newEvent] (const Event & event) -> bool { + return + event.reason == Event::Accessed && + event.application == newEvent.application && + event.uri == newEvent.uri + ; + }); + } + } + + // Process the windowing + // Essentially, this is the brain of SLC. We need to track the + // window focus changes to be able to generate the potential + // missing events like FocussedOut before Closed and similar. + // So, there is no point in having the same logic in SLC plugin + // as well. + + if (newEvent.wid != 0) { + WindowData & data = windows[newEvent.wid]; + const KUrl & kuri(newEvent.uri); + + qDebug() << kuri << data.focussedResource; + + data.application = newEvent.application; + + switch (newEvent.type) { + case Event::Opened: + insertEvent(newEvent); + + if (data.focussedResource.isEmpty()) { + // This window haven't had anything focused, + // assuming the new document is focused + + data.focussedResource = newEvent.uri; + insertEvent(newEvent.deriveWithType(Event::FocussedIn)); + } + + break; + + case Event::FocussedIn: + + if (!data.resources.contains(kuri)) { + // This window did not contain this resource before, + // sending Opened event + + insertEvent(newEvent.deriveWithType(Event::Opened)); + } + + data.focussedResource = newEvent.uri; + insertEvent(newEvent); + + break; + + case Event::Closed: + + qDebug() << data.focussedResource << kuri; + + if (data.focussedResource == kuri) { + // If we are closing a document that is in focus, + // release focus first + + insertEvent(newEvent.deriveWithType(Event::FocussedOut)); + data.focussedResource.clear(); + } + + insertEvent(newEvent); + + break; + + case Event::FocussedOut: + + if (data.focussedResource == kuri) { + data.focussedResource.clear(); + } + + insertEvent(newEvent); + + break; + + default: + insertEvent(newEvent); + break; + + } + } + + start(); +} + +void Resources::Private::windowClosed(WId windowId) +{ + // Testing whether the window is a registered one + + if (!windows.contains(windowId)) { + return; + } + + if (focussedWindow == windowId) { + focussedWindow = 0; + } + + // Closing all the resources that the window registered + + foreach (const KUrl & uri, windows[windowId].resources) { + q->RegisterResourceEvent(windows[windowId].application, + toInt(windowId), uri.url(), Event::Closed, 0); + } + + windows.remove(windowId); +} + +void Resources::Private::activeWindowChanged(WId windowId) +{ + // If the focused window has changed, we need to create a + // FocussedOut event for the resource it contains, + // and FocussedIn for the resource of the new active window. + // The windows can do this manually, but if they are + // SDI, we can do it on our own. + + if (windowId == focussedWindow) return; + + if (windows.contains(focussedWindow)) { + const WindowData & data = windows[focussedWindow]; + + if (!data.focussedResource.isEmpty()) { + insertEvent(Event(data.application, focussedWindow, data.focussedResource.url(), Event::FocussedOut)); + } + } + + focussedWindow = windowId; + + if (windows.contains(focussedWindow)) { + const WindowData & data = windows[focussedWindow]; + + if (!data.focussedResource.isEmpty()) { + insertEvent(Event(data.application, windowId, data.focussedResource.url(), Event::FocussedIn)); + } + } +} + + +Resources::Resources(QObject * parent) + : Module("resources", parent), d(this) +{ + qRegisterMetaType < Event > ("Event"); + qRegisterMetaType < EventList > ("EventList"); + qRegisterMetaType < WId > ("WId"); + + new ResourcesAdaptor(this); + KDBusConnectionPool::threadConnection().registerObject( + ACTIVITY_MANAGER_OBJECT_PATH(Resources), this); + + d->connect(KWindowSystem::self(), SIGNAL(windowRemoved(WId)), + SLOT(windowClosed(WId))); + d->connect(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId)), + SLOT(activeWindowChanged(WId))); + +} + +Resources::~Resources() +{ +} + +void Resources::RegisterResourceEvent(QString application, uint _windowId, + const QString & uri, uint event, uint reason) +{ + Q_ASSERT_X(!uri.startsWith("nepomuk:"), "Resources::RegisterResourceEvent", + "We do not accept nepomuk URIs for resource events"); + + if ( + event > Event::LastEventType + || reason > Event::LastEventReason + || uri.isEmpty() + || application.isEmpty() + // Dirty way to skip special web browser URIs + // This is up to the plugin - whether it wants it filtered out or not + // || uri.startsWith(QLatin1String("about:")) + ) return; + + KUrl kuri(uri); + WId windowId = (WId) _windowId; + + d->addEvent(application, windowId, + kuri.url(), (Event::Type) event, (Event::Reason) reason); +} + + +void Resources::RegisterResourceMimeType(const QString & uri, const QString & mimetype) +{ + if (!mimetype.isEmpty()) return; + + KUrl kuri(uri); + + emit RegisteredResourceMimeType(uri, mimetype); +} + + +void Resources::RegisterResourceTitle(const QString & uri, const QString & title) +{ + // A dirty saninty check for the title + if (title.length() < 3) return; + + KUrl kuri(uri); + + emit RegisteredResourceTitle(uri, title); +} + +bool Resources::isFeatureOperational(const QStringList & feature) const +{ + Q_UNUSED(feature) + return false; +} + +bool Resources::isFeatureEnabled(const QStringList & feature) const +{ + Q_UNUSED(feature) + return false; + +} + +void Resources::setFeatureEnabled(const QStringList & feature, bool value) +{ + Q_UNUSED(feature) + Q_UNUSED(value) +} + +QStringList Resources::listFeatures(const QStringList & feature) const +{ + Q_UNUSED(feature) + static QStringList features; + + return features; +} diff --git a/kactivities/src/service/Resources.h b/kactivities/src/service/Resources.h new file mode 100644 index 00000000..1c931d9c --- /dev/null +++ b/kactivities/src/service/Resources.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef RESOURCES_H +#define RESOURCES_H + +#include +#include + +#include +#include + +#include +#include + +/** + * Resources + */ +class Resources: public Module { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.ActivityManager.Resources") + +public: + Resources(QObject * parent = nullptr); + virtual ~Resources(); + +public Q_SLOTS: + /** + * Registers a new event + * @param application the name of application that sent the event. Ignored if the event is not of type Opened + * @param windowId ID of the window that displays the resource. Ignored if the event is of type Accessed + * @param uri URI of the resource on which the event happened + * @param event type of the event + * @param reason reason for opening the resource + */ + void RegisterResourceEvent(QString application, uint windowId, const QString & uri, uint event, uint reason); + + /** + * Registers resource's mimetype. If not manually specified, it will + * be retrieved if needed from Nepomuk + * + * Note that this will be forgotten when the resource in question is closed. + * @param uri URI of the resource + */ + void RegisterResourceMimeType(const QString & uri, const QString & mimetype); + + /** + * Registers resource's title. If not manually specified, it will be a shortened + * version of the uri + * + * Note that this will be forgotten when the resource in question is closed. + * @param uri URI of the resource + */ + void RegisterResourceTitle(const QString & uri, const QString & title); + +Q_SIGNALS: + void RegisteredResourceEvent(const Event & event); + void ProcessedResourceEvents(const EventList & events); + void RegisteredResourceMimeType(const QString & uri, const QString & mimetype); + void RegisteredResourceTitle(const QString & uri, const QString & title); + +public: + virtual bool isFeatureOperational(const QStringList & feature) const _override; + virtual bool isFeatureEnabled(const QStringList & feature) const _override; + virtual void setFeatureEnabled(const QStringList & feature, bool value) _override; + virtual QStringList listFeatures(const QStringList & feature) const _override; + +private: + D_PTR; +}; + +#endif // RESOURCES_H + diff --git a/kactivities/src/service/Resources_p.h b/kactivities/src/service/Resources_p.h new file mode 100644 index 00000000..f263f855 --- /dev/null +++ b/kactivities/src/service/Resources_p.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2010, 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef RESOURCES_P_H +#define RESOURCES_P_H + +#include "Resources.h" +#include "resourcesadaptor.h" + +#include +#include +#include + +class Resources::Private: public QThread { + Q_OBJECT + +public: + Private(Resources * parent); + + void run(); + + // Inserts the event directly into the queue + void insertEvent(const Event & newEvent); + + // Processes the event and inserts it into the queue + void addEvent(const QString & application, WId wid, const QString & uri, + int type, int reason); + + // Processes the event and inserts it into the queue + void addEvent(const Event & newEvent); + + QList resourcesLinkedToActivity(const QString & activity) const; + +private Q_SLOTS: + // Reacting to window manager signals + void windowClosed(WId windowId); + + void activeWindowChanged(WId windowId); + +private: + struct WindowData { + QSet < KUrl > resources; + KUrl focussedResource; + QString application; + }; + + Event lastEvent; + // EventList events; + // QMutex events_mutex; + + QHash < WId, WindowData > windows; + WId focussedWindow; + + Resources * const q; +}; + +#endif // RESOURCES_P_H diff --git a/kactivities/src/service/common.h b/kactivities/src/service/common.h new file mode 100644 index 00000000..d1bb06ac --- /dev/null +++ b/kactivities/src/service/common.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define ACTIVITY_MANAGER_SERVICE "org.kde.ActivityManager" +#define ACTIVITY_MANAGER_OBJECT_TYPE(A) ACTIVITY_MANAGER_SERVICE #A +#define ACTIVITY_MANAGER_OBJECT_PATH(A) "/ActivityManager/" #A + +#include + +__inline int toInt(WId wid) +{ +#ifdef Q_OS_WIN64 // krazy:skip + return (int)((__int64)wid); +#else + return (int)wid; +#endif +} + diff --git a/kactivities/src/service/files/activitymanager-plugin.desktop b/kactivities/src/service/files/activitymanager-plugin.desktop new file mode 100644 index 00000000..fc744db2 --- /dev/null +++ b/kactivities/src/service/files/activitymanager-plugin.desktop @@ -0,0 +1,58 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=ActivityManager/Plugin + +Comment=Activity manager plugin +Comment[bs]=Priključak za praćenje aktivnosti +Comment[ca]=Connector del gestor d'activitats +Comment[ca@valencia]=Connector del gestor d'activitats +Comment[cs]=Modul Správce aktivit +Comment[da]=Aktivitetshåndtering-plugin +Comment[de]=Modul für Aktivitätenverwaltung +Comment[el]=Πρόσθετο διαχειριστή δραστηριοτήτων +Comment[en_GB]=Activity manager plugin +Comment[es]=Complemento del gestor de actividades +Comment[et]=Tegevuste haldamise plugin +Comment[eu]=Jarduera kudeatzailearen plugina +Comment[fi]=Aktiviteettihallintaliitännäinen +Comment[fr]=Module externe de gestionnaire d'activités +Comment[ga]=Breiseán bainisteora gníomhaíochta +Comment[gl]=Complemento de xestión da actividade +Comment[he]=תוסף מנהל פעילויות +Comment[hu]=Aktivitáskezelő bővítmény +Comment[ia]=Plugin de gerente de activitate +Comment[is]=Virknistjórnunarviðbót +Comment[it]=Estensione di gestione delle attività +Comment[kk]=Белсенділік менеджер плагині +Comment[km]=កម្មវិធី​ជំនួយ​កម្មវិធី​គ្រប់គ្រង​សកម្មភាព +Comment[ko]=활동 관리자 플러그인 +Comment[lt]=Veiklų tvarkyklės priedas +Comment[mr]=कार्यपध्दती व्यवस्थापक प्लगइन +Comment[nb]=Programtillegg for aktivitetsbehandler +Comment[nds]=Aktivitetenpleger-Moduul +Comment[nl]=Plug-in van activiteitenbeheerder +Comment[pa]=ਐਕਟੀਵਿਟੀ ਮੈਨੇਜਰ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka menadżera działań +Comment[pt]='Plugin' de gestão de actividades +Comment[pt_BR]=Plugin do gerenciador de atividades +Comment[ro]=Extensie pentru gestionarul de activități +Comment[ru]=Расширение «Диспетчер комнат» +Comment[se]=Lassemodula aktivitehtagieđahallamii +Comment[sk]=Plugin správcu aktivít +Comment[sl]=Vstavek upravljalnika dejavnosti +Comment[sr]=Прикључак менаџера активности +Comment[sr@ijekavian]=Прикључак менаџера активности +Comment[sr@ijekavianlatin]=Priključak menadžera aktivnosti +Comment[sr@latin]=Priključak menadžera aktivnosti +Comment[sv]=Insticksprogram för aktivitetshantering +Comment[tg]=Плагини мудири фаъолият +Comment[tr]=Etkinlik yöneticisi eklentisi +Comment[ug]=پائالىيەت باشقۇرغۇچ قىستۇرمىسى +Comment[uk]=Додаток керування просторами дій +Comment[x-test]=xxActivity manager pluginxx +Comment[zh_CN]=活动管理器插件 +Comment[zh_TW]=活動管理員外掛程式 + +[PropertyDef::X-ActivityManager-PluginOverrides] +Type=QString + diff --git a/kactivities/src/service/files/kactivitymanagerd.desktop b/kactivities/src/service/files/kactivitymanagerd.desktop new file mode 100644 index 00000000..d7b1062c --- /dev/null +++ b/kactivities/src/service/files/kactivitymanagerd.desktop @@ -0,0 +1,109 @@ +[Desktop Entry] +Type=Service +Icon=preferences-activities +X-KDE-ServiceTypes= +X-DBUS-StartupType=Unique +X-KDE-StartupNotify=false +Exec=kactivitymanagerd + +Name=Activity Manager +Name[bs]=Menadžer aktivnosti +Name[ca]=Gestor d'activitats +Name[ca@valencia]=Gestor d'activitats +Name[cs]=Správce aktivit +Name[da]=Aktivitetshåndtering +Name[de]=Aktivitätenverwaltung +Name[el]=Διαχειριστής δραστηριοτήτων +Name[en_GB]=Activity Manager +Name[es]=Gestor de actividades +Name[et]=Tegevuste haldur +Name[eu]=Jarduera kudeatzailea +Name[fi]=Aktiviteettienhallinta +Name[fr]=Gestionnaire d'activités +Name[ga]=Bainisteoir Gníomhaíochta +Name[gl]=Xestor da actividade +Name[he]=מנהל פעילויות +Name[hu]=Aktivitáskezelő +Name[ia]=Gerente de activitate +Name[is]=Virknistjóri +Name[it]=Gestore delle attività +Name[kk]=Белсенділік менеджері +Name[km]=កម្មវិធី​គ្រប់គ្រង​សកម្មភាព +Name[ko]=활동 관리자 +Name[lt]=Veiklų tvarkyklė +Name[mr]=कार्यपध्दती व्यवस्थापक +Name[nb]=Aktivitetsbehandler +Name[nds]=Aktivitetenpleger +Name[nl]=Activiteitenbeheerder +Name[pa]=ਐਕਟਵਿਟੀ ਮੈਨੇਜਰ +Name[pl]=Menadżer działań +Name[pt]=Gestor de Actividades +Name[pt_BR]=Gerenciador de atividades +Name[ro]=Gestionar de activități +Name[ru]=Диспетчер комнат +Name[se]=Aktivitehtagieđahalli +Name[sk]=Správca aktivít +Name[sl]=Upravljalnik dejavnosti +Name[sr]=Менаџер активности +Name[sr@ijekavian]=Менаџер активности +Name[sr@ijekavianlatin]=Menadžer aktivnosti +Name[sr@latin]=Menadžer aktivnosti +Name[sv]=Aktivitetshanterare +Name[tg]=Мудири фаъолият +Name[tr]=Etkinlik Yöneticisi +Name[ug]=پائالىيەت باشقۇرغۇچ +Name[uk]=Керування просторами дій +Name[x-test]=xxActivity Managerxx +Name[zh_CN]=活动管理器 +Name[zh_TW]=活動管理員 + +Comment=The activity management backend +Comment[bs]=Pozadina za upravljanje aktivnostima +Comment[ca]=Dorsal de gestió d'activitats +Comment[ca@valencia]=Dorsal de gestió d'activitats +Comment[cs]=Podpůrná vrstva pro správu aktivit +Comment[da]=Motor til aktivitetshåndtering +Comment[de]=Backend der Aktivitätenverwaltung +Comment[el]=Το σύστημα υποστήριξης διαχείρισης δραστηριοτήτων +Comment[en_GB]=The activity management backend +Comment[es]=El motor de la gestión de actividades +Comment[et]=Tegevuste haldamise taustaprogramm +Comment[eu]=Jarduera kudeaketa 'backend' +Comment[fi]=Aktiviteettihallintataustaohjelma +Comment[fr]=Le moteur de gestion d'activités +Comment[ga]=Inneall bainisteoireachta gníomhaíochta +Comment[gl]=A infraestrutura de xestión da actividade +Comment[he]=מנוע ניהול הפעילויות +Comment[hu]=Aktivitáskezelő modul +Comment[ia]=Le retro-administration de gestion de activitate +Comment[is]=Stýringarkerfi fyrir virkni +Comment[it]=Il motore di gestione delle attività +Comment[kk]=Белсенділікті басқару бағдарламасы +Comment[km]=កម្មវិធី​ខាងក្រោយ​ការ​គ្រប់គ្រង​សកម្មភាព +Comment[ko]=활동 관리 백엔드 +Comment[lt]=Veiklos tvarkymo sąsaja +Comment[mr]=कार्यपध्दती व्यवस्थापन बॅकएन्ड +Comment[nb]=Bakgrunnsmotoren for aktivitetsbehandling +Comment[nds]=Hülpprogramm för den Aktivitetenpleger +Comment[nl]=De activiteitenbeheerder-backend +Comment[pa]=ਐਕਟਵਿਟੀ ਮੈਨਿਜਮੈਂਟ ਬੈਕਐਂਡ +Comment[pl]=Silnik zarządzania działaniami +Comment[pt]=A infra-estrutura de gestão de actividades +Comment[pt_BR]=Infraestrutura de gerenciamento de atividades +Comment[ro]=Suport pentru administrare activități +Comment[ru]=Механизм управления комнатами +Comment[se]=Aktivitehtagieđahallama duogášmohtor +Comment[sk]=Backend pre správu aktivít +Comment[sl]=Zaledje za upravljanje z dejavnostmi +Comment[sr]=Позадина за управљање активностима +Comment[sr@ijekavian]=Позадина за управљање активностима +Comment[sr@ijekavianlatin]=Pozadina za upravljanje aktivnostima +Comment[sr@latin]=Pozadina za upravljanje aktivnostima +Comment[sv]=Gränssnittet för aktivitetshanteringen +Comment[tg]=Сервери идоракунии фаъолият +Comment[tr]=Etkinlik yönetimi arkaucu +Comment[ug]=پائالىيەت باشقۇرغۇچ ئارقا ئۇچى +Comment[uk]=Сервер керування просторами дій +Comment[x-test]=xxThe activity management backendxx +Comment[zh_CN]=活动管理器后端 +Comment[zh_TW]=活動管理後端介面 diff --git a/kactivities/src/service/jobs/Job.cpp b/kactivities/src/service/jobs/Job.cpp new file mode 100644 index 00000000..931e3cb1 --- /dev/null +++ b/kactivities/src/service/jobs/Job.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Job.h" + +#include + +#include + +class Job::Private { +public: + static QObject * s_global; +}; + +QObject * Job::Private::s_global = nullptr; + +Job::Job(QObject * parent) + :KJob(parent), d() +{ +} + +Job::~Job() +{ +} + +void Job::init() +{ +} + +QObject * Job::global() +{ + if (!Private::s_global) { + Private::s_global = new QObject(); + } + + return Private::s_global; +} + + diff --git a/kactivities/src/service/jobs/Job.h b/kactivities/src/service/jobs/Job.h new file mode 100644 index 00000000..cbf052d1 --- /dev/null +++ b/kactivities/src/service/jobs/Job.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_JOB_H +#define JOBS_JOB_H + +#include + +#include +#include +#include + +/** + * Job + */ +class Job: public KJob { + Q_OBJECT + +public: + Job(QObject * parent = nullptr); + virtual ~Job(); + + virtual void init(); + + static QObject * global(); + +private: + D_PTR; +}; + +#endif // JOBS_JOB_H + diff --git a/kactivities/src/service/jobs/JobFactory.cpp b/kactivities/src/service/jobs/JobFactory.cpp new file mode 100644 index 00000000..1ce50d35 --- /dev/null +++ b/kactivities/src/service/jobs/JobFactory.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "JobFactory.h" + +#include +#include +#include +#include + +#include + +class JobFactory::Private { +public: + QHash < QString, QVariant > properties; +}; + +JobFactory::JobFactory() + : d() +{ +} + +JobFactory::~JobFactory() +{ +} + +void JobFactory::setProperty(const QString & key, const QVariant & value) +{ + d->properties[key] = value; +} + +void JobFactory::clearProperty(const QString & key) +{ + d->properties.remove(key); +} + +void JobFactory::property(const QString & key) const +{ + d->properties[key]; +} + +Job * JobFactory::create(QObject * parent) +{ + Job * result = createJob(parent); + + QHashIterator < QString, QVariant > i(d->properties); + while (i.hasNext()) { + i.next(); + result->setProperty(i.key().toAscii(), i.value()); + } + + return result; +} + +class JobFactoryWrapper: public JobFactory { +public: + JobFactoryWrapper(Job * job) + : m_job(job) + { + } + +protected: + virtual Job * createJob(QObject * parent) _override + { + m_job->setParent(parent); + + return m_job; + } + +private: + Job * m_job; +}; + +JobFactory * JobFactory::wrap(Job * job) +{ + return new JobFactoryWrapper(job); +} + +// class JobFactory + + diff --git a/kactivities/src/service/jobs/JobFactory.h b/kactivities/src/service/jobs/JobFactory.h new file mode 100644 index 00000000..7687bf9a --- /dev/null +++ b/kactivities/src/service/jobs/JobFactory.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_JOB_FACTORY_H +#define JOBS_JOB_FACTORY_H + +#include "Job.h" +#include + +#include + +#define DECLARE_JOB_FACTORY(Type, ConstructorParams) \ + Type(QObject * parent) \ + :Job(parent) \ + { init(); } \ + class Factory: public JobFactory { \ + public: \ + Factory ConstructorParams ; \ + Job * createJob(QObject * parent) { \ + return new Type(parent); \ + } \ + } + +#define JOB_FACTORY Factory::Factory + +#define JOB_FACTORY_PROPERTY(PropertyName) \ + setProperty(#PropertyName, QVariant::fromValue(PropertyName)) + +/** + * JobFactory + */ +class JobFactory { +public: + JobFactory(); + virtual ~JobFactory(); + + virtual Job * create(QObject * parent); + + void setProperty(const QString & key, const QVariant & value); + void clearProperty(const QString & key); + void property(const QString & key) const; + + static JobFactory * wrap(Job * job); + +protected: + virtual Job * createJob(QObject * parent) = 0; + +private: + D_PTR; +}; + +#endif // JOBS_JOB_FACTORY_H + diff --git a/kactivities/src/service/jobs/activity/Start.cpp b/kactivities/src/service/jobs/activity/Start.cpp new file mode 100644 index 00000000..5230ca76 --- /dev/null +++ b/kactivities/src/service/jobs/activity/Start.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Change.h" + +#include + +namespace Jobs { +namespace Activity { + +Change::JOB_FACTORY(QObject * receiver, const QString & slot, const QString & activity) +{ + JOB_FACTORY_PROPERTY(receiver); + JOB_FACTORY_PROPERTY(slot); + JOB_FACTORY_PROPERTY(activity); +} + +QString Change::activity() const +{ + return m_activity; +} + +void Change::setActivity(const QString & activity) +{ + m_activity = activity; +} + +void Change::setReceiver(QObject * receiver) +{ + m_receiver = receiver; +} + +void Change::setSlot(const QString & slot) +{ + m_slot = slot; +} + +void Change::start() +{ + if (m_receiver) { + qDebug() << ">>> Calling the method to set activity" << m_activity << "slot" << m_slot; + bool ret = QMetaObject::invokeMethod(m_receiver, m_slot.toAscii(), Qt::QueuedConnection, Q_ARG(QString, m_activity)); + qDebug() << ret; + + } else { + qDebug() << ">>> Receiver is nullptr, failing"; + setError(1); + setErrorText("There is no receiver registered to signal the change activity"); + + } + + emit emitResult(); +} + +} // namespace Activity +} // namespace Jobs + diff --git a/kactivities/src/service/jobs/activity/Start.h b/kactivities/src/service/jobs/activity/Start.h new file mode 100644 index 00000000..377cf84e --- /dev/null +++ b/kactivities/src/service/jobs/activity/Start.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_ACTIVITY_START_H +#define JOBS_ACTIVITY_START_H + +#include "../Job.h" +#include "../JobFactory.h" + +namespace Jobs { +namespace Activity { + +/** + * Change + */ +class Change: public Job { + Q_OBJECT + Q_PROPERTY(QObject * receiver WRITE setReceiver) + Q_PROPERTY(QString slot WRITE setSlot) + Q_PROPERTY(QString activity READ activity WRITE setActivity) + +public: + enum Type { + Information, + Warning, + Error + }; + + DECLARE_JOB_FACTORY(Change, (QObject * receiver, const QString & slot, const QString & activity)); + + QString activity() const; + void setActivity(const QString & activity); + + void setReceiver(QObject * receiver); + void setSlot(const QString & slot); + + virtual void start() _override; + +private: + QObject * m_receiver; + QString m_slot; + QString m_activity; + +}; + +inline Change::Factory * change(QObject * receiver, const QString & slot, const QString & activity) +{ + return new Change::Factory(receiver, slot, activity); +} + +} // namespace Activity +} // namespace Jobs + +#endif // JOBS_ACTIVITY_START_H + diff --git a/kactivities/src/service/jobs/activity/all.h b/kactivities/src/service/jobs/activity/all.h new file mode 100644 index 00000000..5fe49b30 --- /dev/null +++ b/kactivities/src/service/jobs/activity/all.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_ACTIVITY_ALL_H +#define JOBS_ACTIVITY_ALL_H + +#include "Start.h" + +#endif // JOBS_ACTIVITY_ALL_H + diff --git a/kactivities/src/service/jobs/general/Call.cpp b/kactivities/src/service/jobs/general/Call.cpp new file mode 100644 index 00000000..e30f2c15 --- /dev/null +++ b/kactivities/src/service/jobs/general/Call.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Call.h" + +#include + +namespace Jobs { +namespace General { + +Call::JOB_FACTORY(QObject * receiver, const QString & slot, const QString & argument, bool waitFinished) +{ + JOB_FACTORY_PROPERTY(receiver); + JOB_FACTORY_PROPERTY(slot); + JOB_FACTORY_PROPERTY(argument); + JOB_FACTORY_PROPERTY(waitFinished); +} + +QString Call::argument() const +{ + return m_argument; +} + +void Call::setArgument(const QString & argument) +{ + m_argument = argument; +} + +void Call::setReceiver(QObject * receiver) +{ + m_receiver = receiver; +} + +void Call::setSlot(const QString & slot) +{ + m_slot = slot; +} + +QObject * Call::receiver() const +{ + return m_receiver; +} + +QString Call::slot() const +{ + return m_slot; +} + +bool Call::waitFinished() const +{ + return m_waitFinished; +} + +void Call::setWaitFinished(bool value) +{ + m_waitFinished = value; +} + +void Call::start() +{ + if (m_receiver) { + qDebug() << ">>> Calling the method" << m_slot << "with" << m_argument; + + QMetaObject::invokeMethod(m_receiver, m_slot.toAscii(), + (m_waitFinished ? Qt::QueuedConnection : Qt::DirectConnection), + Q_ARG(QString, m_argument)); + + } else { + qDebug() << ">>> Receiver is nullptr, failing"; + setError(1); + setErrorText("There is no receiver registered to call"); + + } + + emit emitResult(); +} + +} // namespace General +} // namespace Jobs + diff --git a/kactivities/src/service/jobs/general/Call.h b/kactivities/src/service/jobs/general/Call.h new file mode 100644 index 00000000..43eb0ec3 --- /dev/null +++ b/kactivities/src/service/jobs/general/Call.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_GENERAL_CALL_H +#define JOBS_GENERAL_CALL_H + +#include "../Job.h" +#include "../JobFactory.h" + +namespace Jobs { +namespace General { + +/** + * Call + */ +class Call: public Job { + Q_OBJECT + Q_PROPERTY(QObject * receiver READ receiver WRITE setReceiver) + Q_PROPERTY(QString slot READ slot WRITE setSlot) + Q_PROPERTY(QString argument READ argument WRITE setArgument) + Q_PROPERTY(bool waitFinished READ waitFinished WRITE setWaitFinished) + +public: + enum Type { + Information, + Warning, + Error + }; + + DECLARE_JOB_FACTORY(Call, (QObject * receiver, const QString & slot, const QString & argument, bool waitFinished)); + + QString argument() const; + void setArgument(const QString & argument); + + void setReceiver(QObject * receiver); + QObject * receiver() const; + + void setSlot(const QString & slot); + QString slot() const; + + void setWaitFinished(bool value); + bool waitFinished() const; + + virtual void start() _override; + +private: + QObject * m_receiver; + QString m_slot; + QString m_argument; + bool m_waitFinished; + +}; + +inline Call::Factory * call(QObject * receiver, const QString & slot, + const QString & argument, bool waitFinished = false) +{ + return new Call::Factory(receiver, slot, argument, waitFinished); +} + +} // namespace General +} // namespace Jobs + +#endif // JOBS_GENERAL_CALL_H + diff --git a/kactivities/src/service/jobs/general/all.h b/kactivities/src/service/jobs/general/all.h new file mode 100644 index 00000000..61518486 --- /dev/null +++ b/kactivities/src/service/jobs/general/all.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_GENERAL_ALL_H +#define JOBS_GENERAL_ALL_H + +#include "Call.h" + +#endif // JOBS_GENERAL_ALL_H + diff --git a/kactivities/src/service/jobs/ksmserver/KSMServer.cpp b/kactivities/src/service/jobs/ksmserver/KSMServer.cpp new file mode 100644 index 00000000..3d2ebb48 --- /dev/null +++ b/kactivities/src/service/jobs/ksmserver/KSMServer.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "KSMServer.h" +#include "KSMServer_p.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define KWIN_SERVICE "org.kde.kwin" +#define KSMSERVER_SERVICE "org.kde.ksmserver" + +KSMServer::Private::Private(KSMServer * parent) + : serviceWatcher(nullptr), + kwin(nullptr), + ksmserver(nullptr), + processing(false), + q(parent) +{ + serviceWatcher = new QDBusServiceWatcher(this); + + serviceWatcher->setConnection(KDBusConnectionPool::threadConnection()); + serviceWatcher->addWatchedService(KWIN_SERVICE); + serviceWatcher->addWatchedService(KSMSERVER_SERVICE); + + connect(serviceWatcher, SIGNAL(serviceOwnerChanged(QString, QString, QString)), + this, SLOT(serviceOwnerChanged(QString, QString, QString))); + + serviceOwnerChanged(KWIN_SERVICE, QString(), QString()); + serviceOwnerChanged(KSMSERVER_SERVICE, QString(), QString()); +} + +template < typename Func > +static void initializeInterface(QDBusInterface * & service, + const QString & servicePath, const QString & path, const QString & object, Func init) +{ + // Delete the old object, just in case + delete service; + + // Creating the new dbus interface + service = new QDBusInterface(servicePath, path, object); + + qDebug() << path << "is valid?" << service->isValid(); + + // If the service is valid, initialize it + // otherwise delete the object + if (service->isValid()) { + init(service); + + } else { + delete service; + service = nullptr; + + } + +} + +void KSMServer::Private::serviceOwnerChanged(const QString & service, + const QString & oldOwner, const QString & newOwner) +{ + Q_UNUSED(oldOwner); + Q_UNUSED(newOwner); + + if (service == KSMSERVER_SERVICE) { + + initializeInterface( + ksmserver, + KSMSERVER_SERVICE, "/KSMServer", "org.kde.KSMServerInterface", + [this] (QObject * service) { + service->setParent(this); + connect(service, SIGNAL(subSessionOpened()), + this, SLOT(subSessionOpened())); + connect(service, SIGNAL(subSessionClosed()), + this, SLOT(subSessionClosed())); + connect(service, SIGNAL(subSessionCloseCanceled()), + this, SLOT(subSessionCloseCanceled())); + } + ); + + } else if (service == KWIN_SERVICE) { + + initializeInterface( + kwin, + KWIN_SERVICE, "/KWin", "org.kde.KWin", + [this] (QObject * service) { + service->setParent(this); + } + ); + + } +} + +KSMServer::KSMServer(QObject * parent) + : QObject(parent), d(this) +{ +} + +KSMServer::~KSMServer() +{ +} + +void KSMServer::startActivitySession(const QString & activity) +{ + d->processLater(activity, true); +} + +void KSMServer::stopActivitySession(const QString & activity) +{ + d->processLater(activity, false); +} + +void KSMServer::Private::processLater(const QString & activity, bool start) +{ + qDebug() << "Scheduling" << activity << "to be" << (start ? "started" : "stopped"); + + foreach (val & item, queue) { + if (item.first == activity) { + return; + } + } + + queue << qMakePair(activity, start); + + if (!processing) { + processing = true; + QMetaObject::invokeMethod(this, "process", Qt::QueuedConnection); + } +} + +void KSMServer::Private::process() +{ + // If the queue is empty, we have nothing more to do + if (queue.isEmpty()) { + processing = false; + return; + } + + + val item = queue.takeFirst(); + processingActivity = item.first; + + qDebug() << "Processing" << item; + + makeRunning(item.second); + + // Calling process again for the rest of the list + QMetaObject::invokeMethod(this, "process", Qt::QueuedConnection); +} + +void KSMServer::Private::makeRunning(bool value) +{ + if (!kwin) { + qDebug() << "There is no KWin"; + subSessionSendEvent(value ? KSMServer::Started : KSMServer::Stopped); + return; + } + + val call = kwin->asyncCall( + value ? QLatin1String("startActivity") : QLatin1String("stopActivity"), + processingActivity); + + val watcher = new QDBusPendingCallWatcher(call, this); + + QObject::connect( + watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, + value + ? SLOT(startCallFinished(QDBusPendingCallWatcher*)) + : SLOT(stopCallFinished(QDBusPendingCallWatcher*)) + ); +} + +void KSMServer::Private::startCallFinished(QDBusPendingCallWatcher * call) +{ + QDBusPendingReply < bool > reply = * call; + + if (reply.isError()) { + qDebug() << "Session starting call failed, but we are returning success"; + emit q->activitySessionStateChanged(processingActivity, KSMServer::Started); + + } else { + // If we got false, it means something is going on with ksmserver + // and it didn't start our activity + val retval = reply.argumentAt<0>(); + + qDebug() << "Did we start the activity successfully:" << retval; + if (!retval) { + subSessionSendEvent(KSMServer::Stopped); + } + } + + call->deleteLater(); +} + +void KSMServer::Private::stopCallFinished(QDBusPendingCallWatcher * call) +{ + QDBusPendingReply < bool > reply = * call; + + if (reply.isError()) { + qDebug() << "Session stopping call failed, but we are returning success"; + emit q->activitySessionStateChanged(processingActivity, KSMServer::Stopped); + + } else { + // If we got false, it means something is going on with ksmserver + // and it didn't stop our activity + val retval = reply.argumentAt<0>(); + + qDebug() << "Did we stop the activity successfully:" << retval; + if (!retval) { + subSessionSendEvent(KSMServer::FailedToStop); + } + } + + call->deleteLater(); +} + +void KSMServer::Private::subSessionSendEvent(int event) +{ + if (processingActivity.isEmpty()) return; + + emit q->activitySessionStateChanged(processingActivity, event); + + processingActivity.clear(); +} + +void KSMServer::Private::subSessionOpened() +{ + subSessionSendEvent(KSMServer::Started); +} + +void KSMServer::Private::subSessionClosed() +{ + subSessionSendEvent(KSMServer::Stopped); +} + +void KSMServer::Private::subSessionCloseCanceled() +{ + subSessionSendEvent(KSMServer::FailedToStop); +} + diff --git a/kactivities/src/service/jobs/ksmserver/KSMServer.h b/kactivities/src/service/jobs/ksmserver/KSMServer.h new file mode 100644 index 00000000..7e42e56e --- /dev/null +++ b/kactivities/src/service/jobs/ksmserver/KSMServer.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KSMSERVER_H +#define KSMSERVER_H + +#include + +#include +#include + +/** + * KSMServer + */ +class KSMServer: public QObject { + Q_OBJECT +public: + + enum ReturnStatus { + Started = 0, + Stopped = 1, + FailedToStop = 2 + }; + + KSMServer(QObject * parent = nullptr); + virtual ~KSMServer(); + +public Q_SLOTS: + void startActivitySession(const QString & activity); + void stopActivitySession(const QString & activity); + +Q_SIGNALS: + void activitySessionStateChanged(const QString & activity, int status); + +private: + D_PTR; +}; + +#endif // KSMSERVER_H + diff --git a/kactivities/src/service/jobs/ksmserver/KSMServer_p.h b/kactivities/src/service/jobs/ksmserver/KSMServer_p.h new file mode 100644 index 00000000..75f5dcba --- /dev/null +++ b/kactivities/src/service/jobs/ksmserver/KSMServer_p.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KSMSERVER_P_H +#define KSMSERVER_P_H + +#include "KSMServer.h" + +#include + +class QDBusServiceWatcher; +class QDBusInterface; +class QDBusPendingCallWatcher; + +class KSMServer::Private: public QObject { + Q_OBJECT + +public: + Private(KSMServer * parent); + + void processLater(const QString & activity, bool start); + +private Q_SLOTS: + void serviceOwnerChanged(const QString & service, const QString & oldOwner, const QString & newOwner); + + void process(); + void makeRunning(bool value); + + void startCallFinished(QDBusPendingCallWatcher * watcher); + void stopCallFinished(QDBusPendingCallWatcher * watcher); + + void subSessionOpened(); + void subSessionClosed(); + void subSessionCloseCanceled(); + void subSessionSendEvent(int event); + +private: + QDBusServiceWatcher * serviceWatcher; + QDBusInterface * kwin; + QDBusInterface * ksmserver; + + bool processing; + QString processingActivity; + QList < QPair < QString, bool > > queue; + + KSMServer * const q; +}; + +#endif // KSMSERVER_P_H diff --git a/kactivities/src/service/jobs/schedulers/Abstract.cpp b/kactivities/src/service/jobs/schedulers/Abstract.cpp new file mode 100644 index 00000000..a460a5ec --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Abstract.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Abstract.h" +#include "Abstract_p.h" + +#include + +#include + +namespace Jobs { +namespace Schedulers { + +Abstract::Private::Private(Abstract * parent) + : q(parent) +{ +} + +void Abstract::Private::jobFinished(KJob * job) +{ + qDebug() << "Job has finished with this result" << job->error(); + q->jobFinished(job->error()); +} + +bool Abstract::startJob(int index) +{ + d->lastJobStarted = index; + + // If the index is not valid + if (index < 0 || d->jobs.size() <= index) { + returnResult(0); + return false; + } + + JobFactory * factory = d->jobs[index]; + + // If the job factory is null, exit + if (!factory) { + returnResult(0); + return false; + } + + // Starting the job + KJob * job = d->jobs[index]->create(this); + + d->connect(job, SIGNAL(finished(KJob *)), + SLOT(jobFinished(KJob *))); + + job->start(); + + return true; +} + +Abstract::Abstract(QObject * parent) + : Job(parent), d(this) +{ + d->lastJobStarted = -1; + + if (!parent) { + connect( + this, SIGNAL(finished(KJob *)), + this, SLOT(deleteLater()), + Qt::QueuedConnection + ); + } +} + +Abstract::~Abstract() +{ + qDeleteAll(d->jobs); +} + +void Abstract::addJob(Job * other) +{ + d->jobs << JobFactory::wrap(other); +} + +void Abstract::addJob(JobFactory * other) +{ + d->jobs << other; +} + +void Abstract::start() +{ + if (d->jobs.size() == 0) { + returnResult(0); + return; + } + + startJob(0); +} + +int Abstract::lastJobStarted() const +{ + return d->lastJobStarted; +} + +int Abstract::jobCount() const +{ + return d->jobs.size(); +} + +bool Abstract::hasJob(int index) const +{ + return (index >= 0 && index < d->jobs.size() && d->jobs[index] != nullptr); +} + +void Abstract::returnResult(int result) +{ + qDebug() << "Returning" << result; + setError(result); + emitResult(); +} + + +} // namespace Schedulers +} // namespace Jobs diff --git a/kactivities/src/service/jobs/schedulers/Abstract.h b/kactivities/src/service/jobs/schedulers/Abstract.h new file mode 100644 index 00000000..5223e089 --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Abstract.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_SCHEDULER_ABSTRACT_H +#define JOBS_SCHEDULER_ABSTRACT_H + +#include +#include + +#include + +namespace Jobs { +namespace Schedulers { + +/** + * Abstract + */ +class Abstract: public Job { + Q_OBJECT + +public: + Abstract(QObject * parent = nullptr); + virtual ~Abstract(); + + virtual void start() _override; + +protected: + bool startJob(int index); + virtual void jobFinished(int result) = 0; + + int lastJobStarted() const; + int jobCount() const; + bool hasJob(int index) const; + + void addJob(JobFactory * job); + void addJob(Job * job); + + void returnResult(int result); + +private: + Abstract(const Abstract & original); + Abstract & operator = (const Abstract & original); + + D_PTR; +}; + +// class Abstract + +} // namespace Schedulers +} // namespace Jobs + +#endif // JOBS_SCHEDULER_ABSTRACT_H + diff --git a/kactivities/src/service/jobs/schedulers/Abstract_p.h b/kactivities/src/service/jobs/schedulers/Abstract_p.h new file mode 100644 index 00000000..06c40fdb --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Abstract_p.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_SCHEDULER_ABSTRACT_P_H +#define JOBS_SCHEDULER_ABSTRACT_P_H + +#include "Abstract.h" + +#include + +namespace Jobs { +namespace Schedulers { + +class Abstract::Private: public QObject { + Q_OBJECT +public: + Private(Abstract * parent); + + QList < JobFactory * > jobs; + int lastJobStarted; + +public Q_SLOTS: + void jobFinished(KJob * job); + +public: + Abstract * const q; +}; + +} // namespace Schedulers +} // namespace Jobs + +#endif // JOBS_SCHEDULER_ABSTRACT_P_H diff --git a/kactivities/src/service/jobs/schedulers/Fallible.cpp b/kactivities/src/service/jobs/schedulers/Fallible.cpp new file mode 100644 index 00000000..dc4e9dc9 --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Fallible.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Fallible.h" + +namespace Jobs { +namespace Schedulers { + +Fallible::Fallible(QObject * parent) + : Abstract(parent) +{ +} + +Fallible::~Fallible() +{ +} + +Fallible & Fallible::operator << (JobFactory * job) +{ + addJob(job); + + return *this; +} + +Fallible & Fallible::operator << (Job * job) +{ + addJob(job); + + return *this; +} + +void Fallible::jobFinished(int result) +{ + if (result != 0 || lastJobStarted() == jobCount() - 1) { + emitResult(); + + } else { + startJob(lastJobStarted() + 1); + + } +} + + + +} // namespace Schedulers +} // namespace Jobs + diff --git a/kactivities/src/service/jobs/schedulers/Fallible.h b/kactivities/src/service/jobs/schedulers/Fallible.h new file mode 100644 index 00000000..fdde15c8 --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Fallible.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_SCHEDULER_FALLIBLE_H +#define JOBS_SCHEDULER_FALLIBLE_H + +#include +#include +#include + +#define FALLIBLE_JOB(Job) &((new Jobs::Schedulers::Fallible())->operator<<(Job)) + +namespace Jobs { +namespace Schedulers { + +/** + * Fallible + */ +class Fallible: public Abstract { + Q_OBJECT + +public: + Fallible(QObject * parent = nullptr); + virtual ~Fallible(); + + Fallible & operator << (JobFactory * other); + Fallible & operator << (Job * other); + +protected: + virtual void jobFinished(int result); + +private: + Fallible(const Fallible & original); + Fallible & operator = (const Fallible & original); +}; + +} // namespace Schedulers +} // namespace Jobs + +#endif // JOBS_SCHEDULER_FALLIBLE_H + diff --git a/kactivities/src/service/jobs/schedulers/Given.cpp b/kactivities/src/service/jobs/schedulers/Given.cpp new file mode 100644 index 00000000..455a6d65 --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Given.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Given.h" + +namespace Jobs { +namespace Schedulers { + +Given::Given(JobFactory * _condition, JobFactory * _then, JobFactory * _else, bool failOnElse, QObject * parent) + : Abstract(parent), m_failOnElse(failOnElse) +{ + addJob(_condition); + addJob(_then); + addJob(_else); +} + +Given::~Given() +{ +} + +void Given::jobFinished(int result) +{ + if (lastJobStarted() == 0) { + int next = (result == 0) ? 1 : 2; + + if (hasJob(next)) { + startJob(next); + return; + } + + } + + if (lastJobStarted() == 2 && m_failOnElse) { + setError(1); + + } else { + setError(result); + + } + + emitResult(); +} + + + +} // namespace Schedulers +} // namespace Jobs + diff --git a/kactivities/src/service/jobs/schedulers/Given.h b/kactivities/src/service/jobs/schedulers/Given.h new file mode 100644 index 00000000..b311b235 --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Given.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_SCHEDULER_GIVEN_H +#define JOBS_SCHEDULER_GIVEN_H + +#include +#include +#include + +#define DO_OR_DIE(Task, Death) new Jobs::Schedulers::Given(Task, nullptr, Death, true) + +namespace Jobs { +namespace Schedulers { + +/** + * Given + */ +class Given: public Abstract { + Q_OBJECT + +public: + Given(JobFactory * _condition, JobFactory * _then, JobFactory * _else, bool failOnElse, QObject * parent = nullptr); + virtual ~Given(); + +protected: + virtual void jobFinished(int result); + +private: + Given(const Given & original); + Given & operator = (const Given & original); + + bool m_failOnElse; +}; + +} // namespace Schedulers +} // namespace Jobs + +#endif // JOBS_SCHEDULER_GIVEN_H + diff --git a/kactivities/src/service/jobs/schedulers/Ordered.cpp b/kactivities/src/service/jobs/schedulers/Ordered.cpp new file mode 100644 index 00000000..ccee87fb --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Ordered.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Ordered.h" + +#include + +namespace Jobs { +namespace Schedulers { + +Ordered::Ordered(QObject * parent) + : Abstract(parent) +{ +} + +Ordered::~Ordered() +{ +} + +Ordered & Ordered::operator << (JobFactory * job) +{ + addJob(job); + + return *this; +} + +Ordered & Ordered::operator << (Job * job) +{ + addJob(job); + + return *this; +} + +void Ordered::jobFinished(int result) +{ + if (result != 0) { + qDebug() << "So... we got an error" << result; + setError(result); + emitResult(); + + } else if (lastJobStarted() == jobCount() - 1) { + qDebug() << "no error, no jobs"; + emitResult(); + + } else { + qDebug() << "next job please"; + startJob(lastJobStarted() + 1); + + } +} + + + +} // namespace Schedulers +} // namespace Jobs + diff --git a/kactivities/src/service/jobs/schedulers/Ordered.h b/kactivities/src/service/jobs/schedulers/Ordered.h new file mode 100644 index 00000000..e8de59d8 --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Ordered.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_SCHEDULER_ORDERED_H +#define JOBS_SCHEDULER_ORDERED_H + +#include +#include +#include + +#define DEFINE_ORDERED_SCHEDULER(Name) Jobs::Schedulers::Ordered & Name \ + = * (new Jobs::Schedulers::Ordered()) + +namespace Jobs { +namespace Schedulers { + +/** + * Ordered + */ +class Ordered: public Abstract { + Q_OBJECT + +public: + Ordered(QObject * parent = nullptr); + virtual ~Ordered(); + + Ordered & operator << (JobFactory * other); + Ordered & operator << (Job * other); + +protected: + virtual void jobFinished(int result); + +private: + Ordered(const Ordered & original); + Ordered & operator = (const Ordered & original); +}; + +} // namespace Schedulers +} // namespace Jobs + +#endif // JOBS_SCHEDULER_ORDERED_H + diff --git a/kactivities/src/service/jobs/schedulers/Retry.cpp b/kactivities/src/service/jobs/schedulers/Retry.cpp new file mode 100644 index 00000000..d029debc --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Retry.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Retry.h" + +namespace Jobs { +namespace Schedulers { + +Retry::Retry(JobFactory * _input, JobFactory * _test, JobFactory * _error, QObject * parent) + : Abstract(parent) +{ + addJob(_input); + addJob(_test); + addJob(_error); +} + +Retry::~Retry() +{ +} + +void Retry::jobFinished(int result) +{ + switch (lastJobStarted()) { + case 0: { + // We have executed the input, and it has finished. If the input + // failed, we want to exit with an error + + if (result != 0) { + returnResult(1); + return; + } + + startJob(1); + + } break; + + case 1: { + // The test finished. If it fails, executing the error job + + if (result == 0) { + returnResult(0); + return; + } + + startJob(2); + } break; + + case 2: { + // error executed, restarting + startJob(0); + } break; + } +} + + + +} // namespace Schedulers +} // namespace Jobs + diff --git a/kactivities/src/service/jobs/schedulers/Retry.h b/kactivities/src/service/jobs/schedulers/Retry.h new file mode 100644 index 00000000..ab7f43aa --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Retry.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_SCHEDULER_RETRY_H +#define JOBS_SCHEDULER_RETRY_H + +#include +#include +#include + +#define RETRY_JOB(Input, Test, Error) new Jobs::Schedulers::Retry(Input, Test, Error) + +namespace Jobs { +namespace Schedulers { + +/** + * Retry + */ +class Retry: public Abstract { + Q_OBJECT + +public: + Retry(JobFactory * _input, JobFactory * _test, JobFactory * _error, QObject * parent = nullptr); + virtual ~Retry(); + +protected: + virtual void jobFinished(int result); + +private: + Retry(const Retry & original); + Retry & operator = (const Retry & original); + + bool m_failOnElse; +}; + +} // namespace Schedulers +} // namespace Jobs + +#endif // JOBS_SCHEDULER_RETRY_H + diff --git a/kactivities/src/service/jobs/schedulers/Switch.cpp b/kactivities/src/service/jobs/schedulers/Switch.cpp new file mode 100644 index 00000000..6c71fb7b --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Switch.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Switch.h" + +namespace Jobs { +namespace Schedulers { + +Switch::Switch(QObject * parent) + : Abstract(parent) +{ +} + +Switch::~Switch() +{ +} + +Switch & Switch::operator << (JobFactory * job) +{ + addJob(job); + + return *this; +} + +Switch & Switch::operator << (Job * job) +{ + addJob(job); + + return *this; +} + +void Switch::jobFinished(int result) +{ + if (lastJobStarted() != 0) { + // We have executed the switch command + returnResult(result); + return; + } + + if (result >= 0) { + // The test job returned a positive value, this means it failed + returnResult(1); + return; + + } else { + startJob(-result); + + } +} + + + +} // namespace Schedulers +} // namespace Jobs + diff --git a/kactivities/src/service/jobs/schedulers/Switch.h b/kactivities/src/service/jobs/schedulers/Switch.h new file mode 100644 index 00000000..55d74155 --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Switch.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_SCHEDULER_SWITCH_H +#define JOBS_SCHEDULER_SWITCH_H + +#include +#include +#include + +namespace Jobs { +namespace Schedulers { + +/** + * Switch + */ +class Switch: public Abstract { + Q_OBJECT + +public: + Switch(QObject * parent = nullptr); + virtual ~Switch(); + + Switch & operator << (JobFactory * other); + Switch & operator << (Job * other); + +protected: + virtual void jobFinished(int result); + +private: + Switch(const Switch & original); + Switch & operator = (const Switch & original); +}; + +} // namespace Schedulers +} // namespace Jobs + +#endif // JOBS_SCHEDULER_SWITCH_H + diff --git a/kactivities/src/service/jobs/schedulers/Test.cpp b/kactivities/src/service/jobs/schedulers/Test.cpp new file mode 100644 index 00000000..ea0ba1c8 --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Test.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Test.h" + +#include + +namespace Jobs { +namespace Schedulers { + +Test::Test(JobFactory * job, int expectedResult, QObject * parent) + : Abstract(parent), m_expectedResult(expectedResult) +{ + addJob(job); +} + +Test::~Test() +{ +} + +void Test::jobFinished(int result) +{ + qDebug() << "Returned" << result << "expected" << m_expectedResult; + + returnResult(m_expectedResult == result ? 0 : 1); +} + + + +} // namespace Schedulers +} // namespace Jobs + diff --git a/kactivities/src/service/jobs/schedulers/Test.h b/kactivities/src/service/jobs/schedulers/Test.h new file mode 100644 index 00000000..0c657c10 --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/Test.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_SCHEDULER_TEST_H +#define JOBS_SCHEDULER_TEST_H + +#include +#include +#include + +#define TEST_JOB(TestJob, Expect) new Jobs::Schedulers::Test(TestJob, Expect) + +namespace Jobs { +namespace Schedulers { + +/** + * Test + */ +class Test: public Abstract { + Q_OBJECT + +public: + Test(JobFactory * job, int expectedResult, QObject * parent = nullptr); + virtual ~Test(); + +protected: + virtual void jobFinished(int result); + +private: + Test(const Test & original); + Test & operator = (const Test & original); + + int m_expectedResult; +}; + +} // namespace Schedulers +} // namespace Jobs + +#endif // JOBS_SCHEDULER_TEST_H + diff --git a/kactivities/src/service/jobs/schedulers/all.h b/kactivities/src/service/jobs/schedulers/all.h new file mode 100644 index 00000000..c0c20231 --- /dev/null +++ b/kactivities/src/service/jobs/schedulers/all.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef JOBS_SCHEDULERS_ALL_H +#define JOBS_SCHEDULERS_ALL_H + +#include "Abstract.h" +#include "Ordered.h" +#include "Fallible.h" +#include "Given.h" +#include "Retry.h" +#include "Switch.h" +#include "Test.h" + +#endif // JOBS_SCHEDULERS_ALL_H + diff --git a/kactivities/src/service/plugins/CMakeLists.txt b/kactivities/src/service/plugins/CMakeLists.txt new file mode 100644 index 00000000..fc43ba78 --- /dev/null +++ b/kactivities/src/service/plugins/CMakeLists.txt @@ -0,0 +1,9 @@ +macro_optional_add_subdirectory (slc) +macro_optional_add_subdirectory (activityranking) +macro_optional_add_subdirectory (sqlite) +macro_optional_add_subdirectory (globalshortcuts) +macro_optional_add_subdirectory (virtualdesktopswitch) + +if (NepomukCore_FOUND AND Soprano_FOUND) + macro_optional_add_subdirectory (nepomuk) +endif () diff --git a/kactivities/src/service/plugins/activityranking/ActivityData.cpp b/kactivities/src/service/plugins/activityranking/ActivityData.cpp new file mode 100644 index 00000000..fd098427 --- /dev/null +++ b/kactivities/src/service/plugins/activityranking/ActivityData.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "ActivityData.h" + +#include + +class ActivityDataStaticInit { +public: + ActivityDataStaticInit() + { + qDBusRegisterMetaType < ActivityData > (); + qDBusRegisterMetaType < QList < ActivityData > > (); + } + + static ActivityDataStaticInit _instance; + +}; + +ActivityDataStaticInit ActivityDataStaticInit::_instance; + +ActivityData::ActivityData() +{ +} + +ActivityData::ActivityData(const ActivityData & source) + : score(source.score), + id(source.id) +{ +} + +ActivityData & ActivityData::operator = (const ActivityData & source) +{ + if (&source != this) { + score = source.score; + id = source.id; + } + + return *this; +} + +QDBusArgument & operator << (QDBusArgument & arg, const ActivityData r) +{ + arg.beginStructure(); + + arg << r.id; + arg << r.score; + + arg.endStructure(); + + return arg; +} + +const QDBusArgument & operator >> (const QDBusArgument & arg, ActivityData & r) +{ + arg.beginStructure(); + + arg >> r.id; + arg >> r.score; + + arg.endStructure(); + + return arg; +} + +QDebug operator << (QDebug dbg, const ActivityData & r) +{ + dbg << "ActivityData(" << r.score << r.id << ")"; + return dbg.space(); +} + diff --git a/kactivities/src/service/plugins/activityranking/ActivityData.h b/kactivities/src/service/plugins/activityranking/ActivityData.h new file mode 100644 index 00000000..3007dd05 --- /dev/null +++ b/kactivities/src/service/plugins/activityranking/ActivityData.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PLUGINS_ACTIVITY_RANKING_ACTIVITY_DATA_H +#define PLUGINS_ACTIVITY_RANKING_ACTIVITY_DATA_H + +#include +#include +#include + +class ActivityData { +public: + ActivityData(); + ActivityData(const ActivityData & source); + ActivityData & operator = (const ActivityData & source); + + double score; + QString id; + +}; + +typedef QList ActivityDataList; +Q_DECLARE_METATYPE(ActivityData) +Q_DECLARE_METATYPE(ActivityDataList) + +QDBusArgument & operator << (QDBusArgument & arg, const ActivityData); +const QDBusArgument & operator >> (const QDBusArgument & arg, ActivityData & rec); + +QDebug operator << (QDebug dbg, const ActivityData & r); + +#endif // PLUGINS_ACTIVITY_RANKING_ACTIVITY_DATA_H diff --git a/kactivities/src/service/plugins/activityranking/ActivityRanking.cpp b/kactivities/src/service/plugins/activityranking/ActivityRanking.cpp new file mode 100644 index 00000000..8ab00c00 --- /dev/null +++ b/kactivities/src/service/plugins/activityranking/ActivityRanking.cpp @@ -0,0 +1,584 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic ivan.cukic(at)kde.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ActivityRanking.h" +#include "activityrankingadaptor.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "Plugin.h" +#include "Location.h" + +#include +#include +#include + +#define PRINT_LAST_ERROR(A) if (A.lastError().isValid()) qDebug() << "DATABASE ERROR" << A.lastError(); + +class ActivityRanking::Private { +public: + QSqlDatabase database; + + QSqlRecord currentActivityRecord; + QString activity; + QString lastLocation; + qint64 activityStart; + + void processActivityInterval(const QString & activity, const QString & location, qint64 start, qint64 end); + void processWeekData(const QString & activity, const QString & location, qint64 start, qint64 end); + void processMonthData(const QString & activity, const QString & location, qint64 start, qint64 end); + + void closeDanglingActivityRecords(); + void ensureMonthScoreExists(const QString & activity, int year, int month, const QString & location); + void ensureWeekScoreExists(const QString & activity, int year, int week, const QString & location); + + QMap topActivitiesFor(const QDateTime & time, const QString & location); + + static QString insertSchemaInfo; + static QString closeActivityInterval; + static QString insertActivityInterval; + + static QString insertWeekScore; + static QString selectWeekScore; + + static QString insertMonthScore; + static QString selectMonthScore; + + static QString selectScore; +}; + +QString ActivityRanking::Private::insertSchemaInfo = "INSERT INTO schemaInfo VALUES ('%1', '%2')"; +QString ActivityRanking::Private::closeActivityInterval = "UPDATE ActivityEvents SET end = %1 WHERE activity = '%2' AND end IS NULL"; +QString ActivityRanking::Private::insertActivityInterval = "INSERT INTO ActivityEvents VALUES('%1', '%2', %3, NULL)"; + +// For the average and dispersion +// select activity, start - (select max(start) from ActivityEvents as ae where ae.start < a.start) as diff from ActivityEvents as a +// QString ActivityRanking::Private::selectDanglingActivities = "SELECT * FROM ActivityEvents WHERE end IS NULL ORDER BY start"; + +QString ActivityRanking::Private::insertWeekScore = + "INSERT INTO WeekScores (activity, year, week, location) VALUES('%1', %2, %3, '%4')"; + +QString ActivityRanking::Private::selectWeekScore = + "SELECT * FROM WeekScores WHERE activity = '%1' AND year = %2 AND week = %3 AND location = '%4'"; + + +QString ActivityRanking::Private::insertMonthScore = + "INSERT INTO MonthScores (activity, year, month, location) VALUES('%1', %2, %3, '%4')"; + +QString ActivityRanking::Private::selectMonthScore = + "SELECT * FROM MonthScores WHERE activity = '%1' AND year = %2 AND month = %3 AND location = '%4'"; + + +// Not using "ORDER BY sumscore DESC" because in topActivitiesFor we return the result of this query in a QMap, which reorders +// the entries by its key (activity id). +QString ActivityRanking::Private::selectScore = + "SELECT week.activity, week.score + month.score as sumscore " + "FROM " + "(SELECT activity, location, SUM(s%1%2) as score FROM WeekScores GROUP BY activity) AS week, " + "(SELECT activity, location, SUM(s%3) as score FROM MonthScores GROUP BY activity) AS month " + "WHERE week.activity = month.activity AND week.location = '%4' AND month.location = '%4'"; + + +void ActivityRanking::Private::ensureWeekScoreExists(const QString & activity, int year, int week, const QString & location) +{ + // If it fails, it means we already have it, ignore the error + database.exec( + insertWeekScore + .arg(activity) + .arg(year) + .arg(week) + .arg(location) + ); + PRINT_LAST_ERROR(database); +} + +void ActivityRanking::Private::ensureMonthScoreExists(const QString & activity, int year, int month, const QString & location) +{ + // If it fails, it means we already have it, ignore the error + database.exec( + insertMonthScore + .arg(activity) + .arg(year) + .arg(month) + .arg(location) + ); + PRINT_LAST_ERROR(database); +} + +void ActivityRanking::Private::processActivityInterval(const QString & activity, const QString & location, qint64 start, qint64 end) +{ + qDebug() << activity << location << start << end; + + if (activity.isEmpty()) { + qDebug() << "empty activity id. Not processing."; + return; + } + + // Processing the per-week data + processWeekData(activity, location, start, end); + + // Processing the month data + processMonthData(activity, location, start, end); + +} + +void ActivityRanking::Private::processWeekData(const QString & activity, const QString & location, qint64 start, qint64 end) +{ + val startDateTime = QDateTime::fromMSecsSinceEpoch(start); + val endDateTime = QDateTime::fromMSecsSinceEpoch(end); + + #define fordate(What, Start, End) \ + for (int What = Start.date().What(); What <= End.date().What(); What++) + + // This can be a bit more efficient + fordate(year, startDateTime, endDateTime) { + fordate(weekNumber, startDateTime, endDateTime) { + qDebug() << activity << year << weekNumber; + + ensureWeekScoreExists(activity, year, weekNumber, location); + + val weekStartDateTime = QDateTime(QDate(year, 1, 1).addDays((weekNumber - 1) * 7)); + val weekStart = weekStartDateTime.toMSecsSinceEpoch(); + val weekEnd = weekStartDateTime.addDays(7).toMSecsSinceEpoch(); + + val currentStart = QDateTime::fromMSecsSinceEpoch(qMax(weekStart, start)); + val currentEnd = QDateTime::fromMSecsSinceEpoch(qMin(weekEnd, end)); + + auto query = database.exec( + selectWeekScore + .arg(activity) + .arg(year) + .arg(weekNumber) + .arg(location) + ); + PRINT_LAST_ERROR(database); + + + if (query.next()) { + auto record = query.record(); + val iFirstColumn = record.indexOf("s00"); + + #define SEGMENTS 8 + for (int day = currentStart.date().dayOfWeek(); day <= currentEnd.date().dayOfWeek(); day++) { + + val startSegment = floor(currentStart.time().hour() / 3.0); + val endSegment = ceil(currentEnd.time().hour() / 3.0); + + for (int segment = 0; segment < SEGMENTS; segment++) { + val index = iFirstColumn + SEGMENTS * (day - 1) + segment; + + // Setting the 1.0 value for the active segments + if (startSegment <= segment && segment <= endSegment) { + record.setValue(index, 1.0); + } + + // Setting the .33 for the whole day + else if (record.value(index).toDouble() < .33) { + record.setValue(index, .33); + } + } + + // Setting the .67 for the edge stuff + + if (startSegment > 1) { + val index = iFirstColumn + SEGMENTS * (day - 1) + startSegment - 1; + + if (record.value(index).toDouble() < .67) { + record.setValue(index, .67); + } + } + + if (endSegment < 7) { + val index = iFirstColumn + SEGMENTS * (day - 1) + endSegment + 1; + + if (record.value(index).toDouble() < .67) { + record.setValue(index, .67); + } + } + } + #undef SEGMENTS + + static val & where = QString::fromLatin1(" WHERE activity = '%1' AND year = %2 AND week = %3 AND location='%4'"); + + database.exec(database.driver()-> + sqlStatement(QSqlDriver::UpdateStatement, "WeekScores", record, false) + + where.arg(activity).arg(year).arg(weekNumber).arg(location) + ); + PRINT_LAST_ERROR(database); + } + } + } + + #undef fordate +} + +void ActivityRanking::Private::processMonthData(const QString & activity, const QString & location, qint64 start, qint64 end) +{ + val startDateTime = QDateTime::fromMSecsSinceEpoch(start); + val endDateTime = QDateTime::fromMSecsSinceEpoch(end); + + #define fordate(What, Start, End) \ + for (int What = Start.date().What(); What <= End.date().What(); What++) + + // This can be a bit more efficient + fordate(year, startDateTime, endDateTime) { + fordate(month, startDateTime, endDateTime) { + qDebug() << activity << year << month; + + ensureMonthScoreExists(activity, year, month, location); + + val monthStartDateTime = QDateTime(QDate(year, month, 1)); + val monthStart = monthStartDateTime.toMSecsSinceEpoch(); + val monthEnd = monthStartDateTime.addMonths(1).toMSecsSinceEpoch(); + + val currentStart = qMax(monthStart, start); + val currentEnd = qMin(monthEnd, end); + + val coefStart = (currentStart - monthStart) / (qreal) (monthEnd - monthStart) * 64; + val coefEnd = (currentEnd - monthStart) / (qreal) (monthEnd - monthStart) * 64; + + val iStart = ceil(coefStart); + val iEnd = floor(coefEnd); + + auto query = database.exec( + selectMonthScore + .arg(activity) + .arg(year) + .arg(month) + .arg(location) + ); + PRINT_LAST_ERROR(database); + + #define increaseValue(Index, Addition) \ + record.setValue(Index, record.value(Index).toDouble() + Addition) + + if (query.next()) { + auto record = query.record(); + val iFirstColumn = record.indexOf("s00"); + + if (iStart > 0) { + increaseValue(iFirstColumn + iStart - 1, iStart - coefStart); + } + + for (int i = iStart; i < iEnd; i++) { + increaseValue(iFirstColumn + i, 1.0); + } + + if (iEnd + 1 < 64) { + increaseValue(iFirstColumn + iEnd, coefEnd - iEnd); + } + + static val & where = QString::fromLatin1(" WHERE activity = '%1' AND year = %2 AND month = %3 AND location = '%4'"); + + database.exec(database.driver()-> + sqlStatement(QSqlDriver::UpdateStatement, "MonthScores", record, false) + + where.arg(activity).arg(year).arg(month).arg(location) + ); + PRINT_LAST_ERROR(database); + } + + #undef increaseValue + } + } + + #undef fordate + +} + +void ActivityRanking::Private::closeDanglingActivityRecords() +{ + qDebug() << "closing..."; + + // TODO: A possible problem is that theoretically the dangling ones can be + // before a non dangling one which will produce overlapping + + QSqlTableModel tableActivityEvents(nullptr, database); + tableActivityEvents.setTable("ActivityEvents"); + tableActivityEvents.setFilter("end IS NULL"); + tableActivityEvents.select(); + + // Setting the current time as the end of the last dangling event + val i = tableActivityEvents.rowCount() - 1; + qDebug() << "dangling count:" << i+1; + + if (i < 0) return; + + auto record = tableActivityEvents.record(i); + + record.setValue("end", QDateTime::currentMSecsSinceEpoch()); + tableActivityEvents.setRecord(i, record); + + // Setting the start of one event to be the end of the previous + // one + + auto end = record.value("start").toLongLong(); + + for (int i = tableActivityEvents.rowCount() - 2; i >= 0; i--) { + record = tableActivityEvents.record(i); + + record.setValue("end", end); + end = record.value("start").toLongLong(); + + processActivityInterval( + record.value("activity").toString(), + record.value("location").toString(), + end, // record.value("start").toLongLong(), + record.value("end").toLongLong() + ); + + tableActivityEvents.setRecord(i, record); + } + + tableActivityEvents.submitAll(); + +} + +ActivityRanking::ActivityRanking(QObject * parent) + : QObject(parent), d() +{ +} + +void ActivityRanking::init(QObject * activities) +{ + new ActivityRankingAdaptor(this); + KDBusConnectionPool::threadConnection().registerObject("/ActivityRanking", this); + + val path = KStandardDirs::locateLocal("data", "activitymanager/activityranking/database", true); + + d->database = QSqlDatabase::addDatabase("QSQLITE", "plugins_activityranking_db"); + d->database.setDatabaseName(path); + + if (!d->database.open()) { + qDebug() << "Can't open sqlite database" << d->database.lastError() << path; + return; + } + + initDatabaseSchema(); + + d->closeDanglingActivityRecords(); + + activityChanged(Plugin::callOn (activities, "CurrentActivity", "QString")); + connect(activities, SIGNAL(CurrentActivityChanged(QString)), + this, SLOT(activityChanged(QString)), + Qt::QueuedConnection); + connect(Location::self(this), SIGNAL(currentChanged(QString)), + this, SLOT(locationChanged(QString)), + Qt::QueuedConnection); +} + +#define ACTIVITYRANKING_SCHEMA_VERSION "1.0" +void ActivityRanking::initDatabaseSchema() +{ + bool schemaUpToDate = false; + + auto query = d->database.exec("SELECT value FROM SchemaInfo WHERE key = 'version'"); + if (query.next()) { + schemaUpToDate = (ACTIVITYRANKING_SCHEMA_VERSION == query.value(0).toString()); + } + + if (!schemaUpToDate) { + // We just need to initialize - will handle updates if needed in post 1.0 + + query.exec("CREATE TABLE IF NOT EXISTS SchemaInfo (key text PRIMARY KEY, value text)"); + + query.exec(Private::insertSchemaInfo.arg("version", ACTIVITYRANKING_SCHEMA_VERSION)); + + // flags + // 1 - active segment + // 2 - in the same week as an active segment + // 4 - next to an active segment + + query.exec("CREATE TABLE IF NOT EXISTS WeekScores (" + "activity text, year int, week int, " + "s00 double default 0, s01 double default 0, s02 double default 0, s03 double default 0, s04 double default 0, s05 double default 0, s06 double default 0, s07 double default 0, " + "s10 double default 0, s11 double default 0, s12 double default 0, s13 double default 0, s14 double default 0, s15 double default 0, s16 double default 0, s17 double default 0, " + "s20 double default 0, s21 double default 0, s22 double default 0, s23 double default 0, s24 double default 0, s25 double default 0, s26 double default 0, s27 double default 0, " + "s30 double default 0, s31 double default 0, s32 double default 0, s33 double default 0, s34 double default 0, s35 double default 0, s36 double default 0, s37 double default 0, " + "s40 double default 0, s41 double default 0, s42 double default 0, s43 double default 0, s44 double default 0, s45 double default 0, s46 double default 0, s47 double default 0, " + "s50 double default 0, s51 double default 0, s52 double default 0, s53 double default 0, s54 double default 0, s55 double default 0, s56 double default 0, s57 double default 0, " + "s60 double default 0, s61 double default 0, s62 double default 0, s63 double default 0, s64 double default 0, s65 double default 0, s66 double default 0, s67 double default 0, " + "f0 int default 0, " + "f1 int default 0, " + "f2 int default 0, " + "f3 int default 0, " + "f4 int default 0, " + "f5 int default 0, " + "f6 int default 0, " + "location text default NULL, " + "PRIMARY KEY(activity, year, week, location)" + ")"); + + + // flags - nothing yet :) + query.exec("CREATE TABLE IF NOT EXISTS MonthScores (" + "activity text, year int, month int, " + "s00 double default 0, s01 double default 0, s02 double default 0, s03 double default 0, s04 double default 0, s05 double default 0, s06 double default 0, s07 double default 0, " + "s10 double default 0, s11 double default 0, s12 double default 0, s13 double default 0, s14 double default 0, s15 double default 0, s16 double default 0, s17 double default 0, " + "s20 double default 0, s21 double default 0, s22 double default 0, s23 double default 0, s24 double default 0, s25 double default 0, s26 double default 0, s27 double default 0, " + "s30 double default 0, s31 double default 0, s32 double default 0, s33 double default 0, s34 double default 0, s35 double default 0, s36 double default 0, s37 double default 0, " + "s40 double default 0, s41 double default 0, s42 double default 0, s43 double default 0, s44 double default 0, s45 double default 0, s46 double default 0, s47 double default 0, " + "s50 double default 0, s51 double default 0, s52 double default 0, s53 double default 0, s54 double default 0, s55 double default 0, s56 double default 0, s57 double default 0, " + "s60 double default 0, s61 double default 0, s62 double default 0, s63 double default 0, s64 double default 0, s65 double default 0, s66 double default 0, s67 double default 0, " + "s70 double default 0, s71 double default 0, s72 double default 0, s73 double default 0, s74 double default 0, s75 double default 0, s76 double default 0, s77 double default 0, " + "f00 int default 0, " + "location text default NULL, " + "PRIMARY KEY(activity, year, month, location)" + ")"); + + query.exec("CREATE TABLE IF NOT EXISTS ActivityEvents (activity text, location text, start bigint, end bigint DEFAULT NULL)"); + } +} + +ActivityRanking::~ActivityRanking() +{ + d->database.close(); +} + +void ActivityRanking::activityChanged(const QString & activity) +{ + // activity in limbo state, ignore it. + if (activity.isEmpty()) { + return; + } + + qint64 currentTime = QDateTime::currentMSecsSinceEpoch(); + + qDebug() << ">>>> we have the new activity" << activity; + + if (!d->activity.isEmpty()) { + d->database.exec( + Private::closeActivityInterval + .arg(currentTime) + .arg(d->activity) + ); + PRINT_LAST_ERROR(d->database); + + d->processActivityInterval(d->activity, d->lastLocation, d->activityStart, currentTime); + } + + d->activity = activity; + d->lastLocation = Location::self(this)->current(); + d->activityStart = currentTime; + + d->database.exec( + Private::insertActivityInterval + .arg(activity) + .arg(d->lastLocation) + .arg(currentTime) + ); + PRINT_LAST_ERROR(d->database); + emit rankingChanged(topActivities(), activities()); +} + +void ActivityRanking::locationChanged(const QString &location) +{ + qint64 currentTime = QDateTime::currentMSecsSinceEpoch(); + + qDebug() << ">>>> we have the new location" << location; + + if (!d->activity.isEmpty()) { + d->database.exec( + Private::closeActivityInterval + .arg(currentTime) + .arg(d->activity) + ); + PRINT_LAST_ERROR(d->database); + + d->processActivityInterval(d->activity, d->lastLocation, d->activityStart, currentTime); + } + + d->lastLocation = location; + + d->database.exec( + Private::insertActivityInterval + .arg(d->activity) + .arg(d->lastLocation) + .arg(currentTime) + ); + PRINT_LAST_ERROR(d->database); + emit rankingChanged(topActivities(), activities()); +} + + +QStringList ActivityRanking::topActivities() +{ + return d->topActivitiesFor(QDateTime::currentDateTime(), d->lastLocation).keys(); +} + +QMap ActivityRanking::Private::topActivitiesFor(const QDateTime & time, const QString & location) +{ + QMap result; + + // We want to get the scores for the current week segment + val monthStartDateTime = QDateTime(QDate(time.date().year(), time.date().month(), 1)); + val monthStart = monthStartDateTime.toMSecsSinceEpoch(); + val monthEnd = monthStartDateTime.addMonths(1).toMSecsSinceEpoch(); + + // TODO: This should be tested - something is wrong here + const int coefStart = (time.toMSecsSinceEpoch() - monthStart) / (qreal) (monthEnd - monthStart) * 64; + + auto monthSegment = QString::number(coefStart, 8); + if (monthSegment.size() == 1) + monthSegment = '0' + monthSegment; + + auto query = database.exec( + selectScore + .arg(time.date().dayOfWeek() - 1) + .arg(time.time().hour() / 3) + .arg(monthSegment) + .arg(location) + ); + PRINT_LAST_ERROR(database); + + while (query.next()) { + val & record = query.record(); + result[record.value(0).toString()] = record.value(1).toDouble(); + } + + return result; +} + +QList < ActivityData > ActivityRanking::activities() +{ + QList < ActivityData > result; + + val topActivities = d->topActivitiesFor(QDateTime::currentDateTime(), d->lastLocation); + + kamd::utils::for_each_assoc(topActivities, + [&result](const QString & activity, qreal score) { + ActivityData data; + data.id = activity; + data.score = score; + result.append(data); + } + ); + + return result; +} + +#include "ActivityRanking.moc" diff --git a/kactivities/src/service/plugins/activityranking/ActivityRanking.h b/kactivities/src/service/plugins/activityranking/ActivityRanking.h new file mode 100644 index 00000000..0166c40f --- /dev/null +++ b/kactivities/src/service/plugins/activityranking/ActivityRanking.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINS_ACTIVITY_RANKING_ACTIVITY_RANKING_THREAD_H +#define PLUGINS_ACTIVITY_RANKING_ACTIVITY_RANKING_THREAD_H + +#include +#include +#include + +#include "ActivityData.h" + +#include +#include +#include + +class ActivityRanking: public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.ActivityManager.ActivityRanking") + +public: + explicit ActivityRanking(QObject * parent = nullptr); + ~ActivityRanking(); + + void init(QObject * activities); + +public Q_SLOTS: + /** + * Lists top activities based on score calculation described in scoring.pdf. + * All scores are related to the current location + * + * @return list of activities ids. + */ + QStringList topActivities(); + + /** + * @return list of activities data with their rank in the context of the current location. + */ + QList activities(); + +Q_SIGNALS: + void rankingChanged(const QStringList & topActivities, const ActivityDataList & activities); + +protected: + void initDatabaseSchema(); + +private Q_SLOTS: + void activityChanged(const QString & activity); + void locationChanged(const QString & location); + +private: + D_PTR; +}; + +#endif // PLUGINS_ACTIVITY_RANKING_ACTIVITY_RANKING_THREAD_H + diff --git a/kactivities/src/service/plugins/activityranking/ActivityRankingPlugin.cpp b/kactivities/src/service/plugins/activityranking/ActivityRankingPlugin.cpp new file mode 100644 index 00000000..57a22f2d --- /dev/null +++ b/kactivities/src/service/plugins/activityranking/ActivityRankingPlugin.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic ivan.cukic(at)kde.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ActivityRankingPlugin.h" +#include "ActivityRanking.h" + +#include + +#include + +#include + +class ActivityRankingPlugin::Private { +public: + ActivityRanking * ranking; + QThread * rankingThread; +}; + +ActivityRankingPlugin::ActivityRankingPlugin(QObject * parent, const QVariantList & args) + : Plugin(parent) +{ + Q_UNUSED(args) +} + +bool ActivityRankingPlugin::init(const QHash < QString, QObject * > & modules) +{ + d->ranking = new ActivityRanking(); + d->ranking->init(modules["activities"]); + + class Thread: public QThread { + public: + Thread(ActivityRanking * ptr = nullptr) + : QThread(), object(ptr) + { + } + + void run() _override + { + std::unique_ptr o(object); + exec(); + } + + private: + ActivityRanking * object; + + } * thread = new Thread(d->ranking); + + d->rankingThread = thread; + d->ranking->moveToThread(thread); + thread->start(); + + qDebug() << "running in thread" << d->ranking->metaObject()->className(); + + return true; +} + +ActivityRankingPlugin::~ActivityRankingPlugin() +{ + d->rankingThread->exit(); + d->rankingThread->wait(); +} + +KAMD_EXPORT_PLUGIN(ActivityRankingPlugin, "activitymanger_plugin_activityranking") + +#include "ActivityRankingPlugin.moc" diff --git a/kactivities/src/service/plugins/activityranking/ActivityRankingPlugin.h b/kactivities/src/service/plugins/activityranking/ActivityRankingPlugin.h new file mode 100644 index 00000000..a7f48c2a --- /dev/null +++ b/kactivities/src/service/plugins/activityranking/ActivityRankingPlugin.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINS_ACTIVITY_RANKING_ACTIVITY_RANKING_PLUGIN_H +#define PLUGINS_ACTIVITY_RANKING_ACTIVITY_RANKING_PLUGIN_H + +#include + +#include +#include +#include + +class ActivityRankingPlugin: public Plugin +{ + Q_OBJECT + +public: + explicit ActivityRankingPlugin(QObject *parent = nullptr, const QVariantList & args = QVariantList()); + ~ActivityRankingPlugin(); + + virtual bool init(const QHash < QString, QObject * > & modules) _override; + +private: + D_PTR; +}; + +#endif // PLUGINS_ACTIVITY_RANKING_ACTIVITY_RANKING_PLUGIN_H + diff --git a/kactivities/src/service/plugins/activityranking/CMakeLists.txt b/kactivities/src/service/plugins/activityranking/CMakeLists.txt new file mode 100644 index 00000000..8af23a6d --- /dev/null +++ b/kactivities/src/service/plugins/activityranking/CMakeLists.txt @@ -0,0 +1,44 @@ +project (activitymanager-plugin-activityranking) + +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_BINARY_DIR} + ${KDE4_INCLUDES} + ) + +set ( + activityranking_SRCS + ActivityRanking.cpp + ActivityRankingPlugin.cpp + ActivityData.cpp + Location.cpp + ${plugin_implementation_SRCS} + ) + +qt4_add_dbus_adaptor ( + activityranking_SRCS org.kde.ActivityManager.ActivityRanking.xml + ActivityRanking.h ActivityRanking + ) + +qt4_add_dbus_interface ( + activityranking_SRCS org.kde.LocationManager.xml + LocationManagerInterface + ) + +kde4_add_plugin ( + activitymanager_plugin_activityranking + ${activityranking_SRCS} + ) + +target_link_libraries ( + activitymanager_plugin_activityranking + ${QT_QTSQL_LIBRARIES} + ${KDE4_KDECORE_LIBS} + ) + +install (TARGETS activitymanager_plugin_activityranking DESTINATION ${PLUGIN_INSTALL_DIR}) +install (FILES activitymanager-plugin-activityranking.desktop DESTINATION ${SERVICES_INSTALL_DIR}) + diff --git a/kactivities/src/service/plugins/activityranking/Location.cpp b/kactivities/src/service/plugins/activityranking/Location.cpp new file mode 100644 index 00000000..e73d3bc4 --- /dev/null +++ b/kactivities/src/service/plugins/activityranking/Location.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "Location.h" +#include "LocationManagerInterface.h" + +#include +#include + +#include + +#include +#include +#include + +#define LOCATION_MANAGER_SERVICE "org.kde.LocationManager" +#define LOCATION_MANAGER_OBJECT "/LocationManager" + +class Location::Private { +public: + Private() + : manager(nullptr) + { + } + + ~Private() { + delete manager; + } + + org::kde::LocationManager * manager; + QString current; + QDBusServiceWatcher * watcher; + + static Location * s_instance; +}; + +Location * Location::Private::s_instance = nullptr; + +Location::Location(QObject * parent) + : QObject(parent), d() +{ + qDebug() << "Location object initializing"; + + d->watcher = new QDBusServiceWatcher( + LOCATION_MANAGER_SERVICE, + KDBusConnectionPool::threadConnection(), + QDBusServiceWatcher::WatchForRegistration + | QDBusServiceWatcher::WatchForUnregistration, + this); + + connect(d->watcher, SIGNAL(serviceRegistered(QString)), + this, SLOT(enable())); + connect(d->watcher, SIGNAL(serviceUnregistered(QString)), + this, SLOT(disable())); + + if (KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(LOCATION_MANAGER_SERVICE)) { + enable(); + } +} + +Location::~Location() +{ +} + +QString Location::current() const +{ + return d->current; +} + +Location * Location::self(QObject * parent) +{ + if (!Private::s_instance) { + Private:: s_instance = new Location(parent); + } + + return Private::s_instance; +} + +void Location::disable() +{ + d->current.clear(); + delete d->manager; +} + +void Location::enable() +{ + d->manager = new org::kde::LocationManager( + LOCATION_MANAGER_SERVICE, + LOCATION_MANAGER_OBJECT, + KDBusConnectionPool::threadConnection() + ); + + connect(d->manager, SIGNAL(currentLocationChanged(QString, QString)), + this, SLOT(setCurrent(QString))); + + d->current = d->manager->currentLocationId(); +} + +void Location::setCurrent(const QString & location) +{ + d->current = location; + emit currentChanged(location); +} + + diff --git a/kactivities/src/service/plugins/activityranking/Location.h b/kactivities/src/service/plugins/activityranking/Location.h new file mode 100644 index 00000000..f5b26414 --- /dev/null +++ b/kactivities/src/service/plugins/activityranking/Location.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PLUGINS_ACTIVITY_RANKING_LOCATION_H +#define PLUGINS_ACTIVITY_RANKING_LOCATION_H + +#include +#include + +#include + +/** + * Location + */ +class Location: public QObject { + Q_OBJECT + +public: + static Location * self(QObject * parent); + + virtual ~Location(); + + Q_SIGNALS: + void currentChanged(const QString &location); + +public: + QString current() const; + +protected Q_SLOTS: + void enable(); + void disable(); + void setCurrent(const QString & location); + +private: + Location(QObject * parent); + + D_PTR; +}; + + +#endif // PLUGINS_ACTIVITY_RANKING_LOCATION_H + diff --git a/kactivities/src/service/plugins/activityranking/activitymanager-plugin-activityranking.desktop b/kactivities/src/service/plugins/activityranking/activitymanager-plugin-activityranking.desktop new file mode 100644 index 00000000..bc256100 --- /dev/null +++ b/kactivities/src/service/plugins/activityranking/activitymanager-plugin-activityranking.desktop @@ -0,0 +1,108 @@ +[Desktop Entry] +Name=Activity ranking +Name[bs]=Rangiranje aktivnosti +Name[ca]=Ordenació d'activitats +Name[ca@valencia]=Ordenació d'activitats +Name[cs]=Hodnocení aktivit +Name[da]=Aktivitetsrangering +Name[de]=Aktivitätenbewertung +Name[el]=Αξιολόγηση δραστηριοτήτων +Name[en_GB]=Activity ranking +Name[es]=Puntuación de las actividades +Name[et]=Tegevuste hindamine +Name[eu]=Jardueren sailkapena +Name[fi]=Aktiviteettien luokittaminen +Name[fr]=Classement d'activités +Name[gl]=Clasificación das actividades +Name[he]=דירוג פעילות +Name[hu]=Aktivitásértékelő +Name[ia]=Classifica de activitate +Name[it]=Classificazione delle attività +Name[kk]=Белсенділікті бағалау +Name[km]=កម្មវិធី​ជំនួយ​កំណត់​ជួរ​សកម្មភាព +Name[ko]=활동 랭킹 +Name[lt]=Veiklos vertinimas +Name[mr]=कार्यपध्दती गुणवत्ताश्रेणी +Name[nb]=Aktivitetsrangering +Name[nds]=Aktiviteten-Beweerten +Name[nl]=Activiteitenwaardering +Name[pa]=ਐਕਟਵਿਟੀ ਰੈਕਿੰਗ +Name[pl]=Ranking działań +Name[pt]=Classificação da actividade +Name[pt_BR]=Classificação de atividades +Name[ro]=Evaluarea activităților +Name[ru]=Рейтинг комнат +Name[sk]=Hodnotenie aktivít +Name[sl]=Razvrščanje dejavnosti +Name[sr]=Рангирање активности +Name[sr@ijekavian]=Рангирање активности +Name[sr@ijekavianlatin]=Rangiranje aktivnosti +Name[sr@latin]=Rangiranje aktivnosti +Name[sv]=Aktivitetsrankning +Name[tg]=Баҳодиҳии фаъолият +Name[tr]=Etkinlik sıralaması +Name[ug]=پائالىيەت قاتارى +Name[uk]=Оцінювання просторів дій +Name[x-test]=xxActivity rankingxx +Name[zh_CN]=活动评分 +Name[zh_TW]=活動排序 +Comment=Plugin to rank activities based on usage +Comment[bs]=Dodatak za rangiranje aktivnosti prema njihovoj uoptrebi +Comment[ca]=Connector per ordenar les activitats segons l'ús +Comment[ca@valencia]=Connector per ordenar les activitats segons l'ús +Comment[cs]=Modul pro hodnocení aktivit dle jejich používání +Comment[da]=Plugin til at rangere aktiviteter baseret på brug +Comment[de]=Modul, das Aktivitäten anhand der Benutzung bewertet +Comment[el]=Πρόσθετο για την αξιολόγηση δραστηριοτήτων με βάση τη χρήση +Comment[en_GB]=Plugin to rank activities based on usage +Comment[es]=Complemento para puntuar actividades basándose en su uso +Comment[et]=Plugin tegevuste hindamiseks kasutuse põhjal +Comment[eu]=Jarduerak erabileraren arabera sailkatzeko plugina +Comment[fi]=Liitännäinen aktiviteettien luokittamiseen käytön mukaan +Comment[fr]=Module externe pour classer les activités en fonction de leur utilisation +Comment[gl]=Un complemento para clasificar as actividades segundo o uso +Comment[he]=תוסיף לדירוג פעילויות בהתאם לשימוש +Comment[hu]=Bővítmény aktivitások értékeléséhez a használat alapján +Comment[ia]=Plugin pro classificar activitates per lor usage +Comment[it]=Estensione per classificare le attività a seconda dell'uso +Comment[kk]=Белсенділікті пайдалану негізінде бағалау плагині +Comment[km]=កម្មវិធី​ជំនួយ​ត្រូវ​កំណត់​ជួរ​សកម្មភាព​មាន​មូលដ្ឋាន​លើ​ការ​ប្រើប្រាស់ +Comment[ko]=활동 사용량에 따라 별점을 매기는 플러그인 +Comment[lt]=Priedas vertinti veiklas pagal naudojimą +Comment[mr]=वापरानुसार कार्यपध्दतींना श्रेणी देणारे प्लगइन +Comment[nb]=Programtillegg som rangerer aktiviteter etter bruk +Comment[nds]=Moduul för't Beweerten vun Aktiviteten na ehr Bruuk +Comment[nl]=Plug-in om activiteiten te waarderen gebaseerd op gebruik +Comment[pl]=Wtyczka do oceny działań w oparciu o użycie +Comment[pt]='Plugin' de classificação das actividades com base na utilização +Comment[pt_BR]=Plugin para classificação das atividades com base na utilização +Comment[ro]=Extensie pentru evaluarea activităților în funcție de utilizarea acestora +Comment[ru]=Модуль для определения рейтинга комнат по их использованию +Comment[sk]=Plugin na hodnotenie aktivít založený na použití +Comment[sl]=Vstavek za razvrstitev dejavnosti glede na uporabo +Comment[sr]=Прикључак за рангирање активности на основу употребе +Comment[sr@ijekavian]=Прикључак за рангирање активности на основу употребе +Comment[sr@ijekavianlatin]=Priključak za rangiranje aktivnosti na osnovu upotrebe +Comment[sr@latin]=Priključak za rangiranje aktivnosti na osnovu upotrebe +Comment[sv]=Insticksprogram för rankning av aktiviteter baserat på användning +Comment[tr]=Etkinlikleri kullanımlarına göre sıralama eklentisi +Comment[ug]=پائالىيەت قاتارى ئاساسىدا ئىشلىتىدىغان قىستۇرما +Comment[uk]=Додаток для оцінювання просторів дій на основі даних щодо використання +Comment[x-test]=xxPlugin to rank activities based on usagexx +Comment[zh_CN]=根据使用情况对活动评分的插件 +Comment[zh_TW]=以用量來排序活動的外掛程式 +Type=Service +Icon=flag + +X-KDE-ServiceTypes=ActivityManager/Plugin +X-KDE-Library=activitymanager_plugin_activityranking +X-KDE-PluginInfo-Author=Ivan Cukic +X-KDE-PluginInfo-Email=ivan.cukic@kde.org +X-KDE-PluginInfo-Name=org.kde.kactivitymanager.activityranking +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-Website=http://plasma.kde.org/ +X-KDE-PluginInfo-Category= +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false + diff --git a/kactivities/src/service/plugins/activityranking/org.kde.ActivityManager.ActivityRanking.xml b/kactivities/src/service/plugins/activityranking/org.kde.ActivityManager.ActivityRanking.xml new file mode 100644 index 00000000..f516d361 --- /dev/null +++ b/kactivities/src/service/plugins/activityranking/org.kde.ActivityManager.ActivityRanking.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kactivities/src/service/plugins/activityranking/org.kde.LocationManager.xml b/kactivities/src/service/plugins/activityranking/org.kde.LocationManager.xml new file mode 100644 index 00000000..60126be4 --- /dev/null +++ b/kactivities/src/service/plugins/activityranking/org.kde.LocationManager.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kactivities/src/service/plugins/globalshortcuts/CMakeLists.txt b/kactivities/src/service/plugins/globalshortcuts/CMakeLists.txt new file mode 100644 index 00000000..8850fa43 --- /dev/null +++ b/kactivities/src/service/plugins/globalshortcuts/CMakeLists.txt @@ -0,0 +1,32 @@ +project (activitymanager-globalshortcuts) + +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_BINARY_DIR} + ${KDE4_INCLUDES} + ) + +set ( + globalshortcuts_SRCS + GlobalShortcutsPlugin.cpp + ${plugin_implementation_SRCS} + ) + +kde4_add_plugin ( + activitymanager_plugin_globalshortcuts + ${globalshortcuts_SRCS} + ) + +target_link_libraries ( + activitymanager_plugin_globalshortcuts + ${KDE4_KDECORE_LIBS} + ${KDE4_KDEUI_LIBS} + ${KACTIVITIES_LIBS} + ) + +install (TARGETS activitymanager_plugin_globalshortcuts DESTINATION ${PLUGIN_INSTALL_DIR}) +install (FILES activitymanager-plugin-globalshortcuts.desktop DESTINATION ${SERVICES_INSTALL_DIR}) + diff --git a/kactivities/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.cpp b/kactivities/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.cpp new file mode 100644 index 00000000..cd4eb334 --- /dev/null +++ b/kactivities/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * Copyright (C) 2012 Makis Marimpis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "GlobalShortcutsPlugin.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include + +val objectNamePattern = QString::fromLatin1("switch-to-activity-%1"); +val objectNamePatternLength = objectNamePattern.length() - 2; + +GlobalShortcutsPlugin::GlobalShortcutsPlugin(QObject * parent, const QVariantList & args) + : Plugin(parent), + m_activitiesService(nullptr), + m_signalMapper(new QSignalMapper(this)), + m_actionCollection(new KActionCollection(this)) +{ + Q_UNUSED(args) +} + +GlobalShortcutsPlugin::~GlobalShortcutsPlugin() +{ + m_actionCollection->clear(); +} + +bool GlobalShortcutsPlugin::init(const QHash < QString, QObject * > & modules) +{ + m_activitiesService = modules["activities"]; + + val activitiesList = Plugin::callOn (m_activitiesService, "ListActivities", "QStringList"); + + foreach (val & activity, activitiesList) { + activityAdded(activity); + } + + connect( + m_signalMapper, SIGNAL(mapped(QString)), + m_activitiesService, SLOT(SetCurrentActivity(QString)), + Qt::QueuedConnection + ); + + m_actionCollection->readSettings(); + + foreach (val & action, m_actionCollection->actions()) { + if (!activitiesList.contains(action->objectName().mid(objectNamePatternLength))) { + m_actionCollection->removeAction(action); + } + } + + m_actionCollection->writeSettings(); + + return true; +} + +void GlobalShortcutsPlugin::activityAdded(const QString & activity) +{ + val action = m_actionCollection->addAction( + objectNamePattern.arg(activity) + ); + + action->setText(i18nc("@action", "Switch to activity \"%1\"", activityName(activity))); + action->setGlobalShortcut(KShortcut()); + + connect(action, SIGNAL(triggered()), m_signalMapper, SLOT(map())); + m_signalMapper->setMapping(action, activity); + + m_actionCollection->writeSettings(); +} + +void GlobalShortcutsPlugin::activityRemoved(const QString & activity) +{ + foreach (val & action, m_actionCollection->actions()) { + if (activity == action->objectName().mid(objectNamePatternLength)) { + m_actionCollection->removeAction(action); + } + } + + m_actionCollection->writeSettings(); +} + +void GlobalShortcutsPlugin::activityChanged(const QString & activity) +{ + foreach (val & action, m_actionCollection->actions()) { + if (activity == action->objectName().mid(objectNamePatternLength)) { + action->setText(i18nc("@action", "Switch to activity \"%1\"", activityName(activity))); + } + } +} + +QString GlobalShortcutsPlugin::activityName(const QString & activity) const +{ + return Plugin::callOnWithArgs ( + m_activitiesService, "ActivityName", "QString", + Q_ARG(QString, activity) + ); +} + +KAMD_EXPORT_PLUGIN(GlobalShortcutsPlugin, "activitymanager_plugin_globalshortcuts") + diff --git a/kactivities/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.h b/kactivities/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.h new file mode 100644 index 00000000..cb612c54 --- /dev/null +++ b/kactivities/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 Makis Marimpis + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINS_GLOBAL_SHORTCUTS_PLUGIN_H +#define PLUGINS_GLOBAL_SHORTCUTS_PLUGIN_H + +#include + +#include + +class QSignalMapper; +class KActionCollection; + +class GlobalShortcutsPlugin: public Plugin +{ + Q_OBJECT + +public: + GlobalShortcutsPlugin(QObject * parent, const QVariantList & args); + virtual ~GlobalShortcutsPlugin(); + + virtual bool init(const QHash < QString, QObject * > & modules) _override; + +private Q_SLOTS: + void activityAdded(const QString & activity); + void activityRemoved(const QString & activity); + void activityChanged(const QString & activity); + +private: + inline QString activityName(const QString & activity) const; + + QObject * m_activitiesService; + QSignalMapper * m_signalMapper; + KActionCollection * m_actionCollection; +}; + +#endif // PLUGINS_GLOBAL_SHORTCUTS_PLUGIN_H diff --git a/kactivities/src/service/plugins/globalshortcuts/Messages.sh b/kactivities/src/service/plugins/globalshortcuts/Messages.sh new file mode 100644 index 00000000..d2cda809 --- /dev/null +++ b/kactivities/src/service/plugins/globalshortcuts/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp -o $podir/activitymanager_plugin_globalshortcuts.pot diff --git a/kactivities/src/service/plugins/globalshortcuts/activitymanager-plugin-globalshortcuts.desktop b/kactivities/src/service/plugins/globalshortcuts/activitymanager-plugin-globalshortcuts.desktop new file mode 100644 index 00000000..91351be9 --- /dev/null +++ b/kactivities/src/service/plugins/globalshortcuts/activitymanager-plugin-globalshortcuts.desktop @@ -0,0 +1,109 @@ +[Desktop Entry] +Name=Global Shortcuts +Name[bs]=Globalne kratice +Name[ca]=Dreceres globals +Name[ca@valencia]=Dreceres globals +Name[cs]=Globální zkratky +Name[da]=Globale genveje +Name[de]=Globale Kurzbefehle +Name[el]=Καθολικές συντομεύσεις +Name[en_GB]=Global Shortcuts +Name[es]=Accesos rápidos de teclado globales +Name[et]=Globaalsed kiirklahvid +Name[eu]=Lasterbide orokorrak +Name[fi]=Työpöydänlaajuiset pikanäppäimet +Name[fr]=Raccourcis globaux +Name[gl]=Atallos globais +Name[he]=קישורים גלובאלים +Name[hu]=Globális billentyűparancsok +Name[ia]=Vias breve Global +Name[is]=Altækir flýtihnappar +Name[it]=Scorciatoie globali +Name[kk]=Жалпы жүйелік тіркесімдер +Name[km]=​ផ្លូវកាត់​សកល +Name[ko]=전역 단축키 +Name[lt]=Globalieji spartieji klavišai +Name[mr]=जागतिक शॉर्टकट्स +Name[nb]=Globale snarveier +Name[nds]=Globaal Tastkombinatschonen +Name[nl]=Globale sneltoetsen +Name[pa]=ਗਲੋਬਲ ਸ਼ਾਰਟਕੱਟ +Name[pl]=Skróty globalne +Name[pt]=Atalhos Globais +Name[pt_BR]=Atalhos globais +Name[ro]=Acceleratori globali +Name[ru]=Глобальные комбинации клавиш +Name[sk]=Globálne skratky +Name[sl]=Splošne bližnjice +Name[sr]=Глобалне пречице +Name[sr@ijekavian]=Глобалне пречице +Name[sr@ijekavianlatin]=Globalne prečice +Name[sr@latin]=Globalne prečice +Name[sv]=Globala genvägar +Name[tg]=Миёнбурҳои умумӣ +Name[tr]=Genel Kısayollar +Name[ug]=ئومۇمىيەت تېزلەتمە +Name[uk]=Загальні клавіатурні скорочення +Name[x-test]=xxGlobal Shortcutsxx +Name[zh_CN]=全局快捷键 +Name[zh_TW]=全域捷徑 +Comment=Adds global keyboard shortcuts for activity switching +Comment[bs]=Dodaje globalne kratice tastature za prebacivanje aktivnosti +Comment[ca]=Afegeix les dreceres de teclat globals per canvi d'activitats +Comment[ca@valencia]=Afig les dreceres de teclat globals per canvi d'activitats +Comment[cs]=Přidává globální klávesové zkratky pro přepínání aktivit +Comment[da]=Tilføjer globale tastaturgenveje til skift af aktivitet +Comment[de]=Fügt globale Tastatur-Kurzbefehle für das Wechseln von Aktivitäten hinzu +Comment[el]=Προσθέτει καθολικές συντομεύσεις πληκτρολογίου για εναλλαγή δραστηριοτήτων +Comment[en_GB]=Adds global keyboard shortcuts for activity switching +Comment[es]=Añade accesos rápidos de teclado globales para el cambio de actividad +Comment[et]=Globaalsete kiirklahvide lisamine tegevuste vahetamiseks +Comment[eu]=Teklatuaren lasterbide orokorrak eransten ditu jardueren artean aldatzeko +Comment[fi]=Lisää työpöydänlaajuiset pikanäppäimet aktiviteettien vaihtamiseen +Comment[fr]=Ajoute des raccourcis clavier globaux pour le changement d'activités +Comment[gl]=Engade atallos de teclado globais para trocar de actividade +Comment[he]=מוסיף קיצורי מקשים גלובאלים למעבר בין פעילויות +Comment[hu]=Globális gyorsbillentyűk hozzáadása az aktivitás váltóhoz +Comment[ia]=Adde vias breve de claviero global pro commutation de activitate +Comment[it]=Aggiunge scorciatoie della tastiera globali per cambiare attività +Comment[kk]=Белсенділік ауыстыру жалпы жүйелік перне тіркесімдерін қосу +Comment[km]=បន្ថែម​​​ផ្លូវកាត់​ក្ដារចុច​សកល​សម្រាប់​ការ​ប្ដូរ​សកម្ម +Comment[ko]=활동 전환을 위한 전역 단축키 추가 +Comment[lt]=Prideda globalius sparčiuosius klavišus veiklos perjungimui +Comment[mr]=कार्यपध्दती बदलण्याकरिता जागतिक कळफलक शॉर्टकट्स जोढतो +Comment[nb]=Legger til globale snarveistaster som bytter mellom aktiviteter +Comment[nds]=Föögt globaal Tastkombinatschonen för't Wesseln vun Aktiviteten to +Comment[nl]=Voegt globale sneltoetsen toe aan omschakelen van activiteit +Comment[pl]=Dodaje globalne skróty klawiaturowe do przełączania działań +Comment[pt]=Adiciona atalhos globais do teclado para a mudança de actividades +Comment[pt_BR]=Adiciona atalhos de teclado globais para alternar atividades +Comment[ro]=Adaugă acceleratori de tastatură globali pentru comutarea activităților +Comment[ru]=Определяет глобальные комбинации клавиш для переключения комнат +Comment[sk]=Pridá globálne klávesové skratky na prepínanie aktivít +Comment[sl]=Doda splošne tipkovne bližnjice za preklapljanje dejavnosti +Comment[sr]=Додаје глобалне пречице са тастатуре за мењање активности +Comment[sr@ijekavian]=Додаје глобалне пречице са тастатуре за мењање активности +Comment[sr@ijekavianlatin]=Dodaje globalne prečice sa tastature za menjanje aktivnosti +Comment[sr@latin]=Dodaje globalne prečice sa tastature za menjanje aktivnosti +Comment[sv]=Lägger till globala snabbtangenter för aktivitetsbyte +Comment[tr]=Etkinlik geçişi için genel klavye kısayolları ekler +Comment[ug]=پائالىيەت ئالماشتۇرۇش ئۈچۈن ئومۇمىيەت ھەرپتاختا قىسقا يوللىرىنى قوشىدۇ +Comment[uk]=Додає загальні клавіатурні скорочення для перемикання просторів дій +Comment[x-test]=xxAdds global keyboard shortcuts for activity switchingxx +Comment[zh_CN]=为活动切换添加全局键盘快捷键 +Comment[zh_TW]=新增活動切換的全域鍵盤捷徑 +Type=Service +Icon=configure-shortcuts + +X-KDE-ServiceTypes=ActivityManager/Plugin +X-KDE-Library=activitymanager_plugin_globalshortcuts +X-KDE-PluginInfo-Author=Makis Marimpis +X-KDE-PluginInfo-Email=makhsm@gmail.com +X-KDE-PluginInfo-Name=org.kde.kactivitymanager.globalshortcuts +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-Website=http://plasma.kde.org/ +X-KDE-PluginInfo-Category= +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true + diff --git a/kactivities/src/service/plugins/nepomuk/CMakeLists.txt b/kactivities/src/service/plugins/nepomuk/CMakeLists.txt new file mode 100644 index 00000000..e11fb0bb --- /dev/null +++ b/kactivities/src/service/plugins/nepomuk/CMakeLists.txt @@ -0,0 +1,49 @@ +project (activitymanager-plugin-nepomuk) + +set ( + nepomukplugin_SRCS + NepomukPlugin.cpp + NepomukCommon.cpp + ${plugin_implementation_SRCS} + ) + +qt4_add_dbus_adaptor ( + nepomukplugin_SRCS + ${CMAKE_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml + NepomukPlugin.h NepomukPlugin + ) + +soprano_add_ontology ( + nepomukplugin_SRCS + ${CMAKE_SOURCE_DIR}/src/ontologies/kao.trig + "KAO" "KDE::Vocabulary" "trig" + ) + +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/../.. + ${KDE4_INCLUDES} + ${SOPRANO_INCLUDE_DIR} + ${NEPOMUK_CORE_INCLUDE_DIR} + ) + +kde4_add_plugin ( + activitymanager_plugin_nepomuk + ${nepomukplugin_SRCS} + ) + +target_link_libraries ( + activitymanager_plugin_nepomuk + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${NEPOMUK_CORE_LIBRARY} + ${SOPRANO_LIBRARIES} + ) + +install (TARGETS activitymanager_plugin_nepomuk DESTINATION ${PLUGIN_INSTALL_DIR}) +install (FILES activitymanager-plugin-nepomuk.desktop DESTINATION ${SERVICES_INSTALL_DIR}) + diff --git a/kactivities/src/service/plugins/nepomuk/NepomukCommon.cpp b/kactivities/src/service/plugins/nepomuk/NepomukCommon.cpp new file mode 100644 index 00000000..4137b976 --- /dev/null +++ b/kactivities/src/service/plugins/nepomuk/NepomukCommon.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2010, 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "NepomukCommon.h" + +#include + +QUrl resourceForUrl(const QUrl & url) +{ + static val & query = QString::fromLatin1( + "select ?r where { " + "?r nie:url %1 . " + "} LIMIT 1"); + + Soprano::QueryResultIterator it = + Nepomuk::ResourceManager::instance()->mainModel()->executeQuery( + query.arg(Soprano::Node::resourceToN3(url)), Soprano::Query::QueryLanguageSparql); + + if (it.next()) { + return it[0].uri(); + + } else { + Nepomuk::Resource resource(url); + resource.setProperty(NIE::url(), url); + + // Add more data to + + return resource.uri(); + } +} + +QUrl resourceForId(const QString & resourceId, const QUrl & type) +{ + static val & _query = QString::fromLatin1( + "select ?r where { " + "?r a %1 . " + "?r nao:identifier %2 . " + "} LIMIT 1"); + + val & query = _query.arg( + /* %1 */ Soprano::Node::resourceToN3(type), + /* %2 */ Soprano::Node::literalToN3(resourceId) + ); + + Soprano::QueryResultIterator it = + Nepomuk::ResourceManager::instance()->mainModel()->executeQuery( + query, Soprano::Query::QueryLanguageSparql); + + if (it.next()) { + return it[0].uri(); + + } else { + Nepomuk::Resource agent(QUrl(), type); + agent.setProperty(NAO::identifier(), resourceId); + + return agent.uri(); + } +} + +void updateNepomukScore(const QString & activity, const QString & application, const QUrl & resource, qreal score) +{ +#ifdef NEPOMUK_STORE_RESOURCE_SCORES + Nepomuk::Resource scoreCache; + + // Selecting a ResourceScoreCache object that is assigned to the specified + // (activity, application, resource) triple + static val & _query = QString::fromLatin1("select ?r where { " + "?r a %1 . " + "?r kao:usedActivity %2 . " + "?r kao:initiatingAgent %3 . " + "?r kao:targettedResource %4 . " + "} LIMIT 1" + ); + + val query = _query.arg( + /* %1 */ resN3(KAO::ResourceScoreCache()), + /* %2 */ resN3(resourceForId(activity, KAO::Activity())), + /* %3 */ resN3(resourceForId(application, NAO::Agent())), + /* %4 */ resN3(resourceForUrl(resource)) + ); + + auto it = Nepomuk::ResourceManager::instance()->mainModel()->executeQuery(query, Soprano::Query::QueryLanguageSparql); + + // If it already exists - lucky us + // If it does not - we need to create a new one + if (it.next()) { + Nepomuk::Resource result(it[0].uri()); + it.close(); + + scoreCache = result; + + } else { + Nepomuk::Resource result(QUrl(), KAO::ResourceScoreCache()); + + result.setProperty(KAO::targettedResource(), resourceForUrl(resource)); + result.setProperty(KAO::initiatingAgent(), resourceForId(application, NAO::Agent())); + result.setProperty(KAO::usedActivity(), resourceForId(activity, KAO::Activity())); + + scoreCache = result; + } + + // If the score is strictly positive, we are saving it in nepomuk, + // otherwise we are deleting the score cache since the negative + // and zero scores have no use. + // This is (mis)used when clearing the usage history - the + // Scoring object will send us a score smaller than zero + if (score > 0) { + scoreCache.removeProperty(NAO::score()); + scoreCache.removeProperty(KAO::cachedScore()); + scoreCache.setProperty(KAO::cachedScore(), score); + + } else { + scoreCache.remove(); + + } +#endif +} + diff --git a/kactivities/src/service/plugins/nepomuk/NepomukCommon.h b/kactivities/src/service/plugins/nepomuk/NepomukCommon.h new file mode 100644 index 00000000..94b76a0c --- /dev/null +++ b/kactivities/src/service/plugins/nepomuk/NepomukCommon.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PLUGINS_SQLITE_NEPOMUK_COMMON_H +#define PLUGINS_SQLITE_NEPOMUK_COMMON_H + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "kao.h" + +namespace Nepomuk = Nepomuk2; +using namespace KDE::Vocabulary; +using namespace Nepomuk::Vocabulary; +using namespace Soprano::Vocabulary; + +void updateNepomukScore(const QString & activity, const QString & application, const QUrl & resource, qreal score); + +QUrl resourceForUrl(const QUrl & url); + +QUrl resourceForId(const QString & resourceId, const QUrl & type); + +inline QString resN3(const QUrl & uri) +{ + return Soprano::Node::resourceToN3(uri); +} + +inline Nepomuk::Resource activityResource(const QString & activity) +{ + Q_ASSERT(!activity.isEmpty()); + + return Nepomuk::Resource(activity, KAO::Activity()); +} + +#endif // PLUGINS_SQLITE_NEPOMUK_COMMON_H + diff --git a/kactivities/src/service/plugins/nepomuk/NepomukPlugin.cpp b/kactivities/src/service/plugins/nepomuk/NepomukPlugin.cpp new file mode 100644 index 00000000..b71fe2b6 --- /dev/null +++ b/kactivities/src/service/plugins/nepomuk/NepomukPlugin.cpp @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "NepomukPlugin.h" +#include "NepomukCommon.h" +#include "resourceslinkingadaptor.h" + +#include "../../Event.h" + +#include "kao.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace Nepomuk = Nepomuk2; +using namespace KDE::Vocabulary; + +class NepomukPlugin::Private { +public: + Private() + : nepomuk(nullptr), + activities(nullptr), + resourceScoring(nullptr), + nepomukPresent(false) + { + } + + void syncActivities(const QStringList & activities = QStringList()); + + template < typename Fn > + void doWithActivity(const QString & _activity, Fn fn) + { + qDebug() << "Doing something sinister with nepomuk, is it here? " << nepomukPresent; + + if (!nepomukPresent) return; + + val currentActivityId = Plugin::callOn (activities, "CurrentActivity", "QString"); + val activity = _activity.isEmpty() ? currentActivityId : _activity; + + qDebug() << "The current activity is: " << currentActivityId + << "We need to set the linking for: " << activity; + + // JIC checking that the service hasn't returned an empty activity + if (activity.isEmpty()) return; + + fn(activity); + + if (currentActivityId == activity) + org::kde::KDirNotify::emitFilesAdded("activities:/current"); + + org::kde::KDirNotify::emitFilesAdded("activities:/" + activity); + } + + void deleteFromQuery(const QString & query) + { + Soprano::QueryResultIterator it + = Nepomuk::ResourceManager::instance()->mainModel()->executeQuery(query, + Soprano::Query::QueryLanguageSparql); + + QList toDelete; + + while (it.next()) { + toDelete << it[0].uri(); + } + + it.close(); + + Nepomuk::removeResources(toDelete); + } + + Nepomuk::ResourceManager * nepomuk; + QObject * activities; + QObject * resourceScoring; + bool nepomukPresent; + + static NepomukPlugin * s_instance; + static QString protocol; +}; + +NepomukPlugin * NepomukPlugin::Private::s_instance = nullptr; +QString NepomukPlugin::Private::protocol = QLatin1String("activities:/"); + +NepomukPlugin::NepomukPlugin(QObject *parent, const QVariantList & args) + : Plugin(parent) +{ + Q_UNUSED(args) + Private::s_instance = this; + + setName("org.kde.ActivityManager.Nepomuk"); + + new ResourcesLinkingAdaptor(this); + + KDBusConnectionPool::threadConnection().registerObject("/ActivityManager/Resources/Linking", this); + KDBusConnectionPool::threadConnection().registerObject("/ActivityManager/Nepomuk", this); +} + +NepomukPlugin::~NepomukPlugin() +{ + Private::s_instance = nullptr; +} + +bool NepomukPlugin::init(const QHash < QString, QObject * > & modules) +{ + // Listening to changes in activities + d->activities = modules["activities"]; + + connect(d->activities, SIGNAL(ActivityAdded(QString)), + this, SLOT(addActivity(QString))); + + connect(d->activities, SIGNAL(ActivityRemoved(QString)), + this, SLOT(removeActivity(QString))); + + connect(d->activities, SIGNAL(ActivityNameChanged(QString, QString)), + this, SLOT(setActivityName(QString, QString))); + + connect(d->activities, SIGNAL(ActivityIconChanged(QString, QString)), + this, SLOT(setActivityIcon(QString, QString))); + + connect(d->activities, SIGNAL(CurrentActivityChanged(QString)), + this, SLOT(setCurrentActivity(QString))); + + // Listening to resource score changes + d->resourceScoring = modules["org.kde.ActivityManager.Resources.Scoring"]; + + connect(d->resourceScoring, SIGNAL(resourceScoreUpdated(QString, QString, QString, double)), + this, SLOT(resourceScoreUpdated(QString, QString, QString, double))); + + connect(d->resourceScoring, SIGNAL(recentStatsDeleted(QString, int, QString)), + this, SLOT(deleteRecentStats(QString, int, QString))); + + connect(d->resourceScoring, SIGNAL(earlierStatsDeleted(QString, int)), + this, SLOT(deleteEarlierStats(QString, int))); + + // Initializing nepomuk + d->nepomuk = Nepomuk::ResourceManager::instance(); + d->nepomuk->init(); + + connect(d->nepomuk, SIGNAL(nepomukSystemStarted()), + this, SLOT(nepomukSystemStarted())); + connect(d->nepomuk, SIGNAL(nepomukSystemStopped()), + this, SLOT(nepomukSystemStopped())); + + d->nepomukPresent = d->nepomuk->initialized(); + + d->syncActivities(); + + return true; +} + +NepomukPlugin * NepomukPlugin::self() +{ + return Private::s_instance; +} + +void NepomukPlugin::setActivityName(const QString & activity, const QString & name) +{ + Q_ASSERT(!activity.isEmpty()); + Q_ASSERT(!name.isEmpty()); + + if (d->nepomukPresent) + activityResource(activity).setLabel(name); +} + +void NepomukPlugin::setActivityIcon(const QString & activity, const QString & icon) +{ + Q_ASSERT(!activity.isEmpty()); + + if (d->nepomukPresent && !icon.isEmpty()) + activityResource(activity).setSymbols(QStringList() << icon); +} + +void NepomukPlugin::setCurrentActivity(const QString & activity) +{ + Q_UNUSED(activity); + + org::kde::KDirNotify::emitFilesAdded(Private::protocol + "current"); +} + +void NepomukPlugin::addActivity(const QString & activity) +{ + Q_ASSERT(!activity.isEmpty()); + + d->syncActivities(QStringList() << activity); + + org::kde::KDirNotify::emitFilesAdded(Private::protocol); + org::kde::KDirNotify::emitFilesAdded(Private::protocol + activity); +} + +void NepomukPlugin::removeActivity(const QString & activity) +{ + Q_ASSERT(!activity.isEmpty()); + + if (d->nepomukPresent) + activityResource(activity).remove(); + + org::kde::KDirNotify::emitFilesAdded(Private::protocol); +} + + +void NepomukPlugin::nepomukSystemStarted() +{ + if (d->nepomukPresent) return; + d->nepomukPresent = true; +} + +void NepomukPlugin::nepomukSystemStopped() +{ + d->nepomukPresent = false; +} + +bool NepomukPlugin::isFeatureOperational(const QStringList & feature) const +{ + if (feature.size() && feature.first() == "linking") + return d->nepomukPresent; + + return false; +} + +bool NepomukPlugin::isFeatureEnabled(const QStringList & feature) const +{ + Q_UNUSED(feature) + + return false; +} + +void NepomukPlugin::setFeatureEnabled(const QStringList & feature, bool value) +{ + Q_UNUSED(feature) + Q_UNUSED(value) +} + +QStringList NepomukPlugin::listFeatures(const QStringList & feature) const +{ + Q_UNUSED(feature) + + return QStringList() << "linking"; +} + +void NepomukPlugin::Private::syncActivities(const QStringList & activitys) +{ + if (!nepomukPresent) return; + + // If we got an empty list, it means we should synchronize + // all the activities known by the service + foreach (val & activity, + ( + activitys.isEmpty() + ? Plugin::callOn (activities, "ListActivities", "QStringList") + : activitys + ) + ) { + // Notifying KIO of the update + org::kde::KDirNotify::emitFilesAdded("activities:/" + activity); + + // Getting the activity info from the service + val name = Plugin::callOnWithArgs + (activities, "ActivityName", "QString", Q_ARG(QString, activity)); + val icon = Plugin::callOnWithArgs + (activities, "ActivityIcon", "QString", Q_ARG(QString, activity)); + + // Setting the nepomuk resource properties - id, name, icon + auto resource = activityResource(activity); + resource.setProperty(KAO::activityIdentifier(), activity); + + if (!name.isEmpty()) { + resource.setLabel(name); + } + + if (!icon.isEmpty()) { + resource.setSymbols(QStringList() << icon); + + } else { + // If there is no icon reported by the service, and we + // have one in nepomuk, send it to the service + val & symbols = resource.symbols(); + if (symbols.size() > 0) { + Plugin::callOnWithArgs + (activities, "SetActivityIcon", "QString", Q_ARG(QString, activity), Q_ARG(QString, symbols.at(0))); + } + } + } +} + +void NepomukPlugin::resourceScoreUpdated(const QString & activity, const QString & client, const QString & resource, double score) +{ + if (!d->nepomukPresent) return; + + updateNepomukScore(activity, client, resource, score); +} + +void NepomukPlugin::deleteRecentStats(const QString & activity, int count, const QString & what) +{ +#ifdef NEPOMUK_STORE_RESOURCE_SCORES + val activityCheck = activity.isEmpty() + ? QString() + : "?cache kao:usedActivity " + + Soprano::Node::resourceToN3(activityResource(activity).uri()) + + " ."; + + static val _query = QString( + "select ?cache where { " + "?cache a kao:ResourceScoreCache . " + " %1 " // "?cache kao:usedActivity %activity . " + " %2 " // "?cache nao:created ?created . " + // "FILTER ( ?create > %date ) . " + "}" + ); + + // If we need to delete everything, + // no need to bother with the count and the date + + if (what == "everything") { + val query = _query + .arg(activityCheck) + .arg(QString()) + ; + + qDebug() << "This are the results we need to delete: " << query; + + d->deleteFromQuery(query); + + } else { + + // Deleting a specified length of time + // Getting the time based on params + + auto now = QDateTime::currentDateTime(); + + if (what == "h") { + now = now.addSecs(-count * 60 * 60); + + } else if (what == "d") { + now = now.addDays(-count); + + } else if (what == "m") { + now = now.addMonths(-count); + } + + static val timeChecking = QString( + " ?cache nao:created ?created . " + " FILTER ( ?created > %1 ) . " + ); + + val query = _query + .arg(activityCheck) + .arg( + timeChecking.arg(Soprano::Node::literalToN3(now)) + ); + + qDebug() << "This are the results we need to delete: " << query; + + d->deleteFromQuery(query); + } +#endif +} + +void NepomukPlugin::deleteEarlierStats(const QString & activity, int months) +{ +#ifdef NEPOMUK_STORE_RESOURCE_SCORES + if (months == 0) return; + + val activityCheck = activity.isEmpty() + ? QString() + : "?cache kao:usedActivity " + + Soprano::Node::resourceToN3(activityResource(activity).uri()) + + " ."; + + static val _query = QString( + "select ?cache where { " + "?cache a kao:ResourceScoreCache . " + " %1 " // "?cache kao:usedActivity %activity . " + " %2 " // "?cache nao:modified ?modified . " + // "FILTER ( ?modified < %date ) . " + "}" + ); + + val time = QDateTime::currentDateTime().addMonths(-months); + + static val timeChecking = QString( + " ?cache nao:created ?created . " + " FILTER ( ?modified < %1 ) . " + ); + + val query = _query + .arg(activityCheck) + .arg( + timeChecking.arg(Soprano::Node::literalToN3(time)) + ); + + qDebug() << "This are the results we need to delete: " << query; + + d->deleteFromQuery(query); +#endif +} + +void NepomukPlugin::LinkResourceToActivity(const QString & uri, const QString & activity) +{ + Q_ASSERT_X(!uri.isEmpty(), "NepomukPlugin::LinkResourceToActivity", "URI of the resource can not be empty"); + + d->doWithActivity(activity, + [uri] (const QString & activity) { + // linking the resource to the specified activity + qDebug() << "Adding the triple " << activity << " isRelated " << uri; + activityResource(activity).addIsRelated(Nepomuk::Resource(uri)); + }); +} + +void NepomukPlugin::UnlinkResourceFromActivity(const QString & uri, const QString & activity) +{ + Q_ASSERT(!uri.isEmpty()); + + d->doWithActivity(activity, + [uri] (const QString & activity) { + // unlinking the resource from the specified activity + qDebug() << "Removing the triple " << activity << " isRelated " << uri; + activityResource(activity).removeProperty(NAO::isRelated(), Nepomuk::Resource(uri)); + }); +} + +bool NepomukPlugin::IsResourceLinkedToActivity(const QString & uri, const QString & _activity) const +{ + Q_ASSERT(!uri.isEmpty()); + + if (!d->nepomukPresent) return false; + + val activity = _activity.isEmpty() + ? Plugin::callOn (d->activities, "CurrentActivity", "QString") + : _activity; + + static val _query = QString::fromLatin1( + "select ?r where { " + " ?a a kao:Activity . " + " ?a nao:isRelated ?r . " + " ?r nie:url %1 . " + " ?a kao:activityIdentifier %2 " + "}"); + + val query = _query.arg(Soprano::Node::resourceToN3(uri)) + .arg(Soprano::Node::literalToN3(activity)); + + qDebug() << "So, this is the query that should get the linked resource " << query; + + Soprano::QueryResultIterator it + = Nepomuk::ResourceManager::instance()->mainModel()->executeQuery( + query, Soprano::Query::QueryLanguageSparql); + + val hasResults = it.next(); + it.close(); + + return hasResults; +} + +// TODO: This should be removed as soon as we get rid of the ResourcesLinkedToActivity method +// BEGIN + +QStringList NepomukPlugin::ResourcesLinkedToActivity(const QString & activity) const +{ + if (!d->nepomukPresent) return QStringList(); + + QStringList result; + + foreach (const Nepomuk::Resource & resource, activityResource(activity).isRelateds()) { + if (resource.hasProperty(NIE::url())) { + result << resource.property(NIE::url()).toUrl().toString(); + + } else { + result << resource.uri().toString(); + + } + } + + return result; +} + +// END + +KAMD_EXPORT_PLUGIN(NepomukPlugin, "activitymanger_plugin_nepomuk") + diff --git a/kactivities/src/service/plugins/nepomuk/NepomukPlugin.h b/kactivities/src/service/plugins/nepomuk/NepomukPlugin.h new file mode 100644 index 00000000..07d3d22e --- /dev/null +++ b/kactivities/src/service/plugins/nepomuk/NepomukPlugin.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINS_NEPOMUK_PLUGIN_H +#define PLUGINS_NEPOMUK_PLUGIN_H + +#include + +#include + +#include +#include +#include + +class QFileSystemWatcher; + +class NepomukPlugin: public Plugin { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.ActivityManager.ResourcesLinking") + +public: + explicit NepomukPlugin(QObject *parent = nullptr, const QVariantList & args = QVariantList()); + ~NepomukPlugin(); + + static NepomukPlugin * self(); + + virtual bool init(const QHash < QString, QObject * > & modules) _override; + +public Q_SLOTS: + // Resource linking slots + void LinkResourceToActivity(const QString & uri, const QString & activity = QString()); + void UnlinkResourceFromActivity(const QString & uri, const QString & activity = QString()); + bool IsResourceLinkedToActivity(const QString & uri, const QString & activity = QString()) const; + + QStringList ResourcesLinkedToActivity(const QString & activity = QString()) const; + +private Q_SLOTS: + // Activity slots + void setActivityName(const QString & activity, const QString & name); + void setActivityIcon(const QString & activity, const QString & icon); + void setCurrentActivity(const QString & activity); + void addActivity(const QString & activity); + void removeActivity(const QString & activity); + + // Resource score slots + void resourceScoreUpdated(const QString & activity, const QString & client, const QString & resource, double score); + void deleteRecentStats(const QString & activity, int count, const QString & what); + void deleteEarlierStats(const QString & activity, int months); + + // Nepomuk slots + void nepomukSystemStarted(); + void nepomukSystemStopped(); + + // setResourceMimeType + // setResourceTitle + +public: + virtual bool isFeatureOperational(const QStringList & feature) const _override; + virtual bool isFeatureEnabled(const QStringList & feature) const _override; + virtual void setFeatureEnabled(const QStringList & feature, bool value) _override; + virtual QStringList listFeatures(const QStringList & feature) const _override; + +private: + D_PTR; +}; + +#endif // PLUGINS_NEPOMUK_PLUGIN_H diff --git a/kactivities/src/service/plugins/nepomuk/activitymanager-plugin-nepomuk.desktop b/kactivities/src/service/plugins/nepomuk/activitymanager-plugin-nepomuk.desktop new file mode 100644 index 00000000..78cd2965 --- /dev/null +++ b/kactivities/src/service/plugins/nepomuk/activitymanager-plugin-nepomuk.desktop @@ -0,0 +1,104 @@ +[Desktop Entry] +Name=Nepomuk Feeder +Name[bs]=Nepomuk Feeder +Name[ca]=Alimentador del Nepomuk +Name[ca@valencia]=Alimentador del Nepomuk +Name[da]=Nepomuk-feeder +Name[de]=Nepomuk Feeder +Name[el]=Τροφοδότης του Nepomuk +Name[en_GB]=Nepomuk Feeder +Name[es]=Aprovisionamiento de Nepomuk +Name[et]=Nepomuki söötur +Name[fi]=Nepomuk-syötteistin +Name[fr]=Connecteur pour Nepomuk +Name[gl]=Alimentador de Nepomuk +Name[he]=רסס של Nepomuk +Name[hu]=Nepomuk-feltöltő +Name[ia]=Syndacation de Nepomuk +Name[is]=Nepomuk Feeder +Name[it]=Rifornitore di Nepomuk +Name[kk]=Nepomuk бергіші +Name[ko]=Nepomuk 공급자 +Name[mr]=नेपोमुक फीडर +Name[nb]=Nepomuk innmating +Name[nds]=Nepomuk-Ingaavmoduul +Name[nl]=Nepomuk-feeder +Name[pa]=ਨਿਪੋਮੁਕ ਫੀਡਰ +Name[pl]=Podajnik Nepomuka +Name[pt]=Fonte do Nepomuk +Name[pt_BR]=Alimentador do Nepomuk +Name[ro]=Alimentare Nepomuk +Name[ru]=Поставщик данных для Nepomuk +Name[sk]=Podávač Nepomuk +Name[sl]=Podajalnik za Nepomuk +Name[sr]=Непомуков уводник +Name[sr@ijekavian]=Непомуков уводник +Name[sr@ijekavianlatin]=Nepomukov uvodnik +Name[sr@latin]=Nepomukov uvodnik +Name[sv]=Nepomuk-inmatning +Name[tg]=Диҳандаи Nepomuk +Name[tr]=Nepomuk Besleyici +Name[ug]=Nepomuk Feeder +Name[uk]=Додаток передавання даних Nepomuk +Name[x-test]=xxNepomuk Feederxx +Name[zh_CN]=Nepomuk 采集器 +Name[zh_TW]=Nepomuk Feeder +Comment=Plugin to synchronize data with Nepomuk +Comment[bs]=Dodatak za sinhronizaciju podataka s Nepomuk-om +Comment[ca]=Connector per sincronitzar dades amb el Nepomuk +Comment[ca@valencia]=Connector per sincronitzar dades amb el Nepomuk +Comment[cs]=Modul pro synchronizaci dat s Nepomukem +Comment[da]=Plugin til at synkronisere data med Nepomuk +Comment[de]=Modul zum Abgleichen von Daten in Nepomuk +Comment[el]=Πρόσθετο συγχρονισμού γεγονότων με το Nepomuk +Comment[en_GB]=Plugin to synchronize data with Nepomuk +Comment[es]=Complemento para sincronizar datos con Nepomuk +Comment[et]=Plugin andmete sünkroonimiseks Nepomukiga +Comment[fi]=Liitännäinen tietojen synkronointiin Nepomukin kanssa +Comment[fr]=Module externe de synchronisation de données avec Nepomuk +Comment[gl]=Un complemento para sincronizar datos co Nepomuk +Comment[he]=תוסף לסנכרון מול Nepomuk +Comment[hu]=Bővítmény adatok szinkronizálásához a Nepomukkal +Comment[ia]=Plugin pro synchronisar datos con Nepomuk +Comment[is]=Íforrit til að samræma gögn við Nemopuk +Comment[it]=Estensione per sincronizzare i dati con Nepomuk +Comment[kk]=Деректерді қадамдастыратын Nepomuk плагині +Comment[ko]=Nepomuk 데이터 동기화 플러그인 +Comment[mr]=नेमोपुकशी डेटा सिंक्रोनाईझ करण्याकरिता प्लगइन +Comment[nb]=Programtillegg for å synkronisere data med Nepomuk +Comment[nds]=Moduul för't Synkroniseren vun Daten mit Nepomuk +Comment[nl]=Plug-in om gegevens met Nepomuk te synchroniseren +Comment[pl]=Wtyczka do synchronizacji danych z Nepomukiem +Comment[pt]='Plugin' para sincronizar os dados com o Nepomuk +Comment[pt_BR]=Plugin para sincronizar os dados com o Nepomuk +Comment[ro]=Modul pentru sincronizarea datelor cu Nepomuk +Comment[ru]=Модуль синхронизации данных с Nepomuk +Comment[sk]=Plugin na synchronizáciu dát s Nepomukom +Comment[sl]=Vstavek za usklajevanje podatkov z Nepomukom +Comment[sr]=Прикључак за синхронизовање података са Непомуком +Comment[sr@ijekavian]=Прикључак за синхронизовање података са Непомуком +Comment[sr@ijekavianlatin]=Priključak za sinhronizovanje podataka sa Nepomukom +Comment[sr@latin]=Priključak za sinhronizovanje podataka sa Nepomukom +Comment[sv]=Insticksprogram för att synkronisera data med Nepomuk +Comment[tr]=Nepomuk'ta veri eşzamanlaması için eklenti +Comment[ug]=Nepomuk بىلەن سانلىق مەلۇماتلارنى قەدەمداشلايدىغان قىستۇرما +Comment[uk]=Додаток для синхронізації даних з Nepomuk +Comment[x-test]=xxPlugin to synchronize data with Nepomukxx +Comment[zh_CN]=同步数据到 Nepomuk 的插件 +Comment[zh_TW]=在 Nepomuk 裡同步資料用的外掛程式 +Type=Service +Icon=nepomuk + +X-KDE-ServiceTypes=ActivityManager/Plugin +X-KDE-Library=activitymanager_plugin_nepomuk +X-KDE-PluginInfo-Author=Ivan Cukic +X-KDE-PluginInfo-Email=ivan.cukic@kde.org +X-KDE-PluginInfo-Name=org.kde.kactivitymanager.nepomuk +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-Website=http://plasma.kde.org/ +X-KDE-PluginInfo-Category= +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true + +X-ActivityManager-PluginOverrides= diff --git a/kactivities/src/service/plugins/slc/CMakeLists.txt b/kactivities/src/service/plugins/slc/CMakeLists.txt new file mode 100644 index 00000000..dc6e6edc --- /dev/null +++ b/kactivities/src/service/plugins/slc/CMakeLists.txt @@ -0,0 +1,35 @@ +project (activitymanager-plugin-slc) + +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_BINARY_DIR} + ${KDE4_INCLUDES} + ) + +set ( + slc_SRCS + SlcPlugin.cpp + ${plugin_implementation_SRCS} + ) + +qt4_add_dbus_adaptor ( + slc_SRCS org.kde.ActivityManager.SLC.xml + SlcPlugin.h SlcPlugin + ) + +kde4_add_plugin ( + activitymanager_plugin_slc + ${slc_SRCS} + ) + +target_link_libraries ( + activitymanager_plugin_slc + ${KDE4_KDECORE_LIBS} + ) + +install (TARGETS activitymanager_plugin_slc DESTINATION ${PLUGIN_INSTALL_DIR}) +install (FILES activitymanager-plugin-slc.desktop DESTINATION ${SERVICES_INSTALL_DIR}) + diff --git a/kactivities/src/service/plugins/slc/SlcPlugin.cpp b/kactivities/src/service/plugins/slc/SlcPlugin.cpp new file mode 100644 index 00000000..69dea666 --- /dev/null +++ b/kactivities/src/service/plugins/slc/SlcPlugin.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic ivan.cukic(at)kde.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "SlcPlugin.h" +#include "slcadaptor.h" + +#include +#include + +#include + +SlcPlugin::SlcPlugin(QObject * parent, const QVariantList & args) + : Plugin(parent) +{ + Q_UNUSED(args) + + new SLCAdaptor(this); + KDBusConnectionPool::threadConnection().registerObject("/SLC", this); +} + +SlcPlugin::~SlcPlugin() +{ +} + +QString SlcPlugin::focussedResourceURI() const +{ + return m_focussedResource; +} + +QString SlcPlugin::focussedResourceMimetype() const +{ + return m_resources[m_focussedResource].mimetype; +} + +QString SlcPlugin::focussedResourceTitle() const +{ + return m_resources[m_focussedResource].title; +} + +void SlcPlugin::registeredResourceEvent(const Event & event) +{ + switch (event.type) { + case Event::FocussedIn: + + if (!event.uri.startsWith(QLatin1String("about"))) { + if (m_focussedResource != event.uri) { + m_focussedResource = event.uri; + val & info = m_resources[m_focussedResource]; + emit focusChanged(event.uri, info.mimetype, info.title); + } + } else { + m_focussedResource.clear(); + emit focusChanged(QString(), QString(), QString()); + } + + break; + + case Event::FocussedOut: + + if (m_focussedResource == event.uri) { + m_focussedResource.clear(); + emit focusChanged(QString(), QString(), QString()); + } + + break; + + case Event::Closed: + m_resources.remove(event.uri); + + break; + + default: + break; + } +} + +void SlcPlugin::registeredResourceMimeType(const QString & uri, const QString & mimetype) +{ + m_resources[uri].mimetype = mimetype; +} + +void SlcPlugin::registeredResourceTitle(const QString & uri, const QString & title) +{ + m_resources[uri].title = title; +} + +bool SlcPlugin::init(const QHash < QString, QObject * > & modules) +{ + connect(modules["resources"], SIGNAL(RegisteredResourceEvent(Event)), + this, SLOT(registeredResourceEvent(Event)), + Qt::QueuedConnection); + connect(modules["resources"], SIGNAL(RegisteredResourceMimeType(QString, QString)), + this, SLOT(registeredResourceMimeType(QString, QString)), + Qt::QueuedConnection); + connect(modules["resources"], SIGNAL(RegisteredResourceTitle(QString, QString)), + this, SLOT(registeredResourceTitle(QString, QString)), + Qt::QueuedConnection); + + return true; +} + +KAMD_EXPORT_PLUGIN(SlcPlugin, "activitymanger_plugin_slc") diff --git a/kactivities/src/service/plugins/slc/SlcPlugin.h b/kactivities/src/service/plugins/slc/SlcPlugin.h new file mode 100644 index 00000000..c5ad5546 --- /dev/null +++ b/kactivities/src/service/plugins/slc/SlcPlugin.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINS_SLC_PLUGIN_H +#define PLUGINS_SLC_PLUGIN_H + +#include + +#include +#include + +class SlcPlugin: public Plugin +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.ActivityManager.SLC") + +public: + explicit SlcPlugin(QObject * parent = nullptr, const QVariantList & args = QVariantList()); + ~SlcPlugin(); + + virtual bool init(const QHash < QString, QObject * > & modules) _override; + +private Q_SLOTS: + void registeredResourceEvent(const Event & event); + void registeredResourceMimeType(const QString & uri, const QString & mimetype); + void registeredResourceTitle(const QString & uri, const QString & title); + +public Q_SLOTS: + QString focussedResourceURI() const; + QString focussedResourceMimetype() const; + QString focussedResourceTitle() const; + +Q_SIGNALS: + void focusChanged(const QString & uri, const QString & mimetype, const QString & title); + +private: + struct ResourceInfo { + QString title; + QString mimetype; + }; + + QHash < QString, ResourceInfo > m_resources; + QString m_focussedResource; +}; + +#endif // PLUGINS_SLC_PLUGIN_H + diff --git a/kactivities/src/service/plugins/slc/activitymanager-plugin-slc.desktop b/kactivities/src/service/plugins/slc/activitymanager-plugin-slc.desktop new file mode 100644 index 00000000..cd705878 --- /dev/null +++ b/kactivities/src/service/plugins/slc/activitymanager-plugin-slc.desktop @@ -0,0 +1,105 @@ +[Desktop Entry] +Name=Share-Like-Connect +Name[bs]=Povezivanje kao Share +Name[ca]=«Share-Like-Connect» +Name[ca@valencia]=«Share-Like-Connect» +Name[da]=Share-Like-Connect +Name[de]=Share-Like-Connect +Name[el]=Share-Like-Connect +Name[en_GB]=Share-Like-Connect +Name[es]=Compartir-Me gusta-Conectar +Name[et]=Share-Like-Connect +Name[eu]=Share-Like-Connect +Name[fi]=Share-Like-Connect +Name[fr]=Share-Like-Connect +Name[gl]=Compartir-gustar-conectar +Name[he]=שתף-אוהב-מתחבר +Name[hu]=Share-Like-Connect +Name[ia]=Connexion como compartir (Share-Like-Connect) +Name[it]=Share-Like-Connect +Name[kk]=Қосылу-Секілді-Ортақтастыру +Name[km]=កម្មវិធី​ជំនួយ​តភ្ជាប់​ដូច​ជា​ការ​ចែករំលែក​ +Name[ko]=공유-좋아요-연결 +Name[lt]=Share-Like-Connect +Name[mr]=शेअर-लाइक-कनेक्ट +Name[nb]=Share-Like-Connect +Name[nds]=Delen-Mögen-Tokoppeln +Name[nl]='Share-Like-Connect' +Name[pa]=ਸ਼ੇਅਰ-ਲਾਈਕ-ਕੁਨੈਕਟ +Name[pl]=Share-Like-Connect +Name[pt]=Share-Like-Connect +Name[pt_BR]=Share-Like-Connect +Name[ro]=Partajează-Place-Conectează +Name[ru]=Share-Like-Connect +Name[sk]=Share-Like-Connect +Name[sl]=Share-Like-Connect +Name[sr]=Подели-оцени-повежи (СЛЦ) +Name[sr@ijekavian]=Подели-оцени-повежи (СЛЦ) +Name[sr@ijekavianlatin]=Podeli-oceni-poveži (SLC) +Name[sr@latin]=Podeli-oceni-poveži (SLC) +Name[sv]=Share-Like-Connect +Name[tr]=Paylaş-Beğen-Bağlan +Name[ug]=Share-Like-Connect +Name[uk]=Оприлюднти-Уподобати-З’єднати +Name[x-test]=xxShare-Like-Connectxx +Name[zh_CN]=分享-喜爱-连接 +Name[zh_TW]=Share-Like-Connect +Comment=Provides data to Share-Like-Connect applet +Comment[bs]=Pruža podatke za programčić Share-Like-Connect +Comment[ca]=Proporciona dades a la miniaplicació «Share-Like-Connect» +Comment[ca@valencia]=Proporciona dades a la miniaplicació «Share-Like-Connect» +Comment[da]=Giver data til Share-Like-Connect-appletten +Comment[de]=Liefert Daten für das Miniprogramm Share-Like-Connect +Comment[el]=Παρέχει δεδομένα σε μικροεφαρμογή Share-Like-Connect +Comment[en_GB]=Provides data to Share-Like-Connect applet +Comment[es]=Proporciona datos a la miniaplicación Compartir-Me gusta-Conectar +Comment[et]=Andmete edastamine Share-Like-Connecti pluginale +Comment[eu]=Share-Like-Connect appletari datuak hornitzen dizkio +Comment[fi]=Tarjoaa tietoa Share-Like-Connect-sovelmalle +Comment[fr]=Fournit des données à l'applet « Share-Like-Connect » +Comment[gl]=Fornece datos á applet compartir-gustar-conectar +Comment[he]=מספק מידע לתוספ שתף-אוהב-מתחבר +Comment[hu]=Adatot szolgáltat a Share-Like-Connect kisalkalmazáshoz +Comment[ia]=Provide datos pro applet Share-Like-Connect +Comment[it]=Fornisce dati al programma Share-Like-Connect +Comment[kk]=Қосылу-Секілді-Ортақтастыру апплетіне деректерді беру +Comment[km]=ផ្ដល់​ទិន្នន័យ​ដើម្បី​ចែករំលែក​កម្មវិធី​ជំនួយ​តភ្ជាប់​ +Comment[ko]=공유-좋아요-연결 애플릿에 데이터 공급 +Comment[lt]=Teikia duomenis Share-Like-Connect programėlei +Comment[mr]=शेअर-लाइक-कनेक्ट एप्लेटला डेटा पुरवितो +Comment[nb]=Skaffer data til miniprogrammet Share-Like-Connect +Comment[nds]=Stellt dat Delen-Mögen-Tokoppeln-Lüttprogramm Daten praat +Comment[nl]=Levert gegevens aan het applet Share-Like-Connect +Comment[pl]=Dostarcza danych do apletu Share-Like-Connect +Comment[pt]=Fornece os dados à 'applet' do Share-Like-Connect +Comment[pt_BR]=Fornece dados para o miniaplicativo Share-Like-Connect +Comment[ro]=Furnizează date pentru miniaplicația Partajează-Place-Conectează +Comment[ru]=Предоставляет данные аплету Share-Like-Connect +Comment[sk]=Poskytuje dáta pre applet Share-Like-Connect +Comment[sl]=Ponuja podatke apletu Share-Like-Connect +Comment[sr]=Пружа податке аплету подели-оцени-повежи (СЛЦ) +Comment[sr@ijekavian]=Пружа податке аплету подели-оцени-повежи (СЛЦ) +Comment[sr@ijekavianlatin]=Pruža podatke apletu podeli-oceni-poveži (SLC) +Comment[sr@latin]=Pruža podatke apletu podeli-oceni-poveži (SLC) +Comment[sv]=Tillhandahåller data för Share-Like-Connect miniprogram +Comment[tr]=Paylaş-Beğen-Bağlan uygulamacığına veri sağlar +Comment[ug]=Share-Like-Connect ئەپچە ئۈچۈن سانلىق مەلۇمات تەمىنلەيدۇ +Comment[uk]=Надає дані аплетові Оприлюднити-Уподобати-З’єднати +Comment[x-test]=xxProvides data to Share-Like-Connect appletxx +Comment[zh_CN]=为分享-喜爱-连接小程序提供数据 +Comment[zh_TW]=提供資料給 Share-Like-Connect 小程式 +Type=Service +Icon=mail-forward + +X-KDE-ServiceTypes=ActivityManager/Plugin +X-KDE-Library=activitymanager_plugin_slc +X-KDE-PluginInfo-Author=Ivan Cukic +X-KDE-PluginInfo-Email=ivan.cukic@kde.org +X-KDE-PluginInfo-Name=org.kde.kactivitymanager.slc +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-Website=http://plasma.kde.org/ +X-KDE-PluginInfo-Category= +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true + diff --git a/kactivities/src/service/plugins/slc/org.kde.ActivityManager.SLC.xml b/kactivities/src/service/plugins/slc/org.kde.ActivityManager.SLC.xml new file mode 100644 index 00000000..1f414ae3 --- /dev/null +++ b/kactivities/src/service/plugins/slc/org.kde.ActivityManager.SLC.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/kactivities/src/service/plugins/sqlite/CMakeLists.txt b/kactivities/src/service/plugins/sqlite/CMakeLists.txt new file mode 100644 index 00000000..af722e21 --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/CMakeLists.txt @@ -0,0 +1,41 @@ +project (activitymanager-plugin-sqlite) + +set ( + sqliteplugin_SRCS + DatabaseConnection.cpp + StatsPlugin.cpp + ResourceScoreCache.cpp + ResourceScoreMaintainer.cpp + ${plugin_implementation_SRCS} + ) + +qt4_add_dbus_adaptor ( + sqliteplugin_SRCS + org.kde.ActivityManager.Resources.Scoring.xml + StatsPlugin.h StatsPlugin + ) + +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/../.. + ${KDE4_INCLUDES} + ) + +kde4_add_plugin ( + activitymanager_plugin_sqlite + ${sqliteplugin_SRCS} + ) + +target_link_libraries ( + activitymanager_plugin_sqlite + ${QT_QTSQL_LIBRARIES} + ${KDE4_KDECORE_LIBS} + ) + +install (TARGETS activitymanager_plugin_sqlite DESTINATION ${PLUGIN_INSTALL_DIR}) +install (FILES activitymanager-plugin-sqlite.desktop DESTINATION ${SERVICES_INSTALL_DIR}) + diff --git a/kactivities/src/service/plugins/sqlite/DatabaseConnection.cpp b/kactivities/src/service/plugins/sqlite/DatabaseConnection.cpp new file mode 100644 index 00000000..2d30de33 --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/DatabaseConnection.cpp @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DatabaseConnection.h" + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include +#include + +class DatabaseConnection::Private { +public: + QSqlDatabase database; + bool initialized : 1; + + static const QString insertSchemaInfoQuery; + static const QString updateSchemaInfoQuery; + + static const QString openDesktopEventQuery; + static const QString closeDesktopEventQuery; + static const QString getScoreAdditionQuery; + + static const QString createResourceScoreCacheQuery; + static const QString getResourceScoreCacheQuery; + static const QString updateResourceScoreCacheQuery; + + qreal timeFactor(int days) const + { + // Exp is falling rather quickly, we are slowing it 32 times + return ::exp(- days / 32.0); + } + + qreal timeFactor(QDateTime fromTime, QDateTime toTime = QDateTime::currentDateTime()) const + { + return timeFactor(fromTime.daysTo(toTime)); + } +}; + +const QString DatabaseConnection::Private::insertSchemaInfoQuery + = "INSERT INTO schemaInfo VALUES ('%1', '%2')"; + +const QString DatabaseConnection::Private::updateSchemaInfoQuery + = "UPDATE schemaInfo SET value = '%2' WHERE key = '%1'"; + +const QString DatabaseConnection::Private::openDesktopEventQuery + = "INSERT INTO nuao_DesktopEvent VALUES (" + "'%1', " // usedActivity + "'%2', " // initiatingAgent + "'%3', " // targettedResource + "%4, " // start + "%5" // end + ")"; + +const QString DatabaseConnection::Private::closeDesktopEventQuery + = "UPDATE nuao_DesktopEvent SET " + "end = %4 " + "WHERE " + "'%1' = usedActivity AND " + "'%2' = initiatingAgent AND " + "'%3' = targettedResource AND " + "end IS NULL" + ; + +const QString DatabaseConnection::Private::createResourceScoreCacheQuery + = "INSERT INTO kext_ResourceScoreCache VALUES(" + "'%1', " // usedActivity + "'%2', " // initiatingAgent + "'%3', " // targettedResource + "0," // scoreType + "0.0," // cachedScore + "-1," // lastUpdate + "%4" // firstUpdate + ")" + ; + +const QString DatabaseConnection::Private::getResourceScoreCacheQuery + = "SELECT cachedScore, lastUpdate FROM kext_ResourceScoreCache " + "WHERE " + "'%1' = usedActivity AND " + "'%2' = initiatingAgent AND " + "'%3' = targettedResource " + ; + +const QString DatabaseConnection::Private::updateResourceScoreCacheQuery + = "UPDATE kext_ResourceScoreCache SET " + "cachedScore = %4, " + "lastUpdate = %5 " + "WHERE " + "'%1' = usedActivity AND " + "'%2' = initiatingAgent AND " + "'%3' = targettedResource " + ; + +const QString DatabaseConnection::Private::getScoreAdditionQuery + = "SELECT start, end FROM nuao_DesktopEvent " + "WHERE " + "'%1' = usedActivity AND " + "'%2' = initiatingAgent AND " + "'%3' = targettedResource AND " + "start > %4" + ; + +void DatabaseConnection::openDesktopEvent(const QString & usedActivity, const QString & initiatingAgent, + const QString & targettedResource, const QDateTime & start, const QDateTime & end) +{ + d->database.exec( + Private::openDesktopEventQuery + .arg(usedActivity) + .arg(initiatingAgent) + .arg(targettedResource) + .arg(start.toTime_t()) + .arg(end.isNull() ? "NULL" : QString::number(end.toTime_t())) + ); +} + +void DatabaseConnection::closeDesktopEvent(const QString & usedActivity, const QString & initiatingAgent, + const QString & targettedResource, const QDateTime & end) +{ + d->database.exec( + Private::closeDesktopEventQuery + .arg(usedActivity) + .arg(initiatingAgent) + .arg(targettedResource) + .arg(end.toTime_t()) + ); +} + +void DatabaseConnection::getResourceScoreCache(const QString & usedActivity, const QString & initiatingAgent, + const QUrl & targettedResource, qreal & score, QDateTime & lastUpdate) +{ + // This can fail if we have the cache already made + + d->database.exec( + Private::createResourceScoreCacheQuery + .arg(usedActivity) + .arg(initiatingAgent) + .arg(targettedResource.toString()) + .arg(QDateTime::currentDateTime().toTime_t()) + ); + + // Getting the old score + + auto query = d->database.exec( + Private::getResourceScoreCacheQuery + .arg(usedActivity) + .arg(initiatingAgent) + .arg(targettedResource.toString()) + ); + + if (query.next()) { + val time = query.value(1).toLongLong(); + + if (time < 0) { + // If we haven't had the cache before, set the score to 0 + lastUpdate = QDateTime(); + score = 0; + + } else { + // Adjusting the score depending on the time that passed since the + // last update + lastUpdate.setTime_t(time); + + score = query.value(0).toReal(); + score *= d->timeFactor(lastUpdate); + + } + } + + // Calculating the updated score + + query = d->database.exec( + Private::getScoreAdditionQuery + .arg(usedActivity) + .arg(initiatingAgent) + .arg(targettedResource.toString()) + .arg(lastUpdate.toTime_t()) + ); + + long start = 0; + + while (query.next()) { + start = query.value(0).toLongLong(); + + val end = query.value(1).toLongLong(); + val intervalLength = end - start; + + if (intervalLength == 0) { + // We have an Accessed event - otherwise, this wouldn't be 0 + score += d->timeFactor(QDateTime::fromTime_t(end)); // like it is open for 1 minute + + } else if (intervalLength >= 4) { + // Ignoring stuff that was open for less than 4 seconds + score += d->timeFactor(QDateTime::fromTime_t(end)) * intervalLength / 60.0; + + } + + } + + // Updating the score + + d->database.exec( + Private::updateResourceScoreCacheQuery + .arg(usedActivity) + .arg(initiatingAgent) + .arg(targettedResource.toString()) + .arg(score) + .arg(start) + ); +} + +DatabaseConnection * DatabaseConnection::s_instance = nullptr; + +DatabaseConnection * DatabaseConnection::self() +{ + if (!s_instance) { + s_instance = new DatabaseConnection(); + } + + return s_instance; +} + +DatabaseConnection::DatabaseConnection() + : d() +{ + val path = KStandardDirs::locateLocal("data", "activitymanager/resources/database", true); + + d->database = QSqlDatabase::addDatabase("QSQLITE", "plugins_sqlite_db_resources"); + d->database.setDatabaseName(path); + + d->initialized = d->database.open(); + + if (!d->initialized) { + qDebug() << "Failed to open the database" << path + << d->database.lastError(); + } + + initDatabaseSchema(); +} + +DatabaseConnection::~DatabaseConnection() +{ +} + +QSqlDatabase & DatabaseConnection::database() +{ + return d->database; +} + +void DatabaseConnection::initDatabaseSchema() +{ + QString dbSchemaVersion = "0.0"; + + auto query = d->database.exec("SELECT value FROM SchemaInfo WHERE key = 'version'"); + + if (query.next()) { + dbSchemaVersion = query.value(0).toString(); + } + + if (dbSchemaVersion < "1.0") { + qDebug() << "The database doesn't exist, it is being created"; + + query.exec("CREATE TABLE IF NOT EXISTS SchemaInfo (key text PRIMARY KEY, value text)"); + query.exec(Private::insertSchemaInfoQuery.arg("version", "1.0")); + + query.exec( + "CREATE TABLE IF NOT EXISTS nuao_DesktopEvent (" + "usedActivity TEXT, " + "initiatingAgent TEXT, " + "targettedResource TEXT, " + "start INTEGER, " + "end INTEGER " + ")" + ); + + query.exec( + "CREATE TABLE IF NOT EXISTS kext_ResourceScoreCache (" + "usedActivity TEXT, " + "initiatingAgent TEXT, " + "targettedResource TEXT, " + "scoreType INTEGER, " + "cachedScore FLOAT, " + "lastUpdate INTEGER, " + "PRIMARY KEY(usedActivity, initiatingAgent, targettedResource)" + ")" + ); + } + + if (dbSchemaVersion < "1.01") { + qDebug() << "Upgrading the database version to 1.01"; + + // Adding the firstUpdate field so that we can have + // a crude way of deleting the score caches when + // the user requests a partial history deletion + + query.exec(Private::updateSchemaInfoQuery.arg("version", "1.01")); + + query.exec( + "ALTER TABLE kext_ResourceScoreCache " + "ADD COLUMN firstUpdate INTEGER" + ); + + query.exec( + "UPDATE kext_ResourceScoreCache " + "SET firstUpdate = " + QString::number(QDateTime::currentDateTime().toTime_t()) + ); + } +} + diff --git a/kactivities/src/service/plugins/sqlite/DatabaseConnection.h b/kactivities/src/service/plugins/sqlite/DatabaseConnection.h new file mode 100644 index 00000000..14bc1763 --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/DatabaseConnection.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PLUGINS_SQLITE_DATABASE_CONNECTION_H +#define PLUGINS_SQLITE_DATABASE_CONNECTION_H + +#include +#include +#include + +#include + +class QDateTime; +class OUrl; +class QSqlDatabase; + +class DatabaseConnection: public QObject { + Q_OBJECT + +public: + static DatabaseConnection * self(); + + void openDesktopEvent(const QString & usedActivity, const QString & initiatingAgent, + const QString & targettedResource, const QDateTime & start, const QDateTime & end = QDateTime()); + void closeDesktopEvent(const QString & usedActivity, const QString & initiatingAgent, + const QString & targettedResource, const QDateTime & end); + + void getResourceScoreCache(const QString & usedActivity, const QString & initiatingAgent, + const QUrl & targettedResource, qreal & score, QDateTime & lastUpdate); + + QSqlDatabase & database(); + + +private: + static DatabaseConnection * s_instance; + + DatabaseConnection(); + ~DatabaseConnection(); + + void initDatabaseSchema(); + + D_PTR; +}; + +#endif // PLUGINS_SQLITE_DATABASE_CONNECTION_H + diff --git a/kactivities/src/service/plugins/sqlite/Rankings.cpp b/kactivities/src/service/plugins/sqlite/Rankings.cpp new file mode 100644 index 00000000..eab70011 --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/Rankings.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic ivan.cukic(at)kde.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Rankings.h" +#include "rankingsadaptor.h" + +#include +#include +#include +#include + +#include "ResourceScoreCache.h" +#include "DatabaseConnection.h" +#include "StatsPlugin.h" + +#include +#include +#include + +#define RESULT_COUNT_LIMIT 10 +#define COALESCE_ACTIVITY(Activity) ((Activity.isEmpty()) ? \ + (StatsPlugin::self()->currentActivity()) : (Activity)) + +Rankings * Rankings::s_instance = nullptr; + +/** + * + */ +RankingsUpdateThread::RankingsUpdateThread( + const QString & activity, QList < Rankings::ResultItem > * listptr, + QHash < Rankings::Activity, qreal > * scoreTrashold) + : m_activity(activity), m_listptr(listptr), m_scoreTrashold(scoreTrashold) +{ +} + +RankingsUpdateThread::~RankingsUpdateThread() +{ +} + +void RankingsUpdateThread::run() { + qDebug() << "This is the activity we want the results for:" << m_activity; + + val & query = QString::fromLatin1( + "SELECT targettedResource, cachedScore " + "FROM kext_ResourceScoreCache " // this should be kao_ResourceScoreCache, but lets leave it + "WHERE usedActivity = '%1' " + "AND cachedScore > 0 " + "ORDER BY cachedScore DESC LIMIT 30" + ).arg(m_activity); + + qDebug() << query; + + auto result = DatabaseConnection::self()->database().exec(query); + + while (result.next()) { + val url = result.value(0).toString(); + val score = result.value(1).toReal(); + + if (score > (*m_scoreTrashold)[m_activity]) { + (*m_listptr) << Rankings::ResultItem(url, score); + } + } + + emit loaded(m_activity); +} + +void Rankings::init(QObject * parent) +{ + if (s_instance) return; + + s_instance = new Rankings(parent); +} + +Rankings * Rankings::self() +{ + return s_instance; +} + +Rankings::Rankings(QObject * parent) + : QObject(parent) +{ + new RankingsAdaptor(this); + KDBusConnectionPool::threadConnection().registerObject("/Rankings", this); + + initResults(QString()); +} + +Rankings::~Rankings() +{ +} + +void Rankings::registerClient(const QString & client, + const QString & activity, const QString & type) +{ + Q_UNUSED(type); + + if (!m_clients.contains(activity)) { + initResults(COALESCE_ACTIVITY(activity)); + } + + if (!m_clients[activity].contains(client)) { + m_clients[activity] << client; + } + + notifyResultsUpdated(activity, QStringList() << client); +} + +void Rankings::deregisterClient(const QString & client) +{ + QMutableHashIterator < Activity, QStringList > i(m_clients); + + while (i.hasNext()) { + i.next(); + + i.value().removeAll(client); + if (i.value().isEmpty()) { + i.remove(); + } + } +} + +void Rankings::setCurrentActivity(const QString & activity) +{ + // We need to update scores for items that have no + // activity specified + + initResults(activity); +} + +void Rankings::initResults(const QString & _activity) +{ + val & activity = COALESCE_ACTIVITY(_activity); + + m_results[activity].clear(); + notifyResultsUpdated(activity); + updateScoreTrashold(activity); + + val thread = new RankingsUpdateThread( + activity, + &(m_results[activity]), + &m_resultScoreTreshold + ); + connect(thread, SIGNAL(loaded(QString)), + this, SLOT(notifyResultsUpdated(QString))); + connect(thread, SIGNAL(terminated()), + thread, SLOT(deleteLater())); + + thread->start(); +} + +void Rankings::resourceScoreUpdated(const QString & activity, + const QString & application, const QUrl & uri, qreal score) +{ + Q_UNUSED(application); + + if (score <= m_resultScoreTreshold[activity]) { + return; + } + + auto & list = m_results[activity]; + + // Removing the item from the list if it is already in it + + kamd::utils::remove_if(list, [&uri] (const ResultItem & item) { + return item.uri == uri; + }); + + // Adding the item + + ResultItem item(uri, score); + + if (list.size() == 0) { + list << item; + + } else { + int i; + + for (i = 0; i < list.size(); i++) { + if (list[i].score < score) { + list.insert(i, item); + break; + } + } + + if (i == list.size()) { + list << item; + } + } + + while (list.size() > RESULT_COUNT_LIMIT) { + list.removeLast(); + } + + notifyResultsUpdated(activity); +} + +void Rankings::updateScoreTrashold(const QString & activity) +{ + m_resultScoreTreshold[activity] = + (m_results[activity].size() >= RESULT_COUNT_LIMIT) + ? m_results[activity].last().score + : 0 + ; +} + +void Rankings::notifyResultsUpdated(const QString & _activity, QStringList clients) +{ + val & activity = COALESCE_ACTIVITY(_activity); + + updateScoreTrashold(activity); + + QVariantList data; + foreach (val & item, m_results[activity]) { + data << item.uri.toString(); + } + + if (clients.isEmpty()) { + clients = m_clients[activity]; + + if (activity == StatsPlugin::self()->currentActivity()) { + clients.append(m_clients[QString()]); + } + } + + foreach (val & client, clients) { + QDBusInterface rankingsservice(client, "/RankingsClient", "org.kde.ActivityManager.RankingsClient"); + rankingsservice.asyncCall("updated", data); + } +} + +void Rankings::requestScoreUpdate(const QString & activity, const QString & application, const QString & resource) +{ + ResourceScoreCache(activity, application, resource).updateScore(); +} + diff --git a/kactivities/src/service/plugins/sqlite/Rankings.h b/kactivities/src/service/plugins/sqlite/Rankings.h new file mode 100644 index 00000000..1bfe5d46 --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/Rankings.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINS_SQLITE_RANKINGS_H +#define PLUGINS_SQLITE_RANKINGS_H + +#include +#include +#include +#include +#include +#include + +#include + +class Rankings: public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.ActivityManager.Rankings") + +public: + static void init(QObject * parent = nullptr); + static Rankings * self(); + + void resourceScoreUpdated(const QString & activity, + const QString & application, const QUrl & uri, qreal score); + + ~Rankings(); + +public Q_SLOTS: + /** + * Registers a new client for the specified activity and application + * @param client d-bus name + * @param activity activity to track. If empty, uses the default activity. + * @param application application to track. If empty, all applications are aggregated + * @param type resource type that the client is interested in + * @note If the activity is left empty - the results are always related to the current activity, + * not the activity that was current when calling this method. + */ + void registerClient(const QString & client, + const QString & activity = QString(), + const QString & type = QString() + ); + + /** + * Deregisters a client + */ + void deregisterClient(const QString & client); + + /** + * Don't use this, will be removed + */ + void requestScoreUpdate(const QString & activity, const QString & application, const QString & resource); + +private Q_SLOTS: + void setCurrentActivity(const QString & activity); + +private: + Rankings(QObject * parent = nullptr); + void updateScoreTrashold(const QString & activity); + + static Rankings * s_instance; + +public: + class ResultItem { + public: + ResultItem( + const QUrl & _uri, + qreal _score + ) + : uri(_uri), score(_score) + { + } + + QUrl uri; + qreal score; + + }; + + typedef QString Activity; + typedef QString Client; + +private Q_SLOTS: + void initResults(const QString & activity); + void notifyResultsUpdated(const QString & activity, QStringList clients = QStringList()); + +private: + QHash < Activity, QStringList > m_clients; + QHash < Activity, QList < ResultItem > > m_results; + QHash < Activity, qreal > m_resultScoreTreshold; +}; + +class RankingsUpdateThread: public QThread { + Q_OBJECT + +public: + RankingsUpdateThread(const QString & activity, QList < Rankings::ResultItem > * listptr, + QHash < Rankings::Activity, qreal > * scoreTrashold); + virtual ~RankingsUpdateThread(); + + void run(); + +Q_SIGNALS: + void loaded(const QString & activity); + +private: + QString m_activity; + QList < Rankings::ResultItem > * m_listptr; + QHash < Rankings::Activity, qreal > * m_scoreTrashold; +}; + +#endif // PLUGINS_SQLITE_RANKINGS_H + diff --git a/kactivities/src/service/plugins/sqlite/ResourceScoreCache.cpp b/kactivities/src/service/plugins/sqlite/ResourceScoreCache.cpp new file mode 100644 index 00000000..895809ab --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/ResourceScoreCache.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "ResourceScoreCache.h" +#include "StatsPlugin.h" +#include "DatabaseConnection.h" + +#include + +#include + +#include + +/** + * + */ +class ResourceScoreCache::Private { +public: + QString activity; + QString application; + QUrl resource; + +}; + +ResourceScoreCache::ResourceScoreCache(const QString & activity, const QString & application, const QUrl & resource) + : d() +{ + qDebug() << "Going to update score for" + << activity << application << resource; + + d->activity = activity; + d->application = application; + d->resource = resource; +} + +ResourceScoreCache::~ResourceScoreCache() +{ +} + +void ResourceScoreCache::updateScore() +{ + QDateTime lastUpdate; + qreal score; + + DatabaseConnection::self()->getResourceScoreCache( + d->activity, d->application, d->resource, + score, lastUpdate); + + qDebug() << "Sending resourceScoreUpdated event"; + QMetaObject::invokeMethod(StatsPlugin::self(), "resourceScoreUpdated", + Q_ARG(QString, d->activity), + Q_ARG(QString, d->application), + Q_ARG(QString, d->resource.toString()), + Q_ARG(double, score) + ); +} diff --git a/kactivities/src/service/plugins/sqlite/ResourceScoreCache.h b/kactivities/src/service/plugins/sqlite/ResourceScoreCache.h new file mode 100644 index 00000000..59aad799 --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/ResourceScoreCache.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PLUGINS_SQLITE_RESOURCE_SCORE_CACHE_H +#define PLUGINS_SQLITE_RESOURCE_SCORE_CACHE_H + +#include +#include + +#include + +class ResourceScoreCachePrivate; + +/** + * + */ +class ResourceScoreCache { +public: + ResourceScoreCache(const QString & activity, const QString & application, const QUrl & resource); + virtual ~ResourceScoreCache(); + + void updateScore(); + +private: + D_PTR; +}; + +#endif // PLUGINS_SQLITE_RESOURCE_SCORE_CACHE_H diff --git a/kactivities/src/service/plugins/sqlite/ResourceScoreMaintainer.cpp b/kactivities/src/service/plugins/sqlite/ResourceScoreMaintainer.cpp new file mode 100644 index 00000000..f8dc8168 --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/ResourceScoreMaintainer.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "ResourceScoreMaintainer.h" + +#include +#include +#include + +#include + +#include "StatsPlugin.h" +#include "ResourceScoreCache.h" + +#include +#include +#include + +class ResourceScoreMaintainer::Private: public QThread { +public: + typedef QString ApplicationName; + typedef QString ActivityID; + typedef QList < QUrl > ResourceList; + + typedef QMap < ApplicationName, ResourceList > Applications; + typedef QMap < ActivityID, Applications > ResourceTree; + + ResourceTree openResources; + QMutex openResources_mutex; + + void run(); + void processActivity(const ActivityID & activity, const Applications & applications); + + static ResourceScoreMaintainer * s_instance; + +}; + +ResourceScoreMaintainer * ResourceScoreMaintainer::Private::s_instance = nullptr; + +void ResourceScoreMaintainer::Private::run() +{ + forever { + // initial delay before processing the resources + sleep(5); + + ResourceTree resources; + + { + QMutexLocker lock(&openResources_mutex); + resources = openResources; + openResources.clear(); + } + + val & activity = StatsPlugin::self()->currentActivity(); + + // Let us first process the events related to the current + // activity so that the stats are available quicker + + if (resources.contains(activity)) { + processActivity(activity, resources[activity]); + resources.remove(activity); + } + + kamd::utils::for_each_assoc(resources, + [this](const ActivityID & activity, const Applications & applications) { + processActivity(activity, applications); + } + ); + } +} + +void ResourceScoreMaintainer::Private::processActivity(const ActivityID & activity, const Applications & applications) +{ + kamd::utils::for_each_assoc(applications, + [activity](const ApplicationName & application, const ResourceList & resources) { + foreach (const QUrl & resource, resources) { + ResourceScoreCache(activity, application, resource).updateScore(); + } + } + ); +} + +ResourceScoreMaintainer * ResourceScoreMaintainer::self() +{ + if (!Private::s_instance) { + Private::s_instance = new ResourceScoreMaintainer(); + } + + return Private::s_instance; +} + +ResourceScoreMaintainer::ResourceScoreMaintainer() + : d() +{ +} + +ResourceScoreMaintainer::~ResourceScoreMaintainer() +{ +} + +void ResourceScoreMaintainer::processResource(const KUrl & resource, const QString & application) +{ + QMutexLocker lock(&d->openResources_mutex); + + // Checking whether the item is already scheduled for + // processing + + val & activity = StatsPlugin::self()->currentActivity(); + + if (d->openResources.contains(activity) && + d->openResources[activity].contains(application) && + d->openResources[activity][application].contains(resource)) { + + // Nothing + + } else { + d->openResources[activity][application] << resource; + + } + + d->start(); +} + + diff --git a/kactivities/src/service/plugins/sqlite/ResourceScoreMaintainer.h b/kactivities/src/service/plugins/sqlite/ResourceScoreMaintainer.h new file mode 100644 index 00000000..25e8ecef --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/ResourceScoreMaintainer.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PLUGINS_SQLITE_RESOURCE_SCORE_MAINTAINER_H +#define PLUGINS_SQLITE_RESOURCE_SCORE_MAINTAINER_H + +#include + +#include + +class ResourceScoreMaintainerPrivate; + +/** + * Thread to process desktop/usage events + */ +class ResourceScoreMaintainer { +public: + static ResourceScoreMaintainer * self(); + + virtual ~ResourceScoreMaintainer(); + + void processResource(const KUrl & resource, const QString & application); + +private: + ResourceScoreMaintainer(); + + D_PTR; +}; + +#endif // PLUGINS_SQLITE_RESOURCE_SCORE_MAINTAINER_H diff --git a/kactivities/src/service/plugins/sqlite/StatsPlugin.cpp b/kactivities/src/service/plugins/sqlite/StatsPlugin.cpp new file mode 100644 index 00000000..4474e2ac --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/StatsPlugin.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "StatsPlugin.h" +#include "ResourceScoreMaintainer.h" +#include "scoringadaptor.h" + +#include "../../Event.h" +#include + +#include +#include +#include + +#include + +#include "DatabaseConnection.h" + +#include +#include + +StatsPlugin * StatsPlugin::s_instance = nullptr; + +StatsPlugin::StatsPlugin(QObject *parent, const QVariantList & args) + : Plugin(parent), + m_rankings(nullptr), + m_activities(nullptr), + m_resources(nullptr), + m_configWatcher(nullptr) +{ + Q_UNUSED(args) + s_instance = this; + + new ScoringAdaptor(this); + KDBusConnectionPool::threadConnection().registerObject("/ActivityManager/Resources/Scoring", this); + + setName("org.kde.ActivityManager.Resources.Scoring"); +} + +bool StatsPlugin::init(const QHash < QString, QObject * > & modules) +{ + qDebug() << "These are the registered modules: " << modules.keys(); + + m_activities = modules["activities"]; + m_resources = modules["resources"]; + + DatabaseConnection::self(); + // Rankings::init(this); + + // connect(m_activities, SIGNAL(CurrentActivityChanged(QString)), + // Rankings::self(), SLOT(setCurrentActivity(QString))); + + connect(m_resources, SIGNAL(ProcessedResourceEvents(EventList)), + this, SLOT(addEvents(EventList))); + + loadConfiguration(); + + return true; +} + +void StatsPlugin::loadConfiguration() +{ + config().config()->reparseConfiguration(); + static val configFile = KStandardDirs::locateLocal("config", "activitymanager-pluginsrc"); + + if (m_configWatcher) { + // When saving a config file, KConfig deletes the old, + // and creates the new one, so the watcher stops watching + m_configWatcher->addPath(configFile); + + } else { + m_configWatcher = new QFileSystemWatcher(QStringList() << configFile, this); + + connect(m_configWatcher, SIGNAL(fileChanged(QString)), + this, SLOT(loadConfiguration())); + connect(m_activities, SIGNAL(CurrentActivityChanged(QString)), + this, SLOT(loadConfiguration())); + } + + m_blockedByDefault = config().readEntry("blocked-by-default", false); + m_blockAll = false; + m_whatToRemember = (WhatToRemember)config().readEntry("what-to-remember", (int)AllApplications); + + m_apps.clear(); + + if (m_whatToRemember == SpecificApplications) { + m_apps = config().readEntry( + m_blockedByDefault ? "allowed-applications" : "blocked-applications", + QStringList() + ).toSet(); + } + + // Delete old events, as per configuration + // TODO: This should be also done from time to time, + // not only on startup + deleteEarlierStats(QString(), config().readEntry("keep-history-for", 0)); +} + +StatsPlugin * StatsPlugin::self() +{ + return s_instance; +} + +QString StatsPlugin::currentActivity() const +{ + return Plugin::callOn (m_activities, "CurrentActivity", "QString"); +} + +void StatsPlugin::addEvents(const EventList & events) +{ + if (m_blockAll || m_whatToRemember == NoApplications) return; + + for (int i = 0; i < events.size(); i++) { + val & event = events[i]; + + if (event.uri.startsWith(QLatin1String("about"))) continue; + + val currentActivity = Plugin::callOn (m_activities, "CurrentActivity", "QString"); + + // if blocked by default, the list contains allowed applications + // ignore event if the list doesn't contain the application + // if not blocked by default, the list contains blocked applications + // ignore event if the list contains the application + if ( + (m_whatToRemember == SpecificApplications) && + (m_blockedByDefault + ? !m_apps.contains(event.application) + : m_apps.contains(event.application) + ) + ) continue; + + switch (event.type) { + case Event::Accessed: + DatabaseConnection::self()->openDesktopEvent( + currentActivity, event.application, event.uri, event.timestamp, event.timestamp); + ResourceScoreMaintainer::self()->processResource(event.uri, event.application); + + break; + + case Event::Opened: + DatabaseConnection::self()->openDesktopEvent( + currentActivity, event.application, event.uri, event.timestamp); + + break; + + case Event::Closed: + DatabaseConnection::self()->closeDesktopEvent( + currentActivity, event.application, event.uri, event.timestamp); + ResourceScoreMaintainer::self()->processResource(event.uri, event.application); + + break; + + case Event::UserEventType: + ResourceScoreMaintainer::self()->processResource(event.uri, event.application); + break; + + default: + // Nothing yet + // TODO: Add focus and modification + break; + } + } +} + +void StatsPlugin::deleteRecentStats(const QString & activity, int count, const QString & what) +{ + val activityCheck = activity.isEmpty() ? + QString(" 1 ") : + QString(" usedActivity = '" + activity + "' "); + + // If we need to delete everything, + // no need to bother with the count and the date + + if (what == "everything") { + DatabaseConnection::self()->database().exec( + "DELETE FROM kext_ResourceScoreCache WHERE " + activityCheck + ); + DatabaseConnection::self()->database().exec( + "DELETE FROM nuao_DesktopEvent WHERE " + activityCheck + ); + + } else { + + // Deleting a specified length of time + + auto now = QDateTime::currentDateTime(); + + if (what == "h") { + now = now.addSecs(-count * 60 * 60); + + } else if (what == "d") { + now = now.addDays(-count); + + } else if (what == "m") { + now = now.addMonths(-count); + } + + // Maybe we should decrease the scores for the previosuly + // cached items. Thkinking it is not that important - + // if something was accessed before, it is not really a secret + + static val queryRSC = QString( + "DELETE FROM kext_ResourceScoreCache " + " WHERE %1 " + " AND firstUpdate > %2 " + ); + static val queryDE = QString( + "DELETE FROM nuao_DesktopEvent " + " WHERE %1 " + " AND end > %2 " + ); + + DatabaseConnection::self()->database().exec( + queryRSC + .arg(activityCheck) + .arg(now.toTime_t()) + ); + DatabaseConnection::self()->database().exec( + queryDE + .arg(activityCheck) + .arg(now.toTime_t()) + ); + } + + emit recentStatsDeleted(activity, count, what); +} + +void StatsPlugin::deleteEarlierStats(const QString & activity, int months) +{ + if (months == 0) return; + + val activityCheck = activity.isEmpty() ? + QString(" 1 ") : + QString(" usedActivity = '" + activity + "' "); + + // Deleting a specified length of time + + val time = QDateTime::currentDateTime().addMonths(-months); + + static val queryRSC = QString( + "DELETE FROM kext_ResourceScoreCache " + " WHERE %1 " + " AND lastUpdate < %2 " + ); + static val queryDE = QString( + "DELETE FROM nuao_DesktopEvent " + " WHERE %1 " + " AND start < %2 " + ); + + DatabaseConnection::self()->database().exec( + queryRSC + .arg(activityCheck) + .arg(time.toTime_t()) + ); + + DatabaseConnection::self()->database().exec( + queryDE + .arg(activityCheck) + .arg(time.toTime_t()) + ); + + emit earlierStatsDeleted(activity, months); +} + +KAMD_EXPORT_PLUGIN(StatsPlugin, "activitymanger_plugin_sqlite") diff --git a/kactivities/src/service/plugins/sqlite/StatsPlugin.h b/kactivities/src/service/plugins/sqlite/StatsPlugin.h new file mode 100644 index 00000000..67540f52 --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/StatsPlugin.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2011, 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINS_SQLITE_STATS_PLUGIN_H +#define PLUGINS_SQLITE_STATS_PLUGIN_H + +#include +#include + +#include +#include "Rankings.h" + +#include +#include + +class QFileSystemWatcher; + +class StatsPlugin: public Plugin { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.ActivityManager.Resources.Scoring") + +public: + explicit StatsPlugin(QObject *parent = nullptr, const QVariantList & args = QVariantList()); + + static StatsPlugin * self(); + + virtual bool init(const QHash < QString, QObject * > & modules) _override; + + QString currentActivity() const; + +Q_SIGNALS: + void resourceScoreUpdated(const QString & activity, const QString & client, const QString & resource, double score); + void recentStatsDeleted(const QString & activity, int count, const QString & what); + void earlierStatsDeleted(const QString & activity, int months); + +public Q_SLOTS: + void deleteRecentStats(const QString & activity, int count, const QString & what); + void deleteEarlierStats(const QString & activity, int months); + +private Q_SLOTS: + void addEvents(const EventList & events); + void loadConfiguration(); + +private: + enum WhatToRemember { + AllApplications = 0, + SpecificApplications = 1, + NoApplications = 2 + }; + + Rankings * m_rankings; + QObject * m_activities; + QObject * m_resources; + QFileSystemWatcher * m_configWatcher; + + QSet m_apps; + + bool m_blockedByDefault : 1; + bool m_blockAll : 1; + WhatToRemember m_whatToRemember : 2; + + static StatsPlugin * s_instance; +}; + +#endif // PLUGINS_SQLITE_STATS_PLUGIN_H diff --git a/kactivities/src/service/plugins/sqlite/activitymanager-plugin-sqlite.desktop b/kactivities/src/service/plugins/sqlite/activitymanager-plugin-sqlite.desktop new file mode 100644 index 00000000..a1724ef3 --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/activitymanager-plugin-sqlite.desktop @@ -0,0 +1,110 @@ +[Desktop Entry] +Name=Sqlite Feeder +Name[bs]=Punjač za Sqlite +Name[ca]=Alimentador del Sqlite +Name[ca@valencia]=Alimentador del Sqlite +Name[da]=Sqlite-feeder +Name[de]=Sqlite-Modul +Name[el]=Τροφοδότης Sqlite +Name[en_GB]=Sqlite Feeder +Name[es]=Aprovisionamiento de Sqlite +Name[et]=Sqlite'i söötur +Name[eu]=Sqlite elikatzailea +Name[fi]=Sqlite-syötteistin +Name[fr]=Connecteur pour Sqlite +Name[gl]=Alimentador de Sqlite +Name[he]=רסס מבוסס Sqlite +Name[hu]=Sqlite-feltöltő +Name[ia]=Syndacation de Sqlite +Name[is]=Sqlite Feeder +Name[it]=Rifornimento di Sqlite +Name[kk]=Sqlite бергіші +Name[km]=កម្មវិធី​ជំនួយ​មតិ​ព័ត៌មាន​របស់ Sqlite +Name[ko]=SQLite 공급자 +Name[lt]=Sqlite tiekimas +Name[mr]=Sqlite फीडर +Name[nb]=Sqlite innmating +Name[nds]=Sqlite-Ingaavmoduul +Name[nl]=Sqlite-feeder +Name[pa]=ਇਸਕਿਉਲੇਟ ਫੀਡਰ +Name[pl]=Podajnik Sqlite +Name[pt]=Fontes do SQLite +Name[pt_BR]=Fonte de notícias do Sqlite +Name[ro]=Alimentare Sqlite +Name[ru]=Поставщик данных для Sqlite +Name[sk]=Podávač Sqlite +Name[sl]=Podajalnik SQLite +Name[sr]=СКуЛајтов уводник +Name[sr@ijekavian]=СКуЛајтов уводник +Name[sr@ijekavianlatin]=SQLiteov uvodnik +Name[sr@latin]=SQLiteov uvodnik +Name[sv]=SQLite-inmatning +Name[tg]=Диҳандаи Sqlite +Name[tr]=Sqlite Besleyicisi +Name[ug]=Sqlite Feeder +Name[uk]=Передавання даних Sqlite +Name[x-test]=xxSqlite Feederxx +Name[zh_CN]=Sqlite 采集器 +Name[zh_TW]=Sqlite Feeder +Comment=Plugin to store and score events in Sqlite +Comment[bs]=Dodatak za smiještanje i evidentiranje događaja u Sqlite +Comment[ca]=Connector per emmagatzemar i classificar esdeveniments en el Sqlite +Comment[ca@valencia]=Connector per emmagatzemar i classificar esdeveniments en el Sqlite +Comment[da]=Plugin til at gemme og bedømme begivenheder i Sqlite +Comment[de]=Modul zum Speichern und Bewerten von Ereignissen in SQLite +Comment[el]=Πρόσθετο παροχής και υπολογισμού γεγονότων στο Sqlite +Comment[en_GB]=Plugin to store and score events in Sqlite +Comment[es]=Complemento para almacenar y puntuar eventos en Sqlite +Comment[et]=Plugin sündmuste salvestamiseks Sqlite'i ja nende hindamiseks +Comment[eu]=Sqlite-n gertaerak biltegiratu eta puntuatzeko plugina +Comment[fi]=Liitännäinen tapahtumien tallentamiseen ja arvostelemiseen Sqlitessa +Comment[fr]=Module externe de mémorisation et de classement des évènements dans Sqlite +Comment[ga]=Breiseán a stórálann agus scórálann imeachtaí in Sqlite +Comment[gl]=Un complemento para gardar e cualificar acontecementos en Sqlite +Comment[he]=תוסף לשמירה ורישום של אירועים ב־Sqlite +Comment[hu]=Bővítmény események tárolására és pontozására Sqlite-ban +Comment[ia]=Plugin pro immagazinar e dar punctos a eventos in Sqlite +Comment[it]=Estensione per memorizzare e valutare gli eventi in Sqlite +Comment[kk]=Оқиғаларды сақтап бағалайтын Sqlite қызметі +Comment[km]=កម្មវិធី​ជំនួយ​ដើម្បី​រក្សាទុក​ និងដាក់​ពិន្ទុ​ព្រឹត្តិការណ៍​នៅ​ក្នុង Sqlite +Comment[ko]=SQLite에 점수와 이벤트를 저장하는 플러그인 +Comment[lt]=Priedas saugoti ir vertinti įvykius Sqlite saugykloje +Comment[mr]=Sqlite मध्ये घटना साठविणे व त्यांना गुण देणारे प्लगइन +Comment[nb]=Programtillegg for å lagre og poengsette hendelser i Sqlite +Comment[nds]=Sqlite-Moduul för't Sekern un Beweerten vun Begeefnissen +Comment[nl]=Plug-in om gebeurtenissen in Sqlite op te slaan en van een score te voorzien +Comment[pa]=ਇਸਕਿਉਲੇਟ ਵਿੱਚ ਈਵੈਂਟ ਸਟੋਰ ਕਰਨ ਅਤੇ ਸਕੋਰ ਲਈ ਪਲੱਗਇਨ +Comment[pl]=Wtyczka do przechowywania i oceny zdarzeń w Sqlite +Comment[pt]='Plugin' para guardar e classificar os eventos no SQLite +Comment[pt_BR]=Plugin para armazenar e classificar os eventos no Sqlite +Comment[ro]=Extensie pentru stocarea și notarea evenimentelor în Sqlite +Comment[ru]=Модуль для сохранения и оценки событий в Sqlite +Comment[sk]=Plugin na uloženie a bodovanie udalosti v Sqlite +Comment[sl]=Vstavek za shranjevanje in označevanje dogodkov v SQLite +Comment[sr]=Прикључак за складиштење и оцењивање догађаја у СКуЛајту +Comment[sr@ijekavian]=Прикључак за складиштење и оцењивање догађаја у СКуЛајту +Comment[sr@ijekavianlatin]=Priključak za skladištenje i ocenjivanje događaja u SQLiteu +Comment[sr@latin]=Priključak za skladištenje i ocenjivanje događaja u SQLiteu +Comment[sv]=Insticksprogram för att lagra och betygsätta händelser i SQLite +Comment[tr]=Sqlite içerisinde olayları derecelendirme ve saklama eklentisi +Comment[ug]=Sqlite تىكى ئېرىشكەن نومۇر ۋە ساقلاش ھادىسىسىنىڭ قىستۇرمىسى +Comment[uk]=Додаток для зберігання і оцінки подій у Sqlite +Comment[x-test]=xxPlugin to store and score events in Sqlitexx +Comment[zh_CN]=将事件在 Sqlite 中存储并打分的插件 +Comment[zh_TW]=在 Sqlite 裡儲存與評分事件用的外掛程式 +Type=Service +Icon=server-database + +X-KDE-ServiceTypes=ActivityManager/Plugin +X-KDE-Library=activitymanager_plugin_sqlite +X-KDE-PluginInfo-Author=Ivan Cukic +X-KDE-PluginInfo-Email=ivan.cukic@kde.org +X-KDE-PluginInfo-Name=org.kde.kactivitymanager.resourcescoring +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-Website=http://plasma.kde.org/ +X-KDE-PluginInfo-Category= +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true + +X-ActivityManager-PluginOverrides=activitymanager_plugin_dummy diff --git a/kactivities/src/service/plugins/sqlite/org.kde.ActivityManager.Rankings.xml b/kactivities/src/service/plugins/sqlite/org.kde.ActivityManager.Rankings.xml new file mode 100644 index 00000000..23ebf8ad --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/org.kde.ActivityManager.Rankings.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kactivities/src/service/plugins/sqlite/org.kde.ActivityManager.RankingsClient.xml b/kactivities/src/service/plugins/sqlite/org.kde.ActivityManager.RankingsClient.xml new file mode 100644 index 00000000..54d5ccb8 --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/org.kde.ActivityManager.RankingsClient.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/kactivities/src/service/plugins/sqlite/org.kde.ActivityManager.Resources.Scoring.xml b/kactivities/src/service/plugins/sqlite/org.kde.ActivityManager.Resources.Scoring.xml new file mode 100644 index 00000000..043c2e10 --- /dev/null +++ b/kactivities/src/service/plugins/sqlite/org.kde.ActivityManager.Resources.Scoring.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/kactivities/src/service/plugins/virtualdesktopswitch/CMakeLists.txt b/kactivities/src/service/plugins/virtualdesktopswitch/CMakeLists.txt new file mode 100644 index 00000000..fd361c6b --- /dev/null +++ b/kactivities/src/service/plugins/virtualdesktopswitch/CMakeLists.txt @@ -0,0 +1,32 @@ +project (activitymanager-virtualdesktopswitch) + +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_BINARY_DIR} + ${KDE4_INCLUDES} + ) + +set ( + virtualdesktopswitch_SRCS + VirtualDesktopSwitchPlugin.cpp + ${plugin_implementation_SRCS} + ) + +kde4_add_plugin ( + activitymanager_plugin_virtualdesktopswitch + ${virtualdesktopswitch_SRCS} + ) + +target_link_libraries ( + activitymanager_plugin_virtualdesktopswitch + ${KDE4_KDECORE_LIBS} + ${KDE4_KDEUI_LIBS} + ${KACTIVITIES_LIBS} + ) + +install (TARGETS activitymanager_plugin_virtualdesktopswitch DESTINATION ${PLUGIN_INSTALL_DIR}) +install (FILES activitymanager-plugin-virtualdesktopswitch.desktop DESTINATION ${SERVICES_INSTALL_DIR}) + diff --git a/kactivities/src/service/plugins/virtualdesktopswitch/VirtualDesktopSwitchPlugin.cpp b/kactivities/src/service/plugins/virtualdesktopswitch/VirtualDesktopSwitchPlugin.cpp new file mode 100644 index 00000000..ca0cd31f --- /dev/null +++ b/kactivities/src/service/plugins/virtualdesktopswitch/VirtualDesktopSwitchPlugin.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "VirtualDesktopSwitchPlugin.h" + +#include +#include + +#include + +#include +#include + +val configPattern = QString::fromLatin1("desktop-for-%1"); + +VirtualDesktopSwitchPlugin::VirtualDesktopSwitchPlugin(QObject * parent, const QVariantList & args) + : Plugin(parent), + m_activitiesService(nullptr) +{ + Q_UNUSED(args) + + setName("org.kde.ActivityManager.VirtualDesktopSwitch"); +} + +VirtualDesktopSwitchPlugin::~VirtualDesktopSwitchPlugin() +{ +} + +bool VirtualDesktopSwitchPlugin::init(const QHash < QString, QObject * > & modules) +{ + m_activitiesService = modules["activities"]; + + m_currentActivity = Plugin::callOn (m_activitiesService, "CurrentActivity", "QString"); + + connect(m_activitiesService, SIGNAL(CurrentActivityChanged(QString)), + this, SLOT(currentActivityChanged(QString))); + connect(m_activitiesService, SIGNAL(ActivityRemoved(QString)), + this, SLOT(activityRemoved(QString))); + + return true; +} + +void VirtualDesktopSwitchPlugin::currentActivityChanged(const QString & activity) +{ + if (m_currentActivity == activity) return; + + config().writeEntry( + configPattern.arg(m_currentActivity), + QString::number(KWindowSystem::currentDesktop()) + ); + + m_currentActivity = activity; + + val desktopId = config().readEntry(configPattern.arg(m_currentActivity), -1); + + if (desktopId <= KWindowSystem::numberOfDesktops() && desktopId >= 0) { + KWindowSystem::setCurrentDesktop(desktopId); + } +} + +void VirtualDesktopSwitchPlugin::activityRemoved(const QString & activity) +{ + config().deleteEntry(configPattern.arg(activity)); + config().sync(); +} + +KAMD_EXPORT_PLUGIN(VirtualDesktopSwitchPlugin, "activitymanger_plugin_virtualdesktopswitch") + diff --git a/kactivities/src/service/plugins/virtualdesktopswitch/VirtualDesktopSwitchPlugin.h b/kactivities/src/service/plugins/virtualdesktopswitch/VirtualDesktopSwitchPlugin.h new file mode 100644 index 00000000..daff63a3 --- /dev/null +++ b/kactivities/src/service/plugins/virtualdesktopswitch/VirtualDesktopSwitchPlugin.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLUGINS_VIRTUAL_DESKTOP_SWITCH_PLUGIN_H +#define PLUGINS_VIRTUAL_DESKTOP_SWITCH_PLUGIN_H + +#include + +#include + +class VirtualDesktopSwitchPlugin: public Plugin +{ + Q_OBJECT + +public: + VirtualDesktopSwitchPlugin(QObject * parent, const QVariantList & args); + virtual ~VirtualDesktopSwitchPlugin(); + + virtual bool init(const QHash < QString, QObject * > & modules) _override; + +private Q_SLOTS: + void currentActivityChanged(const QString & activity); + void activityRemoved(const QString & activity); + +private: + QString m_currentActivity; + QObject * m_activitiesService; +}; + +#endif // PLUGINS_VIRTUAL_DESKTOP_SWITCH_PLUGIN_H diff --git a/kactivities/src/service/plugins/virtualdesktopswitch/activitymanager-plugin-virtualdesktopswitch.desktop b/kactivities/src/service/plugins/virtualdesktopswitch/activitymanager-plugin-virtualdesktopswitch.desktop new file mode 100644 index 00000000..d78e5465 --- /dev/null +++ b/kactivities/src/service/plugins/virtualdesktopswitch/activitymanager-plugin-virtualdesktopswitch.desktop @@ -0,0 +1,108 @@ +[Desktop Entry] +Name=Virtual desktop switcher +Name[bs]=Prebacivač virtuelnih radnih površina +Name[ca]=Commutador d'escriptoris virtuals +Name[ca@valencia]=Commutador d'escriptoris virtuals +Name[cs]=Přepínač virtuálních ploch +Name[da]=Skift mellem virtuelle skriveborde +Name[de]=Virtueller Arbeitsflächenumschalter +Name[el]=Εναλλάκτης εικονικής επιφάνειας εργασίας +Name[en_GB]=Virtual desktop switcher +Name[es]=Selector de escritorio virtual +Name[et]=Virtuaalsete töölaudade vahetaja +Name[eu]=Alegiazko mahaigain aldatzailea +Name[fi]=Virtuaalityöpöydän valitsin +Name[fr]=Commutateur de bureaux virtuels +Name[gl]=Selector de escritorio virtual +Name[he]=מעביר שולחנות עבודה +Name[hu]=Virtuális asztal váltó +Name[ia]=Commutator de scriptorio virtual +Name[it]=Cambiadesktop virtuale +Name[kk]=Виртуалды үстелін ауыстырушы +Name[km]=កម្មវិធី​ប្ដូរ​ផ្ទៃតុ​និម្មិត +Name[ko]=가상 데스크톱 전환기 +Name[lt]=Virtualaus darbastalio perjungėjas +Name[mr]=आभासी डेस्कटॉप बदलणारा +Name[nb]=Bytter virtuelle skrivebord +Name[nds]=Schriefdischwesseln +Name[nl]=Virtueel bureaublad omschakelaar +Name[pa]=ਵਰਚੁਅਲ ਡੈਸਕਟਾਪ ਸਵਿੱਚਰ +Name[pl]=Przełącznik wirtualnych pulpitów +Name[pt]=Selector de ecrãs virtuais +Name[pt_BR]=Alternador de áreas de trabalho virtuais +Name[ro]=Comutator virtual de birouri +Name[ru]=Переключатель виртуальных рабочих столов +Name[sk]=Prepínač virtuálnych plôch +Name[sl]=Preklapljanje navideznih namizij +Name[sr]=Мењач виртуелних површи +Name[sr@ijekavian]=Мењач виртуелних површи +Name[sr@ijekavianlatin]=Menjač virtuelnih površi +Name[sr@latin]=Menjač virtuelnih površi +Name[sv]=Byte av virtuellt skrivbord +Name[tg]=Васлкунандаи мизи кории виртуалӣ +Name[tr]=Sanal masaüstü değiştirici +Name[ug]=مەۋھۇم ئۈستەلئۈستى ئالماشتۇرغۇچ +Name[uk]=Перемикач віртуальних стільниць +Name[x-test]=xxVirtual desktop switcherxx +Name[zh_CN]=虚拟桌面切换器 +Name[zh_TW]=虛擬桌面切換器 +Comment=When switching to an activity, opens the virtual desktop last used with that activity +Comment[bs]=Pri prebacivanju na aktivnost, otvara vitruelni deskstop koji je zadnji korišten s tom aktivnošću +Comment[ca]=En canviar a una activitat, obre el darrer escriptori virtual utilitzat amb aquesta activitat +Comment[ca@valencia]=En canviar a una activitat, obri el darrer escriptori virtual utilitzat amb esta activitat +Comment[da]=Når der skiftes til en aktivitet, åbnes det virtuelle skrivebord der senest blev brugt med den aktivitet +Comment[de]=Beim Wechseln zu einer Aktivität wird die virtuelle Arbeitsfläche geöffnet, auf der zuletzt diese Aktivität benutzt wurde +Comment[el]=Όταν γίνεται εναλλαγή δραστηριότητας, ανοίγει την εικονική επιφάνεια εργασίας που χρησιμοποιήθηκε τελευταία με τη δραστηριότητα αυτή +Comment[en_GB]=When switching to an activity, opens the virtual desktop last used with that activity +Comment[es]=Cuando se cambia a una actividad, abre el escritorio virtual usado por última vez con dicha actividad +Comment[et]=Avab tegevuse vahetamisel uues tegevuses viimati kasutatud virtuaalse töölaua +Comment[eu]=Jarduera batera aldatzean, jarduera horrekin erabilitako azken alegiazko mahaigaina irekitzen du +Comment[fi]=Aktiviteettia vaihdettaessa avaa sen virtuaalityöpöydän, jota kyseisellä aktiviteetilla viimeksi käytettiin +Comment[fr]=Lors d'un changement d'activité, cette fonction ouvre le dernier bureau virtuel utilisé par cette activité +Comment[gl]=Cando se muda de actividade abre o último escritorio virtual usado con esa actividade +Comment[he]=בעת מעבר בין פעילויות, פותח את שולחן העבודה בשימוש אחרון בפעילות המתאימה +Comment[hu]=Amikor aktivitást vált, megnyitja az aktivitás által utoljára használt virtuális asztalt +Comment[ia]=Quando on commuta a un activitate, il aperi le ultime scriptorio virtual usate con celle activitate +Comment[it]=Quando si passa ad un'attività, apre l'ultimo desktop virtuale usato con quella attività +Comment[kk]=Белсенділікті ауыстырғанда, оны соңғы рет пайдаланған виртуалды үстелді ашу +Comment[km]=នៅ​ពេល​ប្ដូរ​ទៅ​ជា​សកម្ម​ភាព បើក​​ផ្ទៃតុ​និម្មិត​ចុងក្រោយ​ដែល​បាន​ប្រើ​ជាមួយ​​សកម្មភាព​នោះ +Comment[ko]=활동으로 전환할 때 그 활동에서 사용한 마지막 가상 데스크톱을 염 +Comment[lt]=Kai persijungiama į veiklą, atveria paskiausiai naudotą virtualų darbastalį su šia veikla +Comment[mr]=कार्यपध्दती बदलताना त्या कार्यपध्दती बरोबर वापरलेला शेवटचा आभासी डेस्कटॉप उघडतो +Comment[nb]=Når det byttes til en aktivitet, så åpnes det virtuelle skrivebordet som sist ble brukt med den aktiviteten +Comment[nds]=Bi't Wesseln na en Aktiviteet warrt de tolest dor binnen bruukte Schriefdisch opropen +Comment[nl]=Bij het omschakelen naar een activiteit, opent het virtuele bureaublad dat het laatst werd gebruikt met die activiteit +Comment[pl]=Przy przełączaniu do działania, otwiera ostatnio używany, przy tym działaniu, wirtualny pulpit +Comment[pt]=Ao mudar para uma actividade, abre o ecrã virtual usado da última vez com essa actividade +Comment[pt_BR]=Ao alternar para uma atividade, abre a última área de trabalho virtual usada com esta atividade +Comment[ro]=La cumutarea către o activitate, deschide ultimul birou virtual folosit cu acea activitate +Comment[ru]=При переключении на комнату открывает виртуальный рабочий стол, активный в ней ранее +Comment[sk]=Pri prepnutí na aktivitu, otvorí virtuálnu plochu naposledy použitú s danou aktivitou. +Comment[sl]=Ob preklopu na dejavnost odpre navidezno namizje, ki je bilo nazadnje uporabljeno s to dejavnostjo +Comment[sr]=При пребацивању на активност, отвара виртуелну површ која је последња коришћена у тој активности +Comment[sr@ijekavian]=При пребацивању на активност, отвара виртуелну површ која је последња коришћена у тој активности +Comment[sr@ijekavianlatin]=Pri prebacivanju na aktivnost, otvara virtuelnu površ koja je poslednja korišćena u toj aktivnosti +Comment[sr@latin]=Pri prebacivanju na aktivnost, otvara virtuelnu površ koja je poslednja korišćena u toj aktivnosti +Comment[sv]=Vid byte till en aktivitet, öppnar det virtuella skrivbord som senast användes med den aktiviteten +Comment[tr]=Bir etkinliğe geçiş yaparken, o etkinliği kullanmış son sanal masaüstünü açar +Comment[ug]=پائالىيەتكە ئالماشقاندا، ئاخىرقى قېتىم ئىشلەتكەن مەۋھۇم ئۈستەلئۈستى ۋە پائالىيەتنى ئاچىدۇ +Comment[uk]=Під час перемикання на простір дій відкриває віртуальну стільницю, яку було використано у цьому просторі останньою +Comment[x-test]=xxWhen switching to an activity, opens the virtual desktop last used with that activityxx +Comment[zh_CN]=切换活动时打开上次一同使用的虚拟桌面 +Comment[zh_TW]=切換到一個活動時,開啟該活動中上次使用的虛擬桌面 + +Type=Service +Icon=preferences-system-windows + +X-KDE-ServiceTypes=ActivityManager/Plugin +X-KDE-Library=activitymanager_plugin_virtualdesktopswitch +X-KDE-PluginInfo-Author=Ivan Cukic +X-KDE-PluginInfo-Email=ivan.cukic@kde.org +X-KDE-PluginInfo-Name=org.kde.kactivitymanager.virtualdesktopswitch +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-Website=http://plasma.kde.org/ +X-KDE-PluginInfo-Category= +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true + diff --git a/kactivities/src/utils/d_ptr.h b/kactivities/src/utils/d_ptr.h new file mode 100644 index 00000000..8e70362d --- /dev/null +++ b/kactivities/src/utils/d_ptr.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef D_PTR_H +#define D_PTR_H + +#include + +namespace kamd { +namespace utils { + +template +class d_ptr { +private: + std::unique_ptr d; + +public: + d_ptr(); + + template + d_ptr(Args && ...); + + ~d_ptr(); + + T * operator->() const; +}; + +#define D_PTR \ + class Private; \ + friend class Private; \ + const ::kamd::utils::d_ptr d \ + +} // namespace utils +} // namespace kamd + +#endif diff --git a/kactivities/src/utils/d_ptr_implementation.h b/kactivities/src/utils/d_ptr_implementation.h new file mode 100644 index 00000000..e3af873e --- /dev/null +++ b/kactivities/src/utils/d_ptr_implementation.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef D_PTR_IMPLEMENTATION_H +#define D_PTR_IMPLEMENTATION_H + +#include + +namespace kamd { +namespace utils { + +template +d_ptr::d_ptr() : d(new T()) +{ +} + +template +template +d_ptr::d_ptr(Args && ... args) + : d(new T(std::forward(args)... )) +{ +} + +template +d_ptr::~d_ptr() +{ +} + +template +T * d_ptr::operator->() const +{ + return d.get(); +} + +} // namespace utils +} // namespace kamd + +#endif + diff --git a/kactivities/src/utils/find_if_assoc.h b/kactivities/src/utils/find_if_assoc.h new file mode 100644 index 00000000..730e2fb7 --- /dev/null +++ b/kactivities/src/utils/find_if_assoc.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation,3 Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef UTILS_FIND_IF_ASSOC_H +#define UTILS_FIND_IF_ASSOC_H + +#include +#include + +/******************************************************************** + * Associative container's find_if (for hash, map, and similar ) * + ********************************************************************/ + +namespace kamd { +namespace utils { + +namespace details { + + // Iterator Functions + + template + Function qt_find_if_assoc(Iterator start, Iterator end, Function f) + { + for ( ; start != end; ++ start ) { + if (f(start.key(), start.value())) break; + } + + return f; + } + + template + Function stl_find_if_assoc(Iterator start, Iterator end, Function f) + { + for ( ; start != end; ++ start ) { + if (f(start->first, start->second)) break; + } + + return f; + } + + // Container functions + + template + Function _find_if_assoc_helper_container(const Container & c, Function f, + decltype(&Container::constBegin) * ) + { + return qt_find_if_assoc(c.constBegin(), c.constEnd(), f); + } + + template + Function _find_if_assoc_helper_container(const Container & c, Function f, + decltype(&Container::cbegin) * ) + { + return stl_find_if_assoc(c.cbegin(), c.cend(), f); + } + +} // namespace details + +template +Function find_if_assoc(const Container & c, Function f) +{ + return details::_find_if_assoc_helper_container + (c, f, nullptr); +} + +} // namespace utils +} // namespace kamd + +#endif // UTILS_FIND_IF_ASSOC_H diff --git a/kactivities/src/utils/for_each_assoc.h b/kactivities/src/utils/for_each_assoc.h new file mode 100644 index 00000000..ee58727a --- /dev/null +++ b/kactivities/src/utils/for_each_assoc.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation,3 Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef UTILS_FOR_EACH_ASSOC_H +#define UTILS_FOR_EACH_ASSOC_H + +#include +#include + +/******************************************************************** + * Associative container's for_each (for hash, map, and similar ) * + ********************************************************************/ + +namespace kamd { +namespace utils { + +namespace details { + +// Iterator Functions + +template +Function qt_for_each_assoc(Iterator start, Iterator end, Function f) +{ + for ( ; start != end; ++ start ) f(start.key(), start.value()); + + return f; +} + +template +Function stl_for_each_assoc(Iterator start, Iterator end, Function f) +{ + for ( ; start != end; ++ start ) f(start->first, start->second); + + return f; +} + +// Container functions + +template +Function _for_each_assoc_helper_container(const Container & c, Function f, + decltype(&Container::constBegin) * ) +{ + return qt_for_each_assoc(c.constBegin(), c.constEnd(), f); +} + +template +Function _for_each_assoc_helper_container(const Container & c, Function f, + decltype(&Container::cbegin) * ) +{ + return stl_for_each_assoc(c.cbegin(), c.cend(), f); +} + +} // namespace details + +template +Function for_each_assoc(const Container & c, Function f) +{ + return details::_for_each_assoc_helper_container + (c, f, nullptr); +} + +} // namespace utils +} // namespace kamd + +#endif // UTILS_FOR_EACH_ASSOC_H diff --git a/kactivities/src/utils/merge_into.h b/kactivities/src/utils/merge_into.h new file mode 100644 index 00000000..5a2f204f --- /dev/null +++ b/kactivities/src/utils/merge_into.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation,3 Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef UTILS_MERGE_INTO_H +#define UTILS_MERGE_INTO_H + +namespace kamd { +namespace utils { + +template +inline void merge_into(Container & into, const Container & from) +{ + typename Container::iterator into_begin = into.begin(); + typename Container::iterator into_end = into.end(); + typename Container::const_iterator from_begin = from.begin(); + typename Container::const_iterator from_end = from.end(); + + while (from_begin != from_end) { + while (into_begin != into_end && *from_begin >= *into_begin) + into_begin++; + + into_begin = into.insert(into_begin, *from_begin); + into_begin++; + from_begin++; + } +} + +} // namespace utils +} // namespace kamd + +#endif // UTILS_MERGE_INTO_H diff --git a/kactivities/src/utils/nullptr.h b/kactivities/src/utils/nullptr.h new file mode 100644 index 00000000..21f5ef97 --- /dev/null +++ b/kactivities/src/utils/nullptr.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation,3 Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef UTILS_NULLPTR_H +#define UTILS_NULLPTR_H + +#include + +#if !HAVE_CXX11_NULLPTR && !defined(nullptr) + #warning "This compiler doesn't support nullptr" + #define nullptr 0 +#endif + +#endif // UTILS_NULLPTR_H diff --git a/kactivities/src/utils/override.h b/kactivities/src/utils/override.h new file mode 100644 index 00000000..49b97911 --- /dev/null +++ b/kactivities/src/utils/override.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation,3 Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef UTILS_OVERRIDE_MACRO_H +#define UTILS_OVERRIDE_MACRO_H + +#include + +#if HAVE_CXX11_OVERRIDE + #define _override override +#elif defined(HAVE_CXX_OVERRIDE_ATTR) + #warning "The override keyword is not supported by the compiler. Trying the override attribute." + #define _override __attribute__((override)) +#else + #warning "This compiler can not check for overriden methods." + #define _override // nothing +#endif + +#endif // UTILS_OVERRIDE_MACRO_H diff --git a/kactivities/src/utils/remove_if.h b/kactivities/src/utils/remove_if.h new file mode 100644 index 00000000..3f20ed88 --- /dev/null +++ b/kactivities/src/utils/remove_if.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation,3 Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef UTILS_REMOVE_IF_H +#define UTILS_REMOVE_IF_H + +#include +#include + +/******************************************************************** + * Syntactic sugar for the erase-remove idiom * + ********************************************************************/ + +namespace kamd { +namespace utils { + +template +__inline void remove_if(Collection & collection, Filter filter) +{ + collection.erase( + std::remove_if( + collection.begin(), collection.end(), filter + ), collection.end() + ); +} + +} // namespace utils +} // namespace kamd + +#endif // UTILS_REMOVE_IF_H diff --git a/kactivities/src/utils/val.h b/kactivities/src/utils/val.h new file mode 100644 index 00000000..24146499 --- /dev/null +++ b/kactivities/src/utils/val.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation,3 Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef UTILS_VAL_H +#define UTILS_VAL_H + +/// Declares a value. Alias for const auto +#define val const auto + +#endif // UTILS_VAL_H diff --git a/kactivities/src/workspace/CMakeLists.txt b/kactivities/src/workspace/CMakeLists.txt new file mode 100644 index 00000000..907486d0 --- /dev/null +++ b/kactivities/src/workspace/CMakeLists.txt @@ -0,0 +1,29 @@ + +set (sdo_SRCS "") + +# C++11 + +string (COMPARE EQUAL "${CMAKE_CXX_COMPILER_ID}" "Clang" CMAKE_COMPILER_IS_CLANG) + +if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG) + message (STATUS "We have GNU or Clang, adding -std=c++0x flag") + add_definitions ("-std=c++0x") + set (ADDITIONAL_DEFINITIONS "-std=c++0x") +endif () + +if (NepomukCore_FOUND) + include_directories (${NEPOMUK_CORE_INCLUDE_DIR}) + add_subdirectory (fileitemplugin) + add_subdirectory (kio) +endif () + +find_package (KDeclarative) + +macro_log_feature ( + KDeclarative_FOUND "libkdeclarative" "KDE Declarative (QML) support from kdelibs is needed for the settings module" "http://www.kde.org" + TRUE "" "" +) + +if (KDeclarative_FOUND) + add_subdirectory (settings) +endif () diff --git a/kactivities/src/workspace/fileitemplugin/CMakeLists.txt b/kactivities/src/workspace/fileitemplugin/CMakeLists.txt new file mode 100644 index 00000000..1d8e152c --- /dev/null +++ b/kactivities/src/workspace/fileitemplugin/CMakeLists.txt @@ -0,0 +1,23 @@ +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${KDE4_INCLUDES} + ) + + +set (kactivitymanagerd_fileitem_linking_plugin_SRCS + FileItemLinkingPlugin.cpp + ) + +kde4_add_plugin (kactivitymanagerd_fileitem_linking_plugin ${kactivitymanagerd_fileitem_linking_plugin_SRCS}) + +target_link_libraries ( + kactivitymanagerd_fileitem_linking_plugin + ${KDE4_KIO_LIBS} + kactivities + ) + +install (TARGETS kactivitymanagerd_fileitem_linking_plugin DESTINATION ${PLUGIN_INSTALL_DIR}) +install (FILES kactivitymanagerd_fileitem_linking_plugin.desktop DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/kactivities/src/workspace/fileitemplugin/FileItemLinkingPlugin.cpp b/kactivities/src/workspace/fileitemplugin/FileItemLinkingPlugin.cpp new file mode 100644 index 00000000..9161fa19 --- /dev/null +++ b/kactivities/src/workspace/fileitemplugin/FileItemLinkingPlugin.cpp @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "FileItemLinkingPlugin.h" +#include "FileItemLinkingPlugin_p.h" + +#include + +#include +#include + +#include +#include + +void FileItemLinkingPlugin::Private::actionTriggered() +{ + QAction * action = dynamic_cast < QAction * > (sender()); + + if (!action) return; + + bool link = action->property("link").toBool(); + QString activity = action->property("activity").toString(); + + foreach (const KUrl & item, items) { + if (link) { + activities.linkResourceToActivity(item, activity); + + } else { + activities.unlinkResourceFromActivity(item, activity); + + } + } +} + +void FileItemLinkingPlugin::Private::addAction( + const QString & activity, + bool link, + const QString & title, + const QString & icon + ) +{ + QAction * action = rootMenu->addAction( + title.isEmpty() ? KActivities::Info::name(activity) : title + ); + + if (!icon.isEmpty()) { + action->setIcon(QIcon::fromTheme(icon)); + } + + action->setProperty("activity", activity); + action->setProperty("link", link); + + connect(action, SIGNAL(triggered()), + this, SLOT(actionTriggered())); + + action->setVisible(false); +} + +void FileItemLinkingPlugin::Private::addSeparator( + const QString & title) +{ + QAction * separator = rootMenu->addSeparator(); + separator->setText(title); + separator->setVisible(false); +} + +FileItemLinkingPlugin::FileItemLinkingPlugin(QObject * parent, const QVariantList &) + : KAbstractFileItemActionPlugin(parent), d(new Private()) +{ +} + +FileItemLinkingPlugin::~FileItemLinkingPlugin() +{ + if (!d->thread) delete d; +} + +QList FileItemLinkingPlugin::actions(const KFileItemListProperties & fileItemInfos, QWidget * parentWidget) +{ + QAction * root = new QAction(QIcon::fromTheme("preferences-activities"), i18n("Activities..."), parentWidget); + + connect(root, SIGNAL(triggered()), + d, SLOT(showActions())); + + d->items = fileItemInfos.urlList(); + + return QList < QAction * > () << root; +} + +void FileItemLinkingPlugin::Private::showActions() +{ + thread = new FileItemLinkingPluginLoader(this, items); + + connect(thread, SIGNAL(finished()), + thread, SLOT(deleteLater())); + + connect(thread, SIGNAL(requestAction(QString, bool, QString, QString)), + this, SLOT(addAction(QString, bool, QString, QString)), + Qt::QueuedConnection + ); + connect(thread, SIGNAL(requestSeparator(QString)), + this, SLOT(addSeparator(QString)), + Qt::QueuedConnection + ); + connect(thread, SIGNAL(finishedLoading()), + this, SLOT(finishedLoading()), + Qt::QueuedConnection + ); + + rootMenu = new QMenu(); + + rootMenu->addAction("Loading..."); + + rootMenu->popup(QCursor::pos()); + + connect(rootMenu, SIGNAL(aboutToHide()), + this, SLOT(deleteLater())); + + thread->start(); +} + +void FileItemLinkingPlugin::Private::finishedLoading() +{ + rootMenu->removeAction(rootMenu->actions()[0]); + + foreach (QAction * action, rootMenu->actions()) { + action->setVisible(true); + } + + rootMenu->popup(QCursor::pos()); +} + +FileItemLinkingPluginLoader::FileItemLinkingPluginLoader( + QObject * parent, const KUrl::List & items) + : QThread(/*parent*/), m_items(items) +{ + +} + +void FileItemLinkingPluginLoader::run() +{ + const unsigned itemCount = m_items.size(); + + if (itemCount > 10) { + // we are not going to check for this large number of files + emit requestAction( + QString(), + false, + i18n("Unlink from the current activity"), + "list-remove" + ); + emit requestAction( + QString(), + true, + i18n("Link to the current activity"), + "list-add" + ); + + const QStringList activitiesList = activities.listActivities(); + + emit requestSeparator(i18n("Link to:")); + + foreach (const QString & activity, activitiesList) { + emit requestAction(activity, true); + } + + emit requestSeparator(i18n("Unlink from:")); + + foreach (const QString & activity, activitiesList) { + emit requestAction(activity, false); + } + + } else if (itemCount != 0) { + + bool haveLinked = false; + bool haveUnlinked = false; + + foreach (const KUrl & url, m_items) { + (activities.isResourceLinkedToActivity(url) ? haveLinked : haveUnlinked) = true; + } + + if (haveLinked) { + emit requestAction( + QString(), + false, + i18n("Unlink from the current activity"), + "list-remove" + ); + } + + if (haveUnlinked) { + emit requestAction( + QString(), + true, + i18n("Link to the current activity"), + "list-add" + ); + } + + QStringList linkable, unlinkable; + + foreach (const QString & activity, activities.listActivities()) { + haveLinked = haveUnlinked = false; + + foreach (const KUrl & url, m_items) { + (activities.isResourceLinkedToActivity(url, activity) ? haveLinked : haveUnlinked) = true; + } + + if (haveLinked) { + unlinkable << activity; + } + + if (haveUnlinked) { + linkable << activity; + } + } + + emit requestSeparator(i18n("Link to:")); + + foreach (const QString & activity, linkable) { + emit requestAction(activity, true); + } + + emit requestSeparator(i18n("Unlink from:")); + + foreach (const QString & activity, unlinkable) { + emit requestAction(activity, false); + } + + } + + emit finishedLoading(); +} + +K_PLUGIN_FACTORY(FileItemLinkingPluginFactory, registerPlugin();) +K_EXPORT_PLUGIN(FileItemLinkingPluginFactory("kactivitymanagerd_fileitem_linking_plugin")) + diff --git a/kactivities/src/workspace/fileitemplugin/FileItemLinkingPlugin.h b/kactivities/src/workspace/fileitemplugin/FileItemLinkingPlugin.h new file mode 100644 index 00000000..b60c69d4 --- /dev/null +++ b/kactivities/src/workspace/fileitemplugin/FileItemLinkingPlugin.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FILE_ITEM_LINKING_PLUGIN_H +#define FILE_ITEM_LINKING_PLUGIN_H + +#include + +#include +#include +#include + +/** + * FileItemLinkingPlugin + */ +class FileItemLinkingPlugin: public KAbstractFileItemActionPlugin { +public: + FileItemLinkingPlugin(QObject * parent, const QVariantList &); + virtual ~FileItemLinkingPlugin(); + + virtual QList actions (const KFileItemListProperties &fileItemInfos, + QWidget *parentWidget); + +private: + class Private; + Private * const d; +}; + +#endif // FILE_ITEM_LINKING_PLUGIN_H + diff --git a/kactivities/src/workspace/fileitemplugin/FileItemLinkingPlugin_p.h b/kactivities/src/workspace/fileitemplugin/FileItemLinkingPlugin_p.h new file mode 100644 index 00000000..2d15cc38 --- /dev/null +++ b/kactivities/src/workspace/fileitemplugin/FileItemLinkingPlugin_p.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FILE_ITEM_LINKING_PLUGIN_P_H +#define FILE_ITEM_LINKING_PLUGIN_P_H + +#include "FileItemLinkingPlugin.h" +#include + +#include + +#include + +#include "lib/core/consumer.h" +#include "lib/core/info.h" + +class FileItemLinkingPlugin::Private: public QObject { + Q_OBJECT + +public: + KActivities::Consumer activities; + KUrl::List items; + QMenu * rootMenu; + QThread * thread; + +public Q_SLOTS: + void actionTriggered(); + void showActions(); + + void addAction( + const QString & activity, + bool link, + const QString & title = QString(), + const QString & icon = QString() + ); + + void addSeparator(const QString & title); + + void finishedLoading(); + +}; + +class FileItemLinkingPluginLoader: public QThread { + Q_OBJECT + +public: + FileItemLinkingPluginLoader( + QObject * parent, const KUrl::List & items); + +Q_SIGNALS: + void requestAction( + const QString & activity, + bool link, + const QString & title = QString(), + const QString & icon = QString() + ); + + void requestSeparator(const QString & title); + + void finishedLoading(); + +protected: + void run(); //override + +public: + KActivities::Consumer activities; + KUrl::List m_items; +}; + + +#endif // FILE_ITEM_LINKING_PLUGIN_P_H + diff --git a/kactivities/src/workspace/fileitemplugin/Messages.sh b/kactivities/src/workspace/fileitemplugin/Messages.sh new file mode 100644 index 00000000..a80d754c --- /dev/null +++ b/kactivities/src/workspace/fileitemplugin/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp -o $podir/kactivitymanagerd_fileitem_linking_plugin.pot diff --git a/kactivities/src/workspace/fileitemplugin/kactivitymanagerd_fileitem_linking_plugin.desktop b/kactivities/src/workspace/fileitemplugin/kactivitymanagerd_fileitem_linking_plugin.desktop new file mode 100644 index 00000000..95f87c5d --- /dev/null +++ b/kactivities/src/workspace/fileitemplugin/kactivitymanagerd_fileitem_linking_plugin.desktop @@ -0,0 +1,53 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=File to activity linking plugin +Name[bs]=Datoteka dodatka za povezivanje na aktivnost +Name[ca]=Fitxer a connector d'enllaç d'activitat +Name[ca@valencia]=Fitxer a connector d'enllaç d'activitat +Name[da]=Fil til plugin til aktivitetslinking +Name[de]=Modul für die Verknüpfung von Dateien zu Aktivitäten +Name[el]=Αρχείο σε πρόσθετο σύνδεσης δραστηριοτήτων +Name[en_GB]=File to activity linking plugin +Name[es]=Complemento de enlace de archivo a actividad +Name[et]=Faili tegevusega linkimise plugin +Name[eu]=Fitxategia jarduerarekin lotzeko plugina +Name[fi]=Tiedostojen yhdistäminen aktiviteettiin +Name[fr]=Module externe de liaison entre fichiers et activités +Name[gl]=Complemento para ligar ficheiros con actividades +Name[he]=תוסף לקישור פעילויות אל קובץ +Name[hu]=Fájl-aktivitás összelinkelő bővítmény +Name[ia]=File pro Plugin ligante de activitate +Name[it]=Estensione di collegamento tra file e attività +Name[kk]=Белсенділікті файлмен байланыстыру плагині +Name[km]=ឯកសារ​ទៅ​កម្មវិធី​ជំនួយ​​កំណត់​ជួរ​សកម្មភាព +Name[ko]=활동 연결 플러그인 파일 +Name[lt]=Failo pririšimo prie veiklos priedas +Name[mr]=फाईल कार्यपध्दतीस जोडणारे प्लगइन +Name[nb]=Programtillegg som binder fil til aktivitet +Name[nds]=Datei-na-Aktiviteet-Towiesmoduul +Name[nl]=Plug-in voor koppelen van bestand aan activiteit +Name[pa]=ਐਕਟਵਿਟੀ ਲਿੰਕਿੰਗ ਪਲੱਗਇਨ ਲਈ ਫਾਇਲ +Name[pl]=Plik dla wtyczki łączącej działania +Name[pt]='Plugin' de ligação de ficheiros a actividades +Name[pt_BR]=Plugin de ligação de arquivo a atividade +Name[ro]=Extensie pentru legarea fișierelor la activități +Name[ru]=Модуль связывания файлов с комнатами +Name[sk]=Súbor na plugin linkovania aktivít +Name[sl]=Vstavek za povezovanje datoteke z dejavnostjo +Name[sr]=Фајл ка прикључку повезивања активности +Name[sr@ijekavian]=Фајл ка прикључку повезивања активности +Name[sr@ijekavianlatin]=Fajl ka priključku povezivanja aktivnosti +Name[sr@latin]=Fajl ka priključku povezivanja aktivnosti +Name[sv]=Länkinsticksprogram från fil till aktivitet +Name[tg]=Файл барои плагини баҳодиҳии фаъолият +Name[tr]=Etkinliğe dosya bağlama eklentisi +Name[ug]=پائالىيەت ئۇلانما قىستۇرما ھۆججىتى +Name[uk]=Додаток прив’язування файлів до просторів дій +Name[x-test]=xxFile to activity linking pluginxx +Name[zh_CN]=文件关联活动插件 +Name[zh_TW]=活動連結外掛程式 +Icon=preferences-activities +X-KDE-Library=kactivitymanagerd_fileitem_linking_plugin +ServiceTypes=KFileItemAction/Plugin +MimeType=all/all;application/octet-stream;inode/directory; diff --git a/kactivities/src/workspace/kio/CMakeLists.txt b/kactivities/src/workspace/kio/CMakeLists.txt new file mode 100644 index 00000000..7a1b21ae --- /dev/null +++ b/kactivities/src/workspace/kio/CMakeLists.txt @@ -0,0 +1,53 @@ +project (kioslave-activities) + +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${KDE4_INCLUDES} + ${KDE4_KIO_INCLUDES} + ${SOPRANO_INCLUDE_DIR} + ) + +########### next target ############### + +set (kio_activities_PART_SRCS + KioActivities.cpp + ) + +soprano_add_ontology ( + kio_activities_PART_SRCS + ${CMAKE_SOURCE_DIR}/src/ontologies/kao.trig + "KAO" "KDE::Vocabulary" "trig" + ) + + +kde4_add_plugin ( + kio_activities + ${kio_activities_PART_SRCS} + ) + +target_link_libraries ( + kio_activities + ${KDE4_KIO_LIBS} + ${NEPOMUK_CORE_LIBRARY} + ${SOPRANO_LIBRARIES} + kactivities + ) + +install ( + TARGETS + kio_activities + DESTINATION + ${PLUGIN_INSTALL_DIR} + ) + +########### install files ############### + +install ( + FILES + activities.protocol + DESTINATION + ${SERVICES_INSTALL_DIR} + ) diff --git a/kactivities/src/workspace/kio/KioActivities.cpp b/kactivities/src/workspace/kio/KioActivities.cpp new file mode 100644 index 00000000..7190b5f3 --- /dev/null +++ b/kactivities/src/workspace/kio/KioActivities.cpp @@ -0,0 +1,445 @@ +/* + * Copyright 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "KioActivities.h" + +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "kao.h" + +#include +#include +#include +#include +#include + +#include "lib/core/info.h" + +#include + +namespace Nepomuk = Nepomuk2; +using namespace Soprano::Vocabulary; +using namespace KDE::Vocabulary; +using namespace KIO; + +class ActivitiesProtocol::Private { +public: + Private(ActivitiesProtocol * parent) + : kio(parent) + { + } + + enum Path { + RootItem, + ActivityRootItem, + ActivityPathItem, + PrivateActivityPathItem + }; + + KActivities::Consumer activities; + QString activity; + QString filename; + + inline QString realActivityId(const QString & activity) const + { + if (activity == "current") { + return activities.currentActivity(); + } + + return activity; + } + + KIO::UDSEntry createFolderUDSEntry(const QString & name, const QString & displayName, const QDate & date) const + { + KIO::UDSEntry uds; + QDateTime dt(date, QTime(0, 0, 0)); + uds.insert(KIO::UDSEntry::UDS_NAME, name); + uds.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, displayName); + uds.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + uds.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory")); + uds.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, dt.toTime_t()); + uds.insert(KIO::UDSEntry::UDS_CREATION_TIME, dt.toTime_t()); + uds.insert(KIO::UDSEntry::UDS_ACCESS, 0700); + uds.insert(KIO::UDSEntry::UDS_USER, KUser().loginName()); + return uds; + } + + KIO::UDSEntry createUDSEntryForUrl(const KUrl & url) const + { + KIO::UDSEntry uds; + + KFileItem fileItem(KFileItem::Unknown, KFileItem::Unknown, url, false); + + QByteArray encodedPath = QUrl::toPercentEncoding(url.url()); + + uds.insert(KIO::UDSEntry::UDS_NAME, QString::fromUtf8(encodedPath)); + uds.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, fileItem.name()); + uds.insert(KIO::UDSEntry::UDS_MIME_TYPE, fileItem.mimetype()); + uds.insert(KIO::UDSEntry::UDS_SIZE, fileItem.size()); + uds.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, fileItem.time(KFileItem::ModificationTime).toTime_t()); + uds.insert(KIO::UDSEntry::UDS_CREATION_TIME, fileItem.time(KFileItem::CreationTime).toTime_t()); + uds.insert(KIO::UDSEntry::UDS_ACCESS, fileItem.permissions()); + uds.insert(KIO::UDSEntry::UDS_USER, KUser().loginName()); + uds.insert(KIO::UDSEntry::UDS_LOCAL_PATH, url.toLocalFile()); + uds.insert(KIO::UDSEntry::UDS_TARGET_URL, url.prettyUrl()); + + if (fileItem.isDir()) + uds.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + + return uds; + } + + Nepomuk::Query::Query buildQuery(const QString & activity) const + { + Nepomuk::Resource activityResource(activity, KAO::Activity()); + + Nepomuk::Query::FileQuery query; + + Nepomuk::Query::ComparisonTerm usedActivity(NAO::isRelated(), Nepomuk::Query::ResourceTerm(activityResource)); + usedActivity.setInverted(true); + + query.setTerm(usedActivity); + + return query; + } + + void listActivities() const + { + kio->listEntry(createFolderUDSEntry( + QString::fromLatin1("current"), + i18n("Current activity"), + QDate::currentDate()), false + ); + + foreach (const QString & activity, activities.listActivities()) { + kio->listEntry(createFolderUDSEntry( + activity, + KActivities::Info::name(realActivityId(activity)), + QDate::currentDate()), false + ); + } + + kio->listEntry(KIO::UDSEntry(), true); + kio->finished(); + } + + void listActivity() const + { + // We need this to be changeable in a const method + QString activity = realActivityId(this->activity); + + if (!activity.isEmpty()) { + Nepomuk::Resource activityResource(activity, KAO::Activity()); + + const QString query = QString::fromLatin1( + "select distinct ?r, ?url where { " + " ?r a nfo:FileDataObject . " + " ?r nie:url ?url . " + " %1 nao:isRelated ?r . " + "} " + ); + + Soprano::QueryResultIterator it + = Nepomuk::ResourceManager::instance()->mainModel()->executeQuery( + query.arg(Soprano::Node::resourceToN3(activityResource.uri())), + Soprano::Query::QueryLanguageSparql); + + while (it.next()) { + QUrl resource = it[0].uri(); + QUrl file = it[1].uri(); + + + kio->listEntry(createUDSEntryForUrl(it[1].uri()), false); + + } + + it.close(); + + } + + + // kio->listEntry(createFolderUDSEntry( + // "_test", "_Test", QDate::currentDate()), false); + kio->listEntry(KIO::UDSEntry(), true); + kio->finished(); + } + + Path parseUrl(const KUrl & url) + { + activity.clear(); + filename.clear(); + + if (url.path().length() <= 1) { + return RootItem; + } + + QStringList path = url.path().split('/', QString::SkipEmptyParts); + + if (path.isEmpty()) { + return RootItem; + } + + activity = path.takeFirst(); + + if (path.isEmpty()) { + return (KActivities::Info(realActivityId(activity)).isEncrypted()) + ? PrivateActivityPathItem : ActivityRootItem; + } + + filename = path.join("/"); + + return (KActivities::Info(realActivityId(activity)).isEncrypted()) + ? PrivateActivityPathItem : ActivityRootItem; + } + +private: + ActivitiesProtocol * const kio; +}; + + +ActivitiesProtocol::ActivitiesProtocol(const QByteArray & poolSocket, const QByteArray & appSocket) + : KIO::ForwardingSlaveBase("activities", poolSocket, appSocket), d(new Private(this)) +{ +} + + +ActivitiesProtocol::~ActivitiesProtocol() +{ + delete d; +} + + +void ActivitiesProtocol::listDir(const KUrl & url) +{ + // TODO: Test whether necessary services are running + if (false) { + error(KIO::ERR_SLAVE_DEFINED, i18n("Activity manager is not running properly.")); + return; + } + + switch (d->parseUrl(url)) { + case Private::RootItem: + d->listActivities(); + break; + + case Private::ActivityRootItem: + d->listActivity(); + break; + + case Private::ActivityPathItem: + case Private::PrivateActivityPathItem: + ForwardingSlaveBase::listDir(url); + break; + + default: + error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl()); + break; + } +} + + +void ActivitiesProtocol::mkdir(const KUrl & url, int permissions) +{ + Q_UNUSED(permissions); + error(ERR_UNSUPPORTED_ACTION, url.prettyUrl()); +} + + +void ActivitiesProtocol::get(const KUrl & url) +{ + if (d->parseUrl(url)) { + ForwardingSlaveBase::get(url); + + } else { + error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl()); + + } +} + + +void ActivitiesProtocol::put(const KUrl & url, int permissions, KIO::JobFlags flags) +{ + if (d->parseUrl(url)) { + ForwardingSlaveBase::put(url, permissions, flags); + } + else { + error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl()); + } +} + + +void ActivitiesProtocol::copy(const KUrl & src, const KUrl & dest, int permissions, KIO::JobFlags flags) +{ + Q_UNUSED(src); + Q_UNUSED(dest); + Q_UNUSED(permissions); + Q_UNUSED(flags); + + error(ERR_UNSUPPORTED_ACTION, src.prettyUrl()); +} + + +void ActivitiesProtocol::rename(const KUrl & src, const KUrl & dest, KIO::JobFlags flags) +{ + Q_UNUSED(src); + Q_UNUSED(dest); + Q_UNUSED(flags); + + error(ERR_UNSUPPORTED_ACTION, src.prettyUrl()); +} + + +void ActivitiesProtocol::del(const KUrl & url, bool isfile) +{ + Q_UNUSED(url); + Q_UNUSED(isfile); + // qDebug() << url; + // ForwardingSlaveBase::del(url, isfile); + + error(ERR_UNSUPPORTED_ACTION, url.prettyUrl()); +} + + +void ActivitiesProtocol::mimetype(const KUrl & url) +{ + // qDebug() << url; + ForwardingSlaveBase::mimetype(url); +} + + +void ActivitiesProtocol::stat(const KUrl & url) +{ + switch (d->parseUrl(url)) { + case Private::RootItem: + { + KIO::UDSEntry uds; + uds.insert(KIO::UDSEntry::UDS_NAME, QString::fromLatin1("/")); + uds.insert(KIO::UDSEntry::UDS_ICON_NAME, QString::fromLatin1("preferences-activities")); + uds.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + uds.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory")); + statEntry(uds); + finished(); + break; + } + + case Private::ActivityRootItem: + { + KIO::UDSEntry uds; + uds.insert(KIO::UDSEntry::UDS_NAME, d->activity); + uds.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + uds.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory")); + statEntry(uds); + finished(); + break; + } + + case Private::ActivityPathItem: + case Private::PrivateActivityPathItem: + { + // QString path = QUrl::fromPercentEncoding(d->filename.toUtf8()); + // statEntry(d->createUDSEntryForUrl(KUrl(path))); + // finished(); + + ForwardingSlaveBase::stat(url); + + break; + } + + default: + error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl()); + break; + } +} + + +// only used for the queries +bool ActivitiesProtocol::rewriteUrl(const KUrl & url, KUrl & newURL) +{ + Private::Path pathType = d->parseUrl(url); + + if (pathType == Private::ActivityPathItem) { + QString path = QUrl::fromPercentEncoding(d->filename.toUtf8()); + newURL = KUrl(path); + + return true; + } + + if (pathType == Private::PrivateActivityPathItem) { + static QDir activitiesDataFolder = QDir(KStandardDirs::locateLocal("data", "activitymanager/activities")); + + newURL = KUrl("file://" + activitiesDataFolder.filePath("crypt-" + d->activity + "/user/" + d->filename)); + + return true; + } + + return false; +} + + +void ActivitiesProtocol::prepareUDSEntry(KIO::UDSEntry & entry, + bool listing) const +{ + ForwardingSlaveBase::prepareUDSEntry(entry, listing); +} + +extern "C" +{ + KDE_EXPORT int kdemain(int argc, char **argv) + { + // necessary to use other kio slaves + KComponentData("kio_activities"); + QCoreApplication app(argc, argv); + + // qDebug() << "Starting activities slave " << getpid(); + + if (argc != 4) { + // kError() << "Usage: kio_activities protocol domain-socket1 domain-socket2"; + exit(-1); + } + + ActivitiesProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + // qDebug() << "activities slave Done"; + + return 0; + } +} + +#include "KioActivities.moc" diff --git a/kactivities/src/workspace/kio/KioActivities.h b/kactivities/src/workspace/kio/KioActivities.h new file mode 100644 index 00000000..2802c521 --- /dev/null +++ b/kactivities/src/workspace/kio/KioActivities.h @@ -0,0 +1,101 @@ +/* + * Copyright 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KIO_ACTIVITIES_H +#define KIO_ACTIVITIES_H + +#include + +#include + +#include "lib/core/consumer.h" + +class ActivitiesProtocol: public KIO::ForwardingSlaveBase { + Q_OBJECT + +public: + ActivitiesProtocol(const QByteArray & poolSocket, const QByteArray & appSocket); + virtual ~ActivitiesProtocol(); + + /** + * List all files and folders tagged with the corresponding tag. + */ + void listDir(const KUrl & url); + + /** + * Results in the creation of a new tag. + */ + void mkdir(const KUrl & url, int permissions); + + /** + * Will be forwarded for files. + */ + void get(const KUrl & url); + + /** + * Not supported. + */ + void put(const KUrl & url, int permissions, KIO::JobFlags flags); + + /** + * Files and folders can be copied to the virtual folders resulting + * is assignment of the corresponding tag. + */ + void copy(const KUrl & src, const KUrl & dest, int permissions, KIO::JobFlags flags); + + /** + * File renaming will be forwarded. + * Folder renaming results in renaming of the tag. + */ + void rename(const KUrl & src, const KUrl & dest, KIO::JobFlags flags); + + /** + * File deletion means remocing the tag + * Folder deletion will result in deletion of the tag. + */ + void del(const KUrl & url, bool isfile); + + /** + * Files will be forwarded. + * Folders will be created as virtual folders. + */ + void mimetype(const KUrl & url); + + /** + * Files will be forwarded. + * Folders will be created as virtual folders. + */ + void stat(const KUrl & url); + +protected: + /** + * reimplemented from ForwardingSlaveBase + */ + bool rewriteUrl(const KUrl & url, KUrl & newURL); + + void prepareUDSEntry(KIO::UDSEntry & entry, + bool listing = false) const; + +private: + class Private; + Private * const d; +}; + +#endif // KIO_ACTIVITIES_H diff --git a/kactivities/src/workspace/kio/Messages.sh b/kactivities/src/workspace/kio/Messages.sh new file mode 100644 index 00000000..ce38d9c5 --- /dev/null +++ b/kactivities/src/workspace/kio/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp -o $podir/kio_activities.pot diff --git a/kactivities/src/workspace/kio/activities.protocol b/kactivities/src/workspace/kio/activities.protocol new file mode 100644 index 00000000..55c227c7 --- /dev/null +++ b/kactivities/src/workspace/kio/activities.protocol @@ -0,0 +1,16 @@ +[Protocol] +exec=kio_activities +protocol=activities +input=none +output=filesystem +reading=true +writing=true +deleting=true +linking=false +makedir=false +moving=false +listing=Name,Type,Size,Date,AccessDate,Access,Owner,Group,Link +source=false +Icon=nepomuk +Class=:local +maxInstances=100 diff --git a/kactivities/src/workspace/settings/BlacklistedApplicationsModel.cpp b/kactivities/src/workspace/settings/BlacklistedApplicationsModel.cpp new file mode 100644 index 00000000..06e0d27e --- /dev/null +++ b/kactivities/src/workspace/settings/BlacklistedApplicationsModel.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BlacklistedApplicationsModel.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + + +class BlacklistedApplicationsModel::Private { +public: + struct ApplicationData { + QString name; + QString title; + QString icon; + bool blocked; + }; + + QList applications; + QSqlDatabase database; + + KSharedConfig::Ptr pluginConfig; + bool enabled; +}; + +BlacklistedApplicationsModel::BlacklistedApplicationsModel(QObject * parent) + : QAbstractListModel(parent) +{ + QHash < int, QByteArray > roles; + roles[ApplicationIdRole] = "name"; + roles[Qt::DecorationRole] = "icon"; + roles[Qt::DisplayRole] = "title"; + roles[BlockedApplicationRole] = "blocked"; + setRoleNames(roles); + + d->enabled = false; + d->pluginConfig = KSharedConfig::openConfig("activitymanager-pluginsrc"); +} + +void BlacklistedApplicationsModel::load() +{ + // Loading plugin configuration + + val config = d->pluginConfig->group("Plugin-org.kde.kactivitymanager.resourcescoring"); + + val defaultBlockedValue = config.readEntry("blocked-by-default", false); + auto blockedApplications = QSet::fromList(config.readEntry("blocked-applications", QStringList())); + auto allowedApplications = QSet::fromList(config.readEntry("allowed-applications", QStringList())); + + // Reading new applications from the database + + val path = KStandardDirs::locateLocal("data", "activitymanager/resources/database", true); + + d->database = QSqlDatabase::addDatabase("QSQLITE", "plugins_sqlite_db_resources"); + d->database.setDatabaseName(path); + + if (!d->database.open()) { + qDebug() << "Failed to open the database" << path << d->database.lastError(); + return; + } + + auto query = d->database.exec("SELECT DISTINCT(initiatingAgent) FROM kext_ResourceScoreCache ORDER BY initiatingAgent"); + + if (d->applications.length() > 0) { + beginRemoveRows(QModelIndex(), 0, d->applications.length() - 1); + d->applications.clear(); + endRemoveRows(); + } + + while (query.next()) { + val name = query.value(0).toString(); + + if (defaultBlockedValue) { + if (!allowedApplications.contains(name)) + blockedApplications << name; + } else { + if (!blockedApplications.contains(name)) + allowedApplications << name; + } + } + + auto applications = (blockedApplications + allowedApplications).toList(); + + if (applications.length() > 0) { + qSort(applications); + + beginInsertRows(QModelIndex(), 0, applications.length() - 1); + + foreach (val & name, applications) { + val service = KService::serviceByDesktopName(name); + val blocked = blockedApplications.contains(name); + + if (service) { + d->applications << Private::ApplicationData { + name, + service->name(), + service->icon(), + blocked + }; + } else { + d->applications << Private::ApplicationData { name, name, name, blocked }; + } + } + + endInsertRows(); + } +} + +void BlacklistedApplicationsModel::save() +{ + auto config = d->pluginConfig->group("Plugin-org.kde.kactivitymanager.resourcescoring"); + QStringList blockedApplications; + QStringList allowedApplications; + + for (int i = 0; i < rowCount(); i++) { + (d->applications[i].blocked ? blockedApplications : allowedApplications) + << d->applications[i].name; + } + + config.writeEntry("allowed-applications", allowedApplications); + config.writeEntry("blocked-applications", blockedApplications); +} + +void BlacklistedApplicationsModel::defaults() +{ + for (int i = 0; i < rowCount(); i++) { + d->applications[i].blocked = false; + } + + dataChanged(QAbstractListModel::index(0), + QAbstractListModel::index(rowCount() - 1)); +} + +void BlacklistedApplicationsModel::toggleApplicationBlocked(int index) +{ + if (index > rowCount()) + return; + + d->applications[index].blocked = !d->applications[index].blocked; + dataChanged(QAbstractListModel::index(index), + QAbstractListModel::index(index)); + + emit changed(); +} + +QVariant BlacklistedApplicationsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(section) + Q_UNUSED(orientation) + Q_UNUSED(role) + return QVariant(); +} + +QVariant BlacklistedApplicationsModel::data(const QModelIndex & modelIndex, int role) const +{ + val index = modelIndex.row(); + + if (index > rowCount()) + return QVariant(); + + val & application = d->applications[index]; + + switch (role) { + default: + return QVariant(); + + case ApplicationIdRole: + return application.name; + + case Qt::DisplayRole: + return application.title; + + case Qt::DecorationRole: + return application.icon; + + case BlockedApplicationRole: + return application.blocked; + } +} + +int BlacklistedApplicationsModel::rowCount(const QModelIndex & parent) const +{ + Q_UNUSED(parent) + return d->applications.size(); +} + +bool BlacklistedApplicationsModel::enabled() const +{ + return d->enabled; +} + +void BlacklistedApplicationsModel::setEnabled(bool enabled) +{ + d->enabled = enabled; + emit enabledChanged(enabled); +} + +#include diff --git a/kactivities/src/workspace/settings/BlacklistedApplicationsModel.h b/kactivities/src/workspace/settings/BlacklistedApplicationsModel.h new file mode 100644 index 00000000..72828e6f --- /dev/null +++ b/kactivities/src/workspace/settings/BlacklistedApplicationsModel.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef BLACKLISTED_APPLICATIONS_MODEL_H +#define BLACKLISTED_APPLICATIONS_MODEL_H + +#include + +#include +#include +#include + +/** + * BlacklistedApplicationsModel + */ +class BlacklistedApplicationsModel: public QAbstractListModel { + Q_OBJECT + + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + +public: + BlacklistedApplicationsModel(QObject * parent = nullptr); + + enum Roles { + ApplicationIdRole = Qt::UserRole + 1, + BlockedApplicationRole + }; + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const _override; + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const _override; + int rowCount(const QModelIndex & parent = QModelIndex()) const _override; + +Q_SIGNALS: + void changed(); + void enabledChanged(bool enabled); + +public Q_SLOTS: + void toggleApplicationBlocked(int index); + + void setEnabled(bool); + bool enabled() const; + + void load(); + void save(); + void defaults(); + +private: + D_PTR; +}; + +#endif // BLACKLISTED_APPLICATIONS_MODEL_H + diff --git a/kactivities/src/workspace/settings/CMakeLists.txt b/kactivities/src/workspace/settings/CMakeLists.txt new file mode 100644 index 00000000..ed068146 --- /dev/null +++ b/kactivities/src/workspace/settings/CMakeLists.txt @@ -0,0 +1,47 @@ +project (KCMActivities) + +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${KDE4_INCLUDES} + ${KDECLARATIVE_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/ui/ +) + +set (KAMD_KCM_SRCS + kcm_activities.cpp + MainConfigurationWidget.cpp + BlacklistedApplicationsModel.cpp +) + +kde4_add_ui_files ( + KAMD_KCM_SRCS + ui/MainConfigurationWidgetBase.ui +) + +kde4_add_plugin (kcm_activities ${KAMD_KCM_SRCS}) + +target_link_libraries (kcm_activities + ${KDE4_KDEUI_LIBS} + ${KDE4_KCMUTILS_LIBS} + ${QT_QTSQL_LIBRARY} + ${QT_QTDECLARATIVE_LIBRARY} + ${QT_QTOPENGL_LIBRARY} + ${KDECLARATIVE_LIBRARIES} + ${KDE4_PLASMA_LIBS} +) + +install (TARGETS kcm_activities + DESTINATION ${PLUGIN_INSTALL_DIR} +) + +install (FILES kcm_activities.desktop + DESTINATION ${SERVICES_INSTALL_DIR} +) + +install (FILES + qml/BlacklistApplicationView.qml + DESTINATION ${DATA_INSTALL_DIR}/activitymanager/workspace/settings +) diff --git a/kactivities/src/workspace/settings/MainConfigurationWidget.cpp b/kactivities/src/workspace/settings/MainConfigurationWidget.cpp new file mode 100644 index 00000000..76c7c80d --- /dev/null +++ b/kactivities/src/workspace/settings/MainConfigurationWidget.cpp @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "MainConfigurationWidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ui_MainConfigurationWidgetBase.h" +#include "BlacklistedApplicationsModel.h" + +#include +#include + +K_PLUGIN_FACTORY ( ActivitiesKCMFactory, registerPlugin(); ) +K_EXPORT_PLUGIN ( ActivitiesKCMFactory("kcm_activities","kcm_activities") ) + +class MainConfigurationWidget::Private: public Ui::MainConfigurationWidgetBase { +public: + Ui::MainConfigurationWidgetBase ui; + + KSharedConfig::Ptr mainConfig; + KSharedConfig::Ptr pluginConfig; + KPluginSelector * pluginSelector; + BlacklistedApplicationsModel * blacklistedApplicationsModel; + KDeclarative kdeclarative; + Plasma::PackageStructure::Ptr structure; + QGraphicsObject * viewBlacklistedApplicationsRoot; +}; + +MainConfigurationWidget::MainConfigurationWidget(QWidget * parent, QVariantList args) + : KCModule( ActivitiesKCMFactory::componentData(), parent ), d() +{ + Q_UNUSED(args) + + val about = new KAboutData( + "kio_activities", 0, ki18n("Activities"), + KDE_VERSION_STRING, KLocalizedString(), KAboutData::License_GPL, + ki18n("(c) 2012 Ivan Cukic") + ); + + setAboutData(about); + + d->setupUi(this); + + // Plugin selector initialization + + val offers = KServiceTypeTrader::self()->query("ActivityManager/Plugin"); + val plugins = KPluginInfo::fromServices(offers); + + d->mainConfig = KSharedConfig::openConfig("activitymanagerrc"); + d->pluginConfig = KSharedConfig::openConfig("activitymanager-pluginsrc"); + + d->pluginSelector = new KPluginSelector(this); + d->pluginSelector->addPlugins( + plugins, + KPluginSelector::ReadConfigFile, + i18n("Available Features"), + QString(), + d->mainConfig + ); + d->tabWidget->addTab(d->pluginSelector, i18n("Plugins")); + + // Keep history initialization + + d->spinKeepHistory->setRange(0, INT_MAX); + + d->spinKeepHistory->setSuffix(ki18ncp("unit of time. months to keep the history", + " month", " months")); + d->spinKeepHistory->setPrefix(i18nc("for in 'keep history for 5 months'", "for ")); + d->spinKeepHistory->setSpecialValueText(i18nc("unlimited number of months", "forever")); + + // Clear recent history button + + auto menu = new QMenu(this); + + connect( + menu->addAction(i18n("Forget the last hour")), SIGNAL(triggered()), + this, SLOT(forgetLastHour()) + ); + connect( + menu->addAction(i18n("Forget the last two hours")), SIGNAL(triggered()), + this, SLOT(forgetTwoHours()) + ); + connect( + menu->addAction(i18n("Forget a day")), SIGNAL(triggered()), + this, SLOT(forgetDay()) + ); + connect( + menu->addAction(i18n("Forget everything")), SIGNAL(triggered()), + this, SLOT(forgetAll()) + ); + + d->buttonClearRecentHistory->setMenu(menu); + + // Activities must run! :) + + d->checkEnableActivities->setVisible(false); + + // Blacklist applications + d->blacklistedApplicationsModel = new BlacklistedApplicationsModel(this); + + QGraphicsScene * scene = new QGraphicsScene(this); + d->viewBlacklistedApplications->setScene(scene); + QDeclarativeEngine * engine = new QDeclarativeEngine(this); + + d->kdeclarative.setDeclarativeEngine(engine); + d->kdeclarative.initialize(); + d->kdeclarative.setupBindings(); + d->structure = Plasma::PackageStructure::load("Plasma/Generic"); + + engine->rootContext()->setContextProperty("applicationModel", d->blacklistedApplicationsModel); + QDeclarativeComponent component(engine, QUrl(QString(KAMD_DATA_DIR) + "workspace/settings/BlacklistApplicationView.qml")); + qDebug() << "Errors: " << component.errors(); + d->viewBlacklistedApplicationsRoot = qobject_cast(component.create()); + d->viewBlacklistedApplicationsRoot->setProperty("width", d->viewBlacklistedApplications->width()); + scene->addItem(d->viewBlacklistedApplicationsRoot); + + d->viewBlacklistedApplications->installEventFilter(this); + + // React to changes + + connect(d->checkEnableActivities, SIGNAL(toggled(bool)), this, SLOT(changed())); + connect(d->radioRememberAllApplications, SIGNAL(toggled(bool)), this, SLOT(changed())); + connect(d->radioRememberSpecificApplications, SIGNAL(toggled(bool)), this, SLOT(changed())); + connect(d->radioDontRememberApplications, SIGNAL(toggled(bool)), this, SLOT(changed())); + connect(d->spinKeepHistory, SIGNAL(valueChanged(int)), this, SLOT(changed())); + connect(d->pluginSelector, SIGNAL(changed(bool)), this, SLOT(changed())); + connect(d->blacklistedApplicationsModel, SIGNAL(changed()), this, SLOT(changed())); + + connect(d->radioRememberSpecificApplications, SIGNAL(toggled(bool)), + d->blacklistedApplicationsModel, SLOT(setEnabled(bool))); + connect(d->radioRememberSpecificApplications, SIGNAL(toggled(bool)), + d->viewBlacklistedApplications, SLOT(setEnabled(bool))); + connect(d->radioRememberSpecificApplications, SIGNAL(toggled(bool)), + d->checkBlacklistAllNotOnList, SLOT(setEnabled(bool))); + + defaults(); + + d->checkBlacklistAllNotOnList->setEnabled(false); + d->blacklistedApplicationsModel->setEnabled(false); + d->viewBlacklistedApplications->setEnabled(false); +} + +void MainConfigurationWidget::updateLayout() +{ + d->viewBlacklistedApplicationsRoot->setProperty("width", + d->viewBlacklistedApplications->width() - 32 + ); + d->viewBlacklistedApplicationsRoot->setProperty("minimumHeight", + d->viewBlacklistedApplications->height() - 32 + ); +} + +bool MainConfigurationWidget::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == d->viewBlacklistedApplications) { + if (event->type() == QEvent::Resize) { + updateLayout(); + } + } + + return false; +} + +void MainConfigurationWidget::defaults() +{ + d->checkEnableActivities->setChecked(true); + d->radioRememberAllApplications->click(); + d->spinKeepHistory->setValue(0); + d->pluginSelector->defaults(); + d->blacklistedApplicationsModel->defaults(); +} + +void MainConfigurationWidget::load() +{ + d->pluginSelector->load(); + d->blacklistedApplicationsModel->load(); + + val statisticsConfig = d->pluginConfig->group("Plugin-org.kde.kactivitymanager.resourcescoring"); + + val whatToRemember = (WhatToRemember) statisticsConfig.readEntry("what-to-remember", (int)AllApplications); + d->radioRememberAllApplications->setChecked(whatToRemember == AllApplications); + d->radioRememberSpecificApplications->setChecked(whatToRemember == SpecificApplications); + d->radioDontRememberApplications->setChecked(whatToRemember == NoApplications); + + d->spinKeepHistory->setValue(statisticsConfig.readEntry("keep-history-for", 0)); + d->checkBlacklistAllNotOnList->setChecked(statisticsConfig.readEntry("blocked-by-default", false)); +} + +void MainConfigurationWidget::save() +{ + d->pluginSelector->save(); + d->blacklistedApplicationsModel->save(); + + auto statisticsConfig = d->pluginConfig->group("Plugin-org.kde.kactivitymanager.resourcescoring"); + + WhatToRemember whatToRemember = AllApplications; + + if (d->radioRememberSpecificApplications->isChecked()) { + whatToRemember = SpecificApplications; + } else if (d->radioDontRememberApplications->isChecked()) { + whatToRemember = NoApplications; + } + + statisticsConfig.writeEntry("what-to-remember", (int)whatToRemember); + statisticsConfig.writeEntry("keep-history-for", d->spinKeepHistory->value()); + statisticsConfig.writeEntry("blocked-by-default", d->checkBlacklistAllNotOnList->isChecked()); + + auto pluginListConfig = d->mainConfig->group("Plugins"); + + pluginListConfig.writeEntry("org.kde.kactivitymanager.resourcescoringEnabled", + whatToRemember != NoApplications); + + statisticsConfig.sync(); + pluginListConfig.sync(); +} + +void MainConfigurationWidget::forget(int count, const QString & what) +{ + QDBusInterface rankingsservice( + "org.kde.ActivityManager", + "/ActivityManager/Resources/Scoring", + "org.kde.ActivityManager.Resources.Scoring" + ); + + rankingsservice.asyncCall( + "deleteRecentStats", QString(), count, what + ); +} + +void MainConfigurationWidget::forgetLastHour() +{ + forget(1, "h"); +} + +void MainConfigurationWidget::forgetTwoHours() +{ + forget(2, "h"); +} + +void MainConfigurationWidget::forgetDay() +{ + forget(1, "d"); +} + +void MainConfigurationWidget::forgetAll() +{ + forget(0, "everything"); +} + +#include "MainConfigurationWidget.moc" + diff --git a/kactivities/src/workspace/settings/MainConfigurationWidget.h b/kactivities/src/workspace/settings/MainConfigurationWidget.h new file mode 100644 index 00000000..16add033 --- /dev/null +++ b/kactivities/src/workspace/settings/MainConfigurationWidget.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MAIN_CONFIGURATION_WIDGET_H +#define MAIN_CONFIGURATION_WIDGET_H + +#include +#include + +#include +#include + +/** + * MainConfigurationWidget + */ +class MainConfigurationWidget: public KCModule { + Q_OBJECT +public: + MainConfigurationWidget(QWidget * parent, QVariantList args); + +public Q_SLOTS: + virtual void defaults() _override; + virtual void load() _override; + virtual void save() _override; + +private Q_SLOTS: + void updateLayout(); + + void forget(int count, const QString & what); + void forgetLastHour(); + void forgetTwoHours(); + void forgetDay(); + void forgetAll(); + +protected: + bool eventFilter(QObject *obj, QEvent *event); + +private: + enum WhatToRemember { + AllApplications = 0, + SpecificApplications = 1, + NoApplications = 2 + }; + + D_PTR; +}; + + +#endif // MAIN_CONFIGURATION_WIDGET_H + diff --git a/kactivities/src/workspace/settings/Messages.sh b/kactivities/src/workspace/settings/Messages.sh new file mode 100644 index 00000000..d8b64674 --- /dev/null +++ b/kactivities/src/workspace/settings/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` >> rc.cpp +$XGETTEXT *.cpp -o $podir/kcm_activities.pot diff --git a/kactivities/src/workspace/settings/kcm_activities.cpp b/kactivities/src/workspace/settings/kcm_activities.cpp new file mode 100644 index 00000000..40bdec5f --- /dev/null +++ b/kactivities/src/workspace/settings/kcm_activities.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "MainConfigurationWidget.h" diff --git a/kactivities/src/workspace/settings/kcm_activities.desktop b/kactivities/src/workspace/settings/kcm_activities.desktop new file mode 100644 index 00000000..efd1aa7c --- /dev/null +++ b/kactivities/src/workspace/settings/kcm_activities.desktop @@ -0,0 +1,107 @@ +[Desktop Entry] +Icon=preferences-activities +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kcm_activities +X-KDE-FactoryName=kcm_activities +X-KDE-ParentApp=kcontrol +X-KDE-System-Settings-Parent-Category=workspace-behavior +X-KDE-Weight=4 + +Name=Activities +Name[bs]=Aktivnosti +Name[ca]=Activitats +Name[ca@valencia]=Activitats +Name[cs]=Aktivity +Name[da]=Aktiviteter +Name[de]=Aktivitäten +Name[el]=Δραστηριότητες +Name[en_GB]=Activities +Name[es]=Actividades +Name[et]=Tegevused +Name[fi]=Aktiviteetit +Name[fr]=Activités +Name[ga]=Gníomhaíochtaí +Name[gl]=Actividades +Name[he]=פעילויות +Name[hu]=Aktivitások +Name[ia]=Activitates +Name[is]=Virkni +Name[it]=Attività +Name[kk]=Белсенділіктер +Name[km]=​សកម្មភាព +Name[ko]=활동 +Name[lt]=Veiklos +Name[mr]=कार्यपध्दती +Name[nb]=Aktiviteter +Name[nds]=Aktiviteten +Name[nl]=Activiteiten +Name[pa]=ਐਕਟਵਿਟੀ +Name[pl]=Działania +Name[pt]=Actividades +Name[pt_BR]=Atividades +Name[ro]=Activități +Name[ru]=Комнаты +Name[sk]=Aktivity +Name[sl]=Dejavnosti +Name[sr]=Активности +Name[sr@ijekavian]=Активности +Name[sr@ijekavianlatin]=Aktivnosti +Name[sr@latin]=Aktivnosti +Name[sv]=Aktiviteter +Name[tg]=Фаъолият +Name[tr]=Etkinlikler +Name[ug]=پائالىيەتلەر +Name[uk]=Простори дій +Name[x-test]=xxActivitiesxx +Name[zh_CN]=活动 +Name[zh_TW]=活動 +Comment=Configure the activities system +Comment[bs]=Konfiguriši sistem aktivnosti +Comment[ca]=Configura el sistema d'activitats +Comment[ca@valencia]=Configura el sistema d'activitats +Comment[cs]=Nastavit systém aktivit +Comment[da]=Indstil aktivitetssystemet +Comment[de]=Einrichtung des Aktivitätensystems +Comment[el]=Διαμόρφωση του συστήματος δραστηριοτήτων +Comment[en_GB]=Configure the activities system +Comment[es]=Configurar el sistema de actividades +Comment[et]=Tegevuste süsteemi seadistamine +Comment[fi]=Aktiviteettien asetukset +Comment[fr]=Configure le système d'activités +Comment[gl]=Configura o sistema de actividades +Comment[he]=הגדר את הפעילויות במערכת +Comment[hu]=Az aktivitások rendszer beállítása +Comment[ia]=Configura le systema de activitates +Comment[it]=Configura il sistema delle attività +Comment[kk]=Белсенділік жүйесін баптау +Comment[km]=កំណត់​រចនាសម្ព័ន្ធ​ប្រព័ន្ធ​សកម្មភាព +Comment[ko]=활동 시스템 설정 +Comment[lt]=Konfigūruoti veiklų sistemą +Comment[mr]=कार्यपध्दती प्रणाली संयोजीत करा +Comment[nb]=Sett opp aktivitetssystemet +Comment[nds]=Dat Aktivitetensysteem instellen +Comment[nl]=Het systeem van activiteiten instellen +Comment[pa]=ਐਕਟੀਵਿਟੀ ਸਿਸਟਮ ਦੀ ਸੰਰਚਨਾ +Comment[pl]=Konfiguruj system działań +Comment[pt]=Configurar o sistema de actividades +Comment[pt_BR]=Configura o sistema de atividades +Comment[ro]=Configurează sistemul de activități +Comment[ru]=Настраивает систему комнат +Comment[sk]=Nastaviť systém aktivít +Comment[sl]=Nastavite sistem dejavnosti +Comment[sr]=Подесите систем активности +Comment[sr@ijekavian]=Подесите систем активности +Comment[sr@ijekavianlatin]=Podesite sistem aktivnosti +Comment[sr@latin]=Podesite sistem aktivnosti +Comment[sv]=Anpassa aktivitetssystemet +Comment[tg]=Танзимоти системаи фаъолият +Comment[tr]=Etkinlikler sistemini yapılandır +Comment[ug]=پائالىيەت سىستېمىسىنى سەپلەيدۇ +Comment[uk]=Налаштування системи просторів дій +Comment[x-test]=xxConfigure the activities systemxx +Comment[zh_CN]=配置活动系统 +Comment[zh_TW]=設定活動系統 + diff --git a/kactivities/src/workspace/settings/qml/BlacklistApplicationView.qml b/kactivities/src/workspace/settings/qml/BlacklistApplicationView.qml new file mode 100644 index 00000000..ff3a5502 --- /dev/null +++ b/kactivities/src/workspace/settings/qml/BlacklistApplicationView.qml @@ -0,0 +1,120 @@ +/* vim:set foldenable foldmethod=marker: + * + * Copyright (C) 2012 Ivan Cukic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * or (at your option) any later version, as published by the Free + * Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 1.1 +import org.kde.qtextracomponents 0.1 +import org.kde.plasma.core 0.1 as PlasmaCore +import org.kde.plasma.components 0.1 as PlasmaComponents + +Flow { + id: main + + /* property declarations --------------------------{{{ */ + property int minimumHeight: 100 + /* }}} */ + + /* signal declarations ----------------------------{{{ */ + /* }}} */ + + /* JavaScript functions ---------------------------{{{ */ + /* }}} */ + + /* object properties ------------------------------{{{ */ + spacing: 16 + anchors { + fill : parent + rightMargin : 8 + bottomMargin : 8 + leftMargin : 8 + } + + height: Math.max(childrenRect.height, minimumHeight) + + opacity: if (applicationModel.enabled) {1} else {.3} + Behavior on opacity { NumberAnimation { duration: 150 } } + /* }}} */ + + /* child objects ----------------------------------{{{ */ + Repeater { + model: applicationModel + Column { + id: item + + property bool blocked: model.blocked + + Item { + id: mainIcon + + width : 64 + 20 + height : 64 + 20 + + QIconItem { + id: icon + icon: model.icon + + anchors.fill : parent + anchors.margins : 10 + + opacity: if (item.blocked) {0.5} else {1.0} + Behavior on opacity { NumberAnimation { duration: 150 } } + } + + QIconItem { + id: iconNo + icon: "dialog-cancel" + + anchors { + right : parent.right + bottom : parent.bottom + } + + width : 48 + height : 48 + opacity : (1 - icon.opacity) * 2 + } + + MouseArea { + onClicked: applicationModel.toggleApplicationBlocked(model.index) + anchors.fill: parent + } + } + + Text { + elide : Text.ElideRight + width : mainIcon.width + + text : model.title; + opacity : icon.opacity + + anchors.margins : 10 + anchors.horizontalCenter : parent.horizontalCenter + horizontalAlignment : Text.AlignHCenter + } + } + } + /* }}} */ + + /* states -----------------------------------------{{{ */ + /* }}} */ + + /* transitions ------------------------------------{{{ */ + /* }}} */ +} + diff --git a/kactivities/src/workspace/settings/ui/MainConfigurationWidgetBase.ui b/kactivities/src/workspace/settings/ui/MainConfigurationWidgetBase.ui new file mode 100644 index 00000000..443f1524 --- /dev/null +++ b/kactivities/src/workspace/settings/ui/MainConfigurationWidgetBase.ui @@ -0,0 +1,221 @@ + + + MainConfigurationWidgetBase + + + + 0 + 0 + 760 + 613 + + + + + + + Enable activities management + + + + + + + 0 + + + + Privacy + + + + + + Remember opened documents: + + + + + + + + + + + For all applications + + + true + + + + + + + Do not remember + + + + + + + Only for specific applications + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + + + + Keep history + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Clear recent history + + + + + + + + + + + + + 0 + 1 + + + + + + + true + + + + 0 + + + + + Qt::ScrollBarAlwaysOff + + + + + + + Blacklist all applications not on this list + + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + + + KIntSpinBox + QSpinBox +
knuminput.h
+
+
+ + + + checkEnableActivities + toggled(bool) + tabWidget + setEnabled(bool) + + + 44 + 8 + + + 193 + 45 + + + + + radioDontRememberApplications + toggled(bool) + groupHistory + setHidden(bool) + + + 223 + 130 + + + 395 + 145 + + + + + + changed() + +
diff --git a/kcalc/CMakeLists.txt b/kcalc/CMakeLists.txt new file mode 100644 index 00000000..8f3d4b1e --- /dev/null +++ b/kcalc/CMakeLists.txt @@ -0,0 +1,105 @@ +project(kcalc) + +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) + find_package(KDE4) + include(KDE4Defaults) + include_directories(${KDE4_INCLUDES}) + + add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) + add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) + + # If definitions like -D_GNU_SOURCE are needed for these checks they + # should be added to _KDE4_PLATFORM_DEFINITIONS when it is originally + # defined outside this file. Here we include these definitions in + # CMAKE_REQUIRED_DEFINITIONS so they will be included in the build of + # checks below. + set( CMAKE_REQUIRED_DEFINITIONS ${_KDE4_PLATFORM_DEFINITIONS} ) + + set(GMP_REQUIRED TRUE) +else() + set(GMP_REQUIRED FALSE) +endif() + +set (CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" ${CMAKE_SOURCE_DIR}/cmake/modules) +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") + +add_definitions (-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) + +find_package(GMP) +macro_log_feature( GMP_FOUND "GMP" "The GNU Multiple Precision Arithmetic Library" "http://gmplib.org/" ${GMP_REQUIRED} "" "Required for building KCalc.") + +if(NOT GMP_FOUND) + if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) + macro_display_feature_log() + else() + return() + endif() +endif(NOT GMP_FOUND) + +include(CheckTypeSize) +include(CheckIncludeFiles) + +check_include_files(ieeefp.h HAVE_IEEEFP_H) +check_type_size("signed long" SIZEOF_SIGNED_LONG) +check_type_size("unsigned long" SIZEOF_UNSIGNED_LONG) + +configure_file(config-kcalc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kcalc.h ) + +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/knumber ${GMP_INCLUDE_DIR} ) + +########### next target ############### +# Needs absolute paths due to the test program for knumber +set(libknumber_la_SRCS + ${kcalc_SOURCE_DIR}/knumber/knumber.cpp + ${kcalc_SOURCE_DIR}/knumber/knumber_error.cpp + ${kcalc_SOURCE_DIR}/knumber/knumber_float.cpp + ${kcalc_SOURCE_DIR}/knumber/knumber_fraction.cpp + ${kcalc_SOURCE_DIR}/knumber/knumber_integer.cpp + ${kcalc_SOURCE_DIR}/knumber/knumber_operators.cpp +) + +add_subdirectory( knumber ) +# add_subdirectory( tests ) + +set(kcalc_KDEINIT_SRCS ${libknumber_la_SRCS} + kcalc.cpp + bitbutton.cpp + kcalc_bitset.cpp + kcalc_button.cpp + kcalc_const_button.cpp + kcalc_const_menu.cpp + kcalc_core.cpp + kcalcdisplay.cpp + stats.cpp ) + +kde4_add_ui_files(kcalc_KDEINIT_SRCS + kcalc.ui + constants.ui + colors.ui + fonts.ui + general.ui) + +kde4_add_kcfg_files(kcalc_KDEINIT_SRCS kcalc_settings.kcfgc ) + +kde4_add_app_icon(kcalc_KDEINIT_SRCS "${KDE4_INSTALL_DIR}/share/icons/oxygen/*/apps/accessories-calculator.png") + +kde4_add_kdeinit_executable( kcalc ${kcalc_KDEINIT_SRCS}) + +target_link_libraries(kdeinit_kcalc ${QT_QTXML_LIBRARY} ${KDE4_KDEUI_LIBS} ${GMP_LIBRARIES} ${MPFR_LIBRARIES}) + +install(TARGETS kdeinit_kcalc ${INSTALL_TARGETS_DEFAULT_ARGS}) + +target_link_libraries( kcalc kdeinit_kcalc ) +install(TARGETS kcalc ${INSTALL_TARGETS_DEFAULT_ARGS} ) + +########### install files ############### + +install( PROGRAMS kcalc.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) +install( FILES kcalc.kcfg DESTINATION ${KCFG_INSTALL_DIR}) +install( FILES kcalcui.rc DESTINATION ${DATA_INSTALL_DIR}/kcalc) +install( FILES scienceconstants.xml DESTINATION ${DATA_INSTALL_DIR}/kcalc) +install( FILES kcalcrc.upd DESTINATION ${DATA_INSTALL_DIR}/kconf_update) + +kde4_install_icons( ${ICON_INSTALL_DIR} ) + +add_subdirectory(doc) diff --git a/kcalc/COPYING b/kcalc/COPYING new file mode 100644 index 00000000..5185fd3f --- /dev/null +++ b/kcalc/COPYING @@ -0,0 +1,346 @@ +NOTE! The GPL below is copyrighted by the Free Software Foundation, but +the instance of code that it refers to (the kde programs) are copyrighted +by the authors who actually wrote it. + +--------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/kcalc/COPYING.LIB b/kcalc/COPYING.LIB new file mode 100644 index 00000000..2d2d780e --- /dev/null +++ b/kcalc/COPYING.LIB @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/kcalc/CTestConfig.cmake b/kcalc/CTestConfig.cmake new file mode 100644 index 00000000..22e5a449 --- /dev/null +++ b/kcalc/CTestConfig.cmake @@ -0,0 +1,13 @@ +## This file should be placed in the root directory of your project. +## Then modify the CMakeLists.txt file in the root directory of your +## project to incorporate the testing dashboard. +## # The following are required to uses Dart and the Cdash dashboard +## ENABLE_TESTING() +## INCLUDE(CTest) +set(CTEST_PROJECT_NAME "kcalc") +set(CTEST_NIGHTLY_START_TIME "20:00:00 CET") + +set(CTEST_DROP_METHOD "http") +set(CTEST_DROP_SITE "my.cdash.org") +set(CTEST_DROP_LOCATION "/submit.php?project=kcalc") +set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/kcalc/ChangeLog b/kcalc/ChangeLog new file mode 100644 index 00000000..8470e609 --- /dev/null +++ b/kcalc/ChangeLog @@ -0,0 +1,381 @@ +2005-04-30 Klaus Niederkrüger + * Fix BUG 100316 + +2005-04-25 Klaus Niederkrüger + * Fix BUG 99817. + +2005-04-20 Klaus Niederkrüger + * Change C-style casts for C++ casts, removed some C-style typedefs + +2005-04-02 Klaus Niederkrüger + * GUI: added statusflags to display, looks very neat (this was + contributed by Bernd Brandstetter; well done!) + +2005-03-30 Klaus Niederkrüger + * added a Memory-Store button (contributed by Bernd Brandstetter) + +2005-03-29 Klaus Niederkrüger + * fix M+/- button to change when inverse is pressed + +2005-03-08 Evan Teran + * updated about box to reflect my correct email address. This one is + permanent and will never change again + +2005-02-05 Klaus Niederkrüger + * fix BUG 98522: M- button did not work at least since KDE-3.2. Shame on me, and thanks to + Bernd Brandstetter for reporting the bug. + +2004-12-21 Klaus Niederkrüger + * added some more contants and dropped Pi-button since not needed. + Instead new cube and cube root button. + +2004-12-17 Klaus Niederkrüger + * almost finished feature which allows to choose from list + of scientific constants + +2004-12-17 Klaus Niederkrüger + * started implementation of a list of scientific constants + +2004-12-06 Klaus Niederkrüger + * further refactoring of configuration options for const buttons + +2004-12-05 Klaus Niederkrüger + * understood how kconfigxt handles indexed configuration options + +2004-12-03 Klaus Niederkrüger + * moving through history is now done via undo/redo + this implements wish BUG 93938 + +2004-11-23 Klaus Niederkrüger + * improved const buttons + +2004-10-20 Klaus Niederkrüger + * clean up Const-Buttons. More work still required. + +2004-10-16 Klaus Niederkrüger + * improve Const-Buttons: set value by using INV-Button instead of config-menu + +2004-09-24 Klaus Niederkrüger + * fixed BUG 90009: the display does not resize in vertical direction anymore + +2004-09-24 Klaus Niederkrüger + * fixed small bug: switching off Logic-mode tried to change item in statusbar, + but item did not exist. + +2004-09-11 Klaus Niederkrüger * + * fix bug 82433: show a check mark next to the angle mode in popup menu + +2004-09-10 Klaus Niederkrüger * + * bundle precedence of operators and function pointers into a single + struct. + +2004-09-08 Klaus Niederkrüger + * moved angle mode logic into GUI + +2004-09-04 Klaus Niederkrüger + * reduced the number of entry points into the CalcEngine + Aim: enterOperation the only entry point + +2004-08-2? Klaus Niederkrüger + * added some labels and tooltips for inverse mode + +2004-08-2? Klaus Niederkrüger + * fix to BUG 74657 + * fix to BUG 78726 + +2004-07-30 Klaus Niederkrüger + * Square root button did not work correctly after pressing CTRL (Bug 86004) + +2004-07-30 Klaus Niederkrüger + * Added Accels for Const-Buttons + +2004-07-29 Klaus Niederkrüger + * Fixed BUG 67984 (thanks to André Wöbbeking) + +2004-07-17 Klaus Niederkrüger + * Fixed BUG 85357 + +2004-06-05 Klaus Niederkrüger + * It was not possible to paste a hex-number starting with "0x" in hex-mode, because hex-mode + prepended automatically "0x" leading to "0x0x...". + +2004-05-16 Klaus Niederkrüger + * Refactored a bit the kcalc_button code. In particular use Richtext only, when needed (a bit faster, + but a lot still to be done). + +2004-05-04 Klaus Niederkrüger + * Masked "&" for the label of the AND button, when displaying the corresponding accel. + Thanks to Sigrid from Trolltech. + +2004-04-27 Klaus Niederkrüger + * Buttons have now their own modes (like inverse Mode etc.) + This allows moving tooltips and labels into kcalc_button.cpp + instead of having everything in kcalc.cpp. + This needs to be cleaned up and the tooltips and labels should be + revised. + +2004-04-05 Klaus Niederkrüger + * Changed accel for OR-button from "O" to "|" + * More accels are visible, when "Ctrl" is used + * disable some buttons like "Sin", "Cos" etc., when not + in decimal mode + +2004-04-05 Klaus Niederkrüger + * Pasted numbers in Hex-Mode are now always interpreted as + hex-numbers (BUG 65167). + +2004-03-26 Klaus Niederkrüger + * Centered labels on buttons + +2004-03-24 Klaus Niederkrüger + * Press (and hold) CTRL-key to see accels for all + buttons (BUG 69850). + +2004-03-19 Klaus Niederkrüger + * Labels like e.g. "x^y" are drawn with QSimpleRichText + Need to center the labels better!! + * KCalcButton supports now two different labels, one for normal, one + for inverse mode. Switch between the two happens via a signal. + +2004-03-11 Klaus Niederkrüger + * Moved functions that paint e.g. "x^y" to the KCalcButton-class. + +2004-03-01 Klaus Niederkrüger + * Created class KCalcButton, which is now used for all QPushButtons. + This will allow later to change the labels more easily, if INV is + pressed, and mucho more... + +2004-02-25 Thomas Weber + * "x^2" and "x^y"-buttons have now superscripted labels + * pressing "INV"-button changes "x^2"-button to square-root etc. + +2004-02-25 Klaus Niederkrüger + * Changed many accels from "accel()->insert()" to + "button->addAccel()". Fixes BUG 75555. + * deactivate EE-button, when Base != DEC + +2004-02-10 Klaus Niederkrüger + * First step to fix bug 66397. Changing Basemode via Popupmenu is + too clumsy for most programmers. Maybe backport to KDE 3.2 later, + once it has been tested. GUI does not look nice. + +2004-02-06 Klaus Niederkrüger + * Fixed bug 73437. Pasting empty clipboard made kcalc crash. + +2004-02-04 Klaus Niederkrüger + * Moved more accels from keyPressEvent into accels + +2004-01-27 Klaus Niederkrüger + * Fix paste-function: Pasting e.g. "123 \n" did not work + because of the trailing spaces and returns. + +2004-01-25 Klaus Niederkrüger + * Applied patch from Thomas Weber: Creates buttons C1-C6 + to store constants + +2003-12-06 Klaus Niederkrüger + * Fix problem with several identical labels in statusbar + with the help of newly added statusbar->hasItem. + +2003-12-06 Klaus Niederkrüger + * Make Percent work the way it did in KDE-3.1 again (documentation + was not updated to this KDE-version, which confused me on 2003/08/28) + +2003-11-04 Klaus Niederkrüger + * More reorganization and preparation for getting accels right. + +2003-10-31 Klaus Niederkrüger + * Split Constructor into more subfunctions. + To be continued. + +2003-10-20 Klaus Niederkrüger + * At last the calculator window resizes, when buttons are hidden/shown + * Added a few extra buttons to "fill gaps". + +2003-10-14 Klaus Niederkrüger + * RadioButtons for Base and Angle converted to PopUpMenu+Button + * Layout changed + * Moved ENTER form keyPressedEvent to Accel() + +2003-10-10 Klaus Niederkrüger + * Number buttons look like numeric keyboard (on US-keyboards that is) + * Initialize ToggleActions correctly + +2003-10-09 Klaus Niederkrüger + * Reverted the menubar stuff after several complaints. + Now Kcalc-GUI looks again like on 2003-09-11. + +2003-09-30 Klaus Niederkrüger + * Fixed bug (unitialized pointers) + +2003-09-26 Klaus Niederkrüger + * Moved all type of buttons to menubars, which can be hidden/showed + via the menubar. This is quite experimental and full of bugs. + +2003-09-11 Klaus Niederkrüger + * Button groups can be switch on/off via actions. + This is not yet the final GUI-Layout (yes, it looks broken). + * Accordingly the Stat/Trig-mode has been deleted from + configure window. + +2003-09-11 Klaus Niederkrüger + * deleted a few #includes + +2003-09-02 Klaus Niederkrüger + * dropped configure-button and Help-button + * created menubar with standard-actions + * ConfigDialog is not modal anymore + * copy/paste/cut-standard actions applied to calc_display + +2003-08-28 Klaus Niederkrüger + * created Pi-button and moved Inv-button to make space for it + * label of "."-button is now localized + * Caption is set via signal/slots + * Fixed "%"-mode to make it again more conformant with documentation. + "x^y%" does still not work. Did it ever? + * Number-keys are now handled by KAccel instead of keyPressEvent + * ConfigDialog is modal + +2003-08-27 Klaus Niederkrüger + * moved result_history from CalcEngine into DispLogic + * reactivation of rounding towards zero for cos(90) etc. + * quit is done in a more KDE conformant way + * Caption is set via slots etc. + +2003-08-12 Klaus Niederkrüger + * continued separation: now display is independent class and + handles everything related to itself. + GUI passes only information between core and display. + +2003-08-04 Klaus Niederkrüger + * finished the separation of gui-code from the calculating core. + this still needs some clean-up + +2003-02-11 Evan Teran + * replaced cheasy stack with STL stack classes (it really should have been + two stacks, but was mushed into one with a linked list dividing content) + * made it use new headers (no .h) when _ISOC99_SOURCE is defined + * added replace current gui code with ui files to TODO list + +2002-05-10 Evan Teran + * altered makefile to remove building of it as a library then linking that + to a dummy object file, this was silly and caused inclusion of an uneeded + source file + * bumped version to reflect new options dialog, next version will be 2.0.0 + as it will have many new changes/features + +2002-03-11 Evan Teran + * started work on making calculator code _seperate_ from the + gui code, the goal here is the make the calculating core + replacable with any library (hopfully with better precision) simply by + wrapping it in a class + * made it some many buttons are disabled when unavailabled (A-F) not enabled + unless we are in HEX mode, less confusion for users who arent familiar with + different bases + * removed some code that is never getting called + * why oh why are exceptions disabled in the standard config, I would like to + use them :( + +2001-10-18 Evan Teran + * removed configdlg.* from source tree as it is not part of the compile + * changed options dialog to use smarter layouts, and also fixed spinbox + size problem so they are now usable + * synced changed I made to the KDE 2.2.1 release version to match CVS + version which compiles under KDE 3.0 w/ QT 3.0.0 + * made sure the clear (clear entry) button functioned properly, my minor + change before broke it in some situations + + +2001-10-12 Evan Teran + * Reorganized code for all files, much more consistant now + * completely reworked the cvb (convert to binary) function + it now is sane, the last one did a rediculous amount of + unneeded work + * changed C/C++ headers to use new style as per ANSI/ISO + standard + * removed fontdlg.cpp/h from tree, not even used in compile :P + * made binary mode 32-bit + * reorganized UpdateDisplay code to make more sense + * found possible nasty memleak in EnterEqual + * now use my UNUSED macro to perform (void)var on unused parameters + to avoid compile warnings, much more readable this way + * merged setXXX functions to angle_selected/base_selected + then removed the setXXX functions as they no longer have any purpose + * changed a ton of code to use true C++ bools, makes logic + more clear + * made cvb a static member of QtCalculator instead of an ugly global + function + * changed history to use a simple vectory object instead of the storing + the values in a dynamically allocated QList, it was clearly leaking memory + as it was almost never released, this could have been fixed by enabling the + autodelete feature of QList...but why dynamically allocate when we are + storing numerical values?!? simple array with an int as an iterator :) + * changed error trapping from C style signal trapping, rather use exceptions + much cleaner that way + * removed un-used TopOfStack function + * fixed ability to enter a decimal point when in hex/oct/bin mode, those are + integer value only modes + * fixed problem with entering multiple decimal points in decimal mode + * changed CALCAMNT to be defined by a typedef, I did notice that at least my + C++ math headers are broken...functions like cos should (by newest + standards) use float, double, or long double versions automatically based on + datatype, however mine (Redhat 7.1) seems to only have the old double + versions, and have alternate names for these functions + * fixed the fact that the normal clear button seemingly did nothing + + +1999-08-22 Bernd Johannes Wuebben + + * kcalc_core.cpp (UpdateDisplay): + Re-enabled the use of long double. Most if not all distributions + come now with a working glib math library. + +Mon Nov 16 18:05:01 1998 Mario Weilguni + + * There was an error in the stdev forumal. Fixed. + * the population standard deviation had the same fault. Fixed. + +Mon Nov 16 18:05:01 1998 Mario Weilguni + + * calculation the facturial of a too large number was delayed, + even though infinity was already reached. Now kcalc stops + calculation if infinity is reached and displays an error + +Mon May 4 06:28:09 1998 Bernd Johannes Wuebben + + * Added the comma key as an accelerator for '.' + +Sat Apr 18 16:26:52 1998 Bernd Johannes Wuebben + + * Some inverse functions didn't compute right. Wonder who broke them. + +Sun Feb 8 16:11:34 1998 Bernd Johannes Wuebben + + * removed a compiler warning + +Sat Nov 22 14:30:37 1997 Bernd Johannes Wuebben + + * stats.cpp: fixed some bugs in the stats module + mean should now be correct + std shoudl now be correct too. + +Sat Sep 20 23:59:30 1997 Bernd Johannes Wuebben + + * kcalc.cpp: Added statistical functions + +Mon Sep 15 00:34:58 1997 Bernd Johannes Wuebben + + * added cut and pasted functionality + * implemented EE + * implemented result stack + * implemented precision and fixed precision + * added key bindings + * tooltips + * added configuration dialog + + +Sat Aug 2 22:06:59 1997 Bernd Johannes Wuebben + + * kcalc.h: clean up + + diff --git a/kcalc/Mainpage.dox b/kcalc/Mainpage.dox new file mode 100644 index 00000000..209d322c --- /dev/null +++ b/kcalc/Mainpage.dox @@ -0,0 +1,8 @@ +/** @mainpage kcalc + +The kcalc application + +*/ + +// DOXYGEN_REFERENCES = kdecore +// DOXYGEN_SET_PROJECT_NAME = kcalc diff --git a/kcalc/Messages.sh b/kcalc/Messages.sh new file mode 100644 index 00000000..3e5c454d --- /dev/null +++ b/kcalc/Messages.sh @@ -0,0 +1,5 @@ +#! /bin/sh +$EXTRACTRC *.rc *.ui *.kcfg >> rc.cpp +$EXTRACTATTR --attr=constant,name scienceconstants.xml >>rc.cpp ||exit 11 +$XGETTEXT *.cpp -o $podir/kcalc.pot +rm -f rc.cpp diff --git a/kcalc/README b/kcalc/README new file mode 100644 index 00000000..eb64d7c6 --- /dev/null +++ b/kcalc/README @@ -0,0 +1,8 @@ +KCalc +===== +KCalc is currently covered under the GPL version 2. A copy of this +license can be found in a file named COPYING, in the toplevel directory. + +Have fun with kcalc! + + diff --git a/kcalc/TODO b/kcalc/TODO new file mode 100644 index 00000000..8c12c421 --- /dev/null +++ b/kcalc/TODO @@ -0,0 +1,19 @@ +== ToDo == +* Redo KCalcButton + +* Use validator for constant value line edits in configure. + +* Have a look at Evan's new stack engine kcalc_core.cpp. But be very careful +not to create new bugs. Also think why statistic-function in an independent file. + +* Implement integer arithmetic for (unsigned int, signed int, +unsigned short,...). Look here at some of Evan's code. + +* think what to do with cos, sin, etc. right now, as work-around cast KNumber to double, +use standard libc cos, sin, etc., cast back + +====Below old TODOs === +* key groups for color configuration need to be rethought +* better to handle keys with "accel" or with "keyPressEvent"? + +* check whether DelFormer Input in stat-mode works after using median! diff --git a/kcalc/bitbutton.cpp b/kcalc/bitbutton.cpp new file mode 100644 index 00000000..614d2619 --- /dev/null +++ b/kcalc/bitbutton.cpp @@ -0,0 +1,65 @@ +/* +Copyright (C) 2012 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 2006 Michel Marti + mma@objectxp.com + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "bitbutton.h" +#include + +#include "bitbutton.moc" + +//------------------------------------------------------------------------------ +// Name: BitButton +// Desc: constructor +//------------------------------------------------------------------------------ +BitButton::BitButton(QWidget *parent) : QAbstractButton(parent), on_(false) { + + // too many bits for tab focus + setFocusPolicy(Qt::ClickFocus); + + // size button by font + QSize size = fontMetrics().size(0, QLatin1String("M")); + + if (size.width() < size.height()) { + size.setHeight(size.width()); + } else { + size.setWidth(size.height()); + } + + setFixedSize(size.expandedTo(QApplication::globalStrut())); +} + +//------------------------------------------------------------------------------ +// Name: isOn +// Desc: returns true if this bit-button is "on" +//------------------------------------------------------------------------------ +bool BitButton::isOn() const { + + return on_; +} + +//------------------------------------------------------------------------------ +// Name: setOn +// Desc: changes the "on" value for the bitset +//------------------------------------------------------------------------------ +void BitButton::setOn(bool value) { + + on_ = value; + update(); +} diff --git a/kcalc/bitbutton.h b/kcalc/bitbutton.h new file mode 100644 index 00000000..996ab0af --- /dev/null +++ b/kcalc/bitbutton.h @@ -0,0 +1,42 @@ +/* +Copyright (C) 2012 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 2006 Michel Marti + mma@objectxp.com + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef BITBUTTON_H_20120104_ +#define BITBUTTON_H_20120104_ + +#include + +class BitButton : public QAbstractButton { + Q_OBJECT + +public: + explicit BitButton(QWidget *parent = 0); + bool isOn() const; + void setOn(bool value); + +protected: + void paintEvent(QPaintEvent *event); + +private: + bool on_; +}; + +#endif diff --git a/kcalc/cmake/modules/FindMPFR.cmake b/kcalc/cmake/modules/FindMPFR.cmake new file mode 100644 index 00000000..4ccf889d --- /dev/null +++ b/kcalc/cmake/modules/FindMPFR.cmake @@ -0,0 +1,23 @@ +# Try to find the MPFR librairies +# MPFR_FOUND - system has MPFR lib +# MPFR_INCLUDE_DIR - the MPFR include directory +# MPFR_LIBRARIES - Libraries needed to use MPFR + +# Copyright (c) 2012, Evan Teran +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +if (MPFR_INCLUDE_DIR AND MPFR_LIBRARIES) + # Already in cache, be silent + set(MPFR_FIND_QUIETLY TRUE) +endif (MPFR_INCLUDE_DIR AND MPFR_LIBRARIES) + +find_path(MPFR_INCLUDE_DIR NAMES mpfr.h ) +find_library(MPFR_LIBRARIES NAMES mpfr libmpfr) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(MPFR DEFAULT_MSG MPFR_INCLUDE_DIR MPFR_LIBRARIES) + +mark_as_advanced(MPFR_INCLUDE_DIR MPFR_LIBRARIES) diff --git a/kcalc/colors.ui b/kcalc/colors.ui new file mode 100644 index 00000000..9555ef22 --- /dev/null +++ b/kcalc/colors.ui @@ -0,0 +1,276 @@ + + Colors + + + + 0 + 0 + 240 + 344 + + + + + + + Display Colors + + + + + + + + &Foreground: + + + false + + + kcfg_ForeColor + + + + + + + + + + + 0 + 0 + 0 + + + + + + + + &Background: + + + false + + + kcfg_BackColor + + + + + + + + + + + 189 + 255 + 180 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Button Colors + + + + + + + + &Functions: + + + false + + + kcfg_FunctionButtonsColor + + + + + + + + + + + + + + St&atistic functions: + + + false + + + kcfg_StatButtonsColor + + + + + + + + + + + + + + He&xadecimals: + + + false + + + kcfg_HexButtonsColor + + + + + + + + + + + + + + &Numbers: + + + false + + + kcfg_NumberButtonsColor + + + + + + + + + + + + + + &Memory: + + + false + + + kcfg_MemoryButtonsColor + + + + + + + + + + + + + + O&perations: + + + false + + + kcfg_OperationButtonsColor + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 31 + 20 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 151 + 70 + + + + + + + + + KColorButton + QPushButton +
kcolorbutton.h
+
+
+ + kcfg_ForeColor + kcfg_BackColor + kcfg_FunctionButtonsColor + kcfg_StatButtonsColor + kcfg_HexButtonsColor + kcfg_NumberButtonsColor + kcfg_MemoryButtonsColor + kcfg_OperationButtonsColor + + + +
diff --git a/kcalc/config-kcalc.h.cmake b/kcalc/config-kcalc.h.cmake new file mode 100644 index 00000000..a725752b --- /dev/null +++ b/kcalc/config-kcalc.h.cmake @@ -0,0 +1,26 @@ +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_IEEEFP_H 1 + +/* Define if you have isinf */ +#cmakedefine HAVE_FUNC_ISINF 1 + +/* Define if you have isnan */ +#cmakedefine HAVE_FUNC_ISNAN 1 + +/* Define if you have round */ +#define HAVE_FUNC_ROUNDL 1 + +/* Define if you have round */ +#define HAVE_FUNC_ROUND 1 + +/* Define if you have libgmp */ +#define HAVE_GMP 1 + +/* Define if you have support for long double in printf */ +#define HAVE_LONG_DOUBLE 1 + +/* The size of a `signed long', as computed by sizeof. */ +#define SIZEOF_SIGNED_LONG ${SIZEOF_SIGNED_LONG} + +/* The size of a `unsigned long', as computed by sizeof. */ +#define SIZEOF_UNSIGNED_LONG ${SIZEOF_UNSIGNED_LONG} diff --git a/kcalc/constants.ui b/kcalc/constants.ui new file mode 100644 index 00000000..bf3af985 --- /dev/null +++ b/kcalc/constants.ui @@ -0,0 +1,174 @@ + + Constants + + + + 0 + 0 + 185 + 273 + + + + Constants + + + + + + Constants + + + + + + 6 + + + + + + + 40 + + + + + + + Predefined + + + + + + + 6 + + + + + + + 40 + + + + + + + Predefined + + + + + + + 6 + + + + + + + 40 + + + + + + + Predefined + + + + + + + 6 + + + + + + + 40 + + + + + + + Predefined + + + + + + + 6 + + + + + + + 40 + + + + + + + Predefined + + + + + + + 6 + + + + + + + 40 + + + + + + + Predefined + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 40 + + + + + + + + kpushbutton.h + + + + diff --git a/kcalc/doc/CMakeLists.txt b/kcalc/doc/CMakeLists.txt new file mode 100644 index 00000000..b1958f0f --- /dev/null +++ b/kcalc/doc/CMakeLists.txt @@ -0,0 +1,4 @@ +########### install files ############### +# +# +kde4_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en SUBDIR kcalc) diff --git a/kcalc/doc/commands.docbook b/kcalc/doc/commands.docbook new file mode 100644 index 00000000..f0928169 --- /dev/null +++ b/kcalc/doc/commands.docbook @@ -0,0 +1,140 @@ + + +Command Reference + + +Menu Items + + A brief overview of some special menu items in &kcalc;: + + + + +Constants +Mathematics + +Display Pi, Euler Number or Golden Ratio. + + + + + +Constants +Electromagnetism + +Display Light Speed, Elementary Charge, +Impedance of Vacuum, Permeability of Vacuum or Permittivity of Vacuum. + + + + + +Constants +Atomic & Nuclear + +Display Planck's Constant, Elementary Charge or +Fine-Structure Constant. + + + + + +Constants +Thermodynamics + +Display Boltzmann Constant, Atomic Mass Unit, Molar Gas +Constant, Stefan-Boltzmann Constant or Avogadro's Number. + + + + + +Constants +Gravitation + +Display Constant of Gravitation or Earth Acceleration. + + + + + +Settings +Simple Mode + +Displays the simple math buttons. + + + + + +Settings +Science Mode + +Displays trigonometric and science buttons. + + + + + +Settings +Statistic Mode + +Displays statistic +buttons. + + + + + +Settings +Numeral System Mode + +Displays logic +buttons and allows changing the numeral system. + + + + + +Settings +Constants Buttons + +Display constants buttons. They are available in science mode and +statistic mode. + + + + + +Settings +Show Bit Edit + +Displays a bit edit field. Click on a bit to toggle it. Only available in +numeral system mode. + + + + + + +Additionally &kcalc; has the common &kde; File, Edit, +Settings and Help menu items, for more information +read the sections about the Menus +in the &kde; Fundamentals. + + + + + + + + diff --git a/kcalc/doc/index.docbook b/kcalc/doc/index.docbook new file mode 100644 index 00000000..0f00bf53 --- /dev/null +++ b/kcalc/doc/index.docbook @@ -0,0 +1,880 @@ + + + + + + +]> + + + +The &kcalc; Handbook + + + +&Bernd.Johannes.Wuebben; &Bernd.Johannes.Wuebben.mail; + + + +&Pamela.Roberts;&Pamela.Roberts.mail; + + + +&Anne-Marie.Mahfouf;&Anne-Marie.Mahfouf.mail; + + + + + + +2001 2002 2005 2006 +&Bernd.Johannes.Wuebben;, &Pamela.Roberts;, +&Anne-Marie.Mahfouf; + + +&FDLNotice; + +2013-05-24 +2.13 (&kde; 4.11) + +&kcalc; is a calculator for &kde;. In the simple mode it can +be used for basic arithmetic operations, but provides advanced modes for scientific, +statistical and numeral system calculations. + + +KDE +KCalc +calculator + + + + +Introduction + +&kcalc; offers many more mathematical functions than meet the eye +on a first glance. Please study the section on keyboard accelerators and +modes in this handbook to learn more about the many functions +available. + +In addition to the usual functionality offered by most scientific +calculators, &kcalc; offers a number of features, which I think are +worthwhile pointing out: + + + +&kcalc; provides trigonometric functions, logic operations, and it is +able to do statistical calculations. + + +&kcalc; allows you to cut and paste numbers from/into its display. + + +&kcalc; features a results-stack which lets +you conveniently recall previous results. + + +You can configure &kcalc;'s display colors and font. + + +You can configure &kcalc;'s precision, the number of displayed digits and the number +of digits after the period in the settings dialog. + + + &kcalc; offers a great number of useful +key-bindings, which make using &kcalc; without using a pointing device easy. +Hint: pressing (and holding) the &Ctrl;-key, displays on +every button,the corresponding key-binding. + + + + +&kcalc; uses Infix +notation which enforces correct order of operations as taught in school; contrary to +Immediate execution +used by many simple calculators. + + +Have fun with &kcalc;! + +&Bernd.Johannes.Wuebben; + + + + + +Usage + +General Usage + +General usage is straight forward and similar to the way most +simple scientific calculators operate, but take note of the following +special &kcalc; features: + + + +Result Stack +Each time you &LMB; click on the += button or press your keyboard's +&Enter; or = keys, the display result is +written to &kcalc;'s result stack. You can navigate through the result +stack with your keyboard's +&Ctrl;Z +and &Ctrl; &Shift;Z +keys. + + + + +Percent Function + +The percent function works somewhat differently to that on most +calculators. However, once understood, its enhanced functionality proves +quite useful. See the section about the percent function for further details. + + + +Cut and Paste + + + +Pressing &Ctrl;C +will place the displayed number on to the clipboard. + + +Pressing &Ctrl;V +will paste the clipboard content into the display if the content of the +clipboard is a valid floating point number. + + +It is still possible to copy/paste by clicking on &kcalc;'s display, +but this may disappear in future versions. + + + + + +Advanced functions + +When you start &kcalc; for the first time, the calculator will only +display buttons for basic arithmetic computations. +Under the menu entry Settings it is possible to select a mode for &kcalc;: +it is possible to choose +Simple or +Science or +Statistic or +Numeral System mode. Please note, that these modes will +slightly change as &kcalc; evolves. + + + + +Button Layout + +To give easy access to all the functions in advanced modes in &kcalc;, many keys have a second layout. Click +on &Shift; or press the shortcut &Ctrl;2 to make +the second layout of the buttons visible. + + + + + + + + + + +Simple Mode +This mode contains only those buttons and functions, which are essential necessary for basic calculations. +If you just want to sum up all items on an invoice this mode may be your choice. + + + + +Science Mode + +In this mode the left column of buttons is allocated to trigonometric +functions: + + + +Buttons +Function + + +Hyp +Enter Hyperbolic sub mode. Hyp Sin for example is the hyperbolic sine: +sinh + +Sin +Compute the sine + +&Shift; Sin or Asin +Compute the inverse sine + +Cos +Compute the cosine + +&Shift; Cos or Acos +Compute the inverse cosine + +Tan +Compute the tangent + +&Shift; Tan or Atan +Compute the inverse tangent + +Log +Compute the Log base 10 + +&Shift; Log or 10x +Compute 10 to the power of x + +Ln +Compute the natural logarithm. That is the log to base e + +&Shift; Ln or ex +Compute e (the base of the natural logarithm) to the power of x + + + +The second column has buttons for algebraic functions: + + + +Buttons +Function + + +Mod +Compute the remainder in Euclidean division + +&Shift; Mod or IntDiv +Integer division (integer part of the quotient) + +1/x +Compute the reciprocal for a number + +&Shift; 1/x or nCm +Compute the number of distinct second-operand-element subsets of it that can be formed for any set containing first operand elements (binomial coefficient) + +x! +Compute the product of all positive integers less than or equal to the current integer operand (factorial) + + +Compute the square of x + + &Shift; or √x +Compute the square root of x + + +x power y + +&Shift; xy or x1/y +x power 1/y + + +Computes the third (cubic) power of x + +&Shift; or ∛x +Computes the third (cubic) root of x + +x·10ʸ +Computes the product of the first operand and decimal exponent of the second operand + + + + + + + +Statistical Mode + +In this mode the left column of buttons is allocated to statistical +functions: + Most of the functionality in this mode is centered around the Dat + button. To create a data list of numbers, enter a number into the calculator and press + Dat. A sequentially increasing number is shown on the display indicating + which position in the Data list the number occupies. A traditional calculator only + stores three values for statistical functions: The number of discrete items in a list, the sum of + the data items entered and the sum of the square of all data items in the list. &kcalc; differs by + actually storing each discrete value, allowing you to calculate the median value of the data. + + + + +Buttons +Function + + +N +Recall the number of data items entered + +&Shift; N or &Sgr;x +Display the sum of all data items entered + +Mea +Display the mean of the data items entered + +&Shift; Mea or &Sgr;x² +Display the sum of the square of all data items entered + +&sgr;N +Display the standard deviation (n) + +&Shift; &sgr;N or &sgr;N-1 +Display the population standard deviation (n-1) + +Med +Display the median + +Dat +Enter a data item + +&Shift; Dat or CDat +Clear last data item entered + +CSt +Clear the store of all data item entered + + + +The next two columns hold the buttons with trigonometric and algebraic functions described in the +Science mode section. + + + + + +Numeral System Mode +This mode contains buttons and functions to calculate in binary, hexadecimal, octal and decimal. + +You select the numeral system with the radio buttons on the right. To insert a hexadecimal number for example, +just do the following: Select Hex from the radio buttons. Then enter a number and note, that you can +use the letters A to F +for numbers larger than 9. If you would like to see this number in binary, just select Bin from the radio buttons. + + Binary, octal and hexadecimal numbers can be displayed as groups of digits separated by whitespaces. Such grouping can improve readability of numbers. For example, hexadecimal number AF1C42 can be displayed as AF 1C 42 with separation every second digit. Grouping can be turned off or tweaked. Select SettingsConfigure &kcalc;... to bring up the configuration dialog and change the setting on General page as appropriate. + +You may want to select Show Bit Edit from the Settings menu to see selected bits. + +There are logic operators available in this mode. These operators are: + + +Buttons +Function + +AND +Two numbers are logically anded + +OR +Two numbers are logically ored + +XOR +Two numbers are logically exclusive ored + +Lsh +Left shift the value + +Rsh +Right shift the value + +Cmp +Perform a 1's complement + + + + +The second column holds the buttons with algebraic functions described in the +Science mode section. + +The third column with the buttons A to F is +enabled only in Hex mode. + + + + + + + +Memory Operations + +&kcalc; supports the memory operations given by standard +calculators plus six slots to hold constants. + + + Standard Memory Operations + +&kcalc; can remember results of operations for you, and re-use them in +later calculations. You can access these functions via several buttons +labeled MR, MS, +M+ and MC. + + + +MS +The MS button stores the currently +displayed result in memory. + + + +M+ +The M+ button adds the current result to the +one in memory. So, if you had stored a 20, and the current result is a 5, +your memory would contain 25 when you press it. If the memory is empty, it +acts like MS and simply stores the +result. + + + +MR +The MR button gets the value stored +in memory and puts it in the display. + + + +MC +The MC button clears the +memory. + + + +If a value is stored in memory a M will appear in +the status bar, next to the calculator mode indicator + + + +Constants + +The six constants buttons C1 to +C6 will only be visible after activating the +item Constants Buttons in the menu +Settings of the menu bar. They are available in Science mode +and Statistics mode + +To see the value stored in each constant hover the button with the mouse pointer. + +To store the number shown in the &kcalc; display in one of the +six constants, first press &Shift; followed +by the desired button key C1 up to +C6. + +To use the value stored in any of the constants in a calculation, just + press the desired button (C1 to C6), + and the corresponding number will appear in the display. + +It is possible to change the label of the constants button to make it easier + to remember which button holds which constant. Click with the right mouse + button on one of the buttons C1 to + C6. A popup menu appears, in which you select + Set Name. + +There are many (mostly physical) predefined constants, which can +be put on any of the six buttons C1 - +C6 by selecting the desired constant in the +popup menu that appears after right clicking on one of the constant +buttons and selecting Choose from List. Though +the predefined constants can also be accessed via the +Constants in the menu bar, storing it on a +constants button is very handy, if the number is used +frequently. + + + + + + +Single Key Accelerators + +To simplify entering calculations from the keyboard &kcalc; has single key +accelerators for most functions. For example entering 7R +or 7r will calculate the reciprocal of 7 (1/7). + +During a computation, you can always press &Ctrl; to make each +button display its key-binding. + + + +Key +Function +Notes + + + +H +Hyp +Hyperbolic as in Hyp Sin, +the sinh + +S +Sin + + +C +Cos + + +T +Tan + + +N +Ln +log base e + +L +Log +log base 10 + +Ctrl-2 + &Shift; +Second function for this button. ⪚ if +you want arcsin type Ctrl-2 s + +\ ++/- +Change sign + +[ +x^2 + + +^ +x^y + + +! +x! +Factorial + +E +x10y +Exponent + +< +Lsh +Left shift + +> +Rsh +Right shift. +& +AND +Logical AND + +x or * +X +Multiply + +/ +/ +Divide + +D +Dat +Enter data item in statistical mode + +| +OR +Logical OR. Note: &Shift; +OR is XOR + +R +1/x +Reciprocal + +&Enter; += + + +Return += + + +&Backspace; +<= +Delete last number + +PgUp +C +Clear + +&Esc; +C +Clear + +PgDown +AC +Clear all + +Del +AC +Clear all + +: +Mod +remainder of dividing + +&Alt;+1 to &Alt;+6 +C1 +use the value stored in C1 to C6 + + + + + + + +Comments on Specific Functions + + +Mod and IntDiv + + + +Mod gives the remainder of dividing the displayed +number by the next input number. +22 Mod 8 = will give the result +6 +22.345 Mod 8 = will give the result +6.345 + + + +&Shift; IntDiv does integer +division of the displayed number by the next input number. +22 Shift IntDiv 8 = will give the result +2 +22.345 Shift IntDiv 8 = also gives 2 + + + + + + + +% + +Used instead of the = key, +% interprets the final operation carried out in the +current calculation as follows: + + + +If the final operator is + or - the second argument is interpreted as percentage of the first operand. + + + +If the final operator is * divide the result of the multiplication by 100. + + + +If the final operator is / give the left operand +as a percentage of the right operand. + + + + In all other cases the % key gives identical results to the = key. + + + + + + +Examples: + +150 + 50 % gives 225 (150 plus 50 percent of this amount) +42 * 3 % gives +1.26 (42 * 3 / 100) +45 / 55 % gives +81.81... (45 is 81.81.. percent of 55) + + + + + + + +Lsh and Rsh + + +Lsh left shifts the integer part of the displayed +value (multiplies it by 2) n times, where n is the next input number, and +gives an integer result (base is set to Bin): +10 Lsh 3 = gives 80 +(10 multiplied by 2 three times). +10.345 Lsh 3 = also gives +80. + + + +Rsh right shifts the +value (performs an integer divide by 2) n times. +16 Rsh 2 = gives +4 (16 divided by 2 twice). +16.999 Rsh 2 = also gives +4. + + + + + + + +Cmp, And, Or and Xor + +The Cmp, And and +Or functions perform bitwise logical operations and +therefore appear more meaningful if the base is set to +Hex, Oct or Bin +rather than Dec. In the following +examples base is set to Bin. + + + +Cmp performs a 1's complement (inverts the +bits). +101 Cmp gives +111...111010 + + + +AND does a logical AND. +101 AND 110 = gives +100 + + + +OR does the logical OR. +101 OR 110 = gives +111 + + + +XOR performs the logical +XOR (exclusive OR) operation. +101 XOR 110 = gives +11 + + + + + + + + +Questions and Answers + + + + + +How do I get e, the Euler number? +Type 1 Shift Ln. + + + +How do I get two fixed digits after the period? +Select Settings +Configure &kcalc;... on the menubar, this will +bring up the configuration dialog. Check Set decimal +precision and adjust the spin control so that it shows a +2. + + +What about Precision? +The main factor determining the precision of &kcalc; is whether your libc and libmath +supports the C data type long double. If this is the case, &kcalc; will detect this +at compile time and use it as its fundamental data type to represent numbers. + + +Adjust the Precision in &kcalc;'s +Configure dialog so that the above computations +work correctly. I recommend a precision of 14 if the fundamental data type +for your copy of &kcalc; is long double, otherwise 8 or 10. + +Higher precision doesn't necessarily lead to better results. Play with +the precision and you will see what I mean. + + + + + + +&commands; + + +Credits and License + +&kcalc; Program Copyright ©: +&Bernd.Johannes.Wuebben; 1996-2000 +The &kde; Team 2000-2008 + + +&Bernd.Johannes.Wuebben; &Bernd.Johannes.Wuebben.mail; +&Evan.Teran; &Evan.Teran.mail; +&Espen.Sand; &Espen.Sand.mail; +&Chris.Howells; &Chris.Howells.mail; +&Aaron.J.Seigo; &Aaron.J.Seigo.mail; +&Charles.Samuels; &Charles.Samuels.mail; +&David.Johnson; &David.Johnson.mail; + + +&kcalc; was inspired by Martin Bartlett's xfrmcalc, +whose stack engine is still part of &kcalc;. + +Documentation Copyright © 2001,2002,2005, 2006,2010: + +&Bernd.Johannes.Wuebben; &Bernd.Johannes.Wuebben.mail; +&Pamela.Roberts; &Pamela.Roberts.mail; +&J.Hall; &J.Hall.mail; +&Anne-Marie.Mahfouf; &Anne-Marie.Mahfouf.mail; +EikeKrumbacher +eike.krumbacher@x-eike.de + + + +&underFDL; +&underGPL; + + + + +Installation + +&kcalc; is part of the kdeutils package within the &kde; project and will +normally be provided as part of a &kde; installation. For more details about +&kde; visit http://www.kde.org. + + +Compilation and Installation + +&install.intro.documentation; +&install.compile.documentation; + + + + + + + + diff --git a/kcalc/doc/kcalc_on_Aix.txt b/kcalc/doc/kcalc_on_Aix.txt new file mode 100644 index 00000000..186e9d65 --- /dev/null +++ b/kcalc/doc/kcalc_on_Aix.txt @@ -0,0 +1,123 @@ +X-RDate: Mon, 11 Aug 1997 17:34:22 -0400 (EDT) +Return-Path: +Received: from cornell.edu (cornell.edu [132.236.56.6]) by + postoffice2.mail.cornell.edu (8.8.5/8.8.5) with ESMTP id JAA08757 for + ; Mon, 11 Aug 1997 09:50:57 -0400 (EDT) +Received: (from daemon@localhost) by cornell.edu (8.8.5/8.8.5) id JAA11825 for + bw18@postoffice3.mail.cornell.edu; Mon, 11 Aug 1997 09:50:56 -0400 (EDT) +Received: from polygon.math.cornell.edu (POLYGON.MATH.CORNELL.EDU + [128.84.234.110]) by cornell.edu (8.8.5/8.8.5) with SMTP id JAA11800 for + ; Mon, 11 Aug 1997 09:50:53 -0400 (EDT) +Received: from ibmmail.COM by polygon.math.cornell.edu (5.x/SMI-SVR4) id + AA10464; Mon, 11 Aug 1997 09:50:48 -0400 +Received: from IMXGATE.COM by ibmmail.COM (IBM VM SMTP V2R3) with BSMTP id + 5302; Mon, 11 Aug 97 09:50:47 EDT +Received: from mail.schoeck.de by imxgate.com (IBM VM SMTP V2R3) with TCP; + Mon, 11 Aug 97 09:49:44 EDT +Received: from isndj1.ag.schoeck.com by mail.schoeck.de (AIX 4.1/UCB 5.64/4.03) + id AA15070; Mon, 11 Aug 1997 15:46:57 +0100 +Message-Id: <9708111446.AA15070@mail.schoeck.de> +Comments: Authenticated sender is +X-PH: V4.1@cornell.edu (Cornell Modified) +Organization: Schoeck AG +Date: Mon, 11 Aug 1997 15:47:27 +1 +Mime-Version: 1.0 +Content-Type: text/plain; charset=ISO-8859-1 +Content-Transfer-Encoding: 8BIT +Comments: Sender has elected to use 8-bit data in this message. If problems + arise, refer to postmaster at sender's site. +Reply-To: diehl@mail.schoeck.de +Priority: normal +In-Reply-To: +References: <9708101509.AA13824@mail.schoeck.de> +X-Mailer: Pegasus Mail for Win32 (v2.52) +XFMstatus: 0000 +From: "Jochen Diehl" +To: Bernd Johannes Wuebben +Subject: RE: kcalc under AIX + + +> O.K Jochen, +> +> das wird jetzt aber ein bische unuebersichtlich. +> Ich schlage vor, Du findest jetzt erst mal in Ruhe eine +> Loesung die fuer AIX funktioniert. Dann sende mir Dein +> kcalc.h und ich werde meine bestes tun das einzubauen. +> Ich glaube es ist am besten wenn wir das so machen, +> ein ganzen Haufen Leute sind naemlich immer zielich sauer, +> wenn ich eine neue version von kcalc rausbringe und +> es laeuft auf ihrer platform nicht mehr richtig. + +Sowas ist in der Tat aergerlich. Langsam verstehe ich, wieso +kommerzielle Softwareschmieden sich so str?uben, auf n Plattformen zu +portieren... + +Ok. Hier also mal meine Aenderungen, sind eigentlich minimal. +Immerhin funktioniert die Trigonometrie dann bei mir. Ausser beim +ersten Start: cos 0 gibt dann 0.9932483259 irgendwas, erst wenn ich +AC druecke kommt 1 raus. Sonst klappt das erstmal. AIX scheint +uebrigens asinhl und Konsorten nicht zu kennen, obwohl sinhl bekannt +ist. Nun ja, hier der diff: +kcalc.h +72a73,81 +> #if defined(_AIX) && defined(HAVE_FABSL) +> #define __LONGDOUBLE128 +> #define pi M_PI +> #define asinhl(X) asinh(X) +> #define acoshl(X) acosh(X) +> #define atanhl(X) atanh(X) +> #endif +> +> +kcalc_core.cpp +35c35 +39d38 +< #include +40a40 +> #include +68a69 +> #ifndef _AIX +69a71 +> #endif +157a160 +> #ifndef _AIX +158a162 +> #endif +Damit bricht der Compiler wenigstens mal nicht ab. + + +Anscheinend ist IBM echt zu doof zum Rechnen (wundert mich, bei den +Preisen und den unendlich vielen Seriennummern :-), Deine Definition +von pi schluckt es nicht, da kommt dann NaNQ in die Anzeige. + +Auch sonst stimmt etwas noch nicht so, ich habe nur noch nicht +herausgefunden, woran das liegt: +Nach exp(1) zeigt kcalc e^1.5 an usw. Was am Source falsch sein +soll, ist mir absolut schleierhaft, ich habe genau denselben Code in +einem eigenen Programm und da rechnet er es richtig. Weiss der Geier. + +"0!" bringt bei mir einen core dump, schaetze mal, der modfl (IBM) +ist daran schuld. Wenn ich vorher ein cout mache, bleibt das Programm +stehen, cored aber wenigstens nicht. + +Was soll's? Da ich scheinbar der einzige bin, der Interesse an KDE +auf AIX hat, stoert mich das nicht so ungemein, will sagen: ich habe +keine Probleme xcalc zu nehmen (wenn ich ueberhaupt mal einen +Taschenrechner brauche). Ansonsten gilt natuerlich die +Standardaussage: Super, dass sich jemand hinsetzt und den Code +ueberhaupt schreibt und dann noch fuer umme weggibt. + +Fass es bitte nicht als Kritik auf, sondern einfach als Info, was +unter anderen OS so alles passieren kann :-) Was Du mit deinem Source +machst, ist natuerlich Deine Sache, ich will hier ja keinen +veraergern. + +Viele Gruesse +Jochen + + + +------------------------------------------------------------- +Jochen Diehl, R/3-Basis +Schoeck AG, Vimbucher Str. 2, 76534 Baden-Baden +Tel.: +497223967381 Fax.: +497223967352 diff --git a/kcalc/doc/kcalc_on_OSF.txt b/kcalc/doc/kcalc_on_OSF.txt new file mode 100644 index 00000000..59b6e6ef --- /dev/null +++ b/kcalc/doc/kcalc_on_OSF.txt @@ -0,0 +1,62 @@ +X-RDate: Mon, 01 Sep 1997 07:45:22 -0400 (EDT) +Return-Path: +Received: from cornell.edu (cornell.edu [132.236.56.6]) by + postoffice2.mail.cornell.edu (8.8.5/8.8.5) with ESMTP id KAA06175 for + ; Tue, 26 Aug 1997 10:56:10 -0400 (EDT) +Received: (from daemon@localhost) by cornell.edu (8.8.5/8.8.5) id KAA25547 for + bw18@postoffice3.mail.cornell.edu; Tue, 26 Aug 1997 10:55:42 -0400 (EDT) +Received: from polygon.math.cornell.edu (POLYGON.MATH.CORNELL.EDU + [128.84.234.110]) by cornell.edu (8.8.5/8.8.5) with SMTP id KAA25126 for + ; Tue, 26 Aug 1997 10:55:15 -0400 (EDT) +Received: from mpimail.mpi-hd.mpg.de by polygon.math.cornell.edu (5.x/SMI-SVR4) + id AA07964; Tue, 26 Aug 1997 10:54:56 -0400 +Received: from daniel.mpi-hd.mpg.de (daniel.mpi-hd.mpg.de [149.217.1.90]) by + mpimail.mpi-hd.mpg.de (8.8.2/8.8.2) with SMTP id QAA19532 for + ; Tue, 26 Aug 1997 16:54:56 +0200 (MET DST) +Received: from localhost by daniel.mpi-hd.mpg.de + (5.65v4.0/1.1.10.5/31Jul97-0446PM) id AA04869; Tue, 26 Aug 1997 16:54:56 +0200 +Date: Tue, 26 Aug 1997 16:54:56 +0200 (MET DST) +X-PH: V4.1@cornell.edu (Cornell Modified) +Message-Id: +Mime-Version: 1.0 +Content-Type: TEXT/PLAIN; charset=US-ASCII +XFMstatus: 0002 +From: Lars Knoll +To: wuebben@math.cornell.edu +Subject: kcalc unter dec osf4.0 + +Hi, + +ich hatte ein paar Probleme, kcalc auf einer dec alpha unter osf4.0 +zu kompilieren. Das Problem ist, dass bei diesem System + sizeof(long double) = sizeof(double) +ist, und dass Routinen namens asinl, fabsl, ... existieren, aber nicht +in irgendwelchen headers definiert werden. +Zusaetzlich funktioniert fabsl() wie erwartet, asinl gibt aber leider +immer nur 0. zurueck. Der folgende patch loest das Problem bei mir. + +Lars + + +diff -c kcalc/kcalc.h kcalc.osf/kcalc.h +*** kcalc/kcalc.h Sun Aug 3 05:01:41 1997 +--- kcalc.osf/kcalc.h Tue Aug 26 16:53:12 1997 +*************** +*** 63,68 **** +--- 63,72 ---- + + /* TAKE CARE OF TH HAVE_LONG_DOUBLE defines in core.cpp*/ + ++ /* dec osf4.0 has fabsl, but not asinl... */ ++ #ifdef __osf__ ++ #undef HAVE_FABSL ++ #endif + + #ifdef HAVE_FABSL + #define CALCAMNT long double + + + +--- +Lars Knoll knoll@mpi-hd.mpg.de + PGP pub key [6DADF3D5]: finger knoll@pluto.mpi-hd.mpg.de diff --git a/kcalc/fonts.ui b/kcalc/fonts.ui new file mode 100644 index 00000000..8f3980a4 --- /dev/null +++ b/kcalc/fonts.ui @@ -0,0 +1,84 @@ + + Fonts + + + + 0 + 0 + 288 + 74 + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + &Button font: + + + kcfg_ButtonFont + + + + + + + Qt::WheelFocus + + + The font to use for the buttons + + + + + + + &Display font: + + + kcfg_DisplayFont + + + + + + + Qt::WheelFocus + + + The font to use in the display + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + KFontRequester + QWidget +
kfontrequester.h
+
+
+ + +
diff --git a/kcalc/general.ui b/kcalc/general.ui new file mode 100644 index 00000000..5569f37c --- /dev/null +++ b/kcalc/general.ui @@ -0,0 +1,240 @@ + + + General + + + + 0 + 0 + 293 + 319 + + + + General + + + + + + Precision + + + + + + + + &Maximum number of digits: + + + false + + + kcfg_Precision + + + + + + + Maximum number of digits displayed + + + KCalc can compute with many more digits than the number that fits on the display. This setting gives the maximum number of digits displayed, before KCalc starts using scientific notation, i.e. notation of the type 2.34e12. + + + + + + + Whether to use fixed decimal places + + + Set &decimal precision + + + + + + + false + + + Number of fixed decimal digits + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Grouping + + + + + + + + Whether to group digits + + + Group digits + + + true + + + + + + + + + true + + + Binary + + + + + + + Octal + + + + + + + Hexadecimal + + + + + + + + + + + + + + + + + + + + + + + Misc + + + + + + + + Whether to beep on error + + + &Beep on error + + + true + + + + + + + Whether to show the result in the window title + + + Show &result in window title + + + + + + + Whether to use Two's Complement for non-decimal numbers + + + Select to use Two's Complement notation for Binary, Octal and Hexidecimal numbers. This is a common notation to represent negative numbers for non-decimal numbers in computers. + + + Two's complement + + + true + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + kcfg_Precision + kcfg_Fixed + kcfg_FixedPrecision + + + + + kcfg_Fixed + toggled(bool) + kcfg_FixedPrecision + setEnabled(bool) + + + 97 + 76 + + + 256 + 94 + + + + + diff --git a/kcalc/kcalc.cpp b/kcalc/kcalc.cpp new file mode 100644 index 00000000..a6856e07 --- /dev/null +++ b/kcalc/kcalc.cpp @@ -0,0 +1,2328 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 2006 Michel Marti + mma@objectxp.com + +Copyright (C) 1996 - 2000 Bernd Johannes Wuebben + wuebben@kde.org + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "kcalc.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kcalc_bitset.h" +#include "kcalc_const_menu.h" +#include "kcalc_settings.h" +#include "kcalcdisplay.h" +#include "version.h" + +namespace { +const char description[] = I18N_NOOP("KDE Calculator"); +const char version[] = KCALCVERSION; +const int maxprecision = 1000; +} + + +//------------------------------------------------------------------------------ +// Name: KCalculator +// Desc: constructor +//------------------------------------------------------------------------------ +KCalculator::KCalculator(QWidget *parent) : + KXmlGuiWindow(parent), + shift_mode_(false), + hyp_mode_(false), + memory_num_(0.0), + constants_menu_(0), + constants_(0), + core() { + + // central widget to contain all the elements + QWidget *const central = new QWidget(this); + central->setLayoutDirection(Qt::LeftToRight); + setCentralWidget(central); + KAcceleratorManager::setNoAccel(central); + + // load science constants_ from xml-file + KCalcConstMenu::init_consts(); + + // setup interface (order is critical) + setupUi(central); + setupMainActions(); + setupStatusbar(); + createGUI(); + setupKeys(); + + toolBar()->hide(); // hide by default + + // create button groups + base_choose_group_ = new QButtonGroup(this); + base_choose_group_->setExclusive(true); + base_choose_group_->addButton(hexRadio, HexMode); + base_choose_group_->addButton(decRadio, DecMode); + base_choose_group_->addButton(octRadio, OctMode); + base_choose_group_->addButton(binRadio, BinMode); + connect(base_choose_group_, SIGNAL(buttonClicked(int)), + SLOT(slotBaseSelected(int))); + + angle_choose_group_ = new QButtonGroup(this); + angle_choose_group_->setExclusive(true); + angle_choose_group_->addButton(degRadio, DegMode); + angle_choose_group_->addButton(radRadio, RadMode); + angle_choose_group_->addButton(gradRadio, GradMode); + connect(angle_choose_group_, SIGNAL(buttonClicked(int)), + SLOT(slotAngleSelected(int))); + + // additional menu setup + constants_menu_ = createConstantsMenu(); + menuBar()->insertMenu((menuBar()->actions)()[2], constants_menu_); + + // misc setup + setColors(); + setFonts(); + + // Show the result in the app's caption in taskbar (wishlist - bug #52858) + if (KCalcSettings::captionResult() == true) { + connect(calc_display, SIGNAL(changedText(QString)), SLOT(setCaption(QString))); + } + + calc_display->changeSettings(); + setPrecision(); + + updateGeometry(); + + setFixedSize(minimumSize()); + + updateDisplay(UPDATE_FROM_CORE); + + // misc settings + KCalcSettings::EnumCalculatorMode::type calculatorMode = KCalcSettings::calculatorMode(); + + switch (calculatorMode) { + case KCalcSettings::EnumCalculatorMode::science: + action_mode_science_->setChecked(true); + break; + case KCalcSettings::EnumCalculatorMode::statistics: + action_mode_statistic_->setChecked(true); + break; + case KCalcSettings::EnumCalculatorMode::numeral: + action_mode_numeral_->setChecked(true); + break; + case KCalcSettings::EnumCalculatorMode::simple: + default: + action_mode_simple_->setChecked(true); + } + + setAngle(); + setBase(); + + // connections + connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), SLOT(setColors())); + connect(KGlobalSettings::self(), SIGNAL(kdisplayFontChanged()), SLOT(setFonts())); + + calc_display->setFocus(); +} + +//------------------------------------------------------------------------------ +// Name: ~KCalculator +// Desc: deconstructor +//------------------------------------------------------------------------------ +KCalculator::~KCalculator() { + + KCalcSettings::self()->writeConfig(); +} + +//------------------------------------------------------------------------------ +// Name: setupMainActions +// Desc: connects all of the basic actions +//------------------------------------------------------------------------------ +void KCalculator::setupMainActions() { + + // file menu + KStandardAction::quit(this, SLOT(close()), actionCollection()); + + // edit menu + KStandardAction::undo(calc_display, SLOT(slotHistoryBack()), actionCollection()); + KStandardAction::redo(calc_display, SLOT(slotHistoryForward()), actionCollection()); + KStandardAction::cut(calc_display, SLOT(slotCut()), actionCollection()); + KStandardAction::copy(calc_display, SLOT(slotCopy()), actionCollection()); + KStandardAction::paste(calc_display, SLOT(slotPaste()), actionCollection()); + + // mode menu + QActionGroup *modeGroup = new QActionGroup(this); + + action_mode_simple_ = actionCollection()->add(QLatin1String("mode_simple")); + action_mode_simple_->setActionGroup(modeGroup); + action_mode_simple_->setText(i18n("Simple Mode")); + connect(action_mode_simple_, SIGNAL(toggled(bool)), SLOT(slotSetSimpleMode())); + + action_mode_science_ = actionCollection()->add(QLatin1String("mode_science")); + action_mode_science_->setActionGroup(modeGroup); + action_mode_science_->setText(i18n("Science Mode")); + connect(action_mode_science_, SIGNAL(toggled(bool)), SLOT(slotSetScienceMode())); + + action_mode_statistic_ = actionCollection()->add(QLatin1String("mode_statistics")); + action_mode_statistic_->setActionGroup(modeGroup); + action_mode_statistic_->setText(i18n("Statistic Mode")); + connect(action_mode_statistic_, SIGNAL(toggled(bool)), SLOT(slotSetStatisticMode())); + + action_mode_numeral_ = actionCollection()->add(QLatin1String("mode_numeral")); + action_mode_numeral_->setActionGroup(modeGroup); + action_mode_numeral_->setText(i18n("Numeral System Mode")); + connect(action_mode_numeral_, SIGNAL(toggled(bool)), SLOT(slotSetNumeralMode())); + + // settings menu + action_constants_show_ = actionCollection()->add(QLatin1String("show_constants")); + action_constants_show_->setText(i18n("Constants &Buttons")); + action_constants_show_->setChecked(true); + connect(action_constants_show_, SIGNAL(toggled(bool)), SLOT(slotConstantsShow(bool))); + + action_bitset_show_ = actionCollection()->add(QLatin1String("show_bitset")); + action_bitset_show_->setText(i18n("Show B&it Edit")); + action_bitset_show_->setChecked(true); + connect(action_bitset_show_, SIGNAL(toggled(bool)), SLOT(slotBitsetshow(bool))); + + KStandardAction::preferences(this, SLOT(showSettings()), actionCollection()); + KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), actionCollection()); +} + +//------------------------------------------------------------------------------ +// Name: createConstantsMenu +// Desc: creates and returns a pointer to the constant menu +//------------------------------------------------------------------------------ +KCalcConstMenu *KCalculator::createConstantsMenu() { + + KCalcConstMenu *const menu = new KCalcConstMenu(i18n("&Constants"), this); + connect(menu, SIGNAL(triggeredConstant(science_constant)), this, SLOT(slotConstantToDisplay(science_constant))); + return menu; +} + +//------------------------------------------------------------------------------ +// Name: setupStatusbar +// Desc: sets up the status bar with default text +//------------------------------------------------------------------------------ +void KCalculator::setupStatusbar() { + + // Status bar contents + statusBar()->insertPermanentFixedItem(QLatin1String(" NORM "), ShiftField); + statusBar()->setItemAlignment(ShiftField, Qt::AlignCenter); + + statusBar()->insertPermanentFixedItem(QLatin1String(" HEX "), BaseField); + statusBar()->setItemAlignment(BaseField, Qt::AlignCenter); + + statusBar()->insertPermanentFixedItem(QLatin1String(" DEG "), AngleField); + statusBar()->setItemAlignment(AngleField, Qt::AlignCenter); + + statusBar()->insertPermanentFixedItem(QLatin1String(" \xa0\xa0 "), MemField); // nbsp + statusBar()->setItemAlignment(MemField, Qt::AlignCenter); +} + +//------------------------------------------------------------------------------ +// Name: setupNumberKeys +// Desc: sets up number keys and related shortcuts +//------------------------------------------------------------------------------ +void KCalculator::setupNumberKeys() { + + num_button_group_ = new QButtonGroup(this); + connect(num_button_group_, SIGNAL(buttonClicked(int)), SLOT(slotNumberclicked(int))); + + num_button_group_->addButton(pb0, 0); + num_button_group_->addButton(pb1, 1); + num_button_group_->addButton(pb2, 2); + num_button_group_->addButton(pb3, 3); + num_button_group_->addButton(pb4, 4); + num_button_group_->addButton(pb5, 5); + num_button_group_->addButton(pb6, 6); + num_button_group_->addButton(pb7, 7); + num_button_group_->addButton(pb8, 8); + num_button_group_->addButton(pb9, 9); + num_button_group_->addButton(pbA, 0xA); + num_button_group_->addButton(pbB, 0xB); + num_button_group_->addButton(pbC, 0xC); + num_button_group_->addButton(pbD, 0xD); + num_button_group_->addButton(pbE, 0xE); + num_button_group_->addButton(pbF, 0xF); + connect(this, SIGNAL(switchShowAccels(bool)), pb0, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pb1, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pb2, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pb3, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pb4, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pb5, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pb6, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pb7, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pb8, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pb9, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pbA, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pbB, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pbC, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pbD, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pbE, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pbF, SLOT(slotSetAccelDisplayMode(bool))); +} + +//------------------------------------------------------------------------------ +// Name: setupRightKeypad +// Desc: sets up right keypad keys and related shortcuts +//------------------------------------------------------------------------------ +void KCalculator::setupRightKeypad() { + + connect(pbShift, SIGNAL(toggled(bool)), SLOT(slotShifttoggled(bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pbShift, SLOT(slotSetAccelDisplayMode(bool))); + + pbBackspace->setShortcut(QKeySequence(Qt::Key_Backspace)); + new QShortcut(Qt::Key_PageUp, pbBackspace, SLOT(animateClick())); + connect(pbBackspace, SIGNAL(clicked()), SLOT(slotBackspaceclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbBackspace, SLOT(slotSetAccelDisplayMode(bool))); + + pbClear->setShortcut(QKeySequence(Qt::Key_Escape)); + new QShortcut(Qt::Key_PageUp, pbClear, SLOT(animateClick())); + connect(pbClear, SIGNAL(clicked()), SLOT(slotClearclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbClear, SLOT(slotSetAccelDisplayMode(bool))); + + pbAllClear->setShortcut(QKeySequence(Qt::Key_Delete)); + new QShortcut(Qt::Key_PageDown, pbAllClear, SLOT(animateClick())); + connect(pbAllClear, SIGNAL(clicked()), SLOT(slotAllClearclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbAllClear, SLOT(slotSetAccelDisplayMode(bool))); + + pbParenOpen->setShortcut(QKeySequence(Qt::Key_ParenLeft)); + connect(pbParenOpen, SIGNAL(clicked()), SLOT(slotParenOpenclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbParenOpen, SLOT(slotSetAccelDisplayMode(bool))); + + pbParenClose->setShortcut(QKeySequence(Qt::Key_ParenRight)); + connect(pbParenClose, SIGNAL(clicked()), SLOT(slotParenCloseclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbParenClose, SLOT(slotSetAccelDisplayMode(bool))); + + pbMemRecall->setDisabled(true); // nothing in memory at start + connect(pbMemRecall, SIGNAL(clicked()), SLOT(slotMemRecallclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbMemRecall, SLOT(slotSetAccelDisplayMode(bool))); + + connect(pbMemClear, SIGNAL(clicked()), SLOT(slotMemClearclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbMemClear, SLOT(slotSetAccelDisplayMode(bool))); + + pbMemPlusMinus->addMode(ModeNormal, i18nc("Add display to memory", "M+"), i18n("Add display to memory")); + pbMemPlusMinus->addMode(ModeShift, i18nc("Subtract from memory", "M\xe2\x88\x92"), i18n("Subtract from memory")); + connect(pbMemPlusMinus, SIGNAL(clicked()), SLOT(slotMemPlusMinusclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbMemPlusMinus, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbMemPlusMinus, SLOT(slotSetMode(ButtonModeFlags,bool))); + + connect(pbMemStore, SIGNAL(clicked()), SLOT(slotMemStoreclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbMemStore, SLOT(slotSetAccelDisplayMode(bool))); + + pbPercent->setShortcut(QKeySequence(Qt::Key_Percent)); + connect(pbPercent, SIGNAL(clicked()), SLOT(slotPercentclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbPercent, SLOT(slotSetAccelDisplayMode(bool))); + + pbPlusMinus->setShortcut(QKeySequence(Qt::Key_Backslash)); + connect(pbPlusMinus, SIGNAL(clicked()), SLOT(slotPlusMinusclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbPlusMinus, SLOT(slotSetAccelDisplayMode(bool))); +} + +//------------------------------------------------------------------------------ +// Name: setupNumericKeypad +// Desc: sets up numeric keys and related shortcuts +//------------------------------------------------------------------------------ +void KCalculator::setupNumericKeypad() { + + pbCube->addMode(ModeNormal, i18nc("Third power", "x3"), i18n("Third power")); + pbCube->addMode(ModeShift, QLatin1String("3√x"), i18n("Cube root")); + connect(pbCube, SIGNAL(clicked()), SLOT(slotCubeclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbCube, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbCube, SLOT(slotSetMode(ButtonModeFlags,bool))); + + pbDivision->setShortcut(QKeySequence(Qt::Key_Slash)); + new QShortcut(Qt::Key_division, pbDivision, SLOT(animateClick())); + connect(pbDivision, SIGNAL(clicked()), SLOT(slotDivisionclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbDivision, SLOT(slotSetAccelDisplayMode(bool))); + + pbMultiplication->setShortcut(QKeySequence(Qt::Key_Asterisk)); + new QShortcut(Qt::Key_X, pbMultiplication, SLOT(animateClick())); + new QShortcut(Qt::Key_multiply, pbMultiplication, SLOT(animateClick())); + connect(pbMultiplication, SIGNAL(clicked()), SLOT(slotMultiplicationclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbMultiplication, SLOT(slotSetAccelDisplayMode(bool))); + + pbMinus->setShortcut(QKeySequence(Qt::Key_Minus)); + connect(pbMinus, SIGNAL(clicked()), SLOT(slotMinusclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbMinus, SLOT(slotSetAccelDisplayMode(bool))); + + pbPlus->setShortcut(QKeySequence(Qt::Key_Plus)); + connect(pbPlus, SIGNAL(clicked()), SLOT(slotPlusclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbPlus, SLOT(slotSetAccelDisplayMode(bool))); + + pbPeriod->setText(KGlobal::locale()->decimalSymbol()); + pbPeriod->setShortcut(KGlobal::locale()->decimalSymbol()); + + // TODO: is this needed? the above line look slike it should do the right thing? + /* + if (KGlobal::locale()->decimalSymbol() == QLatin1String(".")) { + new QShortcut(Qt::Key_Comma, pbPeriod, SLOT(animateClick())); + } else if (KGlobal::locale()->decimalSymbol() == QLatin1String(",")) { + new QShortcut(Qt::Key_Period, pbPeriod, SLOT(animateClick())); + } + */ + + connect(pbPeriod, SIGNAL(clicked()), SLOT(slotPeriodclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbPeriod, SLOT(slotSetAccelDisplayMode(bool))); + + pbEqual->setShortcut(QKeySequence(Qt::Key_Enter)); + new QShortcut(Qt::Key_Equal, pbEqual, SLOT(animateClick())); + new QShortcut(Qt::Key_Return, pbEqual, SLOT(animateClick())); + connect(pbEqual, SIGNAL(clicked()), SLOT(slotEqualclicked())); + connect(this, SIGNAL(switchShowAccels(bool)), pbEqual, SLOT(slotSetAccelDisplayMode(bool))); +} + +//------------------------------------------------------------------------------ +// Name: setupLogicKeys +// Desc: sets up logic keys and related shortcuts +//------------------------------------------------------------------------------ +void KCalculator::setupLogicKeys() { + + logic_buttons_.append(pbAND); + logic_buttons_.append(pbOR); + logic_buttons_.append(pbXOR); + logic_buttons_.append(pbLsh); + logic_buttons_.append(pbRsh); + logic_buttons_.append(pbCmp); + + pbAND->setShortcut(QKeySequence(Qt::Key_Ampersand)); + connect(this, SIGNAL(switchShowAccels(bool)), pbAND, SLOT(slotSetAccelDisplayMode(bool))); + connect(pbAND, SIGNAL(clicked()), SLOT(slotANDclicked())); + + pbOR->setShortcut(QKeySequence(Qt::Key_Bar)); + connect(this, SIGNAL(switchShowAccels(bool)), pbOR, SLOT(slotSetAccelDisplayMode(bool))); + connect(pbOR, SIGNAL(clicked()), SLOT(slotORclicked())); + + connect(this, SIGNAL(switchShowAccels(bool)), pbXOR, SLOT(slotSetAccelDisplayMode(bool))); + connect(pbXOR, SIGNAL(clicked()), SLOT(slotXORclicked())); + + pbLsh->setShortcut(QKeySequence(Qt::Key_Less)); + connect(this, SIGNAL(switchShowAccels(bool)), pbLsh, SLOT(slotSetAccelDisplayMode(bool))); + connect(pbLsh, SIGNAL(clicked()), SLOT(slotLeftShiftclicked())); + + pbRsh->setShortcut(QKeySequence(Qt::Key_Greater)); + connect(this, SIGNAL(switchShowAccels(bool)), pbRsh, SLOT(slotSetAccelDisplayMode(bool))); + connect(pbRsh, SIGNAL(clicked()), SLOT(slotRightShiftclicked())); + + pbCmp->setShortcut(QKeySequence(Qt::Key_AsciiTilde)); + connect(this, SIGNAL(switchShowAccels(bool)), pbCmp, SLOT(slotSetAccelDisplayMode(bool))); + connect(pbCmp, SIGNAL(clicked()), SLOT(slotNegateclicked())); +} + +//------------------------------------------------------------------------------ +// Name: setupLogicKeys +// Desc: sets up scientific keys and related shortcuts +//------------------------------------------------------------------------------ +void KCalculator::setupScientificKeys() { + + scientific_buttons_.append(pbHyp); + scientific_buttons_.append(pbSin); + scientific_buttons_.append(pbCos); + scientific_buttons_.append(pbTan); + scientific_buttons_.append(pbLog); + scientific_buttons_.append(pbLn); + + connect(this, SIGNAL(switchShowAccels(bool)), pbHyp, SLOT(slotSetAccelDisplayMode(bool))); + connect(pbHyp, SIGNAL(toggled(bool)), SLOT(slotHyptoggled(bool))); + + pbSin->addMode(ModeNormal, i18nc("Sine", "Sin"), i18n("Sine")); + pbSin->addMode(ModeShift, i18nc("Arc sine", "Asin"), i18n("Arc sine")); + pbSin->addMode(ModeHyperbolic, i18nc("Hyperbolic sine", "Sinh"), i18n("Hyperbolic sine")); + pbSin->addMode(ButtonModeFlags(ModeShift | ModeHyperbolic), i18nc("Inverse hyperbolic sine", "Asinh"), i18n("Inverse hyperbolic sine")); + connect(this, SIGNAL(switchShowAccels(bool)), pbSin, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbSin, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbSin, SIGNAL(clicked()), SLOT(slotSinclicked())); + + pbCos->addMode(ModeNormal, i18nc("Cosine", "Cos"), i18n("Cosine")); + pbCos->addMode(ModeShift, i18nc("Arc cosine", "Acos"), i18n("Arc cosine")); + pbCos->addMode(ModeHyperbolic, i18nc("Hyperbolic cosine", "Cosh"), i18n("Hyperbolic cosine")); + pbCos->addMode(ButtonModeFlags(ModeShift | ModeHyperbolic), i18nc("Inverse hyperbolic cosine", "Acosh"), i18n("Inverse hyperbolic cosine")); + connect(this, SIGNAL(switchShowAccels(bool)), pbCos, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbCos, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbCos, SIGNAL(clicked()), SLOT(slotCosclicked())); + + pbTan->addMode(ModeNormal, i18nc("Tangent", "Tan"), i18n("Tangent")); + pbTan->addMode(ModeShift, i18nc("Arc tangent", "Atan"), i18n("Arc tangent")); + pbTan->addMode(ModeHyperbolic, i18nc("Hyperbolic tangent", "Tanh"), i18n("Hyperbolic tangent")); + pbTan->addMode(ButtonModeFlags(ModeShift | ModeHyperbolic), i18nc("Inverse hyperbolic tangent", "Atanh"), i18n("Inverse hyperbolic tangent")); + connect(this, SIGNAL(switchShowAccels(bool)), pbTan, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbTan, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbTan, SIGNAL(clicked()), SLOT(slotTanclicked())); + + pbLog->addMode(ModeNormal, i18nc("Logarithm to base 10", "Log"), i18n("Logarithm to base 10")); + pbLog->addMode(ModeShift, i18nc("10 to the power of x", "10x"), i18n("10 to the power of x")); + connect(this, SIGNAL(switchShowAccels(bool)), pbLog, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbLog, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbLog, SIGNAL(clicked()), SLOT(slotLogclicked())); + pbLn->addMode(ModeNormal, i18nc("Natural log", "Ln"), i18n("Natural log")); + pbLn->addMode(ModeShift, i18nc("Exponential function", "ex"), i18n("Exponential function")); + connect(this, SIGNAL(switchShowAccels(bool)), pbLn, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbLn, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbLn, SIGNAL(clicked()), SLOT(slotLnclicked())); +} + +//------------------------------------------------------------------------------ +// Name: setupStatisticKeys +// Desc: sets up statistical keys and related shortcuts +//------------------------------------------------------------------------------ +void KCalculator::setupStatisticKeys() { + + stat_buttons_.append(pbNData); + stat_buttons_.append(pbMean); + stat_buttons_.append(pbSd); + stat_buttons_.append(pbMed); + stat_buttons_.append(pbDat); + stat_buttons_.append(pbCSt); + + pbNData->addMode(ModeNormal, i18nc("Number of data entered", "N"), i18n("Number of data entered")); + pbNData->addMode(ModeShift, QString::fromUtf8("\xce\xa3") + QLatin1Char('x'), i18n("Sum of all data items")); + connect(this, SIGNAL(switchShowAccels(bool)), pbNData, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbNData, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbNData, SIGNAL(clicked()), SLOT(slotStatNumclicked())); + + pbMean->addMode(ModeNormal, i18nc("Mean", "Mea"), i18n("Mean")); + pbMean->addMode(ModeShift, QString::fromUtf8("\xce\xa3") + QLatin1String("x2"), i18n("Sum of all data items squared")); + connect(this, SIGNAL(switchShowAccels(bool)), pbMean, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbMean, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbMean, SIGNAL(clicked()), SLOT(slotStatMeanclicked())); + + pbSd->addMode(ModeNormal, QString::fromUtf8("\xcf\x83") + QLatin1String("N"), i18n("Standard deviation")); + pbSd->addMode(ModeShift, QString::fromUtf8("\xcf\x83") + QLatin1String("N-1"), i18n("Sample standard deviation")); + connect(this, SIGNAL(switchShowAccels(bool)), pbSd, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbSd, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbSd, SIGNAL(clicked()), SLOT(slotStatStdDevclicked())); + + connect(this, SIGNAL(switchShowAccels(bool)), pbMed, SLOT(slotSetAccelDisplayMode(bool))); + connect(pbMed, SIGNAL(clicked()), SLOT(slotStatMedianclicked())); + + pbDat->addMode(ModeNormal, i18nc("Enter data", "Dat"), i18n("Enter data")); + pbDat->addMode(ModeShift, i18nc("Delete last data item", "CDat"), i18n("Delete last data item")); + connect(this, SIGNAL(switchShowAccels(bool)), pbDat, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbDat, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbDat, SIGNAL(clicked()), SLOT(slotStatDataInputclicked())); + + connect(this, SIGNAL(switchShowAccels(bool)), pbCSt, SLOT(slotSetAccelDisplayMode(bool))); + connect(pbCSt, SIGNAL(clicked()), SLOT(slotStatClearDataclicked())); +} + +//------------------------------------------------------------------------------ +// Name: setupConstantsKeys +// Desc: sets up constants keys and related shortcuts +//------------------------------------------------------------------------------ +void KCalculator::setupConstantsKeys() { + + const_buttons_.append(pbC1); + const_buttons_.append(pbC2); + const_buttons_.append(pbC3); + const_buttons_.append(pbC4); + const_buttons_.append(pbC5); + const_buttons_.append(pbC6); + + pbC1->setButtonNumber(0); + connect(this, SIGNAL(switchShowAccels(bool)), pbC1, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbC1, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbC1, SIGNAL(clicked(int)), this, SLOT(slotConstclicked(int))); + + pbC2->setButtonNumber(1); + connect(this, SIGNAL(switchShowAccels(bool)), pbC2, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbC2, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbC2, SIGNAL(clicked(int)), this, SLOT(slotConstclicked(int))); + + pbC3->setButtonNumber(2); + connect(this, SIGNAL(switchShowAccels(bool)), pbC3, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbC3, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbC3, SIGNAL(clicked(int)), this, SLOT(slotConstclicked(int))); + + pbC4->setButtonNumber(3); + connect(this, SIGNAL(switchShowAccels(bool)), pbC4, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbC4, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbC4, SIGNAL(clicked(int)), this, SLOT(slotConstclicked(int))); + + pbC5->setButtonNumber(4); + connect(this, SIGNAL(switchShowAccels(bool)), pbC5, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbC5, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbC5, SIGNAL(clicked(int)), this, SLOT(slotConstclicked(int))); + + pbC6->setButtonNumber(5); + connect(this, SIGNAL(switchShowAccels(bool)), pbC6, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbC6, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbC6, SIGNAL(clicked(int)), this, SLOT(slotConstclicked(int))); + + changeButtonNames(); +} + +//------------------------------------------------------------------------------ +// Name: setupMiscKeys +// Desc: sets up misc keys and related shortcuts +//------------------------------------------------------------------------------ +void KCalculator::setupMiscKeys() { + + pbMod->addMode(ModeNormal, i18nc("Modulo", "Mod"), i18n("Modulo")); + pbMod->addMode(ModeShift, i18nc("Integer division", "IntDiv"), i18n("Integer division")); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbMod, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pbMod, SLOT(slotSetAccelDisplayMode(bool))); + pbMod->setShortcut(QKeySequence(Qt::Key_Colon)); + connect(pbMod, SIGNAL(clicked()), SLOT(slotModclicked())); + + pbReci->addMode(ModeNormal, i18nc("Reciprocal", "1/x"), i18n("Reciprocal")); + pbReci->addMode(ModeShift, i18nc("n Choose m", "nCm"), i18n("n Choose m")); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbReci, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(this, SIGNAL(switchShowAccels(bool)), pbReci, SLOT(slotSetAccelDisplayMode(bool))); + connect(pbReci, SIGNAL(clicked()), SLOT(slotReciclicked())); + + pbFactorial->addMode(ModeNormal, i18nc("Factorial", "x!"), i18n("Factorial")); + pbFactorial->addMode(ModeShift, QLatin1String("Γ"), i18n("Gamma")); + pbFactorial->setShortcut(QKeySequence(Qt::Key_Exclam)); + connect(this, SIGNAL(switchShowAccels(bool)), pbFactorial, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbFactorial, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbFactorial, SIGNAL(clicked()), SLOT(slotFactorialclicked())); + + pbSquare->addMode(ModeNormal, i18nc("Square", "x2"), i18n("Square")); + pbSquare->addMode(ModeShift, QLatin1String("√x"), i18n("Square root")); + pbSquare->setShortcut(QKeySequence(Qt::Key_BracketLeft)); + new QShortcut(Qt::Key_twosuperior, pbSquare, SLOT(animateClick())); + connect(this, SIGNAL(switchShowAccels(bool)), pbSquare, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbSquare, SLOT(slotSetMode(ButtonModeFlags,bool))); + connect(pbSquare, SIGNAL(clicked()), SLOT(slotSquareclicked())); + + pbPower->addMode(ModeNormal, i18nc("x to the power of y", "xy"), i18n("x to the power of y")); + pbPower->addMode(ModeShift, i18nc("x to the power of 1/y", "x1/y"), i18n("x to the power of 1/y")); + connect(this, SIGNAL(switchShowAccels(bool)), pbPower, SLOT(slotSetAccelDisplayMode(bool))); + connect(this, SIGNAL(switchMode(ButtonModeFlags,bool)), pbPower, SLOT(slotSetMode(ButtonModeFlags,bool))); + pbPower->setShortcut(QKeySequence(Qt::Key_AsciiCircum)); + connect(pbPower, SIGNAL(clicked()), SLOT(slotPowerclicked())); + + pbEE->addMode(ModeNormal, QLatin1String("x" "\xb7" "10y"), i18n("Exponent")); + connect(this, SIGNAL(switchShowAccels(bool)), pbEE, SLOT(slotSetAccelDisplayMode(bool))); + connect(pbEE, SIGNAL(clicked()), SLOT(slotEEclicked())); +} + +//------------------------------------------------------------------------------ +// Name: createConstantsMenu +// Desc: additional setup for button keys +// NOTE: all alphanumeric shorts set in ui file +//------------------------------------------------------------------------------ +void KCalculator::setupKeys() { + + setupNumberKeys(); + setupRightKeypad(); + setupNumericKeypad(); + setupLogicKeys(); + setupScientificKeys(); + setupStatisticKeys(); + setupConstantsKeys(); + setupMiscKeys(); + + // other button lists + + function_button_list_.append(pbHyp); + function_button_list_.append(pbShift); + function_button_list_.append(pbEE); + function_button_list_.append(pbSin); + function_button_list_.append(pbPlusMinus); + function_button_list_.append(pbCos); + function_button_list_.append(pbReci); + function_button_list_.append(pbTan); + function_button_list_.append(pbFactorial); + function_button_list_.append(pbLog); + function_button_list_.append(pbSquare); + function_button_list_.append(pbLn); + function_button_list_.append(pbPower); + function_button_list_.append(pbCube); + + mem_button_list_.append(pbMemRecall); + mem_button_list_.append(pbMemPlusMinus); + mem_button_list_.append(pbMemStore); + mem_button_list_.append(pbMemClear); + mem_button_list_.append(pbClear); + mem_button_list_.append(pbAllClear); + + operation_button_list_.append(pbMultiplication); + operation_button_list_.append(pbParenOpen); + operation_button_list_.append(pbParenClose); + operation_button_list_.append(pbAND); + operation_button_list_.append(pbDivision); + operation_button_list_.append(pbOR); + operation_button_list_.append(pbXOR); + operation_button_list_.append(pbPlus); + operation_button_list_.append(pbMinus); + operation_button_list_.append(pbLsh); + operation_button_list_.append(pbRsh); + operation_button_list_.append(pbPeriod); + operation_button_list_.append(pbEqual); + operation_button_list_.append(pbPercent); + operation_button_list_.append(pbCmp); + operation_button_list_.append(pbMod); +} + +//------------------------------------------------------------------------------ +// Name: updateGeometry +// Desc: makes all the buttons have reasonable sizes +//------------------------------------------------------------------------------ +void KCalculator::updateGeometry() { + + const QSize em = pbAND->fontMetrics().size(0, QLatin1String("M")); + int margin = QApplication::style()->pixelMetric(QStyle::PM_ButtonMargin, 0, 0); + margin = qMax(qMin(margin / 2, 3), 3); + + // left pad + foreach(QObject *obj, leftPad->children()) { + if (KCalcButton *const button = qobject_cast(obj)) { + button->setFixedWidth(em.width() * 4 + margin * 2); + button->installEventFilter(this); + } + } + + // right pad + foreach(QObject *obj, rightPad->children()) { + KCalcButton *const button = qobject_cast(obj); + // let Shift expand freely + if (button && button != pbShift) { + button->setFixedWidth(em.width() * 3 + margin * 2); + button->installEventFilter(this); + } + } + + // numeric pad + foreach(QObject *obj, numericPad->children()) { + if (KCalcButton *const button = qobject_cast(obj)) { + // let pb0 expand freely + if (button != pb0) { + button->setFixedWidth(em.width() * 3 + margin * 2); + } + button->installEventFilter(this); + } + } +} + +//------------------------------------------------------------------------------ +// Name: slotConstantToDisplay +// Desc: inserts a constant +//------------------------------------------------------------------------------ +void KCalculator::slotConstantToDisplay(const science_constant &const_chosen) { + + QString val = const_chosen.value; + val.replace(QLatin1Char('.'), KNumber::decimalSeparator()); + calc_display->setAmount(KNumber(val)); + updateDisplay(0); +} + +//------------------------------------------------------------------------------ +// Name: slotBaseSelected +// Desc: changes the selected numeric base +//------------------------------------------------------------------------------ +void KCalculator::slotBaseSelected(int base) { + + int current_base; + + // set display & statusbar (if item exist in statusbar) + switch (base) { + case BinMode: + current_base = calc_display->setBase(NumBase(2)); + statusBar()->changeItem(QLatin1String("BIN"), BaseField); + calc_display->setStatusText(BaseField, QLatin1String("Bin")); + break; + case OctMode: + current_base = calc_display->setBase(NumBase(8)); + statusBar()->changeItem(QLatin1String("OCT"), BaseField); + calc_display->setStatusText(BaseField, QLatin1String("Oct")); + break; + case DecMode: + current_base = calc_display->setBase(NumBase(10)); + statusBar()->changeItem(QLatin1String("DEC"), BaseField); + calc_display->setStatusText(BaseField, QLatin1String("Dec")); + break; + case HexMode: + current_base = calc_display->setBase(NumBase(16)); + statusBar()->changeItem(QLatin1String("HEX"), BaseField); + calc_display->setStatusText(BaseField, QLatin1String("Hex")); + break; + default: + statusBar()->changeItem(QLatin1String("Error"), BaseField); + calc_display->setStatusText(BaseField, QLatin1String("Error")); + return; + } + + // Enable the buttons available in this base + for (int i = 0; i < current_base; ++i) { + (num_button_group_->buttons()[i])->setEnabled(true); + } + + // Disable the buttons not available in this base + for (int i = current_base; i < 16; ++i) { + (num_button_group_->buttons()[i])->setEnabled(false); + } + + // Only enable the decimal point in decimal + pbPeriod->setEnabled(current_base == NB_DECIMAL); + + // Only enable the x*10^y button in decimal + pbEE->setEnabled(current_base == NB_DECIMAL); + + // Disable buttons that make only sense with floating point numbers + if (current_base != NB_DECIMAL) { + foreach(QAbstractButton *btn, scientific_buttons_) { + btn->setEnabled(false); + } + } else { + foreach(QAbstractButton *btn, scientific_buttons_) { + btn->setEnabled(true); + } + } + + KCalcSettings::setBaseMode(base); +} + +//------------------------------------------------------------------------------ +// Name: keyPressEvent +// Desc: handles keypress events +//------------------------------------------------------------------------------ +void KCalculator::keyPressEvent(QKeyEvent *e) { + + // Fix for bug #314586 + // Basically, on some keyboards such as French, even though the decimal separator + // is "," the numeric keypad has a "." key. So we fake it so people can more seemlessly + // enter numbers using the keypad + if(KNumber::decimalSeparator() != ".") { + if(e->key() == Qt::Key_Period && e->modifiers() & Qt::KeypadModifier) { + pbPeriod->animateClick(); + } + } + + + if (((e->modifiers() & Qt::NoModifier) == 0) || (e->modifiers() & Qt::ShiftModifier)) { + switch (e->key()) { + case Qt::Key_Backspace: + calc_display->deleteLastDigit(); + break; + } + } + + if (e->key() == Qt::Key_Control) { + emit switchShowAccels(true); + } +} + +//------------------------------------------------------------------------------ +// Name: keyReleaseEvent +// Desc: handles keyrelease events +//------------------------------------------------------------------------------ +void KCalculator::keyReleaseEvent(QKeyEvent *e) { + + if (e->key() == Qt::Key_Control) { + emit switchShowAccels(false); + } +} + +//------------------------------------------------------------------------------ +// Name: slotAngleSelected +// Desc: changes the selected angle system +//------------------------------------------------------------------------------ +void KCalculator::slotAngleSelected(int mode) { + + angle_mode_ = mode; + + switch (mode) { + case DegMode: + statusBar()->changeItem(QLatin1String("DEG"), AngleField); + calc_display->setStatusText(AngleField, QLatin1String("Deg")); + break; + case RadMode: + statusBar()->changeItem(QLatin1String("RAD"), AngleField); + calc_display->setStatusText(AngleField, QLatin1String("Rad")); + break; + case GradMode: + statusBar()->changeItem(QLatin1String("GRA"), AngleField); + calc_display->setStatusText(AngleField, QLatin1String("Gra")); + break; + default: // we shouldn't ever end up here + angle_mode_ = RadMode; + } + + KCalcSettings::setAngleMode(angle_mode_); +} + +//------------------------------------------------------------------------------ +// Name: slotEEclicked +// Desc: starts the entering of numers using scientific notation +//------------------------------------------------------------------------------ +void KCalculator::slotEEclicked() { + calc_display->newCharacter(QLatin1Char('e')); +} + +//------------------------------------------------------------------------------ +// Name: slotShifttoggled +// Desc: updates the shift state for alternate button functionality +//------------------------------------------------------------------------------ +void KCalculator::slotShifttoggled(bool flag) { + + shift_mode_ = flag; + + emit switchMode(ModeShift, flag); + + if (shift_mode_) { + statusBar()->changeItem(i18nc("Second button functions are active", "SHIFT"), ShiftField); + calc_display->setStatusText(ShiftField, i18n("Shift")); + } else { + statusBar()->changeItem(i18nc("Normal button functions are active", "NORM"), ShiftField); + calc_display->setStatusText(ShiftField, QString()); + } +} + +//------------------------------------------------------------------------------ +// Name: slotHyptoggled +// Desc: updates the Hyp state for alternate trig button functionality +//------------------------------------------------------------------------------ +void KCalculator::slotHyptoggled(bool flag) { + + // toggle between hyperbolic and standart trig functions + hyp_mode_ = flag; + + emit switchMode(ModeHyperbolic, flag); +} + +//------------------------------------------------------------------------------ +// Name: slotMemRecallclicked +// Desc: recalls a value from memory +//------------------------------------------------------------------------------ +void KCalculator::slotMemRecallclicked() { + + // temp. work-around + calc_display->sendEvent(KCalcDisplay::EventReset); + + calc_display->setAmount(memory_num_); + updateDisplay(0); +} + +//------------------------------------------------------------------------------ +// Name: slotMemStoreclicked +// Desc: stores a value into memory +//------------------------------------------------------------------------------ +void KCalculator::slotMemStoreclicked() { + + EnterEqual(); + + memory_num_ = calc_display->getAmount(); + calc_display->setStatusText(MemField, QLatin1String("M")); + statusBar()->changeItem(QLatin1String("M"), MemField); + pbMemRecall->setEnabled(true); +} + +//------------------------------------------------------------------------------ +// Name: slotNumberclicked +// Desc: user has entered a digit +//------------------------------------------------------------------------------ +void KCalculator::slotNumberclicked(int number_clicked) { + + calc_display->enterDigit(number_clicked); +} + +//------------------------------------------------------------------------------ +// Name: slotSinclicked +// Desc: executes the sine function +//------------------------------------------------------------------------------ +void KCalculator::slotSinclicked() { + + if (hyp_mode_) { + // sinh or arsinh + if (!shift_mode_) { + core.SinHyp(calc_display->getAmount()); + } else { + core.AreaSinHyp(calc_display->getAmount()); + } + } else { + // sine or arcsine + if (!shift_mode_) { + switch (angle_mode_) { + case DegMode: + core.SinDeg(calc_display->getAmount()); + break; + case RadMode: + core.SinRad(calc_display->getAmount()); + break; + case GradMode: + core.SinGrad(calc_display->getAmount()); + break; + } + } else { + switch (angle_mode_) { + case DegMode: + core.ArcSinDeg(calc_display->getAmount()); + break; + case RadMode: + core.ArcSinRad(calc_display->getAmount()); + break; + case GradMode: + core.ArcSinGrad(calc_display->getAmount()); + break; + } + } + } + + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotPlusMinusclicked +// Desc: changes sign of number being displayed +//------------------------------------------------------------------------------ +void KCalculator::slotPlusMinusclicked() { + + // display can only change sign, when in input mode, otherwise we + // need the core to do this. + if (!calc_display->sendEvent(KCalcDisplay::EventChangeSign)) { + core.InvertSign(calc_display->getAmount()); + updateDisplay(UPDATE_FROM_CORE); + } +} + +//------------------------------------------------------------------------------ +// Name: slotMemPlusMinusclicked +// Desc: handles arithmetic on values stored in memory +//------------------------------------------------------------------------------ +void KCalculator::slotMemPlusMinusclicked() { + + bool tmp_shift_mode = shift_mode_; // store this, because next command deletes shift_mode_ + EnterEqual(); // finish calculation so far, to store result into MEM + + if (!tmp_shift_mode) { + memory_num_ += calc_display->getAmount(); + } else { + memory_num_ -= calc_display->getAmount(); + } + + pbShift->setChecked(false); + statusBar()->changeItem(i18n("M"), MemField); + calc_display->setStatusText(MemField, i18n("M")); + pbMemRecall->setEnabled(true); +} + +//------------------------------------------------------------------------------ +// Name: slotSinclicked +// Desc: executes the cosine function +//------------------------------------------------------------------------------ +void KCalculator::slotCosclicked() { + + if (hyp_mode_) { + // cosh or arcosh + if (!shift_mode_) { + core.CosHyp(calc_display->getAmount()); + } else { + core.AreaCosHyp(calc_display->getAmount()); + } + } else { + // cosine or arccosine + if (!shift_mode_) { + switch (angle_mode_) { + case DegMode: + core.CosDeg(calc_display->getAmount()); + break; + case RadMode: + core.CosRad(calc_display->getAmount()); + break; + case GradMode: + core.CosGrad(calc_display->getAmount()); + break; + } + } else { + switch (angle_mode_) { + case DegMode: + core.ArcCosDeg(calc_display->getAmount()); + break; + case RadMode: + core.ArcCosRad(calc_display->getAmount()); + break; + case GradMode: + core.ArcCosGrad(calc_display->getAmount()); + break; + } + } + } + + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotSinclicked +// Desc: executes the recipricol function +//------------------------------------------------------------------------------ +void KCalculator::slotReciclicked() { + + if (shift_mode_) { + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_BINOM); + } else { + core.Reciprocal(calc_display->getAmount()); + updateDisplay(UPDATE_FROM_CORE); + return; + } + + // temp. work-around + KNumber tmp_num = calc_display->getAmount(); + calc_display->sendEvent(KCalcDisplay::EventReset); + calc_display->setAmount(tmp_num); + updateDisplay(0); +} + +//------------------------------------------------------------------------------ +// Name: slotSinclicked +// Desc: executes the tangent function +//------------------------------------------------------------------------------ +void KCalculator::slotTanclicked() { + + if (hyp_mode_) { + // tanh or artanh + if (!shift_mode_) { + core.TangensHyp(calc_display->getAmount()); + } else { + core.AreaTangensHyp(calc_display->getAmount()); + } + } else { + // tan or arctan + if (!shift_mode_) { + switch (angle_mode_) { + case DegMode: + core.TangensDeg(calc_display->getAmount()); + break; + case RadMode: + core.TangensRad(calc_display->getAmount()); + break; + case GradMode: + core.TangensGrad(calc_display->getAmount()); + break; + } + } else { + switch (angle_mode_) { + case DegMode: + core.ArcTangensDeg(calc_display->getAmount()); + break; + case RadMode: + core.ArcTangensRad(calc_display->getAmount()); + break; + case GradMode: + core.ArcTangensGrad(calc_display->getAmount()); + break; + } + } + } + + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotFactorialclicked +// Desc: executes the factorial function +//------------------------------------------------------------------------------ +void KCalculator::slotFactorialclicked() { + + // Set WaitCursor, as this operation may take looooong + // time and UI frezes with large numbers. User needs some + // visual feedback. + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + if (!shift_mode_) { + core.Factorial(calc_display->getAmount()); + } else { + core.Gamma(calc_display->getAmount()); + } + QApplication::restoreOverrideCursor(); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotLogclicked +// Desc: executes the Log function +//------------------------------------------------------------------------------ +void KCalculator::slotLogclicked() { + + if (!shift_mode_) { + core.Log10(calc_display->getAmount()); + } else { + core.Exp10(calc_display->getAmount()); + } + + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotSquareclicked +// Desc: executes the x^2 function +//------------------------------------------------------------------------------ +void KCalculator::slotSquareclicked() { + + if (!shift_mode_) { + core.Square(calc_display->getAmount()); + } else { + core.SquareRoot(calc_display->getAmount()); + } + + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotCubeclicked +// Desc: executes the x^3 function +//------------------------------------------------------------------------------ +void KCalculator::slotCubeclicked() { + + if (!shift_mode_) { + core.Cube(calc_display->getAmount()); + } else { + core.CubeRoot(calc_display->getAmount()); + } + + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotCubeclicked +// Desc: executes the ln function +//------------------------------------------------------------------------------ +void KCalculator::slotLnclicked() { + + if (!shift_mode_) { + core.Ln(calc_display->getAmount()); + } else { + core.Exp(calc_display->getAmount()); + } + + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotPowerclicked +// Desc: executes the x^y function +//------------------------------------------------------------------------------ +void KCalculator::slotPowerclicked() { + + if (shift_mode_) { + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_PWR_ROOT); + pbShift->setChecked(false); + } else { + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_POWER); + } + + // temp. work-around + KNumber tmp_num = calc_display->getAmount(); + calc_display->sendEvent(KCalcDisplay::EventReset); + calc_display->setAmount(tmp_num); + updateDisplay(0); +} + +//------------------------------------------------------------------------------ +// Name: slotMemClearclicked +// Desc: executes the MC function +//------------------------------------------------------------------------------ +void KCalculator::slotMemClearclicked() { + + memory_num_ = KNumber::Zero; + statusBar()->changeItem(QLatin1String(" \xa0\xa0 "), MemField); // nbsp + calc_display->setStatusText(MemField, QString()); + pbMemRecall->setDisabled(true); +} + +//------------------------------------------------------------------------------ +// Name: slotBackspaceclicked +// Desc: removes the last input digit +//------------------------------------------------------------------------------ +void KCalculator::slotBackspaceclicked() { + + calc_display->deleteLastDigit(); +} + +//------------------------------------------------------------------------------ +// Name: slotClearclicked +// Desc: clears the display +//------------------------------------------------------------------------------ +void KCalculator::slotClearclicked() { + + calc_display->sendEvent(KCalcDisplay::EventClear); +} + +//------------------------------------------------------------------------------ +// Name: slotAllClearclicked +// Desc: clears everything +//------------------------------------------------------------------------------ +void KCalculator::slotAllClearclicked() { + + core.Reset(); + calc_display->sendEvent(KCalcDisplay::EventReset); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotParenOpenclicked +// Desc: starts a sub-expression +//------------------------------------------------------------------------------ +void KCalculator::slotParenOpenclicked() { + + core.ParenOpen(calc_display->getAmount()); +} + +//------------------------------------------------------------------------------ +// Name: slotParenCloseclicked +// Desc: ends a sub-expression +//------------------------------------------------------------------------------ +void KCalculator::slotParenCloseclicked() { + + core.ParenClose(calc_display->getAmount()); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotANDclicked +// Desc: executes a bitwise AND +//------------------------------------------------------------------------------ +void KCalculator::slotANDclicked() { + + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_AND); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotMultiplicationclicked +// Desc: executes multiplication +//------------------------------------------------------------------------------ +void KCalculator::slotMultiplicationclicked() { + + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_MULTIPLY); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotDivisionclicked +// Desc: executes division +//------------------------------------------------------------------------------ +void KCalculator::slotDivisionclicked() { + + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_DIVIDE); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotORclicked +// Desc: executes a bitwise OR +//------------------------------------------------------------------------------ +void KCalculator::slotORclicked() { + + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_OR); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotXORclicked +// Desc: executes a bitwise XOR +//------------------------------------------------------------------------------ +void KCalculator::slotXORclicked() { + + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_XOR); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotPlusclicked +// Desc: executes addition +//------------------------------------------------------------------------------ +void KCalculator::slotPlusclicked() { + + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_ADD); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotPlusclicked +// Desc: executes subtraction +//------------------------------------------------------------------------------ +void KCalculator::slotMinusclicked() { + + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_SUBTRACT); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotLeftShiftclicked +// Desc: executes a bitwise left shift +//------------------------------------------------------------------------------ +void KCalculator::slotLeftShiftclicked() { + + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_LSH); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotLeftShiftclicked +// Desc: executes a bitwise right shift +//------------------------------------------------------------------------------ +void KCalculator::slotRightShiftclicked() { + + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_RSH); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotPeriodclicked +// Desc: enters a decimal into the input stream +//------------------------------------------------------------------------------ +void KCalculator::slotPeriodclicked() { + + // i know this isn't locale friendly, should be converted to appropriate + // value at lower levels + calc_display->newCharacter(KGlobal::locale()->decimalSymbol()[0]); +} + +//------------------------------------------------------------------------------ +// Name: EnterEqual +// Desc: calculates and displays the result of the pending operations +//------------------------------------------------------------------------------ +void KCalculator::EnterEqual() { + + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_EQUAL); + updateDisplay(UPDATE_FROM_CORE | UPDATE_STORE_RESULT); +} + +//------------------------------------------------------------------------------ +// Name: slotEqualclicked +// Desc: calculates and displays the result of the pending operations +//------------------------------------------------------------------------------ +void KCalculator::slotEqualclicked() { + + EnterEqual(); +} + +//------------------------------------------------------------------------------ +// Name: slotPercentclicked +// Desc: calculates and displays the result of the pending operations as a percent +//------------------------------------------------------------------------------ +void KCalculator::slotPercentclicked() { + + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_PERCENT); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotNegateclicked +// Desc: executes a bitwise 2's compliment +// NOTE: implicitly converts the value to an unsigned quantity +//------------------------------------------------------------------------------ +void KCalculator::slotNegateclicked() { + + core.Complement(calc_display->getAmount()); + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotModclicked +// Desc: executes modulous (remainder division) +//------------------------------------------------------------------------------ +void KCalculator::slotModclicked(){ + + if (shift_mode_) { + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_INTDIV); + } else { + core.enterOperation(calc_display->getAmount(), CalcEngine::FUNC_MOD); + } + + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotStatNumclicked +// Desc: executes Sum function +//------------------------------------------------------------------------------ +void KCalculator::slotStatNumclicked() { + + if (!shift_mode_) { + core.StatCount(KNumber::Zero); + } else { + pbShift->setChecked(false); + core.StatSum(KNumber::Zero); + } + + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotStatMeanclicked +// Desc: executes Mean function +//------------------------------------------------------------------------------ +void KCalculator::slotStatMeanclicked() { + + if (!shift_mode_) { + core.StatMean(KNumber::Zero); + } else { + pbShift->setChecked(false); + core.StatSumSquares(KNumber::Zero); + } + + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotStatStdDevclicked +// Desc: executes STD function +//------------------------------------------------------------------------------ +void KCalculator::slotStatStdDevclicked() { + + if (shift_mode_) { + // std (n-1) + core.StatStdDeviation(KNumber::Zero); + pbShift->setChecked(false); + } else { + // std (n) + core.StatStdSample(KNumber::Zero); + } + + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotStatMedianclicked +// Desc: executes Median function +//------------------------------------------------------------------------------ +void KCalculator::slotStatMedianclicked() { + + if (!shift_mode_) { + // std (n-1) + core.StatMedian(KNumber::Zero); + } else { + // std (n) + core.StatMedian(KNumber::Zero); + pbShift->setChecked(false); + } + + // TODO: it seems two different modes should be implemented, but...? + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotStatDataInputclicked +// Desc: enters a value for statistical functions +//------------------------------------------------------------------------------ +void KCalculator::slotStatDataInputclicked() { + + if (!shift_mode_) { + core.StatDataNew(calc_display->getAmount()); + } else { + pbShift->setChecked(false); + core.StatDataDel(KNumber::Zero); + statusBar()->showMessage(i18n("Last stat item erased"), 3000); + } + + updateDisplay(UPDATE_FROM_CORE); +} + +//------------------------------------------------------------------------------ +// Name: slotStatClearDataclicked +// Desc: clears memory for statical functions +//------------------------------------------------------------------------------ +void KCalculator::slotStatClearDataclicked() { + + if (!shift_mode_) { + core.StatClearAll(KNumber::Zero); + statusBar()->showMessage(i18n("Stat mem cleared"), 3000); + } else { + pbShift->setChecked(false); + updateDisplay(0); + } +} + +//------------------------------------------------------------------------------ +// Name: slotConstclicked +// Desc: enters a constant +//------------------------------------------------------------------------------ +void KCalculator::slotConstclicked(int button) { + + if(KCalcConstButton *btn = qobject_cast(const_buttons_[button])) { + if (!shift_mode_) { + // set the display to the configured value of constant button + // internally, we deal with C locale style numbers, we need to convert + QString val = btn->constant(); + val.replace(QLatin1Char('.'), KNumber::decimalSeparator()); + calc_display->setAmount(KNumber(val)); + + } else { + pbShift->setChecked(false); + + // internally, we deal with C locale style numbers, we need to convert + QString val = calc_display->text(); + val.replace(KNumber::decimalSeparator(), QLatin1String(".")); + KCalcSettings::setValueConstant(button, val); + + // below set new tooltip + btn->setLabelAndTooltip(); + + // work around: after storing a number, pressing a digit should start + // a new number + calc_display->setAmount(calc_display->getAmount()); + } + + updateDisplay(0); + } +} + +//------------------------------------------------------------------------------ +// Name: showSettings +// Desc: opens the shows the settings dialog +//------------------------------------------------------------------------------ +void KCalculator::showSettings() { + + // Check if there is already a dialog and if so bring + // it to the foreground. + if (KConfigDialog::showDialog(QLatin1String("settings"))) { + return; + } + + // Create a new dialog with the same name as the above checking code. + KConfigDialog *const dialog = new KConfigDialog(this, QLatin1String("settings"), KCalcSettings::self()); + dialog->showButtonSeparator(true); + + // general settings + General *const general = new General(0); + general->kcfg_Precision->setMaximum(maxprecision); + dialog->addPage(general, i18n("General"), QLatin1String("accessories-calculator"), i18n("General Settings")); + + // font settings + Fonts *const fonts = new Fonts(0); + dialog->addPage(fonts, i18n("Font"), QLatin1String("preferences-desktop-font"), i18n("Select Display Font")); + + // color settings + Colors *const color = new Colors(0); + dialog->addPage(color, i18n("Colors"), QLatin1String("format-fill-color"), i18n("Button & Display Colors")); + + // constant settings + if (!constants_) { + constants_ = new Constants(0); + } + + KCalcConstMenu *tmp_menu; + tmp_menu = new KCalcConstMenu(this); + connect(tmp_menu, SIGNAL(triggeredConstant(science_constant)), this, SLOT(slotChooseScientificConst0(science_constant))); + constants_->pushButton0->setMenu(tmp_menu); + + tmp_menu = new KCalcConstMenu(this); + connect(tmp_menu, SIGNAL(triggeredConstant(science_constant)), this, SLOT(slotChooseScientificConst1(science_constant))); + constants_->pushButton1->setMenu(tmp_menu); + + tmp_menu = new KCalcConstMenu(this); + connect(tmp_menu, SIGNAL(triggeredConstant(science_constant)), this, SLOT(slotChooseScientificConst2(science_constant))); + constants_->pushButton2->setMenu(tmp_menu); + + tmp_menu = new KCalcConstMenu(this); + connect(tmp_menu, SIGNAL(triggeredConstant(science_constant)), this, SLOT(slotChooseScientificConst3(science_constant))); + constants_->pushButton3->setMenu(tmp_menu); + + tmp_menu = new KCalcConstMenu(this); + connect(tmp_menu, SIGNAL(triggeredConstant(science_constant)), this, SLOT(slotChooseScientificConst4(science_constant))); + constants_->pushButton4->setMenu(tmp_menu); + + tmp_menu = new KCalcConstMenu(this); + connect(tmp_menu, SIGNAL(triggeredConstant(science_constant)), this, SLOT(slotChooseScientificConst5(science_constant))); + constants_->pushButton5->setMenu(tmp_menu); + + dialog->addPage(constants_, i18n("Constants"), QLatin1String("preferences-kcalc-constants"), i18n("Define Constants")); + + // When the user clicks OK or Apply we want to update our settings. + connect(dialog, SIGNAL(settingsChanged(QString)), SLOT(updateSettings())); + + // Display the dialog. + dialog->show(); +} + + +// these 6 slots are just a quick hack, instead of setting the +// TextEdit fields in the configuration dialog, we are setting the +// Settingvalues themselves!! + +//------------------------------------------------------------------------------ +// Name: slotChooseScientificConst0 +// Desc: updates constants value +//------------------------------------------------------------------------------ +void KCalculator::slotChooseScientificConst0(const science_constant &chosen_const) { + constants_->kcfg_valueConstant0->setText(chosen_const.value); + constants_->kcfg_nameConstant0->setText(chosen_const.label); +} + +//------------------------------------------------------------------------------ +// Name: slotChooseScientificConst1 +// Desc: updates constants value +//------------------------------------------------------------------------------ +void KCalculator::slotChooseScientificConst1(const science_constant &chosen_const) { + constants_->kcfg_valueConstant1->setText(chosen_const.value); + constants_->kcfg_nameConstant1->setText(chosen_const.label); +} + +//------------------------------------------------------------------------------ +// Name: slotChooseScientificConst2 +// Desc: updates constants value +//------------------------------------------------------------------------------ +void KCalculator::slotChooseScientificConst2(const science_constant &chosen_const) { + constants_->kcfg_valueConstant2->setText(chosen_const.value); + constants_->kcfg_nameConstant2->setText(chosen_const.label); +} + +//------------------------------------------------------------------------------ +// Name: slotChooseScientificConst3 +// Desc: updates constants value +//------------------------------------------------------------------------------ +void KCalculator::slotChooseScientificConst3(const science_constant &chosen_const) { + constants_->kcfg_valueConstant3->setText(chosen_const.value); + constants_->kcfg_nameConstant3->setText(chosen_const.label); +} + +//------------------------------------------------------------------------------ +// Name: slotChooseScientificConst4 +// Desc: updates constants value +//------------------------------------------------------------------------------ +void KCalculator::slotChooseScientificConst4(const science_constant &chosen_const) { + constants_->kcfg_valueConstant4->setText(chosen_const.value); + constants_->kcfg_nameConstant4->setText(chosen_const.label); +} + +//------------------------------------------------------------------------------ +// Name: slotChooseScientificConst5 +// Desc: updates constants value +//------------------------------------------------------------------------------ +void KCalculator::slotChooseScientificConst5(const science_constant &chosen_const) { + constants_->kcfg_valueConstant5->setText(chosen_const.value); + constants_->kcfg_nameConstant5->setText(chosen_const.label); +} + +//------------------------------------------------------------------------------ +// Name: slotSetSimpleMode +// Desc: sets the calculator to have a simple layout +//------------------------------------------------------------------------------ +void KCalculator::slotSetSimpleMode() { + + action_constants_show_->setChecked(false); + action_constants_show_->setEnabled(false); + action_bitset_show_->setChecked(false); + action_bitset_show_->setEnabled(false); + showMemButtons(false); + showScienceButtons(false); + showStatButtons(false); + showLogicButtons(false); + + // hide some individual buttons, which are not in one of the above groups + pbShift->hide(); + pbMod->hide(); + pbReci->hide(); + pbFactorial->hide(); + pbSquare->hide(); + pbPower->hide(); + pbCube->hide(); + pbBackspace->hide(); + pbEE->hide(); + + // delete the constant menu since it doesn't fit + delete constants_menu_; + constants_menu_ = 0; + + KCalcSettings::setCalculatorMode(KCalcSettings::EnumCalculatorMode::simple); +} + +//------------------------------------------------------------------------------ +// Name: slotSetScienceMode +// Desc: sets the calculator to science mode +//------------------------------------------------------------------------------ +void KCalculator::slotSetScienceMode() { + + action_constants_show_->setEnabled(true); + action_constants_show_->setChecked(KCalcSettings::showConstants()); + action_bitset_show_->setChecked(false); + action_bitset_show_->setEnabled(false); + + // show some individual buttons + pbShift->show(); + pbMod->show(); + pbReci->show(); + pbFactorial->show(); + pbSquare->show(); + pbPower->show(); + pbCube->show(); + pbBackspace->show(); + pbEE->show(); + + // show or hide some groups of buttons + showMemButtons(true); + showScienceButtons(true); + showStatButtons(false); + showLogicButtons(false); + + if(!constants_menu_) { + constants_menu_ = createConstantsMenu(); + menuBar()->insertMenu((menuBar()->actions)()[2], constants_menu_); + } + + KCalcSettings::setCalculatorMode(KCalcSettings::EnumCalculatorMode::science); +} + +//------------------------------------------------------------------------------ +// Name: slotSetStatisticMode +// Desc: sets the calculator to stats mode +//------------------------------------------------------------------------------ +void KCalculator::slotSetStatisticMode() { + + action_constants_show_->setEnabled(true); + action_constants_show_->setChecked(KCalcSettings::showConstants()); + action_bitset_show_->setChecked(false); + action_bitset_show_->setEnabled(false); + + // show some individual buttons + pbShift->show(); + pbMod->show(); + pbReci->show(); + pbFactorial->show(); + pbSquare->show(); + pbPower->show(); + pbCube->show(); + pbBackspace->show(); + pbEE->show(); + + // show or hide some groups of buttons + showMemButtons(true); + showScienceButtons(true); + showStatButtons(true); + showLogicButtons(false); + + if(!constants_menu_) { + constants_menu_ = createConstantsMenu(); + menuBar()->insertMenu((menuBar()->actions)()[2], constants_menu_); + } + + KCalcSettings::setCalculatorMode(KCalcSettings::EnumCalculatorMode::statistics); +} + +//------------------------------------------------------------------------------ +// Name: slotSetNumeralMode +// Desc: sets the calculator to numerical ("programmers") mode +//------------------------------------------------------------------------------ +void KCalculator::slotSetNumeralMode() { + + action_constants_show_->setChecked(false); + action_constants_show_->setEnabled(false); + action_bitset_show_->setEnabled(true); + action_bitset_show_->setChecked(KCalcSettings::showBitset()); + + // show some individual buttons + pbShift->show(); + pbMod->show(); + pbReci->show(); + pbFactorial->show(); + pbSquare->show(); + pbPower->show(); + pbCube->show(); + pbBackspace->show(); + pbEE->show(); + + // show or hide some groups of buttons + showMemButtons(true); + showScienceButtons(false); + showStatButtons(false); + showLogicButtons(true); + + if(!constants_menu_) { + constants_menu_ = createConstantsMenu(); + menuBar()->insertMenu((menuBar()->actions)()[2], constants_menu_); + } + + KCalcSettings::setCalculatorMode(KCalcSettings::EnumCalculatorMode::numeral); +} + +//------------------------------------------------------------------------------ +// Name: showMemButtons +// Desc: hides or shows the memory buttons +//------------------------------------------------------------------------------ +void KCalculator::showMemButtons(bool toggled) { + + if (toggled) { + foreach(QAbstractButton *btn, mem_button_list_) { + btn->show(); + } + } else { + foreach(QAbstractButton *btn, mem_button_list_) { + btn->hide(); + } + + // these are in the mem_button_list_ but should not be hidden + pbClear->show(); + pbAllClear->show(); + } +} + +//------------------------------------------------------------------------------ +// Name: showStatButtons +// Desc: hides or shows the stat buttons +//------------------------------------------------------------------------------ +void KCalculator::showStatButtons(bool toggled) { + + if (toggled) { + foreach(QAbstractButton *btn, stat_buttons_) { + btn->show(); + } + } else { + foreach(QAbstractButton *btn, stat_buttons_) { + btn->hide(); + } + } +} + +//------------------------------------------------------------------------------ +// Name: showScienceButtons +// Desc: hides or shows the science buttons +//------------------------------------------------------------------------------ +void KCalculator::showScienceButtons(bool toggled) { + + if (toggled) { + foreach(QAbstractButton* btn, scientific_buttons_) { + btn->show(); + } + + foreach(QAbstractButton* btn, angle_choose_group_->buttons()) { + btn->show(); + } + + setAngle(); + statusBar()->setItemFixed(AngleField, -1); + } else { + foreach(QAbstractButton* btn, scientific_buttons_) { + btn->hide(); + } + + foreach(QAbstractButton* btn, angle_choose_group_->buttons()) { + btn->hide(); + } + + statusBar()->changeItem(QString(), AngleField); + statusBar()->setItemFixed(AngleField, 0); + calc_display->setStatusText(AngleField, QString()); + } +} + +//------------------------------------------------------------------------------ +// Name: showLogicButtons +// Desc: hides or shows the logic buttons +//------------------------------------------------------------------------------ +void KCalculator::showLogicButtons(bool toggled) { + + if (toggled) { + mBitset->setEnabled(true); + connect(mBitset, SIGNAL(valueChanged(quint64)), this, SLOT(slotBitsetChanged(quint64))); + connect(calc_display, SIGNAL(changedAmount(KNumber)), SLOT(slotUpdateBitset(KNumber))); + + foreach(QAbstractButton* btn, logic_buttons_) { + btn->show(); + } + + setBase(); + statusBar()->setItemFixed(BaseField, -1); + + foreach(QAbstractButton *btn, base_choose_group_->buttons()) { + btn->show(); + } + + for (int i = 10; i < 16; ++i) { + (num_button_group_->button(i))->show(); + } + } else { + mBitset->setEnabled(false); + disconnect(mBitset, SIGNAL(valueChanged(quint64)), this, SLOT(slotBitsetChanged(quint64))); + disconnect(calc_display, SIGNAL(changedAmount(KNumber)), this, SLOT(slotUpdateBitset(KNumber))); + + foreach(QAbstractButton* btn, logic_buttons_) { + btn->hide(); + } + + // Hide Hex-Buttons, but first switch back to decimal + decRadio->animateClick(0); + + foreach(QAbstractButton *btn, base_choose_group_->buttons()) { + btn->hide(); + } + + statusBar()->changeItem(QString(), BaseField); + statusBar()->setItemFixed(BaseField, 0); + calc_display->setStatusText(BaseField, QString()); + for (int i = 10; i < 16; ++i) { + (num_button_group_->button(i))->hide(); + } + } +} + +//------------------------------------------------------------------------------ +// Name: slotConstantsShow +// Desc: hides or shows the constants buttons +//------------------------------------------------------------------------------ +void KCalculator::slotConstantsShow(bool toggled) { + + if (toggled) { + foreach(QAbstractButton *btn, const_buttons_) { + btn->show(); + } + } else { + foreach(QAbstractButton *btn, const_buttons_) { + btn->hide(); + } + } + + KCalcSettings::setShowConstants(toggled); +} + +//------------------------------------------------------------------------------ +// Name: slotBitsetshow +// Desc: hides or shows the bitset buttons +//------------------------------------------------------------------------------ +void KCalculator::slotBitsetshow(bool toggled) { + + mBitset->setVisible(toggled); + KCalcSettings::setShowBitset(toggled); +} + +//------------------------------------------------------------------------------ +// Name: slotBitsetshow +// Desc: This function is for setting the constant names configured in the +// kcalc settings menu. If the user doesn't enter a name for the +// constant C1 to C6 is used. +//------------------------------------------------------------------------------ +void KCalculator::changeButtonNames() { + + foreach(QAbstractButton *btn, const_buttons_) { + if (KCalcConstButton *const constbtn = qobject_cast(btn)) { + constbtn->setLabelAndTooltip(); + } + } +} + +//------------------------------------------------------------------------------ +// Name: slotBitsetChanged +// Desc: updates the bitset display +// NOTE: sets display to *unsigned* value +//------------------------------------------------------------------------------ +void KCalculator::slotBitsetChanged(quint64 value) { + + calc_display->setAmount(KNumber(value)); + updateDisplay(0); +} + +//------------------------------------------------------------------------------ +// Name: slotUpdateBitset +// Desc: updates the bitset itself +//------------------------------------------------------------------------------ +void KCalculator::slotUpdateBitset(const KNumber &nr) { + + mBitset->setValue(nr.toUint64()); +} + +//------------------------------------------------------------------------------ +// Name: updateSettings +// Desc: updates the persistent settings +//------------------------------------------------------------------------------ +void KCalculator::updateSettings() { + + changeButtonNames(); + setColors(); + setFonts(); + setPrecision(); + + // Show the result in the app's caption in taskbar (wishlist - bug #52858) + disconnect(calc_display, SIGNAL(changedText(QString)), this, 0); + + if (KCalcSettings::captionResult()) { + connect(calc_display, SIGNAL(changedText(QString)), SLOT(setCaption(QString))); + } else { + setCaption(QString()); + } + + calc_display->changeSettings(); + updateGeometry(); +} + +//------------------------------------------------------------------------------ +// Name: updateDisplay +// Desc: updates the display +//------------------------------------------------------------------------------ +void KCalculator::updateDisplay(UpdateFlags flags) { + + if(flags & UPDATE_FROM_CORE) { + calc_display->updateFromCore(core, (flags & UPDATE_STORE_RESULT) != 0); + } else { + calc_display->update(); + } + + pbShift->setChecked(false); + +} + +//------------------------------------------------------------------------------ +// Name: setColors +// Desc: set the various colours +//------------------------------------------------------------------------------ +void KCalculator::setColors() { + + calc_display->changeSettings(); + + KColorScheme schemeButtons(QPalette::Active, KColorScheme::Button); + const QColor defaultColor = schemeButtons.background().color(); + + if (KCalcSettings::numberButtonsColor() == defaultColor + && KCalcSettings::functionButtonsColor() == defaultColor + && KCalcSettings::statButtonsColor() == defaultColor + && KCalcSettings::hexButtonsColor() == defaultColor + && KCalcSettings::memoryButtonsColor() == defaultColor + && KCalcSettings::operationButtonsColor() == defaultColor) { + return; + } + + const QString sheet = QLatin1String("KPushButton { background-color: %1 }"); + + const QColor numPal(KCalcSettings::numberButtonsColor()); + for (int i = 0; i < 10; ++i) { + (num_button_group_->button(i))->setStyleSheet(sheet.arg(numPal.name())); + } + + const QColor funcPal(KCalcSettings::functionButtonsColor()); + foreach(QAbstractButton *btn, function_button_list_) { + btn->setStyleSheet(sheet.arg(funcPal.name())); + } + + const QColor statPal(KCalcSettings::statButtonsColor()); + foreach(QAbstractButton *btn, stat_buttons_) { + btn->setStyleSheet(sheet.arg(statPal.name())); + } + + const QColor hexPal(KCalcSettings::hexButtonsColor()); + for (int i = 10; i < 16; ++i) { + (num_button_group_->button(i))->setStyleSheet(sheet.arg(hexPal.name())); + } + + const QColor memPal(KCalcSettings::memoryButtonsColor()); + foreach(QAbstractButton *btn, mem_button_list_) { + btn->setStyleSheet(sheet.arg(memPal.name())); + } + + const QColor opPal(KCalcSettings::operationButtonsColor()); + foreach(QAbstractButton *btn, operation_button_list_) { + btn->setStyleSheet(sheet.arg(opPal.name())); + } +} + +//------------------------------------------------------------------------------ +// Name: setFonts +// Desc: set the various fonts +//------------------------------------------------------------------------------ +void KCalculator::setFonts() { + + foreach(QObject *obj, leftPad->children()) { + if (KCalcButton *const button = qobject_cast(obj)) { + button->setFont(KCalcSettings::buttonFont()); + } + } + + foreach(QObject *obj, numericPad->children()) { + if (KCalcButton *const button = qobject_cast(obj)) { + button->setFont(KCalcSettings::buttonFont()); + } + } + + foreach(QObject *obj, rightPad->children()) { + if (KCalcButton *const button = qobject_cast(obj)) { + button->setFont(KCalcSettings::buttonFont()); + } + } + + updateGeometry(); +} + +//------------------------------------------------------------------------------ +// Name: setPrecision +// Desc: set the precision of the display +//------------------------------------------------------------------------------ +void KCalculator::setPrecision() { + + KNumber::setDefaultFloatPrecision(KCalcSettings::precision()); + updateDisplay(0); +} + +//------------------------------------------------------------------------------ +// Name: setAngle +// Desc: sets the angle mode +//------------------------------------------------------------------------------ +void KCalculator::setAngle() { + + if (QAbstractButton *const btn = angle_choose_group_->button(KCalcSettings::angleMode())) { + btn->animateClick(0); + } +} + +//------------------------------------------------------------------------------ +// Name: setBase +// Desc: sets the numeric base +//------------------------------------------------------------------------------ +void KCalculator::setBase() { + if (QAbstractButton *const btn = base_choose_group_->button(KCalcSettings::baseMode())) { + btn->animateClick(0); + } +} + +//------------------------------------------------------------------------------ +// Name: eventFilter +// Desc: general event filter used to track events like drag/drop +//------------------------------------------------------------------------------ +bool KCalculator::eventFilter(QObject *o, QEvent *e) { + + switch (e->type()) { + case QEvent::DragEnter: { + QDragEnterEvent *const ev = reinterpret_cast(e); + ev->setAccepted(KColorMimeData::canDecode(ev->mimeData())); + return true; + } + case QEvent::DragLeave: { + return true; + } + case QEvent::Drop: { + KCalcButton *const calcButton = qobject_cast(o); + if (!calcButton) { + return false; + } + + QDropEvent *const ev = reinterpret_cast(e); + QColor c = KColorMimeData::fromMimeData(ev->mimeData()); + + if (c.isValid()) { + QString cn = c.name(); + QString sheet = QLatin1String("background-color: %1"); + + QList *list; + const int num_but = num_button_group_->buttons().indexOf(calcButton); + if (num_but != -1) { + // Was it hex-button or normal digit?? + if (num_but < 10) { + for (int i = 0; i < 10; ++i) { + (num_button_group_->buttons()[i])->setStyleSheet(sheet.arg(cn)); + } + } else { + for (int i = 10; i < 16; ++i) { + (num_button_group_->buttons()[i])->setStyleSheet(sheet.arg(cn)); + } + } + return true; + } else if (function_button_list_.contains(calcButton)) { + list = &function_button_list_; + } else if (stat_button_list_.contains(calcButton)) { + list = &stat_button_list_; + } else if (mem_button_list_.contains(calcButton)) { + list = &mem_button_list_; + } else if (operation_button_list_.contains(calcButton)) { + list = &operation_button_list_; + } else { + return false; + } + + for (int i = 0; i < list->size(); ++i) { + list->at(i)->setStyleSheet(sheet.arg(cn)); + } + } + return true; + } + // FALL THROUGH + default: + return KXmlGuiWindow::eventFilter(o, e); + } +} + +//////////////////////////////////////////////////////////////// +// Include the meta-object code for classes in this file +// +#include "kcalc.moc" + +//------------------------------------------------------------------------------ +// Name: kdemain +// Desc: entry point of the application +//------------------------------------------------------------------------------ +extern "C" KDE_EXPORT int kdemain(int argc, char *argv[]) { + + KAboutData aboutData("kcalc", + 0, + ki18n("KCalc"), + version, + ki18n(description), + KAboutData::License_GPL, + ki18n( + "© 2008-2013, Evan Teran\n" + "© 2000-2008, The KDE Team\n" + "© 2003-2005, Klaus Niederkr" "\xc3\xbc" "ger\n" + "© 1996-2000, Bernd Johannes Wuebben"), + KLocalizedString(), + "http://utils.kde.org/projects/kcalc"); + + // Klaus Niederkrueger + aboutData.addAuthor(ki18n("Klaus Niederkr" "\xc3\xbc" "ger"), KLocalizedString(), "kniederk@math.uni-koeln.de"); + aboutData.addAuthor(ki18n("Bernd Johannes Wuebben"), KLocalizedString(), "wuebben@kde.org"); + aboutData.addAuthor(ki18n("Evan Teran"), ki18n("Maintainer"), "eteran@alum.rit.edu"); + aboutData.addAuthor(ki18n("Espen Sand"), KLocalizedString(), "espen@kde.org"); + aboutData.addAuthor(ki18n("Chris Howells"), KLocalizedString(), "howells@kde.org"); + aboutData.addAuthor(ki18n("Aaron J. Seigo"), KLocalizedString(), "aseigo@olympusproject.org"); + aboutData.addAuthor(ki18n("Charles Samuels"), KLocalizedString(), "charles@altair.dhs.org"); + // Rene Merou + aboutData.addAuthor(ki18n("Ren" "\xc3\xa9" " M" "\xc3\xa9" "rou"), KLocalizedString(), "ochominutosdearco@yahoo.es"); + aboutData.addAuthor(ki18n("Michel Marti"), KLocalizedString(), "mma@objectxp.com"); + aboutData.addAuthor(ki18n("David Johnson"), KLocalizedString(), "david@usermode.org"); + + aboutData.setProgramIconName(QLatin1String("accessories-calculator")); + + KCmdLineArgs::init(argc, argv, &aboutData); + + KApplication app; + + // force system locale to "C" internally [bug 159168] + setlocale(LC_NUMERIC, "C"); + + KNumber::setGroupSeparator(KGlobal::locale()->thousandsSeparator()); + KNumber::setDecimalSeparator(KGlobal::locale()->decimalSymbol()); + + KCalculator *calc = new KCalculator(0); + app.setTopWidget(calc); + + calc->show(); + return app.exec(); +} diff --git a/kcalc/kcalc.desktop b/kcalc/kcalc.desktop new file mode 100755 index 00000000..086ee963 --- /dev/null +++ b/kcalc/kcalc.desktop @@ -0,0 +1,154 @@ +[Desktop Entry] +Name=KCalc +Name[af]=Kcalc +Name[ar]=حاسبة ك +Name[ast]=KCalc +Name[bg]=KCalc +Name[br]=KCalc +Name[bs]=KCalc +Name[ca]=KCalc +Name[ca@valencia]=KCalc +Name[cs]=KCalc +Name[cy]=KCalc +Name[da]=KCalc +Name[de]=KCalc +Name[el]=KCalc +Name[en_GB]=KCalc +Name[eo]=KCalc +Name[es]=KCalc +Name[et]=KCalc +Name[eu]=KCalc +Name[fi]=KCalc +Name[fr]=KCalc +Name[ga]=KCalc +Name[gl]=KCalc +Name[he]=KCalc +Name[hne]=के-केल्क +Name[hr]=KCalc +Name[hu]=KCalc +Name[ia]=KCalc +Name[id]=KCalc +Name[is]=KCalc +Name[it]=KCalc +Name[ja]=KCalc +Name[kk]=KCalc +Name[km]=KCalc +Name[ko]=KCalc +Name[lt]=KCalc +Name[lv]=KCalc +Name[mk]=KCalc +Name[mr]=के-कॅल्क +Name[ms]=KCalc +Name[nb]=KCalc +Name[nds]=KCalc +Name[ne]=केडीई क्याल्क +Name[nl]=KCalc +Name[nn]=KCalc +Name[pa]=ਕੇ-ਕੈਲਕ +Name[pl]=Kalkulator +Name[pt]=KCalc +Name[pt_BR]=KCalc +Name[ro]=KCalc +Name[ru]=KCalc +Name[sk]=KCalc +Name[sl]=KCalc +Name[sq]=KCalc +Name[sr]=К‑калк +Name[sr@ijekavian]=К‑калк +Name[sr@ijekavianlatin]=KCalc +Name[sr@latin]=KCalc +Name[sv]=Kcalc +Name[ta]=கேகணிப்பான் +Name[tg]=KМошинаи ҳисобкунак +Name[th]=เครื่องคิดเลข-K +Name[tr]=KCalc +Name[ug]=KCalc +Name[uk]=KCalc +Name[uz]=Kalkulyator +Name[uz@cyrillic]=Калкулятор +Name[vi]=KCalc +Name[wa]=KCalc +Name[x-test]=xxKCalcxx +Name[zh_CN]=KCalc +Name[zh_TW]=KDE 計算機 +Exec=kcalc -caption %c +Icon=accessories-calculator +X-DocPath=kcalc/index.html +GenericName=Scientific Calculator +GenericName[af]=Wetenskaplike Sakrekenaar +GenericName[ar]=آلة حاسبة علمية +GenericName[ast]=Calculadora Científica +GenericName[bg]=Калкулатор +GenericName[br]=Ur jederez skiantel +GenericName[bs]=Naučni kalkulator +GenericName[ca]=Calculadora científica +GenericName[ca@valencia]=Calculadora científica +GenericName[cs]=Vědecká kalkulačka +GenericName[cy]=Cyfrifydd Gwyddonol +GenericName[da]=Videnskabelig regnemaskine +GenericName[de]=Wissenschaftlicher Taschenrechner +GenericName[el]=Επιστημονικό κομπιουτεράκι +GenericName[en_GB]=Scientific Calculator +GenericName[eo]=Scienca kalkulilo +GenericName[es]=Calculadora científica +GenericName[et]=Teaduslik kalkulaator +GenericName[eu]=Kalkulagailu zientifikoa +GenericName[fa]=ماشین حساب علمی +GenericName[fi]=Tieteellinen laskin +GenericName[fr]=Calculatrice scientifique +GenericName[ga]=Áireamhán Eolaíochta +GenericName[gl]=Unha calculadora científica +GenericName[he]=מחשבון מדעי +GenericName[hne]=वैग्यानिक केल्कुलेटर +GenericName[hr]=Znanstveni kalkulator +GenericName[hu]=Tudományos számológép +GenericName[ia]=Calculator scientific +GenericName[id]=Kalkulator Ilmiah +GenericName[is]=Öflug reiknivél +GenericName[it]=Calcolatrice scientifica +GenericName[ja]=科学電卓 +GenericName[kk]=Ғылыми калькулятор +GenericName[km]=​ម៉ាស៊ីន​គិត​លេខ​​​វិទ្យាសាស្ត្រ +GenericName[ko]=공학용 계산기 +GenericName[lt]=Mokslinis skaičiuotuvas +GenericName[lv]=Zinātniskais kalkulators +GenericName[mk]=Научен калкулатор +GenericName[mr]=शास्त्रीय गणकयंत्र +GenericName[ms]=Scientific Calculator +GenericName[nb]=Vitenskapelig kalkulator +GenericName[nds]=Wetenschaplich Taschenreekner +GenericName[ne]=वैज्ञानिक गणकयन्त्र +GenericName[nl]=Wetenschappelijke rekenmachine +GenericName[nn]=Vitskapleg kalkulator +GenericName[pa]=ਵਿਗਿਆਨਿਕ ਕੈਲਕੂਲੇਟਰ +GenericName[pl]=Kalkulator naukowy +GenericName[pt]=Calculadora Cientifica +GenericName[pt_BR]=Calculadora científica +GenericName[ro]=Calculator științific +GenericName[ru]=Калькулятор +GenericName[sk]=Vedecká kalkulačka +GenericName[sl]=Znanstveno računalo +GenericName[sq]=Makinë Llogaritëse Shkencore +GenericName[sr]=Научни калкулатор +GenericName[sr@ijekavian]=Научни калкулатор +GenericName[sr@ijekavianlatin]=Naučni kalkulator +GenericName[sr@latin]=Naučni kalkulator +GenericName[sv]=Vetenskaplig miniräknare +GenericName[ta]= அறிவியல் சார்ந்த கணிப்பான் +GenericName[tg]=Мошинаи ҳисобкунаки Илмӣ +GenericName[th]=เครื่องคิดเลขทางวิทยาศาสตร์ +GenericName[tr]=Bilimsel Hesap Makinesi +GenericName[ug]=ئىلمىي ھېسابلىغۇچ +GenericName[uk]=Калькулятор для науковців +GenericName[uz]=Ilmiy kalkulyator +GenericName[uz@cyrillic]=Илмий калкулятор +GenericName[vi]=Máy Tính Khoa Học +GenericName[wa]=Carculete syintifike +GenericName[xh]=Umatshini Wokubala Ezenzululwazi +GenericName[x-test]=xxScientific Calculatorxx +GenericName[zh_CN]=科学计算器 +GenericName[zh_TW]=科學計算機 +Terminal=false +Type=Application +X-KDE-StartupNotify=true +Categories=Qt;KDE;Utility;X-KDE-Utilities-Desktop; diff --git a/kcalc/kcalc.h b/kcalc/kcalc.h new file mode 100644 index 00000000..823f6b80 --- /dev/null +++ b/kcalc/kcalc.h @@ -0,0 +1,281 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 1996 - 2000 Bernd Johannes Wuebben + wuebben@kde.org + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KCALC_H_ +#define KCALC_H_ + +class Constants; +class QButtonGroup; +class KToggleAction; +class KCalcConstMenu; + +/* + Kcalc basically consist of a class for the GUI (here), a class for + the display (dlabel.h), and one for the mathematics core + (kcalc_core.h). + + When for example '+' is pressed, one sends the contents of the + Display and the '+' to the core via "core.Plus(DISPLAY_AMOUNT)". + This only updates the core. To bring the changes to the display, + use afterwards "UpdateDisplay(true)". + + "UpdateDisplay(true)" means that the amount to be displayed should + be taken from the core (get the result of some operation that was + performed), "UpdateDisplay(false)" has already the information, what + to be display (e.g. user is typing in a number). Note that in the + last case the core does not know the number typed in until some + operation button is pressed, e.g. "core.Plus(display_number)". + */ + +#include "kcalc_core.h" +#include "kcalc_button.h" +#include "kcalc_const_button.h" + +#include "ui_kcalc.h" +#include "ui_general.h" +#include "ui_fonts.h" +#include "ui_constants.h" +#include "ui_colors.h" + +#include + +#include + +class General: public QWidget, public Ui::General +{ +public: + explicit General(QWidget *parent) : QWidget(parent) { + setupUi(this); + } +}; + +class Fonts: public QWidget, public Ui::Fonts +{ +public: + explicit Fonts(QWidget *parent) : QWidget(parent) { + setupUi(this); + } +}; + +class Constants : public QWidget, public Ui::Constants +{ +public: + explicit Constants(QWidget *parent) : QWidget(parent) { + setupUi(this); + } +}; + +class Colors : public QWidget, public Ui::Colors +{ +public: + explicit Colors(QWidget *parent) : QWidget(parent) { + setupUi(this); + } +}; + + +class KCalculator : public KXmlGuiWindow, private Ui::KCalculator +{ + Q_OBJECT + +public: + explicit KCalculator(QWidget *parent = 0); + ~KCalculator(); + +signals: + void switchShift(bool); + void switchMode(ButtonModeFlags, bool); + void switchShowAccels(bool); + +public: + enum UpdateFlag { + UPDATE_FROM_CORE = 1, + UPDATE_STORE_RESULT = 2 + }; + + Q_DECLARE_FLAGS(UpdateFlags, UpdateFlag) + +private: + virtual bool eventFilter(QObject *o, QEvent *e); + void updateGeometry(); + void setupMainActions(); + void setupStatusbar(); + void setupKeys(); + void setupNumberKeys(); + void setupRightKeypad(); + void setupNumericKeypad(); + void setupLogicKeys(); + void setupScientificKeys(); + void setupStatisticKeys(); + void setupConstantsKeys(); + void setupMiscKeys(); + void keyPressEvent(QKeyEvent *e); + void keyReleaseEvent(QKeyEvent *e); + void setPrecision(); + void setAngle(); + void setBase(); + + void updateDisplay(UpdateFlags flags); + + // button sets + void showMemButtons(bool toggled); + void showStatButtons(bool toggled); + void showScienceButtons(bool toggled); + void showLogicButtons(bool toggled); + + KCalcConstMenu *createConstantsMenu(); + +protected slots: + void changeButtonNames(); + void updateSettings(); + void setColors(); + void setFonts(); + void EnterEqual(); + void showSettings(); + + // Mode + void slotSetSimpleMode(); + void slotSetScienceMode(); + void slotSetStatisticMode(); + void slotSetNumeralMode(); + + void slotConstantsShow(bool toggled); + void slotBitsetshow(bool toggled); + void slotAngleSelected(int mode); + void slotBaseSelected(int base); + void slotNumberclicked(int number_clicked); + void slotEEclicked(); + void slotShifttoggled(bool myboolean); + void slotMemRecallclicked(); + void slotMemStoreclicked(); + void slotSinclicked(); + void slotPlusMinusclicked(); + void slotMemPlusMinusclicked(); + void slotCosclicked(); + void slotReciclicked(); + void slotTanclicked(); + void slotFactorialclicked(); + void slotLogclicked(); + void slotSquareclicked(); + void slotCubeclicked(); + void slotLnclicked(); + void slotPowerclicked(); + void slotMemClearclicked(); + void slotClearclicked(); + void slotAllClearclicked(); + void slotParenOpenclicked(); + void slotParenCloseclicked(); + void slotANDclicked(); + void slotMultiplicationclicked(); + void slotDivisionclicked(); + void slotORclicked(); + void slotXORclicked(); + void slotPlusclicked(); + void slotMinusclicked(); + void slotLeftShiftclicked(); + void slotRightShiftclicked(); + void slotPeriodclicked(); + void slotEqualclicked(); + void slotPercentclicked(); + void slotNegateclicked(); + void slotModclicked(); + void slotStatNumclicked(); + void slotStatMeanclicked(); + void slotStatStdDevclicked(); + void slotStatMedianclicked(); + void slotStatDataInputclicked(); + void slotStatClearDataclicked(); + void slotHyptoggled(bool flag); + void slotConstclicked(int); + void slotBackspaceclicked(); + + void slotConstantToDisplay(const science_constant &const_chosen); + void slotChooseScientificConst0(const science_constant &); + void slotChooseScientificConst1(const science_constant &); + void slotChooseScientificConst2(const science_constant &); + void slotChooseScientificConst3(const science_constant &); + void slotChooseScientificConst4(const science_constant &); + void slotChooseScientificConst5(const science_constant &); + + void slotBitsetChanged(quint64); + void slotUpdateBitset(const KNumber &); + +private: + enum StatusField { + ShiftField = 0, + BaseField, + AngleField, + MemField + }; + + enum AngleMode { + DegMode = 0, + RadMode, + GradMode + }; + + enum BaseMode { + BinMode = 2, + OctMode = 8, + DecMode = 10, + HexMode = 16 + }; + +private: + bool shift_mode_; + bool hyp_mode_; + KNumber memory_num_; + + int angle_mode_; // angle modes for trigonometric values + + KCalcConstMenu* constants_menu_; + + Constants* constants_; // this is the dialog for configuring const buttons + + QButtonGroup* angle_choose_group_; + QButtonGroup* base_choose_group_; + // num_button_group_: 0-9 = digits, 0xA-0xF = hex-keys + QButtonGroup* num_button_group_; + + QList logic_buttons_; + QList scientific_buttons_; + QList stat_buttons_; + QList const_buttons_; + + KToggleAction *action_bitset_show_; + KToggleAction *action_constants_show_; + + KToggleAction *action_mode_simple_; + KToggleAction *action_mode_science_; + KToggleAction *action_mode_statistic_; + KToggleAction *action_mode_numeral_; + + QList function_button_list_; + QList stat_button_list_; + QList mem_button_list_; + QList operation_button_list_; + + CalcEngine core; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(KCalculator::UpdateFlags) + +#endif diff --git a/kcalc/kcalc.kcfg b/kcalc/kcalc.kcfg new file mode 100644 index 00000000..5dfac3b6 --- /dev/null +++ b/kcalc/kcalc.kcfg @@ -0,0 +1,201 @@ + + + KApplication + KGlobalSettings + KColorScheme + KLocale + + + + + KColorScheme schemeView(QPalette::Active, KColorScheme::View); + QColor defaultColor = schemeView.foreground().color(); + defaultColor + + + + defaultColor = schemeView.background().color(); + defaultColor + + + + KColorScheme schemeButtons(QPalette::Active, KColorScheme::Button); + QColor defaultButtonColor = schemeButtons.background().color(); + defaultButtonColor + + + + defaultButtonColor + + + + defaultButtonColor + + + + defaultButtonColor + + + + defaultButtonColor + + + + defaultButtonColor + + + + + + KGlobalSettings::generalFont() + + + + QFont(KGlobalSettings::generalFont().family(), 14, QFont::Bold) + + + + + + + KCalc can compute with many more digits than the number that + fits on the display. This setting gives the maximum number of + digits displayed, before KCalc starts using scientific notation, + i.e. notation of the type 2.34e12. + + 12 + 8 + 200 + + + + 2 + + + + false + + + + + + true + + + + false + + + + true + + + + + Select to use Two's Complement notation for Binary, Octal + and Hexidecimal numbers. This is a common notation to represent + negative numbers in computers. + + true + + + + + + A very simple mode where only the basic calculator buttons are shown + + + + Mode with science buttons and optional constants buttons + + + + Mode with additional statistics buttons and optional constants buttons + + + + Mode with logic buttons and selectable base. Optional bit edit available. + + + + + + false + + + + false + + + + 0 + + + + 10 + + + + + + + For easier readability it's possible to visible group the individual digits into pairs + for example instead of 10111001 you can display 1011 1001, by setting the setting to + 4, thus inserting a whitespace after every 4th digit. + + 4 + + + + + For easier readability it's possible to visible group the individual digits into pairs + for example instead of 42140213 you can display 4214 0213, by setting the setting to + 4, thus inserting a whitespace after every 4th digit. + + 4 + + + + + For easier readability it's possible to visible group the individual digits into pairs + for example instead of AF1C42 you can display AF 1C 42, by setting the setting to + 2, thus inserting a whitespace after every 2nd digit. + + 2 + + + + + + + + QString nameConstant0 = i18nc("Name of the user programmable constant", "C1"); + QString nameConstant1 = i18nc("Name of the user programmable constant", "C2"); + QString nameConstant2 = i18nc("Name of the user programmable constant", "C3"); + QString nameConstant3 = i18nc("Name of the user programmable constant", "C4"); + QString nameConstant4 = i18nc("Name of the user programmable constant", "C5"); + QString nameConstant5 = i18nc("Name of the user programmable constant", "C6"); + + nameConstant0 + nameConstant1 + nameConstant2 + nameConstant3 + nameConstant4 + nameConstant5 + + + + + + 0 + 0 + 0 + 0 + 0 + 0 + + + diff --git a/kcalc/kcalc.ui b/kcalc/kcalc.ui new file mode 100644 index 00000000..7511ffeb --- /dev/null +++ b/kcalc/kcalc.ui @@ -0,0 +1,1094 @@ + + + KCalculator + + + + 0 + 0 + 701 + 320 + + + + + 0 + 0 + + + + KCalc + + + + QLayout::SetMinimumSize + + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + + 0 + 0 + + + + Click on a Bit to toggle it. + + + + + + + + + Deg + + + + + + + Rad + + + + + + + Grad + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Switch base to hexadecimal. + + + He&x + + + + + + + Switch base to decimal. + + + &Dec + + + + + + + Switch base to octal. + + + &Oct + + + + + + + Switch base to binary. + + + &Bin + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 2 + + + 4 + + + + + Number of data entered + + + N + + + + + + + Hyperbolic mode + + + Hyp + + + H + + + true + + + + + + + Bitwise AND + + + AND + + + + + + + Modulo + + + Mod + + + + + + + A + + + A + + + + + + + C1 + + + Alt+1 + + + + + + + Mean + + + Mea + + + + + + + Sine + + + Sin + + + S + + + + + + + Bitwise OR + + + OR + + + + + + + Reciprocal + + + 1/X + + + R + + + + + + + B + + + B + + + + + + + C2 + + + Alt+2 + + + + + + + Standard deviation + + + SD + + + + + + + Cosine + + + Cos + + + C + + + + + + + Bitwise XOR + + + XOR + + + + + + + Factorial + + + x! + + + + + + + C + + + C + + + + + + + C3 + + + Alt+3 + + + + + + + Median + + + Med + + + + + + + Tangent + + + Tan + + + T + + + + + + + Left bit shift + + + Lsh + + + + + + + Square + + + x2 + + + + + + + D + + + D + + + + + + + C4 + + + Alt+4 + + + + + + + Enter data + + + Dat + + + D + + + + + + + Logarithm to base 10 + + + Log + + + L + + + + + + + Right bit shift + + + Rsh + + + + + + + x to the power of y + + + pow + + + + + + + E + + + E + + + + + + + C5 + + + Alt+5 + + + + + + + Clear data store + + + CSt + + + + + + + Natural log + + + Ln + + + N + + + + + + + One's complement + + + Cmp + + + + + + + F + + + F + + + + + + + C6 + + + Alt+6 + + + + + + + Exponent + + + EXP + + + E + + + + + + + + 0 + 0 + + + + Cube + + + x3 + + + + + + + + + + + 0 + 0 + + + + + 2 + + + 4 + + + + + + 0 + 0 + + + + 7 + + + 7 + + + + + + + + 0 + 0 + + + + 8 + + + 8 + + + + + + + + 0 + 0 + + + + 9 + + + 9 + + + + + + + + 0 + 0 + + + + 4 + + + 4 + + + + + + + + 0 + 0 + + + + 5 + + + 5 + + + + + + + + 0 + 0 + + + + 6 + + + 6 + + + + + + + + 0 + 0 + + + + 1 + + + 1 + + + + + + + + 0 + 0 + + + + 2 + + + 2 + + + + + + + + 0 + 0 + + + + 3 + + + 3 + + + + + + + + 0 + 0 + + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + Percent + + + % + + + + + + + + 0 + 0 + + + + Division + + + ÷ + + + + + + + + 0 + 0 + + + + Multiplication + + + × + + + + + + + + 0 + 0 + + + + Minus + + + + + + + + + + + 0 + 0 + + + + Plus + + + + + + + + + + + + 0 + 0 + + + + Result + + + = + + + + + + + + 0 + 0 + + + + Decimal point + + + . + + + + + + + + + + + 2 + + + 4 + + + + + Clear all + + + AC + + + + + + + Memory clear + + + MC + + + + + + + Memory recall + + + MR + + + + + + + Change sign + + + +/− + + + + + + + Open parenthesis + + + ( + + + + + + + Clear + + + C + + + + + + + Add to memory + + + M+ + + + + + + + Memory store + + + MS + + + + + + + Close parenthesis + + + ) + + + + + + + + + + Backspace + + + + + + + Second button function + + + Shift + + + Ctrl+2 + + + true + + + + + + + + + + + + + + + KCalcButton + QPushButton +
kcalc_button.h
+
+ + KCalcDisplay + QFrame +
kcalcdisplay.h
+ 1 +
+ + KCalcBitset + QWidget +
kcalc_bitset.h
+ 1 +
+ + KCalcConstButton + QPushButton +
kcalc_const_button.h
+
+
+ + pb0 + pb1 + pb2 + pb3 + pb4 + pb5 + pb6 + pb7 + pb8 + pb9 + pbPeriod + pbDivision + pbMultiplication + pbMinus + pbPlus + pbEqual + pbPercent + pbClear + pbAllClear + pbParenOpen + pbParenClose + pbPlusMinus + pbMemStore + pbMemClear + pbMemRecall + pbMemPlusMinus + pbHyp + pbSin + pbCos + pbTan + pbLog + pbLn + pbMod + pbReci + pbFactorial + pbSquare + pbPower + pbCube + pbEE + pbAND + pbOR + pbXOR + pbLsh + pbRsh + pbCmp + pbA + pbB + pbC + pbD + pbE + pbF + pbC1 + pbC2 + pbC3 + pbC4 + pbC5 + pbC6 + pbNData + pbMean + pbSd + pbMed + pbDat + pbCSt + degRadio + radRadio + gradRadio + hexRadio + decRadio + octRadio + binRadio + calc_display + + + +
diff --git a/kcalc/kcalc_bitset.cpp b/kcalc/kcalc_bitset.cpp new file mode 100644 index 00000000..9b495649 --- /dev/null +++ b/kcalc/kcalc_bitset.cpp @@ -0,0 +1,145 @@ +/* +Copyright (C) 2012 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 2006 Michel Marti + mma@objectxp.com + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "kcalc_bitset.h" +#include "bitbutton.h" + +#include +#include +#include +#include +#include + +#include "kcalc_bitset.moc" + +// TODO: I think it would actually be appropriate to use a std::bitset<64> +// for the internal representation of this class perhaps +// the only real caveat is the conversion to/from quint64 + +//------------------------------------------------------------------------------ +// Name: paintEvent +// Desc: draws the button +//------------------------------------------------------------------------------ +void BitButton::paintEvent(QPaintEvent *) { + + QPainter painter(this); + QPen pen(palette().text(), 2); + pen.setJoinStyle(Qt::MiterJoin); + painter.setPen(pen); + + if (on_) { + painter.setBrush(palette().text()); + } else { + painter.setBrush(palette().base()); + } + + painter.drawRect(rect().adjusted(1, 1, -1, -1)); +} + +//------------------------------------------------------------------------------ +// Name: KCalcBitset +// Desc: constructor +//------------------------------------------------------------------------------ +KCalcBitset::KCalcBitset(QWidget *parent) : QFrame(parent), value_(0) { + + setFrameStyle(QFrame::Panel | QFrame::Sunken); + + bit_button_group_ = new QButtonGroup(this); + connect(bit_button_group_, SIGNAL(buttonClicked(int)), SLOT(slotToggleBit(int))); + + // smaller label font + QFont fnt = font(); + if (fnt.pointSize() > 6) { + fnt.setPointSize(fnt.pointSize() - 1); + } + + // main layout + QGridLayout *layout = new QGridLayout(this); + layout->setMargin(2); + layout->setSpacing(0); + + // create bits + int bitCounter = 63; + for (int rows = 0; rows < 2; rows++) { + for (int cols = 0; cols < 4; cols++) { + // two rows of four words + QHBoxLayout *const wordlayout = new QHBoxLayout(); + wordlayout->setMargin(2); + wordlayout->setSpacing(2); + layout->addLayout(wordlayout, rows, cols); + + for (int bit = 0; bit < 8; bit++) { + BitButton *const tmpBitButton = new BitButton(this); + wordlayout->addWidget(tmpBitButton); + bit_button_group_->addButton(tmpBitButton, bitCounter); + bitCounter--; + } + + // label word + QLabel *label = new QLabel(this); + label->setText(QString::number(bitCounter + 1)); + label->setFont(fnt); + wordlayout->addWidget(label); + } + } +} + +//------------------------------------------------------------------------------ +// Name: setValue +// Desc: set the value of the bitset based on an unsigned 64-bit number +//------------------------------------------------------------------------------ +void KCalcBitset::setValue(quint64 value) { + + if (value_ == value) { + // don't waste time if there was no change.. + return; + } + + value_ = value; + + // set each bit button + for (int i = 0; i < 64; i++) { + if(BitButton *bb = qobject_cast(bit_button_group_->button(i))) { + bb->setOn(value & 1); + } + value >>= 1; + } +} + +//------------------------------------------------------------------------------ +// Name: getValue +// Desc: returns the bitset value as an unsigned 64-bit number +//------------------------------------------------------------------------------ +quint64 KCalcBitset::getValue() const { + return value_; +} + +//------------------------------------------------------------------------------ +// Name: slotToggleBit +// Desc: inverts the value of a single bit +//------------------------------------------------------------------------------ +void KCalcBitset::slotToggleBit(int bit) { + + const quint64 nv = getValue() ^(1LL << bit); + setValue(nv); + emit valueChanged(value_); +} + diff --git a/kcalc/kcalc_bitset.h b/kcalc/kcalc_bitset.h new file mode 100644 index 00000000..d83d6e66 --- /dev/null +++ b/kcalc/kcalc_bitset.h @@ -0,0 +1,48 @@ +/* +Copyright (C) 2012 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 2006 Michel Marti + mma@objectxp.com + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KCALC_BITSET_H_ +#define KCALC_BITSET_H_ + +#include + +class QButtonGroup; + +class KCalcBitset : public QFrame { + Q_OBJECT + +public: + explicit KCalcBitset(QWidget *parent = 0); + quint64 getValue() const; + +public slots: + void setValue(quint64 value); + void slotToggleBit(int bit); + +signals: + void valueChanged(quint64 value); + +private: + QButtonGroup *bit_button_group_; + quint64 value_; +}; + +#endif diff --git a/kcalc/kcalc_button.cpp b/kcalc/kcalc_button.cpp new file mode 100644 index 00000000..d05483c0 --- /dev/null +++ b/kcalc/kcalc_button.cpp @@ -0,0 +1,249 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 1996 - 2000 Bernd Johannes Wuebben + wuebben@kde.org + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "kcalc_button.h" + +#include +#include +#include +#include +#include + +#include "kcalc_button.moc" + +//------------------------------------------------------------------------------ +// Name: KCalcButton +// Desc: constructor +//------------------------------------------------------------------------------ +KCalcButton::KCalcButton(QWidget *parent) : KPushButton(parent), show_shortcut_mode_(false), mode_flags_(ModeNormal), size_() { + + setAcceptDrops(true); // allow color drops + setFocusPolicy(Qt::TabFocus); + setAutoDefault(false); + + // use preferred size policy for vertical + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); +} + +//------------------------------------------------------------------------------ +// Name: KCalcButton +// Desc: constructor +//------------------------------------------------------------------------------ +KCalcButton::KCalcButton(const QString &label, QWidget *parent, const QString &tooltip) : KPushButton(label, parent), show_shortcut_mode_(false), mode_flags_(ModeNormal), size_() { + + setAutoDefault(false); + addMode(ModeNormal, label, tooltip); + + // use preferred size policy for vertical + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); +} + +//------------------------------------------------------------------------------ +// Name: addMode +// Desc: +//------------------------------------------------------------------------------ +void KCalcButton::addMode(ButtonModeFlags mode, const QString &label, const QString &tooltip) { + + if (mode_.contains(mode)) { + mode_.remove(mode); + } + + mode_[mode] = ButtonMode(label, tooltip); + calcSizeHint(); + + // Need to put each button into default mode first + if (mode == ModeNormal) { + slotSetMode(ModeNormal, true); + } +} + +//------------------------------------------------------------------------------ +// Name: slotSetMode +// Desc: +//------------------------------------------------------------------------------ +void KCalcButton::slotSetMode(ButtonModeFlags mode, bool flag) { + + ButtonModeFlags new_mode; + + if (flag) { // if the specified mode is to be set (i.e. flag = true) + new_mode = ButtonModeFlags(mode_flags_ | mode); + } else if (mode_flags_ && mode) { // if the specified mode is to be cleared (i.e. flag = false) + new_mode = ButtonModeFlags(mode_flags_ - mode); + } else { + return; // nothing to do + } + + if (mode_.contains(new_mode)) { + // save shortcut, because setting label erases it + QKeySequence current_shortcut = shortcut(); + + setText(mode_[new_mode].label); + this->setToolTip(mode_[new_mode].tooltip); + mode_flags_ = new_mode; + + // restore shortcut + setShortcut(current_shortcut); + } + + // this is necessary for people pressing CTRL and changing mode at + // the same time... + if (show_shortcut_mode_) { + slotSetAccelDisplayMode(true); + } + + update(); +} + +//------------------------------------------------------------------------------ +// Name: slotSetAccelDisplayMode +// Desc: +//------------------------------------------------------------------------------ +void KCalcButton::slotSetAccelDisplayMode(bool flag) { + + show_shortcut_mode_ = flag; + + // save shortcut, because setting label erases it + QKeySequence current_shortcut = shortcut(); + + if (flag) { + setText(QString(shortcut())); + } else { + setText(mode_[mode_flags_].label); + } + + // restore shortcut + setShortcut(current_shortcut); + update(); +} + +//------------------------------------------------------------------------------ +// Name: paintEvent +// Desc: draws the button +//------------------------------------------------------------------------------ +void KCalcButton::paintEvent(QPaintEvent *) { + + QPainter p(this); + QStyleOptionButton option; + initStyleOption(&option); + const bool is_down = isDown() || isChecked(); + const int x_offset = is_down ? style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &option, this) : 0; + const int y_offset = is_down ? style()->pixelMetric(QStyle::PM_ButtonShiftVertical, &option, this) : 0; + + // draw bevel + style()->drawControl(QStyle::CE_PushButtonBevel, &option, &p, this); + + // draw label... + p.save(); + + // rant: Qt4 needs QSimpleRichText, dammit! + QTextDocument doc; + QAbstractTextDocumentLayout::PaintContext context; + doc.setHtml(QLatin1String("
") + text() + QLatin1String("
")); + doc.setDefaultFont(font()); + context.palette = palette(); + context.palette.setColor(QPalette::Text, context.palette.buttonText().color()); + + p.translate((width() / 2 - doc.size().width() / 2) + x_offset, (height() / 2 - doc.size().height() / 2) + y_offset); + doc.documentLayout()->draw(&p, context); + p.restore(); + + // draw focus + if (hasFocus()) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(option); + fropt.rect = style()->subElementRect(QStyle::SE_PushButtonFocusRect, &option, this); + style()->drawPrimitive(QStyle::PE_FrameFocusRect, &fropt, &p, this); + } +} + +//------------------------------------------------------------------------------ +// Name: sizeHint +// Desc: +//------------------------------------------------------------------------------ +QSize KCalcButton::sizeHint() const { + // reimplemented to provide a smaller button + return size_; +} + +//------------------------------------------------------------------------------ +// Name: calcSizeHint +// Desc: +//------------------------------------------------------------------------------ +void KCalcButton::calcSizeHint() { + + int margin = style()->pixelMetric(QStyle::PM_ButtonMargin, 0, this); + + // want narrow margin than normal + margin = qMax(margin / 2, 3); + + // approximation because metrics doesn't account for richtext + size_ = fontMetrics().size(0, mode_[ModeNormal].label); + if (mode_.contains(ModeShift)) { + size_ = size_.expandedTo(fontMetrics().size(0, mode_[ModeShift].label)); + } + + if (mode_.contains(ModeHyperbolic)) { + size_ = size_.expandedTo(fontMetrics().size(0, mode_[ModeHyperbolic].label)); + } + + size_ += QSize(margin * 2, margin * 2); + size_ = size_.expandedTo(QApplication::globalStrut()); +} + +//------------------------------------------------------------------------------ +// Name: setFont +// Desc: +//------------------------------------------------------------------------------ +void KCalcButton::setFont(const QFont &fnt) { + + KPushButton::setFont(fnt); + calcSizeHint(); +} + +//------------------------------------------------------------------------------ +// Name: setText +// Desc: +//------------------------------------------------------------------------------ +void KCalcButton::setText(const QString &text) { + + KPushButton::setText(text); + + // normal mode may not have been explicitly set + if (mode_[ModeNormal].label.isEmpty()) { + mode_[ModeNormal].label = text; + } + + calcSizeHint(); +} + +//------------------------------------------------------------------------------ +// Name: setToolTip +// Desc: +//------------------------------------------------------------------------------ +void KCalcButton::setToolTip(const QString &tip) { + + KPushButton::setToolTip(tip); + + // normal mode may not have been explicitly set + if (mode_[ModeNormal].tooltip.isEmpty()) { + mode_[ModeNormal].tooltip = tip; + } +} diff --git a/kcalc/kcalc_button.h b/kcalc/kcalc_button.h new file mode 100644 index 00000000..8b227858 --- /dev/null +++ b/kcalc/kcalc_button.h @@ -0,0 +1,92 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 2003 - 2005 Klaus Niederkrueger + kniederk@math.uni-koeln.de + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KCALC_BUTTON_H_ +#define KCALC_BUTTON_H_ + +#include +#include + +// The class KCalcButton is an overridden KPushButton. It offers extra +// functionality e.g. text can be richtext, and the button can be +// told to display its shortcuts in the label, but the most important +// thing is that the button may have several modes with corresponding +// labels and tooltips. When one switches modes, the corresponding +// label is displayed. + + +enum ButtonModeFlags { + ModeNormal = 0, + ModeShift = 1, + ModeHyperbolic = 2 +}; + + +// Each kcalc button can be in one of several modes. +// The following class describes label, tooltip etc. for each mode... +class ButtonMode { +public: + ButtonMode() { + } + + ButtonMode(const QString &label, const QString &tooltip) : label(label), tooltip(tooltip) { + } + + QString label; + QString tooltip; +}; + + +class KCalcButton : public KPushButton { + Q_OBJECT + +public: + explicit KCalcButton(QWidget *parent); + KCalcButton(const QString &label, QWidget *parent, + const QString &tooltip = QString()); + + void addMode(ButtonModeFlags mode, const QString &label, + const QString &tooltip); + + virtual QSize sizeHint() const; // reimp + + void setFont(const QFont &fnt); + void setText(const QString &text); // reimp + void setToolTip(const QString &tip); // reimp + +public slots: + void slotSetMode(ButtonModeFlags mode, bool flag); + void slotSetAccelDisplayMode(bool flag); + +protected: + virtual void paintEvent(QPaintEvent *e); + +private: + void calcSizeHint(); + +private: + bool show_shortcut_mode_; + ButtonModeFlags mode_flags_; + QMap mode_; + QSize size_; +}; + +#endif diff --git a/kcalc/kcalc_const_button.cpp b/kcalc/kcalc_const_button.cpp new file mode 100644 index 00000000..2e800291 --- /dev/null +++ b/kcalc/kcalc_const_button.cpp @@ -0,0 +1,139 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 2003 - 2005 Klaus Niederkrueger + kniederk@math.uni-koeln.de + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "kcalc_const_button.h" +#include "kcalc_const_menu.h" +#include "kcalc_settings.h" + +#include +#include + +#include "kcalc_const_button.moc" + +//------------------------------------------------------------------------------ +// Name: KCalcConstButton +// Desc: constructor +//------------------------------------------------------------------------------ +KCalcConstButton::KCalcConstButton(QWidget *parent) : KCalcButton(parent), button_num_(-1) { + + addMode(ModeShift, i18nc("Write display data into memory", "Store"), i18n("Write display data into memory")); + initPopupMenu(); + connect(this, SIGNAL(clicked()), SLOT(slotClicked())); +} + +//------------------------------------------------------------------------------ +// Name: KCalcConstButton +// Desc: constructor +//------------------------------------------------------------------------------ +KCalcConstButton::KCalcConstButton(const QString &label, QWidget *parent, const QString &tooltip) : KCalcButton(label, parent, tooltip), button_num_(-1) { + + addMode(ModeShift, i18nc("Write display data into memory", "Store"), i18n("Write display data into memory")); + initPopupMenu(); +} + +//------------------------------------------------------------------------------ +// Name: constant +// Desc: get the value of the const as a QString +//------------------------------------------------------------------------------ +QString KCalcConstButton::constant() const { + + return KCalcSettings::valueConstant(button_num_); +} + +//------------------------------------------------------------------------------ +// Name: setButtonNumber +// Desc: remembers the "index" of the const button +//------------------------------------------------------------------------------ +void KCalcConstButton::setButtonNumber(int num) { + + button_num_ = num; +} + +//------------------------------------------------------------------------------ +// Name: setLabelAndTooltip +// Desc: sets both the label and the tooltip for the const button +//------------------------------------------------------------------------------ +void KCalcConstButton::setLabelAndTooltip() { + + QString new_label = QLatin1String("C") + QString::number(button_num_ + 1); + QString new_tooltip; + + new_label = (KCalcSettings::nameConstant(button_num_).isNull() ? new_label : KCalcSettings::nameConstant(button_num_)); + + new_tooltip = new_label + QLatin1Char('=') + KCalcSettings::valueConstant(button_num_); + + addMode(ModeNormal, new_label, new_tooltip); +} + +//------------------------------------------------------------------------------ +// Name: initPopupMenu +// Desc: initializes the const button popup +//------------------------------------------------------------------------------ +void KCalcConstButton::initPopupMenu() { + + QAction *const a = new QAction(this); + a->setText(i18n("Set Name")); + connect(a, SIGNAL(triggered()), this, SLOT(slotConfigureButton())); + addAction(a); + + KCalcConstMenu *const tmp_menu = new KCalcConstMenu(this); + tmp_menu->menuAction()->setText(i18n("Choose From List")); + addAction(tmp_menu->menuAction()); + setContextMenuPolicy(Qt::ActionsContextMenu); + + connect(tmp_menu, SIGNAL(triggeredConstant(science_constant)), SLOT(slotChooseScientificConst(science_constant))); + +} + +//------------------------------------------------------------------------------ +// Name: slotConfigureButton +// Desc: lets the user set the name for a constant +//------------------------------------------------------------------------------ +void KCalcConstButton::slotConfigureButton() { + + bool yes_no; + const QString input = KInputDialog::getText(i18n("New Name for Constant"), i18n("New name:"), text(), &yes_no, this); // "nameUserConstants-Dialog" + if (yes_no) { + KCalcSettings::setNameConstant(button_num_, input); + setLabelAndTooltip(); + } +} + +//------------------------------------------------------------------------------ +// Name: slotChooseScientificConst +// Desc: set the buttons's scientific constant +//------------------------------------------------------------------------------ +void KCalcConstButton::slotChooseScientificConst(const science_constant &const_chosen) { + + KCalcSettings::setValueConstant(button_num_, const_chosen.value); + KCalcSettings::setNameConstant(button_num_, const_chosen.label); + setLabelAndTooltip(); +} + +//------------------------------------------------------------------------------ +// Name: slotClicked +// Desc: constant button was clicked +//------------------------------------------------------------------------------ +void KCalcConstButton::slotClicked() { + + emit clicked(button_num_); +} + diff --git a/kcalc/kcalc_const_button.h b/kcalc/kcalc_const_button.h new file mode 100644 index 00000000..959b2b66 --- /dev/null +++ b/kcalc/kcalc_const_button.h @@ -0,0 +1,62 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 1996 - 2000 Bernd Johannes Wuebben + wuebben@kde.org + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KCALC_CONST_BUTTON_H_ +#define KCALC_CONST_BUTTON_H_ + +#include "kcalc_button.h" + +struct science_constant; + + +class KCalcConstButton : public KCalcButton { + Q_OBJECT + +public: + + explicit KCalcConstButton(QWidget *parent); + + KCalcConstButton(const QString &label, QWidget *parent, + const QString &tooltip = QString()); + + QString constant() const; + + void setButtonNumber(int num); + + void setLabelAndTooltip(); + +signals: + void clicked(int num); + +private slots: + void slotConfigureButton(); + void slotChooseScientificConst(const science_constant &const_chosen); + void slotClicked(); + +private: + void initPopupMenu(); + +private: + int button_num_; +}; + + +#endif diff --git a/kcalc/kcalc_const_menu.cpp b/kcalc/kcalc_const_menu.cpp new file mode 100644 index 00000000..52bd2c3f --- /dev/null +++ b/kcalc/kcalc_const_menu.cpp @@ -0,0 +1,150 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 2003 - 2005 Klaus Niederkrueger + kniederk@math.uni-koeln.de + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "kcalc_const_menu.h" + +#include +#include +#include +#include +#include + +namespace { + QList Constants; + + ConstantCategory stringToCategory(const QString &s) { + if (s == QLatin1String("mathematics")) { + return Mathematics; + } + + if (s == QLatin1String("electromagnetism")) { + return Electromagnetic; + } + + if (s == QLatin1String("nuclear")) { + return Nuclear; + } + + if (s == QLatin1String("thermodynamics")) { + return Thermodynamics; + } + + if (s == QLatin1String("gravitation")) { + return Gravitation; + } + + kDebug() << "Invalid Category For Constant: " << s; + return Mathematics; + } + +} + + +void KCalcConstMenu::init_consts() { + QDomDocument doc(QLatin1String("list_of_constants")); + QFile file(KGlobal::dirs()->findResource("appdata", QLatin1String("scienceconstants.xml"))); + + if (!file.open(QIODevice::ReadOnly)) { + kDebug() << "Did not find file \"scienceconstants.xml\". No constants will be available."; + return; + } + if (!doc.setContent(&file)) { + file.close(); + kDebug() << "The file \"scienceconstants.xml\" does not seem to be a valid description file. No constants will be available."; + return; + } + file.close(); + + // print out the element names of all elements that are direct children + // of the outermost element. + QDomElement docElem = doc.documentElement(); + + int i = 0; + QDomNode n = docElem.firstChild(); + while (!n.isNull()) { + QDomElement e = n.toElement(); // try to convert the node to an element. + if (!e.isNull() && e.tagName() == QLatin1String("constant")) { + science_constant tmp_const; + + tmp_const.name = I18N_NOOP(e.attributeNode(QLatin1String("name")).value()); + tmp_const.label = e.attributeNode(QLatin1String("symbol")).value(); + tmp_const.value = e.attributeNode(QLatin1String("value")).value(); + + QString tmp_str_category = e.attributeNode(QLatin1String("category")).value(); + + tmp_const.category = stringToCategory(tmp_str_category); + tmp_const.whatsthis = e.firstChildElement(QLatin1String("description")).text(); + + Constants.append(tmp_const); + } + n = n.nextSibling(); + i++; + } +} + +void KCalcConstMenu::init_all() +{ + QMenu *math_menu = addMenu(i18n("Mathematics")); + QMenu *em_menu = addMenu(i18n("Electromagnetism")); + QMenu *nuclear_menu = addMenu(i18n("Atomic && Nuclear")); + QMenu *thermo_menu = addMenu(i18n("Thermodynamics")); + QMenu *gravitation_menu = addMenu(i18n("Gravitation")); + + connect(this, SIGNAL(triggered(QAction*)), SLOT(slotPassSignalThrough(QAction*))); + + + for (int i = 0; i < Constants.size(); i++) { + QAction *tmp_action = new QAction(i18n(Constants.at(i).name.toAscii().data()), this); + tmp_action->setData(QVariant(i)); + if (Constants.at(i).category & Mathematics) + math_menu->addAction(tmp_action); + if (Constants.at(i).category & Electromagnetic) + em_menu->addAction(tmp_action); + if (Constants.at(i).category & Nuclear) + nuclear_menu->addAction(tmp_action); + if (Constants.at(i).category & Thermodynamics) + thermo_menu->addAction(tmp_action); + if (Constants.at(i).category & Gravitation) + gravitation_menu->addAction(tmp_action); + } +} + +void KCalcConstMenu::slotPassSignalThrough(QAction *chosen_const) +{ + bool tmp_bool; + int chosen_const_idx = (chosen_const->data()).toInt(& tmp_bool); + emit triggeredConstant(Constants.at(chosen_const_idx)); +} + +KCalcConstMenu::KCalcConstMenu(const QString &title, QWidget * parent) + : QMenu(title, parent) +{ + init_all(); +} + +KCalcConstMenu::KCalcConstMenu(QWidget * parent) + : QMenu(parent) +{ + init_all(); +} + + +#include "kcalc_const_menu.moc" diff --git a/kcalc/kcalc_const_menu.h b/kcalc/kcalc_const_menu.h new file mode 100644 index 00000000..b7f8c010 --- /dev/null +++ b/kcalc/kcalc_const_menu.h @@ -0,0 +1,65 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 2003 - 2005 Klaus Niederkrueger + kniederk@math.uni-koeln.de + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KCALC_CONST_MENU_H_ +#define KCALC_CONST_MENU_H_ + +#include +#include + +enum ConstantCategory { + Mathematics = 1, + Electromagnetic = 2, + Nuclear = 4, + Thermodynamics = 8, + Gravitation = 16 +}; + +struct science_constant { + QString label; + QString name; + QString whatsthis; + QString value; + ConstantCategory category; +}; + +class KCalcConstMenu : public QMenu { + Q_OBJECT + +public: + explicit KCalcConstMenu(QWidget * parent = 0); + explicit KCalcConstMenu(const QString &title, QWidget * parent = 0); + +public: + static void init_consts(); + +signals: + void triggeredConstant(const science_constant &); + + +private: + void init_all(); + +public slots: + void slotPassSignalThrough(QAction *chosen_const); +}; + +#endif diff --git a/kcalc/kcalc_core.cpp b/kcalc/kcalc_core.cpp new file mode 100644 index 00000000..e7faed6a --- /dev/null +++ b/kcalc/kcalc_core.cpp @@ -0,0 +1,886 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 2003 - 2005 Klaus Niederkrueger + kniederk@math.uni-koeln.de + +Copyright (C) 1996 - 2000 Bernd Johannes Wuebben + wuebben@kde.org + +Copyright (C) 1995 Martin Bartlett + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "kcalc_core.h" +#include +#include +#include + +namespace { + +KNumber Deg2Rad(const KNumber &x) { + return x * (KNumber::Pi() / KNumber(180)); +} + +KNumber Gra2Rad(const KNumber &x) { + return x * (KNumber::Pi() / KNumber(200)); +} + +KNumber Rad2Deg(const KNumber &x) { + return x * (KNumber(180) / KNumber::Pi()); +} + +KNumber Rad2Gra(const KNumber &x) { + return x * (KNumber(200) / KNumber::Pi()); +} + +bool error_; + +KNumber ExecOr(const KNumber &left_op, const KNumber &right_op) { + return left_op | right_op; +} + +KNumber ExecXor(const KNumber &left_op, const KNumber &right_op) { + return left_op ^ right_op; +} + +KNumber ExecAnd(const KNumber &left_op, const KNumber &right_op) { + return left_op & right_op; +} + +KNumber ExecLsh(const KNumber &left_op, const KNumber &right_op) { + return left_op << right_op; +} + +KNumber ExecRsh(const KNumber &left_op, const KNumber &right_op) { + return left_op >> right_op; +} + +KNumber ExecAdd(const KNumber &left_op, const KNumber &right_op) { + return left_op + right_op; +} + +KNumber ExecSubtract(const KNumber &left_op, const KNumber &right_op) { + return left_op - right_op; +} + +KNumber ExecMultiply(const KNumber &left_op, const KNumber &right_op) { + return left_op * right_op; +} + +KNumber ExecDivide(const KNumber &left_op, const KNumber &right_op) { + return left_op / right_op; +} + +KNumber ExecMod(const KNumber &left_op, const KNumber &right_op) { + return left_op % right_op; +} + +KNumber ExecIntDiv(const KNumber &left_op, const KNumber &right_op) { + return (left_op / right_op).integerPart(); +} + +KNumber ExecBinom(const KNumber &left_op, const KNumber &right_op) { + return left_op.bin(right_op); +} + +KNumber ExecPower(const KNumber &left_op, const KNumber &right_op) { + return left_op.pow(right_op); +} + +KNumber ExecPwrRoot(const KNumber &left_op, const KNumber &right_op) { + return left_op.pow(KNumber::One / right_op); +} + +KNumber ExecAddP(const KNumber &left_op, const KNumber &right_op) { + return left_op * (KNumber::One + right_op / KNumber(100)); +} + +KNumber ExecSubP(const KNumber &left_op, const KNumber &right_op) { + return left_op * (KNumber::One - right_op / KNumber(100)); +} + +KNumber ExecMultiplyP(const KNumber &left_op, const KNumber &right_op) { + return left_op * right_op / KNumber(100); +} + +KNumber ExecDivideP(const KNumber &left_op, const KNumber &right_op) { + return left_op * KNumber(100) / right_op; +} + +// move a number into the interval [0,360) by adding multiples of 360 +KNumber moveIntoDegInterval(const KNumber &num) { + KNumber tmp_num = num - (num / KNumber(360)).integerPart() * KNumber(360); + if (tmp_num < KNumber::Zero) + return tmp_num + KNumber(360); + return tmp_num; +} + +// move a number into the interval [0,400) by adding multiples of 400 +KNumber moveIntoGradInterval(const KNumber &num) { + KNumber tmp_num = num - (num / KNumber(400)).integerPart() * KNumber(400); + if (tmp_num < KNumber::Zero) + return tmp_num + KNumber(400); + return tmp_num; +} + +typedef KNumber(*Arith)(const KNumber &, const KNumber &); +typedef KNumber(*Prcnt)(const KNumber &, const KNumber &); + +struct operator_data { + int precedence; // priority of operators in " enum Operation" + Arith arith_ptr; + Prcnt prcnt_ptr; +}; + +// build precedence list +const struct operator_data Operator[] = { + { 0, NULL, NULL}, // FUNC_EQUAL + { 0, NULL, NULL}, // FUNC_PERCENT + { 0, NULL, NULL}, // FUNC_BRACKET + { 1, ExecOr, NULL}, // FUNC_OR + { 2, ExecXor, NULL}, // FUNC_XOR + { 3, ExecAnd, NULL}, // FUNC_AND + { 4, ExecLsh, NULL}, // FUNC_LSH + { 4, ExecRsh, NULL}, // FUNC_RSH + { 5, ExecAdd, ExecAddP}, // FUNC_ADD + { 5, ExecSubtract, ExecSubP}, // FUNC_SUBTRACT + { 6, ExecMultiply, ExecMultiplyP}, // FUNC_MULTIPLY + { 6, ExecDivide, ExecDivideP}, // FUNC_DIVIDE + { 6, ExecMod, NULL}, // FUNC_MOD + { 6, ExecIntDiv, NULL}, // FUNC_INTDIV + { 7, ExecBinom, NULL}, // FUNC_BINOM + { 7, ExecPower, NULL}, // FUNC_POWER + { 7, ExecPwrRoot, NULL} // FUNC_PWR_ROOT +}; + +} + + +CalcEngine::CalcEngine() : percent_mode_(false) { + + last_number_ = KNumber::Zero; + error_ = false; +} + +KNumber CalcEngine::lastOutput(bool &error) const { + error = error_; + return last_number_; +} + +void CalcEngine::ArcCosDeg(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR || input < -KNumber::One || input > KNumber::One) { + last_number_ = KNumber::NaN; + return; + } + + if (input.type() == KNumber::TYPE_INTEGER) { + if (input == KNumber::One) { + last_number_ = KNumber::Zero; + return; + } + if (input == - KNumber::One) { + last_number_ = KNumber(180); + return; + } + if (input == KNumber::Zero) { + last_number_ = KNumber(90); + return; + } + } + last_number_ = Rad2Deg(input.acos()); +} + +void CalcEngine::ArcCosRad(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR || input < -KNumber::One || input > KNumber::One) { + last_number_ = KNumber::NaN; + return; + } + last_number_ = input.acos(); +} + +void CalcEngine::ArcCosGrad(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR || input < -KNumber::One || input > KNumber::One) { + last_number_ = KNumber::NaN; + return; + } + if (input.type() == KNumber::TYPE_INTEGER) { + if (input == KNumber::One) { + last_number_ = KNumber::Zero; + return; + } + if (input == - KNumber::One) { + last_number_ = KNumber(200); + return; + } + if (input == KNumber::Zero) { + last_number_ = KNumber(100); + return; + } + } + last_number_ = Rad2Gra(input.acos()); +} + +void CalcEngine::ArcSinDeg(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR || + input < -KNumber::One || input > KNumber::One) { + last_number_ = KNumber::NaN; + return; + } + if (input.type() == KNumber::TYPE_INTEGER) { + if (input == KNumber::One) { + last_number_ = KNumber(90); + return; + } + if (input == - KNumber::One) { + last_number_ = KNumber(-90); + return; + } + if (input == KNumber::Zero) { + last_number_ = KNumber::Zero; + return; + } + } + last_number_ = Rad2Deg(input.asin()); +} + +void CalcEngine::ArcSinRad(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR || + input < -KNumber::One || input > KNumber::One) { + last_number_ = KNumber::NaN; + return; + } + last_number_ = input.asin(); +} + +void CalcEngine::ArcSinGrad(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR || + input < -KNumber::One || input > KNumber::One) { + last_number_ = KNumber::NaN; + return; + } + if (input.type() == KNumber::TYPE_INTEGER) { + if (input == KNumber::One) { + last_number_ = KNumber(100); + return; + } + if (input == - KNumber::One) { + last_number_ = KNumber(-100); + return; + } + if (input == KNumber::Zero) { + last_number_ = KNumber::Zero; + return; + } + } + last_number_ = Rad2Gra(input.asin()); +} + +void CalcEngine::ArcTangensDeg(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + if (input == KNumber::NaN) last_number_ = KNumber::NaN; + if (input == KNumber::PosInfinity) last_number_ = KNumber(90); + if (input == KNumber::NegInfinity) last_number_ = KNumber(-90); + return; + } + + last_number_ = Rad2Deg(input.atan()); +} + +void CalcEngine::ArcTangensRad(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + if (input == KNumber::NaN) last_number_ = KNumber::NaN; + if (input == KNumber::PosInfinity) + last_number_ = KNumber::Pi() / KNumber(2); + if (input == KNumber::NegInfinity) + last_number_ = -KNumber::Pi() / KNumber(2); + return; + } + + last_number_ = input.atan(); +} + +void CalcEngine::ArcTangensGrad(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + if (input == KNumber::NaN) last_number_ = KNumber::NaN; + if (input == KNumber::PosInfinity) last_number_ = KNumber(100); + if (input == KNumber::NegInfinity) last_number_ = KNumber(-100); + return; + } + + last_number_ = Rad2Gra(input.atan()); +} + +void CalcEngine::AreaCosHyp(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + if (input == KNumber::NaN) last_number_ = KNumber::NaN; + if (input == KNumber::PosInfinity) last_number_ = KNumber::PosInfinity; + if (input == KNumber::NegInfinity) last_number_ = KNumber::NaN; + return; + } + + if (input < KNumber::One) { + last_number_ = KNumber::NaN; + return; + } + if (input == KNumber::One) { + last_number_ = KNumber::Zero; + return; + } + last_number_ = input.acosh(); +} + +void CalcEngine::AreaSinHyp(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + if (input == KNumber::NaN) last_number_ = KNumber::NaN; + if (input == KNumber::PosInfinity) last_number_ = KNumber::PosInfinity; + if (input == KNumber::NegInfinity) last_number_ = KNumber::NegInfinity; + return; + } + + if (input == KNumber::Zero) { + last_number_ = KNumber::Zero; + return; + } + last_number_ = input.asinh(); +} + +void CalcEngine::AreaTangensHyp(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + last_number_ = KNumber::NaN; + return; + } + + if (input < -KNumber::One || input > KNumber::One) { + last_number_ = KNumber::NaN; + return; + } + if (input == KNumber::One) { + last_number_ = KNumber::PosInfinity; + return; + } + if (input == - KNumber::One) { + last_number_ = KNumber::NegInfinity; + return; + } + last_number_ = input.atanh(); +} + +void CalcEngine::Complement(const KNumber &input) +{ + if (input.type() != KNumber::TYPE_INTEGER) { + last_number_ = KNumber::NaN; + return; + } + + last_number_ = ~input; +} + +void CalcEngine::CosDeg(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + last_number_ = KNumber::NaN; + return; + } + + KNumber trunc_input = moveIntoDegInterval(input); + + if (trunc_input.type() == KNumber::TYPE_INTEGER) { + KNumber mult = trunc_input / KNumber(90); + if (mult.type() == KNumber::TYPE_INTEGER) { + if (mult == KNumber::Zero) + last_number_ = KNumber::One; + else if (mult == KNumber::One) + last_number_ = KNumber::Zero; + else if (mult == KNumber(2)) + last_number_ = KNumber::NegOne; + else if (mult == KNumber(3)) + last_number_ = KNumber::Zero; + else kDebug() << "Something wrong in CalcEngine::CosDeg"; + return; + } + } + + trunc_input = Deg2Rad(trunc_input); + last_number_ = trunc_input.cos(); +} + +void CalcEngine::CosRad(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + last_number_ = KNumber::NaN; + return; + } + + last_number_ = input.cos(); +} + +void CalcEngine::CosGrad(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + last_number_ = KNumber::NaN; + return; + } + KNumber trunc_input = moveIntoGradInterval(input); + if (trunc_input.type() == KNumber::TYPE_INTEGER) { + KNumber mult = trunc_input / KNumber(100); + if (mult.type() == KNumber::TYPE_INTEGER) { + if (mult == KNumber::Zero) + last_number_ = KNumber::One; + else if (mult == KNumber::One) + last_number_ = KNumber::Zero; + else if (mult == KNumber(2)) + last_number_ = KNumber::NegOne; + else if (mult == KNumber(3)) + last_number_ = KNumber::Zero; + else kDebug() << "Something wrong in CalcEngine::CosGrad"; + return; + } + } + trunc_input = Gra2Rad(trunc_input); + + last_number_ = trunc_input.cos(); +} + +void CalcEngine::CosHyp(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + if (input == KNumber::NaN) last_number_ = KNumber::NaN; + if (input == KNumber::PosInfinity) last_number_ = KNumber::PosInfinity; + // YES, this should be *positive* infinity. We mimic the behavior of + // libc which says the following for cosh + // + // "If x is positive infinity or negative infinity, positive infinity is returned." + if (input == KNumber::NegInfinity) last_number_ = KNumber::PosInfinity; + return; + } + + last_number_ = input.cosh(); +} + +void CalcEngine::Cube(const KNumber &input) +{ + last_number_ = input * input * input; +} + +void CalcEngine::CubeRoot(const KNumber &input) +{ + last_number_ = input.cbrt(); +} + +void CalcEngine::Exp(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + if (input == KNumber::NaN) last_number_ = KNumber::NaN; + if (input == KNumber::PosInfinity) last_number_ = KNumber::PosInfinity; + if (input == KNumber::NegInfinity) last_number_ = KNumber::Zero; + return; + } + last_number_ = KNumber::Euler().pow(input); +} + +void CalcEngine::Exp10(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + if (input == KNumber::NaN) last_number_ = KNumber::NaN; + if (input == KNumber::PosInfinity) last_number_ = KNumber::PosInfinity; + if (input == KNumber::NegInfinity) last_number_ = KNumber::Zero; + return; + } + last_number_ = KNumber(10).pow(input); +} + + +void CalcEngine::Factorial(const KNumber &input) +{ + if (input == KNumber::PosInfinity) return; + if (input < KNumber::Zero || input.type() == KNumber::TYPE_ERROR) { + error_ = true; + last_number_ = KNumber::NaN; + return; + } + + last_number_ = input.integerPart().factorial(); +} + +void CalcEngine::Gamma(const KNumber &input) +{ + if (input == KNumber::PosInfinity) return; + if (input < KNumber::Zero || input.type() == KNumber::TYPE_ERROR) { + error_ = true; + last_number_ = KNumber::NaN; + return; + } + + last_number_ = input.tgamma(); +} + +void CalcEngine::InvertSign(const KNumber &input) +{ + last_number_ = -input; +} + +void CalcEngine::Ln(const KNumber &input) +{ + if (input < KNumber::Zero) + last_number_ = KNumber::NaN; + else if (input == KNumber::Zero) + last_number_ = KNumber::NegInfinity; + else if (input == KNumber::One) + last_number_ = KNumber::Zero; + else { + last_number_ = input.ln(); + } +} + +void CalcEngine::Log10(const KNumber &input) +{ + if (input < KNumber::Zero) + last_number_ = KNumber::NaN; + else if (input == KNumber::Zero) + last_number_ = KNumber::NegInfinity; + else if (input == KNumber::One) + last_number_ = KNumber::Zero; + else { + last_number_ = input.log10(); + } +} + +void CalcEngine::ParenClose(KNumber input) +{ + // evaluate stack until corresponding opening bracket + while (!stack_.isEmpty()) { + Node tmp_node = stack_.pop(); + if (tmp_node.operation == FUNC_BRACKET) + break; + input = evalOperation(tmp_node.number, tmp_node.operation, input); + } + last_number_ = input; + return; +} + +void CalcEngine::ParenOpen(const KNumber &input) +{ + enterOperation(input, FUNC_BRACKET); +} + +void CalcEngine::Reciprocal(const KNumber &input) +{ + last_number_ = KNumber::One / input; +} + + +void CalcEngine::SinDeg(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + last_number_ = KNumber::NaN; + return; + } + + KNumber trunc_input = moveIntoDegInterval(input); + if (trunc_input.type() == KNumber::TYPE_INTEGER) { + KNumber mult = trunc_input / KNumber(90); + if (mult.type() == KNumber::TYPE_INTEGER) { + if (mult == KNumber::Zero) + last_number_ = KNumber::Zero; + else if (mult == KNumber::One) + last_number_ = KNumber::One; + else if (mult == KNumber(2)) + last_number_ = KNumber::Zero; + else if (mult == KNumber(3)) + last_number_ = KNumber::NegOne; + else kDebug() << "Something wrong in CalcEngine::SinDeg"; + return; + } + } + trunc_input = Deg2Rad(trunc_input); + + last_number_ = trunc_input.sin(); +} + +void CalcEngine::SinRad(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + last_number_ = KNumber::NaN; + return; + } + + last_number_ = input.sin(); +} + +void CalcEngine::SinGrad(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + last_number_ = KNumber::NaN; + return; + } + + KNumber trunc_input = moveIntoGradInterval(input); + if (trunc_input.type() == KNumber::TYPE_INTEGER) { + KNumber mult = trunc_input / KNumber(100); + if (mult.type() == KNumber::TYPE_INTEGER) { + if (mult == KNumber::Zero) + last_number_ = KNumber::Zero; + else if (mult == KNumber::One) + last_number_ = KNumber::One; + else if (mult == KNumber(2)) + last_number_ = KNumber::Zero; + else if (mult == KNumber(3)) + last_number_ = KNumber::NegOne; + else kDebug() << "Something wrong in CalcEngine::SinGrad"; + return; + } + } + + trunc_input = Gra2Rad(trunc_input); + + last_number_ = trunc_input.sin(); +} + +void CalcEngine::SinHyp(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + if (input == KNumber::NaN) last_number_ = KNumber::NaN; + if (input == KNumber::PosInfinity) last_number_ = KNumber::PosInfinity; + if (input == KNumber::NegInfinity) last_number_ = KNumber::NegInfinity; + return; + } + + last_number_ = input.sinh(); +} + +void CalcEngine::Square(const KNumber &input) +{ + last_number_ = input * input; +} + +void CalcEngine::SquareRoot(const KNumber &input) +{ + last_number_ = input.sqrt(); +} + +void CalcEngine::StatClearAll(const KNumber &input) +{ + Q_UNUSED(input); + stats.clearAll(); +} + +void CalcEngine::StatCount(const KNumber &input) +{ + Q_UNUSED(input); + last_number_ = KNumber(stats.count()); +} + +void CalcEngine::StatDataNew(const KNumber &input) +{ + stats.enterData(input); + last_number_ = KNumber(stats.count()); +} + +void CalcEngine::StatDataDel(const KNumber &input) +{ + Q_UNUSED(input); + stats.clearLast(); + last_number_ = KNumber(stats.count()); +} + +void CalcEngine::StatMean(const KNumber &input) +{ + Q_UNUSED(input); + last_number_ = stats.mean(); + + error_ = stats.error(); +} + +void CalcEngine::StatMedian(const KNumber &input) +{ + Q_UNUSED(input); + last_number_ = stats.median(); + + error_ = stats.error(); +} + +void CalcEngine::StatStdDeviation(const KNumber &input) +{ + Q_UNUSED(input); + last_number_ = stats.std(); + + error_ = stats.error(); +} + +void CalcEngine::StatStdSample(const KNumber &input) +{ + Q_UNUSED(input); + last_number_ = stats.sample_std(); + + error_ = stats.error(); +} + +void CalcEngine::StatSum(const KNumber &input) +{ + Q_UNUSED(input); + last_number_ = stats.sum(); +} + +void CalcEngine::StatSumSquares(const KNumber &input) +{ + Q_UNUSED(input); + last_number_ = stats.sum_of_squares(); + + error_ = stats.error(); +} + +void CalcEngine::TangensDeg(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + last_number_ = KNumber::NaN; + return; + } + + SinDeg(input); + KNumber arg1 = last_number_; + CosDeg(input); + KNumber arg2 = last_number_; + + last_number_ = arg1 / arg2; +} + +void CalcEngine::TangensRad(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + last_number_ = KNumber::NaN; + return; + } + + SinRad(input); + KNumber arg1 = last_number_; + CosRad(input); + KNumber arg2 = last_number_; + + last_number_ = arg1 / arg2; +} + +void CalcEngine::TangensGrad(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + last_number_ = KNumber::NaN; + return; + } + + SinGrad(input); + KNumber arg1 = last_number_; + CosGrad(input); + KNumber arg2 = last_number_; + + last_number_ = arg1 / arg2; +} + +void CalcEngine::TangensHyp(const KNumber &input) +{ + if (input.type() == KNumber::TYPE_ERROR) { + if (input == KNumber::NaN) last_number_ = KNumber::NaN; + if (input == KNumber::PosInfinity) last_number_ = KNumber::One; + if (input == KNumber::NegInfinity) last_number_ = KNumber::NegOne; + return; + } + + last_number_ = input.tanh(); +} + +KNumber CalcEngine::evalOperation(const KNumber &arg1, Operation operation, const KNumber &arg2) +{ + if (!percent_mode_ || Operator[operation].prcnt_ptr == NULL) { + return (Operator[operation].arith_ptr)(arg1, arg2); + } else { + percent_mode_ = false; + return (Operator[operation].prcnt_ptr)(arg1, arg2); + } +} + +void CalcEngine::enterOperation(const KNumber &number, Operation func) +{ + Node tmp_node; + + if (func == FUNC_BRACKET) { + tmp_node.number = KNumber::Zero; + tmp_node.operation = FUNC_BRACKET; + + stack_.push(tmp_node); + + return; + } + + if (func == FUNC_PERCENT) { + percent_mode_ = true; + } + + tmp_node.number = number; + tmp_node.operation = func; + + stack_.push(tmp_node); + + evalStack(); +} + +bool CalcEngine::evalStack() +{ + // this should never happen + Q_ASSERT(!stack_.isEmpty()); + + Node tmp_node = stack_.pop(); + + while (! stack_.isEmpty()) { + Node tmp_node2 = stack_.pop(); + if (Operator[tmp_node.operation].precedence <= + Operator[tmp_node2.operation].precedence) { + if (tmp_node2.operation == FUNC_BRACKET) continue; + const KNumber tmp_result = evalOperation(tmp_node2.number, tmp_node2.operation, tmp_node.number); + tmp_node.number = tmp_result; + } else { + stack_.push(tmp_node2); + break; + } + + } + + if (tmp_node.operation != FUNC_EQUAL && tmp_node.operation != FUNC_PERCENT) + stack_.push(tmp_node); + + last_number_ = tmp_node.number; + return true; +} + +void CalcEngine::Reset() +{ + error_ = false; + last_number_ = KNumber::Zero; + + stack_.clear(); +} + + diff --git a/kcalc/kcalc_core.h b/kcalc/kcalc_core.h new file mode 100644 index 00000000..a5947ced --- /dev/null +++ b/kcalc/kcalc_core.h @@ -0,0 +1,149 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 1996 - 2000 Bernd Johannes Wuebben + wuebben@kde.org + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KCALC_CORE_H_ +#define KCALC_CORE_H_ + +#include +#include "stats.h" +#include "knumber.h" + +class CalcEngine { +public: + // operations that can be stored in calculation stack + enum Operation { + FUNC_EQUAL, + FUNC_PERCENT, + FUNC_BRACKET, + FUNC_OR, + FUNC_XOR, + FUNC_AND, + FUNC_LSH, + FUNC_RSH, + FUNC_ADD, + FUNC_SUBTRACT, + FUNC_MULTIPLY, + FUNC_DIVIDE, + FUNC_MOD, + FUNC_INTDIV, + FUNC_BINOM, + FUNC_POWER, + FUNC_PWR_ROOT + }; + + CalcEngine(); + + KNumber lastOutput(bool &error) const; + + void enterOperation(const KNumber &num, Operation func); + + void ArcCosDeg(const KNumber &input); + void ArcCosRad(const KNumber &input); + void ArcCosGrad(const KNumber &input); + void ArcSinDeg(const KNumber &input); + void ArcSinRad(const KNumber &input); + void ArcSinGrad(const KNumber &input); + void ArcTangensDeg(const KNumber &input); + void ArcTangensRad(const KNumber &input); + void ArcTangensGrad(const KNumber &input); + void AreaCosHyp(const KNumber &input); + void AreaSinHyp(const KNumber &input); + void AreaTangensHyp(const KNumber &input); + void Complement(const KNumber &input); + void CosDeg(const KNumber &input); + void CosRad(const KNumber &input); + void CosGrad(const KNumber &input); + void CosHyp(const KNumber &input); + void Cube(const KNumber &input); + void CubeRoot(const KNumber &input); + void Exp(const KNumber &input); + void Exp10(const KNumber &input); + void Factorial(const KNumber &input); + void Gamma(const KNumber &input); + void InvertSign(const KNumber &input); + void Ln(const KNumber &input); + void Log10(const KNumber &input); + void ParenClose(KNumber input); + void ParenOpen(const KNumber &input); + void Reciprocal(const KNumber &input); + void SinDeg(const KNumber &input); + void SinGrad(const KNumber &input); + void SinRad(const KNumber &input); + void SinHyp(const KNumber &input); + void Square(const KNumber &input); + void SquareRoot(const KNumber &input); + void StatClearAll(const KNumber &input); + void StatCount(const KNumber &input); + void StatDataNew(const KNumber &input); + void StatDataDel(const KNumber &input); + void StatMean(const KNumber &input); + void StatMedian(const KNumber &input); + void StatStdDeviation(const KNumber &input); + void StatStdSample(const KNumber &input); + void StatSum(const KNumber &input); + void StatSumSquares(const KNumber &input); + void TangensDeg(const KNumber &input); + void TangensRad(const KNumber &input); + void TangensGrad(const KNumber &input); + void TangensHyp(const KNumber &input); + + void Reset(); + +private: + KStats stats; + + struct Node { + KNumber number; + Operation operation; + }; + + // Stack holds all operations and numbers that have not yet been + // processed, e.g. user types "2+3*", the calculation can not be + // executed, because "*" has a higher precedence than "+", so we + // need to wait for the next number. + // + // In the stack this would be stored as ((2,+),(3,*),...) + // + // "enterOperation": If the introduced Operation has lower priority + // than the preceding operations in the stack, then we can start to + // evaluate the stack (with "evalStack"). Otherwise we append the new + // Operation and number to the stack. + // + // E.g. "2*3+" evaluates to "6+", but "2+3*" can not be evaluated + // yet. + // + // We also take care of brackets, by writing a marker "FUNC_BRACKET" + // into the stack, each time the user opens one. When a bracket is + // closed, everything in the stack is evaluated until the first + // marker "FUNC_BRACKET" found. + QStack stack_; + + KNumber last_number_; + + bool percent_mode_; + + bool evalStack(); + + KNumber evalOperation(const KNumber &arg1, Operation operation, const KNumber &arg2); +}; + + +#endif diff --git a/kcalc/kcalc_settings.kcfgc b/kcalc/kcalc_settings.kcfgc new file mode 100644 index 00000000..5b131600 --- /dev/null +++ b/kcalc/kcalc_settings.kcfgc @@ -0,0 +1,6 @@ +# Code generation options for kconfig_compiler +File=kcalc.kcfg +ClassName=KCalcSettings +Singleton=true +Mutators=true +UseEnumTypes=true diff --git a/kcalc/kcalcdisplay.cpp b/kcalc/kcalcdisplay.cpp new file mode 100644 index 00000000..140be5d2 --- /dev/null +++ b/kcalc/kcalcdisplay.cpp @@ -0,0 +1,968 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 1996 - 2000 Bernd Johannes Wuebben + wuebben@kde.org + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "kcalcdisplay.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "kcalc_core.h" +#include "kcalc_settings.h" + +#include "kcalcdisplay.moc" + +//------------------------------------------------------------------------------ +// Name: KCalcDisplay +// Desc: constructor +//------------------------------------------------------------------------------ +KCalcDisplay::KCalcDisplay(QWidget *parent) : QFrame(parent), beep_(false), + groupdigits_(true), twoscomplement_(true), button_(0), lit_(false), + num_base_(NB_DECIMAL), precision_(9), fixed_precision_(-1), display_amount_(0), + history_index_(0), selection_timer_(new QTimer(this)) { + + setFocusPolicy(Qt::StrongFocus); + + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + + setBackgroundRole(QPalette::Base); + setForegroundRole(QPalette::Text); + setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); // set in kalc.ui + + KNumber::setDefaultFloatOutput(true); + KNumber::setDefaultFractionalInput(true); + + connect(this, SIGNAL(clicked()), this, SLOT(slotDisplaySelected())); + connect(selection_timer_, SIGNAL(timeout()), this, SLOT(slotSelectionTimedOut())); + + sendEvent(EventReset); +} + +//------------------------------------------------------------------------------ +// Name: ~KCalcDisplay +// Desc: destructor +//------------------------------------------------------------------------------ +KCalcDisplay::~KCalcDisplay() { +} + +//------------------------------------------------------------------------------ +// Name: changeSettings +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::changeSettings() { + QPalette pal = palette(); + + pal.setColor(QPalette::Text, KCalcSettings::foreColor()); + pal.setColor(QPalette::Base, KCalcSettings::backColor()); + + setPalette(pal); + + setFont(KCalcSettings::displayFont()); + + setPrecision(KCalcSettings::precision()); + + if (!KCalcSettings::fixed()) { + setFixedPrecision(-1); + } else { + setFixedPrecision(KCalcSettings::fixedPrecision()); + } + + setBeep(KCalcSettings::beep()); + setGroupDigits(KCalcSettings::groupDigits()); + setTwosComplement(KCalcSettings::twosComplement()); + setBinaryGrouping(KCalcSettings::binaryGrouping()); + setOctalGrouping(KCalcSettings::octalGrouping()); + setHexadecimalGrouping(KCalcSettings::hexadecimalGrouping()); + updateDisplay(); +} + +//------------------------------------------------------------------------------ +// Name: +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::updateFromCore(const CalcEngine &core, bool store_result_in_history) { + + bool tmp_error; + const KNumber &output = core.lastOutput(tmp_error); + +#if 0 + // TODO: do we really need explicit error tracking? + // isn't the type of the KNumber good enough? + // I think it is and that this error tracking is cruft + // left over from a LONG time ago... + if(output.type() == KNumber::TYPE_ERROR) { +#else + if (tmp_error) { +#endif + sendEvent(EventError); + } + + if (setAmount(output) && store_result_in_history && (output != KNumber::Zero)) { + // add this latest value to our history + history_list_.insert(history_list_.begin(), output); + history_index_ = 0; + } +} + +//------------------------------------------------------------------------------ +// Name: enterDigit +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::enterDigit(int data) { + + switch (data) { + case 0: newCharacter(QLatin1Char('0')); break; + case 1: newCharacter(QLatin1Char('1')); break; + case 2: newCharacter(QLatin1Char('2')); break; + case 3: newCharacter(QLatin1Char('3')); break; + case 4: newCharacter(QLatin1Char('4')); break; + case 5: newCharacter(QLatin1Char('5')); break; + case 6: newCharacter(QLatin1Char('6')); break; + case 7: newCharacter(QLatin1Char('7')); break; + case 8: newCharacter(QLatin1Char('8')); break; + case 9: newCharacter(QLatin1Char('9')); break; + case 0xa: newCharacter(QLatin1Char('A')); break; + case 0xb: newCharacter(QLatin1Char('B')); break; + case 0xc: newCharacter(QLatin1Char('C')); break; + case 0xd: newCharacter(QLatin1Char('D')); break; + case 0xe: newCharacter(QLatin1Char('E')); break; + case 0xf: newCharacter(QLatin1Char('F')); break; + default: + Q_ASSERT(0); + break; + } +} + +//------------------------------------------------------------------------------ +// Name: slotHistoryForward +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::slotHistoryForward() { + + if (history_list_.empty()) { + return; + } + + if (history_index_ <= 0) { + return; + } + + history_index_--; + setAmount(history_list_[history_index_]); +} + +//------------------------------------------------------------------------------ +// Name: slotHistoryBack +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::slotHistoryBack() { + + if (history_list_.empty()) { + return; + } + + if (history_index_ >= history_list_.size()) { + return; + } + + setAmount(history_list_[history_index_]); + history_index_++; +} + +//------------------------------------------------------------------------------ +// Name: sendEvent +// Desc: +//------------------------------------------------------------------------------ +bool KCalcDisplay::sendEvent(Event event) { + + switch (event) { + case EventClear: + case EventReset: + display_amount_ = KNumber::Zero; + str_int_ = QLatin1String("0"); + str_int_exp_.clear(); + + eestate_ = false; + period_ = false; + neg_sign_ = false; + + updateDisplay(); + + return true; + + case EventChangeSign: + return changeSign(); + + case EventError: + updateDisplay(); + return true; + + default: + return false; + } +} + +//------------------------------------------------------------------------------ +// Name: slotCut +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::slotCut() { + + slotCopy(); + sendEvent(EventReset); +} + +//------------------------------------------------------------------------------ +// Name: slotCopy +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::slotCopy() { + + QString txt = text_; + + switch(num_base_) { + case NB_HEX: + txt.prepend(QLatin1String("0x")); + txt.remove(QLatin1Char(' ')); + break; + case NB_BINARY: + txt.prepend(QLatin1String("0b")); + txt.remove(QLatin1Char(' ')); + break; + case NB_OCTAL: + txt.prepend(QLatin1String("0")); + txt.remove(QLatin1Char(' ')); + break; + case NB_DECIMAL: + break; + } + + (QApplication::clipboard())->setText(txt, QClipboard::Clipboard); + (QApplication::clipboard())->setText(txt, QClipboard::Selection); +} + +//------------------------------------------------------------------------------ +// Name: slotPaste +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::slotPaste(bool bClipboard) { + + QString tmp_str = (QApplication::clipboard())->text(bClipboard ? QClipboard::Clipboard : QClipboard::Selection); + + if (tmp_str.isNull()) { + if (beep_) { + KNotification::beep(); + } + return; + } + + NumBase tmp_num_base = num_base_; + + // fix up string + tmp_str = tmp_str.trimmed(); + + if (groupdigits_) { + tmp_str.remove(KGlobal::locale()->thousandsSeparator()); + } + + tmp_str = tmp_str.toLower(); + + // determine base + if (tmp_str.startsWith(QLatin1String("0x"))) { + tmp_num_base = NB_HEX; + tmp_str.remove(0, 2); + } else if (tmp_str.startsWith(QLatin1String("0b"))) { + tmp_num_base = NB_BINARY; + tmp_str.remove(0, 2); + } else if (tmp_str.startsWith(QLatin1String("0"))) { + // we don't want this to trigger on "0.xxxxxx" cases + if(tmp_str.length() < 2 || QString(tmp_str[1]) != KNumber::decimalSeparator()) { + tmp_num_base = NB_OCTAL; + tmp_str.remove(0, 1); + } + } + + if (tmp_num_base != NB_DECIMAL) { + bool was_ok; + const qint64 tmp_result = tmp_str.toULongLong(&was_ok, tmp_num_base); + + if (!was_ok) { + setAmount(KNumber::NaN); + if (beep_) { + KNotification::beep(); + } + return; + } + setAmount(KNumber(tmp_result)); + } else { + setAmount(KNumber(tmp_str)); + if (beep_ && display_amount_ == KNumber::NaN) { + KNotification::beep(); + } + } +} + +//------------------------------------------------------------------------------ +// Name: slotDisplaySelected +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::slotDisplaySelected() { + + if (button_ == Qt::LeftButton) { + if (lit_) { + slotCopy(); + selection_timer_->start(100); + } else { + selection_timer_->stop(); + } + + invertColors(); + } else { + slotPaste(false); // Selection + } +} + +//------------------------------------------------------------------------------ +// Name: slotSelectionTimedOut +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::slotSelectionTimedOut() { + + lit_ = false; + invertColors(); + selection_timer_->stop(); +} + +//------------------------------------------------------------------------------ +// Name: invertColors +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::invertColors() { + + QPalette tmp_palette = palette(); + tmp_palette.setColor(QPalette::Base, palette().color(QPalette::Text)); + tmp_palette.setColor(QPalette::Text, palette().color(QPalette::Base)); + setPalette(tmp_palette); +} + +//------------------------------------------------------------------------------ +// Name: mousePressEvent +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::mousePressEvent(QMouseEvent *e) { + + if (e->button() == Qt::LeftButton) { + lit_ = !lit_; + button_ = Qt::LeftButton; + } else { + button_ = Qt::MidButton; + } + + emit clicked(); +} + +//------------------------------------------------------------------------------ +// Name: setPrecision +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::setPrecision(int precision) { + + precision_ = precision; +} + +//------------------------------------------------------------------------------ +// Name: setFixedPrecision +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::setFixedPrecision(int precision) { + + if (fixed_precision_ > precision_) { + fixed_precision_ = -1; + } else { + fixed_precision_ = precision; + } +} + +//------------------------------------------------------------------------------ +// Name: setBeep +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::setBeep(bool flag) { + beep_ = flag; +} + +//------------------------------------------------------------------------------ +// Name: setGroupDigits +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::setGroupDigits(bool flag) { + groupdigits_ = flag; +} + +//------------------------------------------------------------------------------ +// Name: setTwosComplement +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::setTwosComplement(bool flag) { + twoscomplement_ = flag; +} + +//------------------------------------------------------------------------------ +// Name: setBinaryGrouping +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::setBinaryGrouping(int digits) { + binaryGrouping_ = digits; +} + +//------------------------------------------------------------------------------ +// Name: setOctalGrouping +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::setOctalGrouping(int digits) { + octalGrouping_ = digits; +} + +//------------------------------------------------------------------------------ +// Name: setHexadecimalGrouping +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::setHexadecimalGrouping(int digits) { + hexadecimalGrouping_ = digits; +} + +//------------------------------------------------------------------------------ +// Name: getAmount +// Desc: +//------------------------------------------------------------------------------ +const KNumber &KCalcDisplay::getAmount() const { + return display_amount_; +} + +//------------------------------------------------------------------------------ +// Name: setAmount +// Desc: +//------------------------------------------------------------------------------ +bool KCalcDisplay::setAmount(const KNumber &new_amount) { + + QString display_str; + + str_int_ = QLatin1String("0"); + str_int_exp_.clear(); + period_ = false; + neg_sign_ = false; + eestate_ = false; + + if ((num_base_ != NB_DECIMAL) && (new_amount.type() != KNumber::TYPE_ERROR)) { + display_amount_ = new_amount.integerPart(); + + if (twoscomplement_) { + // treat number as 64-bit unsigned + const quint64 tmp_workaround = display_amount_.toUint64(); + display_str = QString::number(tmp_workaround, num_base_).toUpper(); + } else { + // QString::number treats non-decimal as unsigned + qint64 tmp_workaround = display_amount_.toInt64(); + const bool neg = tmp_workaround < 0; + if (neg) { + tmp_workaround = qAbs(tmp_workaround); + } + + display_str = QString::number(tmp_workaround, num_base_).toUpper(); + if (neg) { + display_str.prepend(KGlobal::locale()->negativeSign()); + } + } + } else { + // num_base_ == NB_DECIMAL || new_amount.type() == KNumber::TYPE_ERROR + display_amount_ = new_amount; + display_str = display_amount_.toQString(KCalcSettings::precision(), fixed_precision_); + } + + setText(display_str); + emit changedAmount(display_amount_); + return true; +} + +//------------------------------------------------------------------------------ +// Name: setText +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::setText(const QString &string) +{ + // note that "C" locale is being used internally + text_ = string; + + // don't mess with special numbers + const bool special = (string.contains(QLatin1String("nan")) || string.contains(QLatin1String("inf"))); + + // The decimal mode needs special treatment for two reasons, because: a) it uses KGlobal::locale() to get a localized + // format and b) it has possible numbers after the decimal place. Neither applies to Binary, Hexadecimal or Octal. + + if (groupdigits_ && !special){ + switch (num_base_) { + case NB_DECIMAL: + if (string.endsWith(QLatin1Char('.'))) { + text_.chop(1); + // Note: rounding happened already above! + text_ = KGlobal::locale()->formatNumber(text_, false, 0); + text_.append(KGlobal::locale()->decimalSymbol()); + } else { + // Note: rounding happened already above! + text_ = KGlobal::locale()->formatNumber(text_, false, 0); + } + break; + + case NB_BINARY: + text_ = groupDigits(text_, binaryGrouping_); + break; + + case NB_OCTAL: + text_ = groupDigits(text_, octalGrouping_); + break; + + case NB_HEX: + text_ = groupDigits(text_, hexadecimalGrouping_); + break; + } + } else if(special) { +#if 0 + // TODO: enable this code, it replaces the "inf" with an actual infinity + // symbol, but what should be put into the clip board when they copy? + if(string.contains(QLatin1String("inf"))) { + text_.replace("inf", QChar(0x221e)); + } +#endif + } + + update(); + emit changedText(text_); +} + +//------------------------------------------------------------------------------ +// Name: groupDigits +// Desc: +//------------------------------------------------------------------------------ +QString KCalcDisplay::groupDigits(const QString &displayString, int numDigits) { + + QString tmpDisplayString; + const int stringLength = displayString.length(); + + for (int i = stringLength; i > 0 ; i--){ + if(i % numDigits == 0 && i != stringLength) { + tmpDisplayString = tmpDisplayString + ' '; + } + + tmpDisplayString = tmpDisplayString + displayString[stringLength - i]; + } + + return tmpDisplayString; +} + +//------------------------------------------------------------------------------ +// Name: text +// Desc: +//------------------------------------------------------------------------------ +QString KCalcDisplay::text() const { + return text_; +} + +//------------------------------------------------------------------------------ +// Name: setBase +// Desc: change representation of display to new base (i.e. binary, decimal, +// octal, hexadecimal). The amount being displayed is changed to this +// base, but for now this amount can not be modified anymore (like +// being set with "setAmount"). Return value is the new base. +//------------------------------------------------------------------------------ +int KCalcDisplay::setBase(NumBase new_base) { + + switch (new_base) { + case NB_HEX: + num_base_ = NB_HEX; + period_ = false; + break; + case NB_DECIMAL: + num_base_ = NB_DECIMAL; + break; + case NB_OCTAL: + num_base_ = NB_OCTAL; + period_ = false; + break; + case NB_BINARY: + num_base_ = NB_BINARY; + period_ = false; + break; + default: + Q_ASSERT(0); + } + + // reset amount + setAmount(display_amount_); + return num_base_; +} + +//------------------------------------------------------------------------------ +// Name: setStatusText +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::setStatusText(int i, const QString &text) { + + if (i < NUM_STATUS_TEXT) { + str_status_[i] = text; + } + + update(); +} + +//------------------------------------------------------------------------------ +// Name: updateDisplay +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::updateDisplay() { + + // Put sign in front. + QString tmp_string; + if (neg_sign_) { + tmp_string = QLatin1Char('-') + str_int_; + } else { + tmp_string = str_int_; + } + + bool ok; + + switch (num_base_) { + case NB_BINARY: + Q_ASSERT(!period_ && !eestate_); + setText(tmp_string); + display_amount_ = KNumber(str_int_.toULongLong(&ok, 2)); + if (neg_sign_) { + display_amount_ = -display_amount_; + } + break; + + case NB_OCTAL: + Q_ASSERT(!period_ && !eestate_); + setText(tmp_string); + display_amount_ = KNumber(str_int_.toULongLong(&ok, 8)); + if (neg_sign_) { + display_amount_ = -display_amount_; + } + break; + + case NB_HEX: + Q_ASSERT(!period_ && !eestate_); + setText(tmp_string); + display_amount_ = KNumber(str_int_.toULongLong(&ok, 16)); + if (neg_sign_) { + display_amount_ = -display_amount_; + } + break; + + case NB_DECIMAL: + if (!eestate_) { + setText(tmp_string); + display_amount_ = KNumber(tmp_string); + } else { + if (str_int_exp_.isNull()) { + // add 'e0' to display but not to conversion + display_amount_ = KNumber(tmp_string); + setText(tmp_string + QLatin1String("e0")); + } else { + tmp_string += QLatin1Char('e') + str_int_exp_; + setText(tmp_string); + display_amount_ = KNumber(tmp_string); + } + } + break; + + default: + Q_ASSERT(0); + } + + emit changedAmount(display_amount_); +} + +//------------------------------------------------------------------------------ +// Name: newCharacter +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::newCharacter(const QChar new_char) { + + // test if character is valid + switch (new_char.toLatin1()) { + case 'e': + // EE can be set only once and in decimal mode + if (num_base_ != NB_DECIMAL || eestate_) { + if (beep_) { + KNotification::beep(); + } + return; + } + eestate_ = true; + break; + + case 'F': + case 'E': + case 'D': + case 'C': + case 'B': + case 'A': + if (num_base_ == NB_DECIMAL) { + if (beep_) { + KNotification::beep(); + } + return; + } + // no break + case '9': + case '8': + if (num_base_ == NB_OCTAL) { + if (beep_) { + KNotification::beep(); + } + return; + } + // no break + case '7': + case '6': + case '5': + case '4': + case '3': + case '2': + if (num_base_ == NB_BINARY) { + if (beep_) { + KNotification::beep(); + } + return; + } + // no break + case '1': + case '0': + break; + + default: + if(new_char == KGlobal::locale()->decimalSymbol()[0]) { + // Period can be set only once and only in decimal + // mode, also not in EE-mode + if (num_base_ != NB_DECIMAL || period_ || eestate_) { + if (beep_) { + KNotification::beep(); + } + return; + } + period_ = true; + } else { + if (beep_) { + KNotification::beep(); + } + return; + } + } + + // change exponent or mantissa + if (eestate_) { + // ignore '.' before 'e'. turn e.g. '123.e' into '123e' + if (new_char == QLatin1Char('e') && str_int_.endsWith(KGlobal::locale()->decimalSymbol())) { + str_int_.chop(1); + period_ = false; + } + + // 'e' only starts ee_mode, leaves strings unchanged + // do not add '0' if at start of exp + if (new_char != QLatin1Char('e') && !(str_int_exp_.isNull() && new_char == QLatin1Char('0'))) { + str_int_exp_.append(new_char); + } + } else { + // handle first character + if (str_int_ == QLatin1String("0")) { + switch (new_char.toLatin1()) { + case 'e': + // display "0e" not just "e" + // "0e" does not make sense either, but... + str_int_.append(new_char); + break; + default: + if(new_char == KGlobal::locale()->decimalSymbol()[0]) { + // display "0." not just "." + str_int_.append(new_char); + } else { + // no leading '0's + str_int_[0] = new_char; + } + } + } else { + str_int_.append(new_char); + } + } + + updateDisplay(); +} + +//------------------------------------------------------------------------------ +// Name: deleteLastDigit +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::deleteLastDigit() { + + // Only partially implemented !! + if (eestate_) { + if (str_int_exp_.isNull()) { + eestate_ = false; + } else { + const int length = str_int_exp_.length(); + if (length > 1) { + str_int_exp_.chop(1); + } else { + str_int_exp_ = QLatin1String((const char *)0); + } + } + } else { + const int length = str_int_.length(); + if (length > 1) { + if (str_int_[length-1] == KGlobal::locale()->decimalSymbol()[0]) { + period_ = false; + } + str_int_.chop(1); + } else { + Q_ASSERT(!period_); + str_int_[0] = QLatin1Char('0'); + } + } + + updateDisplay(); +} + +//------------------------------------------------------------------------------ +// Name: changeSign +// Desc: change Sign of display. Problem: Only possible here, when in input +// mode. Otherwise return 'false' so that the kcalc_core can handle +// things. +//------------------------------------------------------------------------------ +bool KCalcDisplay::changeSign() { + + //stupid way, to see if in input_mode or display_mode + if (str_int_ == QLatin1String("0")) { + return false; + } + + if (eestate_) { + if (!str_int_exp_.isNull()) { + if (str_int_exp_[0] != QLatin1Char('-')) { + str_int_exp_.prepend(QLatin1Char('-')); + } else { + str_int_exp_.remove(QLatin1Char('-')); + } + } + } else { + neg_sign_ = !neg_sign_; + } + + updateDisplay(); + return true; +} + +//------------------------------------------------------------------------------ +// Name: initStyleOption +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::initStyleOption(QStyleOptionFrame *option) const { + + if (!option) { + return; + } + + option->initFrom(this); + option->state &= ~QStyle::State_HasFocus; // don't draw focus highlight + + if (frameShadow() == QFrame::Sunken) { + option->state |= QStyle::State_Sunken; + } else if (frameShadow() == QFrame::Raised) { + option->state |= QStyle::State_Raised; + } + + option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, this); + option->midLineWidth = 0; +} + +//------------------------------------------------------------------------------ +// Name: paintEvent +// Desc: +//------------------------------------------------------------------------------ +void KCalcDisplay::paintEvent(QPaintEvent *) { + + QPainter painter(this); + + QStyleOptionFrame option; + initStyleOption(&option); + + style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &painter, this); + + // draw display text + const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, 0); + QRect cr = contentsRect(); + cr.adjust(margin*2, 0, -margin*2, 0); // provide a margin + + const int align = QStyle::visualAlignment(layoutDirection(), Qt::AlignRight | Qt::AlignVCenter); + painter.drawText(cr, align | Qt::TextSingleLine, text_); + + // draw the status texts using half of the normal + // font size but not smaller than 7pt + QFont fnt(font()); + fnt.setPointSize(qMax((fnt.pointSize() / 2), 7)); + painter.setFont(fnt); + + QFontMetrics fm(fnt); + const uint w = fm.width(QLatin1String("________")); + const uint h = fm.height(); + + for (int n = 0; n < NUM_STATUS_TEXT; ++n) { + painter.drawText(5 + n * w, h, str_status_[n]); + } +} + +//------------------------------------------------------------------------------ +// Name: sizeHint +// Desc: +//------------------------------------------------------------------------------ +QSize KCalcDisplay::sizeHint() const { + + // basic size + QSize sz = fontMetrics().size(Qt::TextSingleLine, text_); + + // expanded by half font height to make room for the status texts + QFont fnt(font()); + fnt.setPointSize(qMax((fnt.pointSize() / 2), 7)); + + const QFontMetrics fm(fnt); + sz.setHeight(sz.height() + fm.height()); + + QStyleOptionFrame option; + initStyleOption(&option); + + return (style()->sizeFromContents(QStyle::CT_LineEdit, &option, sz.expandedTo(QApplication::globalStrut()), this)); +} diff --git a/kcalc/kcalcdisplay.h b/kcalc/kcalcdisplay.h new file mode 100644 index 00000000..807cb009 --- /dev/null +++ b/kcalc/kcalcdisplay.h @@ -0,0 +1,156 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 1996 - 2000 Bernd Johannes Wuebben + wuebben@kde.org + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KCALCDISPLAY_H_ +#define KCALCDISPLAY_H_ + +#include +#include +#include "knumber.h" + +class CalcEngine; +class KAction; +class QTimer; +class QStyleOptionFrame; + +#define NUM_STATUS_TEXT 4 + +/* + This class provides a pocket calculator display. The display has + implicitely two major modes: One is for editing and one is purely + for displaying. + + When one uses "setAmount", the given amount is displayed, and the + amount which was possibly typed in before is lost. At the same time + this new value can not be modified. + + On the other hand, "addNewChar" adds a new digit to the amount that + is being typed in. If "setAmount" was used before, the display is + cleared and a new input starts. + + TODO: Check overflows, number of digits and such... +*/ + +enum NumBase { + NB_BINARY = 2, + NB_OCTAL = 8, + NB_DECIMAL = 10, + NB_HEX = 16 +}; + + +class KCalcDisplay : public QFrame { + Q_OBJECT + +public: + explicit KCalcDisplay(QWidget *parent = 0); + ~KCalcDisplay(); + + enum Event { + EventReset, // resets display + EventClear, // if no error reset display + EventError, + EventChangeSign + }; + + bool sendEvent(Event event); + void deleteLastDigit(); + const KNumber &getAmount() const; + void newCharacter(const QChar new_char); + bool setAmount(const KNumber &new_amount); + int setBase(NumBase new_base); + void setBeep(bool flag); + void setGroupDigits(bool flag); + void setTwosComplement(bool flag); + void setBinaryGrouping(int digits); + void setOctalGrouping(int digits); + void setHexadecimalGrouping(int digits); + void setFixedPrecision(int precision); + void setPrecision(int precision); + void setText(const QString &string); + QString groupDigits(const QString &displayString, int numDigits); + QString text() const; + void updateDisplay(); + void setStatusText(int i, const QString &text); + virtual QSize sizeHint() const; + + void changeSettings(); + void enterDigit(int data); + void updateFromCore(const CalcEngine &core, + bool store_result_in_history = false); + +public slots: + void slotCut(); + void slotCopy(); + void slotPaste(bool bClipboard = true); + +signals: + void clicked(); + void changedText(const QString &); + void changedAmount(const KNumber &); + +protected: + void mousePressEvent(QMouseEvent *); + virtual void paintEvent(QPaintEvent *p); + +private: + bool changeSign(); + void invertColors(); + void initStyleOption(QStyleOptionFrame *option) const; + +private slots: + void slotSelectionTimedOut(); + void slotDisplaySelected(); + void slotHistoryBack(); + void slotHistoryForward(); + +private: + QString text_; + bool beep_; + bool groupdigits_; + bool twoscomplement_; + int binaryGrouping_; + int octalGrouping_; + int hexadecimalGrouping_; + int button_; + bool lit_; + NumBase num_base_; + + int precision_; + int fixed_precision_; // "-1" = no fixed_precision + + KNumber display_amount_; + + QVector history_list_; + int history_index_; + + // only used for input of new numbers + bool eestate_; + bool period_; + bool neg_sign_; + QString str_int_; + QString str_int_exp_; + QString str_status_[NUM_STATUS_TEXT]; + + QTimer* selection_timer_; +}; + +#endif diff --git a/kcalc/kcalcrc.upd b/kcalc/kcalcrc.upd new file mode 100644 index 00000000..b90fb0b9 --- /dev/null +++ b/kcalc/kcalcrc.upd @@ -0,0 +1,8 @@ +Id=KDE_3_2_0 +File=kcalcrc +Group=General +Key=style,Statistical +Id=KDE_4_3_0 +File=kcalcrc +Group=Font +Key=Font,DisplayFont diff --git a/kcalc/kcalcui.rc b/kcalc/kcalcui.rc new file mode 100644 index 00000000..170ddf97 --- /dev/null +++ b/kcalc/kcalcui.rc @@ -0,0 +1,17 @@ + + + + &Settings + + + + + + + + + + + + + diff --git a/kcalc/knumber/CMakeLists.txt b/kcalc/knumber/CMakeLists.txt new file mode 100644 index 00000000..b2d7cb6d --- /dev/null +++ b/kcalc/knumber/CMakeLists.txt @@ -0,0 +1,2 @@ + +add_subdirectory( tests ) diff --git a/kcalc/knumber/knumber.cpp b/kcalc/knumber/knumber.cpp new file mode 100644 index 00000000..ee05511e --- /dev/null +++ b/kcalc/knumber/knumber.cpp @@ -0,0 +1,946 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include +#include "knumber.h" +#include "knumber_base.h" +#include "knumber_error.h" +#include "knumber_float.h" +#include "knumber_fraction.h" +#include "knumber_integer.h" +#include +#include +#include +#include + +QString KNumber::GroupSeparator = QLatin1String(","); +QString KNumber::DecimalSeparator = QLatin1String("."); + +const KNumber KNumber::Zero(QLatin1String("0")); +const KNumber KNumber::One(QLatin1String("1")); +const KNumber KNumber::NegOne(QLatin1String("-1")); +const KNumber KNumber::PosInfinity(QLatin1String("inf")); +const KNumber KNumber::NegInfinity(QLatin1String("-inf")); +const KNumber KNumber::NaN(QLatin1String("nan")); + +namespace { +namespace impl { + +//------------------------------------------------------------------------------ +// Name: increment +//------------------------------------------------------------------------------ +void increment(QString &str, int position) { + + for (int i = position; i >= 0; i--) { + const char last_char = str[i].toLatin1(); + switch (last_char) { + case '0': + str[i] = QLatin1Char('1'); + break; + case '1': + str[i] = QLatin1Char('2'); + break; + case '2': + str[i] = QLatin1Char('3'); + break; + case '3': + str[i] = QLatin1Char('4'); + break; + case '4': + str[i] = QLatin1Char('5'); + break; + case '5': + str[i] = QLatin1Char('6'); + break; + case '6': + str[i] = QLatin1Char('7'); + break; + case '7': + str[i] = QLatin1Char('8'); + break; + case '8': + str[i] = QLatin1Char('9'); + break; + case '9': + str[i] = QLatin1Char('0'); + if (i == 0) { + str.prepend(QLatin1Char('1')); + } + continue; + case '.': + continue; + } + break; + } +} + +//------------------------------------------------------------------------------ +// Name: round +//------------------------------------------------------------------------------ +void round(QString &str, int precision) { + + // Cut off if more digits in fractional part than 'precision' + + int decimalSymbolPos = str.indexOf(QLatin1Char('.')); + + if (decimalSymbolPos == -1) { + if (precision == 0) { + return; + } else if (precision > 0) { // add dot if missing (and needed) + str.append(QLatin1Char('.')); + decimalSymbolPos = str.length() - 1; + } + } + + // fill up with more than enough zeroes (in case fractional part too short) + str.append(QString().fill(QLatin1Char('0'), precision)); + + // Now decide whether to round up or down + const char last_char = str[decimalSymbolPos + precision + 1].toLatin1(); + switch (last_char) { + case '0': + case '1': + case '2': + case '3': + case '4': + // nothing to do, rounding down + break; + case '5': + case '6': + case '7': + case '8': + case '9': + // rounding up + increment(str, decimalSymbolPos + precision); + break; + default: + break; + } + + decimalSymbolPos = str.indexOf(QLatin1Char('.')); + str.truncate(decimalSymbolPos + precision + 1); + + // if precision == 0 delete also '.' + if (precision == 0) { + str = str.section(QLatin1Char('.'), 0, 0); + } +} +} + +//------------------------------------------------------------------------------ +// Name: round +//------------------------------------------------------------------------------ +QString round(const QString &s, int precision) { + + QString tmp = s; + if (precision < 0 || !QRegExp(QLatin1String("^[+-]?\\d+(\\.\\d+)*(e[+-]?\\d+)?$")).exactMatch(tmp)) { + return s; + } + + // Skip the sign (for now) + const bool neg = (tmp[0] == QLatin1Char('-')); + if (neg || tmp[0] == QLatin1Char('+')) { + tmp.remove(0, 1); + } + + // Split off exponential part (including 'e'-symbol) + QString mantString = tmp.section(QLatin1Char('e'), 0, 0, QString::SectionCaseInsensitiveSeps); + QString expString = tmp.section(QLatin1Char('e'), 1, 1, QString::SectionCaseInsensitiveSeps | QString::SectionIncludeLeadingSep); + + if (expString.length() == 1) { + expString.clear(); + } + + impl::round(mantString, precision); + + if (neg) { + mantString.prepend(QLatin1Char('-')); + } + + return mantString + expString; +} +} + +//------------------------------------------------------------------------------ +// Name: setGroupSeparator +//------------------------------------------------------------------------------ +void KNumber::setGroupSeparator(const QString &ch) { + GroupSeparator = ch; +} + +//------------------------------------------------------------------------------ +// Name: setDecimalSeparator +//------------------------------------------------------------------------------ +void KNumber::setDecimalSeparator(const QString &ch) { + DecimalSeparator = ch; +} + +//------------------------------------------------------------------------------ +// Name: groupSeparator +//------------------------------------------------------------------------------ +QString KNumber::groupSeparator() { + return GroupSeparator; +} + +//------------------------------------------------------------------------------ +// Name: decimalSeparator +//------------------------------------------------------------------------------ +QString KNumber::decimalSeparator() { + return DecimalSeparator; +} + +//------------------------------------------------------------------------------ +// Name: setDefaultFloatPrecision +//------------------------------------------------------------------------------ +void KNumber::setDefaultFloatPrecision(int precision) { + // Need to transform decimal digits into binary digits + const unsigned long int bin_prec = static_cast(double(precision) * M_LN10 / M_LN2 + 1); + mpf_set_default_prec(bin_prec); +} + +//------------------------------------------------------------------------------ +// Name: setSplitoffIntegerForFractionOutput +//------------------------------------------------------------------------------ +void KNumber::setSplitoffIntegerForFractionOutput(bool x) { + detail::knumber_fraction::set_split_off_integer_for_fraction_output(x); +} + +//------------------------------------------------------------------------------ +// Name: setDefaultFractionalInput +//------------------------------------------------------------------------------ +void KNumber::setDefaultFractionalInput(bool x) { + detail::knumber_fraction::set_default_fractional_input(x); +} + +//------------------------------------------------------------------------------ +// Name: setDefaultFloatOutput +//------------------------------------------------------------------------------ +void KNumber::setDefaultFloatOutput(bool x) { + detail::knumber_fraction::set_default_fractional_output(!x); +} + +//------------------------------------------------------------------------------ +// Name: Pi +//------------------------------------------------------------------------------ +KNumber KNumber::Pi() { + + // TODO: after 4.10 release: + // create a new constructor which works just like the normal QString + // accepting constructor, but allows us to specify separator + // characters, this will allow things to be done slightly more + // efficiently + QString s(QLatin1String("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068")); + s.replace('.', DecimalSeparator); + return KNumber(s); +} + +//------------------------------------------------------------------------------ +// Name: Euler +//------------------------------------------------------------------------------ +KNumber KNumber::Euler() { + + // TODO: after 4.10 release: + // create a new constructor which works just like the normal QString + // accepting constructor, but allows us to specify separator + // characters, this will allow things to be done slightly more + // efficiently + QString s(QLatin1String("2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274")); + s.replace('.', DecimalSeparator); + return KNumber(s); +} + +//------------------------------------------------------------------------------ +// Name: KNumber +//------------------------------------------------------------------------------ +KNumber::KNumber() : value_(new detail::knumber_integer(0)) { +} + +//------------------------------------------------------------------------------ +// Name: KNumber +//------------------------------------------------------------------------------ +KNumber::KNumber(const QString &s) : value_(0) { + + const QRegExp special_regex(QLatin1String("^(inf|-inf|nan)$")); + const QRegExp integer_regex(QLatin1String("^[+-]?\\d+$")); + const QRegExp fraction_regex(QLatin1String("^[+-]?\\d+/\\d+$")); + const QRegExp float_regex(QString(QLatin1String("^([+-]?\\d*)(%1\\d*)?(e([+-]?\\d+))?$")).arg(QRegExp::escape(DecimalSeparator))); + + if (special_regex.exactMatch(s)) { + value_ = new detail::knumber_error(s); + } else if (integer_regex.exactMatch(s)) { + value_ = new detail::knumber_integer(s); + } else if (fraction_regex.exactMatch(s)) { + value_ = new detail::knumber_fraction(s); + simplify(); + } else if (float_regex.exactMatch(s)) { + + if(detail::knumber_fraction::default_fractional_input) { + + const QStringList list = float_regex.capturedTexts(); + if(list.size() == 5) { + + const QString ipart = list[1]; + const QString fpart = list[2]; + const QString epart = list[3]; + const int e_val = list[4].toInt(); + + QString num = ipart + fpart.mid(1); + QString den = QLatin1String("1") + QString(fpart.size() - 1, QLatin1Char('0')); + + if(e_val < 0) { + den = den + QString(::abs(e_val), QLatin1Char('0')); + } else if(e_val > 0) { + num = num + QString(::abs(e_val), QLatin1Char('0')); + } + + value_ = new detail::knumber_fraction(QString(QLatin1String("%1/%2")).arg(num, den)); + simplify(); + return; + } + } + + // we need to normalize the decimal searator to US style because that's + // the only type that the GMP function accept + QString new_s = s; + new_s.replace(DecimalSeparator, QLatin1String(".")); + + value_ = new detail::knumber_float(new_s); + simplify(); + } else { + value_ = new detail::knumber_error(detail::knumber_error::ERROR_UNDEFINED); + } +} + +//------------------------------------------------------------------------------ +// Name: KNumber +//------------------------------------------------------------------------------ +KNumber::KNumber(qint32 value) : value_(new detail::knumber_integer(value)) { +} + +//------------------------------------------------------------------------------ +// Name: KNumber +//------------------------------------------------------------------------------ +KNumber::KNumber(qint64 value) : value_(new detail::knumber_integer(value)) { +} + +//------------------------------------------------------------------------------ +// Name: KNumber +//------------------------------------------------------------------------------ +KNumber::KNumber(quint32 value) : value_(new detail::knumber_integer(value)) { +} + +//------------------------------------------------------------------------------ +// Name: KNumber +//------------------------------------------------------------------------------ +KNumber::KNumber(quint64 value) : value_(new detail::knumber_integer(value)) { +} + +//------------------------------------------------------------------------------ +// Name: KNumber +//------------------------------------------------------------------------------ +KNumber::KNumber(qint64 num, quint64 den) : value_(new detail::knumber_fraction(num, den)) { +} + +//------------------------------------------------------------------------------ +// Name: KNumber +//------------------------------------------------------------------------------ +KNumber::KNumber(quint64 num, quint64 den) : value_(new detail::knumber_fraction(num, den)) { +} + +#ifdef HAVE_LONG_DOUBLE +//------------------------------------------------------------------------------ +// Name: KNumber +//------------------------------------------------------------------------------ +KNumber::KNumber(long double value) : value_(new detail::knumber_float(value)) { + simplify(); +} +#endif + +//------------------------------------------------------------------------------ +// Name: KNumber +//------------------------------------------------------------------------------ +KNumber::KNumber(double value) : value_(new detail::knumber_float(value)) { + simplify(); +} + +//------------------------------------------------------------------------------ +// Name: KNumber +//------------------------------------------------------------------------------ +KNumber::KNumber(const KNumber &other) : value_(0) { + if(&other != this) { + value_ = other.value_->clone(); + } +} + +//------------------------------------------------------------------------------ +// Name: ~KNumber +//------------------------------------------------------------------------------ +KNumber::~KNumber() { + delete value_; +} + +//------------------------------------------------------------------------------ +// Name: type +//------------------------------------------------------------------------------ +KNumber::Type KNumber::type() const { + + if(dynamic_cast(value_)) { + return TYPE_INTEGER; + } else if(dynamic_cast(value_)) { + return TYPE_FLOAT; + } else if(dynamic_cast(value_)) { + return TYPE_FRACTION; + } else if(dynamic_cast(value_)) { + return TYPE_ERROR; + } else { + Q_ASSERT(0); + return TYPE_ERROR; + } +} + +//------------------------------------------------------------------------------ +// Name: operator= +//------------------------------------------------------------------------------ +KNumber &KNumber::operator=(const KNumber &rhs) { + KNumber(rhs).swap(*this); + return *this; +} + +//------------------------------------------------------------------------------ +// Name: swap +//------------------------------------------------------------------------------ +void KNumber::swap(KNumber &other) { + qSwap(value_, other.value_); +} + +//------------------------------------------------------------------------------ +// Name: integerPart +//------------------------------------------------------------------------------ +KNumber KNumber::integerPart() const { + + KNumber x(*this); + + if(detail::knumber_integer *const p = dynamic_cast(value_)) { + // NO-OP + Q_UNUSED(p); + } else if(detail::knumber_float *const p = dynamic_cast(value_)) { + detail::knumber_base *v = new detail::knumber_integer(p); + qSwap(v, x.value_); + delete v; + } else if(detail::knumber_fraction *const p = dynamic_cast(value_)) { + detail::knumber_base *v = new detail::knumber_integer(p); + qSwap(v, x.value_); + delete v; + } else if(detail::knumber_error *const p = dynamic_cast(value_)) { + // NO-OP + Q_UNUSED(p); + } else { + Q_ASSERT(0); + } + + return x; +} + +//------------------------------------------------------------------------------ +// Name: simplify +//------------------------------------------------------------------------------ +void KNumber::simplify() { + + if(value_->is_integer()) { + + if(detail::knumber_integer *const p = dynamic_cast(value_)) { + // NO-OP + Q_UNUSED(p); + } else if(detail::knumber_float *const p = dynamic_cast(value_)) { + detail::knumber_base *v = new detail::knumber_integer(p); + qSwap(v, value_); + delete v; + } else if(detail::knumber_fraction *const p = dynamic_cast(value_)) { + detail::knumber_base *v = new detail::knumber_integer(p); + qSwap(v, value_); + delete v; + } else if(detail::knumber_error *const p = dynamic_cast(value_)) { + // NO-OP + Q_UNUSED(p); + } else { + Q_ASSERT(0); + } + } +} + +//------------------------------------------------------------------------------ +// Name: operator+= +//------------------------------------------------------------------------------ +KNumber &KNumber::operator+=(const KNumber &rhs) { + value_ = value_->add(rhs.value_); + simplify(); + return *this; +} + +//------------------------------------------------------------------------------ +// Name: operator-= +//------------------------------------------------------------------------------ +KNumber &KNumber::operator-=(const KNumber &rhs) { + value_ = value_->sub(rhs.value_); + simplify(); + return *this; +} + +//------------------------------------------------------------------------------ +// Name: operator*= +//------------------------------------------------------------------------------ +KNumber &KNumber::operator*=(const KNumber &rhs) { + value_ = value_->mul(rhs.value_); + simplify(); + return *this; +} + +//------------------------------------------------------------------------------ +// Name: operator/= +//------------------------------------------------------------------------------ +KNumber &KNumber::operator/=(const KNumber &rhs) { + + // Fix for bug #330577, x /0 is undefined, not infinity + // Also indirectly fixes bug #329897, tan(90) is undefined, not infinity + if(rhs == Zero) { + *this = NaN; + return *this; + } + + value_ = value_->div(rhs.value_); + simplify(); + return *this; +} + +//------------------------------------------------------------------------------ +// Name: operator%= +//------------------------------------------------------------------------------ +KNumber &KNumber::operator%=(const KNumber &rhs) { + value_ = value_->mod(rhs.value_); + simplify(); + return *this; +} + +//------------------------------------------------------------------------------ +// Name: operator&= +//------------------------------------------------------------------------------ +KNumber &KNumber::operator&=(const KNumber &rhs) { + value_ = value_->bitwise_and(rhs.value_); + return *this; +} + +//------------------------------------------------------------------------------ +// Name: operator|= +//------------------------------------------------------------------------------ +KNumber &KNumber::operator|=(const KNumber &rhs) { + value_ = value_->bitwise_or(rhs.value_); + return *this; +} + +//------------------------------------------------------------------------------ +// Name: operator^= +//------------------------------------------------------------------------------ +KNumber &KNumber::operator^=(const KNumber &rhs) { + value_ = value_->bitwise_xor(rhs.value_); + return *this; +} + +//------------------------------------------------------------------------------ +// Name: operator<< +//------------------------------------------------------------------------------ +KNumber &KNumber::operator<<=(const KNumber &rhs) { + value_ = value_->bitwise_shift(rhs.value_); + return *this; +} + +//------------------------------------------------------------------------------ +// Name: operator>>= +//------------------------------------------------------------------------------ +KNumber &KNumber::operator>>=(const KNumber &rhs) { + const KNumber rhs_neg(-rhs); + value_ = value_->bitwise_shift(rhs_neg.value_); + return *this; +} + +//------------------------------------------------------------------------------ +// Name: operator- +//------------------------------------------------------------------------------ +KNumber KNumber::operator-() const { + KNumber x(*this); + x.value_ = x.value_->neg(); + return x; +} + +//------------------------------------------------------------------------------ +// Name: operator~ +//------------------------------------------------------------------------------ +KNumber KNumber::operator~() const { + KNumber x(*this); + x.value_ = x.value_->cmp(); + return x; +} + +//------------------------------------------------------------------------------ +// Name: toQString +//------------------------------------------------------------------------------ +QString KNumber::toQString(int width, int precision) const { + + if(value_->is_zero()) { + return QLatin1String("0"); + } + + QString s; + + if(detail::knumber_integer *const p = dynamic_cast(value_)) { + if(width > 0) { + s = detail::knumber_float(p).toString(width); + } else { + s = value_->toString(width); + } + } else if(detail::knumber_float *const p = dynamic_cast(value_)) { + if(width > 0) { + s = value_->toString(width); + } else { + s = value_->toString(3 * mpf_get_default_prec() / 10); + } + } else if(detail::knumber_fraction *const p = dynamic_cast(value_)) { + s = value_->toString(width); + } else { + return value_->toString(width); + } + + // now do some rounding to make sure things are displayed reasonably + if (precision >= 0) { + return round(s, precision); + } else { + return s; + } +} + +//------------------------------------------------------------------------------ +// Name: toUint64 +//------------------------------------------------------------------------------ +quint64 KNumber::toUint64() const { + return value_->toUint64(); +} + +//------------------------------------------------------------------------------ +// Name: toInt64 +//------------------------------------------------------------------------------ +qint64 KNumber::toInt64() const { + return value_->toInt64(); +} + +//------------------------------------------------------------------------------ +// Name: abs +//------------------------------------------------------------------------------ +KNumber KNumber::abs() const { + KNumber z(*this); + z.value_ = z.value_->abs(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: cbrt +//------------------------------------------------------------------------------ +KNumber KNumber::cbrt() const { + KNumber z(*this); + z.value_ = z.value_->cbrt(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: sqrt +//------------------------------------------------------------------------------ +KNumber KNumber::sqrt() const { + KNumber z(*this); + z.value_ = z.value_->sqrt(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: pow +//------------------------------------------------------------------------------ +KNumber KNumber::pow(const KNumber &x) const { + + // Fix for bug #330711 (pow(0, -x) was causing crashes + // Fix for bug #330597 (pow(0,0) was 1 now it is NaN + // Thanks to Raushan Kumar for identifying the issue and submitting + // patches + if(*this == Zero && x <= Zero) { + return NaN; + } + + // if the LHS is a special then we can use this function + // no matter what, cause the result is a special too + if(!dynamic_cast(value_)) { + // number much bigger than this tend to crash GMP with + // an abort + if(x > KNumber(QLatin1String("1000000000"))) { + return PosInfinity; + } + } + + KNumber z(*this); + z.value_ = z.value_->pow(x.value_); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: sin +//------------------------------------------------------------------------------ +KNumber KNumber::sin() const { + KNumber z(*this); + z.value_ = z.value_->sin(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: cos +//------------------------------------------------------------------------------ +KNumber KNumber::cos() const { + KNumber z(*this); + z.value_ = z.value_->cos(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: tan +//------------------------------------------------------------------------------ +KNumber KNumber::tan() const { + KNumber z(*this); + z.value_ = z.value_->tan(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: tgamma +//------------------------------------------------------------------------------ +KNumber KNumber::tgamma() const { + KNumber z(*this); + if(z > KNumber(QLatin1String("10000000000"))) { + return PosInfinity; + } + z.value_ = z.value_->tgamma(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: asin +//------------------------------------------------------------------------------ +KNumber KNumber::asin() const { + KNumber z(*this); + z.value_ = z.value_->asin(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: acos +//------------------------------------------------------------------------------ +KNumber KNumber::acos() const { + KNumber z(*this); + z.value_ = z.value_->acos(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: atan +//------------------------------------------------------------------------------ +KNumber KNumber::atan() const { + KNumber z(*this); + z.value_ = z.value_->atan(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: sinh +//------------------------------------------------------------------------------ +KNumber KNumber::sinh() const { + KNumber z(*this); + z.value_ = z.value_->sinh(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: cosh +//------------------------------------------------------------------------------ +KNumber KNumber::cosh() const { + KNumber z(*this); + z.value_ = z.value_->cosh(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: tanh +//------------------------------------------------------------------------------ +KNumber KNumber::tanh() const { + KNumber z(*this); + z.value_ = z.value_->tanh(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: asinh +//------------------------------------------------------------------------------ +KNumber KNumber::asinh() const { + KNumber z(*this); + z.value_ = z.value_->asinh(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: acosh +//------------------------------------------------------------------------------ +KNumber KNumber::acosh() const { + KNumber z(*this); + z.value_ = z.value_->acosh(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: atanh +//------------------------------------------------------------------------------ +KNumber KNumber::atanh() const { + KNumber z(*this); + z.value_ = z.value_->atanh(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: factorial +//------------------------------------------------------------------------------ +KNumber KNumber::factorial() const { + KNumber z(*this); + + // number much bigger than this tend to crash GMP with + // an abort + if(z > KNumber(QLatin1String("10000000000"))) { + return PosInfinity; + } + + z.value_ = z.value_->factorial(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: log2 +//------------------------------------------------------------------------------ +KNumber KNumber::log2() const { + KNumber z(*this); + z.value_ = z.value_->log2(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: log10 +//------------------------------------------------------------------------------ +KNumber KNumber::log10() const { + KNumber z(*this); + z.value_ = z.value_->log10(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: ln +//------------------------------------------------------------------------------ +KNumber KNumber::ln() const { + KNumber z(*this); + z.value_ = z.value_->ln(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: floor +//------------------------------------------------------------------------------ +KNumber KNumber::floor() const { + KNumber z(*this); + z.value_ = z.value_->floor(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: ceil +//------------------------------------------------------------------------------ +KNumber KNumber::ceil() const { + KNumber z(*this); + z.value_ = z.value_->ceil(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: exp2 +//------------------------------------------------------------------------------ +KNumber KNumber::exp2() const { + KNumber z(*this); + z.value_ = z.value_->exp2(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: exp10 +//------------------------------------------------------------------------------ +KNumber KNumber::exp10() const { + KNumber z(*this); + z.value_ = z.value_->exp10(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: exp +//------------------------------------------------------------------------------ +KNumber KNumber::exp() const { + KNumber z(*this); + z.value_ = z.value_->exp(); + z.simplify(); + return z; +} + +//------------------------------------------------------------------------------ +// Name: bin +//------------------------------------------------------------------------------ +KNumber KNumber::bin(const KNumber &x) const { + KNumber z(*this); + z.value_ = z.value_->bin(x.value_); + z.simplify(); + return z; +} diff --git a/kcalc/knumber/knumber.h b/kcalc/knumber/knumber.h new file mode 100644 index 00000000..5794ff73 --- /dev/null +++ b/kcalc/knumber/knumber.h @@ -0,0 +1,175 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KNUMBER_H_ +#define KNUMBER_H_ + +#include "knumber_operators.h" +#include +#include + +namespace detail { +class knumber_base; +} + +class KNumber { +private: + friend bool operator==(const KNumber &lhs, const KNumber &rhs); + friend bool operator!=(const KNumber &lhs, const KNumber &rhs); + friend bool operator>=(const KNumber &lhs, const KNumber &rhs); + friend bool operator<=(const KNumber &lhs, const KNumber &rhs); + friend bool operator>(const KNumber &lhs, const KNumber &rhs); + friend bool operator<(const KNumber &lhs, const KNumber &rhs); + +public: + enum Type { + TYPE_ERROR, + TYPE_INTEGER, + TYPE_FLOAT, + TYPE_FRACTION + }; + +public: + // useful constants + static const KNumber Zero; + static const KNumber One; + static const KNumber NegOne; + static const KNumber PosInfinity; + static const KNumber NegInfinity; + static const KNumber NaN; + +public: + static KNumber Pi(); + static KNumber Euler(); + +public: + // construction/destruction + KNumber(); + explicit KNumber(const QString &s); + + explicit KNumber(qint32 value); + explicit KNumber(qint64 value); + explicit KNumber(quint32 value); + explicit KNumber(quint64 value); + + KNumber(qint64 num, quint64 den); + KNumber(quint64 num, quint64 den); + +#ifdef HAVE_LONG_DOUBLE + explicit KNumber(long double value); +#endif + explicit KNumber(double value); + + KNumber(const KNumber &other); + ~KNumber(); + +public: + Type type() const; + +public: + // assignment + KNumber &operator=(const KNumber &rhs); + +public: + // basic math operators + KNumber &operator+=(const KNumber &rhs); + KNumber &operator-=(const KNumber &rhs); + KNumber &operator*=(const KNumber &rhs); + KNumber &operator/=(const KNumber &rhs); + KNumber &operator%=(const KNumber &rhs); + +public: + // bitwise operators + KNumber &operator&=(const KNumber &rhs); + KNumber &operator|=(const KNumber &rhs); + KNumber &operator^=(const KNumber &rhs); + KNumber &operator<<=(const KNumber &rhs); + KNumber &operator>>=(const KNumber &rhs); + +public: + // neg/cmp + KNumber operator-() const; + KNumber operator~() const; + +public: + KNumber integerPart() const; + +public: + QString toQString(int width = -1, int precision = -1) const; + quint64 toUint64() const; + qint64 toInt64() const; + + +public: + KNumber abs() const; + KNumber cbrt() const; + KNumber sqrt() const; + KNumber pow(const KNumber &x) const; + + KNumber sin() const; + KNumber cos() const; + KNumber tan() const; + KNumber asin() const; + KNumber acos() const; + KNumber atan() const; + KNumber sinh() const; + KNumber cosh() const; + KNumber tanh() const; + KNumber asinh() const; + KNumber acosh() const; + KNumber atanh() const; + KNumber tgamma() const; + + KNumber factorial() const; + + KNumber log2() const; + KNumber log10() const; + KNumber ln() const; + KNumber floor() const; + KNumber ceil() const; + KNumber exp2() const; + KNumber exp10() const; + KNumber exp() const; + KNumber bin(const KNumber &x) const; + +public: + static void setDefaultFloatPrecision(int precision); + static void setSplitoffIntegerForFractionOutput(bool x); + static void setDefaultFractionalInput(bool x); + static void setDefaultFloatOutput(bool x); + static void setGroupSeparator(const QString &ch); + static void setDecimalSeparator(const QString &ch); + + static QString groupSeparator(); + static QString decimalSeparator(); + +public: + void swap(KNumber &other); + +private: + void simplify(); + +private: + detail::knumber_base *value_; + +private: + static QString GroupSeparator; + static QString DecimalSeparator; +}; + +#endif diff --git a/kcalc/knumber/knumber_base.h b/kcalc/knumber/knumber_base.h new file mode 100644 index 00000000..e0792d35 --- /dev/null +++ b/kcalc/knumber/knumber_base.h @@ -0,0 +1,116 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KNUMBER_BASE_H_ +#define KNUMBER_BASE_H_ + +#include + +#ifdef KNUMBER_USE_MPFR +#include +#endif + +#include +#include + +namespace detail { + +class knumber_error; +class knumber_integer; +class knumber_fraction; +class knumber_float; + +class knumber_base { +public: + virtual ~knumber_base() { } + +public: + virtual knumber_base *clone() = 0; + +public: + virtual QString toString(int precision) const = 0; + virtual quint64 toUint64() const = 0; + virtual qint64 toInt64() const = 0; + +public: + virtual bool is_integer() const = 0; + virtual bool is_zero() const = 0; + virtual int sign() const = 0; + +public: + // basic math + virtual knumber_base *add(knumber_base *rhs) = 0; + virtual knumber_base *sub(knumber_base *rhs) = 0; + virtual knumber_base *mul(knumber_base *rhs) = 0; + virtual knumber_base *div(knumber_base *rhs) = 0; + virtual knumber_base *mod(knumber_base *rhs) = 0; + +public: + // logical operators + virtual knumber_base *bitwise_and(knumber_base *rhs) = 0; + virtual knumber_base *bitwise_xor(knumber_base *rhs) = 0; + virtual knumber_base *bitwise_or(knumber_base *rhs) = 0; + virtual knumber_base *bitwise_shift(knumber_base *rhs) = 0; + +public: + // algebraic functions + virtual knumber_base *pow(knumber_base *rhs) = 0; + virtual knumber_base *neg() = 0; + virtual knumber_base *cmp() = 0; + virtual knumber_base *abs() = 0; + virtual knumber_base *sqrt() = 0; + virtual knumber_base *cbrt() = 0; + virtual knumber_base *factorial() = 0; + virtual knumber_base *reciprocal() = 0; + +public: + // special functions + virtual knumber_base *log2() = 0; + virtual knumber_base *log10() = 0; + virtual knumber_base *ln() = 0; + virtual knumber_base *exp2() = 0; + virtual knumber_base *exp10() = 0; + virtual knumber_base *floor() = 0; + virtual knumber_base *ceil() = 0; + virtual knumber_base *exp() = 0; + virtual knumber_base *bin(knumber_base *rhs) = 0; + +public: + // trig functions + virtual knumber_base *sin() = 0; + virtual knumber_base *cos() = 0; + virtual knumber_base *tan() = 0; + virtual knumber_base *asin() = 0; + virtual knumber_base *acos() = 0; + virtual knumber_base *atan() = 0; + virtual knumber_base *sinh() = 0; + virtual knumber_base *cosh() = 0; + virtual knumber_base *tanh() = 0; + virtual knumber_base *asinh() = 0; + virtual knumber_base *acosh() = 0; + virtual knumber_base *atanh() = 0; + virtual knumber_base *tgamma() = 0; + +public: + // comparison + virtual int compare(knumber_base *rhs) = 0; +}; + +} + +#endif diff --git a/kcalc/knumber/knumber_error.cpp b/kcalc/knumber/knumber_error.cpp new file mode 100644 index 00000000..ae6dc9ef --- /dev/null +++ b/kcalc/knumber/knumber_error.cpp @@ -0,0 +1,714 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include +#include "knumber_integer.h" +#include "knumber_float.h" +#include "knumber_fraction.h" +#include "knumber_error.h" +#include // for M_PI +#include + +namespace detail { + + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_error::knumber_error(Error e) : error_(e) { +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_error::knumber_error(const QString &s) { + + if (s == QLatin1String("nan")) error_ = ERROR_UNDEFINED; + else if (s == QLatin1String("inf")) error_ = ERROR_POS_INFINITY; + else if (s == QLatin1String("-inf")) error_ = ERROR_NEG_INFINITY; + else error_ = ERROR_UNDEFINED; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_error::knumber_error() : error_(ERROR_UNDEFINED) { + +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_error::~knumber_error() { + +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_error::knumber_error(const knumber_integer *) : error_(ERROR_UNDEFINED) { +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_error::knumber_error(const knumber_fraction *) : error_(ERROR_UNDEFINED) { +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_error::knumber_error(const knumber_float *) : error_(ERROR_UNDEFINED) { +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_error::knumber_error(const knumber_error *value) : error_(value->error_) { +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +QString knumber_error::toString(int precision) const { + + Q_UNUSED(precision); + + switch(error_) { + case ERROR_POS_INFINITY: + return QLatin1String("inf"); + case ERROR_NEG_INFINITY: + return QLatin1String("-inf"); + case ERROR_UNDEFINED: + default: + return QLatin1String("nan"); + } +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::add(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_error *const p = dynamic_cast(rhs)) { + if(error_ == ERROR_POS_INFINITY && p->error_ == ERROR_NEG_INFINITY) { + error_ = ERROR_UNDEFINED; + } else if(error_ == ERROR_NEG_INFINITY && p->error_ == ERROR_POS_INFINITY) { + error_ = ERROR_UNDEFINED; + } else if(p->error_ == ERROR_UNDEFINED) { + error_ = ERROR_UNDEFINED; + } + return this; + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::sub(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_error *const p = dynamic_cast(rhs)) { + if(error_ == ERROR_POS_INFINITY && p->error_ == ERROR_POS_INFINITY) { + error_ = ERROR_UNDEFINED; + } else if(error_ == ERROR_NEG_INFINITY && p->error_ == ERROR_NEG_INFINITY) { + error_ = ERROR_UNDEFINED; + } else if(p->error_ == ERROR_UNDEFINED) { + error_ = ERROR_UNDEFINED; + } + return this; + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::mul(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + if(p->is_zero()) { + error_ = ERROR_UNDEFINED; + } + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + if(p->is_zero()) { + error_ = ERROR_UNDEFINED; + } + return this; + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + if(p->is_zero()) { + error_ = ERROR_UNDEFINED; + } + return this; + } else if(knumber_error *const p = dynamic_cast(rhs)) { + if(error_ == ERROR_POS_INFINITY && p->error_ == ERROR_NEG_INFINITY) { + error_ = ERROR_NEG_INFINITY; + } else if(error_ == ERROR_NEG_INFINITY && p->error_ == ERROR_POS_INFINITY) { + error_ = ERROR_NEG_INFINITY; + } else if(error_ == ERROR_NEG_INFINITY && p->error_ == ERROR_NEG_INFINITY) { + error_ = ERROR_POS_INFINITY; + } else if(p->error_ == ERROR_UNDEFINED) { + error_ = ERROR_UNDEFINED; + } + return this; + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::div(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_error *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + error_ = ERROR_UNDEFINED; + return this; + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::mod(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_error *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + error_ = ERROR_UNDEFINED; + return this; + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::pow(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + return this; + } else if(knumber_error *const p = dynamic_cast(rhs)) { + + switch(error_) { + case ERROR_POS_INFINITY: + if(p->sign() > 0) { + return this; + } else if(p->sign() < 0) { + knumber_integer *n = new knumber_integer(0); + delete this; + return n; + } else { + error_ = ERROR_UNDEFINED; + return this; + } + break; + case ERROR_NEG_INFINITY: + if(p->sign() > 0) { + error_ = ERROR_POS_INFINITY; + return this; + } else if(p->sign() < 0) { + knumber_integer *n = new knumber_integer(0); + delete this; + return n; + } else { + error_ = ERROR_UNDEFINED; + return this; + } + break; + case ERROR_UNDEFINED: + return this; + } + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::neg() { + + switch(error_) { + case ERROR_POS_INFINITY: + error_ = ERROR_NEG_INFINITY; + break; + case ERROR_NEG_INFINITY: + error_ = ERROR_POS_INFINITY; + break; + case ERROR_UNDEFINED: + default: + break; + } + + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::cmp() { + + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::abs() { + + switch(error_) { + case ERROR_NEG_INFINITY: + error_ = ERROR_POS_INFINITY; + break; + case ERROR_POS_INFINITY: + case ERROR_UNDEFINED: + default: + break; + } + + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::sqrt() { + + switch(error_) { + case ERROR_NEG_INFINITY: + error_ = ERROR_UNDEFINED; + break; + case ERROR_POS_INFINITY: + case ERROR_UNDEFINED: + default: + break; + } + + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::cbrt() { + + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::factorial() { + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::sin() { + + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::cos() { + + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base* knumber_error::tgamma() { + + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::tan() { + + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::asin() { + + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::acos() { + + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::atan() { + + switch(error_) { + case ERROR_POS_INFINITY: + delete this; + return new knumber_float(M_PI / 2.0); + case ERROR_NEG_INFINITY: + delete this; + return new knumber_float(-M_PI / 2.0); + case ERROR_UNDEFINED: + default: + return this; + } +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::sinh() { + + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::cosh() { + + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::tanh() { + + if(sign() > 0) { + delete this; + return new knumber_integer(1); + } else if(sign() < 0) { + delete this; + return new knumber_integer(-1); + } else { + return this; + } +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::asinh() { + + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::acosh() { + + if(sign() < 0) { + error_ = ERROR_UNDEFINED; + } + + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::atanh() { + + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +int knumber_error::compare(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + if(sign() > 0) { + return 1; + } else { + return -1; + } + } else if(knumber_float *const p = dynamic_cast(rhs)) { + if(sign() > 0) { + return 1; + } else { + return -1; + } + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + if(sign() > 0) { + return 1; + } else { + return -1; + } + } else if(knumber_error *const p = dynamic_cast(rhs)) { + return sign() == p->sign(); + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::clone() { + return new knumber_error(this); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::bitwise_and(knumber_base *rhs) { + Q_UNUSED(rhs); + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::bitwise_xor(knumber_base *rhs) { + Q_UNUSED(rhs); + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::bitwise_or(knumber_base *rhs) { + Q_UNUSED(rhs); + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::bitwise_shift(knumber_base *rhs) { + Q_UNUSED(rhs); + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +bool knumber_error::is_integer() const { + + return false; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +bool knumber_error::is_zero() const { + + return false; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +int knumber_error::sign() const { + + switch(error_) { + case ERROR_POS_INFINITY: + return +1; + case ERROR_NEG_INFINITY: + return -1; + case ERROR_UNDEFINED: + default: + return 0; + } +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::reciprocal() { + + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::log2() { + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::log10() { + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::ln() { + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::ceil() { + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::floor() { + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::exp2() { + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::exp10() { + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::exp() { + error_ = ERROR_UNDEFINED; + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +quint64 knumber_error::toUint64() const { + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +qint64 knumber_error::toInt64() const { + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_error::bin(knumber_base *rhs) { + Q_UNUSED(rhs); + error_ = ERROR_UNDEFINED; + return this; +} + +} diff --git a/kcalc/knumber/knumber_error.h b/kcalc/knumber/knumber_error.h new file mode 100644 index 00000000..fb49c0a4 --- /dev/null +++ b/kcalc/knumber/knumber_error.h @@ -0,0 +1,125 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KNUMBER_ERROR_H_ +#define KNUMBER_ERROR_H_ + +#include "knumber_base.h" + +class KNumber; + +namespace detail { + +class knumber_error : public knumber_base { + friend class ::KNumber; + friend class knumber_integer; + friend class knumber_fraction; + friend class knumber_float; + +public: + enum Error { + ERROR_UNDEFINED, + ERROR_POS_INFINITY, + ERROR_NEG_INFINITY + }; + +public: + explicit knumber_error(const QString &s); + explicit knumber_error(Error e); + knumber_error(); + virtual ~knumber_error(); + +public: + virtual QString toString(int precision) const; + virtual quint64 toUint64() const; + virtual qint64 toInt64() const; + +public: + virtual bool is_integer() const; + virtual bool is_zero() const; + virtual int sign() const; + +public: + virtual knumber_base *add(knumber_base *rhs); + virtual knumber_base *sub(knumber_base *rhs); + virtual knumber_base *mul(knumber_base *rhs); + virtual knumber_base *div(knumber_base *rhs); + virtual knumber_base *mod(knumber_base *rhs); + +public: + virtual knumber_base *bitwise_and(knumber_base *rhs); + virtual knumber_base *bitwise_xor(knumber_base *rhs); + virtual knumber_base *bitwise_or(knumber_base *rhs); + virtual knumber_base *bitwise_shift(knumber_base *rhs); + +public: + virtual knumber_base *pow(knumber_base *rhs); + virtual knumber_base *neg(); + virtual knumber_base *cmp(); + virtual knumber_base *abs(); + virtual knumber_base *sqrt(); + virtual knumber_base *cbrt(); + virtual knumber_base *factorial(); + virtual knumber_base *reciprocal(); + virtual knumber_base *tgamma(); + +public: + virtual knumber_base *log2(); + virtual knumber_base *log10(); + virtual knumber_base *ln(); + virtual knumber_base *exp2(); + virtual knumber_base *exp10(); + virtual knumber_base *floor(); + virtual knumber_base *ceil(); + virtual knumber_base *exp(); + virtual knumber_base *bin(knumber_base *rhs); + +public: + virtual knumber_base *sin(); + virtual knumber_base *cos(); + virtual knumber_base *tan(); + virtual knumber_base *asin(); + virtual knumber_base *acos(); + virtual knumber_base *atan(); + virtual knumber_base *sinh(); + virtual knumber_base *cosh(); + virtual knumber_base *tanh(); + virtual knumber_base *asinh(); + virtual knumber_base *acosh(); + virtual knumber_base *atanh(); + +public: + virtual int compare(knumber_base *rhs); + +private: + // conversion constructors + explicit knumber_error(const knumber_integer *value); + explicit knumber_error(const knumber_fraction *value); + explicit knumber_error(const knumber_float *value); + explicit knumber_error(const knumber_error *value); + +public: + virtual knumber_base *clone(); + +private: + Error error_; +}; + +} + +#endif diff --git a/kcalc/knumber/knumber_float.cpp b/kcalc/knumber/knumber_float.cpp new file mode 100644 index 00000000..e697f106 --- /dev/null +++ b/kcalc/knumber/knumber_float.cpp @@ -0,0 +1,1028 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include +#include "knumber_integer.h" +#include "knumber_float.h" +#include "knumber_fraction.h" +#include "knumber_error.h" +#include +#include +#include + +#ifdef _MSC_VER +double log2(double x) { return log(x) / log(2); } +double exp2(double x) { return exp(x * log(2)); } +double exp10(double x) { return exp(x * log(10)); } +#endif + +// NOTE: these assume IEEE floats.. +#ifndef isinf +#define isinf(x) ((x) != 0.0 && (x) + (x) == (x)) +#endif + +#ifndef isnan +#define isnan(x) ((x) != (x)) +#endif + +namespace detail { + +#ifdef KNUMBER_USE_MPFR +const mpfr_rnd_t knumber_float::rounding_mode = MPFR_RNDN; +const mpfr_prec_t knumber_float::precision = 1024; +#endif + +template +knumber_base *knumber_float::execute_libc_func(double x) { + const double r = F(x); + if(isnan(r)) { + knumber_error *e = new knumber_error(knumber_error::ERROR_UNDEFINED); + delete this; + return e; + } else if(isinf(r)) { + knumber_error *e = new knumber_error(knumber_error::ERROR_POS_INFINITY); + delete this; + return e; + } else { + mpf_set_d(mpf_, r); + return this; + } +} + +template +knumber_base *knumber_float::execute_libc_func(double x, double y) { + const double r = F(x, y); + if(isnan(r)) { + knumber_error *e = new knumber_error(knumber_error::ERROR_UNDEFINED); + delete this; + return e; + } else if(isinf(r)) { + knumber_error *e = new knumber_error(knumber_error::ERROR_POS_INFINITY); + delete this; + return e; + } else { + mpf_set_d(mpf_, r); + return this; + } +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_float::knumber_float(const QString &s) { + + mpf_init(mpf_); + mpf_set_str(mpf_, s.toAscii(), 10); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_float::knumber_float(double value) { + + Q_ASSERT(!isinf(value)); + Q_ASSERT(!isnan(value)); + + mpf_init_set_d(mpf_, value); +} + +#ifdef HAVE_LONG_DOUBLE +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_float::knumber_float(long double value) { + + Q_ASSERT(!isinf(value)); + Q_ASSERT(!isnan(value)); + + mpf_init_set_d(mpf_, value); +} +#endif + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_float::knumber_float(mpf_t mpf) { + + mpf_init(mpf_); + mpf_set(mpf_, mpf); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_float::knumber_float(const knumber_float *value) { + + mpf_init_set(mpf_, value->mpf_); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_float::knumber_float(const knumber_integer *value) { + + mpf_init(mpf_); + mpf_set_z(mpf_, value->mpz_); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_float::knumber_float(const knumber_fraction *value) { + + mpf_init(mpf_); + mpf_set_q(mpf_, value->mpq_); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::clone() { + + return new knumber_float(this); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_float::~knumber_float() { + + mpf_clear(mpf_); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::add(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + knumber_float f(p); + return add(&f); + } else if(knumber_float *const p = dynamic_cast(rhs)) { + mpf_add(mpf_, mpf_, p->mpf_); + return this; + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_float f(p); + return add(&f); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + knumber_error *e = new knumber_error(p); + delete this; + return e; + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::sub(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + knumber_float f(p); + return sub(&f); + } else if(knumber_float *const p = dynamic_cast(rhs)) { + mpf_sub(mpf_, mpf_, p->mpf_); + return this; + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_float f(p); + return sub(&f); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + knumber_error *e = new knumber_error(p); + delete this; + return e->neg(); + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::mul(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + knumber_float f(p); + return mul(&f); + } else if(knumber_float *const p = dynamic_cast(rhs)) { + mpf_mul(mpf_, mpf_, p->mpf_); + return this; + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_float f(p); + return mul(&f); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + if(is_zero()) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + + if(sign() < 0) { + delete this; + knumber_error *e = new knumber_error(p); + return e->neg(); + } else { + delete this; + return new knumber_error(p); + } + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::div(knumber_base *rhs) { + + if(rhs->is_zero()) { + if(sign() < 0) { + delete this; + return new knumber_error(knumber_error::ERROR_NEG_INFINITY); + } else { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } + } + + if(knumber_integer *const p = dynamic_cast(rhs)) { + knumber_float f(p); + return div(&f); + } else if(knumber_float *const p = dynamic_cast(rhs)) { + mpf_div(mpf_, mpf_, p->mpf_); + return this; + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_float f(p); + return div(&f); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + if(p->sign() > 0 || p->sign() < 0) { + delete this; + return new knumber_integer(0); + } + + delete this; + return new knumber_error(p); + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::mod(knumber_base *rhs) { + + Q_UNUSED(rhs); + + if(rhs->is_zero()) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + + delete this; + return new knumber_integer(0); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::bitwise_and(knumber_base *rhs) { + + Q_UNUSED(rhs); + delete this; + return new knumber_integer(0); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::bitwise_xor(knumber_base *rhs) { + + Q_UNUSED(rhs); + delete this; + return new knumber_integer(0); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::bitwise_or(knumber_base *rhs) { + + Q_UNUSED(rhs); + delete this; + return new knumber_integer(0); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::bitwise_shift(knumber_base *rhs) { + + Q_UNUSED(rhs); + delete this; + // NOTE: we don't support bitwise operations with non-integer operands + return new knumber_error(knumber_error::ERROR_UNDEFINED); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::neg() { + + mpf_neg(mpf_, mpf_); + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::cmp() { + + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::abs() { + + mpf_abs(mpf_, mpf_); + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::sqrt() { + + if(sign() < 0) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_sqrt(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); +#else + mpf_sqrt(mpf_, mpf_); +#endif + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::cbrt() { + +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_cbrt(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { +#ifdef Q_CC_MSVC + return execute_libc_func< ::pow>(x, 1.0 / 3.0); +#else + return execute_libc_func< ::cbrt>(x); +#endif + } +#endif + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::factorial() { + + if(sign() < 0) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + + knumber_integer *i = new knumber_integer(this); + delete this; + return i->factorial(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::sin() { + +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_sin(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::sin>(x); + } +#endif + +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::floor() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_floor(mpfr, mpfr); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::floor>(x); + } +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::ceil() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_ceil(mpfr, mpfr); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::ceil>(x); + } +#endif +} +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::cos() { + +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_cos(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::cos>(x); + } +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::tan() { + +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_tan(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::tan>(x); + } +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::asin() { + if(mpf_cmp_d(mpf_, 1.0) > 0 || mpf_cmp_d(mpf_, -1.0) < 0) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_asin(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::asin>(x); + } +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------s +knumber_base *knumber_float::acos() { + if(mpf_cmp_d(mpf_, 1.0) > 0 || mpf_cmp_d(mpf_, -1.0) < 0) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_acos(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::acos>(x); + } +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::atan() { + +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_atan(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::atan>(x); + } +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::sinh() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_sinh(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + return execute_libc_func< ::sinh>(x); +#endif + +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::cosh() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_cosh(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + return execute_libc_func< ::cosh>(x); +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::tanh() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_tanh(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + return execute_libc_func< ::tanh>(x); +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::tgamma() { + +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_gamma(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + + } else { + return execute_libc_func< ::tgamma>(x); + } +#endif + +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::asinh() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_asinh(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + return execute_libc_func< ::asinh>(x); +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::acosh() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_acosh(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + return execute_libc_func< ::acosh>(x); +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::atanh() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_atanh(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + return execute_libc_func< ::atanh>(x); +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::pow(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + mpf_pow_ui(mpf_, mpf_, mpz_get_ui(p->mpz_)); + + if(p->sign() < 0) { + return reciprocal(); + } else { + return this; + } + } else if(knumber_float *const p = dynamic_cast(rhs)) { + return execute_libc_func< ::pow>(mpf_get_d(mpf_), mpf_get_d(p->mpf_)); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_float f(p); + return execute_libc_func< ::pow>(mpf_get_d(mpf_), mpf_get_d(f.mpf_)); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + if(p->sign() > 0) { + knumber_error *e = new knumber_error(knumber_error::ERROR_POS_INFINITY); + delete this; + return e; + } else if(p->sign() < 0) { + knumber_integer *n = new knumber_integer(0); + delete this; + return n; + } else { + knumber_error *e = new knumber_error(knumber_error::ERROR_UNDEFINED); + delete this; + return e; + } + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +int knumber_float::compare(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + knumber_float f(p); + return compare(&f); + } else if(knumber_float *const p = dynamic_cast(rhs)) { + return mpf_cmp(mpf_, p->mpf_); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_float f(p); + return compare(&f); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + // NOTE: any number compared to NaN/Inf/-Inf always compares less + // at the moment + return -1; + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +QString knumber_float::toString(int precision) const { + + size_t size; + if (precision > 0) { + size = gmp_snprintf(NULL, 0, "%.*Fg", precision, mpf_) + 1; + } else { + size = gmp_snprintf(NULL, 0, "%.Fg", mpf_) + 1; + } + + QScopedArrayPointer buf(new char[size]); + + if (precision > 0) { + gmp_snprintf(&buf[0], size, "%.*Fg", precision, mpf_); + } else { + gmp_snprintf(&buf[0], size, "%.Fg", mpf_); + } + + return QLatin1String(&buf[0]); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +bool knumber_float::is_integer() const { + + return mpf_integer_p(mpf_) != 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +bool knumber_float::is_zero() const { + + return mpf_sgn(mpf_) == 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +int knumber_float::sign() const { + + return mpf_sgn(mpf_); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::reciprocal() { + + mpf_t mpf; + mpf_init_set_d(mpf, 1.0); + mpf_div(mpf_, mpf, mpf_); + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::log2() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_log2(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::log2>(x); + } +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::log10() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_log10(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::log10>(x); + } +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::ln() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_log(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::log>(x); + } +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::exp2() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_exp2(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::exp2>(x); + } +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::exp10() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_exp10(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::pow>(10, x); + } +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::exp() { +#ifdef KNUMBER_USE_MPFR + mpfr_t mpfr; + mpfr_init_set_f(mpfr, mpf_, rounding_mode); + mpfr_exp(mpfr, mpfr, rounding_mode); + mpfr_get_f(mpf_, mpfr, rounding_mode); + mpfr_clear(mpfr); + return this; +#else + const double x = mpf_get_d(mpf_); + if(isinf(x)) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } else { + return execute_libc_func< ::exp>(x); + } +#endif +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +quint64 knumber_float::toUint64() const { + return knumber_integer(this).toUint64(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +qint64 knumber_float::toInt64() const { + return knumber_integer(this).toInt64(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_float::bin(knumber_base *rhs) { + Q_UNUSED(rhs); + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); +} + +} diff --git a/kcalc/knumber/knumber_float.h b/kcalc/knumber/knumber_float.h new file mode 100644 index 00000000..1a4fa454 --- /dev/null +++ b/kcalc/knumber/knumber_float.h @@ -0,0 +1,135 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KNUMBER_FLOAT_H_ +#define KNUMBER_FLOAT_H_ + +#include "knumber_base.h" + +class KNumber; + +namespace detail { + +class knumber_float : public knumber_base { + friend class ::KNumber; + friend class knumber_error; + friend class knumber_integer; + friend class knumber_fraction; + +private: +#ifdef KNUMBER_USE_MPFR + static const mpfr_rnd_t rounding_mode; + static const mpfr_prec_t precision; +#endif + +public: + explicit knumber_float(const QString &s); + explicit knumber_float(double value); +#ifdef HAVE_LONG_DOUBLE + explicit knumber_float(long double value); +#endif + + explicit knumber_float(mpf_t mpf); + virtual ~knumber_float(); + +private: + // conversion constructors + explicit knumber_float(const knumber_integer *value); + explicit knumber_float(const knumber_fraction *value); + explicit knumber_float(const knumber_float *value); + explicit knumber_float(const knumber_error *value); + +public: + virtual QString toString(int precision) const; + virtual quint64 toUint64() const; + virtual qint64 toInt64() const; + +public: + virtual bool is_integer() const; + virtual bool is_zero() const; + virtual int sign() const; + +public: + virtual knumber_base *add(knumber_base *rhs); + virtual knumber_base *sub(knumber_base *rhs); + virtual knumber_base *mul(knumber_base *rhs); + virtual knumber_base *div(knumber_base *rhs); + virtual knumber_base *mod(knumber_base *rhs); + +public: + virtual knumber_base *pow(knumber_base *rhs); + virtual knumber_base *neg(); + virtual knumber_base *cmp(); + virtual knumber_base *abs(); + virtual knumber_base *sqrt(); + virtual knumber_base *cbrt(); + virtual knumber_base *factorial(); + virtual knumber_base *reciprocal(); + virtual knumber_base *tgamma(); + +public: + virtual knumber_base *log2(); + virtual knumber_base *log10(); + virtual knumber_base *ln(); + virtual knumber_base *floor(); + virtual knumber_base *ceil(); + virtual knumber_base *exp2(); + virtual knumber_base *exp10(); + virtual knumber_base *exp(); + virtual knumber_base *bin(knumber_base *rhs); + +public: + virtual knumber_base *sin(); + virtual knumber_base *cos(); + virtual knumber_base *tan(); + virtual knumber_base *asin(); + virtual knumber_base *acos(); + virtual knumber_base *atan(); + virtual knumber_base *sinh(); + virtual knumber_base *cosh(); + virtual knumber_base *tanh(); + virtual knumber_base *asinh(); + virtual knumber_base *acosh(); + virtual knumber_base *atanh(); + +public: + virtual int compare(knumber_base *rhs); + +public: + virtual knumber_base *bitwise_and(knumber_base *rhs); + virtual knumber_base *bitwise_xor(knumber_base *rhs); + virtual knumber_base *bitwise_or(knumber_base *rhs); + virtual knumber_base *bitwise_shift(knumber_base *rhs); + +public: + virtual knumber_base *clone(); + +private: + template + knumber_base *execute_libc_func(double x); + + template + knumber_base *execute_libc_func(double x, double y); + +private: + mpf_t mpf_; +}; + +} + +#endif diff --git a/kcalc/knumber/knumber_fraction.cpp b/kcalc/knumber/knumber_fraction.cpp new file mode 100644 index 00000000..0de73c5e --- /dev/null +++ b/kcalc/knumber/knumber_fraction.cpp @@ -0,0 +1,909 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include +#include "knumber_integer.h" +#include "knumber_float.h" +#include "knumber_fraction.h" +#include "knumber_error.h" +#include +#include + +namespace detail { + +bool knumber_fraction::default_fractional_input = false; +bool knumber_fraction::default_fractional_output = true; +bool knumber_fraction::split_off_integer_for_fraction_output = false; + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +void knumber_fraction::set_default_fractional_input(bool value) { + default_fractional_input = value; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +void knumber_fraction::set_default_fractional_output(bool value) { + default_fractional_output = value; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +void knumber_fraction::set_split_off_integer_for_fraction_output(bool value) { + split_off_integer_for_fraction_output = value; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_fraction::knumber_fraction(const QString &s) { + mpq_init(mpq_); + mpq_set_str(mpq_, s.toAscii(), 10); + mpq_canonicalize(mpq_); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_fraction::knumber_fraction(qint64 num, quint64 den) { + mpq_init(mpq_); + mpq_set_si(mpq_, num, den); + mpq_canonicalize(mpq_); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_fraction::knumber_fraction(quint64 num, quint64 den) { + mpq_init(mpq_); + mpq_set_ui(mpq_, num, den); + mpq_canonicalize(mpq_); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_fraction::knumber_fraction(mpq_t mpq) { + mpq_init(mpq_); + mpq_set(mpq_, mpq); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_fraction::knumber_fraction(const knumber_fraction *value) { + mpq_init(mpq_); + mpq_set(mpq_, value->mpq_); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_fraction::knumber_fraction(const knumber_integer *value) { + mpq_init(mpq_); + mpq_set_z(mpq_, value->mpz_); +} + +#if 0 +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_fraction::knumber_fraction(const knumber_float *value) { + mpq_init(mpq_); + mpq_set_f(mpq_, value->mpf_); +} +#endif + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::clone() { + return new knumber_fraction(this); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_fraction::~knumber_fraction() { + mpq_clear(mpq_); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +bool knumber_fraction::is_integer() const { + return (mpz_cmp_ui(mpq_denref(mpq_), 1) == 0); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::add(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + knumber_fraction q(p); + mpq_add(mpq_, mpq_, q.mpq_); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *f = new knumber_float(this); + delete this; + return f->add(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + mpq_add(mpq_, mpq_, p->mpq_); + return this; + } else if(knumber_error *const p = dynamic_cast(rhs)) { + knumber_error *e = new knumber_error(p); + delete this; + return e; + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::sub(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + knumber_fraction q(p); + mpq_sub(mpq_, mpq_, q.mpq_); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *f = new knumber_float(this); + delete this; + return f->sub(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + mpq_sub(mpq_, mpq_, p->mpq_); + return this; + } else if(knumber_error *const p = dynamic_cast(rhs)) { + knumber_error *e = new knumber_error(p); + delete this; + return e->neg(); + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::mul(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + knumber_fraction q(p); + mpq_mul(mpq_, mpq_, q.mpq_); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *q = new knumber_float(this); + delete this; + return q->mul(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + mpq_mul(mpq_, mpq_, p->mpq_); + return this; + } else if(knumber_error *const p = dynamic_cast(rhs)) { + if(is_zero()) { + delete this; + knumber_error *e = new knumber_error(knumber_error::ERROR_UNDEFINED); + return e; + } + + if(sign() < 0) { + delete this; + knumber_error *e = new knumber_error(p); + return e->neg(); + } else { + delete this; + knumber_error *e = new knumber_error(p); + return e; + } + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::div(knumber_base *rhs) { + + if(rhs->is_zero()) { + if(sign() < 0) { + delete this; + return new knumber_error(knumber_error::ERROR_NEG_INFINITY); + } else { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } + } + + if(knumber_integer *const p = dynamic_cast(rhs)) { + knumber_fraction f(p); + return div(&f); + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *f = new knumber_float(this); + delete this; + return f->div(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + mpq_div(mpq_, mpq_, p->mpq_); + return this; + } else if(knumber_error *const p = dynamic_cast(rhs)) { + + if(p->sign() > 0) { + delete this; + return new knumber_integer(0); + } else if(p->sign() < 0) { + delete this; + return new knumber_integer(0); + } + + knumber_error *e = new knumber_error(p); + delete this; + return e; + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::mod(knumber_base *rhs) { + + if(rhs->is_zero()) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + + // NOTE: we don't support modulus operations with non-integer operands + mpq_set_d(mpq_, 0); + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::bitwise_and(knumber_base *rhs) { + + Q_UNUSED(rhs); + delete this; + // NOTE: we don't support bitwise operations with non-integer operands + return new knumber_integer(0); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::bitwise_xor(knumber_base *rhs) { + + Q_UNUSED(rhs); + delete this; + // NOTE: we don't support bitwise operations with non-integer operands + return new knumber_integer(0); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::bitwise_or(knumber_base *rhs) { + + Q_UNUSED(rhs); + delete this; + // NOTE: we don't support bitwise operations with non-integer operands + return new knumber_integer(0); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::bitwise_shift(knumber_base *rhs) { + + Q_UNUSED(rhs); + delete this; + // NOTE: we don't support bitwise operations with non-integer operands + return new knumber_error(knumber_error::ERROR_UNDEFINED); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::neg() { + mpq_neg(mpq_, mpq_); + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::abs() { + mpq_abs(mpq_, mpq_); + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::cmp() { + + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::sqrt() { + + if(sign() < 0) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + + if(mpz_perfect_square_p(mpq_numref(mpq_)) && mpz_perfect_square_p(mpq_denref(mpq_))) { + mpz_t num; + mpz_t den; + mpz_init(num); + mpz_init(den); + mpq_get_num(num, mpq_); + mpq_get_den(den, mpq_); + mpz_sqrt(num, num); + mpz_sqrt(den, den); + mpq_set_num(mpq_, num); + mpq_set_den(mpq_, den); + mpq_canonicalize(mpq_); + mpz_clear(num); + mpz_clear(den); + return this; + } else { + knumber_float *f = new knumber_float(this); + delete this; + return f->sqrt(); + } +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::cbrt() { + + // TODO: figure out how to properly use mpq_numref/mpq_denref here + mpz_t num; + mpz_t den; + + mpz_init(num); + mpz_init(den); + + mpq_get_num(num, mpq_); + mpq_get_den(den, mpq_); + + if(mpz_root(num, num, 3) && mpz_root(den, den, 3)) { + mpq_set_num(mpq_, num); + mpq_set_den(mpq_, den); + mpq_canonicalize(mpq_); + mpz_clear(num); + mpz_clear(den); + return this; + } else { + mpz_clear(num); + mpz_clear(den); + knumber_float *f = new knumber_float(this); + delete this; + return f->cbrt(); + } +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::factorial() { + + if(sign() < 0) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + + knumber_integer *i = new knumber_integer(this); + delete this; + return i->factorial(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::pow(knumber_base *rhs) { + + // TODO: figure out how to properly use mpq_numref/mpq_denref here + if(knumber_integer *const p = dynamic_cast(rhs)) { + + mpz_t num; + mpz_t den; + + mpz_init(num); + mpz_init(den); + + mpq_get_num(num, mpq_); + mpq_get_den(den, mpq_); + + mpz_pow_ui(num, num, mpz_get_ui(p->mpz_)); + mpz_pow_ui(den, den, mpz_get_ui(p->mpz_)); + mpq_set_num(mpq_, num); + mpq_set_den(mpq_, den); + mpq_canonicalize(mpq_); + mpz_clear(num); + mpz_clear(den); + + if(p->sign() < 0) { + return reciprocal(); + } else { + return this; + } + } else if(knumber_float *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + knumber_float *f = new knumber_float(this); + delete this; + return f->pow(rhs); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + + // ok, so if any part of the number is > 1,000,000, then we risk + // the pow function overflowing... so we'll just convert to float to be safe + // TODO: at some point, we should figure out exactly what the threashold is + // and if there is a better way to determine if the pow function will + // overflow. + if(mpz_cmpabs_ui(mpq_numref(mpq_), 1000000) > 0 || mpz_cmpabs_ui(mpq_denref(mpq_), 1000000) > 0 || mpz_cmpabs_ui(mpq_numref(p->mpq_), 1000000) > 0 || mpz_cmpabs_ui(mpq_denref(p->mpq_), 1000000) > 0) { + knumber_float *f = new knumber_float(this); + delete this; + return f->pow(rhs); + } + + mpz_t lhs_num; + mpz_t lhs_den; + mpz_t rhs_num; + mpz_t rhs_den; + + mpz_init(lhs_num); + mpz_init(lhs_den); + mpz_init(rhs_num); + mpz_init(rhs_den); + + mpq_get_num(lhs_num, mpq_); + mpq_get_den(lhs_den, mpq_); + mpq_get_num(rhs_num, p->mpq_); + mpq_get_den(rhs_den, p->mpq_); + + mpz_pow_ui(lhs_num, lhs_num, mpz_get_ui(rhs_num)); + mpz_pow_ui(lhs_den, lhs_den, mpz_get_ui(rhs_num)); + + if(mpz_sgn(lhs_num) < 0 && mpz_even_p(rhs_den)) { + mpz_clear(lhs_num); + mpz_clear(lhs_den); + mpz_clear(rhs_num); + mpz_clear(rhs_den); + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + + if(mpz_sgn(lhs_den) < 0 && mpz_even_p(rhs_den)) { + mpz_clear(lhs_num); + mpz_clear(lhs_den); + mpz_clear(rhs_num); + mpz_clear(rhs_den); + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + + const int n1 = mpz_root(lhs_num, lhs_num, mpz_get_ui(rhs_den)); + const int n2 = mpz_root(lhs_den, lhs_den, mpz_get_ui(rhs_den)); + + if(n1 && n2) { + + mpq_set_num(mpq_, lhs_num); + mpq_set_den(mpq_, lhs_den); + mpq_canonicalize(mpq_); + mpz_clear(lhs_num); + mpz_clear(lhs_den); + mpz_clear(rhs_num); + mpz_clear(rhs_den); + + if(p->sign() < 0) { + return reciprocal(); + } else { + return this; + } + } else { + mpz_clear(lhs_num); + mpz_clear(lhs_den); + mpz_clear(rhs_num); + mpz_clear(rhs_den); + knumber_float *f = new knumber_float(this); + delete this; + + return f->pow(rhs); + } + + } else if(knumber_error *const p = dynamic_cast(rhs)) { + if(p->sign() > 0) { + knumber_error *e = new knumber_error(knumber_error::ERROR_POS_INFINITY); + delete this; + return e; + } else if(p->sign() < 0) { + knumber_integer *n = new knumber_integer(0); + delete this; + return n; + } else { + knumber_error *e = new knumber_error(knumber_error::ERROR_UNDEFINED); + delete this; + return e; + } + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::sin() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->sin(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::floor() { + knumber_float *f = new knumber_float(this); + delete this; + return f->floor(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::ceil() { + knumber_float *f = new knumber_float(this); + delete this; + return f->ceil(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::cos() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->cos(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::tgamma() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->tgamma(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::tan() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->tan(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::asin() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->asin(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::acos() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->acos(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::atan() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->atan(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::sinh() { + knumber_float *f = new knumber_float(this); + delete this; + return f->sinh(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::cosh() { + knumber_float *f = new knumber_float(this); + delete this; + return f->cosh(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::tanh() { + knumber_float *f = new knumber_float(this); + delete this; + return f->tanh(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::asinh() { + knumber_float *f = new knumber_float(this); + delete this; + return f->asinh(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::acosh() { + knumber_float *f = new knumber_float(this); + delete this; + return f->acosh(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::atanh() { + knumber_float *f = new knumber_float(this); + delete this; + return f->atanh(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +int knumber_fraction::compare(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + knumber_fraction f(p); + return mpq_cmp(mpq_, f.mpq_); + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float f(this); + return f.compare(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + return mpq_cmp(mpq_, p->mpq_); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + // NOTE: any number compared to NaN/Inf/-Inf always compares less + // at the moment + return -1; + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +QString knumber_fraction::toString(int precision) const { + + + if(knumber_fraction::default_fractional_output) { + + // TODO: figure out how to properly use mpq_numref/mpq_denref here + + knumber_integer integer_part(this); + if(split_off_integer_for_fraction_output && !integer_part.is_zero()) { + + mpz_t num; + mpz_init(num); + mpq_get_num(num, mpq_); + + knumber_integer integer_part_1(this); + + mpz_mul(integer_part.mpz_, integer_part.mpz_, mpq_denref(mpq_)); + mpz_sub(num, num, integer_part.mpz_); + + if(mpz_sgn(num) < 0) { + mpz_neg(num, num); + } + + const size_t size = gmp_snprintf(NULL, 0, "%Zd %Zd/%Zd", integer_part_1.mpz_, num, mpq_denref(mpq_)) + 1; + QScopedArrayPointer buf(new char[size]); + gmp_snprintf(&buf[0], size, "%Zd %Zd/%Zd", integer_part_1.mpz_, num, mpq_denref(mpq_)); + + mpz_clear(num); + + return QLatin1String(&buf[0]); + } else { + + mpz_t num; + mpz_init(num); + mpq_get_num(num, mpq_); + + const size_t size = gmp_snprintf(NULL, 0, "%Zd/%Zd", num, mpq_denref(mpq_)) + 1; + QScopedArrayPointer buf(new char[size]); + gmp_snprintf(&buf[0], size, "%Zd/%Zd", num, mpq_denref(mpq_)); + + mpz_clear(num); + + return QLatin1String(&buf[0]); + } + } else { + return knumber_float(this).toString(precision); + } +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +bool knumber_fraction::is_zero() const { + return mpq_sgn(mpq_) == 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +int knumber_fraction::sign() const { + return mpq_sgn(mpq_); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::reciprocal() { + + mpq_inv(mpq_, mpq_); + return this; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_integer *knumber_fraction::numerator() const { + + mpz_t num; + mpz_init(num); + mpq_get_num(num, mpq_); + knumber_integer *n = new knumber_integer(num); + mpz_clear(num); + return n; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_integer *knumber_fraction::denominator() const { + + mpz_t den; + mpz_init(den); + mpq_get_den(den, mpq_); + knumber_integer *n = new knumber_integer(den); + mpz_clear(den); + return n; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::log2() { + knumber_float *f = new knumber_float(this); + delete this; + return f->log2(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::log10() { + knumber_float *f = new knumber_float(this); + delete this; + return f->log10(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::ln() { + knumber_float *f = new knumber_float(this); + delete this; + return f->ln(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::exp2() { + knumber_float *f = new knumber_float(this); + delete this; + return f->exp2(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::exp10() { + knumber_float *f = new knumber_float(this); + delete this; + return f->exp10(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::exp() { + knumber_float *f = new knumber_float(this); + delete this; + return f->exp(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +quint64 knumber_fraction::toUint64() const { + return knumber_integer(this).toUint64(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +qint64 knumber_fraction::toInt64() const { + return knumber_integer(this).toInt64(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_fraction::bin(knumber_base *rhs) { + Q_UNUSED(rhs); + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + +} + +} diff --git a/kcalc/knumber/knumber_fraction.h b/kcalc/knumber/knumber_fraction.h new file mode 100644 index 00000000..9c50b656 --- /dev/null +++ b/kcalc/knumber/knumber_fraction.h @@ -0,0 +1,137 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KNUMBER_FRACTION_H_ +#define KNUMBER_FRACTION_H_ + +#include "knumber_base.h" + +class KNumber; + +namespace detail { + +class knumber_fraction : public knumber_base { + friend class ::KNumber; + friend class knumber_error; + friend class knumber_integer; + friend class knumber_float; + +public: + static bool default_fractional_input; + static bool default_fractional_output; + static bool split_off_integer_for_fraction_output; + +public: + static void set_default_fractional_input(bool value); + static void set_default_fractional_output(bool value); + static void set_split_off_integer_for_fraction_output(bool value); + +public: + explicit knumber_fraction(const QString &s); + knumber_fraction(qint64 num, quint64 den); + knumber_fraction(quint64 num, quint64 den); + explicit knumber_fraction(mpq_t mpq); + virtual ~knumber_fraction(); + +public: + virtual knumber_base *clone(); + +public: + virtual QString toString(int precision) const; + virtual quint64 toUint64() const; + virtual qint64 toInt64() const; + +public: + virtual bool is_integer() const; + virtual bool is_zero() const; + virtual int sign() const; + +public: + virtual knumber_base *add(knumber_base *rhs); + virtual knumber_base *sub(knumber_base *rhs); + virtual knumber_base *mul(knumber_base *rhs); + virtual knumber_base *div(knumber_base *rhs); + virtual knumber_base *mod(knumber_base *rhs); + +public: + virtual knumber_base *bitwise_and(knumber_base *rhs); + virtual knumber_base *bitwise_xor(knumber_base *rhs); + virtual knumber_base *bitwise_or(knumber_base *rhs); + virtual knumber_base *bitwise_shift(knumber_base *rhs); + +public: + virtual knumber_base *pow(knumber_base *rhs); + virtual knumber_base *neg(); + virtual knumber_base *cmp(); + virtual knumber_base *abs(); + virtual knumber_base *sqrt(); + virtual knumber_base *cbrt(); + virtual knumber_base *factorial(); + virtual knumber_base *reciprocal(); + virtual knumber_base *tgamma(); + +public: + virtual knumber_base *log2(); + virtual knumber_base *log10(); + virtual knumber_base *ln(); + virtual knumber_base *exp2(); + virtual knumber_base *floor(); + virtual knumber_base *ceil(); + virtual knumber_base *exp10(); + virtual knumber_base *exp(); + virtual knumber_base *bin(knumber_base *rhs); + +public: + virtual knumber_base *sin(); + virtual knumber_base *cos(); + virtual knumber_base *tan(); + virtual knumber_base *asin(); + virtual knumber_base *acos(); + virtual knumber_base *atan(); + virtual knumber_base *sinh(); + virtual knumber_base *cosh(); + virtual knumber_base *tanh(); + virtual knumber_base *asinh(); + virtual knumber_base *acosh(); + virtual knumber_base *atanh(); + +public: + virtual int compare(knumber_base *rhs); + +private: + knumber_integer *numerator() const; + knumber_integer *denominator() const; + +private: + // conversion constructors + explicit knumber_fraction(const knumber_integer *value); + explicit knumber_fraction(const knumber_fraction *value); +#if 0 + // TODO: this is omitted because there is no good way to + // implement it + knumber_fraction(const knumber_float *value); +#endif + explicit knumber_fraction(const knumber_error *value); + +private: + mpq_t mpq_; +}; + +} + +#endif diff --git a/kcalc/knumber/knumber_integer.cpp b/kcalc/knumber/knumber_integer.cpp new file mode 100644 index 00000000..bf3c70a1 --- /dev/null +++ b/kcalc/knumber/knumber_integer.cpp @@ -0,0 +1,888 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include +#include "knumber_integer.h" +#include "knumber_float.h" +#include "knumber_fraction.h" +#include "knumber_error.h" +#include +#include + +namespace detail { + +//------------------------------------------------------------------------------ +// Name: knumber_integer +//------------------------------------------------------------------------------ +knumber_integer::knumber_integer(const QString &s) { + mpz_init(mpz_); + mpz_set_str(mpz_, s.toAscii(), 10); +} + +//------------------------------------------------------------------------------ +// Name: knumber_integer +//------------------------------------------------------------------------------ +knumber_integer::knumber_integer(qint32 value) { + mpz_init_set_si(mpz_, static_cast(value)); +} + +//------------------------------------------------------------------------------ +// Name: knumber_integer +//------------------------------------------------------------------------------ +knumber_integer::knumber_integer(qint64 value) { + mpz_init(mpz_); +#if SIZEOF_SIGNED_LONG == 8 + mpz_set_si(mpz_, static_cast(value)); +#elif SIZEOF_SIGNED_LONG == 4 + mpz_set_si(mpz_, static_cast(value >> 32)); + mpz_mul_2exp(mpz_, mpz_, 32); + mpz_add_ui(mpz_, mpz_, static_cast(value)); +#else +#error "SIZEOF_SIGNED_LONG is a unhandled case" +#endif +} + +//------------------------------------------------------------------------------ +// Name: knumber_integer +//------------------------------------------------------------------------------ +knumber_integer::knumber_integer(quint32 value) { + mpz_init_set_ui(mpz_, static_cast(value)); +} + +//------------------------------------------------------------------------------ +// Name: knumber_integer +//------------------------------------------------------------------------------ +knumber_integer::knumber_integer(quint64 value) { + mpz_init(mpz_); +#if SIZEOF_UNSIGNED_LONG == 8 + mpz_set_ui(mpz_, static_cast(value)); +#elif SIZEOF_UNSIGNED_LONG == 4 + mpz_set_ui(mpz_, static_cast(value >> 32)); + mpz_mul_2exp(mpz_, mpz_, 32); + mpz_add_ui(mpz_, mpz_, static_cast(value)); +#else +#error "SIZEOF_UNSIGNED_LONG is a unhandled case" +#endif +} + +//------------------------------------------------------------------------------ +// Name: knumber_integer +//------------------------------------------------------------------------------ +knumber_integer::knumber_integer(mpz_t mpz) { + mpz_init_set(mpz_, mpz); +} + +//------------------------------------------------------------------------------ +// Name: knumber_integer +//------------------------------------------------------------------------------ +knumber_integer::knumber_integer(const knumber_integer *value) { + mpz_init_set(mpz_, value->mpz_); +} + +//------------------------------------------------------------------------------ +// Name: knumber_integer +//------------------------------------------------------------------------------ +knumber_integer::knumber_integer(const knumber_float *value) { + mpz_init(mpz_); + mpz_set_f(mpz_, value->mpf_); +} + +//------------------------------------------------------------------------------ +// Name: knumber_integer +//------------------------------------------------------------------------------ +knumber_integer::knumber_integer(const knumber_fraction *value) { + mpz_init(mpz_); + mpz_set_q(mpz_, value->mpq_); +} + +//------------------------------------------------------------------------------ +// Name: clone +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::clone() { + return new knumber_integer(this); +} + +//------------------------------------------------------------------------------ +// Name: ~knumber_integer +//------------------------------------------------------------------------------ +knumber_integer::~knumber_integer() { + mpz_clear(mpz_); +} + +//------------------------------------------------------------------------------ +// Name: add +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::add(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + mpz_add(mpz_, mpz_, p->mpz_); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *const f = new knumber_float(this); + delete this; + return f->add(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_fraction *const q = new knumber_fraction(this); + delete this; + return q->add(p); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + delete this; + return p->clone(); + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: sub +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::sub(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + mpz_sub(mpz_, mpz_, p->mpz_); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *f = new knumber_float(this); + delete this; + return f->sub(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_fraction *q = new knumber_fraction(this); + delete this; + return q->sub(p); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + knumber_base *e = p->clone(); + delete this; + return e->neg(); + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: mul +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::mul(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + mpz_mul(mpz_, mpz_, p->mpz_); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *f = new knumber_float(this); + delete this; + return f->mul(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_fraction *q = new knumber_fraction(this); + delete this; + return q->mul(p); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + + if(is_zero()) { + delete this; + knumber_error *e = new knumber_error(knumber_error::ERROR_UNDEFINED); + return e->neg(); + } + + if(sign() < 0) { + delete this; + knumber_base *e = p->clone(); + return e->neg(); + } else { + delete this; + return p->clone(); + } + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: div +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::div(knumber_base *rhs) { + + if(rhs->is_zero()) { + if(sign() < 0) { + delete this; + return new knumber_error(knumber_error::ERROR_NEG_INFINITY); + } else { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } + } + + if(knumber_integer *const p = dynamic_cast(rhs)) { + knumber_fraction *q = new knumber_fraction(this); + delete this; + return q->div(p); + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *f = new knumber_float(this); + delete this; + return f->div(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_fraction *q = new knumber_fraction(this); + delete this; + return q->div(p); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + + if(p->sign() > 0) { + delete this; + return new knumber_integer(0); + } else if(p->sign() < 0) { + delete this; + return new knumber_integer(0); + } + + delete this; + return p->clone(); + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: mod +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::mod(knumber_base *rhs) { + + if(rhs->is_zero()) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + + if(knumber_integer *const p = dynamic_cast(rhs)) { + mpz_mod(mpz_, mpz_, p->mpz_); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *f = new knumber_float(this); + delete this; + return f->mod(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_fraction *q = new knumber_fraction(this); + delete this; + return q->mod(p); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + delete this; + return p->clone(); + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: bitwise_and +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::bitwise_and(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + mpz_and(mpz_, mpz_, p->mpz_); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *f = new knumber_float(this); + delete this; + return f->bitwise_and(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_fraction *f = new knumber_fraction(this); + delete this; + return f->bitwise_and(p); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + delete this; + return p->clone(); + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: bitwise_xor +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::bitwise_xor(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + mpz_xor(mpz_, mpz_, p->mpz_); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *f = new knumber_float(this); + delete this; + return f->bitwise_xor(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_fraction *f = new knumber_fraction(this); + delete this; + return f->bitwise_xor(p); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + delete this; + return p->clone(); + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: bitwise_or +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::bitwise_or(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + mpz_ior(mpz_, mpz_, p->mpz_); + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *f = new knumber_float(this); + delete this; + return f->bitwise_or(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_fraction *f = new knumber_fraction(this); + delete this; + return f->bitwise_or(p); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + delete this; + return p->clone(); + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: bitwise_shift +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::bitwise_shift(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + + const signed long int bit_count = mpz_get_si(p->mpz_); + + // TODO: left shift with high bit set is broken in + // non decimal modes :-/, always displays 0 + // interestingly, the bit is not "lost" + // we simply don't have a mechanism to display + // values in HEX/DEC/OCT mode which are greater than + // 64-bits + + if(bit_count > 0) { + // left shift + mpz_mul_2exp(mpz_, mpz_, bit_count); + } else if(bit_count < 0) { + // right shift + if(mpz_sgn(mpz_) < 0) { + mpz_fdiv_q_2exp(mpz_, mpz_, -bit_count); + } else { + mpz_tdiv_q_2exp(mpz_, mpz_, -bit_count); + } + } + return this; + } else if(knumber_float *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + knumber_error *e = new knumber_error(knumber_error::ERROR_UNDEFINED); + delete this; + return e; + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + knumber_error *e = new knumber_error(knumber_error::ERROR_UNDEFINED); + delete this; + return e; + } else if(knumber_error *const p = dynamic_cast(rhs)) { + Q_UNUSED(p); + knumber_error *e = new knumber_error(knumber_error::ERROR_UNDEFINED); + delete this; + return e; + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: neg +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::neg() { + + mpz_neg(mpz_, mpz_); + return this; +} + +//------------------------------------------------------------------------------ +// Name: cmp +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::cmp() { + +#if 0 + // unfortunately this breaks things pretty badly + // for non-decimal modes :-( + mpz_com(mpz_, mpz_); +#else + mpz_swap(mpz_, knumber_integer(~toUint64()).mpz_); +#endif + return this; +} + +//------------------------------------------------------------------------------ +// Name: abs +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::abs() { + + mpz_abs(mpz_, mpz_); + return this; +} + +//------------------------------------------------------------------------------ +// Name: sqrt +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::sqrt() { + + if(sign() < 0) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + + if(mpz_perfect_square_p(mpz_)) { + mpz_sqrt(mpz_, mpz_); + return this; + } else { + knumber_float *f = new knumber_float(this); + delete this; + return f->sqrt(); + } +} + +//------------------------------------------------------------------------------ +// Name: cbrt +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::cbrt() { + + mpz_t x; + mpz_init_set(x, mpz_); + if(mpz_root(x, x, 3)) { + mpz_swap(mpz_, x); + mpz_clear(x); + return this; + } + + mpz_clear(x); + knumber_float *f = new knumber_float(this); + delete this; + return f->cbrt(); +} + +//------------------------------------------------------------------------------ +// Name: pow +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::pow(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + + if(is_zero() && p->is_even() && p->sign() < 0) { + delete this; + return new knumber_error(knumber_error::ERROR_POS_INFINITY); + } + + mpz_pow_ui(mpz_, mpz_, mpz_get_ui(p->mpz_)); + + if(p->sign() < 0) { + return reciprocal(); + } else { + return this; + } + } else if(knumber_float *const p = dynamic_cast(rhs)) { + knumber_float *f = new knumber_float(this); + delete this; + return f->pow(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + knumber_fraction *f = new knumber_fraction(this); + delete this; + return f->pow(p); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + if(p->sign() > 0) { + knumber_error *e = new knumber_error(knumber_error::ERROR_POS_INFINITY); + delete this; + return e; + } else if(p->sign() < 0) { + mpz_init_set_si(mpz_, 0); + return this; + } else { + knumber_error *e = new knumber_error(knumber_error::ERROR_UNDEFINED); + delete this; + return e; + } + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: sin +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::sin() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->sin(); +} + +//------------------------------------------------------------------------------ +// Name: cos +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::cos() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->cos(); +} + +//------------------------------------------------------------------------------ +// Name: tan +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::tan() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->tan(); +} + +//------------------------------------------------------------------------------ +// Name: asin +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::asin() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->asin(); +} + +//------------------------------------------------------------------------------ +// Name: acos +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::acos() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->acos(); +} + +//------------------------------------------------------------------------------ +// Name: atan +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::atan() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->atan(); +} + +//------------------------------------------------------------------------------ +// Name: tgamma +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::tgamma() { + + knumber_float *f = new knumber_float(this); + delete this; + return f->tgamma(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::sinh() { + knumber_float *f = new knumber_float(this); + delete this; + return f->sinh(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::cosh() { + knumber_float *f = new knumber_float(this); + delete this; + return f->cosh(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::tanh() { + knumber_float *f = new knumber_float(this); + delete this; + return f->tanh(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::asinh() { + knumber_float *f = new knumber_float(this); + delete this; + return f->asinh(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::acosh() { + knumber_float *f = new knumber_float(this); + delete this; + return f->acosh(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::atanh() { + knumber_float *f = new knumber_float(this); + delete this; + return f->atanh(); +} + +//------------------------------------------------------------------------------ +// Name: factorial +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::factorial() { + + if(sign() < 0) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + + mpz_fac_ui(mpz_, mpz_get_ui(mpz_)); + return this; +} + +//------------------------------------------------------------------------------ +// Name: compare +//------------------------------------------------------------------------------ +int knumber_integer::compare(knumber_base *rhs) { + + if(knumber_integer *const p = dynamic_cast(rhs)) { + return mpz_cmp(mpz_, p->mpz_); + } else if(knumber_float *const p = dynamic_cast(rhs)) { + return knumber_float(this).compare(p); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + return knumber_fraction(this).compare(p); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + // NOTE: any number compared to NaN/Inf/-Inf always compares less + // at the moment + return -1; + } + + Q_ASSERT(0); + return 0; +} + +//------------------------------------------------------------------------------ +// Name: toString +//------------------------------------------------------------------------------ +QString knumber_integer::toString(int precision) const { + + Q_UNUSED(precision); + + const size_t size = gmp_snprintf(NULL, 0, "%Zd", mpz_) + 1; + QScopedArrayPointer buf(new char[size]); + gmp_snprintf(&buf[0], size, "%Zd", mpz_); + return QLatin1String(&buf[0]); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +quint64 knumber_integer::toUint64() const { + // libgmp doesn't have unsigned long long conversion + // so convert to string and then to unsigned long long + const QString tmpstring = toString(-1); + + bool ok; + quint64 value; + + if (sign() < 0) { + const qint64 signedvalue = tmpstring.toLongLong(&ok, 10); + value = static_cast(signedvalue); + } else { + value = tmpstring.toULongLong(&ok, 10); + } + + if (!ok) { + // TODO: what to do if error? + value = 0; + } + return value; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +qint64 knumber_integer::toInt64() const { + // libgmp doesn't have long long conversion + // so convert to string and then to long long + const QString tmpstring = toString(-1); + + bool ok; + qint64 value = tmpstring.toLongLong(&ok, 10); + + if (!ok) { + // TODO: what to do if error? + value = 0; + } + + return value; +} + +//------------------------------------------------------------------------------ +// Name: is_integer +//------------------------------------------------------------------------------ +bool knumber_integer::is_integer() const { + return true; +} + +//------------------------------------------------------------------------------ +// Name: is_zero +//------------------------------------------------------------------------------ +bool knumber_integer::is_zero() const { + return mpz_sgn(mpz_) == 0; +} + +//------------------------------------------------------------------------------ +// Name: sign +//------------------------------------------------------------------------------ +int knumber_integer::sign() const { + return mpz_sgn(mpz_); +} + +//------------------------------------------------------------------------------ +// Name: is_even +//------------------------------------------------------------------------------ +bool knumber_integer::is_even() const { + return mpz_even_p(mpz_); +} + +//------------------------------------------------------------------------------ +// Name: is_odd +//------------------------------------------------------------------------------ +bool knumber_integer::is_odd() const { + return mpz_odd_p(mpz_); +} + +//------------------------------------------------------------------------------ +// Name: reciprocal +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::reciprocal() { + knumber_fraction *q = new knumber_fraction(this); + delete this; + return q->reciprocal(); +} + +//------------------------------------------------------------------------------ +// Name: log2 +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::log2() { + knumber_float *f = new knumber_float(this); + delete this; + return f->log2(); +} + +//------------------------------------------------------------------------------ +// Name: floor +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::floor() { + // should have no effect on the value + return this; +} + +//------------------------------------------------------------------------------ +// Name: ceil +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::ceil() { + // should have no effect on the value + return this; +} + +//------------------------------------------------------------------------------ +// Name: log10 +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::log10() { + knumber_float *f = new knumber_float(this); + delete this; + return f->log10(); +} + +//------------------------------------------------------------------------------ +// Name: ln +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::ln() { + knumber_float *f = new knumber_float(this); + delete this; + return f->ln(); +} + +//------------------------------------------------------------------------------ +// Name: exp2 +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::exp2() { + knumber_float *f = new knumber_float(this); + delete this; + return f->exp2(); +} + +//------------------------------------------------------------------------------ +// Name: exp10 +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::exp10() { + knumber_float *f = new knumber_float(this); + delete this; + return f->exp10(); +} + +//------------------------------------------------------------------------------ +// Name: exp +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::exp() { + knumber_float *f = new knumber_float(this); + delete this; + return f->exp(); +} + +//------------------------------------------------------------------------------ +// Name: bin +//------------------------------------------------------------------------------ +knumber_base *knumber_integer::bin(knumber_base *rhs) { + + + if(knumber_integer *const p = dynamic_cast(rhs)) { + mpz_bin_ui(mpz_, mpz_, mpz_get_ui(p->mpz_)); + return this; + + } else if(knumber_float *const p = dynamic_cast(rhs)) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } else if(knumber_fraction *const p = dynamic_cast(rhs)) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } else if(knumber_error *const p = dynamic_cast(rhs)) { + delete this; + return new knumber_error(knumber_error::ERROR_UNDEFINED); + } + + Q_ASSERT(0); + return 0; +} + +} diff --git a/kcalc/knumber/knumber_integer.h b/kcalc/knumber/knumber_integer.h new file mode 100644 index 00000000..423a0d6d --- /dev/null +++ b/kcalc/knumber/knumber_integer.h @@ -0,0 +1,123 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KNUMBER_INTEGER_H_ +#define KNUMBER_INTEGER_H_ + +#include "knumber_base.h" + +class KNumber; + +namespace detail { + +class knumber_integer : public knumber_base { + friend class ::KNumber; + friend class knumber_error; + friend class knumber_fraction; + friend class knumber_float; + +public: + explicit knumber_integer(const QString &s); + explicit knumber_integer(qint32 value); + explicit knumber_integer(qint64 value); + explicit knumber_integer(quint32 value); + explicit knumber_integer(quint64 value); + explicit knumber_integer(mpz_t mpz); + virtual ~knumber_integer(); + +public: + virtual knumber_base *clone(); + +public: + virtual QString toString(int precision) const; + virtual quint64 toUint64() const; + virtual qint64 toInt64() const; + +public: + virtual bool is_even() const; + virtual bool is_odd() const; + virtual bool is_integer() const; + virtual bool is_zero() const; + virtual int sign() const; + +public: + virtual knumber_base *add(knumber_base *rhs); + virtual knumber_base *sub(knumber_base *rhs); + virtual knumber_base *mul(knumber_base *rhs); + virtual knumber_base *div(knumber_base *rhs); + virtual knumber_base *mod(knumber_base *rhs); + +public: + virtual knumber_base *bitwise_and(knumber_base *rhs); + virtual knumber_base *bitwise_xor(knumber_base *rhs); + virtual knumber_base *bitwise_or(knumber_base *rhs); + virtual knumber_base *bitwise_shift(knumber_base *rhs); + +public: + virtual knumber_base *pow(knumber_base *rhs); + virtual knumber_base *neg(); + virtual knumber_base *cmp(); + virtual knumber_base *abs(); + virtual knumber_base *sqrt(); + virtual knumber_base *cbrt(); + virtual knumber_base *factorial(); + virtual knumber_base *reciprocal(); + +public: + virtual knumber_base *log2(); + virtual knumber_base *log10(); + virtual knumber_base *ln(); + virtual knumber_base *exp2(); + virtual knumber_base *floor(); + virtual knumber_base *ceil(); + virtual knumber_base *exp10(); + virtual knumber_base *exp(); + virtual knumber_base *bin(knumber_base *rhs); + +public: + virtual knumber_base *sin(); + virtual knumber_base *cos(); + virtual knumber_base *tan(); + virtual knumber_base *asin(); + virtual knumber_base *acos(); + virtual knumber_base *atan(); + virtual knumber_base *sinh(); + virtual knumber_base *cosh(); + virtual knumber_base *tanh(); + virtual knumber_base *asinh(); + virtual knumber_base *acosh(); + virtual knumber_base *atanh(); + virtual knumber_base *tgamma(); + +public: + virtual int compare(knumber_base *rhs); + +private: + // conversion constructors + explicit knumber_integer(const knumber_integer *value); + explicit knumber_integer(const knumber_fraction *value); + explicit knumber_integer(const knumber_float *value); + explicit knumber_integer(const knumber_error *value); + +private: + mpz_t mpz_; +}; + +} + +#endif diff --git a/kcalc/knumber/knumber_operators.cpp b/kcalc/knumber/knumber_operators.cpp new file mode 100644 index 00000000..e86385ae --- /dev/null +++ b/kcalc/knumber/knumber_operators.cpp @@ -0,0 +1,296 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include +#include "knumber_operators.h" +#include "knumber.h" +#include "knumber_base.h" +#include + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber operator+(const KNumber &lhs, const KNumber &rhs) { + KNumber x(lhs); + x += rhs; + return x; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber operator-(const KNumber &lhs, const KNumber &rhs) { + KNumber x(lhs); + x -= rhs; + return x; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber operator*(const KNumber &lhs, const KNumber &rhs) { + KNumber x(lhs); + x *= rhs; + return x; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber operator/(const KNumber &lhs, const KNumber &rhs) { + KNumber x(lhs); + x /= rhs; + return x; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber operator%(const KNumber &lhs, const KNumber &rhs) { + KNumber x(lhs); + x %= rhs; + return x; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber operator&(const KNumber &lhs, const KNumber &rhs) { + KNumber x(lhs); + x &= rhs; + return x; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber operator|(const KNumber &lhs, const KNumber &rhs) { + KNumber x(lhs); + x |= rhs; + return x; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber operator^(const KNumber &lhs, const KNumber &rhs) { + KNumber x(lhs); + x ^= rhs; + return x; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber operator>>(const KNumber &lhs, const KNumber &rhs) { + KNumber x(lhs); + x >>= rhs; + return x; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber operator<<(const KNumber &lhs, const KNumber &rhs) { + KNumber x(lhs); + x <<= rhs; + return x; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber abs(const KNumber &x) { + return x.abs(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber cbrt(const KNumber &x) { + return x.cbrt(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber sqrt(const KNumber &x) { + return x.sqrt(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber sin(const KNumber &x) { + return x.sin(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber cos(const KNumber &x) { + return x.cos(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber tan(const KNumber &x) { + return x.tan(); +} + + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber asin(const KNumber &x) { + return x.asin(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber acos(const KNumber &x) { + return x.acos(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber tgamma(const KNumber &x) { + return x.tgamma(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber atan(const KNumber &x) { + return x.atan(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber pow(const KNumber &x, const KNumber &y) { + return x.pow(y); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber factorial(const KNumber &x) { + return x.factorial(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber log2(const KNumber &x) { + return x.log2(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber floor(const KNumber &x) { + return x.floor(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber ceil(const KNumber &x) { + return x.ceil(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber log10(const KNumber &x) { + return x.log10(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber ln(const KNumber &x) { + return x.ln(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber exp2(const KNumber &x) { + return x.exp2(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber exp10(const KNumber &x) { + return x.exp10(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +KNumber exp(const KNumber &x) { + return x.exp(); +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +bool operator==(const KNumber &lhs, const KNumber &rhs) { + return lhs.value_->compare(rhs.value_) == 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +bool operator!=(const KNumber &lhs, const KNumber &rhs) { + return lhs.value_->compare(rhs.value_) != 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +bool operator>=(const KNumber &lhs, const KNumber &rhs) { + return lhs.value_->compare(rhs.value_) >= 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +bool operator<=(const KNumber &lhs, const KNumber &rhs) { + return lhs.value_->compare(rhs.value_) <= 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +bool operator>(const KNumber &lhs, const KNumber &rhs) { + return lhs.value_->compare(rhs.value_) > 0; +} + +//------------------------------------------------------------------------------ +// Name: +//------------------------------------------------------------------------------ +bool operator<(const KNumber &lhs, const KNumber &rhs) { + return lhs.value_->compare(rhs.value_) < 0; +} diff --git a/kcalc/knumber/knumber_operators.h b/kcalc/knumber/knumber_operators.h new file mode 100644 index 00000000..b6912ee4 --- /dev/null +++ b/kcalc/knumber/knumber_operators.h @@ -0,0 +1,67 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KNUMBER_OPERATORS_H_ +#define KNUMBER_OPERATORS_H_ + +class KNumber; + +bool operator==(const KNumber &lhs, const KNumber &rhs); +bool operator!=(const KNumber &lhs, const KNumber &rhs); +bool operator>=(const KNumber &lhs, const KNumber &rhs); +bool operator<=(const KNumber &lhs, const KNumber &rhs); +bool operator>(const KNumber &lhs, const KNumber &rhs); +bool operator<(const KNumber &lhs, const KNumber &rhs); + +KNumber operator+(const KNumber &lhs, const KNumber &rhs); +KNumber operator-(const KNumber &lhs, const KNumber &rhs); +KNumber operator*(const KNumber &lhs, const KNumber &rhs); +KNumber operator/(const KNumber &lhs, const KNumber &rhs); +KNumber operator%(const KNumber &lhs, const KNumber &rhs); + +KNumber operator&(const KNumber &lhs, const KNumber &rhs); +KNumber operator|(const KNumber &lhs, const KNumber &rhs); +KNumber operator^(const KNumber &lhs, const KNumber &rhs); +KNumber operator>>(const KNumber &lhs, const KNumber &rhs); +KNumber operator<<(const KNumber &lhs, const KNumber &rhs); + +KNumber abs(const KNumber &x); +KNumber cbrt(const KNumber &x); +KNumber sqrt(const KNumber &x); +KNumber pow(const KNumber &x, const KNumber &y); + +KNumber sin(const KNumber &x); +KNumber cos(const KNumber &x); +KNumber tan(const KNumber &x); +KNumber asin(const KNumber &x); +KNumber tgamma(const KNumber &x); +KNumber acos(const KNumber &x); +KNumber atan(const KNumber &x); + +KNumber factorial(const KNumber &x); + +KNumber log2(const KNumber &x); +KNumber log10(const KNumber &x); +KNumber ceil(const KNumber &x); +KNumber floor(const KNumber &x); +KNumber ln(const KNumber &x); +KNumber exp2(const KNumber &x); +KNumber exp10(const KNumber &x); +KNumber exp(const KNumber &x); + +#endif diff --git a/kcalc/knumber/tests/CMakeLists.txt b/kcalc/knumber/tests/CMakeLists.txt new file mode 100644 index 00000000..d309244f --- /dev/null +++ b/kcalc/knumber/tests/CMakeLists.txt @@ -0,0 +1,8 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/knumber ) + +set(knumbertest_SRCS knumbertest.cpp ${libknumber_la_SRCS}) + +kde4_add_unit_test(knumbertest TESTNAME KNumber ${knumbertest_SRCS}) + +target_link_libraries(knumbertest ${KDE4_KDECORE_LIBS} ${GMP_LIBRARIES}) diff --git a/kcalc/knumber/tests/knumbertest.cpp b/kcalc/knumber/tests/knumbertest.cpp new file mode 100644 index 00000000..e077f55e --- /dev/null +++ b/kcalc/knumber/tests/knumbertest.cpp @@ -0,0 +1,857 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 2003 - 2005 Klaus Niederkrueger + kniederk@math.uni-koeln.de + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "knumber.h" +#include +#include +#include +#include + +namespace { +const int precision = 12; + +QString numtypeToString(int arg) { + switch (arg) { + case 0: + return QLatin1String("Special"); + case 1: + return QLatin1String("Integer"); + case 3: + return QLatin1String("Fraction"); + case 2: + return QLatin1String("Float"); + default: + return QLatin1String("Unknown:") + QString::number(arg); + } +} + +void checkResult(const QString &string, const KNumber &result, const QString &desired_string, int desired) { + + std::cout + << "Testing result of: " + << qPrintable(string) + << " should give " + << qPrintable(desired_string) + << " and gives " + << qPrintable(result.toQString(precision)) + << " ...\n"; + + std::cout + << "The type of the result should be " + << qPrintable(numtypeToString(desired)) + << " and gives " + << qPrintable(numtypeToString(result.type())) + << " ... "; + + if (result.type() == desired && result.toQString(precision) == desired_string) { + std::cout << "OK\n"; + return; + } + + std::cout << "Failed\n"; + exit(1); +} + +void checkTruth(const QString &string, bool computation, bool desired_result) { + + std::cout + << "Testing truth of: " + << qPrintable(string) + << " should be " + << desired_result + << " and is " + << computation + << " ... "; + + if (computation == desired_result) { + std::cout << "OK\n"; + return; + } + + std::cout << "Failed\n"; + exit(1); +} + +void checkType(const QString &string, int test_arg, int desired) { + + std::cout + << "Testing type of: " + << qPrintable(string) + << " should give " + << qPrintable(numtypeToString(desired)) + << " and gives " + << qPrintable(numtypeToString(test_arg)) + << " ..."; + + if (test_arg == desired) { + std::cout << "OK\n"; + return; + } + + std::cout << "Failed\n"; + exit(1); +} + +void testingCompare() { + + std::cout << "\n\n"; + std::cout << "Testing Compare:\n"; + std::cout << "----------------\n"; + + checkTruth("KNumber(5) == KNumber(2)", KNumber(5) == KNumber(2), false); + checkTruth("KNumber(5) > KNumber(2)", KNumber(5) > KNumber(2), true); + checkTruth("KNumber(5) < KNumber(2)", KNumber(5) < KNumber(2), false); + checkTruth("KNumber(5) < KNumber(0)", KNumber(5) < KNumber(0), false); + checkTruth("KNumber(-5) < KNumber(0)", KNumber(-5) < KNumber(0), true); + checkTruth("KNumber(5) >= KNumber(2)", KNumber(5) >= KNumber(2), true); + checkTruth("KNumber(5) <= KNumber(2)", KNumber(5) <= KNumber(2), false); + checkTruth("KNumber(5) != KNumber(2)", KNumber(5) != KNumber(2), true); + + checkTruth("KNumber(2) == KNumber(2)", KNumber(2) == KNumber(2), true); + checkTruth("KNumber(2) > KNumber(2)", KNumber(2) > KNumber(2), false); + checkTruth("KNumber(2) < KNumber(2)", KNumber(2) < KNumber(2), false); + checkTruth("KNumber(2) >= KNumber(2)", KNumber(2) >= KNumber(2), true); + checkTruth("KNumber(2) <= KNumber(2)", KNumber(2) <= KNumber(2), true); + checkTruth("KNumber(2) != KNumber(2)", KNumber(2) != KNumber(2), false); + + checkTruth("KNumber(5) == KNumber(\"1/2\")", KNumber(5) == KNumber(QLatin1String("1/2")), false); + checkTruth("KNumber(5) > KNumber(\"1/2\")", KNumber(5) > KNumber(QLatin1String("1/2")), true); + checkTruth("KNumber(5) < KNumber(\"1/2\")", KNumber(5) < KNumber(QLatin1String("1/2")), false); + checkTruth("KNumber(5) >= KNumber(\"1/2\")", KNumber(5) >= KNumber(QLatin1String("1/2")), true); + checkTruth("KNumber(5) <= KNumber(\"1/2\")", KNumber(5) <= KNumber(QLatin1String("1/2")), false); + checkTruth("KNumber(5) != KNumber(\"1/2\")", KNumber(5) != KNumber(QLatin1String("1/2")), true); + + checkTruth("KNumber(\"1/2\") == KNumber(\"1/2\")", KNumber(QLatin1String("1/2")) == KNumber(QLatin1String("1/2")), true); + checkTruth("KNumber(\"1/2\") > KNumber(\"1/2\")", KNumber(QLatin1String("1/2")) > KNumber(QLatin1String("1/2")), false); + checkTruth("KNumber(\"1/2\") < KNumber(\"1/2\")", KNumber(QLatin1String("1/2")) < KNumber(QLatin1String("1/2")), false); + checkTruth("KNumber(\"1/2\") >= KNumber(\"1/2\")", KNumber(QLatin1String("1/2")) >= KNumber(QLatin1String("1/2")), true); + checkTruth("KNumber(\"1/2\") <= KNumber(\"1/2\")", KNumber(QLatin1String("1/2")) <= KNumber(QLatin1String("1/2")), true); + checkTruth("KNumber(\"1/2\") != KNumber(\"1/2\")", KNumber(QLatin1String("1/2")) != KNumber(QLatin1String("1/2")), false); + + checkTruth("KNumber(\"3/2\") == KNumber(\"1/2\")", KNumber(QLatin1String("3/2")) == KNumber(QLatin1String("1/2")), false); + checkTruth("KNumber(\"3/2\") > KNumber(\"1/2\")", KNumber(QLatin1String("3/2")) > KNumber(QLatin1String("1/2")), true); + checkTruth("KNumber(\"3/2\") < KNumber(\"1/2\")", KNumber(QLatin1String("3/2")) < KNumber(QLatin1String("1/2")), false); + checkTruth("KNumber(\"3/2\") >= KNumber(\"1/2\")", KNumber(QLatin1String("3/2")) >= KNumber(QLatin1String("1/2")), true); + checkTruth("KNumber(\"3/2\") <= KNumber(\"1/2\")", KNumber(QLatin1String("3/2")) <= KNumber(QLatin1String("1/2")), false); + checkTruth("KNumber(\"3/2\") != KNumber(\"1/2\")", KNumber(QLatin1String("3/2")) != KNumber(QLatin1String("1/2")), true); + + checkTruth("KNumber(3.2) != KNumber(3)", KNumber(3.2) != KNumber(3), true); + checkTruth("KNumber(3.2) > KNumber(3)", KNumber(3.2) > KNumber(3), true); + checkTruth("KNumber(3.2) < KNumber(3)", KNumber(3.2) < KNumber(3), false); + + checkTruth("KNumber(3.2) < KNumber(\"3/5\")", KNumber(3.2) < KNumber(QLatin1String("3/5")), false); +} + + +void testingAdditions() { + + std::cout << "\n\n"; + std::cout << "Testing additions:\n"; + std::cout << "------------------\n"; + + checkResult("KNumber(5) + KNumber(2)", KNumber(5) + KNumber(2), QLatin1String("7"), KNumber::TYPE_INTEGER); + checkResult("KNumber(5) + KNumber(\"2/3\")", KNumber(5) + KNumber(QLatin1String("2/3")), QLatin1String("17/3"), KNumber::TYPE_FRACTION); + checkResult("KNumber(5) + KNumber(\"2.3\")", KNumber(5) + KNumber(QLatin1String("2.3")), QLatin1String("7.3"), KNumber::TYPE_FLOAT); + + checkResult("KNumber(\"5/3\") + KNumber(2)", KNumber(QLatin1String("5/3")) + KNumber(2), QLatin1String("11/3"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"5/3\") + KNumber(\"2/3\")", KNumber(QLatin1String("5/3")) + KNumber(QLatin1String("2/3")), QLatin1String("7/3"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"5/3\") + KNumber(\"1/3\")", KNumber(QLatin1String("5/3")) + KNumber(QLatin1String("1/3")), QLatin1String("2"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"5/3\") + KNumber(\"-26/3\")", KNumber(QLatin1String("5/3")) + KNumber(QLatin1String("-26/3")), QLatin1String("-7"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"5/2\") + KNumber(2.3)", KNumber(QLatin1String("5/2")) + KNumber(2.3), QLatin1String("4.8"), KNumber::TYPE_FLOAT); + + checkResult("KNumber(5.3) + KNumber(2)", KNumber(5.3) + KNumber(2), QLatin1String("7.3"), KNumber::TYPE_FLOAT); + checkResult("KNumber(5.3) + KNumber(\"2/4\")", KNumber(5.3) + KNumber(QLatin1String("2/4")), QLatin1String("5.8"), KNumber::TYPE_FLOAT); + checkResult("KNumber(5.3) + KNumber(2.3)", KNumber(5.3) + KNumber(2.3), QLatin1String("7.6"), KNumber::TYPE_FLOAT); +} + +void testingSubtractions() { + + std::cout << "\n\n"; + std::cout << "Testing subtractions:\n"; + std::cout << "---------------------\n"; + + checkResult("KNumber(5) - KNumber(2)", KNumber(5) - KNumber(2), QLatin1String("3"), KNumber::TYPE_INTEGER); + checkResult("KNumber(5) - KNumber(\"2/3\")", KNumber(5) - KNumber(QLatin1String("2/3")), QLatin1String("13/3"), KNumber::TYPE_FRACTION); + checkResult("KNumber(5) - KNumber(2.3)", KNumber(5) - KNumber(2.3), QLatin1String("2.7"), KNumber::TYPE_FLOAT); + + checkResult("KNumber(\"5/3\") - KNumber(2)", KNumber(QLatin1String("5/3")) - KNumber(2), QLatin1String("-1/3"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"5/3\") - KNumber(\"1/3\")", KNumber(QLatin1String("5/3")) - KNumber(QLatin1String("1/3")), QLatin1String("4/3"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"5/3\") - KNumber(\"2/3\")", KNumber(QLatin1String("5/3")) - KNumber(QLatin1String("2/3")), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"-5/3\") - KNumber(\"4/3\")", KNumber(QLatin1String("-5/3")) - KNumber(QLatin1String("4/3")), QLatin1String("-3"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"5/4\") - KNumber(2.2)", KNumber(QLatin1String("5/4")) - KNumber(2.2), QLatin1String("-0.95"), KNumber::TYPE_FLOAT); + + checkResult("KNumber(5.3) - KNumber(2)", KNumber(5.3) - KNumber(2), QLatin1String("3.3"), KNumber::TYPE_FLOAT); + checkResult("KNumber(5.3) - KNumber(\"3/4\")", KNumber(5.3) - KNumber(QLatin1String("3/4")), QLatin1String("4.55"), KNumber::TYPE_FLOAT); + checkResult("KNumber(5.3) - KNumber(2.3)", KNumber(5.3) - KNumber(2.3), QLatin1String("3"), KNumber::TYPE_INTEGER); +} + +void testingSpecial() { + + std::cout << "\n\n"; + std::cout << "Testing special functions:\n"; + std::cout << "--------------------------\n"; + + checkResult("log10(KNumber(5))", log10(KNumber(5)), QLatin1String("0.698970004336"), KNumber::TYPE_FLOAT); + checkResult("log10(pow(KNumber(10), KNumber(308)))", log10(pow(KNumber(10), KNumber(308))), QLatin1String("308"), KNumber::TYPE_INTEGER); + + // TODO: enable this check once MPFR is commonly enabled + // checkResult("log10(pow(KNumber(10), KNumber(309)))", log10(pow(KNumber(10), KNumber(309))), QLatin1String("309"), KNumber::TYPE_INTEGER); + + checkResult("exp(KNumber(4.34))", exp(KNumber(4.34)), QLatin1String("76.7075393383"), KNumber::TYPE_FLOAT); +} + +void testingTrig() { + + std::cout << "\n\n"; + std::cout << "Testing trig functions:\n"; + std::cout << "-----------------------\n"; + + checkResult("sin(KNumber(5))", sin(KNumber(5)), QLatin1String("-0.958924274663"), KNumber::TYPE_FLOAT); + checkResult("cos(KNumber(5))", cos(KNumber(5)), QLatin1String("0.283662185463"), KNumber::TYPE_FLOAT); + checkResult("tan(KNumber(5))", tan(KNumber(5)), QLatin1String("-3.38051500625"), KNumber::TYPE_FLOAT); + checkResult("sin(KNumber(-5))", sin(KNumber(-5)), QLatin1String("0.958924274663"), KNumber::TYPE_FLOAT); + checkResult("cos(KNumber(-5))", cos(KNumber(-5)), QLatin1String("0.283662185463"), KNumber::TYPE_FLOAT); + checkResult("tan(KNumber(-5))", tan(KNumber(-5)), QLatin1String("3.38051500625"), KNumber::TYPE_FLOAT); + + checkResult("sin(KNumber(\"5/2\"))", sin(KNumber(QLatin1String("5/2"))), QLatin1String("0.598472144104"), KNumber::TYPE_FLOAT); + checkResult("cos(KNumber(\"5/2\"))", cos(KNumber(QLatin1String("5/2"))), QLatin1String("-0.801143615547"), KNumber::TYPE_FLOAT); + checkResult("tan(KNumber(\"5/2\"))", tan(KNumber(QLatin1String("5/2"))), QLatin1String("-0.747022297239"), KNumber::TYPE_FLOAT); + checkResult("sin(KNumber(\"-5/2\"))", sin(KNumber(QLatin1String("-5/2"))), QLatin1String("-0.598472144104"), KNumber::TYPE_FLOAT); + checkResult("cos(KNumber(\"-5/2\"))", cos(KNumber(QLatin1String("-5/2"))), QLatin1String("-0.801143615547"), KNumber::TYPE_FLOAT); + checkResult("tan(KNumber(\"-5/2\"))", tan(KNumber(QLatin1String("-5/2"))), QLatin1String("0.747022297239"), KNumber::TYPE_FLOAT); + + checkResult("sin(KNumber(5.3))", sin(KNumber(5.3)), QLatin1String("-0.832267442224"), KNumber::TYPE_FLOAT); + checkResult("cos(KNumber(5.3))", cos(KNumber(5.3)), QLatin1String("0.554374336179"), KNumber::TYPE_FLOAT); + checkResult("tan(KNumber(5.3))", tan(KNumber(5.3)), QLatin1String("-1.50127339581"), KNumber::TYPE_FLOAT); + checkResult("sin(KNumber(-5.3))", sin(KNumber(-5.3)), QLatin1String("0.832267442224"), KNumber::TYPE_FLOAT); + checkResult("cos(KNumber(-5.3))", cos(KNumber(-5.3)), QLatin1String("0.554374336179"), KNumber::TYPE_FLOAT); + checkResult("tan(KNumber(-5.3))", tan(KNumber(-5.3)), QLatin1String("1.50127339581"), KNumber::TYPE_FLOAT); + + checkResult("asin(KNumber(5))", asin(KNumber(5)), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("acos(KNumber(5))", acos(KNumber(5)), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("atan(KNumber(5))", atan(KNumber(5)), QLatin1String("1.37340076695"), KNumber::TYPE_FLOAT); + checkResult("asin(KNumber(-5))", asin(KNumber(-5)), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("acos(KNumber(-5))", acos(KNumber(-5)), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("atan(KNumber(-5))", atan(KNumber(-5)), QLatin1String("-1.37340076695"), KNumber::TYPE_FLOAT); + + checkResult("asin(KNumber(\"5/2\"))", asin(KNumber(QLatin1String("5/2"))), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("acos(KNumber(\"5/2\"))", acos(KNumber(QLatin1String("5/2"))), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("atan(KNumber(\"5/2\"))", atan(KNumber(QLatin1String("5/2"))), QLatin1String("1.19028994968"), KNumber::TYPE_FLOAT); + checkResult("asin(KNumber(\"-5/2\"))", asin(KNumber(QLatin1String("-5/2"))), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("acos(KNumber(\"-5/2\"))", acos(KNumber(QLatin1String("-5/2"))), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("atan(KNumber(\"-5/2\"))", atan(KNumber(QLatin1String("-5/2"))), QLatin1String("-1.19028994968"), KNumber::TYPE_FLOAT); + + checkResult("asin(KNumber(5.3))", asin(KNumber(5.3)), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("acos(KNumber(5.3))", acos(KNumber(5.3)), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("atan(KNumber(5.3))", atan(KNumber(5.3)), QLatin1String("1.38430942513"), KNumber::TYPE_FLOAT); + checkResult("asin(KNumber(-5.3))", asin(KNumber(-5.3)), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("acos(KNumber(-5.3))", acos(KNumber(-5.3)), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("atan(KNumber(-5.3))", atan(KNumber(-5.3)), QLatin1String("-1.38430942513"), KNumber::TYPE_FLOAT); + + + checkResult("asin(KNumber(\"2/5\"))", asin(KNumber(QLatin1String("2/5"))), QLatin1String("0.411516846067"), KNumber::TYPE_FLOAT); + checkResult("acos(KNumber(\"2/5\"))", acos(KNumber(QLatin1String("2/5"))), QLatin1String("1.15927948073"), KNumber::TYPE_FLOAT); + checkResult("atan(KNumber(\"2/5\"))", atan(KNumber(QLatin1String("2/5"))), QLatin1String("0.380506377112"), KNumber::TYPE_FLOAT); + checkResult("asin(KNumber(\"-2/5\"))", asin(KNumber(QLatin1String("-2/5"))), QLatin1String("-0.411516846067"), KNumber::TYPE_FLOAT); + checkResult("acos(KNumber(\"-2/5\"))", acos(KNumber(QLatin1String("-2/5"))), QLatin1String("1.98231317286"), KNumber::TYPE_FLOAT); + checkResult("atan(KNumber(\"-2/5\"))", atan(KNumber(QLatin1String("-2/5"))), QLatin1String("-0.380506377112"), KNumber::TYPE_FLOAT); + + checkResult("asin(KNumber(0.3))", asin(KNumber(0.3)), QLatin1String("0.304692654015"), KNumber::TYPE_FLOAT); + checkResult("acos(KNumber(0.3))", acos(KNumber(0.3)), QLatin1String("1.26610367278"), KNumber::TYPE_FLOAT); + checkResult("atan(KNumber(0.3))", atan(KNumber(0.3)), QLatin1String("0.291456794478"), KNumber::TYPE_FLOAT); + checkResult("asin(KNumber(-0.3))", asin(KNumber(-0.3)), QLatin1String("-0.304692654015"), KNumber::TYPE_FLOAT); + checkResult("acos(KNumber(-0.3))", acos(KNumber(-0.3)), QLatin1String("1.87548898081"), KNumber::TYPE_FLOAT); + checkResult("atan(KNumber(-0.3))", atan(KNumber(-0.3)), QLatin1String("-0.291456794478"), KNumber::TYPE_FLOAT); +} + + +void testingMultiplications() { + + std::cout << "\n\n"; + std::cout << "Testing multiplications:\n"; + std::cout << "------------------------\n"; + + checkResult("KNumber(5) * KNumber(2)", KNumber(5) * KNumber(2), QLatin1String("10"), KNumber::TYPE_INTEGER); + checkResult("KNumber(5) * KNumber(\"2/3\")", KNumber(5) * KNumber(QLatin1String("2/3")), QLatin1String("10/3"), KNumber::TYPE_FRACTION); + checkResult("KNumber(5) * KNumber(\"2/5\")", KNumber(5) * KNumber(QLatin1String("2/5")), QLatin1String("2"), KNumber::TYPE_INTEGER); + checkResult("KNumber(5) * KNumber(2.3)", KNumber(5) * KNumber(2.3), QLatin1String("11.5"), KNumber::TYPE_FLOAT); + checkResult("KNumber(0) * KNumber(\"2/5\")", KNumber(0) * KNumber(QLatin1String("2/5")), QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("KNumber(0) * KNumber(2.3)", KNumber(0) * KNumber(2.3), QLatin1String("0"), KNumber::TYPE_INTEGER); + + checkResult("KNumber(\"5/3\") * KNumber(2)", KNumber(QLatin1String("5/3")) * KNumber(2), QLatin1String("10/3"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"5/3\") * KNumber(0)", KNumber(QLatin1String("5/3")) * KNumber(0), QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"5/3\") * KNumber(\"2/3\")", KNumber(QLatin1String("5/3")) * KNumber(QLatin1String("2/3")), QLatin1String("10/9"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"25/6\") * KNumber(\"12/5\")", KNumber(QLatin1String("25/6")) * KNumber(QLatin1String("12/5")), QLatin1String("10"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"5/2\") * KNumber(2.3)", KNumber(QLatin1String("5/2")) * KNumber(2.3), QLatin1String("5.75"), KNumber::TYPE_FLOAT); + + checkResult("KNumber(5.3) * KNumber(2)", KNumber(5.3) * KNumber(2), QLatin1String("10.6"), KNumber::TYPE_FLOAT); + checkResult("KNumber(5.3) * KNumber(0)", KNumber(5.3) * KNumber(0), QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("KNumber(5.3) * KNumber(\"1/2\")", KNumber(5.3) * KNumber(QLatin1String("1/2")), QLatin1String("2.65"), KNumber::TYPE_FLOAT); + checkResult("KNumber(5.3) * KNumber(2.3)", KNumber(5.3) * KNumber(2.3), QLatin1String("12.19"), KNumber::TYPE_FLOAT); +} + +void testingDivisions() { + + std::cout << "\n\n"; + std::cout << "Testing divisions:\n"; + std::cout << "------------------\n"; + + checkResult("KNumber(5) / KNumber(2)", KNumber(5) / KNumber(2), QLatin1String("5/2"), KNumber::TYPE_FRACTION); + checkResult("KNumber(122) / KNumber(2)", KNumber(122) / KNumber(2), QLatin1String("61"), KNumber::TYPE_INTEGER); + checkResult("KNumber(12) / KNumber(0)", KNumber(12) / KNumber(0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-12) / KNumber(0)", KNumber(-12) / KNumber(0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(5) / KNumber(\"2/3\")", KNumber(5) / KNumber(QLatin1String("2/3")), QLatin1String("15/2"), KNumber::TYPE_FRACTION); + checkResult("KNumber(6) / KNumber(\"2/3\")", KNumber(6) / KNumber(QLatin1String("2/3")), QLatin1String("9"), KNumber::TYPE_INTEGER); + checkResult("KNumber(5) / KNumber(2.5)", KNumber(5) / KNumber(2.5), QLatin1String("2"), KNumber::TYPE_INTEGER); + checkResult("KNumber(5) / KNumber(0.0)", KNumber(5) / KNumber(0.0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) / KNumber(0.0)", KNumber(-5) / KNumber(0.0), QLatin1String("nan"), KNumber::TYPE_ERROR); + + checkResult("KNumber(\"5/3\") / KNumber(2)", KNumber(QLatin1String("5/3")) / KNumber(2), QLatin1String("5/6"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"5/3\") / KNumber(0)", KNumber(QLatin1String("5/3")) / KNumber(0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") / KNumber(0)", KNumber(QLatin1String("-5/3")) / KNumber(0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"5/3\") / KNumber(\"2/3\")", KNumber(QLatin1String("5/3")) / KNumber(QLatin1String("2/3")), QLatin1String("5/2"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"49/3\") / KNumber(\"7/9\")", KNumber(QLatin1String("49/3")) / KNumber(QLatin1String("7/9")), QLatin1String("21"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"5/2\") / KNumber(2.5)", KNumber(QLatin1String("5/2")) / KNumber(2.5), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"5/2\") / KNumber(0.0)", KNumber(QLatin1String("5/2")) / KNumber(0.0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/2\") / KNumber(0.0)", KNumber(QLatin1String("-5/2")) / KNumber(0.0), QLatin1String("nan"), KNumber::TYPE_ERROR); + + checkResult("KNumber(5.3) / KNumber(2)", KNumber(5.3) / KNumber(2), QLatin1String("2.65"), KNumber::TYPE_FLOAT); + checkResult("KNumber(5.3) / KNumber(0)", KNumber(5.3) / KNumber(0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.3) / KNumber(0)", KNumber(-5.3) / KNumber(0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(5.3) / KNumber(\"2/3\")", KNumber(5.3) / KNumber(QLatin1String("2/3")), QLatin1String("7.95"), KNumber::TYPE_FLOAT); + checkResult("KNumber(5.5) / KNumber(2.5)", KNumber(5.5) / KNumber(2.5), QLatin1String("2.2"), KNumber::TYPE_FLOAT); + checkResult("KNumber(5.5) / KNumber(0.0)", KNumber(5.5) / KNumber(0.0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.5) / KNumber(0.0)", KNumber(-5.5) / KNumber(0.0), QLatin1String("nan"), KNumber::TYPE_ERROR); +} + +void testingModulus() { + + std::cout << "\n\n"; + std::cout << "Testing modulus:\n"; + std::cout << "----------------\n"; + + checkResult("KNumber(23) % KNumber(4)", KNumber(23) % KNumber(4), QLatin1String("3"), KNumber::TYPE_INTEGER); + checkResult("KNumber(12) % KNumber(-5)", KNumber(12) % KNumber(-5), QLatin1String("2"), KNumber::TYPE_INTEGER); + checkResult("KNumber(-12) % KNumber(5)", KNumber(-12) % KNumber(5), QLatin1String("3"), KNumber::TYPE_INTEGER); + checkResult("KNumber(12) % KNumber(0)", KNumber(-12) % KNumber(0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-12) % KNumber(0)", KNumber(-12) % KNumber(0), QLatin1String("nan"), KNumber::TYPE_ERROR); + +#ifdef __GNUC__ + #warning test for other types +#endif +} + +void testingAndOr() { + + std::cout << "\n\n"; + std::cout << "Testing And/Or:\n"; + std::cout << "---------------\n"; + + checkResult("KNumber(17) & KNumber(9)", KNumber(17) & KNumber(9), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(17) | KNumber(9)", KNumber(17) | KNumber(9), QLatin1String("25"), KNumber::TYPE_INTEGER); + checkResult("KNumber(1023) & KNumber(255)", KNumber(1023) & KNumber(255), QLatin1String("255"), KNumber::TYPE_INTEGER); + checkResult("KNumber(1023) | KNumber(255)", KNumber(1023) | KNumber(255), QLatin1String("1023"), KNumber::TYPE_INTEGER); + +#ifdef __GNUC__ + #warning test for other types +#endif +} + + +void testingAbs() { + + std::cout << "\n\n"; + std::cout << "Testing absolute value:\n"; + std::cout << "-----------------------\n"; + + checkResult("KNumber(5).abs()", KNumber(5).abs(), QLatin1String("5"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"2/3\").abs()", KNumber(QLatin1String("2/3")).abs(), QLatin1String("2/3"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"2.3\").abs()", KNumber(QLatin1String("2.3")).abs(), QLatin1String("2.3"), KNumber::TYPE_FLOAT); + + checkResult("KNumber(-5).abs()", KNumber(-5).abs(), QLatin1String("5"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"-2/3\").abs()", KNumber(QLatin1String("-2/3")).abs(), QLatin1String("2/3"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"-2.3\").abs()", KNumber(QLatin1String("-2.3")).abs(), QLatin1String("2.3"), KNumber::TYPE_FLOAT); +} + +void testingTruncateToInteger() { + + std::cout << "\n\n"; + std::cout << "Testing truncate to an integer:\n"; + std::cout << "-------------------------------\n"; + + checkResult("KNumber(16).integerPart()", KNumber(16).integerPart(), QLatin1String("16"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"43/9\").integerPart()", KNumber(QLatin1String("43/9")).integerPart(), QLatin1String("4"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"-43/9\").integerPart()", KNumber(QLatin1String("-43/9")).integerPart(), QLatin1String("-4"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"5.25\").integerPart()", KNumber(QLatin1String("5.25")).integerPart(), QLatin1String("5"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"-5.25\").integerPart()", KNumber(QLatin1String("-5.25")).integerPart(), QLatin1String("-5"), KNumber::TYPE_INTEGER); +} + + +void testingSqrt() { + + std::cout << "\n\n"; + std::cout << "Testing square root, cubic root:\n"; + std::cout << "--------------------------------\n"; + + checkResult("KNumber(16).sqrt()", KNumber(16).sqrt(), QLatin1String("4"), KNumber::TYPE_INTEGER); + checkResult("KNumber(-16).sqrt()", KNumber(-16).sqrt(), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"16/9\").sqrt()", KNumber(QLatin1String("16/9")).sqrt(), QLatin1String("4/3"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"-16/9\").sqrt()", KNumber(QLatin1String("-16/9")).sqrt(), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(2).sqrt()", KNumber(2).sqrt(), QLatin1String("1.41421356237"), KNumber::TYPE_FLOAT); + checkResult("KNumber(\"2/3\").sqrt()", KNumber(QLatin1String("2/3")).sqrt(), QLatin1String("0.816496580928"), KNumber::TYPE_FLOAT); + checkResult("KNumber(\"0.25\").sqrt()", KNumber(QLatin1String("0.25")).sqrt(), QLatin1String("0.5"), KNumber::TYPE_FLOAT); + checkResult("KNumber(\"-0.25\").sqrt()", KNumber(QLatin1String("-0.25")).sqrt(), QLatin1String("nan"), KNumber::TYPE_ERROR); + + + checkResult("KNumber(27).cbrt()", KNumber(27).cbrt(), QLatin1String("3"), KNumber::TYPE_INTEGER); + checkResult("KNumber(-27).cbrt()", KNumber(-27).cbrt(), QLatin1String("-3"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"27/8\").cbrt()", KNumber(QLatin1String("27/8")).cbrt(), QLatin1String("3/2"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"-8/27\").cbrt()", KNumber(QLatin1String("-8/27")).cbrt(), QLatin1String("-2/3"), KNumber::TYPE_FRACTION); +#ifdef __GNUC__ + #warning need to check non-perfect cube roots +#endif + checkResult("KNumber(2).cbrt()", KNumber(2).cbrt(), QLatin1String("1.25992104989"), KNumber::TYPE_FLOAT); + checkResult("KNumber(\"2/3\").cbrt()", KNumber(QLatin1String("2/3")).cbrt(), QLatin1String("0.873580464736"), KNumber::TYPE_FLOAT); + checkResult("KNumber(\"0.25\").cbrt()", KNumber(QLatin1String("0.25")).cbrt(), QLatin1String("0.629960524947"), KNumber::TYPE_FLOAT); + checkResult("KNumber(\"-0.25\").cbrt()", KNumber(QLatin1String("-0.25")).cbrt(), QLatin1String("-0.629960524947"), KNumber::TYPE_FLOAT); + +} + +void testingFactorial() { + + std::cout << "\n\n"; + std::cout << "Testing factorial:\n"; + std::cout << "------------------\n"; + + checkResult("KNumber(-1).factorial()", KNumber(-1).factorial(), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-2).factorial()", KNumber(-2).factorial(), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-20).factorial()", KNumber(-20).factorial(), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-1/2).factorial()", KNumber(QLatin1String("-1/2")).factorial(), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-0.5).factorial()", KNumber(QLatin1String("-0.5")).factorial(), QLatin1String("nan"), KNumber::TYPE_ERROR); + + checkResult("KNumber(0).factorial()", KNumber(0).factorial(), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(1).factorial()", KNumber(1).factorial(), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(2).factorial()", KNumber(2).factorial(), QLatin1String("2"), KNumber::TYPE_INTEGER); + checkResult("KNumber(3).factorial()", KNumber(3).factorial(), QLatin1String("6"), KNumber::TYPE_INTEGER); + checkResult("KNumber(4).factorial()", KNumber(4).factorial(), QLatin1String("24"), KNumber::TYPE_INTEGER); + checkResult("KNumber(5).factorial()", KNumber(5).factorial(), QLatin1String("120"), KNumber::TYPE_INTEGER); + checkResult("KNumber(6).factorial()", KNumber(6).factorial(), QLatin1String("720"), KNumber::TYPE_INTEGER); + checkResult("KNumber(9).factorial()", KNumber(9).factorial(), QLatin1String("362880"), KNumber::TYPE_INTEGER); + checkResult("KNumber(12).factorial()", KNumber(12).factorial(), QLatin1String("479001600"), KNumber::TYPE_INTEGER); + checkResult("KNumber(13).factorial()", KNumber(13).factorial(), QLatin1String("6227020800"), KNumber::TYPE_INTEGER); + + checkResult("KNumber(1/2).factorial()", KNumber(QLatin1String("1/2")).factorial(), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(2/1).factorial()", KNumber(QLatin1String("2/1")).factorial(), QLatin1String("2"), KNumber::TYPE_INTEGER); + checkResult("KNumber(3/2).factorial()", KNumber(QLatin1String("3/2")).factorial(), QLatin1String("1"), KNumber::TYPE_INTEGER); + + checkResult("KNumber(0.1).factorial()", KNumber(0.1).factorial(), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(0.5).factorial()", KNumber(0.5).factorial(), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(1.5).factorial()", KNumber(1.5).factorial(), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(2.5).factorial()", KNumber(2.5).factorial(), QLatin1String("2"), KNumber::TYPE_INTEGER); + checkResult("KNumber(3.5).factorial()", KNumber(3.5).factorial(), QLatin1String("6"), KNumber::TYPE_INTEGER); +} + +void testingComplement() { + std::cout << "\n\n"; + std::cout << "Testing complement:\n"; + std::cout << "-------------------\n"; + + // at first glance, these look like they should work + // but there is an annoyance. If we use the mpz_com function + // ~-2 == 1, but the HEX/OCT/BIN views are broken :-( + // specifically, if the value is negative, it goes badly pretty quick.. +#if 0 + checkResult("~KNumber(0)", ~KNumber(0), QLatin1String("-1"), KNumber::TYPE_INTEGER); + checkResult("~KNumber(1)", ~KNumber(1), QLatin1String("-2"), KNumber::TYPE_INTEGER); + checkResult("~KNumber(2)", ~KNumber(2), QLatin1String("-3"), KNumber::TYPE_INTEGER); + checkResult("~KNumber(8)", ~KNumber(8), QLatin1String("-9"), KNumber::TYPE_INTEGER); + checkResult("~KNumber(15)", ~KNumber(15), QLatin1String("-16"), KNumber::TYPE_INTEGER); + checkResult("~KNumber(-1)", ~KNumber(-1), QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("~KNumber(-2)", ~KNumber(-2), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("~KNumber(-3)", ~KNumber(-3), QLatin1String("2"), KNumber::TYPE_INTEGER); + checkResult("~KNumber(-9)", ~KNumber(-9), QLatin1String("8"), KNumber::TYPE_INTEGER); + checkResult("~KNumber(-16)", ~KNumber(-16), QLatin1String("15"), KNumber::TYPE_INTEGER); +#endif + + checkResult("~KNumber(0.12345)", ~KNumber(0.12345), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("~KNumber(-0.12345)", ~KNumber(-0.12345), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("~KNumber(\"1/2\")", ~KNumber(QLatin1String("1/2")), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("~KNumber(\"-1/2\")", ~KNumber(QLatin1String("-1/2")), QLatin1String("nan"), KNumber::TYPE_ERROR); +} + +void testingShifts() { + + std::cout << "\n\n"; + std::cout << "Testing left/right shift:\n"; + std::cout << "-------------------------\n"; + + checkResult("KNumber(16) << KNumber(2)", KNumber(16) << KNumber(2), QLatin1String("64"), KNumber::TYPE_INTEGER); + checkResult("KNumber(16) >> KNumber(2)", KNumber(16) >> KNumber(2), QLatin1String("4"), KNumber::TYPE_INTEGER); +} + +void testingPower() { + + std::cout << "\n\n"; + std::cout << "Testing Power:\n"; + std::cout << "--------------\n"; + + checkResult("KNumber(0) ^ KNumber(0)", KNumber(0).pow(KNumber(0)), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(0) ^ KNumber(-4)", KNumber(0).pow(KNumber(-4)), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(5) ^ KNumber(4)", KNumber(5).pow(KNumber(4)), QLatin1String("625"), KNumber::TYPE_INTEGER); + checkResult("KNumber(122) ^ KNumber(0)", KNumber(122).pow(KNumber(0)), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(-5) ^ KNumber(0)", KNumber(-5).pow(KNumber(0)), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(-2) ^ KNumber(3)", KNumber(-2).pow(KNumber(3)), QLatin1String("-8"), KNumber::TYPE_INTEGER); + checkResult("KNumber(-2) ^ KNumber(4)", KNumber(-2).pow(KNumber(4)), QLatin1String("16"), KNumber::TYPE_INTEGER); + checkResult("KNumber(5) ^ KNumber(-2)", KNumber(5).pow(KNumber(-2)), QLatin1String("1/25"), KNumber::TYPE_FRACTION); + checkResult("KNumber(8) ^ KNumber(\"2/3\")", KNumber(8).pow(KNumber(QLatin1String("2/3"))), QLatin1String("4"), KNumber::TYPE_INTEGER); + checkResult("KNumber(8) ^ KNumber(\"-2/3\")", KNumber(8).pow(KNumber(QLatin1String("-2/3"))), QLatin1String("1/4"), KNumber::TYPE_FRACTION); + + checkResult("KNumber(-16) ^ KNumber(\"1/4\")", KNumber(-16).pow(KNumber(QLatin1String("1/4"))), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-8) ^ KNumber(\"1/3\")", KNumber(-8).pow(KNumber(QLatin1String("1/3"))), QLatin1String("-2"), KNumber::TYPE_INTEGER); + checkResult("KNumber(5) ^ KNumber(0.0)", KNumber(5).pow(KNumber(0.0)), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(-5) ^ KNumber(0.0)", KNumber(-5).pow(KNumber(0.0)), QLatin1String("1"), KNumber::TYPE_INTEGER); + + checkResult("KNumber(\"5/3\") ^ KNumber(2)", KNumber(QLatin1String("5/3")).pow(KNumber(2)), QLatin1String("25/9"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"5/3\") ^ KNumber(0)", KNumber(QLatin1String("5/3")).pow(KNumber(0)), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"-5/3\") ^ KNumber(0)", KNumber(QLatin1String("-5/3")).pow(KNumber(0)), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"8/27\") ^ KNumber(\"2/3\")", KNumber(QLatin1String("8/27")).pow(KNumber(QLatin1String("2/3"))), QLatin1String("4/9"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"49/3\") ^ KNumber(\"7/9\")", KNumber(QLatin1String("49/3")).pow(KNumber(QLatin1String("7/9"))), QLatin1String("8.78016428243"), KNumber::TYPE_FLOAT); + checkResult("KNumber(\"5/2\") ^ KNumber(2.5)", KNumber(QLatin1String("5/2")).pow(KNumber(2.5)), QLatin1String("9.88211768803"), KNumber::TYPE_FLOAT); + checkResult("KNumber(\"5/2\") ^ KNumber(0.0)", KNumber(QLatin1String("5/2")).pow(KNumber(0.0)), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"-5/2\") ^ KNumber(0.0)", KNumber(QLatin1String("-5/2")).pow(KNumber(0.0)), QLatin1String("1"), KNumber::TYPE_INTEGER); + + checkResult("KNumber(5.3) ^ KNumber(2)", KNumber(5.3).pow(KNumber(2)), QLatin1String("28.09"), KNumber::TYPE_FLOAT); + checkResult("KNumber(5.3) ^ KNumber(0)", KNumber(5.3).pow(KNumber(0)), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(-5.3) ^ KNumber(0)", KNumber(-5.3).pow(KNumber(0)), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(5.3) ^ KNumber(\"2/3\")", KNumber(5.3).pow(KNumber(QLatin1String("2/3"))), QLatin1String("3.03983898039"), KNumber::TYPE_FLOAT); + checkResult("KNumber(5.5) ^ KNumber(2.5)", KNumber(5.5).pow(KNumber(2.5)), QLatin1String("70.9425383673"), KNumber::TYPE_FLOAT); + checkResult("KNumber(5.5) ^ KNumber(0.0)", KNumber(5.5).pow(KNumber(0.0)), QLatin1String("1"), KNumber::TYPE_INTEGER); + checkResult("KNumber(-5.5) ^ KNumber(0.0)", KNumber(-5.5).pow(KNumber(0.0)), QLatin1String("1"), KNumber::TYPE_INTEGER); + + checkResult("KNumber::Pi() ^ KNumber::Pi()", KNumber::Pi().pow(KNumber::Pi()), QLatin1String("36.4621596072"), KNumber::TYPE_FLOAT); + checkResult("KNumber::Euler() ^ KNumber::Pi()", KNumber::Euler().pow(KNumber::Pi()), QLatin1String("23.1406926328"), KNumber::TYPE_FLOAT); + + + checkResult("KNumber(2.0) ^ KNumber(0.5)", KNumber(2.0).pow(KNumber(0.5)), QLatin1String("1.41421356237"), KNumber::TYPE_FLOAT); + checkResult("KNumber(2.0) ^ KNumber(-0.5)", KNumber(2.0).pow(KNumber(-0.5)), QLatin1String("0.707106781187"), KNumber::TYPE_FLOAT); + + + checkResult("KNumber(-2.0).exp()", KNumber(-2.0).exp(), QLatin1String("0.135335283237"), KNumber::TYPE_FLOAT); + checkResult("KNumber::Euler() ^ KNumber(-2.0)", KNumber::Euler().pow(KNumber(-2.0)), QLatin1String("0.135335283237"), KNumber::TYPE_FLOAT); + + + checkResult("KNumber(2.0).exp()", KNumber(2.0).exp(), QLatin1String("7.38905609893"), KNumber::TYPE_FLOAT); + checkResult("KNumber::Euler() ^ KNumber(2.0)", KNumber::Euler().pow(KNumber(2.0)), QLatin1String("7.38905609893"), KNumber::TYPE_FLOAT); + + // TODO: kinda odd that this ends up being an integer + // i guess since my euler constant is only 100 digits, we've exceeded the fractional part + checkResult("KNumber::Euler() ^ 1000", KNumber::Euler().pow(KNumber(1000)), QLatin1String("1.97007111402e+434"), KNumber::TYPE_INTEGER); + + // TODO: make this test pass + // the problem is that it is using the libc exp function which has limits that GMP does not + // we should basically make this equivalent to KNumber::Euler().pow(KNumber(1000)) + // which does work +#if 0 + checkResult("KNumber(1000).exp()", KNumber(1000).exp(), QLatin1String("23.1406926328"), KNumber::TYPE_FLOAT); +#endif + + KNumber::setDefaultFractionalInput(true); + checkResult("KNumber(\"3.1415926\") ^ KNumber(\"3.1415926\")", KNumber(QLatin1String("3.1415926")).pow(KNumber(QLatin1String("3.1415926"))), QLatin1String("36.4621554164"), KNumber::TYPE_FLOAT); + KNumber::setDefaultFractionalInput(false); +} + +void testingInfArithmetic() { + + std::cout << "\n\n"; + std::cout << "Testing inf/nan-arithmetics:\n"; + std::cout << "----------------------------\n"; + + checkResult("inf + KNumber(2)", KNumber::PosInfinity + KNumber(2), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) + inf", KNumber(-5) + KNumber::PosInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("inf + KNumber(\"1/2\")", KNumber::PosInfinity + KNumber(QLatin1String("1/2")), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") + inf", KNumber(QLatin1String("-5/3")) + KNumber::PosInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("inf + KNumber(2.01)", KNumber::PosInfinity + KNumber(2.01), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.4) + inf", KNumber(-5.4) + KNumber::PosInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("-inf + KNumber(2)", KNumber::NegInfinity + KNumber(2), QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) + -inf", KNumber(-5) + KNumber::NegInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("-inf + KNumber(\"1/2\")", KNumber::NegInfinity + KNumber(QLatin1String("1/2")), QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") + -inf", KNumber(QLatin1String("-5/3")) + KNumber::NegInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("-inf + KNumber(2.01)", KNumber::NegInfinity + KNumber(2.01), QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.4) + -inf", KNumber(-5.4) + KNumber::NegInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("nan + KNumber(2)", KNumber::NaN + KNumber(2), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) + nan", KNumber(-5) + KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("nan + KNumber(\"1/2\")", KNumber::NaN + KNumber(QLatin1String("1/2")), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") + nan", KNumber(QLatin1String("-5/3")) + KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("nan + KNumber(2.01)", KNumber::NaN + KNumber(2.01), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.4) + nan", KNumber(-5.4) + KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("inf + inf", KNumber::PosInfinity + KNumber::PosInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("inf + -inf", KNumber::PosInfinity + KNumber::NegInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf + inf", KNumber::NegInfinity + KNumber::PosInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf + -inf", KNumber::NegInfinity + KNumber::NegInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("inf + nan", KNumber::PosInfinity + KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf + nan", KNumber::NegInfinity + KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("nan + inf", KNumber::NaN + KNumber::PosInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf + nan", KNumber::NegInfinity + KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + + checkResult("inf - KNumber(2)", KNumber::PosInfinity - KNumber(2), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) - inf", KNumber(-5) - KNumber::PosInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("inf - KNumber(\"1/2\")", KNumber::PosInfinity - KNumber(QLatin1String("1/2")), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") - inf", KNumber(QLatin1String("-5/3")) - KNumber::PosInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("inf - KNumber(2.01)", KNumber::PosInfinity - KNumber(2.01), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.4) - inf", KNumber(-5.4) - KNumber::PosInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("-inf - KNumber(2)", KNumber::NegInfinity - KNumber(2), QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) - -inf", KNumber(-5) - KNumber::NegInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("-inf - KNumber(\"1/2\")", KNumber::NegInfinity - KNumber(QLatin1String("1/2")), QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") - -inf", KNumber(QLatin1String("-5/3")) - KNumber::NegInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("-inf - KNumber(2.01)", KNumber::NegInfinity - KNumber(2.01), QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.4) - -inf", KNumber(-5.4) - KNumber::NegInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("nan - KNumber(2)", KNumber::NaN - KNumber(2), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) - nan", KNumber(-5) - KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("nan - KNumber(\"1/2\")", KNumber::NaN - KNumber(QLatin1String("1/2")), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") - nan", KNumber(QLatin1String("-5/3")) - KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("nan - KNumber(2.01)", KNumber::NaN - KNumber(2.01), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.4) - nan", KNumber(-5.4) - KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("inf - inf", KNumber::PosInfinity - KNumber::PosInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("inf - -inf", KNumber::PosInfinity - KNumber::NegInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("-inf - inf", KNumber::NegInfinity - KNumber::PosInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("-inf - -inf", KNumber::NegInfinity - KNumber::NegInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("inf - nan", KNumber::PosInfinity - KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf - nan", KNumber::NegInfinity - KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("nan - inf", KNumber::NaN - KNumber::PosInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf - nan", KNumber::NegInfinity - KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + + checkResult("inf * KNumber(2)", KNumber::PosInfinity * KNumber(2), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) * inf", KNumber(-5) * KNumber::PosInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("inf * KNumber(\"1/2\")", KNumber::PosInfinity * KNumber(QLatin1String("1/2")), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") * inf", KNumber(QLatin1String("-5/3")) * KNumber::PosInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("inf * KNumber(2.01)", KNumber::PosInfinity * KNumber(2.01), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.4) * inf", KNumber(-5.4) * KNumber::PosInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("-inf * KNumber(2)", KNumber::NegInfinity * KNumber(2), QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) * -inf", KNumber(-5) * KNumber::NegInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("-inf * KNumber(\"1/2\")", KNumber::NegInfinity * KNumber(QLatin1String("1/2")), QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") * -inf", KNumber(QLatin1String("-5/3")) * KNumber::NegInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("-inf * KNumber(2.01)", KNumber::NegInfinity * KNumber(2.01), QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.4) * -inf", KNumber(-5.4) * KNumber::NegInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("nan * KNumber(2)", KNumber::NaN * KNumber(2), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) * nan", KNumber(-5) * KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("nan * KNumber(\"1/2\")", KNumber::NaN * KNumber(QLatin1String("1/2")), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") * nan", KNumber(QLatin1String("-5/3")) * KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("nan * KNumber(2.01)", KNumber::NaN * KNumber(2.01), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.4) * nan", KNumber(-5.4) * KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("inf * inf", KNumber::PosInfinity * KNumber::PosInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("inf * -inf", KNumber::PosInfinity * KNumber::NegInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("-inf * inf", KNumber::NegInfinity * KNumber::PosInfinity, QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("-inf * -inf", KNumber::NegInfinity * KNumber::NegInfinity, QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("inf * nan", KNumber::PosInfinity * KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf * nan", KNumber::NegInfinity * KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("nan * inf", KNumber::NaN * KNumber::PosInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf * nan", KNumber::NegInfinity * KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(0) * inf", KNumber(0) * KNumber::PosInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(0) * -inf", KNumber(0) * KNumber::NegInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("inf * KNumber(0)", KNumber::PosInfinity * KNumber(0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf * KNumber(0)", KNumber::NegInfinity * KNumber(0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(0.0) * inf", KNumber(0.0) * KNumber::PosInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(0.0) * -inf", KNumber(0.0) * KNumber::NegInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("inf * KNumber(0.0)", KNumber::PosInfinity * KNumber(0.0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf * KNumber(0.0)", KNumber::NegInfinity * KNumber(0.0), QLatin1String("nan"), KNumber::TYPE_ERROR); + + checkResult("inf / KNumber(2)", KNumber::PosInfinity / KNumber(2), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) / inf", KNumber(-5) / KNumber::PosInfinity, QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("inf / KNumber(\"1/2\")", KNumber::PosInfinity / KNumber(QLatin1String("1/2")), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") / inf", KNumber(QLatin1String("-5/3")) / KNumber::PosInfinity, QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("inf / KNumber(2.01)", KNumber::PosInfinity / KNumber(2.01), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.4) / inf", KNumber(-5.4) / KNumber::PosInfinity, QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("-inf / KNumber(2)", KNumber::NegInfinity / KNumber(2), QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) / -inf", KNumber(-5) / KNumber::NegInfinity, QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("-inf / KNumber(\"1/2\")", KNumber::NegInfinity / KNumber(QLatin1String("1/2")), QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") / -inf", KNumber(QLatin1String("-5/3")) / KNumber::NegInfinity, QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("-inf / KNumber(2.01)", KNumber::NegInfinity / KNumber(2.01), QLatin1String("-inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.4) / -inf", KNumber(-5.4) / KNumber::NegInfinity, QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("nan / KNumber(2)", KNumber::NaN / KNumber(2), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) / nan", KNumber(-5) / KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("nan / KNumber(\"1/2\")", KNumber::NaN / KNumber(QLatin1String("1/2")), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/3\") / nan", KNumber(QLatin1String("-5/3")) / KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("nan / KNumber(2.01)", KNumber::NaN / KNumber(2.01), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5.4) / nan", KNumber(-5.4) / KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("inf / inf", KNumber::PosInfinity / KNumber::PosInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("inf / -inf", KNumber::PosInfinity / KNumber::NegInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf / inf", KNumber::NegInfinity / KNumber::PosInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf / -inf", KNumber::NegInfinity / KNumber::NegInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("inf / nan", KNumber::PosInfinity / KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf / nan", KNumber::NegInfinity / KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("nan / inf", KNumber::NaN / KNumber::PosInfinity, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf / nan", KNumber::NegInfinity / KNumber::NaN, QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(0) / inf", KNumber(0) / KNumber::PosInfinity, QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("KNumber(0) / -inf", KNumber(0) / KNumber::NegInfinity, QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("inf / KNumber(0)", KNumber::PosInfinity / KNumber(0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf / KNumber(0)", KNumber::NegInfinity / KNumber(0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(0.0) / inf", KNumber(0.0) / KNumber::PosInfinity, QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("KNumber(0.0) / -inf", KNumber(0.0) / KNumber::NegInfinity, QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("inf / KNumber(0.0)", KNumber::PosInfinity / KNumber(0.0), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("-inf / KNumber(0.0)", KNumber::NegInfinity / KNumber(0.0), QLatin1String("nan"), KNumber::TYPE_ERROR); + + checkResult("KNumber(5) ^ KNumber(\"inf\")", KNumber(5).pow(KNumber::PosInfinity), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) ^ KNumber(\"inf\")", KNumber(-5).pow(KNumber::PosInfinity), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"5/2\") ^ KNumber(\"inf\")", KNumber(QLatin1String("5/2")).pow(KNumber::PosInfinity), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/2\") ^ KNumber(\"inf\")", KNumber(QLatin1String("-5/2")).pow(KNumber::PosInfinity), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"5.2\") ^ KNumber(\"inf\")", KNumber(QLatin1String("5.2")).pow(KNumber::PosInfinity), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5.2\") ^ KNumber(\"inf\")", KNumber(QLatin1String("-5.2")).pow(KNumber::PosInfinity), QLatin1String("inf"), KNumber::TYPE_ERROR); + + checkResult("KNumber(5) ^ KNumber(\"-inf\")", KNumber(5).pow(KNumber::NegInfinity), QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("KNumber(-5) ^ KNumber(\"-inf\")", KNumber(-5).pow(KNumber::NegInfinity), QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"5/2\") ^ KNumber(\"-inf\")", KNumber(QLatin1String("5/2")).pow(KNumber::NegInfinity), QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"-5/2\") ^ KNumber(\"-inf\")", KNumber(QLatin1String("-5/2")).pow(KNumber::NegInfinity), QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"5.2\") ^ KNumber(\"-inf\")", KNumber(QLatin1String("5.2")).pow(KNumber::NegInfinity), QLatin1String("0"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"-5.2\") ^ KNumber(\"-inf\")", KNumber(QLatin1String("-5.2")).pow(KNumber::NegInfinity), QLatin1String("0"), KNumber::TYPE_INTEGER); + + checkResult("KNumber(5) ^ KNumber(\"nan\")", KNumber(5).pow(KNumber::NaN), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(-5) ^ KNumber(\"nan\")", KNumber(-5).pow(KNumber::NaN), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"5/2\") ^ KNumber(\"nan\")", KNumber(QLatin1String("5/2")).pow(KNumber::NaN), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5/2\") ^ KNumber(\"nan\")", KNumber(QLatin1String("-5/2")).pow(KNumber::NaN), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"5.2\") ^ KNumber(\"nan\")", KNumber(QLatin1String("5.2")).pow(KNumber::NaN), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-5.2\") ^ KNumber(\"nan\")", KNumber(QLatin1String("-5.2")).pow(KNumber::NaN), QLatin1String("nan"), KNumber::TYPE_ERROR); + + + checkResult("KNumber(\"nan\") ^ KNumber(\"nan\")", KNumber::NaN.pow(KNumber::NaN), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"nan\") ^ KNumber(\"inf\")", KNumber::NaN.pow(KNumber::PosInfinity), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"nan\") ^ KNumber(\"-inf\")", KNumber::NaN.pow(KNumber::NegInfinity), QLatin1String("nan"), KNumber::TYPE_ERROR); + + checkResult("KNumber(\"inf\") ^ KNumber(\"nan\")", KNumber::PosInfinity.pow(KNumber::NaN), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"inf\") ^ KNumber(\"inf\")", KNumber::PosInfinity.pow(KNumber::PosInfinity), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"inf\") ^ KNumber(\"-inf\")", KNumber::PosInfinity.pow(KNumber::NegInfinity), QLatin1String("0"), KNumber::TYPE_INTEGER); + + checkResult("KNumber(\"-inf\") ^ KNumber(\"nan\")", KNumber::NegInfinity.pow(KNumber::NaN), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-inf\") ^ KNumber(\"inf\")", KNumber::NegInfinity.pow(KNumber::PosInfinity), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-inf\") ^ KNumber(\"-inf\")", KNumber::NegInfinity.pow(KNumber::NegInfinity), QLatin1String("0"), KNumber::TYPE_INTEGER); +} + +void testingFloatPrecision() { + + KNumber::setDefaultFloatPrecision(100); + checkResult("Precision >= 100: (KNumber(1) + KNumber(\"1e-80\")) - KNumber(1)", (KNumber(1) + KNumber(QLatin1String("1e-80"))) - KNumber(1), QLatin1String("1e-80"), KNumber::TYPE_FLOAT); + checkResult("Precision >= 100: (KNumber(1) + KNumber(\"1e-980\")) - KNumber(1)", (KNumber(1) + KNumber(QLatin1String("1e-980"))) - KNumber(1), QLatin1String("0"), KNumber::TYPE_INTEGER); + + KNumber::setDefaultFloatPrecision(1000); + checkResult("Precision >= 1000: (KNumber(1) + KNumber(\"1e-980\")) - KNumber(1)", (KNumber(1) + KNumber(QLatin1String("1e-980"))) - KNumber(1), QLatin1String("1e-980"), KNumber::TYPE_FLOAT); + + KNumber::setDefaultFloatPrecision(20); + checkResult("Precision >= 20: sin(KNumber(30))", sin(KNumber(30) * (KNumber::Pi() / KNumber(180))), QLatin1String("0.5"), KNumber::TYPE_FLOAT); + +} + +void testingOutput() { + + KNumber::setDefaultFloatOutput(false); + checkResult("Fractional output: KNumber(\"1/4\")", KNumber(QLatin1String("1/4")), QLatin1String("1/4"), KNumber::TYPE_FRACTION); + + KNumber::setDefaultFloatOutput(true); + checkResult("Float: KNumber(\"1/4\")", KNumber(QLatin1String("1/4")), QLatin1String("0.25"), KNumber::TYPE_FRACTION); + + KNumber::setDefaultFloatOutput(false); + KNumber::setSplitoffIntegerForFractionOutput(true); + checkResult("Fractional output: KNumber(\"1/4\")", KNumber(QLatin1String("1/4")), QLatin1String("1/4"), KNumber::TYPE_FRACTION); + checkResult("Fractional output: KNumber(\"-1/4\")", KNumber(QLatin1String("-1/4")), QLatin1String("-1/4"), KNumber::TYPE_FRACTION); + checkResult("Fractional output: KNumber(\"21/4\")", KNumber(QLatin1String("21/4")), QLatin1String("5 1/4"), KNumber::TYPE_FRACTION); + checkResult("Fractional output: KNumber(\"-21/4\")", KNumber(QLatin1String("-21/4")), QLatin1String("-5 1/4"), KNumber::TYPE_FRACTION); + + KNumber::setSplitoffIntegerForFractionOutput(false); + checkResult("Fractional output: KNumber(\"1/4\")", KNumber(QLatin1String("1/4")), QLatin1String("1/4"), KNumber::TYPE_FRACTION); + checkResult("Fractional output: KNumber(\"-1/4\")", KNumber(QLatin1String("-1/4")), QLatin1String("-1/4"), KNumber::TYPE_FRACTION); + checkResult("Fractional output: KNumber(\"21/4\")", KNumber(QLatin1String("21/4")), QLatin1String("21/4"), KNumber::TYPE_FRACTION); + checkResult("Fractional output: KNumber(\"-21/4\")", KNumber(QLatin1String("-21/4")), QLatin1String("-21/4"), KNumber::TYPE_FRACTION); +} + +void testingConstructors() { + std::cout << "Testing Constructors:\n"; + std::cout << "---------------------\n"; + + checkResult("KNumber(5)", KNumber(5), QLatin1String("5"), KNumber::TYPE_INTEGER); + checkType(QLatin1String("KNumber(5.3)"), KNumber(5.3).type(), KNumber::TYPE_FLOAT); + checkType(QLatin1String("KNumber(0.0)"), KNumber(0.0).type(), KNumber::TYPE_INTEGER); + + checkResult("KNumber(\"5\")", KNumber(QLatin1String("5")), QLatin1String("5"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"5/3\")", KNumber(QLatin1String("5/3")), QLatin1String("5/3"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"5/1\")", KNumber(QLatin1String("5/1")), QLatin1String("5"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"0/12\")", KNumber(QLatin1String("0/12")), QLatin1String("0"), KNumber::TYPE_INTEGER); + KNumber::setDefaultFractionalInput(true); + std::cout << "Read decimals as fractions:\n"; + checkResult("KNumber(\"5\")", KNumber(QLatin1String("5")), QLatin1String("5"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"1.2\")", KNumber(QLatin1String("1.2")), QLatin1String("6/5"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"-0.02\")", KNumber(QLatin1String("-0.02")), QLatin1String("-1/50"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"5e-2\")", KNumber(QLatin1String("5e-2")), QLatin1String("1/20"), KNumber::TYPE_FRACTION); + checkResult("KNumber(\"1.2e3\")", KNumber(QLatin1String("1.2e3")), QLatin1String("1200"), KNumber::TYPE_INTEGER); + checkResult("KNumber(\"0.02e+1\")", KNumber(QLatin1String("0.02e+1")), QLatin1String("1/5"), KNumber::TYPE_FRACTION); + + KNumber::setDefaultFractionalInput(false); + std::cout << "Read decimals as floats:\n"; + checkResult("KNumber(\"5.3\")", KNumber(QLatin1String("5.3")), QLatin1String("5.3"), KNumber::TYPE_FLOAT); + + checkResult("KNumber(\"nan\")", KNumber(QLatin1String("nan")), QLatin1String("nan"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"inf\")", KNumber(QLatin1String("inf")), QLatin1String("inf"), KNumber::TYPE_ERROR); + checkResult("KNumber(\"-inf\")", KNumber(QLatin1String("-inf")), QLatin1String("-inf"), KNumber::TYPE_ERROR); + + // test accepting of non-US number formatted strings + KNumber::setDecimalSeparator(","); + checkResult("KNumber(\"5,2\")", KNumber(QLatin1String("5,2")), QLatin1String("5.2"), KNumber::TYPE_FLOAT); + KNumber::setDecimalSeparator("."); +} + +void testingConstants() { + std::cout << "\n\n"; + std::cout << "Constants:\n"; + std::cout << "----------\n"; + + checkType(QLatin1String("KNumber::Zero"), KNumber::Zero.type(), KNumber::TYPE_INTEGER); + checkType(QLatin1String("KNumber::One"), KNumber::One.type(), KNumber::TYPE_INTEGER); + checkType(QLatin1String("KNumber::NegOne"), KNumber::NegOne.type(), KNumber::TYPE_INTEGER); + checkType(QLatin1String("KNumber::Pi"), KNumber::Pi().type(), KNumber::TYPE_FLOAT); + checkType(QLatin1String("KNumber::Euler"), KNumber::Euler().type(), KNumber::TYPE_FLOAT); +} + +} + + +int main() { + + testingConstants(); + testingConstructors(); + testingCompare(); + testingAdditions(); + testingSubtractions(); + testingMultiplications(); + testingDivisions(); + testingAndOr(); + testingModulus(); + testingAbs(); + testingSqrt(); + testingFactorial(); + testingComplement(); + testingPower(); + testingTruncateToInteger(); + testingShifts(); + testingInfArithmetic(); + testingFloatPrecision(); + testingTrig(); + testingSpecial(); + testingOutput(); + std::cout << "SUCCESS" << std::endl; +} + diff --git a/kcalc/scienceconstants.xml b/kcalc/scienceconstants.xml new file mode 100644 index 00000000..ef6ce6df --- /dev/null +++ b/kcalc/scienceconstants.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kcalc/stats.cpp b/kcalc/stats.cpp new file mode 100644 index 00000000..9ea76efa --- /dev/null +++ b/kcalc/stats.cpp @@ -0,0 +1,213 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 1996 - 2000 Bernd Johannes Wuebben + wuebben@kde.org + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "stats.h" + +//------------------------------------------------------------------------------ +// Name: KStats +// Desc: constructor +//------------------------------------------------------------------------------ +KStats::KStats() : error_flag_(false) { +} + +//------------------------------------------------------------------------------ +// Name: ~KStats +// Desc: destructor +//------------------------------------------------------------------------------ +KStats::~KStats() { +} + +//------------------------------------------------------------------------------ +// Name: clearAll +// Desc: empties the data set +//------------------------------------------------------------------------------ +void KStats::clearAll() { + data_.clear(); +} + +//------------------------------------------------------------------------------ +// Name: enterData +// Desc: adds an item to the data set +//------------------------------------------------------------------------------ +void KStats::enterData(const KNumber &data) { + data_.push_back(data); +} + +//------------------------------------------------------------------------------ +// Name: clearLast +// Desc: remoaves the last item from the data set +//------------------------------------------------------------------------------ +void KStats::clearLast() { + + if(!data_.isEmpty()) { + data_.pop_back(); + } +} + +//------------------------------------------------------------------------------ +// Name: sum +// Desc: calculates the SUM of all values in the data set +//------------------------------------------------------------------------------ +KNumber KStats::sum() const { + + KNumber result = KNumber::Zero; + + Q_FOREACH(const KNumber &x, data_) { + result += x; + } + + return result; +} + +//------------------------------------------------------------------------------ +// Name: median +// Desc: calculates the MEDIAN of all values in the data set +//------------------------------------------------------------------------------ +KNumber KStats::median() { + + KNumber result = KNumber::Zero; + size_t index; + + unsigned int bound = count(); + + if (bound == 0) { + error_flag_ = true; + return KNumber::Zero; + } + + if (bound == 1) + return data_.at(0); + + // need to copy data_-list, because sorting afterwards + QVector tmp_data(data_); + qSort(tmp_data); + + if (bound & 1) { // odd + index = (bound - 1) / 2 + 1; + result = tmp_data.at(index - 1); + } else { // even + index = bound / 2; + result = ((tmp_data.at(index - 1)) + (tmp_data.at(index))) / KNumber(2); + } + + return result; +} + +//------------------------------------------------------------------------------ +// Name: std_kernel +// Desc: calculates the STD Kernel of all values in the data set +//------------------------------------------------------------------------------ +KNumber KStats::std_kernel() { + KNumber result = KNumber::Zero; + const KNumber mean_value = mean(); + + if(mean_value.type() != KNumber::TYPE_ERROR) { + Q_FOREACH(const KNumber &x, data_) { + result += (x - mean_value) * (x - mean_value); + } + } + + return result; +} + +//------------------------------------------------------------------------------ +// Name: sum_of_squares +// Desc: calculates the SUM of all values in the data set (each squared) +//------------------------------------------------------------------------------ +KNumber KStats::sum_of_squares() const { + + KNumber result = KNumber::Zero; + + Q_FOREACH(const KNumber &x, data_) { + result += (x * x); + } + + return result; +} + +//------------------------------------------------------------------------------ +// Name: mean +// Desc: calculates the MEAN of all values in the data set +//------------------------------------------------------------------------------ +KNumber KStats::mean() { + + if (data_.isEmpty()) { + error_flag_ = true; + return KNumber::Zero; + } + + return (sum() / KNumber(count())); +} + +//------------------------------------------------------------------------------ +// Name: std +// Desc: calculates the STANDARD DEVIATION of all values in the data set +//------------------------------------------------------------------------------ +KNumber KStats::std() { + + if (data_.isEmpty()) { + error_flag_ = true; + return KNumber::Zero; + } + + return (std_kernel() / KNumber(count())).sqrt(); +} + +//------------------------------------------------------------------------------ +// Name: sample_std +// Desc: calculates the SAMPLE STANDARD DEVIATION of all values in the data set +//------------------------------------------------------------------------------ +KNumber KStats::sample_std() { + + KNumber result = KNumber::Zero; + + if (count() < 2) { + error_flag_ = true; + return KNumber::Zero; + } + + result = (std_kernel() / KNumber(count() - 1)).sqrt(); + + return result; +} + +//------------------------------------------------------------------------------ +// Name: count +// Desc: returns the amount of values in the data set +//------------------------------------------------------------------------------ +int KStats::count() const { + + return data_.size(); +} + +//------------------------------------------------------------------------------ +// Name: error +// Desc: returns the error state AND clears it +//------------------------------------------------------------------------------ +bool KStats::error() { + + bool value = error_flag_; + error_flag_ = false; + return value; +} + + + diff --git a/kcalc/stats.h b/kcalc/stats.h new file mode 100644 index 00000000..e4e32c9d --- /dev/null +++ b/kcalc/stats.h @@ -0,0 +1,53 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +Copyright (C) 1996 - 2000 Bernd Johannes Wuebben + wuebben@kde.org + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KSTATS_H_ +#define KSTATS_H_ + +#include +#include "knumber.h" + +class KStats { +public: + KStats(); + ~KStats(); + +public: + void clearAll(); + void enterData(const KNumber &data); + void clearLast(); + KNumber sum() const; + KNumber sum_of_squares() const; + KNumber mean(); + KNumber median(); + KNumber std_kernel(); + KNumber std(); + KNumber sample_std(); + int count() const; + bool error(); + +private: + QVector data_; + bool error_flag_; +}; + +#endif + diff --git a/kcalc/version.h b/kcalc/version.h new file mode 100644 index 00000000..054ea521 --- /dev/null +++ b/kcalc/version.h @@ -0,0 +1,21 @@ +/* +Copyright (C) 2001 - 2013 Evan Teran + evan.teran@gmail.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef KCALCVERSION +#define KCALCVERSION "2.13" +#endif diff --git a/kcron/AUTHORS b/kcron/AUTHORS new file mode 100644 index 00000000..c1823940 --- /dev/null +++ b/kcron/AUTHORS @@ -0,0 +1,2 @@ +Gary Meyer +Robert Berry diff --git a/kcron/CMakeLists.txt b/kcron/CMakeLists.txt new file mode 100644 index 00000000..9f94d23b --- /dev/null +++ b/kcron/CMakeLists.txt @@ -0,0 +1,46 @@ +project(kcron) + +find_package(KDE4 REQUIRED) + +include(KDE4Defaults) +include(MacroLibrary) + +include(CheckIncludeFile) +include(CheckIncludeFiles) +include(CheckSymbolExists) +include(CheckFunctionExists) +include(CheckLibraryExists) +include(CheckPrototypeExists) +include(CheckTypeSize) + +set(CMAKE_REQUIRED_DEFINITIONS ${_KDE4_PLATFORM_DEFINITIONS}) +if(WIN32) + set(CMAKE_REQUIRED_LIBRARIES ${KDEWIN32_LIBRARIES}) + set(CMAKE_REQUIRED_INCLUDES ${KDEWIN32_INCLUDES}) +endif(WIN32) +add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS} ${KDE4_DEFINITIONS}) +add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) +include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}) + +# Help Eclipse to parse errors more efficiently +if(CMAKE_COMPILER_IS_GNUCC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fmessage-length=0") +endif(CMAKE_COMPILER_IS_GNUCC) +if(CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmessage-length=0") +endif(CMAKE_COMPILER_IS_GNUCXX) +# + + +## +# To specify a different install prefix, use : +# cmake -D CMAKE_INSTALL_PREFIX=build . +# +# To help Eclipse discover include paths, use : +# cmake -D CMAKE_VERBOSE_MAKEFILE=true . +## + +add_subdirectory(src) +add_subdirectory(doc) + + diff --git a/kcron/COPYING b/kcron/COPYING new file mode 100644 index 00000000..37ba36f1 --- /dev/null +++ b/kcron/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/kcron/ChangeLog b/kcron/ChangeLog new file mode 100644 index 00000000..2079fabb --- /dev/null +++ b/kcron/ChangeLog @@ -0,0 +1,69 @@ +nicolas.ternisien@gmail.com February 27, 1999 + Major rewrite of KCron. + +gary@meyer.net September 6, 1999 + + Fixed bug. Program didn't exit after editing. KDE2 only. + Fixed bug. Variable editor didn't update icon. KDE2 only. + Changed default sorting. Alphabetical with System Crontab on top. + Removed -lkfm from Makefile. + Change kcron.kdelnk to kcron.desktop. + +gary@meyer.net September 7, 1999 + + cttask.h, cttash.cpp, kttask.cpp + Fixed bug. New syscron task didn't let user enter "run as" field. + +gary@meyer.net September 10, 1999 + + Initial implementation of cut/copy/paste. + +gary@meyer.net September 17, 1999 + + Work around for Qt 2.0 ~QListViewItem bug. + +gary@meyer.net November 1, 1999 + + Released 0.5. + +gary@meyer.net November 7, 1999 + + Added KCron handbook. Various bug fixes. + +gary@meyer.net November 15, 1999 + + Fixed bug for supporting strftime (in CTTask::describe()) on different + platforms. + +gary@meyer.net November 19, 1999 + + Code clean up effort. + +gary@meyer.net November 29, 1999 + + Removed dependency on langinfo.h for internationalization of days of week + and months. Not broadly supported across platforms/distributions. + +gary@meyer.net December 2, 1999 + + Removed CTDebug, don't really need anymore. + Addeed note for translators so they'll look at README.translators. + Cleaned up CTUnit: moved implementation to cpp file, removed macro, + added support for "sun, Mon" and "jan, FEB" in crontab file. + +code@jamesots.com January 27, 2004 + + * Fixed bug. If only one hour wasn't checked '*' was still being used. + * Fixed bug. Removed autoWrap from KTVariable, as text is wrapped + in the line edit box anyway. Changed to use QTextEdit instead of the + deprecated QMultiLineEdit. + * Fixed bug. Strip out newlines from variable comments, because we + only read one line of comment from crontab for each entry. + * Fixed bug. System Crontab uses correct icon. + * Using standard icons for everything now. + * Added Set/Clear All buttons. + * Added New, Modify and Delete buttons on toolbar. (Bug 54399 and + part of bug 55684. Note about 55684: Use Ctrl-X as shortcut to delete + item.) + * Enabled New action when variables and tasks are selected, instead of + only when the containing label is selected. diff --git a/kcron/Messages.sh b/kcron/Messages.sh new file mode 100755 index 00000000..97d53ea0 --- /dev/null +++ b/kcron/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name "*.cpp"` -o $podir/kcron.pot diff --git a/kcron/README b/kcron/README new file mode 100644 index 00000000..ffcc0159 --- /dev/null +++ b/kcron/README @@ -0,0 +1,12 @@ +KCron + +KDE Task Scheduler + +GUI crontab editor + +Requires: + + Unix POSIX libraries for localized dates and times (glibc) + Cron (vixie-cron) + Crontab (crontabs) + diff --git a/kcron/TODO b/kcron/TODO new file mode 100644 index 00000000..8d9dbe66 --- /dev/null +++ b/kcron/TODO @@ -0,0 +1,5 @@ + +Ideas for new features : + +- Add a way to see next tasks scheduling in KOrganizer +- Support for at command (schedule a one shot task in the future, and remove it after execute it) diff --git a/kcron/doc/CMakeLists.txt b/kcron/doc/CMakeLists.txt new file mode 100644 index 00000000..c636b3a9 --- /dev/null +++ b/kcron/doc/CMakeLists.txt @@ -0,0 +1,3 @@ +########### install files ############### + +kde4_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en SUBDIR kcron) diff --git a/kcron/doc/index.docbook b/kcron/doc/index.docbook new file mode 100644 index 00000000..62c9e792 --- /dev/null +++ b/kcron/doc/index.docbook @@ -0,0 +1,443 @@ + + + +]> + +
+Task Scheduler + + + + +Morgan +N. +Sandquist +
&Morgan.N.Sandquist.mail;
+
+
+ + +Gary +Meyer +
&Gary.Meyer.mail;
+
+Developer +
+ + +Lauri +Watts +
&Lauri.Watts.mail;
+
+Reviewer +
+ + + +
+ + +2000 +&Morgan.N.Sandquist; + + +2013-06-22 +&kde; 4.11 + + +KDE +cron +crontab +scheduler + + +
+ + +Introduction + +This is a module for scheduling programs to run in the +background. It is a graphical user interface to cron, +the &UNIX; system scheduler. + +Don't forget to tell your system to start the +crond cron daemon first, or the settings in this +module will not work. + + +Start Up + +When you start this module you will see a summarized view of existing +scheduled tasks and associated environment variables. If you are running +as the root user, you will see these items for all users on the computer +as well as the system scheduled tasks. + + +start up + + + +start up + + + + +Scheduled Tasks + +Scheduled tasks appear under a Tasks +list. For each scheduled task, the following items are displayed: + + + + + +Scheduling +In this column either "@reboot" for task schedules at boot or +the crontab entry is displayed. + + +Command +Program file and parameters. + + +Status +Enabled or Disabled. + + +Description +A description for the task, such as its +purpose. + + +Scheduling Details +Natural language description of scheduled +task. + + + + + + +Environment Variables + +Environment variables appear in the +Environment Variables list. For each environment variable, +the following are displayed: + + + +Variable +Variable name. + + +Value +Variable value. + + +Status +Enabled or Disabled. + + +Comment +Natural language description of variable. + + + +Environment variables appearing here will override any existing +environment variable for all scheduled tasks. + + + + + +Adding Scheduled Tasks + +To create a new scheduled task, click the New Task... +button. Alternatively, you can select this action from the +right mouse button context menu. + + +The Edit Task Dialog + + +Edit Task dialog. + + +Edit Task dialog + + + + + +Command +Enter the name of the program. You can specify either a +relative path or absolute path. If you want to look up the program, click +Browse icon. + + + +Comment +Enter a description of the task to schedule. + + + +Enable this task +To enable or disable the task, select or de-select +this button. + + + + +Run at system bootup + +Check this button to run the task at system bootup. + + + + +Run every day + +If you want to schedule the task to run daily, select this button. + + + + +Months +Select the months during which the task is to be +scheduled. + + + +Days of Month +Select the days of the month on which the task is to be +scheduled. + + + +Days of Week +Select the days of the week on which the task is to be +scheduled. + + + +Hours +Select the hours on which the task is to be +scheduled. + + +Minutes +Select the minute at which the task is to be scheduled. cron +does not support scheduling tasks at smaller than one minute intervals. + + + +OK +Completes the creation of this task. + + + +Cancel +Cancels the creation of this task. + + + + +If you select both days of the month, and days of the week, the +task will run when either condition is met. For instance, if you select +the 1st and 15th, and select Sunday, the program will be run every 1st +and 15th of the selected months (regardless of day of week) as well as +every Sunday of the selected months (regardless of day of the +month). + +The scheduled task is not actually set up until the +crontab has been saved. + + + + + +Managing Scheduled Tasks + +As with creating new tasks, changes to tasks will not actually be +made until the crontab is saved. + +Use the buttons at the right of the task list to modify, delete, +print or run a selected task now. You reach all these actions from the +context menu as well. Additionally the context menu provides actions to +cut, copy and paste tasks. + + + + +Adding Environment Variables + +To create a new environment variable, click the +New Variable... button. + +Alternatively, you can use the right +mouse button menu to choose this action. + + +The Edit Variable dialog + + +Edit Variable dialog. + + +Edit Variable +dialog. + + + + + +Variable +Enter the environment variable name. You can use the drop down +list box to select from the most common environment variables used by scheduled +tasks. Those include: + + + + +HOME +To be used instead of the default user's home +folder. + + + +MAILTO +To send email output to an email address other than the user's +default email address. + + + +PATH +To be used to search folders for program +files. + + + +SHELL +To be used instead of the user's default +value. + + + +LD_CONFIG_PATH +The location of dynamic libraries. This allows cron jobs to execute +applications which have libraries they need to run installed outside the system library path. + + + + + + +Value +Enter the environment variable value. + + +Comment +Enter a description for the environment variable, such as its +purpose. + + +Enable this variable + +To enable or disable the variable, select or de-select +this button. + + + +OK + +Completes the setting of this variable. + + + +Cancel + +Cancels the setting of this variable. + + + + +The environment variable is not actually set up until the +crontab has been saved. + + + + + +Managing Environment Variables + +As with creating new variables, changes to variables will +not actually be made until the crontab is +saved. + +Use the buttons at the right of the variable list to modify or delete +a selected variable. You reach all these actions from the +context menu as well. Additionally the context menu provides actions to +cut, copy and paste variables. + + + + +
+ + diff --git a/kcron/doc/kcronstart.png b/kcron/doc/kcronstart.png new file mode 100644 index 0000000000000000000000000000000000000000..3c9e92d5952a18cb44b97d8b76ea3c60e91b865d GIT binary patch literal 37391 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 Q1poj507*qoM6N<$g4r6nTL1t6 literal 0 HcmV?d00001 diff --git a/kcron/doc/newtask.png b/kcron/doc/newtask.png new file mode 100644 index 0000000000000000000000000000000000000000..dc7ca43df437cb22141396d1a15eea671d505edc GIT binary patch literal 65045 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

ZXk=BWWLhtZ1xxy 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 beTAZ{2e;bgXqA3J1Fm-pYVxIWrUCySfj+iD literal 0 HcmV?d00001 diff --git a/kcron/doc/newvariable.png b/kcron/doc/newvariable.png new file mode 100644 index 0000000000000000000000000000000000000000..68a0d093ce46bec61a01d01c7302ea94d737697c GIT binary patch literal 25720 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 Gary Meyer Robert Berry +Maintained-by: Nicolas Ternisien +Primary-site: +Home-page: http://www.forum-software.org/ +Original-site: +Platform: Linux and other Unices, needs Qt, KDE, and cron +Copying-policy: GNU Public License +End diff --git a/kcron/src/CMakeLists.txt b/kcron/src/CMakeLists.txt new file mode 100644 index 00000000..30ceac39 --- /dev/null +++ b/kcron/src/CMakeLists.txt @@ -0,0 +1,62 @@ + +########### Build ############### + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/crontablib + ${CMAKE_CURRENT_SOURCE_DIR} +) + +########## KCM Module ############### + +set(kcron_crontablib_SRCS + crontablib/cthost.cpp + crontablib/ctcron.cpp + crontablib/ctmonth.cpp + crontablib/ctminute.cpp + crontablib/cthour.cpp + crontablib/ctdom.cpp + crontablib/ctdow.cpp + crontablib/cttask.cpp + crontablib/ctunit.cpp + crontablib/ctvariable.cpp + crontablib/ctGlobalCron.cpp + crontablib/ctSystemCron.cpp + crontablib/ctInitializationError.cpp + crontablib/ctSaveStatus.cpp + crontablib/ctHelper.cpp +) + + +set(kcm_cron_SRCS + ${kcron_crontablib_SRCS} + genericListWidget.cpp + + tasksWidget.cpp + taskWidget.cpp + + variablesWidget.cpp + variableWidget.cpp + + taskEditorDialog.cpp + variableEditorDialog.cpp + + crontabWidget.cpp + + kcronIcons.cpp + kcronHelper.cpp + + crontabPrinter.cpp + crontabPrinterWidget.cpp + + kcmCron.cpp +) + +kde4_add_plugin(kcm_cron ${kcm_cron_SRCS}) + +target_link_libraries(kcm_cron + ${KDE4_KIO_LIBS} +) + +install(TARGETS kcm_cron DESTINATION ${PLUGIN_INSTALL_DIR} ) + +install(FILES kcm_cron.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) diff --git a/kcron/src/crontabPrinter.cpp b/kcron/src/crontabPrinter.cpp new file mode 100644 index 00000000..e70a467c --- /dev/null +++ b/kcron/src/crontabPrinter.cpp @@ -0,0 +1,419 @@ +/*************************************************************************** + * -------------------------------------------------------------------- * + * KDE\QT printing implementation. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Robert Berry * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "crontabPrinter.h" + +#include +#include + +#include +#include + +#include "crontabWidget.h" + +#include "taskWidget.h" + +#include "ctcron.h" +#include "cttask.h" +#include "ctvariable.h" + +#include "logging.h" + +class CrontabPrinterPrivate { +public: + /** + * Pointer a printer options object + */ + CrontabPrinterWidget* crontabPrinterWidget; + + /** + * Pointer to parent widget + */ + CrontabWidget* crontabWidget; + + QPainter* painter; + + QPrinter* printer; + + QRect* printView; + + int page; + int currentRowPosition; + +}; + +CrontabPrinter::CrontabPrinter(CrontabWidget* crontabWidget) : + d(new CrontabPrinterPrivate()) { + + d->crontabPrinterWidget = NULL; + d->crontabWidget = crontabWidget; + + d->painter = NULL; + d->printer = NULL; + d->printView = NULL; + + d->page = 0; + d->currentRowPosition = 0; +} + +CrontabPrinter::~CrontabPrinter() { + delete d->crontabPrinterWidget; + + delete d->painter; + delete d->printer; + delete d->printView; + + delete d; +} + +bool CrontabPrinter::start() { + logDebug() << "Printing selection..." << endl; + + if (d->printer == NULL) { + d->printer = new QPrinter(); + } + + // do some printer initialization + d->printer->setFullPage( true); + + /* + CrontabPrinterWidget* dialogPage = new CrontabPrinterWidget(d->crontabWidge); + d->printer->addDialogPage(dialogPage); + */ + + // initialize the printer using the print dialog + QPrintDialog *printDialog = KdePrint::createPrintDialog(d->printer, d->crontabWidget); + printDialog->setEnabledOptions(QAbstractPrintDialog::PrintToFile); + if (printDialog->exec() == QDialog::Rejected) { + logDebug() << "Printing canceled" << endl; + delete printDialog; + return false; + } + + delete printDialog; + + // create a painter to paint on the printer object + d->painter = new QPainter(); + + // start painting + d->painter->begin(d->printer); + + int margin = computeMargin(); + d->printView = new QRect(margin, margin, d->painter->device()->width() - 2*margin, d->painter->device()->height() - 2*margin ); + + d->page = 1; + d->currentRowPosition = 0; + + drawMainTitle(); + + + return true; +} + +void CrontabPrinter::printTasks() { + CTCron* cron = d->crontabWidget->currentCron(); + + drawTitle(i18n("Scheduled Tasks")); + + QList tasksContent; + foreach(CTTask* task, cron->tasks()) { + QStringList values; + values << task->schedulingCronFormat(); + values << task->command; + values << task->comment; + + tasksContent.append(values); + } + + QList tasksColumnWidths = findColumnWidths(tasksContent, 3); + + QStringList taskHeaders; + taskHeaders << i18n("Scheduling") << i18n("Command") << i18n("Description"); + drawHeader(tasksColumnWidths, taskHeaders); + + foreach(const QStringList& contents, tasksContent) { + + drawContentRow(tasksColumnWidths, contents); + + needNewPage(); + + } + + drawTable(tasksColumnWidths); + + +} + +void CrontabPrinter::printVariables() { + CTCron* cron = d->crontabWidget->currentCron(); + + d->painter->translate(0, 20); + d->currentRowPosition = 0; + + //Environment Variables + + drawTitle(i18n("Environment Variables")); + + //QList variablesContent; + foreach(CTVariable* variable, cron->variables()) { + d->painter->drawText(*(d->printView), Qt::AlignLeft | Qt::TextWordWrap, variable->variable + QLatin1String( " = " ) + variable->value); + + int moveBy = computeStringHeight(variable->variable); + d->painter->translate(0, moveBy); + } +} + +void CrontabPrinter::drawMainTitle() { + CTCron* cron = d->crontabWidget->currentCron(); + + QFont originalFont = d->painter->font(); + QFont titleFont(originalFont); + titleFont.setPixelSize(20); + titleFont.setBold(true); + + d->painter->setFont(titleFont); + + QString mainTitle; + if (cron->isSystemCron()) + mainTitle = i18n("System Crontab"); + else if (cron->isMultiUserCron()) + mainTitle = i18n("All Users Crontabs"); + else + mainTitle = i18nc("Crontab of user login", "Crontab of user %1", cron->userLogin()); + + d->painter->drawText(*(d->printView), Qt::AlignHCenter | Qt::TextWordWrap, mainTitle); + + d->painter->translate(0, computeStringHeight(mainTitle)); + + d->painter->setFont(originalFont); + +} + +void CrontabPrinter::drawTitle(const QString& title) { + + QFont originalFont = d->painter->font(); + QFont titleFont(originalFont); + titleFont.setPixelSize(16); + titleFont.setBold(true); + + + d->painter->setFont(titleFont); + + d->painter->drawText(*(d->printView), Qt::AlignLeft | Qt::TextWordWrap, title); + + + d->painter->translate(0, computeStringHeight(title)); + + d->painter->setFont(originalFont); +} + +void CrontabPrinter::drawHeader(const QList& columnWidths, const QStringList& headers) { + + QFont originalFont = d->painter->font(); + QFont titleFont(originalFont); + titleFont.setBold(true); + + d->painter->setFont(titleFont); + + drawContentRow(columnWidths, headers); + + d->painter->setFont(originalFont); +} + + +void CrontabPrinter::drawContentRow(const QList& columnWidths, const QStringList& contents) { + + + QString firstColumn; + + int totalWidths = 0; + int index=0; + foreach(const QString& content, contents) { + if (index==0) + firstColumn = content; + + d->painter->drawText(*(d->printView), Qt::AlignLeft | Qt::TextWordWrap, QLatin1String( " " ) + content); + + d->painter->translate(columnWidths[index], 0); + + totalWidths += columnWidths[index]; + + index++; + } + + int moveBy = computeStringHeight(firstColumn); + + + changeRow( -totalWidths, moveBy); +} + + +void CrontabPrinter::finish() { + // stop painting, this will automatically send the print data to the printer + d->painter->end(); + +} + +void CrontabPrinter::printPageNumber() { + logDebug() << "Printing page number..." << endl; + + d->painter->translate(0, - d->currentRowPosition); + d->printView->moveTo(QPoint(0, d->printView->height()) ); + d->painter->translate( 0, - d->printView->height() ); + d->painter->drawText(d->printView->right() - d->painter->fontMetrics().width(QString::number(d->page) ), d->printView->bottom()+ d->painter->fontMetrics().ascent() + 5, QString::number(d->page) ); + +} + +void CrontabPrinter::changeRow(int x, int y) { + d->painter->translate(x, y); + d->currentRowPosition = d->currentRowPosition + y; +} + +int CrontabPrinter::computeMargin() const { + int dpiy = d->painter->device()->logicalDpiY(); + int margin = (int) ( (2/2.54)*dpiy ); // 2 cm margins + + return margin; +} + +int CrontabPrinter::computeStringHeight(const QString& text) const { + + int fontHeight = d->painter->fontMetrics().height(); + int lines = d->painter->fontMetrics().width(text) / d->printView->width() + 1; + int moveBy = (fontHeight + 2) * lines; + + return moveBy; +} + +/** + * Whether crontab should be printed + */ +bool CrontabPrinter::isPrintCrontab() const { + return d->crontabPrinterWidget->printCrontab(); +} + +/** + * Whether all users should be printed (root only) + */ +bool CrontabPrinter::isAllUsers() const { + return d->crontabPrinterWidget->printAllUsers(); +} + + +void CrontabPrinter::drawTable(const QList& columnWidths) { + d->painter->translate(0, - d->currentRowPosition + computeMargin()); + + int columnWidthsTotal = 0; + foreach(int columnWidth, columnWidths) { + columnWidthsTotal += columnWidth; + } + + + int margin = computeMargin(); + int linePositionX = margin; + + QPen originalPen = d->painter->pen(); + QPen pen(originalPen); + + pen.setWidth(1); + + d->painter->setPen(pen); + + + //First horizontal line + d->painter->drawLine(QPoint(margin, 0), QPoint(margin + columnWidthsTotal, 0)); + + //Second horizontal line + d->painter->drawLine(QPoint(margin, 0+computeStringHeight(QLatin1String( " " ))), QPoint(margin + columnWidthsTotal, 0+computeStringHeight(QLatin1String( " " )))); + + //First vertical line + d->painter->drawLine(QPoint(linePositionX, 0), QPoint(linePositionX, d->currentRowPosition)); + + foreach(int columnWidth, columnWidths) { + linePositionX += columnWidth; + d->painter->drawLine(QPoint(linePositionX, 0), QPoint(linePositionX, d->currentRowPosition)); + } + + //Last horizontal line + d->painter->drawLine(QPoint(margin, d->currentRowPosition), QPoint(margin + columnWidthsTotal, d->currentRowPosition)); + + d->painter->setPen(originalPen); + + d->painter->translate(0, d->currentRowPosition - computeMargin()); +} + +QList CrontabPrinter::findMaxWidths(const QList& contents, int columnCount) { + QList columnWidths; + for (int i=0; ipainter->fontMetrics().width(content.at(columnIndex)); + if (columnWidths[columnIndex] < valueWidth) { + columnWidths[columnIndex] = valueWidth; + } + + columnIndex++; + } + + + } + + return columnWidths; +} + +QList CrontabPrinter::findColumnWidths(const QList& contents, int columnCount) { + QList columnWidths = findMaxWidths(contents, columnCount); + + int margin = computeMargin(); + int pageWidth = d->painter->device()->width() - 2*margin; + + int columnWidthSum = 0; + foreach(int width, columnWidths) { + logDebug() << "Column : " << width << endl; + columnWidthSum += width; + } + + if (columnWidthSum >= pageWidth) { + logDebug() << "The printing could be out of the page" << endl; + return columnWidths; + } + + int additionalSpace = (pageWidth - columnWidthSum) / columnWidths.count(); + + + int columnIndex = 0; + while (columnIndex < columnWidths.count()) { + columnWidths[columnIndex] = columnWidths[columnIndex] + additionalSpace; + + columnIndex++; + } + + return columnWidths; +} + + + +void CrontabPrinter::needNewPage() { + int margin = computeMargin(); + if (d->currentRowPosition + margin >= d->printView->height()) { + printPageNumber(); + d->printer->newPage(); + d->page++; + d->currentRowPosition = 0; + } +} diff --git a/kcron/src/crontabPrinter.h b/kcron/src/crontabPrinter.h new file mode 100644 index 00000000..72993ac8 --- /dev/null +++ b/kcron/src/crontabPrinter.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * -------------------------------------------------------------------- * + * KDE\QT Printing class * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Robert Berry * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef KTPRINT_H +#define KTPRINT_H + +#include +#include + +#include "crontabPrinterWidget.h" + +class QString; + +class CrontabWidget; + +class CrontabPrinterPrivate; + +/** + *Provides a wrapper for simple printing of text. + */ +class CrontabPrinter { +public: + + /** + * Contructor + */ + CrontabPrinter(CrontabWidget* crontabWidget); + + /** + * Destructor + */ + ~CrontabPrinter(); + + bool start(); + void finish(); + void printTasks(); + void printVariables(); + + /** + * Whether crontab should be printed + */ + bool isPrintCrontab() const; + + /** + * Whether all users should be printed (root only) + */ + bool isAllUsers() const; + +private: + + /** + *Disable the copy constructor and the assignment operator + */ + CrontabPrinter& operator=(const CrontabPrinter&) { + return *this; + } + + void printPageNumber(); + + void drawMainTitle(); + void drawTitle(const QString& title); + + void drawHeader(const QList& columnWidths, const QStringList& headers); + void drawContentRow(const QList& columnWidths, const QStringList& contents); + + void drawTable(const QList& columnWidths); + + void needNewPage(); + int maxWidth(); + + void changeRow(int x, int y); + int computeMargin() const; + int computeStringHeight(const QString& text) const; + + QList findMaxWidths(const QList& tasksContent, int columnCount); + QList findColumnWidths(const QList& tasksContent, int columnCount); + + CrontabPrinterPrivate* const d; + +}; + +#endif diff --git a/kcron/src/crontabPrinterWidget.cpp b/kcron/src/crontabPrinterWidget.cpp new file mode 100644 index 00000000..06e42ac4 --- /dev/null +++ b/kcron/src/crontabPrinterWidget.cpp @@ -0,0 +1,65 @@ +/*************************************************************************** + * -------------------------------------------------------------------- * + * Print Options Dialog * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Robert Berry * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "crontabPrinterWidget.h" + +#include +#include +#include + +#include +#include + +CrontabPrinterWidget::CrontabPrinterWidget(bool root) : + QWidget() { + setWindowTitle(i18n("Cron Options")); + + QVBoxLayout *main_ = new QVBoxLayout(this); + main_->setMargin(KDialog::marginHint()); + main_->setSpacing(KDialog::spacingHint()); + + chkPrintCrontab = new QCheckBox(i18n("Print cron&tab"), this); + chkPrintCrontab->setObjectName( QLatin1String("chkPrintCrontab" )); + main_->addWidget(chkPrintCrontab); + + chkPrintAllUsers = new QCheckBox(i18n("Print &all users"), this); + chkPrintAllUsers->setObjectName( QLatin1String("chkPrintAllUsers" )); + main_->addWidget(chkPrintAllUsers); + + if (!root) { + chkPrintAllUsers->setChecked(false); + chkPrintAllUsers->setEnabled(false); + } + + setLayout(main_); +} + +CrontabPrinterWidget::~CrontabPrinterWidget() { +} + +bool CrontabPrinterWidget::printCrontab() { + return chkPrintCrontab->isChecked(); +} + +void CrontabPrinterWidget::setPrintCrontab(bool setStatus) { + chkPrintCrontab->setChecked(setStatus); +} + +bool CrontabPrinterWidget::printAllUsers() { + return chkPrintAllUsers->isChecked(); +} + +void CrontabPrinterWidget::setPrintAllUsers(bool setStatus) { + chkPrintAllUsers->setChecked(setStatus); +} + +#include "crontabPrinterWidget.moc" diff --git a/kcron/src/crontabPrinterWidget.h b/kcron/src/crontabPrinterWidget.h new file mode 100644 index 00000000..61bcf2f6 --- /dev/null +++ b/kcron/src/crontabPrinterWidget.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * -------------------------------------------------------------------- * + * Print Options Dialog * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Robert Berry * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef KTPRINTOPT_H +#define KTPRINTOPT_H + +#include + +class QCheckBox; + +/** + *Give the user the option to print the crontab file. + *If the user is root ask if they want to print all the users + */ + +class CrontabPrinterWidget : public QWidget { +Q_OBJECT +public: + + /** + * Constructs the dialog, if root is true the print all users is not disabled + */ + CrontabPrinterWidget(bool root = false); + + ~CrontabPrinterWidget(); + + bool printCrontab(); + void setPrintCrontab(bool setStatus); + + bool printAllUsers(); + void setPrintAllUsers(bool setStatus); + +private: + QCheckBox* chkPrintCrontab; + QCheckBox* chkPrintAllUsers; +}; + +#endif diff --git a/kcron/src/crontabWidget.cpp b/kcron/src/crontabWidget.cpp new file mode 100644 index 00000000..af4021a3 --- /dev/null +++ b/kcron/src/crontabWidget.cpp @@ -0,0 +1,430 @@ +/************************************************************************** + * KT main GUI view implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "crontabWidget.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cthost.h" +#include "ctcron.h" +#include "ctvariable.h" +#include "cttask.h" +#include "ctGlobalCron.h" + +#include "crontabPrinter.h" +#include "kcronIcons.h" +#include "tasksWidget.h" +#include "taskWidget.h" + +#include "variableWidget.h" +#include "variableWidget.h" + +#include "kcmCron.h" + +#include "logging.h" + +class CTGlobalCron; + +class CrontabWidgetPrivate { +public: + + /** + * The application. + */ + CTHost* ctHost; + + /** + * Tree view of the crontab tasks. + */ + TasksWidget* tasksWidget; + + /** + * Tree view of the crontab tasks. + */ + VariablesWidget* variablesWidget; + + QAction* cutAction; + QAction* copyAction; + QAction* pasteAction; + + /** + * Clipboard tasks. + */ + QList clipboardTasks; + + /** + * Clipboard variable. + */ + QList clipboardVariables; + + QRadioButton* currentUserCronRadio; + QRadioButton* systemCronRadio; + QRadioButton* otherUserCronRadio; + + QComboBox* otherUsers; + + /** + * Pointer to the pseudo Global Cron object + */ + CTGlobalCron* ctGlobalCron; + + +}; + +CrontabWidget::CrontabWidget(QWidget* parent, CTHost* ctHost) : + QWidget(parent), d(new CrontabWidgetPrivate()) { + + d->tasksWidget = NULL; + d->variablesWidget = NULL; + + d->ctHost = ctHost; + + if (d->ctHost->isRootUser()) { + d->ctGlobalCron = new CTGlobalCron(d->ctHost); + } + else { + d->ctGlobalCron = NULL; + } + + setupActions(); + + initialize(); + + logDebug() << "Clipboard Status " << hasClipboardContent() << endl; + + d->tasksWidget->setFocus(); + + QTreeWidgetItem* item = d->tasksWidget->treeWidget()->topLevelItem(0); + if (item != NULL) { + logDebug() << "First item found" << d->tasksWidget->treeWidget()->topLevelItemCount() << endl; + item->setSelected(true); + } + + d->tasksWidget->changeCurrentSelection(); + d->variablesWidget->changeCurrentSelection(); + +} + +CrontabWidget::~CrontabWidget() { + delete d->tasksWidget; + delete d->variablesWidget; + + delete d->ctGlobalCron; + + delete d; +} + +bool CrontabWidget::hasClipboardContent() { + if (d->clipboardTasks.isEmpty() == false) + return true; + + if (d->clipboardVariables.isEmpty() == false) + return true; + + return false; +} + +QHBoxLayout* CrontabWidget::createCronSelector() { + QHBoxLayout* layout = new QHBoxLayout(); + + layout->addWidget(new QLabel(i18n("Show the following Cron:"), this)); + + QButtonGroup* group = new QButtonGroup(this); + + d->currentUserCronRadio = new QRadioButton(i18n("Personal Cron"), this); + d->currentUserCronRadio->setChecked(true); + group->addButton(d->currentUserCronRadio); + layout->addWidget(d->currentUserCronRadio); + + d->systemCronRadio = new QRadioButton(i18n("System Cron"), this); + group->addButton(d->systemCronRadio); + layout->addWidget(d->systemCronRadio); + + d->otherUserCronRadio = new QRadioButton(i18n("Cron of User:"), this); + group->addButton(d->otherUserCronRadio); + + d->otherUsers = new QComboBox(this); + + layout->addWidget(d->otherUserCronRadio); + layout->addWidget(d->otherUsers); + + if (ctHost()->isRootUser()) { + QStringList users; + + foreach(CTCron* ctCron, ctHost()->crons) { + if (ctCron->isCurrentUserCron()) + continue; + + if (ctCron->isSystemCron()) + continue; + + users.append(ctCron->userLogin()); + } + + users.sort(); + d->otherUsers->addItems(users); + d->otherUsers->addItem(KIcon( QLatin1String( "users") ), i18n("Show All Personal Crons")); + } else { + d->otherUserCronRadio->hide(); + d->otherUsers->hide(); + } + + connect(group, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(refreshCron())); + connect(d->otherUsers, SIGNAL(currentIndexChanged(int)), this, SLOT(checkOtherUsers())); + + layout->addStretch(1); + + return layout; +} + +void CrontabWidget::initialize() { + QVBoxLayout* layout = new QVBoxLayout(this); + + logDebug() << "Begin view refresh" << endl; + + logDebug() << "Creating Tasks list..." << endl; + + QHBoxLayout* cronSelector = createCronSelector(); + layout->addLayout(cronSelector); + + QSplitter* splitter = new QSplitter(this); + splitter->setOrientation(Qt::Vertical); + layout->addWidget(splitter); + + d->tasksWidget = new TasksWidget(this); + splitter->addWidget(d->tasksWidget); + splitter->setStretchFactor(0, 2); + + d->variablesWidget = new VariablesWidget(this); + splitter->addWidget(d->variablesWidget); + splitter->setStretchFactor(1, 1); + + refreshCron(); + +} + +void CrontabWidget::refreshCron() { + + CTCron* ctCron = currentCron(); + + d->tasksWidget->refreshTasks(ctCron); + d->variablesWidget->refreshVariables(ctCron); + + if (ctCron->isMultiUserCron() && ctHost()->isRootUser()==false) { + logDebug() << "Disabling view..." << endl; + + d->tasksWidget->treeWidget()->setEnabled(false); + d->variablesWidget->treeWidget()->setEnabled(false); + + toggleNewEntryActions(false); + toggleModificationActions(false); + togglePasteAction(false); + d->tasksWidget->toggleRunNowAction(false); + } + else { + logDebug() << "Enabling view..." << endl; + + d->tasksWidget->treeWidget()->setEnabled(true); + d->variablesWidget->treeWidget()->setEnabled(true); + + toggleNewEntryActions(true); + togglePasteAction(hasClipboardContent()); + } +} + + +void CrontabWidget::copy() { + foreach(CTTask* task, d->clipboardTasks) { + delete task; + } + d->clipboardTasks.clear(); + + foreach(CTVariable* variable, d->clipboardVariables) { + delete variable; + } + d->clipboardVariables.clear(); + + QString clipboardText; + + if (d->tasksWidget->treeWidget()->hasFocus()) { + logDebug() << "Tasks copying" << endl; + + QList tasksWidget = d->tasksWidget->selectedTasksWidget(); + foreach(TaskWidget* taskWidget, tasksWidget) { + CTTask* task = new CTTask( *(taskWidget->getCTTask()) ); + d->clipboardTasks.append(task); + + clipboardText += task->exportTask() + QLatin1String( "\n" ); + } + } + + if (d->variablesWidget->treeWidget()->hasFocus()) { + logDebug() << "Variables copying" << endl; + + QList variablesWidget = d->variablesWidget->selectedVariablesWidget(); + foreach(VariableWidget* variableWidget, variablesWidget) { + CTVariable* variable = new CTVariable( *(variableWidget->getCTVariable()) ); + d->clipboardVariables.append(variable); + + clipboardText += variable->exportVariable() + QLatin1String( "\n" ); + } + } + + QApplication::clipboard()->setText(clipboardText, QClipboard::Clipboard); + QApplication::clipboard()->setText(clipboardText, QClipboard::Selection); + + logDebug() << "Clipboard Status " << hasClipboardContent() << endl; + togglePasteAction(hasClipboardContent()); +} + +void CrontabWidget::cut() { + logDebug() << "Cut content" << endl; + + copy(); + + if (d->tasksWidget->treeWidget()->hasFocus()) { + logDebug() << "Tasks cutting" << endl; + d->tasksWidget->deleteSelection(); + } + + if (d->variablesWidget->treeWidget()->hasFocus()) { + logDebug() << "Variables cutting" << endl; + d->variablesWidget->deleteSelection(); + } +} + +void CrontabWidget::paste() { + logDebug() << "Paste content" << endl; + + if (d->tasksWidget->treeWidget()->hasFocus()) { + foreach(CTTask* task, d->clipboardTasks) { + d->tasksWidget->addTask(new CTTask(*task)); + } + } + + if (d->variablesWidget->treeWidget()->hasFocus()) { + foreach(CTVariable* variable, d->clipboardVariables) { + d->variablesWidget->addVariable(new CTVariable(*variable)); + } + } + +} + +CTCron* CrontabWidget::currentCron() const { + if (d->currentUserCronRadio->isChecked()) + return d->ctHost->findCurrentUserCron(); + else if (d->systemCronRadio->isChecked()) + return d->ctHost->findSystemCron(); + + if (d->otherUsers->currentIndex() == d->otherUsers->count()-1) { + logDebug() << "Using Global Cron" << endl; + return d->ctGlobalCron; + } + + QString currentUserLogin = d->otherUsers->currentText(); + return d->ctHost->findUserCron(currentUserLogin); +} + +TasksWidget* CrontabWidget::tasksWidget() const { + return d->tasksWidget; +} + +VariablesWidget* CrontabWidget::variablesWidget() const { + return d->variablesWidget; +} + +CTHost* CrontabWidget::ctHost() const { + return d->ctHost; +} + +void CrontabWidget::checkOtherUsers() { + d->otherUserCronRadio->setChecked(true); + + refreshCron(); +} + +void CrontabWidget::setupActions() { + logDebug() << "Setup actions" << endl; + + //Edit menu + d->cutAction = KStandardAction::cut(this, SLOT(cut()), this); + d->copyAction = KStandardAction::copy(this, SLOT(copy()), this); + d->pasteAction = KStandardAction::paste(this, SLOT(paste()), this); + togglePasteAction(false); + + logDebug() << "Actions initialized" << endl; + +} + + +QList CrontabWidget::cutCopyPasteActions() { + QList actions; + actions.append(d->cutAction); + actions.append(d->copyAction); + actions.append(d->pasteAction); + + return actions; +} + +void CrontabWidget::togglePasteAction(bool state) { + d->pasteAction->setEnabled(state); +} + +void CrontabWidget::toggleModificationActions(bool state) { + d->cutAction->setEnabled(state); + d->copyAction->setEnabled(state); + + d->tasksWidget->toggleModificationActions(state); + d->variablesWidget->toggleModificationActions(state); +} + +void CrontabWidget::toggleNewEntryActions(bool state) { + d->tasksWidget->toggleNewEntryAction(state); + d->variablesWidget->toggleNewEntryAction(state); +} + +void CrontabWidget::print() { + + CrontabPrinter printer(this); + + if (printer.start() == false) { + logDebug() << "Unable to start printer" << endl; + return; + } + printer.printTasks(); + printer.printVariables(); + + printer.finish(); + +} + +#include "crontabWidget.moc" diff --git a/kcron/src/crontabWidget.h b/kcron/src/crontabWidget.h new file mode 100644 index 00000000..dd6f39d5 --- /dev/null +++ b/kcron/src/crontabWidget.h @@ -0,0 +1,119 @@ +/*************************************************************************** + * KT main view header. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef KTVIEW_H +#define KTVIEW_H + +#include + +#include "tasksWidget.h" +#include "variablesWidget.h" + +class QHBoxLayout; + +class KActionCollection; + +class CTHost; +class CTCron; + + +class CrontabWidgetPrivate; + +/** + * Main GUI view of the crontab entries. + */ +class CrontabWidget : public QWidget { +Q_OBJECT + +public: + + /** + * Initializes view. + */ + CrontabWidget(QWidget* parent, CTHost* ctHost); + + /** + * Destructor. + */ + ~CrontabWidget(); + + TasksWidget* tasksWidget() const; + + VariablesWidget* variablesWidget() const; + + CTHost* ctHost() const; + + CTCron* currentCron() const; + + QList cutCopyPasteActions(); + QAction* printAction(); + +public slots: + + /** + * Copies variables and/or tasks. + */ + void copy(); + + void cut(); + + /** + * Pastes variables and/or tasks from the clipboard. + */ + void paste(); + + /** + * Print crontab. + */ + void print(); + +protected slots: + + void refreshCron(); + + void checkOtherUsers(); + +private: + + /** + * Enables/disables paste button + */ + void togglePasteAction(bool enabled); + + /** + * Enables/disables modification buttons + */ + void toggleModificationActions(bool enabled); + + /** + * Enables/disables new entry actions + */ + void toggleNewEntryActions(bool enabled); + + /** + * Initialize actions. + */ + void setupActions(); + + /** + * Initialize view from underlying objects. + */ + void initialize(); + + QHBoxLayout* createCronSelector(); + + bool hasClipboardContent(); + + CrontabWidgetPrivate* const d; + +}; + +#endif // KTVIEW_H diff --git a/kcron/src/crontablib/ctGlobalCron.cpp b/kcron/src/crontablib/ctGlobalCron.cpp new file mode 100644 index 00000000..bd390482 --- /dev/null +++ b/kcron/src/crontablib/ctGlobalCron.cpp @@ -0,0 +1,145 @@ +/*************************************************************************** + * CT Cron Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "ctGlobalCron.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "cthost.h" +#include "cttask.h" +#include "ctvariable.h" + +#include "logging.h" + +CTGlobalCron::CTGlobalCron(CTHost* _ctHost) : + CTCron() { + + logDebug() << "Initializing CTGlobalCron" << endl; + + d->multiUserCron = true; + d->systemCron = false; + d->currentUserCron = false; + + d->userLogin = i18n("All users"); + + ctHost = _ctHost; +} + +CTGlobalCron::~CTGlobalCron() { + +} + +QList CTGlobalCron::tasks() const { + logDebug() << "Global Cron Tasks" << endl; + QList tasks; + + foreach(CTCron* cron, ctHost->crons) { + if (cron->isSystemCron()) + continue; + + foreach(CTTask* task, cron->tasks()) { + tasks.append(task); + } + } + return tasks; +} + +QList CTGlobalCron::variables() const { + logDebug() << "Global Cron Variables" << endl; + QList variables; + + foreach(CTCron* cron, ctHost->crons) { + if (cron->isSystemCron()) + continue; + + foreach(CTVariable* variable, cron->variables()) { + variables.append(variable); + } + } + + return variables; +} + +void CTGlobalCron::addTask(CTTask* task) { + logDebug() << "Global Cron addTask" << endl; + + CTCron* actualCron = ctHost->findUserCron(task->userLogin); + actualCron->addTask(task); +} + +void CTGlobalCron::addVariable(CTVariable* variable) { + logDebug() << "Global Cron addVariable" << endl; + + CTCron* actualCron = ctHost->findUserCron(variable->userLogin); + actualCron->addVariable(variable); +} + + +void CTGlobalCron::modifyTask(CTTask* task) { + logDebug() << "Global Cron modifyTask" << endl; + + CTCron* actualCron = ctHost->findCronContaining(task); + + /* + * actualCron could be NULL is the task came from clipboard because those tasks are never + * linked to an existing CTCron* object + */ + if (actualCron == NULL || actualCron->userLogin() != task->userLogin) { + if (actualCron!=NULL) { + actualCron->removeTask(task); + } + + CTCron* newCron = ctHost->findUserCron(task->userLogin); + newCron->addTask(task); + } +} + +void CTGlobalCron::modifyVariable(CTVariable* variable) { + logDebug() << "Global Cron modifyVariable" << endl; + + CTCron* actualCron = ctHost->findCronContaining(variable); + + /* + * actualCron could be NULL is the task came from clipboard because those tasks are never + * linked to an existing CTCron* object + */ + if (actualCron == NULL || actualCron->userLogin() != variable->userLogin) { + if (actualCron!=NULL) { + actualCron->removeVariable(variable); + } + + CTCron* newCron = ctHost->findUserCron(variable->userLogin); + newCron->addVariable(variable); + } +} + +void CTGlobalCron::removeTask(CTTask* task) { + logDebug() << "Global Cron removeTask" << endl; + + CTCron* actualCron = ctHost->findCronContaining(task); + actualCron->removeTask(task); +} + +void CTGlobalCron::removeVariable(CTVariable* variable) { + logDebug() << "Global Cron removeVariable" << endl; + + CTCron* actualCron = ctHost->findCronContaining(variable); + actualCron->removeVariable(variable); +} + diff --git a/kcron/src/crontablib/ctGlobalCron.h b/kcron/src/crontablib/ctGlobalCron.h new file mode 100644 index 00000000..f997ba19 --- /dev/null +++ b/kcron/src/crontablib/ctGlobalCron.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * CT Cron Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CT_GLOBAL_CRON_H +#define CT_GLOBAL_CRON_H + +#include +#include +#include +#include + +#include "ctcron.h" + +class CTTask; +class CTVariable; +class CTHost; + + +/** + * A user (encapsulation of a single crontab file). Encapsulates + * file i/o, parsing, tokenization, and natural language description. + */ +class CTGlobalCron : public CTCron { +public: + + + explicit CTGlobalCron(CTHost* ctHost); + + /** + * Destructor. + */ + virtual ~CTGlobalCron(); + + virtual QList tasks() const; + + virtual QList variables() const; + + virtual void addTask(CTTask* task); + virtual void addVariable(CTVariable* variable); + + virtual void modifyTask(CTTask* task); + virtual void modifyVariable(CTVariable* variable); + + virtual void removeVariable(CTVariable* variable); + virtual void removeTask(CTTask* task); + +private: + CTHost* ctHost; +}; + +#endif // CT_GLOBAL_CRON_H diff --git a/kcron/src/crontablib/ctHelper.cpp b/kcron/src/crontablib/ctHelper.cpp new file mode 100644 index 00000000..3f5dadb2 --- /dev/null +++ b/kcron/src/crontablib/ctHelper.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * CT Month Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "ctHelper.h" + +#include + +#include + +QString CTHelper::exportComment(const QString& comment) { + QString exportComment; + + if (comment.isEmpty()) { + QString noComment = i18n("No comment"); + exportComment += QLatin1String( "#" ) + noComment + QLatin1String( "\n" ); + return exportComment; + } + + QStringList lines = comment.split(QLatin1String( "\n" )); + foreach(const QString &line, lines) { + exportComment += QLatin1String( "#" ) + line + QLatin1String( "\n" ); + } + + return exportComment; +} diff --git a/kcron/src/crontablib/ctHelper.h b/kcron/src/crontablib/ctHelper.h new file mode 100644 index 00000000..ab303c44 --- /dev/null +++ b/kcron/src/crontablib/ctHelper.h @@ -0,0 +1,24 @@ +/*************************************************************************** + * CT Hour Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CT_HELPER +#define CT_HELPER + +#include + +class CTHelper { +public: + + static QString exportComment(const QString& comment); +}; + + +#endif // CT_HELPER diff --git a/kcron/src/crontablib/ctInitializationError.cpp b/kcron/src/crontablib/ctInitializationError.cpp new file mode 100644 index 00000000..d481ddec --- /dev/null +++ b/kcron/src/crontablib/ctInitializationError.cpp @@ -0,0 +1,12 @@ +/*************************************************************************** + * CT Month Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "ctInitializationError.h" diff --git a/kcron/src/crontablib/ctInitializationError.h b/kcron/src/crontablib/ctInitializationError.h new file mode 100644 index 00000000..839b852e --- /dev/null +++ b/kcron/src/crontablib/ctInitializationError.h @@ -0,0 +1,41 @@ +/*************************************************************************** + * CT Hour Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CT_INITIALIZATION_ERROR +#define CT_INITIALIZATION_ERROR + +#include + +class CTInitializationError { +public: + + QString errorMessage() const { + return error; + } + + void setErrorMessage(const QString& errorMessage) { + this->error = errorMessage; + } + + bool hasErrorMessage() { + if (error.isEmpty() == true) + return false; + + return true; + } + +private: + QString error; + +}; + + +#endif // CT_INITIALIZATION_ERROR diff --git a/kcron/src/crontablib/ctSaveStatus.cpp b/kcron/src/crontablib/ctSaveStatus.cpp new file mode 100644 index 00000000..21226761 --- /dev/null +++ b/kcron/src/crontablib/ctSaveStatus.cpp @@ -0,0 +1,12 @@ +/*************************************************************************** + * CT Month Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "ctSaveStatus.h" diff --git a/kcron/src/crontablib/ctSaveStatus.h b/kcron/src/crontablib/ctSaveStatus.h new file mode 100644 index 00000000..4c8e7e4a --- /dev/null +++ b/kcron/src/crontablib/ctSaveStatus.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * CT Hour Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CT_SAVE_STATUS +#define CT_SAVE_STATUS + +#include + +class CTSaveStatus { +public: + + CTSaveStatus() { + this->errorStatus = false; + } + + CTSaveStatus(const QString& errorMessage, const QString& detailErrorMessage) { + this->errorStatus = true; + this->error = errorMessage; + this->detailError = detailErrorMessage; + } + + QString errorMessage() const { + return error; + } + + QString detailErrorMessage() const { + return detailError; + } + + bool isError() const { + return errorStatus; + } + +private: + bool errorStatus; + + QString error; + + QString detailError; + +}; + + +#endif // CT_SAVE_STATUS diff --git a/kcron/src/crontablib/ctSystemCron.cpp b/kcron/src/crontablib/ctSystemCron.cpp new file mode 100644 index 00000000..f862e54d --- /dev/null +++ b/kcron/src/crontablib/ctSystemCron.cpp @@ -0,0 +1,72 @@ +/*************************************************************************** + * CT Cron Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "ctSystemCron.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "cthost.h" +#include "cttask.h" +#include "ctvariable.h" + +#include "logging.h" + +CTSystemCron::CTSystemCron(const QString& crontabBinary) : + CTCron() { + + d->systemCron = true; + d->multiUserCron = true; + d->currentUserCron = false; + + d->crontabBinary = crontabBinary; + + KTemporaryFile tmp; + tmp.open(); + d->tmpFileName = tmp.fileName(); + + CommandLine readCommandLine; + + readCommandLine.commandLine = QLatin1String( "cat" ); + readCommandLine.parameters << QLatin1String( "/etc/crontab" ); + readCommandLine.standardOutputFile = d->tmpFileName; + + d->writeCommandLine.commandLine = QLatin1String( "cat" ); + d->writeCommandLine.parameters << d->tmpFileName; + d->writeCommandLine.standardOutputFile = QLatin1String( "/etc/crontab" ); + + d->userLogin = i18n("System Crontab"); + d->userRealName = d->userLogin; + + d->initialTaskCount = 0; + d->initialVariableCount = 0; + + // Don't set error if it can't be read, it means the user + // doesn't have a crontab. + if (readCommandLine.execute().exitCode == 0) { + this->parseFile(d->tmpFileName); + } + + d->initialTaskCount = d->task.size(); + d->initialVariableCount = d->variable.size(); +} + + +CTSystemCron::~CTSystemCron() { + +} diff --git a/kcron/src/crontablib/ctSystemCron.h b/kcron/src/crontablib/ctSystemCron.h new file mode 100644 index 00000000..4f42a8d6 --- /dev/null +++ b/kcron/src/crontablib/ctSystemCron.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * CT Cron Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CT_SYSTEM_CRON_H +#define CT_SYSTEM_CRON_H + +#include +#include +#include +#include + +#include "ctcron.h" + +class CTTask; +class CTVariable; +class CTHost; + + +class CTSystemCron : public CTCron { +public: + + + /** + * Constructs the scheduled tasks, environment variables from crontab + * files and obtains some information about the user from the system. + * + * Default is to construct from the user's crontab. Can also be called, + * passing TRUE, to construct from the system crontab. Throws an + * exception if the crontab file can not be found, read, or parsed. + */ + explicit CTSystemCron(const QString& cronBinary); + + + /** + * Destructor. + */ + virtual ~CTSystemCron(); + +}; + +#endif // CT_SYSTEM_CRON_H diff --git a/kcron/src/crontablib/ctcron.cpp b/kcron/src/crontablib/ctcron.cpp new file mode 100644 index 00000000..2ff37bfa --- /dev/null +++ b/kcron/src/crontablib/ctcron.cpp @@ -0,0 +1,449 @@ +/*************************************************************************** + * CT Cron Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "ctcron.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "cttask.h" +#include "ctvariable.h" +#include "ctInitializationError.h" + +#include // getuid(), unlink() +#include // pwd, getpwnam(), getpwuid() + +#include "logging.h" + +CommandLineStatus CommandLine::execute() { + QProcess process; + + if (standardOutputFile.isEmpty() == false) + process.setStandardOutputFile(standardOutputFile); + + int exitCode; + process.start(commandLine, parameters); + if (!process.waitForStarted()) { + exitCode = 127; + } else { + process.waitForFinished(-1); + exitCode = process.exitCode(); + } + + CommandLineStatus commandLineStatus; + + commandLineStatus.commandLine = commandLine + QLatin1String( " " ) + parameters.join(QLatin1String( " " )); + if (standardOutputFile.isEmpty() == false) + commandLineStatus.commandLine += QLatin1String( " > " ) + standardOutputFile; + + commandLineStatus.standardOutput = QLatin1String( process.readAllStandardOutput() ); + commandLineStatus.standardError = QLatin1String( process.readAllStandardError() ); + commandLineStatus.exitCode = exitCode; + + return commandLineStatus; +} + +CTCron::CTCron(const QString& crontabBinary, const struct passwd* userInfos, bool currentUserCron, CTInitializationError& ctInitializationError) : + d(new CTCronPrivate()) { + + Q_ASSERT(userInfos != NULL); + + d->multiUserCron = false; + d->systemCron = false; + d->currentUserCron = currentUserCron; + + d->crontabBinary = crontabBinary; + + KTemporaryFile tmp; + tmp.open(); + d->tmpFileName = tmp.fileName(); + + CommandLine readCommandLine; + + // regular user, so provide user's own crontab + if (currentUserCron == true) { + readCommandLine.commandLine = d->crontabBinary; + readCommandLine.parameters << QLatin1String( "-l" ); + readCommandLine.standardOutputFile = d->tmpFileName; + + d->writeCommandLine.commandLine = d->crontabBinary; + d->writeCommandLine.parameters << d->tmpFileName; + + } + else { + + readCommandLine.commandLine = d->crontabBinary; + readCommandLine.parameters << QLatin1String( "-u" ) << QLatin1String(userInfos->pw_name) << QLatin1String( "-l" ); + readCommandLine.standardOutputFile = d->tmpFileName; + + d->writeCommandLine.commandLine = d->crontabBinary; + d->writeCommandLine.parameters << QLatin1String( "-u" ) << QLatin1String(userInfos->pw_name) << d->tmpFileName; + } + + + d->initialTaskCount = 0; + d->initialVariableCount = 0; + + if (initializeFromUserInfos(userInfos) == false) { + ctInitializationError.setErrorMessage(i18n("No password entry found for uid '%1'", getuid())); + logDebug() << "Error in crontab creation of" << userInfos->pw_name << endl; + return; + } + + // Don't set error if it can't be read, it means the user doesn't have a crontab. + CommandLineStatus commandLineStatus = readCommandLine.execute(); + if (commandLineStatus.exitCode == 0) { + this->parseFile(d->tmpFileName); + } + else { + logDebug() << "Error when executing command" << commandLineStatus.commandLine << endl; + logDebug() << "Standard output :" << commandLineStatus.standardOutput << endl; + logDebug() << "Standard error :" << commandLineStatus.standardError << endl; + } + + d->initialTaskCount = d->task.size(); + d->initialVariableCount = d->variable.size(); +} + +CTCron::CTCron() : + d(new CTCronPrivate()) { + +} + +bool CTCron::initializeFromUserInfos(const struct passwd* userInfos) { + if (userInfos == 0) { + return false; + } else { + d->userLogin = QLatin1String( userInfos->pw_name ); + d->userRealName = QLatin1String( userInfos->pw_gecos ); + return true; + } +} + +CTCron& CTCron::operator = (const CTCron& source) { + if (this == &source) + return *this; + + if (source.isSystemCron() == true) { + logDebug() << "Affect the system cron" << endl; + } + + d->variable.clear(); + foreach(CTVariable* ctVariable, source.variables()) { + CTVariable* tmp = new CTVariable(*ctVariable); + d->variable.append(tmp); + } + + d->task.clear(); + foreach(CTTask* ctTask, source.tasks()) { + CTTask* tmp = new CTTask(*ctTask); + d->task.append(tmp); + } + + return *this; +} + +void CTCron::parseFile(const QString& fileName) { + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return; + + QString comment; + bool leadingComment = true; + + QTextStream in(&file); + while (in.atEnd() == false) { + QString line = in.readLine(); + + // search for comments "#" but not disabled tasks "#\" + if ( line.indexOf(QLatin1String( "#" )) == 0 && line.indexOf(QLatin1String( "\\" )) != 1 ) { + // Skip leading comments with leading spaces, those are not written by KCron + if ( leadingComment && line.startsWith(QLatin1String( "# " ))) { + continue; + } + leadingComment = false; + // If the first 10 characters don't contain a character, it's probably a disabled entry. + int firstText = line.indexOf(QRegExp(QLatin1String( "\\w" ))); + if (firstText < 0) + continue; + + if (firstText < 10) { + // remove leading pound sign + line = line.mid(1, line.length()-1); + if (comment.isEmpty()) + comment = line.trimmed(); + else + comment += QLatin1String( "\n" ) + line.trimmed(); + continue; + } + } + + // either a task or a variable + int firstWhiteSpace(line.indexOf(QRegExp(QLatin1String( "[ \t]" )))); + int firstEquals(line.indexOf(QLatin1String( "=" ))); + + // if there is an equals sign and either there is no + // whitespace or the first whitespace is after the equals + // sign, it must be a variable + if ((firstEquals > 0) && ((firstWhiteSpace == -1) || firstWhiteSpace > firstEquals)) { + // create variable + CTVariable* tmp = new CTVariable(line, comment, d->userLogin); + d->variable.append(tmp); + comment.clear(); + } + // must be a task, either enabled or disabled + else { + if (firstWhiteSpace > 0) { + CTTask* tmp = new CTTask(line, comment, d->userLogin, d->multiUserCron); + d->task.append(tmp); + comment.clear(); + } + } + + + + } + +} + +QString CTCron::exportCron() const { + QString exportCron; + + + foreach(CTVariable* ctVariable, d->variable) { + exportCron += ctVariable->exportVariable(); + exportCron += QLatin1String( "\n" ); + } + + foreach(CTTask* ctTask, d->task) { + exportCron += ctTask->exportTask(); + exportCron += QLatin1String( "\n" ); + } + + exportCron += QLatin1String( "\n" ); + QString exportInfo = i18nc("Generation Message + current date", "File generated by KCron the %1.", KGlobal::locale()->formatDateTime(QDateTime::currentDateTime(), KLocale::LongDate)); + exportCron += QLatin1String( "# " ) + exportInfo + QLatin1String( "\n" ); + + return exportCron; +} + +CTCron::~CTCron() { + foreach(CTTask* ctTask, d->task) { + delete ctTask; + } + + foreach(CTVariable* ctVariable, d->variable) { + delete ctVariable; + } + + delete d; +} + +bool CTCron::saveToFile(const QString& fileName) { + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + return false; + } + + //logDebug() << exportCron() << endl; + + QTextStream out(&file); + out << exportCron(); + + out.flush(); + file.close(); + + return true; +} + +CTSaveStatus CTCron::prepareSaveStatusError(const CommandLineStatus& commandLineStatus) { + QString standardOutput; + if (commandLineStatus.standardOutput.isEmpty()) + standardOutput = i18n("No output."); + else + standardOutput = commandLineStatus.standardOutput; + + QString standardError; + if (commandLineStatus.standardError.isEmpty()) + standardError = i18n("No error."); + else + standardError = commandLineStatus.standardError; + + QString detailError; + if (commandLineStatus.exitCode == 127) + detailError = i18n("

Command: %1

Command could not be started", commandLineStatus.commandLine); + else + detailError = i18n("

Command: %1

Standard Output :
%2
Error Output :
%3
", commandLineStatus.commandLine, standardOutput, standardError); + + return CTSaveStatus(i18n("An error occurred while updating crontab."), detailError); +} + +CTSaveStatus CTCron::save() { + // write to temp file + bool saveStatus = saveToFile(d->tmpFileName); + if (saveStatus == false) { + return CTSaveStatus(i18n("Unable to open crontab file for writing"), i18n("The file %1 could not be opened.", d->tmpFileName)); + } + + CommandLineStatus commandLineStatus = d->writeCommandLine.execute(); + // install temp file into crontab + if (commandLineStatus.exitCode != 0) { + QFile::remove(d->tmpFileName); + return prepareSaveStatusError(commandLineStatus); + } + else { + //Remove the temp file + QFile::remove(d->tmpFileName); + } + + + //Mark as applied + foreach(CTTask* ctTask, d->task) { + ctTask->apply(); + } + + foreach(CTVariable* ctVariable, d->variable) { + ctVariable->apply(); + } + + d->initialTaskCount = d->task.size(); + d->initialVariableCount = d->variable.size(); + + return CTSaveStatus(); +} + +void CTCron::cancel() { + foreach(CTTask* ctTask, d->task) { + ctTask->cancel(); + } + + foreach(CTVariable* ctVariable, d->variable) { + ctVariable->cancel(); + } + +} + +bool CTCron::isDirty() const { + if (d->initialTaskCount != d->task.count()) + return true; + + if (d->initialVariableCount != d->variable.count()) + return true; + + foreach(CTTask* ctTask, d->task) { + if (ctTask->dirty()) + return true; + } + + foreach(CTVariable* ctVariable, d->variable) { + if (ctVariable->dirty()) + return true; + } + + return false; +} + +QString CTCron::path() const { + QString path; + + foreach(CTVariable* ctVariable, d->variable) { + if (ctVariable->variable == QLatin1String( "PATH" )) { + path = ctVariable->value; + } + } + + return path; + +} + +QList CTCron::tasks() const { + return d->task; +} + +QList CTCron::variables() const { + return d->variable; +} + +void CTCron::addTask(CTTask* task) { + if (isSystemCron()) { + task->setSystemCrontab(true); + } + else { + task->userLogin = d->userLogin; + task->setSystemCrontab(false); + } + + logDebug() << "Adding task" << task->comment << " user : "<< task->userLogin << endl; + + d->task.append(task); +} + +void CTCron::addVariable(CTVariable* variable) { + if (isSystemCron()) + variable->userLogin = QLatin1String( "root" ); + else + variable->userLogin = d->userLogin; + + + logDebug() << "Adding variable" << variable->variable << " user : "<< variable->userLogin << endl; + + d->variable.append(variable); +} + + +void CTCron::modifyTask(CTTask* /*task*/) { + //Nothing to do specifically when modifying a task +} + +void CTCron::modifyVariable(CTVariable* /*variable*/) { + //Nothing to do specifically when modifying a variable +} + +void CTCron::removeTask(CTTask* task) { + d->task.removeAll(task); +} + +void CTCron::removeVariable(CTVariable* variable) { + d->variable.removeAll(variable); +} + + +bool CTCron::isMultiUserCron() const { + return d->multiUserCron; +} + +bool CTCron::isCurrentUserCron() const { + return d->currentUserCron; +} + +bool CTCron::isSystemCron() const { + return d->systemCron; +} + + +QString CTCron::userLogin() const { + return d->userLogin; +} + +QString CTCron::userRealName() const { + return d->userRealName; +} + + diff --git a/kcron/src/crontablib/ctcron.h b/kcron/src/crontablib/ctcron.h new file mode 100644 index 00000000..6b9eadd9 --- /dev/null +++ b/kcron/src/crontablib/ctcron.h @@ -0,0 +1,219 @@ +/*************************************************************************** + * CT Cron Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CTCRON_H +#define CTCRON_H + +#include +#include +#include +#include + +class CTTask; +class CTVariable; +class CTInitializationError; + +struct passwd; + +#include "ctSaveStatus.h" + +class CommandLineStatus { +public: + int exitCode; + + QString commandLine; + + QString standardOutput; + QString standardError; +}; + +class CommandLine { +public: + QString commandLine; + + QStringList parameters; + + QString standardOutputFile; + + CommandLineStatus execute(); +}; + +class CTCronPrivate { +public: + + /** + * Indicates whether or not the crontab belongs to the system. + */ + bool systemCron; + + /** + * Indicates if this cron could have tasks and variables from different user + */ + bool multiUserCron; + + /** + * Indicates whether or not the crontab belongs to the current user. + */ + bool currentUserCron; + + /** + * User login. + */ + QString userLogin; + + /** + * User real name. + */ + QString userRealName; + + /** + * User's scheduled tasks. + */ + QList task; + + /** + * User's environment variables. Note: These are only environment variables + * found in the user's crontab file and does not include any set in a + * login or shell script such as ".bash_profile". + */ + QList variable; + + int initialTaskCount; + int initialVariableCount; + + CommandLine writeCommandLine; + + QString tmpFileName; + + /** + * Contains path to the crontab binary file + */ + QString crontabBinary; + +}; + +/** + * A user (encapsulation of a single crontab file). Encapsulates + * file i/o, parsing, tokenization, and natural language description. + */ +class CTCron { +public: + + /** + * If you already have a struct passwd, use it instead. This + * is never used for the system crontab. + */ + explicit CTCron(const QString& cronBinary, const struct passwd* userInfos, bool currentUserCron, CTInitializationError& ctInitializationError); + + /** + * Destructor. + */ + virtual ~CTCron(); + + /** + * Copy one user's tasks and environement variables to another user. + */ + CTCron& operator =(const CTCron& source); + + virtual QList tasks() const; + + virtual QList variables() const; + + virtual void addTask(CTTask* task); + virtual void addVariable(CTVariable* variable); + + virtual void modifyTask(CTTask* task); + virtual void modifyVariable(CTVariable* variable); + + virtual void removeVariable(CTVariable* variable); + virtual void removeTask(CTTask* task); + + /** + * Tokenizes to crontab file format. + */ + QString exportCron() const; + + /** + * Apply changes. + */ + CTSaveStatus save(); + + /** + * Cancel changes. + */ + void cancel(); + + /** + * Indicates whether or not dirty. + */ + bool isDirty() const; + + /** + * Returns the PATH environment variable value. A short cut to iterating + * the tasks vector. + */ + QString path() const; + + /** + * Returns true if this cron could have tasks and variables from different user + */ + bool isMultiUserCron() const; + + /** + * Returns true if this cron is the system cron + */ + bool isSystemCron() const; + + /** + * Returns true if this cron is the cron of the user who launches this app + */ + bool isCurrentUserCron() const; + + QString userLogin() const; + + /** + * Bugged method for the moment (need to parse x,x,x,x data from /etc/passwd) + */ + QString userRealName() const; + +protected: + + /** + * Help constructor for subclasses + */ + explicit CTCron(); + + // Initialize member variables from the struct passwd. + bool initializeFromUserInfos(const struct passwd* userInfos); +private: + + /** + * Can't copy a user! + */ + CTCron(const CTCron& source); + + CTSaveStatus prepareSaveStatusError(const CommandLineStatus& commandLineStatus); + +protected: + + /** + * Parses crontab file format. + */ + void parseFile(const QString& fileName); + + bool saveToFile(const QString& fileName); + + CTCronPrivate* const d; + + +}; + +#endif // CTCRON_H diff --git a/kcron/src/crontablib/ctdom.cpp b/kcron/src/crontablib/ctdom.cpp new file mode 100644 index 00000000..be9e4bb9 --- /dev/null +++ b/kcron/src/crontablib/ctdom.cpp @@ -0,0 +1,36 @@ +/*************************************************************************** + * CT Day of Month Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "ctdom.h" +#include + +QList CTDayOfMonth::shortName; + +CTDayOfMonth::CTDayOfMonth(const QString& tokStr) : + CTUnit(CTDayOfMonth::MINIMUM, CTDayOfMonth::MAXIMUM, tokStr) { +} + +QString CTDayOfMonth::describe() const { + initializeNames(); + return (enabledCount() == CTDayOfMonth::MAXIMUM) ? i18n("every day ") : CTUnit::genericDescribe(shortName); +} + +QString CTDayOfMonth::getName(const int ndx) { + initializeNames(); + return shortName[ndx]; +} + +void CTDayOfMonth::initializeNames() { + if (shortName.isEmpty()) { + shortName << QLatin1String( "" ) << i18n("1st") << i18n("2nd") << i18n("3rd") << i18n("4th") << i18n("5th") << i18n("6th") << i18n("7th") << i18n("8th") << i18n("9th") << i18n("10th") << i18n("11th") << i18n("12th") << i18n("13th") << i18n("14th") << i18n("15th") << i18n("16th") << i18n("17th") + << i18n("18th") << i18n("19th") << i18n("20th") << i18n("21st") << i18n("22nd") << i18n("23rd") << i18n("24th") << i18n("25th") << i18n("26th") << i18n("27th") << i18n("28th") << i18n("29th") << i18n("30th") << i18n("31st"); + } +} diff --git a/kcron/src/crontablib/ctdom.h b/kcron/src/crontablib/ctdom.h new file mode 100644 index 00000000..671a059c --- /dev/null +++ b/kcron/src/crontablib/ctdom.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * CT Day of Month Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CTDOM_H +#define CTDOM_H + +#include +#include + +#include "ctunit.h" + +/** + * Scheduled task days of month. + */ +class CTDayOfMonth : public CTUnit { +public: + /** + * Constructs from a tokenized string. + */ + CTDayOfMonth(const QString& tokStr = QLatin1String("")); + + /** + * Get natural language description. + */ + virtual QString describe() const; + + /** + * Get day of month name. + */ + static QString getName(const int ndx); + + static const int MINIMUM = 1; + static const int MAXIMUM = 31; +private: + static void initializeNames(); + static QList shortName; +}; + +#endif // CTDOM_H diff --git a/kcron/src/crontablib/ctdow.cpp b/kcron/src/crontablib/ctdow.cpp new file mode 100644 index 00000000..562e05f1 --- /dev/null +++ b/kcron/src/crontablib/ctdow.cpp @@ -0,0 +1,61 @@ +/*************************************************************************** + * CT Day Of Week Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "ctdow.h" +#include + +QList CTDayOfWeek::shortName; + +QList CTDayOfWeek::longName; + +CTDayOfWeek::CTDayOfWeek(const QString& tokStr) : + CTUnit(CTDayOfWeek::MINIMUM, CTDayOfWeek::MAXIMUM, tokStr) { + // Compensate for cron supporting Sunday as both 0 and 7. + + if (isEnabled(0)) { + setEnabled(0, false); + setEnabled(7, true); + } +} + +void CTDayOfWeek::initialize(const QString &tokStr) { + CTUnit::initialize(tokStr); + + // Compensate for cron supporting Sunday as both 0 and 7. + + if (isEnabled(0)) { + setEnabled(0, false); + setEnabled(7, true); + apply(); + } +} + +QString CTDayOfWeek::describe() const { + initializeNames(); + if (enabledCount() == CTDayOfWeek::MAXIMUM) + return i18n("every day "); + else + return CTUnit::genericDescribe(shortName); +} + +QString CTDayOfWeek::getName(const int ndx, const bool format) { + initializeNames(); + return (format == shortFormat) ? shortName[ndx] : longName[ndx]; +} + +void CTDayOfWeek::initializeNames() { + if (shortName.isEmpty()) { + shortName << QLatin1String( "" ) << i18n("Mon") << i18n("Tue") << i18n("Wed") << i18n("Thu") << i18n("Fri") << i18n("Sat") << i18n("Sun"); + + longName << QLatin1String( "" ) << i18n("Monday") << i18n("Tuesday") << i18n("Wednesday") << i18n("Thursday") << i18n("Friday") << i18n("Saturday") << i18n("Sunday"); + + } +} diff --git a/kcron/src/crontablib/ctdow.h b/kcron/src/crontablib/ctdow.h new file mode 100644 index 00000000..983ceb8e --- /dev/null +++ b/kcron/src/crontablib/ctdow.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * CT Day of Week Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CTDOW_H +#define CTDOW_H + +#include +#include + +#include "ctunit.h" + +/** + * Scheduled task days of week. + */ +class CTDayOfWeek : public CTUnit { +public: + /** + * Constructs from a tokenized string. + */ + CTDayOfWeek(const QString& tokStr = QLatin1String("")); + + /** + * Override initialize to support crontab using both 0 and 7 for + * Sunday. + */ + void initialize(const QString& tokStr = QLatin1String("")); + + /** + * Get natural language description. + */ + virtual QString describe() const; + + /** + * Get day of week name. + */ + static QString getName(const int ndx, const bool format = CTDayOfWeek::longFormat); + + static const int MINIMUM = 1; + static const int MAXIMUM = 7; + +private: + static void initializeNames(); + static QList shortName; + static QList longName; +}; + +#endif // CTDOW_H diff --git a/kcron/src/crontablib/cthost.cpp b/kcron/src/crontablib/cthost.cpp new file mode 100644 index 00000000..a0a95f44 --- /dev/null +++ b/kcron/src/crontablib/cthost.cpp @@ -0,0 +1,249 @@ +/*************************************************************************** + * -------------------------------------------------------------------- * + * CT Host Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "cthost.h" + +#include // getuid() +#include +#include + +#include +#include + +#include + +#include "ctcron.h" +#include "ctSystemCron.h" +#include "ctInitializationError.h" + + +#include "logging.h" + +CTHost::CTHost(const QString& cronBinary, CTInitializationError& ctInitializationError) { + struct passwd* userInfos = NULL; + + this->crontabBinary = cronBinary; + + // If it is the root user + if (getuid() == 0) { + // Read /etc/passwd + setpwent(); // restart + while ((userInfos=getpwent())) { + if (allowDeny(userInfos->pw_name)) { + QString errorMessage = createCTCron(userInfos); + if (errorMessage.isEmpty()==false) { + ctInitializationError.setErrorMessage(errorMessage); + return; + } + } + //delete userInfos; + } + setpwent(); // restart again for others + } + // Non-root user, so just create user's cron table. + else { + + // Get name from UID, check it against AllowDeny() + unsigned int uid = getuid(); + setpwent(); // restart + while ((userInfos=getpwent())) { + if ((userInfos->pw_uid == uid) && (!allowDeny(userInfos->pw_name))) { + ctInitializationError.setErrorMessage(i18n("You have been blocked from using KCron\ + by either the /etc/cron.allow file or the /etc/cron.deny file.\ + \n\nCheck the crontab man page for further details.") + ); + + return; + } + //delete userInfos; + } + + setpwent(); // restart again for others + + passwd* currentUserPassword = getpwuid(uid); + + QString errorMessage = createCTCron(currentUserPassword); + if (errorMessage.isEmpty()==false) { + ctInitializationError.setErrorMessage(errorMessage); + return; + } + + //delete currentUserPassword; + } + + // Create the system cron table. + createSystemCron(); + +} + +CTHost::~CTHost() { + foreach(CTCron* ctCron, crons) { + delete ctCron; + } +} + +bool CTHost::allowDeny(char *name) { + QFile allow(QLatin1String( "/etc/cron.allow" )); + + // if cron.allow exists make sure user is listed + if (allow.open(QFile::ReadOnly)) { + QTextStream stream(&allow); + while (!stream.atEnd()) { + if (stream.readLine() == QLatin1String( name )) { + allow.close(); + return true; + } + } + allow.close(); + return false; + } else { + allow.close(); + QFile deny(QLatin1String( "/etc/cron.deny" )); + + // else if cron.deny exists make sure user is not listed + if (deny.open(QFile::ReadOnly)) { + QTextStream stream(&deny); + while (!stream.atEnd()) { + if (stream.readLine() == QLatin1String( name )) { + deny.close(); + return false; + } + } + deny.close(); + return true; + } else { + deny.close(); + return true; + } + } +} + +CTSaveStatus CTHost::save() { + if (isRootUser() == false) { + logDebug() << "Save current user cron" << endl; + CTCron* ctCron = findCurrentUserCron(); + + return ctCron->save(); + } + + foreach(CTCron* ctCron, crons) { + CTSaveStatus ctSaveStatus = ctCron->save(); + + if (ctSaveStatus.isError() == true) { + return CTSaveStatus(i18nc("User login: errorMessage", "User %1: %2", ctCron->userLogin(), ctSaveStatus.errorMessage()), ctSaveStatus.detailErrorMessage()); + } + } + + return CTSaveStatus(); +} + +void CTHost::cancel() { + foreach(CTCron* ctCron, crons) { + ctCron->cancel(); + } +} + +bool CTHost::isDirty() { + bool isDirty = false; + + foreach(CTCron* ctCron, crons) { + if (ctCron->isDirty()) { + isDirty = true; + } + } + + return isDirty; +} + +CTCron* CTHost::createSystemCron() { + CTCron* p = new CTSystemCron(crontabBinary); + + crons.append(p); + + return p; +} + + +QString CTHost::createCTCron(const struct passwd* userInfos) { + bool currentUserCron = false; + if (userInfos->pw_uid == getuid()) + currentUserCron = true; + + CTInitializationError ctInitializationError; + CTCron* p = new CTCron(crontabBinary, userInfos, currentUserCron, ctInitializationError); + if (ctInitializationError.hasErrorMessage()) { + delete p; + return ctInitializationError.errorMessage(); + } + + crons.append(p); + + return QString(); +} + +CTCron* CTHost::findCurrentUserCron() const { + foreach(CTCron* ctCron, crons) { + if (ctCron->isCurrentUserCron()) + return ctCron; + } + + logDebug() << "Unable to find the current user Cron. Please report this bug and your crontab config to the developers" << endl; + return NULL; +} + +CTCron* CTHost::findSystemCron() const { + foreach(CTCron* ctCron, crons) { + if (ctCron->isMultiUserCron()) + return ctCron; + } + + logDebug() << "Unable to find the system Cron. Please report this bug and your crontab config to the developers" << endl; + return NULL; +} + +CTCron* CTHost::findUserCron(const QString& userLogin) const { + foreach(CTCron* ctCron, crons) { + if (ctCron->userLogin() == userLogin) + return ctCron; + } + + logDebug() << "Unable to find the user Cron " << userLogin << ". Please report this bug and your crontab config to the developers" << endl; + return NULL; +} + +CTCron* CTHost::findCronContaining(CTTask* ctTask) const { + foreach(CTCron* ctCron, crons) { + if (ctCron->tasks().contains(ctTask) == true) { + return ctCron; + } + } + + logDebug() << "Unable to find the cron of this task. Please report this bug and your crontab config to the developers" << endl; + return NULL; + +} + +CTCron* CTHost::findCronContaining(CTVariable* ctVariable) const { + foreach(CTCron* ctCron, crons) { + if (ctCron->variables().contains(ctVariable) == true) { + return ctCron; + } + } + + logDebug() << "Unable to find the cron of this variable. Please report this bug and your crontab config to the developers" << endl; + return NULL; + +} + +bool CTHost::isRootUser() const { + return (getuid() == 0); +} diff --git a/kcron/src/crontablib/cthost.h b/kcron/src/crontablib/cthost.h new file mode 100644 index 00000000..fafecd83 --- /dev/null +++ b/kcron/src/crontablib/cthost.h @@ -0,0 +1,119 @@ +/*************************************************************************** + * CT Host Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CTHOST_H +#define CTHOST_H + +#include +#include + +#include "ctSaveStatus.h" + +class CTTask; +class CTVariable; +class CTCron; +class CTInitializationError; + +struct passwd; + +/** + * The host machine, or computer (encapsulation of crontab files on the + * host). + * + * If the user is the root user, the cron vector will have a member for + * each user of the host plus one for the system crontab. + * + * If the user is a non-root user, there will be only one member in the + * cron vector. + */ +class CTHost { +public: + + /** + * Constructs the user(s), scheduled tasks, and environment variables + * from crontab files. + */ + CTHost(const QString& cronBinary, CTInitializationError& ctInitializationError); + + /** + * Destroys the user(s), scheduled tasks, and environment variable + * objects. Does not make any changes to the crontab files. Any unapplied + * changes are consequently "cancelled." + */ + ~CTHost(); + + /** + * Apply changes. + * return an empty string if no problem + */ + CTSaveStatus save(); + + /** + * Cancel changes. + */ + void cancel(); + + /** + * Indicates whether or not dirty. + */ + bool isDirty(); + + /** + * Indicates whether or not the user is the root user. + */ + bool isRootUser() const; + + CTCron* findCurrentUserCron() const; + CTCron* findSystemCron() const; + CTCron* findUserCron(const QString& userLogin) const; + + CTCron* findCronContaining(CTTask* ctTask) const; + CTCron* findCronContaining(CTVariable* ctVariable) const; + + /** + * User(s). + * + * If the user is the root user, the cron vector will have a member for + * each user of the host plus one for the system crontab. + * + * If the user is a non-root user, there will be only one member in the + * cron vector. + */ + QList crons; + +private: + + /** + * Copy construction not allowed. + */ + CTHost(const CTHost& source); + + /** + * Assignment not allowed + */ + CTHost& operator =(const CTHost& source); + + /** + * Factory create a cron table. Appends to the end of cron. + */ + CTCron* createSystemCron(); + CTCron* createCurrentUserCron(); + QString createCTCron(const struct passwd* password); + + /** + * Check /etc/cron.allow, /etc/cron.deny + */ + bool allowDeny(char *name); + + QString crontabBinary; +}; + +#endif // CTHOST_H diff --git a/kcron/src/crontablib/cthour.cpp b/kcron/src/crontablib/cthour.cpp new file mode 100644 index 00000000..1ec438d4 --- /dev/null +++ b/kcron/src/crontablib/cthour.cpp @@ -0,0 +1,37 @@ +/*************************************************************************** + * CT Month Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "cthour.h" + +#include + +/** + * Constructs from a tokenized string. + */ +CTHour::CTHour(const QString& tokStr) : + CTUnit(0, 23, tokStr) { +} + + +int CTHour::findPeriod() const { + QList periods; + periods << 2 << 3 << 4 << 6 << 8; + + return CTUnit::findPeriod(periods); +} + +QString CTHour::exportUnit() const { + int period = findPeriod(); + if (period!=0 && period!=1) + return QString(QLatin1String( "*/%1" )).arg(QString::number(period)); + + return CTUnit::exportUnit(); +} diff --git a/kcron/src/crontablib/cthour.h b/kcron/src/crontablib/cthour.h new file mode 100644 index 00000000..e9334d4a --- /dev/null +++ b/kcron/src/crontablib/cthour.h @@ -0,0 +1,33 @@ +/*************************************************************************** + * CT Hour Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CTHOUR_H +#define CTHOUR_H + +#include "ctunit.h" + +/** + * Scheduled task hours. + */ +class CTHour : public CTUnit { +public: + + /** + * Constructs from a tokenized string. + */ + CTHour(const QString& tokStr = QLatin1String("")); + + int findPeriod() const; + + QString exportUnit() const; +}; + +#endif // CTHOUR_H diff --git a/kcron/src/crontablib/ctminute.cpp b/kcron/src/crontablib/ctminute.cpp new file mode 100644 index 00000000..f0661080 --- /dev/null +++ b/kcron/src/crontablib/ctminute.cpp @@ -0,0 +1,41 @@ +/*************************************************************************** + * CT Month Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "ctminute.h" + +#include + +/** + * Constructs from a tokenized string. + */ +CTMinute::CTMinute(const QString& tokStr) : + CTUnit(0, 59, tokStr) { +} + +CTMinute::CTMinute() : + CTUnit(0, 59, QLatin1String( "" )) { + +} + +int CTMinute::findPeriod() const { + QList periods; + periods << 1 << 2 << 5 << 10 << 15 << 20 << 30; + + return CTUnit::findPeriod(periods); +} + +QString CTMinute::exportUnit() const { + int period = findPeriod(); + if (period!=0 && period!=1) + return QString(QLatin1String( "*/%1" )).arg(QString::number(period)); + + return CTUnit::exportUnit(); +} diff --git a/kcron/src/crontablib/ctminute.h b/kcron/src/crontablib/ctminute.h new file mode 100644 index 00000000..ac53163c --- /dev/null +++ b/kcron/src/crontablib/ctminute.h @@ -0,0 +1,36 @@ +/*************************************************************************** + * CT Minute Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CTMINUTE_H +#define CTMINUTE_H + +#include "ctunit.h" + +/** + * Scheduled task minutes. + */ +class CTMinute : public CTUnit { +public: + + /** + * Constructs from a tokenized string. + */ + CTMinute(const QString& tokStr); + + CTMinute(); + + int findPeriod() const; + + QString exportUnit() const; + +}; + +#endif // CTMINUTE_H diff --git a/kcron/src/crontablib/ctmonth.cpp b/kcron/src/crontablib/ctmonth.cpp new file mode 100644 index 00000000..5f9b0def --- /dev/null +++ b/kcron/src/crontablib/ctmonth.cpp @@ -0,0 +1,35 @@ +/*************************************************************************** + * CT Month Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "ctmonth.h" +#include + +QList CTMonth::shortName; + +CTMonth::CTMonth(const QString& tokStr) : + CTUnit(CTMonth::MINIMUM, CTMonth::MAXIMUM, tokStr) { +} + +QString CTMonth::describe() const { + initializeNames(); + return (enabledCount() == CTMonth::MAXIMUM) ? i18n("every month") : CTUnit::genericDescribe(shortName); +} + +QString CTMonth::getName(const int ndx) { + initializeNames(); + return shortName[ndx]; +} + +void CTMonth::initializeNames() { + if (shortName.isEmpty()) { + shortName << QLatin1String( "" ) << i18n("January") << i18n("February") << i18n("March") << i18n("April") << i18nc("May long", "May") << i18n("June") << i18nc("July long", "July") << i18n("August") << i18n("September") << i18n("October") << i18n("November") << i18n("December"); + } +} diff --git a/kcron/src/crontablib/ctmonth.h b/kcron/src/crontablib/ctmonth.h new file mode 100644 index 00000000..8c4baee8 --- /dev/null +++ b/kcron/src/crontablib/ctmonth.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * CT Month Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CTMONTH_H +#define CTMONTH_H + +#include +#include + +#include "ctunit.h" + +/** + * Scheduled task months. + */ +class CTMonth : public CTUnit { +public: + + /** + * Constructs from a tokenized string. + */ + CTMonth(const QString& tokStr = QLatin1String("")); + + /** + * Get natural language description. + */ + virtual QString describe() const; + + /** + * Get month name. + */ + static QString getName(const int ndx); + + static const int MINIMUM = 1; + static const int MAXIMUM = 12; +private: + static void initializeNames(); + static QList shortName; +}; + +#endif // CTMONTH_H diff --git a/kcron/src/crontablib/cttask.cpp b/kcron/src/crontablib/cttask.cpp new file mode 100644 index 00000000..73fb44ce --- /dev/null +++ b/kcron/src/crontablib/cttask.cpp @@ -0,0 +1,462 @@ +/*************************************************************************** + * CT Task Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "cttask.h" + +#include +#include +#include + +#include "ctHelper.h" + +#include "kcronIcons.h" +#include "logging.h" + +CTTask::CTTask(const QString& tokenString, const QString& _comment, const QString& _userLogin, bool _systemCrontab) : + systemCrontab(_systemCrontab) { + + QString tokStr = tokenString; + if (tokStr.mid(0, 2) == QLatin1String( "#\\" )) { + tokStr = tokStr.mid(2, tokStr.length() - 2); + enabled = false; + } else if (tokStr.mid(0, 1) == QLatin1String( "#" )) { + tokStr = tokStr.mid(1, tokStr.length() - 1); + enabled = false; + } else + enabled = true; + + // Skip over 'silence' if found... old option in vixie cron + if (tokStr.mid(0, 1) == QLatin1String( "-" )) + tokStr = tokStr.mid(1, tokStr.length() - 1); + + reboot = false; + if (tokStr.mid(0, 1) == QLatin1String( "@" )) { + if (tokStr.mid(1, 6) == QLatin1String( "yearly" )) { + tokStr = QLatin1String( "0 0 1 1 *" )+tokStr.mid(7, tokStr.length()-1); + } else if (tokStr.mid(1, 8) == QLatin1String( "annually" )) { + tokStr = QLatin1String( "0 0 1 1 *" )+tokStr.mid(9, tokStr.length()-1); + } else if (tokStr.mid(1, 7) == QLatin1String( "monthly" )) { + tokStr = QLatin1String( "0 0 1 * *" )+tokStr.mid(8, tokStr.length()-1); + } else if (tokStr.mid(1, 6) == QLatin1String( "weekly" )) { + tokStr = QLatin1String( "0 0 * * 0" )+tokStr.mid(7, tokStr.length()-1); + } else if (tokStr.mid(1, 5) == QLatin1String( "daily" )) { + tokStr = QLatin1String( "0 0 * * *" )+tokStr.mid(6, tokStr.length()-1); + } else if (tokStr.mid(1, 6) == QLatin1String( "hourly" )) { + tokStr = QLatin1String( "0 * * * *" )+tokStr.mid(7, tokStr.length()-1); + } else if (tokStr.mid(1, 6) == QLatin1String( "reboot" )) { + tokStr = tokStr.mid(7, tokStr.length()-1); + reboot = true; + } + } + + int spacePos(tokStr.indexOf(QRegExp(QLatin1String( "[ \t]" )))); + // If reboot bypass initialize functions so no keys selected in modify task + if (reboot == false) { + + //logDebug() << "Line : " << tokStr << endl; + minute.initialize(tokStr.mid(0, spacePos)); + + while (isSpace(tokStr, spacePos+1)) + spacePos++; + tokStr = tokStr.mid(spacePos+1, tokStr.length()-1); + spacePos = tokStr.indexOf(QRegExp(QLatin1String( "[ \t]" ))); + hour.initialize(tokStr.mid(0, spacePos)); + + while (isSpace(tokStr, spacePos+1)) + spacePos++; + tokStr = tokStr.mid(spacePos+1, tokStr.length()-1); + spacePos = tokStr.indexOf(QRegExp(QLatin1String( "[ \t]" ))); + dayOfMonth.initialize(tokStr.mid(0, spacePos)); + + while (isSpace(tokStr, spacePos+1)) + spacePos++; + tokStr = tokStr.mid(spacePos+1, tokStr.length()-1); + spacePos = tokStr.indexOf(QRegExp(QLatin1String( "[ \t]" ))); + month.initialize(tokStr.mid(0, spacePos)); + + while (isSpace(tokStr, spacePos+1)) + spacePos++; + tokStr = tokStr.mid(spacePos+1, tokStr.length()-1); + spacePos = tokStr.indexOf(QRegExp(QLatin1String( "[ \t]" ))); + dayOfWeek.initialize(tokStr.mid(0, spacePos)); + } + + if (systemCrontab) { + while (isSpace(tokStr, spacePos+1)) + spacePos++; + tokStr = tokStr.mid(spacePos+1, tokStr.length()-1); + spacePos = tokStr.indexOf(QRegExp(QLatin1String( "[ \t]" ))); + userLogin = tokStr.mid(0, spacePos); + } + else { + userLogin = _userLogin; + } + + command = tokStr.mid(spacePos+1, tokStr.length()-1); + // remove leading whitespace + while (command.indexOf(QRegExp(QLatin1String( "[ \t]" ))) == 0) + command = command.mid(1, command.length()-1); + comment = _comment; + + initialUserLogin = userLogin; + initialCommand = command; + initialComment = comment; + initialEnabled = enabled; + initialReboot = reboot; +} + +CTTask::CTTask(const CTTask &source) : + month(source.month), dayOfMonth(source.dayOfMonth), dayOfWeek(source.dayOfWeek), hour(source.hour), minute(source.minute), userLogin(source.userLogin), command(source.command), comment(source.comment), enabled(source.enabled), reboot(source.reboot), initialUserLogin(QLatin1String( "" )), initialCommand(QLatin1String( "" )), + initialComment(QLatin1String( "" )), initialEnabled(true), initialReboot(false) { +} + +CTTask& CTTask::operator = (const CTTask& source) { + if (this == &source) + return *this; + + month = source.month; + dayOfMonth = source.dayOfMonth; + dayOfWeek = source.dayOfWeek; + hour = source.hour; + minute = source.minute; + userLogin = source.userLogin; + command = source.command; + comment = source.comment; + enabled = source.enabled; + reboot = source.reboot; + initialUserLogin = QLatin1String( "" ); + initialCommand = QLatin1String( "" ); + initialComment = QLatin1String( "" ); + initialEnabled = true; + initialReboot = false; + + return *this; +} + +QString CTTask::exportTask() { + QString exportTask; + + exportTask += CTHelper::exportComment(comment); + + if (enabled == false) + exportTask += QLatin1String( "#\\" ); + + exportTask += schedulingCronFormat(); + exportTask += QLatin1String( "\t" ); + + if (isSystemCrontab() == true) + exportTask += userLogin + QLatin1String( "\t" ); + + exportTask += command + QLatin1String( "\n" ); + + return exportTask; +} + +void CTTask::apply() { + month.apply(); + dayOfMonth.apply(); + dayOfWeek.apply(); + hour.apply(); + minute.apply(); + + initialUserLogin = userLogin; + initialCommand = command; + initialComment = comment; + initialEnabled = enabled; + initialReboot = reboot; +} + +void CTTask::cancel() { + month.cancel(); + dayOfMonth.cancel(); + dayOfWeek.cancel(); + hour.cancel(); + minute.cancel(); + + userLogin = initialUserLogin; + command = initialCommand; + comment = initialComment; + enabled = initialEnabled; + reboot = initialReboot; +} + +bool CTTask::dirty() const { + return (month.isDirty() || dayOfMonth.isDirty() || dayOfWeek.isDirty() || hour.isDirty() || minute.isDirty() || (userLogin != initialUserLogin) || (command != initialCommand) || (comment != initialComment) || (enabled != initialEnabled) || (reboot != initialReboot)); +} + +QString CTTask::schedulingCronFormat() const { + if (reboot) { + return QLatin1String( "@reboot" ); + } + + QString scheduling; + + scheduling += minute.exportUnit() + QLatin1String( " " ); + scheduling += hour.exportUnit() + QLatin1String( " " ); + scheduling += dayOfMonth.exportUnit() + QLatin1String( " " ); + scheduling += month.exportUnit() + QLatin1String( " " ); + scheduling += dayOfWeek.exportUnit(); + + return scheduling; + +} + +/** + * Of the whole program, this method is probably the trickiest. + * + * This method creates the natural language description, such as + * "At 1:00am, every Sun". + * + * First, I declare some strings for holding what can be internationalized. + * Note the tokens such as "MONTHS". Translators should reuse these + * tokens in their translations. See README.translators for more + * information. + * + * Second, I get the descriptions from the component parts such as + * days of the month. + * + * Third, I get hour/minute time combinations. Although a little bit + * awkward, I use the tm struct and strftime from . This + * way this code is portable across all Unixes. + * + * Fourth, I know that "every day of the week" and "every day of the + * month" simply makes "every day". + * + * Fifth and finally I do tag substitution to create the natural language + * description. + * + */ +QString CTTask::describe() const { + + if (reboot) { + return i18n("At system startup"); + } + + QString dateFormat = createDateFormat(); + + QString timeFormat = createTimeFormat(); + + return i18nc("1:Time Description, 2:Date Description", "%1, %2", timeFormat, dateFormat); +} + +QString CTTask::describeDayOfWeek() const { + return i18nc("Every 'days of week'", "every %1", dayOfWeek.describe()); +} + +QString CTTask::describeDayOfMonth() const { + return i18nc("'Days of month' of 'Months'", "%1 of %2", dayOfMonth.describe(), month.describe()); +} + +QString CTTask::createDateFormat() const { + /* + * "* * *" means truly every day. + * Note: Languages may use different phrases to indicate + * every day of month versus every day of week. + */ + QString dateFormat; + if ((dayOfMonth.enabledCount() == CTDayOfMonth::MAXIMUM) && (dayOfWeek.enabledCount() == CTDayOfWeek::MAXIMUM)) { + dateFormat = i18n("every day "); + } + // Day of month not specified, so use day of week. + else if (dayOfMonth.enabledCount() == CTDayOfMonth::MAXIMUM) { + dateFormat = describeDayOfWeek(); + } + // Day of week not specified, so use day of month. + else if (dayOfWeek.enabledCount() == CTDayOfWeek::MAXIMUM) { + dateFormat = describeDayOfMonth(); + } + else { + dateFormat = i18nc("1:Day of month, 2:Day of week", "%1 as well as %2", describeDayOfMonth(), describeDayOfWeek()); + } + + return dateFormat; + +} + +QString CTTask::describeDateAndHours() const { + // Create time description. + int total = minute.enabledCount() * hour.enabledCount(); + + QString timeDesc; + int count = 0; + + for (int h = 0; h <= 23; h++) { + if (hour.isEnabled(h)) { + for (int m = 0; m <= 59; m++) { + if (minute.isEnabled(m) == true) { + QString hourString; + if (h<10) + hourString = QLatin1String( "0" ) + QString::number(h); + else + hourString = QString::number(h); + + QString minuteString; + if (m<10) + minuteString = QLatin1String( "0" ) + QString::number(m); + else + minuteString = QString::number(m); + + QString tmpStr = i18nc("1:Hour, 2:Minute", "%1:%2", hourString, minuteString); + + timeDesc += tmpStr; + count++; + switch (total - count) { + case 0: + break; + case 1: + if (total > 2) + timeDesc += i18n(", and "); + else + timeDesc += i18n(" and "); + break; + default: + timeDesc += i18n(", "); + } + } + } + } + } + + + return i18nc("Hour::Minute list", "At %1", timeDesc); + +} + +QString CTTask::createTimeFormat() const { + if (hour.isAllEnabled()) { + int minutePeriod = minute.findPeriod(); + if (minutePeriod != 0) + return i18np("Every minute", "Every %1 minutes", minutePeriod); + } + + return describeDateAndHours(); +} + +bool CTTask::isSystemCrontab() const { + return systemCrontab; +} + +void CTTask::setSystemCrontab(bool _systemCrontab) { + systemCrontab = _systemCrontab; +} + + +QPixmap CTTask::commandIcon() const { + KUrl commandPath(completeCommandPath()); + + KMimeType::Ptr mimeType = KMimeType::findByUrl(commandPath); + //logDebug() << mimeType->name() << endl; + if (mimeType->name() == QLatin1String( "application/x-executable" ) || mimeType->name() == QLatin1String( "application/octet-stream" )) { + + //The next line is identical as SmallIcon(commandPath.fileName()), but is able to return a isNull() QPixmap + QPixmap icon = KIconLoader::global()->loadIcon(commandPath.fileName(), KIconLoader::Small, 0, KIconLoader::DefaultState, QStringList(), 0L, true); + if (icon.isNull()) { + return KCronIcons::task(KCronIcons::Small); + } + + return icon; + } + + QPixmap icon = SmallIcon(KMimeType::iconNameForUrl(commandPath)); + + return icon; + +} + +QPair CTTask::unQuoteCommand() const { + QString fullCommand = command; + fullCommand = fullCommand.trimmed(); + + QStringList quotes; + quotes << QLatin1String( "\"" ) << QLatin1String( "'" ); + + foreach(const QString "e, quotes) { + if (fullCommand.indexOf(quote) == 0) { + int nextQuote = fullCommand.indexOf(quote, 1); + if (nextQuote == -1) + return QPair(QLatin1String( "" ), false); + + return QPair(fullCommand.mid(1, nextQuote-1), true); + } + + } + + return QPair(fullCommand, false); + +} + +QString CTTask::decryptBinaryCommand(const QString& command) const { + QString fullCommand; + + bool found = false; + for (int i=0; i commandQuoted = unQuoteCommand(); + if (commandQuoted.first.isEmpty()) + return QLatin1String( "" ); + + QStringList pathCommand = separatePathCommand(commandQuoted.first, commandQuoted.second); + if (pathCommand.isEmpty()) { + return QLatin1String( "" ); + } + + return pathCommand.join(QLatin1String( "/" )); +} diff --git a/kcron/src/crontablib/cttask.h b/kcron/src/crontablib/cttask.h new file mode 100644 index 00000000..0ecdf324 --- /dev/null +++ b/kcron/src/crontablib/cttask.h @@ -0,0 +1,137 @@ +/*************************************************************************** + * CT Task Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CTTASK_H +#define CTTASK_H + +#include +#include +#include +#include + +#include "ctmonth.h" +#include "ctdom.h" +#include "ctdow.h" +#include "cthour.h" +#include "ctminute.h" + +/** + * A scheduled task (encapsulation of crontab entry). Encapsulates + * parsing, tokenization, and natural language description. + */ +class CTTask { +public: + + /** + * Constructs scheduled task from crontab format string. + */ + explicit CTTask(const QString& tokenString, const QString& _comment, const QString& _userLogin, bool syscron = false); + + /** + * Copy constructor. + */ + CTTask(const CTTask& source); + + /** + * Assignment operator. + */ + CTTask& operator =(const CTTask& source); + + /** + * Tokenizes scheduled task to crontab format. + */ + QString exportTask(); + + /** + * Scheduling using the cron format + */ + QString schedulingCronFormat() const; + + /** + * Mark changes as applied. + */ + void apply(); + + /** + * Cancel changes. + */ + void cancel(); + + /** + * Indicates whether or not the task has been modified. + */ + bool dirty() const; + + /** + * Returns natural language description of the task's schedule. + */ + QString describe() const; + + /** + * Indicates whether or not the task belongs to the system crontab. + */ + bool isSystemCrontab() const; + + void setSystemCrontab(bool systemCrontab); + + QPixmap commandIcon() const; + + /** + * Internal methods + */ + QPair unQuoteCommand() const; + QStringList separatePathCommand(const QString& command, bool quoted) const; + QString decryptBinaryCommand(const QString& command) const; + + QString completeCommandPath() const; + + + CTMonth month; + CTDayOfMonth dayOfMonth; + CTDayOfWeek dayOfWeek; + CTHour hour; + CTMinute minute; + + QString userLogin; + QString command; + QString comment; + + bool enabled; + bool reboot; + +private: + inline bool isSpace(const QString& token, int pos) { + if (pos >= token.length()) + return false; + + if (token.at(pos) == QLatin1Char( ' ' ) ) + return true; + + return false; + } + + QString describeDayOfMonth() const; + QString describeDayOfWeek() const; + QString describeDateAndHours() const; + + QString createTimeFormat() const; + QString createDateFormat() const; + + bool systemCrontab; + + QString initialUserLogin; + QString initialCommand; + QString initialComment; + bool initialEnabled; + bool initialReboot; +}; + +#endif // CTTASK_H diff --git a/kcron/src/crontablib/ctunit.cpp b/kcron/src/crontablib/ctunit.cpp new file mode 100644 index 00000000..724a2951 --- /dev/null +++ b/kcron/src/crontablib/ctunit.cpp @@ -0,0 +1,290 @@ +/*************************************************************************** + * CT Unit of Time Interval Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "ctunit.h" + +#include + +#include "logging.h" + +CTUnit::CTUnit(int _min, int _max, const QString& tokStr) { + min = _min; + max = _max; + initialize(tokStr); +} + +CTUnit::CTUnit(const CTUnit& source) { + min = source.min; + max = source.max; + + initialEnabled.clear(); + enabled.clear(); + for (int i = 0; i <= max; i++) { + initialEnabled.append(false); + enabled.append(source.enabled.at(i)); + } + + initialTokStr = QLatin1String( "" ); + dirty = true; +} + +CTUnit::~CTUnit() { +} + +CTUnit& CTUnit::operator = (const CTUnit& unit) { + if (this == &unit) + return *this; + + min = unit.min; + max = unit.max; + + enabled.clear(); + for (int i = 0; i <= max; i++) { + enabled.append(unit.enabled[i]); + } + dirty = true; + + return *this; +} + +void CTUnit::initialize(const QString& tokStr) { + enabled.clear(); + for (int i = 0; i <= max; i++) { + enabled.append(false); + initialEnabled.append(false); + } + + for (int i = min; i <= max; i++) { + initialEnabled[i] = enabled[i]; + } + + parse(tokStr); + initialTokStr = tokStr; + dirty = false; + +} + +void CTUnit::parse(const QString& tokenString) { + + QString tokStr = tokenString; + + // subelement is that which is between commas + QString subelement; + int commapos, slashpos, dashpos; + int beginat, endat, step; + + // loop through each subelement + tokStr += QLatin1Char( ',' ); + while ((commapos = tokStr.indexOf(QLatin1String( "," ))) > 0) { + subelement = tokStr.mid(0, commapos); + + // find "/" to determine step + slashpos = subelement.indexOf(QLatin1String( "/" )); + if (slashpos == -1) { + step = 1; + slashpos = subelement.length(); + } else { + step = fieldToValue(subelement.mid(slashpos+1, subelement.length()-slashpos-1)); + if (step < 1) + step = 1; + } + + // find "=" to determine range + dashpos = subelement.indexOf(QLatin1String( "-" )); + if (dashpos == -1) { + // deal with "*" + if (subelement.mid(0, slashpos) == QLatin1String( "*" )) { + beginat = min; + endat = max; + } else { + beginat = fieldToValue(subelement.mid(0, slashpos)); + endat = beginat; + } + } else { + beginat = fieldToValue(subelement.mid(0, dashpos)); + endat = fieldToValue(subelement.mid(dashpos+1, slashpos-dashpos-1)); + } + + // ignore out of range + if (beginat < 0) + beginat = 0; + if (endat > max) + endat = max; + + // setup enabled + for (int i = beginat; i <= endat; i+=step) { + initialEnabled[i] = enabled[i] = true; + } + + tokStr = tokStr.mid(commapos+1, tokStr.length()-commapos-1); + } + + return; +} + +QString CTUnit::exportUnit() const { + if (!dirty) { + return initialTokStr; + } + + if (isAllEnabled()) + return QLatin1String( "*" ); + + int total = enabledCount(); + int count = 0; + QString tokenizeUnit; + + for (int num=min; num<=max; num++) { + if (enabled[num]) { + tokenizeUnit += QString::number(num); + count++; + + if (count < total) + tokenizeUnit += QLatin1Char( ',' ); + } + } + + return tokenizeUnit; + +} + +QString CTUnit::genericDescribe(const QList& label) const { + int total(enabledCount()); + int count(0); + QString tmpStr; + for (int i = min; i <= max; i++) { + if (enabled[i]) { + tmpStr += label.at(i); + count++; + switch (total - count) { + case 0: + break; + case 1: + if (total > 2) + tmpStr += i18n(","); + tmpStr += i18n(" and "); + break; + default: + tmpStr += i18n(", "); + break; + } + } + } + return tmpStr; +} + +int CTUnit::minimum() const { + return min; +} + +int CTUnit::maximum() const { + return max; +} + +bool CTUnit::isEnabled(int pos) const { + return enabled.at(pos); +} + +bool CTUnit::isAllEnabled() const { + for (int i = min; i <= max; i++) { + if (enabled.at(i) == false) { + return false; + } + } + + return true; +} + +void CTUnit::setEnabled(int pos, bool value) { + enabled[pos] = value; + dirty = true; + return; +} + +bool CTUnit::isDirty() const { + return dirty; +} + +int CTUnit::enabledCount() const { + int total(0); + for (int i = min; i <= max; i++) + total += (enabled[i] == true); + return total; +} + +void CTUnit::apply() { + initialTokStr = exportUnit(); + for (int i = min; i <= max; i++) + initialEnabled[i] = enabled[i]; + dirty = false; + return; +} + +void CTUnit::cancel() { + for (int i = min; i <= max; i++) + enabled[i] = initialEnabled[i]; + dirty = false; + return; +} + +int CTUnit::fieldToValue(const QString& entry) const { + QString lower = entry.toLower(); + + // check for days + QList days; + days << QLatin1String( "sun" ) << QLatin1String( "mon" ) << QLatin1String( "tue" ) << QLatin1String( "wed" ) << QLatin1String( "thu" ) << QLatin1String( "fri" ) << QLatin1String( "sat" ); + + int day = days.indexOf(lower); + if (day != -1) { + return day; + } + + // check for months + QList months; + months << QLatin1String( "" ) << QLatin1String( "jan" ) << QLatin1String( "feb" ) << QLatin1String( "mar" ) << QLatin1String( "apr" ) << QLatin1String( "may" ) << QLatin1String( "jun" ) << QLatin1String( "jul" ) << QLatin1String( "aug" ) << QLatin1String( "sep" ) << QLatin1String( "oct" ) << QLatin1String( "nov" ) << QLatin1String( "dec" ); + + int month = months.indexOf(lower); + if (month != -1) { + return month; + } + + //If the string does not match a day ora month, then it's a simple number (minute, hour or day of month) + return entry.toInt(); +} + +/** + * Find a period in enabled values + * If no period has been found, return 0 + */ +int CTUnit::findPeriod(const QList& periods) const { + foreach(int period, periods) { + bool validPeriod = true; + + for (int i = minimum(); i <= maximum(); i++) { + bool periodTesting; + if ( (double)i/(double)period == i/period) + periodTesting = true; + else + periodTesting = false; + + if (isEnabled(i) != periodTesting) { + validPeriod = false; + break; + } + } + + if (validPeriod) { + return period; + } + } + + return 0; +} diff --git a/kcron/src/crontablib/ctunit.h b/kcron/src/crontablib/ctunit.h new file mode 100644 index 00000000..d5900e9a --- /dev/null +++ b/kcron/src/crontablib/ctunit.h @@ -0,0 +1,142 @@ +/*************************************************************************** + * CT Unit of Time Interval Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CTUNIT_H +#define CTUNIT_H + +#include +#include + +/** + * A cron table unit parser and tokenizer. + * Parses/tokenizes unit such as "0-3,5,6,10-30/5" + * Also provides default natural language description. + */ +class CTUnit { + +protected: + CTUnit(int min, int max, const QString& tokStr = QLatin1String("")); + + /** + * Get default natural language description. + */ + virtual QString genericDescribe(const QList& label) const; + +public: + + /** + * Base initial image as empty and copy enabled intervals. + */ + CTUnit(const CTUnit& source); + + /** + * Destructor. + */ + virtual ~CTUnit(); + + /** + * Copy enabled intervals. + */ + CTUnit& operator =(const CTUnit& unit); + + /** + * Tokenizes unit into string such as + * "0,1,2,3,5,6,10,15,20,25,30". + */ + virtual QString exportUnit() const; + + /** + * Parses unit such as "0-3,5,6,10-30/5". + * And initialize array of enabled intervals. + */ + void initialize(const QString& tokStr = QLatin1String("")); + + /** + * Lower bound. + */ + int minimum() const; + + /** + * Upper bound. + */ + int maximum() const; + + /** + * Accessor. + */ + bool isEnabled(int pos) const; + + bool isAllEnabled() const; + + void setEnabled(int pos, bool value); + + /** + * Indicates whether enabled intervals have been modified. + */ + bool isDirty() const; + + /** + * Total count of enabled intervals. + */ + int enabledCount() const; + + /** + * Mark changes as applied. + */ + void apply(); + + /** + * Mark cancel changes and revert to initial or last applied + * image. + */ + void cancel(); + + /** + * Find a period in enabled values + * If no period has been found, return 0 + */ + int findPeriod(const QList& periods) const; + +protected: + + /** + * Parses unit such as "0-3,5,6,10-30/5". + * Does not initialize array of enabled intervals. + */ + void parse(const QString& tokenString = QLatin1String("")); + +private: + int min; + int max; + + int fieldToValue(const QString& entry) const; + bool dirty; + + QList enabled; + QList initialEnabled; + + QString initialTokStr; + +public: + + /** + * Constant indicating short format. + */ + static const bool shortFormat = 0; + + /** + * Constant indicating long format. + */ + static const bool longFormat = 1; + +}; + +#endif // CTUNIT_H diff --git a/kcron/src/crontablib/ctvariable.cpp b/kcron/src/crontablib/ctvariable.cpp new file mode 100644 index 00000000..fcfc9dc0 --- /dev/null +++ b/kcron/src/crontablib/ctvariable.cpp @@ -0,0 +1,143 @@ +/*************************************************************************** + * CT Environment Variable Implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "ctvariable.h" + +#include + +#include +#include + +#include "kcronIcons.h" + +#include "ctHelper.h" + +CTVariable::CTVariable(const QString& tokenString, const QString& _comment, const QString& _userLogin) { + + QString tokStr = tokenString; + + if (tokStr.mid(0, 2) == QLatin1String( "#\\" )) { + tokStr = tokStr.mid(2, tokStr.length() - 2); + enabled = false; + } else + enabled = true; + + int spacepos(0); + + spacepos = tokStr.indexOf(QRegExp(QLatin1String( "[ =]" ))); + variable = tokStr.mid(0, spacepos); + + value = tokStr.mid(spacepos+1, tokStr.length()-spacepos-1); + comment = _comment; + + userLogin = _userLogin; + + initialVariable = variable; + initialValue = value; + initialComment = comment; + initialUserLogin = userLogin; + initialEnabled = enabled; + +} + +CTVariable::CTVariable(const CTVariable &source) : + variable(source.variable), value(source.value), comment(source.comment), userLogin(source.userLogin), enabled(source.enabled), initialVariable(QLatin1String( "" )), initialValue(QLatin1String( "" )), initialComment(QLatin1String( "" )), initialUserLogin(QLatin1String( "" )), initialEnabled(true) { +} + +CTVariable& CTVariable::operator = (const CTVariable& source) { + if (this == &source) + return *this; + + variable = source.variable; + value = source.value; + comment = source.comment; + userLogin = source.userLogin; + enabled = source.enabled; + + initialVariable = QLatin1String( "" ); + initialValue = QLatin1String( "" ); + initialComment = QLatin1String( "" ); + initialUserLogin = QLatin1String( "" ); + initialEnabled = true; + return *this; +} + +QString CTVariable::exportVariable() { + QString exportVariable; + + exportVariable += CTHelper::exportComment(comment); + + if (enabled == false) + exportVariable += QLatin1String( "#\\" ); + + exportVariable += variable + QLatin1String( "=" ) + value + QLatin1String( "\n" ); + + return exportVariable; +} + +void CTVariable::apply() { + initialVariable = variable; + initialValue = value; + initialComment = comment; + initialUserLogin = userLogin; + + initialEnabled = enabled; +} + +void CTVariable::cancel() { + variable = initialVariable; + value = initialValue; + comment = initialComment; + userLogin = initialUserLogin; + enabled = initialEnabled; +} + +bool CTVariable::dirty() const { + return ((variable != initialVariable) || (value != initialValue) || (comment != initialComment) || (userLogin != initialUserLogin) || (enabled != initialEnabled)); +} + +QPixmap CTVariable::variableIcon() const { + if (variable == QLatin1String( "MAILTO" )) + return SmallIcon(QLatin1String( "mail-message" )); + else if (variable == QLatin1String( "SHELL" )) + return SmallIcon(QLatin1String( "utilities-terminal" )); + else if (variable == QLatin1String( "HOME" )) + return SmallIcon(QLatin1String( "go-home" )); + else if (variable == QLatin1String( "PATH" )) + return SmallIcon(QLatin1String( "folder" )); + else if (variable == QLatin1String( "LD_CONFIG_PATH" )) + return SmallIcon(QLatin1String( "application-x-sharedlib" )); + + return KCronIcons::variable(KCronIcons::Small); + +} + +QString CTVariable::information() const { + + if (variable == QLatin1String( "HOME" )) { + return i18n("Override default home folder."); + } + else if (variable == QLatin1String( "MAILTO" )) { + return i18n("Email output to specified account."); + } + else if (variable == QLatin1String( "SHELL" )) { + return i18n("Override default shell."); + } + else if (variable == QLatin1String( "PATH" )) { + return i18n("Folders to search for program files."); + } + else if (variable == QLatin1String( "LD_CONFIG_PATH" )) { + return i18n("Dynamic libraries location."); + } + + return i18n("Local Variable"); + +} diff --git a/kcron/src/crontablib/ctvariable.h b/kcron/src/crontablib/ctvariable.h new file mode 100644 index 00000000..1cd04e76 --- /dev/null +++ b/kcron/src/crontablib/ctvariable.h @@ -0,0 +1,80 @@ +/*************************************************************************** + * CT Environment Variable Header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef CTVARIABLE_H +#define CTVARIABLE_H + +#include +#include + +/** + * An environment variable (encapsulation of crontab environment variable + * entry). Encapsulates parsing and tokenization. + */ +class CTVariable { +public: + + /** + * Constructs environment variable from crontab format string. + */ + explicit CTVariable(const QString& tokenString, const QString& _comment, const QString& _userLogin); + + /** + * Copy constructor. + */ + CTVariable(const CTVariable& source); + + /** + * Assignment operator. + */ + CTVariable& operator =(const CTVariable& source); + + /** + * Tokenizes environment variable to crontab format. + */ + QString exportVariable(); + + /** + * Mark changes as applied. + */ + void apply(); + + /** + * Cancel changes. + */ + void cancel(); + + /** + * Indicates whether or not the environment variable has been modified. + */ + bool dirty() const; + + QPixmap variableIcon() const; + + QString information() const; + + QString variable; + QString value; + QString comment; + QString userLogin; + + bool enabled; + +private: + QString initialVariable; + QString initialValue; + QString initialComment; + QString initialUserLogin; + bool initialEnabled; + +}; + +#endif // CTVARIABLE_H diff --git a/kcron/src/crontablib/logging.h b/kcron/src/crontablib/logging.h new file mode 100644 index 00000000..9273d82c --- /dev/null +++ b/kcron/src/crontablib/logging.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + + +#ifndef _KCRON_LOGGING_H_ +#define _KCRON_LOGGING_H_ + +#include + +#define KCRON_KDEBUG_ID 87400 + +#define logDebug() kDebug(KCRON_KDEBUG_ID) + +#endif // _KCRON_LOGGING_H_ diff --git a/kcron/src/genericListWidget.cpp b/kcron/src/genericListWidget.cpp new file mode 100644 index 00000000..d8b57bc0 --- /dev/null +++ b/kcron/src/genericListWidget.cpp @@ -0,0 +1,192 @@ +/*************************************************************************** + * KT list view item tasks implementation. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "genericListWidget.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ctcron.h" +#include "cttask.h" + +#include "crontabWidget.h" +#include "taskWidget.h" +#include "taskEditorDialog.h" +#include "kcronIcons.h" + +#include "logging.h" + +class GenericListWidgetPrivate { + +public: + + QTreeWidget* treeWidget; + + CrontabWidget* crontabWidget; + + QVBoxLayout* actionsLayout; + +}; + +/** + * Construct tasks folder from branch. + */ +GenericListWidget::GenericListWidget(CrontabWidget* crontabWidget, const QString& label, const QPixmap& icon) : + QWidget(crontabWidget), d(new GenericListWidgetPrivate()) { + + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + + d->crontabWidget = crontabWidget; + + // Label layout + QHBoxLayout* labelLayout = new QHBoxLayout(); + + QLabel* tasksIcon = new QLabel(this); + tasksIcon->setPixmap(icon); + labelLayout->addWidget(tasksIcon); + + QLabel* tasksLabel = new QLabel(label, this); + labelLayout->addWidget(tasksLabel, 1, Qt::AlignLeft); + + mainLayout->addLayout(labelLayout); + + + // Tree layout + QHBoxLayout* treeLayout = new QHBoxLayout(); + + d->treeWidget = new QTreeWidget(this); + + d->treeWidget->setRootIsDecorated(true); + d->treeWidget->setAllColumnsShowFocus(true); + + d->treeWidget->header()->setSortIndicatorShown(true); + d->treeWidget->header()->setStretchLastSection(true); + d->treeWidget->header()->setMovable(true); + + d->treeWidget->setSortingEnabled(true); + d->treeWidget->setAnimated(true); + + d->treeWidget->setRootIsDecorated(false); + + d->treeWidget->setAllColumnsShowFocus(true); + + d->treeWidget->setAlternatingRowColors(true); + + d->treeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); + d->treeWidget->setContextMenuPolicy(Qt::ActionsContextMenu); + + treeLayout->addWidget(d->treeWidget); + + d->actionsLayout = new QVBoxLayout(); + + treeLayout->addLayout(d->actionsLayout); + + mainLayout->addLayout(treeLayout); + + logDebug() << "Generic list created" << endl; + connect(treeWidget(), SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), SLOT(modifySelection(QTreeWidgetItem*,int))); + +} + +GenericListWidget::~GenericListWidget() { + delete d; +} + +QTreeWidget* GenericListWidget::treeWidget() const { + return d->treeWidget; +} + +CrontabWidget* GenericListWidget::crontabWidget() const { + return d->crontabWidget; +} + +CTHost* GenericListWidget::ctHost() const { + return d->crontabWidget->ctHost(); +} + +void GenericListWidget::resizeColumnContents() { + + //Resize all columns except the last one (which always take the last available space) + for (int i=0; itreeWidget->columnCount()-1; ++i) { + d->treeWidget->resizeColumnToContents(i); + } + +} + +QTreeWidgetItem* GenericListWidget::firstSelected() const { + QList tasksItems = treeWidget()->selectedItems(); + if (tasksItems.isEmpty()) { + return NULL; + } + + return tasksItems.first(); +} + +void GenericListWidget::keyPressEvent(QKeyEvent *e) { + if ( e->key() == Qt::Key_Delete ) { + deleteSelection(); + } +} + + +void GenericListWidget::removeAll() { + //Remove previous items + for(int i= treeWidget()->topLevelItemCount()-1; i>=0; --i) { + delete treeWidget()->takeTopLevelItem(i); + + } +} + +QAction* GenericListWidget::createSeparator() { + QAction* action = new QAction(this); + action->setSeparator(true); + + return action; +} + +void GenericListWidget::addRightAction(QAction* action, const QObject* receiver, const char* member) { + QPushButton* button = new QPushButton(action->text(), this); + button->setIcon(action->icon()); + button->setWhatsThis(action->whatsThis()); + button->setToolTip(action->toolTip()); + + d->actionsLayout->addWidget(button); + + button->addAction(action); + + connect(button, SIGNAL(clicked(bool)), receiver, member); + connect(action, SIGNAL(triggered(bool)), receiver, member); +} + +void GenericListWidget::addRightStretch() { + d->actionsLayout->addStretch(1); +} + +void GenericListWidget::setActionEnabled(QAction* action, bool enabled) { + foreach(QWidget* widget, action->associatedWidgets()) { + + //Only change status of associated Buttons + QPushButton* button = qobject_cast(widget); + if (button!=NULL) { + button->setEnabled(enabled); + } + } + + action->setEnabled(enabled); +} diff --git a/kcron/src/genericListWidget.h b/kcron/src/genericListWidget.h new file mode 100644 index 00000000..08c03b9c --- /dev/null +++ b/kcron/src/genericListWidget.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * KT list view item cron tasks folder. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef GENERIC_LIST_WIDGET_H +#define GENERIC_LIST_WIDGET_H + +#include +#include + +#include "cthost.h" + +class GenericListWidgetPrivate; +class QKeyEvent; +class QAction; + +class CrontabWidget; + +/** + * GenericListWidget + */ +class GenericListWidget : public QWidget { + Q_OBJECT +public: + + GenericListWidget(CrontabWidget* crontabWidget, const QString& label, const QPixmap& icon); + + ~GenericListWidget(); + + QTreeWidget* treeWidget() const; + + CTHost* ctHost() const; + + CrontabWidget* crontabWidget() const; + + void resizeColumnContents(); + + virtual void keyPressEvent(QKeyEvent *e); + +protected slots: + virtual void modifySelection(QTreeWidgetItem* item, int position) = 0; + + virtual void deleteSelection() = 0; + +protected: + void removeAll(); + + QTreeWidgetItem* firstSelected() const; + + QAction* createSeparator(); + + void addRightAction(QAction* action, const QObject* receiver, const char* member); + void addRightStretch(); + + void setActionEnabled(QAction* action, bool enabled); + +private: + GenericListWidgetPrivate* const d; + +}; + +#endif // GENERIC_LIST_WIDGET_H diff --git a/kcron/src/kcmCron.cpp b/kcron/src/kcmCron.cpp new file mode 100644 index 00000000..9c37fa33 --- /dev/null +++ b/kcron/src/kcmCron.cpp @@ -0,0 +1,169 @@ +/*************************************************************************** + * KT icons implementation. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "kcmCron.h" + +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kcronIcons.h" +#include "crontabWidget.h" + +#include "cttask.h" +#include "ctcron.h" +#include "cthost.h" +#include "ctInitializationError.h" +#include "logging.h" + +K_PLUGIN_FACTORY(KCMCronFactory, registerPlugin();) +K_EXPORT_PLUGIN(KCMCronFactory("kcron")) + +class KCMCronPrivate { +public: + + /** + * Main GUI view/working area. + */ + CrontabWidget* crontabWidget; + + /** + * Document object, here crotab enries. + */ + CTHost* ctHost; + +}; + + + +KCMCron::KCMCron(QWidget* parent, const QVariantList& /*args*/) : + KCModule(KCMCronFactory::componentData(), parent), + d(new KCMCronPrivate()) { + + + KAboutData* aboutData = new KAboutData("kcm_cron", 0, ki18n("Task Scheduler"), + KDE_VERSION_STRING, ki18n("KDE Task Scheduler"), KAboutData::License_GPL, ki18n("(c) 2008, Nicolas Ternisien\n(c) 1999-2000, Gary Meyer")); + + aboutData->addAuthor(ki18n("Nicolas Ternisien"), KLocalizedString(), "nicolas.ternisien@gmail.com"); + aboutData->addAuthor(ki18n("Gary Meyer"), KLocalizedString(), "gary@meyer.net"); + aboutData->addAuthor(ki18n("Robert Berry"), KLocalizedString(), "rjmber@ntlworld.com"); + aboutData->addAuthor(ki18n("James Ots"), KLocalizedString(), "code@jamesots.com"); + aboutData->addAuthor(ki18n("Alberto G. Hierro"), KLocalizedString(), "alberto.hierro@kdemail.net"); + + setAboutData(aboutData); + + + // Initialize document. + CTInitializationError ctInitializationError; + d->ctHost = new CTHost(findCrontabBinary(), ctInitializationError); + if (ctInitializationError.hasErrorMessage()) { + KMessageBox::error(this, i18n("The following error occurred while initializing KCron:" + "\n\n%1\n\nKCron will now exit.\n", ctInitializationError.errorMessage())); + } + + d->crontabWidget = new CrontabWidget(this, d->ctHost); + + logDebug() << "Crontab Widget initialized" << endl; + + connect(d->crontabWidget->tasksWidget(), SIGNAL(taskModified(bool)), this, SIGNAL(changed(bool))); + connect(d->crontabWidget->variablesWidget(), SIGNAL(variableModified(bool)), this, SIGNAL(changed(bool))); + + // Initialize view. + QVBoxLayout* layout = new QVBoxLayout(this); + + layout->addWidget(d->crontabWidget); + + init(); + +} + +QString KCMCron::findCrontabBinary() { + return QLatin1String( CRONTAB_BINARY ); +} + +KCMCron::~KCMCron() { + delete d->crontabWidget; + delete d->ctHost; + + delete d; +} + +void KCMCron::load() { + logDebug() << "Calling load" << endl; + + d->ctHost->cancel(); +} + +void KCMCron::save() { + logDebug() << "Saving crontab..." << endl; + + CTSaveStatus saveStatus = d->ctHost->save(); + if (saveStatus.isError() == true) { + KMessageBox::detailedError(this, saveStatus.errorMessage(), saveStatus.detailErrorMessage()); + } + +} + +void KCMCron::defaults() { + logDebug() << "Loading defaults" << endl; + + d->ctHost->cancel(); +} + + +bool KCMCron::init() { + + // Display greeting screen. + // if there currently are no scheduled tasks... + if (!d->ctHost->isRootUser()) { + int taskCount = 0; + foreach(CTCron* ctCron, d->ctHost->crons) { + taskCount += ctCron->tasks().count(); + } + + if (taskCount == 0) { + show(); + //TODO Add this as a passive popup/message/something else + KMessageBox::information(this, i18n("You can use this application to schedule programs to run in the background.\nTo schedule a new task now, click on the Tasks folder and select Edit/New from the menu."), i18n("Welcome to the Task Scheduler"), QLatin1String( "welcome" )); + } + } + + return true; +} + +CTHost* KCMCron::ctHost() const { + return d->ctHost; +} + +#include "kcmCron.moc" diff --git a/kcron/src/kcmCron.h b/kcron/src/kcmCron.h new file mode 100644 index 00000000..8253ddd0 --- /dev/null +++ b/kcron/src/kcmCron.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * KT icons. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef KCM_CRON_H +#define KCM_CRON_H + +/** + * Crontab binary executable location + * The $PATH variable could be used + */ +#define CRONTAB_BINARY "crontab" + +#include +#include +#include +#include +#include + +#include +#include + +class CTHost; + +class KCMCronPrivate; + +class KCMCron : public KCModule { + Q_OBJECT + +public: + //, const QVariantList& arguments + KCMCron(QWidget* parent, const QVariantList& args); + + ~KCMCron(); + + virtual void load(); + virtual void save(); + virtual void defaults(); + + /** + * Additional init + */ + bool init(); + + /** + * Returns a reference to the CTHost. + */ + CTHost* ctHost() const; + + QString findCrontabBinary(); + +private: + + KCMCronPrivate* const d; +}; + +#endif // KCM_CRON_H diff --git a/kcron/src/kcm_cron.desktop b/kcron/src/kcm_cron.desktop new file mode 100644 index 00000000..0434c0e9 --- /dev/null +++ b/kcron/src/kcm_cron.desktop @@ -0,0 +1,172 @@ +[Desktop Entry] +Exec=kcmshell4 kcm_cron +Icon=preferences-system-cron + +X-DocPath=kcron/index.html + +Type=Service +X-KDE-ServiceTypes=KCModule +X-KDE-Library=kcm_cron +X-KDE-ParentApp=kcontrol + +X-KDE-System-Settings-Parent-Category=system-administration + +Name=Task Scheduler +Name[ast]=Organizador de xeres +Name[bg]=Диспечер на задачи +Name[bs]=Raspored Zadataka +Name[ca]=Planificador de tasques +Name[ca@valencia]=Planificador de tasques +Name[cs]=Plánovač úloh +Name[da]=Opgaveskemalægger +Name[de]=Aufgabenplaner +Name[el]=Task Scheduler +Name[en_GB]=Task Scheduler +Name[es]=Planificador de tareas +Name[et]=Tegumihaldur +Name[eu]=Atazen antolatzailea +Name[fi]=Tehtävien ajastus +Name[fr]=Planificateur de tâches +Name[ga]=Sceidealóir na dTascanna +Name[gl]=Programador de tarefas +Name[hne]=काम जमइया +Name[hr]=Raspoređivač zadataka +Name[hu]=Feladatütemező +Name[ia]=Planificator de Cargas +Name[id]=Penjadwal Tugas +Name[is]=Verkefnastjóri +Name[it]=Pianificatore di operazioni +Name[ja]=タスクスケジューラ +Name[kk]=Тапсырма жоспарлағышы +Name[ko]=작업 스케줄러 +Name[lt]=Užduočių planuoklis +Name[lv]=Uzdevumu plānotājs +Name[ml]=കര്‍ത്തവ്യങ്ങള്‍ ആസൂത്രണം ചെയ്യുവാന്‍ +Name[mr]=कार्य नियोजक +Name[nb]=Oppgavebehandler +Name[nds]=Opgavenpleger +Name[nl]=Takenplanner +Name[nn]=Oppgåveplanleggjar +Name[pa]=ਟਾਸਕ ਸੈਡਿਊਲਰ +Name[pl]=Harmonogram zadań +Name[pt]=Escalonador de Tarefas +Name[pt_BR]=Agendador de tarefas +Name[ro]=Planificator de procese +Name[ru]=Планировщик заданий +Name[se]=Bargoplánejeaddji +Name[sk]=Plánovač úloh +Name[sl]=Razporejevalnik opravil +Name[sq]=Skeduesi i Detyrave +Name[sr]=Распоређивач задатака +Name[sr@ijekavian]=Распоређивач задатака +Name[sr@ijekavianlatin]=Raspoređivač zadataka +Name[sr@latin]=Raspoređivač zadataka +Name[sv]=Jobbschemaläggare +Name[th]=ตัวจัดการตารางงาน +Name[tr]=Görev Zamanlayıcı +Name[ug]=ۋەزىپە پىلانلاش پروگراممىسى +Name[uk]=Планувальник завдань +Name[x-test]=xxTask Schedulerxx +Name[zh_CN]=任务调度 +Name[zh_TW]=工作排程器 +Comment=Configure and schedule tasks +Comment[ast]=Configuración y xeres planificaes +Comment[bg]=Задаване и настройване на задачи +Comment[bs]=Konfiguriraj i rasporedi zadatke +Comment[ca]=Configura i planifica les tasques +Comment[ca@valencia]=Configura i planifica les tasques +Comment[cs]=Nastavení a plánování úloh +Comment[da]=Indstil og skemalæg opgaver +Comment[de]=Aufgaben einrichten und planen +Comment[el]=Διαμορφώνει και χρονοπρογραμματίζει εργασίες +Comment[en_GB]=Configure and schedule tasks +Comment[es]=Configurar y planificar tareas +Comment[et]=Tegumite seadistamine ja ajastamine +Comment[eu]=Atazak konfiguratu eta antolatu +Comment[fi]=Tehtävien ajastuksen asetukset +Comment[fr]=Configurer et planifier les tâches +Comment[ga]=Cumraigh agus sceideal tascanna +Comment[gl]=Configura e planifica tarefas +Comment[hne]=काम मन ल जमाव अउ कान्फिगर करव +Comment[hr]=Podesi i rasporedi zadatke +Comment[hu]=Időzített feladatok beállítása +Comment[ia]=Configura e planifica cargas +Comment[id]=Atur dan jadwal tugas +Comment[is]=Stilla og tímasetja verk +Comment[it]=Configura e pianifica le operazioni +Comment[ja]=タスクの設定とスケジュール +Comment[kk]=Баптау және тапсырмаларды жоспарлау +Comment[ko]=작업을 설정하고 예약하기 +Comment[lt]=Konfigūruoti ir planuoti užduotis +Comment[lv]=Konfigurēt un plānot uzdevumus +Comment[ml]=കര്‍ത്തവ്യങ്ങള്‍ ക്രമീകരിച്ചു് ആസൂത്രണം ചെയ്യുക +Comment[mr]=कार्ये संयोजीत व नियोजीत करा +Comment[nb]=Sett opp og planlegg oppgaver +Comment[nds]=Opgaven planen un plegen +Comment[nl]=Taken instellen en plannen +Comment[nn]=Planlegg og set opp oppgåver +Comment[pa]=ਟਾਸਕ ਸੰਰਚਨਾ ਅਤੇ ਸੈਡਿਊਲ ਕਰੋ +Comment[pl]=Konfiguracja i harmonogramowanie zadań +Comment[pt]=Configurar e agendar tarefas +Comment[pt_BR]=Configura e agenda tarefas +Comment[ro]=Configurează și planifică sarcinile +Comment[ru]=Настройка и планирование заданий +Comment[sk]=Nastavenie a plánovanie úloh +Comment[sl]=Nastavite in razporedite opravila +Comment[sq]=Konfiguro dhe skedo detyrat +Comment[sr]=Подешавање и заказивање задатака +Comment[sr@ijekavian]=Подешавање и заказивање задатака +Comment[sr@ijekavianlatin]=Podešavanje i zakazivanje zadataka +Comment[sr@latin]=Podešavanje i zakazivanje zadataka +Comment[sv]=Anpassa och schemalägg jobb +Comment[th]=ปรับแต่งและจัดตารางงาน +Comment[tr]=Görevleri zamanla ve yapılandır +Comment[ug]=ۋەزىپىنى سەپلەش ۋە پىلانلاش +Comment[uk]=Налаштування і планування завдання +Comment[x-test]=xxConfigure and schedule tasksxx +Comment[zh_CN]=配置并调度计划任务 +Comment[zh_TW]=設定與排程工作 +X-KDE-Keywords=cron,crontab,scheduled,tasks,task,schedule,vixie +X-KDE-Keywords[bs]=cron,crontab,scheduled,tasks,raspoređeni,zadaci,zadato, redoslijed,task,schedule,vixie +X-KDE-Keywords[ca]=cron,crontab,planificat,tasques,tasca,planificació,vixie +X-KDE-Keywords[ca@valencia]=cron,crontab,planificat,tasques,tasca,planificació,vixie +X-KDE-Keywords[cs]=cron,crontab,naplánováno,úlohy,úloha,naplánovat,vixie +X-KDE-Keywords[da]=cron,crontab,skemalagt,opgaver,opgaveskema,skema,vixie +X-KDE-Keywords[de]=cron,crontab,scheduled,tasks,task,schedule,vixie,aufgaben,planung +X-KDE-Keywords[el]=cron,crontab,προγραμματισμένες,εργασίες,εργασία,προγραμματισμός,vixie +X-KDE-Keywords[en_GB]=cron,crontab,scheduled,tasks,task,schedule,vixie +X-KDE-Keywords[es]=cron,crontab,programado,tareas,tarea,programar,vixie +X-KDE-Keywords[et]=cron,crontab,ajastamine,ülesanded,ülesanne,ajastaja,vixie +X-KDE-Keywords[eu]=cron,crontab,scheduled,antolatuta,tasks,task,atazak,ataza,schedule,antolatu,vixie +X-KDE-Keywords[fi]=cron,crontab,ajastus,ajasta,tehtävät,työt,ajastin,vixie +X-KDE-Keywords[fr]=cron, crontab, planification, tâche, tâches, planifiée, vixie +X-KDE-Keywords[ga]=cron,crontab,sceidealta,tascanna,tasc,sceideal,vixie +X-KDE-Keywords[gl]=cron,crontab,planificada,tarefa,planificar,vixie +X-KDE-Keywords[hu]=cron,crontab,ütemezett,feladatok,feladat,ütemezés,vixie +X-KDE-Keywords[ia]=cron,crontab,planificate,cargas,carga,planifica,vixie +X-KDE-Keywords[it]=cron,crontab,piano,compiti,compito,pianificare,vixie +X-KDE-Keywords[kk]=cron,crontab,scheduled,tasks,task,schedule,vixie +X-KDE-Keywords[ko]=cron,crontab,scheduled,tasks,task,schedule,vixie,작업,예약,예약된 작업 +X-KDE-Keywords[lt]=cron,crontab,nustatyta,užduotys,užduotis,tvarkaraštis,vixie +X-KDE-Keywords[mr]=क्रोन,क्रोनटॅब,नियोजीत,कार्य,कार्ये,नियोजन,विक्सी +X-KDE-Keywords[nb]=cron,crontab,planlagt,oppgaver,oppgave,plan,vixie +X-KDE-Keywords[nds]=cron,Opgaav,Opgaven,Opgaventabell,plaant,Plaan,vixie +X-KDE-Keywords[nl]=cron,crontab,gepland,taken,taak,planning,vixie +X-KDE-Keywords[pl]=cron,crontab,program_planujący,zadania,zadanie,planuj,vixie +X-KDE-Keywords[pt]=cron,crontab,agendadas,tarefas,tarefa,agendar,vixie +X-KDE-Keywords[pt_BR]=cron,crontab,agendadas,tarefas,tarefa,agendar,vixie +X-KDE-Keywords[ro]=cron,crontab,sarcini,sarcină,planifica,planifica,vixie,plan +X-KDE-Keywords[ru]=cron,crontab,scheduled,tasks,task,schedule,vixie,планирование,задания,задание,задачи,задача,планировщик,запуск,автозапуск +X-KDE-Keywords[sk]=cron,crontab,plánované,úlohy,úloha,plánovanie,vixie +X-KDE-Keywords[sl]=cron,crontab,razporejeno,opravila,opravilo,razpored,vixie +X-KDE-Keywords[sr]=cron,crontab,scheduled,tasks,task,schedule,vixie,крон,кронтаб,распоред,задаци,распоређен +X-KDE-Keywords[sr@ijekavian]=cron,crontab,scheduled,tasks,task,schedule,vixie,крон,кронтаб,распоред,задаци,распоређен +X-KDE-Keywords[sr@ijekavianlatin]=cron,crontab,scheduled,tasks,task,schedule,vixie,cron,crontab,raspored,zadaci,raspoređen +X-KDE-Keywords[sr@latin]=cron,crontab,scheduled,tasks,task,schedule,vixie,cron,crontab,raspored,zadaci,raspoređen +X-KDE-Keywords[sv]=cron,crontab,schemalagd,uppgifter,uppgift,schema,vixie +X-KDE-Keywords[tr]=cron,crontab,scheduled,tasks,task,schedule,vixie +X-KDE-Keywords[uk]=cron;crontab;scheduled;tasks;task;schedule;vixie;таблиця планування;список планування;завдання;план;запуск +X-KDE-Keywords[x-test]=xxcron,crontab,scheduled,tasks,task,schedule,vixiexx +X-KDE-Keywords[zh_CN]=cron,crontab,scheduled,tasks,task,schedule,vixie,计划,计划任务,任务 +X-KDE-Keywords[zh_TW]=cron,crontab,scheduled,tasks,task,schedule,vixie +Categories=Qt;KDE;X-KDE-settings-system; diff --git a/kcron/src/kcronHelper.cpp b/kcron/src/kcronHelper.cpp new file mode 100644 index 00000000..f76a58ce --- /dev/null +++ b/kcron/src/kcronHelper.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + * KT icons implementation. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "kcronHelper.h" + +#include +#include +#include +#include +#include + +#include + +#include "cttask.h" +#include "ctcron.h" +#include "cthost.h" + +#include "crontabWidget.h" + + +void KCronHelper::initUserCombo(QComboBox* userCombo, CrontabWidget* crontabWidget, const QString& selectedUserLogin) { + int userComboIndex = 0; + + QStringList users; + int selectedIndex = 0; + foreach(CTCron* ctCron, crontabWidget->ctHost()->crons) { + if (ctCron->isSystemCron()) + continue; + + users.append(ctCron->userLogin()); + + //Select the actual user + if (ctCron->userLogin() == selectedUserLogin) { + selectedIndex = userComboIndex; + } + + userComboIndex++; + } + + users.sort(); + + userCombo->addItems(users); + userCombo->setCurrentIndex(selectedIndex); +} + +QTextEdit* KCronHelper::createCommentEdit(QWidget* parent) { + QTextEdit* edit = new QTextEdit(parent); + edit->setAcceptRichText(false); + edit->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum)); + edit->setTabChangesFocus(true); + + QFontMetrics fontMetrics(edit->currentFont()); + edit->setMaximumHeight(fontMetrics.lineSpacing() * 3); //TODO Choose a smarter value + + return edit; +} diff --git a/kcron/src/kcronHelper.h b/kcron/src/kcronHelper.h new file mode 100644 index 00000000..d4824bda --- /dev/null +++ b/kcron/src/kcronHelper.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * KT icons. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef KCRON_HELPER_H +#define KCRON_HELPER_H + +#include +#include + +class QComboBox; +class QTextEdit; + +class CrontabWidget; + + +class KCronHelper { + +public: + static void initUserCombo(QComboBox* userCombo, CrontabWidget* crontabWidget, const QString& selectedUserLogin); + + static QTextEdit* createCommentEdit(QWidget* parent); +}; + +#endif // KCRON_HELPER_H diff --git a/kcron/src/kcronIcons.cpp b/kcron/src/kcronIcons.cpp new file mode 100644 index 00000000..2e7137d0 --- /dev/null +++ b/kcron/src/kcronIcons.cpp @@ -0,0 +1,47 @@ +/*************************************************************************** + * KT icons implementation. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "kcronIcons.h" + +#include +#include + +#include +#include + +QPixmap KCronIcons::getIcon(const QString&name, KCronIcons::IconSize size) { + if (size == KCronIcons::Small) + return SmallIcon(name); + else if (size == KCronIcons::Normal) + return BarIcon(name); + + return DesktopIcon(name); +} + +QPixmap KCronIcons::application(KCronIcons::IconSize size) { + return getIcon(QLatin1String( "kcron" ), size); +} + +QPixmap KCronIcons::variable(KCronIcons::IconSize size) { + return getIcon(QLatin1String( "text" ), size); +} + +QPixmap KCronIcons::task(KCronIcons::IconSize size) { + return getIcon(QLatin1String( "system-run" ), size); +} + +QPixmap KCronIcons::information(KCronIcons::IconSize size) { + return getIcon(QLatin1String( "dialog-information" ), size); +} + +QPixmap KCronIcons::error(KCronIcons::IconSize size) { + return getIcon(QLatin1String( "dialog-error" ), size); +} diff --git a/kcron/src/kcronIcons.h b/kcron/src/kcronIcons.h new file mode 100644 index 00000000..9034afb1 --- /dev/null +++ b/kcron/src/kcronIcons.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * KT icons. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef KCRON_ICONS_H +#define KCRON_ICONS_H + +#include + +/** + * Wraps all icons used by the application. + */ + +class KCronIcons { + +public: + + enum IconSize {Small,Normal,Large}; + + static QPixmap application(KCronIcons::IconSize size); + + static QPixmap variable(KCronIcons::IconSize size); + static QPixmap task(KCronIcons::IconSize size); + + static QPixmap information(KCronIcons::IconSize size); + static QPixmap error(KCronIcons::IconSize size); + +private: + static QPixmap getIcon(const QString&name, KCronIcons::IconSize size); + +}; + +#endif // KCRON_ICONS_H diff --git a/kcron/src/taskEditorDialog.cpp b/kcron/src/taskEditorDialog.cpp new file mode 100644 index 00000000..06cfb6c9 --- /dev/null +++ b/kcron/src/taskEditorDialog.cpp @@ -0,0 +1,1114 @@ +/*************************************************************************** + * KT task editor window implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "taskEditorDialog.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logging.h" +#include "cttask.h" +#include "ctcron.h" +#include "cthost.h" + +#include "crontabWidget.h" + +#include "kcronIcons.h" +#include "kcronHelper.h" + +/** + * TaskEditorDialog class implementation + */ + +TaskEditorDialog::TaskEditorDialog(CTTask* _ctTask, const QString& _caption, CrontabWidget* _crontabWidget) : + KDialog(_crontabWidget) { + + setModal(true); + + // window + setWindowIcon(KCronIcons::application(KCronIcons::Small)); + setCaption(_caption); + + ctTask = _ctTask; + crontabWidget = _crontabWidget; + + QWidget* main = new QWidget(this); + QVBoxLayout* mainLayout = new QVBoxLayout(main); + setMainWidget(main); + + + // top title widget + titleWidget = new KTitleWidget(main); + titleWidget->setText(i18n("Add or modify a scheduled task")); + setupTitleWidget(i18n("This task will be executed at the specified intervals.")); + + mainLayout->addWidget(titleWidget); + + + QGridLayout* commandConfigurationLayout = new QGridLayout(); + mainLayout->addLayout(commandConfigurationLayout); + + // command + QLabel* labCommand = new QLabel( i18n("&Command:"), main ); + commandConfigurationLayout->addWidget(labCommand, 0, 0); + + QHBoxLayout* commandLayout = new QHBoxLayout(); + commandIcon = new QLabel(main); + commandLayout->addWidget(commandIcon); + + command = new KUrlRequester(main); + labCommand->setBuddy(command); + commandLayout->addWidget(command); + + command->setMode(KFile::File | KFile::ExistingOnly | KFile::LocalOnly); + command->setUrl(KUrl(ctTask->command)); + + //Initialize special valid commands + specialValidCommands << QLatin1String( "cd" ); + + commandConfigurationLayout->addLayout(commandLayout, 0, 1); + + // User Combo + QLabel* userLabel = new QLabel( i18n("&Run as:"), main); + commandConfigurationLayout->addWidget(userLabel, 1, 0); + + userCombo = new QComboBox(main); + + userLabel->setBuddy(userCombo); + commandConfigurationLayout->addWidget(userCombo, 1, 1); + + if (crontabWidget->tasksWidget()->needUserColumn()) { + KCronHelper::initUserCombo(userCombo, crontabWidget, ctTask->userLogin); + } else { + userLabel->hide(); + userCombo->hide(); + } + + // comment + QLabel* labComment = new QLabel( i18n("Co&mment:"), main); + commandConfigurationLayout->addWidget(labComment, 2, 0, Qt::AlignTop); + + leComment = KCronHelper::createCommentEdit(main); + labComment->setBuddy(leComment); + commandConfigurationLayout->addWidget(leComment, 2, 1); + + leComment->setText(ctTask->comment); + + QHBoxLayout* checkboxesLayout = new QHBoxLayout(); + mainLayout->addLayout(checkboxesLayout); + + // enabled + chkEnabled = new QCheckBox(i18n("&Enable this task"), main); + chkEnabled->setChecked(ctTask->enabled); + checkboxesLayout->addWidget(chkEnabled); + + // @reboot + chkReboot = new QCheckBox(i18n("Run at system &bootup"), main); + chkReboot->setChecked(ctTask->reboot); + checkboxesLayout->addWidget(chkReboot); + + // Every day + bool everyDay = isEveryDay(); + cbEveryDay = new QCheckBox( i18n("Run &every day"), main); + cbEveryDay->setChecked(everyDay); + checkboxesLayout->addWidget(cbEveryDay); + + QHBoxLayout* schedulingLayout = new QHBoxLayout(); + mainLayout->addLayout(schedulingLayout); + + QVBoxLayout* monthLayout = new QVBoxLayout(); + schedulingLayout->addLayout(monthLayout); + + // months + bgMonth = createMonthsGroup(main); + monthLayout->addWidget(bgMonth); + monthLayout->addStretch(1); + + QVBoxLayout* v1 = new QVBoxLayout(); + schedulingLayout->addLayout(v1); + + // days of the month + bgDayOfMonth = createDaysOfMonthGroup(main); + v1->addWidget(bgDayOfMonth); + + // days of the week + bgDayOfWeek = createDaysOfWeekGroup(main); + v1->addWidget(bgDayOfWeek); + + v1->addStretch(1); + + QVBoxLayout* v2 = new QVBoxLayout(); + schedulingLayout->addLayout(v2); + + hoursGroup = createHoursGroup(main); + v2->addWidget(hoursGroup); + + createMinutesGroup(main); + v2->addWidget(minutesGroup); + + v2->addStretch(1); + + //schedulingLayout->addStretch(1); + + command->setFocus(); + + connect(command, SIGNAL(textChanged(QString)), SLOT(slotWizard())); + + connect(chkEnabled, SIGNAL(clicked()), SLOT(slotEnabledChanged())); + connect(chkEnabled, SIGNAL(clicked()), SLOT(slotWizard())); + + connect(chkReboot, SIGNAL(clicked()), SLOT(slotRebootChanged())); + connect(chkReboot, SIGNAL(clicked()), SLOT(slotWizard())); + + connect(cbEveryDay, SIGNAL(clicked()), SLOT(slotDailyChanged())); + connect(cbEveryDay, SIGNAL(clicked()), SLOT(slotWizard())); + + connect(this, SIGNAL(okClicked()), SLOT(slotOK())); + connect(this, SIGNAL(cancelClicked()), SLOT(slotCancel())); + + //main->layout()->setSizeConstraint(QLayout::SetFixedSize); + //show(); + + if (!chkEnabled->isChecked()) + slotEnabledChanged(); + else if (chkReboot->isChecked()) + slotRebootChanged(); + else if (cbEveryDay->isChecked()) + slotDailyChanged(); + + slotMonthChanged(); + slotDayOfMonthChanged(); + slotDayOfWeekChanged(); + slotHourChanged(); + slotMinuteChanged(); + + slotWizard(); + +} + +TaskEditorDialog::~TaskEditorDialog() { + +} + +bool TaskEditorDialog::isEveryDay() { + for (int dw = CTDayOfWeek::MINIMUM; dw <= CTDayOfWeek::MAXIMUM; dw++) { + if (!ctTask->dayOfWeek.isEnabled(dw)) + return false; + } + + for (int mo = ctTask->month.minimum(); mo <= ctTask->month.maximum(); mo++) { + if (!ctTask->month.isEnabled(mo)) + return false; + } + + for (int dm = CTDayOfMonth::MINIMUM; dm <= CTDayOfMonth::MAXIMUM; dm++) { + if (!ctTask->dayOfMonth.isEnabled(dm)) + return false; + } + + return true; +} + +QGroupBox* TaskEditorDialog::createDaysOfMonthGroup(QWidget* main) { + + QGroupBox* daysOfMonthGroup = new QGroupBox( i18n("Days of Month"), main); + QGridLayout* daysOfMonthLayout = new QGridLayout(daysOfMonthGroup); + + int dm = CTDayOfMonth::MINIMUM; + for (int row = 0; row < 5; ++row) { + for (int column = 0; column < 7; ++column) { + NumberPushButton* day = new NumberPushButton(true, daysOfMonthGroup); + day->setText(QString::number(dm)); + day->setCheckable(true); + day->setChecked(ctTask->dayOfMonth.isEnabled(dm)); + dayOfMonthButtons[dm] = day; + + connect(dayOfMonthButtons[dm], SIGNAL(clicked()), SLOT(slotDayOfMonthChanged())); + connect(dayOfMonthButtons[dm], SIGNAL(clicked()), SLOT(slotWizard())); + + daysOfMonthLayout->addWidget(day, row, column); + + if (dm==CTDayOfMonth::MAXIMUM) { + break; + break; + } + + dm++; + } + } + + + allDaysOfMonth = new SetOrClearAllButton(daysOfMonthGroup, SetOrClearAllButton::SET_ALL); + daysOfMonthLayout->addWidget(allDaysOfMonth, 4, 3, 1, 4); + + connect(allDaysOfMonth, SIGNAL(clicked()), SLOT(slotAllDaysOfMonth())); + connect(allDaysOfMonth, SIGNAL(clicked()), SLOT(slotWizard())); + + return daysOfMonthGroup; +} + +QGroupBox* TaskEditorDialog::createMonthsGroup(QWidget* main) { + + QGroupBox* monthsGroup = new QGroupBox( i18n("Months"), main); + QGridLayout* monthsLayout = new QGridLayout(monthsGroup); + + int column = 0; + int row = 0; + + for (int mo = CTMonth::MINIMUM; mo <= CTMonth::MAXIMUM; mo++) { + monthButtons[mo] = new NumberPushButton(monthsGroup); + monthButtons[mo]->setText(ctTask->month.getName(mo)); + monthButtons[mo]->setCheckable(true); + monthButtons[mo]->setChecked(ctTask->month.isEnabled(mo)); + + monthsLayout->addWidget(monthButtons[mo], row, column); + + connect(monthButtons[mo], SIGNAL(clicked()), SLOT(slotMonthChanged())); + connect(monthButtons[mo], SIGNAL(clicked()), SLOT(slotWizard())); + + if (column == 1) { + column = 0; + row++; + } else { + column = 1; + } + } + + allMonths = new SetOrClearAllButton(monthsGroup, SetOrClearAllButton::SET_ALL); + monthsLayout->addWidget(allMonths, row, 0, 1, 2); + + connect(allMonths, SIGNAL(clicked()), SLOT(slotAllMonths())); + connect(allMonths, SIGNAL(clicked()), SLOT(slotWizard())); + + return monthsGroup; + +} + +QGroupBox* TaskEditorDialog::createDaysOfWeekGroup(QWidget* main) { + + QGroupBox* daysOfWeekGroup = new QGroupBox( i18n("Days of Week"), main); + QGridLayout* daysOfWeekLayout = new QGridLayout(daysOfWeekGroup); + + int column = 0; + int row = 0; + for (int dw = CTDayOfWeek::MINIMUM; dw <= CTDayOfWeek::MAXIMUM; dw++) { + dayOfWeekButtons[dw] = new NumberPushButton(daysOfWeekGroup); + dayOfWeekButtons[dw]->setText(ctTask->dayOfWeek.getName(dw)); + dayOfWeekButtons[dw]->setCheckable(true); + dayOfWeekButtons[dw]->setChecked(ctTask->dayOfWeek.isEnabled(dw)); + daysOfWeekLayout->addWidget(dayOfWeekButtons[dw], row, column); + + connect(dayOfWeekButtons[dw], SIGNAL(clicked()), SLOT(slotDayOfWeekChanged())); + connect(dayOfWeekButtons[dw], SIGNAL(clicked()), SLOT(slotWizard())); + + if (column == 1) { + column = 0; + row++; + } else { + column = 1; + } + + } + + allDaysOfWeek = new SetOrClearAllButton(daysOfWeekGroup, SetOrClearAllButton::SET_ALL); + daysOfWeekLayout->addWidget(allDaysOfWeek); + + connect(allDaysOfWeek, SIGNAL(clicked()), SLOT(slotAllDaysOfWeek())); + connect(allDaysOfWeek, SIGNAL(clicked()), SLOT(slotWizard())); + + return daysOfWeekGroup; +} + + +bool TaskEditorDialog::canReduceMinutesGroup() { + for (int minuteIndex = 0; minuteIndex<=minuteTotal; ++minuteIndex ) { + if (minuteIndex % reducedMinuteStep != 0) { + if (minuteButtons[minuteIndex]->isChecked() == true) { + return false; + } + } + + } + + return true; +} + +void TaskEditorDialog::emptyMinutesGroup() { + + logDebug() << "Empty minutes layout" << endl; + + for (int minuteIndex = 0; minuteIndex<=minuteTotal; ++minuteIndex ) { + minutesLayout->removeWidget(minuteButtons[minuteIndex]); + minuteButtons[minuteIndex]->hide(); + logDebug() << "Layout count" << minutesLayout->count() << endl; + } + + minutesLayout->removeItem(minutesPreselectionLayout); +} + +void TaskEditorDialog::increaseMinutesGroup() { + /* + * Test if the increase is already done. If this is the case, no need to redo it + * + * We test : + * minutesButtons[1] will be hidden because 1%reducedMinuteStep != 0 + */ + /* + if (minuteButtons[1]->isHidden() == false) + return; + */ + emptyMinutesGroup(); + + logDebug() << "Show all minutes" << endl; + + int minuteIndex = 0; + for (int row = 0; row < (minuteTotal+1)/minutePerColumn; ++row) { + for (int column = 0; column < minutePerColumn; ++column) { + minutesLayout->addWidget(minuteButtons[minuteIndex], row, column); + minuteButtons[minuteIndex]->show(); + minuteIndex++; + } + } + + minutesLayout->addLayout(minutesPreselectionLayout, ((minuteTotal+1)/minutePerColumn), 0, 1, minutePerColumn); + minutesLayout->invalidate(); + this->resize(sizeHint()); + +} + +void TaskEditorDialog::reduceMinutesGroup() { + logDebug() << "Reducing view" << endl; + + emptyMinutesGroup(); + + int nextRow = 0; + int nextColumn = 0; + + for (int minuteIndex = 0; minuteIndex <= minuteTotal; ++minuteIndex) { + if (minuteIndex % reducedMinuteStep == 0) { + minutesLayout->addWidget(minuteButtons[minuteIndex], nextRow, nextColumn); + minuteButtons[minuteIndex]->show(); + + nextColumn++; + if (nextColumn==6) { + nextColumn = 0; + nextRow = 1; + } + } + else { + logDebug() << "Reducing id" << minuteIndex << endl; + ctTask->minute.setEnabled(minuteIndex, false); + minuteButtons[minuteIndex]->setChecked(false); + + } + } + + minutesLayout->addLayout(minutesPreselectionLayout, 2, 0, 1, 6); + minutesLayout->invalidate(); + this->resize(sizeHint()); + +} + +NumberPushButton* TaskEditorDialog::createMinuteButton(int minuteIndex) { + NumberPushButton* minuteButton = new NumberPushButton(true, minutesGroup); + minuteButton->setText(QString::number(minuteIndex)); + minuteButton->setCheckable(true); + minuteButton->setChecked(ctTask->minute.isEnabled(minuteIndex)); + + connect(minuteButton, SIGNAL(clicked()), SLOT(slotMinuteChanged())); + connect(minuteButton, SIGNAL(clicked()), SLOT(slotWizard())); + + return minuteButton; +} + +void TaskEditorDialog::createMinutesGroup(QWidget* main) { + logDebug() << "Creating minutes group" << endl; + + minutesGroup = new QGroupBox( i18n("Minutes"), main); + + minutesLayout = new QGridLayout(minutesGroup); + + for (int minuteIndex = 0; minuteIndex<=minuteTotal; ++minuteIndex ) { + minuteButtons[minuteIndex] = createMinuteButton(minuteIndex); + } + + minutesPreselectionLayout = new QHBoxLayout(); + + QLabel* minutesPreselectionLabel = new QLabel(i18n("Preselection:")); + minutesPreselectionLayout->addWidget(minutesPreselectionLabel); + + minutesPreselection = new QComboBox(this); + + minutesPreselectionLabel->setBuddy(minutesPreselection); + + minutesPreselection->addItem(SmallIcon(QLatin1String( "edit-clear-locationbar-ltr" )), i18n("Clear selection"), -1); + minutesPreselection->addItem(SmallIcon(QLatin1String( "edit-rename" )),i18n("Custom selection"), 0); + minutesPreselection->addItem(SmallIcon(QLatin1String( "view-calendar-month" )), i18n("Each minute"), 1); + minutesPreselection->addItem(SmallIcon(QLatin1String( "view-calendar-week" )), i18n("Every 2 minutes"), 2); + minutesPreselection->addItem(SmallIcon(QLatin1String( "view-calendar-workweek" )), i18n("Every 5 minutes"), 5); + minutesPreselection->addItem(SmallIcon(QLatin1String( "view-calendar-upcoming-days" )), i18n("Every 10 minutes"), 10); + minutesPreselection->addItem(SmallIcon(QLatin1String( "view-calendar-upcoming-days" )), i18n("Every 15 minutes"), 15); + minutesPreselection->addItem(SmallIcon(QLatin1String( "view-calendar-day" )), i18n("Every 20 minutes"), 20); + minutesPreselection->addItem(SmallIcon(QLatin1String( "view-calendar-day" )), i18n("Every 30 minutes"), 30); + + minutesPreselectionLayout->addWidget(minutesPreselection); + + connect(minutesPreselection, SIGNAL(activated(int)), SLOT(slotMinutesPreselection(int))); + connect(minutesPreselection, SIGNAL(activated(int)), SLOT(slotWizard())); + + //First mandatory increase + increaseMinutesGroup(); + + if (canReduceMinutesGroup()==true) { + reduceMinutesGroup(); + } + + logDebug() << "Minutes group created" << endl; +} + +NumberPushButton* TaskEditorDialog::createHourButton(QGroupBox* hoursGroup, int hour) { + NumberPushButton* hourButton = new NumberPushButton(true, hoursGroup); + hourButton->setText(QString::number(hour)); + hourButton->setCheckable(true); + hourButton->setChecked(ctTask->hour.isEnabled(hour)); + + connect(hourButton, SIGNAL(clicked()), SLOT(slotHourChanged())); + connect(hourButton, SIGNAL(clicked()), SLOT(slotWizard())); + + return hourButton; +} + +QGroupBox* TaskEditorDialog::createHoursGroup(QWidget* main) { + + logDebug() << "Creating hours group" << endl; + QGroupBox* hoursGroup = new QGroupBox(i18n("Hours"), main); + + QGridLayout* hoursLayout = new QGridLayout(hoursGroup); //5 x 7 + + morningLabel = new QLabel(i18n("AM:"), this); + morningLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + morningLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + hoursLayout->addWidget(morningLabel, 0, 0, Qt::AlignLeft | Qt::AlignVCenter); + + int hourCount = 0; + for (int column = 0; column <= 3 ; ++column) { + + for (int hour = 0; hour <= 5; ++hour) { + NumberPushButton* hourButton = createHourButton(hoursGroup, hourCount); + hourButtons[hourCount] = hourButton; + hoursLayout->addWidget(hourButton, column, hour+1); + hourCount++; + } + + } + + afternoonLabel = new QLabel( i18n("PM:"), this); + afternoonLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + afternoonLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + hoursLayout->addWidget(afternoonLabel, 2, 0, Qt::AlignLeft | Qt::AlignVCenter); + + allHours = new SetOrClearAllButton(this, SetOrClearAllButton::SET_ALL); + hoursLayout->addWidget(allHours, 4, 0, 1, 7); + + connect(allHours, SIGNAL(clicked()), SLOT(slotAllHours())); + connect(allHours, SIGNAL(clicked()), SLOT(slotWizard())); + + logDebug() << "Create hours group" << endl; + return hoursGroup; +} + + +void TaskEditorDialog::setupTitleWidget(const QString& comment, KTitleWidget::MessageType messageType) { + titleWidget->setComment(comment, messageType); + + if (messageType == KTitleWidget::ErrorMessage) + titleWidget->setPixmap(KIcon(KCronIcons::error(KCronIcons::Large)), KTitleWidget::ImageRight); + else + titleWidget->setPixmap(KIcon(KCronIcons::task(KCronIcons::Large)), KTitleWidget::ImageRight); + +} + +void TaskEditorDialog::slotEnabledChanged() { + bool enabled = chkEnabled->isChecked(); + userCombo->setEnabled(enabled); + leComment->setEnabled(enabled); + command->setEnabled(enabled); + chkReboot->setEnabled(enabled); + + // if chkReboot is already checked, allow setEnabled(false) but not setEnable(true) ... + if (!chkReboot->isChecked() || !enabled) { + cbEveryDay->setEnabled(enabled); + hoursGroup->setEnabled(enabled); + minutesGroup->setEnabled(enabled); + } + + // if cbEveryDay is already checked, allow setEnabled(false) but not setEnable(true) ... + if ((!chkReboot->isChecked() && !cbEveryDay->isChecked()) || !enabled) { + bgMonth->setEnabled(enabled); + bgDayOfMonth->setEnabled(enabled); + bgDayOfWeek->setEnabled(enabled); + } +} + +void TaskEditorDialog::slotRebootChanged() { + bool reboot = !chkReboot->isChecked(); + cbEveryDay->setEnabled(reboot); + hoursGroup->setEnabled(reboot); + minutesGroup->setEnabled(reboot); + + // if cbEveryDay is already checked, bgMonth, bgDayOfMonth, bgDayOfWeek are already setEnable(flase) + // so don't overide them ! ... + if (!cbEveryDay->isChecked()) { + bgMonth->setEnabled(reboot); + bgDayOfMonth->setEnabled(reboot); + bgDayOfWeek->setEnabled(reboot); + } +} + +void TaskEditorDialog::slotDailyChanged() { + if (cbEveryDay->isChecked()) { + for (int mo = 1; mo <= 12; mo++) + monthButtons[mo]->setChecked(true); + for (int dm = 1; dm <= 31; dm++) + dayOfMonthButtons[dm]->setChecked(true); + for (int dw = 1; dw <= 7; dw++) + dayOfWeekButtons[dw]->setChecked(true); + bgMonth->setEnabled(false); + bgDayOfMonth->setEnabled(false); + bgDayOfWeek->setEnabled(false); + allMonths->setEnabled(false); + allDaysOfMonth->setEnabled(false); + allDaysOfWeek->setEnabled(false); + } else { + bgMonth->setEnabled(true); + bgDayOfMonth->setEnabled(true); + bgDayOfWeek->setEnabled(true); + allMonths->setEnabled(true); + allDaysOfMonth->setEnabled(true); + allDaysOfWeek->setEnabled(true); + } + + slotMonthChanged(); + slotDayOfMonthChanged(); + slotDayOfWeekChanged(); +} + +void TaskEditorDialog::slotOK() { + // Make it friendly for just selecting days of the month or + // days of the week. + + int monthDaysSelected(0); + for (int dm = 1; dm <= 31; dm++) { + if (dayOfMonthButtons[dm]->isChecked()) + monthDaysSelected++; + } + + int weekDaysSelected(0); + for (int dw = 1; dw <= 7; dw++) { + if (dayOfWeekButtons[dw]->isChecked()) + weekDaysSelected++; + } + + if ((monthDaysSelected == 0) && (weekDaysSelected > 0)) { + for (int dm = 1; dm <= 31; dm++) { + dayOfMonthButtons[dm]->setChecked(true); + } + } + + if ((weekDaysSelected == 0) && (monthDaysSelected > 0)) { + for (int dw = 1; dw <= 7; dw++) { + dayOfWeekButtons[dw]->setChecked(1); + } + } + + // save work in process + if (crontabWidget->tasksWidget()->needUserColumn()) { + ctTask->userLogin = userCombo->currentText(); + } + + ctTask->comment = leComment->toPlainText(); + ctTask->command = command->url().path(); + ctTask->enabled = chkEnabled->isChecked(); + ctTask->reboot = chkReboot->isChecked(); + + for (int mo = CTMonth::MINIMUM; mo <= CTMonth::MAXIMUM; mo++) { + ctTask->month.setEnabled(mo, monthButtons[mo]->isChecked()); + } + + for (int dm = 1; dm <= 31; dm++) { + ctTask->dayOfMonth.setEnabled(dm, dayOfMonthButtons[dm]->isChecked()); + } + for (int dw = 1; dw <= 7; dw++) { + ctTask->dayOfWeek.setEnabled(dw, dayOfWeekButtons[dw]->isChecked()); + } + for (int ho = 0; ho <= 23; ho++) { + ctTask->hour.setEnabled(ho, hourButtons[ho]->isChecked()); + } + + for (int mi = 0; mi <= minuteTotal; ++mi) { + ctTask->minute.setEnabled(mi, minuteButtons[mi]->isChecked()); + } + + close(); +} + + +void TaskEditorDialog::defineCommandIcon() { + CTTask tempTask(*ctTask); + tempTask.command = command->url().path(); + + commandIcon->setPixmap(tempTask.commandIcon()); +} + + +bool TaskEditorDialog::checkCommand() { + CTTask tempTask(*ctTask); + tempTask.command = command->url().path(); + + QPair commandQuoted = tempTask.unQuoteCommand(); + + if (commandQuoted.first.isEmpty()) { + setupTitleWidget(i18n("Please type a valid command line..."), KTitleWidget::ErrorMessage); + KDialog::enableButtonOk(false); + command->setFocus(); + commandIcon->setPixmap(SmallIcon(QLatin1String( "image-missing" ))); + + return false; + } + + + QStringList pathCommand = tempTask.separatePathCommand(commandQuoted.first, commandQuoted.second); + if (pathCommand.isEmpty()) { + setupTitleWidget(i18n("Please type a valid command line..."), KTitleWidget::ErrorMessage); + KDialog::enableButtonOk(false); + command->setFocus(); + commandIcon->setPixmap(SmallIcon(QLatin1String( "image-missing" ))); + + return false; + } + + + QString path = pathCommand.at(0); + QString binaryCommand = pathCommand.at(1); + + logDebug() << "Looking for " << binaryCommand << "in" << path << endl; + + bool found = false; + bool exec = false; + if (!KStandardDirs::findExe(binaryCommand, path, KStandardDirs::IgnoreExecBit).isEmpty() || specialValidCommands.contains(binaryCommand)) + found = true; + if (!KStandardDirs::findExe(binaryCommand, path).isEmpty() || specialValidCommands.contains(binaryCommand)) + exec = true; + + if (found && !exec) { + setupTitleWidget(i18n("Please select an executable program..."), KTitleWidget::ErrorMessage); + KDialog::enableButtonOk(false); + command->setFocus(); + commandIcon->setPixmap(SmallIcon(QLatin1String( "image-missing" ))); + return false; + } + + if (!found) { + setupTitleWidget(i18n("Please browse for a program to execute..."), KTitleWidget::ErrorMessage); + KDialog::enableButtonOk(false); + command->setFocus(); + commandIcon->setPixmap(SmallIcon(QLatin1String( "image-missing" ))); + return false; + } + + + return true; +} + +void TaskEditorDialog::slotWizard() { + if (!chkEnabled->isChecked()) { + setupTitleWidget(i18n("This task is disabled.")); + KDialog::enableButtonOk(true); + chkEnabled->setFocus(); + return; + } + + if (chkReboot->isChecked()) { + setupTitleWidget(i18n("This task will be run on system bootup.")); + KDialog::enableButtonOk(true); + return; + } + + if (command->url().path().isEmpty()) { + setupTitleWidget(i18n("Please browse for a program to execute..."), KTitleWidget::ErrorMessage); + KDialog::enableButtonOk(false); + command->setFocus(); + commandIcon->setPixmap(SmallIcon(QLatin1String( "image-missing" ))); + return; + } + + bool validCommand = checkCommand(); + if (validCommand == false) + return; + + // the months + bool valid(false); + for (int mo = CTMonth::MINIMUM; mo <= CTMonth::MAXIMUM; mo++) { + if (monthButtons[mo]->isChecked()) + valid = true; + } + + if (!valid) { + setupTitleWidget(i18n("Please select from the 'Months' section..."), KTitleWidget::ErrorMessage); + KDialog::enableButtonOk(false); + if (!command->hasFocus()) + monthButtons[1]->setFocus(); + return; + } + + // the days + valid = false; + for (int dm = CTDayOfMonth::MINIMUM; dm <= CTDayOfMonth::MAXIMUM; dm++) { + if (dayOfMonthButtons[dm]->isChecked()) + valid = true; + } + for (int dw = CTDayOfWeek::MINIMUM; dw <= CTDayOfWeek::MAXIMUM; dw++) { + if (dayOfWeekButtons[dw]->isChecked()) + valid = true; + } + + if (!valid) { + setupTitleWidget(i18n("Please select from either the 'Days of Month' or the 'Days of Week' section..."), KTitleWidget::ErrorMessage); + KDialog::enableButtonOk(false); + if (!command->hasFocus()) + dayOfMonthButtons[1]->setFocus(); + return; + } + + // the hours + valid = false; + for (int ho = 0; ho <= 23; ho++) { + if (hourButtons[ho]->isChecked()) + valid = true; + } + + if (!valid) { + setupTitleWidget(i18n("Please select from the 'Hours' section..."), KTitleWidget::ErrorMessage); + KDialog::enableButtonOk(false); + if (!command->hasFocus()) + hourButtons[0]->setFocus(); + return; + } + + // the mins + valid = false; + for (int mi = 0; mi <= minuteTotal; ++mi) { + if (minuteButtons[mi]->isChecked()) + valid = true; + } + + if (!valid) { + setupTitleWidget(i18n("Please select from the 'Minutes' section..."), KTitleWidget::ErrorMessage); + KDialog::enableButtonOk(false); + if (!command->hasFocus()) + minuteButtons[0]->setFocus(); + return; + } + + defineCommandIcon(); + setupTitleWidget(i18n("This task will be executed at the specified intervals.")); + + enableButtonOk(true); + +} + +void TaskEditorDialog::slotCancel() { + close(); +} + +void TaskEditorDialog::slotAllMonths() { + bool checked = false; + if (allMonths->isSetAll()) { + checked = true; + } + + for (int mo = CTMonth::MINIMUM; mo <= CTMonth::MAXIMUM; mo++) { + monthButtons[mo]->setChecked(checked); + } + + slotMonthChanged(); +} + +void TaskEditorDialog::slotMonthChanged() { + bool allCleared = true; + for (int mo = CTMonth::MINIMUM; mo <= CTMonth::MAXIMUM; mo++) { + if (monthButtons[mo]->isChecked()) { + allCleared = false; + break; + } + } + + if (allCleared) { + allMonths->setStatus(SetOrClearAllButton::SET_ALL); + } else { + allMonths->setStatus(SetOrClearAllButton::CLEAR_ALL); + } +} + +void TaskEditorDialog::slotAllDaysOfMonth() { + bool checked = false; + if (allDaysOfMonth->isSetAll()) { + checked = true; + } + + for (int dm = CTDayOfMonth::MINIMUM; dm <= CTDayOfMonth::MAXIMUM; dm++) { + dayOfMonthButtons[dm]->setChecked(checked); + } + + slotDayOfMonthChanged(); +} + +void TaskEditorDialog::slotDayOfMonthChanged() { + bool allCleared = true; + for (int dm = CTDayOfMonth::MINIMUM; dm <= CTDayOfMonth::MAXIMUM; dm++) { + if (dayOfMonthButtons[dm]->isChecked()) { + allCleared = false; + break; + } + } + + if (allCleared) { + allDaysOfMonth->setStatus(SetOrClearAllButton::SET_ALL); + } else { + allDaysOfMonth->setStatus(SetOrClearAllButton::CLEAR_ALL); + } +} + +void TaskEditorDialog::slotAllDaysOfWeek() { + if (allDaysOfWeek->isSetAll()) { + for (int dw = 1; dw <= 7; dw++) { + dayOfWeekButtons[dw]->setChecked(true); + } + } else { + for (int dw = 1; dw <= 7; dw++) { + dayOfWeekButtons[dw]->setChecked(false); + } + } + slotDayOfWeekChanged(); +} + +void TaskEditorDialog::slotDayOfWeekChanged() { + bool allChecked = true; + bool allCleared = true; + for (int dw = 1; dw <= 7; dw++) { + if (dayOfWeekButtons[dw]->isChecked()) { + allCleared = false; + } else { + allChecked = false; + } + } + if (allCleared) { + allDaysOfWeek->setStatus(SetOrClearAllButton::SET_ALL); + } else { + allDaysOfWeek->setStatus(SetOrClearAllButton::CLEAR_ALL); + } +} + +void TaskEditorDialog::slotAllHours() { + if (allHours->isSetAll()) { + for (int ho = 0; ho <= 23; ho++) { + hourButtons[ho]->setChecked(true); + } + } else { + for (int ho = 0; ho <= 23; ho++) { + hourButtons[ho]->setChecked(false); + } + } + slotHourChanged(); +} + +void TaskEditorDialog::slotHourChanged() { + bool allChecked = true; + bool allCleared = true; + for (int ho = 0; ho <= 23; ho++) { + if (hourButtons[ho]->isChecked()) { + allCleared = false; + } else { + allChecked = false; + } + } + + if (allCleared) { + allHours->setStatus(SetOrClearAllButton::SET_ALL); + } else { + allHours->setStatus(SetOrClearAllButton::CLEAR_ALL); + } +} + +void TaskEditorDialog::slotMinutesPreselection(int index) { + QVariant itemData = minutesPreselection->itemData(index); + int step = itemData.toInt(); + logDebug() << "Selected step " << step << endl; + + if (step == -1) { + //Unselect everything + for (int mi = 0; mi <= minuteTotal; ++mi) { + minuteButtons[mi]->setChecked(false); + } + + //Select Custom selection in the combo box + for (int index=0; index < minutesPreselection->count(); ++index) { + if (minutesPreselection->itemData(index).toInt() == 0) { + minutesPreselection->setCurrentIndex(index); + break; + } + } + } + else if (step != 0) { + for (int mi = 0; mi <= minuteTotal; ++mi) { + if (mi % step == 0) + minuteButtons[mi]->setChecked(true); + else + minuteButtons[mi]->setChecked(false); + } + } + + if (stepisChecked()); + } + + int period = minutes.findPeriod(); + + for(int index=0; indexcount(); ++index) { + if (minutesPreselection->itemData(index).toInt() == period) { + minutesPreselection->setCurrentIndex(index); + break; + } + } +} + + +/** + * SetOrClearAllButton class implementation + */ + +SetOrClearAllButton::SetOrClearAllButton(QWidget * parent, SetOrClearAllButton::Status status) : + QPushButton(parent) { + + setStatus(status); +} + +void SetOrClearAllButton::setStatus(SetOrClearAllButton::Status status) { + currentStatus = status; + + if (currentStatus == SetOrClearAllButton::SET_ALL) + setText(i18n("Set All")); + else + setText(i18n("Clear All")); + +} + +bool SetOrClearAllButton::isSetAll() { + return currentStatus == SetOrClearAllButton::SET_ALL; +} + +bool SetOrClearAllButton::isClearAll() { + return currentStatus == SetOrClearAllButton::CLEAR_ALL; +} + +/** + * KTPushButton class implementation + */ + +NumberPushButton::NumberPushButton(QWidget * parent) : + QPushButton(parent), isDirty(false) { + updatePalette(); +} + +NumberPushButton::NumberPushButton(bool digitMode, QWidget * parent) : + QPushButton(parent), isDirty(false) { + if (digitMode) { + setFixedWidth(12 + fontMetrics().width(QLatin1String( "44" ))); + KAcceleratorManager::setNoAccel(this); + } + updatePalette(); +} + +void NumberPushButton::updatePalette() { + palNormal = ((QWidget *)parent())->palette(); + palSelected = palNormal; + for (int cg = (int) QPalette::Active; cg < (int) QPalette::NColorGroups; cg++) { + palSelected.setColor( (QPalette::ColorGroup)cg, QPalette::Button, palSelected.color((QPalette::ColorGroup)cg, QPalette::Highlight)); + palSelected.setColor( (QPalette::ColorGroup)cg, QPalette::ButtonText, palSelected.color((QPalette::ColorGroup)cg, QPalette::HighlightedText)); + } + isDirty = true; +} + +bool NumberPushButton::event(QEvent *e) { + if (e->type() == QEvent::PaletteChange) { + updatePalette(); + update(); + } + return QPushButton::event(e); +} + +void NumberPushButton::paintEvent(QPaintEvent*) { + QStylePainter p(this); + QStyleOptionButton option; + initStyleOption(&option); + + if (isDirty || isChecked()) { + isDirty = false; + if (isChecked()) { + option.palette = palSelected; + QFont f=p.font(); + f.setBold(true); + p.setFont(f); + } + } + p.drawControl(QStyle::CE_PushButton, option); +} + +#include "taskEditorDialog.moc" diff --git a/kcron/src/taskEditorDialog.h b/kcron/src/taskEditorDialog.h new file mode 100644 index 00000000..25f13640 --- /dev/null +++ b/kcron/src/taskEditorDialog.h @@ -0,0 +1,268 @@ +/*************************************************************************** + * KT task editor window header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef TASK_EDITOR_DIALOG_H +#define TASK_EDITOR_DIALOG_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +class QLabel; +class QLineEdit; +class QCheckBox; +class QGridLayout; +class QHBoxLayout; + +class KUrlRequester; + +class CTTask; + +class CrontabWidget; + +class SetOrClearAllButton : public QPushButton { + +public: + + enum Status { + SET_ALL, + CLEAR_ALL + }; + + SetOrClearAllButton(QWidget * parent, SetOrClearAllButton::Status status); + + void setStatus(SetOrClearAllButton::Status status); + + bool isSetAll(); + + bool isClearAll(); + +private: + + SetOrClearAllButton::Status currentStatus; + +}; + +class NumberPushButton : public QPushButton { +public: + + NumberPushButton(QWidget * parent); + NumberPushButton(bool digitMode, QWidget * parent); + + void updatePalette(); + + bool event(QEvent *e); + + void paintEvent(QPaintEvent*); + + bool isDirty; + QPalette palSelected; + QPalette palNormal; +}; + +/** + * Task editor window. + */ +class TaskEditorDialog : public KDialog { +Q_OBJECT + +public: + + /** + * Initialize from CTTask. + */ + explicit TaskEditorDialog(CTTask* ctTask, const QString& caption, CrontabWidget* crontabWidget); + + /** + * Destroy. + */ + ~TaskEditorDialog(); + + + +private slots: + + + /** + * Control the task title bar. + */ + void setupTitleWidget(const QString& comment=QLatin1String(""), KTitleWidget::MessageType = KTitleWidget::PlainMessage); + + /** + * Enable checkbox has changed + */ + void slotEnabledChanged(); + + /** + * Reboot checkbox has changed + */ + void slotRebootChanged(); + + /** + * Daily checkbox has been changed. + */ + void slotDailyChanged(); + + /** + * Apply changes and close. + */ + void slotOK(); + + /** + * Run the wizard. + */ + void slotWizard(); + + /** + * Cancel and close. + */ + void slotCancel(); + + /** + * Set or clear all month checkboxes + */ + void slotAllMonths(); + + /** + * A month checkbox has changed + */ + void slotMonthChanged(); + + /** + * Set or clear all day of month checkboxes + */ + void slotAllDaysOfMonth(); + + /** + * A day of month checkbox has changed + */ + void slotDayOfMonthChanged(); + + /** + * Set or clear all day of week checkboxes + */ + void slotAllDaysOfWeek(); + + /** + * A day of week checkbox has changed + */ + void slotDayOfWeekChanged(); + + /** + * Set or clear all hour checkboxes + */ + void slotAllHours(); + + /** + * An hour checkbox has changed + */ + void slotHourChanged(); + + void slotMinutesPreselection(int index); + + /** + * A minute checkbox has changed + */ + void slotMinuteChanged(); + +private: + NumberPushButton* createHourButton(QGroupBox* hoursGroup, int hour); + QGroupBox* createHoursGroup(QWidget* mainWidget); + + NumberPushButton* createMinuteButton(int minuteIndex); + void createMinutesGroup(QWidget* mainWidget); + + /** + * Returns true if there is no checked minute in the hidden minuteButton + */ + bool canReduceMinutesGroup(); + void emptyMinutesGroup(); + void reduceMinutesGroup(); + void increaseMinutesGroup(); + + QGroupBox* createMonthsGroup(QWidget* mainWidget); + + QGroupBox* createDaysOfMonthGroup(QWidget* mainWidget); + QGroupBox* createDaysOfWeekGroup(QWidget* mainWidget); + + bool checkCommand(); + + void defineCommandIcon(); + + bool isEveryDay(); + + /** + * Task. + */ + CTTask* ctTask; + + CrontabWidget* crontabWidget; + + // Widgets. + + KTitleWidget* titleWidget; + + QComboBox* userCombo; + + QTextEdit* leComment; + + QLabel* commandIcon; + KUrlRequester* command; + + QCheckBox* chkEnabled; + QCheckBox* chkReboot; + QCheckBox* cbEveryDay; + + QGroupBox* bgMonth; + NumberPushButton* monthButtons[13]; // The index 0 is not used + SetOrClearAllButton* allMonths; + + QGroupBox* bgDayOfMonth; + NumberPushButton* dayOfMonthButtons[32]; // The index 0 is not used + SetOrClearAllButton* allDaysOfMonth; + + QGroupBox* bgDayOfWeek; + NumberPushButton* dayOfWeekButtons[8]; // The index 0 is not used + SetOrClearAllButton* allDaysOfWeek; + + QGroupBox* hoursGroup; + QLabel* morningLabel; + QLabel* afternoonLabel; + QPushButton* hourButtons[24]; + SetOrClearAllButton* allHours; + + QGroupBox* minutesGroup; + QGridLayout* minutesLayout; + QPushButton* minuteButtons[60]; + + QHBoxLayout* minutesPreselectionLayout; + QComboBox* minutesPreselection; + + static const int minuteTotal = 59; // or 55 or 59 + + static const int minutePerColumn = 12; // or 30 or 12 + + static const int reducedMinuteStep = 5; + + QStringList specialValidCommands; + +}; + +#endif // TASK_EDITOR_DIALOG_H diff --git a/kcron/src/taskWidget.cpp b/kcron/src/taskWidget.cpp new file mode 100644 index 00000000..68b857ee --- /dev/null +++ b/kcron/src/taskWidget.cpp @@ -0,0 +1,68 @@ +/*************************************************************************** + * KT list view item task implementation. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "taskWidget.h" + +#include +#include + +#include "cttask.h" +#include "ctcron.h" + +#include "tasksWidget.h" +#include "crontabWidget.h" +#include "kcronIcons.h" +#include "taskEditorDialog.h" + +#include "logging.h" + +TaskWidget::TaskWidget(TasksWidget* _tasksWidget, CTTask* _cttask) : + QTreeWidgetItem(_tasksWidget->treeWidget()) { + + ctTask = _cttask; + tasksWidget = _tasksWidget; + + refresh(); +} + +void TaskWidget::refresh() { + int column = 0; + + if (tasksWidget->needUserColumn()) { + setText(column++, ctTask->userLogin); + } + + setText(column++, ctTask->schedulingCronFormat()); + + setText(column, ctTask->command); + setIcon(column++, ctTask->commandIcon()); + + if (ctTask->enabled) { + setText(column, i18n("Enabled")); + setIcon(column++, SmallIcon(QLatin1String( "dialog-ok-apply" ))); + } else { + setText(column, i18n("Disabled")); + setIcon(column++, SmallIcon(QLatin1String( "dialog-cancel" ))); + } + + setText(column++, ctTask->comment); + setText(column++, ctTask->describe()); + +} + +void TaskWidget::toggleEnable() { + ctTask->enabled = !ctTask->enabled; + refresh(); +} + +CTTask* TaskWidget::getCTTask() const { + return ctTask; +} diff --git a/kcron/src/taskWidget.h b/kcron/src/taskWidget.h new file mode 100644 index 00000000..390c4be2 --- /dev/null +++ b/kcron/src/taskWidget.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * KT list view item task header. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef TASK_WIDGET_H +#define TASK_WIDGET_H + +#include + +class CTTask; +class TasksWidget; + +/** + * QTreeWidgetItem with a CTTask. + */ +class TaskWidget : public QTreeWidgetItem { +public: + + /** + * Initialize the list view item and task. + */ + TaskWidget(TasksWidget* tasksWidget, CTTask* _cttask); + + /* + * Change the status of this task + */ + void toggleEnable(); + + /** + * Get the task. + */ + CTTask* getCTTask() const; + + /** + * Refresh from underlying task. + */ + void refresh(); + +private: + + /** + * Task. + */ + CTTask* ctTask; + + TasksWidget* tasksWidget; +}; + +#endif // TASK_WIDGET_H diff --git a/kcron/src/tasksWidget.cpp b/kcron/src/tasksWidget.cpp new file mode 100644 index 00000000..5a60a05a --- /dev/null +++ b/kcron/src/tasksWidget.cpp @@ -0,0 +1,358 @@ +/*************************************************************************** + * KT list view item tasks implementation. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "tasksWidget.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ctcron.h" +#include "cthost.h" +#include "cttask.h" +#include "ctvariable.h" + +#include "kcronIcons.h" +#include "crontabWidget.h" +#include "taskWidget.h" +#include "taskEditorDialog.h" + +#include "logging.h" + +class TasksWidgetPrivate { +public: + + QAction* newTaskAction; + + QAction* modifyAction; + + QAction* deleteAction; + + QAction* runNowAction; + + KAction* printAction; + +}; + +/** + * Construct tasks folder from branch. + */ +TasksWidget::TasksWidget(CrontabWidget* crontabWidget) : + GenericListWidget(crontabWidget, i18n("Scheduled Tasks"), KCronIcons::task(KCronIcons::Small)), + d(new TasksWidgetPrivate()) { + + refreshHeaders(); + + treeWidget()->sortItems(1, Qt::AscendingOrder); + + setupActions(crontabWidget); + prepareContextualMenu(); + + connect(treeWidget(), SIGNAL(itemSelectionChanged()), this, SLOT(changeCurrentSelection())); + + logDebug() << "Tasks list created" << endl; +} + +TasksWidget::~TasksWidget() { + delete d; +} + +QList TasksWidget::selectedTasksWidget() const { + QList tasksWidget; + + QList tasksItems = treeWidget()->selectedItems(); + foreach(QTreeWidgetItem* item, tasksItems) { + TaskWidget* taskWidget = static_cast(item); + tasksWidget.append(taskWidget); + } + + return tasksWidget; +} + +TaskWidget* TasksWidget::firstSelectedTaskWidget() const { + QTreeWidgetItem* item = firstSelected(); + if (item==NULL) + return NULL; + + return static_cast(item); +} + +int TasksWidget::statusColumnIndex() { + if (needUserColumn()) { + return 3; + } + + return 2; +} + +void TasksWidget::runTaskNow() const { + TaskWidget* taskWidget = firstSelectedTaskWidget(); + if (taskWidget == NULL) + return; + QString taskCommand = taskWidget->getCTTask()->command; + + + QString echoMessage = i18nc("Do not use any quote characters (') in this string", "End of script execution. Type Enter or Ctrl+C to exit."); + + CTCron* ctCron = crontabWidget()->currentCron(); + if (ctCron == NULL) { + logDebug() << "Unable to find the related CtCron, please report this bug to KCron developer" << endl; + return; + } + + + QStringList commandList; + + foreach(CTVariable* variable, ctCron->variables()) { + commandList << QString::fromLatin1("export %1=\"%2\"").arg(variable->variable, variable->value); + } + + commandList << taskCommand; + commandList << QLatin1String( "echo '-------------------------------------------------------------------------'" ); + commandList << QLatin1String( "echo " ) + echoMessage; + commandList << QLatin1String( "echo '-------------------------------------------------------------------------'" ); + commandList << QLatin1String( "read" ); + + QStringList parameters; + parameters << QLatin1String( "-e" ) << QLatin1String( "bash" ) << QLatin1String( "-c" ); + parameters << commandList.join(QLatin1String( ";" )); + + QProcess process; + process.startDetached(QLatin1String( "konsole" ), parameters); + +} + +void TasksWidget::createTask() { + CTTask* task = new CTTask(QLatin1String( "" ), QLatin1String( "" ), crontabWidget()->currentCron()->userLogin(), crontabWidget()->currentCron()->isMultiUserCron()); + + TaskEditorDialog taskEditorDialog(task, i18n("New Task"), crontabWidget()); + int result = taskEditorDialog.exec(); + + if (result == QDialog::Accepted) { + addTask(task); + emit taskModified(true); + + changeCurrentSelection(); + } + else { + delete task; + } + +} + +void TasksWidget::addTask(CTTask* task) { + CTCron* cron = crontabWidget()->currentCron(); + + cron->addTask(task); + new TaskWidget(this, task); +} + +void TasksWidget::modifySelection() { + modifySelection(firstSelectedTaskWidget(), -1); +} + +void TasksWidget::modifySelection(QTreeWidgetItem* item, int position) { + TaskWidget* taskWidget = static_cast(item); + if (taskWidget!=NULL) { + + if (position == statusColumnIndex()) { + taskWidget->toggleEnable(); + emit taskModified(true); + } + else { + CTTask* task = taskWidget->getCTTask(); + TaskEditorDialog taskEditorDialog(task, i18n("Modify Task"), crontabWidget()); + int result = taskEditorDialog.exec(); + + if (result == QDialog::Accepted) { + crontabWidget()->currentCron()->modifyTask(task); + taskWidget->refresh(); + emit taskModified(true); + } + } + + } + + logDebug() << "End of modification" << endl; + +} + +void TasksWidget::deleteSelection() { + logDebug() << "Selection deleting..." << endl; + + QList tasksItems = treeWidget()->selectedItems(); + + bool deleteSomething = ! (tasksItems.isEmpty()); + + foreach(QTreeWidgetItem* item, tasksItems) { + TaskWidget* taskWidget = static_cast(item); + + crontabWidget()->currentCron()->removeTask(taskWidget->getCTTask()); + delete taskWidget->getCTTask(); + treeWidget()->takeTopLevelItem( treeWidget()->indexOfTopLevelItem(taskWidget) ); + delete taskWidget; + + } + + if (deleteSomething) { + emit taskModified(true); + changeCurrentSelection(); + } + + logDebug() << "End of deletion" << endl; +} + +void TasksWidget::refreshTasks(CTCron* cron) { + //Remove previous items + removeAll(); + + refreshHeaders(); + + //Add new items + foreach(CTTask* ctTask, cron->tasks()) { + new TaskWidget(this, ctTask); + } + + resizeColumnContents(); + +} + +void TasksWidget::refreshHeaders() { + QStringList headerLabels; + + if (needUserColumn()) { + headerLabels << i18n("User"); + } + + headerLabels << i18n("Scheduling"); + + headerLabels << i18n("Command"); + headerLabels << i18n("Status"); + headerLabels << i18n("Description"); + headerLabels << i18n("Scheduling Details"); + + treeWidget()->setHeaderLabels(headerLabels); + + if (needUserColumn()) + treeWidget()->setColumnCount(6); + else + treeWidget()->setColumnCount(5); + +} + +bool TasksWidget::needUserColumn() const { + CTCron* ctCron = crontabWidget()->currentCron(); + if (ctCron && ctCron->isMultiUserCron()) { + return true; + } + + return false; + +} + +void TasksWidget::setupActions(CrontabWidget* crontabWidget) { + + d->newTaskAction = new QAction(this); + d->newTaskAction->setIcon(KIcon( QLatin1String( "document-new" ))); + d->newTaskAction->setText(i18nc("Adds a new task", "New &Task...") ); + d->newTaskAction->setToolTip(i18n("Create a new task.")); + addRightAction(d->newTaskAction, this, SLOT(createTask())); + + d->modifyAction = new QAction(this); + d->modifyAction->setText(i18n("M&odify...") ); + d->modifyAction->setIcon(KIcon( QLatin1String( "document-open" )) ); + d->modifyAction->setToolTip(i18n("Modify the selected task.")); + addRightAction(d->modifyAction, this, SLOT(modifySelection())); + + d->deleteAction = new QAction(this); + d->deleteAction->setText(i18n("&Delete") ); + d->deleteAction->setIcon(KIcon( QLatin1String( "edit-delete" )) ); + d->deleteAction->setToolTip(i18n("Delete the selected task.")); + addRightAction(d->deleteAction, this, SLOT(deleteSelection())); + + d->runNowAction = new QAction(this); + d->runNowAction->setText(i18n("&Run Now") ); + d->runNowAction->setIcon(KIcon( QLatin1String( "system-run" ))); + d->runNowAction->setToolTip(i18n("Run the selected task now.")); + addRightAction(d->runNowAction, this, SLOT(runTaskNow())); + + d->printAction = KStandardAction::print(crontabWidget, SLOT(print()), this); + addRightAction(d->printAction, crontabWidget, SLOT(print())); + + addRightStretch(); +} + +void TasksWidget::prepareContextualMenu() { + + treeWidget()->addAction(d->newTaskAction); + + treeWidget()->addAction(createSeparator()); + + treeWidget()->addAction(d->modifyAction); + treeWidget()->addAction(d->deleteAction); + + treeWidget()->addAction(createSeparator()); + + foreach(QAction* action, crontabWidget()->cutCopyPasteActions()) { + treeWidget()->addAction(action); + } + + treeWidget()->addAction(createSeparator()); + + treeWidget()->addAction(d->runNowAction); + +} + +void TasksWidget::toggleRunNowAction(bool state) { + setActionEnabled(d->runNowAction, state); +} + +void TasksWidget::togglePrintAction(bool state) { + setActionEnabled(d->printAction, state); +} + +void TasksWidget::toggleModificationActions(bool state) { + setActionEnabled(d->modifyAction, state); + setActionEnabled(d->deleteAction, state); +} + +void TasksWidget::toggleNewEntryAction(bool state) { + setActionEnabled(d->newTaskAction, state); +} + +void TasksWidget::changeCurrentSelection() { + //logDebug() << "Change selection..." << endl; + + if (treeWidget()->topLevelItemCount()==0) { + togglePrintAction(false); + } + else { + togglePrintAction(true); + } + + bool enabled; + if (treeWidget()->selectedItems().isEmpty()) + enabled = false; + else + enabled = true; + + toggleModificationActions(enabled); + toggleRunNowAction(enabled); + +} diff --git a/kcron/src/tasksWidget.h b/kcron/src/tasksWidget.h new file mode 100644 index 00000000..95699a39 --- /dev/null +++ b/kcron/src/tasksWidget.h @@ -0,0 +1,104 @@ +/*************************************************************************** + * KT list view item cron tasks folder. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef TASKS_WIDGET_H +#define TASKS_WIDGET_H + +#include + +#include "genericListWidget.h" +#include "cthost.h" + +class TaskWidget; + +class TasksWidgetPrivate; + +/** + * QTreeWidget of a "tasks" folder. + */ +class TasksWidget : public GenericListWidget { + Q_OBJECT + +public: + + /** + * Construct tasks folder from branch. + */ + TasksWidget(CrontabWidget* crontabWidget); + + ~TasksWidget(); + + TaskWidget* firstSelectedTaskWidget() const; + + QList selectedTasksWidget() const; + + void refreshTasks(CTCron* cron); + + bool needUserColumn() const; + + /** + * Enables/disables modification buttons + */ + void toggleModificationActions(bool enabled); + + /** + * Enables/disables new entry actions + */ + void toggleNewEntryAction(bool enabled); + + /** + * Enables/disables "Run now" + */ + void toggleRunNowAction(bool enabled); + + /** + * Enables/disables Print Action + */ + void togglePrintAction(bool enabled); + +signals: + void taskModified(bool); + +public slots: + void modifySelection(); + + void deleteSelection(); + + /** + * Run task now. + */ + void runTaskNow() const; + + /** + * Create a new task. Default is which type is most recently selected. + */ + void createTask(); + + void addTask(CTTask* task); + + void changeCurrentSelection(); + +protected slots: + void modifySelection(QTreeWidgetItem* item, int position); + +private: + void refreshHeaders(); + + int statusColumnIndex(); + + void setupActions(CrontabWidget* crontabWidget); + void prepareContextualMenu(); + + + TasksWidgetPrivate* const d; +}; + +#endif // TASKS_WIDGET_H diff --git a/kcron/src/variableEditorDialog.cpp b/kcron/src/variableEditorDialog.cpp new file mode 100644 index 00000000..053a2cd9 --- /dev/null +++ b/kcron/src/variableEditorDialog.cpp @@ -0,0 +1,225 @@ +/*************************************************************************** + * KT environment variable editor window implementation * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "variableEditorDialog.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ctvariable.h" +#include "cthost.h" +#include "ctcron.h" + +#include "crontabWidget.h" + +#include "kcronIcons.h" +#include "kcronHelper.h" +#include +VariableEditorDialog::VariableEditorDialog(CTVariable* _ctVariable, const QString &_caption, CrontabWidget* _crontabWidget) : + KDialog(_crontabWidget) { + ctVariable = _ctVariable; + crontabWidget = _crontabWidget; + + setModal(true); + setCaption(_caption); + setButtons(Ok|Cancel); + setDefaultButton(Ok); + + QWidget* page = new QWidget; + QGridLayout* layout = new QGridLayout; + page->setLayout(layout); + + layout->setMargin(0); + layout->setColumnMinimumWidth(1, 270); + layout->setRowStretch(3, 1); + layout->setColumnStretch(1, 1); + + setMainWidget(page); + + setWindowIcon(KCronIcons::application(KCronIcons::Small)); + + int layoutPosition = 0; + + // top title widget + titleWidget = new KTitleWidget(this); + titleWidget->setText(i18n("Add or modify a variable")); + layout->addWidget(titleWidget, layoutPosition, 0, 1, 2); + + // variable + QLabel* labVariable = new QLabel(i18nc("The environmental variable name ie HOME, MAILTO etc", "&Variable:"), this); + layout->addWidget(labVariable, ++layoutPosition, 0, Qt::AlignLeft); + + cmbVariable = new QComboBox(this); + cmbVariable->setEditable(true); + layout->addWidget(cmbVariable, layoutPosition, 1); + + cmbVariable->addItem(QLatin1String( "HOME" )); + cmbVariable->addItem(QLatin1String( "MAILTO" )); + cmbVariable->addItem(QLatin1String( "PATH" )); + cmbVariable->addItem(QLatin1String( "SHELL" )); + cmbVariable->addItem(QLatin1String( "LD_CONFIG_PATH" )); + + labVariable->setBuddy(cmbVariable); + + // details + QLabel* labDetails = new QLabel(QLatin1String( "" ), this); + layout->addWidget(labDetails, ++layoutPosition, 0, Qt::AlignLeft); + + QHBoxLayout* detailsLayout = new QHBoxLayout; + detailsIcon = new QLabel(this); + detailsLayout->addWidget(detailsIcon); + + details = new QLabel(this); + detailsLayout->addWidget(details); + + layout->addLayout(detailsLayout, layoutPosition, 1, Qt::AlignLeft); + + // value + QLabel* labValue = new QLabel(i18n("Va&lue:"), this); + layout->addWidget(labValue, ++layoutPosition, 0, Qt::AlignLeft); + + leValue = new QLineEdit(this); + layout->addWidget(leValue, layoutPosition, 1); + leValue->setMaxLength(255); + labValue->setBuddy(leValue); + + // User Combo + QLabel* userLabel = new QLabel( i18n("&Run as:"), this); + layout->addWidget(userLabel, ++layoutPosition, 0); + + userCombo = new QComboBox(this); + + userLabel->setBuddy(userCombo); + layout->addWidget(userCombo, layoutPosition, 1); + + if (crontabWidget->variablesWidget()->needUserColumn()) { + KCronHelper::initUserCombo(userCombo, crontabWidget, ctVariable->userLogin); + + } + else { + userLabel->hide(); + userCombo->hide(); + } + + // comment + QLabel* labComment = new QLabel(i18n("Co&mment:"), this); + layout->addWidget(labComment, ++layoutPosition, 0, Qt::AlignLeft); + + teComment = KCronHelper::createCommentEdit(this); + layout->addWidget(teComment, layoutPosition, 1); + labComment->setBuddy(teComment); + + // enabled + chkEnabled = new QCheckBox(i18n("&Enable this variable"), this); + layout->addWidget(chkEnabled, ++layoutPosition, 0, 1, 2); + + // set starting field values + cmbVariable->setEditText(ctVariable->variable); + leValue->setText(ctVariable->value); + teComment->setText(ctVariable->comment); + chkEnabled->setChecked(ctVariable->enabled); + cmbVariable->setFocus(); + + slotEnabled(); + slotWizard(); + show(); + + // connect them up + connect(cmbVariable, SIGNAL(editTextChanged(QString)), SLOT(slotWizard())); + connect(leValue, SIGNAL(textEdited(QString)), SLOT(slotWizard())); + connect(this, SIGNAL(okClicked()), this, SLOT(slotOk())); + connect(chkEnabled, SIGNAL(clicked()), SLOT(slotEnabled())); + +} + +VariableEditorDialog::~VariableEditorDialog() { +} + +void VariableEditorDialog::setupTitleWidget(const QString& comment, KTitleWidget::MessageType messageType) { + //krazy:exclude=doublequote_chars + if (comment.isEmpty()) { + titleWidget->setComment(i18n("This variable will be used by scheduled tasks.")); + titleWidget->setPixmap(KCronIcons::variable(KCronIcons::Large), KTitleWidget::ImageRight); + } + else { + titleWidget->setComment(comment, messageType); + if (messageType == KTitleWidget::ErrorMessage) + titleWidget->setPixmap(KIcon(KCronIcons::error(KCronIcons::Large)), KTitleWidget::ImageRight); + else + titleWidget->setPixmap(KIcon(KCronIcons::information(KCronIcons::Large)), KTitleWidget::ImageRight); + } +} + +void VariableEditorDialog::slotEnabled() { + bool enabled = chkEnabled->isChecked(); + cmbVariable->setEnabled(enabled); + leValue->setEnabled(enabled); + teComment->setEnabled(enabled); + userCombo->setEnabled(enabled); +} + +void VariableEditorDialog::slotOk() { + ctVariable->variable = cmbVariable->currentText(); + ctVariable->value = leValue->text(); + ctVariable->comment = teComment->toPlainText(); + ctVariable->enabled = chkEnabled->isChecked(); + + // save work in process + if (crontabWidget->variablesWidget()->needUserColumn()) { + ctVariable->userLogin = userCombo->currentText(); + } + + close(); +} + +void VariableEditorDialog::slotWizard() { + CTVariable tempVariable(*ctVariable); + tempVariable.variable = cmbVariable->currentText(); + + detailsIcon->setPixmap(tempVariable.variableIcon()); + details->setText(tempVariable.information()); + + bool error = false; + + if (!chkEnabled->isChecked()) { + setupTitleWidget(i18n("This variable is disabled.")); + chkEnabled->setFocus(); + KDialog::enableButtonOk(true); + error = true; + } + + if (cmbVariable->currentText().isEmpty() && !error) { + setupTitleWidget(i18n("Please enter the variable name..."), KTitleWidget::ErrorMessage); + cmbVariable->setFocus(); + KDialog::enableButtonOk(false); + error = true; + } + + if (leValue->text().isEmpty() && !error) { + setupTitleWidget(i18n("Please enter the variable value ..."), KTitleWidget::ErrorMessage); + KDialog::enableButtonOk(false); + error = true; + } + + if (!error) { + setupTitleWidget(); + KDialog::enableButtonOk(true); + } +} + +#include "variableEditorDialog.moc" diff --git a/kcron/src/variableEditorDialog.h b/kcron/src/variableEditorDialog.h new file mode 100644 index 00000000..ff2470cd --- /dev/null +++ b/kcron/src/variableEditorDialog.h @@ -0,0 +1,94 @@ +/*************************************************************************** + * KT environment variable editor window header * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef VARIABLE_EDITOR_DIALOG_H +#define VARIABLE_EDITOR_DIALOG_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +class CTVariable; +class CrontabWidget; + +/** + * Environment variable editor window. + */ +class VariableEditorDialog : public KDialog { +Q_OBJECT + +public: + + /** + * Initialize from CTVariable. + */ + explicit VariableEditorDialog(CTVariable* _ctVariable, const QString &_caption, CrontabWidget* crontabWidget); + + /** + * Destroy. + */ + ~VariableEditorDialog(); + +private slots: + + /** + * Setup title widget + */ + void setupTitleWidget(const QString& comment = QLatin1String(""), KTitleWidget::MessageType = KTitleWidget::PlainMessage); + + /** + * Enable / disable variable + */ + void slotEnabled(); + + /** + * Apply changes and close. + */ + void slotOk(); + + /** + * Run the wizard. + */ + void slotWizard(); + +private: + + /** + * Environment variable. + */ + CTVariable* ctVariable; + + CrontabWidget* crontabWidget; + + // Widgets. + KTitleWidget* titleWidget; + + QComboBox* cmbVariable; + + QLabel* detailsIcon; + QLabel* details; + + QLineEdit* leValue; + + QTextEdit* teComment; + + QCheckBox* chkEnabled; + + QComboBox* userCombo; +}; + +#endif // VARIABLE_EDITOR_DIALOG_H diff --git a/kcron/src/variableWidget.cpp b/kcron/src/variableWidget.cpp new file mode 100644 index 00000000..576b2b2e --- /dev/null +++ b/kcron/src/variableWidget.cpp @@ -0,0 +1,65 @@ +/*************************************************************************** + * KT list view item task implementation. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "variableWidget.h" + +#include + +#include "ctvariable.h" + +#include "variableEditorDialog.h" +#include "kcronIcons.h" +#include "taskEditorDialog.h" + +VariableWidget::VariableWidget(VariablesWidget* _variablesWidget, CTVariable* _ctVariable) : + QTreeWidgetItem(_variablesWidget->treeWidget()) { + + ctVariable = _ctVariable; + variablesWidget = _variablesWidget; + + refresh(); +} + +void VariableWidget::refresh() { + int column = 0; + + if (variablesWidget->needUserColumn()) { + setText(column++, ctVariable->userLogin); + } + + setText(column, ctVariable->variable); + setIcon(column++, ctVariable->variableIcon()); + + setText(column++, ctVariable->value); + + if (ctVariable->enabled) { + setText(column, i18n("Enabled")); + setIcon(column++, SmallIcon(QLatin1String( "dialog-ok-apply" ))); + } + else { + setText(column, i18n("Disabled")); + setIcon(column++, SmallIcon(QLatin1String( "dialog-cancel" ))); + } + + setText(column++, ctVariable->comment); + + +} + + +void VariableWidget::toggleEnable() { + ctVariable->enabled = ! ctVariable->enabled; + refresh(); +} + +CTVariable* VariableWidget::getCTVariable() const { + return ctVariable; +} diff --git a/kcron/src/variableWidget.h b/kcron/src/variableWidget.h new file mode 100644 index 00000000..fbf07236 --- /dev/null +++ b/kcron/src/variableWidget.h @@ -0,0 +1,60 @@ +/*************************************************************************** + * KT list view item task header. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef VARIABLE_WIDGET_H +#define VARIABLE_WIDGET_H + +#include + +#include "variablesWidget.h" + + +class CTVariable; + +/** + * QTreeWidgetItem with a CTTask. + */ +class VariableWidget : public QTreeWidgetItem { +public: + + /** + * Initialize the list view item and task. + */ + VariableWidget(VariablesWidget* variablesWidget, CTVariable* _ctVariable); + + /** + * Refresh from underlying task. + */ + void refresh(); + + /* + * Change the status of this variable + */ + void toggleEnable(); + + /** + * Get the task. + */ + CTVariable* getCTVariable() const; + +private: + + QIcon findIcon() const; + + /** + * Variable + */ + CTVariable* ctVariable; + + VariablesWidget* variablesWidget; +}; + +#endif // VARIABLE_WIDGET_H diff --git a/kcron/src/variablesWidget.cpp b/kcron/src/variablesWidget.cpp new file mode 100644 index 00000000..5dadf035 --- /dev/null +++ b/kcron/src/variablesWidget.cpp @@ -0,0 +1,274 @@ +/*************************************************************************** + * KT list view item tasks implementation. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "variablesWidget.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "ctcron.h" +#include "cttask.h" +#include "ctvariable.h" + +#include "kcronIcons.h" +#include "crontabWidget.h" +#include "variableWidget.h" +#include "variableEditorDialog.h" + +#include "logging.h" + +class VariablesWidgetPrivate { +public: + + QAction* newVariableAction; + + QAction* modifyAction; + + QAction* deleteAction; + +}; +/** + * Construct tasks folder from branch. + */ +VariablesWidget::VariablesWidget(CrontabWidget* crontabWidget) : + GenericListWidget(crontabWidget, i18n("Environment Variables"), KCronIcons::variable(KCronIcons::Small)), + d(new VariablesWidgetPrivate()) { + + refreshHeaders(); + + treeWidget()->sortItems(0, Qt::AscendingOrder); + + setupActions(); + prepareContextualMenu(); + + connect(treeWidget(), SIGNAL(itemSelectionChanged()), this, SLOT(changeCurrentSelection())); + + logDebug() << "Variables list created" << endl; + +} + +VariablesWidget::~VariablesWidget() { + delete d; +} + +void VariablesWidget::modifySelection() { + modifySelection(firstSelectedVariableWidget(), -1); +} + +void VariablesWidget::modifySelection(QTreeWidgetItem* item, int position) { + VariableWidget* variableWidget = static_cast(item); + + if (variableWidget!=NULL) { + + if (position == statusColumnIndex()) { + variableWidget->toggleEnable(); + emit variableModified(true); + } + else { + CTVariable* variable = variableWidget->getCTVariable(); + VariableEditorDialog variableEditorDialog(variable, i18n("Modify Variable"), crontabWidget()); + int result = variableEditorDialog.exec(); + + if (result == QDialog::Accepted) { + crontabWidget()->currentCron()->modifyVariable(variable); + variableWidget->refresh(); + + emit variableModified(true); + } + } + } + +} + +QList VariablesWidget::selectedVariablesWidget() const { + QList variablesWidget; + + QList variablesItems = treeWidget()->selectedItems(); + foreach(QTreeWidgetItem* item, variablesItems) { + VariableWidget* variableWidget = static_cast(item); + variablesWidget.append(variableWidget); + } + + return variablesWidget; +} + + +VariableWidget* VariablesWidget::firstSelectedVariableWidget() const { + QTreeWidgetItem* item = firstSelected(); + if (item==NULL) + return NULL; + + return static_cast(item); + +} + +void VariablesWidget::deleteSelection() { + QList variablesItems = treeWidget()->selectedItems(); + bool deleteSomething = ! (variablesItems.isEmpty()); + + foreach(QTreeWidgetItem* item, variablesItems) { + VariableWidget* variableWidget = static_cast(item); + + crontabWidget()->currentCron()->removeVariable(variableWidget->getCTVariable()); + delete variableWidget->getCTVariable(); + treeWidget()->takeTopLevelItem( treeWidget()->indexOfTopLevelItem(variableWidget) ); + delete variableWidget; + + } + + if (deleteSomething) { + emit variableModified(true); + changeCurrentSelection(); + } + +} + +bool VariablesWidget::needUserColumn() { + CTCron* currentCron = crontabWidget()->currentCron(); + if (currentCron->isMultiUserCron()==true && currentCron->isSystemCron()==false) + return true; + + return false; +} + +int VariablesWidget::statusColumnIndex() { + if (needUserColumn() == true) + return 3; + + return 2; +} + + +void VariablesWidget::createVariable() { + CTVariable* variable = new CTVariable(QLatin1String( "" ), QLatin1String( "" ), crontabWidget()->currentCron()->userLogin()); + + VariableEditorDialog variableEditorDialog(variable, i18n("New Variable"), crontabWidget()); + int result = variableEditorDialog.exec(); + + if (result == QDialog::Accepted) { + addVariable(variable); + emit variableModified(true); + changeCurrentSelection(); + } + else { + delete variable; + } +} + +void VariablesWidget::addVariable(CTVariable* variable) { + logDebug() << "Add a new variable" << endl; + crontabWidget()->currentCron()->addVariable(variable); + new VariableWidget(this, variable); + + changeCurrentSelection(); +} + +void VariablesWidget::refreshVariables(CTCron* cron) { + //Remove previous items + removeAll(); + + refreshHeaders(); + + foreach(CTVariable* ctVariable, cron->variables()) { + new VariableWidget(this, ctVariable); + } + + resizeColumnContents(); +} + +void VariablesWidget::refreshHeaders() { + QStringList headerLabels; + + if (needUserColumn()) { + headerLabels << i18n("User"); + } + + headerLabels << i18n("Variable"); + headerLabels << i18n("Value"); + headerLabels << i18n("Status"); + headerLabels << i18n("Comment"); + + treeWidget()->setHeaderLabels(headerLabels); + + if (needUserColumn()) + treeWidget()->setColumnCount(5); + else + treeWidget()->setColumnCount(4); + +} + +void VariablesWidget::setupActions() { + + d->newVariableAction = new QAction(this); + d->newVariableAction->setIcon(KIcon( QLatin1String( "document-new" ))); + d->newVariableAction->setText(i18nc("Adds a new variable", "New &Variable...") ); + d->newVariableAction->setToolTip(i18n("Create a new variable.")); + addRightAction(d->newVariableAction, this, SLOT(createVariable())); + + d->modifyAction = new QAction(this); + d->modifyAction->setText(i18n("M&odify...") ); + d->modifyAction->setIcon(KIcon( QLatin1String( "document-open" )) ); + d->modifyAction->setToolTip(i18n("Modify the selected variable.")); + addRightAction(d->modifyAction, this, SLOT(modifySelection())); + + d->deleteAction = new QAction(this); + d->deleteAction->setText(i18n("&Delete") ); + d->deleteAction->setIcon(KIcon( QLatin1String( "edit-delete" )) ); + d->deleteAction->setToolTip(i18n("Delete the selected variable.")); + addRightAction(d->deleteAction, this, SLOT(deleteSelection())); + + addRightStretch(); +} + + +void VariablesWidget::prepareContextualMenu() { + + treeWidget()->addAction(d->newVariableAction); + + treeWidget()->addAction(createSeparator()); + + treeWidget()->addAction(d->modifyAction); + treeWidget()->addAction(d->deleteAction); + + treeWidget()->addAction(createSeparator()); + + foreach(QAction* action, crontabWidget()->cutCopyPasteActions()) { + treeWidget()->addAction(action); + } + +} + +void VariablesWidget::toggleModificationActions(bool state) { + setActionEnabled(d->modifyAction, state); + setActionEnabled(d->deleteAction, state); +} + +void VariablesWidget::toggleNewEntryAction(bool state) { + setActionEnabled(d->newVariableAction, state); +} + +void VariablesWidget::changeCurrentSelection() { + logDebug() << "Change selection..." << endl; + + bool enabled; + if (treeWidget()->selectedItems().isEmpty()) + enabled = false; + else + enabled = true; + + toggleModificationActions(enabled); +} diff --git a/kcron/src/variablesWidget.h b/kcron/src/variablesWidget.h new file mode 100644 index 00000000..c85d6c90 --- /dev/null +++ b/kcron/src/variablesWidget.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * KT list view item cron tasks folder. * + * -------------------------------------------------------------------- * + * Copyright (C) 1999, Gary Meyer * + * -------------------------------------------------------------------- * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef VARIABLES_WIDGET_H +#define VARIABLES_WIDGET_H + +#include + +#include "genericListWidget.h" +#include "cthost.h" + +class VariableWidget; +class CTVariable; + +class VariablesWidgetPrivate; + +/** + * QListViewItem of a "tasks" folder. + */ +class VariablesWidget : public GenericListWidget { + Q_OBJECT + +public: + + /** + * Construct tasks folder from branch. + */ + VariablesWidget(CrontabWidget* crontabWidget); + + virtual ~VariablesWidget(); + + QList selectedVariablesWidget() const; + + VariableWidget* firstSelectedVariableWidget() const; + + void refreshVariables(CTCron* cron); + + bool needUserColumn(); + + /** + * Enables/disables modification buttons + */ + void toggleModificationActions(bool enabled); + + /** + * Enables/disables new entry actions + */ + void toggleNewEntryAction(bool enabled); + +signals: + void variableModified(bool); + +public slots: + void modifySelection(); + + void deleteSelection(); + + /** + * Create a new variable. Default is which type is most recently selected. + */ + void createVariable(); + + void addVariable(CTVariable* variable); + + void changeCurrentSelection(); + +protected slots: + void modifySelection(QTreeWidgetItem* item, int position); + +private: + void refreshHeaders(); + + int statusColumnIndex(); + + void setupActions(); + + void prepareContextualMenu(); + + VariablesWidgetPrivate* const d; +}; + +#endif // VARIABLES_WIDGET_H diff --git a/kdenetwork-filesharing/CMakeLists.txt b/kdenetwork-filesharing/CMakeLists.txt new file mode 100644 index 00000000..f1039f1f --- /dev/null +++ b/kdenetwork-filesharing/CMakeLists.txt @@ -0,0 +1,45 @@ +project(filesharing) + +if(NOT INSIDE_KDENETWORK) + message("Not building inside KDENetwork, loading KDE CMake Macros.") + + find_package(KDE4 REQUIRED) + + include(KDE4Defaults) + include(MacroLibrary) + + include(CheckIncludeFile) + include(CheckIncludeFiles) + include(CheckSymbolExists) + include(CheckFunctionExists) + include(CheckLibraryExists) + include(CheckPrototypeExists) + include(CheckTypeSize) + + set(CMAKE_REQUIRED_DEFINITIONS ${_KDE4_PLATFORM_DEFINITIONS}) + if(WIN32) + set(CMAKE_REQUIRED_LIBRARIES ${KDEWIN32_LIBRARIES}) + set(CMAKE_REQUIRED_INCLUDES ${KDEWIN32_INCLUDES}) + endif(WIN32) + add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS} ${KDE4_DEFINITIONS}) + add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) + include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}) +endif(NOT INSIDE_KDENETWORK) + +option(SAMBA_INSTALL "Offer to install Samba for file sharing with PackageKit if it is not already installed, use -DSAMBA_INSTALL=off to disable, use -DSAMBA_PACKAGE_NAME= to set package name." ON) +if(NOT SAMBA_PACKAGE_NAME) + set(SAMBA_PACKAGE_NAME \\\"samba\\\") +endif(NOT SAMBA_PACKAGE_NAME) +if(SAMBA_INSTALL) + message(STATUS "Samba install feature will be enabled.") + add_definitions(-DSAMBA_INSTALL) + add_definitions(-DSAMBA_PACKAGE_NAME=${SAMBA_PACKAGE_NAME}) +else(SAMBA_INSTALL) + message(STATUS "Samba install feature will NOT be enabled.") +endif(SAMBA_INSTALL) + +add_subdirectory(samba) + +if(NOT INSIDE_KDENETWORK) + macro_display_feature_log() +endif(NOT INSIDE_KDENETWORK) diff --git a/kdenetwork-filesharing/COPYING b/kdenetwork-filesharing/COPYING new file mode 100644 index 00000000..8900e10b --- /dev/null +++ b/kdenetwork-filesharing/COPYING @@ -0,0 +1,347 @@ +NOTE! The GPL below is copyrighted by the Free Software Foundation, but +the instance of code that it refers to (the kde programs) are copyrighted +by the authors who actually wrote it. + +--------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/kdenetwork-filesharing/COPYING.DOC b/kdenetwork-filesharing/COPYING.DOC new file mode 100644 index 00000000..4a0fe1c8 --- /dev/null +++ b/kdenetwork-filesharing/COPYING.DOC @@ -0,0 +1,397 @@ + GNU Free Documentation License + Version 1.2, November 2002 + + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/kdenetwork-filesharing/COPYING.LIB b/kdenetwork-filesharing/COPYING.LIB new file mode 100644 index 00000000..2d2d780e --- /dev/null +++ b/kdenetwork-filesharing/COPYING.LIB @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/kdenetwork-filesharing/Messages.sh b/kdenetwork-filesharing/Messages.sh new file mode 100644 index 00000000..1436d04d --- /dev/null +++ b/kdenetwork-filesharing/Messages.sh @@ -0,0 +1,3 @@ +#! /bin/sh +$EXTRACTRC `find . -name '*.ui'` >> rc.cpp || exit 11 +$XGETTEXT rc.cpp `find . -name '*.cpp' -o -name '*.h'` -o $podir/kfileshare.pot diff --git a/kdenetwork-filesharing/samba/CMakeLists.txt b/kdenetwork-filesharing/samba/CMakeLists.txt new file mode 100644 index 00000000..965ad992 --- /dev/null +++ b/kdenetwork-filesharing/samba/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(filepropertiesplugin) diff --git a/kdenetwork-filesharing/samba/filepropertiesplugin/CMakeLists.txt b/kdenetwork-filesharing/samba/filepropertiesplugin/CMakeLists.txt new file mode 100644 index 00000000..039526c6 --- /dev/null +++ b/kdenetwork-filesharing/samba/filepropertiesplugin/CMakeLists.txt @@ -0,0 +1,16 @@ +########### next target ############### + +set(sambausershareplugin_PART_SRCS sambausershareplugin.cpp delegate.cpp model.cpp) + +kde4_add_ui_files(sambausershareplugin_PART_SRCS sambausershareplugin.ui) + + +kde4_add_plugin(sambausershareplugin ${sambausershareplugin_PART_SRCS}) + +target_link_libraries(sambausershareplugin ${KDE4_KIO_LIBS}) + +install(TARGETS sambausershareplugin DESTINATION ${PLUGIN_INSTALL_DIR}) + +########### install files ############### + +install(FILES sambausershareplugin.desktop DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/kdenetwork-filesharing/samba/filepropertiesplugin/delegate.cpp b/kdenetwork-filesharing/samba/filepropertiesplugin/delegate.cpp new file mode 100644 index 00000000..c8237167 --- /dev/null +++ b/kdenetwork-filesharing/samba/filepropertiesplugin/delegate.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2011 Rodrigo Belem + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "delegate.h" + +UserPermissionDelegate::UserPermissionDelegate(QObject *parent) + : QItemDelegate(parent) +{ +} + +QWidget *UserPermissionDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem & /* option */, + const QModelIndex &index) const +{ + if (index.column() != 1) { + return 0; + } + + QComboBox *comboBox = new QComboBox(parent); + comboBox->addItem(i18n("---")); + comboBox->addItem(i18n("Full Control"), QLatin1String("F")); + comboBox->addItem(i18n("Read Only"), QLatin1String("R")); + comboBox->addItem(i18n("Deny"), QLatin1String("D")); + + connect(comboBox, SIGNAL(activated(int)), this, SLOT(emitCommitData())); + + return comboBox; +} + +void UserPermissionDelegate::setEditorData(QWidget *editor, + const QModelIndex &index) const +{ + QComboBox *comboBox = qobject_cast(editor); + if (!comboBox || (index.column() != 1)) { + return; + } + + int pos = comboBox->findData(index.model()->data(index, Qt::EditRole)); + if (pos == -1) { + pos = 0; + } + + comboBox->setCurrentIndex(pos); +} + +void UserPermissionDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const +{ + QComboBox *comboBox = qobject_cast(editor); + if (!comboBox || (index.column() != 1)) { + return; + } + + model->setData(index, comboBox->itemData(comboBox->currentIndex())); +} + +void UserPermissionDelegate::emitCommitData() +{ + emit commitData(qobject_cast(sender())); +} diff --git a/kdenetwork-filesharing/samba/filepropertiesplugin/delegate.h b/kdenetwork-filesharing/samba/filepropertiesplugin/delegate.h new file mode 100644 index 00000000..bbaddd77 --- /dev/null +++ b/kdenetwork-filesharing/samba/filepropertiesplugin/delegate.h @@ -0,0 +1,44 @@ +/* + * Copyright 2011 Rodrigo Belem + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef delegate_h +#define delegate_h + +#include + + +class UserPermissionDelegate : public QItemDelegate +{ + Q_OBJECT + +public: + UserPermissionDelegate(QObject *parent = 0); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const; + +private slots: + void emitCommitData(); +}; + +#endif diff --git a/kdenetwork-filesharing/samba/filepropertiesplugin/model.cpp b/kdenetwork-filesharing/samba/filepropertiesplugin/model.cpp new file mode 100644 index 00000000..2accd9fd --- /dev/null +++ b/kdenetwork-filesharing/samba/filepropertiesplugin/model.cpp @@ -0,0 +1,178 @@ +/* + * Copyright 2011 Rodrigo Belem + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include + +#include "model.h" + +UserPermissionModel::UserPermissionModel(KSambaShareData &shareData, QObject *parent) + : QAbstractTableModel(parent) + , userList(getUsersList()) + , shareData(shareData) + , usersAcl() +{ + setupData(); +} + +void UserPermissionModel::setupData() +{ + QStringList acl = shareData.acl().split(",", QString::SkipEmptyParts); + + QList::const_iterator itr; + for (itr = acl.constBegin(); itr != acl.constEnd(); ++itr) { + QStringList userInfo = (*itr).trimmed().split(":"); + usersAcl.insert(userInfo.at(0), QVariant(userInfo.at(1))); + } + if (usersAcl.isEmpty()) { + usersAcl.insert("Everyone", QVariant("R")); + } +} + +QStringList UserPermissionModel::getUsersList() const +{ + unsigned int defminuid; + unsigned int defmaxuid; + +#ifdef __linux__ + struct stat st; + if (!stat("/etc/debian_version", &st)) { /* debian */ + defminuid = 1000; + defmaxuid = 29999; + } else if (!stat("/usr/portage", &st)) { /* gentoo */ + defminuid = 1000; + defmaxuid = 65000; + } else if (!stat("/etc/mandrake-release", &st)) { /* mandrake - check before redhat! */ + defminuid = 500; + defmaxuid = 65000; + } else if (!stat("/etc/redhat-release", &st)) { /* redhat */ + defminuid = 100; + defmaxuid = 65000; + } else /* if (!stat("/etc/SuSE-release", &st)) */ { /* suse */ + defminuid = 500; + defmaxuid = 65000; + } +#else + defminuid = 1000; + defmaxuid = 65000; +#endif + + QStringList userList; + userList.append("Everyone"); + foreach (const QString &username, KUser::allUserNames()) { + if (username == "nobody") { + continue; + } + KUser user(username); + if (user.uid() >= defminuid) { + userList << username; + } + } + + return userList; +} + +int UserPermissionModel::rowCount(const QModelIndex &parent) const +{ + return userList.count(); +} + +int UserPermissionModel::columnCount(const QModelIndex &parent) const +{ + return 2; +} + +QVariant UserPermissionModel::data(const QModelIndex &index, int role) const +{ + if ((role == Qt::DisplayRole) && (index.column() == 0)) { + return QVariant(userList.at(index.row())); + } + + if ((role == Qt::DisplayRole || role == Qt::EditRole) && (index.column() == 1)) { + QMap::ConstIterator itr; + for (itr = usersAcl.constBegin(); itr != usersAcl.constEnd(); ++itr) { + if (itr.key().endsWith(userList.at(index.row()))) { + return itr.value(); + } + } + } + + return QVariant(); +} + +Qt::ItemFlags UserPermissionModel::flags(const QModelIndex &index) const +{ + if (index.column() == 0) { + return Qt::ItemIsSelectable; + } + + if (index.column() == 1) { + return (Qt::ItemIsEnabled | Qt::ItemIsEditable); + } + + return Qt::NoItemFlags; +} + +bool UserPermissionModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if ((role != Qt::EditRole) || (index.column() != 1)) { + return false; + } + + QString key(""); + QMap::ConstIterator itr; + for (itr = usersAcl.constBegin(); itr != usersAcl.constEnd(); ++itr) { + if (itr.key().endsWith(userList.at(index.row()))) { + key = itr.key(); + break; + } + } + + if (key.isEmpty()) { + key = userList.at(index.row()); + } + + if (value.isNull()) { + usersAcl.take(key); + } else { + usersAcl.insert(key, value); + } + + emit dataChanged(index, index); + return true; +} + +QString UserPermissionModel::getAcl() const +{ + QString result(""); + + QMap::ConstIterator itr; + for (itr = usersAcl.constBegin(); itr != usersAcl.constEnd(); ++itr) { + if (!itr.value().toString().isEmpty()) { + result.append(itr.key() + ":" + itr.value().toString().toLower()); + if (itr != (usersAcl.constEnd() - 1)) { + result.append(","); + } + } + } + + return result; +} diff --git a/kdenetwork-filesharing/samba/filepropertiesplugin/model.h b/kdenetwork-filesharing/samba/filepropertiesplugin/model.h new file mode 100644 index 00000000..194bad0c --- /dev/null +++ b/kdenetwork-filesharing/samba/filepropertiesplugin/model.h @@ -0,0 +1,56 @@ +/* + * Copyright 2011 Rodrigo Belem + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef model_h +#define model_h + +#include +#include +#include + +class KSambaShareData; + +class UserPermissionModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + UserPermissionModel(KSambaShareData &shareData, QObject *parent = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + Qt::ItemFlags flags(const QModelIndex & index) const; + bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole); + + QString getAcl() const; + +private: + QStringList userList; + KSambaShareData shareData; + QVariantMap usersAcl; + + void setupData(); + QStringList getUsersList() const; +}; + +#endif diff --git a/kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.cpp b/kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.cpp new file mode 100644 index 00000000..ac15abf5 --- /dev/null +++ b/kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.cpp @@ -0,0 +1,238 @@ +/* + Copyright (c) 2004 Jan Schaefer + Copyright (c) 2011 Rodrigo Belem + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sambausershareplugin.h" +#include "model.h" +#include "delegate.h" + +K_PLUGIN_FACTORY(SambaUserSharePluginFactory, registerPlugin();) +K_EXPORT_PLUGIN(SambaUserSharePluginFactory("fileshare_propsdlgplugin")) + +SambaUserSharePlugin::SambaUserSharePlugin(QObject *parent, const QList &args) + : KPropertiesDialogPlugin(qobject_cast(parent)) + , url() + , shareData() +{ + url = properties->kurl().path(KUrl::RemoveTrailingSlash); + if (url.isEmpty()) { + return; + } + + QFileInfo pathInfo(url); + if (!pathInfo.permission(QFile::ReadUser | QFile::WriteUser)) { + return; + } + + KGlobal::locale()->insertCatalog("kfileshare"); + + KVBox *vbox = new KVBox(); + properties->addPage(vbox, i18n("&Share")); + properties->setFileSharingPage(vbox); + + if (!QFile::exists("/sbin/smbd") + && !QFile::exists("/local/sbin/smbd")) { + + QWidget *widget = new QWidget(vbox); + QVBoxLayout *vLayout = new QVBoxLayout(widget); + vLayout->setAlignment(Qt::AlignJustify); + vLayout->setSpacing(KDialog::spacingHint()); + vLayout->setMargin(0); + + vLayout->addWidget(new QLabel(i18n("Samba is not installed on your system."), widget)); + +#ifdef SAMBA_INSTALL + KPushButton *btn = new KPushButton(i18n("Install Samba..."), widget); + btn->setDefault(false); + vLayout->addWidget(btn); + connect(btn, SIGNAL(clicked()), SLOT(installSamba())); +#endif + + // align items on top + vLayout->addStretch(); + + return; + } + + QWidget *widget = new QWidget(vbox); + propertiesUi.setupUi(widget); + + QList shareList = KSambaShare::instance()->getSharesByPath(url); + + if (!shareList.isEmpty()) { + shareData = shareList.at(0); // FIXME: using just the first in the list for a while + } + + setupModel(); + setupViews(); + load(); + + connect(propertiesUi.sambaChk, SIGNAL(toggled(bool)), this, SLOT(toggleShareStatus(bool))); + connect(propertiesUi.sambaChk, SIGNAL(toggled(bool)), this, SIGNAL(changed())); + connect(propertiesUi.sambaNameEdit, SIGNAL(textChanged(QString)), this, SIGNAL(changed())); + connect(propertiesUi.sambaNameEdit, SIGNAL(textChanged(QString)), this, SLOT(checkShareName(QString))); + connect(propertiesUi.sambaAllowGuestChk, SIGNAL(toggled(bool)), this, SIGNAL(changed())); + connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SIGNAL(changed())); + + for (int i = 0; i < model->rowCount(); ++i) { + propertiesUi.tableView->openPersistentEditor(model->index(i, 1, QModelIndex())); + } +} + +SambaUserSharePlugin::~SambaUserSharePlugin() +{ +} + +void SambaUserSharePlugin::installSamba() +{ + unsigned int xid = 0; + QStringList packages; + packages << SAMBA_PACKAGE_NAME; + QString interaction("show-confirm-install,show-progress"); + + QDBusInterface device("org.freedesktop.PackageKit", "/org/freedesktop/PackageKit", + "org.freedesktop.PackageKit.Modify"); + if (!device.isValid()) { + KMessageBox::sorry(qobject_cast(this), + i18n("Samba could not be installed.
Please, check if kpackagekit is properly installed
")); + return; + } + QDBusReply reply = device.call("InstallPackageNames", xid, packages, interaction); +} + +void SambaUserSharePlugin::setupModel() +{ + model = new UserPermissionModel(shareData, this); +} + +void SambaUserSharePlugin::setupViews() +{ + propertiesUi.tableView->setModel(model); + propertiesUi.tableView->setSelectionMode(QAbstractItemView::NoSelection); + propertiesUi.tableView->setItemDelegate(new UserPermissionDelegate(this)); +} + +void SambaUserSharePlugin::load() +{ + bool guestAllowed = false; + bool sambaShared = KSambaShare::instance()->isDirectoryShared(url); + + propertiesUi.sambaChk->setChecked(sambaShared); + toggleShareStatus(sambaShared); + if (sambaShared) { + guestAllowed = (bool) shareData.guestPermission(); + } + propertiesUi.sambaAllowGuestChk->setChecked(guestAllowed); + + propertiesUi.sambaNameEdit->setText(shareData.name()); +} + +void SambaUserSharePlugin::applyChanges() +{ + KSambaShareData::UserShareError result; + + if (propertiesUi.sambaChk->isChecked()) { + if (shareData.setAcl(model->getAcl()) != KSambaShareData::UserShareAclOk) { + return; + } + + shareData.setName(propertiesUi.sambaNameEdit->text()); + + shareData.setPath(url); + + KSambaShareData::GuestPermission guestOk(shareData.guestPermission()); + + guestOk = (propertiesUi.sambaAllowGuestChk->isChecked() == false) + ? KSambaShareData::GuestsNotAllowed : KSambaShareData::GuestsAllowed; + + shareData.setGuestPermission(guestOk); + + result = shareData.save(); + } else if (KSambaShare::instance()->isDirectoryShared(url)) { + result = shareData.remove(); + } +} + +void SambaUserSharePlugin::toggleShareStatus(bool checked) +{ + propertiesUi.sambaNameEdit->setEnabled(checked); + propertiesUi.sambaAllowGuestChk->setCheckable(checked); + propertiesUi.tableView->setEnabled(checked); + if (checked && propertiesUi.sambaNameEdit->text().isEmpty()) { + propertiesUi.sambaNameEdit->setText(getNewShareName()); + } +} + +void SambaUserSharePlugin::checkShareName(const QString &name) +{ + bool disableButton = false; + + if (name.isEmpty()) { + disableButton = true; + } else if (!KSambaShare::instance()->isShareNameAvailable(name)) { + // There is another Share with the same name + KMessageBox::sorry(qobject_cast(this), + i18n("There is already a share with the name %1.
Please choose another name.
", + propertiesUi.sambaNameEdit->text())); + propertiesUi.sambaNameEdit->selectAll(); + disableButton = true; + } + + if (disableButton) { + properties->enableButtonOk(false); + propertiesUi.sambaNameEdit->setFocus(); + return; + } + + if (!properties->isButtonEnabled(KPropertiesDialog::Ok)) { + properties->enableButtonOk(true); + } +} + +QString SambaUserSharePlugin::getNewShareName() +{ + QString shareName = KUrl(url).fileName(); + + if (!propertiesUi.sambaNameEdit->text().isEmpty()) { + shareName = propertiesUi.sambaNameEdit->text(); + } + + // Windows could have problems with longer names + shareName = shareName.left(12); + + return shareName; +} + +#include "moc_sambausershareplugin.cpp" diff --git a/kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.desktop b/kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.desktop new file mode 100644 index 00000000..0d05b5b2 --- /dev/null +++ b/kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.desktop @@ -0,0 +1,139 @@ +[Desktop Entry] +Type=Service +Icon=preferences-system-network-share-windows +Name=Fileshare Konqueror Directory Properties Page +Name[ar]=صفحة دليل خصائص مشاركة ملفات كونكيورر +Name[ast]=Páxina de propiedaes del direutoriu pa compartir ficheros de Konqueror +Name[bg]=Настройки за приставката за браузъра за споделяна на файлове +Name[bn]=ফাইল ভাগাভাগি কনকরার ডিরেক্টরী বৈশিষ্ট্যাবলী পাতা +Name[bs]=Stranica Konqueror sa svojstvima direktorija za dijeljenje datoteka +Name[ca]=Pàgina de propietats del directori de compartició de fitxers del Konqueror +Name[ca@valencia]=Pàgina de propietats del directori de compartició de fitxers del Konqueror +Name[cs]=Stránka vlastností adresáře sdílení Konqueroru +Name[da]=Fileshare Konqueror-mappens side med egenskaber +Name[de]=Ordnerfreigabe-Eigenschaftenseite für Konqueror +Name[el]=Σελίδα ιδιοτήτων κοινόχρηστου καταλόγου του Konqueror +Name[en_GB]=Fileshare Konqueror Directory Properties Page +Name[eo]=Paĝo de ecoj de la komunaj dosierujoj de Konkeranto +Name[es]=Página de propiedades del directorio para compartir archivos de Konqueror +Name[et]=Failijagamise Konquerori kataloogi omaduste lehekülg +Name[eu]=Fitxategiak partekatzeko Konqueror-en direktorioaren propietateen orria +Name[fa]=صفحه ویژگیهای فهرست راهنمای اشتراک پرونده Konqueror +Name[fi]=Konquerorin tiedostojakojen ominaisuussivu +Name[fr]=Page des propriétés d'un dossier de partage de Konqueror +Name[ga]=Leathanach Airíonna Konqueror: Comhroinnt Comhadlainne +Name[gl]=Páxina coas propiedades do directorio de ficheiros compartidos de Konqueror +Name[he]=דף מאפייני ספרית קבצים משותפת של Konqueror +Name[hi]=फ़ाइलशेयर कॉन्करर डिरेक्ट्रीज़ गुण पृष्ठ +Name[hne]=फाइलसेयर कान्करर डिरेक्टरीज गुन पेज +Name[hr]=Stranica sa postavkama svojstava Konqueror Directoryja za dijeljenje datoteka +Name[hu]=Fájlmegosztási lap a Konqueror könyvtártulajdonságainál +Name[ia]=Pagina de proprietates de directorio de Konqueror pro compartir de file +Name[is]=Skráastjóraeiginleikar Konqueror +Name[it]=Pagina delle proprietà di Konqueror per la condivisione +Name[ja]=Konqueror でファイル共有するディレクトリのプロパティのページ +Name[kk]=Konqueror файлдарын ортақтастыру каталогының қасиеттер беті +Name[km]=ទំព័រ​លក្ខណៈ​សម្បត្តិ​ថត Konqueror សម្រាប់​ការ​ចែក​រំលែក +Name[ko]=Konqueror 디렉터리 속성 페이지의 파일 공유 탭 +Name[lt]=Failų dalinimosi Konqueror aplanko nustatymų puslapis +Name[lv]=Konqueror mapes koplietošanas īpašību lapa +Name[mk]=Страница со параметри за делен именик во Konqueror +Name[ml]=ഫയല്‍ഷെയര്‍ കോണ്‍ക്വറര്‍ തട്ടിന്റെ ഗുണവിശേഷങ്ങളുടെ താള്‍ +Name[nb]=Fildeler – Side i Konquerors mappegenskaper +Name[nds]=Konqueror Ornerfreegaav-Egenschappensiet +Name[ne]=फाइल साझेदारी कन्क्वेरर डाइरेक्टरी विशेषता पृष्ठ +Name[nl]=Configuratiepagina Konqueror bestanden delen +Name[nn]=Side for katalogeigenskapar til fildeling i Konqueror +Name[pa]=ਫਾਇਲ ਸਾਂਝ ਕੋਨਕਿਊਰੋਰ ਡਾਇਰੈਕਟਰੀ ਵਿਸ਼ੇਸ਼ਤਾ ਪੇਜ਼ +Name[pl]=Strona współdzielenia we właściwościach katalogu w Konquerorze +Name[pt]=Página de Propriedades da Pasta de Partilha do Konqueror +Name[pt_BR]=Página de propriedades da pasta de compartilhamento de arquivos do Konqueror +Name[ru]=Общий доступ к файлам — свойства каталога в Konqueror +Name[si]=ගොනු හුවමාරු Konqueror නාමාවලි වත්කම් පිටුව +Name[sk]=Zdieľanie adresárov pre Konqueror +Name[sl]=Stran z lastnostmi za souporabo mape +Name[sr]=Страница К‑освајача са својствима фасцикле за дељење фајлова +Name[sr@ijekavian]=Страница К‑освајача са својствима фасцикле за дијељење фајлова +Name[sr@ijekavianlatin]=Stranica K‑osvajača sa svojstvima fascikle za dijeljenje fajlova +Name[sr@latin]=Stranica K‑osvajača sa svojstvima fascikle za deljenje fajlova +Name[sv]=Konquerors fildelningssida med katalogegenskaper +Name[ta]=Fileshare Konqueror அடைவு பண்புகளின் பக்கம் +Name[tg]=Саҳифаи Феҳристи Хусусиятҳо оиди Konqueror Истифодабарии Муштараки Файлҳо +Name[tr]=Dosya Paylaşımı Konqueror Dizin Özellikleri Sayfası +Name[ug]=Fileshare Konqueror مۇندەرىجە خاسلىق بېتى +Name[uk]=Сторінка властивостей каталогу для спільного доступу +Name[vi]=Trang thuộc tính của chia sẻ thư mục Konqueror +Name[x-test]=xxFileshare Konqueror Directory Properties Pagexx +Name[zh_CN]=Konqueror 文件共享目录属性页 +Name[zh_HK]=檔案分享 Konqueror 目錄屬性頁 +Name[zh_TW]=Konqueror 檔案分享目錄屬性頁 +Comment=Konqueror properties dialog plugin to share a directory with the local network +Comment[ar]=خصائص حوار إضافة كونكيورر لمشاركة مسار مع الشبكة المحلية +Comment[ast]=Complementu de diálogu de propiedaes de Konqueror pa compartir un direutoriu cola rede llocal +Comment[bg]=Настройка на приставката за браузъра Konqueror за споделяна на директории в локалната мрежа +Comment[bn]=স্থানীয় নেটওয়ার্কের সঙ্গে একটি ডিরেক্টরী ভাগাভাগি করতে কনকরার বৈশিষ্ট্যাবলী ডায়ালগ প্লাগিন +Comment[bs]=Priključak dijaloga Konqueror-a za svojstva dijeljenja direktorija u lokalnoj mreži +Comment[ca]=Diàleg de propietats del connector del Konqueror per compartir un directori amb la xarxa local +Comment[ca@valencia]=Diàleg de propietats del connector del Konqueror per compartir un directori amb la xarxa local +Comment[cs]=Modul dialogu vlastností Konqueroru pro sdílení adresářů v lokální síti +Comment[da]=Konqueror egenskaber-dialog plugin til at dele en mappe med det lokale netværk +Comment[de]=Modul für den Konqueror-Eigenschaftendialog zur Freigabe eines Ordners im Netzwerk +Comment[el]=Πρόσθετος διάλογος ρυθμίσεων του Konqueror για την κοινή χρήση ενός καταλόγου με το τοπικό δίκτυο +Comment[en_GB]=Konqueror properties dialogue plugin to share a directory with the local network +Comment[eo]=Kromaĵo de Konkeranto por komunigi dosierujon sur la loka reto +Comment[es]=Complemento de diálogo de propiedades de Konqueror para compartir un directorio con la red local +Comment[et]=Konquerori omaduste dialoogi plugin kataloogi jagamiseks kohtvõrgus +Comment[eu]=Konqueror-en propietateen elkarrizketa-koadroaren plugina direktorio bat sare lokalean partekatzeko +Comment[fa]=ویژگیهای وصله محاوره Konqueror برای اشتراک فهرست راهنما با شبکه محلی +Comment[fi]=Konquerorin liitännäinen, jolla voi jakaa kansioita lähiverkossa +Comment[fr]=Module externe proposant une boîte de dialogue de propriétés pour Konqueror pour partager un dossier sur le réseau local +Comment[ga]=Breiseán dialóige airíonna Konqueror lenar féidir comhadlann a chomhroinnt leis an líonra logánta +Comment[gl]=Diálogo de propiedades do engadido de Konqueror para compartir un directorio pola rede local +Comment[he]=תוסף מאפייני דו-שיח של Konqueror כדי לשתף סיפריה עם רשת מקומית +Comment[hi]=स्थानीय नेटवर्क के साथ डिरेक्ट्री साझा करने के लिए कॉन्करर गुण संवाद प्लगइन +Comment[hne]=लोकल नेटवर्क के साथ डिरेक्टरी साझा करे बर कान्करर गुन गोठ प्लगइन +Comment[hr]=Dijalogni priključak postavki za Konqueror za dijeljenje direktorija na lokalnoj mreži +Comment[hu]=Konqueror párbeszédablak könyvtár megosztásához a helyi hálózaton +Comment[ia]=Plug-in de dialogo de proprietate de Konqueror pro compartir un directorio con le rete local +Comment[is]=Konqueror properties dialog plugin to share a directory with the local network +Comment[it]=Estensione delle proprietà di Konqueror per condividere una cartella con la rete locale +Comment[ja]=ローカルネットワークでディレクトリを共有する Konqueror プロパティダイアログのプラグイン +Comment[kk]=Каталогты жергілікті желімен ортақтастыратын Konqueror қасиеттер диалогының плагині +Comment[km]=ប្រអប់​លក្ខណៈ​សម្បត្តិ Konqueror ដើម្បី​ចែក​រំលែក​ថត​នៅ​ក្នុង​បណ្ដាញ​មូលដ្ឋាន +Comment[ko]=로컬 네트워크에서 파일을 공유하기 위한 Konqueror 속성 대화 상자 플러그인 +Comment[lt]=Konqueror nustatymų dialogo įskiepis, skirtas dalintis aplanku vietiniame tinkle +Comment[lv]=Konqueror īpašību dialoga spraudnis mapes publicēšanai koplietošanai lokālajā tīklā +Comment[mk]=Приклучок со дијалог за својства во Konqueror за делење на датотеки со локалната мрежа +Comment[ml]=കോണ്‍ക്വററിലെ പ്രാദേശിക ശൃഖലയുമായി തട്ടു് പങ്കുവെക്കാനുള്ള സംയോജകത്തിന്റെ ഗുണവിശേഷങ്ങളുടെ സംവാദജാലകം +Comment[nb]=Programtillegg for Konquerors egenskapsdialog for å dele en mappe på lokalnettet +Comment[nds]=Moduul för en Konqueror-Egenschappendialoog för't Freegeven vun Ornern in't lokale Nettwark +Comment[ne]=स्थानीय सञ्जालसँग डाइरेक्टरी साझेदार गर्न कन्क्वेरर विशेषता संवाद प्लगइन +Comment[nl]=Konqueror-plugin met instellingen om bestanden te delen via het lokale netwerk +Comment[nn]=Konqueror-vising av eigenskapar til ein delt katalog i det lokale nettverket +Comment[pa]=ਕੋਨਕਿਉਰੋਰ ਵਿਸ਼ੇਸ਼ਤਾ ਡਾਈਲਾਗ ਪਲੱਗਇਨ ਜੋ ਲੋਕਲ ਨੈੱਟਵਰਕ ਨਾਲ ਡਾਇਰੈਕਟਰੀ ਸਾਂਝੀ ਕਰਦੀ ਹੈ +Comment[pl]=Wtyczka właściwości dla Konquerora umożliwiająca współdzielenie katalogu w sieci lokalnej +Comment[pt]='Plugin' de janela de propriedades do Konqueror para partilhar uma pasta na rede local +Comment[pt_BR]=Plugin de diálogo de propriedades do Konqueror para o compartilhamento de uma pasta em uma rede local +Comment[ro]=Modul pentru dialogul de proprietăți Konqueror pentru a partaja un director cu rețeaua locală +Comment[ru]=Модуль свойств Konqueror для организации общего доступа к каталогу по локальной сети +Comment[si]=ප්‍රාදේශීය ජාලයක් තුල බහලුමක් හුවමාරු කිරීම සඳහා Konqueror වත්කම් සංවාද ප්ලගිනය +Comment[sk]=Modul Konquerora pre zdieľanie adresára v lokálnej sieti +Comment[sl]=Vstavek za Konqueror s pogovornim oknom za lastnosti za souporabo mape v krajevnem omrežju +Comment[sr]=Прикључак дијалога К‑освајача за својства дељења фасцикли у локалној мрежи +Comment[sr@ijekavian]=Прикључак дијалога К‑освајача за својства дијељења фасцикли у локалној мрежи +Comment[sr@ijekavianlatin]=Priključak dijaloga K‑osvajača za svojstva dijeljenja fascikli u lokalnoj mreži +Comment[sr@latin]=Priključak dijaloga K‑osvajača za svojstva deljenja fascikli u lokalnoj mreži +Comment[sv]=Konqueror-insticksprogram med egenskapsdialogruta för att dela en katalog i det lokala nätverket +Comment[ta]=Konqueror பண்புகளின் உரையாடல் செருகுகள் அடைவை சம்பா சேவையகத்துடன் பகிரவேண்டிய செருகுகள் +Comment[tg]=Модули муколамаи хусусиятҳои Konqueror барои истифодабарии муштараки феҳрист бо шабакаи маҳалллӣ +Comment[tr]=Yerel ağ ile dizin paylaşımı için Konqueror iletişim eklentisi özellikleri +Comment[ug]=بۇKonqueror خاسلىق سۆزلەشكۈ قىستۇرمىسى بىلەن يەرلىك تور مۇندەرىجىنى ھەمبەھىرلەيدۇ +Comment[uk]=Додаток діалогового вікна властивостей Konqueror для уможливлення спільного доступу до каталогу з локальної мережі +Comment[vi]=Phần bổ sung hộp thoại thuộc tính Konqueror dùng để chia sẻ một thư mục trên mạng nội bộ +Comment[x-test]=xxKonqueror properties dialog plugin to share a directory with the local networkxx +Comment[zh_CN]=可将目录与局域网共享的 Konqueror 属性页对话框插件 +Comment[zh_HK]=用於與本地網絡分享目錄的 Konqueror 屬性對話盒插件 +Comment[zh_TW]=Konqueror 屬性對話框外掛程式,用於在本地端網路上分享目錄 +X-KDE-Library=sambausershareplugin +X-KDE-Protocol=file +X-KDE-ServiceTypes=KPropertiesDialog/Plugin,inode/directory diff --git a/kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.h b/kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.h new file mode 100644 index 00000000..7c0c2604 --- /dev/null +++ b/kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.h @@ -0,0 +1,60 @@ +/* + Copyright (c) 2004 Jan Schaefer + Copyright (c) 2011 Rodrigo Belem + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#ifndef SAMBAUSERSHAREPLUGIN_H +#define SAMBAUSERSHAREPLUGIN_H + +#include +#include + +#include + +#include "ui_sambausershareplugin.h" + +class UserPermissionModel; + +class SambaUserSharePlugin : public KPropertiesDialogPlugin +{ + Q_OBJECT + +public: + SambaUserSharePlugin(QObject *parent, const QList &args); + virtual ~SambaUserSharePlugin(); + virtual void applyChanges(); + +private Q_SLOTS: + void load(); + void toggleShareStatus(bool checked); + void installSamba(); + void checkShareName(const QString &name); + +private: + QString url; + KSambaShareData shareData; + UserPermissionModel *model; + Ui::PropertiesPageGUI propertiesUi; + + void setupModel(); + void setupViews(); + QStringList getUsersList(); + QString getNewShareName(); +}; + +#endif // SAMBAUSERSHAREPLUGIN_H diff --git a/kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.ui b/kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.ui new file mode 100644 index 00000000..92878520 --- /dev/null +++ b/kdenetwork-filesharing/samba/filepropertiesplugin/sambausershareplugin.ui @@ -0,0 +1,114 @@ + + + PropertiesPageGUI + + + + 0 + 0 + 433 + 353 + + + + + + + + + Share with S&amba (Microsoft Windows) + + + true + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Name: + + + false + + + + + + + + + + + + + + true + + + Allow Guests + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + + QAbstractItemView::NoSelection + + + false + + + false + + + false + + + false + + + false + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + +
diff --git a/kfilemetadata/CMakeLists.txt b/kfilemetadata/CMakeLists.txt new file mode 100644 index 00000000..3b53f43a --- /dev/null +++ b/kfilemetadata/CMakeLists.txt @@ -0,0 +1,57 @@ +project(kfilemetadata) + +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") + +find_package(KDE4 REQUIRED) +include(KDE4Defaults) + +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules" ${CMAKE_MODULE_PATH}) + +find_package(PopplerQt4 0.12.1) +set_package_properties(PopplerQt4 PROPERTIES DESCRIPTION "A PDF rendering library" + URL "http://poppler.freedesktop.org" TYPE OPTIONAL + PURPOSE "Support for PDF files") + +find_package(Taglib) +set_package_properties(Taglib PROPERTIES DESCRIPTION "Id3 tag reader" + URL "http://developer.kde.org/~wheeler/taglib.html" TYPE OPTIONAL + PURPOSE "Support for music metadata") + +find_package(Exiv2 0.21) +set_package_properties(Exiv2 PROPERTIES DESCRIPTION "Image Tag reader" + URL "http://www.exiv2.org" TYPE OPTIONAL + PURPOSE "Support for image metadata") + +find_package(FFmpeg 1.0) +set_package_properties(FFmpeg PROPERTIES DESCRIPTION "Video Tag reader" + URL "http://ffmpeg.org" TYPE OPTIONAL + PURPOSE "Support for video metadata") + +find_package(EPub) +set_package_properties(EPub PROPERTIES DESCRIPTION "Ebook epub reader" + URL "http://sourceforge.net/projects/ebook-tools" TYPE OPTIONAL + PURPOSE "Support for epub metadata") + +find_package(QMobipocket) +set_package_properties(QMobipocket PROPERTIES DESCRIPTION "Mobipocket epub reader" + URL "https://projects.kde.org/projects/kde/kdegraphics/kdegraphics-mobipocket" + TYPE OPTIONAL PURPOSE "Support for mobi metadata") + +include_directories( + ${QT_INCLUDES} + ${KDE4_INCLUDES} + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/src +) + +add_subdirectory(src) +add_subdirectory(autotests) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/KFileMetaDataConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/KFileMetaDataConfig.cmake @ONLY) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KFileMetaDataConfig.cmake + DESTINATION ${LIB_INSTALL_DIR}/cmake/KFileMetaData) + +macro_display_feature_log() + diff --git a/kfilemetadata/COPYING-CMAKE-SCRIPTS b/kfilemetadata/COPYING-CMAKE-SCRIPTS new file mode 100644 index 00000000..4b417765 --- /dev/null +++ b/kfilemetadata/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/kfilemetadata/COPYING.LGPL-2 b/kfilemetadata/COPYING.LGPL-2 new file mode 100644 index 00000000..5bc8fb2c --- /dev/null +++ b/kfilemetadata/COPYING.LGPL-2 @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/kfilemetadata/COPYING.LGPL-2.1 b/kfilemetadata/COPYING.LGPL-2.1 new file mode 100644 index 00000000..4362b491 --- /dev/null +++ b/kfilemetadata/COPYING.LGPL-2.1 @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/kfilemetadata/COPYING.LGPL-3 b/kfilemetadata/COPYING.LGPL-3 new file mode 100644 index 00000000..65c5ca88 --- /dev/null +++ b/kfilemetadata/COPYING.LGPL-3 @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/kfilemetadata/KFileMetaDataConfig.cmake.in b/kfilemetadata/KFileMetaDataConfig.cmake.in new file mode 100644 index 00000000..45cf0399 --- /dev/null +++ b/kfilemetadata/KFileMetaDataConfig.cmake.in @@ -0,0 +1,18 @@ +# Do not modify this file. Any change will be overwritten by CMake. + +# Config file for KFileMetaData. This file will define: +# KFILEMETADATA_INCLUDE_DIR - The KFileMetaData include directory +# KFILEMETADATA_LIBRARY - The library needed to use KFileMetaData + +get_filename_component(_currentDir ${CMAKE_CURRENT_LIST_FILE} PATH) # The current directory +get_filename_component(rootDir ${_currentDir}/@relInstallDir@ ABSOLUTE) # The install prefix + +# Include directory +set(KFILEMETADATA_INSTALL_PREFIX "${rootDir}") +set(KFILEMETADATA_INCLUDE_DIR "@INCLUDE_INSTALL_DIR@") + +include(${_currentDir}/KFileMetaDataTargetsWithPrefix.cmake) + +# Set the library variable +set(KFILEMETADATA_LIBRARY kfilemetadata) + diff --git a/kfilemetadata/Messages.sh b/kfilemetadata/Messages.sh new file mode 100644 index 00000000..5300ca82 --- /dev/null +++ b/kfilemetadata/Messages.sh @@ -0,0 +1,5 @@ +#!bin/sh + +$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` >> rc.cpp +$XGETTEXT `find . -name \*.cc -o -name \*.cpp -o -name \*.h -name \*.qml` -o $podir/kfilemetadata.pot +rm -f rc.cpp diff --git a/kfilemetadata/autotests/CMakeLists.txt b/kfilemetadata/autotests/CMakeLists.txt new file mode 100644 index 00000000..3fd71f5a --- /dev/null +++ b/kfilemetadata/autotests/CMakeLists.txt @@ -0,0 +1,28 @@ +configure_file(indexerextractortestsconfig.h.in + ${CMAKE_CURRENT_BINARY_DIR}/indexerextractortestsconfig.h @ONLY) + +kde4_add_unit_test(extractortests NOGUI + indexerextractortests.cpp + simpleresult.cpp + ../src/extractors/plaintextextractor.cpp +) + +target_link_libraries(extractortests + ${QT_QTTEST_LIBRARY} + ${KDE4_KDECORE_LIBS} + kfilemetadata +) + +# +# Property Info +# +kde4_add_unit_test(propertyinfotest NOGUI + propertyinfotest.cpp +) + +target_link_libraries(propertyinfotest + ${QT_QTTEST_LIBRARY} + ${KDE4_KDECORE_LIBS} + kfilemetadata +) + diff --git a/kfilemetadata/autotests/indexerextractortests.cpp b/kfilemetadata/autotests/indexerextractortests.cpp new file mode 100644 index 00000000..bb695b42 --- /dev/null +++ b/kfilemetadata/autotests/indexerextractortests.cpp @@ -0,0 +1,89 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2013 David Edmundson + Copyright (C) 2014 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "indexerextractortests.h" + +#include +#include "qtest_kde.h" +#include "simpleresult.h" +#include "indexerextractortestsconfig.h" +#include "extractors/plaintextextractor.h" + +using namespace KFileMetaData; + +IndexerExtractorTests::IndexerExtractorTests(QObject* parent) : + QObject(parent) +{ +} + +QString IndexerExtractorTests::testFilePath(const QString& fileName) const +{ + return QLatin1String(INDEXER_TESTS_SAMPLE_FILES_PATH) + QDir::separator() + fileName; +} + +void IndexerExtractorTests::benchMarkPlainTextExtractor() +{ + PlainTextExtractor plugin(this, QVariantList()); + + // generate a test file with varying number of words per line + QTemporaryFile file("XXXXXX.txt"); + QVERIFY(file.open()); + QByteArray chunk("foo bar "); + for (int line = 0; line < 10000; ++line) { + for (int i = 0; i < line % 100; ++i) { + file.write(chunk); + } + file.write("\n"); + } + + SimpleResult result(file.fileName(), "text/plain"); + + QBENCHMARK { + plugin.extract(&result); + } +} + +void IndexerExtractorTests::testPlainTextExtractor() +{ + QScopedPointer plugin(new PlainTextExtractor(this, QVariantList())); + + SimpleResult result(testFilePath("plain_text_file.txt"), "text/plain"); + plugin->extract(&result); + + QString content; + QTextStream(&content) << "This is a text file\n" + << "it is four lines long\n" + << "it has 77 characters\n" + << "and 17 words.\n"; + + QCOMPARE(result.types().size(), 1); + QCOMPARE(result.types().first(), Type::Text); + + QCOMPARE(result.properties().size(), 1); + QCOMPARE(result.properties().value(Property::LineCount), QVariant(4)); + + content.replace(QLatin1Char('\n'), QLatin1Char(' ')); + QCOMPARE(result.text(), content); +} + +QTEST_KDEMAIN_CORE(IndexerExtractorTests) + diff --git a/kfilemetadata/autotests/indexerextractortests.h b/kfilemetadata/autotests/indexerextractortests.h new file mode 100644 index 00000000..000995a0 --- /dev/null +++ b/kfilemetadata/autotests/indexerextractortests.h @@ -0,0 +1,42 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2013 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef INDEXEREXTRACTORTESTS_H +#define INDEXEREXTRACTORTESTS_H + +#include +#include + +class IndexerExtractorTests : public QObject +{ + Q_OBJECT +public: + explicit IndexerExtractorTests(QObject* parent = 0); + +private: + QString testFilePath(const QString& fileName) const; + +private slots: + void benchMarkPlainTextExtractor(); + void testPlainTextExtractor(); +}; + +#endif // INDEXERTESTS_H diff --git a/kfilemetadata/autotests/indexerextractortestsconfig.h.in b/kfilemetadata/autotests/indexerextractortestsconfig.h.in new file mode 100644 index 00000000..0ef657de --- /dev/null +++ b/kfilemetadata/autotests/indexerextractortestsconfig.h.in @@ -0,0 +1,27 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2013 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef INDEXEREXTRACTORTESTSCONFIG_H +#define INDEXEREXTRACTORTESTSCONFIG_H + +#define INDEXER_TESTS_SAMPLE_FILES_PATH "@CMAKE_CURRENT_SOURCE_DIR@/samplefiles" + +#endif diff --git a/kfilemetadata/autotests/propertyinfotest.cpp b/kfilemetadata/autotests/propertyinfotest.cpp new file mode 100644 index 00000000..509cd54e --- /dev/null +++ b/kfilemetadata/autotests/propertyinfotest.cpp @@ -0,0 +1,49 @@ +/* + * This file is part of the KDE KFileMetaData project + * Copyright (C) 2014 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "propertyinfotest.h" +#include "propertyinfo.h" + +#include +#include + +using namespace KFileMetaData; + +void PropertyInfoTest::testNameIdMapping() +{ + // The +1 is to avoid the Empty Property + int i = static_cast(Property::FirstProperty) + 1; + int e = static_cast(Property::LastProperty); + + for (; i <= e; i++) { + Property::Property p = static_cast(i); + PropertyInfo pi(p); + + // qDebug() << pi.name(); + QCOMPARE(pi.property(), p); + QVERIFY(!pi.name().isEmpty()); + QVERIFY(!pi.displayName().isEmpty()); + + PropertyInfo pi2 = PropertyInfo::fromName(pi.name()); + QCOMPARE(pi2.property(), p); + } +} + +QTEST_KDEMAIN_CORE(PropertyInfoTest) diff --git a/kfilemetadata/autotests/propertyinfotest.h b/kfilemetadata/autotests/propertyinfotest.h new file mode 100644 index 00000000..677ade1a --- /dev/null +++ b/kfilemetadata/autotests/propertyinfotest.h @@ -0,0 +1,37 @@ +/* + * This file is part of the KDE KFileMetaData project + * Copyright (C) 2014 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef PROPERTYINFOTEST_H +#define PROPERTYINFOTEST_H + +#include + +namespace KFileMetaData { + +class PropertyInfoTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testNameIdMapping(); +}; + +} + +#endif // PROPERTYINFOTEST_H diff --git a/kfilemetadata/autotests/samplefiles/README b/kfilemetadata/autotests/samplefiles/README new file mode 100644 index 00000000..6e50bdeb --- /dev/null +++ b/kfilemetadata/autotests/samplefiles/README @@ -0,0 +1,4 @@ +This folder contains various small files to be indexed by indexerextractortests. + +plain_text_file.txt + - extract metadata with "cat" and "wc" diff --git a/kfilemetadata/autotests/samplefiles/plain_text_file.txt b/kfilemetadata/autotests/samplefiles/plain_text_file.txt new file mode 100644 index 00000000..c84b8631 --- /dev/null +++ b/kfilemetadata/autotests/samplefiles/plain_text_file.txt @@ -0,0 +1,4 @@ +This is a text file +it is four lines long +it has 77 characters +and 17 words. diff --git a/kfilemetadata/autotests/simpleresult.cpp b/kfilemetadata/autotests/simpleresult.cpp new file mode 100644 index 00000000..8283e1ac --- /dev/null +++ b/kfilemetadata/autotests/simpleresult.cpp @@ -0,0 +1,59 @@ +/* + * + * Copyright (C) 2014 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "simpleresult.h" + +using namespace KFileMetaData; + +SimpleResult::SimpleResult(const QString& url, const QString& mimetype) + : ExtractionResult(url, mimetype) +{ +} + +void SimpleResult::add(Property::Property property, const QVariant& value) +{ + m_properties.insertMulti(property, value); +} + +void SimpleResult::addType(Type::Type type) +{ + m_types << type; +} + +void SimpleResult::append(const QString& text) +{ + m_text.append(text); + m_text.append(QLatin1Char(' ')); +} + +PropertyMap SimpleResult::properties() const +{ + return m_properties; +} + +QString SimpleResult::text() const +{ + return m_text; +} + +QVector SimpleResult::types() const +{ + return m_types; +} diff --git a/kfilemetadata/autotests/simpleresult.h b/kfilemetadata/autotests/simpleresult.h new file mode 100644 index 00000000..f3793b5e --- /dev/null +++ b/kfilemetadata/autotests/simpleresult.h @@ -0,0 +1,51 @@ +/* + * + * Copyright (C) 2014 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef KFILEMETADATA_SIMPLERESULT_H +#define KFILEMETADATA_SIMPLERESULT_H + +#include "extractionresult.h" +#include +#include + +namespace KFileMetaData { + +class SimpleResult : public ExtractionResult +{ +public: + SimpleResult(const QString& url, const QString& mimetype); + + virtual void add(Property::Property property, const QVariant& value); + virtual void addType(Type::Type type); + virtual void append(const QString& text); + + PropertyMap properties() const; + QString text() const; + QVector types() const; + +private: + PropertyMap m_properties; + QString m_text; + QVector m_types; +}; + +} + +#endif // KFILEMETADATA_SIMPLERESULT_H diff --git a/kfilemetadata/cmake/modules/FindEPub.cmake b/kfilemetadata/cmake/modules/FindEPub.cmake new file mode 100644 index 00000000..929ad65f --- /dev/null +++ b/kfilemetadata/cmake/modules/FindEPub.cmake @@ -0,0 +1,35 @@ +# - Find EPub +# Find the EPub library. +# +# This module defines +# EPUB_FOUND - whether the EPub library was found +# EPUB_LIBRARIES - the EPub library +# EPUB_INCLUDE_DIR - the include path of the EPub library + +# Copyright (c) 2008, Pino Toscano, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +if (EPUB_INCLUDE_DIR AND EPUB_LIBRARIES) + + # Already in cache + set (EPUB_FOUND TRUE) + +else (EPUB_INCLUDE_DIR AND EPUB_LIBRARIES) + + find_library (EPUB_LIBRARIES + NAMES epub libepub + ) + + find_path (EPUB_INCLUDE_DIR + NAMES epub.h + ) + + include (FindPackageHandleStandardArgs) + find_package_handle_standard_args (EPub DEFAULT_MSG EPUB_LIBRARIES EPUB_INCLUDE_DIR) + +endif (EPUB_INCLUDE_DIR AND EPUB_LIBRARIES) + +mark_as_advanced(EPUB_INCLUDE_DIR EPUB_LIBRARIES) diff --git a/kfilemetadata/cmake/modules/FindPopplerQt4.cmake b/kfilemetadata/cmake/modules/FindPopplerQt4.cmake new file mode 100644 index 00000000..03aa9095 --- /dev/null +++ b/kfilemetadata/cmake/modules/FindPopplerQt4.cmake @@ -0,0 +1,56 @@ +# - Try to find the Qt4 binding of the Poppler library +# Once done this will define +# +# POPPLER_QT4_FOUND - system has poppler-qt4 +# POPPLER_QT4_INCLUDE_DIR - the poppler-qt4 include directory +# POPPLER_QT4_LIBRARIES - Link these to use poppler-qt4 +# POPPLER_QT4_DEFINITIONS - Compiler switches required for using poppler-qt4 +# + +# use pkg-config to get the directories and then use these values +# in the FIND_PATH() and FIND_LIBRARY() calls + +# Copyright (c) 2006, Wilfried Huss, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +find_package(PkgConfig) +pkg_check_modules(PC_POPPLERQT4 QUIET poppler-qt4) + +set(POPPLER_QT4_DEFINITIONS ${PC_POPPLERQT4_CFLAGS_OTHER}) + +find_path(POPPLER_QT4_INCLUDE_DIR + NAMES poppler-qt4.h + HINTS ${PC_POPPLERQT4_INCLUDEDIR} + PATH_SUFFIXES poppler/qt4 poppler +) + +find_library(POPPLER_QT4_LIBRARY + NAMES poppler-qt4 + HINTS ${PC_POPPLERQT4_LIBDIR} +) + +set(POPPLER_QT4_LIBRARIES ${POPPLER_QT4_LIBRARY}) + +if (POPPLER_QT4_INCLUDE_DIR AND POPPLER_QT4_LIBRARIES) + set(POPPLER_QT4_FOUND TRUE) +else (POPPLER_QT4_INCLUDE_DIR AND POPPLER_QT4_LIBRARIES) + set(POPPLER_QT4_FOUND FALSE) +endif (POPPLER_QT4_INCLUDE_DIR AND POPPLER_QT4_LIBRARIES) + +if (POPPLER_QT4_FOUND) + if (NOT PopplerQt4_FIND_QUIETLY) + message(STATUS "Found poppler-qt4: library: ${POPPLER_QT4_LIBRARIES}, include path: ${POPPLER_QT4_INCLUDE_DIR}") + endif (NOT PopplerQt4_FIND_QUIETLY) +else (POPPLER_QT4_FOUND) + if (PopplerQt4_FIND_REQUIRED) + message(FATAL_ERROR "Could NOT find poppler-qt4") + endif (PopplerQt4_FIND_REQUIRED) +endif (POPPLER_QT4_FOUND) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(PopplerQt4 REQUIRED_VARS POPPLER_QT4_INCLUDE_DIR POPPLER_QT4_LIBRARIES) + +mark_as_advanced(POPPLER_QT4_INCLUDE_DIR POPPLER_QT4_LIBRARIES) diff --git a/kfilemetadata/src/CMakeLists.txt b/kfilemetadata/src/CMakeLists.txt new file mode 100644 index 00000000..a66fa1c3 --- /dev/null +++ b/kfilemetadata/src/CMakeLists.txt @@ -0,0 +1,41 @@ +kde4_add_library(kfilemetadata SHARED + extractionresult.cpp + extractorplugin.cpp + extractorpluginmanager.cpp + propertyinfo.cpp + typeinfo.cpp +) + +target_link_libraries(kfilemetadata + ${QT_QTCORE_LIBRARY} + ${KDE4_KDECORE_LIBS} +) + +set_target_properties(kfilemetadata PROPERTIES + VERSION ${GENERIC_LIB_VERSION} + SOVERSION ${GENERIC_LIB_SOVERSION} +) + +install(TARGETS kfilemetadata ${INSTALL_TARGETS_DEFAULT_ARGS}) + +install(TARGETS kfilemetadata EXPORT KdeLibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) +install(FILES kfilemetadataextractor.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) + +install(EXPORT KdeLibsLibraryTargets + DESTINATION ${LIB_INSTALL_DIR}/cmake/KFileMetaData + FILE KFileMetaDataTargetsWithPrefix.cmake) + +install(FILES + extractionresult.h + extractorplugin.h + extractorpluginmanager.h + properties.h + propertyinfo.h + types.h + typeinfo.h + kfilemetadata_export.h + + DESTINATION ${INCLUDE_INSTALL_DIR}/kfilemetadata COMPONENT Devel +) + +add_subdirectory(extractors) diff --git a/kfilemetadata/src/extractionresult.cpp b/kfilemetadata/src/extractionresult.cpp new file mode 100644 index 00000000..9bc7946f --- /dev/null +++ b/kfilemetadata/src/extractionresult.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#include "extractionresult.h" + +using namespace KFileMetaData; + +class ExtractionResult::Private { +public: + QString url; + QString mimetype; +}; + +ExtractionResult::ExtractionResult(const QString& url, const QString& mimetype) + : d(new Private) +{ + d->url = url; + d->mimetype = mimetype; +} + +ExtractionResult::ExtractionResult(const ExtractionResult& rhs) + : d(new Private(*rhs.d)) +{ +} + +ExtractionResult::~ExtractionResult() +{ + delete d; +} + +QString ExtractionResult::inputUrl() const +{ + return d->url; +} + +QString ExtractionResult::inputMimetype() const +{ + return d->mimetype; +} diff --git a/kfilemetadata/src/extractionresult.h b/kfilemetadata/src/extractionresult.h new file mode 100644 index 00000000..76dfe590 --- /dev/null +++ b/kfilemetadata/src/extractionresult.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2013 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#ifndef _KFILEMETADATA_EXTRACTIONRESULT_H +#define _KFILEMETADATA_EXTRACTIONRESULT_H + +#include +#include + +#include "kfilemetadata_export.h" +#include "properties.h" +#include "types.h" + +namespace KFileMetaData { + +/** + * \class ExtractionResult extractionresult.h + * + * \brief The ExtractionResult class is where all the data extracted by + * the indexer is saved. This class acts as a base class which should be + * dervied from and then passed to the relevant plugins. + * + * The dervied class needs to implement 3 pure virtual functions through + * which it receives the extracted data. + * + * \author Vishesh Handa + */ +class KFILEMETADATA_EXPORT ExtractionResult +{ +public: + ExtractionResult(const QString& url, const QString& mimetype); + ExtractionResult(const ExtractionResult& rhs); + virtual ~ExtractionResult(); + + /** + * The input url which the plugins will use to locate the file + */ + QString inputUrl() const; + + /* + * The input mimetype. This mimetype should correspond with the + * mimetypes supported with the relevant plugin when it is being + * passed to the Extractor + */ + QString inputMimetype() const; + + /** + * This function is called by plugins when they wish for some plain + * text to be indexed without any property. This generally corresponds + * to the text content in a file + */ + virtual void append(const QString& text) = 0; + + /** + * This function is called by the plugins when they wish to + * add a key value pair which should be indexed. This function may be + * called multiple times for the same key. + * + * \p property This specifies a property name. It should be one of the + * properties from the global list of properties. + * + * \p value The value of the property + */ + virtual void add(Property::Property property, const QVariant& value) = 0; + + /** + * This function is caleld by the plugins. + * A type is a higher level classification of the file. Any file can + * have multiple types. Eg - "Audio", "Video" or "Document" + * + * Please choose one type from the list of available types + */ + virtual void addType(Type::Type type) = 0; + +private: + class Private; + Private* d; +}; + +} + +#endif // _KFILEMETADATA_EXTRACTIONRESULT_H diff --git a/kfilemetadata/src/extractorplugin.cpp b/kfilemetadata/src/extractorplugin.cpp new file mode 100644 index 00000000..32eac094 --- /dev/null +++ b/kfilemetadata/src/extractorplugin.cpp @@ -0,0 +1,144 @@ +/* + + Copyright (C) 2012 Vishesh Handa + Copyright (C) 2012 Jörg Ehrichs + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "extractorplugin.h" + +#include + +using namespace KFileMetaData; + +ExtractorPlugin::ExtractorPlugin(QObject* parent): QObject(parent) +{ +} + +ExtractorPlugin::~ExtractorPlugin() +{ +} + +// +// Helper functions +// + +QDateTime ExtractorPlugin::dateTimeFromString(const QString& dateString) +{ + QDateTime dateTime; + + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("yyyy-MM-dd")); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("dd-MM-yyyy")); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("yyyy-MM")); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("MM-yyyy")); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("yyyy.MM.dd")); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("dd.MM.yyyy")); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("dd MMMM yyyy")); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("MM.yyyy")); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("yyyy.MM")); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("yyyy")); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("yy")); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, Qt::ISODate); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("dddd d MMM yyyy h':'mm':'ss AP")); + dateTime.setTimeSpec(Qt::LocalTime); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, QLatin1String("yyyy:MM:dd hh:mm:ss")); + dateTime.setTimeSpec(Qt::LocalTime); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, Qt::SystemLocaleDate); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, Qt::SystemLocaleShortDate); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + dateTime = QDateTime::fromString(dateString, Qt::SystemLocaleLongDate); + dateTime.setTimeSpec(Qt::UTC); + } + if (!dateTime.isValid()) { + kWarning() << "Could not determine correct datetime format from:" << dateString; + return QDateTime(); + } + + return dateTime; +} + +QStringList ExtractorPlugin::contactsFromString(const QString& string) +{ + QString cleanedString = string; + cleanedString = cleanedString.remove('{'); + cleanedString = cleanedString.remove('}'); + + QStringList contactStrings = string.split(',', QString::SkipEmptyParts); + if (contactStrings.size() == 1) + contactStrings = string.split(';', QString::SkipEmptyParts); + + if (contactStrings.size() == 1) + contactStrings = string.split(" ft ", QString::SkipEmptyParts); + + if (contactStrings.size() == 1) + contactStrings = string.split(" feat. ", QString::SkipEmptyParts); + + if (contactStrings.size() == 1) + contactStrings = string.split(" feat ", QString::SkipEmptyParts); + + QStringList list; + foreach(const QString& contactName, contactStrings) { + list << contactName.trimmed(); + } + + return list; +} diff --git a/kfilemetadata/src/extractorplugin.h b/kfilemetadata/src/extractorplugin.h new file mode 100644 index 00000000..d1cddd52 --- /dev/null +++ b/kfilemetadata/src/extractorplugin.h @@ -0,0 +1,112 @@ +/* + + Copyright (C) 2012 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef _KFILEMETADATA_EXTRACTOR_H +#define _KFILEMETADATA_EXTRACTOR_H + +#include +#include + +#include "kfilemetadata_export.h" +#include "extractionresult.h" + +#include +#include + +namespace KFileMetaData +{ + +/** + * \class ExtractorPlugin extractorplugin.h + * + * \brief The ExtractorPlugin is the base class for all file metadata + * extractors. It is responsible for extracting the metadata in a file. + * + * Plugins should derive from this class and implement the mimetypes + * and extract method. + * + * All Plugins should be synchronous and blocking. + * + * \author Vishesh Handa + */ +class KFILEMETADATA_EXPORT ExtractorPlugin : public QObject +{ + Q_OBJECT +public: + ExtractorPlugin(QObject* parent); + virtual ~ExtractorPlugin(); + + /** + * Provide a list of mimetypes which are supported by this plugin. + * Only files with those mimetypes will be provided to the plugin via + * the extract function. + * + * This can also contains partial mimetypes like "text/", in that case + * this plugin will be chosen only if a better plugin does not exist. + * + * \return A StringList containing the mimetypes. + * \sa extract + */ + virtual QStringList mimetypes() const = 0; + + /** + * The main function of the plugin that is responsible for extracting + * the data and filling up the ExtractionResult + * + * The \p result provides the input url and mimetype which + * can be used to identify the file. + * + * This function is synchronous and should be reentrant as it + * can be called by multiple threads. + */ + virtual void extract(ExtractionResult* result) = 0; + + // + // Helper functions + // + + /** + * Tries to extract a valid date time from the string provided. + */ + static QDateTime dateTimeFromString(const QString& dateString); + + /** + * Tries to split the string into names. It cleans up any superflous words + * and removes extra junk such as curly braces + */ + static QStringList contactsFromString(const QString& string); + +private: + class Private; + Private* d; +}; +} + +/** + * Export a file extractor. + * + * \param classname The name of the subclass to export + * \param libname The name of the library which should export the extractor + */ +#define KFILEMETADATA_EXPORT_EXTRACTOR( classname, libname ) \ + K_PLUGIN_FACTORY(factory, registerPlugin();) \ + K_EXPORT_PLUGIN(factory(#libname)) + +#endif // _KFILEMETADATA_EXTRACTOR_H diff --git a/kfilemetadata/src/extractorpluginmanager.cpp b/kfilemetadata/src/extractorpluginmanager.cpp new file mode 100644 index 00000000..27859654 --- /dev/null +++ b/kfilemetadata/src/extractorpluginmanager.cpp @@ -0,0 +1,94 @@ +/* + * + * Copyright (C) 2012 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "extractorplugin.h" +#include "extractorpluginmanager.h" + +#include +#include +#include +#include + +using namespace KFileMetaData; + +class ExtractorPluginManager::Private { +public: + QHash m_extractors; + + QList allExtractors() const; +}; + +ExtractorPluginManager::ExtractorPluginManager(QObject* parent) + : QObject(parent) + , d(new Private) +{ + QList all = d->allExtractors(); + + foreach (ExtractorPlugin* ex, all) { + foreach (const QString& type, ex->mimetypes()) { + d->m_extractors.insertMulti(type, ex); + } + } +} + +ExtractorPluginManager::~ExtractorPluginManager() +{ + qDeleteAll(d->m_extractors.values().toSet()); + delete d; +} + + +QList ExtractorPluginManager::Private::allExtractors() const +{ + // Get all the plugins + KService::List plugins = KServiceTypeTrader::self()->query("KFileMetaDataExtractor"); + + QList extractors; + KService::List::const_iterator it; + for (it = plugins.constBegin(); it != plugins.constEnd(); it++) { + KService::Ptr service = *it; + + QString error; + ExtractorPlugin* ex = service->createInstance(0, QVariantList(), &error); + if (!ex) { + kError() << "Could not create Extractor: " << service->library(); + kError() << error; + continue; + } + + extractors << ex; + } + + return extractors; +} + +QList ExtractorPluginManager::fetchExtractors(const QString& mimetype) const +{ + QList plugins = d->m_extractors.values(mimetype); + if (plugins.isEmpty()) { + QHash::const_iterator it = d->m_extractors.constBegin(); + for (; it != d->m_extractors.constEnd(); it++) { + if (mimetype.startsWith(it.key())) + plugins << it.value(); + } + } + + return plugins; +} diff --git a/kfilemetadata/src/extractorpluginmanager.h b/kfilemetadata/src/extractorpluginmanager.h new file mode 100644 index 00000000..d4a871e7 --- /dev/null +++ b/kfilemetadata/src/extractorpluginmanager.h @@ -0,0 +1,65 @@ +/* + * + * Copyright (C) 2012 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _KFILEMETADATA_EXTRACTORPLUGINMANAGER_H +#define _KFILEMETADATA_EXTRACTORPLUGINMANAGER_H + +#include +#include "kfilemetadata_export.h" + +namespace KFileMetaData +{ + +class ExtractorPlugin; + +/** + * \class ExtractorPluginManager extractorpluginmanager.h + * + * \brief The ExtractorPluginManager is a helper class which internally + * loads all the extractor plugins. It can be used to fetch a certain + * subset of thse pulgins based on a given mimetype. + * + * Once the appropriate plugins have been fetched, an ExtractionResult + * should be created and passed to the plugin's extract function. + * + * \author Vishesh Handa + */ +class KFILEMETADATA_EXPORT ExtractorPluginManager : public QObject +{ +public: + explicit ExtractorPluginManager(QObject* parent = 0); + virtual ~ExtractorPluginManager(); + + /** + * Fetch the extractors which can be used to extract + * data for the respective file with the given mimetype. + * + * If no match is found then all the plugins whose mimetype list + * starts with \p mimetype are returned. + */ + QList fetchExtractors(const QString& mimetype) const; + +private: + class Private; + Private* d; +}; +} + +#endif // _KFILEMETADATA_EXTRACTORPLUGINMANAGER_H diff --git a/kfilemetadata/src/extractors/CMakeLists.txt b/kfilemetadata/src/extractors/CMakeLists.txt new file mode 100644 index 00000000..079712ff --- /dev/null +++ b/kfilemetadata/src/extractors/CMakeLists.txt @@ -0,0 +1,206 @@ +if(POPPLER_QT4_FOUND) + include_directories(${POPPLER_QT4_INCLUDE_DIR}) + + kde4_add_plugin(kfilemetadata_popplerextractor popplerextractor.cpp) + + target_link_libraries(kfilemetadata_popplerextractor + kfilemetadata + ${KDE4_KIO_LIBS} + ${POPPLER_QT4_LIBRARIES} + ) + + install( + FILES kfilemetadata_popplerextractor.desktop + DESTINATION ${SERVICES_INSTALL_DIR}) + + install( + TARGETS kfilemetadata_popplerextractor + DESTINATION ${PLUGIN_INSTALL_DIR}) + +endif(POPPLER_QT4_FOUND) + +if(TAGLIB_FOUND) + include_directories( ${TAGLIB_INCLUDES} ) + + kde4_add_plugin( kfilemetadata_taglibextractor taglibextractor.cpp ) + + target_link_libraries( kfilemetadata_taglibextractor + kfilemetadata + ${KDE4_KIO_LIBS} + ${TAGLIB_LIBRARIES} + ) + + install( + FILES kfilemetadata_taglibextractor.desktop + DESTINATION ${SERVICES_INSTALL_DIR}) + + install( + TARGETS kfilemetadata_taglibextractor + DESTINATION ${PLUGIN_INSTALL_DIR}) + +endif(TAGLIB_FOUND) + +if(EXIV2_FOUND) + include_directories( ${EXIV2_INCLUDE_DIR} ) + + set_source_files_properties( exiv2extractor.cpp PROPERTIES + COMPILE_FLAGS "${KDE4_ENABLE_EXCEPTIONS}" + ) + + kde4_add_plugin(kfilemetadata_exiv2extractor exiv2extractor.cpp) + + target_link_libraries(kfilemetadata_exiv2extractor + kfilemetadata + ${KDE4_KIO_LIBS} + ${EXIV2_LIBRARIES} + ) + + install( + FILES kfilemetadata_exiv2extractor.desktop + DESTINATION ${SERVICES_INSTALL_DIR}) + + install( + TARGETS kfilemetadata_exiv2extractor + DESTINATION ${PLUGIN_INSTALL_DIR}) + +endif(EXIV2_FOUND) + +if(FFMPEG_FOUND) + include_directories( ${FFMPEG_INCLUDE_DIRS} ) + + kde4_add_plugin(kfilemetadata_ffmpegextractor ffmpegextractor.cpp) + + target_link_libraries(kfilemetadata_ffmpegextractor + kfilemetadata + ${KDE4_KIO_LIBS} + ${FFMPEG_LIBRARIES} + ) + + install( + FILES kfilemetadata_ffmpegextractor.desktop + DESTINATION ${SERVICES_INSTALL_DIR}) + + install( + TARGETS kfilemetadata_ffmpegextractor + DESTINATION ${PLUGIN_INSTALL_DIR}) + +endif(FFMPEG_FOUND) + + +if(EPUB_FOUND) + include_directories( ${EPUB_INCLUDE_DIR} ) + + kde4_add_plugin(kfilemetadata_epubextractor epubextractor.cpp) + + target_link_libraries(kfilemetadata_epubextractor + kfilemetadata + ${KDE4_KIO_LIBS} + ${EPUB_LIBRARIES} + ) + + install( + FILES kfilemetadata_epubextractor.desktop + DESTINATION ${SERVICES_INSTALL_DIR}) + + install( + TARGETS kfilemetadata_epubextractor + DESTINATION ${PLUGIN_INSTALL_DIR}) + +endif(EPUB_FOUND) + +# +# Plain Text +# +kde4_add_plugin( kfilemetadata_plaintextextractor plaintextextractor.cpp ) + +target_link_libraries( kfilemetadata_plaintextextractor + kfilemetadata + ${KDE4_KIO_LIBS} +) + +install( +FILES kfilemetadata_plaintextextractor.desktop +DESTINATION ${SERVICES_INSTALL_DIR}) + +install( +TARGETS kfilemetadata_plaintextextractor +DESTINATION ${PLUGIN_INSTALL_DIR}) + +# +# ODF +# + +kde4_add_plugin(kfilemetadata_odfextractor odfextractor.cpp) + +target_link_libraries(kfilemetadata_odfextractor + kfilemetadata + ${KDE4_KIO_LIBS} +) + +install( +FILES kfilemetadata_odfextractor.desktop +DESTINATION ${SERVICES_INSTALL_DIR}) + +install( +TARGETS kfilemetadata_odfextractor +DESTINATION ${PLUGIN_INSTALL_DIR}) + +# +# Office 2007 +# + +kde4_add_plugin(kfilemetadata_office2007extractor office2007extractor.cpp) + +target_link_libraries(kfilemetadata_office2007extractor + kfilemetadata + ${KDE4_KIO_LIBS} +) + +install( +FILES kfilemetadata_office2007extractor.desktop +DESTINATION ${SERVICES_INSTALL_DIR}) + +install( +TARGETS kfilemetadata_office2007extractor +DESTINATION ${PLUGIN_INSTALL_DIR}) + +# +# Office (binary formats) +# + +kde4_add_plugin(kfilemetadata_officeextractor officeextractor.cpp) + +target_link_libraries(kfilemetadata_officeextractor + kfilemetadata + ${KDE4_KIO_LIBS} +) + +install( +FILES kfilemetadata_officeextractor.desktop +DESTINATION ${SERVICES_INSTALL_DIR}) + +install( +TARGETS kfilemetadata_officeextractor +DESTINATION ${PLUGIN_INSTALL_DIR}) + +# +# Mobipocket +# +if (QMOBIPOCKET_FOUND) + kde4_add_plugin(kfilemetadata_mobiextractor mobiextractor.cpp) + + include_directories(${QMOBIPOCKET_INCLUDE_DIR}) + target_link_libraries(kfilemetadata_mobiextractor + kfilemetadata + ${KDE4_KIO_LIBS} + ${QMOBIPOCKET_LIBRARIES} + ) + + install( + FILES kfilemetadata_mobiextractor.desktop + DESTINATION ${SERVICES_INSTALL_DIR}) + + install( + TARGETS kfilemetadata_mobiextractor + DESTINATION ${PLUGIN_INSTALL_DIR}) +endif(QMOBIPOCKET_FOUND) diff --git a/kfilemetadata/src/extractors/epubextractor.cpp b/kfilemetadata/src/extractors/epubextractor.cpp new file mode 100644 index 00000000..0f83cfdb --- /dev/null +++ b/kfilemetadata/src/extractors/epubextractor.cpp @@ -0,0 +1,184 @@ +/* + Copyright (C) 2013 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "epubextractor.h" + +#include + +#include +#include +#include + +using namespace KFileMetaData; + +EPubExtractor::EPubExtractor(QObject* parent, const QVariantList&) + : ExtractorPlugin(parent) +{ + +} + +QStringList EPubExtractor::mimetypes() const +{ + QStringList types; + types << QLatin1String("application/epub+zip"); + + return types; +} + +namespace +{ +QString fetchMetadata(struct epub* e, const epub_metadata& type) +{ + int size = 0; + + unsigned char** data = epub_get_metadata(e, type, &size); + if (data) { + QStringList strList; + for (int i = 0; i < size; i++) { + strList << QString::fromUtf8((char*)data[i]); + free(data[i]); + } + free(data); + + return strList.join(";"); + } + return QString(); +} +} + + +void EPubExtractor::extract(ExtractionResult* result) +{ + struct epub* ePubDoc = epub_open(result->inputUrl().toUtf8().constData(), 1); + if (!ePubDoc) { + kError() << "Invalid document"; + return; + } + + result->addType(Type::Document); + + QString value = fetchMetadata(ePubDoc, EPUB_TITLE); + if (!value.isEmpty()) { + result->add(Property::Title, value); + } + + value = fetchMetadata(ePubDoc, EPUB_SUBJECT); + if (!value.isEmpty()) { + result->add(Property::Subject, value); + } + + value = fetchMetadata(ePubDoc, EPUB_CREATOR); + if (!value.isEmpty()) { + if (value.startsWith(QLatin1String("aut:"), Qt::CaseInsensitive)) { + value = value.mid(4).simplified(); + } else if (value.startsWith(QLatin1String("author:"), Qt::CaseInsensitive)) { + value = value.mid(7).simplified(); + } + + // A lot of authors have their name written in () again. We discard that part + int index = value.indexOf('('); + if (index) + value = value.mid(0, index); + + result->add(Property::Creator, value); + } + + // The Contributor just seems to be mostly Calibre aka the Generator + /* + value = fetchMetadata(ePubDoc, EPUB_CONTRIB); + if( !value.isEmpty() ) { + SimpleResource con; + con.addType( NCO::Contact() ); + con.addProperty( NCO::fullname(), value ); + + fileRes.addProperty( NCO::contributor(), con ); + graph << con; + }*/ + + value = fetchMetadata(ePubDoc, EPUB_PUBLISHER); + if (!value.isEmpty()) { + result->add(Property::Publisher, value); + } + + value = fetchMetadata(ePubDoc, EPUB_DESCRIPTION); + if (!value.isEmpty()) { + result->add(Property::Description, value); + } + + value = fetchMetadata(ePubDoc, EPUB_DATE); + if (!value.isEmpty()) { + if (value.startsWith("Unspecified:", Qt::CaseInsensitive)) { + value = value.mid(QString("Unspecified:").size()).simplified(); + } + int ind = value.indexOf("publication:", Qt::CaseInsensitive); + if (ind != -1) { + value = value.mid(ind + QString("publication:").size()).simplified(); + } + QDateTime dt = ExtractorPlugin::dateTimeFromString(value); + if (!dt.isNull()) + result->add(Property::CreationDate, value); + } + + // + // Plain Text + // + struct eiterator* iter = epub_get_iterator(ePubDoc, EITERATOR_SPINE, 0); + do { + char* curr = epub_it_get_curr(iter); + if (!curr) + continue; + QString html = QString::fromUtf8(curr); + + QTextDocument doc; + doc.setHtml(html); + result->append(doc.toPlainText()); + } while (epub_it_get_next(iter)); + + epub_free_iterator(iter); + + struct titerator* tit; + + tit = epub_get_titerator(ePubDoc, TITERATOR_NAVMAP, 0); + if (!tit) { + tit = epub_get_titerator(ePubDoc, TITERATOR_GUIDE, 0); + } + + if (epub_tit_curr_valid(tit)) { + do { + char* clink = epub_tit_get_curr_link(tit); + + char* data; + int size = epub_get_data(ePubDoc, clink, &data); + free(clink); + + // epub_get_data returns -1 on failure + if (size > 0 && data) { + QString html = QString::fromUtf8(data, size); + + QTextDocument doc; + doc.setHtml(html); + result->append(doc.toPlainText()); + free(data); + } + } while (epub_tit_next(tit)); + } + epub_free_titerator(tit); +} + +KFILEMETADATA_EXPORT_EXTRACTOR(KFileMetaData::EPubExtractor, "kfilemetadata_epubextractor") diff --git a/kfilemetadata/src/extractors/epubextractor.h b/kfilemetadata/src/extractors/epubextractor.h new file mode 100644 index 00000000..486a0496 --- /dev/null +++ b/kfilemetadata/src/extractors/epubextractor.h @@ -0,0 +1,39 @@ +/* + + Copyright (C) 2013 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef EPUBEXTRACTOR_H +#define EPUBEXTRACTOR_H + +#include "extractorplugin.h" + +namespace KFileMetaData +{ + +class EPubExtractor : public ExtractorPlugin +{ +public: + EPubExtractor(QObject* parent, const QVariantList&); + + virtual void extract(ExtractionResult* result); + virtual QStringList mimetypes() const; +}; +} + +#endif // EPUBEXTRACTOR_H diff --git a/kfilemetadata/src/extractors/exiv2extractor.cpp b/kfilemetadata/src/extractors/exiv2extractor.cpp new file mode 100644 index 00000000..b0bcff2d --- /dev/null +++ b/kfilemetadata/src/extractors/exiv2extractor.cpp @@ -0,0 +1,209 @@ +/* + + Copyright (C) 2012 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "exiv2extractor.h" + +#include + +using namespace KFileMetaData; + + +Exiv2Extractor::Exiv2Extractor(QObject* parent, const QVariantList&) + : ExtractorPlugin(parent) +{ + +} + +QStringList Exiv2Extractor::mimetypes() const +{ + QStringList types; + + types << QLatin1String("image/jp2") + << QLatin1String("image/jpeg") + << QLatin1String("image/pgf") + << QLatin1String("image/png") + << QLatin1String("image/tiff") + << QLatin1String("image/x-exv") + << QLatin1String("image/x-canon-cr2") + << QLatin1String("image/x-canon-crw") + << QLatin1String("image/x-fuji-raf") + << QLatin1String("image/x-minolta-mrw") + << QLatin1String("image/x-nikon-nef") + << QLatin1String("image/x-olympus-orf") + << QLatin1String("image/x-panasonic-rw2") + << QLatin1String("image/x-pentax-pef") + << QLatin1String("image/x-photoshop") + << QLatin1String("image/x-samsung-srw"); + + return types; +} + +namespace +{ +QString toString(const Exiv2::Value& value) +{ + std::string str = value.toString(); + return QString::fromUtf8(str.c_str(), str.length()); +} + +QVariant toVariantDateTime(const Exiv2::Value& value) +{ + if (value.typeId() == Exiv2::asciiString) { + QDateTime val = ExtractorPlugin::dateTimeFromString(value.toString().c_str()); + if (val.isValid()) { + // Datetime is stored in exif as local time. + val.setUtcOffset(0); + return QVariant(val); + } + } + + return QVariant(); +} + +QVariant toVariantLong(const Exiv2::Value& value) +{ + if (value.typeId() == Exiv2::unsignedLong || value.typeId() == Exiv2::signedLong) { + qlonglong val = value.toLong(); + return QVariant(val); + } + + QString str(toString(value)); + bool ok = false; + int val = str.toInt(&ok); + if (ok) + return QVariant(val); + + return QVariant(); +} + +QVariant toVariantDouble(const Exiv2::Value& value) +{ + if (value.typeId() == Exiv2::tiffFloat || value.typeId() == Exiv2::tiffDouble + || value.typeId() == Exiv2::unsignedRational || value.typeId() == Exiv2::signedRational) { + return QVariant(static_cast(value.toFloat())); + } + + QString str(toString(value)); + bool ok = false; + double val = str.toDouble(&ok); + if (ok) + return QVariant(val); + + return QVariant(); +} + +QVariant toVariantString(const Exiv2::Value& value) +{ + QString str = toString(value); + if (!str.isEmpty()) + return QVariant(str); + + return QVariant(); +} + +QVariant toVariant(const Exiv2::Value& value, QVariant::Type type) { + switch (type) { + case QVariant::Int: + return toVariantLong(value); + + case QVariant::DateTime: + return toVariantDateTime(value); + + case QVariant::Double: + return toVariantDouble(value); + + case QVariant::String: + default: + return toVariantString(value); + } +} +} + +void Exiv2Extractor::extract(ExtractionResult* result) +{ + QByteArray arr = result->inputUrl().toUtf8(); + std::string fileString(arr.data(), arr.length()); + + Exiv2::Image::AutoPtr image; + try { + image = Exiv2::ImageFactory::open(fileString); + } catch (const std::exception&) { + return; + } + if (!image.get()) { + return; + } + + try { + image->readMetadata(); + } catch (const std::exception&) { + return; + } + result->addType(Type::Image); + + if (image->pixelHeight()) { + result->add(Property::Height, image->pixelHeight()); + } + + if (image->pixelWidth()) { + result->add(Property::Width, image->pixelWidth()); + } + + std::string comment = image->comment(); + if (!comment.empty()) { + result->add(Property::Comment, QString::fromUtf8(comment.c_str(), comment.length())); + } + + const Exiv2::ExifData& data = image->exifData(); + + add(result, data, Property::ImageMake, "Exif.Image.Make", QVariant::String); + add(result, data, Property::ImageModel, "Exif.Image.Model", QVariant::String); + add(result, data, Property::ImageDateTime, "Exif.Image.DateTime", QVariant::DateTime); + add(result, data, Property::ImageOrientation, "Exif.Image.Orientation", QVariant::Int); + add(result, data, Property::PhotoFlash, "Exif.Photo.Flash", QVariant::Int); + add(result, data, Property::PhotoPixelXDimension, "Exif.Photo.PixelXDimension", QVariant::Int); + add(result, data, Property::PhotoPixelXDimension, "Exif.Photo.PixelYDimension", QVariant::Int); + add(result, data, Property::PhotoDateTimeOriginal, "Exif.Photo.DateTimeOriginal", QVariant::DateTime); + add(result, data, Property::PhotoFocalLength, "Exif.Photo.FocalLength", QVariant::Double); + add(result, data, Property::PhotoFocalLengthIn35mmFilm, "Exif.Photo.FocalLengthIn35mmFilm", QVariant::Double); + add(result, data, Property::PhotoExposureTime, "Exif.Photo.ExposureTime", QVariant::Double); + add(result, data, Property::PhotoExposureBiasValue, "Exif.Photo.ExposureBiasValue", QVariant::Double); + add(result, data, Property::PhotoFNumber, "Exif.Photo.FNumber", QVariant::Double); + add(result, data, Property::PhotoApertureValue, "Exif.Photo.ApertureValue", QVariant::Double); + add(result, data, Property::PhotoWhiteBalance, "Exif.Photo.WhiteBalance", QVariant::Int); + add(result, data, Property::PhotoMeteringMode, "Exif.Photo.MeteringMode", QVariant::Int); + add(result, data, Property::PhotoISOSpeedRatings, "Exif.Photo.ISOSpeedRatings", QVariant::Int); + add(result, data, Property::PhotoSaturation, "Exif.Photo.Saturation", QVariant::Int); + add(result, data, Property::PhotoSharpness, "Exif.Photo.Sharpness", QVariant::Int); +} + +void Exiv2Extractor::add(ExtractionResult* result, const Exiv2::ExifData& data, + Property::Property prop, const char* name, + QVariant::Type type) +{ + Exiv2::ExifData::const_iterator it = data.findKey(Exiv2::ExifKey(name)); + if (it != data.end()) { + QVariant value = toVariant(it->value(), type); + if (!value.isNull()) + result->add(prop, value); + } +} + +KFILEMETADATA_EXPORT_EXTRACTOR(KFileMetaData::Exiv2Extractor, "kmetaddata_exivextractor") diff --git a/kfilemetadata/src/extractors/exiv2extractor.h b/kfilemetadata/src/extractors/exiv2extractor.h new file mode 100644 index 00000000..9c2480b9 --- /dev/null +++ b/kfilemetadata/src/extractors/exiv2extractor.h @@ -0,0 +1,45 @@ +/* + + Copyright (C) 2012 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef EXIV2EXTRACTOR_H +#define EXIV2EXTRACTOR_H + +#include "extractorplugin.h" +#include + +namespace KFileMetaData +{ + +class Exiv2Extractor : public ExtractorPlugin +{ +public: + Exiv2Extractor(QObject* parent, const QVariantList&); + + virtual void extract(ExtractionResult* result); + virtual QStringList mimetypes() const; + +private: + void add(ExtractionResult* result, const Exiv2::ExifData& data, + Property::Property prop, + const char* name, QVariant::Type type); +}; +} + +#endif // EXIV2EXTRACTOR_H diff --git a/kfilemetadata/src/extractors/ffmpegextractor.cpp b/kfilemetadata/src/extractors/ffmpegextractor.cpp new file mode 100644 index 00000000..c835bcdd --- /dev/null +++ b/kfilemetadata/src/extractors/ffmpegextractor.cpp @@ -0,0 +1,182 @@ +/* + + Copyright (C) 2012 Vishesh Handa + + Code adapted from Strigi FFmpeg Analyzer - + Copyright (C) 2010 Evgeny Egorochkin + Copyright (C) 2011 Tirtha Chatterjee + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "ffmpegextractor.h" + +#ifdef __cplusplus +#define __STDC_CONSTANT_MACROS +#ifdef _STDINT_H +#undef _STDINT_H +#endif +# include +#endif + +extern "C" { +#include +#include +#include +} + +#include +#include + +using namespace KFileMetaData; + +FFmpegExtractor::FFmpegExtractor(QObject* parent, const QVariantList&) +: ExtractorPlugin(parent) +{ +} + +QStringList FFmpegExtractor::mimetypes() const +{ + QStringList types; + + types << QLatin1String("video/x-ms-asf"); + types << QLatin1String("video/x-msvideo"); + types << QLatin1String("video/x-flv"); + types << QLatin1String("video/quicktime"); + types << QLatin1String("video/mpeg"); + types << QLatin1String("video/x-ms-wmv"); + types << QLatin1String("video/mp4"); + types << QLatin1String("video/x-matroska"); + types << QLatin1String("video/webm"); + + return types; +} + +void FFmpegExtractor::extract(ExtractionResult* result) +{ + AVFormatContext* fmt_ctx = NULL; + + av_register_all(); + + QByteArray arr = result->inputUrl().toUtf8(); + + fmt_ctx = avformat_alloc_context(); + if (int ret = avformat_open_input(&fmt_ctx, arr.data(), NULL, NULL)) { + kError() << "avformat_open_input error: " << ret; + return; + } + + int ret = avformat_find_stream_info(fmt_ctx, NULL); + if (ret < 0) { + kError() << "avform_find_stream_info error: " << ret; + return; + } + + result->addType(Type::Video); + + int totalSecs = fmt_ctx->duration / AV_TIME_BASE; + int bitrate = fmt_ctx->bit_rate; + + result->add(Property::Duration, totalSecs); + result->add(Property::BitRate, bitrate); + + for (uint i = 0; i < fmt_ctx->nb_streams; i++) { + const AVStream* stream = fmt_ctx->streams[i]; + const AVCodecContext* codec = stream->codec; + + if (codec->codec_type == AVMEDIA_TYPE_AUDIO || codec->codec_type == AVMEDIA_TYPE_VIDEO) { + /* + if( codec->codec_type == AVMEDIA_TYPE_AUDIO ) { + subRes.addType( NFO::Audio() ); + subRes.addProperty( NFO::sampleRate(), codec->sample_rate ); + subRes.addProperty( NFO::channels(), codec->channels ); + + //TODO: Fetch Sample Format + }*/ + + if (codec->codec_type == AVMEDIA_TYPE_VIDEO) { + int aspectRatio = codec->sample_aspect_ratio.num; + int frameRate = stream->avg_frame_rate.num; + + if (codec->sample_aspect_ratio.den) + aspectRatio /= codec->sample_aspect_ratio.den; + if (stream->avg_frame_rate.den) + frameRate /= stream->avg_frame_rate.den; + + result->add(Property::Width, codec->width); + result->add(Property::Height, codec->height); + if (aspectRatio) + result->add(Property::AspectRatio, aspectRatio); + if (frameRate) + result->add(Property::FrameRate, frameRate); + } + } + } + + AVDictionary* dict = fmt_ctx->metadata; + AVDictionaryEntry* entry; + + entry = av_dict_get(dict, "title", NULL, 0); + if (entry) { + result->add(Property::Title, QString::fromUtf8(entry->value)); + } + + + entry = av_dict_get(dict, "author", NULL, 0); + if (entry) { + result->add(Property::Author, QString::fromUtf8(entry->value)); + } + + entry = av_dict_get(dict, "copyright", NULL, 0); + if (entry) { + result->add(Property::Copyright, QString::fromUtf8(entry->value)); + } + + entry = av_dict_get(dict, "comment", NULL, 0); + if (entry) { + result->add(Property::Comment, QString::fromUtf8(entry->value)); + } + + entry = av_dict_get(dict, "album", NULL, 0); + if (entry) { + result->add(Property::Album, QString::fromUtf8(entry->value)); + } + + entry = av_dict_get(dict, "genre", NULL, 0); + if (entry) { + result->add(Property::Genre, QString::fromUtf8(entry->value)); + } + + entry = av_dict_get(dict, "track", NULL, 0); + if (entry) { + QString value = QString::fromUtf8(entry->value); + + bool ok = false; + int track = value.toInt(&ok); + if (ok && track) + result->add(Property::TrackNumber, track); + } + + entry = av_dict_get(dict, "year", NULL, 0); + if (entry) { + int year = QString::fromUtf8(entry->value).toInt(); + result->add(Property::ReleaseYear, year); + } + + avformat_close_input(&fmt_ctx); +} + +KFILEMETADATA_EXPORT_EXTRACTOR(KFileMetaData::FFmpegExtractor, "kfilemetadata_ffmpegextractor") diff --git a/kfilemetadata/src/extractors/ffmpegextractor.h b/kfilemetadata/src/extractors/ffmpegextractor.h new file mode 100644 index 00000000..5165c801 --- /dev/null +++ b/kfilemetadata/src/extractors/ffmpegextractor.h @@ -0,0 +1,39 @@ +/* + + Copyright (C) 2012 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef FFMPEGEXTRACTOR_H +#define FFMPEGEXTRACTOR_H + +#include "extractorplugin.h" + +namespace KFileMetaData +{ + +class FFmpegExtractor : public ExtractorPlugin +{ +public: + FFmpegExtractor(QObject* parent, const QVariantList&); + + virtual void extract(ExtractionResult* result); + virtual QStringList mimetypes() const; +}; +} + +#endif // FFMPEGEXTRACTOR_H diff --git a/kfilemetadata/src/extractors/kfilemetadata_epubextractor.desktop b/kfilemetadata/src/extractors/kfilemetadata_epubextractor.desktop new file mode 100644 index 00000000..544e8140 --- /dev/null +++ b/kfilemetadata/src/extractors/kfilemetadata_epubextractor.desktop @@ -0,0 +1,42 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KFileMetaDataExtractor +X-KDE-Library=kfilemetadata_epubextractor +Name=KFileMetaData EPub Extractor +Name[bs]=KFileMetaData EPub ekstraktor +Name[ca]=Extractor EPub del KFileMetaData +Name[ca@valencia]=Extractor EPub del KFileMetaData +Name[cs]=Extraktor EPub KFileMetaData +Name[da]=KFileMetaData EPub-udtrækker +Name[de]=EPub-Extraktion für KFileMetaData +Name[el]=Εξαγωγέας EPub KFileMetaData +Name[en_GB]=KFileMetaData EPub Extractor +Name[es]=Extractor de EPub para KFileMetaData +Name[et]=KFileMetaData EPubi ekstraktimine +Name[fi]=KFileMetaDatan EPub-lukuohjelma +Name[fr]=Extracteur EPub de KFileMetaData +Name[gl]=Extractor de EPub de KFileMetaData +Name[hu]=KFileMetaData EPub kibontó +Name[ia]=KFileMetadata Extractor de EPub +Name[it]=Estrattore EPub di KFileMetaData +Name[ko]=KFileMetaData EPub 추출기 +Name[lt]=KFileMetaData EPub ekstraktorius +Name[nb]=KFileMetaData EPub-uttrekker +Name[nds]=KFile-Metadaten-EPub-Leser +Name[nl]=Extractieprogramma voor KFileMetaData EPub +Name[pl]=Wydobywanie z EPub dla KFileMetaData +Name[pt]=Extracção de EPub do KFileMetaData +Name[pt_BR]=Extrator de EPub do KFileMetaData +Name[ru]=Модуль извлечения метаданных из файлов EPub +Name[sk]=Extraktor KFileMetaData EPub +Name[sl]=Program za izvleko EPub za KFileMetaData +Name[sr]=Издвајач ЕПУБ‑а (KFileMetaData) +Name[sr@ijekavian]=Издвајач ЕПУБ‑а (KFileMetaData) +Name[sr@ijekavianlatin]=Izdvajač EPUB‑a (KFileMetaData) +Name[sr@latin]=Izdvajač EPUB‑a (KFileMetaData) +Name[sv]=Kfilmetadata EPub-extrahering +Name[tr]=KFileMetaData EPub Çıkarıcı +Name[uk]=Засіб видобування метаданих EPub +Name[x-test]=xxKFileMetaData EPub Extractorxx +Name[zh_CN]=KFileMetaData EPub 提取工具 +Name[zh_TW]=KFileMetaData EPub 展開器 diff --git a/kfilemetadata/src/extractors/kfilemetadata_exiv2extractor.desktop b/kfilemetadata/src/extractors/kfilemetadata_exiv2extractor.desktop new file mode 100644 index 00000000..0ac14574 --- /dev/null +++ b/kfilemetadata/src/extractors/kfilemetadata_exiv2extractor.desktop @@ -0,0 +1,42 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KFileMetaDataExtractor +X-KDE-Library=kfilemetadata_exiv2extractor +Name=KFileMetaData Exiv2 Extractor +Name[bs]=KFileMetaData Exiv2 ekstraktor +Name[ca]=Extractor Exiv2 del KFileMetaData +Name[ca@valencia]=Extractor Exiv2 del KFileMetaData +Name[cs]=Extraktor Exiv2 KFileMetaData +Name[da]=KFileMetaData Exiv2-udtrækker +Name[de]=Exiv2-Extraktion für KFileMetaData +Name[el]=Εξαγωγέας Exiv2 KFileMetaData +Name[en_GB]=KFileMetaData Exiv2 Extractor +Name[es]=Extractor de Exiv2 para KFileMetaData +Name[et]=KFileMetaData Exiv2 ekstraktimine +Name[fi]=KFileMetaDatan Exiv2-lukuohjelma +Name[fr]=Extracteur Exiv2 de KFileMetaData +Name[gl]=Extractor de Exiv2 de KFileMetaData +Name[hu]=KFileMetaData Exiv2 kibontó +Name[ia]=Extractor de KFileMetaData Exiv2 +Name[it]=Estrattore Exiv2 di KFileMetaData +Name[ko]=KFileMetaData Exiv2 추출기 +Name[lt]=KFileMetaData Exiv2 ekstraktorius +Name[nb]=KFileMetaData Eviv2-uttrekker +Name[nds]=KFile-Metadaten-Exiv2-Leser +Name[nl]=Extractieprogramma voor KFileMetaData Exiv2 +Name[pl]=Wydobywanie z Exiv2 dla KFileMetaData +Name[pt]=Extracção de Exiv2 do KFileMetaData +Name[pt_BR]=Extrator de Exiv2 do KFileMetaData +Name[ru]=Модуль извлечения метаданных изображений с помощью Exiv2 +Name[sk]=Extraktor KFileMetaData Exiv2 +Name[sl]=Program za izvleko Exiv2 za KFileMetaData +Name[sr]=Издвајач Ексивом 2 (KFileMetaData) +Name[sr@ijekavian]=Издвајач Ексивом 2 (KFileMetaData) +Name[sr@ijekavianlatin]=Izdvajač Exivom 2 (KFileMetaData) +Name[sr@latin]=Izdvajač Exivom 2 (KFileMetaData) +Name[sv]=Kfilmetadata Exiv2-extrahering +Name[tr]=KFileMetaData Exiv2 Çıkarıcı +Name[uk]=Засіб видобування метаданих Exiv2 +Name[x-test]=xxKFileMetaData Exiv2 Extractorxx +Name[zh_CN]=KFileMetaData Exiv2 提取工具 +Name[zh_TW]=KFileMetaData Exiv2 展開器 diff --git a/kfilemetadata/src/extractors/kfilemetadata_ffmpegextractor.desktop b/kfilemetadata/src/extractors/kfilemetadata_ffmpegextractor.desktop new file mode 100644 index 00000000..296f0f00 --- /dev/null +++ b/kfilemetadata/src/extractors/kfilemetadata_ffmpegextractor.desktop @@ -0,0 +1,42 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KFileMetaDataExtractor +X-KDE-Library=kfilemetadata_ffmpegextractor +Name=KFileMetaData FFmpeg Extractor +Name[bs]=KFileMetaData FFmpeg ekstraktor +Name[ca]=Extractor FFmpeg del KFileMetaData +Name[ca@valencia]=Extractor FFmpeg del KFileMetaData +Name[cs]=Extraktor FFmpeg KFileMetaData +Name[da]=KFileMetaData FFmpeg-udtrækker +Name[de]=FFmpeg-Extraktion für KFileMetaData +Name[el]=Εξαγωγέας FFmpeg KFileMetaData +Name[en_GB]=KFileMetaData FFmpeg Extractor +Name[es]=Extractor de FFmpeg para KFileMetaData +Name[et]=KFileMetaData FFmpegi ekstraktimine +Name[fi]=KFileMetaDatan FFmpeg-lukuohjelma +Name[fr]=Extracteur FFmpeg de KFileMetaData +Name[gl]=Extractor de FFmpeg de KFileMetaData +Name[hu]=KFileMetaData FFmpeg kibontó +Name[ia]=Extractor de KFileMetaData FFmpeg +Name[it]=Estrattore FFmpeg di KFileMetaData +Name[ko]=KFileMetaData FFmpeg 추출기 +Name[lt]=KFileMetaData FFmpeg ekstraktorius +Name[nb]=KFileMetaData FFmpeg-uttrekker +Name[nds]=KFile-Metadaten-FFmpeg-Leser +Name[nl]=Extractieprogramma voor KFileMetaData FFmpeg +Name[pl]=Wydobywanie z FFmpeg dla KFileMetaData +Name[pt]=Extracção de FFmpeg do KFileMetaData +Name[pt_BR]=Extrator de FFmpeg do KFileMetaData +Name[ru]=Модуль извлечения метаданных видеофайлов с помощью FFmpeg +Name[sk]=Extraktor KFileMetaData FFmpeg +Name[sl]=Program za izvleko FFmpeg za KFileMetaData +Name[sr]=Издвајач ФФмпегом (KFileMetaData) +Name[sr@ijekavian]=Издвајач ФФмпегом (KFileMetaData) +Name[sr@ijekavianlatin]=Izdvajač FFmpegom (KFileMetaData) +Name[sr@latin]=Izdvajač FFmpegom (KFileMetaData) +Name[sv]=Kfilmetadata FFmpeg-extrahering +Name[tr]=KFileMetaData FFmpeg Çıkarıcı +Name[uk]=Засіб видобування метаданих FFmpeg +Name[x-test]=xxKFileMetaData FFmpeg Extractorxx +Name[zh_CN]=KFileMetaData FFmpeg 提取工具 +Name[zh_TW]=KFileMetaData FFmpeg 展開器 diff --git a/kfilemetadata/src/extractors/kfilemetadata_mobiextractor.desktop b/kfilemetadata/src/extractors/kfilemetadata_mobiextractor.desktop new file mode 100644 index 00000000..50fb6e28 --- /dev/null +++ b/kfilemetadata/src/extractors/kfilemetadata_mobiextractor.desktop @@ -0,0 +1,42 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KFileMetaDataExtractor +X-KDE-Library=kfilemetadata_mobiextractor +Name=KFileMetaData Mobi Extractor +Name[bs]=KFileMetaData Mobi ekstraktor +Name[ca]=Extractor Mobi del KFileMetaData +Name[ca@valencia]=Extractor Mobi del KFileMetaData +Name[cs]=Extraktor Mobi KFileMetaData +Name[da]=KFileMetaData Mobi-udtrækker +Name[de]=Mobi-Extraktion für KFileMetaData +Name[el]=Εξαγωγέας Mobi KFileMetaData +Name[en_GB]=KFileMetaData Mobi Extractor +Name[es]=Extractor de Mobi para KFileMetaData +Name[et]=KFileMetaData Mobi ekstraktimine +Name[fi]=KFileMetaDatan Mobi-lukuohjelma +Name[fr]=Extracteur Mobi de KFileMetaData +Name[gl]=Extractor de Mobi de KFileMetaData +Name[hu]=KFileMetaData Mobi kibontó +Name[ia]=Extractor de KFileMetaData Mobi +Name[it]=Estrattore Mobi di KFileMetaData +Name[ko]=KFileMetaData Mobi 추출기 +Name[lt]=KFileMetaData Mobi ekstraktorius +Name[nb]=KFileMetaData Mobi-uttrekker +Name[nds]=KFile-Metadaten-Mobi-Leser +Name[nl]=Extractieprogramma voor KFileMetaData Mobi +Name[pl]=Wydobywanie z Mobi dla KFileMetaData +Name[pt]=Extracção de Mobi do KFileMetaData +Name[pt_BR]=Extrator de Mobi do KFileMetaData +Name[ru]=Модуль извлечения метаданных из файлов Mobi +Name[sk]=Extraktor KFileMetaData Mobi +Name[sl]=Program za izvleko Mobi za KFileMetaData +Name[sr]=Издвајач Мобипокета (KFileMetaData) +Name[sr@ijekavian]=Издвајач Мобипокета (KFileMetaData) +Name[sr@ijekavianlatin]=Izdvajač Mobipocketa (KFileMetaData) +Name[sr@latin]=Izdvajač Mobipocketa (KFileMetaData) +Name[sv]=Kfilmetadata Mobi-extrahering +Name[tr]=KFileMetaData Mobi Çıkarıcı +Name[uk]=Засіб видобування метаданих Mobi +Name[x-test]=xxKFileMetaData Mobi Extractorxx +Name[zh_CN]=KFileMetaData Mobi 提取工具 +Name[zh_TW]=KFileMetaData Mobi 展開器 diff --git a/kfilemetadata/src/extractors/kfilemetadata_odfextractor.desktop b/kfilemetadata/src/extractors/kfilemetadata_odfextractor.desktop new file mode 100644 index 00000000..69220f0b --- /dev/null +++ b/kfilemetadata/src/extractors/kfilemetadata_odfextractor.desktop @@ -0,0 +1,42 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KFileMetaDataExtractor +X-KDE-Library=kfilemetadata_odfextractor +Name=KFileMetaData Odf Extractor +Name[bs]=KFileMetaData Odf ekstraktor +Name[ca]=Extractor Odf del KFileMetaData +Name[ca@valencia]=Extractor Odf del KFileMetaData +Name[cs]=Extraktor Odf KFileMetaData +Name[da]=KFileMetaData Odf-udtrækker +Name[de]=ODF-Extraktion für KFileMetaData +Name[el]=Εξαγωγέας Odf KFileMetaData +Name[en_GB]=KFileMetaData ODF Extractor +Name[es]=Extractor de Odf para KFileMetaData +Name[et]=KFileMetaData Odfi ekstraktimine +Name[fi]=KFileMetaDatan Odf-lukuohjelma +Name[fr]=Extracteur Odf de KFileMetaData +Name[gl]=Extractor de Odf de KFileMetaData +Name[hu]=KFileMetaData Odf kibontó +Name[ia]=Extractor de KFileMetaData Odf +Name[it]=Estrattore Odf di KFileMetaData +Name[ko]=KFileMetaData Odf 추출기 +Name[lt]=KFileMetaData Odf ekstraktorius +Name[nb]=KFileMetaData Odf-uttrekker +Name[nds]=KFile-Metadaten-ODF-Leser +Name[nl]=Extractieprogramma voor KFileMetaData Odf +Name[pl]=Wydobywanie z Odf dla KFileMetaData +Name[pt]=Extracção de ODF do KFileMetaData +Name[pt_BR]=Extrator de ODF do KFileMetaData +Name[ru]=Модуль извлечения метаданных из файлов OpenDocument Format +Name[sk]=Extraktor KFileMetaData Odf +Name[sl]=Program za izvleko Odf za KFileMetaData +Name[sr]=Издвајач ОДФ‑а (KFileMetaData) +Name[sr@ijekavian]=Издвајач ОДФ‑а (KFileMetaData) +Name[sr@ijekavianlatin]=Izdvajač ODF‑a (KFileMetaData) +Name[sr@latin]=Izdvajač ODF‑a (KFileMetaData) +Name[sv]=Kfilmetadata ODF-extrahering +Name[tr]=KFileMetaData Odf Çıkarıcı +Name[uk]=Засіб видобування метаданих ODF +Name[x-test]=xxKFileMetaData Odf Extractorxx +Name[zh_CN]=KFileMetaData Odf 提取工具 +Name[zh_TW]=KFileMetaData Odf 展開器 diff --git a/kfilemetadata/src/extractors/kfilemetadata_office2007extractor.desktop b/kfilemetadata/src/extractors/kfilemetadata_office2007extractor.desktop new file mode 100644 index 00000000..4a41aa96 --- /dev/null +++ b/kfilemetadata/src/extractors/kfilemetadata_office2007extractor.desktop @@ -0,0 +1,42 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KFileMetaDataExtractor +X-KDE-Library=kfilemetadata_office2007extractor +Name=KFileMetaData Office2007 Extractor +Name[bs]=KFileMetaData Office2007 ekstraktor +Name[ca]=Extractor Office2007 del KFileMetaData +Name[ca@valencia]=Extractor Office2007 del KFileMetaData +Name[cs]=Extraktor Office2007 KFileMetaData +Name[da]=KFileMetaData Office2007-udtrækker +Name[de]=Office 2007-Extraktion für KFileMetaData +Name[el]=Εξαγωγέας Office2007 KFileMetaData +Name[en_GB]=KFileMetaData Office2007 Extractor +Name[es]=Extractor de Office2007 para KFileMetaData +Name[et]=KFileMetaData Office2007 ekstraktimine +Name[fi]=KFileMetaDatan Office2007-lukuohjelma +Name[fr]=Extracteur Office2007 de KFileMetaData +Name[gl]=Extractor de Office2007 de KFileMetaData +Name[hu]=KFileMetaData Office2007 kibontó +Name[ia]=Extractor de KFileMetaData Office2007 +Name[it]=Estrattore Office2007 di KFileMetaData +Name[ko]=KFileMetaData Office2007 추출기 +Name[lt]=KFileMetaData Office2007 ekstraktorius +Name[nb]=KFileMetaData Office2007-uttrekker +Name[nds]=KFile-Metadaten-Office2007-Leser +Name[nl]=Extractieprogramma voor KFileMetaData Office2007 +Name[pl]=Wydobywanie z Office2007 dla KFileMetaData +Name[pt]=Extracção de Office2007 do KFileMetaData +Name[pt_BR]=Extrator de Office 2007 do KFileMetaData +Name[ru]=Модуль извлечения метаданных из файлов Office2007 +Name[sk]=Extraktor KFileMetaData Office2007 +Name[sl]=Program za izvleko Office2007 za KFileMetaData +Name[sr]=Издвајач МС Офиса 2007 (KFileMetaData) +Name[sr@ijekavian]=Издвајач МС Офиса 2007 (KFileMetaData) +Name[sr@ijekavianlatin]=Izdvajač MS Officea 2007 (KFileMetaData) +Name[sr@latin]=Izdvajač MS Officea 2007 (KFileMetaData) +Name[sv]=Kfilmetadata Office 2007-extrahering +Name[tr]=KFileMetaData Office2007 Çıkarıcı +Name[uk]=Засіб видобування метаданих Office2007 +Name[x-test]=xxKFileMetaData Office2007 Extractorxx +Name[zh_CN]=KFileMetaData Office2007 提取工具 +Name[zh_TW]=KFileMetaData Office2007 展開器 diff --git a/kfilemetadata/src/extractors/kfilemetadata_officeextractor.desktop b/kfilemetadata/src/extractors/kfilemetadata_officeextractor.desktop new file mode 100644 index 00000000..c38843f3 --- /dev/null +++ b/kfilemetadata/src/extractors/kfilemetadata_officeextractor.desktop @@ -0,0 +1,42 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KFileMetaDataExtractor +X-KDE-Library=kfilemetadata_officeextractor +Name=KFileMetaData Office Extractor +Name[bs]=KFileMetaData Office ekstraktor +Name[ca]=Extractor Office del KFileMetaData +Name[ca@valencia]=Extractor Office del KFileMetaData +Name[cs]=Extraktor Office KFileMetaData +Name[da]=KFileMetaData Office-udtrækker +Name[de]=Office-Extraktion für KFileMetaData +Name[el]=Εξαγωγέας Office KFileMetaData +Name[en_GB]=KFileMetaData Office Extractor +Name[es]=Extractor de Office para KFileMetaData +Name[et]=KFileMetaData Office'i ekstraktimine +Name[fi]=KFileMetaDatan Office-lukuohjelma +Name[fr]=Extracteur Office de KFileMetaData +Name[gl]=Extractor de Office de KFileMetaData +Name[hu]=KFileMetaData Office kibontó +Name[ia]=Extractor de KFileMetaData Office +Name[it]=Estrattore Office di KFileMetaData +Name[ko]=KFileMetaData Office 추출기 +Name[lt]=KFileMetaData Office ekstraktorius +Name[nb]=KFileMetaData Office-uttrekker +Name[nds]=KFile-Metadaten-Office-Leser +Name[nl]=Extractieprogramma voor KFileMetaData Office +Name[pl]=Wydobywanie z Office dla KFileMetaData +Name[pt]=Extracção de Office do KFileMetaData +Name[pt_BR]=Extrator de Office do KFileMetaData +Name[ru]=Модуль извлечения метаданных из файлов Office +Name[sk]=Extraktor KFileMetaData Office +Name[sl]=Program za izvleko Office za KFileMetaData +Name[sr]=Издвајач МС Офиса (KFileMetaData) +Name[sr@ijekavian]=Издвајач МС Офиса (KFileMetaData) +Name[sr@ijekavianlatin]=Izdvajač MS Officea (KFileMetaData) +Name[sr@latin]=Izdvajač MS Officea (KFileMetaData) +Name[sv]=Kfilmetadata Office-extrahering +Name[tr]=KFileMetaData Office Çıkarıcı +Name[uk]=Засіб видобування метаданих Office +Name[x-test]=xxKFileMetaData Office Extractorxx +Name[zh_CN]=KFileMetaData Office 提取工具 +Name[zh_TW]=KFileMetaData Office 展開器 diff --git a/kfilemetadata/src/extractors/kfilemetadata_plaintextextractor.desktop b/kfilemetadata/src/extractors/kfilemetadata_plaintextextractor.desktop new file mode 100644 index 00000000..35e6abd2 --- /dev/null +++ b/kfilemetadata/src/extractors/kfilemetadata_plaintextextractor.desktop @@ -0,0 +1,42 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KFileMetaDataExtractor +X-KDE-Library=kfilemetadata_plaintextextractor +Name=KFileMetaData Plain Text Extractor +Name[bs]=KFileMetaData ekstraktor običnog teksta +Name[ca]=Extractor de text del KFileMetaData +Name[ca@valencia]=Extractor de text del KFileMetaData +Name[cs]=Extraktor čistého textu KFileMetaData +Name[da]=KFileMetaData Klartekst-udtrækker +Name[de]=Extraktion von einfachen Text für KFileMetaData +Name[el]=Εξαγωγέας απλού κειμένου KFileMetaData +Name[en_GB]=KFileMetaData Plain Text Extractor +Name[es]=Extractor de texto sin formato para KFileMetaData +Name[et]=KFileMetaData lihtteksti ekstraktimine +Name[fi]=KFileMetaDatan muotoilemattoman tekstin lukuohjelma +Name[fr]=Extracteur texte brut de KFileMetaData +Name[gl]=Extractor de texto simple de KFileMetaData +Name[hu]=KFileMetaData egyszerű szöveg kibontó +Name[ia]=Extractor de KFileMetaData Texto Plan +Name[it]=Estrattore per testo semplice di KFileMetaData +Name[ko]=KFileMetaData 일반 텍스트 추출기 +Name[lt]=KFileMetaData Paprastojo teksti ekstraktorius +Name[nb]=KFileMetaData uttrekker for ren tekst +Name[nds]=KFile-Metadaten-Eenfachtext-Leser +Name[nl]=Extractieprogramma voor KFileMetaData Plain Text +Name[pl]=Wydobywanie zwykłego tekstu dla KFileMetaData +Name[pt]=Extracção de Texto Simples do KFileMetaData +Name[pt_BR]=Extrator de Texto Simples do KFileMetaData +Name[ru]=Модуль извлечения метаданных из простых текстовых файлов +Name[sk]=Extraktor KFileMetaData Plain Text +Name[sl]=Program za izvleko golega besedila za KFileMetaData +Name[sr]=Издвајач обичног текста (KFileMetaData) +Name[sr@ijekavian]=Издвајач обичног текста (KFileMetaData) +Name[sr@ijekavianlatin]=Izdvajač običnog teksta (KFileMetaData) +Name[sr@latin]=Izdvajač običnog teksta (KFileMetaData) +Name[sv]=Kfilmetadata vanlig textextrahering +Name[tr]=KFileMetaData Düz Metin Çıkarıcı +Name[uk]=Засіб видобування метаданих з неформатованого тексту +Name[x-test]=xxKFileMetaData Plain Text Extractorxx +Name[zh_CN]=KFileMetaData 纯文本提取工具 +Name[zh_TW]=KFileMetaData 純文字檔展開器 diff --git a/kfilemetadata/src/extractors/kfilemetadata_popplerextractor.desktop b/kfilemetadata/src/extractors/kfilemetadata_popplerextractor.desktop new file mode 100644 index 00000000..5cf7e8ad --- /dev/null +++ b/kfilemetadata/src/extractors/kfilemetadata_popplerextractor.desktop @@ -0,0 +1,42 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KFileMetaDataExtractor +X-KDE-Library=kfilemetadata_popplerextractor +Name=KFileMetaData Poppler Extractor +Name[bs]=KFileMetaData Poppler ekstraktor +Name[ca]=Extractor Poppler del KFileMetaData +Name[ca@valencia]=Extractor Poppler del KFileMetaData +Name[cs]=Extraktor Poppler KFileMetaData +Name[da]=KFileMetaData Poppler-udtrækker +Name[de]=Poppler-Extraktion für KFileMetaData +Name[el]=Εξαγωγέας Poppler KFileMetaData +Name[en_GB]=KFileMetaData Poppler Extractor +Name[es]=Extractor de Poppler para KFileMetaData +Name[et]=KFileMetaData Poppleri ekstraktimine +Name[fi]=KFileMetaDatan Poppler-lukuohjelma +Name[fr]=Extracteur Poppler de KFileMetaData +Name[gl]=Extractor de Poppler de KFileMetaData +Name[hu]=KFileMetaData Poppler kibontó +Name[ia]=Extractor de KFileMetaData Poppler +Name[it]=Estrattore Poppler di KFileMetaData +Name[ko]=KFileMetaData Poppler 추출기 +Name[lt]=KFileMetaData Poppler ekstraktorius +Name[nb]=KFileMetaData Poppler-uttrekker +Name[nds]=KFile-Metadaten-Poppler-Leser +Name[nl]=Extractieprogramma voor KFileMetaData Poppler +Name[pl]=Wydobywanie z Poppler dla KFileMetaData +Name[pt]=Extracção de Poppler do KFileMetaData +Name[pt_BR]=Extrator de Poppler do KFileMetaData +Name[ru]=Модуль извлечения метаданных из файлов PDF с помощью Poppler +Name[sk]=Extraktor KFileMetaData Poppler +Name[sl]=Program za izvleko Poppler za KFileMetaData +Name[sr]=Издвајач Поплером (KFileMetaData) +Name[sr@ijekavian]=Издвајач Поплером (KFileMetaData) +Name[sr@ijekavianlatin]=Izdvajač Popplerom (KFileMetaData) +Name[sr@latin]=Izdvajač Popplerom (KFileMetaData) +Name[sv]=Kfilmetadata Poppler-extrahering +Name[tr]=KFileMetaData Poppler Çıkarıcı +Name[uk]=Засіб видобування метаданих Poppler +Name[x-test]=xxKFileMetaData Poppler Extractorxx +Name[zh_CN]=KFileMetaData Poppler 提取工具 +Name[zh_TW]=KFileMetaData Poppler 展開器 diff --git a/kfilemetadata/src/extractors/kfilemetadata_taglibextractor.desktop b/kfilemetadata/src/extractors/kfilemetadata_taglibextractor.desktop new file mode 100644 index 00000000..066c617d --- /dev/null +++ b/kfilemetadata/src/extractors/kfilemetadata_taglibextractor.desktop @@ -0,0 +1,42 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KFileMetaDataExtractor +X-KDE-Library=kfilemetadata_taglibextractor +Name=KFileMetaData TagLib Extractor +Name[bs]=KFileMetaData TagLib ekstraktor +Name[ca]=Extractor TagLib del KFileMetaData +Name[ca@valencia]=Extractor TagLib del KFileMetaData +Name[cs]=Extraktor TagLib KFileMetaData +Name[da]=KFileMetaData TagLib-udtrækker +Name[de]=TagLib-Extraktion für KFileMetaData +Name[el]=Εξαγωγέας TagLib KFileMetaData +Name[en_GB]=KFileMetaData TagLib Extractor +Name[es]=Extractor de TagLib para KFileMetaData +Name[et]=KFileMetaData TagLibi ekstraktimine +Name[fi]=KFileMetaDatan TagLib-lukuohjelma +Name[fr]=Extracteur TagLib de KFileMetaData +Name[gl]=Extractor de TagLib de KFileMetaData +Name[hu]=KFileMetaData TagLib kibontó +Name[ia]=Extractor de KFileMetaData TagLib +Name[it]=Estrattore TagLib di KFileMetaData +Name[ko]=KFileMetaData TagLib 추출기 +Name[lt]=KFileMetaData TagLib ekstraktorius +Name[nb]=KFileMetaData TagLib-uttrekker +Name[nds]=KFile-Metadaten-TagLib-Leser +Name[nl]=Extractieprogramma voor KFileMetaData TagLib +Name[pl]=Wydobywanie z TagLib dla KFileMetaData +Name[pt]=Extracção de TagLib do KFileMetaData +Name[pt_BR]=Extrator de TagLib do KFileMetaData +Name[ru]=Модуль извлечения метаданных из аудиофайлов с помощью TagLib +Name[sk]=Extraktor KFileMetaData TagLib +Name[sl]=Program za izvleko TagLib za KFileMetaData +Name[sr]=Издвајач Таглибом (KFileMetaData) +Name[sr@ijekavian]=Издвајач Таглибом (KFileMetaData) +Name[sr@ijekavianlatin]=Izdvajač TagLibom (KFileMetaData) +Name[sr@latin]=Izdvajač TagLibom (KFileMetaData) +Name[sv]=Kfilmetadata Taglib-extrahering +Name[tr]=KFileMetaData TagLib Çıkarıcı +Name[uk]=Засіб видобування метаданих TagLib +Name[x-test]=xxKFileMetaData TagLib Extractorxx +Name[zh_CN]=KFileMetaData TagLib 提取工具 +Name[zh_TW]=KFileMetaData TagLib 展開器 diff --git a/kfilemetadata/src/extractors/mobiextractor.cpp b/kfilemetadata/src/extractors/mobiextractor.cpp new file mode 100644 index 00000000..38db683c --- /dev/null +++ b/kfilemetadata/src/extractors/mobiextractor.cpp @@ -0,0 +1,112 @@ +/* + Copyright (C) 2013 Vishesh Handa + + Code adapted from kdegraphics-mobipocket/strigi/ + Copyright (C) 2008 by Jakub Stachowski + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "mobiextractor.h" + +#include + +#include +#include +#include +#include + +using namespace KFileMetaData; + +class QFileStream : public Mobipocket::Stream +{ +public: + QFileStream(const QString& name) : d(name) { + d.open(QIODevice::ReadOnly); + } + int read(char* buf, int size) { + return d.read(buf, size); + } + bool seek(int pos) { + return d.seek(pos); + } +private: + QFile d; +}; + +MobiExtractor::MobiExtractor(QObject* parent, const QVariantList&) + : ExtractorPlugin(parent) +{ + +} + +QStringList MobiExtractor::mimetypes() const +{ + QStringList types; + types << QLatin1String("application/x-mobipocket-ebook"); + + return types; +} + +void MobiExtractor::extract(ExtractionResult* result) +{ + QFileStream stream(result->inputUrl()); + Mobipocket::Document doc(&stream); + if (!doc.isValid()) + return; + + QMapIterator it(doc.metadata()); + while (it.hasNext()) { + it.next(); + switch (it.key()) { + case Mobipocket::Document::Title: + result->add(Property::Title, it.value()); + break; + case Mobipocket::Document::Author: { + result->add(Property::Author, it.value()); + break; + } + case Mobipocket::Document::Description: { + QTextDocument document; + document.setHtml(it.value()); + + QString plain = document.toPlainText(); + if (!plain.isEmpty()) + result->add(Property::Description, it.value()); + break; + } + case Mobipocket::Document::Subject: + result->add(Property::Subject, it.value()); + break; + case Mobipocket::Document::Copyright: + result->add(Property::Copyright, it.value()); + break; + } + } + + if (!doc.hasDRM()) { + QString html = doc.text(); + + QTextDocument document; + document.setHtml(html); + + result->append(document.toPlainText()); + } + + result->addType(Type::Document); +} + +KFILEMETADATA_EXPORT_EXTRACTOR(KFileMetaData::MobiExtractor, "kfilemetadata_mobiextractor") diff --git a/kfilemetadata/src/extractors/mobiextractor.h b/kfilemetadata/src/extractors/mobiextractor.h new file mode 100644 index 00000000..90adb9fc --- /dev/null +++ b/kfilemetadata/src/extractors/mobiextractor.h @@ -0,0 +1,39 @@ +/* + + Copyright (C) 2013 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef MOBIEXTRACTOR_H +#define MOBIEXTRACTOR_H + +#include "extractorplugin.h" + +namespace KFileMetaData +{ + +class MobiExtractor : public ExtractorPlugin +{ +public: + MobiExtractor(QObject* parent, const QVariantList&); + + virtual void extract(ExtractionResult* result); + virtual QStringList mimetypes() const; +}; +} + +#endif // MOBIEXTRACTOR_H diff --git a/kfilemetadata/src/extractors/odfextractor.cpp b/kfilemetadata/src/extractors/odfextractor.cpp new file mode 100644 index 00000000..f09b3c3f --- /dev/null +++ b/kfilemetadata/src/extractors/odfextractor.cpp @@ -0,0 +1,138 @@ +/* + + Copyright (C) 2013 Vishesh Handa + Copyright (C) 2012 Jörg Ehrichs + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "odfextractor.h" + +#include +#include + +#include +#include + +using namespace KFileMetaData; + +OdfExtractor::OdfExtractor(QObject* parent, const QVariantList&): ExtractorPlugin(parent) +{ + +} + +QStringList OdfExtractor::mimetypes() const +{ + QStringList list; + list << QLatin1String("application/vnd.oasis.opendocument.text") + << QLatin1String("application/vnd.oasis.opendocument.presentation") + << QLatin1String("application/vnd.oasis.opendocument.spreadsheet"); + + return list; +} + +void OdfExtractor::extract(ExtractionResult* result) +{ + KZip zip(result->inputUrl()); + if (!zip.open(QIODevice::ReadOnly)) { + qWarning() << "Document is not a valid ZIP archive"; + return; + } + + const KArchiveDirectory* directory = zip.directory(); + if (!directory) { + qWarning() << "Invalid document structure (main directory is missing)"; + return; + } + + const QStringList entries = directory->entries(); + if (!entries.contains("meta.xml")) { + qWarning() << "Invalid document structure (meta.xml is missing)"; + return; + } + + QDomDocument metaData("metaData"); + const KArchiveFile* file = static_cast(directory->entry("meta.xml")); + metaData.setContent(file->data()); + + // parse metadata ... + QDomElement docElem = metaData.documentElement(); + + QDomNode n = docElem.firstChild().firstChild(); // ... ... content + while (!n.isNull()) { + QDomElement e = n.toElement(); + if (!e.isNull()) { + const QString tagName = e.tagName(); + + // Dublin Core + if (tagName == QLatin1String("dc:description")) { + result->add(Property::Description, e.text()); + } else if (tagName == QLatin1String("dc:subject")) { + result->add(Property::Subject, e.text()); + } else if (tagName == QLatin1String("dc:title")) { + result->add(Property::Title, e.text()); + } else if (tagName == QLatin1String("dc:creator")) { + result->add(Property::Creator, e.text()); + } else if (tagName == QLatin1String("dc:langauge")) { + result->add(Property::Langauge, e.text()); + } + + // Meta Properties + else if (tagName == QLatin1String("meta:document-statistic")) { + bool ok = false; + int pageCount = e.attribute("meta:page-count").toInt(&ok); + if (ok) { + result->add(Property::PageCount, pageCount); + } + + int wordCount = e.attribute("meta:word-count").toInt(&ok); + if (ok) { + result->add(Property::WordCount, wordCount); + } + } else if (tagName == QLatin1String("meta:keyword")) { + QString keywords = e.text(); + result->add(Property::Keywords, keywords); + } else if (tagName == QLatin1String("meta:generator")) { + result->add(Property::Creator, e.text()); + } else if (tagName == QLatin1String("meta:creation-date")) { + QDateTime dt = ExtractorPlugin::dateTimeFromString(e.text()); + if (!dt.isNull()) + result->add(Property::CreationDate, dt); + } + } + n = n.nextSibling(); + } + + const KArchiveFile* contentsFile = static_cast(directory->entry("content.xml")); + QXmlStreamReader xml(contentsFile->createDevice()); + + while (!xml.atEnd()) { + xml.readNext(); + if (xml.isCharacters()) { + QString str = xml.text().toString(); + result->append(str); + } + + if (xml.hasError() || xml.isEndDocument()) + break; + } + + result->addType(Type::Document); + + return; +} + +KFILEMETADATA_EXPORT_EXTRACTOR(KFileMetaData::OdfExtractor, "kfilemetadata_odfextractor") diff --git a/kfilemetadata/src/extractors/odfextractor.h b/kfilemetadata/src/extractors/odfextractor.h new file mode 100644 index 00000000..d7127807 --- /dev/null +++ b/kfilemetadata/src/extractors/odfextractor.h @@ -0,0 +1,41 @@ +/* + + Copyright (C) 2013 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef ODF_EXTRACTOR_H +#define ODF_EXTRACTOR_H + +#include "extractorplugin.h" + +namespace KFileMetaData +{ + +class OdfExtractor : public ExtractorPlugin +{ +public: + OdfExtractor(QObject* parent, const QVariantList&); + + virtual QStringList mimetypes() const; + virtual void extract(ExtractionResult* result); + +private: +}; +} + +#endif // ODF_EXTRACTOR_H diff --git a/kfilemetadata/src/extractors/office2007extractor.cpp b/kfilemetadata/src/extractors/office2007extractor.cpp new file mode 100644 index 00000000..16b84853 --- /dev/null +++ b/kfilemetadata/src/extractors/office2007extractor.cpp @@ -0,0 +1,268 @@ +/* + + Copyright (C) 2013 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "office2007extractor.h" + +#include +#include + +#include +#include + +using namespace KFileMetaData; + +Office2007Extractor::Office2007Extractor(QObject* parent, const QVariantList&): ExtractorPlugin(parent) +{ + +} + + +QStringList Office2007Extractor::mimetypes() const +{ + QStringList list; + list << QLatin1String("application/vnd.openxmlformats-officedocument.wordprocessingml.document") + << QLatin1String("application/vnd.openxmlformats-officedocument.presentationml.presentation") + << QLatin1String("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + + return list; +} + +void Office2007Extractor::extract(ExtractionResult* result) +{ + KZip zip(result->inputUrl()); + if (!zip.open(QIODevice::ReadOnly)) { + qWarning() << "Document is not a valid ZIP archive"; + return; + } + + const KArchiveDirectory* rootDir = zip.directory(); + if (!rootDir) { + qWarning() << "Invalid document structure (main directory is missing)"; + return; + } + + const QStringList rootEntries = rootDir->entries(); + if (!rootEntries.contains("docProps")) { + qWarning() << "Invalid document structure (docProps is missing)"; + return; + } + + const KArchiveEntry* docPropEntry = rootDir->entry("docProps"); + if (!docPropEntry->isDirectory()) { + qWarning() << "Invalid document structure (docProps is not a directory)"; + return; + } + + const KArchiveDirectory* docPropDirectory = dynamic_cast(docPropEntry); + const QStringList docPropsEntries = docPropDirectory->entries(); + + if (docPropsEntries.contains("core.xml")) { + QDomDocument coreDoc("core"); + const KArchiveFile* file = static_cast(docPropDirectory->entry("core.xml")); + coreDoc.setContent(file->data()); + + QDomElement docElem = coreDoc.documentElement(); + + QDomElement elem = docElem.firstChildElement("dc:description"); + if (!elem.isNull()) { + QString str = elem.text(); + if (!str.isEmpty()) { + result->add(Property::Description, str); + } + } + + elem = docElem.firstChildElement("dc:subject"); + if (!elem.isNull()) { + QString str = elem.text(); + if (!str.isEmpty()) { + result->add(Property::Subject, str); + } + } + + elem = docElem.firstChildElement("dc:title"); + if (!elem.isNull()) { + QString str = elem.text(); + if (!str.isEmpty()) { + result->add(Property::Title, str); + } + } + + elem = docElem.firstChildElement("dc:creator"); + if (!elem.isNull()) { + QString str = elem.text(); + if (!str.isEmpty()) { + result->add(Property::Creator, str); + } + } + + elem = docElem.firstChildElement("dc:langauge"); + if (!elem.isNull()) { + QString str = elem.text(); + if (!str.isEmpty()) { + result->add(Property::Langauge, str); + } + } + } + + if (docPropsEntries.contains("app.xml")) { + QDomDocument appDoc("app"); + const KArchiveFile* file = static_cast(docPropDirectory->entry("app.xml")); + appDoc.setContent(file->data()); + + QDomElement docElem = appDoc.documentElement(); + + // According to the ontologies only Documents can have a wordCount and pageCount + const QString mimeType = result->inputMimetype(); + if (mimeType == QLatin1String("application/vnd.openxmlformats-officedocument.wordprocessingml.document")) { + QDomElement elem = docElem.firstChildElement("Pages"); + if (!elem.isNull()) { + bool ok = false; + int pageCount = elem.text().toInt(&ok); + if (ok) { + result->add(Property::PageCount, pageCount); + } + } + + elem = docElem.firstChildElement("Words"); + if (!elem.isNull()) { + bool ok = false; + int wordCount = elem.text().toInt(&ok); + if (ok) { + result->add(Property::WordCount, wordCount); + } + } + } + + QDomElement elem = docElem.firstChildElement("Application"); + if (!elem.isNull()) { + QString app = elem.text(); + if (!app.isEmpty()) { + result->add(Property::Generator, app); + } + } + } + + + if (rootEntries.contains("word")) { + const KArchiveEntry* wordEntry = rootDir->entry("word"); + if (!wordEntry->isDirectory()) { + qWarning() << "Invalid document structure (word is not a directory)"; + return; + } + + const KArchiveDirectory* wordDirectory = dynamic_cast(wordEntry); + const QStringList wordEntries = wordDirectory->entries(); + + if (wordEntries.contains("document.xml")) { + QDomDocument appDoc("document"); + const KArchiveFile* file = static_cast(wordDirectory->entry("document.xml")); + + extractTextWithTag(file->createDevice(), QLatin1String("w:t"), result); + } + + result->addType(Type::Document); + } + + else if (rootEntries.contains("xl")) { + const KArchiveEntry* xlEntry = rootDir->entry("xl"); + if (!xlEntry->isDirectory()) { + qWarning() << "Invalid document structure (xl is not a directory)"; + return; + } + + const KArchiveDirectory* xlDirectory = dynamic_cast(xlEntry); + extractTextFromFiles(xlDirectory, result); + + result->addType(Type::Document); + result->addType(Type::Spreadsheet); + } + + else if (rootEntries.contains("ppt")) { + const KArchiveEntry* pptEntry = rootDir->entry("ppt"); + if (!pptEntry->isDirectory()) { + qWarning() << "Invalid document structure (ppt is not a directory)"; + return; + } + + const KArchiveDirectory* pptDirectory = dynamic_cast(pptEntry); + extractTextFromFiles(pptDirectory, result); + + result->addType(Type::Document); + result->addType(Type::Presentation); + } + + return; +} + +void Office2007Extractor::extractAllText(QIODevice* device, ExtractionResult* result) +{ + QXmlStreamReader xml(device); + + while (!xml.atEnd()) { + xml.readNext(); + if (xml.isCharacters()) { + QString str = xml.text().toString(); + result->append(str); + } + + if (xml.isEndDocument() || xml.hasError()) + break; + } +} + +void Office2007Extractor::extractTextFromFiles(const KArchiveDirectory* archiveDir, ExtractionResult* result) +{ + const QStringList entries = archiveDir->entries(); + foreach(const QString & entryName, entries) { + const KArchiveEntry* entry = archiveDir->entry(entryName); + if (entry->isDirectory()) { + const KArchiveDirectory* subDir = dynamic_cast(entry); + extractTextFromFiles(subDir, result); + continue; + } + + if (!entryName.endsWith(".xml")) + continue; + + const KArchiveFile* file = static_cast(entry); + extractAllText(file->createDevice(), result); + } +} + +void Office2007Extractor::extractTextWithTag(QIODevice* device, const QString& tag, ExtractionResult* result) +{ + QXmlStreamReader xml(device); + + while (!xml.atEnd()) { + xml.readNext(); + if (xml.qualifiedName().startsWith(tag) && xml.isStartElement()) { + QString str = xml.readElementText(QXmlStreamReader::IncludeChildElements); + + if (!str.isEmpty()) { + result->append(str); + } + } + + if (xml.isEndDocument() || xml.hasError()) + break; + } +} + +KFILEMETADATA_EXPORT_EXTRACTOR(KFileMetaData::Office2007Extractor, "kfilemetadata_office2007extractor") diff --git a/kfilemetadata/src/extractors/office2007extractor.h b/kfilemetadata/src/extractors/office2007extractor.h new file mode 100644 index 00000000..d65bdd94 --- /dev/null +++ b/kfilemetadata/src/extractors/office2007extractor.h @@ -0,0 +1,46 @@ +/* + + Copyright (C) 2013 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef OFFICE_2007_EXTRACTOR_H +#define OFFICE_2007_EXTRACTOR_H + +#include "extractorplugin.h" + +class KArchiveDirectory; + +namespace KFileMetaData +{ + +class Office2007Extractor : public ExtractorPlugin +{ +public: + Office2007Extractor(QObject* parent, const QVariantList&); + + virtual QStringList mimetypes() const; + virtual void extract(ExtractionResult* result); + +private: + void extractTextWithTag(QIODevice* device, const QString& tag, ExtractionResult* result); + void extractAllText(QIODevice* device, ExtractionResult* result); + void extractTextFromFiles(const KArchiveDirectory* archiveDir, ExtractionResult* result); +}; +} + +#endif // OFFICE_2007_EXTRACTOR_H diff --git a/kfilemetadata/src/extractors/officeextractor.cpp b/kfilemetadata/src/extractors/officeextractor.cpp new file mode 100644 index 00000000..21bf9745 --- /dev/null +++ b/kfilemetadata/src/extractors/officeextractor.cpp @@ -0,0 +1,118 @@ +/* + This file is part of a KMetaData File Extractor + Copyright (C) 2013 Denis Steckelmacher + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "officeextractor.h" + +#include + +#include +#include + +using namespace KFileMetaData; + +OfficeExtractor::OfficeExtractor(QObject* parent, const QVariantList&) + : ExtractorPlugin(parent) +{ + // Find the executables of catdoc, catppt and xls2csv. If an executable cannot + // be found, indexing its corresponding MIME type will be disabled + findExe("application/msword", "catdoc", m_catdoc); + findExe("application/vnd.ms-excel", "xls2csv", m_xls2csv); + findExe("application/vnd.ms-powerpoint", "catppt", m_catppt); +} + +void OfficeExtractor::findExe(const QString& mimeType, const QString& name, QString& fullPath) +{ + fullPath = KStandardDirs::findExe(name); + + if (!fullPath.isEmpty()) { + m_available_mime_types << mimeType; + } +} + +QStringList OfficeExtractor::mimetypes() const +{ + return m_available_mime_types; +} + + +void OfficeExtractor::extract(ExtractionResult* result) +{ + QVariantMap metadata; + QStringList args; + QString contents; + + args << QLatin1String("-s") << QLatin1String("cp1252"); // FIXME: Store somewhere a map between the user's language and the encoding of the Windows files it may use ? + args << QLatin1String("-d") << QLatin1String("utf8"); + + const QString fileUrl = result->inputUrl(); + const QString mimeType = result->inputMimetype(); + if (mimeType == QLatin1String("application/msword")) { + result->addType(Type::Document); + + args << QLatin1String("-w"); + contents = textFromFile(fileUrl, m_catdoc, args); + + // Now that we have the plain text content, count words, lines and characters + // (original code from plaintextextractor.cpp, authored by Vishesh Handa) + int lines = contents.count(QLatin1Char('\n')); + int words = contents.count(QRegExp("\\b\\w+\\b")); + + result->add(Property::WordCount, words); + result->add(Property::LineCount, lines); + } else if (mimeType == QLatin1String("application/vnd.ms-excel")) { + result->addType(Type::Document); + result->addType(Type::Spreadsheet); + + args << QLatin1String("-c") << QLatin1String(" "); + args << QLatin1String("-b") << QLatin1String(" "); + args << QLatin1String("-q") << QLatin1String("0"); + contents = textFromFile(fileUrl, m_xls2csv, args); + } else if (mimeType == QLatin1String("application/vnd.ms-powerpoint")) { + result->addType(Type::Document); + result->addType(Type::Presentation); + + contents = textFromFile(fileUrl, m_catppt, args); + } + + if (contents.isEmpty()) + return; + + result->append(contents); + + return; +} + +QString OfficeExtractor::textFromFile(const QString& fileUrl, const QString& command, QStringList& arguments) +{ + arguments << fileUrl; + + // Start a process and read its standard output + QProcess process; + + process.setReadChannel(QProcess::StandardOutput); + process.start(command, arguments, QIODevice::ReadOnly); + process.waitForFinished(); + + if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) + return QString(); + else + return QString::fromUtf8(process.readAll()); +} + +KFILEMETADATA_EXPORT_EXTRACTOR(KFileMetaData::OfficeExtractor, "kfilemetadata_officeextractor") diff --git a/kfilemetadata/src/extractors/officeextractor.h b/kfilemetadata/src/extractors/officeextractor.h new file mode 100644 index 00000000..f8e48b11 --- /dev/null +++ b/kfilemetadata/src/extractors/officeextractor.h @@ -0,0 +1,50 @@ +/* + This file is part of a KMetaData File Extractor + Copyright (C) 2013 Denis Steckelmacher + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef OFFICE_EXTRACTOR_H +#define OFFICE_EXTRACTOR_H + +#include "extractorplugin.h" + +namespace KFileMetaData +{ + +class OfficeExtractor : public ExtractorPlugin +{ +public: + OfficeExtractor(QObject* parent, const QVariantList&); + + virtual QStringList mimetypes() const; + virtual void extract(ExtractionResult* result); + +private: + void findExe(const QString& mimeType, const QString& name, QString& fullPath); + QString textFromFile(const QString& fileUrl, const QString& command, QStringList& arguments); + +private: + QStringList m_available_mime_types; + + QString m_catdoc; + QString m_catppt; + QString m_xls2csv; +}; +} + +#endif diff --git a/kfilemetadata/src/extractors/plaintextextractor.cpp b/kfilemetadata/src/extractors/plaintextextractor.cpp new file mode 100644 index 00000000..0a6beaa3 --- /dev/null +++ b/kfilemetadata/src/extractors/plaintextextractor.cpp @@ -0,0 +1,60 @@ +/* + + Copyright (C) 2012 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "plaintextextractor.h" +#include + +#include + +using namespace KFileMetaData; + +PlainTextExtractor::PlainTextExtractor(QObject* parent, const QVariantList&) + : ExtractorPlugin(parent) +{ + +} + +QStringList PlainTextExtractor::mimetypes() const +{ + return QStringList() << QLatin1String("text/"); +} + +void PlainTextExtractor::extract(ExtractionResult* result) +{ + std::string line; + int lines = 0; + + std::ifstream fstream(QFile::encodeName(result->inputUrl())); + if (!fstream.is_open()) { + return; + } + + while (std::getline(fstream, line)) { + QByteArray arr = QByteArray::fromRawData(line.c_str(), line.size()); + result->append(QString::fromUtf8(arr)); + + lines += 1; + } + + result->add(Property::LineCount, lines); + result->addType(Type::Text); +} + +KFILEMETADATA_EXPORT_EXTRACTOR(KFileMetaData::PlainTextExtractor, "kfilemetadata_plaintextextractor") diff --git a/kfilemetadata/src/extractors/plaintextextractor.h b/kfilemetadata/src/extractors/plaintextextractor.h new file mode 100644 index 00000000..71248a55 --- /dev/null +++ b/kfilemetadata/src/extractors/plaintextextractor.h @@ -0,0 +1,40 @@ +/* + + Copyright (C) 2012 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef PLAINTEXTEXTRACTOR_H +#define PLAINTEXTEXTRACTOR_H + +#include "extractorplugin.h" + +namespace KFileMetaData +{ + +class PlainTextExtractor : public ExtractorPlugin +{ +public: + PlainTextExtractor(QObject* parent, const QVariantList&); + + virtual QStringList mimetypes() const; + virtual void extract(ExtractionResult* result); +}; + +} + +#endif // PLAINTEXTEXTRACTOR_H diff --git a/kfilemetadata/src/extractors/popplerextractor.cpp b/kfilemetadata/src/extractors/popplerextractor.cpp new file mode 100644 index 00000000..4612317a --- /dev/null +++ b/kfilemetadata/src/extractors/popplerextractor.cpp @@ -0,0 +1,174 @@ +/* + + Copyright (C) 2012 Vishesh Handa + Copyright (C) 2012 Jörg Ehrichs + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "popplerextractor.h" + +#include +#include + +using namespace KFileMetaData; + +PopplerExtractor::PopplerExtractor(QObject* parent, const QVariantList&) + : ExtractorPlugin(parent) +{ + +} + +QStringList PopplerExtractor::mimetypes() const +{ + QStringList list; + list << QLatin1String("application/pdf"); + + return list; +} + + +void PopplerExtractor::extract(ExtractionResult* result) +{ + const QString fileUrl = result->inputUrl(); + QScopedPointer pdfDoc(Poppler::Document::load(fileUrl, 0, 0)); + + if (!pdfDoc || pdfDoc->isLocked()) { + return; + } + + result->addType(Type::Document); + + QString title = pdfDoc->info(QLatin1String("Title")).trimmed(); + + // The title extracted from the pdf metadata is in many cases not the real title + // of the document. Especially for research papers that are exported to pdf. + // As mostly the title of a pdf document is written on the first page in the biggest font + // we use this if the pdfDoc title is considered junk + if (title.isEmpty() || + !title.contains(' ') || // very unlikely the title of a document does only contain one word. + title.contains(QLatin1String("Microsoft"), Qt::CaseInsensitive)) { // most research papers i found written with microsoft word + // have a garbage title of the pdf creator rather than the real document title + title = parseFirstPage(pdfDoc.data(), fileUrl); + } + + if (!title.isEmpty()) { + result->add(Property::Title, title); + } + + QString subject = pdfDoc->info(QLatin1String("Subject")); + if (!subject.isEmpty()) { + result->add(Property::Subject, title); + } + + QString author = pdfDoc->info(QLatin1String("Author")); + if (!author.isEmpty()) { + result->add(Property::Author, author); + } + + QString creator = pdfDoc->info(QLatin1String("Creator")); + if (!author.isEmpty()) { + result->add(Property::Creator, creator); + } + + for (int i = 0; i < pdfDoc->numPages(); i++) { + QScopedPointer page(pdfDoc->page(i)); + if (!page) { // broken pdf files do not return a valid page + kWarning() << "Could not read page content from" << fileUrl; + break; + } + result->append(page->text(QRectF())); + } +} + +QString PopplerExtractor::parseFirstPage(Poppler::Document* pdfDoc, const QString& fileUrl) +{ + QScopedPointer p(pdfDoc->page(0)); + + if (!p) { + kWarning() << "Could not read page content from" << fileUrl; + return QString(); + } + + QList tbList = p->textList(); + QMap possibleTitleMap; + + int currentLargestChar = 0; + int skipTextboxes = 0; + + // Iterate over all textboxes. Each textbox can be a single character/word or textblock + // Here we combine the etxtboxes back together based on the textsize + // Important are the words with the biggest font size + foreach(Poppler::TextBox * tb, tbList) { + + // if we added followup words, skip the textboxes here now + if (skipTextboxes > 0) { + skipTextboxes--; + continue; + } + + int height = tb->charBoundingBox(0).height(); + + // if the following text is smaller than the biggest we found up to now, ignore it + if (height >= currentLargestChar) { + QString possibleTitle; + possibleTitle.append(tb->text()); + currentLargestChar = height; + + // if the text has follow up words add them to to create the full title + Poppler::TextBox* next = tb->nextWord(); + while (next) { + possibleTitle.append(QLatin1Char(' ')); + possibleTitle.append(next->text()); + next = next->nextWord(); + skipTextboxes++; + } + + // now combine text for each font size together, very likeley it must be connected + QString existingTitlePart = possibleTitleMap.value(currentLargestChar, QString()); + existingTitlePart.append(QLatin1Char(' ')); + existingTitlePart.append(possibleTitle); + possibleTitleMap.insert(currentLargestChar, existingTitlePart); + } + } + + qDeleteAll(tbList); + + QList titleSizes = possibleTitleMap.keys(); + qSort(titleSizes.begin(), titleSizes.end(), qGreater()); + + QString newPossibleTitle; + + // find the text with the largest font that is not just 1 character + foreach(int i, titleSizes) { + QString title = possibleTitleMap.value(i); + + // sometime the biggest part is a single letter + // as a starting paragraph letter + if (title.size() < 5) { + continue; + } else { + newPossibleTitle = title.trimmed(); + break; + } + } + + // Sometimes the titles that are extracted are too large. This is a way of trimming them. + newPossibleTitle.truncate(50); + return newPossibleTitle; +} + +KFILEMETADATA_EXPORT_EXTRACTOR(KFileMetaData::PopplerExtractor, "kfilemetadata_popplerextractor") diff --git a/kfilemetadata/src/extractors/popplerextractor.h b/kfilemetadata/src/extractors/popplerextractor.h new file mode 100644 index 00000000..7e7554e8 --- /dev/null +++ b/kfilemetadata/src/extractors/popplerextractor.h @@ -0,0 +1,43 @@ +/* + + Copyright (C) 2012 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef POPPLEREXTRACTOR_H +#define POPPLEREXTRACTOR_H + +#include "extractorplugin.h" +#include + +namespace KFileMetaData +{ + +class PopplerExtractor : public ExtractorPlugin +{ +public: + PopplerExtractor(QObject* parent, const QVariantList&); + + virtual QStringList mimetypes() const; + virtual void extract(ExtractionResult* result); + +private: + QString parseFirstPage(Poppler::Document* pdfDoc, const QString& fileUrl); +}; +} + +#endif // POPPLEREXTRACTOR_H diff --git a/kfilemetadata/src/extractors/taglibextractor.cpp b/kfilemetadata/src/extractors/taglibextractor.cpp new file mode 100644 index 00000000..fb8d5942 --- /dev/null +++ b/kfilemetadata/src/extractors/taglibextractor.cpp @@ -0,0 +1,432 @@ +/* + + Copyright (C) 2012 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "taglibextractor.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KFileMetaData; + +TagLibExtractor::TagLibExtractor(QObject* parent, const QVariantList&) + : ExtractorPlugin(parent) +{ +} + +QStringList TagLibExtractor::mimetypes() const +{ + QStringList types; + // MP3 FLAC, MPC, Speex, WavPack TrueAudio, WAV, AIFF, MP4 and ASF files. + // MP3 + types << QLatin1String("audio/mpeg"); + types << QLatin1String("audio/mpeg3"); types << QLatin1String("audio/x-mpeg"); + + // FLAC + types << QLatin1String("audio/flac"); + + // MPC + types << QLatin1String("audio/x-musepack"); + + //OGG + types << QLatin1String("audio/ogg"); types << QLatin1String("audio/x-vorbis+ogg"); + + // WAV + types << QLatin1String("audio/wav"); + + // AIFF + types << QLatin1String("audio/x-aiff"); + + // APE + types << QLatin1String("audio/x-ape"); + + // WV + types << QLatin1String("audio/x-wavpack"); + + return types; +} + +void TagLibExtractor::extract(ExtractionResult* result) +{ + const QString fileUrl = result->inputUrl(); + const QString mimeType = result->inputMimetype(); + + TagLib::FileRef file(fileUrl.toUtf8().data(), true); + if (file.isNull()) { + return; + } + + TagLib::Tag* tags = file.tag(); + result->addType(Type::Audio); + + TagLib::String artists; + TagLib::String albumArtists; + TagLib::String composers; + TagLib::String lyricists; + TagLib::StringList genres; + + // Handling multiple tags in mpeg files. + if ((mimeType == "audio/mpeg") || (mimeType == "audio/mpeg3") || (mimeType == "audio/x-mpeg")) { + TagLib::MPEG::File mpegFile(fileUrl.toUtf8().data(), true); + if (mpegFile.ID3v2Tag() && !mpegFile.ID3v2Tag()->isEmpty()) { + TagLib::ID3v2::FrameList lstID3v2; + + // Artist. + lstID3v2 = mpegFile.ID3v2Tag()->frameListMap()["TPE1"]; + if (!lstID3v2.isEmpty()) { + for (TagLib::ID3v2::FrameList::ConstIterator it = lstID3v2.begin(); it != lstID3v2.end(); ++it) { + if (!artists.isEmpty()) { + artists += ", "; + } + artists += (*it)->toString(); + } + } + + // Album Artist. + lstID3v2 = mpegFile.ID3v2Tag()->frameListMap()["TPE2"]; + if (!lstID3v2.isEmpty()) { + for (TagLib::ID3v2::FrameList::ConstIterator it = lstID3v2.begin(); it != lstID3v2.end(); ++it) { + if (!albumArtists.isEmpty()) { + albumArtists += ", "; + } + albumArtists += (*it)->toString(); + } + } + + // Composer. + lstID3v2 = mpegFile.ID3v2Tag()->frameListMap()["TCOM"]; + if (!lstID3v2.isEmpty()) { + for (TagLib::ID3v2::FrameList::ConstIterator it = lstID3v2.begin(); it != lstID3v2.end(); ++it) { + if (!composers.isEmpty()) { + composers += ", "; + } + composers += (*it)->toString(); + } + } + + // Lyricist. + lstID3v2 = mpegFile.ID3v2Tag()->frameListMap()["TEXT"]; + if (!lstID3v2.isEmpty()) { + for (TagLib::ID3v2::FrameList::ConstIterator it = lstID3v2.begin(); it != lstID3v2.end(); ++it) { + if (!lyricists.isEmpty()) { + lyricists += ", "; + } + lyricists += (*it)->toString(); + } + } + + // Genre. + lstID3v2 = mpegFile.ID3v2Tag()->frameListMap()["TCON"]; + if (!lstID3v2.isEmpty()) { + for (TagLib::ID3v2::FrameList::ConstIterator it = lstID3v2.begin(); it != lstID3v2.end(); ++it) { + genres.append((*it)->toString()); + } + } + + } + } + + // Handling multiple tags in FLAC files. + if (mimeType == "audio/flac") { + TagLib::FLAC::File flacFile(fileUrl.toUtf8().data(), true); + if (flacFile.xiphComment() && !flacFile.xiphComment()->isEmpty()) { + TagLib::Ogg::FieldListMap lstFLAC = flacFile.xiphComment()->fieldListMap(); + TagLib::Ogg::FieldListMap::ConstIterator itFLAC; + + // Artist. + itFLAC = lstFLAC.find("ARTIST"); + if (itFLAC != lstFLAC.end()) { + if (!artists.isEmpty()) { + artists += ", "; + } + artists += (*itFLAC).second.toString(", "); + } + + // Album Artist. + itFLAC = lstFLAC.find("ALBUMARTIST"); + if (itFLAC != lstFLAC.end()) { + if (!albumArtists.isEmpty()) { + albumArtists += ", "; + } + albumArtists += (*itFLAC).second.toString(", "); + } + + // Composer. + itFLAC = lstFLAC.find("COMPOSER"); + if (itFLAC != lstFLAC.end()) { + if (!composers.isEmpty()) { + composers += ", "; + } + composers += (*itFLAC).second.toString(", "); + } + + // Lyricist. + itFLAC = lstFLAC.find("LYRICIST"); + if (itFLAC != lstFLAC.end()) { + if (!lyricists.isEmpty()) { + lyricists += ", "; + } + lyricists += (*itFLAC).second.toString(", "); + } + + // Genre. + itFLAC = lstFLAC.find("GENRE"); + if (itFLAC != lstFLAC.end()) { + genres.append((*itFLAC).second); + } + } + } + + // Handling multiple tags in Musepack files. + if (mimeType == ("audio/x-musepack")) { + TagLib::MPC::File mpcFile(fileUrl.toUtf8().data(), true); + if (mpcFile.tag() && !mpcFile.tag()->isEmpty()) { + TagLib::APE::ItemListMap lstMusepack = mpcFile.APETag()->itemListMap(); + TagLib::APE::ItemListMap::ConstIterator itMPC; + + // Artist. + itMPC = lstMusepack.find("ARTIST"); + if (itMPC != lstMusepack.end()) { + if (!artists.isEmpty()) { + artists += ", "; + } + artists += (*itMPC).second.toString(); + } + + // Album Artist. + itMPC = lstMusepack.find("ALBUMARTIST"); + if (itMPC != lstMusepack.end()) { + if(!albumArtists.isEmpty()) { + albumArtists += ", "; + } + albumArtists += (*itMPC).second.toString(); + } + + // Composer. + itMPC = lstMusepack.find("COMPOSER"); + if (itMPC != lstMusepack.end()) { + if (!composers.isEmpty()) { + composers += ", "; + } + composers += (*itMPC).second.toString(); + } + + // Lyricist. + itMPC = lstMusepack.find("LYRICIST"); + if (itMPC != lstMusepack.end()) { + if (!lyricists.isEmpty()) { + lyricists += ", "; + } + lyricists += (*itMPC).second.toString(); + } + + // Genre. + itMPC = lstMusepack.find("GENRE"); + if (itMPC != lstMusepack.end()) { + genres.append((*itMPC).second.toString()); + } + } + } + + // Handling multiple tags in OGG files. + if (mimeType == "audio/ogg" || mimeType == "audio/x-vorbis+ogg") { + TagLib::Ogg::Vorbis::File oggFile(fileUrl.toUtf8().data(), true); + if (oggFile.tag() && !oggFile.tag()->isEmpty()) { + TagLib::Ogg::FieldListMap lstOGG = oggFile.tag()->fieldListMap(); + TagLib::Ogg::FieldListMap::ConstIterator itOGG; + + // Artist. + itOGG = lstOGG.find("ARTIST"); + if (itOGG != lstOGG.end()) { + if (!artists.isEmpty()) { + artists += ", "; + } + artists += (*itOGG).second.toString(", "); + } + + // Album Artist. + itOGG = lstOGG.find("ALBUMARTIST"); + if (itOGG != lstOGG.end()) { + if (!albumArtists.isEmpty()) { + albumArtists += ", "; + } + albumArtists += (*itOGG).second.toString(", "); + } + + // Composer. + itOGG = lstOGG.find("COMPOSER"); + if (itOGG != lstOGG.end()) { + if (!composers.isEmpty()) { + composers += ", "; + } + composers += (*itOGG).second.toString(", "); + } + + // Lyricist. + itOGG = lstOGG.find("LYRICIST"); + if (itOGG != lstOGG.end()) { + if (!lyricists.isEmpty()) { + lyricists += ", "; + } + lyricists += (*itOGG).second.toString(", "); + } + + // Genre. + itOGG = lstOGG.find("GENRE"); + if (itOGG != lstOGG.end()) { + genres.append((*itOGG).second); + } + } + } + + if (!tags->isEmpty()) { + QString title = QString::fromUtf8(tags->title().toCString(true)); + if (!title.isEmpty()) { + result->add(Property::Title, title); + } + + QString comment = QString::fromUtf8(tags->comment().toCString(true)); + if (!comment.isEmpty()) { + result->add(Property::Comment, comment); + } + + if (genres.isEmpty()) { + genres.append(tags->genre()); + } + + for (uint i = 0; i < genres.size(); i++) { + QString genre = QString::fromUtf8(genres[i].toCString(true)).trimmed(); + + // Convert from int + bool ok = false; + int genreNum = genre.toInt(&ok); + if (ok) { + genre = QString::fromUtf8(TagLib::ID3v1::genre(genreNum).toCString(true)); + } + + result->add(Property::Genre, genre); + } + + QString artistString; + if (artists.isEmpty()) { + artistString = QString::fromUtf8(tags->artist().toCString(true)); + } else { + artistString = QString::fromUtf8(artists.toCString(true)).trimmed(); + } + + QStringList artists = contactsFromString(artistString); + foreach(const QString& artist, artists) { + result->add(Property::Artist, artist); + } + + QString composersString = QString::fromUtf8(composers.toCString(true)).trimmed(); + QStringList composers = contactsFromString(composersString); + foreach(const QString& comp, composers) { + result->add(Property::Composer, comp); + } + + QString lyricistsString = QString::fromUtf8(lyricists.toCString(true)).trimmed(); + QStringList lyricists = contactsFromString(lyricistsString); + foreach(const QString& lyr, lyricists) { + result->add(Property::Lyricist, lyr); + } + + QString album = QString::fromUtf8(tags->album().toCString(true)); + if (!album.isEmpty()) { + result->add(Property::Album, album); + + QString albumArtistsString = QString::fromUtf8(albumArtists.toCString(true)).trimmed(); + QStringList albumArtists = contactsFromString(albumArtistsString); + foreach(const QString& res, albumArtists) { + result->add(Property::AlbumArtist, res); + } + } + + if (tags->track()) { + result->add(Property::TrackNumber, tags->track()); + } + + if (tags->year()) { + result->add(Property::ReleaseYear, tags->year()); + } + } + + TagLib::AudioProperties* audioProp = file.audioProperties(); + if (audioProp) { + if (audioProp->length()) { + // What about the xml duration? + result->add(Property::Duration, audioProp->length()); + } + + if (audioProp->bitrate()) { + result->add(Property::BitRate, audioProp->bitrate() * 1000); + } + + if (audioProp->channels()) { + result->add(Property::Channels, audioProp->channels()); + } + + if (audioProp->sampleRate()) { + result->add(Property::SampleRate, audioProp->sampleRate()); + } + } + + // TODO: Get more properties based on the file type + // - Codec + // - Album Artist + // - Publisher + + // TAG information (incomplete). + // A good reference: http://qoobar.sourceforge.net/en/documentation.htm + // -- FLAC/OGG -- + // Artist: ARTIST, PERFORMER + // Album artist: ALBUMARTIST + // Composer: COMPOSER + // Lyricist: LYRICIST + // Conductor: CONDUCTOR + // Disc number: DISCNUMBER + // Total discs: TOTALDISCS, DISCTOTAL + // Track number: TRACKNUMBER + // Total tracks: TOTALTRACKS, TRACKTOTAL + // Genre: GENRE + // -- ID3v2 -- + // Artist: TPE1 + // Album artist: TPE2 + // Composer: TCOM + // Lyricist: TEXT + // Conductor: TPE3 + // Disc number[/total dics]: TPOS + // Track number[/total tracks]: TRCK + // Genre: TCON +} + +KFILEMETADATA_EXPORT_EXTRACTOR(KFileMetaData::TagLibExtractor, "kfilemetadata_taglibextextractor") diff --git a/kfilemetadata/src/extractors/taglibextractor.h b/kfilemetadata/src/extractors/taglibextractor.h new file mode 100644 index 00000000..71a7fdce --- /dev/null +++ b/kfilemetadata/src/extractors/taglibextractor.h @@ -0,0 +1,41 @@ +/* + + Copyright (C) 2012 Vishesh Handa + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef TAGLIBEXTRACTOR_H +#define TAGLIBEXTRACTOR_H + +#include "extractorplugin.h" + +namespace KFileMetaData +{ + +class TagLibExtractor : public ExtractorPlugin +{ + +public: + TagLibExtractor(QObject* parent, const QVariantList&); + + virtual void extract(ExtractionResult* result); + virtual QStringList mimetypes() const; +}; + +} + +#endif // TAGLIBEXTRACTOR_H diff --git a/kfilemetadata/src/kfilemetadata_export.h b/kfilemetadata/src/kfilemetadata_export.h new file mode 100644 index 00000000..c7d1ee15 --- /dev/null +++ b/kfilemetadata/src/kfilemetadata_export.h @@ -0,0 +1,39 @@ +/* This file is part of the KDE project + Copyright (C) 2007 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _KFILEMETADATA_EXPORT_H +#define _KFILEMETADATA_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include + +#ifndef KFILEMETADATA_EXPORT +# if defined(KDELIBS_STATIC_LIBS) + /* No export/import for static libraries */ +# define KFILEMETADATA_EXPORT +# elif defined(MAKE_KFILEMETADATA_LIB) + /* We are building this library */ +# define KFILEMETADATA_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define KFILEMETADATA_EXPORT KDE_IMPORT +# endif +#endif + +#endif diff --git a/kfilemetadata/src/kfilemetadataextractor.desktop b/kfilemetadata/src/kfilemetadataextractor.desktop new file mode 100644 index 00000000..246777f9 --- /dev/null +++ b/kfilemetadata/src/kfilemetadataextractor.desktop @@ -0,0 +1,42 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=KFileMetaDataExtractor +Comment=KFileMetaData Extractor +Comment[bs]=KFileMetaData ekstraktor +Comment[ca]=Extractor del KFileMetaData +Comment[ca@valencia]=Extractor del KFileMetaData +Comment[cs]=Extraktor KFileMetaData +Comment[da]=KFileMetaData-udtrækker +Comment[de]=KFileMetaData-Extraktion +Comment[el]=Εξαγωγέας KFileMetaData +Comment[en_GB]=KFileMetaData Extractor +Comment[es]=Extractor para KFileMetaData +Comment[et]=KFileMetaData ekstraktimine +Comment[fi]=KFileMetaDatan lukuohjelma +Comment[fr]=Extracteur de KFileMetaData +Comment[gl]=Extractor de KFileMetaData +Comment[hu]=KFileMetaData kibontó +Comment[ia]=Extractor de KFileMetaData +Comment[it]=Estrattore di KFileMetaData +Comment[ko]=KFileMetaData 추출기 +Comment[lt]=KFileMetaData ekstraktorius +Comment[nb]=KFileMetaData uttrekker +Comment[nds]=KFile-Metadaten-Leser +Comment[nl]=Extractieprogramma voor KFileMetaData +Comment[pa]=KFileMetaData ਐਕਸਟਰੈਕਟਰ +Comment[pl]=Wydobywanie KFileMetaData +Comment[pt]=Extracção do KFileMetaData +Comment[pt_BR]=Extrator do KFileMetaData +Comment[ru]=Модуль извлечения метаданных из файлов +Comment[sk]=Extraktor KFileMetaData +Comment[sl]=Program za izvleko za KFileMetaData +Comment[sr]=Издвајач на основу KFileMetaData +Comment[sr@ijekavian]=Издвајач на основу KFileMetaData +Comment[sr@ijekavianlatin]=Izdvajač na osnovu KFileMetaData +Comment[sr@latin]=Izdvajač na osnovu KFileMetaData +Comment[sv]=Kfilmetadata extrahering +Comment[tr]=KFileMetaData Çıkarıcı +Comment[uk]=Засіб видобування метаданих +Comment[x-test]=xxKFileMetaData Extractorxx +Comment[zh_CN]=KFileMetaData 提取工具 +Comment[zh_TW]=KFileMetaData 展開器 diff --git a/kfilemetadata/src/properties.h b/kfilemetadata/src/properties.h new file mode 100644 index 00000000..a96048eb --- /dev/null +++ b/kfilemetadata/src/properties.h @@ -0,0 +1,126 @@ +/* + * This file is part of KFileMetaData + * Copyright (C) 2014 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef KFILEMETADATA_PROPERTIES +#define KFILEMETADATA_PROPERTIES + +#include +#include + +namespace KFileMetaData { +namespace Property { + +enum Property { + FirstProperty = 0, + Empty = 0, + + // Audio + BitRate, + Channels, + Duration, + Genre, + SampleRate, + TrackNumber, + ReleaseYear, + + // Maybe merge this with the description? + Comment, + + // Music + Artist, + Album, + AlbumArtist, + Composer, + Lyricist, + + // Documents + Author, + Title, + Subject, + Creator, + Generator, // What's the difference? + PageCount, + WordCount, + LineCount, + Langauge, + Copyright, + Publisher, + Description, + CreationDate, + Keywords, + + Width, + Height, + AspectRatio, + FrameRate, + + // Images + ImageMake, + ImageModel, + ImageDateTime, + ImageOrientation, + PhotoFlash, + PhotoPixelXDimension, + PhotoPixelYDimension, + PhotoDateTimeOriginal, + PhotoFocalLength, + PhotoFocalLengthIn35mmFilm, + PhotoExposureTime, + PhotoFNumber, + PhotoApertureValue, + PhotoExposureBiasValue, + PhotoWhiteBalance, + PhotoMeteringMode, + PhotoISOSpeedRatings, + PhotoSaturation, + PhotoSharpness, + + LastProperty = PhotoSharpness +}; + +} // namespace Property + +typedef QMap PropertyMap; + +inline QVariantMap toVariantMap(const PropertyMap& propMap) { + QVariantMap varMap; + PropertyMap::const_iterator it = propMap.constBegin(); + for (; it != propMap.constEnd(); ++it) { + int p = static_cast(it.key()); + varMap.insertMulti(QString::number(p), it.value()); + } + + return varMap; +} + +inline PropertyMap toPropertyMap(const QVariantMap& varMap) { + PropertyMap propMap; + QVariantMap::const_iterator it = varMap.constBegin(); + for (; it != varMap.constEnd(); ++it) { + int p = it.key().toInt(); + propMap.insertMulti(static_cast(p), it.value()); + } + + return propMap; +} + +} // namespace KFileMetaData + +#endif diff --git a/kfilemetadata/src/propertyinfo.cpp b/kfilemetadata/src/propertyinfo.cpp new file mode 100644 index 00000000..3dde0a48 --- /dev/null +++ b/kfilemetadata/src/propertyinfo.cpp @@ -0,0 +1,472 @@ +/* + * This file is part of the KFileMetaData project + * Copyright (C) 2014 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#include "propertyinfo.h" + +#include +#include + +static const KCatalogLoader loader("kfilemetadata"); + +using namespace KFileMetaData; + +class PropertyInfo::Private { +public: + Property::Property prop; + QString name; + QString displayName; + QVariant::Type valueType; + bool shouldBeIndexed; +}; + +PropertyInfo::PropertyInfo(Property::Property property) + : d(new Private) +{ + d->prop = property; + d->shouldBeIndexed = true; + + switch (property) { + case Property::Album: + d->name = QLatin1String("album"); + d->displayName = i18nc("@label music album", "Album"); + d->valueType = QVariant::String; + break; + + case Property::AlbumArtist: + d->name = QLatin1String("albumArtist"); + d->displayName = i18nc("@label", "Album Artist"); + d->valueType = QVariant::StringList; + break; + + case Property::Artist: + d->name = QLatin1String("artist"); + d->displayName = i18nc("@label", "Artist"); + d->valueType = QVariant::StringList; + break; + + case Property::AspectRatio: + d->name = QLatin1String("aspectRatio"); + d->displayName = i18nc("@label", "Aspect Ratio"); + d->valueType = QVariant::Int; + break; + + case Property::Author: + d->name = QLatin1String("author"); + d->displayName = i18nc("@label", "Author"); + d->valueType = QVariant::StringList; + break; + + case Property::BitRate: + d->name = QLatin1String("bitRate"); + d->displayName = i18nc("@label", "Bitrate"); + d->valueType = QVariant::Int; + break; + + case Property::Channels: + d->name = QLatin1String("channels"); + d->displayName = i18nc("@label", "Channels"); + d->valueType = QVariant::Int; + break; + + case Property::Comment: + d->name = QLatin1String("comment"); + d->displayName = i18nc("@label", "Comment"); + d->valueType = QVariant::String; + d->shouldBeIndexed = false; + break; + + case Property::Composer: + d->name = QLatin1String("composer"); + d->displayName = i18nc("@label", "Composer"); + d->valueType = QVariant::String; + d->shouldBeIndexed = false; + break; + + case Property::Copyright: + d->name = QLatin1String("copyright"); + d->displayName = i18nc("@label", "Copyright"); + d->valueType = QVariant::String; + d->shouldBeIndexed = false; + break; + + case Property::CreationDate: + d->name = QLatin1String("creationDate"); + d->displayName = i18nc("@label", "Creation Date"); + d->valueType = QVariant::String; + break; + + case Property::Creator: + d->name = QLatin1String("creator"); + d->displayName = i18nc("@label", "Creator"); + d->valueType = QVariant::String; + d->shouldBeIndexed = false; + break; + + case Property::Description: + d->name = QLatin1String("description"); + d->displayName = i18nc("@label", "Description"); + d->valueType = QVariant::String; + d->shouldBeIndexed = false; + break; + + case Property::Duration: + d->name = QLatin1String("duration"); + d->displayName = i18nc("@label", "Duration"); + d->valueType = QVariant::Int; + break; + + case Property::Empty: + d->name = QLatin1String("empty"); + d->valueType = QVariant::Invalid; + break; + + case Property::FrameRate: + d->name = QLatin1String("frameRate"); + d->displayName = i18nc("@label", "Frame Rate"); + d->valueType = QVariant::Int; + break; + + // FIXME: This doesn't tell the user much + case Property::Generator: + d->name = QLatin1String("generator"); + d->displayName = i18nc("@label", "Generator"); + d->valueType = QVariant::String; + d->shouldBeIndexed = false; + break; + + case Property::Genre: + d->name = QLatin1String("genre"); + d->displayName = i18nc("@label music genre", "Genre"); + d->valueType = QVariant::StringList; + d->shouldBeIndexed = false; + break; + + case Property::Height: + d->name = QLatin1String("height"); + d->displayName = i18nc("@label", "Height"); + d->valueType = QVariant::Int; + break; + + case Property::ImageDateTime: + d->name = QLatin1String("imageDateTime"); + d->displayName = i18nc("@label EXIF", "Image Date Time"); + d->valueType = QVariant::DateTime; + break; + + case Property::ImageMake: + d->name = QLatin1String("imageMake"); + d->displayName = i18nc("@label EXIF", "Image Make"); + d->valueType = QVariant::String; + d->shouldBeIndexed = false; + break; + + case Property::ImageModel: + d->name = QLatin1String("imageModel"); + d->displayName = i18nc("@label EXIF", "Image Model"); + d->valueType = QVariant::String; + d->shouldBeIndexed = false; + break; + + case Property::ImageOrientation: + d->name = QLatin1String("imageOrientation"); + d->displayName = i18nc("@label EXIF", "Image Orientation"); + d->valueType = QVariant::Int; + break; + + case Property::Keywords: + d->name = QLatin1String("keywords"); + d->displayName = i18nc("@label", "Keywords"); + d->valueType = QVariant::StringList; + d->shouldBeIndexed = false; + break; + + case Property::Langauge: + d->name = QLatin1String("language"); + d->displayName = i18nc("@label", "Language"); + d->valueType = QVariant::String; + d->shouldBeIndexed = false; + break; + + case Property::LineCount: + d->name = QLatin1String("lineCount"); + d->displayName = i18nc("@label number of lines", "Line Count"); + d->valueType = QVariant::Int; + break; + + case Property::Lyricist: + d->name = QLatin1String("lyricist"); + d->displayName = i18nc("@label", "Lyricist"); + d->valueType = QVariant::StringList; + d->shouldBeIndexed = false; + break; + + case Property::PageCount: + d->name = QLatin1String("pageCount"); + d->displayName = i18nc("@label", "Page Count"); + d->valueType = QVariant::Int; + break; + + case Property::PhotoApertureValue: + d->name = QLatin1String("photoApertureValue"); + d->displayName = i18nc("@label EXIF", "Photo Aperture Value"); + d->valueType = QVariant::Double; + break; + + case Property::PhotoDateTimeOriginal: + d->name = QLatin1String("photoDateTimeOriginal"); + d->displayName = i18nc("@label EXIF", "Photo Original Date Time"); + d->valueType = QVariant::DateTime; + break; + + case Property::PhotoExposureBiasValue: + d->name = QLatin1String("photoExposureBiasValue"); + d->displayName = i18nc("@label EXIF", "Photo Exposure Bias"); + d->valueType = QVariant::Double; + break; + + case Property::PhotoExposureTime: + d->name = QLatin1String("photoExposureTime"); + d->displayName = i18nc("@label EXIF", "Photo Exposure Time"); + d->valueType = QVariant::Double; + break; + + case Property::PhotoFlash: + d->name = QLatin1String("photoFlash"); + d->displayName = i18nc("@label EXIF", "Photo Flash"); + d->valueType = QVariant::Int; + break; + + case Property::PhotoFNumber: + d->name = QLatin1String("photoFNumber"); + d->displayName = i18nc("@label EXIF", "Photo F Number"); + d->valueType = QVariant::Int; + break; + + case Property::PhotoFocalLength: + d->name = QLatin1String("photoFocalLength"); + d->displayName = i18nc("@label EXIF", "Photo Focal Length"); + d->valueType = QVariant::Double; + break; + + case Property::PhotoFocalLengthIn35mmFilm: + d->name = QLatin1String("photoFocalLengthIn35mmFilm"); + d->displayName = i18nc("@label EXIF", "Photo Focal Length 35mm"); + d->valueType = QVariant::Double; + break; + + case Property::PhotoISOSpeedRatings: + d->name = QLatin1String("photoISOSpeedRatings"); + d->displayName = i18nc("@label EXIF", "Photo ISO Speed Rating"); + d->valueType = QVariant::Int; + break; + + case Property::PhotoMeteringMode: + d->name = QLatin1String("photoMeteringMode"); + d->displayName = i18nc("@label EXIF", "Photo Metering Mode"); + d->valueType = QVariant::Int; + break; + + case Property::PhotoPixelXDimension: + d->name = QLatin1String("photoPixelXDimension"); + d->displayName = i18nc("@label EXIF", "Photo X Dimension"); + d->valueType = QVariant::Int; + break; + + case Property::PhotoPixelYDimension: + d->name = QLatin1String("photoPixelYDimension"); + d->displayName = i18nc("@label EXIF", "Photo Y Dimension"); + d->valueType = QVariant::Int; + break; + + case Property::PhotoSaturation: + d->name = QLatin1String("photoSaturation"); + d->displayName = i18nc("@label EXIF", "Photo Saturation"); + d->valueType = QVariant::Int; + break; + + case Property::PhotoSharpness: + d->name = QLatin1String("photoSharpness"); + d->displayName = i18nc("@label EXIF", "Photo Sharpness"); + d->valueType = QVariant::Int; + break; + + case Property::PhotoWhiteBalance: + d->name = QLatin1String("photoWhiteBalance"); + d->displayName = i18nc("@label EXIF", "Photo White Balance"); + d->valueType = QVariant::Int; + break; + + case Property::Publisher: + d->name = QLatin1String("publisher"); + d->displayName = i18nc("@label", "Publisher"); + d->valueType = QVariant::String; + break; + + case Property::ReleaseYear: + d->name = QLatin1String("releaseYear"); + d->displayName = i18nc("@label", "Release Year"); + d->valueType = QVariant::Int; + break; + + case Property::SampleRate: + d->name = QLatin1String("sampleRate"); + d->displayName = i18nc("@label", "Sample Rate"); + d->valueType = QVariant::Int; + break; + + case Property::Subject: + d->name = QLatin1String("subject"); + d->displayName = i18nc("@label", "Subject"); + d->valueType = QVariant::String; + d->shouldBeIndexed = false; + break; + + case Property::Title: + d->name = QLatin1String("title"); + d->displayName = i18nc("@label", "Title"); + d->valueType = QVariant::String; + break; + + case Property::TrackNumber: + d->name = QLatin1String("trackNumber"); + d->displayName = i18nc("@label music track number", "Track Number"); + d->valueType = QVariant::Int; + break; + + case Property::Width: + d->name = QLatin1String("width"); + d->displayName = i18nc("@label", "Width"); + d->valueType = QVariant::Int; + break; + + case Property::WordCount: + d->name = QLatin1String("wordCount"); + d->displayName = i18nc("@label number of words", "Word Count"); + d->valueType = QVariant::Int; + break; + + default: + break; + } + + if (d->valueType == QVariant::Int || d->valueType == QVariant::DateTime || + d->valueType == QVariant::Double) + { + d->shouldBeIndexed = false; + } +} + +PropertyInfo::PropertyInfo(const PropertyInfo& pi) + : d(new Private(*pi.d)) +{ +} + +PropertyInfo::~PropertyInfo() +{ + delete d; +} + +QString PropertyInfo::displayName() const +{ + return d->displayName; +} + +QString PropertyInfo::name() const +{ + return d->name; +} + +Property::Property PropertyInfo::property() const +{ + return d->prop; +} + +QVariant::Type PropertyInfo::valueType() const +{ + return d->valueType; +} + +bool PropertyInfo::shouldBeIndexed() const +{ + return d->shouldBeIndexed; +} + +PropertyInfo PropertyInfo::fromName(const QString& name) +{ + static QHash propertyHash; + + // FIXME: Multi-threading? + if (propertyHash.isEmpty()) { + propertyHash.insert(QLatin1String("bitrate"), Property::BitRate); + propertyHash.insert(QLatin1String("channels"), Property::Channels); + propertyHash.insert(QLatin1String("duration"), Property::Duration); + propertyHash.insert(QLatin1String("genre"), Property::Genre); + propertyHash.insert(QLatin1String("samplerate"), Property::SampleRate); + propertyHash.insert(QLatin1String("tracknumber"), Property::TrackNumber); + propertyHash.insert(QLatin1String("releaseyear"), Property::ReleaseYear); + propertyHash.insert(QLatin1String("comment"), Property::Comment); + propertyHash.insert(QLatin1String("artist"), Property::Artist); + propertyHash.insert(QLatin1String("album"), Property::Album); + propertyHash.insert(QLatin1String("albumartist"), Property::AlbumArtist); + propertyHash.insert(QLatin1String("composer"), Property::Composer); + propertyHash.insert(QLatin1String("lyricist"), Property::Lyricist); + propertyHash.insert(QLatin1String("author"), Property::Author); + propertyHash.insert(QLatin1String("title"), Property::Title); + propertyHash.insert(QLatin1String("subject"), Property::Subject); + propertyHash.insert(QLatin1String("creator"), Property::Creator); + propertyHash.insert(QLatin1String("generator"), Property::Generator); + propertyHash.insert(QLatin1String("pagecount"), Property::PageCount); + propertyHash.insert(QLatin1String("wordcount"), Property::WordCount); + propertyHash.insert(QLatin1String("linecount"), Property::LineCount); + propertyHash.insert(QLatin1String("language"), Property::Langauge); + propertyHash.insert(QLatin1String("copyright"), Property::Copyright); + propertyHash.insert(QLatin1String("publisher"), Property::Publisher); + propertyHash.insert(QLatin1String("description"), Property::Description); + propertyHash.insert(QLatin1String("creationdate"), Property::CreationDate); + propertyHash.insert(QLatin1String("keywords"), Property::Keywords); + propertyHash.insert(QLatin1String("width"), Property::Width); + propertyHash.insert(QLatin1String("height"), Property::Height); + propertyHash.insert(QLatin1String("aspectratio"), Property::AspectRatio); + propertyHash.insert(QLatin1String("framerate"), Property::FrameRate); + propertyHash.insert(QLatin1String("imagemake"), Property::ImageMake); + propertyHash.insert(QLatin1String("imagemodel"), Property::ImageModel); + propertyHash.insert(QLatin1String("imagedatetime"), Property::ImageDateTime); + propertyHash.insert(QLatin1String("imageorientation"), Property::ImageOrientation); + propertyHash.insert(QLatin1String("photoflash"), Property::PhotoFlash); + propertyHash.insert(QLatin1String("photopixelxdimension"), Property::PhotoPixelXDimension); + propertyHash.insert(QLatin1String("photopixelydimension"), Property::PhotoPixelYDimension); + propertyHash.insert(QLatin1String("photodatetimeoriginal"), Property::PhotoDateTimeOriginal); + propertyHash.insert(QLatin1String("photofocallength"), Property::PhotoFocalLength); + propertyHash.insert(QLatin1String("photofocallengthin35mmfilm"), Property::PhotoFocalLengthIn35mmFilm); + propertyHash.insert(QLatin1String("photoexposuretime"), Property::PhotoExposureTime); + propertyHash.insert(QLatin1String("photofnumber"), Property::PhotoFNumber); + propertyHash.insert(QLatin1String("photoaperturevalue"), Property::PhotoApertureValue); + propertyHash.insert(QLatin1String("photoexposurebiasvalue"), Property::PhotoExposureBiasValue); + propertyHash.insert(QLatin1String("photowhitebalance"), Property::PhotoWhiteBalance); + propertyHash.insert(QLatin1String("photometeringmode"), Property::PhotoMeteringMode); + propertyHash.insert(QLatin1String("photoisospeedratings"), Property::PhotoISOSpeedRatings); + propertyHash.insert(QLatin1String("photosaturation"), Property::PhotoSaturation); + propertyHash.insert(QLatin1String("photosharpness"), Property::PhotoSharpness); + } + + return PropertyInfo(propertyHash.value(name.toLower())); +} diff --git a/kfilemetadata/src/propertyinfo.h b/kfilemetadata/src/propertyinfo.h new file mode 100644 index 00000000..bb54f58c --- /dev/null +++ b/kfilemetadata/src/propertyinfo.h @@ -0,0 +1,81 @@ +/* + * This file is part of the KFileMetaData project + * Copyright (C) 2014 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#ifndef _KFILEMETADATA_PROPERTYINFO_H +#define _KFILEMETADATA_PROPERTYINFO_H + +#include +#include +#include "properties.h" +#include "kfilemetadata_export.h" + +namespace KFileMetaData { + +class KFILEMETADATA_EXPORT PropertyInfo +{ +public: + PropertyInfo(Property::Property property); + PropertyInfo(const PropertyInfo& pi); + ~PropertyInfo(); + + /** + * The enumeration which represents this property + */ + Property::Property property() const; + + /** + * The internal developer named used to refer to the property + */ + QString name() const; + + /** + * A user visible name of the property + */ + QString displayName() const; + + /** + * The type the value of this property should be. + * Eg - Property::Height should be an integer + */ + QVariant::Type valueType() const; + + /** + * Indicates if this property requires indexing or should just be stored. + * Eg - Property::Height does not need to be part of the global index. + * When a user searches for 600, they should not get images with + * that height + */ + bool shouldBeIndexed() const; + + /** + * Construct a PropertyInfo from the internal property name. + * The internal property name is case insensitive + */ + static PropertyInfo fromName(const QString& name); + +private: + class Private; + Private* d; +}; + +} +#endif // _KFILEMETADATA_PROPERTYINFO_H diff --git a/kfilemetadata/src/typeinfo.cpp b/kfilemetadata/src/typeinfo.cpp new file mode 100644 index 00000000..4ae1a65b --- /dev/null +++ b/kfilemetadata/src/typeinfo.cpp @@ -0,0 +1,101 @@ +/* + * + * Copyright (C) 2014 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "typeinfo.h" + +#include + +using namespace KFileMetaData; + +class TypeInfo::Private { +public: + Type::Type type; + QString name; + QString displayName; +}; + +TypeInfo::TypeInfo(Type::Type type) + : d(new Private) +{ + d->type = type; + + switch (type) { + case Type::Archive: + d->name = QLatin1String("Archive"); + d->displayName = i18nc("@label", "Archive"); + break; + + case Type::Audio: + d->name = QLatin1String("Audio"); + d->displayName = i18nc("@label", "Audio"); + break; + + case Type::Document: + d->name = QLatin1String("Document"); + d->displayName = i18nc("@label", "Document"); + break; + + case Type::Image: + d->name = QLatin1String("Image"); + d->displayName = i18nc("@label", "Image"); + break; + + case Type::Presentation: + d->name = QLatin1String("Presentation"); + d->displayName = i18nc("@label", "Presentation"); + break; + + case Type::Spreadsheet: + d->name = QLatin1String("Spreadsheet"); + d->displayName = i18nc("@label", "Spreadsheet"); + break; + + case Type::Text: + d->name = QLatin1String("Text"); + d->displayName = i18nc("@label", "Text"); + break; + + case Type::Video: + d->name = QLatin1String("Video"); + d->displayName = i18nc("@label", "Video"); + break; + } +} + +TypeInfo::TypeInfo(const TypeInfo& ti) + : d(new Private(*ti.d)) +{ +} + +TypeInfo::~TypeInfo() +{ + delete d; +} + +QString TypeInfo::displayName() const +{ + return d->displayName; +} + +QString TypeInfo::name() const +{ + return d->name; +} + diff --git a/kfilemetadata/src/typeinfo.h b/kfilemetadata/src/typeinfo.h new file mode 100644 index 00000000..2675cbbd --- /dev/null +++ b/kfilemetadata/src/typeinfo.h @@ -0,0 +1,58 @@ +/* + * + * Copyright (C) 2014 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef KFILEMETADATA_TYPEINFO_H +#define KFILEMETADATA_TYPEINFO_H + +#include "types.h" +#include "kfilemetadata_export.h" +#include + +namespace KFileMetaData { + +class KFILEMETADATA_EXPORT TypeInfo +{ +public: + TypeInfo(Type::Type type); + TypeInfo(const TypeInfo& ti); + ~TypeInfo(); + + /** + * The type identifier + */ + Type::Type type() const; + + /** + * An internal name for the type + */ + QString name() const; + + /** + * A user visible translated name for this type + */ + QString displayName() const; + +private: + class Private; + Private* d; +}; +} + +#endif // KFILEMETADATA_TYPEINFO_H diff --git a/kfilemetadata/src/types.h b/kfilemetadata/src/types.h new file mode 100644 index 00000000..77b63db8 --- /dev/null +++ b/kfilemetadata/src/types.h @@ -0,0 +1,82 @@ +/* + * This file is part of KFileMetaData + * Copyright (C) 2014 Vishesh Handa + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef KFILEMETADATA_TYPES +#define KFILEMETADATA_TYPES + +namespace KFileMetaData { +namespace Type { + +enum Type { + FirstType = 0, + + /** + * Any file which contains a compressed collection of other files + * eg - tar, zip, rar, gz + */ + Archive = 0, + + /** + * Used to mark any file which just contains audio. Do not use this + * type if the file also contains Video + */ + Audio, + + /** + * Any file which contains Video. It may also contain Audio + */ + Video, + + /** + * Any Image file. This includes both raster and vector formats. + */ + Image, + + /** + * Any file which counts as a document. Documents are generally + * files which contain rich text, formatting and maybe images + */ + Document, + + /** + * A SpreadSheet file. This is a specialization of the Document type + * Any file which has this type should also have the Document type + */ + Spreadsheet, + + /** + * A Presentation file. This is a specialization of the Document type. + * Any file which has this type should also have the Document type + */ + Presentation, + + /** + * Any file which just contains plain text data counts + * as a Text file + */ + Text, + + LastType = Text +}; + +} +} + +#endif diff --git a/kgpg/.gitignore b/kgpg/.gitignore new file mode 100644 index 00000000..df24cd86 --- /dev/null +++ b/kgpg/.gitignore @@ -0,0 +1,2 @@ +.kdev4 +kgpg.kdev4 diff --git a/kgpg/AUTHORS b/kgpg/AUTHORS new file mode 100644 index 00000000..f0ca68e3 --- /dev/null +++ b/kgpg/AUTHORS @@ -0,0 +1,3 @@ +y0k0 +Rolf Eike 'Dakon' Beer +Jimmy Gilles diff --git a/kgpg/CMakeLists.txt b/kgpg/CMakeLists.txt new file mode 100644 index 00000000..554a9256 --- /dev/null +++ b/kgpg/CMakeLists.txt @@ -0,0 +1,187 @@ +project(kgpg) + +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) + find_package(KDE4) + include( KDE4Defaults ) + + add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) + add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) + + # If definitions like -D_GNU_SOURCE are needed for these checks they + # should be added to _KDE4_PLATFORM_DEFINITIONS when it is originally + # defined outside this file. Here we include these definitions in + # CMAKE_REQUIRED_DEFINITIONS so they will be included in the build of + # checks below. + set( CMAKE_REQUIRED_DEFINITIONS ${_KDE4_PLATFORM_DEFINITIONS} ) +endif() + +find_package( KdepimLibs REQUIRED ) +# only headers are used +find_package( Gpgme REQUIRED ) + +add_subdirectory( icons ) +add_subdirectory( doc ) +include_directories( ${KDE4_INCLUDES} ${KDEPIMLIBS_INCLUDE_DIR} ${GPGME_INCLUDES} ) + +option(KGPG_DEBUG_TRANSACTIONS "show commands and results of gpg calls in debug log" Off) + +set(core_SRCS + core/convert.cpp + core/images.cpp + core/emailvalidator.cpp + core/kgpgkey.cpp + core/KGpgExpandableNode.cpp + core/KGpgKeyNode.cpp + core/KGpgGroupMemberNode.cpp + core/KGpgGroupNode.cpp + core/KGpgNode.cpp + core/KGpgOrphanNode.cpp + core/KGpgRefNode.cpp + core/KGpgRootNode.cpp + core/KGpgSignableNode.cpp + core/KGpgSignNode.cpp + core/KGpgSubkeyNode.cpp + core/KGpgUatNode.cpp + core/KGpgUidNode.cpp +) + +set(kgpg_editor_SRCS + editor/kgpgmd5widget.cpp + editor/kgpgeditor.cpp + editor/kgpgtextedit.cpp +) + +set(kgpg_model_SRCS + model/gpgservermodel.cpp + model/kgpgitemmodel.cpp + model/groupeditproxymodel.cpp + model/keylistproxymodel.cpp + model/kgpgsearchresultmodel.cpp + model/selectkeyproxymodel.cpp +) + +set(kgpg_transaction_SRCS + transactions/kgpgtransaction.cpp + transactions/kgpgtransactionjob.cpp + transactions/kgpguidtransaction.cpp + transactions/kgpgeditkeytransaction.cpp + transactions/kgpgsigntransactionhelper.cpp + transactions/kgpgchangedisable.cpp + transactions/kgpgchangeexpire.cpp + transactions/kgpgchangepass.cpp + transactions/kgpgchangetrust.cpp + transactions/kgpgdelsign.cpp + transactions/kgpgdeluid.cpp + transactions/kgpgencrypt.cpp + transactions/kgpgexport.cpp + transactions/kgpgaddphoto.cpp + transactions/kgpgadduid.cpp + transactions/kgpggeneratekey.cpp + transactions/kgpggeneraterevoke.cpp + transactions/kgpgdecrypt.cpp + transactions/kgpgdelkey.cpp + transactions/kgpgimport.cpp + transactions/kgpgkeyservertransaction.cpp + transactions/kgpgkeyservergettransaction.cpp + transactions/kgpgkeyserversearchtransaction.cpp + transactions/kgpgprimaryuid.cpp + transactions/kgpgsendkeys.cpp + transactions/kgpgsignkey.cpp + transactions/kgpgsigntext.cpp + transactions/kgpgsignuid.cpp + transactions/kgpgtextorfiletransaction.cpp + transactions/kgpgverify.cpp +) + +set(kgpg_SRCS ${core_SRCS} ${kgpg_editor_SRCS} ${kgpg_model_SRCS} ${kgpg_transaction_SRCS} + selectpublickeydialog.cpp + selectsecretkey.cpp + kgpgoptions.cpp + keysmanager.cpp + kgpg.cpp + main.cpp + kgpgkeygenerate.cpp + kgpginterface.cpp + kgpgtextinterface.cpp + gpgproc.cpp + klinebufferedprocess.cpp + keyservers.cpp + detailedconsole.cpp + keyinfodialog.cpp + newkey.cpp + keyexport.cpp + conf_encryption.cpp + kgpgrevokewidget.cpp + keytreeview.cpp + groupedit.cpp + sourceselect.cpp + kgpgchangekey.cpp + kgpgfirstassistant.cpp + kgpgexternalactions.cpp + selectexpirydate.cpp + caff.cpp + foldercompressjob.cpp +) + +qt4_add_dbus_adaptor( kgpg_SRCS org.kde.kgpg.Key.xml keysmanager.h KeysManager) + +QT4_ADD_DBUS_INTERFACE(kgpg_SRCS org.kde.kgpg.Key.xml kgpg_interface ) + +kde4_add_ui_files(kgpg_SRCS + adduid.ui + conf_gpg.ui + searchres.ui + groupedit.ui + conf_ui2.ui + conf_servers.ui + conf_misc.ui + newkey.ui + keyexport.ui + conf_encryption.ui + kgpgKeyInfo.ui + kgpgkeygenerate.ui + kgpgrevokewidget.ui + keyserver.ui + conf_decryption.ui + sourceselect.ui +) + +kde4_add_app_icon(kgpg_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/hi*-app*-kgpg.png") + +kde4_add_kcfg_files(kgpg_SRCS kgpgsettings.kcfgc ) + +kde4_add_executable(kgpg ${kgpg_SRCS}) + +if (KGPG_DEBUG_TRANSACTIONS) + get_target_property(KGPG_FLAGS kgpg COMPILE_DEFINITIONS) + if (NOT KGPG_FLAGS) + # get rid of the NOTFOUND + set(KGPG_FLAGS) + endif (NOT KGPG_FLAGS) + list(APPEND KGPG_FLAGS KGPG_DEBUG_TRANSACTIONS) + set_target_properties(kgpg PROPERTIES COMPILE_DEFINITIONS "${KGPG_FLAGS}") +endif (KGPG_DEBUG_TRANSACTIONS) + +target_link_libraries(kgpg + ${KDE4_KIO_LIBS} + ${KDE4_KABC_LIBS} + ${KDE4_KUTILS_LIBS} + ${KDE4_KPIMUTILS_LIBRARY} + ${KDE4_SOLID_LIBS} + ${KDEPIMLIBS_AKONADI_CONTACT_LIBS} +) + +install(TARGETS kgpg ${INSTALL_TARGETS_DEFAULT_ARGS} ) + +########### install files ############### + +install( FILES editor/kgpgeditor.rc keysmanager.rc tips DESTINATION ${DATA_INSTALL_DIR}/kgpg) +install( PROGRAMS kgpg.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) +install( FILES kgpg.appdata.xml DESTINATION share/appdata/ ) +install( FILES kgpg.kcfg DESTINATION ${KCFG_INSTALL_DIR}) +install( FILES kgpg.desktop DESTINATION ${AUTOSTART_INSTALL_DIR} ) +install( FILES encryptfile.desktop encryptfolder.desktop viewdecrypted.desktop DESTINATION +${SERVICES_INSTALL_DIR}/ServiceMenus) +install( FILES org.kde.kgpg.Key.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR}) + +kde4_install_icons( ${ICON_INSTALL_DIR} ) diff --git a/kgpg/COPYING b/kgpg/COPYING new file mode 100644 index 00000000..5185fd3f --- /dev/null +++ b/kgpg/COPYING @@ -0,0 +1,346 @@ +NOTE! The GPL below is copyrighted by the Free Software Foundation, but +the instance of code that it refers to (the kde programs) are copyrighted +by the authors who actually wrote it. + +--------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/kgpg/CTestConfig.cmake b/kgpg/CTestConfig.cmake new file mode 100644 index 00000000..e84f3ecd --- /dev/null +++ b/kgpg/CTestConfig.cmake @@ -0,0 +1,13 @@ +## This file should be placed in the root directory of your project. +## Then modify the CMakeLists.txt file in the root directory of your +## project to incorporate the testing dashboard. +## # The following are required to uses Dart and the Cdash dashboard +## ENABLE_TESTING() +## INCLUDE(CTest) +set(CTEST_PROJECT_NAME "kgpg") +set(CTEST_NIGHTLY_START_TIME "20:00:00 CET") + +set(CTEST_DROP_METHOD "http") +set(CTEST_DROP_SITE "my.cdash.org") +set(CTEST_DROP_LOCATION "/submit.php?project=kgpg") +set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/kgpg/Mainpage.dox b/kgpg/Mainpage.dox new file mode 100644 index 00000000..c326bb27 --- /dev/null +++ b/kgpg/Mainpage.dox @@ -0,0 +1,17 @@ +/** @mainpage KGpg - KDEs graphical frontend for GnuPG + * + * This section describes the internal classes used to create KGpg. + * It should not be used as a public API. + * + * @authors + * Jean-Baptiste Mardelle \ + * Jimmy Gilles \ + * Rolf Eike Beer \ + * + * @maintainers + * Rolf Eike Beer \ + * + */ + +// DOXYGEN_REFERENCES = kdecore +// DOXYGEN_SET_PROJECT_NAME = kgpg diff --git a/kgpg/Messages.sh b/kgpg/Messages.sh new file mode 100644 index 00000000..76c7799d --- /dev/null +++ b/kgpg/Messages.sh @@ -0,0 +1,5 @@ +#! /bin/sh +$PREPARETIPS > tips.cpp +$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` >> rc.cpp +$XGETTEXT *.h *.cpp core/*.cpp editor/*.cpp model/*.cpp transactions/*.cpp -o $podir/kgpg.pot +rm -f tips.cpp diff --git a/kgpg/TODO b/kgpg/TODO new file mode 100644 index 00000000..f782fc22 --- /dev/null +++ b/kgpg/TODO @@ -0,0 +1,7 @@ +-delete multiple signatures at once +-don't do anything in terminal when deleting a private key (?) +-make searching on keyserver available via dbus +-mailto: link as context action for every user id +-make character encoding in decryptText choosable +-r716170 is probably wrong: imported secret keys are not trusted by default :( +-bring back markers for local and revocation signatures diff --git a/kgpg/adduid.ui b/kgpg/adduid.ui new file mode 100644 index 00000000..6f05b0e7 --- /dev/null +++ b/kgpg/adduid.ui @@ -0,0 +1,76 @@ + + AddUid + + + + 0 + 0 + 408 + 283 + + + + + + + Name (minimum 5 characters): + + + false + + + + + + + + + + Email: + + + false + + + + + + + + + + Comment (optional): + + + false + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + +
diff --git a/kgpg/caff.cpp b/kgpg/caff.cpp new file mode 100644 index 00000000..699a290e --- /dev/null +++ b/kgpg/caff.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2009,2010,2012,2013,2014 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "caff.h" +#include "caff_p.h" + +#include "kgpginterface.h" +#include "kgpgsettings.h" +#include "core/KGpgKeyNode.h" +#include "core/KGpgSignableNode.h" +#include "transactions/kgpgdeluid.h" +#include "transactions/kgpgencrypt.h" +#include "transactions/kgpgexport.h" +#include "transactions/kgpgimport.h" +#include "transactions/kgpgsignuid.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +KGpgCaffPrivate::KGpgCaffPrivate(KGpgCaff *parent, const KGpgSignableNode::List &ids, const QStringList &signers, + const KGpgCaff::OperationFlags flags, const KGpgSignTransactionHelper::carefulCheck checklevel) + : QObject(parent), + q_ptr(parent), + m_signers(signers), + m_flags(flags), + m_checklevel(checklevel), + m_allids(ids) +{ + const QString gpgCfg = KGpgSettings::gpgConfigPath(); + const QString secring = KgpgInterface::getGpgSetting(QLatin1String( "secret-keyring" ), gpgCfg); + + if (!secring.isEmpty()) { + m_secringfile = secring; + } else { + QFileInfo fn(gpgCfg); + fn.setFile(fn.dir(), QLatin1String("secring.gpg")); + m_secringfile = QDir::toNativeSeparators(fn.absoluteFilePath()); + } +} + +KGpgCaffPrivate::~KGpgCaffPrivate() +{ +} + +void +KGpgCaffPrivate::reexportKey(const KGpgSignableNode *key) +{ + Q_ASSERT(m_tempdir.isNull()); + + // find out if the given id can be used for encryption + const KGpgKeyNode *k; + if (key->getType() & KgpgCore::ITYPE_PAIR) + k = key->toKeyNode(); + else + k = key->getParentKeyNode()->toKeyNode(); + + // skip if not + if (!k->canEncrypt()) { + m_noEncIds << key; + m_allids.removeFirst(); + checkNextLoop(); + return; + } + + m_tempdir.reset(new KTempDir()); + + // export all keys necessary for signing + QStringList exportkeys(m_signers); + exportkeys << key->getKeyNode()->getId(); + + KGpgImport *imp = new KGpgImport(this); + + QStringList expOptions(QLatin1String( "--export-options" )); + expOptions << QLatin1String( "export-clean,export-attribute" ); + KGpgExport *exp = new KGpgExport(this, exportkeys, expOptions); + exp->setOutputTransaction(imp); + + imp->setGnuPGHome(m_tempdir->name()); + + connect(imp, SIGNAL(done(int)), SLOT(slotReimportDone(int))); + imp->start(); +} + +void +KGpgCaffPrivate::slotReimportDone(int result) +{ + KGpgImport *imp = qobject_cast(sender()); + + if (result != KGpgTransaction::TS_OK) { + abortOperation(result); + } else { + bool ret = (imp->getImportedIds(0x1).count() == 1 + m_signers.count()); + + if (!ret) { + abortOperation(-1); + } else { + KGpgSignUid *signuid = new KGpgSignUid(this, m_signers.first(), m_allids.first(), false, m_checklevel); + signuid->setGnuPGHome(m_tempdir->name()); + signuid->setSecringFile(m_secringfile); + connect(signuid, SIGNAL(done(int)), SLOT(slotSigningFinished(int))); + + signuid->start(); + } + } + + sender()->deleteLater(); +} + +void +KGpgCaffPrivate::abortOperation(int result) +{ + Q_Q(KGpgCaff); + + kDebug(2100) << "transaction" << sender() << "failed, result" << result; + m_tempdir.reset(); + + emit q->aborted(); +} + +void +KGpgCaffPrivate::checkNextLoop() +{ + Q_Q(KGpgCaff); + + m_tempdir.reset(); + + if (m_allids.isEmpty()) { + if (!m_noEncIds.isEmpty()) { + QStringList ids; + + foreach (const KGpgSignableNode *nd, m_noEncIds) + if (nd->getEmail().isEmpty()) + ids << i18nc("%1 is the key id, %2 is the name and comment of the key or uid", + "%1: %2", nd->getId(), nd->getNameComment()); + else + ids << i18nc("%1 is the key id, %2 is the name and comment of the key or uid, %3 is the email address of the uid", + "%1: %2 <%3>", nd->getId(), nd->getNameComment(), nd->getEmail()); + + KMessageBox::detailedSorry(qobject_cast(q->parent()), + i18np("No mail was sent for the following user id because it belongs to a key without encryption capability:", + "No mail was sent for the following user ids because they belong to keys without encryption capability:", + m_noEncIds.count()), + ids.join(QLatin1String("\n"))); + } + + if (!m_alreadyIds.isEmpty()) { + QStringList ids; + + foreach (const KGpgSignableNode *nd, m_alreadyIds) + if (nd->getEmail().isEmpty()) + ids << i18nc("%1 is the key id, %2 is the name and comment of the key or uid", + "%1: %2", nd->getId(), nd->getNameComment()); + else + ids << i18nc("%1 is the key id, %2 is the name and comment of the key or uid, %3 is the email address of the uid", + "%1: %2 <%3>", nd->getId(), nd->getNameComment(), nd->getEmail()); + + KMessageBox::detailedSorry(qobject_cast(q->parent()), + i18np("No mail was sent for the following user id because it was already signed:", + "No mail was sent for the following user ids because they were already signed:", + m_alreadyIds.count()), + ids.join(QLatin1String("\n"))); + } + + emit q->done(); + } else { + reexportKey(m_allids.first()); + } +} + +void +KGpgCaffPrivate::slotSigningFinished(int result) +{ + sender()->deleteLater(); + + if (result != KGpgTransaction::TS_OK) { + if ((result == KGpgSignTransactionHelper::TS_ALREADY_SIGNED) && (m_flags & KGpgCaff::IgnoreAlreadySigned)) { + m_alreadyIds << m_allids.takeFirst(); + checkNextLoop(); + } else { + abortOperation(result); + } + return; + } + + const KGpgSignableNode *uid = m_allids.first(); + + // if there is no email address we can't send this out anyway, so don't bother. + // could be improved: if this is the only selected uid from this key go and select + // a proper mail address to send this to + if (uid->getEmail().isEmpty()) { + m_allids.removeFirst(); + checkNextLoop(); + } + + const KGpgKeyNode *key = uid->getKeyNode(); + + int uidnum; + + if (uid == key) { + uidnum = -1; + } else { + uidnum = -uid->getId().toInt(); + } + + KGpgDelUid::RemoveMode removeMode; + switch (KGpgSettings::mailUats()) { + case 0: + removeMode = KGpgDelUid::RemoveWithEmail; + break; + case 1: + if (uid == key) { + removeMode = KGpgDelUid::RemoveWithEmail; + } else { + // check if this is the first uid with email address + const KGpgSignableNode *otherUid; + int index = 1; + removeMode = KGpgDelUid::RemoveAllOther; + + while ( (otherUid = key->getUid(index++)) != NULL) { + if (otherUid == uid) { + removeMode = KGpgDelUid::RemoveWithEmail; + break; + } + if (!otherUid->getEmail().isEmpty()) + break; + } + } + break; + case 2: + removeMode = KGpgDelUid::RemoveAllOther; + break; + default: + Q_ASSERT(0); + return; + } + + KGpgDelUid *deluid = new KGpgDelUid(this, key, uidnum, removeMode); + + deluid->setGnuPGHome(m_tempdir->name()); + + connect(deluid, SIGNAL(done(int)), SLOT(slotDelUidFinished(int))); + + deluid->start(); +} + +void +KGpgCaffPrivate::slotDelUidFinished(int result) +{ + sender()->deleteLater(); + + const KGpgSignableNode *uid = m_allids.first(); + const KGpgKeyNode *key = uid->getKeyNode(); + + if (result != KGpgTransaction::TS_OK) { + // it's no error if we tried to delete all other ids but there is no other id + if ((uid != key) || (result != KGpgDelUid::TS_NO_SUCH_UID)) { + abortOperation(result); + return; + } + } + + QStringList expOptions(QLatin1String( "--export-options" )); + expOptions << QLatin1String( "export-attribute" ); + + KGpgExport *exp = new KGpgExport(this, QStringList(key->getId()), expOptions); + + exp->setGnuPGHome(m_tempdir->name()); + + connect(exp, SIGNAL(done(int)), SLOT(slotExportFinished(int))); + + exp->start(); +} + +void +KGpgCaffPrivate::slotExportFinished(int result) +{ + sender()->deleteLater(); + + if (result != KGpgTransaction::TS_OK) { + abortOperation(result); + return; + } + + const KGpgSignableNode *uid = m_allids.first(); + const KGpgKeyNode *key = uid->getKeyNode(); + + KGpgExport *exp = qobject_cast(sender()); + Q_ASSERT(exp != NULL); + + QString body = KGpgSettings::emailTemplate(); + body.replace(QLatin1Char( '%' ) + i18nc("Email template placeholder for key id", "KEYID") + QLatin1Char( '%' ), key->getId()); + body.replace(QLatin1Char( '%' ) + i18nc("Email template placeholder for key id", "UIDNAME") + QLatin1Char( '%' ), uid->getNameComment()); + + body += QLatin1Char( '\n' ) + QLatin1String( exp->getOutputData() ); + + KGpgEncrypt *enc = new KGpgEncrypt(this, QStringList(key->getId()), body, KGpgEncrypt::AsciiArmored | KGpgEncrypt::AllowUntrustedEncryption); + + // Set the home directory to make sure custom encrypt options + // as well as the "always encrypt to" setting are not honored. + enc->setGnuPGHome(m_tempdir->name()); + + connect(enc, SIGNAL(done(int)), SLOT(slotTextEncrypted(int))); + + enc->start(); +} + +void +KGpgCaffPrivate::slotTextEncrypted(int result) +{ + sender()->deleteLater(); + + switch (result) { + case KGpgTransaction::TS_OK: { + KGpgEncrypt *enc = qobject_cast(sender()); + Q_ASSERT(enc != NULL); + + const QString text = enc->encryptedText().join(QLatin1String("\n")); + + const KGpgSignableNode *uid = m_allids.takeFirst(); + + const QString email = uid->getEmail(); + const QString keyid = uid->getKeyNode()->getId(); + + KToolInvocation::invokeMailer(email, QString(), QString(), + i18nc("%1 is 64 bit key id (in hex), text is used as email subject", "Your key %1", keyid), + text); + break; + } + default: + abortOperation(result); + break; + case KGpgTransaction::TS_USER_ABORTED: + m_allids.clear(); + break; + } + + checkNextLoop(); +} + +KGpgCaff::KGpgCaff(QObject *parent, const KGpgSignableNode::List &ids, const QStringList &signids, + const int checklevel, const OperationFlags flags) + : QObject(parent), + d_ptr(new KGpgCaffPrivate(this, ids, signids, flags, static_cast(checklevel))) +{ +} + +void +KGpgCaff::run() +{ + Q_D(KGpgCaff); + + d->reexportKey(d->m_allids.first()); +} + +#include "caff.moc" +#include "caff_p.moc" diff --git a/kgpg/caff.h b/kgpg/caff.h new file mode 100644 index 00000000..6ff1a5fa --- /dev/null +++ b/kgpg/caff.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009,2010,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _KGPGCAFF_H +#define _KGPGCAFF_H + +#include "core/KGpgSignableNode.h" + +#include + +class QStringList; + +class KGpgCaffPrivate; + +class KGpgCaff : public QObject { + Q_OBJECT + + KGpgCaffPrivate * const d_ptr; + Q_DECLARE_PRIVATE(KGpgCaff) + Q_DISABLE_COPY(KGpgCaff) + +public: + enum OperationFlags { + DefaultMode = 0, ///< use none of the other flags + IgnoreAlreadySigned = 1 ///< uids that are already signed will not be mailed again + }; + + /** + * @brief create a new object to sign and mail key ids + * @param parent parent object + * @param ids list of keys to sign + * @param signids secret key ids to sign @ids with + * @param flags control flags + */ + KGpgCaff(QObject *parent, const KGpgSignableNode::List &ids, const QStringList &signids, + const int checklevel = 0, const OperationFlags flags = DefaultMode); + +public slots: + void run(); + +signals: + void done(); + void aborted(); +}; + +#endif /* _KGPGCAFF_H */ diff --git a/kgpg/caff_p.h b/kgpg/caff_p.h new file mode 100644 index 00000000..50e8f53b --- /dev/null +++ b/kgpg/caff_p.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2009,2010,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _KGPGCAFF_P_H +#define _KGPGCAFF_P_H + +#include "caff.h" +#include "core/KGpgSignableNode.h" +#include "transactions/kgpgsigntransactionhelper.h" + +#include +#include +#include +#include +#include + +class KTempDir; + +class KGpgCaffPrivate : public QObject { + Q_OBJECT + + KGpgCaff * const q_ptr; + Q_DECLARE_PUBLIC(KGpgCaff) + Q_DISABLE_COPY(KGpgCaffPrivate) + + QScopedPointer m_tempdir; + QStringList m_signers; + QString m_secringfile; + const KGpgCaff::OperationFlags m_flags; + const KGpgSignTransactionHelper::carefulCheck m_checklevel; + + void reexportKey(const KGpgSignableNode *node); + void abortOperation(int result); + void checkNextLoop(); +public: + KGpgCaffPrivate(KGpgCaff *parent, const KGpgSignableNode::List &ids, const QStringList &signers, + const KGpgCaff::OperationFlags flags, const KGpgSignTransactionHelper::carefulCheck checklevel); + ~KGpgCaffPrivate(); + + KGpgSignableNode::List m_allids; + KGpgSignableNode::const_List m_noEncIds; ///< keys without encryption capability that were skipped + KGpgSignableNode::const_List m_alreadyIds; ///< ids already signed + +private slots: + void slotSigningFinished(int result); + void slotDelUidFinished(int result); + void slotExportFinished(int result); + void slotTextEncrypted(int result); + void slotReimportDone(int result); +}; + +#endif /* _KGPGCAFF_P_H */ diff --git a/kgpg/conf_decryption.ui b/kgpg/conf_decryption.ui new file mode 100644 index 00000000..6db47c0c --- /dev/null +++ b/kgpg/conf_decryption.ui @@ -0,0 +1,73 @@ + + Decryption + + + + 0 + 0 + 388 + 233 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 21 + 155 + + + + + + + + <qt><b>Custom Decryption Command:</b><br /> +<p>This option allows the user to specify a custom command to be executed by GPG when decryption occurs. (This is recommended for advanced users only).</p></qt> + + + Custom decryption command: + + + false + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + +
diff --git a/kgpg/conf_encryption.cpp b/kgpg/conf_encryption.cpp new file mode 100644 index 00000000..0d46ecdd --- /dev/null +++ b/kgpg/conf_encryption.cpp @@ -0,0 +1,45 @@ +/*************************************************************************** + ------------------- + begin : Mon Jul 8 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "conf_encryption.h" + +Encryption::Encryption( QWidget* parent ) + : QWidget( parent ), Ui_Encryption() +{ + setupUi( this ); + connect(kcfg_EncryptFilesTo, SIGNAL(toggled(bool)), this,SLOT(encrypt_files_to_toggled(bool))); + connect(kcfg_AllowCustomEncryptionOptions, SIGNAL(toggled(bool)), this, SLOT(allow_custom_option_toggled(bool))); + connect(encrypt_to_always, SIGNAL(toggled(bool)), this, SLOT(encrypt_to_always_toggled(bool))); +} + +void Encryption::encrypt_to_always_toggled(bool isOn) +{ + always_key->setEnabled(isOn); +} + + +void Encryption::encrypt_files_to_toggled(bool isOn) +{ + file_key->setEnabled(isOn); +} + + +void Encryption::allow_custom_option_toggled(bool isOn) +{ + kcfg_CustomEncryptionOptions->setEnabled(isOn); +} + +#include "conf_encryption.moc" diff --git a/kgpg/conf_encryption.h b/kgpg/conf_encryption.h new file mode 100644 index 00000000..8b9c7422 --- /dev/null +++ b/kgpg/conf_encryption.h @@ -0,0 +1,39 @@ +/*************************************************************************** + ------------------- + begin : Mon Jul 8 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef CONF_ENCRYPTION_H +#define CONF_ENCRYPTION_H + +#include "ui_conf_encryption.h" + + +class Encryption : public QWidget, public Ui_Encryption +{ + Q_OBJECT + +public: + explicit Encryption( QWidget* parent = 0 ); + +public slots: + virtual void encrypt_to_always_toggled( bool ); + virtual void encrypt_files_to_toggled( bool ); + virtual void allow_custom_option_toggled( bool ); + +private: + +}; + +#endif diff --git a/kgpg/conf_encryption.ui b/kgpg/conf_encryption.ui new file mode 100644 index 00000000..34d6d489 --- /dev/null +++ b/kgpg/conf_encryption.ui @@ -0,0 +1,190 @@ + + + bj@altern.org + Encryption + + + + 0 + 0 + 421 + 280 + + + + Encryption + + + + 0 + + + + + + 0 + 0 + + + + <qt><b>ASCII armored encryption:</b> <br /> +<p>Checking this option outputs all encrypted files in a format that can be opened by a text editor and as such the output is suitable for placing in the body of an e-mail message.</p></qt> + + + ASCII armored encryption + + + + + + + <qt><b>Use *.pgp extension for encrypted files:</b><br /> +<p>Checking this option will append a .pgp extension to all encrypted files instead of a .gpg extension. This option will maintain compatibility with users of PGP (Pretty Good Privacy) software.</p></qt> + + + Use *.pgp extension for encrypted files + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 16 + + + + + + + + <qt><b>Custom encryption command:</b><br /> +<p>When activated, an entry field will be shown in the key selection dialog, enabling you to enter a custom command for encryption. This option is recommended for experienced users only.</p></qt> + + + Custom encryption command: + + + + + + + false + + + + + + + false + + + + 0 + 0 + + + + + + + + false + + + + 0 + 0 + + + + + + + + <qt><b>Always encrypt with:</b><br /> +<p>This ensures all files/messages will also be encrypted with the chosen key. However, if the "Encrypt files with:" option is selected that chosen key will override the "Always encrypt with:" selection.</p></qt> + + + Always encrypt with: + + + + + + + <qt><b>Encrypt files with:</b><br /> +<p>Checking this option and selecting a key will force any file encryption operation to use the selected key. KGpg will not query for a recipient and the default key will be bypassed.</p></qt> + + + Encrypt files with: + + + + + + + <qt><b>Allow encryption with untrusted keys:</b><br /> +<p>When importing a public key, the key is usually marked as untrusted and as such cannot be used unless it is signed by the default key (thus making it 'trusted'). Checking this box enables any key to be used even if it is untrusted.</p></qt> + + + Allow encryption with untrusted keys + + + + + + + <qt><b>Allow untrusted keys as members of key groups:</b><br /><p>A key group allows simple encryption to multiple recipients at once. Similar to the <em>Allow encryption with untrusted keys</em> option this allows untrusted keys to become member of a key group.</p></qt> + + + Allow untrusted keys as members of key groups + + + + + + + <qt><b>Hide user ID:</b><br /> +<p>Checking this option will remove the keyid of the recipient from all encrypted packets. The advantage: traffic analysis of the encrypted packets cannot be performed as easily because the recipient is unknown. The disadvantage: the receiver of the encrypted packets is forced to try all secret keys before being able to decrypt the packets. This can be a lengthy process depending on the number of secret keys the receiver holds.</p></qt> + + + Hide user id + + + + + + + <qt><b>PGP 6 compatibility:</b><br /> +<p>Checking this option forces GnuPG to output encrypted packets that are as compliant with PGP (Pretty Good Privacy) 6 standards as possible thus allowing GnuPG users to inter operate with PGP 6 users.</p></qt> + + + PGP 6 compatibility + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+ + KComboBox + QComboBox +
kcombobox.h
+
+
+ + +
diff --git a/kgpg/conf_gpg.ui b/kgpg/conf_gpg.ui new file mode 100644 index 00000000..003d44d3 --- /dev/null +++ b/kgpg/conf_gpg.ui @@ -0,0 +1,188 @@ + + + GPGConf + + + + 0 + 0 + 592 + 343 + + + + <qt><b>Global Settings:</b><br /> +<p></p> +</qt> + + + + + + GnuPG Home + + + + + + + + <b>Home Location</b><p>This is the directory where GnuPG stores its configuration and the keyrings. If you have not changed it this is usually <em>~/.gnupg/</em></p> + + + Home location: + + + false + + + + + + + <b>Configuration File</b><p>This is the name of the configuration file in the directory specified above. The default is <em>gnupg.conf</em> while older versions of GnuPG used <em>options</em>.</p> + + + Configuration file: + + + false + + + + + + + + + + + true + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 40 + 20 + + + + + + + + true + + + true + + + + + + + Change... + + + + + + + + + + + + GnuPG Binary + + + + + + <b>Program path</b><p>This is the program that will be called for all GnuPG operations. The default of <em>gpg</em> will work on most systems.</p> + + + Program path: + + + false + + + + + + + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 40 + 20 + + + + + + + + + + + + + <b>Use GnuPG agent</b><p>The GnuPG agent stores the passwords for your secret keys in memory for a limited amount of time. If you use your secret key again while it is cached you do not have to enter it again. This is less secure than typing it every time.</p> + + + Use GnuPG agent + + + + + + + Qt::Vertical + + + + 569 + 181 + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + +
diff --git a/kgpg/conf_misc.ui b/kgpg/conf_misc.ui new file mode 100644 index 00000000..1178b058 --- /dev/null +++ b/kgpg/conf_misc.ui @@ -0,0 +1,386 @@ + + + MiscConf + + + + 0 + 0 + 680 + 411 + + + + + 0 + + + + + 0 + + + + Global Settings + + + + + + + + <qt><b>Start KGpg automatically at KDE startup:</b><br /> +<p>If checked KGpg will start automatically each time that KDE starts up.</p></qt> + + + Start KGpg automatically at login + + + false + + + + + + + + 0 + 0 + + + + <qt><b>Use mouse selection instead of clipboard:</b><br /> +<p>If checked, clipboard operations in KGpg will use the selection clipboard, that means highlighting a text to copy, and middle button (or right+left together) to paste. If this option is not checked, the clipboard will work with Key shortcuts (Ctrl-c, Ctrl-v).</p></qt> + + + Use mouse selection instead of clipboard + + + + + + + + 0 + 0 + + + + <qt><b>Display warning before creating temporary files:</b><br /> +<p></p></qt> + + + Display warning before creating temporary files +(only occurs on remote files operations) + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Applet && Menus + + + + + + Konqueror Service Menus + + + + + + + + + 0 + 0 + + + + <qt><b>Sign file service menu:</b><br /> +<p></p> +</qt> + + + Sign file service menu: + + + false + + + + + + + + 0 + 0 + + + + + Disable + + + + + Enable with All Files + + + + + + + + + + + + + 0 + 0 + + + + <qt><b>Decrypt file service menu:</b><br /> +<p></p> +</qt> + + + Decrypt file service menu: + + + false + + + + + + + + Disable + + + + + Enable with All Files + + + + + Enable with Encrypted Files + + + + + + + + + + + + + System Tray Applet + + + + + + <qt><b>Show system tray icon:</b><br /> +<p>If checked KGpg will minimize to an icon in the system tray.</p></qt> + + + Show system tray icon + + + true + + + + + + + + + Left mouse click opens: + + + false + + + + + + + + 0 + 0 + + + + + Key Manager + + + + + Editor + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Editor + + + + + + + + Recent files + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Key Signing + + + + + + + + Here you can define how User Ids that do not contain an email address (like Photo Ids) are sent to the key owner. You can choose to either send them with every other User Id you signed, only with the first Id you signed or to not send them at all. + + + Mailing of User Ids without Email Addresses + + + + + + + + Send with every Email + + + + + Send only with first Email + + + + + Do not send + + + + + + + + + + Email template + + + + + + This is the text of the email sent by the "Sign and Mail User ID" action. + +The placeholders surrounded by the percent signs (like %KEYID%) will be replaced with the corresponding text for every single mail. + + + + + + + + + + + + + + + KComboBox + QComboBox +
kcombobox.h
+
+ + KTabWidget + QTabWidget +
ktabwidget.h
+ 1 +
+ + KTextEdit + QTextEdit +
ktextedit.h
+
+
+ + +
diff --git a/kgpg/conf_servers.ui b/kgpg/conf_servers.ui new file mode 100644 index 00000000..11ae1d28 --- /dev/null +++ b/kgpg/conf_servers.ui @@ -0,0 +1,105 @@ + + ServerConf + + + + 0 + 0 + 642 + 294 + + + + + + + + + + + + + + <b>INFORMATION</b>: +Only the default server will be stored in GnuPG's configuration file, +all others will be stored for use by KGpg only. + + + true + + + + + + + Honor HTTP proxy when available + + + + + + + + + + + false + + + &Set as Default + + + + + + + &Add... + + + + + + + false + + + &Delete + + + + + + + &Edit... + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + KPushButton + QPushButton +
kpushbutton.h
+
+
+ + +
diff --git a/kgpg/conf_ui2.ui b/kgpg/conf_ui2.ui new file mode 100644 index 00000000..0be97686 --- /dev/null +++ b/kgpg/conf_ui2.ui @@ -0,0 +1,312 @@ + + UIConf + + + + 0 + 0 + 833 + 472 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + Key Colors + + + + + + + + + + Ultimately trusted keys: + + + false + + + + + + + Trusted keys: + + + false + + + + + + + Marginally trusted keys: + + + false + + + + + + + Expired keys: + + + false + + + + + + + Revoked keys: + + + false + + + + + + + Unknown keys: + + + false + + + + + + + Disabled keys: + + + false + + + + + + + + + + + + + + + 68 + 0 + 255 + + + + + + + + + + + + 144 + 255 + 0 + + + + + + + + + + + + 255 + 255 + 0 + + + + + + + + + + + + 150 + 150 + 150 + + + + + + + + + + + + 30 + 30 + 30 + + + + + + + + + + + + + + + + + + + 172 + 0 + 0 + + + + + + + + + + + + Qt::Vertical + + + + 402 + 121 + + + + + + + + + Editor Font + + + + + Key List + + + + + + Sort Order + + + + + + Email: + + + kcfg_EmailSorting + + + + + + + + Left to right, account first + + + + + Right to left, TLD first + + + + + Right to left, domain first + + + + + Right to left, FQDN first + + + + + + + + + + + Qt::Vertical + + + + 20 + 351 + + + + + + + + + + + + + KColorButton + QPushButton +
kcolorbutton.h
+
+ + KTabWidget + QTabWidget +
ktabwidget.h
+
+
+ + kcolorbutton.h + + + +
diff --git a/kgpg/core/KGpgExpandableNode.cpp b/kgpg/core/KGpgExpandableNode.cpp new file mode 100644 index 00000000..0e34bfa0 --- /dev/null +++ b/kgpg/core/KGpgExpandableNode.cpp @@ -0,0 +1,88 @@ +/* Copyright 2008,2009,2012 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgExpandableNode.h" + +#include "kgpgsettings.h" +#include "core/convert.h" +#include "model/kgpgitemmodel.h" + +#include + +KGpgExpandableNode::KGpgExpandableNode(KGpgExpandableNode *parent) + : KGpgNode(parent) +{ + if (parent != NULL) + parent->children.append(this); +} + +KGpgExpandableNode::~KGpgExpandableNode() +{ + for (int i = children.count() - 1; i >= 0; i--) + delete children[i]; +} + +KGpgNode * +KGpgExpandableNode::getChild(const int index) const +{ + if ((index < 0) || (index > children.count())) + return NULL; + return children.at(index); +} + +int +KGpgExpandableNode::getChildCount() +{ + if (children.count() == 0) + readChildren(); + + return children.count(); +} + +bool +KGpgExpandableNode::hasChildren() const +{ + return (children.count() != 0); +} + +bool +KGpgExpandableNode::wasExpanded() const +{ + return (children.count() != 0); +} + +const +KGpgNode::List & +KGpgExpandableNode::getChildren() const +{ + return children; +} + +int +KGpgExpandableNode::getChildIndex(KGpgNode *node) const +{ + return children.indexOf(node); +} + +void +KGpgExpandableNode::deleteChild(KGpgNode *child) +{ + children.removeAll(child); +} + +#include "KGpgExpandableNode.moc" diff --git a/kgpg/core/KGpgExpandableNode.h b/kgpg/core/KGpgExpandableNode.h new file mode 100644 index 00000000..39f351da --- /dev/null +++ b/kgpg/core/KGpgExpandableNode.h @@ -0,0 +1,85 @@ +/* Copyright 2008,2009,2012 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGEXPANDABLENODE_H +#define KGPGEXPANDABLENODE_H + +#include "KGpgNode.h" + +class KGpgSubkeyNode; +class KGpgRefNode; + +/** + * @brief The abstract base class for all classes that may have child objects + * + * Every class that represents something in the keyring that may have + * child objects inherits from this class. That does not mean that every + * child object always has children, but every child \em may have children. + */ +class KGpgExpandableNode : public KGpgNode +{ + Q_OBJECT + + friend class KGpgRefNode; + friend class KGpgSubkeyNode; +protected: + KGpgNode::List children; + + /** + * reimplemented in every base class to read in the child data + * + * This allows the child objects to delay the loading of the + * child objects until they are really needed to avoid time + * consuming operations for data never used. + */ + virtual void readChildren() = 0; + + explicit KGpgExpandableNode(KGpgExpandableNode *parent = NULL); +public: + virtual ~KGpgExpandableNode(); + + /** + * check if there are any child nodes + * + * The default implementation returns true if any child nodes were loaded. + * This may be reimplemented by child classes so they can indicate that + * there are child nodes before actually loading them. + * + * This method indicates if there are children if this node is expanded. + * In contrast wasExpanded() will only return true if the child nodes + * are actually present in memory. + */ + virtual bool hasChildren() const; + /** + * check if there are any child nodes present in memory + * + * Returns true if any child nodes were loaded. + * + * This method indicates if the children of this node are already loaded + * into memory. In contrast hasChildren() may return true even if the child + * objects are not present in memory. + */ + virtual bool wasExpanded() const; + virtual int getChildCount(); + virtual const KGpgNode::List &getChildren() const; + virtual KGpgNode *getChild(const int index) const; + virtual int getChildIndex(KGpgNode *node) const; + virtual void deleteChild(KGpgNode *child); +}; + +#endif /* KGPGEXPANDABLENODE_H */ diff --git a/kgpg/core/KGpgGroupMemberNode.cpp b/kgpg/core/KGpgGroupMemberNode.cpp new file mode 100644 index 00000000..e7a9f974 --- /dev/null +++ b/kgpg/core/KGpgGroupMemberNode.cpp @@ -0,0 +1,100 @@ +/* Copyright 2008,2009 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgGroupMemberNode.h" + +#include + +#include "KGpgGroupNode.h" +#include "KGpgKeyNode.h" + +KGpgGroupMemberNode::KGpgGroupMemberNode(KGpgGroupNode *parent, const QString &k) + : KGpgRefNode(parent, k) +{ +} + +KGpgGroupMemberNode::KGpgGroupMemberNode(KGpgGroupNode *parent, KGpgKeyNode *k) + : KGpgRefNode(parent, k) +{ +} + +KGpgGroupMemberNode::~KGpgGroupMemberNode() +{ +} + +KgpgKeyTrust +KGpgGroupMemberNode::getTrust() const +{ + if (m_keynode != NULL) + return m_keynode->getTrust(); + return KgpgCore::TRUST_NOKEY; +} + +KgpgItemType +KGpgGroupMemberNode::getType() const +{ + if (m_keynode != NULL) + return m_keynode->getType() | KgpgCore::ITYPE_GROUP; + return KgpgCore::ITYPE_PUBLIC | KgpgCore::ITYPE_GROUP; +} + +QString +KGpgGroupMemberNode::getSize() const +{ + if (m_keynode != NULL) + return m_keynode->getSize(); + return QString(); +} + +QDateTime +KGpgGroupMemberNode::getExpiration() const +{ + if (m_keynode != NULL) + return m_keynode->getExpiration(); + return QDateTime(); +} + +QDateTime +KGpgGroupMemberNode::getCreation() const +{ + if (m_keynode != NULL) + return m_keynode->getCreation(); + return QDateTime(); +} + +unsigned int +KGpgGroupMemberNode::getSignKeySize() const +{ + if (m_keynode != NULL) + return m_keynode->getSignKeySize(); + return 0; +} + +unsigned int +KGpgGroupMemberNode::getEncryptionKeySize() const +{ + if (m_keynode != NULL) + return m_keynode->getEncryptionKeySize(); + return 0; +} + +KGpgGroupNode * +KGpgGroupMemberNode::getParentKeyNode() const +{ + return m_parent->toGroupNode(); +} diff --git a/kgpg/core/KGpgGroupMemberNode.h b/kgpg/core/KGpgGroupMemberNode.h new file mode 100644 index 00000000..2a456efa --- /dev/null +++ b/kgpg/core/KGpgGroupMemberNode.h @@ -0,0 +1,61 @@ +/* Copyright 2008,2009 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGGROUPMEMBERNODE_H +#define KGPGGROUPMEMBERNODE_H + +#include "KGpgRefNode.h" + +#include +#include "kgpgkey.h" + +using namespace KgpgCore; + +class KGpgKeyNode; +class KGpgGroupNode; + +/** + * @brief A member of a GnuPG group + */ +class KGpgGroupMemberNode : public KGpgRefNode +{ +public: + explicit KGpgGroupMemberNode(KGpgGroupNode *parent, const QString &k); + explicit KGpgGroupMemberNode(KGpgGroupNode *parent, KGpgKeyNode *k); + virtual ~KGpgGroupMemberNode(); + + virtual KgpgCore::KgpgKeyTrust getTrust() const; + virtual KgpgCore::KgpgItemType getType() const; + virtual QString getSize() const; + virtual QDateTime getExpiration() const; + virtual QDateTime getCreation() const; + virtual KGpgGroupNode *getParentKeyNode() const; + + /** + * Returns the size of the signing key. + * @return signing key size in bits + */ + virtual unsigned int getSignKeySize() const; + /** + * Returns the size of the first encryption subkey. + * @return encryption key size in bits + */ + virtual unsigned int getEncryptionKeySize() const; +}; + +#endif /* KGPGGROUPMEMBERNODE_H */ diff --git a/kgpg/core/KGpgGroupNode.cpp b/kgpg/core/KGpgGroupNode.cpp new file mode 100644 index 00000000..c161d8c2 --- /dev/null +++ b/kgpg/core/KGpgGroupNode.cpp @@ -0,0 +1,277 @@ +/* Copyright 2008,2009,2010,2012 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgGroupNode.h" + +#include "KGpgGroupMemberNode.h" +#include "KGpgRootNode.h" +#include "kgpgsettings.h" + +#include +#include +#include +#include +#include + +class KGpgGroupNodePrivate { +public: + KGpgGroupNodePrivate(const QString &name); + + QString m_name; + + /** + * @brief find the line that defines this group in the configuration + * @param conffile file object (will be initialized) + * @param stream text stream (will be initialized and connected to conffile) + * @param lines the lines found in conffile (will be filled) + * @return the index in lines of the entry defining this group + * @retval -1 no entry defining this group was found + * + * stream will be positioned at the beginning. + */ + int findGroupEntry(QFile &conffile, QTextStream &stream, QStringList &lines); + + static const QRegExp &groupPattern(); + static const QString &groupTag(); +}; + +KGpgGroupNodePrivate::KGpgGroupNodePrivate(const QString &name) + : m_name(name) +{ +} + +int +KGpgGroupNodePrivate::findGroupEntry(QFile &conffile, QTextStream &stream, QStringList &lines) +{ + conffile.setFileName(KGpgSettings::gpgConfigPath()); + + if (!conffile.exists()) + return -1; + + if (!conffile.open(QIODevice::ReadWrite)) + return -1; + + stream.setDevice(&conffile); + int index = -1; + int i = -1; + + while (!stream.atEnd()) { + const QString rawLine = stream.readLine(); + i++; + QString parsedLine = rawLine.simplified().section(QLatin1Char('#'), 0, 0); + + if (groupPattern().exactMatch(parsedLine)) { + // remove "group " + parsedLine = parsedLine.remove(0, 6); + if (parsedLine.startsWith(m_name)) { + if (parsedLine.mid(m_name.length()).simplified().startsWith(QLatin1Char('='))) { + if (index >= 0) { + // multiple definitions of the same group, drop the second one + continue; + } else { + index = i; + } + } + } + } + + lines << rawLine; + } + + stream.seek(0); + + return index; +} + +const QRegExp & +KGpgGroupNodePrivate::groupPattern() +{ + static const QRegExp groupre(QLatin1String("^group [^ ]+ ?= ?([0-9a-fA-F]{8,} ?)*$")); + + return groupre; +} + +const QString & +KGpgGroupNodePrivate::groupTag() +{ + static const QString grouptag(QLatin1String("group ")); + + return grouptag; +} + +KGpgGroupNode::KGpgGroupNode(KGpgRootNode *parent, const QString &name, const QStringList &members) + : KGpgExpandableNode(parent), + d_ptr(new KGpgGroupNodePrivate(name)) +{ + foreach (const QString &id, members) + new KGpgGroupMemberNode(this, id); + + parent->m_groups++; +} + +KGpgGroupNode::KGpgGroupNode(KGpgRootNode *parent, const QString &name, const KGpgKeyNode::List &members) + : KGpgExpandableNode(parent), + d_ptr(new KGpgGroupNodePrivate(name)) +{ + Q_ASSERT(members.count() > 0); + + foreach (KGpgKeyNode *nd, members) + new KGpgGroupMemberNode(this, nd); + + parent->m_groups++; +} + +KGpgGroupNode::~KGpgGroupNode() +{ + KGpgRootNode *root = m_parent->toRootNode(); + + if (root != NULL) + root->m_groups--; +} + +KgpgCore::KgpgItemType +KGpgGroupNode::getType() const +{ + return ITYPE_GROUP; +} + +QString +KGpgGroupNode::getName() const +{ + const Q_D(KGpgGroupNode); + + return d->m_name; +} + +QString +KGpgGroupNode::getSize() const +{ + return i18np("1 key", "%1 keys", children.count()); +} + +void +KGpgGroupNode::readChildren() +{ +} + +void +KGpgGroupNode::rename(const QString &newName) +{ + Q_D(KGpgGroupNode); + + QFile conffile; + QTextStream t; + QStringList lines; + int index = d->findGroupEntry(conffile, t, lines); + + // check if file opening failed + if (!t.device()) + return; + + if (index < 0) { + kDebug(2100) << "Group " << d->m_name << " not renamed, group does not exist"; + return; + } + + // 6 = groupTag().length() + const QString values = lines[index].simplified().mid(6 + d->m_name.length()); + lines[index] = d->groupTag() + newName + QLatin1Char(' ') + values; + + conffile.resize(0); + t << lines.join(QLatin1String("\n")) + QLatin1Char('\n'); + + d->m_name = newName; +} + +void +KGpgGroupNode::saveMembers() +{ + Q_D(KGpgGroupNode); + + QFile conffile; + QTextStream t; + QStringList lines; + int index = d->findGroupEntry(conffile, t, lines); + + // check if file opening failed + if (!t.device()) + return; + + QStringList memberIds; + + for (int j = getChildCount() - 1; j >= 0; j--) + memberIds << getChild(j)->toGroupMemberNode()->getId(); + + const QString groupEntry = d->groupTag() + d->m_name + QLatin1String(" = ") + + memberIds.join(QLatin1String(" ")); + + if (index >= 0) + lines[index] = groupEntry; + else + lines << groupEntry; + + conffile.resize(0); + t << lines.join(QLatin1String("\n")) + QLatin1Char('\n'); +} + +void +KGpgGroupNode::remove() +{ + Q_D(KGpgGroupNode); + + QFile conffile; + QTextStream t; + QStringList lines; + int index = d->findGroupEntry(conffile, t, lines); + + // check if file opening failed + if (!t.device()) + return; + + if (index < 0) + return; + + lines.removeAt(index); + conffile.resize(0); + t << lines.join(QLatin1String("\n")) + QLatin1Char('\n'); +} + +QStringList +KGpgGroupNode::readGroups() +{ + QStringList groups; + QFile qfile(KGpgSettings::gpgConfigPath()); + + if (!qfile.exists() || !qfile.open(QIODevice::ReadOnly)) + return groups; + + QTextStream t(&qfile); + + while (!t.atEnd()) { + QString line = t.readLine().simplified().section(QLatin1Char('#'), 0, 0); + if (!KGpgGroupNodePrivate::groupPattern().exactMatch(line)) + continue; + + // remove the "group " at the start + line.remove(0, 6); + // transform it in a simple space separated list + groups.append(line.replace(QLatin1Char('='), QLatin1Char(' ')).simplified()); + } + + return groups; +} diff --git a/kgpg/core/KGpgGroupNode.h b/kgpg/core/KGpgGroupNode.h new file mode 100644 index 00000000..5aca837e --- /dev/null +++ b/kgpg/core/KGpgGroupNode.h @@ -0,0 +1,84 @@ +/* Copyright 2008,2009,2010,2012 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGGROUPNODE_H +#define KGPGGROUPNODE_H + +#include "KGpgExpandableNode.h" +#include "KGpgKeyNode.h" + +class QString; +class QStringList; + +class KGpgGroupNodePrivate; + +/** + * @brief A GnuPG group of public keys + */ +class KGpgGroupNode : public KGpgExpandableNode +{ +private: + KGpgGroupNodePrivate * const d_ptr; + Q_DECLARE_PRIVATE(KGpgGroupNode) + Q_DISABLE_COPY(KGpgGroupNode) + +protected: + virtual void readChildren(); + +public: + KGpgGroupNode(KGpgRootNode *parent, const QString &name, const QStringList &members); + KGpgGroupNode(KGpgRootNode *parent, const QString &name, const KGpgKeyNode::List &members); + virtual ~KGpgGroupNode(); + + virtual KgpgCore::KgpgItemType getType() const; + /** + * Return size of group + * + * @return the number of keys in this group + */ + virtual QString getSize() const; + virtual QString getName() const; + + /** + * Rename this group node + * + * @param newName new name of the group + */ + void rename(const QString &newName); + + /** + * Write the current members to GnuPG config file + */ + void saveMembers(); + + /** + * Remove this group from the GnuPG config file + */ + void remove(); + + /** + * @brief get all groups from GnuPG config file + * @return list of groups names and their keys + * + * The strings are themself space separated list. The first entry is the + * group name, the others are the keys inside + */ + static QStringList readGroups(); +}; + +#endif /* KGPGGROUPNODE_H */ diff --git a/kgpg/core/KGpgKeyNode.cpp b/kgpg/core/KGpgKeyNode.cpp new file mode 100644 index 00000000..10671a00 --- /dev/null +++ b/kgpg/core/KGpgKeyNode.cpp @@ -0,0 +1,369 @@ +/* Copyright 2008,2009,2010,2012,2013,2014 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgKeyNode.h" + +#include "KGpgGroupMemberNode.h" +#include "KGpgRootNode.h" +#include "KGpgSubkeyNode.h" +#include "KGpgUatNode.h" +#include "KGpgUidNode.h" +#include "convert.h" +#include "kgpginterface.h" +#include "kgpgsettings.h" +#include "model/kgpgitemmodel.h" + +#include + +KGpgKeyNode::KGpgKeyNode(KGpgRootNode *parent, const KgpgCore::KgpgKey &k) + : KGpgSignableNode(parent), + m_key(new KgpgCore::KgpgKey(k)), + m_signs(0) +{ +} + +KGpgKeyNode::~KGpgKeyNode() +{ + foreach (KGpgRefNode *nd, m_refs) { + nd->unRef(m_parent->toRootNode()); + } +} + +KgpgCore::KgpgItemType +KGpgKeyNode::getType() const +{ + return getType(m_key); +} + +bool +KGpgKeyNode::hasChildren() const +{ + return true; +} + +KgpgCore::KgpgItemType +KGpgKeyNode::getType(const KgpgCore::KgpgKey *k) +{ + if (k->secret()) + return KgpgCore::ITYPE_PAIR; + + return KgpgCore::ITYPE_PUBLIC; +} + +KgpgCore::KgpgKeyTrust +KGpgKeyNode::getTrust() const +{ + return m_key->trust(); +} + +const QString & +KGpgKeyNode::getFingerprint() const +{ + return m_key->fingerprint(); +} + +QString +KGpgKeyNode::getSize() const +{ + return i18nc("size of signing key / size of encryption key", + "%1 / %2", QString::number(getSignKeySize()), + QString::number(getEncryptionKeySize())); +} + +QString +KGpgKeyNode::getName() const +{ + return m_key->name(); +} + +QString +KGpgKeyNode::getEmail() const +{ + return m_key->email(); +} + +QDateTime +KGpgKeyNode::getExpiration() const +{ + return m_key->expirationDate(); +} + +QDateTime +KGpgKeyNode::getCreation() const +{ + return m_key->creationDate(); +} + +QString +KGpgKeyNode::getId() const +{ + return m_key->fullId(); +} + +KGpgKeyNode * +KGpgKeyNode::getKeyNode(void) +{ + return this; +} + +bool +KGpgKeyNode::isSecret() const +{ + return m_key->secret(); +} + +const KGpgKeyNode * +KGpgKeyNode::getKeyNode(void) const +{ + return this; +} + +QString +KGpgKeyNode::getBeautifiedFingerprint() const +{ + static const QChar space = QLatin1Char(' '); + QString fingervalue = m_key->fingerprint(); + int len = fingervalue.length(); + if ((len > 0) && (len % 4 == 0)) + for (int n = 0; 4 * (n + 1) < len; n++) + fingervalue.insert(5 * n + 4, space); + return fingervalue; +} + +QString +KGpgKeyNode::getComment() const +{ + return m_key->comment(); +} + +void +KGpgKeyNode::readChildren() +{ + KgpgInterface::readSignatures(this); + + m_signs = 0; + foreach(KGpgNode *n, children) + if (n->getType() == ITYPE_SIGN) + m_signs++; +} + +QString +KGpgKeyNode::getSignCount() const +{ + if (!wasExpanded()) + return QString(); + return i18np("1 signature", "%1 signatures", m_signs); +} + +KgpgKey * +KGpgKeyNode::copyKey() const +{ + return new KgpgKey(*m_key); +} + +void +KGpgKeyNode::setKey(const KgpgKey &key) +{ + Q_ASSERT(m_key->fingerprint() == key.fingerprint()); + delete m_key; + + for (int i = 0; i < children.count(); i++) + delete children.at(i); + children.clear(); + + m_key = new KgpgKey(key); +} + +const KgpgKey * +KGpgKeyNode::getKey() const +{ + return m_key; +} + +unsigned int +KGpgKeyNode::getSignKeySize() const +{ + return m_key->size(); +} + +unsigned int +KGpgKeyNode::getEncryptionKeySize() const +{ + return m_key->encryptionSize(); +} + +void +KGpgKeyNode::addRef(KGpgRefNode *node) +{ + Q_ASSERT(m_refs.indexOf(node) == -1); + m_refs.append(node); +} + +void +KGpgKeyNode::delRef(KGpgRefNode *node) +{ + Q_ASSERT(m_refs.indexOf(node) != -1); + m_refs.removeOne(node); + Q_ASSERT(m_refs.indexOf(node) == -1); +} + +QList +KGpgKeyNode::getGroups(void) const +{ + QList ret; + + foreach (KGpgGroupMemberNode *gnd, getGroupRefs()) + ret.append(gnd->getParentKeyNode()); + + return ret; +} + +QList +KGpgKeyNode::getRefsOfType(const KgpgItemType &type) const +{ + QList ret; + + foreach (KGpgRefNode *nd, m_refs) { + if (nd->getType() & type) + ret.append(nd); + } + + return ret; +} + +QList +KGpgKeyNode::getGroupRefs(void) const +{ + QList ret; + + foreach (KGpgRefNode *rn, getRefsOfType(KgpgCore::ITYPE_GROUP)) + ret.append(rn->toGroupMemberNode()); + + return ret; +} + +KGpgSignNode::List +KGpgKeyNode::getSignRefs(void) const +{ + KGpgSignNode::List ret; + + QList refs = getRefsOfType(KgpgCore::ITYPE_SIGN); + + foreach (KGpgRefNode *rn, refs) + ret.append(rn->toSignNode()); + + return ret; +} + +KGpgSignNode::List +KGpgKeyNode::getSignatures(const bool subkeys) const +{ + KGpgSignNode::List ret = KGpgSignableNode::getSignatures(); + + if (!subkeys) + return ret; + + foreach (KGpgNode *child, children) { + KGpgSignNode::List tmp; + + switch (child->getType()) { + case KgpgCore::ITYPE_UID: + case KgpgCore::ITYPE_UAT: + tmp = child->toSignableNode()->getSignatures(); + break; + default: + continue; + } + + foreach (KGpgSignNode *sn, tmp) { + bool found = false; + const QString snid(sn->getId()); + + foreach (const KGpgSignNode *retsn, ret) { + found = (retsn->getId() == snid); + if (found) + break; + } + + if (!found) + ret << sn; + } + } + + return ret; +} + +const KGpgSignableNode * +KGpgKeyNode::getUid(const unsigned int index) const +{ + Q_ASSERT(index > 0); + + if (index == 1) + return this; + + const QString idxstr(QString::number(index)); + + foreach (const KGpgNode *child, children) { + KGpgSignNode::List tmp; + + switch (child->getType()) { + case KgpgCore::ITYPE_UID: + case KgpgCore::ITYPE_UAT: + if (child->getId() == idxstr) + return child->toSignableNode(); + break; + default: + continue; + } + } + + return NULL; +} + +bool +KGpgKeyNode::compareId(const QString &other) const +{ + if (other.length() == m_key->fullId().length()) + return (other.compare(m_key->fullId(), Qt::CaseInsensitive) == 0); + + if (other.length() == m_key->fingerprint().length()) + return (other.compare(m_key->fingerprint(), Qt::CaseInsensitive) == 0); + + const QString comId = m_key->fullId().isEmpty() ? m_key->fingerprint() : m_key->fullId(); + + return (other.right(comId.length()).compare( + comId.right(other.length()), + Qt::CaseInsensitive) == 0); +} + +bool +KGpgKeyNode::canEncrypt() const +{ + return ((m_key->keytype() & KgpgCore::SKT_ENCRYPTION) != 0); +} + +void +KGpgKeyNode::expand() +{ + if (!wasExpanded()) + readChildren(); + + emit expanded(); +} + +#include "KGpgKeyNode.moc" diff --git a/kgpg/core/KGpgKeyNode.h b/kgpg/core/KGpgKeyNode.h new file mode 100644 index 00000000..ea0d363d --- /dev/null +++ b/kgpg/core/KGpgKeyNode.h @@ -0,0 +1,197 @@ +/* Copyright 2008,2009,2010,2012,2013 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGKEYNODE_H +#define KGPGKEYNODE_H + +#include "KGpgSignableNode.h" +#include "KGpgSignNode.h" + +#include "kgpgkey.h" + +class KGpgExpandableNode; +class KGpgRefNode; + +/** + * @brief A public key with or without corresponding secret key + */ +class KGpgKeyNode : public KGpgSignableNode +{ + Q_OBJECT + + friend class KGpgGroupMemberNode; + +private: + KgpgCore::KgpgKey *m_key; + int m_signs; + +protected: + virtual void readChildren(); + + QList m_refs; + QList getRefsOfType(const KgpgCore::KgpgItemType &type) const; + +public: + typedef QList List; + typedef QList ConstList; + + explicit KGpgKeyNode(KGpgRootNode *parent, const KgpgCore::KgpgKey &k); + virtual ~KGpgKeyNode(); + + virtual bool hasChildren() const; + + static KgpgCore::KgpgItemType getType(const KgpgCore::KgpgKey *k); + + virtual KgpgCore::KgpgItemType getType() const; + virtual KgpgCore::KgpgKeyTrust getTrust() const; + const QString &getFingerprint() const; + virtual QString getSize() const; + virtual QString getName() const; + virtual QString getEmail() const; + virtual QDateTime getExpiration() const; + virtual QDateTime getCreation() const; + virtual QString getId() const; + virtual KGpgKeyNode *getKeyNode(void); + virtual const KGpgKeyNode *getKeyNode(void) const; + /** + * @brief Return if this key has a private key + */ + bool isSecret() const; + /** + * @brief Print the full key fingerprint with spaces inserted + * + * For display purposes you normally don't want to print the full + * fingerprint as is because it's too many hex characters at once. + * This function returns the fingerprint in the format usually used + * for printing this out, i.e. with a space after each fourth hex + * character. + * + * @return the full fingerprint with spaces inserted + */ + QString getBeautifiedFingerprint() const; + virtual QString getComment() const; + /** + * @brief Return the number of signatures of the primary user id + * + * This is different from the number of children of this node as there + * is usually at least one subkey and there may also be additional + * user ids or attributes. This does not count the signatures to those + * slave objects, only the ones that are direct children of this node. + * + * @return the number of signatures to the primary user id + */ + virtual QString getSignCount() const; + /** + * @brief Creates a copy of the KgpgKey that belongs to this class + */ + virtual KgpgCore::KgpgKey *copyKey() const; + /** + * @brief Replaces the current key information with the new one. + * All sub-items (i.e. signatures, user ids ...) will be deleted. This must + * only be used when the id of both new and old key is the same. + */ + void setKey(const KgpgCore::KgpgKey &key); + /** + * @brief Returns a reference to the key used in this object. + * This allows direct access to the values of the key e.g. for KgpgKeyInfo. + */ + const KgpgCore::KgpgKey *getKey() const; + + /** + * @brief Returns the size of the signing key. + * @return signing key size in bits + */ + virtual unsigned int getSignKeySize() const; + /** + * @brief Returns the size of the first encryption subkey. + * @return encryption key size in bits + */ + virtual unsigned int getEncryptionKeySize() const; + /** + * @brief Notify this key that a KGpgRefNode now references this key. + * @param node object that takes the reference + */ + void addRef(KGpgRefNode *node); + /** + * @brief Remove a reference to this object + * @param node node that no longer has the reference + * + * Note that this must not be called as reply when this object + * emits updated(NULL) + */ + void delRef(KGpgRefNode *node); + /** + * @brief returns a list of all groups this key is member of + */ + QList getGroups(void) const; + /** + * @brief returns a list of all group member nodes that reference this key + */ + QList getGroupRefs(void) const; + /** + * @brief returns a list of all sign nodes that reference this key + */ + KGpgSignNode::List getSignRefs(void) const; + /** + * @brief returns a list of signatures to this key + * @param subkeys if signatures on subkeys should be included + */ + KGpgSignNode::List getSignatures(const bool subkeys) const; + /** + * @brief get the user id or user attribute with the given number + * @param index the index of the user id to return + * @return the requested subitem or NULL if that is not present + * + * User ids indexes are 1-based, so 0 is not a valid index. Passing + * 1 as index will return the object itself, representing the primary + * user id. + */ + const KGpgSignableNode *getUid(const unsigned int index) const; + + /** + * @brief compare the id of this node to the given other node + * @param other key id to compare to + * @return if ids are identical + * + * This handles different length of the id string. + */ + bool compareId(const QString &other) const; + + /** + * @brief return if this key can be used for encryption + */ + bool canEncrypt() const; + +Q_SIGNALS: + void expanded(); + +public slots: + /** + * @brief read all subitems + * + * This will read in all subitems (e.g. subkeys, signatures). When + * this is done the expanded() signal is emitted. The signal is emitted + * immediately if the key has been expanded before. + * + * This will not update the child items in case they are already present. + * Use KGpgItemModel::refreshKey() instead. + */ + void expand(); +}; + +#endif /* KGPGKEYNODE_H */ diff --git a/kgpg/core/KGpgNode.cpp b/kgpg/core/KGpgNode.cpp new file mode 100644 index 00000000..32a80aa2 --- /dev/null +++ b/kgpg/core/KGpgNode.cpp @@ -0,0 +1,335 @@ +/* Copyright 2008,2009,2012 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgNode.h" + +#include "KGpgGroupMemberNode.h" +#include "KGpgGroupNode.h" +#include "KGpgOrphanNode.h" +#include "KGpgRootNode.h" +#include "KGpgSubkeyNode.h" +#include "KGpgUatNode.h" +#include "KGpgUidNode.h" +#include "model/kgpgitemmodel.h" + +#include + +KGpgNode::KGpgNode(KGpgExpandableNode *parent) + : QObject(), m_parent(parent) +{ + if (parent == NULL) + m_model = NULL; + else + m_model = parent->m_model; +} + +KGpgNode::~KGpgNode() +{ + Q_ASSERT(m_model); + m_model->invalidateIndexes(this); + + if (m_parent != NULL) + m_parent->deleteChild(this); +} + +QString +KGpgNode::getNameComment() const +{ + if (getComment().isEmpty()) + return getName(); + else + return i18nc("Name of uid (comment)", "%1 (%2)", getName(), getComment()); +} + +KGpgExpandableNode * +KGpgNode::toExpandableNode() +{ + Q_ASSERT(((getType() & KgpgCore::ITYPE_GROUP) && !(getType() & KgpgCore::ITYPE_PAIR)) || + (getType() & (KgpgCore::ITYPE_PAIR | KgpgCore::ITYPE_SUB | KgpgCore::ITYPE_UID | KgpgCore::ITYPE_UAT))); + + return qobject_cast(this); +} + +const KGpgExpandableNode * +KGpgNode::toExpandableNode() const +{ + Q_ASSERT(((getType() & KgpgCore::ITYPE_GROUP) && !(getType() & KgpgCore::ITYPE_PAIR)) || + (getType() & (KgpgCore::ITYPE_PAIR | KgpgCore::ITYPE_SUB | KgpgCore::ITYPE_UID | KgpgCore::ITYPE_UAT))); + + return qobject_cast(this); +} + +KGpgSignableNode * +KGpgNode::toSignableNode() +{ + Q_ASSERT(getType() & (KgpgCore::ITYPE_PAIR | KgpgCore::ITYPE_SUB | KgpgCore::ITYPE_UID | KgpgCore::ITYPE_UAT)); + + return qobject_cast(this); +} + +const KGpgSignableNode * +KGpgNode::toSignableNode() const +{ + Q_ASSERT(getType() & (KgpgCore::ITYPE_PAIR | KgpgCore::ITYPE_SUB | KgpgCore::ITYPE_UID | KgpgCore::ITYPE_UAT)); + + return qobject_cast(this); +} + +KGpgKeyNode * +KGpgNode::toKeyNode() +{ + Q_ASSERT(getType() & KgpgCore::ITYPE_PAIR); + Q_ASSERT(!(getType() & KgpgCore::ITYPE_GROUP)); + + return qobject_cast(this); +} + +const KGpgKeyNode * +KGpgNode::toKeyNode() const +{ + Q_ASSERT(getType() & KgpgCore::ITYPE_PAIR); + Q_ASSERT(!(getType() & KgpgCore::ITYPE_GROUP)); + + return qobject_cast(this); +} + +KGpgRootNode * +KGpgNode::toRootNode() +{ + Q_ASSERT((m_parent == NULL) && (getType() == 0)); + + return static_cast(this)->asRootNode(); +} + +const KGpgRootNode * +KGpgNode::toRootNode() const +{ + Q_ASSERT((m_parent == NULL) && (getType() == 0)); + + return static_cast(this)->asRootNode(); +} + +KGpgUidNode * +KGpgNode::toUidNode() +{ + Q_ASSERT(getType() == KgpgCore::ITYPE_UID); + + return static_cast(this); +} + +const KGpgUidNode * +KGpgNode::toUidNode() const +{ + Q_ASSERT(getType() == KgpgCore::ITYPE_UID); + + return static_cast(this); +} + +KGpgSubkeyNode * +KGpgNode::toSubkeyNode() +{ + Q_ASSERT(getType() == KgpgCore::ITYPE_SUB); + + return static_cast(this); +} + +const KGpgSubkeyNode * +KGpgNode::toSubkeyNode() const +{ + Q_ASSERT(getType() == KgpgCore::ITYPE_SUB); + + return static_cast(this); +} + +KGpgUatNode * +KGpgNode::toUatNode() +{ + Q_ASSERT(getType() == KgpgCore::ITYPE_UAT); + + return static_cast(this); +} + +const KGpgUatNode * +KGpgNode::toUatNode() const +{ + Q_ASSERT(getType() == KgpgCore::ITYPE_UAT); + + return static_cast(this); +} + +KGpgGroupNode * +KGpgNode::toGroupNode() +{ + Q_ASSERT(getType() == KgpgCore::ITYPE_GROUP); + + return static_cast(this); +} + +const KGpgGroupNode * +KGpgNode::toGroupNode() const +{ + Q_ASSERT(getType() == KgpgCore::ITYPE_GROUP); + + return static_cast(this); +} + +KGpgRefNode * +KGpgNode::toRefNode() +{ + Q_ASSERT(((getType() & KgpgCore::ITYPE_GROUP) && (getType() & KgpgCore::ITYPE_PAIR)) || (getType() & KgpgCore::ITYPE_SIGN)); + + return qobject_cast(this); +} + +const KGpgRefNode * +KGpgNode::toRefNode() const +{ + Q_ASSERT(((getType() & KgpgCore::ITYPE_GROUP) && (getType() & KgpgCore::ITYPE_PAIR)) || (getType() & KgpgCore::ITYPE_SIGN)); + + return qobject_cast(this); +} + +KGpgGroupMemberNode * +KGpgNode::toGroupMemberNode() +{ + Q_ASSERT((getType() & KgpgCore::ITYPE_GROUP) && (getType() & KgpgCore::ITYPE_PAIR)); + + return static_cast(this); +} + +const KGpgGroupMemberNode * +KGpgNode::toGroupMemberNode() const +{ + Q_ASSERT((getType() & KgpgCore::ITYPE_GROUP) && (getType() & KgpgCore::ITYPE_PAIR)); + + return static_cast(this); +} + +KGpgSignNode * +KGpgNode::toSignNode() +{ + Q_ASSERT(getType() == KgpgCore::ITYPE_SIGN); + + return static_cast(this); +} + +const KGpgSignNode * +KGpgNode::toSignNode() const +{ + Q_ASSERT(getType() == KgpgCore::ITYPE_SIGN); + + return static_cast(this); +} + +KGpgOrphanNode * +KGpgNode::toOrphanNode() +{ + Q_ASSERT(getType() == KgpgCore::ITYPE_SECRET); + + return static_cast(this); +} + +const KGpgOrphanNode * +KGpgNode::toOrphanNode() const +{ + Q_ASSERT(getType() == KgpgCore::ITYPE_SECRET); + + return static_cast(this); +} + +bool +KGpgNode::hasChildren() const +{ + return false; +} + +int +KGpgNode::getChildCount() +{ + return 0; +} + +KGpgNode * +KGpgNode::getChild(const int index) const +{ + Q_UNUSED(index); + return NULL; +} + +int +KGpgNode::getChildIndex(KGpgNode *node) const +{ + Q_UNUSED(node); + return 0; +} + +KgpgCore::KgpgKeyTrust +KGpgNode::getTrust() const +{ + return TRUST_NOKEY; +} + +QString +KGpgNode::getSize() const +{ + return QString(); +} + +QString +KGpgNode::getName() const +{ + return QString(); +} + +QString +KGpgNode::getEmail() const +{ + return QString(); +} + +QDateTime +KGpgNode::getExpiration() const +{ + return QDateTime(); +} + +QDateTime +KGpgNode::getCreation() const +{ + return QDateTime(); +} + +QString +KGpgNode::getId() const +{ + return QString(); +} + +QString +KGpgNode::getComment() const +{ + return QString(); +} + +KGpgExpandableNode * +KGpgNode::getParentKeyNode() const +{ + return m_parent; +} + +#include "KGpgNode.moc" diff --git a/kgpg/core/KGpgNode.h b/kgpg/core/KGpgNode.h new file mode 100644 index 00000000..1d89875e --- /dev/null +++ b/kgpg/core/KGpgNode.h @@ -0,0 +1,156 @@ +/* Copyright 2008,2009 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGNODE_H +#define KGPGNODE_H + +#include +#include "kgpgkey.h" + +class KGpgExpandableNode; +class KGpgKeyNode; +class KGpgRootNode; +class KGpgUidNode; +class KGpgSignableNode; +class KGpgSubkeyNode; +class KGpgUatNode; +class KGpgGroupNode; +class KGpgRefNode; +class KGpgGroupMemberNode; +class KGpgSignNode; +class KGpgOrphanNode; +class KGpgItemModel; + +/** + * @brief The abstract base class for all classes representing keyring data + */ +class KGpgNode : public QObject +{ + Q_OBJECT + + friend class KGpgItemModel; + + KGpgNode(); // = delete C++0x +protected: + KGpgExpandableNode *m_parent; + KGpgItemModel *m_model; + + /** + * constructor + * @param parent the parent node in item hierarchy + */ + explicit KGpgNode(KGpgExpandableNode *parent); + +public: + typedef QList List; + + /** + * destructor + */ + virtual ~KGpgNode(); + + /** + * Returns if this node has child nodes + * + * This may be reimplemented by child classes so they can indicate that + * there are child nodes before actually loading them. + */ + virtual bool hasChildren() const; + /** + * Return how many child nodes exist + * + * When the child nodes do not already exist this will create them. + * This is the reason why this method is not const. + */ + virtual int getChildCount(); + /** + * Returns the child node at the given index + * @param index child index + * + * index may be in range 0 to getChildCount() - 1. + */ + virtual KGpgNode *getChild(const int index) const; + /** + * Returns the index for a given child node + * @return -1 if the given node is not a child of this object + */ + virtual int getChildIndex(KGpgNode *node) const; + + /** + * Returns the item type of this object + * + * Since every subclass returns a distinct value you can use the + * result of this function to decide which cast to take. Note that + * there are subclasses (KGpgKeyNode, KGpgGroupMemberNode) that + * can return two different values. + */ + virtual KgpgCore::KgpgItemType getType() const = 0; + virtual KgpgCore::KgpgKeyTrust getTrust() const; + /** + * Returns a string describing the size of this object + * + * Subclasses may return a value that makes sense for whatever + * object they represent. + * + * The default implementation returns an empty string. + */ + virtual QString getSize() const; + virtual QString getName() const; + virtual QString getEmail() const; + virtual QDateTime getExpiration() const; + virtual QDateTime getCreation() const; + virtual QString getId() const; + virtual QString getComment() const; + virtual QString getNameComment() const; + /** + * Returns the parent node in the key hierarchy + * + * For all "primary" items like keys and key groups this will + * return the (invisible) root node. Calling this function for + * the root node will return %NULL. No other node but the root + * node has a %NULL parent. + */ + KGpgExpandableNode *getParentKeyNode() const; + + KGpgExpandableNode *toExpandableNode(); + const KGpgExpandableNode *toExpandableNode() const; + KGpgSignableNode *toSignableNode(); + const KGpgSignableNode *toSignableNode() const; + KGpgKeyNode *toKeyNode(); + const KGpgKeyNode *toKeyNode() const; + KGpgRootNode *toRootNode(); + const KGpgRootNode *toRootNode() const; + KGpgUidNode *toUidNode(); + const KGpgUidNode *toUidNode() const; + KGpgSubkeyNode *toSubkeyNode(); + const KGpgSubkeyNode *toSubkeyNode() const; + KGpgUatNode *toUatNode(); + const KGpgUatNode *toUatNode() const; + KGpgGroupNode *toGroupNode(); + const KGpgGroupNode *toGroupNode() const; + KGpgRefNode *toRefNode(); + const KGpgRefNode *toRefNode() const; + KGpgGroupMemberNode *toGroupMemberNode(); + const KGpgGroupMemberNode *toGroupMemberNode() const; + KGpgSignNode *toSignNode(); + const KGpgSignNode *toSignNode() const; + KGpgOrphanNode *toOrphanNode(); + const KGpgOrphanNode *toOrphanNode() const; +}; + +#endif /* KGPGNODE_H */ diff --git a/kgpg/core/KGpgOrphanNode.cpp b/kgpg/core/KGpgOrphanNode.cpp new file mode 100644 index 00000000..f245121e --- /dev/null +++ b/kgpg/core/KGpgOrphanNode.cpp @@ -0,0 +1,84 @@ +/* Copyright 2008,2009,2010,2012 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgOrphanNode.h" + +KGpgOrphanNode::KGpgOrphanNode(KGpgExpandableNode *parent, const KgpgKey &k) + : KGpgNode(parent), + m_key(new KgpgKey(k)) +{ +} + +KGpgOrphanNode::~KGpgOrphanNode() +{ + delete m_key; +} + +KgpgItemType +KGpgOrphanNode::getType() const +{ + return ITYPE_SECRET; +} + +QString +KGpgOrphanNode::getName() const +{ + return m_key->name(); +} + +QString +KGpgOrphanNode::getEmail() const +{ + return m_key->email(); +} + +QString +KGpgOrphanNode::getSize() const +{ + return QString::number(m_key->size()); +} + +QDateTime +KGpgOrphanNode::getExpiration() const +{ + return m_key->expirationDate(); +} + +QDateTime +KGpgOrphanNode::getCreation() const +{ + return m_key->creationDate(); +} + +QString +KGpgOrphanNode::getId() const +{ + return m_key->fullId(); +} + +KgpgCore::KgpgKeyTrust +KGpgOrphanNode::getTrust() const +{ + return m_key->trust(); +} + +const QString & +KGpgOrphanNode::getFingerprint() const +{ + return m_key->fingerprint(); +} diff --git a/kgpg/core/KGpgOrphanNode.h b/kgpg/core/KGpgOrphanNode.h new file mode 100644 index 00000000..e3b6132c --- /dev/null +++ b/kgpg/core/KGpgOrphanNode.h @@ -0,0 +1,53 @@ +/* Copyright 2008,2009,2010,2012 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGORPHANNODE_H +#define KGPGORPHANNODE_H + +#include "KGpgNode.h" + +#include "kgpgkey.h" + +using namespace KgpgCore; + +class KGpgExpandableNode; + +/** + * @brief A lone secret key without public key + */ +class KGpgOrphanNode : public KGpgNode +{ +private: + KgpgCore::KgpgKey *m_key; + +public: + explicit KGpgOrphanNode(KGpgExpandableNode *parent, const KgpgKey &k); + virtual ~KGpgOrphanNode(); + + virtual KgpgCore::KgpgItemType getType() const; + virtual KgpgCore::KgpgKeyTrust getTrust() const; + const QString &getFingerprint() const; + virtual QString getSize() const; + virtual QString getName() const; + virtual QString getEmail() const; + virtual QDateTime getExpiration() const; + virtual QDateTime getCreation() const; + virtual QString getId() const; +}; + +#endif /* KGPGORPHANNODE_H */ diff --git a/kgpg/core/KGpgRefNode.cpp b/kgpg/core/KGpgRefNode.cpp new file mode 100644 index 00000000..dd5bdb27 --- /dev/null +++ b/kgpg/core/KGpgRefNode.cpp @@ -0,0 +1,151 @@ +/* Copyright 2008,2009,2010,2013 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgRefNode.h" + +#include + +#include "KGpgExpandableNode.h" +#include "KGpgRootNode.h" + +KGpgRefNode::KGpgRefNode(KGpgExpandableNode *parent, const QString &keyid) + : KGpgNode(parent), + m_id(keyid) +{ + Q_ASSERT(!keyid.isEmpty()); + + KGpgRootNode *root = getRootNode(); + KGpgExpandableNode *pnd = parent; + + do { + m_selfsig = (pnd->getId().right(keyid.length()) == keyid); + if (m_selfsig) + m_keynode = pnd->toKeyNode(); + else + pnd = pnd->getParentKeyNode(); + } while (!m_selfsig && (pnd != root)); + + // Self signatures do net need to get notified by their key: if the key is changed + // the key node is deleted, then those refnode would be deleted anyway. This avoids + // crashes when they would try to find the root node by iterating over their parent + // when the parents destructor is already called (see bug 208659). + if (!m_selfsig) { + m_keynode = root->findKey(keyid); + + if (m_keynode != NULL) { + m_keynode->addRef(this); + } else { + connect(root, SIGNAL(newKeyNode(KGpgKeyNode*)), this, SLOT(keyUpdated(KGpgKeyNode*))); + } + } + + parent->children.append(this); +} + +KGpgRefNode::KGpgRefNode(KGpgExpandableNode *parent, KGpgKeyNode *key) + : KGpgNode(parent), + m_id(key->getId()), + m_keynode(key) +{ + Q_ASSERT(key != NULL); + Q_ASSERT(parent != NULL); + m_keynode->addRef(this); + + parent->children.append(this); +} + +KGpgRefNode::~KGpgRefNode() +{ + if (m_keynode && !m_selfsig) + m_keynode->delRef(this); +} + +KGpgRootNode * +KGpgRefNode::getRootNode() const +{ + KGpgExpandableNode *root; + KGpgExpandableNode *pt = m_parent; + + do { + root = pt; + pt = pt->getParentKeyNode(); + } while (pt != NULL); + + return root->toRootNode(); +} + +void +KGpgRefNode::keyUpdated(KGpgKeyNode *nkey) +{ + Q_ASSERT(m_keynode == NULL); + Q_ASSERT(nkey != NULL); + + if (nkey->compareId(m_id)) { + disconnect(sender(), NULL, this, SLOT(keyUpdated(KGpgKeyNode*))); + m_keynode = nkey; + m_keynode->addRef(this); + } +} + +void +KGpgRefNode::unRef(KGpgRootNode *root) +{ + if (root != NULL) + connect(root, SIGNAL(newKeyNode(KGpgKeyNode*)), this, SLOT(keyUpdated(KGpgKeyNode*))); + + m_keynode = NULL; +} + +QString +KGpgRefNode::getId() const +{ + if (m_keynode != NULL) + return m_keynode->getFingerprint(); + else + return m_id; +} + +QString +KGpgRefNode::getName() const +{ + if (m_keynode != NULL) + return m_keynode->getName(); + return i18n("[No user id found]"); +} + +QString +KGpgRefNode::getEmail() const +{ + if (m_keynode != NULL) + return m_keynode->getEmail(); + return QString(); +} + +bool +KGpgRefNode::isUnknown() const +{ + return (m_keynode == NULL); +} + +KGpgKeyNode * +KGpgRefNode::getRefNode() const +{ + return m_keynode; +} + +#include "KGpgRefNode.moc" diff --git a/kgpg/core/KGpgRefNode.h b/kgpg/core/KGpgRefNode.h new file mode 100644 index 00000000..f9775679 --- /dev/null +++ b/kgpg/core/KGpgRefNode.h @@ -0,0 +1,98 @@ +/* Copyright 2008,2009 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGREFNODE_H +#define KGPGREFNODE_H + +#include "KGpgNode.h" + +class KGpgKeyNode; +class KGpgRootNode; + +/** + * @brief Class for child objects that are only a reference to a primary key + * + * This is the base class for all type of objects that match these criteria: + * -they can not have child objects + * -they are only a reference to a primary key (which needs not to be in the + * key ring) + * + * Do not create instances from this class. Use KGpgGroupMemberNode and + * KGpgSignNode as those represent the existing objects. This class exists + * only to get the hierarchy right. + */ +class KGpgRefNode : public KGpgNode +{ + Q_OBJECT + +private: + const QString m_id; + bool m_selfsig; ///< if this is a reference to it's own parent + +protected: + KGpgKeyNode *m_keynode; + + explicit KGpgRefNode(KGpgExpandableNode *parent, KGpgKeyNode *key); + explicit KGpgRefNode(KGpgExpandableNode *parent, const QString &keyid); + + KGpgRootNode *getRootNode() const; + +public: + virtual ~KGpgRefNode(); + + virtual QString getId() const; + virtual QString getName() const; + virtual QString getEmail() const; + /** + * Get the node of the primary key this node references to + * + * This will return the key node of the primary key this node + * references. This may be %NULL if the primary key is not in the key + * ring, e.g. if this is a signature of an unknown key. + * + * @return the node of the primary key or %NULL + */ + virtual KGpgKeyNode *getRefNode() const; + + /** + * Check if the referenced key exists + * + * @return if getRefNode() will return %NULL or not + */ + bool isUnknown() const; + + /** + * Break the current reference + * @param root root node + * + * This is called when the referenced node is going away. + * + * The root node is passed for two reasons: + * @li it doesn't need to be searched again for every ref node which + * can be many in case of an important key node get's deleted + * @li the ref node may be a child of the deleted node, then we can + * not call the parents functions to find the root anymore. This helps + * simplifying the code + */ + void unRef(KGpgRootNode *root); + +private Q_SLOTS: + void keyUpdated(KGpgKeyNode *); +}; + +#endif /* KGPGREFNODE_H */ diff --git a/kgpg/core/KGpgRootNode.cpp b/kgpg/core/KGpgRootNode.cpp new file mode 100644 index 00000000..e37ce99c --- /dev/null +++ b/kgpg/core/KGpgRootNode.cpp @@ -0,0 +1,183 @@ +/* Copyright 2008,2009,2010,2012,2013 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgRootNode.h" + +#include "KGpgGroupNode.h" +#include "kgpginterface.h" +#include "KGpgOrphanNode.h" +#include "kgpgsettings.h" + +#include +#include + +KGpgRootNode::KGpgRootNode(KGpgItemModel *model) + : KGpgExpandableNode(NULL), + m_groups(0), + m_deleting(false) +{ + m_model = model; +} + +KGpgRootNode::~KGpgRootNode() +{ + m_deleting = true; +} + +void +KGpgRootNode::readChildren() +{ +} + +KgpgCore::KgpgItemType +KGpgRootNode::getType() const +{ + return 0; +} + +void +KGpgRootNode::addGroups(const QStringList &groups) +{ + foreach (const QString &group, groups) { + QStringList members = group.split(QLatin1Char(' ')); + const QString groupName = members.takeFirst(); + new KGpgGroupNode(this, groupName, members); + } +} + +void +KGpgRootNode::addKeys(const QStringList &ids) +{ + KgpgCore::KgpgKeyList publiclist = KgpgInterface::readPublicKeys(ids); + KgpgCore::KgpgKeyList secretlist = KgpgInterface::readSecretKeys(); + + QStringList issec = secretlist; + + foreach (KgpgCore::KgpgKey key, publiclist) { // krazy:exclude=foreach + int index = issec.indexOf(key.fullId()); + + if (index >= 0) { + key.setSecret(true); + issec.removeAt(index); + secretlist.removeAt(index); + } + + KGpgKeyNode *nd = new KGpgKeyNode(this, key); + emit newKeyNode(nd); + } + + foreach (const KgpgCore::KgpgKey &key, secretlist) + new KGpgOrphanNode(this, key); +} + +void +KGpgRootNode::refreshKeys(KGpgKeyNode::List nodes) +{ + QStringList ids; + + foreach (const KGpgNode *nd, nodes) + ids << nd->getId(); + + KgpgCore::KgpgKeyList publiclist = KgpgInterface::readPublicKeys(ids); + QStringList issec = KgpgInterface::readSecretKeys(ids); + + foreach (KgpgCore::KgpgKey key, publiclist) { // krazy:exclude=foreach + int index = issec.indexOf(key.fullId()); + if (index != -1) { + key.setSecret(true); + issec.removeAt(index); + } + + for (int j = 0; j < nodes.count(); j++) { + KGpgKeyNode *nd = nodes.at(j); + if (nd->getId() == key.fullId()) { + nodes.removeAt(j); + nd->setKey(key); + break; + } + } + } +} + +KGpgKeyNode * +KGpgRootNode::findKey(const QString &keyId) +{ + int i = findKeyRow(keyId); + if (i >= 0) { + return children[i]->toKeyNode(); + } + + return NULL; +} + +int +KGpgRootNode::findKeyRow(const QString &keyId) +{ + int i = 0; + + foreach (const KGpgNode *node, children) { + if ((node->getType() & ITYPE_PAIR) == 0) { + ++i; + continue; + } + + const KGpgKeyNode *key = node->toKeyNode(); + + if (key->compareId(keyId)) + return i; + ++i; + } + + return -1; +} + +int +KGpgRootNode::groupChildren() const +{ + return m_groups; +} + +int +KGpgRootNode::findKeyRow(const KGpgKeyNode *key) +{ + for (int i = 0; i < children.count(); i++) { + if (children[i] == key) + return i; + } + return -1; +} + +KGpgRootNode * +KGpgRootNode::asRootNode() +{ + if (m_deleting) + return NULL; + + return this; +} + +const KGpgRootNode * +KGpgRootNode::asRootNode() const +{ + if (m_deleting) + return NULL; + + return this; +} + +#include "KGpgRootNode.moc" diff --git a/kgpg/core/KGpgRootNode.h b/kgpg/core/KGpgRootNode.h new file mode 100644 index 00000000..97c597cb --- /dev/null +++ b/kgpg/core/KGpgRootNode.h @@ -0,0 +1,129 @@ +/* Copyright 2008,2009 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGROOTNODE_H +#define KGPGROOTNODE_H + +#include "KGpgExpandableNode.h" +#include "KGpgKeyNode.h" + +class KGpgGroupNode; +class QString; +class QStringList; + +/** + * @brief The parent of all key data objects + * + * This object is invisible to the user but acts as the internal base object for + * everything in the keyring. It is anchestor of all other KGpgNode objects and + * the only one that will ever return NULL when calling getParentKeyNode() on it. + * + * There is only one object of this type around at any time. + */ +class KGpgRootNode : public KGpgExpandableNode +{ + Q_OBJECT + + friend class KGpgGroupNode; + +private: + int m_groups; + int m_deleting; + +protected: + virtual void readChildren(); + +public: + explicit KGpgRootNode(KGpgItemModel *model); + virtual ~KGpgRootNode(); + + virtual KgpgCore::KgpgItemType getType() const; + + /** + * Create new group nodes + * @param groups list of group names to create + */ + void addGroups(const QStringList &groups); + void addKeys(const QStringList &ids = QStringList()); + void refreshKeys(KGpgKeyNode::List nodes); + /** + * Find a key node with the given id + * + * This scans the list of primary keys for a key with the given id + * and returns the corresponding key node. + * + * The key id will be matched against the characters given in keyId. + * If you give only 8 or 16 byte you will still find the key if it + * exists. To be really sure to find the correct node you should pass + * the complete fingerprint whenever possible. + * + * @param keyId the key id to find, any length is permitted + * @return pointer to key node or %NULL if no such key + */ + KGpgKeyNode *findKey(const QString &keyId); + /** + * Return the child number of the key with the given id + * + * This scans the list of direct children for a key with the given + * key id. It returns the number in the internal list of children + * which is identical to the row number in the item model. Since + * proxy models may sort the items you should only call this function + * from the primary model (i.e. KGpgItemModel). + * + * The key id will be matched against the characters given in keyId. + * If you give only 8 or 16 byte you will still find the key if it + * exists. To be really sure to find the correct node you should pass + * the complete fingerprint whenever possible. + * + * @param keyId the key id to find, any length is permitted + * @return the child number or -1 if there is no such key + */ + int findKeyRow(const QString &keyId); + + /** + * Return the child number of the given key + * @param key the key to search for + * + * @overload + */ + int findKeyRow(const KGpgKeyNode *key); + + /** + * Return the group count + * @return the number of group nodes + */ + int groupChildren() const; + + /** + * Return a pointer to this object or NULL + * + * This returns a pointer to this object if the object will persist, + * i.e. is not currently in destruction. If the object is already + * cleaning up NULL is returned. + */ + KGpgRootNode *asRootNode(); + /** + * @overload + */ + const KGpgRootNode *asRootNode() const; + +Q_SIGNALS: + void newKeyNode(KGpgKeyNode *); +}; + +#endif /* KGPGROOTNODE_H */ diff --git a/kgpg/core/KGpgSignNode.cpp b/kgpg/core/KGpgSignNode.cpp new file mode 100644 index 00000000..8d27d936 --- /dev/null +++ b/kgpg/core/KGpgSignNode.cpp @@ -0,0 +1,95 @@ +/* Copyright 2008,2009,2010,2013 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgSignNode.h" + +#include "KGpgSignableNode.h" + +#include + +class KGpgSignNodePrivate { +public: + KGpgSignNodePrivate(const QStringList &sl); + + QDateTime m_creation; + QDateTime m_expiration; + bool m_local; + bool m_revocation; +}; + +KGpgSignNodePrivate::KGpgSignNodePrivate(const QStringList &sl) + : m_local(false) +{ + Q_ASSERT(!sl.isEmpty()); + m_revocation = (sl.at(0) == QLatin1String("rev")); + if (sl.count() < 6) + return; + m_creation = QDateTime::fromTime_t(sl.at(5).toUInt()); + if (sl.count() < 7) + return; + if (!sl.at(6).isEmpty()) + m_expiration = QDateTime::fromTime_t(sl.at(6).toUInt()); + if (sl.count() < 11) + return; + m_local = sl.at(10).endsWith(QLatin1Char( 'l' )); +} + +KGpgSignNode::KGpgSignNode(KGpgSignableNode *parent, const QStringList &s) + : KGpgRefNode(parent, s.at(4)), + d_ptr(new KGpgSignNodePrivate(s)) +{ +} + +KGpgSignNode::~KGpgSignNode() +{ + delete d_ptr; +} + +QDateTime +KGpgSignNode::getExpiration() const +{ + const Q_D(KGpgSignNode); + + return d->m_expiration; +} + +QDateTime +KGpgSignNode::getCreation() const +{ + const Q_D(KGpgSignNode); + + return d->m_creation; +} + +QString +KGpgSignNode::getName() const +{ + const Q_D(KGpgSignNode); + const QString name = KGpgRefNode::getName(); + + if (!d->m_local) + return name; + + return i18n("%1 [local signature]", name); +} + +KgpgCore::KgpgItemType +KGpgSignNode::getType() const +{ + return KgpgCore::ITYPE_SIGN; +} diff --git a/kgpg/core/KGpgSignNode.h b/kgpg/core/KGpgSignNode.h new file mode 100644 index 00000000..398ee572 --- /dev/null +++ b/kgpg/core/KGpgSignNode.h @@ -0,0 +1,54 @@ +/* Copyright 2008,2009,2010 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGSIGNNODE_H +#define KGPGSIGNNODE_H + +#include "KGpgRefNode.h" + +class KGpgSignableNode; + +class KGpgSignNodePrivate; + +/** + * @brief A signature to another key object + */ +class KGpgSignNode : public KGpgRefNode +{ +private: + KGpgSignNodePrivate * const d_ptr; + Q_DECLARE_PRIVATE(KGpgSignNode) + +public: + typedef QList List; + + /** + * @brief constructor for KGpgSignNode + * @param parent the signed node + * @param s GnuPG line describing this signature + */ + explicit KGpgSignNode(KGpgSignableNode *parent, const QStringList &s); + virtual ~KGpgSignNode(); + + virtual KgpgCore::KgpgItemType getType() const; + virtual QDateTime getExpiration() const; + virtual QString getName() const; + virtual QDateTime getCreation() const; +}; + +#endif /* KGPGSIGNNODE_H */ diff --git a/kgpg/core/KGpgSignableNode.cpp b/kgpg/core/KGpgSignableNode.cpp new file mode 100644 index 00000000..af48878c --- /dev/null +++ b/kgpg/core/KGpgSignableNode.cpp @@ -0,0 +1,98 @@ +/* Copyright 2008,2009 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgSignableNode.h" + +#include + +KGpgSignableNode::KGpgSignableNode(KGpgExpandableNode *parent) + : KGpgExpandableNode(parent) +{ +} + +KGpgSignableNode::~KGpgSignableNode() +{ +} + +KGpgSignNode::List +KGpgSignableNode::getSignatures(void) const +{ + KGpgSignNode::List ret; + + foreach (KGpgNode *kn, children) { + if (kn->getType() == KgpgCore::ITYPE_SIGN) + ret << kn->toSignNode(); + } + + return ret; +} + +QString +KGpgSignableNode::getSignCount() const +{ + return i18np("1 signature", "%1 signatures", children.count()); +} + +bool +KGpgSignableNode::operator<(const KGpgSignableNode &other) const +{ + return operator<(&other); +} + +bool +KGpgSignableNode::operator<(const KGpgSignableNode *other) const +{ + switch (getType()) { + case KgpgCore::ITYPE_PUBLIC: + case KgpgCore::ITYPE_PAIR: { + const QString myid(getId()); + + switch (other->getType()) { + case KgpgCore::ITYPE_PUBLIC: + case KgpgCore::ITYPE_PAIR: + return (myid < other->getId()); + default: { + const QString otherid(other->getParentKeyNode()->getId()); + + if (myid == otherid) + return true; + return (myid < otherid); + } + } + } + default: { + const QString myp(getParentKeyNode()->getId()); + + switch (other->getType()) { + case KgpgCore::ITYPE_PAIR: + case KgpgCore::ITYPE_PUBLIC: + return (myp < other->getId()); + default: { + const QString otherp(other->getParentKeyNode()->getId()); + + if (otherp == myp) + return (getId().toInt() < other->getId().toInt()); + + return (myp < otherp); + } + } + } + } +} + +#include "KGpgSignableNode.moc" diff --git a/kgpg/core/KGpgSignableNode.h b/kgpg/core/KGpgSignableNode.h new file mode 100644 index 00000000..29ded6e2 --- /dev/null +++ b/kgpg/core/KGpgSignableNode.h @@ -0,0 +1,62 @@ +/* Copyright 2008,2009,2012 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGSIGNABLENODE_H +#define KGPGSIGNABLENODE_H + +#include "KGpgExpandableNode.h" +#include "KGpgSignNode.h" + +/** + * @brief An object that may have KGpgSignNode children + * + * This class represents an object that may be signed, i.e. key nodes, + * user ids, user attributes, and subkeys. + */ +class KGpgSignableNode : public KGpgExpandableNode +{ + Q_OBJECT + +public: + typedef QList List; + typedef QList const_List; + + explicit KGpgSignableNode(KGpgExpandableNode *parent = NULL); + virtual ~KGpgSignableNode(); + + KGpgSignNode::List getSignatures(void) const; + /** + * @brief count signatures + * @return the number of signatures to this object + * + * This does not include the number of signatures to child objects. + */ + virtual QString getSignCount() const; + + bool operator<(const KGpgSignableNode &other) const; + bool operator<(const KGpgSignableNode *other) const; + + /** + * @brief returns the key node this node belongs to + * @returns this node if the node itself is a key or it's parent otherwise + */ + virtual KGpgKeyNode *getKeyNode(void) = 0; + virtual const KGpgKeyNode *getKeyNode(void) const = 0; +}; + +#endif /* KGPGSIGNABLENODE_H */ diff --git a/kgpg/core/KGpgSubkeyNode.cpp b/kgpg/core/KGpgSubkeyNode.cpp new file mode 100644 index 00000000..6bbb7b75 --- /dev/null +++ b/kgpg/core/KGpgSubkeyNode.cpp @@ -0,0 +1,99 @@ +/* Copyright 2008,2009 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgSubkeyNode.h" + +#include + +#include "convert.h" +#include "KGpgKeyNode.h" + +KGpgSubkeyNode::KGpgSubkeyNode(KGpgKeyNode *parent, const KgpgKeySub &k) + : KGpgSignableNode(parent), + m_skey(k) +{ + Q_ASSERT(parent != NULL); +} +KGpgSubkeyNode::~KGpgSubkeyNode() +{ +} + +void +KGpgSubkeyNode::readChildren() +{ +} + +KgpgCore::KgpgItemType +KGpgSubkeyNode::getType() const +{ + return ITYPE_SUB; +} + +KgpgCore::KgpgKeyTrust +KGpgSubkeyNode::getTrust() const +{ + return m_skey.trust(); +} + +QDateTime +KGpgSubkeyNode::getExpiration() const +{ + return m_skey.expirationDate(); +} + +QDateTime +KGpgSubkeyNode::getCreation() const +{ + return m_skey.creationDate(); +} + +QString +KGpgSubkeyNode::getId() const +{ + return m_skey.id(); +} + +KGpgKeyNode * +KGpgSubkeyNode::getKeyNode(void) +{ + return getParentKeyNode()->toKeyNode(); +} + +const KGpgKeyNode * +KGpgSubkeyNode::getKeyNode(void) const +{ + return getParentKeyNode()->toKeyNode(); +} + +QString +KGpgSubkeyNode::getName() const +{ + return i18n("%1 subkey", Convert::toString(m_skey.algorithm())); +} + +QString +KGpgSubkeyNode::getSize() const +{ + return QString::number(m_skey.size()); +} + +KGpgKeyNode * +KGpgSubkeyNode::getParentKeyNode() const +{ + return m_parent->toKeyNode(); +} diff --git a/kgpg/core/KGpgSubkeyNode.h b/kgpg/core/KGpgSubkeyNode.h new file mode 100644 index 00000000..bbe5ab8d --- /dev/null +++ b/kgpg/core/KGpgSubkeyNode.h @@ -0,0 +1,55 @@ +/* Copyright 2008,2009 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGSUBKEYNODE_H +#define KGPGSUBKEYNODE_H + +#include "KGpgSignableNode.h" + +#include "kgpgkey.h" + +using namespace KgpgCore; + +/** + * @brief a subkey of a public key or key pair + */ +class KGpgSubkeyNode : public KGpgSignableNode +{ +private: + KgpgCore::KgpgKeySub m_skey; + +protected: + virtual void readChildren(); + +public: + explicit KGpgSubkeyNode(KGpgKeyNode *parent, const KgpgCore::KgpgKeySub &k); + virtual ~KGpgSubkeyNode(); + + virtual KgpgCore::KgpgItemType getType() const; + virtual KgpgCore::KgpgKeyTrust getTrust() const; + virtual QString getSize() const; + virtual QString getName() const; + virtual QDateTime getExpiration() const; + virtual QDateTime getCreation() const; + virtual QString getId() const; + virtual KGpgKeyNode *getKeyNode(void); + virtual const KGpgKeyNode *getKeyNode(void) const; + virtual KGpgKeyNode *getParentKeyNode() const; +}; + +#endif /* KGPGSUBKEYNODE_H */ diff --git a/kgpg/core/KGpgUatNode.cpp b/kgpg/core/KGpgUatNode.cpp new file mode 100644 index 00000000..ebcf14f4 --- /dev/null +++ b/kgpg/core/KGpgUatNode.cpp @@ -0,0 +1,173 @@ +/* Copyright 2008,2009,2010,2012 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgUatNode.h" + +#include "gpgproc.h" +#include "KGpgKeyNode.h" + +#include +#include + +#include +#include +#include +#include + +class KGpgUatNodePrivate { +public: + KGpgUatNodePrivate(const KGpgKeyNode *parent, const unsigned int index, const QStringList &sl); + + const QString m_idx; + const QPixmap m_pixmap; + QDateTime m_creation; + +private: + static QPixmap loadImage(const KGpgKeyNode *parent, const QString &index); +}; + +KGpgUatNodePrivate::KGpgUatNodePrivate(const KGpgKeyNode *parent, const unsigned int index, const QStringList &sl) + : m_idx(QString::number(index)), + m_pixmap(loadImage(parent, m_idx)) +{ + if (sl.count() < 6) + return; + m_creation = QDateTime::fromTime_t(sl.at(5).toUInt()); +} + +QPixmap +KGpgUatNodePrivate::loadImage(const KGpgKeyNode *parent, const QString &index) +{ + QPixmap pixmap; +#ifdef Q_OS_WIN32 //krazy:exclude=cpp + const QString pgpgoutput = QLatin1String("cmd /C \"echo %I\""); +#else + const QString pgpgoutput = QLatin1String("echo %I"); +#endif + + GPGProc workProcess; + workProcess << + QLatin1String("--no-greeting") << + QLatin1String("--status-fd=2") << + QLatin1String("--photo-viewer") << pgpgoutput << + QLatin1String("--edit-key") << parent->getFingerprint() << + QLatin1String( "uid" ) << index << + QLatin1String( "showphoto" ) << + QLatin1String( "quit" ); + + workProcess.start(); + workProcess.waitForFinished(); + if (workProcess.exitCode() != 0) + return pixmap; + + QString tmpfile; + if (workProcess.readln(tmpfile) < 0) + return pixmap; + + KUrl url(tmpfile); + pixmap.load(url.path()); + QFile::remove(url.path()); + QDir dir; + dir.rmdir(url.directory()); + + return pixmap; +} + +KGpgUatNode::KGpgUatNode(KGpgKeyNode *parent, const unsigned int index, const QStringList &sl) + : KGpgSignableNode(parent), + d_ptr(new KGpgUatNodePrivate(parent, index, sl)) +{ +} + +KGpgUatNode::~KGpgUatNode() +{ + delete d_ptr; +} + +QString +KGpgUatNode::getName() const +{ + return i18n("Photo id"); +} + +QString +KGpgUatNode::getSize() const +{ + const Q_D(KGpgUatNode); + + return QString::number(d->m_pixmap.width()) + QLatin1Char( 'x' ) + QString::number(d->m_pixmap.height()); +} + +QDateTime +KGpgUatNode::getCreation() const +{ + const Q_D(KGpgUatNode); + + return d->m_creation; +} + +KGpgKeyNode * +KGpgUatNode::getParentKeyNode() const +{ + return m_parent->toKeyNode(); +} + +void +KGpgUatNode::readChildren() +{ +} + +KgpgCore::KgpgItemType +KGpgUatNode::getType() const +{ + return KgpgCore::ITYPE_UAT; +} + +KgpgCore::KgpgKeyTrust +KGpgUatNode::getTrust() const +{ + return KgpgCore::TRUST_NOKEY; +} + +const QPixmap & +KGpgUatNode::getPixmap() const +{ + const Q_D(KGpgUatNode); + + return d->m_pixmap; +} + +QString +KGpgUatNode::getId() const +{ + const Q_D(KGpgUatNode); + + return d->m_idx; +} + +KGpgKeyNode * +KGpgUatNode::getKeyNode(void) +{ + return getParentKeyNode()->toKeyNode(); +} + +const KGpgKeyNode * +KGpgUatNode::getKeyNode(void) const +{ + return getParentKeyNode()->toKeyNode(); +} diff --git a/kgpg/core/KGpgUatNode.h b/kgpg/core/KGpgUatNode.h new file mode 100644 index 00000000..b4c67756 --- /dev/null +++ b/kgpg/core/KGpgUatNode.h @@ -0,0 +1,61 @@ +/* Copyright 2008,2009,2010 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGUATNODE_H +#define KGPGUATNODE_H + +#include "KGpgSignableNode.h" + +#include +#include + +class KGpgExpandableNode; +class KGpgKeyNode; +class QPixmap; + +class KGpgUatNodePrivate; + +/** + * @brief A user attribute (i.e. photo id) of a public key or key pair + */ +class KGpgUatNode : public KGpgSignableNode +{ +private: + KGpgUatNodePrivate * const d_ptr; + Q_DECLARE_PRIVATE(KGpgUatNode) + +protected: + virtual void readChildren(); + +public: + explicit KGpgUatNode(KGpgKeyNode *parent, const unsigned int index, const QStringList &sl); + virtual ~KGpgUatNode(); + + virtual KgpgCore::KgpgItemType getType() const; + virtual KgpgCore::KgpgKeyTrust getTrust() const; + const QPixmap &getPixmap() const; + virtual QString getId() const; + virtual QString getSize() const; + virtual QString getName() const; + virtual QDateTime getCreation() const; + virtual KGpgKeyNode *getParentKeyNode() const; + virtual KGpgKeyNode *getKeyNode(void); + virtual const KGpgKeyNode *getKeyNode(void) const; +}; + +#endif /* KGPGUATNODE_H */ diff --git a/kgpg/core/KGpgUidNode.cpp b/kgpg/core/KGpgUidNode.cpp new file mode 100644 index 00000000..0be6c104 --- /dev/null +++ b/kgpg/core/KGpgUidNode.cpp @@ -0,0 +1,146 @@ +/* Copyright 2008,2009,2010 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "KGpgUidNode.h" + +#include "KGpgKeyNode.h" +#include "convert.h" + +class KGpgUidNodePrivate { +public: + KGpgUidNodePrivate(const unsigned int index, const QStringList &sl); + + const QString m_index; + QString m_email; + QString m_name; + QString m_comment; + KgpgCore::KgpgKeyTrust m_trust; + bool m_valid; +}; + +KGpgUidNodePrivate::KGpgUidNodePrivate(const unsigned int index, const QStringList &sl) + : m_index(QString::number(index)) +{ + QString fullname(sl.at(9)); + if (fullname.contains(QLatin1Char( '<' )) ) { + m_email = fullname; + + if (fullname.contains(QLatin1Char( ')' )) ) + m_email = m_email.section(QLatin1Char( ')' ), 1); + + m_email = m_email.section(QLatin1Char( '<' ), 1); + m_email.truncate(m_email.length() - 1); + + if (m_email.contains(QLatin1Char( '<' ))) { + // several email addresses in the same key + m_email = m_email.replace(QLatin1Char( '>' ), QLatin1Char( ';' )); + m_email.remove(QLatin1Char( '<' )); + } + } + + m_name = fullname.section(QLatin1String( " <" ), 0, 0); + if (fullname.contains(QLatin1Char( '(' )) ) { + m_name = m_name.section(QLatin1String( " (" ), 0, 0); + m_comment = fullname.section(QLatin1Char( '(' ), 1, 1); + m_comment = m_comment.section(QLatin1Char( ')' ), 0, 0); + } + + m_trust = KgpgCore::Convert::toTrust(sl.at(1)); + m_valid = ((sl.count() <= 11) || !sl.at(11).contains(QLatin1Char( 'D' ))); +} + + +KGpgUidNode::KGpgUidNode(KGpgKeyNode *parent, const unsigned int index, const QStringList &sl) + : KGpgSignableNode(parent), + d_ptr(new KGpgUidNodePrivate(index, sl)) +{ +} + +KGpgUidNode::~KGpgUidNode() +{ + delete d_ptr; +} + +QString +KGpgUidNode::getName() const +{ + const Q_D(KGpgUidNode); + + return d->m_name; +} + +QString +KGpgUidNode::getEmail() const +{ + const Q_D(KGpgUidNode); + + return d->m_email; +} + +QString +KGpgUidNode::getId() const +{ + const Q_D(KGpgUidNode); + + return d->m_index; +} + +KGpgKeyNode * +KGpgUidNode::getKeyNode(void) +{ + return getParentKeyNode()->toKeyNode(); +} + +const KGpgKeyNode * +KGpgUidNode::getKeyNode(void) const +{ + return getParentKeyNode()->toKeyNode(); +} + +KGpgKeyNode * +KGpgUidNode::getParentKeyNode() const +{ + return m_parent->toKeyNode(); +} + +void +KGpgUidNode::readChildren() +{ +} + +KgpgCore::KgpgItemType +KGpgUidNode::getType() const +{ + return KgpgCore::ITYPE_UID; +} + +KgpgCore::KgpgKeyTrust +KGpgUidNode::getTrust() const +{ + const Q_D(KGpgUidNode); + + return d->m_trust; +} + +QString +KGpgUidNode::getComment() const +{ + const Q_D(KGpgUidNode); + + return d->m_comment; +} diff --git a/kgpg/core/KGpgUidNode.h b/kgpg/core/KGpgUidNode.h new file mode 100644 index 00000000..cd1b5638 --- /dev/null +++ b/kgpg/core/KGpgUidNode.h @@ -0,0 +1,57 @@ +/* Copyright 2008,2009,2010 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGUIDNODE_H +#define KGPGUIDNODE_H + +#include "KGpgSignableNode.h" + +#include "kgpgkey.h" + +class KGpgKeyNode; + +class KGpgUidNodePrivate; + +/** + * @brief A user id of a public key or key pair + */ +class KGpgUidNode : public KGpgSignableNode +{ +private: + KGpgUidNodePrivate * const d_ptr; + Q_DECLARE_PRIVATE(KGpgUidNode) + +protected: + virtual void readChildren(); + +public: + explicit KGpgUidNode(KGpgKeyNode *parent, const unsigned int index, const QStringList &sl); + virtual ~KGpgUidNode(); + + virtual KgpgCore::KgpgItemType getType() const; + virtual KgpgCore::KgpgKeyTrust getTrust() const; + virtual QString getName() const; + virtual QString getEmail() const; + virtual QString getId() const; + virtual KGpgKeyNode *getKeyNode(void); + virtual const KGpgKeyNode *getKeyNode(void) const; + virtual KGpgKeyNode *getParentKeyNode() const; + virtual QString getComment() const; +}; + +#endif /* KGPGUIDNODE_H */ diff --git a/kgpg/core/convert.cpp b/kgpg/core/convert.cpp new file mode 100644 index 00000000..eaa0e14c --- /dev/null +++ b/kgpg/core/convert.cpp @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2006 Jimmy Gilles + * Copyright (C) 2010,2013,2014 Rolf Eike Beer + */ +/*************************************************************************** + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "convert.h" + +#include +#include + +#include "kgpgsettings.h" + +namespace KgpgCore +{ + +namespace Convert +{ + +QString toString(const KgpgKeyAlgo algorithm) +{ + switch (algorithm) { + case ALGO_RSA: + return i18nc("Encryption algorithm", "RSA"); + case ALGO_DSA: + return i18nc("Encryption algorithm", "DSA"); + case ALGO_ELGAMAL: + return i18nc("Encryption algorithm", "ElGamal"); + case ALGO_DSA_ELGAMAL: + return i18nc("Encryption algorithm", "DSA & ElGamal"); + case ALGO_RSA_RSA: + return i18nc("Encryption algorithm RSA, Signing algorithm RSA", "RSA & RSA"); + case ALGO_UNKNOWN: + default: + return i18nc("Unknown algorithm", "Unknown"); + } +} + +QString toString(const gpgme_validity_t ownertrust) +{ + switch (ownertrust) { + case GPGME_VALIDITY_UNDEFINED: + return i18n("Do not Know"); + case GPGME_VALIDITY_NEVER: + return i18n("Do NOT Trust"); + case GPGME_VALIDITY_MARGINAL: + return i18n("Marginally"); + case GPGME_VALIDITY_FULL: + return i18n("Fully"); + case GPGME_VALIDITY_ULTIMATE: + return i18n("Ultimately"); + case GPGME_VALIDITY_UNKNOWN: + default: + return i18nc("Unknown trust in key owner", "Unknown"); + } +} + +QString toString(const KgpgKeyTrust trust) +{ + switch (trust) { + case TRUST_INVALID: + return i18nc("Invalid key", "Invalid"); + case TRUST_DISABLED: + return i18nc("Disabled key", "Disabled"); + case TRUST_REVOKED: + return i18n("Revoked"); + case TRUST_EXPIRED: + return i18nc("Expired key", "Expired"); + case TRUST_UNDEFINED: + return i18nc("Undefined key trust", "Undefined"); + case TRUST_NONE: + return i18nc("No trust in key", "None"); + case TRUST_MARGINAL: + return i18nc("Marginal trust in key", "Marginal"); + case TRUST_FULL: + return i18nc("Full trust in key", "Full"); + case TRUST_ULTIMATE: + return i18nc("Ultimate trust in key", "Ultimate"); + case TRUST_UNKNOWN: + default: + return i18nc("Unknown trust in key", "Unknown"); + } +} + +QString toString(const KgpgCore::KgpgSubKeyType type) +{ + QStringList res; + + if (type & SKT_SIGNATURE) + res << i18nc("key capability", "Signature"); + if (type & SKT_ENCRYPTION) + res << i18nc("key capability", "Encryption"); + if (type & SKT_AUTHENTICATION) + res << i18nc("key capability", "Authentication"); + if (type & SKT_CERTIFICATION) + res << i18nc("key capability", "Certification"); + + return res.join(i18nc("used to join a list of key types, e.g. 'encryption, signature'", ", ")); +} + +KgpgKeyAlgo toAlgo(const uint v) +{ + switch (v) { + case GPGME_PK_RSA: + return ALGO_RSA; + case GPGME_PK_ELG_E: + case GPGME_PK_ELG: + return ALGO_ELGAMAL; + case GPGME_PK_DSA: + return ALGO_DSA; + default: + return ALGO_UNKNOWN; + } +} + +KgpgKeyAlgo toAlgo(const QString &s) +{ + bool b; + unsigned int u = s.toUInt(&b); + return b ? toAlgo(u) : ALGO_UNKNOWN; +} + +KgpgKeyTrust toTrust(const QChar &c) +{ + switch (c.toAscii()) { + case 'o': + return TRUST_UNKNOWN; + case 'i': + return TRUST_INVALID; + case 'd': + return TRUST_DISABLED; + case 'r': + return TRUST_REVOKED; + case 'e': + return TRUST_EXPIRED; + case 'q': + return TRUST_UNDEFINED; + case 'n': + return TRUST_NONE; + case 'm': + return TRUST_MARGINAL; + case 'f': + return TRUST_FULL; + case 'u': + return TRUST_ULTIMATE; + default: + return TRUST_UNKNOWN; + } +} + +KgpgKeyTrust toTrust(const QString &s) +{ + return s.isEmpty() ? TRUST_UNKNOWN : toTrust(s[0]); +} + +gpgme_validity_t toOwnerTrust(const QChar &c) +{ + switch (c.toAscii()) { + case 'n': + return GPGME_VALIDITY_NEVER; + case 'm': + return GPGME_VALIDITY_MARGINAL; + case 'f': + return GPGME_VALIDITY_FULL; + case 'u': + return GPGME_VALIDITY_ULTIMATE; + default: + return GPGME_VALIDITY_UNDEFINED; + } +} + +KgpgSubKeyType toSubType(const QString& capString, bool upper) +{ + KgpgSubKeyType ret; + + foreach (const QChar &ch, capString) { + switch (ch.toAscii()) { + case 's': + case 'S': + if (upper != ch.isUpper()) + continue; + ret |= SKT_SIGNATURE; + break; + case 'e': + case 'E': + if (upper != ch.isUpper()) + continue; + ret |= SKT_ENCRYPTION; + break; + case 'a': + case 'A': + if (upper != ch.isUpper()) + continue; + ret |= SKT_AUTHENTICATION; + break; + case 'c': + case 'C': + if (upper != ch.isUpper()) + continue; + ret |= SKT_CERTIFICATION; + break; + case 'D': // disabled key + case '?': // unknown to GnuPG + continue; + default: + kDebug(2100) << "unknown capability letter" << ch + << "in cap string" << capString; + } + } + + return ret; +} + +} // namespace Convert + +} // namespace KgpgCore diff --git a/kgpg/core/convert.h b/kgpg/core/convert.h new file mode 100644 index 00000000..b8f0a859 --- /dev/null +++ b/kgpg/core/convert.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2006 Jimmy Gilles + * Copyright (C) 2010,2013,2014 Rolf Eike Beer + */ +/*************************************************************************** + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef CONVERT_H +#define CONVERT_H + +#include "kgpgkey.h" + +#include + +class QString; +class QPixmap; + +namespace KgpgCore +{ + +namespace Convert +{ + QString toString(const KgpgCore::KgpgKeyAlgo algorithm); + QString toString(const gpgme_validity_t ownertrust); + QString toString(const KgpgCore::KgpgKeyTrust trust); + QString toString(const KgpgCore::KgpgSubKeyType type); + KgpgKeyAlgo toAlgo(const uint v); + KgpgKeyAlgo toAlgo(const QString &s); + KgpgKeyTrust toTrust(const QChar &c); + KgpgKeyTrust toTrust(const QString &s); + gpgme_validity_t toOwnerTrust(const QChar &c); + /** + * @brief parse the GnuPG capabilities field + * @param capString the capability string as returned by GnuPG + * @param upper if the uppercase or lowercase version should be parsed + */ + KgpgSubKeyType toSubType(const QString &capString, bool upper); +} + +} // namespace KgpgCore + +#endif // CONVERT_H diff --git a/kgpg/core/emailvalidator.cpp b/kgpg/core/emailvalidator.cpp new file mode 100644 index 00000000..be91324d --- /dev/null +++ b/kgpg/core/emailvalidator.cpp @@ -0,0 +1,38 @@ +/*************************************************************************** + * Copyright (C) 2006 by Jimmy Gilles * + * jimmygilles@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "emailvalidator.h" + +#include + +#include "kpimutils/email.h" + +namespace KgpgCore +{ + +QValidator::State EmailValidator::validate(QString &input, int &) const +{ + if (KPIMUtils::isValidSimpleAddress(input)) + return QValidator::Acceptable; + else + return QValidator::Invalid; +} + +} // namespace KgpgCore diff --git a/kgpg/core/emailvalidator.h b/kgpg/core/emailvalidator.h new file mode 100644 index 00000000..65503001 --- /dev/null +++ b/kgpg/core/emailvalidator.h @@ -0,0 +1,38 @@ +/*************************************************************************** + * Copyright (C) 2006 by Jimmy Gilles * + * jimmygilles@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef EMAILVALIDATOR_H +#define EMAILVALIDATOR_H + +#include + +namespace KgpgCore +{ + +class EmailValidator : public QValidator +{ +public: + explicit EmailValidator(QObject *parent = 0) : QValidator(parent) { } + virtual QValidator::State validate(QString &input, int &pos) const; +}; + +} // namespace KgpgCore + +#endif // EMAILVALIDATOR_H diff --git a/kgpg/core/images.cpp b/kgpg/core/images.cpp new file mode 100644 index 00000000..4f74e6b7 --- /dev/null +++ b/kgpg/core/images.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2006 Jimmy Gilles + * Copyright (C) 2013 Rolf Eike Beer + */ +/*************************************************************************** + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "images.h" + +#include + +namespace KgpgCore +{ + +namespace Images +{ + +QPixmap single() +{ + static QPixmap single; + if (single.isNull()) + single = KIconLoader::global()->loadIcon(QLatin1String( "key-single" ), KIconLoader::Small, 20); + return single; +} + +QPixmap pair() +{ + static QPixmap pair; + if (pair.isNull()) + pair = KIconLoader::global()->loadIcon(QLatin1String( "key-pair" ), KIconLoader::Small, 20); + return pair; +} + +QPixmap group() +{ + static QPixmap group; + if (group.isNull()) + group = KIconLoader::global()->loadIcon(QLatin1String( "key-group" ), KIconLoader::Small, 20); + return group; +} + +QPixmap orphan() +{ + static QPixmap oprpan; + if (oprpan.isNull()) + oprpan = KIconLoader::global()->loadIcon(QLatin1String( "key-orphan" ), KIconLoader::Small, 20); + return oprpan; +} + +QPixmap signature() +{ + static QPixmap signature; + if (signature.isNull()) + signature = KIconLoader::global()->loadIcon(QLatin1String( "application-pgp-signature" ), KIconLoader::Small, 20); + return signature; +} + +QPixmap userId() +{ + static QPixmap userid; + if (userid.isNull()) + userid = KIconLoader::global()->loadIcon(QLatin1String( "x-office-contact" ), KIconLoader::Small, 20); + return userid; +} + +QPixmap photo() +{ + static QPixmap photo; + if (photo.isNull()) + photo = KIconLoader::global()->loadIcon(QLatin1String( "image-x-generic" ), KIconLoader::Small, 20); + return photo; +} + +QPixmap revoke() +{ + static QPixmap revoke; + if (revoke.isNull()) + revoke = KIconLoader::global()->loadIcon(QLatin1String( "dialog-error" ), KIconLoader::Small, 20); + return revoke; +} + +QPixmap kgpg() +{ + static QPixmap kgpg; + if (kgpg.isNull()) + kgpg = KIconLoader::global()->loadIcon(QLatin1String( "kgpg" ), KIconLoader::Desktop); + return kgpg; +} + +} // namespace Images + +} // namespace KgpgCore diff --git a/kgpg/core/images.h b/kgpg/core/images.h new file mode 100644 index 00000000..4f8c9d42 --- /dev/null +++ b/kgpg/core/images.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2006 Jimmy Gilles + * Copyright (C) 2013 Rolf Eike Beer + */ +/*************************************************************************** + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef IMAGES_H +#define IMAGES_H + +#include + +namespace KgpgCore +{ + +namespace Images +{ + QPixmap single(); + QPixmap pair(); + QPixmap group(); + QPixmap orphan(); + QPixmap signature(); + QPixmap userId(); + QPixmap photo(); + QPixmap revoke(); + + /* Desktop image */ + QPixmap kgpg(); +} + +} // namespace KgpgCore + +#endif // IMAGES_H diff --git a/kgpg/core/kgpgkey.cpp b/kgpg/core/kgpgkey.cpp new file mode 100644 index 00000000..3d49715b --- /dev/null +++ b/kgpg/core/kgpgkey.cpp @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2006,2007 Jimmy Gilles + * Copyright (C) 2007,2008,2009,2010,2012,2013,2014 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgkey.h" + +#include "convert.h" + +#include + +#include + +namespace KgpgCore +{ + +//BEGIN KeySub +KgpgKeySubPrivate::KgpgKeySubPrivate(const QString &id, const uint size, const KgpgKeyTrust trust, const KgpgKeyAlgo algo, + const KgpgSubKeyType type, const QDateTime &date) + : gpgsubvalid(false), + gpgsubid(id), + gpgsubsize(size), + gpgsubcreation(date), + gpgsubtrust(trust), + gpgsubalgo(algo), + gpgsubtype(type) +{ +} + +bool KgpgKeySubPrivate::operator==(const KgpgKeySubPrivate &other) const +{ + if (gpgsubvalid != other.gpgsubvalid) return false; + if (gpgsubalgo != other.gpgsubalgo) return false; + if (gpgsubid != other.gpgsubid) return false; + if (gpgsubsize != other.gpgsubsize) return false; + if (gpgsubexpiration != other.gpgsubexpiration) return false; + if (gpgsubcreation != other.gpgsubcreation) return false; + if (gpgsubtrust != other.gpgsubtrust) return false; + if (gpgsubtype != other.gpgsubtype) return false; + return true; +} + +KgpgKeySub::KgpgKeySub(const QString &id, const uint size, const KgpgKeyTrust trust, const KgpgKeyAlgo algo, const KgpgSubKeyType type, + const QDateTime &date) + : d(new KgpgKeySubPrivate(id, size, trust, algo, type, date)) +{ +} + +KgpgKeySub::KgpgKeySub(const KgpgKeySub &other) +{ + d = other.d; +} + +void KgpgKeySub::setExpiration(const QDateTime &date) +{ + d->gpgsubexpiration = date; +} + +void KgpgKeySub::setValid(const bool valid) +{ + d->gpgsubvalid = valid; +} + +QString KgpgKeySub::id() const +{ + return d->gpgsubid; +} + +uint KgpgKeySub::size() const +{ + return d->gpgsubsize; +} + +bool KgpgKeySub::unlimited() const +{ + return d->gpgsubexpiration.isNull(); +} + +QDateTime KgpgKeySub::expirationDate() const +{ + return d->gpgsubexpiration; +} + +QDateTime KgpgKeySub::creationDate() const +{ + return d->gpgsubcreation; +} + +KgpgKeyTrust KgpgKeySub::trust() const +{ + return d->gpgsubtrust; +} + +KgpgKeyAlgo KgpgKeySub::algorithm() const +{ + return d->gpgsubalgo; +} + +bool KgpgKeySub::valid() const +{ + return d->gpgsubvalid; +} + +KgpgSubKeyType KgpgKeySub::type() const +{ + return d->gpgsubtype; +} + +bool KgpgKeySub::operator==(const KgpgKeySub &other) const +{ + if (d == other.d) return true; + if ((*d) == (*(other.d))) return true; + return false; +} + +KgpgKeySub& KgpgKeySub::operator=(const KgpgKeySub &other) +{ + d = other.d; + return *this; +} + +//END KeySub + + +//BEGIN Key + +KgpgKeyPrivate::KgpgKeyPrivate(const QString &id, const uint size, const KgpgKeyTrust trust, const KgpgKeyAlgo algo, const KgpgSubKeyType subtype, + const KgpgSubKeyType keytype, const QDateTime &creationDate) + : gpgkeysecret(false), + gpgkeyvalid(false), + gpgkeyid(id), + gpgkeysize(size), + gpgkeytrust(trust), + gpgkeycreation(creationDate), + gpgkeyalgo(algo), + gpgsubtype(subtype), + gpgkeytype(keytype), + gpgsublist(new KgpgKeySubList()) +{ +} + +bool KgpgKeyPrivate::operator==(const KgpgKeyPrivate &other) const +{ + if (gpgkeysecret != other.gpgkeysecret) return false; + if (gpgkeyvalid != other.gpgkeyvalid) return false; + if (gpgkeymail != other.gpgkeymail) return false; + if (gpgkeyname != other.gpgkeyname) return false; + if (gpgkeycomment != other.gpgkeycomment) return false; + if (gpgkeyfingerprint != other.gpgkeyfingerprint) return false; + if (gpgkeyid != other.gpgkeyid) return false; + if (gpgkeysize != other.gpgkeysize) return false; + if (gpgkeyownertrust != other.gpgkeyownertrust) return false; + if (gpgkeytrust != other.gpgkeytrust) return false; + if (gpgkeycreation != other.gpgkeycreation) return false; + if (gpgkeyexpiration != other.gpgkeyexpiration) return false; + if (gpgkeyalgo != other.gpgkeyalgo) return false; + if (gpgsubtype != other.gpgsubtype) return false; + if (gpgkeytype != other.gpgkeytype) return false; + if (gpgsublist != other.gpgsublist) return false; + return true; +} + +KgpgKey::KgpgKey(const QString &id, const uint size, const KgpgKeyTrust trust, const KgpgKeyAlgo algo, const KgpgSubKeyType subtype, + const KgpgSubKeyType keytype, const QDateTime &creationDate) + : d(new KgpgKeyPrivate(id, size, trust, algo, subtype, keytype, creationDate)) +{ +} + +KgpgKey::KgpgKey(const KgpgKey &other) +{ + d = other.d; +} + +void KgpgKey::setSecret(const bool secret) +{ + d->gpgkeysecret = secret; +} + +void KgpgKey::setValid(const bool valid) +{ + d->gpgkeyvalid = valid; +} + +void KgpgKey::setName(const QString &name) +{ + d->gpgkeyname = name; +} + +void KgpgKey::setEmail(const QString &email) +{ + d->gpgkeymail = email; +} + +void KgpgKey::setComment(const QString &comment) +{ + d->gpgkeycomment = comment; +} + +void KgpgKey::setFingerprint(const QString &fingerprint) +{ + d->gpgkeyfingerprint = fingerprint; +} + +void KgpgKey::setOwnerTrust(const gpgme_validity_t owtrust) +{ + d->gpgkeyownertrust = owtrust; +} + +void KgpgKey::setExpiration(const QDateTime &date) +{ + d->gpgkeyexpiration = date; +} + +bool KgpgKey::secret() const +{ + return d->gpgkeysecret; +} + +bool KgpgKey::valid() const +{ + return d->gpgkeyvalid; +} + +QString KgpgKey::id() const +{ + return d->gpgkeyid.right(8); +} + +QString KgpgKey::fullId() const +{ + return d->gpgkeyid; +} + +QString KgpgKey::name() const +{ + return d->gpgkeyname; +} + +QString KgpgKey::email() const +{ + return d->gpgkeymail; +} + +QString KgpgKey::comment() const +{ + return d->gpgkeycomment; +} + +const QString &KgpgKey::fingerprint() const +{ + return d->gpgkeyfingerprint; +} + +uint KgpgKey::size() const +{ + return d->gpgkeysize; +} + +uint KgpgKey::encryptionSize() const +{ + const KgpgKeySub *enc = NULL; + // Get the first encryption subkey + for (int i = 0; i < d->gpgsublist->count(); ++i) { + const KgpgKeySub &temp = d->gpgsublist->at(i); + if (temp.type() & SKT_ENCRYPTION) { + // if the first encryption subkey is expired + // check if there is one that is not + if (temp.trust() > TRUST_EXPIRED) + return temp.size(); + if (enc == NULL) + enc = &temp; + } + } + if (enc != NULL) + return enc->size(); + return 0; +} + +gpgme_validity_t KgpgKey::ownerTrust() const +{ + return d->gpgkeyownertrust; +} + +KgpgKeyTrust KgpgKey::trust() const +{ + return d->gpgkeytrust; +} + +QDateTime KgpgKey::creationDate() const +{ + return d->gpgkeycreation; +} + +QDateTime KgpgKey::expirationDate() const +{ + return d->gpgkeyexpiration; +} + +bool KgpgKey::unlimited() const +{ + return d->gpgkeyexpiration.isNull(); +} + +KgpgKeyAlgo KgpgKey::algorithm() const +{ + return d->gpgkeyalgo; +} + +KgpgKeyAlgo KgpgKey::encryptionAlgorithm() const +{ + // Get the first encryption subkey + for (int i = 0; i < d->gpgsublist->count(); ++i) { + KgpgKeySub temp = d->gpgsublist->at(i); + if (temp.type() & SKT_ENCRYPTION) { + return temp.algorithm(); + } + } + return ALGO_UNKNOWN; +} + +KgpgSubKeyType KgpgKey::subtype() const +{ + return d->gpgsubtype; +} + +KgpgSubKeyType KgpgKey::keytype() const +{ + return d->gpgkeytype; +} + +KgpgKeySubListPtr KgpgKey::subList() const +{ + return d->gpgsublist; +} + +bool KgpgKey::operator==(const KgpgKey &other) const +{ + if (d == other.d) return true; + if ((*d) == (*(other.d))) return true; + return false; +} + +KgpgKey& KgpgKey::operator=(const KgpgKey &other) +{ + d = other.d; + return *this; +} + +KgpgKeyList::operator QStringList() const +{ + QStringList res; + foreach(const KgpgKey &key, *this) + res << key.fullId(); + return res; +} + +//END Key + +} // namespace KgpgCore diff --git a/kgpg/core/kgpgkey.h b/kgpg/core/kgpgkey.h new file mode 100644 index 00000000..1e345f8d --- /dev/null +++ b/kgpg/core/kgpgkey.h @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2006,2007 Jimmy Gilles + * Copyright (C) 2007,2008,2009,2010,2012,2013,2014 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGKEY_H +#define KGPGKEY_H + +#include +#include +#include +#include +#include +#include +#include + +class QStringList; + +namespace KgpgCore +{ + +//BEGIN Enums + +enum KgpgKeyAlgoFlag +{ + ALGO_UNKNOWN = 0, + ALGO_RSA = 1, + ALGO_DSA = 2, + ALGO_ELGAMAL = 4, + ALGO_DSA_ELGAMAL = ALGO_DSA | ALGO_ELGAMAL, + ALGO_RSA_RSA = 0x10001 +}; +Q_DECLARE_FLAGS(KgpgKeyAlgo, KgpgKeyAlgoFlag) +Q_DECLARE_OPERATORS_FOR_FLAGS(KgpgKeyAlgo) + +/*! \brief trust levels of keys, uids and uats + * + * These values represent the trust that you have in a public key or obe if it's + * user ids or attributes (i.e. photo ids). They are more or less ordered by + * the level of trust. Every value but the first and the last matches one trust + * value that is + */ +enum KgpgKeyTrustFlag +{ + TRUST_MINIMUM = 0, //!< internal value for use in filters + TRUST_INVALID = 1, //!< key is invalid + TRUST_DISABLED = 2, //!< key is disabled by user (not owner) + TRUST_REVOKED = 3, //!< key is revoked by owner + TRUST_EXPIRED = 4, //!< key is beyond it's expiry date + TRUST_UNDEFINED = 5, //!< trust value undefined (i.e. you did not set a trust level) + TRUST_UNKNOWN = 6, //!< trust value unknown (i.e. no entry in gpg's trust database) + TRUST_NONE = 7, //!< there is no trusted path to this key + TRUST_MARGINAL = 8, //!< there is a minimal level of trust + TRUST_FULL = 9, //!< you can fully trust this key + TRUST_ULTIMATE = 10, //!< this key has highest possible level of trust (e.g. your own secret keys) + TRUST_NOKEY = 11 //!< internal value, e.g. for key groups +}; +Q_DECLARE_FLAGS(KgpgKeyTrust, KgpgKeyTrustFlag) +Q_DECLARE_OPERATORS_FOR_FLAGS(KgpgKeyTrust) + +enum KgpgSubKeyTypeFlag +{ + SKT_ENCRYPTION = 0x1, + SKT_SIGNATURE = 0x2, + SKT_AUTHENTICATION = 0x4, + SKT_CERTIFICATION = 0x8 +}; +Q_DECLARE_FLAGS(KgpgSubKeyType, KgpgSubKeyTypeFlag) +Q_DECLARE_OPERATORS_FOR_FLAGS(KgpgSubKeyType) + +/*! \brief types of items in the item models + * + * Every item in the item models is of one of the following types. Some of the + * items can have properties of more than one basic type, e.g. a key pair can + * act both as a secret and a public key. Because of this the value for key + * pairs is a composite of the two "elementary" types for secret and public + * keys. Other compositions than the ones defined here must not be used to set + * an item type, but may of course be used as a mask for comparison. + */ +enum KgpgItemTypeFlag +{ + ITYPE_GROUP = 1, //!< the element is a GnuPG key group + ITYPE_SECRET = 2, //!< secret key + ITYPE_PUBLIC = 4, //!< public key + ITYPE_PAIR = ITYPE_SECRET | ITYPE_PUBLIC, //!< key pair + ITYPE_GSECRET = ITYPE_GROUP | ITYPE_SECRET, //!< secret key as member of a key group + ITYPE_GPUBLIC = ITYPE_GROUP | ITYPE_PUBLIC, //!< public key as member of a key group + ITYPE_GPAIR = ITYPE_GROUP | ITYPE_PAIR, //!< key pair as member of a key group + ITYPE_SUB = 8, //!< subkey of a public or secret key + ITYPE_UID = 16, //!< additional user id + ITYPE_UAT = 32, //!< user attribute to a key (i.e. photo id) + ITYPE_REVSIGN = 64, //!< revokation signature + ITYPE_SIGN = 128 //!< signature (to a key, uid or uat) +}; +Q_DECLARE_FLAGS(KgpgItemType, KgpgItemTypeFlag) +Q_DECLARE_OPERATORS_FOR_FLAGS(KgpgItemType) + +//END Enums + +//BEGIN KeySub + +class KgpgKeySubPrivate : public QSharedData +{ + KgpgKeySubPrivate(); +public: + KgpgKeySubPrivate(const QString &id, const uint size, const KgpgKeyTrust trust, const KgpgKeyAlgo algo, const KgpgSubKeyType type, + const QDateTime &date); + + bool gpgsubvalid; + const QString gpgsubid; + const uint gpgsubsize; + QDateTime gpgsubexpiration; + const QDateTime gpgsubcreation; + const KgpgKeyTrust gpgsubtrust; + const KgpgKeyAlgo gpgsubalgo; + const KgpgSubKeyType gpgsubtype; + + bool operator==(const KgpgKeySubPrivate &other) const; + inline bool operator!=(const KgpgKeySubPrivate &other) const + { return !operator==(other); } +}; + +class KgpgKeySub +{ + KgpgKeySub(); +public: + KgpgKeySub(const QString &id, const uint size, const KgpgKeyTrust trust, const KgpgKeyAlgo algo, const KgpgSubKeyType type, + const QDateTime &date); + KgpgKeySub(const KgpgKeySub &other); + + void setExpiration(const QDateTime &date); + void setValid(const bool valid); // FIXME : is it possible to have a subkey that is not valid (disabled)? Please give an example. Thx. If not, this method should be removed. + + QString id() const; + uint size() const; + bool unlimited() const; + QDateTime expirationDate() const; + QDateTime creationDate() const; + KgpgKeyTrust trust() const; + KgpgKeyAlgo algorithm() const; + bool valid() const; + KgpgSubKeyType type() const; + + bool operator==(const KgpgKeySub &other) const; + inline bool operator!=(const KgpgKeySub &other) const + { return !operator==(other); } + KgpgKeySub& operator=(const KgpgKeySub &other); + +private: + QSharedDataPointer d; +}; + +class KgpgKeySubList : public QList, public QObject +{ +public: + inline KgpgKeySubList() { } + inline explicit KgpgKeySubList(const KgpgKeySub &sub) { append(sub); } + inline KgpgKeySubList(const KgpgKeySubList &other) : QList(other), QObject() { } + inline KgpgKeySubList(const QList &other) : QList(other), QObject() { } + + inline KgpgKeySubList operator+(const KgpgKeySubList &other) const + { + KgpgKeySubList n = *this; + n += other; + return n; + } + + inline KgpgKeySubList &operator<<(KgpgKeySub sub) + { + append(sub); + return *this; + } + + inline KgpgKeySubList &operator<<(const KgpgKeySubList &l) + { + *this += l; + return *this; + } +}; +typedef QPointer KgpgKeySubListPtr; + +//END KeySub + + +//BEGIN Key + +class KgpgKeyPrivate : public QSharedData +{ + KgpgKeyPrivate(); +public: + /** + * @brief constructor + * @param id id of the key (i.e. fingerprint) + * @param size length of the key in bits + * @param trust trust to this key + * @param algo public key algorithm + * @param subtype key capabilities of this subkey + * @param keytype key capabilities of this and all subkeys combined + * @param creationDate date of key creation + */ + KgpgKeyPrivate(const QString& id, const uint size, const KgpgCore::KgpgKeyTrust trust, const KgpgCore::KgpgKeyAlgo algo, + const KgpgCore::KgpgSubKeyType subtype, const KgpgCore::KgpgSubKeyType keytype, const QDateTime& creationDate); + + bool gpgkeysecret; + bool gpgkeyvalid; + QString gpgkeymail; + QString gpgkeyname; + QString gpgkeycomment; + QString gpgkeyfingerprint; + const QString gpgkeyid; + const uint gpgkeysize; + gpgme_validity_t gpgkeyownertrust; + const KgpgKeyTrust gpgkeytrust; + const QDateTime gpgkeycreation; + QDateTime gpgkeyexpiration; + const KgpgKeyAlgo gpgkeyalgo; + const KgpgSubKeyType gpgsubtype; + const KgpgSubKeyType gpgkeytype; + + KgpgKeySubListPtr gpgsublist; + + bool operator==(const KgpgKeyPrivate &other) const; + inline bool operator!=(const KgpgKeyPrivate &other) const + { return !operator==(other); } +}; + +class KgpgKey +{ +public: + /** + * @brief constructor + * @param id id of the key (i.e. fingerprint) + * @param size length of the key in bits + * @param trust trust to this key + * @param algo public key algorithm + * @param subtype key capabilities of this subkey + * @param keytype key capabilities of this and all subkeys combined + * @param creationDate date of key creation + */ + KgpgKey(const QString &id, const uint size, const KgpgCore::KgpgKeyTrust trust, const KgpgCore::KgpgKeyAlgo algo, + const KgpgCore::KgpgSubKeyType subtype, const KgpgCore::KgpgSubKeyType keytype, const QDateTime& creationDate); + KgpgKey(const KgpgKey &other); + + void setSecret(const bool secret); + void setValid(const bool valid); + void setName(const QString &name); + void setEmail(const QString &email); + void setComment(const QString &comment); + void setFingerprint(const QString &fingerprint); + void setOwnerTrust(const gpgme_validity_t owtrust); + void setExpiration(const QDateTime &date); + + bool secret() const; + bool valid() const; + QString id() const; + QString fullId() const; + QString name() const; + QString email() const; + QString comment() const; + const QString &fingerprint() const; + uint size() const; + uint encryptionSize() const; + gpgme_validity_t ownerTrust() const; + KgpgKeyTrust trust() const; + QDateTime creationDate() const; + QDateTime expirationDate() const; + bool unlimited() const; + KgpgKeyAlgo algorithm() const; + KgpgKeyAlgo encryptionAlgorithm() const; + KgpgSubKeyType subtype() const; + KgpgSubKeyType keytype() const; + + KgpgKeySubListPtr subList() const; + + bool operator==(const KgpgKey &other) const; + inline bool operator!=(const KgpgKey &other) const + { return !operator==(other); } + KgpgKey& operator=(const KgpgKey &other); + +private: + QSharedDataPointer d; +}; + +class KgpgKeyList : public QList +{ +public: + inline KgpgKeyList() { } + inline explicit KgpgKeyList(const KgpgKey &key) { append(key); } + inline KgpgKeyList(const KgpgKeyList &other) : QList(other) { } + inline KgpgKeyList(const QList &other) : QList(other) { } + + inline KgpgKeyList& operator=(const KgpgKeyList &other) + { + QList::operator=(static_cast >(other)); + return *this; + } + + inline KgpgKeyList operator+(const KgpgKeyList &other) const + { + KgpgKeyList n = *this; + n += other; + return n; + } + + inline KgpgKeyList &operator<<(KgpgKey key) + { + append(key); + return *this; + } + + inline KgpgKeyList &operator<<(const KgpgKeyList &l) + { + *this += l; + return *this; + } + + operator QStringList() const; +}; + +//END Key + +} // namespace + +#endif // KGPGKEY_H diff --git a/kgpg/detailedconsole.cpp b/kgpg/detailedconsole.cpp new file mode 100644 index 00000000..5c417900 --- /dev/null +++ b/kgpg/detailedconsole.cpp @@ -0,0 +1,38 @@ +/*************************************************************************** + detailedconsole.cpp - description + ------------------- + begin : Mon Jul 8 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "detailedconsole.h" + +#include +#include + +KgpgDetailedInfo::KgpgDetailedInfo(QWidget *parent, const QString &boxLabel, const QString &errormessage, + const QStringList &keysList, const QString &caption) + : KDialog(parent) +{ + if (!caption.isEmpty()) + setCaption(caption); + else + setCaption(i18n("Info")); + setButtons(Details | Ok); + setDefaultButton(Ok); + setModal(true); + KMessageBox::createKMessageBox(this, QMessageBox::Information, // krazy:exclude=qtclasses + boxLabel, keysList, QString(), NULL, 0, errormessage); +} + +#include "detailedconsole.moc" diff --git a/kgpg/detailedconsole.h b/kgpg/detailedconsole.h new file mode 100644 index 00000000..131deeb9 --- /dev/null +++ b/kgpg/detailedconsole.h @@ -0,0 +1,31 @@ +/*************************************************************************** + detailledconsole.h - description + ------------------- + begin : Mon Jul 8 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGDETAILEDCONSOLE_H +#define KGPGDETAILEDCONSOLE_H + +#include + +class KgpgDetailedInfo : public KDialog +{ +public: + explicit KgpgDetailedInfo(QWidget *parent = 0, const QString &boxLabel = QString(), + const QString &errormessage = QString(), const QStringList &keysList = QStringList(), + const QString &caption = QString()); +}; + +#endif // KGPGDETAILEDCONSOLE_H diff --git a/kgpg/doc/CMakeLists.txt b/kgpg/doc/CMakeLists.txt new file mode 100644 index 00000000..f06b01cb --- /dev/null +++ b/kgpg/doc/CMakeLists.txt @@ -0,0 +1,3 @@ +########### install files ############### +# +kde4_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en SUBDIR kgpg) diff --git a/kgpg/doc/editor.png b/kgpg/doc/editor.png new file mode 100644 index 0000000000000000000000000000000000000000..e68fcfe3dd4f5ed6c0a0d01d4f30ac401b4a4531 GIT binary patch literal 53308 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

r?ZdxA_nboGY;7&XvKkv=BfLXqg>oc-r z<|i`*waWs-ckf&iKYzya(;eBIlDRr;S+Ur_-kbDtzSJ}@f>~a(E@{tMEh#K|XUV)- z-ZKK@wwz#VE%uSAxgRFYi^}MbqSpgs`aH~nWvqQM(OGPry#y?#y}0nTCeISO?=Eck zyF8ea@7wTk(C7QZEB8j8+Pk+IlRWOHVX@5q;z95BdyyhYKJAvj<2K{k_yM zziHln3sz<~Db3C?5-t%5(ALD#vRzc)>2qQ?)7mP237_yN(yQ{|&POAy%A(eN6x((z zo0?Qr0^(nM{jImHXg}2?9@{y$lpp0@QqlqW=CdD36pCuKRm;gVtPtY!ajyV zD`sRw`}hv<*Uu+;f4$V8bDZ{8UHdzcF^9N%t4UOtw(JN`Z$G7_^wldr6&gf^sZrYs zSsEk3E8Vsz@sL1)MhIA2!q=(wjtY-+lE^)F~7|+#XPh3RC7$GipRyf=k~N zw&;D9)G0>ShzPWGR{Wo;#Tqd)lNz*)CQ&-VAF)WarIS@fUivazr0$vS_yV{bK7;4M zLY@u=reg}h4Mv0R!Sl$)G}@($7cGze#0G(z$KxQ5Bbt-O!HPC|aWX%zs*0{Df0-(wPFNm`;s+`j2c)D7wWQyolv&M&3WK0@Lth4ToH0L8@pBtB+OhnjsbHuBvwTC2xQ z|FI8|&g&l?6ht~>g_pj3in;2Up1R4GnO^@Adfn-seE)XlljP|xdqB{T9-2@hC7t>> zV#Gy+w$7s#Ne1?+jGh#-qDkN><{XjUlzLrbQMwr(M(PO7_3(KfEHE7mOm{J=sGy`9 zv0JBAtCdQG>n@As!6uZOmzS3!pra0t{$yN|d-MpO$940Rq$EEfG`&gaV0uF71Exz( z#v`T=o{-KI7$~N%u2&D<0b?Clv7=eZKDu@v%YX_yj8}rF`C=oE9eAc&N#3!<=-nl| zW1{w;5k`dR|5kc^+i54A;S@^MCy^CieT+$8H&6ObM7h&VvGos^boluT7%M{((3KC@ z2N#hYf^ntN!7F7_XxiZ3CPwD!g(9`qqA)#)6qVEm{s|&*Ieco*g9WC8f$2jVrRQ3W zM#SfTdF2Y?dqF{dX-TnGL;R{6r?VrE_8ERHCtcf)h{%j{+JS7H5Y)UM6T7!lHduQp zP7O}XZjkUwb}#XpHXFA_8bbewMLvGLr z%HD{umm?f~a1o(*&GrsT-&ZFRHfB;oUZ%^Gb<5|+m56obF%hZ#86^8X(jtM$_3-IN zF4zfw!h?b7LoPSS=Ws4x{A+z(Ey~TYg>^^01!q#zma{!=$)dT_r%jt3{^DCSw#MS9 zs|fP9C(QMoxBQQv9*PT2Wx6t5^01ihxXh>-+u}DfRD&O9eVzeW|Kg{=(M~V*?o?QHId-t-_L=*>L{3N~uCNA7+qA#GY;KH3TK7zP%BkbH5 z#1?~5u_8BX3I>LYfg%lvq-8oxFEfXusmR4rfm%r1_ynG$Nn^Ajk%(dDe913OyJ>R% zi!akNCsUa2h)vg$$(cvd$e)j+()UR9MSA^x*3PUr_TPPTG1YnF@`bafTUySXyL9cY zW&e-g%TD~t`HRDSFgvqqUM9V-QJQkt&yJb64il2B6CKk9tn zYQ5Rsekb9{qPnwvH34>X!zz3R*rd&P%Av7Y$7bgUG>32J&F?{-I{YE|Z3r=#Ou zB+{9+Gxl=2aX4M0>4#jsvQhvisOj2rdMP#c_T|uk&bl&wng9^&rfWPSyYaa2oa?01 zOAGU<I&;PuF`zUwfXoY7Ch$=aDh-j&j0 zr2zh2(~Tbl0MK**08Iw~&~yL*O$Pv)4gjF(005c}0HEmr0GbW}G#vmy(*Xc99RNVn z0f43h0BAY@fTjZgXgUC(Y10o5_LcF20DurU*sqCN?VB=w5C9MYwOUnFtGmkhK>$Dq zRQGno-tJdr{2%}z1a@{RqEaa<;|Bo%AyC==BDTw0%J@M5KnRq}TViW-Lm58^00;ry zbFsPcSs6bF00@Eg^)<2fsice_1ONn`yW;4ci&E*MGJX~S5b&U9`}G%_uEqQ;%c|mYS|NxEc(9Zn?54|2-Y6DCw*-y@3$@5t<&Q_0gZFvp`1;Ys{jB1 M07*qoM6N<$g100#-v9sr literal 0 HcmV?d00001 diff --git a/kgpg/doc/index.docbook b/kgpg/doc/index.docbook new file mode 100644 index 00000000..f087dfc8 --- /dev/null +++ b/kgpg/doc/index.docbook @@ -0,0 +1,545 @@ + + + + + +]> + + + + +The &kgpg; Handbook + + + +Jean-Baptiste +Mardelle + +

bj@altern.org
+ + + +Rolf Eike +Beer + +
kde@opensource.sf-tec.de
+
+
+ + + + + + +2002 +2007 +2008 +2009 +2010 +Jean-Baptiste Mardelle +Rolf Eike Beer + + +&FDLNotice; + +2013-05-25 +2.9.50 (&kde; 4.11) + + + +&kgpg; is a simple graphical interface for GnuPG (http://gnupg.org). + + + + +KDE +KGpg +encryption +gpg +pgp +security + + + + + +Introduction + + +&kgpg; is a simple interface for GnuPG, a powerful encryption utility. GnuPG (also known as gpg) is included in most distributions and should be installed on your system. You can get the latest version on http://gnupg.org. + +With &kgpg; you will be able to encrypt and decrypt your files and emails, allowing much more secure communications. A mini howto on encryption with gpg is available on GnuPG's web site. + + +With &kgpg;, you don't need to remember gpg's command lines and options. Almost everything can be done with a few mouse clicks. + + + + +Getting Started + +Here is a list of &kgpg;'s main components: + + + + +System Tray Icon + + + +&kgpg; system tray applet + + + + + + + +When you start &kgpg;, a system tray icon will appear. A &LMB; +click will open the Key Manager window, while a &RMB; click will open a menu allowing quick access to some important features. If you prefer other options you can change the &LMB; action to show the editor or completely disable the system tray icon using the settings dialog. + +Please note that the system tray icon of &kgpg; is marked as "inactive" basically all the time. Since the system tray applet will usually hide inactive icons the one of &kgpg; will not be shown until you explicitly request it. For details please have a look at the &plasma; documentation. + + + + + + +Key Manager Window + + + +Key manager window + + + + + + +That's the central place to manage your keys. To open the Key Manager window, click with the &LMB; on &kgpg;'s applet. +You can import, export, sign and edit your keys. Most actions can be performed with a &RMB; click on a key. + + + + + +Editor Window + + + +Editor window + + + + + + +It's a simple text editor, where you can type or paste text to encrypt/decrypt it. To open the editor, click with the &RMB; on &kgpg;'s applet. + + + + + + +File manager integration + + +&kgpg; is integrated in &konqueror; and Dolphin. It means that when you right click on a file, you can choose + ActionsEncrypt +File to encrypt a file. You can decrypt a file with a &LMB; click. + + + + + + + + + +Using &kgpg; + + +There are two ways to encrypt your data: + +Symmetrical encryption: your data is just encrypted with a password. Anybody who has a computer with gpg can decrypt your message if you give him/her the password. To perform a symmetrical encryption, choose "symmetrical encryption" in the options box when asked to choose an encryption key. +Key encryption: you must first create your key pair (secret key and public key) and give a passphrase. Keep your secret key in a safe place, and exchange your public key with your friends. Then, if you want to send an encrypted message to Alex, you must encrypt the message with Alex's public key. To decrypt the message, the recipient will need Alex's secret key and passphrase. + + +Key encryption is a bit more complicated (you must exchange keys with your friends) but safer. Remember that if you encrypt a message with someone else's key, you will not be able to decrypt it. You can only decrypt messages that have been encrypted with your public key. + + +Generating a key + +If you don't have a key, &kgpg; will automatically pop up +the key generation dialog at the first startup. You can also access it +in the Key Manager from +KeysGenerate Key +Pair. + + +Key generation dialog + + + + + + +Simply enter your name, Email address and click +Ok. This will generate a standard gpg key. If +you want more options, you can click on the Expert Mode button, which +will bring up a &konsole; window with all of gpg's options. +Many people play around with their first +key, generate bad user ids, add comments they later regret or simply forget their +passphrase. To avoid such keys to stay valid forever it's usually a good idea +to limit the lifetime to some 12 month. You can modify the lifetime of your +secret keys later using the key properties window. + + + + +Revoking a key + +A key pair that has expired can be brought back into an operational state +as long as you have access to the private key and the passphrase. To +reliably render a key unusable you need to revoke it. Revoking is done by +adding a special revocation signature to the key. + +This revocation signature can be created together with the key. In this +case it is stored in a separate file. This file can later be imported into +the keyring and is then attached to the key rendering it unusable. Please +note that to import this signature to the key no password is required. +Therefore you should store this revocation signature in a safe place, +usually one that is different from you key pair. It is a good advise to +use a place that is detached from your computer, either copy it to an +external storage device like an USB stick or print it out. + +If you have not created such a detached revocation on key creation you can +create such a revocation signature at any time choosing Keys +Revoke key, +optionally importing it to your keyring immediately. + + + + +Encrypting Your Data + + + +Encrypting a file from &konqueror; or Dolphin + +Click on the file you want to encrypt with the &RMB;. Choose ActionsEncrypt File in the pop up menu. You will then be prompted with the Public key selection dialog. Choose the key of the recipient and click Encrypt. The encrypted file will be saved with a .asc or .gpg extension depending on whether you chose ASCII armored encryption or not. ASCII encrypted files only use readable characters to represent the data resulting in files that are more robust when copied around or sent by mail but are one third larger. + + +Here is a screen shot of the key selection window + + + + + + + + + +Encrypting a text with &kgpg;'s applet + +You can encrypt the contents of the clipboard by selecting the +Encrypt clipboard item in applet menu. When you +choose Sign clipboard then the text will be signed +instead. Both actions will import the current clipboard contents into an +editor window, perform the requested action and +paste the contents back into the editor. + + + + +Encrypting text from &kgpg;'s editor + +This is as simple as clicking on the +Encrypt button. You will then be prompted with +the Public key selection dialog. Choose your key and click +Ok. The encrypted message will +appear in the editor window. + +Usually you can only encrypt files with keys that are trusted by +you. Since you sometimes want to just send a confident note to some random +people you are aware of having a GPG key you can set the option +Allow encryption with untrusted keys. + +To make sure that you can decrypt every file you have encrypted even if +they are encrypted with someone else's key you can use the options +Always encrypt with and Encrypt files with +which are available in the KGpg configuration. + +For more information on the encryption options ASCII +armor, Allow encryption with untrusted keys and +Symmetrical encryption, please refer to gpg's +documentation or man pages. + + + + + +Decrypting Your Data + + + +Decrypting a file from &konqueror; or Dolphin +Left click on the file you want to +decrypt. Enter your passphrase and it will be decrypted. You can also +drag an encrypted text file and drop it into &kgpg;'s editor window. It +will then ask the passphrase and open the decrypted text in &kgpg;'s +editor. You can even drop remote files ! You can also use the +FileDecrypt +File and choose a file to decrypt. + + + + +Decrypting text with &kgpg;'s applet + +You can also decrypt the contents of the clipboard with the +decrypt clipboard menu +entry of the &kgpg; applet. An editor window +will show up with the decrypted text. + + + + +Decrypting a text from the editor + +Copy or Drag and Drop the text you want to decrypt, and click on +the Decrypt button. You will be prompted for the +passphrase. + + + + + + +Key Management + +All basic key management options can be performed through +&kgpg;. To open the key management window click the &LMB; on &kgpg;'s applet. +Most options are available with a right click on a key. +To import/export public keys, you can use drag +and drop or the Copy/Paste keyboard shortcuts. + + +Key Manager + + +Here's a screen shot of key management + + + + + + +In this example you see a key group containing two keys, two key pairs and three public keys. The third column shows the trust you have in the keys. The first key pair is ultimately trusted and is also set as the default key (bold font) while the second one has expired. Two of the public keys are fully trusted while the trust of the last key is marginal. The last key is expanded, showing it's ElGamal subkey, an additional user id, both also with marginal trust, and some of it's signatures. +Signatures allow navigating through your keyring. Double clicking on a signature or a key shown as member of a group will jump directly to the corresponding primary key. + + + +Key properties + + +The key properties window + + + + + + +While the key manager allows you to do general actions with one or multiple keys, key groups or signatures, the key properties window gives you access to a single key. You can reach it by pressing enter in the key manager or double clicking the key. +In this window you can change the key passphrase and expiration of your secret keys. For all keys you can also set the owner trust value. +This value indicates how much you trust the owner of this key to correctly verify the identity of the keys he signs. Taking the owner trust into account gpg creates your own web of trust. You trust the keys you signed. If you assign owner trust to these persons you will also trust the keys they have signed without the need that you first have to sign their keys too. + + + +Signing keys + +When you sign a key of someone else (let's call her Alice) you announce that you are sure that this key really belongs to that person and the key can be trusted. Of course you really should have checked that. This usually means that you have to meet Alice, check at least one identity card and get the full key fingerprint or a copy of her key. Then you go home and sign that key. Usually you will later upload the newly signed key to a key server so everyone knows you have checked that key and the owner may be trusted. Alice will likely do the same so you both will have your keys signed by the other one. If one of you has no identity card at hand it's no problem if the signing happens in only direction. + +But think about what happens if Alice lives on the other end of the world. You communicate with her regularly but there is no chance you will see her anytime soon. How do you trust her key? + +When you select her key and then choose Sign Key... you will get the dialog that allows you to choose the options how you would like to sign that key. + + +Selecting a Secret Key for Signing + + + + + + +First you can choose the key you will use to sign the key. Then you can enter how carefully you checked that she really is the person she pretends to be. This information will be stored together with the signature so it is a guidance for everyone else who might need that signature (more on this below). And then comes the option that would help you if you can't meet Alice in person: Local signature (cannot be exported). When you activate that option a special version of a signature will be created that can never even by accident leave you keyring. + +But why is it important how carefully you checked Alice's identity? Who should care? There is a different way to solve your problem with the identity of Alice. If you can't visit Alice anytime soon just think of Trent. You know Trent has a keypair, too. And Trent is a globetrotter, being on a different continent at least twice a month. If you are lucky he will fly close to Alice soon. So you will go and meet with Trent to sign keys. Then you will drop Alice a note that Trent will be at her place soon and ask her if she can meet with him too to sign keys. After all this has happened you know that Trent's key can be trusted and Trent knows that Alice's key can be trusted. If you trust Trent that he has carefully checked Alice's identity then you can also trust her key. + +These relationships between keys and their owners form a so called web of trust. Within that web there are some important values that define how trustworthy a particular key is. The first thing is how carefully the identity of the key owner was checked. That is the value you have seen above in the secret key selection window. For example you will likely know how to verify your local countries identity card but one from a completely different country may be hard to verify. So you could say that you have very carefully checked Trent's identity because you have seen his identity card and it looks very much the same as yours. But Trent, although he has seen both Alice's identity card and driver license might say he has only done casual checking of her identity as he is not absolutely sure about the documents from that part of the world. + +The next important value is how much you trust the other person to verify documents. You know Trent is good at that. But George for example is no one you would call smart. He barely looked at your id card when you met him for key signing. You are sure that George is the person he pretends to be as you checked his documents carefully. But he doesn't seem to really care if he checks other people so you will have a high trust in the key of George but a very low trust in the signatures of George. If you open the properties of a key you will find the field Owner Trust. This is how much you trust the key owner when he signs keys. This value will not be exported, it is completely up to your personal preference. + + +The key properties window + + + + + + +Now you should have an idea how the web of trust is built, what the owner and key trust values are for, and why you always should be very careful when checking identities: other people might rely on you. But one element in the process is still unverified: the email addresses in the keys you signed. Creating a new user identity in your key with the email address of Alice or Trent will only take a few mouse clicks. You have verified that Trent really owns his key. But noone has checked until now that Trent really controls the email addresses of his user identities. + +If you choose Sign and Mail User ID... from the menu instead you can close that gap. The idea is that you will sign the key as usual and afterwards it will be split into pieces. Every piece will only contain one user identity of Trent's key and your signature to it. This will be encrypted with Trent's key and sent only to the email address given in that identity. Only if Trent can receive this mail and decrypt the message he will be able to import that signature into his key ring. You will not upload your signatures, this is entirely up to him. If your signature will show up on a key server you can be sure that Trent really controls both his key as well as the email address you signed. The signatures you make in this process will also be not part of your keyring. So right after you signed Trent's key it will still be shown as untrusted in your keyring. Once Trent has received your mail and imported your signature into his keyring he can upload them to a keyserver. When you refresh his key from a keyserver you will get the new signatures. While that may sound inconvenient first it makes sure that you will not by accident see one of his identities as trusted that he does not control. Only the signatures that show up on a keyserver are those where everyone, including you, can be sure that he really controls the corresponding email addresses. + + + + + + +Working with key servers + + +Communication with key servers + +The public part of a key pair is usually stored on a key server. These servers allow anyone to search for a key belonging to a specific person or mail address. The signatures are also stored on these servers. + + +Here's a screen shot of keyserver window. + + + + + + +This dialog gives you access to the keyservers. You can search and import keys from a keyserver as well as export keys to a server. An example of searching and importing is when you want to write a mail to someone new. If you would like to encrypt your mail to your contact you can search if he or she has a public key on the key servers. If you have created a new key pair or have signed someone else's key you might want to export the public key (possibly with new signatures) to a keyserver. +Most keyservers synchronize their data between each others so you will get similar search results regardless which server you use. Since there are exceptions of this rule you can choose the keyserver to use in this dialog. It's usually a good idea to choose a default keyserver that is located close to you (i.e. in your country or on your continent) as they usually respond faster to your queries. +Please note that everything you upload to a keyserver usually stays there forever. This is one reason you should usually limit the lifetime of your keys. Also note that the keyservers are sometimes scanned by spammers for email addresses. + + + +Key server search results + +This is an example of the results of a keyserver search. + + + + + + +All results of a search are displayed in this window. This picture shows a search for "@kde.org" addresses which showed up 244 results. Using the search field the displayed list was reduced to a single key. This key has two matches: the primary user id itself matches the search string as well as one of the other user ids. + +You can select one or more keys to import. The ids of those keys are shown in the Keys to import field at the bottom of the window. When you click on Import the key server is contacted again and the keys are fetched into your keyring. + + + + + + +Configuring &kgpg; + +Configuration is accessible through the &kgpg; applet menu (&RMB; +click on the applet) or through the main menu ( +SettingsConfigure KGpg). +You can set default parameters for encryption, decryption, user interface and applet. +Most encryption options are directly related to gpg and are documented in it's man page. + + +Encryption + +A screen shot of the option dialog with encryption tab opened + + + + + +Here you can configure special options to be passed to GnuPG to change the encryption behavior. For detailed description please have a look at the GnuPG manual. + +ASCII armored encryption: this causes encrypted files to be stored in a format that uses only printable ASCII characters and has short lines. Files stored this way are bigger than the files in binary format but are easier to send ⪚ by email. +Allow encryption with untrusted keys: this allows you to encrypt files with keys that are not trusted by you. +PGP 6 compatibility: encrypted files are compatible with the older PGP6 standard. This disables certain features so you should only use this if really needed. +Hide user id: this removes all evidence of the receiver from the encrypted file. In case the transmission is intercepted noone could gain information about the recipient from the file. If the receiver has multiple keys he needs to try which one was used. +Always encrypt with: all encryptions are additionally encrypted with this key. If you set this to one of your private keys this makes sure you can read all data you encrypted by the price of bigger messages. +Encrypt files with: behaves like Always encrypt with for file encryption. +Custom encryption command: if you need to pass some unusual options to GnuPG you can specify the command line here. Most users will not need this. +Use *.pgp extension for encrypted files: if you check this option encrypted files will be named as the input file with the extension .pgp added, otherwise the extension .gpg is used. + + + + +Decryption + +Here you can specify a custom decryption command. This option is seldomly needed and only useful for advanced users that know of GnuPGs command line options. + + + +Appearance +Here you can configure the way &kgpg; looks to you. Possible settings are the colors that reflect the different levels of key trust in the key manager and the font settings for the editor. + + + +GnuPG Settings +Here you can configure which gpg binary and which configuration file and home folder are used. These values are autodetected on first start and should already work. +Using the GnuPG agent makes work with GnuPG more comfortable as you do not need to type in your password for every action. It is cached in memory for a while so any operation that would require a password can immediately be done. Note that this may allow other people to use your private keys if you leave your session accessible to them. + + + +Key Servers +Here you can create a list of keyservers that are shown to you when you open the key server dialog. If you run GnuPG from the command line only the key server you set as default here will be used. +The protocol used for communication with the key servers is based on &HTTP;, so it makes sense in some environments to honor the &HTTP; proxy when available. + + + +Misc +This section allows the setting of some different features that do not fit into the other sections. You can configure for example to start KGpg automatically at login. The option use mouse selection instead of clipboard changes if selection happens by mouse and pasting by middle mouse button or if all operations are done by keyboard shortcuts. +You can also change if the systray icon of &kgpg; is shown or not and what action happens if the icon is clicked with the &LMB;. If the systray icon is shown closing the &kgpg; window will minimize the application to tray. If the systray icon is not shown &kgpg; will exit when all windows are closed. + + + + + + + + + +Credits and License + + +&kgpg; + + +Program copyright © 2002-2003 Jean-Baptiste Mardelle +bj@altern.org. + +© 2006-2007 Jimmy Gilles +jimmygilles@gmail.com + +© 2006,2007,2008,2009,2010 Rolf Eike Beer +kde@opensource.sf-tec.de + + + + +&underFDL; +&underGPL; + + + +&documentation.index; + + + + diff --git a/kgpg/doc/keygen.png b/kgpg/doc/keygen.png new file mode 100644 index 0000000000000000000000000000000000000000..dea171e076205e85b95e62903907fc7491425b8c GIT binary patch literal 23121 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 N^3p0&)siN`{{_uk>L~yK literal 0 HcmV?d00001 diff --git a/kgpg/doc/keymanage.png b/kgpg/doc/keymanage.png new file mode 100644 index 0000000000000000000000000000000000000000..8baf2158565f4de10bbc1e55e261e39264a96f4d GIT binary patch literal 77957 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
0_5R9zjhVIY@`S!Hp&)QQ&k!d(haMsU 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%0A{QQY&Xt=@a zyuZmqPmeN%cvb?;`e!!U=;Btrg+C@c;Ie_I{ZSm=(YJ5EVkuA5$m3G?P)Aw%xdiU^ zE7)0IgXx;Y3+ln^WfCZOfBW4QnVlZF(JmZ>N04aj1wVLWd}!7k@HQqU1`36uqoXS* zC}@R-hVHxq+7=s9L>Sm~yzUS|BKp&ak&KZ4K>qIBx`DW1AmK=LnBkfa5BX7jhjeIc30-X(|at@@hN&VaU-RumaOKB}DD2%G7EmbUg* z7r}omJ>3cq+7kHsiPpe-&aZzOqgPA_1yvg{m>CA^X^Ha%pAkp%C~v((L%Y7bL_tS4 zxHJq12*}9H|}E7{behLeb#K=R7 z0Aoy~_GMA(h(SsNyYKpP(B2uT;P;HHM{I2D{n1n$92|~s!wFV1g<~mfADNla)i;2v z#K8Y{@~yeU6X2dp3bl@&Mg8cTSNr zt*;`c{w);--TTw;m((mNySZcV^5b%jT+l11s=nl74sSId=FO$CF_t|Q!eC0E$SelH z_8@5nZ-M|(6^ItsZJ!jXfPC!O_h5%`P$+?SPTqZDVL7mI$WRRnsJlGh5}6$d4VG-K z;a*(&-{IuOL&@mS$)@z0{CR}^UcgV3s161S_(_KT&;}VhL&$|G*W8?sy#NCPsyYQ{ za_?a*1wV0^*L`(0k}%}h5*ES)&9jF;dwzmJPf@i!oa;E)E~R1` zs~VvmlAfMUPHY=jU%!0W@;LZ4EiN59JCxy|c{#59(Si4Pl%o(J6FFJDpMjCgUwO(t zbN^)~g0E_=H9+Gp8;mA%hH6_FIQsH@FDn%&-~P1h-S}*eMa(e2;itX2H#*3fL+^XG z*17I_@OdBrtL1bVtEv-99~oYaU+Vo4?)-aoAg`mw$%;ecm7TWGilEETX8S5BUTSL1 zLBt5lj!j)}2>O@x7IDeTV}PP4tbOa@IE5it<}D>dzWxqICckI99J8Dhzy!2GO%Q?U zC2kDh%ed|=cj$p6{Q7h2Y-XK3P19Av?+8}atd1tNFtqsd!!5T#!rXvVA-f}9i|o0o zGitPsh#OdytaD7jXtJy&tZHPufNM!z3RKW zUt-I(z)^`?s{}8XL%kR!G6s<24e#Ux;L>DUkV%3bN!!X+>xKNn;PltXuh$$TY$k-Y zRJfGaA#i0&2n9|vp%n5y=l+FmB|kaTpB~COugsnxBX&INjvc9&CmjW1W+_3^7)_Wn zQJa~c4}ygk)HLm$uj5A$t~KLacJ91S@y&$~iI44`Q3QA7Su8%_(uWyJOoCMa+Li`0 zyN%yxFY&)>`8^NDGfqzYaPdvR<6|RVmy_OHi63aHt9z;lj)~(?_*_LVWU@TdlKaW) zi~k)zb0ZJkaN)0Fs6D=5R)`RFbszYvKd{bLnAN37b8Wzun%g2W}uehs&F@09Qtgb~|t9bsURD*E| zIyKDBp&Za8Hd=VFH9X6yJ%GyvOpn`s7XIHAME+b^fPgF(KzVNWh5K~aUGDxtB^ac-3lR5ArL*ECcvor926bYyKOv*AdzjqOpHT3mq2)*YvJFikny?BF z!s&v{Ht3Fpus=eY)`Mbs=mu4@Giz=CVoL>&W(I|X6SnRrIBaRU3w<|TtDgW}94ytP zAZAJm-1qHhNm@O(HdG1R?Ba-B&AsKVhT9M5wB&pG6SyijA@GD7H;c%X_46`%!O--5 zvymN2RS$Q!in?}#+v%MK8rOQi3*l!=oZ0gVo{b*|SUa-&s`R^AE)6}T z%eVZ_lSc0@T%?N1{)J$+TsK!b{lU zl25zD9GAI;%N-0$?#Zpr=G0PXb(}P_y7u63!vA0XqoH#6EGB=(zNaNtN3zt#Or$Z@laQal8W zLMS8FGrzEKyL|{Be~JE}@X=o2aZ*A72bYN68kjTUm)m z@W;5%j@8Xm3{;uehVN!2Wkb)XI3-Xrq73%{75B;ql}j8;LXVc^p+*119$QLY`Sq*Ock1?+5G!}qYniz~jMXY!|A>X$u!29) zyl;=nUD!ANDfDE7`QXu}!$ADiwrh5U_Wn=`nk12>JQx@4aPjoJde7M)7o4H0>X!FSIZF$GC-)JexBQQ7DdZnR zCx)|-{9avSvZoZMC7E)Nil8lq`H55XkjQuLCrtFeYK^%n#@Oyk;&FBUZSFui?!nvc8&jc$?gS z55;s2WjxRMEJ~?*96KYxQ1bsBS$t z8IbJ*ofuCl`yp~mDLv5@aSx*UWk!^v5S*BVNrJy*4LX0n*97Ov_mzc^pKx|_&}xpg*D zSoLUW+;or3Q>uqjhP-FKQRO1$riC=jkd=+gjcGm{Ha`pj1pcNHOpe9l6> zHg&zBz)+b_4DG%RGesetC>}*1F)T5MW*!;qa=jWowygi0a5!XD#vGYtKJ|ca5PF`X z2(v^wojs};%U!l3r&Mtq!SNbsbI4XI#_mLdLU1fM{Bv{h_g4_3GkF|N0Sp)F<(2Vq zo^&h#WX2h-0_v19`Q1-}Xw*b?kv7f14k~R6C7!>u2!o6HqJ_C zXfbVPtvJS&2O{^=Jsx{5{aK=T;oC_yJBhE-Es&`shP6Mj^0IEV&Ujb-z*wgJ=R0(f zoVzJ8cYcMaP+F)lo`i|@qz-tVhOr?E>Cj7rh5rZUA03^clh6KuRxH~ND;O9| zIMviRAApBY-ZL?|&KAcVJ}TX1x-DA?dR@ybw>Er{%v{;E`HK8_SOlqLhT&uG8O?^*M!TE2S)O|l7? z*zQM84W^Vy{ceGwKJAx*2qVwXcv9r|Rp*4i+4(5YUN6H>Q0y9rDR*~zsO-?w=4WvJ zHE!%<=hyRHdbOIe$U36e6=u{mhX6vrZD3BfpbYk6K+DIpBTXm$G?H2VMP;ktpU_2N zI;rp${W&-G!8l-{DBPW%C8G??!VjK2)vBQ~8Q_`_q&O5YjUNlKo9TN6a6*>Z|4b>I zLswkFpCnX2{nDw;7SN+QMlkRBlnNy~X^*K8B(RLlGgr-2jzzVYW>P+-{(9WWluZLk z`V?7k$2un=ybW+~bw0c|pA_n!F>=ybp`8>`TZn+h892A6z082&iPNA&0Vn0yVV zjV_;D11n4+c5|h3B}G|$mXq%#(?4LyYWA62Sd0$7arD4G&?yP7Hrt(CU-I zauIKq2uz;02_u+^7ApASGrHX$xmXmHO~Eg)TyKR#(fR&z0FQ)%p63x`Qcir!Z@@?X zRT@1y7+sY`6xMzVk64(~WLih=y{7Dzdn0i_Si?ek5+1kUQ^NY1A)u2T(ZAS_TI)aY z(8vY_Uv7&%yaT?0R4S#qq1TZ;Mg0VB*W={{&0R8jhfJuur+ZhI2 z!VB9S&EiPwNOX{DX>k%HA^I8rZz!iM14Ox0%c0qoyvsr_-rl@(-vD-1+V*dG21tyI zX;|q2y`%qayJuR60Kz|6+W_*q`a~iIugvlqAAhU@!vBBKZthPgz^3ljA733Xu&@->0QexFaEgnIOO)h!a6MJT)oIt$$Gt8+`Ky~@44w%)$JEq&YqSBv z_|F4x&rn&dbEVkU!@~2hoMzv+=Uvz#P%104WyPhiVPOcojvhuY4(p-2r4A*1EdUpW z2)7JF7MwW~u1|na1av$e--6Fx9t`#M_1E;*ZYnmEsU?S57?1J1uSDWLNJikU3Y!I+}xrYknBD;k(f(PN(u?Sg+f*$a6hT|IxQ~@3n=Bw#C_<8T5cpV zd{_o(&E7&dnzDe@tN;tk8IYZAz7~Z|-v7RD^S-U@#-% zIi$_&M%G-4&uyLar2S9$pYZT-YZ-^k%;bJC-Izv<@lX>Mety_&i7Fc#8|Lb?Tmg^6 zI_LO3rbed*d1-aEgekhP^L`YNyo>z99%=0B3;tGflcQ+y)MwlqV*U+4(|BoVRQ;ZQ z!!>2LP?@>A+TopiDDlu|C?BP%Ri(2IFjfPLt>vHmmJ<>Ekb<;5QR{=~aqWXs{0!PV zdB)=ei&6M~=o<%f{GSc-Uu0#?L{$nY&H#`p4Y$Tu0oNm{5%Idd$jCYlDCWo`twKh# z1!}kN_VxEv+_{Wn;>k{m zwVE^q_``)JjPO9jcllLVX884_kU+*}x9+W0MbJndNVATeL+P#{?_BPStmx~)kn8zR zk6ODO!IrQ?8&5?b!IhVro12%Xe6vTedlkcFTx99%Kka^W=&{-54?wy_{Is9_b_Sxi zk89AX}}Jf5@YW*G*a9P-OrlLC!PhF^NTMW znUF9r&Ym<_VlQ^W>9E)Aj#*|N>(4%2iPW(9rviJsY5nRZjm;FY%mr)~c^d$^#mQBp z+obv7L(jcHrB+RV!92V0+Loa15V*q|s6t+~E#i2I-s7?u8tgg)E|Er(K+v4<+YYPM znv0zQqAEZzR1c_Gu#lVM1}qjdMk=$=G^8t>|C7nnR)8W1H4h~GL{$iqe2HxU-7$%s z0kq?pzkZ1wN?BXeYvMb7hTBh0PnY9CBfw4WQnlbbH-WjiEH0;rPY46|c(cC);d3fT zh22gH#xn^;&e6+=iUaw4D4*IOD{~TuP)B#3c@O=UIn9JgB?ULD>LwfYSj`HZhT5{3 zk)(qU+bd+nhZHf=Q8i3fj@c9i)@E#o;Ypkyn5INE6c)M^0l z4MhH)54JgTlMLxr6ZO<`Yr5hBqYJn3uA-u2ZhTi8iv+$`Hq6qXy{S1t%WaTDTJ{FT zQ!)#W0Rt$)YE;9AsqF5qTDhvK8%#A+?!X&>QMBwjllF0+l?_mAi z`uq1<;%QxrgKy^X-ZMhi1?8K+UoSU#83vf3cUwl9uuxH%P320)(<(yG&(8s>_N%MN z*w}-kl!WuqC5|_&Wf_W$Xat11eOW?JI=82ApizqbiK_DQ)lLVBHE*2ndGUrTa{Q1C(D-U??L3>0WO_cqxsMIk7h zngX51>H@*PeZ{1u&+rTv?Fk_CGx z2z2HSR$I@v1$Bj{-fxId= z$m`$;2rD134&ZJoxiR5fs}#`gp}+5lILtXez5>)U(7vje2B?<)rZq6IVCc}DOZiz@ za5WjqrlIk5Zk4)Qb#kx1u!j!onHywo9=Di%_YN!H3)d02K7Q8;!ueDq_B z77dMxdN^qxj}Rf)%Z;A?35Cn#5hu?KnjZaXkrSz)V9&ET#NnO--K5>qS z9J4yo z$jISIi0k(2PU|i%W(UT;e@p(-_0IlW=L_B_n8wDjgrT7d+AAqxMsxrhNlHROZZ zB$zhar`B$z5hw&bUTI<>R@ixKVikNyc$gx&aujl&n$#eUY!^PLnW1gzN z;xBl6kx%@^_@rVyNE!@HEdkTa(a|n;dr}kC`(jk62^eQRN+%=0U7qO)M*Xj8HpS~# zk^eD1@Q%dDS2~7tfdA(dp7Q6h2;R-@zZK9sQy9d%XD2 ztxe2QmN+;H{{{9_yH2Q&jF+1mzYNZ|;!AOGi6z@Z{ra5_k!~Mh{PrnYWbb`9ul&nWMb4m=?;l z?fRM~F3;QB^|lopiR;p!$kqX>SZ~4%TO$9_(1B!A+tiA?Y zcz9ty!-e~G4BEVTixUU`*6afvze^vf5oBjjh<_a=PbW}66oiU%5vt5*~g zn+A6lfrRxtOjPW$orHz``~Z9oK`tLar;t)01~}=%;=dAyh0_ot5+3TSw8X@E+;XPD zsHv_DZmjWgfa6@S@}~YQ0O2)+L)@07uC-c2T2#~@@a+>qY1?oK06*VE6qX{Ali&RB zx8{xx%Y)NxetDo4;z0(6wg(Q#$RBXdmc??@Le#Pl6ZqFpc_1wKgrU81i?Q$jp`R6E z`b{OtzVl~de`ARf@T`oU@)UgBEdX8gU z-@U!B(lB)LMV3X>gR5oL8t%sVof$j3bsz7Z>9c+Num>~PJ+(KxPp91Vy34f9x0Jvu z??Az1yQof7?*2LZn5s2Gzfd~>*n^B*MwtWp5*`kOE9HG(eX#33uu^MS%gU}U)%6QM zr=+Lrs%goPLs8TSzV=`B1s|)TprXKORs`K^i^9rFUu8`A9VPr(O$A2JhfV(>mcspH zvn%QrElDc_v^YsQ8e=cHf|D@6TaUrBVLlCgxAEQctNhruX#>terp<~?JuKG_2QEh8 z9iO1HT<$?gb2vjx>@#D1=9P!$MkZ>^EHj@EY-FhR>r`?wd0W5X|BYCFd%{c$c>Ki4 z$!SzPJoLIhk|0k};(%Z`nR4$NpV#`Ap&z2Tao7BpMgC_^GU6QI> zLOwIr18bQTX@ZfzDenJVZ<5y5S1)_=4waWiKfd}jw~@q)vr+nJnA3cchoI|g$eBi* zV5b*JG`<2tk%0Nispo;g%x^|UCGm~66Sk!&>H6TTux-=`S+Cr-k?$5*ZaGFC%00aFL%+W$r4;^5a+RW3|Y1&HXk`LVUWa zl2(`eUHMD-8N)o_sHRzN3R* zcy%7+tB9tQ{R;QHb(_YYpBPoN;NrvPqR>adj^E=X<3W zBCpCw(zmUx&*%p0Y;Gj2atn`n`X&j6!kLrS^vU(V=1SCcJs|SjPD1PstW{*sR*N<4 z7wA2|xpyj-r#j2Hgv}?V<&7^Jr2B!fZ>rDFA(y)DOsbE$rE*n~KCnsVu16D7Q?{GZ zdJI7dHRSBhp#0r$8wag#6VPs0^lS6p3kqKO`@obgYZ!!>6-E%mRXnlR($g#*EC#F6 zhS+D|;AmyANDDG?udeH$ZVw$gTtkyeH9$x|5i%j*5JpC_OsSJS*64-IcmPnrp@k>B z9^wuF5cf1RRF-~Ll2H`I*5+__**WzCGeY}T{u_H{7T*>=J<>B&0RnW``*VbGtLdNG zRu!#9zQ@_qHg;_92qrKfIa>6a#@tYt`)$t4 zOe3hLtcTs&*|*h{O8DtIgk!GGrR@!-8Ro&uq0-!Ve%khMo2}di4bJB(9>&zJ<-mz=*+P9)?cCt3`6+Z|Jn{zu}KPugLDh2qke=PBw zwv8NFd%A!3OqLdq`kLsSKg>{3*V@3}q;ZUGOoeAB_vAdzIeX;55Gn+YR^C~vRqT~1 zn-1BZ`La$NI5a-q>UBf^*-#+{m+>8r7^{Z*Ld=Q*e-{|a7TPLYUL45oCHGY_(SMh) z70q0(9b|8#XI1|wK!n>ahISoB*!`3BTBIA5CI^)xXq;KWa!~W21bt6sRFaNxi?vY2 zl;UmKgCpEPZACd>p zF&^D3Sej%5s^f>5ono&mducNKx>@X~EEBz~Uuzx4udv7+*3$g|b>_{qJF?$gbiGmh zBbY^)FNsbSAl+Wt-ckhWIn6BKb{l_XHzDa>Zz=g%Jczimd9LYAS+L$5NMm`aXlb^= zHE*ffib7xg;jR$}E!IzP zh&BkQiPk9SxeLg;oklRSa{H-j6^L6h)bIz@+as4DePKjbpyPAh+kZHh`nPJ*H;LGi zA~6pd@kyix3^RAos6v!ZTUV9TkO2Z3PII;tDw+9IjitRwn{bvn+m3PxEQ1AgO*zh< zpzFSI*}3jog~J{-w2$9EXZ}VY^B?d2v3Zp0yyH_lyTqlhgkDtK^`b_sZ{~Ec7Gtj^ zu$}mRL#bEkyoBoe~lIH4*t&^$HZ#j6Z4& zd~IhdLO3Iw>#5d#v27Ki#aj`&Ah~Lt^4JqhZjyTAdUY`vtA!cgfUoA~E^c>SnVX8% zm|BFr=R7f->R7`~u z!Y~&Ujlag4i~}-hlw+h>96{6K#Pn|ryk8H$?OX%+vaLwzv>ro24MW-Zvf?EHkWt67 z_RC8-|JScSo8h`|-%T)(lKnQ~xU4TxRo-r{F;#klEQw`ikWPb3PZ~=&exEH#(uEDz z9PfU*NNL-84aQlES{)5$TVC*ug{~Eno*7RGp=HPgtbCdenI*?9I^*}Yx=kHw@TZmw z&hb7`ay)ghQhHQ)QPMLM z5yl$csTNJSN_m}3bw0z0`d9fJ)wgT>JVVWn6|Xl;Egyvx4XG;2lc%!y_Xvk*3y<xV+Yp^jfj^_{m3NZ37GVr7x+@%KpQ?k~XmzQba{9z{w!KVufkG z9TuSE;>YPr@s`ybi{{Ix1x{C&w&13j55*!pNsd-ZE3wGK`!V)X^uOcwyShWpk5b)t zHoxm)EgQIL9usYdGL8hUjDJyA(gj4!4CdYeQT>?_JCZe5R4|E4s) zT!2L3!IeHfgkxjnc7g#V-MIqSzT4GX^3OT+ zi8SfxLumn2@_EbD2R^YG=I&q4K~MHj)y z(b_>@WIiD`@mpapA8~eK|7srUe9Ka6J^_xU&8QNvw;?D+8gXG2)pf$qDvA-0<)?D5= zxDtbee($kN-A4Y@qaSjDS%0%dc2l7~>C&gQfn#isnZ(}p4|MKnSr10@>knWrXW3kg zEA7u&<)EsvMNuK5a|dZow3964PqxbFIPkb;emT=3eZN7r!V!E#Wq&iFF+bEW61W?t zI+rV%CnmYs(TFGi~O)mAE&QHM1NBVjc7={ubEHTLZ=3jWG)Z%`B{wqpg@{#A}$mQ~R5qC=x|| zzx7>LAsS)0HYaJ7`0JU?ee3E+o#=0iOLNg2+Dvi1bn^YIzp!~1F%1N-?Db5nFsdFN z#2C1Bje2-QN`u4|*5)#G*8dVR(mI_#J94s?6N%_dG`syrJZE1mLpcym`Oc!ytTAeTW6*8DHy8#L^sskZDklLHsx1;))rae+;OJz`RzO@ z+E;$;3`cg^BSIy)s2Mj}#m8yTweZRLYmCjz66lTvue`x2+wmb&(04}xHQVM%dQCU} z$i1HTRk)H;-wyQ3IweRur5%?CyNw&_t9JLlwkKUmouN4xgGbSpEuJ;`xFs9L`PL+A zumZEUhu&+T6$`$ z$43l9BM?o8jRBea>-cEgNRW+tSgkL*951nY^3>2wux#iX0N z*H&478tZT+Yk#Yt`Cmz&nFHa!vavECX)vR-RR$Dz;KUdA#m`}*J81vq!bb0-l>x0;TX_0k^gN~0S2^~1YM(}r>AG6JIUM{8%eCS zq;PvwxtVz0g3_A($ZY?njLG5ma0gMcqg4&XfvQrj#^9r(wngl zu?p2oA5@!RJ14jt%4;xuahfI#V>XDI7gYLtbkxHWSTn3d6F)a^!tp&i29F5A#%`s{^`s(VYH@#GdgL#TDHF^$9L%%Soyw?Y1RaMEd1s)6UGZMze z#`u`^nzgD)X9T|iV_@@#>c@oN8($Uf=G(mUsBe5Q{uQ`0l?Rdp$Sm$X?>ji!Tc#N& zh2*ZGu5NB7_D!v=d;s3PQb=%zqdbN&{FF>lEhlm zoW&uG9wVIzEn%8mK@_I&Dz2@qy}ou~{0k2WoWRKHwD-XnVc)$~5OI2b-b^M&gFRvro_9n3|e0>vi#D z5Fh!GT%|ugQ^o4@vjrJFP3kmpm1$Jwh++gmAxn^&zV*u1(%OL|X(x1aKsQ7U46+zC z=Z}x#&A+9H!pKj-`nGFbs0BDc%?A$7)=^n;d6&7VTj}a*#zF{~7z^vS5?vPo;PZMu z-Z>Gsdc;9K=#sScN6LTs#?6s_`DYLyMe8ofU0G}QoZ)kK*B3^~t3A2m$u^nZjs2;X zklcB@9yC{`U!8&LhsMYe77^j)(9_0^N?_tX1&Y67SdxR0H<2D*o9zxY0eFIQjI}0* zjX?^KOcdY_V^9bMB{BmjJuR>;5nBL3goJAxcM@pY@| z_wz7>MRc+8MXe~{J*qU#y}abj%sjO{dmS$B1#eF{W_y7$jEmtBmYd)9DMk+`GoPvh zyOcjBP=GgtPNEkOg`N_sVw>2OL#dn(Mk~wdXhS_P-U>u+_5nK5TQ+1wBzOd=KQ2*H zQT??+Bv=Z_(+eG)8xrWgV<-;iUTWzh1R#z5K2a=R2d>w2CGRU=F|!}ON0ywSj!ES)@PlM@3Nr>+nm5ux^;wlr zM}B^6eQ6{;L_>ZiwuDGX&-A;7z*%%oZlP=kzchT3a?|j@4dPI&&`ABy`ka$`?~ht> zA{b)?Z5E;g6vh`f{?IGHV)@j;j|F z6s!Z~jxLi$|MD4ZCqJ(I?}_q3ZGvw8Lmwy`K8D^kFGgI}aug}HeCF@2_08ljrlbrz z=bG1gl9}k93We^CY+g_NfdDpoT;5^^Y!YFxoc#1k5I?0p*i?}U8+0|s15~YXb8`dd z7QiB%nwt8)FjYI%>aW$ickdFeYWVpXpE(F|Y2u7}(1!!wUiJ3&$jdj(k5#-ErbtHk z)46_*S3x%5>c}6+6&!2#)av>SJcq4rb)ME-rSE;dsdzXT*CO<3ss69-B$pC1A1<3% z@O?D{Lq16C8?4ce5a}k;(ao)Al%~}0X!Z*MfrZpfc&**BMl*HJCP9{hekUpnrLDCx z1k2T*Bo=FCaFB!tOV-Sc%405vAS;vspt9{XYRO7_IK6XH+LV$cbNcKxOUK7cf<=yQ z?J+-rA%KA|%E_UN3u8vIHs-BAi%o*hq%0{d4Ny!z^dq~5(?}F~P1|b{D}FN>WK69(#QOE0P5)!ZJj&+Rplr<3a)h|m+@ z5zt70PQ9<{8VczNfRh*+`*U$~fkiVhF%qcg1SCDO9!9dayxYFtv)Am%hh09{h{wSv zl#j|=2Z=>-(!6f)I9cYhfZ9GDt%QR4-q0=`tCx4x;U^5C9uz62qr6oVrR?rj`uzDb zk`CIGkPFhS~X#cR0)0sD6xU+2qXXV$L@nr zCNi4wa{sEIm8x_jNlg7aEC~SO*BHIOPwg;Y22&k z-;fYn%_agf1v^%rqO6gbBYRNom9g5q#4@6$1jR=_jnCS@bHG*TL_Nhq%GESh>#Mv$ z_5=O>e;@Q0H3@xjRR~r!q~xQBYHDf-Yu1S`Xb4FTab*%aI>?;fU@M@}GBYRFNcwX! z5RaYU_k&SGY>bJ%YuzG__>ccQS!qk=YZO&IRHd7vw6n8oes&b~pQ($+bki_$a9qSG zWX8l%?0S3)Uyp)+{N3ZIk9 zVao4UzZvu5h`7z>OVu+39f$0z4=c(pav0-U8{p`JIDEN0XN#R zyCQtYK`Fkd@eFUnwN}2_VhTii;^Mi&W4fCPQ~%5xnX46AkrYjQ;){h2M`}?2I<}D( zW~IL73{V3wrJ+uEG!Pqp1ZpN&SR*En5njfZeWE`uxVMgagg@5fR?!(`1gBg z+emcO0a9mCvTvo4upEMur(Z51GW(|4pbeE!3tW>4(QDrvu?#OOkAFX1*U!&>ywh3j zIPv9O{hDf;I7h`y5f)_fA>0lJvE;J@p&E%?0JOhrFCv}tI?yWN`8ATUHtdUW%Z|;$dTyPjK1@Cv3 zubMp+r@MHzjhNbZb9pjDnK7gSva<536oMS=^o^w#-m1O^WP~)O8N@>BPpLVaP4Nia z%I)#OOqE!W*-sk3<|KqNg{Wee4sFV4C>_FNV)eJT`$J<%gn#y0#SeR2laN@hq@-m} zk|t`i+|r+KDx|1moQis23}YZ$8#~dLfu`d_Pzh;HPM7P$P~QDl@c}fk`uO-j@9zO7 zKnRh3%Aer6ki&N`QTM^-)isNkoj)D`K={DYht3pDJ1oaNHEj_a3HfhQah;)**lH=I)( z^w@6{<&l6+ri)awMCkD}>h)RZZ7>OVw8Hw#bwxmM6GTVZU*#l`V< zJ?GHf`q3L?=6)uX4QQ(mw$Y&YcJ8yvzq>ww$M^CTtj;sdf^4ra3U7QZ1kDZ;3w9yn zGH^`>KLNbT-}ei$39+Ojn*@Pk!@^@YcRwrG)&S=weJArkeCh8E4MViszcv6 ztl)ao&6#L{le=knbdV&B^?rp+M1WjtE8I>vTz_swDB@PPBhc7D?Sq9x+Mos^8yt+( zMYrYmx>86KTmEN69GKdX9g!V0Ngn^JzP0hKbxF})joSWfkp$w6n}vNF!yz5 z<>Bq;B)m}IPc=-A_a`aueT3Ydldz7?B~XdwmEwG-c@h^Zr}_uV&SK6obU6YvfjLV{xW-jfRDjjYm`)jJJdw^10)aeCtlN`-MQoVl2vfXTQ}fZ3FK_No zwYtTg&xcquPMg-+?=#9b?0a;RiFH2_}h^xe!*FGSH z)vWX<{HRo`Vlh+L#XyV0v_$5cIZ2bldK-m=Z_8}#ZglXyNTq8)|^c9n>)NFWsvfl4!f&VS z*4ywGwOZKRjpi|ka+Me>8qNhL1C$u`8{*{nBgMM9=clI|rhcwitxh>YPL{Jo+-S

5@6gdzpz{sqxw6csO3v#|!PqgM4RtGa+feIO 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 literal 0 HcmV?d00001 diff --git a/kgpg/doc/keyprop.png b/kgpg/doc/keyprop.png new file mode 100644 index 0000000000000000000000000000000000000000..23011967b85ac83dd2e16ae0d527ae884ff2b7a1 GIT binary patch literal 37313 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 KMNUMnLSTYb<0cBp0SpYW|G(e2 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 literal 0 HcmV?d00001 diff --git a/kgpg/doc/keyserver-search.png b/kgpg/doc/keyserver-search.png new file mode 100644 index 0000000000000000000000000000000000000000..55c0addacbc55a21e0fa4c476b994ba4e1e56872 GIT binary patch literal 22155 zcmagFb95z5)Gr)!V%ttmCbn(+#I|jFVrydC6HPL)ZQC}#Jny^iy?=h|tnPDq*WSO{ z=-Q`xS9PSKyaWOqE*uC52!fQPs4@r$sLS`E2?O>`8Mlv@`u>7-kkoPp0YU8l?*L7q zL&W&~n5@C{jw^IJss%q(CY6Bu=>tOHXLM&tI zNi6PUZ~Na5XHzFPQzv2pVi^esbI?-m5fBiy3n@_{Rgcy49Cr_NwYR5_D_{RB*RGze z2mUxXEfG;(HJ&7zmq)07zeS(E%_`A(}pLO^c`qbjW0RVK{~^WUv#yjx2^1 zz1fx`q2afU3==bVa}INvMGV4jzgkW%9_PpN{q?-{quh*Z$!%-{5mQnKtEF(P)9=0SMoi%2eQT_GX zi)D-}-Ug2~yJmUs((3B^dZp3@D>hu7k|O6>Zry4!m5RnQz``0^F;vH;GT9R5>{^=) z6rRqog(Z9jy=^|Fj80_+y97f_^@8+T?y7VE0zXXrdI28mdI4+edgp)Ai*u1F5BH6z=9>)-JUf;v@M!98A~C6j%OTnD4$_E% zff_e~*J!Gi%`$H1{`;rZVA-@#blpMlvH|e>Ab`v3P!v!=&2=aQRyqil{Y|J?%uzB4 zOK1d!eM0CDtV3u+va}K5u;`R^68L@!cvt|dmJb9LCKIPbn*9MEn3Dmo#p$M1vp7Hn zCKQ!lfgE*_8iSUV8yu@ynxd)(z`$^zrlzK$p`oUhlA0V5B7A==d<*swn}kJlJpkVS zm#_wBMO*{bwY7d1yul;2#b9u>Xm)f|I#7@pGaAyz&r~e5sRSx%-Ls82ncT?HQws-C zqVw95*QSu8-<5A4xEe(Luii9*pKuD_HzkFFf+9jfwD`_Ji6&(M473k3yb#T&W_Q;p zM!H;)vk!8xaM6Nr<_s^wM2SWX10yJlBih+{Oj;TuH1vH|IZ%}TBA0Plari(iG<3Ph z-*`r!q%*|r2Vw_TMkn(2Y_LzD&tI%K6>NZ!K?DRYW9>U^!658|X5TJLgP>|y&~L$? zWMXwWciaTKD%I-7scs6G>3B*r$*){xtw4xvhlf^0Et*k|Z z3x_Ca%dGwzFX4(gH3H^55@bL_Pk(MUiG_|{rb;WTD&f%kLxVO2^5td2B{3}vo>lWY z6hj146qD%{IiFRZxWh!KCG_dEBx$YHZb6dV?Iq~$28O>tq^d0Wb`-BKv5F3s#?!O7 z)gFt2>IfY}{jZvuWOV<*!Eo)oTShpV3kwfk4&IO0;hna{vl+cS=Q~Pf)}y4fw?xKu z^g-kPcr+Yli%v#n#o_X(BTW(w2k)}^pp|GvlfzCRV6DM(t|)TKX8pQYl+^6-{bH>Z zW$f21CIlO4DmG`UlN0jn_fquW?T zk{{cNPYUsv1k8#{sVL3JJVE6|3o^4f;Oh#;rx-PcQ@&LlN3P`Q=amm6+fy^>F6hKR zzX#|SX^?@F)r_p~!ecT0Y3Qol8d23E7SG`HmlaJ+%uQHb=aV_A?V+~Xz8zpPnjg7? z{@9!Gck`U?RAr0Cz7>v?!Lc0;VoI>&<~BNme`3_RwyYXA31_Q&QUr4rn^Y<5Ew!>N zna4QSY1vLn#|7vABjoeBQTq0YR(gE%nY5^{o}R4*?dfG3(TQ6yhnXyIDz+;`$R#1=k+)Mc$yt7~2jjnUp zXMZ$Yn^lNc_q~mpM2(NHg@&58Iz{a>w{O5Hnu&*rmqX0P=W;RYiA8yk(T%mV!9URJ zn?%A7ic^l@LLCji5qf_|OUPG0`kLH1CU_yrz;ewUN!Ym;TF z^XK72mSDgJDWCzi1g)PPlTx`RX>9M|E%Q ztnai!Pi@~u>28jd*3-3_lZJ+cs+5?jRFXtvS+1-`zoTRZBvn`j3v++1$sn(&U-XaQ z0Y!J6LwOr6&dp0AadUf*y9eb*-0=yqit z$J@fX8Sva=BF?9O0(900QD+IpW`ygwmqeWQqC~Xq*U^&7*w}5qztBSuACaOd>3=CT zvvMd^7D4r{C>%omPEyLRj7wR}JeB$4vpxMa>m}C+!37+S{{PTQ|W#JW5ZuI@{bYX`+!Yy9Cg@WH^uK5QvWRJg| zO;e?O~@V z{U3+V^`Mu84*%y~r^!-sDzZ_VVc{c~5h*EIs$r~L-KO}d%D$F5?K+>8SuA~zT!ic* zatQV(IFtfdCR46NvT*+gJkISSaA+u`+qEA1+5<`CQfWx8kc5$KVKu=3|Kq9*Vt4>H z=r38Sr5Zg*)9j#VZ4yRF?ksPYe42rtLI+W3f!`v8KDBNoK3>W=N(#^R-F-RP>R2FN#HyAKXbztjz?zGj;$B>3>23E=n(@mysR?A)$GQGG| zY`m;XcC(Den6QqC!5T~GHVFBxR?ih@B7Y2~6aA%qs5@C(^v{>~WF){Xo=$8*Tbyto z;x#Vr%bVH6vihun)izNkS_6B}ua^h7wx469J)SgHa2y^(>9~cPCgMs(U)lEd7ILfS zy7_?#Lj$ApW%>W~`BbXuR=D_%o8>er#jv&rSZ>>Y$id^~1+qNa>wdm-|2U~8XG_^; zm7410Jop#w)8#2i6dv+L%8Cn#J^ScHrj0lStChA@HU@lE%`4@=%(~3%ueYX@eJ?1| zb89)HW^zrIObM&6ZX;(q&C*_zeeY^C^VP7lVY_~k%W7ip348;Ls+xUaBFbSmp5nec zTTdLZNQo8Oqx-YCFxdExcIH|`niqz>POK!7vnW!T_ev=R@1HjsANr+Lvs5s*$UWhK zS$W4`%zn_fbtk2#59i$7k@h{EZz8~HPI`)xA^+{tMq< z?eswEJH>;5Lb+O@ZxH@G3QEpox|KoM)03{2)j5>u`BLF>rdj!6L$r zzSIa$F=jf$R~1S9-Emu0L7!yOjg#Utg{d?Ar%I+oIt^=&m)L^rM`B)Dk)7>A@th1S zJ3mvA0$MY}Ln}XwmV>H=Fo-ovnij%I`w!bnsR$BVhPj)^@gFOiL7l?-t`0N9J7{@2 zjXd4JO_wLTEWp?!wPD4}#sVwh^uNHml@3qWckp}_j+9fKjuj$fgNc-fZGrY0+w!p& zgWI6Og|NfJn*ExU3QTGeO4jI=hIq+;=f=rc+1sG#c2Co zoG@O3gnMIgMbWnVTpCW!YNx4I>ti9v>FDmFrv!|-g|bPtb6|HK&K60{r4(fTy&mVN z(arR^38Rei1LLbK7_tf8ya!wN)^?%X$nP)OW^2hz1&6Bh2}}!lM2&gor`8ujPewJ+ zhkW(?c%&-TPnvLR>J??=rA6Dz+RBh+NCw41AycPU=N*kQyuk2iVh?v+gOXAAm z!esi0hQ}+B`zTp&*sYW=A%&h1#4*B@mXiyBH^5@xD9AQQ)P+Iv;*yc>$ zG*VD06coX^z*es2%9RW1axSr0fcEVZEq@vDxjtIXG7&KTtQxRVlcCv)zYn3&3WN6aw3Cm1e#y_62fax^p}vQ!ZSF|jxx z0P=j1?^zB_K?-)?_Do%An7V3mAxDDbzJS+!=F`@9=8*2Ji65iK2~vQrQ*@}W5z{!_ z>TGiMoU!XUoa^ES+@D5VZaB8vOB@p4p+;g&lfc_)C@dZH5RY(GDM=HoyOj19H~b4n zGDdOXIVvH$qownEc`*lOh)AzY?19CXdd4RANkcxUMcMPv%>6ZC4bFq{@qA06)!SXv z5{fPyXbwV40Bvlgh+HU0wl{+k&qbExk?Hf2OlYzg&%Oh*C1Y7bXSF-tvbkNRK+}(` zp5ai<%jU3pU33wvZrDDa{f#4#yktnr5hX7gL-;m-PgH@+P;02-Af;p+9gH_j%#VZf z(_A96N@ z@WTwhihY-d%-r7->p2srwZ+{bG&werLNhk1jRKo@*eYJUN5D|>y5lG$RxY;BxpD{z zRu7GVZCi9_sMTfZ!OC#7W%_rTQ4zxI@xb2Ur8Jtl-ZQC_MP6@N^OLL>ScGO41|_+j zJeSvf*+jO$^;}%5ote($i%8FVkQH?a$CI9)A_!KaEq6IJ-GE9u=QOPYB4K|lEiod8 z6>?uhIhn+->74z|(RzHOxgdGO&v%wx^cm?NhCWfFqj39j%i<5v4{2TAZtiYQ+aLgM zVU*p`W@L)RGwFi(`E~b=zk{d)btAfDhU>6as7RV1=O&SglLI4$UFJ!|UU zVKW=w9rtxt-+g$@21Z!Ga#mg#HX~taV`x~xg|S2ArfgKyUHoLeRjg%3LxlQ>R$E`*-mFFV!S74+THh^XU2q`BAa{rsO0@O46b$tY$=AUoWM{!lYRJ{& zb~)Vp8O{Fjcy{i{SW*cqdjml??g~y%QODenaaR_wK0Ml#eYZ4GS&AzW2b1p+D2B%5 zP)8{o!s8_kGctfdRe9slL(k?c$5;Um5ZO@BmG4R^SASdbYABH+2(KM;)-6%EhA zGOMDJXlcINh=GyA=lvL$MCEAC}l2+6FEA&MRWQV+?(LN{Cc5w`J|R1 zbkp=3>f?|BXG`jYd2g%xsv~R4bcTTBlG7RDaG#>4_Yq-;JIl<`9Pe*$_fr{#PE=L` zuEO34)zBD>MOCT#`}>o*Ux)AQ&PVcj+-k}}tKo|?Krz9cksN`q_r{LDFMCMz>gW7K z5HnbbC|VJ*ge?v`q~at=hZMShTJDMqV!JZ^ zo&^ZrzAE>IzLYPY{p)#B)iCzc-NXJIz|Zy^M`N|xtT=J;^D{oF3+Vy6DarfoRyw&T zxpAi`f0{U^G3ILcuZV`Jlf8ga+_Fel*%rzA8Q!5V6*!yhuI6U8xd~4It(Eoh z`Rq)y*ZZ5=DDWj4RCav;Zw>OVRapG4Y7(2dIGlCvwR#`oa{gyWU<4!zjwEV^^?ht1 zEAXuS0|UNy6C8`q{D>U61A~L4lmzji7{W*vZ7wIXi`6=|+$(=f;a5?nevR~PmIXZv zC$Gfvi&7Xh;x$Zz>DS42921Ak$O}Xm1YZ<)3rD*ApnfD5(m;E4AF4ZW64L4Hwt6wPXJDF zW`&+f*25T!@s8k0o^wIxr+`HKn)R^IEc`%%C}+auqANhky%xPs41;@FOnvaASghU8 z(Cmu37F5cgsjzMT5YlWE8=oD&{Cox|l-4QZ!i!MQCwZr1hWkFcwK6g^UADDvyM5mf z3AjVX1~Xvr38%v7o6a|3!l^~728M=o#EEuMl%&YvLkt55+Ay%OBJ04oJ+6rXm3wq8 zGeK^MT9OVssRp8Irj}Ix5g){yW((W>g*+)vICr!tOdc3S;H`vv;>N(5Aq|_tfld}I zI{a=AB&JINGXxPk6{r?WP*%~!zZ(^i7K{=1B@iob>Ay^Wa;nHWX3-!g(4^jl111Vq zgVx^ZXlbw2D3eGqf3iqv8p@@aDdqGMSeu%fMwlcUBWE3Fh+-~AFavqNt3h6rMgxhs z0Wcq>c-QG{-9~r}7DtKSmEYH&4I@5=6X%u-h%YcqKT(+YgmH|xMW7JQH7O?dfzMRO!kIBomiuO zmrHBd;YJ#653T<>h}mS?V;Hwh2PdcAUW|~0B27v7H!LVMzd;mM-ELOMx3LtqDat#! zj#YC|rNft`$t&8WKYnIZH?qZ&H(J{wV9&U{FN2PI9{$})f~NP<PGseo#ML54JZRyR-TC%WD;(iXlNsq9fOlz}k#?p2-X+ z-xxTi32n_#U07XQUEh@20;P^tonKv2O^TZ@@necm6!g0w|EKf?%sofw=ow7K$1x&Z z+n#jLc{E-~#0-kRsaT=7L4W%0g(47 znnNX>P~^wBPz(q4cib}AOxEkBZ$e47FO-O+(E}bMRCSNh~YyszuS&S0jcX5uaYIJ_Qg#@QpX@s zmMRJ3lLEoH&?5N=KbI{Gw!!A){Mq!0OJAINh`+1PQ|laQo=$K7yE-WaE=L>f0--DMI1^1WBrmj3@!*ZGNj6b};{LnIK`1_+6 zhrbsuu2it?$-?4?J(8I10-&sjR|9c%U6Doxo?NMCp~JHw;Pzch;#Dy0jR%Hc;c);+ z{6@>7gVD)1_^c+2@fRVuUDgf5$Ru<9&c8h-(VZ%81B?cq91LHa1jxRvW47QmM;KQM zUVCgaXw?;udwR}s>?2Al`PUF6V(2FkKSM;*orkHMi-Q|X9cH^bhAsF(j;rSQbK#!RnaE8zP zj6i2&PrfK@Jz0;x>zsrwI5M7&`aP_U7CoE3VG5qM_NkKPCdh?l-JP@k$LpMaN3Dr? zZ=YrVgP_W&!EU;L~ z7b8#)cQjGL*&uWdIZc*2Y?*O~Yn%t0A%1yCqA@FGn@6ppm9#}*6hGDPdbdF!2kf?7 z{1eN5y$RVylHWjGR3*Rofyei-)jnH8kwWjUe;hFM@n@T~+@za@XWxS3(Q)# zAW}l`JGIu~{TG40K1fL4P~c@%<+ww;s_7blf#>Tb2&9K97oX*84&IcF-Me9J8JlZo zOhG^AV5`=Bi^={&T~UNUPsQsn=_N4uJxG7!@x#Uc@7O1;ypp4r{Pz3vq{hK}h*}aQ zBM}SO&w@#QMgXmYybDINt}6+>gCE$2e3m=Sy8q}?mL4q5YU$Q=yt_O`TCNTr`)#D2 z?_jrYr=;1ZCmqC63u7Mh?#O1`6A@v9(G~-Z0DE7Y^z`_<^1}M+*K2?82XuI+bEj;+ zy2^Tn=kc4r9KkGnP8F{ny#yITp;c@NH^68?Fy%e+5P6z!_3fL@NiAiqb<#zs?AAAt-ei|iMoV4}sbUSYk zpInAicH63u)wz=@tmWrqsz~(SapM~%r`<`BTPxJrzC(z?eNLhyQ|UTc<#lYIskl22 z?9!qRBv~4zu#tS-c}%jJouiN9|2(`I#id6CXAZu@b{Vfk_LZsoW$gYo$f zv%gSD9p`Sfe*n4>a^anfIFNf(UwYAB0Ovxxe?|=Rt^QPU> zx=CGzM$>El`%$KS+5codKGLY*)U2){Gtl+alJjEVerzSF;eOqO+z^>8mMv@#8}v+1 zO`%5=QS@}-{Nz22`V9}bAN=i?B*3RZo{;ix=l4L1sd#!?Y5yy3IVGr-?@iAjPe{^- zp3(#sEYd{#Md2QP%|31!&yE#Y3FC7oJ{br>gdgFP;N4Y#eSxH)g5+*#EcB+gO}5m zZH1^zml3*G;Q&Ex!++~!T*w5tv+at+{+T-DZBtxL;^BR&BxLu=y}t{Ysn7b^Tz8HI zBygTwMBXWw<$1cj!>2*uhNL%~o`)w0iFgaFT5pd}&=@rxUkW6V?4n3_BB7^4MIoi% z68{VJw;gz1p^`oxKX5pnt6ebx%od2Sh#O|vHjMx``>vd6b3j>3el>w(NnKu@sr#i- z#=GX~>-{(?z`!*=ii7P@6}wQ3%#<&3m8Rl4k&xdAmtYyGzT0-g1p?G4y zkupDUa{^i=(!VAvc>*kU2P3?Z%1U0@fXg<*Vm;(P7hEh(UpE2(IbP^dkrEFAfP>sf z`2)%)_}dTsj_Wz+V$=#HWsma-M_)p3qv;S+eSeOPEhb?6YO;jACN`&s#V}r$yBwbF zhQI6VX@DN46r5f&4w8;DirKlR@oC*>*W0i3*VpbCD@a^JydOf3LS!R+??Ur!feMHj zZTEN8D&i`!vwgu+vBxYfpa5Q}h8}%pJHagyl}$b^N{x01EH@n`*PF(O8NcAcp7)g! z8e=Sdj0&y#w62u*CX=Qn5pPYeHjVmbX+8qu!P z%5L|rwJGW^B4Z(hIxobiNZWgtAb2HjbD|(Vk>*11WA8I8ejrA!<_j`4(#|fosm};5 zvm@auJicQvB~r#MGT>6{5`58PnBZ9ef(U@t|HQfjb|8QSaLPE(*An4xvpWdnQr-wZ z99_8E{%6hF^f?DUG>Wt%%*wzBqBy&|<3>p`A zH6FJJgFNkD4Nou%mR`?J1GSP)SE>B_k#`}3%^}oBvTiTvSA715h}&HaQ%qaGAr?jc z@5z8RR*ctWZ!C7#-}(8j9M61P-+AUpi^aRqcZgk*PO^rkg9d!q6d99jAk_Ehn0{?w zh()?$*@m0OEj(-QmF`FG$p9oB7Avc9b&wX5`{ofSC}=`LLWld+=6B`iyVmh^zJi8} zi;IfNyfW$LF;GmpF|^2#pZs=js#UQxAD|;^o2BFC`bEk!#HRGFzg;6f zT$A9r)xMY~XGF?KHok#_U`IfBgQqRr=K|vezn(Z~sd#cnFAZ@5jhb`@?B`@}JE


5PSRaYJ!21p@;@vRJV)Y^bok{gqPTCsWlfn8S%BiXoj( zHkDTAQ_XQ!--}U zU}4ls&c?m@R7zb(sw6mannJe!k%fN6S*^$9b3;*cY1^`2E~(-JwRzEizWh_0dhMeA z?hdvx{l&MR%iSK&S6Vl{#ugS9=H})$HhfQJi|H}p?~A*-2$CTQ`vKZb!vg~c3mW
@c9qBW9@3%&)H&O9i=6HmJ)8PH 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 Y1EJs$=ggBv7XSbN07*qoM6N<$f@OY$;Q#;t literal 0 HcmV?d00001 diff --git a/kgpg/doc/options.png b/kgpg/doc/options.png new file mode 100644 index 0000000000000000000000000000000000000000..a97dc4ca71e4313159643c9c534da6b589e795f6 GIT binary patch literal 47516 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

`3nw^}R) zNk?q|4v0J42(jqg&yg+LW(x!@MkR>B&M>*H@F|LevN33R$-W5Qr_g2%=p>X-`hctl z%v>61OUT*=lz!3H)9p(CZJP>P4yf%dC6nv9m-a)W64081V9 z=4!0xJnd~x@neo?ZE9JjJuLXteQd>5#v1rF2?(JZw4C9ehojn0|49N21i4PB9?l!= zHBLdH%opQA25!;;y>js)B5tG^gI=mvJp;E%rBGlMg)Y$~W#EI*{WC8W_#mU4N%2Kf z!n!^u1f@1i^#|!V=C+1&jrPm-1xZ)9X(5~i3BZd9-J-b<2Ue1wo{GSS38aKAP=D^6 z2ZIp^Fb`-il5b!74G+s{v?O7>d7AA|aj1-_a@|79$pd>32AxTv!Q@3$O^f;H**PWQ z6yxIi3Rmdj;}sR-2wV>e6m*Zo*z~tFOtZtcD)n zTU@+7MY}@Q^tDrrq@4_fg+<*s;leeB_ORTigU;z314#1HB%NKgQ^pf9{KMrV1JB8B z;}8e}b2H%uQH%1#j73Ftf6?FPy!;jOk8qKl^P$ocslJ+czl|MAW0dA7%DlXY(WCVY z`z-M7=(D!LVRMWmWH&!Nme4$EOTyF%+3B3fVrW-OLzY;EKMvDs>Txf6BOHgSw#<_AoV4)kZH?~2UqSFU!DPp2#SB~ zRAFFy+SF%?9$DxKY2#U^F_XWSQ^d~M6tphhN<0*Lh~#eFKv-I=I_jz@O&@E)#-yt1 z7W5_Uj-CO;GN|WhA}Zc}!b)`8a~9cDloEUGAUo4Vm9a-sE5s0fH$&|>Ml3bs0i()4 z;f$%4OsZ=zd|zO6dT;B!_KL1!-OdH3ts#{dhBc&vY-kAl&yyF^$e zh^K(tf$z_j&7mIfMLjxNDqxylT{R54I55-0rlvA}PjJ93s>IuUMT9EHup*p8#W^W; z50yv%m0wlWWJfYS{-_dJG)j>C$eM^=^ydwxj>jR#maV>iP+nrfOs>NY4Uagz~xR%Mc=_l~vqv33zcneUJ;Zx%JH zFi?jH)neOA%j(x4b}1$XMnO>#IT@L;iHVA`a)WMj*DA0wDyk)F$p{xWL3*!IxBE-e zDSgIpg#}xxG#D3;4h;>Bj*bow508)c7s?igM@L^=TufG&^)X~MLB&Ua1_Cw5Ocr$W z?;d;%QAgK^T_mlKqIFu+8Rt3rj;XXltna2%>~y9wl(RLHw?QuXhTlF; z;(Lv@0DNiCT7FeyzR5C@iLTN9` zD=LDL5AU7}+8iLCKk!GIh-Tyn$B)1M-OJ_w@uQ%kf;P7__@JWeN7W)wi%1R|dr5Y7 z5M?#UiXmfD!wPEB7X>4b`n&!GnUKsp@guI{Yw(8W#d}F(5p3tj%=C13J}7W@yaUwP zPjP`6rMxhkn3QBhRdD5pS|tew-cY} z6bmuq^-#nu%PYnnQ8c0GOFlZuAwMcw#QhaNQ9Nv$!{b@Xp&85i^O>MHx1H6;T`%%< zNCZ6c=dYrlRQEJV=!^>sXPDJnZb?tVV^T0TBXd`NPob(7h`5G!;i$=@5)xLPMn^`n z;zshMN!hNARo!%K#*f8m1azZdV`IngZ(5CQ-Dahvq_niO#4X>f+DI^T1My?INr+ro zSO^!Z2{Si54Dgmej!%@<+nb8v&hmPM>Rm3}5ze&f-|($p4f0rWTFc6cTk#?XJKw(9 zXwLvUKF0o<>3xG8RV3XY2<9v_^_Rtjhwc6CVTcJoD*#X0vnUam3}2rn<>**nOH^j5 zK(ILt^tbrc9_tYxCpE&~Z_cN$uMc-!lo@$7TGRWLaSXUIcJ^B@ZcS53efBq5j^A>o z{STy7iQr&1{%hJVy}3kMLae)x&DaAHrCX<0?0mO5vt4>Dgr0fgTQg?^)lCGB4SQV%(>xMJC3uvohZv;$ut`7XmHCwI+$gqL; zP(t{krdEuG8MX?P?=`+I31AI?I{U~>*fkJFD`Gc=?vQXwhmQ6_KWjp`>**i=O-5l^c9 z>r!9W0XmdVhU;n2ZD*Uj?KVZC0)6A&o0)xAXEAFGz0yZlC zELkdI6P~}M^9~ngA<-Al`=zbkU&2g;>-BaQS-c0D8VN=I%fr9#9)4n?;-AODWpKHn z!Tqidgt|rzA%JBeailBc-cYeN`iZYRG5=d%ji4Uv3q7+7R zM%p6*4PH>=l@!I}9p4L2T9`W5(V8l$28wCBAM9&?`7~BFJa;U~WFY`%UXlEg2FU|MDH|c+nr9pGavgWSS`FL z?Td{7W|x8aeV6d}K&b)f55tSq>@=>w9Nuvdjpuv(hqqOWHpR&`^C}{z{XbVSmn>aO zeU{d0xVfF%%Nu4I{;n-+FlzOX2{nh(5|bu}B@1Ap?0F{(3?##Y^72I%Q94ir3vB48+cjiV-&lyag#*76sPv6D%=cO6t;d1NWbUSP}yo z>Mr)VML!j7*IC=S@CEpDP5k^`Y_#T9GkzquK=cN>FUy`IdV5lyljDl6YS*bAP|Qtf zp?SEKB8mNw3ju$W9*t}V#Yl2rYH z(~)$F9mJYg$9%CX6ACMBJkhcz_QG8G%!+Pm2rD<>h@Z?}+|Pm^ZVnvK{;9c=^{0{j z0o3hZEWIbkP1o=KY~XcIKq!YOE!uUIGsJ>J!L{ z8Ei(Qsl=FZ{qSCIn*Sg^9hZAJrjn!JM(h(=$uQx`Wpiv#vz(Zm5+=-I28bq6v5;XI zhIeR%Mo-FKOf8BUP6Ma%c+{!CCR4g!V;m}W#Nn=F zV67yfE)+)L4Mopk4@N8unN~`yLz?R!pDz*pGe-U_B?sLfp{wEe;kU+;&ehF9(nYK@ zU0I>mRo2ARpJCI0TM#sJVz9TFDo2t1rsl%^s^qj93ns>_x9m)^6t7!zmehR4{)O`S zP~6?_&@)SIH&kk&(0TKZ&b9C2*;0Bk|Eq{vzD z40|H(!hHsq>bim^`R8&F3*eI_pr-v#*a-}xSB*irMx)g~BKRe_AQT~Z)Da+IHfv(E zMaGIfxyW}t0Q+)wf5k1i<=(TRTa(y&oZkuPN1HRKkT24)T>uvlXDHLZL@bUz)n<6I zss;Ezy9mLPC`djQDW0u5dup+qpOy6EG$66E^wayU4I00>F=@wsQB{sqd6qFgN(=M- z`&5!v1Rk2X2cV&$rY-|j z4hSP&&Av6xtMDW`i&U|vOOvLI%&Od_n3X%8MSnPIIHWnUY9Eg!xnESObOV2w2J9Za zbOWI?kiw*uNCHuMSQF)?jeQz+tQ%xlR5X;XTxQt{hpt2l@1@52v#OuO0^DPf+~@=;OC(^5fY3Tz9V zeZao-iFZI6BA8oRSbv-Q@kzZ{FTA(cd)dsp%6y3#Pl=r9@nr8(cH+W+az!ZSZ#Hic zhRMN`1d3qlr;cPuizqeC>31b#rPu5P$jBH7upac;k zpz@Z;=L~hr^c!7sOBLIc*Z3%|7$PIt$Ao(34;zF&|KcYJjG-}Lp^cUM{r&3%2LZ2u0BSj67onJ7x5zS7@Q#>0VUWwl}B;& zvXkojiSx9eO=7szA);9&mH%u=st1??8$ktnhuPs9&(gMt=k9CYM7#;Kd~pAmT&dNC zQDj0xD~hRhqal7(h@4hB;*X4@w1Fsiix-n50!PLPYkOje4vySeS<)^d)z=@UsK5V7VUsspV~YSm?j#mvkvz*w=P-sP_ZDL?EWSf06wHc{^TpXf>f+xJ@18 zuyI9l=5w0)ykCCjz0+gnkWhl1C6SM3L^@f%^rZ6t$`1s!ziYq zrA<^3IPMk3UY>zj5e%PdmziOcjGv0DG|xSo64E@lxqZ|!oLLnkVPOd&E{z8rtNcExz?c5TK~4MBAOcu@qXc}?DOS+ z+e$jpnV1P&Xj%I7szR&Y)8(?2-X=FJ28jE&shI>0DKWz<7^_MX*C;-H z`lM|ZBb&^R!pXuRLA^W^@Nm#-_|mxXcF*2sk+uD+Ba8VZmbxm1!)?ZizP-7;a=t*> z*rY%%`+f=&onn*!{fu#34*6W{66K4y(}C{$Yp;Ibt4jY{#+jrPW(9`#noc-|DiN-sGq-AyJG*D?PCKhw5Mba_?~L}WX!(dxZ}gOmKRjv_R-$@?p|E` zoz2<^Ljd+{r8RhHl)jb4Ts04|va&Se?o!%)6Z2|!Eg-ij?Z;FfLh_`yQPQ-0euL0Y zZHGH${JKGfCkvrPaU$=_mlYc)A_AEK%CVXA$vAHwmOpX)u|A57Z3@cVJ3E!_`^VXCmFE^ha?}?Wbpo!iP`1A^;J_Jy?Dl)2V4v~i=(m=k02?hBnP*#zqs;zDN zFD;{JknSJ2czG&%>Nz<)J=ZF)sk%9Xb+o$FlXk4%7J|rB9(&Z(7BXTz=kF`pmJ4ms z**{satuP}9!=l2BWKmcs87>HuI&TdGy$K;RCd#OF6(H&9JRl=`ns)$gN;G6cU^9gO5QUid~ZuPCiF%3nmvft2=iC1H} z*YGgnwqD$Xx7<K{uCADIXA?oeE^OmC?XgQt|P)s=!# zm{}i?A-l5Uh96M*aq<2o=&Ea$s*sFKJClyb3|Chw$g_7&RU1SrS1s>9$m?qyi#%UR zGU@lVY1LCn-8@t!ynYjnuT&9KTJ8$I#Kq#Rxr|!*U@-W`rtS8^iq~C0E2{4yzCAl3 z^tt(T=I5bG!;HUE+F?ZH^HpV@DV8N*WK^En6RQ-lxKwD-94u+9l{G4~;G+)c^6L>l z3=GUym6I{b-m>9&^=a73d?8;wcU5vtQxV)0Tj;z&IDH$lu?leK7;gR4U|Q5ea7BWK zL4$^CdS<56hu6cp%LOmEhK@%X2H8w1sPT4lokY`SX*jn%lhcq(laWC!AHhO(f?*)F$KLI z%aO_$ZTG8v&t#iXt!BpGi)9)wb18?2NWYwsRDBF~zVy15DI)FILL_4U_9J4| zS`a00Y6qJ#aolYpNYnC9ROR|#Vrcjx7kw*h=`07wLVrYaOWwKJ+ts>B${HagSdd@4 zgQ@33D)TI0sOu{`QF%Y zWR^-PA-;lb3vb?`q^d%r5az`qkNLUnPc&+LY?k-nSyt_rl&|&__$ey=I_R)#-v~R7 z=MqOG=Lmr|$=gjDV>K!y_G(?z=ykuBoC?+8^Vn z&lnPfsp8M~{vhrurVIc_q@`1#eLZcp#YrUq=H4E2k=za+0SkJv=f%)S7@|#{Afv6- zavRRq-{3Ne?-hCZdG&g_GLB*tm?Fn!*9DSP!^7+P5j`uvq6z+%&Ifsor7iE=9Hd5w z?fuy4=?2F)N=&e&KIZ^C;+cLM|7R9!$a+NM^VPu^s#Vd|zkehD5yVat8{D|S3L*h3K}s}XBsxJ#)6&xNXE;>F%`4Tb6feUiEx>S(l!gc;|@ zKeANn3*nxpgDuZOD79=J||%nbUioG?^1>cUPQe2iy; zzmbls#8QDp1Merqlm&7TVus+uV(^)!iOGI)FBvuU3AlQFZf{IWF#*dJd#+7jM4~E65|{sDS_QSa;>$BX|I*8 z?#A=oNpEj2GZRy_=)dLoPoFiEJX>d%TfLNtfq~GOtOTVTE(^BZgbPyrHg%{ zPeAhFLI0nRl-&+q5Aiz>S+0d3%%CmvvUv#_o0;iMd`2$A#a^WKiBG*HgvWmG|s2$g8+a-@lCvmC8HY z;)trxJ(-$d#1J5I<&loCg?)}7%|oIL$r~ci0hh(cB#kHJIW-pSXUGm z(po1qnlBef99UL$#K*HFXXF=recJPsouGeMdue@GtN*j{dVbN4QUG+flioXE3sCX6 z3Qp+@3@qq_j;|S>F=0thTal4DtLVBMtphDtSup0eRm`o<+psk_)i*e+X-u!SCm@mn z5Jj)QhQo9-7OZl7vT#ZuZJ(=;d&9Jd=Vs5;8Yjt9<20oqtcy-EhOX)c6FMUi9+I z6X1GtdU^JF@?0Hvr&EhgGttGysn9dO@z~P1C%8h!{Xr2xDHjrLY02z_fSJi$U5%)b zOUtu(a~jvb`NuT5xk}OQ%IxW8=Q%c?lM2(HSORpbSZH9us6sjKI6rAMiZ_#Ss_k-g zKBMHL@cVJ7^UTFqxAU=`PxawJhI5wwMWVB3${X|GnT`ByNo#k_T+K0mCp{v?X@ER4 z9gtXQnNmODn}%QLKenpyUEIjnBm$oie*uwGBlPCR{$MPN)##F#2YdG*7kq6+QBXvw zr`3TGV4)C;tw+5(94+hGx9p=Y_}=!%;hcc!+hl1E)bv?nMK;gv=BEI-uRP8yftVxl z@kKJ3{@sBiQK55}Vw%haD z&B6FrKD1p4P`T?}qL;@*SMRyDZnxNQ`mb_Pgov|j8$9=ocA{TPUtY>Gbt}EzSS>#+ zRjLfM^Yhu~JLp=pAMQ0#B(1s)gW!)npZcRdA502jh0Gxla>y%c-JWxQ^phlP;*?Qw z*IVp`F*#8U)Y=y-m6D{qjF%;{4q@PE2l-*_bK zXz10cw-r5lGqFFu1;wAr zyK4aOkXvl+7cF|4{!O0{SU!XX{f7u_=9 zT}8kVH51*{pcvToi+#zeaS1SkETdw>c%X~Jz20csMO}YR?XJ18cQBFm!F^Hh{k`r&wV&sjz`U}U-`!U$dFUK7s^gCO z_cOsxSLoX73UcobzZ!x+aiRU^!t0xcCEAg#=YrOn7bRyg${4|XiN8pa%&&!<;(`A8 zLUt)RoFqVT1dX72ZL-($ySjQJX&2K%e?9P48i0P85O%t#fW@ZQB^D*o^qB$*?R141 z4ZB*aTW_QK%E_~4gLH9nETiVC{c9dODp0I*f@TW^+V;F;`B<5{#?7yzPgspZT`|b1 zz;YuCGO!n`Lj%V-ib0MHroK}8$fIP|9g-pn4 zY;mc*J!5(p7{bO5Ew`u9(H3nEg{X2E*Di9@vff&(>QepKfJh$sAI_dF)@3r-RWv@F zboWfo=rMq+Dxdd&Izq#E7^j%NFdoboi@{zYRBpkG`6WU%s%h@HpM81|SWfTNPuKoF ziX~QmQ8zG0Q-gp#^43KOJ24yhNBtb%$BI+-N!eUjRwQbdp7yX)%9=LZ|Hdi`&O+k} zn4JuTa(t})Kl7ZVgee?Z>PZEiy%XopTRarsCa)2h%hUt2?Sm;Xp?~7yR&oc-`zZ>3 zBT9pqoY#$|;JIdyI!#vA(hY$4)Jg~)p6A+t1Sp`~tL-PvM`M(tv>aUO){&LI3#@BZlLcggO?qmYZR zitP|geZQtu#VBEKhc5`pLV{@w1cqy?(^iJZD3n4YNO3`&endYq^^!Pb7yo|?ZAYsD z9?|xA5U#HF($=#SERRP@tV3=}Ij9waBohSVA|R;leXsos0jWR&x(W`mb}U-f0tuS_ zIN7(nsPzgU!`9;;vX=*_y(>FAa!SmSn3KB+-^0%d|LtP7$snksRl3aBOxv+z!w+4vJwy_UVReIDJX_~UH$#VMuJPN^`?buUb+uqWNM zy<-CB3oI`hV0uKd5DFDS0`XtE$Xfz*Y_^zoi`n*F4O-7CzSb10*!RdK26km`%)GX@ zp1B>q}e0Qqj`39X(G zm=`ap<19i#eT~Yti~q9P@a1m0bY$n_8NBY{6#=_UA#(VA3&Xp#bgze zk&0Ei-nmoTm5i9a+Bq|Eq{hxgLa}3TPV(q?Wjyb4+5ct?F7RMX9I1C=w|tX+LdN-f zK281%6n##}I8CQNDf@Ci%zd1Xc%zVgNyWd`bZoWptd_kmosHzwDcf6%i-1ltCh3Nx9{X1#4`dfIQ)COz#-^Mg7VWEi`F|3GRJ3TS7b=VU*TGO@5K z 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 literal 0 HcmV?d00001 diff --git a/kgpg/doc/select-secret-key.png b/kgpg/doc/select-secret-key.png new file mode 100644 index 0000000000000000000000000000000000000000..e99650c1011e3e17fb0f0df3adf1089692777189 GIT binary patch literal 24247 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 QZRHVUz={$TVg`Z#2M3k>vH$=8 literal 0 HcmV?d00001 diff --git a/kgpg/doc/systray.png b/kgpg/doc/systray.png new file mode 100644 index 0000000000000000000000000000000000000000..9a7c8749f94e0557c4800c0d20f12a9fc1d8bef6 GIT binary patch literal 8533 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 + * Copyright (C) 2007,2008,2009,2010,2011 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgeditor.h" + +#include "detailedconsole.h" +#include "selectsecretkey.h" +#include "kgpgmd5widget.h" +#include "kgpgsettings.h" +#include "keyservers.h" +#include "sourceselect.h" +#include "kgpg.h" +#include "keysmanager.h" +#include "editor/kgpgtextedit.h" +#include "transactions/kgpgdecrypt.h" +#include "transactions/kgpgkeyservergettransaction.h" +#include "transactions/kgpgsigntext.h" +#include "transactions/kgpgverify.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class KgpgView : public QWidget { +public: + KgpgView(QWidget *parent, KgpgTextEdit *editor, KToolBar *toolbar); +}; + +KgpgView::KgpgView(QWidget *parent, KgpgTextEdit *editor, KToolBar *toolbar) + : QWidget(parent) +{ + QVBoxLayout *vb = new QVBoxLayout(this); + vb->setSpacing(3); + vb->addWidget(editor); + vb->addWidget(toolbar); + + setAcceptDrops(true); +} + +KgpgEditor::KgpgEditor(KeysManager *parent, KGpgItemModel *model, Qt::WFlags f) + : KXmlGuiWindow(0, f), + m_editor(new KgpgTextEdit(this, model, parent)), + m_recentfiles(NULL), + m_find(0), + m_textchanged(false), + m_emptytext(true), + m_model(model), + m_parent(parent) +{ + // call inits to invoke all other construction parts + initActions(); + + connect(m_editor, SIGNAL(resetEncoding(bool)), SLOT(slotResetEncoding(bool))); + KgpgView *kb = new KgpgView(this, m_editor, toolBar(QLatin1String( "gpgToolBar" ))); + setCentralWidget(kb); + setCaption(i18n("Untitled"), false); + m_editredo->setEnabled(false); + m_editundo->setEnabled(false); + m_editcopy->setEnabled(false); + m_editcut->setEnabled(false); + + setObjectName( QLatin1String("editor" )); + slotSetFont(KGpgSettings::font()); + setupGUI((ToolBar | Keys | StatusBar | Save | Create), QLatin1String( "kgpgeditor.rc" )); + setAutoSaveSettings(QLatin1String( "Editor" ), true); + + connect(m_editor, SIGNAL(textChanged()), SLOT(modified())); + connect(m_editor, SIGNAL(newText()), SLOT(newText())); + connect(m_editor, SIGNAL(undoAvailable(bool)), SLOT(slotUndoAvailable(bool))); + connect(m_editor, SIGNAL(redoAvailable(bool)), SLOT(slotRedoAvailable(bool))); + connect(m_editor, SIGNAL(copyAvailable(bool)), SLOT(slotCopyAvailable(bool))); +} + +KgpgEditor::~KgpgEditor() +{ + m_recentfiles->saveEntries( KConfigGroup(KGlobal::config(), "Recent Files" ) ); +} + +void KgpgEditor::openDocumentFile(const KUrl& url, const QString &encoding) +{ + QString tempopenfile; + if(KIO::NetAccess::download(url, tempopenfile, this)) + { + QFile qfile(tempopenfile); + if (qfile.open(QIODevice::ReadOnly)) + { + QTextStream t(&qfile); + t.setCodec(encoding.toAscii()); + m_editor->setPlainText(t.readAll()); + qfile.close(); + m_docname = url; + m_textchanged = false; + m_emptytext = false; + setCaption(url.fileName(), false); + m_recentfiles->addUrl(url); + } + KIO::NetAccess::removeTempFile(tempopenfile); + } +} + +void KgpgEditor::openEncryptedDocumentFile(const KUrl& url) +{ + m_editor->slotDroppedFile(url); +} + +void KgpgEditor::slotSetFont(QFont myFont) +{ + m_editor->setFont(myFont); +} + +void KgpgEditor::closeWindow() +{ + m_recentfiles->saveEntries( KConfigGroup(KGlobal::config(), "Recent Files" ) ); + close(); +} + +void KgpgEditor::saveOptions() +{ + KGpgSettings::setFirstRun(false); + KGpgSettings::self()->writeConfig(); +} + +void KgpgEditor::initActions() +{ + KStandardAction::openNew(this, SLOT(slotFileNew()), actionCollection()); + KStandardAction::open(this, SLOT(slotFileOpen()), actionCollection()); + KStandardAction::save(this, SLOT(slotFileSave()), actionCollection()); + KStandardAction::saveAs(this, SLOT(slotFileSaveAs()), actionCollection()); + KStandardAction::close(this, SLOT(slotFileClose()), actionCollection()); + KStandardAction::paste(this, SLOT(slotEditPaste()), actionCollection()); + KStandardAction::print(this, SLOT(slotFilePrint()), actionCollection()); + KStandardAction::selectAll(this, SLOT(slotSelectAll()), actionCollection()); + KStandardAction::find(this, SLOT(slotFind()), actionCollection()); + KStandardAction::findNext(this, SLOT(slotFindNext()), actionCollection()); + KStandardAction::findPrev(this, SLOT(slotFindPrev()), actionCollection()); + actionCollection()->addAction(KStandardAction::Preferences, QLatin1String( "options_configure" ), + this, SLOT(slotOptions())); + + m_editundo = KStandardAction::undo(this, SLOT(slotundo()), actionCollection()); + m_editredo = KStandardAction::redo(this, SLOT(slotredo()), actionCollection()); + m_editcopy = KStandardAction::copy(this, SLOT(slotEditCopy()), actionCollection()); + m_editcut = KStandardAction::cut(this, SLOT(slotEditCut()), actionCollection()); + + m_recentfiles = KStandardAction::openRecent(this, SLOT(openDocumentFile(KUrl)), this); + menuBar()->addAction(m_recentfiles); + + m_recentfiles->loadEntries( KConfigGroup(KGlobal::config(), "Recent Files" ) ); + m_recentfiles->setMaxItems(KGpgSettings::recentFiles()); + + KAction *action = actionCollection()->addAction(QLatin1String("file_encrypt"), this, SLOT(slotFilePreEnc())); + action->setIcon(KIcon( QLatin1String( "document-encrypt" ))); + action->setText(i18n("&Encrypt File...")); + + action = actionCollection()->addAction(QLatin1String("file_decrypt"), this, SLOT(slotFilePreDec())); + action->setIcon(KIcon( QLatin1String( "document-decrypt" ))); + action->setText(i18n("&Decrypt File...")); + + action = actionCollection()->addAction(QLatin1String("key_manage"), this, SLOT(slotKeyManager())); + action->setIcon(KIcon( QLatin1String( "kgpg" ))); + action->setText(i18n("&Open Key Manager")); + + action = actionCollection()->addAction(QLatin1String("sign_generate"), this, SLOT(slotPreSignFile())); + action->setText(i18n("&Generate Signature...")); + action->setIcon(KIcon( QLatin1String( "document-sign-key" ))); + + action = actionCollection()->addAction(QLatin1String("sign_verify"), this, SLOT(slotPreVerifyFile())); + action->setText(i18n("&Verify Signature...")); + + action = actionCollection()->addAction(QLatin1String("sign_check"), this, SLOT(slotCheckMd5())); + action->setText(i18n("&Check MD5 Sum...")); + + m_encodingaction = actionCollection()->add(QLatin1String("charsets"), this, SLOT(slotSetCharset())); + m_encodingaction->setText(i18n("&Unicode (utf-8) Encoding")); + + actionCollection()->addAction(m_recentfiles->objectName(), m_recentfiles); + + action = actionCollection()->addAction(QLatin1String("text_encrypt"), m_editor, SLOT(slotEncode())); + action->setIcon(KIcon( QLatin1String( "document-encrypt" ))); + action->setText(i18n("En&crypt")); + + action = actionCollection()->addAction(QLatin1String("text_decrypt"), m_editor, SLOT(slotDecode())); + action->setIcon(KIcon( QLatin1String( "document-decrypt" ))); + action->setText(i18n("&Decrypt")); + + action = actionCollection()->addAction(QLatin1String("text_sign_verify"), m_editor, SLOT(slotSignVerify())); + action->setIcon(KIcon( QLatin1String( "document-sign-key" ))); + action->setText(i18n("S&ign/Verify")); +} + +bool KgpgEditor::queryClose() +{ + bool b = saveBeforeClear(); + if (b) { + m_editor->clear(); + newText(); + } + return b; +} + +bool KgpgEditor::saveBeforeClear() +{ + if (m_textchanged) + { + QString fname; + if (m_docname.fileName().isEmpty()) + fname = i18n("Untitled"); + else + fname = m_docname.fileName(); + + QString msg = i18n("The document \"%1\" has changed.\nDo you want to save it?", fname); + QString caption = i18n("Close the document"); + int res = KMessageBox::warningYesNoCancel(this, msg, caption, KStandardGuiItem::save(), KStandardGuiItem::discard()); + if (res == KMessageBox::Yes) + return slotFileSave(); + else + if (res == KMessageBox::No) + return true; + else + return false; + } + + return true; +} + +void KgpgEditor::slotFileNew() +{ + if (saveBeforeClear()) + { + m_editor->clear(); + newText(); + } +} + +void KgpgEditor::slotFileOpen() +{ + if (saveBeforeClear()) + { + KEncodingFileDialog::Result loadResult; + loadResult = KEncodingFileDialog::getOpenUrlAndEncoding(QString(), QString(), QString(), this); + KUrl url = loadResult.URLs.first(); + m_textencoding = loadResult.encoding; + + if(!url.isEmpty()) + openDocumentFile(url, m_textencoding); + } +} + +bool KgpgEditor::slotFileSave() +{ + QString filn = m_docname.path(); + if (filn.isEmpty()) + return slotFileSaveAs(); + + QTextCodec *cod = QTextCodec::codecForName(m_textencoding.toAscii()); + + if (cod == NULL) { + KMessageBox::sorry(this, i18n("The document could not been saved, as the selected codec is not supported.")); + return false; + } + + if (!checkEncoding(cod)) + { + KMessageBox::sorry(this, i18n("The document could not been saved, as the selected encoding cannot encode every unicode character in it.")); + return false; + } + + if (m_docname.isLocalFile()) + { + QFile f(filn); + if (!f.open(QIODevice::WriteOnly)) + { + KMessageBox::sorry(this, i18n("The document could not be saved, please check your permissions and disk space.")); + return false; + } + + QTextStream t(&f); + t.setCodec(cod); + t << m_editor->toPlainText(); + f.close(); + } + else + { + KTemporaryFile tmpfile; + tmpfile.open(); + QTextStream stream(&tmpfile); + stream.setCodec(cod); + stream << m_editor->toPlainText(); + + if(!KIO::NetAccess::upload(tmpfile.fileName(), m_docname, this)) + { + KMessageBox::sorry(this, i18n("The document could not be saved, please check your permissions and disk space.")); + return false; + } + } + + m_textchanged = false; + m_emptytext = false; + setCaption(m_docname.fileName(), false); + return true; +} + +bool KgpgEditor::slotFileSaveAs() +{ + KEncodingFileDialog::Result saveResult; + saveResult = KEncodingFileDialog::getSaveUrlAndEncoding(QString(), QString(), QString(), this); + KUrl url; + + if (!saveResult.URLs.empty()) + url = saveResult.URLs.first(); + + if(!url.isEmpty()) { + const QString selectedEncoding = saveResult.encoding; + if (url.isLocalFile()) { + QString filn = url.path(); + QFile f(url.path()); + if (f.exists()) { + const QString message = i18n("Overwrite existing file %1?", url.fileName()); + int result = KMessageBox::warningContinueCancel(this, message, QString(), KStandardGuiItem::overwrite()); + if (result == KMessageBox::Cancel) + return false; + } + f.close(); + } else if (KIO::NetAccess::exists(url, KIO::NetAccess::DestinationSide, this)) { + const QString message = i18n("Overwrite existing file %1?", url.fileName()); + int result = KMessageBox::warningContinueCancel(this, message, QString(), KStandardGuiItem::overwrite()); + if (result == KMessageBox::Cancel) + return false; + } + + m_docname = url; + m_textencoding = selectedEncoding; + slotFileSave(); + return true; + } + + return false; +} + +void KgpgEditor::slotFilePrint() +{ + QPrinter prt; + QPointer printDialog = new QPrintDialog(&prt, this); + if (printDialog->exec() == QDialog::Accepted) { + int width = prt.width(); + int height = prt.height(); + QPainter painter(&prt); + painter.drawText(0, 0, width, height, Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip, m_editor->toPlainText()); + } + delete printDialog; +} + +void KgpgEditor::slotFind() +{ + QPointer fd = new KFindDialog(this); + + if (m_find) { + fd->setOptions(m_find->options()); + fd->setPattern(m_find->pattern()); + } + + if (fd->exec() != QDialog::Accepted) { + delete fd; + return; + } + + if (!m_find) { + m_find = new KFind(fd->pattern(), fd->options(), this); + + if (m_find->options() & KFind::FromCursor) + m_find->setData(m_editor->toPlainText(), m_editor->textCursor().selectionStart()); + else + m_find->setData(m_editor->toPlainText()); + connect(m_find, SIGNAL(highlight(QString,int,int)), m_editor, SLOT(slotHighlightText(QString,int,int))); + connect(m_find, SIGNAL(findNext()), this, SLOT(slotFindText())); + } else { + m_find->setPattern(fd->pattern()); + m_find->setOptions(fd->options()); + m_find->resetCounts(); + } + + slotFindText(); + delete fd; +} + +void KgpgEditor::slotFindNext() +{ + slotFindText(); +} + +void KgpgEditor::slotFindPrev() +{ + if(!m_find) + { + slotFind(); + return; + } + long oldopt = m_find->options(); + long newopt = oldopt ^ KFind::FindBackwards; + m_find->setOptions(newopt); + slotFindText(); + m_find->setOptions(oldopt); +} + +void KgpgEditor::slotFindText() +{ + if (!m_find) + { + slotFind(); + return; + } + + if (m_find->find() == KFind::NoMatch) + { + if (m_find->numMatches() == 0) + { + m_find->displayFinalDialog(); + delete m_find; + m_find = 0; + } + else + { + if (m_find->shouldRestart(true, false)) + { + m_find->setData(m_editor->toPlainText()); + slotFindText(); + } + else + m_find->closeFindNextDialog(); + } + } +} + +void KgpgEditor::slotFilePreEnc() +{ + KUrl::List urls = KFileDialog::getOpenUrls(KUrl(), i18n("*|All Files"), this, i18n("Open File to Encode")); + if (urls.isEmpty()) + return; + + KGpgExternalActions::encryptFiles(m_parent, urls); +} + +void KgpgEditor::slotFilePreDec() +{ + KUrl url = KFileDialog::getOpenUrl(KUrl(), i18n("*|All Files"), this, i18n("Open File to Decode")); + if (url.isEmpty()) + return; + + QString oldname = url.fileName(); + QString newname; + + if (oldname.endsWith(QLatin1String(".gpg")) || oldname.endsWith(QLatin1String(".asc")) || oldname.endsWith(QLatin1String(".pgp"))) + oldname.chop(4); + else + oldname.append(QLatin1String( ".clear" )); + oldname.prepend(url.directory(KUrl::AppendTrailingSlash)); + + QPointer popn = new KDialog(this); + popn->setCaption( i18n("Decrypt File To") ); + popn->setButtons( KDialog::Ok | KDialog::Cancel ); + popn->setDefaultButton( KDialog::Ok ); + popn->setModal( true ); + + SrcSelect *page = new SrcSelect(); + popn->setMainWidget(page); + page->newFilename->setUrl(oldname); + page->newFilename->setMode(KFile::File); + page->newFilename->setWindowTitle(i18n("Save File")); + + page->checkClipboard->setText(i18n("Editor")); + page->resize(page->minimumSize()); + popn->resize(popn->minimumSize()); + if (popn->exec() == QDialog::Accepted) + { + if (page->checkFile->isChecked()) + newname = page->newFilename->url().path(); + } + else + { + delete popn; + return; + } + delete popn; + + if (!newname.isEmpty()) + { + QFile fgpg(newname); + if (fgpg.exists()) { + QPointer over = new KIO::RenameDialog(this, i18n("File Already Exists"), KUrl(), KUrl::fromPath(newname), KIO::M_OVERWRITE); + + if (over->exec() != QDialog::Accepted) { + delete over; + return; + } + newname = over->newDestUrl().path(); + delete over; + } + + KGpgDecrypt *decr = new KGpgDecrypt(this, url, KUrl(newname)); + connect(decr, SIGNAL(done(int)), SLOT(slotLibraryDone())); + decr->start(); + } + else + openEncryptedDocumentFile(url); +} + +void +KgpgEditor::slotLibraryDone() +{ + sender()->deleteLater(); +} + +void KgpgEditor::slotKeyManager() +{ + m_parent->show(); + m_parent->raise(); +} + +void KgpgEditor::slotFileClose() +{ + saveOptions(); + close(); +} + +void KgpgEditor::slotundo() +{ + m_editor->undo(); +} + +void KgpgEditor::slotredo() +{ + m_editor->redo(); +} + +void KgpgEditor::slotEditCut() +{ + m_editor->cut(); +} + +void KgpgEditor::slotEditCopy() +{ + m_editor->copy(); +} + +void KgpgEditor::slotEditPaste() +{ + m_editor->paste(); +} + +void KgpgEditor::slotSelectAll() +{ + m_editor->selectAll(); +} + +void KgpgEditor::slotSetCharset() +{ + if (!m_encodingaction->isChecked()) + m_editor->setPlainText(QString::fromUtf8(m_editor->toPlainText().toAscii())); + else + { + if (checkEncoding(QTextCodec::codecForLocale())) + return; + m_editor->setPlainText(QLatin1String( m_editor->toPlainText().toUtf8() )); + } +} + +bool KgpgEditor::checkEncoding(QTextCodec *codec) +{ + return codec->canEncode(m_editor->toPlainText()); +} + +void KgpgEditor::slotResetEncoding(bool enc) +{ + m_encodingaction->setChecked(enc); +} + +void KgpgEditor::slotPreSignFile() +{ + // create a detached signature for a chosen file + KUrl url = KFileDialog::getOpenUrl(KUrl(), i18n("*|All Files"), this, i18n("Open File to Sign")); + if (!url.isEmpty()) + slotSignFile(url); +} + +void KgpgEditor::slotSignFile(const KUrl &url) +{ + // create a detached signature for a chosen file + if (!url.isEmpty()) + { + QString signKeyID; + QPointer opts = new KgpgSelectSecretKey(this, m_model, false); + if (opts->exec() == QDialog::Accepted) { + signKeyID = opts->getKeyID(); + } else { + delete opts; + return; + } + + delete opts; + + KGpgSignText::SignOptions sopts = KGpgSignText::DetachedSignature; + if (KGpgSettings::asciiArmor()) + sopts |= KGpgSignText::AsciiArmored; + + KGpgSignText *signt = new KGpgSignText(this, signKeyID, KUrl::List(url), sopts); + connect(signt, SIGNAL(done(int)), SLOT(slotSignFileFin())); + signt->start(); + } +} + +void KgpgEditor::slotSignFileFin() +{ + sender()->deleteLater(); +} + +void KgpgEditor::slotPreVerifyFile() +{ + KUrl url = KFileDialog::getOpenUrl(KUrl(), i18n("*|All Files"), this, i18n("Open File to Verify")); + slotVerifyFile(url); +} + +void KgpgEditor::slotVerifyFile(const KUrl &url) +{ + if (!url.isEmpty()) + { + QString sigfile; + if (!url.fileName().endsWith(QLatin1String(".sig"))) + { + sigfile = url.path() + QLatin1String( ".sig" ); + QFile fsig(sigfile); + if (!fsig.exists()) + { + sigfile = url.path() + QLatin1String( ".asc" ); + QFile fsig(sigfile); + // if no .asc or .sig signature file included, assume the file is internally signed + if (!fsig.exists()) + sigfile.clear(); + } + } + + KUrl::List chkfiles; + if (sigfile.isEmpty()) + chkfiles << url; + else + chkfiles << KUrl::fromPath(sigfile); + + KGpgVerify *verify = new KGpgVerify(this, chkfiles); + connect(verify, SIGNAL(done(int)), m_editor, SLOT(slotVerifyDone(int))); + verify->start(); + } +} + +void KgpgEditor::slotCheckMd5() +{ + // display md5 sum for a chosen file + KUrl url = KFileDialog::getOpenUrl(KUrl(), i18n("*|All Files"), this, i18n("Open File to Verify")); + if (!url.isEmpty()) { + QPointer mdwidget = new Md5Widget(this, url); + mdwidget->exec(); + delete mdwidget; + } +} + +void KgpgEditor::importSignatureKey(const QString &id, const QString &fileName) +{ + sender()->deleteLater(); + + if (KMessageBox::questionYesNo(0, + i18n("Missing signature:
Key id: %1

Do you want to import this key from a keyserver?
", id), + fileName, KGuiItem(i18n("Import")), KGuiItem(i18n("Do Not Import"))) != KMessageBox::Yes) + return; + + KeyServer *ks = new KeyServer(this); + + connect(ks, SIGNAL(importFinished(QStringList)), SLOT(slotDownloadKeysFinished(QStringList))); + connect(ks, SIGNAL(importFailed()), ks, SLOT(deleteLater())); + + ks->startImport(QStringList(id), QString(),QLatin1String( qgetenv("http_proxy") )); +} + +void KgpgEditor::slotDownloadKeysFinished(QStringList ids) +{ + m_parent->refreshKeys(ids); + + sender()->deleteLater(); +} + +void +KgpgEditor::slotVerifyFinished(const QString &id, const QString &message) +{ + sender()->deleteLater(); + + QString showId; + + if (id.isEmpty()) + showId = i18n("No signature found."); + else + showId = id; + + (void) new KgpgDetailedInfo(this, showId, message, QStringList(), + i18nc("Caption of message box", "Verification Finished")); +} + +void KgpgEditor::slotOptions() +{ + m_parent->showOptions(); +} + +void KgpgEditor::modified() +{ + QString capt = m_docname.fileName(); + if (m_emptytext) { + m_textchanged = !m_editor->toPlainText().isEmpty(); + if (capt.isEmpty()) + capt = i18n("Untitled"); + } else if (!m_textchanged) { + m_textchanged = true; + } + setCaption(capt, m_textchanged); + m_editor->document()->setModified(m_textchanged); +} + +void KgpgEditor::slotUndoAvailable(const bool v) +{ + m_editundo->setEnabled(v); +} + +void KgpgEditor::slotRedoAvailable(const bool v) +{ + m_editredo->setEnabled(v); +} + +void KgpgEditor::slotCopyAvailable(const bool v) +{ + m_editcopy->setEnabled(v); + m_editcut->setEnabled(v); +} + +void KgpgEditor::newText() +{ + m_textchanged = false; + m_emptytext = true; + m_docname.clear(); + setCaption(i18n("Untitled"), false); + slotResetEncoding(false); +} + +#include "kgpgeditor.moc" diff --git a/kgpg/editor/kgpgeditor.h b/kgpg/editor/kgpgeditor.h new file mode 100644 index 00000000..88918aed --- /dev/null +++ b/kgpg/editor/kgpgeditor.h @@ -0,0 +1,138 @@ +/*************************************************************************** + kgpgeditor.h - description + ------------------- + begin : Mon Jul 8 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGEDITOR_H +#define KGPGEDITOR_H + +#include +#include + +class KToggleAction; +class KAction; +class KFind; + +class KgpgTextEdit; +class KGpgItemModel; +class KeysManager; +class KRecentFilesAction; + +class KgpgEditor : public KXmlGuiWindow +{ + Q_OBJECT + friend class KgpgView; + + KgpgEditor(); // = delete C++0x +public: + KgpgEditor(KeysManager *parent, KGpgItemModel *model, Qt::WFlags f); + ~KgpgEditor(); + + void openEncryptedDocumentFile(const KUrl& url); + + KgpgTextEdit * const m_editor; + KRecentFilesAction *m_recentfiles; + +signals: + void openChangeFont(); + void openConfigDialog(); + +public slots: + void openDocumentFile(const KUrl& url, const QString &encoding = QString()); + void slotSetFont(QFont myFont); + void closeWindow(); + +protected: + void saveOptions(); + void initActions(); + bool queryClose(); + bool saveBeforeClear(); + +private slots: + // File menu + void slotFileNew(); + void slotFileOpen(); + bool slotFileSave(); + bool slotFileSaveAs(); + void slotFilePrint(); + void slotFilePreEnc(); + void slotFilePreDec(); + void slotKeyManager(); + void slotFileClose(); + + // Edit menu + void slotundo(); + void slotredo(); + void slotEditCut(); + void slotEditCopy(); + void slotEditPaste(); + void slotSelectAll(); + void slotFind(); + void slotFindNext(); + void slotFindPrev(); + void slotFindText(); + + // Coding menu + void slotSetCharset(); + void slotResetEncoding(bool enc); + bool checkEncoding(QTextCodec *codec); + + // Signing menu + void slotPreSignFile(); + void slotSignFile(const KUrl &url); + void slotSignFileFin(); + void slotPreVerifyFile(); + void slotVerifyFile(const KUrl &url); + void slotCheckMd5(); + void importSignatureKey(const QString &id, const QString &fileName); + /** + * @param id the key id of the signature + * @param message the verification message from GnuPG + */ + void slotVerifyFinished(const QString &id, const QString &message); + + // Options menu + void slotOptions(); + + void slotUndoAvailable(const bool v); + void slotRedoAvailable(const bool v); + void slotCopyAvailable(const bool v); + + void modified(); + void newText(); + + void slotLibraryDone(); + + void slotDownloadKeysFinished(QStringList ids); + +private: + QString m_textencoding; + + KToggleAction *m_encodingaction; + KAction *m_editundo; + KAction *m_editredo; + KAction *m_editcopy; + KAction *m_editcut; + KFind *m_find; + KUrl m_docname; + + bool m_textchanged; //< text was changed since last save + bool m_emptytext; //< this was not saved to a file ever + + KGpgItemModel *m_model; + KeysManager *m_parent; +}; + +#endif // KGPGEDITOR_H diff --git a/kgpg/editor/kgpgeditor.rc b/kgpg/editor/kgpgeditor.rc new file mode 100644 index 00000000..e3ee1a75 --- /dev/null +++ b/kgpg/editor/kgpgeditor.rc @@ -0,0 +1,29 @@ + + + +

+ + + + + + + + + &View + + + + + Si&gnature + + + + + + + + + + + diff --git a/kgpg/editor/kgpgmd5widget.cpp b/kgpg/editor/kgpgmd5widget.cpp new file mode 100644 index 00000000..8dcab065 --- /dev/null +++ b/kgpg/editor/kgpgmd5widget.cpp @@ -0,0 +1,101 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgmd5widget.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +Md5Widget::Md5Widget(QWidget *parent, const KUrl &url) + : KDialog(parent) +{ + setCaption(i18n("MD5 Checksum")); + setButtons(Apply | Close); + setDefaultButton(Close); + setButtonText(Apply, i18n("Compare MD5 with Clipboard")); + + QFile f(url.path()); + KMD5 checkfile; + + if (f.open(QIODevice::ReadOnly)) { + checkfile.update(f); + f.close(); + } + + m_md5sum = QLatin1String( checkfile.hexDigest().constData() ); + + QWidget *page = new QWidget(this); + + QLabel *firstlabel = new QLabel(page); + firstlabel->setText(i18n("MD5 sum for %1 is:", url.fileName())); + + KLineEdit *md5lineedit = new KLineEdit(m_md5sum, page); + md5lineedit->setReadOnly(true); + + m_led = new KLed(QColor(80, 80, 80), KLed::Off, KLed::Sunken, KLed::Circular, page); + QSizePolicy policy(QSizePolicy::Fixed, QSizePolicy::Fixed); + policy.setVerticalStretch(0); + policy.setHorizontalStretch(0); + policy.setHeightForWidth(m_led->sizePolicy().hasHeightForWidth()); + m_led->setSizePolicy(policy); + + m_label = new QLabel(page); + m_label->setText(i18n("Unknown status")); + + QHBoxLayout *ledlayout = new QHBoxLayout(); + ledlayout->addWidget(m_led); + ledlayout->addWidget(m_label); + + QVBoxLayout *dialoglayout = new QVBoxLayout(page); + dialoglayout->setMargin(marginHint()); + dialoglayout->setSpacing(spacingHint()); + dialoglayout->addWidget(firstlabel); + dialoglayout->addWidget(md5lineedit); + dialoglayout->addLayout(ledlayout); + dialoglayout->addStretch(); + + setMainWidget(page); + + connect(this, SIGNAL(applyClicked()), this, SLOT(slotApply())); +} + +void Md5Widget::slotApply() +{ + QString text = KApplication::clipboard()->text().remove(QLatin1Char( ' ' )); + if (!text.isEmpty()) + { + if (text.length() != m_md5sum.length()) + KMessageBox::sorry(this, i18n("Clipboard content is not a MD5 sum.")); + else + if (text == m_md5sum) + { + m_label->setText(i18n("Correct checksum, file is ok.")); + m_led->setColor(QColor(Qt::green)); + m_led->on(); + } + else + { + m_label->setText(i18n("Wrong checksum, file corrupted")); + m_led->setColor(QColor(Qt::red)); + m_led->on(); + } + } +} + +#include "kgpgmd5widget.moc" diff --git a/kgpg/editor/kgpgmd5widget.h b/kgpg/editor/kgpgmd5widget.h new file mode 100644 index 00000000..70cb5e09 --- /dev/null +++ b/kgpg/editor/kgpgmd5widget.h @@ -0,0 +1,38 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGMD5WIDGET_H +#define KGPGMD5WIDGET_H + + + +#include +#include + +class QLabel; + +class KLed; + +class Md5Widget : public KDialog +{ + Q_OBJECT + +public: + explicit Md5Widget(QWidget *parent = 0, const KUrl &url = KUrl()); + +public slots: + void slotApply(); + +private: + QString m_md5sum; + QLabel *m_label; + KLed *m_led; +}; + +#endif // KGPGMD5WIDGET_H diff --git a/kgpg/editor/kgpgtextedit.cpp b/kgpg/editor/kgpgtextedit.cpp new file mode 100644 index 00000000..c9e091c2 --- /dev/null +++ b/kgpg/editor/kgpgtextedit.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2009,2010,2011 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgtextedit.h" + +#include "selectsecretkey.h" +#include "kgpgsettings.h" +#include "keyservers.h" +#include "selectpublickeydialog.h" +#include "detailedconsole.h" +#include "keysmanager.h" +#include "editor/kgpgeditor.h" +#include "transactions/kgpgdecrypt.h" +#include "transactions/kgpgencrypt.h" +#include "transactions/kgpgimport.h" +#include "transactions/kgpgsigntext.h" +#include "transactions/kgpgverify.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIGNEDMESSAGE_BEGIN QLatin1String( "-----BEGIN PGP SIGNED MESSAGE-----" ) +#define SIGNEDMESSAGE_END QLatin1String( "-----END PGP SIGNATURE-----" ) + +KgpgTextEdit::KgpgTextEdit(QWidget *parent, KGpgItemModel *model, KeysManager *manager) + : KTextEdit(parent), + m_posstart(-1), + m_posend(-1), + m_model(model), + m_keysmanager(manager) +{ + setCheckSpellingEnabled(true); + setAcceptDrops(true); + setReadOnly(false); + setUndoRedoEnabled(true); +} + +KgpgTextEdit::~KgpgTextEdit() +{ +} + +void KgpgTextEdit::dragEnterEvent(QDragEnterEvent *e) +{ + // if a file is dragged into editor ... + if (KUrl::List::canDecode(e->mimeData()) || e->mimeData()->hasText()) + e->acceptProposedAction(); +} + +void KgpgTextEdit::dropEvent(QDropEvent *e) +{ + // decode dropped file or dropped text + KUrl::List uriList = KUrl::List::fromMimeData(e->mimeData()); + if (!uriList.isEmpty()) + slotDroppedFile(uriList.first()); + else + if (e->mimeData()->hasText()) + insertPlainText(e->mimeData()->text()); +} + +void KgpgTextEdit::slotDroppedFile(const KUrl &url) +{ + openDroppedFile(url, true); +} + +void KgpgTextEdit::openDroppedFile(const KUrl& url, const bool probe) +{ + KUrl tmpurl; + + if (url.isLocalFile()) { + m_tempfile = url.path(); + tmpurl = url; + } else { + if (KMessageBox::warningContinueCancel(this, i18n("Remote file dropped.
The remote file will now be copied to a temporary file to process requested operation. This temporary file will be deleted after operation.
"), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QLatin1String( "RemoteFileWarning" )) != KMessageBox::Continue) + return; + + if (!KIO::NetAccess::download(url, m_tempfile, this)) { + KMessageBox::sorry(this, i18n("Could not download file.")); + return; + } + tmpurl = KUrl::fromPath(m_tempfile); + } + + QString result; + + QFile qfile(m_tempfile); + if (qfile.open(QIODevice::ReadOnly)) { + QTextStream t(&qfile); + result = t.readAll(); + qfile.close(); + } + + if (result.isEmpty()) + return; + + if (!probe || KGpgDecrypt::isEncryptedText(result, &m_posstart, &m_posend)) { + // if pgp data found, decode it + KGpgDecrypt *decr = new KGpgDecrypt(this, KUrl::List(tmpurl)); + connect(decr, SIGNAL(done(int)), SLOT(slotDecryptDone(int))); + decr->start(); + return; + } + // remove only here, as KGpgDecrypt will use and remove the file itself + KIO::NetAccess::removeTempFile(m_tempfile); + m_tempfile.clear(); + + QString tmpinfo; + + switch (KGpgImport::isKey(result)) { + case 1: + tmpinfo = i18n("This file is a public key.
Do you want to import it instead of opening it in editor?
"); + break; + case 2: + tmpinfo = i18n("This file is a private key.
Do you want to import it instead of opening it in editor?
"); + break; + } + + if (!tmpinfo.isEmpty()) { + if (KMessageBox::questionYesNo(this, tmpinfo, i18n("Key file dropped on Editor")) != KMessageBox::Yes) { + setPlainText(result); + } else { + KGpgImport *imp = new KGpgImport(this, result); + connect(imp, SIGNAL(done(int)), m_keysmanager, SLOT(slotImportDone(int))); + imp->start(); + } + } else { + if (m_posstart != -1) { + Q_ASSERT(m_posend != -1); + QString fullcontent = toPlainText(); + fullcontent.replace(m_posstart, m_posend - m_posstart, result); + setPlainText(fullcontent); + m_posstart = -1; + m_posend = -1; + } else { + setPlainText(result); + } + } +} + +void KgpgTextEdit::slotEncode() +{ + QPointer dialog = new KgpgSelectPublicKeyDlg(this, m_model, m_keysmanager->goDefaultShortcut(), true); + if (dialog->exec() == KDialog::Accepted) { + QStringList options; + KGpgEncrypt::EncryptOptions opts = KGpgEncrypt::DefaultEncryption; + + if (dialog->getArmor()) + opts |= KGpgEncrypt::AsciiArmored; + + if (dialog->getUntrusted()) + opts |= KGpgEncrypt::AllowUntrustedEncryption; + + if (dialog->getHideId()) + opts |= KGpgEncrypt::HideKeyId; + + if (KGpgSettings::allowCustomEncryptionOptions()) { + const QString customoptions = dialog->getCustomOptions(); + if (!customoptions.isEmpty()) + options << customoptions.split(QLatin1Char(' '), QString::SkipEmptyParts); + } + + if (KGpgSettings::pgpCompatibility()) + options << QLatin1String( "--pgp6" ); + + QStringList listkeys; + if (!dialog->getSymmetric()) + listkeys = dialog->selectedKeys(); + + KGpgEncrypt *encr = new KGpgEncrypt(this, listkeys, toPlainText(), opts, options); + encr->start(); + connect(encr, SIGNAL(done(int)), SLOT(slotEncodeUpdate(int))); + } + delete dialog; +} + +void KgpgTextEdit::slotDecode() +{ + const QString fullcontent = toPlainText(); + + if (!KGpgDecrypt::isEncryptedText(fullcontent, &m_posstart, &m_posend)) + return; + + KGpgDecrypt *decr = new KGpgDecrypt(this, fullcontent.mid(m_posstart, m_posend - m_posstart)); + connect(decr, SIGNAL(done(int)), SLOT(slotDecryptDone(int))); + decr->start(); +} + +void KgpgTextEdit::slotSign(const QString &message) +{ + QString signkeyid; + + QPointer opts = new KgpgSelectSecretKey(this, m_model); + if (opts->exec() == QDialog::Accepted) + signkeyid = opts->getKeyID(); + else + { + delete opts; + return; + } + + delete opts; + + KGpgSignText *signt = new KGpgSignText(this, signkeyid, message); + connect(signt, SIGNAL(done(int)), SLOT(slotSignUpdate(int))); + signt->start(); +} + +void KgpgTextEdit::slotVerify(const QString &message) +{ + const QString startmsg(SIGNEDMESSAGE_BEGIN); + const QString endmsg(SIGNEDMESSAGE_END); + + int posstart = message.indexOf(startmsg); + if (posstart == -1) + return; + + int posend = message.indexOf(endmsg, posstart); + if (posend == -1) + return; + posend += endmsg.length(); + + KGpgVerify *verify = new KGpgVerify(this, message.mid(posstart, posend - posstart)); + connect(verify, SIGNAL(done(int)), SLOT(slotVerifyDone(int))); + verify->start(); +} + +void KgpgTextEdit::slotDecryptDone(int result) +{ + KGpgDecrypt *decr = qobject_cast(sender()); + Q_ASSERT(decr != NULL); + + if (!m_tempfile.isEmpty()) { + KIO::NetAccess::removeTempFile(m_tempfile); + m_tempfile.clear(); + } + + if (result == KGpgTransaction::TS_OK) { + // FIXME choose codec + setPlainText(decr->decryptedText().join(QLatin1String("\n")) + QLatin1Char('\n')); + } else if (result != KGpgTransaction::TS_USER_ABORTED) { + KMessageBox::detailedSorry(this, i18n("Decryption failed."), decr->getMessages().join( QLatin1String( "\n" ))); + } + + decr->deleteLater(); +} + +void KgpgTextEdit::slotEncodeUpdate(int result) +{ + KGpgEncrypt *enc = qobject_cast(sender()); + Q_ASSERT(enc != NULL); + + if (result == KGpgTransaction::TS_OK) { + const QString lf = QLatin1String("\n"); + setPlainText(enc->encryptedText().join(lf) + lf); + } else { + KMessageBox::sorry(this, i18n("The encryption failed with error code %1", result), + i18n("Encryption failed.")); + } + + sender()->deleteLater(); +} + +void KgpgTextEdit::slotSignUpdate(int result) +{ + const KGpgSignText * const signt = qobject_cast(sender()); + sender()->deleteLater(); + Q_ASSERT(signt != NULL); + + if (result != KGpgTransaction::TS_OK) { + KMessageBox::sorry(this, i18n("Signing not possible: bad passphrase or missing key")); + return; + } + + const QString content = signt->signedText().join(QLatin1String("\n")) + QLatin1String("\n"); + + setPlainText(content); + emit resetEncoding(false); +} + +void KgpgTextEdit::slotVerifyDone(int result) +{ + const KGpgVerify * const verify = qobject_cast(sender()); + sender()->deleteLater(); + Q_ASSERT(verify != NULL); + + emit verifyFinished(); + + if (result == KGpgVerify::TS_MISSING_KEY) { + verifyKeyNeeded(verify->missingId()); + return; + } + + const QStringList messages = verify->getMessages(); + + if (messages.isEmpty()) + return; + + QStringList msglist; + foreach (QString rawmsg, messages) // krazy:exclude=foreach + msglist << rawmsg.replace(QLatin1Char('<'), QLatin1String("<")); + + (void) new KgpgDetailedInfo(this, KGpgVerify::getReport(messages, m_model), + msglist.join(QLatin1String("
")), + QStringList(), i18nc("Caption of message box", "Verification Finished")); +} + +void KgpgTextEdit::verifyKeyNeeded(const QString &id) +{ + KGuiItem importitem = KStandardGuiItem::yes(); + importitem.setText(i18n("&Import")); + importitem.setToolTip(i18n("Import key in your list")); + + KGuiItem noimportitem = KStandardGuiItem::no(); + noimportitem.setText(i18n("Do &Not Import")); + noimportitem.setToolTip(i18n("Will not import this key in your list")); + + if (KMessageBox::questionYesNo(this, i18n("Missing signature:
Key id: %1

Do you want to import this key from a keyserver?
", id), i18n("Missing Key"), importitem, noimportitem) == KMessageBox::Yes) + { + KeyServer *kser = new KeyServer(0, m_model, true); + kser->slotSetText(id); + kser->slotImport(); + } +} + +void KgpgTextEdit::slotSignVerify() +{ + signVerifyText(toPlainText()); +} + +void KgpgTextEdit::signVerifyText(const QString &message) +{ + if (message.contains(SIGNEDMESSAGE_BEGIN)) + slotVerify(message); + else + slotSign(message); +} + +void KgpgTextEdit::slotHighlightText(const QString &, const int matchingindex, const int matchedlength) +{ + highlightWord(matchedlength, matchingindex); +} + +#include "kgpgtextedit.moc" diff --git a/kgpg/editor/kgpgtextedit.h b/kgpg/editor/kgpgtextedit.h new file mode 100644 index 00000000..afafa3b1 --- /dev/null +++ b/kgpg/editor/kgpgtextedit.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2009,2010,2011 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGTEXTEDIT_H +#define KGPGTEXTEDIT_H + +#include + +#include +#include + +class QDragEnterEvent; +class QDropEvent; + +class KGpgItemModel; +class KeysManager; + +class KgpgTextEdit : public KTextEdit +{ + Q_OBJECT + +public: + explicit KgpgTextEdit(QWidget *parent, KGpgItemModel *model, KeysManager *manager); + ~KgpgTextEdit(); + + void signVerifyText(const QString &message); + void openDroppedFile(const KUrl &url, const bool probe); + +signals: + void newText(); + void resetEncoding(bool); + void verifyFinished(); + +public slots: + void slotDroppedFile(const KUrl &url); + void slotEncode(); + void slotDecode(); + void slotSign(const QString &message); + void slotVerify(const QString &message); + void slotSignVerify(); + void slotHighlightText(const QString &, const int matchingindex, const int matchedlength); + void slotVerifyDone(int result); + +protected: + void dragEnterEvent(QDragEnterEvent *e); + void dropEvent(QDropEvent *e); + +private: + void verifyKeyNeeded(const QString &id); + +private slots: + void slotEncodeUpdate(int result); + void slotSignUpdate(int result); + + void slotDecryptDone(int result); + +private: + QString m_tempfile; + + int m_posstart; + int m_posend; + + KGpgItemModel *m_model; + KeysManager *m_keysmanager; +}; + +#endif // KGPGVIEW_H diff --git a/kgpg/encryptfile.desktop b/kgpg/encryptfile.desktop new file mode 100644 index 00000000..bee9d3dd --- /dev/null +++ b/kgpg/encryptfile.desktop @@ -0,0 +1,80 @@ +[Desktop Entry] +Type=Service +# all files inherit from application/octet-stream +MimeType=application/octet-stream; +X-KDE-ServiceTypes=KonqPopupMenu/Plugin +Actions=encrypt; + +[Desktop Action encrypt] +Name=Encrypt File +Name[ar]=تشفير ملف +Name[ast]=Encriptar ficheru +Name[bg]=Шифроване на файл +Name[bs]=Šifruj datoteku +Name[ca]=Encripta el fitxer +Name[ca@valencia]=Encripta el fitxer +Name[cs]=Zašifrovat soubor +Name[cy]=Cêl-ysgrifo Ffeil +Name[da]=Kryptér fil +Name[de]=Datei verschlüsseln +Name[el]=Κρυπτογράφηση αρχείου +Name[en_GB]=Encrypt File +Name[eo]=Ĉifri dosieron +Name[es]=Cifrar archivo +Name[et]=Krüpti fail +Name[eu]=Zifratu fitxategia +Name[fa]=رمزبندی پرونده +Name[fi]=Salaa tiedosto +Name[fr]=Chiffrer le fichier +Name[ga]=Criptigh Comhad +Name[gl]=Cifrar o ficheiro +Name[he]=הצפן קובץ +Name[hne]=फाइल एनक्रिप्ट करव +Name[hr]=Kriptiraj datoteku +Name[hu]=Fájl titkosítása +Name[ia]=Crypta file +Name[id]=Enkripsi Berkas +Name[is]=Dulrita skrá +Name[it]=Cifratura file +Name[ja]=ファイルを暗号化 +Name[kk]=Файлды шифрлау +Name[km]=អ៊ីនគ្រិប​ឯកសារ +Name[ko]=파일 암호화 +Name[lt]=Šifruoti failą +Name[lv]=Šifrēt failu +Name[mk]=Криптирај датотеки +Name[mr]=फाईल कुटलिपीबद्ध करा +Name[ms]=Encrypt File +Name[nb]=Krypter fil +Name[nds]=Dateiverslöteln +Name[ne]=फाइल गुप्तीकरण गर्नुहोस् +Name[nl]=Bestand versleutelen +Name[nn]=Krypter fil +Name[pa]=ਫਾਇਲ ਇੰਕ੍ਰਿਪਟ +Name[pl]=Zaszyfruj plik +Name[pt]=Cifrar o Ficheiro +Name[pt_BR]=Criptografar arquivo +Name[ro]=Criptează fișierul +Name[ru]=Зашифровать файл +Name[sk]=Zašifrovať súbor +Name[sl]=Šifriraj datoteko +Name[sq]=Kripto Skedarin +Name[sr]=Шифруј фајл +Name[sr@ijekavian]=Шифруј фајл +Name[sr@ijekavianlatin]=Šifruj fajl +Name[sr@latin]=Šifruj fajl +Name[sv]=Kryptera fil +Name[ta]=சங்கேத கோப்பு +Name[tg]=Рамзкунонии Файл +Name[th]=เข้ารหัสแฟ้ม +Name[tr]=Dosya Şifrele +Name[ug]=ھۆججەت شىفىرلاش +Name[uk]=Зашифрувати файл +Name[vi]=Mã Hóa Tập Tin +Name[wa]=Ecripter fitchî +Name[x-test]=xxEncrypt Filexx +Name[zh_CN]=加密文件 +Name[zh_TW]=加密檔案 +Icon=kgpg +Exec=kgpg -e %F + diff --git a/kgpg/encryptfolder.desktop b/kgpg/encryptfolder.desktop new file mode 100644 index 00000000..36493acd --- /dev/null +++ b/kgpg/encryptfolder.desktop @@ -0,0 +1,72 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=inode/directory,KonqPopupMenu/Plugin +Actions=encrypt; + +[Desktop Action encrypt] +Name=Archive & Encrypt Folder +Name[ar]=أرشفة و تشفير مجلّد +Name[ast]=Ficheru y Directoriu Encriptáu +Name[bg]=Архивиране и шифроване на директория +Name[bs]=Arhiviraj i šifruj direktorij +Name[ca]=Arxiva i encripta la carpeta +Name[ca@valencia]=Arxiva i encripta la carpeta +Name[cs]=Archivovat a zašifrovat složku +Name[da]=Arkivér og kryptér mappe +Name[de]=Ordner packen und verschlüsseln +Name[el]=Αρχειοθέτηση & κρυπτογράφηση φακέλου +Name[en_GB]=Archive & Encrypt Folder +Name[eo]=Arkivigi kaj ĉifri dosierujon +Name[es]=Archivar y cifrar carpeta +Name[et]=Arhiveeri ja krüpti kataloog +Name[eu]=Artxibatu eta zifratu karpeta +Name[fa]=بایگانی و رمزبندی پوشه +Name[fi]=Pakkaa ja salaa kansio +Name[fr]=Archiver puis chiffrer le dossier +Name[ga]=Cuir Fillteán i gCartlann agus Criptigh É +Name[gl]=Arquivar e cifrar o cartafol +Name[he]=כלי להצפנה וכיווץ תקייה +Name[hne]=फोल्डर ल एनक्रिप्ट अउ अभिलेखित करव +Name[hr]=Arhiviraj i kriptiraj mapu +Name[hu]=Könyvtár titkosítása és archiválása +Name[ia]=Dossier de archivar & cryptar +Name[id]=Pengarsipan & Enkripsi Folder +Name[is]=Pakka og dulrita möppu +Name[it]=Archivia e cifra cartella +Name[ja]=フォルダをアーカイブして暗号化 +Name[kk]=Қапшықты архивтеп шифрлау +Name[km]=ប័ណ្ណសារ និង​អ៊ីនគ្រិប​ថត +Name[ko]=파일 및 폴더 암호화 +Name[lt]=Archyvuoti ir šifruoti aplanką +Name[lv]=Arhīvēt un šifrēt mapi +Name[mr]=संचयीका संग्रह व कुटलिपीबद्ध करा +Name[nb]=Arkiver og krypter mappe +Name[nds]=Ornern komprimeren un verslöteln +Name[ne]=फोल्डरलाई सङ्ग्रह र गुप्तीकरण गर्नुहोस् +Name[nl]=Map archiveren en versleutelen +Name[nn]=Arkiver og krypter mappe +Name[pa]=ਫੋਲਡਰ ਅਕਾਇਵ ਤੇ ਇੰਕ੍ਰਿਪਟ +Name[pl]=Zarchiwizuj i zaszyfruj katalog +Name[pt]=Arquivar & Cifrar a Pasta +Name[pt_BR]=Arquivar e criptografar a pasta +Name[ro]=Arhivează și criptează dosarul +Name[ru]=Архивировать и зашифровать папку +Name[sk]=Archivovať a zašifrovať priečinok +Name[sl]=Arhiviraj in ši&friraj mapo +Name[sq]=Arkivo & Kripto Dosjen +Name[sr]=Архивирај и шифруј фасциклу +Name[sr@ijekavian]=Архивирај и шифруј фасциклу +Name[sr@ijekavianlatin]=Arhiviraj i šifruj fasciklu +Name[sr@latin]=Arhiviraj i šifruj fasciklu +Name[sv]=Arkivera och kryptera katalog +Name[ta]=காப்பகம் மாற்றும் மறையாக்க அடைவு +Name[th]=ทำแฟ้มจัดเก็บและเข้ารหัสโฟลเดอร์ +Name[tr]=Dizini Arşivle ve Şifrele +Name[ug]=ئارخىپلاش ۋە قىسقۇچ شىفىرلاش +Name[uk]=Зробити архів теки і зашифрувати +Name[vi]=Đóng Gói & Mã Hóa Thư Mục +Name[x-test]=xxArchive & Encrypt Folderxx +Name[zh_CN]=存档并加密文件夹 +Name[zh_TW]=壓縮並加密資料夾 +Icon=kgpg +Exec=kgpg -e %F diff --git a/kgpg/foldercompressjob.cpp b/kgpg/foldercompressjob.cpp new file mode 100644 index 00000000..59d9dfec --- /dev/null +++ b/kgpg/foldercompressjob.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2011,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "foldercompressjob.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class FolderCompressJobPrivate { + FolderCompressJob * const q_ptr; + Q_DECLARE_PUBLIC(FolderCompressJob) + +public: + FolderCompressJobPrivate(FolderCompressJob *parent, const KUrl::List &sources, const KUrl &dest, KTemporaryFile *tempfile, const QStringList &keys, const QStringList &options, const KGpgEncrypt::EncryptOptions encOptions, const int archive); + + const QString m_description; + const KUrl::List m_sources; + const KUrl m_dest; + KTemporaryFile * const m_tempfile; + const QStringList m_keys; + QStringList m_options; + const KGpgEncrypt::EncryptOptions m_encOptions; + const int m_archiveType; +}; + +FolderCompressJobPrivate::FolderCompressJobPrivate(FolderCompressJob *parent, const KUrl::List &sources, const KUrl &dest, KTemporaryFile *tempfile, const QStringList &keys, const QStringList &options, const KGpgEncrypt::EncryptOptions encOptions, const int archive) + : q_ptr(parent), + m_description(i18n("Processing folder compression and encryption")), + m_sources(sources), + m_dest(dest), + m_tempfile(tempfile), + m_keys(keys), + m_options(options), + m_encOptions(encOptions), + m_archiveType(archive) +{ +} + +FolderCompressJob::FolderCompressJob(QObject *parent, const KUrl::List &sources, const KUrl &dest, KTemporaryFile *tempfile, const QStringList &keys, const QStringList &options, const KGpgEncrypt::EncryptOptions encOptions, const int archive) + : KJob(parent), + d_ptr(new FolderCompressJobPrivate(this, sources, dest, tempfile, keys, options, encOptions, archive)) +{ +} + +FolderCompressJob::~FolderCompressJob() +{ + delete d_ptr; +} + +void +FolderCompressJob::start() +{ + Q_D(FolderCompressJob); + + emit description(this, d->m_description, qMakePair(i18nc("State of operation as in status", "State"), i18nc("Job is started up", "Startup"))); + QMetaObject::invokeMethod(this, "doWork", Qt::QueuedConnection); +} + +void +FolderCompressJob::doWork() +{ + Q_D(FolderCompressJob); + KArchive *arch = NULL; + + switch (d->m_archiveType) { + case 0: + arch = new KZip(d->m_tempfile->fileName()); + break; + case 1: + arch = new KTar(d->m_tempfile->fileName(), QLatin1String( "application/x-gzip" )); + break; + case 2: + arch = new KTar(d->m_tempfile->fileName(), QLatin1String( "application/x-bzip" )); + break; + case 3: + arch = new KTar(d->m_tempfile->fileName(), QLatin1String( "application/x-tar" )); + break; + case 4: + arch = new KTar(d->m_tempfile->fileName(), QLatin1String( "application/x-xz" )); + break; + default: + Q_ASSERT(0); + return; + } + + if (!arch->open(QIODevice::WriteOnly)) { + setError(UserDefinedError); + setErrorText(i18n("Unable to create temporary file")); + delete arch; + emitResult(); + return; + } + + foreach (const KUrl &url, d->m_sources) + arch->addLocalDirectory(url.path(), url.fileName()); + arch->close(); + delete arch; + + setPercent(50); + + QDir outPath = d->m_sources.first().path(); + outPath.cdUp(); + + d->m_options << QLatin1String("--output") << QDir::toNativeSeparators(outPath.path() + QDir::separator()) + d->m_dest.fileName(); + + emit description(this, d->m_description, qMakePair(i18nc("State of operation as in status", "State"), + i18nc("Status message 'Encrypting ' (operation starts)", "Encrypting %1", d->m_dest.path()))); + + + KGpgEncrypt *enc = new KGpgEncrypt(this, d->m_keys, KUrl::List(KUrl::fromPath(d->m_tempfile->fileName())), d->m_encOptions, d->m_options); + connect(enc, SIGNAL(done(int)), SLOT(slotEncryptionDone(int))); + enc->start(); +} + +void +FolderCompressJob::slotEncryptionDone(int result) +{ + Q_D(FolderCompressJob); + + sender()->deleteLater(); + + if ((result != KGpgTransaction::TS_OK) && (result != KGpgTransaction::TS_USER_ABORTED)) { + setError(KJob::UserDefinedError + 1); + setErrorText(i18n("The encryption failed with error code %1", result)); + emit description(this, d->m_description, qMakePair(i18nc("State of operation as in status", "State"), i18n("Encryption failed."))); + } else { + emit description(this, d->m_description, qMakePair(i18nc("State of operation as in status", "State"), + i18nc("Status message 'Encrypted ' (operation was completed)", "Encrypted %1", d->m_dest.path()))); + } + + emitResult(); +} + +QString +FolderCompressJob::extensionForArchive(const int archive) +{ + switch (archive) { + case 0: + return QLatin1String(".zip"); + case 1: + return QLatin1String(".tar.gz"); + case 2: + return QLatin1String(".tar.bz2"); + case 3: + return QLatin1String(".tar"); + case 4: + return QLatin1String(".tar.xz"); + default: + Q_ASSERT(archive <= archiveNames().count()); + Q_ASSERT(archive >= 0); + return QString(); + } +} + +const QStringList & +FolderCompressJob::archiveNames() +{ + static const QStringList archives = + QStringList(i18n("Zip")) << + i18n("Tar/Gzip") << + i18n("Tar/Bzip2") << + i18n("Tar") << + i18n("Tar/XZ"); + + return archives; +} + +#include "foldercompressjob.moc" diff --git a/kgpg/foldercompressjob.h b/kgpg/foldercompressjob.h new file mode 100644 index 00000000..18106e71 --- /dev/null +++ b/kgpg/foldercompressjob.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2011,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef FOLDERCOMPRESSJOB_H +#define FOLDERCOMPRESSJOB_H + +#include +#include + +class KTemporaryFile; +class QString; +class QStringList; + +#include "transactions/kgpgencrypt.h" + +class FolderCompressJobPrivate; + +/** + * @brief Create an encrypted archive of the given folders + * + * @author Rolf Eike Beer + */ +class FolderCompressJob : public KJob { + Q_OBJECT + + Q_DISABLE_COPY(FolderCompressJob) + FolderCompressJob(); // = delete C++0x + + FolderCompressJobPrivate * const d_ptr; + Q_DECLARE_PRIVATE(FolderCompressJob) + +public: + /** + * @brief create a new KJob to compress and encrypt a folder + * @param parent object owning this job + * @param sources the source directories to include + * @param dest the name of the encrypted file + * @param tempfile the temporary file that should be used for archiving + * @param keys the public key ids to encrypt to + * @param options special options to pass to the GnuPG process + * @param encOptions special options to pass to the GnuPG process + * @param archive the archive type to use + */ + FolderCompressJob(QObject *parent, const KUrl::List &sources, const KUrl &dest, KTemporaryFile *tempfile, const QStringList &keys, const QStringList &options, const KGpgEncrypt::EncryptOptions encOptions, const int archive); + + /** + * @brief FolderCompressJob destructor + */ + virtual ~FolderCompressJob(); + + /** + * @brief shows the progress indicator + */ + virtual void start(); + + /** + * @brief query extension for archive type + * @param archive the archive type + * @return the extension including leading dot + */ + static QString extensionForArchive(const int archive); + + /** + * @brief get list of supported archive names + * @return list of archive names + */ + static const QStringList &archiveNames(); + +private slots: + void doWork(); + void slotEncryptionDone(int result); +}; + +#endif /* FOLDERCOMPRESSJOB_H */ diff --git a/kgpg/gpgproc.cpp b/kgpg/gpgproc.cpp new file mode 100644 index 00000000..06d12c95 --- /dev/null +++ b/kgpg/gpgproc.cpp @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2007,2008,2009,2010,2011,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "gpgproc.h" + +#include "kgpgsettings.h" + +#include +#include +#include +#include +#include +#include + +class GnupgBinary { +public: + GnupgBinary(); + + const QString &binary() const; + void setBinary(const QString &executable); + const QStringList &standardArguments() const; + unsigned int version() const; + bool supportsDebugLevel() const; + +private: + QString m_binary; + QStringList m_standardArguments; + unsigned int m_version; + bool m_useDebugLevel; +}; + +GnupgBinary::GnupgBinary() + : m_version(0), + m_useDebugLevel(false) +{ +} + +const QString &GnupgBinary::binary() const +{ + return m_binary; +} + +/** + * @brief check if GnuPG returns an error for this arguments + * @param executable the GnuPG executable to call + * @param arguments the arguments to pass to executable + * + * The arguments will be used together with "--version", so they should not + * be any commands. + */ +static bool checkGnupgArguments(const QString &executable, const QStringList &arguments) +{ + KProcess gpg; + + // We ignore the output anyway, just make sure it doesn't clutter the output of + // the parent process. Simplify the handling by putting all trash in one can. + gpg.setOutputChannelMode(KProcess::MergedChannels); + + QStringList allArguments = arguments; + allArguments << QLatin1String("--version"); + gpg.setProgram(executable, allArguments); + + return (gpg.execute() == 0); +} + +static QString getGpgProcessHome(const QString &binary) +{ + GPGProc process(0, binary); + process << QLatin1String( "--version" ); + process.start(); + process.waitForFinished(-1); + + if (process.exitCode() == 255) { + return QString(); + } + + QString line; + while (process.readln(line) != -1) { + if (line.startsWith(QLatin1String("Home: "))) { + line.remove(0, 6); + return line.trimmed(); + } + } + + return QString(); +} + + +void GnupgBinary::setBinary(const QString &executable) +{ + kDebug(2100) << "checking version of GnuPG executable" << executable; + // must be set first as gpgVersionString() uses GPGProc to parse the output + m_binary = executable; + const QString verstr = GPGProc::gpgVersionString(executable); + m_version = GPGProc::gpgVersion(verstr); + kDebug(2100) << "version is" << verstr << m_version; + + m_useDebugLevel = (m_version > 0x20000); + + const QString gpgConfigFile = KGpgSettings::gpgConfigPath(); + + m_standardArguments.clear(); + m_standardArguments << QLatin1String( "--no-secmem-warning" ) + << QLatin1String( "--no-tty" ) + << QLatin1String("--no-greeting"); + + if (!gpgConfigFile.isEmpty()) { + m_standardArguments << QLatin1String("--options") + << gpgConfigFile; + + // Check if the config file is in the default home directory + // of the binary. If it isn't add --homedir to command line also. + QString gpgdir = GPGProc::getGpgHome(executable); + gpgdir.chop(1); // remove trailing '/' as QFileInfo returns string without it + QFileInfo confFile(gpgConfigFile); + if (confFile.absolutePath() != gpgdir) + m_standardArguments << QLatin1String("--homedir") + << confFile.absolutePath(); + } + + QStringList debugLevelArguments(QLatin1String("--debug-level")); + debugLevelArguments << QLatin1String("none"); + if (checkGnupgArguments(executable, debugLevelArguments)) + m_standardArguments << debugLevelArguments; +} + +const QStringList& GnupgBinary::standardArguments() const +{ + return m_standardArguments; +} + +unsigned int GnupgBinary::version() const +{ + return m_version; +} + +bool GnupgBinary::supportsDebugLevel() const +{ + return m_useDebugLevel; +} + +K_GLOBAL_STATIC(GnupgBinary, lastBinary) + +GPGProc::GPGProc(QObject *parent, const QString &binary) + : KLineBufferedProcess(parent) +{ + resetProcess(binary); +} + +GPGProc::~GPGProc() +{ +} + +void +GPGProc::resetProcess(const QString &binary) +{ + GnupgBinary *bin = lastBinary; + QString executable; + if (binary.isEmpty()) + executable = KGpgSettings::gpgBinaryPath(); + else + executable = binary; + + if (bin->binary() != executable) + bin->setBinary(executable); + + setProgram(executable, bin->standardArguments()); + + setOutputChannelMode(OnlyStdoutChannel); + + disconnect(SIGNAL(finished(int,QProcess::ExitStatus))); + disconnect(SIGNAL(lineReadyStandardOutput())); +} + +void GPGProc::start() +{ + // make sure there is exactly one connection from us to that signal + connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(finished()), Qt::UniqueConnection); + connect(this, SIGNAL(lineReadyStandardOutput()), this, SLOT(received()), Qt::UniqueConnection); + KProcess::start(); +} + +void GPGProc::received() +{ + emit readReady(); +} + +void GPGProc::finished() +{ + emit processExited(); +} + +int GPGProc::readln(QString &line, const bool colons) +{ + QByteArray a; + if (!readLineStandardOutput(&a)) + return -1; + + line = recode(a, colons, m_codec); + + return line.length(); +} + +int GPGProc::readln(QStringList &l) +{ + QString s; + + int len = readln(s); + if (len < 0) + return len; + + l = s.split(QLatin1Char( ':' )); + + for (int i = 0; i < l.count(); ++i) + { + int j = 0; + while ((j = l[i].indexOf(QLatin1String( "\\x3a" ), j, Qt::CaseInsensitive)) >= 0) + { + l[i].replace(j, 4, QLatin1Char( ':' )); + j++; + } + } + + return l.count(); +} + +QString +GPGProc::recode(QByteArray a, const bool colons, const QByteArray &codec) +{ + const char *textcodec = codec.isEmpty() ? "utf8" : codec.constData(); + int pos = 0; + + while ((pos = a.indexOf("\\x", pos)) >= 0) { + if (pos > a.length() - 4) + break; + + const QByteArray pattern(a.mid(pos, 4)); + const QByteArray hexnum(pattern.right(2)); + bool ok; + char n[2]; + n[0] = hexnum.toUShort(&ok, 16); + n[1] = '\0'; // to use n as a 0-terminated string + if (!ok) { + // skip this occurrence + pos += 2; + continue; + } + + // QLatin1Char( ':' ) must be skipped, it is used as column delimiter + // since it is pure ascii it can be replaced in QString. + if (!colons && (n[0] == ':' )) { + pos += 3; + continue; + } + + // it is likely to find the same byte sequence more than once + int npos = pos; + do { + a.replace(npos, 4, n); + } while ((npos = a.indexOf(pattern, npos)) >= 0); + } + + return QTextCodec::codecForName(textcodec)->toUnicode(a); +} + +bool +GPGProc::setCodec(const QByteArray &codec) +{ + const QList codecs = QTextCodec::availableCodecs(); + if (!codecs.contains(codec)) + return false; + + m_codec = codec; + + return true; +} + +int GPGProc::gpgVersion(const QString &vstr) +{ + if (vstr.isEmpty()) + return -1; + + QStringList values(vstr.split(QLatin1Char( '.' ))); + if (values.count() < 3) + return -2; + + return (0x10000 * values[0].toInt() + 0x100 * values[1].toInt() + values[2].toInt()); +} + +QString GPGProc::gpgVersionString(const QString &binary) +{ + GPGProc process(0, binary); + process << QLatin1String( "--version" ); + process.start(); + process.waitForFinished(-1); + + if (process.exitCode() == 255) + return QString(); + + QString line; + if (process.readln(line) != -1) + return line.simplified().section(QLatin1Char( ' ' ), -1); + else + return QString(); +} + +QString GPGProc::getGpgStartupError(const QString &binary) +{ + GPGProc process(0, binary); + process << QLatin1String( "--version" ); + process.start(); + process.waitForFinished(-1); + + QString result; + + while (process.hasLineStandardError()) { + QByteArray tmp; + process.readLineStandardError(&tmp); + tmp += '\n'; + result += QString::fromUtf8(tmp); + } + + return result; +} + +QString GPGProc::getGpgHome(const QString &binary) +{ + // First try: if environment is set GnuPG will use that directory + // We can use this directly without starting a new process + QByteArray env(qgetenv("GNUPGHOME")); + QString gpgHome; + if (!env.isEmpty()) { + gpgHome = QLatin1String( env ); + } else if (!binary.isEmpty()) { + // Second try: start GnuPG and ask what it is + gpgHome = getGpgProcessHome(binary); + } + + // Third try: guess what it is. + if (gpgHome.isEmpty()) { +#ifdef Q_OS_WIN32 //krazy:exclude=cpp + gpgHome = qgetenv("APPDATA") + QLatin1String( "/gnupg/" ); + gpgHome.replace(QLatin1Char( '\\' ), QLatin1Char( '/' )); +#else + gpgHome = QDir::homePath() + QLatin1String( "/.gnupg/" ); +#endif + } + + gpgHome.replace(QLatin1String( "//" ), QLatin1String( "/" )); + + if (!gpgHome.endsWith(QLatin1Char( '/' ))) + gpgHome.append(QLatin1Char( '/' )); + + if (gpgHome.startsWith(QLatin1String("~/"))) + gpgHome.replace(0, 1, QDir::homePath()); + + KStandardDirs::makeDir(gpgHome, 0700); + return gpgHome; +} + +#include "gpgproc.moc" diff --git a/kgpg/gpgproc.h b/kgpg/gpgproc.h new file mode 100644 index 00000000..afa08fd1 --- /dev/null +++ b/kgpg/gpgproc.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2007 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef GPGPROC_H +#define GPGPROC_H + +#include +#include + +#include "klinebufferedprocess.h" + +/** + * @brief A interface to GnuPG handling UTF8 recoding correctly + * + * This class handles the GnuPG formatted UTF8 output correctly. + * GnuPG recodes some characters as \\xnn where nn is the hex representation + * of the character. This can't be fixed up simply when using QString as + * QString already did it's own UTF8 conversion. Therefore we replace this + * sequences by their corresponding character so QString will work just fine. + * + * As we know that GnuPG limits it's columns by QLatin1Char( ':' ) we skip \\x3a. Since this + * is an ascii character (single byte) the replacement can be done later without + * problems after the line has been split into pieces. + * + * @author Rolf Eike Beer + */ +class GPGProc : public KLineBufferedProcess +{ + Q_OBJECT + +public: + /** + * Constructor + * @param parent parent object + * @param binary path to GnuPG binary or QString() to use the configured + */ + explicit GPGProc(QObject *parent = 0, const QString &binary = QString()); + + /** + * Destructor + */ + ~GPGProc(); + + /** + * Starts the process + */ + void start(); + + /** + * Reads a line of text (excluding '\\n'). + * + * Use readln() in response to a readReady() signal. + * You may use it multiple times if more than one line of data is + * available. + * + * readln() never blocks. + * + * @param line is used to store the line that was read. + * @param colons recode also colons + * @return the number of characters read, or -1 if no data is available. + */ + int readln(QString &line, const bool colons = false); + + /** + * Reads a line of text and splits it into parts. + * + * Use readln() in response to a readReady() signal. + * You may use it multiple times if more than one line of data is + * available. + * + * readln() never blocks. + * + * @param l is used to store the parts of the line that was read. + * @return the number of characters read, or -1 if no data is available. + */ + int readln(QStringList &l); + + /** + * Recode a line from GnuPG encoding to UTF8 + * + * @param a data to recode + * @param colons recode also colons + * @return recoded string + */ + static QString recode(QByteArray a, const bool colons = true, const QByteArray &codec = QByteArray()); + + /** + * @brief sets the codec used to translate the incoming data + * @param codec the name of the new codec + * @return if the new codec has been accepted + * + * The default codec is utf8. If the given codec is not known to + * QTextCodec the method will return false. + */ + bool setCodec(const QByteArray &codec); + + /** + * Reset the class to the state it had right after creation + * @param binary path to GnuPG binary or empty string to use the configured one + */ + void resetProcess(const QString &binary = QString()); + + /** + * @brief parse GnuPG version string and return version as number + * @param vstr version string + * @return -1 if vstr is empty, -2 on parse error, parsed number on success + * + * The version string must be in format A.B.C with A, B, and C numbers. The + * returned number is A * 65536 + B * 256 + C. + */ + static int gpgVersion(const QString &vstr); + /** + * @brief get the GnuPG version string of the given binary + * @param binary name or path to GnuPG binary + * @return version string or empty string on error + * + * This starts a GnuPG process and asks the binary for version information. + * The returned string is the version information without any leading text. + */ + static QString gpgVersionString(const QString &binary); + /** + * @brief find users GnuPG directory + * @param binary name or path to GnuPG binary + * @return path to directory + * + * Use this function to find out where GnuPG would store it's configuration + * and data files. The returned path always ends with a '/'. + */ + static QString getGpgHome(const QString &binary); + + /** + * @brief run GnuPG and check if it complains about anything + * @param binary the GnuPG binary to run + * @return the error message GnuPG gave out (if any) + */ + static QString getGpgStartupError(const QString &binary); +signals: + /** + * Emitted when the process is ready for reading. + * The signal is only emitted if at least one complete line of data is ready. + * @param p the process that emitted the signal + */ + void readReady(); + + /** + * Emitted when the process has finished + * @param p the process that emitted the signal + */ + void processExited(); + +protected slots: + void finished(); + void received(); + +private: + QByteArray m_codec; +}; + +#endif // GPGPROC_H diff --git a/kgpg/groupedit.cpp b/kgpg/groupedit.cpp new file mode 100644 index 00000000..2bd9ab49 --- /dev/null +++ b/kgpg/groupedit.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "groupedit.h" + +#include "kgpgsettings.h" +#include "core/kgpgkey.h" +#include "model/groupeditproxymodel.h" +#include "model/kgpgitemmodel.h" + +#include +#include +#include +#include + +groupEdit::groupEdit(QWidget *parent, QList *ids, KGpgItemModel *md) + : QWidget(parent), + m_outFilter(new QSortFilterProxyModel(this)), + members(ids) +{ + Q_ASSERT(ids != NULL); + Q_ASSERT(md != NULL); + + setupUi( this ); + KgpgCore::KgpgKeyTrust mintrust; + if (KGpgSettings::allowUntrustedGroupMembers()) { + mintrust = KgpgCore::TRUST_UNDEFINED; + textLabelAvailable->setText(i18n("Available Keys")); + } else { + mintrust = KgpgCore::TRUST_FULL; + textLabelAvailable->setText(i18n("Available Trusted Keys")); + } + + m_in = new GroupEditProxyModel(this, false, members, KgpgCore::TRUST_MINIMUM); + m_in->setKeyModel(md); + m_out = new GroupEditProxyModel(this, true, members, mintrust); + m_out->setKeyModel(md); + + m_outFilter->setSourceModel(m_out); + m_outFilter->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_outFilter->setFilterKeyColumn(-1); + + connect(filterEdit, SIGNAL(textChanged(QString)), m_outFilter, SLOT(setFilterFixedString(QString))); + + availableKeys->setModel(m_outFilter); + groupKeys->setModel(m_in); + buttonAdd->setIcon(KIcon( QLatin1String( "go-down" ))); + buttonRemove->setIcon(KIcon( QLatin1String( "go-up" ))); + + availableKeys->setColumnWidth(0, 200); + availableKeys->setColumnWidth(1, 200); + availableKeys->setColumnWidth(2, 100); + availableKeys->verticalHeader()->hide(); + + groupKeys->setColumnWidth(0, 200); + groupKeys->setColumnWidth(1, 200); + groupKeys->setColumnWidth(2, 100); + groupKeys->verticalHeader()->hide(); + + setMinimumSize(sizeHint()); + + connect(buttonAdd, SIGNAL(clicked()), this, SLOT(groupAdd())); + connect(buttonRemove, SIGNAL(clicked()), this, SLOT(groupRemove())); + connect(availableKeys, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(groupAdd(QModelIndex))); + connect(groupKeys, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(groupRemove(QModelIndex))); +} + +groupEdit::~groupEdit() +{ + delete m_in; + delete m_out; +} + +void +groupEdit::groupAdd() +{ + QModelIndexList sel = availableKeys->selectionModel()->selectedIndexes(); + for (int i = 0; i < sel.count(); i++) { + if (sel.at(i).column() != 0) + continue; + KGpgNode *nd = m_out->nodeForIndex(m_outFilter->mapToSource(sel.at(i))); + members->append(nd); + } + m_in->invalidate(); + m_out->invalidate(); +} + +void +groupEdit::groupRemove() +{ + Q_ASSERT(!members->isEmpty()); + QModelIndexList sel = groupKeys->selectionModel()->selectedIndexes(); + for (int i = 0; i < sel.count(); i++) { + if (sel.at(i).column() != 0) + continue; + KGpgNode *nd = m_in->nodeForIndex(sel.at(i)); + for (int j = 0; j < members->count(); j++) + if (nd->toKeyNode()->compareId(members->at(j)->getId())) { + members->removeAt(j); + break; + } + } + m_in->invalidate(); + m_out->invalidate(); +} + +void +groupEdit::groupAdd(const QModelIndex &index) +{ + KGpgNode *nd = m_out->nodeForIndex(m_outFilter->mapToSource(index)); + members->append(nd); + m_in->invalidate(); + m_out->invalidate(); +} + +void +groupEdit::groupRemove(const QModelIndex &index) +{ + Q_ASSERT(!members->isEmpty()); + KGpgKeyNode *nd = m_in->nodeForIndex(index)->toKeyNode(); + for (int i = 0; i < members->count(); i++) + if (nd->compareId(members->at(i)->getId())) { + members->removeAt(i); + break; + } + m_in->invalidate(); + m_out->invalidate(); +} + +#include "groupedit.moc" diff --git a/kgpg/groupedit.h b/kgpg/groupedit.h new file mode 100644 index 00000000..a3592842 --- /dev/null +++ b/kgpg/groupedit.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef GROUPEDIT_H +#define GROUPEDIT_H + +#include +#include "ui_groupedit.h" + +class GroupEditProxyModel; +class KGpgNode; +class KGpgItemModel; +class QSortFilterProxyModel; + +/** + * @brief shows a widget that let's you change the keys that are part of a key group + */ +class groupEdit : public QWidget, public Ui::groupEdit +{ + Q_OBJECT + +private: + GroupEditProxyModel *m_in; + GroupEditProxyModel *m_out; + QSortFilterProxyModel * const m_outFilter; + +public: + QList * const members; ///< the list of keys that are members of the group + + /** + * @brief constructor + * @param parent parent widget + * @param ids the members of the group + * @param md model to use + */ + explicit groupEdit(QWidget *parent, QList *ids, KGpgItemModel *md); + /** + * @brief destructor + */ + ~groupEdit(); + +private Q_SLOTS: + /** + * @brief called when the add button is clicked + */ + void groupAdd(); + /** + * @brief called when the remove button is clicked + */ + void groupRemove(); + /** + * @brief called when an available key is double clicked + */ + void groupAdd(const QModelIndex &index); + /** + * @brief clicked when a group member key is double clicked + */ + void groupRemove(const QModelIndex &index); +}; + +#endif diff --git a/kgpg/groupedit.ui b/kgpg/groupedit.ui new file mode 100644 index 00000000..a0d7b7de --- /dev/null +++ b/kgpg/groupedit.ui @@ -0,0 +1,184 @@ + + groupEdit + + + + 0 + 0 + 583 + 456 + + + + + + + + 75 + true + + + + + 0 + 0 + + + + false + + + + + + + + + + Search: + + + + + + + true + + + + + + + + + + true + + + true + + + false + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + Name + + + + + Email + + + + + Id + + + + + + + + + + + 75 + true + + + + Keys in the Group + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 71 + 20 + + + + + + + + + + true + + + true + + + false + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + Name + + + + + Email + + + + + Id + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + +
diff --git a/kgpg/hi16-app-kgpg.png b/kgpg/hi16-app-kgpg.png new file mode 100644 index 0000000000000000000000000000000000000000..08528c865605d8a8dcd54334d8ea59d577166abd GIT binary patch literal 1006 zcmV>9V3WFU8GbZ8({Xk{QrNlj4iWF>9@00UA~dnYqt-+g{2^FLwU1&>%f*^L|LTf>=g4C6}s;E$0 zh~TmdSC*g^YDBb_RAWtJI?Xg`lgvzJZtnZ(AyP0<{K22II=}xp-^alJaOTVfTrB1? zvYaaOJS%e?>%=j%blvKnI&~xj%0mUmj-7*SHksVDtGd5lFCSOc+=M9dg)odP%W^OG z`^FbkawDJ5xC9tGC#rq( z`;b^TKxoY)YqPBwE3dnP8Gk1tN&V_DQvlx*z#9!Celkhh*`KswFK#Ubfr zd8hxhLuEQ}hTcDvz7@p)^=E8#zHjwCqXxm0rKE|3nD3h$x`lWtDloF|?`i zp#iXo0K$kS1|}Q;&-)Ci*wvU6-W=2K`kt?{ORMqMou2gr-HK15FgPG_?xQKzK#b=D z1Po31K(civ1Rrw4uDFMdp*{a&7ufvl*xvEgL(jb!6Vvd1`D@<2lEJ~m=+1!S5&+-? z|Hh*tpQQ-_3jxS^2biiXiFf)M!bAW7n;1qkx7dzP8zy_Y+htzbS9R2!6e7j~0Nn*( z2|yA95Cp!r2A#EBziVqPQPCZs;F(r0K3K{2J-+dKrxy$?GboAJFVY}Ho52nMOb;U6 zgT*^Z>*lh0DdK9MH=a7V{^3WT0S3xUs}Ikgc-Ju6e_UVh2MdN3=jrN()7U48h)RM8 z)&?ZL+OGV%5mi1&6^7>y961pK1=DPCdHNDuxw#Y^eq#UaPRE+nZ9+UZuBWgk#C{Aj z%cWl1)7~l69{K)-H{K6`@<6bS>Fc-SiG3sMrpsLG=&;c<6QeCX!`}@m7Xy<0^wsm9 cxbPqT0m`e48{;;7rT_o{07*qoM6N<$f-6_e@Bjb+ literal 0 HcmV?d00001 diff --git a/kgpg/hi22-app-kgpg.png b/kgpg/hi22-app-kgpg.png new file mode 100644 index 0000000000000000000000000000000000000000..774e01c910eef2814e7a170301b8fa862cb47e02 GIT binary patch literal 1245 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 literal 0 HcmV?d00001 diff --git a/kgpg/icons/CMakeLists.txt b/kgpg/icons/CMakeLists.txt new file mode 100644 index 00000000..492d3424 --- /dev/null +++ b/kgpg/icons/CMakeLists.txt @@ -0,0 +1 @@ +kde4_install_icons( ${DATA_INSTALL_DIR}/kgpg/icons ) diff --git a/kgpg/icons/hi16-actions-document-export-key.png b/kgpg/icons/hi16-actions-document-export-key.png new file mode 100644 index 0000000000000000000000000000000000000000..15d2c3a8a48ce7f8e7ed8fb0c4a27a6235e48b4b GIT binary patch literal 736 zcmV<60w4W}P)Lf*@oq7Ryb{hp={IWo4OeZ|}sh zb}k$aS3Mq&5R1h|5{U$Lxm2y9E7#ILi6k+Sf7TE3f z5;w@51Oj4PX!GdQfw3fsQoT~C+$T#*OFaJ7*6DPBWm%b&QeYSclF1~POs3u%WvLv? zq%06Zc-m^UQn_5tR;^Y6djM9ewYyoS!{LB-yDf_${|GA>Ao!s}DU->(rKM8oHQF3E z7z|8z!y&MV2^3$$H3cXr`)+gS%w}FaECNwnIMpmnj_w4#V^;$5Ed+zXNweAP!=sE~ zzFRC7KjriJS5s3{DKeL5Q7?-0Dp>+32M0|F5bMbC|6Zcs?{{Uh*}Xs@u;07qneX#B zy(9!+@qr0A6$2A*3eXYdvhT>JOr{gdI9+=_VT8%626RB+WBdS~Ebam#PT?qsHO_5Q zg3xY)@5442G%O4%85ldwz~|&IT|WX}H?lSNMs^>j$1DJn2rp3RFV_1Is6WH;Hqw~N zl|jWaT~R>%L@xAtKnUrIDgWUKBt;S!y)z7FZ_NX%x{SF3U93SzcqF~QoB9p3GxA7{ SMyzlE0000gTA`VyVP%>UY8i+xn5Y$qC`Ljk zC?06M9Kk{m=y zNMEXW4O!(PwIBd(Zfz-TZXE6OcPs}Z2IYeh@VPooJ)utHysHh@mTSWybNb$Tv}m+& zw1{or8Q&S#8P6Jm@(bj+S#2@V^8qNFC*>PR&_=b2AP#LMh>9hO#frt6fh*_oy{-hm z?Rx7tCi@U$RN6B_nD+gGA?NXVA>cR)8?EhED%--3+8Yv4_fjp z53(Gm7YYDzB`~K(~GZt1H6WUa) z!~7Pebz6(L{E#9@QK1OZr>Fu|;i^E*N2+|)uqr>TL>;3Rsbe1O9EctWABcX`pgEuk z)g1U`v_(u_;)B?ojN5j^5=6;uf*0C52;T3?AUM{uu4jGEI>g@0-t)bg22pi(jbrtf zlRBxL^t{v-ZDjh6dosOogIuP}lgsdlD!6Mx6^y9n_U==25&NAq_5Duxo-X21fGz^D z&$V}=&vj@HR2(4+vE)O7*m#pXooe1ojJa)r1h2?16N6B-TlKkWxA9l`PI;hw8=~TM z!OWK6ZeP5C%TQy(EbOCp>V{; zzohAn6G^k+$2_vLymo{j-u;8%?T!(Gq00A(@rbfcS+A@^Y{+ehX~)9L_Sn?dlHrH)M+8y6nBYMsfjPw_NZ0c%G9K_Pypof=vwlhP z25{e>@Qr(fc|3^9JfW)89H9IL0HW>b>LT0S#-h1V%$&JF#vTCSW;$oh5?x_gHr?VK z6l-}NE_x`qO<1yI9RY}*L9CPV?-IYgIi6(6_JFo!X#sz72Hu)zm%Jh690kZu1OTwP zbS=wj@)e> z@`vfnt++yeR>oU2@N({S50==QwZ zMV~%(c;Gj3X)qem&|^>-suAD0d^sb~FqQb+>~F4`%}0DbFq!MnEcS!xESqjL6+76} zoJmugm6_UN71!SS5XXZzW}3Odj!Qntvh`S)>?lP4YRdi)cTj%oIN!FJZmlQU&OBCr0vtLJ}LpSM81x5H8L^vik2!-nl+jQhME ze&IeptL35Vp3o^r&dN>?!LeAEWPpDFLUQL806cgq0000YdQ@0+Q*UN;cVTj6004N} pD=#nC%goCzPEIUH)ypqR2LLwM23QbN%3J^d002ovPDHLkV1kMGNaX+k literal 0 HcmV?d00001 diff --git a/kgpg/icons/hi16-actions-document-properties-key.png b/kgpg/icons/hi16-actions-document-properties-key.png new file mode 100644 index 0000000000000000000000000000000000000000..08468950b9d2d9f4fdf7b3a855fc9c4527afb406 GIT binary patch literal 725 zcmV;`0xJE9P)c#e9(XYq2S`AzH9dEw!dW za3O^V1)&vM6-uoiD%eFU?nDX^g%-)mg_{OM;|IYmM2epnKZOj@vb zzF7n^lH|bU@y>hq%)9s80fdkyc%GjM1OlXnZxEVII2^uFC=^gGms@F+G&cw2@k8{I z%jG&iLKTcgCL_;o+gQzEG#V9>$s|gr(|oPu`3bnz(*bcl*cwVbq&St+lpxD8DvM>%>-D?g zKlm>Y=bpcX)AN_@@$F1zoEt0)D!&D)AW0I4Wf5dWCW66WQ-!~Kk?_6x4CkkYx#a$x z;siIinNs;$0tkn?${fd8Rqoz2;fZ{M=@WgHzlU9hE5`m`aj1f(1ndA(6b07T*7f;( z-VwM;Sbk>UdRyxrW#}k9%k)M+0Y+T`+~{O6$z(zfhXY-j978Xk+(&1JID{t>Ii1e#hW~=qYCQu0 zNR5^j4n1C4MD2S`sKS}e=7oj=27_UlB*__d)79Dj`e4TnSLG1D;22HQf81_2RZoBb zfLWGh2P_uLHavmNILV0nP{fwzvf1oLB9ZtC0HgS_1U2vvb_K0bD***z00000NkvXX Hu0mjf?VmuR literal 0 HcmV?d00001 diff --git a/kgpg/icons/hi16-status-key-group.png b/kgpg/icons/hi16-status-key-group.png new file mode 100644 index 0000000000000000000000000000000000000000..a46ee9e27780752663531b1000b5f7b6a00a4134 GIT binary patch literal 861 zcmV-j1ETziP) zFdveV{@PPeK~O!E1U>bXFCnH7q4glXR5&8`B8d-5vaQh?WBtL+={C3N-FEMG@9q0^ z!*+KAK>$gTMA+{i5H>b=jQKAC(leSDgek6}0a%s=1?7H6 zB2dWZg#y4e_}lOZ*n4|>DUZi99Sj669Ri3S*Xz}jR;$`+G^*_kqoUkPuh)Ab7z}KPrFGne;8e3Z>Q;RM*!^Ma5#c7KA-PqrEYr!hGC2- zh3B;_s_Mr#6BEyPo_~a5h{a+zF7)-qL!pqhR4U=ojsd_ht|O}iilTt3sE}LFg>Y=K zS|zrgg-kYs_d--Lq9j5z8hwT19R%fad26c_-HK1#IhjnsL6<9r)P2S|+`U{XgUx2^ z$MG%&*abmIZxYe;=8S9|YsPVfK$Rp35hnxxRDg^80iSJbY>Yz+Fd-%hqZg7Z7~BA& zKck(yz5}#<9e(${rUt555KUDD9LKd(F}Ylha5|m%T-!l#x$oW;1<2dQLJ??&28N-h z&z-LuSJlnN>F(}stjL21^>_rGPS-y&GO{>1IXOghbq%noSY~NuC2f>t1sDwt8Vv*W zdUojHi=~lhZ9a2#HIW>(@n)3Vnh22rQop?a zKwH!N{BX%&Fa*&2ZD{*|O%z3l$Kzl&o9__3vBN9N;<#K^zCx{$)DzCYy(q|mL@Jef zF3a+*+G~oUoXGFfk|@68_nD76n(Up9M!t>&0+!>eqR;pJM=qC%tO+ZER;yibxm?#B n4u=kP)uM${XE z)7O>#3abc@pww5#8TvpWxd5LKS0IfBs&s|UT60|TSNxI4#^x{n6)4SG666=mpkt7e zFR^g-QJ?^0lDE4HLkFv@2apr#>EaktacgfEBVU672Xps@h5!F=cpKyMjbSf~#`H(- zO-g0qds^PURGF%!tP!ktsH7u`ZDwIoWw1yvyUAWt-1fmdEvBBm$Xji gh-nA-Sob|++kKC@IOcJ|dXNh}UHx3vIVCg!0Eh@nIsgCw literal 0 HcmV?d00001 diff --git a/kgpg/icons/hi16-status-key-pair.png b/kgpg/icons/hi16-status-key-pair.png new file mode 100644 index 0000000000000000000000000000000000000000..d473e38befacf3ed2058d8e108b8566e676e7367 GIT binary patch literal 703 zcmV;w0zmzVP)@5g^jCmj%l5rt?%DR3t(1>3m^ra)Rm zi)vrcs$EnRxNsAJ(jqraErJ+D+_W+q7itJXnjncLj5y->@4UV%Z{Vfl2Zwjh&;8yx z_kNdiyWO{RI^AtalD3$s)#{7DiN#_$v0ANzdc7XYWHS4-2^_~oc%F}AX6cfn5 z0P(W4P$=AFYo&2WT-R!~1;5`C;FU^bf{z~|A`T~a`lzpq=Z)_$kc;RHdT z`Fu{N0OD^*#$vG$S<92jM7KtO#AnR97Yqj9Pp4Bg#+as3sW9SSNd`zc#tA!IBalfx zWwY70CX>l991i~`lgTdxC_0cp!aBI2jPptd?Muz(ANp~aGtml`o2L`@{k4$*2ZY1g zqAR<*<#Pz6Uav>B-!<>*2ucEu7jTt=>M{W0)+jEP)A7*EXvKls?{p%cDt<8>HJXAiYgQe+g`8tQ@vil zV>B8`sZ`E{mhfezoAdenmMDtnaKM_)CKCkVvhuZZBG>-wAeYNMcDvn&wOVZ*B8-$8 zjYeWKu(?5yLY}fn*=+Wa!{In=x7)V0^)=FHG}IA0d^j9_Fh8RPSIT5E-qC3EN~KcW z)@rpE>2&(}bUL-oW-|ip^yG98wf8jF+(9rHj7%nz9awyd##;p8AO!*eN+c2_TR-}I zB>Ervx8m{m_kRYUVShXxCnZVhN=f{xm+%WRWXmp!>~G*OeS|1ZAjk2%i(b74uwJ~ z8ZBBVx#8f!{ri_3x^m^nL5BDDBY(V~|Np<#|Nln+|HuCSpZ5QM&;S23{{P?h|Noi) z|NnP>OPvHXjd0)2wuKhmi z>>7Ai{@TZ^^mw^<`WAP_N*#;Uzvg`GR2O`CLHQTQ%RLP(>W7&8E9T5Cv1hiq_u#0< z`G!iTA1pq`Pi!o$n@ex^*G603XRbAuxv!j&may!d>({x_Kl5!H9v08@X6`-`cFtbx WATP_Omkr*afbn$ob6Mw<&;$T-z>!4& literal 0 HcmV?d00001 diff --git a/kgpg/icons/hi22-action-view-key-secret.png b/kgpg/icons/hi22-action-view-key-secret.png new file mode 100644 index 0000000000000000000000000000000000000000..44e105906d55e30d6eb2e38b3628fa16f845cd65 GIT binary patch literal 240 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 DSM34< literal 0 HcmV?d00001 diff --git a/kgpg/icons/hi22-status-key-group.png b/kgpg/icons/hi22-status-key-group.png new file mode 100644 index 0000000000000000000000000000000000000000..8afd1fee82e88e783d190bc8a42c6a153457eb85 GIT binary patch literal 1266 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 literal 0 HcmV?d00001 diff --git a/kgpg/icons/hi32-actions-document-properties-key.png b/kgpg/icons/hi32-actions-document-properties-key.png new file mode 100644 index 0000000000000000000000000000000000000000..a3dfe76ccf0fdc91c6772193db767394ceb2c2a7 GIT binary patch literal 1676 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 Rm7f3r002ovPDHLkV1g(RYx)2H literal 0 HcmV?d00001 diff --git a/kgpg/icons/hi48-actions-document-export-key.png b/kgpg/icons/hi48-actions-document-export-key.png new file mode 100644 index 0000000000000000000000000000000000000000..9caefbf93910b544daf0c63774b7328652352f6a GIT binary patch literal 2513 zcmV;?2`=`DP)7+qv&hIu zqiig)KtVx4Y&0Po)YsQX$b{oJIB~b7rRC4FX3bjiz#Q1PaibC)9sT!FqelJE+uK_q z;O^bK&0Sqx*TTZWQsG!=KtKTb`S}r~M5?MfoayQ5A%?|b>947&*@H~H`ap^}G&D44 z#E22o5nTm_St~0m_f%C?J(iuFopI&Lm5hptiUS=T9U8PxHk*yuamb6{9O-mA&XCve z;lp1%c<|sm9?w8QK|xAlVqz(ZHl8C}U0r=_{`~n+aLB5*w)O@xF>%R~B^~4e=pY9rDk^F}qBoxP%xG+EJTiX#_+pZfnVG3wx^!s@ z=8omav*8S`19k+mb2tzf7#NNW&O34P_&XM}Y0?3lxyUKW5B= zs2Pqrlfh$r&Gi8ZLRO)PH*ekytiM$s0!On5fV7^*B5<^|tu@_XG<*T4YNBJJ|MuDG z&(4s9Bgy3r;5*?Ki^hTQnHYopycQ0uS`oz%jd!j2?=o& zCbZrM&HrTe>eYV{6AKFq*B}y`xR9A_C})g~=PYoafh85IQk$b68yClp*}F_#&B#my z?$ax(Li~sCMMIih=`}fc$*UFfSN3u=D!$>$w>j85;@ELk=mJ$b~4P zPyYV?-pR?yg+)b0)6Hgc1Lh=+9zA*{JIN0c8Q}X^O5!Da&4!~(2R=DK)2GcK>?bM^ zV{Nw*Ha=2yD$jFfm`>Gk%SOuMVm`~0->ri!VUYK`M~@!unl^3P`+69hKmbp&TCMB| zN0|FYEUT{yBiVQ!XG57hPe@0|O6&`~GG#uINpPNQRvXDt7ta&#sN&*UYTeC4 zE1VS9QIKVx9?Rb?B1#w07jh7Wu_Q1@mWz;^Y%tGr$V(LVnX)n(F>)jY1qYEs9)5@)F1|mo?80uv5JIa^D#+ zTE6B&6A}}fQB z+c6G+?3fIm=Nj_-q3^-@tOqO5mZeLV{)zxySy`F+$dMy4z!NY+5k|`?mEdk%tWGYY zD;yNYiaHa9JNmf1{-sKHIu#1{<63ack;%sQmX?-oUbbx6Iuf-Ta94x4Q*LkKHR)tk~!BIGvM|Ga)rK^>AEVoPi%M zHYGNkDPGEOJ_iZMkj>MVFJFEJpPqd*2$V%_QE1)+pQn4nX>ftIE9}OgA2c}cEQ*bd zeH*9FkjB-;EF)0CiXA(4G|@dk zdAUxHrVN}&ZtzIF7oB`7n*6*Jn!T-sO8;f1L2w5gs}&7z^~gW3D+{W5&0bG#U<}dG z=W7iVAK^s|li5yB4V0GXOY+`wyUj+A zZ@5i&+O6&i-3A7$_ucrHs=`ULCV=Ej?A^3`Q5Zcs+?$+$TF}#9-!M>sj~zX$6KzZ# zI9{du4b3(IBv;JM4x?F*1Q4U4t)B$ix^2WrOYo(SUNf?znD3GkRJ!l5rh(+lSDp-@ z*QbOMRNhf#<)N!C zcl$qjssm0`gETm8a^3LQP_%-TDtQF9LXd{0fc+lkT7R$Y`+>zX4rtaw@c92R1J#)G zrpNv#pSFLdh8MmJnm}?zb6-DI)|(vc=;<5GcssZN&+L{?qn?5C!oio&6!9f}eY~sv z>AAIoeh(-B8a=3UqAFiYeMRak5}lz@##@h(aY8Ps`Z)S(XeCWsu6;abzw;+08_Y&{ zfuhxaP3q)p$stXuOe+N$^_20;g%lh+lkNpp%cpdscnw|p!)<9B?&FpnkRwmxrNS6F z>SD}AD`!hKoFoSrn!JqA$qx3L%jnRe66roo blZ^f^;GR!7Pa3XQ00000NkvXXu0mjf$cEMo literal 0 HcmV?d00001 diff --git a/kgpg/icons/hi48-actions-document-import-key.png b/kgpg/icons/hi48-actions-document-import-key.png new file mode 100644 index 0000000000000000000000000000000000000000..38867602e9c4513840d508b5aca49c8c75e7baab GIT binary patch literal 9282 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 literal 0 HcmV?d00001 diff --git a/kgpg/icons/hi48-actions-document-properties-key.png b/kgpg/icons/hi48-actions-document-properties-key.png new file mode 100644 index 0000000000000000000000000000000000000000..c37c7d706c38c3ecc47c0ea5eab407d34dd59d42 GIT binary patch literal 2813 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 P00000NkvXXu0mjfG<-Bb literal 0 HcmV?d00001 diff --git a/kgpg/icons/hi48-status-key-group.png b/kgpg/icons/hi48-status-key-group.png new file mode 100644 index 0000000000000000000000000000000000000000..cf2cbeb04a192463e161141a73e0bcb86dd0a7b4 GIT binary patch literal 3297 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 literal 0 HcmV?d00001 diff --git a/kgpg/icons/hi48-status-key-pair.png b/kgpg/icons/hi48-status-key-pair.png new file mode 100644 index 0000000000000000000000000000000000000000..0c21af7245b1c32a3b3f1449455bba5d9e749c33 GIT binary patch literal 2456 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%0A&dLW_p*6Jz;R!gF6FGa{YoALC?Q4C)g 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 literal 0 HcmV?d00001 diff --git a/kgpg/icons/hisc-actions-document-import-key.svgz b/kgpg/icons/hisc-actions-document-import-key.svgz new file mode 100644 index 0000000000000000000000000000000000000000..d3ea6fe5bfdc0f732c7669596abb1c63fef42006 GIT binary patch literal 20583 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 W3`N`NPdP21GCR2F2-NY=U-%#5nx;hn literal 0 HcmV?d00001 diff --git a/kgpg/icons/hisc-actions-document-properties-key.svgz b/kgpg/icons/hisc-actions-document-properties-key.svgz new file mode 100644 index 0000000000000000000000000000000000000000..c8f9c255d21804e1a5a25bad7725ccd7434a381c GIT binary patch literal 66516 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 literal 0 HcmV?d00001 diff --git a/kgpg/icons/hisc-status-key-group.svgz b/kgpg/icons/hisc-status-key-group.svgz new file mode 100644 index 0000000000000000000000000000000000000000..80b0d98ecee9ecea1002a507750b89da7da99c25 GIT binary patch literal 63827 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 bE69|te^w^s_x=f*Tom$C>x}J=LIC?eDKwXZ literal 0 HcmV?d00001 diff --git a/kgpg/icons/hisc-status-key-pair.svgz b/kgpg/icons/hisc-status-key-pair.svgz new file mode 100644 index 0000000000000000000000000000000000000000..40fe025e248594a5d5b80abc11a84dde764c1d50 GIT binary patch literal 62891 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
kcombobox.h
+ + + KUrlRequester + QFrame +
kurlrequester.h
+
+ + + kpushbutton.h + + + + + checkServer + toggled(bool) + destServer + setEnabled(bool) + + + 72 + 69 + + + 95 + 106 + + + + + checkFile + toggled(bool) + newFilename + setEnabled(bool) + + + 39 + 133 + + + 48 + 159 + + + + + diff --git a/kgpg/keyinfodialog.cpp b/kgpg/keyinfodialog.cpp new file mode 100644 index 00000000..fb8eb20b --- /dev/null +++ b/kgpg/keyinfodialog.cpp @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007 Jimmy Gilles + * Copyright (C) 2008,2009,2010,2011,2012,2013,2014 Rolf Eike Beer + * Copyright (C) 2011 Philip Greggory Lee + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "keyinfodialog.h" + +#include "kgpgchangekey.h" +#include +#include "selectexpirydate.h" +#include "core/convert.h" +#include "core/images.h" +#include "core/kgpgkey.h" +#include "model/kgpgitemmodel.h" +#include "model/kgpgitemnode.h" +#include "transactions/kgpgchangepass.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KgpgCore; + +KgpgTrustLabel::KgpgTrustLabel(QWidget *parent, const QString &text, const QColor &color) + : QWidget(parent), + m_text_w(new QLabel(this)), + m_color_w(new QLabel(this)), + m_text(text), + m_color(color) +{ + m_text_w->setTextInteractionFlags(Qt::TextSelectableByMouse); + + m_color_w->setLineWidth(1); + m_color_w->setFrameShape(QFrame::Box); + m_color_w->setAutoFillBackground(true); + m_color_w->setMinimumWidth(64); + + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setSpacing(10); + layout->setMargin(2); + layout->addWidget(m_text_w); + layout->addWidget(m_color_w); + + change(); +} + +void KgpgTrustLabel::setText(const QString &text) +{ + m_text = text; + change(); +} + +void KgpgTrustLabel::setColor(const QColor &color) +{ + m_color = color; + change(); +} + +QString KgpgTrustLabel::text() const +{ + return m_text; +} + +QColor KgpgTrustLabel::color() const +{ + return m_color; +} + +void KgpgTrustLabel::change() +{ + m_text_w->setText(m_text); + + QPalette palette = m_color_w->palette(); + palette.setColor(m_color_w->backgroundRole(), m_color); + m_color_w->setPalette(palette); +} + +KgpgKeyInfo::KgpgKeyInfo(KGpgKeyNode *node, KGpgItemModel *model, QWidget *parent) + : KDialog(parent), + keychange(new KGpgChangeKey(node, this)), + m_node(node), + m_model(model), + m_keywaschanged(false), + m_closewhendone(false) +{ + Q_ASSERT(m_model != NULL); + Q_ASSERT(m_node != NULL); + + setupUi(this); + + setButtons(Ok | Apply | Cancel); + setDefaultButton(Ok); + setModal(true); + enableButtonApply(false); + + m_email->setUnderline(false); + m_trust = new KgpgTrustLabel(this); + int trustRow; + formLayout_keyproperties->getWidgetPosition(tl_trust, &trustRow, NULL); + formLayout_keyproperties->setWidget(trustRow, QFormLayout::FieldRole, m_trust); + + // Hide some widgets if this is not a secret node. + if ( ! m_node->isSecret() ) { + m_expirationbtn->hide(); + m_password->hide(); + } + + setMainWidget(page); + + connect(m_owtrust, SIGNAL(activated(int)), this, SLOT(slotChangeTrust(int))); + connect(m_photoid, SIGNAL(activated(QString)), this, SLOT(slotLoadPhoto(QString))); + connect(m_email, SIGNAL(leftClickedUrl(QString)), this, SLOT(slotOpenUrl(QString))); + connect(keychange, SIGNAL(done(int)), SLOT(slotApplied(int))); + connect(m_disable, SIGNAL(toggled(bool)), this, SLOT(slotDisableKey(bool))); + connect(m_expirationbtn, SIGNAL(clicked()), this, SLOT(slotChangeDate())); + connect(m_password, SIGNAL(clicked()), this, SLOT(slotChangePass())); + + displayKey(); + adjustSize(); + gr_fingerprint->setMinimumHeight(gr_fingerprint->height()); +} + +KgpgKeyInfo::~KgpgKeyInfo() +{ + if (keychange) + keychange->selfdestruct(false); +} + +void KgpgKeyInfo::reloadNode() +{ + const QString kid(m_node->getId()); + + // this will delete m_node + m_model->refreshKey(m_node); + + m_node = m_model->getRootNode()->findKey(kid); + if (m_node != NULL) { + displayKey(); + } else { + KMessageBox::error(this, i18n("The requested key is not present in the keyring anymore.
Perhaps it was deleted by another application
"), i18n("Key not found")); + m_keywaschanged = false; + close(); + } +} + +void KgpgKeyInfo::displayKey() +{ + const QString name = m_node->getName(); + setCaption(name); + m_name->setText(QLatin1String( "" ) + name + QLatin1String( "" )); + + const QString email = m_node->getEmail(); + if (email.isEmpty()) { + m_email->setText(i18nc("no email address", "none")); + m_email->setUrl(QString()); + m_email->setEnabled(false); + } else { + m_email->setText(QLatin1String( "<" ) + email + QLatin1String( ">" )); + m_email->setUrl(QLatin1String( "mailto:" ) + name + QLatin1Char( '<' ) + email + QLatin1Char( '>' )); + } + + const KgpgKey *key = m_node->getKey(); + m_caps->setText(Convert::toString(key->keytype())); + + QString trust; + QColor trustcolor; + + if (key->valid()) { + QModelIndex idx = m_model->nodeIndex(m_node, KEYCOLUMN_TRUST); + trust = m_model->data(idx, Qt::AccessibleTextRole).toString(); + trustcolor = m_model->data(idx, Qt::BackgroundColorRole).value(); + } else { + trust = Convert::toString(TRUST_DISABLED); + trustcolor = KGpgSettings::colorBad(); + } + + m_id->setText(m_node->getId().right(16)); + m_algorithm->setText(Convert::toString(key->algorithm()) + QLatin1String( " / " ) + Convert::toString(key->encryptionAlgorithm())); + m_algorithm->setWhatsThis(i18n("The left part is the algorithm used by the signature key. The right part is the algorithm used by the encryption key.")); + m_creation->setText(KGlobal::locale()->formatDate(m_node->getCreation().date(), KLocale::ShortDate)); + if (m_node->getExpiration().isNull()) + m_expiration->setText(i18nc("Unlimited key lifetime", "Unlimited")); + else + m_expiration->setText(KGlobal::locale()->formatDate(m_node->getExpiration().date(), KLocale::ShortDate)); + m_trust->setText(trust); + m_trust->setColor(trustcolor); + m_length->setText(m_node->getSize()); + m_length->setWhatsThis(i18n("The left part is the size of the signature key. The right part is the size of the encryption key.")); + m_fingerprint->setText(m_node->getBeautifiedFingerprint()); + + const QString comment = m_node->getComment(); + if (comment.isEmpty()) { + m_comment->setText(i18nc("no key comment", "none")); + m_comment->setTextFormat(Qt::RichText); + } else { + m_comment->setText(comment); + m_comment->setTextFormat(Qt::PlainText); + } + + switch (key->ownerTrust()) { + case GPGME_VALIDITY_NEVER: + m_owtrust->setCurrentIndex(1); + break; + case GPGME_VALIDITY_MARGINAL: + m_owtrust->setCurrentIndex(2); + break; + case GPGME_VALIDITY_FULL: + m_owtrust->setCurrentIndex(3); + break; + case GPGME_VALIDITY_ULTIMATE: + m_owtrust->setCurrentIndex(4); + break; + case GPGME_VALIDITY_UNDEFINED: + default: + m_owtrust->setCurrentIndex(0); + break; + } + + if (!key->valid()) + m_disable->setChecked(true); + + connect(m_node, SIGNAL(expanded()), SLOT(slotKeyExpanded())); + m_node->expand(); + m_photoid->clear(); +} + +void KgpgKeyInfo::slotOpenUrl(const QString &url) const +{ + KToolInvocation::invokeBrowser(url); +} + +void KgpgKeyInfo::slotLoadPhoto(const QString &uid) +{ + int i = uid.toInt(); + QPixmap pixmap = m_node->getUid(i)->toUatNode()->getPixmap(); + QImage img = pixmap.toImage(); + pixmap = QPixmap::fromImage(img.scaled(m_photo->width(), m_photo->height(), Qt::KeepAspectRatio)); + m_photo->setPixmap(pixmap); +} + +void KgpgKeyInfo::slotChangeDate() +{ + QPointer dialog = new SelectExpiryDate(this, m_node->getExpiration()); + if (dialog->exec() == QDialog::Accepted) { + keychange->setExpiration(dialog->date()); + enableButtonApply(keychange->wasChanged()); + } + delete dialog; +} + +void KgpgKeyInfo::slotDisableKey(const bool ison) +{ + keychange->setDisable(ison); + enableButtonApply(keychange->wasChanged()); +} + +void KgpgKeyInfo::slotChangePass() +{ + KGpgChangePass *cp = new KGpgChangePass(this, m_node->getId()); + + connect(cp, SIGNAL(done(int)), SLOT(slotInfoPasswordChanged(int))); + + cp->start(); + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); +} + +void KgpgKeyInfo::slotInfoPasswordChanged(int result) +{ + sender()->deleteLater(); + + QApplication::restoreOverrideCursor(); + + switch (result) { + case KGpgTransaction::TS_OK: + KMessageBox::information(this, i18n("Passphrase for the key was changed")); + break; + case KGpgTransaction::TS_BAD_PASSPHRASE: + KMessageBox::error(this, i18n("Bad old passphrase, the passphrase for the key was not changed"), i18n("Could not change passphrase")); + break; + case KGpgTransaction::TS_USER_ABORTED: + break; + default: + KMessageBox::error(this, i18n("KGpg was unable to change the passphrase.")); + } +} + +void KgpgKeyInfo::slotChangeTrust(const int newtrust) +{ + keychange->setOwTrust(static_cast(newtrust + 1)); + enableButtonApply(keychange->wasChanged()); +} + +void KgpgKeyInfo::setControlEnable(const bool b) +{ + m_owtrust->setEnabled(b); + m_disable->setEnabled(b); + enableButtonApply(b && keychange->wasChanged()); + + if (m_expirationbtn) + m_expirationbtn->setEnabled(b); + if (m_password) + m_password->setEnabled(b); + + if (b) + QApplication::restoreOverrideCursor(); + else + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); +} + +void KgpgKeyInfo::slotButtonClicked(int button) +{ + switch (button) { + case Ok: + m_closewhendone = true; + // Fall-through + case Apply: + setControlEnable(false); + keychange->apply(); + break; + case Cancel: + if (m_keywaschanged && m_node) + emit keyNeedsRefresh(m_node); + reject(); + break; + default: + KDialog::slotButtonClicked(button); + } +} + +void KgpgKeyInfo::slotApplied(int result) +{ + if (result) { + KMessageBox::error(this, i18n("Changing key properties failed."), i18n("Key properties")); + } else { + m_keywaschanged = true; + if (m_node) + emit keyNeedsRefresh(m_node); + reloadNode(); + } + setControlEnable(true); + + if (m_closewhendone) + accept(); +} + +void KgpgKeyInfo::slotKeyExpanded() +{ + // the counting starts at 1 and that is the primary uid which can't be a photo id + int i = 2; + const KGpgSignableNode *uat; + + while ((uat = m_node->getUid(i++)) != NULL) { + if (uat->getType() != KgpgCore::ITYPE_UAT) + continue; + + m_photoid->addItem(uat->getId()); + } + + bool hasphoto = (m_photoid->count() > 0); + + m_photoid->setVisible(hasphoto); + m_photoid->setEnabled(hasphoto); + if (hasphoto) + slotLoadPhoto(m_photoid->currentText()); +} + +#include "keyinfodialog.moc" diff --git a/kgpg/keyinfodialog.h b/kgpg/keyinfodialog.h new file mode 100644 index 00000000..010f0952 --- /dev/null +++ b/kgpg/keyinfodialog.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007 Jimmy Gilles + * Copyright (C) 2008,2014 Rolf Eike Beer + * Copyright (C) 2011 Philip G. Lee + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGKEYINFODIALOG_H +#define KGPGKEYINFODIALOG_H + +#include +#include +#include + +#include + +#include "ui_kgpgKeyInfo.h" + +class QCheckBox; +class QGroupBox; + +class KPushButton; +class KUrlLabel; +class KComboBox; + +class KGpgItemModel; +class KGpgKeyNode; +class KGpgChangeKey; +class KGpgChangePass; + +class KgpgTrustLabel : public QWidget +{ + Q_OBJECT + +public: + explicit KgpgTrustLabel(QWidget *parent = 0, const QString &text = QString(), const QColor &color = QColor()); + + void setText(const QString &text); + void setColor(const QColor &color); + + QString text() const; + QColor color() const; + +private: + void change(); + + QLabel *m_text_w; + QLabel *m_color_w; + + QString m_text; + QColor m_color; +}; + +class KgpgKeyInfo : public KDialog, public Ui::kgpgKeyInfo +{ + Q_OBJECT + + KgpgKeyInfo(); // = delete C++0x + Q_DISABLE_COPY(KgpgKeyInfo) +public: + KgpgKeyInfo(KGpgKeyNode *node, KGpgItemModel *model, QWidget *parent); + ~KgpgKeyInfo(); + + KGpgChangeKey *keychange; + +signals: + void keyNeedsRefresh(KGpgKeyNode *node); + +protected slots: + void slotButtonClicked(int button); + +private: + void reloadKey(); + void reloadNode(); + void displayKey(); + void setControlEnable(const bool b); + +private slots: + void slotOpenUrl(const QString &url) const; + + void slotChangeDate(); + + void slotDisableKey(const bool ison); + + void slotChangePass(); + void slotInfoPasswordChanged(int result); + + void slotChangeTrust(const int newtrust); + + void slotLoadPhoto(const QString &uid); + + void slotApplied(int result); + + void slotKeyExpanded(); + +private: + KGpgKeyNode *m_node; + KGpgItemModel *m_model; + + KgpgTrustLabel *m_trust; + + bool m_keywaschanged; + bool m_closewhendone; +}; + +#endif // KGPGKEYINFODIALOG_H diff --git a/kgpg/keyserver.ui b/kgpg/keyserver.ui new file mode 100644 index 00000000..80b421fc --- /dev/null +++ b/kgpg/keyserver.ui @@ -0,0 +1,315 @@ + + keyServerWidget + + + + 0 + 0 + 529 + 339 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + Import + + + + + + <qt><b>Key Server:</b><br /> <p>A Key Server is a centralized repository of PGP/GnuPG keys connected to the Internet which can be conveniently accessed in order to obtain or deposit keys. Select from the drop down list to specify which key server should be used.</p> <p>Often these keys are held by people whom the user has never met and as such the authenticity is dubious at best. Refer to the GnuPG manual covering "Web-of-Trust" relationships to find out how GnuPG works around the problem of verifying authenticity.</p> </qt> + + + Key server: + + + false + + + + + + + <b>Key Server Drop Down Dialog:</b> +Allows the user to select the Key Server which will be used to import PGP/GnuPG keys into the local keyring. + + + + + + + <qt><b>Text to search or ID of the key to import:</b><br /> +<p>There are multiple ways to search for a key, you can use a text or partial text search (example: entering Phil or Zimmerman will bring up all keys in which Phil or Zimmerman shows up) or you can search by the ID's of the key. Key ID's are strings of letters and numbers that uniquely identify a key (example: searching for 0xED7585F4 would bring up the key associated with that ID).</p></qt> + + + QFrame::NoFrame + + + QFrame::Plain + + + Text to search or ID of the key to import: + + + false + + + + + + + true + + + + + + + + + false + + + Honor HTTP proxy: + + + + + + + false + + + + 0 + 0 + + + + true + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 477 + 121 + + + + + + + + + + &Search + + + true + + + + + + + &Import + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 160 + 20 + + + + + + + + + + + Export + + + + + + Key server: + + + false + + + + + + + + + + <qt><b>Key to be exported:</b><br /> +<p>This allows the user to specify the key from the drop down list that will be exported to the key server selected.</p></qt> + + + Key to be exported: + + + false + + + + + + + + + + Export attributes (photo id) + + + true + + + + + + + + + false + + + Honor HTTP proxy: + + + + + + + false + + + + 0 + 0 + + + + true + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 507 + 63 + + + + + + + + + + <qt><b>Export:</b><br /> +<p>Pushing this button will export the specified key to the specified server.</p></qt> + + + &Export + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 250 + 20 + + + + + + + + + + + + + + + KComboBox + QComboBox +
kcombobox.h
+
+ + KLineEdit + QLineEdit +
klineedit.h
+
+ + KTabWidget + QTabWidget +
ktabwidget.h
+
+
+ + +
diff --git a/kgpg/keyservers.cpp b/kgpg/keyservers.cpp new file mode 100644 index 00000000..0f3e59cb --- /dev/null +++ b/kgpg/keyservers.cpp @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2006,2007,2008,2009,2010,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "keyservers.h" + +#include "core/convert.h" +#include "detailedconsole.h" +#include "gpgproc.h" +#include "kgpgsettings.h" +#include "model/keylistproxymodel.h" +#include "model/kgpgitemmodel.h" +#include "model/kgpgsearchresultmodel.h" +#include "transactions/kgpgimport.h" +#include "transactions/kgpgkeyservergettransaction.h" +#include "transactions/kgpgkeyserversearchtransaction.h" +#include "transactions/kgpgsendkeys.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +KeyServer::KeyServer(QWidget *parent, KGpgItemModel *model, const bool autoclose) + : KDialog(parent), + m_dialogserver(NULL), + m_searchproc(NULL), + page(new keyServerWidget()), + m_listpop(NULL), + m_resultmodel(NULL), + m_itemmodel(new KeyListProxyModel(this, KeyListProxyModel::SingleColumnIdFirst)) +{ + setCaption(i18n("Key Server")); + setButtons(Close); + setModal(false); + + m_autoclose = autoclose; + m_filtermodel.setSortCaseSensitivity(Qt::CaseInsensitive); + m_filtermodel.setDynamicSortFilter(true); + m_filtermodel.setFilterKeyColumn(0); + + setMainWidget(page); + + const QStringList serverlist(getServerList()); + page->kCBexportks->addItems(serverlist); + page->kCBimportks->addItems(serverlist); + page->kLEimportid->setFocus(); + + connect(page->Buttonimport, SIGNAL(clicked()), SLOT(slotImport())); + connect(page->Buttonsearch, SIGNAL(clicked()), SLOT(slotSearch())); + connect(page->Buttonexport, SIGNAL(clicked()), SLOT(slotPreExport())); + connect(page->kLEimportid, SIGNAL(returnPressed()), SLOT(slotSearch())); + connect(this, SIGNAL(okClicked()), SLOT(slotOk())); + connect(page->cBproxyI, SIGNAL(toggled(bool)), SLOT(slotEnableProxyI(bool))); + connect(page->cBproxyE, SIGNAL(toggled(bool)), SLOT(slotEnableProxyE(bool))); + connect(page->kLEimportid, SIGNAL(textChanged(QString)), SLOT(slotTextChanged(QString))); + + page->cBproxyI->setChecked(KGpgSettings::useProxy()); + page->cBproxyE->setChecked(KGpgSettings::useProxy()); + + const QString httpproxy(QLatin1String( qgetenv("http_proxy") )); + if (!httpproxy.isEmpty()) { + page->cBproxyI->setEnabled(true); + page->cBproxyE->setEnabled(true); + page->kLEproxyI->setText(httpproxy); + page->kLEproxyE->setText(httpproxy); + } + + page->Buttonimport->setEnabled(!page->kLEimportid->text().isEmpty()); + page->Buttonsearch->setEnabled(!page->kLEimportid->text().isEmpty()); + setMinimumSize(sizeHint()); + + m_itemmodel->setKeyModel(model); + m_itemmodel->setTrustFilter(KgpgCore::TRUST_UNDEFINED); + page->kCBexportkey->setModel(m_itemmodel); +} + +KeyServer::~KeyServer() +{ + delete page; +} + +void KeyServer::slotImport() +{ + if (page->kCBimportks->currentText().isEmpty()) + return; + + if (page->kLEimportid->text().isEmpty()) { + KMessageBox::sorry(this, i18n("You must enter a search string.")); + return; + } + + startImport(page->kLEimportid->text().simplified().split(QLatin1Char( ' ' )), page->kCBimportks->currentText(), page->kLEproxyI->text()); +} + +void KeyServer::startImport(const QStringList &keys, QString server, const QString &proxy) +{ + if (server.isEmpty()) { + const QStringList kservers = KeyServer::getServerList(); + if (kservers.isEmpty()) { + KMessageBox::sorry(this, i18n("You need to configure keyservers before trying to download keys."), + i18n("No keyservers defined")); + return; + } + + server = kservers.first(); + } + + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + + KGpgReceiveKeys *proc = new KGpgReceiveKeys(this, server, keys, true, proxy); + connect(proc, SIGNAL(done(int)), SLOT(slotDownloadKeysFinished(int))); + + proc->start(); +} + +void KeyServer::slotDownloadKeysFinished(int resultcode) +{ + QApplication::restoreOverrideCursor(); + + KGpgKeyserverGetTransaction *t = qobject_cast(sender()); + const QStringList log(t->getLog()); + + t->deleteLater(); + + if (resultcode == KGpgTransaction::TS_USER_ABORTED) { + emit importFailed(); + return; + } + + const QStringList keys(KGpgImport::getImportedIds(log)); + const QString resultmessage(KGpgImport::getImportMessage(log)); + + if (!keys.empty()) + emit importFinished(keys); + + (void) new KgpgDetailedInfo(this, resultmessage, log.join(QLatin1String("\n")), + KGpgImport::getDetailedImportMessage(log).split(QLatin1Char( '\n' )), + i18nc("Caption of message box", "Key Import Finished")); +} + +void KeyServer::slotExport(const QStringList &keyIds) +{ + if (page->kCBexportks->currentText().isEmpty()) + return; + + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + + KGpgSendKeys *nk = new KGpgSendKeys(this, page->kCBimportks->currentText(), keyIds, expattr, true, page->kLEproxyI->text()); + connect(nk, SIGNAL(done(int)), SLOT(slotUploadKeysFinished(int))); + + nk->start(); +} + +void KeyServer::slotUploadKeysFinished(int resultcode) +{ + KGpgSendKeys *nk = qobject_cast(sender()); + Q_ASSERT(nk != NULL); + + const QStringList message(nk->getLog()); + nk->deleteLater(); + + QApplication::restoreOverrideCursor(); + + QString title; + if (resultcode == KGpgTransaction::TS_OK) + title = i18n("Upload to keyserver finished without errors"); + else + title = i18n("Upload to keyserver failed"); + KMessageBox::informationList(this, title, message); +} + +void KeyServer::slotSearch() +{ + if (page->kCBimportks->currentText().isEmpty()) + return; + + if (page->kLEimportid->text().isEmpty()) { + KMessageBox::sorry(this, i18n("You must enter a search string.")); + return; + } + + page->Buttonsearch->setEnabled(false); + if (m_searchproc) + return; + + if (m_resultmodel != NULL) + m_resultmodel->deleteLater(); + m_resultmodel = new KGpgSearchResultModel(this); + m_filtermodel.setSourceModel(m_resultmodel); + m_filtermodel.setFilterRegExp(QRegExp()); + + m_dialogserver = new KDialog(this ); + m_dialogserver->setCaption( i18n("Import Key From Keyserver") ); + m_dialogserver->setButtons( KDialog::Ok | KDialog::Close ); + m_dialogserver->setDefaultButton( KDialog::Ok); + m_dialogserver->setModal( true ); + + m_dialogserver->setButtonText(KDialog::Ok, i18n("&Import")); + m_dialogserver->enableButtonOk(false); + m_listpop = new searchRes(m_dialogserver); + m_listpop->kLVsearch->setModel(&m_filtermodel); + m_listpop->kLVsearch->setColumnWidth(0, 180); + m_listpop->statusText->setText(i18n("Connecting to the server...")); + + connect(m_listpop->filterEdit, SIGNAL(textChanged(QString)), SLOT(slotSetFilterString(QString))); + connect(m_listpop->kLVsearch->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(transferKeyID())); + connect(m_dialogserver, SIGNAL(okClicked()), this, SLOT(slotPreImport())); + connect(m_listpop->kLVsearch, SIGNAL(activated(QModelIndex)), m_dialogserver, SIGNAL(okClicked())); + connect(m_dialogserver, SIGNAL(closeClicked()), this, SLOT(handleQuit())); + connect(m_listpop->kLEID, SIGNAL(clearButtonClicked()), m_listpop->kLVsearch->selectionModel(), SLOT(clearSelection())); + + m_listpop->kLVsearch->setSelectionMode(QAbstractItemView::ExtendedSelection); + + m_readmessage.clear(); + + const QString keyserv(page->kCBimportks->currentText()); + + bool useproxy = page->cBproxyI->isChecked(); + QString proxy; + if (useproxy) + proxy = page->kLEproxyI->text(); + + m_searchproc = new KGpgKeyserverSearchTransaction(this, keyserv, page->kLEimportid->text().simplified(), + true, proxy); + connect(m_searchproc, SIGNAL(done(int)), SLOT(slotSearchResult(int))); + connect(m_searchproc, SIGNAL(newKey(QStringList)), m_resultmodel, SLOT(slotAddKey(QStringList))); + m_searchproc->start(); + + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + m_dialogserver->setMainWidget(m_listpop); + m_listpop->setMinimumSize(m_listpop->sizeHint()); + m_dialogserver->exec(); +} + +void KeyServer::slotSearchResult(int result) +{ + Q_ASSERT(sender() == m_searchproc); + m_searchproc->deleteLater(); + m_searchproc = NULL; + page->Buttonsearch->setEnabled(true); + QApplication::restoreOverrideCursor(); + + if (result == KGpgTransaction::TS_USER_ABORTED) { + delete m_dialogserver; + m_dialogserver = NULL; + return; + } + + m_dialogserver->enableButtonOk(true); + + const int keys = m_resultmodel->rowCount(QModelIndex()); + + if (keys > 0) { + m_listpop->statusText->setText(i18np("Found 1 matching key", "Found %1 matching keys", keys)); + m_listpop->kLVsearch->selectionModel()->setCurrentIndex(m_resultmodel->index(0, 0), + QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + } else { + m_listpop->statusText->setText(i18n("No matching keys found")); + } +} + +void KeyServer::slotSetText(const QString &text) +{ + page->kLEimportid->setText(text); +} + +void KeyServer::slotTextChanged(const QString &text) +{ + page->Buttonimport->setEnabled(!text.isEmpty()); + page->Buttonsearch->setEnabled(!text.isEmpty() && (m_searchproc == NULL)); +} + +void KeyServer::slotSetExportAttribute(const QString &state) +{ + if (!state.isEmpty()) + expattr = state; + else + expattr.clear(); +} + +void KeyServer::slotEnableProxyI(const bool on) +{ + page->kLEproxyI->setEnabled(on); +} + +void KeyServer::slotEnableProxyE(const bool on) +{ + page->kLEproxyE->setEnabled(on); +} + +void KeyServer::transferKeyID() +{ + QSet ids; + + foreach (const QModelIndex &index, m_listpop->kLVsearch->selectionModel()->selectedIndexes()) + ids << m_resultmodel->idForIndex(m_filtermodel.mapToSource(index)); + + const QStringList idlist(ids.toList()); + m_listpop->kLEID->setText(idlist.join( QLatin1String( " " ))); +} + +void KeyServer::slotPreImport() +{ + transferKeyID(); + if (m_listpop->kLEID->text().isEmpty()) { + KMessageBox::sorry(this, i18n("You must choose a key.")); + return; + } + const QStringList keys = m_listpop->kLEID->text().simplified().split(QLatin1Char(' ')); + m_dialogserver->close(); + startImport(keys, page->kCBimportks->currentText(), page->kLEproxyI->text()); +} + +void KeyServer::slotPreExport() +{ + slotExport(QStringList(page->kCBexportkey->currentText().section(QLatin1Char( ':' ), 0, 0))); +} + +void KeyServer::slotOk() +{ + accept(); +} + +QStringList KeyServer::getServerList() +{ + QStringList serverList(KGpgSettings::keyServers()); // From kgpg config + if (!serverList.isEmpty()) { + serverList.replaceInStrings(QRegExp(QLatin1String(" .*")), QString()); // Remove kde 3.5 (Default) tag. + const QString defaultServer(serverList.takeFirst()); + qSort(serverList); + serverList.prepend(defaultServer); + } + + return serverList; +} + +void KeyServer::handleQuit() +{ + if (m_searchproc != NULL) { + QApplication::restoreOverrideCursor(); + disconnect(m_searchproc, 0, 0, 0); + m_searchproc->deleteLater(); + m_searchproc = NULL; + } + m_dialogserver->close(); + page->Buttonsearch->setEnabled(true); +} + +void KeyServer::slotSetKeyserver(const QString &server) +{ + page->kCBimportks->setCurrentIndex(page->kCBimportks->findText(server)); +} + +void KeyServer::slotSetFilterString(const QString &expression) +{ + m_filtermodel.setFilterRegExp(QRegExp(expression, Qt::CaseInsensitive, QRegExp::RegExp2)); +} + +#include "keyservers.moc" diff --git a/kgpg/keyservers.h b/kgpg/keyservers.h new file mode 100644 index 00000000..93230f18 --- /dev/null +++ b/kgpg/keyservers.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2009,2010,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KEYSERVERS_H +#define KEYSERVERS_H + +#include + +#include + +#include "core/kgpgkey.h" +#include "ui_searchres.h" +#include "ui_keyserver.h" + +class KGpgKeyserverSearchTransaction; +class KeyListProxyModel; +class KGpgItemModel; +class KGpgSearchResultModel; + +class keyServerWidget : public QWidget, public Ui::keyServerWidget +{ +public: + explicit keyServerWidget(QWidget *parent = 0) + : QWidget(parent) + { + setupUi(this); + } +}; + +class searchRes : public QWidget, public Ui::searchRes +{ +public: + explicit searchRes(QWidget *parent) + : QWidget(parent) + { + setupUi(this); + } +}; + +class KeyServer : public KDialog +{ + Q_OBJECT + +public: + explicit KeyServer(QWidget *parent = 0, KGpgItemModel *model = 0, const bool autoclose = false); + ~KeyServer(); + + /** + * Returns the server list. + * The first item is the server configured in gpg. + */ + static QStringList getServerList(); + + /** + * @brief import the given keys + * @param keys the key fingerprints or ids to import + * @param server the key server to use, if empty the default server is used + * @param proxy the proxy to use + * + * This will set up the underlying transaction to fetch the keys as well + * as it will take care of setting up everything so the user will get the + * results shown. + */ + void startImport(const QStringList &keys, QString server = QString(), const QString &proxy = QString()); + +signals: + void importFinished(QStringList); + /** + * @brief importing failed + * + * This is emitted when key importing is finished and importFinished() is + * _not_ emitted. The main usage is to properly clean up the object when + * using startImport(). + */ + void importFailed(); + +public slots: + void slotImport(); + + void slotExport(const QStringList &keyIds); + + void slotSetText(const QString &text); + void slotTextChanged(const QString &text); + void slotSetExportAttribute(const QString &attr); + void slotEnableProxyI(const bool on); + void slotEnableProxyE(const bool on); + void slotSetKeyserver(const QString &server); + + void transferKeyID(); + void slotPreImport(); + void slotPreExport(); + + void slotOk(); + void handleQuit(); + +private slots: + void slotDownloadKeysFinished(int resultcode); + void slotUploadKeysFinished(int resultcode); + + void slotSearchResult(int result); + void slotSearch(); + void slotSetFilterString(const QString &expression); + +private: + QString m_readmessage; + + KDialog *m_dialogserver; + KGpgKeyserverSearchTransaction *m_searchproc; + + keyServerWidget *page; + searchRes *m_listpop; + + bool m_autoclose; + QString expattr; + + KGpgSearchResultModel *m_resultmodel; + QSortFilterProxyModel m_filtermodel; + + KeyListProxyModel *m_itemmodel; +}; + +#endif // KEYSERVERS_H diff --git a/kgpg/keysmanager.cpp b/kgpg/keysmanager.cpp new file mode 100644 index 00000000..f27cedd6 --- /dev/null +++ b/kgpg/keysmanager.cpp @@ -0,0 +1,2861 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2009,2010,2011,2012,2013 + * Rolf Eike Beer + * Copyright (C) 2011 Luis Ángel Fernández Fernández + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "keysmanager.h" + +#include "caff.h" +#include "core/images.h" +#include "core/kgpgkey.h" +#include "detailedconsole.h" +#include "groupedit.h" +#include "keyadaptor.h" +#include "keyexport.h" +#include "keyinfodialog.h" +#include "keyservers.h" +#include "keytreeview.h" +#include "kgpg.h" +#include "kgpgchangekey.h" +#include "kgpgkeygenerate.h" +#include "kgpgoptions.h" +#include "kgpgrevokewidget.h" +#include "kgpgsettings.h" +#include "newkey.h" +#include "selectpublickeydialog.h" +#include "selectsecretkey.h" +#include "sourceselect.h" +#include "editor/kgpgeditor.h" +#include "editor/kgpgtextedit.h" +#include "model/keylistproxymodel.h" +#include "transactions/kgpgaddphoto.h" +#include "transactions/kgpgadduid.h" +#include "transactions/kgpgdecrypt.h" +#include "transactions/kgpgdelkey.h" +#include "transactions/kgpgdelsign.h" +#include "transactions/kgpgdeluid.h" +#include "transactions/kgpgencrypt.h" +#include "transactions/kgpgexport.h" +#include "transactions/kgpggeneratekey.h" +#include "transactions/kgpggeneraterevoke.h" +#include "transactions/kgpgimport.h" +#include "transactions/kgpgkeyservergettransaction.h" +#include "transactions/kgpgprimaryuid.h" +#include "transactions/kgpgsignkey.h" +#include "transactions/kgpgsignuid.h" +#include "transactions/kgpgtransactionjob.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include TODO +#include +#include +#include +#include + +using namespace KgpgCore; + +KeysManager::KeysManager(QWidget *parent) + : KXmlGuiWindow(parent), + imodel(new KGpgItemModel(this)), + m_adduid(NULL), + m_genkey(NULL), + m_delkey(NULL), + terminalkey(NULL), + m_trayicon(NULL) +{ + new KeyAdaptor(this); + QDBusConnection::sessionBus().registerObject(QLatin1String( "/KeyInterface" ), this); + + setAttribute(Qt::WA_DeleteOnClose, false); + setWindowTitle(i18n("Key Management")); + + KStandardAction::quit(this, SLOT(quitApp()), actionCollection()); + actionCollection()->addAction(KStandardAction::Preferences, QLatin1String( "options_configure" ), this, SLOT(showOptions())); + + openEditor = actionCollection()->addAction(QLatin1String("kgpg_editor"), this, SLOT(slotOpenEditor())); + openEditor->setIcon(KIcon( QLatin1String( "accessories-text-editor" ))); + openEditor->setText(i18n("&Open Editor")); + + kserver = actionCollection()->addAction( QLatin1String("key_server"), this, SLOT(showKeyServer())); + kserver->setText( i18n("&Key Server Dialog") ); + kserver->setIcon( KIcon( QLatin1String( "network-server" )) ); + + goToDefaultKey = actionCollection()->addAction(QLatin1String("go_default_key"), this, SLOT(slotGotoDefaultKey())); + goToDefaultKey->setIcon(KIcon( QLatin1String( "go-home" ))); + goToDefaultKey->setText(i18n("&Go to Default Key")); + goToDefaultKey->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Home)); + + s_kgpgEditor = new KgpgEditor(this, imodel, Qt::Dialog); + s_kgpgEditor->setAttribute(Qt::WA_DeleteOnClose, false); + + // this must come after kserver, preferences, and openEditor are created + // because they are used to set up the tray icon context menu + readOptions(); + + if (showTipOfDay) + installEventFilter(this); + + KAction *action; + + action = actionCollection()->addAction(QLatin1String("help_tipofday"), this, SLOT(slotTip())); + action->setIcon( KIcon( QLatin1String( "help-hint" )) ); + action->setText( i18n("Tip of the &Day") ); + + action = actionCollection()->addAction(QLatin1String("gpg_man"), this, SLOT(slotManpage())); + action->setText( i18n("View GnuPG Manual") ); + action->setIcon( KIcon( QLatin1String( "help-contents" )) ); + + action = actionCollection()->addAction(QLatin1String("key_refresh"), this, SLOT(refreshkey())); + action->setIcon(KIcon( QLatin1String( "view-refresh" ))); + action->setText(i18n("&Refresh List")); + action->setShortcuts(KStandardShortcut::reload()); + + longId = actionCollection()->add(QLatin1String("show_long_keyid"), this, SLOT(slotShowLongId(bool))); + longId->setText(i18n("Show &Long Key Id")); + longId->setChecked(KGpgSettings::showLongKeyId()); + + QAction *infoKey = actionCollection()->addAction(QLatin1String("key_info"), this, SLOT(keyproperties())); + infoKey->setIcon(KIcon( QLatin1String( "document-properties-key" ))); + infoKey->setText(i18n("K&ey Properties")); + + QAction *openKeyUrl = actionCollection()->addAction(QLatin1String("key_url"), this, SLOT(slotOpenKeyUrl())); + openKeyUrl->setIcon(KIcon(QLatin1String("applications-internet"))); + openKeyUrl->setText(i18n("&Open Key URL")); + + editKey = actionCollection()->addAction(QLatin1String("key_edit"), this, SLOT(slotedit())); + editKey->setIcon(KIcon( QLatin1String( "utilities-terminal" ))); + editKey->setText(i18n("Edit Key in &Terminal")); + editKey->setShortcut(QKeySequence(Qt::ALT + Qt::Key_Return)); + + KAction *generateKey = actionCollection()->addAction(QLatin1String("key_gener"), this, SLOT(slotGenerateKey())); + generateKey->setIcon(KIcon( QLatin1String( "key-generate-pair" ))); + generateKey->setText(i18n("&Generate Key Pair...")); + generateKey->setShortcuts(KStandardShortcut::shortcut(KStandardShortcut::New)); + + exportPublicKey = actionCollection()->addAction(QLatin1String("key_export"), this, SLOT(slotexport())); + exportPublicKey->setIcon(KIcon( QLatin1String( "document-export-key" ))); + exportPublicKey->setShortcuts(KStandardShortcut::shortcut(KStandardShortcut::Copy)); + + KAction *importKey = actionCollection()->addAction(QLatin1String("key_import"), this, SLOT(slotPreImportKey())); + importKey->setIcon(KIcon( QLatin1String( "document-import-key" ))); + importKey->setText(i18n("&Import Key...")); + importKey->setShortcuts(KStandardShortcut::shortcut(KStandardShortcut::Paste)); + + m_sendEmail = actionCollection()->addAction(QLatin1String("send_mail"), this, SLOT(slotSendEmail())); + m_sendEmail->setIcon(KIcon(QLatin1String("mail-send"))); + m_sendEmail->setText(i18n("Send Ema&il")); + + QAction *newContact = actionCollection()->addAction(QLatin1String("add_kab"), this, SLOT(addToKAB())); + newContact->setIcon(KIcon( QLatin1String( "contact-new" ))); + newContact->setText(i18n("&Create New Contact in Address Book")); + + createGroup = actionCollection()->addAction(QLatin1String("create_group"), this, SLOT(createNewGroup())); + createGroup->setIcon(Images::group()); + + editCurrentGroup = actionCollection()->addAction(QLatin1String("edit_group"), this, SLOT(editGroup())); + editCurrentGroup->setText(i18n("&Edit Group...")); + + delGroup = actionCollection()->addAction(QLatin1String("delete_group"), this, SLOT(deleteGroup())); + delGroup->setText(i18n("&Delete Group")); + delGroup->setIcon(KIcon( QLatin1String( "edit-delete" ))); + + m_groupRename = actionCollection()->addAction(QLatin1String("rename_group"), this, SLOT(renameGroup())); + m_groupRename->setText(i18n("&Rename Group")); + m_groupRename->setIcon(KIcon( QLatin1String( "edit-rename" ))); + m_groupRename->setShortcut(QKeySequence(Qt::Key_F2)); + + deleteKey = actionCollection()->addAction(QLatin1String("key_delete"), this, SLOT(confirmdeletekey())); + deleteKey->setIcon(KIcon( QLatin1String( "edit-delete" ))); + deleteKey->setShortcut(QKeySequence(Qt::Key_Delete)); + + setDefaultKey = actionCollection()->addAction(QLatin1String("key_default"), this, SLOT(slotSetDefKey())); + setDefaultKey->setText(i18n("Set as De&fault Key")); + + QAction *addPhoto = actionCollection()->addAction(QLatin1String("add_photo"), this, SLOT(slotAddPhoto())); + addPhoto->setText(i18n("&Add Photo...")); + + QAction *addUid = actionCollection()->addAction(QLatin1String("add_uid"), this, SLOT(slotAddUid())); + addUid->setText(i18n("&Add User Id...")); + + QAction *exportSecretKey = actionCollection()->addAction(QLatin1String("key_sexport"), this, SLOT(slotexportsec())); + exportSecretKey->setText(i18n("Export Secret Key...")); + + QAction *deleteKeyPair = actionCollection()->addAction(QLatin1String("key_pdelete"), this, SLOT(deleteseckey())); + deleteKeyPair->setText(i18n("Delete Key Pair")); + deleteKeyPair->setIcon(KIcon( QLatin1String( "edit-delete" ))); + + m_revokeKey = actionCollection()->addAction(QLatin1String("key_revoke"), this, SLOT(revokeWidget())); + m_revokeKey->setText(i18n("Revoke Key...")); + + QAction *regeneratePublic = actionCollection()->addAction(QLatin1String("key_regener"), this, SLOT(slotregenerate())); + regeneratePublic->setText(i18n("&Regenerate Public Key")); + + delUid = actionCollection()->addAction(QLatin1String("del_uid"), this, SLOT(slotDelUid())); + delUid->setIcon(KIcon( QLatin1String( "edit-delete" ))); + + setPrimUid = actionCollection()->addAction(QLatin1String("prim_uid"), this, SLOT(slotPrimUid())); + setPrimUid->setText(i18n("Set User Id as &Primary")); + + QAction *openPhoto = actionCollection()->addAction(QLatin1String("key_photo"), this, SLOT(slotShowPhoto())); + openPhoto->setIcon(KIcon( QLatin1String( "image-x-generic" ))); + openPhoto->setText(i18n("&Open Photo")); + + QAction *deletePhoto = actionCollection()->addAction(QLatin1String("delete_photo"), this, SLOT(slotDeletePhoto())); + deletePhoto->setIcon(KIcon( QLatin1String( "edit-delete" ))); + deletePhoto->setText(i18n("&Delete Photo")); + + delSignKey = actionCollection()->addAction(QLatin1String("key_delsign"), this, SLOT(delsignkey())); + delSignKey->setIcon(KIcon( QLatin1String( "edit-delete" ))); + delSignKey->setEnabled(false); + + importAllSignKeys = actionCollection()->addAction(QLatin1String("key_importallsign"), this, SLOT(importallsignkey())); + importAllSignKeys->setIcon(KIcon( QLatin1String( "document-import" ))); + importAllSignKeys->setText(i18n("Import &Missing Signatures From Keyserver")); + + refreshKey = actionCollection()->addAction(QLatin1String("key_server_refresh"), this, SLOT(refreshKeyFromServer())); + refreshKey->setIcon(KIcon( QLatin1String( "view-refresh" ))); + + signKey = actionCollection()->addAction(QLatin1String("key_sign"), this, SLOT(signkey())); + signKey->setIcon(KIcon( QLatin1String( "document-sign-key" ))); + + signUid = actionCollection()->addAction(QLatin1String("key_sign_uid"), this, SLOT(signuid())); + signUid->setIcon(KIcon( QLatin1String( "document-sign-key" ))); + + signMailUid = actionCollection()->addAction(QLatin1String("key_sign_mail_uid"), this, SLOT(caff())); + signMailUid->setIcon(KIcon( QLatin1String( "document-sign-key" ))); + + importSignatureKey = actionCollection()->addAction(QLatin1String("key_importsign"), this, SLOT(preimportsignkey())); + importSignatureKey->setIcon(KIcon( QLatin1String( "document-import-key" ))); + + sTrust = actionCollection()->add(QLatin1String("show_trust"), this, SLOT(slotShowTrust())); + sTrust->setText(i18n("Trust")); + + sSize = actionCollection()->add(QLatin1String("show_size"), this, SLOT(slotShowSize())); + sSize->setText(i18n("Size")); + + sCreat = actionCollection()->add(QLatin1String("show_creat"), this, SLOT(slotShowCreation())); + sCreat->setText(i18n("Creation")); + + sExpi = actionCollection()->add(QLatin1String("show_expi"), this, SLOT(slotShowExpiration())); + sExpi->setText(i18n("Expiration")); + + photoProps = actionCollection()->add(QLatin1String( "photo_settings" )); + photoProps->setIcon(KIcon( QLatin1String( "image-x-generic" ))); + photoProps->setText(i18n("&Photo ID's")); + + // Keep the list in kgpg.kcfg in sync with this one! + QStringList list; + list.append(i18n("Disable")); + list.append(i18nc("small picture", "Small")); + list.append(i18nc("medium picture", "Medium")); + list.append(i18nc("large picture", "Large")); + photoProps->setItems(list); + + trustProps = actionCollection()->add(QLatin1String( "trust_filter_settings" )); + trustProps->setText(i18n("Minimum &Trust")); + + QStringList tlist; + tlist.append(i18nc("no filter: show all keys", "&None")); + tlist.append(i18nc("show only active keys", "&Active")); + tlist.append(i18nc("show only keys with at least marginal trust", "&Marginal")); + tlist.append(i18nc("show only keys with at least full trust", "&Full")); + tlist.append(i18nc("show only ultimately trusted keys", "&Ultimate")); + + trustProps->setItems(tlist); + + iproxy = new KeyListProxyModel(this); + iproxy->setKeyModel(imodel); + connect(this, SIGNAL(readAgainOptions()), iproxy, SLOT(settingsChanged())); + + iview = new KeyTreeView(this, iproxy); + connect(iview, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(defaultAction(QModelIndex))); + connect(iview, SIGNAL(importDrop(KUrl::List)), SLOT(slotImport(KUrl::List))); + iview->setSelectionMode(QAbstractItemView::ExtendedSelection); + setCentralWidget(iview); + iview->resizeColumnsToContents(); + iview->setAlternatingRowColors(true); + iview->setSortingEnabled(true); + connect(iview, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotMenu(QPoint))); + iview->setContextMenuPolicy(Qt::CustomContextMenu); + connect(iview->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(checkList())); + + connect (iview, SIGNAL(returnPressed()), SLOT(slotDefaultAction())); + + hPublic = actionCollection()->add(QLatin1String("show_secret"), iproxy, SLOT(setOnlySecret(bool))); + hPublic->setIcon(KIcon( QLatin1String( "view-key-secret" ))); + hPublic->setText(i18n("&Show Only Secret Keys")); + hPublic->setChecked(KGpgSettings::showSecret()); + + int psize = KGpgSettings::photoProperties(); + photoProps->setCurrentItem(psize); + slotSetPhotoSize(psize); + psize = KGpgSettings::trustLevel(); + trustProps->setCurrentItem(psize); + slotSetTrustFilter(psize); + slotShowLongId(KGpgSettings::showLongKeyId()); + + m_popuppub = new KMenu(this); + m_popuppub->addAction(exportPublicKey); + m_popuppub->addAction(m_sendEmail); + m_popuppub->addAction(signMailUid); + m_popuppub->addAction(signKey); + m_popuppub->addAction(signUid); + m_popuppub->addAction(deleteKey); + m_popuppub->addAction(infoKey); + m_popuppub->addAction(openKeyUrl); + m_popuppub->addAction(editKey); + m_popuppub->addAction(refreshKey); + m_popuppub->addAction(createGroup); + m_popuppub->addSeparator(); + m_popuppub->addAction(importAllSignKeys); + + m_popupsec = new KMenu(this); + m_popupsec->addAction(exportPublicKey); + m_popupsec->addAction(m_sendEmail); + m_popupsec->addAction(signKey); + m_popupsec->addAction(signUid); + m_popupsec->addAction(signMailUid); + m_popupsec->addAction(infoKey); + m_popupsec->addAction(openKeyUrl); + m_popupsec->addAction(editKey); + m_popupsec->addAction(refreshKey); + m_popupsec->addAction(setDefaultKey); + m_popupsec->addSeparator(); + m_popupsec->addAction(importAllSignKeys); + m_popupsec->addSeparator(); + m_popupsec->addAction(addPhoto); + m_popupsec->addAction(addUid); + m_popupsec->addAction(exportSecretKey); + m_popupsec->addAction(deleteKeyPair); + + m_popupgroup = new KMenu(this); + m_popupgroup->addAction(editCurrentGroup); + m_popupgroup->addAction(m_groupRename); + m_popupgroup->addAction(delGroup); + m_popupgroup->addAction(refreshKey); + + m_popupout = new KMenu(this); + m_popupout->addAction(importKey); + + m_popupsig = new KMenu(); + m_popupsig->addAction(importSignatureKey); + m_popupsig->addAction(delSignKey); + + m_popupphoto = new KMenu(this); + m_popupphoto->addAction(openPhoto); + m_popupphoto->addAction(signUid); + m_popupphoto->addAction(signMailUid); + m_popupphoto->addAction(deletePhoto); + + m_popupuid = new KMenu(this); + m_popupuid->addAction(m_sendEmail); + m_popupuid->addAction(signMailUid); + m_popupuid->addAction(signUid); + m_popupuid->addAction(delUid); + m_popupuid->addAction(setPrimUid); + + m_popuporphan = new KMenu(this); + m_popuporphan->addAction(regeneratePublic); + m_popuporphan->addAction(deleteKeyPair); + + exportPublicKey->setEnabled(false); + + KConfigGroup cg = KConfigGroup(KGlobal::config().data(), "KeyView"); + iview->restoreLayout(cg); + + connect(photoProps, SIGNAL(triggered(int)), this, SLOT(slotSetPhotoSize(int))); + connect(trustProps, SIGNAL(triggered(int)), this, SLOT(slotSetTrustFilter(int))); + + QLabel *searchLabel = new QLabel(i18n("Search:"), this); + m_listviewsearch = new KLineEdit(this); + m_listviewsearch->setClearButtonShown(true); + + QWidget *searchWidget = new QWidget(this); + QHBoxLayout *searchLayout = new QHBoxLayout(searchWidget); + searchLayout->setMargin(0); + searchLayout->addWidget(searchLabel); + searchLayout->addWidget(m_listviewsearch); + searchLayout->addStretch(); + + KAction *searchLineAction = new KAction(i18nc("Name of the action that is a search line, shown for example in the toolbar configuration dialog", + "Search Line"), this); + actionCollection()->addAction(QLatin1String( "search_line" ), searchLineAction); + searchLineAction->setDefaultWidget(searchWidget); + + action = actionCollection()->addAction(QLatin1String("search_focus"), m_listviewsearch, SLOT(setFocus())); + action->setText(i18nc("Name of the action that gives the focus to the search line", "Focus Search Line")); + action->setShortcut(QKeySequence(Qt::Key_F6)); + connect(m_listviewsearch, SIGNAL(textChanged(QString)), iproxy, SLOT(setFilterFixedString(QString))); + + setActionDescriptions(1); + + // get all keys data + setupGUI(KXmlGuiWindow::Create | Save | ToolBar | StatusBar | Keys, QLatin1String( "keysmanager.rc" )); + + sTrust->setChecked(KGpgSettings::showTrust()); + iview->setColumnHidden(2, !KGpgSettings::showTrust()); + sSize->setChecked(KGpgSettings::showSize()); + iview->setColumnHidden(3, !KGpgSettings::showSize()); + sCreat->setChecked(KGpgSettings::showCreat()); + iview->setColumnHidden(4, !KGpgSettings::showCreat()); + sExpi->setChecked(KGpgSettings::showExpi()); + iview->setColumnHidden(5, !KGpgSettings::showExpi()); + iproxy->setOnlySecret(KGpgSettings::showSecret()); + + KStatusBar *statusbar = statusBar(); + statusbar->insertPermanentFixedItem(KGpgItemModel::statusCountMessageString(9999, 999), 0); + statusbar->changeItem(QString(), 0); + + cg = KConfigGroup(KGlobal::config().data(), "MainWindow"); + setAutoSaveSettings(cg, true); + applyMainWindowSettings(cg); + + connect(this, SIGNAL(fontChanged(QFont)), s_kgpgEditor, SLOT(slotSetFont(QFont))); + + m_netnote = Solid::Networking::notifier(); + connect(m_netnote, SIGNAL(shouldConnect()), SLOT(slotNetworkUp())); + connect(m_netnote, SIGNAL(shouldDisconnect()), SLOT(slotNetworkDown())); + + toggleNetworkActions(Solid::Networking::status() == Solid::Networking::Unknown || Solid::Networking::status() == Solid::Networking::Connected); + importSignatureKey->setEnabled(false); + + stateChanged("empty_list"); + + QMetaObject::invokeMethod(this, "refreshkey", Qt::QueuedConnection); +} + +KeysManager::~KeysManager() +{ +} + +void KeysManager::slotGenerateKey() +{ + if (m_genkey) { + KMessageBox::error(this, + i18n("Another key generation operation is still in progress.\nPlease wait a moment until this operation is complete."), + i18n("Generating new key pair")); + return; + } + + QPointer kg = new KgpgKeyGenerate(this); + if (kg->exec() == QDialog::Accepted) { + if (!kg->isExpertMode()) { + KGpgGenerateKey *genkey = new KGpgGenerateKey(this, kg->name(), kg->email(), + kg->comment(), kg->algo(), kg->size(), kg->days(), kg->expiration(), + kg->caps()); + + m_genkey = new KGpgTransactionJob(genkey); + connect(m_genkey, SIGNAL(result(KJob*)), SLOT(slotGenerateKeyDone(KJob*))); + + KIO::getJobTracker()->registerJob(m_genkey); + m_genkey->start(); + QApplication::setOverrideCursor(Qt::BusyCursor); + } else { + KConfigGroup config(KGlobal::config(), "General"); + + QString terminalApp(config.readPathEntry("TerminalApplication", QLatin1String( "konsole" ))); + QStringList args; + args << QLatin1String( "-e" ) + << KGpgSettings::gpgBinaryPath() + << QLatin1String("--gen-key") + << QLatin1String("--expert"); + + QProcess *genKeyProc = new QProcess(this); + genKeyProc->start(terminalApp, args); + if (!genKeyProc->waitForStarted(-1)) { + KMessageBox::error(this, i18n("Generating new key pair"), + i18n("Can not start \"konsole\" application for expert mode.")); + } else { + genKeyProc->waitForFinished(-1); + refreshkey(); + } + } + } + + delete kg; +} + +void KeysManager::showKeyManager() +{ + show(); +} + +void KeysManager::slotOpenEditor() +{ + KgpgEditor *kgpgtxtedit = new KgpgEditor(this, imodel, Qt::Window); + + connect(this, SIGNAL(fontChanged(QFont)), kgpgtxtedit, SLOT(slotSetFont(QFont))); + + kgpgtxtedit->show(); +} + +void KeysManager::changeMessage(const QString &msg, const bool keep) +{ + int timeout = keep ? 0 : 10000; + + statusBar()->showMessage(msg, timeout); +} + +void KeysManager::updateStatusCounter() +{ + statusBar()->changeItem(imodel->statusCountMessage(), 0); +} + +void KeysManager::slotGenerateKeyDone(KJob *job) +{ + changeMessage(i18nc("Application ready for user input", "Ready")); + QApplication::restoreOverrideCursor(); + + KGpgTransactionJob *tjob = qobject_cast(job); + + const KGpgGenerateKey * const genkey = qobject_cast(tjob->getTransaction()); + int res = tjob->getResultCode(); + + const QString infomessage(i18n("Generating new key pair")); + + switch (res) { + case KGpgTransaction::TS_BAD_PASSPHRASE: + KMessageBox::error(this, i18n("Bad passphrase. Cannot generate a new key pair."), infomessage); + break; + case KGpgTransaction::TS_USER_ABORTED: + KMessageBox::error(this, i18n("Aborted by the user. Cannot generate a new key pair."), infomessage); + break; + case KGpgTransaction::TS_INVALID_EMAIL: + KMessageBox::error(this, i18n("The email address is not valid. Cannot generate a new key pair."), infomessage); + break; + case KGpgGenerateKey::TS_INVALID_NAME: + KMessageBox::error(this, i18n("The name is not accepted by gpg. Cannot generate a new key pair."), infomessage); + break; + case KGpgTransaction::TS_OK: { + updateStatusCounter(); + + QPointer keyCreated = new KDialog(this); + keyCreated->setCaption(i18n("New Key Pair Created")); + keyCreated->setButtons(KDialog::Ok); + keyCreated->setDefaultButton(KDialog::Ok); + keyCreated->setModal(true); + + newKey *page = new newKey(keyCreated); + page->TLname->setText(QLatin1String( "" ) + genkey->getName() + QLatin1String( "" )); + + const QString email(genkey->getEmail()); + page->TLemail->setText(QLatin1String( "" ) + email + QLatin1String( "" )); + + QString revurl; + const QString gpgPath(KGpgSettings::gpgConfigPath()); + if (!gpgPath.isEmpty()) + revurl = KUrl::fromPath(gpgPath).directory(KUrl::AppendTrailingSlash); + else + revurl = QDir::homePath() + QLatin1Char( '/' ); + + if (!email.isEmpty()) + page->kURLRequester1->setUrl(QString(revurl + email.section(QLatin1Char( '@' ), 0, 0) + QLatin1String( ".revoke" ))); + else + page->kURLRequester1->setUrl(QString(revurl + genkey->getName().section(QLatin1Char(' '), 0, 0) + QLatin1String(".revoke"))); + + const QString fingerprint(genkey->getFingerprint()); + page->TLid->setText(QLatin1String( "" ) + fingerprint.right(8) + QLatin1String( "" )); + page->LEfinger->setText(fingerprint); + page->CBdefault->setChecked(true); + page->show(); + keyCreated->setMainWidget(page); + + keyCreated->exec(); + if (keyCreated.isNull()) + return; + + imodel->refreshKey(fingerprint); + KGpgKeyNode *knode = imodel->getRootNode()->findKey(fingerprint); + if (page->CBdefault->isChecked()) + imodel->setDefaultKey(knode); + + iview->selectNode(knode); + + if (page->CBsave->isChecked() || page->CBprint->isChecked()) { + KUrl revurl; + if (page->CBsave->isChecked()) + revurl = page->kURLRequester1->url(); + + KGpgGenerateRevoke *genRev = new KGpgGenerateRevoke(this, fingerprint, revurl, + 0, i18n("backup copy")); + + connect(genRev, SIGNAL(done(int)), SLOT(slotRevokeGenerated(int))); + + if (page->CBprint->isChecked()) + connect(genRev, SIGNAL(revokeCertificate(QString)), SLOT(doPrint(QString))); + + genRev->start(); + } + delete keyCreated; + break; + } + default: + KMessageBox::detailedError(this, + i18n("gpg process did not finish. Cannot generate a new key pair."), + genkey->gpgErrorMessage(), infomessage); + } + + m_genkey = NULL; +} + +void KeysManager::slotShowTrust() +{ + bool b = !sTrust->isChecked(); + iview->setColumnHidden(KEYCOLUMN_TRUST, b); + if (!b && (iview->columnWidth(KEYCOLUMN_TRUST) == 0)) + iview->resizeColumnToContents(KEYCOLUMN_TRUST); +} + +void KeysManager::slotShowExpiration() +{ + bool b = !sExpi->isChecked(); + iview->setColumnHidden(KEYCOLUMN_EXPIR, b); + if (!b && (iview->columnWidth(KEYCOLUMN_EXPIR) == 0)) + iview->resizeColumnToContents(KEYCOLUMN_EXPIR); +} + +void KeysManager::slotShowSize() +{ + bool b = !sSize->isChecked(); + iview->setColumnHidden(KEYCOLUMN_SIZE, b); + if (!b && (iview->columnWidth(KEYCOLUMN_SIZE) == 0)) + iview->resizeColumnToContents(KEYCOLUMN_SIZE); +} + +void KeysManager::slotShowCreation() +{ + bool b = !sCreat->isChecked(); + iview->setColumnHidden(KEYCOLUMN_CREAT, b); + if (!b && (iview->columnWidth(KEYCOLUMN_CREAT) == 0)) + iview->resizeColumnToContents(KEYCOLUMN_CREAT); +} + +void KeysManager::slotShowLongId(bool b) +{ + iproxy->setIdLength(b ? 16 : 8); +} + +void KeysManager::slotSetTrustFilter(int i) +{ + KgpgCore::KgpgKeyTrustFlag t; + + Q_ASSERT((i >= 0) && (i < 5)); + switch (i) { + case 0: + t = TRUST_MINIMUM; + break; + case 1: + t = TRUST_UNDEFINED; + break; + case 2: + t = TRUST_MARGINAL; + break; + case 3: + t = TRUST_FULL; + break; + default: + t = TRUST_ULTIMATE; + } + + iproxy->setTrustFilter(t); +} + +bool KeysManager::eventFilter(QObject *, QEvent *e) +{ + if ((e->type() == QEvent::Show) && (showTipOfDay)) { + KTipDialog::showTip(this, QLatin1String("kgpg/tips"), false); + showTipOfDay = false; + } + + return false; +} + +void KeysManager::slotGotoDefaultKey() +{ + iview->selectNode(imodel->getRootNode()->findKey(KGpgSettings::defaultKey())); +} + +void KeysManager::refreshKeyFromServer() +{ + QList keysList(iview->selectedNodes()); + if (keysList.isEmpty()) + return; + + QStringList keyIDS; + + foreach (KGpgNode *item, keysList) { + if (item->getType() == ITYPE_GROUP) + { + for (int j = 0; j < item->getChildCount(); j++) + keyIDS << item->getChild(j)->getId(); + + continue; + } + + if (item->getType() & ITYPE_PAIR) { + keyIDS << item->getId(); + } else { + KMessageBox::sorry(this, i18n("You can only refresh primary keys. Please check your selection.")); + return; + } + } + + QString proxy; + if (KGpgSettings::useProxy()) + proxy = QLatin1String( qgetenv("http_proxy") ); + + KGpgRefreshKeys *t = new KGpgRefreshKeys(this, KGpgSettings::keyServers().first(), keyIDS, true, proxy); + connect(t, SIGNAL(done(int)), SLOT(slotKeyRefreshDone(int))); + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + t->start(); +} + +void KeysManager::slotKeyRefreshDone(int result) +{ + KGpgRefreshKeys *t = qobject_cast(sender()); + Q_ASSERT(t != NULL); + + if (result == KGpgTransaction::TS_USER_ABORTED) { + t->deleteLater(); + QApplication::restoreOverrideCursor(); + return; + } + + const QStringList log(t->getLog()); + const QStringList keys = KGpgImport::getImportedIds(log, 0xffff); + const QStringList message(KGpgImport::getImportMessage(log)); + + t->deleteLater(); + + if (!keys.empty()) + imodel->refreshKeys(keys); + + QApplication::restoreOverrideCursor(); + (void) new KgpgDetailedInfo(this, message.join(QLatin1String("\n")), log.join(QLatin1String("
")), + KGpgImport::getDetailedImportMessage(log, imodel).split(QLatin1Char('\n'))); +} + +void KeysManager::slotDelUid() +{ + KGpgUidNode *nd = iview->selectedNode()->toUidNode(); + + KGpgDelUid *deluid = new KGpgDelUid(this, nd); + + connect(deluid, SIGNAL(done(int)), SLOT(slotDelUidDone(int))); + deluid->start(); +} + +void KeysManager::slotDelUidDone(int result) +{ + KGpgDelUid * const deluid = qobject_cast(sender()); + Q_ASSERT(deluid != NULL); + + sender()->deleteLater(); + if (result == KGpgTransaction::TS_OK) + imodel->refreshKey(deluid->getKeyId()); + // FIXME: do something useful with result if it is a failure +} + +void KeysManager::slotPrimUid() +{ + KGpgPrimaryUid *puid = new KGpgPrimaryUid(this, iview->selectedNode()->toUidNode()); + + connect(puid, SIGNAL(done(int)), SLOT(slotPrimUidDone(int))); + + puid->start(); +} + +void KeysManager::slotPrimUidDone(int result) +{ + const QString kid(qobject_cast(sender())->getKeyId()); + + sender()->deleteLater(); + + if (result == KGpgTransaction::TS_OK) + imodel->refreshKey(kid); + // FIXME: some error reporting +} + +void KeysManager::slotregenerate() +{ + QString regID = iview->selectedNode()->getId(); + KProcess *p1, *p2, *p3; + + p1 = new KProcess(this); + *p1 << KGpgSettings::gpgBinaryPath() + << QLatin1String("--no-secmem-warning") + << QLatin1String("--export-secret-key") + << regID; + p1->setOutputChannelMode(KProcess::OnlyStdoutChannel); + + p2 = new KProcess(this); + *p2 << QLatin1String("gpgsplit") + << QLatin1String("--no-split") + << QLatin1String("--secret-to-public"); + p2->setOutputChannelMode(KProcess::OnlyStdoutChannel); + + p3 = new KProcess(this); + *p3 << KGpgSettings::gpgBinaryPath() + << QLatin1String("--import"); + + p1->setStandardOutputProcess(p2); + p2->setStandardOutputProcess(p3); + + p1->start(); + p2->start(); + p3->start(); + + p1->waitForFinished(); + p2->waitForFinished(); + p3->waitForFinished(); + + delete p1; + delete p2; + delete p3; + + imodel->refreshKey(regID); +} + +void KeysManager::slotAddUid() +{ + if (m_adduid) { + KMessageBox::error(this, i18n("Another operation is still in progress.\nPlease wait a moment until this operation is complete."), + i18n("Add New User Id")); + return; + } + + addUidWidget = new KDialog(this ); + addUidWidget->setCaption( i18n("Add New User Id") ); + addUidWidget->setButtons( KDialog::Ok | KDialog::Cancel ); + addUidWidget->setDefaultButton( KDialog::Ok ); + addUidWidget->setModal( true ); + addUidWidget->enableButtonOk(false); + AddUid *keyUid = new AddUid(addUidWidget); + addUidWidget->setMainWidget(keyUid); + //keyUid->setMinimumSize(keyUid->sizeHint()); + keyUid->setMinimumWidth(300); + + connect(keyUid->kLineEdit1, SIGNAL(textChanged(QString)), this, SLOT(slotAddUidEnable(QString))); + if (addUidWidget->exec() != QDialog::Accepted) + return; + + m_adduid = new KGpgAddUid(this, iview->selectedNode()->getId(), keyUid->kLineEdit1->text(), + keyUid->kLineEdit2->text(), keyUid->kLineEdit3->text()); + connect(m_adduid, SIGNAL(done(int)), SLOT(slotAddUidFin(int))); + m_adduid->start(); +} + +void KeysManager::slotAddUidFin(int res) +{ + // TODO error reporting + if (res == 0) + imodel->refreshKey(m_adduid->getKeyid()); + m_adduid->deleteLater(); + m_adduid = NULL; +} + +void KeysManager::slotAddUidEnable(const QString & name) +{ + addUidWidget->enableButtonOk(name.length() > 4); +} + +void KeysManager::slotAddPhoto() +{ + QString mess = i18n("The image must be a JPEG file. Remember that the image is stored within your public key, so " + "if you use a very large picture, your key will become very large as well. The size should not exceed 6 KiB. " + "An image size of around 240x288 is a good size to use."); + + if (KMessageBox::warningContinueCancel(0, mess) != KMessageBox::Continue) + return; + + QString imagepath = KFileDialog::getOpenFileName(KUrl(), QLatin1String( "image/jpeg" ), 0); + if (imagepath.isEmpty()) + return; + + KGpgAddPhoto *addphoto = new KGpgAddPhoto(this, iview->selectedNode()->getId(), imagepath); + connect(addphoto, SIGNAL(done(int)), SLOT(slotAddPhotoFinished(int))); + addphoto->start(); +} + +void KeysManager::slotAddPhotoFinished(int res) +{ + sender()->deleteLater(); + + // TODO : add res == 3 (bad passphrase) + + if (res == 0) + slotUpdatePhoto(); +} + +void KeysManager::slotDeletePhoto() +{ + KGpgNode *nd = iview->selectedNode(); + KGpgUatNode *und = nd->toUatNode(); + KGpgKeyNode *parent = und->getParentKeyNode(); + + QString mess = i18n("Are you sure you want to delete Photo id %1
from key %2 <%3>?
", + und->getId(), parent->getName(), parent->getEmail()); + + KGpgDelUid *deluid = new KGpgDelUid(this, und); + connect(deluid, SIGNAL(done(int)), SLOT(slotDelPhotoFinished(int))); + + deluid->start(); +} + +void KeysManager::slotDelPhotoFinished(int res) +{ + sender()->deleteLater(); + + // TODO : add res == 3 (bad passphrase) + + if (res == 0) { + KGpgNode *nd = iview->selectedNode(); + imodel->refreshKey(nd->getParentKeyNode()->toKeyNode()); + } +} + +void KeysManager::slotUpdatePhoto() +{ + KGpgNode *nd = iview->selectedNode(); + imodel->refreshKey(nd->toKeyNode()); +} + +void KeysManager::slotSetPhotoSize(int size) +{ + switch(size) { + case 1: + iproxy->setPreviewSize(22); + break; + case 2: + iproxy->setPreviewSize(42); + break; + case 3: + iproxy->setPreviewSize(65); + break; + default: + iproxy->setPreviewSize(0); + break; + } +} + +void KeysManager::addToKAB() +{ + KGpgNode *nd = iview->selectedNode(); + if (nd == NULL) + return; + + Akonadi::ContactSearchJob * const job = new Akonadi::ContactSearchJob(); + job->setLimit(1); + job->setQuery(Akonadi::ContactSearchJob::Email, nd->getEmail()); + connect(job, SIGNAL(result(KJob*)), this, SLOT(slotAddressbookSearchResult(KJob*))); + + m_addIds[job] = nd; +} + +void KeysManager::slotAddressbookSearchResult(KJob *job) +{ + KGpgNode * const nd = m_addIds.value(job, 0); + + if (!nd) + return; + + Akonadi::ContactSearchJob *searchJob = qobject_cast(job); + Q_ASSERT(searchJob); + const KABC::Addressee::List addresseeList = searchJob->contacts(); + + m_addIds.take(job); + + Akonadi::ContactEditorDialog *dlg; +// KABC::Key key; TODO + if (!addresseeList.isEmpty()) { + dlg = new Akonadi::ContactEditorDialog(Akonadi::ContactEditorDialog::EditMode, this); + dlg->setContact(searchJob->items().first()); + } else { + KABC::Addressee addressee; + addressee.setNameFromString(nd->getName()); + addressee.setEmails(QStringList(nd->getEmail())); + dlg = new Akonadi::ContactEditorDialog(Akonadi::ContactEditorDialog::CreateMode, this); + dlg->editor()->setContactTemplate(addressee); + } + + connect(dlg, SIGNAL(finished()), dlg, SLOT(deleteLater())); + dlg->show(); +} + +void KeysManager::slotManpage() +{ + KToolInvocation::startServiceByDesktopName(QLatin1String("khelpcenter"), + QLatin1String("man:/gpg"), 0, 0, 0, QByteArray(), true); +} + +void KeysManager::slotTip() +{ + KTipDialog::showTip(this, QLatin1String("kgpg/tips"), true); +} + +void KeysManager::showKeyServer() +{ + QPointer ks = new KeyServer(this, imodel); + connect(ks, SIGNAL(importFinished(QStringList)), imodel, SLOT(refreshKeys(QStringList))); + ks->exec(); + + delete ks; + refreshkey(); +} + +void KeysManager::checkList() +{ + QList exportList = iview->selectedNodes(); + + switch (exportList.count()) { + case 0: + stateChanged("empty_list"); + return; + case 1: + if (exportList.at(0)->getType() == ITYPE_GROUP) { + stateChanged(QLatin1String( "group_selected" )); + } else { + stateChanged(QLatin1String( "single_selected" )); + m_revokeKey->setEnabled(exportList.at(0)->getType() == ITYPE_PAIR); + if (terminalkey) + editKey->setEnabled(false); + m_sendEmail->setEnabled(!exportList[0]->getEmail().isEmpty()); + setDefaultKey->setEnabled(!imodel->isDefaultKey(exportList[0])); + } + break; + default: + stateChanged(QLatin1String( "multi_selected" )); + } + if (!m_online) + refreshKey->setEnabled(false); + + switch (exportList.at(0)->getType()) { + case ITYPE_PUBLIC: + changeMessage(i18n("Public Key")); + break; + case ITYPE_SUB: + changeMessage(i18n("Sub Key")); + break; + case ITYPE_PAIR: + changeMessage(i18n("Secret Key Pair")); + break; + case ITYPE_GROUP: + changeMessage(i18n("Key Group")); + break; + case ITYPE_SIGN: + changeMessage(i18n("Signature")); + break; + case ITYPE_UID: + changeMessage(i18n("User ID")); + break; + case ITYPE_REVSIGN: + changeMessage(i18n("Revocation Signature")); + break; + case ITYPE_UAT: + changeMessage(i18n("Photo ID")); + break; + case ITYPE_SECRET: + changeMessage(i18n("Orphaned Secret Key")); + break; + case ITYPE_GPUBLIC: + case ITYPE_GSECRET: + case ITYPE_GPAIR: + changeMessage(i18n("Group member")); + break; + default: + kDebug(2100) << "Oops, unmatched type value" << exportList.at(0)->getType(); + } +} + +void KeysManager::quitApp() +{ + // close window + saveToggleOpts(); + qApp->quit(); +} + +void KeysManager::saveToggleOpts(void) +{ + KConfigGroup cg = KConfigGroup(KGlobal::config().data(), "KeyView"); + iview->saveLayout(cg); + KGpgSettings::setPhotoProperties(photoProps->currentItem()); + KGpgSettings::setShowTrust(sTrust->isChecked()); + KGpgSettings::setShowExpi(sExpi->isChecked()); + KGpgSettings::setShowCreat(sCreat->isChecked()); + KGpgSettings::setShowSize(sSize->isChecked()); + KGpgSettings::setTrustLevel(trustProps->currentItem()); + KGpgSettings::setShowSecret(hPublic->isChecked()); + KGpgSettings::setShowLongKeyId(longId->isChecked()); + KGpgSettings::self()->writeConfig(); +} + +void KeysManager::readOptions() +{ + m_clipboardmode = QClipboard::Clipboard; + if (KGpgSettings::useMouseSelection() && (kapp->clipboard()->supportsSelection())) + m_clipboardmode = QClipboard::Selection; + + if (imodel != NULL) + updateStatusCounter(); + + showTipOfDay = KGpgSettings::showTipOfDay(); + + if (KGpgSettings::showSystray()) { + setupTrayIcon(); + } else { + delete m_trayicon; + m_trayicon = NULL; + } +} + +void KeysManager::showOptions() +{ + if (KConfigDialog::showDialog(QLatin1String( "settings" ))) + return; + + QPointer optionsDialog = new kgpgOptions(this, imodel); + connect(optionsDialog, SIGNAL(settingsUpdated()), SLOT(readAllOptions())); + connect(optionsDialog, SIGNAL(homeChanged()), imodel, SLOT(refreshKeys())); + connect(optionsDialog, SIGNAL(homeChanged()), imodel, SLOT(refreshGroups())); + connect(optionsDialog, SIGNAL(refreshTrust(KgpgCore::KgpgKeyTrust,QColor)), imodel, SLOT(refreshTrust(KgpgCore::KgpgKeyTrust,QColor))); + connect(optionsDialog, SIGNAL(changeFont(QFont)), SIGNAL(fontChanged(QFont))); + optionsDialog->exec(); + delete optionsDialog; + + s_kgpgEditor->m_recentfiles->setMaxItems(KGpgSettings::recentFiles()); +} + +void KeysManager::readAllOptions() +{ + readOptions(); + emit readAgainOptions(); +} + +void KeysManager::slotSetDefKey() +{ + setDefaultKeyNode(iview->selectedNode()->toKeyNode()); +} + +void KeysManager::slotSetDefaultKey(const QString &newID) +{ + KGpgKeyNode *ndef = imodel->getRootNode()->findKey(newID); + + if (ndef == NULL) { + KGpgSettings::setDefaultKey(newID); + KGpgSettings::self()->writeConfig(); + return; + } + + setDefaultKeyNode(ndef); +} + +void KeysManager::setDefaultKeyNode(KGpgKeyNode *key) +{ + const QString &newID(key->getId()); + + if (newID == KGpgSettings::defaultKey()) + return; + + KGpgSettings::setDefaultKey(newID); + KGpgSettings::self()->writeConfig(); + + imodel->setDefaultKey(key); +} + +void +KeysManager::setActionDescriptions(int cnt) +{ + signUid->setText(i18np("&Sign User ID ...", "&Sign User IDs ...", cnt)); + signMailUid->setText(i18np("Sign and &Mail User ID ...", "Sign and &Mail User IDs ...", cnt)); + exportPublicKey->setText(i18np("E&xport Public Key...", "E&xport Public Keys...", cnt)); + refreshKey->setText(i18np("&Refresh Key From Keyserver", "&Refresh Keys From Keyserver", cnt)); + createGroup->setText(i18np("&Create Group with Selected Key...", "&Create Group with Selected Keys...", cnt)); + signKey->setText(i18np("&Sign Key...", "&Sign Keys...", cnt)); + delUid->setText(i18np("&Delete User ID", "&Delete User IDs", cnt)); + delSignKey->setText(i18np("Delete Sign&ature", "Delete Sign&atures", cnt)); + importSignatureKey->setText(i18np("Import Key From Keyserver", "Import Keys From Keyserver", cnt)); + deleteKey->setText(i18np("&Delete Key", "&Delete Keys", cnt)); +} + +void +KeysManager::slotMenu(const QPoint &pos) +{ + QPoint globpos = iview->mapToGlobal(pos); + bool sametype; + KgpgItemType itype; + QList ndlist(iview->selectedNodes(&sametype, &itype)); + bool unksig = false; + QSet l; + int cnt = ndlist.count(); + + // find out if an item has unknown signatures. Only check if the item has been + // expanded before as expansion is very expensive and can take several seconds + // that will freeze the UI meanwhile. + foreach (KGpgNode *nd, ndlist) { + if (!nd->hasChildren()) + continue; + + KGpgExpandableNode *exnd = nd->toExpandableNode(); + if (!exnd->wasExpanded()) { + unksig = true; + break; + } + getMissingSigs(l, exnd); + if (!l.isEmpty()) { + unksig = true; + break; + } + } + importAllSignKeys->setEnabled(unksig && m_online); + + signUid->setEnabled(!(itype & ~(ITYPE_PAIR | ITYPE_UID | ITYPE_UAT))); + signMailUid->setEnabled(signUid->isEnabled()); + setActionDescriptions(cnt); + + if (itype == ITYPE_SIGN) { + bool allunksig = true; + foreach (KGpgNode *nd, ndlist) { + allunksig = nd->toSignNode()->isUnknown(); + if (!allunksig) + break; + } + + importSignatureKey->setEnabled(allunksig && m_online); + delSignKey->setEnabled( (cnt == 1) ); + m_popupsig->exec(globpos); + } else if (itype == ITYPE_UID) { + if (cnt == 1) { + KGpgKeyNode *knd = ndlist.at(0)->toUidNode()->getParentKeyNode(); + setPrimUid->setEnabled(knd->getType() & ITYPE_SECRET); + } + m_popupuid->exec(globpos); + } else if ((itype == ITYPE_UAT) && (cnt == 1)) { + m_popupphoto->exec(globpos); + } else if ((itype == ITYPE_PAIR) && (cnt == 1)) { + m_popupsec->exec(globpos); + } else if ((itype == ITYPE_SECRET) && (cnt == 1)) { + m_popuporphan->exec(globpos); + } else if (itype == ITYPE_GROUP) { + delGroup->setEnabled( (cnt == 1) ); + editCurrentGroup->setEnabled( (cnt == 1) ); + m_groupRename->setEnabled( (cnt == 1) ); + m_popupgroup->exec(globpos); + } else if (!(itype & ~(ITYPE_PAIR | ITYPE_GROUP))) { + signKey->setEnabled(!(itype & ITYPE_GROUP)); + deleteKey->setEnabled(!(itype & ITYPE_GROUP)); + setDefaultKey->setEnabled( (cnt == 1) ); + m_popuppub->exec(globpos); + } else if (!(itype & ~(ITYPE_UID | ITYPE_PAIR | ITYPE_UAT))) { + setPrimUid->setEnabled(false); + delUid->setEnabled(false); + m_popupuid->exec(globpos); + } else { + m_popupout->exec(globpos); + } +} + +void KeysManager::revokeWidget() +{ + KGpgNode *nd = iview->selectedNode(); + KDialog *keyRevokeDialog = new KGpgRevokeDialog(this, nd->toKeyNode()); + + connect(keyRevokeDialog, SIGNAL(finished(int)), SLOT(slotRevokeDialogFinished(int))); + + keyRevokeDialog->open(); +} + +void KeysManager::slotRevokeDialogFinished(int result) +{ + sender()->deleteLater(); + + if (result != QDialog::Accepted) + return; + + KGpgRevokeDialog *keyRevokeDialog = qobject_cast(sender()); + + KGpgGenerateRevoke *genRev = new KGpgGenerateRevoke(this, keyRevokeDialog->getId(), keyRevokeDialog->saveUrl(), + keyRevokeDialog->getReason(), keyRevokeDialog->getDescription()); + + connect(genRev, SIGNAL(done(int)), SLOT(slotRevokeGenerated(int))); + + if (keyRevokeDialog->printChecked()) + connect(genRev, SIGNAL(revokeCertificate(QString)), SLOT(doPrint(QString))); + if (keyRevokeDialog->importChecked()) + connect(genRev, SIGNAL(revokeCertificate(QString)), SLOT(slotImportRevokeTxt(QString))); + + genRev->start(); +} + +void KeysManager::slotRevokeGenerated(int result) +{ + KGpgGenerateRevoke *genRev = qobject_cast(sender()); + + genRev->deleteLater(); + + switch (result) { + case KGpgTransaction::TS_OK: + case KGpgTransaction::TS_USER_ABORTED: + break; + default: + KMessageBox::detailedSorry(this, i18n("Creation of the revocation certificate failed..."), genRev->getOutput()); + break; + } +} + +void KeysManager::slotImportRevokeTxt(const QString &revokeText) +{ + KGpgImport *import = new KGpgImport(this, revokeText); + connect(import, SIGNAL(done(int)), SLOT(slotImportDone(int))); + import->start(); +} + +void KeysManager::slotexportsec() +{ + // export secret key + const QString warn(i18n("Secret keys should not be saved in an unsafe place.
" + "If someone else can access this file, encryption with this key will be compromised.
Continue key export?
")); + int result = KMessageBox::warningContinueCancel(this, warn); + if (result != KMessageBox::Continue) + return; + KGpgNode *nd = iview->selectedNode(); + + QString sname(nd->getEmail().section(QLatin1Char( '@' ), 0, 0).section(QLatin1Char( '.' ), 0, 0)); + if (sname.isEmpty()) + sname = nd->getName().section(QLatin1Char( ' ' ), 0, 0); + sname.append(QLatin1String( ".asc" )); + sname.prepend(QDir::homePath() + QLatin1Char( '/' )); + KUrl url(KFileDialog::getSaveUrl(sname, i18n( "*.asc|*.asc Files" ), this, i18n("Export PRIVATE KEY As"))); + + if(!url.isEmpty()) { + KGpgExport *exp = new KGpgExport(this, QStringList(nd->getId()), url.path(), QStringList(QLatin1String( "--armor" )), true); + + connect(exp, SIGNAL(done(int)), SLOT(slotExportSecFinished(int))); + + exp->start(); + } +} + +void KeysManager::slotExportSecFinished(int result) +{ + KGpgExport *exp = qobject_cast(sender()); + Q_ASSERT(exp != NULL); + + if (result == KGpgTransaction::TS_OK) { + KMessageBox::information(this, + i18n("Your private key \"%1\" was successfully exported to
%2.
Do not leave it in an insecure place.
", + exp->getKeyIds().first(), exp->getOutputFile())); + } else { + KMessageBox::sorry(this, i18n("Your secret key could not be exported.\nCheck the key.")); + } +} + +void KeysManager::slotexport() +{ + bool same; + KgpgItemType tp; + + QList ndlist(iview->selectedNodes(&same, &tp)); + if (ndlist.isEmpty()) + return; + if (!(tp & ITYPE_PUBLIC) || (tp & ~ITYPE_GPAIR)) + return; + + QString sname; + + if (ndlist.count() == 1) { + sname = ndlist.at(0)->getEmail().section(QLatin1Char( '@' ), 0, 0).section(QLatin1Char( '.' ), 0, 0); + if (sname.isEmpty()) + sname = ndlist.at(0)->getName().section(QLatin1Char(' '), 0, 0); + } else + sname = QLatin1String( "keyring" ); + + QStringList klist; + for (int i = 0; i < ndlist.count(); ++i) { + klist << ndlist.at(i)->getId(); + } + + sname.append(QLatin1String( ".asc" )); + sname.prepend(QDir::homePath() + QLatin1Char( '/' )); + + QStringList serverList(KGpgSettings::keyServers()); + serverList.replaceInStrings(QRegExp( QLatin1String( " .*") ), QLatin1String( "" ) ); // Remove kde 3.5 (Default) tag. + if (!serverList.isEmpty()) { + QString defaultServer = serverList.takeFirst(); + qSort(serverList); + serverList.prepend(defaultServer); + } + + QPointer page = new KeyExport(this, serverList); + + page->newFilename->setUrl(sname); + + if (!m_online) + page->checkServer->setEnabled(false); + + if (page->exec() == QDialog::Accepted) { + // export to file + QString exportAttr; + + if (page->checkAttrAll->isChecked()) { + // nothing + } else if (page->checkAttrPhoto->isChecked()) { + exportAttr = QLatin1String( "no-export-attributes" ); + } else { + exportAttr = QLatin1String( "export-minimal" ); + } + QStringList expopts; + + if (!exportAttr.isEmpty()) + expopts << QLatin1String( "--export-options" ) << exportAttr; + + if (page->checkServer->isChecked()) { + KeyServer *expServer = new KeyServer(0, imodel); + expServer->slotSetExportAttribute(exportAttr); + expServer->slotSetKeyserver(page->destServer->currentText()); + + expServer->slotExport(klist); + } else if (page->checkFile->isChecked()) { + const QString expname(page->newFilename->url().path().simplified()); + if (!expname.isEmpty()) { + + expopts.append(QLatin1String( "--armor" )); + + KGpgExport *exp = new KGpgExport(this, klist, expname, expopts); + + connect(exp, SIGNAL(done(int)), SLOT(slotExportFinished(int))); + + exp->start(); + } + } else { + KGpgExport *exp = new KGpgExport(this, klist, expopts); + + if (page->checkClipboard->isChecked()) + connect(exp, SIGNAL(done(int)), SLOT(slotProcessExportClip(int))); + else + connect(exp, SIGNAL(done(int)), SLOT(slotProcessExportMail(int))); + + exp->start(); + } + } + + delete page; +} + +void KeysManager::slotExportFinished(int result) +{ + KGpgExport *exp = qobject_cast(sender()); + Q_ASSERT(exp != NULL); + + if (result == KGpgTransaction::TS_OK) { + KMessageBox::information(this, + i18np("The public key was successfully exported to
%2
", + "The %1 public keys were successfully exported to
%2
", + exp->getKeyIds().count(), exp->getOutputFile())); + } else { + KMessageBox::sorry(this, i18n("Your public key could not be exported\nCheck the key.")); + } + + exp->deleteLater(); +} + +void KeysManager::slotProcessExportMail(int result) +{ + KGpgExport *exp = qobject_cast(sender()); + Q_ASSERT(exp != NULL); + + // start default Mail application + if (result == KGpgTransaction::TS_OK) { + KToolInvocation::invokeMailer(QString(), QString(), QString(), QString(),QLatin1String( exp->getOutputData() )); + } else { + KMessageBox::sorry(this, i18n("Your public key could not be exported\nCheck the key.")); + } + + exp->deleteLater(); +} + +void KeysManager::slotProcessExportClip(int result) +{ + KGpgExport *exp = qobject_cast(sender()); + Q_ASSERT(exp != NULL); + + if (result == KGpgTransaction::TS_OK) { + kapp->clipboard()->setText(QLatin1String( exp->getOutputData() ), m_clipboardmode); + } else { + KMessageBox::sorry(this, i18n("Your public key could not be exported\nCheck the key.")); + } + + exp->deleteLater(); +} + +void KeysManager::showKeyInfo(const QString &keyID) +{ + KGpgKeyNode *key = imodel->getRootNode()->findKey(keyID); + + if (key == NULL) + return; + + showProperties(key); +} + +void KeysManager::slotShowPhoto() +{ + KService::List list(KMimeTypeTrader::self()->query(QLatin1String( "image/jpeg" ))); + if (list.isEmpty()) { + KMessageBox::sorry(NULL, i18n("A viewer for JPEG images is not specified.
Please check your installation.
"), + i18n("Show photo")); + return; + } + KGpgNode *nd = iview->selectedNode(); + KGpgUatNode *und = nd->toUatNode(); + KGpgKeyNode *parent = und->getParentKeyNode(); + KService::Ptr ptr = list.first(); + + KProcess p; + p << KGpgSettings::gpgBinaryPath() + << QLatin1String("--no-tty") + << QLatin1String("--photo-viewer") + << (ptr->desktopEntryName() + QLatin1String( " %i" )) + << QLatin1String("--edit-key") + << parent->getId() + << QLatin1String("uid") + << und->getId() + << QLatin1String("showphoto") + << QLatin1String("quit"); + p.startDetached(); +} + +void KeysManager::defaultAction(const QModelIndex &index) +{ + KGpgNode *nd = iproxy->nodeForIndex(index); + + defaultAction(nd); +} + +void KeysManager::slotDefaultAction() +{ + defaultAction(iview->selectedNode()); +} + +void KeysManager::defaultAction(KGpgNode *nd) +{ + if (nd == NULL) + return; + + if (iview->isEditing()) + return; + + switch (nd->getType()) { + case ITYPE_GROUP: + editGroup(); + break; + case ITYPE_UAT: + slotShowPhoto(); + break; + case ITYPE_SIGN: + case ITYPE_GPUBLIC: + case ITYPE_GSECRET: + case ITYPE_GPAIR: + iview->selectNode(nd->toRefNode()->getRefNode()); + break; + case ITYPE_SECRET: + slotregenerate(); + break; + case ITYPE_PAIR: + case ITYPE_PUBLIC: + showProperties(nd); + return; + } +} + +void +KeysManager::showProperties(KGpgNode *n) +{ + switch (n->getType()) { + case ITYPE_UAT: + return; + case ITYPE_PUBLIC: + case ITYPE_PAIR: { + KGpgKeyNode *k = n->toKeyNode(); + QPointer opts = new KgpgKeyInfo(k, imodel, this); + connect(opts, SIGNAL(keyNeedsRefresh(KGpgKeyNode*)), imodel, SLOT(refreshKey(KGpgKeyNode*))); + connect(opts->keychange, SIGNAL(keyNeedsRefresh(KGpgKeyNode*)), imodel, SLOT(refreshKey(KGpgKeyNode*))); + opts->exec(); + delete opts; + } + default: + return; + } +} + +void KeysManager::keyproperties() +{ + KGpgNode *cur = iview->selectedNode(); + if (cur == NULL) + return; + + KGpgKeyNode *kn; + + switch (cur->getType()) { + case ITYPE_SECRET: + case ITYPE_GSECRET: + if (KMessageBox::questionYesNo(this, + i18n("

This key is an orphaned secret key (secret key without public key.) It is currently not usable.

" + "

Would you like to regenerate the public key?

"), + QString(), KGuiItem(i18n("Generate")), KGuiItem(i18n("Do Not Generate"))) == KMessageBox::Yes) + slotregenerate(); + return; + case ITYPE_PAIR: + case ITYPE_PUBLIC: { + kn = cur->toKeyNode(); + break; + } + case ITYPE_GPAIR: + case ITYPE_GPUBLIC: { + kn = cur->toGroupMemberNode()->getRefNode(); + break; + } + default: + kDebug(2100) << "Oops, called with invalid item type" << cur->getType(); + return; + } + + QPointer opts = new KgpgKeyInfo(kn, imodel, this); + connect(opts, SIGNAL(keyNeedsRefresh(KGpgKeyNode*)), imodel, SLOT(refreshKey(KGpgKeyNode*))); + opts->exec(); + delete opts; +} + +void KeysManager::deleteGroup() +{ + KGpgNode *nd = iview->selectedNode(); + if (!nd || (nd->getType() != ITYPE_GROUP)) + return; + + int result = KMessageBox::warningContinueCancel(this, i18n("Are you sure you want to delete group %1 ?", + nd->getName()), QString(), KGuiItem(i18n("Delete"), QLatin1String("edit-delete"))); + if (result != KMessageBox::Continue) + return; + + nd->toGroupNode()->remove(); + imodel->delNode(nd); + + updateStatusCounter(); +} + +void KeysManager::renameGroup() +{ + if (iview->selectionModel()->selectedIndexes().isEmpty()) + return; + + QModelIndex selectedNodeIndex = iview->selectionModel()->selectedIndexes().first(); + + iview->edit(selectedNodeIndex); +} + +void KeysManager::createNewGroup() +{ + QStringList badkeys; + KGpgKeyNode::List keysList; + KgpgItemType tp; + KGpgNode::List ndlist(iview->selectedNodes(NULL, &tp)); + + if (ndlist.isEmpty()) + return; + if (tp & ~ITYPE_PAIR) { + KMessageBox::sorry(this, i18n("You cannot create a group containing signatures, subkeys or other groups.")); + return; + } + + KgpgKeyTrustFlag mintrust; + if (KGpgSettings::allowUntrustedGroupMembers()) { + mintrust = KgpgCore::TRUST_UNDEFINED; + } else { + mintrust = KgpgCore::TRUST_FULL; + } + + foreach (KGpgNode *nd, ndlist) { + if (nd->getTrust() >= mintrust) { + keysList.append(nd->toKeyNode()); + } else { + badkeys += i18nc(" () ID: ", "%1 (%2) ID: %3", + nd->getName(), nd->getEmail(), nd->getId()); + } + } + + QString groupName(KInputDialog::getText(i18n("Create New Group"), + i18nc("Enter the name of the group you are creating now", "Enter new group name:"), + QString(), 0, this)); + if (groupName.isEmpty()) + return; + if (!keysList.isEmpty()) { + if (!badkeys.isEmpty()) + KMessageBox::informationList(this, i18n("Following keys are not valid or not trusted and will not be added to the group:"), badkeys); + + iview->selectNode(imodel->addGroup(groupName, keysList)); + updateStatusCounter(); + } else { + KMessageBox::sorry(this, + i18n("No valid or trusted key was selected. The group %1 will not be created.", + groupName)); + } +} + +void KeysManager::editGroup() +{ + KGpgNode *nd = iview->selectedNode(); + if (!nd || (nd->getType() != ITYPE_GROUP)) + return; + KGpgGroupNode *gnd = nd->toGroupNode(); + QPointer dialogGroupEdit = new KDialog(this ); + dialogGroupEdit->setCaption( i18n("Group Properties") ); + dialogGroupEdit->setButtons( KDialog::Ok | KDialog::Cancel ); + dialogGroupEdit->setDefaultButton( KDialog::Ok ); + dialogGroupEdit->setModal( true ); + + QList members(gnd->getChildren()); + + groupEdit *gEdit = new groupEdit(dialogGroupEdit, &members, imodel); + + dialogGroupEdit->setMainWidget(gEdit); + + gEdit->show(); + + if (dialogGroupEdit->exec() == QDialog::Accepted) + imodel->changeGroup(gnd, members); + + delete dialogGroupEdit; +} + +void KeysManager::signkey() +{ + // another sign operation is still running + if (!signList.isEmpty()) + return; + + KgpgItemType tp; + QList tmplist = iview->selectedNodes(NULL, &tp); + if (tmplist.isEmpty()) + return; + + if (tp & ~ITYPE_PAIR) { + KMessageBox::sorry(this, i18n("You can only sign primary keys. Please check your selection.")); + return; + } + + if (tmplist.count() == 1) { + KGpgKeyNode *nd = tmplist.at(0)->toKeyNode(); + QString opt; + + if (nd->getEmail().isEmpty()) + opt = i18n("You are about to sign key:

%1
ID: %2
Fingerprint:
%3.

" + "You should check the key fingerprint by phoning or meeting the key owner to be sure that someone " + "is not trying to intercept your communications.
", + nd->getName(), nd->getId().right(8), nd->getBeautifiedFingerprint()); + else + opt = i18n("You are about to sign key:

%1 (%2)
ID: %3
Fingerprint:
%4.

" + "You should check the key fingerprint by phoning or meeting the key owner to be sure that someone " + "is not trying to intercept your communications.
", + nd->getName(), nd->getEmail(), nd->getId().right(8), nd->getBeautifiedFingerprint()); + + if (KMessageBox::warningContinueCancel(this, opt) != KMessageBox::Continue) { + return; + } + signList.append(nd); + } else { + QStringList signKeyList; + foreach (KGpgNode *n, tmplist) { + const KGpgKeyNode *nd = n->toKeyNode(); + + if (nd->getEmail().isEmpty()) + signKeyList += i18nc("Name: ID", "%1: %2", nd->getName(), nd->getBeautifiedFingerprint()); + else + signKeyList += i18nc("Name (Email): ID", "%1 (%2): %3", nd->getName(), nd->getEmail(), nd->getBeautifiedFingerprint()); + + signList.append(n->toSignableNode()); + } + + if (KMessageBox::Continue != KMessageBox::warningContinueCancelList(this, + i18n("You are about to sign the following keys in one pass.
If you have not carefully checked all fingerprints," + " the security of your communications may be compromised.
"), + signKeyList)) + return; + } + + QPointer opts = new KgpgSelectSecretKey(this, imodel, signList.count()); + if (opts->exec() != QDialog::Accepted) { + delete opts; + signList.clear(); + return; + } + + globalkeyID = QString(opts->getKeyID()); + const bool localsign = opts->isLocalSign(); + const int checklevel = opts->getSignTrust(); + bool isterminal = opts->isTerminalSign(); + delete opts; + + if (isterminal) { + const QString keyid(signList.at(0)->getId()); + signList.clear(); + signKeyOpenConsole(globalkeyID, keyid, checklevel, localsign); + } else { + keyCount = 0; + m_signuids = false; + signLoop(localsign, checklevel); + } +} + +void KeysManager::signuid() +{ + // another sign operation is still running + if (!signList.isEmpty()) + return; + + KgpgItemType tp; + KGpgNode::List tmplist = iview->selectedNodes(NULL, &tp); + if (tmplist.isEmpty()) + return; + + if (tp & ~(ITYPE_PAIR | ITYPE_UID | ITYPE_UAT)) { + KMessageBox::sorry(this, i18n("You can only sign user ids and photo ids. Please check your selection.")); + return; + } + + if (tmplist.count() == 1) { + KGpgSignableNode *nd = tmplist.at(0)->toSignableNode(); + KGpgKeyNode *pnd; + if (tp & ITYPE_PUBLIC) + pnd = nd->toKeyNode(); + else + pnd = nd->getParentKeyNode()->toKeyNode(); + QString opt; + + if (nd->getEmail().isEmpty()) + opt = i18n("You are about to sign user id:

%1
ID: %2
Fingerprint:
%3.

" + "You should check the key fingerprint by phoning or meeting the key owner to be sure that someone " + "is not trying to intercept your communications.
", nd->getName(), nd->getId(), pnd->getBeautifiedFingerprint()); + else + opt = i18n("You are about to sign user id:

%1 (%2)
ID: %3
Fingerprint:
%4.

" + "You should check the key fingerprint by phoning or meeting the key owner to be sure that someone " + "is not trying to intercept your communications.
", nd->getName(), nd->getEmail(), nd->getId(), pnd->getBeautifiedFingerprint()); + + if (KMessageBox::warningContinueCancel(this, opt) != KMessageBox::Continue) { + return; + } + signList.append(nd); + } else { + QStringList signKeyList; + + foreach (KGpgNode *nd, tmplist) { + const KGpgKeyNode *pnd = (nd->getType() & (ITYPE_UID | ITYPE_UAT)) ? + nd->getParentKeyNode()->toKeyNode() : nd->toKeyNode(); + + if (nd->getEmail().isEmpty()) + signKeyList += i18nc("Name: ID", "%1: %2", + nd->getName(), pnd->getBeautifiedFingerprint()); + else + signKeyList += i18nc("Name (Email): ID", "%1 (%2): %3", + nd->getName(), nd->getEmail(), pnd->getBeautifiedFingerprint()); + + signList.append(nd->toSignableNode()); + } + + if (KMessageBox::warningContinueCancelList(this, + i18n("You are about to sign the following user ids in one pass.
If you have not carefully checked all fingerprints," + " the security of your communications may be compromised.
"), + signKeyList) != KMessageBox::Continue) + return; + } + + QPointer opts = new KgpgSelectSecretKey(this, imodel, signList.count()); + if (opts->exec() != QDialog::Accepted) { + delete opts; + signList.clear(); + return; + } + + globalkeyID = QString(opts->getKeyID()); + const bool localsign = opts->isLocalSign(); + const int checklevel = opts->getSignTrust(); + bool isterminal = opts->isTerminalSign(); + delete opts; + + if (isterminal) { + const QString keyid(signList.at(0)->getId()); + signList.clear(); + signKeyOpenConsole(globalkeyID, keyid, checklevel, localsign); + } else { + keyCount = 0; + m_signuids = true; + signLoop(localsign, checklevel); + } +} + +void KeysManager::signLoop(const bool localsign, const int checklevel) +{ + Q_ASSERT(keyCount < signList.count()); + + KGpgSignableNode *nd = signList.at(keyCount); + QString uid; + QString keyid; + const KGpgSignTransactionHelper::carefulCheck cc = static_cast(checklevel); + KGpgTransaction *sta; + + if (m_signuids) { + sta = new KGpgSignUid(this, globalkeyID, nd, localsign, cc); + } else { + sta = new KGpgSignKey(this, globalkeyID, nd->toKeyNode(), localsign, cc); + } + + connect(sta, SIGNAL(done(int)), SLOT(signatureResult(int))); + sta->start(); +} + +void KeysManager::signatureResult(int success) +{ + KGpgSignTransactionHelper *ta; + KGpgSignUid *suid = qobject_cast(sender()); + if (suid != NULL) { + ta = static_cast(suid); + } else { + ta = static_cast(static_cast(sender())); + } + KGpgKeyNode *nd = const_cast(ta->getKey()); + const bool localsign = ta->getLocal(); + const int checklevel = ta->getChecking(); + const QString signer(ta->getSigner()); + sender()->deleteLater(); + + switch (success) { + case KGpgTransaction::TS_OK: + if (refreshList.indexOf(nd) == -1) + refreshList.append(nd); + break; + case KGpgTransaction::TS_BAD_PASSPHRASE: + KMessageBox::sorry(this, i18n("Bad passphrase, key %1 (%2) not signed.", + nd->getName(), nd->getEmail())); + break; + case KGpgSignTransactionHelper::TS_ALREADY_SIGNED: + KMessageBox::sorry(this, i18n("The key %1 (%2) is already signed.", + nd->getName(), nd->getEmail())); + break; + default: + if (KMessageBox::questionYesNo(this, + i18n("Signing key %1 with key %2 failed.
" + "Do you want to try signing the key in console mode?
", + nd->getId(), signer)) == KMessageBox::Yes) + signKeyOpenConsole(signer, nd->getId(), checklevel, localsign); + } + + if (++keyCount == signList.count()) { + signList.clear(); + imodel->refreshKeys(refreshList); + refreshList.clear(); + } else { + signLoop(localsign, checklevel); + } +} + +void KeysManager::caff() +{ + KgpgItemType tp; + KGpgNode::List tmplist = iview->selectedNodes(NULL, &tp); + KGpgSignableNode::List slist; + if (tmplist.isEmpty()) + return; + + if (tp & ~(ITYPE_PAIR | ITYPE_UID | ITYPE_UAT)) { + KMessageBox::sorry(this, i18n("You can only sign user ids and photo ids. Please check your selection.")); + return; + } + + foreach (KGpgNode *nd, tmplist) { + switch (nd->getType()) { + case KgpgCore::ITYPE_PAIR: + case KgpgCore::ITYPE_PUBLIC: { + KGpgKeyNode *knd = qobject_cast(nd); + if (!knd->wasExpanded()) + knd->getChildCount(); + } + } + slist.append(nd->toSignableNode()); + } + + QPointer opts = new KgpgSelectSecretKey(this, imodel, slist.count(), false, false); + if (opts->exec() != QDialog::Accepted) { + delete opts; + return; + } + + KGpgCaff *ca = new KGpgCaff(this, slist, QStringList(opts->getKeyID()), opts->getSignTrust(), KGpgCaff::IgnoreAlreadySigned); + delete opts; + + connect(ca, SIGNAL(done()), SLOT(slotCaffDone())); + connect(ca, SIGNAL(aborted()), SLOT(slotCaffDone())); + + ca->run(); +} + +void KeysManager::slotCaffDone() +{ + Q_ASSERT(qobject_cast(sender()) != NULL); + + sender()->deleteLater(); +} + +void KeysManager::signKeyOpenConsole(const QString &signer, const QString &keyid, const int checking, const bool local) +{ + KConfigGroup config(KGlobal::config(), "General"); + + KProcess process; + process << config.readPathEntry("TerminalApplication", QLatin1String("konsole")) + << QLatin1String("-e") + << KGpgSettings::gpgBinaryPath() + << QLatin1String("--no-secmem-warning") + << QLatin1String("-u") << signer + << QLatin1String("--default-cert-level") + << QString(checking); + + if (!local) + process << QLatin1String( "--sign-key" ) << keyid; + else + process << QLatin1String( "--lsign-key" ) << keyid; + + process.execute(); +} + +void KeysManager::getMissingSigs(QSet &missingKeys, const KGpgExpandableNode *nd) +{ + foreach (const KGpgNode *ch, nd->getChildren()) { + if (ch->hasChildren()) { + getMissingSigs(missingKeys, ch->toExpandableNode()); + continue; + } else if (ch->getType() == ITYPE_SIGN) { + if (ch->toSignNode()->isUnknown()) + missingKeys << ch->getId(); + } + } +} + +void KeysManager::importallsignkey() +{ + const QList sel(iview->selectedNodes()); + QSet missingKeys; + + if (sel.isEmpty()) + return; + + foreach (const KGpgNode *nd, sel) { + if (nd->hasChildren()) { + getMissingSigs(missingKeys, nd->toExpandableNode()); + } else if (nd->getType() == ITYPE_SIGN) { + const KGpgSignNode *sn = nd->toSignNode(); + + if (sn->isUnknown()) + missingKeys << sn->getId(); + } + } + + if (missingKeys.isEmpty()) { + KMessageBox::information(this, + i18np("All signatures for this key are already in your keyring", + "All signatures for this keys are already in your keyring", sel.count())); + return; + } + + importRemoteKeys(missingKeys.toList()); +} + +void KeysManager::preimportsignkey() +{ + const QList exportList(iview->selectedNodes()); + QStringList idlist; + + if (exportList.empty()) + return; + + foreach (const KGpgNode *nd, exportList) + idlist << nd->getId(); + + importRemoteKeys(idlist); +} + +bool KeysManager::importRemoteKey(const QString &keyIDs) +{ + return importRemoteKeys(keyIDs.simplified().split(QLatin1Char( ' ' )), false); +} + +bool KeysManager::importRemoteKeys(const QStringList &keyIDs, const bool dialog) +{ + QStringList kservers(KeyServer::getServerList()); + if (kservers.isEmpty()) + return false; + + KGpgReceiveKeys *proc = new KGpgReceiveKeys(this, kservers.first(), keyIDs, dialog, QLatin1String( qgetenv("http_proxy") )); + connect(proc, SIGNAL(done(int)), SLOT(importRemoteFinished(int))); + + proc->start(); + + return true; +} + +void KeysManager::importRemoteFinished(int result) +{ + KGpgReceiveKeys *t = qobject_cast(sender()); + Q_ASSERT(t != NULL); + + const QStringList keys(KGpgImport::getImportedIds(t->getLog())); + + t->deleteLater(); + + if (result == KGpgTransaction::TS_OK) + imodel->refreshKeys(keys); +} + +void KeysManager::refreshKeys(const QStringList& ids) +{ + imodel->refreshKeys(ids); +} + +void KeysManager::delsignkey() +{ + KGpgNode *nd = iview->selectedNode(); + if (nd == NULL) + return; + + QString uid; + QString parentKey; + + KGpgExpandableNode *parent = nd->getParentKeyNode(); + switch (parent->getType()) { + case ITYPE_PAIR: + case ITYPE_PUBLIC: + uid = QLatin1Char( '1' ); + parentKey = parent->getId(); + break; + case ITYPE_UID: + case ITYPE_UAT: + uid = parent->getId(); + parentKey = parent->getParentKeyNode()->getId(); + break; + default: + Q_ASSERT(0); + return; + } + + const QString signID(nd->getId()); + QString signMail(nd->getNameComment()); + QString parentMail(parent->getNameComment()); + + if (!parent->getEmail().isEmpty()) + parentMail += QLatin1String( " <" ) + parent->getEmail() + QLatin1String( ">" ); + if (!nd->getEmail().isEmpty()) + signMail += QLatin1String( " <" ) + nd->getEmail() + QLatin1String( ">" ); + + if (parentKey == signID) { + KMessageBox::sorry(this, i18n("Edit key manually to delete a self-signature.")); + return; + } + + QString ask = i18n("Are you sure you want to delete signature
%1
from user id %2
of key: %3?
", + signMail, parentMail, parentKey); + + if (KMessageBox::questionYesNo(this, ask, QString(), KStandardGuiItem::del(), KStandardGuiItem::cancel()) != KMessageBox::Yes) + return; + + KGpgDelSign *delsig = new KGpgDelSign(this, nd->toSignNode()); + connect(delsig, SIGNAL(done(int)), SLOT(delsignatureResult(int))); + delsig->start(); +} + +void KeysManager::delsignatureResult(int success) +{ + sender()->deleteLater(); + + if (success == KGpgTransaction::TS_OK) { + KGpgNode *nd = iview->selectedNode()->getParentKeyNode(); + + while (!(nd->getType() & ITYPE_PAIR)) + nd = nd->getParentKeyNode(); + imodel->refreshKey(nd->toKeyNode()); + } else { + KMessageBox::sorry(this, i18n("Requested operation was unsuccessful, please edit the key manually.")); + } +} + +void KeysManager::slotSendEmail() +{ + QStringList maillist; + + foreach (const KGpgNode *nd, iview->selectedNodes()) { + if (nd->getEmail().isEmpty()) + continue; + + maillist << QLatin1Char('"') + nd->getName() + QLatin1String("\" <") + nd->getEmail() + QLatin1Char('>'); + } + + if (maillist.isEmpty()) + return; + + KToolInvocation::invokeMailer(maillist.join(QLatin1String(", ")), QString()); +} + +void KeysManager::slotedit() +{ + KGpgNode *nd = iview->selectedNode(); + Q_ASSERT(nd != NULL); + + if (!(nd->getType() & ITYPE_PAIR)) + return; + if (terminalkey) + return; + if ((m_delkey != NULL) && m_delkey->keys().contains(nd->toKeyNode())) + return; + + KProcess *kp = new KProcess(this); + KConfigGroup config(KGlobal::config(), "General"); + *kp << config.readPathEntry("TerminalApplication", QLatin1String("konsole")) + << QLatin1String("-e") + << KGpgSettings::gpgBinaryPath() + << QLatin1String("--no-secmem-warning") + << QLatin1String("--edit-key") + << nd->getId() + << QLatin1String("help"); + terminalkey = nd->toKeyNode(); + editKey->setEnabled(false); + + connect(kp, SIGNAL(finished(int)), SLOT(slotEditDone(int))); + kp->start(); +} + +void KeysManager::slotEditDone(int exitcode) +{ + if (exitcode == 0) + imodel->refreshKey(terminalkey); + + terminalkey = NULL; + editKey->setEnabled(true); +} + +void KeysManager::doPrint(const QString &txt) +{ + QPrinter prt; + //kDebug(2100) << "Printing..." ; + QPointer printDialog = new QPrintDialog(&prt, this); + if (printDialog->exec() == QDialog::Accepted) { + QPainter painter(&prt); + int width = painter.device()->width(); + int height = painter.device()->height(); + painter.drawText(0, 0, width, height, Qt::AlignLeft|Qt::AlignTop|Qt::TextDontClip, txt); + } + delete printDialog; +} + +void KeysManager::removeFromGroups(KGpgKeyNode *node) +{ + QStringList groupNames; + + foreach (const KGpgGroupNode *gnd, node->getGroups()) + groupNames << gnd->getName(); + + if (groupNames.isEmpty()) + return; + + const QString ask = i18np("The key you are deleting is a member of the following key group. Do you want to remove it from this group?", + "The key you are deleting is a member of the following key groups. Do you want to remove it from these groups?", + groupNames.count()); + + if (KMessageBox::questionYesNoList(this, ask, groupNames, i18n("Delete key")) != KMessageBox::Yes) + return; + + bool groupDeleted = false; + + foreach (KGpgGroupMemberNode *gref, node->getGroupRefs()) { + KGpgGroupNode *group = gref->getParentKeyNode(); + + bool deleteWholeGroup = (group->getChildCount() == 1) && + (group->getChild(0)->toGroupMemberNode() == gref); + if (deleteWholeGroup) + deleteWholeGroup = (KMessageBox::questionYesNo(this, + i18n("You are removing the last key from key group %1.
Do you want to delete the group, too?", group->getName()), + i18n("Delete key")) == KMessageBox::Yes); + + if (!deleteWholeGroup) { + imodel->deleteFromGroup(group, gref); + } else { + group->remove(); + imodel->delNode(group); + groupDeleted = true; + } + } + + if (groupDeleted) { + updateStatusCounter(); + } +} + +void KeysManager::deleteseckey() +{ + KGpgKeyNode *nd = iview->selectedNode()->toKeyNode(); + Q_ASSERT(nd != NULL); + + // delete a key + int result = KMessageBox::warningContinueCancel(this, + i18n("

Delete secret key pair %1?

Deleting this key pair means you will never be able to decrypt files encrypted with this key again.", + nd->getNameComment()), + QString(), + KGuiItem(i18n("Delete"), QLatin1String( "edit-delete" ))); + if (result != KMessageBox::Continue) + return; + + if (terminalkey == nd) + return; + if (m_delkey != NULL) { + KMessageBox::error(this, + i18n("Another key delete operation is still in progress.\nPlease wait a moment until this operation is complete."), + i18n("Delete key")); + return; + } + + removeFromGroups(nd); + + m_delkey = new KGpgDelKey(this, nd); + connect(m_delkey, SIGNAL(done(int)), SLOT(secretKeyDeleted(int))); + m_delkey->start(); +} + +void KeysManager::secretKeyDeleted(int retcode) +{ + KGpgKeyNode *delkey = m_delkey->keys().first(); + if (retcode == 0) { + KMessageBox::information(this, i18n("Key %1 deleted.", delkey->getBeautifiedFingerprint()), i18n("Delete key")); + imodel->delNode(delkey); + } else { + KMessageBox::error(this, i18n("Deleting key %1 failed.", delkey->getBeautifiedFingerprint()), i18n("Delete key")); + } + m_delkey->deleteLater(); + m_delkey = NULL; +} + +void KeysManager::confirmdeletekey() +{ + if (m_delkey) { + KMessageBox::error(this, + i18n("Another key delete operation is still in progress.\nPlease wait a moment until this operation is complete."), + i18n("Delete key")); + return; + } + + KgpgCore::KgpgItemType pt; + bool same; + QList ndlist(iview->selectedNodes(&same, &pt)); + if (ndlist.isEmpty()) + return; + + // do not delete a key currently edited in terminal + if ((!(pt & ~ITYPE_PAIR)) && (ndlist.at(0) == terminalkey) && (ndlist.count() == 1)) { + KMessageBox::error(this, + i18n("Can not delete key %1 while it is edited in terminal.", + terminalkey->getBeautifiedFingerprint()), i18n("Delete key")); + return; + } else if (pt == ITYPE_GROUP) { + deleteGroup(); + return; + } else if (!(pt & ITYPE_GROUP) && (pt & ITYPE_SECRET) && (ndlist.count() == 1)) { + deleteseckey(); + return; + } else if ((pt == ITYPE_UID) && (ndlist.count() == 1)) { + slotDelUid(); + return; + } else if ((pt & ITYPE_GROUP) && !(pt & ~ITYPE_GPAIR)) { + bool invalidDelete = false; + foreach (const KGpgNode *nd, ndlist) + if (nd->getType() == ITYPE_GROUP) { + invalidDelete = true; + break; + } + + // only allow removing group members if they belong to the same group + if (!invalidDelete) { + const KGpgNode * const group = ndlist.first()->getParentKeyNode(); + foreach (const KGpgNode *nd, ndlist) + if (nd->getParentKeyNode() != group) { + invalidDelete = true; + break; + } + } + + if (!invalidDelete) { + KGpgGroupNode *gnd = ndlist.first()->getParentKeyNode()->toGroupNode(); + + QList members = gnd->getChildren(); + + foreach (KGpgNode *nd, ndlist) { + int r = members.removeAll(nd); + Q_ASSERT(r == 1); + Q_UNUSED(r); + } + + imodel->changeGroup(gnd, members); + return; + } + } + + if (pt & ~ITYPE_PAIR) { + KMessageBox::error(this, + i18n("You have selected items that are not keys. They can not be deleted with this menu entry."), + i18n("Delete key")); + return; + } + + QStringList keysToDelete; + QStringList deleteIds; + QStringList secList; + KGpgKeyNode::List delkeys; + + bool secretKeyInside = (pt & ITYPE_SECRET); + foreach (KGpgNode *nd, ndlist) { + KGpgKeyNode *ki = nd->toKeyNode(); + + if (ki->getType() & ITYPE_SECRET) { + secList += ki->getNameComment(); + } else if (ki != terminalkey) { + keysToDelete += ki->getNameComment(); + deleteIds << ki->getId(); + delkeys << ki; + } + } + + if (secretKeyInside) { + int result = KMessageBox::warningContinueCancel(this, + i18n("The following are secret key pairs:
%1
They will not be deleted.
", + secList.join( QLatin1String( "
" )))); + if (result != KMessageBox::Continue) + return; + } + + if (keysToDelete.isEmpty()) + return; + + int result = KMessageBox::warningContinueCancelList(this, + i18np("Delete the following public key?", + "Delete the following %1 public keys?", + keysToDelete.count()), keysToDelete, QString(), + KStandardGuiItem::del()); + if (result != KMessageBox::Continue) + return; + + foreach (KGpgNode *nd, ndlist) + removeFromGroups(nd->toKeyNode()); + + m_delkey = new KGpgDelKey(this, delkeys); + connect(m_delkey, SIGNAL(done(int)), SLOT(slotDelKeyDone(int))); + m_delkey->start(); +} + +void KeysManager::slotDelKeyDone(int res) +{ + if (res == 0) { + foreach (KGpgKeyNode *kn, m_delkey->keys()) + imodel->delNode(kn); + } + + m_delkey->deleteLater(); + m_delkey = NULL; + + updateStatusCounter(); +} + +void KeysManager::slotPreImportKey() +{ + QPointer dial = new KDialog(this); + dial->setCaption(i18n("Key Import")); + dial->setButtons(KDialog::Ok | KDialog::Cancel); + dial->setDefaultButton(KDialog::Ok); + dial->setModal(true); + + SrcSelect *page = new SrcSelect(); + dial->setMainWidget(page); + page->newFilename->setWindowTitle(i18n("Open File")); + page->newFilename->setMode(KFile::File); + + if (dial->exec() == QDialog::Accepted) { + if (page->checkFile->isChecked()) { + KUrl impname = page->newFilename->url(); + if (!impname.isEmpty()) + slotImport(KUrl::List(impname)); + } else if (page->checkServer->isChecked()) { + const QString ids(page->keyIds->text().simplified()); + if (!ids.isEmpty()) + importRemoteKeys(ids.split(QLatin1Char( ' ' ))); + } else { + slotImport(kapp->clipboard()->text(m_clipboardmode)); + } + } + + delete dial; +} + +void KeysManager::slotImport(const QString &text) +{ + if (text.isEmpty()) + return; + + KGpgImport *imp; + + if (!KGpgImport::isKey(text) && KGpgDecrypt::isEncryptedText(text)) { + if (KMessageBox::questionYesNo(this, + i18n("The text in the clipboard does not look like a key, but like encrypted text.
Do you want to decrypt it first" + " and then try importing it?
"), + i18n("Import from Clipboard")) != KMessageBox::Yes) + return; + + imp = new KGpgImport(this); + KGpgDecrypt *decr = new KGpgDecrypt(this, text); + imp->setInputTransaction(decr); + } else { + imp = new KGpgImport(this, text); + } + + startImport(imp); +} + +void KeysManager::slotImport(const KUrl::List &files) +{ + startImport(new KGpgImport(this, files)); +} + +void KeysManager::startImport(KGpgImport *import) +{ + changeMessage(i18n("Importing..."), true); + connect(import, SIGNAL(done(int)), SLOT(slotImportDone(int))); + import->start(); +} + +void KeysManager::slotImportDone(int result) +{ + KGpgImport *import = qobject_cast(sender()); + Q_ASSERT(import != NULL); + const QStringList rawmsgs(import->getMessages()); + + if (result != 0) { + KMessageBox::detailedSorry(this, i18n("Key importing failed. Please see the detailed log for more information."), + rawmsgs.join(QLatin1String("\n")) , i18n("Key Import")); + } + + QStringList keys(import->getImportedIds(0x1f)); + const bool needsRefresh = !keys.isEmpty(); + keys << import->getImportedIds(0); + + if (!keys.isEmpty()) { + const QString msg(import->getImportMessage()); + const QStringList keynames(import->getImportedKeys()); + + new KgpgDetailedInfo(this, msg, rawmsgs.join(QLatin1String("\n")), keynames, i18n("Key Import")); + if (needsRefresh) + imodel->refreshKeys(keys); + else + changeMessage(i18nc("Application ready for user input", "Ready")); + } else{ + changeMessage(i18nc("Application ready for user input", "Ready")); + } + + import->deleteLater(); +} + +void KeysManager::refreshkey() +{ + imodel->refreshKeys(); + updateStatusCounter(); +} + +KGpgItemModel *KeysManager::getModel() +{ + return imodel; +} + +void +KeysManager::slotNetworkUp() +{ + toggleNetworkActions(true); +} + +void +KeysManager::slotNetworkDown() +{ + toggleNetworkActions(false); +} + +void +KeysManager::toggleNetworkActions(bool online) +{ + m_online = online; + kserver->setEnabled(online); + importSignatureKey->setEnabled(online); + importAllSignKeys->setEnabled(online); + refreshKey->setEnabled(online); +} + +void +KeysManager::setupTrayIcon() +{ + bool newtray = (m_trayicon == NULL); + + if (newtray) { + m_trayicon = new KStatusNotifierItem(this); + m_trayicon->setIconByName(QLatin1String( "kgpg" )); + m_trayicon->setToolTip(QLatin1String( "kgpg" ), i18n("KGpg - encryption tool"), QString()); + } + + switch (KGpgSettings::leftClick()) { + case KGpgSettings::EnumLeftClick::Editor: + m_trayicon->setAssociatedWidget(s_kgpgEditor); + break; + case KGpgSettings::EnumLeftClick::KeyManager: + m_trayicon->setAssociatedWidget(this); + break; + } + + m_trayicon->setCategory(KStatusNotifierItem::ApplicationStatus); + + if (!newtray) + return; + + KMenu *conf_menu = m_trayicon->contextMenu(); + + QAction *KgpgOpenManager = actionCollection()->addAction(QLatin1String("kgpg_manager"), this, SLOT(show())); + KgpgOpenManager->setIcon(KIcon( QLatin1String( "kgpg" ))); + KgpgOpenManager->setText(i18n("Ke&y Manager")); + + QAction *KgpgEncryptClipboard = actionCollection()->addAction(QLatin1String("clip_encrypt"), this, SLOT(clipEncrypt())); + KgpgEncryptClipboard->setText(i18n("&Encrypt Clipboard")); + + QAction *KgpgDecryptClipboard = actionCollection()->addAction(QLatin1String("clip_decrypt"), this, SLOT(clipDecrypt())); + KgpgDecryptClipboard->setText(i18n("&Decrypt Clipboard")); + + QAction *KgpgSignClipboard = actionCollection()->addAction(QLatin1String("clip_sign"), this, SLOT(clipSign())); + KgpgSignClipboard->setText(i18n("&Sign/Verify Clipboard")); + KgpgSignClipboard->setIcon(KIcon( QLatin1String( "document-sign-key" ))); + + QAction *KgpgPreferences = KStandardAction::preferences(this, SLOT(showOptions()), actionCollection()); + + conf_menu->addAction( KgpgEncryptClipboard ); + conf_menu->addAction( KgpgDecryptClipboard ); + conf_menu->addAction( KgpgSignClipboard ); + conf_menu->addAction( KgpgOpenManager ); + conf_menu->addAction( openEditor ); + conf_menu->addAction( kserver ); + conf_menu->addSeparator(); + conf_menu->addAction( KgpgPreferences ); +} + +void +KeysManager::showTrayMessage(const QString &message) +{ + if (m_trayicon == NULL) + return; + + m_trayicon->showMessage(QString(), message, QLatin1String( "kgpg" )); +} + +KShortcut +KeysManager::goDefaultShortcut() const +{ + return goToDefaultKey->shortcut(); +} + +void +KeysManager::clipEncrypt() +{ + const QString cliptext(kapp->clipboard()->text(m_clipboardmode)); + + if (cliptext.isEmpty()) { + Q_ASSERT(m_trayicon != NULL); + m_trayicon->showMessage(QString(), i18n("Clipboard is empty."), QLatin1String( "kgpg" )); + return; + } + + QPointer dialog = new KgpgSelectPublicKeyDlg(this, imodel, goToDefaultKey->shortcut(), true); + if (dialog->exec() == KDialog::Accepted) { + KGpgEncrypt::EncryptOptions encOptions = KGpgEncrypt::AsciiArmored; + QStringList options; + + if (!dialog->getCustomOptions().isEmpty() && KGpgSettings::allowCustomEncryptionOptions()) + options = dialog->getCustomOptions().split(QLatin1Char(' '), QString::SkipEmptyParts); + + if (dialog->getUntrusted()) + encOptions |= KGpgEncrypt::AllowUntrustedEncryption; + if (dialog->getHideId()) + encOptions |= KGpgEncrypt::HideKeyId; + + if (KGpgSettings::pgpCompatibility()) + options.append(QLatin1String( "--pgp6" )); + + KGpgEncrypt *enc = new KGpgEncrypt(this, dialog->selectedKeys(), cliptext, encOptions, options); + connect(enc, SIGNAL(done(int)), SLOT(slotSetClip(int))); + + m_trayicon->setStatus(KStatusNotifierItem::Active); + enc->start(); + } + + delete dialog; +} + +void +KeysManager::slotSetClip(int result) +{ + KGpgEncrypt *enc = qobject_cast(sender()); + Q_ASSERT(enc != NULL); + sender()->deleteLater(); + + m_trayicon->setStatus(KStatusNotifierItem::Passive); + + if (result != KGpgTransaction::TS_OK) + return; + + kapp->clipboard()->setText(enc->encryptedText().join(QLatin1String("\n")), m_clipboardmode); + + Q_ASSERT(m_trayicon != NULL); + m_trayicon->showMessage(QString(), i18n("Text successfully encrypted."), QLatin1String( "kgpg" )); +} + +void +KeysManager::slotOpenKeyUrl() +{ + KGpgNode *cur = iview->selectedNode(); + if (cur == NULL) + return; + + QString id; + + switch (cur->getType()) { + case ITYPE_PAIR: + case ITYPE_PUBLIC: { + id = cur->toKeyNode()->getFingerprint(); + break; + } + case ITYPE_GPAIR: + case ITYPE_GPUBLIC: { + id = cur->getId(); + break; + } + default: + return; + } + + const QStringList servers = KGpgSettings::infoServers(); + if (servers.isEmpty()) + return; + + QString url = servers.first(); + + url.replace(QLatin1String("$$ID8$$"), id.right(8).toUpper()); + url.replace(QLatin1String("$$ID16$$"), id.toUpper()); + url.replace(QLatin1String("$$FPR$$"), id.toUpper()); + url.replace(QLatin1String("$$id8$$"), id.right(8).toLower()); + url.replace(QLatin1String("$$id16$$"), id.toLower()); + url.replace(QLatin1String("$$fpr$$"), id.toLower()); + + new KRun(url, this); +} + +void +KeysManager::clipDecrypt() +{ + const QString cliptext(kapp->clipboard()->text(m_clipboardmode).trimmed()); + + if (cliptext.isEmpty()) { + Q_ASSERT(m_trayicon != NULL); + m_trayicon->showMessage(QString(), i18n("Clipboard is empty."), QLatin1String( "kgpg" )); + return; + } + + KgpgEditor *kgpgtxtedit = new KgpgEditor(this, imodel, 0); + kgpgtxtedit->setAttribute(Qt::WA_DeleteOnClose); + connect(this, SIGNAL(fontChanged(QFont)), kgpgtxtedit, SLOT(slotSetFont(QFont))); + kgpgtxtedit->m_editor->setPlainText(cliptext); + kgpgtxtedit->m_editor->slotDecode(); + kgpgtxtedit->show(); +} + +void +KeysManager::clipSign() +{ + QString cliptext = kapp->clipboard()->text(m_clipboardmode); + if (cliptext.isEmpty()) { + Q_ASSERT(m_trayicon != NULL); + m_trayicon->showMessage(QString(), i18n("Clipboard is empty."), QLatin1String( "kgpg" )); + return; + } + + KgpgEditor *kgpgtxtedit = new KgpgEditor(this, imodel, 0); + kgpgtxtedit->setAttribute(Qt::WA_DeleteOnClose); + connect(kgpgtxtedit->m_editor, SIGNAL(verifyFinished()), kgpgtxtedit, SLOT(closeWindow())); + + kgpgtxtedit->m_editor->signVerifyText(cliptext); + kgpgtxtedit->show(); +} + +#include "keysmanager.moc" diff --git a/kgpg/keysmanager.h b/kgpg/keysmanager.h new file mode 100644 index 00000000..bc423e80 --- /dev/null +++ b/kgpg/keysmanager.h @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2009,2010,2011,2012,2013 + * Rolf Eike Beer + */ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KEYSMANAGER_H +#define KEYSMANAGER_H + +#include "ui_adduid.h" + +#include "core/kgpgkey.h" +#include "model/kgpgitemmodel.h" +#include "model/kgpgitemnode.h" + +#include +#include +#include +#include +#include +#include +#include + +class KJob; +class QEvent; + +class KSelectAction; +class KStatusBar; +class KMenu; +class KLineEdit; +class KAction; +class KJob; +class KShortcut; + +class KeyServer; +class KgpgEditor; +class KeyServer; +class KeyListProxyModel; +class KeyTreeView; +class KGpgAddUid; +class KGpgDelKey; +class KGpgImport; +class KGpgTransactionJob; + +class KStatusNotifierItem; + + +class AddUid : public QWidget, public Ui::AddUid +{ +public: + explicit AddUid( QWidget *parent ) + : QWidget( parent ) + { + setupUi( this ); + } +}; + +class KeysManager : public KXmlGuiWindow +{ + Q_OBJECT + +public: + explicit KeysManager(QWidget *parent = 0); + ~KeysManager(); + + KGpgItemModel *getModel(); + + KgpgEditor *s_kgpgEditor; + + void saveToggleOpts(void); + void showTrayMessage(const QString &message); + + /** + * @brief returns the shortcut to go to the default key in a key selection + */ + KShortcut goDefaultShortcut() const; + +private: + KToggleAction *sTrust; + KToggleAction *sCreat; + KToggleAction *sExpi; + KToggleAction *sSize; + KToggleAction *hPublic; + KToggleAction *longId; + KSelectAction *photoProps; + KSelectAction *trustProps; + +signals: + void readAgainOptions(); + void certificate(QString); + void closeAsked(); + void fontChanged(QFont); + +public slots: + void slotGenerateKey(); + void refreshkey(); + void readAllOptions(); + void showKeyInfo(const QString &keyID); + void slotSetDefaultKey(const QString &newID); + void showKeyManager(); + bool importRemoteKey(const QString &keyIDs); + bool importRemoteKeys(const QStringList &keyIDs, const bool dialog = true); + void showKeyServer(); + void showOptions(); + void slotOpenEditor(); + void slotImport(const QString &text); + void slotImport(const KUrl::List &files); + /** + * When you click on "encrypt the clipboard" in the systray, + * this slot will open the dialog to choose a key and encrypt the + * clipboard. + */ + void clipEncrypt(); + void clipDecrypt(); + void clipSign(); + void slotImportDone(int ret); + void refreshKeys(const QStringList &ids); + +protected: + bool eventFilter(QObject *, QEvent *e); + void removeFromGroups(KGpgKeyNode *nd); + void setDefaultKeyNode(KGpgKeyNode *key); + +private slots: + void slotGenerateKeyDone(KJob *job); + + void slotShowTrust(); + void slotShowSize(); + void slotShowCreation(); + void slotShowExpiration(); + + void slotAddUidFin(int res); + void slotDelPhotoFinished(int res); + void quitApp(); + void slotShowLongId(bool); + void slotSetTrustFilter(int); + void slotGotoDefaultKey(); + void slotDelUid(); + void slotDelUidDone(int); + void slotPrimUid(); + void slotPrimUidDone(int result); + void slotAddUid(); + void slotAddUidEnable(const QString &name); + void slotUpdatePhoto(); + void slotDeletePhoto(); + void slotAddPhoto(); + void slotAddPhotoFinished(int res); + void slotSetPhotoSize(int size); + void slotShowPhoto(); + void revokeWidget(); + void slotRevokeDialogFinished(int result); + void slotRevokeGenerated(int result); + void doPrint(const QString &txt); + void checkList(); + void slotManpage(); + void slotTip(); + void slotExportFinished(int result); + void slotProcessExportMail(int result); + void slotProcessExportClip(int result); + void readOptions(); + void slotSetDefKey(); + void confirmdeletekey(); + void deleteseckey(); + void signkey(); + void signuid(); + void caff(); + void slotCaffDone(); + void delsignkey(); + void preimportsignkey(); + void importallsignkey(); + void signatureResult(int success); + void delsignatureResult(int success); + void defaultAction(const QModelIndex &); + void defaultAction(KGpgNode *); + void slotDefaultAction(); + void showProperties(KGpgNode *); + void keyproperties(); + void slotexport(); + void slotexportsec(); + void slotExportSecFinished(int result); + + void slotMenu(const QPoint &); + + void slotPreImportKey(); + void slotSendEmail(); + void slotedit(); + + /** + * @brief start an "add to addressbook" operation + * + * This searches if given id already exists in the addressbook. + * The search result is handled in slotAddressbookSearchResult() + */ + void addToKAB(); + + /** + * @brief add or change the addressbook entry + * @param job the search job + * + * This handles the result of the search started in addToKAB(). + */ + void slotAddressbookSearchResult(KJob *job); + + void editGroup(); + void createNewGroup(); + void deleteGroup(); + void renameGroup(); + void slotImportRevokeTxt(const QString &revokeText); + void refreshKeyFromServer(); + void slotKeyRefreshDone(int result); + void slotregenerate(); + void secretKeyDeleted(int); + void getMissingSigs(QSet &missingKeys, const KGpgExpandableNode *nd); + void slotEditDone(int exitcode); + void importRemoteFinished(int result); + void slotDelKeyDone(int ret); + void slotSetClip(int result); + void slotOpenKeyUrl(); + + void slotNetworkUp(); + void slotNetworkDown(); + +private: + KGpgItemModel *imodel; + KeyListProxyModel *iproxy; + KeyTreeView *iview; + + KGpgAddUid *m_adduid; + KGpgTransactionJob *m_genkey; + KGpgDelKey *m_delkey; + + QString globalkeyID; + QString searchString; + + QList signList; + QList refreshList; + QHash m_addIds; ///< user ids to add to addressbook + + QClipboard::Mode m_clipboardmode; + + KMenu *m_popuppub; // popup on a public key + KMenu *m_popupsec; // popup on a secret key + KMenu *m_popupgroup; // popup on a group + KMenu *m_popupout; // popup there is no key or when the user don't right-click on a key + KMenu *m_popupsig; // popup on a signature + KMenu *m_popupphoto; // popup on a photo + KMenu *m_popupuid; // popup on an user id + KMenu *m_popuporphan; // popup on an orphan key + + KLineEdit *m_listviewsearch; + KDialog *addUidWidget; + + KAction *importSignatureKey; + KAction *importAllSignKeys; + KAction *signKey; + KAction *signUid; + KAction *signMailUid; + KAction *refreshKey; + KAction *editKey; + KAction *setPrimUid; + KAction *delUid; + KAction *delSignKey; + KAction *deleteKey; + KAction *editCurrentGroup; + KAction *delGroup; + KAction *setDefaultKey; + KAction *kserver; + KAction *openEditor; + KAction *goToDefaultKey; + KAction *exportPublicKey; + KAction *m_sendEmail; + KAction *createGroup; + KAction *m_groupRename; + KAction *m_revokeKey; + + bool showTipOfDay; + bool m_signuids; + + int keyCount; + + KGpgKeyNode *terminalkey; // the key currently edited in a terminal + + void startImport(KGpgImport *import); + + // react to network status changes + bool m_online; + Solid::Networking::Notifier *m_netnote; + void toggleNetworkActions(bool online); + + KStatusNotifierItem *m_trayicon; + void setupTrayIcon(); + + void setActionDescriptions(int cnt); + /** + * @brief show a message in the status bar + * @param msg the text to show + * @param keep if the text should stay visible or may be hidden after a while + */ + void changeMessage(const QString &msg, const bool keep = false); + /** + * @brief update the key and group counter in the status bar + */ + void updateStatusCounter(); + /** + * @brief sign the next key from signList + * @param localsign if signature should be a local (not exportable) one + * @param checklevel how careful the identity of the key was checked + */ + void signLoop(const bool localsign, const int checklevel); + /** + * @brief Opens the console when the user want to sign a key manually. + * @param signer key to sign with + * @param keyid key to sign + * @param checking how carefule the identify was checked + * @param local if signature should be local (not exportable) + */ + void signKeyOpenConsole(const QString &signer, const QString &keyid, const int checking, const bool local); +}; + +#endif // KEYSMANAGER_H diff --git a/kgpg/keysmanager.rc b/kgpg/keysmanager.rc new file mode 100644 index 00000000..1b3fea11 --- /dev/null +++ b/kgpg/keysmanager.rc @@ -0,0 +1,155 @@ + + + + + + &Keys + + + + + + + + + + + + + + + + + + + &View + + + + + &Show Details + + + + + + + + + + + + &Groups + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kgpg/keytreeview.cpp b/kgpg/keytreeview.cpp new file mode 100644 index 00000000..5edee659 --- /dev/null +++ b/kgpg/keytreeview.cpp @@ -0,0 +1,231 @@ +/* Copyright 2008 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "keytreeview.h" + +#include "model/keylistproxymodel.h" +#include "model/kgpgitemmodel.h" +#include "model/kgpgitemnode.h" +#include "transactions/kgpgexport.h" +#include "transactions/kgpgimport.h" + +#include +#include +#include +#include +#include +#include + +KeyTreeView::KeyTreeView(QWidget *parent, KeyListProxyModel *model) + : QTreeView(parent), m_proxy(model) +{ + setModel(model); + setDragEnabled(true); + setDragDropMode(DragDrop); + setAcceptDrops(true); + setEditTriggers(QTreeView::NoEditTriggers); +} + +QList +KeyTreeView::selectedNodes(bool *psame, KgpgCore::KgpgItemType *pt) const +{ + QModelIndexList selidx = selectedIndexes(); + QList ndlist; + KgpgItemType tp = 0; + bool sametype = true; + + if (selidx.count() == 0) { + if (pt != NULL) + *pt = tp; + if (psame != NULL) + *psame = sametype; + return ndlist; + } + + tp = m_proxy->nodeForIndex(selidx[0])->getType(); + + for (int i = 0; i < selidx.count(); i++) { + if (selidx[i].column() != 0) + continue; + KGpgNode *nd = m_proxy->nodeForIndex(selidx[i]); + + if (nd->getType() != tp) { + tp |= nd->getType(); + sametype = false; + } + + ndlist << nd; + } + + if (pt != NULL) + *pt = tp; + if (psame != NULL) + *psame = sametype; + return ndlist; +} + +KGpgNode * +KeyTreeView::selectedNode() const +{ + QModelIndexList selidx = selectedIndexes(); + + if (selidx.isEmpty()) + return NULL; + + return m_proxy->nodeForIndex(selidx[0]); +} + +void +KeyTreeView::selectNode(KGpgNode *nd) +{ + if (nd == NULL) + return; + + QModelIndex idx = m_proxy->nodeIndex(nd); + + selectionModel()->select(idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + selectionModel()->setCurrentIndex(idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); +} + +void +KeyTreeView::restoreLayout(KConfigGroup &cg) +{ + QStringList cols(cg.readEntry("ColumnWidths", QStringList())); + int i = 0; + + QStringList::ConstIterator it(cols.constBegin()); + const QStringList::ConstIterator itEnd(cols.constEnd()); + for (; it != itEnd; ++it) + setColumnWidth(i++, (*it).toInt()); + + while (i < model()->columnCount(QModelIndex())) { + int width = 100; + switch (i) { + case KEYCOLUMN_NAME: + width = 250; + break; + case KEYCOLUMN_EMAIL: + width = 150; + break; + case KEYCOLUMN_TRUST: + // the trust column needs to be only that big as the header which is done automatically + i++; + continue; + } + setColumnWidth(i, width); + i++; + } + + if (cg.hasKey("SortColumn")) { + Qt::SortOrder order = cg.readEntry("SortAscending", true) ? Qt::AscendingOrder : Qt::DescendingOrder; + sortByColumn(cg.readEntry("SortColumn", 0), order); + } +} + +void +KeyTreeView::saveLayout(KConfigGroup &cg) const +{ + QStringList widths; + + const int colCount = model()->columnCount(); + + for (int i = 0; i < colCount; ++i) { + widths << QString::number(columnWidth(i)); + } + cg.writeEntry("ColumnWidths", widths); + cg.writeEntry( "SortColumn", header ()->sortIndicatorSection () ); + cg.writeEntry( "SortAscending", ( header()->sortIndicatorOrder () == Qt::AscendingOrder ) ); +} + +void +KeyTreeView::contentsDragMoveEvent(QDragMoveEvent *e) +{ + e->setAccepted(KUrl::List::canDecode(e->mimeData())); +} + +void +KeyTreeView::contentsDropEvent(QDropEvent *o) +{ + KUrl::List uriList = KUrl::List::fromMimeData(o->mimeData()); + if (!uriList.isEmpty()) { + if (KMessageBox::questionYesNo(this, i18n("

Do you want to import file %1 into your key ring?

", + uriList.first().path()), QString(), KGuiItem(i18n("Import")), + KGuiItem(i18n("Do Not Import"))) != KMessageBox::Yes) + return; + + emit importDrop(uriList); + } +} + +void +KeyTreeView::startDrag(Qt::DropActions supportedActions) +{ + QList nodes = selectedNodes(); + + if (nodes.isEmpty()) + return; + + KGpgNode *nd = nodes.first(); + QString keyid = nd->getId(); + + if (!(nd->getType() & ITYPE_PUBLIC)) + return; + + KGpgExport *exp = new KGpgExport(this, QStringList(keyid)); + exp->start(); + + int result = exp->waitForFinished(); + + if (result == KGpgTransaction::TS_OK) { + QMimeData *m = new QMimeData(); + m->setText(QString::fromLatin1( exp->getOutputData() )); + QDrag *drag = new QDrag(this); + drag->setMimeData(m); + drag->exec(supportedActions, Qt::IgnoreAction); + // do NOT delete drag. + } + + delete exp; +} + +void +KeyTreeView::resizeColumnsToContents() +{ + for (int i = m_proxy->columnCount() - 1; i >= 0; i--) + resizeColumnToContents(i); +} + +void +KeyTreeView::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Return) { + if (!event->isAutoRepeat()) + emit returnPressed(); + + return; + } + QTreeView::keyPressEvent(event); +} + +bool +KeyTreeView::isEditing() const +{ + return (state() == EditingState); +} + +#include "keytreeview.moc" diff --git a/kgpg/keytreeview.h b/kgpg/keytreeview.h new file mode 100644 index 00000000..d1609c83 --- /dev/null +++ b/kgpg/keytreeview.h @@ -0,0 +1,67 @@ +/* Copyright 2008 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KEYTREEVIEW_H +#define KEYTREEVIEW_H + +#include + +#include + +#include "core/kgpgkey.h" + +class QDragMoveEvent; +class QDropEvent; + +class KGpgNode; +class KeyListProxyModel; +class KConfigGroup; + +class KeyTreeView: public QTreeView +{ + Q_OBJECT +private: + KeyListProxyModel *m_proxy; + +public: + explicit KeyTreeView(QWidget *parent = 0, KeyListProxyModel *model = 0); + + QList selectedNodes(bool *psame = NULL, KgpgCore::KgpgItemType *pt = NULL) const; + KGpgNode *selectedNode() const; + + void restoreLayout(KConfigGroup &cg); + void saveLayout(KConfigGroup &cg) const; + + bool isEditing() const; + +signals: + void importDrop(const KUrl::List &urls); + void returnPressed(); + +public slots: + void selectNode(KGpgNode *nd); + void resizeColumnsToContents(); + +protected: + virtual void contentsDragMoveEvent(QDragMoveEvent *e); + virtual void contentsDropEvent(QDropEvent *e); + virtual void startDrag(Qt::DropActions); + virtual void keyPressEvent(QKeyEvent *event); +}; + +#endif diff --git a/kgpg/kgpg.appdata.xml b/kgpg/kgpg.appdata.xml new file mode 100644 index 00000000..6df6d2c1 --- /dev/null +++ b/kgpg/kgpg.appdata.xml @@ -0,0 +1,111 @@ + + + kgpg.desktop + CC0-1.0 + GPL-2.0+ + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + KGpg + КГПГ + KGPG + КГПГ + KGPG + Kgpg + KGpg + xxKGpgxx + KGpg + 加密工具_KGpg + Encryption Tool + Eina d'encriptatge + Nástroj pro šifrování + Krypteringsværktøj + Verschlüsselungsprogramm + Encryption Tool + Herramienta de cifrado + Krüptimistööriist + Salaustyökalu + Titkosító eszköz + Strumento di cifratura + 암호화 도구 + Šifravimo įrankis + Krypteringsverktøy + Verslötel-Warktüüch + Hulpmiddel voor versleuteling + Narzędzie szyfrowania + Ferramenta de Cifra + Ferramenta de criptografia + Šifrovací nástroj + Orodje za šifriranje + Алатка за шифровање + Alatka za šifrovanje + Алатка за шифровање + Alatka za šifrovanje + Krypteringsverktyg + Програма для шифрування + xxEncryption Toolxx + 加密工具 + 加密工具 + +

+ KGpg is a simple interface for GnuPG, a powerful encryption utility. + It can help you set up and manage your keys, import and export keys, view key signatures, trust status and expiry dates. +

+

El KGpg és una interfície senzilla pel GnuPG, una utilitat potent d'encriptatge. Pot ajudar a configurar i gestionar les vostres claus, importar i exportar claus, veure les signatures de les claus, l'estat de confiança i les dates de venciment.

+

KGpg er en nem brugerflade til GnuPG, et kraftfuldt krypteringsværktøj. Det kan hjælpe dig med at opsætte og håndtere dine nøgler, importere og eksportere nøgler, vise nøglesignaturer, betroelsesstatus og udløbsdatoer.

+

KGpg ist eine einfache Bedienungsoberfläche für GnuPG, ein leistungsfähiges Dienstprogramm zur Verschlüsselung. KGpg kann Ihnen bei der Einrichtung und Verwaltung Ihrer Schlüssel und beim Import und Export von Schlüsseln helfen, kann Schlüsselsignaturen, Vertrauensstatus und Ablaufdaten anzeigen.

+

KGpg is a simple interface for GnuPG, a powerful encryption utility. It can help you set up and manage your keys, import and export keys, view key signatures, trust status and expiry dates.

+

KGpg es una sencilla interfaz para GnuPG, una potente utilidad de cifrado. Puede ayudarle a configurar y gestionar sus claves, a importar y exportar claves, a ver firmas de claves, su nivel de confianza y sus fechas de expiración.

+

KGpg on võimsa krüptimistööriista GnuPG lihtne graafiline kasutajaliides. See aitab luua ja hallata võtmeid, neid importida ja eksportida, näha võtmete allkirju, usaldusväärsust ja aegumise tähtaega.

+

KGpg on yksinkertainen käyttöliittymä GnuPG:lle, tehokkaalle salaustyökalulle. KGpg auttaa avainten luomisessa, hallinnoinnissa, tuomisessa ja viennissä. KGpg:llä voi myös katsella avainten allekirjoituksia, luotettavuutta sekä vanhentumispäiviä.

+

A KGpg egy egyszerű felület a GnuPG-hez, a hatékony titkosító segédprogramhoz. Segíthet a kulcsok beállításában és kezelésében, kulcsok importálásában és exportálásában, kulcsaláírások, megbízhatósági állapot és lejárati idők megtekintésében.

+

KPgp è un'interfaccia semplice per GnuPG, un potente strumento di cifratura. Ti aiuta a configurare e gestire le tue chiavi, importare ed esportare le chiavi, visualizzarne le firme, lo stato di affidabilità e la data di scadenza.

+

KGpg는 강력한 암호화 유틸리티 GnuPG의 GUI 인터페이스입니다. 키를 설정하고 관리할 수 있으며, 키를 내보내고 가져오고, 키 서명, 신뢰 상태, 만료일을 볼 수 있습니다.

+

KGpg er en enkel brukerflate for GnuPG, et kraftig krypteringsverktøy. Det kan hjelpe til å opprette og håndtere nøkler, importere og eksportere nøkler, vise nøkkelsignaturer, tillitsstatus og utløpsdatoer.

+

KGpg is en eenfach Böversiet för GnuPGP, en deegt Verslötelwarktüüch. Dat kann Di bi't Opstellen un Plegen vun Dien Slötels hölpen, Slötels im- un exporteren un Slötelünnerschriften, Totroenstatus un Aflooptieden wiesen.

+

KGpg is een eenvoudig interface voor GnuPG, een krachtig hulpmiddel voor versleuteling. Het kan u helpen met het instellen en beheren van uw sleutels, sleutels im- en exporteren, ondertekening met sleutels, de vertrouwensstatus en verloopdatums bekijken.

+

KGpg jest prostym układem sterownia dla GnuPG, zaawansowanego narzędzia szyfrowania. Pomoże ustawić i zarządzać kluczami, importować i eksportować klucze, oglądać podpisy kluczy, stan zaufania oraz daty przedawnienia.

+

O KGpg é uma interface simples para o GnuPG, um utilitário poderoso de cifra ou encriptação. Podê-lo-á ajudar a configurar e a gerir as suas chaves, importá-las e exportá-las, ver as assinaturas das chaves, o estado e as datas de validade.

+

KGpg é uma interface simples para o GnuPG, um utilitário de criptografia poderoso. Ele pode ajudá-lo a configurar e a gerenciar as suas chaves, importá-las e exportá-las, ver as assinaturas das chaves, o estado e as datas de validade.

+

KGpg je jednoduché rozhranie pre GnuPG, silný šifrovací nástroj. Môže vám pomôcť nastaviť a spravovať vaše kľúče, importovať a exportovať kľúče, prezerať podpisy kľúčov, stav dôveryhodnosti a dátumy expirácie.

+

KGpg je preprost vmesnik za močno šifrirno orodje GnuPG. Pomaga vam lahko pri nastavljanju, upravljanju in uvažanju/izvažanju ključev. Z njim si lahko ogledate podpise ključev ter njihovo stanje zaupanja in datum preteka.

+

КГПГ је једноставно сучеље за ГнуПГ, моћну алатку за шифровање. Може вам помоћи да постављате кључеве, управљате њима, увозите их и извозите, као и да приказујете отиске, поузданост и датуме истицања кључева.

+

KGPG je jednostavno sučelje za GnuPG, moćnu alatku za šifrovanje. Može vam pomoći da postavljate ključeve, upravljate njima, uvozite ih i izvozite, kao i da prikazujete otiske, pouzdanost i datume isticanja ključeva.

+

КГПГ је једноставно сучеље за ГнуПГ, моћну алатку за шифровање. Може вам помоћи да постављате кључеве, управљате њима, увозите их и извозите, као и да приказујете отиске, поузданост и датуме истицања кључева.

+

KGPG je jednostavno sučelje za GnuPG, moćnu alatku za šifrovanje. Može vam pomoći da postavljate ključeve, upravljate njima, uvozite ih i izvozite, kao i da prikazujete otiske, pouzdanost i datume isticanja ključeva.

+

Kgpg är ett enkelt gränssnitt för GnuPG, ett kraftfullt krypteringsverktyg. Det kan hjälpa till att ställa in och hantera nycklar, importera och exportera nycklar, samt granska nyckelsignaturer, pålitlighetsstatus och utgångsdatum.

+

KGpg — простий інтерфейс до GnuPG, потужного засобу шифрування. Програма допоможе вам у керуванні ключами, імпортуванні та експортуванні ключів, перегляді підписів ключів, станів довіри до ключів та даних щодо строку дії ключів.

+

xxKGpg is a simple interface for GnuPG, a powerful encryption utility. It can help you set up and manage your keys, import and export keys, view key signatures, trust status and expiry dates.xx

+

KGpg 是压缩工具 GnuPG 的简单界面,可以设置和管理密钥,导入导出密钥,查看密钥签名、信任状态和过期时间。

+

KGpg 是 GnuPG 的簡易介面。它可以協助您設定並管理您的金鑰,匯入或匯出,檢視金鑰簽章,信任狀態與到期日等等。

+
+ http://utils.kde.org/projects/kgpg/ + https://bugs.kde.org/enter_bug.cgi?format=guided&product=kgpg + http://docs.kde.org/stable/en/kdeutils/kgpg/index.html + + + http://kde.org/images/screenshots/kgpg.png + + + KDE + + kgpg + +
diff --git a/kgpg/kgpg.cpp b/kgpg/kgpg.cpp new file mode 100644 index 00000000..e42cdcfb --- /dev/null +++ b/kgpg/kgpg.cpp @@ -0,0 +1,188 @@ +/*************************************************************************** + kgpg.cpp - description + ------------------- + begin : Mon Nov 18 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpg.h" + +#include "gpgproc.h" +#include "kgpgsettings.h" +#include "keysmanager.h" +#include "kgpg_interface.h" +#include "kgpgexternalactions.h" +#include "kgpginterface.h" +#include "core/images.h" +#include "editor/kgpgeditor.h" +#include "transactions/kgpgimport.h" + +#include +#include +#include +#include +#include +#include + +using namespace KgpgCore; + +KGpgApp::KGpgApp() + : KUniqueApplication(), + running(false), + w(NULL), + s_keyManager(0) +{ +} + +KGpgApp::~KGpgApp() +{ + delete s_keyManager; +} + +void KGpgApp::slotHandleQuit() +{ + s_keyManager->saveToggleOpts(); + quit(); +} + +void KGpgApp::assistantOver(const QString &defaultKeyId) +{ + if (!defaultKeyId.isEmpty()) + s_keyManager->slotSetDefaultKey(defaultKeyId); + + s_keyManager->show(); + s_keyManager->raise(); +} + +int KGpgApp::newInstance() +{ + if (!running) { + running = true; + + const QString gpgPath(KGpgSettings::gpgConfigPath()); + + const QString gpgError = GPGProc::getGpgStartupError(KGpgSettings::gpgBinaryPath()); + if (!gpgError.isEmpty()) { + KMessageBox::detailedError(0, i18n("GnuPG failed to start.
You must fix the GnuPG error first before running KGpg."), gpgError, i18n("GnuPG error")); + KApplication::quit(); + } + + s_keyManager = new KeysManager(); + + w = new KGpgExternalActions(s_keyManager, s_keyManager->getModel()); + + connect(s_keyManager, SIGNAL(readAgainOptions()), w, SLOT(readOptions())); + connect(w, SIGNAL(updateDefault(QString)), SLOT(assistantOver(QString))); + connect(w, SIGNAL(createNewKey()), s_keyManager, SLOT(slotGenerateKey())); + + if (!gpgPath.isEmpty()) { + if ((KgpgInterface::getGpgBoolSetting(QLatin1String( "use-agent" ), gpgPath)) && (qgetenv("GPG_AGENT_INFO").isEmpty())) + KMessageBox::sorry(0, i18n("The use of GnuPG Agent is enabled in GnuPG's configuration file (%1).
" + "However, the agent does not seem to be running. This could result in problems with signing/decryption.
" + "Please disable GnuPG Agent from KGpg settings, or fix the agent.
", gpgPath)); + } + } + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + // parsing of command line args + if (args->isSet("k") || (!KGpgSettings::showSystray() && (args->count() == 0) && !args->isSet("d"))) { + s_keyManager->show(); + KWindowSystem::setOnDesktop(s_keyManager->winId(), KWindowSystem::currentDesktop()); //set on the current desktop + KWindowSystem::unminimizeWindow(s_keyManager->winId()); //de-iconify window + s_keyManager->raise(); // set on top + } else if (args->isSet("d")) { + s_keyManager->slotOpenEditor(); + s_keyManager->hide(); + } else { + KUrl::List urlList; + + for (int ct = 0; ct < args->count(); ct++) + urlList.append(args->url(ct)); + + bool directoryInside = false; + foreach (const KUrl &url, urlList) + if (KMimeType::findByUrl(url)->name() == QLatin1String( "inode/directory" )) { + directoryInside = true; + break; + } + + if (args->isSet("e")) { + if (urlList.isEmpty()) + KMessageBox::sorry(0, i18n("No files given.")); + else if (!directoryInside) + KGpgExternalActions::encryptFiles(s_keyManager, urlList); + else + KGpgExternalActions::encryptFolders(s_keyManager, urlList); + } else if (args->isSet("s")) { + if (urlList.isEmpty()) + KMessageBox::sorry(0, i18n("No files given.")); + else if (!directoryInside) + w->showDroppedFile(urlList.first()); + else + KMessageBox::sorry(0, i18n("Cannot decrypt and show folder.")); + } else if (args->isSet("S")) { + if (urlList.isEmpty()) + KMessageBox::sorry(0, i18n("No files given.")); + else if (!directoryInside) + KGpgExternalActions::signFiles(s_keyManager, urlList); + else + KMessageBox::sorry(0, i18n("Cannot sign folder.")); + } else if (args->isSet("V") != 0) { + if (urlList.isEmpty()) + KMessageBox::sorry(0, i18n("No files given.")); + else if (!directoryInside) + w->verifyFile(urlList.first()); + else + KMessageBox::sorry(0, i18n("Cannot verify folder.")); + } else { + if (directoryInside && (urlList.count() > 1)) { + KMessageBox::sorry(0, i18n("Unable to perform requested operation.\nPlease select only one folder, or several files, but do not mix files and folders.")); + return 0; + } + + if (urlList.isEmpty()) { + /* do nothing */ + } else if (urlList.first().fileName().endsWith(QLatin1String(".sig"))) { + w->verifyFile(urlList.first()); + } else { + bool haskeys = false; + bool hastext = false; + foreach (const KUrl &url, urlList) { + QFile qfile(url.path()); + if (qfile.open(QIODevice::ReadOnly)) { + const int probelen = 4096; + QTextStream t(&qfile); + QString probetext(t.read(probelen)); + qfile.close(); + + if (KGpgImport::isKey(probetext, probetext.length() == probelen)) + haskeys = true; + else + hastext = true; + } + } + + if (hastext) { + KGpgExternalActions::decryptFiles(s_keyManager, urlList); + } else if (haskeys) { + s_keyManager->slotImport(urlList); + } + } + } + } + + return 0; +} + +#include "kgpg.moc" diff --git a/kgpg/kgpg.desktop b/kgpg/kgpg.desktop new file mode 100755 index 00000000..5865efb0 --- /dev/null +++ b/kgpg/kgpg.desktop @@ -0,0 +1,220 @@ +# KDE Config File +[Desktop Entry] +Type=Application +Exec=kgpg %U +Icon=kgpg +X-DocPath=kgpg/index.html +MimeType=application/pgp-encrypted;application/pgp-signature;application/pgp-keys; +GenericName=Encryption Tool +GenericName[ar]=أداة للتشفير +GenericName[ast]=Ferramienta d'encriptación +GenericName[bg]=Програма за шифроване +GenericName[br]=Ostilh enrinegadur +GenericName[bs]=Alatka za šifrovanje +GenericName[ca]=Eina d'encriptatge +GenericName[ca@valencia]=Eina d'encriptatge +GenericName[cs]=Šifrovací nástroj +GenericName[cy]=Erfyn Cêl-ysgrifo +GenericName[da]=Krypteringsværktøj +GenericName[de]=Verschlüsselungsprogramm +GenericName[el]=Εργαλείο κρυπτογράφησης +GenericName[en_GB]=Encryption Tool +GenericName[eo]=Ĉifrilo +GenericName[es]=Herramienta de cifrado +GenericName[et]=Krüptimisvahend +GenericName[eu]=Zifratzeko Tresna +GenericName[fa]=میله رمزبندی +GenericName[fi]=Salaustyökalu +GenericName[fr]=Outil de chiffrement +GenericName[ga]=Uirlis Chriptithe +GenericName[gl]=Utilidade de cifraxe +GenericName[he]=כלי הצפנה +GenericName[hne]=एनक्रिप्सन औजार +GenericName[hr]=Alat za kriptiranje +GenericName[hu]=Titkosítóeszköz +GenericName[ia]=Instrumento de cryptation +GenericName[id]=Perkakas Enkripsi +GenericName[is]=Dulritunartól +GenericName[it]=Strumento di crittografia +GenericName[ja]=暗号化ツール +GenericName[kk]=Шифрлау құралы +GenericName[km]=ឧបករណ៍​អ៊ីនគ្រិប +GenericName[ko]=암호화 도구 +GenericName[lt]=Šifravimo įrankis +GenericName[lv]=Šifrēšanas rīks +GenericName[mk]=Алатка за криптирање +GenericName[mr]=कुटलिपी साधन +GenericName[nb]=Krypteringsverktøy +GenericName[nds]=Verslötel-Warktüüch +GenericName[ne]=गुप्तीकरण उपकरण +GenericName[nl]=Cryptografisch programma +GenericName[nn]=Krypteringsverktøy +GenericName[pa]=ਇੰਕ੍ਰਿਪਸ਼ਨ ਟੂਲ +GenericName[pl]=Narzędzie do szyfrowania +GenericName[pt]=Ferramenta de Encriptação +GenericName[pt_BR]=Ferramenta de criptografia +GenericName[ro]=Utilitar de criptare +GenericName[ru]=Шифрование +GenericName[sk]=Šifrovací nástroj +GenericName[sl]=Orodje za šifriranje +GenericName[sq]=Mjeti i Kriptimit +GenericName[sr]=Алатка за шифровање +GenericName[sr@ijekavian]=Алатка за шифровање +GenericName[sr@ijekavianlatin]=Alatka za šifrovanje +GenericName[sr@latin]=Alatka za šifrovanje +GenericName[sv]=Krypteringsverktyg +GenericName[ta]=சங்கேத கருவி +GenericName[tg]=Асбоби Рамзкунонӣ +GenericName[th]=เครื่องมือการเข้ารหัส +GenericName[tr]=Şifreleme Aracı +GenericName[ug]=شىفىرلاش قورالى +GenericName[uk]=Засіб для шифрування +GenericName[vi]=Công Cụ Mã Hóa +GenericName[x-test]=xxEncryption Toolxx +GenericName[zh_CN]=加密工具 +GenericName[zh_TW]=加密工具 +Comment=A GnuPG frontend +Comment[ar]=واجهة لـ GnuPG +Comment[ast]=Una interfaz GnuPG +Comment[bg]=Клиент за GnuPG +Comment[bs]=Okruženje za GnuPG +Comment[ca]=Un frontal gràfic pel GnuPG +Comment[ca@valencia]=Un frontal gràfic pel GnuPG +Comment[cs]=Rozhraní pro GnuPG +Comment[cy]=Blaen-wyneb GnuPG +Comment[da]=En GnuPG-brugerflade +Comment[de]=Eine Benutzeroberfläche für GnuPG +Comment[el]=Λογισμικό κρυπτογράφησης (GnuPG) +Comment[en_GB]=A GnuPG frontend +Comment[eo]=Fasado por GnuPG +Comment[es]=Interfaz gráfica para GnuPG +Comment[et]=GnuPG kasutajaliides +Comment[eu]=GnuPGren aurrekaldeko bat +Comment[fa]=GnuPGنرم‌افزار نهایی +Comment[fi]=GnuPG-käyttöliittymä +Comment[fr]=Une interface à GnuPG +Comment[ga]=Comhéadan do GnuPG +Comment[gl]=Unha interface para GnuPG +Comment[he]=מנשק גרפי של GnuPG +Comment[hne]=एक ग्नू-पीजी फ्रन्टएन्ड +Comment[hr]=Pristupni dio GnuPG-a +Comment[hu]=Grafikus kezelőprogram a GnuPG-hez +Comment[ia]=Un fronte anterior de GnuPG +Comment[id]=Depanan GnuPG +Comment[is]=Viðmót á GnuPG +Comment[it]=Interfaccia grafica per GnuPG +Comment[ja]=GnuPG のフロントエンド +Comment[kk]=GnuPG интерфейсі +Comment[km]=ផ្នែក​ខាង​មុខ​របស់ GnuPG +Comment[ko]=GnuPG 프론트엔드 +Comment[lt]=GnuPG naudotojo sąsaja +Comment[lv]=GnuPG priekšpuse +Comment[mr]=GnuPG फ्रंटएन्ड +Comment[nb]=En GnuPG grenseflate +Comment[nds]=En GnuPG-Böversiet +Comment[ne]=GnuPG फ्रन्टइन्ड +Comment[nl]=Een programma om GnuPG-cryptografie en -ondertekening te gebruiken +Comment[nn]=Ei GnuPG-grenseflate +Comment[pa]=GnuPG ਫਰੰਟਐਂਡ +Comment[pl]=Interfejs do GnuPG +Comment[pt]=Uma interface para o GnuPG +Comment[pt_BR]=Uma interface para o GnuPG +Comment[ro]=Interfață grafică pentru GnuPG +Comment[ru]=Управление ключами GPG +Comment[sk]=Rozhranie pre GnuPG +Comment[sl]=Začelje za GnuPG +Comment[sq]=Një frontend GnuPG +Comment[sr]=Прочеље за ГнуПГ +Comment[sr@ijekavian]=Прочеље за ГнуПГ +Comment[sr@ijekavianlatin]=Pročelje za GnuPG +Comment[sr@latin]=Pročelje za GnuPG +Comment[sv]=Gränssnitt till GnuPG +Comment[ta]=GnuPG முன் அமைப்பு +Comment[tg]=Пешохири GnuPG +Comment[th]=ฟร้อนเอนด์ของ GnuPG +Comment[tr]=Bir GnuPG arayüzü +Comment[ug]=GnuPG ئالدى ئۇچ پروگراممىسى +Comment[uk]=Інтерфейс до GnuPG +Comment[uz]=GnuPG uchun grafik interfeys +Comment[uz@cyrillic]=GnuPG учун график интерфейс +Comment[wa]=Ene eterface po GnuPG +Comment[x-test]=xxA GnuPG frontendxx +Comment[zh_CN]=GnuPG 前端 +Comment[zh_TW]=GnuPG 前端軟體 +Terminal=false +Name=KGpg +Name[ar]=KGpg +Name[ast]=KGpg +Name[bg]=KGpg +Name[br]=KGpg +Name[bs]=KGpg +Name[ca]=KGpg +Name[ca@valencia]=KGpg +Name[cs]=KGpg +Name[cy]=KGpg +Name[da]=KGpg +Name[de]=KGpg +Name[el]=KGpg +Name[en_GB]=KGpg +Name[eo]=KGpg +Name[es]=KGpg +Name[et]=KGpg +Name[eu]=KGpg +Name[fi]=KGpg +Name[fr]=KGpg +Name[ga]=KGpg +Name[gl]=KGpg +Name[he]=KGpg +Name[hne]=के-जीपीजी +Name[hr]=KGpg +Name[hu]=KGpg +Name[ia]=KGpg +Name[id]=KGpg +Name[is]=KGpg +Name[it]=KGpg +Name[ja]=KGpg +Name[kk]=KGpg +Name[km]=KGpg +Name[ko]=KGpg +Name[lt]=KGpg +Name[lv]=KGpg +Name[mk]=KGpg +Name[mr]=के-जीपीजी +Name[ms]=KGpg +Name[nb]=KGpg +Name[nds]=KGpg +Name[ne]=KGpg +Name[nl]=KGPG +Name[nn]=KGpg +Name[pa]=KGpg +Name[pl]=KGpg +Name[pt]=KGpg +Name[pt_BR]=KGpg +Name[ro]=KGpg +Name[ru]=KGpg +Name[sk]=KGpg +Name[sl]=KGpg +Name[sq]=KGpg +Name[sr]=КГПГ +Name[sr@ijekavian]=КГПГ +Name[sr@ijekavianlatin]=KGPG +Name[sr@latin]=KGPG +Name[sv]=Kgpg +Name[ta]= KGpg +Name[tg]=KGpg +Name[th]=KGpg +Name[tr]=KGpg +Name[ug]=KGpg +Name[uk]=KGpg +Name[uz]=KGpg +Name[uz@cyrillic]=KGpg +Name[vi]=KGpg +Name[wa]=KGpg +Name[x-test]=xxKGpgxx +Name[zh_CN]=KGpg +Name[zh_TW]=KGpg + +X-KDE-autostart-after=panel +X-KDE-autostart-condition=kgpgrc:User Interface:AutoStart:false + +Categories=Qt;KDE;Utility;X-KDE-Utilities-PIM; diff --git a/kgpg/kgpg.h b/kgpg/kgpg.h new file mode 100644 index 00000000..5c9d68ed --- /dev/null +++ b/kgpg/kgpg.h @@ -0,0 +1,63 @@ +/*************************************************************************** + kgpg.h - description + ------------------- + begin : Mon Nov 18 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGAPPLET_H +#define KGPGAPPLET_H + +#include +#include +#include +#include + +class KCmdLineArgs; +class KeysManager; +class KGpgExternalActions; +class QString; + +static const char * const EMailTemplateText=I18N_NOOP( + "Hi,\n\nplease find attached the user id '%UIDNAME%' of your key %KEYID% signed by me. " + "This mail is encrypted with that key to make sure you control both the email address and the key.\n\n" + "If you have multiple user ids, I sent the signature for each user id separately to that user id's associated email address. " + "You can import the signatures by running each through `gpg --import` after you have decrypted them with `gpg --decrypt`.\n\n" + "If you are using KGpg store the attachment to disk and then import it. Just select `Import Key...` from `Keys` menu and open the file.\n\n" + "Note that I did not upload your key to any keyservers. If you want this new signature to be available to others, please upload it yourself. " + "With GnuPG this can be done using gpg --keyserver subkeys.pgp.net --send-key %KEYID%.\n\n" + "With KGpg you can right click on the key once you imported all user ids and choose `Export Public Key...`.\n\n" + "If you have any questions, don't hesitate to ask.\n"); + +class KGpgApp : public KUniqueApplication +{ + Q_OBJECT + +public: + KGpgApp(); + ~KGpgApp(); + + int newInstance (); + bool running; + KShortcut goHome; + +private: + KGpgExternalActions *w; + KeysManager *s_keyManager; + +private slots: + void slotHandleQuit(); + void assistantOver(const QString &defaultKeyId); +}; + +#endif // KGPGAPPLET_H diff --git a/kgpg/kgpg.kcfg b/kgpg/kgpg.kcfg new file mode 100644 index 00000000..c38b3ae7 --- /dev/null +++ b/kgpg/kgpg.kcfg @@ -0,0 +1,278 @@ + + + + kglobalsettings.h + kgpg.h + + + + + + + + + + + + + + + false + + + + + + + false + + + + true + + + + false + + + + false + + + + false + + + + false + + + + false + + + + + + + + + + gpg + + + + + + + + + + + + Disable + + + + true + + + + + + + true + + + + true + + + + true + + + + true + + + + false + + + + false + + + + + + + + + + + Undefined + + + + + + + false + + + + false + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Disabled + + + + + + + + + Disabled + + + + true + + + + 10 + + + + + + + true + + + + + + + 144,255,0 + + + + 30,30,30 + + + + 255,255,255 + + + + 172,0,0 + + + + 68,0,255 + + + + 255,255,0 + + + + 150,150,150 + + + + + + + KGlobalSettings::fixedFont() + + + + + + + + + + + + Alphabetical + + + + + + + + hkp://wwwkeys.pgp.net,hkp://pgp.dtype.org,hkp://search.keyserver.net,hkp://subkeys.pgp.net,hkp://wwwkeys.pgp.net,hkp://wwwkeys.us.pgp.net + + + + http://pgp.cs.uu.nl/stats/$$ID8$$.html,http://webware.lysator.liu.se/jc/wotsap/wots/latest/keystatistics/0x$$ID8$$.txt,http://pgp.surfnet.nl:11371/pks/lookup?op=vindex&fingerprint=on&search=0x$$ID8$$;http://biglumber.com/x/web?pk=$$FPR$$ + + + + + + + false + + + + + + + i18n(EMailTemplateText) + + + + + + + + All + + + diff --git a/kgpg/kgpgKeyInfo.ui b/kgpg/kgpgKeyInfo.ui new file mode 100644 index 00000000..af7a77fd --- /dev/null +++ b/kgpg/kgpgKeyInfo.ui @@ -0,0 +1,468 @@ + + + kgpgKeyInfo + + + + 0 + 0 + 558 + 488 + + + + Kgpg + + + true + + + + + 10 + 20 + 521 + 431 + + + + + + + 0 + + + + + Key properties + + + + + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + + Qt::TextSelectableByMouse + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Key ID: + + + + + + + + + + Qt::TextSelectableByMouse + + + + + + + Comment: + + + + + + + + + + Qt::PlainText + + + Qt::TextSelectableByMouse + + + + + + + Creation: + + + + + + + + + + Qt::TextSelectableByMouse + + + + + + + Expiration: + + + + + + + + + + Qt::TextSelectableByMouse + + + + + + + Trust: + + + + + + + Owner trust: + + + + + + + + 0 + 0 + + + + + 128 + 0 + + + + QComboBox::AdjustToContents + + + + I do not know + + + + + I do NOT trust + + + + + Marginally + + + + + Fully + + + + + Ultimately + + + + + + + + Algorithm: + + + + + + + + + + Qt::TextSelectableByMouse + + + + + + + Length: + + + + + + + + + + Qt::TextSelectableByMouse + + + + + + + Capabilities + + + + + + + + 0 + 0 + + + + + + + Qt::PlainText + + + true + + + Qt::TextSelectableByMouse + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + QLayout::SetMinAndMaxSize + + + 5 + + + + + + 0 + 0 + + + + Photo + + + + + + + + + 140 + 120 + + + + + 140 + 32767 + + + + <qt><b>Photo:</b><p>A photo can be included with a public key for extra security. The photo can be used as an additional method of authenticating the key. However, it should not be relied upon as the only form of authentication.</p></qt> + + + QFrame::Box + + + 2 + + + No Photo + + + Qt::AlignCenter + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + Disable key + + + + + + + Change Expiration + + + + + + + Change Passphrase + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + Fingerprint + + + + QLayout::SetMinimumSize + + + + + + 0 + 0 + + + + + + + Qt::TextSelectableByMouse + + + + + + + + + + + + KDialog + QDialog +
kdialog.h
+ 1 +
+ + KPushButton + QPushButton +
kpushbutton.h
+
+ + KUrlLabel + QLabel +
kurllabel.h
+
+ + KComboBox + QComboBox +
kcombobox.h
+
+
+ + +
diff --git a/kgpg/kgpgchangekey.cpp b/kgpg/kgpgchangekey.cpp new file mode 100644 index 00000000..11ef03ce --- /dev/null +++ b/kgpg/kgpgchangekey.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2008,2009,2010,2012,2014 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgchangekey.h" + +#include "model/kgpgitemnode.h" +#include "transactions/kgpgchangetrust.h" +#include "transactions/kgpgchangeexpire.h" +#include "transactions/kgpgchangedisable.h" + +#include + +KGpgChangeKey::KGpgChangeKey(KGpgKeyNode *node, QWidget *widget) + : QObject(NULL), + m_expiration(node->getExpiration()), + m_key(*node->copyKey()), + m_node(node), + m_current(NULL), + m_parentWidget(widget), + m_step(0), + m_failed(0), + m_autodestroy(false) +{ + m_disable = !m_key.valid(); + m_owtrust = m_key.ownerTrust(); +} + +KGpgChangeKey::~KGpgChangeKey() +{ + Q_ASSERT(m_current == NULL); +} + +void KGpgChangeKey::setExpiration(const QDateTime &date) +{ + m_expiration = date; +} + +void KGpgChangeKey::setDisable(const bool disable) +{ + m_disable = disable; +} + +void KGpgChangeKey::setOwTrust(const gpgme_validity_t trust) +{ + m_owtrust = trust; +} + +bool KGpgChangeKey::apply() +{ + if (!wasChanged()) { + emit done(0); + return true; + } + + if (m_step != 0) + return false; + + m_step = 0; + m_failed = 0; + + nextStep(0); + + return true; +} + +void KGpgChangeKey::nextStep(int result) +{ + if (m_step == 0) { + Q_ASSERT(sender() == NULL); + Q_ASSERT(m_current == NULL); + } else { + Q_ASSERT(sender() != NULL); + Q_ASSERT(sender() == m_current); + sender()->deleteLater(); + m_current = NULL; + } + + m_step++; + + switch (m_step) { + case 1: + if (m_expiration != m_key.expirationDate()) { + m_current = new KGpgChangeExpire(m_parentWidget, m_key.fingerprint(), m_expiration); + + connect(m_current, SIGNAL(done(int)), SLOT(nextStep(int))); + + m_current->start(); + break; + } else { + m_step++; + } + // fall through + case 2: + if (result == KGpgTransaction::TS_OK) { + m_key.setExpiration(m_expiration); + } else { + m_failed |= 1; + } + if (m_owtrust != m_key.ownerTrust()) { + m_current = new KGpgChangeTrust(m_parentWidget, m_key.fingerprint(), m_owtrust); + + connect(m_current, SIGNAL(done(int)), SLOT(nextStep(int))); + + m_current->start(); + break; + } else { + m_step++; + } + // fall through + case 3: + if (result == KGpgTransaction::TS_OK) { + m_key.setOwnerTrust(m_owtrust); + } else { + m_failed |= 2; + } + if (m_key.valid() == m_disable) { + m_current = new KGpgChangeDisable(m_parentWidget, m_key.fingerprint(), m_disable); + + connect(m_current, SIGNAL(done(int)), SLOT(nextStep(int))); + + m_current->start(); + break; + } else { + m_step++; + } + // fall through + default: + if (result == KGpgTransaction::TS_OK) { + m_key.setValid(!m_disable); + } else { + m_failed |= 4; + } + m_step = 0; + emit done(m_failed); + if (m_autodestroy) { + if (m_node) + emit keyNeedsRefresh(m_node); + deleteLater(); + } + } +} + +bool KGpgChangeKey::wasChanged() +{ + if (m_key.expirationDate() != m_expiration) + return true; + + if (m_key.ownerTrust() != m_owtrust) + return true; + + if (m_key.valid() == m_disable) + return true; + + return false; +} + +void KGpgChangeKey::selfdestruct(const bool applyChanges) +{ + m_autodestroy = true; + + // if apply is already running it will take care of everything + if (m_step != 0) + return; + + if (applyChanges && wasChanged()) + apply(); + else + deleteLater(); +} + +void KGpgChangeKey::setParentWidget(QWidget *widget) +{ + m_parentWidget = widget; + if (m_current != NULL) + m_current->setParent(widget); +} + +#include "kgpgchangekey.moc" diff --git a/kgpg/kgpgchangekey.h b/kgpg/kgpgchangekey.h new file mode 100644 index 00000000..f2dad902 --- /dev/null +++ b/kgpg/kgpgchangekey.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2008,2012,2014 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGCHANGEKEY_H +#define KGPGCHANGEKEY_H + +#include "core/kgpgkey.h" + +#include +#include +#include + +class KGpgKeyNode; +class KGpgTransaction; +class QWidget; + +/** + * @short A class for changing several properties of a key at once + * + * This class can enable or disable a key and at the same time change owner + * trust and expiration. It may also run in "detached" mode, i.e. the creator + * may be destroyed and the class will finish it's operation and delete itself + * when done. + * + * The class may be reused, i.e. if one change operation finished another one + * can be started (for the same key). It does not take care of any locking, + * the caller must prevent calls to this object while it works. + * + * None of the functions in this object block so it's safe to be called from + * the main application thread (e.g. it will not freeze the GUI). + * + * @author Rolf Eike Beer + */ +class KGpgChangeKey : public QObject +{ + Q_OBJECT + +public: + /** + * Creates a change object for a given key node + * + * @param node pointer to key node to take care of + * @param widget parent widget for password dialogs + * + * KGpgChangeKey stores a copy of the key object of the node + * internally to track changes it made. Once everything is + * finished the caller get notified that this node needs refresh. + * + * The widget parameter will not be used to parent this object as + * it may need to run even after the parent window was closed. + * This widget will be used as parent for the transactions so that + * they can show a passphrase prompt with correct widget inheritance, + * otherwise the modal passphrase dialog may be blocked by the modal + * key info dialog. Do not forget to call setParentWidget() if you + * destroy the parent widget while this still needs to run. + */ + KGpgChangeKey(KGpgKeyNode *node, QWidget *widget); + /** + * Destroys the object + */ + ~KGpgChangeKey(); + + /** + * Cache new expiration date + * + * @param date new expiration date or QDateTime() if key should get + * unlimited lifetime + */ + void setExpiration(const QDateTime &date); + + /** + * Cache new disable flag + * + * @param disable if the key should become disabled or not + */ + void setDisable(const bool disable); + + /** + * Cache new owner trust + * + * @param trust new owner trust level + */ + void setOwTrust(const gpgme_validity_t trust); + + /** + * Apply all cached changes to the key + * + * @return true if started successfully or false if already running + * (this should never happen). + * + * It is save to call this function if there were no changes to the + * key. It will detect this case and will exit immediately. + * + * The done() signal is emitted when this function has done all the + * work. It is also emitted if the function was called without work to + * do. This case is not considered an error. + */ + bool apply(); + + /** + * Checks if the cached values differ from those of the key + * + * @return true if the cached values differ from the stored key + * + * This compares the cached values to those of the stored key. If you + * change one value forth and back this function would return false at + * the end. + */ + bool wasChanged(); + + /** + * Tell the object to remove itself once all work is done + * + * @param applyChanges if pending changes should be applied or dropped + * + * This function may safely be called whether apply() is running or not. + * If applyChanges is set to yes apply() is called and the object + * deletes itself once all changes are done. If apply() already runs + * it is noticed to destruct afterwards. If applyChanges is set to + * false and apply() is not running or if no work has to be done the + * object is destroyed the next time the event loop runs. + */ + void selfdestruct(const bool applyChanges); + + /** + * @brief set a new parent widget for the transactions + */ + void setParentWidget(QWidget *widget); +signals: + /** + * This signal gets emitted every time apply() has done all of it's work. + * + * The result argument will be 0 if everything went fine. If no work + * had to be done that was fine, too. If anything goes wrong it will be + * a logic or of every change that caused trouble which can be + * expiration date (1), owner trust (2), or disable (4). + */ + void done(int result); + + /** + * This signal get's emitted if apply finishes in detached mode + * + * When the class is in detached mode (i.e. selfdestruct() was called) + * and the key operation finishes this signal is emitted. If apply() + * finishes and the object is not in detached mode only done() is + * emitted. + * + * The reason for this behaviour is that if the owner is around he can + * take care of noticing it's parent when it's the best time (e.g. on + * dialog close). If the owner is gone we need to inform his parent + * about the change ourself. When the owner is around we usually don't + * want to refresh the key immediately as the user might do another + * change and we want to avoid useless refreshes as they are rather + * expensive. + */ + void keyNeedsRefresh(KGpgKeyNode *node); + +private slots: + /** + * @internal + */ + void nextStep(int result); + +private: + QDateTime m_expiration; + bool m_disable; + gpgme_validity_t m_owtrust; + KgpgCore::KgpgKey m_key; + KGpgKeyNode *m_node; + KGpgTransaction *m_current; ///< the currently active transaction object + QWidget *m_parentWidget; + int m_step; + int m_failed; + bool m_autodestroy; +}; + +#endif // KGPGCHANGEKEY_H diff --git a/kgpg/kgpgexternalactions.cpp b/kgpg/kgpgexternalactions.cpp new file mode 100644 index 00000000..c29ff84d --- /dev/null +++ b/kgpg/kgpgexternalactions.cpp @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2008,2009,2010,2011,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgexternalactions.h" + +#include "detailedconsole.h" +#include "foldercompressjob.h" +#include "keyservers.h" +#include "keysmanager.h" +#include "kgpgfirstassistant.h" +#include "kgpginterface.h" +#include "kgpgsettings.h" +#include "kgpgtextinterface.h" +#include "selectpublickeydialog.h" +#include "selectsecretkey.h" +#include "core/images.h" +#include "editor/kgpgeditor.h" +#include "editor/kgpgtextedit.h" +#include "transactions/kgpgdecrypt.h" +#include "transactions/kgpgencrypt.h" +#include "transactions/kgpgsigntext.h" +#include "transactions/kgpgtransactionjob.h" +#include "transactions/kgpgverify.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +KGpgExternalActions::KGpgExternalActions(KeysManager *parent, KGpgItemModel *model) + : QObject(parent), + compressionScheme(0), + m_model(model), + m_kgpgfoldertmp(NULL), + m_keysmanager(parent) +{ + readOptions(); +} + +KGpgExternalActions::~KGpgExternalActions() +{ + delete m_kgpgfoldertmp; +} + +void KGpgExternalActions::encryptFiles(KeysManager *parent, const KUrl::List &urls) +{ + Q_ASSERT(!urls.isEmpty()); + + KGpgExternalActions *encActions = new KGpgExternalActions(parent, parent->getModel()); + + KgpgSelectPublicKeyDlg *dialog = new KgpgSelectPublicKeyDlg(parent, parent->getModel(), encActions->goDefaultKey(), false, urls); + connect(dialog, SIGNAL(accepted()), encActions, SLOT(slotEncryptionKeySelected())); + connect(dialog, SIGNAL(rejected()), dialog, SLOT(deleteLater())); + connect(dialog, SIGNAL(rejected()), encActions, SLOT(deleteLater())); + dialog->show(); +} + +void KGpgExternalActions::slotEncryptionKeySelected() +{ + KgpgSelectPublicKeyDlg *dialog = qobject_cast(sender()); + Q_ASSERT(dialog != NULL); + sender()->deleteLater(); + + QStringList opts; + QString defaultKey; + + if (KGpgSettings::encryptFilesTo()) { + if (KGpgSettings::pgpCompatibility()) + opts << QLatin1String( "--pgp6" ); + + defaultKey = KGpgSettings::fileEncryptionKey(); + } + + KGpgEncrypt::EncryptOptions eopt = KGpgEncrypt::DefaultEncryption; + + if (dialog->getUntrusted()) + eopt |= KGpgEncrypt::AllowUntrustedEncryption; + if (dialog->getArmor()) + eopt |= KGpgEncrypt::AsciiArmored; + if (dialog->getHideId()) + eopt |= KGpgEncrypt::HideKeyId; + + if (KGpgSettings::allowCustomEncryptionOptions()) { + const QString customopts(dialog->getCustomOptions().isEmpty()); + + if (!customopts.isEmpty()) + opts << customopts.split(QLatin1Char(' '), QString::SkipEmptyParts); + } + + QStringList keys = dialog->selectedKeys(); + + if (!defaultKey.isEmpty() && !keys.contains(defaultKey)) + keys.append(defaultKey); + + if (dialog->getSymmetric()) + keys.clear(); + + KGpgEncrypt *enc = new KGpgEncrypt(dialog->parent(), keys, dialog->getFiles(), eopt, opts); + KGpgTransactionJob *encjob = new KGpgTransactionJob(enc); + + KIO::getJobTracker()->registerJob(encjob); + encjob->start(); + + deleteLater(); +} + +void KGpgExternalActions::encryptFolders(KeysManager *parent, const KUrl::List &urls) +{ + KTemporaryFile *tmpfolder = new KTemporaryFile(); + + if (!tmpfolder->open()) { + delete tmpfolder; + KMessageBox::sorry(parent, i18n("Cannot create temporary file for folder compression."), i18n("Temporary File Creation")); + return; + } + + if (KMessageBox::Continue != KMessageBox::warningContinueCancel(parent, + i18n("KGpg will now create a temporary archive file:
%1 to process the encryption. " + "The file will be deleted after the encryption is finished.
", + tmpfolder->fileName()), i18n("Temporary File Creation"), KStandardGuiItem::cont(), + KStandardGuiItem::cancel(), QLatin1String( "FolderTmpFile" ))) { + delete tmpfolder; + return; + } + + KGpgExternalActions *encActions = new KGpgExternalActions(parent, parent->getModel()); + KgpgSelectPublicKeyDlg *dialog = new KgpgSelectPublicKeyDlg(parent, parent->getModel(), encActions->goDefaultKey(), false, urls); + encActions->m_kgpgfoldertmp = tmpfolder; + + KHBox *bGroup = new KHBox(dialog->optionsbox); + + (void) new QLabel(i18n("Compression method for archive:"), bGroup); + + KComboBox *optionbx = new KComboBox(bGroup); + optionbx->setModel(new QStringListModel(FolderCompressJob::archiveNames(), bGroup)); + + connect(optionbx, SIGNAL(activated(int)), encActions, SLOT(slotSetCompression(int))); + connect(dialog, SIGNAL(accepted()), encActions, SLOT(startFolderEncode())); + connect(dialog, SIGNAL(rejected()), encActions, SLOT(deleteLater())); + connect(dialog, SIGNAL(rejected()), dialog, SLOT(deleteLater())); + + dialog->show(); +} + +void KGpgExternalActions::slotSetCompression(int cp) +{ + compressionScheme = cp; +} + +void KGpgExternalActions::startFolderEncode() +{ + KgpgSelectPublicKeyDlg *dialog = qobject_cast(sender()); + Q_ASSERT(dialog != NULL); + dialog->deleteLater(); + + const KUrl::List urls = dialog->getFiles(); + + QStringList selec = dialog->selectedKeys(); + KGpgEncrypt::EncryptOptions encOptions = KGpgEncrypt::DefaultEncryption; + const QStringList encryptOptions = dialog->getCustomOptions().split(QLatin1Char(' '), QString::SkipEmptyParts); + if (dialog->getSymmetric()) { + selec.clear(); + } else { + Q_ASSERT(!selec.isEmpty()); + } + + QString extension = FolderCompressJob::extensionForArchive(compressionScheme); + + if (dialog->getArmor()) + extension += QLatin1String( ".asc" ); + else if (KGpgSettings::pgpExtension()) + extension += QLatin1String( ".pgp" ); + else + extension += QLatin1String( ".gpg" ); + + if (dialog->getArmor()) + encOptions |= KGpgEncrypt::AsciiArmored; + if (dialog->getHideId()) + encOptions |= KGpgEncrypt::HideKeyId; + if (dialog->getUntrusted()) + encOptions |= KGpgEncrypt::AllowUntrustedEncryption; + + KUrl encryptedFile(KUrl::fromPath(urls.first().path(KUrl::RemoveTrailingSlash) + extension)); + QFile encryptedFolder(encryptedFile.path()); + dialog->hide(); + if (encryptedFolder.exists()) { + QPointer over = new KIO::RenameDialog(m_keysmanager, i18n("File Already Exists"), + KUrl(), encryptedFile, KIO::M_OVERWRITE); + if (over->exec() == QDialog::Rejected) { + dialog = NULL; + delete over; + deleteLater(); + return; + } + encryptedFile = over->newDestUrl(); + delete over; + } + + FolderCompressJob *trayinfo = new FolderCompressJob(m_keysmanager, urls, encryptedFile, m_kgpgfoldertmp, + selec, encryptOptions, encOptions, compressionScheme); + connect(trayinfo, SIGNAL(result(KJob*)), SLOT(slotFolderFinished(KJob*))); + KIO::getJobTracker()->registerJob(trayinfo); + trayinfo->start(); +} + +void KGpgExternalActions::slotFolderFinished(KJob *job) +{ + FolderCompressJob *trayinfo = qobject_cast(job); + Q_ASSERT(trayinfo != NULL); + + if (trayinfo->error()) + KMessageBox::sorry(m_keysmanager, trayinfo->errorString()); + + deleteLater(); +} + +void KGpgExternalActions::verifyFile(KUrl url) +{ + // check file signature + if (url.isEmpty()) + return; + + QString sigfile; + // try to find detached signature. + if (!url.fileName().endsWith(QLatin1String(".sig"))) { + sigfile = url.path() + QLatin1String( ".sig" ); + QFile fsig(sigfile); + if (!fsig.exists()) { + sigfile = url.path() + QLatin1String( ".asc" ); + QFile fsig(sigfile); + // if no .asc or .sig signature file included, assume the file is internally signed + if (!fsig.exists()) + sigfile.clear(); + } + } else { + sigfile = url.path(); + url = KUrl(sigfile.left(sigfile.length() - 4)); + } + + KGpgVerify *kgpv = new KGpgVerify(parent(), KUrl::List(sigfile)); + connect(kgpv, SIGNAL(done(int)), SLOT(slotVerificationDone(int))); + kgpv->start(); +} + +void KGpgExternalActions::slotVerificationDone(int result) +{ + KGpgVerify *kgpv = qobject_cast(sender()); + Q_ASSERT(kgpv != NULL); + kgpv->deleteLater(); + + if (result == KGpgVerify::TS_MISSING_KEY) { + KeyServer *kser = new KeyServer(m_keysmanager, m_model); + kser->slotSetText(kgpv->missingId()); + kser->slotImport(); + } else { + const QStringList messages = kgpv->getMessages(); + + if (messages.isEmpty()) + return; + + QStringList msglist; + foreach (QString rawmsg, messages) // krazy:exclude=foreach + msglist << rawmsg.replace(QLatin1Char('<'), QLatin1String("<")); + + (void) new KgpgDetailedInfo(m_keysmanager, KGpgVerify::getReport(messages, m_model), + msglist.join(QLatin1String("
")), + QStringList(), i18nc("Caption of message box", "Verification Finished")); + } +} + +void KGpgExternalActions::signFiles(KeysManager* parent, const KUrl::List& urls) +{ + Q_ASSERT(!urls.isEmpty()); + + KGpgExternalActions *signActions = new KGpgExternalActions(parent, parent->getModel()); + + signActions->droppedUrls = urls; + + KgpgSelectSecretKey *keydlg = new KgpgSelectSecretKey(parent, parent->getModel(), false); + connect(keydlg, SIGNAL(accepted()), signActions, SLOT(slotSignFiles())); + connect(keydlg, SIGNAL(rejected()), keydlg, SLOT(deleteLater())); + connect(keydlg, SIGNAL(rejected()), signActions, SLOT(deleteLater())); + keydlg->show(); +} + +void KGpgExternalActions::slotSignFiles() +{ + KgpgSelectSecretKey *keydlg = qobject_cast(sender()); + Q_ASSERT(keydlg != NULL); + sender()->deleteLater(); + + const QString signKeyID = keydlg->getKeyID(); + + QStringList Options; + KGpgSignText::SignOptions sopts = KGpgSignText::DetachedSignature; + if (KGpgSettings::asciiArmor()) { + Options << QLatin1String( "--armor" ); + sopts |= KGpgSignText::AsciiArmored; + } + if (KGpgSettings::pgpCompatibility()) + Options << QLatin1String( "--pgp6" ); + + if (droppedUrls.count() > 1) { + KGpgTextInterface *signFileProcess = new KGpgTextInterface(parent(), signKeyID, Options); + connect(signFileProcess, SIGNAL(fileSignFinished()), signFileProcess, SLOT(deleteLater())); + signFileProcess->signFiles(droppedUrls); + } else { + KGpgSignText *signt = new KGpgSignText(parent(), signKeyID, droppedUrls, sopts); + connect(signt, SIGNAL(done(int)), signt, SLOT(deleteLater())); + signt->start(); + } + + deleteLater(); +} + +void KGpgExternalActions::decryptFiles(KeysManager* parent, const KUrl::List &urls) +{ + KGpgExternalActions *decActions = new KGpgExternalActions(parent, parent->getModel()); + + decActions->droppedUrls = urls; + + decActions->decryptFile(urls); +} + +void KGpgExternalActions::decryptFile(KUrl::List urls) +{ + if (urls.isEmpty()) { + deleteLater(); + return; + } + + while (!urls.first().isLocalFile()) { + showDroppedFile(urls.takeFirst()); + } + + KUrl first = urls.first(); + + QString oldname(first.fileName()); + if (oldname.endsWith(QLatin1String(".gpg"), Qt::CaseInsensitive) || + oldname.endsWith(QLatin1String(".asc"), Qt::CaseInsensitive) || + oldname.endsWith(QLatin1String(".pgp"), Qt::CaseInsensitive)) + oldname.chop(4); + else + oldname.append(QLatin1String( ".clear" )); + + KUrl swapname(first.directory(KUrl::AppendTrailingSlash) + oldname); + QFile fgpg(swapname.path()); + if (fgpg.exists()) { + QPointer over = new KIO::RenameDialog(m_keysmanager, + i18n("File Already Exists"), KUrl(), swapname, KIO::M_OVERWRITE); + if (over->exec() != QDialog::Accepted) { + delete over; + urls.pop_front(); + decryptFile(urls); + return; + } + + swapname = over->newDestUrl(); + delete over; + } + + droppedUrls = urls; + KGpgDecrypt *decr = new KGpgDecrypt(this, droppedUrls.first(), swapname); + connect(decr, SIGNAL(done(int)), SLOT(slotDecryptionDone(int))); + decr->start(); +} + +void KGpgExternalActions::slotDecryptionDone(int status) +{ + KGpgDecrypt *decr = qobject_cast(sender()); + Q_ASSERT(decr != NULL); + + if (status != KGpgTransaction::TS_OK) + m_decryptionFailed << droppedUrls.first(); + + decr->deleteLater(); + + droppedUrls.pop_front(); + + if (!droppedUrls.isEmpty()) { + decryptFile(droppedUrls); + } else { + if (!m_decryptionFailed.isEmpty()) { + KMessageBox::errorList(NULL, + i18np("Decryption of this file failed:", "Decryption of these files failed:", + m_decryptionFailed.count()), m_decryptionFailed.toStringList(), + i18n("Decryption failed.")); + } + deleteLater(); + } +} + +void KGpgExternalActions::showDroppedFile(const KUrl &file) +{ + KgpgEditor *kgpgtxtedit = new KgpgEditor(m_keysmanager, m_model, 0); + connect(m_keysmanager, SIGNAL(fontChanged(QFont)), kgpgtxtedit, SLOT(slotSetFont(QFont))); + + kgpgtxtedit->m_editor->openDroppedFile(file, false); + + kgpgtxtedit->show(); +} + +void KGpgExternalActions::readOptions() +{ + clipboardMode = QClipboard::Clipboard; + if (KGpgSettings::useMouseSelection() && kapp->clipboard()->supportsSelection()) + clipboardMode = QClipboard::Selection; + + if (KGpgSettings::firstRun()) { + firstRun(); + } else if (KGpgSettings::gpgConfigPath().isEmpty()) { + if (KMessageBox::Yes == KMessageBox::questionYesNo(0, + i18n("You have not set a path to your GnuPG config file.
This may cause some surprising results in KGpg's execution." + "
Would you like to start KGpg's assistant to fix this problem?
"), + QString(), KGuiItem(i18n("Start Assistant")), KGuiItem(i18n("Do Not Start")))) + startAssistant(); + } +} + +void KGpgExternalActions::firstRun() +{ + QProcess *createConfigProc = new QProcess(this); + QStringList args; + args << QLatin1String( "--no-tty" ) << QLatin1String( "--list-secret-keys" ); + createConfigProc->start(QLatin1String( "gpg" ), args); // start GnuPG so that it will create a config file + createConfigProc->waitForFinished(); + startAssistant(); +} + +void KGpgExternalActions::startAssistant() +{ + if (m_assistant.isNull()) { + m_assistant = new KGpgFirstAssistant(m_keysmanager); + + connect(m_assistant, SIGNAL(accepted()), SLOT(slotSaveOptionsPath())); + connect(m_assistant, SIGNAL(rejected()), m_assistant, SLOT(deleteLater())); + connect(m_assistant, SIGNAL(helpClicked()), SLOT(help())); + } + + m_assistant->show(); +} + +void KGpgExternalActions::slotSaveOptionsPath() +{ + KGpgSettings::setAutoStart(m_assistant->getAutoStart()); + KGpgSettings::setGpgConfigPath(m_assistant->getConfigPath()); + KGpgSettings::setFirstRun(false); + + const QString gpgConfServer(KgpgInterface::getGpgSetting(QLatin1String( "keyserver" ), KGpgSettings::gpgConfigPath())); + if (!gpgConfServer.isEmpty()) { + // The user already had configured a keyserver, set this one as default. + QStringList serverList(KGpgSettings::keyServers()); + serverList.prepend(gpgConfServer); + KGpgSettings::setKeyServers(serverList); + } + + const QString defaultID(m_assistant->getDefaultKey()); + + KGpgSettings::self()->writeConfig(); + emit updateDefault(defaultID); + if (m_assistant->runKeyGenerate()) + emit createNewKey(); + m_assistant->deleteLater(); +} + +void KGpgExternalActions::help() +{ + KToolInvocation::invokeHelp(QString(), QLatin1String( "kgpg" )); +} + +KShortcut KGpgExternalActions::goDefaultKey() const +{ + return qobject_cast(m_keysmanager->actionCollection()->action(QLatin1String( "go_default_key" )))->shortcut(); +} + +#include "kgpgexternalactions.moc" diff --git a/kgpg/kgpgexternalactions.h b/kgpg/kgpgexternalactions.h new file mode 100644 index 00000000..5236941a --- /dev/null +++ b/kgpg/kgpgexternalactions.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2008,2009,2010,2011 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _KGPGEXTERNALACTIONS_H +#define _KGPGEXTERNALACTIONS_H + +#include +#include +#include +#include + +#include + +class KeysManager; +class KGpgFirstAssistant; +class KGpgItemModel; +class KGpgTextInterface; +class KJob; +class KShortcut; +class KTemporaryFile; +class QFont; +class QString; + +/** + * @brief handle actions called from outside the application + * + * This class takes care about everything sent to us from outside the + * application, e.g. command line arguments given on startup. + */ +class KGpgExternalActions : public QObject +{ + Q_OBJECT + +public: + KGpgExternalActions(KeysManager *parent, KGpgItemModel *model); + ~KGpgExternalActions(); + + void showDroppedFile(const KUrl &file); + void verifyFile(KUrl url); + + /** + * @brief create a detached signature for the given files + */ + static void signFiles(KeysManager* parent, const KUrl::List &urls); + + static void decryptFiles(KeysManager* parent, const KUrl::List& urls); + static void encryptFolders(KeysManager* parent, const KUrl::List &urls); + + /** + * @brief create a new object, encrypt the given files, and destroy the object + */ + static void encryptFiles(KeysManager* parent, const KUrl::List& urls); +signals: + void createNewKey(); + void updateDefault(QString); + +private: + QStringList customDecrypt; + QPointer m_assistant; + int compressionScheme; + QClipboard::Mode clipboardMode; + KGpgItemModel *m_model; + KTemporaryFile *m_kgpgfoldertmp; + + void startAssistant(); + void firstRun(); + + KUrl::List m_decryptionFailed; + KeysManager *m_keysmanager; + KUrl::List droppedUrls; + + KShortcut goDefaultKey() const; + void decryptFile(KUrl::List urls); + +private slots: + void startFolderEncode(); + void slotSaveOptionsPath(); + void slotVerificationDone(int result); + void help(); + void readOptions(); + void slotSetCompression(int cp); + void slotDecryptionDone(int status); + void slotFolderFinished(KJob *job); + void slotSignFiles(); + void slotEncryptionKeySelected(); +}; + +#endif /* _KGPGEXTERNALACTIONS_H */ diff --git a/kgpg/kgpgfirstassistant.cpp b/kgpg/kgpgfirstassistant.cpp new file mode 100644 index 00000000..47d461fe --- /dev/null +++ b/kgpg/kgpgfirstassistant.cpp @@ -0,0 +1,375 @@ +/* Copyright 2008,2010,2012 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "kgpgfirstassistant.h" + +#include "gpgproc.h" +#include "kgpginterface.h" +#include "core/kgpgkey.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KgpgCore; + +KGpgFirstAssistant::KGpgFirstAssistant(QWidget *parent) + : KAssistantDialog(parent) +{ + setCaption(i18n("KGpg Assistant")); + + QWidget *page = new QWidget(this); + QGridLayout *gridLayout = new QGridLayout(page); + gridLayout->setSpacing(6); + gridLayout->setMargin(11); + gridLayout->setContentsMargins(0, 0, 0, 0); + + QLabel *label = new QLabel(page); + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(label->sizePolicy().hasHeightForWidth()); + label->setSizePolicy(sizePolicy); + label->setFrameShape(QFrame::NoFrame); + label->setFrameShadow(QFrame::Plain); + label->setScaledContents(false); + label->setAlignment(Qt::AlignTop); + label->setWordWrap(false); + + gridLayout->addWidget(label, 0, 0, 3, 1); + + label = new QLabel(page); + label->setAlignment(Qt::AlignTop); + label->setWordWrap(true); + label->setText(i18n("This assistant will first setup some basic configuration options required for KGpg to work properly. Next, it will allow you to create your own key pair, enabling you to encrypt your files and emails.")); + + gridLayout->addWidget(label, 0, 1, 1, 1); + + QSpacerItem *spacerItem = new QSpacerItem(20, 41, QSizePolicy::Minimum, QSizePolicy::Expanding); + + gridLayout->addItem(spacerItem, 1, 1, 1, 1); + + page_welcome = addPage(page, i18n("Welcome to the KGpg Assistant")); + + page = new QWidget(this); + + gridLayout = new QGridLayout(page); + gridLayout->setSpacing(6); + gridLayout->setMargin(11); + gridLayout->setContentsMargins(0, 0, 0, 0); + + label = new QLabel(page); + label->setAlignment(Qt::AlignTop); + label->setWordWrap(true); + label->setText(i18n("KGpg needs to know which GnuPG binary to use.")); + + gridLayout->addWidget(label, 0, 1, 1, 1); + + label = new QLabel(page); + label->setAlignment(Qt::AlignTop); + label->setWordWrap(true); + label->setText(i18n("Unless you want to try some unusual settings, just click on the \"next\" button.")); + + gridLayout->addWidget(label, 1, 1, 1, 1); + + spacerItem = new QSpacerItem(20, 60, QSizePolicy::Minimum, QSizePolicy::Expanding); + + gridLayout->addItem(spacerItem, 4, 1, 1, 1); + + txtGpgVersion = new QLabel(page); + txtGpgVersion->setWordWrap(true); + + gridLayout->addWidget(txtGpgVersion, 3, 1, 1, 1); + + binURL = new KUrlRequester(page); + binURL->setFilter(i18nc("search filter for gpg binary", "gpg|GnuPG binary\n*|All files")); + QString gpgBin = KStandardDirs::findExe(QLatin1String("gpg2")); + if (gpgBin.isEmpty()) + gpgBin = KStandardDirs::findExe(QLatin1String("gpg")); + if (gpgBin.isEmpty()) + gpgBin = QLatin1String("gpg"); + binURL->setUrl(KUrl::fromPath(gpgBin)); + + connect(binURL, SIGNAL(textChanged(QString)), SLOT(slotBinaryChanged(QString))); + slotBinaryChanged(gpgBin); + + gridLayout->addWidget(binURL, 2, 1, 1, 1); + + page_binary = addPage(page, i18n("GnuPG Binary")); + + page = new QWidget(this); + + gridLayout = new QGridLayout(page); + gridLayout->setSpacing(6); + gridLayout->setMargin(11); + gridLayout->setContentsMargins(0, 0, 0, 0); + text_optionsfound = new QLabel(page); + text_optionsfound->setAlignment(Qt::AlignTop); + text_optionsfound->setWordWrap(true); + text_optionsfound->setText(i18n("Unless you want to try some unusual settings, just click on the \"next\" button.")); + + gridLayout->addWidget(text_optionsfound, 1, 1, 1, 1); + + label = new QLabel(page); + label->setAlignment(Qt::AlignTop); + label->setWordWrap(true); + label->setText(i18n("KGpg needs to know where your GnuPG configuration file is stored.")); + + gridLayout->addWidget(label, 0, 1, 1, 1); + + spacerItem = new QSpacerItem(20, 60, QSizePolicy::Minimum, QSizePolicy::Expanding); + + gridLayout->addItem(spacerItem, 4, 1, 1, 1); + + pathURL = new KUrlRequester(page); + + gridLayout->addWidget(pathURL, 3, 1, 1, 1); + + label = new QLabel(page); + label->setAlignment(Qt::AlignVCenter); + label->setWordWrap(true); + label->setText(i18n("Path to your GnuPG configuration file:")); + + gridLayout->addWidget(label, 2, 1, 1, 1); + + label = new QLabel(page); + sizePolicy.setHeightForWidth(label->sizePolicy().hasHeightForWidth()); + label->setSizePolicy(sizePolicy); + label->setFrameShape(QFrame::NoFrame); + label->setFrameShadow(QFrame::Plain); + label->setScaledContents(false); + label->setAlignment(Qt::AlignTop); + label->setWordWrap(false); + + gridLayout->addWidget(label, 0, 0, 5, 1); + page_config = addPage(page, i18n("Configuration File")); + + page = new QWidget(this); + gridLayout = new QGridLayout(page); + gridLayout->setSpacing(6); + gridLayout->setMargin(11); + gridLayout->setContentsMargins(0, 0, 0, 0); + + QHBoxLayout *hboxLayout = new QHBoxLayout(); + hboxLayout->setSpacing(6); + label = new QLabel(page); + label->setText(i18n("Your default key:")); + + hboxLayout->addWidget(label); + + CBdefault = new KComboBox(page); + QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Fixed); + sizePolicy1.setHorizontalStretch(0); + sizePolicy1.setVerticalStretch(0); + sizePolicy1.setHeightForWidth(CBdefault->sizePolicy().hasHeightForWidth()); + CBdefault->setSizePolicy(sizePolicy1); + + hboxLayout->addWidget(CBdefault); + + gridLayout->addLayout(hboxLayout, 0, 1, 1, 1); + + spacerItem = new QSpacerItem(20, 30, QSizePolicy::Minimum, QSizePolicy::Expanding); + + gridLayout->addItem(spacerItem, 1, 1, 1, 1); + + page_defaultkey = addPage(page, i18n("Default Key")); + + page = new QWidget(this); + + gridLayout = new QGridLayout(page); + gridLayout->setSpacing(6); + gridLayout->setMargin(11); + gridLayout->setContentsMargins(0, 0, 0, 0); + + binlabel = new QLabel(page); + + gridLayout->addWidget(binlabel, 0, 1, 1, 1); + + versionLabel = new QLabel(page); + + gridLayout->addWidget(versionLabel, 1, 1, 1, 1); + + defaultkeylabel = new QLabel(page); + + gridLayout->addWidget(defaultkeylabel, 2, 1, 1, 1); + + generateCB = new QCheckBox(page); + generateCB->setText(i18n("Generate new key")); + + gridLayout->addWidget(generateCB, 3, 1, 1, 1); + + spacerItem = new QSpacerItem(20, 30, QSizePolicy::Minimum, QSizePolicy::Expanding); + + gridLayout->addItem(spacerItem, 4, 1, 1, 1); + + autostartCB = new QCheckBox(page); + autostartCB->setChecked(true); + autostartCB->setText(i18n("Start KGpg automatically at KDE startup.")); + + gridLayout->addWidget(autostartCB, 5, 1, 1, 1); + + page_done = addPage(page, i18n("Done")); +} + +void +KGpgFirstAssistant::findConfigPath() +{ + const QString gpgHome = GPGProc::getGpgHome(binURL->url().path()); + QString confPath = gpgHome + QLatin1String( "gpg.conf" ); + + if (!QFile(confPath).exists()) { + confPath = gpgHome + QLatin1String( "options" ); + if (!QFile(confPath).exists()) { + if (KMessageBox::questionYesNo(0, i18n("The GnuPG configuration file was not found. Should KGpg try to create a config file ?"), QString(), KGuiItem(i18n("Create Config")), KGuiItem(i18n("Do Not Create"))) == KMessageBox::Yes) { + confPath = gpgHome + QLatin1String( "gpg.conf" ); + QFile file(confPath); + if (file.open(QIODevice::WriteOnly)) { + QTextStream stream(&file); + stream << "# GnuPG config file created by KGpg" << "\n"; + file.close(); + } + } else { + text_optionsfound->setText(i18n("The GnuPG configuration file was not found.")); + confPath.clear(); + } + } + } + + pathURL->setUrl(confPath); + + QStringList secids = KgpgInterface::readSecretKeys(); + if (secids.isEmpty()) { + setAppropriate(page_defaultkey, false); + generateCB->setChecked(true); + defaultkeylabel->setVisible(false); + return; + } + + KgpgKeyList publiclist = KgpgInterface::readPublicKeys(secids); + + generateCB->setChecked(false); + setAppropriate(page_defaultkey, true); + + CBdefault->clear(); + + foreach (const KgpgKey &k, publiclist) { + QString s; + + if (k.email().isEmpty()) + s = i18nc("Name: ID", "%1: %2", k.name(), k.id()); + else + s = i18nc("Name (Email): ID", "%1 (%2): %3", k.name(), k.email(), k.id()); + + CBdefault->addItem(s, k.fingerprint()); + } + + CBdefault->setCurrentIndex(0); +} + +void +KGpgFirstAssistant::next() +{ + if (currentPage() == page_binary) { + binlabel->setText(i18n("Your GnuPG binary is: %1", binURL->url().path())); + findConfigPath(); + } else if (currentPage() == page_config) { + QString tst, name; + m_confPath = pathURL->url().path(); + + QString defaultID = KgpgInterface::getGpgSetting(QLatin1String( "default-key" ), m_confPath); + + if (!defaultID.isEmpty()) { + for (int i = 0; i < CBdefault->count(); i++) { + if (defaultID == CBdefault->itemData(i).toString().right(defaultID.length())) { + CBdefault->setCurrentIndex(i); + break; + } + } + } + versionLabel->setText(i18n("You have GnuPG version: %1", m_gpgVersion)); + } else if (currentPage() == page_defaultkey) { + defaultkeylabel->setVisible(true); + defaultkeylabel->setText(i18n("Your default key is: %1", CBdefault->currentText())); + int i = CBdefault->currentIndex(); + if (i >= 0) { + defaultkeylabel->setToolTip(CBdefault->itemData(i).toString()); + } + } + KAssistantDialog::next(); +} + +bool +KGpgFirstAssistant::runKeyGenerate() const +{ + return generateCB->isChecked(); +} + +QString +KGpgFirstAssistant::getConfigPath() const +{ + return m_confPath; +} + +QString +KGpgFirstAssistant::getDefaultKey() const +{ + int i = CBdefault->currentIndex(); + if (i < 0) + return QString(); + else + return CBdefault->itemData(i).toString(); +} + +bool +KGpgFirstAssistant::getAutoStart() const +{ + return autostartCB->isChecked(); +} + +void +KGpgFirstAssistant::slotBinaryChanged(const QString &binary) +{ + if (binary.isEmpty()) { + setValid(page_binary, false); + return; + } + + m_gpgVersion = GPGProc::gpgVersionString(binary); + setValid(page_binary, !m_gpgVersion.isEmpty()); + if (!m_gpgVersion.isEmpty()) { + const int gpgver = GPGProc::gpgVersion(m_gpgVersion); + + if (gpgver < 0x10400) { + txtGpgVersion->setText(i18n("Your GnuPG version (%1) seems to be too old.
Compatibility with versions before 1.4.0 is no longer guaranteed.", m_gpgVersion)); + } else { + txtGpgVersion->setText(i18n("You have GnuPG version: %1", m_gpgVersion)); + } + } +} + +#include "kgpgfirstassistant.moc" diff --git a/kgpg/kgpgfirstassistant.h b/kgpg/kgpgfirstassistant.h new file mode 100644 index 00000000..51b44081 --- /dev/null +++ b/kgpg/kgpgfirstassistant.h @@ -0,0 +1,97 @@ +/* Copyright 2008 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGFIRSTASSISTANT_H +#define KGPGFIRSTASSISTANT_H + +#include + +class QCheckBox; +class QLabel; +class KPageWidgetItem; +class KUrlRequester; +class KComboBox; + +class KGpgFirstAssistant : public KAssistantDialog +{ + Q_OBJECT + +private: + KPageWidgetItem *page_welcome; + KPageWidgetItem *page_binary; + KPageWidgetItem *page_config; + KPageWidgetItem *page_defaultkey; + KPageWidgetItem *page_done; + + QLabel *defaultkeylabel; + QLabel *txtGpgVersion; + QLabel *text_optionsfound; + QLabel *versionLabel; + QLabel *binlabel; + + QCheckBox *generateCB; + QCheckBox *autostartCB; + + KComboBox *CBdefault; + + KUrlRequester *binURL; + KUrlRequester *pathURL; + + QString m_gpgVersion; + QString m_confPath; + + void findConfigPath(); + +public: + /** + * @brief constructor of KGpgFirstAssistant + */ + explicit KGpgFirstAssistant(QWidget *parent = 0); + + /** + * @brief check if key generation dialog should be started + * @return if user requests dialog to be started + */ + bool runKeyGenerate() const; + /** + * @brief get user selected GnuPG home directory + * @return path to users GnuPG directory + */ + QString getConfigPath() const; + /** + * @brief get fingerprint of default key + * @return full fingerprint or empty string if user has not selected a default key + */ + QString getDefaultKey() const; + /** + * @brief check if KGpg autostart should be activated + * @return if user requests autostart or not + */ + bool getAutoStart() const; + +public Q_SLOTS: + /** + * @brief called when "next" button is pressed + */ + virtual void next(); + +private Q_SLOTS: + void slotBinaryChanged(const QString &binary); +}; + +#endif diff --git a/kgpg/kgpginterface.cpp b/kgpg/kgpginterface.cpp new file mode 100644 index 00000000..a553139b --- /dev/null +++ b/kgpg/kgpginterface.cpp @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2009,2010,2011,2012,2013 + * Rolf Eike Beer + */ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpginterface.h" + +#include "gpgproc.h" +#include "core/convert.h" +#include "core/KGpgKeyNode.h" +#include "core/KGpgSignNode.h" +#include "core/KGpgSubkeyNode.h" +#include "core/KGpgUatNode.h" +#include "core/KGpgUidNode.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KgpgCore; + +QString KgpgInterface::getGpgSetting(const QString &name, const QString &configfile) +{ + const QString tmp(name.simplified() + QLatin1Char( ' ' )); + QFile qfile(configfile); + + if (qfile.open(QIODevice::ReadOnly) && (qfile.exists())) { + QTextStream t(&qfile); + while (!t.atEnd()) { + QString result(t.readLine().simplified()); + if (result.startsWith(tmp)) { + result = result.mid(tmp.length()).simplified(); + return result.section(QLatin1Char( ' ' ), 0, 0); + } + } + qfile.close(); + } + + return QString(); +} + +void KgpgInterface::setGpgSetting(const QString &name, const QString &value, const QString &url) +{ + QFile qfile(url); + + if (qfile.open(QIODevice::ReadOnly) && (qfile.exists())) { + const QString temp(name + QLatin1Char( ' ' )); + QString texttowrite; + bool found = false; + QTextStream t(&qfile); + + while (!t.atEnd()) { + QString result = t.readLine(); + if (result.simplified().startsWith(temp)) { + if (!value.isEmpty()) + result = temp + QLatin1Char( ' ' ) + value; + else + result.clear(); + found = true; + } + + texttowrite += result + QLatin1Char( '\n' ); + } + + qfile.close(); + if ((!found) && (!value.isEmpty())) + texttowrite += QLatin1Char( '\n' ) + temp + QLatin1Char( ' ' ) + value; + + if (qfile.open(QIODevice::WriteOnly)) { + QTextStream t(&qfile); + t << texttowrite; + qfile.close(); + } + } +} + +bool KgpgInterface::getGpgBoolSetting(const QString &name, const QString &configfile) +{ + QFile qfile(configfile); + if (qfile.open(QIODevice::ReadOnly) && (qfile.exists())) { + QTextStream t(&qfile); + while (!t.atEnd()) { + if (t.readLine().simplified().startsWith(name)) + return true; + } + qfile.close(); + } + return false; +} + +void KgpgInterface::setGpgBoolSetting(const QString &name, const bool enable, const QString &url) +{ + QFile qfile(url); + + if (qfile.open(QIODevice::ReadOnly) && (qfile.exists())) { + QString texttowrite; + bool found = false; + QTextStream t(&qfile); + + while (!t.atEnd()) { + QString result(t.readLine()); + + if (result.simplified().startsWith(name)) { + if (enable) + result = name; + else + result.clear(); + + found = true; + } + + texttowrite += result + QLatin1Char( '\n' ); + } + qfile.close(); + + if ((!found) && (enable)) + texttowrite += name; + + if (qfile.open(QIODevice::WriteOnly)) { + QTextStream t(&qfile); + t << texttowrite; + qfile.close(); + } + } +} + +/** + * @param p the process that reads the GnuPG data + * @param readNode the node where the signatures are read for + */ +static KgpgCore::KgpgKeyList +readPublicKeysProcess(GPGProc &p, KGpgKeyNode *readNode) +{ + QStringList lsp; + int items; + KgpgCore::KgpgKeyList publiclistkeys; + KgpgCore::KgpgKey *publickey = NULL; + unsigned int idIndex = 0; + QString log; + KGpgSignableNode *currentSNode = NULL; ///< the current (sub)node signatures are read for + + while ((items = p.readln(lsp)) >= 0) { + if ((lsp.at(0) == QLatin1String( "pub" )) && (items >= 10)) { + KgpgSubKeyType subtype; + KgpgSubKeyType keytype; + bool enabled = true; + if (items > 11) { + const QString &caps = lsp.at(11); + + enabled = !caps.contains(QLatin1Char('D'), Qt::CaseSensitive); + + subtype = Convert::toSubType(caps, false); + keytype = Convert::toSubType(caps, true); + } + + publiclistkeys << KgpgKey(lsp.at(4), lsp.at(2).toUInt(), Convert::toTrust(lsp.at(1)), + Convert::toAlgo(lsp.at(3).toInt()), subtype, keytype, + QDateTime::fromTime_t(lsp.at(5).toUInt())); + + publickey = &publiclistkeys.last(); + + const QString &owTrust = lsp.at(8); + if (owTrust.isEmpty()) + publickey->setOwnerTrust(GPGME_VALIDITY_UNDEFINED); + else + publickey->setOwnerTrust(Convert::toOwnerTrust(owTrust[0])); + + const QString &endDate = lsp.at(6); + if (endDate.isEmpty()) + publickey->setExpiration(QDateTime()); + else + publickey->setExpiration(QDateTime::fromTime_t(endDate.toUInt())); + + publickey->setValid(enabled); // disabled key + + idIndex = 0; + } else if (publickey && (lsp.at(0) == QLatin1String( "fpr" )) && (items >= 10)) { + const QString fingervalue(lsp.at(9)); + + publickey->setFingerprint(fingervalue); + } else if (publickey && (lsp.at(0) == QLatin1String( "sub" )) && (items >= 7)) { + KgpgSubKeyType subtype; + + if (items > 11) + subtype = Convert::toSubType(lsp.at(11), false); + + KgpgKeySub sub(lsp.at(4), lsp.at(2).toUInt(), Convert::toTrust(lsp.at(1)), + Convert::toAlgo(lsp.at(3).toInt()), subtype, QDateTime::fromTime_t(lsp.at(5).toUInt())); + + // FIXME: Please see kgpgkey.h, KgpgSubKey class + if (items <= 11) + sub.setValid(true); + else + sub.setValid(!lsp.at(11).contains(QLatin1Char( 'D' ))); + + if (lsp.at(6).isEmpty()) + sub.setExpiration(QDateTime()); + else + sub.setExpiration(QDateTime::fromTime_t(lsp.at(6).toUInt())); + + publickey->subList()->append(sub); + if (readNode == NULL) + currentSNode = NULL; + else + currentSNode = new KGpgSubkeyNode(readNode, sub); + } else if (publickey && (lsp.at(0) == QLatin1String( "uat" ))) { + idIndex++; + if (readNode != NULL) { + currentSNode = new KGpgUatNode(readNode, idIndex, lsp); + } + } else if (publickey && (lsp.at(0) == QLatin1String( "uid" )) && (items >= 10)) { + if (idIndex == 0) { + QString fullname(lsp.at(9)); + QString kmail; + if (fullname.contains(QLatin1Char( '<' )) ) { + kmail = fullname; + + if (fullname.contains(QLatin1Char( ')' )) ) + kmail = kmail.section(QLatin1Char( ')' ), 1); + + kmail = kmail.section(QLatin1Char( '<' ), 1); + kmail.truncate(kmail.length() - 1); + + if (kmail.contains(QLatin1Char( '<' ))) { + // several email addresses in the same key + kmail = kmail.replace(QLatin1Char( '>' ), QLatin1Char( ';' )); + kmail.remove(QLatin1Char( '<' )); + } + } + + QString kname(fullname.section( QLatin1String( " <" ), 0, 0)); + QString comment; + if (fullname.contains(QLatin1Char( '(' )) ) { + kname = kname.section( QLatin1String( " (" ), 0, 0); + comment = fullname.section(QLatin1Char( '(' ), 1, 1); + comment = comment.section(QLatin1Char( ')' ), 0, 0); + } + + idIndex++; + publickey->setEmail(kmail); + publickey->setComment(comment); + publickey->setName(kname); + + currentSNode = readNode; + } else { + idIndex++; + if (readNode != NULL) { + currentSNode = new KGpgUidNode(readNode, idIndex, lsp); + } + } + } else if (publickey && ((lsp.at(0) == QLatin1String( "sig" )) || (lsp.at(0) == QLatin1String( "rev" ))) && (items >= 11)) { + // there are no strings here that could have a recoded QLatin1Char( ':' ) in them + const QString signature = lsp.join(QLatin1String(":")); + + if (currentSNode != NULL) + (void) new KGpgSignNode(currentSNode, lsp); + } else { + log += lsp.join(QString(QLatin1Char( ':' ))) + QLatin1Char( '\n' ); + } + } + + if (p.exitCode() != 0) { + KMessageBox::detailedError(NULL, i18n("An error occurred while scanning your keyring"), log); + log.clear(); + } + + return publiclistkeys; +} + +KgpgKeyList KgpgInterface::readPublicKeys(const QStringList &ids) +{ + GPGProc process; + process << + QLatin1String("--with-colons") << + QLatin1String("--with-fingerprint") << + QLatin1String("--fixed-list-mode") << + QLatin1String("--list-keys") << + ids; + + process.setOutputChannelMode(KProcess::MergedChannels); + + process.start(); + process.waitForFinished(-1); + return readPublicKeysProcess(process, NULL); +} + +void KgpgInterface::readSignatures(KGpgKeyNode *node) +{ + GPGProc process; + process << + QLatin1String("--with-colons") << + QLatin1String("--with-fingerprint") << + QLatin1String("--fixed-list-mode") << + QLatin1String("--list-sigs") << + node->getId(); + + process.setOutputChannelMode(KProcess::MergedChannels); + + process.start(); + process.waitForFinished(-1); + + readPublicKeysProcess(process, node); +} + +static KgpgCore::KgpgKeyList +readSecretKeysProcess(GPGProc &p) +{ + QStringList lsp; + int items; + bool hasuid = false; + KgpgCore::KgpgKeyList result; + KgpgCore::KgpgKey *secretkey = NULL; + + while ( (items = p.readln(lsp)) >= 0 ) { + if ((lsp.at(0) == QLatin1String( "sec" )) && (items >= 10)) { + KgpgSubKeyType subtype; + KgpgSubKeyType keytype; + + if (items >= 11) { + const QString &caps = lsp.at(11); + + subtype = Convert::toSubType(caps, false); + keytype = Convert::toSubType(caps, true); + } + + result << KgpgKey(lsp.at(4), lsp.at(2).toUInt(), Convert::toTrust(lsp.at(1)), + Convert::toAlgo(lsp.at(3).toInt()), subtype, keytype, + QDateTime::fromTime_t(lsp.at(5).toUInt())); + + secretkey = &result.last(); + + secretkey->setSecret(true); + + if (lsp.at(6).isEmpty()) + secretkey->setExpiration(QDateTime()); + else + secretkey->setExpiration(QDateTime::fromTime_t(lsp.at(6).toUInt())); + hasuid = true; + } else if ((lsp.at(0) == QLatin1String( "uid" )) && (items >= 10)) { + if (hasuid) + continue; + + hasuid = true; + + const QString fullname(lsp.at(9)); + if (fullname.contains(QLatin1Char( '<' ) )) { + QString kmail(fullname); + + if (fullname.contains(QLatin1Char( ')' ) )) + kmail = kmail.section(QLatin1Char( ')' ), 1); + + kmail = kmail.section(QLatin1Char( '<' ), 1); + kmail.truncate(kmail.length() - 1); + + if (kmail.contains(QLatin1Char( '<' ) )) { // several email addresses in the same key + kmail = kmail.replace(QLatin1Char( '>' ), QLatin1Char( ';' )); + kmail.remove(QLatin1Char( '<' )); + } + + secretkey->setEmail(kmail); + } else { + secretkey->setEmail(QString()); + } + + QString kname(fullname.section( QLatin1String( " <" ), 0, 0)); + if (fullname.contains(QLatin1Char( '(' ) )) { + kname = kname.section( QLatin1String( " (" ), 0, 0); + QString comment = fullname.section(QLatin1Char( '(' ), 1, 1); + comment = comment.section(QLatin1Char( ')' ), 0, 0); + + secretkey->setComment(comment); + } else { + secretkey->setComment(QString()); + } + secretkey->setName(kname); + } else if ((lsp.at(0) == QLatin1String( "fpr" )) && (items >= 10)) { + secretkey->setFingerprint(lsp.at(9)); + } + } + + return result; +} + +KgpgKeyList KgpgInterface::readSecretKeys(const QStringList &ids) +{ + GPGProc process; + process << + QLatin1String("--with-colons") << + QLatin1String("--list-secret-keys") << + QLatin1String("--with-fingerprint") << + QLatin1String("--fixed-list-mode") << + ids; + + process.start(); + process.waitForFinished(-1); + KgpgCore::KgpgKeyList result = readSecretKeysProcess(process); + + return result; +} + +#include "kgpginterface.moc" diff --git a/kgpg/kgpginterface.h b/kgpg/kgpginterface.h new file mode 100644 index 00000000..af469226 --- /dev/null +++ b/kgpg/kgpginterface.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2009,2010,2011,2012 + * Rolf Eike Beer + */ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGINTERFACE_H +#define KGPGINTERFACE_H + +#include "core/kgpgkey.h" +#include + +class KGpgKeyNode; +class QString; + +/** + * GnuPG interface functions + */ +namespace KgpgInterface { + QString getGpgSetting(const QString &name, const QString &configfile); + void setGpgSetting(const QString &name, const QString &value, const QString &url); + + bool getGpgBoolSetting(const QString &name, const QString &configfile); + void setGpgBoolSetting(const QString &name, const bool enable, const QString &url); + + KgpgCore::KgpgKeyList readPublicKeys(const QStringList &ids = QStringList()); + void readSignatures(KGpgKeyNode *node); + KgpgCore::KgpgKeyList readSecretKeys(const QStringList &ids = QStringList()); +}; + +#endif // KGPGINTERFACE_H diff --git a/kgpg/kgpgkeygenerate.cpp b/kgpg/kgpgkeygenerate.cpp new file mode 100644 index 00000000..7a26e179 --- /dev/null +++ b/kgpg/kgpgkeygenerate.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2009,2010,2012,2013,2014 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgkeygenerate.h" + +#include "core/convert.h" +#include "core/emailvalidator.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KgpgCore; + +KgpgKeyGenerate::KgpgKeyGenerate(QWidget *parent) + : KDialog(parent), + m_expert(false) +{ + setupUi(this); + + setButtons(User1 | Ok | Cancel); + + setButtonText(User1, i18n("&Expert Mode")); + setButtonToolTip(User1, i18n("Go to Expert Mode")); + setButtonWhatsThis(User1, i18n( "If you go to expert mode, you will use the command line to create your key." )); + + connect(m_kname, SIGNAL(textChanged(QString)), this, SLOT(slotEnableOk())); + + KHBox *hgroup = new KHBox(vgroup); + hgroup->setFrameShape(QFrame::StyledPanel); + hgroup->setMargin(marginHint()); + hgroup->setSpacing(spacingHint()); + m_days->setParent(hgroup); + QIntValidator *validator = new QIntValidator(m_days); + validator->setBottom(0); + m_days->setValidator(validator); + m_days->setMaxLength(4); + m_days->setDisabled(true); + + m_keyexp = new KComboBox(hgroup); + m_keyexp->addItem(i18nc("Key will not expire", "Never"), 0); + m_keyexp->addItem(i18n("Days"), 1); + m_keyexp->addItem(i18n("Weeks"), 2); + m_keyexp->addItem(i18n("Months"), 3); + m_keyexp->addItem(i18n("Years"), 4); + m_keyexp->setMinimumSize(m_keyexp->sizeHint()); + connect(m_keyexp, SIGNAL(activated(int)), this, SLOT(slotEnableDays(int))); + + qobject_cast(vgroup->layout())->insertWidget(7, hgroup); + + m_keysize->addItem(i18n("1024")); + m_keysize->addItem(i18n("2048")); + m_keysize->addItem(i18n("4096")); + m_keysize->setCurrentIndex(1); // 2048 + m_keysize->setMinimumSize(m_keysize->sizeHint()); + + m_keykind->addItem(KgpgCore::Convert::toString(KgpgCore::ALGO_DSA_ELGAMAL)); + m_keykind->addItem(KgpgCore::Convert::toString(KgpgCore::ALGO_RSA_RSA)); + m_keykind->addItem(KgpgCore::Convert::toString(KgpgCore::ALGO_RSA)); + m_keykind->setCurrentIndex(1); // RSA+RSA + slotEnableCaps(m_keykind->currentIndex()); + m_keykind->setMinimumSize(m_keykind->sizeHint()); + + setMainWidget(vgroup); + + slotEnableOk(); + updateGeometry(); + show(); + + connect(this, SIGNAL(okClicked()), this, SLOT(slotOk())); + connect(this, SIGNAL(user1Clicked()), this, SLOT(slotUser1())); + connect(m_keykind, SIGNAL(activated(int)), SLOT(slotEnableCaps(int))); +} + +void KgpgKeyGenerate::slotButtonClicked(int button) +{ + if (button == Ok) + slotOk(); + else + if (button == User1) + slotUser1(); + else + if (button == Cancel) + reject(); +} + +void KgpgKeyGenerate::slotOk() +{ + if (m_kname->text().simplified().isEmpty()) + { + KMessageBox::sorry(this, i18n("You must give a name.")); + return; + } + + if (m_kname->text().simplified().length() < 5) + { + KMessageBox::sorry(this, i18n("The name must have at least 5 characters")); + return; + } + + if (m_kname->text().simplified().at(0).isDigit()) + { + KMessageBox::sorry(this, i18n("The name must not start with a digit")); + return; + } + + QString vmail = m_mail->text(); + if (vmail.isEmpty()) + { + int result = KMessageBox::warningContinueCancel(this, i18n("You are about to create a key with no email address")); + if (result != KMessageBox::Continue) + return; + } else { + int pos = 0; + if (EmailValidator().validate(vmail, pos) == QValidator::Invalid) + { + KMessageBox::sorry(this, i18n("Email address not valid")); + return; + } + } + + accept(); +} + +void KgpgKeyGenerate::slotUser1() +{ + m_expert = true; + accept(); +} + +void KgpgKeyGenerate::slotEnableDays(const int state) +{ + m_days->setDisabled(state == 0); +} + +void KgpgKeyGenerate::slotEnableCaps(const int state) +{ + // currently only supported for standalone RSA keys + capabilities->setDisabled(state != 2); +} + +void KgpgKeyGenerate::slotEnableOk() +{ + enableButtonOk((m_kname->text().simplified().length() >= 5) && + !m_kname->text().simplified().at(0).isDigit()); +} + +bool KgpgKeyGenerate::isExpertMode() const +{ + return m_expert; +} + +KgpgCore::KgpgKeyAlgo KgpgKeyGenerate::algo() const +{ + if (m_keykind->currentText() == KgpgCore::Convert::toString(KgpgCore::ALGO_RSA)) + return KgpgCore::ALGO_RSA; + else if (m_keykind->currentText() == KgpgCore::Convert::toString(KgpgCore::ALGO_RSA_RSA)) + return KgpgCore::ALGO_RSA_RSA; + else + return KgpgCore::ALGO_DSA_ELGAMAL; +} + +KgpgCore::KgpgSubKeyType KgpgKeyGenerate::caps() const +{ + KgpgCore::KgpgSubKeyType ret; + + if (!capabilities->isEnabled()) + return ret; + + if (capAuth->isChecked()) + ret |= KgpgCore::SKT_AUTHENTICATION; + if (capCert->isChecked()) + ret |= KgpgCore::SKT_CERTIFICATION; + if (capEncrypt->isChecked()) + ret |= KgpgCore::SKT_ENCRYPTION; + if (capSign->isChecked()) + ret |= KgpgCore::SKT_SIGNATURE; + + return ret; +} + +uint KgpgKeyGenerate::size() const +{ + return m_keysize->currentText().toUInt(); +} + +char KgpgKeyGenerate::expiration() const +{ + switch (m_keyexp->currentIndex()) { + case 1: + return 'd'; + case 2: + return 'w'; + case 3: + return 'm'; + case 4: + default: + return 'y'; + } +} + +uint KgpgKeyGenerate::days() const +{ + if (m_days->text().isEmpty()) + return 0; + return m_days->text().toUInt(); +} + +QString KgpgKeyGenerate::name() const +{ + if (m_kname->text().isEmpty()) + return QString(); + return m_kname->text(); +} + +QString KgpgKeyGenerate::email() const +{ + if (m_mail->text().isEmpty()) + return QString(); + return m_mail->text(); +} + +QString KgpgKeyGenerate::comment() const +{ + if (m_comment->text().isEmpty()) + return QString(); + return m_comment->text(); +} + +#include "kgpgkeygenerate.moc" diff --git a/kgpg/kgpgkeygenerate.h b/kgpg/kgpgkeygenerate.h new file mode 100644 index 00000000..d77437ec --- /dev/null +++ b/kgpg/kgpgkeygenerate.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2009,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGKEYGENERATE_H +#define KGPGKEYGENERATE_H + +#include "ui_kgpgkeygenerate.h" + +#include "core/kgpgkey.h" + +#include + +class KComboBox; +class KLineEdit; + +class KgpgKeyGenerate : public KDialog, public Ui::kgpgKeyGenerate +{ + Q_OBJECT + +public: + explicit KgpgKeyGenerate(QWidget *parent = 0); + + bool isExpertMode() const; + QString name() const; + QString email() const; + QString comment() const; + KgpgCore::KgpgKeyAlgo algo() const; + + /** + * @brief return the selected capabilities for the new key + * @retval 0 default capabilities of the selected algorithm should be used + */ + KgpgCore::KgpgSubKeyType caps() const; + uint size() const; + char expiration() const; + uint days() const; + +private slots: + void slotOk(); + void slotUser1(); + void slotButtonClicked(int button); + void slotEnableOk(); + void slotEnableDays(const int state); + void slotEnableCaps(const int state); + +private: + KComboBox *m_keyexp; + bool m_expert; +}; + +#endif // KGPGKEYGENERATE_H diff --git a/kgpg/kgpgkeygenerate.ui b/kgpg/kgpgkeygenerate.ui new file mode 100644 index 00000000..7e650554 --- /dev/null +++ b/kgpg/kgpgkeygenerate.ui @@ -0,0 +1,205 @@ + + + kgpgKeyGenerate + + + + 0 + 0 + 367 + 464 + + + + Key Generation + + + + + 0 + 10 + 361 + 434 + + + + Generate Key Pair + + + + + + &Name: + + + m_kname + + + + + + + The name must have a length of at least 5 characters and must not begin with a digit. + + + Real name, at least 5 characters, no leading digits + + + + + + + E&mail (optional): + + + m_mail + + + + + + + + + + Commen&t (optional): + + + m_comment + + + + + + + + + + Expiration: + + + m_days + + + + + + + 0 + + + + + + + &Key size: + + + m_keysize + + + + + + + + + + &Algorithm: + + + m_keykind + + + + + + + + + + + 0 + 0 + + + + Capabilities + + + + QFormLayout::ExpandingFieldsGrow + + + + + false + + + Certification is automatically enabled for all keys + + + Certification + + + true + + + + + + + Signature + + + true + + + + + + + Authentication + + + true + + + + + + + Encryption + + + true + + + + + + + + + + + + KDialog + QDialog +
kdialog.h
+ 1 +
+ + KComboBox + QComboBox +
kcombobox.h
+
+ + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + +
diff --git a/kgpg/kgpgoptions.cpp b/kgpg/kgpgoptions.cpp new file mode 100644 index 00000000..199d6f49 --- /dev/null +++ b/kgpg/kgpgoptions.cpp @@ -0,0 +1,608 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2006,2007,2008,2009,2010,2011,2012,2013,2014 + * Rolf Eike Beer + */ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgoptions.h" + +#include "kgpgsettings.h" +#include "kgpginterface.h" +#include "conf_encryption.h" +#include "core/images.h" +#include "model/gpgservermodel.h" +#include "model/keylistproxymodel.h" +#include "model/kgpgitemmodel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KgpgCore; + +// main window +kgpgOptions::kgpgOptions(QWidget *parent, KGpgItemModel *model) + : KConfigDialog(parent, QLatin1String( "settings" ), KGpgSettings::self()), + m_config(new KConfig( QLatin1String( "kgpgrc" ), KConfig::SimpleConfig)), + m_page1(new Encryption()), + m_page2(new Decryption()), + m_page3(new UIConf()), + m_page4(new GPGConf()), + m_page6(new ServerConf()), + m_page7(new MiscConf()), + m_serverModel(new GpgServerModel(m_page6)), + m_fontchooser(new KFontChooser(m_page3->tabWidget3->widget(1), KFontChooser::NoDisplayFlags, QStringList())), + m_model(model), + m_combomodel(new KeyListProxyModel(this, KeyListProxyModel::SingleColumnIdFirst)) +{ + m_page7->EmailTemplateEdit->setPlainText(KGpgSettings::emailTemplate()); + + m_combomodel->setKeyModel(m_model); + m_combomodel->setTrustFilter(KgpgCore::TRUST_MARGINAL); + m_combomodel->setEncryptionKeyFilter(true); + m_combomodel->sort(0); + + // Initialize the default server and the default server list. + defaultKeyServer = QLatin1String("hkp://wwwkeys.pgp.net"); + defaultServerList << defaultKeyServer + << QLatin1String("hkp://search.keyserver.net") + << QLatin1String("hkp://pgp.dtype.org") + << QLatin1String("hkp://subkeys.pgp.net"); + + // Read the default keyserver from the GnuPG settings. + keyServer = KgpgInterface::getGpgSetting(QLatin1String( "keyserver" ), KGpgSettings::gpgConfigPath()); + + // Read the servers stored in kgpgrc + serverList = KGpgSettings::keyServers(); + + // Remove everything after a whitespace. This will normally be + // ' (Default)' from KDE 3.x.x + serverList.replaceInStrings(QRegExp( QLatin1String( " .*") ), QString() ); + + m_serverModel->setStringList(serverList); + // if the server from GnuPG config is set and is not in the list of servers put it there + if (!keyServer.isEmpty() && !serverList.contains(keyServer)) + serverList.prepend(keyServer); + m_page6->ServerBox->setModel(m_serverModel); + + defaultConfigPath = KUrl::fromPath(gpgConfigPath).fileName(); + defaultHomePath = KUrl::fromPath(gpgConfigPath).directory(KUrl::AppendTrailingSlash); + defaultBinPath = KGpgSettings::gpgBinaryPath(); + + m_showsystray = KGpgSettings::showSystray(); + m_trayaction = KGpgSettings::leftClick(); + m_mailUats = KGpgSettings::mailUats(); + + QVBoxLayout *fontlayout = new QVBoxLayout(m_page3->tabWidget3->widget(1)); + fontlayout->setSpacing(spacingHint()); + + m_fontchooser->setObjectName( QLatin1String("kcfg_Font" )); + fontlayout->addWidget(m_fontchooser); + + m_page3->kcfg_EmailSorting->addItem(i18n("Left to right, account first")); ///< KGpgSettings::EnumEmailSorting::Alphabetical + m_page3->kcfg_EmailSorting->addItem(i18n("Right to left, TLD first")); ///< KGpgSettings::EnumEmailSorting::TLDfirst + m_page3->kcfg_EmailSorting->addItem(i18n("Right to left, domain first")); ///< KGpgSettings::EnumEmailSorting::DomainFirst + m_page3->kcfg_EmailSorting->addItem(i18n("Right to left, FQDN first")); ///< KGpgSettings::EnumEmailSorting::FQDNFirst + m_emailSortingIndex = KGpgSettings::emailSorting(); + + pixkeySingle = Images::single(); + pixkeyDouble = Images::pair(); + addPage(m_page1, i18n("Encryption"), QLatin1String( "document-encrypt" )); + addPage(m_page2, i18n("Decryption"), QLatin1String( "document-decrypt") ); + addPage(m_page3, i18n("Appearance"), QLatin1String( "preferences-desktop-theme" )); + addPage(m_page4, i18n("GnuPG Settings"), QLatin1String( "kgpg" )); + addPage(m_page6, i18n("Key Servers"), QLatin1String( "network-wired" )); + addPage(m_page7, i18n("Misc"), QLatin1String( "preferences-other" )); + + // The following widgets are managed manually. + connect(m_page1->encrypt_to_always, SIGNAL(toggled(bool)), this, SLOT(slotChangeEncryptTo())); + connect(m_page4->changeHome, SIGNAL(clicked()), this, SLOT(slotChangeHome())); + connect(m_page6->server_add, SIGNAL(clicked()), this, SLOT(slotAddKeyServer())); + connect(m_page6->server_del, SIGNAL(clicked()), this, SLOT(slotDelKeyServer())); + connect(m_page6->server_edit, SIGNAL(clicked()), this, SLOT(slotEditKeyServer())); + connect(m_page6->server_default, SIGNAL(clicked()), this, SLOT(slotDefaultKeyServer())); + connect(m_page6->ServerBox->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(slotChangeKeyServerButtonEnable())); + connect(m_page6->ServerBox, SIGNAL(doubleClicked(QModelIndex)), SLOT(slotEditKeyServer(QModelIndex))); + connect(m_page7->kcfg_ShowSystray, SIGNAL(clicked()), SLOT(slotSystrayEnable())); + + keyUltimate = KGpgSettings::colorUltimate(); + keyGood = KGpgSettings::colorGood(); + keyMarginal = KGpgSettings::colorMarginal(); + keyExpired = KGpgSettings::colorExpired(); + keyUnknown = KGpgSettings::colorUnknown(); + keyRev = KGpgSettings::colorRev(); + keyBad = KGpgSettings::colorBad(); +} + +kgpgOptions::~kgpgOptions() +{ + delete m_config; + delete m_page1; + delete m_page2; + delete m_page3; + delete m_page4; + delete m_page6; + delete m_page7; +} + +void kgpgOptions::slotChangeHome() +{ + QString gpgHome = KFileDialog::getExistingDirectory(m_page4->gpg_home_path->text(), this, i18n("New GnuPG Home Location")); + if (gpgHome.isEmpty()) + return; + + if (!gpgHome.endsWith(QLatin1Char( '/' ))) + gpgHome.append(QLatin1Char( '/' )); + + QString confPath = QLatin1String( "options" ); + if (!QFile(gpgHome + confPath).exists()) { + confPath = QLatin1String( "gpg.conf" ); + if (!QFile(gpgHome + confPath).exists()) { + // Try to create config File by running gpg once + if (KMessageBox::Yes == KMessageBox::questionYesNo(this, + i18n("No configuration file was found in the selected location.\nDo you want to create it now?\n\nWithout a configuration file, neither KGpg nor GnuPG will work properly."), + i18n("No Configuration File Found"), + KGuiItem(i18n("Create")), + KGuiItem(i18n("Ignore")))) { + // start GnuPG so that it will create a config file + QString gpgbin = m_page4->kcfg_GpgBinaryPath->text(); + if (!QFile::exists(gpgbin)) + gpgbin = QLatin1String( "gpg" ); + + KProcess p; + p << gpgbin << QLatin1String( "--homedir" ) << gpgHome << QLatin1String( "--no-tty" ) << QLatin1String( "--list-secret-keys" ); + p.execute(); + // end of creating config file + + confPath = QLatin1String( "gpg.conf" ); + QFile confFile(gpgHome + confPath); + if (!confFile.open(QIODevice::WriteOnly)) { + KMessageBox::sorry(this, i18n("Cannot create configuration file. Please check if destination media is mounted and if you have write access.")); + return; + } else { + QTextStream stream(&confFile); + stream << "# Config file created by KGpg\n\n"; + confFile.close(); + } + } else { + confPath.clear(); + } + } + } + + m_page4->gpg_conf_path->setText(confPath); + m_page4->gpg_home_path->setText(gpgHome); +} + +bool kgpgOptions::isValidKeyserver(const QString &server) +{ + if (server.isEmpty()) + return false; + + if (server.contains(QLatin1Char( ' ' ))) { + KMessageBox::sorry(this, i18n("Key server URLs may not contain whitespace.")); + return false; + } + + if (serverList.contains(server)) { + KMessageBox::sorry(this, i18n("Key server already in the list.")); + return false; + } + + return true; +} + +void kgpgOptions::slotAddKeyServer() +{ + const QString newServer(KInputDialog::getText(i18n("Add New Key Server"), i18n("Server URL:"))); + + if (!isValidKeyserver(newServer)) + return; + + m_serverModel->setStringList(m_serverModel->stringList() << newServer); + + enableButtonApply(true); +} + +void kgpgOptions::slotChangeEncryptTo() +{ + bool enable = (m_page1->encrypt_to_always->isChecked() != m_encrypttoalways); + enableButtonApply(enable); +} + +void kgpgOptions::slotDelKeyServer() +{ + QModelIndex cur = m_page6->ServerBox->selectionModel()->currentIndex(); + m_serverModel->removeRows(cur.row(), 1); + + enableButtonApply(true); +} + +void kgpgOptions::slotEditKeyServer() +{ + slotEditKeyServer(m_page6->ServerBox->selectionModel()->currentIndex()); +} + +void kgpgOptions::slotEditKeyServer(const QModelIndex &index) +{ + if (!index.isValid()) + return; + + m_page6->ServerBox->edit(index); + + enableButtonApply(true); +} + +void kgpgOptions::slotDefaultKeyServer() +{ + QModelIndex cur = m_page6->ServerBox->selectionModel()->currentIndex(); + + m_serverModel->setDefault(cur.row()); + + enableButtonApply(true); +} + +void kgpgOptions::slotChangeKeyServerButtonEnable() +{ + QModelIndex cur = m_page6->ServerBox->selectionModel()->currentIndex(); + + m_page6->server_del->setEnabled(cur.isValid()); + m_page6->server_edit->setEnabled(cur.isValid()); + m_page6->server_default->setEnabled(cur.isValid() && + (cur.row() != m_serverModel->defaultRow())); +} + +void kgpgOptions::updateWidgets() +{ + alwaysKeyID = KgpgInterface::getGpgSetting(QLatin1String( "encrypt-to" ), KGpgSettings::gpgConfigPath()); + + m_page7->EmailTemplateEdit->setPlainText(KGpgSettings::emailTemplate()); + + m_encrypttoalways = !alwaysKeyID.isEmpty(); + m_defaultencrypttoalways = false; + + m_page1->encrypt_to_always->setChecked(m_encrypttoalways); + + listKeys(); + fileEncryptionKey = KGpgSettings::fileEncryptionKey(); + // the contents are totally mess. There were key id, name and email stored + // try to extract the key id, that's the only thing we really need + if (!fileEncryptionKey.isEmpty()) { + int idpos = m_page1->file_key->findText(fileEncryptionKey); + if (idpos == -1) { + idpos = fileEncryptionKey.indexOf(QRegExp( QLatin1String( "([0-9A-Fa-F]{8})+" ))); + if (idpos >= 0) { + QString fileId = fileEncryptionKey.mid(idpos); + idpos = fileId.indexOf(QRegExp( QLatin1String( "[^a-fA-F0-9]" ))); + if (idpos >= 0) { + fileId = fileId.left(idpos); + fileId.chop(fileId.length() % 8); + } + + KGpgKeyNode *anode = m_combomodel->getModel()->findKeyNode(fileId); + + if (anode != NULL) + idpos = m_combomodel->nodeIndex(anode).row(); + } + } + m_page1->file_key->setCurrentIndex(idpos); + } + + if (!alwaysKeyID.isEmpty()) { + KGpgKeyNode *anode = m_combomodel->getModel()->findKeyNode(alwaysKeyID); + if (anode != NULL) { + const QModelIndex midx(m_combomodel->nodeIndex(anode)); + m_page1->always_key->setCurrentIndex(midx.row()); + } + } + + gpgConfigPath = KGpgSettings::gpgConfigPath(); + m_page4->gpg_conf_path->setText(KUrl::fromPath(gpgConfigPath).fileName()); + m_page4->gpg_home_path->setText(KUrl::fromPath(gpgConfigPath).directory(KUrl::AppendTrailingSlash)); + + m_useagent = KgpgInterface::getGpgBoolSetting(QLatin1String( "use-agent" ), KGpgSettings::gpgConfigPath()); + m_defaultuseagent = false; + + m_page4->use_agent->setChecked(m_useagent); + + m_emailSortingIndex = KGpgSettings::emailSorting(); + m_page3->kcfg_EmailSorting->setCurrentIndex(m_emailSortingIndex); + + m_serverModel->setStringList(serverList); + if (!serverList.isEmpty()) + m_serverModel->setDefault(keyServer); + + kDebug(2100) << "Finishing options"; +} + +void kgpgOptions::updateWidgetsDefault() +{ + m_page7->EmailTemplateEdit->setPlainText(m_emailTemplate); + + m_page1->encrypt_to_always->setChecked(m_defaultencrypttoalways); + m_page4->use_agent->setChecked(m_defaultuseagent); + + m_page4->gpg_conf_path->setText(defaultConfigPath); + m_page4->gpg_home_path->setText(defaultHomePath); + + m_serverModel->setStringList(defaultServerList); + m_serverModel->setDefault(0); + + m_page3->kcfg_EmailSorting->setCurrentIndex(KGpgSettings::EnumEmailSorting::Alphabetical); + + kDebug(2100) << "Finishing default options" ; +} + +void kgpgOptions::updateSettings() +{ + // Update config path first! + const QString newConfigFile = m_page4->gpg_home_path->text() + m_page4->gpg_conf_path->text(); + if (newConfigFile != gpgConfigPath) { + KGpgSettings::setGpgConfigPath(newConfigFile); + emit homeChanged(); + + gpgConfigPath = newConfigFile; + } + + // save selected keys for file encryption & always encrypt with + if (m_page1->kcfg_EncryptFilesTo->isChecked()) + fileEncryptionKey = m_page1->file_key->itemData(m_page1->file_key->currentIndex(), Qt::ToolTipRole).toString(); + else + fileEncryptionKey.clear(); + + if (fileEncryptionKey != KGpgSettings::fileEncryptionKey()) + KGpgSettings::setFileEncryptionKey(fileEncryptionKey); + + m_encrypttoalways = m_page1->encrypt_to_always->isChecked(); + + if (m_encrypttoalways) + alwaysKeyID = m_page1->always_key->itemData(m_page1->always_key->currentIndex(), Qt::ToolTipRole).toString(); + else + alwaysKeyID.clear(); + + KgpgInterface::setGpgSetting(QLatin1String( "encrypt-to" ), alwaysKeyID, KGpgSettings::gpgConfigPath()); + + emit changeFont(m_fontchooser->font()); + + // install service menus + if (m_page7->kcfg_SignMenu->currentIndex() == KGpgSettings::EnumSignMenu::AllFiles) + slotInstallSign(QLatin1String( "application/octet-stream" )); + else + slotRemoveMenu(QLatin1String( "signfile.desktop" )); + + if (m_page7->kcfg_DecryptMenu->currentIndex() == KGpgSettings::EnumDecryptMenu::AllFiles) + slotInstallDecrypt(QLatin1String( "application/octet-stream" )); + else + if (m_page7->kcfg_DecryptMenu->currentIndex() == KGpgSettings::EnumDecryptMenu::EncryptedFiles) + slotInstallDecrypt(QLatin1String( "application/pgp-encrypted,application/pgp-signature,application/pgp-keys" )); + else + slotRemoveMenu(QLatin1String( "decryptfile.desktop" )); + + m_useagent = m_page4->use_agent->isChecked(); + + if (m_useagent) { + KgpgInterface::setGpgBoolSetting(QLatin1String( "use-agent" ), true, KGpgSettings::gpgConfigPath()); + KgpgInterface::setGpgBoolSetting(QLatin1String( "no-use-agent" ), false, KGpgSettings::gpgConfigPath()); + } else { + KgpgInterface::setGpgBoolSetting(QLatin1String( "use-agent" ), false, KGpgSettings::gpgConfigPath()); + } + + // Store the default server in ~/.gnupg + KgpgInterface::setGpgSetting(QLatin1String("keyserver"), m_serverModel->defaultServer(), KGpgSettings::gpgConfigPath()); + + // Store additional servers in kgpgrc. + serverList = m_serverModel->stringList(); + int defaultRow = m_serverModel->defaultRow(); + if (!serverList.isEmpty()) + serverList.move(defaultRow, 0); + + KGpgSettings::setKeyServers(serverList); + + if (keyUltimate != m_page3->kcfg_ColorUltimate->color()) + emit refreshTrust(TRUST_ULTIMATE, m_page3->kcfg_ColorUltimate->color()); + + if (keyGood != m_page3->kcfg_ColorGood->color()) + emit refreshTrust(TRUST_FULL, m_page3->kcfg_ColorGood->color()); + + if (keyExpired != m_page3->kcfg_ColorExpired->color()) + emit refreshTrust(TRUST_EXPIRED, m_page3->kcfg_ColorExpired->color()); + + if (keyMarginal != m_page3->kcfg_ColorMarginal->color()) + emit refreshTrust(TRUST_MARGINAL, m_page3->kcfg_ColorMarginal->color()); + + if (keyBad != m_page3->kcfg_ColorBad->color()) { + emit refreshTrust(TRUST_INVALID, m_page3->kcfg_ColorBad->color()); + emit refreshTrust(TRUST_DISABLED, m_page3->kcfg_ColorBad->color()); + } + + if (keyUnknown != m_page3->kcfg_ColorUnknown->color()) { + emit refreshTrust(TRUST_UNDEFINED, m_page3->kcfg_ColorUnknown->color()); + emit refreshTrust(TRUST_NONE, m_page3->kcfg_ColorUnknown->color()); + emit refreshTrust(TRUST_UNKNOWN, m_page3->kcfg_ColorUnknown->color()); + } + + if (keyRev != m_page3->kcfg_ColorRev->color()) + emit refreshTrust(TRUST_REVOKED, m_page3->kcfg_ColorRev->color()); + + m_showsystray = m_page7->kcfg_ShowSystray->isChecked(); + KGpgSettings::setShowSystray(m_showsystray); + + m_trayaction = m_page7->kcfg_LeftClick->currentIndex(); + KGpgSettings::setLeftClick(m_trayaction); + + m_mailUats = m_page7->kcfg_MailUats->currentIndex(); + KGpgSettings::setMailUats(m_mailUats); + + m_emailTemplate = m_page7->EmailTemplateEdit->toPlainText(); + KGpgSettings::setEmailTemplate(m_emailTemplate); + + m_emailSortingIndex = m_page3->kcfg_EmailSorting->currentIndex(); + KGpgSettings::setEmailSorting(m_emailSortingIndex); + + KGpgSettings::self()->writeConfig(); + m_config->sync(); + + emit settingsUpdated(); +} + +void kgpgOptions::listKeys() +{ + if (m_model->rowCount(QModelIndex()) == 0) { + ids += QLatin1String("0"); + m_page1->file_key->addItem(i18nc("no key available", "none")); + m_page1->file_key->setModel(NULL); + m_page1->always_key->addItem(i18nc("no key available", "none")); + m_page1->always_key->setModel(NULL); + } else { + m_page1->file_key->setModel(m_combomodel); + m_page1->always_key->setModel(m_combomodel); + } +} + +void kgpgOptions::slotInstallDecrypt(const QString &mimetype) +{ + const QString path(KStandardDirs::locateLocal("data", QLatin1String( "konqueror/servicemenus/decryptfile.desktop" ))); + KDesktopFile configl2(path); + if (!configl2.isImmutable()) { + KConfigGroup gr(configl2.group("Desktop Entry")); + + gr.writeXdgListEntry("MimeType", QStringList(mimetype)); + gr.writeEntry("X-KDE-ServiceTypes", "KonqPopupMenu/Plugin"); + gr.writeEntry("Actions", "decrypt"); + + gr = configl2.group("Desktop Action decrypt"); + gr.writeEntry("Name", i18n("Decrypt File")); + //gr.writeEntry("Icon", "decrypt_file"); + gr.writeEntry("Exec", "kgpg %U"); + } +} + +void kgpgOptions::slotInstallSign(const QString &mimetype) +{ + QString path(KStandardDirs::locateLocal("services", QLatin1String( "ServiceMenus/signfile.desktop" ))); + KDesktopFile configl2(path); + if (!configl2.isImmutable()) { + KConfigGroup gr = configl2.group("Desktop Entry"); + gr.writeXdgListEntry("MimeType", QStringList(mimetype)); + gr.writeEntry("X-KDE-ServiceTypes", "KonqPopupMenu/Plugin"); + gr.writeEntry("Actions", "sign"); + + gr = configl2.group("Desktop Action sign"); + gr.writeEntry("Name", i18n("Sign File")); + //gr.writeEntry("Icon", "sign_file"); + gr.writeEntry("Exec","kgpg -S %F"); + } +} + +void kgpgOptions::slotRemoveMenu(const QString &menu) +{ + QString path = KStandardDirs::locateLocal("services", QLatin1String( "ServiceMenus/" ) + menu); + QFile qfile(path); + if (qfile.exists()) + qfile.remove(); +} + +bool kgpgOptions::hasChanged() +{ + if (m_page1->kcfg_EncryptFilesTo->isChecked() && (m_page1->file_key->currentText() != fileEncryptionKey)) + return true; + + if (m_page1->encrypt_to_always->isChecked() != m_encrypttoalways) + return true; + + if (m_page1->encrypt_to_always->isChecked() && + (m_page1->always_key->itemData(m_page1->always_key->currentIndex(), Qt::ToolTipRole).toString()) != alwaysKeyID) + return true; + + if (m_page4->gpg_conf_path->text() != KUrl::fromPath(gpgConfigPath).fileName()) + return true; + + if (m_page4->gpg_home_path->text() != KUrl::fromPath(gpgConfigPath).directory(KUrl::AppendTrailingSlash)) + return true; + + if (m_page4->use_agent->isChecked() != m_useagent) + return true; + + // Did the default server change + if (m_serverModel->defaultServer() != keyServer) + return true; + + // Did the servers change? + if (m_serverModel->stringList() != serverList) + return true; + + if (m_page7->kcfg_ShowSystray->isChecked() != m_showsystray) + return true; + + if (m_page7->kcfg_LeftClick->currentIndex() != m_trayaction) + return true; + + if (m_page7->kcfg_MailUats->currentIndex() != m_mailUats) + return true; + + if (m_page3->kcfg_EmailSorting->currentIndex() != m_emailSortingIndex) + return true; + + return false; +} + +bool kgpgOptions::isDefault() +{ + if (m_page1->encrypt_to_always->isChecked() != m_defaultencrypttoalways) + return false; + + if (m_page4->gpg_conf_path->text() != defaultConfigPath) + return false; + + if (m_page4->gpg_home_path->text() != defaultHomePath) + return false; + + if (m_page4->use_agent->isChecked() != m_defaultuseagent) + return false; + + if (m_serverModel->defaultServer() != defaultKeyServer) + return false; + + if (m_serverModel->stringList() != defaultServerList) + return false; + + if (m_page7->kcfg_ShowSystray->isChecked() != m_showsystray) + return false; + + if (m_page7->kcfg_LeftClick->currentIndex() != KGpgSettings::EnumLeftClick::KeyManager) + return false; + + if (m_page7->kcfg_MailUats->currentIndex() != KGpgSettings::EnumMailUats::All) + return false; + + if (m_page3->kcfg_EmailSorting->currentIndex() != KGpgSettings::EnumEmailSorting::Alphabetical) + return false; + + return true; +} + +void kgpgOptions::slotSystrayEnable() +{ + m_page7->kcfg_LeftClick->setEnabled(m_page7->kcfg_ShowSystray->isChecked()); +} + +#include "kgpgoptions.moc" diff --git a/kgpg/kgpgoptions.h b/kgpg/kgpgoptions.h new file mode 100644 index 00000000..ad716db5 --- /dev/null +++ b/kgpg/kgpgoptions.h @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2006,2007,2008,2009,2010,2011,2012,2013,2014 + * Rolf Eike Beer + */ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGOPTIONS_H +#define KGPGOPTIONS_H + +#include "ui_conf_gpg.h" +#include "ui_conf_ui2.h" +#include "ui_conf_servers.h" +#include "ui_conf_misc.h" +#include "ui_conf_decryption.h" + +#include "core/kgpgkey.h" + +#include +#include +#include + +#include + +class KFontChooser; +class KConfig; + +class Encryption; +class GpgServerModel; +class KGpgItemModel; +class KeyListProxyModel; + +class Decryption : public QWidget, public Ui::Decryption +{ +public: + explicit Decryption( QWidget *parent = 0 ) + : QWidget( parent) + { + setupUi(this); + } +}; + +class MiscConf : public QWidget, public Ui::MiscConf +{ +public: + explicit MiscConf( QWidget *parent = 0 ) + : QWidget( parent ) + { + setupUi( this ); + } +}; + + +class UIConf : public QWidget, public Ui::UIConf +{ +public: + explicit UIConf( QWidget *parent = 0 ) + : QWidget( parent ) + { + setupUi( this ); + } +}; + +class ServerConf : public QWidget, public Ui::ServerConf +{ +public: + explicit ServerConf( QWidget *parent = 0 ) + : QWidget( parent ) + { + setupUi( this ); + } +}; + + +class GPGConf : public QWidget, public Ui::GPGConf +{ +public: + explicit GPGConf( QWidget *parent = 0 ) + : QWidget( parent ) + { + setupUi( this ); + } +}; + + +class kgpgOptions : public KConfigDialog +{ + Q_OBJECT + +public: + explicit kgpgOptions(QWidget *parent = 0, KGpgItemModel *model = 0); + ~kgpgOptions(); + +signals: + void updateDisplay(); + void settingsUpdated(); + void changeFont(QFont); + void homeChanged(); + void refreshTrust(KgpgCore::KgpgKeyTrust, QColor); + +private slots: + void slotChangeHome(); + void slotAddKeyServer(); + void slotChangeEncryptTo(); + void slotDelKeyServer(); + void slotEditKeyServer(); + void slotEditKeyServer(const QModelIndex &index); + void slotChangeKeyServerButtonEnable(); + void slotDefaultKeyServer(); + void updateWidgets(); + void updateWidgetsDefault(); + void updateSettings(); + void listKeys(); + void slotInstallDecrypt(const QString &mimetype); + void slotInstallSign(const QString &mimetype); + void slotRemoveMenu(const QString &menu); + void slotSystrayEnable(); + +protected: + virtual bool hasChanged(); + virtual bool isDefault(); + +private: + QStringList names; + QStringList ids; + QString alwaysKeyID; + QString fileEncryptionKey; + QString gpgConfigPath; + QString keyServer; ///< Server stored in GnuPG config + QStringList serverList; ///< Servers stored in kgpgrc + QString defaultKeyServer; ///< Default keyserver + QStringList defaultServerList; ///< Default list of servers including the default key server; + QString defaultConfigPath; + QString defaultHomePath; + QString defaultBinPath; + QPixmap pixkeySingle; + QPixmap pixkeyDouble; + QColor keyUltimate; + QColor keyGood; + QColor keyExpired; + QColor keyMarginal; + QColor keyBad; + QColor keyUnknown; + QColor keyRev; + + KConfig *m_config; + + Encryption * const m_page1; + Decryption * const m_page2; + UIConf * const m_page3; + GPGConf * const m_page4; + ServerConf * const m_page6; + MiscConf * const m_page7; + + GpgServerModel * const m_serverModel; ///< model holding the servers + KFontChooser * const m_fontchooser; + + bool m_useagent; + bool m_defaultuseagent; + bool m_encrypttoalways; + bool m_defaultencrypttoalways; + bool m_showsystray; + int m_trayaction; + int m_mailUats; + int m_emailSortingIndex; + QString m_emailTemplate; + + KGpgItemModel * const m_model; + KeyListProxyModel * const m_combomodel; + + bool isValidKeyserver(const QString &); +}; + +#endif // KGPGOPTIONS_H diff --git a/kgpg/kgpgrevokewidget.cpp b/kgpg/kgpgrevokewidget.cpp new file mode 100644 index 00000000..328c06e4 --- /dev/null +++ b/kgpg/kgpgrevokewidget.cpp @@ -0,0 +1,88 @@ +/*************************************************************************** + begin : Thu Jul 4 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#include "kgpgrevokewidget.h" + +#include "core/KGpgKeyNode.h" + +#include + +KgpgRevokeWidget::KgpgRevokeWidget(QWidget* parent) + : QWidget(parent), + Ui_KgpgRevokeWidget() +{ + setupUi(this); + connect(cbSave, SIGNAL(toggled(bool)), SLOT(cbSave_toggled(bool))); +} + +void KgpgRevokeWidget::cbSave_toggled(bool isOn) +{ + outputFile->setEnabled(isOn); +} + +KGpgRevokeDialog::KGpgRevokeDialog(QWidget* parent, const KGpgKeyNode *node) + : KDialog(parent), + m_revWidget(new KgpgRevokeWidget(this)), + m_id(node->getId()) +{ + setCaption(i18n("Create Revocation Certificate")); + setButtons(KDialog::Ok | KDialog::Cancel); + setDefaultButton(KDialog::Ok); + setModal(true); + + m_revWidget->keyID->setText(i18nc(" () ID: ", "%1 (%2) ID: %3", + node->getName(), node->getEmail(), m_id)); + m_revWidget->outputFile->setUrl(QString(QDir::homePath() + QLatin1Char( '/' ) + node->getEmail().section( QLatin1Char( '@' ), 0, 0 ) + QLatin1String( ".revoke" ) )); + m_revWidget->outputFile->setMode(KFile::File); + + setMinimumSize(m_revWidget->sizeHint()); + setMainWidget(m_revWidget); +} + +QString KGpgRevokeDialog::getDescription() const +{ + return m_revWidget->textDescription->toPlainText(); +} + +int KGpgRevokeDialog::getReason() const +{ + return m_revWidget->comboBox1->currentIndex(); +} + +KUrl KGpgRevokeDialog::saveUrl() const +{ + if (m_revWidget->cbSave->isChecked()) + return m_revWidget->outputFile->url(); + else + return KUrl(); +} + +QString KGpgRevokeDialog::getId() const +{ + return m_id; +} + +bool KGpgRevokeDialog::importChecked() +{ + return m_revWidget->cbImport->isChecked(); +} + +bool KGpgRevokeDialog::printChecked() +{ + return m_revWidget->cbPrint->isChecked(); +} + +#include "kgpgrevokewidget.moc" diff --git a/kgpg/kgpgrevokewidget.h b/kgpg/kgpgrevokewidget.h new file mode 100644 index 00000000..c318a730 --- /dev/null +++ b/kgpg/kgpgrevokewidget.h @@ -0,0 +1,58 @@ +/*************************************************************************** + begin : Thu Jul 4 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#ifndef KGPGREVOKEWIDGET_H +#define KGPGREVOKEWIDGET_H + +#include "ui_kgpgrevokewidget.h" +#include + +class KGpgKeyNode; + +class KgpgRevokeWidget : public QWidget, public Ui_KgpgRevokeWidget +{ + Q_OBJECT + +public: + explicit KgpgRevokeWidget(QWidget* parent = 0); + + +public slots: + virtual void cbSave_toggled(bool isOn); +}; + +class KGpgRevokeDialog: public KDialog +{ + Q_OBJECT + + Q_DISABLE_COPY(KGpgRevokeDialog) + KGpgRevokeDialog(); // = delete C++0x +public: + KGpgRevokeDialog(QWidget* parent, const KGpgKeyNode* node); + + QString getDescription() const; + int getReason() const; + KUrl saveUrl() const; + QString getId() const; + bool printChecked(); + bool importChecked(); + +private: + KgpgRevokeWidget *m_revWidget; + const QString m_id; +}; + +#endif diff --git a/kgpg/kgpgrevokewidget.ui b/kgpg/kgpgrevokewidget.ui new file mode 100644 index 00000000..474eb46a --- /dev/null +++ b/kgpg/kgpgrevokewidget.ui @@ -0,0 +1,154 @@ + + KgpgRevokeWidget + + + + 0 + 0 + 472 + 374 + + + + + + + + + Create revocation certificate for + + + false + + + + + + + key id + + + Qt::AlignVCenter + + + false + + + + + + + + + + + Reason for revocation: + + + false + + + + + + + + No Reason + + + + + Key Has Been Compromised + + + + + Key is Superseded + + + + + Key is No Longer Used + + + + + + + + + + Description: + + + false + + + + + + + + + + + + Save certificate: + + + true + + + + + + + + + + + + Print certificate + + + + + + + true + + + Import into keyring + + + + + + + + KUrlRequester + QFrame +
kurlrequester.h
+
+ + KComboBox + QComboBox +
kcombobox.h
+
+ + KTextEdit + QTextEdit +
ktextedit.h
+
+
+ + comboBox1 + textDescription + cbSave + cbPrint + cbImport + outputFile + + + +
diff --git a/kgpg/kgpgsettings.kcfgc b/kgpg/kgpgsettings.kcfgc new file mode 100644 index 00000000..a5c0d404 --- /dev/null +++ b/kgpg/kgpgsettings.kcfgc @@ -0,0 +1,9 @@ +# Code generation options for kconfig_compiler +File=kgpg.kcfg +ClassName=KGpgSettings +Singleton=true +Mutators=true +# Inherits=KConfigSkeleton +IncludeFiles=kgpginterface.h +# MemberVariables=public +CustomAdditions=true diff --git a/kgpg/kgpgsettings_addons.h b/kgpg/kgpgsettings_addons.h new file mode 100644 index 00000000..1834b1aa --- /dev/null +++ b/kgpg/kgpgsettings_addons.h @@ -0,0 +1,42 @@ +/*************************************************************************** + kgpgsettings_addons.h - description + ------------------- + begin : Mon Jul 8 2002 + copyright : (C) 2003Waldo Bastian + email : bastian@kde.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGSETTINGS_ADDONS_H +#define KGPGSETTINGS_ADDONS_H + +public: + static + QString defaultKey() + { + if (self()->mDefaultKey.isEmpty()) + { + self()->mDefaultKey = KgpgInterface::getGpgSetting(QLatin1String( "default-key" ), gpgConfigPath()); + } + return self()->mDefaultKey; + } + + static + void setDefaultKey(const QString &_defaultKey) + { + self()->mDefaultKey = _defaultKey; + KgpgInterface::setGpgSetting(QLatin1String( "default-key" ), _defaultKey, gpgConfigPath()); + } + +private: + QString mDefaultKey; + +#endif //KGPGSETTINGS_ADDONS_H diff --git a/kgpg/kgpgtextinterface.cpp b/kgpg/kgpgtextinterface.cpp new file mode 100644 index 00000000..f07240d7 --- /dev/null +++ b/kgpg/kgpgtextinterface.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2009,2010,2011,2012 + * Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgtextinterface.h" + +#include "gpgproc.h" +#include "kgpgsettings.h" + +#include +#include +#include +#include +#include + +class KGpgTextInterfacePrivate +{ +public: + KGpgTextInterfacePrivate(QObject *parent, const QString &keyID, const QStringList &options); + + GPGProc * const m_process; + int m_step; + const QStringList m_gpgopts; + KUrl::List m_files; + + void signFile(const QString &fileName); +}; + +static QStringList +buildCmdLine(const QString &keyID, const QStringList &options) +{ + return QStringList(QLatin1String("-u")) << + keyID << + options << + QLatin1String("--detach-sign") << + QLatin1String("--output"); +} + +KGpgTextInterfacePrivate::KGpgTextInterfacePrivate(QObject *parent, const QString &keyID, const QStringList &options) + : m_process(new GPGProc(parent)), + m_step(0), + m_gpgopts(buildCmdLine(keyID, options)) +{ +} + +void +KGpgTextInterfacePrivate::signFile(const QString &fileName) +{ + m_process->resetProcess(); + *m_process << + m_gpgopts << + fileName + QLatin1String(".sig") << + fileName; + + m_process->start(); +} + +KGpgTextInterface::KGpgTextInterface(QObject *parent, const QString &keyID, const QStringList &options) + : QObject(parent), + d(new KGpgTextInterfacePrivate(parent, keyID, options)) +{ + connect(d->m_process, SIGNAL(processExited()), SLOT(slotSignFile())); +} + +KGpgTextInterface::~KGpgTextInterface() +{ + delete d->m_process; + delete d; +} + +// signatures +void +KGpgTextInterface::signFiles(const KUrl::List &srcUrls) +{ + d->m_files = srcUrls; + + slotSignFile(); +} + +void +KGpgTextInterface::slotSignFile() +{ + const QString fileName = d->m_files.takeFirst().path(); + + if (d->m_files.isEmpty()) { + disconnect(d->m_process, SIGNAL(processExited()), this, SLOT(slotSignFile())); + connect(d->m_process, SIGNAL(processExited()), SLOT(slotSignFinished())); + } + + d->signFile(fileName); +} + +void +KGpgTextInterface::slotSignFinished() +{ + emit fileSignFinished(); +} + +#include "kgpgtextinterface.moc" diff --git a/kgpg/kgpgtextinterface.h b/kgpg/kgpgtextinterface.h new file mode 100644 index 00000000..195ec506 --- /dev/null +++ b/kgpg/kgpgtextinterface.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2009,2010,2011,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGTEXTINTERFACE_H +#define KGPGTEXTINTERFACE_H + +#include + +class KGpgTextInterfacePrivate; +class QString; +class QStringList; + +class KGpgTextInterface : public QObject +{ + Q_OBJECT + +private: + KGpgTextInterfacePrivate * const d; + + KGpgTextInterface(); + Q_DISABLE_COPY(KGpgTextInterface) + +public: + explicit KGpgTextInterface(QObject *parent, const QString &keyID, const QStringList &options); + ~KGpgTextInterface(); + +signals: + /** + * Emitted when all files passed to KgpgSignFile() where processed. + */ + void fileSignFinished(); + +public Q_SLOTS: + /** + * Sign file function + * @param keyID the signing key ID. + * @param srcUrl file to sign. + * @param options additional gpg options, e.g. "--armor" + */ + void signFiles(const KUrl::List &srcUrl); + +private Q_SLOTS: + void slotSignFile(); + void slotSignFinished(); +}; + +#endif diff --git a/kgpg/klinebufferedprocess.cpp b/kgpg/klinebufferedprocess.cpp new file mode 100644 index 00000000..adca814f --- /dev/null +++ b/kgpg/klinebufferedprocess.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2008 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "klinebufferedprocess.h" + +class KLineBufferedProcessPrivate +{ +public: + KLineBufferedProcessPrivate(KLineBufferedProcess *parent); + +//private slot implementations + void _k_receivedStdout(); + void _k_receivedStderr(); + + QByteArray m_stdoutBuffer; + QByteArray m_stderrBuffer; + int m_newlineInStdout; + int m_newlineInStderr; + KLineBufferedProcess * const m_parent; + const QByteArray m_lineEnd; +}; + +KLineBufferedProcessPrivate::KLineBufferedProcessPrivate(KLineBufferedProcess *parent) + : m_newlineInStdout(-1), + m_newlineInStderr(-1), + m_parent(parent), +#ifdef Q_OS_WIN32 //krazy:exclude=cpp + m_lineEnd("\r\n") +#else + m_lineEnd("\n") +#endif +{ +} + +KLineBufferedProcess::KLineBufferedProcess(QObject *parent) + : KProcess(parent), + d(new KLineBufferedProcessPrivate(this)) +{ + connect(this, SIGNAL(readyReadStandardOutput()), this, SLOT(_k_receivedStdout())); + connect(this, SIGNAL(readyReadStandardError()), this, SLOT(_k_receivedStderr())); +} + +KLineBufferedProcess::~KLineBufferedProcess() +{ + delete d; +} + +void KLineBufferedProcessPrivate::_k_receivedStdout() +{ + QByteArray ndata = m_parent->readAllStandardOutput(); + int oldBufferSize = m_stdoutBuffer.size(); + m_stdoutBuffer.append(ndata); + + if (m_newlineInStdout < 0) { + m_newlineInStdout = ndata.indexOf(m_lineEnd); + if (m_newlineInStdout >= 0) { + m_newlineInStdout += oldBufferSize; + emit m_parent->lineReadyStandardOutput(); + } + } +} + +void KLineBufferedProcessPrivate::_k_receivedStderr() +{ + QByteArray ndata = m_parent->readAllStandardError(); + int oldBufferSize = m_stderrBuffer.size(); + m_stderrBuffer.append(ndata); + + if (m_newlineInStderr < 0) { + m_newlineInStderr = ndata.indexOf(m_lineEnd); + if (m_newlineInStderr >= 0) { + m_newlineInStderr += oldBufferSize; + emit m_parent->lineReadyStandardError(); + } + } +} + +bool KLineBufferedProcess::readLineStandardOutput(QByteArray *line) +{ + if (d->m_newlineInStdout < 0) { + return false; + } + + // don't copy '\n' + *line = d->m_stdoutBuffer.left(d->m_newlineInStdout); + d->m_stdoutBuffer.remove(0, d->m_newlineInStdout + d->m_lineEnd.length()); + + d->m_newlineInStdout = d->m_stdoutBuffer.indexOf(d->m_lineEnd); + + return true; +} + +bool KLineBufferedProcess::readLineStandardError(QByteArray *line) +{ + if (d->m_newlineInStderr < 0) { + return false; + } + + // don't copy '\n' + *line = d->m_stderrBuffer.left(d->m_newlineInStderr); + d->m_stderrBuffer.remove(0, d->m_newlineInStderr + d->m_lineEnd.length()); + + d->m_newlineInStderr = d->m_stderrBuffer.indexOf(d->m_lineEnd); + + return true; +} + +bool KLineBufferedProcess::hasLineStandardOutput() const +{ + return d->m_newlineInStdout >= 0; +} + +bool KLineBufferedProcess::hasLineStandardError() const +{ + return d->m_newlineInStderr >= 0; +} + +#include "klinebufferedprocess.moc" diff --git a/kgpg/klinebufferedprocess.h b/kgpg/klinebufferedprocess.h new file mode 100644 index 00000000..5eac1f23 --- /dev/null +++ b/kgpg/klinebufferedprocess.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2008 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KLINEBUFFEREDPROCESS_H +#define KLINEBUFFEREDPROCESS_H + +#include + +class QByteArray; +class KLineBufferedProcessPrivate; + +/** + * Read output of a process split into lines + * + * This class reads the output of a process and splits it up into lines. This + * is especially useful if you try to parse the output of a command line tool. + * + * \b Usage \n + * + * The class is created and set up like a KProcess. After this you can do + * something like this: + * + * \code + * connect(m_linebufprocess, SIGNAL(lineReadyStandardOutput()), SLOT(dataStdout())); + * ... + * void myobj::dataStdout() + * { + * while (m_linebufprocess->hasLineStandardOutput()) { + * QByteArray line; + * m_linebufprocess->readLineStandardOutput(line); + * ... + * } + * } + * \endcode + * + * Never use the read functionality of KProcess with this class. This class + * needs to read all data from the process into an internal buffer first. If + * you try to use the read functions of the parent classes you would normally + * get no output at all. + * + * The write functions of the parent classes are not effected. You can use + * them exactly the same way as in KProcess. + * + * @author Rolf Eike Beer + */ +class KLineBufferedProcess : public KProcess +{ + Q_OBJECT + friend class KLineBufferedProcessPrivate; + +public: + /** + * Constructor + */ + explicit KLineBufferedProcess(QObject *parent = 0); + + /** + * Destructor + */ + ~KLineBufferedProcess(); + + /** + * Reads a line of text (excluding '\\n') from stdout. + * + * Use readLineStdout() in response to a lineReadyStdout() signal or + * when hasLineStdout() returns true. You may use it multiple times if + * more than one line of data is available. If no complete line is + * available the content of line is undefined and the function returns + * false. + * + * @param line is used to store the line that was read. + * @return if data was read or not + */ + bool readLineStandardOutput(QByteArray *line); + + /** + * Reads a line of text (excluding '\\n') from stderr. + * + * Use readLineStderr() in response to a lineReadyStderr() signal or + * when hasLineStderr() returns true. You may use it multiple times if + * more than one line of data is available. If no complete line is + * available the content of line is undefined and the function returns + * false. + * + * @param line is used to store the line that was read. + * @return if data was read or not + */ + bool readLineStandardError(QByteArray *line); + + /** + * Checks if a line is ready on stdout + * + * @return true if a complete line can be read + */ + bool hasLineStandardOutput() const; + + /** + * Checks if a line is ready on stdout + * + * @return true if a complete line can be read + */ + bool hasLineStandardError() const; + +signals: + /** + * Emitted when there is a line of data available from stdout when there was + * previously none. + * There may or may not be more than one line available for reading when this + * signal is emitted. + */ + void lineReadyStandardOutput(); + + /** + * Emitted when there is a line of data available from stderr when there was + * previously none. + * There may or may not be more than one line available for reading when this + * signal is emitted. + */ + void lineReadyStandardError(); + +private: + KLineBufferedProcessPrivate* const d; + + Q_PRIVATE_SLOT(d, void _k_receivedStdout()) + Q_PRIVATE_SLOT(d, void _k_receivedStderr()) +}; + +#endif // KLINEBUFFEREDPROCESS_H diff --git a/kgpg/main.cpp b/kgpg/main.cpp new file mode 100644 index 00000000..8615a122 --- /dev/null +++ b/kgpg/main.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2009,2010,2011,2012,2013,2014 + * Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpg.h" + +#include +#include +#include +#include + +static const char description[] = + I18N_NOOP("KGpg - simple gui for gpg\n\nKGpg was designed to make gpg very easy to use.\nI tried to make it as secure as possible.\nHope you enjoy it."); + +static const char version[] = "2.13.1"; + +int main(int argc, char *argv[]) +{ + KAboutData about("kgpg", 0, ki18n("KGpg"), version, ki18n(description), KAboutData::License_GPL, ki18n("(C) 2003 Jean-Baptiste Mardelle"), KLocalizedString(), "http://utils.kde.org/projects/kgpg"); + about.addAuthor(ki18n("Jean-Baptiste Mardelle"), ki18n("Author and former maintainer"), "bj@altern.org"); + about.addAuthor(ki18n("Jimmy Gilles"), KLocalizedString(), "jimmygilles@gmail.com"); + about.addAuthor(ki18n("Rolf Eike Beer"), ki18n("Maintainer"), "kde@opensource.sf-tec.de"); + + KCmdLineArgs::init(argc, argv, &about); + + KCmdLineOptions options; + options.add("e", ki18n("Encrypt file")); + options.add("k", ki18n("Open key manager")); + options.add("d", ki18n("Open editor")); + options.add("s", ki18n("Show encrypted file")); + options.add("S", ki18n("Sign File")); + options.add("V", ki18n("Verify signature")); + options.add("+[File]", ki18n("File to open")); + KCmdLineArgs::addCmdLineOptions(options); + KUniqueApplication::addCmdLineOptions(); + + if (!KUniqueApplication::start()) + return 0; + + KApplication *app = new KGpgApp(); + app->setQuitOnLastWindowClosed(false); + return app->exec(); +} diff --git a/kgpg/model/gpgservermodel.cpp b/kgpg/model/gpgservermodel.cpp new file mode 100644 index 00000000..2710adb8 --- /dev/null +++ b/kgpg/model/gpgservermodel.cpp @@ -0,0 +1,109 @@ +/* Copyright 2014 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gpgservermodel.h" + +#include +#include + +GpgServerModel::GpgServerModel(QObject *parent) + : QStringListModel(parent), + m_defaultRow(-1) +{ + connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotRowsRemoved(QModelIndex,int,int))); +} + +GpgServerModel::~GpgServerModel() +{ +} + +void +GpgServerModel::setDefault(const int row) +{ + Q_ASSERT(row < stringList().count()); + + if (m_defaultRow == row) + return; + + const int oldRow = m_defaultRow; + m_defaultRow = row; + if (oldRow >= 0) + emit dataChanged(index(oldRow, 0), index(oldRow, 0)); + if (row >= 0) + emit dataChanged(index(row, 0), index(row, 0)); +} + +void +GpgServerModel::setDefault(const QString &server) +{ + if (server.isEmpty()) { + setDefault(-1); + } else { + const int row = stringList().indexOf(server); + Q_ASSERT(row >= 0); + setDefault(row); + } +} + +int +GpgServerModel::defaultRow() const +{ + // only in case there is not set any default yet promote the first entry of the list + if ((m_defaultRow >= 0) || stringList().empty()) + return m_defaultRow; + else + return 0; +} + +QString +GpgServerModel::defaultServer() const +{ + if (stringList().isEmpty()) + return QString(); + if (m_defaultRow < 0) + return stringList().first(); + else + return stringList().at(m_defaultRow); +} + +QVariant +GpgServerModel::data(const QModelIndex &index, int role) const +{ + QVariant res = QStringListModel::data(index, role); + + if ((role == Qt::DisplayRole) && (index.row() == m_defaultRow)) + res = i18nc("Mark default keyserver in GUI", "%1 (Default)", res.toString()); + + return res; +} + +void +GpgServerModel::slotRowsRemoved(const QModelIndex &, int start, int end) +{ + if (end < m_defaultRow) { + // removed before default, i.e. default is moved up + setDefault(m_defaultRow - (end - start) - 1); + } else if ((start <= m_defaultRow) && (end >= m_defaultRow)) { + // the default was deleted + if (m_defaultRow >= rowCount()) + m_defaultRow = -1; // avoid sending dataChanged() for the already deleted row + if (rowCount() > 0) + setDefault(0); + } +} diff --git a/kgpg/model/gpgservermodel.h b/kgpg/model/gpgservermodel.h new file mode 100644 index 00000000..044b0eca --- /dev/null +++ b/kgpg/model/gpgservermodel.h @@ -0,0 +1,56 @@ +/* Copyright 2014 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef GPGSERVERMODEL_H +#define GPGSERVERMODEL_H + +#include + +/** + * @brief model holding the configured GnuPG key servers + */ +class GpgServerModel: public QStringListModel { + Q_OBJECT +public: + GpgServerModel(QObject *parent = NULL); + virtual ~GpgServerModel(); + + void setDefault(const QString &server); + void setDefault(const int index); + + /** + * @brief returns the row of the current default keyserver + */ + int defaultRow() const; + + /** + * @brief returns the URL of the default server + * @retval QString() if no default server is selected + */ + QString defaultServer() const; + + virtual QVariant data(const QModelIndex &index, int role) const; + +private slots: + void slotRowsRemoved(const QModelIndex &, int start, int end); + +private: + int m_defaultRow; +}; + +#endif diff --git a/kgpg/model/groupeditproxymodel.cpp b/kgpg/model/groupeditproxymodel.cpp new file mode 100644 index 00000000..3f92c507 --- /dev/null +++ b/kgpg/model/groupeditproxymodel.cpp @@ -0,0 +1,166 @@ +/* Copyright 2008,2010,2012,2013,2014 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "groupeditproxymodel.h" +#include "model/kgpgitemnode.h" +#include "kgpgitemmodel.h" +#include "core/kgpgkey.h" + +#include +#include +#include + +using namespace KgpgCore; + +GroupEditProxyModel::GroupEditProxyModel(QObject *parent, const bool invert, QList *ids, const KgpgCore::KgpgKeyTrust mintrust) + : QSortFilterProxyModel(parent), + m_model(NULL), + m_invert(invert), + m_ids(ids), + m_mintrust(mintrust) +{ +} + +void +GroupEditProxyModel::setKeyModel(KGpgItemModel *md) +{ + m_model = md; + setSourceModel(md); +} + +bool +GroupEditProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + QModelIndex idx = m_model->index(source_row, 0, source_parent); + KGpgNode *l = m_model->nodeForIndex(idx); + + if (l->getType() & ~ITYPE_PAIR) + return false; + + if (l->getTrust() < m_mintrust) + return false; + + const KGpgKeyNode * const lk = l->toKeyNode(); + for (int i = 0; i < m_ids->count(); i++) + if (lk->compareId(m_ids->at(i)->getId())) + return !m_invert; + + return m_invert; +} + +KGpgNode * +GroupEditProxyModel::nodeForIndex(const QModelIndex &index) const +{ + return m_model->nodeForIndex(mapToSource(index)); +} + +int +GroupEditProxyModel::columnCount(const QModelIndex &) const +{ + return 3; +} + +int +GroupEditProxyModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + if (parent.isValid()) + return 0; + if (m_model == NULL) + return 0; + return QSortFilterProxyModel::rowCount(parent); +} + +QVariant +GroupEditProxyModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || (index.column() >= 3)) + return QVariant(); + + KGpgNode *nd = m_model->nodeForIndex(mapToSource(index)); + + switch (role) { + case Qt::ToolTipRole: + case Qt::DisplayRole: + switch (index.column()) { + case 0: + if (role == Qt::ToolTipRole) + return nd->getNameComment(); + else + return nd->getName(); + case 1: + if (role == Qt::ToolTipRole) { + if (nd->toKeyNode()->getExpiration().isValid() && (nd->toKeyNode()->getExpiration() <= QDateTime::currentDateTime())) + return i18nc("Expired key", "Expired"); + break; + } else { + return nd->getEmail(); + } + case 2: + if (role == Qt::ToolTipRole) + return nd->toKeyNode()->getBeautifiedFingerprint(); + else + return nd->getId().right(8); + default: + break; + } + case Qt::DecorationRole: + if (index.column() != 1) + break; + + if (nd->toKeyNode()->getExpiration().isValid() && (nd->toKeyNode()->getExpiration() <= QDateTime::currentDateTime())) + return QIcon::fromTheme(QLatin1String("dialog-warning")); + } + + return QVariant(); +} + +bool +GroupEditProxyModel::hasChildren(const QModelIndex &parent) const +{ + if (m_model == NULL) + return false; + if (parent.column() > 0) + return false; + return !parent.isValid(); +} + +QVariant +GroupEditProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation != Qt::Horizontal) + return QVariant(); + + if (m_model == NULL) + return QVariant(); + + switch (section) { + case 0: + return m_model->headerData(KEYCOLUMN_NAME, orientation, role); + case 1: + return m_model->headerData(KEYCOLUMN_EMAIL, orientation, role); + case 2: + return m_model->headerData(KEYCOLUMN_ID, orientation, role); + default: + return QVariant(); + } +} diff --git a/kgpg/model/groupeditproxymodel.h b/kgpg/model/groupeditproxymodel.h new file mode 100644 index 00000000..b95cc5b9 --- /dev/null +++ b/kgpg/model/groupeditproxymodel.h @@ -0,0 +1,55 @@ +/* Copyright 2008,2014 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef GROUPEDITPROXYMODEL_H +#define GROUPEDITPROXYMODEL_H + +#include "core/kgpgkey.h" + +#include + +class KGpgNode; +class KGpgItemModel; + +class GroupEditProxyModel: public QSortFilterProxyModel +{ +public: + explicit GroupEditProxyModel(QObject * parent, const bool invert, QList *ids, const KgpgCore::KgpgKeyTrust mintrust = KgpgCore::TRUST_FULL); + + void setKeyModel(KGpgItemModel *); + + KGpgNode *nodeForIndex(const QModelIndex &index) const; + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual bool hasChildren(const QModelIndex &parent) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + virtual int columnCount(const QModelIndex &) const; + +private: + KGpgItemModel *m_model; + bool m_invert; + QList *m_ids; + KgpgCore::KgpgKeyTrust m_mintrust; +}; + +#endif diff --git a/kgpg/model/keylistproxymodel.cpp b/kgpg/model/keylistproxymodel.cpp new file mode 100644 index 00000000..dc3d4432 --- /dev/null +++ b/kgpg/model/keylistproxymodel.cpp @@ -0,0 +1,595 @@ +/* Copyright 2008,2009,2010,2012,2013 Rolf Eike Beer + * Copyright 2013 Thomas Fischer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "keylistproxymodel.h" +#include "model/kgpgitemnode.h" +#include "kgpgitemmodel.h" +#include "kgpgsettings.h" +#include "core/kgpgkey.h" +#include "core/images.h" + +#include +#include + +using namespace KgpgCore; + +class KeyListProxyModelPrivate { + KeyListProxyModel * const q_ptr; + + Q_DECLARE_PUBLIC(KeyListProxyModel) +public: + KeyListProxyModelPrivate(KeyListProxyModel *parent, const KeyListProxyModel::DisplayMode mode); + + bool lessThan(const KGpgNode *left, const KGpgNode *right, const int column) const; + bool nodeLessThan(const KGpgNode *left, const KGpgNode *right, const int column) const; + KGpgItemModel *m_model; + bool m_onlysecret; + bool m_encryptionKeys; + KgpgCore::KgpgKeyTrustFlag m_mintrust; + int m_previewsize; + int m_idLength; + KeyListProxyModel::DisplayMode m_displaymode; + int m_emailSorting; + + QString reorderEmailComponents(const QString &emailAddress) const; + QVariant dataSingleColumn(const QModelIndex &index, int role, const KGpgNode *node) const; + QVariant dataMultiColumn(const QModelIndex &index, int role, const KGpgNode *node) const; +}; + +KeyListProxyModelPrivate::KeyListProxyModelPrivate(KeyListProxyModel *parent, const KeyListProxyModel::DisplayMode mode) + : q_ptr(parent), + m_model(NULL), + m_onlysecret(false), + m_encryptionKeys(false), + m_mintrust(TRUST_UNKNOWN), + m_previewsize(22), + m_idLength(8), + m_displaymode(mode), + m_emailSorting(KGpgSettings::emailSorting()) +{ +} + +/** + * Reverses the list's order (this modifies the list) and returns + * a string containing the reversed list's elements joined by a char. + */ +static QString reverseListAndJoinWithChar(const QStringList &list, const QChar &separator) +{ + QString result = list.last(); + for (int i = list.count() - 2; i >= 0; --i) + result.append(separator).append(list[i]); + return result; +} + +QString +KeyListProxyModelPrivate::reorderEmailComponents(const QString &emailAddress) const +{ + if (emailAddress.isEmpty()) + return QString(); + + /// split email addresses at @ + static const QChar charAt = QLatin1Char('@'); + /// split domain at . + static const QChar charDot = QLatin1Char('.'); + + /// convert result to lower case to make sorting case-insensitive + QString result = emailAddress.toLower(); + + switch (m_emailSorting) { + case KGpgSettings::EnumEmailSorting::TLDfirst: + { + /// get components of an email address + /// john.doe@mail.kde.org becomes [john.doe, mail.kde.org] + const QStringList emailComponents = result.split(charAt); + if (emailComponents.count() != 2) /// expect an email address to contain exactly one @ + break; + /// get components of a domain + /// mail.kde.org becomes [mail, kde, org] + const QString fqdn = emailComponents.last(); + QStringList fqdnComponents = fqdn.split(charDot); + if (fqdnComponents.count() < 2) /// if domain consists of less than two components ... + return fqdn + charDot + emailComponents.first(); /// ... take shortcut + /// prepend localpart, will be last after list is reversed + fqdnComponents.insert(0, emailComponents.first()); + /// reverse components of domain, result becomes e.g. org.kde.mail + /// with localpart already in the list it becomes org.kde.mail.john.doe + result = reverseListAndJoinWithChar(fqdnComponents, charDot); + break; + } + case KGpgSettings::EnumEmailSorting::DomainFirst: + { + /// get components of an email address + /// john.doe@mail.kde.org becomes [john.doe, mail.kde.org] + const QStringList emailComponents = result.split(charAt); + if (emailComponents.count() != 2) /// expect an email address to contain exactly one @ + break; + /// get components of a domain + /// mail.kde.org becomes [mail, kde, org] + const QString fqdn = emailComponents.last(); + QStringList fqdnComponents = fqdn.split(charDot); + if (fqdnComponents.count() < 2) /// if domain consists of less than two components ... + return fqdn + charDot + emailComponents.first(); /// ... take shortcut + /// reverse last two components of domain, becomes e.g. kde.org + /// TODO will fail for three-part domains like kde.org.uk + result = charDot + fqdnComponents.takeLast(); + result.prepend(fqdnComponents.takeLast()); + /// append remaining components of domain, becomes e.g. kde.org.mail + result.append(charDot).append(fqdnComponents.join(charDot)); + /// append user name component of email address, becomes e.g. kde.org.mail.john.doe + result.append(charDot).append(emailComponents.first()); + break; + } + case KGpgSettings::EnumEmailSorting::FQDNFirst: + { + /// get components of an email address + /// john.doe@mail.kde.org becomes [john.doe, mail.kde.org] + const QStringList emailComponents = result.split(charAt); + /// assemble result by joining components in reverse order, + /// separated by a dot, becomes e.g. mail.kde.org.john.doe + result = reverseListAndJoinWithChar(emailComponents, charDot); + break; + } + case KGpgSettings::EnumEmailSorting::Alphabetical: + /// do not modify email address except for lower-case conversion + break; + } + + return result; +} + +QVariant +KeyListProxyModelPrivate::dataSingleColumn(const QModelIndex &index, int role, const KGpgNode *node) const +{ + Q_Q(const KeyListProxyModel); + + if (index.column() != 0) + return QVariant(); + + switch (role) { + case Qt::DecorationRole: + if (node->getType() == ITYPE_UAT) { + if (m_previewsize > 0) { + const KGpgUatNode *nd = node->toUatNode(); + return nd->getPixmap().scaled(m_previewsize + 5, m_previewsize, Qt::KeepAspectRatio); + } else { + return Images::photo(); + } + } else { + return m_model->data(q->mapToSource(index), Qt::DecorationRole); + } + case Qt::DisplayRole: { + const QModelIndex srcidx(q->mapToSource(index)); + const int srcrow = srcidx.row(); + + const QModelIndex ididx(srcidx.sibling(srcrow, KEYCOLUMN_ID)); + const QString id(m_model->data(ididx, Qt::DisplayRole).toString().right(m_idLength)); + + const QModelIndex mailidx(srcidx.sibling(srcrow, KEYCOLUMN_EMAIL)); + const QString mail(m_model->data(mailidx, Qt::DisplayRole).toString()); + + const QModelIndex nameidx(srcidx.sibling(srcrow, KEYCOLUMN_NAME)); + const QString name(m_model->data(nameidx, Qt::DisplayRole).toString()); + + if (m_displaymode == KeyListProxyModel::SingleColumnIdFirst) { + if (mail.isEmpty()) + return i18nc("ID: Name", "%1: %2", id, name); + else + return i18nc("ID: Name ", "%1: %2 <%3>", id, name, mail); + } else { + if (mail.isEmpty()) + return i18nc("Name: ID", "%1: %2", name, id); + else + return i18nc("Name : ID", "%1 <%2>: %3", name, mail, id); + } + } + case Qt::ToolTipRole: { + const QModelIndex srcidx(q->mapToSource(index)); + const int srcrow = srcidx.row(); + + const QModelIndex ididx(srcidx.sibling(srcrow, KEYCOLUMN_ID)); + return m_model->data(ididx, Qt::DisplayRole); + } + default: + return QVariant(); + } +} + +QVariant +KeyListProxyModelPrivate::dataMultiColumn(const QModelIndex &index, int role, const KGpgNode *node) const +{ + Q_Q(const KeyListProxyModel); + + if ((node->getType() == ITYPE_UAT) && (role == Qt::DecorationRole) && (index.column() == 0)) { + if (m_previewsize > 0) { + const KGpgUatNode *nd = node->toUatNode(); + return nd->getPixmap().scaled(m_previewsize + 5, m_previewsize, Qt::KeepAspectRatio); + } else { + return Images::photo(); + } + } else if ((role == Qt::DisplayRole) && (index.column() == KEYCOLUMN_ID)) { + QString id = m_model->data(q->mapToSource(index), Qt::DisplayRole).toString(); + return id.right(m_idLength); + } + return m_model->data(q->mapToSource(index), role); +} + +KeyListProxyModel::KeyListProxyModel(QObject *parent, const DisplayMode mode) + : QSortFilterProxyModel(parent), + d_ptr(new KeyListProxyModelPrivate(this, mode)) +{ + setFilterCaseSensitivity(Qt::CaseInsensitive); + setFilterKeyColumn(-1); + setDynamicSortFilter(true); +} + +KeyListProxyModel::~KeyListProxyModel() +{ + delete d_ptr; +} + +bool +KeyListProxyModel::hasChildren(const QModelIndex &idx) const +{ + return sourceModel()->hasChildren(mapToSource(idx)); +} + +void +KeyListProxyModel::setKeyModel(KGpgItemModel *md) +{ + Q_D(KeyListProxyModel); + + d->m_model = md; + setSourceModel(md); +} + +bool +KeyListProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + Q_D(const KeyListProxyModel); + + KGpgNode *l = d->m_model->nodeForIndex(left); + KGpgNode *r = d->m_model->nodeForIndex(right); + + return d->lessThan(l, r, left.column()); +} + +bool +KeyListProxyModelPrivate::lessThan(const KGpgNode *left, const KGpgNode *right, const int column) const +{ + const KGpgRootNode * const r = m_model->getRootNode(); + Q_ASSERT(r != left); + Q_ASSERT(r != right); + + if (r == left->getParentKeyNode()) { + if (r == right->getParentKeyNode()) { + if (left->getType() == ITYPE_GROUP) { + if (right->getType() == ITYPE_GROUP) + return left->getName() < right->getName(); + else + return true; + } else if (right->getType() == ITYPE_GROUP) + return false; + + // we don't need to care about group members here because they will never have root as parent + bool test1 = (left->getType() & ITYPE_PUBLIC) && !(left->getType() & ITYPE_SECRET); // only a public key + bool test2 = (right->getType() & ITYPE_PUBLIC) && !(right->getType() & ITYPE_SECRET); // only a public key + + // key-pair goes before simple public key + // extra check needed to get sorting by trust right + if (left->getType() == ITYPE_PAIR && test2) return (column != KEYCOLUMN_TRUST); + if (right->getType() == ITYPE_PAIR && test1) return (column == KEYCOLUMN_TRUST); + + return nodeLessThan(left, right, column); + } else { + return lessThan(left, right->getParentKeyNode(), column); + } + } else { + if (r == right->getParentKeyNode()) { + return lessThan(left->getParentKeyNode(), right, column); + } else if (left->getParentKeyNode() == right->getParentKeyNode()) { + if (left->getType() != right->getType()) + return (left->getType() < right->getType()); + + return nodeLessThan(left, right, column); + } else { + return lessThan(left->getParentKeyNode(), right->getParentKeyNode(), column); + } + } + return false; +} + +bool +KeyListProxyModelPrivate::nodeLessThan(const KGpgNode *left, const KGpgNode *right, const int column) const +{ + Q_ASSERT(left->getType() == right->getType()); + + switch (column) { + case KEYCOLUMN_NAME: + if (left->getType() == ITYPE_SIGN) { + if (left->getName().startsWith(QLatin1Char( '[' )) && !right->getName().startsWith(QLatin1Char( '[' ))) + return false; + else if (!left->getName().startsWith(QLatin1Char( '[' )) && right->getName().startsWith(QLatin1Char( '[' ))) + return true; + else if (left->getName().startsWith(QLatin1Char( '[' )) && right->getName().startsWith(QLatin1Char( '[' ))) + return (left->getId() < right->getId()); + } + return (left->getName().compare(right->getName().toLower(), Qt::CaseInsensitive) < 0); + case KEYCOLUMN_EMAIL: + /// reverse email address to sort by TLD first, then domain, and account name last + return (reorderEmailComponents(left->getEmail()) < reorderEmailComponents(right->getEmail())); + case KEYCOLUMN_TRUST: + return (left->getTrust() < right->getTrust()); + case KEYCOLUMN_EXPIR: + return (left->getExpiration() < right->getExpiration()); + case KEYCOLUMN_SIZE: + if ((left->getType() & ITYPE_PAIR) && (right->getType() & ITYPE_PAIR)) { + unsigned int lsign, lenc, rsign, renc; + + if (left->getType() & ITYPE_GROUP) { + const KGpgGroupMemberNode *g = static_cast(left); + + lsign = g->getSignKeySize(); + lenc = g->getEncryptionKeySize(); + } else { + const KGpgKeyNode *g = static_cast(left); + + lsign = g->getSignKeySize(); + lenc = g->getEncryptionKeySize(); + } + + if (right->getType() & ITYPE_GROUP) { + const KGpgGroupMemberNode *g = static_cast(right); + + rsign = g->getSignKeySize(); + renc = g->getEncryptionKeySize(); + } else { + const KGpgKeyNode *g = static_cast(right); + + rsign = g->getSignKeySize(); + renc = g->getEncryptionKeySize(); + } + + if (lsign != rsign) + return lsign < rsign; + else + return lenc < renc; + } else { + return (left->getSize() < right->getSize()); + } + case KEYCOLUMN_CREAT: + return (left->getCreation() < right->getCreation()); + default: + Q_ASSERT(column == KEYCOLUMN_ID); + return (left->getId().right(m_idLength) < right->getId().right(m_idLength)); + } +} + +bool +KeyListProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + Q_D(const KeyListProxyModel); + QModelIndex idx = d->m_model->index(source_row, 0, source_parent); + const KGpgNode *l = d->m_model->nodeForIndex(idx); + + if (l == d->m_model->getRootNode()) + return false; + + if (d->m_onlysecret) { + switch (l->getType()) { + case ITYPE_PUBLIC: + case ITYPE_GPUBLIC: + case ITYPE_GROUP: + return false; + default: + break; + } + } + + switch (d->m_displaymode) { + case SingleColumnIdFirst: + case SingleColumnIdLast: + if (l->getType() == ITYPE_GROUP) + return false; + default: + break; + } + + if (l->getTrust() < d->m_mintrust) + return false; + + /* check for expired signatures */ + if ((d->m_mintrust > TRUST_EXPIRED) && (l->getType() == ITYPE_SIGN)) { + const QDateTime expDate = l->toSignNode()->getExpiration(); + if (expDate.isValid() && (expDate < QDateTime::currentDateTime())) + return false; + } + + if (l->getParentKeyNode() != d->m_model->getRootNode()) + return true; + + if (d->m_encryptionKeys && ((l->getType() & ITYPE_GROUP) == 0)) { + if (!l->toKeyNode()->canEncrypt()) + return false; + } + + if (l->getName().contains(filterRegExp())) + return true; + + if (l->getEmail().contains(filterRegExp())) + return true; + + if (l->getId().contains(filterRegExp())) + return true; + + return false; +} + +void +KeyListProxyModel::setOnlySecret(const bool b) +{ + Q_D(KeyListProxyModel); + + d->m_onlysecret = b; + invalidateFilter(); +} + +void +KeyListProxyModel::settingsChanged() +{ + Q_D(KeyListProxyModel); + + const int newSort = KGpgSettings::emailSorting(); + + if (newSort != d->m_emailSorting) { + d->m_emailSorting = newSort; + invalidate(); + } +} + +void +KeyListProxyModel::setTrustFilter(const KgpgCore::KgpgKeyTrustFlag t) +{ + Q_D(KeyListProxyModel); + + d->m_mintrust = t; + invalidateFilter(); +} + +void +KeyListProxyModel::setEncryptionKeyFilter(bool b) +{ + Q_D(KeyListProxyModel); + + d->m_encryptionKeys = b; + invalidateFilter(); +} + +KGpgNode * +KeyListProxyModel::nodeForIndex(const QModelIndex &index) const +{ + Q_D(const KeyListProxyModel); + + return d->m_model->nodeForIndex(mapToSource(index)); +} + +QModelIndex +KeyListProxyModel::nodeIndex(KGpgNode *node) +{ + Q_D(KeyListProxyModel); + + return mapFromSource(d->m_model->nodeIndex(node)); +} + +void +KeyListProxyModel::setPreviewSize(const int pixel) +{ + Q_D(KeyListProxyModel); + + emit layoutAboutToBeChanged(); + d->m_previewsize = pixel; + emit layoutChanged(); +} + +QVariant +KeyListProxyModel::data(const QModelIndex &index, int role) const +{ + Q_D(const KeyListProxyModel); + + if (!index.isValid()) + return QVariant(); + + const KGpgNode *node = nodeForIndex(index); + + switch (d->m_displaymode) { + case MultiColumn: + return d->dataMultiColumn(index, role, node); + case SingleColumnIdFirst: + case SingleColumnIdLast: + return d->dataSingleColumn(index, role, node); + } + + Q_ASSERT(0); + + return QVariant(); +} + +KGpgItemModel * +KeyListProxyModel::getModel() const +{ + Q_D(const KeyListProxyModel); + + return d->m_model; +} + +int +KeyListProxyModel::idLength() const +{ + Q_D(const KeyListProxyModel); + + return d->m_idLength; +} + +void +KeyListProxyModel::setIdLength(const int length) +{ + Q_D(KeyListProxyModel); + + if (length == d->m_idLength) + return; + + d->m_idLength = length; + invalidate(); +} + +bool +KeyListProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_UNUSED(role); + + if (value.type() != QVariant::String) + return false; + + KGpgNode *node = nodeForIndex(index); + + if (!node) + return false; + + const QString newName = value.toString(); + + if (newName.isEmpty() || (newName == node->getName())) + return false; + + node->toGroupNode()->rename(newName); + + return true; +} + +Qt::ItemFlags +KeyListProxyModel::flags(const QModelIndex &index) const +{ + KGpgNode *node = nodeForIndex(index); + Qt::ItemFlags flags = QSortFilterProxyModel::flags(index); + + if ((node->getType() == ITYPE_GROUP) && (index.column() == KEYCOLUMN_NAME)) + flags |= Qt::ItemIsEditable; + + return flags; +} + +#include "keylistproxymodel.moc" diff --git a/kgpg/model/keylistproxymodel.h b/kgpg/model/keylistproxymodel.h new file mode 100644 index 00000000..cd957779 --- /dev/null +++ b/kgpg/model/keylistproxymodel.h @@ -0,0 +1,92 @@ +/* Copyright 2008,2013 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KEYLISTPROXYMODEL_H +#define KEYLISTPROXYMODEL_H + +#include +#include "core/kgpgkey.h" + +class KGpgNode; +class KGpgExpandableNode; +class KGpgItemModel; +class KeyListProxyModelPrivate; + +class KeyListProxyModel: public QSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(int idLength READ idLength WRITE setIdLength) + Q_DECLARE_PRIVATE(KeyListProxyModel) + + KeyListProxyModelPrivate * const d_ptr; + +public: + enum DisplayMode { + MultiColumn = 0, + SingleColumnIdFirst = 1, + SingleColumnIdLast = 2 + }; + + explicit KeyListProxyModel(QObject * parent = 0, const DisplayMode mode = MultiColumn); + virtual ~KeyListProxyModel(); + + virtual bool hasChildren(const QModelIndex &idx) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + void setKeyModel(KGpgItemModel *); + /** + * @brief set the minimum trust level to be shown + * @param t trust level + * + * This enables filtering by key trust. All keys that have a lower trust than + * the given value will be hidden. + */ + void setTrustFilter(const KgpgCore::KgpgKeyTrustFlag t); + + /** + * @brief show only keys capable of encryption + */ + void setEncryptionKeyFilter(bool b); + + KGpgNode *nodeForIndex(const QModelIndex &index) const; + QModelIndex nodeIndex(KGpgNode *node); + void setPreviewSize(const int pixel); + KGpgItemModel *getModel() const; + int idLength() const; + void setIdLength(const int length); + +public slots: + /** + * @brief set if only secret keys should be shown + * @param b new value + */ + void setOnlySecret(const bool b); + + /** + * @brief call this when the settings have changed + */ + void settingsChanged(); + +protected: + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; +}; + +#endif diff --git a/kgpg/model/kgpgitemmodel.cpp b/kgpg/model/kgpgitemmodel.cpp new file mode 100644 index 00000000..4f06db04 --- /dev/null +++ b/kgpg/model/kgpgitemmodel.cpp @@ -0,0 +1,557 @@ +/* Copyright 2008,2009,2010,2011,2012,2013 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "kgpgitemmodel.h" + +#include "kgpgsettings.h" +#include "core/convert.h" +#include "core/images.h" +#include "model/kgpgitemnode.h" + +#include +#include +#include + +KGpgItemModel::KGpgItemModel(QObject *parent) + : QAbstractItemModel(parent), + m_root(new KGpgRootNode(this)), + m_default(KGpgSettings::defaultKey()) +{ + QMetaObject::invokeMethod(this, "refreshGroups", Qt::QueuedConnection); +} + +KGpgItemModel::~KGpgItemModel() +{ + delete m_root; +} + +QModelIndex +KGpgItemModel::index(int row, int column, const QModelIndex &parent) const +{ + if (hasIndex(row, column, parent)) { + KGpgNode *parentNode = nodeForIndex(parent); + KGpgNode *childNode = parentNode->getChild(row); + return createIndex(row, column, childNode); + } + return QModelIndex(); +} + +QModelIndex +KGpgItemModel::parent(const QModelIndex &child) const +{ + if (!child.isValid()) + return QModelIndex(); + KGpgNode *childNode = nodeForIndex(child); + KGpgNode *parentNode = childNode->m_parent; + + if (parentNode == m_root) + return QModelIndex(); + + Q_ASSERT(parentNode != NULL); + int row = rowForNode(parentNode); + int column = 0; + + return createIndex(row, column, parentNode); +} + +int +KGpgItemModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + + KGpgNode *parentNode = nodeForIndex(parent); + + return parentNode->getChildCount(); +} + +bool +KGpgItemModel::hasChildren(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return false; + + KGpgNode *parentNode = nodeForIndex(parent); + + return parentNode->hasChildren(); +} + +QVariant +KGpgItemModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + KGpgNode *node = nodeForIndex(index); + + if (role == Qt::FontRole) { + QFont f; + f.setBold(isDefaultKey(node)); + return f; + } + + switch (index.column()) { + case KEYCOLUMN_NAME: + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + return node->getName(); + case Qt::DecorationRole: + switch (node->getType()) { + case ITYPE_GROUP: + return Images::group(); + case ITYPE_GSECRET: + case ITYPE_SECRET: + return Images::orphan(); + case ITYPE_GPUBLIC: + case ITYPE_SUB: + case ITYPE_PUBLIC: + return Images::single(); + case ITYPE_GPAIR: + case ITYPE_PAIR: + return Images::pair(); + case ITYPE_UID: + return Images::userId(); + case ITYPE_UAT: + return node->toUatNode()->getPixmap(); + case ITYPE_REVSIGN: + return Images::revoke(); + case ITYPE_SIGN: + return Images::signature(); + default: + Q_ASSERT(0); + return QVariant(); + } + case Qt::ToolTipRole: + return node->getComment(); + } + break; + case KEYCOLUMN_EMAIL: + if (role == Qt::DisplayRole) + return node->getEmail(); + break; + case KEYCOLUMN_TRUST: + { + KgpgKeyTrust t = node->getTrust(); + + switch (role) { + case Qt::BackgroundColorRole: + switch (t) { + case TRUST_INVALID: + case TRUST_DISABLED: + return KGpgSettings::colorBad(); + case TRUST_EXPIRED: + return KGpgSettings::colorExpired(); + case TRUST_MARGINAL: + return KGpgSettings::colorMarginal(); + case TRUST_REVOKED: + return KGpgSettings::colorRev(); + case TRUST_UNDEFINED: + case TRUST_NONE: + return KGpgSettings::colorUnknown(); + case TRUST_FULL: + return KGpgSettings::colorGood(); + case TRUST_ULTIMATE: + return KGpgSettings::colorUltimate(); + case TRUST_UNKNOWN: + default: + return KGpgSettings::colorUnknown(); + } + case Qt::AccessibleTextRole: + return Convert::toString(t); + } + break; + } + case KEYCOLUMN_EXPIR: + if (role == Qt::DisplayRole) + return KGlobal::locale()->formatDate(node->getExpiration().date(), KLocale::ShortDate); + break; + case KEYCOLUMN_SIZE: + switch (role) { + case Qt::DisplayRole: + return node->getSize(); + case Qt::ToolTipRole: + switch (node->getType()) { + case ITYPE_PAIR: + case ITYPE_PUBLIC: + return node->toKeyNode()->getSignCount(); + case ITYPE_UAT: + return node->toUatNode()->getSignCount(); + case ITYPE_UID: + return node->toUidNode()->getSignCount(); + case ITYPE_SUB: + return node->toSubkeyNode()->getSignCount(); + } + } + break; + case KEYCOLUMN_CREAT: + if (role == Qt::DisplayRole) + return KGlobal::locale()->formatDate(node->getCreation().date(), KLocale::ShortDate); + break; + case KEYCOLUMN_ID: + switch (role) { + case Qt::DisplayRole: + return node->getId(); + case Qt::ToolTipRole: + switch (node->getType()) { + case ITYPE_PAIR: + case ITYPE_PUBLIC: + return node->toKeyNode()->getFingerprint(); + case ITYPE_SECRET: + return node->toOrphanNode()->getFingerprint(); + default: + return QVariant(); + } + default: + return QVariant(); + } + break; + } + + return QVariant(); +} + +KGpgNode * +KGpgItemModel::nodeForIndex(const QModelIndex &index) const +{ + if (index.isValid()) + return static_cast(index.internalPointer()); + return m_root; +} + +KGpgKeyNode * +KGpgItemModel::findKeyNode(const QString& id) const +{ + return m_root->findKey(id); +} + +int +KGpgItemModel::rowForNode(KGpgNode *node) const +{ + return node->m_parent->getChildIndex(node); +} + +KGpgRootNode * +KGpgItemModel::getRootNode() const +{ + return m_root; +} + +QString +KGpgItemModel::statusCountMessage() const +{ + const int groups = m_root->groupChildren(); + const int keys = m_root->getChildCount() - groups; + + return statusCountMessageString(keys, groups); +} + +QString +KGpgItemModel::statusCountMessageString(const unsigned int keys, const unsigned int groups) +{ + // Most people will not have groups. Handle this case + // special so the string isn't displayed in this case at all + if (groups == 0) { + return i18np("1 Key", "%1 Keys", keys); + } + + const QString keyString = i18np("1 Key", "%1 Keys", keys); + const QString groupString = i18np("1 Group", "%1 Groups", groups); + + return i18nc("%1 = something like 7 keys, %2 = something like 2 groups", "%1, %2", keyString, groupString); + +} + +KGpgGroupNode * +KGpgItemModel::addGroup(const QString &name, const KGpgKeyNode::List &keys) +{ + KGpgGroupNode *nd; + const int cIndex = m_root->getChildCount(); // row of the new node + + beginInsertRows(QModelIndex(), cIndex, cIndex); + nd = new KGpgGroupNode(m_root, name, keys); + endInsertRows(); + + nd->saveMembers(); + + Q_ASSERT(m_root->getChildIndex(nd) == cIndex); + + return nd; +} + +void +KGpgItemModel::delNode(KGpgNode *node) +{ + beginResetModel(); + delete node; + endResetModel(); +} + +void +KGpgItemModel::changeGroup(KGpgGroupNode *node, const QList &keys) +{ + const QModelIndex gIndex = nodeIndex(node); + for (int i = node->getChildCount() - 1; i >= 0; i--) { + bool found = false; + + foreach (const KGpgNode *nd, keys) { + found = (node->getChild(i)->getId() == nd->getId()); + if (found) + break; + } + if (found) + continue; + + beginRemoveRows(gIndex, i, i); + delete node->getChild(i); + endRemoveRows(); + } + + int cnt = node->getChildCount(); + + for (int i = 0; i < keys.count(); i++) { + bool found = false; + + foreach (const KGpgNode *nd, node->getChildren()) { + found = (nd->getId() == keys.at(i)->getId()); + if (found) + break; + } + if (found) + continue; + + beginInsertRows(gIndex, cnt, cnt); + new KGpgGroupMemberNode(node, keys.at(i)->toKeyNode()); + endInsertRows(); + cnt++; + } + + node->saveMembers(); +} + +void +KGpgItemModel::deleteFromGroup(KGpgGroupNode *group, KGpgGroupMemberNode *member) +{ + Q_ASSERT(group == member->getParentKeyNode()); + + const int childRow = group->getChildIndex(member); + const QModelIndex pIndex = nodeIndex(group); + + beginRemoveRows(pIndex, childRow, childRow); + delete member; + endRemoveRows(); + + group->saveMembers(); +} + +QVariant +KGpgItemModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation != Qt::Horizontal) + return QVariant(); + + switch (section) { + case KEYCOLUMN_NAME: return QString(i18n("Name")); + case KEYCOLUMN_EMAIL: return QString(i18nc("@title:column Title of a column of emails", "Email")); + case KEYCOLUMN_TRUST: return QString(i18n("Trust")); + case KEYCOLUMN_SIZE: return QString(i18n("Size")); + case KEYCOLUMN_EXPIR: return QString(i18n("Expiration")); + case KEYCOLUMN_CREAT: return QString(i18n("Creation")); + case KEYCOLUMN_ID: return QString(i18n("ID")); + default: return QVariant(); + } +} + +void +KGpgItemModel::setDefaultKey(KGpgKeyNode *def) +{ + int defrow = m_root->findKeyRow(def); + int odefrow = m_root->findKeyRow(m_default); + if (defrow == odefrow) + return; + + int lastcol = columnCount(QModelIndex()) - 1; + if (odefrow >= 0) { + KGpgNode *nd = m_root->getChild(odefrow); + emit dataChanged(createIndex(odefrow, 0, nd), createIndex(odefrow, lastcol, nd)); + } + + if (def) { + m_default = def->getId(); + emit dataChanged(createIndex(defrow, 0, def), createIndex(defrow, lastcol, def)); + } else { + m_default.clear(); + } +} + +QModelIndex +KGpgItemModel::nodeIndex(KGpgNode *node, const int column) +{ + KGpgNode *p = node->getParentKeyNode(); + + for (int i = 0; i < p->getChildCount(); i++) + if (p->getChild(i) == node) + return createIndex(i, column, node); + + Q_ASSERT(0); + return QModelIndex(); +} + +void +KGpgItemModel::refreshKey(KGpgKeyNode *nd) +{ + KGpgKeyNode::List nodes; + + nodes.append(nd); + + refreshKeyIds(nodes); +} + +void +KGpgItemModel::refreshKey(const QString &id) +{ + refreshKeyIds(QStringList(id)); +} + +void +KGpgItemModel::refreshKeys(KGpgKeyNode::List keys) +{ + refreshKeyIds(keys); +} + +void +KGpgItemModel::refreshKeys(const QStringList &ids) +{ + refreshKeyIds(ids); +} + +void +KGpgItemModel::refreshKeyIds(const QStringList &ids) +{ + beginResetModel(); + if (ids.isEmpty()) { + for (int i = m_root->getChildCount() - 1; i >= 0; i--) { + KGpgNode *nd = m_root->getChild(i); + if (nd->getType() == ITYPE_GROUP) + continue; + delete nd; + } + m_root->addKeys(); + } else { + QStringList::ConstIterator it = ids.constBegin(); + const QStringList::ConstIterator itEnd = ids.constEnd(); + + KGpgKeyNode::List refreshNodes; + QStringList addIds; + + for (; it != itEnd; ++it) { + KGpgKeyNode *nd = m_root->findKey(*it); + if (nd) + refreshNodes << nd; + else + addIds << *it; + } + + if (!refreshNodes.isEmpty()) + m_root->refreshKeys(refreshNodes); + if (!addIds.isEmpty()) + m_root->addKeys(addIds); + } + + endResetModel(); +} + +void +KGpgItemModel::refreshKeyIds(KGpgKeyNode::List &nodes) +{ + beginResetModel(); + m_root->refreshKeys(nodes); + endResetModel(); +} + +void +KGpgItemModel::refreshGroups() +{ + for (int i = m_root->getChildCount() - 1; i >= 0; i--) { + KGpgNode *nd = m_root->getChild(i); + if (nd->getType() != ITYPE_GROUP) + continue; + + beginRemoveRows(QModelIndex(), i, i); + delete nd; + endRemoveRows(); + } + + const QStringList groups = KGpgGroupNode::readGroups(); + + if (groups.isEmpty()) + return; + + const int oldCount = m_root->getChildCount(); + beginInsertRows(QModelIndex(), oldCount, oldCount + groups.count()); + m_root->addGroups(groups); + endInsertRows(); +} + +bool +KGpgItemModel::isDefaultKey(const KGpgNode *node) const +{ + return !m_default.isEmpty() && (m_default == node->getId().right(m_default.length())); +} + +void +KGpgItemModel::invalidateIndexes(KGpgNode *nd) +{ + foreach (const QModelIndex &idx, persistentIndexList()) { + KGpgNode *n = nodeForIndex(idx); + + if (n != nd) + continue; + + changePersistentIndex(idx, QModelIndex()); + } +} + +void +KGpgItemModel::refreshTrust(const KgpgCore::KgpgKeyTrust trust, const QColor& color) +{ + updateNodeTrustColor(m_root, trust, color); +} + +void +KGpgItemModel::updateNodeTrustColor(KGpgExpandableNode *node, const KgpgCore::KgpgKeyTrust trust, const QColor &color) +{ + for (int i = 0; i < node->getChildCount(); i++) { + KGpgNode *child = node->getChild(i); + + if (child->getTrust() == trust) + emit dataChanged(createIndex(i, KEYCOLUMN_TRUST, child), createIndex(i, KEYCOLUMN_TRUST, child)); + + if (!child->hasChildren()) + continue; + + KGpgExpandableNode *echild = child->toExpandableNode(); + if (echild->wasExpanded()) + updateNodeTrustColor(echild, trust, color); + } +} + +#include "kgpgitemmodel.moc" diff --git a/kgpg/model/kgpgitemmodel.h b/kgpg/model/kgpgitemmodel.h new file mode 100644 index 00000000..f53dfebf --- /dev/null +++ b/kgpg/model/kgpgitemmodel.h @@ -0,0 +1,99 @@ +/* Copyright 2008,2009,2010,2011,2012 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGITEMMODEL_H +#define KGPGITEMMODEL_H + +#include "core/kgpgkey.h" +#include "core/KGpgKeyNode.h" +#include "core/KGpgNode.h" + +#include +#include +#include + +#define KEYCOLUMN_NAME 0 +#define KEYCOLUMN_EMAIL 1 +#define KEYCOLUMN_TRUST 2 +#define KEYCOLUMN_EXPIR 3 +#define KEYCOLUMN_SIZE 4 +#define KEYCOLUMN_CREAT 5 +#define KEYCOLUMN_ID 6 + +class KGpgExpandableNode; +class KGpgGroupNode; +class KGpgGroupMemberNode; +class KGpgRootNode; +class QColor; + +class KGpgItemModel : public QAbstractItemModel +{ + Q_OBJECT + +private: + KGpgRootNode *m_root; + QString m_default; + +public: + + explicit KGpgItemModel(QObject *parent = 0); + virtual ~KGpgItemModel(); + + virtual QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex &child) const; + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex & /*parent = QModelIndex()*/ ) const + { return 7; } + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual bool hasChildren(const QModelIndex &parent) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + KGpgNode *nodeForIndex(const QModelIndex &index) const; + KGpgKeyNode *findKeyNode(const QString &id) const; + + KGpgRootNode *getRootNode() const; + QString statusCountMessage() const; + static QString statusCountMessageString(const unsigned int keys, const unsigned int groups); + bool isDefaultKey(const KGpgNode *node) const; + +public Q_SLOTS: + KGpgGroupNode *addGroup(const QString &name, const KGpgKeyNode::List &keys); + void delNode(KGpgNode *node); + void changeGroup(KGpgGroupNode *node, const KGpgNode::List &keys); + void deleteFromGroup(KGpgGroupNode *group, KGpgGroupMemberNode *member); + void setDefaultKey(KGpgKeyNode *def); + QModelIndex nodeIndex(KGpgNode *node, const int column = 0); + void refreshKey(const QString &id); + void refreshKey(KGpgKeyNode *nd); + void refreshKeys(const QStringList &ids = QStringList()); + void refreshKeys(KGpgKeyNode::List keys); + void refreshGroups(); + void invalidateIndexes(KGpgNode *nd); + void refreshTrust(const KgpgCore::KgpgKeyTrust trust, const QColor &color); + +protected: + int rowForNode(KGpgNode *node) const; + void refreshKeyIds(const QStringList &id); + void refreshKeyIds(KGpgKeyNode::List &nodes); + void updateNodeTrustColor(KGpgExpandableNode *node, const KgpgCore::KgpgKeyTrust trust, const QColor &color); +}; + +#endif diff --git a/kgpg/model/kgpgitemnode.h b/kgpg/model/kgpgitemnode.h new file mode 100644 index 00000000..2ef54dff --- /dev/null +++ b/kgpg/model/kgpgitemnode.h @@ -0,0 +1,35 @@ +/* Copyright 2008,2009 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGITEMNODE_H +#define KGPGITEMNODE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif /* KGPGITEMNODE_H */ diff --git a/kgpg/model/kgpgsearchresultmodel.cpp b/kgpg/model/kgpgsearchresultmodel.cpp new file mode 100644 index 00000000..c2b9e7d7 --- /dev/null +++ b/kgpg/model/kgpgsearchresultmodel.cpp @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2009,2010,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgsearchresultmodel.h" + +#include +#include +#include +#include +#include +#include + +#include "core/convert.h" +#include "core/kgpgkey.h" + +class SearchResult { +private: + QStringList m_emails; + QStringList m_names; + +public: + SearchResult(const QString &line); + + bool m_validPub; // true when the "pub" line passed to constructor was valid + + void addUid(const QString &id); + const QString &getName(const int index) const; + const QString &getEmail(const int index) const; + int getUidCount() const; + + QString m_fingerprint; + unsigned int m_uatCount; + + QVariant summary() const; +private: + KDateTime m_expiry; + KDateTime m_creation; + bool m_revoked; + unsigned int m_bits; + KgpgCore::KgpgKeyAlgo m_algo; +}; + +class KGpgSearchResultModelPrivate { +public: + explicit KGpgSearchResultModelPrivate(); + ~KGpgSearchResultModelPrivate(); + + QList m_items; + + QString urlDecode(const QString &line); +}; + +SearchResult::SearchResult(const QString &line) + : m_validPub(false), + m_uatCount(0), + m_revoked(false), + m_bits(0) +{ + const QStringList parts(line.split(QLatin1Char( ':' ))); + + if (parts.count() < 6) + return; + + if (parts.at(1).isEmpty()) + return; + + m_fingerprint = parts.at(1); + m_algo = KgpgCore::Convert::toAlgo(parts.at(2)); + m_bits = parts.at(3).toUInt(); + m_creation.setTime_t(parts.at(4).toULongLong()); + m_revoked = (parts.at(6) == QLatin1String( "r" )); + + m_validPub = true; +} + +void +SearchResult::addUid(const QString &id) +{ + Q_ASSERT(m_emails.count() == m_names.count()); + QRegExp hasmail( QLatin1String( "(.*) <(.*)>" )); + + if (hasmail.exactMatch(id)) { + m_names.append(hasmail.capturedTexts().at(1)); + m_emails.append(hasmail.capturedTexts().at(2)); + } else { + m_names.append(id); + m_emails.append(QString()); + } +} + +const QString & +SearchResult::getName(const int index) const +{ + return m_names.at(index); +} + +const QString & +SearchResult::getEmail(const int index) const +{ + return m_emails.at(index); +} + +int +SearchResult::getUidCount() const +{ + Q_ASSERT(m_emails.count() == m_names.count()); + + return m_emails.count(); +} + +QVariant +SearchResult::summary() const +{ + if (m_revoked) { + return i18nc("example: ID abc123xy, 1024-bit RSA key, created Jan 12 2009, revoked", + "ID %1, %2-bit %3 key, created %4, revoked", m_fingerprint, + m_bits, KgpgCore::Convert::toString(m_algo), + m_creation.toString(KDateTime::LocalDate)); + } else { + return i18nc("example: ID abc123xy, 1024-bit RSA key, created Jan 12 2009", + "ID %1, %2-bit %3 key, created %4", m_fingerprint, + m_bits, KgpgCore::Convert::toString(m_algo), + m_creation.toString(KDateTime::LocalDate)); + } +} + +KGpgSearchResultModelPrivate::KGpgSearchResultModelPrivate() +{ +} + +KGpgSearchResultModelPrivate::~KGpgSearchResultModelPrivate() +{ + foreach (SearchResult *item, m_items) + delete item; +} + +QString +KGpgSearchResultModelPrivate::urlDecode(const QString &line) +{ + if (!line.contains(QLatin1Char( '%' ))) + return line; + + QByteArray tmp(line.toAscii()); + const QRegExp hex( QLatin1String( "[A-F0-9]{2}" )); // URL-encoding uses only uppercase + + int pos = -1; // avoid error if '%' is URL-encoded + while ((pos = tmp.indexOf("%", pos + 1)) >= 0) { + const QByteArray hexnum(tmp.mid(pos + 1, 2)); + + // the input is not properly URL-encoded, so assume it does not need to be decoded at all + if (!hex.exactMatch(QLatin1String( hexnum ))) + return line; + + char n[2]; + // this must work as we checked the regexp before + n[0] = hexnum.toUShort(NULL, 16); + n[1] = '\0'; // to use n as a 0-terminated string + + tmp.replace(pos, 3, n); + } + + return QTextCodec::codecForName("utf8")->toUnicode(tmp); +} + +KGpgSearchResultModel::KGpgSearchResultModel(QObject *parent) + : QAbstractItemModel(parent), d(new KGpgSearchResultModelPrivate()) +{ +} + +KGpgSearchResultModel::~KGpgSearchResultModel() +{ + delete d; +} + +QVariant +KGpgSearchResultModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role != Qt::DisplayRole) + return QVariant(); + + if (index.row() < 0) + return QVariant(); + + SearchResult *tmp = static_cast(index.internalPointer()); + int row; + + if (tmp == NULL) { + // this is a "top" item, show the first uid + if (index.row() >= d->m_items.count()) + return QVariant(); + + tmp = d->m_items.at(index.row()); + row = 0; + } else { + row = index.row() + 1; + int summaryRow = tmp->getUidCount(); + int uatRow; + if (tmp->m_uatCount != 0) { + uatRow = summaryRow; + summaryRow++; + } else { + uatRow = -1; + } + + if (row == uatRow) { + if (index.column() == 0) + return i18np("One Photo ID", "%1 Photo IDs", tmp->m_uatCount); + else + return QVariant(); + } else if (row == summaryRow) { + if (index.column() == 0) + return tmp->summary(); + else + return QVariant(); + } + Q_ASSERT(row < tmp->getUidCount()); + } + + switch (index.column()) { + case 0: + return tmp->getName(row); + case 1: + return tmp->getEmail(row); + default: + return QVariant(); + } +} + +int +KGpgSearchResultModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + if (parent.column() != 0) + return 0; + + SearchResult *tmp = static_cast(parent.internalPointer()); + + if (tmp == NULL) + return 2; + else + return 0; + } else { + return 2; + } +} + +QModelIndex +KGpgSearchResultModel::index(int row, int column, const QModelIndex &parent) const +{ + // there are three hierarchy levels: + // root: this is simply QModelIndex() + // key items: parent is invalid, internalPointer is NULL + // uid entries: parent is key item, internalPointer is set to SearchResult* + + if (parent.isValid()) { + if (parent.internalPointer() != NULL) { + return QModelIndex(); + } else { + if (parent.row() >= d->m_items.count()) + return QModelIndex(); + SearchResult *tmp = d->m_items.at(parent.row()); + int maxRow = tmp->getUidCount(); + if (tmp->m_uatCount != 0) + maxRow++; + if ((row >= maxRow) || (column > 1)) + return QModelIndex(); + return createIndex(row, column, tmp); + } + } else { + if ((row >= d->m_items.count()) || (column > 1) || (row < 0) || (column < 0)) + return QModelIndex(); + return createIndex(row, column); + } +} + +QModelIndex +KGpgSearchResultModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + SearchResult *tmp = static_cast(index.internalPointer()); + + if (tmp == NULL) + return QModelIndex(); + + return createIndex(d->m_items.indexOf(tmp), 0); +} + +int +KGpgSearchResultModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return d->m_items.count(); + } else if (parent.column() == 0) { + if (parent.internalPointer() != NULL) + return 0; + + SearchResult *item = d->m_items.at(parent.row()); + int cnt = item->getUidCount(); + if (item->m_uatCount != 0) + cnt++; + + return cnt; + } else { + return 0; + } +} + +QVariant +KGpgSearchResultModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation != Qt::Horizontal) + return QVariant(); + + switch (section) { + case 0: + return i18n("Name"); + case 1: + return QString(i18nc("@title:column Title of a column of emails", "Email")); + default: + return QVariant(); + } +} + +const QString & +KGpgSearchResultModel::idForIndex(const QModelIndex &index) const +{ + Q_ASSERT(index.isValid()); + + SearchResult *tmp = static_cast(index.internalPointer()); + if (tmp == NULL) + tmp = d->m_items.at(index.row()); + + return tmp->m_fingerprint; +} + +void +KGpgSearchResultModel::slotAddKey(QStringList lines) +{ + Q_ASSERT(!lines.isEmpty()); + Q_ASSERT(lines.first().startsWith(QLatin1String("pub:"))); + + if (lines.count() == 1) + return; + + SearchResult *nkey = new SearchResult(lines.takeFirst()); + if (!nkey->m_validPub) { + delete nkey; + return; + } + + foreach (const QString &line, lines) { + if (line.startsWith(QLatin1String("uid:"))) { + QString kid = d->urlDecode(line.section(QLatin1Char( ':' ), 1, 1)); + + nkey->addUid(kid); + } else if (line.startsWith(QLatin1String("uat:"))) { + nkey->m_uatCount++; + } else { + kDebug(2100) << "ignored search result line" << line; + } + } + + if (nkey->getUidCount() > 0) { + beginInsertRows(QModelIndex(), d->m_items.count(), d->m_items.count()); + d->m_items.append(nkey); + endInsertRows(); + } else { + // key server sent back a crappy key + delete nkey; + } +} + +#include "kgpgsearchresultmodel.moc" diff --git a/kgpg/model/kgpgsearchresultmodel.h b/kgpg/model/kgpgsearchresultmodel.h new file mode 100644 index 00000000..e02a43ec --- /dev/null +++ b/kgpg/model/kgpgsearchresultmodel.h @@ -0,0 +1,63 @@ +/* Copyright 2009,2010 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef KGPGSEARCHRESULTMODEL_H +#define KGPGSEARCHRESULTMODEL_H + +#include + +class QString; +class QStringList; + +class KGpgSearchResultModelPrivate; + +/** + * @brief Model of the results of a keyserver search + * + * This model parses and stores the results of a search on a keyserver. + * + * @author Rolf Eike Beer + */ +class KGpgSearchResultModel : public QAbstractItemModel { + Q_OBJECT +public: + explicit KGpgSearchResultModel(QObject *parent = NULL); + ~KGpgSearchResultModel(); + + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex &index) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + /** + * @brief get the key fingerprint for the given index + * @param index valid index of any item in the model + * @return fingerprint of the corresponding key + */ + const QString &idForIndex(const QModelIndex &index) const; + +public slots: + void slotAddKey(QStringList lines); + +private: + KGpgSearchResultModelPrivate * const d; +}; + +#endif diff --git a/kgpg/model/selectkeyproxymodel.cpp b/kgpg/model/selectkeyproxymodel.cpp new file mode 100644 index 00000000..bce2fc12 --- /dev/null +++ b/kgpg/model/selectkeyproxymodel.cpp @@ -0,0 +1,272 @@ +/* Copyright 2008,2010,2012,2013 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "selectkeyproxymodel.h" +#include "model/kgpgitemnode.h" +#include "kgpgitemmodel.h" +#include "core/kgpgkey.h" + +#include +#include + +using namespace KgpgCore; + +SelectKeyProxyModel::SelectKeyProxyModel(QObject *parent) + : QSortFilterProxyModel(parent), + m_model(NULL), + m_showUntrusted(false) +{ +} + +void +SelectKeyProxyModel::setKeyModel(KGpgItemModel *md) +{ + m_model = md; + setSourceModel(md); +} + +bool +SelectKeyProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + QModelIndex idx = m_model->index(source_row, 0, source_parent); + KGpgNode *l = m_model->nodeForIndex(idx); + + switch (l->getType()) { + case ITYPE_GROUP: + break; + case ITYPE_PAIR: + case ITYPE_PUBLIC: + if (!l->toKeyNode()->canEncrypt()) + return false; + break; + default: + return false; + } + + if (!m_showUntrusted && ((l->getTrust() != TRUST_FULL) && (l->getTrust() != TRUST_ULTIMATE))) + return false; + + // there is probably a better place to do this + QRegExp rx = filterRegExp(); + rx.setCaseSensitivity(Qt::CaseInsensitive); + + if (l->getName().contains(rx)) + return true; + + if (l->getEmail().contains(rx)) + return true; + + if (l->getId().contains(rx)) + return true; + + return false; +} + +KGpgNode * +SelectKeyProxyModel::nodeForIndex(const QModelIndex &index) const +{ + return m_model->nodeForIndex(mapToSource(index)); +} + +int +SelectKeyProxyModel::columnCount(const QModelIndex &) const +{ + return 3; +} + +int +SelectKeyProxyModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + if (parent.isValid()) + return 0; + if (m_model == NULL) + return 0; + return QSortFilterProxyModel::rowCount(parent); +} + +QVariant +SelectKeyProxyModel::data(const QModelIndex &index, int role) const +{ + if (index.column() >= 3) + return QVariant(); + + QModelIndex sidx = mapToSource(index); + KGpgNode *nd = m_model->nodeForIndex(sidx); + + if ((index.column() == 2) && (role == Qt::ToolTipRole)) + return nd->getId(); + + if ((role != Qt::DisplayRole) && (index.column() <= 1)) + return m_model->data(sidx, role); + + if (role != Qt::DisplayRole) + return QVariant(); + + switch (index.column()) { + case 0: return nd->getName(); + case 1: return nd->getEmail(); + case 2: return nd->getId().right(8); + } + + return QVariant(); +} + +bool +SelectKeyProxyModel::hasChildren(const QModelIndex &parent) const +{ + if (m_model == NULL) + return false; + return !parent.isValid(); +} + +QVariant +SelectKeyProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation != Qt::Horizontal) + return QVariant(); + + if (m_model == NULL) + return QVariant(); + + switch (section) { + case 0: + return m_model->headerData(KEYCOLUMN_NAME, orientation, role); + case 1: + return m_model->headerData(KEYCOLUMN_EMAIL, orientation, role); + case 2: + return m_model->headerData(KEYCOLUMN_ID, orientation, role); + default: + return QVariant(); + } +} + +bool +SelectKeyProxyModel::showUntrusted() const +{ + return m_showUntrusted; +} + +void +SelectKeyProxyModel::setShowUntrusted(const bool b) +{ + m_showUntrusted = b; + invalidate(); +} + +SelectSecretKeyProxyModel::SelectSecretKeyProxyModel(QObject *parent) + : SelectKeyProxyModel(parent) +{ +} + +bool +SelectSecretKeyProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + QModelIndex idx = m_model->index(source_row, 0, source_parent); + KGpgNode *l = m_model->nodeForIndex(idx); + + return ((l->getType() == ITYPE_PAIR) && !(l->getTrust() == TRUST_EXPIRED) && !(l->getTrust() == TRUST_DISABLED)); +} + +int +SelectSecretKeyProxyModel::columnCount(const QModelIndex &) const +{ + return 4; +} + +int +SelectSecretKeyProxyModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + if (parent.isValid()) + return 0; + if (m_model == NULL) + return 0; + return QSortFilterProxyModel::rowCount(parent); +} + +QVariant +SelectSecretKeyProxyModel::data(const QModelIndex &index, int role) const +{ + if (index.column() >= 4) + return QVariant(); + + QModelIndex sidx = mapToSource(index); + KGpgNode *nd = m_model->nodeForIndex(sidx); + + if ((index.column() == 3) && (role == Qt::ToolTipRole)) + return nd->getId(); + + if ((role != Qt::DisplayRole) && (index.column() <= 1)) + return m_model->data(sidx, role); + + if (role != Qt::DisplayRole) + return QVariant(); + + switch (index.column()) { + case 0: + return nd->getName(); + case 1: + return nd->getEmail(); + case 2: + return KGlobal::locale()->formatDate(nd->getExpiration().date(), KLocale::ShortDate); + case 3: + return nd->getId().right(8); + } + + return QVariant(); +} + +bool +SelectSecretKeyProxyModel::hasChildren(const QModelIndex &parent) const +{ + if (m_model == NULL) + return false; + return !parent.isValid(); +} + +QVariant +SelectSecretKeyProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation != Qt::Horizontal) + return QVariant(); + + if (m_model == NULL) + return QVariant(); + + switch (section) { + case 0: + return m_model->headerData(KEYCOLUMN_NAME, orientation, role); + case 1: + return m_model->headerData(KEYCOLUMN_EMAIL, orientation, role); + case 2: + return m_model->headerData(KEYCOLUMN_EXPIR, orientation, role); + case 3: + return m_model->headerData(KEYCOLUMN_ID, orientation, role); + default: + return QVariant(); + } +} diff --git a/kgpg/model/selectkeyproxymodel.h b/kgpg/model/selectkeyproxymodel.h new file mode 100644 index 00000000..c0244f27 --- /dev/null +++ b/kgpg/model/selectkeyproxymodel.h @@ -0,0 +1,76 @@ +/* Copyright 2008 Rolf Eike Beer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef SELECTKEYPROXYMODEL_H +#define SELECTKEYPROXYMODEL_H + +#include + +class KGpgNode; +class KGpgItemModel; + +/** + * @brief filter model to select a public key for encryption + */ +class SelectKeyProxyModel: public QSortFilterProxyModel +{ + Q_PROPERTY(bool showUntrusted read showUntrusted write setShowUntrusted) + +public: + explicit SelectKeyProxyModel(QObject * parent); + + void setKeyModel(KGpgItemModel *); + + KGpgNode *nodeForIndex(const QModelIndex &index) const; + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual bool hasChildren(const QModelIndex &parent) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + bool showUntrusted() const; + void setShowUntrusted(const bool b); + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + virtual int columnCount(const QModelIndex &) const; + + KGpgItemModel *m_model; + +private: + bool m_showUntrusted; +}; + +class SelectSecretKeyProxyModel: public SelectKeyProxyModel +{ +public: + explicit SelectSecretKeyProxyModel(QObject *parent); + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual bool hasChildren(const QModelIndex &parent) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + virtual int columnCount(const QModelIndex &) const; +}; + +#endif diff --git a/kgpg/newkey.cpp b/kgpg/newkey.cpp new file mode 100644 index 00000000..e7ff401a --- /dev/null +++ b/kgpg/newkey.cpp @@ -0,0 +1,32 @@ +/*************************************************************************** + newkey.cpp - description + ------------------- + begin : Thu Jul 4 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "newkey.h" + +newKey::newKey(QWidget* parent) + : QWidget(parent), Ui_newKey() +{ + setupUi(this); + connect(CBsave, SIGNAL(toggled(bool)), this, SLOT(CBsave_toggled(bool))); +} + +void newKey::CBsave_toggled(bool isOn) +{ + kURLRequester1->setEnabled(isOn); +} + +#include "newkey.moc" diff --git a/kgpg/newkey.h b/kgpg/newkey.h new file mode 100644 index 00000000..088f6aab --- /dev/null +++ b/kgpg/newkey.h @@ -0,0 +1,36 @@ +/*************************************************************************** + newkey.h - description + ------------------- + begin : Thu Jul 4 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef NEWKEY_H +#define NEWKEY_H + +#include "ui_newkey.h" + + +class newKey : public QWidget, public Ui_newKey +{ + Q_OBJECT + +public: + explicit newKey(QWidget* parent = 0); + + +public slots: + void CBsave_toggled(bool); +}; + +#endif diff --git a/kgpg/newkey.ui b/kgpg/newkey.ui new file mode 100644 index 00000000..7a712bcf --- /dev/null +++ b/kgpg/newkey.ui @@ -0,0 +1,221 @@ + + newKey + + + + 0 + 0 + 616 + 489 + + + + New Key Pair Created + + + + + + New Key Created + + + + + + You have successfully created the following key: + + + false + + + + + + + + + Name: + + + false + + + + + + + + 0 + 0 + + + + textLabel7 + + + false + + + + + + + + + + + Email: + + + false + + + + + + + textLabel8 + + + false + + + + + + + + + + + Key ID: + + + false + + + + + + + textLabel10 + + + false + + + + + + + + + + + Fingerprint: + + + false + + + + + + + true + + + true + + + + + + + <qt><b>Set as your default key:</b><br /> +<p>Checking this option sets the newly created key pair as the default key pair.</p></qt> + + + Set as your default key + + + + + + + + + + + + Revocation Certificate + + + + + + It is recommended to save or print a revocation certificate in case your key is compromised. + + + false + + + + + + + + + Save as: + + + + + + + false + + + + + + + + + Print + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+ + KUrlRequester + QFrame +
kurlrequester.h
+
+
+ + +
diff --git a/kgpg/org.kde.kgpg.Key.xml b/kgpg/org.kde.kgpg.Key.xml new file mode 100644 index 00000000..557e03b7 --- /dev/null +++ b/kgpg/org.kde.kgpg.Key.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/kgpg/searchres.ui b/kgpg/searchres.ui new file mode 100644 index 00000000..28fa637b --- /dev/null +++ b/kgpg/searchres.ui @@ -0,0 +1,98 @@ + + searchRes + + + + 0 + 0 + 446 + 262 + + + + + + + + + + false + + + + + + + + + Search: + + + true + + + + + + + true + + + + + + + + + true + + + true + + + true + + + + Keys + + + + + + + + + + Key to import: + + + false + + + + + + + true + + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + kLVsearch + kLEID + + + +
diff --git a/kgpg/selectexpirydate.cpp b/kgpg/selectexpirydate.cpp new file mode 100644 index 00000000..83e71320 --- /dev/null +++ b/kgpg/selectexpirydate.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007 Jimmy Gilles + * Copyright (C) 2008 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "selectexpirydate.h" + +#include +#include + +#include +#include + +SelectExpiryDate::SelectExpiryDate(QWidget* parent, QDateTime date) + : KDialog(parent) +{ + setCaption(i18n("Choose New Expiration")); + setButtons(Ok | Cancel); + setDefaultButton(Ok); + setModal(true); + + QWidget *page = new QWidget(this); + m_unlimited = new QCheckBox(i18nc("Key has unlimited lifetime", "Unlimited"), page); + m_unlimited->setChecked(date.isNull()); + + if (date.isNull()) + date = QDateTime::currentDateTime(); + + m_datepicker = new KDatePicker(date.date(), page); + if (date.isNull()) { + m_datepicker->setEnabled(false); + m_unlimited->setChecked(true); + } + + QVBoxLayout *layout = new QVBoxLayout(page); + layout->setSpacing(3); + layout->addWidget(m_datepicker); + layout->addWidget(m_unlimited); + + connect(m_unlimited, SIGNAL(toggled(bool)), this, SLOT(slotEnableDate(bool))); + connect(m_datepicker, SIGNAL(dateChanged(QDate)), this, SLOT(slotCheckDate(QDate))); + connect(m_datepicker, SIGNAL(dateEntered(QDate)), this, SLOT(slotCheckDate(QDate))); + + setMainWidget(page); + show(); + + slotEnableDate(m_unlimited->isChecked()); +} + +QDateTime SelectExpiryDate::date() const +{ + if (m_unlimited->isChecked()) + return QDateTime(); + else + return QDateTime(m_datepicker->date()); +} + +void SelectExpiryDate::slotCheckDate(const QDate& date) +{ + enableButtonOk(QDateTime(date) >= QDateTime::currentDateTime()); +} + +void SelectExpiryDate::slotEnableDate(const bool ison) +{ + m_datepicker->setEnabled(!ison); + if (ison) + enableButtonOk(true); + else + slotCheckDate(m_datepicker->date()); +} + +#include "selectexpirydate.moc" diff --git a/kgpg/selectexpirydate.h b/kgpg/selectexpirydate.h new file mode 100644 index 00000000..97a6c02b --- /dev/null +++ b/kgpg/selectexpirydate.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007 Jimmy Gilles + * Copyright (C) 2008 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef SELECTEXPIRYDATE_H +#define SELECTEXPIRYDATE_H + +#include + +#include + +class KDatePicker; +class QCheckBox; + +/** + * @brief shows a dialog to choose expiry date + * + * This dialog allows the user to choose a expiry date or set a + * checkbox to select that the given item will never expire. + */ +class SelectExpiryDate : public KDialog +{ + Q_OBJECT + +public: + explicit SelectExpiryDate(QWidget *parent = 0, QDateTime date = QDateTime()); + + QDateTime date() const; + +private slots: + void slotCheckDate(const QDate &date); + void slotEnableDate(const bool ison); + +private: + QCheckBox *m_unlimited; + KDatePicker *m_datepicker; +}; + +#endif /* SELECTEXPIRYDATE_H */ diff --git a/kgpg/selectpublickeydialog.cpp b/kgpg/selectpublickeydialog.cpp new file mode 100644 index 00000000..df9639b4 --- /dev/null +++ b/kgpg/selectpublickeydialog.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2009,2010,2011,2012 + * Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "selectpublickeydialog.h" + +#include "kgpgsettings.h" +#include "core/images.h" +#include "core/KGpgRootNode.h" +#include "model/kgpgitemmodel.h" +#include "model/selectkeyproxymodel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KgpgCore; + +KgpgSelectPublicKeyDlg::KgpgSelectPublicKeyDlg(QWidget *parent, KGpgItemModel *model, const KShortcut &goDefaultKey, const bool hideasciioption, const KUrl::List &files) + : KDialog(parent), + m_customoptions(NULL), + imodel(model), + m_files(files), + m_hideasciioption(hideasciioption) +{ + setButtons(Details | Ok | Cancel); + setDefaultButton(Ok); + setButtonText(Details, i18n("O&ptions")); + + int fcount = files.count(); + bool fmode = (fcount > 0); + + switch (fcount) { + case 0: + setCaption(i18n("Select Public Key")); + break; + case 1: + setCaption(i18n("Select Public Key for %1", files.first().fileName())); + break; + default: + setCaption(i18np("Select Public Key for %2 and one more file", "Select Public Key for %2 and %1 more files", files.count() - 1, files.first().fileName())); + } + + QWidget *page = new QWidget(this); + + m_searchbar = new KHBox(page); + m_searchbar->setSpacing(spacingHint()); + m_searchbar->setFrameShape(QFrame::StyledPanel); + + QLabel *searchlabel = new QLabel(i18n("&Search: "), m_searchbar); + + m_searchlineedit = new KLineEdit(m_searchbar); + m_searchlineedit->setClearButtonShown(true); + searchlabel->setBuddy(m_searchlineedit); + + iproxy = new SelectKeyProxyModel(this); + iproxy->setKeyModel(imodel); + + m_keyslist = new QTableView(page); + m_keyslist->setSortingEnabled(true); + m_keyslist->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_keyslist->setSelectionBehavior(QAbstractItemView::SelectRows); + m_keyslist->setModel(iproxy); + m_keyslist->resizeColumnsToContents(); + m_keyslist->setWhatsThis(i18n("Public keys list: select the key that will be used for encryption.")); + connect(m_searchlineedit, SIGNAL(textChanged(QString)), iproxy, SLOT(setFilterFixedString(QString))); + + optionsbox = new KVBox(); + optionsbox->setFrameShape(QFrame::StyledPanel); + setDetailsWidget(optionsbox); + + if (m_hideasciioption) + m_cbarmor = 0; + else + { + m_cbarmor = new QCheckBox(i18n("ASCII armored encryption"), optionsbox); + m_cbarmor->setChecked(KGpgSettings::asciiArmor()); + m_cbarmor->setWhatsThis(i18n("ASCII encryption: makes it possible to open the encrypted file/message in a text editor")); + } + + m_cbuntrusted = new QCheckBox(i18n("Allow encryption with untrusted keys"), optionsbox); + m_cbuntrusted->setChecked(KGpgSettings::allowUntrustedKeys()); + m_cbuntrusted->setWhatsThis(i18n("Allow encryption with untrusted keys: when you import a public key, it is usually " + "marked as untrusted and you cannot use it unless you sign it in order to make it 'trusted'. Checking this " + "box enables you to use any key, even if it has not be signed.")); + + m_cbhideid = new QCheckBox(i18n("Hide user id"), optionsbox); + connect(m_cbuntrusted, SIGNAL(toggled(bool)), this, SLOT(slotUntrusted(bool))); + m_cbhideid->setChecked(KGpgSettings::hideUserID()); + m_cbhideid->setWhatsThis(i18n("Hide user ID: Do not put the keyid into encrypted packets. This option hides the receiver " + "of the message and is a countermeasure against traffic analysis. It may slow down the decryption process because " + "all available secret keys are tried.")); + + m_cbsymmetric = new QCheckBox(i18n("Symmetrical encryption"), optionsbox); + m_cbsymmetric->setWhatsThis(i18n("Symmetrical encryption: encryption does not use keys. You just need to give a password " + "to encrypt/decrypt the file")); + + QVBoxLayout *dialoglayout = new QVBoxLayout(page); + dialoglayout->setSpacing(spacingHint()); + dialoglayout->setMargin(0); + dialoglayout->addWidget(m_searchbar); + dialoglayout->addWidget(m_keyslist); + page->setLayout(dialoglayout); + + if (KGpgSettings::allowCustomEncryptionOptions()) + { + KHBox *expertbox = new KHBox(page); + (void) new QLabel(i18n("Custom option:"), expertbox); + + m_customoptions = new KLineEdit(expertbox); + m_customoptions->setText(KGpgSettings::customEncryptionOptions()); + m_customoptions->setWhatsThis(i18n("Custom option: for experienced users only, allows you to enter a gpg command line option, like: '--armor'")); + + dialoglayout->addWidget(expertbox); + } + + KActionCollection *actcol = new KActionCollection(this); + KAction *action = actcol->addAction(QLatin1String( "go_default_key" )); + action->setText(i18n("&Go to Default Key")); + action->setShortcut(goDefaultKey); + + connect(action, SIGNAL(triggered(bool)), SLOT(slotGotoDefaultKey())); + connect(m_cbsymmetric, SIGNAL(toggled(bool)), this, SLOT(slotSymmetric(bool))); + connect(m_keyslist->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectionChanged())); + connect(m_keyslist, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotOk())); + + setMinimumSize(550, 200); + updateGeometry(); + + setMainWidget(page); + slotSelectionChanged(); + + if (fmode) + slotGotoDefaultKey(); +} + +QStringList KgpgSelectPublicKeyDlg::selectedKeys() const +{ + if (getSymmetric()) + return QStringList(); + + QStringList selectedKeys; + + foreach (const QModelIndex &idx, m_keyslist->selectionModel()->selectedIndexes()) { + if (idx.column() != 0) + continue; + KGpgNode *nd = iproxy->nodeForIndex(idx); + if (nd->getType() == ITYPE_GROUP) + selectedKeys << nd->getName(); + else + selectedKeys << nd->getId(); + } + + return selectedKeys; +} + +bool KgpgSelectPublicKeyDlg::getSymmetric() const +{ + return m_cbsymmetric->isChecked(); +} + +QString KgpgSelectPublicKeyDlg::getCustomOptions() const +{ + if (m_customoptions == 0) + return QString(); + return m_customoptions->text().simplified(); +} + +bool KgpgSelectPublicKeyDlg::getUntrusted() const +{ + return m_cbuntrusted->isChecked(); +} + +bool KgpgSelectPublicKeyDlg::getArmor() const +{ + return m_hideasciioption || m_cbarmor->isChecked(); +} + +const KUrl::List &KgpgSelectPublicKeyDlg::getFiles() const +{ + return m_files; +} + +bool KgpgSelectPublicKeyDlg::getHideId() const +{ + return m_cbhideid->isChecked(); +} + +void KgpgSelectPublicKeyDlg::slotOk() +{ + if (getSymmetric() || m_keyslist->selectionModel()->hasSelection()) + slotButtonClicked(Ok); +} + +void KgpgSelectPublicKeyDlg::slotSelectionChanged() +{ + enableButtonOk(getSymmetric() || m_keyslist->selectionModel()->hasSelection()); +} + +void KgpgSelectPublicKeyDlg::slotSymmetric(const bool state) +{ + m_keyslist->setDisabled(state); + m_cbuntrusted->setDisabled(state); + m_cbhideid->setDisabled(state); + m_searchbar->setDisabled(state); + slotSelectionChanged(); +} + +void KgpgSelectPublicKeyDlg::slotUntrusted(const bool state) +{ + iproxy->setShowUntrusted(state); +} + +void KgpgSelectPublicKeyDlg::slotGotoDefaultKey() +{ + KGpgNode *nd = imodel->getRootNode()->findKey(KGpgSettings::defaultKey()); + if (nd == NULL) + return; + QModelIndex sidx = imodel->nodeIndex(nd); + QModelIndex pidx = iproxy->mapFromSource(sidx); + m_keyslist->selectionModel()->setCurrentIndex(pidx, QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); +} + +#include "selectpublickeydialog.moc" diff --git a/kgpg/selectpublickeydialog.h b/kgpg/selectpublickeydialog.h new file mode 100644 index 00000000..66080115 --- /dev/null +++ b/kgpg/selectpublickeydialog.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2009,2010,2011,2012,2013 + * Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef SELECTPUBLICKEYDIALOG_H +#define SELECTPUBLICKEYDIALOG_H + +#include "core/kgpgkey.h" + +#include +#include +#include +#include +#include + +class QCheckBox; +class QTableView; + +class KLineEdit; +class KHBox; + +class SelectKeyProxyModel; +class KGpgItemModel; + +/** + * @brief shows a dialog to select a public key for encryption + */ +class KgpgSelectPublicKeyDlg : public KDialog +{ + Q_OBJECT + +public: + /* TODO: the goDefaultKey shortcut should be stored in a way it can be accessed from everywhere. So we don't have to pass it as an argument. + */ + + KgpgSelectPublicKeyDlg(QWidget *parent, KGpgItemModel *model, const KShortcut &goDefaultKey = KShortcut(QKeySequence(Qt::CTRL + Qt::Key_Home)), const bool hideasciioption = false, const KUrl::List &files = KUrl::List()); + + QStringList selectedKeys() const; + QString getCustomOptions() const; + bool getSymmetric() const; + bool getUntrusted() const; + bool getHideId() const; + bool getArmor() const; + /** + * @brief return the files passed in the constructor + */ + const KUrl::List &getFiles() const; + + KVBox *optionsbox; + +private slots: + void slotOk(); + void slotSelectionChanged(); + void slotSymmetric(const bool state); + void slotUntrusted(const bool state); + void slotGotoDefaultKey(); + +private: + QCheckBox *m_cbarmor; + QCheckBox *m_cbuntrusted; + QCheckBox *m_cbhideid; + QCheckBox *m_cbsymmetric; + + KHBox *m_searchbar; + KLineEdit *m_customoptions; + QTableView *m_keyslist; + KLineEdit *m_searchlineedit; + SelectKeyProxyModel *iproxy; + KGpgItemModel *imodel; + const KUrl::List m_files; + + bool m_hideasciioption; +}; + +#endif // SELECTPUBLICKEYDIALOG_H diff --git a/kgpg/selectsecretkey.cpp b/kgpg/selectsecretkey.cpp new file mode 100644 index 00000000..174f5495 --- /dev/null +++ b/kgpg/selectsecretkey.cpp @@ -0,0 +1,139 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "selectsecretkey.h" + +#include "kgpgsettings.h" +#include "core/images.h" +#include "core/KGpgRootNode.h" +#include "model/kgpgitemmodel.h" +#include "model/selectkeyproxymodel.h" + +#include +#include +#include +#include +#include +#include + +using namespace KgpgCore; + +KgpgSelectSecretKey::KgpgSelectSecretKey(QWidget *parent, KGpgItemModel *model, const int countkey, const bool allowLocal, const bool allowTerminal) + : KDialog(parent), + m_localsign(NULL), + m_terminalsign(NULL), + m_signtrust(NULL), + m_proxy(new SelectSecretKeyProxyModel(this)) +{ + setCaption(i18n("Private Key List")); + setButtons(Ok | Cancel); + setDefaultButton(Ok); + QWidget *page = new QWidget(this); + + QLabel *label = new QLabel(i18n("Choose secret key for signing:"), page); + + m_proxy->setKeyModel(model); + + m_keyslist = new QTableView(page); + m_keyslist->setModel(m_proxy); + m_keyslist->setSortingEnabled(true); + m_keyslist->setSelectionBehavior(QAbstractItemView::SelectRows); + m_keyslist->resizeColumnsToContents(); + + QVBoxLayout *vbox = new QVBoxLayout(page); + vbox->addWidget(label); + vbox->addWidget(m_keyslist); + + if (countkey > 0) { + QLabel *signchecklabel = new QLabel(i18np("How carefully have you checked that the key really " + "belongs to the person with whom you wish to communicate:", + "How carefully have you checked that the %1 keys really " + "belong to the people with whom you wish to communicate:", countkey), page); + signchecklabel->setWordWrap(true); + + m_signtrust = new KComboBox(page); + m_signtrust->addItem(i18n("I Will Not Answer")); + m_signtrust->addItem(i18n("I Have Not Checked at All")); + m_signtrust->addItem(i18n("I Have Done Casual Checking")); + m_signtrust->addItem(i18n("I Have Done Very Careful Checking")); + + vbox->addWidget(signchecklabel); + vbox->addWidget(m_signtrust); + if (allowLocal){ + m_localsign = new QCheckBox(i18n("Local signature (cannot be exported)"), page); + vbox->addWidget(m_localsign); + } + if (allowTerminal && (countkey == 1)) { + m_terminalsign = new QCheckBox(i18n("Do not sign all user id's (open terminal)"), page); + vbox->addWidget(m_terminalsign); + } + } + + KGpgNode *nd = model->getRootNode()->findKey(KGpgSettings::defaultKey()); + if (nd != NULL) { + QModelIndex sidx = model->nodeIndex(nd); + QModelIndex pidx = m_proxy->mapFromSource(sidx); + m_keyslist->selectionModel()->setCurrentIndex(pidx, QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + } + + setMinimumSize(550, 200); + slotSelectionChanged(); + setMainWidget(page); + + connect(m_keyslist->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectionChanged())); + connect(m_keyslist, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotOk())); +} + +KgpgSelectSecretKey::~KgpgSelectSecretKey() +{ +} + +QString KgpgSelectSecretKey::getKeyID() const +{ + if (!m_keyslist->selectionModel()->hasSelection()) + return QString(); + return m_proxy->nodeForIndex(m_keyslist->selectionModel()->selectedIndexes().at(0))->getId(); +} + +QString KgpgSelectSecretKey::getKeyMail() const +{ + if (!m_keyslist->selectionModel()->hasSelection()) + return QString(); + return m_proxy->nodeForIndex(m_keyslist->selectionModel()->selectedIndexes().at(0))->getEmail(); +} + +int KgpgSelectSecretKey::getSignTrust() const +{ + if (m_signtrust) + return m_signtrust->currentIndex(); + return -1; +} + +bool KgpgSelectSecretKey::isLocalSign() const +{ + return m_localsign && m_localsign->isChecked(); +} + +bool KgpgSelectSecretKey::isTerminalSign() const +{ + return m_terminalsign && m_terminalsign->isChecked(); +} + +void KgpgSelectSecretKey::slotSelectionChanged() +{ + enableButtonOk(m_keyslist->selectionModel()->hasSelection()); +} + +void KgpgSelectSecretKey::slotOk() +{ + if (m_keyslist->selectionModel()->hasSelection()) + slotButtonClicked(Ok); +} + +#include "selectsecretkey.moc" diff --git a/kgpg/selectsecretkey.h b/kgpg/selectsecretkey.h new file mode 100644 index 00000000..9c4b6108 --- /dev/null +++ b/kgpg/selectsecretkey.h @@ -0,0 +1,71 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGSELECTSECRETKEY_H +#define KGPGSELECTSECRETKEY_H + +#include + +class QCheckBox; +class QTableView; + +class KComboBox; + +class KGpgItemModel; +class SelectSecretKeyProxyModel; + +class KgpgSelectSecretKey : public KDialog +{ + Q_OBJECT + +public: + /** + * Constructor. + * @param parent is the parent object + * @param model pass a pointer to a KGpgItemModel that stores the keys + * to select from + * @param countkey set to the number of keys that you are going to sign, 0 if you are going to sign a file + * @param allowLocal if option to sign keys locally should be offered (ignored if countkey == 0) + * @param allowTerminal if option to sign keys in terminal should be offered (ignored if countkey > 1) + */ + KgpgSelectSecretKey(QWidget *parent, KGpgItemModel *model, const int countkey = 0, const bool allowLocal = true, const bool allowTerminal = true); + ~KgpgSelectSecretKey(); + + QString getKeyID() const; + QString getKeyMail() const; + int getSignTrust() const; + + /** + * @return true if the constructor is called with \em signkey to true + * and if the user checked \em localsign + * @return false otherwise + */ + bool isLocalSign() const; + + /** + * @return true if the constructor is called with \em signkey to true + * and if the user checked \em terminalsign + * @return false otherwise + */ + bool isTerminalSign() const; + +private slots: + void slotOk(); + void slotSelectionChanged(); + +private: + QCheckBox *m_localsign; + QCheckBox *m_terminalsign; + + KComboBox *m_signtrust; + QTableView *m_keyslist; + SelectSecretKeyProxyModel *m_proxy; +}; + +#endif // KGPGSELECTSECRETKEY_H diff --git a/kgpg/sourceselect.cpp b/kgpg/sourceselect.cpp new file mode 100644 index 00000000..276b43a2 --- /dev/null +++ b/kgpg/sourceselect.cpp @@ -0,0 +1,37 @@ +/*************************************************************************** + sourceselect.h - description + ------------------- + begin : Mo April 30 2007 + copyright : (C) 2007 by Lukas Kropatschek + email : lukas.krop@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "sourceselect.h" +#include "sourceselect.moc" + +SrcSelect::SrcSelect(QWidget *parent) + : QWidget(parent) +{ + setupUi(this); + connect(checkFile, SIGNAL(toggled(bool)), this, SLOT(checkFile_toggled(bool))); + connect(checkServer, SIGNAL(toggled(bool)), this, SLOT(checkServer_toggled(bool))); +} + +void SrcSelect::checkFile_toggled(bool isOn) +{ + newFilename->setEnabled(isOn); +} + +void SrcSelect::checkServer_toggled(bool isOn) +{ + keyIds->setEnabled(isOn); +} diff --git a/kgpg/sourceselect.h b/kgpg/sourceselect.h new file mode 100644 index 00000000..1823e6d6 --- /dev/null +++ b/kgpg/sourceselect.h @@ -0,0 +1,35 @@ +/*************************************************************************** + sourceselect.h - description + ------------------- + begin : Mo April 30 2007 + copyright : (C) 2007 by Lukas Kropatschek + email : lukas.krop@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef SOURCESELECT_H +#define SOURCESELECT_H + +#include "ui_sourceselect.h" + +class SrcSelect : public QWidget, public Ui::SrcSelect +{ + Q_OBJECT + +public: + explicit SrcSelect(QWidget *parent = 0); + +private slots: + void checkFile_toggled(bool isOn); + void checkServer_toggled(bool isOn); +}; + +#endif // SOURCESELECT_H diff --git a/kgpg/sourceselect.ui b/kgpg/sourceselect.ui new file mode 100644 index 00000000..ebe9a2c4 --- /dev/null +++ b/kgpg/sourceselect.ui @@ -0,0 +1,120 @@ + + SrcSelect + + + + 0 + 0 + 380 + 243 + + + + + 0 + 0 + + + + + 350 + 0 + + + + + + + + + + + + + Clipboard + + + + + + + + + File: + + + true + + + + + + + true + + + KFile::File|KFile::ExistingOnly + + + + + + + + + + + Keyserver: + + + false + + + + + + + false + + + Enter the ids or fingerprints of the keys to import as space separated list. + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 331 + 51 + + + + + + + + + KUrlRequester + QFrame +
kurlrequester.h
+
+
+ + kurlrequester.h + klineedit.h + kpushbutton.h + + +
diff --git a/kgpg/tips b/kgpg/tips new file mode 100644 index 00000000..6b977468 --- /dev/null +++ b/kgpg/tips @@ -0,0 +1,60 @@ + + +

If you want to decrypt a text file, simply drag and drop it into the editor window. KGpg will do the rest. Even remote files can be dropped.

+

Drag a public key into the editor window and kgpg will automatically import it if you want.

+ + + +

The easiest way to encrypt a file: simply right click on the file, and you have an encrypt option in the contextual menu. +This works in konqueror or on your Desktop!

+ +
+ + +

If you want to encrypt a message for several persons, just select several encryption keys by pressing the "Ctrl" key.

+ +
+ + +

You do not have to be an expert in encryption to use this tool?
+Simply create yourself a key pair in the key management window. Then, export your public key and mail it to your friends.
+Ask them to do the same and import their public keys. Finally, to send an encrypted message, type it in the KGpg editor, then click "encrypt". Choose +your friend's key and click "encrypt" again. The message will be encrypted, ready to be sent by email.

+ +
+ + +

To perform an operation on a key, open the key management window and right click on the key. A popup menu with all available options will appear.

+ +
+ + +

Decrypt a file with a single mouse click on it. You will then be prompted for password, and then that is it!

+ +
+ + +

If you only want to open the key manager, type this in the command line prompt:

kgpg -k
+The editor can be reached by:
kgpg -d

+ +
+ + +

To open a file in the KGpg's editor and immediately decrypt it type:

kgpg -s filename

+ +
+ + +

If you want to change the password or expiration of a secret key simply double click on it to get the key properties dialog.

+ +
+ + +

You can reach your default key by pressing "Ctrl+Home" in the key manager.

+ +
+ + +

The number of signatures is shown as a tooltip if you hover the mouse on the size column in key manager. A key has to be expanded once for this to work.

+ +
diff --git a/kgpg/transactions/kgpgaddphoto.cpp b/kgpg/transactions/kgpgaddphoto.cpp new file mode 100644 index 00000000..cf8b1ba8 --- /dev/null +++ b/kgpg/transactions/kgpgaddphoto.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2008,2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgaddphoto.h" + +#include +#include + +KGpgAddPhoto::KGpgAddPhoto(QObject *parent, const QString &keyid, const QString &imagepath) + : KGpgEditKeyTransaction(parent, keyid, QLatin1String( "addphoto" ), false) +{ + setImagePath(imagepath); +} + +KGpgAddPhoto::~KGpgAddPhoto() +{ +} + +bool +KGpgAddPhoto::nextLine(const QString &line) +{ + if (!line.startsWith(QLatin1String("[GNUPG:] "))) + return false; + + if (line.contains(QLatin1String( "GOOD_PASSPHRASE" ))) { + setSuccess(TS_MSG_SEQUENCE); + } else if (line.endsWith(QLatin1String("photoid.jpeg.add"))) { + write(m_photourl.toUtf8()); + setSuccess(TS_OK); + } else if (line.contains(QLatin1String( "photoid.jpeg.size" ))) { + if (KMessageBox::questionYesNo(0, i18n("This image is very large. Use it anyway?"), QString(), KGuiItem(i18n("Use Anyway")), KGuiItem(i18n("Do Not Use"))) == KMessageBox::Yes) { + write("YES"); + } else { + setSuccess(TS_USER_ABORTED); + return true; + } + } else { + return KGpgEditKeyTransaction::nextLine(line); + } + + return false; +} + +void +KGpgAddPhoto::setImagePath(const QString &photourl) +{ + m_photourl = photourl; +} + +#include "kgpgaddphoto.moc" diff --git a/kgpg/transactions/kgpgaddphoto.h b/kgpg/transactions/kgpgaddphoto.h new file mode 100644 index 00000000..141ec651 --- /dev/null +++ b/kgpg/transactions/kgpgaddphoto.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008,2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGADDPHOTO_H +#define KGPGADDPHOTO_H + +#include + +#include "kgpgeditkeytransaction.h" + +class QString; + +class KGpgAddPhoto: public KGpgEditKeyTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgAddPhoto) +public: + KGpgAddPhoto(QObject *parent, const QString &keyid, const QString &imagepath); + virtual ~KGpgAddPhoto(); + + void setImagePath(const QString &imagepath); + +protected: + virtual bool nextLine(const QString &line); + +private: + QString m_photourl; +}; + +#endif // KGPGADDPHOTO_H diff --git a/kgpg/transactions/kgpgadduid.cpp b/kgpg/transactions/kgpgadduid.cpp new file mode 100644 index 00000000..543f1af8 --- /dev/null +++ b/kgpg/transactions/kgpgadduid.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008,2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgadduid.h" + +#include + +KGpgAddUid::KGpgAddUid(QObject *parent, const QString &keyid, const QString &name, const QString &email, const QString &comment) + : KGpgEditKeyTransaction(parent, keyid, QLatin1String("adduid"), false, true) +{ + setName(name); + setEmail(email); + setComment(comment); +} + +KGpgAddUid::~KGpgAddUid() +{ +} + +bool +KGpgAddUid::preStart() +{ + if (!KGpgEditKeyTransaction::preStart()) + return false; + + if (!m_email.isEmpty() && !KPIMUtils::isValidSimpleAddress(m_email)) { + setSuccess(TS_INVALID_EMAIL); + return false; + } + + return true; +} + +bool +KGpgAddUid::nextLine(const QString &line) +{ + if (!line.startsWith(QLatin1String("[GNUPG:] "))) + return false; + + if (line.contains(QLatin1String( "GOOD_PASSPHRASE" ))) { + setSuccess(TS_OK); + } else if (line.contains(QLatin1String( "keygen.name" ))) { + write(m_name.toUtf8()); + } else if (line.contains(QLatin1String( "keygen.email" ))) { + write(m_email.toAscii()); + } else if (line.contains(QLatin1String( "keygen.comment" ))) { + write(m_comment.toUtf8()); + } else { + return KGpgEditKeyTransaction::nextLine(line); + } + + return false; +} + +void +KGpgAddUid::setName(const QString &name) +{ + m_name = name; +} + +void +KGpgAddUid::setEmail(const QString &email) +{ + m_email = email; +} + +void +KGpgAddUid::setComment(const QString &comment) +{ + m_comment = comment; +} + +#include "kgpgadduid.moc" diff --git a/kgpg/transactions/kgpgadduid.h b/kgpg/transactions/kgpgadduid.h new file mode 100644 index 00000000..f56098ad --- /dev/null +++ b/kgpg/transactions/kgpgadduid.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008,2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGADDUID_H +#define KGPGADDUID_H + +#include + +#include "kgpgeditkeytransaction.h" + +class QString; + +/** + * @brief add a new user id to a key pair + */ +class KGpgAddUid: public KGpgEditKeyTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgAddUid) +public: + KGpgAddUid(QObject *parent, const QString &keyid, const QString &name, const QString &email = QString(), const QString &comment = QString()); + virtual ~KGpgAddUid(); + + void setName(const QString &name); + void setEmail(const QString &email); + void setComment(const QString &comment); + +protected: + virtual bool preStart(); + virtual bool nextLine(const QString &line); + +private: + QString m_name; + QString m_email; + QString m_comment; +}; + +#endif // KGPGADDUID_H diff --git a/kgpg/transactions/kgpgchangedisable.cpp b/kgpg/transactions/kgpgchangedisable.cpp new file mode 100644 index 00000000..6944a406 --- /dev/null +++ b/kgpg/transactions/kgpgchangedisable.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2008,2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgchangedisable.h" + +KGpgChangeDisable::KGpgChangeDisable(QObject *parent, const QString &keyid, const bool disable) + : KGpgEditKeyTransaction(parent, keyid, QString(), false) +{ + setDisable(disable); +} + +KGpgChangeDisable::~KGpgChangeDisable() +{ +} + +void +KGpgChangeDisable::setDisable(bool disable) +{ + QString cmd; + if (disable) + cmd = QLatin1String( "disable" ); + else + cmd = QLatin1String( "enable" ); + + replaceCommand(cmd); +} + +bool +KGpgChangeDisable::preStart() +{ + if (!KGpgEditKeyTransaction::preStart()) + return false; + + setSuccess(TS_OK); + + return true; +} + +bool +KGpgChangeDisable::nextLine(const QString &line) +{ + return KGpgEditKeyTransaction::nextLine(line); +} + +#include "kgpgchangedisable.moc" diff --git a/kgpg/transactions/kgpgchangedisable.h b/kgpg/transactions/kgpgchangedisable.h new file mode 100644 index 00000000..a176b927 --- /dev/null +++ b/kgpg/transactions/kgpgchangedisable.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008,2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGCHANGEDISABLE_H +#define KGPGCHANGEDISABLE_H + +#include + +#include "kgpgeditkeytransaction.h" + +/** + * @brief enable of disable a key + */ +class KGpgChangeDisable: public KGpgEditKeyTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgChangeDisable) +public: + KGpgChangeDisable(QObject *parent, const QString &keyid, const bool disable); + virtual ~KGpgChangeDisable(); + + void setDisable(bool disable); + +protected: + virtual bool preStart(); + virtual bool nextLine(const QString &line); +}; + +#endif // KGPGCHANGEDISABLE_H diff --git a/kgpg/transactions/kgpgchangeexpire.cpp b/kgpg/transactions/kgpgchangeexpire.cpp new file mode 100644 index 00000000..bc7b8eb4 --- /dev/null +++ b/kgpg/transactions/kgpgchangeexpire.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008,2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgchangeexpire.h" + +#include "detailedconsole.h" + +#include +#include + +KGpgChangeExpire::KGpgChangeExpire(QObject *parent, const QString &keyid, const QDateTime &date) + : KGpgEditKeyTransaction(parent, keyid, QLatin1String( "expire" ), false) +{ + setDate(date); +} + +KGpgChangeExpire::~KGpgChangeExpire() +{ +} + +bool +KGpgChangeExpire::nextLine(const QString &line) +{ + if (!line.startsWith(QLatin1String("[GNUPG:]"))) + return false; + + if (line.contains(QLatin1String( "GOOD_PASSPHRASE" ))) { + setSuccess(TS_OK); + + return false; + } else if (line.contains(QLatin1String( "keygen.valid" ))) { + if (m_date.isNull()) + write("0"); + else + write(QByteArray::number(QDate::currentDate().daysTo(m_date.date()))); + + return false; + } else { + return KGpgEditKeyTransaction::nextLine(line); + } +} + +void +KGpgChangeExpire::setDate(const QDateTime &date) +{ + m_date = date; +} + +#include "kgpgchangeexpire.moc" diff --git a/kgpg/transactions/kgpgchangeexpire.h b/kgpg/transactions/kgpgchangeexpire.h new file mode 100644 index 00000000..a1c8d465 --- /dev/null +++ b/kgpg/transactions/kgpgchangeexpire.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008,2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGCHANGEEXPIRE_H +#define KGPGCHANGEEXPIRE_H + +#include +#include + +#include "kgpgeditkeytransaction.h" + +/** + * @brief change the key lifetime + */ +class KGpgChangeExpire: public KGpgEditKeyTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgChangeExpire) +public: + KGpgChangeExpire(QObject *parent, const QString &keyid, const QDateTime &date); + virtual ~KGpgChangeExpire(); + + void setDate(const QDateTime &date); + +protected: + virtual bool nextLine(const QString &line); + +private: + QDateTime m_date; +}; + +#endif // KGPGCHANGEEXPIRE_H diff --git a/kgpg/transactions/kgpgchangepass.cpp b/kgpg/transactions/kgpgchangepass.cpp new file mode 100644 index 00000000..cce55d90 --- /dev/null +++ b/kgpg/transactions/kgpgchangepass.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2008,2009,2010,2011,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgchangepass.h" + +#include + +KGpgChangePass::KGpgChangePass(QObject *parent, const QString &keyid) + : KGpgTransaction(parent), + m_seenold(false) +{ + addArgument(QLatin1String( "--status-fd=1" )); + addArgument(QLatin1String( "--command-fd=0" )); + addArgument(QLatin1String( "--edit-key" )); + addArgument(keyid); + addArgument(QLatin1String( "passwd" )); +} + +KGpgChangePass::~KGpgChangePass() +{ +} + +bool +KGpgChangePass::preStart() +{ + setSuccess(TS_MSG_SEQUENCE); + + return true; +} + +bool +KGpgChangePass::nextLine(const QString &line) +{ + if (!line.startsWith(QLatin1String("[GNUPG:] "))) + return false; + + if (line.contains(QLatin1String( "keyedit.prompt" ))) { + if (m_seenold && (getSuccess() != TS_USER_ABORTED)) { + setSuccess(TS_OK); + write("save"); + } else { + // some sort of error, we already set the error code + return true; + } + } else if (line.contains(QLatin1String( "GET_" ))) { + setSuccess(TS_MSG_SEQUENCE); + return true; + } + + return false; +} + +bool +KGpgChangePass::passphraseRequested() +{ + const QString userIDs = getIdHints(); + + if (!m_seenold) { + return askPassphrase(i18n("Enter old passphrase for %1", userIDs)); + } else { + askNewPassphrase(i18n("Enter new passphrase for %1
If you forget this passphrase all your encrypted files and messages will be inaccessible.
", userIDs)); + } + + return true; +} + +bool +KGpgChangePass::passphraseReceived() +{ + m_seenold = true; + setSuccess(TS_MSG_SEQUENCE); + return false; +} + +#include "kgpgchangepass.moc" diff --git a/kgpg/transactions/kgpgchangepass.h b/kgpg/transactions/kgpgchangepass.h new file mode 100644 index 00000000..d14b2ef0 --- /dev/null +++ b/kgpg/transactions/kgpgchangepass.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008,2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGCHANGEPASS_H +#define KGPGCHANGEPASS_H + +#include + +#include "kgpgtransaction.h" + +/** + * @brief set a new passphrase for a key pair + */ +class KGpgChangePass: public KGpgTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgChangePass) +public: + KGpgChangePass(QObject *parent, const QString &keyid); + virtual ~KGpgChangePass(); + +protected: + virtual bool nextLine(const QString &line); + virtual bool preStart(); + virtual bool passphraseRequested(); + virtual bool passphraseReceived(); + +private: + bool m_seenold; ///< old password correctly entered +}; + +#endif // KGPGCHANGEPASS_H diff --git a/kgpg/transactions/kgpgchangetrust.cpp b/kgpg/transactions/kgpgchangetrust.cpp new file mode 100644 index 00000000..d2539291 --- /dev/null +++ b/kgpg/transactions/kgpgchangetrust.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2008,2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgchangetrust.h" + +KGpgChangeTrust::KGpgChangeTrust(QObject *parent, const QString &keyid, const gpgme_validity_t trust) + : KGpgEditKeyTransaction(parent, keyid, QLatin1String( "trust" ), false) +{ + setTrust(trust); +} + +KGpgChangeTrust::~KGpgChangeTrust() +{ +} + +bool +KGpgChangeTrust::preStart() +{ + setSuccess(TS_MSG_SEQUENCE); + + return true; +} + +bool +KGpgChangeTrust::nextLine(const QString &line) +{ + if (line.contains(QLatin1String( "edit_ownertrust.value" ))) { + write(QByteArray::number(m_trust)); + setSuccess(TS_OK); + } else { + return KGpgEditKeyTransaction::nextLine(line); + } + + return false; +} + +KGpgTransaction::ts_boolanswer +KGpgChangeTrust::boolQuestion(const QString& line) +{ + if (line == QLatin1String("edit_ownertrust.set_ultimate.okay")) { + return BA_YES; + } else { + return KGpgTransaction::boolQuestion(line); + } +} + +void +KGpgChangeTrust::setTrust(const gpgme_validity_t trust) +{ + m_trust = trust; +} + +#include "kgpgchangetrust.moc" diff --git a/kgpg/transactions/kgpgchangetrust.h b/kgpg/transactions/kgpgchangetrust.h new file mode 100644 index 00000000..67e717ce --- /dev/null +++ b/kgpg/transactions/kgpgchangetrust.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008,2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGCHANGETRUST_H +#define KGPGCHANGETRUST_H + +#include "kgpgeditkeytransaction.h" + +#include "core/kgpgkey.h" + +#include +#include + +/** + * @brief change the owner trust level of a public key + */ +class KGpgChangeTrust: public KGpgEditKeyTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgChangeTrust) +public: + KGpgChangeTrust(QObject *parent, const QString &keyid, const gpgme_validity_t trust); + virtual ~KGpgChangeTrust(); + + void setTrust(const gpgme_validity_t trust); + +protected: + virtual bool nextLine(const QString &line); + virtual ts_boolanswer boolQuestion(const QString &line); + virtual bool preStart(); + +private: + gpgme_validity_t m_trust; +}; + +#endif // KGPGCHANGETRUST_H diff --git a/kgpg/transactions/kgpgdecrypt.cpp b/kgpg/transactions/kgpgdecrypt.cpp new file mode 100644 index 00000000..deebf21d --- /dev/null +++ b/kgpg/transactions/kgpgdecrypt.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2010,2011,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgdecrypt.h" + +#include "gpgproc.h" +#include "kgpgsettings.h" + +#include + +KGpgDecrypt::KGpgDecrypt(QObject *parent, const QString &text) + : KGpgTextOrFileTransaction(parent, text), + m_fileIndex(-1), + m_plainLength(-1) +{ +} + +KGpgDecrypt::KGpgDecrypt(QObject *parent, const KUrl::List &files) + : KGpgTextOrFileTransaction(parent, files), + m_fileIndex(0), + m_plainLength(-1) +{ +} + +KGpgDecrypt::KGpgDecrypt(QObject* parent, const KUrl& infile, const KUrl& outfile) + : KGpgTextOrFileTransaction(parent, KUrl::List(infile)), + m_fileIndex(0), + m_plainLength(-1), + m_outFilename(outfile.toLocalFile()) +{ +} + +KGpgDecrypt::~KGpgDecrypt() +{ +} + +QStringList +KGpgDecrypt::command() const +{ + QStringList ret; + + ret << QLatin1String("--decrypt") << QLatin1String("--command-fd=0"); + + if (!m_outFilename.isEmpty()) + ret << QLatin1String("-o") << m_outFilename; + + ret << KGpgSettings::customDecrypt().simplified().split(QLatin1Char(' '), QString::SkipEmptyParts); + + return ret; +} + +QStringList +KGpgDecrypt::decryptedText() const +{ + QStringList result; + int txtlength = 0; + + foreach (const QString &line, getMessages()) + if (!line.startsWith(QLatin1String("[GNUPG:] "))) { + result.append(line); + txtlength += line.length() + 1; + } + + if (result.isEmpty()) + return result; + + QString last = result.last(); + // this may happen when the original text did not end with a newline + if (last.endsWith(QLatin1String("[GNUPG:] DECRYPTION_OKAY"))) { + // if GnuPG doesn't tell us the length assume that this happend + // if it told us the length then check if it _really_ happend + if (((m_plainLength != -1) && (txtlength != m_plainLength)) || + (m_plainLength == -1)) { + last.chop(24); + result[result.count() - 1] = last; + } + } + + return result; +} + +bool +KGpgDecrypt::isEncryptedText(const QString &text, int *startPos, int *endPos) +{ + int posStart = text.indexOf(QLatin1String("-----BEGIN PGP MESSAGE-----")); + if (posStart == -1) + return false; + + int posEnd = text.indexOf(QLatin1String("-----END PGP MESSAGE-----"), posStart); + if (posEnd == -1) + return false; + + if (startPos != NULL) + *startPos = posStart; + if (endPos != NULL) + *endPos = posEnd; + + return true; +} + +bool +KGpgDecrypt::nextLine(const QString& line) +{ + const KUrl::List &inputFiles = getInputFiles(); + + if (!inputFiles.isEmpty()) { + if (line == QLatin1String("[GNUPG:] BEGIN_DECRYPTION")) { + emit statusMessage(i18nc("Status message 'Decrypting ' (operation starts)", "Decrypting %1", inputFiles.at(m_fileIndex).fileName())); + emit infoProgress(2 * m_fileIndex + 1, inputFiles.count() * 2); + } else if (line == QLatin1String("[GNUPG:] END_DECRYPTION")) { + emit statusMessage(i18nc("Status message 'Decrypted ' (operation was completed)", "Decrypted %1", inputFiles.at(m_fileIndex).fileName())); + m_fileIndex++; + emit infoProgress(2 * m_fileIndex, inputFiles.count() * 2); + } + } else { + if (line.startsWith(QLatin1String("[GNUPG:] PLAINTEXT_LENGTH "))) { + bool ok; + m_plainLength = line.mid(26).toInt(&ok); + if (!ok) + m_plainLength = -1; + } else if (line == QLatin1String("[GNUPG:] BEGIN_DECRYPTION")) { + // close the command channel (if any) to signal GnuPG that it + // can start sending the output. + getProcess()->closeWriteChannel(); + } + } + + return KGpgTextOrFileTransaction::nextLine(line); +} + +#include "kgpgdecrypt.moc" diff --git a/kgpg/transactions/kgpgdecrypt.h b/kgpg/transactions/kgpgdecrypt.h new file mode 100644 index 00000000..cbc02581 --- /dev/null +++ b/kgpg/transactions/kgpgdecrypt.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2010,2011 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGDECRYPT_H +#define KGPGDECRYPT_H + +#include + +#include + +#include "kgpgtextorfiletransaction.h" + +class QProcess; +class QStringList; + +/** + * @brief decrypt the given text or files + */ +class KGpgDecrypt: public KGpgTextOrFileTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgDecrypt) + KGpgDecrypt(); // = delete C++0x +public: + /** + * @brief decrypt given text + * @param parent parent object + * @param text text to decrypt + */ + explicit KGpgDecrypt(QObject *parent, const QString &text = QString()); + + /** + * @brief decrypt file(s) + * @param parent parent object + * @param files list of file locations to decrypt + */ + KGpgDecrypt(QObject *parent, const KUrl::List &files); + + /** + * @brief decrypt file to given output filename + * @param parent parent object + * @param infile name of file to decrypt + * @param outfile name of file to write output to (will be overwritten) + */ + KGpgDecrypt(QObject *parent, const KUrl &infile, const KUrl &outfile); + + /** + * @brief destructor + */ + virtual ~KGpgDecrypt(); + + /** + * @brief get decryption result + * @return decrypted text + */ + QStringList decryptedText() const; + + /** + * @brief check if the given text contains an encoded message + * @param text text to check + * @param startPos if not NULL start offset of encoded text will be returned here + * @param endPos if not NULL end offset of encoded text will be returned here + */ + static bool isEncryptedText(const QString &text, int *startPos = NULL, int *endPos = NULL); + +protected: + virtual QStringList command() const; + virtual bool nextLine(const QString &line); + +private: + int m_fileIndex; + int m_plainLength; ///< length of decrypted plain text if given by GnuPG + const QString m_outFilename; ///< name of file to write output to +}; + +#endif // KGPGDECRYPT_H diff --git a/kgpg/transactions/kgpgdelkey.cpp b/kgpg/transactions/kgpgdelkey.cpp new file mode 100644 index 00000000..4d867565 --- /dev/null +++ b/kgpg/transactions/kgpgdelkey.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2008,2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgdelkey.h" + +#include "gpgproc.h" + +#include +#include + +KGpgDelKey::KGpgDelKey(QObject *parent, KGpgKeyNode *key) + : KGpgTransaction(parent) +{ + m_keys << key; + setCmdLine(); +} + +KGpgDelKey::KGpgDelKey(QObject *parent, const KGpgKeyNode::List &keys) + : KGpgTransaction(parent), + m_keys(keys) +{ + setCmdLine(); +} + +KGpgDelKey::~KGpgDelKey() +{ +} + +KGpgKeyNode::List +KGpgDelKey::keys() const +{ + return m_keys; +} + +bool +KGpgDelKey::nextLine(const QString &line) +{ + if (!line.startsWith(QLatin1String("[GNUPG:] GOT_IT"))) + setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); + + return false; +} + +KGpgTransaction::ts_boolanswer +KGpgDelKey::boolQuestion(const QString &line) +{ + if (line.startsWith(QLatin1String("delete_key.okay"))) + return KGpgTransaction::BA_YES; + + if (line.startsWith(QLatin1String("delete_key.secret.okay"))) + return KGpgTransaction::BA_YES; + + return KGpgTransaction::boolQuestion(line); +} + +bool +KGpgDelKey::preStart() +{ + GPGProc *proc = getProcess(); + QStringList args = proc->program(); + + foreach (const KGpgKeyNode *key, m_keys) + args << key->getFingerprint(); + + proc->setProgram(args); + + setSuccess(KGpgTransaction::TS_OK); + + return true; +} + +void +KGpgDelKey::setCmdLine() +{ + addArgument(QLatin1String( "--status-fd=1" )); + addArgument(QLatin1String( "--command-fd=0" )); + addArgument(QLatin1String( "--delete-secret-and-public-key" )); + + m_argscount = getProcess()->program().count(); +} + +#include "kgpgdelkey.moc" diff --git a/kgpg/transactions/kgpgdelkey.h b/kgpg/transactions/kgpgdelkey.h new file mode 100644 index 00000000..ce78d74d --- /dev/null +++ b/kgpg/transactions/kgpgdelkey.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2008,2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGDELKEY_H +#define KGPGDELKEY_H + +#include "kgpgtransaction.h" + +#include + +#include + +/** + * @brief delete a public key + */ +class KGpgDelKey: public KGpgTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgDelKey) + KGpgDelKey(); // = delete C++0x +public: + KGpgDelKey(QObject *parent, KGpgKeyNode *key); + KGpgDelKey(QObject *parent, const KGpgKeyNode::List &keys); + virtual ~KGpgDelKey(); + + /** + * @brief the keys that were requested to be removed + * @return the list of key nodes + */ + KGpgKeyNode::List keys() const; + +protected: + virtual bool nextLine(const QString &line); + virtual ts_boolanswer boolQuestion(const QString &line); + virtual bool preStart(); + +private: + KGpgKeyNode::List m_keys; + int m_argscount; + + void setCmdLine(); +}; + +#endif // KGPGDELKEY_H diff --git a/kgpg/transactions/kgpgdelsign.cpp b/kgpg/transactions/kgpgdelsign.cpp new file mode 100644 index 00000000..154ab3b9 --- /dev/null +++ b/kgpg/transactions/kgpgdelsign.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2010,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgdelsign.h" + +#include "model/kgpgitemnode.h" +#include "gpgproc.h" + +#include + +KGpgDelSign::KGpgDelSign(QObject *parent, const KGpgSignNode::List &signids) + : KGpgUidTransaction(parent, signids.at(0)->getParentKeyNode()->getId()) +{ + addArgument(QLatin1String( "delsig" )); + + const QStringList args = getProcess()->program(); + + // If we run with --no-tty GnuPG will not tell which sign it is currently + // asking to remove :( + int ntty = args.indexOf(QLatin1String("--no-tty")); + if (ntty >= 0) + replaceArgument(ntty, QLatin1String("--with-colons")); + else + insertArgument(1, QLatin1String( "--with-colons" )); + + if (signids.at(0)->getParentKeyNode()->getType() & KgpgCore::ITYPE_PUBLIC) + setUid(QLatin1String( "1" )); + else + setUid(signids.at(0)->getParentKeyNode()->getId()); + +#ifndef QT_NO_DEBUG + foreach (const KGpgSignNode *snode, signids) { + Q_ASSERT(signids.at(0)->getParentKeyNode() == snode->getParentKeyNode()); + } +#endif + + setSignIds(signids); +} + +KGpgDelSign::KGpgDelSign(QObject* parent, KGpgSignNode *signid) + : KGpgUidTransaction(parent, signid->getParentKeyNode()->getId()) +{ + addArgument(QLatin1String( "delsig" )); + insertArgument(1, QLatin1String( "--with-colons" )); + + if (signid->getParentKeyNode()->getType() & KgpgCore::ITYPE_PUBLIC) + setUid(QLatin1String( "1" )); + else + setUid(signid->getParentKeyNode()->getId()); + + setSignId(signid); +} + + +KGpgDelSign::~KGpgDelSign() +{ +} + +KGpgSignNode::List KGpgDelSign::getSignIds(void) const +{ + return m_signids; +} + +void KGpgDelSign::setSignId(KGpgSignNode* keyid) +{ + m_signids.clear(); + m_signids << keyid; +} + +void KGpgDelSign::setSignIds(const KGpgSignNode::List &keyids) +{ + m_signids = keyids; +} + +bool +KGpgDelSign::nextLine(const QString &line) +{ + if (line.startsWith(QLatin1String("sig:"))) { + m_cachedid = line; + return false; + } else if (line.startsWith(QLatin1String("[GNUPG:] "))) { + return standardCommands(line); + } else { + // GnuPG will tell us a bunch of stuff because we are not in + // --no-tty mode but we don't care. + return false; + } +} + +KGpgTransaction::ts_boolanswer +KGpgDelSign::boolQuestion(const QString &line) +{ + if (line.startsWith(QLatin1String("keyedit.delsig."))) { + const QStringList parts = m_cachedid.split(QLatin1Char( ':' )); + + if (parts.count() < 7) + return KGpgTransaction::BA_NO; + + const QString &sigid = parts[4]; + const int snlen = sigid.length(); + KGpgSignNode *signode = NULL; + + foreach (KGpgSignNode *snode, m_signids) { + if (sigid == snode->getId().right(snlen)) { + signode = snode; + break; + } + } + + if (signode == NULL) + return KGpgTransaction::BA_NO; + + const QDateTime creation = QDateTime::fromTime_t(parts[5].toUInt()); + if (creation != signode->getCreation()) + return KGpgTransaction::BA_NO; + + QDateTime sigexp; + if (!parts[6].isEmpty() && (parts[6] != QLatin1String("0"))) + sigexp = QDateTime::fromTime_t(parts[6].toUInt()); + if (sigexp != signode->getExpiration()) + return KGpgTransaction::BA_NO; + + m_signids.removeOne(signode); + return KGpgTransaction::BA_YES; + } else { + return KGpgTransaction::boolQuestion(line); + } +} + +#include "kgpgdelsign.moc" diff --git a/kgpg/transactions/kgpgdelsign.h b/kgpg/transactions/kgpgdelsign.h new file mode 100644 index 00000000..731c8f97 --- /dev/null +++ b/kgpg/transactions/kgpgdelsign.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2010,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGDELSIGN_H +#define KGPGDELSIGN_H + +#include "kgpguidtransaction.h" + +#include "core/KGpgSignNode.h" + +#include +#include + +/** + * @brief delete signatures from user ids + */ +class KGpgDelSign: public KGpgUidTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgDelSign) + KGpgDelSign(); // = delete C++0x +public: + /** + * @brief construct a new transaction to delete signatures + * @param parent object that own the transaction + * @param signids list of signature ids to remove + * + * All members of signids need to have the same parent node, i.e. + * they not even may be signatures to different uids of the same key. + */ + KGpgDelSign(QObject *parent, const KGpgSignNode::List &signids); + /** + * @brief construct a new transaction to delete signatures + * @param parent object that own the transaction + * @param signid signature to delete + */ + KGpgDelSign(QObject *parent, KGpgSignNode *signid); + + virtual ~KGpgDelSign(); + + /** + * @brief set the ids to delete + * @param signids fingerprints of the signatures to delete + * + * This will replace all previously set signature ids. + */ + void setSignIds(const KGpgSignNode::List &keyids); + /** + * @brief set the id to delete + * @param keyid fingerprint of the signatures to delete + * @overload + * + * This will replace all previously set signature ids. + */ + void setSignId(KGpgSignNode *keyid); + /** + * @brief return the signature ids to delete + */ + KGpgSignNode::List getSignIds(void) const; + +protected: + virtual bool nextLine(const QString &line); + virtual ts_boolanswer boolQuestion(const QString &line); + +private: + KGpgSignNode::List m_signids; ///< the list of ids to delete + QString m_cachedid; ///< the next id GnuPG will ask to delete +}; + +#endif // KGPGDELSIGN_H diff --git a/kgpg/transactions/kgpgdeluid.cpp b/kgpg/transactions/kgpgdeluid.cpp new file mode 100644 index 00000000..0adfdb18 --- /dev/null +++ b/kgpg/transactions/kgpgdeluid.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2008,2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgdeluid.h" + +#include "gpgproc.h" +#include "core/kgpgkey.h" +#include "core/KGpgKeyNode.h" + +#include + +KGpgDelUid::KGpgDelUid(QObject *parent, const KGpgSignableNode *uid) + : KGpgUidTransaction(parent, uid->getParentKeyNode()->getId(), uid->getId()), + m_fixargs(addArgument(QLatin1String( "deluid" ))) +{ + setUid(uid); +} + +KGpgDelUid::KGpgDelUid(QObject *parent, const KGpgSignableNode::const_List &uids) + : KGpgUidTransaction(parent), + m_fixargs(addArgument(QLatin1String( "deluid" ))) +{ + setUids(uids); +} + +KGpgDelUid::KGpgDelUid(QObject *parent, const KGpgKeyNode *keynode, const int uid, const RemoveMode removeMode) + : KGpgUidTransaction(parent), + m_fixargs(addArgument(QLatin1String( "deluid" ))) +{ + setUid(keynode, uid, removeMode); +} + +KGpgDelUid::~KGpgDelUid() +{ +} + +void +KGpgDelUid::setUid(const KGpgSignableNode *uid) +{ + KGpgSignableNode::const_List uids; + + uids.append(uid); + setUids(uids); +} + +bool +signNodeGreaterThan(const KGpgSignableNode *s1, const KGpgSignableNode *s2) +{ + return *s2 < *s1; +} + +void +KGpgDelUid::setUids(const KGpgSignableNode::const_List &uids) +{ + Q_ASSERT(!uids.isEmpty()); + + m_uids = uids; + + GPGProc *proc = getProcess(); + + QStringList args(proc->program()); + proc->clearProgram(); + + for (int i = args.count() - m_fixargs - 1; i > 0; i--) + args.removeLast(); + + // FIXME: can this use qGreater<>()? + qSort(m_uids.begin(), m_uids.end(), signNodeGreaterThan); + + const KGpgSignableNode *nd = m_uids.first(); + const KGpgExpandableNode *parent; + if (nd->getType() & KgpgCore::ITYPE_PAIR) + parent = nd; + else + parent = nd->getParentKeyNode(); + + foreach (nd, m_uids.mid(1)) { + Q_ASSERT((nd->getParentKeyNode() == parent) || (nd == parent)); + + args.append(QLatin1String( "uid" )); + if (nd->getType() & KgpgCore::ITYPE_PAIR) + args.append(QLatin1String("1")); + else + args.append(nd->getId()); + args.append(QLatin1String( "deluid" )); + } + + proc->setProgram(args); + nd = m_uids.first(); + + switch (nd->getType()) { + case KgpgCore::ITYPE_PUBLIC: + case KgpgCore::ITYPE_PAIR: + KGpgUidTransaction::setUid(1); + break; + default: + KGpgUidTransaction::setUid(nd->getId()); + break; + } + setKeyId(parent->getId()); +} + +void +KGpgDelUid::setUid(const KGpgKeyNode *keynode, const int uid, const RemoveMode removeMode) +{ + Q_ASSERT(uid != 0); + + KGpgSignableNode::const_List uids; + const KGpgSignableNode *uidnode; + + if (uid > 0) { + uidnode = keynode->getUid(uid); + + Q_ASSERT(uidnode != NULL); + uids.append(uidnode); + } else { + Q_ASSERT(keynode->wasExpanded()); + int idx = 0; + + forever { + idx++; + if (idx == -uid) + continue; + + uidnode = keynode->getUid(idx); + + if (uidnode == NULL) + break; + + switch (removeMode) { + case RemoveAllOther: + uids.append(uidnode); + break; + case KeepUats: + if (uidnode->getType() != KgpgCore::ITYPE_UAT) + uids.append(uidnode); + break; + case RemoveWithEmail: + if (!uidnode->getEmail().isEmpty()) + uids.append(uidnode); + break; + } + } + } + + if (!uids.isEmpty()) + setUids(uids); +} + +bool +KGpgDelUid::preStart() +{ + if (m_uids.isEmpty()) { + setSuccess(TS_NO_SUCH_UID); + return false; + } + + return true; +} + +bool +KGpgDelUid::nextLine(const QString &line) +{ + return standardCommands(line); +} + +KGpgTransaction::ts_boolanswer +KGpgDelUid::boolQuestion(const QString& line) +{ + if (line == QLatin1String("keyedit.remove.uid.okay")) { + m_uids.removeFirst(); + return BA_YES; + } else { + return KGpgTransaction::boolQuestion(line); + } +} + +void +KGpgDelUid::finish() +{ + if (!m_uids.isEmpty()) + setSuccess(TS_MSG_SEQUENCE); +} + +#include "kgpgdeluid.moc" diff --git a/kgpg/transactions/kgpgdeluid.h b/kgpg/transactions/kgpgdeluid.h new file mode 100644 index 00000000..42871429 --- /dev/null +++ b/kgpg/transactions/kgpgdeluid.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2008,2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGDELUID_H +#define KGPGDELUID_H + +#include "kgpguidtransaction.h" + +#include "core/KGpgSignableNode.h" + +#include +#include + +class KGpgKeyNode; +class KGpgUidNode; + +class KGpgDelUid: public KGpgUidTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgDelUid) + KGpgDelUid(); // = delete C++0x +public: + enum ts_deluid { + TS_NO_SUCH_UID = KGpgTransaction::TS_COMMON_END + 1 ///< user id does not exist + }; + + enum RemoveMode { + RemoveAllOther = 0, ///< remove all other uids + KeepUats = 1, ///< remove all other uids, but keep uats + RemoveWithEmail = 2 ///< remove only those other uids that have an email address + }; + + /** + * @brief constructor + * @param parent parent object + * @param uid user id to delete + */ + KGpgDelUid(QObject *parent, const KGpgSignableNode *uid); + /** + * @brief constructor + * @param parent parent object + * @param uids user ids to delete + * + * All entries in uids must be children of the same keynode. The keynode itself + * may be part of the list, representing the primary user id. The nodes must be + * either the keynode itself, user id nodes, or user attribute nodes. + */ + KGpgDelUid(QObject *parent, const KGpgSignableNode::const_List &uids); + /** + * @brief constructor + * @param parent parent object + * @param keynode key to edit + * @param uid uid to delete, negative to delete all others (see also removeMode) + * @param removeMode control which other uids are removed in case uid is negative + */ + KGpgDelUid(QObject *parent, const KGpgKeyNode *keynode, const int uid, const RemoveMode removeMode = RemoveAllOther); + /** + * @brief destructor + */ + virtual ~KGpgDelUid(); + + /** + * @brief set the user id to be deleted + * + * This removes all previously set user ids from the list. + */ + void setUid(const KGpgSignableNode *uid); + /** + * @brief set the user id to be deleted + * + * @overload + */ + void setUid(const KGpgKeyNode *keynode, const int uid, const RemoveMode removeMode = RemoveAllOther); + /** + * @brief set the user ids to be deleted + * + * This removes all previously set user ids from the list. + */ + void setUids(const KGpgSignableNode::const_List &uids); + +protected: + virtual bool preStart(); + virtual bool nextLine(const QString &line); + virtual ts_boolanswer boolQuestion(const QString &line); + virtual void finish(); + +private: + int m_fixargs; + QList m_uids; +}; + +#endif // KGPGDELUID_H diff --git a/kgpg/transactions/kgpgeditkeytransaction.cpp b/kgpg/transactions/kgpgeditkeytransaction.cpp new file mode 100644 index 00000000..2fd5725d --- /dev/null +++ b/kgpg/transactions/kgpgeditkeytransaction.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgeditkeytransaction.h" + +KGpgEditKeyTransaction::KGpgEditKeyTransaction(QObject *parent, const QString &keyid, + const QString &command, const bool hasValue, const bool autoSave) + : KGpgTransaction(parent), + m_autosave(autoSave), + m_keyid(keyid) +{ + addArgument(QLatin1String( "--status-fd=1" )); + addArgument(QLatin1String( "--command-fd=0" )); + addArgument(QLatin1String( "--edit-key" )); + addArgument(keyid); + + m_cmdpos = addArgument(command); + addArgumentRef(&m_cmdpos); + + if (hasValue) { + m_argpos = addArgument(QString()); + addArgumentRef(&m_argpos); + } else { + m_argpos = -1; + } + + if (autoSave) + addArgument(QLatin1String( "save" )); +} + +KGpgEditKeyTransaction::~KGpgEditKeyTransaction() +{ +} + +QString +KGpgEditKeyTransaction::getKeyid() const +{ + return m_keyid; +} + +bool +KGpgEditKeyTransaction::preStart() +{ + setSuccess(TS_MSG_SEQUENCE); + + return true; +} + +bool +KGpgEditKeyTransaction::nextLine(const QString &line) +{ + if (line == QLatin1String("[GNUPG:] GOT_IT")) { + setSuccess(TS_OK); + return false; + } else if (getSuccess() == TS_USER_ABORTED) { + if (line.contains(QLatin1String( "GET_" ) )) + return true; + } else if ((getSuccess() == TS_OK) && line.contains(QLatin1String( "keyedit.prompt" ))) { + return true; + } else if (line.contains(QLatin1String( "NEED_PASSPHRASE" ))) { + // nothing for now + // we could use the id from NEED_PASSPHRASE as user id hint ... + } else { + if (getSuccess() != TS_BAD_PASSPHRASE) + setSuccess(TS_MSG_SEQUENCE); + return true; + } + + return false; +} + +KGpgTransaction::ts_boolanswer +KGpgEditKeyTransaction::boolQuestion(const QString& line) +{ + if ((getSuccess() == TS_OK) && (line == QLatin1String("keyedit.save.okay")) && !m_autosave) { + return BA_YES; + } else { + return KGpgTransaction::boolQuestion(line); + } +} + +void +KGpgEditKeyTransaction::replaceValue(const QString &arg) +{ + Q_ASSERT(m_argpos >= 0); + + replaceArgument(m_argpos, arg); +} + +void +KGpgEditKeyTransaction::replaceCommand(const QString &cmd) +{ + replaceArgument(m_cmdpos, cmd); +} + +#include "kgpgeditkeytransaction.moc" diff --git a/kgpg/transactions/kgpgeditkeytransaction.h b/kgpg/transactions/kgpgeditkeytransaction.h new file mode 100644 index 00000000..920d2525 --- /dev/null +++ b/kgpg/transactions/kgpgeditkeytransaction.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGEDITKEYTRANSACTION_H +#define KGPGEDITKEYTRANSACTION_H + +#include + +#include "kgpgtransaction.h" + +/** + * @brief edit a single property of a key + */ +class KGpgEditKeyTransaction: public KGpgTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgEditKeyTransaction) + KGpgEditKeyTransaction(); // = delete C++0x + +protected: + /** + * @brief constructor + * @param parent parent object + * @param keyid key to edit + * @param command GnuPG command to use + * @param hasValue if the command takes an extra argument + * @param autoSave if a "save" command should be sent to GnuPG immediately + */ + KGpgEditKeyTransaction(QObject *parent, const QString &keyid, const QString &command, const bool hasValue, const bool autoSave = true); + +public: + /** + * @brief destructor + */ + virtual ~KGpgEditKeyTransaction(); + + /** + * @brief return the id of the key we are editing + */ + QString getKeyid() const; + +protected: + /** + * @brief reset class before next operation starts + * + * If you inherit from this class make sure this method is called + * from your inherited method before you do anything else there. + */ + virtual bool preStart(); + + /** + * @brief handle standard GnuPG prompts + * @param line the line to handle + * + * By default this handles passphrase questions and quits the + * operation when GnuPG returns to it's command prompt. The + * "GOOD_PASSPHRASE" line is _not_ handled here. When you inherit + * this class and need to handle specific line do them first and + * then call this method at the end of your method to handle all + * standard things (if you don't want to handle them yourself). + * Every line sent here by GnuPG not recognised as command handled + * here will set a sequence error so be sure to handle your stuff first! + */ + virtual bool nextLine(const QString &line); + + virtual ts_boolanswer boolQuestion(const QString &line); + + /** + * @brief replace the argument of the edit command + * @param arg new argument + * + * Calling this function when the hasValue parameter of the constructor was false is an error. + */ + void replaceValue(const QString &arg); + + /** + * @brief replace the command + * @param cmd new command + * + * This is seldomly needed, only when a command has different names + * for positive or negative action instead of taking that as argument. + */ + void replaceCommand(const QString &cmd); + +private: + int m_cmdpos; ///< position of the command thas is passed to the edit command + int m_argpos; ///< position of the argument that is passed to the edit command + const bool m_autosave; ///< if autosave was requested in constructor + const QString m_keyid; ///< id of the key we are editing +}; + +#endif // KGPGEDITKEYTRANSACTION_H diff --git a/kgpg/transactions/kgpgencrypt.cpp b/kgpg/transactions/kgpgencrypt.cpp new file mode 100644 index 00000000..cb1a6cbc --- /dev/null +++ b/kgpg/transactions/kgpgencrypt.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2011,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgencrypt.h" + +#include "kgpgsettings.h" +#include "gpgproc.h" + +static QStringList trustOptions(const QString &binary) +{ + const int gpgver = GPGProc::gpgVersion(GPGProc::gpgVersionString(binary)); + QStringList args; + if (gpgver >= 0x10302) + args << QLatin1String("--trust-model") + << QLatin1String("always"); + else + args << QLatin1String("--always-trust"); + + return args; +} + +KGpgEncrypt::KGpgEncrypt(QObject *parent, const QStringList &userIds, const QString &text, const EncryptOptions &options, const QStringList &extraOptions) + : KGpgTextOrFileTransaction(parent, text), + m_fileIndex(-1), + m_options(options), + m_userIds(userIds), + m_extraOptions(extraOptions) +{ + if ((m_options & AllowUntrustedEncryption) && !m_userIds.isEmpty()) + m_extraOptions << trustOptions(getProcess()->program().at(0)); +} + +KGpgEncrypt::KGpgEncrypt(QObject *parent, const QStringList &userIds, const KUrl::List &files, const EncryptOptions &options, const QStringList &extraOptions) + : KGpgTextOrFileTransaction(parent, files), + m_fileIndex(0), + m_options(options), + m_userIds(userIds), + m_extraOptions(extraOptions) +{ + if ((m_options & AllowUntrustedEncryption) && !m_userIds.isEmpty()) + m_extraOptions << trustOptions(getProcess()->program().at(0)); +} + +KGpgEncrypt::~KGpgEncrypt() +{ +} + +QStringList +KGpgEncrypt::command() const +{ + QStringList ret = m_extraOptions; + + if (m_options.testFlag(AsciiArmored)) + ret << QLatin1String("--armor"); + + if (m_userIds.isEmpty()) { + ret << QLatin1String( "--symmetric" ); + } else { + if (m_options.testFlag(HideKeyId)) + ret << QLatin1String("--throw-keyid"); + + foreach (const QString &uid, m_userIds) + ret << QLatin1String( "--recipient" ) << uid; + ret << QLatin1String( "--encrypt" ); + } + + return ret; +} + +QStringList +KGpgEncrypt::encryptedText() const +{ + QStringList result; + int txtlength = 0; + + foreach (const QString &line, getMessages()) + if (!line.startsWith(QLatin1String("[GNUPG:] "))) { + result.append(line); + txtlength += line.length() + 1; + } + + return result; +} + +bool +KGpgEncrypt::nextLine(const QString &line) +{ + const KUrl::List &inputFiles = getInputFiles(); + + if (!inputFiles.isEmpty()) { + static const QString encStart = QLatin1String("[GNUPG:] FILE_START 2 "); + static const QString encDone = QLatin1String("[GNUPG:] FILE_DONE"); + + if (line.startsWith(encStart)) { + m_currentFile = line.mid(encStart.length()); + emit statusMessage(i18nc("Status message 'Encrypting ' (operation starts)", "Encrypting %1", m_currentFile)); + emit infoProgress(2 * m_fileIndex + 1, inputFiles.count() * 2); + } else if (line == encDone) { + emit statusMessage(i18nc("Status message 'Encrypted ' (operation was completed)", "Encrypted %1", m_currentFile)); + m_fileIndex++; + emit infoProgress(2 * m_fileIndex, inputFiles.count() * 2); + } + } + + return KGpgTextOrFileTransaction::nextLine(line); +} + +KGpgTransaction::ts_boolanswer +KGpgEncrypt::confirmOverwrite(KUrl ¤tFile) +{ + const QString ext = encryptExtension(m_options.testFlag(AsciiArmored)); + + if (m_currentFile.isEmpty()) + currentFile = KUrl::fromLocalFile(getInputFiles().at(m_fileIndex).toLocalFile() + ext); + else + currentFile = KUrl::fromLocalFile(m_currentFile + ext); + return BA_UNKNOWN; +} + + +QString +KGpgEncrypt::encryptExtension(const bool ascii) +{ + if (ascii) + return QLatin1String( ".asc" ); + else if (KGpgSettings::pgpExtension()) + return QLatin1String( ".pgp" ); + else + return QLatin1String( ".gpg" ); +} + +#include "kgpgencrypt.moc" diff --git a/kgpg/transactions/kgpgencrypt.h b/kgpg/transactions/kgpgencrypt.h new file mode 100644 index 00000000..ef0aa972 --- /dev/null +++ b/kgpg/transactions/kgpgencrypt.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2011,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGENCRYPT_H +#define KGPGENCRYPT_H + +#include +#include +#include + +#include + +#include "kgpgtextorfiletransaction.h" + +class QProcess; + +/** + * @brief encrypt the given text or files + */ +class KGpgEncrypt: public KGpgTextOrFileTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgEncrypt) + KGpgEncrypt(); // = delete C++0x +public: + enum EncryptOption { + DefaultEncryption = 0, ///< use whatever GnuPGs defaults are + AsciiArmored = 0x1, ///< output the data as printable ASCII as opposed to binary data + AllowUntrustedEncryption = 0x2, ///< allow encryption with untrusted keys, ignored for symmetric encryption + HideKeyId = 0x4 ///< remove anything that shows which key ids this data is encrypted to, ignored for symmetric encryption + }; + Q_DECLARE_FLAGS(EncryptOptions, EncryptOption); + + /** + * @brief encrypt given text + * @param parent parent object + * @param userIds ids to encrypt to or empty list to use symmetric encryption with passphrase + * @param text text to encrypt + * @param options encryption options + */ + explicit KGpgEncrypt(QObject *parent, const QStringList &userIds = QStringList(), const QString &text = QString(), const EncryptOptions &options = DefaultEncryption, const QStringList &extraOptions = QStringList()); + + /** + * @brief encrypt file(s) + * @param parent parent object + * @param userIds ids to encrypt to or empty list to use symmetric encryption with passphrase + * @param files list of file locations to encrypt + * @param options encryption options + */ + KGpgEncrypt(QObject *parent, const QStringList &userIds, const KUrl::List &files, const EncryptOptions &options = DefaultEncryption, const QStringList &extraOptions = QStringList()); + + /** + * @brief destructor + */ + virtual ~KGpgEncrypt(); + + /** + * @brief get decryption result + * @return decrypted text + */ + QStringList encryptedText() const; + + /** + * @brief return the preferred extension for encrypted files + * @param ascii if the file is encrypted with ASCII armor + * @return the file extension with leading dot + */ + static QString encryptExtension(const bool ascii); + +protected: + virtual QStringList command() const; + virtual bool nextLine(const QString &line); + virtual ts_boolanswer confirmOverwrite (KUrl ¤tFile); + +private: + int m_fileIndex; + const EncryptOptions m_options; + const QStringList m_userIds; + QStringList m_extraOptions; + QString m_currentFile; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(KGpgEncrypt::EncryptOptions); + +#endif // KGPGENCRYPT_H diff --git a/kgpg/transactions/kgpgexport.cpp b/kgpg/transactions/kgpgexport.cpp new file mode 100644 index 00000000..155ee899 --- /dev/null +++ b/kgpg/transactions/kgpgexport.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgexport.h" + +#include "gpgproc.h" + +#include +#include + +KGpgExport::KGpgExport(QObject *parent, const QStringList &ids, QProcess *outp, const QStringList &options, const bool secret) + : KGpgTransaction(parent), + m_keyids(ids), + m_outp(outp), + m_outputmode(ModeProcess) +{ + procSetup(options, secret); +} + +KGpgExport::KGpgExport(QObject *parent, const QStringList &ids, const QString &file, const QStringList &options, const bool secret) + : KGpgTransaction(parent), + m_keyids(ids), + m_outp(NULL), + m_outf(file), + m_outputmode(ModeFile) +{ + procSetup(options, secret); +} + +KGpgExport::KGpgExport(QObject *parent, const QStringList &ids, const QStringList &options, const bool secret) + : KGpgTransaction(parent), + m_keyids(ids), + m_outp(NULL), + m_outputmode(ModeStdout) +{ + procSetup(options, secret); +} + +KGpgExport::KGpgExport(QObject *parent, const QStringList &ids, KGpgTransaction *outt, const QStringList &options, const bool secret) + : KGpgTransaction(parent), + m_keyids(ids), + m_outp(NULL), + m_outputmode(ModeTransaction) +{ + procSetup(options, secret); + outt->setInputTransaction(this); +} + +KGpgExport::~KGpgExport() +{ +} + +void +KGpgExport::setKeyId(const QString &id) +{ + m_keyids.clear(); + m_keyids.append(id); +} + +void +KGpgExport::setKeyIds(const QStringList &ids) +{ + m_keyids = ids; +} + +const QStringList & +KGpgExport::getKeyIds() const +{ + return m_keyids; +} + +void +KGpgExport::setOutputProcess(QProcess *outp) +{ + m_outf.clear(); + m_outp = outp; + m_outputmode = ModeProcess; +} + +void +KGpgExport::setOutputFile(const QString &filename) +{ + m_outp = NULL; + m_outf = filename; + if (filename.isEmpty()) + m_outputmode = ModeStdout; + else + m_outputmode = ModeFile; +} + +void +KGpgExport::setOutputTransaction(KGpgTransaction *outt) +{ + m_outp = NULL; + m_outf.clear(); + m_outputmode = ModeTransaction; + outt->setInputTransaction(this); +} + +const QString & +KGpgExport::getOutputFile() const +{ + return m_outf; +} + +const QByteArray & +KGpgExport::getOutputData() const +{ + return m_data; +} + +bool +KGpgExport::preStart() +{ + setSuccess(TS_OK); + + switch (m_outputmode) { + case ModeFile: + { + Q_ASSERT(!m_outf.isEmpty()); + Q_ASSERT(m_outp == NULL); + + addArgument(QLatin1String( "--output" )); + addArgument(m_outf); + + QFile ofile(m_outf); + if (ofile.exists()) + ofile.remove(); + + break; + } + case ModeProcess: + Q_ASSERT(m_outf.isEmpty()); + Q_ASSERT(m_outp != NULL); + + getProcess()->setStandardOutputProcess(m_outp); + + break; + case ModeStdout: + Q_ASSERT(m_outf.isEmpty()); + Q_ASSERT(m_outp == NULL); + break; + case ModeTransaction: + Q_ASSERT(m_outf.isEmpty()); + Q_ASSERT(m_outp == NULL); + break; + default: + Q_ASSERT(0); + } + + addArguments(m_keyids); + + m_data.clear(); + + return true; +} + +bool +KGpgExport::nextLine(const QString &line) +{ + // key exporting does not send any messages + + m_data.append(line.toAscii() + '\n'); + + if (m_outputmode != 2) + setSuccess(TS_MSG_SEQUENCE); + + return false; +} + +void +KGpgExport::procSetup(const QStringList &options, const bool secret) +{ + getProcess()->resetProcess(); + + if (secret) + addArgument(QLatin1String( "--export-secret-key" )); + else + addArgument(QLatin1String( "--export" )); + + if ((m_outputmode == 2) && !options.contains(QLatin1String( "--armor" ))) + addArgument(QLatin1String( "--armor" )); + + addArguments(options); +} + +#include "kgpgexport.moc" diff --git a/kgpg/transactions/kgpgexport.h b/kgpg/transactions/kgpgexport.h new file mode 100644 index 00000000..f393bf45 --- /dev/null +++ b/kgpg/transactions/kgpgexport.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGEXPORT_H +#define KGPGEXPORT_H + +#include +#include + +#include + +#include "kgpgtransaction.h" + +class QProcess; + +/** + * @brief export one or more keys from keyring + * + * The exported keys can be written to a file or sent to standard input of another + * QProcess. + */ +class KGpgExport: public KGpgTransaction { + Q_OBJECT + + KGpgExport(); // = delete C++0x + Q_DISABLE_COPY(KGpgExport) +public: + /** + * @brief export keys to QProcess + * @param parent parent object + * @param ids ids to export + * @param outp process to write into + * @param options additional options to pass to GnuPG (e.g. export ascii armored) + * @param secret if secret key exporting is allowed + */ + KGpgExport(QObject *parent, const QStringList &ids, QProcess *outp, const QStringList &options = QStringList(), const bool secret = false); + + /** + * @brief export keys to KGpgTransaction + * @param parent parent object + * @param ids ids to export + * @param outt transaction to write into + * @param options additional options to pass to GnuPG (e.g. export ascii armored) + * @param secret if secret key exporting is allowed + */ + KGpgExport(QObject *parent, const QStringList &ids, KGpgTransaction *outt, const QStringList &options = QStringList(), const bool secret = false); + + /** + * @brief export keys to file + * @param parent parent object + * @param ids ids to export + * @param file filename to write into + * @param options additional options to pass to GnuPG (e.g. export ascii armored) + * @param secret if secret key exporting is allowed + */ + KGpgExport(QObject *parent, const QStringList &ids, const QString &file, const QStringList &options = QStringList(), const bool secret = false); + + /** + * @brief export keys to standard output + * @param parent parent object + * @param ids ids to export + * @param options additional options to pass to GnuPG (e.g. export ascii armored) + * @param secret if secret key exporting is allowed + * + * Only ascii-armored export is supported in standard output mode. If it is not + * already set in the given option it will be added automatically. + */ + KGpgExport(QObject *parent, const QStringList &ids, const QStringList &options = QStringList(), const bool secret = false); + + /** + * @brief destructor + */ + virtual ~KGpgExport(); + + /** + * @brief set key id to export + * @param id key fingerprint + */ + void setKeyId(const QString &id); + /** + * @brief set key ids to export + * @param ids key fingerprints + */ + void setKeyIds(const QStringList &ids); + /** + * @brief return the key ids to export + * @return list of key fingerprints + */ + const QStringList &getKeyIds() const; + /** + * @brief set the process the output is sent to + * @param outp process to send output to + */ + void setOutputProcess(QProcess *outp); + /** + * @brief set the transaction the output is sent to + * @param outd transaction to send output to + */ + void setOutputTransaction(KGpgTransaction *outt); + /** + * @brief set filename to send output to + * @param filename file to send output to + */ + void setOutputFile(const QString &filename); + /** + * @brief return the output filename currently set + * @return filename key will get written to + */ + const QString &getOutputFile() const; + /** + * @brief return the data read from standard output + * @return standard output data + */ + const QByteArray &getOutputData() const; + +protected: + virtual bool preStart(); + virtual bool nextLine(const QString &line); + +private: + QStringList m_keyids; + QProcess *m_outp; + QString m_outf; + QByteArray m_data; + + enum OutputMode { + ModeFile = 0, + ModeProcess = 1, + ModeStdout = 2, + ModeTransaction = 3 + }; + enum OutputMode m_outputmode; + + void procSetup(const QStringList &options, const bool secret); +}; + +#endif // KGPGEXPORT_H diff --git a/kgpg/transactions/kgpggeneratekey.cpp b/kgpg/transactions/kgpggeneratekey.cpp new file mode 100644 index 00000000..1e949d1d --- /dev/null +++ b/kgpg/transactions/kgpggeneratekey.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2008,2009,2010,2011,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpggeneratekey.h" + +#include "gpgproc.h" + +#include +#include +#include +#include + +KGpgGenerateKey::KGpgGenerateKey(QObject *parent, const QString &name, const QString &email, const QString &comment, + const KgpgCore::KgpgKeyAlgo &algorithm, const uint size, const unsigned int expire, + const char expireunit, const KgpgCore::KgpgSubKeyType capabilities) + : KGpgTransaction(parent), + m_name(name), + m_email(email), + m_comment(comment), + m_algorithm(algorithm), + m_capabilities(capabilities), + m_size(size), + m_expire(expire), + m_expireunit(expireunit) +{ + Q_ASSERT((expireunit == 'd') || (expireunit == 'w') || + (expireunit == 'm') || (expireunit == 'y')); + + addArgument(QLatin1String("--status-fd=1")); + addArgument(QLatin1String("--command-fd=0")); + addArgument(QLatin1String("--no-verbose")); + addArgument(QLatin1String("--gen-key")); + addArgument(QLatin1String("--batch")); + + getProcess()->setOutputChannelMode(KProcess::SeparateChannels); +} + +KGpgGenerateKey::~KGpgGenerateKey() +{ +} + +bool +KGpgGenerateKey::preStart() +{ + if (!m_email.isEmpty() && !KPIMUtils::isValidSimpleAddress(m_email)) { + setSuccess(TS_INVALID_EMAIL); + return false; + } + + m_fingerprint.clear(); + + setSuccess(TS_MSG_SEQUENCE); + + setDescription(i18n("Generating New Key for %1", m_name)); + + return true; +} + +void +KGpgGenerateKey::postStart() +{ + QByteArray keymessage = "Key-Type: "; + + switch (m_algorithm) { + case KgpgCore::ALGO_RSA: + keymessage.append("RSA"); + break; + case KgpgCore::ALGO_RSA_RSA: + keymessage.append("RSA\nSubkey-Type: RSA"); + break; + case KgpgCore::ALGO_DSA_ELGAMAL: + keymessage.append("DSA\nSubkey-Type: ELG-E"); + break; + default: + Q_ASSERT(m_algorithm == KgpgCore::ALGO_RSA); + return; + } + + const QByteArray keylen = QByteArray::number(m_size); + + keymessage.append("\nKey-Length: "); + keymessage.append(keylen); + keymessage.append("\nSubkey-Length: "); + keymessage.append(keylen); + keymessage.append("\nName-Real: "); + keymessage.append(m_name.toUtf8()); + + if (!m_email.isEmpty()) { + keymessage.append("\nName-Email: "); + keymessage.append(m_email.toAscii()); + } + + if (!m_comment.isEmpty()) { + keymessage.append("\nName-Comment: "); + keymessage.append(m_comment.toUtf8()); + } + + if (m_expire != 0) { + keymessage.append("\nExpire-Date: "); + keymessage.append(QByteArray::number(m_expire)); + keymessage.append(m_expireunit); + } + + if (m_capabilities) { + keymessage.append("\nKey-Usage: "); + QStringList usage; +#if 0 + // GnuPG always adds cert, but it does not allow this to be + // explicitly specified + if (m_capabilities & KgpgCore::SKT_CERTIFICATION) + usage << QLatin1String("cert"); +#endif + if (m_capabilities & KgpgCore::SKT_AUTHENTICATION) + usage << QLatin1String("auth"); + if (m_capabilities & KgpgCore::SKT_ENCRYPTION) + usage << QLatin1String("encrypt"); + if (m_capabilities & KgpgCore::SKT_SIGNATURE) + usage << QLatin1String("sign"); + keymessage.append(usage.join(QLatin1String(" ")).toAscii()); + } + + keymessage.append("\nPassphrase: "); + write(keymessage, false); + + QString passdlgmessage; + if (!m_email.isEmpty()) { + passdlgmessage = i18n("

Enter passphrase for %1 <%2>:
Passphrase should include non alphanumeric characters and random sequences.

", + m_name, m_email); + } else { + passdlgmessage = i18n("

Enter passphrase for %1:
Passphrase should include non alphanumeric characters and random sequences.

", + m_name); + } + + QApplication::restoreOverrideCursor(); + askNewPassphrase(passdlgmessage); +} + +bool +KGpgGenerateKey::nextLine(const QString &line) +{ + QString msg = i18n("Generating Key"); + + if (!line.startsWith(QLatin1String("[GNUPG:] "))) + return false; + + int result = false; + + if (line.contains(QLatin1String( "PROGRESS" ))) { + const QStringList parts = line.mid(18).split(QLatin1Char(' ')); + + if (parts.count() >= 4) { + const QString p0(parts.at(0)); + if (p0 == QLatin1String( "primegen" )) { + msg = i18n("Generating prime numbers"); + } else if (p0 == QLatin1String( "pk_dsa" )) { + msg = i18n("Generating DSA key"); + } else if (p0 == QLatin1String( "pk_elg" )) { + msg = i18n("Generating ElGamal key"); + } else if (p0 == QLatin1String( "need_entropy" )) { + msg = i18n("Waiting for entropy"); + + // This message is currenlty not displayed. Nevertheless it's + // included here so string freeze is not broken if it will be + // displayed later on. + QString msglong = i18n("The entropy pool ran empty. The key generation process is stalled until enough entropy is present. You can generate entropy e.g. by moving the mouse or typing at the keyboard. The easiest way is by using another application until the key generation continues."); + } + if (parts.at(3) != QLatin1String( "0" )) + emit infoProgress(parts.at(2).toUInt(), parts.at(3).toUInt()); + } + } else if (line.contains(QLatin1String( "GOOD_PASSPHRASE" ))) { + setSuccess(TS_MSG_SEQUENCE); + } else if (line.contains(QLatin1String( "KEY_CREATED" ))) { + m_fingerprint = line.right(40); + setSuccess(TS_OK); + result = true; + } else if (line.contains(QLatin1String( "NEED_PASSPHRASE" ))) { + setSuccess(TS_USER_ABORTED); + } else if (line.contains(QLatin1String( "GET_" ))) { + setSuccess(TS_MSG_SEQUENCE); + result = true; + } else if (line.contains(QLatin1String("KEY_NOT_CREATED"))) { + result = true; + } + + emit statusMessage(msg); + + return result; +} + +void +KGpgGenerateKey::finish() +{ + switch (getSuccess()) { + case TS_BAD_PASSPHRASE: + emit statusMessage(i18n("Bad passphrase. Cannot generate a new key pair.")); + break; + case TS_USER_ABORTED: + emit statusMessage(i18n("Aborted by the user. Cannot generate a new key pair.")); + break; + case TS_INVALID_EMAIL: + emit statusMessage(i18n("The email address is not valid. Cannot generate a new key pair.")); + break; + case TS_INVALID_NAME: + emit statusMessage(i18n("The name is not accepted by gpg. Cannot generate a new key pair.")); + break; + case TS_OK: + emit statusMessage(i18n("Key %1 generated", getFingerprint())); + break; + default: + { + QStringList errorLines; + + while (getProcess()->hasLineStandardError()) { + QByteArray b; + getProcess()->readLineStandardError(&b); + errorLines << QString::fromUtf8(b); + } + + m_errorOutput = errorLines.join(QLatin1String("\n")); + emit statusMessage(i18n("gpg process did not finish. Cannot generate a new key pair.")); + } + } +} + +void +KGpgGenerateKey::newPassphraseEntered() +{ + QApplication::setOverrideCursor(Qt::BusyCursor); + write("%commit"); +} + +QString +KGpgGenerateKey::getName() const +{ + return m_name; +} + +QString +KGpgGenerateKey::getEmail() const +{ + return m_email; +} + +QString +KGpgGenerateKey::getFingerprint() const +{ + return m_fingerprint; +} + +QString +KGpgGenerateKey::gpgErrorMessage() const +{ + return m_errorOutput; +} + +#include "kgpggeneratekey.moc" diff --git a/kgpg/transactions/kgpggeneratekey.h b/kgpg/transactions/kgpggeneratekey.h new file mode 100644 index 00000000..2deb3ccd --- /dev/null +++ b/kgpg/transactions/kgpggeneratekey.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2008,2009,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGGENERATEKEY_H +#define KGPGGENERATEKEY_H + +#include "kgpgtransaction.h" + +#include "core/kgpgkey.h" + +#include + +class QString; + +/** + * @brief generate a new key pair + */ +class KGpgGenerateKey: public KGpgTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgGenerateKey) + KGpgGenerateKey(); // = delete C++0x +public: + enum ts_generatekey { + TS_INVALID_NAME = TS_COMMON_END + 1 ///< the owners name is not accepted by GnuPG + }; + + /** + * @brief KGpgGenerateKey's constructor + * @param parent parent object + * @param name the name of the key, it is also the user's name. + * @param email email MUST be a valid email address or an empty string. + * @param comment is a comment, it can be an empty string + * @param algorithm this is the type of the key, RSA or DSA & ELGAMAL (\see Kgpg::KeyAlgo ). + * @param size this is the length of the key (1024, 2048, ...) + * @param expire defines the key expiry time together with \em expireunit, 0 for unlimited key lifetime + * @param expireunit is the unit of the number given as \em expire. Valid units are 'd', 'w', 'm' and 'y'. The unit is ignored if expire is 0. + * @param capabilities capabilities for the primary key + */ + KGpgGenerateKey(QObject *parent, const QString &name, const QString &email, const QString &comment, + const KgpgCore::KgpgKeyAlgo &algorithm, const uint size, const unsigned int expire = 0, + const char expireunit = 'd', const KgpgCore::KgpgSubKeyType capabilities = 0); + virtual ~KGpgGenerateKey(); + + QString getName() const; + QString getEmail() const; + + /** + * @brief return the fingerprint of the generated key + */ + QString getFingerprint() const; + + /** + * @brief get error output of GnuPG + * @return the messages GnuPG printed to standard error + * + * This will only return data after the done() signal has been emitted. + */ + QString gpgErrorMessage() const; + +protected: + virtual bool preStart(); + virtual void postStart(); + virtual bool nextLine(const QString &line); + virtual void finish(); + virtual void newPassphraseEntered(); + +private: + const QString m_name; + const QString m_email; + const QString m_comment; + const KgpgCore::KgpgKeyAlgo m_algorithm; + const KgpgCore::KgpgSubKeyType m_capabilities; + const unsigned int m_size; + const unsigned int m_expire; + const unsigned int m_expireunit; + QString m_fingerprint; + QString m_errorOutput; +}; + +#endif // KGPGGENERATEKEY_H diff --git a/kgpg/transactions/kgpggeneraterevoke.cpp b/kgpg/transactions/kgpggeneraterevoke.cpp new file mode 100644 index 00000000..4834930c --- /dev/null +++ b/kgpg/transactions/kgpggeneraterevoke.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2010,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpggeneraterevoke.h" + +#include +#include +#include + +KGpgGenerateRevoke::KGpgGenerateRevoke(QObject *parent, const QString &keyID, const KUrl &revokeUrl, const int reason, const QString &description) + : KGpgTransaction(parent), + m_keyid(keyID), + m_revUrl(revokeUrl), + m_reason(reason), + m_description(description) +{ + addArgument(QLatin1String( "--status-fd=1" )); + addArgument(QLatin1String( "--command-fd=0" )); + addArgument(QLatin1String( "--no-verbose" )); + + if (!revokeUrl.isEmpty()) { + addArgument(QLatin1String( "-o" )); + addArgument(revokeUrl.toLocalFile()); + } + addArgument(QLatin1String( "--gen-revoke" )); + addArgument(keyID); +} + +KGpgGenerateRevoke::~KGpgGenerateRevoke() +{ +} + +bool +KGpgGenerateRevoke::preStart() +{ + setSuccess(TS_MSG_SEQUENCE); + + setDescription(i18n("Generating Revocation Certificate for key %1", m_keyid)); + + return true; +} + +bool +KGpgGenerateRevoke::nextLine(const QString &line) +{ + if (!line.startsWith(QLatin1String("[GNUPG:] "))) { + m_output.append(line + QLatin1Char( '\n' )); + return false; + } + + if (line.contains(QLatin1String( "NEED_PASSPHRASE" ))) { + setSuccess(TS_USER_ABORTED); + } else if (line.contains(QLatin1String( "ask_revocation_reason.code" ))) { + write(QByteArray::number(m_reason)); + } else if (line.contains(QLatin1String( "ask_revocation_reason.text" ))) { + write(m_description.toUtf8()); + // GnuPG stops asking if we pass an empty line + m_description.clear(); + } else if (line.contains(QLatin1String( "GET_" ))) { + setSuccess(TS_MSG_SEQUENCE); + kDebug(2100) << line; + return true; + } + + return false; +} + +KGpgTransaction::ts_boolanswer +KGpgGenerateRevoke::boolQuestion(const QString& line) +{ + if (line == QLatin1String("gen_revoke.okay")) { + return BA_YES; + } else if (line == QLatin1String("ask_revocation_reason.okay")) { + return BA_YES; + } else { + return KGpgTransaction::boolQuestion(line); + } +} + +void +KGpgGenerateRevoke::finish() +{ + if (getSuccess() == TS_OK) { + if (!m_revUrl.isEmpty()) { + QFile of(m_revUrl.toLocalFile()); + if (of.open(QIODevice::ReadOnly)) { + m_output = QLatin1String( of.readAll() ); + of.close(); + } + } + emit revokeCertificate(m_output); + } +} + +bool +KGpgGenerateRevoke::passphraseReceived() +{ + setSuccess(TS_OK); + + return false; +} + +KGpgTransaction::ts_boolanswer +KGpgGenerateRevoke::confirmOverwrite(KUrl ¤tFile) +{ + currentFile = m_revUrl; + return BA_UNKNOWN; +} + +const QString & +KGpgGenerateRevoke::getOutput() const +{ + return m_output; +} + +#include "kgpggeneraterevoke.moc" diff --git a/kgpg/transactions/kgpggeneraterevoke.h b/kgpg/transactions/kgpggeneraterevoke.h new file mode 100644 index 00000000..af9c63ee --- /dev/null +++ b/kgpg/transactions/kgpggeneraterevoke.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGGENERATEREVOKE_H +#define KGPGGENERATEREVOKE_H + +#include "kgpgtransaction.h" + +#include "core/kgpgkey.h" + +#include +#include + +class QString; + +/** + * @brief generate a revokation certificate + */ +class KGpgGenerateRevoke: public KGpgTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgGenerateRevoke) + KGpgGenerateRevoke(); // = delete C++0x +public: + /** + * @brief KGpgGenerateRevoke's constructor + * @param parent parent object + * @param id key fingerprint to generate revokation certificate for + * @param revokeUrl place to store the certificate (may be empty) + * @param reason revokation reason + * @param description text description for revokation + */ + KGpgGenerateRevoke(QObject *parent, const QString &keyID, const KUrl &revokeUrl, const int reason, const QString &description); + virtual ~KGpgGenerateRevoke(); + + /** + * @brief returns the revokation certificate + */ + const QString &getOutput() const; + +signals: + void revokeCertificate(const QString &cert); + +protected: + virtual bool preStart(); + virtual bool nextLine(const QString &line); + virtual ts_boolanswer boolQuestion(const QString &line); + virtual void finish(); + virtual bool passphraseReceived(); + virtual ts_boolanswer confirmOverwrite (KUrl ¤tFile); + +private: + QString m_keyid; + KUrl m_revUrl; + int m_reason; + QString m_description; + QString m_output; +}; + +#endif // KGPGGENERATEREVOKE_H diff --git a/kgpg/transactions/kgpgimport.cpp b/kgpg/transactions/kgpgimport.cpp new file mode 100644 index 00000000..e1203e4c --- /dev/null +++ b/kgpg/transactions/kgpgimport.cpp @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2008,2009,2010,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgimport.h" + +#include "model/kgpgitemmodel.h" +#include "core/KGpgKeyNode.h" + +#include +#include + +KGpgImport::KGpgImport(QObject *parent, const QString &text) + : KGpgTextOrFileTransaction(parent, text, true) +{ +} + +KGpgImport::KGpgImport(QObject *parent, const KUrl::List &files) + : KGpgTextOrFileTransaction(parent, files, true) +{ +} + +KGpgImport::~KGpgImport() +{ +} + +QStringList +KGpgImport::command() const +{ + QStringList ret; + + ret << QLatin1String( "--import" ) << QLatin1String( "--allow-secret-key-import" ); + + return ret; +} + +QStringList +KGpgImport::getImportedKeys() const +{ + QStringList res; + + foreach (const QString &str, getMessages()) + if (str.startsWith(QLatin1String("[GNUPG:] IMPORTED "))) + res << str.mid(18); + + return res; +} + +QStringList +KGpgImport::getImportedIds(const QStringList &log, const int reason) +{ + QStringList res; + + foreach (const QString &str, log) { + if (!str.startsWith(QLatin1String("[GNUPG:] IMPORT_OK "))) + continue; + + QString tmpstr(str.mid(19).simplified()); + + int space = tmpstr.indexOf(QLatin1Char( ' ' )); + if (space <= 0) { + kDebug(2100) << __LINE__ << "invalid format:" << str; + continue; + } + + bool ok; + unsigned char code = tmpstr.left(space).toUInt(&ok); + if (!ok) { + kDebug(2100) << __LINE__ << "invalid format:" << str << space << tmpstr.left(space - 1); + continue; + } + + if ((reason == -1) || ((reason == 0) && (code == 0)) || ((reason & code) != 0)) + res << tmpstr.mid(space + 1); + } + + return res; +} + +QStringList +KGpgImport::getImportedIds(const int reason) const +{ + return getImportedIds(getMessages(), reason); +} + +QString +KGpgImport::getImportMessage() const +{ + return getImportMessage(getMessages()); +} + +QString +KGpgImport::getImportMessage(const QStringList &log) +{ +#define RESULT_PARTS 14 + unsigned long rcode[RESULT_PARTS]; + unsigned int i = 0; + int line = 0; + bool fine; + + memset(rcode, 0, sizeof(rcode)); + + foreach (const QString &str, log) { + line++; + if (!str.startsWith(QLatin1String("[GNUPG:] IMPORT_RES "))) + continue; + + const QStringList rstr(str.mid(20).simplified().split(QLatin1Char( ' ' ))); + + fine = (rstr.count() == RESULT_PARTS); + + i = 0; + while (fine && (i < RESULT_PARTS)) { + rcode[i] += rstr.at(i).toULong(&fine); + i++; + } + + if (!fine) + return i18n("The import result string has an unsupported format in line %1.
Please see the detailed log for more information.", line); + } + + fine = false; + i = 0; + while (!fine && (i < RESULT_PARTS)) { + fine = (rcode[i] != 0); + i++; + } + + if (!fine) + return i18n("No key imported.
Please see the detailed log for more information."); + + QString resultMessage(i18np("%1 key processed.", "%1 keys processed.", rcode[0])); + + if (rcode[1]) + resultMessage += i18np("
One key without ID.
", "
%1 keys without ID.
", rcode[1]); + if (rcode[2]) + resultMessage += i18np("
One key imported:
", "
%1 keys imported:
", rcode[2]); + if (rcode[3]) + resultMessage += i18np("
One RSA key imported.
", "
%1 RSA keys imported.
", rcode[3]); + if (rcode[4]) + resultMessage += i18np("
One key unchanged.
", "
%1 keys unchanged.
", rcode[4]); + if (rcode[5]) + resultMessage += i18np("
One user ID imported.
", "
%1 user IDs imported.
", rcode[5]); + if (rcode[6]) + resultMessage += i18np("
One subkey imported.
", "
%1 subkeys imported.
", rcode[6]); + if (rcode[7]) + resultMessage += i18np("
One signature imported.
", "
%1 signatures imported.
", rcode[7]); + if (rcode[8]) + resultMessage += i18np("
One revocation certificate imported.
", "
%1 revocation certificates imported.
", rcode[8]); + if (rcode[9]) + resultMessage += i18np("
One secret key processed.
", "
%1 secret keys processed.
", rcode[9]); + if (rcode[10]) + resultMessage += i18np("
One secret key imported.
", "
%1 secret keys imported.
", rcode[10]); + if (rcode[11]) + resultMessage += i18np("
One secret key unchanged.
", "
%1 secret keys unchanged.
", rcode[11]); + if (rcode[12]) + resultMessage += i18np("
One secret key not imported.
", "
%1 secret keys not imported.
", rcode[12]); + + if (rcode[9]) + resultMessage += i18n("
You have imported a secret key.
" + "Please note that imported secret keys are not trusted by default.
" + "To fully use this secret key for signing and encryption, you must edit the key (double click on it) and set its trust to Full or Ultimate.
"); + + return resultMessage; +} + +static QString +beautifyKeyList(const QStringList &keyIds, const KGpgItemModel *model) +{ + QString result; + + result.append(QLatin1String("\n")); + if (model == NULL) { + result.append(QLatin1String(" ") + keyIds.join(QLatin1String("\n "))); + } else { + foreach (const QString &changed, keyIds) { + const KGpgKeyNode *node = model->findKeyNode(changed); + QString line; + + if (node == NULL) { + line = changed; + } else { + if (node->getEmail().isEmpty()) + line = i18nc("ID: Name", "%1: %2", node->getFingerprint(), node->getName()); + else + line = i18nc("ID: Name ", "%1: %2 <%3>", node->getFingerprint(), node->getName(), node->getEmail()); + } + + result.append(QLatin1String(" ") + line + QLatin1String("\n")); + } + } + + return result; +} + +QString +KGpgImport::getDetailedImportMessage(const QStringList &log, const KGpgItemModel *model) +{ + QString result; + QMap resultcodes; + + foreach (const QString &keyresult, log) { + if (!keyresult.startsWith(QLatin1String("[GNUPG:] IMPORT_OK "))) + continue; + + QStringList rc(keyresult.mid(19).split(QLatin1Char( ' ' ))); + if (rc.count() < 2) { + kDebug(2100) << "unexpected syntax:" << keyresult; + continue; + } + + resultcodes[rc.at(1)] = rc.at(0).toUInt(); + } + + QMap::const_iterator iterend = resultcodes.constEnd(); + + for (unsigned int flag = 1; flag <= 16; flag <<= 1) { + QStringList thischanged; + + for (QMap::const_iterator iter = resultcodes.constBegin(); iter != iterend; ++iter) { + if (iter.value() & flag) + thischanged << iter.key(); + } + + if (thischanged.isEmpty()) + continue; + + switch (flag) { + case 1: + result.append(i18np("New Key", "New Keys", thischanged.count())); + break; + case 2: + result.append(i18np("Key with new User Id", "Keys with new User Ids", thischanged.count())); + break; + case 4: + result.append(i18np("Key with new Signatures", "Keys with new Signatures", thischanged.count())); + break; + case 8: + result.append(i18np("Key with new Subkeys", "Keys with new Subkeys", thischanged.count())); + break; + case 16: + result.append(i18np("New Private Key", "New Private Keys", thischanged.count())); + break; + default: + Q_ASSERT(flag == 1); + } + + result.append(beautifyKeyList(thischanged, model)); + result.append(QLatin1String("\n\n")); + } + + QStringList unchanged(resultcodes.keys(0)); + + if (unchanged.isEmpty()) { + // remove empty line at end + result.chop(1); + } else { + result.append(i18np("Unchanged Key", "Unchanged Keys", unchanged.count())); + result.append(beautifyKeyList(unchanged, model)); + result.append(QLatin1String("\n")); + } + + return result; +} + +int +KGpgImport::isKey(const QString &text, const bool incomplete) +{ + int markpos = text.indexOf(QLatin1String("-----BEGIN PGP PUBLIC KEY BLOCK-----")); + if (markpos >= 0) { + markpos = text.indexOf(QLatin1String("-----END PGP PUBLIC KEY BLOCK-----"), markpos); + return ((markpos > 0) || incomplete) ? 1 : 0; + } + + markpos = text.indexOf(QLatin1String("-----BEGIN PGP PRIVATE KEY BLOCK-----")); + if (markpos < 0) + return 0; + + markpos = text.indexOf(QLatin1String("-----END PGP PRIVATE KEY BLOCK-----"), markpos); + if ((markpos < 0) && !incomplete) + return 0; + + return 2; +} + +#include "kgpgimport.moc" diff --git a/kgpg/transactions/kgpgimport.h b/kgpg/transactions/kgpgimport.h new file mode 100644 index 00000000..e3f94c7e --- /dev/null +++ b/kgpg/transactions/kgpgimport.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2008,2009,2010 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGIMPORT_H +#define KGPGIMPORT_H + +#include +#include +#include +#include + +#include + +#include "kgpgtextorfiletransaction.h" + +class KGpgItemModel; + +/** + * @brief import one or more keys into the keyring + */ +class KGpgImport: public KGpgTextOrFileTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgImport) +public: + /** + * @brief import given text + * @param parent parent object + * @param text key text to import + */ + explicit KGpgImport(QObject *parent, const QString &text = QString()); + + /** + * @brief import key(s) from file(s) + * @param parent parent object + * @param files list of file locations to import from + */ + KGpgImport(QObject *parent, const KUrl::List &files); + + /** + * @brief destructor + */ + virtual ~KGpgImport(); + + /** + * @brief get the names and short fingerprints of the imported keys + * @return list of keys that were imported + */ + QStringList getImportedKeys() const; + + /** + * @brief get the full fingerprints of the imported keys + * @param log transaction log to scan + * @param reason key import reason + * @return list of ids that were imported + * + * You can filter the list of keys returned by the status of that key + * as reported by GnuPG. See doc/DETAILS of GnuPG for the meaning of + * the different flags. + * + * If reason is -1 (the default) all processed key ids are returned. + * If reason is 0 only keys of status 0 (unchanged) are returned. For + * any other value a key is returned if one of his status bits matched + * one of the bits in reason (i.e. (reason & status) != 0). + */ + static QStringList getImportedIds(const QStringList &log, const int reason = -1); + /** + * @brief get the full fingerprints of the imported keys + * + * This is an overloaded member. It calls the static function with the + * result log from this transaction object. + */ + QStringList getImportedIds(const int reason = -1) const; + + /** + * @brief get textual summary of the import events + * @return messages describing what was imported + * + * This is an overloaded member. It calls the static function with the + * result log from this transaction object. + */ + QString getImportMessage() const; + + /** + * @brief get textual summary of the import events + * @param log import log + * @return messages describing what was imported + * + * The log must contain a "IMPORT_RES" line. If this is not present + * the result string will contain an error message. + */ + static QString getImportMessage(const QStringList &log); + + /** + * @brief get detailed summary of import + * @param log import log + * @return message describing which keys changed and how + * + * The log must contain a "IMPORT_RES" line. If this is not present + * the result string will contain an error message. + */ + static QString getDetailedImportMessage(const QStringList &log, const KGpgItemModel *model = NULL); + + /** + * @brief check if the given text contains a private or public key + * @param text text to check + * @param incomplete assume text is only the beginning of the data + * @return if text contains a key or not + * @retval 0 no key found + * @retval 1 public key found + * @retval 2 private key found + */ + static int isKey(const QString &text, const bool incomplete = false); + +protected: + virtual QStringList command() const; +}; + +#endif // KGPGIMPORT_H diff --git a/kgpg/transactions/kgpgkeyservergettransaction.cpp b/kgpg/transactions/kgpgkeyservergettransaction.cpp new file mode 100644 index 00000000..acd54e1b --- /dev/null +++ b/kgpg/transactions/kgpgkeyservergettransaction.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +***************************************************************************/ + +#include "kgpgkeyservergettransaction.h" + +#include "gpgproc.h" + +KGpgKeyserverGetTransaction::KGpgKeyserverGetTransaction(QObject *parent, const QString &keyserver, const QStringList &keys, const bool withProgress, const QString &proxy) + : KGpgKeyserverTransaction(parent, keyserver, withProgress, proxy) +{ + m_cmdpos = addArgument(QString()); + setKeyIds(keys); +} + +KGpgKeyserverGetTransaction::~KGpgKeyserverGetTransaction() +{ +} + +bool +KGpgKeyserverGetTransaction::preStart() +{ + GPGProc *proc = getProcess(); + QStringList args(proc->program()); + + int num = args.count(); + while (num > m_cmdpos) + args.removeAt(--num); + + args << getGpgCommand() << m_keys; + + proc->setProgram(args); + + setSuccess(TS_MSG_SEQUENCE); + + return KGpgKeyserverTransaction::preStart(); +} + +bool +KGpgKeyserverGetTransaction::nextLine(const QString &line) +{ + m_log.append(line); + + setSuccess(TS_OK); + + return false; +} + +const QStringList & +KGpgKeyserverGetTransaction::getLog() const +{ + return m_log; +} + +void +KGpgKeyserverGetTransaction::setKeyIds(const QStringList &keys) +{ + m_keys = keys; +} + +KGpgReceiveKeys::KGpgReceiveKeys(QObject *parent, const QString &keyserver, const QStringList &keys, const bool withProgress, const QString &proxy) + : KGpgKeyserverGetTransaction(parent, keyserver, keys, withProgress, proxy) +{ +} + +KGpgReceiveKeys::~KGpgReceiveKeys() +{ +} + +QString +KGpgReceiveKeys::getGpgCommand() const +{ + return QLatin1String( "--recv-keys" ); +} + +KGpgRefreshKeys::KGpgRefreshKeys(QObject *parent, const QString &keyserver, const QStringList &keys, const bool withProgress, const QString &proxy) + : KGpgKeyserverGetTransaction(parent, keyserver, keys, withProgress, proxy) +{ +} + +KGpgRefreshKeys::~KGpgRefreshKeys() +{ +} + +QString +KGpgRefreshKeys::getGpgCommand() const +{ + return QLatin1String( "--refresh-keys" ); +} + +#include "kgpgkeyservergettransaction.moc" diff --git a/kgpg/transactions/kgpgkeyservergettransaction.h b/kgpg/transactions/kgpgkeyservergettransaction.h new file mode 100644 index 00000000..5fd01b37 --- /dev/null +++ b/kgpg/transactions/kgpgkeyservergettransaction.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGKEYSERVERGETTRANSACTION_H +#define KGPGKEYSERVERGETTRANSACTION_H + +#include +#include +#include + +#include "kgpgkeyservertransaction.h" + +/** + * @brief base class for transactions downloading from key servers + */ +class KGpgKeyserverGetTransaction: public KGpgKeyserverTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgKeyserverGetTransaction) + /** + * @brief forbidden + */ + KGpgKeyserverGetTransaction(); // = delete C++0x + +public: + /** + * @brief construct a new transaction for the given keyserver + * @param parent object that owns the transaction + * @param keyserver keyserver to work with + * @param keys the key ids to get + * @param withProgress show a progress window with cancel button + * @param proxy http proxy to use + * + * You should call this from the childrens constructor to set up + * everything properly + */ + KGpgKeyserverGetTransaction(QObject *parent, const QString &keyserver, const QStringList &keys, const bool withProgress = false, const QString &proxy = QString()); + virtual ~KGpgKeyserverGetTransaction(); + + void setKeyIds(const QStringList &keys); + + const QStringList &getLog() const; + +protected: + virtual QString getGpgCommand() const = 0; + virtual bool preStart(); + virtual bool nextLine(const QString &line); + +private: + int m_cmdpos; + QStringList m_keys; + QStringList m_log; +}; + +/** + * @brief class for downloading new keys from keyserver + */ +class KGpgReceiveKeys: public KGpgKeyserverGetTransaction { + Q_OBJECT + +public: + /** + * @brief construct a new transaction for the given keyserver + * @param parent object that owns the transaction + * @param keyserver keyserver to work with + * @param keys the key ids to get + * @param withProgress show a progress window with cancel button + * @param proxy http proxy to use + */ + KGpgReceiveKeys(QObject *parent, const QString &keyserver, const QStringList &keys, const bool withProgress = false, const QString &proxy = QString()); + virtual ~KGpgReceiveKeys(); + +protected: + virtual QString getGpgCommand() const; +}; + +/** + * @brief class for refreshing keys already in the keyring from keyserver + */ +class KGpgRefreshKeys: public KGpgKeyserverGetTransaction { + Q_OBJECT + +public: + /** + * @brief construct a new transaction for the given keyserver + * @param parent object that owns the transaction + * @param keyserver keyserver to work with + * @param keys the key ids to get + * @param withProgress show a progress window with cancel button + * @param proxy http proxy to use + */ + KGpgRefreshKeys(QObject *parent, const QString &keyserver, const QStringList &keys, const bool withProgress = false, const QString &proxy = QString()); + virtual ~KGpgRefreshKeys(); + +protected: + virtual QString getGpgCommand() const; +}; + +#endif // KGPGUIDTRANSACTION_H diff --git a/kgpg/transactions/kgpgkeyserversearchtransaction.cpp b/kgpg/transactions/kgpgkeyserversearchtransaction.cpp new file mode 100644 index 00000000..ad0cfa7e --- /dev/null +++ b/kgpg/transactions/kgpgkeyserversearchtransaction.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2010,2012 Rolf Eike Beer + */ + +/*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +***************************************************************************/ + +#include "kgpgkeyserversearchtransaction.h" + +KGpgKeyserverSearchTransaction::KGpgKeyserverSearchTransaction(QObject *parent, const QString &keyserver, const QString &pattern, const bool withProgress, const QString &proxy) + : KGpgKeyserverTransaction(parent, keyserver, withProgress, proxy), + m_pageEmpty(true) +{ + addArgument(QLatin1String( "--with-colons" )); + addArgument(QLatin1String( "--search-keys" )); + m_patternPos = addArgument(pattern); +} + +KGpgKeyserverSearchTransaction::~KGpgKeyserverSearchTransaction() +{ +} + +bool +KGpgKeyserverSearchTransaction::preStart() +{ + setSuccess(TS_MSG_SEQUENCE); + m_keyLines.clear(); + + return KGpgKeyserverTransaction::preStart(); +} + +bool +KGpgKeyserverSearchTransaction::nextLine(const QString &line) +{ + if (line.startsWith(QLatin1String("[GNUPG:] GET_LINE keysearch.prompt"))) { + if (!m_pageEmpty) { + write("n"); + m_pageEmpty = true; + } else { + return true; + } + } else if (!line.isEmpty() && !line.startsWith(QLatin1String("[GNUPG:] "))) { + m_pageEmpty = false; + if (line.startsWith(QLatin1String("pub:"))) { + if (!m_keyLines.isEmpty()) { + emit newKey(m_keyLines); + m_keyLines.clear(); + } + m_keyLines.append(line); + } else if (!m_keyLines.isEmpty() && (line != QLatin1String("\r"))) + m_keyLines.append(line); + } + + return false; +} + +void +KGpgKeyserverSearchTransaction::finish() +{ + if (!m_keyLines.isEmpty()) { + emit newKey(m_keyLines); + m_keyLines.clear(); + } +} + +void +KGpgKeyserverSearchTransaction::setPattern(const QString &pattern) +{ + replaceArgument(m_patternPos, pattern); +} + +#include "kgpgkeyserversearchtransaction.moc" diff --git a/kgpg/transactions/kgpgkeyserversearchtransaction.h b/kgpg/transactions/kgpgkeyserversearchtransaction.h new file mode 100644 index 00000000..0c41359e --- /dev/null +++ b/kgpg/transactions/kgpgkeyserversearchtransaction.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2010 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGKEYSERVERSEARCHTRANSACTION_H +#define KGPGKEYSERVERSEARCHTRANSACTION_H + +#include +#include +#include + +#include "kgpgkeyservertransaction.h" + +/** + * @brief base class for transactions downloading from key servers + */ +class KGpgKeyserverSearchTransaction: public KGpgKeyserverTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgKeyserverSearchTransaction) + /** + * @brief forbidden + */ + KGpgKeyserverSearchTransaction(); // = delete C++0x + +public: + /** + * @brief construct a new search on the given keyserver + * @param parent object that owns the transaction + * @param keyserver keyserver to work with + * @param pattern the search pattern + * @param withProgress show a progress window with cancel button + * @param proxy http proxy to use + * + * You should call this from the childrens constructor to set up + * everything properly + */ + KGpgKeyserverSearchTransaction(QObject *parent, const QString &keyserver, const QString &pattern, const bool withProgress = false, const QString &proxy = QString()); + virtual ~KGpgKeyserverSearchTransaction(); + + void setPattern(const QString &pattern); + +signals: + /** + * @brief emitted every time a new key is completed + * @param lines the lines that belong to that key + */ + void newKey(QStringList lines); + +protected: + virtual bool preStart(); + virtual bool nextLine(const QString &line); + /** + * @brief needed to submit the last search result + */ + virtual void finish(); + +private: + QStringList m_keyLines; ///< the lines belonging to one key + int m_patternPos; + bool m_pageEmpty; ///< if the current page of output is empty +}; + +#endif // KGPGUIDTRANSACTION_H diff --git a/kgpg/transactions/kgpgkeyservertransaction.cpp b/kgpg/transactions/kgpgkeyservertransaction.cpp new file mode 100644 index 00000000..aa68ae23 --- /dev/null +++ b/kgpg/transactions/kgpgkeyservertransaction.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgkeyservertransaction.h" + +#include "gpgproc.h" + +#include +#include + +KGpgKeyserverTransaction::KGpgKeyserverTransaction(QObject *parent, const QString &keyserver, const bool withProgress, const QString &proxy) + : KGpgTransaction(parent), + m_progress(NULL), + m_showprogress(false) +{ + addArgument(QLatin1String( "--status-fd=1" )); + addArgument(QLatin1String( "--command-fd=0" )); + addArgument(QLatin1String( "--keyserver-options" )); + m_proxypos = addArgument(QString()); + addArgument(QLatin1String( "--keyserver" )); + m_keyserverpos = addArgument(QString()); + + setKeyserver(keyserver); + setProxy(proxy); + + setProgressEnable(withProgress); +} + +KGpgKeyserverTransaction::~KGpgKeyserverTransaction() +{ + delete m_progress; +} + +void +KGpgKeyserverTransaction::setKeyserver(const QString &server) +{ + m_keyserver = server; + + replaceArgument(m_keyserverpos, server); +} + +void +KGpgKeyserverTransaction::setProxy(const QString &proxy) +{ + m_proxy = proxy; + + replaceArgument(m_proxypos, proxy); +} + +void +KGpgKeyserverTransaction::finish() +{ + if (m_progress != NULL) + m_progress->hide(); +} + +bool +KGpgKeyserverTransaction::preStart() +{ + if (m_showprogress) { + Q_ASSERT(m_progress != NULL); + m_progress->show(); + } + + return true; +} + +void +KGpgKeyserverTransaction::slotAbort() +{ + // no idea if this works on Windows, maybe we need ->kill() there + getProcess()->terminate(); + setSuccess(TS_USER_ABORTED); +} + +void +KGpgKeyserverTransaction::setProgressEnable(const bool b) +{ + m_showprogress = b; + + if (b && (m_progress == NULL)) { + m_progress = new KProgressDialog(qobject_cast(parent()), + i18n("Keyserver"), i18n("Connecting to the server...")); + + m_progress->hide(); + m_progress->setModal(true); + m_progress->progressBar()->setRange(0, 0); + + connect(m_progress, SIGNAL(cancelClicked()), SLOT(slotAbort())); + } +} + +#include "kgpgkeyservertransaction.moc" diff --git a/kgpg/transactions/kgpgkeyservertransaction.h b/kgpg/transactions/kgpgkeyservertransaction.h new file mode 100644 index 00000000..f202a340 --- /dev/null +++ b/kgpg/transactions/kgpgkeyservertransaction.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGKEYSERVERTRANSACTION_H +#define KGPGKEYSERVERTRANSACTION_H + +#include +#include + +#include "kgpgtransaction.h" + +class KProgressDialog; + +/** + * @brief base class for transactions involving key servers + */ +class KGpgKeyserverTransaction: public KGpgTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgKeyserverTransaction) + /** + * @brief forbidden + */ + KGpgKeyserverTransaction(); // = delete C++0x +protected: + /** + * @brief construct a new transaction for the given keyserver + * @param parent object that owns the transaction + * @param keyserver keyserver to work with + * @param withProgress show a progress window with cancel button + * @param proxy http proxy to use + * + * You should call this from the childrens constructor to set up + * everything properly. + * + * If the progress window is enabled and parent is an QWidget it is + * set as the parent of the progress window. + */ + KGpgKeyserverTransaction(QObject *parent, const QString &keyserver, const bool withProgress = false, const QString &proxy = QString()); +public: + virtual ~KGpgKeyserverTransaction(); + + /** + * @brief set the keyserver + * @param server keyserver to work with + */ + void setKeyserver(const QString &server); + /** + * @brief set the http proxy + * @param proxy http proxy to use + * + * If the server is set to an empty value no proxy is used. + */ + void setProxy(const QString &proxy); + /** + * @brief activate the progress window + * @param b new activation status + */ + void setProgressEnable(const bool b); + +protected: + virtual void finish(); + virtual bool preStart(); + +private slots: + /** + * @brief abort the current operation + */ + void slotAbort(); + +private: + QString m_keyserver; + int m_keyserverpos; + QString m_proxy; + int m_proxypos; + KProgressDialog *m_progress; + bool m_showprogress; +}; + +#endif // KGPGUIDTRANSACTION_H diff --git a/kgpg/transactions/kgpgprimaryuid.cpp b/kgpg/transactions/kgpgprimaryuid.cpp new file mode 100644 index 00000000..d8a54bf6 --- /dev/null +++ b/kgpg/transactions/kgpgprimaryuid.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2009,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgprimaryuid.h" + +#include "model/kgpgitemnode.h" + +KGpgPrimaryUid::KGpgPrimaryUid(QObject *parent, KGpgUidNode *uid) + : KGpgUidTransaction(parent, uid->getParentKeyNode()->getId(), uid->getId()) +{ + addArgument(QLatin1String("primary")); + addArgument(QLatin1String("save")); +} + +KGpgPrimaryUid::~KGpgPrimaryUid() +{ +} + +bool +KGpgPrimaryUid::nextLine(const QString &) +{ + setSuccess(TS_MSG_SEQUENCE); + return true; +} + +bool +KGpgPrimaryUid::passphraseReceived() +{ + setSuccess(TS_OK); + return KGpgTransaction::passphraseReceived(); +} + +#include "kgpgprimaryuid.moc" diff --git a/kgpg/transactions/kgpgprimaryuid.h b/kgpg/transactions/kgpgprimaryuid.h new file mode 100644 index 00000000..a9923aae --- /dev/null +++ b/kgpg/transactions/kgpgprimaryuid.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2009,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGPRIMARYUID_H +#define KGPGPRIMARYUID_H + +#include +#include + +#include "kgpguidtransaction.h" + +class KGpgUidNode; + +/** + * @brief transaction class to change the primary user id of a key + */ +class KGpgPrimaryUid: public KGpgUidTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgPrimaryUid) + KGpgPrimaryUid(); // = delete C++0x + +public: + /** + * @brief constructor + * @param parent parent object + * @param uid user id to become new primary one + */ + KGpgPrimaryUid(QObject *parent, KGpgUidNode *uid); + /** + * @brief destructor + */ + virtual ~KGpgPrimaryUid(); + +protected: + virtual bool nextLine(const QString &line); + virtual bool passphraseReceived(); + +private: + int m_fixargs; +}; + +#endif // KGPGPRIMARYUID_H diff --git a/kgpg/transactions/kgpgsendkeys.cpp b/kgpg/transactions/kgpgsendkeys.cpp new file mode 100644 index 00000000..4eddfe84 --- /dev/null +++ b/kgpg/transactions/kgpgsendkeys.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +***************************************************************************/ + +#include "kgpgsendkeys.h" + +#include "gpgproc.h" + +KGpgSendKeys::KGpgSendKeys(QObject *parent, const QString &keyserver, const QStringList &keys, const QString &attropt, const bool withProgress, const QString &proxy) + : KGpgKeyserverTransaction(parent, keyserver, withProgress, proxy) +{ + addArgument(QLatin1String( "--export-options" )); + m_attrpos = addArgument(QString()); + addArgument(QLatin1String( "--send-keys" )); + setAttributeOptions(attropt); + setKeyIds(keys); + + getProcess()->setOutputChannelMode(KProcess::MergedChannels); +} + +KGpgSendKeys::~KGpgSendKeys() +{ +} + +bool +KGpgSendKeys::preStart() +{ + GPGProc *proc = getProcess(); + QStringList args(proc->program()); + + int num = args.count(); + while (num > m_attrpos + 2) + args.removeAt(--num); + + args << m_keys; + + proc->setProgram(args); + + setSuccess(TS_MSG_SEQUENCE); + + return KGpgKeyserverTransaction::preStart(); +} + +bool +KGpgSendKeys::nextLine(const QString &line) +{ + m_log.append(line); + setSuccess(TS_OK); + + return false; +} + +const QStringList & +KGpgSendKeys::getLog() const +{ + return m_log; +} + +void +KGpgSendKeys::setKeyIds(const QStringList &keys) +{ + m_keys = keys; +} + +void +KGpgSendKeys::setAttributeOptions(const QString &opt) +{ + if (opt.isEmpty()) + m_attributeopt = QLatin1String( "no-export-attributes" ); + else + m_attributeopt = opt; + + replaceArgument(m_attrpos, m_attributeopt); +} + +#include "kgpgsendkeys.moc" diff --git a/kgpg/transactions/kgpgsendkeys.h b/kgpg/transactions/kgpgsendkeys.h new file mode 100644 index 00000000..6066f7be --- /dev/null +++ b/kgpg/transactions/kgpgsendkeys.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGSENDKEYS_H +#define KGPGSENDKEYS_H + +#include +#include +#include + +#include "kgpgkeyservertransaction.h" + +/** + * @brief class for uploading keys from the keyring to a keyserver + */ +class KGpgSendKeys: public KGpgKeyserverTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgSendKeys) + /** + * @brief forbidden + */ + KGpgSendKeys(); // = delete C++0x +public: + /** + * @brief construct a new transaction for the given keyserver + * @param parent object that own the transaction + * @param keyserver keyserver to work with + * @param keys the key ids to get + * @param attropt attributes to export (@see setAttributeOptions()) + * @param withProgress show a progress window with cancel button + * @param proxy http proxy to use + * + * You should call this from the childrens constructor to set up + * everything properly + */ + KGpgSendKeys(QObject *parent, const QString &keyserver, const QStringList &keys, const QString &attropt = QString(), const bool withProgress = false, const QString &proxy = QString()); + virtual ~KGpgSendKeys(); + + void setKeyIds(const QStringList &keys); + /** + * @brief set which attributes are exported + * @param opt GnuPG attribute options + * + * If opt is empty no attributes are exported. + */ + void setAttributeOptions(const QString &opt); + + const QStringList &getLog() const; + +protected: + virtual bool preStart(); + virtual bool nextLine(const QString &line); + +private: + int m_attrpos; + QString m_attributeopt; + QStringList m_keys; + QStringList m_log; +}; + +#endif // KGPGSENDKEYS_H diff --git a/kgpg/transactions/kgpgsignkey.cpp b/kgpg/transactions/kgpgsignkey.cpp new file mode 100644 index 00000000..41eb07b0 --- /dev/null +++ b/kgpg/transactions/kgpgsignkey.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgsignkey.h" + +#include "model/kgpgitemnode.h" + +KGpgSignKey::KGpgSignKey(QObject *parent, const QString &signer, KGpgKeyNode *key, const bool local, const carefulCheck checking) + : KGpgEditKeyTransaction(parent, key->getId(), QString(), false, false), + KGpgSignTransactionHelper(signer, !local, checking) +{ + insertArgument(1, QLatin1String( "-u" )); + insertArgument(2, signer); + m_signerPos = 2; + addArgumentRef(&m_signerPos); + + addArgument(QLatin1String("save")); + + setKey(key); + + setLocal(local); +} + +KGpgSignKey::~KGpgSignKey() +{ +} + +bool +KGpgSignKey::nextLine(const QString &line) +{ + switch (KGpgSignTransactionHelper::nextLine(line)) { + case KGpgSignTransactionHelper::handledFalse: + return false; + case KGpgSignTransactionHelper::handledTrue: + return true; + default: + // just to keep the compiler happy + Q_ASSERT(0); + case KGpgSignTransactionHelper::notHandled: + return KGpgEditKeyTransaction::nextLine(line); + } +} + +KGpgTransaction::ts_boolanswer +KGpgSignKey::boolQuestion(const QString& line) +{ + if (line.contains(QLatin1String("sign_all.okay"))) + return BA_YES; + + ts_boolanswer ret = KGpgSignTransactionHelper::boolQuestion(line); + + if (ret == BA_UNKNOWN) + ret = KGpgTransaction::boolQuestion(line); + + return ret; +} + +bool +KGpgSignKey::passphraseReceived() +{ + setSuccess(KGpgTransaction::TS_OK); + return true; +} + +KGpgTransaction * +KGpgSignKey::asTransaction() +{ + return this; +} + +void +KGpgSignKey::replaceCmd(const QString &cmd) +{ + replaceCommand(cmd); +} + +#include "kgpgsignkey.moc" diff --git a/kgpg/transactions/kgpgsignkey.h b/kgpg/transactions/kgpgsignkey.h new file mode 100644 index 00000000..0e3c229f --- /dev/null +++ b/kgpg/transactions/kgpgsignkey.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGSIGNKEY_H +#define KGPGSIGNKEY_H + +#include +#include + +#include "kgpgeditkeytransaction.h" +#include "kgpgsigntransactionhelper.h" + +class KGpgKeyNode; + +/** + * @brief transaction class to sign all user ids of a key + */ +class KGpgSignKey: public KGpgEditKeyTransaction, public KGpgSignTransactionHelper { + Q_OBJECT + + Q_DISABLE_COPY(KGpgSignKey) + KGpgSignKey(); // = delete C++0x + +public: + /** + * @brief constructor + * @param parent parent object + * @param signer id of the key to sign with + * @param key node to sign + * @param local if signature should be local (not exportable) + * @param checking how carefully the identity of the key owner was checked + * + * See setUid() for description of uid. + */ + KGpgSignKey(QObject *parent, const QString &signer, KGpgKeyNode *key, const bool local, const carefulCheck checking); + /** + * @brief destructor + */ + virtual ~KGpgSignKey(); + +protected: + virtual bool nextLine(const QString &line); + virtual ts_boolanswer boolQuestion(const QString &line); + virtual bool passphraseReceived(); + + virtual KGpgTransaction *asTransaction(); + virtual void replaceCmd(const QString &cmd); +}; + +#endif // KGPGSIGNKEY_H diff --git a/kgpg/transactions/kgpgsigntext.cpp b/kgpg/transactions/kgpgsigntext.cpp new file mode 100644 index 00000000..818019c7 --- /dev/null +++ b/kgpg/transactions/kgpgsigntext.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgsigntext.h" + +#include "kgpgsettings.h" + +KGpgSignText::KGpgSignText(QObject *parent, const QString &signId, const QString &text, const SignOptions &options, const QStringList &extraOptions) + : KGpgTextOrFileTransaction(parent, text), + m_fileIndex(-1), + m_options(options), + m_signId(signId), + m_extraOptions(extraOptions) +{ +} + +KGpgSignText::KGpgSignText(QObject *parent, const QString &signId, const KUrl::List &files, const SignOptions &options, const QStringList &extraOptions) + : KGpgTextOrFileTransaction(parent, files), + m_fileIndex(0), + m_options(options), + m_signId(signId), + m_extraOptions(extraOptions) +{ + /* GnuPG can only handle one file at a time when signing */ + Q_ASSERT(files.count() == 1); +} + +KGpgSignText::~KGpgSignText() +{ +} + +QStringList +KGpgSignText::command() const +{ + QStringList ret = m_extraOptions; + + const KUrl::List &files = getInputFiles(); + QString fileName; + + if (!files.isEmpty()) + fileName = files.first().path(); + + ret << QLatin1String("-u") << m_signId; + + if (m_options & AsciiArmored) { + if (fileName.isEmpty()) + ret << QLatin1String("--clearsign"); + else + ret << QLatin1String("--armor"); + } + if (KGpgSettings::pgpCompatibility()) + ret << QLatin1String("--pgp6"); + + if (!fileName.isEmpty()) { + if (m_options & DetachedSignature) + ret << QLatin1String("--detach-sign") << + QLatin1String("--output") << fileName + QLatin1String(".sig"); + } + + return ret; +} + +QStringList +KGpgSignText::signedText() const +{ + QStringList result; + + foreach (const QString &line, getMessages()) + if (!line.startsWith(QLatin1String("[GNUPG:] "))) { + result.append(line); + } + + return result; +} + +#include "kgpgsigntext.moc" diff --git a/kgpg/transactions/kgpgsigntext.h b/kgpg/transactions/kgpgsigntext.h new file mode 100644 index 00000000..1b66097a --- /dev/null +++ b/kgpg/transactions/kgpgsigntext.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGSIGNTEXT_H +#define KGPGSIGNTEXT_H + +#include "kgpgtextorfiletransaction.h" + +#include +#include +#include +#include + +class QProcess; + +/** + * @brief sign the given text or files + */ +class KGpgSignText: public KGpgTextOrFileTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgSignText) + KGpgSignText(); // = delete C++0x +public: + enum SignOption { + DefaultSignature = 0, ///< use whatever GnuPGs defaults are + AsciiArmored = 0x1, ///< output the data as printable ASCII as opposed to binary data + DetachedSignature = 0x2, ///< save the signature in a separate file + }; + Q_DECLARE_FLAGS(SignOptions, SignOption); + + /** + * @brief sign given text + * @param parent parent object + * @param signId the key to use for signing + * @param text text to sign + * @param options signing options + */ + KGpgSignText(QObject *parent, const QString &signId, const QString &text = QString(), const SignOptions &options = AsciiArmored, const QStringList &extraOptions = QStringList()); + + /** + * @brief sign file + * @param parent parent object + * @param signId the key to use for signing + * @param files list of file locations to sign (must only be 1 file) + * @param options signing options + * + * @warning GnuPG can currently handle only one file per invocation for + * signing, so files may only contain one single file. + */ + KGpgSignText(QObject *parent, const QString &signId, const KUrl::List &files, const SignOptions &options = DefaultSignature, const QStringList &extraOptions = QStringList()); + + /** + * @brief destructor + */ + virtual ~KGpgSignText(); + + /** + * @brief get signing result + * @return signed text + */ + QStringList signedText() const; + +protected: + virtual QStringList command() const; + +private: + int m_fileIndex; + const SignOptions m_options; + const QString m_signId; + QStringList m_extraOptions; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(KGpgSignText::SignOptions); + +#endif // KGPGSIGNTEXT_H diff --git a/kgpg/transactions/kgpgsigntransactionhelper.cpp b/kgpg/transactions/kgpgsigntransactionhelper.cpp new file mode 100644 index 00000000..efdff3d2 --- /dev/null +++ b/kgpg/transactions/kgpgsigntransactionhelper.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2009,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgsigntransactionhelper.h" + +#include "kgpgtransaction.h" +#include "model/kgpgitemnode.h" + +KGpgSignTransactionHelper::KGpgSignTransactionHelper(const QString &signer, const bool local, const carefulCheck checking) + : m_node(NULL), + m_signer(signer), + m_local(local), + m_checking(checking), + m_signerPos(-1) +{ +} + +KGpgSignTransactionHelper::~KGpgSignTransactionHelper() +{ +} + +void +KGpgSignTransactionHelper::setKey(const KGpgKeyNode *node) +{ + m_node = node; +} + +const KGpgKeyNode * +KGpgSignTransactionHelper::getKey(void) const +{ + return m_node; +} + +KGpgSignTransactionHelper::lineParseResults +KGpgSignTransactionHelper::nextLine(const QString &line) +{ + if (!line.startsWith(QLatin1String("[GNUPG:]"))) { + return notHandled; + } + + if (line.contains(QLatin1String( "ALREADY_SIGNED" ))) { + asTransaction()->setSuccess(TS_ALREADY_SIGNED); + return handledFalse; + } else if (line.contains(QLatin1String( "GOOD_PASSPHRASE" ))) { + asTransaction()->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); + return handledFalse; + } else if (line.contains(QLatin1String( "sign_uid.expire" ))) { + asTransaction()->write("Never"); + return handledFalse; + } else if (line.contains(QLatin1String( "sign_uid.class" ))) { + asTransaction()->write(m_checking); + return handledFalse; + } else { + return notHandled; + } +} + +KGpgTransaction::ts_boolanswer +KGpgSignTransactionHelper::boolQuestion(const QString& line) +{ + if (line == QLatin1String("sign_uid.okay")) { + return KGpgTransaction::BA_YES; + } else if (line == QLatin1String("keyedit.save.okay")) { + KGpgTransaction *ta = asTransaction(); + + switch (ta->getSuccess()) { + case TS_ALREADY_SIGNED: + case KGpgTransaction::TS_USER_ABORTED: + break; + default: + asTransaction()->setSuccess(KGpgTransaction::TS_OK); + } + + return KGpgTransaction::BA_YES; + } else { + return KGpgTransaction::BA_UNKNOWN; + } +} + +void +KGpgSignTransactionHelper::setLocal(const bool local) +{ + if (local == m_local) + return; + + m_local = local; + if (local) + replaceCmd(QLatin1String( "lsign" )); + else + replaceCmd(QLatin1String( "sign" )); +} + +bool +KGpgSignTransactionHelper::getLocal(void) const +{ + return m_local; +} + +void +KGpgSignTransactionHelper::setChecking(const carefulCheck level) +{ + m_checking = level; +} + +KGpgSignTransactionHelper::carefulCheck +KGpgSignTransactionHelper::getChecking(void) const +{ + return m_checking; +} + +void +KGpgSignTransactionHelper::setSigner(const QString &signer) +{ + m_signer = signer; + asTransaction()->replaceArgument(m_signerPos, signer); +} + + +QString +KGpgSignTransactionHelper::getSigner(void) const +{ + return m_signer; +} + +void +KGpgSignTransactionHelper::setSecringFile(const QString &filename) +{ + QStringList secringargs(QLatin1String( "--secret-keyring" )); + secringargs << filename; + + asTransaction()->insertArguments(1, secringargs); +} + +#include "kgpgsigntransactionhelper.moc" diff --git a/kgpg/transactions/kgpgsigntransactionhelper.h b/kgpg/transactions/kgpgsigntransactionhelper.h new file mode 100644 index 00000000..e215b20c --- /dev/null +++ b/kgpg/transactions/kgpgsigntransactionhelper.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGSIGNTRANSACTIONHELPER_H +#define KGPGSIGNTRANSACTIONHELPER_H + +#include "kgpgtransaction.h" + +class KGpgKeyNode; +class QString; + +/** + * @brief helper class for key signing transactions + */ +class KGpgSignTransactionHelper { + Q_DISABLE_COPY(KGpgSignTransactionHelper) + KGpgSignTransactionHelper(); // = delete C++0x +public: + /** + * @brief the outcomes of nextLine() + */ + enum lineParseResults { + handledFalse, ///< the line was parsed successfully and transaction can continue + handledTrue, ///< the line was parsed successfully and the transaction shoult be shut down + notHandled ///< the line was not handled + }; + + enum carefulCheck { + noAnswer = 0, + notChecked = 1, + normalChecking = 2, + carefulChecking = 3 + }; + + enum ts_signuid { + TS_ALREADY_SIGNED = KGpgTransaction::TS_COMMON_END + 1 ///< user id is alredy signed by given key + }; + + /** + * @brief destructor + */ + virtual ~KGpgSignTransactionHelper(); + +protected: + /** + * @brief constructor + * @param signer id of the key to sign with + * @param local if signature should be local (not exportable) + * @param checking how carefully the identity of the key owner was checked + */ + KGpgSignTransactionHelper(const QString &signer, const bool local, const carefulCheck checking); + /** + * @brief handle signing commands from GnuPG + * @param line input to parse + * + * This will handle the GnuPG commands specific to signing. + */ + lineParseResults nextLine(const QString &line); + KGpgTransaction::ts_boolanswer boolQuestion(const QString &line); + +public: + /** + * @brief set key node this transaction is using + * @param node new key node + */ + void setKey(const KGpgKeyNode *node); + + /** + * @brief get the key node this transaction is using + */ + const KGpgKeyNode *getKey(void) const; + + /** + * @brief set if the signature should be local (not exportable) + * @param local flag if local signature should be applied + */ + void setLocal(const bool local); + + /** + * @brief check if local signing is requested + */ + bool getLocal(void) const; + + /** + * @brief set the level how carefully the identity was checked + * @param level level to set + */ + void setChecking(const carefulCheck level); + + /** + * @brief check if local signing is requested + */ + carefulCheck getChecking(void) const; + + /** + * @brief set which private key is used to sign + * @param signer id of private key to use + */ + void setSigner(const QString &signer); + + /** + * @brief get key id which is used to sign + */ + QString getSigner(void) const; + + /** + * @brief add a secret keyring file + * + * This allows to specify an additional file where secret keys are + * stored to be used by this operation. This is especially useful + * if a different GnuPG home directory is set but the original keys + * should be used for signing. + */ + void setSecringFile(const QString &filename); + +private: + const KGpgKeyNode *m_node; + QString m_signer; + bool m_local; + carefulCheck m_checking; + +protected: + int m_signerPos; ///< position of the signer argument in GnuPG command line + + /** + * @brief returns the transaction object to use + * + * This should really be static_cast<>(this) as you should + * only use this class as one of two anchestors of a transaction. + */ + virtual KGpgTransaction *asTransaction() = 0; + /** + * @brief replaces the command passed to GnuPG + * @param cmd new command to use + */ + virtual void replaceCmd(const QString &cmd) = 0; +}; + +#endif // KGPGSIGNTRANSACTIONHELPER_H diff --git a/kgpg/transactions/kgpgsignuid.cpp b/kgpg/transactions/kgpgsignuid.cpp new file mode 100644 index 00000000..6084c309 --- /dev/null +++ b/kgpg/transactions/kgpgsignuid.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgsignuid.h" + +#include "model/kgpgitemnode.h" + +KGpgSignUid::KGpgSignUid(QObject *parent, const QString &signer, const KGpgSignableNode *uid, const bool local, const carefulCheck checking) + : KGpgUidTransaction(parent), + KGpgSignTransactionHelper(signer, !local, checking) +{ + insertArgument(1, QLatin1String( "-u" )); + insertArgument(2, signer); + m_signerPos = 2; + addArgumentRef(&m_signerPos); + + m_cmdPos = addArgument(QString()); + addArgumentRef(&m_cmdPos); + + addArgument(QLatin1String("save")); + + setUid(uid); + + setLocal(local); +} + +KGpgSignUid::~KGpgSignUid() +{ +} + +void +KGpgSignUid::setUid(const KGpgSignableNode *uid) +{ + switch (uid->getType()) { + case ITYPE_PUBLIC: + case ITYPE_PAIR: + KGpgUidTransaction::setUid(1); + setKey(uid->toKeyNode()); + break; + case ITYPE_UAT: + case ITYPE_UID: + KGpgUidTransaction::setUid(uid->getId()); + setKey(uid->getParentKeyNode()->toKeyNode()); + break; + default: + Q_ASSERT(0); + } + + setKeyId(getKey()->getId()); +} + +bool +KGpgSignUid::nextLine(const QString &line) +{ + switch (KGpgSignTransactionHelper::nextLine(line)) { + case KGpgSignTransactionHelper::handledFalse: + return false; + case KGpgSignTransactionHelper::handledTrue: + return true; + default: + Q_ASSERT(0); + case KGpgSignTransactionHelper::notHandled: + return standardCommands(line); + } +} + +KGpgTransaction::ts_boolanswer +KGpgSignUid::boolQuestion(const QString& line) +{ + ts_boolanswer ret = KGpgSignTransactionHelper::boolQuestion(line); + + if (ret == BA_UNKNOWN) + ret = KGpgTransaction::boolQuestion(line); + + return ret; +} + +bool +KGpgSignUid::passphraseReceived() +{ + setSuccess(KGpgTransaction::TS_OK); + return true; +} + +KGpgTransaction * +KGpgSignUid::asTransaction() +{ + return this; +} + +void +KGpgSignUid::replaceCmd(const QString &cmd) +{ + replaceArgument(m_cmdPos, cmd); +} + +#include "kgpgsignuid.moc" diff --git a/kgpg/transactions/kgpgsignuid.h b/kgpg/transactions/kgpgsignuid.h new file mode 100644 index 00000000..635d3823 --- /dev/null +++ b/kgpg/transactions/kgpgsignuid.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGSIGNUID_H +#define KGPGSIGNUID_H + +#include + +#include "kgpguidtransaction.h" +#include "kgpgsigntransactionhelper.h" + +class KGpgSignableNode; +class QString; + +/** + * @brief transaction class to sign a single user id of a key + */ +class KGpgSignUid: public KGpgUidTransaction, public KGpgSignTransactionHelper { + Q_OBJECT + + Q_DISABLE_COPY(KGpgSignUid) + KGpgSignUid(); // = delete C++0x + +public: + /** + * @brief constructor + * @param parent parent object + * @param signer id of the key to sign with + * @param uid node to sign + * @param local if signature should be local (not exportable) + * @param checking how carefully the identity of the key owner was checked + * + * See setUid() for description of uid. + */ + KGpgSignUid(QObject *parent, const QString &signer, const KGpgSignableNode *uid, const bool local, const carefulCheck checking); + /** + * @brief destructor + */ + virtual ~KGpgSignUid(); + + /** + * @brief set node to sign + * @param uid node to sign + * + * If uid is a KGpgKeyNode only the primary id of that key is + * signed. If you want to sign all user ids at once use + * KGpgSignKey instead. Legal values for uid are also KGpgUidNode + * and KGpgUatNode. + */ + void setUid(const KGpgSignableNode *uid); + +protected: + virtual bool nextLine(const QString &line); + virtual ts_boolanswer boolQuestion(const QString &line); + virtual bool passphraseReceived(); + + virtual KGpgTransaction *asTransaction(); + virtual void replaceCmd(const QString &cmd); + +private: + int m_cmdPos; ///< position of the command in GnuPG command line +}; + +#endif // KGPGSIGNUID_H diff --git a/kgpg/transactions/kgpgtextorfiletransaction.cpp b/kgpg/transactions/kgpgtextorfiletransaction.cpp new file mode 100644 index 00000000..374b69c1 --- /dev/null +++ b/kgpg/transactions/kgpgtextorfiletransaction.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2008,2009,2010,2011,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgtextorfiletransaction.h" + +#include "gpgproc.h" + +#include +#include +#include +#include + +KGpgTextOrFileTransaction::KGpgTextOrFileTransaction(QObject *parent, const QString &text, const bool allowChaining) + : KGpgTransaction(parent, allowChaining) +{ + setText(text); +} + +KGpgTextOrFileTransaction::KGpgTextOrFileTransaction(QObject *parent, const KUrl::List &files, const bool allowChaining) + : KGpgTransaction(parent, allowChaining) +{ + setUrls(files); +} + +KGpgTextOrFileTransaction::~KGpgTextOrFileTransaction() +{ + cleanUrls(); +} + +void +KGpgTextOrFileTransaction::setText(const QString &text) +{ + m_text = text; + cleanUrls(); + + int begin = text.indexOf(QRegExp(QLatin1String("^(.*\n)?-----BEGIN PGP [A-Z ]*-----\r?\n"))); + if (begin < 0) + return; + + // find the end of the BEGIN PGP ... line + static const QChar lf = QLatin1Char('\n'); + begin = text.indexOf(lf, begin); + Q_ASSERT(begin > 0); + + // now loop until either an empty line is found (end of header) or + // a line beginning with Charset is found. If the latter, use the + // charset found there as hint for the following operation + int nextlf; + begin++; + while ((nextlf = text.indexOf(lf, begin)) > 0) { + static const QChar cr = QLatin1Char('\r'); + if ((nextlf == begin) || ((nextlf == begin + 1) && (text[begin] == cr))) + break; + + const QString charset = QLatin1String("Charset: "); + if (text.mid(begin, charset.length()) == charset) { + QString cs = text.mid(begin + charset.length(), nextlf - begin - charset.length()); + if (!getProcess()->setCodec(cs.toAscii())) + kDebug(2100) << "unsupported charset found in header" << cs; + break; + } + begin = nextlf + 1; + } + + +} + +void +KGpgTextOrFileTransaction::setUrls(const KUrl::List &files) +{ + m_text.clear(); + m_inpfiles = files; +} + +bool +KGpgTextOrFileTransaction::preStart() +{ + QStringList locfiles; + + foreach (const KUrl &url, m_inpfiles) { + if (url.isLocalFile()) { + locfiles.append(url.toLocalFile()); + } else { + QString tmpfile; + + if (KIO::NetAccess::download(url, tmpfile, 0)) { + m_tempfiles.append(tmpfile); + } else { + m_messages.append(KIO::NetAccess::lastErrorString()); + cleanUrls(); + setSuccess(TS_KIO_FAILED); + return false; + } + } + } + + if (locfiles.isEmpty() && m_tempfiles.isEmpty() && m_text.isEmpty() && !hasInputTransaction()) { + setSuccess(TS_MSG_SEQUENCE); + return false; + } + + QStringList args(QLatin1String("--status-fd=1")); + + args << command(); + // if the input is not stdin set command-fd so GnuPG + // can ask if e.g. the file already exists + if (!locfiles.isEmpty() || !m_tempfiles.isEmpty()) { + args << QLatin1String("--command-fd=0"); + m_closeInput = false; + } else { + m_closeInput = !args.contains(QLatin1String("--command-fd=0")); + } + if (locfiles.count() + m_tempfiles.count() > 1) + args << QLatin1String("--multifile"); + args << locfiles << m_tempfiles; + addArguments(args); + + return true; +} + +void +KGpgTextOrFileTransaction::postStart() +{ + if (!m_text.isEmpty()){ + GPGProc *proc = getProcess(); + proc->write(m_text.toUtf8()); + if (m_closeInput) + proc->closeWriteChannel(); + } +} + +bool +KGpgTextOrFileTransaction::nextLine(const QString &line) +{ + m_messages.append(line); + + return false; +} + +void +KGpgTextOrFileTransaction::finish() +{ + if (getProcess()->exitCode() != 0) { + setSuccess(TS_MSG_SEQUENCE); + } +} + +const QStringList & +KGpgTextOrFileTransaction::getMessages() const +{ + return m_messages; +} + +void +KGpgTextOrFileTransaction::cleanUrls() +{ + foreach (const QString &u, m_tempfiles) + KIO::NetAccess::removeTempFile(u); + + m_tempfiles.clear(); + m_locfiles.clear(); + m_inpfiles.clear(); +} + +const KUrl::List & +KGpgTextOrFileTransaction::getInputFiles() const +{ + return m_inpfiles; +} + +#include "kgpgtextorfiletransaction.moc" diff --git a/kgpg/transactions/kgpgtextorfiletransaction.h b/kgpg/transactions/kgpgtextorfiletransaction.h new file mode 100644 index 00000000..14b19b94 --- /dev/null +++ b/kgpg/transactions/kgpgtextorfiletransaction.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2008,2009,2010 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGTEXTORFILETRANSACTION_H +#define KGPGTEXTORFILETRANSACTION_H + +#include +#include +#include +#include + +#include + +#include "kgpgtransaction.h" + +/** + * @brief feed a text or file through gpg + */ +class KGpgTextOrFileTransaction: public KGpgTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgTextOrFileTransaction) + +public: + /** + * @brief additional status codes for KGpgImport + */ + enum ts_import { + TS_KIO_FAILED = TS_COMMON_END + 1 ///< download of remote file failed + }; + +protected: + /** + * @brief work with given text + * @param parent parent object + * @param text text to work with + */ + explicit KGpgTextOrFileTransaction(QObject *parent = 0, const QString &text = QString(), const bool allowChaining = false); + + /** + * @brief work with given file(s) + * @param parent parent object + * @param keys list of file locations to work with + */ + KGpgTextOrFileTransaction(QObject *parent, const KUrl::List &files, const bool allowChaining = false); + +public: + /** + * @brief destructor + */ + virtual ~KGpgTextOrFileTransaction(); + + /** + * @brief set text to work with + * @param text text to work with + */ + void setText(const QString &text); + /** + * @brief set file locations to work with + * @param keys list of file locations to work with + */ + void setUrls(const KUrl::List &files); + + /** + * @brief get gpg info message + * @return the raw messages from gpg during the operation + */ + const QStringList &getMessages() const; + +protected: + /** + * @brief construct the command line of the process + */ + virtual bool preStart(); + virtual bool nextLine(const QString &line); + /** + * @brief implement special handling for GnuPG return codes + */ + virtual void finish(); + + virtual QStringList command() const = 0; + + const KUrl::List &getInputFiles() const; + +private: + QStringList m_tempfiles; + QStringList m_locfiles; + KUrl::List m_inpfiles; + QString m_text; + QStringList m_messages; + bool m_closeInput; ///< if input channel of GnuPG should be closed after m_text is written + + void cleanUrls(); + +private slots: + void postStart(); +}; + +#endif // KGPGTEXTORFILETRANSACTION_H diff --git a/kgpg/transactions/kgpgtransaction.cpp b/kgpg/transactions/kgpgtransaction.cpp new file mode 100644 index 00000000..7b237b06 --- /dev/null +++ b/kgpg/transactions/kgpgtransaction.cpp @@ -0,0 +1,746 @@ +/* + * Copyright (C) 2008,2009,2010,2011,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgtransaction.h" + +#include "gpgproc.h" +#include "kgpginterface.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class KGpgTransactionPrivate { +public: + KGpgTransactionPrivate(KGpgTransaction *parent, bool allowChaining); + ~KGpgTransactionPrivate(); + + KGpgTransaction *m_parent; + GPGProc *m_process; + KGpgTransaction *m_inputTransaction; + KNewPasswordDialog *m_newPasswordDialog; + KPasswordDialog *m_passwordDialog; + int m_success; + int m_tries; + QString m_description; + bool m_chainingAllowed; + + QStringList m_idhints; + + KUrl m_overwriteUrl; ///< the file to overwrite or it's new name + + void slotReadReady(); + void slotProcessExited(); + void slotProcessStarted(); + void slotInputTransactionDone(int result); + void slotPassphraseEntered(const QString &passphrase); + /** + * @brief a slot to handle the case that the passphrase entry was aborted by the user + * + * This will delete the sender as well as do the internal passphrase aborted handling. + */ + void slotPassphraseAborted(); + /** + * @brief do the internal passphrase aborted handling + */ + void handlePassphraseAborted(); + + QList m_argRefs; + bool m_inputProcessDone; + int m_inputProcessResult; + bool m_ownProcessFinished; + + /** + * terminate GnuPG session + */ + void sendQuit(void); + + void write(const QByteArray &a); + + static const QStringList &hintNames(void); + +private: + void processDone(); + + unsigned int m_quitTries; ///< how many times we tried to quit + QStringList m_quitLines; ///< what we received after we tried to quit +}; + +KGpgTransactionPrivate::KGpgTransactionPrivate(KGpgTransaction *parent, bool allowChaining) + : m_parent(parent), + m_process(new GPGProc()), + m_inputTransaction(NULL), + m_newPasswordDialog(NULL), + m_passwordDialog(NULL), + m_success(KGpgTransaction::TS_OK), + m_tries(3), + m_chainingAllowed(allowChaining), + m_inputProcessDone(false), + m_inputProcessResult(KGpgTransaction::TS_OK), + m_ownProcessFinished(false), + m_quitTries(0) +{ +} + +KGpgTransactionPrivate::~KGpgTransactionPrivate() +{ + if (m_newPasswordDialog) { + m_newPasswordDialog->close(); + m_newPasswordDialog->deleteLater(); + } + if (m_process->state() == QProcess::Running) { + m_process->closeWriteChannel(); + m_process->terminate(); + } + delete m_inputTransaction; + delete m_process; +} + +KGpgTransaction::KGpgTransaction(QObject *parent, const bool allowChaining) + : QObject(parent), + d(new KGpgTransactionPrivate(this, allowChaining)) +{ + connect(d->m_process, SIGNAL(readReady()), SLOT(slotReadReady())); + connect(d->m_process, SIGNAL(processExited()), SLOT(slotProcessExited())); + connect(d->m_process, SIGNAL(started()), SLOT(slotProcessStarted())); +} + +KGpgTransaction::~KGpgTransaction() +{ + delete d; +} + +void +KGpgTransactionPrivate::slotReadReady() +{ + QString line; + QWeakPointer process(m_process); + QWeakPointer par(m_parent); + + while (!process.isNull() && (m_process->readln(line, true) >= 0)) { + if (m_quitTries) + m_quitLines << line; +#ifdef KGPG_DEBUG_TRANSACTIONS + kDebug(2100) << m_parent << line; +#endif /* KGPG_DEBUG_TRANSACTIONS */ + + static const QString getBool = QLatin1String("[GNUPG:] GET_BOOL "); + + if (line.startsWith(QLatin1String("[GNUPG:] USERID_HINT "))) { + m_parent->addIdHint(line); + } else if (line.startsWith(QLatin1String("[GNUPG:] BAD_PASSPHRASE "))) { + // the MISSING_PASSPHRASE line comes first, in that case ignore a + // following BAD_PASSPHRASE + if (m_success != KGpgTransaction::TS_USER_ABORTED) + m_success = KGpgTransaction::TS_BAD_PASSPHRASE; + } else if (line.startsWith(QLatin1String("[GNUPG:] GET_HIDDEN passphrase.enter"))) { + const bool goOn = m_parent->passphraseRequested(); + + // Check if the object was deleted while waiting for the result + if (!goOn || par.isNull()) + return; + + } else if (line.startsWith(QLatin1String("[GNUPG:] GOOD_PASSPHRASE"))) { + emit m_parent->statusMessage(i18n("Got Passphrase")); + + if (m_passwordDialog != NULL) { + m_passwordDialog->close(); + m_passwordDialog->deleteLater(); + m_passwordDialog = NULL; + } + + if (m_parent->passphraseReceived()) { + // signal GnuPG that there will be no further input and it can + // begin sending output. + m_process->closeWriteChannel(); + } + + } else if (line.startsWith(getBool)) { + static const QString overwrite = QLatin1String("openfile.overwrite.okay"); + const QString question = line.mid(getBool.length()); + + KGpgTransaction::ts_boolanswer answer; + + if (question.startsWith(overwrite)) { + m_overwriteUrl.clear(); + answer = m_parent->confirmOverwrite(m_overwriteUrl); + + if ((answer == KGpgTransaction::BA_UNKNOWN) && !m_overwriteUrl.isEmpty()) { + QPointer over = new KIO::RenameDialog(qobject_cast(m_parent->parent()), + i18n("File Already Exists"), KUrl(), + m_overwriteUrl, KIO::M_OVERWRITE); + + m_overwriteUrl.clear(); + + switch (over->exec()) { + case KIO::R_OVERWRITE: + answer = KGpgTransaction::BA_YES; + break; + case KIO::R_RENAME: + answer = KGpgTransaction::BA_NO; + m_overwriteUrl = over->newDestUrl(); + break; + default: + answer = KGpgTransaction::BA_UNKNOWN; + m_parent->setSuccess(KGpgTransaction::TS_USER_ABORTED); + // Close the pipes, otherwise GnuPG will try to answer + // further questions about this file. + m_process->closeWriteChannel(); + m_process->closeReadChannel(QProcess::StandardOutput); + break; + } + + delete over; + + if (answer == KGpgTransaction::BA_UNKNOWN) + continue; + } + } else { + answer = m_parent->boolQuestion(question); + } + + switch (answer) { + case KGpgTransaction::BA_YES: + write("YES\n"); + break; + case KGpgTransaction::BA_NO: + write("NO\n"); + break; + case KGpgTransaction::BA_UNKNOWN: + m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); + m_parent->unexpectedLine(line); + sendQuit(); + } + } else if (!m_overwriteUrl.isEmpty() && line.startsWith(QLatin1String("[GNUPG:] GET_LINE openfile.askoutname"))) { + write(m_overwriteUrl.toLocalFile().toUtf8() + '\n'); + m_overwriteUrl.clear(); + } else if (line.startsWith(QLatin1String("[GNUPG:] MISSING_PASSPHRASE"))) { + m_success = KGpgTransaction::TS_USER_ABORTED; + } else if (line.startsWith(QLatin1String("[GNUPG:] CARDCTRL "))) { + // just ignore them, pinentry should handle that + } else { + // all known hints + int i = 0; + bool matched = false; + foreach (const QString &hintName, hintNames()) { + const KGpgTransaction::ts_hintType h = static_cast(i++); + if (!line.startsWith(hintName)) + continue; + + matched = true; + + bool r; + const int skip = hintName.length(); + if (line.length() == skip) { + r = m_parent->hintLine(h, QString()); + } else { + r = m_parent->hintLine(h, line.mid(skip + 1).trimmed()); + } + + if (!r) { + m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); + sendQuit(); + } + + break; + } + + if (!matched) { + if (m_parent->nextLine(line)) + sendQuit(); + } + } + } +} + +void +KGpgTransactionPrivate::slotProcessExited() +{ + Q_ASSERT(m_parent->sender() == m_process); + m_ownProcessFinished = true; + + if (m_inputProcessDone) + processDone(); +} + +void +KGpgTransactionPrivate::slotProcessStarted() +{ + m_parent->postStart(); +} + +void +KGpgTransactionPrivate::sendQuit(void) +{ + write("quit\n"); + +#ifdef KGPG_DEBUG_TRANSACTIONS + if (m_quitTries == 0) + kDebug(2100) << "sending quit"; +#endif /* KGPG_DEBUG_TRANSACTIONS */ + + if (m_quitTries++ >= 3) { + kDebug(2100) << "tried" << m_quitTries << "times to quit the GnuPG session"; + kDebug(2100) << "last input was" << m_quitLines; + kDebug(2100) << "please file a bug report at https://bugs.kde.org"; + m_process->closeWriteChannel(); + m_success = KGpgTransaction::TS_MSG_SEQUENCE; + } +} + +void +KGpgTransactionPrivate::slotInputTransactionDone(int result) +{ + Q_ASSERT(m_parent->sender() == m_inputTransaction); + + m_inputProcessDone = true; + m_inputProcessResult = result; + + if (m_ownProcessFinished) + processDone(); +} + +void +KGpgTransactionPrivate::slotPassphraseEntered(const QString &passphrase) +{ + // not calling KGpgTransactionPrivate::write() here for obvious privacy reasons + m_process->write(passphrase.toUtf8() + '\n'); + if (m_parent->sender() == m_newPasswordDialog) { + m_newPasswordDialog->deleteLater(); + m_newPasswordDialog = NULL; + m_parent->newPassphraseEntered(); + } else { + Q_ASSERT(m_parent->sender() == m_passwordDialog); + } +} + +void +KGpgTransactionPrivate::slotPassphraseAborted() +{ + Q_ASSERT((m_parent->sender() == m_passwordDialog) ^ (m_parent->sender() == m_newPasswordDialog)); + m_parent->sender()->deleteLater(); + m_newPasswordDialog = NULL; + m_passwordDialog = NULL; + handlePassphraseAborted(); +} + +void +KGpgTransactionPrivate::handlePassphraseAborted() +{ + // sending "quit" here is useless as it would be interpreted as the passphrase + m_process->closeWriteChannel(); + m_success = KGpgTransaction::TS_USER_ABORTED; +} + +void +KGpgTransactionPrivate::write(const QByteArray &a) +{ + m_process->write(a); +#ifdef KGPG_DEBUG_TRANSACTIONS + kDebug(2100) << m_parent << a; +#endif /* KGPG_DEBUG_TRANSACTIONS */ +} + +const QStringList & +KGpgTransactionPrivate::hintNames (void) +{ + static QStringList hints; + + if (hints.isEmpty()) { + hints.insert(KGpgTransaction::HT_KEYEXPIRED, QLatin1String("[GNUPG:] KEYEXPIRED")); + hints.insert(KGpgTransaction::HT_SIGEXPIRED, QLatin1String("[GNUPG:] SIGEXPIRED")); + hints.insert(KGpgTransaction::HT_NOSECKEY, QLatin1String("[GNUPG:] NO_SECKEY")); + hints.insert(KGpgTransaction::HT_ENCTO, QLatin1String("[GNUPG:] ENC_TO")); + } + + return hints; +} + +void +KGpgTransactionPrivate::processDone() +{ + m_parent->finish(); + emit m_parent->infoProgress(100, 100); + emit m_parent->done(m_success); +#ifdef KGPG_DEBUG_TRANSACTIONS + kDebug(2100) << this << "result:" << m_success; +#endif /* KGPG_DEBUG_TRANSACTIONS */ +} + +void +KGpgTransaction::start() +{ + d->m_inputProcessResult = false; + d->m_inputProcessDone = (d->m_inputTransaction == NULL); + + setSuccess(TS_OK); + d->m_idhints.clear(); + d->m_tries = 3; + if (preStart()) { + d->m_ownProcessFinished = false; + if (d->m_inputTransaction != NULL) + d->m_inputTransaction->start(); +#ifdef KGPG_DEBUG_TRANSACTIONS + kDebug(2100) << this << d->m_process->program(); +#endif /* KGPG_DEBUG_TRANSACTIONS */ + d->m_process->start(); + emit infoProgress(0, 1); + } else { + emit done(d->m_success); + } +} + +void +KGpgTransaction::write(const QByteArray &a, const bool lf) +{ + if (lf) + d->write(a + '\n'); + else + d->write(a); +} + +void +KGpgTransaction::write(const int i) +{ + write(QByteArray::number(i)); +} + +void +KGpgTransaction::askNewPassphrase(const QString& text) +{ + emit statusMessage(i18n("Requesting Passphrase")); + + d->m_newPasswordDialog = new KNewPasswordDialog(qobject_cast(parent())); + d->m_newPasswordDialog->setPrompt(text); + d->m_newPasswordDialog->setAllowEmptyPasswords(false); + connect(d->m_newPasswordDialog, SIGNAL(newPassword(QString)), SLOT(slotPassphraseEntered(QString))); + connect(d->m_newPasswordDialog, SIGNAL(rejected()), SLOT(slotPassphraseAborted())); + connect(d->m_process, SIGNAL(processExited()), d->m_newPasswordDialog->button(KDialog::Cancel), SLOT(click())); + d->m_newPasswordDialog->show(); +} + +int +KGpgTransaction::getSuccess() const +{ + return d->m_success; +} + +void +KGpgTransaction::setSuccess(const int v) +{ +#ifdef KGPG_DEBUG_TRANSACTIONS + kDebug(2100) << "old" << d->m_success << "new" << v; +#endif /* KGPG_DEBUG_TRANSACTIONS */ + d->m_success = v; +} + +KGpgTransaction::ts_boolanswer +KGpgTransaction::boolQuestion(const QString& line) +{ + Q_UNUSED(line) + + return BA_UNKNOWN; +} + +KGpgTransaction::ts_boolanswer +KGpgTransaction::confirmOverwrite(KUrl ¤tFile) +{ + Q_UNUSED(currentFile) + + return BA_UNKNOWN; +} + +bool +KGpgTransaction::hintLine(const ts_hintType hint, const QString &args) +{ + switch (hint) { + case HT_KEYEXPIRED: + return !args.isEmpty(); + default: + return true; + } +} + +void +KGpgTransaction::finish() +{ +} + +void +KGpgTransaction::setDescription(const QString &description) +{ + d->m_description = description; +} + +void +KGpgTransaction::waitForInputTransaction() +{ + Q_ASSERT(d->m_inputTransaction != NULL); + + if (d->m_inputProcessDone) + return; + + d->m_inputTransaction->waitForFinished(); +} + +void +KGpgTransaction::unexpectedLine(const QString &line) +{ + kDebug(2100) << this << "unexpected input line" << line << "for command" << d->m_process->program(); +} + +bool +KGpgTransaction::passphraseRequested() +{ + return askPassphrase(); +} + +bool +KGpgTransaction::passphraseReceived() +{ + return true; +} + +bool +KGpgTransaction::preStart() +{ + return true; +} + +void +KGpgTransaction::postStart() +{ +} + +void +KGpgTransaction::addIdHint(QString txt) +{ + int cut = txt.indexOf(QLatin1Char( ' ' ), 22, Qt::CaseInsensitive); + txt.remove(0, cut); + + if (txt.contains(QLatin1Char( '(' ), Qt::CaseInsensitive)) + txt = txt.section(QLatin1Char( '(' ), 0, 0) + txt.section(QLatin1Char( ')' ), -1); + + txt.replace(QLatin1Char( '<' ), QLatin1String( "<" )); + + if (!d->m_idhints.contains(txt)) + d->m_idhints << txt; +} + +QString +KGpgTransaction::getIdHints() const +{ + return d->m_idhints.join( i18n(" or " )); +} + +GPGProc * +KGpgTransaction::getProcess() +{ + return d->m_process; +} + +int +KGpgTransaction::addArgument(const QString &arg) +{ + int r = d->m_process->program().count(); + + *d->m_process << arg; + + return r; +} + +int +KGpgTransaction::addArguments(const QStringList &args) +{ + int r = d->m_process->program().count(); + + *d->m_process << args; + + return r; +} + +void +KGpgTransaction::replaceArgument(const int pos, const QString &arg) +{ + QStringList args(d->m_process->program()); + d->m_process->clearProgram(); + + args.replace(pos, arg); + + d->m_process->setProgram(args); +} + +void +KGpgTransaction::insertArgument(const int pos, const QString &arg) +{ + insertArguments(pos, QStringList(arg)); +} + +void +KGpgTransaction::insertArguments(const int pos, const QStringList &args) +{ + QStringList tmp(d->m_process->program()); + + int tmppos = pos; + foreach (const QString &s, args) { + tmp.insert(tmppos++, s); + } + d->m_process->setProgram(tmp); + + int move = args.count(); + foreach (int *ref, d->m_argRefs) { + if (*ref >= pos) + *ref += move; + } +} + +void +KGpgTransaction::addArgumentRef(int *ref) +{ + d->m_argRefs.append(ref); +} + +bool +KGpgTransaction::askPassphrase(const QString &message) +{ + emit statusMessage(i18n("Requesting Passphrase")); + + if (d->m_passwordDialog == NULL) { + d->m_passwordDialog = new KPasswordDialog(qobject_cast(parent())); + + QString passdlgmessage; + if (message.isEmpty()) { + QString userIDs(getIdHints()); + if (userIDs.isEmpty()) + userIDs = i18n("[No user id found]"); + else + userIDs.replace(QLatin1Char( '<' ), QLatin1String( "<" )); + + passdlgmessage = i18n("Enter passphrase for %1", userIDs); + } else { + passdlgmessage = message; + } + + d->m_passwordDialog->setPrompt(passdlgmessage); + + connect(d->m_passwordDialog, SIGNAL(gotPassword(QString,bool)), SLOT(slotPassphraseEntered(QString))); + connect(d->m_passwordDialog, SIGNAL(rejected()), SLOT(slotPassphraseAborted())); + connect(d->m_process, SIGNAL(processExited()), d->m_passwordDialog->button(KDialog::Cancel), SLOT(click())); + } else { + // we already have a dialog, so this is a "bad passphrase" situation + --d->m_tries; + + d->m_passwordDialog->showErrorMessage(i18np("

Bad passphrase. You have 1 try left.

", + "

Bad passphrase. You have %1 tries left.

", d->m_tries), + KPasswordDialog::PasswordError); + } + + d->m_passwordDialog->show(); + + return true; +} + +void +KGpgTransaction::setGnuPGHome(const QString &home) +{ + QStringList tmp(d->m_process->program()); + + Q_ASSERT(tmp.count() > 3); + int homepos = tmp.indexOf(QLatin1String("--options"), 1); + if (homepos == -1) + homepos = tmp.indexOf(QLatin1String("--homedir"), 1); + Q_ASSERT(homepos != -1); + Q_ASSERT(homepos + 1 < tmp.count()); + + tmp[homepos] = QLatin1String("--homedir"); + tmp[homepos + 1] = home; + + d->m_process->setProgram(tmp); +} + +int +KGpgTransaction::waitForFinished(const int msecs) +{ + int ret = TS_OK; + + if (d->m_inputTransaction != NULL) { + int ret = d->m_inputTransaction->waitForFinished(msecs); + if ((ret != TS_OK) && (msecs != -1)) + return ret; + } + + bool b = d->m_process->waitForFinished(msecs); + + if (ret != TS_OK) + return ret; + + if (!b) + return TS_USER_ABORTED; + else + return getSuccess(); +} + +const QString & +KGpgTransaction::getDescription() const +{ + return d->m_description; +} + +void +KGpgTransaction::setInputTransaction(KGpgTransaction *ta) +{ + Q_ASSERT(d->m_chainingAllowed); + + if (d->m_inputTransaction != NULL) + clearInputTransaction(); + d->m_inputTransaction = ta; + + GPGProc *proc = ta->getProcess(); + proc->setStandardOutputProcess(d->m_process); + connect(ta, SIGNAL(done(int)), SLOT(slotInputTransactionDone(int))); +} + +void +KGpgTransaction::clearInputTransaction() +{ + disconnect(d->m_inputTransaction, SIGNAL(done(int)), this, SLOT(slotInputTransactionDone(int))); + d->m_inputTransaction = NULL; +} + +bool +KGpgTransaction::hasInputTransaction() const +{ + return (d->m_inputTransaction != NULL); +} + +void +KGpgTransaction::kill() +{ + d->m_process->kill(); +} + +void +KGpgTransaction::newPassphraseEntered() +{ +} + +#include "kgpgtransaction.moc" diff --git a/kgpg/transactions/kgpgtransaction.h b/kgpg/transactions/kgpgtransaction.h new file mode 100644 index 00000000..35f1ca39 --- /dev/null +++ b/kgpg/transactions/kgpgtransaction.h @@ -0,0 +1,460 @@ +/* + * Copyright (C) 2008,2009,2012,2013 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGTRANSACTION_H +#define KGPGTRANSACTION_H + +#include +#include + +class GPGProc; +class KGpgSignTransactionHelper; +class KGpgTransactionPrivate; +class KUrl; +class QByteArray; +class QProcess; + +/** + * @brief Process one GnuPG operation + * + * This class encapsulates one GnuPG operation. It will care for all + * interaction with the gpg process. Everything you have to care about + * is to set up the object properly, call start() and catch the done signal. + * + * This is an abstract base class for specific operations that implements + * the basic I/O loop, the process setup and interaction and some convenience + * members to set extra arguments for the process. + * + * If you want to add a new operation create a child class that implements + * nextLine(). Ususally you also need a constructor that takes some information + * like the id of the key to modify. + * + * @author Rolf Eike Beer + */ +class KGpgTransaction: public QObject { + Q_OBJECT + + friend class KGpgTransactionPrivate; + friend class KGpgSignTransactionHelper; + + Q_DISABLE_COPY(KGpgTransaction) + +public: + /** + * @brief return codes common to many transactions + * + * Every transaction may define additional return codes, which + * should start at TS_COMMON_END + 1. + */ + enum ts_transaction { + TS_OK = 0, ///< everything went fine + TS_BAD_PASSPHRASE = 1, ///< the passphrase was not correct + TS_MSG_SEQUENCE = 2, ///< unexpected sequence of GnuPG messages + TS_USER_ABORTED = 3, ///< the user aborted the transaction + TS_INVALID_EMAIL = 4, ///< the given email address is invalid + TS_INPUT_PROCESS_ERROR = 5, ///< the connected input process returned an error + TS_COMMON_END = 100 ///< placeholder for return values of derived classes + }; + /** + * @brief result codes for GnuPG boolean questions + * + * These are the possible answers to a boolean question of a GnuPG process. + */ + enum ts_boolanswer { + BA_UNKNOWN = 0, ///< the question is not supported (this is an error) + BA_YES = 1, ///< answer "YES" + BA_NO = 2 ///< answer "NO" + }; + /** + * @brief the known hints sent by GnuPG + */ + enum ts_hintType { + HT_KEYEXPIRED = 0, ///< key is expired + HT_SIGEXPIRED = 1, ///< deprecated by GnuPG + HT_NOSECKEY = 2, ///< secret key not available + HT_ENCTO = 3 ///< message is encrypted for this key + }; + + /** + * @brief KGpgTransaction constructor + */ + explicit KGpgTransaction(QObject *parent = 0, const bool allowChaining = false); + /** + * @brief KGpgTransaction destructor + */ + virtual ~KGpgTransaction(); + + /** + * @brief Start the operation. + */ + void start(); + + /** + * @brief sets the home directory of GnuPG called for this transaction + */ + void setGnuPGHome(const QString &home); + + /** + * @brief blocks until the transaction is complete + * @return the result of the transaction like done() would + * @retval TS_USER_ABORTED the timeout expired + * + * If this transaction has another transaction set as input then + * it would wait for those transaction to finish first. The msecs + * argument is used as limit for both transactions then so you + * can end up waiting twice the given time (or longer if you have + * more transactions chained). + */ + int waitForFinished(const int msecs = -1); + + /** + * @brief return description of this transaction + * @return string used to describe what's going on + * + * This is especially useful when using this transaction from a KJob. + */ + const QString &getDescription() const; + + /** + * @brief connect the standard input of this transaction to another process + * @param proc process to read data from + * + * Once the input process is connected this transaction will not emit + * the done signal until the input process sends the done signal. + * + * The basic idea is that when an input transaction is set you only need + * to care about this transaction. The other transaction is automatically + * started when this one is started and is destroyed when this one is. + */ + void setInputTransaction(KGpgTransaction *ta); + + /** + * @brief tell the process the standard input is no longer connected + * + * If you had connected an input process you need to tell the transaction + * once this input process is gone. Otherwise you will not get a done + * signal from this transaction as it will wait for the finished signal + * from the process that will never come. + */ + void clearInputTransaction(); + + /** + * @brief check if another transaction will sent input to this + */ + bool hasInputTransaction() const; + + /** + * @brief abort this operation as soon as possible + */ + void kill(); + +signals: + /** + * @brief Emitted when the operation was completed. + * @param result return status of the transaction + * + * @see ts_transaction for the common status codes. Each transaction + * may define additional status codes. + */ + void done(int result); + + /** + * @brief emits textual status information + * @param msg the status message + */ + void statusMessage(const QString &msg); + + /** + * @brief emits procentual status information + * @param processedAmount how much of the job is done + * @param totalAmount how much needs to be done to complete this job + */ + void infoProgress(qulonglong processedAmount, qulonglong totalAmount); + +protected: + /** + * @brief Called before the gpg process is started. + * @return true if the process should be started + * + * You may reimplement this member if you need to do some special + * operations before the process is started. The command line of the + * process may be modified for the last time here. + * + * When you notice that some values passed are invalid or the + * transaction does not need to be run for some other reason you should + * call setSuccess() to set the return value and return false. In this + * case the process is not started but the value is immediately + * returned. + */ + virtual bool preStart(); + /** + * @brief Called when the gpg process is up and running. + * + * This functions is connected to the started() signal of the gpg process. + */ + virtual void postStart(); + /** + * @brief Called for every line the gpg process writes. + * @param line the input from the process + * @return true if "quit" should be sent to process + * + * You need to implement this member to get a usable subclass. + * + * When this function returns true "quit" is written to the process. + */ + virtual bool nextLine(const QString &line) = 0; + /** + * @brief Called for every boolean question GnuPG answers + * @param line the question GnuPG asked + * @return what to answer GnuPG + * + * This is called instead of nextLine() if the line contains a boolean + * question. Returning BA_UNKNOWN will cancel the current transaction + * and will set the transaction result to TS_MSG_SEQUENCE. + * + * The default implementation will answer BA_UNKNOWN to every question. + */ + virtual ts_boolanswer boolQuestion(const QString &line); + + /** + * @brief called when GnuPG asks for confirmation for overwriting a file + * @param currentFile fill in the current filename for the user dialog + * @return what to answer to GnuPG + * @retval BA_YES file will be overwritten, @currentFile is ignored + * @retval BA_NO file will not be overwritten, if currentFile is given this will automatically be provided as alternative to GnuPG + * @retval BA_UNKNOWN ask the user for a choice or abort, currentFile is provided to the user as a hint about the original filename, if currentFile is empty the transaction is aborted + * + * The default implementation will just return BA_UNKNOWN without setting + * a filename, causing a sequence error. + */ + virtual ts_boolanswer confirmOverwrite(KUrl ¤tFile); + + /** + * @brief Called for a set of hint messages + * + * @param hint the hint type given by GnuPG + * @param args the arguments given to the hint + * @return if the hint was parsed correctly + * @retval true everything is fine + * @retval false something went wrong (e.g. syntax error) + * + * The default implementation will do nothing but checking for some + * argument counts. Override this and handle all interesting hints + * yourself. Don't forget to call the default implementation at the end. + */ + virtual bool hintLine(const ts_hintType hint, const QString &args); + /** + * @brief Called when the gpg process finishes. + * + * You may reimplement this member if you need to do some special + * operations after process completion. The provided one simply + * does nothing which should be enough for most cases. + */ + virtual void finish(); + /** + * @brief called when the user entered a new passphrase + * + * This is called after askNewPassphrase() was called, the user has + * entered a new passphrase and it was sent to the GnuPG process. + * + * The default implementation does nothing. + */ + virtual void newPassphraseEntered(); + /** + * @brief set the description returned in getDescription() + * @param description the new description of this transaction + */ + void setDescription(const QString &description); + + /** + * @brief wait until the input transaction has finished + */ + void waitForInputTransaction(); + + /** + * @brief notify of an unexpected line + * + * This will print out the line to the console to ease debugging. + */ + void unexpectedLine(const QString &line); + + /** + * @brief called when GnuPG asks for a passphrase + * @return if the processing should continue + * @retval true processing should continue + * @retval false an error occurred, transaction should be aborted + * + * This allows a transaction to implement special handling for + * passphrases, e.g. when both old and new passphrase must be + * requested when changing it. The default implementation will just + * call askPassphrase(). + */ + virtual bool passphraseRequested(); + + /** + * @brief called when GnuPG accepted the passphrase + * @return if the input channel to GnuPG should be closed + * @retval true close the input channel of the GnuPG process + * @retval false keep the GnuPG input channel open + * + * This allows a transaction to handle passphrase success in a + * special way. The default implementation will just return true. + */ + virtual bool passphraseReceived(); + +private: + KGpgTransactionPrivate* const d; + + Q_PRIVATE_SLOT(d, void slotReadReady()) + Q_PRIVATE_SLOT(d, void slotProcessExited()) + Q_PRIVATE_SLOT(d, void slotProcessStarted()) + Q_PRIVATE_SLOT(d, void slotInputTransactionDone(int)) + Q_PRIVATE_SLOT(d, void slotPassphraseEntered(const QString &)) + Q_PRIVATE_SLOT(d, void slotPassphraseAborted()) + +protected: + /** + * @brief Ask user for passphrase and send it to gpg process. + * + * If the gpg process asks for a new passphrase this function will do + * all necessary steps for you: ask the user for the passphrase and write + * it to the gpg process. If the passphrase is wrong the user is prompted + * again for the correct passphrase. If the user aborts the passphrase + * entry the gpg process will be killed and the transaction result will + * be set to TS_USER_ABORTED. + * + * @see askPassphrase + */ + void askNewPassphrase(const QString &text); + + /** + * @brief get the success value that will be returned with the done signal + */ + int getSuccess() const; + /** + * @brief set the success value that will be returned with the done signal + * @param v the new success value + * + * You should use 0 as success value. Other values can be defined as needed. + */ + void setSuccess(const int v); + + /** + * @brief add a userid hint + * @param txt userid description + * + * Before GnuPG asks for a passphrase it usually sends out a hint message + * for which key the passphrase will be needed. There may be several hint + * messages, e.g. if a text was encrypted with multiple keys. + */ + void addIdHint(QString txt); + /** + * @brief get string of all userid hints + * @returns concatenation of all ids previously added with addIdHint(). + */ + QString getIdHints() const; + + /** + * @brief get a reference to the gpg process object + * @returns gpg process object + * + * This returns a reference to the gpg process object used internally. + * In case you need to do some special things (e.g. changing the output + * mode) you can modify this object. + * + * Usually you will not need this. + * + * @warning Never free this object! + */ + GPGProc *getProcess(); + /** + * @brief add a command line argument to gpg process + * @param arg new argument + * @returns the position of the new argument + * + * This is a convenience function that allows adding one additional + * argument to the command line of the process. This must be called + * before start() is called. Usually you will call this from your + * constructor. + */ + int addArgument(const QString &arg); + /** + * @brief add command line arguments to gpg process + * @param args new arguments + * @returns the position of the first argument added + * + * This is a convenience function that allows adding additional + * arguments to the command line of the process. This must be called + * before start() is called. + */ + int addArguments(const QStringList &args); + /** + * @brief replace the argument at the given position + * @param pos position of old argument + * @param arg new argument + */ + void replaceArgument(const int pos, const QString &arg); + /** + * @brief insert an argument at the given position + * @param pos position to insert at + * @param arg new argument + */ + void insertArgument(const int pos, const QString &arg); + /** + * @brief insert arguments at the given position + * @param pos position to insert at + * @param args new arguments + */ + void insertArguments(const int pos, const QStringList &args); + /** + * @brief make sure the reference to a specific argument is kept up to date + * @param ref the value where the position is stored + * + * You might want to keep the position of a specific argument to + * later be able to repace it easily. In that case put it into + * this function too so every time someone mofifies the argument + * list (especially by insertArgument() and insertArguments()) + * this reference will be kept up to date. + */ + void addArgumentRef(int *ref); + /** + * @brief write data to standard input of gpg process + * @param a data to write + * @param lf if line feed should be appended to message + * + * Use this function to interact with the gpg process. A carriage + * return is appended to the data automatically. Usually you will + * call this function from nextLine(). + */ + void write(const QByteArray &a, const bool lf = true); + /** + * @brief write data to standard input of gpg process + * @param i data to write + * + * @overload + */ + void write(const int i); + /** + * @brief ask user for passphrase + * @param message message to display to the user. If message is empty + * "Enter passphrase for [UID]" will be used. + * @return true if the authorization was successful + * + * This function handles user authorization for key operations. It will + * take care to display the message asking the user for the passphrase + * and the number of tries left. + */ + bool askPassphrase(const QString &message = QString()); +}; + +#endif // KGPGTRANSACTION_H diff --git a/kgpg/transactions/kgpgtransactionjob.cpp b/kgpg/transactions/kgpgtransactionjob.cpp new file mode 100644 index 00000000..392eb34f --- /dev/null +++ b/kgpg/transactions/kgpgtransactionjob.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgtransactionjob.h" + +#include "kgpgtransaction.h" + +#include + +KGpgTransactionJob::KGpgTransactionJob(KGpgTransaction *transaction) + : KJob(transaction->parent()), + m_transaction(transaction), + m_result(-1) +{ +} + +KGpgTransactionJob::~KGpgTransactionJob() +{ + delete m_transaction; +} + +void +KGpgTransactionJob::start() +{ + connect(m_transaction, SIGNAL(done(int)), SLOT(slotTransactionDone(int))); + connect(m_transaction, SIGNAL(statusMessage(QString)), SLOT(slotStatusMessage(QString))); + connect(m_transaction, SIGNAL(infoProgress(qulonglong,qulonglong)), SLOT(slotInfoProgress(qulonglong,qulonglong))); + + slotStatusMessage(i18nc("Job is started up", "Startup")); + m_transaction->start(); +} + +const KGpgTransaction * +KGpgTransactionJob::getTransaction() const +{ + return m_transaction; +} + +int +KGpgTransactionJob::getResultCode() const +{ + return m_result; +} + +void +KGpgTransactionJob::slotTransactionDone(int result) +{ + m_result = result; + emitResult(); +} + +void +KGpgTransactionJob::slotStatusMessage(const QString &msg) +{ + emit description(this, m_transaction->getDescription(), qMakePair(i18nc("State of operation as in status", "State"), msg)); +} + +void +KGpgTransactionJob::slotInfoProgress(qulonglong processedAmount, qulonglong totalAmount) +{ + emitPercent(processedAmount, totalAmount); +} + +bool +KGpgTransactionJob::doKill() +{ + m_transaction->kill(); + + return true; +} + + +#include "kgpgtransactionjob.moc" diff --git a/kgpg/transactions/kgpgtransactionjob.h b/kgpg/transactions/kgpgtransactionjob.h new file mode 100644 index 00000000..6eb4539e --- /dev/null +++ b/kgpg/transactions/kgpgtransactionjob.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGTRANSACTIONJOB_H +#define KGPGTRANSACTIONJOB_H + +#include + +class KGpgTransaction; + +/** +* @brief Wrap a GnuPG transaction in a job +* +* This class allows to run any KGpgTransaction as KJob. +* +* @author Rolf Eike Beer +*/ +class KGpgTransactionJob : public KJob { + Q_OBJECT + + Q_DISABLE_COPY(KGpgTransactionJob) + KGpgTransactionJob(); // = delete C++0x + + KGpgTransaction * const m_transaction; + int m_result; + +public: + /** + * @brief create a new KJob for this transaction + * @param transaction operation to do + * + * The job will take ownership of the transaction, i.e. + * will delete the transaction object when the job is done. + */ + explicit KGpgTransactionJob(KGpgTransaction *transaction); + /** + * @brief KGpgTransactionJob destructor + */ + virtual ~KGpgTransactionJob(); + + /** + * @brief starts the transaction + */ + virtual void start(); + + /** + * @brief get the transaction this job is handling + */ + const KGpgTransaction *getTransaction() const; + + /** + * @brief get the result of the transaction + */ + int getResultCode() const; + +protected: + virtual bool doKill(); + +private slots: + void slotTransactionDone(int result); + void slotStatusMessage(const QString &plain); + void slotInfoProgress(qulonglong processedAmount, qulonglong totalAmount); +}; + +#endif + + diff --git a/kgpg/transactions/kgpguidtransaction.cpp b/kgpg/transactions/kgpguidtransaction.cpp new file mode 100644 index 00000000..bafa7993 --- /dev/null +++ b/kgpg/transactions/kgpguidtransaction.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2008,2009,2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpguidtransaction.h" + +KGpgUidTransaction::KGpgUidTransaction(QObject *parent, const QString &keyid, const QString &uid) + : KGpgTransaction(parent) +{ + addArgument(QLatin1String( "--status-fd=1" )); + addArgument(QLatin1String( "--command-fd=0" )); + addArgument(QLatin1String( "--edit-key" )); + m_keyidpos = addArgument(QString()); + addArgumentRef(&m_keyidpos); + addArgument(QLatin1String( "uid" )); + m_uidpos = addArgument(QString()); + addArgumentRef(&m_uidpos); + + setKeyId(keyid); + setUid(uid); +} + +KGpgUidTransaction::~KGpgUidTransaction() +{ +} + +bool +KGpgUidTransaction::preStart() +{ + setSuccess(TS_MSG_SEQUENCE); + + return true; +} + +bool +KGpgUidTransaction::standardCommands(const QString &line) +{ + if (!line.startsWith(QLatin1String("[GNUPG:] "))) + return false; + + if (line.contains(QLatin1String( "GOOD_PASSPHRASE" ))) { + setSuccess(TS_MSG_SEQUENCE); + } else if (line.contains(QLatin1String( "keyedit.prompt" ))) { + write("save"); + if (getSuccess() == TS_MSG_SEQUENCE) + setSuccess(TS_OK); + return true; + } else if (line.contains(QLatin1String( "GET_" ))) { + setSuccess(TS_MSG_SEQUENCE); + return true; + } + + return false; +} + +void +KGpgUidTransaction::setKeyId(const QString &keyid) +{ + m_keyid = keyid; + + replaceArgument(m_keyidpos, keyid); +} + +QString +KGpgUidTransaction::getKeyId(void) const +{ + return m_keyid; +} + +void +KGpgUidTransaction::setUid(const QString &uid) +{ + m_uid = uid; + + replaceArgument(m_uidpos, uid); +} + +void +KGpgUidTransaction::setUid(const unsigned int uid) +{ + setUid(QString::number(uid)); +} + +#include "kgpguidtransaction.moc" diff --git a/kgpg/transactions/kgpguidtransaction.h b/kgpg/transactions/kgpguidtransaction.h new file mode 100644 index 00000000..43869319 --- /dev/null +++ b/kgpg/transactions/kgpguidtransaction.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008,2009 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGUIDTRANSACTION_H +#define KGPGUIDTRANSACTION_H + +#include +#include + +#include "kgpgtransaction.h" + +/** + * @brief base class for transactions involving only one user id of a key + */ +class KGpgUidTransaction: public KGpgTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgUidTransaction) +public: + /** + * @brief construct a new transaction for the given key and uid + * @param parent object that own the transaction + * @param keyid key to work with + * @param uid uid to work with + * + * You should call this from the childrens constructor to set up + * everything properly + */ + explicit KGpgUidTransaction(QObject *parent, const QString &keyid = QString(), const QString &uid = QString()); + virtual ~KGpgUidTransaction(); + + /** + * @brief set the key id of the transaction to the given value + * @param keyid fingerprint of the key to work with + */ + void setKeyId(const QString &keyid); + /** + * @brief return the key id of the current transaction + */ + QString getKeyId(void) const; + /** + * @brief set the uid number of the transaction to the given value + * @param uid the number of the user id to work with + */ + void setUid(const QString &uid); + /** + * @brief set the uid number of the transaction to the given value + * @param uid the number of the user id to work with + * + * @overload + */ + void setUid(const unsigned int uid); + +protected: + virtual bool preStart(); + + /** + * @brief handle common GnuPG messages for uid transactions + * @param line GnuPG message + * @return true if "quit" should be sent to process + * + * You should call these function for all messages in nextLine() + * you do not need to handle yourself. + */ + bool standardCommands(const QString &line); + +private: + QString m_uid; + int m_uidpos; + QString m_keyid; + int m_keyidpos; +}; + +#endif // KGPGUIDTRANSACTION_H diff --git a/kgpg/transactions/kgpgverify.cpp b/kgpg/transactions/kgpgverify.cpp new file mode 100644 index 00000000..03c41341 --- /dev/null +++ b/kgpg/transactions/kgpgverify.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kgpgverify.h" + +#include "gpgproc.h" +#include "core/KGpgKeyNode.h" +#include "model/kgpgitemmodel.h" + +#include +#include + +KGpgVerify::KGpgVerify(QObject *parent, const QString &text) + : KGpgTextOrFileTransaction(parent, text), + m_fileIndex(-1) +{ +} + +KGpgVerify::KGpgVerify(QObject *parent, const KUrl::List &files) + : KGpgTextOrFileTransaction(parent, files), + m_fileIndex(0) +{ +} + +KGpgVerify::~KGpgVerify() +{ +} + +QStringList +KGpgVerify::command() const +{ + QStringList ret(QLatin1String("--verify")); + + return ret; +} + +bool +KGpgVerify::nextLine(const QString &line) +{ + if (line.startsWith(QLatin1String("[GNUPG:] NO_PUBKEY "))) { + setSuccess(TS_MISSING_KEY); + m_missingId = line.mid(19).simplified(); + return false; + } + + if (line.startsWith(QLatin1String("[GNUPG:] ")) && + line.contains(QLatin1String("SIG"))) { + if (line.startsWith(QLatin1String("[GNUPG:] BADSIG"))) + setSuccess(KGpgVerify::TS_BAD_SIGNATURE); + else + setSuccess(KGpgTransaction::TS_OK); + } + + return KGpgTextOrFileTransaction::nextLine(line); +} + +void +KGpgVerify::finish() +{ + // GnuPG will return error code 2 if it wasn't able to verify the file. + // If it complained about a missing signature before that is fine. + if (((getProcess()->exitCode() == 2) && (getSuccess() == TS_MISSING_KEY)) || + ((getProcess()->exitCode() == 1) && (getSuccess() == TS_BAD_SIGNATURE))) + return; + + KGpgTextOrFileTransaction::finish(); +} + +static QString +sigTimeMessage(const QString &sigtime) +{ + QDateTime stamp; + if (sigtime.contains(QLatin1Char('T'))) { + stamp = QDateTime::fromString(sigtime, Qt::ISODate); + } else { + bool ok; + qint64 secs = sigtime.toLongLong(&ok); + if (ok) + stamp = QDateTime::fromMSecsSinceEpoch(secs * 1000); + } + + if (!stamp.isValid()) + return QString(); + + return i18nc("first argument is formatted date, second argument is formatted time", + "The signature was created at %1 %2", + KGlobal::locale()->formatDate(stamp.date(), KLocale::LongDate), + KGlobal::locale()->formatTime(stamp.time(), KLocale::LongDate)) + + QLatin1String("
"); +} + +QString +KGpgVerify::getReport(const QStringList &log, const KGpgItemModel *model) +{ + QString result; + // newer versions of GnuPG emit both VALIDSIG and GOODSIG + // for a good signature. Since VALIDSIG has more information + // we use that. + const QRegExp validsig(QLatin1String("^\\[GNUPG:\\] VALIDSIG([ ]+[^ ]+){10,}.*$")); + const bool useGoodSig = (model == NULL) || (log.indexOf(validsig) == -1); + QString sigtime; // timestamp of signature creation + + foreach (const QString &line, log) { + if (!line.startsWith(QLatin1String("[GNUPG:] "))) + continue; + + const QString msg = line.mid(9); + + if (!useGoodSig && msg.startsWith(QLatin1String("VALIDSIG "))) { + // from GnuPG source, doc/DETAILS: + // VALIDSIG + // + // + const QStringList vsig = msg.mid(9).split(QLatin1Char(' '), QString::SkipEmptyParts); + Q_ASSERT(vsig.count() >= 10); + + const KGpgKeyNode *node = model->findKeyNode(vsig[9]); + + if (node != NULL) { + // ignore for now if this is signed with the primary id (vsig[0] == vsig[9]) or not + if (node->getEmail().isEmpty()) + result += i18n("Good signature from:
%1
Key ID: %2
", + node->getName(), vsig[9]); + else + result += i18nc("Good signature from: NAME , Key ID: HEXID", + "Good signature from:
%1 <%2>
Key ID: %3
", + node->getName(), node->getEmail(), vsig[9]); + + result += sigTimeMessage(vsig[2]); + } else { + // this should normally never happen, but one could delete + // the key just after the verification. Brute force solution: + // do the whole report generation again, but this time make + // sure GOODSIG is used. + return getReport(log, NULL); + } + } else if (msg.startsWith(QLatin1String("UNEXPECTED")) || + msg.startsWith(QLatin1String("NODATA"))) { + result += i18n("No signature found.") + QLatin1Char('\n'); + } else if (useGoodSig && msg.startsWith(QLatin1String("GOODSIG "))) { + int sigpos = msg.indexOf( ' ' , 8); + const QString keyid = msg.mid(8, sigpos - 8); + + // split the name/email pair to give translators more power to handle this + QString email; + QString name = msg.mid(sigpos + 1); + + int oPos = name.indexOf(QLatin1Char('<')); + int cPos = name.indexOf(QLatin1Char('>')); + if ((oPos >= 0) && (cPos >= 0)) { + email = name.mid(oPos + 1, cPos - oPos - 1); + name = name.left(oPos).simplified(); + } + + if (email.isEmpty()) + result += i18n("Good signature from:
%1
Key ID: %2
", + name, keyid); + else + result += i18nc("Good signature from: NAME , Key ID: HEXID", + "Good signature from:
%1 <%2>
Key ID: %3
", + name, email, keyid); + if (!sigtime.isEmpty()) { + result += sigTimeMessage(sigtime); + sigtime.clear(); + } + } else if (msg.startsWith(QLatin1String("SIG_ID "))) { + const QStringList parts = msg.simplified().split(QLatin1Char(' ')); + if (parts.count() > 2) + sigtime = parts[2]; + } else if (msg.startsWith(QLatin1String("BADSIG"))) { + int sigpos = msg.indexOf( ' ', 7); + result += i18n("BAD signature from:
%1
Key id: %2

The file is corrupted
", + msg.mid(sigpos + 1).replace(QLatin1Char('<'), QLatin1String("<")), + msg.mid(7, sigpos - 7)); + } else if (msg.startsWith(QLatin1String("TRUST_UNDEFINED"))) { + result += i18n("The signature is valid, but the key is untrusted
"); + } else if (msg.startsWith(QLatin1String("TRUST_ULTIMATE"))) { + result += i18n("The signature is valid, and the key is ultimately trusted
"); + } + } + + return result; +} + +QString +KGpgVerify::missingId() const +{ + return m_missingId; +} + +#include "kgpgverify.moc" diff --git a/kgpg/transactions/kgpgverify.h b/kgpg/transactions/kgpgverify.h new file mode 100644 index 00000000..a4caac37 --- /dev/null +++ b/kgpg/transactions/kgpgverify.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2012 Rolf Eike Beer + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KGPGVERIFY_H +#define KGPGVERIFY_H + +#include "kgpgtextorfiletransaction.h" + +#include +#include +#include + +#include + +class KGpgItemModel; +class QProcess; + +/** + * @brief verify the signature of the given text or files + */ +class KGpgVerify: public KGpgTextOrFileTransaction { + Q_OBJECT + + Q_DISABLE_COPY(KGpgVerify) + KGpgVerify(); // = delete C++0x +public: + enum ts_verify { + TS_MISSING_KEY = KGpgTransaction::TS_COMMON_END + 1, ///< signing key not in keyring + TS_BAD_SIGNATURE = TS_MISSING_KEY + 1 ///< the file is signed, but the signature is invalid + }; + + /** + * @brief verify signature of given text + * @param parent parent object + * @param text text to verify + */ + explicit KGpgVerify(QObject *parent, const QString &text = QString()); + + /** + * @brief verify signatures of file(s) + * @param parent parent object + * @param files list of file locations to verify + */ + KGpgVerify(QObject *parent, const KUrl::List &files); + + /** + * @brief destructor + */ + virtual ~KGpgVerify(); + + /** + * @brief get verification report + * @param log the log lines to scan + * @param model key model to use for key lookups + * @return verification report of GnuPG + */ + static QString getReport(const QStringList &log, const KGpgItemModel *model = NULL); + + /** + * @brief get the missing key id + * @return key id that signed the message + * + * This is only valid if the transaction returned with result + * TS_MISSING_KEY. + */ + QString missingId() const; + +protected: + virtual QStringList command() const; + virtual bool nextLine(const QString &line); + virtual void finish(); + +private: + int m_fileIndex; + QString m_currentFile; + QStringList m_report; + QString m_missingId; +}; + +#endif // KGPGVERIFY_H diff --git a/kgpg/viewdecrypted.desktop b/kgpg/viewdecrypted.desktop new file mode 100644 index 00000000..e4f6f819 --- /dev/null +++ b/kgpg/viewdecrypted.desktop @@ -0,0 +1,64 @@ +[Desktop Entry] +Type=Service +MimeType=application/pgp-encrypted; +X-KDE-ServiceTypes=KonqPopupMenu/Plugin +Actions=decrypt-view; + +[Desktop Action decrypt-view] +Name=View file decrypted +Name[ar]=اعرض ملف مفكوك التشفير +Name[ast]=Ver ficheru desencriptáu +Name[bg]=Дешифровани файлове +Name[bs]=Prikaži dešifrovanu datoteku +Name[ca]=Mostra el fitxer desencriptat +Name[ca@valencia]=Mostra el fitxer desencriptat +Name[cs]=Zobrazit dešifrovaný soubor +Name[da]=Se filen dekrypteret +Name[de]=Datei entschlüsselt anzeigen +Name[el]=Προβολή αποκρυπτογραφημένου αρχείου +Name[en_GB]=View file decrypted +Name[es]=Ver archivo descifrado +Name[et]=Vaata faili lahtikrüptitult +Name[eu]=Ikusi deszifratutako fitxategia +Name[fi]=Katso tiedostoa salaus purettuna +Name[fr]=Afficher le fichier déchiffré +Name[ga]=Féach ar an gcomhad díchriptithe +Name[gl]=Ver o ficheiro descifrado +Name[hr]=Pogledaj datoteku dešifrirano +Name[hu]=Visszafejtett fájl megjelenítése +Name[ia]=Vide file decryptate +Name[id]=Menampilkan berkas yang dienkripsi +Name[it]=Visualizza file decifrato +Name[ja]=復号されたファイルを表示 +Name[kk]=Шифры шешілгенді көру +Name[km]=មើល​ឯកសារ​ដែល​បានឌិគ្រិប +Name[ko]=복호화된 파일 보기 +Name[lt]=Žiūrėti iššifruotą failą +Name[lv]=Apskatīt atšifrētu failu +Name[mr]=कुटलिपी सोडविलेली फाईल बघा +Name[nb]=Vis fil dekryptert +Name[nds]=Datei opslötelt ankieken +Name[nl]=Bestand ontcijferd tonen +Name[nn]=Vis fila dekryptert +Name[pa]=ਫਾਇਲ ਡਿ-ਕ੍ਰਿਪਟਡ ਵੇਖੋ +Name[pl]=Pokaż odszyfrowany plik +Name[pt]=Ver o ficheiro descodificado +Name[pt_BR]=Exibir o arquivo descriptografado +Name[ro]=Vizualizează fișierul decriptat +Name[ru]=Показать расшифрованный файл +Name[sk]=Zobraziť dešifrovaný súbor +Name[sl]=Prikaži odšifrirano datoteko +Name[sq]=Shfaq skedarin të dekriptuar +Name[sr]=Прикажи дешифрован фајл +Name[sr@ijekavian]=Прикажи дешифрован фајл +Name[sr@ijekavianlatin]=Prikaži dešifrovan fajl +Name[sr@latin]=Prikaži dešifrovan fajl +Name[sv]=Visa filen avkodad +Name[th]=ดูแฟ้มที่ถูกเข้ารหัส +Name[tr]=Dosyayı şifrelemesi açılmış olarak göster +Name[uk]=Перегляньте розшифрований файл +Name[x-test]=xxView file decryptedxx +Name[zh_CN]=查看解密文件 +Name[zh_TW]=檢視解密後檔案 +Icon=kgpg +Exec=kgpg -s %F diff --git a/kmix/AUTHORS b/kmix/AUTHORS new file mode 100644 index 00000000..a09a5ed3 --- /dev/null +++ b/kmix/AUTHORS @@ -0,0 +1,12 @@ +kmix +Written by Christian Esken (esken@kde.org) +Solaris port Brian Hanson Brian (bhanson@hotmail.com) +ALSA port Nick Lopez +ALSA 0.9.x port Helio Chissini de Castro + +Thanks: +Fixes for FreeBSD Sebestyen Zoltan (szoli@digo.inf.elte.hu) +Solaris fixes Faraut Jean-Louis +TerraTec DMX6Fire support Valentin Rusu + +The package is maintained by Christian Esken (esken@kde.org) diff --git a/kmix/CMakeLists.txt b/kmix/CMakeLists.txt new file mode 100644 index 00000000..c8290053 --- /dev/null +++ b/kmix/CMakeLists.txt @@ -0,0 +1,252 @@ +project(kmix) + +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) + +find_package(KDE4 REQUIRED) +include(KDE4Defaults) +include(MacroLibrary) + +# Do not yet REQUIRE Phonon. Hint: As long as we do not find_package(), ${KDE4_PHONON_LIBS} will be empty below, but that should not hurt. +#find_package(Phonon REQUIRED) + +find_package(Alsa) + +set(PA_VER "0.9.16") +macro_optional_find_package(PulseAudio "${PA_VER}") +macro_log_feature(PULSEAUDIO_FOUND "PulseAudio" "PulseAudio Audio Server" + "http://www.pulseaudio.org/" FALSE "${PA_VER}" "libpulse is needed to let KMix control PulseAudio") +find_package(GLIB2) +macro_optional_find_package(Canberra) +macro_log_feature(CANBERRA_FOUND "libcanberra" "libcanberra audio library" + "http://0pointer.de/lennart/projects/libcanberra/" FALSE "" "libcanberra is needed for kmix sound feedback") + +alsa_configure_file(${CMAKE_BINARY_DIR}/config-alsa.h) + + +add_definitions (${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS} ${KDE4_DEFINITIONS} ) +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=67100) + +include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}) +if(MSVC) + include_directories( ${TAGLIB_INCLUDES} ) +endif(MSVC) + + +include(CheckCXXSourceCompiles) +check_cxx_source_compiles(" + #include + int main() { std::shared_ptr p; return 0; } +" HAVE_STD_SHARED_PTR) +check_cxx_source_compiles(" + #include + int main() { std::tr1::shared_ptr p; return 0; } +" HAVE_STD_TR1_SHARED_PTR) + + +configure_file (config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h ) + + +include_directories( ${GLIB2_INCLUDE_DIR} /usr/lib/oss/include) + +add_subdirectory( doc ) +add_subdirectory( pics ) +add_subdirectory( profiles ) +#add_subdirectory( tests ) + +if (PULSEAUDIO_FOUND) + add_definitions(-DHAVE_PULSE) + + include_directories(${PULSEAUDIO_INCLUDE_DIR}) +endif (PULSEAUDIO_FOUND) + +if (CANBERRA_FOUND) + add_definitions(-DHAVE_CANBERRA) + + include_directories(${CANBERRA_INCLUDE_DIRS}) +endif (CANBERRA_FOUND) + + +set(kmix_adaptor_SRCS + dbus/dbusmixerwrapper.cpp + dbus/dbusmixsetwrapper.cpp + dbus/dbuscontrolwrapper.cpp) +qt4_add_dbus_adaptor( kmix_adaptor_SRCS dbus/org.kde.kmix.control.xml + dbus/dbuscontrolwrapper.h DBusControlWrapper ) +qt4_add_dbus_adaptor( kmix_adaptor_SRCS dbus/org.kde.kmix.mixer.xml + dbus/dbusmixerwrapper.h DBusMixerWrapper ) +qt4_add_dbus_adaptor( kmix_adaptor_SRCS dbus/org.kde.kmix.mixset.xml + dbus/dbusmixsetwrapper.h DBusMixSetWrapper ) + +set(kmix_backend_SRCS + backends/mixer_backend.cpp + backends/mixer_mpris2.cpp + ) + +if (HAVE_LIBASOUND2) + set(kmix_backend_SRCS ${kmix_backend_SRCS} + backends/mixer_alsa9.cpp ) +endif (HAVE_LIBASOUND2) + +if (PULSEAUDIO_FOUND) + set(kmix_backend_SRCS ${kmix_backend_SRCS} + backends/mixer_pulse.cpp ) +endif (PULSEAUDIO_FOUND) + +set(kmix_KDEINIT_SRCS ${kmix_adaptor_SRCS} ${kmix_backend_SRCS} + apps/main.cpp + apps/kmix.cpp + apps/KMixApp.cpp + gui/kmixdockwidget.cpp + gui/kmixprefdlg.cpp + gui/viewbase.cpp + gui/viewdockareapopup.cpp + gui/viewsliders.cpp + gui/mixdevicewidget.cpp + gui/mdwmoveaction.cpp + gui/mdwslider.cpp + gui/mdwenum.cpp + gui/kmixerwidget.cpp + gui/ksmallslider.cpp + gui/verticaltext.cpp + gui/volumeslider.cpp + gui/kmixtoolbox.cpp + gui/dialogaddview.cpp + gui/dialogviewconfiguration.cpp + gui/dialogselectmaster.cpp + gui/dialogchoosebackends.cpp + gui/guiprofile.cpp + gui/osdwidget.cpp + core/MediaController.cpp + core/mixertoolbox.cpp + core/kmixdevicemanager.cpp + core/ControlManager.cpp +# core/ControlPool.cpp + core/GlobalConfig.cpp + core/MasterControl.cpp + core/mixer.cpp + core/mixset.cpp + core/mixdevice.cpp + core/mixdevicecomposite.cpp + core/volume.cpp + ) + +kde4_add_kdeinit_executable( kmix ${kmix_KDEINIT_SRCS}) + +target_link_libraries(kdeinit_kmix ${KDE4_SOLID_LIBS} ${KDE4_KDEUI_LIBS} ${KDE4_PLASMA_LIBS} ${QT_QTXML_LIBRARY} ${KDE4_PHONON_LIBS}) +#target_link_libraries(kdeinit_kmix ${KDE4_KUTILS_LIBS} /home/kde/workspace/kdelibs/build/lib/libsolid.so.4.7.0 ${KDE4_KDEUI_LIBS} ${KDE4_PLASMA_LIBS} ${QT_QTXML_LIBRARY}) + +if (HAVE_LIBASOUND2) + target_link_libraries(kdeinit_kmix ${ASOUND_LIBRARY}) +endif (HAVE_LIBASOUND2) + +if (PULSEAUDIO_FOUND) + target_link_libraries(kdeinit_kmix ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} ${GLIB2_LIBRARIES}) +endif (PULSEAUDIO_FOUND) + +if (CANBERRA_FOUND) + target_link_libraries(kdeinit_kmix ${CANBERRA_LIBRARIES}) +endif (CANBERRA_FOUND) + +install(TARGETS kdeinit_kmix DESTINATION ${LIB_INSTALL_DIR} ) + +target_link_libraries( kmix kdeinit_kmix ) +install(TARGETS kmix ${INSTALL_TARGETS_DEFAULT_ARGS} ) + +########### next target ############### + + set(kded_kmixd_SRCS ${kmix_adaptor_SRCS} ${kmix_backend_SRCS} + apps/kmixd.cpp + core/ControlManager.cpp +# core/ControlPool.cpp + core/GlobalConfig.cpp + core/MasterControl.cpp + core/MediaController.cpp + core/mixer.cpp + core/mixset.cpp + core/mixdevice.cpp + core/volume.cpp + core/mixertoolbox.cpp + core/kmixdevicemanager.cpp + ) + +#qt4_add_dbus_adaptor(kded_kmixd_SRCS org.kde.KMixD.xml kmixd.h Mixer) + +kde4_add_plugin(kded_kmixd ${kded_kmixd_SRCS}) + + +target_link_libraries(kded_kmixd ${KDE4_KDEUI_LIBS} ${KDE4_SOLID_LIBS} ${QT_QTXML_LIBRARY} ${KDE4_PHONON_LIBS}) + +if (HAVE_LIBASOUND2) + target_link_libraries(kded_kmixd ${ASOUND_LIBRARY}) +endif (HAVE_LIBASOUND2) + +if (PULSEAUDIO_FOUND) + target_link_libraries(kded_kmixd ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} ${GLIB2_LIBRARIES}) +endif (PULSEAUDIO_FOUND) + +if (CANBERRA_FOUND) + target_link_libraries(kded_kmixd ${CANBERRA_LIBRARIES}) +endif (CANBERRA_FOUND) + +install(TARGETS kded_kmixd DESTINATION ${PLUGIN_INSTALL_DIR}) + +#target_link_libraries( kmixd kded_kmixd ) +#install(TARGETS kmixd DESTINATION ${PLUGIN_INSTALL_DIR} ) + +install( FILES kmixd.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kded ) + +########### next target ############### + +set(kmixctrl_KDEINIT_SRCS ${kmix_adaptor_SRCS} ${kmix_backend_SRCS} + apps/kmixctrl.cpp + core/ControlManager.cpp +# core/ControlPool.cpp + core/GlobalConfig.cpp + core/MasterControl.cpp + core/MediaController.cpp + core/mixer.cpp + core/mixset.cpp + core/mixdevice.cpp + core/volume.cpp + core/mixertoolbox.cpp + core/kmixdevicemanager.cpp + ) + +# gui/guiprofile.cpp + + +kde4_add_kdeinit_executable( kmixctrl ${kmixctrl_KDEINIT_SRCS}) + +target_link_libraries(kdeinit_kmixctrl ${KDE4_KDEUI_LIBS} ${KDE4_SOLID_LIBS} ${QT_QTXML_LIBRARY} ${KDE4_PHONON_LIBS}) + +if (HAVE_LIBASOUND2) + target_link_libraries(kdeinit_kmixctrl ${ASOUND_LIBRARY}) +endif (HAVE_LIBASOUND2) + +if (PULSEAUDIO_FOUND) + target_link_libraries(kdeinit_kmixctrl ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} ${GLIB2_LIBRARIES}) +endif (PULSEAUDIO_FOUND) + +if (CANBERRA_FOUND) + target_link_libraries(kdeinit_kmixctrl ${CANBERRA_LIBRARIES}) +endif (CANBERRA_FOUND) + +########### next target ############### +add_subdirectory( plasma ) + +install( TARGETS kdeinit_kmixctrl DESTINATION ${LIB_INSTALL_DIR} ) + +target_link_libraries( kmixctrl kdeinit_kmixctrl ) +install( TARGETS kmixctrl ${INSTALL_TARGETS_DEFAULT_ARGS} ) + +install( PROGRAMS kmix.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) +install( PROGRAMS apps/kmixremote DESTINATION ${BIN_INSTALL_DIR} ) +install( FILES restore_kmix_volumes.desktop DESTINATION ${AUTOSTART_INSTALL_DIR}) +install( FILES kmix_autostart.desktop DESTINATION ${AUTOSTART_INSTALL_DIR}) +install( FILES kmixui.rc DESTINATION ${DATA_INSTALL_DIR}/kmix ) +install( FILES kmixctrl_restore.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +install( FILES dbus/org.kde.kmix.control.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR} ) +install( FILES dbus/org.kde.kmix.mixer.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR} ) +install( FILES dbus/org.kde.kmix.mixset.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR} ) + +macro_display_feature_log() diff --git a/kmix/COPYING b/kmix/COPYING new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/kmix/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/kmix/COPYING.DOC b/kmix/COPYING.DOC new file mode 100644 index 00000000..a988da5a --- /dev/null +++ b/kmix/COPYING.DOC @@ -0,0 +1,397 @@ + GNU Free Documentation License + Version 1.2, November 2002 + + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/kmix/COPYING.LIB b/kmix/COPYING.LIB new file mode 100644 index 00000000..5bc8fb2c --- /dev/null +++ b/kmix/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/kmix/ChangeLog b/kmix/ChangeLog new file mode 100644 index 00000000..02f72dc2 --- /dev/null +++ b/kmix/ChangeLog @@ -0,0 +1,55 @@ +KDE v4.13 +- Configuration menu is now using Tabs to provide a more standard, convenient and pleasing layout +- Sound Menu: Play button reflects playback status and shows either "Play" or "Pause" + +- Bugfixes and Features: +Bug ID Severity Summary +317926 critical kmix start delayed on 20 seconds +315383 normal mpris volume/play status not in sync with app in kmix applet +299477 normal KMix Does Not Unmute Master Audio Channel +256854 normal Tray popup obscures vertical panel +304144 normal KMix does not distinguish separate channels on one volume control +296951 normal Tray plasmoid scroll/hiding issues [read description] +303608 crash Kmix crashed after selecting Amarok as master and instantly crashed when changing song. Volumes in exeption of the integrated subwoofer go fine; the built-in woofer must be changed trough Alsa, and Amarok must be volumed up/down by it's bar. +214854 normal kmix size is not restored by session management, only position. +319600 major KMix volume icon is unstale during playback + + +KDE v4.11 +- Full Sound Menu: Media Player control support + +v3.8 +- Feature : KMix has now a profile for TerraTec DMX6Fire cards and is able to handle it's weird volume controls + +v3.6 +- This is the KDE 4.4 version +- Wish 166591: "Integrate OSS4 support". +- Wish 70042: "What's this help documentation on channels (aka Tooltip)" +- Wish 57946: "horizontally movable kmix controls". Added GUI for ordering (w/o GUI it was possible since 2006-02-21) +- Wish 172883: "recording tab is gone". Working on it. Some issues left. most important: Controls might "disappear" in the written XML GUIProfile (duplicate handling)- +- Wish 188803: "No osd for mute/unmute" + +i001: When the user redefines the automatially grabbed XF86Volume* keys, he doesn't have an easy possibilty to get them back, because they are now marked as "no shortcut" in the global shortcut registry. The user must go the KDE system settings module "global shortcuts" and reset them to their default. No user will wfind that!!! So this should be possible inside KMix (ShortcutsDialog or a button "reset volume keys" in the configuration dialog). + +v3.5 +- This is the KDE 4.2 head version +- Wish 132330: On-screen display of volume level +- Wish 157701: Smaller neater dockarea popup +- Bug 161393: kmix does not associate hotplugged USB HID volume control +- Bug 168658: kmix - master channel missing +- Bug 172958: OSD doesn't update when volume is changed using keyboard +- Feature : Automatically grab XF86VolumeUp, XF86VolumeDown and XF86VolumeMute (done) + +v3.0 +- KDE 4 version + +V1.91 +- Multiple soundcards on ALSA are now supported +- Mixer device names are now shown with a vertical label (saves space and looks nice) +- MixerDevice categories. Allows to distribute devices on "New Mixer Tab...". It is used + in the panel applet to show only important devices initally. +- Much nicer "New Mixer Tab..." dialog + +V1.90 +Version shipped with KDE3.1 + diff --git a/kmix/ConfigureChecks.cmake b/kmix/ConfigureChecks.cmake new file mode 100644 index 00000000..8e653b2d --- /dev/null +++ b/kmix/ConfigureChecks.cmake @@ -0,0 +1,28 @@ +include(CheckIncludeFiles) +include(CheckTypeSize) +include(CheckStructMember) +include(MacroBoolTo01) + +# The FindKDE4.cmake module sets _KDE4_PLATFORM_DEFINITIONS with +# definitions like _GNU_SOURCE that are needed on each platform. +set(CMAKE_REQUIRED_DEFINITIONS ${_KDE4_PLATFORM_DEFINITIONS}) + +macro_bool_to_01(OGGVORBIS_FOUND HAVE_VORBIS) + +#now check for dlfcn.h using the cmake supplied CHECK_include_FILE() macro +# If definitions like -D_GNU_SOURCE are needed for these checks they +# should be added to _KDE4_PLATFORM_DEFINITIONS when it is originally +# defined outside this file. Here we include these definitions in +# CMAKE_REQUIRED_DEFINITIONS so they will be included in the build of +# checks below. +set(CMAKE_REQUIRED_DEFINITIONS ${_KDE4_PLATFORM_DEFINITIONS}) +if (WIN32) + set(CMAKE_REQUIRED_LIBRARIES ${KDEWIN32_LIBRARIES} ) + set(CMAKE_REQUIRED_INCLUDES ${KDEWIN32_INCLUDES} ) +endif (WIN32) + +check_include_files(machine/endian.h HAVE_MACHINE_ENDIAN_H) +# Linux has , FreeBSD has and Solaris has neither. +check_include_files(endian.h HAVE_ENDIAN_H) +check_include_files(sys/endian.h HAVE_SYS_ENDIAN_H) +check_include_files(unistd.h HAVE_UNISTD_H) diff --git a/kmix/Messages.sh b/kmix/Messages.sh new file mode 100644 index 00000000..917fb6c5 --- /dev/null +++ b/kmix/Messages.sh @@ -0,0 +1,4 @@ +#! /bin/sh +$EXTRACTRC *.rc *.ui >> rc.cpp +$XGETTEXT `find . -name '*.cpp' | grep -v '/tests/'` -o $podir/kmix.pot +rm -f rc.cpp diff --git a/kmix/TODO b/kmix/TODO new file mode 100644 index 00000000..d3d4cc1b --- /dev/null +++ b/kmix/TODO @@ -0,0 +1,91 @@ +TODO list + +KDE4.3 +1) Add feature list to KDE site + +2) Add Icon and text to the OSD (optional/config?) + +3) Show OSD for every global key assignment (optional/config?) + +4) Warn about standard shortcuts (XF86AudioVolume*) assigned to something else (another App, or another Control in KMix). + + +KDE4 +1) Remove ComboBox and replace it by a TabBar "corner widget" (Postponed: Not for KDE4.0) +(like the konsole session menu) + +2) Replace "confusing green LED" (DONE) + +3) Replace "confusing red LED" (DONE) + +4) Change "id" of capture controls in mixer_alsa9.cpp. (DONE) +Rationale: We need this, so that no control id conflicts arise. +e.g. if there is a "PCM play" and a "PCM capture", everything will work strange. +Especially saving and restoring will fail. But there can also be a lot of runtime issues, +at ANY place that uses mixer ID's (and there are a lot!) + +5) Move balance slider out of bottom and make it a virtual control. +Status: Pending + +6) Change everything in the source code to a consistent terminology: + The piece of hardware is now called: Card + The devices/slider/controllable-thingy is now called: Control +Status: pending + +7) Several bug reports, including the Audigy stuff. (DONE) + +8) Resolve show issue after starting KMix (DONE) + +9) Get Global Keyboard Shurtcuts work again (Check below pending) + Check, whether ALL controls now have a global shortcut + entry in the global KDE shortcuts "kcm" module : + ... they should NOT, but instead only those who have actually been assigned one) + +10) Layout: KMix can be made so small that Labels are partially hidden (DONE) + +11) Layout: MDWSwitch layout has to be redone (Pending) + +------------------------------------------------------------------------------------ + +Bug2 in KMix2.1pre: +- Keys not saved/restored (DONE) +- DockApplet shows "show/hide menubar" (DONE) +- "reversed" in panelapplet is broken. Remove it for now (DONE) +- Initial paint of sliders in KMixApplet is broken. (DONE) +- PanelApplet width wrong. (DONE) + +IMPORTANT: +1) Get Switches working +MUTE-LED's : Read: OK, Click: OK , Saved/Restored: no (was not in KMix2.0 and is shifted) +Record-LED's: Read: OK, Click: OK , Saved/Restored: yes +Switches : Read: OK, Click: OK , Saved/Restored: yes +Dockarea : Read: OK, Click: OK , Saved/Restored: n/a (its a view, not HW) + +2a) Splitted sliders and balance +OK +Splitted sliders: Read: OK, Click: OK, Drag: OK, Wheel: OK +Balance slider : OK (works like in KMix2.0) + +3) Mouse wheel +OK + +4) Make Volume Tip work (is currently always "0%") +OK + +5) Switches MUST be restored properly +OK + +6) kmixapplet restoring and working +OK + +7) Keys are not saved +OK + + + +14 december 2002 - Helio Chissini de Castro +- Figure out devices like SBLive with external Live Drives and their multiple in/outs. As a sample, using headphone output from live drive, mute switch not work as they must, but we need mutting the headphone lfe and center channel to really mute headphone output. + +- Introduce a new widget to enable route control in pro's ( Turtle ) and pro like ( Audigy, Live ) cards. + + diff --git a/kmix/TestCases b/kmix/TestCases new file mode 100644 index 00000000..b495bbbc --- /dev/null +++ b/kmix/TestCases @@ -0,0 +1,36 @@ +These are the recommended test cases for KMix. +They should be tested before a new KDE version is being shipped. + +01: Basic: Start KMix with existing profile (kmixrc) +02: Basic: Close KMix -> Dock +03: Basic: Close KMix -> Exit +04: Show/Hide Labels +05: Show/Hide Tickmarks +06: New Mixer Tab ("distribute" and "no distribute") +07: System tray volume control +08: Start KMix with no Mixer Hardware/Drivers available +09: Basic: Start KMix with NO existing profile (kmixrc) +10: Basic: KMix KControl module +11: kmixctrl --restore (with existing .kmixctrlrc) must restore volumes +12: kmixctrl --restore (without existing .kmixctrlrc) must NOT change volumes +13: "Switch-only" controls are available and work (e.g: IEC958': Capabilities: pswitch pswitch-joined cswitch cswitch-joined) +14: Rapid stream creation/deletion works properly (KMix shows and hides the controls, no crash). for i in 1 2 3 4 5 6 7 8 9 10; do paplay /Multimedia/Kennedy_berliner.ogg& done + + + +-----------+----+----+----+----+----+----+----+----+----+----+----+----+ +TestCase> | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | +Version | | | | | | | | | | | | | +-----------+----+----+----+----+----+----+----+----+----+----+----+----+ +CVS20030502| OK | OK | OK | OK | OK | OK | OK | | | | | | +CVS20031102| | | | | | na | | | | OK | | | +KDE3.2 | OK | | | | | na | | | | | | | +KDE3.4 | OK | OK | OK | OK | OK | na | OK | OK | OK | na | OK | OK | +-----------+----+----+----+----+----+----+----+----+----+----+----+----+ + +Test case results: +OK : Fully OK + : Not tested +xx : Failure +na : not applicable (Feature discontinued) + diff --git a/kmix/apps/KMixApp.cpp b/kmix/apps/KMixApp.cpp new file mode 100644 index 00000000..7f018ce4 --- /dev/null +++ b/kmix/apps/KMixApp.cpp @@ -0,0 +1,141 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright (C) 2000 Stefan Schimanski + * Copyright (C) 2001 Preston Brown + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "KMixApp.h" +#include "apps/kmix.h" +#include "core/ControlManager.h" +#include "core/GlobalConfig.h" +#include + + +bool KMixApp::_keepVisibility = false; + +KMixApp::KMixApp() + : KUniqueApplication(), m_kmix( 0 ) +{ + GlobalConfig::init(); + + // We must disable QuitOnLastWindowClosed. Rationale: + // 1) The normal state of KMix is to only have the dock icon shown. + // 2a) The dock icon gets reconstructed, whenever a soundcard is hotplugged or unplugged. + // 2b) The dock icon gets reconstructed, when the user selects a new master. + // 3) During the reconstruction, it can easily happen that no window is present => KMix would quit + // => disable QuitOnLastWindowClosed + setQuitOnLastWindowClosed ( false ); +} + + +KMixApp::~KMixApp() +{ + ControlManager::instance().shutdownNow(); + delete m_kmix; +} + + +int +KMixApp::newInstance() +{ + // There are 3 cases for a new instance + + //kDebug(67100) << "KMixApp::newInstance() isRestored()=" << isRestored() << "_keepVisibility=" << _keepVisibility; + static bool first = true; + if ( !first ) + { // There already exists an instance/window + + /* !!! @bug : _keepVisibilty has the wrong value here. + It is supposed to have the value set by the command line + arg, and the keepVisibilty() method. + All looks fine, BUT(!!!) THIS code is NEVER entered in + the just started process. + KDE IPC (DBUS) has instead notified the already running + KMix process, about a newInstance(). So _keepVisibilty + has always the value of the first started KMix process. + This is a bug in KMix and must be fixed. + cesken, 2008-11-01 + */ + + kDebug(67100) << "KMixApp::newInstance() Instance exists"; + + if ( ! _keepVisibility && !isSessionRestored() ) { + kDebug(67100) << "KMixApp::newInstance() SHOW WINDOW (_keepVisibility=" << _keepVisibility << ", isSessionRestored=" << isSessionRestored(); + // CASE 1: If KMix is running AND the *USER* + // starts it again, the KMix main window will be shown. + // If KMix is restored by SM or the --keepvisibilty is used, KMix will NOT + // explicitly be shown. + KUniqueApplication::newInstance(); +// if ( !m_kmix ) { +// m_kmix->show(); +// } else { +// kWarning(67100) << "KMixApp::newInstance() Window has not finished constructing yet so ignoring the show() request."; +// } + } + else { + // CASE 2: If KMix is running, AND ( session gets restored OR keepvisibilty command line switch ) + kDebug(67100) << "KMixApp::newInstance() REGULAR_START _keepVisibility=" << _keepVisibility; + // Special case: Command line arg --keepVisibility was used: + // We don't want to change the visibiliy, thus we don't call show() here. + // + // Hint: --keepVisibility is a special option for applications that + // want to start a mixer service, but don't need to show the KMix + // GUI (like KMilo , KAlarm, ...). + // See (e.g.) Bug 58901 for deeper insight. + } + } + else + { + // CASE 3: KMix was not running yet => instanciate a new one + //kDebug(67100) << "KMixApp::newInstance() Instanciate: _keepVisibility=" << _keepVisibility ; + first = false; // NB See https://qa.mandriva.com/show_bug.cgi?id=56893#c3 + // It is important to track this via a separate variable and not + // based on m_kmix to handle this race condition. + // Specific protection for the activation-prior-to-full-construction + // case exists above in the 'already running case' + GlobalConfig::init(); + m_kmix = new KMixWindow(_keepVisibility); + //connect(this, SIGNAL(stopUpdatesOnVisibility()), m_kmix, SLOT(stopVisibilityUpdates())); + if ( isSessionRestored() && KMainWindow::canBeRestored(0) ) + { + m_kmix->restore(0, false); + } + } + + return 0; +} + +void KMixApp::keepVisibility(bool val_keepVisibility) { + _keepVisibility = val_keepVisibility; +} + +/* +void +KMixApp::quitExtended() +{ + // This method is here to quit hold from the dock icon: When directly calling + // quit(), the main window will be hidden before saving the configuration. + // isVisible() would return on quit always false (which would be bad). + kDebug(67100) << "quitExtended ENTER"; + emit stopUpdatesOnVisibility(); + quit(); +} +*/ + + +#include "KMixApp.moc" diff --git a/kmix/apps/KMixApp.h b/kmix/apps/KMixApp.h new file mode 100644 index 00000000..f1a614d2 --- /dev/null +++ b/kmix/apps/KMixApp.h @@ -0,0 +1,48 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef KMixApp_h +#define KMixApp_h + +#include + +class KMixWindow; + +class KMixApp : public KUniqueApplication +{ +Q_OBJECT + public: + KMixApp(); + ~KMixApp(); + int newInstance (); + + public slots: + //void quitExtended(); // For a hack on visibility() + static void keepVisibility(bool); +/* + signals: + void stopUpdatesOnVisibility(); +*/ + private: + KMixWindow *m_kmix; + static bool _keepVisibility; +}; + +#endif diff --git a/kmix/apps/kmix.cpp b/kmix/apps/kmix.cpp new file mode 100644 index 00000000..9e0f806c --- /dev/null +++ b/kmix/apps/kmix.cpp @@ -0,0 +1,1376 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 1996-2014 The KMix authors. Maintainer: Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "apps/kmix.h" + +// include files for QT +#include +#include +#include +#include +#include +#include +#include + +// include files for KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KMix +#include "gui/guiprofile.h" +#include "core/ControlManager.h" +#include "core/GlobalConfig.h" +#include "core/MasterControl.h" +#include "core/MediaController.h" +#include "core/mixertoolbox.h" +#include "core/kmixdevicemanager.h" +#include "gui/kmixerwidget.h" +#include "gui/kmixprefdlg.h" +#include "gui/kmixdockwidget.h" +#include "gui/kmixtoolbox.h" +#include "core/version.h" +#include "gui/viewdockareapopup.h" +#include "gui/dialogaddview.h" +#include "gui/dialogselectmaster.h" +#include "dbus/dbusmixsetwrapper.h" +#include "gui/osdwidget.h" + + +/* KMixWindow + * Constructs a mixer window (KMix main window) + */ + +KMixWindow::KMixWindow(bool invisible) : + KXmlGuiWindow(0, + Qt::WindowFlags( + KDE_DEFAULT_WINDOWFLAGS | Qt::WindowContextHelpButtonHint)), m_multiDriverMode(false), // -<- I never-ever want the multi-drivermode to be activated by accident + m_dockWidget(), m_dsm(0), m_dontSetDefaultCardOnStart(false) +{ + setObjectName(QLatin1String("KMixWindow")); + // disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in + setAttribute(Qt::WA_DeleteOnClose, false); + + initActions(); // init actions first, so we can use them in the loadConfig() already + loadConfig(); // Load config before initMixer(), e.g. due to "MultiDriver" keyword + initActionsLate(); // init actions that require a loaded config + KGlobal::locale()->insertCatalog(QLatin1String("kmix-controls")); + initWidgets(); + initPrefDlg(); + DBusMixSetWrapper::initialize(this, "/Mixers"); + MixerToolBox::instance()->initMixer(m_multiDriverMode, m_backendFilter, + m_hwInfoString); + KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance(); + initActionsAfterInitMixer(); // init actions that require initialized mixer backend(s). + + recreateGUI(false); + if (m_wsMixers->count() < 1) + { + // Something is wrong. Perhaps a hardware or driver or backend change. Let KMix search harder + recreateGUI(false, QString(), true); + } + + if (!kapp->isSessionRestored() ) // done by the session manager otherwise + setInitialSize(); + + fixConfigAfterRead(); + theKMixDeviceManager->initHotplug(); + connect(theKMixDeviceManager, SIGNAL(plugged(const char*,QString,QString&)), + SLOT (plugged(const char*,QString,QString&))); + connect(theKMixDeviceManager, SIGNAL(unplugged(QString)), + SLOT (unplugged(QString))); + if (m_startVisible && !invisible) + show(); // Started visible + + connect(kapp, SIGNAL(aboutToQuit()), SLOT(saveConfig()) ); + + ControlManager::instance().addListener( + QString(), // All mixers (as the Global master Mixer might change) + (ControlChangeType::Type)(ControlChangeType::ControlList | ControlChangeType::MasterChanged), + this, + QString("KMixWindow") + ); + + // Send an initial volume refresh (otherwise all volumes are 0 until the next change) + ControlManager::instance().announce(QString(), ControlChangeType::Volume, QString("Startup")); +} + +KMixWindow::~KMixWindow() +{ + ControlManager::instance().removeListener(this); + + delete m_dsm; + delete osdWidget; + + // -1- Cleanup Memory: clearMixerWidgets + while (m_wsMixers->count() != 0) + { + QWidget *mw = m_wsMixers->widget(0); + m_wsMixers->removeTab(0); + delete mw; + } + + // -2- Mixer HW + MixerToolBox::instance()->deinitMixer(); + + // -3- Action collection (just to please Valgrind) + actionCollection()->clear(); + + // GUIProfile cache should be cleared very very late, as GUIProfile instances are used in the Views, which + // means main window and potentially also in the tray popup (at least we might do so in the future). + // This place here could be to early, if we would start to GUIProfile outside KMixWIndow, e.g. in the tray popup. + // Until we do so, this is the best place to call clearCache(). Later, e.g. in main() would likely be problematic. + + GUIProfile::clearCache(); + + +} + + + +void KMixWindow::controlsChange(int changeType) +{ + ControlChangeType::Type type = ControlChangeType::fromInt(changeType); + switch (type ) + { + case ControlChangeType::ControlList: + case ControlChangeType::MasterChanged: + updateDocking(); + break; + + default: + ControlManager::warnUnexpectedChangeType(type, this); + break; + } + +} + +void +KMixWindow::initActions() +{ + // file menu + KStandardAction::quit(this, SLOT(quit()), actionCollection()); + + // settings menu + _actionShowMenubar = KStandardAction::showMenubar(this, SLOT(toggleMenuBar()), + actionCollection()); + //actionCollection()->addAction( a->objectName(), a ); + KStandardAction::preferences(this, SLOT(showSettings()), actionCollection()); + KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), + actionCollection()); + KAction* action = actionCollection()->addAction("launch_kdesoundsetup"); + action->setText(i18n("Audio Setup")); + connect(action, SIGNAL(triggered(bool)), SLOT(slotKdeAudioSetupExec())); + + action = actionCollection()->addAction("hwinfo"); + action->setText(i18n("Hardware &Information")); + connect(action, SIGNAL(triggered(bool)), SLOT(slotHWInfo())); + action = actionCollection()->addAction("hide_kmixwindow"); + action->setText(i18n("Hide Mixer Window")); + connect(action, SIGNAL(triggered(bool)), SLOT(hideOrClose())); + action->setShortcut(QKeySequence(Qt::Key_Escape)); + action = actionCollection()->addAction("toggle_channels_currentview"); + action->setText(i18n("Configure &Channels...")); + connect(action, SIGNAL(triggered(bool)), SLOT(slotConfigureCurrentView())); + action = actionCollection()->addAction("select_master"); + action->setText(i18n("Select Master Channel...")); + connect(action, SIGNAL(triggered(bool)), SLOT(slotSelectMaster())); + + action = actionCollection()->addAction("save_1"); + action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_1)); + action->setText(i18n("Save volume profile 1")); + connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes1())); + + action = actionCollection()->addAction("save_2"); + action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_2)); + action->setText(i18n("Save volume profile 2")); + connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes2())); + + action = actionCollection()->addAction("save_3"); + action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_3)); + action->setText(i18n("Save volume profile 3")); + connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes3())); + + action = actionCollection()->addAction("save_4"); + action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_4)); + action->setText(i18n("Save volume profile 4")); + connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes4())); + + action = actionCollection()->addAction("load_1"); + action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_1)); + action->setText(i18n("Load volume profile 1")); + connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes1())); + + action = actionCollection()->addAction("load_2"); + action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_2)); + action->setText(i18n("Load volume profile 2")); + connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes2())); + + action = actionCollection()->addAction("load_3"); + action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_3)); + action->setText(i18n("Load volume profile 3")); + connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes3())); + + action = actionCollection()->addAction("load_4"); + action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_4)); + action->setText(i18n("Load volume profile 4")); + connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes4())); + + osdWidget = new OSDWidget(); + + createGUI(QLatin1String("kmixui.rc")); +} + +void +KMixWindow::initActionsLate() +{ + if (m_autouseMultimediaKeys) + { + KAction* globalAction = actionCollection()->addAction("increase_volume"); + globalAction->setText(i18n("Increase Volume")); + globalAction->setGlobalShortcut(KShortcut(Qt::Key_VolumeUp)); + connect(globalAction, SIGNAL(triggered(bool)), + SLOT(slotIncreaseVolume())); + + globalAction = actionCollection()->addAction("decrease_volume"); + globalAction->setText(i18n("Decrease Volume")); + globalAction->setGlobalShortcut(KShortcut(Qt::Key_VolumeDown)); + connect(globalAction, SIGNAL(triggered(bool)), + SLOT(slotDecreaseVolume())); + + globalAction = actionCollection()->addAction("mute"); + globalAction->setText(i18n("Mute")); + globalAction->setGlobalShortcut(KShortcut(Qt::Key_VolumeMute)); + connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotMute())); + } +} + +void +KMixWindow::initActionsAfterInitMixer() +{ + // Only show the new tab widget if Pulseaudio is not used. Hint: The Pulseaudio backend always + // runs with 4 fixed Tabs. + if (!Mixer::pulseaudioPresent()) + { + QPixmap cornerNewPM = KIconLoader::global()->loadIcon("tab-new", KIconLoader::Toolbar, KIconLoader::SizeSmall); + QPushButton* _cornerLabelNew = new QPushButton(); + _cornerLabelNew->setIcon(cornerNewPM); + //cornerLabelNew->setSizePolicy(QSizePolicy()); + m_wsMixers->setCornerWidget(_cornerLabelNew, Qt::TopLeftCorner); + connect(_cornerLabelNew, SIGNAL(clicked()), SLOT (newView())); + } +} + +void +KMixWindow::initPrefDlg() +{ + KMixPrefDlg* prefDlg = KMixPrefDlg::createInstance(this, GlobalConfig::instance()); + connect(prefDlg, SIGNAL(kmixConfigHasChanged()), SLOT(applyPrefs())); +} + +void +KMixWindow::initWidgets() +{ + m_wsMixers = new KTabWidget(); + m_wsMixers->setDocumentMode(true); + setCentralWidget(m_wsMixers); + m_wsMixers->setTabsClosable(false); + connect(m_wsMixers, SIGNAL(tabCloseRequested(int)), + SLOT(saveAndCloseView(int))); + + QPixmap cornerNewPM = KIconLoader::global()->loadIcon("tab-new", + KIconLoader::Toolbar, KIconLoader::SizeSmall); + + connect(m_wsMixers, SIGNAL(currentChanged(int)), SLOT(newMixerShown(int))); + + // show menubar if the actions says so (or if the action does not exist) + menuBar()->setVisible( + (_actionShowMenubar == 0) || _actionShowMenubar->isChecked()); +} + +void +KMixWindow::setInitialSize() +{ + KConfigGroup config(KGlobal::config(), "Global"); + + // HACK: QTabWidget will bound its sizeHint to 200x200 unless scrollbuttons + // are disabled, so we disable them, get a decent sizehint and enable them + // back + m_wsMixers->setUsesScrollButtons(false); + QSize defSize = sizeHint(); + m_wsMixers->setUsesScrollButtons(true); + QSize size = config.readEntry("Size", defSize); + if (!size.isEmpty()) + resize(size); + + QPoint defPos = pos(); + QPoint pos = config.readEntry("Position", defPos); + move(pos); +} + + +void KMixWindow::removeDock() +{ + if (m_dockWidget) + { + m_dockWidget->deleteLater(); + m_dockWidget = 0; + } +} + +/** + * Creates or deletes the KMixDockWidget, depending on whether there is a Mixer instance available. + * + * @returns true, if the docking succeeded. Failure usually means that there + * was no suitable mixer control selected. + */ +bool KMixWindow::updateDocking() +{ + GlobalConfigData& gcd = GlobalConfig::instance().data; + + if ( !gcd.showDockWidget || Mixer::mixers().isEmpty()) + { + removeDock(); + return false; + } + if (!m_dockWidget) + { + m_dockWidget = new KMixDockWidget(this); + } + return true; +} +void +KMixWindow::saveConfig() +{ + saveBaseConfig(); + saveViewConfig(); + saveVolumes(); +#ifdef __GNUC_ +#warn We must Sync here, or we will lose configuration data. The reson for that is unknown. +#endif + + // TODO cesken The reason for not writing might be that we have multiple cascaded KConfig objects. I must migrate to KSharedConfig !!! + KGlobal::config()->sync(); + kDebug() + << "Saved config ... sync finished"; +} + +void KMixWindow::saveBaseConfig() +{ + GlobalConfig::instance().writeConfig(); + + KConfigGroup config(KGlobal::config(), "Global"); + + config.writeEntry("Size", size()); + config.writeEntry("Position", pos()); + // Cannot use isVisible() here, as in the "aboutToQuit()" case this widget is already hidden. + // (Please note that the problem was only there when quitting via Systray - esken). + // Using it again, as internal behaviour has changed with KDE4 + config.writeEntry("Visible", isVisible()); + config.writeEntry("Menubar", _actionShowMenubar->isChecked()); + config.writeEntry("Soundmenu.Mixers", GlobalConfig::instance().getMixersForSoundmenu().toList()); + + config.writeEntry("DefaultCardOnStart", m_defaultCardOnStart); + config.writeEntry("ConfigVersion", KMIX_CONFIG_VERSION); + config.writeEntry("AutoUseMultimediaKeys", m_autouseMultimediaKeys); + + MasterControl& master = Mixer::getGlobalMasterPreferred(); + if (master.isValid()) + { + config.writeEntry("MasterMixer", master.getCard()); + config.writeEntry("MasterMixerDevice", master.getControl()); + } + QString mixerIgnoreExpression = MixerToolBox::instance()->mixerIgnoreExpression(); + config.writeEntry("MixerIgnoreExpression", mixerIgnoreExpression); + + kDebug() + << "Base configuration saved"; +} + +void KMixWindow::saveViewConfig() +{ + QMap mixerViews; + + // The following loop is necessary for the case that the user has hidden all views for a Mixer instance. + // Otherwise we would not save the Meta information (step -2- below for that mixer. + // We also do not save dynamic mixers (e.g. PulseAudio) + foreach ( Mixer* mixer, Mixer::mixers() ) + { + if ( !mixer->isDynamic() ) + { + mixerViews[mixer->id()]; // just insert a map entry + } + } + + // -1- Save the views themselves + for (int i = 0; i < m_wsMixers->count(); ++i) + { + QWidget *w = m_wsMixers->widget(i); + if (w->inherits("KMixerWidget")) + { + KMixerWidget* mw = (KMixerWidget*) w; + // Here also Views are saved. even for Mixers that are closed. This is necessary when unplugging cards. + // Otherwise the user will be confused afer re-plugging the card (as the config was not saved). + mw->saveConfig(KGlobal::config().data()); + // add the view to the corresponding mixer list, so we can save a views-per-mixer list below + if (!mw->mixer()->isDynamic()) + { + QStringList& qsl = mixerViews[mw->mixer()->id()]; + qsl.append(mw->getGuiprof()->getId()); + } + } + } + + // -2- Save Meta-Information (which views, and in which order). views-per-mixer list + KConfigGroup pconfig(KGlobal::config(), "Profiles"); + QMap::const_iterator itEnd = mixerViews.constEnd(); + for (QMap::const_iterator it = mixerViews.constBegin(); it != itEnd; ++it) + { + const QString& mixerProfileKey = it.key(); // this is actually some mixer->id() + const QStringList& qslProfiles = it.value(); + pconfig.writeEntry(mixerProfileKey, qslProfiles); + kDebug() + << "Save Profile List for " << mixerProfileKey << ", number of views is " << qslProfiles.count(); + } + + kDebug() + << "View configuration saved"; +} + +/** + * Stores the volumes of all mixers Can be restored via loadVolumes() or + * the kmixctrl application. + */ +void +KMixWindow::saveVolumes() +{ + saveVolumes(QString()); +} + +void +KMixWindow::saveVolumes(QString postfix) +{ + const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix); + KConfig *cfg = new KConfig(kmixctrlRcFilename); + for (int i = 0; i < Mixer::mixers().count(); ++i) + { + Mixer *mixer = (Mixer::mixers())[i]; + if (mixer->isOpen()) + { // protect from unplugged devices (better do *not* save them) + mixer->volumeSave(cfg); + } + } + cfg->sync(); + delete cfg; + kDebug() + << "Volume configuration saved"; +} + +QString +KMixWindow::getKmixctrlRcFilename(QString postfix) +{ + QString kmixctrlRcFilename("kmixctrlrc"); + if (!postfix.isEmpty()) + { + kmixctrlRcFilename.append(".").append(postfix); + } + return kmixctrlRcFilename; +} + +void +KMixWindow::loadConfig() +{ + loadBaseConfig(); + + //loadViewConfig(); // mw->loadConfig() explicitly called always after creating mw. + //loadVolumes(); // not in use + + // create an initial snapshot, so we have a reference of the state before changes through the preferences dialog + configDataSnapshot = GlobalConfig::instance().data; +} + +void +KMixWindow::loadBaseConfig() +{ + KConfigGroup config(KGlobal::config(), "Global"); + +// GlobalConfig& gcfg = GlobalConfig::instance(); + GlobalConfigData& gcd = GlobalConfig::instance().data; + + QList preferredMixersInSoundMenu; + preferredMixersInSoundMenu = config.readEntry("Soundmenu.Mixers", preferredMixersInSoundMenu); + GlobalConfig::instance().setMixersForSoundmenu(preferredMixersInSoundMenu.toSet()); + + setBeepOnVolumeChange(gcd.volumeFeedback); + m_startVisible = config.readEntry("Visible", false); + m_multiDriverMode = config.readEntry("MultiDriver", false); + m_defaultCardOnStart = config.readEntry("DefaultCardOnStart", ""); + m_configVersion = config.readEntry("ConfigVersion", 0); + // WARNING Don't overwrite m_configVersion with the "correct" value, before having it + // evaluated. Better only write that in saveBaseConfig() + m_autouseMultimediaKeys = config.readEntry("AutoUseMultimediaKeys", true); + QString mixerMasterCard = config.readEntry("MasterMixer", ""); + QString masterDev = config.readEntry("MasterMixerDevice", ""); + Mixer::setGlobalMaster(mixerMasterCard, masterDev, true); + QString mixerIgnoreExpression = config.readEntry("MixerIgnoreExpression", + "Modem"); + MixerToolBox::instance()->setMixerIgnoreExpression(mixerIgnoreExpression); + + // --- Advanced options, without GUI: START ------------------------------------- + QString volumePercentageStepString = config.readEntry("VolumePercentageStep"); + if (!volumePercentageStepString.isNull()) + { + float volumePercentageStep = volumePercentageStepString.toFloat(); + if (volumePercentageStep > 0 && volumePercentageStep <= 100) + Volume::VOLUME_STEP_DIVISOR = (100 / volumePercentageStep); + } + + // --- Advanced options, without GUI: END ------------------------------------- + + m_backendFilter = config.readEntry<>("Backends", QList()); + kDebug() << "Backends: " << m_backendFilter; + + // show/hide menu bar + bool showMenubar = config.readEntry("Menubar", true); + + if (_actionShowMenubar) + _actionShowMenubar->setChecked(showMenubar); +} + +/** + * Loads the volumes of all mixers from kmixctrlrc. + * In other words: + * Restores the default voumes as stored via saveVolumes() or the + * execution of "kmixctrl --save" + */ + +void +KMixWindow::loadVolumes() +{ + loadVolumes(QString()); +} + +void +KMixWindow::loadVolumes(QString postfix) +{ + kDebug() + << "About to load config (Volume)"; + const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix); + + KConfig *cfg = new KConfig(kmixctrlRcFilename); + for (int i = 0; i < Mixer::mixers().count(); ++i) + { + Mixer *mixer = (Mixer::mixers())[i]; + mixer->volumeLoad(cfg); + } + delete cfg; +} + + +void +KMixWindow::recreateGUIwithSavingView() +{ +// saveViewConfig(); + recreateGUI(true); +} + +void +KMixWindow::recreateGUI(bool saveConfig) +{ + recreateGUI(saveConfig, QString(), false); +} + +/** + * Create or recreate the Mixer GUI elements + * + * @param saveConfig Whether to save all View configurations before recreating + * @param forceNewTab To enforce opening a new tab, even when the profileList in the kmixrc is empty. + * It should only be set to "true" in case of a Hotplug (because then the user definitely expects a new Tab to show). + */ +void +KMixWindow::recreateGUI(bool saveConfig, const QString& mixerId, + bool forceNewTab) +{ + // -1- Remember which of the tabs is currently selected for restoration for re-insertion + int oldTabPosition = m_wsMixers->currentIndex(); + + if (saveConfig) + saveViewConfig(); // save the state before recreating + + // -2- RECREATE THE ALREADY EXISTING TABS ********************************** + QMap mixerHasProfile; + + // -2a- Build a list of all active profiles in the main window (that means: from all tabs) + QList activeGuiProfiles; + for (int i = 0; i < m_wsMixers->count(); ++i) + { + KMixerWidget* kmw = dynamic_cast(m_wsMixers->widget(i)); + if (kmw) + { + activeGuiProfiles.append(kmw->getGuiprof()); + } + } + + // TODO The following loop is a bit buggy, as it iterates over all cached Profiles. But that is wrong for Tabs that got closed. + // I need to loop over something else, e.g. a profile list built from the currently open Tabs. + // Or (if it that is easier) I might discard the Profile from the cache on "close-tab" (but that must also include unplug actions). + foreach( GUIProfile* guiprof, activeGuiProfiles){ + KMixerWidget* kmw = findKMWforTab(guiprof->getId()); + Mixer *mixer = Mixer::findMixer( guiprof->getMixerId() ); + if ( mixer == 0 ) + { + kError() << "MixerToolBox::find() hasn't found the Mixer for the profile " << guiprof->getId(); + continue; + } + mixerHasProfile[mixer] = true; + if ( kmw == 0 ) + { + // does not yet exist => create + addMixerWidget(mixer->id(), guiprof->getId(), -1); + } + else + { + // did exist => remove and insert new guiprof at old position + int indexOfTab = m_wsMixers->indexOf(kmw); + if ( indexOfTab != -1 ) m_wsMixers->removeTab(indexOfTab); + delete kmw; + addMixerWidget(mixer->id(), guiprof->getId(), indexOfTab); + } +} // Loop over all GUIProfile's + +// -3- ADD TABS FOR Mixer instances that have no tab yet ********************************** + KConfigGroup pconfig(KGlobal::config(), "Profiles"); + foreach ( Mixer *mixer, Mixer::mixers()){ + if ( mixerHasProfile.contains(mixer)) + { + continue; // OK, this mixer already has a profile => skip it + } + // No TAB YET => This should mean KMix is just started, or the user has just plugged in a card + bool profileListHasKey = false; + QStringList profileList; + bool aProfileWasAddedSucesufully = false; + + if ( !mixer->isDynamic() ) + { + // We do not support save profiles for dynamic mixers (i.e. PulseAudio) + + profileListHasKey = pconfig.hasKey( mixer->id() );// <<< SHOULD be before the following line + profileList = pconfig.readEntry( mixer->id(), QStringList() ); + + foreach ( QString profileId, profileList) + { + // This handles the profileList form the kmixrc + kDebug() << "Now searching for profile: " << profileId; + GUIProfile* guiprof = GUIProfile::find(mixer, profileId, true, false);// ### Card specific profile ### + if ( guiprof != 0 ) + { + addMixerWidget(mixer->id(), guiprof->getId(), -1); + aProfileWasAddedSucesufully = true; + } + else + { + kError() << "Cannot load profile " << profileId << " . It was removed by the user, or the KMix config file is defective."; + } + } + } + + // The we_need_a_fallback case is a bit tricky. Please ask the author (cesken) before even considering to change the code. + bool we_need_a_fallback = !aProfileWasAddedSucesufully;// we *possibly* want a fallback, if we couldn't add one + bool thisMixerShouldBeForced = forceNewTab && ( mixerId.isEmpty() || (mixer->id() == mixerId) ); + we_need_a_fallback = we_need_a_fallback && ( thisMixerShouldBeForced || !profileListHasKey );// Additional requirement: "forced-tab-for-this-mixer" OR "no key stored in kmixrc yet" + if ( we_need_a_fallback ) + { + // The profileList was empty or nothing could be loaded + // (Hint: This means the user cannot hide a device completely + + // Lets try a bunch of fallback strategies: + GUIProfile* guiprof = 0; + if ( !mixer->isDynamic() ) + { + // We know that GUIProfile::find() will return 0 if the mixer is dynamic, so don't bother checking. + kDebug() << "Attempting to find a card-specific GUI Profile for the mixer " << mixer->id(); + guiprof = GUIProfile::find(mixer, QString("default"), false, false);// ### Card specific profile ### + if ( guiprof == 0 ) + { + kDebug() << "Not found. Attempting to find a generic GUI Profile for the mixer " << mixer->id(); + guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ### + } + } + if ( guiprof == 0) + { + kDebug() << "Using fallback GUI Profile for the mixer " << mixer->id(); + // This means there is neither card specific nor card unspecific profile + // This is the case for some backends (as they don't ship profiles). + guiprof = GUIProfile::fallbackProfile(mixer); + } + + if ( guiprof != 0 ) + { + guiprof->setDirty(); // All fallback => dirty + addMixerWidget(mixer->id(), guiprof->getId(), -1); + } + else + { + kError() << "Cannot use ANY profile (including Fallback) for mixer " << mixer->id() << " . This is impossible, and thus this mixer can NOT be used."; + } + + } +} + mixerHasProfile.clear(); + + // -4- FINALIZE ********************************** + if (m_wsMixers->count() > 0) + { + if (oldTabPosition >= 0) + { + m_wsMixers->setCurrentIndex(oldTabPosition); + } + bool dockingSucceded = updateDocking(); + if (!dockingSucceded && !Mixer::mixers().empty()) + { + show(); // avoid invisible and unaccessible main window + } + } + else + { + // No soundcard found. Do not complain, but sit in the background, and wait for newly plugged soundcards. + updateDocking(); // -<- removes the DockIcon + hide(); + } + +} + +KMixerWidget* +KMixWindow::findKMWforTab(const QString& kmwId) +{ + for (int i = 0; i < m_wsMixers->count(); ++i) + { + KMixerWidget* kmw = (KMixerWidget*) m_wsMixers->widget(i); + if (kmw->getGuiprof()->getId() == kmwId) + { + return kmw; + } + } + return 0; +} + +void +KMixWindow::newView() +{ + if (Mixer::mixers().empty()) + { + kError() << "Trying to create a View, but no Mixer exists"; + return; // should never happen + } + + Mixer *mixer = Mixer::mixers()[0]; + QPointer dav = new DialogAddView(this, mixer); + int ret = dav->exec(); + + if (QDialog::Accepted == ret) + { + QString profileName = dav->getresultViewName(); + QString mixerId = dav->getresultMixerId(); + mixer = Mixer::findMixer(mixerId); + kDebug() + << ">>> mixer = " << mixerId << " -> " << mixer; + + GUIProfile* guiprof = GUIProfile::find(mixer, profileName, false, false); + if (guiprof == 0) + { + guiprof = GUIProfile::find(mixer, profileName, false, true); + } + + if (guiprof == 0) + { + static const QString msg( + i18n("Cannot add view - GUIProfile is invalid.")); + errorPopup(msg); + } + else + { + bool ret = addMixerWidget(mixer->id(), guiprof->getId(), -1); + if (ret == false) + { + errorPopup(i18n("View already exists. Cannot add View.")); + } + } + + delete dav; + } + + //kDebug() << "Exit"; +} + +/** + * Save the view and close it + * + * @arg idx The index in the TabWidget + */ +void +KMixWindow::saveAndCloseView(int idx) +{ + kDebug() + << "Enter"; + QWidget *w = m_wsMixers->widget(idx); + KMixerWidget* kmw = ::qobject_cast(w); + if (kmw) + { + kmw->saveConfig(KGlobal::config().data()); // -<- This alone is not enough, as I need to save the META information as well. Thus use saveViewConfig() below + m_wsMixers->removeTab(idx); + updateTabsClosable(); + saveViewConfig(); + delete kmw; + } + kDebug() + << "Exit"; +} + + +void +KMixWindow::fixConfigAfterRead() +{ + KConfigGroup grp(KGlobal::config(), "Global"); + unsigned int configVersion = grp.readEntry("ConfigVersion", 0); + if (configVersion < 3) + { + // Fix the "double Base" bug, by deleting all groups starting with "View.Base.Base.". + // The group has been copied over by KMixToolBox::loadView() for all soundcards, so + // we should be fine now + QStringList cfgGroups = KGlobal::config()->groupList(); + QStringListIterator it(cfgGroups); + while (it.hasNext()) + { + QString groupName = it.next(); + if (groupName.indexOf("View.Base.Base") == 0) + { + kDebug(67100) + << "Fixing group " << groupName; + KConfigGroup buggyDevgrpCG = KGlobal::config()->group(groupName); + buggyDevgrpCG.deleteGroup(); + } // remove buggy group + } // for all groups + } // if config version < 3 +} + +void +KMixWindow::plugged(const char* driverName, const QString& udi, QString& dev) +{ + kDebug() + << "Plugged: dev=" << dev << "(" << driverName << ") udi=" << udi << "\n"; + QString driverNameString; + driverNameString = driverName; + int devNum = dev.toInt(); + Mixer *mixer = new Mixer(driverNameString, devNum); + if (mixer != 0) + { + kDebug() + << "Plugged: dev=" << dev << "\n"; + MixerToolBox::instance()->possiblyAddMixer(mixer); + recreateGUI(true, mixer->id(), true); + } +} + +void +KMixWindow::unplugged(const QString& udi) +{ + kDebug() + << "Unplugged: udi=" << udi << "\n"; + for (int i = 0; i < Mixer::mixers().count(); ++i) + { + Mixer *mixer = (Mixer::mixers())[i]; + // kDebug(67100) << "Try Match with:" << mixer->udi() << "\n"; + if (mixer->udi() == udi) + { + kDebug() << "Unplugged Match: Removing udi=" << udi << "\n"; + //KMixToolBox::notification("MasterFallback", "aaa"); + bool globalMasterMixerDestroyed = (mixer == Mixer::getGlobalMasterMixer()); + // Part 1) Remove Tab + for (int i = 0; i < m_wsMixers->count(); ++i) + { + QWidget *w = m_wsMixers->widget(i); + KMixerWidget* kmw = ::qobject_cast(w); + if (kmw && kmw->mixer() == mixer) + { + saveAndCloseView(i); + i = -1; // Restart loop from scratch (indices are most likely invalidated at removeTab() ) + } + } + MixerToolBox::instance()->removeMixer(mixer); + // Check whether the Global Master disappeared, and select a new one if necessary + shared_ptr md = Mixer::getGlobalMasterMD(); + if (globalMasterMixerDestroyed || md.get() == 0) + { + // We don't know what the global master should be now. + // So lets play stupid, and just select the recommended master of the first device + if (Mixer::mixers().count() > 0) + { + shared_ptr master = + ((Mixer::mixers())[0])->getLocalMasterMD(); + if (master.get() != 0) + { + QString localMaster = master->id(); + Mixer::setGlobalMaster(((Mixer::mixers())[0])->id(), localMaster, false); + + QString text; + text = + i18n( + "The soundcard containing the master device was unplugged. Changing to control %1 on card %2.", + master->readableName(), + ((Mixer::mixers())[0])->readableName()); + KMixToolBox::notification("MasterFallback", text); + } + } + } + if (Mixer::mixers().count() == 0) + { + QString text; + text = i18n("The last soundcard was unplugged."); + KMixToolBox::notification("MasterFallback", text); + } + recreateGUI(true); + break; + } + } + +} + +/** + * Create a widget with an error message + * This widget shows an error message like "no mixers detected. + void KMixWindow::setErrorMixerWidget() + { + QString s = i18n("Please plug in your soundcard.No soundcard found. Probably you have not set it up or are missing soundcard drivers. Please check your operating system manual for installing your soundcard."); // !! better text + m_errorLabel = new QLabel( s,this ); + m_errorLabel->setAlignment( Qt::AlignCenter ); + m_errorLabel->setWordWrap(true); + m_errorLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + m_wsMixers->addTab( m_errorLabel, i18n("No soundcard found") ); + } + */ + +/** + * + */ +bool +KMixWindow::profileExists(QString guiProfileId) +{ + for (int i = 0; i < m_wsMixers->count(); ++i) + { + KMixerWidget* kmw = dynamic_cast(m_wsMixers->widget(i)); + if (kmw && kmw->getGuiprof()->getId() == guiProfileId) + return true; + } + return false; +} + +bool +KMixWindow::addMixerWidget(const QString& mixer_ID, QString guiprofId, int insertPosition) +{ + kDebug() << "Add " << guiprofId; + GUIProfile* guiprof = GUIProfile::find(guiprofId); + if (guiprof != 0 && profileExists(guiprof->getId())) // TODO Bad place. Should be checked in the add-tab-dialog + return false; // already present => don't add again + Mixer *mixer = Mixer::findMixer(mixer_ID); + if (mixer == 0) + return false; // no such Mixer + + // kDebug(67100) << "KMixWindow::addMixerWidget() " << mixer_ID << " is being added"; + ViewBase::ViewFlags vflags = ViewBase::HasMenuBar; + if ((_actionShowMenubar == 0) || _actionShowMenubar->isChecked()) + vflags |= ViewBase::MenuBarVisible; + if (GlobalConfig::instance().data.getToplevelOrientation() == Qt::Vertical) + vflags |= ViewBase::Horizontal; + else + vflags |= ViewBase::Vertical; + + KMixerWidget *kmw = new KMixerWidget(mixer, this, vflags, guiprofId, + actionCollection()); + /* A newly added mixer will automatically added at the top + * and thus the window title is also set appropriately */ + + /* + * Skip the name from the profile for now. I would at least have to do the '&' quoting for the tab label. But I am + * also not 100% sure whether the current name from the profile is any good - it does (likely) not even contain the + * card ID. This means you cannot distinguish between cards with an identical name. + */ +// QString tabLabel = guiprof->getName(); +// if (tabLabel.isEmpty()) +// QString tabLabel = kmw->mixer()->readableName(true); + + QString tabLabel = kmw->mixer()->readableName(true); + + m_dontSetDefaultCardOnStart = true; // inhibit implicit setting of m_defaultCardOnStart + + if (insertPosition == -1) + m_wsMixers->addTab(kmw, tabLabel); + else + m_wsMixers->insertTab(insertPosition, kmw, tabLabel); + + if (kmw->getGuiprof()->getId() == m_defaultCardOnStart) + { + m_wsMixers->setCurrentWidget(kmw); + } + + updateTabsClosable(); + m_dontSetDefaultCardOnStart = false; + + kmw->loadConfig(KGlobal::config().data()); + // Now force to read for new tabs, especially after hotplug. Note: Doing it here is bad design and possibly + // obsolete, as the backend should take care of upating itself. + kmw->mixer()->readSetFromHWforceUpdate(); + return true; +} + +void KMixWindow::updateTabsClosable() +{ + // Pulseaudio runs with 4 fixed tabs - don't allow to close them. + // Also do not allow to close the last view + m_wsMixers->setTabsClosable(!Mixer::pulseaudioPresent() && m_wsMixers->count() > 1); +} + +bool KMixWindow::queryClose() +{ + GlobalConfigData& gcd = GlobalConfig::instance().data; + if (gcd.showDockWidget && !kapp->sessionSaving() ) + { + // Hide (don't close and destroy), if docking is enabled. Except when session saving (shutdown) is in process. + hide(); + return false; + } + else + { + // Accept the close, if: + // The user has disabled docking + // or SessionSaving() is running + // kDebug(67100) << "close"; + return true; + } +} + +void KMixWindow::hideOrClose() +{ + GlobalConfigData& gcd = GlobalConfig::instance().data; + if (gcd.showDockWidget && m_dockWidget != 0) + { + // we can hide if there is a dock widget + hide(); + } + else + { + // if there is no dock widget, we will quit + quit(); + } +} + +// internal helper to prevent code duplication in slotIncreaseVolume and slotDecreaseVolume +void +KMixWindow::increaseOrDecreaseVolume(bool increase) +{ + Mixer* mixer = Mixer::getGlobalMasterMixer(); // only needed for the awkward construct below + if (mixer == 0) + return; // e.g. when no soundcard is available + shared_ptr md = Mixer::getGlobalMasterMD(); + if (md.get() == 0) + return; // shouldn't happen, but lets play safe + + Volume::VolumeTypeFlag volumeType = md->playbackVolume().hasVolume() ? Volume::Playback : Volume::Capture; + md->increaseOrDecreaseVolume(!increase, volumeType); + md->mixer()->commitVolumeChange(md); + + showVolumeDisplay(); +} + +void +KMixWindow::slotIncreaseVolume() +{ + increaseOrDecreaseVolume(true); +} + +void +KMixWindow::slotDecreaseVolume() +{ + increaseOrDecreaseVolume(false); +} + +void +KMixWindow::showVolumeDisplay() +{ + Mixer* mixer = Mixer::getGlobalMasterMixer(); + if (mixer == 0) + return; // e.g. when no soundcard is available + shared_ptr md = Mixer::getGlobalMasterMD(); + if (md.get() == 0) + return; // shouldn't happen, but lets play safe + +// Current volume + // Setting not required any more, as the OSD updates the volume level itself +// Volume& vol = md->playbackVolume(); +// osdWidget->setCurrentVolume(vol.getAvgVolumePercent(Volume::MALL), +// md->isMuted()); + if (GlobalConfig::instance().data.showOSD) + { + osdWidget->show(); + osdWidget->activateOSD(); //Enable the hide timer + } + //Center the OSD + QRect rect = KApplication::kApplication()->desktop()->screenGeometry( + QCursor::pos()); + QSize size = osdWidget->sizeHint(); + int posX = rect.x() + (rect.width() - size.width()) / 2; + int posY = rect.y() + 4 * rect.height() / 5; + osdWidget->setGeometry(posX, posY, size.width(), size.height()); +} + +/** + * Mutes the global master. (SLOT) + */ +void KMixWindow::slotMute() +{ + Mixer* mixer = Mixer::getGlobalMasterMixer(); + if (mixer == 0) + return; // e.g. when no soundcard is available + shared_ptr md = Mixer::getGlobalMasterMD(); + if (md.get() == 0) + return; // shouldn't happen, but lets play safe + md->toggleMute(); + mixer->commitVolumeChange(md); + showVolumeDisplay(); +} + +void +KMixWindow::quit() +{ + // kDebug(67100) << "quit"; + kapp->quit(); +} + +/** + * Shows the configuration dialog, with the "general" tab opened. + */ +void KMixWindow::showSettings() +{ + KMixPrefDlg::getInstance()->switchToPage(KMixPrefDlg::PrefGeneral); + KMixPrefDlg::getInstance()->show(); +} + +void +KMixWindow::showHelp() +{ + actionCollection()->action("help_contents")->trigger(); +} + +void +KMixWindow::showAbout() +{ + actionCollection()->action("help_about_app")->trigger(); +} + +/** + * Apply the Preferences from the preferences dialog. Depending on what has been changed, + * the corresponding announcemnts are made. + */ +void KMixWindow::applyPrefs() +{ + // -1- Determine what has changed ------------------------------------------------------------------ + GlobalConfigData& config = GlobalConfig::instance().data; + GlobalConfigData& configBefore = configDataSnapshot; + + bool labelsHasChanged = config.showLabels ^ configBefore.showLabels; + bool ticksHasChanged = config.showTicks ^ configBefore.showTicks; + + bool dockwidgetHasChanged = config.showDockWidget ^ configBefore.showDockWidget; + + bool toplevelOrientationHasChanged = config.getToplevelOrientation() != configBefore.getToplevelOrientation(); + bool traypopupOrientationHasChanged = config.getTraypopupOrientation() != configBefore.getTraypopupOrientation(); + kDebug() << "toplevelOrientationHasChanged=" << toplevelOrientationHasChanged << + ", config=" << config.getToplevelOrientation() << ", configBefore=" << configBefore.getToplevelOrientation(); + kDebug() << "trayOrientationHasChanged=" << traypopupOrientationHasChanged << + ", config=" << config.getTraypopupOrientation() << ", configBefore=" << configBefore.getTraypopupOrientation(); + + // -2- Determine what effect the changes have ------------------------------------------------------------------ + + if (dockwidgetHasChanged || toplevelOrientationHasChanged + || traypopupOrientationHasChanged) + { + // These might need a complete relayout => announce a ControlList change to rebuild everything + ControlManager::instance().announce(QString(), ControlChangeType::ControlList, QString("Preferences Dialog")); + } + else if (labelsHasChanged || ticksHasChanged) + { + ControlManager::instance().announce(QString(), ControlChangeType::GUI, QString("Preferences Dialog")); + } + // showOSD does not require any information. It reads on-the-fly from GlobalConfig. + + + // -3- Apply all changes ------------------------------------------------------------------ + +// this->repaint(); // make KMix look fast (saveConfig() often uses several seconds) + kapp->processEvents(); + + configDataSnapshot = GlobalConfig::instance().data; // create a new snapshot as all current changes are applied now + + // Remove saveConfig() IF aa changes have been migrated to GlobalConfig. + // Currently there is still stuff like "show menu bar". + saveConfig(); +} + +/** + * Sets whether a beep on volume change should be done. + * This method store the value internally and also propagates + * this to the Mixer core. + * + * @param beep true, if a beep should be changed + */ +void KMixWindow::setBeepOnVolumeChange(bool beep) +{ + Mixer::setBeepOnVolumeChange(beep); +} + +void +KMixWindow::toggleMenuBar() +{ + menuBar()->setVisible(_actionShowMenubar->isChecked()); +} + +void +KMixWindow::slotHWInfo() +{ + KMessageBox::information(0, m_hwInfoString, + i18n("Mixer Hardware Information")); +} + +void +KMixWindow::slotKdeAudioSetupExec() +{ + QStringList args; + args << "kcmshell4" << "kcm_phonon"; + forkExec(args); +} + +void +KMixWindow::forkExec(const QStringList& args) +{ + int pid = KProcess::startDetached(args); + if (pid == 0) + { + static const QString startErrorMessage( + i18n( + "The helper application is either not installed or not working.")); + QString msg; + msg += startErrorMessage; + msg += "\n("; + msg += args.join(QLatin1String(" ")); + msg += ')'; + errorPopup(msg); + } + +} + +void +KMixWindow::errorPopup(const QString& msg) +{ + QPointer dialog = new KDialog(this); + dialog->setButtons(KDialog::Ok); + dialog->setCaption(i18n("Error")); + QLabel* qlbl = new QLabel(msg); + dialog->setMainWidget(qlbl); + dialog->exec(); + delete dialog; + kWarning() << msg; +} + +void +KMixWindow::slotConfigureCurrentView() +{ + KMixerWidget* mw = (KMixerWidget*) m_wsMixers->currentWidget(); + ViewBase* view = 0; + if (mw) + view = mw->currentView(); + if (view) + view->configureView(); +} + +void KMixWindow::slotSelectMasterClose(QObject*) +{ + m_dsm = 0; +} + +void KMixWindow::slotSelectMaster() +{ + Mixer *mixer = Mixer::getGlobalMasterMixer(); + if (mixer != 0) + { + if (!m_dsm) { + m_dsm = new DialogSelectMaster(Mixer::getGlobalMasterMixer(), this); + connect(m_dsm, SIGNAL(destroyed(QObject*)), this, SLOT(slotSelectMasterClose(QObject*))); + m_dsm->setAttribute(Qt::WA_DeleteOnClose, true); + m_dsm->show(); + } + m_dsm->raise(); + m_dsm->activateWindow(); + } + else + { + KMessageBox::error(0, i18n("No sound card is installed or currently plugged in.")); + } +} + +void +KMixWindow::newMixerShown(int /*tabIndex*/) +{ + KMixerWidget* kmw = (KMixerWidget*) m_wsMixers->currentWidget(); + if (kmw) + { + // I am using the app name as a PREFIX, as KMix is a single window app, and it is + // more helpful to the user to see "KDE Mixer" in a window list than a possibly cryptic + // soundcard name like "HDA ATI SB" + setWindowTitle(i18n("KDE Mixer") + " - " + kmw->mixer()->readableName()); + if (!m_dontSetDefaultCardOnStart) + m_defaultCardOnStart = kmw->getGuiprof()->getId(); + // As switching the tab does NOT mean switching the master card, we do not need to update dock icon here. + // It would lead to unnecesary flickering of the (complete) dock area. + + // We only show the "Configure Channels..." menu item if the mixer is not dynamic + ViewBase* view = kmw->currentView(); + QAction* action = actionCollection()->action( + "toggle_channels_currentview"); + if (view && action) + action->setVisible(!view->isDynamic()); + } +} + +#include "kmix.moc" diff --git a/kmix/apps/kmix.h b/kmix/apps/kmix.h new file mode 100644 index 00000000..4e016206 --- /dev/null +++ b/kmix/apps/kmix.h @@ -0,0 +1,173 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KMIX_H +#define KMIX_H + + +#include + +// Qt +#include + +class QLabel; +#include +#include +class QPushButton; +#include +class KTabWidget; + +// KDE +class KAccel; +class KAction; +#include + +// KMix +#include "core/GlobalConfig.h" + +class KMixDockWidget; +class KMixerWidget; +class KMixWindow; +class Mixer; +#include "core/mixer.h" + +class OSDWidget; +class DialogSelectMaster; + +class +KMixWindow : public KXmlGuiWindow +{ + Q_OBJECT + + public: + KMixWindow(bool invisible); + ~KMixWindow(); + + private: + void saveBaseConfig(); + void saveViewConfig(); + void loadConfig(); + void loadBaseConfig(); + + void initPrefDlg(); + void initActions(); + void initActionsLate(); + void initActionsAfterInitMixer(); + //void recreateGUI(); + void initWidgets(); + //void setErrorMixerWidget(); + + void setBeepOnVolumeChange(bool beep); + void fixConfigAfterRead(); + + virtual bool queryClose(); + + public slots: + void controlsChange(int changeType); + void quit(); + void showSettings(); + void showHelp(); + void showAbout(); + void toggleMenuBar(); + void loadVolumes(); + void loadVolumes(QString postfix); + void saveVolumes(); + void saveVolumes(QString postfix); + void saveConfig(); + virtual void applyPrefs(); + void recreateGUI(bool saveView); + void recreateGUI(bool saveConfig, const QString& mixerId, bool forceNewTab); + void recreateGUIwithSavingView(); + void newMixerShown(int tabIndex); + void slotSelectMaster(); + + private: + KMixerWidget* findKMWforTab( const QString& tabId ); + + void forkExec(const QStringList& args); + void errorPopup(const QString& msg); + + KAccel *m_keyAccel; + KAction* _actionShowMenubar; + +private: + /** + * configSnapshot is used to hold the original state before modifications in the preferences dialog + */ + GlobalConfigData configDataSnapshot; + + bool m_startVisible; + bool m_visibilityUpdateAllowed; + bool m_multiDriverMode; // Not officially supported. + bool m_autouseMultimediaKeys; // Due to message freeze, not in config dialog in KDE4.4 + + KTabWidget *m_wsMixers; + + KMixDockWidget *m_dockWidget; + DialogSelectMaster *m_dsm; + + QString m_hwInfoString; + QString m_defaultCardOnStart; + bool m_dontSetDefaultCardOnStart; + QLabel *m_errorLabel; + QList m_backendFilter; + unsigned int m_configVersion; + void showVolumeDisplay(); + void increaseOrDecreaseVolume(bool increase); + + OSDWidget* osdWidget; + + bool addMixerWidget(const QString& mixer_ID, QString guiprofId, int insertPosition); + void setInitialSize(); + + private: + static QString getKmixctrlRcFilename(QString postfix); + bool profileExists(QString guiProfileId); + bool updateDocking(); + void removeDock(); + void updateTabsClosable(); + + private slots: + void slotHWInfo(); + void slotKdeAudioSetupExec(); + void slotConfigureCurrentView(); + void plugged( const char* driverName, const QString& udi, QString& dev); + void unplugged( const QString& udi); + void hideOrClose(); + void slotIncreaseVolume(); + void slotDecreaseVolume(); + void slotMute(); + void slotSelectMasterClose(QObject*); + + void newView(); + void saveAndCloseView(int); + + void loadVolumes1() { loadVolumes(QString("1")); } + void loadVolumes2() { loadVolumes(QString("2")); } + void loadVolumes3() { loadVolumes(QString("3")); } + void loadVolumes4() { loadVolumes(QString("4")); } + + void saveVolumes1() { saveVolumes(QString("1")); } + void saveVolumes2() { saveVolumes(QString("2")); } + void saveVolumes3() { saveVolumes(QString("3")); } + void saveVolumes4() { saveVolumes(QString("4")); } +}; + +#endif // KMIX_H diff --git a/kmix/apps/kmixctrl.cpp b/kmix/apps/kmixctrl.cpp new file mode 100644 index 00000000..02c28ec8 --- /dev/null +++ b/kmix/apps/kmixctrl.cpp @@ -0,0 +1,86 @@ +/* + * kmixctrl - kmix volume save/restore utility + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "core/mixertoolbox.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gui/kmixtoolbox.h" +#include "core/GlobalConfig.h" +#include "core/mixer.h" +#include "core/version.h" + +static const char description[] = +I18N_NOOP("kmixctrl - kmix volume save/restore utility"); + +extern "C" KDE_EXPORT int kdemain(int argc, char *argv[]) +{ + KLocale::setMainCatalog("kmix"); + KAboutData aboutData( "kmixctrl", 0, ki18n("KMixCtrl"), + APP_VERSION, ki18n(description), KAboutData::License_GPL, + ki18n("(c) 2000 by Stefan Schimanski")); + + aboutData.addAuthor(ki18n("Stefan Schimanski"), KLocalizedString(), "1Stein@gmx.de"); + + KCmdLineArgs::init( argc, argv, &aboutData ); + + KCmdLineOptions options; + options.add("s"); + options.add("save", ki18n("Save current volumes as default")); + options.add("r"); + options.add("restore", ki18n("Restore default volumes")); + KCmdLineArgs::addCmdLineOptions( options ); // Add our own options. + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + KApplication app( false ); + + GlobalConfig::init(); + + // create mixers + QString dummyStringHwinfo; + MixerToolBox::instance()->initMixer(false, QList(), dummyStringHwinfo); + + // load volumes + if ( args->isSet("restore") ) + { + for (int i=0; ivolumeLoad( KGlobal::config().data() ); + } + } + + // save volumes + if ( args->isSet("save") ) + { + for (int i=0; ivolumeSave( KGlobal::config().data() ); + } + } + + MixerToolBox::instance()->deinitMixer(); + + return 0; +} diff --git a/kmix/apps/kmixd.cpp b/kmix/apps/kmixd.cpp new file mode 100644 index 00000000..94f2b018 --- /dev/null +++ b/kmix/apps/kmixd.cpp @@ -0,0 +1,322 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 1996-2000 Christian Esken + * Copyright 2000-2003 Christian Esken , Stefan Schimanski <1Stein@gmx.de> + * Copyright 2002-2007 Christian Esken , Helio Chissini de Castro + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kmixd.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KMix +#include "core/GlobalConfig.h" +#include "core/mixertoolbox.h" +#include "core/kmixdevicemanager.h" +#include "core/version.h" + +K_PLUGIN_FACTORY(KMixDFactory, + registerPlugin(); + ) +K_EXPORT_PLUGIN(KMixDFactory("kmixd")) + + + +/* +static const char description[] = +I18N_NOOP("KMixD - KDE's full featured mini mixer Service"); + +extern "C" KDE_EXPORT int kdemain(int argc, char *argv[]) +{ + KAboutData aboutData( "kmixd", 0, ki18n("KMixD"), + APP_VERSION, ki18n(description), KAboutData::License_GPL, + ki18n("(c) 2010 Christian Esken")); + + KCmdLineArgs::init( argc, argv, &aboutData ); + +// As in the KUniqueApplication example only create a instance AFTER +// calling KUniqueApplication::start() + KUniqueApplication app; + KMixD kmixd; + app.exec(); +} + + */ + +/* KMixD + * Constructs a mixer window (KMix main window) + */ +KMixD::KMixD(QObject* parent, const QList&) : + KDEDModule(parent), + m_multiDriverMode (false), // -<- I never-ever want the multi-drivermode to be activated by accident + m_dontSetDefaultCardOnStart (false) +{ + setObjectName( QLatin1String("KMixD" )); + // disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in + //setAttribute(Qt::WA_DeleteOnClose, false); + + GlobalConfig::init(); + + //initActions(); // init actions first, so we can use them in the loadConfig() already + loadConfig(); // Load config before initMixer(), e.g. due to "MultiDriver" keyword + //initActionsLate(); // init actions that require a loaded config + //KGlobal::locale()->insertCatalog( QLatin1String( "kmix-controls" )); + //initWidgets(); + //initPrefDlg(); + MixerToolBox::instance()->initMixer(m_multiDriverMode, m_backendFilter, m_hwInfoString); + KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance(); + fixConfigAfterRead(); + theKMixDeviceManager->initHotplug(); + connect(theKMixDeviceManager, SIGNAL(plugged(const char*,QString,QString&)), SLOT (plugged(const char*,QString,QString&)) ); + connect(theKMixDeviceManager, SIGNAL(unplugged(QString)), SLOT (unplugged(QString)) ); +} + + +KMixD::~KMixD() +{ + MixerToolBox::instance()->deinitMixer(); +} + + + +/* +void KMixD::initActionsLate() +{ + if ( m_autouseMultimediaKeys ) { + KAction* globalAction = actionCollection()->addAction("increase_volume"); + globalAction->setText(i18n("Increase Volume")); + globalAction->setGlobalShortcut(KShortcut(Qt::Key_VolumeUp), ( KAction::ShortcutTypes)( KAction::ActiveShortcut | KAction::DefaultShortcut), KAction::NoAutoloading); + connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotIncreaseVolume())); + + globalAction = actionCollection()->addAction("decrease_volume"); + globalAction->setText(i18n("Decrease Volume")); + globalAction->setGlobalShortcut(KShortcut(Qt::Key_VolumeDown)); + connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotDecreaseVolume())); + + globalAction = actionCollection()->addAction("mute"); + globalAction->setText(i18n("Mute")); + globalAction->setGlobalShortcut(KShortcut(Qt::Key_VolumeMute)); + connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotMute())); + } +} +*/ + + +void KMixD::saveConfig() +{ + kDebug() << "About to save config"; + saveBaseConfig(); + saveVolumes(); +#ifdef __GNUC_ +#warn We must Sync here, or we will lose configuration data. The reson for that is unknown. +#endif + + kDebug() << "Saved config ... now syncing explicitly"; + KGlobal::config()->sync(); + kDebug() << "Saved config ... sync finished"; +} + +void KMixD::saveBaseConfig() +{ + kDebug() << "About to save config (Base)"; + KConfigGroup config(KGlobal::config(), "Global"); + + config.writeEntry( "DefaultCardOnStart", m_defaultCardOnStart ); + config.writeEntry( "ConfigVersion", KMIX_CONFIG_VERSION ); + config.writeEntry( "AutoUseMultimediaKeys", m_autouseMultimediaKeys ); + Mixer* mixerMasterCard = Mixer::getGlobalMasterMixer(); + if ( mixerMasterCard != 0 ) { + config.writeEntry( "MasterMixer", mixerMasterCard->id() ); + } + shared_ptr mdMaster = Mixer::getGlobalMasterMD(); + if ( mdMaster ) { + config.writeEntry( "MasterMixerDevice", mdMaster->id() ); + } + QString mixerIgnoreExpression = MixerToolBox::instance()->mixerIgnoreExpression(); + config.writeEntry( "MixerIgnoreExpression", mixerIgnoreExpression ); + + kDebug() << "Config (Base) saving done"; +} + +/** + * Stores the volumes of all mixers Can be restored via loadVolumes() or + * the kmixctrl application. + */ +void KMixD::saveVolumes() +{ + kDebug() << "About to save config (Volume)"; + KConfig *cfg = new KConfig( QLatin1String( "kmixctrlrc" ) ); + for ( int i=0; iisOpen() ) { // protect from unplugged devices (better do *not* save them) + mixer->volumeSave( cfg ); + } + } + cfg->sync(); + delete cfg; + kDebug() << "Config (Volume) saving done"; +} + + + +void KMixD::loadConfig() +{ + loadBaseConfig(); + //loadViewConfig(); // mw->loadConfig() explicitly called always after creating mw. + //loadVolumes(); // not in use +} + +void KMixD::loadBaseConfig() +{ + KConfigGroup config(KGlobal::config(), "Global"); + + m_multiDriverMode = config.readEntry("MultiDriver", false); + m_defaultCardOnStart = config.readEntry( "DefaultCardOnStart", "" ); + m_configVersion = config.readEntry( "ConfigVersion", 0 ); + // WARNING Don't overwrite m_configVersion with the "correct" value, before having it + // evaluated. Better only write that in saveBaseConfig() + m_autouseMultimediaKeys = config.readEntry( "AutoUseMultimediaKeys", true ); // currently not in use in kmixd + QString mixerMasterCard = config.readEntry( "MasterMixer", "" ); + QString masterDev = config.readEntry( "MasterMixerDevice", "" ); + //if ( ! mixerMasterCard.isEmpty() && ! masterDev.isEmpty() ) { + Mixer::setGlobalMaster(mixerMasterCard, masterDev, true); + //} + QString mixerIgnoreExpression = config.readEntry( "MixerIgnoreExpression", "Modem" ); + m_backendFilter = config.readEntry<>( "Backends", QList() ); + MixerToolBox::instance()->setMixerIgnoreExpression(mixerIgnoreExpression); +} + +/** + * Loads the volumes of all mixers from kmixctrlrc. + * In other words: + * Restores the default voumes as stored via saveVolumes() or the + * execution of "kmixctrl --save" + */ +/* Currently this is not in use +void +KMixD::loadVolumes() +{ + KConfig *cfg = new KConfig( QLatin1String( "kmixctrlrc" ), true ); + for ( int i=0; ivolumeLoad( cfg ); + } + delete cfg; +} +*/ + + + + +void KMixD::fixConfigAfterRead() +{ + KConfigGroup grp(KGlobal::config(), "Global"); + unsigned int configVersion = grp.readEntry( "ConfigVersion", 0 ); + if ( configVersion < 3 ) { + // Fix the "double Base" bug, by deleting all groups starting with "View.Base.Base.". + // The group has been copied over by KMixToolBox::loadView() for all soundcards, so + // we should be fine now + QStringList cfgGroups = KGlobal::config()->groupList(); + QStringListIterator it(cfgGroups); + while ( it.hasNext() ) { + QString groupName = it.next(); + if ( groupName.indexOf("View.Base.Base" ) == 0 ) { + kDebug(67100) << "Fixing group " << groupName; + KConfigGroup buggyDevgrpCG = KGlobal::config()->group( groupName ); + buggyDevgrpCG.deleteGroup(); + } // remove buggy group + } // for all groups + } // if config version < 3 +} + +void KMixD::plugged( const char* driverName, const QString& /*udi*/, QString& dev) +{ +// kDebug(67100) << "Plugged: dev=" << dev << "(" << driverName << ") udi=" << udi << "\n"; + QString driverNameString; + driverNameString = driverName; + int devNum = dev.toInt(); + Mixer *mixer = new Mixer( driverNameString, devNum ); + if ( mixer != 0 ) { + kDebug(67100) << "Plugged: dev=" << dev << "\n"; + MixerToolBox::instance()->possiblyAddMixer(mixer); + } + +} + +void KMixD::unplugged( const QString& udi) +{ +// kDebug(67100) << "Unplugged: udi=" <udi() << "\n"; + if (mixer->udi() == udi ) { + kDebug(67100) << "Unplugged Match: Removing udi=" <removeMixer(mixer); + // Check whether the Global Master disappeared, and select a new one if necessary + shared_ptr md = Mixer::getGlobalMasterMD(); + if ( globalMasterMixerDestroyed || md.get() == 0 ) { + // We don't know what the global master should be now. + // So lets play stupid, and just select the recommended master of the first device + if ( Mixer::mixers().count() > 0 ) { + shared_ptr master = ((Mixer::mixers())[0])->getLocalMasterMD(); + if ( master.get() != 0 ) { + QString localMaster = master->id(); + Mixer::setGlobalMaster( ((Mixer::mixers())[0])->id(), localMaster, false); + + QString text; + text = i18n("The soundcard containing the master device was unplugged. Changing to control %1 on card %2.", + master->readableName(), + ((Mixer::mixers())[0])->readableName() + ); +// KMixToolBox::notification("MasterFallback", text); + } + } + } + if ( Mixer::mixers().count() == 0 ) { + QString text; + text = i18n("The last soundcard was unplugged."); +// KMixToolBox::notification("MasterFallback", text); + } + break; + } + } + +} + + +#include "kmixd.moc" diff --git a/kmix/apps/kmixd.h b/kmix/apps/kmixd.h new file mode 100644 index 00000000..6ab53831 --- /dev/null +++ b/kmix/apps/kmixd.h @@ -0,0 +1,98 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KMIXD_H +#define KMIXD_H + + +#include + +// Qt +#include +#include + +#include +#include + +// KDE +//class KAccel; +class KAction; +//#include +#include + +// KMix +//class KMixPrefDlg; +//class KMixDockWidget; +//class KMixWindow; +//class ViewDockAreaPopup; +#include "core/mixer.h" + +//class OSDWidget; + +class +KMixD : public KDEDModule, protected QDBusContext +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.KMixD") + + public: + KMixD(QObject* parent, const QList&); + ~KMixD(); + + private: + void saveBaseConfig(); + void loadConfig(); + void loadBaseConfig(); + + void initActions(); + void initActionsLate(); + + void fixConfigAfterRead(); + + public slots: + //void loadVolumes(); + void saveVolumes(); + //virtual void applyPrefs( KMixPrefDlg *prefDlg ); + + private: + //KAccel *m_keyAccel; + //KAction* _actionShowMenubar; + + bool m_multiDriverMode; // Not officially supported. + bool m_autouseMultimediaKeys; // Due to message freeze, not in config dialog in KDE4.4 + + QString m_hwInfoString; + QString m_defaultCardOnStart; + bool m_dontSetDefaultCardOnStart; + unsigned int m_configVersion; + //void increaseOrDecreaseVolume(bool increase); + QList m_backendFilter; + + private slots: + void saveConfig(); + //void slotHWInfo(); + void plugged( const char* driverName, const QString& udi, QString& dev); + void unplugged( const QString& udi); + //void slotIncreaseVolume(); + //void slotDecreaseVolume(); + //void slotMute(); +}; + +#endif // KMIXD_H diff --git a/kmix/apps/kmixremote b/kmix/apps/kmixremote new file mode 100755 index 00000000..2f212096 --- /dev/null +++ b/kmix/apps/kmixremote @@ -0,0 +1,128 @@ +#!/bin/sh +################################################################################# +# kmixremote - control kmix from a script. +# +# Set volume +# Get volume +# Mute +################################################################################# + +function usage +{ + echo "Usage:" + echo "List mixers # $0 list" + echo "List controls # $0 list " + echo "Get Volume # $0 get [--master | ]" + echo "Set Volume # $0 set [--master | ] <0..100>" + echo "Mute/Unmute # $0 mute [--master | ] true|false" + echo +} + +function exit_with_error +{ + echo "Error: $1" + echo + usage + exit 1 +} + +# Prints the mixer DBUS ID's on the console. leaving out the "/Mixers/" prefix +function listMixers +{ + qdbus org.kde.kmix /Mixers org.freedesktop.DBus.Properties.Get org.kde.KMix.MixSet mixers | cut -f3 -d/ + errorCode=$? + if test $errorCode != 0; then + echo "Error $errorCode listing mixers. KMix is not running." + fi +} + +# Prints the mixer control DBUS ID's of the given mixer on the console. leaving out the "/Mixers/" prefix +function listControls +{ + qdbus org.kde.kmix $1 org.freedesktop.DBus.Properties.Get org.kde.KMix.Mixer controls | cut -f4 -d/ + errorCode=$? + if test $errorCode != 0; then + echo "Error $errorCode listing controls. KMix is not running." + fi +} + +command="" + +if ! type qdbus >/dev/null 2>&1 ; then + exit_with_error "$0 requires qdbus, but it cannot be found. Please install or check \$PATH" +fi + +# Read args +while true; do + arg=$1 + shift + if test -z "$arg"; then + break + elif test "x--master" = "x$arg"; then + mixer=`qdbus org.kde.kmix /Mixers org.kde.KMix.MixSet.currentMasterMixer` + control=`qdbus org.kde.kmix /Mixers org.kde.KMix.MixSet.currentMasterControl` + elif test "x--help" = "x$arg" -o "x-h" = "x$arg"; then + usage + exit 0 + else + # If not a specific option, then interpret as standad args, in this order: command, mixer, control and genericArg + if test -z "$command"; then + command=$arg + elif test -z "$mixer"; then + mixer="${arg}" + elif test -z "$control"; then + control=$arg + elif test -z "$genericArg"; then + genericArg=$arg + else + exit_with_error "Too many aguments" + fi + fi + #echo $arg +done + + +if test -z "$command"; then + usage + echo " - The mixer to use. Select one from the following list:" + echo "-----------------------------------------------------------------" + listMixers + exit 0 +elif test "xlist" = "x$command"; then + if test -z "$mixer"; then + listMixers + else + # List controls + listControls "/Mixers/${mixer}" + fi + exit 0 +fi + +# All following commands require a mixer +if test -z "$mixer"; then + exit_with_error " argument missing" +fi +if test -z "$control"; then + exit_with_error " argument missing" +fi + +# All following commands require a mixer and a control + +targetControl="/Mixers/${mixer}/${control}" +#echo "ARGS: $command $targetControl $genericArg" + +# --- EXECUTE PHASE -------------------------------------------------------------------------------------------------- +if test "xget" = "x$command"; then + # GET + qdbus org.kde.kmix $targetControl org.freedesktop.DBus.Properties.Get org.kde.KMix.Control volume +elif test "xset" = "x$command"; then + # SET + qdbus org.kde.kmix $targetControl org.freedesktop.DBus.Properties.Set org.kde.KMix.Control volume $genericArg +elif test "xmute" = "x$command"; then + # MUTE + qdbus org.kde.kmix $targetControl org.freedesktop.DBus.Properties.Set org.kde.KMix.Control mute $genericArg +else + exit_with_error "No such command '$command'" +fi + +exit 0 diff --git a/kmix/apps/main.cpp b/kmix/apps/main.cpp new file mode 100644 index 00000000..d765e29e --- /dev/null +++ b/kmix/apps/main.cpp @@ -0,0 +1,79 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2000 Stefan Schimanski + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "KMixApp.h" +#include "core/version.h" + +static const char description[] = +I18N_NOOP("KMix - KDE's full featured mini mixer"); + +extern "C" KDE_EXPORT int kdemain(int argc, char *argv[]) +{ + KAboutData aboutData( "kmix", 0, ki18n("KMix"), + APP_VERSION, ki18n(description), KAboutData::License_GPL, + ki18n("(c) 1996-2013 The KMix Authors")); + + // Author Policy: Long-term maintainers and backend writers/maintainers go in the Authors list. + aboutData.addAuthor(ki18n("Christian Esken") , ki18n("Original author and current maintainer"), "esken@kde.org"); + aboutData.addAuthor(ki18n("Colin Guthrie") , ki18n("PulseAudio support"), "colin@mageia.org"); + aboutData.addAuthor(ki18n("Helio Chissini de Castro"), ki18n("ALSA 0.9x port"), "helio@kde.org" ); + aboutData.addAuthor(ki18n("Brian Hanson") , ki18n("Solaris support"), "bhanson@hotmail.com"); +// The HP/UX port is not maintained anymore, and no official part of KMix anymore +// aboutData.addAuthor(ki18n("Helge Deller") , ki18n("HP/UX port"), "deller@gmx.de"); +// The initial support was for ALSA 0.5. The new code is not based on it IIRC. +// aboutData.addAuthor(ki18n("Nick Lopez") , ki18n("Initial ALSA port"), "kimo_sabe@usa.net"); + + // Credit Policy: Authors who did a discrete part, like the Dataengine, OSD, help on specific platforms or soundcards. + aboutData.addCredit(ki18n("Igor Poboiko") , ki18n("Plasma Dataengine"), "igor.poboiko@gmail.com"); + aboutData.addCredit(ki18n("Stefan Schimanski") , ki18n("Temporary maintainer"), "schimmi@kde.org"); + aboutData.addCredit(ki18n("Sebestyen Zoltan") , ki18n("*BSD fixes"), "szoli@digo.inf.elte.hu"); + aboutData.addCredit(ki18n("Lennart Augustsson"), ki18n("*BSD fixes"), "augustss@cs.chalmers.se"); + aboutData.addCredit(ki18n("Nadeem Hasan") , ki18n("Mute and volume preview, other fixes"), "nhasan@kde.org"); + aboutData.addCredit(ki18n("Erwin Mascher") , ki18n("Improving support for emu10k1 based soundcards")); + aboutData.addCredit(ki18n("Valentin Rusu") , ki18n("TerraTec DMX6Fire support"), "kde@rusu.info"); + + KCmdLineArgs::init( argc, argv, &aboutData ); + + KCmdLineOptions options; + options.add("keepvisibility", ki18n("Inhibits the unhiding of the KMix main window, if KMix is already running.")); + KCmdLineArgs::addCmdLineOptions( options ); // Add our own options. + KUniqueApplication::addCmdLineOptions(); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + bool hasArgKeepvisibility = args->isSet("keepvisibility"); + //kDebug(67100) << "hasArgKeepvisibility=" << hasArgKeepvisibility; + KMixApp::keepVisibility(hasArgKeepvisibility); + + if (!KMixApp::start()) + return 0; + + KMixApp *app = new KMixApp(); + int ret = app->exec(); + delete app; + return ret; +} diff --git a/kmix/backends/kmix-backends.cpp b/kmix/backends/kmix-backends.cpp new file mode 100644 index 00000000..14ecfb30 --- /dev/null +++ b/kmix/backends/kmix-backends.cpp @@ -0,0 +1,118 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2000 Christian Esken + * esken@kde.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* This code is being #include'd from mixer.cpp */ + +#include +#include + +#include "mixer_backend.h" +#include "core/mixer.h" + +#include + + + +#if defined(sun) || defined(__sun__) +#define SUN_MIXER +#endif + +#ifdef __linux__ + +#define OSS_MIXER +#endif + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(_UNIXWARE) +#define OSS_MIXER +#endif + +#if defined(hpux) +#error "The HP/UX port is not maintained anymore, an no official part of KMix / KDE at this point of time! Please contact the current KMix maintainer if you would like to maintain the port." +#endif // hpux + +// PORTING: add #ifdef PLATFORM , commands , #endif, add your new mixer below + +// Compiled by its own! +//#include "backends/mixer_mpris2.cpp" + +#if defined(SUN_MIXER) +#include "backends/mixer_sun.cpp" +#endif + +// OSS 3 / 4 +#if defined(OSS_MIXER) +#include "backends/mixer_oss.cpp" + +#if !defined(__NetBSD__) && !defined(__OpenBSD__) +#include +#else +#include +#endif +#if !defined(__FreeBSD__) && (SOUND_VERSION >= 0x040000) +#define OSS4_MIXER +#endif +#endif + +#if defined(OSS4_MIXER) +#include "backends/mixer_oss4.cpp" +#endif + + +// Possibly encapsualte by #ifdef HAVE_DBUS +Mixer_Backend* MPRIS2_getMixer(Mixer *mixer, int device ); +QString MPRIS2_getDriverName(); + +Mixer_Backend* ALSA_getMixer(Mixer *mixer, int device ); +QString ALSA_getDriverName(); + +Mixer_Backend* PULSE_getMixer(Mixer *mixer, int device ); +QString PULSE_getDriverName(); + +MixerFactory g_mixerFactories[] = { + +#if defined(SUN_MIXER) + { SUN_getMixer, SUN_getDriverName }, +#endif + +#if defined(HAVE_PULSE) + { PULSE_getMixer, PULSE_getDriverName }, +#endif + +#if defined(HAVE_LIBASOUND2) + { ALSA_getMixer, ALSA_getDriverName }, +#endif + +#if defined(OSS_MIXER) + { OSS_getMixer, OSS_getDriverName }, +#endif + +#if defined(OSS4_MIXER) + { OSS4_getMixer, OSS4_getDriverName }, +#endif + + // Make sure MPRIS2 is at the end. Implementation of SINGLE_PLUS_MPRIS2 in MixerToolBox is much easier. + // And also we make sure, streams are always the last backend, which is important for the default KMix GUI layout. + { MPRIS2_getMixer, MPRIS2_getDriverName }, + + { 0, 0 } +}; + diff --git a/kmix/backends/mixer_alsa.h b/kmix/backends/mixer_alsa.h new file mode 100644 index 00000000..9a75eaec --- /dev/null +++ b/kmix/backends/mixer_alsa.h @@ -0,0 +1,90 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef MIXER_ALSA_H +#define MIXER_ALSA_H + +// QT includes +#include +#include + +// Forward QT includes +class QString; +class QSocketNotifier; + +#include "mixer_backend.h" + +extern "C" +{ + #include +} + +class Mixer_ALSA : public Mixer_Backend +{ +public: + explicit Mixer_ALSA(Mixer *mixer, int device = -1 ); + ~Mixer_ALSA(); + + virtual int readVolumeFromHW( const QString& id, shared_ptr md ); + virtual int writeVolumeToHW ( const QString& id, shared_ptr md ); + virtual void setEnumIdHW( const QString& id, unsigned int); + virtual unsigned int enumIdHW(const QString& id); + virtual bool prepareUpdateFromHW(); + + virtual bool needsPolling() { return false; } + virtual QString getDriverName(); + +protected: + virtual int open(); + virtual int close(); + int id2num(const QString& id); + +private: + + int openAlsaDevice(const QString& devName); + void addEnumerated(snd_mixer_elem_t *elem, QList&); + Volume* addVolume(snd_mixer_elem_t *elem, bool capture); + int setupAlsaPolling(); + void deinitAlsaPolling(); + + virtual bool isRecsrcHW( const QString& id ); + int identify( snd_mixer_selem_id_t *sid ); + snd_mixer_elem_t* getMixerElem(int devnum); + + virtual QString errorText(int mixer_error); + typedef QListAlsaMixerSidList; + AlsaMixerSidList mixer_sid_list; + typedef QList AlsaMixerElemList; + AlsaMixerElemList mixer_elem_list; + typedef QHash Id2numHash; + Id2numHash m_id2numHash; + + bool _initialUpdate; + snd_mixer_t* _handle; + snd_ctl_t* ctl_handle; + + QString devName; + struct pollfd *m_fds; + QList m_sns; + //int m_count; + static bool warnOnce; +}; + +#endif diff --git a/kmix/backends/mixer_alsa9.cpp b/kmix/backends/mixer_alsa9.cpp new file mode 100644 index 00000000..f48dd4e9 --- /dev/null +++ b/kmix/backends/mixer_alsa9.cpp @@ -0,0 +1,986 @@ +/* + * KMix -- KDE's full featured mini mixer + * Alsa 0.9x and 1.0 - Based on original alsamixer code + * from alsa-project ( www/alsa-project.org ) + * + * + * Copyright (C) 2002 Helio Chissini de Castro + * 2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +// KMix +#include "mixer_alsa.h" +#include "core/kmixdevicemanager.h" +#include "core/mixer.h" +#include "core/volume.h" + +// KDE +#include +#include + +// Qt +#include + +// STD Headers +#include +#include +#include +#include +#include + +//#include "core/mixer.h" +//class Mixer; + +// #define if you want MUCH debugging output +//#define ALSA_SWITCH_DEBUG +//#define KMIX_ALSA_VOLUME_DEBUG + +Mixer_Backend* +ALSA_getMixer(Mixer *mixer, int device ) +{ + + Mixer_Backend *l_mixer; + + l_mixer = new Mixer_ALSA(mixer, device ); + return l_mixer; +} + +Mixer_ALSA::Mixer_ALSA( Mixer* mixer, int device ) : Mixer_Backend(mixer, device ) +{ + m_fds = 0; + _handle = 0; + ctl_handle = 0; + _initialUpdate = true; +} + +Mixer_ALSA::~Mixer_ALSA() +{ + close(); +} + +int Mixer_ALSA::identify( snd_mixer_selem_id_t *sid ) +{ + QString name = snd_mixer_selem_id_get_name( sid ); + if (name.contains("master" , Qt::CaseInsensitive)) return MixDevice::VOLUME; + if (name.contains("master mono", Qt::CaseInsensitive)) return MixDevice::VOLUME; + if (name.contains("front" , Qt::CaseInsensitive) && + !name.contains("mic" , Qt::CaseInsensitive)) return MixDevice::VOLUME; + if (name.contains("pc speaker" , Qt::CaseInsensitive)) return MixDevice::SPEAKER; + if (name.contains("capture" , Qt::CaseInsensitive)) return MixDevice::RECMONITOR; + if (name.contains("music" , Qt::CaseInsensitive)) return MixDevice::MIDI; + if (name.contains("Synth" , Qt::CaseInsensitive)) return MixDevice::MIDI; + if (name.contains("FM" , Qt::CaseInsensitive)) return MixDevice::MIDI; + if (name.contains("headphone" , Qt::CaseInsensitive)) return MixDevice::HEADPHONE; + if (name.contains("bass" , Qt::CaseInsensitive)) return MixDevice::BASS; + if (name.contains("treble" , Qt::CaseInsensitive)) return MixDevice::TREBLE; + if (name.contains("cd" , Qt::CaseInsensitive)) return MixDevice::CD; + if (name.contains("video" , Qt::CaseInsensitive)) return MixDevice::VIDEO; + if (name.contains("pcm" , Qt::CaseInsensitive)) return MixDevice::AUDIO; + if (name.contains("Wave" , Qt::CaseInsensitive)) return MixDevice::AUDIO; + if (name.contains("surround" , Qt::CaseInsensitive)) return MixDevice::SURROUND_BACK; + if (name.contains("center" , Qt::CaseInsensitive)) return MixDevice::SURROUND_CENTERFRONT; + if (name.contains("ac97" , Qt::CaseInsensitive)) return MixDevice::AC97; + if (name.contains("coaxial" , Qt::CaseInsensitive)) return MixDevice::DIGITAL; + if (name.contains("optical" , Qt::CaseInsensitive)) return MixDevice::DIGITAL; + if (name.contains("iec958" , Qt::CaseInsensitive)) return MixDevice::DIGITAL; + if (name.contains("digital" , Qt::CaseInsensitive)) return MixDevice::DIGITAL; + if (name.contains("mic boost" , Qt::CaseInsensitive)) return MixDevice::MICROPHONE_BOOST; + if (name.contains("Mic Front" , Qt::CaseInsensitive)) return MixDevice::MICROPHONE_FRONT; + if (name.contains("Front Mic" , Qt::CaseInsensitive)) return MixDevice::MICROPHONE_FRONT; + if (name.contains("mic" , Qt::CaseInsensitive)) return MixDevice::MICROPHONE; + if (name.contains("lfe" , Qt::CaseInsensitive)) return MixDevice::SURROUND_LFE; + if (name.contains("monitor" , Qt::CaseInsensitive)) return MixDevice::RECMONITOR; + if (name.contains("3d" , Qt::CaseInsensitive)) return MixDevice::SURROUND; + if (name.contains("side" , Qt::CaseInsensitive)) return MixDevice::SURROUND_BACK; + + return MixDevice::EXTERNAL; +} + +int Mixer_ALSA::open() +{ + int masterChosenQuality = 0; + int err; + + snd_mixer_elem_t *elem; + snd_mixer_selem_id_t *sid; + bool USE_ALSO_ALLOCA = true; // TODO remove alloca() when adding "delete sid" in the destructor or close() + if (USE_ALSO_ALLOCA) + { + snd_mixer_selem_id_alloca( &sid ); + } + + // Determine a card name + if( m_devnum < -1 || m_devnum > 31 ) + devName = "default"; + else + devName = QString( "hw:%1" ).arg( m_devnum ); + + // Open the card + err = openAlsaDevice(devName); + if ( err != 0 ) { + return err; + } + + _udi = KMixDeviceManager::instance()->getUDI_ALSA(m_devnum); + if ( _udi.isEmpty() ) { + QString msg("No UDI found for '"); + msg += devName; + msg += "'. Hotplugging not possible"; + kWarning() << msg; + } + // Run a loop over all controls of the card + unsigned int idx = 0; + for ( elem = snd_mixer_first_elem( _handle ); elem; elem = snd_mixer_elem_next( elem ) ) + { + // If element is not active, just skip + if ( ! snd_mixer_selem_is_active ( elem ) ) { + continue; + } + + /* --- Create basic control structures: snd_mixer_selem_id_t*, ID, ... --------- */ + // snd_mixer_selem_id_t* + // I believe we must malloc it ourself (just guessing due to missing ALSA documentation) + snd_mixer_selem_id_malloc ( &sid ); // !! Return code should be checked. Resource must be freed when unplugging card + snd_mixer_selem_get_id( elem, sid ); + // Generate ID + QString mdID("%1:%2"); + mdID = mdID.arg(snd_mixer_selem_id_get_name ( sid ) ) + .arg(snd_mixer_selem_id_get_index( sid ) ); + mdID.replace(' ','_'); // Any key/ID we use, must not uses spaces (rule) + + MixDevice::ChannelType ct = (MixDevice::ChannelType)identify( sid ); + + /* ------------------------------------------------------------------------------- */ + + Volume* volPlay = 0; + Volume* volCapture = 0; + QList enumList; + + if ( snd_mixer_selem_is_enumerated(elem) ) { + // --- Enumerated --- + addEnumerated(elem, enumList); + } + else + { + volPlay = addVolume(elem, false); + volCapture = addVolume(elem, true ); + } + + + QString readableName; + readableName = snd_mixer_selem_id_get_name( sid ); + int controlInstanceIndex = snd_mixer_selem_id_get_index( sid ); + if ( controlInstanceIndex > 0 ) { + // Add a number to the control name, like "PCM 2", when the index is > 0 + QString idxString; + idxString.setNum(1+controlInstanceIndex); + readableName += ' '; + readableName += idxString; + } + + // There can be an Enum-Control with the same name as a regular control. So we append a ".[cp]enum" prefix to always create a unique ID + QString finalMixdeviceID = mdID; + if ( ! enumList.isEmpty() ) + { + if (snd_mixer_selem_is_enum_capture ( elem ) ) + finalMixdeviceID = mdID + ".cenum"; // capture enum + else + finalMixdeviceID = mdID + ".penum"; // playback enum + } + + m_id2numHash[finalMixdeviceID] = idx; + //kDebug() << "m_id2numHash[mdID] mdID=" << mdID << " idx=" << idx; + mixer_elem_list.append( elem ); + mixer_sid_list.append( sid ); + idx++; + + + MixDevice* mdNew = new MixDevice(_mixer, finalMixdeviceID, readableName, ct ); + + if ( volPlay != 0 ) + { + mdNew->addPlaybackVolume(*volPlay); + delete volPlay; + } + if ( volCapture != 0 ) + { + mdNew->addCaptureVolume (*volCapture); + delete volCapture; + } + if ( !enumList.isEmpty() ) + { + mdNew->addEnums(enumList); + qDeleteAll(enumList); // clear temporary list + } + + shared_ptr md = mdNew->addToPool(); + m_mixDevices.append( md ); + + + // --- Recommended master ---------------------------------------- + if ( md->playbackVolume().hasVolume() ) + { + if ( mdID == "Master:0" && masterChosenQuality < 100 ) { +// kDebug() << "Setting m_recommendedMaster to " << mdID; + m_recommendedMaster = md; + masterChosenQuality = 100; + } + else if ( mdID == "PCM:0" && masterChosenQuality < 80) { +// kDebug() << "Setting m_recommendedMaster to " << mdID; + m_recommendedMaster = md; + masterChosenQuality = 80; + } + else if ( mdID == "Front:0" && masterChosenQuality < 60) { +// kDebug() << "Setting m_recommendedMaster to " << mdID; + m_recommendedMaster = md; + masterChosenQuality = 60; + } + else if ( mdID == "DAC:0" && masterChosenQuality < 50) { +// kDebug() << "Setting m_recommendedMaster to " << mdID; + m_recommendedMaster = md; + masterChosenQuality = 50; + } + else if ( mdID == "Headphone:0" && masterChosenQuality < 40) { +// kDebug() << "Setting m_recommendedMaster to " << mdID; + m_recommendedMaster = md; + masterChosenQuality = 40; + } + else if ( mdID == "Master Mono:0" && masterChosenQuality < 30) { +// kDebug() << "Setting m_recommendedMaster to " << mdID; + m_recommendedMaster = md; + masterChosenQuality = 30; + } + } + } // for all elems + + + + m_isOpen = true; // return with success + + setupAlsaPolling(); // For updates + return 0; +} + +// warnOnce will make sure we only print the first ALSA device not found +bool Mixer_ALSA::warnOnce = true; +/** + * This opens a ALSA device for further interaction. + * As this is "slightly" more complicated than calling ::open(), it is put in a separate method. + */ +int Mixer_ALSA::openAlsaDevice(const QString& devName) +{ + int err; + + QString probeMessage; + probeMessage += "Trying ALSA Device '" + devName + "': "; + + if ( ( err = snd_ctl_open ( &ctl_handle, devName.toAscii().data(), 0 ) ) < 0 ) + { + if (Mixer_ALSA::warnOnce) + { + Mixer_ALSA::warnOnce = false; + kDebug() << probeMessage << "not found: snd_ctl_open err=" << snd_strerror(err); + } + return Mixer::ERR_OPEN; + } + + + // Mixer name + snd_ctl_card_info_t *hw_info; + snd_ctl_card_info_alloca(&hw_info); + if ( ( err = snd_ctl_card_info ( ctl_handle, hw_info ) ) < 0 ) + { + if (Mixer_ALSA::warnOnce) + { + Mixer_ALSA::warnOnce = false; + kDebug() << probeMessage << "not found: snd_ctl_card_info err=" << snd_strerror(err); + } + //_stateMessage = errorText( Mixer::ERR_READ ); + snd_ctl_close( ctl_handle ); + return Mixer::ERR_READ; + } + const char* mixer_card_name = snd_ctl_card_info_get_name( hw_info ); + //QString mixer_card_name_QString = mixer_card_name; + registerCard(mixer_card_name); + + + snd_ctl_close( ctl_handle ); + + /* open mixer device */ + if ( ( err = snd_mixer_open ( &_handle, 0 ) ) < 0 ) + { + if (Mixer_ALSA::warnOnce) + { + Mixer_ALSA::warnOnce = false; + kDebug() << probeMessage << "not found: snd_mixer_open err=" << snd_strerror(err); + } + _handle = 0; + return Mixer::ERR_OPEN; // if we cannot open the mixer, we have no devices + } + + if ( ( err = snd_mixer_attach ( _handle, devName.toAscii().data() ) ) < 0 ) + { + if (Mixer_ALSA::warnOnce) + { + Mixer_ALSA::warnOnce = false; + kDebug() << probeMessage << "not found: snd_mixer_attach err=" << snd_strerror(err); + } + return Mixer::ERR_OPEN; + } + + if ( ( err = snd_mixer_selem_register ( _handle, NULL, NULL ) ) < 0 ) + { + if (Mixer_ALSA::warnOnce) + { + Mixer_ALSA::warnOnce = false; + kDebug() << probeMessage << "not found: snd_mixer_selem_register err=" << snd_strerror(err); + } + return Mixer::ERR_READ; + } + + if ( ( err = snd_mixer_load ( _handle ) ) < 0 ) + { + if (Mixer_ALSA::warnOnce) + { + Mixer_ALSA::warnOnce = false; + kDebug() << probeMessage << "not found: snd_mixer_load err=" << snd_strerror(err); + } + close(); + return Mixer::ERR_READ; + } + + Mixer_ALSA::warnOnce = true; + kDebug() << probeMessage << "found"; + + return 0; +} + + +/* setup for select on stdin and the mixer fd */ +int Mixer_ALSA::setupAlsaPolling() +{ + // --- Step 1: Retrieve FD's from ALSALIB + int err; + int countNew = 0; + if ((countNew = snd_mixer_poll_descriptors_count(_handle)) < 0) { + kDebug() << "Mixer_ALSA::poll() , snd_mixer_poll_descriptors_count() err=" << countNew << "\n"; + return Mixer::ERR_OPEN; + } + + //if ( countNew != m_sns.size() ) + if (true) + { + // Redo everything if count of FD's have changed (emulating alsamixer behaviour here) + while (!m_sns.isEmpty()) + delete m_sns.takeFirst(); + + + free(m_fds); + m_fds = (struct pollfd*)calloc(countNew, sizeof(struct pollfd)); + if (m_fds == NULL) { + kDebug() << "Mixer_ALSA::poll() , calloc() = null" << "\n"; + return Mixer::ERR_OPEN; + } + + + if ((err = snd_mixer_poll_descriptors(_handle, m_fds, countNew)) < 0) { + kDebug() << "Mixer_ALSA::poll() , snd_mixer_poll_descriptors_count() err=" << err << "\n"; + return Mixer::ERR_OPEN; + } + if (err != countNew) { + kDebug() << "Mixer_ALSA::poll() , snd_mixer_poll_descriptors_count() err=" << err << " m_count=" << countNew << "\n"; + return Mixer::ERR_OPEN; + } + + + // --- Step 2: Create QSocketNotifier's for the FD's + //m_sns = new QSocketNotifier*[m_count]; + for ( int i = 0; i < countNew; ++i ) + { + //kDebug() << "socket " << i; + QSocketNotifier* qsn = new QSocketNotifier(m_fds[i].fd, QSocketNotifier::Read); + m_sns.append(qsn); + connect(m_sns[i], SIGNAL(activated(int)), SLOT(readSetFromHW()), Qt::QueuedConnection); + } + } + + return 0; +} + +void Mixer_ALSA::addEnumerated(snd_mixer_elem_t *elem, QList& enumList) +{ + // --- get Enum names START --- + int numEnumitems = snd_mixer_selem_get_enum_items(elem); + if ( numEnumitems > 0 ) { + // OK. no error + for (int iEnum = 0; iEnum ignore this entry + } +} + + +Volume* Mixer_ALSA::addVolume(snd_mixer_elem_t *elem, bool capture) +{ + Volume* vol = 0; + long maxVolume = 0, minVolume = 0; + + // Add volumes + if ( !capture && snd_mixer_selem_has_playback_volume(elem) ) { + snd_mixer_selem_get_playback_volume_range( elem, &minVolume, &maxVolume ); + } + else if ( capture && snd_mixer_selem_has_capture_volume(elem) ) { + snd_mixer_selem_get_capture_volume_range( elem, &minVolume, &maxVolume ); + } + + + // Check if this control has at least one volume control + bool hasVolume = snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem); + + // Check if a appropriate switch is present (appropriate means, based o nthe "capture" parameer) + bool hasCommonSwitch = snd_mixer_selem_has_common_switch ( elem ); + + bool hasSwitch = hasCommonSwitch | + capture + ? snd_mixer_selem_has_capture_switch ( elem ) + : snd_mixer_selem_has_playback_switch ( elem ); + + if ( hasVolume || hasSwitch ) { + //kDebug() << "Add somthing with chn=" << chn << ", capture=" << capture; + vol = new Volume( maxVolume, minVolume, hasSwitch, capture); + + // Add volumes + if ( !capture && snd_mixer_selem_has_playback_volume(elem) ) { + if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_FRONT_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::LEFT)); + if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_FRONT_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::RIGHT)); + if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_FRONT_CENTER)) vol->addVolumeChannel(VolumeChannel(Volume::CENTER)); + if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_REAR_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::SURROUNDLEFT)); + if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_REAR_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::SURROUNDRIGHT)); + if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_REAR_CENTER )) vol->addVolumeChannel(VolumeChannel(Volume::REARCENTER)); + if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_WOOFER )) vol->addVolumeChannel(VolumeChannel(Volume::WOOFER)); + if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_SIDE_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::REARSIDELEFT)); + if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_SIDE_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::REARSIDERIGHT)); + } + else if ( capture && snd_mixer_selem_has_capture_volume(elem) ) { + if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_FRONT_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::LEFT)); + if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_FRONT_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::RIGHT)); + if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_FRONT_CENTER)) vol->addVolumeChannel(VolumeChannel(Volume::CENTER)); + if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_REAR_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::SURROUNDLEFT)); + if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_REAR_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::SURROUNDRIGHT)); + if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_REAR_CENTER )) vol->addVolumeChannel(VolumeChannel(Volume::REARCENTER)); + if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_WOOFER )) vol->addVolumeChannel(VolumeChannel(Volume::WOOFER)); + if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_SIDE_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::REARSIDELEFT)); + if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_SIDE_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::REARSIDERIGHT)); + } + } + + return vol; +} + + +void Mixer_ALSA::deinitAlsaPolling() +{ + if ( m_fds ) + free( m_fds ); + m_fds = 0; + + while (!m_sns.isEmpty()) + delete m_sns.takeFirst(); +} + +int +Mixer_ALSA::close() +{ +// kDebug() << "close " << this; + + int ret=0; + m_isOpen = false; + + if ( ctl_handle != 0) + { + //snd_ctl_close( ctl_handle ); + ctl_handle = 0; + } + + if ( _handle != 0 ) + { + //kDebug() << "IN Mixer_ALSA::close()"; + snd_mixer_free ( _handle ); + if ( ( ret = snd_mixer_detach ( _handle, devName.toAscii().data() ) ) < 0 ) + { + kDebug() << "snd_mixer_detach err=" << snd_strerror(ret); + } + int ret2 = 0; + if ( ( ret2 = snd_mixer_close ( _handle ) ) < 0 ) + { + kDebug() << "snd_mixer_close err=" << snd_strerror(ret2); + if ( ret == 0 ) ret = ret2; // no error before => use current error code + } + + _handle = 0; + //kDebug() << "OUT Mixer_ALSA::close()"; + + } + + mixer_elem_list.clear(); + mixer_sid_list.clear(); + m_id2numHash.clear(); + + deinitAlsaPolling(); + + closeCommon(); + return ret; +} + + +/** + * Resolve index to a control (snd_mixer_elem_t*) + * @par idx Index to query. For any invalid index (including -1) returns a 0 control. + */ +snd_mixer_elem_t* Mixer_ALSA::getMixerElem(int idx) { + snd_mixer_elem_t* elem = 0; + if ( ! m_isOpen ) return elem; // unplugging guard + + if ( idx == -1 ) { + return elem; + } + if ( int( mixer_sid_list.count() ) > idx ) { + snd_mixer_selem_id_t * sid = mixer_sid_list[ idx ]; + // The next line (hopefully) only finds selem's, not elem's. + elem = snd_mixer_find_selem(_handle, sid); + + if ( elem == 0 ) { + // !! Check, whether the warning should be omitted. Probably + // Route controls are non-simple elements. + kDebug() << "Error finding mixer element " << idx; + } + } + return elem; + +/* + I would have liked to use the following trivial implementation instead of the + code above. But it will also return elem's. which are not selem's. As there is + no way to check an elem's type (e.g. elem->type == SND_MIXER_ELEM_SIMPLE), callers + of getMixerElem() cannot check the type. :-( + snd_mixer_elem_t* elem = mixer_elem_list[ devnum ]; + return elem; + */ +} + +int Mixer_ALSA::id2num(const QString& id) { + //kDebug() << "id2num() id=" << id; + int num = -1; + if ( m_id2numHash.contains(id) ) { + num = m_id2numHash[id]; + } + //kDebug() << "id2num() num=" << num; + return num; +} + +bool Mixer_ALSA::prepareUpdateFromHW() { + if ( !m_fds || !m_isOpen ) + return false; + + setupAlsaPolling(); + // Poll on fds with 10ms timeout + // Hint: alsamixer has an infinite timeout, but we cannot do this because we would block + // the X11 event handling (Qt event loop) with this. + int finished = poll(m_fds, m_sns.size(), 10); + + bool updated = false; + + if (finished > 0) { + //kDebug() << "Mixer_ALSA::prepareUpdate() 5\n"; + + unsigned short revents; + + if (snd_mixer_poll_descriptors_revents(_handle, m_fds, m_sns.size(), &revents) >= 0) + { + //kDebug() << "Mixer_ALSA::prepareUpdate() 6\n"; + + if (revents & POLLNVAL) { + /* Bug 127294 shows, that we receive POLLNVAL when the user + unplugs an USB soundcard. Lets close the card. */ + kDebug() << "Mixer_ALSA::poll() , Error: poll() returns POLLNVAL\n"; + close(); // Card was unplugged (unplug, driver unloaded) + return false; + } + if (revents & POLLERR) { + kDebug() << "Mixer_ALSA::poll() , Error: poll() returns POLLERR\n"; + return false; + } + if (revents & POLLIN) { + //kDebug() << "Mixer_ALSA::prepareUpdate() 7\n"; + snd_mixer_handle_events(_handle); + updated = true; + } + } + } + + //kDebug() << "Mixer_ALSA::prepareUpdate() 8\n"; + return updated; +} + +bool Mixer_ALSA::isRecsrcHW( const QString& id ) +{ + int devnum = id2num(id); + bool isCurrentlyRecSrc = false; + snd_mixer_elem_t *elem = getMixerElem( devnum ); + + if ( !elem ) { + return false; + } + + if ( snd_mixer_selem_has_capture_switch( elem ) ) { + // Has a on-off switch + // Yes, this element can be record source. But the user can switch it off, so lets see if it is switched on. + int swLeft; + int ret = snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &swLeft ); + if ( ret != 0 ) kDebug() << "snd_mixer_selem_get_capture_switch() failed 1\n"; + + if (snd_mixer_selem_has_capture_switch_joined( elem ) ) { + isCurrentlyRecSrc = (swLeft != 0); +#ifdef ALSA_SWITCH_DEBUG + kDebug() << "has_switch joined: #" << devnum << " >>> " << swLeft << " : " << isCurrentlyRecSrc; +#endif + } + else { + int swRight; + snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_RIGHT, &swRight ); + isCurrentlyRecSrc = ( (swLeft != 0) || (swRight != 0) ); +#ifdef ALSA_SWITCH_DEBUG + kDebug() << "has_switch non-joined, state " << isCurrentlyRecSrc; +#endif + } + } + else { + // Has no on-off switch + if ( snd_mixer_selem_has_capture_volume( elem ) ) { + // Has a volume, but has no OnOffSwitch => We assume that this is a fixed record source (always on). (esken) + isCurrentlyRecSrc = true; +#ifdef ALSA_SWITCH_DEBUG + kDebug() << "has_no_switch, state " << isCurrentlyRecSrc; +#endif + } + } + + return isCurrentlyRecSrc; +} + + +/** + * Sets the ID of the currently selected Enum entry. + * Warning: ALSA supports to have different enums selected on each channel + * of the SAME snd_mixer_elem_t. KMix does NOT support that and + * always sets both channels (0 and 1). + */ +void Mixer_ALSA::setEnumIdHW(const QString& id, unsigned int idx) { + //kDebug() << "Mixer_ALSA::setEnumIdHW() id=" << id << " , idx=" << idx << ") 1\n"; + int devnum = id2num(id); + snd_mixer_elem_t *elem = getMixerElem( devnum ); + + for (int i = 0; i <= SND_MIXER_SCHN_LAST; ++i) + { + int ret = snd_mixer_selem_set_enum_item(elem, (snd_mixer_selem_channel_id_t)i,idx); + if (ret < 0 && i == 0) + { + // Log errors only for one channel. This should be enough, and another reason is that I also do not check which channels are supported at all. + kError() << "Mixer_ALSA::setEnumIdHW(" << devnum << "), errno=" << ret << "\n"; + } + } + + return; +} + +/** + * Return the ID of the currently selected Enum entry. + * Warning: ALSA supports to have different enums selected on each channel + * of the SAME snd_mixer_elem_t. KMix does NOT support that and + * always shows the value of the first channel. + */ +unsigned int Mixer_ALSA::enumIdHW(const QString& id) { + int devnum = id2num(id); + snd_mixer_elem_t *elem = getMixerElem( devnum ); + unsigned int idx = 0; + + if ( elem != 0 && snd_mixer_selem_is_enumerated(elem) ) + { + int ret = snd_mixer_selem_get_enum_item(elem,SND_MIXER_SCHN_FRONT_LEFT,&idx); + if (ret < 0) { + idx = 0; + kError() << "Mixer_ALSA::enumIdHW(" << devnum << "), errno=" << ret << "\n"; + } + } + return idx; +} + + +int +Mixer_ALSA::readVolumeFromHW( const QString& id, shared_ptr md ) +{ + Volume& volumePlayback = md->playbackVolume(); + Volume& volumeCapture = md->captureVolume(); + int devnum = id2num(id); + int elem_sw; + long vol; + + snd_mixer_elem_t *elem = getMixerElem( devnum ); + if ( !elem ) + { + return Mixer::OK_UNCHANGED; + } + + vol = Volume::MNONE; + // --- playback volume + if ( snd_mixer_selem_has_playback_volume( elem ) ) + { + if ( md->isVirtuallyMuted() ) + { + // Special code path for controls w/o physical mute switch. Doing it in all backends is not perfect, + // but it saves a lot of code and removes a lot of complexity in the Volume and MixDevice classes. + + // Don't feed back the actual 0 volume back from the device to KMix. Just do nothing! + } + else + { + foreach (VolumeChannel vc, volumePlayback.getVolumes() ) + { + int ret = 0; + switch(vc.chid) { + case Volume::LEFT : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_FRONT_LEFT , &vol); break; + case Volume::RIGHT : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT , &vol); break; + case Volume::CENTER : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_FRONT_CENTER, &vol); break; + case Volume::SURROUNDLEFT : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_REAR_LEFT , &vol); break; + case Volume::SURROUNDRIGHT: ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_REAR_RIGHT , &vol); break; + case Volume::REARCENTER : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_REAR_CENTER , &vol); break; + case Volume::WOOFER : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_WOOFER , &vol); break; + case Volume::REARSIDELEFT : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_SIDE_LEFT , &vol); break; + case Volume::REARSIDERIGHT: ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_SIDE_RIGHT , &vol); break; + default: kDebug() << "FATAL: Unknown channel type for playback << " << vc.chid << " ... please report this"; break; + } + if ( ret != 0 ) + kDebug() << "readVolumeFromHW(" << devnum << ") [get_playback_volume] failed, errno=" << ret; + else + volumePlayback.setVolume( vc.chid, vol); + //if (id== "Master:0" || id== "PCM:0" ) { kDebug() << "volumePlayback control=" << id << ", chid=" << i << ", vol=" << vol; } + } + } + } // has playback volume + + // --- playback switch + // TODO: What about has_common_switch() + if ( snd_mixer_selem_has_playback_switch( elem ) ) + { + snd_mixer_selem_get_playback_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &elem_sw ); + md->setMuted( elem_sw == 0 ); + } + + vol = Volume::MNONE; + // --- capture volume + if ( snd_mixer_selem_has_capture_volume ( elem ) ) + { + foreach (VolumeChannel vc, volumeCapture.getVolumes() ) + { + int ret = 0; + switch(vc.chid) { + case Volume::LEFT : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_FRONT_LEFT , &vol); break; + case Volume::RIGHT : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT , &vol); break; + case Volume::CENTER : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_FRONT_CENTER, &vol); break; + case Volume::SURROUNDLEFT : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_REAR_LEFT , &vol); break; + case Volume::SURROUNDRIGHT: ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_REAR_RIGHT , &vol); break; + case Volume::REARCENTER : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_REAR_CENTER , &vol); break; + case Volume::WOOFER : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_WOOFER , &vol); break; + case Volume::REARSIDELEFT : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_SIDE_LEFT , &vol); break; + case Volume::REARSIDERIGHT: ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_SIDE_RIGHT , &vol); break; + default: kDebug() << "FATAL: Unknown channel type for capture << " << vc.chid << " ... please report this"; break; + } + if ( ret != 0 ) + kDebug() << "readVolumeFromHW(" << devnum << ") [get_capture_volume] failed, errno=" << ret; + else volumeCapture.setVolume( vc.chid, vol); + } + } // has capture volume + + // --- capture switch + // TODO: What about has_common_switch() + if ( snd_mixer_selem_has_capture_switch( elem ) ) + { + snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &elem_sw ); + md->setRecSource( elem_sw == 1 ); + + // Refresh the capture switch information of *all* controls of this card. + // Doing it for all is necessary, because enabling one record source often + // automatically disables another record source (due to the hardware design) + foreach ( shared_ptr md, m_mixDevices ) + { + bool isRecsrc = isRecsrcHW( md->id() ); + // kDebug() << "Mixer::setRecordSource(): isRecsrcHW(" << md->id() << ") =" << isRecsrc; + md->setRecSource( isRecsrc ); + } + + } + + // The state Mixer::OK_UNCHANGED is not implemented. It is not strictly required for + // non-pollling backends. + return Mixer::OK; +} + +int +Mixer_ALSA::writeVolumeToHW( const QString& id, shared_ptr md ) +{ + Volume& volumePlayback = md->playbackVolume(); + Volume& volumeCapture = md->captureVolume(); + + int devnum = id2num(id); + + snd_mixer_elem_t *elem = getMixerElem( devnum ); + if ( !elem ) + { + return 0; + } + + + // --- playback switch + bool hasPlaybackSwitch = snd_mixer_selem_has_playback_switch( elem ) || snd_mixer_selem_has_common_switch ( elem ); + if (hasPlaybackSwitch) + { + int sw = 0; + if (!md->isMuted()) + sw = !sw; // invert all bits + snd_mixer_selem_set_playback_switch_all(elem, sw); + } + + + // --- playback volume + if ( snd_mixer_selem_has_playback_volume( elem ) ) + { +// kDebug() << "phys=" << md->hasPhysicalMuteSwitch() << ", muted=" << md->isMuted(); + if ( md->isVirtuallyMuted() ) + { + // Special code path for controls w/o physical mute switch. Doing it in all backends is not perfect, + // but it saves a lot of code and removes a lot of complexity in the Volume and MixDevice classes. + int ret = snd_mixer_selem_set_playback_volume_all( elem, (long)0); + if ( ret != 0 ) + kDebug() << "writeVolumeToHW(" << devnum << ") [set_playback_volume] failed, errno=" << ret; + } + else + { + foreach (VolumeChannel vc, volumePlayback.getVolumes() ) + { + int ret = 0; + switch(vc.chid) + { + case Volume::LEFT : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_FRONT_LEFT , vc.volume); break; + case Volume::RIGHT : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT , vc.volume); break; + case Volume::CENTER : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_FRONT_CENTER, vc.volume); break; + case Volume::SURROUNDLEFT : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_REAR_LEFT , vc.volume); break; + case Volume::SURROUNDRIGHT: ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_REAR_RIGHT , vc.volume); break; + case Volume::REARCENTER : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_REAR_CENTER , vc.volume); break; + case Volume::WOOFER : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_WOOFER , vc.volume); break; + case Volume::REARSIDELEFT : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_SIDE_LEFT , vc.volume); break; + case Volume::REARSIDERIGHT: ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_SIDE_RIGHT , vc.volume); break; + default: kDebug() << "FATAL: Unknown channel type for playback << " << vc.chid << " ... please report this"; break; + } + if ( ret != 0 ) + kDebug() << "writeVolumeToHW(" << devnum << ") [set_playback_volume] failed, errno=" << ret; + //if (id== "Master:0" || id== "PCM:0" ) { kDebug() << "volumePlayback control=" << id << ", chid=" << vc.chid << ", vol=" << vc.volume; } + } + } + } // has playback volume + + + + // --- capture volume + if ( snd_mixer_selem_has_capture_volume ( elem ) ) + { + foreach (VolumeChannel vc, volumeCapture.getVolumes() ) + { + int ret = 0; + switch(vc.chid) { + case Volume::LEFT : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_FRONT_LEFT , vc.volume); break; + case Volume::RIGHT : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT , vc.volume); break; + case Volume::CENTER : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_FRONT_CENTER, vc.volume); break; + case Volume::SURROUNDLEFT : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_REAR_LEFT , vc.volume); break; + case Volume::SURROUNDRIGHT: ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_REAR_RIGHT , vc.volume); break; + case Volume::REARCENTER : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_REAR_CENTER , vc.volume); break; + case Volume::WOOFER : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_WOOFER , vc.volume); break; + case Volume::REARSIDELEFT : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_SIDE_LEFT , vc.volume); break; + case Volume::REARSIDERIGHT: ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_SIDE_RIGHT , vc.volume); break; + default: kDebug() << "FATAL: Unknown channel type for capture << " << vc.chid << " ... please report this"; break; + } + if ( ret != 0 ) kDebug() << "writeVolumeToHW(" << devnum << ") [set_capture_volume] failed, errno=" << ret; + //if (id== "Master:0" || id== "PCM:0" ) { kDebug() << "volumecapture control=" << id << ", chid=" << i << ", vol=" << vc.volume; } + } + } // has capture volume + + // --- capture switch + if ( snd_mixer_selem_has_capture_switch( elem ) ) { + // Hint: snd_mixer_selem_has_common_switch() is already covered in the playback . + // switch. This is probably enough. It would be helpful, if the ALSA project would + // write documentation. Until then, I need to continue guessing semantics. + int sw = 0; + if ( md->isRecSource()) + sw = !sw; // invert all bits + snd_mixer_selem_set_capture_switch_all( elem, sw ); + } + + return 0; +} + +QString +Mixer_ALSA::errorText( int mixer_error ) +{ + QString l_s_errmsg; + switch ( mixer_error ) + { + case Mixer::ERR_PERM: + l_s_errmsg = i18n("You do not have permission to access the alsa mixer device.\n" \ + "Please verify if all alsa devices are properly created."); + break; + case Mixer::ERR_OPEN: + l_s_errmsg = i18n("Alsa mixer cannot be found.\n" \ + "Please check that the soundcard is installed and the\n" \ + "soundcard driver is loaded.\n" ); + break; + default: + l_s_errmsg = Mixer_Backend::errorText( mixer_error ); + } + return l_s_errmsg; +} + + +QString +ALSA_getDriverName() +{ + return "ALSA"; +} + +QString Mixer_ALSA::getDriverName() +{ + return "ALSA"; +} + + diff --git a/kmix/backends/mixer_backend.cpp b/kmix/backends/mixer_backend.cpp new file mode 100644 index 00000000..236a950b --- /dev/null +++ b/kmix/backends/mixer_backend.cpp @@ -0,0 +1,330 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2006-2007 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "mixer_backend.h" + +#include + +// for the "ERR_" declartions, #include mixer.h +#include "core/mixer.h" +#include "core/ControlManager.h" + +#include + +#define POLL_RATE_SLOW 1500 +#define POLL_RATE_FAST 50 + + +#include "mixer_backend_i18n.cpp" + +Mixer_Backend::Mixer_Backend(Mixer *mixer, int device) : +m_devnum (device) , m_isOpen(false), m_recommendedMaster(), _mixer(mixer), _pollingTimer(0), _cardInstance(1) + +{ + // In all cases create a QTimer. We will use it once as a singleShot(), even if something smart + // like ::select() is possible (as in ALSA). And force to do an update. + _readSetFromHWforceUpdate = true; + _pollingTimer = new QTimer(); // will be started on open() and stopped on close() + connect( _pollingTimer, SIGNAL(timeout()), this, SLOT(readSetFromHW()), Qt::QueuedConnection); + +} + +void Mixer_Backend::closeCommon() +{ + freeMixDevices(); +} + +int Mixer_Backend::close() +{ + kDebug() << "Implicit close on " << this << ". Please instead call closeCommon() and close() explicitly (in concrete Backend destructor)"; + // ^^^ Background. before the destructor runs, the C++ runtime changes the virtual pointers to point back + // to the common base class. So what actually runs is not run Mixer_ALSA::close(), but this method. + // + // Comment: IMO this is totally stupid and insane behavior of C++, because you cannot simply cannot call + // the overwritten (cleanup) methods in the destructor. + return 0; +} + +Mixer_Backend::~Mixer_Backend() +{ + unregisterCard(this->getName()); + if (!m_mixDevices.isEmpty()) + { + kDebug() << "Implicit close on " << this << ". Please instead call closeCommon() and close() explicitly (in concrete Backend destructor)"; + } + delete _pollingTimer; +} + +void Mixer_Backend::freeMixDevices() +{ + foreach (shared_ptr md, m_mixDevices) + md->close(); + + m_mixDevices.clear(); +} + +bool Mixer_Backend::openIfValid() +{ + int ret = open(); + if (ret == 0 && (m_mixDevices.count() > 0 || _mixer->isDynamic())) + { + // Hint: _id is probably not yet perfectly set, as it requires the value from open() and an external + // counter. Thus we start the Timer while _id is not properly set. But it will be done immediately + // by the caller of this method. + // Future directions: Do the counter calculation in the backend. It really belongs there, as it is part of + // the PK calculation. Probably provide a standard implementation in Mixer_Backend itself. Also the + // key should be an own class, like: MixerKey(QString backend, QString baseId, int cardInstance) + if (needsPolling()) + { + _pollingTimer->start(POLL_RATE_FAST); + } + else + { + // The initial state must be read manually + QTimer::singleShot( POLL_RATE_FAST, this, SLOT(readSetFromHW())); + } + return true; // could be opened + } + else + { + //shutdown(); + return false; // could not open + } +} + +bool Mixer_Backend::isOpen() { + return m_isOpen; +} + +/** + * Queries the backend driver whether there are new changes in any of the controls. + * If you cannot find out for a backend, return "true" - this is also the default implementation. + * @return true, if there are changes. Otherwise false is returned. + */ +bool Mixer_Backend::prepareUpdateFromHW() { + return true; +} + +/** + * The name of the Mixer this backend represents. + * Often it is just a name/id for the kernel. so name and id are usually identical. Virtual/abstracting backends are + * different, as they represent some distinct function like "Application streams" or "Capture Devices". Also backends + * that do not have names might can to set ID and name different like i18n("SUN Audio") and "SUNAudio". + */ +QString Mixer_Backend::getName() const +{ + return m_mixerName; +} + +/** + * The id of the Mixer this backend represents. The default implementation simply returns the name. + * Often it is just a name/id for the kernel. so name and id are usually identical. See also #Mixer_Backend::getName(). + * You must override this method if you want to set ID different from name. + */ +QString Mixer_Backend::getId() const +{ + return m_mixerName; // Backwards compatibility. PulseAudio overrides it. +} + +/** + * After calling this, readSetFromHW() will do a complete update. This will + * trigger emitting the appropriate signals like controlChanged(). + * + * This method is useful, if you need to get a "refresh signal" - used at: + * 1) Start of KMix - so that we can be sure an initial signal is emitted + * 2) When reconstructing any MixerWidget (e.g. DockIcon after applying preferences) + */ +void Mixer_Backend::readSetFromHWforceUpdate() const { + _readSetFromHWforceUpdate = true; +} + + +/** + * You can call this to retrieve the freshest information from the mixer HW. + * This method is also called regularly by the mixer timer. + */ +void Mixer_Backend::readSetFromHW() +{ + bool updated = prepareUpdateFromHW(); + if ( (! updated) && (! _readSetFromHWforceUpdate) ) { + // Some drivers (ALSA) are smart. We don't need to run the following + // time-consuming update loop if there was no change + kDebug(67100) << "Mixer::readSetFromHW(): smart-update-tick"; + return; + } + + _readSetFromHWforceUpdate = false; + + int ret = Mixer::OK_UNCHANGED; + + foreach (shared_ptr md, m_mixDevices ) + { + //bool debugMe = (md->id() == "PCM:0" ); + bool debugMe = false; + if (debugMe) kDebug() << "Old PCM:0 playback state" << md->isMuted() + << ", vol=" << md->playbackVolume().getAvgVolumePercent(Volume::MALL); + + int retLoop = readVolumeFromHW( md->id(), md ); + if (debugMe) kDebug() << "New PCM:0 playback state" << md->isMuted() + << ", vol=" << md->playbackVolume().getAvgVolumePercent(Volume::MALL); + if (md->isEnum() ) + { + /* + * This could be reworked: + * Plan: Read everything (incuding enum's) in readVolumeFromHW(). + * readVolumeFromHW() should then be renamed to readHW(). + */ + md->setEnumId( enumIdHW(md->id()) ); + } + + // Transition the outer return value with the value from this loop iteration + if ( retLoop == Mixer::OK && ret == Mixer::OK_UNCHANGED ) + { + // Unchanged => OK (Changed) + ret = Mixer::OK; + } + else if ( retLoop != Mixer::OK && retLoop != Mixer::OK_UNCHANGED ) + { + // If current ret from loop in not OK, then transition to that: ret (Something) => retLoop (Error) + ret = retLoop; + } + } + + if ( ret == Mixer::OK ) + { + // We explicitly exclude Mixer::OK_UNCHANGED and Mixer::ERROR_READ + if ( needsPolling() ) + { + // Upgrade polling frequency temporarily to be more smoooooth + _pollingTimer->setInterval(POLL_RATE_FAST); + QTime fastPollingEndsAt = QTime::currentTime (); + fastPollingEndsAt = fastPollingEndsAt.addSecs(5); + _fastPollingEndsAt = fastPollingEndsAt; + //_fastPollingEndsAt = fastPollingEndsAt; + kDebug() << "Start fast polling from " << QTime::currentTime() <<"until " << _fastPollingEndsAt; + } + + ControlManager::instance().announce(_mixer->id(), ControlChangeType::Volume, QString("Mixer.fromHW")); + } + + else + { + // This code path is entered on Mixer::OK_UNCHANGED and ERROR + bool fastPollingEndsNow = (!_fastPollingEndsAt.isNull()) && _fastPollingEndsAt < QTime::currentTime (); + if ( fastPollingEndsNow ) + { + kDebug() << "End fast polling"; + _fastPollingEndsAt = QTime(); // NULL time + _pollingTimer->setInterval(POLL_RATE_SLOW); + } + } +} + +/** + * Return the MixDevice, that would qualify best as MasterDevice. The default is to return the + * first device in the device list. Backends can override this (i.e. the ALSA Backend does so). + * The users preference is NOT returned by this method - see the Mixer class for that. + */ +shared_ptr Mixer_Backend::recommendedMaster() +{ + if ( m_recommendedMaster ) + { + // Backend has set a recommended master. Thats fine. Using it. + return m_recommendedMaster; + } + else if ( ! m_mixDevices.isEmpty() ) + { + // Backend has NOT set a recommended master. Evil backend + // => lets help out, using the first device (if exists) + return m_mixDevices.at(0); + } + else + { + if ( !_mixer->isDynamic()) + // This should never ever happen, as KMix does NOT accept soundcards without controls + kError(67100) << "Mixer_Backend::recommendedMaster(): returning invalid master. This is a bug in KMix. Please file a bug report stating how you produced this." << endl; + } + + // If we reach this code path, then obiously m_recommendedMaster == 0 (see above) + return m_recommendedMaster; + +} + +/** + * Sets the ID of the currently selected Enum entry. + * This is a dummy implementation - if the Mixer backend + * wants to support it, it must implement the driver specific + * code in its subclass (see Mixer_ALSA.cpp for an example). + */ +void Mixer_Backend::setEnumIdHW(const QString& , unsigned int) { + return; +} + +/** + * Return the ID of the currently selected Enum entry. + * This is a dummy implementation - if the Mixer backend + * wants to support it, it must implement the driver specific + * code in its subclass (see Mixer_ALSA.cpp for an example). + */ +unsigned int Mixer_Backend::enumIdHW(const QString& ) { + return 0; +} + +/** + * Move the stream to a new destination + */ +bool Mixer_Backend::moveStream( const QString& id, const QString& destId ) { + Q_UNUSED(id); + Q_UNUSED(destId); + return false; +} + + +QString Mixer_Backend::errorText(int mixer_error) +{ + QString l_s_errmsg; + switch (mixer_error) + { + case Mixer::ERR_PERM: + l_s_errmsg = i18n("kmix:You do not have permission to access the mixer device.\n" \ + "Please check your operating systems manual to allow the access."); + break; + case Mixer::ERR_WRITE: + l_s_errmsg = i18n("kmix: Could not write to mixer."); + break; + case Mixer::ERR_READ: + l_s_errmsg = i18n("kmix: Could not read from mixer."); + break; + case Mixer::ERR_OPEN: + l_s_errmsg = i18n("kmix: Mixer cannot be found.\n" \ + "Please check that the soundcard is installed and that\n" \ + "the soundcard driver is loaded.\n"); + break; + default: + l_s_errmsg = i18n("kmix: Unknown error. Please report how you produced this error."); + break; + } + return l_s_errmsg; +} + + + + +#include "mixer_backend.moc" diff --git a/kmix/backends/mixer_backend.h b/kmix/backends/mixer_backend.h new file mode 100644 index 00000000..b1431fef --- /dev/null +++ b/kmix/backends/mixer_backend.h @@ -0,0 +1,238 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2006-2007 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MIXER_BACKEND_H +#define MIXER_BACKEND_H + +#include +#include +//#include "core/mixer.h" +#include "core/mixdevice.h" +#include "core/mixset.h" +class Mixer; + + +class Mixer_Backend : public QObject +{ + Q_OBJECT + +friend class Mixer; + +// The Mixer Backend's may only be accessed from the Mixer class. +protected: + Mixer_Backend(Mixer *mixer, int devnum); + virtual ~Mixer_Backend(); + + /** + * Derived classes MUST implement this to open the mixer. + * + * @return a KMix error code (O=OK). + */ + virtual int open() = 0; + /** + * Derived classes MUST implement this to close the mixer. Do not call this directly, but use shutdown() instead. + * The method cannot be made pure virtual, as we use close() in the destructor, and C++ does not allow this. + * http://stackoverflow.com/questions/99552/where-do-pure-virtual-function-call-crashes-come-from?lq=1 + * + * @return a KMix error code (O=OK). + */ + virtual int close(); // Not pure virtual. See comment! + + /** + * Shutdown deinitializes this MixerBackend, freeing resources + */ + void closeCommon(); + + /** + * Returns the driver name, e.g. "ALSA" or "OSS". This virtual method is for looking up the + * driver name on instanciated objects. + * + * Please note, that there is also a static implementation of the driverName + * (Because there is no "virtual static" in C++, I need the method twice). + * The static implementation is for the Mixer Factory (who needs it *before* instanciating an object). + * While it is not a member function, its implementation can still be found in the corresponding + * Backend implementation. For example in mixer_oss.cpp there is a global function called OSS_getDriverName(). + */ + virtual QString getDriverName() = 0; + + /** + * Opens the mixer, if it constitures a valid Device. You should return "false", when + * the Mixer with the devnum given in the constructor is not supported by the Backend. The two + * typical cases are: + * (1) No such hardware installed + * (2) The hardware exists, but has no mixer support (e.g. external soundcard with only mechanical volume knobs) + * The implementation calls open(), checks the return code and whether the number of + * supported channels is > 0. The device remains opened if it is valid, otherwise a close() is done. + */ + bool openIfValid(); + + /** @return true, if the Mixer is open (and thus can be operated) */ + bool isOpen(); + + virtual bool prepareUpdateFromHW(); + void readSetFromHWforceUpdate() const; + + /// Volume Read + virtual int readVolumeFromHW( const QString& id, shared_ptr ) = 0; + /// Volume Write + virtual int writeVolumeToHW( const QString& id, shared_ptr ) = 0; + + /// Enums + virtual void setEnumIdHW(const QString& id, unsigned int); + virtual unsigned int enumIdHW(const QString& id); + + virtual bool moveStream( const QString& id, const QString& destId ); + + // Future directions: Move media*() methods to MediaController class + virtual int mediaPlay(QString ) { return 0; }; // implement in the backend if it supports it + virtual int mediaPrev(QString ) { return 0; }; // implement in the backend if it supports it + virtual int mediaNext(QString ) { return 0;}; // implement in the backend if it supports it + + /// Overwrite in the backend if the backend can see changes without polling + virtual bool needsPolling() { return true; } + + shared_ptr recommendedMaster(); + + /** + * Return a translated error text for the given error number. + * Subclasses can override this method to produce platform + * specific error descriptions. + */ + virtual QString errorText(int mixer_error); + + /// Returns translated WhatsThis messages for a control.Translates from + virtual QString translateKernelToWhatsthis(const QString &kernelName); + + // Return an Universal Device Identification (suitable for the OS, especially for Hotplug and Unplug events) + virtual QString& udi() { return _udi; }; + + int m_devnum; + /** + * User friendly name of the Mixer (e.g. "USB 7.1 Surround System"). If your mixer API gives you a usable name, use that name. + */ + virtual QString getName() const; + virtual QString getId() const; + virtual int getCardInstance() const { return _cardInstance; } + + // All controls of this card + MixSet m_mixDevices; + + /****************************************************************************************** + * Please don't access the next vars from the Mixer class (even though Mixer is a friend). + * There are proper access methods for them. + ******************************************************************************************/ + bool m_isOpen; + // The MixDevice that would qualify best as MasterDevice (according to the taste of the Backend developer) + shared_ptr m_recommendedMaster; + // The Mixer is stored her only for one reason: The backend creates the MixDevice's, and it has shown + // that it is helpful if the MixDevice's know their corresponding Mixer. KMix lived 10 years without that, + // but just believe me. It's *really* better, for example, you can put controls of different soundcards in + // one View. That is very cool! Also the MDW doesn't need to store the Mixer any longer (MDW is a GUI element, + // so that was 'wrong' anyhow + Mixer* _mixer; + QTimer* _pollingTimer; + QString _udi; // Universal Device Identification + + mutable bool _readSetFromHWforceUpdate; + +signals: + void controlChanged( void ); // TODO remove? + +public slots: +/** + * Re-initialize. Currently only implemented by PulseAudio backend, and this slot might get moved there + */ + virtual void reinit() {}; + +protected: + void freeMixDevices(); + + QMap s_mixerNums; + + /** + * Registers the card for this Backend and sets the card discriminator for the given card name. + * The discriminator should always be 1, unless a second card with + * the same name of a registered card was already registered. Default implementation will return 2, 3 and so on + * for more cards. Subclasses can override this and return arbitrary ID's, but any ID that is not 1 will be + * displayed to the user everywhere where a mixer name is shown, like in the tab name. + * + * For the background please see BKO-327471 and read the following info: + * "Count mixer nums for every mixer name to identify mixers with equal names. + * This is for creating persistent (reusable) primary keys, which can safely + * be referenced (especially for config file access, so it is meant to be persistent!)." + * + * + * + * @param cardBaseName + */ + void registerCard(QString cardBaseName) + { + m_mixerName = cardBaseName; + int cardDiscriminator = 1 + s_mixerNums[cardBaseName]; + kDebug() << "cardBaseName=" << cardBaseName << ", cardDiscriminator=" << cardDiscriminator; + _cardInstance = cardDiscriminator; +// return cardDiscriminator; + } + + /** + * Unregisters the card of this Backend. The cardDiscriminator counter for this card name is reduced by 1. + * See #registerCard() for more info. + * + * TODO This is not entirely correct. Example: If the first card (cardDiscrimiator == 1) is unpluggged, then + * s_mixerNums["cardName"] is changed from 2 to 1. The next plug of registerCard("cardName") will use + * cardDiscriminator == 2, but the card with taht discrimniator was not unplugged => BANG!!! + * + * @param cardBaseName + */ + void unregisterCard(QString cardBaseName) + { + QMap::const_iterator it = s_mixerNums.constFind(cardBaseName); + if (it != s_mixerNums.constEnd()) + { + int beforeValue = it.value(); + int afterValue = beforeValue-1; + if (beforeValue > 0) + s_mixerNums[cardBaseName] = afterValue; + kDebug() << "beforeValue=" << beforeValue << ", afterValue" << afterValue; + } + } + + int _cardInstance; + + +protected slots: + virtual void readSetFromHW(); +private: + QTime _fastPollingEndsAt; + QString m_mixerName; +}; + +typedef Mixer_Backend *getMixerFunc( Mixer* mixer, int device ); +typedef QString getDriverNameFunc( ); + +struct MixerFactory +{ + getMixerFunc *getMixer; + getDriverNameFunc *getDriverName; +}; + + +#endif diff --git a/kmix/backends/mixer_backend_i18n.cpp b/kmix/backends/mixer_backend_i18n.cpp new file mode 100644 index 00000000..ddb72798 --- /dev/null +++ b/kmix/backends/mixer_backend_i18n.cpp @@ -0,0 +1,29 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2006-2009 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +QString Mixer_Backend::translateKernelToWhatsthis(const QString &kernelName) +{ + if (kernelName == "Mic:0") return i18n("Recording level of the microphone input."); + else if (kernelName == "Master:0") return i18n("Controls the volume of the front speakers or all speakers (depending on your soundcard model). If you use a digital output, you might need to also use other controls like ADC or DAC. For headphones, soundcards often supply a Headphone control."); + else if (kernelName == "PCM:0") return i18n("Most media, such as MP3s or Videos, are played back using the PCM channel. As such, the playback volume of such media is controlled by both this and the Master or Headphone channels."); + else if (kernelName == "Headphone:0") return i18n("Controls the headphone volume. Some soundcards include a switch that must be manually activated to enable the headphone output."); + else return i18n("---"); +} diff --git a/kmix/backends/mixer_mpris2.cpp b/kmix/backends/mixer_mpris2.cpp new file mode 100644 index 00000000..5ce28a00 --- /dev/null +++ b/kmix/backends/mixer_mpris2.cpp @@ -0,0 +1,697 @@ +/** + * KMix -- MPRIS2 backend + * + * Copyright (C) 2011 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "mixer_mpris2.h" +#include "core/mixer.h" +#include "core/ControlManager.h" +#include "core/GlobalConfig.h" + +#include +#include +#include +#include + +#include +#include + +// Set the QDBUS_DEBUG env variable for debugging Qt DBUS calls. + +Mixer_Backend* MPRIS2_getMixer(Mixer *mixer, int device ) +{ + return new Mixer_MPRIS2(mixer, device ); +} + +Mixer_MPRIS2::Mixer_MPRIS2(Mixer *mixer, int device) : Mixer_Backend(mixer, device ) +{ +} + + +int Mixer_MPRIS2::open() +{ + if ( m_devnum != 0 ) + return Mixer::ERR_OPEN; + + registerCard(i18n("Playback Streams")); + _id = "Playback Streams"; + _mixer->setDynamic(); + return addAllRunningPlayersAndInitHotplug(); +} + +int Mixer_MPRIS2::close() +{ + m_isOpen = false; + closeCommon(); + qDeleteAll(controls); + controls.clear(); + return 0; +} + +int Mixer_MPRIS2::mediaPlay(QString id) +{ + return mediaControl(id, "PlayPause"); +} + +int Mixer_MPRIS2::mediaPrev(QString id) +{ + return mediaControl(id, "Previous"); +} + +int Mixer_MPRIS2::mediaNext(QString id) +{ + return mediaControl(id, "Next"); +} + +/** + * Sends a media control command to the given application. + * @param applicationId The MPRIS applicationId + * @returns Always 0. Hint: Currently nobody uses the return code + */ +int Mixer_MPRIS2::mediaControl(QString applicationId, QString commandName) +{ + MPrisControl* mad = controls.value(applicationId); + if ( mad == 0 ) + return 0; // Might have disconnected recently => simply ignore command + + kDebug() << "Send " << commandName << " to id=" << applicationId; + QDBusPendingReply<> repl2 = + mad->playerIfc->asyncCall(commandName); + + + QDBusPendingCallWatcher* watchMediaControlReply = new QDBusPendingCallWatcher(repl2, mad); + connect(watchMediaControlReply, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(watcherMediaControl(QDBusPendingCallWatcher *))); + + return 0; // Presume everything went well. Can't do more for ASYNC calls +} + +void Mixer_MPRIS2::watcherMediaControl(QDBusPendingCallWatcher* watcher) +{ + MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher); + if (mprisCtl == 0) + { + return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged) + } + + // Actually the code below in this method is more or less just debugging + const QDBusMessage& msg = watcher->reply(); + QString id = mprisCtl->getId(); + QString busDestination = mprisCtl->getBusDestination(); + kDebug() << "Media control for id=" << id << ", path=" << msg.path() << ", interface=" << msg.interface() << ", busDestination" << busDestination; +} + +/** + * readVolumeFromHW() should be used only for hotplug (and even that should go away). Everything should operate via + * the slot volumeChanged in the future. + */ +int Mixer_MPRIS2::readVolumeFromHW( const QString& /*id*/, shared_ptr /*md*/) +{ + // Everything is done by notifications => no code neccessary + return Mixer::OK_UNCHANGED; +} + + +/** + * A slot that processes data from the MPrisControl that emit the signal. + * + * @param The emitting MPrisControl + * @param newVolume The new volume + */ +void Mixer_MPRIS2::playbackStateChanged(MPrisControl* mad, MediaController::PlayState playState) +{ + shared_ptr md = m_mixDevices.get(mad->getId()); + md->getMediaController()->setPlayState(playState); + QMetaObject::invokeMethod(this, "announceGUI", Qt::QueuedConnection); +// ControlManager::instance().announce(_mixer->id(), ControlChangeType::GUI, QString("MixerMPRIS2.playbackStateChanged")); +} + + +/** + * A slot that processes data from the MPrisControl that emit the signal. + * + * @param The emitting MPrisControl + * @param newVolume The new volume + */ +void Mixer_MPRIS2::volumeChanged(MPrisControl* mad, double newVolume) +{ + shared_ptr md = m_mixDevices.get(mad->getId()); + int volInt = newVolume *100; + if (GlobalConfig::instance().data.debugVolume) + kDebug() << "changed" << volInt; + volumeChangedInternal(md, volInt); +} + +void Mixer_MPRIS2::volumeChangedInternal(shared_ptr md, int volumePercentage) +{ + if ( md->isVirtuallyMuted() && volumePercentage == 0) + { + // Special code path for virtual mute switches. Don't write back the volume if it is muted in the KMix GUI + return; + } + + Volume& vol = md->playbackVolume(); + vol.setVolume( Volume::LEFT, volumePercentage); + md->setMuted(volumePercentage == 0); + QMetaObject::invokeMethod(this, "announceVolume", Qt::QueuedConnection); +// ControlManager::instance().announce(_mixer->id(), ControlChangeType::Volume, QString("MixerMPRIS2.volumeChanged")); +} + +// The following is an example message for an incoming volume change: +/* +signal sender=:1.125 -> dest=(null destination) serial=503 path=/org/mpris/MediaPlayer2; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged + string "org.mpris.MediaPlayer2.Player" + array [ + dict entry( + string "Volume" + variant double 0.81 + ) + ] + array [ + ] + */ + +/** + * @overload + * + * @param id + * @param md + * @return + */ +int Mixer_MPRIS2::writeVolumeToHW( const QString& id, shared_ptr md ) +{ + Volume& vol = md->playbackVolume(); + double volFloat = 0; + if ( ! md->isMuted() ) + { + int volInt = vol.getVolume(Volume::LEFT); + volFloat = volInt/100.0; + } + + QList arg; + arg.append(QString("org.mpris.MediaPlayer2.Player")); + arg.append(QString("Volume")); + arg << QVariant::fromValue(QDBusVariant(volFloat)); + + MPrisControl* mad = controls.value(id); + + QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2.Player")); + QVariant v2 = QVariant(QString("Volume")); + QVariant v3 = QVariant::fromValue(QDBusVariant(volFloat)); +// QVariant v3 = QVariant(volFloat); + + // I don't care too much for the reply, as I won't receive a result. Thus fire-and-forget here. + mad->propertyIfc->asyncCall("Set", v1, v2, v3); + return 0; +} + +void Mixer_MPRIS2::setEnumIdHW(const QString&, unsigned int) +{ + // no enums in MPRIS +} + +unsigned int Mixer_MPRIS2::enumIdHW(const QString&) +{ + // no enums in MPRIS + return 0; +} + +bool Mixer_MPRIS2::moveStream( const QString&, const QString& ) +{ + // not supported in MPRIS + return false; +} + + +/** + * Adds all currently running players and then starts listening + * for changes (new players, and disappearing players).
+ * + * @return int + **/ +int Mixer_MPRIS2::addAllRunningPlayersAndInitHotplug() +{ + QDBusConnection dbusConn = QDBusConnection::sessionBus(); + if (! dbusConn.isConnected() ) + { + kError(67100) << "Cannot connect to the D-Bus session bus.\n" + << "To start it, run:\n" + <<"\teval `dbus-launch --auto-syntax`\n"; + return Mixer::ERR_OPEN; + } + + // Start listening for new Mediaplayers + bool connected = dbusConn.connect("", QString("/org/freedesktop/DBus"), "org.freedesktop.DBus", "NameOwnerChanged", this, SLOT(newMediaPlayer(QString,QString,QString)) ); + if (!connected) + { + kWarning() << "MPRIS2 hotplug init failure. New Media Players will not be detected."; + } + + /* Here is a small concurrency issue. + * If new players appear between registeredServiceNames() below and the connect() above these players *might* show up doubled in KMix. + * There is no simple solution (reversing could have the problem of not-adding), so we live for now with it. + */ + + /* + * Bug 311189: Introspecting via "dbusConn.interface()->registeredServiceNames()" does not work too well. + * Comment: I am not so sure that registeredServiceNames() is really an issue. It is more likely + * in a later step, when talking to the probed apps. Still, I now do a hand crafted 3-line version of + * registeredServiceNames() via "ListNames", so I can later more easily change to async. + */ + QDBusInterface dbusIfc("org.freedesktop.DBus", "/org/freedesktop/DBus", + "org.freedesktop.DBus", dbusConn); + QDBusPendingReply repl = dbusIfc.asyncCall("ListNames"); + repl.waitForFinished(); + + if (! repl.isValid() ) + { + kError() << "Invalid reply while listing Media Players. MPRIS2 players will not be available." << repl.error(); + return 1; + } + + QString busDestination; + foreach ( busDestination , repl.value() ) + { + if ( busDestination.startsWith("org.mpris.MediaPlayer2") ) + { + addMprisControlAsync(busDestination); + kDebug() << "MPRIS2: Attached media player on busDestination=" << busDestination; + } + } + + return 0; +} + +QString Mixer_MPRIS2::busDestinationToControlId(const QString& busDestination) +{ + const QString prefix = "org.mpris.MediaPlayer2."; + if (! busDestination.startsWith(prefix)) + { + kWarning() << "Ignoring unsupported control, busDestination=" << busDestination; + return QString(); + } + + return busDestination.mid(prefix.length()); +} + +/** + * Asynchronously add the MPRIS control designated by the DBUS busDestination. + * to the internal apps list. + * + * @param conn An open connection to the DBUS Session Bus + * @param busDestination The DBUS busDestination, e.g. "org.mpris.MediaPlayer2.amarok" + */ +void Mixer_MPRIS2::addMprisControlAsync(QString busDestination) +{ + // -1- Create a MPrisControl. Its fields will be filled partially here, partially via ASYNC DUBUS replies + QString id = busDestinationToControlId(busDestination); + kDebug() << "Get control of busDestination=" << busDestination << "id=" << id; + + QDBusConnection conn = QDBusConnection::sessionBus(); + QDBusInterface *qdbiProps = new QDBusInterface(QString(busDestination), QString("/org/mpris/MediaPlayer2"), "org.freedesktop.DBus.Properties", conn, this); + QDBusInterface *qdbiPlayer = new QDBusInterface(QString(busDestination), QString("/org/mpris/MediaPlayer2"), "org.mpris.MediaPlayer2.Player", conn, this); + + // -2- Add the control to our official control list + MPrisControl* mad = new MPrisControl(id, busDestination); + mad->propertyIfc = qdbiProps; + mad->playerIfc = qdbiPlayer; + controls.insert(id, mad); + + + /* + * WTF: - asyncCall("Get", arg) : returns an error message (see below) + * - asyncCallWithArgumentList("Get", arg) : returns an error message (see below) + * - callWithArgumentList(QDBus::Block, "Get", arg) : works + * - syncCall("Get", v1, v2) : works + * + * kmix(13543) Mixer_MPRIS2::addMPrisControl: (marok), msg2= QDBusMessage(type=Error, service=":1.44", error name="org.freedesktop.DBus.Error.UnknownMethod", error message="No such method 'Get' in interface 'org.freedesktop.DBus.Properties' at object path '/org/mpris/MediaPlayer2' (signature 'av')", signature="s", contents=("No such method 'Get' in interface 'org.freedesktop.DBus.Properties' at object path '/org/mpris/MediaPlayer2' (signature 'av')") ) , isValid= false , isFinished= true , isError= true + * + * This behavior is total counter-intuitive :-((( + */ + + // Create ASYNC DBUS queries for the new control. This effectively starts a chain of async DBUS commands. + QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2")); + QVariant v2 = QVariant(QString("Identity")); + QDBusPendingReply repl2 = mad->propertyIfc->asyncCall("Get", v1, v2); + QDBusPendingCallWatcher* watchIdentity = new QDBusPendingCallWatcher(repl2, mad); + connect(watchIdentity, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(watcherPlugControlId(QDBusPendingCallWatcher *))); +} + +MixDevice::ChannelType Mixer_MPRIS2::getChannelTypeFromPlayerId(const QString& id) +{ + // TODO This hardcoded application list is a quick hack. It should be generalized. + MixDevice::ChannelType ct = MixDevice::APPLICATION_STREAM; + if (id.startsWith("amarok")) + { + ct = MixDevice::APPLICATION_AMAROK; + } + else if (id.startsWith("banshee")) + { + ct = MixDevice::APPLICATION_BANSHEE; + } + else if (id.startsWith("vlc")) + { + ct = MixDevice::APPLICATION_VLC; + } + else if (id.startsWith("xmms")) + { + ct = MixDevice::APPLICATION_XMM2; + } + else if (id.startsWith("tomahawk")) + { + ct = MixDevice::APPLICATION_TOMAHAWK; + } + else if (id.startsWith("clementine")) + { + ct = MixDevice::APPLICATION_CLEMENTINE; + } + + return ct; +} + +void Mixer_MPRIS2::watcherInitialVolume(QDBusPendingCallWatcher* watcher) +{ + MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher); + if (mprisCtl == 0) + return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged) + + const QDBusMessage& msg = watcher->reply(); + QList repl = msg.arguments(); + if ( ! repl.isEmpty() ) + { + QDBusVariant dbusVariant = qvariant_cast(repl.at(0)); + QVariant result2 = dbusVariant.variant(); + double volume = result2.toDouble(); + volumeChanged(mprisCtl, volume); + } + + watcher->deleteLater(); +} + +void Mixer_MPRIS2::watcherInitialPlayState(QDBusPendingCallWatcher* watcher) +{ + MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher); + if (mprisCtl == 0) + return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged) + + const QDBusMessage& msg = watcher->reply(); + QList repl = msg.arguments(); + if ( ! repl.isEmpty() ) + { + QDBusVariant dbusVariant = qvariant_cast(repl.at(0)); + QVariant result2 = dbusVariant.variant(); + QString playbackStateString = result2.toString(); + + MediaController::PlayState playState = Mixer_MPRIS2::mprisPlayStateString2PlayState(playbackStateString); + playbackStateChanged(mprisCtl, playState); + } + + watcher->deleteLater(); +} + + +/** + * Convenience method for the watcher*() methods. + * Returns the MPrisControl that is parent of the given watcher, if the reply is valid. In this case you can + * use the result and call watcher->deleteLater() after processing the result. + * + * Otherwise 0 is returned, and watcher->deleteLater() is called. Important You must call watcher->deleteLater() + * yourself for the other (normal/good) case. + * + * @param watcher + * @return + */ +MPrisControl* Mixer_MPRIS2::watcherHelperGetMPrisControl(QDBusPendingCallWatcher* watcher) +{ + const QDBusMessage& msg = watcher->reply(); + if ( msg.type() == QDBusMessage::ReplyMessage ) + { + QObject* obj = watcher->parent(); + MPrisControl* mad = qobject_cast(obj); + if (mad != 0) + { + return mad; + } + kWarning() << "Ignoring unexpected Control Id. object=" << obj; + } + + else if ( msg.type() == QDBusMessage::ErrorMessage ) + { + kError() << "ERROR in Media control operation, path=" << msg.path() << ", msg=" << msg; + } + + + watcher->deleteLater(); + return 0; +} + +void Mixer_MPRIS2::watcherPlugControlId(QDBusPendingCallWatcher* watcher) +{ + MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher); + if (mprisCtl == 0) + { + return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged) + } + + const QDBusMessage& msg = watcher->reply(); + QString id = mprisCtl->getId(); + QString busDestination = mprisCtl->getBusDestination(); + QString readableName = id; // Start with ID, but replace with reply (if exists) + + kDebug() << "Plugging id=" << id << ", busDestination" << busDestination << ", name= " << readableName; + + QList repl = msg.arguments(); + if ( ! repl.isEmpty() ) + { + // We have to do some very ugly casting from QVariant to QDBusVariant to QVariant. This API totally sucks. + QDBusVariant dbusVariant = qvariant_cast(repl.at(0)); + QVariant result2 = dbusVariant.variant(); + readableName = result2.toString(); + +// kDebug() << "REPLY " << result2.type() << ": " << readableName; + + MixDevice::ChannelType ct = getChannelTypeFromPlayerId(id); + MixDevice* mdNew = new MixDevice(_mixer, id, readableName, ct); + // MPRIS2 doesn't support an actual mute switch. Mute is defined as volume = 0.0 + // Thus we won't add the playback switch + Volume* vol = new Volume( 100, 0, false, false); + vol->addVolumeChannel(VolumeChannel(Volume::LEFT)); // MPRIS is only one control ("Mono") + MediaController* mediaContoller = mdNew->getMediaController(); + mediaContoller->addMediaPlayControl(); + mediaContoller->addMediaNextControl(); + mediaContoller->addMediaPrevControl(); + mdNew->setApplicationStream(true); + mdNew->addPlaybackVolume(*vol); + + m_mixDevices.append( mdNew->addToPool() ); + + delete vol; // vol is only temporary. mdNew has its own volume object. => delete + + QDBusConnection sessionBus = QDBusConnection::sessionBus(); + sessionBus.connect(busDestination, QString("/org/mpris/MediaPlayer2"), "org.freedesktop.DBus.Properties", "PropertiesChanged", mprisCtl, SLOT(onPropertyChange(QString,QVariantMap,QStringList)) ); + connect(mprisCtl, SIGNAL(volumeChanged(MPrisControl*,double)), this, SLOT(volumeChanged(MPrisControl*,double)) ); + connect(mprisCtl, SIGNAL(playbackStateChanged(MPrisControl*,MediaController::PlayState)), SLOT (playbackStateChanged(MPrisControl*,MediaController::PlayState)) ); + + sessionBus.connect(busDestination, QString("/Player"), "org.freedesktop.MediaPlayer", "TrackChange", mprisCtl, SLOT(trackChangedIncoming(QVariantMap)) ); + + // The following line is evil: mad->playerIfc->property("Volume") is in fact a synchronous call, and + // sync calls are strictly forbidden, see bug 317926 + //volumeChanged(mad, mad->playerIfc->property("Volume").toDouble()); + + + // --- Query initial state -------------------------------------------------------------------------------- + QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2.Player")); + + QVariant v2 = QVariant(QString("Volume")); + QDBusPendingReply repl2 = mprisCtl->propertyIfc->asyncCall("Get", v1, v2); + QDBusPendingCallWatcher* watcherOutgoing = new QDBusPendingCallWatcher(repl2, mprisCtl); + connect(watcherOutgoing, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(watcherInitialVolume(QDBusPendingCallWatcher *))); + + v2 = QVariant(QString("PlaybackStatus")); + repl2 = mprisCtl->propertyIfc->asyncCall("Get", v1, v2); + watcherOutgoing = new QDBusPendingCallWatcher(repl2, mprisCtl); + connect(watcherOutgoing, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(watcherInitialPlayState(QDBusPendingCallWatcher *))); + + // Push notifyToReconfigureControls to stack, so it will not be executed synchronously + announceControlListAsync(id); + } + + watcher->deleteLater(); +} + + +// ----------------------------------------------------------------------------------------------------------- +// ASYNC announce slots, including convenience wrappers +// ----------------------------------------------------------------------------------------------------------- +/** + * Convenience wrapper to do the ASYNC call to #announceControlList() + * @param + */ +void Mixer_MPRIS2::announceControlListAsync(QString /*streamId*/) +{ + // currently we do not use the streamId + QMetaObject::invokeMethod(this, "announceControlList", Qt::QueuedConnection); +} + +void Mixer_MPRIS2::announceControlList() +{ + ControlManager::instance().announce(_mixer->id(), ControlChangeType::ControlList, getDriverName()); +} + +void Mixer_MPRIS2::announceGUI() +{ + ControlManager::instance().announce(_mixer->id(), ControlChangeType::GUI, getDriverName()); +} + +void Mixer_MPRIS2::announceVolume() +{ + ControlManager::instance().announce(_mixer->id(), ControlChangeType::Volume, getDriverName()); +} + +// ----------------------------------------------------------------------------------------------------------- + + +/** + * Handles the hotplug of new MPRIS2 enabled Media Players + */ +void Mixer_MPRIS2::newMediaPlayer(QString name, QString oldOwner, QString newOwner) +{ + if ( name.startsWith("org.mpris.MediaPlayer2") ) + { + if ( oldOwner.isEmpty() && !newOwner.isEmpty()) + { + kDebug() << "Mediaplayer registers: " << name; + addMprisControlAsync(name); + } + else if ( !oldOwner.isEmpty() && newOwner.isEmpty()) + { + QString id = busDestinationToControlId(name); + kDebug() << "Mediaplayer unregisters: " << name << " , id=" << id; + + // -1- Remove Mediaplayer connection + if (controls.contains(id)) + { + const MPrisControl *control = controls.value(id); + QObject::disconnect(control,0,0,0); + controls.remove(id); + } + + // -2- Remove MixDevice from internal list + shared_ptr md = m_mixDevices.get(id); + if (md) + { + // We know about the player that is unregistering => remove internally + md->close(); + m_mixDevices.removeById(id); + announceControlListAsync(id); + kDebug() << "MixDevice 4 useCount=" << md.use_count(); + } + } + else + { + kWarning() << "Mediaplayer has registered under a new name. This is currently not supported by KMix"; + } + } + +} + +/** + * This slot is a simple proxy that enriches the DBUS signal with our data, which especially contains the id of the MixDevice. + */ +void MPrisControl::trackChangedIncoming(QVariantMap /*msg*/) +{ + kDebug() << "Track changed"; +} + +MediaController::PlayState Mixer_MPRIS2::mprisPlayStateString2PlayState(const QString& playbackStatus) +{ + MediaController::PlayState playState; + if (playbackStatus == "Playing") + { + playState = MediaController::PlayPlaying; + } + else if (playbackStatus == "Stopped") + { + playState = MediaController::PlayStopped; + } + else if (playbackStatus == "Paused") + { + playState = MediaController::PlayPaused; + } + + return playState; +} + +/** + * This slot is a simple proxy that enriches the DBUS signal with our data, which especially contains the id of the MixDevice. + */ +void MPrisControl::onPropertyChange(QString /*ifc*/,QVariantMap msg ,QStringList /*sl*/) +{ + QMap::iterator v = msg.find("Volume"); + if (v != msg.end() ) + { + double volDouble = v.value().toDouble(); + kDebug(67100) << "volumeChanged incoming: vol=" << volDouble; + emit volumeChanged( this, volDouble); + } + + v = msg.find("PlaybackStatus"); + if (v != msg.end() ) + { + QString playbackStatus = v.value().toString(); + MediaController::PlayState playState = Mixer_MPRIS2::mprisPlayStateString2PlayState(playbackStatus); + kDebug() << "PlaybackStatus is now " << playbackStatus; + + emit playbackStateChanged(this, playState); + } +} + + +Mixer_MPRIS2::~Mixer_MPRIS2() +{ + close(); +} + +MPrisControl::MPrisControl(QString id, QString busDestination) + : propertyIfc(0) + , playerIfc(0) + +{ + volume = 0; + this->id = id; + this->busDestination = busDestination; + retrievedElems = MPrisControl::NONE; +} + +MPrisControl::~MPrisControl() +{ + delete propertyIfc; + delete playerIfc; +} + +QString Mixer_MPRIS2::getDriverName() +{ + return "MPRIS2"; +} + +QString MPRIS2_getDriverName() +{ + return "MPRIS2"; +} + +#include "mixer_mpris2.moc" diff --git a/kmix/backends/mixer_mpris2.h b/kmix/backends/mixer_mpris2.h new file mode 100644 index 00000000..ef61dcb2 --- /dev/null +++ b/kmix/backends/mixer_mpris2.h @@ -0,0 +1,176 @@ +/** + * KMix -- MPRIS2 backend + * + * Copyright (C) 2011 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef Mixer_MPRIS2_H +#define Mixer_MPRIS2_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mixer_backend.h" + +class MPrisControl : public QObject +{ + Q_OBJECT + +public: + MPrisControl(QString id, QString busDestination); + ~MPrisControl(); + + enum DATA_ELEM + { + NONE = 0, NAME = 1, VOLUME = 2, ALL = 3 + }; + + QDBusInterface* propertyIfc; + QDBusInterface* playerIfc; + + +private: + QString id; + QString busDestination; + QString name; + int volume; + + int retrievedElems; + +public: + const QString& getId() const + { + return id; + } + + const QString& getBusDestination() const + { + return busDestination; + } + + const QString& getName() const + { + return name; + } + + void setName(const QString& name) + { + retrievedElems |= MPrisControl::NAME; + this->name = name; + } + + int getVolume() const + { + return volume; + } + + void setVolume(int volume) + { + retrievedElems |= MPrisControl::VOLUME; + this->volume = volume; + } + + bool isComplete() + { + return retrievedElems == MPrisControl::ALL; + } + + + +public slots: + void trackChangedIncoming(QVariantMap msg); + void onPropertyChange(QString,QVariantMap,QStringList); + +signals: + void volumeChanged(MPrisControl* mad, double); + void playbackStateChanged(MPrisControl* mad, MediaController::PlayState); +}; + +class Mixer_MPRIS2 : public Mixer_Backend +{ + Q_OBJECT + +// friend class Mixer_MPRIS2_Thread; + +public: + Mixer_MPRIS2(Mixer *mixer, int device); + virtual ~Mixer_MPRIS2(); + QString getDriverName(); + virtual QString getId() const { return _id; }; + + virtual int open(); + virtual int close(); + virtual int readVolumeFromHW( const QString& id, shared_ptr ); + virtual int writeVolumeToHW( const QString& id, shared_ptr ); + virtual void setEnumIdHW(const QString& id, unsigned int); + virtual unsigned int enumIdHW(const QString& id); + virtual bool moveStream( const QString& id, const QString& destId ); + virtual bool needsPolling() { return false; } + + virtual int mediaPlay(QString id); + virtual int mediaPrev(QString id); + virtual int mediaNext(QString id); + virtual int mediaControl(QString id, QString command); + + static MediaController::PlayState mprisPlayStateString2PlayState(const QString& playbackStatus); + +public slots: + void volumeChanged(MPrisControl *mad, double); + void playbackStateChanged(MPrisControl* mad, MediaController::PlayState); + + void newMediaPlayer(QString name, QString oldOwner, QString newOwner); + void addMprisControlAsync(QString arg1); + void announceControlListAsync(QString streamId); + +private slots: + // asynchronous announce call slots + void announceControlList(); + void announceGUI(); + void announceVolume(); + + // Async QDBusPendingCallWatcher's + void watcherMediaControl(QDBusPendingCallWatcher* watcher); + void watcherPlugControlId(QDBusPendingCallWatcher* watcher); + void watcherInitialVolume(QDBusPendingCallWatcher* watcher); + void watcherInitialPlayState(QDBusPendingCallWatcher* watcher); + +private: + // Helpers for the watchers + MPrisControl* watcherHelperGetMPrisControl(QDBusPendingCallWatcher* watcher); + + +private: +// void asyncAddMprisControl(QString busDestination); +// void messageQueueThreadLoop(); + int addAllRunningPlayersAndInitHotplug(); + void volumeChangedInternal(shared_ptr md, int volumePercentage); + QString busDestinationToControlId(const QString& busDestination); + MixDevice::ChannelType getChannelTypeFromPlayerId(const QString& id); + + QMap controls; + QString _id; +}; + +#endif + diff --git a/kmix/backends/mixer_oss.cpp b/kmix/backends/mixer_oss.cpp new file mode 100644 index 00000000..150078b5 --- /dev/null +++ b/kmix/backends/mixer_oss.cpp @@ -0,0 +1,483 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright (C) 1996-2000 Christian Esken + * esken@kde.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "mixer_oss.h" +#include "core/mixer.h" +#include "core/kmixdevicemanager.h" + +#include +#include +#include +#include +#include +#include + +// Since we're guaranteed an OSS setup here, let's make life easier +#if !defined(__NetBSD__) && !defined(__OpenBSD__) + #include +#else + #include +#endif + + +#include +#include +/* + I am using a fixed MAX_MIXDEVS #define here. + People might argue, that I should rather use the SOUND_MIXER_NRDEVICES + #define used by OSS. But using this #define is not good, because it is + evaluated during compile time. Compiling on one platform and running + on another with another version of OSS with a different value of + SOUND_MIXER_NRDEVICES is very bad. Because of this, usage of + SOUND_MIXER_NRDEVICES should be discouraged. + + The #define below is only there for internal reasons. + In other words: Don't play around with this value + */ +#define MAX_MIXDEVS 32 + +const char* MixerDevNames[32]={ + I18N_NOOP("Volume"), I18N_NOOP("Bass"), I18N_NOOP("Treble"), + I18N_NOOP("Synth"), I18N_NOOP("Pcm"), I18N_NOOP("Speaker"), + I18N_NOOP("Line"), I18N_NOOP("Microphone"), I18N_NOOP("CD"), + I18N_NOOP("Mix"), I18N_NOOP("Pcm2"), I18N_NOOP("RecMon"), + I18N_NOOP("IGain"), I18N_NOOP("OGain"), I18N_NOOP("Line1"), + I18N_NOOP("Line2"), I18N_NOOP("Line3"), I18N_NOOP("Digital1"), + I18N_NOOP("Digital2"), I18N_NOOP("Digital3"), I18N_NOOP("PhoneIn"), + I18N_NOOP("PhoneOut"), I18N_NOOP("Video"), I18N_NOOP("Radio"), + I18N_NOOP("Monitor"), I18N_NOOP("3D-depth"), I18N_NOOP("3D-center"), + I18N_NOOP("unknown"), I18N_NOOP("unknown"), I18N_NOOP("unknown"), + I18N_NOOP("unknown") , I18N_NOOP("unused") }; + +const MixDevice::ChannelType MixerChannelTypes[32] = { + MixDevice::VOLUME, MixDevice::BASS, MixDevice::TREBLE, + MixDevice::MIDI, MixDevice::AUDIO, MixDevice::SPEAKER, + MixDevice::EXTERNAL, MixDevice::MICROPHONE, MixDevice::CD, + MixDevice::VOLUME, MixDevice::AUDIO, MixDevice::RECMONITOR, + MixDevice::VOLUME, MixDevice::RECMONITOR, MixDevice::EXTERNAL, + MixDevice::EXTERNAL, MixDevice::EXTERNAL, MixDevice::DIGITAL, + MixDevice::DIGITAL, MixDevice::DIGITAL, MixDevice::EXTERNAL, + MixDevice::EXTERNAL, MixDevice::VIDEO, MixDevice::EXTERNAL, + MixDevice::EXTERNAL, MixDevice::VOLUME, MixDevice::VOLUME, + MixDevice::UNKNOWN, MixDevice::UNKNOWN, MixDevice::UNKNOWN, + MixDevice::UNKNOWN, MixDevice::UNKNOWN }; + +Mixer_Backend* OSS_getMixer( Mixer* mixer, int device ) +{ + Mixer_Backend *l_mixer; + l_mixer = new Mixer_OSS( mixer, device ); + return l_mixer; +} + +Mixer_OSS::Mixer_OSS(Mixer* mixer, int device) : + Mixer_Backend(mixer, device) +{ + if (device == -1) + { + m_devnum = 0; + } + m_fd = -1; // point to an invalid FD +} + +Mixer_OSS::~Mixer_OSS() +{ + close(); +} + +int Mixer_OSS::open() +{ + QString finalDeviceName; + finalDeviceName = deviceName( m_devnum ); + kDebug() << "OSS open() " << finalDeviceName; + if ((m_fd= ::open( finalDeviceName.toAscii().data(), O_RDWR)) < 0) + { + if ( errno == EACCES ) + return Mixer::ERR_PERM; + else { + finalDeviceName = deviceNameDevfs( m_devnum ); + if ((m_fd= ::open( finalDeviceName.toAscii().data(), O_RDWR)) < 0) + { + if ( errno == EACCES ) + return Mixer::ERR_PERM; + else + return Mixer::ERR_OPEN; + } + } + } + + _udi = KMixDeviceManager::instance()->getUDI_OSS(finalDeviceName); + if ( _udi.isEmpty() ) { + QString msg("No UDI found for '"); + msg += finalDeviceName; + msg += "'. Hotplugging not possible"; + kDebug(67100) << msg; + } + int devmask, recmask, i_recsrc, stereodevs; + // Mixer is open. Now define properties + if (ioctl(m_fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) + return Mixer::ERR_READ; + if (ioctl(m_fd, SOUND_MIXER_READ_RECMASK, &recmask) == -1) + return Mixer::ERR_READ; + if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1) + return Mixer::ERR_READ; + if (ioctl(m_fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs) == -1) + return Mixer::ERR_READ; + + int idx = 0; + while( devmask && idx < MAX_MIXDEVS ) + { + if( devmask & ( 1 << idx ) ) // device active? + { + Volume playbackVol( 100, 1, true, false ); + playbackVol.addVolumeChannel(VolumeChannel(Volume::LEFT)); + if ( stereodevs & ( 1 << idx ) ) + playbackVol.addVolumeChannel(VolumeChannel(Volume::RIGHT)); + + QString id; + id.setNum(idx); + MixDevice* md = new MixDevice( + _mixer, + id, + i18n(MixerDevNames[idx]), + MixerChannelTypes[idx]); + md->addPlaybackVolume(playbackVol); + + // Tutorial: Howto add a simple capture switch + if ( recmask & ( 1 << idx ) ) { + // can be captured => add capture volume, with no capture volume + Volume captureVol( 100, 1, true, true ); + md->addCaptureVolume(captureVol); + } + + m_mixDevices.append( md->addToPool() ); + } + idx++; + } + +#if defined(SOUND_MIXER_INFO) + struct mixer_info l_mix_info; + if (ioctl(m_fd, SOUND_MIXER_INFO, &l_mix_info) != -1) + { + registerCard(l_mix_info.name); + } + else +#endif + { + registerCard("OSS Audio Mixer"); + } + + m_isOpen = true; + return 0; +} + +int Mixer_OSS::close() +{ + _pollingTimer->stop(); + m_isOpen = false; + int l_i_ret = ::close(m_fd); + closeCommon(); + return l_i_ret; +} + + +QString Mixer_OSS::deviceName(int devnum) +{ + switch (devnum) { + case 0: + return QString("/dev/mixer"); + break; + + default: + QString devname("/dev/mixer%1"); + return devname.arg(devnum); + } +} + +QString Mixer_OSS::deviceNameDevfs(int devnum) +{ + switch (devnum) { + case 0: + return QString("/dev/sound/mixer"); + break; + + default: + QString devname("/dev/sound/mixer"); + devname += ('0'+devnum); + return devname; + } +} + +QString Mixer_OSS::errorText(int mixer_error) +{ + QString l_s_errmsg; + switch (mixer_error) + { + case Mixer::ERR_PERM: + l_s_errmsg = i18n("kmix: You do not have permission to access the mixer device.\n" \ + "Login as root and do a 'chmod a+rw /dev/mixer*' to allow the access."); + break; + case Mixer::ERR_OPEN: + l_s_errmsg = i18n("kmix: Mixer cannot be found.\n" \ + "Please check that the soundcard is installed and the\n" \ + "soundcard driver is loaded.\n" \ + "On Linux you might need to use 'insmod' to load the driver.\n" \ + "Use 'soundon' when using commercial OSS."); + break; + default: + l_s_errmsg = Mixer_Backend::errorText(mixer_error); + break; + } + return l_s_errmsg; +} + + +void print_recsrc(int recsrc) +{ + int i; + + QString msg; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + { + if ((1 << i) & recsrc) + msg += '+'; + else + msg += '.'; + } + kDebug() << msg; +} + +int Mixer_OSS::setRecsrcToOSS( const QString& id, bool on ) +{ + int i_recsrc; //, oldrecsrc; + int devnum = id2num(id); + if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1) + { + errormsg(Mixer::ERR_READ); + return Mixer::ERR_READ; + } + +// oldrecsrc = i_recsrc = on ? +// (i_recsrc | (1 << devnum )) : +// (i_recsrc & ~(1 << devnum )); + + // Change status of record source(s) + if (ioctl(m_fd, SOUND_MIXER_WRITE_RECSRC, &i_recsrc) == -1) + { + errormsg (Mixer::ERR_WRITE); + // don't return here. It is much better to re-read the capture switch states. + } + + /* The following if {} patch was submitted by Tim McCormick . */ + /* Comment (cesken): This patch fixes an issue with mutual exclusive recording sources. + Actually the kernel soundcard driver *could* "do the right thing" by examining the change + (old-recsrc XOR new-recsrc), and knowing which sources are mutual exclusive. + The OSS v3 API docs indicate that the behaviour is undefined for this case, and it is not + clearly documented how and whether SOUND_MIXER_CAP_EXCL_INPUT is evaluated in the OSS driver. + Evaluating that in the application (KMix) could help, but the patch will work independent + on whether SOUND_MIXER_CAP_EXCL_INPUT is set or not. + + In any case this patch is a superb workaround for a shortcoming of the OSS v3 API. + */ + // If the record source is supposed to be on, but wasn't set, explicitly + // set the record source. Not all cards support multiple record sources. + // As a result, we also need to do the read & write again. + if (((i_recsrc & ( 1< Try to enable it *exclusively* + +// oldrecsrc = i_recsrc = 1 << devnum; + + if (ioctl(m_fd, SOUND_MIXER_WRITE_RECSRC, &i_recsrc) == -1) + errormsg (Mixer::ERR_WRITE); + if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1) + errormsg(Mixer::ERR_READ); + } + + // Re-read status of record source(s). Just in case the hardware/driver has + // some limitaton (like exclusive switches) + int recsrcMask; + if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &recsrcMask) == -1) + errormsg(Mixer::ERR_READ); + else + { + for(int i=0; i< m_mixDevices.count() ; i++ ) + { + shared_ptr md = m_mixDevices[i]; + bool isRecsrc = ( (recsrcMask & ( 1<setRecSource(isRecsrc); + } // for all controls + } // reading newrecsrcmask is OK + + return Mixer::OK; + +} + + +int Mixer_OSS::id2num(const QString& id) +{ + return id.toInt(); +} + +/** + * Prints out a translated error text for the given error number on stderr + */ +void Mixer_OSS::errormsg(int mixer_error) +{ + QString l_s_errText; + l_s_errText = errorText(mixer_error); + kError() << l_s_errText << "\n"; +} + + +int Mixer_OSS::readVolumeFromHW( const QString& id, shared_ptr md ) +{ + int ret = 0; + + // --- VOLUME --- + Volume& vol = md->playbackVolume(); + int devnum = id2num(id); + + + bool controlChanged = false; + + if ( vol.hasVolume() ) { + int volume; + if (ioctl(m_fd, MIXER_READ( devnum ), &volume) == -1) + { + /* Oops, can't read mixer */ + errormsg(Mixer::ERR_READ); + ret = Mixer::ERR_READ; + } + else + { + + int volLeft = (volume & 0x7f); + int volRight = ((volume>>8) & 0x7f); +// +// if ( md->id() == "0" ) +// kDebug() << md->id() << ": " << "volLeft=" << volLeft << ", volRight" << volRight; + + bool isMuted = volLeft==0 && ( vol.count() < 2 || volRight==0 ); // muted is "left and right muted" or "left muted when mono" + md->setMuted( isMuted ); + if ( ! isMuted ) { + // Muted is represented in OSS by value 0. We don't want to write the value 0 as a volume, + // but instead we only mark it muted (see setMuted() above). + + foreach (VolumeChannel vc, vol.getVolumes() ) + { + long volOld = 0; + long volNew = 0; + switch(vc.chid) { + case Volume::LEFT: + volOld = vol.getVolume(Volume::LEFT); + volNew = volLeft; + vol.setVolume( Volume::LEFT, volNew ); + break; + case Volume::RIGHT: + volOld = vol.getVolume(Volume::RIGHT); + volNew = volRight; + vol.setVolume( Volume::RIGHT, volNew ); + break; + default: + // not supported by OSSv3 + break; + } + + if ( volOld != volNew ) { + controlChanged = true; + //if ( md->id() == "0" ) kDebug() << "changed"; + } + } // foreach + } // muted + } + } + + + // --- RECORD SWITCH --- + //Volume& captureVol = md->captureVolume(); + int recsrcMask; + if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &recsrcMask) == -1) + ret = Mixer::ERR_READ; + else + { + bool isRecsrcOld = md->isRecSource(); + // test if device bit is set in record bit mask + bool isRecsrc = ( (recsrcMask & ( 1<setRecSource(isRecsrc); + if ( isRecsrcOld != isRecsrc ) + controlChanged = true; + + } + + if ( ret== 0) + { + if ( controlChanged ) + { + //kDebug() << "FINE! " << ret; + return Mixer::OK; + } + else + { + return Mixer::OK_UNCHANGED; + } + } + else + { + //kDebug() << "SHIT! " << ret; + return ret; + } +} + + + +int Mixer_OSS::writeVolumeToHW( const QString& id, shared_ptr md) +{ + int volume; + int devnum = id2num(id); + + Volume& vol = md->playbackVolume(); + if( md->isMuted() ) + volume = 0; + else + { + if ( vol.getVolumes().count() > 1 ) + volume = (vol.getVolume(Volume::LEFT) + (vol.getVolume(Volume::RIGHT)<<8)); + else + volume = vol.getVolume(Volume::LEFT); + } + + if (ioctl(m_fd, MIXER_WRITE( devnum ), &volume) == -1) + return Mixer::ERR_WRITE; + + setRecsrcToOSS( id, md->isRecSource() ); + + + return 0; +} + +QString OSS_getDriverName() { + return "OSS"; +} + +QString Mixer_OSS::getDriverName() { + return "OSS"; +} + diff --git a/kmix/backends/mixer_oss.h b/kmix/backends/mixer_oss.h new file mode 100644 index 00000000..c69e8abc --- /dev/null +++ b/kmix/backends/mixer_oss.h @@ -0,0 +1,58 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MIXER_OSS_H +#define MIXER_OSS_H + +#include + +#include "mixer_backend.h" + +class Mixer_OSS : public Mixer_Backend +{ +public: + Mixer_OSS(Mixer *mixer, int device); + virtual ~Mixer_OSS(); + + virtual QString errorText(int mixer_error); + virtual int readVolumeFromHW( const QString& id, shared_ptr ); + virtual int writeVolumeToHW ( const QString& id, shared_ptr ); + + virtual QString getDriverName(); + +protected: + + virtual int open(); + virtual int close(); + + virtual QString deviceName( int ); + virtual QString deviceNameDevfs( int ); + +private: + int m_fd; + QString m_deviceName; + + int setRecsrcToOSS( const QString& id, bool on ); + void errormsg(int mixer_error); + int id2num(const QString& id); +}; + +#endif diff --git a/kmix/backends/mixer_oss4.cpp b/kmix/backends/mixer_oss4.cpp new file mode 100644 index 00000000..a4adfa93 --- /dev/null +++ b/kmix/backends/mixer_oss4.cpp @@ -0,0 +1,799 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright (C) 1996-2000 Christian Esken + * esken@kde.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +//OSS4 mixer backend for KMix by Yoper Team released under GPL v2 or later + +/* We're getting soundcard.h via mixer_oss4.h */ +#include "mixer_oss4.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +Mixer_Backend* OSS4_getMixer(Mixer *mixer, int device) +{ + Mixer_Backend *l_mixer; + l_mixer = new Mixer_OSS4(mixer, device); + return l_mixer; +} + +Mixer_OSS4::Mixer_OSS4(Mixer *mixer, int device) : Mixer_Backend(mixer, device) +{ + if ( device == -1 ) m_devnum = 0; + m_numExtensions = 0; + m_fd = -1; + m_ossVersion = 0; + m_modifyCounter = -1; +} + +bool Mixer_OSS4::CheckCapture(oss_mixext *ext) +{ + QString name = ext->extname; + if ( ext->flags & MIXF_RECVOL || name.split('.').contains("in") ) + { + return true; + } + return false; +} + +Mixer_OSS4::~Mixer_OSS4() +{ + close(); +} + +//classifies mixexts according to their name, last classification wins +MixDevice::ChannelType Mixer_OSS4::classifyAndRename(QString &name, int flags) +{ + MixDevice::ChannelType cType = MixDevice::UNKNOWN; + QStringList classes = name.split (QRegExp( QLatin1String( "[-,.]" ) )); + + + if ( flags & MIXF_PCMVOL || + flags & MIXF_MONVOL || + flags & MIXF_MAINVOL ) + { + cType = MixDevice::VOLUME; + } + + for ( QStringList::Iterator it = classes.begin(); it != classes.end(); ++it ) + { + if ( *it == "line" ) + { + *it = "Line"; + cType = MixDevice::EXTERNAL; + + } else + if ( *it == "mic" ) + { + *it = "Microphone"; + cType = MixDevice::MICROPHONE; + } else + if ( *it == "vol" ) + { + *it = "Volume"; + cType = MixDevice::VOLUME; + } else + if ( *it == "surr" ) + { + *it = "Surround"; + cType = MixDevice::SURROUND; + } else + if ( *it == "bass" ) + { + *it = "Bass"; + cType = MixDevice::BASS; + } else + if ( *it == "treble" ) + { + *it = "Treble"; + cType = MixDevice::TREBLE; + } else + if ( (*it).startsWith ( "pcm" ) ) + { + (*it).replace ( "pcm","PCM" ); + cType = MixDevice::AUDIO; + } else + if ( *it == "src" ) + { + *it = "Source"; + } else + if ( *it == "rec" ) + { + *it = "Recording"; + } else + if ( *it == "cd" ) + { + *it = (*it).toUpper(); + cType = MixDevice::CD; + } + if ( (*it).startsWith("vmix") ) + { + (*it).replace("vmix","Virtual Mixer"); + cType = MixDevice::VOLUME; + } else + if ( (*it).endsWith("vol") ) + { + QCharRef ref = (*it)[0]; + ref = ref.toUpper(); + cType = MixDevice::VOLUME; + } else + if ( (*it).contains("speaker") ) + { + QCharRef ref = (*it)[0]; + ref = ref.toUpper(); + cType = MixDevice::SPEAKER; + } else + if ( (*it).contains("center") && (*it).contains("lfe")) + { + QCharRef ref = (*it)[0]; + ref = ref.toUpper(); + cType = MixDevice::SURROUND_LFE; + } else + if ( (*it).contains("rear") ) + { + QCharRef ref = (*it)[0]; + ref = ref.toUpper(); + cType = MixDevice::SURROUND_CENTERBACK; + } else + if ( (*it).contains("front") ) + { + QCharRef ref = (*it)[0]; + ref = ref.toUpper(); + cType = MixDevice::SURROUND_CENTERFRONT; + } else + if ( (*it).contains("headphone") ) + { + QCharRef ref = (*it)[0]; + ref = ref.toUpper(); + cType = MixDevice::HEADPHONE; + } + else + { + QCharRef ref = (*it)[0]; + ref = ref.toUpper(); + } + } + name = classes.join( QLatin1String( " " )); + return cType; +} + +int Mixer_OSS4::open() +{ + if ( (m_fd= ::open("/dev/mixer", O_RDWR)) < 0 ) + { + if ( errno == EACCES ) + return Mixer::ERR_PERM; + else + return Mixer::ERR_OPEN; + } + + /* + * Intentionally not wrapped - some systems may not support this ioctl, and therefore + * aren't OSSv4. No need to throw needless error messages at the user in that case. + */ + if( ::ioctl (m_fd, OSS_GETVERSION, &m_ossVersion) < 0) + { + return Mixer::ERR_OPEN; + } + if (m_ossVersion < 0x040000) + { + return Mixer::ERR_OPEN; + } + + wrapIoctl( ioctl (m_fd, SNDCTL_MIX_NRMIX, &m_numMixers) ); + + if ( m_mixDevices.isEmpty() ) + { + if ( m_devnum >= 0 && m_devnum < m_numMixers ) + { + m_numExtensions = m_devnum; + bool masterChosen = false; + bool masterHeuristicAvailable = false; + bool saveAsMasterHeuristc = false; + shared_ptr masterHeuristic; + + oss_mixext ext; + ext.dev = m_devnum; + oss_mixerinfo mi; + + mi.dev = m_devnum; + if ( wrapIoctl( ioctl (m_fd, SNDCTL_MIXERINFO, &mi) ) < 0 ) + { + return Mixer::ERR_READ; + } + + /* Mixer is disabled - this can happen, e.g. disconnected USB device */ + if (!mi.enabled) + { + return Mixer::ERR_READ; + } + + ::close(m_fd); + if ( (m_fd= ::open(mi.devnode, O_RDWR)) < 0 ) + { + return Mixer::ERR_OPEN; + } + + if ( wrapIoctl( ioctl (m_fd, SNDCTL_MIX_NREXT, &m_numExtensions) ) < 0 ) + { + //TO DO: more specific error handling here + return Mixer::ERR_READ; + } + + if( m_numExtensions == 0 ) + { + return Mixer::ERR_OPEN; + } + + ext.ctrl = 0; + + //read MIXT_DEVROOT, return Mixer::NODEV on error + if ( wrapIoctl ( ioctl( m_fd, SNDCTL_MIX_EXTINFO, &ext) ) < 0 ) + { + return Mixer::ERR_OPEN; + } + + oss_mixext_root *root = (oss_mixext_root *) ext.data; + registerCard(root->name); + + for ( int i = 1; i < m_numExtensions; i++ ) + { + bool isCapture = false; + + ext.dev = m_devnum; + ext.ctrl = i; + + //wrapIoctl handles reinitialization, cancel loading on EIDRM + if ( wrapIoctl( ioctl( m_fd, SNDCTL_MIX_EXTINFO, &ext) ) == EIDRM ) + { + return 0; + } + + QString name = ext.extname; + + //skip unreadable controls + if ( ext.flags & MIXF_READABLE +#ifndef MIXT_MUTE + && (name.contains("mute")) +#endif +#ifdef MIXT_MUTE + && (name.contains("mute") || ext.flags == MIXT_MUTE) +#endif + ) + { + continue; + } + //skip all vmix controls with the exception of outvol + else if ( name.contains("vmix") && ! name.contains("enable") ) + { + //some heuristic in case we got no exported main volume control + if( name.contains("outvol") && ext.type != MIXT_ONOFF ) + { + saveAsMasterHeuristc = true; + } + else + { + continue; + } + } + + + //fix for old legacy names, according to Hannu's suggestions + if ( name.contains('_') ) + { + name = name.section('_',1,1).toLower(); + } + + isCapture = CheckCapture (&ext); + Volume::ChannelMask chMask = Volume::MNONE; + + MixDevice::ChannelType cType = classifyAndRename(name, ext.flags); + + if ( (ext.type == MIXT_STEREOSLIDER16 || + ext.type == MIXT_STEREOSLIDER || + ext.type == MIXT_MONOSLIDER16 || + ext.type == MIXT_MONOSLIDER || + ext.type == MIXT_SLIDER + ) ) + { + if ( ext.type == MIXT_STEREOSLIDER16 || + ext.type == MIXT_STEREOSLIDER + ) + { + chMask = Volume::ChannelMask(Volume::MLEFT|Volume::MRIGHT); + } + else + { + chMask = Volume::MLEFT; + } + + Volume vol (ext.maxvalue, ext.minvalue, false, isCapture); + vol.addVolumeChannels(chMask); + + MixDevice* md_ptr = new MixDevice(_mixer, + QString::number(i), + name, + cType); + + shared_ptr md = md_ptr->addToPool(); + m_mixDevices.append(md); + + if(isCapture) + { + md->addCaptureVolume(vol); + } + else + { + md->addPlaybackVolume(vol); + } + + if( saveAsMasterHeuristc && ! masterHeuristicAvailable ) + { + masterHeuristic = md; + masterHeuristicAvailable = true; + } + + if ( !masterChosen && ext.flags & MIXF_MAINVOL ) + { + m_recommendedMaster = md; + masterChosen = true; + } + } + else if ( ext.type == MIXT_HEXVALUE ) + { + chMask = Volume::ChannelMask(Volume::MLEFT); + Volume vol (ext.maxvalue, ext.minvalue, false, isCapture); + vol.addVolumeChannels(chMask); + + MixDevice* md_ptr = new MixDevice(_mixer, + QString::number(i), + name, + cType); + + shared_ptr md = md_ptr->addToPool(); + m_mixDevices.append(md); + + if(isCapture) + { + md->addCaptureVolume(vol); + } + else + { + md->addPlaybackVolume(vol); + } + + if ( !masterChosen && ext.flags & MIXF_MAINVOL ) + { + m_recommendedMaster = md; + masterChosen = true; + } + } + else if ( ext.type == MIXT_ONOFF +#ifdef MIXT_MUTE + || ext.type == MIXT_MUTE +#endif + ) + { + Volume vol(1, 0, true, isCapture); + + if (isCapture) + vol.setSwitchType (Volume::CaptureSwitch); + else if (ext.type == MIXT_ONOFF) + { + vol.setSwitchType (Volume::SpecialSwitch); + } + + MixDevice* md_ptr = new MixDevice(_mixer, + QString::number(i), + name, + cType); + + shared_ptr md = md_ptr->addToPool(); + m_mixDevices.append(md); + + if(isCapture) + { + md->addCaptureVolume(vol); + } + else + { + md->addPlaybackVolume(vol); + } + } + else if ( ext.type == MIXT_ENUM ) + { + oss_mixer_enuminfo ei; + ei.dev = m_devnum; + ei.ctrl = i; + + if ( wrapIoctl( ioctl (m_fd, SNDCTL_MIX_ENUMINFO, &ei) ) != -1 ) + { + Volume vol(ext.maxvalue, ext.minvalue, + false, isCapture); + vol.addVolumeChannel(VolumeChannel(Volume::LEFT)); + + MixDevice* md_ptr = new MixDevice (_mixer, + QString::number(i), + name, + cType); + + QList enumValuesRef; + QString thisElement; + + for ( int j = 0; j < ei.nvalues; j++ ) + { + thisElement = &ei.strings[ ei.strindex[j] ]; + + if ( thisElement.isEmpty() ) + { + thisElement = QString::number(j); + } + enumValuesRef.append( new QString(thisElement) ); + } + md_ptr->addEnums(enumValuesRef); + + shared_ptr md = md_ptr->addToPool(); + m_mixDevices.append(md); + } + } + + if ( ! masterChosen && masterHeuristicAvailable ) + { + m_recommendedMaster = masterHeuristic; + } + } + } + else + { + return -1; + } + } + m_isOpen = true; + return 0; +} + +int Mixer_OSS4::close() +{ + m_isOpen = false; + int l_i_ret = ::close(m_fd); + m_recommendedMaster.reset(); + closeCommon(); + return l_i_ret; +} + +QString Mixer_OSS4::errorText(int mixer_error) +{ + QString l_s_errmsg; + + switch( mixer_error ) + { + case Mixer::ERR_PERM: + l_s_errmsg = i18n("kmix: You do not have permission to access the mixer device.\n" \ + "Login as root and do a 'chmod a+rw /dev/mixer*' to allow the access."); + break; + case Mixer::ERR_OPEN: + l_s_errmsg = i18n("kmix: Mixer cannot be found.\n" \ + "Please check that the soundcard is installed and the\n" \ + "soundcard driver is loaded.\n" \ + "On Linux you might need to use 'insmod' to load the driver.\n" \ + "Use 'soundon' when using OSS4 from 4front."); + break; + default: + l_s_errmsg = Mixer_Backend::errorText(mixer_error); + } + return l_s_errmsg; +} + +int Mixer_OSS4::id2num(const QString& id) +{ + return id.toInt(); +} + +bool Mixer_OSS4::prepareUpdateFromHW() +{ + oss_mixerinfo minfo; + + minfo.dev = -1; + if ( wrapIoctl( ioctl(m_fd, SNDCTL_MIXERINFO, &minfo) ) < 0 ) + { + kDebug(67100) << "Can't get mixerinfo from card!\n" << endl; + return false; + } + + if (!minfo.enabled) + { + // Mixer is disabled. Probably disconnected USB device or card is unavailable; + kDebug(67100) << "Mixer for card is disabled!\n" << endl; + close(); + return false; + } + if (minfo.modify_counter == m_modifyCounter) return false; + else m_modifyCounter = minfo.modify_counter; + return true; +} + +int Mixer_OSS4::readVolumeFromHW(const QString& id, shared_ptr md) +{ + oss_mixext extinfo; + oss_mixer_value mv; + + extinfo.dev = m_devnum; + extinfo.ctrl = id2num(id); + + if ( wrapIoctl( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 ) + { + //TO DO: more specific error handling + return Mixer::ERR_READ; + } + + Volume &vol = (CheckCapture (&extinfo)) ? md->captureVolume() : md->playbackVolume(); + mv.dev = extinfo.dev; + mv.ctrl = extinfo.ctrl; + mv.timestamp = extinfo.timestamp; + + if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_READ, &mv) ) < 0 ) + { + /* Oops, can't read mixer */ + return Mixer::ERR_READ; + } + else + { + if ( md->isMuted() && extinfo.type != MIXT_ONOFF ) + { + return 0; + } + + switch ( extinfo.type ) + { +#ifdef MIXT_MUTE + case MIXT_MUTE: +#endif + case MIXT_ONOFF: + md->setMuted(mv.value != extinfo.minvalue); + break; + + case MIXT_MONOSLIDER: + vol.setVolume(Volume::LEFT, mv.value & 0xff); + break; + + case MIXT_STEREOSLIDER: + vol.setVolume(Volume::LEFT, mv.value & 0xff); + vol.setVolume(Volume::RIGHT, ( mv.value >> 8 ) & 0xff); + break; + + case MIXT_SLIDER: + vol.setVolume(Volume::LEFT, mv.value); + break; + + case MIXT_MONOSLIDER16: + vol.setVolume(Volume::LEFT, mv.value & 0xffff); + break; + + case MIXT_STEREOSLIDER16: + vol.setVolume(Volume::LEFT, mv.value & 0xffff); + vol.setVolume(Volume::RIGHT, ( mv.value >> 16 ) & 0xffff); + break; + } + } + return 0; +} + +int Mixer_OSS4::writeVolumeToHW(const QString& id, shared_ptr md) +{ + int volume = 0; + + oss_mixext extinfo; + oss_mixer_value mv; + + extinfo.dev = m_devnum; + extinfo.ctrl = id2num(id); + + if ( wrapIoctl( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 ) + { + //TO DO: more specific error handling + kDebug ( 67100 ) << "failed to read info for control " << id2num(id) << endl; + return Mixer::ERR_READ; + } + + Volume &vol = (CheckCapture (&extinfo)) ? md->captureVolume() : md->playbackVolume(); + + switch ( extinfo.type ) + { +#ifdef MIXT_MUTE + case MIXT_MUTE: +#endif + case MIXT_ONOFF: + volume = (md->isMuted()) ? (extinfo.maxvalue) : (extinfo.minvalue); + break; + + case MIXT_MONOSLIDER: + volume = vol.getVolume(Volume::LEFT); + break; + + case MIXT_STEREOSLIDER: + volume = vol.getVolume(Volume::LEFT) | ( vol.getVolume(Volume::RIGHT) << 8 ); + break; + + case MIXT_SLIDER: + volume = vol.getVolume(Volume::LEFT); + break; + + case MIXT_MONOSLIDER16: + volume = vol.getVolume(Volume::LEFT); + break; + + case MIXT_STEREOSLIDER16: + volume = vol.getVolume(Volume::LEFT) | ( vol.getVolume(Volume::RIGHT) << 16 ); + break; + default: + return -1; + } + + mv.dev = extinfo.dev; + mv.ctrl = extinfo.ctrl; + mv.timestamp = extinfo.timestamp; + mv.value = volume - extinfo.minvalue; + + if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_WRITE, &mv) ) < 0 ) + { + kDebug ( 67100 ) << "error writing to control" << extinfo.extname << endl; + return Mixer::ERR_WRITE; + } + return 0; +} + +void Mixer_OSS4::setEnumIdHW(const QString& id, unsigned int idx) +{ + oss_mixext extinfo; + oss_mixer_value mv; + + extinfo.dev = m_devnum; + extinfo.ctrl = id2num(id); + + if ( wrapIoctl ( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 ) + { + //TO DO: more specific error handling + kDebug ( 67100 ) << "failed to read info for control " << id2num(id) << endl; + return; + } + + if ( extinfo.type != MIXT_ENUM ) + { + return; + } + + + //according to oss docs maxVal < minVal could be true - strange... + unsigned int maxVal = (unsigned int) extinfo.maxvalue; + unsigned int minVal = (unsigned int) extinfo.minvalue; + + if ( maxVal < minVal ) + { + int temp; + temp = maxVal; + maxVal = minVal; + minVal = temp; + } + + if ( idx > maxVal || idx < minVal ) + idx = minVal; + + mv.dev = extinfo.dev; + mv.ctrl = extinfo.ctrl; + mv.timestamp = extinfo.timestamp; + mv.value = idx; + + if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_WRITE, &mv) ) < 0 ) + { + /* Oops, can't write to mixer */ + kDebug ( 67100 ) << "error writing to control" << extinfo.extname << endl; + } +} + +unsigned int Mixer_OSS4::enumIdHW(const QString& id) +{ + oss_mixext extinfo; + oss_mixer_value mv; + + extinfo.dev = m_devnum; + extinfo.ctrl = id2num(id); + + if ( wrapIoctl ( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 ) + { + //TO DO: more specific error handling + //TO DO: check whether those return values are actually possible + return Mixer::ERR_READ; + } + + if ( extinfo.type != MIXT_ENUM ) + { + return Mixer::ERR_READ; + } + + mv.dev = extinfo.dev; + mv.ctrl = extinfo.ctrl; + mv.timestamp = extinfo.timestamp; + + if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_READ, &mv) ) < 0 ) + { + /* Oops, can't read mixer */ + return Mixer::ERR_READ; + } + return mv.value; +} + +int Mixer_OSS4::wrapIoctl(int ioctlRet) +{ + switch( ioctlRet ) + { + case EIO: + { + kDebug ( 67100 ) << "A hardware level error occurred" << endl; + break; + } + case EINVAL: + { + kDebug ( 67100 ) << "Operation caused an EINVAL. You may have found a bug in kmix OSS4 module or in OSS4 itself" << endl; + break; + } + case ENXIO: + { + kDebug ( 67100 ) << "Operation index out of bounds or requested device does not exist - you likely found a bug in the kmix OSS4 module" << endl; + break; + } + case EPERM: + case EACCES: + { + kDebug ( 67100 ) << errorText ( Mixer::ERR_PERM ) << endl; + break; + } + case ENODEV: + { + kDebug ( 67100 ) << "kmix received an ENODEV error - are the OSS4 drivers loaded ?" << endl; + break; + } + case EPIPE: + case EIDRM: + { + reinitialize(); + } + + } + return ioctlRet; +} + +QString Mixer_OSS4::getDriverName() +{ + return "OSS4"; +} + +QString OSS4_getDriverName() +{ + return "OSS4"; +} + diff --git a/kmix/backends/mixer_oss4.h b/kmix/backends/mixer_oss4.h new file mode 100644 index 00000000..2df1a36b --- /dev/null +++ b/kmix/backends/mixer_oss4.h @@ -0,0 +1,44 @@ +//-*-C++-*- + +#ifndef MIXER_OSS4_H +#define MIXER_OSS4_H + +#include "mixer_backend.h" +#include + +class Mixer_OSS4 : public Mixer_Backend +{ +public: + Mixer_OSS4(Mixer* mixer, int device); + virtual ~Mixer_OSS4(); + + virtual QString errorText(int mixer_error); + virtual QString getDriverName(); + virtual bool CheckCapture(oss_mixext *ext); + virtual bool prepareUpdateFromHW(); + virtual int readVolumeFromHW(const QString& id, shared_ptr md); + virtual int writeVolumeToHW(const QString& id, shared_ptr md ); + virtual void setEnumIdHW(const QString& id, unsigned int idx); + virtual unsigned int enumIdHW(const QString& id); + +protected: + + MixDevice::ChannelType classifyAndRename(QString &name, int flags); + + int wrapIoctl(int ioctlRet); + + void reinitialize() { open(); close(); }; + virtual int open(); + virtual int close(); + + int m_ossVersion; + int m_fd; + int m_numMixers; + int m_numExtensions; + int m_modifyCounter; + QString m_deviceName; + +private: + int id2num(const QString& id); +}; +#endif diff --git a/kmix/backends/mixer_pulse.cpp b/kmix/backends/mixer_pulse.cpp new file mode 100644 index 00000000..88d4591b --- /dev/null +++ b/kmix/backends/mixer_pulse.cpp @@ -0,0 +1,1457 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2008 Helio Chissini de Castro + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "mixer_pulse.h" + +#include +#include +#include + +#include + +#include "core/mixer.h" +#include "core/ControlManager.h" +#include "core/GlobalConfig.h" + +#include +#include +#if defined(HAVE_CANBERRA) +# include +#endif + +// PA_VOLUME_UI_MAX landed in pulseaudio-0.9.23, so this can be removed when/if +// minimum requirement is ever bumped up (from 0.9.12 currently) +#ifndef PA_VOLUME_UI_MAX +#define PA_VOLUME_UI_MAX (pa_sw_volume_from_dB(+11.0)) +#endif + +#define HAVE_SOURCE_OUTPUT_VOLUMES PA_CHECK_VERSION(1,0,0) + +#define KMIXPA_PLAYBACK 0 +#define KMIXPA_CAPTURE 1 +#define KMIXPA_APP_PLAYBACK 2 +#define KMIXPA_APP_CAPTURE 3 +#define KMIXPA_WIDGET_MAX KMIXPA_APP_CAPTURE + +#define KMIXPA_EVENT_KEY "sink-input-by-media-role:event" + +static unsigned int refcount = 0; +static pa_glib_mainloop *s_mainloop = NULL; +static pa_context *s_context = NULL; +static enum { UNKNOWN, ACTIVE, INACTIVE } s_pulseActive = UNKNOWN; +static int s_outstandingRequests = 0; + +#if defined(HAVE_CANBERRA) +static ca_context *s_ccontext = NULL; +#endif + +QMap s_mixers; + +static devmap outputDevices; +static devmap captureDevices; +static QMap clients; +static devmap outputStreams; +static devmap captureStreams; +static devmap outputRoles; + +typedef struct { + pa_channel_map channel_map; + pa_cvolume volume; + bool mute; + QString device; +} restoreRule; +static QMap s_RestoreRules; + +static void dec_outstanding(pa_context *c) { + if (s_outstandingRequests <= 0) + return; + + if (--s_outstandingRequests == 0) + { + s_pulseActive = ACTIVE; + + // If this is our probe phase, exit our context immediately + if (s_context != c) { + pa_context_disconnect(c); + } else + kDebug(67100) << "Reconnected to PulseAudio"; + } +} + +static void translateMasksAndMaps(devinfo& dev) +{ + dev.chanMask = Volume::MNONE; + dev.chanIDs.clear(); + + if (dev.channel_map.channels != dev.volume.channels) { + kError() << "Hiddeous Channel mixup map says " << dev.channel_map.channels << ", volume says: " << dev.volume.channels; + return; + } + if (1 == dev.channel_map.channels && PA_CHANNEL_POSITION_MONO == dev.channel_map.map[0]) { + // We just use the left channel to represent this. + dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MLEFT); + dev.chanIDs[0] = Volume::LEFT; + } else { + for (uint8_t i = 0; i < dev.channel_map.channels; ++i) { + switch (dev.channel_map.map[i]) { + case PA_CHANNEL_POSITION_MONO: + kWarning(67100) << "Channel Map contains a MONO element but has >1 channel - we can't handle this."; + return; + + case PA_CHANNEL_POSITION_FRONT_LEFT: + dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MLEFT); + dev.chanIDs[i] = Volume::LEFT; + break; + case PA_CHANNEL_POSITION_FRONT_RIGHT: + dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MRIGHT); + dev.chanIDs[i] = Volume::RIGHT; + break; + case PA_CHANNEL_POSITION_FRONT_CENTER: + dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MCENTER); + dev.chanIDs[i] = Volume::CENTER; + break; + case PA_CHANNEL_POSITION_REAR_CENTER: + dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARCENTER); + dev.chanIDs[i] = Volume::REARCENTER; + break; + case PA_CHANNEL_POSITION_REAR_LEFT: + dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MSURROUNDLEFT); + dev.chanIDs[i] = Volume::SURROUNDLEFT; + break; + case PA_CHANNEL_POSITION_REAR_RIGHT: + dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MSURROUNDRIGHT); + dev.chanIDs[i] = Volume::SURROUNDRIGHT; + break; + case PA_CHANNEL_POSITION_LFE: + dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MWOOFER); + dev.chanIDs[i] = Volume::WOOFER; + break; + case PA_CHANNEL_POSITION_SIDE_LEFT: + dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARSIDELEFT); + dev.chanIDs[i] = Volume::REARSIDELEFT; + break; + case PA_CHANNEL_POSITION_SIDE_RIGHT: + dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARSIDERIGHT); + dev.chanIDs[i] = Volume::REARSIDERIGHT; + break; + default: + kWarning(67100) << "Channel Map contains a pa_channel_position we cannot handle " << dev.channel_map.map[i]; + break; + } + } + } +} + +static QString getIconNameFromProplist(pa_proplist *l) { + const char *t; + + if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ICON_NAME))) + return QString::fromUtf8(t); + + if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME))) + return QString::fromUtf8(t); + + if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME))) + return QString::fromUtf8(t); + + if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE))) { + + if (strcmp(t, "video") == 0 || strcmp(t, "phone") == 0) + return QString::fromUtf8(t); + + if (strcmp(t, "music") == 0) + return "audio"; + + if (strcmp(t, "game") == 0) + return "applications-games"; + + if (strcmp(t, "event") == 0) + return "dialog-information"; + } + + return ""; +} + +static void sink_cb(pa_context *c, const pa_sink_info *i, int eol, void *) { + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + kWarning(67100) << "Sink callback failure"; + return; + } + + if (eol > 0) { + dec_outstanding(c); + if (s_mixers.contains(KMIXPA_PLAYBACK)) + s_mixers[KMIXPA_PLAYBACK]->triggerUpdate(); + return; + } + + devinfo s; + s.index = s.device_index = i->index; + s.name = QString::fromUtf8(i->name).replace(' ', '_'); + s.description = QString::fromUtf8(i->description); + s.icon_name = QString::fromUtf8(pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME)); + s.volume = i->volume; + s.channel_map = i->channel_map; + s.mute = !!i->mute; + s.stream_restore_rule = ""; + + s.priority = 0; + if (i->active_port != NULL) + s.priority = i->active_port->priority; + + translateMasksAndMaps(s); + + bool is_new = !outputDevices.contains(s.index); + outputDevices[s.index] = s; +// kDebug(67100) << "Got some info about sink: " << s.description; + + if (s_mixers.contains(KMIXPA_PLAYBACK)) { + if (is_new) + s_mixers[KMIXPA_PLAYBACK]->addWidget(s.index); + else { + int mid = s_mixers[KMIXPA_PLAYBACK]->id2num(s.name); + if (mid >= 0) { + MixSet *ms = s_mixers[KMIXPA_PLAYBACK]->getMixSet(); + (*ms)[mid]->setReadableName(s.description); + } + } + } +} + +static void source_cb(pa_context *c, const pa_source_info *i, int eol, void *) { + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + kWarning(67100) << "Source callback failure"; + return; + } + + if (eol > 0) { + dec_outstanding(c); + if (s_mixers.contains(KMIXPA_CAPTURE)) + s_mixers[KMIXPA_CAPTURE]->triggerUpdate(); + return; + } + + // Do something.... + if (PA_INVALID_INDEX != i->monitor_of_sink) + { + kDebug(67100) << "Ignoring Monitor Source: " << i->description; + return; + } + + devinfo s; + s.index = s.device_index = i->index; + s.name = QString::fromUtf8(i->name).replace(' ', '_'); + s.description = QString::fromUtf8(i->description); + s.icon_name = QString::fromUtf8(pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME)); + s.volume = i->volume; + s.channel_map = i->channel_map; + s.mute = !!i->mute; + s.stream_restore_rule = ""; + + translateMasksAndMaps(s); + + bool is_new = !captureDevices.contains(s.index); + captureDevices[s.index] = s; +// kDebug(67100) << "Got some info about source: " << s.description; + + if (s_mixers.contains(KMIXPA_CAPTURE)) { + if (is_new) + s_mixers[KMIXPA_CAPTURE]->addWidget(s.index); + else { + int mid = s_mixers[KMIXPA_CAPTURE]->id2num(s.name); + if (mid >= 0) { + MixSet *ms = s_mixers[KMIXPA_CAPTURE]->getMixSet(); + (*ms)[mid]->setReadableName(s.description); + } + } + } +} + +static void client_cb(pa_context *c, const pa_client_info *i, int eol, void *) { + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + kWarning(67100) << "Client callback failure"; + return; + } + + if (eol > 0) { + dec_outstanding(c); + return; + } + + clients[i->index] = QString::fromUtf8(i->name); + //kDebug(67100) << "Got some info about client: " << clients[i->index]; +} + +static void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *) { + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + kWarning(67100) << "Sink Input callback failure"; + return; + } + + if (eol > 0) { + dec_outstanding(c); + if (s_mixers.contains(KMIXPA_APP_PLAYBACK)) + s_mixers[KMIXPA_APP_PLAYBACK]->triggerUpdate(); + return; + } + + const char *t; + if ((t = pa_proplist_gets(i->proplist, "module-stream-restore.id"))) { + if (strcmp(t, KMIXPA_EVENT_KEY) == 0) { + //kDebug(67100) << "Ignoring sink-input due to it being designated as an event and thus handled by the Event slider"; + return; + } + } + + QString appname = i18n("Unknown Application"); + if (clients.contains(i->client)) + appname = clients[i->client]; + + QString prefix = QString("%1: ").arg(appname); + + devinfo s; + s.index = i->index; + s.device_index = i->sink; + s.description = prefix + QString::fromUtf8(i->name); + s.name = QString("stream:") + QString::number(i->index); //appname.replace(' ', '_').toLower(); + s.icon_name = getIconNameFromProplist(i->proplist); + s.channel_map = i->channel_map; + s.volume = i->volume; + s.mute = !!i->mute; + s.stream_restore_rule = QString::fromUtf8(t); + + translateMasksAndMaps(s); + + bool is_new = !outputStreams.contains(s.index); + outputStreams[s.index] = s; +// kDebug(67100) << "Got some info about sink input (playback stream): " << s.description; + + if (s_mixers.contains(KMIXPA_APP_PLAYBACK)) { + if (is_new) + s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index, true); + else { + int mid = s_mixers[KMIXPA_APP_PLAYBACK]->id2num(s.name); + if (mid >= 0) { + MixSet *ms = s_mixers[KMIXPA_APP_PLAYBACK]->getMixSet(); + (*ms)[mid]->setReadableName(s.description); + } + } + } +} + +static void source_output_cb(pa_context *c, const pa_source_output_info *i, int eol, void *) { + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + kWarning(67100) << "Source Output callback failure"; + return; + } + + if (eol > 0) { + dec_outstanding(c); + if (s_mixers.contains(KMIXPA_APP_CAPTURE)) + s_mixers[KMIXPA_APP_CAPTURE]->triggerUpdate(); + return; + } + + /* NB Until Source Outputs support volumes, we just use the volume of the source itself */ + if (!captureDevices.contains(i->source)) { + kDebug(67100) << "Source Output refers to a Source we don't have any info for (probably just a peak meter or similar)"; + return; + } + + QString appname = i18n("Unknown Application"); + if (clients.contains(i->client)) + appname = clients[i->client]; + + QString prefix = QString("%1: ").arg(appname); + + devinfo s; + s.index = i->index; + s.device_index = i->source; + s.description = prefix + QString::fromUtf8(i->name); + s.name = QString("stream:") + QString::number(i->index); //appname.replace(' ', '_').toLower(); + s.icon_name = getIconNameFromProplist(i->proplist); + s.channel_map = i->channel_map; +#if HAVE_SOURCE_OUTPUT_VOLUMES + s.volume = i->volume; + s.mute = !!i->mute; +#else + s.volume = captureDevices[i->source].volume; + s.mute = captureDevices[i->source].mute; +#endif + s.stream_restore_rule = QString::fromUtf8(pa_proplist_gets(i->proplist, "module-stream-restore.id")); + + translateMasksAndMaps(s); + + bool is_new = !captureStreams.contains(s.index); + captureStreams[s.index] = s; +// kDebug(67100) << "Got some info about source output (capture stream): " << s.description; + + if (s_mixers.contains(KMIXPA_APP_CAPTURE)) { + if (is_new) + s_mixers[KMIXPA_APP_CAPTURE]->addWidget(s.index, true); + else { + int mid = s_mixers[KMIXPA_APP_CAPTURE]->id2num(s.name); + if (mid >= 0) { + MixSet *ms = s_mixers[KMIXPA_APP_CAPTURE]->getMixSet(); + (*ms)[mid]->setReadableName(s.description); + } + } + } +} + + +static devinfo create_role_devinfo(QString name) { + + Q_ASSERT(s_RestoreRules.contains(name)); + + devinfo s; + s.index = s.device_index = PA_INVALID_INDEX; + s.description = i18n("Event Sounds"); + s.name = QString("restore:") + name; + s.icon_name = "dialog-information"; + s.channel_map = s_RestoreRules[name].channel_map; + s.volume = s_RestoreRules[name].volume; + s.mute = s_RestoreRules[name].mute; + s.stream_restore_rule = name; + + translateMasksAndMaps(s); + return s; +} + + +void ext_stream_restore_read_cb(pa_context *c, const pa_ext_stream_restore_info *i, int eol, void *) { + + if (eol < 0) { + dec_outstanding(c); + kWarning(67100) << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(s_context)); + return; + } + + if (eol > 0) { + dec_outstanding(c); + + // Special case: ensure that our media events exists. + // On first login by a new users, this wont be in our database so we should create it. + if (!s_RestoreRules.contains(KMIXPA_EVENT_KEY)) { + // Create a fake rule + restoreRule rule; + rule.channel_map.channels = 1; + rule.channel_map.map[0] = PA_CHANNEL_POSITION_MONO; + rule.volume.channels = 1; + rule.volume.values[0] = PA_VOLUME_NORM; + rule.mute = false; + rule.device = ""; + s_RestoreRules[KMIXPA_EVENT_KEY] = rule; + kDebug(67100) << "Initialising restore rule for new user: " << i18n("Event Sounds"); + } + + if (s_mixers.contains(KMIXPA_APP_PLAYBACK)) { + // If we have rules, it will be created below... but if no rules + // then we add it here. + if (!outputRoles.contains(PA_INVALID_INDEX)) { + devinfo s = create_role_devinfo(KMIXPA_EVENT_KEY); + outputRoles[s.index] = s; + + s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index); + } + + s_mixers[KMIXPA_APP_PLAYBACK]->triggerUpdate(); + } + + return; + } + + + QString name = QString::fromUtf8(i->name); +// kDebug(67100) << QString("Got some info about restore rule: '%1' (Device: %2)").arg(name).arg(i->device ? i->device : "None"); + restoreRule rule; + rule.channel_map = i->channel_map; + rule.volume = i->volume; + rule.mute = !!i->mute; + rule.device = i->device; + + if (rule.channel_map.channels < 1 && name == KMIXPA_EVENT_KEY) { + // Stream restore rules may not have valid volumes/channel maps (as these are optional) + // but we need a valid volume+channelmap for our events sounds so fix it up. + rule.channel_map.channels = 1; + rule.channel_map.map[0] = PA_CHANNEL_POSITION_MONO; + rule.volume.channels = 1; + rule.volume.values[0] = PA_VOLUME_NORM; + } + + s_RestoreRules[name] = rule; + + if (s_mixers.contains(KMIXPA_APP_PLAYBACK)) { + // We only want to know about Sound Events for now... + if (name == KMIXPA_EVENT_KEY) { + devinfo s = create_role_devinfo(name); + bool is_new = !outputRoles.contains(s.index); + outputRoles[s.index] = s; + + if (is_new) + s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index, true); + } + } +} + +static void ext_stream_restore_subscribe_cb(pa_context *c, void *) { + + Q_ASSERT(c == s_context); + + pa_operation *o; + if (!(o = pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, NULL))) { + kWarning(67100) << "pa_ext_stream_restore_read() failed"; + return; + } + + pa_operation_unref(o); +} + + +static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *) { + + Q_ASSERT(c == s_context); + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SINK: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (s_mixers.contains(KMIXPA_PLAYBACK)) + s_mixers[KMIXPA_PLAYBACK]->removeWidget(index); + } else { + pa_operation *o; + if (!(o = pa_context_get_sink_info_by_index(c, index, sink_cb, NULL))) { + kWarning(67100) << "pa_context_get_sink_info_by_index() failed"; + return; + } + pa_operation_unref(o); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (s_mixers.contains(KMIXPA_CAPTURE)) + s_mixers[KMIXPA_CAPTURE]->removeWidget(index); + } else { + pa_operation *o; + if (!(o = pa_context_get_source_info_by_index(c, index, source_cb, NULL))) { + kWarning(67100) << "pa_context_get_source_info_by_index() failed"; + return; + } + pa_operation_unref(o); + } + break; + + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (s_mixers.contains(KMIXPA_APP_PLAYBACK)) + s_mixers[KMIXPA_APP_PLAYBACK]->removeWidget(index); + } else { + pa_operation *o; + if (!(o = pa_context_get_sink_input_info(c, index, sink_input_cb, NULL))) { + kWarning(67100) << "pa_context_get_sink_input_info() failed"; + return; + } + pa_operation_unref(o); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (s_mixers.contains(KMIXPA_APP_CAPTURE)) + s_mixers[KMIXPA_APP_CAPTURE]->removeWidget(index); + } else { + pa_operation *o; + if (!(o = pa_context_get_source_output_info(c, index, source_output_cb, NULL))) { + kWarning(67100) << "pa_context_get_sink_input_info() failed"; + return; + } + pa_operation_unref(o); + } + break; + + case PA_SUBSCRIPTION_EVENT_CLIENT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + clients.remove(index); + } else { + pa_operation *o; + if (!(o = pa_context_get_client_info(c, index, client_cb, NULL))) { + kWarning(67100) << "pa_context_get_client_info() failed"; + return; + } + pa_operation_unref(o); + } + break; + + } +} + + +static void context_state_callback(pa_context *c, void *) +{ + pa_context_state_t state = pa_context_get_state(c); + if (state == PA_CONTEXT_READY) { + // Attempt to load things up + pa_operation *o; + + // 1. Register for the stream changes (except during probe) + if (s_context == c) { + pa_context_set_subscribe_callback(c, subscribe_cb, NULL); + + if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t) + (PA_SUBSCRIPTION_MASK_SINK| + PA_SUBSCRIPTION_MASK_SOURCE| + PA_SUBSCRIPTION_MASK_CLIENT| + PA_SUBSCRIPTION_MASK_SINK_INPUT| + PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) { + kWarning(67100) << "pa_context_subscribe() failed"; + return; + } + pa_operation_unref(o); + } + + if (!(o = pa_context_get_sink_info_list(c, sink_cb, NULL))) { + kWarning(67100) << "pa_context_get_sink_info_list() failed"; + return; + } + pa_operation_unref(o); + s_outstandingRequests++; + + if (!(o = pa_context_get_source_info_list(c, source_cb, NULL))) { + kWarning(67100) << "pa_context_get_source_info_list() failed"; + return; + } + pa_operation_unref(o); + s_outstandingRequests++; + + + if (!(o = pa_context_get_client_info_list(c, client_cb, NULL))) { + kWarning(67100) << "pa_context_client_info_list() failed"; + return; + } + pa_operation_unref(o); + s_outstandingRequests++; + + if (!(o = pa_context_get_sink_input_info_list(c, sink_input_cb, NULL))) { + kWarning(67100) << "pa_context_get_sink_input_info_list() failed"; + return; + } + pa_operation_unref(o); + s_outstandingRequests++; + + if (!(o = pa_context_get_source_output_info_list(c, source_output_cb, NULL))) { + kWarning(67100) << "pa_context_get_source_output_info_list() failed"; + return; + } + pa_operation_unref(o); + s_outstandingRequests++; + + /* These calls are not always supported */ + if ((o = pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, NULL))) { + pa_operation_unref(o); + s_outstandingRequests++; + + pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, NULL); + + if ((o = pa_ext_stream_restore_subscribe(c, 1, NULL, NULL))) + pa_operation_unref(o); + } else { + kWarning(67100) << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(s_context)); + } + } else if (!PA_CONTEXT_IS_GOOD(state)) { + // If this is our probe phase, exit our context immediately + if (s_context != c) { + pa_context_disconnect(c); + } else { + // If we're not probing, it means we've been disconnected from our + // glib context + pa_context_unref(s_context); + s_context = NULL; + + // Remove all GUI elements + QMap::iterator it; + for (it = s_mixers.begin(); it != s_mixers.end(); ++it) { + (*it)->removeAllWidgets(); + } + // This one is not handled above. + clients.clear(); + + if (s_mixers.contains(KMIXPA_PLAYBACK)) { + kWarning(67100) << "Connection to PulseAudio daemon closed. Attempting reconnection."; + s_pulseActive = UNKNOWN; + QTimer::singleShot(50, s_mixers[KMIXPA_PLAYBACK], SLOT(reinit())); + } + } + } +} + +static void setVolumeFromPulse(Volume& volume, const devinfo& dev) +{ + chanIDMap::const_iterator iter; + for (iter = dev.chanIDs.begin(); iter != dev.chanIDs.end(); ++iter) + { + //kDebug(67100) << "Setting volume for channel " << iter.value() << " to " << (long)dev.volume.values[iter.key()] << " (" << ((100*(long)dev.volume.values[iter.key()]) / PA_VOLUME_NORM) << "%)"; + volume.setVolume(iter.value(), (long)dev.volume.values[iter.key()]); + } +} + +static pa_cvolume genVolumeForPulse(const devinfo& dev, Volume& volume) +{ + pa_cvolume cvol = dev.volume; + + chanIDMap::const_iterator iter; + for (iter = dev.chanIDs.begin(); iter != dev.chanIDs.end(); ++iter) + { + cvol.values[iter.key()] = (uint32_t)volume.getVolume(iter.value()); + //kDebug(67100) << "Setting volume for channel " << iter.value() << " to " << cvol.values[iter.key()] << " (" << ((100*cvol.values[iter.key()]) / PA_VOLUME_NORM) << "%)"; + } + return cvol; +} + +static devmap* get_widget_map(int type, QString id = "") +{ + Q_ASSERT(type >= 0 && type <= KMIXPA_WIDGET_MAX); + + if (KMIXPA_PLAYBACK == type) + return &outputDevices; + else if (KMIXPA_CAPTURE == type) + return &captureDevices; + else if (KMIXPA_APP_PLAYBACK == type) { + if (id.startsWith("restore:")) + return &outputRoles; + return &outputStreams; + } else if (KMIXPA_APP_CAPTURE == type) + return &captureStreams; + + Q_ASSERT(0); + return NULL; +} +static devmap* get_widget_map(int type, int index) +{ + if (PA_INVALID_INDEX == (uint32_t)index) + return get_widget_map(type, "restore:"); + return get_widget_map(type); +} + +void Mixer_PULSE::emitControlsReconfigured() +{ + // emit controlsReconfigured(_mixer->id()); + // Do not emit directly to ensure all connected slots are executed + // in their own event loop. + + /* + * Bug 309464: + * + * Comment by cesken: I am not really sure what the comment above means. + * 1) IIRC coling told me "otherwise KMix crashes". + * 2) There are also bug reports that heavily indicate the crash when operation the "move stream" from a popup + * menu. + * 3) I don't know what the "executed in their own event loop" means. Are we in a "wrong" thread here (PA), + * which is not suitable for GUI code?!? + * + * Work note: Ouch. it means PA thread makes direct calls via announce(), and do even GUI code. OUCH. Redo this comments! + * + * Conclusions: + * a) It seems there seems to be some object deletion hazard with a QMenu (the one for "move stream") + * b) I do not see why executing it Queued is better, because you can never know when it is actually being + * executed: it could be "right now". It looks like Qt currently executes it after the QMenu hazard has + * resolved itselves miracously. + * c) I am definitely strongly opposed on this "execute later" approach. It is pure gambling IMO and might be + * broken any time (from DEBUG to RELEASE build, or by a new Qt or KDE version). + * + * TODO Somebody with more Qt and PA internal insight might help to clear up things here. + * + * Temporary solution: Do the QueuedConnection until we really know hat is going on. But the called code + * pulseControlsReconfigured() will then do the standard announce() so that every part of + * KMix automatically gets updated. + * + */ + QMetaObject::invokeMethod(this, + "pulseControlsReconfigured", + Qt::QueuedConnection); + +// QMetaObject::invokeMethod(this, +// "pulseControlsReconfigured", +// Qt::QueuedConnection, +// Q_ARG(QString, _mixer->id())); +} + +void Mixer_PULSE::pulseControlsReconfigured() +{ + kDebug() << "Reconfigure " << _mixer->id(); + ControlManager::instance().announce(_mixer->id(), ControlChangeType::ControlList, getDriverName()); +} + +void Mixer_PULSE::pulseControlsReconfigured(QString mixerId) +{ + kDebug() << "Reconfigure " << mixerId; + ControlManager::instance().announce(mixerId, ControlChangeType::ControlList, getDriverName()); +} + +void Mixer_PULSE::updateRecommendedMaster(devmap* map) +{ + unsigned int prio = 0; + shared_ptr res; + MixSet::iterator iter; + + for (iter = m_mixDevices.begin(); iter != m_mixDevices.end(); ++iter) { + unsigned int devprio = map->value( id2num((*iter)->id()) ).priority; + if (( devprio > prio ) || !res ) { + prio = devprio; + res = *iter; + } + } + + if (res) + kDebug(67100) << "Selecting master " << res->id() + << " for type " << m_devnum; + m_recommendedMaster = res; +} + +void Mixer_PULSE::addWidget(int index, bool isAppStream) +{ + devmap* map = get_widget_map(m_devnum, index); + + if (!map->contains(index)) { + kWarning(67100) << "New " << m_devnum << " widget notified for index " + << index << " but I cannot find it in my list :s"; + return; + } + + if (addDevice((*map)[index], isAppStream)) + updateRecommendedMaster(map); + emitControlsReconfigured(); +} + +void Mixer_PULSE::removeWidget(int index) +{ + devmap* map = get_widget_map(m_devnum); + + if (!map->contains(index)) { + kDebug(67100) << "Removing " << m_devnum << " widget notified for index " + << index << " but I cannot find it in my list :s"; + // Sometimes we ignore things (e.g. event sounds) so don't be too noisy here. + return; + } + + QString id = (*map)[index].name; + map->remove(index); + + // We need to find the MixDevice that goes with this widget and remove it. + MixSet::iterator iter; + shared_ptr md; + for (iter = m_mixDevices.begin(); iter != m_mixDevices.end(); ++iter) + { + if ((*iter)->id() == id) + { + md = m_mixDevices.get(id); + kDebug() << "MixDevice 1 useCount=" << md.use_count(); + md->close(); + kDebug() << "MixDevice 2 useCount=" << md.use_count(); + m_mixDevices.erase(iter); + kDebug() << "MixDevice 3 useCount=" << md.use_count(); + break; + } + } + + if (md) + updateRecommendedMaster(map); + emitControlsReconfigured(); + kDebug() << "MixDevice 4 useCount=" << md.use_count(); +} + +void Mixer_PULSE::removeAllWidgets() +{ + devmap* map = get_widget_map(m_devnum); + map->clear(); + + // Special case + if (KMIXPA_APP_PLAYBACK == m_devnum) + outputRoles.clear(); + + freeMixDevices(); + emitControlsReconfigured(); +} + +bool Mixer_PULSE::addDevice(devinfo& dev, bool isAppStream) +{ + if (dev.chanMask == Volume::MNONE) + return false; + + MixSet *ms = 0; + if (m_devnum == KMIXPA_APP_PLAYBACK && s_mixers.contains(KMIXPA_PLAYBACK)) + ms = s_mixers[KMIXPA_PLAYBACK]->getMixSet(); + else if (m_devnum == KMIXPA_APP_CAPTURE && s_mixers.contains(KMIXPA_CAPTURE)) + ms = s_mixers[KMIXPA_CAPTURE]->getMixSet(); + + int maxVol = GlobalConfig::instance().data.volumeOverdrive ? PA_VOLUME_UI_MAX : PA_VOLUME_NORM; + Volume v(maxVol, PA_VOLUME_MUTED, true, false); + v.addVolumeChannels(dev.chanMask); + setVolumeFromPulse(v, dev); + MixDevice* md = new MixDevice( _mixer, dev.name, dev.description, dev.icon_name, ms); + if (isAppStream) + md->setApplicationStream(true); + + kDebug(67100) << "Adding Pulse volume " << dev.name << ", isCapture= " + << (m_devnum == KMIXPA_CAPTURE || m_devnum == KMIXPA_APP_CAPTURE) + << ", isAppStream= " << isAppStream << "=" << md->isApplicationStream() << ", devnum=" << m_devnum; + md->addPlaybackVolume(v); + md->setMuted(dev.mute); + m_mixDevices.append(md->addToPool()); + return true; +} + +Mixer_Backend* PULSE_getMixer( Mixer *mixer, int devnum ) +{ + Mixer_Backend *l_mixer; + l_mixer = new Mixer_PULSE( mixer, devnum ); + return l_mixer; +} + +bool Mixer_PULSE::connectToDaemon() +{ + Q_ASSERT(NULL == s_context); + + kDebug(67100) << "Attempting connection to PulseAudio sound daemon"; + pa_mainloop_api *api = pa_glib_mainloop_get_api(s_mainloop); + Q_ASSERT(api); + + s_context = pa_context_new(api, "KMix"); + Q_ASSERT(s_context); + + if (pa_context_connect(s_context, NULL, PA_CONTEXT_NOFAIL, 0) < 0) { + pa_context_unref(s_context); + s_context = NULL; + return false; + } + pa_context_set_state_callback(s_context, &context_state_callback, NULL); + return true; +} + + +Mixer_PULSE::Mixer_PULSE(Mixer *mixer, int devnum) : Mixer_Backend(mixer, devnum) +{ + if ( devnum == -1 ) + m_devnum = 0; + + QString pulseenv = qgetenv("KMIX_PULSEAUDIO_DISABLE"); + if (pulseenv.toInt()) + s_pulseActive = INACTIVE; + + // We require a glib event loop + if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("EventDispatcherGlib")) { + kDebug(67100) << "Disabling PulseAudio integration for lack of GLib event loop"; + s_pulseActive = INACTIVE; + } + + + ++refcount; + if (INACTIVE != s_pulseActive && 1 == refcount) + { + // First of all conenct to PA via simple/blocking means and if that succeeds, + // use a fully async integrated mainloop method to connect and get proper support. + pa_mainloop *p_test_mainloop; + if (!(p_test_mainloop = pa_mainloop_new())) { + kDebug(67100) << "PulseAudio support disabled: Unable to create mainloop"; + s_pulseActive = INACTIVE; + goto endconstruct; + } + + pa_context *p_test_context; + if (!(p_test_context = pa_context_new(pa_mainloop_get_api(p_test_mainloop), "kmix-probe"))) { + kDebug(67100) << "PulseAudio support disabled: Unable to create context"; + pa_mainloop_free(p_test_mainloop); + s_pulseActive = INACTIVE; + goto endconstruct; + } + + kDebug(67100) << "Probing for PulseAudio..."; + // (cg) Convert to PA_CONTEXT_NOFLAGS when PulseAudio 0.9.19 is required + if (pa_context_connect(p_test_context, NULL, static_cast(0), NULL) < 0) { + kDebug(67100) << QString("PulseAudio support disabled: %1").arg(pa_strerror(pa_context_errno(p_test_context))); + pa_context_disconnect(p_test_context); + pa_context_unref(p_test_context); + pa_mainloop_free(p_test_mainloop); + s_pulseActive = INACTIVE; + goto endconstruct; + } + + // Assume we are inactive, it will be set to active if appropriate + s_pulseActive = INACTIVE; + pa_context_set_state_callback(p_test_context, &context_state_callback, NULL); + for (;;) { + pa_mainloop_iterate(p_test_mainloop, 1, NULL); + + if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(p_test_context))) { + kDebug(67100) << "PulseAudio probe complete."; + break; + } + } + pa_context_disconnect(p_test_context); + pa_context_unref(p_test_context); + pa_mainloop_free(p_test_mainloop); + + + if (INACTIVE != s_pulseActive) + { + // Reconnect via integrated mainloop + s_mainloop = pa_glib_mainloop_new(NULL); + Q_ASSERT(s_mainloop); + + connectToDaemon(); + +#if defined(HAVE_CANBERRA) + int ret = ca_context_create(&s_ccontext); + if (ret < 0) { + kDebug(67100) << "Disabling Sound Feedback. Canberra context failed."; + s_ccontext = NULL; + } else + ca_context_set_driver(s_ccontext, "pulse"); +#endif + } + + kDebug(67100) << "PulseAudio status: " << (s_pulseActive==UNKNOWN ? "Unknown (bug)" : (s_pulseActive==ACTIVE ? "Active" : "Inactive")); + } + +endconstruct: + s_mixers[m_devnum] = this; +} + +Mixer_PULSE::~Mixer_PULSE() +{ + s_mixers.remove(m_devnum); + + if (refcount > 0) + { + --refcount; + if (0 == refcount) + { +#if defined(HAVE_CANBERRA) + if (s_ccontext) { + ca_context_destroy(s_ccontext); + s_ccontext = NULL; + } +#endif + + if (s_context) { + pa_context_unref(s_context); + s_context = NULL; + } + + if (s_mainloop) { + pa_glib_mainloop_free(s_mainloop); + s_mainloop = NULL; + } + } + } + + closeCommon(); +} + +int Mixer_PULSE::open() +{ + //kDebug(67100) << "Trying Pulse sink"; + + if (ACTIVE == s_pulseActive && m_devnum <= KMIXPA_APP_CAPTURE) + { + // Make sure the GUI layers know we are dynamic so as to always paint us + _mixer->setDynamic(); + + devmap::iterator iter; + if (KMIXPA_PLAYBACK == m_devnum) + { + _id = "Playback Devices"; + registerCard(i18n("Playback Devices")); + for (iter = outputDevices.begin(); iter != outputDevices.end(); ++iter) + addDevice(*iter); + updateRecommendedMaster(&outputDevices); + } + else if (KMIXPA_CAPTURE == m_devnum) + { + _id = "Capture Devices"; + registerCard(i18n("Capture Devices")); + for (iter = captureDevices.begin(); iter != captureDevices.end(); ++iter) + addDevice(*iter); + updateRecommendedMaster(&outputDevices); + } + else if (KMIXPA_APP_PLAYBACK == m_devnum) + { + _id = "Playback Streams"; + registerCard(i18n("Playback Streams")); + for (iter = outputRoles.begin(); iter != outputRoles.end(); ++iter) + addDevice(*iter, true); + updateRecommendedMaster(&outputRoles); + for (iter = outputStreams.begin(); iter != outputStreams.end(); ++iter) + addDevice(*iter, true); + updateRecommendedMaster(&outputStreams); + } + else if (KMIXPA_APP_CAPTURE == m_devnum) + { + _id = "Capture Streams"; + registerCard(i18n("Capture Streams")); + for (iter = captureStreams.begin(); iter != captureStreams.end(); ++iter) + addDevice(*iter); + updateRecommendedMaster(&captureStreams); + } + + kDebug(67100) << "Using PulseAudio for mixer: " << getName(); + m_isOpen = true; + } + + return 0; +} + +int Mixer_PULSE::close() +{ + closeCommon(); + return 1; +} + +int Mixer_PULSE::id2num(const QString& id) { + int num = -1; + // todo: Store this in a hash or similar + int i; + for (i = 0; i < m_mixDevices.size(); ++i) { + if (m_mixDevices[i]->id() == id) { + num = i; + break; + } + } + //kDebug(67100) << "id2num() num=" << num; + return num; +} + +int Mixer_PULSE::readVolumeFromHW( const QString& id, shared_ptr md ) +{ + devmap *map = get_widget_map(m_devnum, id); + + devmap::iterator iter; + for (iter = map->begin(); iter != map->end(); ++iter) + { + if (iter->name == id) + { + setVolumeFromPulse(md->playbackVolume(), *iter); + md->setMuted(iter->mute); + break; + } + } + + return 0; +} + +int Mixer_PULSE::writeVolumeToHW( const QString& id, shared_ptr md ) +{ + devmap::iterator iter; + if (KMIXPA_PLAYBACK == m_devnum) + { + for (iter = outputDevices.begin(); iter != outputDevices.end(); ++iter) + { + if (iter->name == id) + { + pa_operation *o; + + pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume()); + if (!(o = pa_context_set_sink_volume_by_index(s_context, iter->index, &volume, NULL, NULL))) { + kWarning(67100) << "pa_context_set_sink_volume_by_index() failed"; + return Mixer::ERR_READ; + } + pa_operation_unref(o); + + if (!(o = pa_context_set_sink_mute_by_index(s_context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) { + kWarning(67100) << "pa_context_set_sink_mute_by_index() failed"; + return Mixer::ERR_READ; + } + pa_operation_unref(o); + +#if defined(HAVE_CANBERRA) + if (s_ccontext && Mixer::getBeepOnVolumeChange() ) { + int playing = 0; + int cindex = 2; // Note "2" is simply the index we've picked. It's somewhat irrelevant. + + + ca_context_playing(s_ccontext, cindex, &playing); + + // NB Depending on how this is desired to work, we may want to simply + // skip playing, or cancel the currently playing sound and play our + // new one... for now, let's do the latter. + if (playing) { + ca_context_cancel(s_ccontext, cindex); + playing = 0; + } + + if (!playing) { + char dev[64]; + + snprintf(dev, sizeof(dev), "%lu", (unsigned long) iter->index); + ca_context_change_device(s_ccontext, dev); + + // Ideally we'd use something like ca_gtk_play_for_widget()... + ca_context_play( + s_ccontext, + cindex, + CA_PROP_EVENT_DESCRIPTION, i18n("Volume Control Feedback Sound").toUtf8().constData(), + CA_PROP_EVENT_ID, "audio-volume-change", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + CA_PROP_CANBERRA_ENABLE, "1", + NULL + ); + + ca_context_change_device(s_ccontext, NULL); + } + } +#endif + + return 0; + } + } + } + else if (KMIXPA_CAPTURE == m_devnum) + { + for (iter = captureDevices.begin(); iter != captureDevices.end(); ++iter) + { + if (iter->name == id) + { + pa_operation *o; + + pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume()); + if (!(o = pa_context_set_source_volume_by_index(s_context, iter->index, &volume, NULL, NULL))) { + kWarning(67100) << "pa_context_set_source_volume_by_index() failed"; + return Mixer::ERR_READ; + } + pa_operation_unref(o); + + if (!(o = pa_context_set_source_mute_by_index(s_context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) { + kWarning(67100) << "pa_context_set_source_mute_by_index() failed"; + return Mixer::ERR_READ; + } + pa_operation_unref(o); + + return 0; + } + } + } + else if (KMIXPA_APP_PLAYBACK == m_devnum) + { + if (id.startsWith("stream:")) + { + for (iter = outputStreams.begin(); iter != outputStreams.end(); ++iter) + { + if (iter->name == id) + { + pa_operation *o; + + pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume()); + if (!(o = pa_context_set_sink_input_volume(s_context, iter->index, &volume, NULL, NULL))) { + kWarning(67100) << "pa_context_set_sink_input_volume() failed"; + return Mixer::ERR_READ; + } + pa_operation_unref(o); + + if (!(o = pa_context_set_sink_input_mute(s_context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) { + kWarning(67100) << "pa_context_set_sink_input_mute() failed"; + return Mixer::ERR_READ; + } + pa_operation_unref(o); + + return 0; + } + } + } + else if (id.startsWith("restore:")) + { + for (iter = outputRoles.begin(); iter != outputRoles.end(); ++iter) + { + if (iter->name == id) + { + restoreRule &rule = s_RestoreRules[iter->stream_restore_rule]; + pa_ext_stream_restore_info info; + info.name = iter->stream_restore_rule.toUtf8().constData(); + info.channel_map = rule.channel_map; + info.volume = genVolumeForPulse(*iter, md->playbackVolume()); + info.device = rule.device.isEmpty() ? NULL : rule.device.toUtf8().constData(); + info.mute = (md->isMuted() ? 1 : 0); + + pa_operation* o; + if (!(o = pa_ext_stream_restore_write(s_context, PA_UPDATE_REPLACE, &info, 1, true, NULL, NULL))) { + kWarning(67100) << "pa_ext_stream_restore_write() failed" << info.channel_map.channels << info.volume.channels << info.name; + return Mixer::ERR_READ; + } + pa_operation_unref(o); + + return 0; + } + } + } + } + else if (KMIXPA_APP_CAPTURE == m_devnum) + { + for (iter = captureStreams.begin(); iter != captureStreams.end(); ++iter) + { + if (iter->name == id) + { + pa_operation *o; + +#if HAVE_SOURCE_OUTPUT_VOLUMES + pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume()); + if (!(o = pa_context_set_source_output_volume(s_context, iter->index, &volume, NULL, NULL))) { + kWarning(67100) << "pa_context_set_source_output_volume_by_index() failed"; + return Mixer::ERR_READ; + } + pa_operation_unref(o); + + if (!(o = pa_context_set_source_output_mute(s_context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) { + kWarning(67100) << "pa_context_set_source_output_mute_by_index() failed"; + return Mixer::ERR_READ; + } + pa_operation_unref(o); +#else + // NB Note that this is different from APP_PLAYBACK in that we set the volume on the source itself. + pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume()); + if (!(o = pa_context_set_source_volume_by_index(s_context, iter->device_index, &volume, NULL, NULL))) { + kWarning(67100) << "pa_context_set_source_volume_by_index() failed"; + return Mixer::ERR_READ; + } + pa_operation_unref(o); + + if (!(o = pa_context_set_source_mute_by_index(s_context, iter->device_index, (md->isMuted() ? 1 : 0), NULL, NULL))) { + kWarning(67100) << "pa_context_set_source_mute_by_index() failed"; + return Mixer::ERR_READ; + } + pa_operation_unref(o); +#endif + + return 0; + } + } + } + + return 0; +} + +/** +* Move the stream to a new destination +*/ +bool Mixer_PULSE::moveStream( const QString& id, const QString& destId ) { + Q_ASSERT(KMIXPA_APP_PLAYBACK == m_devnum || KMIXPA_APP_CAPTURE == m_devnum); + + kDebug(67100) << "Mixer_PULSE::moveStream(): Move Stream Requested - Stream: " << id << ", Destination: " << destId; + + // Lookup the stream index. + uint32_t stream_index = PA_INVALID_INDEX; + QString stream_restore_rule = ""; + devmap::iterator iter; + devmap *map = get_widget_map(m_devnum); + for (iter = map->begin(); iter != map->end(); ++iter) { + if (iter->name == id) { + stream_index = iter->index; + stream_restore_rule = iter->stream_restore_rule; + break; + } + } + + if (PA_INVALID_INDEX == stream_index) { + kError(67100) << "Mixer_PULSE::moveStream(): Cannot find stream index"; + return false; + } + + if (destId.isEmpty()) { + // We want to remove any specific device in the stream restore rule. + if (stream_restore_rule.isEmpty() || !s_RestoreRules.contains(stream_restore_rule)) { + kWarning(67100) << "Mixer_PULSE::moveStream(): Trying to set Automatic on a stream with no rule"; + } else { + restoreRule &rule = s_RestoreRules[stream_restore_rule]; + pa_ext_stream_restore_info info; + info.name = stream_restore_rule.toUtf8().constData(); + info.channel_map = rule.channel_map; + info.volume = rule.volume; + info.device = NULL; + info.mute = rule.mute ? 1 : 0; + + pa_operation* o; + if (!(o = pa_ext_stream_restore_write(s_context, PA_UPDATE_REPLACE, &info, 1, true, NULL, NULL))) { + kWarning(67100) << "pa_ext_stream_restore_write() failed" << info.channel_map.channels << info.volume.channels << info.name; + return Mixer::ERR_READ; + } + pa_operation_unref(o); + } + } else { + pa_operation* o; + if (KMIXPA_APP_PLAYBACK == m_devnum) { + if (!(o = pa_context_move_sink_input_by_name(s_context, stream_index, destId.toUtf8().constData(), NULL, NULL))) { + kWarning(67100) << "pa_context_move_sink_input_by_name() failed"; + return false; + } + } else { + if (!(o = pa_context_move_source_output_by_name(s_context, stream_index, destId.toUtf8().constData(), NULL, NULL))) { + kWarning(67100) << "pa_context_move_source_output_by_name() failed"; + return false; + } + } + pa_operation_unref(o); + } + + return true; +} + +void Mixer_PULSE::reinit() +{ + // We only support reinit on our primary mixer. + Q_ASSERT(KMIXPA_PLAYBACK == m_devnum); + connectToDaemon(); +} + +void Mixer_PULSE::triggerUpdate() +{ + readSetFromHWforceUpdate(); + readSetFromHW(); +} + +// Please see KMixWindow::initActionsAfterInitMixer(), it uses the driverName + +QString PULSE_getDriverName() { + return "PulseAudio"; +} + +QString Mixer_PULSE::getDriverName() +{ + return "PulseAudio"; +} + +#include "mixer_pulse.moc" diff --git a/kmix/backends/mixer_pulse.h b/kmix/backends/mixer_pulse.h new file mode 100644 index 00000000..5720ad63 --- /dev/null +++ b/kmix/backends/mixer_pulse.h @@ -0,0 +1,95 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2008 Helio Chissini de Castro + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MIXER_PULSE_H +#define MIXER_PULSE_H + +#include + +#include "mixer_backend.h" +#include + +typedef QMap chanIDMap; +typedef struct { + int index; + int device_index; + QString name; + QString description; + QString icon_name; + pa_cvolume volume; + pa_channel_map channel_map; + bool mute; + QString stream_restore_rule; + + Volume::ChannelMask chanMask; + chanIDMap chanIDs; + unsigned int priority; +} devinfo; +typedef QMap devmap; + +class Mixer_PULSE : public Mixer_Backend +{ + Q_OBJECT + + public: + Mixer_PULSE(Mixer *mixer, int devnum); + virtual ~Mixer_PULSE(); + + virtual int readVolumeFromHW( const QString& id, shared_ptr ); + virtual int writeVolumeToHW ( const QString& id, shared_ptr ); + + virtual bool moveStream( const QString& id, const QString& destId ); + + virtual QString getDriverName(); + virtual QString getId() const { return _id; }; + + virtual bool needsPolling() { return false; } + + void triggerUpdate(); + void addWidget(int index, bool = false); + void removeWidget(int index); + void removeAllWidgets(); + MixSet *getMixSet() { return &m_mixDevices; } + int id2num(const QString& id); + + protected: + virtual int open(); + virtual int close(); + + int fd; + QString _id; + + private: + bool addDevice(devinfo& dev, bool isAppStream = false); + bool connectToDaemon(); + void emitControlsReconfigured(); + void updateRecommendedMaster(devmap* map); + + protected slots: + void pulseControlsReconfigured(QString mixerId); + void pulseControlsReconfigured(); + +public: + void reinit(); + +}; + +#endif diff --git a/kmix/backends/mixer_sun.cpp b/kmix/backends/mixer_sun.cpp new file mode 100644 index 00000000..85e1d0e5 --- /dev/null +++ b/kmix/backends/mixer_sun.cpp @@ -0,0 +1,495 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2000 Christian Esken + * 2000 Brian Hanson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "mixer_sun.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/mixer.h" +#include +#include +//====================================================================== +// CONSTANT/ENUM DEFINITIONS +//====================================================================== + +// +// Mixer Device Numbers +// +// Note: We can't just use the Sun port #defines because : +// 1) Some logical devices don't correspond to ports (master&recmon) +// 2) The play and record port definitions reuse the same values +// +enum MixerDevs +{ + MIXERDEV_MASTER_VOLUME, + MIXERDEV_INTERNAL_SPEAKER, + MIXERDEV_HEADPHONE, + MIXERDEV_LINE_OUT, + MIXERDEV_RECORD_MONITOR, + MIXERDEV_MICROPHONE, + MIXERDEV_LINE_IN, + MIXERDEV_CD, + // Insert new devices before this marker + MIXERDEV_END_MARKER +}; +const int numDevs = MIXERDEV_END_MARKER; + +// +// Device name strings +// +const char* MixerDevNames[] = +{ + I18N_NOOP("Master Volume"), + I18N_NOOP("Internal Speaker"), + I18N_NOOP("Headphone"), + I18N_NOOP("Line Out"), + I18N_NOOP("Record Monitor"), + I18N_NOOP("Microphone"), + I18N_NOOP("Line In"), + I18N_NOOP("CD") +}; + +// +// Channel types (this specifies which icon to display) +// +const MixDevice::ChannelType MixerChannelTypes[] = +{ + MixDevice::VOLUME, // MASTER_VOLUME + MixDevice::AUDIO, // INTERNAL_SPEAKER + MixDevice::EXTERNAL, // HEADPHONE (we really need an icon for this) + MixDevice::EXTERNAL, // LINE_OUT + MixDevice::RECMONITOR, // RECORD_MONITOR + MixDevice::MICROPHONE, // MICROPHONE + MixDevice::EXTERNAL, // LINE_IN + MixDevice::CD // CD +}; + +// +// Mapping from device numbers to Sun port mask values +// +const uint_t MixerSunPortMasks[] = +{ + 0, // MASTER_VOLUME - no associated port + AUDIO_SPEAKER, + AUDIO_HEADPHONE, + AUDIO_LINE_OUT, + 0, // RECORD_MONITOR - no associated port + AUDIO_MICROPHONE, + AUDIO_LINE_IN, + AUDIO_CD +}; + + +//====================================================================== +// FUNCTION/METHOD DEFINITIONS +//====================================================================== + + +//====================================================================== +// FUNCTION : SUN_getMixer +// DESCRIPTION : Creates and returns a new mixer object. +//====================================================================== +Mixer_Backend* SUN_getMixer( Mixer *mixer, int devnum ) +{ + Mixer_Backend *l_mixer; + l_mixer = new Mixer_SUN( mixer, devnum ); + return l_mixer; +} + + +//====================================================================== +// FUNCTION : Mixer::Mixer +// DESCRIPTION : Class constructor. +//====================================================================== +Mixer_SUN::Mixer_SUN(Mixer *mixer, int devnum) : Mixer_Backend(mixer, devnum) +{ + if ( devnum == -1 ) + m_devnum = 0; +} + +//====================================================================== +// FUNCTION : Mixer::Mixer +// DESCRIPTION : Class destructor. +//====================================================================== +Mixer_SUN::~Mixer_SUN() +{ + close(); +} + +//====================================================================== +// FUNCTION : Mixer::open +// DESCRIPTION : Initialize the mixer and open the hardware driver. +//====================================================================== +int Mixer_SUN::open() +{ + // + // We don't support multiple devices + // + if ( m_devnum !=0 ) + return Mixer::ERR_OPEN; + + // + // Open the mixer hardware driver + // + QString audiodev(getenv("AUDIODEV")); + if(audiodev.isNull()) + audiodev = "/dev/audio"; + audiodev += "ctl"; + _udi = audiodev; // use device name as UDI. Doesn't matter as we only use it for hotplugging/unplugging. + if ( ( fd = ::open( audiodev.toAscii().data(), O_RDWR ) ) < 0 ) + { + if ( errno == EACCES ) + return Mixer::ERR_PERM; + else + return Mixer::ERR_OPEN; + } + else + { + // + // Mixer is open. Now define all of the mix devices. + // + int devmask, recmask, i_recsrc, stereodevs; + // Mixer is open. Now define properties + if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) + return Mixer::ERR_READ; + if (ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask) == -1) + return Mixer::ERR_READ; + if (ioctl(fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1) + return Mixer::ERR_READ; + if (ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs) == -1) + return Mixer::ERR_READ; + + for ( int idx = 0; idx < numDevs; idx++ ) + { + Volume::ChannelMask chnmask = Volume::MLEFT; + if ( stereodevs & ( 1 << idx ) ) chnmask = (Volume::ChannelMask)(chnmask|Volume::MRIGHT); + + Volume playbackVol( 100, 1, true, false ); + QString id; + id.setNum(idx); + MixDevice* md = new MixDevice( _mixer, id, + QString(MixerDevNames[idx]), MixerChannelTypes[idx]); + md->addPlaybackVolume(playbackVol); + // Tutorial: Howto add a simple capture switch + if ( recmask & ( 1 << idx ) ) { + // can be captured => add capture volume, with no capture volume + chnmask = Volume::MNONE; + Volume captureVol( 100, 1, true, true ); + md->addCaptureVolume(captureVol); + } + m_mixDevices.append( md->addToPool() ); + } + + registerCard("SUN Audio Mixer"); + m_isOpen = true; + + return 0; + } +} + +//====================================================================== +// FUNCTION : Mixer::close +// DESCRIPTION : Close the hardware driver. +//====================================================================== +int Mixer_SUN::close() +{ + _pollingTimer->stop(); + m_isOpen = false; + int l_i_ret = ::close( fd ); + closeCommon(); + return l_i_ret; +} + +//====================================================================== +// FUNCTION : Mixer::errorText +// DESCRIPTION : Convert an error code enum to a text string. +//====================================================================== +QString Mixer_SUN::errorText( int mixer_error ) +{ + QString errmsg; + switch (mixer_error) + { + case Mixer::ERR_PERM: + errmsg = i18n( + "kmix: You do not have permission to access the mixer device.\n" + "Ask your system administrator to fix /dev/audioctl to allow access." + ); + break; + default: + errmsg = Mixer_Backend::errorText( mixer_error ); + } + return errmsg; +} + + +//====================================================================== +// FUNCTION : Mixer::readVolumeFromHW +// DESCRIPTION : Read the audio information from the driver. +//====================================================================== +int Mixer_SUN::readVolumeFromHW( const QString& id, shared_ptr md ) +{ + audio_info_t audioinfo; + int devnum = id2num(id); + uint_t devMask = MixerSunPortMasks[devnum]; + + Volume& volume = md->playbackVolume(); + // + // Read the current audio information from the driver + // + if ( ioctl( fd, AUDIO_GETINFO, &audioinfo ) < 0 ) + { + return( Mixer::ERR_READ ); + } + else + { + // + // Extract the appropriate fields based on the requested device + // + switch ( devnum ) + { + case MIXERDEV_MASTER_VOLUME : + //volume.setSwitchActivated( audioinfo.output_muted ); + GainBalanceToVolume( audioinfo.play.gain, + audioinfo.play.balance, + volume ); + break; + + case MIXERDEV_RECORD_MONITOR : + md->setMuted(false); + volume.setAllVolumes( audioinfo.monitor_gain ); + break; + + case MIXERDEV_INTERNAL_SPEAKER : + case MIXERDEV_HEADPHONE : + case MIXERDEV_LINE_OUT : + md->setMuted( (audioinfo.play.port & devMask) ? false : true ); + GainBalanceToVolume( audioinfo.play.gain, + audioinfo.play.balance, + volume ); + break; + + case MIXERDEV_MICROPHONE : + case MIXERDEV_LINE_IN : + case MIXERDEV_CD : + md->setMuted( (audioinfo.record.port & devMask) ? false : true ); + GainBalanceToVolume( audioinfo.record.gain, + audioinfo.record.balance, + volume ); + break; + + default : + return Mixer::ERR_READ; + } + return 0; + } +} + +//====================================================================== +// FUNCTION : Mixer::writeVolumeToHW +// DESCRIPTION : Write the specified audio settings to the hardware. +//====================================================================== +int Mixer_SUN::writeVolumeToHW( const QString& id, shared_ptr md ) +{ + uint_t gain; + uchar_t balance; + uchar_t mute; + + Volume& volume = md->playbackVolume(); + int devnum = id2num(id); + // + // Convert the Volume(left vol, right vol) to the Gain/Balance Sun uses + // + VolumeToGainBalance( volume, gain, balance ); + mute = md->isMuted() ? 1 : 0; + + // + // Read the current audio settings from the hardware + // + audio_info_t audioinfo; + if ( ioctl( fd, AUDIO_GETINFO, &audioinfo ) < 0 ) + { + return( Mixer::ERR_READ ); + } + + // + // Now, based on the devnum that we are writing to, update the appropriate + // volume field and twiddle the appropriate bitmask to enable/mute the + // device as necessary. + // + switch ( devnum ) + { + case MIXERDEV_MASTER_VOLUME : + audioinfo.play.gain = gain; + audioinfo.play.balance = balance; + audioinfo.output_muted = mute; + break; + + case MIXERDEV_RECORD_MONITOR : + audioinfo.monitor_gain = gain; + // no mute or balance for record monitor + break; + + case MIXERDEV_INTERNAL_SPEAKER : + case MIXERDEV_HEADPHONE : + case MIXERDEV_LINE_OUT : + audioinfo.play.gain = gain; + audioinfo.play.balance = balance; + if ( mute ) + audioinfo.play.port &= ~MixerSunPortMasks[devnum]; + else + audioinfo.play.port |= MixerSunPortMasks[devnum]; + break; + + case MIXERDEV_MICROPHONE : + case MIXERDEV_LINE_IN : + case MIXERDEV_CD : + audioinfo.record.gain = gain; + audioinfo.record.balance = balance; + if ( mute ) + audioinfo.record.port &= ~MixerSunPortMasks[devnum]; + else + audioinfo.record.port |= MixerSunPortMasks[devnum]; + break; + + default : + return Mixer::ERR_READ; + } + + // + // Now that we've updated the audioinfo struct, write it back to the hardware + // + if ( ioctl( fd, AUDIO_SETINFO, &audioinfo ) < 0 ) + { + return( Mixer::ERR_WRITE ); + } + else + { + return 0; + } +} + + +//====================================================================== +// FUNCTION : Mixer::isRecsrcHW +// DESCRIPTION : Returns true if the specified device is a record source. +//====================================================================== + +// isRecsrcHW() is not supported any longer. You must set the state in the MixDevice in readVolumeFromHW() or writeVolumeFromHW() appropriately + +//bool Mixer_SUN::isRecsrcHW( isRecsrcHW(const QString& id ) +//{ +// int devnum = id2num(id); +// switch ( devnum ) +// { +// case MIXERDEV_MICROPHONE : +// case MIXERDEV_LINE_IN : +// case MIXERDEV_CD : +// return true; +// +// default : +// return false; +// } +//} + +//====================================================================== +// FUNCTION : Mixer::VolumeToGainBalance +// DESCRIPTION : Converts a Volume(left vol + right vol) into the +// Gain/Balance values used by Sun. +//====================================================================== +void Mixer_SUN::VolumeToGainBalance( Volume& volume, uint_t& gain, uchar_t& balance ) +{ + if ( ( volume.count() == 1 ) || + ( volume.getVolume(Volume::LEFT) == volume.getVolume(Volume::RIGHT) ) ) + { + gain = volume.getVolume(Volume::LEFT); + balance = AUDIO_MID_BALANCE; + } + else + { + if ( volume.getVolume(Volume::LEFT) > volume.getVolume(Volume::RIGHT) ) + { + gain = volume.getVolume(Volume::LEFT); + balance = AUDIO_LEFT_BALANCE + + ( AUDIO_MID_BALANCE - AUDIO_LEFT_BALANCE ) * + volume.getVolume(Volume::RIGHT) / volume.getVolume(Volume::LEFT); + } + else + { + gain = volume.getVolume(Volume::RIGHT); + balance = AUDIO_RIGHT_BALANCE - + ( AUDIO_RIGHT_BALANCE - AUDIO_MID_BALANCE ) * + volume.getVolume(Volume::LEFT) / volume.getVolume(Volume::RIGHT); + } + } +} + +//====================================================================== +// FUNCTION : Mixer::GainBalanceToVolume +// DESCRIPTION : Converts Gain/Balance returned by Sun driver to the +// Volume(left vol + right vol) format used by kmix. +//====================================================================== +void Mixer_SUN::GainBalanceToVolume( uint_t& gain, uchar_t& balance, Volume& volume ) +{ + if ( volume.count() == 1 ) + { + volume.setVolume( Volume::LEFT, gain ); + } + else + { + if ( balance <= AUDIO_MID_BALANCE ) + { + volume.setVolume( Volume::LEFT, gain ); + volume.setVolume( Volume::RIGHT, gain * + ( balance - AUDIO_LEFT_BALANCE ) / + ( AUDIO_MID_BALANCE - AUDIO_LEFT_BALANCE ) ); + } + else + { + volume.setVolume( Volume::RIGHT, gain ); + volume.setVolume( Volume::LEFT, gain * + ( AUDIO_RIGHT_BALANCE - balance ) / + ( AUDIO_RIGHT_BALANCE - AUDIO_MID_BALANCE ) ); + } + } +} + +int Mixer_SUN::id2num(const QString& id) +{ + return id.toInt(); +} + +QString SUN_getDriverName() { + return "SUNAudio"; +} + +QString Mixer_SUN::getDriverName() +{ + return "SUNAudio"; +} + diff --git a/kmix/backends/mixer_sun.h b/kmix/backends/mixer_sun.h new file mode 100644 index 00000000..9fe3d587 --- /dev/null +++ b/kmix/backends/mixer_sun.h @@ -0,0 +1,55 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2000 Christian Esken + * 2000 Brian Hanson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MIXER_SUN_H +#define MIXER_SUN_H + +#include + +#include "mixer_backend.h" + +class Mixer_SUN : public Mixer_Backend +{ +public: + Mixer_SUN(Mixer *mixer, int devnum); + virtual ~Mixer_SUN(); + + virtual QString errorText(int mixer_error); + virtual int readVolumeFromHW( const QString& id, shared_ptr ); + virtual int writeVolumeToHW ( const QString& id, shared_ptr ); + + virtual QString getDriverName(); + +protected: + virtual int open(); + virtual int close(); + + void VolumeToGainBalance( Volume& volume, uint_t& gain, uchar_t& balance ); + void GainBalanceToVolume( uint_t& gain, uchar_t& balance, Volume& volume ); + + int fd; + +private: + int id2num(const QString& id); +}; + +#endif diff --git a/kmix/cmake/modules/COPYING-CMAKE-SCRIPTS b/kmix/cmake/modules/COPYING-CMAKE-SCRIPTS new file mode 100644 index 00000000..4b417765 --- /dev/null +++ b/kmix/cmake/modules/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/kmix/cmake/modules/FindCanberra.cmake b/kmix/cmake/modules/FindCanberra.cmake new file mode 100644 index 00000000..7aad1857 --- /dev/null +++ b/kmix/cmake/modules/FindCanberra.cmake @@ -0,0 +1,29 @@ +# - Find libcanberra's libraries and headers. +# This module defines the following variables: +# +# CANBERRA_FOUND - true if libcanberra was found +# CANBERRA_LIBRARIES - libcanberra libraries to link against +# CANBERRA_INCLUDE_DIRS - include path for libcanberra +# +# Copyright (c) 2012 Raphael Kubo da Costa +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +find_package(PkgConfig) +pkg_check_modules(PC_CANBERRA libcanberra) + +find_library(CANBERRA_LIBRARIES + NAMES canberra + HINTS ${PC_CANBERRA_LIBRARY_DIRS} ${PC_CANBERRA_LIBDIR} +) + +find_path(CANBERRA_INCLUDE_DIRS + NAMES canberra.h + HINTS ${PC_CANBERRA_INCLUDE_DIRS} ${PC_CANBERRA_INCLUDEDIR} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Canberra REQUIRED_VARS CANBERRA_LIBRARIES CANBERRA_INCLUDE_DIRS) + +mark_as_advanced(CANBERRA_LIBRARIES CANBERRA_INCLUDE_DIRS) diff --git a/kmix/colorwidget.ui b/kmix/colorwidget.ui new file mode 100644 index 00000000..5aabf8de --- /dev/null +++ b/kmix/colorwidget.ui @@ -0,0 +1,271 @@ + + Stefan Schimanski <1Stein@gmx.de> + ColorWidget + + + + 0 + 0 + 272 + 305 + + + + + 0 + + + + + &Use custom colors + + + + + + + true + + + Active + + + + 0 + + + + + true + + + + + + + + + + true + + + &Silent: + + + activeLow + + + false + + + + + + + true + + + + + + + + + + true + + + + 1 + 0 + 1 + 0 + + + + + + + + + + + true + + + &Loud: + + + activeHigh + + + false + + + + + + + true + + + &Background: + + + activeBack + + + false + + + + + + + + + + true + + + Muted + + + + 0 + + + + + true + + + Lou&d: + + + mutedHigh + + + false + + + + + + + true + + + Backgrou&nd: + + + mutedBack + + + false + + + + + + + true + + + Silen&t: + + + mutedLow + + + false + + + + + + + true + + + + 1 + 0 + 1 + 0 + + + + + + + + + + + true + + + + + + + + + + true + + + + + + + + + + + + + + 20 + 20 + + + + Expanding + + + Vertical + + + + + + + customColors + activeHigh + activeLow + activeBack + mutedHigh + mutedLow + mutedBack + + + klocale.h + kseparator.h + + + + customColors + toggled(bool) + activeColors + setEnabled(bool) + + + customColors + toggled(bool) + mutedColors + setEnabled(bool) + + + diff --git a/kmix/config.h.cmake b/kmix/config.h.cmake new file mode 100644 index 00000000..acd9a9c2 --- /dev/null +++ b/kmix/config.h.cmake @@ -0,0 +1,23 @@ +/* config.h. Generated by cmake from config.h.cmake */ +/* Don't add anything new here!!! Use per-directory generated files. */ + +/* Define if you ogg/vorbis installed */ +#cmakedefine HAVE_VORBIS ${OGGVORBIS_VERSION} + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_MACHINE_ENDIAN_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_ENDIAN_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ENDIAN_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H 1 + +/* Define to 1 if exists and defines std::tr1::shared_ptr. */ +#cmakedefine HAVE_STD_TR1_SHARED_PTR 1 + +/* Define to 1 if exists and defines std::shared_ptr. */ +#cmakedefine HAVE_STD_SHARED_PTR 1 diff --git a/kmix/core/ControlManager.cpp b/kmix/core/ControlManager.cpp new file mode 100644 index 00000000..d181db27 --- /dev/null +++ b/kmix/core/ControlManager.cpp @@ -0,0 +1,203 @@ +/* + KMix -- KDE's full featured mini mixer + Copyright (C) 2012 Christian Esken + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ControlManager.h" +#include "core/GlobalConfig.h" + +#include + +#include + + +ControlManager ControlManager::instanceSingleton; + +ControlManager& ControlManager::instance() +{ + return instanceSingleton; +} + +ControlManager::ControlManager() +{ + listenersChanged = false; +} + +/** + * Announce a change for one or all mixers. + * + * @param mixerId The mixerId. Use an empty QString() to announce a change for all mixers + * @param changeType A bit array of ControlChangeType flags + * @param sourceId Only for logging + * + */ +void ControlManager::announce(QString mixerId, ControlChangeType::Type changeType, QString sourceId) +{ + + bool listenersModified = false; + QSet processedListeners; + do + { + listenersModified = false; + QList::iterator it; + for (it = listeners.begin(); it != listeners.end(); ++it) + { + Listener& listener = *it; + if ( &listener == 0 ) + { + kWarning() << "null Listener detected ... skipping"; + continue; + } + + bool mixerIsOfInterest = listener.getMixerId().isEmpty() || mixerId.isEmpty() + || listener.getMixerId() == mixerId; + + bool listenerAlreadyProcesed = processedListeners.contains(&listener); + if ( listenerAlreadyProcesed ) + { + if (GlobalConfig::instance().data.debugControlManager) + kDebug() << "Skipping already processed listener"; + continue; + } + if (mixerIsOfInterest && listener.getChangeType() == changeType) + { + bool success = QMetaObject::invokeMethod(listener.getTarget(), "controlsChange", Qt::DirectConnection, + Q_ARG(int, changeType)); + if (GlobalConfig::instance().data.debugControlManager) + { + kDebug() << "Listener " << listener.getSourceId() <<" is interested in " << mixerId + << ", " << ControlChangeType::toString(changeType); + } + + if (!success) + { + kError() << "Listener Failed to send to " << listener.getTarget()->metaObject()->className(); + } + processedListeners.insert(&listener); + if (listenersChanged) + { + // The invokeMethod() above has changed the listeners => my Iterator is invalid => restart loop + if (GlobalConfig::instance().data.debugControlManager) + kDebug() << "Listeners modified => restart loop"; + listenersChanged = false; + listenersModified = true; + break; // break inner loop => restart via outer loop + } + + } + } + } + while ( listenersModified); + + if (GlobalConfig::instance().data.debugControlManager) + { + kDebug() + << "Announcing " << ControlChangeType::toString(changeType) << " for " + << (mixerId.isEmpty() ? "all cards" : mixerId) << " by " << sourceId; + } +} + +/** + * Adds a listener for the given mixerId and changeType. + * Listeners are informed about all corresponding changes via a signal. + * Listeners are not informed about changes that originate from oneself (according to sourceId). + * + * @param mixerId The id of the Mixer you are interested in + * @param changetType The changeType of interest + * @param target The QObject, where the notification signal is sent to. It must implement the SLOT controlChanged(QString mixerId, ControlChangeType::Type changeType). + * @param sourceId Only for logging + */ +void ControlManager::addListener(QString mixerId, ControlChangeType::Type changeType, QObject* target, QString sourceId) +{ + if (GlobalConfig::instance().data.debugControlManager) + { + kDebug() + << "Listening to " << ControlChangeType::toString(changeType) << " for " + << (mixerId.isEmpty() ? "all cards" : mixerId) << " by " << sourceId << ". Announcements are sent to " + << target; + } + + for ( ControlChangeType::Type ct = ControlChangeType::TypeFirst; ct != ControlChangeType::TypeLast; ct = (ControlChangeType::Type)(ct << 1)) + { + if ( changeType & ct ) + { + // Add all listeners. + Listener listener = Listener(mixerId, ct, target, sourceId); + listeners.append(listener); + listenersChanged = true; + } + } + if (GlobalConfig::instance().data.debugControlManager) + { + kDebug() + << "We now have" << listeners.size() << "listeners"; + } +} + +/** + * Removes all listeners of the given target. + * @param target The QObject that was used to register via addListener() + */ +void ControlManager::removeListener(QObject* target) +{ + ControlManager::instance().removeListener(target, target->metaObject()->className()); +} + +/** + * Removes all listeners of the given target. + * @param target The QObject that was used to register via addListener() + * @param sourceId Optional: Only for logging + */ +void ControlManager::removeListener(QObject* target, QString sourceId) +{ + QMutableListIterator it(listeners); + while ( it.hasNext()) + { + Listener& listener = it.next(); + if (listener.getTarget() == target) + { + if (GlobalConfig::instance().data.debugControlManager) + kDebug() + << "Stop Listening of " << listener.getSourceId() << " requested by " << sourceId << " from " << target; + it.remove(); + // Hint: As we have actual objects no explicit delete is needed + listenersChanged = true; + } + } +} + +void ControlManager::warnUnexpectedChangeType(ControlChangeType::Type type, QObject *obj) +{ + kWarning() << "Unexpected type " << type << " received by " << obj->metaObject()->className(); +} + +void ControlManager::shutdownNow() +{ + if (GlobalConfig::instance().data.debugControlManager) + kDebug() << "Shutting down ControlManager"; + QList::iterator it; + for (it = listeners.begin(); it != listeners.end(); ++it) + { + Listener& listener = *it; + if (GlobalConfig::instance().data.debugControlManager) + kDebug() + << "Listener still connected. Closing it. source=" << listener.getSourceId() << "listener=" + << listener.getTarget()->metaObject()->className(); + } +} + +#include "ControlManager.moc" diff --git a/kmix/core/ControlManager.h b/kmix/core/ControlManager.h new file mode 100644 index 00000000..e5139e1f --- /dev/null +++ b/kmix/core/ControlManager.h @@ -0,0 +1,149 @@ +/* + + Copyright (C) 2012 Christian Esken + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef CONTROLMANAGER_H +#define CONTROLMANAGER_H + +#include +#include + + +// typedef int ControlChangeType; +// enum ControlChangeType { +// Volume, // Volume or Switch change (Mute or Capture Switch, or Enum) +// ControlList, // Control added or deleted +// GUI // Visual changes, like "split channel" OR "show labels" +// }; + +class ControlChangeType: QObject +{ +Q_OBJECT + +public: + enum Type + { + None = 0, // + TypeFirst = 1, + Volume = 1, // Volume or Switch change (Mute or Capture Switch, or Enum) + ControlList = 2, // Control added or deleted + GUI = 4, // Visual changes, like "split channel" OR "show labels" + MasterChanged = 8 // Master (global or local) has changed + , + TypeLast = 16 + }; + + static QString toString(Type changeType) + { + QString ret; + bool needsSeparator = false; + for (ControlChangeType::Type ct = ControlChangeType::TypeFirst; ct != ControlChangeType::TypeLast; ct = + (ControlChangeType::Type) (ct << 1)) + { + if (changeType & ct) + { + if (needsSeparator) + ret.append('|'); + switch (ct) + { + case Volume: + ret.append("Volume"); + break; + case ControlList: + ret.append("ControlList"); + break; + case GUI: + ret.append("GUI"); + break; + case MasterChanged: + ret.append("MasterChange"); + break; + default: + ret.append("Invalid"); + break; + } + + needsSeparator = true; + } + } + + return ret; + + }; + + static ControlChangeType::Type fromInt(int type) + { + switch ( type ) + { + case 1: return Volume; + case 2: return ControlList; + case 4: return GUI; + case 8: return MasterChanged; + default: return None; + } + }; + +}; + +class Listener +{ +public: + Listener(const QString mixerId, ControlChangeType::Type changeType, QObject* target, QString& sourceId) + { + this->mixerId = mixerId; + this->controlChangeType = changeType; + // target is bit dangerous, as it might get deleted. + this->target = target; + this->sourceId = sourceId; + } + + const QString& getMixerId() { return mixerId; }; + ControlChangeType::Type& getChangeType() { return controlChangeType; }; + QObject* getTarget() { return target; }; + const QString& getSourceId() { return sourceId; }; + +private: + QString mixerId; + ControlChangeType::Type controlChangeType; + QObject* target; + QString sourceId; + + +}; + +class ControlManager +{ +public: + static ControlManager& instance(); + + void announce(QString mixerId, ControlChangeType::Type changeType, QString sourceId); + void addListener(QString mixerId, ControlChangeType::Type changeType, QObject* target, QString sourceId); + void removeListener(QObject* target); + void removeListener(QObject* target, QString sourceId); + + static void warnUnexpectedChangeType(ControlChangeType::Type type, QObject *obj); + void shutdownNow(); + +private: + ControlManager(); + static ControlManager instanceSingleton; + QList listeners; + bool listenersChanged; +}; + +#endif // CONTROLMANAGER_H diff --git a/kmix/core/ControlPool.cpp b/kmix/core/ControlPool.cpp new file mode 100644 index 00000000..f2dd17b4 --- /dev/null +++ b/kmix/core/ControlPool.cpp @@ -0,0 +1,83 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (c) The KMix Authors + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "ControlPool.h" + +#include +#include + + +shared_ptr ControlPool::TheEmptyDevice; // = shared_ptr(ControlPool::TheEmptyDevicePtr); + +ControlPool::ControlPool() +{ + pool = new QMap >(); +} + +ControlPool* ControlPool::_instance = 0; + +ControlPool* ControlPool::instance() +{ + if ( _instance == 0 ) + ControlPool::_instance = new ControlPool(); + + return ControlPool::_instance; +} + +/** + * Adds a Control to the pool, and returns it wrapped in QSharedPointer. + * if the Control was already in the Pool, the existing Control is returned + * + * @param key A key, unique over all controls of all cards, e.g. "Master:0@ALSA::Creative_XFI:0" + * @param mixDevice + * @return + */ + +shared_ptr ControlPool::add(const QString& key, MixDevice* md) +{ + shared_ptr controlFromPool(get(key)); + if ( controlFromPool.get() != 0) + { + kDebug() << "----ControlPool already cached key=" << key; + return controlFromPool; + } + + // else: Add the control to the pool + kDebug() << "----ControlPool add key=" << key; + shared_ptr mdShared(md); + pool->insert(key, mdShared); + return mdShared; + +} + + +/** + * Retrieves a Control from the pool as QSharedPointer. If the Control is not + * in the pool, a QSharedPointer that points to null (0) is returned. + * + * @param key + * @return The Control wrapped in QSharedPointer. If not found, a QSharedPointer that points to null. + */ +shared_ptr ControlPool::get(const QString& key) +{ + shared_ptr mixDeviceShared = pool->value(key, TheEmptyDevice); + return mixDeviceShared; +} diff --git a/kmix/core/ControlPool.h b/kmix/core/ControlPool.h new file mode 100644 index 00000000..b045ce06 --- /dev/null +++ b/kmix/core/ControlPool.h @@ -0,0 +1,56 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (c) The KMix Authors + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef CONTROL_POOL_H +#define CONTROL_POOL_H + +#include "config.h" + +#if defined(HAVE_STD_SHARED_PTR) +#include +using std::shared_ptr; +#elif defined(HAVE_STD_TR1_SHARED_PTR) +#include +using std::tr1::shared_ptr; +#endif + +#include "core/mixdevice.h" + +class ControlPool +{ + +public: + static ControlPool* instance(); + shared_ptr add(const QString& key, MixDevice* mixDevice); + shared_ptr get(const QString& key); + + +private: + ControlPool(); + virtual ~ControlPool() {}; + + + QMap > *pool; + static ControlPool* _instance; + static shared_ptr TheEmptyDevice; +}; + +#endif diff --git a/kmix/core/GlobalConfig.cpp b/kmix/core/GlobalConfig.cpp new file mode 100644 index 00000000..bece77fd --- /dev/null +++ b/kmix/core/GlobalConfig.cpp @@ -0,0 +1,110 @@ +/* + KMix -- KDE's full featured mini mixer + Copyright (C) 2012 Christian Esken + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "GlobalConfig.h" + +// instanceObj must be created "late", so we can refer to the correct application config file kmixrc instead of kderc. +GlobalConfig* GlobalConfig::instanceObj; + +GlobalConfig::GlobalConfig() : + KConfigSkeleton() +{ + setCurrentGroup("Global"); + // General + addItemBool("Tickmarks", data.showTicks, true); + addItemBool("Labels", data.showLabels, true); + addItemBool("VolumeOverdrive", data.volumeOverdrive, false); + addItemBool("VolumeFeedback", data.beepOnVolumeChange, true); + ItemString* is = addItemString("Orientation", data.orientationMainGUIString, "Vertical"); + kDebug() << is->name() << is->value(); + addItemString("Orientation.TrayPopup", data.orientationTrayPopupString, QLatin1String("Vertical")); + + // Sound Menu + addItemBool("showOSD", data.showOSD, true); + addItemBool("AllowDocking", data.showDockWidget, true); + +// addItemBool("TrayVolumeControl", data.trayVolumePopupEnabled, true); // removed support in KDE4.13. Always active! + + // Startup + addItemBool("AutoStart", data.allowAutostart, true); + addItemBool("VolumeFeedback", data.volumeFeedback, true); + addItemBool("startkdeRestore", data.startkdeRestore, true); + + // Debug options: Not in dialog + addItemBool("Debug.ControlManager", data.debugControlManager, false); + addItemBool("Debug.GUI", data.debugGUI, false); + addItemBool("Debug.Volume", data.debugVolume, false); + + readConfig(); +} + +// --- Special READ/WRITE ---------------------------------------------------------------------------------------- +void GlobalConfig::usrReadConfig() +{ +// kDebug() << "or=" << data.orientationMainGUIString; + // Convert orientation strings to Qt::Orientation + data.convertOrientation(); +} + +//void GlobalConfig::usrWriteConfig() +//{ +// // TODO: Is this any good? When is usrWriteConfig() called? Hopefully BEFORE actually writing. Otherwise +// // I must move this code to #setToplevelOrientation() and #setTraypopupOrientation(). +//} + +Qt::Orientation GlobalConfigData::getToplevelOrientation() +{ + return toplevelOrientation; +} + +Qt::Orientation GlobalConfigData::getTraypopupOrientation() +{ + return traypopupOrientation; +} + +/** + * Converts the orientation strings to Qt::Orientation + */ +void GlobalConfigData::convertOrientation() +{ + toplevelOrientation = stringToOrientation(orientationMainGUIString); + traypopupOrientation = stringToOrientation(orientationTrayPopupString); +} + +void GlobalConfigData::setToplevelOrientation(Qt::Orientation orientation) +{ + toplevelOrientation = orientation; + orientationMainGUIString = orientationToString(toplevelOrientation); +} + +void GlobalConfigData::setTraypopupOrientation(Qt::Orientation orientation) +{ + traypopupOrientation = orientation; + orientationTrayPopupString = orientationToString(traypopupOrientation); +} + +Qt::Orientation GlobalConfigData::stringToOrientation(QString& orientationString) +{ + return orientationString == "Horizontal" ? Qt::Horizontal : Qt::Vertical; +} + +QString GlobalConfigData::orientationToString(Qt::Orientation orientation) +{ + return orientation == Qt::Horizontal ? "Horizontal" : "Vertical"; +} diff --git a/kmix/core/GlobalConfig.h b/kmix/core/GlobalConfig.h new file mode 100644 index 00000000..c561f3eb --- /dev/null +++ b/kmix/core/GlobalConfig.h @@ -0,0 +1,126 @@ +/* + KMix -- KDE's full featured mini mixer + Copyright (C) 2012 Christian Esken + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef GLOBALCONFIG_H +#define GLOBALCONFIG_H + +#include +#include + +#include +#include + +class GlobalConfigData +{ + friend class GlobalConfig; + +public: + // Hint: We are using the standard 1-arg constructor as copy constructor + + bool showTicks; + bool showLabels; + bool showOSD; + + bool volumeFeedback; + + bool volumeOverdrive; // whether more than recommended volume (typically 0dB) is allowed + bool beepOnVolumeChange; + + // Startup + bool allowAutostart; + bool showDockWidget; + bool startkdeRestore; + + // Debug options + bool debugControlManager; + bool debugGUI; + bool debugVolume; + + Qt::Orientation getToplevelOrientation(); + Qt::Orientation getTraypopupOrientation(); + + void setToplevelOrientation(Qt::Orientation orientation); + void setTraypopupOrientation(Qt::Orientation orientation); + +private: + QString orientationMainGUIString; + QString orientationTrayPopupString; + // The following two values are only converted/cached date from the former fields. + Qt::Orientation toplevelOrientation; + Qt::Orientation traypopupOrientation; + + void convertOrientation(); + Qt::Orientation stringToOrientation(QString& orientationString); + QString orientationToString(Qt::Orientation orientation); + +}; + +class GlobalConfig: public KConfigSkeleton +{ +private: + static GlobalConfig* instanceObj; + +public: + static GlobalConfig& instance() + { + return *instanceObj; + } + ; + + /** + * Call this init method when your app core is properly initialized. + * It is very important that KGlobal is initialized then. Otherwise KGlobal::config() could return a reference to + * the "kderc" config instead of the actual application config "kmixrc" or "kmixctrlrc". + * + */ + static void init() + { + instanceObj = new GlobalConfig(); + } + ; + + GlobalConfigData data; + void setMixersForSoundmenu(QSet mixersForSoundmenu) + { + this->mixersForSoundmenu = mixersForSoundmenu; + } + ; + QSet getMixersForSoundmenu() + { + return mixersForSoundmenu; + } + ; + +protected: + QSet mixersForSoundmenu; + +private: + + GlobalConfig(); + /** + * @Override + */ + virtual void usrReadConfig(); + /** + * @Override + */ +// virtual void usrWriteConfig(); +}; + +#endif // GLOBALCONFIG_H diff --git a/kmix/core/MasterControl.cpp b/kmix/core/MasterControl.cpp new file mode 100644 index 00000000..c6e446d1 --- /dev/null +++ b/kmix/core/MasterControl.cpp @@ -0,0 +1,41 @@ +/* + * MasterControl.cpp + * + * Created on: 02.01.2011 + * Author: kde + */ + +#include "MasterControl.h" + +MasterControl::MasterControl() +{ +} + +MasterControl::~MasterControl() +{ +} + +QString MasterControl::getCard() const +{ + return card; +} + +QString MasterControl::getControl() const +{ + return control; +} + +void MasterControl::set(QString card, QString control) +{ + this->card = card; + this->control = control; +} + +bool MasterControl::isValid() +{ + if ( control.isEmpty() || card.isEmpty() ) + return false; + + return true; +} + diff --git a/kmix/core/MasterControl.h b/kmix/core/MasterControl.h new file mode 100644 index 00000000..16472fff --- /dev/null +++ b/kmix/core/MasterControl.h @@ -0,0 +1,40 @@ +/* + * MasterControl.h + * + * Created on: 02.01.2011 + * Author: kde + */ + +#ifndef MASTERCONTROL_H_ +#define MASTERCONTROL_H_ + +#include "config.h" + +#if defined(HAVE_STD_SHARED_PTR) +#include +using std::shared_ptr; +#elif defined(HAVE_STD_TR1_SHARED_PTR) +#include +using std::tr1::shared_ptr; +#endif + +#include + +class MasterControl +{ +public: + MasterControl(); + virtual ~MasterControl(); + QString getCard() const; + QString getControl() const; + void set(QString card, QString control); + + bool isValid(); + +private: + QString card; + QString control; + +}; + +#endif /* MASTERCONTROL_H_ */ diff --git a/kmix/core/MediaController.cpp b/kmix/core/MediaController.cpp new file mode 100644 index 00000000..9af793c2 --- /dev/null +++ b/kmix/core/MediaController.cpp @@ -0,0 +1,88 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 1996-2014 The KMix authors. Maintainer: Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +/* + * MediaController.cpp + * + * Created on: 17.12.2013 + * Author: chris + */ + +#include "core/MediaController.h" + +//#include +//#include + +#include + +MediaController::MediaController(QString controlId) : + id(controlId), playState(PlayUnknown) +{ + mediaPlayControl = false; + mediaNextControl = false; + mediaPrevControl = false; + + /* + { + // Phonon connection test code + QList devs = Phonon::BackendCapabilities::availableAudioOutputDevices(); + + if (devs.isEmpty()) + return; + + Phonon::AudioOutputDevice& dev = devs[0]; + + QList props = dev.propertyNames(); + kDebug() << "desc=" << dev.description() << ", name=" << dev.name() << ", props="; + QByteArray prop; + int i=0; + foreach (prop, props) + { + kDebug() << "#" << i << ": "<< prop; + ++i; + } + } + */ +} + +MediaController::~MediaController() +{ +} + +/** + * Returns whether this device has at least one media player control. + * @return + */ +bool MediaController::hasControls() +{ + return mediaPlayControl | mediaNextControl | mediaPrevControl; +} + +MediaController::PlayState MediaController::getPlayState() +{ + return playState; +} + +void MediaController::setPlayState(PlayState playState) +{ + this->playState = playState; +} diff --git a/kmix/core/MediaController.h b/kmix/core/MediaController.h new file mode 100644 index 00000000..3f719132 --- /dev/null +++ b/kmix/core/MediaController.h @@ -0,0 +1,69 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 1996-2014 The KMix authors. Maintainer: Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* + * MediaController.h + * + * Created on: 17.12.2013 + * Author: chris + */ + +#ifndef MEDIACONTROLLER_H_ +#define MEDIACONTROLLER_H_ + +#include + +/** + * A MediaController controls exactly one Media Player. You can think of it as a single control, like PCM. + */ +class MediaController +{ +public: + enum PlayState { PlayPaused, PlayPlaying, PlayStopped, PlayUnknown }; + + MediaController(QString); + virtual ~MediaController(); + + void addMediaPlayControl() { mediaPlayControl = true; }; + void addMediaNextControl() { mediaNextControl = true; }; + void addMediaPrevControl() { mediaPrevControl = true; }; + bool hasMediaPlayControl() { return mediaPlayControl; }; + bool hasMediaNextControl() { return mediaNextControl; }; + bool hasMediaPrevControl() { return mediaPrevControl; }; + bool hasControls(); + + + MediaController::PlayState getPlayState(); + void setPlayState(PlayState playState); + + bool canSkipNext(); + bool canSkipPrevious(); + +private: + QString id; + PlayState playState; + + bool mediaPlayControl; + bool mediaNextControl; + bool mediaPrevControl; +}; + +#endif /* MEDIACONTROLLER_H_ */ diff --git a/kmix/core/kmixdevicemanager.cpp b/kmix/core/kmixdevicemanager.cpp new file mode 100644 index 00000000..79f3f3d7 --- /dev/null +++ b/kmix/core/kmixdevicemanager.cpp @@ -0,0 +1,204 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2006-2007 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "core/kmixdevicemanager.h" +#include + +#include + +#include +#include + + +#include +#include +#include + +KMixDeviceManager* KMixDeviceManager::s_KMixDeviceManager = 0; + +KMixDeviceManager::KMixDeviceManager() +{ +} + +KMixDeviceManager::~KMixDeviceManager() +{ +} + +KMixDeviceManager* KMixDeviceManager::instance() +{ + if ( s_KMixDeviceManager == 0 ) { + s_KMixDeviceManager = new KMixDeviceManager(); + } + return s_KMixDeviceManager; +} + +void KMixDeviceManager::initHotplug() +{ + connect (Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)), SLOT(pluggedSlot(QString)) ); + connect (Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)), SLOT(unpluggedSlot(QString)) ); +} + +QString KMixDeviceManager::getUDI_ALSA(int num) +{ + QList dl = Solid::Device::listFromType(Solid::DeviceInterface::AudioInterface); + + QString numString; + numString.setNum(num); + bool found = false; + QString udi; + QString devHandle; + foreach ( const Solid::Device &device, dl ) + { + // std::cout << "Coldplug udi = '" << device.udi().toUtf8().data() << "'\n"; + // LEAK audiohw leaks, but Solid does not document whether it is its own or "my" Object. + const Solid::AudioInterface *audiohw = device.as(); + if (audiohw != 0) + { + if (audiohw->deviceType() & ( Solid::AudioInterface::AudioControl)) + { + switch (audiohw->driver()) + { + case Solid::AudioInterface::Alsa: + devHandle = audiohw->driverHandle().toList().first().toString(); + if ( numString == devHandle ) + { + found = true; + udi = device.udi(); + } + break; + default: + break; + } // driver type + } // is an audio control + + // If I delete audiohw, kmix crashes. If I do not, there is a definite leak according to valgrind: +// ==24561== 4,958 (1,200 direct, 3,758 indirect) bytes in 25 blocks are definitely lost in loss record 1,882 of 1,918 +// ==24561== at 0x4C27D49: operator new(unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +// ==24561== by 0x5665462: ??? (in /usr/lib64/libsolid.so.4.11.3) +// ==24561== by 0x565EC57: ??? (in /usr/lib64/libsolid.so.4.11.3) +// ==24561== by 0x563610B: Solid::Device::asDeviceInterface(Solid::DeviceInterface::Type const&) const (in /usr/lib64/libsolid.so.4.11.3) +// ==24561== by 0x4EB7DA6: Solid::AudioInterface const* Solid::Device::as() const (device.h:254) +// ==24561== by 0x4EB6F6F: KMixDeviceManager::getUDI_ALSA(int) (kmixdevicemanager.cpp:70) +// ==24561== by 0x4E6C809: Mixer_ALSA::open() (mixer_alsa9.cpp:139) +// ==24561== by 0x4E6334E: Mixer_Backend::openIfValid() (mixer_backend.cpp:84) +// ==24561== by 0x4EBD4F9: Mixer::openIfValid(int) (mixer.cpp:268) +// ==24561== by 0x4EB5F10: MixerToolBox::possiblyAddMixer(Mixer*) (mixertoolbox.cpp:310) +// ==24561== by 0x4EB57AE: MixerToolBox::initMixerInternal(MixerToolBox::MultiDriverMode, QList, QString&) (mixertoolbox.cpp:165) +// ==24561== by 0x4EB52B6: MixerToolBox::initMixer(MixerToolBox::MultiDriverMode, QList, QString&) (mixertoolbox.cpp:85) +// ==24561== by 0x4EB5267: MixerToolBox::initMixer(bool, QList, QString&) (mixertoolbox.cpp:80) +// ==24561== by 0x4E7ED75: KMixWindow::KMixWindow(bool) (kmix.cpp:96) +// ==24561== by 0x4E8A7CF: KMixApp::newInstance() (KMixApp.cpp:112) +// ==24561== by 0x5B30A7E: KUniqueApplication::Private::_k_newInstanceNoFork() (in /usr/lib64/libkdeui.so.5.11.3) +// ==24561== by 0x774A11D: QObject::event(QEvent*) (in /usr/lib64/libQtCore.so.4.8.5) +// ==24561== by 0x61338A2: QApplication::event(QEvent*) (in /usr/lib64/libQtGui.so.4.8.5) +// ==24561== by 0x612E8AB: QApplicationPrivate::notify_helper(QObject*, QEvent*) (in /usr/lib64/libQtGui.so.4.8.5) +// ==24561== by 0x6134E6F: QApplication::notify(QObject*, QEvent*) (in /usr/lib64/libQtGui.so.4.8.5) + + // The data seems to be "static" device info from Solid, so I will leave it alone. +// delete audiohw; + } + if (found) + { + break; + } + } // foreach + return udi; +} + +QString KMixDeviceManager::getUDI_OSS(const QString& devname) +{ + QList dl = Solid::Device::listFromType(Solid::DeviceInterface::AudioInterface); + + bool found = false; + QString udi; + QString devHandle; + foreach ( const Solid::Device &device, dl ) + { +// std::cout << "Coldplug udi = '" << device.udi().toUtf8().data() << "'\n"; + const Solid::AudioInterface *audiohw = device.as(); + if (audiohw && (audiohw->deviceType() & ( Solid::AudioInterface::AudioControl))) { + switch (audiohw->driver()) { + case Solid::AudioInterface::OpenSoundSystem: + devHandle = audiohw->driverHandle().toString(); +// std::cout << ">>> Coldplugged OSS ='" << devHandle.toUtf8().data() << "'\n"; + if ( devname == devHandle ) { + found = true; +// std::cout << ">>> Match!!! Coldplugged OSS ='" << devHandle.toUtf8().data() << "'\n"; + udi = device.udi(); + } + break; + default: + break; + } // driver type + } // is an audio control + if ( found) break; + } // foreach + return udi; +} + + +void KMixDeviceManager::pluggedSlot(const QString& udi) { +// std::cout << "Plugged udi='" << udi.toUtf8().data() << "'\n"; + Solid::Device device(udi); + Solid::AudioInterface *audiohw = device.as(); + if (audiohw && (audiohw->deviceType() & ( Solid::AudioInterface::AudioControl))) { + QString dev; + QRegExp devExpr( QLatin1String( "^\\D+(\\d+)$" )); + switch (audiohw->driver()) { + case Solid::AudioInterface::Alsa: + if ( _hotpluggingBackend == "ALSA" || _hotpluggingBackend == "*" ) { + dev = audiohw->driverHandle().toList().first().toString(); + emit plugged("ALSA", udi, dev); + } + break; + case Solid::AudioInterface::OpenSoundSystem: + if ( _hotpluggingBackend == "OSS" || _hotpluggingBackend == "*" ) { + dev = audiohw->driverHandle().toString(); + if ( devExpr.indexIn(dev) > -1 ) { + dev = devExpr.cap(1); // Get device number from device name (e.g "/dev/mixer1" or "/dev/sound/mixer2") + } + else { + dev = '0'; // "/dev/mixer" or "/dev/sound/mixer" + } + emit plugged("OSS", udi, dev); + } + break; + default: + kError(67100) << "Plugged UNKNOWN Audio device (ignored)"; + break; + } + } +} + + +void KMixDeviceManager::unpluggedSlot(const QString& udi) { +// std::cout << "Unplugged udi='" << udi.toUtf8().data() << "'\n"; +// Solid::Device device(udi); + // At this point the device has already been unplugged by the user. Solid doesn't know anything about the + // device except the UDI (not even device.as() is possible). Thus I'll forward any + // unplugging action (could e.g. also be HID or mass storage). The receiver of the signal has to deal with it, + // but a simple UDI matching is enough. + emit unplugged(udi); + +} + + +#include "kmixdevicemanager.moc" + diff --git a/kmix/core/kmixdevicemanager.h b/kmix/core/kmixdevicemanager.h new file mode 100644 index 00000000..cc6516a4 --- /dev/null +++ b/kmix/core/kmixdevicemanager.h @@ -0,0 +1,55 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2006-2007 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef kmixdevicemanager_h +#define kmixdevicemanager_h + +#include + +class KMixDeviceManager : public QObject +{ + Q_OBJECT + + public: + static KMixDeviceManager* instance(); + void initHotplug(); + void setHotpluggingBackends(const QString& backendName) { _hotpluggingBackend = backendName; } ; + QString getUDI_ALSA(int num); + QString getUDI_OSS(const QString& devname); + + signals: + void plugged( const char* driverName, const QString& udi, QString& dev); + void unplugged( const QString& udi); + + private: + KMixDeviceManager(); + ~KMixDeviceManager(); + QString _hotpluggingBackend; + + private slots: + void pluggedSlot(const QString&); + void unpluggedSlot(const QString&); + + private: + static KMixDeviceManager* s_KMixDeviceManager; +}; + +#endif + diff --git a/kmix/core/mixdevice.cpp b/kmix/core/mixdevice.cpp new file mode 100644 index 00000000..57354117 --- /dev/null +++ b/kmix/core/mixdevice.cpp @@ -0,0 +1,471 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "core/mixdevice.h" + +#include + +#include +#include + +#include "core/ControlPool.h" +#include "core/mixer.h" +#include "dbus/dbuscontrolwrapper.h" +#include "gui/guiprofile.h" +#include "core/volume.h" + +static const QString channelTypeToIconName( MixDevice::ChannelType type ) +{ + switch (type) { + case MixDevice::AUDIO: + return "mixer-pcm"; + case MixDevice::BASS: + case MixDevice::SURROUND_LFE: // "LFE" SHOULD have an own icon + return "mixer-lfe"; + case MixDevice::CD: + return "mixer-cd"; + case MixDevice::EXTERNAL: + return "mixer-line"; + case MixDevice::MICROPHONE: + return "mixer-microphone"; + case MixDevice::MIDI: + return "mixer-midi"; + case MixDevice::RECMONITOR: + return "mixer-capture"; + case MixDevice::TREBLE: + return "mixer-pcm-default"; + case MixDevice::UNKNOWN: + return "mixer-front"; + case MixDevice::VOLUME: + return "mixer-master"; + case MixDevice::VIDEO: + return "mixer-video"; + case MixDevice::SURROUND: + case MixDevice::SURROUND_BACK: + return "mixer-surround"; + case MixDevice::SURROUND_CENTERFRONT: + case MixDevice::SURROUND_CENTERBACK: + return "mixer-surround-center"; + case MixDevice::HEADPHONE: + return "mixer-headset"; + case MixDevice::DIGITAL: + return "mixer-digital"; + case MixDevice::AC97: + return "mixer-ac97"; + case MixDevice::SPEAKER: + return "mixer-pc-speaker"; + case MixDevice::MICROPHONE_BOOST: + return "mixer-microphone-boost"; + case MixDevice::MICROPHONE_FRONT_BOOST: + return "mixer-microphone-front-boost"; + case MixDevice::MICROPHONE_FRONT: + return "mixer-microphone-front"; + case MixDevice::KMIX_COMPOSITE: + return "mixer-line"; + + case MixDevice::APPLICATION_AMAROK: + return "amarok"; + case MixDevice::APPLICATION_BANSHEE: + return "media-player-banshee"; + case MixDevice::APPLICATION_XMM2: + return "xmms"; + case MixDevice::APPLICATION_TOMAHAWK: + return "tomahawk"; + case MixDevice::APPLICATION_CLEMENTINE: + return "application-x-clementine"; + case MixDevice::APPLICATION_VLC: + return "vlc"; + + case MixDevice::APPLICATION_STREAM: + return "mixer-pcm"; + + } + return "mixer-front"; +} + + +/** + * Constructs a MixDevice. A MixDevice represents one channel or control of + * the mixer hardware. A MixDevice has a type (e.g. PCM), a descriptive name + * (for example "Master" or "Headphone" or "IEC 958 Output"), + * can have a volume level (2 when stereo), can be recordable and muted. + * The ChannelType tells which kind of control the MixDevice is. + */ +MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, ChannelType type ) +{ + init(mixer, id, name, channelTypeToIconName(type), (MixSet*)0); +} + +MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ) +{ + init(mixer, id, name, iconName, moveDestinationMixSet); +} + +void MixDevice::init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ) +{ + _artificial = false; + _applicationStream = false; + _dbusControlWrapper = 0; // will be set in addToPool() + _mixer = mixer; + _id = id; + _enumCurrentId = 0; + + mediaController = new MediaController(_id); + if( name.isEmpty() ) + _name = i18n("unknown"); + else + _name = name; + if ( iconName.isEmpty() ) + _iconName = "mixer-front"; + else + _iconName = iconName; + _moveDestinationMixSet = moveDestinationMixSet; + if ( _id.contains(' ') ) { + // The key is used in the config file. IdbusControlWrappert MUST NOT contain spaces + kError(67100) << "MixDevice::setId(\"" << id << "\") . Invalid key - it must not contain spaces" << endl; + _id.replace(' ', '_'); + } +// kDebug(67100) << "MixDevice::init() _id=" << _id; +} + + +/* + * When a MixDevice shall be finally discarded, you must use this method to free its resources. + * You must not use this MixDevice after calling close(). + *
+ * The necessity stems from a memory leak due to object cycle (MixDevice<->DBusControlWrapper), so the reference + * counting shared_ptr has no chance to clean up. See Bug 309464 for background information. + */ +void MixDevice::close() +{ + delete _dbusControlWrapper; + _dbusControlWrapper = 0; +} + + +MediaController* MixDevice::getMediaController() +{ + return mediaController; +} + + +shared_ptr MixDevice::addToPool() +{ +// kDebug() << "id=" << _mixer->id() << ":" << _id; + shared_ptr thisSharedPtr(this); + //shared_ptr thisSharedPtr = ControlPool::instance()->add(fullyQualifiedId, this); + _dbusControlWrapper = new DBusControlWrapper( thisSharedPtr, dbusPath() ); + return thisSharedPtr; +} + + +/** + * Changes the internal state of this MixDevice. + * It does not commit the change to the hardware. + * + * You might want to call something like m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); after calling this method. + */ +void MixDevice::increaseOrDecreaseVolume(bool decrease, Volume::VolumeTypeFlag volumeType) +{ + bool debugme = false; +// bool debugme = id() == "PCM:0" ; + if (volumeType & Volume::Playback) + { + Volume& volP = playbackVolume(); + long inc = volP.volumeStep(decrease); + + if (debugme) + kDebug() << ( decrease ? "decrease by " : "increase by " ) << inc ; + + if (!decrease && isMuted()) + { + // increasing from muted state: unmute and start with a low volume level + if (debugme) + kDebug() << "set all to " << inc << "muted old=" << isMuted(); + + setMuted(false); + volP.setAllVolumes(inc); + } + else + { + volP.changeAllVolumes(inc); + if (debugme) + kDebug() << (decrease ? "decrease by " : "increase by ") << inc; + } + } + + if (volumeType & Volume::Capture) + { + if (debugme) + kDebug() << "VolumeType=" << volumeType << " c"; + + Volume& volC = captureVolume(); + long inc = volC.volumeStep(decrease); + volC.changeAllVolumes(inc); + } + +} + + + +/** + * Returns the name of the config group + * @param Prefix of the group, e.g. "View_ALSA_USB_01" + * @returns The config group name in the format "prefix.mixerId,controlId" + */ +QString MixDevice::configGroupName(QString prefix) +{ + QString devgrp = QString("%1.%2.%3").arg(prefix).arg(mixer()->id()).arg(id()); + return devgrp; +} + + +QString MixDevice::getFullyQualifiedId() +{ + QString fqId = QString("%1@%2").arg(_id).arg(_mixer->id()); + return fqId; +} + +void MixDevice::addPlaybackVolume(Volume &playbackVol) +{ + // Hint: "_playbackVolume" gets COPIED from "playbackVol", because the copy-constructor actually copies the volume levels. + _playbackVolume = playbackVol; + _playbackVolume.setSwitchType(Volume::PlaybackSwitch); +} + +void MixDevice::addCaptureVolume (Volume &captureVol) +{ + _captureVolume = captureVol; + _captureVolume.setSwitchType(Volume::CaptureSwitch); +} + +void MixDevice::addEnums(QList& ref_enumList) +{ + if ( ref_enumList.count() > 0 ) { + int maxEnumId = ref_enumList.count(); + for (int i=0; i& MixDevice::enumValues() { + return _enumValues; +} + + +const QString& MixDevice::id() const { + return _id; +} + +const QString MixDevice::dbusPath() { + QString controlPath = _id; + controlPath.replace(QRegExp("[^a-zA-Z0-9_]"), "_"); + controlPath.replace(QLatin1String("//"), QLatin1String("/")); + + if ( controlPath.endsWith( '/' ) ) + { + controlPath.chop(1); + } + + return _mixer->dbusPath() + '/' + controlPath; +} + + +bool MixDevice::isMuted() { return ! _playbackVolume.isSwitchActivated(); } +/** + * Returns whether this MixDevice is virtually muted. Only MixDevice objects w/o a physical switch can be muted virtually. + */ +bool MixDevice::isVirtuallyMuted() +{ + return !hasPhysicalMuteSwitch() && isMuted(); +} +void MixDevice::setMuted(bool mute) { _playbackVolume.setSwitch(!mute); } +void MixDevice::toggleMute() { setMuted( _playbackVolume.isSwitchActivated()); } +bool MixDevice::hasMuteSwitch() { return playbackVolume().hasVolume() || playbackVolume().hasSwitch(); } +bool MixDevice::hasPhysicalMuteSwitch() { return playbackVolume().hasSwitch(); } +bool MixDevice::isRecSource() { return ( _captureVolume.hasSwitch() && _captureVolume.isSwitchActivated() ); } +bool MixDevice::isNotRecSource() { return ( _captureVolume.hasSwitch() && !_captureVolume.isSwitchActivated() ); } +void MixDevice::setRecSource(bool value) { _captureVolume.setSwitch( value ); } +bool MixDevice::isEnum() { return ( ! _enumValues.empty() ); } + +int MixDevice::mediaPlay() { return mixer()->mediaPlay(_id); } +int MixDevice::mediaPrev() { return mixer()->mediaPrev(_id); } +int MixDevice::mediaNext() { return mixer()->mediaNext(_id); } + +bool MixDevice::operator==(const MixDevice& other) const +{ + return ( _id == other._id ); +} + +void MixDevice::setControlProfile(ProfControl* control) +{ + _profControl = control; +} + +ProfControl* MixDevice::controlProfile() { + return _profControl; +} + +/** + * This method is currently only called on "kmixctrl --restore" + * + * Normally we have a working _volume object already, which is very important, + * because we need to read the minimum and maximum volume levels. + * (Another solution would be to "equip" volFromConfig with maxInt and minInt values). + */ +bool MixDevice::read( KConfig *config, const QString& grp ) +{ + if ( _mixer->isDynamic() || isArtificial() ) { + kDebug(67100) << "MixDevice::read(): This MixDevice does not permit volume restoration (i.e. because it is handled lower down in the audio stack). Ignoring."; + return false; + } + + QString devgrp = QString("%1.Dev%2").arg(grp).arg(_id); + KConfigGroup cg = config->group( devgrp ); + //kDebug(67100) << "MixDevice::read() of group devgrp=" << devgrp; + + readPlaybackOrCapture(cg, false); + readPlaybackOrCapture(cg, true ); + + bool mute = cg.readEntry("is_muted", false); + setMuted( mute ); + + bool recsrc = cg.readEntry("is_recsrc", false); + setRecSource( recsrc ); + + int enumId = cg.readEntry("enum_id", -1); + if ( enumId != -1 ) { + setEnumId( enumId ); + } + return true; +} + +void MixDevice::readPlaybackOrCapture(const KConfigGroup& config, bool capture) +{ + Volume& volume = capture ? captureVolume() : playbackVolume(); + + for ( Volume::ChannelID chid=Volume::CHIDMIN; chid<= Volume::CHIDMAX; ) + { + QString volstr = getVolString(chid,capture); + if ( config.hasKey(volstr) ) { + volume.setVolume(chid, config.readEntry(volstr, 0)); + } // if saved channel exists + chid = (Volume::ChannelID)( 1 + (int)chid); // ugly + } // for all channels +} + +/** + * called on "kmixctrl --save" and from the GUI's (currently only on exit) + */ +bool MixDevice::write( KConfig *config, const QString& grp ) +{ + if (_mixer->isDynamic() || isArtificial()) { + kDebug(67100) << "MixDevice::write(): This MixDevice does not permit volume saving (i.e. because it is handled lower down in the audio stack). Ignoring."; + return false; + } + + QString devgrp = QString("%1.Dev%2").arg(grp).arg(_id); + KConfigGroup cg = config->group(devgrp); + // kDebug(67100) << "MixDevice::write() of group devgrp=" << devgrp; + + writePlaybackOrCapture(cg, false); + writePlaybackOrCapture(cg, true ); + + cg.writeEntry("is_muted" , isMuted() ); + cg.writeEntry("is_recsrc", isRecSource() ); + cg.writeEntry("name", _name); + if ( isEnum() ) { + cg.writeEntry("enum_id", enumId() ); + } + return true; +} + +void MixDevice::writePlaybackOrCapture(KConfigGroup& config, bool capture) +{ + Volume& volume = capture ? captureVolume() : playbackVolume(); + foreach (VolumeChannel vc, volume.getVolumes() ) + { + config.writeEntry(getVolString(vc.chid,capture) , (int)vc.volume); + } // for all channels + +} + +QString MixDevice::getVolString(Volume::ChannelID chid, bool capture) +{ + QString volstr (Volume::ChannelNameForPersistence[chid]); + if ( capture ) volstr += "Capture"; + return volstr; +} + +/** + * Returns the playback volume level in percent. If the volume is muted, 0 is returned. + * If the given MixDevice contains no playback volume, the capture volume isd used + * instead, and 0 is returned if capturing is disabled for the given MixDevice. + * + * @returns The volume level in percent + */ +int MixDevice::getUserfriendlyVolumeLevel() +{ + MixDevice* md = this; + bool usePlayback = md->playbackVolume().hasVolume(); + Volume& vol = usePlayback ? md->playbackVolume() : md->captureVolume(); + bool isActive = usePlayback ? !md->isMuted() : md->isRecSource(); + int val = isActive ? vol.getAvgVolumePercent(Volume::MALL) : 0; + return val; +} + + +#include "mixdevice.moc" + diff --git a/kmix/core/mixdevice.h b/kmix/core/mixdevice.h new file mode 100644 index 00000000..6696ae06 --- /dev/null +++ b/kmix/core/mixdevice.h @@ -0,0 +1,261 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef MixDevice_h +#define MixDevice_h + +#include "config.h" + +#if defined(HAVE_STD_SHARED_PTR) +#include +using std::shared_ptr; +#elif defined(HAVE_STD_TR1_SHARED_PTR) +#include +using std::tr1::shared_ptr; +#endif + +//KMix +#include "core/MediaController.h" +class Mixer; +class MixSet; +class ProfControl; +#include "core/volume.h" +class DBusControlWrapper; + +// KDE +#include +#include + +// Qt +#include +#include +#include + +/** + * This is the abstraction of a single control of a sound card, e.g. the PCM control. A control + * can contain the 5 following subcontrols: playback-volume, capture-volume, playback-switch, + * capture-switch and enumeration. + + The class is called MixDevice for historical reasons. Today it is just the Synonym for "Control". + + Design hint: In the past I (esken) considered merging the MixDevice and Volume classes. + I finally decided against it, as it seems better to have the MixDevice being the container + for the embedded subcontrol(s). These could be either Volume, Enum or some virtual MixDevice. + */ +class MixDevice : public QObject +{ +Q_OBJECT + +public: + // For each ChannelType a special icon exists + enum ChannelType { AUDIO = 1, + BASS, + CD, + EXTERNAL, + MICROPHONE, + MIDI, + RECMONITOR, + TREBLE, + UNKNOWN, + VOLUME, + VIDEO, + SURROUND, + HEADPHONE, + DIGITAL, + AC97, + SURROUND_BACK, + SURROUND_LFE, + SURROUND_CENTERFRONT, + SURROUND_CENTERBACK, + SPEAKER, + MICROPHONE_BOOST, + MICROPHONE_FRONT_BOOST, + MICROPHONE_FRONT, + KMIX_COMPOSITE, + + APPLICATION_STREAM, + // Some specific applications + APPLICATION_AMAROK, + APPLICATION_BANSHEE, + APPLICATION_XMM2, + APPLICATION_TOMAHAWK, + APPLICATION_CLEMENTINE, + // Hint: VLC still has compatibility problems: + // 2.0 is not detected + // 2.2-nightly has volume issues (total overdrive) + APPLICATION_VLC, + }; + + enum SwitchType { OnOff, Mute, Capture, Activator }; + + /** + * Constructor for a MixDevice. + * After having constructed a MixDevice, you must add it to the ControlPool + * by calling addToPool(). You may then not delete this object. + * + * @par mixer The mixer this control belongs to + * @par id Defines the ID, e.g. used in looking up the keys in kmixrc. Also it is used heavily inside KMix as unique key. + * It is advised to set a nice name, like 'PCM:2', which would mean + * "2nd PCM device of the sound card". The ID's may NOT contain whitespace. + * The Creator (normally the backend) MUST pass distinct ID's for each MixDevices of one card. + * + * Virtual Controls (controls not created by a backend) are prefixed with "KMix::", e.g. + * "KMix::RecSelector:0" + * @par name is the readable name. This one is presented to the user in the GUI + * @par type The control type. It is only used to find an appropriate icon + */ + MixDevice( Mixer* mixer, const QString& id, const QString& name, ChannelType type ); + MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName = "", MixSet* moveDestinationMixSet = 0 ); + ~MixDevice(); + + void close(); + + shared_ptr addToPool(); + + const QString& iconName() const { return _iconName; } + + void addPlaybackVolume(Volume &playbackVol); + void addCaptureVolume (Volume &captureVol); + void addEnums (QList& ref_enumList); + + // Media controls. New for KMix 4.0 + MediaController* getMediaController(); + // TODO move all media player controls to the MediaController class + int mediaPlay(); + int mediaPrev(); + int mediaNext(); + + // Returns a user readable name of the control. + QString readableName() { return _name; } + // Sets a user readable name for the control. + void setReadableName(QString& name) { _name = name; } + + QString configGroupName(QString prefix); + + /** + * Returns an ID of this MixDevice, as passed in the constructor. The Creator (normally the backend) + * MUST ensure that all MixDevices's of one card have unique ID's. + * The ID is used through the whole KMix application (including the config file) for identifying controls. + */ + + const QString& id() const; + QString getFullyQualifiedId(); + + /** + * Returns the DBus path for this MixDevice + */ + const QString dbusPath(); + + // Returns the associated mixer + Mixer* mixer() { return _mixer; } + + // operator==() is used currently only for duplicate detection with QList's contains() method + bool operator==(const MixDevice& other) const; + + // Methods for handling the switches. This methods are useful, because the Switch in the Volume object + // is an abstract concept. It places no interpretation on the meaning of the switch (e.g. does "switch set" mean + // "mute on", or does it mean "playback on", or "Capture active", or ... + virtual bool isMuted(); + virtual bool isVirtuallyMuted(); + virtual void setMuted(bool value); + virtual bool hasMuteSwitch(); + virtual void toggleMute(); + virtual bool isRecSource(); + virtual bool isNotRecSource(); + virtual void setRecSource(bool value); + virtual bool isEnum(); + /** + * Returns whether this is an application stream. + */ + virtual bool isApplicationStream() const { return _applicationStream; }; + /** + * Mark this MixDevice as application stream + */ + void setApplicationStream(bool applicationStream) { _applicationStream = applicationStream; } + + bool isMovable() const + { + return (0 != _moveDestinationMixSet); + } + MixSet *getMoveDestinationMixSet() const + { + return _moveDestinationMixSet; + } + + bool isArtificial() const + { + return _artificial; + } + void setArtificial(bool artificial) + { + _artificial = artificial; + } + + void setControlProfile(ProfControl* control); + ProfControl* controlProfile(); + + virtual Volume& playbackVolume(); + virtual Volume& captureVolume(); + + void setEnumId(int); + unsigned int enumId(); + QList& enumValues(); + + bool hasPhysicalMuteSwitch(); + + bool read( KConfig *config, const QString& grp ); + bool write( KConfig *config, const QString& grp ); + int getUserfriendlyVolumeLevel(); + + void increaseOrDecreaseVolume(bool decrease, Volume::VolumeTypeFlag volumeType); + +protected: + void init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ); + +private: + QString getVolString(Volume::ChannelID chid, bool capture); + Mixer *_mixer; + Volume _playbackVolume; + Volume _captureVolume; + int _enumCurrentId; + QList _enumValues; // A MixDevice, that is an ENUM, has these _enumValues + + DBusControlWrapper *_dbusControlWrapper; + MediaController* mediaController; + + // A virtual control. It will not be saved/restored and/or doesn't get shortcuts + // Actually we discriminate those "virtual" controls in artificial controls and dynamic controls: + // Type Shortcut Restore + // Artificial: yes no Virtual::GlobalMaster or Virtual::CaptureGroup_3 (controls that are constructed artificially from other controls) + // Dynamic : no no Controls that come and go, like Pulse Stream controls + bool _artificial; + MixSet *_moveDestinationMixSet; + QString _iconName; + bool _applicationStream; + + QString _name; // Channel name + QString _id; // Primary key, used as part in config file keys + ProfControl *_profControl; + + void readPlaybackOrCapture(const KConfigGroup& config, bool capture); + void writePlaybackOrCapture(KConfigGroup& config, bool capture); +}; + +#endif diff --git a/kmix/core/mixdevicecomposite.cpp b/kmix/core/mixdevicecomposite.cpp new file mode 100644 index 00000000..dec3a8f6 --- /dev/null +++ b/kmix/core/mixdevicecomposite.cpp @@ -0,0 +1,159 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2010 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "core/mixdevicecomposite.h" + + + +const long MixDeviceComposite::VolMax = 10000; + +MixDeviceComposite::MixDeviceComposite( Mixer* mixer, const QString& id, QList >& mds, const QString& name, ChannelType type ) : + MixDevice( mixer, id, name, type ) // this will use doNotRestore == true +{ + setArtificial(true); + _compositePlaybackVolume = new Volume( MixDeviceComposite::VolMax, 0, true, false); + _compositePlaybackVolume->addVolumeChannel(Volume::LEFT); + _compositePlaybackVolume->addVolumeChannel(Volume::RIGHT); + + QListIterator > it(mds); + while ( it.hasNext()) { + shared_ptr md = it.next(); + _mds.append(md); + } +} + + +MixDeviceComposite::~MixDeviceComposite() +{ + while ( ! _mds.empty() ) { + _mds.removeAt(0); + } + delete _compositePlaybackVolume; +// delete _compositeCaptureVolume; +} + + + +Volume& MixDeviceComposite::playbackVolume() +{ + return *_compositePlaybackVolume; +} + +// Volume& MixDeviceComposite::captureVolume() +// { +// return *_compositeCaptureVolume; +// } + + +void MixDeviceComposite::update() +{ + long volAvg; + volAvg = calculateVolume( Volume::PlaybackVT ); + _compositePlaybackVolume->setAllVolumes(volAvg); + volAvg = calculateVolume( Volume::CaptureVT ); +// _compositeCaptureVolume->setAllVolumes(volAvg); + +} + +long MixDeviceComposite::calculateVolume(Volume::VolumeType vt) +{ + QListIterator > it(_mds); + long volSum = 0; + int volCount = 0; + while ( it.hasNext()) + { + shared_ptr md = it.next(); + + Volume& vol = ( vt == Volume::CaptureVT ) ? md->captureVolume() : md->playbackVolume(); + if (vol.hasVolume() && (vol.maxVolume() != 0) ) { + qreal normalizedVolume = + ( vol.getAvgVolumePercent(Volume::MALL) * MixDeviceComposite::VolMax ) + / vol.maxVolume(); + volSum += normalizedVolume; + ++volCount; + } + } + if ( volCount == 0 ) + return 0; + else + return (volSum/volCount); +} + + +bool MixDeviceComposite::isMuted() +{ + bool isMuted = false; + QListIterator > it(_mds); + while ( it.hasNext()) { + shared_ptr md = it.next(); + isMuted |= md->isMuted(); + if ( isMuted ) break; // Enough. It can't get more true :-) + } + return isMuted; +} + + + +void MixDeviceComposite::setMuted(bool value) +{ + QListIterator > it(_mds); + while ( it.hasNext()) { + shared_ptr md = it.next(); + md->setMuted(value); + } +} + +bool MixDeviceComposite::isRecSource() +{ + bool isRecSource = false; + QListIterator > it(_mds); + while ( it.hasNext()) { + shared_ptr md = it.next(); + isRecSource |= md->isRecSource(); + if ( isRecSource ) break; // Enough. It can't get more true :-) + } + return isRecSource; +} + + +void MixDeviceComposite::setRecSource(bool value) +{ + QListIterator > it(_mds); + while ( it.hasNext()) { + shared_ptr md = it.next(); + md->setRecSource(value); + } +} + + +bool MixDeviceComposite::isEnum() +{ + bool isEnum = true; + QListIterator > it(_mds); + while ( it.hasNext()) { + shared_ptr md = it.next(); + isEnum &= md->isEnum(); + if ( ! isEnum ) break; // Enough. It can't get more false :-) + } + return isEnum; +} + +#include "mixdevicecomposite.moc" diff --git a/kmix/core/mixdevicecomposite.h b/kmix/core/mixdevicecomposite.h new file mode 100644 index 00000000..0180e245 --- /dev/null +++ b/kmix/core/mixdevicecomposite.h @@ -0,0 +1,110 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef MixDeviceComposite_h +#define MixDeviceComposite_h + +//KMix +class Mixer; +class MixSet; +#include "core/mixdevice.h" +#include "core/volume.h" + +// KDE +#include +#include + +// Qt +#include +#include +#include + + +// !!! This SHOULD be subclassed (MixDeviceVolume, MixDeviceEnum). +// The isEnum() works out OK as a workaround, but it is insane +// in the long run. +// Additionally there might be Implementations for virtual MixDevice's, e.g. +// MixDeviceRecselector, MixDeviceCrossfader. +// I am not sure if a MixDeviceBalancing would work out. + +/** + * This is the abstraction of a single control of a sound card, e.g. the PCM control. A control + * can contain the 5 following subcontrols: playback-volume, capture-volume, playback-switch, + * capture-switch and enumeration. + + The class is called MixDevice for historical reasons. Today it is just the Synonym for "Control". + + Design hint: In the past I (esken) considered merging the MixDevice and Volume classes. + I finally decided against it, as it seems better to have the MixDevice being the container + for the embedded subcontrol(s). These could be either Volume, Enum or some virtual MixDevice. + */ +class MixDeviceComposite : public MixDevice +{ +Q_OBJECT + +public: + + /** + * Constructor: + * @par mixer The mixer this control belongs to + * @par id Defines the ID, e.g. used in looking up the keys in kmixrc. Also it is used heavily inside KMix as unique key. + * It is advised to set a nice name, like 'PCM:2', which would mean + * "2nd PCM device of the sound card". The ID's may NOT contain whitespace. + * The Creator (normally the backend) MUST pass distinct ID's for each MixDevices of one card. + * + * Virtual Controls (controls not created by a backend) are prefixed with "KMix::", e.g. + * "KMix::RecSelector:0" + * @par name is the readable name. This one is presented to the user in the GUI + * @par type The control type. It is only used to find an appropriate icon + */ + MixDeviceComposite( Mixer* mixer, const QString& id, QList >& mds, const QString& name, ChannelType type ); +// MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName = "", bool doNotRestore = false, MixSet* moveDestinationMixSet = 0 ); + ~MixDeviceComposite(); + + + + // Methods for handling the switches. This methods are useful, because the Sswitch in the Volume object + // is an abstract concept. It places no interpration on the meaning of the switch (e.g. does "switch set" mean + // "mute on", or does it mean "playback on". + virtual bool isMuted(); + virtual void setMuted(bool value); + virtual bool isRecSource(); + virtual void setRecSource(bool value); + virtual bool isEnum(); + + // Refresh the composite from its components + void update(); + + virtual Volume& playbackVolume(); + //virtual Volume& captureVolume(); + +private: + long calculateVolume(Volume::VolumeType vt); + + Mixer *_mixer; + QList > _mds; + + static const long VolMax; + + Volume* _compositePlaybackVolume; + // Volume* _compositeCaptureVolume; +}; + +#endif diff --git a/kmix/core/mixer.cpp b/kmix/core/mixer.cpp new file mode 100644 index 00000000..321bd70b --- /dev/null +++ b/kmix/core/mixer.cpp @@ -0,0 +1,721 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken - esken@kde.org + * 2002 Helio Chissini de Castro - helio@conectiva.com.br + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "core/mixer.h" + +#include +#include +#include +#include + +#include "backends/mixer_backend.h" +#include "backends/kmix-backends.cpp" +#include "core/ControlManager.h" +#include "core/GlobalConfig.h" +#include "core/volume.h" + +/** + * Some general design hints. Hierachy is Mixer->MixDevice->Volume + */ + +QList Mixer::s_mixers; +MasterControl Mixer::_globalMasterCurrent; +MasterControl Mixer::_globalMasterPreferred; +bool Mixer::m_beepOnVolumeChange = false; + +int Mixer::numDrivers() +{ + MixerFactory *factory = g_mixerFactories; + int num = 0; + while( factory->getMixer!=0 ) + { + num++; + factory++; + } + + return num; +} + +/* + * Returns a reference of the current mixer list. + */ +QList& Mixer::mixers() +{ + return s_mixers; +} + +/** + * Returns whether there is at least one dynamic mixer active. + * @returns true, if at least one dynamic mixer is active + */ +bool Mixer::dynamicBackendsPresent() +{ + foreach ( Mixer* mixer, Mixer::mixers() ) + { + if ( mixer->isDynamic() ) + return true; + } + return false; +} + +bool Mixer::pulseaudioPresent() +{ + foreach ( Mixer* mixer, Mixer::mixers() ) + { + if ( mixer->getDriverName() == "PulseAudio" ) + return true; + } + return false; +} + + +Mixer::Mixer( QString& ref_driverName, int device ) + : m_balance(0), _mixerBackend(0L), m_dynamic(false) +{ + _mixerBackend = 0; + int driverCount = numDrivers(); + for (int driver=0; driver retrieve Mixer factory for that driver + getMixerFunc *f = g_mixerFactories[driver].getMixer; + if( f!=0 ) { + _mixerBackend = f( this, device ); + readSetFromHWforceUpdate(); // enforce an initial update on first readSetFromHW() + } + break; + } + } +} + + + +Mixer::~Mixer() { + // Close the mixer. This might also free memory, depending on the called backend method + close(); + delete _mixerBackend; +} + + +/* + * Find a Mixer. If there is no mixer with the given id, 0 is returned + */ +Mixer* Mixer::findMixer( const QString& mixer_id) +{ + Mixer *mixer = 0; + int mixerCount = Mixer::mixers().count(); + for ( int i=0; iid() == mixer_id ) + { + mixer = (Mixer::mixers())[i]; + break; + } + } + return mixer; +} + + +///** +// * Set the card instance. Usually this will be 1, but if there is +// * more than one card with the same name install, then you need +// * to use 2, 3, ... +// */ +//void Mixer::setCardInstance(int cardInstance) +//{ +// _cardInstance = cardInstance; +// recreateId(); +// // DBusMixerWrapper must be called after recreateId(), as it uses the id +// new DBusMixerWrapper(this, dbusPath()); +//} + +/** + * Set the final ID of this Mixer. + *
Warning: This method is VERY fragile, because it is requires information that we have very late, + * especially the _cardInstance. We only know the _cardInstance, when we know the ID of the _mixerBackend->getId(). + * OTOH, the Mixer backend needs the _cardInstance during construction of its MixDevice instances. + * + * This means, we need the _cardInstance during construction of the Mixer, but we only know it after its constructed. + * Actually its a design error. The _cardInstance MUST be set and managed by the backend. + * + * The current solution works but is very hacky - cardInstance is a parameter of openIfValid(). + * + */ +void Mixer::recreateId() +{ + /* As we use "::" and ":" as separators, the parts %1,%2 and %3 may not + * contain it. + * %1, the driver name is from the KMix backends, it does not contain colons. + * %2, the mixer name, is typically coming from an OS driver. It could contain colons. + * %3, the mixer number, is a number: it does not contain colons. + */ + QString mixerName = _mixerBackend->getId(); + mixerName.replace(':','_'); + QString primaryKeyOfMixer = QString("%1::%2:%3") + .arg(getDriverName()) + .arg(mixerName) + .arg(getCardInstance()); + // The following 3 replaces are for not messing up the config file + primaryKeyOfMixer.replace(']','_'); + primaryKeyOfMixer.replace('[','_'); // not strictly necessary, but lets play safe + primaryKeyOfMixer.replace(' ','_'); + primaryKeyOfMixer.replace('=','_'); + _id = primaryKeyOfMixer; + kDebug() << "Early _id=" << _id; +} + +const QString Mixer::dbusPath() +{ + // _id needs to be fixed from the very beginning, as the MixDevice construction uses MixDevice::dbusPath(). + // So once the first MixDevice is created, this must return the correct value + if (_id.isEmpty()) + { + // Bug 308014: This a rather dirty hack, but it will guarantee that _id is definitely set. + // Even the _cardInstance is set at default value during construction of the MixDevice instances + recreateId(); + } + +// kDebug() << "Late _id=" << _id; +// kDebug() << "handMade=" << QString("/Mixers/" + getDriverName() + "." + _mixerBackend->getId()).replace(" ", "x").replace(".", "_"); + + // mixerName may contain arbitrary characters, so replace all that are not allowed to be be part of a DBUS path + QString cardPath = _id; + cardPath.replace(QRegExp("[^a-zA-Z0-9_]"), "_"); + cardPath.replace(QLatin1String("//"), QLatin1String("/")); + + return QString("/Mixers/" + cardPath); +} + +void Mixer::volumeSave( KConfig *config ) +{ + // kDebug(67100) << "Mixer::volumeSave()"; + _mixerBackend->readSetFromHW(); + QString grp("Mixer"); + grp.append(id()); + _mixerBackend->m_mixDevices.write( config, grp ); +} + +void Mixer::volumeLoad( KConfig *config ) +{ + QString grp("Mixer"); + grp.append(id()); + if ( ! config->hasGroup(grp) ) { + // no such group. Volumes (of this mixer) were never saved beforehand. + // Thus don't restore anything (also see Bug #69320 for understanding the real reason) + return; // make sure to bail out immediately + } + + // else restore the volumes + if ( ! _mixerBackend->m_mixDevices.read( config, grp ) ) { + // Some mixer backends don't support reading the volume into config + // files, so bail out early if that's the case. + return; + } + + // set new settings + for(int i=0; i<_mixerBackend->m_mixDevices.count() ; i++ ) + { + shared_ptr md = _mixerBackend->m_mixDevices[i]; + if ( md.get() == 0 ) + continue; + + _mixerBackend->writeVolumeToHW( md->id(), md ); + if ( md->isEnum() ) + _mixerBackend->setEnumIdHW( md->id(), md->enumId() ); + } +} + + +/** + * Opens the mixer. + * Also, starts the polling timer, for polling the Volumes from the Mixer. + * + * @param cardId The cardId Usually this will be 1, but if there is + * more than one card with the same name install, then you need + * to use 2, 3, ... + * + * @return true, if Mixer could be opened. + */ +bool Mixer::openIfValid() +{ + if (_mixerBackend == 0 ) + { + // if we did not instantiate a suitable Backend, then Mixer is invalid + return false; + } + + bool ok = _mixerBackend->openIfValid(); + if ( ok ) + { + recreateId(); + shared_ptr recommendedMaster = _mixerBackend->recommendedMaster(); + if ( recommendedMaster.get() != 0 ) + { + QString recommendedMasterStr = recommendedMaster->id(); + setLocalMasterMD( recommendedMasterStr ); + kDebug() << "Mixer::open() detected master: " << recommendedMaster->id(); + } + else + { + if ( !m_dynamic ) + kError(67100) << "Mixer::open() no master detected." << endl; + QString noMaster = "---no-master-detected---"; + setLocalMasterMD(noMaster); // no master + } + // cesken: The following connect() looks mighty strange. I removed it on 2013-12-18 + //connect( _mixerBackend, SIGNAL(controlChanged()), SIGNAL(controlChanged()) ); + new DBusMixerWrapper(this, dbusPath()); + } + + return ok; +} + +/** + * Closes the mixer. + */ +void Mixer::close() +{ + if ( _mixerBackend != 0) + _mixerBackend->closeCommon(); +} + + +/* ------- WRAPPER METHODS. START ------------------------------ */ +unsigned int Mixer::size() const +{ + return _mixerBackend->m_mixDevices.count(); +} + +shared_ptr Mixer::operator[](int num) +{ + shared_ptr md = _mixerBackend->m_mixDevices.at( num ); + return md; +} + +MixSet& Mixer::getMixSet() +{ + return _mixerBackend->m_mixDevices; +} + + +/** + * Returns the driver name, that handles this Mixer. + */ +QString Mixer::getDriverName() +{ + QString driverName = _mixerBackend->getDriverName(); +// kDebug(67100) << "Mixer::getDriverName() = " << driverName << "\n"; + return driverName; +} + +bool Mixer::isOpen() const { + if ( _mixerBackend == 0 ) + return false; + else + return _mixerBackend->isOpen(); +} + +void Mixer::readSetFromHWforceUpdate() const { + _mixerBackend->readSetFromHWforceUpdate(); +} + + /// Returns translated WhatsThis messages for a control.Translates from +QString Mixer::translateKernelToWhatsthis(const QString &kernelName) +{ + return _mixerBackend->translateKernelToWhatsthis(kernelName); +} + +/* ------- WRAPPER METHODS. END -------------------------------- */ + +void Mixer::setBeepOnVolumeChange(bool beepOnVolumeChange) +{ + m_beepOnVolumeChange = beepOnVolumeChange; +} + +int Mixer::balance() const { + return m_balance; +} + +void Mixer::setBalance(int balance) +{ + if( balance == m_balance ) { + // balance unchanged => return + return; + } + + m_balance = balance; + + shared_ptr master = getLocalMasterMD(); + if ( master.get() == 0 ) + { + // no master device available => return + return; + } + + Volume& volP = master->playbackVolume(); + setBalanceInternal(volP); + Volume& volC = master->captureVolume(); + setBalanceInternal(volC); + + _mixerBackend->writeVolumeToHW( master->id(), master ); + emit newBalance( volP ); +} + +void Mixer::setBalanceInternal(Volume& vol) +{ + //_mixerBackend->readVolumeFromHW( master->id(), master ); + + int left = vol.getVolume(Volume::LEFT); + int right = vol.getVolume( Volume::RIGHT ); + int refvol = left > right ? left : right; + if( m_balance < 0 ) // balance left + { + vol.setVolume( Volume::LEFT, refvol); + vol.setVolume( Volume::RIGHT, (m_balance * refvol) / 100 + refvol ); + } + else + { + vol.setVolume( Volume::LEFT, -(m_balance * refvol) / 100 + refvol ); + vol.setVolume( Volume::RIGHT, refvol); + } +} + +/** + * Returns a name suitable for a human user to read (on a label, ...) + */ +QString Mixer::readableName() +{ + return readableName(false); +} + +/** + * Returns a name suitable for a human user to read, possibly with quoted ampersand. The latter is required by + * some GUI elements like QRadioButton or when used as a Tab label, as '&' introduces an accelerator there. + * + * @param ampersandQuoted + * @return + */ +QString Mixer::readableName(bool ampersandQuoted) +{ + QString finalName = _mixerBackend->getName(); + if (ampersandQuoted) + finalName.replace('&', "&&"); + + if ( getCardInstance() > 1) + finalName = finalName.append(" %1").arg(getCardInstance()); + +// kDebug() << "name=" << _mixerBackend->getName() << "instance=" << getCardInstance() << ", finalName" << finalName; + return finalName; +} + + +QString Mixer::getBaseName() +{ + return _mixerBackend->getName(); +} + +/** + * Queries the Driver Factory for a driver. + * @par driver Index number. 0 <= driver < numDrivers() + */ +QString Mixer::driverName( int driver ) +{ + getDriverNameFunc *f = g_mixerFactories[driver].getDriverName; + if( f!=0 ) + return f(); + else + return "unknown"; +} + +/* obsoleted by setInstance() +void Mixer::setID(QString& ref_id) +{ + _id = ref_id; +} +*/ + +QString& Mixer::id() +{ + return _id; +} + +QString& Mixer::udi(){ + return _mixerBackend->udi(); +} + +/** + * Set the global master, which is shown in the dock area and which is accessible via the + * DBUS masterVolume() method. + * + * The parameters are taken over as-is, this means without checking for validity. + * This allows the User to define a master card that is not always available + * (e.g. it is an USB hotplugging device). Also you can set the master at any time you + * like, e.g. after reading the KMix configuration file and before actually constructing + * the Mixer instances (hint: this method is static!). + * + * @param ref_card The card id + * @param ref_control The control id. The corresponding control must be present in the card. + * @param preferred Whether this is the preferred master (auto-selected on coldplug and hotplug). + */ +void Mixer::setGlobalMaster(QString ref_card, QString ref_control, bool preferred) +{ + kDebug() << "ref_card=" << ref_card << ", ref_control=" << ref_control << ", preferred=" << preferred; + _globalMasterCurrent.set(ref_card, ref_control); + if ( preferred ) + _globalMasterPreferred.set(ref_card, ref_control); + kDebug() << "Mixer::setGlobalMaster() card=" <id(); + return mixer; +} + + +/** + * Return the preferred global master. + * If there is no preferred global master, returns the current master instead. + */ +MasterControl& Mixer::getGlobalMasterPreferred() +{ + if ( _globalMasterPreferred.isValid() ) { + kDebug() << "Returning preferred master"; + return _globalMasterPreferred; + } + else { + kDebug() << "Returning current master"; + return _globalMasterCurrent; + } +} + + +shared_ptr Mixer::getGlobalMasterMD() +{ + return getGlobalMasterMD(true); +} + + +shared_ptr Mixer::getGlobalMasterMD(bool fallbackAllowed) +{ + shared_ptr mdRet; + shared_ptr firstDevice; + Mixer *mixer = fallbackAllowed ? + Mixer::getGlobalMasterMixer() : Mixer::getGlobalMasterMixerNoFalback(); + + if ( mixer == 0 ) + return mdRet; + + foreach (shared_ptr md, mixer->_mixerBackend->m_mixDevices ) + { + if ( md.get() == 0 ) + continue; // invalid + + firstDevice=md; + if ( md->id() == _globalMasterCurrent.getControl() ) + { + mdRet = md; + break; // found + } + } + if ( mdRet.get() == 0 ) + { + //For some sound cards when using pulseaudio the mixer id is not proper hence returning the first device as master channel device + //This solves the bug id:290177 and problems stated in review #105422 + kDebug() << "Mixer::masterCardDevice() returns 0 (no globalMaster), returning the first device"; + mdRet=firstDevice; + } + + return mdRet; +} + + + + +shared_ptr Mixer::getLocalMasterMD() +{ + return find( _masterDevicePK ); +} + +void Mixer::setLocalMasterMD(QString &devPK) +{ + _masterDevicePK = devPK; +} + + +shared_ptr Mixer::find(const QString& mixdeviceID) +{ + + shared_ptr mdRet; + + foreach (shared_ptr md, _mixerBackend->m_mixDevices ) + { + if ( md.get() == 0 ) + continue; // invalid + if ( md->id() == mixdeviceID ) + { + mdRet = md; + break; // found + } + } + + return mdRet; +} + + +shared_ptr Mixer::getMixdeviceById( const QString& mixdeviceID ) +{ + kDebug() << "id=" << mixdeviceID << "md=" << _mixerBackend->m_mixDevices.get(mixdeviceID).get()->id(); + return _mixerBackend->m_mixDevices.get(mixdeviceID); +// shared_ptr md; +// int num = _mixerBackend->id2num(mixdeviceID); +// if ( num!=-1 && num < (int)size() ) +// { +// md = (*this)[num]; +// } +// return md; +} + +/** + Call this if you have a *reference* to a Volume object and have modified that locally. + Pass the MixDevice associated to that Volume to this method for writing back + the changed value to the mixer. + Hint: Why do we do it this way? + - It is fast (no copying of Volume objects required) + - It is easy to understand ( read - modify - commit ) +*/ +void Mixer::commitVolumeChange(shared_ptr md) +{ + _mixerBackend->writeVolumeToHW(md->id(), md); + if (md->isEnum()) + { + _mixerBackend->setEnumIdHW(md->id(), md->enumId()); + } + if (md->captureVolume().hasSwitch()) + { + // Make sure to re-read the hardware, because setting capture might have failed. + // This is due to exclusive capture groups. + // If we wouldn't do this, KMix might show a Capture Switch disabled, but + // in reality the capture switch is still on. + // + // We also cannot rely on a notification from the driver (SocketNotifier), because + // nothing has changed, and so there s nothing to notify. + _mixerBackend->readSetFromHWforceUpdate(); + if (GlobalConfig::instance().data.debugControlManager) + kDebug() + << "committing a control with capture volume, that might announce: " << md->id(); + _mixerBackend->readSetFromHW(); + } + if (GlobalConfig::instance().data.debugControlManager) + kDebug() + << "committing announces the change of: " << md->id(); + + // We announce the change we did, so all other parts of KMix can pick up the change + ControlManager::instance().announce(md->mixer()->id(), ControlChangeType::Volume, + QString("Mixer.commitVolumeChange()")); +} + +// @dbus, used also in kmix app +void Mixer::increaseVolume( const QString& mixdeviceID ) +{ + increaseOrDecreaseVolume(mixdeviceID, false); +} + +// @dbus +void Mixer::decreaseVolume( const QString& mixdeviceID ) +{ + increaseOrDecreaseVolume(mixdeviceID, true); +} + +/** + * Increase or decrease all playback and capture channels of the given control. + * This method is very similar to MDWSlider::increaseOrDecreaseVolume(), but it will + * NOT auto-unmute. + * + * @param mixdeviceID The control name + * @param decrease true for decrease. false for increase + */ +void Mixer::increaseOrDecreaseVolume( const QString& mixdeviceID, bool decrease ) +{ + + shared_ptr md= getMixdeviceById( mixdeviceID ); + if (md.get() != 0) + { + Volume& volP=md->playbackVolume(); + if ( volP.hasVolume() ) + { + volP.changeAllVolumes(volP.volumeStep(decrease)); + } + + Volume& volC=md->captureVolume(); + if ( volC.hasVolume() ) + { + volC.changeAllVolumes(volC.volumeStep(decrease)); + } + + _mixerBackend->writeVolumeToHW(mixdeviceID, md); + } + ControlManager::instance().announce(md->mixer()->id(), ControlChangeType::Volume, QString("Mixer.increaseOrDecreaseVolume()")); + + /************************************************************ + It is important, not to implement this method like this: + int vol=volume(mixdeviceID); + setVolume(mixdeviceID, vol-5); + It creates too big rounding errors. If you don't believe me, then + do a decreaseVolume() and increaseVolume() with "vol.maxVolume() == 31". + ***********************************************************/ +} + + +void Mixer::setDynamic ( bool dynamic ) +{ + m_dynamic = dynamic; +} + +bool Mixer::isDynamic() +{ + return m_dynamic; +} + +bool Mixer::moveStream( const QString id, const QString& destId ) +{ + // We should really check that id is within our md's.... + bool ret = _mixerBackend->moveStream( id, destId ); + ControlManager::instance().announce(QString(), ControlChangeType::ControlList, QString("Mixer.moveStream()")); + return ret; +} + +#include "mixer.moc" diff --git a/kmix/core/mixer.h b/kmix/core/mixer.h new file mode 100644 index 00000000..e8c7eaee --- /dev/null +++ b/kmix/core/mixer.h @@ -0,0 +1,213 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * 1996-2000 Christian Esken + * Sven Fischer + * 2002 - Helio Chissini de Castro + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef RANDOMPREFIX_MIXER_H +#define RANDOMPREFIX_MIXER_H + +#include +#include +#include + +#include "core/volume.h" +#include "backends/mixer_backend.h" +#include "core/MasterControl.h" +#include "mixset.h" +#include "core/mixdevice.h" +#include "dbus/dbusmixerwrapper.h" + +class Volume; +class KConfig; + +class Mixer : public QObject +{ + Q_OBJECT + +public: + /** + * Status for Mixer operations. + * + * OK_UNCHANGED is a apecial variant of OK. It must be implemented by + * backends that use needsPolling() == true. See Mixer_OSS.cpp for an + * example. Rationale is that we need a proper change check: Otherwise + * the DBUS Session Bus is massively spammed. Also quite likely the Mixer + * GUI might get updated all the time. + * + */ + enum MixerError { OK=0, ERR_PERM=1, ERR_WRITE, ERR_READ, + ERR_OPEN, OK_UNCHANGED }; + + + Mixer( QString& ref_driverName, int device ); + virtual ~Mixer(); + + static int numDrivers(); + QString getDriverName(); + + shared_ptr find(const QString& devPK); + static Mixer* findMixer( const QString& mixer_id); + + void volumeSave( KConfig *config ); + void volumeLoad( KConfig *config ); + + /// Tells the number of the mixing devices + unsigned int size() const; + + /// Returns a pointer to the mix device with the given number + // TODO remove this method. Only used by ViewDockAreaPopup: dockMD = (*mixer)[0]; + shared_ptr operator[](int val_i_num); + + /// Returns a pointer to the mix device whose type matches the value + /// given by the parameter and the array MixerDevNames given in + /// mixer_oss.cpp (0 is Volume, 4 is PCM, etc.) + shared_ptr getMixdeviceById( const QString& deviceID ); + + /// Open/grab the mixer for further intraction + bool openIfValid(); + + /// Returns whether the card is open/operational + bool isOpen() const; + + /// Close/release the mixer + virtual void close(); + + /// Reads balance + int balance() const; + + /// Returns a detailed state message after errors. Only for diagnostic purposes, no i18n. + QString& stateMessage() const; + + /** + * Returns the name of the card/chip/hardware, as given by the driver. The name is NOT instance specific, + * so if you install two identical soundcards, two of them will deliver the same mixerName(). + * Use this method if you need an instance-UNspecific name, e.g. for finding an appropriate + * mixer layout for this card, or as a prefix for constructing instance specific ID's like in id(). + */ + virtual QString getBaseName(); + + /// Wrapper to Mixer_Backend + QString translateKernelToWhatsthis(const QString &kernelName); + + /// Return the name of the card/chip/hardware, which is suitable for humans + QString readableName(); + QString readableName(bool ampersandQuoted); + + // Returns the name of the driver, e.g. "OSS" or "ALSA0.9" + static QString driverName(int num); + + static void setBeepOnVolumeChange(bool m_beepOnVolumeChange); + static bool getBeepOnVolumeChange() { return m_beepOnVolumeChange; } + + /** + * Returns an unique ID of the Mixer. It currently looks like ":::" + */ + QString& id(); + + int getCardInstance() const { return _mixerBackend->getCardInstance(); } + + /// Returns an Universal Device Identifaction of the Mixer. This is an ID that relates to the underlying operating system. + // For OSS and ALSA this is taken from Solid (actually HAL). For Solaris this is just the device name. + // Examples: + // ALSA: /org/freedesktop/Hal/devices/usb_device_d8c_1_noserial_if0_sound_card_0_2_alsa_control__1 + // OSS: /org/freedesktop/Hal/devices/usb_device_d8c_1_noserial_if0_sound_card_0_2_oss_mixer__1 + // Solaris: /dev/audio + QString& udi(); + + // Returns a DBus path for this mixer + // Used also by MixDevice to bind to this path + const QString dbusPath(); + + static QList & mixers(); + + /****************************************** + The KMix GLOBAL master card. Please note that KMix and KMixPanelApplet can have a + different MasterCard's at the moment (but actually KMixPanelApplet does not read/save this yet). + At the moment it is only used for selecting the Mixer to use in KMix's DockIcon. + ******************************************/ + static void setGlobalMaster(QString ref_card, QString ref_control, bool preferred); + static shared_ptr getGlobalMasterMD(); + static shared_ptr getGlobalMasterMD(bool fallbackAllowed); + static Mixer* getGlobalMasterMixer(); + static Mixer* getGlobalMasterMixerNoFalback(); + static MasterControl& getGlobalMasterPreferred(); + + /****************************************** + The recommended master of this Mixer. + ******************************************/ + shared_ptr getLocalMasterMD(); + void setLocalMasterMD(QString&); + + /// get the actual MixSet + MixSet& getMixSet(); + + /// DBUS oriented methods + virtual void increaseVolume( const QString& mixdeviceID ); + virtual void decreaseVolume( const QString& mixdeviceID ); + + /// Says if we are dynamic (e.g. widgets can come and go) + virtual void setDynamic( bool dynamic = true ); + virtual bool isDynamic(); + + static bool dynamicBackendsPresent(); + static bool pulseaudioPresent(); + + virtual bool moveStream( const QString id, const QString& destId ); + + virtual int mediaPlay(QString id) { return _mixerBackend->mediaPlay(id); }; + virtual int mediaPrev(QString id) { return _mixerBackend->mediaPrev(id); }; + virtual int mediaNext(QString id) { return _mixerBackend->mediaNext(id); }; + + + void commitVolumeChange( shared_ptr md ); + +public slots: + void readSetFromHWforceUpdate() const; + virtual void setBalance(int balance); // sets the m_balance (see there) + +signals: + void newBalance(Volume& ); + void controlChanged(void); // TODO remove? + +protected: + int m_balance; // from -100 (just left) to 100 (just right) + static QList s_mixers; + +private: + void setBalanceInternal(Volume& vol); + void recreateId(); + void increaseOrDecreaseVolume( const QString& mixdeviceID, bool decrease ); + + Mixer_Backend *_mixerBackend; + QString _id; + QString _masterDevicePK; + static MasterControl _globalMasterCurrent; + static MasterControl _globalMasterPreferred; + + bool m_dynamic; + + static bool m_beepOnVolumeChange; + +}; + +#endif diff --git a/kmix/core/mixertoolbox.cpp b/kmix/core/mixertoolbox.cpp new file mode 100644 index 00000000..c35a9e51 --- /dev/null +++ b/kmix/core/mixertoolbox.cpp @@ -0,0 +1,395 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "core/mixer.h" + +#include +#include +#include + +//#include +#include +#include + +#include "core/kmixdevicemanager.h" +#include "core/mixdevice.h" + +#include "core/mixertoolbox.h" + + +MixerToolBox* MixerToolBox::s_instance = 0; +QRegExp MixerToolBox::s_ignoreMixerExpression( QLatin1String( "Modem" )); +//KLocale* MixerToolBox::s_whatsthisLocale = 0; + +/*********************************************************************************** + Attention: + This MixerToolBox is linked to the KMix Main Program, the KMix Applet and kmixctrl. + As we do not want to link in more than necessary to kmixctrl, you are asked + not to put any GUI classes in here. + In the case where it is unavoidable, please put them in KMixToolBox. + ***********************************************************************************/ + +MixerToolBox* MixerToolBox::instance() +{ + if ( s_instance == 0 ) { + s_instance = new MixerToolBox(); +// if ( s_ignoreMixerExpression.isEmpty() ) +// s_ignoreMixerExpression.setPattern("Modem"); + } + return s_instance; +} + + +/** + * Scan for Mixers in the System. This is the method that implicitely fills the + * list of Mixer's, which is accessible via the static Mixer::mixer() method. + * + * This is run only once during the initialization phase of KMix. It has the following tasks: + * 1) Coldplug scan, to fill the initial mixer list + * 2) Rember UDI's, to match them when unplugging a device + * 3) Find out, which Backend to use (plugin events of other Backends are ignored). + * + * @deprecated TODO this method has to go away. Migrate to MultiDriverMode enum + * + * @par multiDriverMode Whether the Mixer scan should try more all backendends. + * 'true' means to scan all backends. 'false' means: After scanning the + * current backend the next backend is only scanned if no Mixers were found yet. + * @par backendList Activated backends (typically a value from the kmixrc or a default) + * @par ref_hwInfoString Here a descripitive text of the scan is returned (Hardware Information) + */ +void MixerToolBox::initMixer(bool multiDriverModeBool, QList backendList, QString& ref_hwInfoString) +{ + MultiDriverMode multiDriverMode = multiDriverModeBool ? MULTI : SINGLE_PLUS_MPRIS2; + initMixer(multiDriverMode, backendList, ref_hwInfoString); +} + +void MixerToolBox::initMixer(MultiDriverMode multiDriverMode, QList backendList, QString& ref_hwInfoString) +{ + initMixerInternal(multiDriverMode, backendList, ref_hwInfoString); + if ( Mixer::mixers().isEmpty() ) + initMixerInternal(multiDriverMode, QList(), ref_hwInfoString); // try again without filter +} + + +/** + * + */ +void MixerToolBox::initMixerInternal(MultiDriverMode multiDriverMode, QList backendList, QString& ref_hwInfoString) +{ + bool useBackendFilter = ( ! backendList.isEmpty() ); + bool backendMprisFound = false; // only for SINGLE_PLUS_MPRIS2 + bool regularBackendFound = false; // only for SINGLE_PLUS_MPRIS2 + + kDebug() << "multiDriverMode=" << multiDriverMode << ", backendList=" << backendList; + + // Find all mixers and initialize them + int drvNum = Mixer::numDrivers(); + + int driverWithMixer = -1; + bool multipleDriversActive = false; + + QString driverInfo = ""; + QString driverInfoUsed = ""; + + for( int drv1=0; drv1 0 ) { + driverInfo += " + "; + } + driverInfo += driverName; + } + /* Run a loop over all drivers. The loop will terminate after the first driver which + has mixers. And here is the reason: + - If you run ALSA with ALSA-OSS-Emulation enabled, mixers will show up twice: once + as native ALSA mixer, once as OSS mixer (emulated by ALSA). This is bad and WILL + confuse users. So it is a design decision that we can compile in multiple drivers + but we can run only one driver. + - For special usage scenarios, people will still want to run both drivers at the + same time. We allow them to hack their Config-File, where they can enable a + multi-driver mode. + - Another remark: For KMix3.0 or so, we should allow multiple-driver, for allowing + addition of special-use drivers, e.g. an Jack-Mixer-Backend, or a CD-Rom volume Backend. + */ + + bool autodetectionFinished = false; + for( int drv=0; drv sane exit from outer loop + break; + } + + QString driverName = Mixer::driverName(drv); + kDebug(67100) << "Looking for mixers with the : " << driverName << " driver"; + if ( useBackendFilter && ! backendList.contains(driverName) ) + { + kDebug() << "Skipping " << driverName << " (filtered)"; + continue; + } + + + bool regularBackend = driverName != "MPRIS2" && driverName != "PulseAudio"; + if (regularBackend && regularBackendFound) + { + // Only accept one regular backend => skip this one + continue; + } + + bool drvInfoAppended = false; + // The "19" below is just a "silly" number: + // (Old: The loop will break as soon as an error is detected - e.g. on 3rd loop when 2 soundcards are installed) + // New: We don't try be that clever anymore. We now blindly scan 20 cards, as the clever + // approach doesn't work for the one or other user (e.g. hotplugging might create holes in the list of soundcards). + int devNumMax = 19; + for( int dev=0; dev<=devNumMax; dev++ ) + { + Mixer *mixer = new Mixer( driverName, dev ); + bool mixerAccepted = possiblyAddMixer(mixer); + + /* Lets decide if the autoprobing shall end. + * If the user has configured a backend filter, we will use that as a plain list to obey. It overrides the + * multiDriverMode. + */ + if ( ! useBackendFilter ) + { + bool foundSomethingAndLastControlReached = dev == devNumMax && ! Mixer::mixers().isEmpty(); + switch ( multiDriverMode ) + { + case SINGLE: + // In Single-Driver-mode we only need to check after we reached devNumMax + if ( foundSomethingAndLastControlReached ) + autodetectionFinished = true; // highest device number of driver and a Mixer => finished + break; + + case MULTI: + // In multiDriver mode we scan all devices, so we will simply continue + break; + + case SINGLE_PLUS_MPRIS2: + if ( driverName == "MPRIS2" ) + { + backendMprisFound = true; + } + else if ( driverName == "PulseAudio" ) + { + // PulseAudio is not useful together with MPRIS2. Treat it as "single" + if ( foundSomethingAndLastControlReached ) + autodetectionFinished = true; + } + else + { + // same check as in SINGLE + if ( foundSomethingAndLastControlReached ) + regularBackendFound = true; + } + + if ( backendMprisFound && regularBackendFound ) + autodetectionFinished = true; // highest device number of driver and a Mixer => finished + break; + } + } + else + { + // Using backend filter. This is a plain list to obey. + // Simply continue (and filter at the start of the loop). + } + + if ( mixerAccepted ) + { + kDebug(67100) << "Success! Found a mixer with the : " << driverName << " driver"; + // append driverName (used drivers) + if ( !drvInfoAppended ) + { + drvInfoAppended = true; + if ( Mixer::mixers().count() > 1) + driverInfoUsed += " + "; + driverInfoUsed += driverName; + } + + // Check whether there are mixers in different drivers, so that the user can be warned + if ( !multipleDriversActive ) + { + if ( driverWithMixer == -1 ) + { + // Aha, this is the very first detected device + driverWithMixer = drv; + } + else if ( driverWithMixer != drv ) + { + // Got him: There are mixers in different drivers + multipleDriversActive = true; + } + } // !multipleDriversActive + } // mixerAccepted + + } // loop over sound card devices of current driver + + if (autodetectionFinished) { + break; + } + } // loop over soundcard drivers + + + // Add a master device (if we haven't defined one yet) + if ( !Mixer::getGlobalMasterMD(false) ) { + // We have no master card yet. This actually only happens when there was + // not one defined in the kmixrc. + // So lets just set the first card as master card. + if ( Mixer::mixers().count() > 0 ) { + shared_ptr master = Mixer::mixers().first()->getLocalMasterMD(); + if ( master ) { + QString controlId = master->id(); + Mixer::setGlobalMaster( Mixer::mixers().first()->id(), controlId, true); + } + } + } + else { + // setGlobalMaster was already set after reading the configuration. + // So we must make the local master consistent + shared_ptr md = Mixer::getGlobalMasterMD(); + QString mdID = md->id(); + md->mixer()->setLocalMasterMD(mdID); + } + + + + if ( Mixer::mixers().count() == 0 ) + { + // If there was no mixer found, we assume, that hotplugging will take place + // on the preferred driver (this is always the first in the backend list). + driverInfoUsed = Mixer::driverName(0); + } + + ref_hwInfoString = i18n("Sound drivers supported:"); + ref_hwInfoString.append(" ").append( driverInfo ).append( "\n").append(i18n("Sound drivers used:")) .append(" ").append(driverInfoUsed); + + if ( multipleDriversActive ) + { + // this will only be possible by hacking the config-file, as it will not be officially supported + ref_hwInfoString.append("\n").append(i18n("Experimental multiple-Driver mode activated")); + QString allDrivermatch("*"); + KMixDeviceManager::instance()->setHotpluggingBackends(allDrivermatch); + } + else { + KMixDeviceManager::instance()->setHotpluggingBackends(driverInfoUsed); + } + + kDebug(67100) << ref_hwInfoString << endl << "Total number of detected Mixers: " << Mixer::mixers().count(); + //kDebug(67100) << "OUT MixerToolBox::initMixer()"; + +} + +/** + * Opens and adds a mixer to the KMix wide Mixer array, if the given Mixer is valid. + * Otherwise the Mixer is deleted. + * This method can be used for adding "static" devices (at program start) and also for hotplugging. + * + * @arg mixer + * @returns true if the Mixer was added + */ +bool MixerToolBox::possiblyAddMixer(Mixer *mixer) +{ + if ( mixer->openIfValid() ) + { + if ( (!s_ignoreMixerExpression.isEmpty()) && mixer->id().contains(s_ignoreMixerExpression) ) + { + // This Mixer should be ignored (default expression is "Modem"). + // next 3 lines are duplicated code + delete mixer; + mixer = 0; + return false; + } + else + { + Mixer::mixers().append( mixer ); + kDebug(67100) << "Added card " << mixer->id(); + + emit mixerAdded(mixer->id()); // TODO should we still use this, as we now have our publish/subscribe notification system? + return true; + } + } // valid + else + { + delete mixer; + mixer = 0; + return false; + } // invalid +} + +/* This allows to set an expression form Mixers that should be ignored. + The default is "Modem", because most people don't want to control the modem volume. */ +void MixerToolBox::setMixerIgnoreExpression(const QString& ignoreExpr) +{ + s_ignoreMixerExpression.setPattern(ignoreExpr); +} + +QString MixerToolBox::mixerIgnoreExpression() const +{ + return s_ignoreMixerExpression.pattern( ); +} + +void MixerToolBox::removeMixer(Mixer *par_mixer) +{ + for (int i=0; iid(); + Mixer::mixers().removeAt(i); + delete mixer; + } + } +} + + + +/* + * Clean up and free all resources of all found Mixers, which were found in the initMixer() call + */ +void MixerToolBox::deinitMixer() +{ + //kDebug(67100) << "IN MixerToolBox::deinitMixer()"; + int mixerCount = Mixer::mixers().count(); + for ( int i=0; iclose(); + delete mixer; + } + Mixer::mixers().clear(); + // kDebug(67100) << "OUT MixerToolBox::deinitMixer()"; +} + + +/* +KLocale* MixerToolBox::whatsthisControlLocale() +{ + if ( s_whatsthisLocale == 0 ) { + s_whatsthisLocale = new KLocale("kmix-controls"); + } + return s_whatsthisLocale; +} +*/ + + +#include "mixertoolbox.moc" diff --git a/kmix/core/mixertoolbox.h b/kmix/core/mixertoolbox.h new file mode 100644 index 00000000..80228a0d --- /dev/null +++ b/kmix/core/mixertoolbox.h @@ -0,0 +1,67 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef MIXERTOOLBOX_H +#define MIXERTOOLBOX_H + +#include +#include +#include +#include +#include + +class Mixer; + +/** + * This toolbox contains various static methods that are shared throughout KMix. + * It only contains no-GUI code. The shared with-GUI code is in KMixToolBox + * The reason, why it is not put in a common base class is, that the classes are + * very different and cannot be changed (e.g. KPanelApplet) without major headache. + */ +class MixerToolBox : public QObject +{ + Q_OBJECT + + enum MultiDriverMode { SINGLE, SINGLE_PLUS_MPRIS2, MULTI }; + + public: + static MixerToolBox* instance(); + void initMixer(bool, QList backendList, QString&); + void initMixer(MultiDriverMode, QList backendList, QString&); + void initMixerInternal(MultiDriverMode, QList backendList, QString&); + void deinitMixer(); + bool possiblyAddMixer(Mixer *mixer); + void removeMixer(Mixer *mixer); + void setMixerIgnoreExpression(const QString& ignoreExpr); + QString mixerIgnoreExpression() const; + + //static KLocale* whatsthisControlLocale(); + + signals: + void mixerAdded(QString mixerID); + + private: + static MixerToolBox* s_instance; + static QRegExp s_ignoreMixerExpression; + + //static KLocale* s_whatsthisLocale; +}; + +#endif diff --git a/kmix/core/mixset.cpp b/kmix/core/mixset.cpp new file mode 100644 index 00000000..67cd2c58 --- /dev/null +++ b/kmix/core/mixset.cpp @@ -0,0 +1,107 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +//KMix +#include "mixset.h" +#include "core/mixdevice.h" + +// KDE +#include +#include +#include + +// Qt +#include + + +MixSet::~MixSet() +{ + clear(); +} + +bool MixSet::read( KConfig *config, const QString& grp ) +{ + kDebug(67100) << "MixSet::read() of group " << grp; + KConfigGroup group = config->group(grp); + m_name = group.readEntry( "name", m_name ); + + bool have_success = false, have_fail = false; + foreach ( shared_ptr md, *this) + { + if ( md->read( config, grp ) ) + have_success = true; + else + have_fail = true; + } + return have_success && !have_fail; +} + +bool MixSet::write( KConfig *config, const QString& grp ) +{ + kDebug(67100) << "MixSet::write() of group " << grp; + KConfigGroup conf = config->group(grp); + conf.writeEntry( "name", m_name ); + + bool have_success = false, have_fail = false; + foreach ( shared_ptr md, *this) + { + if ( md->write( config, grp ) ) + have_success = true; + else + have_fail = true; + } + return have_success && !have_fail; +} + +void MixSet::setName( const QString &name ) +{ + m_name = name; +} + +shared_ptr MixSet::get(QString id) +{ + shared_ptr mdRet; + + foreach ( shared_ptr md, *this) + { + if ( md->id() == id ) + { + mdRet = md; + break; + } + } + return mdRet; +} + +void MixSet::removeById(QString id) +{ + for (int i=0; i < count() ; i++ ) + { + shared_ptr md = operator[](i); + if ( md->id() == id ) + { + removeAt(i); + break; + } + } +} + diff --git a/kmix/core/mixset.h b/kmix/core/mixset.h new file mode 100644 index 00000000..acea5991 --- /dev/null +++ b/kmix/core/mixset.h @@ -0,0 +1,47 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef MixSet_h +#define MixSet_h + +#include + +#include "core/mixdevice.h" + +class MixSet : public QList > +{ + public: + ~MixSet(); + + bool read( KConfig *config, const QString& grp ); + bool write( KConfig *config, const QString& grp ); + + QString name() { return m_name; } + void setName( const QString &name ); + + shared_ptr get(QString id); + + void removeById(QString id); + + private: + QString m_name; +}; + +#endif diff --git a/kmix/core/version.h b/kmix/core/version.h new file mode 100644 index 00000000..d40fb473 --- /dev/null +++ b/kmix/core/version.h @@ -0,0 +1,25 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef APP_VERSION +#define APP_VERSION "4.5" +#define KMIX_CONFIG_VERSION 3 +#endif // APP_VERSION diff --git a/kmix/core/volume.cpp b/kmix/core/volume.cpp new file mode 100644 index 00000000..dfc363f4 --- /dev/null +++ b/kmix/core/volume.cpp @@ -0,0 +1,381 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "core/volume.h" + +// for operator<<() +#include + +#include +#include + +float Volume::VOLUME_STEP_DIVISOR = 20; +float Volume::VOLUME_PAGESTEP_DIVISOR = 10; + + +int Volume::_channelMaskEnum[9] = +{ MLEFT, MRIGHT, MCENTER, + MWOOFER, + MSURROUNDLEFT, MSURROUNDRIGHT, + MREARSIDELEFT, MREARSIDERIGHT, + MREARCENTER +}; + +QString Volume::ChannelNameReadable[9] = +{ +// "Left", "Right", +// "Center", "Subwoofer", +// "Surround Left", "Surround Right", +// "Side Left", "Side Right", +// "Rear Center" + + i18nc("Channel name", "Left"), i18nc("Channel name", "Right"), + i18nc("Channel name", "Center"), i18nc("Channel name", "Subwoofer"), + i18nc("Channel name", "Surround Left"), i18nc("Channel name", "Surround Right"), + i18nc("Channel name", "Side Left"), i18nc("Channel name", "Side Right"), + i18nc("Channel name", "Rear Center") + +}; + +char Volume::ChannelNameForPersistence[9][30] = { + "volumeL", "volumeR", + "volumeCenter", "volumeWoofer", + "volumeSurroundL", "volumeSurroundR", + "volumeSideL", "volumeSideR", + "volumeRearCenter" +}; + +// Forbidden/private. Only here because if there is no CaptureVolume we need the values initialized +// And also QMap requires it. +Volume::Volume() +{ + _minVolume = 0; + _maxVolume = 0; + _hasSwitch = false; + _switchActivated = false; + _switchType = None; + _isCapture = false; + _chmask = MNONE; +} + +/** + * Do not use. Only implicitely required for QMap. + * + * @deprecated Do not use + */ +VolumeChannel::VolumeChannel() +{ + volume = 0; + chid = Volume::NOCHANNEL; +} + +VolumeChannel::VolumeChannel(Volume::ChannelID chid) +{ + volume = 0; + this->chid = chid; +} + +Volume::Volume(long maxVolume, long minVolume, bool hasSwitch, bool isCapture ) +{ + init((ChannelMask)0, maxVolume, minVolume, hasSwitch, isCapture ); +} + +/** + * @deprecated + */ +void Volume::addVolumeChannels(ChannelMask chmask) +{ + for ( Volume::ChannelID chid=Volume::CHIDMIN; chid<= Volume::CHIDMAX; ) + { + if ( chmask & Volume::_channelMaskEnum[chid] ) + { + addVolumeChannel(VolumeChannel(chid)); + } + chid = (Volume::ChannelID)( 1 + (int)chid); // ugly + } // for all channels +} + +void Volume::addVolumeChannel(VolumeChannel vc) +{ + _volumesL.insert(vc.chid, vc); + // Add the correpsonnding "muted version" of the chnnel. +// VolumeChannel* zeroChannel = new VolumeChannel(vc.chid); +// zeroChannel->volume = 0; +// _volumesMuted.insert(zeroChannel->chid, *zeroChannel); // TODO remove _volumesMuted +} + + + +void Volume::init( ChannelMask chmask, long maxVolume, long minVolume, bool hasSwitch, bool isCapture) +{ + _chmask = chmask; + _maxVolume = maxVolume; + _minVolume = minVolume; + _hasSwitch = hasSwitch; + _isCapture = isCapture; + //_muted = false; + // Presume that the switch is active. This will always work: + // a) Physical switches will be updated after start from the hardware. + // b) Emulated virtual/switches will not receive updates from the hardware, so they shouldn't disable the channels. + _switchActivated = true; +} + +QMap Volume::getVolumesWhenActive() const +{ + return _volumesL; +} + +QMap Volume::getVolumes() const +{ + return _volumesL; +} + +/** + * Returns the absolute change to do one "step" for this volume. This is similar to a page step in a slider, + * namely a fixed percentage of the range. + * One step is the percentage given by 100/VOLUME_STEP_DIVISOR. The + * default VOLUME_STEP_DIVISOR is 20, so default change is 5% of the volumeSpan(). + * + * This method guarantees a minimum absolute change of 1, zero is never returned. + * + * It is NOT verified, that such a volume change would actually be possible. You might hit the upper or lower bounds + * of the volume range. + * + * + * @param decrease true, if you want a volume step that decreases the volume by one page step + * @return The volume step. It will be negative if you have used decrease==true + * + */ +long Volume::volumeStep(bool decrease) +{ + long inc = volumeSpan() / Volume::VOLUME_STEP_DIVISOR; + if ( inc == 0 ) inc = 1; + if ( decrease ) inc *= -1; + return inc; +} + + +// @ compatibility +void Volume::setAllVolumes(long vol) +{ + long int finalVol = volrange(vol); + QMap::iterator it = _volumesL.begin(); + while (it != _volumesL.end()) + { + it.value().volume = finalVol; + //it.value().unmutedVolume= finalVol; + ++it; + } +} + +void Volume::changeAllVolumes( long step ) +{ + QMap::iterator it = _volumesL.begin(); + while (it != _volumesL.end()) + { + long int finalVol = volrange(it.value().volume + step); + it.value().volume = finalVol; +// it.value().unmutedVolume= finalVol; + ++it; + } +} + + +/** + * Sets the volume for the given Channel + * @ compatibility + */ +void Volume::setVolume( ChannelID chid, long vol) +{ + QMap::iterator it = _volumesL.find(chid); + if ( it != _volumesL.end()) + { + it.value().volume = vol; + //it.value().unmutedVolume = vol; + } +} + +/** + * Copy the volume elements contained in v to this Volume object. + */ +// void Volume::setVolume(const Volume &v) +// { +// foreach (VolumeChannel vc, _volumesL ) +// { +// ChannelID chid = vc.chid; +// v.getVolumes()[chid].volume = vc.volume; +// //v.getVolumes()[chid].unmutedVolume = vc.volume; +// } +// } + + void Volume::setSwitch( bool active ) + { + _switchActivated = active; + +// if ( isCapture() ) +// return; +// + // for playback volumes we will not only do the switch, but also set the volume to 0 +// QMap::iterator it = _volumesL.begin(); +// if ( active ) +// { +// while (it != _volumesL.end()) +// { +// VolumeChannel& vc = it.value(); +// vc.volume = vc.unmutedVolume; +// ++it; +// } +// } +// else +// { +// while (it != _volumesL.end()) +// { +// VolumeChannel& vc = it.value(); +// vc.unmutedVolume = vc.volume; +// vc.volume = 0; +// ++it; +// } +// } + } + +long Volume::maxVolume() { + return _maxVolume; +} + +long Volume::minVolume() { + return _minVolume; +} + +long Volume::volumeSpan() { + return _maxVolume - _minVolume + 1; +} + +/** + * Returns the volume of the given channel. + */ +long Volume::getVolume(ChannelID chid) +{ + return _volumesL.value(chid).volume; +} + +/** + * Returns the volume of the given channel. If this Volume is inactive (switched off), 0 is returned. + */ +long Volume::getVolumeForGUI(ChannelID chid) +{ + if (! isSwitchActivated() ) + return 0; + + return _volumesL.value(chid).volume; +} + +qreal Volume::getAvgVolume(ChannelMask chmask) +{ + int avgVolumeCounter = 0; + long long sumOfActiveVolumes = 0; + foreach (VolumeChannel vc, _volumesL ) + { + if (Volume::_channelMaskEnum[vc.chid] & chmask ) + { + sumOfActiveVolumes += vc.volume; + ++avgVolumeCounter; + } + } + if (avgVolumeCounter != 0) { + qreal sumOfActiveVolumesQreal = sumOfActiveVolumes; + sumOfActiveVolumesQreal /= avgVolumeCounter; + return sumOfActiveVolumesQreal; + } + else + return 0; +} + + +int Volume::getAvgVolumePercent(ChannelMask chmask) +{ + qreal volume = getAvgVolume(chmask); + // min=-100, max=200 => volSpan = 301 + // volume = -50 => volShiftedToZero = -50+min = 50 + qreal volSpan = volumeSpan(); + qreal volShiftedToZero = volume - _minVolume; + qreal percentReal = ( volSpan == 0 ) ? 0 : ( 100 * volShiftedToZero ) / ( volSpan - 1); + int percent = qRound(percentReal); + //kDebug() << "volSpan=" << volSpan << ", volume=" << volume << ", volShiftedToPositive=" << volShiftedToZero << ", percent=" << percent; + + return percent; +} + +int Volume::count() { + return getVolumes().count(); +} + +/** + * returns a "sane" volume level. This means, it is a volume level inside the + * valid bounds + */ +long Volume::volrange( long vol ) +{ + if ( vol < _minVolume ) { + return _minVolume; + } + else if ( vol < _maxVolume ) { + return vol; + } + else { + return _maxVolume; + } +} + + +std::ostream& operator<<(std::ostream& os, const Volume& vol) { + os << "("; + + bool first = true; + foreach ( const VolumeChannel vc, vol.getVolumes() ) + { + if ( !first ) os << ","; + else first = false; + os << vc.volume; + } // all channels + os << ")"; + + os << " [" << vol._minVolume << "-" << vol._maxVolume; + if ( vol._switchActivated ) { os << " : switch active ]"; } else { os << " : switch inactive ]"; } + + return os; +} + +QDebug operator<<(QDebug os, const Volume& vol) { + os << "("; + bool first = true; + foreach ( VolumeChannel vc, vol.getVolumes() ) + { + if ( !first ) os << ","; + else first = false; + os << vc.volume; + } // all channels + os << ")"; + + os << " [" << vol._minVolume << "-" << vol._maxVolume; + if ( vol._switchActivated ) { os << " : switch active ]"; } else { os << " : switch inactive ]"; } + + return os; +} diff --git a/kmix/core/volume.h b/kmix/core/volume.h new file mode 100644 index 00000000..da509470 --- /dev/null +++ b/kmix/core/volume.h @@ -0,0 +1,203 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef RADOMPREFIX_VOLUME_H +#define RADOMPREFIX_VOLUME_H + +#include + +#include + +#include + +class VolumeChannel; + + +class Volume +{ + +friend class MixDevice; + + public: + // Channel definition: + // For example a 2.0 system just has MLEFT and MRIGHT. + // A 5.1 system adds MCENTER, MWOOFER, MSURROUNDLEFT and MSURROUNDRIGHT. + // A 7.1 system furthermore adds MREARLEFT and MREARRIGHT. + enum ChannelMask { MNONE = 0, + + MLEFT = 1, MRIGHT = 2, MCENTER = 4, + MMAIN = 3, MFRONT = 7, + + MWOOFER = 8, + + // SURROUND (4.0 or 4.1 or in higher - like 5.1) + MSURROUNDLEFT = 0x10, MSURROUNDRIGHT = 0x20, + // MSURROUND + MSURROUND = 0x30, + + // REARSIDE (Usually only in 7.1) + MREARSIDELEFT = 0x40, MREARSIDERIGHT = 0x80, + // REARCENTER (Usually only in 6.1) + MREARCENTER = 0x100, + // MREAR + MREAR = 0x1C0, + + MALL=0xFFFF }; + + + enum ChannelID { NOCHANNEL =-1, CHIDMIN = 0, + LEFT = 0, RIGHT = 1, CENTER = 2, + + WOOFER = 3, + + SURROUNDLEFT = 4, SURROUNDRIGHT = 5, + + REARSIDELEFT = 6, REARSIDERIGHT = 7, + + REARCENTER = 8, + + CHIDMAX = 8 }; + + static char ChannelNameForPersistence[9][30]; + static QString ChannelNameReadable[9]; + + enum VolumeType { PlaybackVT = 0 , CaptureVT = 1 }; + + enum VolumeTypeFlag { Playback = 1, Capture = 2, Both = 3 }; + + // regular constructor (old, deprecsted) + //Volume( ChannelMask chmask, long maxVolume, long minVolume, bool hasSwitch, bool isCapture ); + // regular constructor + Volume(long maxVolume, long minVolume, bool hasSwitch, bool isCapture ); + void addVolumeChannel(VolumeChannel ch); + /// @Deprecated + void addVolumeChannels(ChannelMask chmask); + + // Set all volumes as given by vol + void setAllVolumes(long vol); + // Set all volumes to the ones given in vol +// void setVolume(const Volume &vol ); + // Set volumes as specified by the channel mask + //void setVolume( const Volume &vol, ChannelMask chmask); + void setVolume( ChannelID chid, long volume); + + // Increase or decrease all volumes by step + void changeAllVolumes( long step ); + + long getVolume(ChannelID chid); + long getVolumeForGUI(ChannelID chid); + qreal getAvgVolume(ChannelMask chmask); + int getAvgVolumePercent(ChannelMask chmask); + + //long operator[](int); + long maxVolume(); + long minVolume(); + /** + * The number of valid volume levels, mathematically: maxVolume - minVolume + 1 + */ + long volumeSpan(); + int count(); + + bool hasSwitch() const + { + return _hasSwitch; + }; + bool hasVolume() const { return (_maxVolume != _minVolume); } + /** + * Returns whether this is a playback or capture volume. + * + * @return true, if it is a capture volume + */ + bool isCapture() const + { + return _isCapture; + } + + // Some playback switches control playback, and some are special. + // ALSA doesn't differentiate between playback, OnOff and special, so users can add this information in the profile. + // It is only used for GUI things, like showing a "Mute" text or tooltip + // Capture is not really used, and has only been added for completeness and future extensibility. + enum SwitchType { None, PlaybackSwitch, CaptureSwitch, OnSwitch, OffSwitch, SpecialSwitch }; + void setSwitchType(SwitchType type) { _switchType = type; } + Volume::SwitchType switchType() { return _switchType; } + + friend std::ostream& operator<<(std::ostream& os, const Volume& vol); + friend QDebug operator<<(QDebug os, const Volume& vol); + + // _channelMaskEnum[] and the following elements moved to public seection. operator<<() could not + // access it, when private. Strange, as operator<<() is declared friend. + static int _channelMaskEnum[9]; + QMap getVolumes() const; + QMap getVolumesWhenActive() const; + long volumeStep(bool decrease); + + static float VOLUME_STEP_DIVISOR; // The divisor for defining volume control steps (for mouse-wheel, DBUS and Normal step for Sliders ) + static float VOLUME_PAGESTEP_DIVISOR; // The divisor for defining volume control steps (page-step for sliders) + +protected: + long _chmask; + QMap _volumesL; +// QMap _volumesMuted; + + long _minVolume; + long _maxVolume; + // setSwitch() and isSwitchActivated() are tricky. No regular class (incuding the Backends) shall use + // these functions. Our friend class MixDevice will handle that gracefully for us. + void setSwitch( bool active ); + bool isSwitchActivated() const // TODO rename to isActive() + { + return _switchActivated; + }; + + +private: + // constructor for dummy volumes + Volume(); + + void init( ChannelMask chmask, long maxVolume, long minVolume, bool hasSwitch, bool isCapture); + + long volrange( long vol ); + + bool _hasSwitch; + bool _switchActivated; + SwitchType _switchType; + bool _isCapture; +}; + +class VolumeChannel +{ +public: + VolumeChannel(); + /** + * Construct a channel for the given channel id. + * + * @param chid + */ + VolumeChannel(Volume::ChannelID chid); + + long volume; + Volume::ChannelID chid; +}; + +std::ostream& operator<<(std::ostream& os, const Volume& vol); +QDebug operator<<(QDebug os, const Volume& vol); + +#endif // RADOMPREFIX_VOLUME + diff --git a/kmix/dbus/dbuscontrolwrapper.cpp b/kmix/dbus/dbuscontrolwrapper.cpp new file mode 100644 index 00000000..4971bd82 --- /dev/null +++ b/kmix/dbus/dbuscontrolwrapper.cpp @@ -0,0 +1,145 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 1996-2004 Christian Esken + * Copyright 2011 Igor Poboiko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "dbuscontrolwrapper.h" +#include "controladaptor.h" +#include "core/mixer.h" +#include "core/volume.h" + +DBusControlWrapper::DBusControlWrapper(shared_ptr parent, const QString& path) + : QObject(0) +{ +// kDebug() << "QDBusConnection for control created" << path; + m_md = parent; + new ControlAdaptor( this ); + QDBusConnection::sessionBus().registerObject( path, this ); +} + +DBusControlWrapper::~DBusControlWrapper() +{ +} + +QString DBusControlWrapper::id() +{ + return m_md->id(); +} + +QString DBusControlWrapper::readableName() +{ + return m_md->readableName(); +} + +QString DBusControlWrapper::iconName() +{ + return m_md->iconName(); +} + +void DBusControlWrapper::setVolume(int percentage) +{ + Volume& volP = m_md->playbackVolume(); + Volume& volC = m_md->captureVolume(); + volP.setAllVolumes( volP.minVolume() + ((percentage * volP.volumeSpan()) / 100) ); + volC.setAllVolumes( volC.minVolume() + ((percentage * volC.volumeSpan()) / 100) ); + m_md->mixer()->commitVolumeChange( m_md ); +} + +int DBusControlWrapper::volume() +{ + Volume &useVolume = (m_md->playbackVolume().count() != 0) ? m_md->playbackVolume() : m_md->captureVolume(); + return useVolume.getAvgVolumePercent(Volume::MALL); +} + +void DBusControlWrapper::increaseVolume() +{ + m_md->mixer()->increaseVolume(m_md->id()); +} + +void DBusControlWrapper::decreaseVolume() +{ + m_md->mixer()->decreaseVolume(m_md->id()); +} + +long DBusControlWrapper::absoluteVolumeMin() +{ + Volume &useVolume = (m_md->playbackVolume().count() != 0) ? m_md->playbackVolume() : m_md->captureVolume(); + return useVolume.minVolume(); +} + +long DBusControlWrapper::absoluteVolumeMax() +{ + Volume &useVolume = (m_md->playbackVolume().count() != 0) ? m_md->playbackVolume() : m_md->captureVolume(); + return useVolume.maxVolume(); +} + +void DBusControlWrapper::setAbsoluteVolume(long absoluteVolume) +{ + m_md->playbackVolume().setAllVolumes( absoluteVolume ); + m_md->captureVolume().setAllVolumes( absoluteVolume ); + m_md->mixer()->commitVolumeChange( m_md ); +} + +long DBusControlWrapper::absoluteVolume() +{ + Volume &useVolume = (m_md->playbackVolume().count() != 0) ? m_md->playbackVolume() : m_md->captureVolume(); + qreal avgVol= useVolume.getAvgVolume( Volume::MALL ); + long avgVolRounded = avgVol <0 ? avgVol-.5 : avgVol+.5; + return avgVolRounded; +} + +void DBusControlWrapper::setMute(bool muted) +{ + m_md->setMuted( muted ); + m_md->mixer()->commitVolumeChange( m_md ); +} + +void DBusControlWrapper::toggleMute() +{ + m_md->toggleMute(); + m_md->mixer()->commitVolumeChange( m_md ); +} + +bool DBusControlWrapper::canMute() +{ + return m_md->hasMuteSwitch(); +} + +bool DBusControlWrapper::isMuted() +{ + return m_md->isMuted(); +} + +bool DBusControlWrapper::isRecordSource() +{ + return m_md->isRecSource(); +} + +void DBusControlWrapper::setRecordSource(bool on) +{ + m_md->setRecSource(on); + m_md->mixer()->commitVolumeChange( m_md ); +} + +bool DBusControlWrapper::hasCaptureSwitch() +{ + return m_md->captureVolume().hasSwitch(); +} + +#include "dbuscontrolwrapper.moc" diff --git a/kmix/dbus/dbuscontrolwrapper.h b/kmix/dbus/dbuscontrolwrapper.h new file mode 100644 index 00000000..5355f974 --- /dev/null +++ b/kmix/dbus/dbuscontrolwrapper.h @@ -0,0 +1,74 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 1996-2004 Christian Esken + * Copyright 2011 Igor Poboiko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef DBUS_CONTROL_WRAPPER_H +#define DBUS_CONTROL_WRAPPER_H + +#include +#include "core/mixdevice.h" + +class DBusControlWrapper : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString id READ id) + Q_PROPERTY(QString readableName READ readableName) + Q_PROPERTY(QString iconName READ iconName) + Q_PROPERTY(int volume READ volume WRITE setVolume) + Q_PROPERTY(long absoluteVolume READ absoluteVolume WRITE setAbsoluteVolume) + Q_PROPERTY(long absoluteVolumeMin READ absoluteVolumeMin) + Q_PROPERTY(long absoluteVolumeMax READ absoluteVolumeMax) + Q_PROPERTY(bool mute READ isMuted WRITE setMute) + Q_PROPERTY(bool recordSource READ isRecordSource WRITE setRecordSource) + Q_PROPERTY(bool canMute READ canMute) + Q_PROPERTY(bool hasCaptureSwitch READ hasCaptureSwitch) + + public: + DBusControlWrapper(shared_ptr parent, const QString& path); + ~DBusControlWrapper(); + + void increaseVolume(); + void decreaseVolume(); + void toggleMute(); + private: + shared_ptr m_md; + + QString id(); + QString readableName(); + QString iconName(); + + void setVolume(int percentage); + int volume(); + + void setAbsoluteVolume(long absoluteVolume); + long absoluteVolumeMin(); + long absoluteVolumeMax(); + long absoluteVolume(); + + bool canMute(); + void setMute(bool muted); + bool isMuted(); + + bool hasCaptureSwitch(); + void setRecordSource(bool on); + bool isRecordSource(); +}; + +#endif /* DBUS_MIXER_WRAPPER_H */ diff --git a/kmix/dbus/dbusmixerwrapper.cpp b/kmix/dbus/dbusmixerwrapper.cpp new file mode 100644 index 00000000..d135d420 --- /dev/null +++ b/kmix/dbus/dbusmixerwrapper.cpp @@ -0,0 +1,145 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 1996-2004 Christian Esken + * Copyright 2011 Igor Poboiko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "dbusmixerwrapper.h" + +#include + +#include "core/ControlManager.h" +#include "core/mixdevice.h" +#include "core/volume.h" +#include "dbus/dbusmixsetwrapper.h" +#include "mixeradaptor.h" + +DBusMixerWrapper::DBusMixerWrapper(Mixer* parent, const QString& path) + : QObject(parent) + , m_dbusPath(path) +{ + m_mixer = parent; + new MixerAdaptor( this ); + kDebug() << "Create QDBusConnection for object " << path; + QDBusConnection::sessionBus().registerObject( path, this ); + + ControlManager::instance().addListener( + m_mixer->id(), + (ControlChangeType::Type)(ControlChangeType::ControlList | ControlChangeType::Volume), + this, + QString("DBusMixerWrapper.%1").arg(m_mixer->id()) + ); + if (DBusMixSetWrapper::instance()) + DBusMixSetWrapper::instance()->signalMixersChanged(); +} + +DBusMixerWrapper::~DBusMixerWrapper() +{ + ControlManager::instance().removeListener(this); + kDebug() << "Remove QDBusConnection for object " << m_dbusPath; + if (DBusMixSetWrapper::instance()) + DBusMixSetWrapper::instance()->signalMixersChanged(); +} + +void DBusMixerWrapper::controlsChange(int changeType) +{ + ControlChangeType::Type type = ControlChangeType::fromInt(changeType); + switch (type ) + { + case ControlChangeType::ControlList: + createDeviceWidgets(); + break; + + case ControlChangeType::Volume: + refreshVolumeLevels(); + break; + + default: + ControlManager::warnUnexpectedChangeType(type, this); + break; + } +} + + +QString DBusMixerWrapper::driverName() +{ + return m_mixer->getDriverName(); +} + +QStringList DBusMixerWrapper::controls() +{ + QStringList result; + foreach ( shared_ptr md, m_mixer->getMixSet() ) + { + result.append( md->dbusPath() ); + } + return result; +} + +QString DBusMixerWrapper::masterControl() +{ + shared_ptr md = m_mixer->getLocalMasterMD(); + // XXX: Since empty object path is invalid, using "/" + return md ? md->dbusPath() : QString("/"); +} + +bool DBusMixerWrapper::isOpened() +{ + return m_mixer->isOpen(); +} + +int DBusMixerWrapper::balance() +{ + return m_mixer->balance(); +} + +void DBusMixerWrapper::setBalance(int balance) +{ + m_mixer->setBalance(balance); +} + +QString DBusMixerWrapper::readableName() +{ + return m_mixer->readableName(); +} + +QString DBusMixerWrapper::id() +{ + return m_mixer->id(); +} + +QString DBusMixerWrapper::udi() +{ + return m_mixer->udi(); +} + +void DBusMixerWrapper::refreshVolumeLevels() +{ + QDBusMessage signal = QDBusMessage::createSignal( m_dbusPath, + "org.kde.KMix.Mixer", "controlChanged" ); + QDBusConnection::sessionBus().send( signal ); +} + +void DBusMixerWrapper::createDeviceWidgets() +{ + QDBusMessage signal = QDBusMessage::createSignal( m_dbusPath, + "org.kde.KMix.Mixer", "changed" ); + QDBusConnection::sessionBus().send( signal ); +} + +#include "dbusmixerwrapper.moc" diff --git a/kmix/dbus/dbusmixerwrapper.h b/kmix/dbus/dbusmixerwrapper.h new file mode 100644 index 00000000..f6a0384c --- /dev/null +++ b/kmix/dbus/dbusmixerwrapper.h @@ -0,0 +1,66 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 1996-2004 Christian Esken + * Copyright 2011 Igor Poboiko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef DBUSMIXERWRAPPER_H +#define DBUSMIXERWRAPPER_H + +#include +#include + +#include "core/mixer.h" + +class DBusMixerWrapper : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString driverName READ driverName) + Q_PROPERTY(QString masterControl READ masterControl) + Q_PROPERTY(QString readableName READ readableName) + Q_PROPERTY(bool opened READ isOpened) + Q_PROPERTY(QString id READ id) + Q_PROPERTY(QString udi READ udi) + Q_PROPERTY(int balance READ balance WRITE setBalance) + Q_PROPERTY(QStringList controls READ controls) + + public: + DBusMixerWrapper(Mixer* parent, const QString& path); + ~DBusMixerWrapper(); + QString driverName(); + + QStringList controls(); + QString masterControl(); + + bool isOpened(); + QString readableName(); + QString id(); + QString udi(); + + int balance(); + void setBalance(int balance); + public slots: + void controlsChange(int changeType); + private: + void createDeviceWidgets(); + void refreshVolumeLevels(); + Mixer *m_mixer; + QString m_dbusPath; +}; + +#endif /* DBUSMIXERWRAPPER_H */ diff --git a/kmix/dbus/dbusmixsetwrapper.cpp b/kmix/dbus/dbusmixsetwrapper.cpp new file mode 100644 index 00000000..63012d7c --- /dev/null +++ b/kmix/dbus/dbusmixsetwrapper.cpp @@ -0,0 +1,126 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2011 Igor Poboiko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "dbusmixsetwrapper.h" + +#include "core/mixdevice.h" +#include "core/ControlManager.h" +#include "mixsetadaptor.h" + +DBusMixSetWrapper* DBusMixSetWrapper::instanceSingleton; + +void DBusMixSetWrapper::initialize(QObject* parent, const QString& path) +{ + /* This should not happen! */ + if (instanceSingleton) + delete instanceSingleton; + instanceSingleton = new DBusMixSetWrapper(parent, path); +} + +DBusMixSetWrapper* DBusMixSetWrapper::instance() { + return instanceSingleton; +} + +DBusMixSetWrapper::DBusMixSetWrapper(QObject* parent, const QString& path) + : QObject(parent) + , m_dbusPath( path ) +{ + new MixSetAdaptor( this ); + QDBusConnection::sessionBus().registerObject( m_dbusPath, this ); + + ControlManager::instance().addListener( + QString(), + ControlChangeType::MasterChanged, + this, + QString("DBusMixSetWrapper")); +} + +DBusMixSetWrapper::~DBusMixSetWrapper() +{ +} + +void DBusMixSetWrapper::controlsChange(int changeType) +{ + ControlChangeType::Type type = ControlChangeType::fromInt(changeType); + switch (type) + { + case ControlChangeType::MasterChanged: + signalMasterChanged(); + break; + default: + ControlManager::warnUnexpectedChangeType(type, this); + } +} + +QStringList DBusMixSetWrapper::mixers() const +{ + QStringList result; + Q_FOREACH(Mixer* mixer, Mixer::mixers()) + result.append( mixer->dbusPath() ); + return result; +} + +QString DBusMixSetWrapper::currentMasterMixer() const +{ + Mixer* masterMixer = Mixer::getGlobalMasterMixer(); + return masterMixer ? masterMixer->id() : QString(); +} + +QString DBusMixSetWrapper::currentMasterControl() const +{ + shared_ptr masterControl = Mixer::getGlobalMasterMD(); + return masterControl ? masterControl->id() : QString(); +} + +QString DBusMixSetWrapper::preferredMasterMixer() const +{ + return Mixer::getGlobalMasterPreferred().getCard(); +} + +QString DBusMixSetWrapper::preferredMasterControl() const +{ + return Mixer::getGlobalMasterPreferred().getControl(); +} + +void DBusMixSetWrapper::setCurrentMaster(const QString &mixer, const QString &control) +{ + Mixer::setGlobalMaster(mixer, control, false); +} + +void DBusMixSetWrapper::setPreferredMaster(const QString &mixer, const QString &control) +{ + Mixer::setGlobalMaster(mixer, control, true); +} + +void DBusMixSetWrapper::signalMixersChanged() +{ + QDBusMessage signal = QDBusMessage::createSignal( m_dbusPath, + "org.kde.KMix.MixSet", "mixersChanged" ); + QDBusConnection::sessionBus().send( signal ); +} + +void DBusMixSetWrapper::signalMasterChanged() +{ + QDBusMessage signal = QDBusMessage::createSignal( m_dbusPath, + "org.kde.KMix.MixSet", "masterChanged" ); + QDBusConnection::sessionBus().send( signal ); +} + +#include "dbusmixsetwrapper.moc" diff --git a/kmix/dbus/dbusmixsetwrapper.h b/kmix/dbus/dbusmixsetwrapper.h new file mode 100644 index 00000000..f7c67d1d --- /dev/null +++ b/kmix/dbus/dbusmixsetwrapper.h @@ -0,0 +1,60 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2011 Igor Poboiko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef DBUSMIXSETWRAPPER_H +#define DBUSMIXSETWRAPPER_H + +#include +#include "core/mixer.h" + +class DBusMixSetWrapper : public QObject +{ + Q_OBJECT + Q_PROPERTY(QStringList mixers READ mixers) + Q_PROPERTY(QString currentMasterMixer READ currentMasterMixer) + Q_PROPERTY(QString currentMasterControl READ currentMasterControl) + Q_PROPERTY(QString preferredMasterMixer READ preferredMasterMixer) + Q_PROPERTY(QString preferredMasterControl READ preferredMasterControl) + public: + static void initialize(QObject* parent, const QString& path); + static DBusMixSetWrapper* instance(); + + DBusMixSetWrapper(QObject* parent, const QString& path); + ~DBusMixSetWrapper(); + + void signalMixersChanged(); + void signalMasterChanged(); + public slots: + QStringList mixers() const; + + QString currentMasterMixer() const; + QString currentMasterControl() const; + QString preferredMasterMixer() const; + QString preferredMasterControl() const; + void setCurrentMaster(const QString &mixer, const QString &control); + void setPreferredMaster(const QString &mixer, const QString &control); + void controlsChange(int changeType); + private: + static DBusMixSetWrapper* instanceSingleton; + + QString m_dbusPath; +}; + +#endif /* DBUSMIXSETWRAPPER_H */ diff --git a/kmix/dbus/org.kde.kmix.control.xml b/kmix/dbus/org.kde.kmix.control.xml new file mode 100644 index 00000000..c66d1c6b --- /dev/null +++ b/kmix/dbus/org.kde.kmix.control.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff --git a/kmix/dbus/org.kde.kmix.mixer.xml b/kmix/dbus/org.kde.kmix.mixer.xml new file mode 100644 index 00000000..23fa7804 --- /dev/null +++ b/kmix/dbus/org.kde.kmix.mixer.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/kmix/dbus/org.kde.kmix.mixset.xml b/kmix/dbus/org.kde.kmix.mixset.xml new file mode 100644 index 00000000..fafc50a3 --- /dev/null +++ b/kmix/dbus/org.kde.kmix.mixset.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/kmix/doc/CMakeLists.txt b/kmix/doc/CMakeLists.txt new file mode 100644 index 00000000..0ce90e97 --- /dev/null +++ b/kmix/doc/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kde4_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en SUBDIR kmix) diff --git a/kmix/doc/ControlNames.txt b/kmix/doc/ControlNames.txt new file mode 100644 index 00000000..5b18298e --- /dev/null +++ b/kmix/doc/ControlNames.txt @@ -0,0 +1,84 @@ +This document describes standard names of mixer controls. + +Syntax: SOURCE [DIRECTION] FUNCTION + +DIRECTION: + (both directions) + Playback + Capture + Bypass Playback + Bypass Capture + +FUNCTION: + Switch (on/off switch) + Volume + Route (route control, hardware specific) + +SOURCE: + Master + Master Mono + Hardware Master + Headphone + PC Speaker + Phone + Phone Input + Phone Output + Synth + FM + Mic + Line + CD + Video + Zoom Video + Aux + PCM + PCM Front + PCM Rear + PCM Pan + Loopback + Analog Loopback (D/A -> A/D loopback) + Digital Loopback (playback -> capture loopback - without analog path) + Mono + Mono Output + Multi + ADC + Wave + Music + I2S + IEC958 + +Exceptions: + [Digital] Capture Source + [Digital] Capture Switch (aka input gain switch) + [Digital] Capture Volume (aka input gain volume) + [Digital] Playback Switch (aka output gain switch) + [Digital] Playback Volume (aka output gain volume) + Tone Control - Switch + Tone Control - Bass + Tone Control - Treble + 3D Control - Switch + 3D Control - Center + 3D Control - Depth + 3D Control - Wide + 3D Control - Space + 3D Control - Level + Mic Boost [(?dB)] + +PCM interface: + + Sample Clock Source { "Word", "Internal", "AutoSync" } + Clock Sync Status { "Lock", "Sync", "No Lock" } + External Rate /* external capture rate */ + Capture Rate /* capture rate taken from external source */ + +IEC958 (S/PDIF) interface: + + IEC958 [...] [Playback|Capture] Switch /* turn on/off the IEC958 interface */ + IEC958 [...] [Playback|Capture] Volume /* digital volume control */ + IEC958 [...] [Playback|Capture] Default /* default or global value - read/write */ + IEC958 [...] [Playback|Capture] Mask /* consumer and professional mask */ + IEC958 [...] [Playback|Capture] Con Mask /* consumer mask */ + IEC958 [...] [Playback|Capture] Pro Mask /* professional mask */ + IEC958 [...] [Playback|Capture] PCM Stream /* the settings assigned to a PCM stream */ + IEC958 Q-subcode [Playback|Capture] Default /* Q-subcode bits */ + IEC958 Preamble [Playback|Capture] Default /* burst preamble words (4*16bits) */ diff --git a/kmix/doc/index.docbook b/kmix/doc/index.docbook new file mode 100644 index 00000000..6140fb3e --- /dev/null +++ b/kmix/doc/index.docbook @@ -0,0 +1,457 @@ + + + + + +]> + + + + +The &kmix; Handbook + + + +Gaurav +Chaturvedi +
gaurav.p.chaturvedi@gmail.com
+
+ + +Christian +Esken +
esken@kde.org
+Developer +
+ + +Helio +Chissini de Castro +
helio@kde.org
+Developer +
+ + + +Brian +Hanson +
bhanson@hotmail.com
+Developer +
+ + +
+ + +19962005 +Christian Esken & &Matt.Johnston; + + +&FDLNotice; + +2013-12-21 +4.5 (&kde; 4.13) + + +&kmix; is an application that allows you to change the volume of your sound card. + + + +KDE +KMix +kdemultimedia +sound +volume +mixer + + + +
+ + +Introduction + + + +&kmix; supports several platforms and sound drivers. + + + +If you have both ALSA and Open Sound System drivers installed, &kmix; will use the ALSA driver. + + + + Screenshot of &kmix; + + + + + + Screenshot of &kmix; + + + + + + + + + + +Main Window + +Overview of &kmix; Main Window + + Screenshot of &kmix; + + + + + + Screenshot of &kmix; + + + + + +File Options + + &kmix; File options + + + + + + &kmix; File options + + + + + + + + +&Esc; + +File +Hide Mixer Window + +Hide Mixer Window + + + + + +Settings + + + &kmix; Settings + + + + + + &kmix; Settings + + + + + + + +Settings +Audio Setup + +Open &phonon; &systemsettings; module to configure the sound and video device preference and the backends used by &kde; multimedia API, &phonon;. + + + + + + + +Select Master Channel + + &kmix; Master Channel + + + + + + &kmix; Master Channel + + + +Select your &kmix; master channel. + + + +Configure &kmix; + + + General configuration + + + General configuration of &kmix; + + + + + + General configuration of &kmix; + + + +Configure general &kmix; parameters. + + + + +Behavior +Volume Feedback + +Check this box to enable audible feedback on volume change. + + + + +Behavior +Volume Overdrive + +Check this box to allow volume to be more than recommended value (sometimes PulseAudio maximal volume exceeds the normal value). &kmix; restart is needed for this setting to take effect. + + + Uncheck this item if there are audible sound distortions at the maximal volume. + + + + + + + +Visual +Show tickmarks + +Check this box to show tickmarks on controls. + + + + +Visual +Show labels + +Check this box to show labels of controls. + + + + +Visual +Show On Screen Display (OSD) + +Check this box to enable OSD show on hovering &kmix; tray widget. + + + + +Slider orientation +Horizontal + +Check this radio button to orientate the control slider horizontally. + + + + +Slider orientation +Vertical + +Check this radio button to orientate the control slider vertically. + + + + +Slider orientation (System tray volume control) +Horizontal/Vertical + +Same as the previous two radio buttons but for the system tray volume control (the panel that is shown after &LMB; click on &kmix; tray icon). + + + + + + + + Start configuration + + + Start configuration of &kmix; + + + + + + Start configuration of &kmix; + + + +This page allows you to configure various &kmix; start parameters. + + + + +Startup +Restore volumes on login + +Check this box to enable volume restoration on login. + + + Dynamic controls from PulseAudio and MPRIS2 will not be restored. + + + + + + + +Startup +Autostart + +Check this box to enable &kmix; autostart with desktop environment. + + + + + + + + Sound menu configuration + + + Sound menu configuration of &kmix; + + + + + + Sound menu configuration of &kmix; + + + +This page allows you to configure various &kmix; sound menu parameters. + + + +Dock in system tray + +Check this box to dock &kmix; in system tray. + + + + + It is possible to select mixers that will be shown in the sound menu using the corresponding list on this page. + + + + + + + +Advanced usage + +Remote controlling and scripting via &DBus; + +You can control &kmix; via &DBus;. This is for advanced users who want to achieve special tasks, like muting the PC every day at 10 pm, or for any other scripting task. + + + +List all &kmix; related &DBus; methods and properties +qdbus org.kde.kmix + + + +Getting volume level (0-100%): +qdbus org.kde.kmix /Mixers/0/Master_0 org.freedesktop.DBus.Properties.Get org.kde.KMix.Control volume + + + +Setting volume level (0-100%): +qdbus org.kde.kmix /Mixers/0/Master_0 org.freedesktop.DBus.Properties.Set org.kde.KMix.Control volume 70 + + + + + + +Credits and License + + +&kmix; + + +Documentation copyright © 2010 Gaurav Chaturvedi gaurav.p.chaturvedi@gmail.com +Thanks to: +&Matt.Johnston; &Matt.Johnston.mail; (copy and pasted a lot of things from his old &kmix; documentation) + + +&underFDL; +&underGPL; + + + + +Installation + + +How to obtain &kmix; + +&install.intro.documentation; + + + + +Compilation and installation + +&install.compile.documentation; + + + + +&documentation.index; +
+ + diff --git a/kmix/doc/kmix-configure-general.png b/kmix/doc/kmix-configure-general.png new file mode 100644 index 0000000000000000000000000000000000000000..8ac7854860fd36da63ace5647877129531926f4e GIT binary patch literal 17571 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 literal 0 HcmV?d00001 diff --git a/kmix/doc/kmix-configure-start.png b/kmix/doc/kmix-configure-start.png new file mode 100644 index 0000000000000000000000000000000000000000..e07163e347f1f0c3fe19f8b8fad713d2aa1cd7f7 GIT binary patch literal 12697 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

$+pp7whdt_2$0|X^#;4#qiZSf?w^jp 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

8kZw9WG#{E=~0mz?8k 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 AFaQ7m literal 0 HcmV?d00001 diff --git a/kmix/gui/dialogaddview.cpp b/kmix/gui/dialogaddview.cpp new file mode 100644 index 00000000..4cd196a3 --- /dev/null +++ b/kmix/gui/dialogaddview.cpp @@ -0,0 +1,243 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "gui/dialogaddview.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "core/mixdevice.h" +#include "core/mixer.h" + + +QStringList DialogAddView::viewNames; +QStringList DialogAddView::viewIds; + + +DialogAddView::DialogAddView(QWidget* parent, Mixer *mixer ) + : KDialog( parent ) +{ + // TODO 000 Adding View for MPRIS2 is broken. We need at least a dummy XML GUI Profile. Also the + // fixed list below is plain wrong. Actually we should get the Profile list from either the XML files or + // from the backend. The latter is probably easier for now. + if ( viewNames.isEmpty() ) + { + // initialize static list. Later this list could be generated from the actually installed profiles + viewNames.append(i18n("All controls")); + viewNames.append(i18n("Only playback controls")); + viewNames.append(i18n("Only capture controls")); + + viewIds.append("default"); + viewIds.append("playback"); + viewIds.append("capture"); + } + + setCaption( i18n( "Add View" ) ); + if ( Mixer::mixers().count() > 0 ) + setButtons( Ok|Cancel ); + else { + setButtons( Cancel ); + } + setDefaultButton( Ok ); + _layout = 0; + m_vboxForScrollView = 0; + m_scrollableChannelSelector = 0; + m_buttonGroupForScrollView = 0; + createWidgets(mixer); // Open with Mixer Hardware #0 + +} + +DialogAddView::~DialogAddView() +{ + delete _layout; + delete m_vboxForScrollView; +} + +/** + * Create basic widgets of the Dialog. + */ +void DialogAddView::createWidgets(Mixer *ptr_mixer) +{ + m_mainFrame = new QFrame( this ); + setMainWidget( m_mainFrame ); + _layout = new QVBoxLayout(m_mainFrame); + _layout->setMargin(0); + + if ( Mixer::mixers().count() > 1 ) { + // More than one Mixer => show Combo-Box to select Mixer + // Mixer widget line + QHBoxLayout* mixerNameLayout = new QHBoxLayout(); + _layout->addItem( mixerNameLayout ); + mixerNameLayout->setSpacing(KDialog::spacingHint()); + + QLabel *qlbl = new QLabel( i18n("Select mixer:"), m_mainFrame ); + mixerNameLayout->addWidget(qlbl); + qlbl->setFixedHeight(qlbl->sizeHint().height()); + + m_cMixer = new KComboBox( false, m_mainFrame); + m_cMixer->setObjectName( QLatin1String( "mixerCombo" ) ); + m_cMixer->setFixedHeight(m_cMixer->sizeHint().height()); + connect( m_cMixer, SIGNAL(activated(int)), this, SLOT(createPageByID(int)) ); + + for( int i =0; iaddItem( mixer->readableName() ); + } // end for all_Mixers + // Make the current Mixer the current item in the ComboBox + int findIndex = m_cMixer->findText( ptr_mixer->readableName() ); + if ( findIndex != -1 ) m_cMixer->setCurrentIndex( findIndex ); + + + m_cMixer->setToolTip( i18n("Current mixer" ) ); + mixerNameLayout->addWidget(m_cMixer); + + } // end if (more_than_1_Mixer) + + + if ( Mixer::mixers().count() > 0 ) { + QLabel *qlbl = new QLabel( i18n("Select the design for the new view:"), m_mainFrame ); + _layout->addWidget(qlbl); + + createPage(); + connect( this, SIGNAL(okClicked()) , this, SLOT(apply()) ); + } + else { + QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), m_mainFrame ); + _layout->addWidget(qlbl); + } +} + +/** + * Create RadioButton's for the Mixer with number 'mixerId'. + * @par mixerId The Mixer, for which the RadioButton's should be created. + */ +void DialogAddView::createPageByID(int mixerId) +{ + //kDebug(67100) << "DialogAddView::createPage()"; + QString selectedMixerName = m_cMixer->itemText(mixerId); + for( int i =0; ireadableName() == selectedMixerName ) { + createPage(); + break; + } + } // for +} + +/** + * Create RadioButton's for the Mixer with number 'mixerId'. + * @par mixerId The Mixer, for which the RadioButton's should be created. + * TODO: The mixer's backend MUST be inspected to find out the supported profiles. + */ +void DialogAddView::createPage() +{ + /** --- Reset page ----------------------------------------------- + * In case the user selected a new Mixer via m_cMixer, we need + * to remove the stuff created on the last call. + */ + // delete the VBox. This should automatically remove all contained QRadioButton's. + delete m_vboxForScrollView; + delete m_scrollableChannelSelector; + delete m_buttonGroupForScrollView; + enableButton(Ok, false); + + + /** Reset page end -------------------------------------------------- */ + + m_buttonGroupForScrollView = new QButtonGroup(this); // invisible QButtonGroup + + m_scrollableChannelSelector = new QScrollArea(m_mainFrame); + _layout->addWidget(m_scrollableChannelSelector); + + m_vboxForScrollView = new KVBox(); + + + for( int i=0; isetObjectName(viewIds.at(i)); // The object name is used as ID here: see apply() + m_buttonGroupForScrollView->addButton(qrb); + } + + m_scrollableChannelSelector->setWidget(m_vboxForScrollView); + m_vboxForScrollView->show(); // show() is necessary starting with the second call to createPage() +} + + +void DialogAddView::profileRbtoggled(bool selected) +{ + if ( selected) + enableButton(Ok, true); +} + +void DialogAddView::apply() +{ + Mixer *mixer = 0; + if ( Mixer::mixers().count() == 1 ) { + // only one mixer => no combo box => take first entry + mixer = (Mixer::mixers())[0]; + } + else if ( Mixer::mixers().count() > 1 ) { + // find mixer that is currently active in the ComboBox + QString selectedMixerName = m_cMixer->itemText(m_cMixer->currentIndex()); + + for( int i =0; ireadableName() == selectedMixerName ) { + mixer = (Mixer::mixers())[i]; + break; + } + } // for + } + + QAbstractButton* button = m_buttonGroupForScrollView->checkedButton(); + if ( button != 0 ) { + QString viewName = button->objectName(); + if ( mixer == 0 ) { + kError(67100) << "DialogAddView::createPage(): Invalid Mixer (mixer=0)" << endl; + return; // can not happen + } + else { + kDebug() << "We should now create a new view " << viewName << " for mixer " << mixer->id(); + resultMixerId = mixer->id(); + resultViewName = viewName; + } + } +} + +#include "dialogaddview.moc" + diff --git a/kmix/gui/dialogaddview.h b/kmix/gui/dialogaddview.h new file mode 100644 index 00000000..570e316e --- /dev/null +++ b/kmix/gui/dialogaddview.h @@ -0,0 +1,69 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef DIALOGADDVIEW_H +#define DIALOGADDVIEW_H + +class QButtonGroup; +#include +class KComboBox; +#include +class QScrollArea; +#include +class QVBoxLayout; + +#include + +class Mixer; + +class DialogAddView : public KDialog +{ + Q_OBJECT + public: + DialogAddView(QWidget* parent, Mixer*); + ~DialogAddView(); + + QString getresultViewName() { return resultViewName; } + QString getresultMixerId() { return resultMixerId; } + + public slots: + void apply(); + + private: + void createWidgets(Mixer*); + void createPage(); + QVBoxLayout* _layout; + KComboBox* m_cMixer; + QScrollArea* m_scrollableChannelSelector; + KVBox *m_vboxForScrollView; + QButtonGroup *m_buttonGroupForScrollView; + QFrame *m_mainFrame; + static QStringList viewNames; + static QStringList viewIds; + + QString resultViewName; + QString resultMixerId; + + private slots: + void createPageByID(int mixerId); + void profileRbtoggled(bool selected); +}; + +#endif diff --git a/kmix/gui/dialogchoosebackends.cpp b/kmix/gui/dialogchoosebackends.cpp new file mode 100644 index 00000000..66ec8e7c --- /dev/null +++ b/kmix/gui/dialogchoosebackends.cpp @@ -0,0 +1,165 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "gui/dialogchoosebackends.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "core/ControlManager.h" +#include "core/GlobalConfig.h" +#include "core/mixdevice.h" +#include "core/mixer.h" + +/** + * Creates a dialog to choose mixers from. All currently known mixers will be shown, and the given mixerID's + * will be preselected. + * + * @param mixerIds A set of preselected mixer ID's + * @param noButtons is a migration option. When DialogChooseBackends has been integrated as a Tab, it will be removed. + */ +DialogChooseBackends::DialogChooseBackends(QWidget* parent, const QSet& mixerIds) + : QWidget(parent), modified(false) +{ +// setCaption( i18n( "Select Mixers" ) ); +// setButtons( None ); + + _layout = 0; + m_vboxForScrollView = 0; + m_scrollableChannelSelector = 0; + m_buttonGroupForScrollView = 0; + createWidgets(mixerIds); + +} + +DialogChooseBackends::~DialogChooseBackends() +{ + delete _layout; + delete m_vboxForScrollView; +} + +/** + * Create basic widgets of the Dialog. + */ +void DialogChooseBackends::createWidgets(const QSet& mixerIds) +{ + m_mainFrame = this; +// m_mainFrame = new QFrame( this ); +// setMainWidget( m_mainFrame ); + _layout = new QVBoxLayout(m_mainFrame); + _layout->setMargin(0); + + if ( !Mixer::mixers().isEmpty() ) + { + QLabel *qlbl = new QLabel( i18n("Select the Mixers to display in the sound menu"), m_mainFrame ); + _layout->addWidget(qlbl); + + createPage(mixerIds); + } + else + { + QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), m_mainFrame ); + _layout->addWidget(qlbl); + } +} + + +/** + * Create RadioButton's for the Mixer with number 'mixerId'. + * @par mixerId The Mixer, for which the RadioButton's should be created. + */ +void DialogChooseBackends::createPage(const QSet& mixerIds) +{ + m_buttonGroupForScrollView = new QButtonGroup(this); // invisible QButtonGroup + m_scrollableChannelSelector = new QScrollArea(m_mainFrame); + +#ifndef QT_NO_ACCESSIBILITY + m_scrollableChannelSelector->setAccessibleName(i18n("Select Mixers")); +#endif + + _layout->addWidget(m_scrollableChannelSelector); + + m_vboxForScrollView = new KVBox(); + + bool hasMixerFilter = !mixerIds.isEmpty(); + kDebug() << "MixerIds=" << mixerIds; + foreach ( Mixer* mixer, Mixer::mixers()) + { + QCheckBox* qrb = new QCheckBox(mixer->readableName(true), m_vboxForScrollView); + qrb->setObjectName(mixer->id());// The object name is used as ID here: see getChosenBackends() + connect(qrb, SIGNAL(stateChanged(int)), SLOT(backendsModifiedSlot())); + checkboxes.append(qrb); + bool mixerShouldBeShown = !hasMixerFilter || mixerIds.contains(mixer->id()); + qrb->setChecked(mixerShouldBeShown); + } + + m_scrollableChannelSelector->setWidget(m_vboxForScrollView); + m_vboxForScrollView->show(); // show() is necessary starting with the second call to createPage() +} + +QSet DialogChooseBackends::getChosenBackends() +{ + QSet newMixerList; + foreach ( QCheckBox* qcb, checkboxes) + { + if (qcb->isChecked()) + { + newMixerList.insert(qcb->objectName()); + kDebug() << "apply found " << qcb->objectName(); + } + } + kDebug() << "New list is " << newMixerList; + return newMixerList; +} + +/** + * Returns whether there were any modifications (activation/deactivation) and resets the flag. + * @return + */ +bool DialogChooseBackends::getAndResetModifyFlag() +{ + bool modifiedOld = modified; + modified = false; + return modifiedOld; +} + +bool DialogChooseBackends::getModifyFlag() +{ + return modified; +} + +void DialogChooseBackends::backendsModifiedSlot() +{ + modified = true; + emit backendsModified(); +} + +#include "dialogchoosebackends.moc" + diff --git a/kmix/gui/dialogchoosebackends.h b/kmix/gui/dialogchoosebackends.h new file mode 100644 index 00000000..d7222fd9 --- /dev/null +++ b/kmix/gui/dialogchoosebackends.h @@ -0,0 +1,66 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef DIALOGCHOOSEBACKENDS_H +#define DIALOGCHOOSEBACKENDS_H + +class QButtonGroup; + +#include +#include +class QScrollArea; +class QVBoxLayout; + +class KComboBox; +#include +#include + +class Mixer; + +class DialogChooseBackends: public QWidget +{ +Q_OBJECT +public: + DialogChooseBackends(QWidget* parent, const QSet& backends); + ~DialogChooseBackends(); + + QSet getChosenBackends(); + bool getAndResetModifyFlag(); + bool getModifyFlag(); + +signals: + void backendsModified(); + +private: + void createWidgets(const QSet& backends); + void createPage(const QSet& backends); + QVBoxLayout* _layout; + QScrollArea* m_scrollableChannelSelector; + KVBox *m_vboxForScrollView; + QButtonGroup *m_buttonGroupForScrollView; + QList checkboxes; + QWidget *m_mainFrame; + bool modified; + +private slots: + void backendsModifiedSlot(); +}; + +#endif diff --git a/kmix/gui/dialogselectmaster.cpp b/kmix/gui/dialogselectmaster.cpp new file mode 100644 index 00000000..04f18c4d --- /dev/null +++ b/kmix/gui/dialogselectmaster.cpp @@ -0,0 +1,218 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "gui/dialogselectmaster.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "core/ControlManager.h" +#include "core/mixdevice.h" +#include "core/mixer.h" + +DialogSelectMaster::DialogSelectMaster( Mixer *mixer, QWidget *parent ) + : KDialog( parent ) +{ + setCaption( i18n( "Select Master Channel" ) ); + if ( Mixer::mixers().count() > 0 ) + setButtons( Ok|Cancel ); + else { + setButtons( Cancel ); + } + setDefaultButton( Ok ); + _layout = 0; + m_vboxForScrollView = 0; + m_scrollableChannelSelector = 0; + m_buttonGroupForScrollView = 0; + createWidgets(mixer); // Open with Mixer Hardware #0 + +} + +DialogSelectMaster::~DialogSelectMaster() +{ + delete _layout; + delete m_vboxForScrollView; +} + +/** + * Create basic widgets of the Dialog. + */ +void DialogSelectMaster::createWidgets(Mixer *ptr_mixer) +{ + m_mainFrame = new QFrame( this ); + setMainWidget( m_mainFrame ); + _layout = new QVBoxLayout(m_mainFrame); + _layout->setMargin(0); + + if ( Mixer::mixers().count() > 1 ) { + // More than one Mixer => show Combo-Box to select Mixer + // Mixer widget line + QHBoxLayout* mixerNameLayout = new QHBoxLayout(); + _layout->addItem( mixerNameLayout ); + mixerNameLayout->setSpacing(KDialog::spacingHint()); + + QLabel *qlbl = new QLabel( i18n("Current mixer:"), m_mainFrame ); + mixerNameLayout->addWidget(qlbl); + qlbl->setFixedHeight(qlbl->sizeHint().height()); + + m_cMixer = new KComboBox( false, m_mainFrame); + m_cMixer->setObjectName( QLatin1String( "mixerCombo" ) ); + m_cMixer->setFixedHeight(m_cMixer->sizeHint().height()); + connect( m_cMixer, SIGNAL(activated(int)), this, SLOT(createPageByID(int)) ); + + for( int i =0; iaddItem( mixer->readableName(), mixer->id() ); + } // end for all_Mixers + // Make the current Mixer the current item in the ComboBox + int findIndex = m_cMixer->findData( ptr_mixer->id() ); + if ( findIndex != -1 ) m_cMixer->setCurrentIndex( findIndex ); + + + m_cMixer->setToolTip( i18n("Current mixer" ) ); + mixerNameLayout->addWidget(m_cMixer); + + } // end if (more_than_1_Mixer) + + + if ( Mixer::mixers().count() > 0 ) { + QLabel *qlbl = new QLabel( i18n("Select the channel representing the master volume:"), m_mainFrame ); + _layout->addWidget(qlbl); + + createPage(ptr_mixer); + connect( this, SIGNAL(okClicked()) , this, SLOT(apply()) ); + } + else { + QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), m_mainFrame ); + _layout->addWidget(qlbl); + } +} + +/** + * Create RadioButton's for the Mixer with number 'mixerId'. + * @par mixerId The Mixer, for which the RadioButton's should be created. + */ +void DialogSelectMaster::createPageByID(int mixerId) +{ + QString mixer_id = m_cMixer->itemData(mixerId).toString(); + Mixer * mixer = Mixer::findMixer(mixer_id); + + if ( mixer != NULL ) + createPage(mixer); +} + +/** + * Create RadioButton's for the Mixer with number 'mixerId'. + * @par mixerId The Mixer, for which the RadioButton's should be created. + */ +void DialogSelectMaster::createPage(Mixer* mixer) +{ + + /** --- Reset page ----------------------------------------------- + * In case the user selected a new Mixer via m_cMixer, we need + * to remove the stuff created on the last call. + */ + // delete the VBox. This should automatically remove all contained QRadioButton's. + delete m_vboxForScrollView; + delete m_scrollableChannelSelector; + delete m_buttonGroupForScrollView; + + /** Reset page end -------------------------------------------------- */ + + m_buttonGroupForScrollView = new QButtonGroup(this); // invisible QButtonGroup + //m_buttonGroupForScrollView->hide(); + + m_scrollableChannelSelector = new QScrollArea(m_mainFrame); + +#ifndef QT_NO_ACCESSIBILITY + m_scrollableChannelSelector->setAccessibleName( i18n("Select Master Channel") ); +#endif + +// m_scrollableChannelSelector->viewport()->setBackgroundRole(QPalette::Background); + _layout->addWidget(m_scrollableChannelSelector); + + m_vboxForScrollView = new KVBox(); //m_scrollableChannelSelector->viewport() + + + shared_ptr master = mixer->getLocalMasterMD(); + QString masterKey = ( master.get() != 0 ) ? master->id() : "----noMaster---"; // Use non-matching name as default + + const MixSet& mixset = mixer->getMixSet(); + MixSet& mset = const_cast(mixset); + for( int i=0; i< mset.count(); ++i ) + { + shared_ptr md = mset[i]; + // Create a RadioButton for each MixDevice (excluding Enum's) + if ( md->playbackVolume().hasVolume() ) + { +// kDebug(67100) << "DialogSelectMaster::createPage() mset append qrb"; + QString mdName = md->readableName(); + mdName.replace('&', "&&"); // Quoting the '&' needed, to prevent QRadioButton creating an accelerator + QRadioButton* qrb = new QRadioButton(mdName, m_vboxForScrollView); + qrb->setObjectName(md->id()); // The object name is used as ID here: see apply() + m_buttonGroupForScrollView->addButton(qrb); //(qrb, md->num()); + qrb->setChecked(md->id() == masterKey); // preselect the current master + } + } + + m_scrollableChannelSelector->setWidget(m_vboxForScrollView); + m_vboxForScrollView->show(); // show() is necessary starting with the second call to createPage() +} + + +void DialogSelectMaster::apply() +{ + Mixer *mixer = 0; + if ( Mixer::mixers().count() == 1 ) { + // only one mxier => no combo box => take first entry + mixer = (Mixer::mixers())[0]; + } + else if ( Mixer::mixers().count() > 1 ) { + // find mixer that is currently active in the ComboBox + int idx = m_cMixer->currentIndex(); + QString mixer_id = m_cMixer->itemData(idx).toString(); + mixer = Mixer::findMixer(mixer_id); + } + + if ( mixer == 0 ) + return; // User must have unplugged everything + + QAbstractButton* button = m_buttonGroupForScrollView->checkedButton(); + if ( button != 0 ) + { + QString control_id = button->objectName(); + mixer->setLocalMasterMD( control_id ); + Mixer::setGlobalMaster(mixer->id(), control_id, true); + ControlManager::instance().announce(mixer->id(), ControlChangeType::MasterChanged, QString("Select Master Dialog")); + } +} + +#include "dialogselectmaster.moc" + diff --git a/kmix/gui/dialogselectmaster.h b/kmix/gui/dialogselectmaster.h new file mode 100644 index 00000000..50f36de3 --- /dev/null +++ b/kmix/gui/dialogselectmaster.h @@ -0,0 +1,60 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef DIALOGSELECTMASTER_H +#define DIALOGSELECTMASTER_H + +class QButtonGroup; +class KComboBox; +#include +class QScrollArea; +#include +class QVBoxLayout; + +#include + +class Mixer; + +class DialogSelectMaster : public KDialog +{ + Q_OBJECT + public: + DialogSelectMaster(Mixer * = 0, QWidget *parent = 0); + ~DialogSelectMaster(); + + public slots: + void apply(); + + private: + void createWidgets(Mixer*); + void createPage(Mixer*); + QVBoxLayout* _layout; + KComboBox* m_cMixer; + QScrollArea* m_scrollableChannelSelector; + KVBox *m_vboxForScrollView; + QButtonGroup *m_buttonGroupForScrollView; + //QStringList m_mixerPKs; + QFrame *m_mainFrame; + + private slots: + void createPageByID(int mixerId); +}; + +#endif diff --git a/kmix/gui/dialogviewconfiguration.cpp b/kmix/gui/dialogviewconfiguration.cpp new file mode 100644 index 00000000..567c2c82 --- /dev/null +++ b/kmix/gui/dialogviewconfiguration.cpp @@ -0,0 +1,460 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "dialogviewconfiguration.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "gui/guiprofile.h" +#include "gui/mixdevicewidget.h" +#include "core/ControlManager.h" +#include "core/mixdevice.h" +#include "core/mixer.h" + +DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent) : + QListWidgetItem(parent) +{ + kDebug() << "DialogViewConfigurationItem() default constructor"; + refreshItem(); +} + + + +DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, QString id, bool shown, QString name, int splitted, const QString& iconName) : + QListWidgetItem(parent), _id(id), _shown(shown), _name(name), _splitted(splitted), _iconName(iconName) +{ + refreshItem(); +} + +void DialogViewConfigurationItem::refreshItem() +{ + setFlags((flags() | Qt::ItemIsDragEnabled) & ~Qt::ItemIsDropEnabled); + setText(_name); + setIcon(KIconLoader::global()->loadIcon( _iconName, KIconLoader::Small, KIconLoader::SizeSmallMedium ) ); + setData(Qt::ToolTipRole, _id); // a hack. I am giving up to do it right + setData(Qt::DisplayRole, _name); +} + +/** + * Serializer. Used for DnD. + */ +static QDataStream & operator<< ( QDataStream & s, const DialogViewConfigurationItem & item ) { + s << item._id; + s << item._shown; + s << item._name; + s << item._splitted; + s << item._iconName; + //kDebug() << "<< serialize << " << s; + return s; +} + +/** + * Deserializer. Used for DnD. + */ +static QDataStream & operator>> ( QDataStream & s, DialogViewConfigurationItem & item ) { + QString id; + s >> id; + item._id = id; + bool shown; + s >> shown; + item._shown = shown; + QString name; + s >> name; + item._name = name; + int splitted; + s >> splitted; + item._splitted = splitted; + QString iconName; + s >> iconName; + item._iconName = iconName; + //kDebug() << ">> deserialize >> " << id << name << iconName; + return s; +} + +DialogViewConfigurationWidget::DialogViewConfigurationWidget(QWidget *parent) + : QListWidget(parent), + m_activeList(true) +{ + setDragDropMode(QAbstractItemView::DragDrop); + setDropIndicatorShown(true); + setAcceptDrops(true); +setSelectionMode(QAbstractItemView::SingleSelection); +setDragEnabled(true); +viewport()->setAcceptDrops(true); +setAlternatingRowColors(true); + +} + + + +QMimeData* DialogViewConfigurationWidget::mimeData(const QList items) const +{ + if (items.isEmpty()) + return 0; + QMimeData* mimedata = new QMimeData(); + + DialogViewConfigurationItem* item = 0; + QByteArray data; + { + QDataStream stream(&data, QIODevice::WriteOnly); + // we only support single selection + item = static_cast(items.first()); + stream << *item; + } + + bool active = isActiveList(); + mimedata->setData("application/x-kde-action-list", data); + mimedata->setData("application/x-kde-source-treewidget", active ? "active" : "inactive"); + + return mimedata; +} + +bool DialogViewConfigurationWidget::dropMimeData(int index, const QMimeData * mimeData, Qt::DropAction /*action*/) +{ + const QByteArray data = mimeData->data("application/x-kde-action-list"); + if (data.isEmpty()) + return false; + QDataStream stream(data); + const bool sourceIsActiveList = mimeData->data("application/x-kde-source-treewidget") == "active"; + + DialogViewConfigurationItem* item = new DialogViewConfigurationItem(0); // needs parent, use this temporarily + stream >> *item; + item->refreshItem(); + emit dropped(this, index, item, sourceIsActiveList); + return true; +} + +DialogViewConfiguration::DialogViewConfiguration( QWidget*, ViewBase& view) + : KDialog( 0), + _view(view) +{ + setCaption( i18n( "Configure Channels" ) ); + setButtons( Ok|Cancel ); + setDefaultButton( Ok ); + frame = new QWidget( this ); + frame->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding); + + setMainWidget( frame ); + + // The _layout will hold two items: The title and the Drag-n-Drop area + _layout = new QVBoxLayout(frame ); + _layout->setMargin( 0 ); + _layout->setSpacing(KDialog::spacingHint()); + + // --- HEADER --- + qlb = new QLabel( i18n("Configuration of the channels. Drag icon to update."), frame ); + _layout->addWidget(qlb); + + _glayout = new QGridLayout(); + _layout->addLayout(_glayout); + + _qlw = 0; + _qlwInactive = 0; + createPage(); +} + + + +/** + * Drop an item from one list to the other + */ +void DialogViewConfiguration::slotDropped ( DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item, bool sourceIsActiveList ) +{ +//kDebug() << "dropped item (index" << index << "): " << item->_id << item->_shown << item->_name << item->_splitted << item->_iconName; + + if ( list == _qlw ) { + //DialogViewConfigurationItem* after = index > 0 ? static_cast(list->item(index-1)) : 0; + + //kDebug() << "after" << after->text() << after->internalTag(); + if ( sourceIsActiveList ) { + // has been dragged within the active list (moved). + _qlw->insertItem ( index, item ); + //moveActive(item, after); + } else { + // dragged from the inactive list to the active list + _qlw->insertItem ( index, item ); + //insertActive(item, after, true); + } + } + else if ( list == _qlwInactive ) { + // has been dragged to the inactive list -> remove from the active list. + //removeActive(item); + _qlwInactive->insertItem ( index, item ); + } + +} + + +void DialogViewConfiguration::addSpacer(int row, int col) +{ + QWidget *dummy = new QWidget(); + dummy->setFixedWidth(4); + _glayout->addWidget(dummy,row,col); +} + + +void DialogViewConfiguration::moveSelection(DialogViewConfigurationWidget* from, DialogViewConfigurationWidget* to) +{ + foreach ( QListWidgetItem* item, from->selectedItems() ) + { + QListWidgetItem *clonedItem = item->clone(); + to->addItem ( clonedItem ); + to->setCurrentItem(clonedItem); + delete item; + } +} + +void DialogViewConfiguration::moveSelectionToActiveList() +{ + moveSelection(_qlwInactive, _qlw); +} + +void DialogViewConfiguration::moveSelectionToInactiveList() +{ + moveSelection(_qlw, _qlwInactive); +} + +void DialogViewConfiguration::selectionChangedActive() +{ +// bool activeIsNotEmpty = _qlw->selectedItems().isEmpty(); + moveRightButton->setEnabled(! _qlw->selectedItems().isEmpty()); + moveLeftButton->setEnabled(false); +} + +void DialogViewConfiguration::selectionChangedInactive() +{ + moveLeftButton->setEnabled(! _qlwInactive->selectedItems().isEmpty()); + moveRightButton->setEnabled(false); +} + +/** + * Create basic widgets of the Dialog. + */ +void DialogViewConfiguration::createPage() +{ + QList &mdws = _view._mdws; + + QLabel *l1 = new QLabel( i18n("Visible channels") ); + _glayout->addWidget(l1,0,0); + + QLabel *l2 = new QLabel( i18n("Available channels") ); + _glayout->addWidget(l2,0,6); + + _qlwInactive = new DialogViewConfigurationWidget(frame); + _qlwInactive->setDragDropMode(QAbstractItemView::DragDrop); + _qlwInactive->setActiveList(false); + _glayout->addWidget(_qlwInactive,1,6); + connect(_qlwInactive, SIGNAL(dropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool)), + this , SLOT(slotDropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool))); + + addSpacer(1,1); + const KIcon& icon = KIcon( QLatin1String( "arrow-left" )); + moveLeftButton = new QPushButton(icon, ""); + moveLeftButton->setEnabled(false); + _glayout->addWidget(moveLeftButton,1,2); + connect(moveLeftButton, SIGNAL(clicked(bool)), SLOT(moveSelectionToActiveList())); + addSpacer(1,3); + + const KIcon& icon2 = KIcon( QLatin1String( "arrow-right" )); + moveRightButton = new QPushButton(icon2, ""); + moveRightButton->setEnabled(false); + _glayout->addWidget(moveRightButton,1,4); + connect(moveRightButton, SIGNAL(clicked(bool)), SLOT(moveSelectionToInactiveList())); + addSpacer(1,5); + + _qlw = new DialogViewConfigurationWidget(frame); + _glayout->addWidget(_qlw,1,0); + connect(_qlw , SIGNAL(dropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool)), + this , SLOT(slotDropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool))); + + // --- CONTROLS IN THE GRID ------------------------------------ + //QPalette::ColorRole bgRole; + for ( int i=0; iinherits("MixDeviceWidget") ) { + MixDeviceWidget *mdw = static_cast(qw); + shared_ptr md = mdw->mixDevice(); + QString mdName = md->readableName(); + + int splitted = -1; + if ( ! md->isEnum() ) { + splitted = ( md->playbackVolume().count() > 1) || ( md->captureVolume().count() > 1 ) ; + } + + //kDebug() << "add DialogViewConfigurationItem: " << mdName << " visible=" << mdw->isVisible() << "splitted=" << splitted; + if ( mdw->isVisible() ) { + new DialogViewConfigurationItem(_qlw, md->id(), mdw->isVisible(), mdName, splitted, mdw->mixDevice()->iconName()); + } + else { + new DialogViewConfigurationItem(_qlwInactive, md->id(), mdw->isVisible(), mdName, splitted, mdw->mixDevice()->iconName()); + } + +/* + if ( ! md->isEnum() && ( ( md->playbackVolume().count() > 1) || ( md->captureVolume().count() > 1) ) ) { + cb = new QCheckBox( "", vboxForScrollView ); // split + cb->setBackgroundRole(bgRole); + cb->setAutoFillBackground(true); + _qSplitCB.append(cb); + cb->setChecked( ! mdw->isStereoLinked() ); + grid->addWidget(cb,1+i,1); + } + else { + _qSplitCB.append(0); + } +*/ + /* + if ( ! md->isEnum() && ( md->playbackVolume().count() + md->captureVolume().count() >0 ) ) { + cb = new QCheckBox( "", vboxForScrollView ); // limit + cb->setBackgroundRole(bgRole); + cb->setAutoFillBackground(true); + _qLimitCB.append(cb); + grid->addWidget(cb,1+i,2); + } + else { + */ + //_qLimitCB.append(0); + /*}*/ + } // is not enum + } // for all MDW's + + + connect(_qlwInactive, SIGNAL(itemSelectionChanged()), + this , SLOT(selectionChangedInactive())); + connect(_qlw, SIGNAL(itemSelectionChanged()), + this , SLOT(selectionChangedActive())); + +// scrollArea->updateGeometry(); + updateGeometry(); + connect( this, SIGNAL(okClicked()) , this, SLOT(apply()) ); + +#ifndef QT_NO_ACCESSIBILITY + moveLeftButton->setAccessibleName( i18n("Show the selected channel") ); + moveRightButton->setAccessibleName( i18n("Hide the selected channel") ); + _qlw->setAccessibleName( i18n("Visible channels") ); + _qlwInactive->setAccessibleName( i18n("Available channels") ); +#endif +} + +DialogViewConfiguration::~DialogViewConfiguration() +{ +} + + +void DialogViewConfiguration::apply() +{ + // --- We have a 3-Step Apply of the Changes ------------------------------- + + // -1- Update view and profile ***************************************** + GUIProfile* prof = _view.guiProfile(); + GUIProfile::ControlSet& oldControlset = prof->getControls(); + GUIProfile::ControlSet newControlset; + + QAbstractItemModel* model; + model = _qlw->model(); + prepareControls(model, true, oldControlset, newControlset); + model = _qlwInactive->model(); + prepareControls(model, false, oldControlset, newControlset); + + // -2- Copy all mandatory "catch-all" controls form the old to the new ControlSet ******* + foreach ( ProfControl* pctl, oldControlset) + { + if ( pctl->isMandatory() ) { + ProfControl* newCtl = new ProfControl(*pctl); + newCtl->show = "full"; // The user has selected controls => mandatory controls are now only necessary in extended or full mode + newControlset.push_back(newCtl); + } + } + + prof->setControls(newControlset); + prof->finalizeProfile(); + prof->setDirty(); + + // --- Step 3: Tell the view, that it has changed (probably it needs some "polishing" --- + if ( _view.getMixers().size() == 1 ) + ControlManager::instance().announce(_view.getMixers().first()->id(), ControlChangeType::ControlList, QString("View Configuration Dialog")); + else + ControlManager::instance().announce(QString(), ControlChangeType::ControlList, QString("View Configuration Dialog")); +} + +void DialogViewConfiguration::prepareControls(QAbstractItemModel* model, bool isActiveView, GUIProfile::ControlSet& oldCtlSet, GUIProfile::ControlSet& newCtlSet) +{ + int numRows = model->rowCount(); + for (int row = 0; row < numRows; ++row) { + // -1- Extract the value from the model *************************** + QModelIndex index = model->index(row, 0); + QVariant vdci; + vdci = model->data(index, Qt::ToolTipRole); // TooltipRole stores the ID (well, thats not really clean design, but it works) + QString ctlId = vdci.toString(); + + + // -2- Find the mdw, und update it ************************** + foreach ( QWidget *qw, _view._mdws ) + { + MixDeviceWidget *mdw = dynamic_cast(qw); + if ( !mdw ) { + continue; + } + + if ( mdw->mixDevice()->id() == ctlId ) { + mdw->setVisible(isActiveView); + break; + } // mdw was found + } // find mdw + + + // -3- Insert it in the new ControlSet ************************** +// kDebug() << "Should add to new ControlSet: " << ctlId; + foreach ( ProfControl* control, oldCtlSet) + { + //kDebug() << " checking " << control->id; + QRegExp idRegexp(control->id); + if ( ctlId.contains(idRegexp) ) { + // found. Create a copy + ProfControl* newCtl = new ProfControl(*control); + newCtl->id = '^' + ctlId + '$'; // Replace the (possible generic) regexp by the actual ID + // We have made this an an actual control. As it is derived (from e.g. ".*") it is NOT mandatory. + newCtl->setMandatory(false); + newCtl->setVisible(isActiveView); + newCtlSet.push_back(newCtl); +// kDebug() << "Added to new ControlSet (done): " << newCtl->id; + break; + } + } + } + +} + +#include "dialogviewconfiguration.moc" + diff --git a/kmix/gui/dialogviewconfiguration.h b/kmix/gui/dialogviewconfiguration.h new file mode 100644 index 00000000..3a60f464 --- /dev/null +++ b/kmix/gui/dialogviewconfiguration.h @@ -0,0 +1,145 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef DIALOGVIEWCONFIGURATION_H +#define DIALOGVIEWCONFIGURATION_H + +// QT +#include +class QLabel; +#include +class QHBoxLayout; +#include +#include +class QScrollArea; +class QVBoxLayout; + +// QT DND +#include +#include + +// KDE +#include +#include + +// KMix +#include "gui/guiprofile.h" +#include "viewbase.h" + +class DialogViewConfigurationItem : public QListWidgetItem +{ + friend class QDataStream; + + + public: + DialogViewConfigurationItem( QListWidget *parent); + DialogViewConfigurationItem( QListWidget *parent, QString id, bool shown, QString name, int splitted, const QString& iconName ); + + void refreshItem(); + public: + QString _id; + bool _shown; + QString _name; + int _splitted; + QString _iconName; +}; + +class DialogViewConfigurationWidget : public QListWidget +{ + Q_OBJECT +public: + DialogViewConfigurationWidget(QWidget *parent=0); + + void setActiveList(bool isActiveList) { + m_activeList = isActiveList; + } + bool isActiveList() const { return m_activeList; }; + + Q_SIGNALS: + void dropped(DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item, bool sourceIsActiveList); + +protected: + virtual QMimeData* mimeData(const QList items) const; + virtual bool dropMimeData(int index, const QMimeData * mimeData, Qt::DropAction action); + + virtual Qt::DropActions supportedDropActions() const + { + //kDebug() << "supportedDropActions!"; + return Qt::MoveAction; + } + virtual QStringList mimeTypes() const + { + //kDebug() << "mimeTypes!"; + return QStringList() << "application/x-kde-action-list"; + } + + // Skip internal dnd handling in QListWidget ---- how is one supposed to figure this out + // without reading the QListWidget code !? + virtual void dropEvent(QDropEvent* ev) { + QAbstractItemView::dropEvent(ev); + } + +private: + bool m_activeList; +}; + + +class DialogViewConfiguration : public KDialog +{ + Q_OBJECT + public: + DialogViewConfiguration(QWidget* parent, ViewBase& view); + ~DialogViewConfiguration(); + + + // QSize sizeHint() const; + public slots: + void apply(); + + private slots: + void slotDropped(DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item, bool sourceIsActiveList ); + + void moveSelectionToActiveList(); + void moveSelectionToInactiveList(); + void selectionChangedActive(); + void selectionChangedInactive(); + + private: + //void dragEnterEvent(QDragEnterEvent *event); + void prepareControls(QAbstractItemModel* model, bool isActiveView, GUIProfile::ControlSet& oldCtlSet, GUIProfile::ControlSet& newCtlSet); + void createPage(); + void addSpacer(int row, int col); + void moveSelection(DialogViewConfigurationWidget* from, DialogViewConfigurationWidget* to); + QVBoxLayout* _layout; + ViewBase& _view; + QWidget * frame; + QGridLayout *_glayout; + + QLabel* qlb; + QPushButton* moveLeftButton; + QPushButton* moveRightButton; + + DialogViewConfigurationWidget *_qlw; + DialogViewConfigurationWidget *_qlwInactive; +}; + + + +#endif diff --git a/kmix/gui/guiprofile.cpp b/kmix/gui/guiprofile.cpp new file mode 100644 index 00000000..538ae0b1 --- /dev/null +++ b/kmix/gui/guiprofile.cpp @@ -0,0 +1,921 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2006-2007 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "gui/guiprofile.h" + +// Qt +#include +#include +#include + +// System +#include +#include + +// KDE +#include +#include + +// KMix +#include "core/mixer.h" +#include + +QMap GUIProfile::s_profiles; +QString const GUIProfile::PNameSimple("simple"); +QString const GUIProfile::PNameExtended("extended"); +QString const GUIProfile::PNameAll("all"); +QString const GUIProfile::PNameCustom("custom"); + +bool SortedStringComparator::operator()(const std::string& s1, const std::string& s2) const { + return ( s1 < s2 ); +} + +/** + * Product comparator for sorting: + * We want the comparator to sort ascending by Vendor. "Inside" the Vendors, we sort by Product Name. + */ +bool ProductComparator::operator()(const ProfProduct* p1, const ProfProduct* p2) const { + if ( p1->vendor < p2->vendor ) { + return ( true ); + } + else if ( p1->vendor > p2->vendor ) { + return ( false ); + } + else if ( p1->productName < p2->productName ) { + return ( true ); + } + else if ( p1->productName > p2->productName ) { + return ( false ); + } + else { + /** + * We reach this point, if vendor and product name is identical. + * Actually we don't care about the order then, so we decide that "p1" comes first. + * + * (Hint: As this is a set comparator, the return value HERE doesn't matter that + * much. But if we would decide later to change this Comparator to be a Map Comparator, + * we must NOT return a "0" for identity - this would lead to non-insertion on insert()) + */ + return true; + } +} + +GUIProfile::GUIProfile() +{ + _dirty = false; + _driverVersionMin = 0; + _driverVersionMax = 0; + _generation = 1; +} + +GUIProfile::~GUIProfile() +{ + kWarning() << "Thou shalt not delete any GUI profile. This message is only OK, when quitting KMix"; + qDeleteAll(_controls); + qDeleteAll(_products); +} + +/** + * Clears the GUIProfile cache. You must only call this + * before termination of the application, as GUIProfile instances are used in other classes, especially the views. + * There is no need to call this in non-GUI applications like kmixd and kmixctrl. + */ +void GUIProfile::clearCache() +{ + qDeleteAll(s_profiles); + s_profiles.clear(); +} + + + + +void GUIProfile::setId(const QString& id) +{ + _id = id; +} + +QString GUIProfile::getId() const +{ + return _id; +} + +bool GUIProfile::isDirty() const { + return _dirty; +} + +void GUIProfile::setDirty() { + _dirty = true; +} + +/** + * Build a profile name. Suitable to use as primary key and to build filenames. + * @arg mixer The mixer + * @arg profileName The profile name (e.g. "capture", "playback", "my-cool-profile", or "any" + * @return The profile name + */ +QString GUIProfile::buildProfileName(Mixer* mixer, QString profileName, bool ignoreCard) +{ + QString fname; + fname += mixer->getDriverName(); + if (!ignoreCard) { + fname += ".%1.%2"; + fname = fname.arg(mixer->getBaseName()).arg(mixer->getCardInstance()); + } + fname += '.' + profileName; + + fname.replace(' ','_'); + return fname; +} + +/** + * Generate a readable profile name (for presenting to the user). + * Hint: Currently used as Tab label. + */ +QString GUIProfile::buildReadableProfileName(Mixer* mixer, QString profileName) +{ + QString fname; + fname += mixer->getBaseName(); + if ( mixer->getCardInstance() > 1 ) { + fname += " %1"; + fname = fname.arg(mixer->getCardInstance()); + } + if ( profileName != "default" ) { + fname += ' ' + profileName; + } + + kDebug() << fname; + return fname; +} + +/** + * Returns the GUIProfile for the given ID (= "fullyQualifiedName"). + * If not found 0 is returned. There is no try to load it. + * + * @returns The loaded GUIProfile for the given ID + */ +GUIProfile* GUIProfile::find(QString id) +{ + // Not thread safe (due to non-atomic contains()/get() + if ( s_profiles.contains(id) ) + { + return s_profiles[id]; + } + else + { + return 0; + } +} + + +/** + * Finds the correct profile for the given mixer. + * If already loaded from disk, returns the cached version. + * Otherwise load profile from disk: Priority: Card specific profile, Card unspecific profile + * + * @arg mixer The mixer + * @arg profileName The profile name (e.g. "ALSA.X-Fi.default", or "OSS.intel-cha51.playback") + * A special case is "", which means that a card specific name should be generated. + * @arg profileNameIsFullyQualified If true, an exact match will be searched. Otherwise it is a simple name like "playback" or "capture" + * @arg ignoreCardName If profileName not fully qualified, this is used in building the requestedProfileName + * @return GUIProfile* The loaded GUIProfile, or 0 if no profile matched. Hint: if you use allowFallback==true, this should never return 0. + */ +GUIProfile* GUIProfile::find(Mixer* mixer, QString profileName, bool profileNameIsFullyQualified, bool ignoreCardName) +{ + GUIProfile* guiprof = 0; + + if ( mixer == 0 || profileName.isEmpty() ) + return 0; + + if ( mixer->isDynamic() ) { + kDebug(67100) << "GUIProfile::find() Not loading GUIProfile for Dynamic Mixer (e.g. PulseAudio)"; + return 0; + } + + QString requestedProfileName; + QString fullQualifiedProfileName; + if ( profileNameIsFullyQualified ) { + requestedProfileName = profileName; + fullQualifiedProfileName = profileName; + } + else { + requestedProfileName = buildProfileName(mixer, profileName, ignoreCardName); + fullQualifiedProfileName = buildProfileName(mixer, profileName, false); + } + + if ( s_profiles.contains(fullQualifiedProfileName) ) { + guiprof = s_profiles.value(fullQualifiedProfileName); // Cached + } + else { + guiprof = loadProfileFromXMLfiles(mixer, requestedProfileName); // Load from XML ###Card specific profile### + if ( guiprof != 0 ) { + guiprof->_mixerId = mixer->id(); + guiprof->setId(fullQualifiedProfileName); // this one contains some soundcard id (basename + instance) + + if ( guiprof->getName().isEmpty() ) { + // If the profile didn't contain a name then lets define one + guiprof->setName(buildReadableProfileName(mixer,profileName)); // The caller can rename this if he likes + guiprof->setDirty(); + } + + if ( requestedProfileName != fullQualifiedProfileName) { + // This is very important! + // When the final profileName (fullQualifiedProfileName) is different from + // what we have loaded (requestedProfileName, e.g. "default"), we MUST + // set the profile dirty, so it gets saved. Otherwise we would write the + // fullQualifiedProfileName in the kmixrc, and will not find it on the next + // start of KMix. + guiprof->setDirty(); + } + addProfile(guiprof); + } + } + + return guiprof; +} + +/* + * Add the profile to the internal list of profiles (Profile caching). + */ +void GUIProfile::addProfile(GUIProfile* guiprof) +{ + // Possible TODO: Delete old mapped GUIProfile, if it exists. Otherwise we might leak one GUIProfile instance + // per unplug/plug sequence. Its quite likely possible that currently no Backend leads to a + // leak: This is because they either don't hotplug cards (PulseAudio, MPRIS2), or they ship + // a XML gui profile (so the Cached version is retrieved, and addProfile() is not called). + + s_profiles[guiprof->getId()] = guiprof; + kDebug() << "I have added" << guiprof->getId() << "; Number of profiles is now " << s_profiles.size() ; +} + + + + +/** + * Loads a GUI Profile from disk (xml profile file). + * It tries to load the Soundcard specific file first (a). + * If it doesn't exist, it will load the default profile corresponding to the soundcard driver (b). + */ +GUIProfile* GUIProfile::loadProfileFromXMLfiles(Mixer* mixer, QString profileName) +{ + GUIProfile* guiprof = 0; + QString fileName = createNormalizedFilename(profileName); + QString fileNameFQ = KStandardDirs::locate("appdata", fileName ); + + if ( ! fileNameFQ.isEmpty() ) { + guiprof = new GUIProfile(); + if ( guiprof->readProfile(fileNameFQ) && ( guiprof->match(mixer) > 0) ) { + // loaded + } + else { + delete guiprof; // not good (e.g. Parsing error => drop this profile silently) + guiprof = 0; + } + } + else { + kDebug() << "Ignore file " <vendor = mixer->getDriverName(); + prd->productName = mixer->readableName(); + prd->productRelease = "1.0"; + fallback->_products.insert(prd); + + static QString matchAll(".*"); + static QString matchAllSctl(".*"); + ProfControl* ctl = new ProfControl(matchAll, matchAllSctl); + //ctl->regexp = matchAll; // make sure id matches the regexp + ctl->setMandatory(true); + fallback->_controls.push_back(ctl); + + fallback->_soundcardDriver = mixer->getDriverName(); + fallback->_soundcardName = mixer->readableName(); + + fallback->finalizeProfile(); + + fallback->_mixerId = mixer->id(); + fallback->setId(fullQualifiedProfileName); // this one contains some soundcard id (basename + instance) + fallback->setName(buildReadableProfileName(mixer, QString("default"))); // The caller can rename this if he likes + fallback->setDirty(); + + /* -3- Add the profile to the static list + * Hint: This looks like a memory leak, as we never remove profiles from memory while KMix runs. + * Especially with application streams it looks suspicious. But please be aware that this method is only + * called for soundcard hotplugs, and not on stream hotplugs. At least it is supposed to be like that. + * + * Please also see the docs at addProfile(), they also address the possible memory leakage. + */ + addProfile(fallback); + + return fallback; +} + + +/** + * Fill the profile with the data from the given XML profile file. + * @par ref_fileName: Full qualified filename (with path). + * @return bool True, if the profile was successfully created. False if not (e.g. parsing error). + */ +bool GUIProfile::readProfile(const QString& ref_fileName) +{ + QXmlSimpleReader *xmlReader = new QXmlSimpleReader(); + kDebug() << "Read profile:" << ref_fileName ; + QFile xmlFile( ref_fileName ); + QXmlInputSource source( &xmlFile ); + GUIProfileParser* gpp = new GUIProfileParser(this); + xmlReader->setContentHandler(gpp); + bool ok = xmlReader->parse( source ); + + //std::cout << "Raw Profile: " << *this; + if ( ok ) { + ok = finalizeProfile(); + } // Read OK + else { + // !! this error message about faulty profiles should probably be surrounded with i18n() + kError(67100) << "ERROR: The profile '" << ref_fileName<< "' contains errors, and is not used." << endl; + } + delete gpp; + delete xmlReader; + + return ok; +} + +const QString GUIProfile::createNormalizedFilename(const QString& profileId) +{ + QString profileIdNormalized(profileId); + profileIdNormalized.replace(':', '.'); + + QString fileName("profiles/"); + fileName = fileName + profileIdNormalized + ".xml"; + return fileName; + } + +bool GUIProfile::writeProfile() +{ + bool ret = false; + QString profileId = getId(); + QString fileName = createNormalizedFilename(profileId); + QString fileNameFQ = KStandardDirs::locateLocal("appdata", fileName, true ); + + kDebug() << "Write profile:" << fileNameFQ ; + QFile f(fileNameFQ); + if ( f.open(QIODevice::WriteOnly | QFile::Truncate) ) + { + QTextStream out(&f); + out << *this; + f.close(); + ret = true; + } + + if ( ret ) { + _dirty = false; + } + return ret; +} + +/** This is now empty. It can be removed */ +bool GUIProfile::finalizeProfile() const +{ + bool ok = true; + return ok; +} + + +// ------------------------------------------------------------------------------------- +void GUIProfile::setControls(ControlSet& newControlSet) +{ + qDeleteAll(_controls); + _controls = newControlSet; +} + +const GUIProfile::ControlSet& GUIProfile::getControls() const +{ + return _controls; +} + +GUIProfile::ControlSet& GUIProfile::getControls() +{ + return _controls; +} + +void GUIProfile::addProduct(ProfProduct* prd) +{ + _products.insert(prd); +} + +// ------------------------------------------------------------------------------------- + + +/** + * Returns how good the given Mixer matches this GUIProfile. + * A value between 0 (not matching at all) and MAXLONG (perfect match) is returned. + * + * Here is the current algorithm: + * + * If the driver doesn't match, 0 is returned. (OK) + * If the card-name ... (OK) + * is "*", this is worth 1 point + * doesn't match, 0 is returned. + * matches, this is worth 500 points. + * + * If the "card type" ... + * is empty, this is worth 0 points. !!! not implemented yet + * doesn't match, 0 is returned. !!! not implemented yet + * matches , this is worth 500 points. !!! not implemented yet + * + * If the "driver version" doesn't match, 0 is returned. !!! not implemented yet + * If the "driver version" matches, this is worth ... + * 4000 unlimited <=> "*:*" + * 6000 toLower-bound-limited <=> "toLower-bound:*" + * 6000 upper-bound-limited <=> "*:upper-bound" + * 8000 upper- and toLower-bound limited <=> "toLower-bound:upper-bound" + * or 10000 points (upper-bound=toLower-bound=bound <=> "bound:bound" + * + * The Profile-Generation is added to the already achieved points. (done) + * The maximum gain is 900 points. + * Thus you can create up to 900 generations (0-899) without "overriding" + * the points gained from the "driver version" or "card-type". + * + * For example: card-name="*" (1), card-type matches (1000), + * driver version "*:*" (4000), Profile-Generation 4 (4). + * Sum: 1 + 1000 + 4000 + 4 = 5004 + * + * @todo Implement "card type" match value + * @todo Implement "version" match value (must be in backends as well) + */ +unsigned long GUIProfile::match(Mixer* mixer) { + unsigned long matchValue = 0; + if ( _soundcardDriver != mixer->getDriverName() ) { + return 0; + } + if ( _soundcardName == "*" ) { + matchValue += 1; + } + else if ( _soundcardName != mixer->getBaseName() ) { + return 0; // card name does not match + } + else { + matchValue += 500; // card name matches + } + + // !!! we don't check current for the driver version. + // So we assign simply 4000 points for now. + matchValue += 4000; + if ( _generation < 900 ) { + matchValue += _generation; + } + else { + matchValue += 900; + } + return matchValue; +} + +QString xmlify(QString raw); + +QString xmlify(QString raw) +{ +// kDebug() << "Before: " << raw; + raw = raw.replace('&', "&"); + raw = raw.replace('<', "<"); + raw = raw.replace('>', ">"); + raw = raw.replace("'", "'"); + raw = raw.replace("\"", """); +// kDebug() << "After : " << raw; + return raw; +} + + +QTextStream& operator<<(QTextStream &os, const GUIProfile& guiprof) +{ +// kDebug() << "ENTER QTextStream& operator<<"; + os << ""; + os << endl << endl; + + os << "" << endl << endl ; + + os << "" << endl; + + for ( GUIProfile::ProductSet::const_iterator it = guiprof._products.begin(); it != guiprof._products.end(); ++it) + { + ProfProduct* prd = *it; + os << "vendor).toUtf8().constData() << "\" name=\"" << xmlify(prd->productName).toUtf8().constData() << "\""; + if ( ! prd->productRelease.isNull() ) { + os << " release=\"" << xmlify(prd->productRelease).toUtf8().constData() << "\""; + } + if ( ! prd->comment.isNull() ) { + os << " comment=\"" << xmlify(prd->comment).toUtf8().constData() << "\""; + } + os << " />" << endl; + } // for all products + os << endl; + + foreach ( ProfControl* profControl, guiprof.getControls() ) + { + os << "id).toUtf8().constData() << "\"" ; + if ( !profControl->name.isNull() && profControl->name != profControl->id ) { + os << " name=\"" << xmlify(profControl->name).toUtf8().constData() << "\"" ; + } + os << " subcontrols=\"" << xmlify( profControl->renderSubcontrols().toUtf8().constData()) << "\"" ; + os << " show=\"" << xmlify(profControl->show).toUtf8().constData() << "\"" ; + if ( profControl->isMandatory() ) { + os << " mandatory=\"true\""; + } + if ( profControl->isSplit() ) { + os << " split=\"true\""; + } + os << " />" << endl; + } // for all controls + os << endl; + + os << "" << endl; +// kDebug() << "EXIT QTextStream& operator<<"; + return os; +} + +std::ostream& operator<<(std::ostream& os, const GUIProfile& guiprof) { + os << "Soundcard:" << std::endl + << " Driver=" << guiprof._soundcardDriver.toUtf8().constData() << std::endl + << " Driver-Version min=" << guiprof._driverVersionMin + << " max=" << guiprof._driverVersionMax << std::endl + << " Card-Name=" << guiprof._soundcardName.toUtf8().constData() << std::endl + << " Card-Type=" << guiprof._soundcardType.toUtf8().constData() << std::endl + << " Profile-Generation=" << guiprof._generation + << std::endl; + + os << "Profile:" << std::endl + << " Id=" << guiprof._id.toUtf8().constData() << std::endl + << " Name=" << guiprof._name.toUtf8().constData() << std::endl; + + for ( GUIProfile::ProductSet::const_iterator it = guiprof._products.begin(); it != guiprof._products.end(); ++it) + { + ProfProduct* prd = *it; + os << "Product:\n Vendor=" << prd->vendor.toUtf8().constData() << std::endl << " Name=" << prd->productName.toUtf8().constData() << std::endl; + if ( ! prd->productRelease.isNull() ) { + os << " Release=" << prd->productRelease.toUtf8().constData() << std::endl; + } + if ( ! prd->comment.isNull() ) { + os << " Comment = " << prd->comment.toUtf8().constData() << std::endl; + } + } // for all products + + foreach ( ProfControl* profControl, guiprof.getControls() ) + { +// ProfControl* profControl = *it; + os << "Control:\n ID=" << profControl->id.toUtf8().constData() << std::endl; + if ( !profControl->name.isNull() && profControl->name != profControl->id ) { + os << " Name = " << profControl->name.toUtf8().constData() << std::endl; + } + os << " Subcontrols=" << profControl->renderSubcontrols().toUtf8().constData() << std::endl; + if ( profControl->isMandatory() ) { + os << " mandatory=\"true\"" << std::endl; + } + if ( profControl->isSplit() ) { + os << " split=\"true\"" << std::endl; + } + } // for all controls + + return os; +} + +ProfControl::ProfControl(QString& id, QString& subcontrols ) : + _mandatory(false), _split(false) { + d = new ProfControlPrivate(); + this->show = "simple"; + this->id = id; + setSubcontrols(subcontrols); +} + +ProfControl::ProfControl(const ProfControl &profControl) : + _mandatory(false), _split(false) { + d = new ProfControlPrivate(); + id = profControl.id; + name = profControl.name; + + _useSubcontrolPlayback = profControl._useSubcontrolPlayback; + _useSubcontrolCapture = profControl._useSubcontrolCapture; + _useSubcontrolPlaybackSwitch = profControl._useSubcontrolPlaybackSwitch; + _useSubcontrolCaptureSwitch = profControl._useSubcontrolCaptureSwitch; + _useSubcontrolEnum = profControl._useSubcontrolEnum; + d->subcontrols = profControl.d->subcontrols; + + name = profControl.name; + show = profControl.show; + backgroundColor = profControl.backgroundColor; + switchtype = profControl.switchtype; + _mandatory = profControl._mandatory; + _split = profControl._split; +} + +ProfControl::~ProfControl() { + delete d; +} + +void ProfControl::setVisible(bool visible) +{ + show = visible ? GUIProfile::PNameSimple : GUIProfile::PNameExtended; +} + +void ProfControl::setSubcontrols(QString sctls) +{ + d->subcontrols = sctls; + + _useSubcontrolPlayback = false; + _useSubcontrolCapture = false; + _useSubcontrolPlaybackSwitch = false; + _useSubcontrolCaptureSwitch = false; + _useSubcontrolEnum = false; + + QStringList qsl = sctls.split( ',', QString::SkipEmptyParts, Qt::CaseInsensitive); + QStringListIterator qslIt(qsl); + while (qslIt.hasNext()) { + QString sctl = qslIt.next(); + //kDebug() << "setSubcontrols found: " << sctl.toLocal8Bit().constData() << endl; + if ( sctl == "pvolume" ) _useSubcontrolPlayback = true; + else if ( sctl == "cvolume" ) _useSubcontrolCapture = true; + else if ( sctl == "pswitch" ) _useSubcontrolPlaybackSwitch = true; + else if ( sctl == "cswitch" ) _useSubcontrolCaptureSwitch = true; + else if ( sctl == "enum" ) _useSubcontrolEnum = true; + else if ( sctl == "*" || sctl == ".*") { + _useSubcontrolCapture = true; + _useSubcontrolCaptureSwitch = true; + _useSubcontrolPlayback = true; + _useSubcontrolPlaybackSwitch = true; + _useSubcontrolEnum = true; + } + else kWarning() << "Ignoring unknown subcontrol type '" << sctl << "' in profile"; + } +} + +QString ProfControl::renderSubcontrols() +{ + QString sctlString; + if ( _useSubcontrolPlayback && _useSubcontrolPlaybackSwitch && _useSubcontrolCapture && _useSubcontrolCaptureSwitch && _useSubcontrolEnum ) { + return QString("*"); + } + else { + if ( _useSubcontrolPlayback ) { + sctlString += "pvolume,"; + } + if ( _useSubcontrolCapture ) { + sctlString += "cvolume,"; + } + if ( _useSubcontrolPlaybackSwitch ) { + sctlString += "pswitch,"; + } + if ( _useSubcontrolCaptureSwitch ) { + sctlString += "cswitch,"; + } + if ( _useSubcontrolEnum ) { + sctlString += "enum,"; + } + if ( sctlString.length() > 0 ) { + sctlString.chop(1); + } + return sctlString; + } +} + + +// ### PARSER START ################################################ + + +GUIProfileParser::GUIProfileParser(GUIProfile* ref_gp) : _guiProfile(ref_gp) +{ + _scope = GUIProfileParser::NONE; // no scope yet +} + +bool GUIProfileParser::startDocument() +{ + _scope = GUIProfileParser::NONE; // no scope yet + return true; +} + +bool GUIProfileParser::startElement( const QString& , + const QString& , + const QString& qName, + const QXmlAttributes& attributes ) +{ + switch ( _scope ) { + case GUIProfileParser::NONE: + /** we are reading the "top level" ***************************/ + if ( qName.toLower() == "soundcard" ) { + _scope = GUIProfileParser::SOUNDCARD; + addSoundcard(attributes); + } + else { + // skip unknown top-level nodes + std::cerr << "Ignoring unsupported element '" << qName.toUtf8().constData() << "'" << std::endl; + } + // we are accepting only + break; + + case GUIProfileParser::SOUNDCARD: + if ( qName.toLower() == "product" ) { + // Defines product names under which the chipset/hardware is sold + addProduct(attributes); + } + else if ( qName.toLower() == "control" ) { + addControl(attributes); + } + else if ( qName.toLower() == "profile" ) { + addProfileInfo(attributes); + } + else { + std::cerr << "Ignoring unsupported element '" << qName.toUtf8().constData() << "'" << std::endl; + } + // we are accepting , and + + break; + + } // switch() + return true; +} + +bool GUIProfileParser::endElement( const QString&, const QString&, const QString& qName ) +{ + if ( qName == "soundcard" ) { + _scope = GUIProfileParser::NONE; // should work out OK, as we don't nest soundcard entries + } + return true; +} + +void GUIProfileParser::addSoundcard(const QXmlAttributes& attributes) { +/* + std::cout << "Soundcard: "; + printAttributes(attributes); +*/ + QString driver = attributes.value("driver"); + QString version = attributes.value("version"); + QString name = attributes.value("name"); + QString type = attributes.value("type"); + QString generation = attributes.value("generation"); + if ( !driver.isNull() && !name.isNull() ) { + _guiProfile->_soundcardDriver = driver; + _guiProfile->_soundcardName = name; + if ( type.isNull() ) { + _guiProfile->_soundcardType = ""; + } + else { + _guiProfile->_soundcardType = type; + } + if ( version.isNull() ) { + _guiProfile->_driverVersionMin = 0; + _guiProfile->_driverVersionMax = 0; + } + else { + std::pair versionMinMax; + splitPair(version, versionMinMax, ':'); + _guiProfile->_driverVersionMin = versionMinMax.first.toULong(); + _guiProfile->_driverVersionMax = versionMinMax.second.toULong(); + } + if ( type.isNull() ) { type = ""; }; + if ( generation.isNull() ) { + _guiProfile->_generation = 0; + } + else { + // Hint: If the conversion fails, _generation will be assigned 0 (which is fine) + _guiProfile->_generation = generation.toUInt(); + } + } + +} + + + +void GUIProfileParser::addProfileInfo(const QXmlAttributes& attributes) { + QString name = attributes.value("name"); + QString id = attributes.value("id"); + + _guiProfile->setId(id); + _guiProfile->setName(name); +} + +void GUIProfileParser::addProduct(const QXmlAttributes& attributes) { + /* + std::cout << "Product: "; + printAttributes(attributes); + */ + QString vendor = attributes.value("vendor"); + QString name = attributes.value("name"); + QString release = attributes.value("release"); + QString comment = attributes.value("comment"); + if ( !vendor.isNull() && !name.isNull() ) { + // Adding a product makes only sense if we have at least vendor and product name + ProfProduct *prd = new ProfProduct(); + prd->vendor = vendor; + prd->productName = name; + prd->productRelease = release; + prd->comment = comment; + + _guiProfile->addProduct(prd); + } +} + +void GUIProfileParser::addControl(const QXmlAttributes& attributes) { + /* + std::cout << "Control: "; + printAttributes(attributes); + */ + QString id = attributes.value("id"); + QString subcontrols = attributes.value("subcontrols"); + QString name = attributes.value("name"); + QString show = attributes.value("show"); + QString background = attributes.value("background"); + QString switchtype = attributes.value("switchtype"); + QString mandatory = attributes.value("mandatory"); + QString split = attributes.value("split"); + bool isMandatory = false; + + if ( !id.isNull() ) { + // We need at least an "id". We can set defaults for the rest, if undefined. + if ( subcontrols.isNull() || subcontrols.isEmpty() ) { + subcontrols = '*'; // for compatibility reasons, we interpret an empty string as match-all (aka "*") + } + if ( name.isNull() ) { + // ignore. isNull() will be checked by all users. + } + if ( ! mandatory.isNull() && mandatory == "true" ) { + isMandatory = true; + } + if ( !background.isNull() ) { + // ignore. isNull() will be checked by all users. + } + if ( !switchtype.isNull() ) { + // ignore. isNull() will be checked by all users. + } + + ProfControl *profControl = new ProfControl(id, subcontrols); + if ( show.isNull() ) { show = '*'; } + + profControl->name = name; + profControl->show = show; + profControl->setBackgroundColor( background ); + profControl->setSwitchtype(switchtype); + profControl->setMandatory(isMandatory); + + if ( !split.isNull() && split=="true") { + profControl->setSplit(true); + } + _guiProfile->getControls().push_back(profControl); + } // id != null +} + +void GUIProfileParser::printAttributes(const QXmlAttributes& attributes) { + if ( attributes.length() > 0 ) { + for ( int i = 0 ; i < attributes.length(); i++ ) { + std::cout << attributes.qName(i).toUtf8().constData() << ":"<< attributes.value(i).toUtf8().constData() << " , "; + } + std::cout << std::endl; + } +} + +void GUIProfileParser::splitPair(const QString& pairString, std::pair& result, char delim) +{ + int delimPos = pairString.indexOf(delim); + if ( delimPos == -1 ) { + // delimiter not found => use an empty String for "second" + result.first = pairString; + result.second = ""; + } + else { + // delimiter found + result.first = pairString.mid(0,delimPos); + result.second = pairString.left(delimPos+1); + } +} diff --git a/kmix/gui/guiprofile.h b/kmix/gui/guiprofile.h new file mode 100644 index 00000000..28cbbd38 --- /dev/null +++ b/kmix/gui/guiprofile.h @@ -0,0 +1,244 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2006-2007 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _GUIPROFILE_H_ +#define _GUIPROFILE_H_ + +class Mixer; + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +struct SortedStringComparator +{ + bool operator()(const std::string&, const std::string&) const; +}; + + +struct ProfProduct +{ + QString vendor; + QString productName; + // In case the vendor ships different products under the same productName + QString productRelease; + QString comment; +}; + +class ProfControlPrivate +{ +public: + // List of controls, e.g: "rec:1-2,recswitch" + // THIS IS RAW DATA AS LOADED FROM THE PROFILE. DO NOT USE IT, except for debugging. + QString subcontrols; + +}; + +class ProfControl +{ +public: + ProfControl(QString& id, QString& subcontrols); + ProfControl(const ProfControl &ctl); // copy constructor + ~ProfControl(); + // ID as returned by the Mixer Backend, e.g. Master:0 + QString id; + + void setSubcontrols(QString sctls); + bool useSubcontrolPlayback() {return _useSubcontrolPlayback;}; + bool useSubcontrolCapture() {return _useSubcontrolCapture;}; + bool useSubcontrolPlaybackSwitch() {return _useSubcontrolPlaybackSwitch;}; + bool useSubcontrolCaptureSwitch() {return _useSubcontrolCaptureSwitch;}; + bool useSubcontrolEnum() {return _useSubcontrolEnum;}; + QString renderSubcontrols(); + + QString getBackgroundColor() const { return backgroundColor; } + void setBackgroundColor(QString& backgroundColor) { this->backgroundColor = backgroundColor; } + QString getSwitchtype() const { return switchtype; } + void setSwitchtype(QString switchtype) { this->switchtype = switchtype; } + + // Visible name for the User ( if name.isNull(), id will be used - And in the future a default lookup table will be consulted ). + // Because the name is visible, some kind of i18n() should be used. + QString name; + + void setVisible(bool); + // show or hide (contains the GUI type: simple, extended, all) + // Future direction: Make "show" private + QString show; + + bool isMandatory() const + { + return _mandatory; + } + + void setMandatory(bool _mandatory) + { + this->_mandatory = _mandatory; + } + void setSplit ( bool split ) { + _split = split; + } + bool isSplit() const { + return _split; + } + +private: + // The following are the deserialized values of _subcontrols + bool _useSubcontrolPlayback; + bool _useSubcontrolCapture; + bool _useSubcontrolPlaybackSwitch; + bool _useSubcontrolCaptureSwitch; + bool _useSubcontrolEnum; + + // For applying custom colors + QString backgroundColor; + // For defining the switch type when it is not a standard palyback or capture switch + QString switchtype; + + bool _mandatory; // A mandatory control must be included in all GUIProfile copies + + ProfControlPrivate *d; + bool _split; // true if this widget is to show two sliders +}; + + +struct ProductComparator +{ + bool operator()(const ProfProduct*, const ProfProduct*) const; +}; + +class GUIProfile +{ +public: + typedef std::set ProductSet; + typedef QList ControlSet; + + static const QString PNameSimple; + static const QString PNameExtended; + static const QString PNameAll; + static const QString PNameCustom; + +private: + static QMap& getProfiles() { return s_profiles; } + // Loading + static QString buildProfileName(Mixer* mixer, QString profileName, bool ignoreCard); + static QString buildReadableProfileName(Mixer* mixer, QString profileName); + + static GUIProfile* loadProfileFromXMLfiles(Mixer* mixer, QString profileName); + static void addProfile(GUIProfile* guiprof); + static const QString createNormalizedFilename(const QString& profileId); + + static QMap s_profiles; + + +public: + GUIProfile(); + virtual ~GUIProfile(); + + static void clearCache(); + + bool readProfile(const QString& ref_fileNamestring); + bool finalizeProfile() const; + bool writeProfile(); + + bool isDirty() const; + void setDirty(); + + void setId(const QString& id); + QString getId() const; + QString getMixerId() const { return _mixerId; } + + + unsigned long match(Mixer* mixer); + friend std::ostream& operator<<(std::ostream& os, const GUIProfile& vol); + friend QTextStream& operator<<(QTextStream &outStream, const GUIProfile& guiprof); + + + + static GUIProfile* find(Mixer* mixer, QString profileName, bool profileNameIsFullyQualified, bool ignoreCardName); + static GUIProfile* find(QString id); + static GUIProfile* selectProfileFromXMLfiles(Mixer*, QString preferredProfile); + static GUIProfile* fallbackProfile(Mixer*); + + // --- Getters and setters ---------------------------------------------------------------------- + const ControlSet& getControls() const; + ControlSet& getControls(); + void setControls(ControlSet& newControlSet); + + QString getName() const { return _name; } + void setName(QString _name) { this->_name = _name; } + + void addProduct(ProfProduct*); + + + // --- The values from the tag: No getters and setters for them (yet) ----------------------------- + QString _soundcardDriver; + // The driver version: 1000*1000*MAJOR + 1000*MINOR + PATCHLEVEL + unsigned long _driverVersionMin; + unsigned long _driverVersionMax; + QString _soundcardName; + QString _soundcardType; + unsigned long _generation; + +private: + ControlSet _controls; + ProductSet _products; + + QString _id; + QString _name; + QString _mixerId; + bool _dirty; +}; + +std::ostream& operator<<(std::ostream& os, const GUIProfile& vol); +QTextStream& operator<<(QTextStream &outStream, const GUIProfile& guiprof); + +class GUIProfileParser : public QXmlDefaultHandler +{ +public: + GUIProfileParser(GUIProfile* ref_gp); + // Enumeration for the scope + enum ProfileScope { NONE, SOUNDCARD }; + + bool startDocument(); + bool startElement( const QString&, const QString&, const QString& , const QXmlAttributes& ); + bool endElement( const QString&, const QString&, const QString& ); + +private: + void addControl(const QXmlAttributes& attributes); + void addProduct(const QXmlAttributes& attributes); + void addSoundcard(const QXmlAttributes& attributes); + void addProfileInfo(const QXmlAttributes& attributes); + void printAttributes(const QXmlAttributes& attributes); + void splitPair(const QString& pairString, std::pair& result, char delim); + + ProfileScope _scope; + GUIProfile* _guiProfile; +}; + +#endif //_GUIPROFILE_H_ diff --git a/kmix/gui/kmixdockwidget.cpp b/kmix/gui/kmixdockwidget.cpp new file mode 100644 index 00000000..5d608d87 --- /dev/null +++ b/kmix/gui/kmixdockwidget.cpp @@ -0,0 +1,388 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * Copyright (C) 2001 Preston Brown + * Copyright (C) 2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "gui/kmixdockwidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "apps/kmix.h" +#include "core/ControlManager.h" +#include "core/mixer.h" +#include "core/mixertoolbox.h" +#include "gui/dialogselectmaster.h" +#include "gui/mixdevicewidget.h" +#include "gui/viewdockareapopup.h" + + +//#define FEATURE_UNITY_POPUP true + +KMixDockWidget::KMixDockWidget(KMixWindow* parent) + : KStatusNotifierItem(parent) + , _oldToolTipValue(-1) + , _oldPixmapType('-') + , _kmixMainWindow(parent) +{ + setToolTipIconByName("kmix"); + setTitle(i18n( "Volume Control")); + setCategory(Hardware); + setStatus(Active); + + // TODO Unity / Gnome only support one type of activation (left-click == right-click) + // So we should show here the ViewDockAreaPopup instead of the menu: + //bool onlyOneMouseButtonAction = onlyHaveOneMouseButtonAction(); + + createMenuActions(); + + connect(this, SIGNAL(scrollRequested(int,Qt::Orientation)), this, SLOT(trayWheelEvent(int,Qt::Orientation))); + connect(this, SIGNAL(secondaryActivateRequested(QPoint)), this, SLOT(dockMute())); + + // For bizarre reasons, we wrap the ViewDockAreaPopup in a KMenu. Must relate to how KStatusNotifierItem works. + _dockAreaPopupMenuWrapper = new KMenu(parent); + _volWA = new QWidgetAction(_dockAreaPopupMenuWrapper); + _dockView = new ViewDockAreaPopup(_dockAreaPopupMenuWrapper, "dockArea", 0, QString("no-guiprofile-yet-in-dock"), parent); + _volWA->setDefaultWidget(_dockView); + _dockAreaPopupMenuWrapper->addAction(_volWA); + connect(contextMenu(), SIGNAL(aboutToShow()), this, SLOT(contextMenuAboutToShow())); + + ControlManager::instance().addListener( + QString(), // All mixers (as the Global master Mixer might change) + (ControlChangeType::Type) (ControlChangeType::Volume | ControlChangeType::MasterChanged), this, + QString("KMixDockWidget")); + + // Refresh in all cases. When there is no Golbal Master we still need + // to initialize correctly (e.g. for showin 0% or hiding it) + refreshVolumeLevels(); +} + +KMixDockWidget::~KMixDockWidget() +{ + ControlManager::instance().removeListener(this); + // Note: deleting _volWA also deletes its associated ViewDockAreaPopup (_referenceWidget) and prevents the + // action to be left with a dangling pointer. + // cesken: I adapted the patch from https://bugs.kde.org/show_bug.cgi?id=220621#c27 to branch /branches/work/kmix + delete _volWA; +} + +void KMixDockWidget::controlsChange(int changeType) +{ + ControlChangeType::Type type = ControlChangeType::fromInt(changeType); + switch (type ) + { + case ControlChangeType::MasterChanged: + // Notify the main window, as it might need to update the visibiliy of the dock icon. +// _kmixMainWindow->updateDocking(); +// _kmixMainWindow->saveConfig(); + refreshVolumeLevels(); + actionCollection()->action(QLatin1String("select_master"))->setEnabled(Mixer::getGlobalMasterMixer() != 0); + break; + + case ControlChangeType::Volume: + refreshVolumeLevels(); + break; + + default: + ControlManager::warnUnexpectedChangeType(type, this); + } +} + +/** + * Updates all visual parts of the volume, namely tooltip and pixmap + */ +void KMixDockWidget::refreshVolumeLevels() +{ + setVolumeTip(); + updatePixmap(); +} + +/** + * Creates the right-click menu + */ +void KMixDockWidget::createMenuActions() +{ + QMenu *menu = contextMenu(); + if ( menu == 0) + return; // We do not use a menu + + shared_ptr md = Mixer::getGlobalMasterMD(); + if ( md.get() != 0 && md->hasMuteSwitch() ) { + // Put "Mute" selector in context menu + KToggleAction *action = actionCollection()->add( "dock_mute" ); + updateDockMuteAction(action); + action->setText( i18n( "M&ute" ) ); + connect(action, SIGNAL(triggered(bool)), SLOT(dockMute())); + menu->addAction( action ); + } + + // Put "Select Master Channel" dialog in context menu + QAction *action = actionCollection()->addAction( "select_master" ); + action->setText( i18n("Select Master Channel...") ); + action->setEnabled(Mixer::getGlobalMasterMixer() != 0); + connect(action, SIGNAL(triggered(bool)), _kmixMainWindow, SLOT(slotSelectMaster())); + menu->addAction( action ); + + //Context menu entry to access phonon settings + menu->addAction(_kmixMainWindow->actionCollection()->action("launch_kdesoundsetup")); +} + +void +KMixDockWidget::setVolumeTip() +{ + shared_ptr md = Mixer::getGlobalMasterMD(); + QString tip; + int virtualToolTipValue = 0; + + if ( md.get() == 0 ) + { + tip = i18n("Mixer cannot be found"); // !! text could be reworked + virtualToolTipValue = -2; + } + else + { + // Playback volume will be used for the DockIcon if available. + // This heuristic is "good enough" for the DockIcon for now. + int val = md->getUserfriendlyVolumeLevel(); + tip += "" + i18n( "Volume at %1%", val ) + ""; + if ( md->isMuted() ) + tip += i18n( " (Muted)" ); + tip += QString( "
%1
%2
" ) + .arg( Qt::escape(md->mixer()->readableName()) ).arg( Qt::escape(md->readableName()) ); + + // create a new "virtual" value. With that we see "volume changes" as well as "muted changes" + virtualToolTipValue = val; + if ( md->isMuted() ) + virtualToolTipValue += 10000; + } + + // The actual updating is only done when the "toolTipValue" was changed (to avoid flicker) + if ( virtualToolTipValue != _oldToolTipValue ) + { + // changed (or completely new tooltip) + setToolTipTitle(tip); + } + _oldToolTipValue = virtualToolTipValue; +} + + +void KMixDockWidget::updatePixmap() +{ + shared_ptr md = Mixer::getGlobalMasterMD(); + + char newPixmapType; + if ( !md ) + { + // no such control => error + newPixmapType = 'e'; + } + else + { + int percentage = md->getUserfriendlyVolumeLevel(); + if ( percentage <= 0 ) newPixmapType = '0'; // Hint: also muted, and also negative-values + else if ( percentage < 25 ) newPixmapType = '1'; + else if ( percentage < 75 ) newPixmapType = '2'; + else newPixmapType = '3'; + } + + if ( newPixmapType != _oldPixmapType ) { + // Pixmap must be changed => do so + switch ( newPixmapType ) { + case 'e': setIconByName( "kmixdocked_error" ); break; + case 'm': + case '0': setIconByName( "audio-volume-muted" ); break; + case '1': setIconByName( "audio-volume-low" ); break; + case '2': setIconByName( "audio-volume-medium" ); break; + case '3': setIconByName( "audio-volume-high" ); break; + } + } + + _oldPixmapType = newPixmapType; +} + +/** + * Called whenever the icon gets "activated". Unusally whn its clicked. + * @overload + * @param pos + */ +void KMixDockWidget::activate(const QPoint &pos) +{ + QWidget* dockAreaPopup = _dockAreaPopupMenuWrapper; // TODO Refactor to use _referenceWidget directly + if (dockAreaPopup->isVisible()) + { + dockAreaPopup->hide(); + return; + } + + _dockAreaPopupMenuWrapper->removeAction(_volWA); + delete _volWA; + _volWA = new QWidgetAction(_dockAreaPopupMenuWrapper); + _dockView = new ViewDockAreaPopup(_dockAreaPopupMenuWrapper, "dockArea", 0, QString("no-guiprofile-yet-in-dock"), + _kmixMainWindow); + _volWA->setDefaultWidget(_dockView); + _dockAreaPopupMenuWrapper->addAction(_volWA); + + //_dockView->show(); // TODO cesken check: this should be automatic + // Showing, to hopefully get the geometry manager started. We need width and height below. Also + // vdesktop->availableGeometry(dockAreaPopup) needs to know on which screen the widget will be shown. +// dockAreaPopup->show(); + _dockView->adjustSize(); + dockAreaPopup->adjustSize(); + + int x = pos.x() - dockAreaPopup->width() / 2; + if (x < 0) + x = pos.x(); + int y = pos.y() - dockAreaPopup->height() / 2; + if (y < 0) + y = pos.y(); + + // Now handle Multihead displays. And also make sure that the dialog is not + // moved out-of-the screen on the right (see Bug 101742). + const QDesktopWidget* vdesktop = QApplication::desktop(); + const QRect& vScreenSize = vdesktop->availableGeometry(dockAreaPopup); + + if ((x + dockAreaPopup->width()) > (vScreenSize.width() + vScreenSize.x())) + { + // move horizontally, so that it is completely visible + x = vScreenSize.width() + vScreenSize.x() - dockAreaPopup->width() - 1; + kDebug() + << "Multihead: (case 1) moving to" << vScreenSize.x() << "," << vScreenSize.y(); + } + else if (x < vScreenSize.x()) + { + // horizontally out-of bound + x = vScreenSize.x(); + kDebug() << "Multihead: (case 2) moving to" << vScreenSize.x() << "," << vScreenSize.y(); + } + + if ((y + dockAreaPopup->height()) > (vScreenSize.height() + vScreenSize.y())) + { + // move horizontally, so that it is completely visible + y = vScreenSize.height() + vScreenSize.y() - dockAreaPopup->height() - 1; + kDebug() << "Multihead: (case 3) moving to" << vScreenSize.x() << "," << vScreenSize.y(); + } + else if (y < vScreenSize.y()) + { + // horizontally out-of bound + y = vScreenSize.y(); + kDebug() << "Multihead: (case 4) moving to" << vScreenSize.x() << "," << vScreenSize.y(); + } + + + KWindowSystem::setType(dockAreaPopup->winId(), NET::Dock); + KWindowSystem::setState(dockAreaPopup->winId(), NET::StaysOnTop | NET::SkipTaskbar | NET::SkipPager); + dockAreaPopup->show(); + dockAreaPopup->move(x, y); +} + + +void +KMixDockWidget::trayWheelEvent(int delta,Qt::Orientation wheelOrientation) +{ + shared_ptr md = Mixer::getGlobalMasterMD(); + if ( md.get() == 0 ) + return; + + + Volume &vol = ( md->playbackVolume().hasVolume() ) ? md->playbackVolume() : md->captureVolume(); + // bko313579 Do not use "delta", as that is setting more related to documents (Editor, Browser). KMix should + // simply always use its own VOLUME_STEP_DIVISOR as a base for percentage change. + bool decrease = delta < 0; + if (wheelOrientation == Qt::Horizontal) // Reverse horizontal scroll: bko228780 + decrease = !decrease; + long cv = vol.volumeStep(decrease); + + bool isInactive = vol.isCapture() ? !md->isRecSource() : md->isMuted(); + kDebug() << "Operating on capture=" << vol.isCapture() << ", isInactive=" << isInactive; + if ( cv > 0 && isInactive) + { + // increasing from muted state: unmute and start with a low volume level + if ( vol.isCapture()) + md->setRecSource(true); + else + md->setMuted(false); + vol.setAllVolumes(cv); + } + else + vol.changeAllVolumes(cv); + + md->mixer()->commitVolumeChange(md); + refreshVolumeLevels(); +} + + +void +KMixDockWidget::dockMute() +{ + shared_ptr md = Mixer::getGlobalMasterMD(); + if ( md ) + { + md->toggleMute(); + md->mixer()->commitVolumeChange( md ); + refreshVolumeLevels(); + } +} + +/** + * Returns whether the running Desktop only supports one Mouse Button + * Hint: Unity / Gnome only support one type of activation (left-click == right-click). + */ +bool KMixDockWidget::onlyHaveOneMouseButtonAction() +{ + QDBusConnection connection = QDBusConnection::sessionBus(); + bool unityIsRunnig = (connection.interface()->isServiceRegistered("com.canonical.Unity.Panel.Service")); + // Possibly implement other detectors, like for Gnome 3 or Gnome 2 + return unityIsRunnig; + +} + +void KMixDockWidget::contextMenuAboutToShow() +{ + // Enable/Disable "Muted" menu item + KToggleAction *dockMuteAction = static_cast(actionCollection()->action("dock_mute")); + updateDockMuteAction(dockMuteAction); +} + +void KMixDockWidget::updateDockMuteAction ( KToggleAction* dockMuteAction ) +{ + shared_ptr md = Mixer::getGlobalMasterMD(); + if ( md && dockMuteAction != 0 ) + { + Volume& vol = md->playbackVolume().hasVolume() ? md->playbackVolume() : md->captureVolume(); + bool isInactive = vol.isCapture() ? !md->isRecSource() : md->isMuted(); + bool hasSwitch = vol.isCapture() ? vol.hasSwitch() : md->hasMuteSwitch(); + dockMuteAction->setEnabled( hasSwitch ); + dockMuteAction->setChecked( isInactive ); + } +} + +#include "kmixdockwidget.moc" diff --git a/kmix/gui/kmixdockwidget.h b/kmix/gui/kmixdockwidget.h new file mode 100644 index 00000000..6eea270a --- /dev/null +++ b/kmix/gui/kmixdockwidget.h @@ -0,0 +1,80 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * Copyright (C) 2003 Sven Leiber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KMIXDOCKWIDGET_H +#define KMIXDOCKWIDGET_H + +class QString; +class QWidgetAction; + +class KToggleAction; +#include + +class KMixWindow; +class Mixer; +#include "core/mixdevice.h" +class ViewDockAreaPopup; +class Volume; + +class KMixDockWidget : public KStatusNotifierItem +{ + Q_OBJECT + + friend class KMixWindow; + + public: + explicit KMixDockWidget(KMixWindow *parent); + virtual ~KMixDockWidget(); + + void setErrorPixmap(); + void ignoreNextEvent(); + void update(); + + public slots: + void setVolumeTip(); + void updatePixmap(); + void activate(const QPoint &pos); + void controlsChange(int changeType); + + protected: + void createMenuActions(); + void toggleMinimizeRestore(); + + private: + ViewDockAreaPopup *_dockView; + KMenu *_dockAreaPopupMenuWrapper; + QWidgetAction *_volWA; + int _oldToolTipValue; + char _oldPixmapType; + KMixWindow* _kmixMainWindow; + + bool onlyHaveOneMouseButtonAction(); + void refreshVolumeLevels(); + void updateDockMuteAction ( KToggleAction* dockMuteAction ); + + private slots: + void dockMute(); + void trayWheelEvent(int delta,Qt::Orientation); + void contextMenuAboutToShow(); +}; + +#endif diff --git a/kmix/gui/kmixerwidget.cpp b/kmix/gui/kmixerwidget.cpp new file mode 100644 index 00000000..bbfb13be --- /dev/null +++ b/kmix/gui/kmixerwidget.cpp @@ -0,0 +1,183 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright (C) 2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "gui/kmixerwidget.h" + +// Qt +#include +#include +#include +#include +#include // for QApplication::revsreseLayout() +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include + +// KMix +#include "apps/kmix.h" +#include "gui/guiprofile.h" +#include "gui/kmixtoolbox.h" +#include "gui/mixdevicewidget.h" +#include "gui/viewsliders.h" +#include "core/GlobalConfig.h" +#include "core/mixer.h" +#include "core/mixertoolbox.h" + + +/** + This widget is embedded in the KMix Main window. Each Hardware Card is visualized by one KMixerWidget. + KMixerWidget contains + a TabBar with n Tabs (at least one per soundcard). These contain View's with sliders, switches and other GUI elements visualizing the Mixer) +*/ +KMixerWidget::KMixerWidget( Mixer *mixer, + QWidget * parent, ViewBase::ViewFlags vflags, QString guiprofId, + KActionCollection* actionCollection ) + : QWidget( parent ), _mixer(mixer), + m_topLayout(0), _guiprofId(guiprofId), + _actionCollection(actionCollection) +{ + createLayout(vflags); +} + +KMixerWidget::~KMixerWidget() +{ + foreach (ViewBase *view, _views) + { + delete view; + } + _views.clear(); +} + +/** + * Creates the widgets as described in the KMixerWidget constructor + */ +void KMixerWidget::createLayout(ViewBase::ViewFlags vflags) +{ + // delete old objects + delete m_topLayout; + + // create main layout + m_topLayout = new QVBoxLayout( this ); + m_topLayout->setSpacing( 3 ); + m_topLayout->setObjectName( QLatin1String( "m_topLayout" ) ); + + /******************************************************************* + * Now the main GUI is created. + * 1) Select a (GUI) profile, which defines which controls to show on which Tab + * 2a) Create the Tab's and the corresponding Views + * 2b) Create device widgets + * 2c) Add Views to Tab + ********************************************************************/ + GUIProfile* guiprof = getGuiprof(); + if ( guiprof != 0 ) + { + if (GlobalConfig::instance().data.debugGUI) + kDebug() << "Add a view " << _guiprofId; + ViewSliders* view = new ViewSliders( this, guiprof->getId(), _mixer, vflags, _guiprofId, _actionCollection ); + possiblyAddView(view); + } + show(); +} + + + +/** + * Add the given view, if it is valid - it must have controls or at least have the chance to gain some (dynamic views) + * @param vbase + * @return true, if the view was added + */ +bool KMixerWidget::possiblyAddView(ViewBase* vbase) +{ + if ( ! vbase->isValid() ) { + delete vbase; + return false; + } + else { + m_topLayout->addWidget(vbase); + _views.push_back(vbase); + connect( vbase, SIGNAL(toggleMenuBar()), parentWidget(), SLOT(toggleMenuBar()) ); + if (GlobalConfig::instance().data.debugGUI) + kDebug() << "CONNECT ViewBase count " << vbase->getMixers().size(); + return true; + } +} + +/** + * Returns the current View. Normally we have only one View, so we always return the first view. + * This method is only here for one reason: We can plug in an action in the main menu for the view, so that + * 99,99% of all users will be well served. Those who hack their own XML Profile to contain more than one view + must use the context menu for configuring the additional views. + */ +ViewBase* KMixerWidget::currentView() +{ + return _views.empty() ? 0 : _views[0]; +} + + +void KMixerWidget::setIcons( bool on ) +{ +const std::vector::const_iterator viewsEnd = _views.end(); + for ( std::vector::const_iterator it = _views.begin(); it != viewsEnd; ++it) { + ViewBase* mixerWidget = *it; + mixerWidget->setIcons(on); + } // for all tabs +} + + +void KMixerWidget::loadConfig( KConfig *config ) +{ + const std::vector::const_iterator viewsEnd = _views.end(); + for ( std::vector::const_iterator it = _views.begin(); it != viewsEnd; ++it) { + ViewBase* view = *it; + if (GlobalConfig::instance().data.debugVolume) + kDebug(67100) << "KMixerWidget::loadConfig()" << view->id(); + view->load(config); + view->configurationUpdate(); + } // for all tabs +} + + + +void KMixerWidget::saveConfig(KConfig *config) +{ + const std::vector::const_iterator viewsEnd = _views.end(); + for (std::vector::const_iterator it = _views.begin(); it != viewsEnd; ++it) + { + ViewBase* view = *it; + if (GlobalConfig::instance().data.debugVolume) + kDebug(67100) + << "KMixerWidget::saveConfig()" << view->id(); + view->save(config); + } // for all tabs +} + + +void KMixerWidget::toggleMenuBarSlot() { + emit toggleMenuBar(); +} + +#include "kmixerwidget.moc" diff --git a/kmix/gui/kmixerwidget.h b/kmix/gui/kmixerwidget.h new file mode 100644 index 00000000..be84a791 --- /dev/null +++ b/kmix/gui/kmixerwidget.h @@ -0,0 +1,87 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KMIXERWIDGET_H +#define KMIXERWIDGET_H + +#include + +#include +class QString; + + +#include "core/ControlManager.h" +#include "core/mixer.h" +#include "gui/mixdevicewidget.h" + +// QT +#include + +// KDE +class KActionCollection; +class KConfig; +//class KTabWidget; + +// KMix +class GUIProfile; +class ProfTab; +class Mixer; +#include "viewbase.h" +// KMix experimental + + +class KMixerWidget : public QWidget +{ + Q_OBJECT + + public: + explicit KMixerWidget( Mixer *mixer, + QWidget *parent, ViewBase::ViewFlags vflags, QString guiprofId, KActionCollection* coll = 0 ); + ~KMixerWidget(); + + Mixer *mixer() { return _mixer; } + ViewBase* currentView(); + GUIProfile* getGuiprof() { return GUIProfile::find(_guiprofId); }; + + + signals: + void toggleMenuBar(); + + public slots: + void setIcons( bool on ); + void toggleMenuBarSlot(); + + void saveConfig( KConfig *config ); + void loadConfig( KConfig *config ); + + private: + Mixer *_mixer; + QVBoxLayout *m_topLayout; // contains TabWidget + QString _guiprofId; + ProfTab* _tab; + std::vector _views; + KActionCollection* _actionCollection; // -<- applciations wide action collection + + void createLayout(ViewBase::ViewFlags vflags); + bool possiblyAddView(ViewBase* vbase); +}; + +#endif diff --git a/kmix/gui/kmixprefdlg.cpp b/kmix/gui/kmixprefdlg.cpp new file mode 100644 index 00000000..5419fd41 --- /dev/null +++ b/kmix/gui/kmixprefdlg.cpp @@ -0,0 +1,428 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * Copyright (C) 2001 Preston Brown + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "gui/kmixprefdlg.h" + +#include +#include +#include +#include +//#include + +#include +#include +#include +#include +#include + +#include "gui/kmixerwidget.h" +#include "core/GlobalConfig.h" + +KMixPrefDlg* KMixPrefDlg::instance = 0; + +KMixPrefDlg* KMixPrefDlg::getInstance() +{ + return instance; +} + +KMixPrefDlg* KMixPrefDlg::createInstance(QWidget *parent, GlobalConfig& config) +{ + if (instance == 0) + { + instance = new KMixPrefDlg(parent, config); + } + return instance; + +} + +KMixPrefDlg::KMixPrefDlg(QWidget *parent, GlobalConfig& config) : + KConfigDialog(parent, i18n("Configure"), &config), dialogConfig(config) + +{ + setFaceType(KPageDialog::List); + //setCaption(i18n("Configure")); + setButtons(Ok | Cancel | Apply); + + setDefaultButton(Ok); + + dvc = 0; + + // general buttons + m_generalTab = new QFrame(this); + m_controlsTab = new QFrame(this); + m_startupTab = new QFrame(this); + + createStartupTab(); + createGeneralTab(); + createControlsTab(); + updateWidgets(); // I thought KConfigDialog would call this, but I saw during a gdb session that it does not do so. + + showButtonSeparator(true); + + generalPage = addPage(m_generalTab, i18n("General"), "configure"); + startupPage = addPage(m_startupTab, i18n("Start"), "preferences-system-login"); + soundmenuPage = addPage(m_controlsTab, i18n("Sound Menu"), "audio-volume-high"); +} + +KMixPrefDlg::~KMixPrefDlg() +{ +} + +/** + * Switches to a specific page and shows it. + * @param page + */ +void KMixPrefDlg::switchToPage(KMixPrefPage page) +{ + switch (page) + { + case PrefGeneral: + setCurrentPage(generalPage); + break; + case PrefSoundMenu: + setCurrentPage(soundmenuPage); + break; + case PrefStartup: + setCurrentPage(startupPage); + break; + default: + kWarning() << "Tried to activated unknown preferences page" << page; + break; + } + show(); +} + + +// --- TABS -------------------------------------------------------------------------------------------------- +void KMixPrefDlg::createStartupTab() +{ + QBoxLayout* layoutStartupTab = new QVBoxLayout(m_startupTab); + layoutStartupTab->setMargin(0); + layoutStartupTab->setSpacing(KDialog::spacingHint()); + + QLabel* label = new QLabel(i18n("Startup"), m_startupTab); + layoutStartupTab->addWidget(label); + + m_onLogin = new QCheckBox(i18n("Restore volumes on login"), m_startupTab); + addWidgetToLayout(m_onLogin, layoutStartupTab, 10, i18n("Restore all volume levels and switches."), "startkdeRestore"); + + dynamicControlsRestoreWarning = new QLabel( + i18n("Dynamic controls from Pulseaudio and MPRIS2 will not be restored."), m_startupTab); + dynamicControlsRestoreWarning->setEnabled(false); + addWidgetToLayout(dynamicControlsRestoreWarning, layoutStartupTab, 10, "", ""); + + allowAutostart = new QCheckBox(i18n("Autostart"), m_startupTab); + addWidgetToLayout(allowAutostart, layoutStartupTab, 10, + i18n("Enables the KMix autostart service (kmix_autostart.desktop)"), "AutoStart"); + + + allowAutostartWarning = new QLabel( + i18n("Autostart can not be enabled, as the autostart file kmix_autostart.desktop is not installed."), + m_startupTab); + addWidgetToLayout(allowAutostartWarning, layoutStartupTab, 10, "", ""); + layoutStartupTab->addStretch(); +} + +void KMixPrefDlg::createOrientationGroup(const QString& labelSliderOrientation, QGridLayout* orientationLayout, int row, KMixPrefDlgPrefOrientationType prefType) +{ + QButtonGroup* orientationGroup = new QButtonGroup(m_generalTab); + orientationGroup->setExclusive(true); + QLabel* qlb = new QLabel(labelSliderOrientation, m_generalTab); + + QRadioButton* qrbHor = new QRadioButton(i18n("&Horizontal"), m_generalTab); + QRadioButton* qrbVert = new QRadioButton(i18n("&Vertical"), m_generalTab); + + if (prefType == TrayOrientation) + { + _rbTraypopupHorizontal = qrbHor; + _rbTraypopupVertical = qrbVert; + orientationGroup->setObjectName("Orientation.TrayPopup"); + } + else + { + _rbHorizontal = qrbHor; + _rbVertical = qrbVert; + orientationGroup->setObjectName("Orientation"); + } + + // Add both buttons to button group + orientationGroup->addButton(qrbHor); + orientationGroup->addButton(qrbVert); + // Add both buttons and label to layout + orientationLayout->addWidget(qlb, row, 0); + orientationLayout->addWidget(qrbHor, row, 1); + orientationLayout->addWidget(qrbVert, row, 2); + + connect(qrbHor, SIGNAL(toggled(bool)), SLOT(updateButtons())); + connect(qrbVert, SIGNAL(toggled(bool)), SLOT(updateButtons())); + + connect(this, SIGNAL(applyClicked()), SLOT(kmixConfigHasChangedEmitter())); + connect(this, SIGNAL(okClicked()), SLOT(kmixConfigHasChangedEmitter())); + +// connect(qrbHor, SIGNAL(toggled(bool)), SLOT(settingsChangedSlot())); +// connect(qrbVert, SIGNAL(toggled(bool)), SLOT(settingsChangedSlot())); +} + +void KMixPrefDlg::createGeneralTab() +{ + QBoxLayout* layout = new QVBoxLayout(m_generalTab); + layout->setMargin(0); + layout->setSpacing(KDialog::spacingHint()); + + // --- Behavior --------------------------------------------------------- + QLabel* label = new QLabel(i18n("Behavior"), m_generalTab); + layout->addWidget(label); + + // [CONFIG] + m_beepOnVolumeChange = new QCheckBox(i18n("Volume Feedback"), m_generalTab); + addWidgetToLayout(m_beepOnVolumeChange, layout, 10, "", "VolumeFeedback"); + + volumeFeedbackWarning = new QLabel(i18n("Volume feedback is only available for Pulseaudio."), m_generalTab); + volumeFeedbackWarning->setEnabled(false); + addWidgetToLayout(volumeFeedbackWarning, layout, 20, "", ""); + + // [CONFIG] + m_volumeOverdrive = new QCheckBox(i18n("Volume Overdrive"), m_generalTab); + addWidgetToLayout(m_volumeOverdrive, layout, 10, i18nc("@info:tooltip", "Raise volume maximum to 150% (PulseAudio only)"), "VolumeOverdrive"); + volumeOverdriveWarning = new QLabel(i18n("You must restart KMix for this setting to take effect."), m_generalTab); + volumeOverdriveWarning->setEnabled(false); + addWidgetToLayout(volumeOverdriveWarning, layout, 20, "", ""); + + // --- Visual --------------------------------------------------------- + QLabel* label2 = new QLabel(i18n("Visual"), m_generalTab); + layout->addWidget(label2); + + // [CONFIG] + m_showTicks = new QCheckBox(i18n("Show &tickmarks"), m_generalTab); + addWidgetToLayout(m_showTicks, layout, 10, i18n("Enable/disable tickmark scales on the sliders"), "Tickmarks"); + + m_showLabels = new QCheckBox(i18n("Show &labels"), m_generalTab); + addWidgetToLayout(m_showLabels, layout, 10, i18n("Enables/disables description labels above the sliders"), + "Labels"); + + // [CONFIG] + m_showOSD = new QCheckBox(i18n("Show On Screen Display (&OSD)"), m_generalTab); + addWidgetToLayout(m_showOSD, layout, 10, "", "showOSD"); + + + // [CONFIG] Slider orientation (main window) + QGridLayout* orientationGrid = new QGridLayout(); + layout->addItem(orientationGrid); + + createOrientationGroup(i18n("Slider orientation: "), orientationGrid, 0, KMixPrefDlg::MainOrientation); + + // Slider orientation (tray popup). We use an extra setting + QBoxLayout* orientation2Layout = new QHBoxLayout(); + layout->addItem(orientation2Layout); + createOrientationGroup(i18n("Slider orientation (System tray volume control):"), orientationGrid, 1, KMixPrefDlg::TrayOrientation); + + // Push everything above to the top + layout->addStretch(); +} + +void KMixPrefDlg::createControlsTab() +{ + layoutControlsTab = new QVBoxLayout(m_controlsTab); + layoutControlsTab->setMargin(0); + layoutControlsTab->setSpacing(KDialog::spacingHint()); + m_dockingChk = new QCheckBox(i18n("&Dock in system tray"), m_controlsTab); + + addWidgetToLayout(m_dockingChk, layoutControlsTab, 10, i18n("Docks the mixer into the KDE system tray"), + "AllowDocking"); + + replaceBackendsInTab(); +} + + + +// --- Helper -------------------------------------------------------------------------------------------------- + +/** + * Register widget with correct name for KConfigDialog, then add it to the given layout + * + * @param widget + * @param layout + * @param spacingBefore + * @param toopTipText + * @param objectName + */ +void KMixPrefDlg::addWidgetToLayout(QWidget* widget, QBoxLayout* layout, int spacingBefore, QString tooltip, QString kconfigName) +{ + if (!kconfigName.isEmpty()) + { + // Widget to be registered for KConfig + widget->setObjectName("kcfg_" + kconfigName); + } + + if ( !tooltip.isEmpty() ) + { + widget->setToolTip(tooltip); + } + + QBoxLayout *l = new QHBoxLayout(); + l->addSpacing(spacingBefore); + l->addWidget(widget); + layout->addItem(l); +} + +// --- KConfigDialog CUSTOM WIDGET management ------------------------------------------------------------------------ + + +/** + * Update Widgets from config. + *

+ * Hint: this get internally called by KConfigdialog on initialization and reset. + */ +void KMixPrefDlg::updateWidgets() +{ + kDebug() << ""; + bool toplevelHorizontal = dialogConfig.data.getToplevelOrientation() == Qt::Horizontal; + _rbHorizontal->setChecked(toplevelHorizontal); + _rbVertical->setChecked(!toplevelHorizontal); + + bool trayHorizontal = dialogConfig.data.getTraypopupOrientation() == Qt::Horizontal; + _rbTraypopupHorizontal->setChecked(trayHorizontal); + _rbTraypopupVertical->setChecked(!trayHorizontal); +} + +/** + * Updates config from the widgets. And emits the signal kmixConfigHasChanged(). + *

+ * Hint: this get internally called by KConfigDialog after pressing the OK or Apply button. + */ +void KMixPrefDlg::updateSettings() +{ + Qt::Orientation toplevelOrientation = _rbHorizontal->isChecked() ? Qt::Horizontal : Qt::Vertical; + kDebug() << "toplevelOrientation" << toplevelOrientation << ", _rbHorizontal->isChecked()" << _rbHorizontal->isChecked(); + dialogConfig.data.setToplevelOrientation(toplevelOrientation); + + Qt::Orientation trayOrientation = _rbTraypopupHorizontal->isChecked() ? Qt::Horizontal : Qt::Vertical; + kDebug() << "trayOrientation" << trayOrientation << ", _rbTraypopupHorizontal->isChecked()" << _rbTraypopupHorizontal->isChecked(); + dialogConfig.data.setTraypopupOrientation(trayOrientation); + + // Announcing MasterChanged, as the sound menu (aka ViewDockAreaPopup) primarily shows master volume(s). + // In any case, ViewDockAreaPopup treats MasterChanged and ControlList the same, so it is better to announce + // the "smaller" change. + bool modified = dvc->getAndResetModifyFlag(); + if (modified) + { + GlobalConfig::instance().setMixersForSoundmenu(dvc->getChosenBackends()); + ControlManager::instance().announce(QString(), ControlChangeType::MasterChanged, QString("Select Backends Dialog")); + } +} + +void KMixPrefDlg::kmixConfigHasChangedEmitter() +{ + emit(kmixConfigHasChanged()); +} + + + +/** + * Returns whether the custom widgets (orientation checkboxes) has changed. + *

+ * Hint: this get internally called by KConfigDialog from updateButtons(). + * @return + */ +bool KMixPrefDlg::hasChanged() +{ + bool orientationFromConfigIsHor = dialogConfig.data.getToplevelOrientation() == Qt::Horizontal; + bool orientationFromWidgetIsHor = _rbHorizontal->isChecked(); + kDebug() << "Orientation MAIN fromConfig=" << (orientationFromConfigIsHor ? "Hor" : "Vert") << ", fromWidget=" << (orientationFromWidgetIsHor ? "Hor" : "Vert"); + + bool changed = orientationFromConfigIsHor ^ orientationFromWidgetIsHor; + if (!changed) + { + bool orientationFromConfigIsHor = dialogConfig.data.getTraypopupOrientation() == Qt::Horizontal; + orientationFromWidgetIsHor = _rbTraypopupHorizontal->isChecked(); + kDebug() << "Orientation TRAY fromConfig=" << (orientationFromConfigIsHor ? "Hor" : "Vert") << ", fromWidget=" << (orientationFromWidgetIsHor ? "Hor" : "Vert"); + + changed = orientationFromConfigIsHor ^ orientationFromWidgetIsHor; + } + if (!changed) + { + changed = dvc->getModifyFlag(); + } + + kDebug() << "hasChanged=" << changed; + + return changed; +} + + +void KMixPrefDlg::showEvent(QShowEvent * event) +{ + // -1- Replace widgets ------------------------------------------------------------ + // Hotplug can change mixers or backends => recreate tab + replaceBackendsInTab(); + + // -2- Change visibility and enable status (of the new widgets) ---------------------- + + // As GUI can change, the warning will only been shown on demand + dynamicControlsRestoreWarning->setVisible(Mixer::dynamicBackendsPresent()); + + // Pulseaudio supports volume feedback. Disable the configuaration option for all other backends + // and show a warning. + bool volumeFeebackAvailable = Mixer::pulseaudioPresent(); + volumeFeedbackWarning->setVisible(!volumeFeebackAvailable); + m_beepOnVolumeChange->setDisabled(!volumeFeebackAvailable); + + bool overdriveAvailable = volumeFeebackAvailable; // "shortcut" for Mixer::pulseaudioPresent() (see above) + m_volumeOverdrive->setVisible(overdriveAvailable); + volumeOverdriveWarning->setVisible(overdriveAvailable); + + QString autostartConfigFilename = KGlobal::dirs()->findResource("autostart", QString("kmix_autostart.desktop")); + kDebug() + << "autostartConfigFilename = " << autostartConfigFilename; + bool autostartFileExists = !autostartConfigFilename.isNull(); + + //allowAutostartWarning->setEnabled(autostartFileExists); + allowAutostartWarning->setVisible(!autostartFileExists); + allowAutostart->setEnabled(autostartFileExists); + + KDialog::showEvent(event); +} + + +void KMixPrefDlg::replaceBackendsInTab() +{ + if (dvc != 0) + { + layoutControlsTab->removeWidget(dvc); + delete dvc; + } + + QSet backendsFromConfig = GlobalConfig::instance().getMixersForSoundmenu(); + dvc = new DialogChooseBackends(0, backendsFromConfig); + connect(dvc, SIGNAL(backendsModified()), SLOT(updateButtons())); + + dvc->show(); + layoutControlsTab->addWidget(dvc); + + // Push everything above to the top + layoutControlsTab->addStretch(); +} + + + + +#include "kmixprefdlg.moc" diff --git a/kmix/gui/kmixprefdlg.h b/kmix/gui/kmixprefdlg.h new file mode 100644 index 00000000..72237afe --- /dev/null +++ b/kmix/gui/kmixprefdlg.h @@ -0,0 +1,129 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KMIXPREFDLG_H +#define KMIXPREFDLG_H + +#include +#include + +class KMixPrefWidget; + +class QBoxLayout; +class QCheckBox; +class QFrame; +#include +class QLabel; +class QRadioButton; +class QShowEvent; +class QWidget; + +#include "core/GlobalConfig.h" +#include "gui/dialogchoosebackends.h" + + +class KMixPrefDlg: public KConfigDialog +{ +Q_OBJECT + +public: + enum KMixPrefPage + { + PrefGeneral, PrefSoundMenu, PrefStartup + }; + + static KMixPrefDlg* createInstance(QWidget *parent, GlobalConfig& config); + static KMixPrefDlg* getInstance(); + void switchToPage(KMixPrefPage page); + +signals: + void kmixConfigHasChanged(); + +private slots: + void kmixConfigHasChangedEmitter(); + +protected: + void showEvent(QShowEvent * event); + /** + * Orientation is not supported by default => implement manually + * @Override + */ + void updateWidgets(); + /** + * Orientation is not supported by default => implement manually + * @Override + */ + void updateSettings(); + + bool hasChanged(); + +private: + static KMixPrefDlg* instance; + + KMixPrefDlg(QWidget *parent, GlobalConfig& config); + virtual ~KMixPrefDlg(); + + enum KMixPrefDlgPrefOrientationType + { + MainOrientation, TrayOrientation + }; + + GlobalConfig& dialogConfig; + + void addWidgetToLayout(QWidget* widget, QBoxLayout* layout, int spacingBefore, QString tooltip, QString kconfigName); + + void createStartupTab(); + void replaceBackendsInTab(); + void createGeneralTab(); + void createControlsTab(); + void createOrientationGroup(const QString& labelSliderOrientation, QGridLayout* orientationLayout, int row, KMixPrefDlgPrefOrientationType type); + + QFrame *m_generalTab; + QFrame *m_startupTab; + QFrame *m_controlsTab; + + QCheckBox *m_dockingChk; + QLabel *dynamicControlsRestoreWarning; + QCheckBox *m_showTicks; + QCheckBox *m_showLabels; + QCheckBox* m_showOSD; + QCheckBox *m_onLogin; + QCheckBox *allowAutostart; + QLabel *allowAutostartWarning; + QCheckBox *m_beepOnVolumeChange; + QCheckBox *m_volumeOverdrive; + QLabel *volumeFeedbackWarning; + QLabel *volumeOverdriveWarning; + + QBoxLayout *layoutControlsTab; + DialogChooseBackends* dvc; + + QRadioButton *_rbVertical; + QRadioButton *_rbHorizontal; + QRadioButton *_rbTraypopupVertical; + QRadioButton *_rbTraypopupHorizontal; + + KPageWidgetItem* generalPage; + KPageWidgetItem* soundmenuPage; + KPageWidgetItem* startupPage; +}; + +#endif // KMIXPREFDLG_H diff --git a/kmix/gui/kmixtoolbox.cpp b/kmix/gui/kmixtoolbox.cpp new file mode 100644 index 00000000..07e0df8c --- /dev/null +++ b/kmix/gui/kmixtoolbox.cpp @@ -0,0 +1,91 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "gui/kmixtoolbox.h" + +#include +#include + +//#include +#include +#include +#include + +#include "gui/guiprofile.h" +#include "mdwslider.h" +#include "gui/mixdevicewidget.h" +#include "core/mixdevice.h" +#include "core/mixer.h" +#include "viewbase.h" + +// TODO KMixToolbox is rather superfluous today, as there is no "KMix Applet" any more, and it was probably always bad style. +// I only have to think what to do with KMixToolBox::notification() + +/*********************************************************************************** + KMixToolbox contains several GUI relevant methods that are shared between the + KMix Main Program, and the KMix Applet. + kmixctrl - as not non-GUI application - does NOT link to KMixToolBox. + + This means: Shared GUI stuff goes into the KMixToolBox class , non-GUI stuff goes + into the MixerToolBox class. + ***********************************************************************************/ +void KMixToolBox::setIcons(QList &mdws, bool on ) { + for (int i=0; i < mdws.count(); ++i ){ + QWidget *mdw = mdws[i]; + if ( mdw->inherits("MixDeviceWidget") ) { // -<- play safe here + static_cast(mdw)->setIcons( on ); + } + } +} + +void KMixToolBox::setLabels(QList &mdws, bool on ) { + for (int i=0; i < mdws.count(); ++i ){ + QWidget *mdw = mdws[i]; + if ( mdw->inherits("MixDeviceWidget") ) { // -<- play safe here + static_cast(mdw)->setLabeled( on ); + } + } +} + +void KMixToolBox::setTicks(QList &mdws, bool on ) { + for (int i=0; i < mdws.count(); ++i ){ + QWidget *mdw = mdws[i]; + if ( mdw->inherits("MixDeviceWidget") ) { // -<- play safe here + static_cast(mdw)->setTicks( on ); + } + } +} + +void KMixToolBox::notification(const char *notificationName, const QString &text, + const QStringList &actions, QObject *receiver, + const char *actionSlot) +{ + KNotification *notification = new KNotification(notificationName); + //notification->setComponentData(componentData()); + notification->setText(text); + //notification->setPixmap(...); + notification->addContext(QLatin1String("Application"), KGlobal::mainComponent().componentName()); + if (!actions.isEmpty() && receiver && actionSlot) { + notification->setActions(actions); + QObject::connect(notification, SIGNAL(activated(uint)), receiver, actionSlot); + } + notification->sendEvent(); +} diff --git a/kmix/gui/kmixtoolbox.h b/kmix/gui/kmixtoolbox.h new file mode 100644 index 00000000..834f2adc --- /dev/null +++ b/kmix/gui/kmixtoolbox.h @@ -0,0 +1,44 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef KMIXTOOLBOX_H +#define KMIXTOOLBOX_H + +#include "qlist.h" +#include "qwidget.h" + + + +/** + * This toolbox contains various static methods that are shared throughout KMix. + * The reason, why it is not put in a common base class is, that the classes are + * very different and cannot be changed (e.g. KPanelApplet) without major headache. + */ + +class KMixToolBox { + public: + static void setIcons (QList &mdws, bool on ); + static void setLabels (QList &mdws, bool on ); + static void setTicks (QList &mdws, bool on ); + + static void notification(const char *notificationName, const QString &text, const QStringList &actions = QStringList(), QObject *receiver = 0, const char *actionSlot = 0); +}; + +#endif diff --git a/kmix/gui/ksmallslider.cpp b/kmix/gui/ksmallslider.cpp new file mode 100644 index 00000000..a681b4fd --- /dev/null +++ b/kmix/gui/ksmallslider.cpp @@ -0,0 +1,441 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "gui/ksmallslider.h" + +// For INT_MAX +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kglobalsettings.h" +#include "core/mixer.h" + +KSmallSlider::KSmallSlider( int minValue, int maxValue, int pageStep, + int value, Qt::Orientation orientation, + QWidget *parent, const char * /*name*/ ) + : QAbstractSlider( parent ) +{ + init(); + setOrientation(orientation); + setRange(minValue, maxValue); + setSingleStep(1); + setPageStep(pageStep); + setValue(value); + setTracking(true); +} + +void KSmallSlider::init() +{ + grayed = false; + setFocusPolicy( Qt::TabFocus ); + + colHigh = QColor(0,255,0); + colLow = QColor(255,0,0); + colBack = QColor(0,0,0); + + grayHigh = QColor(255,255,255); + grayLow = QColor(128,128,128); + grayBack = QColor(0,0,0); +} + +int KSmallSlider::positionFromValue( int v ) const +{ + return positionFromValue( v, available() ); +} + +int KSmallSlider::valueFromPosition( int p ) const +{ + if ( orientation() == Qt::Vertical ) { + // Coordiante System starts at TopLeft, but the slider values increase from Bottom to Top + // Thus "revert" the position + int avail = available(); + return valueFromPosition( avail - p, avail ); + } + else { + // Horizontal everything is fine. Slider values match with Coordinate System + return valueFromPosition( p, available() ); + } +} + +/* postionFromValue() discontinued in in Qt4 => taken from Qt3 */ +int KSmallSlider::positionFromValue( int logical_val, int span ) const +{ + if ( span <= 0 || logical_val < minimum() || maximum() <= minimum() ) + return 0; + if ( logical_val > maximum() ) + return span; + + uint range = maximum() - minimum(); + uint p = logical_val - minimum(); + + if ( range > (uint)INT_MAX/4096 ) { + const int scale = 4096*2; + return ( (p/scale) * span ) / (range/scale); + // ### the above line is probably not 100% correct + // ### but fixing it isn't worth the extreme pain... + } else if ( range > (uint)span ) { + return (2*p*span + range) / (2*range); + } else { + uint div = span / range; + uint mod = span % range; + return p*div + (2*p*mod + range) / (2*range); + } + //equiv. to (p*span)/range + 0.5 + // no overflow because of this implicit assumption: + // span <= 4096 +} + +/* valueFromPositon() discontinued in in Qt4 => taken from Qt3 */ +int KSmallSlider::valueFromPosition( int pos, int span ) const +{ + if ( span <= 0 || pos <= 0 ) + return minimum(); + if ( pos >= span ) + return maximum(); + + uint range = maximum() - minimum(); + + if ( (uint)span > range ) + return minimum() + (2*pos*range + span) / (2*span); + else { + uint div = range / span; + uint mod = range % span; + return minimum() + pos*div + (2*pos*mod + span) / (2*span); + } + // equiv. to minimum() + (pos*range)/span + 0.5 + // no overflow because of this implicit assumption: + // pos <= span < sqrt(INT_MAX+0.0625)+0.25 ~ sqrt(INT_MAX) +} + + +void KSmallSlider::resizeEvent( QResizeEvent * ) +{ + update(); + //QWidget::resizeEvent( ev ); +} + +// Returns the really available space for the slider. If there is no space, 0 is returned; +int KSmallSlider::available() const +{ + int available = 0; + if ( orientation() == Qt::Vertical) { + available = height(); + } + else { + available = width(); + } + if ( available > 1 ) { + available -= 2; + } + else { + available = 0; + } + return available; +} + + + +namespace +{ + +void gradient( QPainter &p, bool hor, const QRect &rect, const QColor &ca, const QColor &cb, int /*ncols*/) +{ + int rDiff, gDiff, bDiff; + int rca, gca, bca, rcb, gcb, bcb; + + register int x, y; + + if ((rect.width()<=0) || (rect.height()<=0)) return; + + rDiff = (rcb = cb.red()) - (rca = ca.red()); + gDiff = (gcb = cb.green()) - (gca = ca.green()); + bDiff = (bcb = cb.blue()) - (bca = ca.blue()); + + register int rl = rca << 16; + register int gl = gca << 16; + register int bl = bca << 16; + + int rcdelta = ((1<<16) / ((!hor) ? rect.height() : rect.width())) * rDiff; + int gcdelta = ((1<<16) / ((!hor) ? rect.height() : rect.width())) * gDiff; + int bcdelta = ((1<<16) / ((!hor) ? rect.height() : rect.width())) * bDiff; + + // these for-loops could be merged, but the if's in the inner loop + // would make it slow + if (!hor) + { + for ( y = rect.top(); y <= rect.bottom(); y++ ) { + rl += rcdelta; + gl += gcdelta; + bl += bcdelta; + + p.setPen(QColor(rl>>16, gl>>16, bl>>16)); + p.drawLine(rect.left(), y, rect.right(), y); + } + } else + { + for( x = rect.left(); x <= rect.right(); x++) { + rl += rcdelta; + gl += gcdelta; + bl += bcdelta; + + p.setPen(QColor(rl>>16, gl>>16, bl>>16)); + p.drawLine(x, rect.top(), x, rect.bottom()); + } + } +} + +QColor interpolate( const QColor& low, const QColor& high, int percent ) { + if ( percent<=0 ) return low; else + if ( percent>=100 ) return high; else + return QColor( + low.red() + (high.red()-low.red()) * percent/100, + low.green() + (high.green()-low.green()) * percent/100, + low.blue() + (high.blue()-low.blue()) * percent/100 ); +} + +} + +void KSmallSlider::paintEvent( QPaintEvent * ) +{ +// kDebug(67100) << "KSmallSlider::paintEvent: width() = " << width() << ", height() = " << height(); + QPainter p( this ); + + int sliderPos = positionFromValue( QAbstractSlider::value() ); + + // ------------------------ draw 3d border --------------------------------------------- + QStyleOptionSlider option; + option.init(this); + style()->drawPrimitive ( QStyle::PE_Frame, &option, &p ); + + + // ------------------------ draw lower/left part ---------------------------------------- + if ( width()>2 && height()>2 ) + { + if ( orientation() == Qt::Horizontal ) { + QRect outer = QRect( 1, 1, sliderPos, height() - 2 ); +// kDebug(67100) << "KSmallSlider::paintEvent: outer = " << outer; + + if ( grayed ) + gradient( p, true, outer, grayLow, + interpolate( grayLow, grayHigh, 100*sliderPos/(width()-2) ), + 32 ); + else + gradient( p, true, outer, colLow, + interpolate( colLow, colHigh, 100*sliderPos/(width()-2) ), + 32 ); + } + else { + QRect outer = QRect( 1, height()-sliderPos-1, width() - 2, sliderPos-1 ); +/* + kDebug(67100) << "KSmallSlider::paintEvent: sliderPos=" << sliderPos + << "height()=" << height() + << "width()=" << width() + << "outer = " << outer << endl; +*/ + if ( grayed ) + gradient( p, false, outer, + interpolate( grayLow, grayHigh, 100*sliderPos/(height()-2) ), + grayLow, 32 ); + else + gradient( p, false, outer, + interpolate( colLow, colHigh, 100*sliderPos/(height()-2) ), + colLow, 32 ); + } + + // -------- draw upper/right part -------------------------------------------------- + QRect inner; + if ( orientation() == Qt::Vertical ) { + inner = QRect( 1, 1, width() - 2, height() - 2 -sliderPos ); + } + else { + inner = QRect( sliderPos + 1, 1, width() - 2 - sliderPos, height() - 2 ); + } + + if ( grayed ) { + p.setBrush( grayBack ); + p.setPen( grayBack ); + } else { + p.setBrush( colBack ); + p.setPen( colBack ); + } + p.drawRect( inner ); + } +} + +void KSmallSlider::mousePressEvent( QMouseEvent *e ) +{ + //resetState(); + + if ( e->button() == Qt::RightButton ) { + return; + } + + int pos = goodPart( e->pos() ); + moveSlider( pos ); +} + +void KSmallSlider::mouseMoveEvent( QMouseEvent *e ) +{ + int pos = goodPart( e->pos() ); + moveSlider( pos ); +} + + +void KSmallSlider::wheelEvent( QWheelEvent * qwe) +{ +// kDebug(67100) << "KSmallslider::wheelEvent()"; + // bko313579 Do not use "delta", as that is setting more related to documents (Editor, Browser). KMix should + // simply always use its own VOLUME_STEP_DIVISOR as a base for percentage change. + bool decrease = qwe->delta() < 0; + if (qwe->orientation() == Qt::Horizontal) // Reverse horizontal scroll: bko228780 + decrease = !decrease; + + int inc = ( maximum() - minimum() ) / Volume::VOLUME_STEP_DIVISOR; + if ( inc < 1) + inc = 1; + + //kDebug(67100) << "KSmallslider::wheelEvent() inc=" << inc << "delta=" << e->delta(); + int newVal; + + if ( !decrease ) { + newVal = QAbstractSlider::value() + inc; + } + else { + newVal = QAbstractSlider::value() - inc; + } + setValue( newVal ); + emit valueChanged(newVal); + qwe->accept(); // Accept the event + + // Hint: Qt autmatically triggers a valueChange() when we do setValue() +} + + + +/* + * Moves slider to a dedicated position. If the value has changed + */ +void KSmallSlider::moveSlider( int pos ) +{ + int a = available(); + int newPos = qMin( a, qMax( 0, pos ) ); // keep it inside the available bounds of the slider + int newVal = valueFromPosition( newPos ); + + if ( newVal != value() ) { + setValue( newVal ); + emit valueChanged(newVal); + // probably done by Qt: emit valueChanged( value() ); // Only for external use + // probably we need update() here + } + update(); +} + + +int KSmallSlider::goodPart( const QPoint &p ) const +{ + if ( orientation() == Qt::Vertical ) { + return p.y() - 1; + } + else { + return p.x() - 1; + } +} + +/***************** SIZE STUFF START ***************/ +QSize KSmallSlider::sizeHint() const +{ + //constPolish(); + const int length = 25; + const int thick = 10; + + if ( orientation() == Qt::Vertical ) + return QSize( thick, length ); + else + return QSize( length, thick ); +} + + +QSize KSmallSlider::minimumSizeHint() const +{ + QSize s(10,10); + return s; +} + + +QSizePolicy KSmallSlider::sizePolicy() const +{ + + if ( orientation() == Qt::Vertical ) { + //kDebug(67100) << "KSmallSlider::sizePolicy() vertical value=(Fixed,MinimumExpanding)\n"; + return QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Expanding ); + } + else { + //kDebug(67100) << "KSmallSlider::sizePolicy() horizontal value=(MinimumExpanding,Fixed)\n"; + return QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); + } +} +/***************** SIZE STUFF END ***************/ + + + +void KSmallSlider::setGray( bool value ) +{ + if ( grayed!=value ) + { + grayed = value; + update(); + //repaint(); + } +} + +bool KSmallSlider::gray() const +{ + return grayed; +} + +void KSmallSlider::setColors( QColor high, QColor low, QColor back ) +{ + colHigh = high; + colLow = low; + colBack = back; + update(); + //repaint(); +} + +void KSmallSlider::setGrayColors( QColor high, QColor low, QColor back ) +{ + grayHigh = high; + grayLow = low; + grayBack = back; + update(); + //repaint(); +} + +#include "ksmallslider.moc" diff --git a/kmix/gui/ksmallslider.h b/kmix/gui/ksmallslider.h new file mode 100644 index 00000000..ee20f523 --- /dev/null +++ b/kmix/gui/ksmallslider.h @@ -0,0 +1,88 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KSMALLSLIDER_H +#define KSMALLSLIDER_H + +#include +#include + +#include "core/volume.h" +#include "volumesliderextradata.h" + +class KSmallSlider : public QAbstractSlider +{ + Q_OBJECT + + public: + KSmallSlider( int minValue, int maxValue, int pageStep, int value, + Qt::Orientation, QWidget *parent, const char *name=0 ); + +/* void setChid(Volume::ChannelID chid) { this->chid = chid; }; + Volume::ChannelID getChid() { return chid; };*/ + + QSize sizeHint() const; + QSizePolicy sizePolicy() const; + QSize minimumSizeHint() const; + + bool gray() const; + + VolumeSliderExtraData extraData; + +public slots: + void setGray( bool value ); + void setColors( QColor high, QColor low, QColor back ); + void setGrayColors( QColor high, QColor low, QColor back ); + + signals: + void valueChanged( int value ); + + protected: + void resizeEvent( QResizeEvent * ); + void paintEvent( QPaintEvent * ); + + void mousePressEvent( QMouseEvent * ); + void mouseMoveEvent( QMouseEvent * ); + void wheelEvent( QWheelEvent * ); + + void valueChange(); + + private: + + void init(); + int positionFromValue( int ) const; + int valueFromPosition( int ) const; + int positionFromValue( int logical_val, int span ) const; + int valueFromPosition( int pos, int span ) const; + void moveSlider( int ); + + int available() const; + int goodPart( const QPoint& ) const; + + bool grayed; + QColor colHigh, colLow, colBack; + QColor grayHigh, grayLow, grayBack; + +// Volume::ChannelID chid; +}; + +#endif diff --git a/kmix/gui/mdwenum.cpp b/kmix/gui/mdwenum.cpp new file mode 100644 index 00000000..ca899279 --- /dev/null +++ b/kmix/gui/mdwenum.cpp @@ -0,0 +1,199 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +// KMix +#include "mdwenum.h" +#include "core/mixer.h" +#include "viewbase.h" + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Qt +#include +#include +#include +#include +#include + +/** + * Class that represents an Enum element (a select one-from-many selector) + * The orientation (horizontal, vertical) is ignored + */ +MDWEnum::MDWEnum( shared_ptr md, + Qt::Orientation orientation, + QWidget* parent, ViewBase* view, ProfControl* par_pctl) : + MixDeviceWidget(md, false, orientation, parent, view, par_pctl), + _label(0), _enumCombo(0), _layout(0) +{ + // create actions (on _mdwActions, see MixDeviceWidget) + + // KStandardAction::showMenubar() is in MixDeviceWidget now + KToggleAction *action = _mdwActions->add( "hide" ); + action->setText( i18n("&Hide") ); + connect(action, SIGNAL(triggered(bool)), SLOT(setDisabled(bool))); + QAction *c = _mdwActions->addAction( "keys" ); + c->setText( i18n("C&onfigure Shortcuts...") ); + connect(c, SIGNAL(triggered(bool)), SLOT(defineKeys())); + + // create widgets + createWidgets(); + + /* remove this for production version + QAction *a = _mdwActions->addAction( "Next Value" ); + c->setText( i18n( "Next Value" ) ); + connect(a, SIGNAL(triggered(bool)), SLOT(nextEnumId())); + */ + + installEventFilter( this ); // filter for popup +} + +MDWEnum::~MDWEnum() +{ +} + + +void MDWEnum::createWidgets() +{ + if ( _orientation == Qt::Vertical ) { + _layout = new QVBoxLayout( this ); + _layout->setAlignment(Qt::AlignLeft); + } + else { + _layout = new QHBoxLayout( this ); + _layout->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); + } + + _label = new QLabel( m_mixdevice->readableName(), this); + _layout->addWidget(_label); + _enumCombo = new KComboBox( false, this); + _enumCombo->installEventFilter(this); + // ------------ fill ComboBox start ------------ + int maxEnumId= m_mixdevice->enumValues().count(); + for (int i=0; iaddItem( m_mixdevice->enumValues().at(i)); + } + // ------------ fill ComboBox end -------------- + _layout->addWidget(_enumCombo); + connect( _enumCombo, SIGNAL(activated(int)), this, SLOT(setEnumId(int)) ); + _enumCombo->setToolTip( m_mixdevice->readableName() ); + _layout->addStretch(1); +} + +void MDWEnum::update() +{ + if ( m_mixdevice->isEnum() ) { + //kDebug(67100) << "MDWEnum::update() enumID=" << m_mixdevice->enumId(); + _enumCombo->setCurrentIndex( m_mixdevice->enumId() ); + } + else { + kError(67100) << "MDWEnum::update() enumID=" << m_mixdevice->enumId() << " is no Enum ... skipped" << endl; + } +} + +void MDWEnum::showContextMenu(const QPoint& pos ) +{ + if( m_view == 0 ) + return; + + KMenu *menu = m_view->getPopup(); + + menu->popup( pos ); +} + + +QSizePolicy MDWEnum::sizePolicy() const +{ + return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); +} + +/** + This slot is called, when a user has clicked the mute button. Also it is called by any other + associated KAction like the context menu. +*/ +void MDWEnum::nextEnumId() { + if( m_mixdevice->isEnum() ) { + int curEnum = enumId(); + if ( curEnum < m_mixdevice->enumValues().count() ) { + // next enum value + setEnumId(curEnum+1); + } + else { + // wrap around + setEnumId(0); + } + } // isEnum +} + +void MDWEnum::setEnumId(int value) +{ + if ( m_mixdevice->isEnum() ) { + m_mixdevice->setEnumId( value ); + m_mixdevice->mixer()->commitVolumeChange( m_mixdevice ); + } +} + +int MDWEnum::enumId() +{ + if ( m_mixdevice->isEnum() ) { + return m_mixdevice->enumId(); + } + else { + return 0; + } +} + + +void MDWEnum::setDisabled( bool hide ) +{ + emit guiVisibilityChange(this, !hide); +} + +/** + * An event filter for the various QWidgets. We watch for Mouse press Events, so + * that we can popup the context menu. + */ +bool MDWEnum::eventFilter( QObject* obj, QEvent* e ) +{ + if (e->type() == QEvent::MouseButtonPress) { + QMouseEvent *qme = static_cast(e); + if (qme->button() == Qt::RightButton) { + showContextMenu(); + return true; + } + } else if (e->type() == QEvent::ContextMenu) { + QPoint pos = reinterpret_cast(obj)->mapToGlobal(QPoint(0, 0)); + showContextMenu(pos); + return true; + } + return QWidget::eventFilter(obj,e); +} + +#include "mdwenum.moc" diff --git a/kmix/gui/mdwenum.h b/kmix/gui/mdwenum.h new file mode 100644 index 00000000..d0c3bf44 --- /dev/null +++ b/kmix/gui/mdwenum.h @@ -0,0 +1,80 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2004 Chrisitan Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MDWENUM_H +#define MDWENUM_H + +#include +#include "core/volume.h" + +// KMix +class MixDevice; +class ViewBase; + +// KDE +class KAction; +class KComboBox; + +// Qt +class QBoxLayout; +class QLabel; + +#include "gui/mixdevicewidget.h" + +class MDWEnum : public MixDeviceWidget +{ + Q_OBJECT + +public: + MDWEnum( shared_ptr md, + Qt::Orientation orientation, + QWidget* parent, ViewBase* view, ProfControl* pctl); + ~MDWEnum(); + + void addActionToPopup( KAction *action ); + QSizePolicy sizePolicy() const; + bool eventFilter( QObject* obj, QEvent* e ); + +public slots: + // GUI hide and show + void setDisabled(bool); + + // Enum handling: next and selecting + void nextEnumId(); + int enumId(); + void setEnumId(int value); + + void update(); + virtual void showContextMenu(const QPoint& pos = QCursor::pos()); + +signals: + virtual void guiVisibilityChange(MixDeviceWidget* source, bool enable); + +private: + void createWidgets(); + + QLabel *_label; + KComboBox *_enumCombo; + QBoxLayout *_layout; +}; + +#endif diff --git a/kmix/gui/mdwmoveaction.cpp b/kmix/gui/mdwmoveaction.cpp new file mode 100644 index 00000000..4fafdf1b --- /dev/null +++ b/kmix/gui/mdwmoveaction.cpp @@ -0,0 +1,48 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +//KMix +#include "mdwmoveaction.h" +#include "core/mixdevice.h" + +// Qt +#include + +MDWMoveAction::MDWMoveAction(shared_ptr md, QObject *parent) + : KAction(parent), m_mixDevice(md) +{ + Q_ASSERT(md); + + setText(m_mixDevice->readableName()); + setIcon(KIcon(m_mixDevice->iconName())); + connect(this, SIGNAL(triggered(bool)), SLOT(triggered(bool))); +} + +MDWMoveAction::~MDWMoveAction() +{ +} + +void MDWMoveAction::triggered(bool checked) +{ + Q_UNUSED(checked); + emit moveRequest(m_mixDevice->id()); +} diff --git a/kmix/gui/mdwmoveaction.h b/kmix/gui/mdwmoveaction.h new file mode 100644 index 00000000..acc6e6e4 --- /dev/null +++ b/kmix/gui/mdwmoveaction.h @@ -0,0 +1,46 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef MDWMoveAction_h +#define MDWMoveAction_h + +#include + +#include "core/mixdevice.h" + +class MDWMoveAction : public KAction +{ + Q_OBJECT + + public: + MDWMoveAction(shared_ptr md, QObject *parent); + ~MDWMoveAction(); + + signals: + void moveRequest(QString id); + + protected slots: + void triggered(bool checked); + + private: + shared_ptr m_mixDevice; +}; + +#endif diff --git a/kmix/gui/mdwslider.cpp b/kmix/gui/mdwslider.cpp new file mode 100644 index 00000000..2e55af8d --- /dev/null +++ b/kmix/gui/mdwslider.cpp @@ -0,0 +1,1303 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2007 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "gui/mdwslider.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/ControlManager.h" +#include "core/mixer.h" +#include "gui/guiprofile.h" +#include "gui/volumeslider.h" +#include "gui/viewbase.h" +#include "gui/ksmallslider.h" +#include "gui/verticaltext.h" +#include "gui/mdwmoveaction.h" + + +bool MDWSlider::debugMe = false; + /** + * MixDeviceWidget that represents a single mix device, including PopUp, muteLED, ... + * + * Used in KMix main window and DockWidget and PanelApplet. + * It can be configured to include or exclude the captureLED and the muteLED. + * The direction (horizontal, vertical) can be configured and whether it should + * be "small" (uses KSmallSlider instead of a normal slider widget). + * + * Due to the many options, this is the most complicated MixDeviceWidget subclass. + */ +MDWSlider::MDWSlider(shared_ptr md, bool showMuteLED, bool showCaptureLED + , bool includeMixerName, bool small, Qt::Orientation orientation, QWidget* parent + , ViewBase* view + , ProfControl* par_ctl + ) : + MixDeviceWidget(md,small,orientation,parent,view, par_ctl), + m_linked(true), muteButtonSpacer(0), captureSpacer(0), labelSpacer(0), + m_iconLabelSimple(0), m_qcb(0), m_muteText(0), + m_label( 0 ), + mediaButton(0), + m_captureCheckbox(0), m_captureText(0), labelSpacing(0), + muteButtonSpacing(false), captureLEDSpacing(false), _mdwMoveActions(new KActionCollection(this)), m_moveMenu(0), + m_sliderInWork(0), m_waitForSoundSetComplete(0) +{ + createActions(); + createWidgets( showMuteLED, showCaptureLED, includeMixerName ); + createShortcutActions(); + installEventFilter( this ); // filter for popup + update(); +} + +MDWSlider::~MDWSlider() +{ + foreach( QAbstractSlider* slider, m_slidersPlayback) + { + delete slider; + } + foreach( QAbstractSlider* slider, m_slidersCapture) + { + delete slider; + } +} + +void MDWSlider::createActions() +{ + // create actions (on _mdwActions, see MixDeviceWidget) + KToggleAction *taction = _mdwActions->add( "stereo" ); + taction->setText( i18n("&Split Channels") ); + connect( taction, SIGNAL(triggered(bool)), SLOT(toggleStereoLinked()) ); + + KAction *action; + if ( ! m_mixdevice->mixer()->isDynamic() ) { + action = _mdwActions->add( "hide" ); + action->setText( i18n("&Hide") ); + connect( action, SIGNAL(triggered(bool)), SLOT(setDisabled(bool)) ); + } + + if( m_mixdevice->hasMuteSwitch() ) + { + taction = _mdwActions->add( "mute" ); + taction->setText( i18n("&Muted") ); + connect( taction, SIGNAL(toggled(bool)), SLOT(toggleMuted()) ); + } + + if( m_mixdevice->captureVolume().hasSwitch() ) { + taction = _mdwActions->add( "recsrc" ); + taction->setText( i18n("Set &Record Source") ); + connect( taction, SIGNAL(toggled(bool)), SLOT(toggleRecsrc()) ); + } + + if( m_mixdevice->isMovable() ) { + m_moveMenu = new KMenu( i18n("Mo&ve"), this); + connect( m_moveMenu, SIGNAL(aboutToShow()), SLOT(showMoveMenu()) ); + } + + action = _mdwActions->addAction( "keys" ); + action->setText( i18n("C&onfigure Shortcuts...") ); + connect( action, SIGNAL(triggered(bool)), SLOT(defineKeys()) ); +} + +void MDWSlider::addGlobalShortcut(KAction* action, const QString& label, bool dynamicControl) +{ + QString finalLabel(label); + finalLabel += " - " + mixDevice()->readableName() + ", " + mixDevice()->mixer()->readableName(); + + action->setText(label); + if (!dynamicControl) + { + // virtual / dynamic controls won't get shortcuts + // #ifdef __GNUC__ + // #warning GLOBAL SHORTCUTS ARE NOW ASSIGNED TO ALL CONTROLS, as enableGlobalShortcut(), has not been committed + // #endif + // b->enableGlobalShortcut(); + // enableGlobalShortcut() is not there => use workaround + action->setGlobalShortcut(dummyShortcut); + } +} + +void MDWSlider::createShortcutActions() +{ + bool dynamicControl = mixDevice()->mixer()->isDynamic(); + // The following actions are for the "Configure Shortcuts" dialog + /* PLEASE NOTE THAT global shortcuts are saved with the name as set with setName(), instead of their action name. + This is a bug according to the thread "Global shortcuts are saved with their text-name and not their action-name - Bug?" on kcd. + I work around this by using a text with setText() that is unique, but still readable to the user. + */ + QString actionSuffix = QString(" - %1, %2").arg( mixDevice()->readableName() ).arg( mixDevice()->mixer()->readableName() ); + KAction *b; + + // -1- INCREASE VOLUME SHORTCUT ----------------------------------------- + b = _mdwPopupActions->addAction( QString("Increase volume %1").arg( actionSuffix ) ); + QString increaseVolumeName = i18n( "Increase Volume" ); + addGlobalShortcut(b, increaseVolumeName, dynamicControl); + if ( ! dynamicControl ) + connect( b, SIGNAL(triggered(bool)), SLOT(increaseVolume()) ); + + // -2- DECREASE VOLUME SHORTCUT ----------------------------------------- + b = _mdwPopupActions->addAction( QString("Decrease volume %1").arg( actionSuffix ) ); + QString decreaseVolumeName = i18n( "Decrease Volume" ); + addGlobalShortcut(b, decreaseVolumeName, dynamicControl); + if ( ! dynamicControl ) + connect(b, SIGNAL(triggered(bool)), SLOT(decreaseVolume())); + + // -3- MUTE VOLUME SHORTCUT ----------------------------------------- + b = _mdwPopupActions->addAction( QString("Toggle mute %1").arg( actionSuffix ) ); + QString muteVolumeName = i18n( "Toggle Mute" ); + addGlobalShortcut(b, muteVolumeName, dynamicControl); + if ( ! dynamicControl ) + connect( b, SIGNAL(triggered(bool)), SLOT(toggleMuted()) ); + +} + + +QSizePolicy MDWSlider::sizePolicy() const +{ + if ( _orientation == Qt::Vertical ) + { + return QSizePolicy( QSizePolicy::Preferred, QSizePolicy::MinimumExpanding ); + } + else + { + return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred ); +// return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); + } +} + +QSize MDWSlider::sizeHint() const +{ + return QSize( 90, QWidget::sizeHint().height()); +} + +/** + * This method is a helper for users of this class who would like + * to show multiple MDWSlider, and align the sliders. + * It returns the "height" (if vertical) of this widgets label. + * Warning: Line wraps are computed for a fixed size (100), this may be unaccurate in case, + * the widgets have different sizes. + */ +int MDWSlider::labelExtentHint() const +{ + if ( _orientation == Qt::Vertical && m_label ) { + return m_label->heightForWidth(m_label->minimumWidth()); + } + return 0; +} + +/** + * If a label from another widget has more lines than this widget, then a spacer is added under the label + */ +void MDWSlider::setLabelExtent(int extent) { + if ( _orientation == Qt::Vertical ) { + if ( labelExtentHint() < extent ) + labelSpacer->setFixedHeight( extent - labelExtentHint() ); + else + labelSpacer->setFixedHeight(0); + } +} + +/** + * Alignment helper + */ +bool MDWSlider::hasMuteButton() const +{ + return m_qcb!=0; +} + + +/** + * If this widget does not have a mute button, but another widget has, we add a spacer here with the + * size of a QToolButton (don't know how to make a better estimate) + */ +void MDWSlider::setMuteButtonSpace(bool value) +{ + if (hasMuteButton() || !value) { + muteButtonSpacer->setFixedSize(0,0); + muteButtonSpacer->setVisible(false); + } else { + QToolButton b; + muteButtonSpacer->setFixedSize( b.sizeHint() ); + } +} + +/** + * See "hasMuteButton" + */ +bool MDWSlider::hasCaptureLED() const +{ + return m_captureCheckbox!=0; +} + +/** + * See "setMuteButtonSpace" + */ +void MDWSlider::setCaptureLEDSpace(bool showCaptureLED) +{ + if ( !showCaptureLED || hasCaptureLED() ) { + captureSpacer->setFixedSize(0,0); + captureSpacer->setVisible(false); + } else + captureSpacer->setFixedSize(QCheckBox().sizeHint()); +} + +void MDWSlider::guiAddSlidersAndMediacontrols(bool playSliders, bool capSliders, bool mediaControls, QBoxLayout* layout, const QString& tooltipText, const QString& captureTooltipText) +{ + if (playSliders) + addSliders(layout, 'p', m_mixdevice->playbackVolume(), m_slidersPlayback, tooltipText); + + if (capSliders) + addSliders(layout, 'c', m_mixdevice->captureVolume(), m_slidersCapture, captureTooltipText); + + if (mediaControls) + addMediaControls(layout); +} + +void MDWSlider::guiAddCaptureCheckbox(bool wantsCaptureLED, const Qt::Alignment& alignmentForCapture, QBoxLayout* layoutForCapture, const QString& captureTooltipText) +{ + if (wantsCaptureLED && m_mixdevice->captureVolume().hasSwitch()) + { + m_captureCheckbox = new QCheckBox(i18n("capture"), this); + m_captureCheckbox->installEventFilter(this); + layoutForCapture->addWidget(m_captureCheckbox, alignmentForCapture); + connect(m_captureCheckbox, SIGNAL(toggled(bool)), this, SLOT(setRecsrc(bool))); + m_captureCheckbox->setToolTip(captureTooltipText); + } +} + +void MDWSlider::guiAddMuteButton(bool wantsMuteButton, Qt::Alignment alignment, QBoxLayout* layoutForMuteButton, const QString& muteTooltipText) +{ + if (wantsMuteButton && m_mixdevice->hasMuteSwitch()) + { + m_qcb = new QToolButton(this); + m_qcb->setAutoRaise(true); + m_qcb->setCheckable(false); + m_qcb->setIcon(QIcon(loadIcon("audio-volume-muted"))); + layoutForMuteButton->addWidget(m_qcb, 0, alignment); + m_qcb->installEventFilter(this); + connect(m_qcb, SIGNAL(clicked(bool)), this, SLOT(toggleMuted())); + m_qcb->setToolTip(muteTooltipText); + } + + // Spacer will be shown, when no mute button is displayed + muteButtonSpacer = new QWidget(this); + layoutForMuteButton->addWidget( muteButtonSpacer ); + muteButtonSpacer->installEventFilter(this); + +} + +void MDWSlider::guiAddControlIcon(Qt::Alignment alignment, QBoxLayout* layout, const QString& tooltipText) +{ + m_iconLabelSimple = new QLabel(this); + installEventFilter(m_iconLabelSimple); + setIcon(m_mixdevice->iconName(), m_iconLabelSimple); + m_iconLabelSimple->setToolTip(tooltipText); + layout->addWidget(m_iconLabelSimple, 0, alignment); +} + +/** + * Creates all widgets : Icon, Label, Mute-Button, Slider(s) and Capture-Button. + */ +void MDWSlider::createWidgets( bool showMuteButton, bool showCaptureLED, bool includeMixerName ) +{ + bool includePlayback = _pctl->useSubcontrolPlayback(); + bool includeCapture = _pctl->useSubcontrolCapture(); + bool wantsPlaybackSliders = includePlayback && ( m_mixdevice->playbackVolume().count() > 0 ); + bool wantsCaptureSliders = includeCapture && ( m_mixdevice->captureVolume().count() > 0 ); + bool wantsCaptureLED = showCaptureLED && includeCapture; + bool wantsMuteButton = showMuteButton && includePlayback; + bool hasVolumeSliders = wantsPlaybackSliders || wantsCaptureSliders; + // bool bothCaptureANDPlaybackExist = wantsPlaybackSliders && wantsCaptureSliders; + + MediaController* mediaController = m_mixdevice->getMediaController(); + bool wantsMediaControls = mediaController->hasControls(); + + QString tooltipText = m_mixdevice->readableName(); + QString captureTooltipText( i18n( "Capture/Uncapture %1", m_mixdevice->readableName() ) ); + QString muteTooltipText( i18n( "Mute/Unmute %1", m_mixdevice->readableName() ) ); + if (includeMixerName) { + tooltipText = QString( "%1\n%2" ).arg( m_mixdevice->mixer()->readableName() ).arg( tooltipText ); + captureTooltipText = QString( "%1\n%2" ).arg( m_mixdevice->mixer()->readableName() ).arg( captureTooltipText ); + muteTooltipText = QString( "%1\n%2" ).arg( m_mixdevice->mixer()->readableName() ).arg( muteTooltipText ); + } + + // case of vertical sliders: + if ( _orientation == Qt::Vertical ) + { + QVBoxLayout *controlLayout = new QVBoxLayout(this); + controlLayout->setAlignment(Qt::AlignHCenter|Qt::AlignTop); + setLayout(controlLayout); + controlLayout->setContentsMargins(0,0,0,0); + + guiAddControlIcon(Qt::AlignHCenter|Qt::AlignTop, controlLayout, tooltipText); + + Qt::Alignment centerAlign = Qt::AlignHCenter | Qt::AlignBottom; + + //the device label + m_label = new QLabel( m_mixdevice->readableName(), this); + m_label->setWordWrap(true); + int max = 80; + QStringList words = m_mixdevice->readableName().split(QChar(' ')); + foreach (QString name, words) + max = qMax(max,QLabel(name).sizeHint().width()); +// if (words.size()>1 && m_label) +// m_label->setMinimumWidth(80); +// if (m_label->sizeHint().width()>max && m_label->sizeHint().width()>80) +// m_label->setMinimumWidth(max); + m_label->setMinimumWidth(max); + m_label->setMinimumHeight(m_label->heightForWidth(m_label->minimumWidth())); + m_label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + m_label->setAlignment(Qt::AlignHCenter); + controlLayout->addWidget(m_label, 0, centerAlign ); + + //spacer with height to match height difference to other slider widgets + labelSpacer = new QWidget(this); + controlLayout->addWidget( labelSpacer ); + labelSpacer->installEventFilter(this); + + // sliders + QBoxLayout *volLayout = new QHBoxLayout( ); + volLayout->setAlignment(centerAlign); + controlLayout->addItem( volLayout ); + + guiAddSlidersAndMediacontrols(wantsPlaybackSliders, wantsCaptureSliders, wantsMediaControls, volLayout, tooltipText, captureTooltipText); + if ( !hasVolumeSliders ) + controlLayout->addStretch(1); // Not sure why we have this for "vertical sliders" case + + guiAddCaptureCheckbox(wantsCaptureLED, centerAlign, controlLayout, captureTooltipText); + + // spacer which is shown when no capture button present + captureSpacer = new QWidget(this); + controlLayout->addWidget( captureSpacer ); + captureSpacer->installEventFilter(this); + + + //mute button + guiAddMuteButton(wantsMuteButton, centerAlign, controlLayout, muteTooltipText); + } + else + { + /* + * Horizontal sliders: row1 contains the label (and capture button). + * row2 contains icon, sliders, and mute button + */ + + QVBoxLayout *rows = new QVBoxLayout( this ); + + // --- ROW1 ------------------------------------------------------------------------ + QHBoxLayout *row1 = new QHBoxLayout(); + rows->addItem( row1 ); + + m_label = new QLabel(m_mixdevice->readableName(), this); + m_label->installEventFilter( this ); + row1->addWidget( m_label ); + row1->setAlignment(m_label, Qt::AlignVCenter); + + row1->addStretch(); + row1->addWidget(captureSpacer); + + guiAddCaptureCheckbox(wantsCaptureLED, Qt::AlignRight, row1, captureTooltipText); + captureSpacer = new QWidget(this); // create, but do not add to any layout (not used!) + + + // --- ROW2 ------------------------------------------------------------------------ + QHBoxLayout *row2 = new QHBoxLayout(); + row2->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); + rows->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); + rows->addItem( row2 ); + + guiAddControlIcon(Qt::AlignVCenter, row2, tooltipText); + + + + // --- SLIDERS --------------------------- + QBoxLayout *volLayout = new QVBoxLayout( ); + volLayout->setAlignment(Qt::AlignVCenter|Qt::AlignRight); + row2->addItem( volLayout ); + + guiAddSlidersAndMediacontrols(wantsPlaybackSliders, wantsCaptureSliders, wantsMediaControls, volLayout, tooltipText, captureTooltipText); + guiAddMuteButton(wantsMuteButton, Qt::AlignRight, row2, muteTooltipText); + } + + bool stereoLinked = !_pctl->isSplit(); + setStereoLinked( stereoLinked ); + + layout()->activate(); // Activate it explicitly in KDE3 because of PanelApplet/kicker issues +} + +QString MDWSlider::calculatePlaybackIcon(MediaController::PlayState playState) +{ + QString mediaIconName; + switch (playState) + { + case MediaController::PlayPlaying: + // playing => show pause icon + mediaIconName = "media-playback-pause"; + break; + case MediaController::PlayPaused: + // stopped/paused => show play icon + mediaIconName = "media-playback-start"; + break; + case MediaController::PlayStopped: + // stopped/paused => show play icon + mediaIconName = "media-playback-start"; + break; + default: + // unknown => not good, probably result from player has not yet arrived => show a play button + mediaIconName = "media-playback-start"; + break; + } + + return mediaIconName; +} + +void MDWSlider::addMediaControls(QBoxLayout* volLayout) +{ + MediaController* mediaController = mixDevice()->getMediaController(); + + QBoxLayout *mediaLayout; + if (_orientation == Qt::Vertical) + mediaLayout = new QVBoxLayout(); + else + mediaLayout = new QHBoxLayout(); + +// QFrame* frame1 = new QFrame(this); +// frame1->setFrameShape(QFrame::StyledPanel); + QWidget* frame = this; // or frame1 + mediaLayout->addStretch(); + if (mediaController->hasMediaPrevControl()) + { + QToolButton *lbl = addMediaButton("media-skip-backward", mediaLayout, frame); + connect(lbl, SIGNAL(clicked(bool)), this, SLOT(mediaPrev(bool))); + } + if (mediaController->hasMediaPlayControl()) + { + MediaController::PlayState playState = mediaController->getPlayState(); + QString mediaIcon = calculatePlaybackIcon(playState); + mediaButton = addMediaButton(mediaIcon, mediaLayout, frame); + connect(mediaButton, SIGNAL(clicked(bool)), this, SLOT(mediaPlay(bool))); + } + + if (mediaController->hasMediaNextControl()) + { + QToolButton *lbl = addMediaButton("media-skip-forward", mediaLayout, frame); + connect(lbl, SIGNAL(clicked(bool)), this, SLOT(mediaNext(bool))); + } + mediaLayout->addStretch(); + volLayout->addLayout(mediaLayout); +} + + +QToolButton* MDWSlider::addMediaButton(QString iconName, QLayout* layout, QWidget *parent) +{ + QToolButton *lbl = new QToolButton(parent); + lbl->setIconSize(QSize(22,22)); + lbl->setAutoRaise(true); + lbl->setCheckable(false); + + setIcon(iconName, lbl); + layout->addWidget(lbl); + + return lbl; +} + +/** + * Updates the icon according to the data model. + */ +void MDWSlider::updateMediaButton() +{ + if (mediaButton == 0) + return; // has no media button + + MediaController* mediaController = mixDevice()->getMediaController(); + QString mediaIconName = calculatePlaybackIcon(mediaController->getPlayState()); + setIcon(mediaIconName, mediaButton); +} + +void MDWSlider::mediaPrev(bool) +{ + mixDevice()->mediaPrev(); +} + +void MDWSlider::mediaNext(bool) +{ + mixDevice()->mediaNext(); +} + +void MDWSlider::mediaPlay(bool) +{ + mixDevice()->mediaPlay(); +} + +void MDWSlider::addSliders( QBoxLayout *volLayout, char type, Volume& vol, + QList& ref_sliders, QString tooltipText) +{ + const int minSliderSize = fontMetrics().height() * 10; + long minvol = vol.minVolume(); + long maxvol = vol.maxVolume(); + + QMap vols = vol.getVolumes(); + + foreach (VolumeChannel vc, vols ) + { + //kDebug(67100) << "Add label to " << vc.chid << ": " << Volume::ChannelNameReadable[vc.chid]; + QWidget *subcontrolLabel; + + QString subcontrolTranslation; + if ( type == 'c' ) subcontrolTranslation += i18n("Capture") + ' '; + subcontrolTranslation += Volume::ChannelNameReadable[vc.chid]; //Volume::getSubcontrolTranslation(chid); + subcontrolLabel = createLabel(this, subcontrolTranslation, volLayout, true); + + QAbstractSlider* slider; + if ( m_small ) + { + slider = new KSmallSlider( minvol, maxvol, (maxvol-minvol+1) / Volume::VOLUME_PAGESTEP_DIVISOR, + vol.getVolume( vc.chid ), _orientation, this ); + } // small + else { + slider = new VolumeSlider( _orientation, this ); + slider->setMinimum(minvol); + slider->setMaximum(maxvol); + slider->setPageStep(maxvol / Volume::VOLUME_PAGESTEP_DIVISOR); + slider->setValue( vol.getVolume( vc.chid ) ); + volumeValues.push_back( vol.getVolume( vc.chid ) ); + + extraData(slider).setSubcontrolLabel(subcontrolLabel); + + if ( _orientation == Qt::Vertical ) { + slider->setMinimumHeight( minSliderSize ); + } + else { + slider->setMinimumWidth( minSliderSize ); + } + if ( ! _pctl->getBackgroundColor().isEmpty() ) { + slider->setStyleSheet("QSlider { background-color: " + _pctl->getBackgroundColor() + " }"); + } + } // not small + + extraData(slider).setChid(vc.chid); + slider->installEventFilter( this ); + if ( type == 'p' ) { + slider->setToolTip( tooltipText ); + } + else { + QString captureTip( i18n( "%1 (capture)", tooltipText ) ); + slider->setToolTip( captureTip ); + } + + volLayout->addWidget( slider ); // add to layout + ref_sliders.append ( slider ); // add to list + //ref_slidersChids.append(vc.chid); + connect( slider, SIGNAL(valueChanged(int)), SLOT(volumeChange(int)) ); + connect( slider, SIGNAL(sliderPressed()), SLOT(sliderPressed()) ); + connect( slider, SIGNAL(sliderReleased()), SLOT(sliderReleased()) ); + + } // for all channels of this device +} + +/** + * Return the VolumeSliderExtraData from either VolumeSlider or KSmallSlider. + * You MUST extend this method, should you decide to add more Slider Widget classes. + * + * @param slider + * @return + */ +VolumeSliderExtraData& MDWSlider::extraData(QAbstractSlider *slider) +{ + VolumeSlider* sl = qobject_cast(slider); + if ( sl ) + return sl->extraData; + + KSmallSlider* sl2 = qobject_cast(slider); + return sl2->extraData; +} + + +void MDWSlider::sliderPressed() +{ + m_sliderInWork = true; +} + + +void MDWSlider::sliderReleased() +{ + m_sliderInWork = false; +} + + +QWidget* MDWSlider::createLabel(QWidget* parent, QString& label, QBoxLayout *layout, bool small) +{ + QFont qf; + qf.setPointSize(8); + + QWidget* labelWidget; + if (_orientation == Qt::Horizontal) + { + labelWidget = new QLabel(label, parent); + if ( small ) ((QLabel*)labelWidget)->setFont(qf); + } + else { + labelWidget = new VerticalText(parent, label); + if ( small ) ((VerticalText*)labelWidget)->setFont(qf); + } + + labelWidget->installEventFilter( parent ); + layout->addWidget(labelWidget); + + return labelWidget; +} + + +QPixmap MDWSlider::loadIcon( QString filename ) +{ + return KIconLoader::global()->loadIcon( filename, KIconLoader::Small, KIconLoader::SizeSmallMedium ); +} + + +//void MDWSlider::setIcon( QString filename, QLabel** label ) +//{ +// if( (*label) == 0 ) +// { +// *label = new QLabel(this); +// installEventFilter( *label ); +// } +// setIcon(filename, *label); +//} + +void MDWSlider::setIcon( QString filename, QWidget* label ) +{ + QPixmap miniDevPM = loadIcon( filename ); + if ( !miniDevPM.isNull() ) + { + if ( m_small ) + { + // scale icon + QMatrix t; + t = t.scale( 10.0/miniDevPM.width(), 10.0/miniDevPM.height() ); + miniDevPM = miniDevPM.transformed( t ); + label->resize( 10, 10 ); + } // small size + else + { + label->setMinimumSize(22,22); + } + label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + QLabel* lbl = qobject_cast(label); + if ( lbl != 0 ) + { + lbl->setPixmap( miniDevPM ); + lbl->setAlignment(Qt::AlignHCenter | Qt::AlignCenter); + } // QLabel + else + { + QToolButton* tbt = qobject_cast(label); + if ( tbt != 0 ) + { + tbt->setIcon( miniDevPM ); + } // QToolButton + } + } + else + { + kError(67100) << "Pixmap missing. filename=" << filename << endl; + } +} + +QString MDWSlider::iconName() +{ + return m_mixdevice->iconName(); +} + +void +MDWSlider::toggleStereoLinked() +{ + setStereoLinked( !isStereoLinked() ); +} + +void +MDWSlider::setStereoLinked(bool value) +{ + m_linked = value; + + int overallSlidersToShow = 0; + if ( ! m_slidersPlayback.isEmpty() ) overallSlidersToShow += ( m_linked ? 1 : m_slidersPlayback.count() ); + if ( ! m_slidersCapture.isEmpty() ) overallSlidersToShow += ( m_linked ? 1 : m_slidersCapture.count() ); + + bool showSubcontrolLabels = (overallSlidersToShow >= 2); + setStereoLinkedInternal(m_slidersPlayback, showSubcontrolLabels); + setStereoLinkedInternal(m_slidersCapture , showSubcontrolLabels); + update(); // Call update(), so that the sliders can adjust EITHER to the individual values OR the average value. +} + +void +MDWSlider::setStereoLinkedInternal(QList& ref_sliders, bool showSubcontrolLabels) +{ + if ( ref_sliders.isEmpty()) + return; + + bool first = true; + foreach ( QAbstractSlider* slider1, ref_sliders ) + { + slider1->setVisible(!m_linked || first); // One slider (the 1st) is always shown + extraData(slider1).getSubcontrolLabel()->setVisible(!m_linked && showSubcontrolLabels); // (*) + first = false; + /* (*) cesken: I have excluded the "|| first" check because the text would not be nice: + * It would be "Left" or "Capture Left", while it should be "Playback" and "Capture" in the "linked" case. + * + * But the only affected situation is when we have playback AND capture on the same control, where we show no label. + * It would be nice to put at least a "Capture" label on the capture subcontrol instead. + * To achieve this we would need to exchange the Text on the first capture subcontrol dynamically. This can + * be done, but I'll leave this open for now. + */ + } + + + + // Redo the tickmarks to last slider in the slider list. + // The implementation is not obvious, so lets explain: + // We ALWAYS have tickmarks on the LAST slider. Sometimes the slider is not shown, and then we just don't bother. + // a) So, if the last slider has tickmarks, we can always call setTicks( true ). + // b) if the last slider has NO tickmarks, there ae no tickmarks at all, and we don't need to redo the tickmarks. + QSlider* slider = qobject_cast( ref_sliders.last() ); + if( slider && slider->tickPosition() != QSlider::NoTicks) + setTicks( true ); + +} + + +void +MDWSlider::setLabeled(bool value) +{ + if ( m_label != 0) m_label->setVisible(value); + if ( m_muteText != 0) m_muteText->setVisible(value); + if ( m_captureText != 0) m_captureText->setVisible(value); + layout()->activate(); +} + +void +MDWSlider::setTicks( bool value ) +{ + if (m_slidersPlayback.count() != 0) setTicksInternal(m_slidersPlayback, value); + if (m_slidersCapture.count() != 0) setTicksInternal(m_slidersCapture, value); +} + +/** + * Enables or disables tickmarks + * Please note that always only the first and last slider has tickmarks. + * + */ +void MDWSlider::setTicksInternal(QList& ref_sliders, bool ticks) +{ + VolumeSlider* slider = qobject_cast( ref_sliders[0]); + if (slider == 0 ) return; // Ticks are only in VolumeSlider, but not in KSmallslider + + if( ticks ) + { + if( isStereoLinked() ) + slider->setTickPosition( QSlider::TicksRight ); + else + { + slider->setTickPosition( QSlider::NoTicks ); + slider = qobject_cast(ref_sliders.last()); + slider->setTickPosition( QSlider::TicksLeft ); + } + } + else + { + slider->setTickPosition( QSlider::NoTicks ); + slider = qobject_cast(ref_sliders.last()); + slider->setTickPosition( QSlider::NoTicks ); + } +} + +void +MDWSlider::setIcons(bool value) +{ + if ( m_iconLabelSimple != 0 ) { + if ( ( !m_iconLabelSimple->isHidden() ) !=value ) { + if (value) + m_iconLabelSimple->show(); + else + m_iconLabelSimple->hide(); + + layout()->activate(); + } + } // if it has an icon +} + +void +MDWSlider::setColors( QColor high, QColor low, QColor back ) +{ + for( int i=0; i(slider); + if ( smallSlider ) smallSlider->setColors( high, low, back ); + } + for( int i=0; i(slider); + if ( smallSlider ) smallSlider->setColors( high, low, back ); + } +} + +void +MDWSlider::setMutedColors( QColor high, QColor low, QColor back ) +{ + for( int i=0; i(slider); + if ( smallSlider ) smallSlider->setGrayColors( high, low, back ); + } + for( int i=0; i(slider); + if ( smallSlider ) smallSlider->setGrayColors( high, low, back ); + } +} + + +/** This slot is called, when a user has changed the volume via the KMix Slider. */ +void MDWSlider::volumeChange( int ) +{ +// if ( mixDevice()->id() == "Headphone:0" ) +// { +// kDebug(67100) << "headphone bug"; +// } + if (!m_slidersPlayback.isEmpty()) + { + m_waitForSoundSetComplete ++; + volumeValues.push_back(m_slidersPlayback.first()->value()); + volumeChangeInternal(m_mixdevice->playbackVolume(), m_slidersPlayback); + } + if (!m_slidersCapture.isEmpty()) + { + volumeChangeInternal(m_mixdevice->captureVolume(), m_slidersCapture); + } + + bool oldViewBlockSignalState = m_view->blockSignals(true); + m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); + m_view->blockSignals(oldViewBlockSignalState); +} + +void MDWSlider::volumeChangeInternal(Volume& vol, QList& ref_sliders) +{ + if (isStereoLinked()) + { + QAbstractSlider* firstSlider = ref_sliders.first(); + m_mixdevice->setMuted(false); + vol.setAllVolumes(firstSlider->value()); + } + else + { + for (int i = 0; i < ref_sliders.count(); i++) + { + if (m_mixdevice->isMuted()) + { // changing from muted state: unmute (the "if" above is actually superfluous) + m_mixdevice->setMuted(false); + } + QAbstractSlider *sliderWidget = ref_sliders[i]; + vol.setVolume(extraData(sliderWidget).getChid(), sliderWidget->value()); + } // iterate over all sliders + } +} + + +/** + This slot is called, when a user has clicked the recsrc button. Also it is called by any other + associated KAction like the context menu. + */ +void MDWSlider::toggleRecsrc() +{ + setRecsrc( m_mixdevice->isRecSource() ); +} + +void MDWSlider::setRecsrc(bool value ) +{ + if ( m_mixdevice->captureVolume().hasSwitch() ) + { + m_mixdevice->setRecSource( value ); + m_mixdevice->mixer()->commitVolumeChange( m_mixdevice ); + } +} + + +/** + This slot is called, when a user has clicked the mute button. Also it is called by any other + associated KAction like the context menu. + */ +void MDWSlider::toggleMuted() +{ + setMuted( !m_mixdevice->isMuted() ); +} + +void MDWSlider::setMuted(bool value) +{ + if ( m_mixdevice->hasMuteSwitch() ) + { + m_mixdevice->setMuted( value ); + m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); + } +} + + +void MDWSlider::setDisabled( bool hide ) +{ + emit guiVisibilityChange(this, !hide); +} + + +/** + * This slot is called on a Keyboard Shortcut event, except for the XF86Audio* shortcuts which hare handeled by the + * KMixWindow class. So for 99.9% of all users, this methos is never called. + */ +void MDWSlider::increaseVolume() +{ + increaseOrDecreaseVolume(false, Volume::Both); +} + +/** + * This slot is called on a Keyboard Shortcut event, except for the XF86Audio* shortcuts which hare handeled by the + * KMixWindow class. So for 99.9% of all users, this methos is never called. + */ +void MDWSlider::decreaseVolume() +{ + increaseOrDecreaseVolume(true, Volume::Both); +} + +/** + * Increase or decrease all playback and capture channels of the given control. + * This method is very similar to Mixer::increaseOrDecreaseVolume(), but it will + * auto-unmute on increase. + * + * @param mixdeviceID The control name + * @param decrease true for decrease. false for increase + */ +void MDWSlider::increaseOrDecreaseVolume(bool decrease, Volume::VolumeTypeFlag volumeType) +{ + m_mixdevice->increaseOrDecreaseVolume(decrease, volumeType); + // I should possibly not block, as the changes that come back from the Soundcard + // will be ignored (e.g. because of capture groups) +// kDebug() << "MDWSlider is blocking signals for " << m_view->id(); +// bool oldViewBlockSignalState = m_view->blockSignals(true); + m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); +// kDebug() << "MDWSlider is unblocking signals for " << m_view->id(); +// m_view->blockSignals(oldViewBlockSignalState); +} + +void MDWSlider::moveStreamAutomatic() +{ + m_mixdevice->mixer()->moveStream(m_mixdevice->id(), ""); +} + +void MDWSlider::moveStream(QString destId) +{ + m_mixdevice->mixer()->moveStream(m_mixdevice->id(), destId); +} + +/** + * This is called whenever there are volume updates pending from the hardware for this MDW. + */ +void MDWSlider::update() +{ +// bool debugMe = (mixDevice()->id() == "PCM:0" ); +// if (debugMe) kDebug() << "The update() PCM:0 playback state" << mixDevice()->isMuted() +// << ", vol=" << mixDevice()->playbackVolume().getAvgVolumePercent(Volume::MALL); + + if ( m_slidersPlayback.count() != 0 || m_mixdevice->hasMuteSwitch() ) + updateInternal(m_mixdevice->playbackVolume(), m_slidersPlayback, m_mixdevice->isMuted() ); + if ( m_slidersCapture.count() != 0 || m_mixdevice->captureVolume().hasSwitch() ) + updateInternal(m_mixdevice->captureVolume(), m_slidersCapture, m_mixdevice->isNotRecSource() ); + if (m_label) + { + QLabel *l; + VerticalText *v; + if ((l = dynamic_cast(m_label))) + l->setText(m_mixdevice->readableName()); + else if ((v = dynamic_cast(m_label))) + v->setText(m_mixdevice->readableName()); + } + updateAccesability(); +} + +/** + * + * @param vol + * @param ref_sliders + * @param muted Future directions: passing "muted" should not be necessary any longer - due to getVolumeForGUI() + */ +void MDWSlider::updateInternal(Volume& vol, QList& ref_sliders, bool muted) +{ +// bool debugMe = (mixDevice()->id() == "PCM:0" ); +// if (debugMe) +// { +// kDebug() << "The updateInternal() PCM:0 playback state" << mixDevice()->isMuted() +// << ", vol=" << mixDevice()->playbackVolume().getAvgVolumePercent(Volume::MALL); +// } + + for( int i=0; iblockSignals( true ); + +// slider->setValue( useVolume ); + // --- Avoid feedback loops START ----------------- + if((volume_index = volumeValues.indexOf(useVolume)) > -1 && --m_waitForSoundSetComplete < 1) + { + m_waitForSoundSetComplete = 0; + volumeValues.removeAt(volume_index); + + if(!m_sliderInWork) + slider->setValue(useVolume); + } + else if(!m_sliderInWork && m_waitForSoundSetComplete < 1) + { + slider->setValue(useVolume); + } + // --- Avoid feedback loops END ----------------- + + if ( slider->inherits( "KSmallSlider" ) ) + { + ((KSmallSlider*)slider)->setGray( m_mixdevice->isMuted() ); + } + slider->blockSignals( oldBlockState ); + } // for all sliders + + + // update mute + + if( m_qcb != 0 ) + { + bool oldBlockState = m_qcb->blockSignals( true ); + if (m_mixdevice->isMuted()) + m_qcb->setIcon( QIcon( loadIcon("audio-volume-muted") ) ); + else + m_qcb->setIcon( QIcon( loadIcon("audio-volume-high") ) ); + m_qcb->blockSignals( oldBlockState ); + } + + if( m_captureCheckbox ) + { + bool oldBlockState = m_captureCheckbox->blockSignals( true ); + m_captureCheckbox->setChecked( m_mixdevice->isRecSource() ); + m_captureCheckbox->blockSignals( oldBlockState ); + } + +} + +#ifndef QT_NO_ACCESSIBILITY +void MDWSlider::updateAccesability() +{ + if (m_linked) { + if (!m_slidersPlayback.isEmpty()) + m_slidersPlayback[0]->setAccessibleName(m_slidersPlayback[0]->toolTip()); + if (!m_slidersCapture.isEmpty()) + m_slidersCapture[0]->setAccessibleName(m_slidersCapture[0]->toolTip()); + } else { + QList vols = m_mixdevice->playbackVolume().getVolumes().values(); + foreach (QAbstractSlider *slider, m_slidersPlayback) { + slider->setAccessibleName(slider->toolTip()+ " (" +Volume::ChannelNameReadable[vols.first().chid]+")"); + vols.pop_front(); + } + vols = m_mixdevice->captureVolume().getVolumes().values(); + foreach (QAbstractSlider *slider, m_slidersCapture) { + slider->setAccessibleName(slider->toolTip()+ " (" +Volume::ChannelNameReadable[vols.first().chid]+")"); + vols.pop_front(); + } + } +} +#endif + + +void MDWSlider::showContextMenu( const QPoint& pos ) +{ + if( m_view == 0 ) + return; + + KMenu *menu = m_view->getPopup(); + menu->addTitle( SmallIcon( "kmix" ), m_mixdevice->readableName() ); + + if (m_moveMenu) { + MixSet *ms = m_mixdevice->getMoveDestinationMixSet(); + Q_ASSERT(ms); + + m_moveMenu->setEnabled((ms->count() > 1)); + menu->addMenu( m_moveMenu ); + } + + if ( m_slidersPlayback.count()>1 || m_slidersCapture.count()>1) { + KToggleAction *stereo = (KToggleAction *)_mdwActions->action( "stereo" ); + if ( stereo ) { + stereo->setChecked( !isStereoLinked() ); + menu->addAction( stereo ); + } + } + + if ( m_mixdevice->captureVolume().hasSwitch() ) { + KToggleAction *ta = (KToggleAction *)_mdwActions->action( "recsrc" ); + if ( ta ) { + ta->setChecked( m_mixdevice->isRecSource() ); + menu->addAction( ta ); + } + } + + if ( m_mixdevice->hasMuteSwitch() ) { + KToggleAction *ta = ( KToggleAction* )_mdwActions->action( "mute" ); + if ( ta ) { + ta->setChecked( m_mixdevice->isMuted() ); + menu->addAction( ta ); + } + } + + QAction *a = _mdwActions->action( "hide" ); + if ( a ) + menu->addAction( a ); + + QAction *b = _mdwActions->action( "keys" ); + if ( b ) { +// QAction sep( _mdwPopupActions ); +// sep.setSeparator( true ); +// menu->addAction( &sep ); + menu->addAction( b ); + } + + menu->popup( pos ); +} + + +void MDWSlider::showMoveMenu() +{ + MixSet *ms = m_mixdevice->getMoveDestinationMixSet(); + Q_ASSERT(ms); + + _mdwMoveActions->clear(); + m_moveMenu->clear(); + + // Default + KAction *a = new KAction(_mdwMoveActions); + a->setText( i18n("Automatic According to Category") ); + _mdwMoveActions->addAction( QString("moveautomatic"), a); + connect(a, SIGNAL(triggered(bool)), SLOT(moveStreamAutomatic()), Qt::QueuedConnection); + m_moveMenu->addAction( a ); + + a = new KAction(_mdwMoveActions); + a->setSeparator(true); + _mdwMoveActions->addAction( QString("-"), a); + + m_moveMenu->addAction( a ); + foreach (shared_ptr md, *ms) + { + a = new MDWMoveAction(md, _mdwMoveActions); + _mdwMoveActions->addAction( QString("moveto") + md->id(), a); + connect(a, SIGNAL(moveRequest(QString)), SLOT(moveStream(QString)), Qt::QueuedConnection); + m_moveMenu->addAction( a ); + } +} + +/** + * An event filter for the various QWidgets. We watch for Mouse press Events, so + * that we can popup the context menu. + */ +bool MDWSlider::eventFilter( QObject* obj, QEvent* e ) +{ + QEvent::Type eventType = e->type(); + if (eventType == QEvent::MouseButtonPress) { + QMouseEvent *qme = static_cast(e); + if (qme->button() == Qt::RightButton) { + showContextMenu(); + return true; + } + } else if (eventType == QEvent::ContextMenu) { + QPoint pos = reinterpret_cast(obj)->mapToGlobal(QPoint(0, 0)); + showContextMenu(pos); + return true; + } + // Attention: We don't filter WheelEvents for KSmallSlider, because it handles WheelEvents itself + else if ( eventType == QEvent::Wheel ) +// && strcmp(obj->metaObject()->className(),"KSmallSlider") != 0 ) { // Remove the KSmallSlider check. If KSmallSlider comes back, use a cheaper type check - e.g. a boolean value. + { + QWheelEvent *qwe = static_cast(e); + + bool increase = (qwe->delta() > 0); + if (qwe->orientation() == Qt::Horizontal) // Reverse horizontal scroll: bko228780 + increase = !increase; + + Volume::VolumeTypeFlag volumeType = Volume::Playback; + QAbstractSlider *slider = qobject_cast(obj); + if (slider != 0) + { +// kDebug(); +// kDebug(); +// kDebug() << "----------------------------- Slider is " << slider; + // Mouse is over a slider. So lets apply the wheel event to playback or capture only + if(m_slidersCapture.contains(slider)) + { +// kDebug() << "Slider is capture " << slider; + volumeType = Volume::Capture; + } + } + else + { + // Mouse not over a slider => do a little guessing + if (!m_slidersPlayback.isEmpty()) + slider = qobject_cast(m_slidersPlayback.first()); + else if (!m_slidersCapture.isEmpty()) + slider = qobject_cast(m_slidersCapture.first()); + else + slider = 0; + } + + increaseOrDecreaseVolume(!increase, volumeType); + + if (slider != 0) + { + Volume& volP = m_mixdevice->playbackVolume(); +// kDebug() << "slider=" << slider->objectName(); + VolumeSliderExtraData& sliderExtraData = extraData(slider); +// kDebug() << "slider=" << slider->objectName() << "sliderExtraData=" << sliderExtraData.getSubcontrolLabel() << " , chid=" << sliderExtraData.getChid(); + volumeValues.push_back(volP.getVolume(sliderExtraData.getChid())); + } + return true; + } + return QWidget::eventFilter(obj,e); +} + +#include "mdwslider.moc" diff --git a/kmix/gui/mdwslider.h b/kmix/gui/mdwslider.h new file mode 100644 index 00000000..d2aa78f7 --- /dev/null +++ b/kmix/gui/mdwslider.h @@ -0,0 +1,187 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright Chrisitan Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MDWSLIDER_H +#define MDWSLIDER_H + +#include +#include "volumeslider.h" +#include +#include +#include +#include +#include + +class QBoxLayout; +class QToolButton; +class QLabel; + +class KAction; +class KMenu; +#include + +class MixDevice; +class VerticalText; +class ViewBase; + +#include "gui/mixdevicewidget.h" +#include "core/volume.h" + + +class MDWSlider : public MixDeviceWidget +{ + Q_OBJECT + +public: + MDWSlider( shared_ptr md, + bool includePlayback, bool includeCapture, + bool includeMixerName, bool small, Qt::Orientation, + QWidget* parent, ViewBase* view, ProfControl *pctl); + virtual ~MDWSlider(); + + enum LabelType { LT_ALL, LT_FIRST_CAPTURE, LT_NONE }; + void addActionToPopup( KAction *action ); + void createActions(); + void createShortcutActions(); + + // GUI + bool isStereoLinked() const { return m_linked; } + void setStereoLinked( bool value ); + void setLabeled( bool value ); + void setTicks( bool ticks ); + void setIcons( bool value ); +// void setIcon( QString filename, QLabel** label ); + void setIcon( QString filename, QWidget* label ); + QToolButton* addMediaButton(QString iconName, QLayout* layout, QWidget *parent); + void updateMediaButton(); + void setColors( QColor high, QColor low, QColor back ); + void setMutedColors( QColor high, QColor low, QColor back ); + + bool eventFilter( QObject* obj, QEvent* e ); + QString iconName(); + // Layout + QSizePolicy sizePolicy() const; + QSize sizeHint() const; + int labelExtentHint() const; + void setLabelExtent(int extent); + bool hasMuteButton() const; + void setMuteButtonSpace(bool); + void setCaptureLEDSpace(bool); + bool hasCaptureLED() const; + + static VolumeSliderExtraData DummVolumeSliderExtraData; + static bool debugMe; + + +public slots: + void toggleRecsrc(); + void toggleMuted(); + void toggleStereoLinked(); + + void setDisabled( bool value ); + void update(); + void showMoveMenu(); + virtual void showContextMenu( const QPoint &pos = QCursor::pos() ); + void increaseOrDecreaseVolume(bool arg1, Volume::VolumeTypeFlag volumeType); + VolumeSliderExtraData& extraData(QAbstractSlider *slider); + void addMediaControls(QBoxLayout* arg1); + + +signals: + void toggleMenuBar(bool value); + void guiVisibilityChange(MixDeviceWidget* source, bool enable); + +private slots: + void setRecsrc( bool value ); + void setMuted(bool value); + void volumeChange( int ); + void sliderPressed(); + void sliderReleased(); + + void increaseVolume(); + void decreaseVolume(); + + void moveStreamAutomatic(); + void moveStream( QString destId ); + + void mediaPlay(bool); + void mediaNext(bool); + void mediaPrev(bool); + +private: + KShortcut dummyShortcut; + QPixmap loadIcon( QString filename ); + void createWidgets( bool showMuteLED, bool showCaptureLED, bool includeMixer ); + void addSliders( QBoxLayout *volLayout, char type, Volume& vol, + QList& ref_sliders, QString tooltipText ); + //void addDefaultLabel(QBoxLayout *layout, Qt::Orientation orientation); + + // Methods that are called two times from a wrapper. Once for playabck, once for capture + void setStereoLinkedInternal( QList< QAbstractSlider* >& ref_sliders, bool showSubcontrolLabels); + void setTicksInternal( QList< QAbstractSlider* >& ref_sliders, bool ticks ); + void volumeChangeInternal(Volume& vol, QList< QAbstractSlider* >& ref_sliders ); + void updateInternal(Volume& vol, QList< QAbstractSlider* >& ref_sliders, bool muted); +#ifndef QT_NO_ACCESSIBILITY + void updateAccesability(); +#endif + + QWidget* createLabel(QWidget* parent, QString& label, QBoxLayout *layout, bool); + QString calculatePlaybackIcon(MediaController::PlayState playState); + void guiAddSlidersAndMediacontrols(bool playSliders, bool capSliders, bool mediaControls, QBoxLayout* layout, const QString& tooltipText, const QString& captureTooltipText); + void guiAddCaptureCheckbox(bool wantsCaptureLED, const Qt::Alignment& alignmentForCapture, + QBoxLayout* layoutForCapture, const QString& captureTooltipText); + void guiAddMuteButton(bool wantsMuteButton, Qt::Alignment alignment, QBoxLayout* layoutForMuteButton, const QString& muteTooltipText); + void guiAddControlIcon(Qt::Alignment alignment, QBoxLayout* layout, const QString& tooltipText); + void addGlobalShortcut(KAction* action, const QString& label, bool dynamicControl); + + bool m_linked; + + QWidget *muteButtonSpacer; + QWidget *captureSpacer; + QWidget *labelSpacer; + + // GUI: Top portion ( Icon + Mute) + QLabel *m_iconLabelSimple; + QToolButton* m_qcb; + QLabel* m_muteText; + + QLabel *m_label; // is either QLabel or VerticalText + QToolButton *mediaButton; + + QCheckBox* m_captureCheckbox; + QLabel* m_captureText; + + int labelSpacing; + bool muteButtonSpacing; + bool captureLEDSpacing; + + KActionCollection* _mdwMoveActions; + KMenu *m_moveMenu; + + QList m_slidersPlayback; + QList m_slidersCapture; + bool m_sliderInWork; + int m_waitForSoundSetComplete; + QList volumeValues; +}; + +#endif diff --git a/kmix/gui/mdwswitch.cpp b/kmix/gui/mdwswitch.cpp new file mode 100644 index 00000000..3a922f0b --- /dev/null +++ b/kmix/gui/mdwswitch.cpp @@ -0,0 +1,232 @@ +///* +// * KMix -- KDE's full featured mini mixer +// * +// * +// * Copyright (C) 2004 Christian Esken +// * +// * This program is free software; you can redistribute it and/or +// * modify it under the terms of the GNU Library General Public +// * License as published by the Free Software Foundation; either +// * version 2 of the License, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * Library General Public License for more details. +// * +// * You should have received a copy of the GNU Library General Public +// * License along with this program; if not, write to the Free +// * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// */ +// +//#include +//#include +//#include +//#include +// +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +// +//#include "mdwswitch.h" +//#include "core/mixer.h" +//#include "viewbase.h" +//#include "verticaltext.h" +// +///** +// * Class that represents a single Switch +// * The orientation (horizontal, vertical) can be configured +// */ +//MDWSwitch::MDWSwitch(MixDevice* md, +// bool small, Qt::Orientation orientation, +// QWidget* parent, ViewBase* mw) : +// MixDeviceWidget(md,small,orientation,parent,mw), +// _label(0) , _labelV(0) , _switchLED(0), _layout(0) +//{ +// // create actions (on _mdwActions, see MixDeviceWidget) +// +// // KStandardAction::showMenubar() is in MixDeviceWidget now +// KToggleAction *action = _mdwActions->add( "hide" ); +// action->setText( i18n("&Hide") ); +// connect(action, SIGNAL(triggered(bool)), SLOT(setDisabled())); +// KAction *b = _mdwActions->addAction( "keys" ); +// b->setText( i18n("C&onfigure Shortcuts...") ); +// connect(b, SIGNAL(triggered(bool)), SLOT(defineKeys())); +// +// // create widgets +// createWidgets(); +// +// KAction *a = _mdwActions->addAction( "Toggle switch" ); +// a->setText( i18n( "Toggle Switch" ) ); +// connect(a, SIGNAL(triggered(bool)), SLOT(toggleSwitch())); +// +// // The accel keys are loaded in KMixerWidget::loadConfig, see kmixtoolbox.cpp +// +// installEventFilter( this ); // filter for popup +//} +// +//MDWSwitch::~MDWSwitch() +//{ +//} +// +// +//void MDWSwitch::createWidgets() +//{ +// if ( _orientation == Qt::Vertical ) { +// _layout = new QVBoxLayout( this ); +// _layout->setAlignment(Qt::AlignHCenter); +// } +// else { +// _layout = new QHBoxLayout( this ); +// _layout->setAlignment(Qt::AlignVCenter); +// } +// this->setToolTip( m_mixdevice->readableName() ); +// +// +// _layout->addSpacing( 4 ); +// // --- LEDS -------------------------- +// if ( _orientation == Qt::Vertical ) { +// if( m_mixdevice->captureVolume().hasSwitch() ) +// _switchLED = new QCheckBox( Qt::red, +// m_mixdevice->isRecSource()?KLed::On:KLed::Off, +// KLed::Sunken, KLed::Circular, this, "RecordLED" ); +// else +// _switchLED = new QCheckBox( Qt::yellow, KLed::On, KLed::Sunken, KLed::Circular, this, "SwitchLED" ); +// _switchLED->setFixedSize(16,16); +// _labelV = new VerticalText( this, m_mixdevice->readableName().toUtf8().data() ); +// +// _layout->addWidget( _switchLED ); +// _layout->addSpacing( 2 ); +// _layout->addWidget( _labelV ); +// +// _switchLED->installEventFilter( this ); +// _labelV->installEventFilter( this ); +// } +// else +// { +// if( m_mixdevice->captureVolume().hasSwitch() ) +// _switchLED = new QCheckBox( Qt::red, +// m_mixdevice->isRecSource()?KLed::On:KLed::Off, +// KLed::Sunken, KLed::Circular, this, "RecordLED" ); +// else +// _switchLED = new QCheckBox( Qt::yellow, KLed::On, KLed::Sunken, KLed::Circular, this, "SwitchLED" ); +// _switchLED->setFixedSize(16,16); +// _label = new QLabel(m_mixdevice->readableName(), this ); +// _label->setObjectName( QLatin1String("SwitchName" )); +// +// _layout->addWidget( _switchLED ); +// _layout->addSpacing( 1 ); +// _layout->addWidget( _label ); +// _switchLED->installEventFilter( this ); +// _label->installEventFilter( this ); +// } +// connect( _switchLED, SIGNAL(stateChanged(bool)), this, SLOT(toggleSwitch()) ); +// _layout->addSpacing( 4 ); +//} +// +//void MDWSwitch::update() +//{ +// if ( _switchLED != 0 ) { +// _switchLED->blockSignals( true ); +// if( m_mixdevice->captureVolume().hasSwitch() ) +// _switchLED->setState( m_mixdevice->isRecSource() ? KLed::On : KLed::Off ); +// else +// _switchLED->setState( m_mixdevice->isMuted() ? KLed::Off : KLed::On ); +// +// _switchLED->blockSignals( false ); +// } +//} +// +//void MDWSwitch::setBackgroundRole(QPalette::ColorRole m) +//{ +// if ( _label != 0 ){ +// _label->setBackgroundRole(m); +// } +// if ( _labelV != 0 ){ +// _labelV->setBackgroundRole(m); +// } +// _switchLED->setBackgroundRole(m); +// MixDeviceWidget::setBackgroundRole(m); +//} +// +//void MDWSwitch::showContextMenu() +//{ +// if( m_view == 0 ) +// return; +// +// KMenu *menu = m_view->getPopup(); +// +// QPoint pos = QCursor::pos(); +// menu->popup( pos ); +//} +// +// +//QSizePolicy MDWSwitch::sizePolicy() const +//{ +// if ( _orientation == Qt::Vertical ) { +// return QSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding ); +// } +// else { +// return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); +// } +//} +// +///** +// This slot is called, when a user has clicked the mute button. Also it is called by any other +// associated KAction like the context menu. +//*/ +//void MDWSwitch::toggleSwitch() { +// if( m_mixdevice->captureVolume().hasSwitch() ) +// setSwitch( !m_mixdevice->isRecSource() ); +// else +// setSwitch( !m_mixdevice->isMuted() ); +//} +// +//void MDWSwitch::setSwitch(bool value) +//{ +// if ( m_mixdevice->playbackVolume().hasSwitch() ) { +// if ( m_mixdevice->captureVolume().hasSwitch() ) { +// m_mixdevice->mixer()->setRecordSource( m_mixdevice->id(), value ); +// } +// else { +// m_mixdevice->setMuted( value ); +// m_mixdevice->mixer()->commitVolumeChange( m_mixdevice ); +// } +// } +//} +// +//void MDWSwitch::setDisabled() +//{ +// setDisabled( true ); +//} +// +//void MDWSwitch::setDisabled( bool value ) { +// if ( m_disabled!=value) +// { +// value ? hide() : show(); +// m_disabled = value; +// } +//} +// +///** +// * An event filter for the various QWidgets. We watch for Mouse press Events, so +// * that we can popup the context menu. +// */ +//bool MDWSwitch::eventFilter( QObject* obj, QEvent* e ) +//{ +// if (e->type() == QEvent::MouseButtonPress) { +// QMouseEvent *qme = static_cast(e); +// if (qme->button() == Qt::RightButton) { +// showContextMenu(); +// return true; +// } +// } +// return QWidget::eventFilter(obj,e); +//} +// +//#include "mdwswitch.moc" diff --git a/kmix/gui/mdwswitch.h b/kmix/gui/mdwswitch.h new file mode 100644 index 00000000..6d43c2e7 --- /dev/null +++ b/kmix/gui/mdwswitch.h @@ -0,0 +1,79 @@ +////-*-C++-*- +///* +// * KMix -- KDE's full featured mini mixer +// * +// * +// * Copyright Chrisitan Esken +// * +// * This program is free software; you can redistribute it and/or +// * modify it under the terms of the GNU Library General Public +// * License as published by the Free Software Foundation; either +// * version 2 of the License, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * Library General Public License for more details. +// * +// * You should have received a copy of the GNU Library General Public +// * License along with this program; if not, write to the Free +// * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// */ +// +//#ifndef MDWSWITCH_H +//#define MDWSWITCH_H +// +//#include +//#include "core/volume.h" +//#include +// +//class QBoxLayout; +//class QLabel; +// +//class QCheckBox; +//class KAction; +// +//class MixDevice; +//class VerticalText; +//class Mixer; +//class ViewBase; +// +//#include "gui/mixdevicewidget.h" +// +//class MDWSwitch : public MixDeviceWidget +//{ +// Q_OBJECT +// +//public: +// MDWSwitch( MixDevice* md, +// bool small, Qt::Orientation orientation, +// QWidget* parent = 0, ViewBase* mw = 0); +// ~MDWSwitch(); +// +// void addActionToPopup( KAction *action ); +// QSizePolicy sizePolicy() const; +// void setBackgroundRole(QPalette::ColorRole m); +// bool eventFilter( QObject* obj, QEvent* e ); +// +//public slots: +// // GUI hide and show +// void setDisabled(); +// void setDisabled(bool); +// +// // Switch on/off +// void toggleSwitch(); +// void setSwitch(bool value); +// +// void update(); +// virtual void showContextMenu(); +// +//private: +// void createWidgets(); +// +// QLabel *_label; +// VerticalText *_labelV; +// QCheckBox *_switchLED; +// QBoxLayout *_layout; +//}; +// +//#endif diff --git a/kmix/gui/mixdevicewidget.cpp b/kmix/gui/mixdevicewidget.cpp new file mode 100644 index 00000000..ac7e626c --- /dev/null +++ b/kmix/gui/mixdevicewidget.cpp @@ -0,0 +1,118 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "gui/mixdevicewidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "core/mixer.h" +#include "core/mixertoolbox.h" +#include "viewbase.h" +#include "ksmallslider.h" +#include "verticaltext.h" + +/** + * Base Class for any Widget that represents a MixDevice. + * The mix device can be a real (hardware bound) MixDevice or a virtual mix device. + * + * The direction (horizontal, vertical) can be configured and whether it should + * be "small" (uses KSmallSlider instead of a normal slider widget). The actual implementations + * SHOULD honor these values - those who do not might not be suitable for placing in + * the panel applet or any other smallish settings. + */ +MixDeviceWidget::MixDeviceWidget(shared_ptr md, + bool small, Qt::Orientation orientation, + QWidget* parent, ViewBase* view, ProfControl* par_pctl) : + QWidget( parent ), m_mixdevice( md ), m_view( view ), _pctl(par_pctl), + _orientation( orientation ), m_small( small ) + , m_shortcutsDialog(0) +{ + _mdwActions = new KActionCollection( this ); + _mdwPopupActions = new KActionCollection( this ); + + QString name (md->id()); + /* char* whatsThisChar = whatsthis.toUtf8().data(); + QString w; + w = ki18n(whatsThisChar).toString(MixerToolBox::whatsthisControlLocale() ); + this->setWhatsThis(w); + */ + QString whatsthisText = mixDevice()->mixer()->translateKernelToWhatsthis(name); + if ( whatsthisText != "---") { + setWhatsThis(whatsthisText); + } +} + +MixDeviceWidget::~MixDeviceWidget() +{ +} + +void MixDeviceWidget::addActionToPopup( KAction *action ) +{ + _mdwActions->addAction( action->objectName(), action ); +} + +void MixDeviceWidget::defineKeys() +{ + // Dialog for *global* shortcuts of this MDW + if ( m_shortcutsDialog == 0 ) { + m_shortcutsDialog = new KShortcutsDialog( KShortcutsEditor::GlobalAction ); + m_shortcutsDialog->addCollection(_mdwPopupActions); + } + m_shortcutsDialog->configure(); +} + +void MixDeviceWidget::volumeChange( int ) { /* is virtual */ } +//void MixDeviceWidget::setDisabled( bool ) { /* is virtual */ } +//void MixDeviceWidget::setVolume( int /*channel*/, int /*vol*/ ) { /* is virtual */ } +//void MixDeviceWidget::setVolume( Volume /*vol*/ ) { /* is virtual */ } +//void MixDeviceWidget::update() { /* is virtual */ } +//void MixDeviceWidget::showContextMenu( const QPoint &pos ) { /* is virtual */ } +void MixDeviceWidget::setColors( QColor , QColor , QColor ) { /* is virtual */ } +void MixDeviceWidget::setIcons( bool ) { /* is virtual */ } +void MixDeviceWidget::setLabeled( bool ) { /* is virtual */ } +void MixDeviceWidget::setMutedColors( QColor , QColor , QColor ) { /* is virtual */ } + + + +void MixDeviceWidget::mousePressEvent( QMouseEvent *e ) +{ + if ( e->button() == Qt::RightButton ) + showContextMenu(); + else { + QWidget::mousePressEvent(e); + } +} + + +#include "mixdevicewidget.moc" diff --git a/kmix/gui/mixdevicewidget.h b/kmix/gui/mixdevicewidget.h new file mode 100644 index 00000000..68033b2e --- /dev/null +++ b/kmix/gui/mixdevicewidget.h @@ -0,0 +1,97 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * 1996-2000 Christian Esken + * Sven Fischer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MIXDEVICEWIDGET_H +#define MIXDEVICEWIDGET_H + +#include +#include "core/mixdevice.h" +#include "core/volume.h" +#include + + +class KAction; +class KActionCollection; +class KShortcutsDialog; + +class MixDevice; +class ProfControl; +class ViewBase; + +class MixDeviceWidget + : public QWidget +{ + Q_OBJECT + +public: + MixDeviceWidget( shared_ptr md, + bool small, Qt::Orientation orientation, + QWidget* parent, ViewBase*, ProfControl * ); + virtual ~MixDeviceWidget(); + + void addActionToPopup( KAction *action ); + + shared_ptr mixDevice() { return m_mixdevice; } + + virtual void setColors( QColor high, QColor low, QColor back ); + virtual void setIcons( bool value ); + virtual void setMutedColors( QColor high, QColor low, QColor back ); + + virtual bool isStereoLinked() const { return false; } + virtual void setStereoLinked( bool ) {} + virtual void setLabeled( bool ); + virtual void setTicks( bool ) {} + + +public slots: + virtual void defineKeys(); + virtual void showContextMenu( const QPoint &pos = QCursor::pos() ) = 0; + /** + * update() is called whenever there are volume updates pending from the hardware for this MDW. + */ + virtual void update() = 0; + +signals: + virtual void guiVisibilityChange(MixDeviceWidget* source, bool enable) = 0; + +protected slots: + virtual void setDisabled( bool value ) = 0; + void volumeChange( int ); + +protected: + + shared_ptr m_mixdevice; + KActionCollection* _mdwActions; + KActionCollection* _mdwPopupActions; + ViewBase* m_view; + ProfControl* _pctl; + Qt::Orientation _orientation; + bool m_small; + KShortcutsDialog* m_shortcutsDialog; + +private: + void mousePressEvent( QMouseEvent *e ); +}; + +#endif diff --git a/kmix/gui/osdwidget.cpp b/kmix/gui/osdwidget.cpp new file mode 100644 index 00000000..cd47f530 --- /dev/null +++ b/kmix/gui/osdwidget.cpp @@ -0,0 +1,220 @@ +/******************************************************************* +* osdwidget.cpp +* Copyright 2009 Aurélien Gâteau +* Copyright 2009 Dario Andres Rodriguez +* Copyright 2009 Christian Esken +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +******************************************************************/ + +#include "gui/osdwidget.h" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/ControlManager.h" +#include + +OSDWidget::OSDWidget(QWidget * parent) + : Plasma::Dialog(parent, Qt::ToolTip), + m_scene(new QGraphicsScene(this)), + m_container(new QGraphicsWidget), + m_iconLabel(new Plasma::Label), + m_volumeLabel(new Plasma::Label), + m_meter(new Plasma::Meter), + m_hideTimer(new QTimer(this)) +{ + //Setup the window properties + KWindowSystem::setState(winId(), NET::KeepAbove); + KWindowSystem::setType(winId(), NET::Tooltip); + setAttribute(Qt::WA_X11NetWmWindowTypeToolTip, true); + + m_meter->setMeterType(Plasma::Meter::BarMeterHorizontal); + m_meter->setMaximum(100); + + //Set a fixed width for the volume label. To do that we need the text with the maximum width + //(this is true if the volume is at 100%). We simply achieve that by calling "setCurrentVolume". + setCurrentVolume(100, false); + + /* We are registering for volume changes of all cards. An alternative + * would be to register to volume changes of the global master and additionally + * register to MasterChanges. That could be slightly more efficient + */ + ControlManager::instance().addListener( + QString(), // all mixers + ControlChangeType::Volume, + this, + QString("OSDWidget") + ); + + //Setup the auto-hide timer + m_hideTimer->setInterval(2000); + m_hideTimer->setSingleShot(true); + connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hide())); + + //Setup the OSD layout + QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(m_container); + layout->setContentsMargins(0, 0, 0, 0); + layout->addItem(m_iconLabel); + layout->addItem(m_meter); + layout->addItem(m_volumeLabel); + + m_scene->addItem(m_container); + setGraphicsWidget(m_container); + + themeUpdated(); + connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeUpdated())); // e.g. for updating font +} + + +OSDWidget::~OSDWidget() +{ + ControlManager::instance().removeListener(this); +} +void OSDWidget::controlsChange(int changeType) +{ + ControlChangeType::Type type = ControlChangeType::fromInt(changeType); + shared_ptr master = Mixer::getGlobalMasterMD(); + switch (type ) + { + case ControlChangeType::Volume: + if ( master ) + { + setCurrentVolume(master->playbackVolume().getAvgVolumePercent(Volume::MALL), master->isMuted()); + } + break; + + default: + ControlManager::warnUnexpectedChangeType(type, this); + break; + } + +} +void OSDWidget::activateOSD() +{ + m_hideTimer->start(); +} + +void OSDWidget::themeUpdated() +{ + //Set a font which makes the text appear as big (height-wise) as the meter. + //QFont font = QFont(m_volumeLabel->nativeWidget()->font()); + Plasma::Theme* theme = Plasma::Theme::defaultTheme(); + + + QPalette palette = m_volumeLabel->palette(); + palette.setColor(QPalette::WindowText, theme->color(Plasma::Theme::TextColor)); + m_volumeLabel->setPalette(palette); + + QFont font = theme->font(Plasma::Theme::DefaultFont); + font.setPointSize(15); + m_volumeLabel->setFont(font); + QFontMetrics qfm(font); + QRect textSize = qfm.boundingRect("100 % "); + + int widthHint = textSize.width(); + int heightHint = textSize.height(); + //setCurrentVolume(100,false); + m_volumeLabel->setMinimumWidth(widthHint); + m_volumeLabel->setMaximumHeight(heightHint); + m_volumeLabel->nativeWidget()->setFixedWidth(widthHint); +// m_volumeLabel->setText(oldText); + + //Cache the icon pixmaps + QSize iconSize; + + if (!Plasma::Theme::defaultTheme()->imagePath("icons/audio").isEmpty()) { + QFontMetrics fm(m_volumeLabel->font()); + iconSize = QSize(fm.height(), fm.height()); + Plasma::Svg svgIcon; + svgIcon.setImagePath("icons/audio"); + svgIcon.setContainsMultipleImages(true); + svgIcon.resize(iconSize); + m_volumeHighPixmap = svgIcon.pixmap("audio-volume-high"); + m_volumeMediumPixmap = svgIcon.pixmap("audio-volume-medium"); + m_volumeLowPixmap = svgIcon.pixmap("audio-volume-low"); + m_volumeMutedPixmap = svgIcon.pixmap("audio-volume-muted"); + } else { + iconSize = QSize(KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium); + m_volumeHighPixmap = KIcon( QLatin1String( "audio-volume-high" )).pixmap(iconSize); + m_volumeMediumPixmap = KIcon( QLatin1String( "audio-volume-medium" )).pixmap(iconSize); + m_volumeLowPixmap = KIcon( QLatin1String( "audio-volume-low" )).pixmap(iconSize); + m_volumeMutedPixmap = KIcon( QLatin1String( "audio-volume-muted" )).pixmap(iconSize); + } + + m_iconLabel->nativeWidget()->setPixmap(m_volumeHighPixmap); + m_iconLabel->nativeWidget()->setFixedSize(iconSize); + m_iconLabel->setMinimumSize(iconSize); + m_iconLabel->setMaximumSize(iconSize); + + m_meter->setMaximumHeight(iconSize.height()); + + m_volumeLabel->setMinimumHeight(iconSize.height()); + m_volumeLabel->setMaximumHeight(iconSize.height()); + m_volumeLabel->nativeWidget()->setFixedHeight(iconSize.height()); + + m_volumeLabel->setAlignment(Qt::AlignCenter); + m_volumeLabel->setWordWrap(false); + + m_container->setMinimumSize(iconSize.width() * 13 + m_volumeLabel->nativeWidget()->width(), iconSize.height()); + m_container->setMaximumSize(iconSize.width() * 13 + m_volumeLabel->nativeWidget()->width(), iconSize.height()); + + syncToGraphicsWidget(); +} + + +/** + * Set volume level in percent + */ +void OSDWidget::setCurrentVolume(int volumeLevel, bool muted) +{ +// kDebug() << "Meter is visible: " << m_meter->isVisible(); + + if ( muted ) + { + volumeLevel = 0; + } + m_meter->setValue(volumeLevel); + + if (!muted && (volumeLevel > 0)) { + if (volumeLevel < 25) { + m_iconLabel->nativeWidget()->setPixmap(m_volumeLowPixmap); + } else if (volumeLevel < 75) { + m_iconLabel->nativeWidget()->setPixmap(m_volumeMediumPixmap); + } else { + m_iconLabel->nativeWidget()->setPixmap(m_volumeHighPixmap); + } + } else { + m_iconLabel->nativeWidget()->setPixmap(m_volumeMutedPixmap); + } + + //Show the volume % + m_volumeLabel->setText(QString::number(volumeLevel) + " %"); // if you change the text, please adjust textSize in themeUpdated() +} + +#include "osdwidget.moc" diff --git a/kmix/gui/osdwidget.h b/kmix/gui/osdwidget.h new file mode 100644 index 00000000..3e80cd62 --- /dev/null +++ b/kmix/gui/osdwidget.h @@ -0,0 +1,68 @@ +/******************************************************************* +* osdwidget.h +* Copyright 2009 Aurélien Gâteau +* Copyright 2009 Dario Andres Rodriguez +* Copyright 2009 Christian Esken +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +******************************************************************/ + +#ifndef OSDWIDGET__H +#define OSDWIDGET__H + +#include + +#include + +class QTimer; +class QGraphicsWidget; + +namespace Plasma +{ +class Label; +class Meter; +} + +class OSDWidget : public Plasma::Dialog +{ +Q_OBJECT +public: + OSDWidget(QWidget * parent = 0); + virtual ~OSDWidget(); + + void setCurrentVolume(int volumeLevel, bool muted); + void activateOSD(); + + public slots: + void controlsChange(int changeType); + +private: + QGraphicsScene *m_scene; + QGraphicsWidget *m_container; + Plasma::Label *m_iconLabel; + Plasma::Label *m_volumeLabel; + Plasma::Meter *m_meter; + QTimer *m_hideTimer; + + QPixmap m_volumeHighPixmap; + QPixmap m_volumeMediumPixmap; + QPixmap m_volumeLowPixmap; + QPixmap m_volumeMutedPixmap; + +private slots: + void themeUpdated(); +}; + +#endif diff --git a/kmix/gui/verticaltext.cpp b/kmix/gui/verticaltext.cpp new file mode 100644 index 00000000..54913a9d --- /dev/null +++ b/kmix/gui/verticaltext.cpp @@ -0,0 +1,76 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2003-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "verticaltext.h" +#include +#include + + + +VerticalText::VerticalText(QWidget * parent, const QString& text, Qt::WFlags f) : QWidget(parent,f) +{ + m_labelText = text; +} + +VerticalText::~VerticalText() { +} + +void VerticalText::setText(const QString& text) { + if (m_labelText != text) { + m_labelText = text; + updateGeometry(); + } +} + + +void VerticalText::paintEvent ( QPaintEvent * /*event*/ ) { + QPainter paint(this); + paint.rotate(270); +// paint.translate(0,-4); // Silly "solution" to make underlengths work + + // Fix for bug 72520 + //- paint.drawText(-height()+2,width(),name()); + //+ paint.drawText( -height()+2, width(), QString::fromUtf8(name()) ); + int posX = -height(); + int posY = width(); + paint.drawText( posX, posY, m_labelText ); +} + +QSize VerticalText::sizeHint() const { + const QFontMetrics& fontMetr = fontMetrics(); + QSize textSize(fontMetr.width(m_labelText), fontMetr.height()); + textSize.transpose(); + return textSize; +} + +QSize VerticalText::minimumSizeHint() const +{ + const QFontMetrics& fontMetr = fontMetrics(); + QSize textSize(fontMetr.width("MMMM"), fontMetr.height()); + textSize.transpose(); + return textSize; +} + +QSizePolicy VerticalText::sizePolicy () const +{ + return QSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding); +} + diff --git a/kmix/gui/verticaltext.h b/kmix/gui/verticaltext.h new file mode 100644 index 00000000..179ae2be --- /dev/null +++ b/kmix/gui/verticaltext.h @@ -0,0 +1,44 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef VerticalText_h +#define VerticalText_h + +#include +#include + +class VerticalText : public QWidget +{ + public: + VerticalText(QWidget * parent, const QString&, Qt::WFlags f = 0); + ~VerticalText(); + + void setText(const QString& text); + QSize sizeHint() const; + QSizePolicy sizePolicy () const; + QSize minimumSizeHint() const; + protected: + void paintEvent ( QPaintEvent * event ); + + private: + QString m_labelText; +}; + +#endif diff --git a/kmix/gui/viewbase.cpp b/kmix/gui/viewbase.cpp new file mode 100644 index 00000000..905d8ff3 --- /dev/null +++ b/kmix/gui/viewbase.cpp @@ -0,0 +1,493 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "viewbase.h" + +// QT +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include +#include +#include +// KMix +#include "dialogviewconfiguration.h" +#include "gui/guiprofile.h" +#include "gui/kmixtoolbox.h" +#include "gui/mixdevicewidget.h" +#include "gui/mdwslider.h" +#include "core/ControlManager.h" +#include "core/GlobalConfig.h" +#include "core/mixer.h" +#include "core/mixertoolbox.h" + + +/** + * Creates an empty View. To populate it with MixDevice instances, you must implement + * _setMixSet() in your derived class. + */ +ViewBase::ViewBase(QWidget* parent, QString id, Qt::WFlags f, ViewBase::ViewFlags vflags, QString guiProfileId, KActionCollection *actionColletion) + : QWidget(parent, f), _popMenu(NULL), _actions(actionColletion), _vflags(vflags), _guiProfileId(guiProfileId) +{ + setObjectName(id); + m_viewId = id; + guiComplexity = ViewBase::SIMPLE; + configureIcon = new KIcon( QLatin1String( "configure" )); + + + if ( _actions == 0 ) { + // We create our own action collection, if the actionColletion was 0. + // This is currently done for the ViewDockAreaPopup, but only because it has not been converted to use the app-wide + // actionCollection(). This is a @todo. + _actions = new KActionCollection( this ); + } + _localActionColletion = new KActionCollection( this ); + + // Plug in the "showMenubar" action, if the caller wants it. Typically this is only necessary for views in the KMix main window. + if ( vflags & ViewBase::HasMenuBar ) + { + KToggleAction *m = static_cast( _actions->action( name(KStandardAction::ShowMenubar) ) ) ; + if ( m != 0 ) { + bool visible = ( vflags & ViewBase::MenuBarVisible ); + m->setChecked(visible); + } + } +} + +ViewBase::~ViewBase() +{ + delete configureIcon; + // Hint: The GUI profile will not be removed, as it is pooled and might be applied to a new View. +} + + +void ViewBase::addMixer(Mixer *mixer) +{ + _mixers.append(mixer); +} + +//void ViewBase::configurationUpdate() { +//} + + + +QPushButton* ViewBase::createConfigureViewButton() +{ + QPushButton* configureViewButton = new QPushButton(*configureIcon, "", this); + configureViewButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + configureViewButton->setToolTip(i18n( "Configure Channels" )); + connect(configureViewButton, SIGNAL(clicked(bool)), SLOT(configureView())); + return configureViewButton; +} + +void ViewBase::updateGuiOptions() +{ + setTicks(GlobalConfig::instance().data.showTicks); + setLabels(GlobalConfig::instance().data.showLabels); + updateMediaPlaybackIcons(); +} + +QString ViewBase::id() const { + return m_viewId; +} + +bool ViewBase::isValid() const +{ + return ( !_mixSet.isEmpty() || isDynamic() ); +} + +void ViewBase::setIcons (bool on) { KMixToolBox::setIcons (_mdws, on ); } +void ViewBase::setLabels(bool on) { KMixToolBox::setLabels(_mdws, on ); } +void ViewBase::setTicks (bool on) { KMixToolBox::setTicks (_mdws, on ); } + +/** + * Updates all playback icons to their (new) state, e.g. Paused, or Playing + */ +void ViewBase::updateMediaPlaybackIcons() +{ + for (int i = 0; i < _mdws.count(); ++i) + { + // Currently media controls are always attached to sliders => use MDWSlider + MDWSlider* mdw = qobject_cast(_mdws[i]); + if (mdw != 0) + { + mdw->updateMediaButton(); + } + } +} + +/** + * Create all widgets. + * This is a loop over all supported devices of the corresponding view. + * On each device add() is called - the derived class must implement add() for creating and placing + * the real MixDeviceWidget. + * The added MixDeviceWidget is appended to the _mdws list. + */ +void ViewBase::createDeviceWidgets() +{ + _setMixSet(); + foreach ( shared_ptr md, _mixSet ) + { + QWidget* mdw = add(md); // a) Let the View implementation do its work + _mdws.append(mdw); // b) Add it to the local list + connect(mdw, SIGNAL(guiVisibilityChange(MixDeviceWidget*, bool)), SLOT(guiVisibilitySlot(MixDeviceWidget*, bool))); + } + + if ( !isDynamic() ) + { + QAction *action = _localActionColletion->addAction("toggle_channels"); + action->setText(i18n("&Channels")); + connect(action, SIGNAL(triggered(bool)), SLOT(configureView())); + } + + // allow view to "polish" itself + constructionFinished(); +} + +/** + * Called when a specific control is to be shown or hidden. At the moment it is only called via + * the "hide" action in the MDW context menu. + * + * @param mdw + * @param enable + */ +void ViewBase::guiVisibilitySlot(MixDeviceWidget* mdw, bool enable) +{ + MixDevice* md = mdw->mixDevice().get(); + kDebug() << "Change " << md->id() << " to visible=" << enable; + ProfControl* pctl = findMdw(md->id()); + if (pctl == 0) + { + kWarning() << "MixDevice not found, and cannot be hidden, id=" << md->id(); + return; // Ignore + } + + pctl->setVisible(enable); + ControlManager::instance().announce(md->mixer()->id(), ControlChangeType::ControlList, QString("ViewBase::guiVisibilitySlot")); +} + +// ---------- Popup stuff START --------------------- +void ViewBase::mousePressEvent( QMouseEvent *e ) +{ + if ( e->button() == Qt::RightButton ) + showContextMenu(); +} + +/** + * Return a popup menu. This contains basic entries. + * More can be added by the caller. + */ +KMenu* ViewBase::getPopup() +{ + popupReset(); + return _popMenu; +} + +void ViewBase::popupReset() +{ + QAction *act; + + delete _popMenu; + _popMenu = new KMenu( this ); + _popMenu->addTitle( KIcon( QLatin1String( "kmix" ) ), i18n("Device Settings" )); + + act = _localActionColletion->action( "toggle_channels" ); + if ( act ) _popMenu->addAction(act); + + act = _actions->action( "options_show_menubar" ); + if ( act ) _popMenu->addAction(act); +} + + +/** + This will only get executed, when the user has removed all items from the view. + Don't remove this method, because then the user cannot get a menu for getting his + channels back +*/ +void ViewBase::showContextMenu() +{ + //kDebug(67100) << "ViewBase::showContextMenu()"; + popupReset(); + + QPoint pos = QCursor::pos(); + _popMenu->popup( pos ); +} + +void ViewBase::refreshVolumeLevels() +{ + // is virtual +} +/** + * Check all Mixer instances of this view. + * If at least on is dynamic, return true. + * Please note that usually there is only one Mixer instance per View. + * The only exception as of today (June 2011) is the Tray Popup, which + * can contain controls from e.g. ALSA and MPRIS2 backends. + */ +bool ViewBase::isDynamic() const +{ + foreach (Mixer* mixer , _mixers ) + { + if ( mixer->isDynamic() ) + return true; + } + return false; +} + +bool ViewBase::pulseaudioPresent() const +{ + foreach (Mixer* mixer , _mixers ) + { + if ( mixer->getDriverName() == "PulseAudio" ) + return true; + } + return false; +} + + +void ViewBase::resetMdws() +{ + // We need to delete the current MixDeviceWidgets so we can redraw them + while (!_mdws.isEmpty()) + delete _mdws.takeFirst(); + + // _mixSet contains shared_ptr instances, so clear() should be enough to prevent mem leak + _mixSet.clear(); // Clean up our _mixSet so we can reapply our GUIProfile +} + + +int ViewBase::visibleControls() +{ + int visibleCount = 0; + foreach (QWidget* qw, _mdws) + { + if (qw->isVisible()) + ++ visibleCount; + } + return visibleCount; +} + +/** + * Open the View configuration dialog. The user can select which channels he wants + * to see and which not. + */ +void ViewBase::configureView() +{ + Q_ASSERT( !isDynamic() ); + Q_ASSERT( !pulseaudioPresent() ); + + DialogViewConfiguration* dvc = new DialogViewConfiguration(0, *this); + dvc->show(); +} + +void ViewBase::toggleMenuBarSlot() { + //kDebug(67100) << "ViewBase::toggleMenuBarSlot() start\n"; + emit toggleMenuBar(); + //kDebug(67100) << "ViewBase::toggleMenuBarSlot() done\n"; +} + + + +/** + * Loads the configuration of this view. + *

+ * Future directions: The view should probably know its config in advance, so we can use it in #load() and #save() + * + * + * @param config The view for this config + */ +void ViewBase::load(KConfig *config) +{ + ViewBase *view = this; + QString grp = "View."; + grp += view->id(); + //KConfigGroup cg = config->group( grp ); + kDebug(67100) + << "KMixToolBox::loadView() grp=" << grp.toAscii(); + + static QString guiComplexityNames[3] = + { GUIProfile::PNameSimple, GUIProfile::PNameExtended, GUIProfile::PNameAll }; + + // Certain bits are not saved for dynamic mixers (e.g. PulseAudio) + bool dynamic = isDynamic(); + + for (GUIComplexity guiCompl = ViewBase::SIMPLE; guiCompl <= ViewBase::ALL; ++guiCompl) + { + bool atLeastOneControlIsShown = false; + foreach(QWidget *qmdw, view->_mdws) +// for (int i = 0; i < view->_mdws.count(); ++i) + { +// QWidget *qmdw = view->_mdws[i]; + if (qmdw->inherits("MixDeviceWidget")) + { + MixDeviceWidget* mdw = (MixDeviceWidget*) qmdw; + shared_ptr md = mdw->mixDevice(); + QString devgrp = md->configGroupName(grp); + KConfigGroup devcg = config->group(devgrp); + + if (mdw->inherits("MDWSlider")) + { + // only sliders have the ability to split apart in mutliple channels + bool splitChannels = devcg.readEntry("Split", !mdw->isStereoLinked()); + mdw->setStereoLinked(!splitChannels); + } + + // Future directions: "Visibility" is very dirty: It is read from either config file or + // GUIProfile. Thus we have a lot of doubled mdw visibility code all throughout KMix. + bool mdwEnabled = false; + if (!dynamic && devcg.hasKey("Show")) + { + mdwEnabled = (true == devcg.readEntry("Show", true)); + } + else + { + // If not configured in config file, use the default from the profile + if (findMdw(mdw->mixDevice()->id(), guiComplexityNames[guiCompl]) != 0) + { + mdwEnabled = true; + } + } + if (mdwEnabled) + { + atLeastOneControlIsShown = true; + } + mdw->setVisible(mdwEnabled); + } // inherits MixDeviceWidget + } // for all MDW's + if (atLeastOneControlIsShown) + { + this->guiComplexity = guiCompl; + break; // If there were controls in this complexity level, don't try more + } + } // for try = 0 ... 1 +} + +/** + * Checks whether the given mixDevice shall be shown according to the requested + * GUI complexity. All ProfControl objects are inspected. The first found is returned. + * + * @param mdwId The control ID + * @param requestedGuiComplexityName The GUI name + * @return The corresponding ProfControl* + * + */ +ProfControl* ViewBase::findMdw(const QString& mdwId, QString requestedGuiComplexityName) +{ + foreach ( ProfControl* pControl, guiProfile()->getControls() ) + { + QRegExp idRegExp(pControl->id); + //kDebug(67100) << "KMixToolBox::loadView() try match " << (*pControl).id << " for " << mdw->mixDevice()->id(); + if ( mdwId.contains(idRegExp) && + pControl->show == requestedGuiComplexityName ) + { + return pControl; + } + } // iterate over all ProfControl entries + + return 0;// not found +} + + +/** + * Returns the ProfControl* to the given id. If none is found, 0 is returned. + * GUI complexity. All ProfControl objects are inspected. The first found is returned. + * + * @param id The control ID + * @return The corresponding ProfControl* + */ +ProfControl* ViewBase::findMdw(const QString& id) +{ + foreach ( ProfControl* pControl, guiProfile()->getControls() ) + { + QRegExp idRegExp(pControl->id); + //kDebug(67100) << "KMixToolBox::loadView() try match " << (*pControl).id << " for " << mdw->mixDevice()->id(); + if ( id.contains(idRegExp) ) + { + return pControl; + } + } // iterate over all ProfControl entries + + return 0;// not found +} + +/* + * Saves the View configuration + */ +void ViewBase::save(KConfig *config) +{ + ViewBase *view = this; + QString grp = "View."; + grp += view->id(); + + // Certain bits are not saved for dynamic mixers (e.g. PulseAudio) + bool dynamic = isDynamic(); // TODO 11 Dynamic view configuration + + for (int i = 0; i < view->_mdws.count(); ++i) + { + QWidget *qmdw = view->_mdws[i]; + if (qmdw->inherits("MixDeviceWidget")) + { + MixDeviceWidget* mdw = (MixDeviceWidget*) qmdw; + shared_ptr md = mdw->mixDevice(); + + //kDebug(67100) << " grp=" << grp.toAscii(); + //kDebug(67100) << " mixer=" << view->id().toAscii(); + //kDebug(67100) << " mdwPK=" << mdw->mixDevice()->id().toAscii(); + + QString devgrp = QString("%1.%2.%3").arg(grp).arg(md->mixer()->id()).arg(md->id()); + KConfigGroup devcg = config->group(devgrp); + + if (mdw->inherits("MDWSlider")) + { + // only sliders have the ability to split apart in mutliple channels + devcg.writeEntry("Split", !mdw->isStereoLinked()); + } + if (!dynamic) + { + devcg.writeEntry("Show", mdw->isVisibleTo(view)); +// kDebug() << "Save devgrp" << devgrp << "show=" << mdw->isVisibleTo(view); + } + + } // inherits MixDeviceWidget + } // for all MDW's + + if (!dynamic) + { + // We do not save GUIProfiles (as they cannot be customized) for dynamic mixers (e.g. PulseAudio) + if (guiProfile()->isDirty()) + { + kDebug(67100) + << "Writing dirty profile. grp=" << grp; + guiProfile()->writeProfile(); + } + } +} + + +// ---------- Popup stuff END --------------------- + +#include "viewbase.moc" diff --git a/kmix/gui/viewbase.h b/kmix/gui/viewbase.h new file mode 100644 index 00000000..a660c116 --- /dev/null +++ b/kmix/gui/viewbase.h @@ -0,0 +1,177 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef ViewBase_h +#define ViewBase_h + +// QT +#include +#include +#include + +// KDE +#include +class KIcon; +class KMenu; + +class Mixer; +class MixDevice; + +// KMix +#include "core/mixdevice.h" +#include "core/mixset.h" +#include "gui/guiprofile.h" +#include "gui/mixdevicewidget.h" + +/** + * The ViewBase is a virtual base class, to be used for subclassing the real Mixer Views. + */ +class ViewBase : public QWidget +{ + Q_OBJECT + +friend class KMixToolBox; // the toolbox is everybodys friend :-) + +public: + + typedef uint ViewFlags; + enum ViewFlagsEnum { + // Regular flags + HasMenuBar = 0x0001, + MenuBarVisible = 0x0002, + Horizontal = 0x0004, + Vertical = 0x0008 + }; + + typedef uint GUIComplexity; + enum + { + SIMPLE = 0, + EXTENDED = 1, + ALL = 2 + }; + + ViewBase(QWidget* parent, QString id, Qt::WFlags, ViewFlags vflags, QString guiProfileId, KActionCollection* actionCollection = 0); + virtual ~ViewBase(); + + void addMixer(Mixer *mixer); + + QString id() const; + + // This method is called by ViewBase at the end of createDeviceWidgets(). The default + // implementation does nothing. Subclasses can override this method for doing final + // touches. This is very much like polish(), but called at an exactly well-known time. + // Also I do not want Views to interfere with polish() + virtual void constructionFinished() = 0; + + /** + * Creates a suitable representation for the given MixDevice. + */ + virtual QWidget* add(shared_ptr) = 0; + + // This method is called after a configuration update (show/hide controls, split/unsplit). + virtual void configurationUpdate() = 0; + + void load(KConfig *config); + void save(KConfig *config); + + /** + * Creates the widgets for all supported devices. The default implementation loops + * over the supported MixDevice's and calls add() for each of it. + */ + virtual void createDeviceWidgets(); + + int visibleControls(); + + bool isDynamic() const; + bool pulseaudioPresent() const; + + /** + * Popup stuff + */ + virtual KMenu* getPopup(); + virtual void popupReset(); + virtual void showContextMenu(); + + virtual bool isValid() const; + + void setIcons(bool on); + void setLabels(bool on); + void setTicks(bool on); + + GUIProfile* guiProfile() { return GUIProfile::find(_guiProfileId); }; + GUIComplexity getGuiComplexity() { return guiComplexity; }; + ProfControl* findMdw(const QString& id); + ProfControl* findMdw(const QString& mdwId, QString requestedGuiComplexityName); + + + KActionCollection* actionCollection() { return _actions; }; + + QList& getMixers() { return _mixers; }; + + /** + * Contains the widgets for the _mixSet. There is a 1:1 relationship, which means: + * _mdws[i] is the Widget for the MixDevice _mixSet[i] - please see ViewBase::createDeviceWidgets(). + * Hint: !! The new ViewSurround class shows that a 1:1 relationship does not work in a general scenario. + * I actually DID expect this. The solution is unclear yet, probably there will be a virtual mapper method. + */ + QList _mdws; + +protected: + MixSet _mixSet; + QList _mixers; + KMenu *_popMenu; + KActionCollection* _actions; // -<- application wide action collection + + ViewFlags _vflags; + const QString _guiProfileId; + KActionCollection *_localActionColletion; + + KIcon* configureIcon; + + virtual void _setMixSet() = 0; + void resetMdws(); + void updateGuiOptions(); + QPushButton* createConfigureViewButton(); + + GUIComplexity guiComplexity; + +public slots: + virtual void refreshVolumeLevels(); // TODO remove + virtual void configureView(); + void toggleMenuBarSlot(); + +protected slots: + void mousePressEvent( QMouseEvent *e ); + +signals: + void toggleMenuBar(); + +private: + QString m_viewId; + void updateMediaPlaybackIcons(); + +private slots: + void guiVisibilitySlot(MixDeviceWidget* source, bool enable); + +}; + +#endif + diff --git a/kmix/gui/viewdockareapopup.cpp b/kmix/gui/viewdockareapopup.cpp new file mode 100644 index 00000000..c7c709b5 --- /dev/null +++ b/kmix/gui/viewdockareapopup.cpp @@ -0,0 +1,422 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "gui/viewdockareapopup.h" + +// Qt +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include +#include + +// KMix +#include "apps/kmix.h" +#include "core/mixer.h" +#include "core/ControlManager.h" +#include "core/GlobalConfig.h" +#include "gui/dialogchoosebackends.h" +#include "gui/guiprofile.h" +#include "gui/kmixprefdlg.h" +#include "gui/mdwslider.h" + +// Restore volume button feature is incomplete => disabling for KDE 4.10 +#undef RESTORE_VOLUME_BUTTON + +QString ViewDockAreaPopup::InternedString_Star = QString("*"); +QString ViewDockAreaPopup::InternedString_Subcontrols = QString("pvolume,cvolume,pswitch,cswitch"); +ProfControl ViewDockAreaPopup::MatchAllForSoundMenu = ProfControl(ViewDockAreaPopup::InternedString_Star, ViewDockAreaPopup::InternedString_Subcontrols); + +ViewDockAreaPopup::ViewDockAreaPopup(QWidget* parent, QString id, ViewBase::ViewFlags vflags, QString guiProfileId, + KMixWindow *dockW) : + ViewBase(parent, id, 0, vflags, guiProfileId), _kmixMainWindow(dockW) +{ + resetRefs(); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + + foreach ( Mixer* mixer, Mixer::mixers() ) + { + // Adding all mixers, as we potentially want to show all master controls + addMixer(mixer); + // The list will be redone in _setMixSet() with the actual Mixer instances to use + } + + restoreVolumeIcon = new KIcon(QLatin1String("quickopen-file")); + createDeviceWidgets(); + + // Register listeners for all mixers + ControlManager::instance().addListener( + QString(), // all mixers + (ControlChangeType::Type) (ControlChangeType::GUI | ControlChangeType::ControlList | ControlChangeType::Volume + | ControlChangeType::MasterChanged), this, QString("ViewDockAreaPopup")); +} + + +ViewDockAreaPopup::~ViewDockAreaPopup() +{ + ControlManager::instance().removeListener(this); + delete _layoutMDW; + delete restoreVolumeIcon; + // Hint: optionsLayout and "everything else" is deleted when "delete _layoutMDW" cascades down +} + + +void ViewDockAreaPopup::controlsChange(int changeType) +{ + ControlChangeType::Type type = ControlChangeType::fromInt(changeType); + switch (type ) + { + case ControlChangeType::ControlList: + case ControlChangeType::MasterChanged: + createDeviceWidgets(); + break; + case ControlChangeType::GUI: + updateGuiOptions(); + break; + + case ControlChangeType::Volume: + refreshVolumeLevels(); + break; + + default: + ControlManager::warnUnexpectedChangeType(type, this); + break; + } + +} + + +void ViewDockAreaPopup::wheelEvent ( QWheelEvent * e ) +{ + if ( _mdws.isEmpty() ) + return; + + // Pass wheel event from "border widget" to child + QApplication::sendEvent( _mdws.first(), e); +} + + +void ViewDockAreaPopup::configurationUpdate() +{ + // TODO Do we still need configurationUpdate(). It was never implemented for ViewDockAreaPopup +} + +// TODO Currently no right-click, as we have problems to get the ViewDockAreaPopup resized + void ViewDockAreaPopup::showContextMenu() + { + // no right-button-menu on "dock area popup" + return; + } + +void ViewDockAreaPopup::resetRefs() +{ + seperatorBetweenMastersAndStreams = 0; + separatorBetweenMastersAndStreamsInserted = false; + separatorBetweenMastersAndStreamsRequired = false; + configureViewButton = 0; + restoreVolumeButton1 = 0; + restoreVolumeButton2 = 0; + restoreVolumeButton3 = 0; + restoreVolumeButton4 = 0; + mainWindowButton = 0; + optionsLayout = 0; + _layoutMDW = 0; +} + +void ViewDockAreaPopup::_setMixSet() +{ + resetMdws(); + + if (optionsLayout != 0) + { + QLayoutItem *li2; + while ((li2 = optionsLayout->takeAt(0))) + delete li2; + } +// Hint : optionsLayout itself is deleted when "delete _layoutMDW" cascades down + + if (_layoutMDW != 0) + { + QLayoutItem *li; + while ((li = _layoutMDW->takeAt(0))) + delete li; + } + + /* + * Strangely enough, I cannot delete optionsLayout in a loop. I get a strange stacktrace: + * +Application: KMix (kmix), signal: Segmentation fault +[...] +#6 0x00007f9c9a282900 in QString::shared_null () from /usr/lib/x86_64-linux-gnu/libQtCore.so.4 +#7 0x00007f9c9d4286b0 in ViewDockAreaPopup::_setMixSet (this=0x1272b60) at /home/chris/workspace/kmix-git-trunk/gui/viewdockareapopup.cpp:164 +#8 0x00007f9c9d425700 in ViewBase::createDeviceWidgets (this=0x1272b60) at /home/chris/workspace/kmix-git-trunk/gui/viewbase.cpp:137 +#9 0x00007f9c9d42845b in ViewDockAreaPopup::controlsChange (this=0x1272b60, changeType=2) at /home/chris/workspace/kmix-git-trunk/gui/viewdockareapopup.cpp:91 + */ +// if ( optionsLayout != 0 ) +// { +// QLayoutItem *li2; +// while ( ( li2 = optionsLayout->takeAt(0) ) ) // strangely enough, it crashes here +// delete li2; +// } + + // --- Due to the strange crash, delete everything manually : START --------------- + // I am a bit confused why this doesn't crash. I moved the "optionsLayout->takeAt(0) delete" loop at the beginning, + // so the obejcts should already be deleted. ... + delete configureViewButton; + delete restoreVolumeButton1; + delete restoreVolumeButton2; + delete restoreVolumeButton3; + delete restoreVolumeButton4; + + delete mainWindowButton; + delete seperatorBetweenMastersAndStreams; + // --- Due to the strange crash, delete everything manually : END --------------- + + resetRefs(); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + + /* + * BKO 299754: Looks like I need to explicitly delete layout(). I have no idea why + * "delete _layoutMDW" is not enough, as that is supposed to be the layout + * of this ViewDockAreaPopup + * (Hint: it might have been 0 already. Nowadays it is definitely, see #resetRefs()) + */ + delete layout(); // BKO 299754 + _layoutMDW = new QGridLayout(this); + _layoutMDW->setSpacing(KDialog::spacingHint()); + _layoutMDW->setMargin(0); + //_layoutMDW->setSizeConstraint(QLayout::SetMinimumSize); + _layoutMDW->setSizeConstraint(QLayout::SetMaximumSize); + _layoutMDW->setObjectName(QLatin1String("KmixPopupLayout")); + setLayout(_layoutMDW); + + // Adding all mixers, as we potentially want to show all master controls. Due to hotplugging + // we have to redo the list on each _setMixSet() (instead of setting it once in the Constructor) + _mixers.clear(); + + QSet preferredMixersForSoundmenu = GlobalConfig::instance().getMixersForSoundmenu(); +// kDebug() << "Launch with " << preferredMixersForSoundmenu; + foreach ( Mixer* mixer, Mixer::mixers() ) + { + bool useMixer = preferredMixersForSoundmenu.isEmpty() || preferredMixersForSoundmenu.contains(mixer->id()); + if (useMixer) + addMixer(mixer); + } + + // The following loop is for the case when everything gets filtered out. We "reset" to show everything then. + // Hint: Filtering everything out can only be an "accident", e.g. when restarting KMix with changed hardware or + // backends. + if ( _mixers.isEmpty() ) + { + foreach ( Mixer* mixer, Mixer::mixers() ) + { + addMixer(mixer); + } + } + + + // A loop that adds the Master control of each card + foreach ( Mixer* mixer, _mixers ) + { +// kDebug() << "ADD? mixerId=" << mixer->id(); + shared_ptrdockMD = mixer->getLocalMasterMD(); + if ( !dockMD && mixer->size() > 0 ) + { + // If we have no dock device yet, we will take the first available mixer device. + dockMD = (*mixer)[0]; + } + if ( dockMD ) + { +// kDebug() << "ADD? mixerId=" << mixer->id() << ", md=" << dockMD->id(); + if ( !dockMD->isApplicationStream() && dockMD->playbackVolume().hasVolume()) + { +// kDebug() << "ADD? mixerId=" << mixer->id() << ", md=" << dockMD->id() << ": YES"; + // don't add application streams here. They are handled below, so + // we make sure to not add them twice + _mixSet.append(dockMD); + } + } + } // loop over all cards + + // Add all application streams + foreach ( Mixer* mixer2 , _mixers ) + { + foreach ( shared_ptr md, mixer2->getMixSet() ) + { + if (md->isApplicationStream()) + { + _mixSet.append(md); + } + } + } + +} + + +QWidget* ViewDockAreaPopup::add(shared_ptr md) +{ + bool vertical = (GlobalConfig::instance().data.getTraypopupOrientation() == Qt::Vertical); // I am wondering whether using vflags for this would still make sense + /* + QString dummyMatchAll("*"); + QString matchAllPlaybackAndTheCswitch("pvolume,cvolume,pswitch,cswitch"); + // Leak | relevant | pctl Each time a stream is added, a new ProfControl gets created. + // It cannot be deleted in ~MixDeviceWidget, as ProfControl* ownership is not consistent. + // here a new pctl is created (could be deleted), but in ViewSliders the ProcControl is taken from the + // MixDevice, which in turn uses it from the GUIProfile. + // Summarizing: ProfControl* is either owned by the GUIProfile or created new (ownership unclear). + // Hint: dummyMatchAll and matchAllPlaybackAndTheCswitch leak together with pctl + ProfControl *pctl = new ProfControl( dummyMatchAll, matchAllPlaybackAndTheCswitch); + */ + + if ( !md->isApplicationStream() ) + { + separatorBetweenMastersAndStreamsRequired = true; + } + if ( !separatorBetweenMastersAndStreamsInserted && separatorBetweenMastersAndStreamsRequired && md->isApplicationStream() ) + { + // First application stream => add separator + separatorBetweenMastersAndStreamsInserted = true; + + int sliderColumn = vertical ? _layoutMDW->columnCount() : _layoutMDW->rowCount(); + int row = vertical ? 0 : sliderColumn; + int col = vertical ? sliderColumn : 0; + seperatorBetweenMastersAndStreams = new QFrame(this); + if (vertical) + seperatorBetweenMastersAndStreams->setFrameStyle(QFrame::VLine); + else + seperatorBetweenMastersAndStreams->setFrameStyle(QFrame::HLine); +_layoutMDW->addWidget( seperatorBetweenMastersAndStreams, row, col ); +//_layoutMDW->addItem( new QSpacerItem( 5, 5 ), row, col ); + } + + ProfControl *pctl = &MatchAllForSoundMenu; + MixDeviceWidget *mdw = new MDWSlider( + md, // only 1 device. + true, // Show Mute LE + true, // Show Record LED + true, // Include Mixer Name + false, // Small + vertical ? Qt::Vertical : Qt::Horizontal, + this, // parent + this // NOT ANYMORE!!! -> Is "NULL", so that there is no RMB-popup + , pctl + ); + mdw->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + int sliderColumn = vertical ? _layoutMDW->columnCount() : _layoutMDW->rowCount(); + //if (sliderColumn == 1 ) sliderColumn =0; + int row = vertical ? 0 : sliderColumn; + int col = vertical ? sliderColumn : 0; + + _layoutMDW->addWidget( mdw, row, col ); + + //kDebug(67100) << "ADDED " << md->id() << " at column " << sliderColumn; + return mdw; +} + +void ViewDockAreaPopup::constructionFinished() +{ +// kDebug(67100) << "ViewDockAreaPopup::constructionFinished()\n"; + + mainWindowButton = new QPushButton(i18n("Mixer"), this); + mainWindowButton->setObjectName(QLatin1String("MixerPanel")); + connect(mainWindowButton, SIGNAL(clicked()), SLOT(showPanelSlot())); + + configureViewButton = createConfigureViewButton(); + + optionsLayout = new QHBoxLayout(); + optionsLayout->addWidget(mainWindowButton); + optionsLayout->addWidget(configureViewButton); + +#ifdef RESTORE_VOLUME_BUTTON + restoreVolumeButton1 = createRestoreVolumeButton(1); + optionsLayout->addWidget( restoreVolumeButton1 ); // TODO enable only if user has saved a volume profile + +// optionsLayout->addWidget( createRestoreVolumeButton(2) ); +// optionsLayout->addWidget( createRestoreVolumeButton(3) ); +// optionsLayout->addWidget( createRestoreVolumeButton(4) ); +#endif + + int sliderRow = _layoutMDW->rowCount(); + _layoutMDW->addLayout(optionsLayout, sliderRow, 0, 1, _layoutMDW->columnCount()); + + updateGuiOptions(); + + _layoutMDW->update(); + _layoutMDW->activate(); +// kDebug() << "F layout()=" << layout() << ", _layoutMDW=" << _layoutMDW; +} + +QPushButton* ViewDockAreaPopup::createRestoreVolumeButton ( int storageSlot ) +{ + QString buttonText = QString("%1").arg(storageSlot); + QPushButton* profileButton = new QPushButton(*restoreVolumeIcon, buttonText, this); + profileButton->setToolTip(i18n("Load volume profile %1").arg(storageSlot)); + profileButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + return profileButton; +} + +void ViewDockAreaPopup::refreshVolumeLevels() +{ + foreach ( QWidget* qw, _mdws ) + { + //kDebug() << "rvl: " << qw; + MixDeviceWidget* mdw = qobject_cast(qw); + if ( mdw != 0 ) mdw->update(); + } +} + +void ViewDockAreaPopup::configureView() +{ +// Q_ASSERT( !pulseaudioPresent() ); + +// QSet currentlyActiveMixersInDockArea; +// foreach ( Mixer* mixer, _mixers ) +// { +// currentlyActiveMixersInDockArea.insert(mixer->id()); +// } + + KMixPrefDlg* prefDlg = KMixPrefDlg::getInstance(); + //prefDlg->setActiveMixersInDock(currentlyActiveMixersInDockArea); + prefDlg->switchToPage(KMixPrefDlg::PrefSoundMenu); +} + +/** + * This gets activated whne a user clicks the "Mixer" PushButton in this popup. + */ +void ViewDockAreaPopup::showPanelSlot() +{ + _kmixMainWindow->setVisible(true); + KWindowSystem::setOnDesktop(_kmixMainWindow->winId(), KWindowSystem::currentDesktop()); + KWindowSystem::activateWindow(_kmixMainWindow->winId()); + // This is only needed when the window is already visible. + static_cast(parent())->hide(); +} + +#include "viewdockareapopup.moc" diff --git a/kmix/gui/viewdockareapopup.h b/kmix/gui/viewdockareapopup.h new file mode 100644 index 00000000..e56a1ba2 --- /dev/null +++ b/kmix/gui/viewdockareapopup.h @@ -0,0 +1,87 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef ViewDockAreaPopup_h +#define ViewDockAreaPopup_h + +#include "viewbase.h" + +class QBoxLayout; +class QFrame; +class QGridLayout; +#include +class QWidget; + +class KIcon; + +class Mixer; +class MixDevice; +class KMixWindow; + +class ViewDockAreaPopup : public ViewBase +{ + Q_OBJECT +public: + ViewDockAreaPopup(QWidget* parent, QString id, ViewBase::ViewFlags vflags, QString guiProfileId, KMixWindow *dockW); + virtual ~ViewDockAreaPopup(); + + virtual QWidget* add(shared_ptr md); + virtual void constructionFinished(); + virtual void refreshVolumeLevels(); + virtual void showContextMenu(); + virtual void configurationUpdate(); + +protected: + KMixWindow *_kmixMainWindow; + + void wheelEvent ( QWheelEvent * e ); + virtual void _setMixSet(); + +private: + QGridLayout* _layoutMDW; + QPushButton* createRestoreVolumeButton ( int storageSlot ); + + bool separatorBetweenMastersAndStreamsInserted; + bool separatorBetweenMastersAndStreamsRequired; + QFrame* seperatorBetweenMastersAndStreams; + QBoxLayout* optionsLayout; + QPushButton* configureViewButton; + QPushButton *mainWindowButton; + QPushButton *restoreVolumeButton1; + QPushButton *restoreVolumeButton2; + QPushButton *restoreVolumeButton3; + QPushButton *restoreVolumeButton4; + KIcon* restoreVolumeIcon; + + static ProfControl MatchAllForSoundMenu; + static QString InternedString_Star; + static QString InternedString_Subcontrols; + +public slots: + void controlsChange(int changeType); + virtual void configureView(); + +private slots: + void showPanelSlot(); + void resetRefs(); +}; + +#endif + diff --git a/kmix/gui/viewsliders.cpp b/kmix/gui/viewsliders.cpp new file mode 100644 index 00000000..52ec6a1d --- /dev/null +++ b/kmix/gui/viewsliders.cpp @@ -0,0 +1,442 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +//#define TEST_MIXDEVICE_COMPOSITE +#undef TEST_MIXDEVICE_COMPOSITE + +#ifdef TEST_MIXDEVICE_COMPOSITE +#ifdef __GNUC__ +#warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +#warning !!! MIXDEVICE COMPOSITE TESTING IS ACTIVATED !!! +#warning !!! THIS IS PRE-ALPHA CODE! !!! +#warning !!! DO NOT SHIP KMIX IN THIS STATE !!! +#warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +#endif +#endif + +#include "gui/viewsliders.h" + +// KMix +#include "core/ControlManager.h" +#include "core/GlobalConfig.h" +#include "core/mixdevicecomposite.h" +#include "core/mixer.h" +#include "gui/guiprofile.h" +#include "gui/mdwenum.h" +#include "gui/mdwslider.h" +#include "gui/verticaltext.h" + + +// KDE +#include +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include + +/** + * Generic View implementation. This can hold now all kinds of controls (not just Sliders, as + * the class name suggests). + */ +ViewSliders::ViewSliders(QWidget* parent, QString id, Mixer* mixer, ViewBase::ViewFlags vflags, QString guiProfileId, + KActionCollection *actColl) : + ViewBase(parent, id, Qt::FramelessWindowHint, vflags, guiProfileId, actColl), _layoutEnum(0) +{ + addMixer(mixer); + + _configureViewButton = 0; + _layoutMDW = 0; + _layoutSliders = 0; + _layoutEnum = 0; + emptyStreamHint = 0; + + createDeviceWidgets(); + + ControlManager::instance().addListener(mixer->id(), + (ControlChangeType::Type) (ControlChangeType::GUI | ControlChangeType::ControlList | ControlChangeType::Volume), + this, QString("ViewSliders.%1").arg(mixer->id())); + +} + +ViewSliders::~ViewSliders() +{ + ControlManager::instance().removeListener(this); + delete _layoutMDW; +// qDeleteAll(_separators); +} + +void ViewSliders::controlsChange(int changeType) +{ + ControlChangeType::Type type = ControlChangeType::fromInt(changeType); + switch (type) + { + case ControlChangeType::ControlList: + createDeviceWidgets(); + break; + case ControlChangeType::GUI: + updateGuiOptions(); + break; + + case ControlChangeType::Volume: + if (GlobalConfig::instance().data.debugVolume) + kDebug() + << "NOW I WILL REFRESH VOLUME LEVELS. I AM " << id(); + refreshVolumeLevels(); + break; + + default: + ControlManager::warnUnexpectedChangeType(type, this); + break; + } + +} + + +QWidget* ViewSliders::add(shared_ptr md) +{ + MixDeviceWidget *mdw; + Qt::Orientation orientation = GlobalConfig::instance().data.getToplevelOrientation(); + + if ( md->isEnum() ) + { + mdw = new MDWEnum( + md, // MixDevice (parameter) + orientation, // Orientation + this, // parent + this // View widget + , md->controlProfile() + ); +// if ( _layoutEnum == 0 ) { +// // lazily creation of Layout for the first enum +// _layoutEnum = new QVBoxLayout(); +// _layoutMDW->addLayout( _layoutEnum ); +// } + _layoutEnum->addWidget(mdw); + } // an enum + else + { + mdw = new MDWSlider( + md, // MixDevice (parameter) + true, // Show Mute LED + true, // Show Record LED + false, // Include Mixer Name + false, // Small + orientation, // Orientation + this, // parent + this + , md->controlProfile() + ); // View widget + _layoutSliders->addWidget(mdw); + } + + return mdw; +} + + +void ViewSliders::_setMixSet() +{ + resetMdws(); + + delete emptyStreamHint; + emptyStreamHint = 0; + + // Our _layoutSliders now should only contain spacer widgets from the addSpacing() calls in add() above. + // We need to trash those too otherwise all sliders gradually migrate away from the edge :p + if (_layoutSliders != 0) + { + QLayoutItem *li; + while ((li = _layoutSliders->takeAt(0))) + delete li; +// delete _layoutSliders; + _layoutSliders = 0; + } + + + delete _configureViewButton; + _configureViewButton = 0; + + if (_layoutEnum != 0) + { + QLayoutItem *li2; + while ((li2 = _layoutEnum->takeAt(0)) != 0) + delete li2; +// delete _layoutEnum; + _layoutEnum = 0; + } + + delete _layoutMDW; + _layoutMDW = 0; + + // We will be recreating our sliders, so make sure we trash all the separators too. + //qDeleteAll(_separators); + _separators.clear(); + + if (GlobalConfig::instance().data.getToplevelOrientation() == Qt::Horizontal) + { + // Horizontal slider => put them vertically + _layoutMDW = new QVBoxLayout(this); + _layoutMDW->setAlignment(Qt::AlignLeft | Qt::AlignTop); + _layoutSliders = new QVBoxLayout(); + _layoutSliders->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); + } + else + { + // Vertical slider => put them horizontally + _layoutMDW = new QHBoxLayout(this); + _layoutMDW->setAlignment(Qt::AlignHCenter | Qt::AlignTop); + _layoutSliders = new QHBoxLayout(); + _layoutSliders->setAlignment(Qt::AlignHCenter | Qt::AlignTop); + } + _layoutSliders->setContentsMargins(0, 0, 0, 0); + _layoutSliders->setSpacing(0); + _layoutMDW->setContentsMargins(0, 0, 0, 0); + _layoutMDW->setSpacing(0); + _layoutMDW->addItem(_layoutSliders); + + _layoutEnum = new QVBoxLayout(); + _layoutMDW->addLayout(_layoutEnum); + + // Hint: This text comparison is not a clean solution, but one that will work for quite a while. + QString viewId(id()); + if (viewId.contains(".Capture_Streams.")) + emptyStreamHint = new QLabel(i18n("Nothing is capturing audio.")); + else if (viewId.contains(".Playback_Streams.")) + emptyStreamHint = new QLabel(i18n("Nothing is playing audio.")); + else if (viewId.contains(".Capture_Devices.")) + emptyStreamHint = new QLabel(i18n("No capture devices.")); + else if (viewId.contains(".Playback_Devices.")) + emptyStreamHint = new QLabel(i18n("No playback devices.")); + else + emptyStreamHint = new QLabel(i18n("Nothing is playing audio.")); // Fallback. Assume Playback stream + + emptyStreamHint->setAlignment(Qt::AlignCenter); + emptyStreamHint->setWordWrap(true); + emptyStreamHint->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + _layoutMDW->addWidget(emptyStreamHint); + +#ifdef TEST_MIXDEVICE_COMPOSITE + QList > mds; // For temporary test +#endif + + // This method iterates the controls from the Profile + // Each control is checked, whether it is also contained in the mixset, and + // applicable for this kind of View. If yes, the control is accepted and inserted. + + GUIProfile* guiprof = guiProfile(); + if (guiprof != 0) + { + foreach (Mixer* mixer , _mixers ){ + const MixSet& mixset = mixer->getMixSet(); + + foreach ( ProfControl* control, guiprof->getControls() ) + { + //ProfControl* control = *it; + // The TabName of the control matches this View name (!! attention: Better use some ID, due to i18n() ) + QRegExp idRegexp(control->id); + //bool isExactRegexp = control->id.startsWith('^') && control->id.endsWith('$'); // for optimizing + //isExactRegexp &= ( ! control->id.contains(".*") ); // For now. Might be removed in the future, as it cannot be done properly !!! + //kDebug(67100) << "ViewSliders::setMixSet(): Check GUIProfile id==" << control->id << "\n"; + // The following for-loop could be simplified by using a std::find_if + for ( int i=0; i md = mixset[i]; + + if ( md->id().contains(idRegexp) ) + { + // Match found (by name) + if ( _mixSet.contains( md ) ) + { + continue; // dup check + } + + // Now check whether subcontrols match + bool subcontrolPlaybackWanted = (control->useSubcontrolPlayback() && ( md->playbackVolume().hasVolume() || md->hasMuteSwitch()) ); + bool subcontrolCaptureWanted = (control->useSubcontrolCapture() && ( md->captureVolume() .hasVolume() || md->captureVolume() .hasSwitch()) ); + bool subcontrolEnumWanted = (control->useSubcontrolEnum() && md->isEnum()); + bool subcontrolWanted = subcontrolPlaybackWanted | subcontrolCaptureWanted | subcontrolEnumWanted; + + if ( !subcontrolWanted ) + { + continue; + } + + md->setControlProfile(control); + if ( !control->name.isNull() ) + { + // Apply the custom name from the profile + md->setReadableName(control->name);// @todo: This is the wrong place. It only applies to controls in THIS type of view + } + if ( !control->getSwitchtype().isNull() ) + { + if ( control->getSwitchtype() == "On" ) + md->playbackVolume().setSwitchType(Volume::OnSwitch); + else if ( control->getSwitchtype() == "Off" ) + md->playbackVolume().setSwitchType(Volume::OffSwitch); + } + _mixSet.append(md); + +#ifdef TEST_MIXDEVICE_COMPOSITE + if ( md->id() == "Front:0" || md->id() == "Surround:0") + { mds.append(md);} // For temporary test +#endif + // We use no "break;" ,as multiple devices could match the regexp (e.g. "^.*$") + } // name matches + } // loop for finding a suitable MixDevice + } // iteration over all controls from the Profile + } // if there is a profile +} // Iteration over all Mixers + + emptyStreamHint->setVisible(_mixSet.isEmpty() && isDynamic()); // show a hint why a tab is empty (dynamic controls!!!) + // visibleControls() == 0 could be used for the !isDynamic() case + +#ifdef TEST_MIXDEVICE_COMPOSITE + // @todo: This is currently hardcoded, and instead must be read as usual from the Profile + MixDeviceComposite *mdc = new MixDeviceComposite(_mixer, "Composite_Test", mds, "A Composite Control #1", MixDevice::KMIX_COMPOSITE); + Volume::ChannelMask chn = Volume::MMAIN; + Volume* vol = new Volume( chn, 0, 100, true, true); + mdc->addPlaybackVolume(*vol); + QString ctlId("Composite_Test"); + QString ctlMatchAll("*"); + ProfControl* pctl = new ProfControl(ctlId, ctlMatchAll); + mdc->setControlProfile(pctl); + _mixSet->append(mdc); +#endif +} + + +void ViewSliders::constructionFinished() { + configurationUpdate(); + //if ( !pulseaudioPresent() ) // TODO 11 Dynamic view configuration + if ( !isDynamic() ) + { + _configureViewButton = createConfigureViewButton(); + _layoutEnum->addStretch(); + _layoutEnum->addWidget(_configureViewButton); + } + + updateGuiOptions(); +} + + +void ViewSliders::configurationUpdate() +{ + // Adjust height of top part by setting it to the maximum of all mdw's + bool haveCaptureLEDs = false; + int labelExtent = 0; + bool haveMuteButtons = false; + + // Find out whether any MDWSlider has Switches. If one has, then we need "extents" + for (int i = 0; i < _mdws.count(); i++) + { + MDWSlider* mdw = ::qobject_cast(_mdws[i]); + if (mdw && mdw->isVisibleTo(this)) + { + if (mdw->labelExtentHint() > labelExtent) + labelExtent = mdw->labelExtentHint(); + haveCaptureLEDs = haveCaptureLEDs || mdw->hasCaptureLED(); + haveMuteButtons = haveMuteButtons || mdw->hasMuteButton(); + } + + if (haveCaptureLEDs && haveMuteButtons) + break; // We know all we want. Lets break. + } + //kDebug(67100) << "topPartExtent is " << topPartExtent; + bool firstVisibleControlFound = false; + for ( int i=0; i<_mdws.count(); i++ ) + { + MixDeviceWidget* mdw = ::qobject_cast(_mdws[i]); + MDWSlider* mdwSlider = ::qobject_cast(_mdws[i]); + if ( mdw ) + { + // This is a bit hacky. Using "simple" can be wrong on the very first start of KMix (but usually it is not!) + ProfControl* matchingControl = findMdw(mdw->mixDevice()->id(), QString("simple")); + mdw->setVisible(matchingControl != 0); + + if ( mdwSlider ) + { + // additional options for sliders + mdwSlider->setLabelExtent(labelExtent); + mdwSlider->setMuteButtonSpace(haveMuteButtons); + mdwSlider->setCaptureLEDSpace(haveCaptureLEDs); + } + bool thisControlIsVisible = mdw->isVisibleTo(this); + bool showSeparator = ( firstVisibleControlFound && thisControlIsVisible); + if ( _separators.contains( mdw->mixDevice()->id() )) + { + QFrame* sep = _separators[mdw->mixDevice()->id()]; + sep->setVisible(showSeparator); + } + if ( thisControlIsVisible ) + firstVisibleControlFound=true; + } + } // for all MDW's + + _layoutMDW->activate(); +} + +void ViewSliders::refreshVolumeLevels() +{ + for (int i = 0; i < _mdws.count(); i++) + { + QWidget *mdwx = _mdws[i]; + + MixDeviceWidget* mdw = ::qobject_cast(mdwx); + if (mdw != 0) + { // sanity check + +#ifdef TEST_MIXDEVICE_COMPOSITE + // --- start --- The following 4 code lines should be moved to a more + // generic place, as it only works in this View. But it + // should also work in the ViewDockareaPopup and everywhere else. + MixDeviceComposite* mdc = ::qobject_cast(mdw->mixDevice()); + if (mdc != 0) + { + mdc->update(); + } + // --- end --- +#endif + + if (GlobalConfig::instance().data.debugVolume) + { + bool debugMe = (mdw->mixDevice()->id() == "PCM:0"); + if (debugMe) + kDebug() + << "Old PCM:0 playback state" << mdw->mixDevice()->isMuted() << ", vol=" + << mdw->mixDevice()->playbackVolume().getAvgVolumePercent(Volume::MALL); + } + + mdw->update(); + } + else + { + kError(67100) << "ViewSliders::refreshVolumeLevels(): mdw is not a MixDeviceWidget\n"; + // no slider. Cannot happen in theory => skip it + } + } +} + +#include "viewsliders.moc" diff --git a/kmix/gui/viewsliders.h b/kmix/gui/viewsliders.h new file mode 100644 index 00000000..70728117 --- /dev/null +++ b/kmix/gui/viewsliders.h @@ -0,0 +1,63 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef ViewSliders_h +#define ViewSliders_h + +class QBoxLayout; +#include +#include +class QLabel; +#include +class QWidget; + +class Mixer; +#include "viewbase.h" + +class ViewSliders : public ViewBase +{ + Q_OBJECT +public: + ViewSliders(QWidget* parent, QString id, Mixer* mixer, ViewBase::ViewFlags vflags, QString guiProfileId, KActionCollection *actColl); + virtual ~ViewSliders(); + + virtual QWidget* add(shared_ptr); + virtual void constructionFinished(); + virtual void configurationUpdate(); + +public slots: + void controlsChange(int changeType); + +protected: + virtual void _setMixSet(); + +private: + virtual void refreshVolumeLevels(); + + QBoxLayout* _layoutMDW; + QLayout* _layoutSliders; + QBoxLayout* _layoutEnum; + QHash _separators; + QPushButton* _configureViewButton; + QLabel* emptyStreamHint; +}; + +#endif + diff --git a/kmix/gui/volumeslider.cpp b/kmix/gui/volumeslider.cpp new file mode 100644 index 00000000..1a4166e0 --- /dev/null +++ b/kmix/gui/volumeslider.cpp @@ -0,0 +1,30 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "volumeslider.h" + +VolumeSlider::VolumeSlider(Qt::Orientation orientation, QWidget* parent) : QSlider(orientation, parent) +{ +} + +#include "volumeslider.moc" + diff --git a/kmix/gui/volumeslider.h b/kmix/gui/volumeslider.h new file mode 100644 index 00000000..616c9a01 --- /dev/null +++ b/kmix/gui/volumeslider.h @@ -0,0 +1,39 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright Christian Esken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef VOLUMESLIDER_H +#define VOLUMESLIDER_H + +#include + +#include "volumesliderextradata.h" + +class VolumeSlider : public QSlider +{ + Q_OBJECT +public: + VolumeSlider(Qt::Orientation orientation, QWidget* parent); // : QSlider(orientation, parent); + + VolumeSliderExtraData extraData; +}; + +#endif diff --git a/kmix/gui/volumesliderextradata.h b/kmix/gui/volumesliderextradata.h new file mode 100644 index 00000000..c530c5c7 --- /dev/null +++ b/kmix/gui/volumesliderextradata.h @@ -0,0 +1,48 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef VOLUMESLIDEREXTRADATA_H +#define VOLUMESLIDEREXTRADATA_H + +#include +#include "core/volume.h" + +class VolumeSliderExtraData +{ +public: + VolumeSliderExtraData() : chid(Volume::NOCHANNEL), subcontrolLabel(0) {}; + ~VolumeSliderExtraData() {}; + void setChid(Volume::ChannelID chid) { this->chid = chid; }; + Volume::ChannelID getChid() { return chid; }; + void setSubcontrolLabel(QWidget *subcontrolLabel) { this->subcontrolLabel = subcontrolLabel; }; + QWidget* getSubcontrolLabel() {return subcontrolLabel; } + +protected: + Volume::ChannelID chid; + QWidget *subcontrolLabel; +}; + +class AbstractVolumeSlider : public QAbstractSlider, public VolumeSliderExtraData +{ +}; + +#endif diff --git a/kmix/kmix.desktop b/kmix/kmix.desktop new file mode 100755 index 00000000..867827ae --- /dev/null +++ b/kmix/kmix.desktop @@ -0,0 +1,112 @@ +[Desktop Entry] +Exec=kmix -caption %c %i +X-DocPath=kmix/index.html +OnlyShowIn=KDE; +Type=Application +Terminal=false +Icon=kmix +GenericName=Sound Mixer +GenericName[bg]=Звуков смесител +GenericName[bs]=Mikser zvuka +GenericName[ca]=Mesclador de so +GenericName[ca@valencia]=Mesclador de so +GenericName[cs]=Zvukový směšovač +GenericName[da]=Lydmikser +GenericName[de]=Lautstärkeregler +GenericName[el]=Μείκτης ήχου +GenericName[en_GB]=Sound Mixer +GenericName[es]=Mezclador de sonido +GenericName[et]=Helimikser +GenericName[fi]=Äänimikseri +GenericName[fr]=Console de mixage +GenericName[ga]=Meascthóir Fuaime +GenericName[gl]=Mesturador de son +GenericName[hu]=Hangkeverő +GenericName[ia]=Miscitor de sono +GenericName[id]=Mixer Suara +GenericName[is]=Hljóðblöndun +GenericName[it]=Mixer audio +GenericName[ja]=サウンドミキサー +GenericName[kk]=Дыбыс микшері +GenericName[km]=កម្មវិធី​លាយ​សំលេង +GenericName[ko]=소리 믹서 +GenericName[lt]=Garsų maišiklis +GenericName[mr]=आवाज मिक्सर +GenericName[nb]=Lydmikser +GenericName[nds]=Klangmischer +GenericName[nl]=Geluidsmixer +GenericName[nn]=Lydmiksar +GenericName[pa]=ਸਾਊਡ ਮਿਕਸਰ +GenericName[pl]=Mikser dźwięku +GenericName[pt]=Mistura de Áudio +GenericName[pt_BR]=Mixer de som +GenericName[ro]=Mixer de sunet +GenericName[ru]=Звуковой микшер +GenericName[se]=Jietnamixer +GenericName[sk]=Zvukový mixér +GenericName[sl]=Mešalnik zvoka +GenericName[sr]=Звучна миксета +GenericName[sr@ijekavian]=Звучна миксета +GenericName[sr@ijekavianlatin]=Zvučna mikseta +GenericName[sr@latin]=Zvučna mikseta +GenericName[sv]=Ljudmixer +GenericName[tr]=Ses Karıştırıcı +GenericName[ug]=ئاۋاز كىرىشتۈرگۈچى +GenericName[uk]=Аудіомікшер +GenericName[x-test]=xxSound Mixerxx +GenericName[zh_CN]=混音器 +GenericName[zh_TW]=音效混音器 +Name=KMix +Name[bg]=KMix +Name[bs]=KMix +Name[ca]=KMix +Name[ca@valencia]=KMix +Name[cs]=KMix +Name[da]=KMix +Name[de]=KMix +Name[el]=KMix +Name[en_GB]=KMix +Name[es]=KMix +Name[et]=KMix +Name[fi]=KMix +Name[fr]=KMix +Name[ga]=KMix +Name[gl]=KMix +Name[hu]=KMix +Name[ia]=KMix +Name[id]=KMix +Name[is]=KMix +Name[it]=KMix +Name[ja]=KMix +Name[kk]=KMix +Name[km]=KMix +Name[ko]=KMix +Name[lt]=KMix +Name[mr]=के-मिक्स +Name[nb]=KMix +Name[nds]=KMix +Name[nl]=KMix +Name[nn]=KMix +Name[pa]=ਕੇਮਿਕਸ +Name[pl]=KMix +Name[pt]=KMix +Name[pt_BR]=KMix +Name[ro]=KMix +Name[ru]=KMix +Name[se]=KMix +Name[sk]=KMix +Name[sl]=KMix +Name[sr]=К‑миксета +Name[sr@ijekavian]=К‑миксета +Name[sr@ijekavianlatin]=K‑mikseta +Name[sr@latin]=K‑mikseta +Name[sv]=Kmix +Name[tr]=KMix +Name[ug]=KMix +Name[uk]=KMix +Name[x-test]=xxKMixxx +Name[zh_CN]=KMix +Name[zh_TW]=音效調整_KMix +X-KDE-StartupNotify=true +X-DBUS-StartupType=Unique +Categories=Qt;KDE;AudioVideo;Audio;Mixer; diff --git a/kmix/kmix.notifyrc b/kmix/kmix.notifyrc new file mode 100644 index 00000000..80dcb3fe --- /dev/null +++ b/kmix/kmix.notifyrc @@ -0,0 +1,156 @@ +[Global] +IconName=kmix +Comment=Sound Mixer +Comment[bg]=Звуков смесител +Comment[bs]=Mikser zvuka +Comment[ca]=Mesclador de so +Comment[ca@valencia]=Mesclador de so +Comment[cs]=Zvukový směšovač +Comment[da]=Lydmikser +Comment[de]=Lautstärkeregler +Comment[el]=Μείκτης ήχου +Comment[en_GB]=Sound Mixer +Comment[es]=Mezclador de sonido +Comment[et]=Helimikser +Comment[fi]=Äänimikseri +Comment[fr]=Console de mixage +Comment[ga]=Meascthóir Fuaime +Comment[gl]=Mesturador de son +Comment[hu]=Hangkeverő +Comment[ia]=Mixer de Sono (Miscitor de sono) +Comment[id]=Mixer Suara +Comment[is]=Hljóðblandari +Comment[it]=Mixer audio +Comment[ja]=サウンドミキサー +Comment[kk]=Дыбыс микшері +Comment[km]=កម្មវិធី​លាយ​សំឡេង +Comment[ko]=소리 믹서 +Comment[lt]=Garsų maišiklis +Comment[mr]=आवाज मिक्सर +Comment[nb]=Lydmikser +Comment[nds]=Klangmischer +Comment[nl]=Geluidsmixer +Comment[nn]=Lydmiksar +Comment[pa]=ਸਾਊਡ ਮਿਕਸਰ +Comment[pl]=Mikser dźwięku +Comment[pt]=Mistura de Som +Comment[pt_BR]=Mixer de som +Comment[ro]=Mixer de sunet +Comment[ru]=Звуковой микшер +Comment[se]=Jietnamixer +Comment[sk]=Zvukový mixér +Comment[sl]=Mešalnik zvoka +Comment[sr]=Звучна миксета +Comment[sr@ijekavian]=Звучна миксета +Comment[sr@ijekavianlatin]=Zvučna mikseta +Comment[sr@latin]=Zvučna mikseta +Comment[sv]=Ljudmixer +Comment[tr]=Ses Karıştırıcı +Comment[ug]=ئاۋاز كىرىشتۈرگۈچى +Comment[uk]=Аудіомікшер +Comment[x-test]=xxSound Mixerxx +Comment[zh_CN]=混音器 +Comment[zh_TW]=音效混音器 + +[Event/MasterFallback] +Name=Audio Device Fallback +Name[bg]=Резервно звуково устройство +Name[bs]=Rezervni zvučni uređaj +Name[ca]=Dispositiu d'àudio alternatiu +Name[ca@valencia]=Dispositiu d'àudio alternatiu +Name[cs]=Náhradní zvukové zařízení +Name[da]=Erstatning for lydenhed +Name[de]=Audio-Ausweichgerät +Name[el]=Μετάπτωση συσκευής ήχου +Name[en_GB]=Audio Device Fallback +Name[es]=Dispositivo de audio al que recurrir +Name[et]=Tagavara-heliseade +Name[fi]=Varaäänilaite +Name[fr]=Périphérique audio sur lequel se replier +Name[ga]=Gléas Tacachumais Fuaime +Name[gl]=Dispositivo de son de reserva +Name[hu]=Tartalék hangeszköz +Name[ia]=Replica de dispositivo audio +Name[id]=Perangkat Audio Belakang +Name[is]=Varahljóðtæki +Name[it]=Dispositivo audio di ripiego +Name[ja]=音声デバイスのフォールバック +Name[kk]=Қосалқы дыбыс құрылғысы +Name[km]=ឧបករណ៍​អូឌីយ៉ូ Fallback +Name[ko]=오디오 장치 폴백 +Name[lt]=Audio įrenginio atsarginis variantas +Name[mr]=ऑडिओ साधन पाठबळ +Name[nb]=Skifte av lydenhet +Name[nds]=Klang-Opbackreedschap +Name[nl]=Terugvalapparaat voor geluid +Name[nn]=Reservelydeining +Name[pa]=ਆਡੀਓ ਜੰਤਰ ਫਾਲਬੈਕ +Name[pl]=Urządzenie dźwiękowe w przypadku awarii +Name[pt]=Dispositivo Alternativo de Áudio: +Name[pt_BR]=Dispositivo de áudio alternativo +Name[ro]=Dispozitiv audio de rezervă +Name[ru]=Звуковое устройство по умолчанию +Name[sk]=Záložné audio zariadenie +Name[sl]=Zasilna zvočna naprava +Name[sr]=Одступни аудио уређај +Name[sr@ijekavian]=Одступни аудио уређај +Name[sr@ijekavianlatin]=Odstupni audio uređaj +Name[sr@latin]=Odstupni audio uređaj +Name[sv]=Reservljudenhet +Name[tr]=Ses Aygıtı Yedeğe Geçiş +Name[ug]=ئاۋاز ئۈسكۈنىسىنى تۆۋەنلەت +Name[uk]=Другорядний аудіопристрій +Name[x-test]=xxAudio Device Fallbackxx +Name[zh_CN]=备用音频设备 +Name[zh_TW]=預設音效裝置 +Comment=Notification on automatic fallback if the preferred device is unavailable +Comment[bg]=Уведомление за резервно устройство ако подразбиращото се не е налично +Comment[bs]=Obavještenje o automatskoj alternativi ako željeni uređaj nije dostupan +Comment[ca]=Notificació del canvi automàtic al dispositiu l'alternatiu si el dispositiu preferit no està disponible +Comment[ca@valencia]=Notificació del canvi automàtic al dispositiu l'alternatiu si el dispositiu preferit no està disponible +Comment[cs]=Upozornění na automatickou náhradu, pokud je preferované zařízení nedostupné +Comment[da]=Bekendtgørelse af automatisk erstatning hvis den foretrukne enhed er utilgængelig +Comment[de]=Benachrichtigung bei automatischem Wechsel zum Ausweichgerät, wenn das bevorzugte Gerät nicht verfügbar ist +Comment[el]=Ειδοποίηση σε αυτόματη μετάπτωση αν η προτιμώμενη συσκευή δεν είναι διαθέσιμη +Comment[en_GB]=Notification on automatic fallback if the preferred device is unavailable +Comment[es]=Notificación si se recurre automáticamente a otro dispositivo cuando el preferido no está disponible +Comment[et]=Automaatne varuvariant, kui eelistatud seade pole saadaval +Comment[fi]=Ilmoita varalaitteeseen turvautumisesta, mikäli suositumpi laite ei ole käytettävissä +Comment[fr]=Notification sur le repli automatique si les périphériques préférés ne sont pas disponibles +Comment[ga]=Fógairt nuair nach bhfuil do rogha ghléas ar fáil agus úsáidtear gléas tacachumais +Comment[gl]=Notificación sobre a selección automática dun dispositivo de son alternativo se o dispositivo preferido non está dispoñíbel +Comment[hu]=Értesítés az automatikus átállásról, ha az előnyben részesített eszköz nem érhető el +Comment[ia]=Notification sur replica automatic si l edispositivo preferite non es disponibile +Comment[id]=Pemberitahuan otomatis diberikan jika perangkat disukai tidak tersedia +Comment[is]=Aðvörun um sjálfvirkjun varatækis ef valið tæki er ekki tiltækt +Comment[it]=Notifica sul ripiego automatico se il dispositivo preferito non è disponibile +Comment[ja]=優先デバイスが利用できないために自動的にフォールバックしたときの通知 +Comment[kk]=Артық көретін құрылғы істемей, автоматты түрде қосалқысына ауысқандағы құлақтандыру +Comment[km]=កា​រជូន​ដំណឹង​នៅពេល fallback ស្វ័យប្រវត្តិ ប្រសិនបើ​មិនមាន​ឧបករណ៍​ដែល​ពេញ​ចិត្ត +Comment[ko]=선호하는 장치를 사용할 수 없을 때 자동 폴백 알림 +Comment[lt]=Pranešimai apie automatinį atsarginį įrenginį, jei pageidautinas įrenginys neprieinamas +Comment[mr]=जर पसंत साधन उपलब्ध नसेल तर स्वयंचलित पाठबळाची सूचना +Comment[nb]=Gi varsel om automatisk reserveenhet dersom den foretrukne enheten til ikke er tilgjengelig +Comment[nds]=Bescheed, wenn de vörtrocken Reedschap nich verföögbor is un ansteed de Opbackreedschap bruukt warrt +Comment[nl]=Melding bij automatische terugval als het apparaat van voorkeur niet beschikbaar is +Comment[nn]=Varsling om automatisk reserveeining om den føretrekte eininga ikkje er tilgjengeleg +Comment[pl]=Powiadomienie o samoczynnej zmianie urządzenia audio, kiedy preferowane nie jest dostępne +Comment[pt]=Notificação de contingência se o dispositivo preferido estiver indisponível +Comment[pt_BR]=Notificação de alternativa automática se os dispositivos preferidos estiverem indisponíveis +Comment[ro]=Notificare la trecerea automată pe rezervă dacă dispozitivul preferat nu este disponibil +Comment[ru]=Уведомление о недоступности звукового устройства по умолчанию +Comment[sk]=Upozornenie na automatické použitie záložného zariadenia, ak je preferované zariadenie nedostupné +Comment[sl]=Obvestilo ob samodejni izbiri zasilne naprave, če prednostna naprava ni na voljo +Comment[sr]=Обавештење о аутоматском спадању ако жељени уређај није доступан +Comment[sr@ijekavian]=Обавјештење о аутоматском спадању ако жељени уређај није доступан +Comment[sr@ijekavianlatin]=Obavještenje o automatskom spadanju ako željeni uređaj nije dostupan +Comment[sr@latin]=Obaveštenje o automatskom spadanju ako željeni uređaj nije dostupan +Comment[sv]=Underrättelse vid automatisk återgång om önskad enhet inte är tillgänglig +Comment[tr]=Tercih edilen aygıt erişilemez ise otomatik olarak yeni aygıta geçme bildirimi +Comment[ug]=مايىللىقتىكى ئۈسكۈنەنى ئىشلەتكىلى بولمىغاندا ئۆزلۈكىدىن تۆۋەنلىتىش ئۇقتۇرۇشى +Comment[uk]=Сповіщення про автоматичне повернення до типового пристрою, якщо бажаний пристрій недоступний +Comment[x-test]=xxNotification on automatic fallback if the preferred device is unavailablexx +Comment[zh_CN]=如果首选设备不可用,在自动使用备用设备时进行通知 +Comment[zh_TW]=若是選用的裝置無法使用時,通知會自動使用預設的裝置 +Contexts=Application +Action=Popup diff --git a/kmix/kmix_autostart.desktop b/kmix/kmix_autostart.desktop new file mode 100755 index 00000000..0db27fff --- /dev/null +++ b/kmix/kmix_autostart.desktop @@ -0,0 +1,115 @@ +[Desktop Entry] +Exec=kmix +DocPath=kmix/index.html +OnlyShowIn=KDE; +Type=Application +MimeType= +Terminal=false +X-KDE-autostart-after=panel +X-KDE-StartupNotify=false +X-DBUS-StartupType=Unique +X-KDE-autostart-condition=kmixrc:Global:AutoStart:true +Icon=kmix +GenericName=Sound Mixer +GenericName[bg]=Звуков смесител +GenericName[bs]=Mikser zvuka +GenericName[ca]=Mesclador de so +GenericName[ca@valencia]=Mesclador de so +GenericName[cs]=Zvukový směšovač +GenericName[da]=Lydmikser +GenericName[de]=Lautstärkeregler +GenericName[el]=Μείκτης ήχου +GenericName[en_GB]=Sound Mixer +GenericName[es]=Mezclador de sonido +GenericName[et]=Helimikser +GenericName[fi]=Äänimikseri +GenericName[fr]=Console de mixage +GenericName[ga]=Meascthóir Fuaime +GenericName[gl]=Mesturador de son +GenericName[hu]=Hangkeverő +GenericName[ia]=Miscitor de sono +GenericName[id]=Mixer Suara +GenericName[is]=Hljóðblöndun +GenericName[it]=Mixer audio +GenericName[ja]=サウンドミキサー +GenericName[kk]=Дыбыс микшері +GenericName[km]=កម្មវិធី​លាយ​សំលេង +GenericName[ko]=소리 믹서 +GenericName[lt]=Garsų maišiklis +GenericName[mr]=आवाज मिक्सर +GenericName[nb]=Lydmikser +GenericName[nds]=Klangmischer +GenericName[nl]=Geluidsmixer +GenericName[nn]=Lydmiksar +GenericName[pa]=ਸਾਊਡ ਮਿਕਸਰ +GenericName[pl]=Mikser dźwięku +GenericName[pt]=Mistura de Áudio +GenericName[pt_BR]=Mixer de som +GenericName[ro]=Mixer de sunet +GenericName[ru]=Звуковой микшер +GenericName[se]=Jietnamixer +GenericName[sk]=Zvukový mixér +GenericName[sl]=Mešalnik zvoka +GenericName[sr]=Звучна миксета +GenericName[sr@ijekavian]=Звучна миксета +GenericName[sr@ijekavianlatin]=Zvučna mikseta +GenericName[sr@latin]=Zvučna mikseta +GenericName[sv]=Ljudmixer +GenericName[tr]=Ses Karıştırıcı +GenericName[ug]=ئاۋاز كىرىشتۈرگۈچى +GenericName[uk]=Аудіомікшер +GenericName[x-test]=xxSound Mixerxx +GenericName[zh_CN]=混音器 +GenericName[zh_TW]=音效混音器 +Name=KMix +Name[bg]=KMix +Name[bs]=KMix +Name[ca]=KMix +Name[ca@valencia]=KMix +Name[cs]=KMix +Name[da]=KMix +Name[de]=KMix +Name[el]=KMix +Name[en_GB]=KMix +Name[es]=KMix +Name[et]=KMix +Name[fi]=KMix +Name[fr]=KMix +Name[ga]=KMix +Name[gl]=KMix +Name[hu]=KMix +Name[ia]=KMix +Name[id]=KMix +Name[is]=KMix +Name[it]=KMix +Name[ja]=KMix +Name[kk]=KMix +Name[km]=KMix +Name[ko]=KMix +Name[lt]=KMix +Name[mr]=के-मिक्स +Name[nb]=KMix +Name[nds]=KMix +Name[nl]=KMix +Name[nn]=KMix +Name[pa]=ਕੇਮਿਕਸ +Name[pl]=KMix +Name[pt]=KMix +Name[pt_BR]=KMix +Name[ro]=KMix +Name[ru]=KMix +Name[se]=KMix +Name[sk]=KMix +Name[sl]=KMix +Name[sr]=К‑миксета +Name[sr@ijekavian]=К‑миксета +Name[sr@ijekavianlatin]=K‑mikseta +Name[sr@latin]=K‑mikseta +Name[sv]=Kmix +Name[tr]=KMix +Name[ug]=KMix +Name[uk]=KMix +Name[x-test]=xxKMixxx +Name[zh_CN]=KMix +Name[zh_TW]=音效調整_KMix +Categories=Qt;KDE;AudioVideo;Audio;Mixer; diff --git a/kmix/kmixctrl_restore.desktop b/kmix/kmixctrl_restore.desktop new file mode 100644 index 00000000..2f70b788 --- /dev/null +++ b/kmix/kmixctrl_restore.desktop @@ -0,0 +1,54 @@ +[Desktop Entry] +Type=Service +Name=Restore Mixer Settings +Name[bg]=Възстановяване настройките на смесителя +Name[bs]=Obnovi postavke miksete +Name[ca]=Restaura l'arranjament del mesclador +Name[ca@valencia]=Restaura l'arranjament del mesclador +Name[cs]=Obnovit nastavení směšovače +Name[da]=Genskab mikserindstillinger +Name[de]=Lautstärkeeinstellungen wiederherstellen +Name[el]=Επαναφορά ρυθμίσεων μείκτη +Name[en_GB]=Restore Mixer Settings +Name[es]=Restaurar las preferencias del mezclador +Name[et]=Mikseri seadistuste taastamine +Name[fi]=Palauta mikserin asetukset +Name[fr]=Restaurer la configuration du mixage +Name[ga]=Athchóirigh Socruithe an Mheascthóra +Name[gl]=Restaurar os parámetros do mesturador +Name[hu]=Keverőbeállítások visszaállítása +Name[ia]=Restabili preferentias de Mixer +Name[id]=Kembalikan Pengaturan Mixer +Name[is]=Frumstilla hljóðblöndunarstillingar +Name[it]=Ripristina le impostazioni del mixer +Name[ja]=ミキサーの設定を復元 +Name[kk]=Микшер баптауларын қалпына келтіру +Name[km]=ស្ដារ​ការ​កំណត់​របស់​កម្មវិធី​លាយ​ឡើងវិញ +Name[ko]=믹서 설정 복원 +Name[lt]=Atstatyti maišytuvo nustatymus +Name[mr]=मिक्सर संयोजना पुन्हस्थापित करा +Name[nb]=Gjennopprett mikserinnstillinger +Name[nds]=Mischerinstellen wedderherstellen +Name[nl]=Mixerinstellingen herstellen +Name[nn]=Gjenopprett miksarinnstillingar +Name[pa]=ਮਿਕਸਰ ਸੈਟਿੰਗ ਮੁੜ-ਸਟੋਰ ਕਰੋ +Name[pl]=Przywróć ustawienia miksera +Name[pt]=Repor a Configuração do Volume +Name[pt_BR]=Restaurar configurações do Mixer +Name[ro]=Restabilește configurările mixerului +Name[ru]=Восстановление параметров микшера +Name[se]=Máhcat mixerheivehusat +Name[sk]=Obnoviť nastavenie mixéra +Name[sl]=Obnovi nastavitve mešalnika +Name[sr]=Поврати поставке миксете +Name[sr@ijekavian]=Поврати поставке миксете +Name[sr@ijekavianlatin]=Povrati postavke miksete +Name[sr@latin]=Povrati postavke miksete +Name[sv]=Återställ mixerinställningar +Name[tr]=Karıştırıcı Ayarlarını Yeniden Yükle +Name[ug]=سازلىغۇچ تەڭشەكلىرىنى ئەسلىگە كەلتۈر +Name[uk]=Відновити параметри мікшера +Name[x-test]=xxRestore Mixer Settingsxx +Name[zh_CN]=还原混音器设置 +Name[zh_TW]=回復混音器設定 +Exec=kmixctrl --restore diff --git a/kmix/kmixd.desktop b/kmix/kmixd.desktop new file mode 100644 index 00000000..da3d6609 --- /dev/null +++ b/kmix/kmixd.desktop @@ -0,0 +1,111 @@ +[Desktop Entry] +Type=Service +Icon=preferences-desktop-keyboard +Name=KMixD +Name[bg]=KMixD +Name[bs]=KMixD +Name[ca]=KMixD +Name[ca@valencia]=KMixD +Name[cs]=KMixD +Name[da]=KMixD +Name[de]=KMixD +Name[el]=KMixD +Name[en_GB]=KMixD +Name[es]=KMixD +Name[et]=KMixD +Name[fi]=KMixD +Name[fr]=KMixD +Name[ga]=KMixD +Name[gl]=KMixD +Name[hu]=KMixD +Name[ia]=KMixD +Name[id]=KMixD +Name[is]=KMixD +Name[it]=KMixD +Name[ja]=KMixD +Name[kk]=KMixD +Name[km]=KMixD +Name[ko]=KMixD +Name[lt]=KMixD +Name[mr]=के-मिक्स-डि +Name[nb]=KMixD +Name[nds]=KMixD +Name[nl]=KMixD +Name[pa]=KMixD +Name[pl]=KMixD +Name[pt]=KMixD +Name[pt_BR]=KMixD +Name[ro]=KMixD +Name[ru]=KMixD +Name[sk]=KMixD +Name[sl]=KMixD +Name[sr]=К‑миксета‑д +Name[sr@ijekavian]=К‑миксета‑д +Name[sr@ijekavianlatin]=K‑mikseta‑d +Name[sr@latin]=K‑mikseta‑d +Name[sv]=Kmixd +Name[tr]=KMixD +Name[ug]=KMixD +Name[uk]=KMixD +Name[x-test]=xxKMixDxx +Name[zh_CN]=KMixD +Name[zh_TW]=KMix 伺服程式 +Comment=KMixD Mixer Service +Comment[bg]=Услуга за смесител KMixD +Comment[bs]=KMixD Mikser servis +Comment[ca]=Servei de mesclador KMixD +Comment[ca@valencia]=Servei de mesclador KMixD +Comment[cs]=KMixD služba směšovače +Comment[da]=KMixD miksertjeneste +Comment[de]=KMixD-Lautstärkeregelungsdienst +Comment[el]=Υπηρεσία μείκτη KMixD +Comment[en_GB]=KMixD Mixer Service +Comment[es]=Servicio de mezclador KMixD +Comment[et]=KMixD mikseriteenus +Comment[fi]=KMixD-mikseripalvelu +Comment[fr]=Service de mixage « KMixd » +Comment[ga]=Seirbhís Meascóra KMixD +Comment[gl]=Servizo mesturador KMixD +Comment[hu]=KMixD keverőszolgáltatás +Comment[ia]=Servicio de Miscer de KMixD +Comment[id]=Layanan Mixer KMixD +Comment[is]=KMixD hljóðblöndunarþjónusta +Comment[it]=Servizio mixer KMixD +Comment[kk]=KMixD микшер қызметі +Comment[km]=សេវាកម្ម​នៃ​កម្មវិធី​លាយ KMixD +Comment[ko]=KMixD 믹서 서비스 +Comment[lt]=KMixD maišytuvo tarnyba +Comment[mr]=के-मिक्स-डि मिक्सर सेवा +Comment[nb]=KMixD miksertjeneste +Comment[nds]=KMixD-Mischer +Comment[nl]=KMixD mixerservice +Comment[pa]=KMixD ਮਿਕਸਰ ਸਰਵਿਸ +Comment[pl]=Usługa miksera KMixD +Comment[pt]=Serviço de Mistura KMixD +Comment[pt_BR]=Serviço de Mixer KMixD +Comment[ro]=Serviciu de mixer KMixD +Comment[ru]=Служба микшера KMixD +Comment[sk]=Služba mixéra KMixD +Comment[sl]=Storitev mešalnika KMixD +Comment[sr]=Миксетни сервис К‑миксете +Comment[sr@ijekavian]=Миксетни сервис К‑миксете +Comment[sr@ijekavianlatin]=Miksetni servis K‑miksete +Comment[sr@latin]=Miksetni servis K‑miksete +Comment[sv]=Kmixd mixertjänst +Comment[tr]=KMixD Karıştırıcı Servisi +Comment[ug]=KMixD سازلىغۇچ مۇلازىمىتى +Comment[uk]=Служба мікшування KMixD +Comment[x-test]=xxKMixD Mixer Servicexx +Comment[zh_CN]=KMixD 混音器服务 +Comment[zh_TW]=KMixD 混音器服務 +#X-KDE-autostart-phase=1 +#X-DBUS-StartupType=Unique +#X-KDE-ServiceTypes= +#X-KDE-StartupNotify=false + +# KDED registration +X-KDE-ServiceTypes=KDEDModule +X-KDE-Library=kmixd +X-KDE-DBus-ModuleName=kmixd +X-KDE-Kded-autoload=true +OnlyShowIn=KDE; diff --git a/kmix/kmixui.rc b/kmix/kmixui.rc new file mode 100644 index 00000000..ffb220f6 --- /dev/null +++ b/kmix/kmixui.rc @@ -0,0 +1,26 @@ + + + + + + + + + +

&File + + + + + + + + + + + &Help + + + + + diff --git a/kmix/l10n/README b/kmix/l10n/README new file mode 100644 index 00000000..ecd1dcbc --- /dev/null +++ b/kmix/l10n/README @@ -0,0 +1,11 @@ +Contains localizations for the WhatsThis help for the soundcard controls. + +This is not yet integrated into the KDE build / install. + +For testing, you can install it like this: + +loc=de; m=kmix-controls-$loc ; msgfmt -o ${m}.mo ${m}.po +cp $m.mo /usr/share/locale/$loc/LC_MESSAGES/kmix-controls.mo + +You can also do it with loc=en + diff --git a/kmix/l10n/kmix-controls-de.po b/kmix/l10n/kmix-controls-de.po new file mode 100644 index 00000000..a297a28f --- /dev/null +++ b/kmix/l10n/kmix-controls-de.po @@ -0,0 +1,35 @@ +# Translation of kmix-controls-de.po, containing +# WhatsThis in German for the Soundcard Controls +# Copyright (C)2009 This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Christian Esken , 2009. +msgid "" +msgstr "" +"Project-Id-Version: kmix-controls-de\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2009-08-18 19:09+0200\n" +"PO-Revision-Date: 2009-08-18 21:52+0200\n" +"Last-Translator: Christian Esken \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" + +#: Master:1 +msgid "Master" +msgstr "Reguliert die Lautstärke der Front-Lautsprecher oder aller Lautsprecher (abhängig vom Sounkarten-Modell). Im Fall der digitalen Ausgabe müssen Sie zumeist auch andere Regler wie ADC or DAC einstellen. Bei Kopfhörern existiert meist ein Headphone/Kopfhörer Regler." + +#: PCM:1 +msgid "PCM" +msgstr "Reguliert die Lautstärke für die meisten Medien. Beispielsweise werden MP3 oder Videos über PCM wiedergegeben." + +#: Mic:1 +msgid "Mic" +msgstr "Aufnahmestärke des Mikrophoneingangs" + +#: Headphone:1 +msgid "Headphone" +msgstr "Einstellen der Kopfhörer-Lautstärke. Bei manchen Soundkarten gibt es einen Schalter, mit dem der Kopfhörerausgang manuell angeschaltet werden muss." + diff --git a/kmix/l10n/kmix-controls-en.po b/kmix/l10n/kmix-controls-en.po new file mode 100644 index 00000000..bd7974ee --- /dev/null +++ b/kmix/l10n/kmix-controls-en.po @@ -0,0 +1,116 @@ +# translation of kmix-controls-en.po to +# Translation of kmix-controls-en.po, containing +# WhatsThis in English for the Soundcard Controls +# Copyright (C)2009 This_file_is_part_of_KDE +# This file is distributed under the same license as the kmix package. +# +# Christian Esken , 2009, 2010. +msgid "" +msgstr "" +"Project-Id-Version: kmix-controls-en\n" +"Report-Msgid-Bugs-To: http://bugs.kde.org\n" +"POT-Creation-Date: 2009-08-18 19:09+0200\n" +"PO-Revision-Date: 2010-06-29 20:55+0200\n" +"Last-Translator: \n" +"Language-Team: German \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.0\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#: Master:1 +msgid "Master" +msgstr "Controls the volume of the front speakers or all speakers (depending on your soundcard model). If you use a digital output, you might need to also use other controls like ADC or DAC. For headphones soundcards often supply a Headphone control." + +#: PCM:1 +msgid "PCM" +msgstr "Most media like MP3 or Videos are played back using PCM. For playControls the volume of the front speakers or all speakers (depending on your soundcard model). If you use a digital output, you might need to also use other controls like ADC or DAC." + +#: Mic:1 +msgid "Mic" +msgstr "Recording level of the microphone input." + +#: Mic_Boost:1 +msgid "Mic_Boost" +msgstr "Volume level boost of the microphone input." + +#: Front_Mic:1 +msgid "Front_Mic" +msgstr "Recording level of the front microphone input." + +#: Front_Mic_Boost:1 +msgid "Front_Mic_Boost" +msgstr "Volume level boost of the front microphone input." + +#: Headphone:1 +msgid "Headphone" +msgstr "Controls the headphone volume. Some soundcards include a switch that must be manually activated to enable the headphone output." + +#: Beep:1 +msgid "Beep" +msgstr "Controls the built in speaker volume." + +#: Capture:1 +msgid "Capture" +msgstr "" +"This is the overall volume for recording (or capturing). When recording please also look for other controls that can influence the result, like a capture switch or specific capture volumes. For recording usual audio files, many soundcards have a PCM " +"capture volume, and S/PDIF or IEC958 for capturing digital Inputs." + +#: Front:1 +msgid "Front" +msgstr "Controls the volume of the Front speakers." + +#: Center:1 +msgid "Center" +msgstr "Controls the volume of the Center speaker." + +#: LFE:1 +msgid "LFE" +msgstr "Controls the volume of the LFE speaker." + +#: Surround:1 +msgid "Surround" +msgstr "Controls the volume of the Surround (rear) speakers." + +#: Side:1 +msgid "Side" +msgstr "Controls the volume of the Side speakers." + +#: Center/LFE:1 +msgid "Center/LFE" +msgstr "Controls the volume of the Center and LFE speakers. KMix allows splitting the control for soundcards that support to separately control the Center and LFE volumes." + +#: Digital:1 +msgid "Digital" +msgstr "Digital input or output." + +#: IEC958:1 +msgid "IEC958" +msgstr "Digital input or output." + +#: S/PDIF-in:1 +msgid "S/PDIF-in" +msgstr "Controls the volume of the digital input." + +#: S/PDIF-out:1 +msgid "S/PDIF-out" +msgstr "Controls the volume of the digital output." + +#: IEC958_Default_PCM:1, unknown meaning +msgid "IEC958_Default_PCM" +msgstr "" + +#: Input_Source:1 +msgid "Input_Source" +msgstr "Allows the selction of the input source. The sources are soundcard specific." + +#: Line:1 +msgid "Line" +msgstr "Controls the volume of the line output" + +#: Line-in:1 +msgid "Line-in" +msgstr "Controls the volume of the line input" + + diff --git a/kmix/pics/CMakeLists.txt b/kmix/pics/CMakeLists.txt new file mode 100644 index 00000000..e66960da --- /dev/null +++ b/kmix/pics/CMakeLists.txt @@ -0,0 +1,28 @@ + + + + +########### install files ############### + +install( FILES + mixer-pcm.png + mixer-lfe.png + mixer-cd.png + mixer-line.png + mixer-microphone.png + mixer-midi.png + mixer-capture.png + mixer-pcm-default.png + mixer-front.png + mixer-master.png + mixer-surround.png + mixer-video.png + mixer-headset.png + mixer-digital.png + mixer-ac97.png + kmixdocked.png + kmixdocked_mute.png + kmixdocked_error.png + DESTINATION ${DATA_INSTALL_DIR}/kmix/pics ) + +kde4_install_icons( ${ICON_INSTALL_DIR} ) diff --git a/kmix/pics/hi128-app-kmix.png b/kmix/pics/hi128-app-kmix.png new file mode 100644 index 0000000000000000000000000000000000000000..f42d0b5818e02090122139681901e0820a10afe1 GIT binary patch literal 10735 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 literal 0 HcmV?d00001 diff --git a/kmix/pics/hi16-app-kmix.png b/kmix/pics/hi16-app-kmix.png new file mode 100644 index 0000000000000000000000000000000000000000..6f28ee122421893c82684d58c4c3dd027217f50b GIT binary patch literal 906 zcmV;519kj~P)UK>u(K=j-`B|K}VI zA_56u8kplLvZToIGdX43Y=3fB+2W3JpC7nKijQehPhwLjstA}N#%1LJ+d1!KZzOrthW*Nb@F9{cl_)pMcr~JOS+DzbXM9rGnE0xWj4t z*PuO802n1dVz<2m90nlI_5l$boMi_FD*FJ30t%q=813qJ?6HLlfExRIfCn9T%Y=N( zIKL@3d7%7+D0kU1S3V?7o9e}`V(F+-XORn zAJNG-yvrX1O0H9;?=Lt}_7?y<0YB{+`ldOK12r0IdAY58SDCHM0R?Fe?FcV3&r7y1 zton6EI=}ztcixA_xk$!q@$xl+S=Nrr>95qpm;nk$z)Qdrlw?ec2=_4tbDn6>#w6n zo_wX~U}R(@76=5a;LgrY866#!xw$#(@bdDqEG;cr^M!>4SzTR~_4Qv&hp*oPc3Xhv z=H_$#{rwi8x3^ae!;rzjK?w$f($&=^p-@OBCMINYaZ%RR)?{O2!*tp6J#f$hR8>`- zY;A4L2U)e(*VjvRb+x$NZZY@7U3{ z3s{a~v6!UO7fjGu(~&2uX|{3T7~m{k;{RlKxm<78*4F-d;)HjrrKM%-%$e8LU9Lmd gfCiupDE{Yv0U_#Vw}D;4v;Y7A07*qoM6N<$g3{Kj;s5{u literal 0 HcmV?d00001 diff --git a/kmix/pics/hi32-app-kmix.png b/kmix/pics/hi32-app-kmix.png new file mode 100644 index 0000000000000000000000000000000000000000..37decff210c2054f30b5b3b1a28fa72342948228 GIT binary patch literal 1865 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 Hu0mjfxA`>4 literal 0 HcmV?d00001 diff --git a/kmix/pics/kmixdocked_error.png b/kmix/pics/kmixdocked_error.png new file mode 100644 index 0000000000000000000000000000000000000000..841ca1592c555c56e7bd52d1edb426ef6c598f80 GIT binary patch literal 1165 zcmV;81akX{P)Xl^z=44qXkwy{$q56wBXl=K zxq}0aP!ZTj!U8umsKkN}uv%=X8RZ;d>!2-Wb2?rNxKGDDQ^%kw_T9dF{{KDC|MUOt zyYC~+Mw&D>;>w1Df*kyN3%3N`(KbjC?~<*7*w-V_O%eFMms==?2EDon#nK znV%VYP^$}7)qZNJ>Fg#VDuyjvwh$j5kMaCPDsJ|$zp(U|X9CA-Pv13u)5E%;5Z0_& zLs(cCv9Yo2-Mg3a##Rp2o7i{EkTfgkV#PkQ{yQ`89W~(P<%Pe$KOrF@#KgpqoScj% z_bA7kZ<2KUDI@Vr%F(x2WV$iSIUxu=*|LDvq$>qg&{@MmSX2Xp5k~zn_VT2>_a#n@LGYL8H;2GnTQV z@+$9S>t}AsbINqH%i&LWrT#YgJ=kpgd4z2T)KpbfF*G#9*w`2kRZ>zyZf-6*osM^_ zOeiw-oil=8&ADL7xb_bxeuc6>p!9dhyFS4Bx8vyT?Pa9C9?Qv-SPBcV6csV9*YkH? z9``gF>$HWuamY9@4XVsGB!!_NGCbHWs`N5TQMi!zgy6|HQj{hpAzYiw=!i7yk=M4(m+ zDixeN2RS*Aoeim}pwo%#gap{WohD!3;THrU>TzF{yOQiSDv=$l=J}FKEdAhfR#vrR zt%EpaY%ExFwW+-w;^M@M1_$BJ9f*vCP2uz`b0=I7TBl4cS@F8XY5!4Vnlfa0H7qtX zF#Pf=P%0rfSbSc-j7<#-#SbnnyeSB#Dd0J;tuxyPDUj{XLU!0_ z^UzDn!P^_=%>y4F@brZ0YHS)90EGhF+(0e|2M0PF94EnFPyIoHHib&$?iOLuQY7xH zaonL|&{Ynut{@1amo0c^0H}Re5W=EL_{x7{49O+^2?Ke8)JQwE75f83i z3w?d?;DMOER*Ov)72xP-`=(LmOt3AAC+JV9{R9$;2%e0@WP+fehZBm8(d)t9-nPGF z1qTX3%M-!Y>V(%y9W0MPB4C;=R(Duqh_aL3mI}gd;c2wUb!l?Fom7O(=*Lh&gnA24 zt3^J3#Rf+wnq+giD080TOHR&2+evRp9z*Sf*+{-nzAs?mlDmHMmyG)_kdFq;ThKSz ffQ@3NQ=tC>xt#Ns|B7$S00000NkvXXu0mjfg9#)p literal 0 HcmV?d00001 diff --git a/kmix/pics/kmixdocked_mute.png b/kmix/pics/kmixdocked_mute.png new file mode 100644 index 0000000000000000000000000000000000000000..82184af76754422fe825635bcbefe3e706ea8bfd GIT binary patch literal 598 zcmV-c0;&CpP)kbKv)=p;8k$!CI%6VKoHNn2llX?IIZ{R;=se};_$WoyZcHcJ5n4jgyZEW zs?Y&-%$Oy`9J56960L&!Tu+T9+G*uH<190nF6-3M#ux!QB)vyKF)^pB-cWEb#7E-T`N1wo`iK}kaWmGv43ZOD!OwGk zvm{#XOR!(6Ih zHRvbFE3qUqcqQH9JKu5yoxCb^1P9C4E32A);tv6U>mNn^We~ z*X0AkQw~x|Gf+)8b;T`iV@q#AYiiO%ld-MxvO!JmB z3oNsOj~3lZWsL=ulz^Yek!ONtPO4Tab1Mx-P+)OsrZ_{(hJ~6trtUY~^x(rmljRpI k9aj!z4-^mTuD`DE2a&3zpKx_JfdBvi07*qoM6N<$f@g~clmGw# literal 0 HcmV?d00001 diff --git a/kmix/pics/mixer-ac97.png b/kmix/pics/mixer-ac97.png new file mode 100644 index 0000000000000000000000000000000000000000..b3e2db083b48c3acf94d001394b26b82b084702f GIT binary patch literal 453 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqJQaoCO|{#S9GGLLkg|>2BR0pdfpR zr>`sfB_2K=Va+*SYjuD^IRQQ)u0Wc$!2bU`1_q!D{sXN!tuP5lDwG8I1v4=735tkH z$jB=xtEj4}YkK)cM8_njWoG5%7FV?OPx^M*;3!ZnW0JSK3quF1tOt;@+0(@_q~ca- zUpL<&0}j{I8?0Vu2R(oFTkHS-{zqrjLE0TK9R=+|kIDT)T<{vGkRi-;%hX}8H zHfNH_)YC~#Z}kNYFKg`1P-Q!w`g5Aqgt{j?--MKB?l@+0;<1)i&u?a1vHqh@OxwjQ zwoK1vRB5qeEx5(-`)d)S%w~NyFP2>ybD#VQzO`9Rz33!E%)9#sSBQNSo!QKMX1T`9 x#-z)BtO~i=ufl8k%#Ed-`lqS;$XVGiuKy;wcs`%RETHolJYD@<);T3K0RXDhe!Ktx literal 0 HcmV?d00001 diff --git a/kmix/pics/mixer-capture.png b/kmix/pics/mixer-capture.png new file mode 100644 index 0000000000000000000000000000000000000000..800e6dbcafe79fd3243d0148a49f1793df91afae GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqJQaJOMr-u0R?H&Ye5=pMfDkWwQa0 z!&nmJ7tG-B>_!@hljQC0!qCAg>jC8Wdb&7&SJ$fQR|uiGTm+pPqC$NTiuh za@w~&`Drr*3Z`d@AAYmSWtC6;-*Y}TefE8WsHykapv_lww$`tV%$hb!b+z^R z-wgMcGb*oOmM#Js>|Ej+Q4*Y=R#Ki=l*-_sp{(hdo0y*Jo0y)NoULG@XQXGYU}>pu zV6JaqsE}NgSzJ=-kXu<7-*ap*tpE}yu^}Hpm`=~ y25FXt$tLE8mIld5smUgW#z_{5=BWmzmd57hh9-sSFKd9h7(8A5T-G@yGywp~zGox= literal 0 HcmV?d00001 diff --git a/kmix/pics/mixer-cd.png b/kmix/pics/mixer-cd.png new file mode 100644 index 0000000000000000000000000000000000000000..0cf143c5e341bce75ff2cf9d05fcea462ee1754a GIT binary patch literal 358 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqJQaq5(c3u0R?H{x>lEUlH;D&Wiv4 z5B&fCKX3h>SfD6lNswPKgTu2MX&_FLx4R2N2dk_Hkdy1_;uunKYwCrIT+D_XE*J03 z-T43i$*VI|H%)PRo@pUCWlvq}smVE!2D;NC#gE^+SS72vDAc*||Ej+Q4*Y=R#Ki=l*-_sp{(hdo0y*Jo0y)NoULG@XQXGYU}>puV6JaqsE}Ng zSzJ=-kXu<7-*ap*tpE}yu^}Hpm}L2mS#o<$tFpr qNyf=028O1l$p+@h28k)DW=ZC$MrQvi96theF?hQAxvX{XE z)7O>#5)U7b80W3v%7s87wg8_HS0D`pOVf9r2U3hBL4Lsu4$p3+fjCLt?k)@+tg;?J zj;^PRV+hCf25MyRboFyt=akR{0B6!Im;e9( literal 0 HcmV?d00001 diff --git a/kmix/pics/mixer-front.png b/kmix/pics/mixer-front.png new file mode 100644 index 0000000000000000000000000000000000000000..fb85333ef43347bac10df80601c47f43b51de549 GIT binary patch literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqJQaoB=)|u0R?D{xb-;i);ci7)yfu zf*Bm1-ADs*lDyqr7&=&GJ%Ai{PZ!6Kid&_p7`Ye}cn%-9`rkgvd`94^)<(I`^IOXP zp3Vzh`0B+k-n7JN>CaX$9y8P4EUmtcbIIZ(8irmyLW^@Y3oV|aJ8yPc4a4luekPxR zMmm?cMwA5Sr2BR0pdfpR zr>`sfB_2K=Y36>7cb|Ylq5(c3u0WcB;Xi{t7=bxJ_J^x4UI5vwB|(0{K$=mHeVG-= zWX2?KcNc~ZR#^`qC&kmnF{I*F>Ul?@1_KV}153RA|4(5)d_?P|>YD=#E1Z*Bf)^W| z&6*v>>^UcDYTomt@MNLg(y2vt?@v$2x_zi-<;LoKcM&h=WYB=a?_mGUZ1fHrfTWkHOQ`&t;ucLK6TZB2+K{ literal 0 HcmV?d00001 diff --git a/kmix/pics/mixer-lfe.png b/kmix/pics/mixer-lfe.png new file mode 100644 index 0000000000000000000000000000000000000000..00708bc3e00070dad51184a63515b3dc4695fba6 GIT binary patch literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAf3?x51|2hvyaR&H=xB_V~_`fcZ%@@dGED7=p zW^j0RBMrn!@^*J&=wOxg0CLUg~!jKLnkKK&U4$U z@FZpig>ySue@nf{{cvUK(J3b<*}Pt{U^`>(l8*3&%`SK1S$G*3{^YU>I;Z4M)CL;q zT;dv05}cn_Ql40p%HW`(tm&DXn4apJn4X!Otze>Oq-U;RX{m2uu5VzdkX)2mTvF+f zTUlI^nXH#utd~++ke^qgmz_!@hljQC0!qCAg>jC7Ld%8G=RNR_-Y9l9u0nd>G)A#;A=PAq06jrIB z{EBtmN2#n8jgs8nS5B?*(oOpr{rY-}k|alDIYUJpL;EF7^#Y&~&Lyr9CBgY=CFO}l zsSFMp%9@_JiRr1niRqci*$O6lMtbH7mX`Vk=K2PP3du#8#U+&vxs}BwnaO&o#d;~F z1^Ia;ddc~@*-Q+FfyQZpjmu2WODrh`nwMsrXpn4XXp)?2Zena;X=s{gZfRs_Vw94W ZYGiDYVvtkHGasmn!PC{xWt~$(69B#mR@?vp literal 0 HcmV?d00001 diff --git a/kmix/pics/mixer-master.png b/kmix/pics/mixer-master.png new file mode 100644 index 0000000000000000000000000000000000000000..4c3b3c83f1c8e8ca506a99acc10d2470797c535a GIT binary patch literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqJQaoB=)|u0R?D{xb-;i);ci7)yfu zf*Bm1-ADs*lDyqr7&=&GJ%Ai%PZ!6Kid(&>thpEzcw7!1`7Iyy@vy*_f^OY5@%g7K zBXbi^U&@s^#mAS*J~NruDD}k@pOYZ8224z=)SdS_N0VV;WxDY#kZ~oh5hcO-X(i=} zMX3x98p@iUxrynizKQ9X$=M1fdPaKY3YM1o2Il$(h6>3=nZ+fQ4!M=ZC7H>3sl|FJ zr3LwUC3?yEx!Ft%hk?dvfsM;d&r2*R1)67RkeY02X_1s@Vwz-Wlw@FLZjx%8WMO8R aWNv6+WM*#vLvbEZ7lWs(pUXO@geCyF5nNON literal 0 HcmV?d00001 diff --git a/kmix/pics/mixer-microphone.png b/kmix/pics/mixer-microphone.png new file mode 100644 index 0000000000000000000000000000000000000000..3205e91b5c5780d035b8403baf85a6c670fb55ce GIT binary patch literal 321 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc0wmQNuC@UwrX+877lys^v2U1xyi!jW$B>F! zN6&5KZ7|?raacboRAaN7msi+=6qfZ9CW#s`scIWERldAzrPtB2c6ztH=EV2OcV?XI zlwowOWz^Wjr_se;did^yH$l6tL+`K_F7!y&d8nl468KZ&di$;a)7^SXwkKJp+-b1< zy1%DDOSz{#L#mol)+(Fj%eQN-KnFONxJHx&=ckpFCl;kLIA|zqdgdmkr}`$QXC`MW znCKblnJZXY>KmBr8yG4i7iAWgR668V7MElu>!lX!rIZ%r=auLs=jUcKF&qXOrv)}H zGd(Y{q!ehLfw756nwhzwk+G?1nuTeaiE)yVX-Z;hl97d_VX9f`>PeA~KwS);u6{1- HoD!MT^vI=WLXbxexl)FLj~X)7RlYRV0U zoP-zI)!f2QIQPB_brD#2eWDWE$qUczTx%pEg6|qVU}pHk6vE5cx@bZG&@|@~*NBqf z{Irtt#G+IN2MuLS&)mfHRNut(%;anZ6Fnn6a|KIFeFJlS14D)6qRirwN{8Ia;*!i{ zz0_j8l+uFyyb`_S{M>9NhQmPPw7|w?rspM=lmg95OH55RO*64HG&46!G%_(TO-V8| fOEEW3O|~#kNis-oHtco+>SFM8^>bP0l+XkKZ!uS_ literal 0 HcmV?d00001 diff --git a/kmix/pics/mixer-pcm-default.png b/kmix/pics/mixer-pcm-default.png new file mode 100644 index 0000000000000000000000000000000000000000..ac620fb98cee9a23925ada3889154669befcaaeb GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAf3?x51|2hvyaR&H=xB_V~_`fcZ%@@dGED7=p zW^j0RBMrn!@^*J&=wOxg0CK!MT^vI=WLXbx=cSEV9Abhp66o)@?qX6t57TIw5^>l+v!p+yCFkd6GcgSFM8^>bP0l+XkKZ=_%m literal 0 HcmV?d00001 diff --git a/kmix/pics/mixer-pcm.png b/kmix/pics/mixer-pcm.png new file mode 100644 index 0000000000000000000000000000000000000000..0fe6404db400219b3f92e1c0c12f4aa84ef50fce GIT binary patch literal 303 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqJQaoB=)|u0R?L{;x}9^98aPOM?7@ z862M7NCR<_yxm-L1;Gm(b>6x3Dp6Z*Jo|&AjV4`QFXRcsrsc&GeZ(yj9T$EW{Qt6OeSzMBt zte0A>mr`1gpI4%noS&P`#BdmBoEF%)%=Em(l2V{~hN-EBMh0f4X=&yr=EjMsCYDCY gCTSK%sYa%$mS*M_yz`#S0P14!boFyt=akR{0AyBN2LJ#7 literal 0 HcmV?d00001 diff --git a/kmix/pics/mixer-surround.png b/kmix/pics/mixer-surround.png new file mode 100644 index 0000000000000000000000000000000000000000..797d44726982c47b305ddb1207defc45472e0a6d GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqJQaEa{HEjtmSN`?>!lvI6-+0X`wF zK$_t{LvC*F|NsAiOwk)|&)b~hfGn1hAirRc?B>a7vw&2nr;B4q#jU+Pfm{a+IGoq2 zec%5{Nl*7p@`m}gt;-LZ)wTA%<#CeE>-Jf~a5eXSSZKp5NtsC<=@#{KFEni4*EZ#n rqTr(Pg_U#aw30k?H<$6Ai;>^SFg=Wku{bGr1IQvzS3j3^P6{8hVUz8JpBEh^W6n9xGCJ))@l5dSnTpKi{rZ#N zeD78eNWWJb`G5MSD+T7YveUA2XRb@%ab=?L*$)0-hAqpK(i>O(F$Y?};OXk;vd$@? F2>_tAHM;-+ literal 0 HcmV?d00001 diff --git a/kmix/plasma/CMakeLists.txt b/kmix/plasma/CMakeLists.txt new file mode 100644 index 00000000..5e1dc90c --- /dev/null +++ b/kmix/plasma/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory( engine ) diff --git a/kmix/plasma/engine/CMakeLists.txt b/kmix/plasma/engine/CMakeLists.txt new file mode 100644 index 00000000..198ef2b0 --- /dev/null +++ b/kmix/plasma/engine/CMakeLists.txt @@ -0,0 +1,22 @@ +include_directories( ../../ ) + +set(mixer_engine_SRCS + mixerengine.cpp + mixerservice.cpp +) + +qt4_add_dbus_interface(mixer_engine_SRCS ../../dbus/org.kde.kmix.mixset.xml + mixset_interface) +qt4_add_dbus_interface(mixer_engine_SRCS ../../dbus/org.kde.kmix.mixer.xml + mixer_interface) +qt4_add_dbus_interface(mixer_engine_SRCS ../../dbus/org.kde.kmix.control.xml + control_interface) + +kde4_add_plugin(plasma_engine_mixer ${mixer_engine_SRCS}) +target_link_libraries(plasma_engine_mixer ${KDE4_KDECORE_LIBS} + ${KDE4_PLASMA_LIBS}) + +install(TARGETS plasma_engine_mixer DESTINATION ${PLUGIN_INSTALL_DIR}) +install(FILES plasma-engine-mixer.desktop DESTINATION ${SERVICES_INSTALL_DIR}) +install(FILES mixer.operations DESTINATION + ${DATA_INSTALL_DIR}/plasma/services) diff --git a/kmix/plasma/engine/mixer.operations b/kmix/plasma/engine/mixer.operations new file mode 100644 index 00000000..0d228b02 --- /dev/null +++ b/kmix/plasma/engine/mixer.operations @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff --git a/kmix/plasma/engine/mixerengine.cpp b/kmix/plasma/engine/mixerengine.cpp new file mode 100644 index 00000000..22a35ab9 --- /dev/null +++ b/kmix/plasma/engine/mixerengine.cpp @@ -0,0 +1,410 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2011 Igor Poboiko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "mixerengine.h" +#include "mixset_interface.h" +#include "mixer_interface.h" +#include "control_interface.h" +#include "mixerservice.h" +#include +#include + + +const QString MixerEngine::KMIX_DBUS_SERVICE = "org.kde.kmix"; +const QString MixerEngine::KMIX_DBUS_PATH = "/Mixers"; + +MixerEngine::MixerEngine(QObject *parent, const QVariantList &args) + : Plasma::DataEngine(parent, args) + , m_kmix(0) +{ + Q_UNUSED(args) + + interface = QDBusConnection::sessionBus().interface(); + watcher = new QDBusServiceWatcher( this ); + watcher->addWatchedService( KMIX_DBUS_SERVICE ); + watcher->setConnection( QDBusConnection::sessionBus() ); + watcher->setWatchMode( QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration ); + connect( watcher, SIGNAL(serviceRegistered(QString)), + this, SLOT(slotServiceRegistered(QString)) ); + connect( watcher, SIGNAL(serviceUnregistered(QString)), + this, SLOT(slotServiceUnregistered(QString)) ); +} + +MixerEngine::~MixerEngine() +{ + // Cleanup + delete watcher; + // it is bad idea to call removeSource() here + clearInternalData(false); + delete m_kmix; +} + +QStringList MixerEngine::sources() const +{ + QStringList sources; + sources << "Mixers"; + return sources; +} + +void MixerEngine::init() +{ + getInternalData(); +} + +MixerInfo* MixerEngine::createMixerInfo( const QString& dbusPath ) +{ + MixerInfo* curmi = new MixerInfo; + curmi->iface = new OrgKdeKMixMixerInterface( KMIX_DBUS_SERVICE, dbusPath, + QDBusConnection::sessionBus(), this ); + curmi->id = curmi->iface->id(); + curmi->dbusPath = dbusPath; + curmi->updateRequired = false; + curmi->unused = false; + curmi->connected = false; + QDBusConnection::sessionBus().connect( KMIX_DBUS_SERVICE, dbusPath, + "org.kde.KMix.Mixer", "changed", + this, SLOT(slotControlsReconfigured()) ); + m_mixers.insert( dbusPath, curmi ); + return curmi; +} + +ControlInfo* MixerEngine::createControlInfo( const QString& mixerId, const QString& dbusPath ) +{ + ControlInfo* curci = new ControlInfo; + curci->iface = new OrgKdeKMixControlInterface( KMIX_DBUS_SERVICE, dbusPath, + QDBusConnection::sessionBus(), this ); + curci->mixerId = mixerId; + curci->id = curci->iface->id(); + curci->dbusPath = dbusPath; + curci->updateRequired = false; + curci->unused = false; + m_controls.insertMulti( mixerId, curci ); + return curci; +} + +void MixerEngine::getInternalData() +{ + clearInternalData(true); + if ( !interface->isServiceRegistered( KMIX_DBUS_SERVICE ) ) + return; + if ( !m_kmix ) + { + m_kmix = new OrgKdeKMixMixSetInterface( KMIX_DBUS_SERVICE, KMIX_DBUS_PATH, + QDBusConnection::sessionBus(), this ); + QDBusConnection::sessionBus().connect( KMIX_DBUS_SERVICE, KMIX_DBUS_PATH, + "org.kde.KMix.MixSet", "mixersChanged", + this, SLOT(slotMixersChanged()) ); + QDBusConnection::sessionBus().connect( KMIX_DBUS_SERVICE, KMIX_DBUS_PATH, + "org.kde.KMix.MixSet", "masterChanged", + this, SLOT(slotMasterChanged()) ); + } + Q_FOREACH( const QString& path, m_kmix->mixers() ) + { + MixerInfo* curmi = createMixerInfo( path ); + Q_FOREACH( const QString& controlPath, curmi->iface->controls() ) + createControlInfo( curmi->id, controlPath ); + } + // Update "Mixers" source + getMixersData(); +} + +void MixerEngine::clearInternalData(bool removeSources) +{ + Q_FOREACH( MixerInfo* mi, m_mixers ) + { + if ( removeSources ) + removeSource( mi->id ); + delete mi->iface; + delete mi; + } + m_mixers.clear(); + Q_FOREACH( ControlInfo* ci, m_controls ) + { + if ( removeSources ) + removeSource( ci->mixerId + '/' + ci->id ); + delete ci->iface; + delete ci; + } + m_controls.clear(); +} + +bool MixerEngine::sourceRequestEvent( const QString &name ) +{ + if ( name == "Mixers" ) + return getMixersData(); + else if ( name.indexOf("/") == -1 ) + // This request is for mixer + return getMixerData( name ); + else + // This request is for control + return getControlData( name ); +} + +bool MixerEngine::updateSourceEvent( const QString &name ) +{ + return sourceRequestEvent( name ); +} + +bool MixerEngine::getMixersData() +{ + QStringList mixerIds; + if ( interface->isServiceRegistered( KMIX_DBUS_SERVICE ) ) + { + Q_FOREACH( MixerInfo* mi, m_mixers ) + mixerIds.append( mi->id ); + /* FIXME: this is used to know whether kmix isn't running or + * it can't find any audio device; also it works as a strange + * workaround: without it there is no dataUpdated() call sometimes + * when it is updated here */ + setData( "Mixers", "Running", true ); + setData( "Mixers", "Mixers", mixerIds ); + setData( "Mixers", "Current Master Mixer", m_kmix->currentMasterMixer() ); + setData( "Mixers", "Current Master Control", m_kmix->currentMasterControl() ); + } + else + { + setData( "Mixers", "Running", false ); + removeData( "Mixers", "Mixers" ); + removeData( "Mixers", "Current Master Mixer" ); + removeData( "Mixers", "Current Master Control" ); + } + return true; +} + +bool MixerEngine::getMixerData( const QString& source ) +{ + // Trying to find this mixer + MixerInfo *curmi = 0; + Q_FOREACH( MixerInfo* mi, m_mixers ) + if ( mi->id == source ) + { + curmi = mi; + break; + } + if ( !curmi || !curmi->iface->connection().isConnected() ) + return false; + // Setting data + curmi->updateRequired = true; + QStringList controlIds; + QStringList controlReadableNames; + QStringList controlIcons; + Q_FOREACH( ControlInfo* ci, m_controls.values( curmi->id ) ) + if ( ci->iface->connection().isConnected() ) + { + controlIds.append( ci->id ); + controlReadableNames.append( ci->iface->readableName() ); + controlIcons.append( ci->iface->iconName() ); + } + setData( source, "Opened", curmi->iface->opened() ); + setData( source, "Readable Name", curmi->iface->readableName() ); + setData( source, "Balance", curmi->iface->balance() ); + setData( source, "Controls", controlIds ); + setData( source, "Controls Readable Names", controlReadableNames ); + setData( source, "Controls Icons Names", controlIcons ); + return true; +} + +bool MixerEngine::getControlData( const QString &source ) +{ + QString mixerId = source.section( '/', 0, 0 ); + QString controlId = source.section( '/', 1 ); + // Trying to find mixer for this control + // and monitor for its changes + Q_FOREACH( MixerInfo* mi, m_mixers ) + if ( mi->id == mixerId ) + { + if ( !mi->connected ) + { + QDBusConnection::sessionBus().connect( KMIX_DBUS_SERVICE, mi->dbusPath, + "org.kde.KMix.Mixer", "controlChanged", + this, SLOT(slotControlChanged()) ); + mi->connected = true; + } + break; + } + // Trying to find this control + ControlInfo *curci = 0; + Q_FOREACH( ControlInfo* ci, m_controls.values( mixerId ) ) + if ( ci->id == controlId ) { + curci = ci; + break; + } + if ( !curci || !curci->iface->connection().isConnected() ) + return false; + // Setting data + curci->updateRequired = true; + setControlData( curci ); + return true; +} + +void MixerEngine::setControlData(ControlInfo* ci) +{ + QString source = ci->mixerId + '/' + ci->id; + setData( source, "Volume", ci->iface->volume() ); + setData( source, "Mute", ci->iface->mute() ); + setData( source, "Can Be Muted", ci->iface->canMute() ); + setData( source, "Readable Name", ci->iface->readableName() ); + setData( source, "Icon", KIcon(ci->iface->iconName()) ); + setData( source, "Record Source", ci->iface->recordSource() ); + setData( source, "Has Capture Switch", ci->iface->hasCaptureSwitch() ); +} + + +void MixerEngine::slotServiceRegistered( const QString &serviceName) +{ + // Let's give KMix some time to load + if ( serviceName == KMIX_DBUS_SERVICE ) + QTimer::singleShot( 1000, this, SLOT(getInternalData()) ); +} + +void MixerEngine::slotServiceUnregistered( const QString &serviceName) +{ + if ( serviceName == KMIX_DBUS_SERVICE ) + clearInternalData(true); + // Updating 'Mixers' source + getMixersData(); +} + +void MixerEngine::slotControlChanged() +{ + // Trying to find mixer from which signal was emitted + MixerInfo* curmi = m_mixers.value( message().path(), 0 ); + if ( !curmi ) + return; + // Updating all controls that might change + Q_FOREACH( ControlInfo* ci, m_controls.values( curmi->id ) ) + if ( ci->updateRequired ) + setControlData( ci ); +} + +void MixerEngine::slotControlsReconfigured() +{ + // Trying to find mixer from which signal was emitted + MixerInfo* curmi = m_mixers.value( message().path(), 0 ); + if ( !curmi ) + return; + // Updating + QList controlsForMixer = m_controls.values( curmi->id ); + QStringList controlIds; + QStringList controlReadableNames; + QStringList controlIconNames; + Q_FOREACH( ControlInfo* ci, controlsForMixer ) + ci->unused = true; + Q_FOREACH( const QString& controlPath, curmi->iface->controls() ) + { + ControlInfo* curci = 0; + Q_FOREACH( ControlInfo* ci, controlsForMixer ) + if ( ci->dbusPath == controlPath ) + { + curci = ci; + break; + } + // If control not found then we should add a new + if ( !curci ) + curci = createControlInfo( curmi->id, controlPath ); + curci->unused = false; + controlIds.append( curci->id ); + controlReadableNames.append( curci->iface->readableName() ); + controlIconNames.append( curci->iface->iconName() ); + } + // If control is unused then we should remove it + Q_FOREACH( ControlInfo* ci, controlsForMixer ) + if ( ci->unused ) + { + m_controls.remove( curmi->id, ci ); + delete ci->iface; + delete ci; + } + if ( curmi->updateRequired ) + { + setData( curmi->id, "Controls", controlIds ); + setData( curmi->id, "Controls Readable Names", controlReadableNames ); + setData( curmi->id, "Controls Icons Names", controlIconNames ); + } +} + +void MixerEngine::updateInternalMixersData() +{ + // Some mixer added or removed + Q_FOREACH( MixerInfo* mi, m_mixers ) + mi->unused = true; + Q_FOREACH( const QString& mixerPath, m_kmix->mixers() ) + { + MixerInfo* curmi = m_mixers.value( mixerPath, 0 ); + // if mixer was added, we need to add one to m_mixers + // and add all controls for this mixer to m_controls + if ( !curmi ) + { + curmi = createMixerInfo( mixerPath ); + Q_FOREACH( const QString& controlPath, curmi->iface->controls() ) + createControlInfo( curmi->id, controlPath ); + } + curmi->unused = false; + } + // and if it was removed, we should remove it + // and remove all controls + Q_FOREACH( MixerInfo* mi, m_mixers ) + if ( mi->unused ) + { + Q_FOREACH( ControlInfo* ci, m_controls.values( mi->id ) ) + { + m_controls.remove( mi->id, ci ); + removeSource( ci->mixerId + '/' + ci->id ); + delete ci->iface; + delete ci; + } + m_mixers.remove( mi->dbusPath ); + removeSource( mi->id ); + delete mi->iface; + delete mi; + } +} + +void MixerEngine::slotMixersChanged() +{ + // Let's give KMix some time to register this mixer on bus and so on + QTimer::singleShot( 1000, this, SLOT(updateInternalMixersData()) ); +} + +void MixerEngine::slotMasterChanged() +{ + setData( "Mixers", "Current Master Mixer", m_kmix->currentMasterMixer() ); + setData( "Mixers", "Current Master Control", m_kmix->currentMasterControl() ); +} + +Plasma::Service* MixerEngine::serviceForSource(const QString& source) +{ + QString mixerId = source.section( '/', 0, 0 ); + QString controlId = source.section( '/', 1 ); + // Trying to find this control + ControlInfo *curci = 0; + Q_FOREACH( ControlInfo* ci, m_controls.values( mixerId ) ) + if ( ci->id == controlId ) { + curci = ci; + break; + } + if ( !curci ) + return Plasma::DataEngine::serviceForSource( source ); + return new MixerService( this, curci->iface ); +} + +K_EXPORT_PLASMA_DATAENGINE(mixer, MixerEngine) + +#include "mixerengine.moc" diff --git a/kmix/plasma/engine/mixerengine.h b/kmix/plasma/engine/mixerengine.h new file mode 100644 index 00000000..4a841e28 --- /dev/null +++ b/kmix/plasma/engine/mixerengine.h @@ -0,0 +1,103 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2011 Igor Poboiko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MIXER_ENGINE_H +#define MIXER_ENGINE_H + +#include +#include + +#include +#include +#include +#include +#include +#include + +class OrgKdeKMixMixSetInterface; +class OrgKdeKMixMixerInterface; +class OrgKdeKMixControlInterface; + +struct ControlInfo { + QString mixerId; + QString id; + QString dbusPath; + bool unused; + bool updateRequired; + OrgKdeKMixControlInterface *iface; +}; + +struct MixerInfo { + QString id; + QString dbusPath; + bool unused; + bool updateRequired; + bool connected; + OrgKdeKMixMixerInterface *iface; +}; + +class MixerEngine : public Plasma::DataEngine, + protected QDBusContext +{ + Q_OBJECT + +public: + MixerEngine(QObject* parent, const QVariantList& args); + ~MixerEngine(); + + QStringList sources() const; + + void init(); + Plasma::Service* serviceForSource(const QString& source); +protected: + bool sourceRequestEvent( const QString &name ); + bool updateSourceEvent( const QString &name ); +private: + static const QString KMIX_DBUS_SERVICE; + static const QString KMIX_DBUS_PATH; + QDBusConnectionInterface *interface; + QDBusServiceWatcher *watcher; + OrgKdeKMixMixSetInterface *m_kmix; + // Keys are mixers DBus paths + QHash m_mixers; + // Keys are mixerIds for control + QMultiHash m_controls; + + MixerInfo* createMixerInfo( const QString& dbusPath ); + ControlInfo* createControlInfo( const QString& mixerId, const QString& dbusPath ); + + void clearInternalData(bool removeSources); + bool getMixersData(); + bool getMixerData( const QString& source ); + bool getControlData( const QString& source ); + void setControlData( ControlInfo *ci ); +private slots: + void getInternalData(); + void updateInternalMixersData(); + void slotServiceRegistered( const QString &serviceName ); + void slotServiceUnregistered( const QString &serviceName ); + + void slotMixersChanged(); + void slotMasterChanged(); + void slotControlChanged(); + void slotControlsReconfigured(); +}; + +#endif /* MIXER_ENGINE_H */ diff --git a/kmix/plasma/engine/mixerservice.cpp b/kmix/plasma/engine/mixerservice.cpp new file mode 100644 index 00000000..13b4fa2d --- /dev/null +++ b/kmix/plasma/engine/mixerservice.cpp @@ -0,0 +1,70 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2011 Igor Poboiko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "mixerservice.h" + +MixerService::MixerService(QObject *parent, OrgKdeKMixControlInterface *iface) + : Plasma::Service( parent ) +{ + m_iface = iface; + + setName("mixer"); + setDestination( "mixer" ); +} + +OrgKdeKMixControlInterface* MixerService::iface() +{ + return m_iface; +} + +Plasma::ServiceJob* MixerService::createJob(const QString& operation, + QMap& parameters) +{ + return new MixerJob( this, operation, parameters ); +} + +MixerJob::MixerJob( MixerService* service, const QString &operation, + QMap& parameters ) + : Plasma::ServiceJob(service->destination(), operation, parameters, service) + , m_service( service ) +{ +} + +void MixerJob::start() +{ + QString operation = operationName(); + if ( operation == "setVolume" ) { + bool res = m_service->iface()->setProperty( "volume", parameters().value("level").toInt() ); + setResult( res ); + return; + } + else if ( operation == "setMute" ) { + bool res = m_service->iface()->setProperty( "mute", parameters().value("muted").toBool() ); + setResult( res ); + return; + } + else if ( operation == "setRecordSource" ) { + bool res = m_service->iface()->setProperty( "recordSource", parameters().value("recordSource").toBool() ); + setResult( res ); + return; + } +} + +#include "mixerservice.moc" diff --git a/kmix/plasma/engine/mixerservice.h b/kmix/plasma/engine/mixerservice.h new file mode 100644 index 00000000..2f97d467 --- /dev/null +++ b/kmix/plasma/engine/mixerservice.h @@ -0,0 +1,53 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright 2011 Igor Poboiko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MIXERSERVICE_H +#define MIXERSERVICE_H + +#include +#include +#include "control_interface.h" + +// +class MixerService : public Plasma::Service +{ + Q_OBJECT +public: + MixerService( QObject *parent, OrgKdeKMixControlInterface *iface ); + OrgKdeKMixControlInterface* iface(); +protected: + Plasma::ServiceJob* createJob(const QString& operation, + QMap& parameters); + OrgKdeKMixControlInterface* m_iface; +}; + +// +class MixerJob : public Plasma::ServiceJob +{ + Q_OBJECT +public: + MixerJob( MixerService *parent, const QString &operation, + QMap& parameters ); + void start(); +private: + MixerService *m_service; +}; + +#endif /* MIXERSERVICE_H */ diff --git a/kmix/plasma/engine/plasma-engine-mixer.desktop b/kmix/plasma/engine/plasma-engine-mixer.desktop new file mode 100644 index 00000000..902db5a7 --- /dev/null +++ b/kmix/plasma/engine/plasma-engine-mixer.desktop @@ -0,0 +1,54 @@ +[Desktop Entry] +Name=Mixer DataEngine +Name[bg]=Ядро за смесител +Name[bs]=Mikser pogon podataka +Name[ca]=Motor de dades del mesclador +Name[ca@valencia]=Motor de dades del mesclador +Name[cs]=Datový stroj směšovače +Name[da]=Datamotor til mikser +Name[de]=Mixer-Datenmodul +Name[el]=Μηχανή δεδομένων μείκτη +Name[en_GB]=Mixer DataEngine +Name[es]=Motor de datos del mezclador +Name[et]=Mikseri andmemootor +Name[fi]=Mikseritietomoottori +Name[fr]=Moteur de données du mixeur +Name[ga]=Inneall Sonraí an Mheascóra +Name[gl]=Motor de datos do mesturador +Name[hu]=Keverő adatmodul +Name[ia]=Motor de datos de Mxer +Name[id]=Mesindata Mixer +Name[is]=Hljóðblöndunargagnavél +Name[it]=Motore di dati del mixer +Name[kk]=Микшер деректер тетігі +Name[km]=ម៉ាស៊ីន​ទិន្នន័យ​នៃ​កម្មវិធី​លាយ +Name[ko]=믹서 데이터 엔진 +Name[lt]=Maišytuvo duomenų variklis +Name[mr]=मिक्सर डेटाइंजिन +Name[nb]=Mikser datamotor +Name[nds]=Mischer-Datenkarn +Name[nl]=Gegevensengine voor mixer +Name[pa]=ਮਿਕਸਰ ਡਾਟਾਇੰਜਣ +Name[pl]=Silnik danych miksera +Name[pt]=Motor de Dados da Mesa de Mistura +Name[pt_BR]=Mecanismo de dados do mixer +Name[ro]=Motor de date Mixer +Name[ru]=Источник данных Plasma для работы с микшером +Name[sk]=Dátový engine mixéra +Name[sl]=DataEngine mešalnika +Name[sr]=Датомотор миксете +Name[sr@ijekavian]=Датомотор миксете +Name[sr@ijekavianlatin]=Datomotor miksete +Name[sr@latin]=Datomotor miksete +Name[sv]=Datagränssnitt för mixer +Name[tr]=Karıştırıcı Veri Sağlayıcısı +Name[ug]=سازلىغۇچ سانلىق مەلۇمات ماتورى +Name[uk]=Рушій даних мікшера +Name[x-test]=xxMixer DataEnginexx +Name[zh_CN]=混音器数据引擎 +Name[zh_TW]=混合器資料引擎 +X-KDE-ServiceTypes=Plasma/DataEngine +Type=Service +Icon=kmix +X-KDE-Library=plasma_engine_mixer +X-KDE-PluginInfo-Name=mixer diff --git a/kmix/profiles/ALSA.Sound_Fusion_CS46xx.xml b/kmix/profiles/ALSA.Sound_Fusion_CS46xx.xml new file mode 100644 index 00000000..7c780a53 --- /dev/null +++ b/kmix/profiles/ALSA.Sound_Fusion_CS46xx.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kmix/profiles/ALSA.TerraTec_DMX6Fire.1.default.xml b/kmix/profiles/ALSA.TerraTec_DMX6Fire.1.default.xml new file mode 100644 index 00000000..b50610fa --- /dev/null +++ b/kmix/profiles/ALSA.TerraTec_DMX6Fire.1.default.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kmix/profiles/ALSA.capture.xml b/kmix/profiles/ALSA.capture.xml new file mode 100644 index 00000000..237c7614 --- /dev/null +++ b/kmix/profiles/ALSA.capture.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/kmix/profiles/ALSA.default.xml b/kmix/profiles/ALSA.default.xml new file mode 100644 index 00000000..04609446 --- /dev/null +++ b/kmix/profiles/ALSA.default.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kmix/profiles/ALSA.playback.xml b/kmix/profiles/ALSA.playback.xml new file mode 100644 index 00000000..e0b3cab3 --- /dev/null +++ b/kmix/profiles/ALSA.playback.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/kmix/profiles/CMakeLists.txt b/kmix/profiles/CMakeLists.txt new file mode 100644 index 00000000..ff6d2340 --- /dev/null +++ b/kmix/profiles/CMakeLists.txt @@ -0,0 +1,8 @@ +install( FILES + OSS.default.xml + ALSA.default.xml + ALSA.playback.xml + ALSA.capture.xml + ALSA.TerraTec_DMX6Fire.1.default.xml + DESTINATION ${DATA_INSTALL_DIR}/kmix/profiles ) + diff --git a/kmix/profiles/OSS.default.xml b/kmix/profiles/OSS.default.xml new file mode 100644 index 00000000..7ede4ed7 --- /dev/null +++ b/kmix/profiles/OSS.default.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/kmix/restore_kmix_volumes.desktop b/kmix/restore_kmix_volumes.desktop new file mode 100755 index 00000000..304c1073 --- /dev/null +++ b/kmix/restore_kmix_volumes.desktop @@ -0,0 +1,57 @@ +[Desktop Entry] +X-KDE-autostart-phase=1 +X-KDE-autostart-condition=kmixrc:Global:startkdeRestore:true +Type=Application +Exec=kmixctrl --restore +OnlyShowIn=KDE; +Name=Restore Mixer Volumes +Name[bg]=Възстановяване силата на звука в смесителя +Name[bs]=Obnovi jačine miksete +Name[ca]=Restaura els volums del mesclador +Name[ca@valencia]=Restaura els volums del mesclador +Name[cs]=Obnovit nastavení hlasitosti +Name[da]=Genskab mikserlydstyrke +Name[de]=Lautstärken wiederherstellen +Name[el]=Επαναφορά εντάσεων μείκτη +Name[en_GB]=Restore Mixer Volumes +Name[es]=Restaurar los volúmenes del mezclador +Name[et]=Mikseri helitugevuste taastamine +Name[fi]=Palauta mikserin äänivoimakkuudet +Name[fr]=Restaurer les volumes du mixage +Name[ga]=Athchóirigh Leibhéil Airde an Mheascthóra +Name[gl]=Restaurar os volumes do mesturador +Name[hu]=Hangerők visszaállítása +Name[ia]=Restabili volumines de Mixer +Name[id]=Kembalikan Volume Mixer +Name[is]=Endurheimta stillingar hljóðrása +Name[it]=Ripristina i volumi del Mixer +Name[ja]=ミキサーの音量設定を復元する +Name[kk]=Микшер деңгейлерін қалпына келтіру +Name[km]=ស្ដារ​កម្រិត​សំឡេង​នៃ​កម្មវិធី​លាយ +Name[ko]=믹서 음량 복원 +Name[lt]=Atstatyti maišytuvo garso lygius +Name[mr]=मिक्सर आवाज पुन्हस्थापित करा +Name[nb]=Gjenopprett lydstyrkene til mikser +Name[nds]=Mischerluutstärken wedderherstellen +Name[nl]=Mixervolumes herstellen +Name[nn]=Gjenopprett miksarlydstyrkar +Name[pa]=ਮਿਕਸਰ ਵਾਲੀਅਮ ਮੁੜ-ਸਟੋਰ ਕਰੋ +Name[pl]=Przywróć ustawienia głośności miksera +Name[pt]=Repor os Volumes +Name[pt_BR]=Restaurar volumes do Mixer +Name[ro]=Restabilește volumurile mixerelor +Name[ru]=Восстановление параметров микшера +Name[sk]=Obnoviť hlasitosti mixéra +Name[sl]=Obnovi glasnosti mešalnika +Name[sr]=Обнови јачине миксете +Name[sr@ijekavian]=Обнови јачине миксете +Name[sr@ijekavianlatin]=Obnovi jačine miksete +Name[sr@latin]=Obnovi jačine miksete +Name[sv]=Återställ mixervolymer +Name[tr]=Karıştırıcı Ses Düzeylerini Yeniden Yükle +Name[ug]=سازلىغۇچ ئاۋازىنى ئەسلىگە كەلتۈر +Name[uk]=Відновити параметри мікшера +Name[x-test]=xxRestore Mixer Volumesxx +Name[zh_CN]=还原混音器音量 +Name[zh_TW]=回復混音器音量 + diff --git a/kmix/tests/CMakeLists.txt b/kmix/tests/CMakeLists.txt new file mode 100644 index 00000000..1028bdb0 --- /dev/null +++ b/kmix/tests/CMakeLists.txt @@ -0,0 +1,45 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/.. ) + +set(kmix_adaptor_SRCS) +qt4_add_dbus_adaptor( kmix_adaptor_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/../dbus/org.kde.KMix.Mixer.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../dbus/dbusmixerwrapper.cpp DBusMixerWrapper) + + +########### next target ############### +set(profiletest_SRCS profiletest.cpp ) + + +kde4_add_executable(profiletest ${profiletest_SRCS} + ${CMAKE_CURRENT_SOURCE_DIR}/../gui/guiprofile.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../core/MasterControl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../core/mixer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../core/mixdevice.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../core/volume.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../backends/mixer_backend.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../core/mixset.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../core/kmixdevicemanager.cpp + ${kmix_adaptor_SRCS} + ) + +target_link_libraries(profiletest ${KDE4_KDECORE_LIBS} ${KDE4_SOLID_LIBS} ${QT_QTGUI_LIBRARY} ${QT_QTXML_LIBRARY} ) + +if (HAVE_LIBASOUND2) + target_link_libraries(profiletest ${ASOUND_LIBRARY}) +endif (HAVE_LIBASOUND2) + +if (PULSEAUDIO_FOUND) + target_link_libraries(profiletest ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} ${GLIB2_LIBRARIES}) +endif (PULSEAUDIO_FOUND) + +########### next target ############### +set(dialogtest_KDEINIT_SRCS dialogtest.cpp ) + +kde4_add_kdeinit_executable( dialogtest ${dialogtest_KDEINIT_SRCS} + ) + +target_link_libraries(kdeinit_dialogtest ${KDE4_KDEUI_LIBS} ) + + diff --git a/kmix/tests/dialogtest.cpp b/kmix/tests/dialogtest.cpp new file mode 100644 index 00000000..64655e25 --- /dev/null +++ b/kmix/tests/dialogtest.cpp @@ -0,0 +1,72 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + + +extern "C" KDE_EXPORT int kdemain(int argc, char *argv[]) +{ + KAboutData aboutData( "dialogtest", 0, ki18n("dialogtest"), + "1.0", ki18n("bla"), KAboutData::License_GPL, + ki18n("(c) 2000 by Christian Esken")); + + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::parsedArgs(); + App *app = new App(); + int ret = app->exec(); + + delete app; + return ret; +} + +App::App() :KApplication() { + DialogTest *dialog = new DialogTest(); + dialog->show(); +} + +DialogTest::DialogTest() : QDialog( 0) +{ + /* + setCaption("Configure" ); + setButtons( Ok|Cancel ); + setDefaultButton( Ok ); +*/ + /* + QWidget * frame = new QWidget( this ); + setMainWidget( frame ); +*/ + QWidget * frame = this; + + QVBoxLayout* layout = new QVBoxLayout(frame ); + QLabel* qlb = new QLabel( "Configuration of the channels.", frame ); + layout->addWidget(qlb); + + QScrollArea* scrollArea = new QScrollArea(frame); + scrollArea->setWidgetResizable(true); // avoid unnecesary scrollbars + layout->addWidget(scrollArea); + + QWidget* vboxForScrollView = new QWidget(); + QGridLayout* grid = new QGridLayout(vboxForScrollView); + grid->setHorizontalSpacing(0); + + for (int i=0; i<10; ++i ) { + QCheckBox* cb = new QCheckBox( "abcdefg abcdefg abcdefg", vboxForScrollView ); + grid->addWidget(cb,i,0); + cb = new QCheckBox( "", vboxForScrollView ); + grid->addWidget(cb,i,1); + cb = new QCheckBox( "", vboxForScrollView ); + grid->addWidget(cb,i,2); + } + + scrollArea->setWidget(vboxForScrollView); +} + +#include "dialogtest.moc" + diff --git a/kmix/tests/dialogtest.h b/kmix/tests/dialogtest.h new file mode 100644 index 00000000..3ad109a4 --- /dev/null +++ b/kmix/tests/dialogtest.h @@ -0,0 +1,23 @@ +#ifndef DIALOGTEST_H +#define DIALOGTEST_H + +#include + +#include + + +class App : public KApplication +{ + Q_OBJECT + public: + App(); +}; + +class DialogTest : public QDialog +{ + Q_OBJECT + public: + DialogTest(); +}; + +#endif diff --git a/kmix/tests/profiletest-wrong.xml b/kmix/tests/profiletest-wrong.xml new file mode 100644 index 00000000..1e22784c --- /dev/null +++ b/kmix/tests/profiletest-wrong.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kmix/tests/profiletest.cpp b/kmix/tests/profiletest.cpp new file mode 100644 index 00000000..77421e35 --- /dev/null +++ b/kmix/tests/profiletest.cpp @@ -0,0 +1,34 @@ +#include "../gui/guiprofile.h" + +#include + +int main(int argc, char* argv[]) { + GUIProfile* guiprof = new GUIProfile(); + + char dummyFilename[] = "profiletest.xml"; + QString fileName; + + if ( argc >=1 && argv[1] != 0 ) { + fileName = argv[1]; + } + else { + fileName = dummyFilename; + } + + bool ok = guiprof->readProfile(fileName); + if ( !ok ) { + std::cerr << "Error: GuiProfile '" << qPrintable(fileName) << "' is NOT ok" << std::endl; + } + else { + std::cout << "GuiProfile '" << qPrintable(fileName) << "' read successfully:\n----------------------\n"; + std::cout << (*guiprof) ; + std::cout << "----------------------\n"; + } + + if ( ok ) { + return 0; + } + else { + return 1; + } +} diff --git a/kmix/tests/profiletest.xml b/kmix/tests/profiletest.xml new file mode 100644 index 00000000..5e51c2c5 --- /dev/null +++ b/kmix/tests/profiletest.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kolourpaint/.krazy b/kolourpaint/.krazy new file mode 100644 index 00000000..d850d7bb --- /dev/null +++ b/kolourpaint/.krazy @@ -0,0 +1 @@ +EXTRA multiclasses diff --git a/kolourpaint/AUTHORS b/kolourpaint/AUTHORS new file mode 100644 index 00000000..9822db57 --- /dev/null +++ b/kolourpaint/AUTHORS @@ -0,0 +1,38 @@ + +Authors +======= + +Clarence Dang +Project Founder + +Thurston Dang +Chief Investigator + +Kristof Borrey +Icons + +Kazuki Ohta +InputMethod Support + +Nuno Pinheiro +Icons + +Danny Allen +Icons + +Mike Gashler +Imaqe Effects + +Laurent Montel +KDE 4 Porting + +Martin Koller +Scanning Support, Current Maintainer + +Tasuku Suzuki +InputMethod Support + +Thanks To +========= + +Thanks to the many others who have helped to make this program possible. diff --git a/kolourpaint/BUGS b/kolourpaint/BUGS new file mode 100644 index 00000000..93c622f1 --- /dev/null +++ b/kolourpaint/BUGS @@ -0,0 +1,138 @@ + +Please send bug reports and feature requests to http://bugs.kde.org/. +Don't hesitate to report bugs nor hesitate to send us your wishes - it +provides valuable feedback that will help to improve future versions of +KolourPaint and you will not receive flames for reporting duplicates. + + +This file lists known bugs in this version that are not considered +"release critical" and are difficult to fix: + + +3. Tool Box & Colour Box RMB ToolBar Menus do not work. + +4. Image dialog spinboxes should accept Enter Key (instead of the dialog's + OK button) after the user has typed something. + + OR + + Spinboxes should signal that their values have changed every time the + user changes the text (rather than after pressing Enter or clicking on + another spinbox etc.). + + The need for the "Update Preview" button and the difficulty of keeping + the percentages and dimensions in sync in the Resize / Scale dialog are + manifestations of the current QSpinBox behaviour. + + Update: Text input is broken in KDE4. + +6. a) The undo history and document modified state are not updated during + the drawing of multi-segment shapes (Polygon, Connected Lines, + Curve). They are however updated after shapes' completion. + + b) The brush-like tools set the document modified flag even if + user cancels the draw operation. + + c) Select a region, manipulate it (e.g. move), undo - the document is + still marked as modified (because 2 commands - the create selection + and the move - were added but only one was undone). + +7. Certain shapes may have the wrong size (usually only a pixel off and + only in extreme cases). This is a Qt bug. + +8. At zoom levels that aren't multiples of 100%, parts of the image may + appear to move when the user interacts with it. Other minor redraw + glitches may also occur at such zoom levels. + +9. Keyboard shortcut changes do not propagate to other KolourPaint windows + (but will propagate to future windows). + +10. "File/Open Recent" entries are not updated interprocess. + +11. The blinking text cursor will "disappear" if you type more text than + you can fit in a text box. + +12. You cannot select only parts of the text you write. + +14. The text cursor may be momentarily misrendered when scrolling the view. + +17. a) Using KolourPaint on a remote X display may result in redraw errors + and pixel data corruption. + +19. Read support for EPS files is extremely slow. You should not enable + the "Save Preview" dialog when saving to EPS. This is an issue with + KDE. + +20. Pasting a large image (esp. one that doesn't compress well as PNG) + into an image editor (not necessarily KolourPaint) running as + different process from the KolourPaint which was the source of the + image, on a sufficiently slow computer, may fail with the following + output to STDERR: + + "kolourpaint: ERROR: kpMainWindow::paste() with sel without pixmap + QClipboard: timed out while sending data" + + This is a Qt bug. + +21. It is not always possible to copy and paste between 2 instances of + KolourPaint running different Qt versions. See + QDataStream::setVersion(). + +23. Changing tool options while in the middle of a drawing option should + work but confuses KolourPaint instead. For instance: + + a) With the brush tools, the cursor incorrectly appears. + + b) With the rectangle-based tools, the temporary pixmap does not resize + when the line width increases. + +25. Sometimes when you take a screenshot of a window, and then paste in a + new window, it will be greyscale. When pasting again, it will still be + greyscale. Cannot consistently reproduce. [Thurston] + +26. Drawing with the keyboard is unreliable. Depending on the X server, + either holding down Enter may continually switch between drawing and + not drawing or KolourPaint may fail to detect the release of the Enter + key. + +27. InputMethod had not been tested at zoom levels other than 100%. + +28. KolourPaint has not been tested against invalid or malicious clipboard + data. + +29. The Tool Box and Color Tool Bar are no longer movable or floatable. + +30. The "Skew", "Rotate" and "Smooth Scale" effects produce low quality + results. + +31. The rendering quality of a text box with opaque text but a see-through + background, on top of transparent document areas, is lower than in KDE 3 + versions of KolourPaint. + + +Issue with XFree86 <= 3.3.6 with the "Emulate3Buttons" Option +============================================================= + +When drawing, clicking the left or right mouse button that did not +initiate the current operation will, in this order: + +1. finalise the current drawing operation +2. attempt to paste the contents of the middle-mouse-button clipboard + +instead of canceling the current drawing operation. + +This is due to XFree86 sending a release notification for the button that +initiated the drawing operation, followed by a press notification for the +emulated 3rd button; instead of just a single press notification for the +button that is intended to cancel the operation. This works correctly in +XFree86 4.x with "Emulate3Buttons" on because it is harder to trigger the +emulation for the 3rd button as it is only invoked if the left and right +buttons are pressed at almost the same time. + +Possible solutions: + +a) Use XFree86 4.x or an X server from another vendor (e.g. X.org). +b) Press Escape in KolourPaint to cancel the current drawing operation + instead of using the problematic click method described above. +c) Disable "Emulate3Buttons". + diff --git a/kolourpaint/CMakeLists.txt b/kolourpaint/CMakeLists.txt new file mode 100644 index 00000000..72da7683 --- /dev/null +++ b/kolourpaint/CMakeLists.txt @@ -0,0 +1,580 @@ +project(kolourpaint) + +find_package(KDE4 REQUIRED) +include(KDE4Defaults) + +find_package(QImageBlitz REQUIRED) +add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) + +include_directories( + +# GENERATED BY ./gen_cmake_include_dirs + +${CMAKE_CURRENT_SOURCE_DIR}/commands +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/flow +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/polygonal +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/rectangular +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text +${CMAKE_CURRENT_SOURCE_DIR}/cursors +${CMAKE_CURRENT_SOURCE_DIR}/dialogs +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/effects +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms +${CMAKE_CURRENT_SOURCE_DIR}/document +${CMAKE_CURRENT_SOURCE_DIR}/environments +${CMAKE_CURRENT_SOURCE_DIR}/environments/commands +${CMAKE_CURRENT_SOURCE_DIR}/environments/dialogs +${CMAKE_CURRENT_SOURCE_DIR}/environments/dialogs/imagelib +${CMAKE_CURRENT_SOURCE_DIR}/environments/dialogs/imagelib/transforms +${CMAKE_CURRENT_SOURCE_DIR}/environments/document +${CMAKE_CURRENT_SOURCE_DIR}/environments/tools +${CMAKE_CURRENT_SOURCE_DIR}/environments/tools/selection +${CMAKE_CURRENT_SOURCE_DIR}/generic +${CMAKE_CURRENT_SOURCE_DIR}/generic/widgets +${CMAKE_CURRENT_SOURCE_DIR}/imagelib +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms +${CMAKE_CURRENT_SOURCE_DIR}/layers +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text +${CMAKE_CURRENT_SOURCE_DIR}/layers/tempImage +${CMAKE_CURRENT_SOURCE_DIR}/lgpl +${CMAKE_CURRENT_SOURCE_DIR}/lgpl/generic +${CMAKE_CURRENT_SOURCE_DIR}/lgpl/generic/widgets +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow +${CMAKE_CURRENT_SOURCE_DIR}/pixmapfx +${CMAKE_CURRENT_SOURCE_DIR}/tools +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow +${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal +${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text +${CMAKE_CURRENT_SOURCE_DIR}/views +${CMAKE_CURRENT_SOURCE_DIR}/views/manager +${CMAKE_CURRENT_SOURCE_DIR}/widgets +${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options + +${KDE4_INCLUDES} +${QIMAGEBLITZ_INCLUDES} +) + +add_subdirectory( pics ) +add_subdirectory( doc ) + + +########### next target ############### +macro(CREATE_VERSION _in_FILE _out_FILE) + FILE(READ ${_in_FILE} _contents) + FILE(WRITE ${_out_FILE} "static const char * const kpVersionText =") + STRING(REGEX REPLACE "\n" "" _contents "${_contents}" ) + FILE(APPEND ${_out_FILE} "${_contents}") + FILE(APPEND ${_out_FILE} ";\n") +endmacro(CREATE_VERSION) + + +#macro_additional_clean_files( ${CMAKE_CURRENT_BINARY_DIR}/kolourpaintversion.h ) + +create_version(${CMAKE_CURRENT_SOURCE_DIR}/VERSION ${CMAKE_CURRENT_BINARY_DIR}/kolourpaintversion.h) + + +macro(CREATE_LICENSE _in_FILE _out_FILE) + FILE(READ ${_in_FILE} _contents) + FILE(WRITE ${_out_FILE} "static const char * const kpLicenseText =") + STRING(REGEX REPLACE "\"" "\\\\\"" _contents "${_contents}" ) + STRING(REGEX REPLACE "\n" "\\\\n\"\n\"" _contents "${_contents}" ) + FILE(APPEND ${_out_FILE} "\"${_contents}\"") + FILE(APPEND ${_out_FILE} ";\n") +endmacro(CREATE_LICENSE) + +#macro_additional_clean_files( ${CMAKE_CURRENT_BINARY_DIR}/kolourpaintlicense.h ) + +create_license(${CMAKE_CURRENT_SOURCE_DIR}/COPYING ${CMAKE_CURRENT_BINARY_DIR}/kolourpaintlicense.h) + + +# GENERATED BY ./gen_cmake_srcs | fgrep -v /lgpl/ + +if(CMAKE_MAJOR_VERSION MATCHES "2" AND CMAKE_MINOR_VERSION MATCHES "4" AND MINGW) + +set(kolourpaint_lib1_SRCS +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectBalanceCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectClearCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectCommandBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectEmbossCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectFlattenCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectGrayscaleCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectHSVCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectInvertCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectReduceColorsCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectToneEnhanceCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/kpDocumentMetaInfoCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms/kpTransformFlipCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms/kpTransformRotateCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms/kpTransformSkewCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/kpCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/kpCommandHistoryBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/kpCommandHistory.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/kpCommandSize.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/kpMacroCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/kpNamedCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/flow/kpToolFlowCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/kpToolColorPickerCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/kpToolFloodFillCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/polygonal/kpToolPolygonalCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/rectangular/kpToolRectangularCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpAbstractSelectionContentCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionCreateCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionDestroyCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionMoveCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextBackspaceCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextDeleteCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextEnterCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextInsertCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/cursors/kpCursorLightCross.cpp +${CMAKE_CURRENT_SOURCE_DIR}/cursors/kpCursorProvider.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/effects/kpEffectsDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms/kpTransformRotateDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms/kpTransformSkewDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/kpColorSimilarityDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocument.cpp +${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocument_Open.cpp +${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocument_Save.cpp +${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocumentSaveOptions.cpp +${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocument_Selection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/environments/commands/kpCommandEnvironment.cpp +${CMAKE_CURRENT_SOURCE_DIR}/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.cpp +${CMAKE_CURRENT_SOURCE_DIR}/environments/document/kpDocumentEnvironment.cpp +${CMAKE_CURRENT_SOURCE_DIR}/environments/kpEnvironmentBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/environments/tools/kpToolEnvironment.cpp +${CMAKE_CURRENT_SOURCE_DIR}/environments/tools/selection/kpToolSelectionEnvironment.cpp +${CMAKE_CURRENT_SOURCE_DIR}/generic/kpSetOverrideCursorSaver.cpp +${CMAKE_CURRENT_SOURCE_DIR}/generic/kpWidgetMapper.cpp +${CMAKE_CURRENT_SOURCE_DIR}/generic/widgets/kpResizeSignallingLabel.cpp +${CMAKE_CURRENT_SOURCE_DIR}/generic/widgets/kpSubWindow.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectBalance.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectBlurSharpen.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectEmboss.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectFlatten.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectGrayscale.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectHSV.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectInvert.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectReduceColors.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectToneEnhance.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpColor_Constants.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpColor.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpDocumentMetaInfo.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpFloodFill.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpPainter.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms/kpTransformAutoCrop.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms/kpTransformCrop.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms/kpTransformCrop_ImageSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms/kpTransformCrop_TextSelection.cpp +) # kolourpaint_lib1_SRCS + +set(kolourpaint_lib2_SRCS +${CMAKE_CURRENT_SOURCE_DIR}/kolourpaint.cpp +${CMAKE_CURRENT_SOURCE_DIR}/kpThumbnail.cpp +${CMAKE_CURRENT_SOURCE_DIR}/kpViewScrollableContainer.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpAbstractImageSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpEllipticalImageSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpFreeFormImageSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpImageSelectionTransparency.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpRectangularImageSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/kpAbstractSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/kpSelectionDrag.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/kpSelectionFactory.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpTextSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpTextSelection_Cursor.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpTextSelection_Paint.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpTextStyle.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpPreeditText.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/tempImage/kpTempImage.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Colors.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Edit.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_File.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Image.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Settings.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_StatusBar.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Text.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Tools.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_View.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_View_Thumbnail.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_View_Zoom.cpp +${CMAKE_CURRENT_SOURCE_DIR}/pixmapfx/kpPixmapFX_DrawShapes.cpp +${CMAKE_CURRENT_SOURCE_DIR}/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp +${CMAKE_CURRENT_SOURCE_DIR}/pixmapfx/kpPixmapFX_Transforms.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolBrush.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolColorEraser.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolEraser.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolFlowBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolFlowPixmapBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolPen.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolSpraycan.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpToolAction.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpToolColorPicker.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_Drawing.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpToolFloodFill.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_KeyboardEvents.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_MouseEvents.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_OtherEvents.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_UserNotifications.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_Utilities.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpToolZoom.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolCurve.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolLine.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolPolygonalBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolPolygon.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolPolyline.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular/kpToolEllipse.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular/kpToolRectangle.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular/kpToolRectangularBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular/kpToolRoundedRectangle.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpAbstractImageSelectionTool.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpAbstractImageSelectionTool_Transparency.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpToolEllipticalSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpToolFreeFormSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpToolRectSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool_Create.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool_KeyboardEvents.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool_Move.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool_ResizeScale.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_Commands.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_Create.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_CursorCalc.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_InputMethodEvents.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_KeyboardEvents.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_KeyboardEvents_HandleArrowKeys.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_KeyboardEvents_HandleTypingKeys.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_Move.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_ResizeScale.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_SelectText.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_TextStyle.cpp +) # kolourpaint_lib2_SRCS + +set(kolourpaint_SRCS +${CMAKE_CURRENT_SOURCE_DIR}/views/kpThumbnailView.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpUnzoomedThumbnailView.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpView.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpView_Events.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpView_Paint.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpView_Selections.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpZoomedThumbnailView.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpZoomedView.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/manager/kpViewManager.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/manager/kpViewManager_TextCursor.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/manager/kpViewManager_ViewUpdates.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity/kpColorSimilarityFrame.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity/kpColorSimilarityHolder.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity/kpColorSimilarityToolBarItem.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectBalanceWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectBlurSharpenWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectEmbossWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectFlattenWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectHSVWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectInvertWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectReduceColorsWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectToneEnhanceWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectWidgetBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpColorCells.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpColorPalette.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpDefaultColorCollection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpDocumentSaveOptionsWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpDualColorButton.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpPrintDialogPage.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpTransparentColorCell.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/kpColorToolBar.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/kpToolToolBar.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetBrush.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetEraserSize.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetFillStyle.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetLineWidth.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetSpraycanSize.cpp + + +) # set(kolourpaint_SRCS +kde4_add_library(kolourpaint_lib1 STATIC ${kolourpaint_lib1_SRCS}) +kde4_add_library(kolourpaint_lib2 STATIC ${kolourpaint_lib2_SRCS}) +# doubled for the linker +set(mingw_libs kolourpaint_lib1 kolourpaint_lib2 kolourpaint_lib1 kolourpaint_lib2) + +else(CMAKE_MAJOR_VERSION MATCHES "2" AND CMAKE_MINOR_VERSION MATCHES "4" AND MINGW) + +set(kolourpaint_SRCS + +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectBalanceCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectClearCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectCommandBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectEmbossCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectFlattenCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectGrayscaleCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectHSVCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectInvertCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectReduceColorsCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectToneEnhanceCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/kpDocumentMetaInfoCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms/kpTransformFlipCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms/kpTransformRotateCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms/kpTransformSkewCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/kpCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/kpCommandHistoryBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/kpCommandHistory.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/kpCommandSize.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/kpMacroCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/kpNamedCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/flow/kpToolFlowCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/kpToolColorPickerCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/kpToolFloodFillCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/polygonal/kpToolPolygonalCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/rectangular/kpToolRectangularCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpAbstractSelectionContentCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionCreateCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionDestroyCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionMoveCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextBackspaceCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextDeleteCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextEnterCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextInsertCommand.cpp +${CMAKE_CURRENT_SOURCE_DIR}/cursors/kpCursorLightCross.cpp +${CMAKE_CURRENT_SOURCE_DIR}/cursors/kpCursorProvider.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/effects/kpEffectsDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms/kpTransformRotateDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms/kpTransformSkewDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/kpColorSimilarityDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp +${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocument.cpp +${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocument_Open.cpp +${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocument_Save.cpp +${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocumentSaveOptions.cpp +${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocument_Selection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/environments/commands/kpCommandEnvironment.cpp +${CMAKE_CURRENT_SOURCE_DIR}/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.cpp +${CMAKE_CURRENT_SOURCE_DIR}/environments/document/kpDocumentEnvironment.cpp +${CMAKE_CURRENT_SOURCE_DIR}/environments/kpEnvironmentBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/environments/tools/kpToolEnvironment.cpp +${CMAKE_CURRENT_SOURCE_DIR}/environments/tools/selection/kpToolSelectionEnvironment.cpp +${CMAKE_CURRENT_SOURCE_DIR}/generic/kpSetOverrideCursorSaver.cpp +${CMAKE_CURRENT_SOURCE_DIR}/generic/kpWidgetMapper.cpp +${CMAKE_CURRENT_SOURCE_DIR}/generic/widgets/kpResizeSignallingLabel.cpp +${CMAKE_CURRENT_SOURCE_DIR}/generic/widgets/kpSubWindow.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectBalance.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectBlurSharpen.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectEmboss.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectFlatten.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectGrayscale.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectHSV.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectInvert.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectReduceColors.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectToneEnhance.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpColor_Constants.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpColor.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpDocumentMetaInfo.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpFloodFill.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpPainter.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms/kpTransformAutoCrop.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms/kpTransformCrop.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms/kpTransformCrop_ImageSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms/kpTransformCrop_TextSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/kolourpaint.cpp +${CMAKE_CURRENT_SOURCE_DIR}/kpThumbnail.cpp +${CMAKE_CURRENT_SOURCE_DIR}/kpViewScrollableContainer.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpAbstractImageSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpEllipticalImageSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpFreeFormImageSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpImageSelectionTransparency.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpRectangularImageSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/kpAbstractSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/kpSelectionDrag.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/kpSelectionFactory.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpTextSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpTextSelection_Cursor.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpTextSelection_Paint.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpTextStyle.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpPreeditText.cpp +${CMAKE_CURRENT_SOURCE_DIR}/layers/tempImage/kpTempImage.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Colors.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Edit.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_File.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Image.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Settings.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_StatusBar.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Text.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Tools.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_View.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_View_Thumbnail.cpp +${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_View_Zoom.cpp +${CMAKE_CURRENT_SOURCE_DIR}/pixmapfx/kpPixmapFX_DrawShapes.cpp +${CMAKE_CURRENT_SOURCE_DIR}/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp +${CMAKE_CURRENT_SOURCE_DIR}/pixmapfx/kpPixmapFX_Transforms.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolBrush.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolColorEraser.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolEraser.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolFlowBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolFlowPixmapBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolPen.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolSpraycan.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpToolAction.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpToolColorPicker.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_Drawing.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpToolFloodFill.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_KeyboardEvents.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_MouseEvents.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_OtherEvents.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_UserNotifications.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_Utilities.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/kpToolZoom.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolCurve.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolLine.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolPolygonalBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolPolygon.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolPolyline.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular/kpToolEllipse.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular/kpToolRectangle.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular/kpToolRectangularBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular/kpToolRoundedRectangle.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpAbstractImageSelectionTool.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpAbstractImageSelectionTool_Transparency.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpToolEllipticalSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpToolFreeFormSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpToolRectSelection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool_Create.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool_KeyboardEvents.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool_Move.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool_ResizeScale.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_Commands.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_Create.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_CursorCalc.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_InputMethodEvents.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_KeyboardEvents.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_KeyboardEvents_HandleArrowKeys.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_KeyboardEvents_HandleTypingKeys.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_Move.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_ResizeScale.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_SelectText.cpp +${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_TextStyle.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpThumbnailView.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpUnzoomedThumbnailView.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpView.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpView_Events.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpView_Paint.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpView_Selections.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpZoomedThumbnailView.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/kpZoomedView.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/manager/kpViewManager.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/manager/kpViewManager_TextCursor.cpp +${CMAKE_CURRENT_SOURCE_DIR}/views/manager/kpViewManager_ViewUpdates.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity/kpColorSimilarityFrame.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity/kpColorSimilarityHolder.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity/kpColorSimilarityToolBarItem.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectBalanceWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectBlurSharpenWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectEmbossWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectFlattenWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectHSVWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectInvertWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectReduceColorsWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectToneEnhanceWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectWidgetBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpColorCells.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpColorPalette.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpDefaultColorCollection.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpDocumentSaveOptionsWidget.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpDualColorButton.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpPrintDialogPage.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpTransparentColorCell.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/kpColorToolBar.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/kpToolToolBar.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetBase.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetBrush.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetEraserSize.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetFillStyle.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetLineWidth.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.cpp +${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetSpraycanSize.cpp + + +) # set(kolourpaint_SRCS +endif(CMAKE_MAJOR_VERSION MATCHES "2" AND CMAKE_MINOR_VERSION MATCHES "4" AND MINGW) + +set(kolourpaint_lgpl_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/lgpl/generic/kpUrlFormatter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/lgpl/generic/kpColorCollection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/lgpl/generic/widgets/kpColorCellsBase.cpp + ) + + +# +# LGPL Library +# +# This MUST be a dynamic link library to avoid LGPL license infection. +# + +kde4_add_library(kolourpaint_lgpl SHARED ${kolourpaint_lgpl_SRCS}) + +target_link_libraries(kolourpaint_lgpl ${KDE4_KIO_LIBS}) +set_target_properties(kolourpaint_lgpl PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} DEFINE_SYMBOL MAKE_KOLOURPAINT4_LGPL_LIB ) + +install(TARGETS kolourpaint_lgpl ${INSTALL_TARGETS_DEFAULT_ARGS} ) + + +# +# Executable +# + +kde4_add_app_icon(kolourpaint_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/pics/app/hi*-app-kolourpaint.png") + +kde4_add_executable(kolourpaint ${kolourpaint_SRCS}) + +target_link_libraries(kolourpaint ${KDE4_KIO_LIBS} ${KDE4_KUTILS_LIBS} +${QIMAGEBLITZ_LIBRARIES} kolourpaint_lgpl) + +install(TARGETS kolourpaint ${INSTALL_TARGETS_DEFAULT_ARGS}) + + +########### install files ############### + +install(PROGRAMS kolourpaint.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) +install(FILES kolourpaint.appdata.xml DESTINATION share/appdata/) +install(FILES kolourpaintui.rc DESTINATION ${DATA_INSTALL_DIR}/kolourpaint) diff --git a/kolourpaint/COPYING b/kolourpaint/COPYING new file mode 100644 index 00000000..6b56fcf6 --- /dev/null +++ b/kolourpaint/COPYING @@ -0,0 +1,139 @@ +Copyright (c) 2003-2007 Clarence Dang +Portions Copyright (c) 2005 Kazuki Ohta +Portions Copyright (c) 2006-2007 Mike Gashler +Portions Copyright (c) 2007,2011 Martin Koller +Portions Copyright (c) 2007 John Layt +Portions Copyright (c) 2010 Tasuku Suzuki +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"libkolourpaint" Library +~~~~~~~~~~~~~~~~~~~~~~~~ + +The "libkolourpaint" dynamic link library contains various pieces of code, +each with a different license: + + +License 1 +--------- + +Copyright (C) 1999 Waldo Bastian (bastian@kde.org) +Copyright (C) 2007 Clarence Dang (dang@kde.org) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this library; see the file COPYING.LIB. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. + + +License 2 +--------- + +Copyright (C) 2007 David Faure + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this library; see the file COPYING.LIB. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. + + +License 3 +--------- + +Copyright (c) 2003-2007 Clarence Dang +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +License 4 +--------- + +Copyright (C) 1997 Martin Jones (mjones@kde.org) +Copyright (C) 2007 Roberto Raggi (roberto@kdevelop.org) +Copyright (C) 2007 Clarence Dang (dang@kde.org) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this library; see the file COPYING.LIB. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. + + +Icons +~~~~~ + +Copyright (c) Nuno Pinheiro +Copyright (c) Danny Allen +Portions Copyright (c) Clarence Dang +Portions Copyright (c) Kristof Borrey + diff --git a/kolourpaint/COPYING.DOC b/kolourpaint/COPYING.DOC new file mode 100644 index 00000000..4a0fe1c8 --- /dev/null +++ b/kolourpaint/COPYING.DOC @@ -0,0 +1,397 @@ + GNU Free Documentation License + Version 1.2, November 2002 + + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/kolourpaint/COPYING.LIB b/kolourpaint/COPYING.LIB new file mode 100644 index 00000000..2676d08a --- /dev/null +++ b/kolourpaint/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/kolourpaint/ChangeLog b/kolourpaint/ChangeLog new file mode 100644 index 00000000..9acd0397 --- /dev/null +++ b/kolourpaint/ChangeLog @@ -0,0 +1,15 @@ + +For logs of _every_ single change made to KolourPaint between any date or +revision, visit: + + http://websvn.kde.org/trunk/KDE/kdegraphics/kolourpaint + + http://websvn.kde.org/branches/KDE//kdegraphics/kolourpaint + http://websvn.kde.org/tags/KDE//kdegraphics/kolourpaint + + http://websvn.kde.org/branches/kolourpaint + http://websvn.kde.org/tags/kolourpaint + + +For a summary of user-visible changes between each release, read NEWS. + diff --git a/kolourpaint/Messages.sh b/kolourpaint/Messages.sh new file mode 100644 index 00000000..5ac09ab7 --- /dev/null +++ b/kolourpaint/Messages.sh @@ -0,0 +1,3 @@ +#! /bin/sh +$EXTRACTRC `find . -name "*.rc" -o -name "*.ui" ` >> rc.cpp +$XGETTEXT `find . -name "*.cpp" -o -name "*.h"` -o $podir/kolourpaint.pot diff --git a/kolourpaint/NEWS b/kolourpaint/NEWS new file mode 100644 index 00000000..4f32e104 --- /dev/null +++ b/kolourpaint/NEWS @@ -0,0 +1,383 @@ + +KolourPaint 4 Series (trunk/KDE/) +==================== + +KolourPaint 4.0.0 BETA (Frozen 2007-10-26) + + * Port to KDE 4 (Clarence Dang, Laurent Montel) + + Note: This is a beta release and has not been through a full QA review. + If you want stability, use KolourPaint/KDE3, which can be + installed in parallel with KolourPaint4 BETA and runs under KDE4. + + * Configurable Color Palette + + * Add "Hue, Saturation, Value" effect (Mike Gashler) + + * Add "Histogram Equalizer" effect (Mike Gashler) + + * Add Zoom Tool + + * Add "File / Properties..." + + * Rectangles, rounded rectangles and ellipsed are now bounded by + the dimensions of the dragged out rectangle + + * Add, to the print dialog, a choice between printing the image at the + top-left of the page or at the center (this was previously a hidden + configuration option) + (Bug #33481) + + * Add hidden configuration option "Open Images in the Same Window" + (Bug #125116) + + * Add "Rotate Left" (CTRL+SHIFT+Left) and "Rotate Right" (CTRL+SHIFT+R) + to "Image" menu as a quick way to access the common types of "Rotate..." + (Bug #135184, #141530)) + + * Add "Fit to Page", "Fit to Page Width" and "Fit to Page Height" to the + "View" Menu + + * Add "Image / Draw Opaque" menu item since some users expect it. + It duplicates the functionality of the already existent Tool Box widget. + + * Animate the Color Similarity Tool Bar Item, to highlight the existence + of the feature + - And make the configuration more accessible + - Also add "Image / Draw With Color Similarity" to duplicate the tool + bar item + + * Make the Tool Box use as much vertical space as possible, since it + needs it for the option widgets. + + * Save local files atomically - KolourPaint will no longer truncate + an existing file if the KImageIO plugin for the file format is + missing or if you run out of disk space. + [also in branches/KDE/3.5] + + * Add "File / Scan..." feature (Martin Koller) + [also in branches/KDE/3.5/] + + * Fix crash triggered by rapidly deselecting a selection after + drag-scaling the selection (Bug #117866) + [also in branches/KDE/3.[345]/, branches/kolourpaint/1.2_kde3/] + + * Add global session save/restore (Bug #94651) + [also in branches/KDE/3.5/] + + * Make "File / Open Recent" consistently work when multiple windows are + open + [also in branches/KDE/3.5/] + + * CTRL+C'ing a text box also places the text in the middle-mouse-button + clipboard, in lieu of being able to highlight the text to do this + [also in branches/KDE/3.5/] + + * Change minimum allowed zoom level for the grid from 600% to 400% + [also in branches/KDE/3.5/] + + * Printing improvements (Bug #108976) + - Respect image DPI + - Fit image to page if image is too big + - Center image on page + [also in branches/KDE/3.5/] + + * Paste transparent pixels as black instead of uninitialized colors, + when the app does not support pasting transparent pixels (such as + OpenOffice.org) + [also in branches/KDE/3.5/] + + * Make "Edit / Paste in New Window" always paste white pixels as white + (it used to paste them as transparent when the selection transparency + mode was set to Transparent) + [also in branches/KDE/3.5/] + + * REGRESSION: The "Skew", "Rotate" and "Smooth Scale" effects produce + low quality results + (Bug #30) + + * REGRESSION: The rendering quality of a text box with opaque text but + a see-through background, on top of transparent document + areas, is lower than in KDE 3 versions of KolourPaint + (Bug #31) + + * REGRESSION: Spinboxes do not support text input + (see the "Update" part of Bug #4) + + * REGRESSION: InputMethod support was not ported to Qt4 so has been disabled + (Bug #27) + + * REGRESSION: The Tool Box and Color Tool Bar are no longer movable or + floatable + (Bug #29) + + KolourPaint 4.0.0 BETA contains all the fixes and features in KolourPaint + 1.4.8_relight (KDE 3.5.8), even though not all of them were listed + above. + + +KolourPaint 1.4_relight Series (branches/KDE/3.5/) +=============================== + +KolourPaint 1.4.1_relight (Frozen 2006-01-15) + + * Updated documentation (Thurston) + +KolourPaint 1.4_relight (Frozen 2005-11-08) + + * New icons (Danny Allen, Nuno Pinheiro) + + * Tool Box icon size is 22x22, not 16x16, at screen resolution >= 1024x768 + + * CTRL + Mouse Wheel = Zoom + + * While freehand selection scaling, holding Shift maintains aspect ratio + + * Prevent accidental drags in the Colour Palette from pasting text + containing the colour code + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Cells in the bottom row and cells in the rightmost column of the Colour + Palette are now the same size as the other cells + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Text drops to the empty part of the scrollview will not be placed + outside the document + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Rename icons from "hi" to "cr" - back to the state of 1.0 (Danny Allen) + but leave application icons as "hi" (Jonathan Riddell) + + * Enforce text box font height to prevent e.g. Chinese characters in + buggy fonts from enlarging the text box and putting the cursor out of + sync with the text + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Clicking in a text box selects a character based on its midpoint - + not leftmost point - to be consistent with all text editors + (esp. noticeable with big fonts) + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Return and Numpad 5 Key now draw + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Tool Actions placed outside the Tool Box resize with their toolbars + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Ensure Color Similarity maximum is 30, not 29 due to gcc4 + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Tool Box traps right clicks (for the RMB Menu) on top of tool options + widgets and the empty part of the Tool Box + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Correct and update image format associations to all formats supported + by KDE 3.5 (kdelibs/kimgio/:r466654) + + * String fixes (Stefan Winter) + [also in branches/KDE/3.4/] + + * Other string fixes (Malcolm Hunter, Clarence Dang, Stephan Binner) + + +KolourPaint 1.4_light Series (branches/KDE/3.4/) +============================ + +KolourPaint 1.4_light (Frozen 2005-02-22) + * Antialias text when the text box has a transparent background (Bug #24) + [later backported to branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Add Unzoomed Thumbnail Mode and Thumbnail Rectangle + * Add RMB context menu for when a selection tool is active (closing KDE + Bug #92882) + * More intuitive "Set as Image" behaviour (esp. with selection borders). + Thanks to Michael Lake for the feedback. + [later backported to branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * InputMethod support + [later backported to branches/kolourpaint/1.2_kde3/] + * Save "More Effects" dialog's last effect to config file + * Save "Resize / Scale" dialog's last "Keep aspect ratio" setting to + config file + * Add "Help / Acquiring Screenshots" + * Fix selection regressions introduced in 1.2: + - Make selection dragging with CTRL work again (copies selection onto + document) + - When creating freeform selections, include the starting point; also + avoids a QRegion crash with constructing 1-point regions + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Fix other selection bugs: + - When the user drags very quickly on a resize handle, resize the + selection instead of moving it + - Draw resize handles above the grid lines - not below - so that the + handles are always visible if they are supposed to be there + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Smaller selection and text box resize handles (visually not + actually) - covers up fewer selected pixels, doesn't cover up text + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Restore mouse cursor after deselecting selection/text tools + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Empty text clipboard fixes: + - Don't get stuck on a wait cursor after attempting to paste empty + text into a text box + - Prevent pasting text from creating a new text box if text is empty + - Prevent copying of empty text box + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Speed up renderer (most noticeable with diagonal drag-scrolling at + high zoom) + - Don't paint anything outside of the view's visible region + (previously, clipped only on view _widget_ region) + - Region-aware: paint component rectangles of the update region, + rather than the bounding rectangle + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * When changing between colour depth and quality widgets in the save + filedialog, make sure "Convert to:" and "Quality:" are correctly + rendered (hacking around a Qt redraw glitch) + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Fix crash after using the Colour Picker if it was the first used tool + [kolourpaint-1.2.2_kde3-color_picker_crash.diff] + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Fix crash due to text box when scaling image behind it + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Even when the thumbnail has focus (and not the main window), blink the + text cursor in all views + [kolourpaint-1.2.2_kde3-thumbnail_blink_text_cursor.diff] + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Correct "Soften" and "Sharpen" commands' command history names + * Correct invert commands' command history names + * Fix remaining untranslatable strings (closing KDE Bug #85785) + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Update image format associations to all formats supported by KDE 3.4 + * Remove unused images in doc directory + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Correct kolourpaint.desktop "Terminal=" and "Categories=" syntax + (Benjamin Meyer) + + +KolourPaint 1.2 Series (branches/KDE/3.3/) +====================== + +Version 1.2 "ByFiat Everytime" (2004-08-18) + * Add up to 500 levels of Undo/Redo (minimum of 10 levels, maximum of + 500 as long as the total history size < 16MB) + * Add freehand resizing of image + * Add freehand smooth scaling of selections + * [also in 1.0 branch] New icons (Kristof Borrey) + * [also in 1.0 branch] Prefer Crystal SVG text icons over KolourPaint's + * [also in 1.0 branch] Add documentation in the KDE Help Centre + * Add drag scrolling + * Add "More Effects" dialog: + - Balance (Brightness, Contrast, Gamma) + - Emboss + - Flatten + - Invert (with choice of channels) + - Reduce Colours + - Soften & Sharpen + * File saving improvements: + - Support colour depths (optional dithering) and "colour monochrome" + - Support JPEG quality + - Realtime file dialog preview with estimated file size + - Retain PNG metadata + - Prompt when attempting lossy save + - Correctly save transparent selections (not as opaque) + * Dither more often when loading (and pasting) images for better quality + * Single key shortcuts for all tools and tool options (automatically + turned off when editing text but can then use Alt+Shift+) + * Arrow keys now move one document pixel - not view pixel - at a time + (more usable when zoomed in) + * Fix selection bugs: + - Fix duplicate "Selection: Create" undo entries (Bug #5a) + - Allow redoing of selection operation if border deselected (Bug #5b) + - Don't print to STDERR when undoing a selection border create + operation and border has already been deselected + - [also in 1.0 branch] When pulling a selection from the document, + only set the bits of the document to the background colour where the + transparent selection is opaque in the same place (this is only + noticeable with colour similarity turned on). Now moving a + selection away and then back to its original place is always a NOP + as it should be. + * Selections can be deselected using Esc or clicking on icon in Tool Box + * Accidental drag detection when deselecting selections or text boxes + * Prevent selection from being moved completely offscreen (at least 1 + pixel of the selection will stay within the view) + * Speed up copying selection when transparency is on + * Improve Text Tool usability: + - Allow single click creation of text box with a sane default size + - Allow freehand resizing of text boxes + - Add Opaque/Transparent selector for greater usability and + consistency with selections + - Minimum size is now 7x7 document pixels (1x1 - not 4x4 - border) + - Text cursor doesn't overlap border anymore + - When dropping text, paste at drop point + - When MMB pasting creates a new text box, do so at mouse position + * When MMB pasting text in an existing box, correctly paste multiline + clipboard contents + * Improve text quality: + - With a transparent background, don't antialias foreground opaque + text with arbitrarily chosen black + - Make sure transparent text shows up on opaque (usually, grey was + problematic) background + * Improve Resize/Scale dialog usability: + - Add Smooth Scale (useful for creating screenshot thumbnails) + - Allow manipulating image when selection is active + - Operation choices stand out as massive, easily clickable buttons + - Default focus on operation choices + * Warn if Resize/Scale, Rotate or Skew will take lots of memory + * Limit startup image size to 2048x2048 + * Eliminate flicker when scrolling + * Thumbnail fixes: + - Reduce flicker when appearing (Bug #2) + - More reasonable minimum size (actually enforce it) + - [also in 1.0 branch] Use deleteLater() + - [also in 1.0 branch] Save geometry even if it's closed very quickly + after a geometry change + * Restore last used tool and tool options on startup + * Add Export, Copy To File, Paste From File, Paste in New Window, + Full Screen Mode + * Add Zoom In/Out buttons to main toolbar + * Rename Crop options in an attempt to reduce confusion: + - "Autocrop" --> "Remove Internal Border" when selection active + - "Crop Outside Selection" --> "Set as Image (Crop)" + * "Set as Image" changes: + - Enable for text boxes + - Underneath transparent bits of selection, fill image with + transparent rather than with background colour + * Permit "reloading" of an empty document + * Fixes when the current URL doesn't exist: + - Don't reload if underlying file disappeared + - Don't add non-existent file to Recent Files history + - Ask to save before mailing or setting as wallpaper + * Only enable Show Path when there is a URL + * Pop up dialog (instead of printing to STDERR) and disable Edit/Paste + on CTRL+V if the clipboard contents disappeared due to the source + application quitting (and Klipper didn't retain clipboard contents) + * Image/Clear now always sets _everything_ within the selection boundary + to the background colour - including transparent pixels + * Add Preview button to Colour Similarity Dialog to work around Bug #4 + regarding spinboxes and enter key + * Colour Picker disallows trying to pick colour outside of image + * Make sure colour palette contains valid and visible colours at 8-bit + * [also in 1.0 branch] Fix (big) memory leak on kpSelection destruction + (Albert Astals Cid) + * Don't leak image dialogs' memory + * [also in 1.0 branch] Don't let C++ destruct the mask bitmap before its + painter when dbl-clicking the color eraser does NOP (avoids + QPaintDevice and X error) + * [also in 1.0 branch] Check for QImageDrag::canDecode() before calling + QImageDrag::decode() (prevents X and valgrind errors) + * [also in 1.0 branch] Fix compilation problem with QT_NO_ASCII_CAST + (Waldo Bastian) + * [also in 1.0 branch] Decrease application preference to below that of + a viewer (Stephan Kulow) + * Remember dialog dimensions + * Remove double dialog margins + * Fix missing i18n()'s + * Fix some untranslatable strings + * [also in 1.0 branch] Corrected several strings + * Remove unused icons + + +KolourPaint 1.0 Series (branches/kolourpaint/1.0/) +====================== + +Version 1.0 "Seagull" (2004-02-29) + * First stable release + diff --git a/kolourpaint/README b/kolourpaint/README new file mode 100644 index 00000000..f281e628 --- /dev/null +++ b/kolourpaint/README @@ -0,0 +1,105 @@ + +KolourPaint Version 4.0.0 BETA (KDE 4.0.0 Release Frozen 2007-10-26) +http://www.kolourpaint.org/ + +Copyright (c) 2003-2007 Clarence Dang + + +For licensing and warranty information, read COPYING. +For known problems with this release of KolourPaint, read BUGS. +For what changes have been made, read NEWS. +For developer information, checkout branches/kolourpaint/control/. +For general information, read this file (README): + + +1. What is KolourPaint? +======================= + +KolourPaint is a free, easy-to-use paint program for KDE. + +It aims to be conceptually simple to understand; providing a level of +functionality targeted towards the average user. It's designed for daily +tasks like: + +* Painting - drawing diagrams and "finger painting" +* Image Manipulation - editing screenshots and photos; applying effects +* Icon Editing - drawing clipart and logos with transparency + +It's not an unusable and monolithic program where simple tasks like drawing +lines become near impossible. Nor is it so simple that it lacks essential +features like Undo/Redo. + +KolourPaint is opensource software written in C++ using the Qt and KDE +libraries. + + +2. Features +=========== + +* Undo/Redo Support (10-500 levels of history depending on memory usage) + +* Tools (single key shortcuts available for all tools) + - Brush, Color Eraser, Color Picker, Connected Lines a.k.a. Polyline + - Curve, Ellipse, Eraser, Flood Fill, Line, Pen, Polygon, Rectangle + - Rounded Rectangle, Spraycan, Text, Zoom + +* Selections (fully undo- and redo-able) + - Rectangular, Elliptical, Free-Form shapes + - Choice between Opaque and Transparent selections + - Full Clipboard/Edit Menu support + - Freehand resizeable + +* Configurable Color Palette + +* Color Similarity means that you can fill regions in dithered images and + photos + +* Transparency + - Draw transparent icons and logos on a checkerboard background + - All tools can draw in the "Transparent Color" + +* Image Effects + - Autocrop / Remove Internal Border + - Balance (Brightness, Contrast, Gamma) + - Clear, Emboss, Flatten, Flip, Histogram Equalizer + - Hue, Saturation, Value + - Invert (with choice of channels) + - Reduce Colors, Reduce to Grayscale, Resize, Rotate + - Scale, Set as Image (Crop), Skew, Smooth Scale, Soften & Sharpen + +* Close-up Editing + - Zoom (from 0.01x to 16x) + - Grid + - Thumbnail + +* File Operations + - Open/Save in all file formats provided by KImageIO + (PNG, JPEG, BMP, ICO, PCX, TIFF,...) with preview + - Print, Print Preview + - Mail + - Set as Wallpaper + + +3. Updates & More Information +============================= + +Visit: http://www.kolourpaint.org/ + + +4. Support +========== + +Visit: http://www.kolourpaint.org/ + +If you have any questions about compiling, installing or using KolourPaint, +don't be afraid to contact us. We try to support all versions of +KolourPaint and even issues with 3rd party binary packages. + + +5. Feedback +=========== + +Please send bug reports and feature requests to http://bugs.kde.org/. +Don't hesitate to report bugs nor hesitate to send us your wishes -- it +provides valuable feedback that will help to improve future versions of +KolourPaint and you will not receive flames for reporting duplicates. diff --git a/kolourpaint/VERSION b/kolourpaint/VERSION new file mode 100644 index 00000000..641b3a0f --- /dev/null +++ b/kolourpaint/VERSION @@ -0,0 +1 @@ +KDE_VERSION_STRING diff --git a/kolourpaint/commands/imagelib/effects/kpEffectBalanceCommand.cpp b/kolourpaint/commands/imagelib/effects/kpEffectBalanceCommand.cpp new file mode 100644 index 00000000..33b4122c --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectBalanceCommand.cpp @@ -0,0 +1,61 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_BALANCE 0 + + +#include + +#include +#include + +#include + + +kpEffectBalanceCommand::kpEffectBalanceCommand (int channels, + int brightness, int contrast, int gamma, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (i18n ("Balance"), actOnSelection, environ), + m_channels (channels), + m_brightness (brightness), m_contrast (contrast), m_gamma (gamma) +{ +} + +kpEffectBalanceCommand::~kpEffectBalanceCommand () +{ +} + + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectBalanceCommand::applyEffect (const kpImage &image) +{ + return kpEffectBalance::applyEffect (image, m_channels, + m_brightness, m_contrast, m_gamma); +} + diff --git a/kolourpaint/commands/imagelib/effects/kpEffectBalanceCommand.h b/kolourpaint/commands/imagelib/effects/kpEffectBalanceCommand.h new file mode 100644 index 00000000..c5053c12 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectBalanceCommand.h @@ -0,0 +1,56 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectBalanceCommand_H +#define kpEffectBalanceCommand_H + + +#include +#include + + +class kpEffectBalanceCommand : public kpEffectCommandBase +{ +public: + // (, & are from -50 to 50) + kpEffectBalanceCommand (int channels, + int brightness, int contrast, int gamma, + bool actOnSelection, + kpCommandEnvironment *environ); + virtual ~kpEffectBalanceCommand (); + +protected: + virtual kpImage applyEffect (const kpImage &image); + +protected: + int m_channels; + int m_brightness, m_contrast, m_gamma; +}; + + +#endif // kpEffectBalanceCommand_H diff --git a/kolourpaint/commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp b/kolourpaint/commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp new file mode 100644 index 00000000..d2f0a70f --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp @@ -0,0 +1,71 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_BLUR_SHARPEN 0 + + +#include + +#include +#include + + +kpEffectBlurSharpenCommand::kpEffectBlurSharpenCommand (kpEffectBlurSharpen::Type type, + int strength, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (kpEffectBlurSharpenCommand::nameForType (type), + actOnSelection, environ), + m_type (type), + m_strength (strength) +{ +} + +kpEffectBlurSharpenCommand::~kpEffectBlurSharpenCommand () +{ +} + + +// public static +QString kpEffectBlurSharpenCommand::nameForType (kpEffectBlurSharpen::Type type) +{ + if (type == kpEffectBlurSharpen::Blur) + return i18n ("Soften"); + else if (type == kpEffectBlurSharpen::Sharpen) + return i18n ("Sharpen"); + else + return QString(); +} + + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectBlurSharpenCommand::applyEffect (const kpImage &image) +{ + return kpEffectBlurSharpen::applyEffect (image, m_type, m_strength); +} + diff --git a/kolourpaint/commands/imagelib/effects/kpEffectBlurSharpenCommand.h b/kolourpaint/commands/imagelib/effects/kpEffectBlurSharpenCommand.h new file mode 100644 index 00000000..b2358371 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectBlurSharpenCommand.h @@ -0,0 +1,58 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectBlurSharpenCommand_H +#define kpEffectBlurSharpenCommand_H + + +#include +#include +#include + + +class kpEffectBlurSharpenCommand : public kpEffectCommandBase +{ +public: + kpEffectBlurSharpenCommand (kpEffectBlurSharpen::Type type, + int strength, + bool actOnSelection, + kpCommandEnvironment *environ); + virtual ~kpEffectBlurSharpenCommand (); + + static QString nameForType (kpEffectBlurSharpen::Type type); + +protected: + virtual kpImage applyEffect (const kpImage &image); + +protected: + kpEffectBlurSharpen::Type m_type; + int m_strength; +}; + + +#endif // kpEffectBlurSharpenCommand_H diff --git a/kolourpaint/commands/imagelib/effects/kpEffectClearCommand.cpp b/kolourpaint/commands/imagelib/effects/kpEffectClearCommand.cpp new file mode 100644 index 00000000..dafdb73a --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectClearCommand.cpp @@ -0,0 +1,113 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include + +#include +#include +#include +#include + + +kpEffectClearCommand::kpEffectClearCommand (bool actOnSelection, + const kpColor &newColor, + kpCommandEnvironment *environ) + : kpCommand (environ), + m_actOnSelection (actOnSelection), + m_newColor (newColor), + m_oldImagePtr (0) +{ +} + +kpEffectClearCommand::~kpEffectClearCommand () +{ + delete m_oldImagePtr; +} + + +// public virtual [base kpCommand] +QString kpEffectClearCommand::name () const +{ + QString opName = i18n ("Clear"); + + if (m_actOnSelection) + return i18n ("Selection: %1", opName); + else + return opName; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpEffectClearCommand::size () const +{ + return ImageSize (m_oldImagePtr); +} + + +// public virtual [base kpCommand] +void kpEffectClearCommand::execute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + + m_oldImagePtr = new kpImage (); + *m_oldImagePtr = doc->image (m_actOnSelection); + + + // REFACTOR: Would like to derive entire class from kpEffectCommandBase but + // this code makes it difficult since it's not just acting on pixels + // (kpAbstractImageSelection::fill() takes into account the shape of a selection). + if (m_actOnSelection) + { + // OPT: could just edit pixmap directly and signal change + kpAbstractImageSelection *sel = doc->imageSelection (); + Q_ASSERT (sel); + sel->fill (m_newColor); + } + else + doc->fill (m_newColor); +} + +// public virtual [base kpCommand] +void kpEffectClearCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + + doc->setImage (m_actOnSelection, *m_oldImagePtr); + + + delete m_oldImagePtr; + m_oldImagePtr = 0; +} + diff --git a/kolourpaint/commands/imagelib/effects/kpEffectClearCommand.h b/kolourpaint/commands/imagelib/effects/kpEffectClearCommand.h new file mode 100644 index 00000000..6578e260 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectClearCommand.h @@ -0,0 +1,62 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectClearCommand_H +#define kpEffectClearCommand_H + + +#include + +#include +#include + + +class kpEffectClearCommand : public kpCommand +{ +public: + kpEffectClearCommand (bool actOnSelection, + const kpColor &newColor, + kpCommandEnvironment *environ); + virtual ~kpEffectClearCommand (); + + virtual QString name () const; + + virtual SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + bool m_actOnSelection; + + kpColor m_newColor; + kpImage *m_oldImagePtr; +}; + + +#endif // kpEffectClearCommand_H diff --git a/kolourpaint/commands/imagelib/effects/kpEffectCommandBase.cpp b/kolourpaint/commands/imagelib/effects/kpEffectCommandBase.cpp new file mode 100644 index 00000000..d43d82df --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectCommandBase.cpp @@ -0,0 +1,127 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include + +#include +#include +#include + + +struct kpEffectCommandBasePrivate +{ + QString name; + bool actOnSelection; + + kpImage oldImage; +}; + +kpEffectCommandBase::kpEffectCommandBase (const QString &name, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpCommand (environ), + d (new kpEffectCommandBasePrivate ()) +{ + d->name = name; + d->actOnSelection = actOnSelection; +} + +kpEffectCommandBase::~kpEffectCommandBase () +{ + delete d; +} + + +// public virtual [base kpCommand] +QString kpEffectCommandBase::name () const +{ + if (d->actOnSelection) + return i18n ("Selection: %1", d->name); + else + return d->name; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpEffectCommandBase::size () const +{ + return ImageSize (d->oldImage); +} + + +// public virtual [base kpCommand] +void kpEffectCommandBase::execute () +{ + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + kpDocument *doc = document (); + Q_ASSERT (doc); + + + const kpImage oldImage = doc->image (d->actOnSelection); + + if (!isInvertible ()) + { + d->oldImage = oldImage; + } + + + kpImage newImage = /*pure virtual*/applyEffect (oldImage); + + doc->setImage (d->actOnSelection, newImage); +} + +// public virtual [base kpCommand] +void kpEffectCommandBase::unexecute () +{ + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + kpDocument *doc = document (); + Q_ASSERT (doc); + + + kpImage newImage; + + if (!isInvertible ()) + { + newImage = d->oldImage; + } + else + { + newImage = /*pure virtual*/applyEffect (doc->image (d->actOnSelection)); + } + + doc->setImage (d->actOnSelection, newImage); + + + d->oldImage = kpImage (); +} + diff --git a/kolourpaint/commands/imagelib/effects/kpEffectCommandBase.h b/kolourpaint/commands/imagelib/effects/kpEffectCommandBase.h new file mode 100644 index 00000000..3cf15e69 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectCommandBase.h @@ -0,0 +1,67 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectCommandBase_H +#define kpEffectCommandBase_H + + +#include + +#include +#include + + +class kpEffectCommandBase : public kpCommand +{ +public: + kpEffectCommandBase (const QString &name, + bool actOnSelection, + kpCommandEnvironment *environ); + virtual ~kpEffectCommandBase (); + + virtual QString name () const; + virtual SizeType size () const; + +public: + virtual void execute (); + virtual void unexecute (); + +public: + // Return true if applyEffect(applyEffect(image)) == image + // to avoid storing the old image, saving memory. + virtual bool isInvertible () const { return false; } + +protected: + virtual kpImage applyEffect (const kpImage &image) = 0; + +private: + struct kpEffectCommandBasePrivate *d; +}; + + +#endif // kpEffectCommandBase_H diff --git a/kolourpaint/commands/imagelib/effects/kpEffectEmbossCommand.cpp b/kolourpaint/commands/imagelib/effects/kpEffectEmbossCommand.cpp new file mode 100644 index 00000000..473db0f7 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectEmbossCommand.cpp @@ -0,0 +1,58 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_EMBOSS 0 + + +#include + +#include +#include + +#include + + +kpEffectEmbossCommand::kpEffectEmbossCommand (int strength, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (i18n ("Emboss"), actOnSelection, environ), + m_strength (strength) +{ +} + +kpEffectEmbossCommand::~kpEffectEmbossCommand () +{ +} + + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectEmbossCommand::applyEffect (const kpImage &image) +{ + return kpEffectEmboss::applyEffect (image, m_strength); +} + diff --git a/kolourpaint/commands/imagelib/effects/kpEffectEmbossCommand.h b/kolourpaint/commands/imagelib/effects/kpEffectEmbossCommand.h new file mode 100644 index 00000000..b97d9f02 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectEmbossCommand.h @@ -0,0 +1,53 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectEmbossCommand_H +#define kpEffectEmbossCommand_H + + +#include +#include + + +class kpEffectEmbossCommand : public kpEffectCommandBase +{ +public: + kpEffectEmbossCommand (int strength, + bool actOnSelection, + kpCommandEnvironment *environ); + virtual ~kpEffectEmbossCommand (); + +protected: + virtual kpImage applyEffect (const kpImage &image); + +protected: + int m_strength; +}; + + +#endif // kpEffectEmbossCommand_H diff --git a/kolourpaint/commands/imagelib/effects/kpEffectFlattenCommand.cpp b/kolourpaint/commands/imagelib/effects/kpEffectFlattenCommand.cpp new file mode 100644 index 00000000..1188e76d --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectFlattenCommand.cpp @@ -0,0 +1,64 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_FLATTEN 0 + + +#include + +#include +#include + +#include + + +kpEffectFlattenCommand::kpEffectFlattenCommand (const QColor &color1, + const QColor &color2, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (i18n ("Flatten"), actOnSelection, environ), + m_color1 (color1), m_color2 (color2) +{ +} + +kpEffectFlattenCommand::~kpEffectFlattenCommand () +{ +} + + +// +// kpEffectFlattenCommand implements kpEffectCommandBase interface +// + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectFlattenCommand::applyEffect (const kpImage &image) +{ + return kpEffectFlatten::applyEffect (image, m_color1, m_color2); +} + + diff --git a/kolourpaint/commands/imagelib/effects/kpEffectFlattenCommand.h b/kolourpaint/commands/imagelib/effects/kpEffectFlattenCommand.h new file mode 100644 index 00000000..a69716a3 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectFlattenCommand.h @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectFlattenCommand_H +#define kpEffectFlattenCommand_H + + +#include + +#include +#include + + +class kpEffectFlattenCommand : public kpEffectCommandBase +{ +public: + kpEffectFlattenCommand (const QColor &color1, const QColor &color2, + bool actOnSelection, + kpCommandEnvironment *environ); + virtual ~kpEffectFlattenCommand (); + + + // + // kpEffectCommandBase interface + // + +protected: + virtual kpImage applyEffect (const kpImage &image); + + QColor m_color1, m_color2; +}; + + +#endif // kpEffectFlattenCommand_H diff --git a/kolourpaint/commands/imagelib/effects/kpEffectGrayscaleCommand.cpp b/kolourpaint/commands/imagelib/effects/kpEffectGrayscaleCommand.cpp new file mode 100644 index 00000000..e4659f90 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectGrayscaleCommand.cpp @@ -0,0 +1,61 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include + +#include + + +kpEffectGrayscaleCommand::kpEffectGrayscaleCommand ( + bool actOnSelection, + kpCommandEnvironment *environ) + + : kpEffectCommandBase ( + i18n ("Reduce to Grayscale"), + actOnSelection, + environ) +{ +} + +kpEffectGrayscaleCommand::~kpEffectGrayscaleCommand () +{ +} + + +// +// kpEffectGrayscaleCommand implements kpEffectCommandBase interface +// + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectGrayscaleCommand::applyEffect (const kpImage &image) +{ + return kpEffectGrayscale::applyEffect (image); +} + diff --git a/kolourpaint/commands/imagelib/effects/kpEffectGrayscaleCommand.h b/kolourpaint/commands/imagelib/effects/kpEffectGrayscaleCommand.h new file mode 100644 index 00000000..56c92590 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectGrayscaleCommand.h @@ -0,0 +1,57 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectGrayscaleCommand_H +#define kpEffectGrayscaleCommand_H + + +#include +#include + + +class kpEffectGrayscaleCommand : public kpEffectCommandBase +{ +public: + kpEffectGrayscaleCommand (bool actOnSelection, + kpCommandEnvironment *environ); + virtual ~kpEffectGrayscaleCommand (); + + + // + // kpEffectCommandBase interface + // + +public: + virtual bool isInvertible () const { return false; } + +protected: + virtual kpImage applyEffect (const kpImage &image); +}; + + +#endif // kpEffectGrayscaleCommand_H diff --git a/kolourpaint/commands/imagelib/effects/kpEffectHSVCommand.cpp b/kolourpaint/commands/imagelib/effects/kpEffectHSVCommand.cpp new file mode 100644 index 00000000..5f9b327f --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectHSVCommand.cpp @@ -0,0 +1,50 @@ +/* + Copyright (c) 2007 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include + +//--------------------------------------------------------------------- + +kpEffectHSVCommand::kpEffectHSVCommand (double hue, double saturation, double value, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (i18n ("Hue, Saturation, Value"), actOnSelection, environ), + m_hue (hue), m_saturation (saturation), m_value (value) +{ +} + +//--------------------------------------------------------------------- + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectHSVCommand::applyEffect (const kpImage &image) +{ + return kpEffectHSV::applyEffect (image, m_hue, m_saturation, m_value); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/commands/imagelib/effects/kpEffectHSVCommand.h b/kolourpaint/commands/imagelib/effects/kpEffectHSVCommand.h new file mode 100644 index 00000000..9c293358 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectHSVCommand.h @@ -0,0 +1,51 @@ + +/* + Copyright (c) 2007 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectHSVCommand_H +#define kpEffectHSVCommand_H + + +#include + + +class kpEffectHSVCommand : public kpEffectCommandBase +{ +public: + kpEffectHSVCommand (double hue, double saturation, double value, + bool actOnSelection, + kpCommandEnvironment *environ); + +protected: + virtual kpImage applyEffect (const kpImage &image); + +protected: + double m_hue, m_saturation, m_value; +}; + + +#endif // kpEffectHSVCommand_H diff --git a/kolourpaint/commands/imagelib/effects/kpEffectInvertCommand.cpp b/kolourpaint/commands/imagelib/effects/kpEffectInvertCommand.cpp new file mode 100644 index 00000000..373c8470 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectInvertCommand.cpp @@ -0,0 +1,78 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_INVERT 0 + + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + + +kpEffectInvertCommand::kpEffectInvertCommand (int channels, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (channels == kpEffectInvert::RGB ? + i18n ("Invert Colors") : i18n ("Invert"), + actOnSelection, environ), + m_channels (channels) +{ +} + +kpEffectInvertCommand::kpEffectInvertCommand (bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (i18n ("Invert Colors"), actOnSelection, environ), + m_channels (kpEffectInvert::RGB) +{ +} + +kpEffectInvertCommand::~kpEffectInvertCommand () +{ +} + + +// +// kpEffectInvertCommand implements kpEffectCommandBase interface +// + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectInvertCommand::applyEffect (const kpImage &image) +{ + return kpEffectInvert::applyEffect (image, m_channels); +} + + diff --git a/kolourpaint/commands/imagelib/effects/kpEffectInvertCommand.h b/kolourpaint/commands/imagelib/effects/kpEffectInvertCommand.h new file mode 100644 index 00000000..f5eee36e --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectInvertCommand.h @@ -0,0 +1,63 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectInvertCommand_H +#define kpEffectInvertCommand_H + + +#include +#include + + + +class kpEffectInvertCommand : public kpEffectCommandBase +{ +public: + kpEffectInvertCommand (int channels, + bool actOnSelection, + kpCommandEnvironment *environ); + kpEffectInvertCommand (bool actOnSelection, + kpCommandEnvironment *environ); + virtual ~kpEffectInvertCommand (); + + + // + // kpEffectCommandBase interface + // + +public: + virtual bool isInvertible () const { return true; } + +protected: + virtual kpImage applyEffect (const kpImage &image); + + int m_channels; +}; + + +#endif // kpEffectInvertCommand_H diff --git a/kolourpaint/commands/imagelib/effects/kpEffectReduceColorsCommand.cpp b/kolourpaint/commands/imagelib/effects/kpEffectReduceColorsCommand.cpp new file mode 100644 index 00000000..4e5f5bf7 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectReduceColorsCommand.cpp @@ -0,0 +1,86 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_REDUCE_COLORS 0 + + +#include + +#include +#include + +#include + +//--------------------------------------------------------------------- + +kpEffectReduceColorsCommand::kpEffectReduceColorsCommand (int depth, bool dither, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (commandName (depth, dither), actOnSelection, environ), + m_depth (depth), m_dither (dither) +{ +} + +//--------------------------------------------------------------------- + +// public +QString kpEffectReduceColorsCommand::commandName (int depth, int dither) const +{ + if (depth == 1) + { + if (dither) + return i18n ("Reduce to Monochrome (Dithered)"); + else + return i18n ("Reduce to Monochrome"); + } + else if (depth == 8) + { + if (dither) + return i18n ("Reduce to 256 Color (Dithered)"); + else + return i18n ("Reduce to 256 Color"); + } + else + { + return QString(); + } +} + +//--------------------------------------------------------------------- + +// +// kpEffectReduceColorsCommand implements kpEffectCommandBase interface +// + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectReduceColorsCommand::applyEffect (const kpImage &image) +{ + return kpEffectReduceColors::applyEffect (image, m_depth, m_dither); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/commands/imagelib/effects/kpEffectReduceColorsCommand.h b/kolourpaint/commands/imagelib/effects/kpEffectReduceColorsCommand.h new file mode 100644 index 00000000..06ecac6f --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectReduceColorsCommand.h @@ -0,0 +1,60 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectReduceColorsCommand_H +#define kpEffectReduceColorsCommand_H + + +#include +#include + + + +class kpEffectReduceColorsCommand : public kpEffectCommandBase +{ +public: + // depth must be 1 or 8 + kpEffectReduceColorsCommand (int depth, bool dither, + bool actOnSelection, + kpCommandEnvironment *environ); + + QString commandName (int depth, int dither) const; + + // + // kpEffectCommandBase interface + // + +protected: + virtual kpImage applyEffect (const kpImage &image); + + int m_depth; + bool m_dither; +}; + + +#endif // kpEffectReduceColorsCommand_H diff --git a/kolourpaint/commands/imagelib/effects/kpEffectToneEnhanceCommand.cpp b/kolourpaint/commands/imagelib/effects/kpEffectToneEnhanceCommand.cpp new file mode 100644 index 00000000..1d6d2892 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectToneEnhanceCommand.cpp @@ -0,0 +1,56 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2006 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include + +#include + + +kpEffectToneEnhanceCommand::kpEffectToneEnhanceCommand (double granularity, double amount, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (i18n ("Histogram Equalizer"), actOnSelection, environ), + m_granularity (granularity), m_amount (amount) +{ +} + +kpEffectToneEnhanceCommand::~kpEffectToneEnhanceCommand () +{ +} + + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectToneEnhanceCommand::applyEffect (const kpImage &image) +{ + return kpEffectToneEnhance::applyEffect (image, m_granularity, m_amount); +} + diff --git a/kolourpaint/commands/imagelib/effects/kpEffectToneEnhanceCommand.h b/kolourpaint/commands/imagelib/effects/kpEffectToneEnhanceCommand.h new file mode 100644 index 00000000..7a29da80 --- /dev/null +++ b/kolourpaint/commands/imagelib/effects/kpEffectToneEnhanceCommand.h @@ -0,0 +1,53 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2006 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectToneEnhanceCommand_H +#define kpEffectToneEnhanceCommand_H + + +#include + + +class kpEffectToneEnhanceCommand : public kpEffectCommandBase +{ +public: + kpEffectToneEnhanceCommand (double granularity, double amount, + bool actOnSelection, + kpCommandEnvironment *environ); + virtual ~kpEffectToneEnhanceCommand (); + +protected: + virtual kpImage applyEffect (const kpImage &image); + +protected: + double m_granularity, m_amount; +}; + + +#endif // kpEffectToneEnhanceCommand_H diff --git a/kolourpaint/commands/imagelib/kpDocumentMetaInfoCommand.cpp b/kolourpaint/commands/imagelib/kpDocumentMetaInfoCommand.cpp new file mode 100644 index 00000000..78384822 --- /dev/null +++ b/kolourpaint/commands/imagelib/kpDocumentMetaInfoCommand.cpp @@ -0,0 +1,89 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include + +#include +#include +#include + + +struct kpDocumentMetaInfoCommandPrivate +{ + kpDocumentMetaInfo metaInfo, oldMetaInfo; +}; + +kpDocumentMetaInfoCommand::kpDocumentMetaInfoCommand (const QString &name, + const kpDocumentMetaInfo &metaInfo, + const kpDocumentMetaInfo &oldMetaInfo, + kpCommandEnvironment *environ) + + : kpNamedCommand (name, environ), + d (new kpDocumentMetaInfoCommandPrivate ()) +{ + d->metaInfo = metaInfo; + d->oldMetaInfo = oldMetaInfo; +} + +kpDocumentMetaInfoCommand::~kpDocumentMetaInfoCommand () +{ + delete d; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpDocumentMetaInfoCommand::size () const +{ + return d->metaInfo.size () + d->oldMetaInfo.size (); +} + + +// public virtual [base kpCommand] +void kpDocumentMetaInfoCommand::execute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + doc->setMetaInfo (d->metaInfo); + doc->setModified (); +} + +// public virtual [base kpCommand] +void kpDocumentMetaInfoCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + // REFACTOR: Document in kpDocument.h that kpDocument::setMetaInfo() does not mutate modified state + doc->setMetaInfo (d->oldMetaInfo); + doc->setModified (); +} + diff --git a/kolourpaint/commands/imagelib/kpDocumentMetaInfoCommand.h b/kolourpaint/commands/imagelib/kpDocumentMetaInfoCommand.h new file mode 100644 index 00000000..c7c40e2c --- /dev/null +++ b/kolourpaint/commands/imagelib/kpDocumentMetaInfoCommand.h @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDocumentMetaInfoCommand_H +#define kpDocumentMetaInfoCommand_H + + +#include + + +class kpDocumentMetaInfo; + + +class kpDocumentMetaInfoCommand : public kpNamedCommand +{ +public: + kpDocumentMetaInfoCommand (const QString &name, + const kpDocumentMetaInfo &metaInfo, + const kpDocumentMetaInfo &oldMetaInfo, + kpCommandEnvironment *environ); + virtual ~kpDocumentMetaInfoCommand (); + + virtual SizeType size () const; + +public: + virtual void execute (); + virtual void unexecute (); + +private: + struct kpDocumentMetaInfoCommandPrivate * const d; +}; + + +#endif // kpDocumentMetaInfoCommand_H diff --git a/kolourpaint/commands/imagelib/transforms/kpTransformFlipCommand.cpp b/kolourpaint/commands/imagelib/transforms/kpTransformFlipCommand.cpp new file mode 100644 index 00000000..b8228bfa --- /dev/null +++ b/kolourpaint/commands/imagelib/transforms/kpTransformFlipCommand.cpp @@ -0,0 +1,141 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +kpTransformFlipCommand::kpTransformFlipCommand (bool actOnSelection, + bool horiz, bool vert, + kpCommandEnvironment *environ) + : kpCommand (environ), + m_actOnSelection (actOnSelection), + m_horiz (horiz), m_vert (vert) +{ +} + +//--------------------------------------------------------------------- + +kpTransformFlipCommand::~kpTransformFlipCommand () +{ +} + +//--------------------------------------------------------------------- +// public virtual [base kpCommand] + +QString kpTransformFlipCommand::name () const +{ + QString opName; + + +#if 1 + opName = i18n ("Flip"); +#else // re-enable when giving full descriptions for all actions + if (m_horiz && m_vert) + opName = i18n ("Flip horizontally and vertically"); + else if (m_horiz) + opName = i18n ("Flip horizontally"); + else if (m_vert) + opName = i18n ("Flip vertically"); + else + { + kError () << "kpTransformFlipCommand::name() not asked to flip" << endl; + return QString(); + } +#endif + + + if (m_actOnSelection) + return i18n ("Selection: %1", opName); + else + return opName; +} + +//--------------------------------------------------------------------- +// public virtual [base kpCommand] + +kpCommandSize::SizeType kpTransformFlipCommand::size () const +{ + return 0; +} + +//--------------------------------------------------------------------- +// public virtual [base kpCommand] + +void kpTransformFlipCommand::execute () +{ + flip (); +} + +//--------------------------------------------------------------------- +// public virtual [base kpCommand] + +void kpTransformFlipCommand::unexecute () +{ + flip (); +} + +//--------------------------------------------------------------------- +// private + +void kpTransformFlipCommand::flip () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + QApplication::setOverrideCursor (Qt::WaitCursor); + + if (m_actOnSelection) + { + Q_ASSERT (doc->imageSelection ()); + doc->imageSelection ()->flip (m_horiz, m_vert); + environ ()->somethingBelowTheCursorChanged (); + } + else + { + doc->setImage(doc->image().mirrored(m_horiz, m_vert)); + } + + QApplication::restoreOverrideCursor (); +} diff --git a/kolourpaint/commands/imagelib/transforms/kpTransformFlipCommand.h b/kolourpaint/commands/imagelib/transforms/kpTransformFlipCommand.h new file mode 100644 index 00000000..fd11483f --- /dev/null +++ b/kolourpaint/commands/imagelib/transforms/kpTransformFlipCommand.h @@ -0,0 +1,60 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformFlipCommand_H +#define kpTransformFlipCommand_H + + +#include + + +class kpTransformFlipCommand : public kpCommand +{ +public: + kpTransformFlipCommand (bool actOnSelection, + bool horiz, bool vert, + kpCommandEnvironment *environ); + + virtual ~kpTransformFlipCommand (); + + virtual QString name () const; + + virtual SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + void flip (); + + bool m_actOnSelection; + bool m_horiz, m_vert; +}; + + +#endif // kpTransformFlipCommand_H diff --git a/kolourpaint/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp b/kolourpaint/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp new file mode 100644 index 00000000..bdc33e65 --- /dev/null +++ b/kolourpaint/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp @@ -0,0 +1,486 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND 0 +#define DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG 0 + + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +kpTransformResizeScaleCommand::kpTransformResizeScaleCommand (bool actOnSelection, + int newWidth, int newHeight, + Type type, + kpCommandEnvironment *environ) + : kpCommand (environ), + m_actOnSelection (actOnSelection), + m_type (type), + m_backgroundColor (environ->backgroundColor ()), + m_oldSelectionPtr (0) +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + m_oldWidth = doc->width (m_actOnSelection); + m_oldHeight = doc->height (m_actOnSelection); + + m_actOnTextSelection = (m_actOnSelection && + doc->textSelection ()); + + resize (newWidth, newHeight); + + // If we have a selection _border_ (but not a floating selection), + // then scale the selection with the document + m_scaleSelectionWithImage = (!m_actOnSelection && + (m_type == Scale || m_type == SmoothScale) && + document ()->selection () && + !document ()->selection ()->hasContent ()); +} + +kpTransformResizeScaleCommand::~kpTransformResizeScaleCommand () +{ + delete m_oldSelectionPtr; +} + + +// public virtual [base kpCommand] +QString kpTransformResizeScaleCommand::name () const +{ + if (m_actOnSelection) + { + if (m_actOnTextSelection) + { + if (m_type == Resize) + return i18n ("Text: Resize Box"); + } + else + { + if (m_type == Scale) + return i18n ("Selection: Scale"); + else if (m_type == SmoothScale) + return i18n ("Selection: Smooth Scale"); + } + } + else + { + switch (m_type) + { + case Resize: + return i18n ("Resize"); + case Scale: + return i18n ("Scale"); + case SmoothScale: + return i18n ("Smooth Scale"); + } + } + + return QString (); +} + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpTransformResizeScaleCommand::size () const +{ + return ImageSize (m_oldImage) + + ImageSize (m_oldRightImage) + + ImageSize (m_oldBottomImage) + + SelectionSize (m_oldSelectionPtr); +} + + +// public +int kpTransformResizeScaleCommand::newWidth () const +{ + return m_newWidth; +} + +// public +void kpTransformResizeScaleCommand::setNewWidth (int width) +{ + resize (width, newHeight ()); +} + + +// public +int kpTransformResizeScaleCommand::newHeight () const +{ + return m_newHeight; +} + +// public +void kpTransformResizeScaleCommand::setNewHeight (int height) +{ + resize (newWidth (), height); +} + + +// public +QSize kpTransformResizeScaleCommand::newSize () const +{ + return QSize (newWidth (), newHeight ()); +} + +// public virtual +void kpTransformResizeScaleCommand::resize (int width, int height) +{ + m_newWidth = width; + m_newHeight = height; + + m_isLosslessScale = ((m_type == Scale) && + (m_newWidth / m_oldWidth * m_oldWidth == m_newWidth) && + (m_newHeight / m_oldHeight * m_oldHeight == m_newHeight)); +} + + +// public +bool kpTransformResizeScaleCommand::scaleSelectionWithImage () const +{ + return m_scaleSelectionWithImage; +} + + +// private +void kpTransformResizeScaleCommand::scaleSelectionRegionWithDocument () +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND + kDebug () << "kpTransformResizeScaleCommand::scaleSelectionRegionWithDocument" + << endl; +#endif + + Q_ASSERT (m_oldSelectionPtr); + Q_ASSERT (!m_oldSelectionPtr->hasContent ()); + + + const double horizScale = double (m_newWidth) / double (m_oldWidth); + const double vertScale = double (m_newHeight) / double (m_oldHeight); + + const int newX = (int) (m_oldSelectionPtr->x () * horizScale); + const int newY = (int) (m_oldSelectionPtr->y () * vertScale); + + + QPolygon currentPoints = m_oldSelectionPtr->calculatePoints (); + currentPoints.translate (-currentPoints.boundingRect ().x (), + -currentPoints.boundingRect ().y ()); + + // TODO: refactor into kpPixmapFX + // TODO: Can we get to size 0x0 accidently? + QMatrix scaleMatrix; + scaleMatrix.scale (horizScale, vertScale); + currentPoints = scaleMatrix.map (currentPoints); + + currentPoints.translate ( + -currentPoints.boundingRect ().x () + newX, + -currentPoints.boundingRect ().y () + newY); + + kpAbstractImageSelection *imageSel = + dynamic_cast (m_oldSelectionPtr); + kpTextSelection *textSel = + dynamic_cast (m_oldSelectionPtr); + if (imageSel) + { + document ()->setSelection ( + kpFreeFormImageSelection (currentPoints, kpImage (), + imageSel->transparency ())); + } + else if (textSel) + { + document ()->setSelection ( + kpTextSelection (currentPoints.boundingRect (), + textSel->textLines (), + textSel->textStyle ())); + } + else + Q_ASSERT (!"Unknown selection type"); + + + environ ()->somethingBelowTheCursorChanged (); +} + + +// public virtual [base kpCommand] +void kpTransformResizeScaleCommand::execute () +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND + kDebug () << "kpTransformResizeScaleCommand::execute() type=" + << (int) m_type + << " oldWidth=" << m_oldWidth + << " oldHeight=" << m_oldHeight + << " newWidth=" << m_newWidth + << " newHeight=" << m_newHeight + << endl; +#endif + + if (m_oldWidth == m_newWidth && m_oldHeight == m_newHeight) + return; + + if (m_type == Resize) + { + if (m_actOnSelection) + { + if (!m_actOnTextSelection) + Q_ASSERT (!"kpTransformResizeScaleCommand::execute() resizing sel doesn't make sense"); + + QApplication::setOverrideCursor (Qt::WaitCursor); + + kpTextSelection *textSel = textSelection (); + Q_ASSERT (textSel); + + kpTextSelection *newSel = textSel->resized (m_newWidth, m_newHeight); + document ()->setSelection (*newSel); + delete newSel; + + environ ()->somethingBelowTheCursorChanged (); + + QApplication::restoreOverrideCursor (); + } + else + { + QApplication::setOverrideCursor (Qt::WaitCursor); + + + if (m_newWidth < m_oldWidth) + { + m_oldRightImage = document ()->getImageAt ( + QRect (m_newWidth, 0, + m_oldWidth - m_newWidth, m_oldHeight)); + } + + if (m_newHeight < m_oldHeight) + { + m_oldBottomImage = document ()->getImageAt ( + QRect (0, m_newHeight, + m_newWidth, m_oldHeight - m_newHeight)); + } + + document ()->resize (m_newWidth, m_newHeight, m_backgroundColor); + + + QApplication::restoreOverrideCursor (); + } + } + // Scale + else + { + QApplication::setOverrideCursor (Qt::WaitCursor); + + + kpImage oldImage = document ()->image (m_actOnSelection); + + if (!m_isLosslessScale) + m_oldImage = oldImage; + + kpImage newImage = kpPixmapFX::scale (oldImage, m_newWidth, m_newHeight, + m_type == SmoothScale); + + + if (!m_oldSelectionPtr && document ()->selection ()) + { + // Save sel border + m_oldSelectionPtr = document ()->selection ()->clone (); + m_oldSelectionPtr->deleteContent (); + } + + if (m_actOnSelection) + { + if (m_actOnTextSelection) + Q_ASSERT (!"kpTransformResizeScaleCommand::execute() scaling text sel doesn't make sense"); + + Q_ASSERT (m_oldSelectionPtr); + if ( !m_oldSelectionPtr ) // make coverity happy + return; + + QRect newRect = QRect (m_oldSelectionPtr->x (), m_oldSelectionPtr->y (), + newImage.width (), newImage.height ()); + + // Not possible to retain non-rectangular selection borders on scale + // (think about e.g. a 45 deg line as part of the border & 2x scale) + Q_ASSERT (dynamic_cast (m_oldSelectionPtr)); + document ()->setSelection ( + kpRectangularImageSelection (newRect, newImage, + static_cast (m_oldSelectionPtr) + ->transparency ())); + + environ ()->somethingBelowTheCursorChanged (); + } + else + { + document ()->setImage (newImage); + + if (m_scaleSelectionWithImage) + { + scaleSelectionRegionWithDocument (); + } + } + + + QApplication::restoreOverrideCursor (); + } +} + +// public virtual [base kpCommand] +void kpTransformResizeScaleCommand::unexecute () +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND + kDebug () << "kpTransformResizeScaleCommand::unexecute() type=" + << m_type << endl; +#endif + + if (m_oldWidth == m_newWidth && m_oldHeight == m_newHeight) + return; + + kpDocument *doc = document (); + Q_ASSERT (doc); + + if (m_type == Resize) + { + if (m_actOnSelection) + { + if (!m_actOnTextSelection) + Q_ASSERT (!"kpTransformResizeScaleCommand::unexecute() resizing sel doesn't make sense"); + + QApplication::setOverrideCursor (Qt::WaitCursor); + + kpTextSelection *textSel = textSelection (); + Q_ASSERT (textSel); + + kpTextSelection *newSel = textSel->resized (m_oldWidth, m_oldHeight); + document ()->setSelection (*newSel); + delete newSel; + + environ ()->somethingBelowTheCursorChanged (); + + QApplication::restoreOverrideCursor (); + } + else + { + QApplication::setOverrideCursor (Qt::WaitCursor); + + + kpImage newImage (m_oldWidth, m_oldHeight, QImage::Format_ARGB32_Premultiplied); + + kpPixmapFX::setPixmapAt (&newImage, QPoint (0, 0), + doc->image ()); + + if (m_newWidth < m_oldWidth) + { + kpPixmapFX::setPixmapAt (&newImage, + QPoint (m_newWidth, 0), + m_oldRightImage); + } + + if (m_newHeight < m_oldHeight) + { + kpPixmapFX::setPixmapAt (&newImage, + QPoint (0, m_newHeight), + m_oldBottomImage); + } + + doc->setImage (newImage); + + + QApplication::restoreOverrideCursor (); + } + } + // Scale + else + { + QApplication::setOverrideCursor (Qt::WaitCursor); + + + kpImage oldImage; + + if (!m_isLosslessScale) + oldImage = m_oldImage; + else + oldImage = kpPixmapFX::scale (doc->image (m_actOnSelection), + m_oldWidth, m_oldHeight); + + + if (m_actOnSelection) + { + if (m_actOnTextSelection) + Q_ASSERT (!"kpTransformResizeScaleCommand::unexecute() scaling text sel doesn't make sense"); + + Q_ASSERT (dynamic_cast (m_oldSelectionPtr)); + kpAbstractImageSelection *oldImageSel = + static_cast (m_oldSelectionPtr); + + kpAbstractImageSelection *oldSelection = oldImageSel->clone (); + oldSelection->setBaseImage (oldImage); + doc->setSelection (*oldSelection); + delete oldSelection; + + environ ()->somethingBelowTheCursorChanged (); + } + else + { + doc->setImage (oldImage); + + if (m_scaleSelectionWithImage) + { + doc->setSelection (*m_oldSelectionPtr); + + environ ()->somethingBelowTheCursorChanged (); + } + } + + + QApplication::restoreOverrideCursor (); + } +} + diff --git a/kolourpaint/commands/imagelib/transforms/kpTransformResizeScaleCommand.h b/kolourpaint/commands/imagelib/transforms/kpTransformResizeScaleCommand.h new file mode 100644 index 00000000..ea29e0fe --- /dev/null +++ b/kolourpaint/commands/imagelib/transforms/kpTransformResizeScaleCommand.h @@ -0,0 +1,102 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformResizeScaleCommand_H +#define kpTransformResizeScaleCommand_H + + +#include + +#include + +#include +#include +#include + + +class QSize; + +class kpAbstractSelection; + + +// REFACTOR: Split into multiple classes, each doing a different thing +// e.g. resize, scale and smooth scale. +// REFACTOR: Replace kpToolSelectionResizeScaleCommand with us. +class kpTransformResizeScaleCommand : public kpCommand +{ +public: + enum Type + { + Resize, Scale, SmoothScale + }; + + kpTransformResizeScaleCommand (bool actOnSelection, + int newWidth, int newHeight, + Type type, + kpCommandEnvironment *environ); + virtual ~kpTransformResizeScaleCommand (); + + virtual QString name () const; + virtual SizeType size () const; + +public: + int newWidth () const; + void setNewWidth (int width); + + int newHeight () const; + void setNewHeight (int height); + + QSize newSize () const; + virtual void resize (int width, int height); + +public: + bool scaleSelectionWithImage () const; + +private: + void scaleSelectionRegionWithDocument (); + +public: + virtual void execute (); + virtual void unexecute (); + +protected: + bool m_actOnSelection; + int m_newWidth, m_newHeight; + Type m_type; + bool m_isLosslessScale; + bool m_scaleSelectionWithImage; + kpColor m_backgroundColor; + + int m_oldWidth, m_oldHeight; + bool m_actOnTextSelection; + kpImage m_oldImage, m_oldRightImage, m_oldBottomImage; + kpAbstractSelection *m_oldSelectionPtr; +}; + + +#endif // kpTransformResizeScaleCommand_H diff --git a/kolourpaint/commands/imagelib/transforms/kpTransformRotateCommand.cpp b/kolourpaint/commands/imagelib/transforms/kpTransformRotateCommand.cpp new file mode 100644 index 00000000..4eb96b1d --- /dev/null +++ b/kolourpaint/commands/imagelib/transforms/kpTransformRotateCommand.cpp @@ -0,0 +1,223 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_ROTATE 0 + + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +kpTransformRotateCommand::kpTransformRotateCommand (bool actOnSelection, + double angle, + kpCommandEnvironment *environ) + : kpCommand (environ), + m_actOnSelection (actOnSelection), + m_angle (angle), + m_backgroundColor (environ->backgroundColor (actOnSelection)), + m_losslessRotation (kpPixmapFX::isLosslessRotation (angle)), + m_oldSelectionPtr (0) +{ +} + +kpTransformRotateCommand::~kpTransformRotateCommand () +{ + delete m_oldSelectionPtr; +} + + +// public virtual [base kpCommand] +QString kpTransformRotateCommand::name () const +{ + QString opName = i18n ("Rotate"); + + if (m_actOnSelection) + return i18n ("Selection: %1", opName); + else + return opName; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpTransformRotateCommand::size () const +{ + return ImageSize (m_oldImage) + + SelectionSize (m_oldSelectionPtr); +} + + +// public virtual [base kpCommand] +void kpTransformRotateCommand::execute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + + QApplication::setOverrideCursor (Qt::WaitCursor); + + + if (!m_losslessRotation) + m_oldImage = doc->image (m_actOnSelection); + + + kpImage newImage = kpPixmapFX::rotate (doc->image (m_actOnSelection), + m_angle, + m_backgroundColor); + + if (!m_actOnSelection) + doc->setImage (newImage); + else + { + kpAbstractImageSelection *sel = doc->imageSelection (); + Q_ASSERT (sel); + + // Save old selection + m_oldSelectionPtr = sel->clone (); + + // Conserve memmory: + // + // 1. If it's a lossless rotation, we don't need to the store old + // image anywhere at all, as we can reconstruct it by rotating in + // reverse. + // 2. If it's not a lossless rotation, "m_oldImage" already holds + // a copy of the old image. In this case, we actually save very + // little with this line (just, the computed transpareny mask) since + // kpImage is copy-on-write. + m_oldSelectionPtr->setBaseImage (kpImage ()); + + + // Calculate new top left (so selection rotates about center) + // (the Times2 trickery is used to reduce integer division error without + // resorting to the troublesome world of floating point) + QPoint oldCenterTimes2 (sel->x () * 2 + sel->width (), + sel->y () * 2 + sel->height ()); + QPoint newTopLeftTimes2 (oldCenterTimes2 - QPoint (newImage.width (), newImage.height ())); + QPoint newTopLeft (newTopLeftTimes2.x () / 2, newTopLeftTimes2.y () / 2); + + + // Calculate rotated points + QPolygon currentPoints = sel->calculatePoints (); + currentPoints.translate (-currentPoints.boundingRect ().x (), + -currentPoints.boundingRect ().y ()); + QMatrix rotateMatrix = kpPixmapFX::rotateMatrix (doc->image (m_actOnSelection), m_angle); + currentPoints = rotateMatrix.map (currentPoints); + currentPoints.translate (-currentPoints.boundingRect ().x () + newTopLeft.x (), + -currentPoints.boundingRect ().y () + newTopLeft.y ()); + + + if (currentPoints.boundingRect ().width () == newImage.width () && + currentPoints.boundingRect ().height () == newImage.height ()) + { + doc->setSelection ( + kpFreeFormImageSelection ( + currentPoints, newImage, + m_oldSelectionPtr->transparency ())); + } + else + { + // TODO: fix the latter "victim of" problem in kpAbstractImageSelection by + // allowing the border width & height != pixmap width & height + // Or maybe autocrop? + #if DEBUG_KP_TOOL_ROTATE + kDebug () << "kpTransformRotateCommand::execute() currentPoints.boundingRect=" + << currentPoints.boundingRect () + << " newPixmap: w=" << newImage.width () + << " h=" << newImage.height () + << " (victim of rounding error and/or rotated-a-(rectangular)-pixmap-that-was-transparent-in-the-corners-making-sel-uselessly-bigger-than-needs-be)" + << endl; + #endif + doc->setSelection ( + kpRectangularImageSelection ( + QRect (newTopLeft.x (), newTopLeft.y (), + newImage.width (), newImage.height ()), + newImage, + m_oldSelectionPtr->transparency ())); + } + + environ ()->somethingBelowTheCursorChanged (); + } + + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpCommand] +void kpTransformRotateCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + + QApplication::setOverrideCursor (Qt::WaitCursor); + + + kpImage oldImage; + + if (!m_losslessRotation) + { + oldImage = m_oldImage; + m_oldImage = kpImage (); + } + else + { + oldImage = kpPixmapFX::rotate (doc->image (m_actOnSelection), + 360 - m_angle, + m_backgroundColor); + } + + + if (!m_actOnSelection) + doc->setImage (oldImage); + else + { + m_oldSelectionPtr->setBaseImage (oldImage); + doc->setSelection (*m_oldSelectionPtr); + delete m_oldSelectionPtr; m_oldSelectionPtr = 0; + + environ ()->somethingBelowTheCursorChanged (); + } + + + QApplication::restoreOverrideCursor (); +} + diff --git a/kolourpaint/commands/imagelib/transforms/kpTransformRotateCommand.h b/kolourpaint/commands/imagelib/transforms/kpTransformRotateCommand.h new file mode 100644 index 00000000..40c903a7 --- /dev/null +++ b/kolourpaint/commands/imagelib/transforms/kpTransformRotateCommand.h @@ -0,0 +1,68 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformRotateCommand_H +#define kpTransformRotateCommand_H + + +#include +#include +#include + + +class kpAbstractImageSelection; + + +class kpTransformRotateCommand : public kpCommand +{ +public: + kpTransformRotateCommand (bool actOnSelection, + double angle, // 0 <= angle < 360 (clockwise) + kpCommandEnvironment *environ); + virtual ~kpTransformRotateCommand (); + + virtual QString name () const; + + virtual SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + bool m_actOnSelection; + double m_angle; + + kpColor m_backgroundColor; + + bool m_losslessRotation; + kpImage m_oldImage; + kpAbstractImageSelection *m_oldSelectionPtr; +}; + + +#endif // kpTransformRotateCommand_H diff --git a/kolourpaint/commands/imagelib/transforms/kpTransformSkewCommand.cpp b/kolourpaint/commands/imagelib/transforms/kpTransformSkewCommand.cpp new file mode 100644 index 00000000..acf74c32 --- /dev/null +++ b/kolourpaint/commands/imagelib/transforms/kpTransformSkewCommand.cpp @@ -0,0 +1,199 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SKEW 0 +#define DEBUG_KP_TOOL_SKEW_DIALOG 0 + + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +// TODO: nasty, should avoid using GUI class in this command class +#include + + +kpTransformSkewCommand::kpTransformSkewCommand (bool actOnSelection, + int hangle, int vangle, + kpCommandEnvironment *environ) + : kpCommand (environ), + m_actOnSelection (actOnSelection), + m_hangle (hangle), m_vangle (vangle), + m_backgroundColor (environ->backgroundColor (actOnSelection)), + m_oldSelectionPtr (0) +{ +} + +kpTransformSkewCommand::~kpTransformSkewCommand () +{ + delete m_oldSelectionPtr; +} + + +// public virtual [base kpCommand] +QString kpTransformSkewCommand::name () const +{ + QString opName = i18n ("Skew"); + + if (m_actOnSelection) + return i18n ("Selection: %1", opName); + else + return opName; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpTransformSkewCommand::size () const +{ + return ImageSize (m_oldImage) + + SelectionSize (m_oldSelectionPtr); +} + + +// public virtual [base kpCommand] +void kpTransformSkewCommand::execute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + + QApplication::setOverrideCursor (Qt::WaitCursor); + + + kpImage newImage = kpPixmapFX::skew (doc->image (m_actOnSelection), + kpTransformSkewDialog::horizontalAngleForPixmapFX (m_hangle), + kpTransformSkewDialog::verticalAngleForPixmapFX (m_vangle), + m_backgroundColor); + + if (!m_actOnSelection) + { + m_oldImage = doc->image (m_actOnSelection); + + doc->setImage (newImage); + } + else + { + kpAbstractImageSelection *sel = doc->imageSelection (); + Q_ASSERT (sel); + + // Save old selection + m_oldSelectionPtr = sel->clone (); + + + // Calculate skewed points + QPolygon currentPoints = sel->calculatePoints (); + currentPoints.translate (-currentPoints.boundingRect ().x (), + -currentPoints.boundingRect ().y ()); + QMatrix skewMatrix = kpPixmapFX::skewMatrix ( + doc->image (m_actOnSelection), + kpTransformSkewDialog::horizontalAngleForPixmapFX (m_hangle), + kpTransformSkewDialog::verticalAngleForPixmapFX (m_vangle)); + currentPoints = skewMatrix.map (currentPoints); + currentPoints.translate (-currentPoints.boundingRect ().x () + m_oldSelectionPtr->x (), + -currentPoints.boundingRect ().y () + m_oldSelectionPtr->y ()); + + + if (currentPoints.boundingRect ().width () == newImage.width () && + currentPoints.boundingRect ().height () == newImage.height ()) + { + doc->setSelection ( + kpFreeFormImageSelection ( + currentPoints, newImage, + m_oldSelectionPtr->transparency ())); + } + else + { + // TODO: fix the latter "victim of" problem in kpAbstractImageSelection by + // allowing the border width & height != pixmap width & height + // Or maybe autocrop? + #if DEBUG_KP_TOOL_SKEW + kDebug () << "kpTransformSkewCommand::execute() currentPoints.boundingRect=" + << currentPoints.boundingRect () + << " newPixmap: w=" << newImage.width () + << " h=" << newImage.height () + << " (victim of rounding error and/or skewed-a-(rectangular)-pixmap-that-was-transparent-in-the-corners-making-sel-uselessly-bigger-than-needs-be))" + << endl; + #endif + doc->setSelection ( + kpRectangularImageSelection ( + QRect (currentPoints.boundingRect ().x (), + currentPoints.boundingRect ().y (), + newImage.width (), + newImage.height ()), + newImage, + m_oldSelectionPtr->transparency ())); + } + + environ ()->somethingBelowTheCursorChanged (); + } + + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpCommand] +void kpTransformSkewCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + + QApplication::setOverrideCursor (Qt::WaitCursor); + + + if (!m_actOnSelection) + { + doc->setImage (m_oldImage); + m_oldImage = kpImage (); + } + else + { + doc->setSelection (*m_oldSelectionPtr); + delete m_oldSelectionPtr; m_oldSelectionPtr = 0; + + environ ()->somethingBelowTheCursorChanged (); + } + + + QApplication::restoreOverrideCursor (); +} + diff --git a/kolourpaint/commands/imagelib/transforms/kpTransformSkewCommand.h b/kolourpaint/commands/imagelib/transforms/kpTransformSkewCommand.h new file mode 100644 index 00000000..4f96ece4 --- /dev/null +++ b/kolourpaint/commands/imagelib/transforms/kpTransformSkewCommand.h @@ -0,0 +1,66 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformSkewCommand_H +#define kpTransformSkewCommand_H + + +#include +#include +#include + + +class kpAbstractSelection; + + +class kpTransformSkewCommand : public kpCommand +{ +public: + kpTransformSkewCommand (bool actOnSelection, + int hangle, int vangle, + kpCommandEnvironment *environ); + virtual ~kpTransformSkewCommand (); + + virtual QString name () const; + + virtual SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + bool m_actOnSelection; + int m_hangle, m_vangle; + + kpColor m_backgroundColor; + kpImage m_oldImage; + kpAbstractImageSelection *m_oldSelectionPtr; +}; + + +#endif // kpTransformSkewCommand_H diff --git a/kolourpaint/commands/kpCommand.cpp b/kolourpaint/commands/kpCommand.cpp new file mode 100644 index 00000000..6a2b028f --- /dev/null +++ b/kolourpaint/commands/kpCommand.cpp @@ -0,0 +1,85 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COMMAND_HISTORY 0 + + +#include + +#include + + +kpCommand::kpCommand (kpCommandEnvironment *environ) + : m_environ (environ) +{ + Q_ASSERT (environ); +} + +kpCommand::~kpCommand () +{ +} + + +kpCommandEnvironment *kpCommand::environ () const +{ + return m_environ; +} + + +// protected +kpDocument *kpCommand::document () const +{ + return m_environ->document (); +} + + +// protected +kpAbstractSelection *kpCommand::selection () const +{ + return m_environ->selection (); +} + +// protected +kpAbstractImageSelection *kpCommand::imageSelection () const +{ + return m_environ->imageSelection (); +} + +// protected +kpTextSelection *kpCommand::textSelection () const +{ + return m_environ->textSelection (); +} + + +// protected +kpViewManager *kpCommand::viewManager () const +{ + return m_environ->viewManager (); +} + diff --git a/kolourpaint/commands/kpCommand.h b/kolourpaint/commands/kpCommand.h new file mode 100644 index 00000000..7824811f --- /dev/null +++ b/kolourpaint/commands/kpCommand.h @@ -0,0 +1,91 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpCommand_H +#define kpCommand_H + + +#include +#undef environ // macro on win32 + + +class QString; + +class kpAbstractImageSelection; +class kpAbstractSelection; +class kpCommandEnvironment; +class kpDocument; +class kpMainWindow; +class kpTextSelection; +class kpViewManager; + + +class kpCommand : public kpCommandSize +{ +public: + kpCommand (kpCommandEnvironment *environ); + virtual ~kpCommand (); + +public: + virtual QString name () const = 0; + + // Returns the estimated size in bytes. + // + // You only have to factor in the size of variables that change according + // to the amount of input e.g. pixmap size, text size. There is no need + // to include the size of O(1) variables unless they are huge. + // + // If in doubt, return the largest possible amount of memory that your + // command will take. This is better than making the user unexpectedly + // run out of memory. + // + // Implement this by measuring the size of all of your fields, using + // kpCommandSize. + virtual SizeType size () const = 0; + + virtual void execute () = 0; + virtual void unexecute () = 0; + +protected: + kpCommandEnvironment *environ () const; + + // Commonly used accessors - simply forwards to environ(). + kpDocument *document () const; + + kpAbstractSelection *selection () const; + kpAbstractImageSelection *imageSelection () const; + kpTextSelection *textSelection () const; + + kpViewManager *viewManager () const; + +private: + kpCommandEnvironment * const m_environ; +}; + + +#endif // kpCommand_H diff --git a/kolourpaint/commands/kpCommandHistory.cpp b/kolourpaint/commands/kpCommandHistory.cpp new file mode 100644 index 00000000..bff5a186 --- /dev/null +++ b/kolourpaint/commands/kpCommandHistory.cpp @@ -0,0 +1,128 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COMMAND_HISTORY 0 + + +#include + +#include +#include +#include +#include + + +kpCommandHistory::kpCommandHistory (bool doReadConfig, kpMainWindow *mainWindow) + : kpCommandHistoryBase (doReadConfig, mainWindow->actionCollection ()), + m_mainWindow (mainWindow) +{ +} + +kpCommandHistory::~kpCommandHistory () +{ +} + + +static bool NextUndoCommandIsCreateBorder (kpCommandHistory *commandHistory) +{ + Q_ASSERT (commandHistory); + + kpCommand *cmd = commandHistory->nextUndoCommand (); + if (!cmd) + return false; + + kpToolSelectionCreateCommand *c = dynamic_cast (cmd); + if (!c) + return false; + + const kpAbstractSelection *sel = c->fromSelection (); + Q_ASSERT (sel); + + return (!sel->hasContent ()); +} + +// public +void kpCommandHistory::addCreateSelectionCommand (kpToolSelectionCreateCommand *cmd, + bool execute) +{ + if (cmd->fromSelection ()->hasContent ()) + { + addCommand (cmd, execute); + return; + } + + if (::NextUndoCommandIsCreateBorder (this)) + { + setNextUndoCommand (cmd); + if (execute) + cmd->execute (); + } + else + addCommand (cmd, execute); +} + +//--------------------------------------------------------------------- + +// public slot virtual [base KCommandHistory] +void kpCommandHistory::undo () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistory::undo() CALLED!"; +#endif + if (m_mainWindow && m_mainWindow->toolHasBegunShape ()) + { + #if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\thas begun shape - cancel draw"; + #endif + m_mainWindow->tool ()->cancelShapeInternal (); + } + else + kpCommandHistoryBase::undo (); +} + +//--------------------------------------------------------------------- + +// public slot virtual [base KCommandHistory] +void kpCommandHistory::redo () +{ + if (m_mainWindow && m_mainWindow->toolHasBegunShape ()) + { + // Not completely obvious but what else can we do? + // + // Ignoring the request would not be intuitive for tools like + // Polygon & Polyline (where it's not always apparent to the user + // that s/he's still drawing a shape even though the mouse isn't + // down). + m_mainWindow->tool ()->cancelShapeInternal (); + } + else + kpCommandHistoryBase::redo (); +} + + +#include diff --git a/kolourpaint/commands/kpCommandHistory.h b/kolourpaint/commands/kpCommandHistory.h new file mode 100644 index 00000000..199eed1e --- /dev/null +++ b/kolourpaint/commands/kpCommandHistory.h @@ -0,0 +1,105 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpCommandHistory_H +#define kpCommandHistory_H + + +#include + + +class kpToolSelectionCreateCommand; + + +// +// KolourPaint-specific command history functionality. +// +// Intercepts Undo/Redo requests: +// +// If the user is currently drawing a shape, it cancels it. +// Else it passes on the Undo/Redo request to kpCommandHistoryBase. +// +// TODO: This is wrong. It won't work if the Undo action is disabled, +// for instance. Later: What about kpToolText::viewEvent()'s use of +// QEvent::ShortcutOverride? +// +// Maybe the real solution is to call kpCommandHistoryBase::addCommand() +// as _soon_ as the shape starts - not after it ends. But the +// trouble with this solution is that if the user Undoes/cancels +// the shape s/he's currently drawing, it would replace a Redo +// slot in the history. Arguably you shouldn't be able to Redo +// something you never finished drawing. +// +// The solution is to add this functionality to kpCommandHistoryBase. +// +class kpCommandHistory : public kpCommandHistoryBase +{ +Q_OBJECT + +public: + kpCommandHistory (bool doReadConfig, kpMainWindow *mainWindow); + virtual ~kpCommandHistory (); + +public: + // Same as addCommand(), except that this has a more desirable behavior + // when adding a selection border creation command: If the next undo command + // also creates a selection border, it overwrites that command + // with the given , instead of adding to the undo history. + // + // This helps to reduce the number of consecutive selection border + // creation commands in the history. Exactly one border creation + // command before each "real" selection command is useful as it allows + // users to undo just that "real" operation and then do a different "real" + // operation with the same border (as sometimes, exact borders are difficult + // to recreate). However, multiple consecutive border creation + // commands get annoying since none of them mutate the document, + // so if the user has not done a "real" command with the last selection + // border (i.e. the next undo command), what this method is saying is + // that the user wanted to throw away that border drag anyway. + // + // This special behavior is perfectly safe since border creation commands + // do not mutate the document. + // + // If creates a selection that is not just a border, this + // method has the same effect as addCommand(). + // + // REFACTOR: Why not just override addCommand() and test if it was given a + // kpToolSelectionCreateCommand? + void addCreateSelectionCommand (kpToolSelectionCreateCommand *cmd, + bool execute = true); + +public slots: + virtual void undo (); + virtual void redo (); + +protected: + kpMainWindow *m_mainWindow; +}; + + +#endif // kpCommandHistory_H diff --git a/kolourpaint/commands/kpCommandHistoryBase.cpp b/kolourpaint/commands/kpCommandHistoryBase.cpp new file mode 100644 index 00000000..80d2bbbc --- /dev/null +++ b/kolourpaint/commands/kpCommandHistoryBase.cpp @@ -0,0 +1,752 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COMMAND_HISTORY 0 + + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +//template +static void ClearPointerList (QLinkedList *listPtr) +{ + if (!listPtr) + return; + + qDeleteAll (listPtr->begin (), listPtr->end ()); + + listPtr->clear (); +} + + +struct kpCommandHistoryBasePrivate +{ +}; + + +kpCommandHistoryBase::kpCommandHistoryBase (bool doReadConfig, + KActionCollection *ac) + : d (new kpCommandHistoryBasePrivate ()) +{ + m_actionUndo = new KToolBarPopupAction (KIcon ("edit-undo"), undoActionText (), this); + ac->addAction (KStandardAction::name (KStandardAction::Undo), m_actionUndo); + m_actionUndo->setShortcuts (KStandardShortcut::shortcut (KStandardShortcut::Undo)); + connect (m_actionUndo, SIGNAL(triggered(bool)), this, SLOT (undo ())); + + m_actionRedo = new KToolBarPopupAction (KIcon ("edit-redo"), redoActionText (), this); + ac->addAction (KStandardAction::name (KStandardAction::Redo), m_actionRedo); + m_actionRedo->setShortcuts (KStandardShortcut::shortcut (KStandardShortcut::Redo)); + connect (m_actionRedo, SIGNAL(triggered(bool)), this, SLOT (redo ())); + + + m_actionUndo->setEnabled (false); + m_actionRedo->setEnabled (false); + + + connect (m_actionUndo->menu (), SIGNAL (triggered (QAction *)), + this, SLOT (undoUpToNumber (QAction *))); + connect (m_actionRedo->menu (), SIGNAL (triggered (QAction *)), + this, SLOT (redoUpToNumber (QAction *))); + + + m_undoMinLimit = 10; + m_undoMaxLimit = 500; + m_undoMaxLimitSizeLimit = 16 * 1048576; + + + m_documentRestoredPosition = 0; + + + if (doReadConfig) + readConfig (); +} + +kpCommandHistoryBase::~kpCommandHistoryBase () +{ + ::ClearPointerList (&m_undoCommandList); + ::ClearPointerList (&m_redoCommandList); + + delete d; +} + + +// public +int kpCommandHistoryBase::undoLimit () const +{ + return undoMinLimit (); +} + +// public +void kpCommandHistoryBase::setUndoLimit (int limit) +{ + setUndoMinLimit (limit); +} + + +// public +int kpCommandHistoryBase::undoMinLimit () const +{ + return m_undoMinLimit; +} + +// public +void kpCommandHistoryBase::setUndoMinLimit (int limit) +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::setUndoMinLimit(" + << limit << ")" + << endl; +#endif + + if (limit < 1 || limit > 5000/*"ought to be enough for anybody"*/) + { + kError () << "kpCommandHistoryBase::setUndoMinLimit(" + << limit << ")" + << endl; + return; + } + + if (limit == m_undoMinLimit) + return; + + m_undoMinLimit = limit; + trimCommandListsUpdateActions (); +} + + +// public +int kpCommandHistoryBase::undoMaxLimit () const +{ + return m_undoMaxLimit; +} + +// public +void kpCommandHistoryBase::setUndoMaxLimit (int limit) +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::setUndoMaxLimit(" + << limit << ")" + << endl; +#endif + + if (limit < 1 || limit > 5000/*"ought to be enough for anybody"*/) + { + kError () << "kpCommandHistoryBase::setUndoMaxLimit(" + << limit << ")" + << endl; + return; + } + + if (limit == m_undoMaxLimit) + return; + + m_undoMaxLimit = limit; + trimCommandListsUpdateActions (); +} + + +// public +kpCommandSize::SizeType kpCommandHistoryBase::undoMaxLimitSizeLimit () const +{ + return m_undoMaxLimitSizeLimit; +} + +// public +void kpCommandHistoryBase::setUndoMaxLimitSizeLimit (kpCommandSize::SizeType sizeLimit) +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::setUndoMaxLimitSizeLimit(" + << sizeLimit << ")" + << endl; +#endif + + if (sizeLimit < 0 || + sizeLimit > (500 * 1048576)/*"ought to be enough for anybody"*/) + { + kError () << "kpCommandHistoryBase::setUndoMaxLimitSizeLimit(" + << sizeLimit << ")" + << endl; + return; + } + + if (sizeLimit == m_undoMaxLimitSizeLimit) + return; + + m_undoMaxLimitSizeLimit = sizeLimit; + trimCommandListsUpdateActions (); +} + + +// public +void kpCommandHistoryBase::readConfig () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::readConfig()"; +#endif + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupUndoRedo); + + setUndoMinLimit (cfg.readEntry (kpSettingUndoMinLimit, undoMinLimit ())); + setUndoMaxLimit (cfg.readEntry (kpSettingUndoMaxLimit, undoMaxLimit ())); + setUndoMaxLimitSizeLimit ( + cfg.readEntry (kpSettingUndoMaxLimitSizeLimit, + undoMaxLimitSizeLimit ())); + + trimCommandListsUpdateActions (); +} + +// public +void kpCommandHistoryBase::writeConfig () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::writeConfig()"; +#endif + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupUndoRedo); + + cfg.writeEntry (kpSettingUndoMinLimit, undoMinLimit ()); + cfg.writeEntry (kpSettingUndoMaxLimit, undoMaxLimit ()); + cfg.writeEntry ( + kpSettingUndoMaxLimitSizeLimit, undoMaxLimitSizeLimit ()); + + cfg.sync (); +} + + +// public +void kpCommandHistoryBase::addCommand (kpCommand *command, bool execute) +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::addCommand(" + << command + << ",execute=" << execute << ")" + << endl; +#endif + + if (execute) + command->execute (); + + m_undoCommandList.push_front (command); + ::ClearPointerList (&m_redoCommandList); + +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; +#endif + if (m_documentRestoredPosition != INT_MAX) + { + if (m_documentRestoredPosition > 0) + m_documentRestoredPosition = INT_MAX; + else + m_documentRestoredPosition--; + #if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; + #endif + } + + trimCommandListsUpdateActions (); +} + +// public +void kpCommandHistoryBase::clear () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::clear()"; +#endif + + ::ClearPointerList (&m_undoCommandList); + ::ClearPointerList (&m_redoCommandList); + + m_documentRestoredPosition = 0; + + updateActions (); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpCommandHistoryBase::undoInternal () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::undoInternal()"; +#endif + + kpCommand *undoCommand = nextUndoCommand (); + if (!undoCommand) + return; + + undoCommand->unexecute (); + + + m_undoCommandList.erase (m_undoCommandList.begin ()); + m_redoCommandList.push_front (undoCommand); + + +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; +#endif + if (m_documentRestoredPosition != INT_MAX) + { + m_documentRestoredPosition++; + if (m_documentRestoredPosition == 0) + emit documentRestored (); + #if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; + #endif + } +} + +//--------------------------------------------------------------------- + +// protected slot +void kpCommandHistoryBase::redoInternal () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::redoInternal()"; +#endif + + kpCommand *redoCommand = nextRedoCommand (); + if (!redoCommand) + return; + + redoCommand->execute (); + + + m_redoCommandList.erase (m_redoCommandList.begin ()); + m_undoCommandList.push_front (redoCommand); + + +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; +#endif + if (m_documentRestoredPosition != INT_MAX) + { + m_documentRestoredPosition--; + if (m_documentRestoredPosition == 0) + emit documentRestored (); + #if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; + #endif + } +} + +//--------------------------------------------------------------------- + +// public slot virtual +void kpCommandHistoryBase::undo () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::undo()"; +#endif + + undoInternal (); + trimCommandListsUpdateActions (); +} + +//--------------------------------------------------------------------- + +// public slot virtual +void kpCommandHistoryBase::redo () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::redo()"; +#endif + + redoInternal (); + trimCommandListsUpdateActions (); +} + +//--------------------------------------------------------------------- + +// public slot virtual +void kpCommandHistoryBase::undoUpToNumber (QAction *which) +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::undoUpToNumber(" << which << ")"; +#endif + + for (int i = 0; + i <= which->data().toInt() && !m_undoCommandList.isEmpty (); + i++) + { + undoInternal (); + } + + trimCommandListsUpdateActions (); +} + +// public slot virtual +void kpCommandHistoryBase::redoUpToNumber (QAction *which) +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::redoUpToNumber(" << which << ")"; +#endif + + for (int i = 0; + i <= which->data().toInt() && !m_redoCommandList.isEmpty (); + i++) + { + redoInternal (); + } + + trimCommandListsUpdateActions (); +} + + +// protected +QString kpCommandHistoryBase::undoActionText () const +{ + kpCommand *undoCommand = nextUndoCommand (); + + if (undoCommand) + return i18n ("&Undo: %1", undoCommand->name ()); + else + return i18n ("&Undo"); +} + +// protected +QString kpCommandHistoryBase::redoActionText () const +{ + kpCommand *redoCommand = nextRedoCommand (); + + if (redoCommand) + return i18n ("&Redo: %1", redoCommand->name ()); + else + return i18n ("&Redo"); +} + + +// protected +QString kpCommandHistoryBase::undoActionToolTip () const +{ + kpCommand *undoCommand = nextUndoCommand (); + + if (undoCommand) + return i18n ("Undo: %1", undoCommand->name ()); + else + return i18n ("Undo"); +} + +// protected +QString kpCommandHistoryBase::redoActionToolTip () const +{ + kpCommand *redoCommand = nextRedoCommand (); + + if (redoCommand) + return i18n ("Redo: %1", redoCommand->name ()); + else + return i18n ("Redo"); +} + + +// protected +void kpCommandHistoryBase::trimCommandListsUpdateActions () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::trimCommandListsUpdateActions()"; +#endif + + trimCommandLists (); + updateActions (); +} + +// protected +void kpCommandHistoryBase::trimCommandList (QLinkedList *commandList) +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::trimCommandList()"; + QTime timer; timer.start (); +#endif + + if (!commandList) + { + kError () << "kpCommandHistoryBase::trimCommandList() passed 0 commandList" + << endl; + return; + } + + +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\tsize=" << commandList->size () + << " undoMinLimit=" << m_undoMinLimit + << " undoMaxLimit=" << m_undoMaxLimit + << " undoMaxLimitSizeLimit=" << m_undoMaxLimitSizeLimit + << endl; +#endif + if ((int) commandList->size () <= m_undoMinLimit) + { + #if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\t\tsize under undoMinLimit - done"; + #endif + return; + } + + +#if DEBUG_KP_COMMAND_HISTORY && 0 + kDebug () << "\tsize over undoMinLimit - iterating thru cmds:"; +#endif + + QLinkedList ::iterator it = commandList->begin (); + int upto = 0; + + kpCommandSize::SizeType sizeSoFar = 0; + + while (it != commandList->end ()) + { + bool advanceIt = true; + + if (sizeSoFar <= m_undoMaxLimitSizeLimit) + { + sizeSoFar += (*it)->size (); + } + + #if DEBUG_KP_COMMAND_HISTORY && 0 + kDebug () << "\t\t" << upto << ":" + << " name='" << (*it)->name () + << "' size=" << (*it)->size () + << " sizeSoFar=" << sizeSoFar + << endl; + #endif + + if (upto >= m_undoMinLimit) + { + if (upto >= m_undoMaxLimit || + sizeSoFar > m_undoMaxLimitSizeLimit) + { + #if DEBUG_KP_COMMAND_HISTORY && 0 + kDebug () << "\t\t\tkill"; + #endif + delete (*it); + it = m_undoCommandList.erase (it); + advanceIt = false; + } + } + + if (advanceIt) + it++; + upto++; + } + +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\ttook " << timer.elapsed () << "ms"; +#endif +} + +// protected +void kpCommandHistoryBase::trimCommandLists () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::trimCommandLists()"; +#endif + + trimCommandList (&m_undoCommandList); + trimCommandList (&m_redoCommandList); + +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; +#endif + if (m_documentRestoredPosition != INT_MAX) + { + #if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\t\tundoCmdList.size=" << m_undoCommandList.size () + << " redoCmdList.size=" << m_redoCommandList.size () + << endl; + #endif + if (m_documentRestoredPosition > (int) m_redoCommandList.size () || + -m_documentRestoredPosition > (int) m_undoCommandList.size ()) + { + #if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\t\t\tinvalidate documentRestoredPosition"; + #endif + m_documentRestoredPosition = INT_MAX; + } + } +} + + +static void populatePopupMenu (KMenu *popupMenu, + const QString &undoOrRedo, + const QLinkedList &commandList) +{ + if (!popupMenu) + return; + + popupMenu->clear (); + + QLinkedList ::const_iterator it = commandList.begin (); + int i = 0; + while (i < 10 && it != commandList.end ()) + { + QAction *action = new QAction(i18n ("%1: %2", undoOrRedo, (*it)->name ()), popupMenu); + action->setData(i); + popupMenu->addAction (action); + i++, it++; + } + + if (it != commandList.end ()) + { + // TODO: maybe have a scrollview show all the items instead, like KOffice in KDE 3 + // LOCOMPAT: should be centered text. + popupMenu->addTitle (i18np ("%1 more item", "%1 more items", + commandList.size () - i)); + } +} + + +// protected +void kpCommandHistoryBase::updateActions () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::updateActions()"; +#endif + + m_actionUndo->setEnabled ((bool) nextUndoCommand ()); + // Don't want to keep changing toolbar text. + // TODO: As a bad side-effect, the menu doesn't have "Undo: " + // anymore. In any case, the KDE4 KToolBarPopupAction + // sucks in menus as it forces the clicking of a submenu. IMO, + // there should be no submenu in the menu. + //m_actionUndo->setText (undoActionText ()); + + // But in icon mode, a tooltip with context is useful. + m_actionUndo->setToolTip (undoActionToolTip ()); +#if DEBUG_KP_COMMAND_HISTORY + QTime timer; timer.start (); +#endif + populatePopupMenu (qobject_cast (m_actionUndo->menu ()), + i18n ("Undo"), + m_undoCommandList); +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\tpopuplatePopupMenu undo=" << timer.elapsed () + << "ms" << endl;; +#endif + + m_actionRedo->setEnabled ((bool) nextRedoCommand ()); + // Don't want to keep changing toolbar text. + // TODO: As a bad side-effect, the menu doesn't have "Undo: " + // anymore. In any case, the KDE4 KToolBarPopupAction + // sucks in menus as it forces the clicking of a submenu. IMO, + // there should be no submenu in the menu. + //m_actionRedo->setText (redoActionText ()); + + // But in icon mode, a tooltip with context is useful. + m_actionRedo->setToolTip (redoActionToolTip ()); +#if DEBUG_KP_COMMAND_HISTORY + timer.restart (); +#endif + populatePopupMenu (qobject_cast (m_actionRedo->menu ()), + i18n ("Redo"), + m_redoCommandList); +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\tpopuplatePopupMenu redo=" << timer.elapsed () + << "ms" << endl; +#endif +} + + +// public +kpCommand *kpCommandHistoryBase::nextUndoCommand () const +{ + if (m_undoCommandList.isEmpty ()) + return 0; + + return m_undoCommandList.first (); +} + +// public +kpCommand *kpCommandHistoryBase::nextRedoCommand () const +{ + if (m_redoCommandList.isEmpty ()) + return 0; + + return m_redoCommandList.first (); +} + + +// public +void kpCommandHistoryBase::setNextUndoCommand (kpCommand *command) +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::setNextUndoCommand(" + << command + << ")" + << endl; +#endif + + if (m_undoCommandList.isEmpty ()) + return; + + + delete *m_undoCommandList.begin (); + *m_undoCommandList.begin () = command; + + + trimCommandListsUpdateActions (); +} + + +// public slot virtual +void kpCommandHistoryBase::documentSaved () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpCommandHistoryBase::documentSaved()"; +#endif + + m_documentRestoredPosition = 0; +} + + +#include diff --git a/kolourpaint/commands/kpCommandHistoryBase.h b/kolourpaint/commands/kpCommandHistoryBase.h new file mode 100644 index 00000000..87481367 --- /dev/null +++ b/kolourpaint/commands/kpCommandHistoryBase.h @@ -0,0 +1,155 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpCommandHistoryBase_H +#define kpCommandHistoryBase_H + + +#include +#include +#include + +#include + +class QAction; + +class KActionCollection; +class KToolBarPopupAction; + +class kpAbstractImageSelection; +class kpAbstractSelection; +class kpCommand; +class kpCommandEnvironment; +class kpDocument; +class kpMainWindow; +class kpTextSelection; +class kpViewManager; + + +// Clone of KCommandHistory with features required by KolourPaint but which +// could also be useful for other apps: +// - nextUndoCommand()/nextRedoCommand() +// - undo/redo history limited by both number and size +// +// Features not required by KolourPaint (e.g. commandExecuted()) are not +// implemented and undo limit == redo limit. So compared to +// KCommandHistory, this is only "almost source compatible". +class kpCommandHistoryBase : public QObject +{ +Q_OBJECT + +public: + kpCommandHistoryBase (bool doReadConfig, KActionCollection *ac); + virtual ~kpCommandHistoryBase (); + +public: + // (provided for compatibility with KCommandHistory) + int undoLimit () const; + void setUndoLimit (int limit); + + + int undoMinLimit () const; + void setUndoMinLimit (int limit); + + int undoMaxLimit () const; + void setUndoMaxLimit (int limit); + + kpCommandSize::SizeType undoMaxLimitSizeLimit () const; + void setUndoMaxLimitSizeLimit (kpCommandSize::SizeType sizeLimit); + +public: + // Read and write above config + void readConfig (); + void writeConfig (); + +public: + void addCommand (kpCommand *command, bool execute = true); + void clear (); + +protected slots: + // (same as undo() & redo() except they don't call + // trimCommandListsUpdateActions()) + void undoInternal (); + void redoInternal (); + +public slots: + virtual void undo (); + virtual void redo (); + + virtual void undoUpToNumber (QAction *which); + virtual void redoUpToNumber (QAction *which); + +protected: + QString undoActionText () const; + QString redoActionText () const; + + QString undoActionToolTip () const; + QString redoActionToolTip () const; + + void trimCommandListsUpdateActions (); + void trimCommandList (QLinkedList *commandList); + void trimCommandLists (); + void updateActions (); + +public: + kpCommand *nextUndoCommand () const; + kpCommand *nextRedoCommand () const; + + void setNextUndoCommand (kpCommand *command); + +public slots: + virtual void documentSaved (); + +signals: + void documentRestored (); + +protected: + KToolBarPopupAction *m_actionUndo, *m_actionRedo; + + // (Front element is the next one) + QLinkedList m_undoCommandList; + QLinkedList m_redoCommandList; + + int m_undoMinLimit, m_undoMaxLimit; + kpCommandSize::SizeType m_undoMaxLimitSizeLimit; + + // What you have to do to get back to the document's unmodified state: + // * -x: must Undo x times + // * 0: unmodified + // * +x: must Redo x times + // * INT_MAX: can never become unmodified again + // + // ASSUMPTION: will never have INT_MAX commands in any list. + int m_documentRestoredPosition; + +private: + struct kpCommandHistoryBasePrivate * const d; +}; + + +#endif // kpCommandHistoryBase_H diff --git a/kolourpaint/commands/kpCommandSize.cpp b/kolourpaint/commands/kpCommandSize.cpp new file mode 100644 index 00000000..65481f54 --- /dev/null +++ b/kolourpaint/commands/kpCommandSize.cpp @@ -0,0 +1,157 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COMMAND_SIZE 0 + + +#include + +#include +#include +#include + +#include + + +// public static +kpCommandSize::SizeType kpCommandSize::PixmapSize (const QImage &image) +{ + return kpCommandSize::PixmapSize (image.width (), image.height (), image.depth ()); +} + +// public static +kpCommandSize::SizeType kpCommandSize::PixmapSize (const QImage *image) +{ + return (image ? kpCommandSize::PixmapSize (*image) : 0); +} + +// public static +kpCommandSize::SizeType kpCommandSize::PixmapSize (int width, int height, int depth) +{ + // handle 15bpp + int roundedDepth = (depth > 8 ? (depth + 7) / 8 * 8 : depth); + kpCommandSize::SizeType ret = + (kpCommandSize::SizeType) width * height * roundedDepth / 8; + +#if DEBUG_KP_COMMAND_SIZE && 0 + kDebug () << "kpCommandSize::PixmapSize() w=" << width + << " h=" << height + << " d=" << depth + << " roundedDepth=" << roundedDepth + << " ret=" << ret + << endl; +#endif + return ret; +} + + +// public static +kpCommandSize::SizeType kpCommandSize::QImageSize (const QImage &image) +{ + return kpCommandSize::QImageSize (image.width (), image.height (), image.depth ()); +} + +// public static +kpCommandSize::SizeType kpCommandSize::QImageSize (const QImage *image) +{ + return (image ? kpCommandSize::QImageSize (*image) : 0); +} + +// public static +kpCommandSize::SizeType kpCommandSize::QImageSize (int width, int height, int depth) +{ + // handle 15bpp + int roundedDepth = (depth > 8 ? (depth + 7) / 8 * 8 : depth); + kpCommandSize::SizeType ret = + (kpCommandSize::SizeType) width * height * roundedDepth / 8; + +#if DEBUG_KP_COMMAND_SIZE && 0 + kDebug () << "kpCommandSize::QImageSize() w=" << width + << " h=" << height + << " d=" << depth + << " roundedDepth=" << roundedDepth + << " ret=" << ret + << endl; +#endif + + return ret; +} + + +// public static +kpCommandSize::SizeType kpCommandSize::ImageSize (const kpImage &image) +{ + return kpCommandSize::PixmapSize (image); +} + +// public static +kpCommandSize::SizeType kpCommandSize::ImageSize (const kpImage *image) +{ + return kpCommandSize::PixmapSize (image); +} + + +// public static +kpCommandSize::SizeType kpCommandSize::SelectionSize (const kpAbstractSelection &sel) +{ + return sel.size (); +} + +// public static +kpCommandSize::SizeType kpCommandSize::SelectionSize (const kpAbstractSelection *sel) +{ + return (sel ? sel->size () : 0); +} + + +// public static +kpCommandSize::SizeType kpCommandSize::StringSize (const QString &string) +{ +#if DEBUG_KP_COMMAND_SIZE && 1 + kDebug () << "kpCommandSize::StringSize(" << string << ")" + << " len=" << string.length () + << " sizeof(QChar)=" << sizeof (QChar) + << endl; +#endif + return ((SizeType) string.length () * sizeof (QChar)); +} + + +// public static +kpCommandSize::SizeType kpCommandSize::PolygonSize (const QPolygon &points) +{ +#if DEBUG_KP_COMMAND_SIZE && 1 + kDebug () << "kpCommandSize::PolygonSize() points.size=" + << points.size () + << " sizeof(QPoint)=" << sizeof (QPoint) + << endl; +#endif + + return ((SizeType) points.size () * sizeof (QPoint)); +} + diff --git a/kolourpaint/commands/kpCommandSize.h b/kolourpaint/commands/kpCommandSize.h new file mode 100644 index 00000000..126713f9 --- /dev/null +++ b/kolourpaint/commands/kpCommandSize.h @@ -0,0 +1,87 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpCommandSize_H +#define kpCommandSize_H + + +#include + + +class QImage; +class QPolygon; +class QString; + +class kpAbstractSelection; + + +// +// Estimates the size of the object being pointed to, in bytes. +// +// This is used by the command history to trim stored commands, once a +// certain amount of memory is used by those commands. +// +class kpCommandSize +{ +public: + // Force 64-bit arithmetic, instead of 32-bit, to prevent overflow + // when determining whether to clip the command history -- we might be + // adding a large number of large sizes. This will eventually help + // KolourPaint support more than 2GB of image data. + // + // For some reason, GCC doesn't warn of accidental casts to smaller types + // (e.g. 32-bit). An easy way to get around this is to change "SizeType" + // to be "double" temporarily and recompile - every time an implicit cast to + // "int" (32-bit) is made, we'll be warned. + // + // TODO: Exhaustively test that we're not accidentally doing intermediate + // calculations using 32-bit in some places (mainly inside + // implementations of kpCommand::size()). + typedef qlonglong SizeType; + + static SizeType PixmapSize (const QImage &image); + static SizeType PixmapSize (const QImage *image); + static SizeType PixmapSize (int width, int height, int depth); + + static SizeType QImageSize (const QImage &image); + static SizeType QImageSize (const QImage *image); + static SizeType QImageSize (int width, int height, int depth); + + static SizeType ImageSize (const kpImage &image); + static SizeType ImageSize (const kpImage *image); + + static SizeType SelectionSize (const kpAbstractSelection &sel); + static SizeType SelectionSize (const kpAbstractSelection *sel); + + static SizeType StringSize (const QString &string); + + static SizeType PolygonSize (const QPolygon &points); +}; + + +#endif // kpCommandSize_H diff --git a/kolourpaint/commands/kpMacroCommand.cpp b/kolourpaint/commands/kpMacroCommand.cpp new file mode 100644 index 00000000..aa9031b0 --- /dev/null +++ b/kolourpaint/commands/kpMacroCommand.cpp @@ -0,0 +1,148 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COMMAND_HISTORY 0 + + +#include +#include + +#include + +#include + +//--------------------------------------------------------------------- + +struct kpMacroCommandPrivate +{ +}; + + +kpMacroCommand::kpMacroCommand (const QString &name, kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + d (new kpMacroCommandPrivate ()) +{ +} + +//--------------------------------------------------------------------- + +kpMacroCommand::~kpMacroCommand () +{ + qDeleteAll (m_commandList.begin (), m_commandList.end ()); + delete d; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpMacroCommand::size () const +{ +#if DEBUG_KP_COMMAND_HISTORY && 0 + kDebug () << "kpMacroCommand::size()"; +#endif + SizeType s = 0; + +#if DEBUG_KP_COMMAND_HISTORY && 0 + kDebug () << "\tcalculating:"; +#endif + foreach (kpCommand *cmd, m_commandList) + { + #if DEBUG_KP_COMMAND_HISTORY && 0 + kDebug () << "\t\tcurrentSize=" << s << " + " + << cmd->name () << ".size=" << cmd->size () + << endl; + #endif + s += cmd->size (); + } + +#if DEBUG_KP_COMMAND_HISTORY && 0 + kDebug () << "\treturning " << s; +#endif + return s; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void kpMacroCommand::execute () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpMacroCommand::execute()"; +#endif + + viewManager()->setQueueUpdates(); + + for (QLinkedList ::const_iterator it = m_commandList.begin (); + it != m_commandList.end (); + ++it) + { + #if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\texecuting " << (*it)->name (); + #endif + (*it)->execute (); + } + + viewManager()->restoreQueueUpdates(); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void kpMacroCommand::unexecute () +{ +#if DEBUG_KP_COMMAND_HISTORY + kDebug () << "kpMacroCommand::unexecute()"; +#endif + + viewManager()->setQueueUpdates(); + + QLinkedList ::const_iterator it = m_commandList.end (); + it--; + + while (it != m_commandList.end ()) + { + #if DEBUG_KP_COMMAND_HISTORY + kDebug () << "\tunexecuting " << (*it)->name (); + #endif + (*it)->unexecute (); + + it--; + } + + viewManager()->restoreQueueUpdates(); +} + +//--------------------------------------------------------------------- + +// public +void kpMacroCommand::addCommand (kpCommand *command) +{ + m_commandList.push_back (command); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/commands/kpMacroCommand.h b/kolourpaint/commands/kpMacroCommand.h new file mode 100644 index 00000000..1283615e --- /dev/null +++ b/kolourpaint/commands/kpMacroCommand.h @@ -0,0 +1,69 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpMacroCommand_H +#define kpMacroCommand_H + + +#include + +#include + + +class kpMacroCommand : public kpNamedCommand +{ +public: + kpMacroCommand (const QString &name, kpCommandEnvironment *environ); + virtual ~kpMacroCommand (); + + + // + // kpCommand Interface + // + + virtual SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + + + // + // Interface + // + + void addCommand (kpCommand *command); + +protected: + QLinkedList m_commandList; + +private: + struct kpMacroCommandPrivate * const d; +}; + + +#endif // kpMacroCommand_H diff --git a/kolourpaint/commands/kpNamedCommand.cpp b/kolourpaint/commands/kpNamedCommand.cpp new file mode 100644 index 00000000..e8aec642 --- /dev/null +++ b/kolourpaint/commands/kpNamedCommand.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +//--------------------------------------------------------------------- + +kpNamedCommand::kpNamedCommand (const QString &name, kpCommandEnvironment *environ) + : kpCommand (environ), + m_name (name) +{ +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +QString kpNamedCommand::name () const +{ + return m_name; +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/commands/kpNamedCommand.h b/kolourpaint/commands/kpNamedCommand.h new file mode 100644 index 00000000..2d3416a0 --- /dev/null +++ b/kolourpaint/commands/kpNamedCommand.h @@ -0,0 +1,50 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpNamedCommand_H +#define kpNamedCommand_H + + +#include + +#include + + +class kpNamedCommand : public kpCommand +{ +public: + kpNamedCommand (const QString &name, kpCommandEnvironment *environ); + + virtual QString name () const; + +protected: + QString m_name; +}; + + +#endif // kpNamedCommand_H diff --git a/kolourpaint/commands/tools/flow/kpToolFlowCommand.cpp b/kolourpaint/commands/tools/flow/kpToolFlowCommand.cpp new file mode 100644 index 00000000..2b6d93e6 --- /dev/null +++ b/kolourpaint/commands/tools/flow/kpToolFlowCommand.cpp @@ -0,0 +1,141 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_FLOW_COMMAND 0 + + +#include + +#include + +#include +#include +#include +#include +#include + + +struct kpToolFlowCommandPrivate +{ + kpImage image; + QRect boundingRect; +}; + + +kpToolFlowCommand::kpToolFlowCommand (const QString &name, kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + d (new kpToolFlowCommandPrivate ()) +{ + d->image = document ()->image (); +} + +kpToolFlowCommand::~kpToolFlowCommand () +{ + delete d; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolFlowCommand::size () const +{ + return ImageSize (d->image); +} + + +// public virtual [base kpCommand] +void kpToolFlowCommand::execute () +{ + swapOldAndNew (); +} + +// public virtual [base kpCommand] +void kpToolFlowCommand::unexecute () +{ + swapOldAndNew (); +} + + +// private +void kpToolFlowCommand::swapOldAndNew () +{ + if (d->boundingRect.isValid ()) + { + const kpImage oldImage = document ()->getImageAt (d->boundingRect); + + document ()->setImageAt (d->image, d->boundingRect.topLeft ()); + + d->image = oldImage; + } +} + +// public +void kpToolFlowCommand::updateBoundingRect (const QPoint &point) +{ + updateBoundingRect (QRect (point, point)); +} + +// public +void kpToolFlowCommand::updateBoundingRect (const QRect &rect) +{ +#if DEBUG_KP_TOOL_FLOW_COMMAND & 0 + kDebug () << "kpToolFlowCommand::updateBoundingRect() existing=" + << d->boundingRect + << " plus=" + << rect + << endl; +#endif + d->boundingRect = d->boundingRect.unite (rect); +#if DEBUG_KP_TOOL_FLOW_COMMAND & 0 + kDebug () << "\tresult=" << d->boundingRect; +#endif +} + +// public +void kpToolFlowCommand::finalize () +{ + if (d->boundingRect.isValid ()) + { + // Store only the needed part of doc image. + d->image = kpTool::neededPixmap (d->image, d->boundingRect); + } + else + { + d->image = kpImage (); + } +} + +// public +void kpToolFlowCommand::cancel () +{ + if (d->boundingRect.isValid ()) + { + viewManager ()->setFastUpdates (); + document ()->setImageAt (d->image, d->boundingRect.topLeft ()); + viewManager ()->restoreFastUpdates (); + } +} diff --git a/kolourpaint/commands/tools/flow/kpToolFlowCommand.h b/kolourpaint/commands/tools/flow/kpToolFlowCommand.h new file mode 100644 index 00000000..fe6a48c9 --- /dev/null +++ b/kolourpaint/commands/tools/flow/kpToolFlowCommand.h @@ -0,0 +1,64 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_FLOW_COMMAND_H +#define KP_TOOL_FLOW_COMMAND_H + + +#include + + +class QPoint; +class QRect; + + +class kpToolFlowCommand : public kpNamedCommand +{ +public: + kpToolFlowCommand (const QString &name, kpCommandEnvironment *environ); + virtual ~kpToolFlowCommand (); + + virtual kpCommandSize::SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + + // interface for kpToolFlowBase + void updateBoundingRect (const QPoint &point); + void updateBoundingRect (const QRect &rect); + void finalize (); + void cancel (); + +private: + void swapOldAndNew (); + + struct kpToolFlowCommandPrivate * const d; +}; + + +#endif // KP_TOOL_FLOW_COMMAND_H diff --git a/kolourpaint/commands/tools/kpToolColorPickerCommand.cpp b/kolourpaint/commands/tools/kpToolColorPickerCommand.cpp new file mode 100644 index 00000000..e9f5e333 --- /dev/null +++ b/kolourpaint/commands/tools/kpToolColorPickerCommand.cpp @@ -0,0 +1,83 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_COLOR_PICKER 0 + + +#include + +#include + +#include +#include + + +kpToolColorPickerCommand::kpToolColorPickerCommand ( + int mouseButton, + const kpColor &newColor, + const kpColor &oldColor, + kpCommandEnvironment *environ) + + : kpCommand (environ), + m_mouseButton (mouseButton), + m_newColor (newColor), + m_oldColor (oldColor) +{ +} + +kpToolColorPickerCommand::~kpToolColorPickerCommand () +{ +} + + +// public virtual [base kpCommand] +QString kpToolColorPickerCommand::name () const +{ + return i18n ("Color Picker"); +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolColorPickerCommand::size () const +{ + return 0; +} + + +// public virtual [base kpCommand] +void kpToolColorPickerCommand::execute () +{ + environ ()->setColor (m_mouseButton, m_newColor); +} + +// public virtual [base kpCommand] +void kpToolColorPickerCommand::unexecute () +{ + environ ()->setColor (m_mouseButton, m_oldColor); +} + diff --git a/kolourpaint/commands/tools/kpToolColorPickerCommand.h b/kolourpaint/commands/tools/kpToolColorPickerCommand.h new file mode 100644 index 00000000..25707489 --- /dev/null +++ b/kolourpaint/commands/tools/kpToolColorPickerCommand.h @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolColorPickerCommand_H +#define kpToolColorPickerCommand_H + + +#include +#include + + +class kpToolColorPickerCommand : public kpCommand +{ +public: + kpToolColorPickerCommand (int mouseButton, + const kpColor &newColor, const kpColor &oldColor, + kpCommandEnvironment *environ); + virtual ~kpToolColorPickerCommand (); + + virtual QString name () const; + + virtual SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + int m_mouseButton; + kpColor m_newColor; + kpColor m_oldColor; +}; + + +#endif // kpToolColorPickerCommand_H diff --git a/kolourpaint/commands/tools/kpToolFloodFillCommand.cpp b/kolourpaint/commands/tools/kpToolFloodFillCommand.cpp new file mode 100644 index 00000000..3387dd49 --- /dev/null +++ b/kolourpaint/commands/tools/kpToolFloodFillCommand.cpp @@ -0,0 +1,170 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_FLOOD_FILL 0 + + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +struct kpToolFloodFillCommandPrivate +{ + kpImage oldImage; + bool fillEntireImage; +}; + +//--------------------------------------------------------------------- + +kpToolFloodFillCommand::kpToolFloodFillCommand (int x, int y, + const kpColor &color, int processedColorSimilarity, + kpCommandEnvironment *environ) + + : kpCommand (environ), + kpFloodFill (document ()->imagePointer (), x, y, color, processedColorSimilarity), + d (new kpToolFloodFillCommandPrivate ()) +{ + d->fillEntireImage = false; +} + +//--------------------------------------------------------------------- + +kpToolFloodFillCommand::~kpToolFloodFillCommand () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +QString kpToolFloodFillCommand::name () const +{ + return i18n ("Flood Fill"); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolFloodFillCommand::size () const +{ + return kpFloodFill::size () + ImageSize (d->oldImage); +} + +//--------------------------------------------------------------------- + +// public +void kpToolFloodFillCommand::setFillEntireImage (bool yes) +{ + d->fillEntireImage = yes; +} + +//--------------------------------------------------------------------- + +// protected virtual [base kpCommand] +void kpToolFloodFillCommand::execute () +{ +#if DEBUG_KP_TOOL_FLOOD_FILL && 1 + kDebug () << "kpToolFloodFillCommand::execute() fillEntireImage=" + << d->fillEntireImage << endl; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + + if (d->fillEntireImage) + { + doc->fill (kpFloodFill::color ()); + } + else + { + QRect rect = kpFloodFill::boundingRect (); + if (rect.isValid ()) + { + QApplication::setOverrideCursor (Qt::WaitCursor); + { + d->oldImage = doc->getImageAt (rect); + + kpFloodFill::fill (); + doc->slotContentsChanged (rect); + } + QApplication::restoreOverrideCursor (); + } + else + { + #if DEBUG_KP_TOOL_FLOOD_FILL && 1 + kDebug () << "\tinvalid boundingRect - must be NOP case"; + #endif + } + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base kpCommand] +void kpToolFloodFillCommand::unexecute () +{ +#if DEBUG_KP_TOOL_FLOOD_FILL && 1 + kDebug () << "kpToolFloodFillCommand::unexecute() fillEntireImage=" + << d->fillEntireImage << endl; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + + if (d->fillEntireImage) + { + doc->fill (kpFloodFill::colorToChange ()); + } + else + { + QRect rect = kpFloodFill::boundingRect (); + if (rect.isValid ()) + { + doc->setImageAt (d->oldImage, rect.topLeft ()); + + d->oldImage = kpImage (); + + doc->slotContentsChanged (rect); + } + } +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/commands/tools/kpToolFloodFillCommand.h b/kolourpaint/commands/tools/kpToolFloodFillCommand.h new file mode 100644 index 00000000..094b9e60 --- /dev/null +++ b/kolourpaint/commands/tools/kpToolFloodFillCommand.h @@ -0,0 +1,68 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolFloodFillCommand_H +#define kpToolFloodFillCommand_H + + +#include +#include + + +class kpColor; +class kpCommandEnvironment; + + +struct kpToolFloodFillCommandPrivate; + +class kpToolFloodFillCommand : public kpCommand, public kpFloodFill +{ +public: + kpToolFloodFillCommand (int x, int y, + const kpColor &color, int processedColorSimilarity, + kpCommandEnvironment *environ); + virtual ~kpToolFloodFillCommand (); + + virtual QString name () const; + + virtual kpCommandSize::SizeType size () const; + + // Optimization hack: filling a fresh, unmodified document does not require + // reading any pixels - just set the whole document to + // . + void setFillEntireImage (bool yes = true); + + virtual void execute (); + virtual void unexecute (); + +private: + kpToolFloodFillCommandPrivate * const d; +}; + + +#endif // kpToolFloodFillCommand_H diff --git a/kolourpaint/commands/tools/polygonal/kpToolPolygonalCommand.cpp b/kolourpaint/commands/tools/polygonal/kpToolPolygonalCommand.cpp new file mode 100644 index 00000000..e21eb7cb --- /dev/null +++ b/kolourpaint/commands/tools/polygonal/kpToolPolygonalCommand.cpp @@ -0,0 +1,145 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_POLYGON 0 + + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct kpToolPolygonalCommandPrivate +{ + kpToolPolygonalBase::DrawShapeFunc drawShapeFunc; + + QPolygon points; + QRect boundingRect; + + kpColor fcolor; + int penWidth; + kpColor bcolor; + + kpImage oldImage; +}; + +kpToolPolygonalCommand::kpToolPolygonalCommand (const QString &name, + kpToolPolygonalBase::DrawShapeFunc drawShapeFunc, + const QPolygon &points, + const QRect &boundingRect, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + kpCommandEnvironment *environ) + + : kpNamedCommand (name, environ), + d (new kpToolPolygonalCommandPrivate ()) +{ + d->drawShapeFunc = drawShapeFunc; + + d->points = points; + d->boundingRect = boundingRect; + + d->fcolor = fcolor; + d->penWidth = penWidth; + d->bcolor = bcolor; +} + +kpToolPolygonalCommand::~kpToolPolygonalCommand () +{ + delete d; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolPolygonalCommand::size () const +{ + return PolygonSize (d->points) + + ImageSize (d->oldImage); +} + +// public virtual [base kpCommand] +void kpToolPolygonalCommand::execute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + // Store Undo info. + Q_ASSERT (d->oldImage.isNull ()); + d->oldImage = doc->getImageAt (d->boundingRect); + + // Invoke shape drawing function passed in ctor. + kpImage image = d->oldImage; + + QPolygon pointsTranslated = d->points; + pointsTranslated.translate (-d->boundingRect.x (), -d->boundingRect.y ()); + + (*d->drawShapeFunc) (&image, + pointsTranslated, + d->fcolor, d->penWidth, + d->bcolor, + true/*final shape*/); + + doc->setImageAt (image, d->boundingRect.topLeft ()); +} + +// public virtual [base kpCommand] +void kpToolPolygonalCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + Q_ASSERT (!d->oldImage.isNull ()); + doc->setImageAt (d->oldImage, d->boundingRect.topLeft ()); + + d->oldImage = kpImage (); +} + diff --git a/kolourpaint/commands/tools/polygonal/kpToolPolygonalCommand.h b/kolourpaint/commands/tools/polygonal/kpToolPolygonalCommand.h new file mode 100644 index 00000000..7d0aac14 --- /dev/null +++ b/kolourpaint/commands/tools/polygonal/kpToolPolygonalCommand.h @@ -0,0 +1,68 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolPolygonalCommand_H +#define kpToolPolygonalCommand_H + + +#include +#include + + +class QPolygon; +class QRect; + +class kpColor; + + +// TODO: merge with kpToolRectangularCommand due to code duplication. +class kpToolPolygonalCommand : public kpNamedCommand +{ +public: + // = the bounding rectangle for including . + kpToolPolygonalCommand (const QString &name, + kpToolPolygonalBase::DrawShapeFunc drawShapeFunc, + const QPolygon &points, + const QRect &boundingRect, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + kpCommandEnvironment *environ); + virtual ~kpToolPolygonalCommand (); + + virtual kpCommandSize::SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + struct kpToolPolygonalCommandPrivate * const d; + kpToolPolygonalCommand &operator= (const kpToolPolygonalCommand &) const; +}; + + +#endif // kpToolPolygonalCommand_H diff --git a/kolourpaint/commands/tools/rectangular/kpToolRectangularCommand.cpp b/kolourpaint/commands/tools/rectangular/kpToolRectangularCommand.cpp new file mode 100644 index 00000000..1c0ab84a --- /dev/null +++ b/kolourpaint/commands/tools/rectangular/kpToolRectangularCommand.cpp @@ -0,0 +1,134 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_RECTANGULAR_COMMAND 0 + + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct kpToolRectangularCommandPrivate +{ + kpToolRectangularBase::DrawShapeFunc drawShapeFunc; + + QRect rect; + + kpColor fcolor; + int penWidth; + kpColor bcolor; + + kpImage oldImage; +}; + +kpToolRectangularCommand::kpToolRectangularCommand (const QString &name, + kpToolRectangularBase::DrawShapeFunc drawShapeFunc, + const QRect &rect, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + kpCommandEnvironment *environ) + + : kpNamedCommand (name, environ), + d (new kpToolRectangularCommandPrivate ()) +{ + d->drawShapeFunc = drawShapeFunc; + + d->rect = rect; + + d->fcolor = fcolor; + d->penWidth = penWidth; + d->bcolor = bcolor; +} + +kpToolRectangularCommand::~kpToolRectangularCommand () +{ + delete d; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolRectangularCommand::size () const +{ + return ImageSize (d->oldImage); +} + + +// public virtual [base kpCommand] +void kpToolRectangularCommand::execute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + // Store Undo info. + // OPT: For a pure rectangle, can do better if there is no bcolor, by only + // saving 4 pixmaps corresponding to the pixels dirtied by the 4 edges. + Q_ASSERT (d->oldImage.isNull ()); + d->oldImage = doc->getImageAt (d->rect); + + // Invoke shape drawing function passed in ctor. + kpImage image = d->oldImage; + (*d->drawShapeFunc) (&image, + 0, 0, d->rect.width (), d->rect.height (), + d->fcolor, d->penWidth, + d->bcolor); + + doc->setImageAt (image, d->rect.topLeft ()); +} + +// public virtual [base kpCommand] +void kpToolRectangularCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + Q_ASSERT (!d->oldImage.isNull ()); + doc->setImageAt (d->oldImage, d->rect.topLeft ()); + + d->oldImage = kpImage (); +} + diff --git a/kolourpaint/commands/tools/rectangular/kpToolRectangularCommand.h b/kolourpaint/commands/tools/rectangular/kpToolRectangularCommand.h new file mode 100644 index 00000000..f4710127 --- /dev/null +++ b/kolourpaint/commands/tools/rectangular/kpToolRectangularCommand.h @@ -0,0 +1,62 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_RECTANGULAR_COMMAND_H +#define KP_TOOL_RECTANGULAR_COMMAND_H + + +#include +#include + + +class kpColor; + + +class kpToolRectangularCommand : public kpNamedCommand +{ +public: + kpToolRectangularCommand (const QString &name, + kpToolRectangularBase::DrawShapeFunc drawShapeFunc, + const QRect &rect, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + kpCommandEnvironment *environ); + virtual ~kpToolRectangularCommand (); + + virtual kpCommandSize::SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + struct kpToolRectangularCommandPrivate * const d; + kpToolRectangularCommand &operator= (const kpToolRectangularCommand &) const; +}; + + +#endif // KP_TOOL_RECTANGULAR_COMMAND_H diff --git a/kolourpaint/commands/tools/selection/kpAbstractSelectionContentCommand.cpp b/kolourpaint/commands/tools/selection/kpAbstractSelectionContentCommand.cpp new file mode 100644 index 00000000..66669a06 --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpAbstractSelectionContentCommand.cpp @@ -0,0 +1,69 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include + + +struct kpAbstractSelectionContentCommandPrivate +{ + const kpAbstractSelection *orgSelBorder; +}; + +kpAbstractSelectionContentCommand::kpAbstractSelectionContentCommand ( + const kpAbstractSelection &originalSelBorder, + const QString &name, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + d (new kpAbstractSelectionContentCommandPrivate ()) +{ + Q_ASSERT (!originalSelBorder.hasContent ()); + + d->orgSelBorder = originalSelBorder.clone (); +} + +kpAbstractSelectionContentCommand::~kpAbstractSelectionContentCommand () +{ + delete d->orgSelBorder; + delete d; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpAbstractSelectionContentCommand::size () const +{ + return d->orgSelBorder->size (); +} + + +// public +const kpAbstractSelection *kpAbstractSelectionContentCommand::originalSelection () const +{ + return d->orgSelBorder; +} diff --git a/kolourpaint/commands/tools/selection/kpAbstractSelectionContentCommand.h b/kolourpaint/commands/tools/selection/kpAbstractSelectionContentCommand.h new file mode 100644 index 00000000..173f91da --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpAbstractSelectionContentCommand.h @@ -0,0 +1,71 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpAbstractSelectionContentCommand_H +#define kpAbstractSelectionContentCommand_H + + +#include + + +// Converts a selection border to a selection with content. +// This must be executed before any manipulations can be made +// to a selection. +// +// Its construction and execution always follows that of a +// kpToolSelectionCreateCommand, which must be given a selection with +// no content. +// +// It's always the first subcommand of a kpMacroCommand, with the following +// subcommands being whatever the selection operation is (e.g. movement, +// resizing). +class kpAbstractSelectionContentCommand : public kpNamedCommand +{ +// LOREFACTOR: Pull up more methods into here? Looking at the code, not +// much could be dragged up without unnecessarily complicated +// abstraction. +public: + // must be a border i.e. have no content. + kpAbstractSelectionContentCommand ( + const kpAbstractSelection &originalSelBorder, + const QString &name, + kpCommandEnvironment *environ); + virtual ~kpAbstractSelectionContentCommand (); + + virtual kpCommandSize::SizeType size () const; + + // Note: Returned pointer is only valid for as long as this command is + // alive. + const kpAbstractSelection *originalSelection () const; + +private: + struct kpAbstractSelectionContentCommandPrivate * const d; +}; + + +#endif // kpAbstractSelectionContentCommand_H diff --git a/kolourpaint/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp b/kolourpaint/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp new file mode 100644 index 00000000..de8048a6 --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp @@ -0,0 +1,97 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include + +#include + +#include +#include + +#include +#include +#include +#include + + +kpToolImageSelectionTransparencyCommand::kpToolImageSelectionTransparencyCommand ( + const QString &name, + const kpImageSelectionTransparency &st, + const kpImageSelectionTransparency &oldST, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_st (st), + m_oldST (oldST) +{ +} + +kpToolImageSelectionTransparencyCommand::~kpToolImageSelectionTransparencyCommand () +{ +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolImageSelectionTransparencyCommand::size () const +{ + return 0; +} + + +// public virtual [base kpCommand] +void kpToolImageSelectionTransparencyCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpToolImageSelectionTransparencyCommand::execute()"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + environ ()->setImageSelectionTransparency (m_st, true/*force colour change*/); + + if (imageSelection ()) + imageSelection ()->setTransparency (m_st); +} + +// public virtual [base kpCommand] +void kpToolImageSelectionTransparencyCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpToolImageSelectionTransparencyCommand::unexecute()"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + environ ()->setImageSelectionTransparency (m_oldST, true/*force colour change*/); + + if (imageSelection ()) + imageSelection ()->setTransparency (m_oldST); +} + diff --git a/kolourpaint/commands/tools/selection/kpToolImageSelectionTransparencyCommand.h b/kolourpaint/commands/tools/selection/kpToolImageSelectionTransparencyCommand.h new file mode 100644 index 00000000..cd6054b1 --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpToolImageSelectionTransparencyCommand.h @@ -0,0 +1,56 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolImageSelectionTransparencyCommand_H +#define kpToolImageSelectionTransparencyCommand_H + + +#include +#include + + +class kpToolImageSelectionTransparencyCommand : public kpNamedCommand +{ +public: + kpToolImageSelectionTransparencyCommand (const QString &name, + const kpImageSelectionTransparency &st, + const kpImageSelectionTransparency &oldST, + kpCommandEnvironment *environ); + virtual ~kpToolImageSelectionTransparencyCommand (); + + virtual kpCommandSize::SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + kpImageSelectionTransparency m_st, m_oldST; +}; + + +#endif // kpToolImageSelectionTransparencyCommand_H diff --git a/kolourpaint/commands/tools/selection/kpToolSelectionCreateCommand.cpp b/kolourpaint/commands/tools/selection/kpToolSelectionCreateCommand.cpp new file mode 100644 index 00000000..7cfbad22 --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpToolSelectionCreateCommand.cpp @@ -0,0 +1,159 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +kpToolSelectionCreateCommand::kpToolSelectionCreateCommand (const QString &name, + const kpAbstractSelection &fromSelection, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_fromSelection (0), + m_textRow (0), m_textCol (0) +{ + setFromSelection (fromSelection); +} + +kpToolSelectionCreateCommand::~kpToolSelectionCreateCommand () +{ + delete m_fromSelection; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolSelectionCreateCommand::size () const +{ + return SelectionSize (m_fromSelection); +} + + +// public +const kpAbstractSelection *kpToolSelectionCreateCommand::fromSelection () const +{ + return m_fromSelection; +} + +// public +void kpToolSelectionCreateCommand::setFromSelection (const kpAbstractSelection &fromSelection) +{ + delete m_fromSelection; + m_fromSelection = fromSelection.clone (); +} + +// public virtual [base kpCommand] +void kpToolSelectionCreateCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpToolSelectionCreateCommand::execute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + if (m_fromSelection) + { + #if DEBUG_KP_TOOL_SELECTION + kDebug () << "\tusing fromSelection"; + kDebug () << "\t\thave sel=" << doc->selection () + << endl; + #endif + kpAbstractImageSelection *imageSel = + dynamic_cast (m_fromSelection); + kpTextSelection *textSel = + dynamic_cast (m_fromSelection); + if (imageSel) + { + if (imageSel->transparency () != environ ()->imageSelectionTransparency ()) + environ ()->setImageSelectionTransparency (imageSel->transparency ()); + } + else if (textSel) + { + if (textSel->textStyle () != environ ()->textStyle ()) + environ ()->setTextStyle (textSel->textStyle ()); + } + else + Q_ASSERT (!"Unknown selection type"); + + viewManager ()->setTextCursorPosition (m_textRow, m_textCol); + doc->setSelection (*m_fromSelection); + + environ ()->somethingBelowTheCursorChanged (); + } +} + +// public virtual [base kpCommand] +void kpToolSelectionCreateCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + if (!doc->selection ()) + { + // Was just a border that got deselected? + if (m_fromSelection && !m_fromSelection->hasContent ()) + return; + + Q_ASSERT (!"kpToolSelectionCreateCommand::unexecute() without sel region"); + return; + } + + m_textRow = viewManager ()->textCursorRow (); + m_textCol = viewManager ()->textCursorCol (); + + doc->selectionDelete (); + + environ ()->somethingBelowTheCursorChanged (); +} + diff --git a/kolourpaint/commands/tools/selection/kpToolSelectionCreateCommand.h b/kolourpaint/commands/tools/selection/kpToolSelectionCreateCommand.h new file mode 100644 index 00000000..6a286efd --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpToolSelectionCreateCommand.h @@ -0,0 +1,63 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolSelectionCreateCommand_H +#define kpToolSelectionCreateCommand_H + + +#include + + +class kpAbstractSelection; +class kpCommandHistory; + + +class kpToolSelectionCreateCommand : public kpNamedCommand +{ +public: + // (if fromSelection doesn't have a pixmap, it will only recreate the region) + kpToolSelectionCreateCommand (const QString &name, const kpAbstractSelection &fromSelection, + kpCommandEnvironment *environ); + virtual ~kpToolSelectionCreateCommand (); + + virtual kpCommandSize::SizeType size () const; + + const kpAbstractSelection *fromSelection () const; + void setFromSelection (const kpAbstractSelection &fromSelection); + + virtual void execute (); + virtual void unexecute (); + +private: + kpAbstractSelection *m_fromSelection; + + int m_textRow, m_textCol; +}; + + +#endif // kpToolSelectionCreateCommand_H diff --git a/kolourpaint/commands/tools/selection/kpToolSelectionDestroyCommand.cpp b/kolourpaint/commands/tools/selection/kpToolSelectionDestroyCommand.cpp new file mode 100644 index 00000000..b556a05f --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpToolSelectionDestroyCommand.cpp @@ -0,0 +1,185 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +kpToolSelectionDestroyCommand::kpToolSelectionDestroyCommand (const QString &name, + bool pushOntoDocument, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_pushOntoDocument (pushOntoDocument), + m_oldSelectionPtr (0), + m_textRow(0), m_textCol(0) +{ +} + +//--------------------------------------------------------------------- + +kpToolSelectionDestroyCommand::~kpToolSelectionDestroyCommand () +{ + delete m_oldSelectionPtr; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolSelectionDestroyCommand::size () const +{ + return ImageSize (m_oldDocImage) + + SelectionSize (m_oldSelectionPtr); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void kpToolSelectionDestroyCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpToolSelectionDestroyCommand::execute () CALLED"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + Q_ASSERT (doc->selection ()); + + m_textRow = viewManager ()->textCursorRow (); + m_textCol = viewManager ()->textCursorCol (); + + Q_ASSERT (!m_oldSelectionPtr); + m_oldSelectionPtr = doc->selection ()->clone (); + + if (m_pushOntoDocument) + { + m_oldDocImage = doc->getImageAt (doc->selection ()->boundingRect ()); + doc->selectionPushOntoDocument (); + } + else + doc->selectionDelete (); + + environ ()->somethingBelowTheCursorChanged (); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void kpToolSelectionDestroyCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpToolSelectionDestroyCommand::unexecute () CALLED"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + if (doc->selection ()) + { + // not error because it's possible that the user dragged out a new + // region (without pulling image), and then CTRL+Z + #if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpToolSelectionDestroyCommand::unexecute() already has sel region"; + #endif + + if (doc->selection ()->hasContent ()) + { + Q_ASSERT (!"kpToolSelectionDestroyCommand::unexecute() already has sel content"); + return; + } + } + + Q_ASSERT (m_oldSelectionPtr); + + if (m_pushOntoDocument) + { + #if DEBUG_KP_TOOL_SELECTION + kDebug () << "\tunpush oldDocImage onto doc first"; + #endif + doc->setImageAt (m_oldDocImage, m_oldSelectionPtr->topLeft ()); + } + +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "\tsetting selection to: rect=" << m_oldSelectionPtr->boundingRect () + << " hasContent=" << m_oldSelectionPtr->hasContent () + << endl; +#endif + kpAbstractImageSelection *imageSel = + dynamic_cast (m_oldSelectionPtr); + kpTextSelection *textSel = + dynamic_cast (m_oldSelectionPtr); + if (imageSel) + { + if (imageSel->transparency () != environ ()->imageSelectionTransparency ()) + environ ()->setImageSelectionTransparency (imageSel->transparency ()); + if (dynamic_cast (doc->selection())) + doc->selectionPushOntoDocument(); + } + else if (textSel) + { + if (textSel->textStyle () != environ ()->textStyle ()) + environ ()->setTextStyle (textSel->textStyle ()); + if (dynamic_cast (doc->selection())) + doc->selectionPushOntoDocument(); + } + else + Q_ASSERT (!"Unknown selection type"); + + viewManager ()->setTextCursorPosition (m_textRow, m_textCol); + doc->setSelection (*m_oldSelectionPtr); + + environ ()->somethingBelowTheCursorChanged (); + + delete m_oldSelectionPtr; + m_oldSelectionPtr = 0; +} + diff --git a/kolourpaint/commands/tools/selection/kpToolSelectionDestroyCommand.h b/kolourpaint/commands/tools/selection/kpToolSelectionDestroyCommand.h new file mode 100644 index 00000000..07f2ca94 --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpToolSelectionDestroyCommand.h @@ -0,0 +1,61 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolSelectionDestroyCommand_H +#define kpToolSelectionDestroyCommand_H + + +#include +#include + + +class kpAbstractSelection; + + +class kpToolSelectionDestroyCommand : public kpNamedCommand +{ +public: + kpToolSelectionDestroyCommand (const QString &name, bool pushOntoDocument, + kpCommandEnvironment *environ); + virtual ~kpToolSelectionDestroyCommand (); + + virtual kpCommandSize::SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + bool m_pushOntoDocument; + kpImage m_oldDocImage; + kpAbstractSelection *m_oldSelectionPtr; + + int m_textRow, m_textCol; +}; + + +#endif // kpToolSelectionDestroyCommand_H diff --git a/kolourpaint/commands/tools/selection/kpToolSelectionMoveCommand.cpp b/kolourpaint/commands/tools/selection/kpToolSelectionMoveCommand.cpp new file mode 100644 index 00000000..24bc1923 --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpToolSelectionMoveCommand.cpp @@ -0,0 +1,229 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +kpToolSelectionMoveCommand::kpToolSelectionMoveCommand (const QString &name, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ) +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + Q_ASSERT (doc->selection ()); + + m_startPoint = m_endPoint = doc->selection ()->topLeft (); +} + +kpToolSelectionMoveCommand::~kpToolSelectionMoveCommand () +{ +} + + +// public +kpAbstractSelection *kpToolSelectionMoveCommand::originalSelectionClone () const +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + Q_ASSERT (doc->selection ()); + + kpAbstractSelection *selection = doc->selection ()->clone (); + selection->moveTo (m_startPoint); + + return selection; +} + + +// public virtual [base kpComand] +kpCommandSize::SizeType kpToolSelectionMoveCommand::size () const +{ + return ImageSize (m_oldDocumentImage) + + PolygonSize (m_copyOntoDocumentPoints); +} + + +// public virtual [base kpCommand] +void kpToolSelectionMoveCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpToolSelectionMoveCommand::execute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpAbstractSelection *sel = doc->selection (); + // Must have content before it can be moved. + Q_ASSERT (sel && sel->hasContent ()); + + kpViewManager *vm = viewManager (); + Q_ASSERT (vm); + + vm->setQueueUpdates (); + { + foreach (const QPoint &p, m_copyOntoDocumentPoints) + { + sel->moveTo (p); + doc->selectionCopyOntoDocument (); + } + + sel->moveTo (m_endPoint); + + environ ()->somethingBelowTheCursorChanged (); + } + vm->restoreQueueUpdates (); +} + +// public virtual [base kpCommand] +void kpToolSelectionMoveCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpToolSelectionMoveCommand::unexecute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpAbstractSelection *sel = doc->selection (); + // Must have content before it can be un-moved. + Q_ASSERT (sel && sel->hasContent ()); + + kpViewManager *vm = viewManager (); + Q_ASSERT (vm); + + vm->setQueueUpdates (); + + if (!m_oldDocumentImage.isNull ()) + doc->setImageAt (m_oldDocumentImage, m_documentBoundingRect.topLeft ()); +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "\tmove to startPoint=" << m_startPoint; +#endif + sel->moveTo (m_startPoint); + + environ ()->somethingBelowTheCursorChanged (); + + vm->restoreQueueUpdates (); +} + +// public +void kpToolSelectionMoveCommand::moveTo (const QPoint &point, bool moveLater) +{ +#if DEBUG_KP_TOOL_SELECTION && 0 + kDebug () << "kpToolSelectionMoveCommand::moveTo" << point + << " moveLater=" << moveLater + <selection (); + // Must have content before it can be moved. + Q_ASSERT (sel && sel->hasContent ()); + + if (point == sel->topLeft ()) + return; + + sel->moveTo (point); + } + + m_endPoint = point; +} + +// public +void kpToolSelectionMoveCommand::moveTo (int x, int y, bool moveLater) +{ + moveTo (QPoint (x, y), moveLater); +} + +// public +void kpToolSelectionMoveCommand::copyOntoDocument () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpToolSelectionMoveCommand::copyOntoDocument()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpAbstractSelection *sel = doc->selection (); + // Must have content before we allow it be stamped onto the document, + // to be consistent with the requirement on other selection operations. + Q_ASSERT (sel && sel->hasContent ()); + + if (m_oldDocumentImage.isNull ()) + m_oldDocumentImage = doc->image (); + + QRect selBoundingRect = sel->boundingRect (); + m_documentBoundingRect.unite (selBoundingRect); + + doc->selectionCopyOntoDocument (); + + m_copyOntoDocumentPoints.putPoints (m_copyOntoDocumentPoints.count (), + 1, + selBoundingRect.x (), + selBoundingRect.y ()); +} + +// public +void kpToolSelectionMoveCommand::finalize () +{ + if (!m_oldDocumentImage.isNull () && !m_documentBoundingRect.isNull ()) + { + m_oldDocumentImage = kpTool::neededPixmap (m_oldDocumentImage, + m_documentBoundingRect); + } +} + diff --git a/kolourpaint/commands/tools/selection/kpToolSelectionMoveCommand.h b/kolourpaint/commands/tools/selection/kpToolSelectionMoveCommand.h new file mode 100644 index 00000000..584555d9 --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpToolSelectionMoveCommand.h @@ -0,0 +1,74 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolSelectionMoveCommand_H +#define kpToolSelectionMoveCommand_H + + +#include +#include +#include + +#include +#include + + +class kpAbstractSelection; + + +class kpToolSelectionMoveCommand : public kpNamedCommand +{ +public: + kpToolSelectionMoveCommand (const QString &name, kpCommandEnvironment *environ); + virtual ~kpToolSelectionMoveCommand (); + + kpAbstractSelection *originalSelectionClone () const; + + virtual kpCommandSize::SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + + void moveTo (const QPoint &point, bool moveLater = false); + void moveTo (int x, int y, bool moveLater = false); + void copyOntoDocument (); + void finalize (); + +private: + QPoint m_startPoint, m_endPoint; + + kpImage m_oldDocumentImage; + + // area of document affected (not the bounding rect of the sel) + QRect m_documentBoundingRect; + + QPolygon m_copyOntoDocumentPoints; +}; + + +#endif // kpToolSelectionMoveCommand_H diff --git a/kolourpaint/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp b/kolourpaint/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp new file mode 100644 index 00000000..4079a978 --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp @@ -0,0 +1,144 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include + +#include +#include + +#include +#include +#include +#include + + +kpToolSelectionPullFromDocumentCommand::kpToolSelectionPullFromDocumentCommand ( + const kpAbstractImageSelection &originalSelBorder, + const kpColor &backgroundColor, + const QString &name, + kpCommandEnvironment *environ) + : kpAbstractSelectionContentCommand (originalSelBorder, name, environ), + m_backgroundColor (backgroundColor) +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpToolSelectionPullFromDocumentCommand::() environ=" + << environ + << endl; +#endif +} + +kpToolSelectionPullFromDocumentCommand::~kpToolSelectionPullFromDocumentCommand () +{ +} + + +// public virtual [base kpCommand] +void kpToolSelectionPullFromDocumentCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpToolSelectionPullFromDocumentCommand::execute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpViewManager *vm = viewManager (); + Q_ASSERT (vm); + + vm->setQueueUpdates (); + { + // + // Recreate border + // + + // The previously executed command is required to have been a + // kpToolSelectionCreateCommand, which must have been given an image + // selection with no content. + // + // However, there is a tricky case. Suppose we are called for the first + // time, where the above precondition holds. We would add content + // to the selection as expected. But the user then undoes (CTRL+Z) the + // operation, calling unexecute(). There is now no content again. + // Since selection is only a border, the user can freely deselect it + // and/or select another region without changing the command history + // or document modified state. Therefore, if they now call us again + // by redoing (CTRL+Shift+Z), there is potentially no selection at all + // or it is at an arbitrary location. + // + // This assertion covers all 3 possibilities: + // + // 1. First call: image selection with no content + // 2. Later calls: + // a) no image selection (due to deselection) + // b) image selection with no content, at an arbitrary location + Q_ASSERT (!imageSelection () || !imageSelection ()->hasContent ()); + + const kpAbstractImageSelection *originalImageSel = + static_cast (originalSelection ()); + if (originalImageSel->transparency () != + environ ()->imageSelectionTransparency ()) + { + environ ()->setImageSelectionTransparency (originalImageSel->transparency ()); + } + + doc->setSelection (*originalSelection ()); + + + // + // Add content + // + + doc->imageSelectionPullFromDocument (m_backgroundColor); + } + vm->restoreQueueUpdates (); +} + +// public virtual [base kpCommand] +void kpToolSelectionPullFromDocumentCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpToolSelectionPullFromDocumentCommand::unexecute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + // Must have selection image content. + Q_ASSERT (doc->imageSelection () && doc->imageSelection ()->hasContent ()); + + + // We can have faith that this is the state of the selection after + // execute(), rather than after the user tried to throw us off by + // simply selecting another region as to do that, a destroy command + // must have been used. + doc->selectionCopyOntoDocument (false/*use opaque pixmap*/); + doc->imageSelection ()->deleteContent (); +} + diff --git a/kolourpaint/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.h b/kolourpaint/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.h new file mode 100644 index 00000000..d07241fb --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.h @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolSelectionPullFromDocumentCommand_H +#define kpToolSelectionPullFromDocumentCommand_H + + +#include +#include + + +class kpAbstractImageSelection; + + +class kpToolSelectionPullFromDocumentCommand : + public kpAbstractSelectionContentCommand +{ +public: + kpToolSelectionPullFromDocumentCommand ( + const kpAbstractImageSelection &originalSelBorder, + const kpColor &backgroundColor, + const QString &name, + kpCommandEnvironment *environ); + virtual ~kpToolSelectionPullFromDocumentCommand (); + + virtual void execute (); + virtual void unexecute (); + +private: + kpColor m_backgroundColor; +}; + + +#endif // kpToolSelectionPullFromDocumentCommand_H diff --git a/kolourpaint/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp b/kolourpaint/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp new file mode 100644 index 00000000..9c23ea42 --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp @@ -0,0 +1,277 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +kpToolSelectionResizeScaleCommand::kpToolSelectionResizeScaleCommand ( + kpCommandEnvironment *environ) + : kpNamedCommand (environ->textSelection () ? + i18n ("Text: Resize Box") : + i18n ("Selection: Smooth Scale"), + environ), + m_smoothScaleTimer (new QTimer (this)) +{ + m_originalSelectionPtr = selection ()->clone (); + + m_newTopLeft = selection ()->topLeft (); + m_newWidth = selection ()->width (); + m_newHeight = selection ()->height (); + + m_smoothScaleTimer->setSingleShot (true); + connect (m_smoothScaleTimer, SIGNAL (timeout ()), + this, SLOT (resizeScaleAndMove ())); +} + +kpToolSelectionResizeScaleCommand::~kpToolSelectionResizeScaleCommand () +{ + delete m_originalSelectionPtr; +} + + +// public virtual +kpCommandSize::SizeType kpToolSelectionResizeScaleCommand::size () const +{ + return SelectionSize (m_originalSelectionPtr); +} + + +// public +const kpAbstractSelection *kpToolSelectionResizeScaleCommand::originalSelection () const +{ + return m_originalSelectionPtr; +} + + +// public +QPoint kpToolSelectionResizeScaleCommand::topLeft () const +{ + return m_newTopLeft; +} + +// public +void kpToolSelectionResizeScaleCommand::moveTo (const QPoint &point) +{ + if (point == m_newTopLeft) + return; + + m_newTopLeft = point; + selection ()->moveTo (m_newTopLeft); +} + + +// public +int kpToolSelectionResizeScaleCommand::width () const +{ + return m_newWidth; +} + +// public +int kpToolSelectionResizeScaleCommand::height () const +{ + return m_newHeight; +} + +// public +void kpToolSelectionResizeScaleCommand::resize (int width, int height, + bool delayed) +{ + if (width == m_newWidth && height == m_newHeight) + return; + + m_newWidth = width; + m_newHeight = height; + + resizeScaleAndMove (delayed); +} + + +// public +void kpToolSelectionResizeScaleCommand::resizeAndMoveTo (int width, int height, + const QPoint &point, + bool delayed) +{ + if (width == m_newWidth && height == m_newHeight && + point == m_newTopLeft) + { + return; + } + + m_newWidth = width; + m_newHeight = height; + m_newTopLeft = point; + + resizeScaleAndMove (delayed); +} + + +// protected +void kpToolSelectionResizeScaleCommand::killSmoothScaleTimer () +{ + m_smoothScaleTimer->stop (); +} + + +// protected +void kpToolSelectionResizeScaleCommand::resizeScaleAndMove (bool delayed) +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpToolSelectionResizeScaleCommand::resizeScaleAndMove(delayed=" + << delayed << ")" << endl; +#endif + + killSmoothScaleTimer (); + + kpAbstractSelection *newSelPtr = 0; + + if (textSelection ()) + { + Q_ASSERT (dynamic_cast (m_originalSelectionPtr)); + kpTextSelection *orgTextSel = + static_cast (m_originalSelectionPtr); + + newSelPtr = orgTextSel->resized (m_newWidth, m_newHeight); + } + else + { + Q_ASSERT (dynamic_cast (m_originalSelectionPtr)); + kpAbstractImageSelection *imageSel = + static_cast (m_originalSelectionPtr); + + newSelPtr = new kpRectangularImageSelection ( + QRect (imageSel->x (), + imageSel->y (), + m_newWidth, + m_newHeight), + kpPixmapFX::scale (imageSel->baseImage (), + m_newWidth, m_newHeight, + !delayed/*if not delayed, smooth*/), + imageSel->transparency ()); + + if (delayed) + { + // Call self (once) with delayed==false in 200ms + m_smoothScaleTimer->start (200/*ms*/); + } + } + + Q_ASSERT (newSelPtr); + newSelPtr->moveTo (m_newTopLeft); + + document ()->setSelection (*newSelPtr); + + delete newSelPtr; +} + +// protected slots +void kpToolSelectionResizeScaleCommand::resizeScaleAndMove () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpToolSelectionResizeScaleCommand::resizeScaleAndMove()"; +#endif + resizeScaleAndMove (false/*no delay*/); +} + + +// public +void kpToolSelectionResizeScaleCommand::finalize () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpToolSelectionResizeScaleCommand::finalize()" + << " smoothScaleTimer->isActive=" + << m_smoothScaleTimer->isActive () + << endl; +#endif + + // Make sure the selection contains the final image and the timer won't + // fire afterwards. + if (m_smoothScaleTimer->isActive ()) + { + resizeScaleAndMove (); + Q_ASSERT (!m_smoothScaleTimer->isActive ()); + } +} + + +// public virtual [base kpToolResizeScaleCommand] +void kpToolSelectionResizeScaleCommand::execute () +{ + QApplication::setOverrideCursor (Qt::WaitCursor); + + killSmoothScaleTimer (); + + resizeScaleAndMove (); + + environ ()->somethingBelowTheCursorChanged (); + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpToolResizeScaleCommand] +void kpToolSelectionResizeScaleCommand::unexecute () +{ + QApplication::setOverrideCursor (Qt::WaitCursor); + + killSmoothScaleTimer (); + + document ()->setSelection (*m_originalSelectionPtr); + + environ ()->somethingBelowTheCursorChanged (); + + QApplication::restoreOverrideCursor (); +} + + +#include diff --git a/kolourpaint/commands/tools/selection/kpToolSelectionResizeScaleCommand.h b/kolourpaint/commands/tools/selection/kpToolSelectionResizeScaleCommand.h new file mode 100644 index 00000000..16057757 --- /dev/null +++ b/kolourpaint/commands/tools/selection/kpToolSelectionResizeScaleCommand.h @@ -0,0 +1,107 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolSelectionResizeScaleCommand_H +#define kpToolSelectionResizeScaleCommand_H + + +#include +#include + +#include + + +class QTimer; + +class kpAbstractSelection; + + +// You could subclass kpToolResizeScaleCommand and/or +// kpToolSelectionMoveCommand instead if want a disaster. +// This is different to kpToolResizeScaleCommand in that: +// +// 1. This only works for selections. +// 2. This is designed for the size and position to change several times +// before execute(). +// +// REFACTOR: Later: I take that all back. We should merge with +// kpToolResizeScaleCommand to reduce code duplication. +class kpToolSelectionResizeScaleCommand : public QObject, + public kpNamedCommand +{ +Q_OBJECT + +public: + kpToolSelectionResizeScaleCommand (kpCommandEnvironment *environ); + virtual ~kpToolSelectionResizeScaleCommand (); + + virtual kpCommandSize::SizeType size () const; + +public: + const kpAbstractSelection *originalSelection () const; + + QPoint topLeft () const; + void moveTo (const QPoint &point); + + int width () const; + int height () const; + void resize (int width, int height, bool delayed = false); + + // (equivalent to resize() followed by moveTo() but faster) + void resizeAndMoveTo (int width, int height, const QPoint &point, + bool delayed = false); + +protected: + void killSmoothScaleTimer (); + + // If , does a fast, low-quality scale and then calls itself + // with unset for a smooth scale, a short time later. + // If acting on a text box, is ignored. + void resizeScaleAndMove (bool delayed); + +protected slots: + void resizeScaleAndMove (/*delayed = false*/); + +public: + void finalize (); + +public: + virtual void execute (); + virtual void unexecute (); + +protected: + kpAbstractSelection *m_originalSelectionPtr; + + QPoint m_newTopLeft; + int m_newWidth, m_newHeight; + + QTimer *m_smoothScaleTimer; +}; + + +#endif // kpToolSelectionResizeScaleCommand_H diff --git a/kolourpaint/commands/tools/selection/text/kpToolTextBackspaceCommand.cpp b/kolourpaint/commands/tools/selection/text/kpToolTextBackspaceCommand.cpp new file mode 100644 index 00000000..85090dfd --- /dev/null +++ b/kolourpaint/commands/tools/selection/text/kpToolTextBackspaceCommand.cpp @@ -0,0 +1,151 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include + +#include + +#include +#include + + +kpToolTextBackspaceCommand::kpToolTextBackspaceCommand (const QString &name, + int row, int col, Action action, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_row (row), m_col (col), + m_numBackspaces (0) +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + if (action == AddBackspaceNow) + addBackspace (); +} + +kpToolTextBackspaceCommand::~kpToolTextBackspaceCommand () +{ +} + + +// public +void kpToolTextBackspaceCommand::addBackspace () +{ + QList textLines = textSelection ()->textLines (); + + if (m_col > 0) + { + m_deletedText.prepend (textLines [m_row][m_col - 1]); + + textLines [m_row] = textLines [m_row].left (m_col - 1) + + textLines [m_row].mid (m_col); + m_col--; + } + else + { + if (m_row > 0) + { + int newCursorRow = m_row - 1; + int newCursorCol = textLines [newCursorRow].length (); + + m_deletedText.prepend ('\n'); + + textLines [newCursorRow] += textLines [m_row]; + + textLines.erase (textLines.begin () + m_row); + + m_row = newCursorRow; + m_col = newCursorCol; + } + } + + textSelection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_numBackspaces++; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolTextBackspaceCommand::size () const +{ + return (kpCommandSize::SizeType) m_deletedText.length () * sizeof (QChar); +} + + +// public virtual [base kpCommand] +void kpToolTextBackspaceCommand::execute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_deletedText.clear (); + int oldNumBackspaces = m_numBackspaces; + m_numBackspaces = 0; + + for (int i = 0; i < oldNumBackspaces; i++) + addBackspace (); +} + +// public virtual [base kpCommand] +void kpToolTextBackspaceCommand::unexecute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + QList textLines = textSelection ()->textLines (); + + for (int i = 0; i < (int) m_deletedText.length (); i++) + { + if (m_deletedText [i] == '\n') + { + const QString rightHalf = textLines [m_row].mid (m_col); + + textLines [m_row].truncate (m_col); + textLines.insert (textLines.begin () + m_row + 1, rightHalf); + + m_row++; + m_col = 0; + } + else + { + const QString leftHalf = textLines [m_row].left (m_col); + const QString rightHalf = textLines [m_row].mid (m_col); + + textLines [m_row] = leftHalf + m_deletedText [i] + rightHalf; + m_col++; + } + } + + m_deletedText.clear (); + + textSelection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + diff --git a/kolourpaint/commands/tools/selection/text/kpToolTextBackspaceCommand.h b/kolourpaint/commands/tools/selection/text/kpToolTextBackspaceCommand.h new file mode 100644 index 00000000..4c557f2c --- /dev/null +++ b/kolourpaint/commands/tools/selection/text/kpToolTextBackspaceCommand.h @@ -0,0 +1,64 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_TEXT_BACKSPACE_COMMAND_H +#define KP_TOOL_TEXT_BACKSPACE_COMMAND_H + + +#include + + +class kpToolTextBackspaceCommand : public kpNamedCommand +{ +public: + enum Action + { + DontAddBackspaceYet, + AddBackspaceNow + }; + + kpToolTextBackspaceCommand (const QString &name, + int row, int col, Action action, + kpCommandEnvironment *environ); + virtual ~kpToolTextBackspaceCommand (); + + void addBackspace (); + + virtual kpCommandSize::SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +protected: + int m_row, m_col; + int m_numBackspaces; + QString m_deletedText; +}; + + +#endif // KP_TOOL_TEXT_BACKSPACE_COMMAND_H diff --git a/kolourpaint/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp b/kolourpaint/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp new file mode 100644 index 00000000..10f9b954 --- /dev/null +++ b/kolourpaint/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp @@ -0,0 +1,99 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include + +#include + +#include +#include + + +kpToolTextChangeStyleCommand::kpToolTextChangeStyleCommand (const QString &name, + const kpTextStyle &newTextStyle, const kpTextStyle &oldTextStyle, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_newTextStyle (newTextStyle), + m_oldTextStyle (oldTextStyle) +{ +} + +kpToolTextChangeStyleCommand::~kpToolTextChangeStyleCommand () +{ +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolTextChangeStyleCommand::size () const +{ + return 0; +} + + +// public virtual [base kpCommand] +void kpToolTextChangeStyleCommand::execute () +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kDebug () << "kpToolTextChangeStyleCommand::execute()" + << " font=" << m_newTextStyle.fontFamily () + << " fontSize=" << m_newTextStyle.fontSize () + << " isBold=" << m_newTextStyle.isBold () + << " isItalic=" << m_newTextStyle.isItalic () + << " isUnderline=" << m_newTextStyle.isUnderline () + << " isStrikeThru=" << m_newTextStyle.isStrikeThru () + << endl; +#endif + + environ ()->setTextStyle (m_newTextStyle); + + if (textSelection ()) + textSelection ()->setTextStyle (m_newTextStyle); +} + +// public virtual [base kpCommand] +void kpToolTextChangeStyleCommand::unexecute () +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kDebug () << "kpToolTextChangeStyleCommand::unexecute()" + << " font=" << m_newTextStyle.fontFamily () + << " fontSize=" << m_newTextStyle.fontSize () + << " isBold=" << m_newTextStyle.isBold () + << " isItalic=" << m_newTextStyle.isItalic () + << " isUnderline=" << m_newTextStyle.isUnderline () + << " isStrikeThru=" << m_newTextStyle.isStrikeThru () + << endl; +#endif + + environ ()->setTextStyle (m_oldTextStyle); + + if (textSelection ()) + textSelection ()->setTextStyle (m_oldTextStyle); +} + diff --git a/kolourpaint/commands/tools/selection/text/kpToolTextChangeStyleCommand.h b/kolourpaint/commands/tools/selection/text/kpToolTextChangeStyleCommand.h new file mode 100644 index 00000000..9dda7495 --- /dev/null +++ b/kolourpaint/commands/tools/selection/text/kpToolTextChangeStyleCommand.h @@ -0,0 +1,55 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_TEXT_CHANGE_STYLE_COMMAND_H +#define KP_TOOL_TEXT_CHANGE_STYLE_COMMAND_H + + +#include +#include + + +class kpToolTextChangeStyleCommand : public kpNamedCommand +{ +public: + kpToolTextChangeStyleCommand (const QString &name, + const kpTextStyle &newTextStyle, const kpTextStyle &oldTextStyle, + kpCommandEnvironment *environ); + virtual ~kpToolTextChangeStyleCommand (); + + virtual kpCommandSize::SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +protected: + kpTextStyle m_newTextStyle, m_oldTextStyle; +}; + + +#endif // KP_TOOL_TEXT_CHANGE_STYLE_COMMAND_H diff --git a/kolourpaint/commands/tools/selection/text/kpToolTextDeleteCommand.cpp b/kolourpaint/commands/tools/selection/text/kpToolTextDeleteCommand.cpp new file mode 100644 index 00000000..a248f6f2 --- /dev/null +++ b/kolourpaint/commands/tools/selection/text/kpToolTextDeleteCommand.cpp @@ -0,0 +1,139 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include + +#include + +#include +#include + + +kpToolTextDeleteCommand::kpToolTextDeleteCommand (const QString &name, + int row, int col, Action action, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_row (row), m_col (col), + m_numDeletes (0) +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + if (action == AddDeleteNow) + addDelete (); +} + +kpToolTextDeleteCommand::~kpToolTextDeleteCommand () +{ +} + + +// public +void kpToolTextDeleteCommand::addDelete () +{ + QList textLines = textSelection ()->textLines (); + + if (m_col < (int) textLines [m_row].length ()) + { + m_deletedText.prepend (textLines [m_row][m_col]); + + textLines [m_row] = textLines [m_row].left (m_col) + + textLines [m_row].mid (m_col + 1); + } + else + { + if (m_row < (int) textLines.size () - 1) + { + m_deletedText.prepend ('\n'); + + textLines [m_row] += textLines [m_row + 1]; + textLines.erase (textLines.begin () + m_row + 1); + } + } + + textSelection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_numDeletes++; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolTextDeleteCommand::size () const +{ + return (kpCommandSize::SizeType) m_deletedText.length () * sizeof (QChar); +} + + +// public virtual [base kpCommand] +void kpToolTextDeleteCommand::execute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_deletedText.clear (); + int oldNumDeletes = m_numDeletes; + m_numDeletes = 0; + + for (int i = 0; i < oldNumDeletes; i++) + addDelete (); +} + +// public virtual [base kpCommand] +void kpToolTextDeleteCommand::unexecute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + QList textLines = textSelection ()->textLines (); + + for (int i = 0; i < (int) m_deletedText.length (); i++) + { + if (m_deletedText [i] == '\n') + { + const QString rightHalf = textLines [m_row].mid (m_col); + + textLines [m_row].truncate (m_col); + textLines.insert (textLines.begin () + m_row + 1, rightHalf); + } + else + { + const QString leftHalf = textLines [m_row].left (m_col); + const QString rightHalf = textLines [m_row].mid (m_col); + + textLines [m_row] = leftHalf + m_deletedText [i] + rightHalf; + } + } + + m_deletedText.clear (); + + textSelection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + diff --git a/kolourpaint/commands/tools/selection/text/kpToolTextDeleteCommand.h b/kolourpaint/commands/tools/selection/text/kpToolTextDeleteCommand.h new file mode 100644 index 00000000..2d820363 --- /dev/null +++ b/kolourpaint/commands/tools/selection/text/kpToolTextDeleteCommand.h @@ -0,0 +1,64 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_TEXT_DELETE_COMMAND_H +#define KP_TOOL_TEXT_DELETE_COMMAND_H + + +#include + + +class kpToolTextDeleteCommand : public kpNamedCommand +{ +public: + enum Action + { + DontAddDeleteYet, + AddDeleteNow + }; + + kpToolTextDeleteCommand (const QString &name, + int row, int col, Action action, + kpCommandEnvironment *environ); + virtual ~kpToolTextDeleteCommand (); + + void addDelete (); + + virtual kpCommandSize::SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +protected: + int m_row, m_col; + int m_numDeletes; + QString m_deletedText; +}; + + +#endif // KP_TOOL_TEXT_DELETE_COMMAND_H diff --git a/kolourpaint/commands/tools/selection/text/kpToolTextEnterCommand.cpp b/kolourpaint/commands/tools/selection/text/kpToolTextEnterCommand.cpp new file mode 100644 index 00000000..6c4a82e2 --- /dev/null +++ b/kolourpaint/commands/tools/selection/text/kpToolTextEnterCommand.cpp @@ -0,0 +1,125 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include + +#include + +#include +#include + + +kpToolTextEnterCommand::kpToolTextEnterCommand (const QString &name, + int row, int col, Action action, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_row (row), m_col (col), + m_numEnters (0) +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + if (action == AddEnterNow) + addEnter (); +} + +kpToolTextEnterCommand::~kpToolTextEnterCommand () +{ +} + + +// public +void kpToolTextEnterCommand::addEnter () +{ + QList textLines = textSelection ()->textLines (); + + const QString rightHalf = textLines [m_row].mid (m_col); + + textLines [m_row].truncate (m_col); + textLines.insert (textLines.begin () + m_row + 1, rightHalf); + + textSelection ()->setTextLines (textLines); + + m_row++; + m_col = 0; + + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_numEnters++; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolTextEnterCommand::size () const +{ + return 0; +} + + +// public virtual [base kpCommand] +void kpToolTextEnterCommand::execute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + int oldNumEnters = m_numEnters; + m_numEnters = 0; + + for (int i = 0; i < oldNumEnters; i++) + addEnter (); +} + +// public virtual [base kpCommand] +void kpToolTextEnterCommand::unexecute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + QList textLines = textSelection ()->textLines (); + + for (int i = 0; i < m_numEnters; i++) + { + Q_ASSERT (m_col == 0); + + if (m_row <= 0) + break; + + int newRow = m_row - 1; + int newCol = textLines [newRow].length (); + + textLines [newRow] += textLines [m_row]; + + textLines.erase (textLines.begin () + m_row); + + m_row = newRow; + m_col = newCol; + } + + textSelection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + diff --git a/kolourpaint/commands/tools/selection/text/kpToolTextEnterCommand.h b/kolourpaint/commands/tools/selection/text/kpToolTextEnterCommand.h new file mode 100644 index 00000000..babf968e --- /dev/null +++ b/kolourpaint/commands/tools/selection/text/kpToolTextEnterCommand.h @@ -0,0 +1,63 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_TEXT_ENTER_COMMAND_H +#define KP_TOOL_TEXT_ENTER_COMMAND_H + + +#include + + +class kpToolTextEnterCommand : public kpNamedCommand +{ +public: + enum Action + { + DontAddEnterYet, + AddEnterNow + }; + + kpToolTextEnterCommand (const QString &name, + int row, int col, Action action, + kpCommandEnvironment *environ); + virtual ~kpToolTextEnterCommand (); + + void addEnter (); + + virtual kpCommandSize::SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +protected: + int m_row, m_col; + int m_numEnters; +}; + + +#endif // KP_TOOL_TEXT_ENTER_COMMAND_H diff --git a/kolourpaint/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp b/kolourpaint/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp new file mode 100644 index 00000000..ab2347ff --- /dev/null +++ b/kolourpaint/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp @@ -0,0 +1,154 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include + +#include +#include + +#include +#include +#include +#include + + +kpToolTextGiveContentCommand::kpToolTextGiveContentCommand ( + const kpTextSelection &originalSelBorder, + const QString &name, + kpCommandEnvironment *environ) + : kpAbstractSelectionContentCommand (originalSelBorder, name, environ) +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpToolTextGiveContentCommand::() environ=" + << environ + << endl; +#endif +} + +kpToolTextGiveContentCommand::~kpToolTextGiveContentCommand () +{ +} + + +// public virtual [base kpCommand] +void kpToolTextGiveContentCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpToolTextGiveContentCommand::execute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpViewManager *vm = viewManager (); + Q_ASSERT (vm); + + // See API Doc for kpViewManager::textCursorRow() & textCursorCol(). + Q_ASSERT (vm->textCursorRow () == 0 && vm->textCursorCol () == 0); + + vm->setQueueUpdates (); + { + // + // Recreate border + // + + // The previously executed command is required to have been a + // kpToolSelectionCreateCommand, which must have been given a text + // selection with no content. + // + // However, there is a tricky case. Suppose we are called for the first + // time, where the above precondition holds. We would add content + // to the selection as expected. But the user then undoes (CTRL+Z) the + // operation, calling unexecute(). There is now no content again. + // Since selection is only a border, the user can freely deselect it + // and/or select another region without changing the command history + // or document modified state. Therefore, if they now call us again + // by redoing (CTRL+Shift+Z), there is potentially no selection at all + // or it is at an arbitrary location. + // + // This assertion covers all 3 possibilities: + // + // 1. First call: text selection with no content + // 2. Later calls: + // a) no text selection (due to deselection) + // b) text selection with no content, at an arbitrary location + Q_ASSERT (!textSelection () || !textSelection ()->hasContent ()); + + const kpTextSelection *originalTextSel = + static_cast (originalSelection ()); + if (originalTextSel->textStyle () != environ ()->textStyle ()) + environ ()->setTextStyle (originalTextSel->textStyle ()); + + doc->setSelection (*originalSelection ()); + + + // + // Add Content + // + + QList listOfOneEmptyString; + listOfOneEmptyString.append (QString ()); + textSelection ()->setTextLines (listOfOneEmptyString); + } + vm->restoreQueueUpdates (); + + // This should not have changed from the start of the method. + Q_ASSERT (vm->textCursorRow () == 0 && vm->textCursorCol () == 0); +} + +// public virtual [base kpCommand] +void kpToolTextGiveContentCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpToolTextGiveContentCommand::unexecute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + // Must have selection text content. + Q_ASSERT (doc->textSelection () && doc->textSelection ()->hasContent ()); + + kpViewManager *vm = viewManager (); + Q_ASSERT (vm); + // All the commands after us have been unexecuted, so we must be back + // to the state we were after our execute(). + Q_ASSERT (vm->textCursorRow () == 0 && vm->textCursorCol () == 0); + + // We can have faith that this is the state of the selection after + // execute(), rather than after the user tried to throw us off by + // simply selecting another region as to do that, a destroy command + // must have been used. + doc->textSelection ()->deleteContent (); + + // This should not have changed from the start of the method. + Q_ASSERT (vm->textCursorRow () == 0 && vm->textCursorCol () == 0); +} + diff --git a/kolourpaint/commands/tools/selection/text/kpToolTextGiveContentCommand.h b/kolourpaint/commands/tools/selection/text/kpToolTextGiveContentCommand.h new file mode 100644 index 00000000..796aa18e --- /dev/null +++ b/kolourpaint/commands/tools/selection/text/kpToolTextGiveContentCommand.h @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolTextGiveContentCommand_H +#define kpToolTextGiveContentCommand_H + + +#include + + +class kpTextSelection; + + +// Converts a text border (no text lines) to a text selection with 1 empty +// text line. This must be executed before any manipulations can be made +// to a text selection. +// +// Text analogue of kpToolSelectionPullFromDocumentCommand. +class kpToolTextGiveContentCommand : + public kpAbstractSelectionContentCommand +{ +public: + kpToolTextGiveContentCommand ( + const kpTextSelection &originalSelBorder, + const QString &name, + kpCommandEnvironment *environ); + virtual ~kpToolTextGiveContentCommand (); + + virtual void execute (); + virtual void unexecute (); +}; + + +#endif // kpToolTextGiveContentCommand_H diff --git a/kolourpaint/commands/tools/selection/text/kpToolTextInsertCommand.cpp b/kolourpaint/commands/tools/selection/text/kpToolTextInsertCommand.cpp new file mode 100644 index 00000000..a946a37f --- /dev/null +++ b/kolourpaint/commands/tools/selection/text/kpToolTextInsertCommand.cpp @@ -0,0 +1,108 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include + +#include + +#include +#include + +//--------------------------------------------------------------------- + +kpToolTextInsertCommand::kpToolTextInsertCommand (const QString &name, + int row, int col, QString newText, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_row (row), m_col (col) +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + addText (newText); +} + +//--------------------------------------------------------------------- + +// public +void kpToolTextInsertCommand::addText (const QString &moreText) +{ + if (moreText.isEmpty ()) + return; + + QList textLines = textSelection ()->textLines (); + const QString leftHalf = textLines [m_row].left (m_col); + const QString rightHalf = textLines [m_row].mid (m_col); + textLines [m_row] = leftHalf + moreText + rightHalf; + textSelection ()->setTextLines (textLines); + + m_newText += moreText; + m_col += moreText.length (); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolTextInsertCommand::size () const +{ + return (kpCommandSize::SizeType) m_newText.length () * sizeof (QChar); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void kpToolTextInsertCommand::execute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + QString text = m_newText; + m_newText.clear (); + addText (text); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void kpToolTextInsertCommand::unexecute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + QList textLines = textSelection ()->textLines (); + const QString leftHalf = textLines [m_row].left (m_col - m_newText.length ()); + const QString rightHalf = textLines [m_row].mid (m_col); + textLines [m_row] = leftHalf + rightHalf; + textSelection ()->setTextLines (textLines); + + m_col -= m_newText.length (); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/commands/tools/selection/text/kpToolTextInsertCommand.h b/kolourpaint/commands/tools/selection/text/kpToolTextInsertCommand.h new file mode 100644 index 00000000..b2bc3a57 --- /dev/null +++ b/kolourpaint/commands/tools/selection/text/kpToolTextInsertCommand.h @@ -0,0 +1,56 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_TEXT_INSERT_COMMAND_H +#define KP_TOOL_TEXT_INSERT_COMMAND_H + + +#include + + +class kpToolTextInsertCommand : public kpNamedCommand +{ +public: + kpToolTextInsertCommand (const QString &name, + int row, int col, QString newText, + kpCommandEnvironment *environ); + + void addText (const QString &moreText); + + virtual kpCommandSize::SizeType size () const; + + virtual void execute (); + virtual void unexecute (); + +protected: + int m_row, m_col; + QString m_newText; +}; + + +#endif // KP_TOOL_TEXT_INSERT_COMMAND_H diff --git a/kolourpaint/cursors/kpCursorLightCross.cpp b/kolourpaint/cursors/kpCursorLightCross.cpp new file mode 100644 index 00000000..10f11e82 --- /dev/null +++ b/kolourpaint/cursors/kpCursorLightCross.cpp @@ -0,0 +1,130 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_CURSOR_LIGHT_CROSS 0 + + +#include + +#include +#include + +#include + + +enum PixelValue +{ + White, Black, Transparent +}; + +static void setPixel (unsigned char *colorBitmap, + unsigned char *maskBitmap, + int width, + int y, int x, enum PixelValue pv) +{ + const int ColorBlack = 1; + const int ColorWhite = 0; + + const int MaskOpaque = 1; + const int MaskTransparent = 0; + + int colorValue, maskValue; + + switch (pv) + { + case White: + colorValue = ColorWhite; + maskValue = MaskOpaque; + break; + + case Black: + colorValue = ColorBlack; + maskValue = MaskOpaque; + break; + + case Transparent: + default: + colorValue = ColorWhite; + maskValue = MaskTransparent; + break; + } + + if (colorValue) + colorBitmap [y * (width / 8) + (x / 8)] |= (1 << (x % 8)); + + if (maskValue) + maskBitmap [y * (width / 8) + (x / 8)] |= (1 << (x % 8)); +} + + +const QCursor *kpCursorLightCrossCreate () +{ +#if DEBUG_KP_CURSOR_LIGHT_CROSS + kDebug () << "kpCursorLightCrossCreate() "; +#endif + + const int side = 24; + const int byteSize = (side * side) / 8; + unsigned char *colorBitmap = new unsigned char [byteSize]; + unsigned char *maskBitmap = new unsigned char [byteSize]; + + memset (colorBitmap, 0, byteSize); + memset (maskBitmap, 0, byteSize); + + const int oddSide = side - 1; + const int strokeLen = oddSide * 3 / 8; + + for (int i = 0; i < strokeLen; i++) + { + const enum PixelValue pv = (i % 2) ? Black : White; + + #define X_(val) (val) + #define Y_(val) (val) + #define DRAW(y,x) setPixel (colorBitmap, maskBitmap, side, (y), (x), pv) + // horizontal + DRAW (Y_(side / 2), X_(1 + i)); + DRAW (Y_(side / 2), X_(side - 1 - i)); + + // vertical + DRAW (Y_(1 + i), X_(side / 2)); + DRAW (Y_(side - 1 - i), X_(side / 2)); + #undef DRAW + #undef Y_ + #undef X_ + } + + const QSize size (side, side); + QCursor *cursor = new QCursor ( + QBitmap::fromData (size, colorBitmap, QImage::Format_MonoLSB), + QBitmap::fromData (size, maskBitmap, QImage::Format_MonoLSB)); + + delete [] maskBitmap; + delete [] colorBitmap; + + return cursor; +} + diff --git a/kolourpaint/cursors/kpCursorLightCross.h b/kolourpaint/cursors/kpCursorLightCross.h new file mode 100644 index 00000000..941e825c --- /dev/null +++ b/kolourpaint/cursors/kpCursorLightCross.h @@ -0,0 +1,39 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_CURSOR_LIGHT_CROSS_H +#define KP_CURSOR_LIGHT_CROSS_H + + +class QCursor; + + +const QCursor *kpCursorLightCrossCreate (); + + +#endif // KP_CURSOR_LIGHT_CROSS_H diff --git a/kolourpaint/cursors/kpCursorProvider.cpp b/kolourpaint/cursors/kpCursorProvider.cpp new file mode 100644 index 00000000..96c4e69b --- /dev/null +++ b/kolourpaint/cursors/kpCursorProvider.cpp @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include + +#include + + +static const QCursor *TheLightCursor = 0; + + +// public static +QCursor kpCursorProvider::lightCross () +{ + // TODO: don't leak (although it's cleaned up on exit by OS anyway) + if (!::TheLightCursor) + ::TheLightCursor = kpCursorLightCrossCreate (); + + return *::TheLightCursor; +} diff --git a/kolourpaint/cursors/kpCursorProvider.h b/kolourpaint/cursors/kpCursorProvider.h new file mode 100644 index 00000000..9714261d --- /dev/null +++ b/kolourpaint/cursors/kpCursorProvider.h @@ -0,0 +1,43 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_CURSOR_PROVIDER_H +#define KP_CURSOR_PROVIDER_H + + +class QCursor; + + +class kpCursorProvider +{ +public: + static QCursor lightCross (); +}; + + +#endif // KP_CURSOR_PROVIDER_H diff --git a/kolourpaint/dialogs/imagelib/effects/kpEffectsDialog.cpp b/kolourpaint/dialogs/imagelib/effects/kpEffectsDialog.cpp new file mode 100644 index 00000000..1f9326fe --- /dev/null +++ b/kolourpaint/dialogs/imagelib/effects/kpEffectsDialog.cpp @@ -0,0 +1,366 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECTS_DIALOG 0 + + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// protected static +int kpEffectsDialog::s_lastWidth = 640; +int kpEffectsDialog::s_lastHeight = 620; + + +kpEffectsDialog::kpEffectsDialog (bool actOnSelection, + kpTransformDialogEnvironment *_env, + QWidget *parent, + int defaultSelectedEffect) + : kpTransformPreviewDialog (kpTransformPreviewDialog::Preview, + true/*reserve top row*/, + QString()/*caption*/, + QString()/*afterActionText (no Dimensions Group Box)*/, + actOnSelection, + _env, + parent), + m_delayedUpdateTimer (new QTimer (this)), + m_effectsComboBox (0), + m_settingsGroupBox (0), + m_settingsLayout (0), + m_effectWidget (0) +{ +#if DEBUG_KP_EFFECTS_DIALOG + kDebug () << "kpEffectsDialog::kpEffectsDialog()"; +#endif + const bool e = updatesEnabled (); + setUpdatesEnabled (false); + + + if (actOnSelection) + setWindowTitle (i18nc ("@title:window", "More Image Effects (Selection)")); + else + setWindowTitle (i18nc ("@title:window", "More Image Effects")); + + + m_delayedUpdateTimer->setSingleShot (true); + connect (m_delayedUpdateTimer, SIGNAL (timeout ()), + this, SLOT (slotUpdateWithWaitCursor ())); + + + KHBox *effectContainer = new KHBox (mainWidget ()); + effectContainer->setSpacing (spacingHint () * 4 + /*need more space for QGroupBox titles*/); + effectContainer->setMargin (0); + + QLabel *label = new QLabel (i18n ("&Effect:"), effectContainer); + + m_effectsComboBox = new KComboBox (effectContainer); + // Keep in alphabetical order. + // TODO: What about translations? + // sync: order in selectEffect(). + m_effectsComboBox->addItem (i18n ("Balance")); + m_effectsComboBox->addItem (i18n ("Emboss")); + m_effectsComboBox->addItem (i18n ("Flatten")); + m_effectsComboBox->addItem (i18n ("Histogram Equalizer")); + m_effectsComboBox->addItem (i18n ("Hue, Saturation, Value")); + m_effectsComboBox->addItem (i18n ("Invert")); + m_effectsComboBox->addItem (i18n ("Reduce Colors")); + m_effectsComboBox->addItem (i18n ("Soften & Sharpen")); + + label->setBuddy (m_effectsComboBox); + effectContainer->setStretchFactor (m_effectsComboBox, 1); + + addCustomWidgetToFront (effectContainer); + + + m_settingsGroupBox = new QGroupBox (mainWidget ()); + m_settingsLayout = new QVBoxLayout ( m_settingsGroupBox ); + m_settingsLayout->setMargin( marginHint () * 2 ); + m_settingsLayout->setSpacing( spacingHint () ); + addCustomWidgetToBack (m_settingsGroupBox); + + + connect (m_effectsComboBox, SIGNAL (activated (int)), + this, SLOT (selectEffect (int))); + selectEffect (defaultSelectedEffect); + + + resize (s_lastWidth, s_lastHeight); + + +#if DEBUG_KP_EFFECTS_DIALOG + kDebug () << "about to setUpdatesEnabled()"; +#endif + // OPT: The preview pixmap gets recalculated here and then possibly + // again when QResizeEvent fires, when the dialog is shown. + setUpdatesEnabled (e); +#if DEBUG_KP_EFFECTS_DIALOG + kDebug () << endl + << endl + << endl; +#endif +} + +kpEffectsDialog::~kpEffectsDialog () +{ + s_lastWidth = width (); + s_lastHeight = height (); +} + + +// public virtual [base kpTransformPreviewDialog] +bool kpEffectsDialog::isNoOp () const +{ + if (!m_effectWidget) + return true; + + return m_effectWidget->isNoOp (); +} + +// public +kpEffectCommandBase *kpEffectsDialog::createCommand () const +{ + if (!m_effectWidget) + return 0; + + return m_effectWidget->createCommand (m_environ->commandEnvironment ()); +} + + +// protected virtual [base kpTransformPreviewDialog] +QSize kpEffectsDialog::newDimensions () const +{ + kpDocument *doc = document (); + if (!doc) + return QSize (); + + return QSize (doc->width (m_actOnSelection), + doc->height (m_actOnSelection)); +} + +// protected virtual [base kpTransformPreviewDialog] +QImage kpEffectsDialog::transformPixmap (const QImage &pixmap, + int targetWidth, int targetHeight) const +{ + QImage pixmapWithEffect; + + if (m_effectWidget && !m_effectWidget->isNoOp ()) + pixmapWithEffect = m_effectWidget->applyEffect (pixmap); + else + pixmapWithEffect = pixmap; + + return kpPixmapFX::scale (pixmapWithEffect, targetWidth, targetHeight); +} + + +// public +int kpEffectsDialog::selectedEffect () const +{ + return m_effectsComboBox->currentIndex (); +} + +// public slot +void kpEffectsDialog::selectEffect (int which) +{ +#if DEBUG_KP_EFFECTS_DIALOG + kDebug () << "kpEffectsDialog::selectEffect(" << which << ")"; +#endif + + if (which < 0 || + which >= m_effectsComboBox->count ()) + { + return; + } + + if (which != m_effectsComboBox->currentIndex ()) + m_effectsComboBox->setCurrentIndex (which); + + + delete m_effectWidget; + m_effectWidget = 0; + + + m_settingsGroupBox->setWindowTitle(QString()); + +#define CREATE_EFFECT_WIDGET(name) \ + m_effectWidget = new name (m_actOnSelection, m_settingsGroupBox) + // sync: order in constructor. + switch (which) + { + case 0: + CREATE_EFFECT_WIDGET (kpEffectBalanceWidget); + break; + + case 1: + CREATE_EFFECT_WIDGET (kpEffectEmbossWidget); + break; + + case 2: + CREATE_EFFECT_WIDGET (kpEffectFlattenWidget); + break; + + case 3: + CREATE_EFFECT_WIDGET (kpEffectToneEnhanceWidget); + break; + + case 4: + CREATE_EFFECT_WIDGET (kpEffectHSVWidget); + break; + + case 5: + CREATE_EFFECT_WIDGET (kpEffectInvertWidget); + break; + + case 6: + CREATE_EFFECT_WIDGET (kpEffectReduceColorsWidget); + break; + + case 7: + CREATE_EFFECT_WIDGET (kpEffectBlurSharpenWidget); + break; + } +#undef CREATE_EFFECT_WIDGET + + + if (m_effectWidget) + { + const bool e = updatesEnabled (); + setUpdatesEnabled (false); + + #if DEBUG_KP_EFFECTS_DIALOG + kDebug () << "widget exists for effect #"; + #endif + m_settingsGroupBox->setTitle (m_effectWidget->caption ()); + + // Show widget. + // + // Don't resize the whole dialog when doing this. + // This seems to work magically without any extra code with Qt4. + #if DEBUG_KP_EFFECTS_DIALOG + kDebug () << "addWidget"; + #endif + m_settingsLayout->addWidget (m_effectWidget); + #if DEBUG_KP_EFFECTS_DIALOG + kDebug () << "show widget"; + #endif + m_effectWidget->show (); + + connect (m_effectWidget, SIGNAL (settingsChangedNoWaitCursor ()), + this, SLOT (slotUpdate ())); + connect (m_effectWidget, SIGNAL (settingsChanged ()), + this, SLOT (slotUpdateWithWaitCursor ())); + connect (m_effectWidget, SIGNAL (settingsChangedDelayed ()), + this, SLOT (slotDelayedUpdate ())); + + #if DEBUG_KP_EFFECTS_DIALOG + kDebug () << "about to setUpdatesEnabled()"; + #endif + setUpdatesEnabled (e); + } + + +#if DEBUG_KP_EFFECTS_DIALOG + kDebug () << "done" + << endl + << endl + << endl; +#endif +} + + +// protected slot virtual [base kpTransformPreviewDialog] +void kpEffectsDialog::slotUpdate () +{ +#if DEBUG_KP_EFFECTS_DIALOG + kDebug () << "kpEffectsDialog::slotUpdate()" + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif + + m_delayedUpdateTimer->stop (); + + kpTransformPreviewDialog::slotUpdate (); +} + +// protected slot virtual [base kpTransformPreviewDialog] +void kpEffectsDialog::slotUpdateWithWaitCursor () +{ +#if DEBUG_KP_EFFECTS_DIALOG + kDebug () << "kpEffectsDialog::slotUpdateWithWaitCursor()" + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif + + m_delayedUpdateTimer->stop (); + + kpTransformPreviewDialog::slotUpdateWithWaitCursor (); +} + + +// protected slot +void kpEffectsDialog::slotDelayedUpdate () +{ +#if DEBUG_KP_EFFECTS_DIALOG + kDebug () << "kpEffectsDialog::slotDelayedUpdate()" + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif + m_delayedUpdateTimer->stop (); + + // (single shot) + m_delayedUpdateTimer->start (400/*ms*/); +} + + +#include diff --git a/kolourpaint/dialogs/imagelib/effects/kpEffectsDialog.h b/kolourpaint/dialogs/imagelib/effects/kpEffectsDialog.h new file mode 100644 index 00000000..1a21c7e0 --- /dev/null +++ b/kolourpaint/dialogs/imagelib/effects/kpEffectsDialog.h @@ -0,0 +1,92 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_EFFECTS_DIALOG_H +#define KP_EFFECTS_DIALOG_H + + +#include + + +class QGroupBox; +class QImage; +class QTimer; +class QVBoxLayout; + +class KComboBox; + +class kpEffectCommandBase; +class kpEffectWidgetBase; + + +class kpEffectsDialog : public kpTransformPreviewDialog +{ +Q_OBJECT + +public: + // Specifying is more efficient than leaving it + // as 0 and then calling selectEffect() afterwards. + kpEffectsDialog (bool actOnSelection, + kpTransformDialogEnvironment *_env, + QWidget *parent, + int defaultSelectedEffect = 0); + virtual ~kpEffectsDialog (); + + virtual bool isNoOp () const; + kpEffectCommandBase *createCommand () const; + +protected: + virtual QSize newDimensions () const; + virtual QImage transformPixmap (const QImage &pixmap, + int targetWidth, int targetHeight) const; + +public: + int selectedEffect () const; +public slots: + void selectEffect (int which); + +protected slots: + virtual void slotUpdate (); + virtual void slotUpdateWithWaitCursor (); + + void slotDelayedUpdate (); + +protected: + static int s_lastWidth, s_lastHeight; + + QTimer *m_delayedUpdateTimer; + + KComboBox *m_effectsComboBox; + QGroupBox *m_settingsGroupBox; + QVBoxLayout *m_settingsLayout; + + kpEffectWidgetBase *m_effectWidget; +}; + + +#endif // KP_EFFECTS_DIALOG_H diff --git a/kolourpaint/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp b/kolourpaint/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp new file mode 100644 index 00000000..cccba280 --- /dev/null +++ b/kolourpaint/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp @@ -0,0 +1,793 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DOCUMENT_META_INFO_DIALOG 0 + + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +struct kpDocumentMetaInfoDialogPrivate +{ + const kpDocumentMetaInfo *originalMetaInfoPtr; + + KDoubleNumInput *horizDpiInput, *vertDpiInput; + KIntNumInput *horizOffsetInput, *vertOffsetInput; + + QTableWidget *fieldsTableWidget; + QPushButton *fieldsAddRowButton, *fieldsDeleteRowButton, *fieldsResetButton; +}; + + +// (shared by all dialogs, across all main windows, in a KolourPaint instance) +static int LastWidth = -1, LastHeight = -1; + + +// sync: You must keep DpiMinStep = 10 ^ (-DpiPrecision). +// +// You can increase the precision to reduce the chance of inadvertently changing +// the resolution when converting from kpDocumentMetaInfo's "dots per meter" +// to our "dots per inch" and back. It would be bad if simply going into this +// dialog and pressing OK changed the resolution (it's unlikely but I still think +// it might happen with the current precision). +// TODO: On a related note, for many particular resolutions, if the user enters +// one of them into the UI, presses OK and then comes back to the dialog, +// s/he is presented with a different resolution to the one typed in. +// Maybe make DotsPerMeter[XY] be of type "double" instead of "int" to +// solve this problem? +// +// Of course, if you increase the precision too much, the minimum step will +// become so small that it will start experiencing floating point inaccuracies +// esp. since we use it for the "DpiUnspecified" hack. +static const int DpiPrecision = 3; +static const double DpiMinStep = 0.001; + +static const double DpiLegalMin = + kpDocumentMetaInfo::MinDotsPerMeter / KP_INCHES_PER_METER; + +// Out of range represents unspecified DPI. +static const double DpiUnspecified = ::DpiLegalMin - ::DpiMinStep; + +static const double DpiInputMin = ::DpiUnspecified; +static const double DpiInputMax = + kpDocumentMetaInfo::MaxDotsPerMeter / KP_INCHES_PER_METER; + +// The increment the DPI spinboxes jump by when they're clicked. +// +// We make this relatively big since people don't usually just increase their +// DPIs by 1 or so -- they are usually changing from say, 72, to 96. +// +// Obviously, making it equal to DpiMinStep is too slow a UI. Therefore, with +// our big setting, the user will still have to manually change the value in +// the spinbox, using the keyboard, after all their clicking to ensure it is +// exactly the value they want. +static const double DpiInputStep = 10; + + +// TODO: Halve groupbox layout margins in every other file since it doesn't +// seem to be need in Qt4. +kpDocumentMetaInfoDialog::kpDocumentMetaInfoDialog ( + const kpDocumentMetaInfo *docMetaInfo, + QWidget *parent) + + : KDialog (parent), + d (new kpDocumentMetaInfoDialogPrivate ()) +{ + d->originalMetaInfoPtr = docMetaInfo; + + + setCaption (i18nc ("@title:window", "Document Properties")); + setButtons (KDialog::Ok | KDialog::Cancel); + + QWidget *baseWidget = new QWidget (this); + setMainWidget (baseWidget); + + + // + // DPI Group Box + // + + + Q_ASSERT (::DpiInputMin < ::DpiInputMax); + + QGroupBox *dpiGroupBox = new QGroupBox (i18n ("Dots &Per Inch (DPI)"), + baseWidget); + + d->horizDpiInput = new KDoubleNumInput ( + ::DpiInputMin/*lower*/, + ::DpiInputMax/*upper*/, + 0/*value*/, + dpiGroupBox, + ::DpiInputStep/*step*/, + ::DpiPrecision/*precision*/); + d->horizDpiInput->setLabel (i18n ("Horizontal:"), + Qt::AlignTop | Qt::AlignHCenter); + d->horizDpiInput->setSpecialValueText (i18n ("Unspecified")); + + QLabel *dpiXLabel = new QLabel ( + i18nc ("Horizontal DPI 'x' Vertical DPI", " x "), dpiGroupBox); + dpiXLabel->setAlignment (Qt::AlignCenter); + + d->vertDpiInput = new KDoubleNumInput ( + ::DpiInputMin/*lower*/, + ::DpiInputMax/*upper*/, + 0/*value*/, + dpiGroupBox, + ::DpiInputStep/*step*/, + ::DpiPrecision/*precision*/); + d->vertDpiInput->setLabel (i18n ("Vertical:"), + Qt::AlignTop | Qt::AlignHCenter); + d->vertDpiInput->setSpecialValueText (i18n ("Unspecified")); + + + QGridLayout *dpiLay = new QGridLayout (dpiGroupBox); + dpiLay->setSpacing (spacingHint ()); + dpiLay->setMargin (marginHint ()); + + dpiLay->addWidget (d->horizDpiInput, 0, 0); + dpiLay->addWidget (dpiXLabel, 0, 1); + dpiLay->addWidget (d->vertDpiInput, 0, 2); + + dpiLay->addItem (new QSpacerItem (0/*width*/, 0/*height*/), + 1, 0, 1/*row span*/, 3/*col span*/); + dpiLay->setRowStretch (1, 1); + + + dpiGroupBox->setWhatsThis ( + i18n ( + "" + "

Dots Per Inch (DPI) specifies the number of pixels" + " of the image that should be printed inside one inch (2.54cm).

" + + "

The higher the image's DPI, the smaller the printed image." + " Note that your printer is unlikely to produce high" + " quality prints if you increase this to more than 300 or 600 DPI," + " depending on the printer.

" + + "

If you would like to print the image so that it is the same" + " size as it is displayed on the screen, set the image's DPI" + " values to be the same as the screen's.

" + + // TODO: This is currently not true! + // See "96dpi" TODO in kpMainWindow::sendPixmapToPrinter(). + // This also why we don't try to report the current screen DPI + // for the above paragraph. + "

If either DPI value is Unspecified, the image will also" + " be printed to be the same size as on the screen.

" + + "

Not all image formats support DPI values. If the format you" + " save in does not support them, they will not be saved.

" + "
" + )); + + + // + // Offset Group Box + // + + + QGroupBox *offsetGroupBox = new QGroupBox (i18n ("O&ffset"), baseWidget); + + d->horizOffsetInput = new KIntNumInput (offsetGroupBox); + d->horizOffsetInput->setLabel (i18n ("Horizontal:"), + Qt::AlignTop | Qt::AlignHCenter); + d->horizOffsetInput->setRange ( + kpDocumentMetaInfo::MinOffset, + kpDocumentMetaInfo::MaxOffset); + d->horizOffsetInput->setSliderEnabled(false); + + d->vertOffsetInput = new KIntNumInput (offsetGroupBox); + d->vertOffsetInput->setLabel (i18n ("Vertical:"), + Qt::AlignTop | Qt::AlignHCenter); + d->vertOffsetInput->setRange ( + kpDocumentMetaInfo::MinOffset, + kpDocumentMetaInfo::MaxOffset); + d->vertOffsetInput->setSliderEnabled(false); + + + QGridLayout *offsetLay = new QGridLayout (offsetGroupBox); + offsetLay->setSpacing (spacingHint ()); + offsetLay->setMargin (marginHint ()); + + offsetLay->addWidget (d->horizOffsetInput, 0, 0); + offsetLay->addWidget (d->vertOffsetInput, 0, 1); + + offsetLay->addItem (new QSpacerItem (0/*width*/, 0/*height*/), + 1, 0, 1/*row span*/, 2/*col span*/); + offsetLay->setRowStretch (1, 1); + + + offsetGroupBox->setWhatsThis ( + i18n ( + "" + "

The Offset is the relative position where this image" + " should be placed, compared to other images.

" + + "

Not all image formats support the Offset feature." + " If the format you save in does not support it, the values" + " specified here will not be saved.

" + "
" + )); + + + // + // Fields Group Box + // + + + QGroupBox *fieldsGroupBox = new QGroupBox (i18n ("&Text Fields"), + baseWidget); + + d->fieldsTableWidget = new QTableWidget (fieldsGroupBox); + d->fieldsTableWidget->setEditTriggers(QAbstractItemView::AllEditTriggers); + + connect (d->fieldsTableWidget, SIGNAL (currentCellChanged (int, int, int, int)), + SLOT (slotFieldsCurrentCellChanged (int, int, int, int))); + + connect (d->fieldsTableWidget, SIGNAL (itemChanged (QTableWidgetItem *)), + SLOT (slotFieldsItemChanged (QTableWidgetItem *))); + + KHBox *fieldsAddDeleteButtonsBox = new KHBox (fieldsGroupBox); + fieldsAddDeleteButtonsBox->setMargin (0); + fieldsAddDeleteButtonsBox->setSpacing (spacingHint ()); + + d->fieldsAddRowButton = new QPushButton (i18n ("&Add Row"), + fieldsAddDeleteButtonsBox); + connect (d->fieldsAddRowButton, SIGNAL (clicked ()), + SLOT (slotFieldsAddRowButtonClicked ())); + + d->fieldsDeleteRowButton = new QPushButton (i18n ("&Delete Row"), + fieldsAddDeleteButtonsBox); + connect (d->fieldsDeleteRowButton, SIGNAL (clicked ()), + SLOT (slotFieldsDeleteRowButtonClicked ())); + + + d->fieldsResetButton = new QPushButton (i18n ("&Reset"), + fieldsGroupBox); + connect (d->fieldsResetButton, SIGNAL (clicked ()), + SLOT (setUIToOriginalMetaInfo ())); + + + QGridLayout *fieldsLayout = new QGridLayout (fieldsGroupBox); + fieldsLayout->setSpacing (spacingHint ()); + fieldsLayout->setMargin (marginHint ()); + + fieldsLayout->addWidget (d->fieldsTableWidget, 0, 0, 1/*row span*/, 2/*col span*/); + fieldsLayout->addWidget (fieldsAddDeleteButtonsBox, 1, 0, Qt::AlignLeft); + fieldsLayout->addWidget (d->fieldsResetButton, 1, 1, Qt::AlignRight); + + + fieldsGroupBox->setWhatsThis ( + i18n ( + "" + "

Text Fields provide extra information about the image." + " This is probably a comment area that you can freely write any text in.

" + + "

However, this is format-specific so the fields could theoretically be" + " computer-interpreted data - that you should not modify -" + " but this is unlikely.

" + + "

Not all image formats support Text Fields. If the format" + " you save in does not support them, they will not be saved.

" + "
" + )); + + + // + // Global Layout + // + + + QGridLayout *baseLayout = new QGridLayout (baseWidget); + baseLayout->setSpacing (spacingHint ()); + baseLayout->setMargin (0); + + // Col 0 + baseLayout->addWidget (dpiGroupBox, 0, 0); + baseLayout->addWidget (offsetGroupBox, 1, 0); + + // Col 1 + baseLayout->addWidget (fieldsGroupBox, 0, 1, 2/*row span*/, 1/*col span*/); + baseLayout->setColumnStretch (1, 1/*stretch*/); + + + // + // Remaining UI Setup + // + + + setUIToOriginalMetaInfo (); + + + if (::LastWidth > 0 && ::LastHeight > 0) + resize (::LastWidth, ::LastHeight); +} + +//--------------------------------------------------------------------- + +kpDocumentMetaInfoDialog::~kpDocumentMetaInfoDialog () +{ + ::LastWidth = width (), ::LastHeight = height (); + + delete d; +} + +//--------------------------------------------------------------------- +// private + +void kpDocumentMetaInfoDialog::editCell (int r, int c) +{ + d->fieldsTableWidget->setCurrentCell (r, c); + d->fieldsTableWidget->editItem (d->fieldsTableWidget->item (r, c)); +} + +//--------------------------------------------------------------------- +// private slot + +void kpDocumentMetaInfoDialog::setUIToOriginalMetaInfo () +{ + // Set DPI spinboxes. + d->horizDpiInput->setValue (d->originalMetaInfoPtr->dotsPerMeterX () / + KP_INCHES_PER_METER); + d->vertDpiInput->setValue (d->originalMetaInfoPtr->dotsPerMeterY () / + KP_INCHES_PER_METER); + + + // Set Offset spinboxes. + d->horizOffsetInput->setValue (d->originalMetaInfoPtr->offset ().x ()); + d->vertOffsetInput->setValue (d->originalMetaInfoPtr->offset ().y ()); + + + // Set Text Fields. + // + // Block itemChanged() signal as slotFieldsItemChanged() should not get called + // when rows are half-created. + const bool b = d->fieldsTableWidget->blockSignals (true); + { + d->fieldsTableWidget->clear (); + + d->fieldsTableWidget->setRowCount (d->originalMetaInfoPtr->textKeys ().size ()); + d->fieldsTableWidget->setColumnCount (2); + + QStringList fieldsHeader; + fieldsHeader << i18n ("Key") << i18n ("Value"); + d->fieldsTableWidget->setHorizontalHeaderLabels (fieldsHeader); + + int row = 0; + foreach (const QString &key, d->originalMetaInfoPtr->textKeys ()) + { + d->fieldsTableWidget->setItem (row, 0/*1st col*/, + new QTableWidgetItem (key)); + d->fieldsTableWidget->setItem (row, 1/*2nd col*/, + new QTableWidgetItem (d->originalMetaInfoPtr->text (key))); + + row++; + } + + fieldsAppendEmptyRow (); + } + d->fieldsTableWidget->blockSignals (b); + + + editCell (0/*row*/, 0/*col*/); + + + enableFieldsDeleteRowButtonIfShould (); +} + +//--------------------------------------------------------------------- +// public + +bool kpDocumentMetaInfoDialog::isNoOp () const +{ + return (metaInfo () == *d->originalMetaInfoPtr); +} + +//--------------------------------------------------------------------- + +// public +kpDocumentMetaInfo kpDocumentMetaInfoDialog::originalMetaInfo () const +{ + return *d->originalMetaInfoPtr; +} + +// public +kpDocumentMetaInfo kpDocumentMetaInfoDialog::metaInfo ( + QString *errorMessage) const +{ + if (errorMessage) + { + // No errors to start with. + *errorMessage = QString (); + } + + + kpDocumentMetaInfo ret; + + + if (d->horizDpiInput->value () < ::DpiLegalMin) + ret.setDotsPerMeterX (0/*unspecified*/); + else + ret.setDotsPerMeterX (qRound (d->horizDpiInput->value () * KP_INCHES_PER_METER)); + + if (d->vertDpiInput->value () < ::DpiLegalMin) + ret.setDotsPerMeterY (0/*unspecified*/); + else + ret.setDotsPerMeterY (qRound (d->vertDpiInput->value () * KP_INCHES_PER_METER)); + + + ret.setOffset (QPoint (d->horizOffsetInput->value (), + d->vertOffsetInput->value ())); + + + for (int r = 0; r < d->fieldsTableWidget->rowCount (); r++) + { + const QString key = d->fieldsTableWidget->item (r, 0)->text (); + const QString value = d->fieldsTableWidget->item (r, 1)->text (); + + // Empty key? + if (key.isEmpty ()) + { + // Empty value too? + if (value.isEmpty ()) + { + // Ignore empty row. + continue; + } + // Value without a key? + else + { + if (errorMessage) + { + *errorMessage = + ki18n ("The text value \"%1\" on line %2 requires a key.") + .subs (value).subs (r + 1/*count from 1*/).toString (); + + // Print only 1 error message per method invocation. + errorMessage = 0; + } + + // Ignore. + continue; + } + } + + // Duplicate key? + if (ret.textKeys ().contains (key)) + { + if (errorMessage) + { + int q; + for (q = 0; q < r; q++) + { + if (d->fieldsTableWidget->item (q, 0)->text () == key) + break; + } + Q_ASSERT (q != r); + + *errorMessage = + ki18n ("All text keys must be unique. The text key \"%1\"" + " on lines %2 and %3 are identical.") + .subs (key) + .subs (q + 1/*count from 1*/) + .subs (r + 1/*count from 1*/) + .toString (); + + // Print only 1 error message per method invocation. + errorMessage = 0; + } + + // Ignore this duplicate - keep the first value of the key. + continue; + } + + ret.setText (key, value); + + } // for (r = 0; r < table widget rows; r++) { + + + return ret; +} + + +// private +void kpDocumentMetaInfoDialog::fieldsUpdateVerticalHeader () +{ + QStringList vertLabels; + for (int r = 1; r <= d->fieldsTableWidget->rowCount (); r++) + vertLabels << QString::number (r); + + d->fieldsTableWidget->setVerticalHeaderLabels (vertLabels); +} + + +// private +void kpDocumentMetaInfoDialog::fieldsAddEmptyRow (int atRow) +{ + // Block itemChanged() signal as slotFieldsItemChanged() should not get called + // when rows are half-created. + const bool b = d->fieldsTableWidget->blockSignals (true); + { + d->fieldsTableWidget->insertRow (atRow); + + d->fieldsTableWidget->setItem (atRow, 0, new QTableWidgetItem (QString ())); + d->fieldsTableWidget->setItem (atRow, 1, new QTableWidgetItem (QString ())); + } + d->fieldsTableWidget->blockSignals (b); + + // Hack around Qt's failure to redraw these sometimes. + fieldsUpdateVerticalHeader (); + + enableFieldsDeleteRowButtonIfShould (); +} + +// private +void kpDocumentMetaInfoDialog::fieldsAppendEmptyRow () +{ + fieldsAddEmptyRow (d->fieldsTableWidget->rowCount ()); +} + + +// private +bool kpDocumentMetaInfoDialog::isFieldsRowDeleteable (int row) const +{ + // Can't delete no row and can't delete last (always blank) row, which + // is used to make it easy for the user to add rows without pressing + // the "Add" button explicitly. + return (row >= 0 && row < d->fieldsTableWidget->rowCount () - 1); +} + +// private +void kpDocumentMetaInfoDialog::fieldsDeleteRow (int r) +{ +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "kpDocumentMetaInfoDialog::fieldsDeleteRow(" + << "row=" << r << ")" + << " currentRow=" << d->fieldsTableWidget->currentRow () << endl; +#endif + + Q_ASSERT (isFieldsRowDeleteable (r)); + + if (r == d->fieldsTableWidget->currentRow ()) + { + // Assertion follows from previous assertion. + const int newRow = r + 1; + #if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "\tnewRow=" << newRow; + #endif + Q_ASSERT (newRow < d->fieldsTableWidget->rowCount ()); + + int newCol = d->fieldsTableWidget->currentColumn (); + #if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "\tnewCol=" << newCol; + #endif + if (newCol != 0 && newCol != 1) + { + newCol = 0; + #if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "\t\tcorrecting to " << newCol; + #endif + } + + // WARNING: You must call this _before_ deleting the row. Else, you'll + // trigger a Qt bug where if the editor is active on the row to + // be deleted, it might crash. To reproduce, move this line to + // after removeRow() (and subtract 1 from newRow) and + // press the "Delete Row" button several times in succession + // very quickly. + // + // TODO: This usually results in a redraw error if the scrollbar scrolls + // after deleting the 2nd last row. Qt bug. + editCell (newRow, newCol); + } + + + d->fieldsTableWidget->removeRow (r); + + + fieldsUpdateVerticalHeader (); + + enableFieldsDeleteRowButtonIfShould (); +} + + +// private +void kpDocumentMetaInfoDialog::enableFieldsDeleteRowButtonIfShould () +{ +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "kpDocumentMetaInfoDialog::enableFieldsDeleteRowButtonIfShould()"; +#endif + + const int r = d->fieldsTableWidget->currentRow (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "\tr=" << r; +#endif + + d->fieldsDeleteRowButton->setEnabled (isFieldsRowDeleteable (r)); +} + + +// private slot +void kpDocumentMetaInfoDialog::slotFieldsCurrentCellChanged (int row, int col, + int oldRow, int oldCol) +{ +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "kpDocumentMetaInfoDialog::slotFieldsCurrentCellChanged(" + << "row=" << row << ",col=" << col + << ",oldRow=" << oldRow << ",oldCol=" << oldCol + << ")" << endl; +#endif + + (void) row; + (void) col; + (void) oldRow; + (void) oldCol; + + enableFieldsDeleteRowButtonIfShould (); +} + +//--------------------------------------------------------------------- +// private slot + +void kpDocumentMetaInfoDialog::slotFieldsItemChanged (QTableWidgetItem *it) +{ +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "kpDocumentMetaInfoDialog::slotFieldsItemChanged(" + << "item=" << it << ") rows=" << d->fieldsTableWidget->rowCount () + << endl; +#endif + + const int r = d->fieldsTableWidget->row (it); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "\tr=" << r; +#endif + Q_ASSERT (r >= 0 && r < d->fieldsTableWidget->rowCount ()); + + const QString key = d->fieldsTableWidget->item (r, 0)->text (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << " key='" << key << "'"; +#endif + + const QString value = d->fieldsTableWidget->item (r, 1)->text (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << " value='" << value << "'"; +#endif + + // At the last row? + if (r == d->fieldsTableWidget->rowCount () - 1) + { + // Typed some text? + if (!key.isEmpty () || !value.isEmpty ()) + { + // LOTODO: If we're called due to the cell's text being finalized + // as a result of the user pressing the "Add Row" button, + // should this really append a row since + // slotFieldsAddRowButtonClicked() button is going to add + // another one? That's two rows when the user only clicked + // that button once! + fieldsAppendEmptyRow (); + } + } +// Deletes a row if it is emptied of text. +#if 0 + // At any normal row? + else + { // Emptied all the text? + if (key.isEmpty () && value.isEmpty ()) + { + // This crashes when the user tabs away after the text is deleted. + // We could use a single shot but that's asking for trouble depending + // on what happens between us and the single shot. + // + // In any case, disabling this makes us more consistent with + // "Add Row" which allows us to add empty rows. + //fieldsDeleteRow (r); + } + } +#endif +} + +//--------------------------------------------------------------------- +// private slot + +void kpDocumentMetaInfoDialog::slotFieldsAddRowButtonClicked () +{ +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "kpDocumentMetaInfoDialog::slotFieldsAddRowButtonClicked()" + << endl; +#endif + + const int r = d->fieldsTableWidget->currentRow (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "\tr=" << r; +#endif + + // (if no row is selected, r = -1) + fieldsAddEmptyRow (r + 1); + + // Edit the key of this new row (column 0). + // No one edits the value first (column 1). + editCell ((r + 1)/*row*/, 0/*col*/); +} + +//--------------------------------------------------------------------- + +// private slot +void kpDocumentMetaInfoDialog::slotFieldsDeleteRowButtonClicked () +{ +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "kpDocumentMetaInfoDialog::slotFieldsDeleteRowButtonClicked()" + << endl; +#endif + + const int r = d->fieldsTableWidget->currentRow (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + kDebug () << "\tr=" << r; +#endif + + Q_ASSERT (isFieldsRowDeleteable (r)); + fieldsDeleteRow (r); +} + + +// private slot virtual [base QDialog] +void kpDocumentMetaInfoDialog::accept () +{ + // Validate text fields. + QString errorMessage; + (void) metaInfo (&errorMessage); + if (!errorMessage.isEmpty ()) + { + KMessageBox::sorry (this, errorMessage, i18nc ("@title:window", "Invalid Text Fields")); + return; + } + + KDialog::accept (); +} + + +#include diff --git a/kolourpaint/dialogs/imagelib/kpDocumentMetaInfoDialog.h b/kolourpaint/dialogs/imagelib/kpDocumentMetaInfoDialog.h new file mode 100644 index 00000000..24e7f04a --- /dev/null +++ b/kolourpaint/dialogs/imagelib/kpDocumentMetaInfoDialog.h @@ -0,0 +1,114 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDocumentMetaInfoDialog_H +#define kpDocumentMetaInfoDialog_H + + +#include + + +class QTableWidgetItem; + +class kpDocumentMetaInfo; + + +// Dialog for editing document meta information (see kpDocumentMetaInfo). +// It contains: +// +// 1. DPI spinboxes +// 2. Offset spinboxes +// 3. Text Fields +// +// The Text Fields widget always keeps an empty key-value row at the bottom. +// If text is entered in this row, a new one is created. This allows users +// to add new rows without pressing any pushbuttons. +class kpDocumentMetaInfoDialog : public KDialog +{ +Q_OBJECT + +public: + kpDocumentMetaInfoDialog (const kpDocumentMetaInfo *docMetaInfo, + QWidget *parent); + virtual ~kpDocumentMetaInfoDialog (); + +public: + bool isNoOp () const; + + kpDocumentMetaInfo originalMetaInfo () const; + + // Returns the meta information gathered from all the UI. + // + // If there is any invalid data in the UI (e.g. duplicate text field + // keys), the returned meta information will still be valid but will not + // contain the complete contents of the UI (this description is + // deliberately vague so that you don't use the return value in such a + // situation). In this situation, if is set, a non-empty, + // translated error message will be returned through it. + // + // If all data in the UI is valid and is set, an empty + // string will be returned through it. + // + // If KDialog::exec() succeeded, all data in the UI was valid so the + // returned meta information will be complete and correct. + // + // This is a slow method as it recalculates the meta information each + // time it's called. + kpDocumentMetaInfo metaInfo (QString *errorMessage = 0) const; + +private: + void editCell (int r, int c); + void fieldsUpdateVerticalHeader (); + + void fieldsAddEmptyRow (int atRow); + void fieldsAppendEmptyRow (); + + bool isFieldsRowDeleteable (int row) const; + void fieldsDeleteRow (int r); + + void enableFieldsDeleteRowButtonIfShould (); + +private slots: + void setUIToOriginalMetaInfo (); + void slotFieldsCurrentCellChanged (int row, int col, int oldRow, int oldCol); + + // Allows the user to add a row without pressing any pushbuttons: + // Appends a new, blank row when text has been added to the last row. + void slotFieldsItemChanged (QTableWidgetItem *item); + + void slotFieldsAddRowButtonClicked (); + void slotFieldsDeleteRowButtonClicked (); + + virtual void accept (); + +private: + struct kpDocumentMetaInfoDialogPrivate * const d; +}; + + +#endif // kpDocumentMetaInfoDialog_H diff --git a/kolourpaint/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp b/kolourpaint/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp new file mode 100644 index 00000000..c3369265 --- /dev/null +++ b/kolourpaint/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp @@ -0,0 +1,459 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TRANSFORM_PREVIEW_DIALOG 0 + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + + +kpTransformPreviewDialog::kpTransformPreviewDialog (Features features, + bool reserveTopRow, + const QString &caption, + const QString &afterActionText, + bool actOnSelection, + kpTransformDialogEnvironment *_env, + QWidget *parent) + : KDialog (parent), + m_afterActionText (afterActionText), + m_actOnSelection (actOnSelection), + m_dimensionsGroupBox (0), + m_afterTransformDimensionsLabel (0), + m_previewGroupBox (0), + m_previewPixmapLabel (0), + m_gridLayout (0), + m_environ (_env) +{ + setCaption (caption); + setButtons (KDialog::Ok | KDialog::Cancel); + QWidget *baseWidget = new QWidget (this); + setMainWidget (baseWidget); + + + if (document ()) + { + m_oldWidth = document ()->width (actOnSelection); + m_oldHeight = document ()->height (actOnSelection); + } + else + { + m_oldWidth = m_oldHeight = 1; + } + + + if (features & Dimensions) + createDimensionsGroupBox (); + + if (features & Preview) + createPreviewGroupBox (); + + + m_gridLayout = new QGridLayout (baseWidget ); + m_gridLayout->setSpacing (spacingHint ()); + m_gridNumRows = reserveTopRow ? 1 : 0; + if (m_dimensionsGroupBox || m_previewGroupBox) + { + if (m_dimensionsGroupBox && m_previewGroupBox) + { + m_gridLayout->addWidget (m_dimensionsGroupBox, m_gridNumRows, 0); + m_gridLayout->addWidget (m_previewGroupBox, m_gridNumRows, 1); + + m_gridLayout->setColumnStretch (1, 1); + } + else if (m_dimensionsGroupBox) + { + m_gridLayout->addWidget (m_dimensionsGroupBox, + m_gridNumRows, 0, 1, 2); + } + else if (m_previewGroupBox) + { + m_gridLayout->addWidget (m_previewGroupBox, + m_gridNumRows, 0, 1, 2); + } + + m_gridLayout->setRowStretch (m_gridNumRows, 1); + m_gridNumRows++;; + } +} + +kpTransformPreviewDialog::~kpTransformPreviewDialog () +{ +} + + +// private +void kpTransformPreviewDialog::createDimensionsGroupBox () +{ + m_dimensionsGroupBox = new QGroupBox (i18n ("Dimensions"), mainWidget ()); + + QLabel *originalLabel = new QLabel (i18n ("Original:"), m_dimensionsGroupBox); + QString originalDimensions; + if (document ()) + { + originalDimensions = i18n ("%1 x %2", + m_oldWidth, + m_oldHeight); + + // Stop the Dimensions Group Box from resizing so often + const QString minimumLengthString ("100000 x 100000"); + const int padLength = minimumLengthString.length (); + for (int i = originalDimensions.length (); i < padLength; i++) + originalDimensions += ' '; + } + QLabel *originalDimensionsLabel = new QLabel (originalDimensions, m_dimensionsGroupBox); + + QLabel *afterTransformLabel = new QLabel (m_afterActionText, m_dimensionsGroupBox); + m_afterTransformDimensionsLabel = new QLabel (m_dimensionsGroupBox); + + + QGridLayout *dimensionsLayout = new QGridLayout (m_dimensionsGroupBox ); + dimensionsLayout->setMargin( marginHint () * 2 ); + dimensionsLayout->setSpacing( spacingHint ()); + + dimensionsLayout->addWidget (originalLabel, 0, 0, Qt::AlignBottom); + dimensionsLayout->addWidget (originalDimensionsLabel, 0, 1, Qt::AlignBottom); + dimensionsLayout->addWidget (afterTransformLabel, 1, 0, Qt::AlignTop); + dimensionsLayout->addWidget (m_afterTransformDimensionsLabel, 1, 1, Qt::AlignTop); +} + +// private +void kpTransformPreviewDialog::createPreviewGroupBox () +{ + m_previewGroupBox = new QGroupBox (i18n ("Preview"), mainWidget ()); + + m_previewPixmapLabel = new kpResizeSignallingLabel (m_previewGroupBox); + m_previewPixmapLabel->setMinimumSize (150, 110); + connect (m_previewPixmapLabel, SIGNAL (resized ()), + this, SLOT (updatePreview ())); + + QPushButton *updatePushButton = new QPushButton (i18n ("&Update"), + m_previewGroupBox); + connect (updatePushButton, SIGNAL (clicked ()), + this, SLOT (slotUpdateWithWaitCursor ())); + + + QVBoxLayout *previewLayout = new QVBoxLayout (m_previewGroupBox); + previewLayout->setMargin (marginHint () * 2); + previewLayout->setSpacing (qMax (1, spacingHint () / 2)); + + previewLayout->addWidget (m_previewPixmapLabel, 1/*stretch*/); + previewLayout->addWidget (updatePushButton, 0/*stretch*/, Qt::AlignHCenter); +} + + +// protected +kpDocument *kpTransformPreviewDialog::document () const +{ + return m_environ->document (); +} + + +// protected +void kpTransformPreviewDialog::addCustomWidgetToFront (QWidget *w) +{ + m_gridLayout->addWidget (w, 0, 0, 1, 2); +} + +// protected +void kpTransformPreviewDialog::addCustomWidget (QWidget *w) +{ + m_gridLayout->addWidget (w, m_gridNumRows, 0, 1, 2); + m_gridNumRows++; +} + + +// public override [base QWidget] +void kpTransformPreviewDialog::setUpdatesEnabled (bool enable) +{ + KDialog::setUpdatesEnabled (enable); + + if (enable) + slotUpdateWithWaitCursor (); +} + + +// private +void kpTransformPreviewDialog::updateDimensions () +{ + if (!m_dimensionsGroupBox) + return; + + kpDocument *doc = document (); + if (!doc) + return; + + if (!updatesEnabled ()) + { + #if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + kDebug () << "updates not enabled - aborting"; + #endif + return; + } + + QSize newDim = newDimensions (); +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + kDebug () << "kpTransformPreviewDialog::updateDimensions(): newDim=" << newDim; +#endif + + QString newDimString = i18n ("%1 x %2", + newDim.width (), + newDim.height ()); + m_afterTransformDimensionsLabel->setText (newDimString); +} + + +// public static +double kpTransformPreviewDialog::aspectScale (int newWidth, int newHeight, + int oldWidth, int oldHeight) +{ + double widthScale = double (newWidth) / double (oldWidth); + double heightScale = double (newHeight) / double (oldHeight); + + // Keeps aspect ratio + return qMin (widthScale, heightScale); +} + +// public static +int kpTransformPreviewDialog::scaleDimension (int dimension, double scale, int min, int max) +{ + return qMax (min, + qMin (max, + qRound (dimension * scale))); +} + + +// private +void kpTransformPreviewDialog::updateShrunkenDocumentPixmap () +{ +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + kDebug () << "kpTransformPreviewDialog::updateShrunkenDocumentPixmap()" + << " shrunkenDocPixmap.size=" + << m_shrunkenDocumentPixmap.size () + << " previewPixmapLabelSizeWhenUpdatedPixmap=" + << m_previewPixmapLabelSizeWhenUpdatedPixmap + << " previewPixmapLabel.size=" + << m_previewPixmapLabel->size () + << endl; +#endif + + if (!m_previewGroupBox) + return; + + + kpDocument *doc = document (); + Q_ASSERT (doc && !doc->image ().isNull ()); + + if (m_shrunkenDocumentPixmap.isNull () || + m_previewPixmapLabel->size () != m_previewPixmapLabelSizeWhenUpdatedPixmap) + { + #if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + kDebug () << "\tupdating shrunkenDocPixmap"; + #endif + + // TODO: Why the need to keep aspect ratio here? + // Isn't scaling the skewed result maintaining aspect enough? + double keepsAspectScale = aspectScale (m_previewPixmapLabel->width (), + m_previewPixmapLabel->height (), + m_oldWidth, + m_oldHeight); + + kpImage image; + + if (m_actOnSelection) + { + kpAbstractImageSelection *sel = doc->imageSelection ()->clone (); + if (!sel->hasContent ()) + sel->setBaseImage (doc->getSelectedBaseImage ()); + + image = sel->transparentImage (); + delete sel; + } + else + { + image = doc->image (); + } + + m_shrunkenDocumentPixmap = kpPixmapFX::scale ( + image, + scaleDimension (m_oldWidth, + keepsAspectScale, + 1, m_previewPixmapLabel->width ()), + scaleDimension (m_oldHeight, + keepsAspectScale, + 1, m_previewPixmapLabel->height ())); + #if 0 + m_shrunkenDocumentPixmap = kpPixmapFX::scale ( + m_actOnSelection ? doc->getSelectedPixmap () : *doc->pixmap (), + m_previewPixmapLabel->width (), + m_previewPixmapLabel->height ()); + #endif + + m_previewPixmapLabelSizeWhenUpdatedPixmap = m_previewPixmapLabel->size (); + } +} + + +// private +void kpTransformPreviewDialog::updatePreview () +{ +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + kDebug () << "kpTransformPreviewDialog::updatePreview()"; +#endif + + if (!m_previewGroupBox) + return; + + + kpDocument *doc = document (); + if (!doc) + return; + + + if (!updatesEnabled ()) + { + #if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + kDebug () << "updates not enabled - aborting"; + #endif + return; + } + + + updateShrunkenDocumentPixmap (); + + if (!m_shrunkenDocumentPixmap.isNull ()) + { + QSize newDim = newDimensions (); + double keepsAspectScale = aspectScale (m_previewPixmapLabel->width (), + m_previewPixmapLabel->height (), + newDim.width (), + newDim.height ()); + + int targetWidth = scaleDimension (newDim.width (), + keepsAspectScale, + 1, // min + m_previewPixmapLabel->width ()); // max + int targetHeight = scaleDimension (newDim.height (), + keepsAspectScale, + 1, // min + m_previewPixmapLabel->height ()); // max + + // TODO: Some effects work directly on QImage; so could cache the + // QImage so that transformPixmap() is faster + QImage transformedShrunkenDocumentPixmap = + transformPixmap (m_shrunkenDocumentPixmap, targetWidth, targetHeight); + + QImage previewPixmap (m_previewPixmapLabel->width (), + m_previewPixmapLabel->height (), QImage::Format_ARGB32_Premultiplied); + previewPixmap.fill(QColor(Qt::transparent).rgba()); + kpPixmapFX::setPixmapAt (&previewPixmap, + (previewPixmap.width () - transformedShrunkenDocumentPixmap.width ()) / 2, + (previewPixmap.height () - transformedShrunkenDocumentPixmap.height ()) / 2, + transformedShrunkenDocumentPixmap); + +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + kDebug () << "kpTransformPreviewDialog::updatePreview ():" + << " shrunkenDocumentPixmap: w=" + << m_shrunkenDocumentPixmap.width () + << " h=" + << m_shrunkenDocumentPixmap.height () + << " previewPixmapLabel: w=" + << m_previewPixmapLabel->width () + << " h=" + << m_previewPixmapLabel->height () + << " transformedShrunkenDocumentPixmap: w=" + << transformedShrunkenDocumentPixmap.width () + << " h=" + << transformedShrunkenDocumentPixmap.height () + << " previewPixmap: w=" + << previewPixmap.width () + << " h=" + << previewPixmap.height () + << endl; +#endif + + m_previewPixmapLabel->setPixmap (QPixmap::fromImage(previewPixmap)); + + // immediate update esp. for expensive previews + m_previewPixmapLabel->repaint (); + +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + kDebug () << "\tafter QLabel::setPixmap() previewPixmapLabel: w=" + << m_previewPixmapLabel->width () + << " h=" + << m_previewPixmapLabel->height () + << endl; +#endif + } +} + + +// protected slot virtual +void kpTransformPreviewDialog::slotUpdate () +{ +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + kDebug () << "kpTransformPreviewDialog::slotUpdate()"; +#endif + updateDimensions (); + updatePreview (); +} + +// protected slot virtual +void kpTransformPreviewDialog::slotUpdateWithWaitCursor () +{ +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + kDebug () << "kpTransformPreviewDialog::slotUpdateWithWaitCursor()"; +#endif + + QApplication::setOverrideCursor (Qt::WaitCursor); + + slotUpdate (); + + QApplication::restoreOverrideCursor (); +} + + +#include diff --git a/kolourpaint/dialogs/imagelib/transforms/kpTransformPreviewDialog.h b/kolourpaint/dialogs/imagelib/transforms/kpTransformPreviewDialog.h new file mode 100644 index 00000000..cc6e3fe7 --- /dev/null +++ b/kolourpaint/dialogs/imagelib/transforms/kpTransformPreviewDialog.h @@ -0,0 +1,144 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformPreviewDialog_H +#define kpTransformPreviewDialog_H + + +#include + +#include + + +class QLabel; +class QGridLayout; +class QGroupBox; + +class kpColor; +class kpDocument; +class kpResizeSignallingLabel; +class kpTransformDialogEnvironment; + + +class kpTransformPreviewDialog : public KDialog +{ +Q_OBJECT + +public: + enum Features + { + Dimensions = 1, Preview = 2, + AllFeatures = Dimensions | Preview + }; + + // You must call slotUpdate() in your constructor + kpTransformPreviewDialog (Features features, + bool reserveTopRow, + // e.g. "Skew (Image|Selection)" + const QString &caption, + // (in the Dimensions Group Box) e.g. "After Skew:" + const QString &afterActionText, + bool actOnSelection, + kpTransformDialogEnvironment *_env, + QWidget *parent); + virtual ~kpTransformPreviewDialog (); + +private: + void createDimensionsGroupBox (); + void createPreviewGroupBox (); + +public: + virtual bool isNoOp () const = 0; + +protected: + kpDocument *document () const; + + // All widgets must have mainWidget() as their parent + void addCustomWidgetToFront (QWidget *w); // see in ctor + void addCustomWidget (QWidget *w); + void addCustomWidgetToBack (QWidget *w) + { + addCustomWidget (w); + } + + virtual QSize newDimensions () const = 0; + virtual QImage transformPixmap (const QImage &pixmap, + int targetWidth, int targetHeight) const = 0; + +public: + // Use to avoid excessive, expensive preview pixmap label recalcuations, + // during init and widget relayouts. + // + // Setting to true automatically calls slotUpdateWithWaitCursor(). + // + // WARNING: This overrides a non-virtual method in QWidget. + void setUpdatesEnabled (bool enable); + +private: + void updateDimensions (); + +public: + static double aspectScale (int newWidth, int newHeight, + int oldWidth, int oldHeight); + static int scaleDimension (int dimension, double scale, int min, int max); + +private: + void updateShrunkenDocumentPixmap (); + +protected slots: + void updatePreview (); + + // Call this whenever a value (e.g. an angle) changes + // and the Dimensions & Preview need to be updated + virtual void slotUpdate (); + + virtual void slotUpdateWithWaitCursor (); + +protected: + // REFACTOR: Use d-ptr + QString m_afterActionText; + bool m_actOnSelection; + + int m_oldWidth, m_oldHeight; + + QGroupBox *m_dimensionsGroupBox; + QLabel *m_afterTransformDimensionsLabel; + + QGroupBox *m_previewGroupBox; + kpResizeSignallingLabel *m_previewPixmapLabel; + QSize m_previewPixmapLabelSizeWhenUpdatedPixmap; + QImage m_shrunkenDocumentPixmap; + + QGridLayout *m_gridLayout; + int m_gridNumRows; + + kpTransformDialogEnvironment *m_environ; +}; + + +#endif // kpTransformPreviewDialog_H diff --git a/kolourpaint/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp b/kolourpaint/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp new file mode 100644 index 00000000..19e29f36 --- /dev/null +++ b/kolourpaint/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp @@ -0,0 +1,817 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG 0 + + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +#define kpSettingResizeScaleLastKeepAspect "Resize Scale - Last Keep Aspect" +#define kpSettingResizeScaleScaleType "Resize Scale - ScaleType" + +//--------------------------------------------------------------------- + +#define SET_VALUE_WITHOUT_SIGNAL_EMISSION(knuminput_instance,value) \ +{ \ + knuminput_instance->blockSignals (true); \ + knuminput_instance->setValue (value); \ + knuminput_instance->blockSignals (false); \ +} + +#define IGNORE_KEEP_ASPECT_RATIO(cmd) \ +{ \ + m_ignoreKeepAspectRatio++; \ + cmd; \ + m_ignoreKeepAspectRatio--; \ +} + +//--------------------------------------------------------------------- + +kpTransformResizeScaleDialog::kpTransformResizeScaleDialog ( + kpTransformDialogEnvironment *_env, QWidget *parent) + : KDialog (parent), + m_environ (_env), + m_ignoreKeepAspectRatio (0), + m_lastType(kpTransformResizeScaleCommand::Resize) +{ + setCaption( i18nc ("@title:window", "Resize / Scale") ); + setButtons( KDialog::Ok | KDialog::Cancel); + + QWidget *baseWidget = new QWidget (this); + setMainWidget (baseWidget); + + KHBox *actOnBox = createActOnBox(baseWidget); + QGroupBox *operationGroupBox = createOperationGroupBox(baseWidget); + QGroupBox *dimensionsGroupBox = createDimensionsGroupBox(baseWidget); + + QVBoxLayout *baseLayout = new QVBoxLayout (baseWidget); + baseLayout->setSpacing (spacingHint ()); + baseLayout->setMargin(0/*margin*/); + baseLayout->addWidget(actOnBox); + baseLayout->addWidget(operationGroupBox); + baseLayout->addWidget(dimensionsGroupBox); + + KConfigGroup cfg(KGlobal::config(), kpSettingsGroupGeneral); + setKeepAspectRatio(cfg.readEntry(kpSettingResizeScaleLastKeepAspect, false)); + m_lastType = static_cast + (cfg.readEntry(kpSettingResizeScaleScaleType, + static_cast(kpTransformResizeScaleCommand::Resize))); + + slotActOnChanged (); + + m_newWidthInput->setEditFocus (); + + //enableButtonOk (!isNoOp ()); +} + +//--------------------------------------------------------------------- +// private + +kpDocument *kpTransformResizeScaleDialog::document () const +{ + return m_environ->document (); +} + +//--------------------------------------------------------------------- +// private + +kpAbstractSelection *kpTransformResizeScaleDialog::selection () const +{ + Q_ASSERT (document ()); + return document ()->selection (); +} + +//--------------------------------------------------------------------- +// private + +kpTextSelection *kpTransformResizeScaleDialog::textSelection () const +{ + Q_ASSERT (document ()); + return document ()->textSelection (); +} + +//--------------------------------------------------------------------- +// private + +KHBox *kpTransformResizeScaleDialog::createActOnBox(QWidget *baseWidget) +{ + KHBox *actOnBox = new KHBox (baseWidget); + actOnBox->setSpacing (spacingHint () * 2); + + + QLabel *actOnLabel = new QLabel (i18n ("Ac&t on:"), actOnBox); + m_actOnCombo = new KComboBox (actOnBox); + + + actOnLabel->setBuddy (m_actOnCombo); + + m_actOnCombo->insertItem (Image, i18n ("Entire Image")); + if (selection ()) + { + QString selName = i18n ("Selection"); + + if (textSelection ()) + selName = i18n ("Text Box"); + + m_actOnCombo->insertItem (Selection, selName); + m_actOnCombo->setCurrentIndex (Selection); + } + else + { + actOnLabel->setEnabled (false); + m_actOnCombo->setEnabled (false); + } + + + actOnBox->setStretchFactor (m_actOnCombo, 1); + + + connect (m_actOnCombo, SIGNAL (activated (int)), + this, SLOT (slotActOnChanged ())); + + return actOnBox; +} + +//--------------------------------------------------------------------- + +static void toolButtonSetLook (QToolButton *button, + const QString &iconName, + const QString &name) +{ + QPixmap icon = UserIcon (iconName); + button->setIconSize (QSize (icon.width (), icon.height ())); + button->setIcon (icon); + + button->setToolButtonStyle (Qt::ToolButtonTextUnderIcon); + button->setText (name); + button->setFocusPolicy (Qt::StrongFocus); + button->setCheckable (true); +} + +//--------------------------------------------------------------------- +// private + +QGroupBox *kpTransformResizeScaleDialog::createOperationGroupBox (QWidget *baseWidget) +{ + QGroupBox *operationGroupBox = new QGroupBox (i18n ("Operation"), baseWidget); + operationGroupBox->setWhatsThis( + i18n ("" + "
    " + "
  • Resize: The size of the picture will be" + " increased" + " by creating new areas to the right and/or bottom" + " (filled in with the background color) or" + " decreased by cutting" + " it at the right and/or bottom.
  • " + + "
  • Scale: The picture will be expanded" + " by duplicating pixels or squashed by dropping pixels.
  • " + + "
  • Smooth Scale: This is the same as" + " Scale except that it blends neighboring" + " pixels to produce a smoother looking picture.
  • " + "
" + "
")); + + m_resizeButton = new QToolButton (operationGroupBox); + toolButtonSetLook (m_resizeButton, + QLatin1String ("resize"), + i18n ("&Resize")); + + m_scaleButton = new QToolButton (operationGroupBox); + toolButtonSetLook (m_scaleButton, + QLatin1String ("scale"), + i18n ("&Scale")); + + m_smoothScaleButton = new QToolButton (operationGroupBox); + toolButtonSetLook (m_smoothScaleButton, + QLatin1String ("smooth_scale"), + i18n ("S&mooth Scale")); + + QButtonGroup *resizeScaleButtonGroup = new QButtonGroup (baseWidget); + resizeScaleButtonGroup->addButton (m_resizeButton); + resizeScaleButtonGroup->addButton (m_scaleButton); + resizeScaleButtonGroup->addButton (m_smoothScaleButton); + + + QGridLayout *operationLayout = new QGridLayout (operationGroupBox ); + operationLayout->setMargin (marginHint () * 2/*don't overlap groupbox title*/); + operationLayout->setSpacing (spacingHint ()); + + operationLayout->addWidget (m_resizeButton, 0, 0, Qt::AlignCenter); + operationLayout->addWidget (m_scaleButton, 0, 1, Qt::AlignCenter); + operationLayout->addWidget (m_smoothScaleButton, 0, 2, Qt::AlignCenter); + + connect (m_resizeButton, SIGNAL (toggled (bool)), + this, SLOT (slotTypeChanged ())); + connect (m_scaleButton, SIGNAL (toggled (bool)), + this, SLOT (slotTypeChanged ())); + connect (m_smoothScaleButton, SIGNAL (toggled (bool)), + this, SLOT (slotTypeChanged ())); + + return operationGroupBox; +} + +//--------------------------------------------------------------------- +// private + +QGroupBox *kpTransformResizeScaleDialog::createDimensionsGroupBox(QWidget *baseWidget) +{ + QGroupBox *dimensionsGroupBox = new QGroupBox (i18n ("Dimensions"), baseWidget); + + QLabel *widthLabel = new QLabel (i18n ("Width:"), dimensionsGroupBox); + widthLabel->setAlignment (widthLabel->alignment () | Qt::AlignHCenter); + QLabel *heightLabel = new QLabel (i18n ("Height:"), dimensionsGroupBox); + heightLabel->setAlignment (heightLabel->alignment () | Qt::AlignHCenter); + + QLabel *originalLabel = new QLabel (i18n ("Original:"), dimensionsGroupBox); + m_originalWidthInput = new KIntNumInput ( + document ()->width ((bool) selection ()), + dimensionsGroupBox); + QLabel *xLabel0 = new QLabel (i18n ("x"), dimensionsGroupBox); + m_originalHeightInput = new KIntNumInput ( + document ()->height ((bool) selection ()), + dimensionsGroupBox); + + QLabel *newLabel = new QLabel (i18n ("&New:"), dimensionsGroupBox); + m_newWidthInput = new KIntNumInput (dimensionsGroupBox); + QLabel *xLabel1 = new QLabel (i18n ("x"), dimensionsGroupBox); + m_newHeightInput = new KIntNumInput (dimensionsGroupBox); + + QLabel *percentLabel = new QLabel (i18n ("&Percent:"), dimensionsGroupBox); + m_percentWidthInput = new KDoubleNumInput (0.01/*lower*/, 1000000/*upper*/, + 100/*value*/, + dimensionsGroupBox, + 1/*step*/, + 2/*precision*/); + m_percentWidthInput->setSuffix (i18n ("%")); + QLabel *xLabel2 = new QLabel (i18n ("x"), dimensionsGroupBox); + m_percentHeightInput = new KDoubleNumInput (0.01/*lower*/, 1000000/*upper*/, + 100/*value*/, + dimensionsGroupBox, + 1/*step*/, + 2/*precision*/); + m_percentHeightInput->setSuffix (i18n ("%")); + + m_keepAspectRatioCheckBox = new QCheckBox (i18n ("Keep &aspect ratio"), + dimensionsGroupBox); + + + m_originalWidthInput->setEnabled (false); + m_originalHeightInput->setEnabled (false); + originalLabel->setBuddy (m_originalWidthInput); + newLabel->setBuddy (m_newWidthInput); + m_percentWidthInput->setValue (100); + m_percentHeightInput->setValue (100); + percentLabel->setBuddy (m_percentWidthInput); + + + QGridLayout *dimensionsLayout = new QGridLayout (dimensionsGroupBox); + dimensionsLayout->setMargin (marginHint () * 2); + dimensionsLayout->setSpacing (spacingHint ()); + dimensionsLayout->setColumnStretch (1/*column*/, 1); + dimensionsLayout->setColumnStretch (3/*column*/, 1); + + + dimensionsLayout->addWidget (widthLabel, 0, 1); + dimensionsLayout->addWidget (heightLabel, 0, 3); + + dimensionsLayout->addWidget (originalLabel, 1, 0); + dimensionsLayout->addWidget (m_originalWidthInput, 1, 1); + dimensionsLayout->addWidget (xLabel0, 1, 2); + dimensionsLayout->addWidget (m_originalHeightInput, 1, 3); + + dimensionsLayout->addWidget (newLabel, 2, 0); + dimensionsLayout->addWidget (m_newWidthInput, 2, 1); + dimensionsLayout->addWidget (xLabel1, 2, 2); + dimensionsLayout->addWidget (m_newHeightInput, 2, 3); + + dimensionsLayout->addWidget (percentLabel, 3, 0); + dimensionsLayout->addWidget (m_percentWidthInput, 3, 1); + dimensionsLayout->addWidget (xLabel2, 3, 2); + dimensionsLayout->addWidget (m_percentHeightInput, 3, 3); + + dimensionsLayout->addWidget (m_keepAspectRatioCheckBox, 4, 0, 1, 4); + dimensionsLayout->setRowStretch (4/*row*/, 1); + dimensionsLayout->setRowMinimumHeight (4/*row*/, dimensionsLayout->rowMinimumHeight (4) * 2); + + + connect (m_newWidthInput, SIGNAL (valueChanged (int)), + this, SLOT (slotWidthChanged (int))); + connect (m_newHeightInput, SIGNAL (valueChanged (int)), + this, SLOT (slotHeightChanged (int))); + + // COMPAT: KDoubleNumInput only fires valueChanged(double) once per + // edit. It should either fire: + // + // 1. At the end of the edit (triggered by clicking or tabbing + // away), like with KDE 3. + // + // OR + // + // 2. Once per keystroke. + // + // Bug in KDoubleNumInput. + connect (m_percentWidthInput, SIGNAL (valueChanged (double)), + this, SLOT (slotPercentWidthChanged (double))); + connect (m_percentHeightInput, SIGNAL (valueChanged (double)), + this, SLOT (slotPercentHeightChanged (double))); + + connect (m_keepAspectRatioCheckBox, SIGNAL (toggled (bool)), + this, SLOT (setKeepAspectRatio (bool))); + + return dimensionsGroupBox; +} + +//--------------------------------------------------------------------- +// private + +void kpTransformResizeScaleDialog::widthFitHeightToAspectRatio () +{ + if (m_keepAspectRatioCheckBox->isChecked () && !m_ignoreKeepAspectRatio) + { + // width / height = oldWidth / oldHeight + // height = width * oldHeight / oldWidth + const int newHeight = qRound (double (imageWidth ()) * double (originalHeight ()) + / double (originalWidth ())); + IGNORE_KEEP_ASPECT_RATIO (m_newHeightInput->setValue (newHeight)); + } +} + +//--------------------------------------------------------------------- +// private + +void kpTransformResizeScaleDialog::heightFitWidthToAspectRatio () +{ + if (m_keepAspectRatioCheckBox->isChecked () && !m_ignoreKeepAspectRatio) + { + // width / height = oldWidth / oldHeight + // width = height * oldWidth / oldHeight + const int newWidth = qRound (double (imageHeight ()) * double (originalWidth ()) + / double (originalHeight ())); + IGNORE_KEEP_ASPECT_RATIO (m_newWidthInput->setValue (newWidth)); + } +} + +//--------------------------------------------------------------------- +// private + +bool kpTransformResizeScaleDialog::resizeEnabled () const +{ + return (!actOnSelection () || + (actOnSelection () && textSelection ())); +} + +//--------------------------------------------------------------------- +// private + +bool kpTransformResizeScaleDialog::scaleEnabled () const +{ + return (!(actOnSelection () && textSelection ())); +} + +//--------------------------------------------------------------------- +// private + +bool kpTransformResizeScaleDialog::smoothScaleEnabled () const +{ + return scaleEnabled (); +} + +//--------------------------------------------------------------------- +// public slot + +void kpTransformResizeScaleDialog::slotActOnChanged () +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + kDebug () << "kpTransformResizeScaleDialog::slotActOnChanged()"; +#endif + + m_resizeButton->setEnabled (resizeEnabled ()); + m_scaleButton->setEnabled (scaleEnabled ()); + m_smoothScaleButton->setEnabled (smoothScaleEnabled ()); + + // TODO: somehow share logic with (resize|*scale)Enabled() + if (actOnSelection ()) + { + if (textSelection ()) + { + m_resizeButton->setChecked (true); + } + else + { + if (m_lastType == kpTransformResizeScaleCommand::Scale) + m_scaleButton->setChecked (true); + else + m_smoothScaleButton->setChecked (true); + } + } + else + { + if (m_lastType == kpTransformResizeScaleCommand::Resize) + m_resizeButton->setChecked (true); + else if (m_lastType == kpTransformResizeScaleCommand::Scale) + m_scaleButton->setChecked (true); + else + m_smoothScaleButton->setChecked (true); + } + + + m_originalWidthInput->setValue (originalWidth ()); + m_originalHeightInput->setValue (originalHeight ()); + + + m_newWidthInput->blockSignals (true); + m_newHeightInput->blockSignals (true); + + m_newWidthInput->setMinimum (actOnSelection () ? + selection ()->minimumWidth () : + 1); + m_newHeightInput->setMinimum (actOnSelection () ? + selection ()->minimumHeight () : + 1); + + m_newWidthInput->blockSignals (false); + m_newHeightInput->blockSignals (false); + + + IGNORE_KEEP_ASPECT_RATIO (slotPercentWidthChanged (m_percentWidthInput->value ())); + IGNORE_KEEP_ASPECT_RATIO (slotPercentHeightChanged (m_percentHeightInput->value ())); + + setKeepAspectRatio (m_keepAspectRatioCheckBox->isChecked ()); +} + +//--------------------------------------------------------------------- +// public slot + +void kpTransformResizeScaleDialog::slotTypeChanged () +{ + m_lastType = type (); +} + +//--------------------------------------------------------------------- + +// public slot +void kpTransformResizeScaleDialog::slotWidthChanged (int width) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + kDebug () << "kpTransformResizeScaleDialog::slotWidthChanged(" + << width << ")" << endl; +#endif + const double newPercentWidth = double (width) * 100 / double (originalWidth ()); + + SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_percentWidthInput, newPercentWidth); + + widthFitHeightToAspectRatio (); + + //enableButtonOk (!isNoOp ()); +} + +//--------------------------------------------------------------------- +// public slot + +void kpTransformResizeScaleDialog::slotHeightChanged (int height) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + kDebug () << "kpTransformResizeScaleDialog::slotHeightChanged(" + << height << ")" << endl; +#endif + const double newPercentHeight = double (height) * 100 / double (originalHeight ()); + + SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_percentHeightInput, newPercentHeight); + + heightFitWidthToAspectRatio (); + + //enableButtonOk (!isNoOp ()); +} + +//--------------------------------------------------------------------- +// public slot + +void kpTransformResizeScaleDialog::slotPercentWidthChanged (double percentWidth) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + kDebug () << "kpTransformResizeScaleDialog::slotPercentWidthChanged(" + << percentWidth << ")" << endl; +#endif + + SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_newWidthInput, + qRound (percentWidth * originalWidth () / 100.0)); + + widthFitHeightToAspectRatio (); + + //enableButtonOk (!isNoOp ()); +} + +//--------------------------------------------------------------------- +// public slot + +void kpTransformResizeScaleDialog::slotPercentHeightChanged (double percentHeight) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + kDebug () << "kpTransformResizeScaleDialog::slotPercentHeightChanged(" + << percentHeight << ")" << endl; +#endif + + SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_newHeightInput, + qRound (percentHeight * originalHeight () / 100.0)); + + heightFitWidthToAspectRatio (); + + //enableButtonOk (!isNoOp ()); +} + +//--------------------------------------------------------------------- +// public slot + +void kpTransformResizeScaleDialog::setKeepAspectRatio (bool on) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + kDebug () << "kpTransformResizeScaleDialog::setKeepAspectRatio(" + << on << ")" << endl; +#endif + if (on != m_keepAspectRatioCheckBox->isChecked ()) + m_keepAspectRatioCheckBox->setChecked (on); + + if (on) + widthFitHeightToAspectRatio (); +} + +//--------------------------------------------------------------------- + +#undef IGNORE_KEEP_ASPECT_RATIO +#undef SET_VALUE_WITHOUT_SIGNAL_EMISSION + + +//--------------------------------------------------------------------- +// private + +int kpTransformResizeScaleDialog::originalWidth () const +{ + return document ()->width (actOnSelection ()); +} + +//--------------------------------------------------------------------- +// private + +int kpTransformResizeScaleDialog::originalHeight () const +{ + return document ()->height (actOnSelection ()); +} + +//--------------------------------------------------------------------- +// public + +int kpTransformResizeScaleDialog::imageWidth () const +{ + return m_newWidthInput->value (); +} + +//--------------------------------------------------------------------- +// public + +int kpTransformResizeScaleDialog::imageHeight () const +{ + return m_newHeightInput->value (); +} + +//--------------------------------------------------------------------- +// public + +bool kpTransformResizeScaleDialog::actOnSelection () const +{ + return (m_actOnCombo->currentIndex () == Selection); +} + +//--------------------------------------------------------------------- +// public + +kpTransformResizeScaleCommand::Type kpTransformResizeScaleDialog::type () const +{ + if (m_resizeButton->isChecked ()) + return kpTransformResizeScaleCommand::Resize; + else if (m_scaleButton->isChecked ()) + return kpTransformResizeScaleCommand::Scale; + else + return kpTransformResizeScaleCommand::SmoothScale; +} + +//--------------------------------------------------------------------- +// public + +bool kpTransformResizeScaleDialog::isNoOp () const +{ + return (imageWidth () == originalWidth () && + imageHeight () == originalHeight ()); +} + +//--------------------------------------------------------------------- +// private slot virtual [base QDialog] + +void kpTransformResizeScaleDialog::accept () +{ + enum { eText, eSelection, eImage } actionTarget = eText; + + if (actOnSelection ()) + { + if (textSelection ()) + { + actionTarget = eText; + } + else + { + actionTarget = eSelection; + } + } + else + { + actionTarget = eImage; + } + + + KLocalizedString message; + QString caption, continueButtonText; + + // Note: If eText, can't Scale nor SmoothScale. + // If eSelection, can't Resize. + + switch (type ()) + { + default: + case kpTransformResizeScaleCommand::Resize: + if (actionTarget == eText) + { + message = + ki18n ("

Resizing the text box to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to resize the text box?

"); + + caption = i18nc ("@title:window", "Resize Text Box?"); + continueButtonText = i18n ("R&esize Text Box"); + } + else if (actionTarget == eImage) + { + message = + ki18n ("

Resizing the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to resize the image?

"); + + caption = i18nc ("@title:window", "Resize Image?"); + continueButtonText = i18n ("R&esize Image"); + } + + break; + + case kpTransformResizeScaleCommand::Scale: + if (actionTarget == eImage) + { + message = + ki18n ("

Scaling the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to scale the image?

"); + + caption = i18nc ("@title:window", "Scale Image?"); + continueButtonText = i18n ("Scal&e Image"); + } + else if (actionTarget == eSelection) + { + message = + ki18n ("

Scaling the selection to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to scale the selection?

"); + + caption = i18nc ("@title:window", "Scale Selection?"); + continueButtonText = i18n ("Scal&e Selection"); + } + + break; + + case kpTransformResizeScaleCommand::SmoothScale: + if (actionTarget == eImage) + { + message = + ki18n ("

Smooth Scaling the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to smooth scale the image?

"); + + caption = i18nc ("@title:window", "Smooth Scale Image?"); + continueButtonText = i18n ("Smooth Scal&e Image"); + } + else if (actionTarget == eSelection) + { + message = + ki18n ("

Smooth Scaling the selection to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to smooth scale the selection?

"); + + caption = i18nc ("@title:window", "Smooth Scale Selection?"); + continueButtonText = i18n ("Smooth Scal&e Selection"); + } + + break; + } + + + if (kpTool::warnIfBigImageSize (originalWidth (), + originalHeight (), + imageWidth (), imageHeight (), + message.subs (imageWidth ()).subs (imageHeight ()).toString (), + caption, + continueButtonText, + this)) + { + KDialog::accept (); + } + + // store settings + KConfigGroup cfg(KGlobal::config(), kpSettingsGroupGeneral); + + cfg.writeEntry(kpSettingResizeScaleLastKeepAspect, m_keepAspectRatioCheckBox->isChecked()); + cfg.writeEntry(kpSettingResizeScaleScaleType, static_cast(m_lastType)); + cfg.sync(); +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.h b/kolourpaint/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.h new file mode 100644 index 00000000..baeb448d --- /dev/null +++ b/kolourpaint/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.h @@ -0,0 +1,124 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformResizeScaleDialog_H +#define kpTransformResizeScaleDialog_H + +#include + +#include +#include + + +class QCheckBox; +class QGroupBox; +class QToolButton; + +class KComboBox; +class KDoubleNumInput; +class KHBox; +class KIntNumInput; + +class kpAbstractSelection; +class kpDocument; +class kpTextSelection; +class kpTransformDialogEnvironment; + + +class kpTransformResizeScaleDialog : public KDialog +{ +Q_OBJECT + + public: + kpTransformResizeScaleDialog(kpTransformDialogEnvironment *_env, QWidget *parent); + + enum ActOn + { + Image, Selection + }; + + int imageWidth () const; + int imageHeight () const; + bool actOnSelection () const; + kpTransformResizeScaleCommand::Type type () const; + + bool isNoOp () const; + + public slots: + void slotActOnChanged (); + void slotTypeChanged (); + + void slotWidthChanged (int width); + void slotHeightChanged (int height); + + void slotPercentWidthChanged (double percentWidth); + void slotPercentHeightChanged (double percentHeight); + + private: + kpDocument *document () const; + kpAbstractSelection *selection () const; + kpTextSelection *textSelection () const; + + KHBox *createActOnBox(QWidget *baseWidget); + QGroupBox *createOperationGroupBox(QWidget *baseWidget); + QGroupBox *createDimensionsGroupBox(QWidget *baseWidget); + + void widthFitHeightToAspectRatio (); + void heightFitWidthToAspectRatio (); + + bool resizeEnabled () const; + bool scaleEnabled () const; + bool smoothScaleEnabled () const; + int originalWidth () const; + int originalHeight () const; + + private slots: + virtual void accept(); + void setKeepAspectRatio(bool on); + + private: + kpTransformDialogEnvironment *m_environ; + + KComboBox *m_actOnCombo; + + QToolButton *m_resizeButton, + *m_scaleButton, + *m_smoothScaleButton; + + KIntNumInput *m_originalWidthInput, *m_originalHeightInput, + *m_newWidthInput, *m_newHeightInput; + KDoubleNumInput *m_percentWidthInput, *m_percentHeightInput; + QCheckBox *m_keepAspectRatioCheckBox; + + int m_ignoreKeepAspectRatio; + + kpTransformResizeScaleCommand::Type m_lastType; +}; + + +#endif // kpTransformResizeScaleDialog_H diff --git a/kolourpaint/dialogs/imagelib/transforms/kpTransformRotateDialog.cpp b/kolourpaint/dialogs/imagelib/transforms/kpTransformRotateDialog.cpp new file mode 100644 index 00000000..bb5d52b8 --- /dev/null +++ b/kolourpaint/dialogs/imagelib/transforms/kpTransformRotateDialog.cpp @@ -0,0 +1,315 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_ROTATE 0 + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +// private static +int kpTransformRotateDialog::s_lastWidth = -1, + kpTransformRotateDialog::s_lastHeight = -1; + +// private static +bool kpTransformRotateDialog::s_lastIsClockwise = true; +int kpTransformRotateDialog::s_lastAngleCustom = 0; + + +kpTransformRotateDialog::kpTransformRotateDialog (bool actOnSelection, + kpTransformDialogEnvironment *_env, QWidget *parent) + : kpTransformPreviewDialog (kpTransformPreviewDialog::AllFeatures, + false/*don't reserve top row*/, + actOnSelection ? i18nc ("@title:window", "Rotate Selection") : i18nc ("@title:window", "Rotate Image"), + i18n ("After rotate:"), + actOnSelection, + _env, parent) +{ + s_lastAngleCustom = 0; + + + createDirectionGroupBox (); + createAngleGroupBox (); + + + if (s_lastWidth > 0 && s_lastHeight > 0) + resize (s_lastWidth, s_lastHeight); + + + slotAngleCustomRadioButtonToggled (m_angleCustomRadioButton->isChecked ()); + slotUpdate (); +} + +kpTransformRotateDialog::~kpTransformRotateDialog () +{ + s_lastWidth = width (), s_lastHeight = height (); +} + + +// private +void kpTransformRotateDialog::createDirectionGroupBox () +{ + QGroupBox *directionGroupBox = new QGroupBox (i18n ("Direction"), mainWidget ()); + addCustomWidget (directionGroupBox); + + + QLabel *antiClockwisePixmapLabel = new QLabel (directionGroupBox); + antiClockwisePixmapLabel->setPixmap (UserIcon ("image_rotate_anticlockwise")); + + QLabel *clockwisePixmapLabel = new QLabel (directionGroupBox); + clockwisePixmapLabel->setPixmap (UserIcon ("image_rotate_clockwise")); + + + m_antiClockwiseRadioButton = new QRadioButton (i18n ("Cou&nterclockwise"), directionGroupBox); + m_clockwiseRadioButton = new QRadioButton (i18n ("C&lockwise"), directionGroupBox); + + + m_antiClockwiseRadioButton->setChecked (!s_lastIsClockwise); + m_clockwiseRadioButton->setChecked (s_lastIsClockwise); + + + QGridLayout *directionLayout = new QGridLayout (directionGroupBox ); + directionLayout->setSpacing( spacingHint() ); + directionLayout->setMargin( marginHint () * 2 ); + directionLayout->addWidget (antiClockwisePixmapLabel, 0, 0, Qt::AlignCenter); + directionLayout->addWidget (clockwisePixmapLabel, 0, 1, Qt::AlignCenter); + directionLayout->addWidget (m_antiClockwiseRadioButton, 1, 0, Qt::AlignCenter); + directionLayout->addWidget (m_clockwiseRadioButton, 1, 1, Qt::AlignCenter); + + + connect (m_antiClockwiseRadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotUpdate ())); + connect (m_clockwiseRadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotUpdate ())); +} + +// private +void kpTransformRotateDialog::createAngleGroupBox () +{ + QGroupBox *angleGroupBox = new QGroupBox (i18n ("Angle"), mainWidget ()); + addCustomWidget (angleGroupBox); + + + m_angle90RadioButton = new QRadioButton (i18n ("90 °rees"), angleGroupBox); + m_angle180RadioButton = new QRadioButton (i18n ("180 d&egrees"), angleGroupBox); + m_angle270RadioButton = new QRadioButton (i18n ("270 de&grees"), angleGroupBox); + + m_angleCustomRadioButton = new QRadioButton (i18n ("C&ustom:"), angleGroupBox); + m_angleCustomInput = new KIntNumInput (s_lastAngleCustom, angleGroupBox); + m_angleCustomInput->setMinimum (-359); + m_angleCustomInput->setMaximum (+359); + QLabel *degreesLabel = new QLabel (i18n ("degrees"), angleGroupBox); + + + m_angleCustomRadioButton->setChecked (true); + + + QGridLayout *angleLayout = new QGridLayout (angleGroupBox ); + angleLayout->setMargin( marginHint () * 2 ); + angleLayout->setSpacing( spacingHint ()); + + angleLayout->addWidget (m_angle90RadioButton, 0, 0, 1, 3); + angleLayout->addWidget (m_angle180RadioButton, 1, 0, 1, 3); + angleLayout->addWidget (m_angle270RadioButton, 2, 0, 1, 3); + + angleLayout->addWidget (m_angleCustomRadioButton, 3, 0); + angleLayout->addWidget (m_angleCustomInput, 3, 1); + angleLayout->addWidget (degreesLabel, 3, 2); + + angleLayout->setColumnStretch (1, 2); // Stretch Custom Angle Input + + + connect (m_angle90RadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotUpdate ())); + connect (m_angle180RadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotUpdate ())); + connect (m_angle270RadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotUpdate ())); + + connect (m_angleCustomRadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotAngleCustomRadioButtonToggled (bool))); + connect (m_angleCustomRadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotUpdate ())); + + connect (m_angleCustomInput, SIGNAL (valueChanged (int)), + this, SLOT (slotUpdate ())); +} + + +// public virtual [base kpTransformPreviewDialog] +bool kpTransformRotateDialog::isNoOp () const +{ + return (angle () == 0); +} + +// public +int kpTransformRotateDialog::angle () const +{ + int retAngle; + + + if (m_angle90RadioButton->isChecked ()) + retAngle = 90; + else if (m_angle180RadioButton->isChecked ()) + retAngle = 180; + else if (m_angle270RadioButton->isChecked ()) + retAngle = 270; + else // if (m_angleCustomRadioButton->isChecked ()) + retAngle = m_angleCustomInput->value (); + + + if (m_antiClockwiseRadioButton->isChecked ()) + retAngle *= -1; + + + if (retAngle < 0) + retAngle += ((0 - retAngle) / 360 + 1) * 360; + + if (retAngle >= 360) + retAngle -= ((retAngle - 360) / 360 + 1) * 360; + + + return retAngle; +} + + +// private virtual [base kpTransformPreviewDialog] +QSize kpTransformRotateDialog::newDimensions () const +{ + QMatrix matrix = kpPixmapFX::rotateMatrix (m_oldWidth, m_oldHeight, angle ()); + QRect rect = matrix.mapRect (QRect (0, 0, m_oldWidth, m_oldHeight)); + return rect.size (); +} + +// private virtual [base kpTransformPreviewDialog] +QImage kpTransformRotateDialog::transformPixmap (const QImage &image, + int targetWidth, int targetHeight) const +{ + return kpPixmapFX::rotate (image, angle (), + m_environ->backgroundColor (m_actOnSelection), + targetWidth, targetHeight); +} + + +// private slot +void kpTransformRotateDialog::slotAngleCustomRadioButtonToggled (bool isChecked) +{ + m_angleCustomInput->setEnabled (isChecked); + + if (isChecked) + m_angleCustomInput->setEditFocus (); +} + +// private slot virtual [base kpTransformPreviewDialog] +void kpTransformRotateDialog::slotUpdate () +{ + s_lastIsClockwise = m_clockwiseRadioButton->isChecked (); + s_lastAngleCustom = m_angleCustomInput->value (); + + kpTransformPreviewDialog::slotUpdate (); +} + + +// private slot virtual [base QDialog] +void kpTransformRotateDialog::accept () +{ + KLocalizedString message; + QString caption, continueButtonText; + + if (document ()->selection ()) + { + if (!document ()->textSelection ()) + { + message = + ki18n ("

Rotating the selection to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure want to rotate the selection?

"); + + caption = i18nc ("@title:window", "Rotate Selection?"); + continueButtonText = i18n ("Rotat&e Selection"); + } + } + else + { + message = + ki18n ("

Rotating the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure want to rotate the image?

"); + + caption = i18nc ("@title:window", "Rotate Image?"); + continueButtonText = i18n ("Rotat&e Image"); + } + + + const int newWidth = newDimensions ().width (); + const int newHeight = newDimensions ().height (); + + if (kpTool::warnIfBigImageSize (m_oldWidth, + m_oldHeight, + newWidth, newHeight, + message.subs (newWidth).subs (newHeight).toString (), + caption, + continueButtonText, + this)) + { + KDialog::accept (); + } +} + + +#include diff --git a/kolourpaint/dialogs/imagelib/transforms/kpTransformRotateDialog.h b/kolourpaint/dialogs/imagelib/transforms/kpTransformRotateDialog.h new file mode 100644 index 00000000..50282cc4 --- /dev/null +++ b/kolourpaint/dialogs/imagelib/transforms/kpTransformRotateDialog.h @@ -0,0 +1,93 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformRotateDialog_H +#define kpTransformRotateDialog_H + + +#include +#include + +#include +#include + + +class QButtonGroup; +class QRadioButton; + +class KIntNumInput; + + +class kpTransformRotateDialog : public kpTransformPreviewDialog +{ +Q_OBJECT + +public: + kpTransformRotateDialog (bool actOnSelection, + kpTransformDialogEnvironment *_env, + QWidget *parent); + virtual ~kpTransformRotateDialog (); + +private: + static int s_lastWidth, s_lastHeight; + static bool s_lastIsClockwise; + static int s_lastAngleCustom; + + void createDirectionGroupBox (); + void createAngleGroupBox (); + +public: + virtual bool isNoOp () const; + int angle () const; // 0 <= angle < 360 (clockwise); + +private: + virtual QSize newDimensions () const; + virtual QImage transformPixmap (const QImage &pixmap, + int targetWidth, int targetHeight) const; + +private slots: + void slotAngleCustomRadioButtonToggled (bool isChecked); + virtual void slotUpdate (); + +private slots: + virtual void accept (); + +private: + QRadioButton *m_antiClockwiseRadioButton, + *m_clockwiseRadioButton; + + QButtonGroup *m_angleButtonGroup; + QRadioButton *m_angle90RadioButton, + *m_angle180RadioButton, + *m_angle270RadioButton, + *m_angleCustomRadioButton; + KIntNumInput *m_angleCustomInput; +}; + + +#endif // kpTransformRotateDialog_H diff --git a/kolourpaint/dialogs/imagelib/transforms/kpTransformSkewDialog.cpp b/kolourpaint/dialogs/imagelib/transforms/kpTransformSkewDialog.cpp new file mode 100644 index 00000000..bed43f91 --- /dev/null +++ b/kolourpaint/dialogs/imagelib/transforms/kpTransformSkewDialog.cpp @@ -0,0 +1,296 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SKEW 0 +#define DEBUG_KP_TOOL_SKEW_DIALOG 0 + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +// private static +int kpTransformSkewDialog::s_lastWidth = -1, + kpTransformSkewDialog::s_lastHeight = -1; + +// private static +int kpTransformSkewDialog::s_lastHorizontalAngle = 0, + kpTransformSkewDialog::s_lastVerticalAngle = 0; + + +kpTransformSkewDialog::kpTransformSkewDialog (bool actOnSelection, + kpTransformDialogEnvironment *_env, QWidget *parent) + : kpTransformPreviewDialog (kpTransformPreviewDialog::AllFeatures, + false/*don't reserve top row*/, + actOnSelection ? i18nc ("@title:window", "Skew Selection") : i18nc ("@title:window", "Skew Image"), + i18n ("After skew:"), + actOnSelection, + _env, parent) +{ + // Too confusing - disable for now + s_lastHorizontalAngle = s_lastVerticalAngle = 0; + + + createAngleGroupBox (); + + + if (s_lastWidth > 0 && s_lastHeight > 0) + resize (s_lastWidth, s_lastHeight); + + + slotUpdate (); + + + m_horizontalSkewInput->setEditFocus (); +} + +kpTransformSkewDialog::~kpTransformSkewDialog () +{ + s_lastWidth = width (), s_lastHeight = height (); +} + + +// private +void kpTransformSkewDialog::createAngleGroupBox () +{ + QGroupBox *angleGroupBox = new QGroupBox (i18n ("Angle"), mainWidget ()); + addCustomWidget (angleGroupBox); + + + QLabel *horizontalSkewPixmapLabel = new QLabel (angleGroupBox); + horizontalSkewPixmapLabel->setPixmap (UserIcon ("image_skew_horizontal")); + + QLabel *horizontalSkewLabel = new QLabel (i18n ("&Horizontal:"), angleGroupBox); + m_horizontalSkewInput = new KIntNumInput (s_lastHorizontalAngle, angleGroupBox); + m_horizontalSkewInput->setMinimum (-89); + m_horizontalSkewInput->setMaximum (+89); + + QLabel *horizontalSkewDegreesLabel = new QLabel (i18n ("degrees"), angleGroupBox); + + + QLabel *verticalSkewPixmapLabel = new QLabel (angleGroupBox); + verticalSkewPixmapLabel->setPixmap (UserIcon ("image_skew_vertical")); + + QLabel *verticalSkewLabel = new QLabel (i18n ("&Vertical:"), angleGroupBox); + m_verticalSkewInput = new KIntNumInput (s_lastVerticalAngle, angleGroupBox); + m_verticalSkewInput->setMinimum (-89); + m_verticalSkewInput->setMaximum (+89); + + QLabel *verticalSkewDegreesLabel = new QLabel (i18n ("degrees"), angleGroupBox); + + + horizontalSkewLabel->setBuddy (m_horizontalSkewInput); + verticalSkewLabel->setBuddy (m_verticalSkewInput); + + + QGridLayout *angleLayout = new QGridLayout (angleGroupBox); + angleLayout->setMargin (marginHint () * 2); + angleLayout->setSpacing (spacingHint ()); + + angleLayout->addWidget (horizontalSkewPixmapLabel, 0, 0); + angleLayout->addWidget (horizontalSkewLabel, 0, 1); + angleLayout->addWidget (m_horizontalSkewInput, 0, 2, Qt::AlignVCenter); + angleLayout->addWidget (horizontalSkewDegreesLabel, 0, 3); + + angleLayout->addWidget (verticalSkewPixmapLabel, 1, 0); + angleLayout->addWidget (verticalSkewLabel, 1, 1); + angleLayout->addWidget (m_verticalSkewInput, 1, 2, Qt::AlignVCenter); + angleLayout->addWidget (verticalSkewDegreesLabel, 1, 3); + + + connect (m_horizontalSkewInput, SIGNAL (valueChanged (int)), + this, SLOT (slotUpdate ())); + connect (m_verticalSkewInput, SIGNAL (valueChanged (int)), + this, SLOT (slotUpdate ())); +} + + +// private virtual [base kpTransformPreviewDialog] +QSize kpTransformSkewDialog::newDimensions () const +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + QMatrix skewMatrix = kpPixmapFX::skewMatrix (doc->image (), + horizontalAngleForPixmapFX (), + verticalAngleForPixmapFX ()); + QRect skewRect = skewMatrix.mapRect (doc->rect (m_actOnSelection)); + + return QSize (skewRect.width (), skewRect.height ()); +} + +// private virtual [base kpTransformPreviewDialog] +QImage kpTransformSkewDialog::transformPixmap (const QImage &image, + int targetWidth, int targetHeight) const +{ + return kpPixmapFX::skew (image, + horizontalAngleForPixmapFX (), + verticalAngleForPixmapFX (), + m_environ->backgroundColor (m_actOnSelection), + targetWidth, + targetHeight); +} + + +// private +void kpTransformSkewDialog::updateLastAngles () +{ + s_lastHorizontalAngle = horizontalAngle (); + s_lastVerticalAngle = verticalAngle (); +} + +// private slot virtual [base kpTransformPreviewDialog] +void kpTransformSkewDialog::slotUpdate () +{ + updateLastAngles (); + kpTransformPreviewDialog::slotUpdate (); +} + + +// public +int kpTransformSkewDialog::horizontalAngle () const +{ + return m_horizontalSkewInput->value (); +} + +// public +int kpTransformSkewDialog::verticalAngle () const +{ + return m_verticalSkewInput->value (); +} + + +// public static +int kpTransformSkewDialog::horizontalAngleForPixmapFX (int hangle) +{ + return -hangle; +} + +// public static +int kpTransformSkewDialog::verticalAngleForPixmapFX (int vangle) +{ + return -vangle; +} + + +// public +int kpTransformSkewDialog::horizontalAngleForPixmapFX () const +{ + return kpTransformSkewDialog::horizontalAngleForPixmapFX (horizontalAngle ()); +} + +// public +int kpTransformSkewDialog::verticalAngleForPixmapFX () const +{ + return kpTransformSkewDialog::verticalAngleForPixmapFX (verticalAngle ()); +} + + +// public virtual [base kpTransformPreviewDialog] +bool kpTransformSkewDialog::isNoOp () const +{ + return (horizontalAngle () == 0) && (verticalAngle () == 0); +} + + +// private slot virtual [base QDialog] +void kpTransformSkewDialog::accept () +{ + KLocalizedString message; + QString caption, continueButtonText; + + if (document ()->selection ()) + { + if (!document ()->textSelection ()) + { + message = + ki18n ("

Skewing the selection to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure want to skew the selection?

"); + + caption = i18nc ("@title:window", "Skew Selection?"); + continueButtonText = i18n ("Sk&ew Selection"); + } + } + else + { + message = + ki18n ("

Skewing the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure want to skew the image?

"); + + caption = i18nc ("@title:window", "Skew Image?"); + continueButtonText = i18n ("Sk&ew Image"); + } + + + const int newWidth = newDimensions ().width (); + const int newHeight = newDimensions ().height (); + + if (kpTool::warnIfBigImageSize (m_oldWidth, + m_oldHeight, + newWidth, newHeight, + message.subs (newWidth).subs (newHeight).toString (), + caption, + continueButtonText, + this)) + { + KDialog::accept (); + } +} + + +#include diff --git a/kolourpaint/dialogs/imagelib/transforms/kpTransformSkewDialog.h b/kolourpaint/dialogs/imagelib/transforms/kpTransformSkewDialog.h new file mode 100644 index 00000000..7c788978 --- /dev/null +++ b/kolourpaint/dialogs/imagelib/transforms/kpTransformSkewDialog.h @@ -0,0 +1,90 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformSkewDialog_H +#define kpTransformSkewDialog_H + + +#include + +#include +#include + + +class QPixmap; + +class KIntNumInput; + + +class kpTransformSkewDialog : public kpTransformPreviewDialog +{ +Q_OBJECT + +public: + kpTransformSkewDialog (bool actOnSelection, + kpTransformDialogEnvironment *_env, QWidget *parent); + virtual ~kpTransformSkewDialog (); + +private: + static int s_lastWidth, s_lastHeight; + static int s_lastHorizontalAngle, s_lastVerticalAngle; + + void createAngleGroupBox (); + + virtual QSize newDimensions () const; + virtual QImage transformPixmap (const QImage &image, + int targetWidth, int targetHeight) const; + + void updateLastAngles (); + +private slots: + virtual void slotUpdate (); + +public: + // These are the angles the users sees in the dialog and... + int horizontalAngle () const; + int verticalAngle () const; + + // ...these functions translate them for use in kpPixmapFX::skew(). + static int horizontalAngleForPixmapFX (int hangle); + static int verticalAngleForPixmapFX (int vangle); + + int horizontalAngleForPixmapFX () const; + int verticalAngleForPixmapFX () const; + + virtual bool isNoOp () const; + +private slots: + virtual void accept (); + +private: + KIntNumInput *m_horizontalSkewInput, *m_verticalSkewInput; +}; + + +#endif // kpTransformSkewDialog_H diff --git a/kolourpaint/dialogs/kpColorSimilarityDialog.cpp b/kolourpaint/dialogs/kpColorSimilarityDialog.cpp new file mode 100644 index 00000000..afd19d5c --- /dev/null +++ b/kolourpaint/dialogs/kpColorSimilarityDialog.cpp @@ -0,0 +1,156 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + + +kpColorSimilarityDialog::kpColorSimilarityDialog (QWidget *parent) + : KDialog (parent) +{ + setCaption (i18nc ("@title:window", "Color Similarity")); + setButtons (KDialog::Ok | KDialog::Cancel); + QWidget *baseWidget = new QWidget (this); + setMainWidget (baseWidget); + + + QGroupBox *cubeGroupBox = new QGroupBox (i18n ("Preview"), baseWidget); + + m_colorSimilarityFrame = new kpColorSimilarityFrame(cubeGroupBox); + m_colorSimilarityFrame->setMinimumSize (240, 180); + + QPushButton *updatePushButton = new QPushButton (i18n ("&Update"), cubeGroupBox); + + + QVBoxLayout *cubeLayout = new QVBoxLayout (cubeGroupBox); + cubeLayout->setSpacing(spacingHint ()); + cubeLayout->setMargin (marginHint () * 2); + cubeLayout->addWidget (m_colorSimilarityFrame, 1/*stretch*/); + cubeLayout->addWidget (updatePushButton, 0/*stretch*/, Qt::AlignHCenter); + + + connect (updatePushButton, SIGNAL (clicked ()), + this, SLOT (slotColorSimilarityValueChanged ())); + + + QGroupBox *inputGroupBox = new QGroupBox (i18n ("&RGB Color Cube Distance"), + baseWidget); + + m_colorSimilarityInput = new KIntNumInput (inputGroupBox); + m_colorSimilarityInput->setRange (0, int (kpColorSimilarityHolder::MaxColorSimilarity * 100 + .1/*don't floor below target int*/), + 5/*step*/); + m_colorSimilarityInput->setSliderEnabled (true); + m_colorSimilarityInput->setSuffix (i18n ("%")); + m_colorSimilarityInput->setSpecialValueText (i18n ("Exact Match")); + + // TODO: We have a good handbook section on this, which we should + // somehow link to. + m_whatIsLabel = new QLabel ( + i18n ("
" + "What is Color Similarity?"), + inputGroupBox); + m_whatIsLabel->setAlignment (Qt::AlignHCenter); + connect (m_whatIsLabel, SIGNAL (linkActivated (const QString &)), + SLOT (slotWhatIsLabelClicked ())); + + + QVBoxLayout *inputLayout = new QVBoxLayout (inputGroupBox); + inputLayout->setSpacing (spacingHint () * 4); + inputLayout->setMargin (marginHint () * 2); + + inputLayout->addWidget (m_colorSimilarityInput); + inputLayout->addWidget (m_whatIsLabel); + + + // COMPAT: This is not firing properly when the user is typing in a + // new value. + connect (m_colorSimilarityInput, SIGNAL (valueChanged (int)), + this, SLOT (slotColorSimilarityValueChanged ())); + + + QVBoxLayout *baseLayout = new QVBoxLayout (baseWidget); + baseLayout->setSpacing (spacingHint () * 2); + baseLayout->setMargin (0/*margin*/); + baseLayout->addWidget (cubeGroupBox, 1/*stretch*/); + baseLayout->addWidget (inputGroupBox); +} + +kpColorSimilarityDialog::~kpColorSimilarityDialog () +{ +} + + +// public +double kpColorSimilarityDialog::colorSimilarity () const +{ + return m_colorSimilarityFrame->colorSimilarity (); +} + +// public +void kpColorSimilarityDialog::setColorSimilarity (double similarity) +{ + m_colorSimilarityInput->setValue (qRound (similarity * 100)); +} + + +// private slot +void kpColorSimilarityDialog::slotColorSimilarityValueChanged () +{ + m_colorSimilarityFrame->setColorSimilarity (double (m_colorSimilarityInput->value ()) / 100); +} + + +// private slot +void kpColorSimilarityDialog::slotWhatIsLabelClicked () +{ + QWhatsThis::showText (QCursor::pos (), m_colorSimilarityFrame->whatsThis (), + this); + + // LOTODO: It looks weird with the focus rectangle. + // It's also very hard for the user to make it lose focus for some reason + // (you must click on the label - nowhere else will work). + // + // This doesn't work - I don't know why: + // m_whatIsLabel->clearFocus (); + // + // Maybe it's a weird kind of focus? +} + + +#include diff --git a/kolourpaint/dialogs/kpColorSimilarityDialog.h b/kolourpaint/dialogs/kpColorSimilarityDialog.h new file mode 100644 index 00000000..528a078a --- /dev/null +++ b/kolourpaint/dialogs/kpColorSimilarityDialog.h @@ -0,0 +1,68 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_COLOR_SIMILARITY_DIALOG_H +#define KP_COLOR_SIMILARITY_DIALOG_H + + +#include + + +class QLabel; + +class KIntNumInput; + +class kpColorSimilarityFrame; + + +// LOTODO: Why doesn't this dialog automatically enforce a minimum size +// based on layout magic, like Image -> Resize / Scale? +class kpColorSimilarityDialog : public KDialog +{ +Q_OBJECT + +public: + kpColorSimilarityDialog (QWidget *parent); + virtual ~kpColorSimilarityDialog (); + + double colorSimilarity () const; + void setColorSimilarity (double similarity); + +private slots: + void slotColorSimilarityValueChanged (); + + void slotWhatIsLabelClicked (); + +private: + kpColorSimilarityFrame *m_colorSimilarityFrame; + KIntNumInput *m_colorSimilarityInput; + QLabel *m_whatIsLabel; +}; + + +#endif // KP_COLOR_SIMILARITY_DIALOG_H diff --git a/kolourpaint/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp b/kolourpaint/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp new file mode 100644 index 00000000..a3cac24f --- /dev/null +++ b/kolourpaint/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp @@ -0,0 +1,249 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET 0 + + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +// protected static +const QSize kpDocumentSaveOptionsPreviewDialog::s_pixmapLabelMinimumSize (25, 25); + + +kpDocumentSaveOptionsPreviewDialog::kpDocumentSaveOptionsPreviewDialog ( + QWidget *parent ) + : kpSubWindow (parent), +#if 0 +KDialogBase (parent, name, false/*non-modal*/, + i18n ("Save Preview"), + 0/*no buttons*/), +#endif + m_filePixmap (0), + m_fileSize (0) +{ + setWindowTitle (i18nc ("@title:window", "Save Preview")); + + QWidget *baseWidget = this;//new QWidget (this); + //setMainWidget (baseWidget); + + + QGridLayout *lay = new QGridLayout ( baseWidget ); + lay->setMargin( KDialog::marginHint () ); + lay->setSpacing( KDialog::spacingHint ()); + + m_filePixmapLabel = new kpResizeSignallingLabel (baseWidget); + m_fileSizeLabel = new QLabel (baseWidget); + + + m_filePixmapLabel->setMinimumSize (s_pixmapLabelMinimumSize); + + + lay->addWidget (m_filePixmapLabel, 0, 0); + lay->addWidget (m_fileSizeLabel, 1, 0, Qt::AlignHCenter); + + + lay->setRowStretch (0, 1); + + + connect (m_filePixmapLabel, SIGNAL (resized ()), + this, SLOT (updatePixmapPreview ())); +} + +kpDocumentSaveOptionsPreviewDialog::~kpDocumentSaveOptionsPreviewDialog () +{ + delete m_filePixmap; +} + + +// public +QSize kpDocumentSaveOptionsPreviewDialog::preferredMinimumSize () const +{ + const int contentsWidth = 180; + const int totalMarginsWidth = 2 * KDialog::marginHint (); + + return QSize (contentsWidth + totalMarginsWidth, + contentsWidth * 3 / 4 + totalMarginsWidth); +} + + +// public slot +void kpDocumentSaveOptionsPreviewDialog::setFilePixmapAndSize (const QImage &pixmap, + qint64 fileSize) +{ + delete m_filePixmap; + m_filePixmap = new QImage (pixmap); + + updatePixmapPreview (); + + m_fileSize = fileSize; + + const kpCommandSize::SizeType pixmapSize = kpCommandSize::PixmapSize (pixmap); + // (int cast is safe as long as the file size is not more than 20 million + // -- i.e. INT_MAX / 100 -- times the pixmap size) + const int percent = pixmapSize ? + qMax (1, + (int) ((kpCommandSize::SizeType) fileSize * 100 / pixmapSize)) : + 0; +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsPreviewDialog::setFilePixmapAndSize()" + << " pixmapSize=" << pixmapSize + << " fileSize=" << fileSize + << " raw fileSize/pixmapSize%=" + << (pixmapSize ? (kpCommandSize::SizeType) fileSize * 100 / pixmapSize : 0) + << endl; +#endif + + m_fileSizeLabel->setText (i18np ("1 byte (approx. %2%)", "%1 bytes (approx. %2%)", + m_fileSize, percent)); +} + +// public slot +void kpDocumentSaveOptionsPreviewDialog::updatePixmapPreview () +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsPreviewDialog::updatePreviewPixmap()" + << " filePixmapLabel.size=" << m_filePixmapLabel->size () + << " filePixmap.size=" << m_filePixmap->size () + << endl; +#endif + + if (m_filePixmap) + { + int maxNewWidth = qMin (m_filePixmap->width (), + m_filePixmapLabel->width ()), + maxNewHeight = qMin (m_filePixmap->height (), + m_filePixmapLabel->height ()); + + double keepsAspect = kpTransformPreviewDialog::aspectScale ( + maxNewWidth, maxNewHeight, + m_filePixmap->width (), m_filePixmap->height ()); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\tmaxNewWidth=" << maxNewWidth + << " maxNewHeight=" << maxNewHeight + << " keepsAspect=" << keepsAspect + << endl; + #endif + + + const int newWidth = kpTransformPreviewDialog::scaleDimension ( + m_filePixmap->width (), + keepsAspect, + 1, + maxNewWidth); + const int newHeight = kpTransformPreviewDialog::scaleDimension ( + m_filePixmap->height (), + keepsAspect, + 1, + maxNewHeight); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\tnewWidth=" << newWidth + << " newHeight=" << newHeight + << endl; + #endif + + + QImage transformedPixmap = + kpPixmapFX::scale (*m_filePixmap, + newWidth, newHeight); + + + QImage labelPixmap (m_filePixmapLabel->width (), + m_filePixmapLabel->height (), QImage::Format_ARGB32_Premultiplied); + labelPixmap.fill(QColor(Qt::transparent).rgba()); + kpPixmapFX::setPixmapAt (&labelPixmap, + (labelPixmap.width () - transformedPixmap.width ()) / 2, + (labelPixmap.height () - transformedPixmap.height ()) / 2, + transformedPixmap); + + + m_filePixmapLabel->setPixmap (QPixmap::fromImage(labelPixmap)); + } + else + { + m_filePixmapLabel->setPixmap (QPixmap ()); + } +} + + +// protected virtual [base QWidget] +void kpDocumentSaveOptionsPreviewDialog::closeEvent (QCloseEvent *e) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsPreviewDialog::closeEvent()"; +#endif + + QWidget::closeEvent (e); + + emit finished (); +} + +// protected virtual [base QWidget] +void kpDocumentSaveOptionsPreviewDialog::moveEvent (QMoveEvent *e) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsPreviewDialog::moveEvent()"; +#endif + + QWidget::moveEvent (e); + + emit moved (); +} + +// protected virtual [base QWidget] +void kpDocumentSaveOptionsPreviewDialog::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsPreviewDialog::resizeEvent()"; +#endif + + QWidget::resizeEvent (e); + + emit resized (); +} + + +#include diff --git a/kolourpaint/dialogs/kpDocumentSaveOptionsPreviewDialog.h b/kolourpaint/dialogs/kpDocumentSaveOptionsPreviewDialog.h new file mode 100644 index 00000000..1c45f7c7 --- /dev/null +++ b/kolourpaint/dialogs/kpDocumentSaveOptionsPreviewDialog.h @@ -0,0 +1,83 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDocumentSaveOptionsPreviewDialog_H +#define kpDocumentSaveOptionsPreviewDialog_H + + +#include + +#include + + +class QCloseEvent; +class QImage; +class QLabel; +class QMoveEvent; +class QResizeEvent; + +class kpResizeSignallingLabel; + + +class kpDocumentSaveOptionsPreviewDialog : public kpSubWindow +{ +Q_OBJECT + +public: + kpDocumentSaveOptionsPreviewDialog (QWidget *parent); + virtual ~kpDocumentSaveOptionsPreviewDialog (); + + QSize preferredMinimumSize () const; + +protected: + static const QSize s_pixmapLabelMinimumSize; + +signals: + void moved (); + void resized (); + void finished (); + +public slots: + void setFilePixmapAndSize (const QImage &filePixmap, qint64 fileSize); + void updatePixmapPreview (); + +protected: + virtual void closeEvent (QCloseEvent *e); + virtual void moveEvent (QMoveEvent *e); + virtual void resizeEvent (QResizeEvent *e); + +protected: + QImage *m_filePixmap; + qint64 m_fileSize; + + kpResizeSignallingLabel *m_filePixmapLabel; + QLabel *m_fileSizeLabel; +}; + + +#endif // kpDocumentSaveOptionsPreviewDialog_H diff --git a/kolourpaint/doc/CMakeLists.txt b/kolourpaint/doc/CMakeLists.txt new file mode 100644 index 00000000..9d1e30d0 --- /dev/null +++ b/kolourpaint/doc/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kde4_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en SUBDIR kolourpaint) diff --git a/kolourpaint/doc/KolourPaint.png b/kolourpaint/doc/KolourPaint.png new file mode 100644 index 0000000000000000000000000000000000000000..0db8993432cc80fb918bde3438fdaa8cf2043437 GIT binary patch literal 55836 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 OS&_Ob<@D0rzy1%xc2fob literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/eraser_shapes.png b/kolourpaint/doc/eraser_shapes.png new file mode 100644 index 0000000000000000000000000000000000000000..d3379e92e357502ef8e682418c20bce3f00847c9 GIT binary patch literal 188 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/fill_color_similarity.png b/kolourpaint/doc/fill_color_similarity.png new file mode 100644 index 0000000000000000000000000000000000000000..a07eb59ee303f4ec6edbaf5220a5dacd0f1cb326 GIT binary patch literal 13479 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/fill_style.png b/kolourpaint/doc/fill_style.png new file mode 100644 index 0000000000000000000000000000000000000000..4f9885870f879ab96599d91917dc81e0083f2d2f GIT binary patch literal 181 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 kz>)1MNSl%L-gVbhZ+Wcan?a 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 VbUYLdsFvPHiOGGf7BvX?{{Sr3f#U!G literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/image_emboss.png b/kolourpaint/doc/image_emboss.png new file mode 100644 index 0000000000000000000000000000000000000000..13206c4e6a9088aa7defc6dbe91b6d13a6c64a2f GIT binary patch literal 86303 zcmX_nV{|25*KN9E+qP}z#5OzXBpusMI<{@QW81c^6P-96yKkQN8{__{v8z_iy=u+9 z=0+((Sy2iR4i63t3=C05T3i(j?7Pd?h5-ZhWl74ZPx&ff9i(-f!N8D){@cE#&?Di0 z8F5`Cv|S`k?VMdqovdw)oSjX=U7Z|^EbUzUO&q>3U^xGQ)Gb|1ZNL=lolHq& zY>muKNhHk7Oif&z+1UAkVBeI(WyAsM9vc@q5&jrQAER%?_fM*IR-1Xh3x|6Q=yDlD zyq1VQli`|MT9$z_RT`)m$Q~)Og_@|}nlX(fcttS+MC?d^*Bz3T6%5{antp?@Ei$x}<3}RtiD= zH&!XWQIixuwlz~!y#;L{Ay$SfnS_gVw`AO`jTW{%VUlHK!-e`-(Pww!Sl+*%-iM2< zSO%jd(58pB^>@!L>Sictl-!5}3mhB`s)&_`#)t+_Osv7<=*G+RufoVlRD>{EuP{}B z^!I>t1SvLwKV$3CGr2M2(iG(6k

JgfUsH#x@LgBZJX#O0@faqe~*I`GGfY?1j;w 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

VK->& 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/image_flatten.png b/kolourpaint/doc/image_flatten.png new file mode 100644 index 0000000000000000000000000000000000000000..fe35614148b8a871527ec1545dc437fee4f4c96f GIT binary patch literal 35714 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/image_reduce_colors.png b/kolourpaint/doc/image_reduce_colors.png new file mode 100644 index 0000000000000000000000000000000000000000..177627036e57ae9bfd28de929e2e3672887dcd9e GIT binary patch literal 34759 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 G?|%TjoaNL2 literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/image_rotate.png b/kolourpaint/doc/image_rotate.png new file mode 100644 index 0000000000000000000000000000000000000000..d81f63a25094314043f1c5498c70f25beacced3d GIT binary patch literal 43590 zcmYIuWmFtp(=F}TZ`XA-B3^!Wpa{Y8lJ1CIgkL{yN%Ijid!$? z?W>hlCXJewxTR;2@+ERlBhAXiGKRbIrDyRy%_>rZJD6$|1BNstS}FqQU6jChIB^(c zst172^f{n2OBfsb+G}6X=VgE05Re3xFxoerM)kM-aS_s%-kwC|dlFxL-`D?Z$pC5(Kl3$b+m z@FxxZB2{o`jHC+3BSGX6k+IMyT-bHLdg@K}rC*lH$$U__A4g)lWzu}%#Z$K|M7*}L z}PNRJ~dY~{^0 zyH-V+4l&|TWWXti52_>IstXbketMddyp52=m0Xl8A~tb0;nd%D-geS|QonsT0a98R ziKY!Yf1+3da}58@r;-?&!AzqWN}$mTgKTZhCRv6nn{+1=<361ftxUr{F~n;1 ziX54eFUwqYLyn3b-=vx?dj_%SIu67rBp!rNm}iA4B;9oZ;StL@uoH zL3v5ZWknOld8Gd53jQmbRI)wV*RNDR^>wB~fZw=52F<8!t!Xu`u4ak&k$>+7POggc zt+;?x=^)Qb@W9{6kpKG>u;a{&Ln^i=hZ0rNK@IGLaj#~)A1xv$LGF%eZN!hJ164rsq3rlL&h^w7CJd<9 z{MOV|)ezb>O=ROrrh@1?509dX3gnuan#0r6WP_|v9)EPe|F9VvpO&QDJX5UNg(0pzN67*k6+Uu zH$7pF=)!)_s%~4h(yA(dxf08G2z#>aSNirJDvxT$;`JUy;lZ35(+JXbvBEt_PXWOm$q+@G$0;(h-{z+Rj;E*t;$>(vSgIV4A7aj45yWSu zE`RjGJZmY7JqPNWQsd~x)neYC{tmXJUOChxif{PG!^7jqM+#!t=@c0E^UyjE9r&SE ztuP#|*Px)xX)nCh%efVjrMhk^M!rOA(nE~3w1scgS!R^uh!z#Xfsc$FG~i3CBhe3X zHaZZNp>^F;3KU9gd5H9qDWH(u#Z{(F_A&=10#h2?PH$Q}GMPS06tMho2$>M6vU-DHIat&WA~A<+iOCl~v;Q{#__duAuE^?qt7Hh8>ssoK+T4F>qPD zHBG02qh31&)esm+O@@p{t^LkWSb>JV490 zUyK%8Pec?Z;ex!^){+eJ4ju^}aMZKD>?0Mj>7e4;Dq$X;$m*lh)4q9SVTfV2PwegL z>@3?fF^cj8(Eb4*Z0P3JTtQk@Sy^b$Y#*k&boQrv4YZbAkz7-HvwU)Q*sO3fZ+EfU zj>A^gL%(#oKM^wfRXwB`)naS&Jv}h8D^9i33Vk)}J}U{ik@}8CZCnm9#C9|^wTL+? z7!hN?QeX>XMDW_l=6Z+)9eNN}*z#q6I$ieW?Lq1kTY6<E9;z5i9I1O*uw07I^*E8 zDKhbOLR$aMLo%%xRh?9hzq7TyWS!t;xmfq24;jj-RbZ2$tN( z_}2nSNpW6V&u8|w`=3e04@%jd{O5i}X!=z6P()?}wX%4izTzA9k$)B-Sx}xodam|j zSWlD2)6DJGV&qx(RsX;8%OUL*cnqWQqGj6Rs=wyAPl+ZFfy>w4(w&aR#5Iv4ly!Q4 z2QtoBqb5RyJ_@PlN*PCJ#XNrWyoFE>zGOYNrI?1#=lv;3iKDjL;5G|;#*V@#pz+Ci zjcys7e!}kkKOIet-Q|n}5+l3_?_Sq2CKM|QvXSpD{Pu-jWq)E~-rjybZ;~lS80!0# zmTJBnzN}rYdJ8_eZob2!2$?ctolqOYo@;h`*ffN}B*bDh>*(r68Y<^j|01qLVOT(K z{N7OA(4h9Gg|9mbp>FTD6zr}BJi}^fm9JQ?7?(I5mto~hmwkyO)N|du_#*GC9 z^{{EM=Z0|&d6j|wkxmKU!ao#VFDeu#U2bdf4sINbq^7Yam$Li05rp&*hKZL%Rw)@F zUCCaX9a{~}r5TP+2(w8(`#3pT6<+hd{%Njz7#J*%`c=VSa;GCAgiB>cn1ZH|Md!QeZ_rF%f3c@3jo$68_QPdFozYTXbD>niHd}l%4n_8;*hTdpCd<}lwaBvtXl*+SiFrVC?s;F|- zXSn=453IJkI(`#d_ZTPG^39>0D4^%SUTQ6?_dQo!UzrvTeL;Q6s^$6h z;m`08my(Q_zsN6<6VT6$*ADMa#&3+KT>BT&nr~`!;zUZ~?T>}1PFwK3K^yi22VQt~ z4(oM+yM>NH9J(1uaoS%lfPwV!0qvadVV=+%MAEWM< z5uT=X9c7~|N~OupcD>`ke+=8zi1tirE;Y+PI6RRhRy?`yyUd5?OmGT`y2k??*KsJ-%t64MPv<5^NKDfp zUdxjJ&*a-g_ovBaLedisMBnpmGiKO?{M@h8-G-#v(Ww6<2$@@a3Du{vmSMvQc3TCX zz=5~MUmx6c^R=iu;!4w0C3|NV4_I}rvfh-Ov? z?;>9GhI1*sGv1$86udHS1o`ey1B}nJe_yv$%oRrSO7(QUmc}m0nG6t_WG_>}-V7>G z0|a&H=LEX&(?eh{G1x>)*khq9QTU$?bs+179??3X6T1bE2GuvQ$`DK{0M1~w&ePO# zGKPj&d1IKQ81@Vfve=gVW7un;#SHAsgU^eA z{n`gaYI>4u0tCwQ|5G!DM`b8*!w$Nf&=S=t8c7d%LK?-#yFpSn0P#7a{9a4~dq zKwFzku!hk8rmd9@z{Le}R%%3-?Yz}^-dc35O@_>?2bLwJtd6q!sj1xaOk1QtL>Cpn zVRm1F#5Mw7#n2>PKSzr-yY6BmNzQh-3j9haogVTtRvsAIA*TF+*ak>vcbNa=( zV4y+UL(mG$NI&~E(IG32#uzZs>hSqz=5Q2#LGJ#E+0?z=8t#T(H&eNd<=RqBW(x)n zS#pT;X7|G!Z+v+ZKcH5oa1IMuj7UbO%yRAGAwd*(?bvbUsFvAlEx9IaLr(;&gU73p zDWHP--{QXmNZ}DcNA6SGOM`+O$4A2Xux(7y5l)66lnBWc&|W&#d9-ZJ6P)+YFXsfV zZnM|jMG>g>*z$hu{>;pX8ZYGW)jKy2s^J?bwr=rb9io{z2hRIMa8d>jF97}hd zmBF8i-yGP9@aBkQ{D83jj$?fw-(^To3A#Z_9GA>|XYOjKK!sL@z0NJVN=p`!<+I(u zIEHP&X(2%T(@9Q0*ER@iXc9;Bom`o#^7>%s$SSVjCry9|9i&whiKOAKZ58k8`X$h$O_W&bFLx znyO)H2AnU9zR6C>ZYFC0F12JDFf*71%o+@?m+}44QHf$WaqY3#%3NY!8{5Kb)zMs{ z^W;03#;VxN8u_5^)V)y%r@tExXE0EdC|42;%KDCv>C~R%S$-+j)$=cpo0Zc;6E4o5 z*rIi%#vrC7X?Ujdg1AYGpy=<}k@n5`W}T0OSSd>QxnsV+V1n9geDjJ<{b0QJZ1k{{ zl-lp*-?PRq&j!BQ@Pnhit?Bvz93Oe07$3@e^t|PprbE#ZfjHkv5D)ui7^-p+SW2lE zE+Dg!Tz^X>q~wZ#z4G0Sqy*$cT1nJYEC@#ZD5{g-##%xM)@2y(gc3DY@T)QE4n z?k!)bo~XV9q^`?zV)IhW3e}q$>&kkq=#GB=Mcr|pWbZQUn_HZYhizY5 zPQ;eLxLtIABbe(qK#WjyTm>!C;sYi2l~u2)J*vKz>C0fW z7~MjRgucGFj)Xq%$5-$QH*epoa1C2l3_^+G9-*7lZvjEZIN)0?K3G4aJ-QfFVml>t z&N`>`@0)I>eO5<$3Zf{JS8)uGOs^J*aO9995IS24HC4ox9rzC3NQ zsO#*({>HRcXY>k%Z$~shKq9UdToUlgOuGGRy!REqp2-U%Hq4oHy@Pc6kDFpCCn3|$ zVWb%MCUAGJX87&Zt$an5Ej8xboW_h&<&RNoRc+=}_A;OH#n!H?l0?4(jtV#$LO7_a zfFiN4^&!;ZIQnc7oFP^Q4AF;ul(A@K-t^^FfXJR(WGr#oUmaG(_X7}|a(0351vE4N zNW`n-lq6M&UnK}rr{c(2Urr}i}J3X(0EZFU7Z;hA3vMa!nXxN1kM0otKKAr99dGj&d zDvonp@=iEHvM>U@yvGzPR`jR)H=TDkLux-%c43P8<28qbyu36{o23Yyo3n|OTQr0{ z4L}0sk;T`qwc28KH!dK(+Me22;F2gc+JwM_&_s`~zqDGaTIumdM-|adzCDMxb-?ej z>$KFOJuBhnkL5V3Qar^$n{b0+;l+6lm)9sy7cVXmW{Qy@#lf}%edt9zJcf>)Pu#yU zLkF0DNN$|xFua)Y?u*<|ruN>LtQyTBa$w4TtU1it*}EM4?0-tcuD7io>FM?z04ZiY&)UuPXV=)u*>~nU+e-n-rviXAn}Th<^8U z)@S7;o|2N`bb50YRb#49VH`-0B)53M3_I>}j-W@a{hG@#DYIT{>_9FJc8v=-*f8HNQ1Edztt#BCERQQQ8P zf4B1U+$NSTM0@Hkfm_TxK`QyGx(XQfP6OQ%QD=+Su@h#7o0slF{{)7&tKXY*^>~(!a%eV>@k@*^*OIwxXuLjLM)A%rU#s z&jyFMe#YO6)}_{CFghX#Ix-gR@VjrE78x9XM?Fj_wG!Dduz6bc^P?^WCbDD4s2p;l zb;t!ucFNkiyoFQDF)PmddK>Egnv^s#0Fd0LZDqbNbNcPLa(nO$9=YzcD_dX1?d6PZ z+9&avFaFHX=JEo{8zeJfk#~p}{A;2qr?@gQrAbNu&s)Q{NB`K+M4yUX0~XzOZ2}OQ zlT_;_M;HQPqCS|^q`samin@*sReB+k@9~;Y=EJW91B9H$WGdGame*v#*IS|PIqs>? zS5ZCDqA(Mr6NOq=(4*G`?_q{Hvgt40lDCl=up@kQQ~M)6fmCkS(My4-WJMGh$tqDX1@ zt3eHTr@%~U$UmBu8v;pRS7C|B>l~stWhH;C445+EQRL~#ht{#-oh(MW)tjlZEx`{) z=*BDJ!zU|pUV@Dc2$zP9^~smuP4ua|TxOdjVbBc1wBsXqqUlUfym(CH0PU4(&g^(6 zrcC^p3bnff1ZY_Gl*F~RkK05J>2GLQlLJP#PD2T>Ub||PA-|c-UgXVKqy}hB6ag*w zyf&^)MK+SIw1=mn53+S`rS&fL{eOlX&5BWD?OB!JHyl?}+U8caoEe-H_^|n!J+pto zvH5ke5p>xLuVV*|A}JH+X<<0P^rQB&!3@$<=J$W;&dmGmUr)ZiVLehmiC8lO1|aT#aMRJtB+6Y6Zt#mR?$73OTQLcy2K2 z>FTi+xzM}WALV$QW}6)_4Jx@R@GZLHuwamSyJGQ5oI2Y|T1a;g(M*i871Brv5|9|P zo;0%H0B~K@j(&z|*aC{@q%8j0f%Iq)hvg%*R3^D43$_A z4Pq_B;zFkTTx--BayB@QOCQGSmc7R{vr(q@$p@AeDgC?rCg%ho@keRQ?JD8 z0bP>G07ku220D72=~GU%n3p;S2>+;#4|f?M^?m??8M%!%g~_xVPx zMrF>@ls%^#=xB;9u^#FG3E30$TTvcp%Ac2rn~5>C<3zc&U&0Tjp&4@fZe8w4z{OJg z$CC0D{zVODNAKZdnV><08E=pzZBLW^xD6Izv@lDQv$;RCOgh{ibeFXd?BM69;5ZF) z1$HF!xz=-bH1o~7a-PcTNy@W*LU)DX$Yzpda@Iph*H&v=Y~w~E=eL*eHQ*;Vg~NEU z>hkr)W;ykIg!kLhru$1_!{VSWO7JFPGIt5Uln#;^kn!Z&Qljw$Zpj-{qL*P4P^E=Q zK$5mn>zJK5Tz@gU|EhOMm}MOWY|Z4SN|zyY5|a!v1a}azbn+1`OSu+X?wBaVPYF#I zfCxbhzKPDMku77 zR>-?;9?5#$Sr2U|(0TA8b2MYX80w@H7~-aRG_UB|Um>G~k`+RO(}J5reCq95dN@<^ zO%ZW6H-T*=f@@BiwEIO#kmdnsEIwYU5m7B0`d5MO;Xkgj69%#q-Xv0W?SoAX1n`H= z^r4pM`qSVCP34JwoiItA9QP7@0@LGVE!=765~Ls;3DN8st}V^`VfVZP>)-vC5bgw# znWvjLToLb(|9tQHC+0Vh6NavIjrhp5MV;upwPij=J2+YtHEo0M;Es97^UV`Pg~S@!H(w2){UVbiK#vmS_VuI72qye(euB^DbRQORSStPYiT$KW_h>90W^|zgUPg zU30L0C}c30K^{JpOZn3x=9Fzq`#9! zJqcv(|Fbs@3Pq%gW31OsMfLbOoNe?|A#AIGl4L-5sV$DWwtg5ah95E?#0`p&;M)Jt z+|f+Y>Mx?@cwsZMkRN_PWTXj(vgNr^V;D1|_+7$4Fdwe8uzq-07s z19eR31NMc-W6eUIFr1q)DbF8*JGuM*TkzomFu@)BPQsjDu2Cu4PJ@Jmr=tfSNh(xs z;eiLfa=e$>G;OQh^J>DcW1F-0Af{UZG8YG5L^|+?$&9rbtt5pjTsNRDimpg?-JiWX{60-?ljWiJk8+db65QO(Q3cP)@vSRNc0MoDLEl! zinOjoT;nHbNv4|yC?URlcWCdgIdx>VC_tVnXH)n4l?bv8VzgcQvaS0e>;*9 zh@KV^31;Yl@-K2_6!7~(E>1rT?f*W@A>jSeyRmU}5DvNwX0+TN{AR6&LMl`C^)Oil zRIdEjOIPbP$8hdlxMM!eiBMZ;oA_2f#q&Fu!TXi+&2 zuz9ruQ{$9=cHGWn$ez#X|vI6K`544EdCcXpL2h?D#*LcB3Ec6~aR z!!OIgjg`7*?Xs@Aw(xNC?oXXS%kWc&=BPL2O7%hVG~qtvi;l1P6+qF|I1Rl36Tv3Z zU%%3!psiSLtz0Q94FW0$V$HMT2wDsutr80qQ{#mBLwxZS-o`^!fUHC}g^-cFn;Ftg z*VWyu4lY-!&(~Mv3(|f~pM+6FQ1dDjMyoj*sL%(}EUw420wpEfGE9<=xff4=UBAU(XgN{(LCOmRiE*gdQHz)p*k89lLk zz~Qghm#43D_z0wnJ)Iov5=CeObv1y@6ac5?bTBv0RKD$WL^lJowH^;N_osv5b)|*(UA83BppjCRzCQWn5KtsWmd)=Ja-a(9R&hF5}q~KbgxEjRvZ3LPw?kEy@%CUTWmyb?kLDt5)sE1_v$Na2JtRH(LLBozjeaQiw(`SHC z56ZiB9X(l@Jg$>Dc)|IES;YU(qbq1a;#65hf5}Wu>24L&J)H|`!s>g)sg=RKMRXqc z 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 W)T&duw)1z8m#mZ`s9NGn;Qs;UoI@=D literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/image_skew.png b/kolourpaint/doc/image_skew.png new file mode 100644 index 0000000000000000000000000000000000000000..4eba9b65fe62d71e3fdc42dc1baadc0619481074 GIT binary patch literal 34922 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

8NN<|@JJ{dD-&S&Drh~<@4lWAqJJ@n3B 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/index.docbook b/kolourpaint/doc/index.docbook new file mode 100644 index 00000000..c0cc8e84 --- /dev/null +++ b/kolourpaint/doc/index.docbook @@ -0,0 +1,1529 @@ + + + ClarenceDang"> + dang@kde.org"> + ThurstonDang"> + thurston_dang@users.sourceforge.net"> + + + +]> + + + + +The &kolourpaint; Handbook + + + + +Thurston +Dang + +&Thurston.Dang.mail; + + + + +Clarence +Dang + + + + +&Lauri.Watts; + + + + + +2004 +2005 +&Thurston.Dang; + + + +&FDLNotice; + +2011-04-03 +&kde; 4.7 + + + +&kolourpaint; is a free, easy-to-use paint program for &kde;. + + + + +kolourpaint +kdegraphics + + + + + +Introduction +&kolourpaint; is a free, easy-to-use paint program for &kde;. It's +perfect for everyday tasks such as: + + + +Painting - drawing diagrams and finger painting + + +Manipulating Screenshots - acquiring and editing screenshots + + +Image Manipulation - editing photos and acquired images; applying +effects + + +Icon Editing - drawing clipart and logos with transparency + + + + + + + + + + + + + +Using &kolourpaint; + +Click on the following links to explore &kolourpaint;'s +capabilities: + + + +Tools + + +Working with Color + + +View Options + + +Image Effects + + + + + +Tools + + +Acquiring Screenshots + +A screenshot is a snapshot of what is on your computer's screen. It can be useful +to explain some actions that should be done to obtain the result, or to show the issue that you have found. + +To make screenshot ready for editing in &kolourpaint; window you can use File +Acquire Screenshot item from &kolourpaint; main menu. + + + + + + + + + +Using the opened dialog you can change Screenshot Delay (in seconds), and choose +to Hide Main Window during screenshoting. When you are ready to take screenshot +just click OK. The screenshot taken will be placed directly into the &kolourpaint; +editing area. + + + + +Tool Reference + + +A quick way to select a tool in &kolourpaint; is to press the single key shortcut associated with it, +documented below and in the Tool Box tooltips. You can also hold +&Alt;&Shift; while pressing the key, which is necessary when you are +writing text (as the single key shortcuts will be disabled). For example, to select the brush, press +&Alt;&Shift;B or just B (when not writing text). + + + + + + + +Brush (B) + + + + + + + + +Color Eraser (O) + + + + + + + + + +Color Picker (C) + + + + + + + + + + + + +Connected Lines (N) + + + + + + + + + + + +Curve (V) + + + + + + + + + + + + +Ellipse (E) + + + + + + + + + + + + +Eraser (A) + + + + + + + + + + + + +Flood Fill (F) + + + + + + + + + + + + +Line (L) + + + + + + + + + + + + +Pen (P) + + + + + + + + + + + + +Polygon (G) + + + + + + + + + + + + +Rectangle (R) + + + + + + + + + + + + +Rounded Rectangle (U) + + + + + + + + + + + + +Selection (Elliptical) (I) + + + + + + + + + + + + +Selection (Free-Form) (M) + + + + + + + + + + + + +Selection (Rectangular) (S) + + + + + + + + + + + + +Spraycan (Y) + + + + + + + + + + + + +Text (T) + + + + + + + +Brush +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_brush.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click or click and drag with the brush to draw. + + + + + + + + + + + + + + + + +Click on one of the shapes to select the brush shape. You can use a +circular, square, slash or backslash brush shape. + + + + + + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color. + + + +Color Picker <inlinemediaobject> +<imageobject> +<imagedata fileref="tool_color_picker.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +To set the foreground color, left click on +a pixel. To set the background color, right click on a pixel. +&kolourpaint; will then return to the previously selected tool. + + + + +Connected Lines and Polygon +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_polystar.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to draw connected lines. The polygon tool is used in +the same way, however, the start and end points are automatically connected +to form a polygon. + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color, and will also reverse the fill color for polygons. + +You can set the line width. For +polygons, you can also set the fill +style. + + + + +Curve <inlinemediaobject> +<imageobject> +<imagedata fileref="tool_curve.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to draw a line - this sets the start and end +points. You can set up to two control points by dragging. To finish the +curve without using both or any control points, click the other mouse +button. The curve tool draws a Cubic Bezier. + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color. + +You can also set the line +width. + + + + +Ellipse <inlinemediaobject> +<imageobject> +<imagedata fileref="tool_ellipse.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to draw an ellipse. + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color, and will reverse the fill color. + +You can also set the line width and fill +style. + +For additional functionality, use the modifier keys: + + + +Hold &Shift; and drag to draw a circle. + + +To draw an ellipse with a center point of your choice, hold &Ctrl;, +click on the center point, and drag until the ellipse is the correct size +and shape. + + +To draw a circle with a center point of your choice, hold &Ctrl; and +&Shift;, click on the center point, and drag until the circle is the correct +size. + + + + + + +Erasers + + +Eraser +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_eraser.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag with the eraser to rub out mistakes. + + +Unlike other tools, the erasers draw in the background color. To draw +in the foreground color, use the &RMB;. + + +The eraser only has square +shapes. To draw with other shapes such as circles use the Brush and the &RMB;. + + +Double-click on the Eraser icon to clear the entire image. This is +equivalent to using the Clear option on +the Image menu. + + + + + +Color Eraser +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_color_washer.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to replace pixels of the foreground color with the +background color. To replace all pixels similar (but not necessarily exactly +equal) to the foreground color, such as in dithered images and photos, use a +Color Similarity setting other than +Exact. + + +Unlike other tools, the erasers draw in the background color. To +replace pixels of the background color with the foreground color, use the +&RMB;. + + +You can configure the eraser +size. + + +Double-click on the Color Eraser icon to apply it to the entire image. + + + + + + +Flood Fill +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_flood_fill.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click to fill a region. To fill a dithered region, use a Color Similarity setting other than Exact. + +The &LMB; fills in the foreground color. The &RMB; fills in the +background color. + + + + +Line +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_line.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to draw a line. + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color. + +You can also set the line +width. + + + + + + + + + + + + + + + + + + + + + +Hold &Ctrl; to draw lines angled at the nearest multiple of 30 degrees +- these are the lines in the red diagram. + + +Hold &Shift; to draw lines angled at the nearest multiple of 45 +degrees - these are the lines in the blue diagram. + + +Hold &Ctrl; and &Shift; to draw lines angled at the nearest multiple +of 30 or 45 degrees - these are the lines in the green diagram. + + + + + + +Pen +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_pen.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click to draw a dot or click and drag to draw a freehand line. + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color. + + + + +Rectangles +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_rectangles.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to draw a rectangle. The Rounded Rectangle is a Rectangle with rounded +corners. + +The &LMB; draws in the foreground color. The &RMB; draws in the background color, +and will reverse the fill color. + +You can also set the line width and fill +style. + + For additional functionality, use the modifier keys: + + + +Hold &Shift; and drag to draw a square. + + +To draw a rectangle with a center point of your choice, hold &Ctrl;, +click on the center point, and drag until the rectangle is the correct size +and shape. + + +To draw a square with a center point of your choice, hold &Ctrl; and &Shift;, +click on the center point, and drag until the square is the correct size. + + + + + + +Selections +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_selections.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Use the selection tools to draw out the boundary of a +selection. + +To move the selection, click and drag on it. The main view will scroll as required to allow you to move the selection to part of the image that is not currently displayed. + + +You can free-form Resize the entire image or +Smooth Scale the selection using the corresponding handles. +Hold &Shift; while free-form scaling the selection to maintain aspect ratio. +The &RMB; invokes a context menu with common Edit commands and Image Effects. + + + +You can use the cursor keys while drawing out the boundary of the +selection or while moving it. + + + +If you hold &Ctrl; before moving the selection, then you will move a +copy of it. The selection will be smeared when moving it while &Shift; is held. + + + + + + + + + + + + + + +There are two selection modes: Opaque (default) and Transparent. If +you use the Transparent selection mode, all pixels of the background color +will be transparent (background subtraction). This allows you to paste a +selection without the background. To perform background subtraction on a +dithered image, use a Color Similarity +setting other than Exact. + + + + + + +You can apply Image Effects to a selection - see the Image Effects section for more +information. + + + +Spraycan +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_spraycan.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to spray graffiti. Hold down the mouse button for a +more concentrated spray. + + + + + + + + + + + + + +Click on one of the shapes to select the spray size. You can select +from spray sizes of 9x9, 17x17 and 29x29. + + + + + + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color. + + + + +Text +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_text.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + + +Click and drag an area in which to write text. As soon as the border will be shown you can start writing the text. Click and drag +on the border to move it. You can resize the text box by dragging on the +handles or by using the Resize dialog. + + + +If you have deselected a text box you can use Undo to edit the text +again. + + + +Using the Transparent Color + + + + + + + + +The left picture shows the example image. The right picture shows the addition of text +with opaque foreground and background colors. + + + + + + + + + + +The left picture shows the addition of text with opaque foreground +colors and a transparent background color. The right picture shows the +addition of text with a transparent foreground color and opaque background +color. + + + + +Common Tool Options + + + + + + + + + + + + + +Click on one of the squares to select the eraser size. You can select +from squares of side length 2, 3, 5, 9, 17 and 29 pixels. + +The eraser size setting affects the Erasers. + + + + + + + + + + + +Click on one of the lines to select the line width. You can select +from line widths of 1, 2, 3, 5 and 8 pixel(s). + +The line width setting affects the Connected Lines, Curve, Ellipse, Line, Polygon, Rectangle and Rounded Rectangle tools. + + + + + + + + + + + +Click on one of the rectangles to select the fill style. You can +select from No Fill, Fill with Background Color and Fill with Foreground +Color. The fill style setting affects the Ellipse, Polygon, Rectangle and Rounded Rectangle tools. + + + + + + + + + + + +Working with Color + + +The Color Box + + + + + + + +Color Box + + + + +The Color Box has 3 main sections: the Color Tablet, the Color Palette +and the Color Similarity Selector. + +The Color Tablet shows the current foreground color as a square on top +of another square representing the current background color. When drawing +with the &LMB;, the foreground color is used, and when drawing with the +&RMB; the background color is used (except for the Erasers). You can click on the double-ended +arrow to swap the foreground and background colors. + +The Color Palette shows a selection of colors for you to choose +from. The translucent pyramid represents the transparent color. Left-click +on a color to set the foreground color and right-click on a color to set the +background color. You can also drag and drop any opaque color into the Color +Tablet squares. To edit a color in the Color Tablet or Palette, double-click +on it. The Color Picker tool allows +you to select a color from the image. + +Color Similarity allows you to work more effectively with dithered +images and photos, in a comparable manner to the Magic Wand feature of other paint programs. It applies to transparent selections, as well as the +Flood Fill, Color Eraser and Autocrop / Remove Internal Border tools. Double-click on the Color +Similarity Selector to choose how similar colors must be to be considered +identical. When using selections in Transparent mode, any color in the +selection that is similar to the background color will also be made +transparent. + + + + + + + + + + +The left picture shows the example image. The right pictures demonstrate the use of a flood fill, with Color Similarity settings of 5%, 15% and 30%. In this example, with a Color Similarity setting of Exact, a flood fill at (80, 100) would only fill one pixel, as the surrounding pixels are similar but not identical. As Color Similarity is increased, more pixels that are similar in color are considered identical, hence the fill extends further. + + + + + + +View Options + + +View Options Reference + +Zoom incorporating the Grid +Thumbnail + + + + +Zoom incorporating the Grid +Increase the zoom level to edit images with more precision, or reduce it to see more of the image. + + + +At zoom levels that aren't multiples of 100%, parts of the image may appear to move when the user interacts with it. Other minor redraw glitches may also occur at such zoom levels. + + + +At zoom levels of 400% or greater that are also multiples of 100%, you can Show Grid to more accurately edit individual pixels. + + + + + + + + + + + + + + + +The first picture shows the Text tool icon, while the latter shows it at 600% zoom with the grid on. + + + +Another way of zooming when not drawing is to scroll the wheel while holding &Ctrl;. + + + + + + +Thumbnail + + + + + + + + +If Zoomed Thumbnail Mode is selected, the entire image is displayed, scaled as required to fit the thumbnail window (top-right picture). + + +Otherwise, the thumbnail displays as much of the image as possible, starting from the top-left of the main view (bottom-right picture). + + + + + + +Image Effects + + +Image Effects Reference +Autocrop / Remove Internal Border +Balance +Clear +Emboss +Flatten +Flip (upside down) +Invert +Reduce Colors +Reduce to Grayscale +Reduce to Monochrome (Dithered) +Mirror (horizontally) +Resize / Scale +Rotate +Set as Image (Crop) +Skew +Soften & Sharpen +More Effects +Notes + + + +Autocrop / Remove Internal Border + +This automatically removes the border of an image or selection. Use +Autocrop if you have a figure that does not fill the entire image or selection and you +wish to remove the excess whitespace. To use this feature with a dithered +image border, you will also need to use Color +Similarity. + + + + +Balance + + +This feature is accessible from the More Effects dialog. + + + + + + + + + +This allows you to set the brightness, contrast and gamma of the image or selection. + + + + +The more common measure of gamma (a decimal from 0.10 to 10.00) is located between the +Gamma spinbox and the Reset button. + + + + + +Clear + +This fills the entire image or selection with the background +color. + + +Double-click on the Eraser +icon to clear the entire image. + + + + + +Emboss + + +This feature is accessible from the More Effects dialog. + + + + + + + + + + +Check Enable to apply the Emboss effect. This emphasises the edges and gives the +image or selection an "engraved look". + + + + +Flatten + + +This feature is accessible from the More Effects dialog. + + + + + + + + + + +This recolors the image with varying shades of the two selected colors. + + + + +Flip (upside down) + +This flips the entire image or selection vertically. + + + + +Invert + + +This feature is accessible from the More Effects dialog. + + + + + + + + + +This allows you to invert one or more RGB channels in the image or selection. Select All to change a photo into a negative and vice versa. This generally looks quite funny. + + +To quickly invert all channels, you do not need to use this dialog. You can instead +access the Invert Colors item in the Image or +Selection menu. The Selection item is only displayed +in the menubar if you use one of the selection tools. Additionally you can reach this action +from the context menu opened with a &RMB; click in the image area. + + + + + + +Mirror (horizontally) + +This mirrors the entire image or selection horizontally. + + + + +Reduce Colors + + +This feature is accessible from the More Effects dialog. + + + + + + + + + + +This reduces the number of colors used by the image or selection, with or without dithering. + + + +Dithering generally provides better quality results, however, you may wish to disable it for artistic effects; +⪚ using Monochrome instead of +Monochrome (Dithered) gives a silhouette effect. + + + +Another important distinction is that while Monochrome (Dithered) will always reduce the entire image or selection to black and white, Monochrome will do this only if the image or selection contains more than 2 colors. + + + +For a quick, dithered monochrome image or selection, use the Reduce to Monochrome (Dithered) item of the Image or Selection menu. The Selection item is only displayed +in the menubar if you use one of the selection tools. Additionally you can reach this action +from the context menu opened with a &RMB; click in the image area. + + + + + +Changing the number of colors here has no effect on the color depth of the file format. If you want to +change the color depth, you should select it in the file saving dialogs. Note that, confusingly, changing +the color depth also changes the number of colors. + + + + + +Reduce to Grayscale + + This reduces the entire image or selection to grayscale. + + + + +Reduce to Monochrome (Dithered) + +This reduces the entire image or selection to black and white. + + + +If you do not want the image or selection to be dithered, use the +Reduce Colors dialog. + + + + + +Resize / Scale + + + + + + + + + +Resizing the image changes the dimensions of the image without +applying a transformation to the existing contents. Scaling the image will +stretch the existing contents to the new dimensions. Smooth Scale +generally provides better quality results than Scaling, by blending neighbouring colors. + +You can express the new dimensions in pixels, or as a percentage of +the original size. If you select Keep aspect ratio, the +width and height will be scaled by the same percentage. + + + +You can free-form Resize the entire image or Smooth Scale the selection using the corresponding handles. + + + + +Only scaling is supported for selections, and only resizing is +supported for text boxes. See Notes for +additional details about applying these effects. + + + + +Rotate + + + + + + + + + +This rotates the image. You can specify the angle and direction of +rotation. + + +You can reverse the direction of rotation by specifying a negative +custom angle. + + + +See Notes for details about +applying this effect to a selection. + + + + + +Set as Image (Crop) + +This will set the selection as the image. + + +This is only available when you have an active selection. + + + + +Skew + + + + + + + + + +This skews the entire image or selection horizontally and/or +vertically. + + +See Notes for details about +applying this effect to a selection. + + + + +Soften & Sharpen + + +This feature is accessible from the More Effects dialog. + + + + + + + + + + +Use this effect to soften or sharpen the image. + + + + + +More Effects + + +This dialog contains the Balance, +Emboss, Flatten, +Invert, Reduce Colors +and Soften & Sharpen features. + + + + +Notes + +Resizing / Scaling, Rotating and Skewing may change the dimensions of the +image. You can view the new dimensions in the dialog. + +If you apply these effects to an image, the image will be resized if +necessary. However, if you apply these effects to a selection, the image +will not be resized, even if the transformed selection does not fit. + + + + + + + + + + +The left image has been rotated 30 degrees clockwise to form the right image. &kolourpaint; has +automatically enlarged the image to accommodate the larger contents. + + + + + + + + + + + +The left selection has been rotated 30 degrees clockwise to form the right +selection. The image size has remained the same, hence parts of the selection will not be visible +without Resizing the image. + + + + + + +Credits and License + +Carl Tucker + +It might not be concise documentation; it might not be complete documentation; but it is +honest documentation. + + + + +&kolourpaint; + +Program Copyright © 2003, 2004, 2005 &Clarence.Dang; &Clarence.Dang.mail; + + +&kolourpaint;-specific icons Copyright © 2004, 2005 +Kristof Borrey borrey@kde.org, +Nuno Pinheiro nf.pinheiro@gmail.com, +&Danny.Allen; dannya40uk@yahoo.co.uk + + +Documentation and additional documentation artwork Copyright © 2004, 2005 +&Thurston.Dang; &Thurston.Dang.mail; + +Portions reproduced with permission from . + + + +&underFDL; + +This program is licensed as follows: + +Copyright © 2003, 2004, 2005 &Clarence.Dang; &Clarence.Dang.mail; + + +All rights reserved. + + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + +Installation + +How to obtain &kolourpaint; + +&install.intro.documentation; + +More frequent releases with support for previous versions of &kde; are +available at http://kolourpaint.sourceforge.net/. + + + + +Compilation and Installation + + + +If you are reading this help in the &khelpcenter;, then &kolourpaint; has already been +installed on this system and you do not need to follow these generic instructions. + + + +&install.compile.documentation; + + + + + +&documentation.index; + + + diff --git a/kolourpaint/doc/line_width.png b/kolourpaint/doc/line_width.png new file mode 100644 index 0000000000000000000000000000000000000000..13fd4c5b4665f5edd3103dbb5d7dfc6b30daf880 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^7C;=p!3-p;|GP;8DbWC*5LY10!0{geW=iG-_gN4mm7V})R P1KH^5>gTe~DWM4fi3UA| literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/lines_30_45_deg.png b/kolourpaint/doc/lines_30_45_deg.png new file mode 100644 index 0000000000000000000000000000000000000000..0b38c991b227ef2deb9871f620161f203fd4f044 GIT binary patch literal 551 zcmV+?0@(eDP)4sCB@e1oQpnj?UYl|=cNOS zxqjrGxc=17^FiC!`*JzRAMX_ht#=P!4#1usf8B_0AGd+-e%FEC|A~Q?zl_&&fBqY; z=NI4J`0w4c=7(v&=`8m2Fq|_@HJqmU=GyD1i+vw8TPHowS-L;8*3^eg|~b<-PBhX(l2il={3Z(=46vjI7VuHqBri=hLFiZY`{^PIK*8b3lg5Hu>S&y;2PP z@R1X5cud`mdc&L1UF@$top$#pZdpJWuX3UsiBezXOFfdP47pVBA+rj3$TKH#%R0yU pt*2tV$Vd%os#~^NQz0MZ{{gedWGBiY8yo-t002ovPDHLkV1kk2_YD95 literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/lines_30_deg.png b/kolourpaint/doc/lines_30_deg.png new file mode 100644 index 0000000000000000000000000000000000000000..454b5ba30ca1d03f232d70f64c6b50815e650904 GIT binary patch literal 430 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/selections_opaque_transparent.png b/kolourpaint/doc/selections_opaque_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..24f5ffda75d704caad84ac60132021dab5cfc0a7 GIT binary patch literal 969 zcmWlYeK6Yx9LB$)t_jUJ!>Z9~522@aq+OFvaY7{8G(){y*$h>7ePI@Bt75%s1)E{r zdbHTJ^}e?1u2$)gpJYVP(3Hx&RZXn2AoS)Mt@tkc<9VL@{QKPV6h{cSRO=nq06-1r zg+`f^WmW{)((JhyMgt%nkKjjh%m{#x3UCGB4$wE>_dWmg;HC?IlO7WjlhBA?n0!yS z;@75wq7E!Dg_J6=(!{SG#u_rzP~f&bxp53z?W`VBK@tKI4oJ8l;X#+sS1OefkAV0c zh%O)qAY?$uf%pRydmR)$4vMgk^bbJa2Ksi;cY(oRSj+HV%RFQ>8clv+5+5~*1;_yW z*b>=P454D=c8qk!C}&J^$0QF-_P|VE%sYhbfR6z?06PJtfCQinP!8Aw*bArt>;qH+ z_5%(84gx*_Q~?eF4g)>~R0BQ(9042!d=5AUr~&*Fa2#*~a1w9|a2oIh;0)kPz*)e* z0OtU;fb)Q_02cuN0n`I70WJfs0ImWeRxf{OZr#E=JTy4^!o*SqaoQo+rI%HJGpbar%!|i!-u$haRu$4Z?2lYOx1a7f$!oF+`Z?}i#z~jjW4Wv| zkLvcU|Eeoh$>T?^9<91R)X7Tx(2$Z}tg+KBoHv9(% z41QBt@4mX&K-;-E#b>ls40vs8Ojin%i&7gB7$>4V?U)wyLTl%?+s^eg%c3$(ZQWdG zwv%>~^;3!04RrwPRL{F0{aC4ufAX%mwRQIs35U~qsI8hC>)XHK*2WWCC-UM791i!5 zcMWxho!?+NeikwmG04ky$x2jJxr(G>*0(q1+KRg}#*DByGeOs_&TV?YMJZcq#X; z^wsyCR`vcDd+R9yRrkfsx-Lc1LYjS($fbn2WS*NpWc*9FRXwz!UsiDMXuMZeb#8V~ zR@HB!19xz90|VX3+FNtQiau$nqtUoX>h7#HG>f*7V_(kY30Y_RPevQROmw2WZE*q{Uk1;1mUE;I%sf()NXw9Uyk@%Y`=Gn1>{3bDvaWmT hRLtzyf?*?Q_lDIlFw0XdJU{vWlHnXdXbU?j|3AlT;O77U literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/spraycan_patterns.png b/kolourpaint/doc/spraycan_patterns.png new file mode 100644 index 0000000000000000000000000000000000000000..136ae06a1fe2867417b1757d48a8337f4afa2aa9 GIT binary patch literal 406 zcmV;H0crk;P)b2;G);d_5ZhHua2SL=lT6Y3C@bbIVW$+8V$Y?u`sKOwi+ zEa1qLAZ$^DjWLie`tYoZcZQ1rL7-Z|oSX_L=?T1>lmxMJ+!w_g%TVkkbhT7FLnK)0 zpGOwx1@HXjfI>E*i^MjI2od=KdBC;to%HMOSCBJUD4# z@(;k}QX{y1S&=jM=`3J6j*2k15=u@bQ9zgY>eJeO2MFe0Qoyu49L)~&N$0y+Z9tcN zxEjzU#;8*a8_gPeEc?(Hq8EL3jEy<}o&SUI3TTx!to_Pv4FCWD07*qoM6N<$f+zL2 AZvX%Q literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/text_zoom_grid.png b/kolourpaint/doc/text_zoom_grid.png new file mode 100644 index 0000000000000000000000000000000000000000..41988e0f013efe252de4dd130c5a0a386432daaa GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^2_Vb?Bp8l=)_4M>=6kw0hE&{od&`ik!GOo@;SJ62 z_RWjg-z-b%Qka#*8}X~|p{`#mKYMB9)@5;<-fmlvWPSa;n{H@MrEb>LJ$>~}_FA77 zak#G2_|&W<@>B!F)^G}(w`jGn$ifxz7dNYlECh=DikNugo+wa8uvtWVbcku%j;;TJJGvm+oUd%F6$ JtaD0e0swunQ-1&e literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/tool_brush.png b/kolourpaint/doc/tool_brush.png new file mode 100644 index 0000000000000000000000000000000000000000..372ff624e715c8ce737123076fa599eea96a6c23 GIT binary patch literal 675 zcmV;U0$lxxP)#dEwU9+cA2$^g!Eln6 z*LFU3S#P)3LMMMyqXHB~se*U92TV+$DG|pD0B^Ad6)NC8*3z-LZEurEWkj063S?9? z*niO4O_yTe`Gxd{@Aw88iX%82r>ncVT#sgEq7N_H8XB2ZKXNn{pM;Scfed*l9?!iC zfk3#xaU5aUC2z-RFXX>2Yw5q%8*JUvZmz4Z(TidUqL>3IWTDb*1c3(me1VHZLiWBsH&>Q{nWE^Y zJus|Hw_;K_lQg#2^)y2p2r)oQ1nBiTWV0C~$K^7LA)eZe<2(1kRIU9=9TB|x&(Y>z z9}^u(b%Rx#&#P;Ao{-h0dCup%&MO)mJ&CjW?Vun65CB_Ul(7i|47a?kFurvDhc5Mw z2|XVlM$eV7gS0;e*lrv>btgLi%b8~lh_w@Ai_V)Ji z@$vKXtEi|1lG)igtXa$%85x{qoQ&lR0(AnyRYH30dK@{dKyGDa6+;b!=@b)|7Us#5 zr_7o)$8V`GV<+RDJ$qaxJ1<_mbjgxsOP4OsoSQXk^Q=vqHUq(yEnBv2+rEAK4u*LQ z3`-dpRxvWHW@K2)$T*jQaVaD7QWoZw%q-KHSr#z!)N%5)af@~c*=@6PSZ*J>DJ1N0 zSj5V(h|OVb7uq^Ebx*l5Wyg-)Cr_R}dGgGuQ)f?~2BLFk&H&N*bLTFeKY!`Mh07N& zUb=Yk@})~xE?>TS`SP_ZSFT^ZdgI~eho3H8m;nq~%aS0!U8c3Fj1|8puZwV4 zvH8s72(#vZRQWRtQ|LN&z&*|B^bw5QmSO+uEjkVjf4O1ip{E3NR^umyM;RQq`F?ypW#%PQ} zGB3RF!ejyw2?k~cC?U9o(lMYEMmvXF$@;&pw52WmIpzHIcxuGVJD%jto4ik+C-3_{ zKvmTzDh<%5yDgiw4zHa*k@%U7EVFd{>pNm@wZ1$tzVgW~`;NyPThHyet8JZqCQ(i?Ly6)JGL&rvMUO00^ambPj-?UQDe6>nxUt zI>wOpi!ftqIbvyRYly4GVx<BtoND^mwUdXa07N;m3Vr01r{j^gwo;lLQ zWmbsAj<(7SYy9c;bMd$S%9KJd&>%=kP10@$5L9Pb7LvqTOSxQ0(4u;SX{^;^HHwl_NvC-aO-EniIF6NNnSufI03&d6Ih!wC zX6#FsrOtafb-r5@%iv$Y`wxBpEkWpuk_1k_6I!D-iR~ zMUHJfG=8SLy9-4SLIe7By@ZCy^-m) z+34x%OYNfYDD*#rfbY_((>at^RnQ>FTBWv%^G%c9>0b-7EdCQ!EE+x4)zzB}EiJyM dCE#Cc@Ha^a*X4y~GcW)E002ovPDHLkV1k-A*mVE^ literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/tool_curve.png b/kolourpaint/doc/tool_curve.png new file mode 100644 index 0000000000000000000000000000000000000000..c7e0b274b95e3531c4c11927cb8b3269b25623f7 GIT binary patch literal 478 zcmV<40U`d0P)0X+9>t6Ok3Hr8@KvR5f7x4Dlfi<5!oZv;jTA@DlkBVW3+pyM3rLyiHpgD>^un8}Z z@JY__X4yu#S2)a%QX&5t9&`$98X?`S@oHIP0tkz0>VUe8iaHS|7N~KSrE9dGou6hN z?YaYu>=_hiNH#hgKSH=ktjn#qp@!~4A)iBBH^IhRRcld1Z8Vt9outbK{QqtI1~IH> U^@~7-xc~qF07*qoM6N<$f+x1ndjJ3c literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/tool_ellipse.png b/kolourpaint/doc/tool_ellipse.png new file mode 100644 index 0000000000000000000000000000000000000000..4e2fe52462b19e9d30faaeae7888c85b418b79f7 GIT binary patch literal 807 zcmV+?1K9kDP)sz zAz?4A-5`4LlCfe$fyt6)uC1fH&V9~YPG=@IP^uRX9De5<&gc8PoZlf3LV=A8@VCRJ zy)7&@b$lq&=8xX}j zh?gZltt>B`{V+Z{wMGDYB$Aa|)4En{@9pVDot?*Zf@Cddxe!{&GNB74Nw?DJr^1Wp z1LZU;3`@nuE*uf^Q>aK(Jxo-@j^4O_&j5gf(8i$Qc%W%|0;UU18(0U9oG#QibbPzt zcjb^}#qVJB0?NWV@ilb*ftBVVHsFk1~ zOHzE0q!^l@7>1-sGC*YWQ?(ReM}Z=*_Pu#E*k851byk%5rKvBfXxYdufdWr~5Kq#0 z4T8*X5vUj0`I*=Cu5H}(``~cfvAc-mI+8oD4pVg4&od%WG@eYDr)ZudX)Z|8EEaHV zl^J?`tx1*(LnH5oFaBvH5`-Ti7oLbF+cLGgPR*3X!yppE0wId^1ON%y$?>78_ao1$ zXKyZ zDpXLRQe3LIMxzyDtys0Kp{7Zzwx*fMo3}WrC?fsyz~%DZyXW5X;2w@K2L1P?M}En+MNTxFG&^%WHrvnHw){ytRI*J~1@9fF4}o)!ux*EOu_(0wQ~ch17) zJs-N~FI~C1ha$2i8hz09oAc)j?|Od}Gi2@WxOcO(tbAD_GjUK`a{)6Cr9kY->Cau&f-hOe_Ben5G9h z9r1f0&%>j&bq#TkN6t8tOiY-W&o+jpO_-B`g%C3-WjKeJ)jMD%@5V+x7@?Id9OSCF z<0sD^*}82<*yR>Yf=OO3A`IA$)N#pxa(G}kmrkUtZ&nuCqL;ZvFDF0+?(#P zjUEZs5CX;tgb~|za%PNv|JauuOr+U&MWP;`#*4BDJzkgKcDpzMp{)Drl_thNkKMl> z42DyIDdAy+X)ZH{N89dF-s9# z?q*7H^Y|i_RaUDnlV{Mh!dr`qj7077n(bXBOCo}y@|vbXU6FY;{iQ$j>eZv!wRIcf zyd<8F6scW*Ht{#g%a_e(Jh%)1D=zR)d35;1#bzs;44yfCXy;IRaE2^votBqh8*OVF zozM%h*yFc(FXfH%^=o48P`}y-8W$dvtjzCoc_Ja+u(-9CL^ zubRNI)j$knKfQoUe?h{N1W_gc8RsHI5p3lijm-$Rx@xuBI_vez&kh}EsZKA*f_|J*RaJc?JgyE&ag9v4A(j}8#w$HNJ%;2DAM(|B&fOk&HE;re zI0sOY0!XR=5?I?eRe1=7m1P~m0tiKDPGGTED$Qo|Om}zp=}<5zSaBm@k@2r&!;sZyHTt`|@Y%JRmMzI63PV(`^ce1X?8`34}B$v}c(@>nb;M^W^< zsxmYVpC4PUI@wIQ3s`;HgWPynG)scfE@n0RTk*^&;2gX0^zBH=JMIXUSY z9O%CW01BSS-_p7@wzyueKQlf){&{|WK4dbPUTZX(_I#YTi+f@A5uOzWgW)275}U5u camU~JH?K^VLN}qmng9R*07*qoM6N<$f|D6(3jhEB literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/tool_flood_fill.png b/kolourpaint/doc/tool_flood_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..f0900a77b4c17526d0e8a481c64944b9509573a6 GIT binary patch literal 636 zcmV-?0)zdDP)^+L%VuxQdP7M-hd<#zIh8jlm+O2qY5G1ng`U3u}!CNhL|C zh=ms7$7aEQKm|cW!9puR3$q6E-puvf*<@J~S;c|N&fb~xd*|HuSVXAP;?ju4&tB9U zdAqRTvgsu5IXuz5DRnhweucje0CWEg*wH`P!%Vd_as6px36Rrh&jVuq3fNVi+U|_0 zJ8KRk)==UsDFo|0Y7P)yySu?sU-`;T5vf_ryJeNX7F@7d$?`F58>lJm#UUQ4OefzP zdC3Rb&&FI2fqFeIdo=POl;?pmA0YOWA7w9Fbnn(kS95z4D^T3rzZO~eSL!HbKA=@_wDhKnv0 z1eCgfi5SCcc~Lhi)XK8VpNrUHAsxW^p31@EW|DA|2pBCjf*TGSt7Vy7^R28T&JF!~ zefQxXzr@n<$ysJI9-RmZJ>Qy%EW_E*=lbo(F;3sW_#oiJnU_l+Yjce@$UGn;8YK?4 z8Ruv2rSj>;%#+rKlv=)%SRQqj$J$+-JyaYO3HPJC8D|O6PPU69fSI*G4udI@?N`$y{!7w_HD_!p?dGI#^0kZ<3+XR~$skM$dm WNd2~UPoo0>00005r00004b3#c}2nYxW zddg1vx2*%t%L50bqY zC8FR*n;y{75dv8QIJ?{ zvy6^NISvLq9xre@=P|%xevTLXEc!wyaB_K!naW1m8k%Ihx83B1XMW z9}*&oot(0Z9@pJBj#`H0=~>1hh837O)UDsLYMS>)7rGliFy$)!%_P;`K2Zn-V zS*B8}J8_|{r9sVcZ1?ok#5Jqk>okvpNyuWt-e^)95g8mRli-ry1K%`V-`kTAe0qIi zTZ~jvf)Uo)!Y3)Y4?iYoRFrCuSgN3dWF#2~t**LwbH(rbI?&rwv7--Mu527*=Sv-y z*98$_VtN&p(zxtHdqXxF8zF*x)viwL?Cb<+`=@uomL;Qb2~FcP9`Ox^)7m}fSd z|81-%v28*^D=Dd{V{T<*WovI`=V<5XV(;wg;Ns@u;RR7?Xku<;Y7SIv0#);Gj z>FDa=;^FP??FTXws?5sF(%Re_sLbBl&e7HpsLajD&C}J>$HT|}QufhRK*z|H1o;Is zh>D4ei%UpINJ>gcNl9P2eC6uZYuB&exOwZ=?K^i0s=o^XRVI15IEGZ*iamdl$w7g^ zHIS<_plw2>+`s?Kx$*Db>8wvy{dK+QjxDp5QThC)%IZ>1iyS}Y18eMVNyxm|+j*dF z>s6UIQ&;mH+!CS1ruH+2EyGmQ?#9&QcQ?aC*f#|VvH$+fY{bi2)Ett#8)zGYr>mdK II;Vst00`WD6aWAK literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/tool_pen.png b/kolourpaint/doc/tool_pen.png new file mode 100644 index 0000000000000000000000000000000000000000..467b0d511edca61f86661d0ec46bd41cffa60f5e GIT binary patch literal 572 zcmV-C0>k}@P)1jIsl>gY{kZhA9$h6bYV|m1o{mv5g!QpzeIjh?Y7CnN?&d$mh4QI~3;M zK%hr!c6%aC#00EkB&{|(@P1*a>_GiR?-R$mKx!72!pu$cvVTgfvI6tHn#=U0&JjHT z_HiZa84a}74zNbK@to{ok6*@b(RhEmJ>b~}O1EnIf>093Rh zs};;GTKMx*+3cbWL!GwOowc2= zxi(8Fpjeo7qXRJ$!o~Oc5H%6OfUd12q)4c(XnO2pVLGXB2qDVaTUHe_TGlWlnl6TB z%v@J7C#tql14I@3lF6p;E;&E3@5s#{l#m^A4%$OQzHAW|hD~9WWx||jDx-`sU^W5B zIAn8*p8WnjBryQTp^lR+k#N6Fm@^#!147dpp)F0f%6mHM%76)ig(aE{4yTs-23HrA zI7tJdxS(VjK>&cRMP({CmBy5DL@+V#%2=Scv3MmuyB1%Lx)$ENu;&!i%sK2Pl;=dU zEJK)_j&0{$eFw!aSrvDg9FO?3RZNJ{`uKPlg@-(Qh@Lvf8L8HmJjb)pv^&pfmfg8# ztJ`I^5xkhq84W`qM5yMO80~i^k_>w2u?UZS-k`sJEoX)`}Y3b$a`RL$|nX%4YcrEHOTWoRRA?pCK zNOJ%KdHnTNfSk9#`g?wE4re@ZtHXV{yR)HWW@;ecBHN=z9c~z)L>mAgO~KEeIF!y@f#p-{?9Fl)`~5x7`@WM}Yw;hLq69ka&Ml~2NO^PhfZ*WB}%Is1CCLn*y+`O`Ee? zeXjSX+3IjrKnpZRvEj)$pa&K^C<)fpB?Ewu1`9lgHHT<8^uo4B$=p>^O}RY-W>Ems z8!*qXT!CLHCM#m{KwG355{bk_Q2@U#^ZkZtDSJ(c0))@c9?=iEPu{PaouA(2VBQGn zzE~vmkv+dqR1vhEC!nda$}(o=hl^F}Di*oi&O8b1g!~5NJL<_$#;lon#$BVd+gZAP zoL6W*-{^IFoM@A#SvE|*g^&Xi^C@Bn-TqiO6w8~{5W1k&diwhJ+47h&jApK%Im1RW zrPqT2|FenUrqY&p!E_~C(bH^&TaXU5PE7{`$Ns`sS;`>oHT2#T9#h_8Y=hrY$<$yd gc=9fO!N32`pXr%sj}tV+?*IS*07*qoM6N<$f@f~|+5i9m literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/tool_polystar.png b/kolourpaint/doc/tool_polystar.png new file mode 100644 index 0000000000000000000000000000000000000000..b8f72e8e91a7321a0aad403269ff3998e075dbf8 GIT binary patch literal 1308 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/tool_rectangle.png b/kolourpaint/doc/tool_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..b373fa46bfd8edc9296f14a0afd259fb9803b848 GIT binary patch literal 548 zcmV+<0^9wGP)r0`JA~9X>ay*I1zq-n03{VM2qKjP8ZL3{ zbD7RW)QJREB{&+-v8DOG|18GJdk;@3^>X#~yUopt63@89EVTG|o z!V*7ukDbGI_e~_S_hV-{3e@)2mJdBc@=WZR1z1=SAv1d5*mDB*qNpf57bMIq{C;!& zMHD~)qLnBJP!ux>V}Qg5U@Qg303uV6DJ~djaK*#Ojs*n@-b*E!N(GK04x%gj$^kC}wfUet}QNNXr23UwhZX!S;x=WuwelD z91B+`x|%lgvnFZOwRKjR{eOa=?cwQ{-K&!-P#dk57EEoSXOrn)=+Vc5@9=tIwFkIW=aj2k@P5AKr!#{ovTnn4YsXNsNi9)|&8Wjl>*Ts{l6e mW`6}MCKy3=1vY*1@B9}*x^o$e&Z-vx0000e1v&@gnfXBfry}jhK-1hqK=!(mZ8m?o~E3t=A5hOovY@ZtLdGs z>7A|Wo~`MguIitz>Y=deq_XR#wCtv}>ZrBssJ85?x9qE|t*yH4ue$BBzwNWX?X$q` zwY$}}!SA=i?zqG5xw^W!!|%Jr@4d(Iy~**u$nn6-@x#pW#KpzM&-2E~*v8NE$k6i1 z(eugD^UKuo&D8YG)b-BR^v~7w(bx0Q+4Rxb_0rn))YiOvG`s?oc z?CtRE?(pvK@&5k)+h)|D00001bW%=J06^y0W&i*I6iGxuR49>SU?3L6dZU0?giJ?u zC`2r@x&z2%)-p0SGSJu4)zMT}Q&D1utE&{{l7om#atN0(Ffg{XG}YJDlo#b^Wv3-4 zGQ!lgb6YjS#8b^V+ZY&|n;L4XiVO2{(o>S+qTmWm%#g)ZH5eF?jEf0yfh**XM;5o> zVPLGSDuHN=^l`ScgDYgNKo+-VW?(D@YfJERwF3dTLJV<6khX9SJ19UXL>Fhw%}#KK zaqS!tR%arM+XAi52(yRsoLoJ<;0pNyk;N@}85o11TnAS#U;iMuLPG^)ad~Y9Mwq2; zKK@}5(Qt*Wtjabp@eot?Rt6>!aWIgOl9rKGlx2cPVSxaP5JX&zO|XE3L_sV703hOd U_Ydty@c;k-07*qoM6N<$f~nn~SO5S3 literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/tool_rounded_rectangle.png b/kolourpaint/doc/tool_rounded_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..2fd0b3e83923b00c42f45d7365d47646394714aa GIT binary patch literal 723 zcmV;^0xbQBP)8+~FEE4<7arcn`7WIAeCIyy!2p1c6Xrg-c=-y} zDXuY~aq^IQM`Kmv`^wTH>^F!mfBE|0Y&>zRu=C66?Tynwutg{ZN(1x<9`|zPcbRPF z)4@*mzk=B)HN-0fI3ph31Td)@v{jlnco@lHI5K_wu`U@oZSOaSLXahGay4xBa+Z!qADAzo`Jn##HbW?lO%=Zwe+nj-+;#Tr z+>_P0MJKbl&1>k0Aci2G)(p0sa59+`smbo#>_R!GCXvss#5$t!@xRM-{_5m_pX8o{taX^pom&R6>;J~K z2x+oR)8``ugljvce8g{uoEkedyfF80#cOb^Wjgjc{{c{=m9|*cr11a%002ovPDHLk FV1lJDSy})9 literal 0 HcmV?d00001 diff --git a/kolourpaint/doc/tool_selections.png b/kolourpaint/doc/tool_selections.png new file mode 100644 index 0000000000000000000000000000000000000000..ca7430ee827c4999e4fc470e9bc605e17bba941f GIT binary patch literal 1562 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

R|G_`xHm9Q}=vK}(t29u$uWl`A_4 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 B@Hzkh literal 0 HcmV?d00001 diff --git a/kolourpaint/document/kpDocument.cpp b/kolourpaint/document/kpDocument.cpp new file mode 100644 index 00000000..da2eb6a1 --- /dev/null +++ b/kolourpaint/document/kpDocument.cpp @@ -0,0 +1,468 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DOCUMENT 0 + + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +kpDocument::kpDocument (int w, int h, + kpDocumentEnvironment *environ) + : QObject (), + m_constructorWidth (w), m_constructorHeight (h), + m_isFromURL (false), + m_savedAtLeastOnceBefore (false), + m_saveOptions (new kpDocumentSaveOptions ()), + m_metaInfo (new kpDocumentMetaInfo ()), + m_modified (false), + m_selection (0), + m_oldWidth (-1), m_oldHeight (-1), + d (new kpDocumentPrivate ()) +{ +#if DEBUG_KP_DOCUMENT && 0 + kDebug () << "kpDocument::kpDocument (" << w << "," << h << ")"; +#endif + + m_image = new kpImage(w, h, QImage::Format_ARGB32_Premultiplied); + m_image->fill(QColor(Qt::white).rgb()); + + d->environ = environ; +} + +//--------------------------------------------------------------------- + +kpDocument::~kpDocument () +{ + delete d; + + delete m_image; + + delete m_saveOptions; + delete m_metaInfo; + + delete m_selection; +} + +//--------------------------------------------------------------------- + +// public +kpDocumentEnvironment *kpDocument::environ () const +{ + return d->environ; +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setEnviron (kpDocumentEnvironment *environ) +{ + d->environ = environ; +} + +//--------------------------------------------------------------------- + +// public +bool kpDocument::savedAtLeastOnceBefore () const +{ + return m_savedAtLeastOnceBefore; +} + +//--------------------------------------------------------------------- + +// public +KUrl kpDocument::url () const +{ + return m_url; +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setURL (const KUrl &url, bool isFromURL) +{ + m_url = url; + m_isFromURL = isFromURL; +} + +//--------------------------------------------------------------------- + +// public +bool kpDocument::isFromURL (bool checkURLStillExists) const +{ + if (!m_isFromURL) + return false; + + if (!checkURLStillExists) + return true; + + return (!m_url.isEmpty () && + KIO::NetAccess::exists (m_url, KIO::NetAccess::SourceSide/*open*/, + d->environ->dialogParent ())); +} + +//--------------------------------------------------------------------- + +// public +QString kpDocument::prettyUrl () const +{ + return kpUrlFormatter::PrettyUrl (m_url); +} + +//--------------------------------------------------------------------- + +// public +QString kpDocument::prettyFilename () const +{ + return kpUrlFormatter::PrettyFilename (m_url); +} + +//--------------------------------------------------------------------- + +// public +const kpDocumentSaveOptions *kpDocument::saveOptions () const +{ + return m_saveOptions; +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setSaveOptions (const kpDocumentSaveOptions &saveOptions) +{ + *m_saveOptions = saveOptions; +} + +//--------------------------------------------------------------------- + +// public +const kpDocumentMetaInfo *kpDocument::metaInfo () const +{ + return m_metaInfo; +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setMetaInfo (const kpDocumentMetaInfo &metaInfo) +{ + *m_metaInfo = metaInfo; +} + +//--------------------------------------------------------------------- + +/* + * Properties + */ + +void kpDocument::setModified (bool yes) +{ + if (yes == m_modified) + return; + + m_modified = yes; + + if (yes) + emit documentModified (); +} + +//--------------------------------------------------------------------- + +bool kpDocument::isModified () const +{ + return m_modified; +} + +//--------------------------------------------------------------------- + +bool kpDocument::isEmpty () const +{ + return url ().isEmpty () && !isModified (); +} + +//--------------------------------------------------------------------- + +int kpDocument::constructorWidth () const +{ + return m_constructorWidth; +} + +//--------------------------------------------------------------------- + +int kpDocument::width (bool ofSelection) const +{ + if (ofSelection && m_selection) + return m_selection->width (); + else + return m_image->width (); +} + +//--------------------------------------------------------------------- + +int kpDocument::oldWidth () const +{ + return m_oldWidth; +} + +//--------------------------------------------------------------------- + +void kpDocument::setWidth (int w, const kpColor &backgroundColor) +{ + resize (w, height (), backgroundColor); +} + +//--------------------------------------------------------------------- + +int kpDocument::constructorHeight () const +{ + return m_constructorHeight; +} + +//--------------------------------------------------------------------- + +int kpDocument::height (bool ofSelection) const +{ + if (ofSelection && m_selection) + return m_selection->height (); + else + return m_image->height (); +} + +//--------------------------------------------------------------------- + +int kpDocument::oldHeight () const +{ + return m_oldHeight; +} + +//--------------------------------------------------------------------- + +void kpDocument::setHeight (int h, const kpColor &backgroundColor) +{ + resize (width (), h, backgroundColor); +} + +//--------------------------------------------------------------------- + +QRect kpDocument::rect (bool ofSelection) const +{ + if (ofSelection && m_selection) + return m_selection->boundingRect (); + else + return m_image->rect (); +} + +//--------------------------------------------------------------------- + +// public +kpImage kpDocument::getImageAt (const QRect &rect) const +{ + return kpPixmapFX::getPixmapAt (*m_image, rect); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setImageAt (const kpImage &image, const QPoint &at) +{ +#if DEBUG_KP_DOCUMENT && 0 + kDebug () << "kpDocument::setImageAt (image (w=" + << image.width () + << ",h=" << image.height () + << "), x=" << at.x () + << ",y=" << at.y () + << endl; +#endif + + kpPixmapFX::setPixmapAt (m_image, at, image); + slotContentsChanged (QRect (at.x (), at.y (), image.width (), image.height ())); +} + +//--------------------------------------------------------------------- + +// public +kpImage kpDocument::image (bool ofSelection) const +{ + kpImage ret; + + if (ofSelection) + { + kpAbstractImageSelection *imageSel = imageSelection (); + Q_ASSERT (imageSel); + + ret = imageSel->baseImage (); + } + else + ret = *m_image; + + return ret; +} + +//--------------------------------------------------------------------- + +// public +kpImage *kpDocument::imagePointer () const +{ + return m_image; +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setImage (const kpImage &image) +{ + m_oldWidth = width (), m_oldHeight = height (); + + *m_image = image; + + if (m_oldWidth == width () && m_oldHeight == height ()) + slotContentsChanged (image.rect ()); + else + slotSizeChanged (width (), height ()); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setImage (bool ofSelection, const kpImage &image) +{ + if (ofSelection) + { + kpAbstractImageSelection *imageSel = imageSelection (); + + // Have to have an image selection in order to set its pixmap. + Q_ASSERT (imageSel); + + imageSel->setBaseImage (image); + } + else + setImage (image); +} + +//--------------------------------------------------------------------- + +void kpDocument::fill (const kpColor &color) +{ +#if DEBUG_KP_DOCUMENT + kDebug () << "kpDocument::fill ()"; +#endif + + m_image->fill(color.toQRgb()); + slotContentsChanged (m_image->rect ()); +} + +//--------------------------------------------------------------------- + +void kpDocument::resize (int w, int h, const kpColor &backgroundColor) +{ +#if DEBUG_KP_DOCUMENT + kDebug () << "kpDocument::resize (" << w << "," << h << ")"; +#endif + + m_oldWidth = width (), m_oldHeight = height (); + +#if DEBUG_KP_DOCUMENT && 1 + kDebug () << "\toldWidth=" << m_oldWidth + << " oldHeight=" << m_oldHeight + << endl; +#endif + + if (w == m_oldWidth && h == m_oldHeight) + return; + + kpPixmapFX::resize (m_image, w, h, backgroundColor); + + slotSizeChanged (width (), height ()); +} + +//--------------------------------------------------------------------- + +void kpDocument::slotContentsChanged (const QRect &rect) +{ + setModified (); + emit contentsChanged (rect); +} + +//--------------------------------------------------------------------- + +void kpDocument::slotSizeChanged (int newWidth, int newHeight) +{ + setModified (); + emit sizeChanged (newWidth, newHeight); + emit sizeChanged (QSize (newWidth, newHeight)); +} + +//--------------------------------------------------------------------- + +void kpDocument::slotSizeChanged (const QSize &newSize) +{ + slotSizeChanged (newSize.width (), newSize.height ()); +} + +//--------------------------------------------------------------------- + + +#include + diff --git a/kolourpaint/document/kpDocument.h b/kolourpaint/document/kpDocument.h new file mode 100644 index 00000000..03726242 --- /dev/null +++ b/kolourpaint/document/kpDocument.h @@ -0,0 +1,362 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_DOCUMENT_H +#define KP_DOCUMENT_H + + +#include +#include +#include + +#include + +#include +#include +#undef environ + +class QImage; +class QIODevice; +class QPixmap; +class QPoint; +class QRect; +class QSize; + +class kpColor; +class kpDocumentEnvironment; +class kpDocumentSaveOptions; +class kpDocumentMetaInfo; +class kpAbstractImageSelection; +class kpAbstractSelection; +class kpTextSelection; + + +// REFACTOR: rearrange method order to make sense and reflect kpDocument_*.cpp split. +class kpDocument : public QObject +{ +Q_OBJECT + +public: + // REFACTOR: Hide constructor and have 2 factory methods: + // + // Method 1. Creates a blank document with dimensions x. + // + // Method 2. Calls open(). and (aka constructorWidth() + // and constructorHeight()) need not be specified. + // + // ? + kpDocument (int w, int h, kpDocumentEnvironment *environ); + ~kpDocument (); + + kpDocumentEnvironment *environ () const; + void setEnviron (kpDocumentEnvironment *environ); + + + // + // File I/O - Open + // + + + static QImage getPixmapFromFile (const KUrl &url, bool suppressDoesntExistDialog, + QWidget *parent, + kpDocumentSaveOptions *saveOptions = 0, + kpDocumentMetaInfo *metaInfo = 0); + // REFACTOR: fix: open*() should only be called once. + // Create a new kpDocument() if you want to open again. + void openNew (const KUrl &url); + bool open (const KUrl &url, bool newDocSameNameIfNotExist = false); + + static void getDataFromImage(const QImage &image, + kpDocumentSaveOptions &saveOptions, + kpDocumentMetaInfo &metaInfo); + + // + // File I/O - Save + // + + static bool lossyPromptContinue (const QImage &pixmap, + const kpDocumentSaveOptions &saveOptions, + QWidget *parent); + static bool savePixmapToDevice (const QImage &pixmap, + QIODevice *device, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + bool lossyPrompt, + QWidget *parent, + bool *userCancelled = 0); + static bool savePixmapToFile (const QImage &pixmap, + const KUrl &url, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + bool overwritePrompt, + bool lossyPrompt, + QWidget *parent); + bool save (bool overwritePrompt = false, bool lossyPrompt = false); + bool saveAs (const KUrl &url, + const kpDocumentSaveOptions &saveOptions, + bool overwritePrompt = true, + bool lossyPrompt = true); + + + // Returns whether save() or saveAs() have ever been called and returned true + bool savedAtLeastOnceBefore () const; + + KUrl url () const; + void setURL (const KUrl &url, bool isFromURL); + + // Returns whether the document's image was successfully opened from + // or saved to the URL returned by url(). This is not true for a + // new kpDocument and in the case of open() being passed + // "newDocSameNameIfNotExist = true" when the URL doesn't exist. + // + // If this returns true and the kpDocument hasn't been modified, + // this gives a pretty good indication that the image stored at url() + // is equal to image() (unless the something has happened to that url + // outside of KolourPaint). + // + // e.g. If the user types "kolourpaint doesnotexist.png" to start + // KolourPaint, this method will return false. + bool isFromURL (bool checkURLStillExists = true) const; + + // (will convert: empty Url --> "Untitled") + QString prettyUrl () const; + + // (will convert: empty Url --> "Untitled") + QString prettyFilename () const; + + // (guaranteed to return valid pointer) + + const kpDocumentSaveOptions *saveOptions () const; + void setSaveOptions (const kpDocumentSaveOptions &saveOptions); + + const kpDocumentMetaInfo *metaInfo () const; + void setMetaInfo (const kpDocumentMetaInfo &metaInfo); + + + /* + * Properties (modified, width, height, color depth...) + */ + + void setModified (bool yes = true); + bool isModified () const; + bool isEmpty () const; + + // REFACTOR: Rename to originalWidth()? + int constructorWidth () const; // as passed to the constructor + int width (bool ofSelection = false) const; + int oldWidth () const; // only valid in a slot connected to sizeChanged() + void setWidth (int w, const kpColor &backgroundColor); + + // REFACTOR: Rename to originalHeight()? + int constructorHeight () const; // as passed to the constructor + int height (bool ofSelection = false) const; + int oldHeight () const; // only valid in a slot connected to sizeChanged() + void setHeight (int h, const kpColor &backgroundColor); + + QRect rect (bool ofSelection = false) const; + + + // + // Image access + // + + // Returns a copy of part of the document's image (not including the + // selection). + kpImage getImageAt (const QRect &rect) const; + + void setImageAt (const kpImage &image, const QPoint &at); + + // "image(false)" returns a copy of the document's image, ignoring any + // floating selection. + // + // "image(true)" returns a copy of a floating image selection's base + // image (i.e. before selection transparency is applied), which may be + // null if the image selection is a just a border. + // + // ASSUMPTION: For == true only, an image selection exists. + kpImage image (bool ofSelection = false) const; + kpImage *imagePointer () const; + + void setImage (const kpImage &image); + // ASSUMPTION: If setting the selection's image, the selection must be + // an image selection. + void setImage (bool ofSelection, const kpImage &image); + + + // + // Selections + // + +public: + kpAbstractSelection *selection () const; + kpAbstractImageSelection *imageSelection () const; + kpTextSelection *textSelection () const; + + // Sets the document's selection to the given one and changes to the + // matching selection tool. Tool changes occur in the following situations: + // + // 1. Setting a when a selection tool is not active. + // + // 2. Setting an image when the text tool is active. + // ASSUMPTION: There is no text selection active when calling this + // method (push it onto the document before calling this, + // to avoid this problem). + // + // 3. Setting a text when an image selection tool is active. + // ASSUMPTION: There is no image selection active when calling this + // method (push it onto the document before calling this, + // to avoid this problem). + // + // The justification for the above assumptions are to reduce the complexity + // of this method's implementation -- changing from an image selection tool + // to a text selection tool, or vice-versa, calls the end() method of the + // current tool, which pushes any active selection onto the document. Since + // this method sets the selection, losing the old selection in the middle of + // the method would be tricky to work around. + // + // WARNING: Before calling this, you must ensure that the UI (kpMainWindow) + // has the 's selection transparency or + // for a text selection, its text style, selected. + // TODO: Why can't we change it for them, if we change tool automatically for them already? + void setSelection (const kpAbstractSelection &selection); + + // Returns the base image of the current image selection. If this is + // null (because the selection is still a border), it extracts the + // pixels of the document marked out by the border of the selection. + // + // ASSUMPTION: There is an imageSelection(). + // + // TODO: this always returns base image - need ver that applies selection + // transparency. + kpImage getSelectedBaseImage () const; + + // Sets the base image of the current image selection to the pixels + // of the document marked out by the border of the selection. + // + // ASSUMPTION: There is an imageSelection() that is just a border + // (no base image). + void imageSelectionPullFromDocument (const kpColor &backgroundColor); + + // Deletes the current selection, if there is a selection(), else NOP + void selectionDelete (); + + // Stamps a copy of the selection onto the document. + // + // For image selections, set to true, means that + // the transparent image of the selection is used. If set to false, + // the base image of the selection is used. This argument is ignored + // for non-image selections. + // + // ASSUMPTION: There is a selection() with content, else NOP + void selectionCopyOntoDocument (bool applySelTransparency = true); + + // Same as selectionCopyOntoDocument() but deletes the selection + // afterwards. + void selectionPushOntoDocument (bool applySelTransparency = true); + + // + // Same as image() but returns a _copy_ of the document image + // + any (even non-image) selection pasted on top. + // + // Even if the selection has no content, it is still pasted: + // + // 1. For an image selection, this makes no difference. + // + // 2. For a text selection: + // + // a) with an opaque background: the background rectangle is + // included -- this is necessary since the rectangle is visually + // there after all, and the intention of this method is to report + // everything. + // + // b) with a transparent background: this makes no difference. + // + kpImage imageWithSelection () const; + + + /* + * Transformations + * (convenience only - you could achieve the same effect (and more) with + * kpPixmapFX: these functions do not affect the selection) + */ + + void fill (const kpColor &color); + void resize (int w, int h, const kpColor &backgroundColor); + + +public slots: + // these will emit signals! + void slotContentsChanged (const QRect &rect); + void slotSizeChanged (int newWidth, int newHeight); + void slotSizeChanged (const QSize &newSize); + +signals: + void documentOpened (); + void documentSaved (); + + // Emitted whenever the isModified() flag changes from false to true. + // This is the _only_ signal that may be emitted in addition to the others. + void documentModified (); + + void contentsChanged (const QRect &rect); + void sizeChanged (int newWidth, int newHeight); // see oldWidth(), oldHeight() + void sizeChanged (const QSize &newSize); + + void selectionEnabled (bool on); + + // Emitted when setSelection() is given a selection such that we change + // from a non-text-selection tool to the text selection tool or vice-versa. + // reports whether the new selection is text (and therefore, + // whether we've switched to the text tool). + void selectionIsTextChanged (bool isText); + +private: + int m_constructorWidth, m_constructorHeight; + kpImage *m_image; + + KUrl m_url; + bool m_isFromURL; + bool m_savedAtLeastOnceBefore; + + kpDocumentSaveOptions *m_saveOptions; + kpDocumentMetaInfo *m_metaInfo; + + bool m_modified; + + kpAbstractSelection *m_selection; + + int m_oldWidth, m_oldHeight; + + // There is no need to maintain binary compatibility at this stage. + // The d-pointer is just so that you can experiment without recompiling + // the kitchen sink. + struct kpDocumentPrivate *d; +}; + + +#endif // KP_DOCUMENT_H diff --git a/kolourpaint/document/kpDocumentPrivate.h b/kolourpaint/document/kpDocumentPrivate.h new file mode 100644 index 00000000..0fb564b3 --- /dev/null +++ b/kolourpaint/document/kpDocumentPrivate.h @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDocumentPrivate_H +#define kpDocumentPrivate_H + + +class kpDocumentEnvironment; + + +struct kpDocumentPrivate +{ + kpDocumentPrivate () + : environ(0) + { + } + + kpDocumentEnvironment *environ; +}; + + +#endif // kpDocumentPrivate_H diff --git a/kolourpaint/document/kpDocumentSaveOptions.cpp b/kolourpaint/document/kpDocumentSaveOptions.cpp new file mode 100644 index 00000000..ac589554 --- /dev/null +++ b/kolourpaint/document/kpDocumentSaveOptions.cpp @@ -0,0 +1,628 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_DOCUMENT_SAVE_OPTIONS 0 + + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +//--------------------------------------------------------------------- + +class kpDocumentSaveOptionsPrivate +{ +public: + QString m_mimeType; + int m_colorDepth; + bool m_dither; + int m_quality; +}; + +//--------------------------------------------------------------------- + +kpDocumentSaveOptions::kpDocumentSaveOptions () + : d (new kpDocumentSaveOptionsPrivate ()) +{ + d->m_mimeType = invalidMimeType (); + d->m_colorDepth = invalidColorDepth (); + d->m_dither = initialDither (); + d->m_quality = invalidQuality (); +} + +//--------------------------------------------------------------------- + +kpDocumentSaveOptions::kpDocumentSaveOptions (const kpDocumentSaveOptions &rhs) + : d (new kpDocumentSaveOptionsPrivate ()) +{ + d->m_mimeType = rhs.mimeType (); + d->m_colorDepth = rhs.colorDepth (); + d->m_dither = rhs.dither (); + d->m_quality = rhs.quality (); +} + +//--------------------------------------------------------------------- + +kpDocumentSaveOptions::kpDocumentSaveOptions (QString mimeType, int colorDepth, bool dither, int quality) + : d (new kpDocumentSaveOptionsPrivate ()) +{ + d->m_mimeType = mimeType; + d->m_colorDepth = colorDepth; + d->m_dither = dither; + d->m_quality = quality; +} + +//--------------------------------------------------------------------- + +kpDocumentSaveOptions::~kpDocumentSaveOptions () +{ + delete d; +} + +//--------------------------------------------------------------------- + + +// public +bool kpDocumentSaveOptions::operator== (const kpDocumentSaveOptions &rhs) const +{ + return (mimeType () == rhs.mimeType () && + colorDepth () == rhs.colorDepth () && + dither () == rhs.dither () && + quality () == rhs.quality ()); +} + +//--------------------------------------------------------------------- + +// public +bool kpDocumentSaveOptions::operator!= (const kpDocumentSaveOptions &rhs) const +{ + return !(*this == rhs); +} + +//--------------------------------------------------------------------- + + +// public +kpDocumentSaveOptions &kpDocumentSaveOptions::operator= (const kpDocumentSaveOptions &rhs) +{ + setMimeType (rhs.mimeType ()); + setColorDepth (rhs.colorDepth ()); + setDither (rhs.dither ()); + setQuality (rhs.quality ()); + + return *this; +} + +//--------------------------------------------------------------------- + + +// public +void kpDocumentSaveOptions::printDebug (const QString &prefix) const +{ + const QString usedPrefix = !prefix.isEmpty () ? + prefix + QLatin1String (": ") : + QString(); + + kDebug () << usedPrefix + << "mimeType=" << mimeType () + << " colorDepth=" << colorDepth () + << " dither=" << dither () + << " quality=" << quality () + << endl; +} + +//--------------------------------------------------------------------- + + +// public +QString kpDocumentSaveOptions::mimeType () const +{ + return d->m_mimeType; +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentSaveOptions::setMimeType (const QString &mimeType) +{ + d->m_mimeType = mimeType; +} + +//--------------------------------------------------------------------- + + +// public static +QString kpDocumentSaveOptions::invalidMimeType () +{ + return QString(); +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocumentSaveOptions::mimeTypeIsInvalid (const QString &mimeType) +{ + return (mimeType == invalidMimeType ()); +} + +//--------------------------------------------------------------------- + +// public +bool kpDocumentSaveOptions::mimeTypeIsInvalid () const +{ + return mimeTypeIsInvalid (mimeType ()); +} + +//--------------------------------------------------------------------- + + +// public +int kpDocumentSaveOptions::colorDepth () const +{ + return d->m_colorDepth; +} + +// public +void kpDocumentSaveOptions::setColorDepth (int depth) +{ + d->m_colorDepth = depth; +} + + +// public static +int kpDocumentSaveOptions::invalidColorDepth () +{ + return -1; +} + +// public static +bool kpDocumentSaveOptions::colorDepthIsInvalid (int colorDepth) +{ + return (colorDepth != 1 && colorDepth != 8 && colorDepth != 32); +} + +// public +bool kpDocumentSaveOptions::colorDepthIsInvalid () const +{ + return colorDepthIsInvalid (colorDepth ()); +} + + +// public +bool kpDocumentSaveOptions::dither () const +{ + return d->m_dither; +} + +// public +void kpDocumentSaveOptions::setDither (bool dither) +{ + d->m_dither = dither; +} + + +// public static +int kpDocumentSaveOptions::initialDither () +{ + return false; // to avoid accidental double dithering +} + + +// public +int kpDocumentSaveOptions::quality () const +{ + return d->m_quality; +} + +// public +void kpDocumentSaveOptions::setQuality (int quality) +{ + d->m_quality = quality; +} + + +// public static +int kpDocumentSaveOptions::invalidQuality () +{ + return -2; +} + +// public static +bool kpDocumentSaveOptions::qualityIsInvalid (int quality) +{ + return (quality < -1 || quality > 100); +} + +// public +bool kpDocumentSaveOptions::qualityIsInvalid () const +{ + return qualityIsInvalid (quality ()); +} + + +// public static +QString kpDocumentSaveOptions::defaultMimeType (const KConfigGroup &config) +{ + return config.readEntry (kpSettingForcedMimeType, + QString::fromLatin1 ("image/png")); +} + +// public static +void kpDocumentSaveOptions::saveDefaultMimeType (KConfigGroup &config, + const QString &mimeType) +{ + config.writeEntry (kpSettingForcedMimeType, mimeType); +} + + +// public static +int kpDocumentSaveOptions::defaultColorDepth (const KConfigGroup &config) +{ + int colorDepth = + config.readEntry (kpSettingForcedColorDepth, -1); + + if (colorDepthIsInvalid (colorDepth)) + { + // (not screen depth, in case of transparency) + colorDepth = 32; + } + + return colorDepth; +} + +//--------------------------------------------------------------------- + +// public static +void kpDocumentSaveOptions::saveDefaultColorDepth (KConfigGroup &config, int colorDepth) +{ + config.writeEntry (kpSettingForcedColorDepth, colorDepth); +} + +//--------------------------------------------------------------------- + + +// public static +int kpDocumentSaveOptions::defaultDither (const KConfigGroup &config) +{ + return config.readEntry (kpSettingForcedDither, initialDither ()); +} + +//--------------------------------------------------------------------- + +// public static +void kpDocumentSaveOptions::saveDefaultDither (KConfigGroup &config, bool dither) +{ + config.writeEntry (kpSettingForcedDither, dither); +} + +//--------------------------------------------------------------------- + + +// public static +int kpDocumentSaveOptions::defaultQuality (const KConfigGroup &config) +{ + int val = config.readEntry (kpSettingForcedQuality, -1); + if (qualityIsInvalid (val)) + val = -1; + + return val; +} + +//--------------------------------------------------------------------- + +// public static +void kpDocumentSaveOptions::saveDefaultQuality (KConfigGroup &config, int quality) +{ + config.writeEntry (kpSettingForcedQuality, quality); +} + +//--------------------------------------------------------------------- + + +// public static +kpDocumentSaveOptions kpDocumentSaveOptions::defaultDocumentSaveOptions (const KConfigGroup &config) +{ + kpDocumentSaveOptions saveOptions; + saveOptions.setMimeType (defaultMimeType (config)); + saveOptions.setColorDepth (defaultColorDepth (config)); + saveOptions.setDither (defaultDither (config)); + saveOptions.setQuality (defaultQuality (config)); + +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS + saveOptions.printDebug ("kpDocumentSaveOptions::defaultDocumentSaveOptions()"); +#endif + + return saveOptions; +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocumentSaveOptions::saveDefaultDifferences (KConfigGroup &config, + const kpDocumentSaveOptions &oldDocInfo, + const kpDocumentSaveOptions &newDocInfo) +{ + bool savedSomething = false; + +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS + kDebug () << "kpDocumentSaveOptions::saveDefaultDifferences()"; + oldDocInfo.printDebug ("\told"); + newDocInfo.printDebug ("\tnew"); +#endif + + if (newDocInfo.mimeType () != oldDocInfo.mimeType ()) + { + saveDefaultMimeType (config, newDocInfo.mimeType ()); + savedSomething = true; + } + + if (newDocInfo.colorDepth () != oldDocInfo.colorDepth ()) + { + saveDefaultColorDepth (config, newDocInfo.colorDepth ()); + savedSomething = true; + } + + if (newDocInfo.dither () != oldDocInfo.dither ()) + { + saveDefaultDither (config, newDocInfo.dither ()); + savedSomething = true; + } + + if (newDocInfo.quality () != oldDocInfo.quality ()) + { + saveDefaultQuality (config, newDocInfo.quality ()); + savedSomething = true; + } + + return savedSomething; +} + +//--------------------------------------------------------------------- + + +static QStringList mimeTypesSupportingProperty (const QString &property, + const QStringList &defaultMimeTypesWithPropertyList) +{ + QStringList mimeTypeList; + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupMimeTypeProperties); + + if (cfg.hasKey (property)) + { + mimeTypeList = cfg.readEntry (property, QStringList ()); + } + else + { + mimeTypeList = defaultMimeTypesWithPropertyList; + + cfg.writeEntry (property, mimeTypeList); + cfg.sync (); + } + + return mimeTypeList; +} + +//--------------------------------------------------------------------- + +static bool mimeTypeSupportsProperty (const QString &mimeType, + const QString &property, const QStringList &defaultMimeTypesWithPropertyList) +{ + const QStringList mimeTypeList = mimeTypesSupportingProperty ( + property, defaultMimeTypesWithPropertyList); + + return mimeTypeList.contains (mimeType); +} + +//--------------------------------------------------------------------- + + +// SYNC: update mime info +// +// Only care about writable mimetypes. +// +// Run: +// +// branches/kolourpaint/control/scripts/gen_mimetype_line.sh Write | +// branches/kolourpaint/control/scripts/split_mimetype_line.pl +// +// in the version of kdelibs/kimgio/ (e.g. KDE 4.0) KolourPaint is shipped with, +// to check for any new mimetypes to add info for. In the methods below, +// you can specify this info (maximum color depth, whether it's lossy etc.). +// +// Update the below list and if you do change any of that info, bump up +// "kpSettingsGroupMimeTypeProperties" in kpDefs.h. +// +// Currently, Depth and Quality settings are mutually exclusive with +// Depth overriding Quality. I've currently favoured Quality with the +// below mimetypes (i.e. all lossy mimetypes are only given Quality settings, +// no Depth settings). +// +// Mimetypes done: +// image/bmp +// image/jpeg +// image/jp2 +// image/png +// image/tiff +// image/x-eps +// image/x-pcx +// image/x-portable-bitmap +// image/x-portable-graymap +// image/x-portable-pixmap +// image/x-rgb +// image/x-tga +// image/x-xbitmap +// image/x-xpixmap +// video/x-mng [COULD NOT TEST] +// +// To test whether depth is configurable, write an image in the new +// mimetype with all depths and read each one back. See what +// kpDocument thinks the depth is when it gets QImage to read it. + + +// public static +int kpDocumentSaveOptions::mimeTypeMaximumColorDepth (const QString &mimeType) +{ + QStringList defaultList; + + // SYNC: update mime info here + + // Grayscale actually (unenforced since depth not set to configurable) + defaultList << QLatin1String ("image/x-eps:32"); + + defaultList << QLatin1String ("image/x-portable-bitmap:1"); + + // Grayscale actually (unenforced since depth not set to configurable) + defaultList << QLatin1String ("image/x-portable-graymap:8"); + + defaultList << QLatin1String ("image/x-xbitmap:1"); + + const QStringList mimeTypeList = mimeTypesSupportingProperty ( + kpSettingMimeTypeMaximumColorDepth, defaultList); + + const QString mimeTypeColon = mimeType + QLatin1String (":"); + for (QStringList::const_iterator it = mimeTypeList.begin (); + it != mimeTypeList.end (); + ++it) + { + if ((*it).startsWith (mimeTypeColon)) + { + int number = (*it).mid (mimeTypeColon.length ()).toInt (); + if (!colorDepthIsInvalid (number)) + { + return number; + } + } + } + + return 32; +} + +//--------------------------------------------------------------------- + +// public +int kpDocumentSaveOptions::mimeTypeMaximumColorDepth () const +{ + return mimeTypeMaximumColorDepth (mimeType ()); +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth (const QString &mimeType) +{ + QStringList defaultMimeTypes; + + // SYNC: update mime info here + defaultMimeTypes << QLatin1String ("image/png"); + defaultMimeTypes << QLatin1String ("image/bmp"); + defaultMimeTypes << QLatin1String ("image/x-pcx"); + + // TODO: Only 1, 24 not 8; Qt only sees 32 but "file" cmd realises + // it's either 1 or 24. + defaultMimeTypes << QLatin1String ("image/x-rgb"); + + // TODO: Only 8 and 24 - no 1. + defaultMimeTypes << QLatin1String ("image/x-xpixmap"); + + return mimeTypeSupportsProperty (mimeType, + kpSettingMimeTypeHasConfigurableColorDepth, + defaultMimeTypes); +} + +//--------------------------------------------------------------------- + +// public +bool kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth () const +{ + return mimeTypeHasConfigurableColorDepth (mimeType ()); +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocumentSaveOptions::mimeTypeHasConfigurableQuality (const QString &mimeType) +{ + QStringList defaultMimeTypes; + + // SYNC: update mime info here + defaultMimeTypes << QLatin1String ("image/jp2"); + defaultMimeTypes << QLatin1String ("image/jpeg"); + defaultMimeTypes << QLatin1String ("image/x-webp"); + + return mimeTypeSupportsProperty (mimeType, + kpSettingMimeTypeHasConfigurableQuality, + defaultMimeTypes); +} + +//--------------------------------------------------------------------- + +// public +bool kpDocumentSaveOptions::mimeTypeHasConfigurableQuality () const +{ + return mimeTypeHasConfigurableQuality (mimeType ()); +} + +//--------------------------------------------------------------------- + +// public +int kpDocumentSaveOptions::isLossyForSaving (const QImage &image) const +{ + int ret = 0; + + if (mimeTypeMaximumColorDepth () < image.depth ()) + { + ret |= MimeTypeMaximumColorDepthLow; + } + + if (mimeTypeHasConfigurableColorDepth () && + !colorDepthIsInvalid () /*REFACTOR: guarantee it is valid*/ && + ((colorDepth () < image.depth ()) || + (colorDepth () < 32 && image.hasAlphaChannel()))) + { + ret |= ColorDepthLow; + } + + if (mimeTypeHasConfigurableQuality () && + !qualityIsInvalid ()) + { + ret |= Quality; + } + + return ret; +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/document/kpDocumentSaveOptions.h b/kolourpaint/document/kpDocumentSaveOptions.h new file mode 100644 index 00000000..ef3d7999 --- /dev/null +++ b/kolourpaint/document/kpDocumentSaveOptions.h @@ -0,0 +1,150 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_DOCUMENT_SAVE_OPTIONS_H +#define KP_DOCUMENT_SAVE_OPTIONS_H + + +class QImage; +class QString; + +class KConfigGroup; + + +class kpDocumentSaveOptions +{ +public: + kpDocumentSaveOptions (); + kpDocumentSaveOptions (const kpDocumentSaveOptions &rhs); + kpDocumentSaveOptions (QString mimeType, int colorDepth, bool dither, int quality); + virtual ~kpDocumentSaveOptions (); + + bool operator== (const kpDocumentSaveOptions &rhs) const; + bool operator!= (const kpDocumentSaveOptions &rhs) const; + + kpDocumentSaveOptions &operator= (const kpDocumentSaveOptions &rhs); + + + void printDebug (const QString &prefix) const; + + + QString mimeType () const; + void setMimeType (const QString &mimeType); + + static QString invalidMimeType (); + static bool mimeTypeIsInvalid (const QString &mimeType); + bool mimeTypeIsInvalid () const; + + + int colorDepth () const; + void setColorDepth (int depth); + + static int invalidColorDepth (); + static bool colorDepthIsInvalid (int colorDepth); + bool colorDepthIsInvalid () const; + + + bool dither () const; + void setDither (bool dither); + + static int initialDither (); + + + int quality () const; + void setQuality (int quality); + + static int invalidQuality (); + static bool qualityIsInvalid (int quality); + bool qualityIsInvalid () const; + + + // (All assume that 's group has been set) + // (None of them call KConfigBase::reparseConfig() nor KConfigBase::sync()) + + static QString defaultMimeType (const KConfigGroup &config); + static void saveDefaultMimeType (KConfigGroup &config, const QString &mimeType); + + static int defaultColorDepth (const KConfigGroup &config); + static void saveDefaultColorDepth (KConfigGroup &config, int colorDepth); + + static int defaultDither (const KConfigGroup &config); + static void saveDefaultDither (KConfigGroup &config, bool dither); + + static int defaultQuality (const KConfigGroup &config); + static void saveDefaultQuality (KConfigGroup &config, int quality); + + + static kpDocumentSaveOptions defaultDocumentSaveOptions (const KConfigGroup &config); + // (returns true if it encountered a difference (and saved it to )) + static bool saveDefaultDifferences (KConfigGroup &config, + const kpDocumentSaveOptions &oldDocInfo, + const kpDocumentSaveOptions &newDocInfo); + + +public: + // (purely for informational purposes - not enforced by this class) + static int mimeTypeMaximumColorDepth (const QString &mimeType); + int mimeTypeMaximumColorDepth () const; + + + static bool mimeTypeHasConfigurableColorDepth (const QString &mimeType); + bool mimeTypeHasConfigurableColorDepth () const; + + static bool mimeTypeHasConfigurableQuality (const QString &mimeType); + bool mimeTypeHasConfigurableQuality () const; + + + // TODO: checking for mask loss due to format e.g. BMP + enum LossyType + { + LossLess = 0, + + // mimeTypeMaximumColorDepth() < .depth() + MimeTypeMaximumColorDepthLow = 1, + // i.e. colorDepth() < .depth() || + // colorDepth() < 32 && .mask() + ColorDepthLow = 2, + // i.e. mimeTypeHasConfigurableQuality() + Quality = 4 + }; + + // Returns whether saving with these options will result in + // loss of information. Returned value is the bitwise OR of + // LossType enum possiblities. + int isLossyForSaving (const QImage &image) const; + + +private: + // There is no need to maintain binary compatibility at this stage. + // The d-pointer is just so that you can experiment without recompiling + // the kitchen sink. + class kpDocumentSaveOptionsPrivate *d; +}; + + +#endif // KP_DOCUMENT_SAVE_OPTIONS_H diff --git a/kolourpaint/document/kpDocument_Open.cpp b/kolourpaint/document/kpDocument_Open.cpp new file mode 100644 index 00000000..1ad78a8c --- /dev/null +++ b/kolourpaint/document/kpDocument_Open.cpp @@ -0,0 +1,269 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DOCUMENT 0 + + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include // TODO: isn't this in KIO? +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +void kpDocument::getDataFromImage(const QImage &image, + kpDocumentSaveOptions &saveOptions, + kpDocumentMetaInfo &metaInfo) +{ + saveOptions.setColorDepth(image.depth()); + saveOptions.setDither(false); // avoid double dithering when saving + + metaInfo.setDotsPerMeterX(image.dotsPerMeterX()); + metaInfo.setDotsPerMeterY(image.dotsPerMeterY()); + metaInfo.setOffset(image.offset()); + + QStringList keys = image.textKeys(); + for (int i = 0; i < keys.count(); i++) + metaInfo.setText(keys[i], image.text(keys[i])); +} + +//--------------------------------------------------------------------- + +// public static +QImage kpDocument::getPixmapFromFile(const KUrl &url, bool suppressDoesntExistDialog, + QWidget *parent, + kpDocumentSaveOptions *saveOptions, + kpDocumentMetaInfo *metaInfo) +{ +#if DEBUG_KP_DOCUMENT + kDebug () << "kpDocument::getPixmapFromFile(" << url << "," << parent << ")"; +#endif + + if (saveOptions) + *saveOptions = kpDocumentSaveOptions (); + + if (metaInfo) + *metaInfo = kpDocumentMetaInfo (); + + QString tempFile; + if (url.isEmpty () || !KIO::NetAccess::download (url, tempFile, parent)) + { + if (!suppressDoesntExistDialog) + { + // TODO: Use "Cannot" instead of "Could not" in all dialogs in KolourPaint. + // Or at least choose one consistently. + // + // TODO: Have captions for all dialogs in KolourPaint. + KMessageBox::sorry (parent, + i18n ("Could not open \"%1\".", + kpUrlFormatter::PrettyFilename (url))); + } + + return QImage (); + } + + QImage image; + + // sync: remember to "KIO::NetAccess::removeTempFile (tempFile)" in all exit paths + { + QString detectedMimeType; + + KMimeType::Ptr detectedMimeTypePtr = KMimeType::findByUrl (url); + if(detectedMimeTypePtr && + detectedMimeTypePtr != KMimeType::defaultMimeTypePtr ()) + { + detectedMimeType = detectedMimeTypePtr->name (); + } else { + detectedMimeTypePtr = KMimeType::findByPath (tempFile); + if(detectedMimeTypePtr && + detectedMimeTypePtr != KMimeType::defaultMimeTypePtr ()) + { + detectedMimeType = detectedMimeTypePtr->name (); + } + } + + if (saveOptions) + saveOptions->setMimeType (detectedMimeType); + + #if DEBUG_KP_DOCUMENT + kDebug () << "\ttempFile=" << tempFile; + kDebug () << "\tmimetype=" << detectedMimeType; + kDebug () << "\tsrc=" << url.path (); + #endif + + if (detectedMimeType.isEmpty ()) + { + KMessageBox::sorry (parent, + i18n ("Could not open \"%1\" - unknown mimetype.", + kpUrlFormatter::PrettyFilename (url))); + KIO::NetAccess::removeTempFile (tempFile); + return QImage (); + } + + + // TODO: might be different. + // Should we feed it into QImage to solve this problem? + // + // If so, should we have used KMimeType::findByContent() + // instead? Are some image types not detectable by findByContent() + // (e.g. image types that are only detected by extension)? + // + // Currently, opening a PNG with a ".jpg" extension does not + // work -- QImage and findByUrl() both think it's a JPG based + // on the extension, but findByContent() correctly detects + // it as a PNG. + image = QImage (tempFile); + KIO::NetAccess::removeTempFile (tempFile); + } + + if (image.isNull ()) + { + KMessageBox::sorry (parent, + i18n ("Could not open \"%1\" - unsupported image format.\n" + "The file may be corrupt.", + kpUrlFormatter::PrettyFilename (url))); + return QImage (); + } + +#if DEBUG_KP_DOCUMENT + kDebug () << "\tpixmap: depth=" << image.depth () + << " hasAlphaChannel=" << image.hasAlphaChannel () + << endl; +#endif + + if ( saveOptions && metaInfo ) + getDataFromImage(image, *saveOptions, *metaInfo); + + // make sure we always have Format_ARGB32_Premultiplied as this is the fastest to draw on + // and Qt can not draw onto Format_Indexed8 (Qt-4.7) + if ( image.format() != QImage::Format_ARGB32_Premultiplied ) + image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + + return image; +} + +//--------------------------------------------------------------------- + +void kpDocument::openNew (const KUrl &url) +{ +#if DEBUG_KP_DOCUMENT + kDebug () << "kpDocument::openNew (" << url << ")"; +#endif + + m_image->fill(QColor(Qt::white).rgb()); + + setURL (url, false/*not from url*/); + // TODO: Maybe we should guess the mimetype from "url"'s filename + // extension. + // + // That way "kolourpaint doesnotexist.bmp" would automatically + // select the BMP file format when the save dialog comes up for + // the first time. + *m_saveOptions = kpDocumentSaveOptions (); + *m_metaInfo = kpDocumentMetaInfo (); + m_modified = false; + + emit documentOpened (); +} + +//--------------------------------------------------------------------- + +bool kpDocument::open (const KUrl &url, bool newDocSameNameIfNotExist) +{ +#if DEBUG_KP_DOCUMENT + kDebug () << "kpDocument::open (" << url << ")"; +#endif + + kpDocumentSaveOptions newSaveOptions; + kpDocumentMetaInfo newMetaInfo; + QImage newPixmap = kpDocument::getPixmapFromFile (url, + newDocSameNameIfNotExist/*suppress "doesn't exist" dialog*/, + d->environ->dialogParent (), + &newSaveOptions, + &newMetaInfo); + + if (!newPixmap.isNull ()) + { + delete m_image; + m_image = new kpImage (newPixmap); + + setURL (url, true/*is from url*/); + *m_saveOptions = newSaveOptions; + *m_metaInfo = newMetaInfo; + m_modified = false; + + emit documentOpened (); + return true; + } + + if (newDocSameNameIfNotExist) + { + if (!url.isEmpty () && + // not just a permission error? + !KIO::NetAccess::exists (url, KIO::NetAccess::SourceSide/*open*/, d->environ->dialogParent ())) + { + openNew (url); + } + else + { + openNew (KUrl ()); + } + + return true; + } + else + { + return false; + } +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/document/kpDocument_Save.cpp b/kolourpaint/document/kpDocument_Save.cpp new file mode 100644 index 00000000..5f441660 --- /dev/null +++ b/kolourpaint/document/kpDocument_Save.cpp @@ -0,0 +1,514 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DOCUMENT 0 + + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include // TODO: isn't this in KIO? +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +bool kpDocument::save (bool overwritePrompt, bool lossyPrompt) +{ +#if DEBUG_KP_DOCUMENT + kDebug () << "kpDocument::save(" + << "overwritePrompt=" << overwritePrompt + << ",lossyPrompt=" << lossyPrompt + << ") url=" << m_url + << " savedAtLeastOnceBefore=" << savedAtLeastOnceBefore () + << endl; +#endif + + // TODO: check feels weak + if (m_url.isEmpty () || m_saveOptions->mimeType ().isEmpty ()) + { + KMessageBox::detailedError (d->environ->dialogParent (), + i18n ("Could not save image - insufficient information."), + i18n ("URL: %1\n" + "Mimetype: %2", + prettyUrl (), + m_saveOptions->mimeType ().isEmpty () ? + i18n ("") : + m_saveOptions->mimeType ()), + i18nc ("@title:window", "Internal Error")); + return false; + } + + return saveAs (m_url, *m_saveOptions, + overwritePrompt, + lossyPrompt); +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocument::lossyPromptContinue (const QImage &pixmap, + const kpDocumentSaveOptions &saveOptions, + QWidget *parent) +{ +#if DEBUG_KP_DOCUMENT + kDebug () << "kpDocument::lossyPromptContinue()"; +#endif + +#define QUIT_IF_CANCEL(messageBoxCommand) \ +{ \ + if (messageBoxCommand != KMessageBox::Continue) \ + { \ + return false; \ + } \ +} + + const int lossyType = saveOptions.isLossyForSaving (pixmap); + if (lossyType & (kpDocumentSaveOptions::MimeTypeMaximumColorDepthLow | + kpDocumentSaveOptions::Quality)) + { + QUIT_IF_CANCEL ( + KMessageBox::warningContinueCancel (parent, + i18n ("

The %1 format may not be able" + " to preserve all of the image's color information.

" + + "

Are you sure you want to save in this format?

", + KMimeType::mimeType (saveOptions.mimeType ())->comment ()), + // TODO: caption misleading for lossless formats that have + // low maximum colour depth + i18nc ("@title:window", "Lossy File Format"), + KStandardGuiItem::save (), + KStandardGuiItem::cancel(), + QLatin1String ("SaveInLossyMimeTypeDontAskAgain"))); + } + else if (lossyType & kpDocumentSaveOptions::ColorDepthLow) + { + QUIT_IF_CANCEL ( + KMessageBox::warningContinueCancel (parent, + i18n ("

Saving the image at the low color depth of %1-bit" + " may result in the loss of color information." + + // TODO: It looks like 8-bit QImage's now support alpha. + // Update kpDocumentSaveOptions::isLossyForSaving() + // and change "might" to "will". + " Any transparency might also be removed.

" + + "

Are you sure you want to save at this color depth?

", + saveOptions.colorDepth ()), + i18nc ("@title:window", "Low Color Depth"), + KStandardGuiItem::save (), + KStandardGuiItem::cancel(), + QLatin1String ("SaveAtLowColorDepthDontAskAgain"))); + } +#undef QUIT_IF_CANCEL + + return true; +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocument::savePixmapToDevice (const QImage &image, + QIODevice *device, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + bool lossyPrompt, + QWidget *parent, + bool *userCancelled) +{ + if (userCancelled) + *userCancelled = false; + + QStringList types = KImageIO::typeForMime (saveOptions.mimeType ()); +#if DEBUG_KP_DOCUMENT + kDebug () << "\ttypes=" << types; +#endif + if (types.isEmpty ()) + return false; + + // It's safe to arbitrarily choose the 0th type as any type in the list + // should invoke the same KImageIO image loader. + const QString type = types [0]; + +#if DEBUG_KP_DOCUMENT + kDebug () << "\tmimeType=" << saveOptions.mimeType () + << " type=" << type << endl; +#endif + + if (lossyPrompt && !lossyPromptContinue (image, saveOptions, parent)) + { + if (userCancelled) + *userCancelled = true; + + #if DEBUG_KP_DOCUMENT + kDebug () << "\treturning false because of lossyPrompt"; + #endif + return false; + } + + + // TODO: fix dup with kpDocumentSaveOptions::isLossyForSaving() + const bool useSaveOptionsColorDepth = + (saveOptions.mimeTypeHasConfigurableColorDepth () && + !saveOptions.colorDepthIsInvalid ()); + const bool useSaveOptionsQuality = + (saveOptions.mimeTypeHasConfigurableQuality () && + !saveOptions.qualityIsInvalid ()); + + + // + // Reduce colors if required + // + +#if DEBUG_KP_DOCUMENT + kDebug () << "\tuseSaveOptionsColorDepth=" << useSaveOptionsColorDepth + << "current image depth=" << image.depth () + << "save options depth=" << saveOptions.colorDepth (); +#endif + QImage imageToSave(image); + + if (useSaveOptionsColorDepth && + imageToSave.depth () != saveOptions.colorDepth ()) + { + // TODO: I think this erases the mask! + // + // I suspect this doesn't matter since this is only called to + // reduce color depth and QImage's with depth < 32 don't + // support masks anyway. + // + // Later: I think the mask is preserved for 8-bit since Qt4 + // seems to support it for QImage. + imageToSave = kpEffectReduceColors::convertImageDepth (imageToSave, + saveOptions.colorDepth (), + saveOptions.dither ()); + } + + + // + // Write Meta Info + // + + imageToSave.setDotsPerMeterX (metaInfo.dotsPerMeterX ()); + imageToSave.setDotsPerMeterY (metaInfo.dotsPerMeterY ()); + imageToSave.setOffset (metaInfo.offset ()); + + QList keyList = metaInfo.textKeys (); + for (QList ::const_iterator it = keyList.constBegin (); + it != keyList.constEnd (); + ++it) + { + imageToSave.setText (*it, metaInfo.text (*it)); + } + + + // + // Save at required quality + // + + int quality = -1; // default + + if (useSaveOptionsQuality) + quality = saveOptions.quality (); + +#if DEBUG_KP_DOCUMENT + kDebug () << "\tsaving"; +#endif + if (!imageToSave.save (device, type.toLatin1 (), quality)) + { + #if DEBUG_KP_DOCUMENT + kDebug () << "\tQImage::save() returned false"; + #endif + return false; + } + + +#if DEBUG_KP_DOCUMENT + kDebug () << "\tsave OK"; +#endif + return true; +} + +//--------------------------------------------------------------------- + +static void CouldNotCreateTemporaryFileDialog (QWidget *parent) +{ + KMessageBox::error (parent, + i18n ("Could not save image - unable to create temporary file.")); +} + +//--------------------------------------------------------------------- + +static void CouldNotSaveDialog (const KUrl &url, QWidget *parent) +{ + // TODO: use file.errorString() + KMessageBox::error (parent, + i18n ("Could not save as \"%1\".", + kpUrlFormatter::PrettyFilename (url))); +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocument::savePixmapToFile (const QImage &pixmap, + const KUrl &url, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + bool overwritePrompt, + bool lossyPrompt, + QWidget *parent) +{ + // TODO: Use KIO::NetAccess:mostLocalURL() for accessing home:/ (and other + // such local URLs) for efficiency and because only local writes + // are atomic. +#if DEBUG_KP_DOCUMENT + kDebug () << "kpDocument::savePixmapToFile (" + << url + << ",overwritePrompt=" << overwritePrompt + << ",lossyPrompt=" << lossyPrompt + << ")" << endl; + saveOptions.printDebug (QLatin1String ("\tsaveOptions")); + metaInfo.printDebug (QLatin1String ("\tmetaInfo")); +#endif + + if (overwritePrompt && + KIO::NetAccess::exists (url, KIO::NetAccess::DestinationSide/*write*/, parent)) + { + int result = KMessageBox::warningContinueCancel (parent, + i18n ("A document called \"%1\" already exists.\n" + "Do you want to overwrite it?", + kpUrlFormatter::PrettyFilename (url)), + QString(), + KGuiItem(i18n ("Overwrite"))); + + if (result != KMessageBox::Continue) + { + #if DEBUG_KP_DOCUMENT + kDebug () << "\tuser doesn't want to overwrite"; + #endif + + return false; + } + } + + + if (lossyPrompt && !lossyPromptContinue (pixmap, saveOptions, parent)) + { + #if DEBUG_KP_DOCUMENT + kDebug () << "\treturning false because of lossyPrompt"; + #endif + return false; + } + + + // Local file? + if (url.isLocalFile ()) + { + const QString filename = url.toLocalFile (); + + // sync: All failure exit paths _must_ call KSaveFile::abort() or + // else, the KSaveFile destructor will overwrite the file, + // , despite the failure. + KSaveFile atomicFileWriter (filename); + { + if (!atomicFileWriter.open ()) + { + // We probably don't need this as has not been + // opened. + atomicFileWriter.abort (); + + #if DEBUG_KP_DOCUMENT + kDebug () << "\treturning false because could not open KSaveFile" + << " error=" << atomicFileWriter.error () << endl; + #endif + ::CouldNotCreateTemporaryFileDialog (parent); + return false; + } + + // Write to local temporary file. + if (!savePixmapToDevice (pixmap, &atomicFileWriter, + saveOptions, metaInfo, + false/*no lossy prompt*/, + parent)) + { + atomicFileWriter.abort (); + + #if DEBUG_KP_DOCUMENT + kDebug () << "\treturning false because could not save pixmap to device" + << endl; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + + // Atomically overwrite local file with the temporary file + // we saved to. + if (!atomicFileWriter.finalize ()) + { + atomicFileWriter.abort (); + + #if DEBUG_KP_DOCUMENT + kDebug () << "\tcould not close KSaveFile"; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + } // sync KSaveFile.abort() + } + // Remote file? + else + { + // Create temporary file that is deleted when the variable goes + // out of scope. + KTemporaryFile tempFile; + if (!tempFile.open ()) + { + #if DEBUG_KP_DOCUMENT + kDebug () << "\treturning false because could not open tempFile"; + #endif + ::CouldNotCreateTemporaryFileDialog (parent); + return false; + } + + // Write to local temporary file. + if (!savePixmapToDevice (pixmap, &tempFile, + saveOptions, metaInfo, + false/*no lossy prompt*/, + parent)) + { + #if DEBUG_KP_DOCUMENT + kDebug () << "\treturning false because could not save pixmap to device" + << endl; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + + // Collect name of temporary file now, as QTemporaryFile::fileName() + // stops working after close() is called. + const QString tempFileName = tempFile.fileName (); + #if DEBUG_KP_DOCUMENT + kDebug () << "\ttempFileName='" << tempFileName << "'"; + #endif + Q_ASSERT (!tempFileName.isEmpty ()); + + tempFile.close (); + if (tempFile.error () != QFile::NoError) + { + #if DEBUG_KP_DOCUMENT + kDebug () << "\treturning false because could not close"; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + + // Copy local temporary file to overwrite remote. + // TODO: No one seems to know how to do this atomically + // [http://lists.kde.org/?l=kde-core-devel&m=117845162728484&w=2]. + // At least, fish:// (ssh) is definitely not atomic. + if (!KIO::NetAccess::upload (tempFileName, url, parent)) + { + #if DEBUG_KP_DOCUMENT + kDebug () << "\treturning false because could not upload"; + #endif + KMessageBox::error (parent, + i18n ("Could not save image - failed to upload.")); + return false; + } + } + + + return true; +} + +//--------------------------------------------------------------------- + +bool kpDocument::saveAs (const KUrl &url, + const kpDocumentSaveOptions &saveOptions, + bool overwritePrompt, + bool lossyPrompt) +{ +#if DEBUG_KP_DOCUMENT + kDebug () << "kpDocument::saveAs (" << url << "," + << saveOptions.mimeType () << ")" << endl; +#endif + + if (kpDocument::savePixmapToFile (imageWithSelection (), + url, + saveOptions, *metaInfo (), + overwritePrompt, + lossyPrompt, + d->environ->dialogParent ())) + { + setURL (url, true/*is from url*/); + *m_saveOptions = saveOptions; + m_modified = false; + + m_savedAtLeastOnceBefore = true; + + emit documentSaved (); + return true; + } + else + { + return false; + } +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/document/kpDocument_Selection.cpp b/kolourpaint/document/kpDocument_Selection.cpp new file mode 100644 index 00000000..ccd15214 --- /dev/null +++ b/kolourpaint/document/kpDocument_Selection.cpp @@ -0,0 +1,342 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DOCUMENT 0 + + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +// public +kpAbstractSelection *kpDocument::selection () const +{ + return m_selection; +} + +//--------------------------------------------------------------------- + +// public +kpAbstractImageSelection *kpDocument::imageSelection () const +{ + return dynamic_cast (m_selection); +} + +//--------------------------------------------------------------------- + +// public +kpTextSelection *kpDocument::textSelection () const +{ + return dynamic_cast (m_selection); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setSelection (const kpAbstractSelection &selection) +{ +#if DEBUG_KP_DOCUMENT && 1 + kDebug () << "kpDocument::setSelection() sel boundingRect=" + << selection.boundingRect () + << endl; +#endif + + d->environ->setQueueViewUpdates (); + { + const bool hadSelection = (bool) m_selection; + kpAbstractSelection *oldSelection = m_selection; + + + // (must be called before giving the document a new selection, to + // avoid a potential mess where switchToCompatibleTool() ends + // the current selection tool, killing the new selection) + bool isTextChanged = false; + d->environ->switchToCompatibleTool (selection, &isTextChanged); + Q_ASSERT (m_selection == oldSelection); + + + m_selection = selection.clone (); + + // There's no need to uninitialize the old selection + // (e.g. call disconnect()) since we delete it later. + connect (m_selection, SIGNAL (changed (const QRect &)), + this, SLOT (slotContentsChanged (const QRect &))); + + + // + // Now all kpDocument state has been set. + // We can _only_ change the environment after that, as the environment + // may access the document. Exception is above with + // switchToCompatibleTool(). + // + + d->environ->assertMatchingUIState (selection); + + + // + // Now all kpDocument and environment state has been set. + // We can _only_ fire signals after that, as the signal receivers (the + // "wider environment") may access the document and the environment. + // + + #if DEBUG_KP_DOCUMENT && 1 + kDebug () << "\tcheck sel " << (int *) m_selection + << " boundingRect=" << m_selection->boundingRect () + << endl; + #endif + if (oldSelection) + { + if (oldSelection->hasContent ()) + slotContentsChanged (oldSelection->boundingRect ()); + else + emit contentsChanged (oldSelection->boundingRect ()); + + delete oldSelection; + oldSelection = 0; + } + + if (m_selection->hasContent ()) + slotContentsChanged (m_selection->boundingRect ()); + else + emit contentsChanged (m_selection->boundingRect ()); + + + if (!hadSelection) + emit selectionEnabled (true); + + if (isTextChanged) + emit selectionIsTextChanged (textSelection ()); + } + d->environ->restoreQueueViewUpdates (); + +#if DEBUG_KP_DOCUMENT && 1 + kDebug () << "\tkpDocument::setSelection() ended"; +#endif +} + +//--------------------------------------------------------------------- + +// public +kpImage kpDocument::getSelectedBaseImage () const +{ + kpAbstractImageSelection *imageSel = imageSelection (); + Q_ASSERT (imageSel); + + // Easy if we already have it :) + const kpImage image = imageSel->baseImage (); + if (!image.isNull ()) + return image; + + + const QRect boundingRect = imageSel->boundingRect (); + Q_ASSERT (boundingRect.isValid ()); + + // OPT: This is very slow. Image / More Effects ... calls us twice + // unnecessarily. + return imageSel->givenImageMaskedByShape (getImageAt (boundingRect)); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::imageSelectionPullFromDocument (const kpColor &backgroundColor) +{ + kpAbstractImageSelection *imageSel = imageSelection (); + Q_ASSERT (imageSel); + + // Should not already have an image or we would not be pulling. + Q_ASSERT (!imageSel->hasContent ()); + + const QRect boundingRect = imageSel->boundingRect (); + Q_ASSERT (boundingRect.isValid ()); + + // + // Get selection image from document + // + + kpImage selectedImage = getSelectedBaseImage (); + + d->environ->setQueueViewUpdates (); + + imageSel->setBaseImage (selectedImage); + + // + // Fill opaque bits of the hole in the document + // + +#if !defined (QT_NO_DEBUG) && !defined (NDEBUG) + if (imageSel->transparency ().isTransparent ()) + { + Q_ASSERT (backgroundColor == imageSel->transparency ().transparentColor ()); + } + else + { + // If this method is begin called by a tool, the assert does not + // have to hold since transparentColor() might not be defined in Opaque + // Mode. + // + // If we were called by a tricky sequence of undo/redo commands, the assert + // does not have to hold for additional reason, which is that + // kpMainWindow::setImageSelectionTransparency() does not have to + // set in Opaque Mode. + // + // In practice, it probably does hold but I wouldn't bet on it. + } +#endif + + kpImage eraseImage(boundingRect.size(), QImage::Format_ARGB32_Premultiplied); + eraseImage.fill(backgroundColor.toQRgb()); + + // only paint the region of the shape of the selection + QPainter painter(m_image); + painter.setClipRegion(imageSel->shapeRegion()); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.drawImage(boundingRect.topLeft(), eraseImage); + slotContentsChanged(boundingRect); + + d->environ->restoreQueueViewUpdates (); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::selectionDelete () +{ + if ( !m_selection ) + return; + + const QRect boundingRect = m_selection->boundingRect (); + Q_ASSERT (boundingRect.isValid ()); + + const bool selectionHadContent = m_selection->hasContent (); + + delete m_selection; + m_selection = 0; + + + // HACK to prevent document from being modified when + // user cancels dragging out a new selection + // REFACTOR: Extract this out into a method. + if (selectionHadContent) + slotContentsChanged (boundingRect); + else + emit contentsChanged (boundingRect); + + emit selectionEnabled (false); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::selectionCopyOntoDocument (bool applySelTransparency) +{ + // Empty selection, just doing nothing + if ( !m_selection || !m_selection->hasContent() ) + return; + + const QRect boundingRect = m_selection->boundingRect (); + Q_ASSERT (boundingRect.isValid ()); + + if (imageSelection ()) + { + if (applySelTransparency) + imageSelection ()->paint (m_image, rect ()); + else + imageSelection ()->paintWithBaseImage (m_image, rect ()); + } + else + { + // (for antialiasing with background) + m_selection->paint (m_image, rect ()); + } + + slotContentsChanged (boundingRect); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::selectionPushOntoDocument (bool applySelTransparency) +{ + selectionCopyOntoDocument (applySelTransparency); + selectionDelete (); +} + +//--------------------------------------------------------------------- + +// public +kpImage kpDocument::imageWithSelection () const +{ +#if DEBUG_KP_DOCUMENT && 1 + kDebug () << "kpDocument::imageWithSelection()"; +#endif + + // Have selection? + // + // It need not have any content because e.g. a text box with an opaque + // background, but no content, is still visually there. + if (m_selection) + { + #if DEBUG_KP_DOCUMENT && 1 + kDebug () << "\tselection @ " << m_selection->boundingRect (); + #endif + kpImage output = *m_image; + + // (this is a NOP for image selections without content) + m_selection->paint (&output, rect ()); + + return output; + } + else + { + #if DEBUG_KP_DOCUMENT && 1 + kDebug () << "\tno selection"; + #endif + return *m_image; + } +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/environments/commands/kpCommandEnvironment.cpp b/kolourpaint/environments/commands/kpCommandEnvironment.cpp new file mode 100644 index 00000000..e59ddb96 --- /dev/null +++ b/kolourpaint/environments/commands/kpCommandEnvironment.cpp @@ -0,0 +1,103 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include +#include +#include +#include +#include + + +struct kpCommandEnvironmentPrivate +{ +}; + +kpCommandEnvironment::kpCommandEnvironment (kpMainWindow *mainWindow) + : kpEnvironmentBase (mainWindow), + d (new kpCommandEnvironmentPrivate ()) +{ +} + +kpCommandEnvironment::~kpCommandEnvironment () +{ + delete d; +} + + +// public +void kpCommandEnvironment::setColor (int which, const kpColor &color) const +{ + kpColorToolBar *toolBar = mainWindow ()->colorToolBar (); + Q_ASSERT (toolBar); + + toolBar->setColor (which, color); +} + + +// public +void kpCommandEnvironment::somethingBelowTheCursorChanged () const +{ + kpTool *tool = mainWindow ()->tool (); + Q_ASSERT (tool); + + tool->somethingBelowTheCursorChanged (); +} + + +// public +kpImageSelectionTransparency kpCommandEnvironment::imageSelectionTransparency () const +{ + return mainWindow ()->imageSelectionTransparency (); +} + +// public +void kpCommandEnvironment::setImageSelectionTransparency ( + const kpImageSelectionTransparency &transparency, + bool forceColorChange) +{ + mainWindow ()->setImageSelectionTransparency (transparency, forceColorChange); +} + + +// public +kpTextStyle kpCommandEnvironment::textStyle () const +{ + return mainWindow ()->textStyle (); +} + +// public +void kpCommandEnvironment::setTextStyle (const kpTextStyle &textStyle) +{ + mainWindow ()->setTextStyle (textStyle); +} + + +#include diff --git a/kolourpaint/environments/commands/kpCommandEnvironment.h b/kolourpaint/environments/commands/kpCommandEnvironment.h new file mode 100644 index 00000000..fb2b0ad6 --- /dev/null +++ b/kolourpaint/environments/commands/kpCommandEnvironment.h @@ -0,0 +1,80 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpCommandEnvironment_H +#define kpCommandEnvironment_H + + +#include + + +class kpMainWindow; +class kpImageSelectionTransparency; +class kpTextStyle; +class kpViewManager; + + +// Facade for kpCommand clients. +class kpCommandEnvironment : public kpEnvironmentBase +{ +Q_OBJECT + +public: + // Note: Our interface must never publicly leak or any other + // classes we are trying to hide as that would defeat the point of + // the facade. + kpCommandEnvironment (kpMainWindow *mainWindow); + virtual ~kpCommandEnvironment (); + + + void somethingBelowTheCursorChanged () const; + + + // Sets the foreground and background drawing colors in the UI. + void setColor (int which, const kpColor &color) const; + + + // + // Selections + // + + kpImageSelectionTransparency imageSelectionTransparency () const; + void setImageSelectionTransparency (const kpImageSelectionTransparency &transparency, + bool forceColorChange = false); + + kpTextStyle textStyle () const; + void setTextStyle (const kpTextStyle &textStyle); + + +private: + struct kpCommandEnvironmentPrivate * const d; +}; + + +#endif // kpCommandEnvironment_H + diff --git a/kolourpaint/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.cpp b/kolourpaint/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.cpp new file mode 100644 index 00000000..97bf916a --- /dev/null +++ b/kolourpaint/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.cpp @@ -0,0 +1,43 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include + + +//--------------------------------------------------------------------- + +kpTransformDialogEnvironment::kpTransformDialogEnvironment(kpMainWindow *mainWindow) + : kpEnvironmentBase(mainWindow) +{ +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h b/kolourpaint/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h new file mode 100644 index 00000000..a3f4b218 --- /dev/null +++ b/kolourpaint/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h @@ -0,0 +1,53 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformDialogEnvironment_H +#define kpTransformDialogEnvironment_H + + +#include + + +class kpCommandEnvironment; + + +// Facade for kpTransformDialog* clients. +class kpTransformDialogEnvironment : public kpEnvironmentBase +{ +Q_OBJECT + +public: + // Note: Our interface must never publicly leak or any other + // classes we are trying to hide as that would defeat the point of + // the facade. + kpTransformDialogEnvironment (kpMainWindow *mainWindow); +}; + + +#endif // kpTransformDialogEnvironment_H + diff --git a/kolourpaint/environments/document/kpDocumentEnvironment.cpp b/kolourpaint/environments/document/kpDocumentEnvironment.cpp new file mode 100644 index 00000000..8dee2b71 --- /dev/null +++ b/kolourpaint/environments/document/kpDocumentEnvironment.cpp @@ -0,0 +1,213 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DOCUMENT_ENVIRONMENT 0 + + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if DEBUG_KP_DOCUMENT_ENVIRONMENT + #include +#endif +#include + + +struct kpDocumentEnvironmentPrivate +{ +}; + +kpDocumentEnvironment::kpDocumentEnvironment (kpMainWindow *mainWindow) + : kpEnvironmentBase (mainWindow), + d (new kpDocumentEnvironmentPrivate ()) +{ +} + +kpDocumentEnvironment::~kpDocumentEnvironment () +{ + delete d; +} + + +// public +QWidget *kpDocumentEnvironment::dialogParent () const +{ + return mainWindow (); +} + + +static kpViewManager *ViewManager (kpMainWindow *mw) +{ + return mw->viewManager (); +} + +// public +void kpDocumentEnvironment::setQueueViewUpdates () const +{ + ::ViewManager (mainWindow ())->setQueueUpdates (); +} + +// public +void kpDocumentEnvironment::restoreQueueViewUpdates () const +{ + ::ViewManager (mainWindow ())->restoreQueueUpdates (); +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentEnvironment::switchToCompatibleTool (const kpAbstractSelection &selection, + bool *isTextChanged) const +{ +#if DEBUG_KP_DOCUMENT_ENVIRONMENT + kDebug () << "kpDocumentEnvironment::switchToCompatibleTool(" + << &selection << ")" + << " mainwindow.tool=" + << (mainWindow ()->tool () ? mainWindow ()->tool ()->objectName () : 0) + << " mainWindow.toolIsTextTool=" << mainWindow ()->toolIsTextTool () + << " current selection=" + << document ()->selection () + << " new selection is text=" + << dynamic_cast (&selection) + << endl; +#endif + + *isTextChanged = (mainWindow ()->toolIsTextTool () != + (dynamic_cast (&selection) != 0)); + + // We don't change the Selection Tool if the new selection's + // shape is merely different to the current tool's (e.g. rectangular + // vs elliptical) because: + // + // 1. All image selection tools support editing selections of all the + // different shapes anyway. + // 2. Suppose the user is trying out different drags of selection borders + // and then decides to paste a differently shaped selection before continuing + // to try out different borders. If the pasting were to switch to + // a differently shaped tool, the borders drawn after the paste would + // be using a new shape rather than the shape before the paste. This + // could get irritating so we don't do the switch. + if (!mainWindow ()->toolIsASelectionTool () || *isTextChanged) + { + // See kpDocument::setSelection() APIDoc for this assumption. + Q_ASSERT (!document ()->selection ()); + + // Switch to the appropriately shaped selection tool + // _before_ we change the selection + // (all selection tool's ::end() functions nuke the current selection) + if (dynamic_cast (&selection)) + { + #if DEBUG_KP_DOCUMENT_ENVIRONMENT + kDebug () << "\tswitch to rect selection tool"; + #endif + mainWindow ()->slotToolRectSelection (); + } + else if (dynamic_cast (&selection)) + { + #if DEBUG_KP_DOCUMENT_ENVIRONMENT + kDebug () << "\tswitch to elliptical selection tool"; + #endif + mainWindow ()->slotToolEllipticalSelection (); + } + else if (dynamic_cast (&selection)) + { + #if DEBUG_KP_DOCUMENT_ENVIRONMENT + kDebug () << "\tswitch to free form selection tool"; + #endif + mainWindow ()->slotToolFreeFormSelection (); + } + else if (dynamic_cast (&selection)) + { + #if DEBUG_KP_DOCUMENT_ENVIRONMENT + kDebug () << "\tswitch to text selection tool"; + #endif + mainWindow ()->slotToolText (); + } + else + Q_ASSERT (!"Unknown selection type"); + } + +#if DEBUG_KP_DOCUMENT_ENVIRONMENT + kDebug () << "kpDocumentEnvironment::switchToCompatibleTool(" << &selection << ") finished"; +#endif +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentEnvironment::assertMatchingUIState (const kpAbstractSelection &selection) const +{ + // Trap and try to recover from bugs. + // TODO: See kpDocument::setSelection() API comment and determine best fix. + const kpAbstractImageSelection *imageSelection = + dynamic_cast (&selection); + const kpTextSelection *textSelection = + dynamic_cast (&selection); + if (imageSelection) + { + if (imageSelection->transparency () != mainWindow ()->imageSelectionTransparency ()) + { + kError () << "kpDocument::setSelection() sel's transparency differs " + "from mainWindow's transparency - setting mainWindow's transparency " + "to sel" + << endl; + kError () << "\tisOpaque: sel=" << imageSelection->transparency ().isOpaque () + << " mainWindow=" << mainWindow ()->imageSelectionTransparency ().isOpaque () + << endl; + mainWindow ()->setImageSelectionTransparency (imageSelection->transparency ()); + } + } + else if (textSelection) + { + if (textSelection->textStyle () != mainWindow ()->textStyle ()) + { + kError () << "kpDocument::setSelection() sel's textStyle differs " + "from mainWindow's textStyle - setting mainWindow's textStyle " + "to sel" + << endl; + mainWindow ()->setTextStyle (textSelection->textStyle ()); + } + } + else + { + Q_ASSERT (!"Unknown selection type"); + } +} + + +#include diff --git a/kolourpaint/environments/document/kpDocumentEnvironment.h b/kolourpaint/environments/document/kpDocumentEnvironment.h new file mode 100644 index 00000000..264d3355 --- /dev/null +++ b/kolourpaint/environments/document/kpDocumentEnvironment.h @@ -0,0 +1,72 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDocumentEnvironment_H +#define kpDocumentEnvironment_H + + +#include + + +class QRect; +class QWidget; + +class kpAbstractSelection; + + +// Facade for kpDocument clients. +class kpDocumentEnvironment : public kpEnvironmentBase +{ +Q_OBJECT + +public: + // Note: Our interface must never publicly leak or any other + // classes we are trying to hide as that would defeat the point of + // the facade. + kpDocumentEnvironment (kpMainWindow *mainWindow); + virtual ~kpDocumentEnvironment (); + + + QWidget *dialogParent () const; + + void setQueueViewUpdates () const; + void restoreQueueViewUpdates () const; + + void switchToCompatibleTool (const kpAbstractSelection &selection, + // REFACTOR: This is horrible API and terribly named. + bool *isTextChanged) const; + void assertMatchingUIState (const kpAbstractSelection &selection) const; + + +private: + struct kpDocumentEnvironmentPrivate * const d; +}; + + +#endif // kpDocumentEnvironment_H + diff --git a/kolourpaint/environments/kpEnvironmentBase.cpp b/kolourpaint/environments/kpEnvironmentBase.cpp new file mode 100644 index 00000000..d2155f32 --- /dev/null +++ b/kolourpaint/environments/kpEnvironmentBase.cpp @@ -0,0 +1,121 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include +#include +#include +#include + + +struct kpEnvironmentBasePrivate +{ + kpMainWindow *mainWindow; +}; + +kpEnvironmentBase::kpEnvironmentBase (kpMainWindow *mainWindow) + : QObject (mainWindow), + d (new kpEnvironmentBasePrivate ()) +{ + Q_ASSERT (mainWindow); + + d->mainWindow = mainWindow; +} + +kpEnvironmentBase::~kpEnvironmentBase () +{ + delete d; +} + + +// public +kpDocument *kpEnvironmentBase::document () const +{ + return d->mainWindow->document (); +} + + +// public +kpAbstractSelection *kpEnvironmentBase::selection () const +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + return doc->selection (); +} + +// public +kpAbstractImageSelection *kpEnvironmentBase::imageSelection () const +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + return doc->imageSelection (); +} + +// public +kpTextSelection *kpEnvironmentBase::textSelection () const +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + return doc->textSelection (); +} + + +// public +kpViewManager *kpEnvironmentBase::viewManager () const +{ + return mainWindow ()->viewManager (); +} + + +// public +kpCommandEnvironment *kpEnvironmentBase::commandEnvironment () const +{ + return mainWindow ()->commandEnvironment (); +} + + +// public +kpColor kpEnvironmentBase::backgroundColor (bool ofSelection) const +{ + return d->mainWindow->backgroundColor (ofSelection); +} + + +// protected +kpMainWindow *kpEnvironmentBase::mainWindow () const +{ + return d->mainWindow; +} + + +#include diff --git a/kolourpaint/environments/kpEnvironmentBase.h b/kolourpaint/environments/kpEnvironmentBase.h new file mode 100644 index 00000000..d460fbc2 --- /dev/null +++ b/kolourpaint/environments/kpEnvironmentBase.h @@ -0,0 +1,99 @@ + +// LOREFACTOR: The files in this folder duplicate too much code. +// Use mixin multiple inheritance to get around this. +// +// LOREFACTOR: Move as much kpMainWindow code as possible into these classes +// to reduce the size of kpMainWindow. But do not split concerns / +// "aspects" in kpMainWindow, with half the code there and half here, +// as that would be confusing. + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEnvironmentBase_H +#define kpEnvironmentBase_H + + +#include + + +class kpAbstractImageSelection; +class kpAbstractSelection; +class kpColor; +class kpCommandEnvironment; +class kpDocument; +class kpMainWindow; +class kpTextSelection; +class kpViewManager; + + +// Abstract facade bridging kpMainWindow and other suppliers (e.g. kpTool, +// kpToolToolBar, kpColorToolBar) to clients. +// +// This decouples as many classes as possible from clients, for maintainability. +// If adding new methods to this class, it is preferable to expose additional +// functionality, rather than expose additional classes. +// +// This class also avoids cramming excessive functionality into kpMainWindow. +// +// If this interface gets too bloated, push down the specialized methods into +// a new class. +class kpEnvironmentBase : public QObject +{ +Q_OBJECT + +// (must derive from) +protected: + // Note: Our interface must never publicly leak or any other + // classes we are trying to hide as that would defeat the point of + // the facade. + kpEnvironmentBase (kpMainWindow *mainWindow); + virtual ~kpEnvironmentBase (); + +public: + kpDocument *document () const; + + kpAbstractSelection *selection () const; + kpAbstractImageSelection *imageSelection () const; + kpTextSelection *textSelection () const; + + kpViewManager *viewManager () const; + + kpCommandEnvironment *commandEnvironment () const; + + kpColor backgroundColor (bool ofSelection = false) const; + +protected: + kpMainWindow *mainWindow () const; + +private: + struct kpEnvironmentBasePrivate * const d; +}; + + +#endif // kpEnvironmentBase_H + diff --git a/kolourpaint/environments/tools/kpToolEnvironment.cpp b/kolourpaint/environments/tools/kpToolEnvironment.cpp new file mode 100644 index 00000000..92b7bccf --- /dev/null +++ b/kolourpaint/environments/tools/kpToolEnvironment.cpp @@ -0,0 +1,212 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + + +struct kpToolEnvironmentPrivate +{ +}; + +kpToolEnvironment::kpToolEnvironment (kpMainWindow *mainWindow) + : kpEnvironmentBase (mainWindow), + d (new kpToolEnvironmentPrivate ()) +{ +} + +kpToolEnvironment::~kpToolEnvironment () +{ + delete d; +} + + +// public +KActionCollection *kpToolEnvironment::actionCollection () const +{ + return mainWindow ()->actionCollection (); +} + +// public +kpCommandHistory *kpToolEnvironment::commandHistory () const +{ + return mainWindow ()->commandHistory (); +} + + +// public +QActionGroup *kpToolEnvironment::toolsActionGroup () const +{ + return mainWindow ()->toolsActionGroup (); +} + +// public +kpToolToolBar *kpToolEnvironment::toolToolBar () const +{ + return mainWindow ()->toolToolBar (); +} + +// public +void kpToolEnvironment::hideAllToolWidgets () const +{ + toolToolBar ()->hideAllToolWidgets (); +} + +// public +bool kpToolEnvironment::selectPreviousTool () const +{ + kpToolToolBar *tb = toolToolBar (); + + // (don't end up with no tool selected) + if (!tb->previousTool ()) + return false; + + // endInternal() will be called by kpMainWindow (thanks to this line) + // so we won't have the view anymore + // TODO: Update comment. + tb->selectPreviousTool (); + return true; +} + + +static kpColorToolBar *ColorToolBar (kpMainWindow *mw) +{ + return mw->colorToolBar (); +} + +// public +kpColor kpToolEnvironment::color (int which) const +{ + return ::ColorToolBar (mainWindow ())->color (which); +} + +// public +double kpToolEnvironment::colorSimilarity () const +{ + return ::ColorToolBar (mainWindow ())->colorSimilarity (); +} + +// public +int kpToolEnvironment::processedColorSimilarity () const +{ + return ::ColorToolBar (mainWindow ())->processedColorSimilarity (); +} + +// public +kpColor kpToolEnvironment::oldForegroundColor () const +{ + return ::ColorToolBar (mainWindow ())->oldForegroundColor (); +} + +// public +kpColor kpToolEnvironment::oldBackgroundColor () const +{ + return ::ColorToolBar (mainWindow ())->oldBackgroundColor (); +} + +// public +double kpToolEnvironment::oldColorSimilarity () const +{ + return ::ColorToolBar (mainWindow ())->oldColorSimilarity (); +} + + +// public +void kpToolEnvironment::flashColorSimilarityToolBarItem () const +{ + ::ColorToolBar (mainWindow ())->flashColorSimilarityToolBarItem (); +} + + +// public +void kpToolEnvironment::setColor (int which, const kpColor &color) const +{ + kpColorToolBar *toolBar = mainWindow ()->colorToolBar (); + Q_ASSERT (toolBar); + + toolBar->setColor (which, color); +} + + +// public +void kpToolEnvironment::deleteSelection () const +{ + mainWindow ()->slotDelete (); +} + +// public +void kpToolEnvironment::pasteTextAt (const QString &text, const QPoint &point, + bool allowNewTextSelectionPointShift) const +{ + mainWindow ()->pasteTextAt (text, point, allowNewTextSelectionPointShift); +} + + +// public +void kpToolEnvironment::zoomIn (bool centerUnderCursor) const +{ + mainWindow ()->zoomIn (centerUnderCursor); +} + +// public +void kpToolEnvironment::zoomOut (bool centerUnderCursor) const +{ + mainWindow ()->zoomOut (centerUnderCursor); +} + + +// public +void kpToolEnvironment::zoomToRect ( + const QRect &normalizedDocRect, + bool accountForGrips, + bool careAboutWidth, bool careAboutHeight) const +{ + mainWindow ()->zoomToRect ( + normalizedDocRect, + accountForGrips, + careAboutWidth, careAboutHeight); +} + +// public +void kpToolEnvironment::fitToPage () const +{ + mainWindow ()->slotFitToPage (); +} + + +#include diff --git a/kolourpaint/environments/tools/kpToolEnvironment.h b/kolourpaint/environments/tools/kpToolEnvironment.h new file mode 100644 index 00000000..16c43b0a --- /dev/null +++ b/kolourpaint/environments/tools/kpToolEnvironment.h @@ -0,0 +1,161 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolEnvironment_H +#define kpToolEnvironment_H + + +#include + + +class QActionGroup; +class QPoint; +class QRect; +class QString; + +class KActionCollection; + +class kpColor; +class kpCommandHistory; +class kpToolToolBar; + + +// Facade for kpTool clients. +class kpToolEnvironment : public kpEnvironmentBase +{ +Q_OBJECT + +public: + // Note: Our interface must never publicly leak or any other + // classes we are trying to hide as that would defeat the point of + // the facade. + kpToolEnvironment (kpMainWindow *mainWindow); + virtual ~kpToolEnvironment (); + + + KActionCollection *actionCollection () const; + + kpCommandHistory *commandHistory () const; + + QActionGroup *toolsActionGroup () const; + kpToolToolBar *toolToolBar () const; + void hideAllToolWidgets () const; + bool selectPreviousTool () const; + + kpColor color (int which) const; + double colorSimilarity () const; + int processedColorSimilarity () const; + + // (only valid in kpTool::slotForegroundColorChanged()) + kpColor oldForegroundColor () const; + // (only valid in kpTool::slotBackgroundColorChanged()) + kpColor oldBackgroundColor () const; + + // (only valid in kpTool::slotColorSimilarityChanged()) + double oldColorSimilarity () const; + + // Flashes the Color Similarity Tool Bar Item to highlight to the user, + // the existence of the Color Similarity feature. + // + // This should be used in only 3 circumstances: + // + // 1. Tools not acting on the selection but using Color Similarity + // e.g. Color Eraser, Flood Fill, Auto Crop. Note that Auto Crop + // becomes Remove Internal Border when a selection is active but + // the flashing still occurs because the extent of the effect is + // directly dependent on Color Similarity. + // + // 2. A change to selection transparency (background color, color similarity + // percentage, change from opaque to transparent). + // + // It is not used when the transpareny is opaque or when changing from + // transparent to opaque, as you're not using Color Similarity in these + // cases. + // + // Similarly, it is not used when there is no image selection or the + // image selection is just a border, as changing the selection + // transparency also does nothing in these cases. + // + // 3. Pulling a selection from the document, when selection transparency + // is transparent. + // + // Except for any pulling phase, it is not used for effects that use + // the "transparent image" of a selection (e.g. smearing a transparent + // selection). This is to minimize distractions and because the flashing + // has already been done by pulling. + // + // This should always be called _before_ an operation related to Color + // Similarity, to indicate that the Color Similarity was applied throughout + // the operation -- not at the end. This aspect is most noticeable for + // the time-consuming Autocrop feature, which may fail with a dialog -- + // it would look wrong to only flash the Color Similarity Tool Bar Item when + // the dialog comes up after the failed operation, as it implies that Color + // Similarity was not used during the operation. Having said all this, + // currently the flashing still happens afterwards because the flashing is + // not done in a separate thread. However, assume that the implementation + // will be fixed later. + // + // It is tempting to simply disable the Color Similarity Tool Bar Item + // if the current tool does not use Color Similarity - this change in + // enabled state would be enough to highlight the existence of Color + // Similarity, without the need for this flashing. However, Auto Crop + // is always available - independent of the current tool - and it uses + // Color Similarity, so we can never disable the Tool Bar Item. + // + // We flash in tools but not commands as else it would be very distracting + // Undo/Redo - this flashing in the tools is distracting enough already :) + void flashColorSimilarityToolBarItem () const; + + void setColor (int which, const kpColor &color) const; + + void deleteSelection () const; + void pasteTextAt (const QString &text, const QPoint &point, + // Allow tiny adjustment of so that mouse + // pointer is not exactly on top of the topLeft of + // any new text selection (so that it doesn't look + // weird by being on top of a resize handle just after + // a paste). + bool allowNewTextSelectionPointShift = false) const; + + void zoomIn (bool centerUnderCursor = false) const; + void zoomOut (bool centerUnderCursor = false) const; + + void zoomToRect (const QRect &normalizedDocRect, + bool accountForGrips, + bool careAboutWidth, bool careAboutHeight) const; + + void fitToPage () const; + + +private: + struct kpToolEnvironmentPrivate * const d; +}; + + +#endif // kpToolEnvironment_H + diff --git a/kolourpaint/environments/tools/selection/kpToolSelectionEnvironment.cpp b/kolourpaint/environments/tools/selection/kpToolSelectionEnvironment.cpp new file mode 100644 index 00000000..d6fc1bb9 --- /dev/null +++ b/kolourpaint/environments/tools/selection/kpToolSelectionEnvironment.cpp @@ -0,0 +1,97 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include +#include + + +struct kpToolSelectionEnvironmentPrivate +{ +}; + +kpToolSelectionEnvironment::kpToolSelectionEnvironment (kpMainWindow *mainWindow) + : kpToolEnvironment (mainWindow), + d (new kpToolSelectionEnvironmentPrivate ()) +{ +} + +kpToolSelectionEnvironment::~kpToolSelectionEnvironment () +{ + delete d; +} + + +// public +kpImageSelectionTransparency kpToolSelectionEnvironment::imageSelectionTransparency () const +{ + return mainWindow ()->imageSelectionTransparency (); +} + +// public +int kpToolSelectionEnvironment::settingImageSelectionTransparency () const +{ + return mainWindow ()->settingImageSelectionTransparency (); +} + + +// public +void kpToolSelectionEnvironment::deselectSelection () const +{ + mainWindow ()->slotDeselect (); +} + +// public +QMenu *kpToolSelectionEnvironment::selectionToolRMBMenu () const +{ + return mainWindow ()->selectionToolRMBMenu (); +} + + +// public +void kpToolSelectionEnvironment::enableTextToolBarActions (bool enable) const +{ + mainWindow ()->enableTextToolBarActions (enable); +} + +// public +kpTextStyle kpToolSelectionEnvironment::textStyle () const +{ + return mainWindow ()->textStyle (); +} + +// public +int kpToolSelectionEnvironment::settingTextStyle () const +{ + return mainWindow ()->settingTextStyle (); +} + + +#include diff --git a/kolourpaint/environments/tools/selection/kpToolSelectionEnvironment.h b/kolourpaint/environments/tools/selection/kpToolSelectionEnvironment.h new file mode 100644 index 00000000..6df7dc19 --- /dev/null +++ b/kolourpaint/environments/tools/selection/kpToolSelectionEnvironment.h @@ -0,0 +1,72 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolSelectionEnvironment_H +#define kpToolSelectionEnvironment_H + + +#include + + +class QMenu; + +class kpImageSelectionTransparency; +class kpTextStyle; + + +// Facade for kpToolSelection clients. +class kpToolSelectionEnvironment : public kpToolEnvironment +{ +Q_OBJECT + +public: + // Note: Our interface must never publicly leak or any other + // classes we are trying to hide as that would defeat the point of + // the facade. + kpToolSelectionEnvironment (kpMainWindow *mainWindow); + virtual ~kpToolSelectionEnvironment (); + + kpImageSelectionTransparency imageSelectionTransparency () const; + int settingImageSelectionTransparency () const; + + void deselectSelection () const; + + QMenu *selectionToolRMBMenu () const; + + void enableTextToolBarActions (bool enable = true) const; + + kpTextStyle textStyle () const; + int settingTextStyle () const; + +private: + struct kpToolSelectionEnvironmentPrivate * const d; +}; + + +#endif // kpToolSelectionEnvironment_H + diff --git a/kolourpaint/gen_cmake_include_dirs b/kolourpaint/gen_cmake_include_dirs new file mode 100755 index 00000000..df6260a4 --- /dev/null +++ b/kolourpaint/gen_cmake_include_dirs @@ -0,0 +1,7 @@ +#!/bin/bash +# Recalculates the KolourPaint-specific part of CMakeLists.txt's "include_directories()" + +for f in `find -type d | fgrep -v .git | egrep -v '^\.$' | egrep -v '^\./pics ^\./patches$ ^\./tests$' | cut -c3- | sort` +do + echo '${CMAKE_CURRENT_SOURCE_DIR}/'$f +done diff --git a/kolourpaint/gen_cmake_srcs b/kolourpaint/gen_cmake_srcs new file mode 100755 index 00000000..db6884ea --- /dev/null +++ b/kolourpaint/gen_cmake_srcs @@ -0,0 +1,7 @@ +#!/bin/bash +# Recalculates the KolourPaint-specific part of CMakeLists.txt's "set(kolourpaint_SRCS" + +for f in `find -name \*.cpp | cut -c3- | sort` +do + echo '${CMAKE_CURRENT_SOURCE_DIR}/'$f +done diff --git a/kolourpaint/generic/kpSetOverrideCursorSaver.cpp b/kolourpaint/generic/kpSetOverrideCursorSaver.cpp new file mode 100644 index 00000000..c695e89c --- /dev/null +++ b/kolourpaint/generic/kpSetOverrideCursorSaver.cpp @@ -0,0 +1,43 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include + + +kpSetOverrideCursorSaver::kpSetOverrideCursorSaver (const QCursor &cursor) +{ + QApplication::setOverrideCursor (cursor); +} + +kpSetOverrideCursorSaver::~kpSetOverrideCursorSaver () +{ + QApplication::restoreOverrideCursor (); +} + diff --git a/kolourpaint/generic/kpSetOverrideCursorSaver.h b/kolourpaint/generic/kpSetOverrideCursorSaver.h new file mode 100644 index 00000000..cdc0d018 --- /dev/null +++ b/kolourpaint/generic/kpSetOverrideCursorSaver.h @@ -0,0 +1,107 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpSetOverrideCursorSaver_H +#define kpSetOverrideCursorSaver_H + + +class QCursor; + + +// +// A less error-prone way of setting the override cursor, compared to +// the QApplication::{set,restore}OverrideCursor() pair. +// +// To use this class, allocate it on the stack with the desired cursor. +// +// This class sets the application's override cursor to on +// construction. It restores the cursor on destruction. +// +// Example Usage 1 - Cursor active during the entire method: +// +// void method () +// { +// kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); +// +// +// +// } // Stack unwinds, calling cursorSaver's destructor, +// // which restores the cursor +// +// Example Usage 2 - Cursor active during part of the method: +// +// void method () +// { +// +// +// { +// kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); +// +// +// +// } // Stack unwinds, calling cursorSaver's destructor, +// // which restores the cursor +// +// // (cursor is restored before this code executes) +// +// } +// +// Note that the kpSetOverrideCursorSaver must be defined as a local +// variable inside -- not outside --- the braces containing the code the +// cursor can be active over, else the destruction and cursor restoration +// might not occur at the right time. In other words, the following usage +// is wrong: +// +// INCORRECT Example Usage 2: +// +// void method () +// { +// +// +// kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); +// // BUG: These braces do nothing to limit the effect of cursorSaver. +// { +// +// } +// +// // BUG: cursorSaver has not yet restored the cursor before this +// // code executes. +// +// +// } // Stack unwinds, calling cursorSaver's destructor, +// // which restores the cursor +// +class kpSetOverrideCursorSaver +{ +public: + kpSetOverrideCursorSaver (const QCursor &cursor); + ~kpSetOverrideCursorSaver (); +}; + + +#endif // kpSetOverrideCursorSaver_H diff --git a/kolourpaint/generic/kpWidgetMapper.cpp b/kolourpaint/generic/kpWidgetMapper.cpp new file mode 100644 index 00000000..25d5fb42 --- /dev/null +++ b/kolourpaint/generic/kpWidgetMapper.cpp @@ -0,0 +1,76 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include +#include + + +namespace kpWidgetMapper +{ + + +QPoint fromGlobal (const QWidget *widget, const QPoint &point) +{ + if (!widget) + return point; + + return widget->mapFromGlobal (point); +} + +QRect fromGlobal (const QWidget *widget, const QRect &rect) +{ + if (!widget || !rect.isValid ()) + return rect; + + QPoint topLeft = fromGlobal (widget, rect.topLeft ()); + return QRect (topLeft.x (), topLeft.y (), rect.width (), rect.height ()); +} + + +QPoint toGlobal (const QWidget *widget, const QPoint &point) +{ + if (!widget) + return point; + + return widget->mapToGlobal (point); +} + +QRect toGlobal (const QWidget *widget, const QRect &rect) +{ + if (!widget || !rect.isValid ()) + return rect; + + QPoint topLeft = toGlobal (widget, rect.topLeft ()); + return QRect (topLeft.x (), topLeft.y (), rect.width (), rect.height ()); +} + + +} // namespace kpWidgetMapper { diff --git a/kolourpaint/generic/kpWidgetMapper.h b/kolourpaint/generic/kpWidgetMapper.h new file mode 100644 index 00000000..727f90d5 --- /dev/null +++ b/kolourpaint/generic/kpWidgetMapper.h @@ -0,0 +1,48 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_WIDGET_MAPPER_H +#define KP_WIDGET_MAPPER_H + + +class QWidget; +class QPoint; +class QRect; + + +namespace kpWidgetMapper +{ + QPoint fromGlobal (const QWidget *widget, const QPoint &point); + QRect fromGlobal (const QWidget *widget, const QRect &rect); + + QPoint toGlobal (const QWidget *widget, const QPoint &point); + QRect toGlobal (const QWidget *widget, const QRect &rect); +} + + +#endif // KP_WIDGET_MAPPER_H diff --git a/kolourpaint/generic/widgets/kpResizeSignallingLabel.cpp b/kolourpaint/generic/widgets/kpResizeSignallingLabel.cpp new file mode 100644 index 00000000..60e811b2 --- /dev/null +++ b/kolourpaint/generic/widgets/kpResizeSignallingLabel.cpp @@ -0,0 +1,68 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_RESIZE_SIGNALLING_LABEL 0 + + +#include + +#include + +#include + + +kpResizeSignallingLabel::kpResizeSignallingLabel (const QString &string, + QWidget *parent ) + : QLabel (string, parent) +{ +} + +kpResizeSignallingLabel::kpResizeSignallingLabel (QWidget *parent ) + : QLabel (parent) +{ +} + +kpResizeSignallingLabel::~kpResizeSignallingLabel () +{ +} + + +// protected virtual [base QLabel] +void kpResizeSignallingLabel::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_RESIZE_SIGNALLING_LABEL + kDebug () << "kpResizeSignallingLabel::resizeEvent() newSize=" << e->size () + << " oldSize=" << e->oldSize () << endl; +#endif + QLabel::resizeEvent (e); + + emit resized (); +} + + +#include diff --git a/kolourpaint/generic/widgets/kpResizeSignallingLabel.h b/kolourpaint/generic/widgets/kpResizeSignallingLabel.h new file mode 100644 index 00000000..2434fe05 --- /dev/null +++ b/kolourpaint/generic/widgets/kpResizeSignallingLabel.h @@ -0,0 +1,56 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_RESIZE_SIGNALLING_LABEL_H +#define KP_RESIZE_SIGNALLING_LABEL_H + + +#include + + +class QResizeEvent; + + +class kpResizeSignallingLabel : public QLabel +{ +Q_OBJECT + +public: + kpResizeSignallingLabel (const QString &string, QWidget *parent); + kpResizeSignallingLabel (QWidget *parent); + virtual ~kpResizeSignallingLabel (); + +signals: + void resized (); + +protected: + virtual void resizeEvent (QResizeEvent *e); +}; + + +#endif // KP_RESIZE_SIGNALLING_LABEL_H diff --git a/kolourpaint/generic/widgets/kpSubWindow.cpp b/kolourpaint/generic/widgets/kpSubWindow.cpp new file mode 100644 index 00000000..667910dc --- /dev/null +++ b/kolourpaint/generic/widgets/kpSubWindow.cpp @@ -0,0 +1,35 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + + +kpSubWindow::kpSubWindow (QWidget *parent) + : QDialog (parent) +{ +} diff --git a/kolourpaint/generic/widgets/kpSubWindow.h b/kolourpaint/generic/widgets/kpSubWindow.h new file mode 100644 index 00000000..e81e927d --- /dev/null +++ b/kolourpaint/generic/widgets/kpSubWindow.h @@ -0,0 +1,57 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpSubWindow_H +#define kpSubWindow_H + + +#include + +// +// A tool window with the following properties, in order of importance: +// +// 1. Stays on top of its parent window, but not on top of any other +// window (this rules out Qt::WindowStaysOnTop) +// 2. Does not auto-hide when its parent window loses focus +// (this rules out Qt::Tool). +// 3. Does not have a taskbar entry. +// 4. TODO: Does not take keyboard focus away from the parent window, +// when it is shown. +// 5. TODO: Is not in the Alt+Tab list +// +// We mean "tool window" in the window system sense. It has nothing to do +// with kpTool so to avoid confusion, we do not name it "kpToolWindow". +// +class kpSubWindow : public QDialog +{ + public: + kpSubWindow(QWidget *parent); +}; + + +#endif // kpSubWindow_H diff --git a/kolourpaint/imagelib/effects/kpEffectBalance.cpp b/kolourpaint/imagelib/effects/kpEffectBalance.cpp new file mode 100644 index 00000000..380ea2a3 --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectBalance.cpp @@ -0,0 +1,213 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_BALANCE 0 + + +#include + +#include + +#include + +#include + +#include + + +#if DEBUG_KP_EFFECT_BALANCE + #include +#endif + + +static inline int between0And255 (int val) +{ + if (val < 0) + return 0; + else if (val > 255) + return 255; + else + return val; +} + + +static inline int brightness (int base, int strength) +{ + return between0And255 (base + strength * 255 / 50); +} + +static inline int contrast (int base, int strength) +{ + return between0And255 ((base - 127) * (strength + 50) / 50 + 127); +} + +static inline int gamma (int base, int strength) +{ + return between0And255 (qRound (255.0 * pow (base / 255.0, 1.0 / pow (10., strength / 50.0)))); +} + + +static inline int brightnessContrastGamma (int base, + int newBrightness, + int newContrast, + int newGamma) +{ + return gamma (contrast (brightness (base, newBrightness), + newContrast), + newGamma); +} + +static inline QRgb brightnessContrastGammaForRGB (QRgb rgb, + int channels, + int brightness, int contrast, int gamma) +{ + int red = qRed (rgb); + int green = qGreen (rgb); + int blue = qBlue (rgb); + + + if (channels & kpEffectBalance::Red) + red = brightnessContrastGamma (red, brightness, contrast, gamma); + if (channels & kpEffectBalance::Green) + green = brightnessContrastGamma (green, brightness, contrast, gamma); + if (channels & kpEffectBalance::Blue) + blue = brightnessContrastGamma (blue, brightness, contrast, gamma); + + + return qRgba (red, green, blue, qAlpha (rgb)); +} + + +// public static +kpImage kpEffectBalance::applyEffect (const kpImage &image, + int channels, + int brightness, int contrast, int gamma) +{ +#if DEBUG_KP_EFFECT_BALANCE + kDebug () << "kpEffectBalance::applyEffect(" + << "channels=" << channels + << ",brightness=" << brightness + << ",contrast=" << contrast + << ",gamma=" << gamma + << ")" << endl; + QTime timer; timer.start (); +#endif + + QImage qimage = image; +#if DEBUG_KP_EFFECT_BALANCE + kDebug () << "\tconvertToImage=" << timer.restart (); +#endif + + + quint8 transformRed [256], + transformGreen [256], + transformBlue [256]; + + for (int i = 0; i < 256; i++) + { + quint8 applied = (quint8) brightnessContrastGamma (i, brightness, contrast, gamma); + + if (channels & kpEffectBalance::Red) + transformRed [i] = applied; + else + transformRed [i] = i; + + if (channels & kpEffectBalance::Green) + transformGreen [i] = applied; + else + transformGreen [i] = i; + + if (channels & kpEffectBalance::Blue) + transformBlue [i] = applied; + else + transformBlue [i] = i; + } + +#if DEBUG_KP_EFFECT_BALANCE + kDebug () << "\tbuild lookup=" << timer.restart (); +#endif + + + if (qimage.depth () > 8) + { + for (int y = 0; y < qimage.height (); y++) + { + for (int x = 0; x < qimage.width (); x++) + { + const QRgb rgb = qimage.pixel (x, y); + + const quint8 red = (quint8) qRed (rgb); + const quint8 green = (quint8) qGreen (rgb); + const quint8 blue = (quint8) qBlue (rgb); + const quint8 alpha = (quint8) qAlpha (rgb); + + qimage.setPixel (x, y, + qRgba (transformRed [red], + transformGreen [green], + transformBlue [blue], + alpha)); + + #if 0 + qimage.setPixel (x, y, + brightnessContrastGammaForRGB (qimage.pixel (x, y), + channels, + brightness, contrast, gamma)); + #endif + } + } + } + else + { + for (int i = 0; i < qimage.numColors (); i++) + { + const QRgb rgb = qimage.color (i); + + const quint8 red = (quint8) qRed (rgb); + const quint8 green = (quint8) qGreen (rgb); + const quint8 blue = (quint8) qBlue (rgb); + const quint8 alpha = (quint8) qAlpha (rgb); + + qimage.setColor (i, + qRgba (transformRed [red], + transformGreen [green], + transformBlue [blue], + alpha)); + + #if 0 + qimage.setColor (i, + brightnessContrastGammaForRGB (qimage.color (i), + channels, + brightness, contrast, gamma)); + #endif + } + + } + + return qimage; +} + diff --git a/kolourpaint/imagelib/effects/kpEffectBalance.h b/kolourpaint/imagelib/effects/kpEffectBalance.h new file mode 100644 index 00000000..6635864f --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectBalance.h @@ -0,0 +1,53 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectBalance_H +#define kpEffectBalance_H + + +#include + + +class kpEffectBalance +{ +public: + enum Channel + { + None = 0, + Red = 1, Green = 2, Blue = 4, + RGB = Red | Green | Blue + }; + + // (, & are from -50 to 50) + static kpImage applyEffect (const kpImage &image, + int channels, + int brightness, int contrast, int gamma); +}; + + +#endif // kpEffectBalance_H diff --git a/kolourpaint/imagelib/effects/kpEffectBlurSharpen.cpp b/kolourpaint/imagelib/effects/kpEffectBlurSharpen.cpp new file mode 100644 index 00000000..fa29f5b7 --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectBlurSharpen.cpp @@ -0,0 +1,204 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_BLUR_SHARPEN 0 + + +#include + +#include + +#include +#include + +#include + +#include + + +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + #include +#endif + + +//--------------------------------------------------------------------- + +// +// For info on "radius" and "sigma", see http://redskiesatnight.com/Articles/IMsharpen/ +// +// Daniel Duley says: +// +// +// I don't think I can describe it any better than the article: The radius +// controls many how pixels are taken into account when determining the value +// of the center pixel. This controls the quality [and speed] of the result but not +// necessarily the strength. The sigma controls how those neighboring pixels +// are weighted depending on how far the are from the center one. This is +// closer to strength, but not exactly >:) +// +// + + +static QImage BlurQImage (const QImage qimage_, int strength) +{ + QImage qimage = qimage_; + if (strength == 0) + return qimage; + + + // The numbers that follow were picked by experimentation to try to get + // an effect linearly proportional to and at the same time, + // be fast enough. + // + // I still have no idea what "radius" means. + + const double RadiusMin = 1; + const double RadiusMax = 10; + const double radius = RadiusMin + + (strength - 1) * + (RadiusMax - RadiusMin) / + (kpEffectBlurSharpen::MaxStrength - 1); + +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + kDebug () << "kpEffectBlurSharpen.cpp:BlurQImage(strength=" << strength << ")" + << " radius=" << radius + << endl; +#endif + + + qimage = Blitz::blur (qimage, qRound (radius)); + + + return qimage; +} + +//--------------------------------------------------------------------- + +static QImage SharpenQImage (const QImage &qimage_, int strength) +{ + QImage qimage = qimage_; + if (strength == 0) + return qimage; + + + // The numbers that follow were picked by experimentation to try to get + // an effect linearly proportional to and at the same time, + // be fast enough. + // + // I still have no idea what "radius" and "sigma" mean. + + const double RadiusMin = .1; + const double RadiusMax = 2.5; + const double radius = RadiusMin + + (strength - 1) * + (RadiusMax - RadiusMin) / + (kpEffectBlurSharpen::MaxStrength - 1); + + const double SigmaMin = .5; + const double SigmaMax = 3.0; + const double sigma = SigmaMin + + (strength - 1) * + (SigmaMax - SigmaMin) / + (kpEffectBlurSharpen::MaxStrength - 1); + + const double RepeatMin = 1; + const double RepeatMax = 2; + const double repeat = qRound (RepeatMin + + (strength - 1) * + (RepeatMax - RepeatMin) / + (kpEffectBlurSharpen::MaxStrength - 1)); + +// I guess these values are more proper as they use an auto-calculated +// radius but they cause sharpen() to be too slow. +#if 0 + const double radius = 0/*auto-calculate*/; + + const double SigmaMin = .6; + const double SigmaMax = 1.0; + const double sigma = SigmaMin + + (strength - 1) * + (SigmaMax - SigmaMin) / + (kpEffectBlurSharpen::MaxStrength - 1); + + const double RepeatMin = 1; + const double RepeatMax = 3; + const double repeat = qRound (RepeatMin + + (strength - 1) * + (RepeatMax - RepeatMin) / + (kpEffectBlurSharpen::MaxStrength - 1)); +#endif + +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + kDebug () << "kpEffectBlurSharpen.cpp:SharpenQImage(strength=" << strength << ")" + << " radius=" << radius + << " sigma=" << sigma + << " repeat=" << repeat + << endl; +#endif + + + for (int i = 0; i < repeat; i++) + { + #if DEBUG_KP_EFFECT_BLUR_SHARPEN + QTime timer; timer.start (); + #endif + qimage = Blitz::gaussianSharpen (qimage, radius, sigma); + #if DEBUG_KP_EFFECT_BLUR_SHARPEN + kDebug () << "\titeration #" + QString::number (i) + << ": " + QString::number (timer.elapsed ()) << "ms" << endl; + #endif + } + + + return qimage; +} + +//--------------------------------------------------------------------- + +// public static +kpImage kpEffectBlurSharpen::applyEffect (const kpImage &image, + Type type, int strength) +{ +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + kDebug () << "kpEffectBlurSharpen::applyEffect(image.rect=" << image.rect () + << ",type=" << int (type) + << ",strength=" << strength + << ")" << endl; +#endif + + Q_ASSERT (strength >= MinStrength && strength <= MaxStrength); + + if (type == Blur) + return ::BlurQImage (image, strength); + else if (type == Sharpen) + return ::SharpenQImage (image, strength); + else + return kpImage(); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/imagelib/effects/kpEffectBlurSharpen.h b/kolourpaint/imagelib/effects/kpEffectBlurSharpen.h new file mode 100644 index 00000000..60f1b899 --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectBlurSharpen.h @@ -0,0 +1,57 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectBlurSharpen_H +#define kpEffectBlurSharpen_H + + +#include + + +class kpEffectBlurSharpen +{ +public: + enum Type + { + None = 0, Blur, Sharpen + }; + + // Blur or Sharpen with this strength is the same as None. + // This will always be 0 - this constant will not change. + static const int MinStrength = 0; + + static const int MaxStrength = 10; + + // = strength of the effect + // (must be between MinStrength and MaxStrength inclusive) + static kpImage applyEffect (const kpImage &image, + Type type, int strength); +}; + + +#endif // kpEffectBlurSharpen_H diff --git a/kolourpaint/imagelib/effects/kpEffectEmboss.cpp b/kolourpaint/imagelib/effects/kpEffectEmboss.cpp new file mode 100644 index 00000000..0e5503af --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectEmboss.cpp @@ -0,0 +1,93 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_EMBOSS 0 + + +#include + +#include + +#include + +#include + +#include + + +static QImage EmbossQImage (const QImage &qimage_, int strength) +{ + QImage qimage = qimage_; + if (strength == 0) + return qimage; + + + // The numbers that follow were picked by experimentation to try to get + // an effect linearly proportional to and at the same time, + // be fast enough. + // + // I still have no idea what "radius" and "sigma" mean. + + const double radius = 0; + +#if 0 + const double SigmaMin = 1; + const double SigmaMax = 1.2; + + return SigmaMin + + (kpEffectEmboss::MaxStrength - strength) * + (SigmaMax - SigmaMin) / + (kpEffectEmboss::MaxStrength - 1); +#endif + const double sigma = 1; + + const int repeat = 1; + + + for (int i = 0; i < repeat; i++) + { + qimage = Blitz::emboss (qimage, radius, sigma); + } + + + return qimage; +} + + +// public static +kpImage kpEffectEmboss::applyEffect (const kpImage &image, int strength) +{ +#if DEBUG_KP_EFFECT_EMBOSS + kDebug () << "kpEffectEmboss::applyEffect(strength=" << strength << ")" + << endl; +#endif + + Q_ASSERT (strength >= MinStrength && strength <= MaxStrength); + + return ::EmbossQImage (image, strength); +} diff --git a/kolourpaint/imagelib/effects/kpEffectEmboss.h b/kolourpaint/imagelib/effects/kpEffectEmboss.h new file mode 100644 index 00000000..7b72ad62 --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectEmboss.h @@ -0,0 +1,52 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectEmboss_H +#define kpEffectEmboss_H + + +#include + + +class kpEffectEmboss +{ +public: + // This will always be 0 - this constant will not change. + static const int MinStrength = 0; + + static const int MaxStrength = 10; + + // = strength of the effect + // (must be between MinStrength and MaxStrength inclusive) + // + // Currently, all non-zero strengths are the same. + static kpImage applyEffect (const kpImage &image, int strength); +}; + + +#endif // kpEffectEmboss_H diff --git a/kolourpaint/imagelib/effects/kpEffectFlatten.cpp b/kolourpaint/imagelib/effects/kpEffectFlatten.cpp new file mode 100644 index 00000000..0427fabf --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectFlatten.cpp @@ -0,0 +1,64 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_FLATTEN 0 + + +#include + +#include + +#include +#include + +#include +#include + +#include + + +// public static +void kpEffectFlatten::applyEffect (QImage *destImagePtr, + const QColor &color1, const QColor &color2) +{ + if (!destImagePtr) + return; + + Blitz::flatten (*destImagePtr/*ref*/, color1, color2); +} + +// public static +QImage kpEffectFlatten::applyEffect (const QImage &img, + const QColor &color1, const QColor &color2) +{ + QImage retImage = img; + applyEffect (&retImage, color1, color2); + return retImage; +} + + diff --git a/kolourpaint/imagelib/effects/kpEffectFlatten.h b/kolourpaint/imagelib/effects/kpEffectFlatten.h new file mode 100644 index 00000000..2fb0aaab --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectFlatten.h @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectFlatten_H +#define kpEffectFlatten_H + + +class QColor; +class QImage; + + +class kpEffectFlatten +{ +public: + static void applyEffect (QImage *destImagePtr, + const QColor &color1, const QColor &color2); + static QImage applyEffect (const QImage &img, + const QColor &color1, const QColor &color2); +}; + + +#endif // kpEffectFlatten_H diff --git a/kolourpaint/imagelib/effects/kpEffectGrayscale.cpp b/kolourpaint/imagelib/effects/kpEffectGrayscale.cpp new file mode 100644 index 00000000..900c45f4 --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectGrayscale.cpp @@ -0,0 +1,74 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_PIXMAP_FX 0 + + +#include + +#include + + +static QRgb toGray (QRgb rgb) +{ + // naive way that doesn't preserve brightness + // int gray = (qRed (rgb) + qGreen (rgb) + qBlue (rgb)) / 3; + + // over-exaggerates red & blue + // int gray = qGray (rgb); + + int gray = (212671 * qRed (rgb) + 715160 * qGreen (rgb) + 72169 * qBlue (rgb)) / 1000000; + return qRgba (gray, gray, gray, qAlpha (rgb)); +} + + +// public static +kpImage kpEffectGrayscale::applyEffect (const kpImage &image) +{ + kpImage qimage(image); + + // TODO: Why not just write to the kpImage directly? + if (qimage.depth () > 8) + { + for (int y = 0; y < qimage.height (); y++) + { + for (int x = 0; x < qimage.width (); x++) + { + qimage.setPixel (x, y, toGray (qimage.pixel (x, y))); + } + } + } + else + { + // 1- & 8- bit images use a color table + for (int i = 0; i < qimage.numColors (); i++) + qimage.setColor (i, toGray (qimage.color (i))); + } + + return qimage; +} diff --git a/kolourpaint/imagelib/effects/kpEffectGrayscale.h b/kolourpaint/imagelib/effects/kpEffectGrayscale.h new file mode 100644 index 00000000..696c6ab7 --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectGrayscale.h @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectGrayscale_H +#define kpEffectGrayscale_H + + +#include + + +// +// Converts the image to grayscale. +// + +class kpEffectGrayscale +{ +public: + static kpImage applyEffect (const kpImage &image); +}; + + +#endif // kpEffectGrayscale_H diff --git a/kolourpaint/imagelib/effects/kpEffectHSV.cpp b/kolourpaint/imagelib/effects/kpEffectHSV.cpp new file mode 100644 index 00000000..03377dd5 --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectHSV.cpp @@ -0,0 +1,173 @@ + +/* + Copyright (c) 2007 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// TODO: Clarence's code review + +#include + +#include + +#include +#include + +#include + +#include + + +static void ColorToHSV(unsigned int c, float* pHue, float* pSaturation, float* pValue) +{ + int r = qRed(c); + int g = qGreen(c); + int b = qBlue(c); + int min; + if(b >= g && b >= r) + { + // Blue + min = qMin(r, g); + if(b != min) + { + *pHue = (float)(r - g) / ((b - min) * 6) + (float)2 / 3; + *pSaturation = (float)1 - (float)min / (float)b; + } + else + { + *pHue = 0; + *pSaturation = 0; + } + *pValue = (float)b / 255; + } + else if(g >= r) + { + // Green + min = qMin(b, r); + if(g != min) + { + *pHue = (float)(b - r) / ((g - min) * 6) + (float)1 / 3; + *pSaturation = (float)1 - (float)min / (float)g; + } + else + { + *pHue = 0; + *pSaturation = 0; + } + *pValue = (float)g / 255; + } + else + { + // Red + min = qMin(g, b); + if(r != min) + { + *pHue = (float)(g - b) / ((r - min) * 6); + if(*pHue < 0) + (*pHue) += (float)1; + *pSaturation = (float)1 - (float)min / (float)r; + } + else + { + *pHue = 0; + *pSaturation = 0; + } + *pValue = (float)r / 255; + } +} + +static unsigned int HSVToColor(int alpha, float hue, float saturation, float value) +{ + //Q_ASSERT (hue >= 0 && hue <= 1 && saturation >= 0 && saturation <= 1 && value >= 0 && value <= 1); + + hue *= (float)5.999999; + int h = (int)hue; + float f = hue - h; + float p = value * ((float)1 - saturation); + float q = value * ((float)1 - ((h & 1) == 0 ? (float)1 - f : f) * saturation); + switch(h) + { + case 0: return qRgba((int)(value * 255.999999), (int)(q * 255.999999), (int)(p * 255.999999), alpha); + case 1: return qRgba((int)(q * 255.999999), (int)(value * 255.999999), (int)(p * 255.999999), alpha); + case 2: return qRgba((int)(p * 255.999999), (int)(value * 255.999999), (int)(q * 255.999999), alpha); + case 3: return qRgba((int)(p * 255.999999), (int)(q * 255.999999), (int)(value * 255.999999), alpha); + case 4: return qRgba((int)(q * 255.999999), (int)(p * 255.999999), (int)(value * 255.999999), alpha); + case 5: return qRgba((int)(value * 255.999999), (int)(p * 255.999999), (int)(q * 255.999999), alpha); + } + return qRgba(0, 0, 0, alpha); +} + +static QRgb AdjustHSVInternal (QRgb pix, double hueDiv360, double saturation, double value) +{ + float h, s, v; + ::ColorToHSV(pix, &h, &s, &v); + + const int alpha = qAlpha(pix); + + h += (float)hueDiv360; + h -= floor(h); + + s = qMax((float)0, qMin((float)1, s + (float)saturation)); + + v = qMax((float)0, qMin((float)1, v + (float)value)); + + return ::HSVToColor(alpha, h, s, v); +} + +static void AdjustHSV (QImage* pImage, double hue, double saturation, double value) +{ + hue /= 360; + + if (pImage->depth () > 8) + { + for (int y = 0; y < pImage->height (); y++) + { + for (int x = 0; x < pImage->width (); x++) + { + QRgb pix = pImage->pixel (x, y); + pix = ::AdjustHSVInternal (pix, hue, saturation, value); + pImage->setPixel (x, y, pix); + } + } + } + else + { + for (int i = 0; i < pImage->numColors (); i++) + { + QRgb pix = pImage->color (i); + pix = ::AdjustHSVInternal (pix, hue, saturation, value); + pImage->setColor (i, pix); + } + } +} + +// public static +kpImage kpEffectHSV::applyEffect (const kpImage &image, + double hue, double saturation, double value) +{ + QImage qimage(image); + ::AdjustHSV (&qimage, hue, saturation, value); + return qimage; +} + diff --git a/kolourpaint/imagelib/effects/kpEffectHSV.h b/kolourpaint/imagelib/effects/kpEffectHSV.h new file mode 100644 index 00000000..d637190a --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectHSV.h @@ -0,0 +1,44 @@ + +/* + Copyright (c) 2007 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectHSV_H +#define kpEffectHSV_H + + +#include + + +class kpEffectHSV +{ +public: + static kpImage applyEffect (const kpImage &image, + double hue, double saturation, double value); +}; + + +#endif // kpEffectHSV_H diff --git a/kolourpaint/imagelib/effects/kpEffectInvert.cpp b/kolourpaint/imagelib/effects/kpEffectInvert.cpp new file mode 100644 index 00000000..c9abc88b --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectInvert.cpp @@ -0,0 +1,90 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_INVERT 0 + + +#include + +#include +#include + +#include + +#include + + +// public static +void kpEffectInvert::applyEffect (QImage *destImagePtr, int channels) +{ + QRgb mask = qRgba ((channels & Red) ? 0xFF : 0, + (channels & Green) ? 0xFF : 0, + (channels & Blue) ? 0xFF : 0, + 0/*don't invert alpha*/); +#if DEBUG_KP_EFFECT_INVERT + kDebug () << "kpEffectInvert::applyEffect(channels=" << channels + << ") mask=" << (int *) mask + << endl; +#endif + + if (destImagePtr->depth () > 8) + { + #if 0 + // SYNC: TODO: Qt BUG - invertAlpha argument is inverted!!! + destImagePtr->invertPixels (true/*no invert alpha (Qt 3.2)*/); + #else + // Above version works for Qt 3.2 at least. + // But this version will always work (slower, though) and supports + // inverting particular channels. + for (int y = 0; y < destImagePtr->height (); y++) + { + for (int x = 0; x < destImagePtr->width (); x++) + { + destImagePtr->setPixel (x, y, destImagePtr->pixel (x, y) ^ mask); + } + } + #endif + } + else + { + for (int i = 0; i < destImagePtr->numColors (); i++) + { + destImagePtr->setColor (i, destImagePtr->color (i) ^ mask); + } + } +} + +// public static +QImage kpEffectInvert::applyEffect (const QImage &img, int channels) +{ + QImage retImage = img; + applyEffect (&retImage, channels); + return retImage; +} + + diff --git a/kolourpaint/imagelib/effects/kpEffectInvert.h b/kolourpaint/imagelib/effects/kpEffectInvert.h new file mode 100644 index 00000000..60888435 --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectInvert.h @@ -0,0 +1,62 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectInvert_H +#define kpEffectInvert_H + + +class QImage; + + +class kpEffectInvert +{ +public: + enum Channel + { + None = 0, + Red = 1, Green = 2, Blue = 4, + RGB = Red | Green | Blue + }; + + // + // Inverts the colours of each pixel in the given image. + // These functions differ from QImage::invertPixels() in the following ways: + // + // 1. for 8-bit images, it inverts the colours of the Colour Table + // (this means that you would get visually similar results to inversion + // at higher bit depths - rather than a "random-looking" inversion + // depending on the contents of the Colour Table) + // 2. never inverts the Alpha Buffer + // + + static void applyEffect (QImage *destImagePtr, int channels = RGB); + static QImage applyEffect (const QImage &img, int channels = RGB); +}; + + +#endif // kpEffectInvert_H diff --git a/kolourpaint/imagelib/effects/kpEffectReduceColors.cpp b/kolourpaint/imagelib/effects/kpEffectReduceColors.cpp new file mode 100644 index 00000000..bfedb798 --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectReduceColors.cpp @@ -0,0 +1,236 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_REDUCE_COLORS 0 + + +#include + +#include + +//--------------------------------------------------------------------- + +static QImage::Format DepthToFormat (int depth) +{ + // These values are QImage's supported depths. + switch (depth) + { + case 1: + // (can be MSB instead, I suppose) + return QImage::Format_MonoLSB; + + case 8: + return QImage::Format_Indexed8; + + case 16: + return QImage::Format_ARGB4444_Premultiplied; + + case 24: + return QImage::Format_ARGB6666_Premultiplied; + + case 32: + return QImage::Format_ARGB32_Premultiplied; + + default: + Q_ASSERT (!"unknown depth"); + return QImage::Format_Invalid; + } +} + +//--------------------------------------------------------------------- + +// public static +QImage kpEffectReduceColors::convertImageDepth (const QImage &image, int depth, bool dither) +{ +#if DEBUG_KP_EFFECT_REDUCE_COLORS + kDebug () << "kpeffectreducecolors.cpp:ConvertImageDepth() changing image (w=" << image.width () + << ",h=" << image.height () + << ") depth from " << image.depth () + << " to " << depth + << " (dither=" << dither << ")" + << endl; +#endif + + if (image.isNull ()) + return image; + + if (depth == image.depth ()) + return image; + + +#if DEBUG_KP_EFFECT_REDUCE_COLORS && 0 + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + fprintf (stderr, " %08X", image.pixel (x, y)); + } + fprintf (stderr, "\n"); + } +#endif + + + // Hack around Qt's braindead QImage::convertToFormat(QImage::Format_MonoLSB, ...) + // (with dithering off) which produces pathetic results with an image that + // only has 2 colors - sometimes it just gives a completely black + // result (try yellow and white as input). Instead, we simply preserve + // the 2 colours. + // + // One use case is resaving a "color monochrome" image (<= 2 colors but + // not necessarily black & white). + if (depth == 1 && !dither) + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kDebug () << "\tinvoking convert-to-depth 1 hack"; + #endif + QRgb color0 = 0, color1 = 0; + bool color0Valid = false, color1Valid = false; + + bool moreThan2Colors = false; + + QImage monoImage (image.width (), image.height (), QImage::Format_MonoLSB); + monoImage.setNumColors (2); + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kDebug () << "\t\tinitialising output image w=" << monoImage.width () + << ",h=" << monoImage.height () + << ",d=" << monoImage.depth () + << endl; + #endif + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + // (this can be transparent) + QRgb imagePixel = image.pixel (x, y); + + if (color0Valid && imagePixel == color0) + monoImage.setPixel (x, y, 0); + else if (color1Valid && imagePixel == color1) + monoImage.setPixel (x, y, 1); + else if (!color0Valid) + { + color0 = imagePixel; + color0Valid = true; + monoImage.setPixel (x, y, 0); + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kDebug () << "\t\t\tcolor0=" << (int *) color0 + << " at x=" << x << ",y=" << y << endl; + #endif + } + else if (!color1Valid) + { + color1 = imagePixel; + color1Valid = true; + monoImage.setPixel (x, y, 1); + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kDebug () << "\t\t\tcolor1=" << (int *) color1 + << " at x=" << x << ",y=" << y << endl; + #endif + } + else + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kDebug () << "\t\t\timagePixel=" << (int *) imagePixel + << " at x=" << x << ",y=" << y + << " moreThan2Colors - abort hack" << endl; + #endif + moreThan2Colors = true; + + // Dijkstra, this is clearer than double break'ing or + // a check in both loops + goto exit_loop; + } + } + } + exit_loop: + + if (!moreThan2Colors) + { + monoImage.setColor (0, color0Valid ? color0 : 0xFFFFFF); + monoImage.setColor (1, color1Valid ? color1 : 0x000000); + return monoImage; + } + } + + QImage retImage = image.convertToFormat (::DepthToFormat (depth), + Qt::AutoColor | + (dither ? Qt::DiffuseDither : Qt::ThresholdDither) | + Qt::ThresholdAlphaDither | + (dither ? Qt::PreferDither : Qt::AvoidDither)); +#if DEBUG_KP_EFFECT_REDUCE_COLORS + kDebug () << "\tformat: before=" << image.format () + << "after=" << retImage.format (); +#endif + +#if DEBUG_KP_EFFECT_REDUCE_COLORS && 0 + kDebug () << "After colour reduction:"; + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + fprintf (stderr, " %08X", image.pixel (x, y)); + } + fprintf (stderr, "\n"); + } +#endif + + return retImage; +} + +//--------------------------------------------------------------------- + +// public static +void kpEffectReduceColors::applyEffect (QImage *destPtr, int depth, bool dither) +{ + if (!destPtr) + return; + + // You can't "reduce" to 32-bit since it's the highest depth. + if (depth != 1 && depth != 8) + return; + + *destPtr = convertImageDepth(*destPtr, depth, dither); + + // internally we always use QImage::Format_ARGB32_Premultiplied and + // this effect is just an "effect" in that it changes the image (the look) somehow + // When one wants a different depth on the file, then he needs to save the image + // in that depth + *destPtr = destPtr->convertToFormat(QImage::Format_ARGB32_Premultiplied); +} + +//--------------------------------------------------------------------- + +QImage kpEffectReduceColors::applyEffect (const QImage &pm, int depth, bool dither) +{ + QImage ret = pm; + applyEffect (&ret, depth, dither); + return ret; +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/imagelib/effects/kpEffectReduceColors.h b/kolourpaint/imagelib/effects/kpEffectReduceColors.h new file mode 100644 index 00000000..b137a112 --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectReduceColors.h @@ -0,0 +1,51 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectReduceColors_H +#define kpEffectReduceColors_H + +#include + +// The specified must be supported by QImage. +class kpEffectReduceColors +{ +public: + // TODO: Why isn't applyEffect() for the public API sufficient? + // Ans: See TODO in kpDocument_Save.cpp. Maybe we should rename + // this method? + // + // Also, this can increase the image depth while applyEffect() + // will not. + static QImage convertImageDepth (const QImage &image, int depth, bool dither); + + static void applyEffect (QImage *destPixmapPtr, int depth, bool dither); + static QImage applyEffect (const QImage &pm, int depth, bool dither); +}; + + +#endif // kpEffectReduceColors_H diff --git a/kolourpaint/imagelib/effects/kpEffectToneEnhance.cpp b/kolourpaint/imagelib/effects/kpEffectToneEnhance.cpp new file mode 100644 index 00000000..2a005cd7 --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectToneEnhance.cpp @@ -0,0 +1,283 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2006 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +// TODO: Clarence's code review + +#include + +#include + +#include + +#include + + +#define RED_WEIGHT 77 +#define GREEN_WEIGHT 150 +#define BLUE_WEIGHT 29 + +#define MAX_TONE_VALUE ((RED_WEIGHT + GREEN_WEIGHT + BLUE_WEIGHT) * 255) +#define TONE_DROP_BITS 5 +#define TONE_MAP_SIZE ((MAX_TONE_VALUE >> TONE_DROP_BITS) + 1) +#define MAX_GRANULARITY 25 +#define MIN_IMAGE_DIM 3 + +//--------------------------------------------------------------------- + +inline unsigned int ComputeTone(unsigned int color) +{ + return RED_WEIGHT * qRed(color) + GREEN_WEIGHT * qGreen(color) + BLUE_WEIGHT * qBlue(color); +} + +//--------------------------------------------------------------------- + +inline unsigned int AdjustTone(unsigned int color, unsigned int oldTone, unsigned int newTone, double amount) +{ + return qRgba( + qMax(0, qMin(255, (int) (amount * qRed(color) * newTone / oldTone + (1.0 - amount) * qRed(color)))), + qMax(0, qMin(255, (int) (amount * qGreen(color) * newTone / oldTone + (1.0 - amount) * qGreen(color)))), + qMax(0, qMin(255, (int) (amount * qBlue(color) * newTone / oldTone + (1.0 - amount) * qBlue(color)))), + qAlpha(color) + ); +} + +//--------------------------------------------------------------------- + +class kpEffectToneEnhanceApplier +{ + public: + kpEffectToneEnhanceApplier (); + ~kpEffectToneEnhanceApplier (); + + void BalanceImageTone(QImage* pImage, double granularity, double amount); + + protected: + int m_nToneMapGranularity, m_areaWid, m_areaHgt; + unsigned int m_nComputedWid, m_nComputedHgt; + // LOTODO: Use less error-prone QTL containers instead. + unsigned int* m_pHistogram; + unsigned int** m_pToneMaps; + + void DeleteToneMaps(); + unsigned int* MakeToneMap(QImage* pImage, int x, int y, int nGranularity); + void ComputeToneMaps(QImage* pImage, int nGranularity); + unsigned int InterpolateNewTone(QImage* pImage, unsigned int oldTone, int x, int y, int nGranularity); +}; + +//--------------------------------------------------------------------- + +kpEffectToneEnhanceApplier::kpEffectToneEnhanceApplier () +{ + m_nToneMapGranularity = 0; + m_areaWid = 0; + m_areaHgt = 0; + m_nComputedWid = 0; + m_nComputedHgt = 0; + m_pHistogram = new unsigned int[TONE_MAP_SIZE]; + m_pToneMaps = 0; +} + +//--------------------------------------------------------------------- + +kpEffectToneEnhanceApplier::~kpEffectToneEnhanceApplier () +{ + DeleteToneMaps(); + delete[] m_pHistogram; +} + +//--------------------------------------------------------------------- + +// protected +void kpEffectToneEnhanceApplier::DeleteToneMaps() +{ + int nToneMaps = m_nToneMapGranularity * m_nToneMapGranularity; + for(int i = 0; i < nToneMaps; i++) + delete[] m_pToneMaps[i]; + delete[] m_pToneMaps; + m_pToneMaps = 0; + m_nToneMapGranularity = 0; +} + +//--------------------------------------------------------------------- + +// protected +unsigned int* kpEffectToneEnhanceApplier::MakeToneMap(QImage* pImage, int u, int v, int nGranularity) +{ + // Compute the region to make the tone map for + int xx, yy; + if(nGranularity > 1) + { + xx = u * (pImage->width() - 1) / (nGranularity - 1) - m_areaWid / 2; + if(xx < 0) + xx = 0; + else if(xx + m_areaWid > pImage->width()) + xx = pImage->width() - m_areaWid; + yy = v * (pImage->width() - 1) / (nGranularity - 1) - m_areaHgt / 2; + if(yy < 0) + yy = 0; + else if(yy + m_areaHgt > pImage->height()) + yy = pImage->height() - m_areaHgt; + } + else + { + xx = 0; + yy = 0; + } + + // Make a tone histogram for the region + memset(m_pHistogram, '\0', sizeof(unsigned int) * TONE_MAP_SIZE); + int x, y; + unsigned int tone; + for(y = 0; y < m_areaHgt; y++) + { + for(x = 0; x < m_areaWid; x++) + { + tone = ComputeTone(pImage->pixel(xx + x, yy + y)); + m_pHistogram[tone >> TONE_DROP_BITS]++; + } + } + + // Forward sum the tone histogram + int i; + for(i = 1; i < TONE_MAP_SIZE; i++) + m_pHistogram[i] += m_pHistogram[i - 1]; + + // Compute the forward contribution to the tone map + unsigned int total = m_pHistogram[i - 1]; + unsigned int* pToneMap = new unsigned int[TONE_MAP_SIZE]; + for(i = 0; i < TONE_MAP_SIZE; i++) + pToneMap[i] = (uint)((unsigned long long int)m_pHistogram[i] * MAX_TONE_VALUE / total); +/* + // Undo the forward sum and reverse sum the tone histogram + m_pHistogram[TONE_MAP_SIZE - 1] -= m_pHistogram[TONE_MAP_SIZE - 2]; + for(i = TONE_MAP_SIZE - 2; i > 0; i--) + { + m_pHistogram[i] -= m_pHistogram[i - 1]; + m_pHistogram[i] += m_pHistogram[i + 1]; + } + m_pHistogram[0] += m_pHistogram[1]; +*/ + return pToneMap; +} + +//--------------------------------------------------------------------- + +// protected +void kpEffectToneEnhanceApplier::ComputeToneMaps(QImage* pImage, int nGranularity) +{ + if(nGranularity == m_nToneMapGranularity && pImage->width() == (int) m_nComputedWid && pImage->height() == (int) m_nComputedHgt) + { + return; // We've already computed tone maps for this granularity + } + DeleteToneMaps(); + m_pToneMaps = new unsigned int*[nGranularity * nGranularity]; + m_nToneMapGranularity = nGranularity; + m_nComputedWid = pImage->width(); + m_nComputedHgt = pImage->height(); + int u, v; + for(v = 0; v < nGranularity; v++) + { + for(u = 0; u < nGranularity; u++) + m_pToneMaps[nGranularity * v + u] = MakeToneMap(pImage, u, v, nGranularity); + } +} + +//--------------------------------------------------------------------- + +// protected +unsigned int kpEffectToneEnhanceApplier::InterpolateNewTone(QImage* pImage, unsigned int oldTone, int x, int y, int nGranularity) +{ + oldTone = (oldTone >> TONE_DROP_BITS); + if(m_nToneMapGranularity <= 1) + return m_pToneMaps[0][oldTone]; + int u = x * (nGranularity - 1) / pImage->width(); + int v = y * (nGranularity - 1) / pImage->height(); + unsigned int x1y1 = m_pToneMaps[m_nToneMapGranularity * v + u][oldTone]; + unsigned int x2y1 = m_pToneMaps[m_nToneMapGranularity * v + u + 1][oldTone]; + unsigned int x1y2 = m_pToneMaps[m_nToneMapGranularity * (v + 1) + u][oldTone]; + unsigned int x2y2 = m_pToneMaps[m_nToneMapGranularity * (v + 1) + u + 1][oldTone]; + int hFac = x - (u * (pImage->width() - 1) / (nGranularity - 1)); + if(hFac > m_areaWid) + hFac = m_areaWid; + unsigned int y1 = (x1y1 * (m_areaWid - hFac) + x2y1 * hFac) / m_areaWid; + unsigned int y2 = (x1y2 * (m_areaWid - hFac) + x2y2 * hFac) / m_areaWid; + int vFac = y - (v * (pImage->height() - 1) / (nGranularity - 1)); + if(vFac > m_areaHgt) + vFac = m_areaHgt; + return (y1 * (m_areaHgt - vFac) + y2 * vFac) / m_areaHgt; +} + +//--------------------------------------------------------------------- + +// public +void kpEffectToneEnhanceApplier::BalanceImageTone(QImage* pImage, double granularity, double amount) +{ + if(pImage->width() < MIN_IMAGE_DIM || pImage->height() < MIN_IMAGE_DIM) + return; // the image is not big enough to perform this operation + int nGranularity = (int)(granularity * (MAX_GRANULARITY - 2)) + 1; + m_areaWid = pImage->width() / nGranularity; + if(m_areaWid < MIN_IMAGE_DIM) + m_areaWid = MIN_IMAGE_DIM; + m_areaHgt = pImage->height() / nGranularity; + if(m_areaHgt < MIN_IMAGE_DIM) + m_areaHgt = MIN_IMAGE_DIM; + ComputeToneMaps(pImage, nGranularity); + int x, y; + unsigned int oldTone, newTone, col; + for(y = 0; y < pImage->height(); y++) + { + for(x = 0; x < pImage->width(); x++) + { + col = pImage->pixel(x, y); + oldTone = ComputeTone(col); + newTone = InterpolateNewTone(pImage, oldTone, x, y, nGranularity); + pImage->setPixel(x, y, AdjustTone(col, oldTone, newTone, amount)); + } + } +} + +//--------------------------------------------------------------------- + +// public static +kpImage kpEffectToneEnhance::applyEffect (const kpImage &image, + double granularity, double amount) +{ + if (amount == 0) + return image; + + QImage qimage(image); + + // OPT: Cache the calculated values? + kpEffectToneEnhanceApplier applier; + applier.BalanceImageTone (&qimage, granularity, amount); + + return qimage; +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/imagelib/effects/kpEffectToneEnhance.h b/kolourpaint/imagelib/effects/kpEffectToneEnhance.h new file mode 100644 index 00000000..6bda7d28 --- /dev/null +++ b/kolourpaint/imagelib/effects/kpEffectToneEnhance.h @@ -0,0 +1,61 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2006 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectToneEnhance_H +#define kpEffectToneEnhance_H + + +#include + + +// +// Histogram Equalizer effect. +// +// It just divides out the color histogram from the pixel values. (So if +// you plot the color histogram after equalizing, you should get a nearly +// flat/uniform distribution.) +// +// The two sliders adjust: +// +// 1. The extent to which it equalizes +// 2. The local-ness of the equalization. (In other words, it computes just +// the histogram of the regions near the pixel it is adjusting.) +// +// ASSUMPTION: The given is not paletted (currently, this is the +// same as the screen mode not being paletted). +// +class kpEffectToneEnhance +{ +public: + static kpImage applyEffect (const kpImage &image, + double granularity, double amount); +}; + + +#endif // kpEffectToneEnhance_H diff --git a/kolourpaint/imagelib/kpColor.cpp b/kolourpaint/imagelib/kpColor.cpp new file mode 100644 index 00000000..a4a18d0b --- /dev/null +++ b/kolourpaint/imagelib/kpColor.cpp @@ -0,0 +1,310 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COLOR 0 + + +#include + +#include + +#include + +//--------------------------------------------------------------------- + +kpColor::kpColor() + : m_rgbaIsValid(false), + m_rgba(0), + m_colorCacheIsValid(false) +{ +} + +//--------------------------------------------------------------------- + +kpColor::kpColor (int red, int green, int blue, bool isTransparent) + : m_rgba(0), m_colorCacheIsValid(false) +{ +#if DEBUG_KP_COLOR + kDebug () << "kpColor::(r=" << red << ",g=" << green << ",b=" << blue + << ",isTrans=" << isTransparent << ")" << endl; +#endif + if (red < 0 || red > 255 || + green < 0 || green > 255 || + blue < 0 || blue > 255) + { + kError () << "kpColor::(r=" << red + << ",g=" << green + << ",b=" << blue + << ",t=" << isTransparent + << ") passed out of range values" << endl; + m_rgbaIsValid = false; + return; + } + + m_rgba = qRgba (red, green, blue, isTransparent ? 0 : 255/*opaque*/); + m_rgbaIsValid = true; +} + +//--------------------------------------------------------------------- + +kpColor::kpColor (const QRgb &rgba) + : m_colorCacheIsValid (false) +{ +#if DEBUG_KP_COLOR + kDebug () << "kpColor::(rgba=" << (int *) rgba << ")"; +#endif + m_rgba = rgba; + m_rgbaIsValid = true; +} + +//--------------------------------------------------------------------- + +kpColor::kpColor (const kpColor &rhs) + : m_rgbaIsValid (rhs.m_rgbaIsValid), + m_rgba (rhs.m_rgba), + m_colorCacheIsValid (rhs.m_colorCacheIsValid), + m_colorCache (rhs.m_colorCache) +{ +#if DEBUG_KP_COLOR + kDebug () << "kpColor::()"; +#endif +} + +//--------------------------------------------------------------------- + +// friend +QDataStream &operator<< (QDataStream &stream, const kpColor &color) +{ + stream << int (color.m_rgbaIsValid) << int (color.m_rgba); + + return stream; +} + +//--------------------------------------------------------------------- + +// friend +QDataStream &operator>> (QDataStream &stream, kpColor &color) +{ + int a, b; + stream >> a >> b; + color.m_rgbaIsValid = a; + color.m_rgba = b; + + color.m_colorCacheIsValid = false; + + return stream; +} + +//--------------------------------------------------------------------- + +kpColor &kpColor::operator= (const kpColor &rhs) +{ + // (as soon as you add a ptr, you won't be complaining to me that this + // method was unnecessary :)) + + if (this == &rhs) + return *this; + + m_rgbaIsValid = rhs.m_rgbaIsValid; + m_rgba = rhs.m_rgba; + m_colorCacheIsValid = rhs.m_colorCacheIsValid; + m_colorCache = rhs.m_colorCache; + + return *this; +} + +bool kpColor::operator== (const kpColor &rhs) const +{ + return isSimilarTo (rhs, kpColor::Exact); +} + +bool kpColor::operator!= (const kpColor &rhs) const +{ + return !(*this == rhs); +} + +//--------------------------------------------------------------------- + + +template +inline dtype square (dtype val) +{ + return val * val; +} + +//--------------------------------------------------------------------- + +// public static +int kpColor::processSimilarity (double colorSimilarity) +{ + // sqrt (dr ^ 2 + dg ^ 2 + db ^ 2) <= colorSimilarity * sqrt (255 ^ 2 * 3) + // dr ^ 2 + dg ^ 2 + db ^ 2 <= (colorSimilarity ^ 2) * (255 ^ 2 * 3) + + return int (square (colorSimilarity) * (square (255) * 3)); +} + +//--------------------------------------------------------------------- + +bool kpColor::isSimilarTo (const kpColor &rhs, int processedSimilarity) const +{ + // Are we the same? + if (this == &rhs) + return true; + + + // Do we dither in terms of validity? + if (isValid () != rhs.isValid ()) + return false; + + // Are both of us invalid? + if (!isValid ()) + return true; + + // --- both are now valid --- + + if (m_rgba == rhs.m_rgba) + return true; + + if (processedSimilarity == kpColor::Exact) + return false; + else + { + return (square (qRed (m_rgba) - qRed (rhs.m_rgba)) + + square (qGreen (m_rgba) - qGreen (rhs.m_rgba)) + + square (qBlue (m_rgba) - qBlue (rhs.m_rgba)) + <= processedSimilarity); + } +} + +//--------------------------------------------------------------------- + +// public +bool kpColor::isValid () const +{ + return m_rgbaIsValid; +} + +//--------------------------------------------------------------------- + +// public +int kpColor::red () const +{ + if (!m_rgbaIsValid) + { + kError () << "kpColor::red() called with invalid kpColor" << endl; + return 0; + } + + return qRed (m_rgba); +} + +//--------------------------------------------------------------------- + +// public +int kpColor::green () const +{ + if (!m_rgbaIsValid) + { + kError () << "kpColor::green() called with invalid kpColor" << endl; + return 0; + } + + return qGreen (m_rgba); +} + +//--------------------------------------------------------------------- + +// public +int kpColor::blue () const +{ + if (!m_rgbaIsValid) + { + kError () << "kpColor::blue() called with invalid kpColor" << endl; + return 0; + } + + return qBlue (m_rgba); +} + +//--------------------------------------------------------------------- + +// public +int kpColor::alpha () const +{ + if (!m_rgbaIsValid) + { + kError () << "kpColor::alpha() called with invalid kpColor" << endl; + return 0; + } + + return qAlpha (m_rgba); +} + +//--------------------------------------------------------------------- + +// public +bool kpColor::isTransparent () const +{ + return (alpha () == 0); +} + +//--------------------------------------------------------------------- + +// public +QRgb kpColor::toQRgb () const +{ + if (!m_rgbaIsValid) + { + kError () << "kpColor::toQRgb() called with invalid kpColor" << endl; + return 0; + } + + return m_rgba; +} + +//--------------------------------------------------------------------- + +// public +QColor kpColor::toQColor () const +{ + if (!m_rgbaIsValid) + { + kError () << "kpColor::toQColor() called with invalid kpColor" << endl; + return Qt::black; + } + + if (m_colorCacheIsValid) + return m_colorCache; + + m_colorCache = QColor(qRed(m_rgba), qGreen(m_rgba), qBlue(m_rgba), qAlpha(m_rgba)); + m_colorCacheIsValid = true; + + return m_colorCache; +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/imagelib/kpColor.h b/kolourpaint/imagelib/kpColor.h new file mode 100644 index 00000000..34ea5e3c --- /dev/null +++ b/kolourpaint/imagelib/kpColor.h @@ -0,0 +1,156 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_COLOR_H +#define KP_COLOR_H + + +#include + + +class QDataStream; + + +// +// kpColor is an object-oriented abstraction of QRgb, for document image data. +// In the future, other color models such as +// 8-bit indexed will be supported. It also provides better error handling, +// reporting (noisy kError()'s) and recovery compared to Qt. This abstraction +// will allow us to eventually dump the Qt paint routines. +// +// In general, you should pass around kpColor objects instead of QRgb +// and QColor. Only convert an opaque kpColor to a QColor (using toQColor()) +// if you need to draw something on-screen. +// +// Constructing a kpColor object from QColor is usually wrong since QColor's +// come from on-screen pixels, which may lack the full color resolution of +// kpColor, due to the limited color range on e.g. a 16-bit screen. +// +class kpColor +{ +public: + kpColor (); + kpColor (int red, int green, int blue, bool isTransparent = false); + kpColor (const QRgb &rgba); + kpColor (const kpColor &rhs); + friend QDataStream &operator<< (QDataStream &stream, const kpColor &color); + friend QDataStream &operator>> (QDataStream &stream, kpColor &color); + kpColor &operator= (const kpColor &rhs); + bool operator== (const kpColor &rhs) const; + bool operator!= (const kpColor &rhs) const; + + +// +// Constants +// +public: + // "lhs.isSimilarTo (rhs, kpColor::Exact)" is exactly the same as calling + // "lhs == rhs". + static const int Exact; + + static const kpColor Invalid; + static const kpColor Transparent; + + + // + // Primary Colors + B&W + // + + static const kpColor Red, Green, Blue; + static const kpColor Black, White; + + + // + // Full-brightness Colors + // + + static const kpColor Yellow, Purple, Aqua; + + + // + // Mixed Colors + // + + static const kpColor Gray, LightGray, Orange; + + + // + // Pastel Colors + // + + static const kpColor Pink, LightGreen, LightBlue, Tan; + + + // + // Dark Colors + // + + static const kpColor DarkRed; + + // (identical) + static const kpColor DarkOrange, Brown; + + static const kpColor DarkYellow, DarkGreen, DarkAqua, DarkBlue, + DarkPurple, DarkGray; + + +public: + static int processSimilarity (double colorSimilarity); + // Usage: isSimilarTo (rhs, kpColor::processSimilarity (.1)) checks for + // Color Similarity within 10% + bool isSimilarTo (const kpColor &rhs, int processedSimilarity) const; + + bool isValid () const; + + int red () const; + int green () const; + int blue () const; + int alpha () const; + bool isTransparent () const; + + // Cast operators will most likely result in careless conversions so + // use explicit functions instead: + QRgb toQRgb () const; + + QColor toQColor () const; + +private: + // Catch accidental call to "const QRgb &rgba" (unsigned int) ctor + // by e.g. "kpColor(Qt::black)" (Qt::black is an enum element that can cast + // to "unsigned int"). + kpColor (Qt::GlobalColor color); + + bool m_rgbaIsValid; + QRgb m_rgba; + + mutable bool m_colorCacheIsValid; + mutable QColor m_colorCache; +}; + + +#endif // KP_COLOR_H diff --git a/kolourpaint/imagelib/kpColor_Constants.cpp b/kolourpaint/imagelib/kpColor_Constants.cpp new file mode 100644 index 00000000..4b519269 --- /dev/null +++ b/kolourpaint/imagelib/kpColor_Constants.cpp @@ -0,0 +1,117 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COLOR 0 + + +#include + + +static inline int RoundUp2 (int val) +{ + return val % 2 ? val + 1 : val; +} + +static inline int Bound0_255 (int val) +{ + return qBound (0, val, 255); +} + + +enum +{ + BlendDark = 25, + BlendNormal = 50, + BlendLight = 75, + BlendAdd = 100 +}; + +// Adds the 2 given colors together and then multiplies by the given . +static inline kpColor Blend (const kpColor &a, const kpColor &b, + int percent = ::BlendNormal) +{ + return kpColor (::Bound0_255 (::RoundUp2 (a.red () + b.red ()) * percent / 100), + ::Bound0_255 (::RoundUp2 (a.green () + b.green ()) * percent / 100), + ::Bound0_255 (::RoundUp2 (a.blue () + b.blue ()) * percent / 100)); +} + +static inline kpColor Add (const kpColor &a, const kpColor &b) +{ + return ::Blend (a, b, ::BlendAdd); +} + + +// (intentionally _not_ an HSV darkener) +static inline kpColor Dark (const kpColor &color) +{ + return ::Blend (color, kpColor::Black); +} + + +// public static +const int kpColor::Exact = 0; + +// public static +const kpColor kpColor::Invalid; // LOTODO: what's wrong with explicitly specifying () constructor? +const kpColor kpColor::Transparent (0, 0, 0, true/*isTransparent*/); + + +// +// Make our own colors in case weird ones like "Qt::cyan" +// (turquoise) get changed by Qt. +// + + +const kpColor kpColor::Red (255, 0, 0); +const kpColor kpColor::Green (0, 255, 0); +const kpColor kpColor::Blue (0, 0, 255); +const kpColor kpColor::Black (0, 0, 0); +const kpColor kpColor::White (255, 255, 255); + +const kpColor kpColor::Yellow = ::Add (kpColor::Red, kpColor::Green); +const kpColor kpColor::Purple = ::Add (kpColor::Red, kpColor::Blue); +const kpColor kpColor::Aqua = ::Add (kpColor::Green, kpColor::Blue); + +const kpColor kpColor::Gray = ::Blend (kpColor::Black, kpColor::White); +const kpColor kpColor::LightGray = ::Blend (kpColor::Gray, kpColor::White); +const kpColor kpColor::Orange = ::Blend (kpColor::Red, kpColor::Yellow); + +const kpColor kpColor::Pink = ::Blend (kpColor::Red, kpColor::White); +const kpColor kpColor::LightGreen = ::Blend (kpColor::Green, kpColor::White); +const kpColor kpColor::LightBlue = ::Blend (kpColor::Blue, kpColor::White); +const kpColor kpColor::Tan = ::Blend (kpColor::Yellow, kpColor::White); + +const kpColor kpColor::DarkRed = ::Dark (kpColor::Red); +const kpColor kpColor::DarkOrange = ::Dark (kpColor::Orange); +const kpColor kpColor::Brown = kpColor::DarkOrange; +const kpColor kpColor::DarkYellow = ::Dark (kpColor::Yellow); +const kpColor kpColor::DarkGreen = ::Dark (kpColor::Green); +const kpColor kpColor::DarkAqua = ::Dark (kpColor::Aqua); +const kpColor kpColor::DarkBlue = ::Dark (kpColor::Blue); +const kpColor kpColor::DarkPurple = ::Dark (kpColor::Purple); +const kpColor kpColor::DarkGray = ::Dark (kpColor::Gray); diff --git a/kolourpaint/imagelib/kpDocumentMetaInfo.cpp b/kolourpaint/imagelib/kpDocumentMetaInfo.cpp new file mode 100644 index 00000000..55fb55b6 --- /dev/null +++ b/kolourpaint/imagelib/kpDocumentMetaInfo.cpp @@ -0,0 +1,283 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include + +#include +#include + +#include + +#include + + +// +// Constants which "ought to be enough for anybody" +// LOTODO: Maybe there are some QImage constants somewhere? +// + +// public static + +// (round up to guarantee at least 1 dot per inch) +const int kpDocumentMetaInfo::MinDotsPerMeter = + int (ceil (1/*single dot per inch - a very low DPI*/ * KP_INCHES_PER_METER) + .1); + +const int kpDocumentMetaInfo::MaxDotsPerMeter = + int ((600 * 100)/*a lot of DPI*/ * KP_INCHES_PER_METER); + +// public static +const int kpDocumentMetaInfo::MaxOffset = (4000/*big image*/ * 100)/*a very big image*/; +const int kpDocumentMetaInfo::MinOffset = -kpDocumentMetaInfo::MaxOffset; + +//--------------------------------------------------------------------- + +struct kpDocumentMetaInfoPrivate +{ + int m_dotsPerMeterX, m_dotsPerMeterY; + QPoint m_offset; + + QMap m_textMap; +}; + +//--------------------------------------------------------------------- + +// public +kpDocumentMetaInfo::kpDocumentMetaInfo () + : d (new kpDocumentMetaInfoPrivate ()) +{ + d->m_dotsPerMeterX = 0; + d->m_dotsPerMeterY = 0; + d->m_offset = QPoint (0, 0); +} + +//--------------------------------------------------------------------- + +kpDocumentMetaInfo::kpDocumentMetaInfo (const kpDocumentMetaInfo &rhs) + : d (new kpDocumentMetaInfoPrivate ()) +{ + d->m_dotsPerMeterX = rhs.dotsPerMeterX (); + d->m_dotsPerMeterY = rhs.dotsPerMeterY (); + d->m_offset = rhs.offset (); + d->m_textMap = rhs.textMap (); +} + +//--------------------------------------------------------------------- + +// public +kpDocumentMetaInfo::~kpDocumentMetaInfo () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// public +bool kpDocumentMetaInfo::operator== (const kpDocumentMetaInfo &rhs) const +{ + return (d->m_dotsPerMeterX == rhs.d->m_dotsPerMeterX && + d->m_dotsPerMeterY == rhs.d->m_dotsPerMeterY && + d->m_offset == rhs.d->m_offset && + d->m_textMap == rhs.d->m_textMap); +} + +//--------------------------------------------------------------------- + +// public +bool kpDocumentMetaInfo::operator!= (const kpDocumentMetaInfo &rhs) const +{ + return !(*this == rhs); +} + +//--------------------------------------------------------------------- + +// public +kpDocumentMetaInfo &kpDocumentMetaInfo::operator= (const kpDocumentMetaInfo &rhs) +{ + if (this == &rhs) + return *this; + + d->m_dotsPerMeterX = rhs.dotsPerMeterX (); + d->m_dotsPerMeterY = rhs.dotsPerMeterY (); + d->m_offset = rhs.offset (); + d->m_textMap = rhs.textMap (); + + return *this; +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentMetaInfo::printDebug (const QString &prefix) const +{ + const QString usedPrefix = !prefix.isEmpty() ? QString(prefix + QLatin1String(":")) : QString(); + + kDebug () << usedPrefix; + + kDebug () << "dotsPerMeter X=" << dotsPerMeterX () + << " Y=" << dotsPerMeterY () + << " offset=" << offset () << endl; + + QList keyList = textKeys (); + for (QList ::const_iterator it = keyList.constBegin (); + it != keyList.constEnd (); + ++it) + { + kDebug () << "key=" << (*it) + << " text=" << text (*it) + << endl; + } + + kDebug () << usedPrefix << "ENDS"; +} + +//--------------------------------------------------------------------- + +// public +kpCommandSize::SizeType kpDocumentMetaInfo::size () const +{ + kpCommandSize::SizeType ret = 0; + + foreach (const QString &key, d->m_textMap.keys ()) + { + ret += kpCommandSize::StringSize (key) + + kpCommandSize::StringSize (d->m_textMap [key]); + } + + // We don't know what the QMap size overhead is so overestimate the size + // rather than underestimating it. + // LOTODO: Find the proper size in bytes. + return ret * 3; +} + +//--------------------------------------------------------------------- + +// public +int kpDocumentMetaInfo::dotsPerMeterX () const +{ + return d->m_dotsPerMeterX; +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentMetaInfo::setDotsPerMeterX (int val) +{ + // Unspecified resolution? + if (val == 0) + { + d->m_dotsPerMeterX = 0; + return; + } + + d->m_dotsPerMeterX = qBound (MinDotsPerMeter, val, MaxDotsPerMeter); +} + +//--------------------------------------------------------------------- + +// public +int kpDocumentMetaInfo::dotsPerMeterY () const +{ + return d->m_dotsPerMeterY; +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentMetaInfo::setDotsPerMeterY (int val) +{ + // Unspecified resolution? + if (val == 0) + { + d->m_dotsPerMeterY = 0; + return; + } + + d->m_dotsPerMeterY = qBound (MinDotsPerMeter, val, MaxDotsPerMeter); +} + +//--------------------------------------------------------------------- + +// public +QPoint kpDocumentMetaInfo::offset () const +{ + return d->m_offset; +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentMetaInfo::setOffset (const QPoint &point) +{ + const int x = qBound (MinOffset, point.x (), MaxOffset); + const int y = qBound (MinOffset, point.y (), MaxOffset); + + d->m_offset = QPoint (x, y); +} + +//--------------------------------------------------------------------- + +// public +QMap kpDocumentMetaInfo::textMap () const +{ + return d->m_textMap; +} + +//--------------------------------------------------------------------- + +// public +QList kpDocumentMetaInfo::textKeys () const +{ + return d->m_textMap.keys (); +} + +//--------------------------------------------------------------------- + +// public +QString kpDocumentMetaInfo::text (const QString &key) const +{ + if (key.isEmpty ()) + return QString (); + + return d->m_textMap [key]; +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentMetaInfo::setText (const QString &key, const QString &value) +{ + if (key.isEmpty ()) + return; + + d->m_textMap [key] = value; +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/imagelib/kpDocumentMetaInfo.h b/kolourpaint/imagelib/kpDocumentMetaInfo.h new file mode 100644 index 00000000..671f5cc7 --- /dev/null +++ b/kolourpaint/imagelib/kpDocumentMetaInfo.h @@ -0,0 +1,108 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_DOCUMENT_META_INFO_H +#define KP_DOCUMENT_META_INFO_H + + +#include +#include +#include +#include + +#include + + +class QPoint; +class QStringList; + + +class kpDocumentMetaInfo +{ +public: + kpDocumentMetaInfo (); + kpDocumentMetaInfo (const kpDocumentMetaInfo &rhs); + virtual ~kpDocumentMetaInfo (); + + bool operator== (const kpDocumentMetaInfo &rhs) const; + bool operator!= (const kpDocumentMetaInfo &rhs) const; + + kpDocumentMetaInfo &operator= (const kpDocumentMetaInfo &rhs); + + + void printDebug (const QString &prefix) const; + + + kpCommandSize::SizeType size () const; + + + // + // Constants (enforced by methods) + // + + static const int MinDotsPerMeter, MaxDotsPerMeter; + static const int MinOffset, MaxOffset; + + + // See QImage documentation + + // is 0 if the resolution is unspecified. + // Else, these methods automatically bound to be between + // MinDotsPerMeter ... MaxDotsPerMeter inclusive. + int dotsPerMeterX () const; + void setDotsPerMeterX (int val); + + // is 0 if the resolution is unspecified. + // Else, these methods automatically bound to be between + // MinDotsPerMeter ... MaxDotsPerMeter inclusive. + int dotsPerMeterY () const; + void setDotsPerMeterY (int val); + + + // These methods automatically bound each of X and Y to be between + // MinOffset and MaxOffset inclusive. + QPoint offset () const; + void setOffset (const QPoint &point); + + + QMap textMap () const; + QList textKeys () const; + + // (if is empty, it returns an empty string) + QString text (const QString &key) const; + + // (if is empty, the operation is ignored) + void setText (const QString &key, const QString &value); + + +private: + struct kpDocumentMetaInfoPrivate *d; +}; + + +#endif // KP_DOCUMENT_META_INFO_H diff --git a/kolourpaint/imagelib/kpFloodFill.cpp b/kolourpaint/imagelib/kpFloodFill.cpp new file mode 100644 index 00000000..a9151a4a --- /dev/null +++ b/kolourpaint/imagelib/kpFloodFill.cpp @@ -0,0 +1,413 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_FLOOD_FILL 0 + + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +class kpFillLine +{ +public: + kpFillLine (int y = -1, int x1 = -1, int x2 = -1) + : m_y (y), m_x1 (x1), m_x2 (x2) + { + } + + static kpCommandSize::SizeType size () + { + return sizeof (kpFillLine); + } + + int m_y, m_x1, m_x2; +}; + +//--------------------------------------------------------------------- + +static kpCommandSize::SizeType FillLinesListSize (const QLinkedList &fillLines) +{ + return (fillLines.size () * kpFillLine::size ()); +} + +//--------------------------------------------------------------------- + +struct kpFloodFillPrivate +{ + // + // Copy of whatever was passed to the constructor. + // + + kpImage *imagePtr; + int x, y; + kpColor color; + int processedColorSimilarity; + + + // + // Set by Step 1. + // + + kpColor colorToChange; + + + // + // Set by Step 2. + // + + QLinkedList fillLines; + QList < QLinkedList > fillLinesCache; + + QRect boundingRect; + + bool prepared; +}; + +//--------------------------------------------------------------------- + +kpFloodFill::kpFloodFill (kpImage *image, int x, int y, + const kpColor &color, int processedColorSimilarity) + : d (new kpFloodFillPrivate ()) +{ + d->imagePtr = image; + d->x = x, d->y = y; + d->color = color, d->processedColorSimilarity = processedColorSimilarity; + + d->prepared = false; +} + +//--------------------------------------------------------------------- + +kpFloodFill::~kpFloodFill () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// public +kpColor kpFloodFill::color () const +{ + return d->color; +} + +//--------------------------------------------------------------------- + +// public +int kpFloodFill::processedColorSimilarity () const +{ + return d->processedColorSimilarity; +} + +//--------------------------------------------------------------------- + +// public +kpCommandSize::SizeType kpFloodFill::size () const +{ + kpCommandSize::SizeType fillLinesCacheSize = 0; + foreach (const QLinkedList &linesList, d->fillLinesCache) + { + fillLinesCacheSize += ::FillLinesListSize (linesList); + } + + return ::FillLinesListSize(d->fillLines) + + kpCommandSize::QImageSize(d->imagePtr) + + fillLinesCacheSize; +} + +//--------------------------------------------------------------------- + +// public +void kpFloodFill::prepareColorToChange () +{ + if (d->colorToChange.isValid ()) + return; + +#if DEBUG_KP_FLOOD_FILL && 1 + kDebug () << "kpFloodFill::prepareColorToChange()"; +#endif + + d->colorToChange = kpPixmapFX::getColorAtPixel (*d->imagePtr, QPoint (d->x, d->y)); +} + +//--------------------------------------------------------------------- + +// public +kpColor kpFloodFill::colorToChange () +{ + prepareColorToChange (); + + return d->colorToChange; +} + +//--------------------------------------------------------------------- + +// Derived from the zSprite2 Graphics Engine + +// private +kpColor kpFloodFill::pixelColor (int x, int y, bool *beenHere) const +{ + if (beenHere) + *beenHere = false; + + Q_ASSERT (y >= 0 && y < (int) d->fillLinesCache.count ()); + + foreach (const kpFillLine &line, d->fillLinesCache [y]) + { + if (x >= line.m_x1 && x <= line.m_x2) + { + if (beenHere) + *beenHere = true; + return d->color; + } + } + + return kpPixmapFX::getColorAtPixel (*(d->imagePtr), QPoint (x, y)); +} + +//--------------------------------------------------------------------- + +// private +bool kpFloodFill::shouldGoTo (int x, int y) const +{ + bool beenThere; + const kpColor col = pixelColor (x, y, &beenThere); + + return (!beenThere && col.isSimilarTo (d->colorToChange, d->processedColorSimilarity)); +} + +//--------------------------------------------------------------------- + +// private +int kpFloodFill::findMinX (int y, int x) const +{ + for (;;) + { + if (x < 0) + return 0; + + if (shouldGoTo (x, y)) + x--; + else + return x + 1; + } +} + +//--------------------------------------------------------------------- + +// private +int kpFloodFill::findMaxX (int y, int x) const +{ + for (;;) + { + if (x > d->imagePtr->width () - 1) + return d->imagePtr->width () - 1; + + if (shouldGoTo (x, y)) + x++; + else + return x - 1; + } +} + +//--------------------------------------------------------------------- + +// private +void kpFloodFill::addLine (int y, int x1, int x2) +{ +#if DEBUG_KP_FLOOD_FILL && 0 + kDebug () << "kpFillCommand::fillAddLine (" + << y << "," << x1 << "," << x2 << ")" << endl; +#endif + + d->fillLines.append (kpFillLine (y, x1, x2)); + d->fillLinesCache [y].append ( + kpFillLine (y/*OPT: can determine from array index*/, x1, x2)); + d->boundingRect = d->boundingRect.unite (QRect (QPoint (x1, y), QPoint (x2, y))); +} + +//--------------------------------------------------------------------- + +// private +void kpFloodFill::findAndAddLines (const kpFillLine &fillLine, int dy) +{ + // out of bounds? + if (fillLine.m_y + dy < 0 || fillLine.m_y + dy >= d->imagePtr->height ()) + return; + + for (int xnow = fillLine.m_x1; xnow <= fillLine.m_x2; xnow++) + { + // At current position, right colour? + if (shouldGoTo (xnow, fillLine.m_y + dy)) + { + // Find minimum and maximum x values + int minxnow = findMinX (fillLine.m_y + dy, xnow); + int maxxnow = findMaxX (fillLine.m_y + dy, xnow); + + // Draw line + addLine (fillLine.m_y + dy, minxnow, maxxnow); + + // Move x pointer + xnow = maxxnow; + } + } +} + +//--------------------------------------------------------------------- + +// public +void kpFloodFill::prepare () +{ + if (d->prepared) + return; + +#if DEBUG_KP_FLOOD_FILL && 1 + kDebug () << "kpFloodFill::prepare()"; +#endif + + prepareColorToChange (); + + d->boundingRect = QRect (); + + +#if DEBUG_KP_FLOOD_FILL && 1 + kDebug () << "\tperforming NOP check"; +#endif + + // get the color we need to replace + if (d->processedColorSimilarity == 0 && d->color == d->colorToChange) + { + // need to do absolutely nothing (this is a significant optimization + // for people who randomly click a lot over already-filled areas) + d->prepared = true; // sync with all "return true"'s + return; + } + +#if DEBUG_KP_FLOOD_FILL && 1 + kDebug () << "\tcreating fillLinesCache"; +#endif + + // ready cache + for (int i = 0; i < d->imagePtr->height (); i++) + d->fillLinesCache.append (QLinkedList ()); + +#if DEBUG_KP_FLOOD_FILL && 1 + kDebug () << "\tcreating fill lines"; +#endif + + // draw initial line + addLine (d->y, findMinX (d->y, d->x), findMaxX (d->y, d->x)); + + for (QLinkedList ::ConstIterator it = d->fillLines.begin (); + it != d->fillLines.end (); + ++it) + { + #if DEBUG_KP_FLOOD_FILL && 0 + kDebug () << "Expanding from y=" << (*it).m_y + << " x1=" << (*it).m_x1 + << " x2=" << (*it).m_x2 + << endl; + #endif + + // + // Make more lines above and below current line. + // + // WARNING: Adds to end of "fillLines" (the linked list we are iterating + // through). Therefore, "fillLines" must remain a linked list + // - you cannot change it into a vector. Also, do not use + // "foreach" for this loop as that makes a copy of the linked + // list at the start and won't see new lines. + // + findAndAddLines (*it, -1); + findAndAddLines (*it, +1); + } + +#if DEBUG_KP_FLOOD_FILL && 1 + kDebug () << "\tfinalising memory usage"; +#endif + + // finalize memory usage + d->fillLinesCache.clear (); + + d->prepared = true; // sync with all "return true"'s +} + +//--------------------------------------------------------------------- + +// public +QRect kpFloodFill::boundingRect () +{ + prepare (); + + return d->boundingRect; +} + +//--------------------------------------------------------------------- +// public +void kpFloodFill::fill() +{ + prepare(); + + QApplication::setOverrideCursor(Qt::WaitCursor); + + QPainter painter(d->imagePtr); + + // by definition, flood fill with a fully transparent color erases the pixels + // and sets them to be fully transparent + if ( d->color.isTransparent() ) + painter.setCompositionMode(QPainter::CompositionMode_Clear); + + painter.setPen(d->color.toQColor()); + + foreach (const kpFillLine &l, d->fillLines) + { + if ( l.m_x1 == l.m_x2 ) + painter.drawPoint(l.m_x1, l.m_y); + else + painter.drawLine(l.m_x1, l.m_y, l.m_x2, l.m_y); + } + + QApplication::restoreOverrideCursor(); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/imagelib/kpFloodFill.h b/kolourpaint/imagelib/kpFloodFill.h new file mode 100644 index 00000000..195bcbf6 --- /dev/null +++ b/kolourpaint/imagelib/kpFloodFill.h @@ -0,0 +1,128 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_FLOOD_FILL_H +#define KP_FLOOD_FILL_H + + +#include +#include + + +class kpColor; +class kpFillLine; + + +struct kpFloodFillPrivate; + +class kpFloodFill +{ +public: + kpFloodFill (kpImage *image, int x, int y, + const kpColor &color, + int processedColorSimilarity); + ~kpFloodFill (); + + + // + // Spits back constructor arguments. + // + +public: + kpColor color () const; + int processedColorSimilarity () const; + + +public: + // Used for calculating the size of a command in the command history. + kpCommandSize::SizeType size () const; + + + // + // Step 1: Determines the colour that will be changed to color(). + // + // Very fast. + // + +public: + void prepareColorToChange (); + + // (may invoke prepareColorToChange()). + kpColor colorToChange (); + + + // + // Step 2: Determines the scanlines / pixels that will be changed to color(). + // + // The slowest part of the whole fill operation. + // + // Before calling a Step 2 function, you don't have to (but you can) + // call any of the functions in Step 1. + // + +private: + kpColor pixelColor (int x, int y, bool *beenHere = 0) const; + bool shouldGoTo (int x, int y) const; + + // Finds the minimum x value at a certain line to be filled. + int findMinX (int y, int x) const; + + // Finds the maximum x value at a certain line to be filled. + int findMaxX (int y, int x) const; + + void addLine (int y, int x1, int x2); + void findAndAddLines (const kpFillLine &fillLine, int dy); + +public: + // (may invoke Step 1's prepareColorToChange()) + void prepare (); + + // (may invoke prepare()) + QRect boundingRect (); + + + // + // Step 3: Draws the lines identified in Step 2 in color(). + // + // Between the speeds of Step 2 and Step 1. + // + // Before calling a Step 3 function, you don't have to (but you can) + // call any of the functions in Step 1 or 2. + // + +public: + // (may invoke Step 2's prepare()) + void fill (); + + +private: + kpFloodFillPrivate * const d; +}; + + +#endif // KP_FLOOD_FILL_H diff --git a/kolourpaint/imagelib/kpImage.h b/kolourpaint/imagelib/kpImage.h new file mode 100644 index 00000000..428f94b1 --- /dev/null +++ b/kolourpaint/imagelib/kpImage.h @@ -0,0 +1,38 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_IMAGE_H +#define KP_IMAGE_H + + +#include + +typedef QImage kpImage; + + +#endif // KP_IMAGE_H diff --git a/kolourpaint/imagelib/kpPainter.cpp b/kolourpaint/imagelib/kpPainter.cpp new file mode 100644 index 00000000..f2658d08 --- /dev/null +++ b/kolourpaint/imagelib/kpPainter.cpp @@ -0,0 +1,600 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_PAINTER 0 + + +#include + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +// public static +bool kpPainter::pointsAreCardinallyAdjacent (const QPoint &p, const QPoint &q) +{ + int dx = qAbs (p.x () - q.x ()); + int dy = qAbs (p.y () - q.y ()); + + return (dx + dy == 1); +} + +//--------------------------------------------------------------------- + +// Returns a random integer from 0 to 999 inclusive. +static int RandomNumberFrom0to999 () +{ + return (KRandom::random () % 1000); +} + +//--------------------------------------------------------------------- + +// public static +QList kpPainter::interpolatePoints (const QPoint &startPoint, + const QPoint &endPoint, + bool cardinalAdjacency, + double probability) +{ +#if DEBUG_KP_PAINTER + kDebug () << "CALL(startPoint=" << startPoint + << ",endPoint=" << endPoint << ")"; +#endif + + QList ret; + + Q_ASSERT (probability >= 0.0 && probability <= 1.0); + const int probabilityTimes1000 = qRound (probability * 1000); +#define SHOULD_DRAW() (probabilityTimes1000 == 1000/*avoid ::RandomNumberFrom0to999() call*/ || \ + ::RandomNumberFrom0to999 () < probabilityTimes1000) + +#if 0 + kDebug () << "prob=" << probability + << " *1000=" << probabilityTimes1000; +#endif + + + // Derived from the zSprite2 Graphics Engine. + // "MODIFIED" comment shows deviation from zSprite2 and Bresenham's line + // algorithm. + + const int x1 = startPoint.x (), + y1 = startPoint.y (), + x2 = endPoint.x (), + y2 = endPoint.y (); + + // Difference of x and y values + const int dx = x2 - x1; + const int dy = y2 - y1; + + // Absolute values of differences + const int ix = qAbs (dx); + const int iy = qAbs (dy); + + // Larger of the x and y differences + const int inc = ix > iy ? ix : iy; + + // Plot location + int plotx = x1; + int ploty = y1; + + int x = 0; + int y = 0; + + if (SHOULD_DRAW ()) + ret.append (QPoint (plotx, ploty)); + + + for (int i = 0; i <= inc; i++) + { + // oldplotx is equally as valid but would look different + // (but nobody will notice which one it is) + const int oldploty = ploty; + int plot = 0; + + x += ix; + y += iy; + + if (x > inc) + { + plot++; + x -= inc; + + if (dx < 0) + plotx--; + else + plotx++; + } + + if (y > inc) + { + plot++; + y -= inc; + + if (dy < 0) + ploty--; + else + ploty++; + } + + if (plot) + { + if (cardinalAdjacency && plot == 2) + { + // MODIFIED: Every point is + // horizontally or vertically adjacent to another point (if there + // is more than 1 point, of course). This is in contrast to the + // ordinary line algorithm which can create diagonal adjacencies. + + if (SHOULD_DRAW ()) + ret.append (QPoint (plotx, oldploty)); + } + + if (SHOULD_DRAW ()) + ret.append (QPoint (plotx, ploty)); + } + } + +#undef SHOULD_DRAW + + return ret; +} + +//--------------------------------------------------------------------- + +// public static +void kpPainter::drawLine (kpImage *image, + int x1, int y1, int x2, int y2, + const kpColor &color, int penWidth) +{ + kpPixmapFX::drawLine (image, x1, y1, x2, y2, color, penWidth); +} + +//--------------------------------------------------------------------- + +// public static +void kpPainter::drawPolyline (kpImage *image, + const QPolygon &points, + const kpColor &color, int penWidth) +{ + kpPixmapFX::drawPolyline (image, points, color, penWidth); +} + +//--------------------------------------------------------------------- + +// public static +void kpPainter::drawPolygon (kpImage *image, + const QPolygon &points, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + bool isFinal) +{ + kpPixmapFX::drawPolygon (image, points, fcolor, penWidth, bcolor, isFinal); +} + +//--------------------------------------------------------------------- + + +// public static +void kpPainter::drawCurve (kpImage *image, + const QPoint &startPoint, + const QPoint &controlPointP, const QPoint &controlPointQ, + const QPoint &endPoint, + const kpColor &color, int penWidth) +{ + kpPixmapFX::drawCurve (image, + startPoint, controlPointP, controlPointQ, endPoint, color, penWidth); +} + +//--------------------------------------------------------------------- + + +// public static +void kpPainter::fillRect (kpImage *image, + int x, int y, int width, int height, + const kpColor &color) +{ + kpPixmapFX::fillRect (image, x, y, width, height, color); +} + +//--------------------------------------------------------------------- + + +// public static +void kpPainter::drawRect (kpImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor) +{ + kpPixmapFX::drawRect (image, x, y, width, height, fcolor, penWidth, bcolor); +} + +//--------------------------------------------------------------------- + +// public static +void kpPainter::drawRoundedRect (kpImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor) +{ + kpPixmapFX::drawRoundedRect (image, x, y, width, height, fcolor, penWidth, bcolor); +} + +//--------------------------------------------------------------------- + +// public static +void kpPainter::drawEllipse (kpImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor) +{ + kpPixmapFX::drawEllipse (image, x, y, width, height, fcolor, penWidth, bcolor); +} + +//--------------------------------------------------------------------- + + +// are operating on the original image +// (the original image is not passed to this function). +// +// = subset of the original image containing all the pixels in +// +// = the rectangle, relative to the painters, whose pixels we +// want to change +static bool ReadableImageWashRect (QPainter *rgbPainter, + const QImage &image, + const kpColor &colorToReplace, + const QRect &imageRect, const QRect &drawRect, + int processedColorSimilarity) +{ + bool didSomething = false; + +#if DEBUG_KP_PAINTER && 0 + kDebug () << "kppixmapfx.cpp:WashRect(imageRect=" << imageRect + << ",drawRect=" << drawRect + << ")" << endl; +#endif + + // If you're going to pass painter pointers, those painters had better be + // active (i.e. QPainter::begin() has been called). + Q_ASSERT (!rgbPainter || rgbPainter->isActive ()); + +// make use of scanline coherence +#define FLUSH_LINE() \ +{ \ + if (rgbPainter) { \ + if (startDrawX == x - 1) \ + rgbPainter->drawPoint (startDrawX + imageRect.x (), \ + y + imageRect.y ()); \ + else \ + rgbPainter->drawLine (startDrawX + imageRect.x (), \ + y + imageRect.y (), \ + x - 1 + imageRect.x (), \ + y + imageRect.y ()); \ + } \ + didSomething = true; \ + startDrawX = -1; \ +} + + const int maxY = drawRect.bottom () - imageRect.top (); + + const int minX = drawRect.left () - imageRect.left (); + const int maxX = drawRect.right () - imageRect.left (); + + for (int y = drawRect.top () - imageRect.top (); + y <= maxY; + y++) + { + int startDrawX = -1; + + int x; // for FLUSH_LINE() + for (x = minX; x <= maxX; x++) + { + #if DEBUG_KP_PAINTER && 0 + fprintf (stderr, "y=%i x=%i colorAtPixel=%08X colorToReplace=%08X ... ", + y, x, + kpPixmapFX::getColorAtPixel (image, QPoint (x, y)).toQRgb (), + colorToReplace.toQRgb ()); + #endif + if (kpPixmapFX::getColorAtPixel (image, QPoint (x, y)).isSimilarTo (colorToReplace, processedColorSimilarity)) + { + #if DEBUG_KP_PAINTER && 0 + fprintf (stderr, "similar\n"); + #endif + if (startDrawX < 0) + startDrawX = x; + } + else + { + #if DEBUG_KP_PAINTER && 0 + fprintf (stderr, "different\n"); + #endif + if (startDrawX >= 0) + FLUSH_LINE (); + } + } + + if (startDrawX >= 0) + FLUSH_LINE (); + } + +#undef FLUSH_LINE + + return didSomething; +} + +//--------------------------------------------------------------------- + +struct WashPack +{ + QPoint startPoint, endPoint; + kpColor color; + int penWidth, penHeight; + kpColor colorToReplace; + int processedColorSimilarity; + + QRect readableImageRect; + QImage readableImage; +}; + +//--------------------------------------------------------------------- + +static QRect Wash (kpImage *image, + const QPoint &startPoint, const QPoint &endPoint, + const kpColor &color, int penWidth, int penHeight, + const kpColor &colorToReplace, + int processedColorSimilarity, + QRect (*drawFunc) (QPainter * /*rgbPainter*/, void * /*data*/)) +{ + WashPack pack; + pack.startPoint = startPoint; pack.endPoint = endPoint; + pack.color = color; + pack.penWidth = penWidth; pack.penHeight = penHeight; + pack.colorToReplace = colorToReplace; + pack.processedColorSimilarity = processedColorSimilarity; + + + // Get the rectangle that bounds the changes and the pixmap for that + // rectangle. + const QRect normalizedRect = kpPainter::normalizedRect(pack.startPoint, pack.endPoint); + pack.readableImageRect = kpTool::neededRect (normalizedRect, + qMax (pack.penWidth, pack.penHeight)); +#if DEBUG_KP_PAINTER + kDebug () << "kppainter.cpp:Wash() startPoint=" << startPoint + << " endPoint=" << endPoint + << " --> normalizedRect=" << normalizedRect + << " readableImageRect=" << pack.readableImageRect + << endl; +#endif + pack.readableImage = kpPixmapFX::getPixmapAt (*image, pack.readableImageRect); + + QPainter painter(image); + return (*drawFunc)(&painter, &pack); +} + +//--------------------------------------------------------------------- + +void WashHelperSetup (QPainter *rgbPainter, const WashPack *pack) +{ + // Set the drawing colors for the painters. + + if (rgbPainter) + rgbPainter->setPen (pack->color.toQColor()); +} + +//--------------------------------------------------------------------- + +static QRect WashLineHelper (QPainter *rgbPainter, void *data) +{ +#if DEBUG_KP_PAINTER && 0 + kDebug () << "Washing pixmap (w=" << rect.width () + << ",h=" << rect.height () << ")" << endl; + QTime timer; + int convAndWashTime; +#endif + + WashPack *pack = static_cast (data); + + + // Setup painters. + ::WashHelperSetup (rgbPainter, pack); + + + bool didSomething = false; + + QList points = kpPainter::interpolatePoints (pack->startPoint, pack->endPoint); + for (QList ::const_iterator pit = points.constBegin (); + pit != points.constEnd (); + ++pit) + { + // OPT: This may be reading and possibly writing pixels that were + // visited on a previous iteration, since the pen is usually + // bigger than 1 pixel. Maybe we could use QRegion to determine + // all the non-intersecting regions and only wash each region once. + // + // Profiling needs to be done as QRegion is known to be a CPU hog. + if (::ReadableImageWashRect (rgbPainter, + pack->readableImage, + pack->colorToReplace, + pack->readableImageRect, + kpToolFlowBase::hotRectForMousePointAndBrushWidthHeight ( + *pit, pack->penWidth, pack->penHeight), + pack->processedColorSimilarity)) + { + didSomething = true; + } + } + + +#if DEBUG_KP_PAINTER && 0 + int ms = timer.restart (); + kDebug () << "\ttried to wash: " << ms << "ms" + << " (" << (ms ? (rect.width () * rect.height () / ms) : -1234) + << " pixels/ms)" + << endl; + convAndWashTime += ms; +#endif + + + // TODO: Rectangle may be too big. Use QRect::unite() incrementally? + // Efficiency? + return didSomething ? pack->readableImageRect : QRect (); +} + +//--------------------------------------------------------------------- + +// public static +QRect kpPainter::washLine (kpImage *image, + int x1, int y1, int x2, int y2, + const kpColor &color, int penWidth, int penHeight, + const kpColor &colorToReplace, + int processedColorSimilarity) +{ + return ::Wash (image, + QPoint (x1, y1), QPoint (x2, y2), + color, penWidth, penHeight, + colorToReplace, + processedColorSimilarity, + &::WashLineHelper); +} + +//--------------------------------------------------------------------- + +static QRect WashRectHelper (QPainter *rgbPainter, void *data) +{ +#if DEBUG_KP_PAINTER && 0 + kDebug () << "Washing pixmap (w=" << rect.width () + << ",h=" << rect.height () << ")" << endl; + QTime timer; + int convAndWashTime; +#endif + + WashPack *pack = static_cast (data); + + + // Setup painters. + ::WashHelperSetup (rgbPainter, pack); + + + const QRect drawRect (pack->startPoint, pack->endPoint); + + bool didSomething = false; + + if (::ReadableImageWashRect (rgbPainter, + pack->readableImage, + pack->colorToReplace, + pack->readableImageRect, + drawRect, + pack->processedColorSimilarity)) + { + didSomething = true; + } + + +#if DEBUG_KP_PAINTER && 0 + int ms = timer.restart (); + kDebug () << "\ttried to wash: " << ms << "ms" + << " (" << (ms ? (rect.width () * rect.height () / ms) : -1234) + << " pixels/ms)" + << endl; + convAndWashTime += ms; +#endif + + + return didSomething ? drawRect : QRect (); +} + +//--------------------------------------------------------------------- + +// public static +QRect kpPainter::washRect (kpImage *image, + int x, int y, int width, int height, + const kpColor &color, + const kpColor &colorToReplace, + int processedColorSimilarity) +{ + return ::Wash (image, + QPoint (x, y), QPoint (x + width - 1, y + height - 1), + color, 1/*pen width*/, 1/*pen height*/, + colorToReplace, + processedColorSimilarity, + &::WashRectHelper); +} + +//--------------------------------------------------------------------- + +// public static +void kpPainter::sprayPoints (kpImage *image, + const QList &points, + const kpColor &color, + int spraycanSize) +{ +#if DEBUG_KP_PAINTER + kDebug () << "kpPainter::sprayPoints()"; +#endif + + Q_ASSERT (spraycanSize > 0); + + QPainter painter(image); + const int radius = spraycanSize / 2; + + // Set the drawing colors for the painters. + + painter.setPen(color.toQColor()); + + foreach (const QPoint &p, points) + { + for (int i = 0; i < 10; i++) + { + const int dx = (KRandom::random () % spraycanSize) - radius; + const int dy = (KRandom::random () % spraycanSize) - radius; + + // Make it look circular. + // TODO: Can be done better by doing a random vector angle & length + // but would sin and cos be too slow? + if ((dx * dx) + (dy * dy) > (radius * radius)) + continue; + + const QPoint p2 (p.x () + dx, p.y () + dy); + + painter.drawPoint(p2); + } + } +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/imagelib/kpPainter.h b/kolourpaint/imagelib/kpPainter.h new file mode 100644 index 00000000..fe9ea2fc --- /dev/null +++ b/kolourpaint/imagelib/kpPainter.h @@ -0,0 +1,188 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_PAINTER_H +#define KP_PAINTER_H + + +#include +#include + + +class QPolygon; + + +// +// Stateless painter with sane semantics that works on kpImage's i.e. it +// works on document - not view - data. If you find that you need state, +// you should probably move it into kpPainter to avoid the overhead of +// passing around this state (e.g. color, line width) and for reuse. +// +// kpPainter is to kpImage as QPainter is to QPixmap. +// +// This encapsulates the set of functionality used by all of KolourPaint's +// document drawing functions and nothing more, permitting rewriting of +// the image library. Currently uses QPainter/kpPixmapFX as the image library. +// + +struct kpPainterPrivate; + +class kpPainter +{ +public: + // helper to make a correct QRect out of 2 QPoints regardless of their relative position + // to each other + static QRect normalizedRect(const QPoint& p1, const QPoint& p2) + { + return QRect(qMin(p1.x(), p2.x()), qMin(p1.y(), p2.y()), + qAbs(p2.x() - p1.x()) + 1, qAbs(p2.y() - p1.y()) + 1); + } + + // Returns whether the given points are cardinally adjacent (i.e. one point + // is exactly 1 pixel north, east, south or west of the other). Equal + // points are not cardinally adjacent. + static bool pointsAreCardinallyAdjacent (const QPoint &p, const QPoint &q); + + // Returns a list of points representing a straight line from + // to inclusive, using Bresenham's line algorithm. Each point + // is created only with the specified . + // + // If is set, a modified Bresenham's algorithm will add + // an extra point between every pair of originally strictly-diagonally-adjacent + // points, such that these points become cardinally adjacent. However, these + // extra points are also created only with the specified . + // + // For instance, must be set if a diagonal line is to + // drawn at each of the returned points, otherwise things won't look right: + // + // .\..... + // \.\.... + // .\.B... + // ..Ac\.. + // ...\.\. + // ....\.. + // + // 'A' is the previous Bresenham point. 'B' is the new point. See how if + // diagonal lines are drawn at A and B, there is a gap between the lines. + // Setting will solve this problem, since it will add + // a point at 'c'. + // + // ASSUMPTION: is between 0.0 and 1.0 inclusive. + static QList interpolatePoints (const QPoint &startPoint, + const QPoint &endPoint, + bool cardinalAdjacency = false, + double probability = 1.0); + + // Draws a line from (x1,y1) to (x2,y2) onto , with + // and . The corners are rounded and centred at those + // coordinates so if > 1, the line is likely to extend past + // a rectangle with those corners. + static void drawLine (kpImage *image, + int x1, int y1, int x2, int y2, + const kpColor &color, int penWidth); + static void drawPolyline (kpImage *image, + const QPolygon &points, + const kpColor &color, int penWidth); + // = shape completed else drawing but haven't finalised. + // If not , the edge that would form the closure, if the + // shape were finalised now, is highlighted specially. Unfortunately, + // the argument is currently ignored. + // + // Odd-even fill. + static void drawPolygon (kpImage *image, + const QPolygon &points, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor = kpColor::Invalid, + bool isFinal = true); + // Cubic Beizer. + static void drawCurve (kpImage *image, + const QPoint &startPoint, + const QPoint &controlPointP, const QPoint &controlPointQ, + const QPoint &endPoint, + const kpColor &color, int penWidth); + + static void fillRect (kpImage *image, + int x, int y, int width, int height, + const kpColor &color); + + // Draws a rectangle / rounded rectangle / ellipse with top-left at + // (x, y) with width and height . Unlike QPainter, + // this rectangle will really fit inside x and won't + // be one pixel higher or wider etc. + // + // and must be >= 0. + // + // must not be invalid. However, may be invalid + // to signify an unfilled rectangle / rounded rectangle /ellipse. + static void drawRect (kpImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, int penWidth = 1, + const kpColor &bcolor = kpColor::Invalid); + static void drawRoundedRect (kpImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, int penWidth = 1, + const kpColor &bcolor = kpColor::Invalid); + static void drawEllipse (kpImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, int penWidth = 1, + const kpColor &bcolor = kpColor::Invalid); + + // Replaces all pixels of on the line + // from (x1,y1) to (x2,y2) of , with a pen of with + // dimensions x. + // + // The corners are centred at those coordinates so if > 1 or + // > 1, the line is likely to extend past a rectangle with + // those corners. + // + // Returns the dirty rectangle. + static QRect washLine (kpImage *image, + int x1, int y1, int x2, int y2, + const kpColor &color, int penWidth, int penHeight, + const kpColor &colorToReplace, + int processedColorSimilarity); + + static QRect washRect (kpImage *image, + int x, int y, int width, int height, + const kpColor &color, + const kpColor &colorToReplace, + int processedColorSimilarity); + + // For each point in , sprays a random pattern of 10 dots of , + // each within a circle of diameter , onto . + // + // ASSUMPTION: spraycanSize > 0. + // TODO: I think this diameter is 1 or 2 off. + static void sprayPoints (kpImage *image, + const QList &points, + const kpColor &color, + int spraycanSize); +}; + + +#endif // KP_PAINTER_H diff --git a/kolourpaint/imagelib/transforms/kpTransformAutoCrop.cpp b/kolourpaint/imagelib/transforms/kpTransformAutoCrop.cpp new file mode 100644 index 00000000..bee701a8 --- /dev/null +++ b/kolourpaint/imagelib/transforms/kpTransformAutoCrop.cpp @@ -0,0 +1,769 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// TODO: Color Similarity is obviously useful in Autocrop but it isn't +// obvious as to how to implement it. The current heuristic, +// for each side, chooses an arbitrary reference color for which +// all other candidate pixels in that side are tested against +// for similarity. But if the reference color happens to be at +// one extreme of the range of colors in that side, then pixels +// at the other extreme would not be deemed similar enough. The +// key is to find the median color as the reference but how do +// you do this if you don't know which pixels to sample in the first +// place (that's what you're trying to find)? Chicken and egg situation. +// +// The other heuristic that is in doubt is the use of the average +// color in determining the similarity of sides (it is possible +// to get vastly differently colors in both sides yet they will be +// considered similar). + +#define DEBUG_KP_TOOL_AUTO_CROP 0 + + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +class kpTransformAutoCropBorder +{ +public: + // WARNING: Only call the with imagePtr = 0 if you are going to use + // operator= to fill it in with a valid imagePtr immediately + // afterwards. + kpTransformAutoCropBorder (const kpImage *imagePtr = 0, int processedColorSimilarity = 0); + + kpCommandSize::SizeType size () const; + + const kpImage *image () const; + int processedColorSimilarity () const; + QRect rect () const; + int left () const; + int right () const; + int top () const; + int bottom () const; + kpColor referenceColor () const; + kpColor averageColor () const; + bool isSingleColor () const; + + // (returns true on success (even if no rect) or false on error) + bool calculate (int isX, int dir); + + bool fillsEntireImage () const; + bool exists () const; + void invalidate (); + +private: + const kpImage *m_imagePtr; + int m_processedColorSimilarity; + + QRect m_rect; + kpColor m_referenceColor; + int m_redSum, m_greenSum, m_blueSum; + bool m_isSingleColor; +}; + +kpTransformAutoCropBorder::kpTransformAutoCropBorder (const kpImage *imagePtr, + int processedColorSimilarity) + : m_imagePtr (imagePtr), + m_processedColorSimilarity (processedColorSimilarity) +{ + invalidate (); +} + + +// public +kpCommandSize::SizeType kpTransformAutoCropBorder::size () const +{ + return sizeof (kpTransformAutoCropBorder); +} + + +// public +const kpImage *kpTransformAutoCropBorder::image () const +{ + return m_imagePtr; +} + +// public +int kpTransformAutoCropBorder::processedColorSimilarity () const +{ + return m_processedColorSimilarity; +} + +// public +QRect kpTransformAutoCropBorder::rect () const +{ + return m_rect; +} + +// public +int kpTransformAutoCropBorder::left () const +{ + return m_rect.left (); +} + +// public +int kpTransformAutoCropBorder::right () const +{ + return m_rect.right (); +} + +// public +int kpTransformAutoCropBorder::top () const +{ + return m_rect.top (); +} + +// public +int kpTransformAutoCropBorder::bottom () const +{ + return m_rect.bottom (); +} + +// public +kpColor kpTransformAutoCropBorder::referenceColor () const +{ + return m_referenceColor; +} + +// public +kpColor kpTransformAutoCropBorder::averageColor () const +{ + if (!m_rect.isValid ()) + return kpColor::Invalid; + + if (m_referenceColor.isTransparent ()) + return kpColor::Transparent; + else if (m_processedColorSimilarity == 0) + return m_referenceColor; + else + { + int numPixels = (m_rect.width () * m_rect.height ()); + Q_ASSERT (numPixels > 0); + + return kpColor (m_redSum / numPixels, + m_greenSum / numPixels, + m_blueSum / numPixels); + } +} + +//--------------------------------------------------------------------- + +bool kpTransformAutoCropBorder::isSingleColor () const +{ + return m_isSingleColor; +} + +//--------------------------------------------------------------------- + +// public +bool kpTransformAutoCropBorder::calculate (int isX, int dir) +{ +#if DEBUG_KP_TOOL_AUTO_CROP && 1 + kDebug () << "kpTransformAutoCropBorder::calculate() CALLED!"; +#endif + int maxX = m_imagePtr->width () - 1; + int maxY = m_imagePtr->height () - 1; + + QImage qimage = *m_imagePtr; + Q_ASSERT (!qimage.isNull ()); + + // (sync both branches) + if (isX) + { + int numCols = 0; + int startX = (dir > 0) ? 0 : maxX; + + kpColor col = kpPixmapFX::getColorAtPixel (qimage, startX, 0); + for (int x = startX; + x >= 0 && x <= maxX; + x += dir) + { + int y; + for (y = 0; y <= maxY; y++) + { + if (!kpPixmapFX::getColorAtPixel (qimage, x, y).isSimilarTo (col, m_processedColorSimilarity)) + break; + } + + if (y <= maxY) + break; + else + numCols++; + } + + if (numCols) + { + m_rect = + kpPainter::normalizedRect(QPoint(startX, 0), + QPoint(startX + (numCols - 1) * dir, maxY)); + m_referenceColor = col; + } + } + else + { + int numRows = 0; + int startY = (dir > 0) ? 0 : maxY; + + kpColor col = kpPixmapFX::getColorAtPixel (qimage, 0, startY); + for (int y = startY; + y >= 0 && y <= maxY; + y += dir) + { + int x; + for (x = 0; x <= maxX; x++) + { + if (!kpPixmapFX::getColorAtPixel (qimage, x, y).isSimilarTo (col, m_processedColorSimilarity)) + break; + } + + if (x <= maxX) + break; + else + numRows++; + } + + if (numRows) + { + m_rect = kpPainter::normalizedRect(QPoint(0, startY), + QPoint(maxX, startY + (numRows - 1) * dir)); + m_referenceColor = col; + } + } + + + if (m_rect.isValid ()) + { + m_isSingleColor = true; + + if (m_processedColorSimilarity != 0) + { + for (int y = m_rect.top (); y <= m_rect.bottom (); y++) + { + for (int x = m_rect.left (); x <= m_rect.right (); x++) + { + kpColor colAtPixel = kpPixmapFX::getColorAtPixel (qimage, x, y); + + if (m_isSingleColor && colAtPixel != m_referenceColor) + m_isSingleColor = false; + + m_redSum += colAtPixel.red (); + m_greenSum += colAtPixel.green (); + m_blueSum += colAtPixel.blue (); + } + } + } + } + + + return true; +} + +// public +bool kpTransformAutoCropBorder::fillsEntireImage () const +{ + return (m_rect == m_imagePtr->rect ()); +} + +// public +bool kpTransformAutoCropBorder::exists () const +{ + // (will use in an addition so make sure returns 1 or 0) + return (m_rect.isValid () ? 1 : 0); +} + +// public +void kpTransformAutoCropBorder::invalidate () +{ + m_rect = QRect (); + m_referenceColor = kpColor::Invalid; + m_redSum = m_greenSum = m_blueSum = 0; + m_isSingleColor = false; +} + + +struct kpTransformAutoCropCommandPrivate +{ + bool actOnSelection; + kpTransformAutoCropBorder leftBorder, rightBorder, topBorder, botBorder; + kpImage *leftImage, *rightImage, *topImage, *botImage; + + QRect contentsRect; + int oldWidth, oldHeight; + kpAbstractImageSelection *oldSelectionPtr; +}; + +// REFACTOR: Move to /commands/ +kpTransformAutoCropCommand::kpTransformAutoCropCommand (bool actOnSelection, + const kpTransformAutoCropBorder &leftBorder, + const kpTransformAutoCropBorder &rightBorder, + const kpTransformAutoCropBorder &topBorder, + const kpTransformAutoCropBorder &botBorder, + kpCommandEnvironment *environ) + : kpNamedCommand(text(actOnSelection, DontShowAccel), environ), + d (new kpTransformAutoCropCommandPrivate ()) +{ + d->actOnSelection = actOnSelection; + d->leftBorder = leftBorder; + d->rightBorder = rightBorder; + d->topBorder = topBorder; + d->botBorder = botBorder; + d->leftImage = 0; + d->rightImage = 0; + d->topImage = 0; + d->botImage = 0; + + kpDocument *doc = document (); + Q_ASSERT (doc); + + d->oldWidth = doc->width (d->actOnSelection); + d->oldHeight = doc->height (d->actOnSelection); + + d->oldSelectionPtr = 0; +} + +//--------------------------------------------------------------------- + +kpTransformAutoCropCommand::~kpTransformAutoCropCommand () +{ + deleteUndoImages (); + + delete d->oldSelectionPtr; + delete d; +} + +//--------------------------------------------------------------------- +// public static + +QString kpTransformAutoCropCommand::text(bool actOnSelection, int options) +{ + if (actOnSelection) + { + if (options & kpTransformAutoCropCommand::ShowAccel) + return i18n ("Remove Internal B&order"); + else + return i18n ("Remove Internal Border"); + } + else + { + if (options & kpTransformAutoCropCommand::ShowAccel) + return i18n ("Autocr&op"); + else + return i18n ("Autocrop"); + } +} + +//--------------------------------------------------------------------- +// public virtual [base kpCommand] + +kpCommandSize::SizeType kpTransformAutoCropCommand::size () const +{ + return d->leftBorder.size () + + d->rightBorder.size () + + d->topBorder.size () + + d->botBorder.size () + + ImageSize (d->leftImage) + + ImageSize (d->rightImage) + + ImageSize (d->topImage) + + ImageSize (d->botImage) + + SelectionSize (d->oldSelectionPtr); +} + +//--------------------------------------------------------------------- +// private + +void kpTransformAutoCropCommand::getUndoImage (const kpTransformAutoCropBorder &border, kpImage **image) +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + +#if DEBUG_KP_TOOL_AUTO_CROP && 1 + kDebug () << "kpTransformAutoCropCommand::getUndoImage()"; + kDebug () << "\timage=" << image + << " border: rect=" << border.rect () + << " isSingleColor=" << border.isSingleColor () + << endl; +#endif + + if (image && border.exists () && !border.isSingleColor ()) + { + if (*image) + { + #if DEBUG_KP_TOOL_AUTO_CROP && 1 + kDebug () << "\talready have *image - delete it"; + #endif + delete *image; + } + + *image = new kpImage ( + kpPixmapFX::getPixmapAt (doc->image (d->actOnSelection), + border.rect ())); + } +} + + +// private +void kpTransformAutoCropCommand::getUndoImages () +{ + getUndoImage (d->leftBorder, &d->leftImage); + getUndoImage (d->rightBorder, &d->rightImage); + getUndoImage (d->topBorder, &d->topImage); + getUndoImage (d->botBorder, &d->botImage); +} + +// private +void kpTransformAutoCropCommand::deleteUndoImages () +{ +#if DEBUG_KP_TOOL_AUTO_CROP && 1 + kDebug () << "kpTransformAutoCropCommand::deleteUndoImages()"; +#endif + + delete d->leftImage; d->leftImage = 0; + delete d->rightImage; d->rightImage = 0; + delete d->topImage; d->topImage = 0; + delete d->botImage; d->botImage = 0; +} + + +// public virtual [base kpCommand] +void kpTransformAutoCropCommand::execute () +{ + if (!d->contentsRect.isValid ()) + d->contentsRect = contentsRect (); + + + getUndoImages (); + + + kpDocument *doc = document (); + Q_ASSERT (doc); + + + kpImage imageWithoutBorder = + kpTool::neededPixmap (doc->image (d->actOnSelection), + d->contentsRect); + + + if (!d->actOnSelection) + doc->setImage (imageWithoutBorder); + else + { + d->oldSelectionPtr = doc->imageSelection ()->clone (); + d->oldSelectionPtr->setBaseImage (kpImage ()); + + // d->contentsRect is relative to the top of the sel + // while sel is relative to the top of the doc + QRect rect = d->contentsRect; + rect.translate (d->oldSelectionPtr->x (), d->oldSelectionPtr->y ()); + + kpRectangularImageSelection sel ( + rect, + imageWithoutBorder, + d->oldSelectionPtr->transparency ()); + + doc->setSelection (sel); + + environ ()->somethingBelowTheCursorChanged (); + } +} + +// public virtual [base kpCommand] +void kpTransformAutoCropCommand::unexecute () +{ +#if DEBUG_KP_TOOL_AUTO_CROP && 1 + kDebug () << "kpTransformAutoCropCommand::unexecute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpImage image (d->oldWidth, d->oldHeight, QImage::Format_ARGB32_Premultiplied); + + // restore the position of the center image + kpPixmapFX::setPixmapAt (&image, d->contentsRect, + doc->image (d->actOnSelection)); + + // draw the borders + + const kpTransformAutoCropBorder *borders [] = + { + &d->leftBorder, &d->rightBorder, + &d->topBorder, &d->botBorder, + 0 + }; + + const kpImage *images [] = + { + d->leftImage, d->rightImage, + d->topImage, d->botImage, + 0 + }; + + const kpImage **p = images; + for (const kpTransformAutoCropBorder **b = borders; *b; b++, p++) + { + if (!(*b)->exists ()) + continue; + + if ((*b)->isSingleColor ()) + { + kpColor col = (*b)->referenceColor (); + #if DEBUG_KP_TOOL_AUTO_CROP && 1 + kDebug () << "\tdrawing border " << (*b)->rect () + << " rgb=" << (int *) col.toQRgb () /* %X hack */ << endl; + #endif + + const QRect r = (*b)->rect (); + kpPainter::fillRect (&image, + r.x (), r.y (), r.width (), r.height (), + col); + } + else + { + #if DEBUG_KP_TOOL_AUTO_CROP && 1 + kDebug () << "\trestoring border image " << (*b)->rect (); + #endif + if (*p) + { + // REFACTOR: Add equivalent method to kpPainter and use. + kpPixmapFX::setPixmapAt (&image, (*b)->rect (), **p); + } + } + } + + + if (!d->actOnSelection) + doc->setImage (image); + else + { + d->oldSelectionPtr->setBaseImage (image); + + doc->setSelection (*d->oldSelectionPtr); + delete d->oldSelectionPtr; d->oldSelectionPtr = 0; + + environ ()->somethingBelowTheCursorChanged (); + } + + + deleteUndoImages (); +} + + +// private +QRect kpTransformAutoCropCommand::contentsRect () const +{ + const kpImage image = document ()->image (d->actOnSelection); + + QPoint topLeft (d->leftBorder.exists () ? + d->leftBorder.rect ().right () + 1 : + 0, + d->topBorder.exists () ? + d->topBorder.rect ().bottom () + 1 : + 0); + QPoint botRight (d->rightBorder.exists () ? + d->rightBorder.rect ().left () - 1 : + image.width () - 1, + d->botBorder.exists () ? + d->botBorder.rect ().top () - 1 : + image.height () - 1); + + return QRect (topLeft, botRight); +} + + +static void ShowNothingToAutocropMessage (kpMainWindow *mainWindow, bool actOnSelection) +{ + kpSetOverrideCursorSaver cursorSaver (Qt::ArrowCursor); + + if (actOnSelection) + { + KMessageBox::information (mainWindow, + i18n ("KolourPaint cannot remove the selection's internal border as it" + " could not be located."), + i18nc ("@title:window", "Cannot Remove Internal Border"), + "NothingToAutoCrop"); + } + else + { + KMessageBox::information (mainWindow, + i18n ("KolourPaint cannot automatically crop the image as its" + " border could not be located."), + i18nc ("@title:window", "Cannot Autocrop"), + "NothingToAutoCrop"); + } +} + +bool kpTransformAutoCrop (kpMainWindow *mainWindow) +{ +#if DEBUG_KP_TOOL_AUTO_CROP + kDebug () << "kpTransformAutoCrop() CALLED!"; +#endif + + Q_ASSERT (mainWindow); + kpDocument *doc = mainWindow->document (); + Q_ASSERT (doc); + + // OPT: if already pulled selection image, no need to do it again here + kpImage image = doc->selection () ? doc->getSelectedBaseImage () : doc->image (); + Q_ASSERT (!image.isNull ()); + + kpViewManager *vm = mainWindow->viewManager (); + Q_ASSERT (vm); + + int processedColorSimilarity = mainWindow->colorToolBar ()->processedColorSimilarity (); + kpTransformAutoCropBorder leftBorder (&image, processedColorSimilarity), + rightBorder (&image, processedColorSimilarity), + topBorder (&image, processedColorSimilarity), + botBorder (&image, processedColorSimilarity); + + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + mainWindow->colorToolBar ()->flashColorSimilarityToolBarItem (); + + // TODO: With Colour Similarity, a lot of weird (and wonderful) things can + // happen resulting in a huge number of code paths. Needs refactoring + // and regression testing. + // + // TODO: e.g. When the top fills entire rect but bot doesn't we could + // invalidate top and continue autocrop. + int numRegions = 0; + if (!leftBorder.calculate (true/*x*/, +1/*going right*/) || + leftBorder.fillsEntireImage () || + !rightBorder.calculate (true/*x*/, -1/*going left*/) || + rightBorder.fillsEntireImage () || + !topBorder.calculate (false/*y*/, +1/*going down*/) || + topBorder.fillsEntireImage () || + !botBorder.calculate (false/*y*/, -1/*going up*/) || + botBorder.fillsEntireImage () || + ((numRegions = leftBorder.exists () + + rightBorder.exists () + + topBorder.exists () + + botBorder.exists ()) == 0)) + { + #if DEBUG_KP_TOOL_AUTO_CROP + kDebug () << "\tcan't find border; leftBorder.rect=" << leftBorder.rect () + << " rightBorder.rect=" << rightBorder.rect () + << " topBorder.rect=" << topBorder.rect () + << " botBorder.rect=" << botBorder.rect () + << endl; + #endif + ::ShowNothingToAutocropMessage (mainWindow, (bool) doc->selection ()); + return false; + } + +#if DEBUG_KP_TOOL_AUTO_CROP + kDebug () << "\tnumRegions=" << numRegions; + kDebug () << "\t\tleft=" << leftBorder.rect () + << " refCol=" << (leftBorder.exists () ? (int *) leftBorder.referenceColor ().toQRgb () : 0) + << " avgCol=" << (leftBorder.exists () ? (int *) leftBorder.averageColor ().toQRgb () : 0) + << endl; + kDebug () << "\t\tright=" << rightBorder.rect () + << " refCol=" << (rightBorder.exists () ? (int *) rightBorder.referenceColor ().toQRgb () : 0) + << " avgCol=" << (rightBorder.exists () ? (int *) rightBorder.averageColor ().toQRgb () : 0) + << endl; + kDebug () << "\t\ttop=" << topBorder.rect () + << " refCol=" << (topBorder.exists () ? (int *) topBorder.referenceColor ().toQRgb () : 0) + << " avgCol=" << (topBorder.exists () ? (int *) topBorder.averageColor ().toQRgb () : 0) + << endl; + kDebug () << "\t\tbot=" << botBorder.rect () + << " refCol=" << (botBorder.exists () ? (int *) botBorder.referenceColor ().toQRgb () : 0) + << " avgCol=" << (botBorder.exists () ? (int *) botBorder.averageColor ().toQRgb () : 0) + << endl; +#endif + + + // In case e.g. the user pastes a solid, coloured-in rectangle, + // we favor killing the bottom and right regions + // (these regions probably contain the unwanted whitespace due + // to the doc being bigger than the pasted selection to start with). + // + // We also kill if they kiss or even overlap. + + if (leftBorder.exists () && rightBorder.exists ()) + { + const kpColor leftCol = leftBorder.averageColor (); + const kpColor rightCol = rightBorder.averageColor (); + + if ((numRegions == 2 && !leftCol.isSimilarTo (rightCol, processedColorSimilarity)) || + leftBorder.right () >= rightBorder.left () - 1) // kissing or overlapping + { + #if DEBUG_KP_TOOL_AUTO_CROP + kDebug () << "\tignoring left border"; + #endif + leftBorder.invalidate (); + } + } + + if (topBorder.exists () && botBorder.exists ()) + { + const kpColor topCol = topBorder.averageColor (); + const kpColor botCol = botBorder.averageColor (); + + if ((numRegions == 2 && !topCol.isSimilarTo (botCol, processedColorSimilarity)) || + topBorder.bottom () >= botBorder.top () - 1) // kissing or overlapping + { + #if DEBUG_KP_TOOL_AUTO_CROP + kDebug () << "\tignoring top border"; + #endif + topBorder.invalidate (); + } + } + + + mainWindow->addImageOrSelectionCommand ( + new kpTransformAutoCropCommand ( + (bool) doc->selection (), + leftBorder, rightBorder, + topBorder, botBorder, + mainWindow->commandEnvironment ())); + + + return true; +} diff --git a/kolourpaint/imagelib/transforms/kpTransformAutoCrop.h b/kolourpaint/imagelib/transforms/kpTransformAutoCrop.h new file mode 100644 index 00000000..df2a93fe --- /dev/null +++ b/kolourpaint/imagelib/transforms/kpTransformAutoCrop.h @@ -0,0 +1,85 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_AUTO_CROP_H +#define KP_TOOL_AUTO_CROP_H + + +#include + + +class QRect; + +//class kpImage; +class kpMainWindow; +class kpTransformAutoCropBorder; + + +// REFACTOR: This should be moved into /commands/ +class kpTransformAutoCropCommand : public kpNamedCommand +{ +public: + kpTransformAutoCropCommand (bool actOnSelection, + const kpTransformAutoCropBorder &leftBorder, + const kpTransformAutoCropBorder &rightBorder, + const kpTransformAutoCropBorder &topBorder, + const kpTransformAutoCropBorder &botBorder, + kpCommandEnvironment *environ); + virtual ~kpTransformAutoCropCommand (); + + enum NameOptions + { + DontShowAccel = 0, + ShowAccel = 1 + }; + + static QString text(bool actOnSelection, int options); + + virtual SizeType size () const; + +private: + void getUndoImage (const kpTransformAutoCropBorder &border, kpImage **image); + void getUndoImages (); + void deleteUndoImages (); + +public: + virtual void execute (); + virtual void unexecute (); + +private: + QRect contentsRect () const; + + struct kpTransformAutoCropCommandPrivate *d; +}; + + +// (returns true on success (even if it did nothing) or false on error) +bool kpTransformAutoCrop (kpMainWindow *mainWindow); + + +#endif // KP_TOOL_AUTO_CROP_H diff --git a/kolourpaint/imagelib/transforms/kpTransformCrop.cpp b/kolourpaint/imagelib/transforms/kpTransformCrop.cpp new file mode 100644 index 00000000..dc295956 --- /dev/null +++ b/kolourpaint/imagelib/transforms/kpTransformCrop.cpp @@ -0,0 +1,76 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_CROP 0 + + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + + +void kpTransformCrop (kpMainWindow *mainWindow) +{ + kpDocument *doc = mainWindow->document (); + Q_ASSERT (doc); + + kpAbstractSelection *sel = doc->selection (); + Q_ASSERT (sel); + + + kpCommand *resizeDocCommand = + new kpTransformResizeScaleCommand ( + false/*act on doc, not sel*/, + sel->width (), sel->height (), + kpTransformResizeScaleCommand::Resize, + mainWindow->commandEnvironment ()); + + + kpTextSelection *textSel = + dynamic_cast (sel); + kpAbstractImageSelection *imageSel = + dynamic_cast (sel); + // It's either a text selection or an image selection, but cannot be + // neither or both. + Q_ASSERT (!!textSel != !!imageSel); + + if (textSel) + ::kpTransformCrop_TextSelection (mainWindow, i18n ("Set as Image"), resizeDocCommand); + else if (imageSel) + ::kpTransformCrop_ImageSelection (mainWindow, i18n ("Set as Image"), resizeDocCommand); + else + Q_ASSERT (!"unreachable"); +} diff --git a/kolourpaint/imagelib/transforms/kpTransformCrop.h b/kolourpaint/imagelib/transforms/kpTransformCrop.h new file mode 100644 index 00000000..2a5ba48c --- /dev/null +++ b/kolourpaint/imagelib/transforms/kpTransformCrop.h @@ -0,0 +1,83 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_CROP_H +#define KP_TOOL_CROP_H + + +class kpMainWindow; + + +// +// ASSUMPTION: There is a current selection. +// +// +// In all cases, the document is resized to be the same size as the bounding +// rectangle of the selection. Additional behavior depends on the type of +// selection: +// +// +// If it's a text box: +// +// 1. It's moved to (0, 0) and kept editable. +// +// 2. The document background always becomes completely transparent. +// +// Text boxes with transparent backgrounds, before calling this method, +// antialias their text with the pixels of the document below. Such +// pixels are unlikely to be all of the same color, so there is no single +// "correct" color for the new document background. We choose transparent +// because it's the most neutral and forces the text to not antialias. +// TODO: Perhaps a better approach would have been to simply copy the +// pixels of the document below the text box to (0, 0)? +// +// For text boxes with opaque backgrounds, the new transparent document +// background means that the extents of text boxes are clear, when the +// boxes are moved around -- this is handy. +// +// +// If it's an image selection: +// +// 1. The pixels of the document starting from position (0, 0) are set the +// same as those inside the selection region. Unlike other image selection +// commands, if the selection is not floating, there is still no pulling +// of the selection from the document. +// +// The pixels outside the selection region are set to the background color. +// +// 2. The selection border is discarded -- even if the selection was floating +// before -- and replaced by a new one, of the same shape, but located at (0, 0). +// This allows the user to pull off a selection, if they would like. +// +// For user convenience, this border is created by a undoable +// create-selection-border command added to the undo history. +// +void kpTransformCrop (kpMainWindow *mainWindow); + + +#endif // KP_TOOL_CROP_H diff --git a/kolourpaint/imagelib/transforms/kpTransformCropPrivate.h b/kolourpaint/imagelib/transforms/kpTransformCropPrivate.h new file mode 100644 index 00000000..08f69612 --- /dev/null +++ b/kolourpaint/imagelib/transforms/kpTransformCropPrivate.h @@ -0,0 +1,49 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformCropPrivate_H +#define kpTransformCropPrivate_H + + +class QString; + +class kpCommand; +class kpMainWindow; + + +// Adds a kpMacroCommand, with name , to the command history. +// +// The first subcommand of this kpMacroCommand should be +// which resizes the document to the size of the selection. +void kpTransformCrop_TextSelection (kpMainWindow *mainWindow, + const QString &commandName, kpCommand *resizeDocCommand); +void kpTransformCrop_ImageSelection (kpMainWindow *mainWindow, + const QString &commandName, kpCommand *resizeDocCommand); + + +#endif // kpTransformCropPrivate_H diff --git a/kolourpaint/imagelib/transforms/kpTransformCrop_ImageSelection.cpp b/kolourpaint/imagelib/transforms/kpTransformCrop_ImageSelection.cpp new file mode 100644 index 00000000..abb2f687 --- /dev/null +++ b/kolourpaint/imagelib/transforms/kpTransformCrop_ImageSelection.cpp @@ -0,0 +1,269 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_CROP 0 + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// See the "image selection" part of the kpTransformCrop() API Doc. +// +// REFACTOR: Move into commands/ +class SetDocumentToSelectionImageCommand : public kpCommand +{ +public: + SetDocumentToSelectionImageCommand (kpCommandEnvironment *environ); + virtual ~SetDocumentToSelectionImageCommand (); + + /* (uninteresting child of macro cmd) */ + virtual QString name () const { return QString(); } + + virtual kpCommandSize::SizeType size () const + { + return ImageSize (m_oldImage) + + SelectionSize (m_fromSelectionPtr) + + ImageSize (m_imageIfFromSelectionDoesntHaveOne); + } + + // ASSUMPTION: Document has been resized to be the same size as the + // selection. + virtual void execute (); + virtual void unexecute (); + +protected: + kpColor m_backgroundColor; + kpImage m_oldImage; + kpAbstractImageSelection *m_fromSelectionPtr; + kpImage m_imageIfFromSelectionDoesntHaveOne; +}; + + +SetDocumentToSelectionImageCommand::SetDocumentToSelectionImageCommand (kpCommandEnvironment *environ) + : kpCommand (environ), + m_backgroundColor (environ->backgroundColor ()), + m_fromSelectionPtr ( + dynamic_cast ( + environ->document ()->selection ()->clone ())) +{ + Q_ASSERT (m_fromSelectionPtr); + + if ( m_fromSelectionPtr ) // make coverity happy + { + m_imageIfFromSelectionDoesntHaveOne = + m_fromSelectionPtr->hasContent () ? + kpImage () : + document ()->getSelectedBaseImage (); + } +} + +//--------------------------------------------------------------------- + +SetDocumentToSelectionImageCommand::~SetDocumentToSelectionImageCommand () +{ + delete m_fromSelectionPtr; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void SetDocumentToSelectionImageCommand::execute () +{ +#if DEBUG_KP_TOOL_CROP + kDebug () << "SetDocumentToSelectionImageCommand::execute()"; +#endif + + viewManager ()->setQueueUpdates (); + { + // kpTransformCrop_ImageSelection's has + // executed, resizing the document to be the size of the selection + // bounding rectangle. + Q_ASSERT (document ()->width () == m_fromSelectionPtr->width ()); + Q_ASSERT (document ()->height () == m_fromSelectionPtr->height ()); + m_oldImage = document ()->image (); + + + // + // e.g. original elliptical selection: + // + // t/---\ T = original transparent selection pixel + // | TT | t = outside the selection region + // t\__/t [every other character] = original opaque selection pixel + // + // Afterwards, the _document_ image becomes: + // + // b/---\ T = [unchanged] + // | TT | b = background color + // b\__/b [every other character] = [unchanged] + // + // The selection is deleted. + // + // TODO: Do not introduce a mask if the result will not contain + // any transparent pixels. + // + + QImage newDocImage(document()->width(), document()->height(), QImage::Format_ARGB32_Premultiplied); + newDocImage.fill(m_backgroundColor.toQRgb()); + + #if DEBUG_KP_TOOL_CROP + kDebug () << "\tsel: rect=" << m_fromSelectionPtr->boundingRect () + << " pm=" << m_fromSelectionPtr->hasContent () + << endl; + #endif + QImage setTransparentImage; + + if (m_fromSelectionPtr->hasContent ()) + { + setTransparentImage = m_fromSelectionPtr->transparentImage (); + + #if DEBUG_KP_TOOL_CROP + kDebug () << "\thave pixmap; rect=" + << setTransparentImage.rect () + << endl; + #endif + } + else + { + setTransparentImage = m_imageIfFromSelectionDoesntHaveOne; + #if DEBUG_KP_TOOL_CROP + kDebug () << "\tno pixmap in sel - get it; rect=" + << setTransparentImage.rect () + << endl; + #endif + } + +#if 0 + kpPixmapFX::paintMaskTransparentWithBrush (&newDocImage, + QPoint (0, 0), + m_fromSelectionPtr->shapeBitmap ()); +#endif + + kpPixmapFX::paintPixmapAt (&newDocImage, + QPoint (0, 0), + setTransparentImage); + + + document ()->setImageAt (newDocImage, QPoint (0, 0)); + document ()->selectionDelete (); + + + environ ()->somethingBelowTheCursorChanged (); + } + viewManager ()->restoreQueueUpdates (); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void SetDocumentToSelectionImageCommand::unexecute () +{ +#if DEBUG_KP_TOOL_CROP + kDebug () << "SetDocumentToSelectionImageCommand::unexecute()"; +#endif + + viewManager ()->setQueueUpdates (); + { + document ()->setImageAt (m_oldImage, QPoint (0, 0)); + m_oldImage = kpImage (); + + #if DEBUG_KP_TOOL_CROP + kDebug () << "\tsel: rect=" << m_fromSelectionPtr->boundingRect () + << " pm=" << m_fromSelectionPtr->hasContent () + << endl; + #endif + document ()->setSelection (*m_fromSelectionPtr); + + environ ()->somethingBelowTheCursorChanged (); + } + viewManager ()->restoreQueueUpdates (); +} + +//--------------------------------------------------------------------- + + +void kpTransformCrop_ImageSelection (kpMainWindow *mainWindow, + const QString &commandName, kpCommand *resizeDocCommand) +{ + // Save starting selection, minus the border. + kpAbstractImageSelection *borderImageSel = + dynamic_cast ( + mainWindow->document ()->selection ()->clone ()); + Q_ASSERT (borderImageSel); + + if ( !borderImageSel ) // make coverity happy + return; + + // (only interested in border) + borderImageSel->deleteContent (); + borderImageSel->moveTo (QPoint (0, 0)); + + + kpCommandEnvironment *environ = mainWindow->commandEnvironment (); + + + kpMacroCommand *macroCmd = new kpMacroCommand (commandName, environ); + + // (must resize doc _before_ SetDocumentToSelectionImageCommand in case + // doc needs to gets bigger - else selection image may not fit) + macroCmd->addCommand (resizeDocCommand); + +#if DEBUG_KP_TOOL_CROP + kDebug () << "\tis pixmap sel"; + kDebug () << "\tcreating SetImage cmd"; +#endif + macroCmd->addCommand (new SetDocumentToSelectionImageCommand (environ)); + + + mainWindow->addImageOrSelectionCommand ( + macroCmd, + true/*add create cmd*/, + false/*don't add pull cmd*/); + + + // Add selection border back for convenience. + mainWindow->commandHistory ()->addCommand ( + new kpToolSelectionCreateCommand ( + i18n ("Selection: Create"), + *borderImageSel, + mainWindow->commandEnvironment ())); + + + delete borderImageSel; +} diff --git a/kolourpaint/imagelib/transforms/kpTransformCrop_TextSelection.cpp b/kolourpaint/imagelib/transforms/kpTransformCrop_TextSelection.cpp new file mode 100644 index 00000000..b4853bc9 --- /dev/null +++ b/kolourpaint/imagelib/transforms/kpTransformCrop_TextSelection.cpp @@ -0,0 +1,76 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_CROP 0 + + +#include +#include + +#include +#include +#include +#include + + +void kpTransformCrop_TextSelection (kpMainWindow *mainWindow, + const QString &commandName, kpCommand *resizeDocCommand) +{ + kpCommandEnvironment *environ = mainWindow->commandEnvironment (); + + + kpMacroCommand *macroCmd = new kpMacroCommand (commandName, environ); + + macroCmd->addCommand (resizeDocCommand); + +#if DEBUG_KP_TOOL_CROP + kDebug () << "\tisText"; + kDebug () << "\tclearing doc with trans cmd"; +#endif + macroCmd->addCommand ( + new kpEffectClearCommand ( + false/*act on doc*/, + kpColor::Transparent, + environ)); + +#if DEBUG_KP_TOOL_CROP + kDebug () << "\tmoving sel to (0,0) cmd"; +#endif + kpToolSelectionMoveCommand *moveCmd = + new kpToolSelectionMoveCommand ( + QString()/*uninteresting child of macro cmd*/, + environ); + moveCmd->moveTo (QPoint (0, 0), true/*move on exec, not now*/); + moveCmd->finalize (); + macroCmd->addCommand (moveCmd); + + + mainWindow->addImageOrSelectionCommand ( + macroCmd, + true/*add create cmd*/, + true/*add create content cmd*/); +} diff --git a/kolourpaint/kolourpaint.appdata.xml b/kolourpaint/kolourpaint.appdata.xml new file mode 100644 index 00000000..cba6ea96 --- /dev/null +++ b/kolourpaint/kolourpaint.appdata.xml @@ -0,0 +1,238 @@ + + + kolourpaint.desktop + MIT + BSD-2-Clause and LGPL-2.0+ and GFDL-1.2-without-invariant + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPain + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + xxKolourPaintxx + 小畫家_KolourPaint + Paint Program + Programa de pintura + Program pro malování + Mal- und Zeichenprogramm + Paint Program + Programa de dibujo + Joonistamisrakendus + Piirto-ohjelma + Logiciel de dessin + Rajzolóprogram + Programma per pinger + Programma di disegno + 그리기 프로그램 + Piešimo programa + Maalprogramm + Tekenprogramma + Program Paint + Programa de Pintura + Programa de desenho + Kresliaci program + Program za risanje + Ritprogram + Програма для малювання + xxPaint Programxx + 繪圖程式 + +

+ KolourPaint is a simple painting program to quickly create raster images. + It is useful as a touch-up tool and simple image editing tasks. +

+

El KolourPaint és un programa senzill de pintura per crear ràpidament imatges ràster. És útil com a eina de retoc i tasques senzilles d'edició d'imatges.

+

KolourPaint is a simple painting program to quickly create raster images. It is useful as a touch-up tool and simple image editing tasks.

+

KolourPaint es un sencillo programa de dibujo que permite crear imágenes de mapas de bits de una forma rápida. Resulta útil como herramienta de retoque y para tareas de edición sencillas.

+

KolourPaint on lihtne joonistamisrakendus rasterpiltide kiireks loomiseks. Sellega on hõlpus luua visandeid ja täita lihtsamaid piltide töötlemise ülesandeid.

+

KolourPaint on yksinkertainen piirto-ohjelma, jolla voi luoda nopeasti bittikartakuvia. Sitä voi käyttää myös kuvien parantamiseen ja yksinkertaiseen kuvankäsittelyyn.

+

KolourPaint est logiciel de dessin simple qui permet de créer rapidement des images. Il est pratique pour retoucher ou modifier simplement des images.

+

A KolourPaint egy egyszerű rajzolóprogram raszteres képek gyors létrehozásához. Hasznos retusálóeszközként és egyszerű képszerkesztő feladatokhoz.

+

KolourPaint es un simple programma per pinger e designar pro crear rapidemente imagines raster. Illo es utile como instrumento de retocco e per cargas de simple modification de images.

+

KolourPaint è un semplice programma di disegno per la creazione rapida di immagini. È utile come strumento da usare «al volo» e per effettuare semplici modifiche alle immagini.

+

KolourPaint는 래스터 그림을 빠르게 만드는 프로그램입니다. 간단한 보정 도구 및 편집 도구로 사용할 수 있습니다.

+

KolourPaint is en eenfach Maalprogramm, mit dat sik gau Pixelbiller opstellen laat. Dat is goot för lütte Utbetern un eenfache Bildbewerken.

+

KolourPaint is een eenvoudig tekenprogramma om snel rasterafbeeldingen te maken. Het is nuttig als een hulpmiddel voor bijwerken en eenvoudige taken voor bewerking van afbeeldingen.

+

KolourPaint jest prostym programem do malowania i szybkiego tworzenia obrazów rastrowych. Jest przydatny przy retuszowaniu i prostych pracach edytowania.

+

O KolourPaint é um programa simples de pintura que cria rapidamente imagens rasterizadas. É útil como uma ferramenta de retoques e para tarefas simples de edição de imagens.

+

KolourPaint é um programa de desenho simples, que cria rapidamente imagens rasterizadas. É útil como uma ferramenta de retoques e para tarefas simples de edição de imagens.

+

KolourPaint je jednoduchý kresliaci program na rýchlu tvorbu rastrových obrázkov. Je užitočný ako touch-up nástroj a na jednoduché úpravy obrázkov.

+

KolourPaint je preprost program za risanje in hitro ustvarjanje rastrskih slik. Uporaben je za izboljšave slik in preprosto urejanje slik.

+

KolourPaint är ett enkelt ritprogram för att snabbt skapa punktavbildningar. Det är användbart som förbättringsverktyg och för enkla bildredigeringsuppgifter.

+

KolourPaint — проста програма для малювання, за допомогою якої можна швидко створювати растрові зображення. Ця програма може бути корисною для виконання простих завдань з редагування зображень.

+

xxKolourPaint is a simple painting program to quickly create raster images. It is useful as a touch-up tool and simple image editing tasks.xx

+

KolourPaint 是一套類似小畫家的簡易繪圖程式。

+

Features:

+

Funcionalitats:

+

Vlastnosti:

+

Leistungsmerkmale:

+

Features:

+

Funcionalidades:

+

Omadused:

+

Ominaisuudet:

+

Fonctionnalités :

+

Szolgáltatások:

+

Characteristicas:

+

Caratteristiche:

+

기능:

+

Galimybės:

+

Markmalen:

+

Mogelijkheden:

+

Cechy:

+

Características:

+

Funcionalidades:

+

Funkcie:

+

Zmožnosti:

+

Funktioner:

+

Можливості:

+

xxFeatures:xx

+

功能:

+
    +
  • Support for drawing various shapes - lines, rectangles, rounded rectangles, ovals and polygons
  • +
  • Permet dibuixar diverses formes: línies, rectangles, rectangles arrodonits, ovals i polígons
  • +
  • Das Zeichnen verschiedener Objekte wie Linien, Rechtecke, abgerundete Rechtecke, Ellipsen und Polygone wird unterstützt.
  • +
  • Support for drawing various shapes - lines, rectangles, rounded rectangles, ovals and polygons
  • +
  • Permite dibujar distintas formas (líneas, rectángulos, rectángulos de esquinas redondeadas, óvalos y polígonos)
  • +
  • Mitmesuguste kujundite joonistamise toetus: jooned, ristkülikud, ümarnurksed ristkülikud, ovaalid, hulknurgad
  • +
  • Piirtämistuki useille muodoille – viivoille, suorakaiteille, pyöristetyille suorakaiteille, ellipseille ja monikulmioille
  • +
  • Dessin de formes variées : lignes, rectangles, rectangles à bords arrondis, ovales, polygones
  • +
  • Különféle alakzatok rajzolásának támogatása - vonalak, téglalapok, lekerekített téglalapok, oválisok és sokszögek
  • +
  • Supporto pro varie formas de designo - lineas, rectangulos, rectangulos tundite, ovales e polygonos.
  • +
  • Supporto per il disegno di varie forme - linee, rettangoli, rettangoli arrotondati, ovali e poligoni
  • +
  • 다양한 도형 그리기 - 직선, 사각형, 둥근 사각형, 타원형, 다각형
  • +
  • Ünnerstütt dat Teken vun en Reeg vun Formen - Lienen, Rechtecks, afrundt Rechtecks, Ovalen un Veelecks
  • +
  • Ondersteuning voor het tekenen van verschillende vormen - lijnen, rechthoeken, afgeronde rechthoeken, ovalen en polygonen
  • +
  • Obsługa rysowania rozmaitych kształtów - proste, prostokąty, zaokrąglone prostokąty, owale i wielokąty
  • +
  • Suporte para desenhar várias formas - linhas, rectângulos, rectângulos arredondados, ovais e polígonos
  • +
  • Suporte para desenhar várias formas - linhas, retângulos, retângulos arredondados, ovais e polígonos
  • +
  • Podpora pre kreslenie rôznych tvarov - čiary, obdĺžniky, zaoblené obdĺžniky, ovály a polygóny
  • +
  • Podpora risanju različnih oblik - črt, pravokotnikov, zaobljenih pravokotnikov, ovalov in mnogokotnikov
  • +
  • Stöd för att rita diverse former: linjer, rektanglar, rundade rektanglar, ovaler och polygoner
  • +
  • Підтримка малювання різноманітних форм: прямих, прямокутників, прямокутників із заокругленими кутами, овалів та багатокутників.
  • +
  • xxSupport for drawing various shapes - lines, rectangles, rounded rectangles, ovals and polygonsxx
  • +
  • 支援繪出不同的形狀 -- 線條、矩形、圓角矩形、橢圓與多邊形等
  • +
  • Curves, lines and text
  • +
  • Corbes, línies i text
  • +
  • Kurven, Linien und Text
  • +
  • Curves, lines and text
  • +
  • Curvas, líneas y texto
  • +
  • Kõverad, sirged ja tekst
  • +
  • Käyrät, viivat ja teksti
  • +
  • Courbes, lignes et textes
  • +
  • Görbék, vonalak és szöveg
  • +
  • Curvas, lineas e texto
  • +
  • Curve, linee e testo
  • +
  • 곡선, 직선 및 텍스트
  • +
  • Kreivės, tiesės ir tekstas
  • +
  • Bagens, Lienen un Text
  • +
  • Krommen, lijnen en tekst
  • +
  • Krzywe, proste i teks
  • +
  • Curvas, linhas e texto
  • +
  • Curvas, linhas e texto
  • +
  • Krivky, čiary a text
  • +
  • Krivulje, črte in besedilo
  • +
  • Kurvor, linjer och text
  • +
  • Можливість малювання кривих, прямих та тексту.
  • +
  • xxCurves, lines and textxx
  • +
  • 曲線、線條與文字
  • +
  • Colour picker
  • +
  • Selector de color
  • +
  • Farbwähler
  • +
  • Colour picker
  • +
  • Selección de color
  • +
  • Värvivalija
  • +
  • Värivalitsin
  • +
  • Sélecteur de couleur
  • +
  • Színválasztó
  • +
  • Seligitor de color
  • +
  • Selettore del colore
  • +
  • 색 선택기
  • +
  • Spalvų parinkiklis
  • +
  • Klöörköör
  • +
  • Kleurkiezer
  • +
  • Wybierak barwy
  • +
  • Selecção de cores
  • +
  • Seleção de cor
  • +
  • Výber farieb
  • +
  • Izbirnik barv
  • +
  • Färgväljare
  • +
  • Піпетка для отримання кольору із зображення.
  • +
  • xxColour pickerxx
  • +
  • 顏色挑選器
  • +
  • Selections
  • +
  • Seleccions
  • +
  • Mehre Auswahlarten
  • +
  • Selections
  • +
  • Selecciones
  • +
  • Valikud
  • +
  • Valintatyökalu
  • +
  • Sélections
  • +
  • Kijelölések
  • +
  • Selectiones
  • +
  • Selezioni
  • +
  • 선택
  • +
  • Žymėjimai
  • +
  • Köören
  • +
  • Selecties
  • +
  • Zaznaczenia
  • +
  • Selecções
  • +
  • Seleções
  • +
  • Výbery
  • +
  • Izbire
  • +
  • Markeringar
  • +
  • Можливість позначення ділянок зображення.
  • +
  • xxSelectionsxx
  • +
  • 選取區
  • +
  • Rotation, monochrome and other advanced effects
  • +
  • Gir, monocrom i altres efectes avançats.
  • +
  • Drehung, Umwandlung in Schwarzweiß und weitere Effekte können angewendet werden
  • +
  • Rotation, monochrome and other advanced effects
  • +
  • Rotación, conversión a monocromo y otros efectos avanzados
  • +
  • Pööramine, ühevärvilisus ja veel mõned efektid
  • +
  • Kierto, pelkistäminen yksiväriseksi sekä muita tehosteita
  • +
  • Rotation, monochrome e altere effectos avantiate
  • +
  • Rotazione, monocromia e altri effetti avanzati
  • +
  • 회전, 흑백, 기타 고급 효과
  • +
  • Dreihen, Swatt-Witt un en Reeg verwiedert Effekten
  • +
  • Roteren, monochroom en andere geavanceerde effecten
  • +
  • Obrót, monochromatyczność i inne zaawansowane efekty
  • +
  • Rotação, monocromáticos e outros efeitos avançados
  • +
  • Rotação, monocromático e outros efeitos avançados
  • +
  • Otáčanie, monochromatické a iné pokročilé efekty
  • +
  • Vrtenje, monokromatsko in drugi napredni učinki
  • +
  • Rotation, svartvitt och andra avancerade effekter
  • +
  • Можливість обертання частин зображення, перетворення зображення на чорно-біле, різноманітні ефекти перетворення зображення.
  • +
  • xxRotation, monochrome and other advanced effectsxx
  • +
  • 旋轉、單色與其他進階效果
  • +
+
+ http://www.kolourpaint.org/ + https://bugs.kde.org/enter_bug.cgi?format=guided&product=kolourpaint + http://docs.kde.org/stable/en/kdegraphics/kolourpaint/index.html + + + http://kde.org/images/screenshots/kolourpaint.png + + + KDE + + kolourpaint + +
diff --git a/kolourpaint/kolourpaint.cpp b/kolourpaint/kolourpaint.cpp new file mode 100644 index 00000000..f8de7661 --- /dev/null +++ b/kolourpaint/kolourpaint.cpp @@ -0,0 +1,119 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + + +int main (int argc, char *argv []) +{ + KAboutData aboutData + ( + "kolourpaint", 0, + ki18n ("KolourPaint"), + kpVersionText, + ki18n ("Paint Program for KDE"), + KAboutData::License_Custom, + ki18n (0/*copyright statement - see license instead*/), + ki18n ("To obtain support, please visit the website."), + "http://www.kolourpaint.org/" + ); + + // (this is _not_ the same as KAboutData::License_BSD) + aboutData.setLicenseText (ki18n (kpLicenseText)); + + + // Please add yourself here if you feel you're missing. + // SYNC: with AUTHORS + + aboutData.addAuthor (ki18n ("Clarence Dang"), ki18n ("Project Founder"), "dang@kde.org"); + aboutData.addAuthor (ki18n ("Thurston Dang"), ki18n ("Chief Investigator"), + "thurston_dang@users.sourceforge.net"); + aboutData.addAuthor (ki18n ("Martin Koller"), ki18n ("Scanning Support, Alpha Support, Current Maintainer"), "kollix@aon.at"); + aboutData.addAuthor (ki18n ("Kristof Borrey"), ki18n ("Icons"), "borrey@kde.org"); + aboutData.addAuthor (ki18n ("Tasuku Suzuki"), ki18n ("InputMethod Support"), "stasuku@gmail.com"); + aboutData.addAuthor (ki18n ("Kazuki Ohta"), ki18n ("InputMethod Support"), "mover@hct.zaq.ne.jp"); + aboutData.addAuthor (ki18n ("Nuno Pinheiro"), ki18n ("Icons"), "nf.pinheiro@gmail.com"); + aboutData.addAuthor (ki18n ("Danny Allen"), ki18n ("Icons"), "dannya40uk@yahoo.co.uk"); + aboutData.addAuthor (ki18n ("Mike Gashler"), ki18n ("Image Effects"), "gashlerm@yahoo.com"); + + aboutData.addAuthor (ki18n ("Laurent Montel"), ki18n ("KDE 4 Porting"), "montel@kde.org"); + + // TODO: missing a lot of people who helped with the KDE 4 port. + + aboutData.addCredit (ki18n ("Thanks to the many others who have helped to make this program possible.")); + + KCmdLineArgs::init (argc, argv, &aboutData); + + KCmdLineOptions cmdLineOptions; + cmdLineOptions.add ("+[file]", ki18n ("Image file to open")); + KCmdLineArgs::addCmdLineOptions (cmdLineOptions); + + KApplication app; + + if (app.isSessionRestored ()) + { + // Creates a kpMainWindow using the default constructor and then + // calls kpMainWindow::readProperties(). + RESTORE (kpMainWindow) + } + else + { + kpMainWindow *mainWindow; + KCmdLineArgs *args = KCmdLineArgs::parsedArgs (); + + if (args->count () >= 1) + { + for (int i = 0; i < args->count (); i++) + { + mainWindow = new kpMainWindow (args->url (i)); + mainWindow->show (); + } + } + else + { + mainWindow = new kpMainWindow (); + mainWindow->show (); + } + + args->clear (); + } + + return app.exec (); +} diff --git a/kolourpaint/kolourpaint.desktop b/kolourpaint/kolourpaint.desktop new file mode 100644 index 00000000..dbe519c2 --- /dev/null +++ b/kolourpaint/kolourpaint.desktop @@ -0,0 +1,165 @@ +[Desktop Entry] + +Name=KolourPaint +Name[ar]=كُولُرْبينْت +Name[ast]=KolourPaint +Name[be]=KolourPaint +Name[bg]=KolourPaint +Name[br]=KolourPaint +Name[bs]=KolurPaint +Name[ca]=KolourPaint +Name[ca@valencia]=KolourPaint +Name[cs]=KolourPaint +Name[cy]=KolourPaint +Name[da]=KolourPaint +Name[de]=KolourPaint +Name[el]=KolourPaint +Name[en_GB]=KolourPaint +Name[eo]=KolourPaint +Name[es]=KolourPaint +Name[et]=KolourPaint +Name[eu]=KolourPaint +Name[fa]=KolourPaint +Name[fi]=KolourPaint +Name[fr]=KolourPaint +Name[ga]=KolourPaint +Name[gl]=KolourPaint +Name[he]=KolourPaint +Name[hi]=के-कलर-पेंट +Name[hne]=के-कलर-पेंट +Name[hr]=KolourPaint +Name[hu]=KolourPaint +Name[ia]=KolourPaint +Name[is]=KolourPaint +Name[it]=KolourPaint +Name[ja]=KolourPaint +Name[kk]=KolourPaint +Name[km]=KolourPaint +Name[ko]=KolourPaint +Name[ku]=KolourPaint +Name[lt]=KolourPaint +Name[lv]=KolourPaint +Name[mr]=कलरपेंट +Name[ms]=KolourPaint +Name[nb]=KolourPaint +Name[nds]=KolourPaint +Name[ne]=रङ पेन्ट +Name[nl]=KolourPaint +Name[nn]=KolourPaint +Name[pa]=ਕੇ-ਰੰਗ-ਪੇਂਟ +Name[pl]=KolourPaint +Name[pt]=KolourPaint +Name[pt_BR]=KolourPaint +Name[ro]=KolourPaint +Name[ru]=KolourPaint +Name[se]=KolourPaint +Name[si]=KolourPaint +Name[sk]=KolourPaint +Name[sl]=KolourPaint +Name[sr]=Колор-сликање +Name[sr@ijekavian]=Колор-сликање +Name[sr@ijekavianlatin]=Kolor-slikanje +Name[sr@latin]=Kolor-slikanje +Name[sv]=Kolourpaint +Name[ta]=நிற பெயின்ட் +Name[tg]=KolourPaint +Name[th]=วาดภาพระบายสี-K +Name[tr]=KolourPaint +Name[ug]=KolourPaint +Name[uk]=KolourPaint +Name[uz]=KolourPaint +Name[uz@cyrillic]=KolourPaint +Name[vi]=KolourPaint +Name[x-test]=xxKolourPaintxx +Name[zh_CN]=KolourPaint +Name[zh_HK]=KolourPaint +Name[zh_TW]=KolourPaint 小畫家 +GenericName=Paint Program +GenericName[af]=Verf Program +GenericName[ar]=برنامج التلوين +GenericName[ast]=Programa de pintura +GenericName[bg]=Графичен редактор +GenericName[br]=Goulev tresañ +GenericName[bs]=Program za slikanje +GenericName[ca]=Programa de pintura +GenericName[ca@valencia]=Programa de pintura +GenericName[cs]=Program pro malování +GenericName[cy]=Rhaglen Peintio +GenericName[da]=Maleprogram +GenericName[de]=Mal- und Zeichenprogramm +GenericName[el]=Πρόγραμμα ζωγραφικής +GenericName[en_GB]=Paint Program +GenericName[eo]=Pentrilo +GenericName[es]=Programa de pintura +GenericName[et]=Joonistusprogramm +GenericName[eu]=Marrazteko programa +GenericName[fa]=برنامه رنگ +GenericName[fi]=Piirto-ohjelma +GenericName[fr]=Programme de dessin +GenericName[ga]=Clár Péinteála +GenericName[gl]=Programa de debuxo +GenericName[he]=תוכנית ציור +GenericName[hi]=पेंट प्रोग्राम +GenericName[hne]=पेंट प्रोग्राम +GenericName[hr]=Program za slikanje +GenericName[hu]=Rajzolóprogram +GenericName[ia]=Programma per pinger +GenericName[is]=Teikniforrit +GenericName[it]=Programma di disegno +GenericName[ja]=ペイントプログラム +GenericName[kk]=Сурет салу бағдарламасы +GenericName[km]=កម្មវិធី​គូរ +GenericName[ko]=그리기 프로그램 +GenericName[ku]=Bernameya Nexşe Kirinê +GenericName[lt]=Piešimo programa +GenericName[lv]=Krāsošanas programma +GenericName[mr]=रंग कार्यक्रम +GenericName[ms]=Program Mewarna +GenericName[nb]=Tegneprogram +GenericName[nds]=Maalprogramm +GenericName[ne]=रङ्गयाउने कार्यक्रम +GenericName[nl]=Tekenprogramma +GenericName[nn]=Måleprogram +GenericName[pa]=ਰੰਗ ਪਰੋਗਰਾਮ +GenericName[pl]=Program Paint +GenericName[pt]=Programa de Pintura +GenericName[pt_BR]=Programa de desenho +GenericName[ro]=Program de desenare +GenericName[ru]=Простой редактор изображений +GenericName[se]=Málenprográmma +GenericName[si]=පින්තාරු වැඩසටහන +GenericName[sk]=Kresliaci program +GenericName[sl]=Program za risanje +GenericName[sr]=Програм за сликање +GenericName[sr@ijekavian]=Програм за сликање +GenericName[sr@ijekavianlatin]=Program za slikanje +GenericName[sr@latin]=Program za slikanje +GenericName[sv]=Ritprogram +GenericName[ta]=பெயிண்ட் நிரலி +GenericName[tg]=Муҳаррири графикӣ +GenericName[th]=โปรแกรมวาดภาพ +GenericName[tr]=Boyama Uygulaması +GenericName[ug]=سىزىش پروگراممىسى +GenericName[uk]=Програма для малювання +GenericName[uz]=Chizish dasturi +GenericName[uz@cyrillic]=Чизиш дастури +GenericName[vi]=Chương trình vẽ +GenericName[wa]=Program di dessinaedje +GenericName[xh]=Udweliso lwenkqubo lwepeyinti +GenericName[x-test]=xxPaint Programxx +GenericName[zh_CN]=绘图程序 +GenericName[zh_HK]=繪圖程式 +GenericName[zh_TW]=繪圖程式 +Icon=kolourpaint + +Type=Application +Exec=kolourpaint %u +X-DocPath=kolourpaint/index.html + +# SYNC: Run branches/kolourpaint/control/scripts/kde4port/gen_mimetype_line.sh in +# the version of kdelibs/kimgio/ (e.g. KDE 4.0) KolourPaint is +# shipped with. +MimeType=image/bmp;image/gif;image/jpeg;image/jp2;image/jpeg2000;image/png;image/tiff;image/x-dds;image/x-eps;image/x-exr;image/x-hdr;image/x-ico;image/vnd.microsoft.icon;image/x-pcx;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-psd;image/x-rgb;image/x-tga;image/x-xbitmap;image/x-xcf;image/x-xpixmap;video/x-mng;image/x-sun-raster;image/svg+xml;image/svg+xml-compressed;image/x-webp; + +Categories=Qt;KDE;Graphics;2DGraphics;RasterGraphics; +Terminal=false diff --git a/kolourpaint/kolourpaintui.rc b/kolourpaint/kolourpaintui.rc new file mode 100644 index 00000000..465a647e --- /dev/null +++ b/kolourpaint/kolourpaintui.rc @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + &View + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &Image + + + + + + + + + + + + + + + + + + + + + + + + + + &Colors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Main Toolbar + + + + + + + + + + + + + + + + +Selection Tool RMB Menu + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kolourpaint/kpDefs.h b/kolourpaint/kpDefs.h new file mode 100644 index 00000000..6d4881b5 --- /dev/null +++ b/kolourpaint/kpDefs.h @@ -0,0 +1,152 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_DEFS_H +#define KP_DEFS_H + + +#include + +#include +#include +#include +#include + +#include + + +// approx. 2896x2896x32bpp or 3344x3344x24bpp (TODO: 24==32?) or 4096*4096x16bpp +#define KP_BIG_IMAGE_SIZE (32 * 1048576) + + +#define KP_PI 3.141592653589793238462 + + +#define KP_DEGREES_TO_RADIANS(deg) ((deg) * KP_PI / 180.0) +#define KP_RADIANS_TO_DEGREES(rad) ((rad) * 180.0 / KP_PI) + + +#define KP_INVALID_POINT QPoint (INT_MIN / 8, INT_MIN / 8) +#define KP_INVALID_WIDTH (INT_MIN / 8) +#define KP_INVALID_HEIGHT (INT_MIN / 8) +#define KP_INVALID_SIZE QSize (INT_MIN / 8, INT_MIN / 8) + + +#define KP_INCHES_PER_METER (100 / 2.54) +#define KP_MILLIMETERS_PER_INCH 25.4 + + +// +// Settings +// + +#define kpSettingsGroupRecentFiles "Recent Files" + +#define kpSettingsGroupGeneral "General Settings" +#define kpSettingFirstTime "First Time" +#define kpSettingShowGrid "Show Grid" +#define kpSettingShowPath "Show Path" +#define kpSettingColorSimilarity "Color Similarity" +#define kpSettingDitherOnOpen "Dither on Open if Screen is 15/16bpp and Image Num Colors More Than" +#define kpSettingPrintImageCenteredOnPage "Print Image Centered On Page" +#define kpSettingOpenImagesInSameWindow "Open Images in the Same Window" + +#define kpSettingsGroupFileSaveAs "File/Save As" +#define kpSettingsGroupFileExport "File/Export" +#define kpSettingsGroupEditCopyTo "Edit/Copy To" + +#define kpSettingForcedMimeType "Forced MimeType" +#define kpSettingForcedColorDepth "Forced Color Depth" +#define kpSettingForcedDither "Forced Dither" +#define kpSettingForcedQuality "Forced Quality" + +#define kpSettingLastDocSize "Last Document Size" + +#define kpSettingMoreEffectsLastEffect "More Effects - Last Effect" + +#define kpSettingsGroupMimeTypeProperties "MimeType Properties Version 1.2-3" +#define kpSettingMimeTypeMaximumColorDepth "Maximum Color Depth" +#define kpSettingMimeTypeHasConfigurableColorDepth "Configurable Color Depth" +#define kpSettingMimeTypeHasConfigurableQuality "Configurable Quality Setting" + + +#define kpSettingsGroupUndoRedo "Undo/Redo Settings" +#define kpSettingUndoMinLimit "Min Limit" +#define kpSettingUndoMaxLimit "Max Limit" +#define kpSettingUndoMaxLimitSizeLimit "Max Limit Size Limit" + + +#define kpSettingsGroupThumbnail "Thumbnail Settings" +#define kpSettingThumbnailShown "Shown" +#define kpSettingThumbnailGeometry "Geometry" +#define kpSettingThumbnailZoomed "Zoomed" +#define kpSettingThumbnailShowRectangle "ShowRectangle" + + +#define kpSettingsGroupPreviewSave "Save Preview Settings" +#define kpSettingPreviewSaveGeometry "Geometry" +#define kpSettingPreviewSaveUpdateDelay "Update Delay" + + +#define kpSettingsGroupTools "Tool Settings" +#define kpSettingLastTool "Last Used Tool" +#define kpSettingToolBoxIconSize "Tool Box Icon Size" + + +#define kpSettingsGroupText "Text Settings" +#define kpSettingFontFamily "Font Family" +#define kpSettingFontSize "Font Size" +#define kpSettingBold "Bold" +#define kpSettingItalic "Italic" +#define kpSettingUnderline "Underline" +#define kpSettingStrikeThru "Strike Thru" + + +#define kpSettingsGroupFlattenEffect "Flatten Effect Settings" +#define kpSettingFlattenEffectColor1 "Color1" +#define kpSettingFlattenEffectColor2 "Color2" + + +// +// Session Restore Setting +// + +// URL of the document in the main window. +// +// This key only exists if the document does. If it exists, it can be empty. +// The URL need not point to a file that exists e.g. "kolourpaint doesnotexist.png". +#define kpSessionSettingDocumentUrl QString::fromLatin1 ("Session Document Url") + +// The size of a document which is not from a URL e.g. "kolourpaint doesnotexist.png". +// This key does not exist for documents from URLs. +#define kpSessionSettingNotFromUrlDocumentSize QString::fromLatin1 ("Session Not-From-Url Document Size") + + +#endif // KP_DEFS_H + + diff --git a/kolourpaint/kpThumbnail.cpp b/kolourpaint/kpThumbnail.cpp new file mode 100644 index 00000000..d1ffe09f --- /dev/null +++ b/kolourpaint/kpThumbnail.cpp @@ -0,0 +1,179 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_THUMBNAIL 0 + + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + + +struct kpThumbnailPrivate +{ + kpMainWindow *mainWindow; + kpThumbnailView *view; + QHBoxLayout *lay; +}; + +kpThumbnail::kpThumbnail (kpMainWindow *parent) + : kpSubWindow (parent), + d (new kpThumbnailPrivate ()) +{ + Q_ASSERT (parent); + + d->mainWindow = parent; + d->view = 0; + d->lay = new QHBoxLayout (this); + + + setMinimumSize (64, 64); + + + updateCaption (); +} + +kpThumbnail::~kpThumbnail () +{ + delete d; +} + + +// public +kpThumbnailView *kpThumbnail::view () const +{ + return d->view; +} + +// public +void kpThumbnail::setView (kpThumbnailView *view) +{ +#if DEBUG_KP_THUMBNAIL + kDebug () << "kpThumbnail::setView(" << view << ")"; +#endif + + if (d->view == view) + return; + + + if (d->view) + { + disconnect (d->view, SIGNAL (destroyed ()), + this, SLOT (slotViewDestroyed ())); + disconnect (d->view, SIGNAL (zoomLevelChanged (int, int)), + this, SLOT (updateCaption ())); + + d->lay->removeWidget (d->view); + } + + d->view = view; + + if (d->view) + { + connect (d->view, SIGNAL (destroyed ()), + this, SLOT (slotViewDestroyed ())); + connect (d->view, SIGNAL (zoomLevelChanged (int, int)), + this, SLOT (updateCaption ())); + + Q_ASSERT (d->view->parent () == this); + d->lay->addWidget (d->view, Qt::AlignCenter); + + d->view->show (); + } + + updateCaption (); +} + + +// public slot +void kpThumbnail::updateCaption () +{ + setWindowTitle (view () ? view ()->caption () : i18nc ("@title:window", "Thumbnail")); +} + + +// protected slot +void kpThumbnail::slotViewDestroyed () +{ +#if DEBUG_KP_THUMBNAIL + kDebug () << "kpThumbnail::slotViewDestroyed()"; +#endif + + d->view = 0; + updateCaption (); +} + + +// protected virtual [base QWidget] +void kpThumbnail::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_THUMBNAIL + kDebug () << "kpThumbnail::resizeEvent(" << width () + << "," << height () << ")" << endl; +#endif + + QWidget::resizeEvent (e); + + // updateVariableZoom (); TODO: is below a good idea since this commented out? + + if (d->mainWindow) + { + d->mainWindow->notifyThumbnailGeometryChanged (); + + if (d->mainWindow->tool ()) + d->mainWindow->tool ()->somethingBelowTheCursorChanged (); + } +} + +// protected virtual [base QWidget] +void kpThumbnail::moveEvent (QMoveEvent * /*e*/) +{ + if (d->mainWindow) + d->mainWindow->notifyThumbnailGeometryChanged (); +} + +// protected virtual [base QWidget] +void kpThumbnail::closeEvent (QCloseEvent *e) +{ + QWidget::closeEvent (e); + + emit windowClosed (); +} + + +#include diff --git a/kolourpaint/kpThumbnail.h b/kolourpaint/kpThumbnail.h new file mode 100644 index 00000000..2f0299ee --- /dev/null +++ b/kolourpaint/kpThumbnail.h @@ -0,0 +1,76 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_THUMBNAIL_H +#define KP_THUMBNAIL_H + + +#include + + +class QMoveEvent; +class QResizeEvent; + +class kpMainWindow; +class kpThumbnailView; + + +struct kpThumbnailPrivate; + +class kpThumbnail : public kpSubWindow +{ +Q_OBJECT + +public: + kpThumbnail (kpMainWindow *parent); + virtual ~kpThumbnail (); + +public: + kpThumbnailView *view () const; + void setView (kpThumbnailView *view); + +public slots: + void updateCaption (); + +protected slots: + void slotViewDestroyed (); + +protected: + virtual void resizeEvent (QResizeEvent *e); + virtual void moveEvent (QMoveEvent *e); + virtual void closeEvent (QCloseEvent *e); + +signals: + void windowClosed (); + +private: + kpThumbnailPrivate * const d; +}; + + +#endif // KP_THUMBNAIL_H diff --git a/kolourpaint/kpViewScrollableContainer.cpp b/kolourpaint/kpViewScrollableContainer.cpp new file mode 100644 index 00000000..3105574c --- /dev/null +++ b/kolourpaint/kpViewScrollableContainer.cpp @@ -0,0 +1,1181 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_VIEW_SCROLLABLE_CONTAINER 0 + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +// (Pulled from out of Thurston's hat) +static const int DragScrollLeftTopMargin = 0; +static const int DragScrollRightBottomMargin = 16; // scrollbarish +static const int DragScrollInterval = 150; +static const int DragScrollInitialInterval = DragScrollInterval * 2; +static const int DragScrollNumPixels = 10; +static const int DragDistanceFromRectMaxFor1stMultiplier = 50; +static const int DragDistanceFromRectMaxFor2ndMultiplier = 100; + +//--------------------------------------------------------------------- +//--------------------------------------------------------------------- +// a transparent widget above all others in the viewport used only while resizing the document +// to be able to show the resize lines above everything else + +class kpOverlay : public QWidget +{ + public: + kpOverlay(QWidget *parent, kpViewScrollableContainer *container) + : QWidget(parent), m_container(container) + { + } + + virtual void paintEvent(QPaintEvent *) + { + m_container->drawResizeLines(); + } + + private: + kpViewScrollableContainer *m_container; + +}; + +//--------------------------------------------------------------------- + +const int kpGrip::Size = 5; + +//--------------------------------------------------------------------- + +kpGrip::kpGrip (GripType type, QWidget *parent) + : QWidget(parent), + m_type (type), + m_startPoint (KP_INVALID_POINT), + m_currentPoint (KP_INVALID_POINT), + m_shouldReleaseMouseButtons (false) +{ + setCursor(cursorForType(m_type)); + + setMouseTracking(true); // mouseMoveEvent's even when no mousebtn down + + setAutoFillBackground(true); + setBackgroundRole(QPalette::Highlight); + + setFixedSize(kpGrip::Size, kpGrip::Size); +} + +//--------------------------------------------------------------------- + +// public +kpGrip::GripType kpGrip::type () const +{ + return m_type; +} + +//--------------------------------------------------------------------- + +// public static +QCursor kpGrip::cursorForType (GripType type) +{ + switch (type) + { + case kpGrip::Bottom: + return Qt::SizeVerCursor; + break; // one day you'll forget + + case kpGrip::Right: + return Qt::SizeHorCursor; + break; // one day you'll forget + + case kpGrip::BottomRight: + return Qt::SizeFDiagCursor; + break; // one day you'll forget + } + + return Qt::ArrowCursor; +} + +//--------------------------------------------------------------------- + +// public +bool kpGrip::containsCursor() +{ + return isVisible() && + QRect(mapToGlobal(rect().topLeft()), + mapToGlobal(rect().bottomRight())).contains(QCursor::pos()); +} + +//--------------------------------------------------------------------- + +// public +bool kpGrip::isDrawing () const +{ + return (m_startPoint != KP_INVALID_POINT); +} + +//--------------------------------------------------------------------- + +// public +QString kpGrip::haventBegunDrawUserMessage () const +{ + return i18n ("Left drag the handle to resize the image."); +} + +//--------------------------------------------------------------------- + +// public +QString kpGrip::userMessage () const +{ + return m_userMessage; +} + +//--------------------------------------------------------------------- + +// public +void kpGrip::setUserMessage (const QString &message) +{ + // Don't do NOP checking here since another grip might have changed + // the message so an apparent NOP for this grip is not a NOP in the + // global sense (kpViewScrollableContainer::slotGripStatusMessageChanged()). + + m_userMessage = message; + emit statusMessageChanged (message); +} + +//--------------------------------------------------------------------- + +// protected +void kpGrip::cancel () +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kDebug () << "kpGrip::cancel()"; +#endif + if (m_currentPoint == KP_INVALID_POINT) + return; + + m_startPoint = KP_INVALID_POINT; + m_currentPoint = KP_INVALID_POINT; + + setUserMessage (i18n ("Resize Image: Let go of all the mouse buttons.")); + setCursor (Qt::ArrowCursor); + m_shouldReleaseMouseButtons = true; + + releaseKeyboard (); + emit cancelledDraw (); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpGrip::keyReleaseEvent (QKeyEvent *e) +{ + if (m_startPoint != KP_INVALID_POINT && + e->key () == Qt::Key_Escape) + { + cancel (); + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpGrip::mousePressEvent (QMouseEvent *e) +{ + if (m_startPoint == KP_INVALID_POINT && + (e->buttons () & Qt::MouseButtonMask) == Qt::LeftButton) + { + m_startPoint = e->pos (); + m_currentPoint = e->pos (); + emit beganDraw (); + grabKeyboard (); + + setUserMessage (i18n ("Resize Image: Right click to cancel.")); + setCursor (cursorForType (m_type)); + } + else + { + if (m_startPoint != KP_INVALID_POINT) + cancel (); + } +} + +//--------------------------------------------------------------------- + +// public +QPoint kpGrip::viewDeltaPoint () const +{ + if (m_startPoint == KP_INVALID_POINT) + return KP_INVALID_POINT; + + const QPoint point = mapFromGlobal (QCursor::pos ()); + + // TODO: this is getting out of sync with m_currentPoint + + return QPoint (((m_type & kpGrip::Right) ? point.x () - m_startPoint.x () : 0), + ((m_type & kpGrip::Bottom) ? point.y () - m_startPoint.y () : 0)); + +} + +//--------------------------------------------------------------------- + +// public +void kpGrip::mouseMovedTo (const QPoint &point, bool dueToDragScroll) +{ + if (m_startPoint == KP_INVALID_POINT) + return; + + m_currentPoint = point; + + emit continuedDraw (((m_type & kpGrip::Right) ? point.x () - m_startPoint.x () : 0), + ((m_type & kpGrip::Bottom) ? point.y () - m_startPoint.y () : 0), + dueToDragScroll); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpGrip::mouseMoveEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kDebug () << "kpGrip::mouseMoveEvent() m_startPoint=" << m_startPoint + << " stateAfter: buttons=" << (int *) (int) e->buttons () + << endl; +#endif + + if (m_startPoint == KP_INVALID_POINT) + { + if ((e->buttons () & Qt::MouseButtonMask) == 0) + setUserMessage (haventBegunDrawUserMessage ()); + return; + } + + mouseMovedTo (e->pos (), false/*not due to drag scroll*/); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpGrip::mouseReleaseEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kDebug () << "kpGrip::mouseReleaseEvent() m_startPoint=" << m_startPoint + << " stateAfter: buttons=" << (int *) (int) e->buttons () + << endl; +#endif + + if (m_startPoint != KP_INVALID_POINT) + { + const int dx = m_currentPoint.x () - m_startPoint.x (), + dy = m_currentPoint.y () - m_startPoint.y (); + + m_currentPoint = KP_INVALID_POINT; + m_startPoint = KP_INVALID_POINT; + + releaseKeyboard (); + emit endedDraw ((m_type & kpGrip::Right) ? dx : 0, + (m_type & kpGrip::Bottom) ? dy : 0); + } + + if ((e->buttons () & Qt::MouseButtonMask) == 0) + { + m_shouldReleaseMouseButtons = false; + setUserMessage(QString()); + setCursor (cursorForType (m_type)); + + releaseKeyboard (); + emit releasedAllButtons (); + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpGrip::enterEvent (QEvent * /*e*/) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kDebug () << "kpGrip::enterEvent()" + << " m_startPoint=" << m_startPoint + << " shouldReleaseMouseButtons=" + << m_shouldReleaseMouseButtons << endl; +#endif + + if (m_startPoint == KP_INVALID_POINT && + !m_shouldReleaseMouseButtons) + { + #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kDebug () << "\tsending message"; + #endif + setUserMessage (haventBegunDrawUserMessage ()); + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpGrip::leaveEvent (QEvent * /*e*/) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kDebug () << "kpGrip::leaveEvent()" + << " m_startPoint=" << m_startPoint + << " shouldReleaseMouseButtons=" + << m_shouldReleaseMouseButtons << endl; +#endif + if (m_startPoint == KP_INVALID_POINT && + !m_shouldReleaseMouseButtons) + { + setUserMessage(QString()); + } +} + +//--------------------------------------------------------------------- +//--------------------------------------------------------------------- +//--------------------------------------------------------------------- + +// TODO: Are we checking for m_view == 0 often enough? Also an issue in KDE 3. +kpViewScrollableContainer::kpViewScrollableContainer(QWidget *parent) + : QScrollArea(parent), + m_view(0), m_overlay(new kpOverlay(viewport(), this)), + m_docResizingGrip (0), + m_dragScrollTimer (new QTimer (this)), + m_zoomLevel (100), + m_scrollTimerRunOnce (false), + m_resizeRoundedLastViewX (-1), m_resizeRoundedLastViewY (-1), + m_resizeRoundedLastViewDX (0), m_resizeRoundedLastViewDY (0), + m_haveMovedFromOriginalDocSize (false) + +{ + // the base widget holding the documents view plus the resize grips + setWidget(new QWidget(viewport())); + + m_bottomGrip = new kpGrip(kpGrip::Bottom, widget()); + m_rightGrip = new kpGrip(kpGrip::Right, widget()); + m_bottomRightGrip = new kpGrip(kpGrip::BottomRight, widget()); + + m_bottomGrip->setObjectName(QLatin1String("Bottom Grip")); + m_rightGrip->setObjectName(QLatin1String("Right Grip")); + m_bottomRightGrip->setObjectName(QLatin1String("BottomRight Grip")); + + m_bottomGrip->hide (); + connectGripSignals (m_bottomGrip); + + m_rightGrip->hide (); + connectGripSignals (m_rightGrip); + + m_bottomRightGrip->hide (); + connectGripSignals (m_bottomRightGrip); + + + connect (horizontalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(slotContentsMoved())); + + connect (verticalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(slotContentsMoved())); + + connect (m_dragScrollTimer, SIGNAL (timeout ()), + this, SLOT (slotDragScroll ())); + + m_overlay->hide(); +} + +//--------------------------------------------------------------------- + +// protected +void kpViewScrollableContainer::connectGripSignals (kpGrip *grip) +{ + connect (grip, SIGNAL (beganDraw ()), + this, SLOT (slotGripBeganDraw ())); + connect (grip, SIGNAL (continuedDraw (int, int, bool)), + this, SLOT (slotGripContinuedDraw (int, int, bool))); + connect (grip, SIGNAL (cancelledDraw ()), + this, SLOT (slotGripCancelledDraw ())); + connect (grip, SIGNAL (endedDraw (int, int)), + this, SLOT (slotGripEndedDraw (int, int))); + + connect (grip, SIGNAL (statusMessageChanged (const QString &)), + this, SLOT (slotGripStatusMessageChanged (const QString &))); + + connect (grip, SIGNAL (releasedAllButtons ()), + this, SLOT (recalculateStatusMessage ())); +} + +//--------------------------------------------------------------------- + +// public +QSize kpViewScrollableContainer::newDocSize () const +{ + return newDocSize (m_resizeRoundedLastViewDX, + m_resizeRoundedLastViewDY); +} + +//--------------------------------------------------------------------- + +// public +bool kpViewScrollableContainer::haveMovedFromOriginalDocSize () const +{ + return m_haveMovedFromOriginalDocSize; +} + +//--------------------------------------------------------------------- + +// public +QString kpViewScrollableContainer::statusMessage () const +{ + return m_gripStatusMessage; +} + +//--------------------------------------------------------------------- + +// public +void kpViewScrollableContainer::clearStatusMessage () +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 1 + kDebug () << "kpViewScrollableContainer::clearStatusMessage()"; +#endif + m_bottomRightGrip->setUserMessage(QString()); + m_bottomGrip->setUserMessage(QString()); + m_rightGrip->setUserMessage(QString()); +} + +//--------------------------------------------------------------------- + +// protected +QSize kpViewScrollableContainer::newDocSize (int viewDX, int viewDY) const +{ + if (!m_view) + return QSize (); + + if (!docResizingGrip ()) + return QSize (); + + const int docX = (int) m_view->transformViewToDocX (m_view->width () + viewDX); + const int docY = (int) m_view->transformViewToDocY (m_view->height () + viewDY); + + return QSize (qMax (1, docX), qMax (1, docY)); +} + +//--------------------------------------------------------------------- + +// protected +void kpViewScrollableContainer::calculateDocResizingGrip () +{ + if (m_bottomRightGrip->isDrawing ()) + m_docResizingGrip = m_bottomRightGrip; + else if (m_bottomGrip->isDrawing ()) + m_docResizingGrip = m_bottomGrip; + else if (m_rightGrip->isDrawing ()) + m_docResizingGrip = m_rightGrip; + else + m_docResizingGrip = 0; +} + +//--------------------------------------------------------------------- + +// protected +kpGrip *kpViewScrollableContainer::docResizingGrip () const +{ + return m_docResizingGrip; +} + +//--------------------------------------------------------------------- + +// protected +int kpViewScrollableContainer::bottomResizeLineWidth () const +{ + if (!docResizingGrip ()) + return -1; + + if (!m_view) + return -1; + + if (docResizingGrip ()->type () & kpGrip::Bottom) + return qMax (m_view->zoomLevelY () / 100, 1); + else + return 1; +} + +//--------------------------------------------------------------------- + +// protected +int kpViewScrollableContainer::rightResizeLineWidth () const +{ + if (!docResizingGrip ()) + return -1; + + if (!m_view) + return -1; + + if (docResizingGrip ()->type () & kpGrip::Right) + return qMax (m_view->zoomLevelX () / 100, 1); + else + return 1; +} + +//--------------------------------------------------------------------- + +// protected +QRect kpViewScrollableContainer::bottomResizeLineRect () const +{ + if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) + return QRect (); + + QRect visibleArea = QRect(QPoint(horizontalScrollBar()->value(),verticalScrollBar()->value()), viewport()->size()); + + return QRect (QPoint (0, + m_resizeRoundedLastViewY), + QPoint (m_resizeRoundedLastViewX - 1, + m_resizeRoundedLastViewY + bottomResizeLineWidth () - 1)).intersected(visibleArea); +} + +//--------------------------------------------------------------------- + +// protected +QRect kpViewScrollableContainer::rightResizeLineRect () const +{ + if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) + return QRect (); + + QRect visibleArea = QRect(QPoint(horizontalScrollBar()->value(),verticalScrollBar()->value()), viewport()->size()); + + return QRect (QPoint (m_resizeRoundedLastViewX, + 0), + QPoint (m_resizeRoundedLastViewX + rightResizeLineWidth () - 1, + m_resizeRoundedLastViewY - 1)).intersected(visibleArea); +} + +//--------------------------------------------------------------------- + +// protected +QRect kpViewScrollableContainer::bottomRightResizeLineRect () const +{ + if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) + return QRect (); + + QRect visibleArea = QRect(QPoint(horizontalScrollBar()->value(),verticalScrollBar()->value()), viewport()->size()); + + return QRect (QPoint (m_resizeRoundedLastViewX, + m_resizeRoundedLastViewY), + QPoint (m_resizeRoundedLastViewX + rightResizeLineWidth () - 1, + m_resizeRoundedLastViewY + bottomResizeLineWidth () - 1)).intersected(visibleArea); +} + +//--------------------------------------------------------------------- + +// private +QRect kpViewScrollableContainer::mapViewToViewport (const QRect &viewRect) +{ + if (!viewRect.isValid ()) + return QRect (); + + QRect ret = viewRect; + ret.translate (-horizontalScrollBar()->value() - viewport()->x(), -verticalScrollBar()->value() - viewport()->y()); + return ret; +} + +//--------------------------------------------------------------------- + +void kpViewScrollableContainer::drawResizeLines () +{ + static const char *stipple[] = + { + "8 8 2 1", + ". c #000000", + "# c #ffffff", + "....####", + "....####", + "....####", + "....####", + "####....", + "####....", + "####....", + "####...." + }; + + QPainter p(m_overlay); + p.setBackground(QPixmap(stipple)); + + const QRect rightRect = rightResizeLineRect(); + if ( rightRect.isValid() ) + { + QRect rect = mapViewToViewport(rightRect); + p.setBrushOrigin(rect.x(), rect.y()); + p.eraseRect(rect); + } + + const QRect bottomRect = bottomResizeLineRect(); + if ( bottomRect.isValid() ) + { + QRect rect = mapViewToViewport(bottomRect); + p.setBrushOrigin(rect.x(), rect.y()); + p.eraseRect(rect); + } + + const QRect bottomRightRect = bottomRightResizeLineRect (); + if ( bottomRightRect.isValid() ) + { + QRect rect = mapViewToViewport(bottomRightRect); + p.setBrushOrigin(rect.x(), rect.y()); + p.eraseRect(rect); + } +} + +//--------------------------------------------------------------------- + +// protected +void kpViewScrollableContainer::updateResizeLines (int viewX, int viewY, + int viewDX, int viewDY) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 0 + kDebug () << "kpViewScrollableContainer::updateResizeLines(" + << viewX << "," << viewY << ")" + << " oldViewX=" << m_resizeRoundedLastViewX + << " oldViewY=" << m_resizeRoundedLastViewY + << " viewDX=" << viewDX + << " viewDY=" << viewDY + << endl; +#endif + + + if (viewX >= 0 && viewY >= 0) + { + m_resizeRoundedLastViewX = (int) m_view->transformDocToViewX ((int) m_view->transformViewToDocX (viewX)); + m_resizeRoundedLastViewY = (int) m_view->transformDocToViewY ((int) m_view->transformViewToDocY (viewY)); + + m_resizeRoundedLastViewDX = viewDX; + m_resizeRoundedLastViewDY = viewDY; + } + else + { + m_resizeRoundedLastViewX = -1; + m_resizeRoundedLastViewY = -1; + + m_resizeRoundedLastViewDX = 0; + m_resizeRoundedLastViewDY = 0; + } + + m_overlay->update(); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotGripBeganDraw () +{ + if (!m_view) + return; + + m_overlay->resize(viewport()->size()); // make it cover whole viewport + m_overlay->move(viewport()->pos()); + m_overlay->show(); + m_overlay->raise(); // make it top-most + + calculateDocResizingGrip (); + + m_haveMovedFromOriginalDocSize = false; + + updateResizeLines (m_view->width (), m_view->height (), + 0/*viewDX*/, 0/*viewDY*/); + + emit beganDocResize (); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotGripContinuedDraw (int inViewDX, int inViewDY, + bool dueToDragScroll) +{ + int viewDX = inViewDX, + viewDY = inViewDY; + +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kDebug () << "kpViewScrollableContainer::slotGripContinuedDraw(" + << viewDX << "," << viewDY << ") size=" + << newDocSize (viewDX, viewDY) + << " dueToDragScroll=" << dueToDragScroll + << endl; +#endif + + if (!m_view) + return; + + if (!dueToDragScroll && + beginDragScroll(m_view->zoomLevelX ())) + { + const QPoint newViewDeltaPoint = docResizingGrip ()->viewDeltaPoint (); + viewDX = newViewDeltaPoint.x (); + viewDY = newViewDeltaPoint.y (); + #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kDebug () << "\tdrag scrolled - new view delta point=" + << newViewDeltaPoint + << endl; + #endif + } + + m_haveMovedFromOriginalDocSize = true; + + updateResizeLines (qMax (1, qMax (m_view->width () + viewDX, (int) m_view->transformDocToViewX (1))), + qMax (1, qMax (m_view->height () + viewDY, (int) m_view->transformDocToViewY (1))), + viewDX, viewDY); + + emit continuedDocResize (newDocSize ()); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotGripCancelledDraw () +{ + m_haveMovedFromOriginalDocSize = false; + + updateResizeLines (-1, -1, 0, 0); + + calculateDocResizingGrip (); + + emit cancelledDocResize (); + + endDragScroll (); + + m_overlay->hide(); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotGripEndedDraw (int viewDX, int viewDY) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kDebug () << "kpViewScrollableContainer::slotGripEndedDraw(" + << viewDX << "," << viewDY << ") size=" + << newDocSize (viewDX, viewDY) + << endl; +#endif + + if (!m_view) + return; + + const QSize newSize = newDocSize (viewDX, viewDY); + + m_haveMovedFromOriginalDocSize = false; + + // must erase lines before view size changes + updateResizeLines (-1, -1, 0, 0); + + calculateDocResizingGrip (); + + emit endedDocResize (newSize); + + endDragScroll (); + + m_overlay->hide(); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotGripStatusMessageChanged (const QString &string) +{ + if (string == m_gripStatusMessage) + return; + + m_gripStatusMessage = string; + emit statusMessageChanged (string); +} + +//--------------------------------------------------------------------- + +// public slot +void kpViewScrollableContainer::recalculateStatusMessage () +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kDebug () << "kpViewScrollabelContainer::recalculateStatusMessage()"; + kDebug () << "\tQCursor::pos=" << QCursor::pos () + << " global visibleRect=" + << kpWidgetMapper::toGlobal (this, + QRect(0, 0, viewport->width(), viewport->height())) + << endl; +#endif + + // HACK: After dragging to a new size, handles move so that they are now + // under the mouse pointer but no mouseMoveEvent() is generated for + // any grip. This also handles the case of canceling over any + // grip. + // + if (kpWidgetMapper::toGlobal (this, + QRect(0, 0, viewport()->width(), viewport()->height())) + .contains (QCursor::pos ())) + { + if ( m_bottomRightGrip->containsCursor() ) + { + m_bottomRightGrip->setUserMessage (i18n ("Left drag the handle to resize the image.")); + } + else if ( m_bottomGrip->containsCursor() ) + { + m_bottomGrip->setUserMessage (i18n ("Left drag the handle to resize the image.")); + } + else if ( m_rightGrip->containsCursor() ) + { + m_rightGrip->setUserMessage (i18n ("Left drag the handle to resize the image.")); + } + else + { + clearStatusMessage (); + } + } + else + { + clearStatusMessage (); + } +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotContentsMoved () +{ + kpGrip *grip = docResizingGrip (); + if (grip) + { + grip->mouseMovedTo (grip->mapFromGlobal (QCursor::pos ()), + true/*moved due to drag scroll*/); + } + + m_overlay->move(viewport()->pos()); + m_overlay->update(); + + emit contentsMoved(); +} + +//--------------------------------------------------------------------- + +// protected +void kpViewScrollableContainer::disconnectViewSignals () +{ + disconnect (m_view, SIGNAL (sizeChanged (const QSize &)), + this, SLOT (updateGrips ())); + disconnect (m_view, SIGNAL (destroyed ()), + this, SLOT (slotViewDestroyed ())); +} + +//--------------------------------------------------------------------- + +// protected +void kpViewScrollableContainer::connectViewSignals () +{ + connect (m_view, SIGNAL (sizeChanged (const QSize &)), + this, SLOT (updateGrips ())); + connect (m_view, SIGNAL (destroyed ()), + this, SLOT (slotViewDestroyed ())); +} + +//--------------------------------------------------------------------- + +// public +kpView *kpViewScrollableContainer::view () const +{ + return m_view; +} + +//--------------------------------------------------------------------- + +// public +void kpViewScrollableContainer::setView (kpView *view) +{ + if (m_view == view) + return; + + if (m_view) + { + disconnectViewSignals (); + } + + m_view = view; + + if ( m_view ) + { + m_view->setParent(widget()); + m_view->show(); + } + + updateGrips (); + + if (m_view) + { + connectViewSignals (); + } +} + +//--------------------------------------------------------------------- + +// public slot +void kpViewScrollableContainer::updateGrips () +{ + if (m_view) + { + widget()->resize(m_view->size() + m_bottomRightGrip->size()); + + // to make the grip more easily "touchable" make it as high as the view + m_rightGrip->setFixedHeight(m_view->height()); + m_rightGrip->move(m_view->width(), 0); + + // to make the grip more easily "touchable" make it as wide as the view + m_bottomGrip->setFixedWidth(m_view->width()); + m_bottomGrip->move(0, m_view->height ()); + + m_bottomRightGrip->move(m_view->width(), m_view->height()); + } + + m_bottomGrip->setHidden (m_view == 0); + m_rightGrip->setHidden (m_view == 0); + m_bottomRightGrip->setHidden (m_view == 0); + + recalculateStatusMessage (); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotViewDestroyed () +{ + m_view = 0; + updateGrips (); +} + +//--------------------------------------------------------------------- + +// public slot +bool kpViewScrollableContainer::beginDragScroll(int zoomLevel, bool *didSomething) +{ + if (didSomething) + *didSomething = false; + + m_zoomLevel = zoomLevel; + + const QPoint p = mapFromGlobal (QCursor::pos ()); + + bool stopDragScroll = true; + bool scrolled = false; + + if (!noDragScrollRect ().contains (p)) + { + if (m_dragScrollTimer->isActive ()) + { + if (m_scrollTimerRunOnce) + { + scrolled = slotDragScroll (); + } + } + else + { + m_scrollTimerRunOnce = false; + m_dragScrollTimer->start (DragScrollInitialInterval); + } + + stopDragScroll = false; + } + + if (stopDragScroll) + m_dragScrollTimer->stop (); + + if (didSomething) + *didSomething = scrolled; + + return scrolled; +} + +//--------------------------------------------------------------------- + +// public slot +bool kpViewScrollableContainer::beginDragScroll(int zoomLevel) +{ + return beginDragScroll(zoomLevel, + 0/*don't want scrolled notification*/); +} + +//--------------------------------------------------------------------- + +// public slot +bool kpViewScrollableContainer::endDragScroll () +{ + if (m_dragScrollTimer->isActive ()) + { + m_dragScrollTimer->stop (); + return true; + } + else + { + return false; + } +} + +//--------------------------------------------------------------------- + +static int distanceFromRectToMultiplier (int dist) +{ + if (dist < 0) + return 0; + else if (dist < DragDistanceFromRectMaxFor1stMultiplier) + return 1; + else if (dist < DragDistanceFromRectMaxFor2ndMultiplier) + return 2; + else + return 4; +} + +//--------------------------------------------------------------------- + +// protected slot +bool kpViewScrollableContainer::slotDragScroll (bool *didSomething) +{ + bool scrolled = false; + + if (didSomething) + *didSomething = false; + + + const QRect rect = noDragScrollRect (); + const QPoint pos = mapFromGlobal (QCursor::pos ()); + + int dx = 0, dy = 0; + int dxMultiplier = 0, dyMultiplier = 0; + + if (pos.x () < rect.left ()) + { + dx = -DragScrollNumPixels; + dxMultiplier = distanceFromRectToMultiplier (rect.left () - pos.x ()); + } + else if (pos.x () > rect.right ()) + { + dx = +DragScrollNumPixels; + dxMultiplier = distanceFromRectToMultiplier (pos.x () - rect.right ()); + } + + if (pos.y () < rect.top ()) + { + dy = -DragScrollNumPixels; + dyMultiplier = distanceFromRectToMultiplier (rect.top () - pos.y ()); + } + else if (pos.y () > rect.bottom ()) + { + dy = +DragScrollNumPixels; + dyMultiplier = distanceFromRectToMultiplier (pos.y () - rect.bottom ()); + } + + dx *= dxMultiplier;// * qMax (1, m_zoomLevel / 100); + dy *= dyMultiplier;// * qMax (1, m_zoomLevel / 100); + + if (dx || dy) + { + const int oldContentsX = horizontalScrollBar()->value (), + oldContentsY = verticalScrollBar()->value (); + + horizontalScrollBar()->setValue(oldContentsX + dx); + verticalScrollBar()->setValue(oldContentsY + dy); + + scrolled = (oldContentsX != horizontalScrollBar()->value () || + oldContentsY != verticalScrollBar()->value ()); + + if (scrolled) + { + QRegion region = QRect (horizontalScrollBar()->value (), verticalScrollBar()->value (), + viewport()->width(), viewport()->height()); + region -= QRect (oldContentsX, oldContentsY, + viewport()->width(), viewport()->height()); + + // Repaint newly exposed region immediately to reduce tearing + // of scrollView. + m_view->repaint (region); + } + } + + m_dragScrollTimer->start (DragScrollInterval); + m_scrollTimerRunOnce = true; + + if (didSomething) + *didSomething = scrolled; + + return scrolled; +} + +//--------------------------------------------------------------------- + +// protected slot +bool kpViewScrollableContainer::slotDragScroll () +{ + return slotDragScroll (0/*don't want scrolled notification*/); +} + +//--------------------------------------------------------------------- + +// protected virtual +void kpViewScrollableContainer::wheelEvent (QWheelEvent *e) +{ + e->ignore (); + + if (m_view) + m_view->wheelEvent (e); + + if ( !e->isAccepted() ) + QScrollArea::wheelEvent(e); +} + +//--------------------------------------------------------------------- + +QRect kpViewScrollableContainer::noDragScrollRect () const +{ + return QRect (DragScrollLeftTopMargin, DragScrollLeftTopMargin, + width () - DragScrollLeftTopMargin - DragScrollRightBottomMargin, + height () - DragScrollLeftTopMargin - DragScrollRightBottomMargin); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QScrollView] +void kpViewScrollableContainer::resizeEvent (QResizeEvent *e) +{ + QScrollArea::resizeEvent (e); + + emit resized (); +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/kpViewScrollableContainer.h b/kolourpaint/kpViewScrollableContainer.h new file mode 100644 index 00000000..abd4a7e7 --- /dev/null +++ b/kolourpaint/kpViewScrollableContainer.h @@ -0,0 +1,216 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_VIEW_SCROLLABLE_CONTAINER_H +#define KP_VIEW_SCROLLABLE_CONTAINER_H + + +#include +#include +#include +#include + + +class QCursor; +class QDragMoveEvent; +class QEvent; +class QKeyEvent; +class QMouseEvent; +class QPaintEvent; +class QRect; +class QResizeEvent; +class QTimer; + +class kpView; +class kpOverlay; + + +//--------------------------------------------------------------------- + +// REFACTOR: refactor by sharing iface's with kpTool +class kpGrip : public QWidget +{ +Q_OBJECT + +public: + enum GripType + { + Right = 1, Bottom = 2, + BottomRight = Right | Bottom + }; + + kpGrip (GripType type, QWidget *parent); + + GripType type () const; + + static QCursor cursorForType (GripType type); + + bool containsCursor(); + + bool isDrawing () const; + + static const int Size; + +signals: + void beganDraw (); + void continuedDraw (int viewDX, int viewDY, bool dueToDragScroll); + void cancelledDraw (); + void endedDraw (int viewDX, int viewDY); + + void statusMessageChanged (const QString &string); + + void releasedAllButtons (); + +public: + QString haventBegunDrawUserMessage () const; + + QString userMessage () const; + void setUserMessage (const QString &message); + +protected: + void cancel (); + +protected: + virtual void keyReleaseEvent (QKeyEvent *e); + virtual void mousePressEvent (QMouseEvent *e); +public: + QPoint viewDeltaPoint () const; + void mouseMovedTo (const QPoint &point, bool dueToDragScroll); +protected: + virtual void mouseMoveEvent (QMouseEvent *e); + virtual void mouseReleaseEvent (QMouseEvent *e); + + virtual void enterEvent (QEvent *e); + virtual void leaveEvent (QEvent *e); + +protected: + GripType m_type; + QPoint m_startPoint, m_currentPoint; + QString m_userMessage; + bool m_shouldReleaseMouseButtons; +}; + +//--------------------------------------------------------------------- + +class kpViewScrollableContainer : public QScrollArea +{ +Q_OBJECT + +public: + kpViewScrollableContainer(QWidget *parent); + + QSize newDocSize () const; + bool haveMovedFromOriginalDocSize () const; + QString statusMessage () const; + void clearStatusMessage (); + + kpView *view () const; + void setView (kpView *view); + + void drawResizeLines(); // public only for kpOverlay + +signals: + void contentsMoved(); + + void beganDocResize (); + void continuedDocResize (const QSize &size); + void cancelledDocResize (); + void endedDocResize (const QSize &size); + + // (string.isEmpty() if kpViewScrollableContainer has nothing to say) + void statusMessageChanged (const QString &string); + + void resized (); + +public slots: + void recalculateStatusMessage (); + + void updateGrips (); + + // TODO: Why the need for view's zoomLevel? We have the view() anyway. + bool beginDragScroll (int zoomLevel, + bool *didSomething); + bool beginDragScroll (int zoomLevel); + bool endDragScroll (); + +private: + void connectGripSignals (kpGrip *grip); + + QSize newDocSize (int viewDX, int viewDY) const; + + void calculateDocResizingGrip (); + kpGrip *docResizingGrip () const; + + int bottomResizeLineWidth () const; + int rightResizeLineWidth () const; + + QRect bottomResizeLineRect () const; + QRect rightResizeLineRect () const; + QRect bottomRightResizeLineRect () const; + + QRect mapViewToViewport (const QRect &viewRect); + + void updateResizeLines (int viewX, int viewY, + int viewDX, int viewDY); + + void disconnectViewSignals (); + void connectViewSignals (); + + QRect noDragScrollRect () const; + + virtual void wheelEvent(QWheelEvent *e); + virtual void resizeEvent(QResizeEvent *e); + +private slots: + void slotGripBeganDraw (); + void slotGripContinuedDraw (int viewDX, int viewDY, bool dueToScrollView); + void slotGripCancelledDraw (); + void slotGripEndedDraw (int viewDX, int viewDY); + + void slotGripStatusMessageChanged (const QString &string); + + void slotContentsMoved (); + void slotViewDestroyed (); + bool slotDragScroll (bool *didSomething); + bool slotDragScroll (); + +private: + kpView *m_view; + kpOverlay *m_overlay; + kpGrip *m_bottomGrip, *m_rightGrip, *m_bottomRightGrip; + kpGrip *m_docResizingGrip; + QTimer *m_dragScrollTimer; + int m_zoomLevel; + bool m_scrollTimerRunOnce; + int m_resizeRoundedLastViewX, m_resizeRoundedLastViewY; + int m_resizeRoundedLastViewDX, m_resizeRoundedLastViewDY; + bool m_haveMovedFromOriginalDocSize; + QString m_gripStatusMessage; +}; + +#endif // KP_VIEW_SCROLLABLE_CONTAINER_H diff --git a/kolourpaint/layers/selections/image/kpAbstractImageSelection.cpp b/kolourpaint/layers/selections/image/kpAbstractImageSelection.cpp new file mode 100644 index 00000000..ca248378 --- /dev/null +++ b/kolourpaint/layers/selections/image/kpAbstractImageSelection.cpp @@ -0,0 +1,583 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include + +#include +#include + +#include + +//--------------------------------------------------------------------- + +// Returns whether can be set to have . +// In other words, this is the precondition for .setBaseImage(width () && + baseImage.height () == sel->height ()); +} + +//--------------------------------------------------------------------- + +struct kpAbstractImageSelectionPrivate +{ + kpImage baseImage; + + kpImageSelectionTransparency transparency; + + // The mask for the image, after selection transparency (a.k.a. background + // subtraction) is applied. + QBitmap transparencyMaskCache; // OPT: calculate lazily i.e. on-demand only +}; + +//--------------------------------------------------------------------- + +// protected +kpAbstractImageSelection::kpAbstractImageSelection ( + const kpImageSelectionTransparency &transparency) + : kpAbstractSelection (), + d (new kpAbstractImageSelectionPrivate ()) +{ + setTransparency (transparency); +} + +//--------------------------------------------------------------------- + +// protected +kpAbstractImageSelection::kpAbstractImageSelection (const QRect &rect, + const kpImage &baseImage, + const kpImageSelectionTransparency &transparency) + : kpAbstractSelection (rect), + d (new kpAbstractImageSelectionPrivate ()) +{ + // This also checks that and have compatible + // relative dimensions. + setBaseImage (baseImage); + + setTransparency (transparency); +} + +//--------------------------------------------------------------------- + +// protected +kpAbstractImageSelection::kpAbstractImageSelection (const QRect &rect, + const kpImageSelectionTransparency &transparency) + : kpAbstractSelection (rect), + d (new kpAbstractImageSelectionPrivate ()) +{ + setTransparency (transparency); +} + +//--------------------------------------------------------------------- + +// protected +kpAbstractImageSelection &kpAbstractImageSelection::operator= ( + const kpAbstractImageSelection &rhs) +{ + kpAbstractSelection::operator= (rhs); + + d->baseImage = rhs.d->baseImage; + + d->transparency = rhs.d->transparency; + d->transparencyMaskCache = rhs.d->transparencyMaskCache; + + return *this; +} + +//--------------------------------------------------------------------- + +// protected +kpAbstractImageSelection::~kpAbstractImageSelection () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpAbstractSelection] +bool kpAbstractImageSelection::readFromStream (QDataStream &stream) +{ + if (!kpAbstractSelection::readFromStream (stream )) + return false; + + QImage qimage; + stream >> qimage; +#if DEBUG_KP_SELECTION && 1 + kDebug () << "\timage: w=" << qimage.width () << " h=" << qimage.height () + << " depth=" << qimage.depth () << endl; +#endif + + if (!qimage.isNull ()) + { + // Image size does not match the selection's dimensions? + // This call only accesses our superclass' fields, which have already + // been read in. + if (!::CanSetBaseImageTo (this, qimage)) + { + return false; + } + + d->baseImage = qimage; + } + // (was just a selection border in the clipboard, even though KolourPaint's + // GUI doesn't allow you to copy such a thing into the clipboard) + else + d->baseImage = kpImage (); + + // TODO: Reset transparency mask? + // TODO: Concrete subclass need to emit changed()? + // [we can't since changed() must be called after all reading + // is complete and subclasses always call this method + // _before_ their reading logic] + return true; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpAbstractSelection] +void kpAbstractImageSelection::writeToStream (QDataStream &stream) const +{ + kpAbstractSelection::writeToStream (stream); + + if (!d->baseImage.isNull ()) + { + const QImage image = d->baseImage; + #if DEBUG_KP_SELECTION && 1 + kDebug () << "\twrote image rect=" << image.rect (); + #endif + stream << image; + } + else + { + #if DEBUG_KP_SELECTION && 1 + kDebug () << "\twrote no image because no pixmap"; + #endif + stream << QImage (); + } +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +QString kpAbstractImageSelection::name () const +{ + return i18n ("Selection"); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpAbstractSelection] +kpCommandSize::SizeType kpAbstractImageSelection::size () const +{ + return kpAbstractSelection::size () + + kpCommandSize::ImageSize (d->baseImage) + + (d->transparencyMaskCache.width() * d->transparencyMaskCache.height()) / 8; +} + +//--------------------------------------------------------------------- + +// public +kpCommandSize::SizeType kpAbstractImageSelection::sizeWithoutImage () const +{ + return (size () - kpCommandSize::ImageSize (d->baseImage)); +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +int kpAbstractImageSelection::minimumWidth () const +{ + return 1; +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +int kpAbstractImageSelection::minimumHeight () const +{ + return 1; +} + +//--------------------------------------------------------------------- +// public virtual +QBitmap kpAbstractImageSelection::shapeBitmap (bool nullForRectangular) const +{ + (void) nullForRectangular; + + Q_ASSERT (boundingRect ().isValid ()); + + QBitmap maskBitmap (width (), height ()); + maskBitmap.fill (Qt::color0/*transparent*/); + + { + QPainter painter(&maskBitmap); + + painter.setPen (Qt::color1/*opaque*/); + painter.setBrush (Qt::color1/*opaque*/); + + QPolygon points = calculatePoints (); + points.translate (-x (), -y ()); + + // Unlike QPainter::drawRect(), this draws the points literally + // without being 1 pixel wider and higher. This requires a QPen + // or it will draw 1 pixel narrower and shorter. + painter.drawPolygon (points, Qt::OddEvenFill); + } + + return maskBitmap; +} + +//--------------------------------------------------------------------- + +// public +kpImage kpAbstractImageSelection::givenImageMaskedByShape (const kpImage &image) const +{ +#if DEBUG_KP_SELECTION + kDebug () << "kpAbstractImageSelection::givenImageMaskedByShape() boundingRect=" + << boundingRect () << endl; +#endif + Q_ASSERT (image.width () == width () && image.height () == height ()); + + if (isRectangular ()) + return image; + + const QRegion mRegion = shapeRegion ().translated (-topLeft ()); + +#if DEBUG_KP_SELECTION + kDebug () << "\tshapeRegion=" << shapeRegion () + << " [rect=" << shapeRegion ().boundingRect () << "]" + << " calculatePoints=" << calculatePoints () + << " [rect=" << calculatePoints ().boundingRect () << "]" + << endl; +#endif + + kpImage retImage(width (), height (), QImage::Format_ARGB32_Premultiplied); + retImage.fill(0); // transparent + + QPainter painter(&retImage); + painter.setClipRegion(mRegion); + painter.drawImage(0, 0, image); + painter.end(); + + return retImage; +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +bool kpAbstractImageSelection::hasContent () const +{ + return !d->baseImage.isNull (); +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +void kpAbstractImageSelection::deleteContent () +{ + if (!hasContent ()) + return; + + setBaseImage (kpImage ()); +} + +//--------------------------------------------------------------------- + + +// public +kpImage kpAbstractImageSelection::baseImage () const +{ + return d->baseImage; +} + +//--------------------------------------------------------------------- + +// public +void kpAbstractImageSelection::setBaseImage (const kpImage &baseImage) +{ + Q_ASSERT (::CanSetBaseImageTo (this, baseImage)); + + // qt doc: the image format must be set to Format_ARGB32Premultiplied or Format_ARGB32 + // for the composition modes to have any effect + d->baseImage = baseImage.convertToFormat(QImage::Format_ARGB32_Premultiplied); + + recalculateTransparencyMaskCache (); + + emit changed (boundingRect ()); +} + +//--------------------------------------------------------------------- + +// public +kpImageSelectionTransparency kpAbstractImageSelection::transparency () const +{ + return d->transparency; +} + +//--------------------------------------------------------------------- + +// public +bool kpAbstractImageSelection::setTransparency ( + const kpImageSelectionTransparency &transparency, + bool checkTransparentPixmapChanged) +{ + if (d->transparency == transparency) + return false; + + d->transparency = transparency; + + bool haveChanged = true; + + QBitmap oldTransparencyMaskCache = d->transparencyMaskCache; + recalculateTransparencyMaskCache (); + + if ( oldTransparencyMaskCache.size() == d->transparencyMaskCache.size() ) + { + if (d->transparencyMaskCache.isNull ()) + { + #if DEBUG_KP_SELECTION + kDebug () << "\tboth old and new pixmaps are null - nothing changed"; + #endif + haveChanged = false; + } + else if (checkTransparentPixmapChanged) + { + QImage oldTransparencyMaskImage = oldTransparencyMaskCache.toImage(); + QImage newTransparencyMaskImage = d->transparencyMaskCache.toImage(); + + bool changed = false; + for (int y = 0; y < oldTransparencyMaskImage.height () && !changed; y++) + { + for (int x = 0; x < oldTransparencyMaskImage.width () && !changed; x++) + { + if (kpPixmapFX::getColorAtPixel (oldTransparencyMaskImage, x, y) != + kpPixmapFX::getColorAtPixel (newTransparencyMaskImage, x, y)) + { + #if DEBUG_KP_SELECTION + kDebug () << "\tdiffer at " << QPoint (x, y) + << " old=" << kpPixmapFX::getColorAtPixel (oldTransparencyMaskImage, x, y).toQRgb () + << " new=" << kpPixmapFX::getColorAtPixel (newTransparencyMaskImage, x, y).toQRgb () + << endl; + #endif + changed = true; + break; + } + } + } + + if (!changed) + haveChanged = false; + } + } + + + if (haveChanged) + emit changed (boundingRect ()); + + return haveChanged; +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractImageSelection::recalculateTransparencyMaskCache () +{ +#if DEBUG_KP_SELECTION + kDebug () << "kpAbstractImageSelection::recalculateTransparencyMaskCache()"; +#endif + + if (d->baseImage.isNull ()) + { + #if DEBUG_KP_SELECTION + kDebug () << "\tno image - no need for transparency mask"; + #endif + d->transparencyMaskCache = QBitmap (); + return; + } + + if (d->transparency.isOpaque ()) + { + #if DEBUG_KP_SELECTION + kDebug () << "\topaque - no need for transparency mask"; + #endif + d->transparencyMaskCache = QBitmap (); + return; + } + + d->transparencyMaskCache = QBitmap(d->baseImage.size()); + + QPainter transparencyMaskPainter (&d->transparencyMaskCache); + + bool hasTransparent = false; + for (int y = 0; y < d->baseImage.height (); y++) + { + for (int x = 0; x < d->baseImage.width (); x++) + { + const kpColor pixelCol = kpPixmapFX::getColorAtPixel (d->baseImage, x, y); + if (pixelCol == kpColor::Transparent || + pixelCol.isSimilarTo (d->transparency.transparentColor (), + d->transparency.processedColorSimilarity ())) + { + transparencyMaskPainter.setPen (Qt::color1/*transparent*/); + hasTransparent = true; + } + else + { + transparencyMaskPainter.setPen (Qt::color0/*opaque*/); + } + + transparencyMaskPainter.drawPoint (x, y); + } + } + + transparencyMaskPainter.end (); + + if (!hasTransparent) + { + #if DEBUG_KP_SELECTION + kDebug () << "\tcolour useless - completely opaque"; + #endif + d->transparencyMaskCache = QBitmap (); + return; + } +} + +//--------------------------------------------------------------------- + +// public +kpImage kpAbstractImageSelection::transparentImage () const +{ + kpImage image = baseImage (); + + if (!d->transparencyMaskCache.isNull ()) + { + QPainter painter(&image); + painter.setCompositionMode(QPainter::CompositionMode_Clear); + painter.drawPixmap(0, 0, d->transparencyMaskCache); + } + + return image; +} + +//--------------------------------------------------------------------- + +// public +void kpAbstractImageSelection::fill (const kpColor &color) +{ + QImage newImage(width(), height(), QImage::Format_ARGB32_Premultiplied); + newImage.fill(color.toQRgb()); + + // LOTODO: Maybe disable Image/Clear menu item if transparent color + if ( !color.isTransparent() ) + { + QPainter painter(&newImage); + painter.setCompositionMode(QPainter::CompositionMode_Clear); + painter.drawPixmap(0, 0, shapeBitmap()); + } + + setBaseImage (newImage); +} + +//--------------------------------------------------------------------- + +// public virtual +void kpAbstractImageSelection::flip (bool horiz, bool vert) +{ +#if DEBUG_KP_SELECTION && 1 + kDebug () << "kpAbstractImageSelection::flip(horiz=" << horiz + << ",vert=" << vert << ")" << endl; +#endif + + if (!d->baseImage.isNull ()) + { + #if DEBUG_KP_SELECTION && 1 + kDebug () << "\thave pixmap - flipping that"; + #endif + d->baseImage = d->baseImage.mirrored(horiz, vert); + } + + if (!d->transparencyMaskCache.isNull ()) + { + #if DEBUG_KP_SELECTION && 1 + kDebug () << "\thave transparency mask - flipping that"; + #endif + QImage image = d->transparencyMaskCache.toImage().mirrored(horiz, vert); + d->transparencyMaskCache = QBitmap::fromImage(image); + } + + emit changed (boundingRect ()); +} + +//--------------------------------------------------------------------- + +static void Paint (const kpAbstractImageSelection *sel, const kpImage &srcImage, + QImage *destImage, const QRect &docRect) +{ + if (!srcImage.isNull ()) + { + kpPixmapFX::paintPixmapAt (destImage, + sel->topLeft () - docRect.topLeft (), + srcImage); + } +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +void kpAbstractImageSelection::paint (QImage *destImage, + const QRect &docRect) const +{ + ::Paint (this, transparentImage (), destImage, docRect); +} + +//--------------------------------------------------------------------- + +// public +void kpAbstractImageSelection::paintWithBaseImage (QImage *destImage, + const QRect &docRect) const +{ + ::Paint (this, baseImage (), destImage, docRect); +} + +//--------------------------------------------------------------------- + + +#include diff --git a/kolourpaint/layers/selections/image/kpAbstractImageSelection.h b/kolourpaint/layers/selections/image/kpAbstractImageSelection.h new file mode 100644 index 00000000..6753629a --- /dev/null +++ b/kolourpaint/layers/selections/image/kpAbstractImageSelection.h @@ -0,0 +1,267 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpAbstractImageSelection_H +#define kpAbstractImageSelection_H + + +#include + +#include +#include + + +// +// An abstract selection with optional image content and background +// subtraction. If there is image content, it is known as a "floating +// selection" that hovers above the document. Otherwise, it is an +// image selection "border", that highlights pixels of the document, and +// may later be upgraded to a floating selection by giving it an image +// consisting of those pixels. +// +// The images passed to this class (known as "base images") should have all +// pixels, outside of the border, set to transparent. However, nothing +// enforces this. Pixels on, or inside, the border might be opaque or +// transparent, depending on the content. +// +// The "transparent image" is the base image with background subtraction +// (kpImageSelectionTransparency) applied. This is automatically computed. +// +// The boundingRect() is the size of the border. The base image must be of +// exactly the same size, except that the base image is allowed to be null +// (for a selection that only consists of a border). +// +// Instead of copying selections' images to the clipboard, we copy +// selections, to preserve the border across KolourPaint instances. +// Background subtraction is not copied to the clipboard so that the base +// image is affected by the background subtraction of the destination +// KolourPaint window. +// +class kpAbstractImageSelection : public kpAbstractSelection +{ +Q_OBJECT + +// +// Initialization +// + +protected: + // (Call these in subclass constructors) + kpAbstractImageSelection (const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpAbstractImageSelection (const QRect &rect, + const kpImage &baseImage = kpImage (), + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpAbstractImageSelection (const QRect &rect, + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + // (Call this in subclass implementations of operator=) + kpAbstractImageSelection &operator= (const kpAbstractImageSelection &rhs); + +public: + // (Covariant return-type specialization of superclass pure virtual method) + virtual kpAbstractImageSelection *clone () const = 0; + + virtual ~kpAbstractImageSelection (); + + +// +// Marshalling +// + +public: + // You must override this if you have extra serializable fields. + // Remember to call this base implementation before your code. + virtual bool readFromStream (QDataStream &stream); + + // You must override this if you have extra serializable fields. + // Remember to call this base implementation before your code. + virtual void writeToStream (QDataStream &stream) const; + + +// +// General Queries +// + +public: + virtual QString name () const; + + // You must override this, if you have extra fields that take a + // non-constant amount of space, and add the size returned by this + // implementation. + virtual kpCommandSize::SizeType size () const; + + // Same as virtual size() (it even calls it) but subtracts the size of the + // baseImage(). + // + // kpCommand's store the kpImage's they are working on. These images may + // be from documents or selections. In the case of a selection, the + // selection's baseImage() is identical to that image, but takes no extra + // space due to kpImage's copy-on-write. This method fixes that + // double-counting of baseImage()'s size. + // + // The size of the internal transparency() mask is still included + // (see recalculateTransparencyMask()). + // + // sync: kpImage copy-on-write behavior + // + // TODO: Check all size() implementations are correct since we've + // started removing the old kpSelection::setPixmap(QPixmap()) + // (now kpAbstractImageSelection::setBaseImage(kpImage()) or + // kpAbstractImageSelection::deleteContent()) space saving hack. + kpCommandSize::SizeType sizeWithoutImage () const; + + +// +// Dimensions +// + +public: + virtual int minimumWidth () const; + virtual int minimumHeight () const; + + +// +// Shape Mask +// +// These methods do not access any class instance fields. +// + +public: + // Returns the mask corresponding to the shape of the selection. + // + // If is set, the method _may_ return a null + // bitmap if the selection is rectangular. + // + // This base implementation calls calculatePoints() and ignores + // . + // + // You should override this if you can implement it more efficiently or + // if you can honor . + // + // Note: This must be consistent with the outputs of calculatePoints() and + // shapeRegion(). + // + // TODO: Try to get rid of this method since it's slow. + virtual QBitmap shapeBitmap (bool nullForRectangular = false) const; + + // Returns the region corresponding to the shape of the selection + // e.g. elliptical region for an elliptical selection. + // + // Very slow. + // + // Note: This must be consistent with the outputs of calculatePoints() and + // shapeRegion(). + // + // OPT: QRegion is probably incredibly slow - cache + virtual QRegion shapeRegion () const = 0; + + // Returns the given with the pixels outside of the selection's + // shape set to transparent. + // + // Very slow. + // + // ASSUMPTION: The image has the same dimensions as the selection. + kpImage givenImageMaskedByShape (const kpImage &image) const; + + +// +// Content - Base Image +// + +public: + // Returns whether there's a non-null base image. + virtual bool hasContent () const; + + virtual void deleteContent (); + +public: + kpImage baseImage () const; + void setBaseImage (const kpImage &baseImage); + + +// +// Background Subtraction +// + +public: + kpImageSelectionTransparency transparency () const; + + // Returns whether or not the selection changed due to setting the + // transparency info. If is set, + // it will try harder to return false (although the check is + // expensive). + bool setTransparency (const kpImageSelectionTransparency &transparency, + bool checkTransparentPixmapChanged = false); + +private: + // Updates the selection transparency (a.k.a. background subtraction) mask + // so that transparentImage() will work. + // + // Called when the base image or selection transparency changes. + void recalculateTransparencyMaskCache (); + +public: + // Returns baseImage() after applying kpImageSelectionTransparency + kpImage transparentImage () const; + + +// +// Mutation - Effects +// + +public: + // Overwrites the base image with the selection's shape (e.g. ellipse) + // filled in with . See shapeBitmap(). + void fill (const kpColor &color); + + virtual void flip (bool horiz, bool vert); + + +// +// Rendering +// + +public: + // (using transparent image) + virtual void paint (QImage *destPixmap, const QRect &docRect) const; + + // (using base image) + void paintWithBaseImage (QImage *destPixmap, const QRect &docRect) const; + + +private: + struct kpAbstractImageSelectionPrivate * const d; +}; + + +#endif // kpAbstractImageSelection_H diff --git a/kolourpaint/layers/selections/image/kpEllipticalImageSelection.cpp b/kolourpaint/layers/selections/image/kpEllipticalImageSelection.cpp new file mode 100644 index 00000000..2c43285b --- /dev/null +++ b/kolourpaint/layers/selections/image/kpEllipticalImageSelection.cpp @@ -0,0 +1,183 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include + +#include +#include + + +struct kpEllipticalImageSelectionPrivate +{ +}; + + +kpEllipticalImageSelection::kpEllipticalImageSelection ( + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (transparency), + d (new kpEllipticalImageSelectionPrivate ()) +{ +} + +kpEllipticalImageSelection::kpEllipticalImageSelection (const QRect &rect, + const kpImage &baseImage, + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (rect, baseImage, transparency), + d (new kpEllipticalImageSelectionPrivate ()) +{ +} + +kpEllipticalImageSelection::kpEllipticalImageSelection (const QRect &rect, + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (rect, transparency), + d (new kpEllipticalImageSelectionPrivate ()) +{ +} + +kpEllipticalImageSelection::kpEllipticalImageSelection (const kpEllipticalImageSelection &rhs) + : kpAbstractImageSelection (), + d (new kpEllipticalImageSelectionPrivate ()) +{ + *this = rhs; +} + +kpEllipticalImageSelection &kpEllipticalImageSelection::operator= ( + const kpEllipticalImageSelection &rhs) +{ + kpAbstractImageSelection::operator= (rhs); + + return *this; +} + +kpEllipticalImageSelection *kpEllipticalImageSelection::clone () const +{ + kpEllipticalImageSelection *sel = new kpEllipticalImageSelection (); + *sel = *this; + return sel; +} + +kpEllipticalImageSelection::~kpEllipticalImageSelection () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +int kpEllipticalImageSelection::serialID () const +{ + return SerialID; +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +bool kpEllipticalImageSelection::isRectangular () const +{ + return false; +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +QPolygon kpEllipticalImageSelection::calculatePoints () const +{ + Q_ASSERT (boundingRect ().isValid ()); + + if (width () == 1 && height () == 1) + { + QPolygon ret; + ret.append (topLeft ()); + return ret; + } + + QPainterPath path; + if (width () == 1 || height () == 1) + { + path.moveTo (x (), y ()); + // This does not work when the width _and_ height are 1 since lineTo() + // would not move at all. This is why we have a separate case for that + // at the top of the method. + path.lineTo (x () + width () - 1, y () + height () - 1); + } + else + { + // The adjusting is to fight QPainterPath::addEllipse() making + // the ellipse 1 pixel higher and wider than specified. + path.addEllipse (boundingRect ().adjusted (0, 0, -1, -1)); + } + + const QList polygons = path.toSubpathPolygons (); + Q_ASSERT (polygons.size () == 1); + + const QPolygonF firstPolygonF = polygons.first (); + return firstPolygonF.toPolygon (); +} + +//--------------------------------------------------------------------- + + +// protected virtual [kpAbstractImageSelection] +QRegion kpEllipticalImageSelection::shapeRegion () const +{ + QRegion reg(calculatePoints()); + return reg; +} + +//--------------------------------------------------------------------- + + +// public virtual [kpAbstractSelection] +bool kpEllipticalImageSelection::contains (const QPoint &point) const +{ + if (!boundingRect ().contains (point)) + return false; + + return shapeRegion ().contains (point); +} + +//--------------------------------------------------------------------- + + +// public virtual [kpAbstractSelection] +void kpEllipticalImageSelection::paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const +{ + if ( !boundingRect().isValid() ) + return; + + paintPolygonalBorder (calculatePoints (), + destPixmap, docRect, + selectionFinished); +} + + +#include diff --git a/kolourpaint/layers/selections/image/kpEllipticalImageSelection.h b/kolourpaint/layers/selections/image/kpEllipticalImageSelection.h new file mode 100644 index 00000000..e52a6cb9 --- /dev/null +++ b/kolourpaint/layers/selections/image/kpEllipticalImageSelection.h @@ -0,0 +1,118 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEllipticalImageSelection_H +#define kpEllipticalImageSelection_H + + +#include + + +class kpEllipticalImageSelection : public kpAbstractImageSelection +{ +Q_OBJECT + +public: + kpEllipticalImageSelection (const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpEllipticalImageSelection (const QRect &rect, + const kpImage &baseImage = kpImage (), + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpEllipticalImageSelection (const QRect &rect, + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpEllipticalImageSelection (const kpEllipticalImageSelection &rhs); + + kpEllipticalImageSelection &operator= (const kpEllipticalImageSelection &rhs); + + virtual kpEllipticalImageSelection *clone () const; + + virtual ~kpEllipticalImageSelection (); + + +// +// Marshalling +// + +public: + static const int SerialID = 1; + virtual int serialID () const; + + +// +// General Queries +// + +public: + virtual bool isRectangular () const; + + +// +// Position & Dimensions +// + +public: + virtual QPolygon calculatePoints () const; + + +// +// Shape Mask +// + +public: + virtual QRegion shapeRegion () const; + + +// +// Point Testing +// + +public: + virtual bool contains (const QPoint &point) const; + + +// +// Rendering +// + +public: + virtual void paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const; + + +private: + struct kpEllipticalImageSelectionPrivate * const d; +}; + + + +#endif // kpEllipticalImageSelection_H diff --git a/kolourpaint/layers/selections/image/kpFreeFormImageSelection.cpp b/kolourpaint/layers/selections/image/kpFreeFormImageSelection.cpp new file mode 100644 index 00000000..f419bde8 --- /dev/null +++ b/kolourpaint/layers/selections/image/kpFreeFormImageSelection.cpp @@ -0,0 +1,389 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include + +#include + +#include + + +struct kpFreeFormImageSelectionPrivate +{ + QPolygon orgPoints; + + // Various Qt methods that take a QPolygon interpolate points differently + // (e.g. QPainter::drawPolygon() vs QRegion(QPolygon)) when given consecutive + // points that are not cardinally adjacent e.g. these 2 points: + // + // # + // # + // + // are diagonally, but not cardinally, adjacent. They are rendered + // inconsistently. Also, points which are not adjacent at all definitely + // require interpolation and are inconsistently rendered: + // + // # + // # + // + // So, we only pass cardinally interpolated points to those methods to + // avoid this issue: + // + // ## + // # + // + // These interpolated points are stored in . Regarding + // , see the APIDoc for cardinallyAdjacentPointsLoop(). + QPolygon cardPointsCache, cardPointsLoopCache; +}; + + +kpFreeFormImageSelection::kpFreeFormImageSelection ( + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (transparency), + d (new kpFreeFormImageSelectionPrivate ()) +{ +} + +kpFreeFormImageSelection::kpFreeFormImageSelection (const QPolygon &points, + const kpImage &baseImage, + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (points.boundingRect (), baseImage, transparency), + d (new kpFreeFormImageSelectionPrivate ()) +{ + d->orgPoints = points; + recalculateCardinallyAdjacentPoints (); +} + +kpFreeFormImageSelection::kpFreeFormImageSelection (const QPolygon &points, + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (points.boundingRect (), transparency), + d (new kpFreeFormImageSelectionPrivate ()) +{ + d->orgPoints = points; + recalculateCardinallyAdjacentPoints (); +} + +kpFreeFormImageSelection::kpFreeFormImageSelection (const kpFreeFormImageSelection &rhs) + : kpAbstractImageSelection (), + d (new kpFreeFormImageSelectionPrivate ()) +{ + *this = rhs; +} + +kpFreeFormImageSelection &kpFreeFormImageSelection::operator= (const kpFreeFormImageSelection &rhs) +{ + kpAbstractImageSelection::operator= (rhs); + + d->orgPoints = rhs.d->orgPoints; + d->cardPointsCache = rhs.d->cardPointsCache; + d->cardPointsLoopCache = rhs.d->cardPointsLoopCache; + + return *this; +} + +// public virtual [kpAbstractSelection] +kpFreeFormImageSelection *kpFreeFormImageSelection::clone () const +{ + kpFreeFormImageSelection *sel = new kpFreeFormImageSelection (); + *sel = *this; + return sel; +} + +kpFreeFormImageSelection::~kpFreeFormImageSelection () +{ + delete d; +} + + +// public virtual [kpAbstractSelection] +int kpFreeFormImageSelection::serialID () const +{ + return SerialID; +} + +// public virtual [base kpAbstractImageSelection] +bool kpFreeFormImageSelection::readFromStream (QDataStream &stream) +{ + if (!kpAbstractImageSelection::readFromStream (stream)) + return false; + + stream >> d->orgPoints; + recalculateCardinallyAdjacentPoints (); + + return true; +} + +// public virtual [base kpAbstractImageSelection] +void kpFreeFormImageSelection::writeToStream (QDataStream &stream) const +{ + kpAbstractImageSelection::writeToStream (stream); + + stream << d->orgPoints; +} + + +// public virtual [base kpAbstractImageSelection] +kpCommandSize::SizeType kpFreeFormImageSelection::size () const +{ + return kpAbstractImageSelection::size () + + (kpCommandSize::PolygonSize (d->orgPoints) + + kpCommandSize::PolygonSize (d->cardPointsCache) + + kpCommandSize::PolygonSize (d->cardPointsLoopCache)); +} + +// public virtual [kpAbstractSelection] +bool kpFreeFormImageSelection::isRectangular () const +{ + return false; +} + +// public +QPolygon kpFreeFormImageSelection::originalPoints () const +{ + return d->orgPoints; +} + + +static QPolygon RecalculateCardinallyAdjacentPoints (const QPolygon &points) +{ +#if DEBUG_KP_SELECTION + kDebug () << "kpFreeFormImageSelection.cpp:RecalculateCardinallyAdjacentPoints()" + << endl; + kDebug () << "\tpoints=" << points; +#endif + + // Filter out duplicates. + QPolygon noDups; + foreach (const QPoint &p, points) + { + if (!noDups.isEmpty () && p == noDups.last ()) + continue; + + noDups.append (p); + } +#if DEBUG_KP_SELECTION + kDebug () << "\twithout dups=" << noDups; +#endif + + // Interpolate to ensure cardinal adjacency. + QPolygon cardPoints; + foreach (const QPoint &p, noDups) + { + if (!cardPoints.isEmpty () && + !kpPainter::pointsAreCardinallyAdjacent (p, cardPoints.last ())) + { + const QPoint lastPoint = cardPoints.last (); + + QList interpPoints = kpPainter::interpolatePoints ( + lastPoint, + p, + true/*cardinal adjacency*/); + + Q_ASSERT (interpPoints.size () >= 2); + Q_ASSERT (interpPoints [0] == lastPoint); + Q_ASSERT (interpPoints.last () == p); + + for (int i = 1/*skip already existing point*/; + i < interpPoints.size (); + i++) + { + cardPoints.append (interpPoints [i]); + } + } + else + cardPoints.append (p); + } +#if DEBUG_KP_SELECTION + kDebug () << "\tcardinally adjacent=" << cardPoints; +#endif + + return cardPoints; +} + +// protected +void kpFreeFormImageSelection::recalculateCardinallyAdjacentPoints () +{ + d->cardPointsCache = ::RecalculateCardinallyAdjacentPoints (d->orgPoints); + + + QPolygon pointsLoop = d->cardPointsCache; + if (!pointsLoop.isEmpty ()) + pointsLoop.append (pointsLoop.first ()); + + // OPT: We know this method only needs to act on the last 2 points of + // "pointLoop", since the previous points are definitely cardinally + // adjacent. + d->cardPointsLoopCache = ::RecalculateCardinallyAdjacentPoints (pointsLoop); +} + +// public +QPolygon kpFreeFormImageSelection::cardinallyAdjacentPoints () const +{ + return d->cardPointsCache; +} + +// public +QPolygon kpFreeFormImageSelection::cardinallyAdjacentPointsLoop () const +{ + return d->cardPointsLoopCache; +} + + +// public virtual [kpAbstractSelection] +QPolygon kpFreeFormImageSelection::calculatePoints () const +{ + return d->cardPointsLoopCache; +} + + +// protected virtual [kpAbstractSelection] +QRegion kpFreeFormImageSelection::shapeRegion () const +{ + const QRegion region = QRegion (d->cardPointsLoopCache, Qt::OddEvenFill); + + // In Qt4, while QPainter::drawRect() gives you rectangles 1 pixel + // wider and higher, QRegion(QPolygon) gives you regions 1 pixel + // narrower and shorter! Compensate for this by merging shifted + // versions of the region. This seems to be consistent with shapeBitmap() + // but I am a bit worried. + // + // Regarding alternative solutions: + // 1. Instead of doing this region shifting and merging, if we were to + // construct a QRegion simply from a point array with 4 points for + // every point in "d->cardPointsLoopCache" (4 points = original point + 3 + // translations below), it probably wouldn't work because the order of + // the points in any point array matter for the odd-even fill + // algorithm. This would probably manifest as problems with + // self-intersecting borders. + // 2. Constructing a QRegion from QBitmap (from shapeBitmap()) is probably + // very slow since it would have to read each pixel of the QBitmap. + // Having said that, this is probably the safest option as region shifting + // is dodgy. Also, this would guarantee that shapeBitmap() and shapeRegion() + // are consistent and we wouldn't need cardinally adjacent points either + // (d->cardPointsCache and d->cardPointsLoopCache). + const QRegion regionX = region.translated (1, 0); + const QRegion regionY = region.translated (0, 1); + const QRegion regionXY = region.translated (1, 1); + + return region.united (regionX).united (regionY).united (regionXY); +} + + +// public virtual [kpAbstractSelection] +bool kpFreeFormImageSelection::contains (const QPoint &point) const +{ + if (!boundingRect ().contains (point)) + return false; + + // We can't use the baseImage() (when non-null) and get the transparency of + // the pixel at , instead of this region test, as the pixel may be + // transparent but still within the border. + return shapeRegion ().contains (point); +} + + +// public virtual [base kpAbstractSelection] +void kpFreeFormImageSelection::moveBy (int dx, int dy) +{ + d->orgPoints.translate (dx, dy); + + d->cardPointsCache.translate (dx, dy); + d->cardPointsLoopCache.translate (dx, dy); + + // Call base last since it fires the changed() signal and we only + // want that to fire at the very end of this method, after all + // the selection state has been changed. + kpAbstractImageSelection::moveBy (dx, dy); +} + +//--------------------------------------------------------------------- + +static void FlipPoints (QPolygon *points, + bool horiz, bool vert, + const QRect &oldRect) +{ + points->translate (-oldRect.x (), -oldRect.y ()); + + const QMatrix matrix (horiz ? -1 : +1, // m11 + 0, // m12 + 0, // m21 + vert ? -1 : +1, // m22 + horiz ? (oldRect.width() - 1) : 0, // dx + vert ? (oldRect.height() - 1) : 0); // dy + +#if !defined (QT_NO_DEBUG) && !defined (NDEBUG) + QPolygon oldPoints = *points; +#endif + + *points = matrix.map (*points); + +#if !defined (QT_NO_DEBUG) && !defined (NDEBUG) + // Sanity check: flipping the points twice gives us the original points. + Q_ASSERT (oldPoints == matrix.map (*points)); +#endif + + points->translate (oldRect.x (), oldRect.y ()); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpAbstractImageSelection] +void kpFreeFormImageSelection::flip (bool horiz, bool vert) +{ + ::FlipPoints (&d->orgPoints, horiz, vert, boundingRect ()); + + ::FlipPoints (&d->cardPointsCache, horiz, vert, boundingRect ()); + ::FlipPoints (&d->cardPointsLoopCache, horiz, vert, boundingRect ()); + + + // Call base last since it fires the changed() signal and we only + // want that to fire at the very end of this method, after all + // the selection state has been changed. + kpAbstractImageSelection::flip (horiz, vert); +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +void kpFreeFormImageSelection::paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const +{ + if (selectionFinished) + paintPolygonalBorder (cardinallyAdjacentPointsLoop (), + destPixmap, docRect, selectionFinished); + else + paintPolygonalBorder (cardinallyAdjacentPoints (), + destPixmap, docRect, selectionFinished); +} + + +#include diff --git a/kolourpaint/layers/selections/image/kpFreeFormImageSelection.h b/kolourpaint/layers/selections/image/kpFreeFormImageSelection.h new file mode 100644 index 00000000..c2751d1d --- /dev/null +++ b/kolourpaint/layers/selections/image/kpFreeFormImageSelection.h @@ -0,0 +1,159 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpFreeFormImageSelection_H +#define kpFreeFormImageSelection_H + + +#include + + +class kpFreeFormImageSelection : public kpAbstractImageSelection +{ +Q_OBJECT + +public: + kpFreeFormImageSelection (const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpFreeFormImageSelection (const QPolygon &points, + const kpImage &baseImage = kpImage (), + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpFreeFormImageSelection (const QPolygon &points, + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpFreeFormImageSelection (const kpFreeFormImageSelection &rhs); + + kpFreeFormImageSelection &operator= (const kpFreeFormImageSelection &rhs); + + virtual kpFreeFormImageSelection *clone () const; + + virtual ~kpFreeFormImageSelection (); + + +// +// Marshalling +// + +public: + static const int SerialID = 2; + virtual int serialID () const; + + virtual bool readFromStream (QDataStream &stream); + + virtual void writeToStream (QDataStream &stream) const; + + +// +// General Queries +// + +public: + virtual kpCommandSize::SizeType size () const; + + virtual bool isRectangular () const; + + // (as passed to the constructor) + QPolygon originalPoints () const; + + +// +// Cardinally Adjacent Points +// + +protected: + void recalculateCardinallyAdjacentPoints (); + +public: + // Returns the originalPoints() interpolated to be cardinally adjacent. + QPolygon cardinallyAdjacentPoints () const; + + // Returns cardinallyAdjacentPoints() but with extra points interpolated + // from the last point to the first point (the original points are + // thought of as a polygon where the first and last points are connected, + // rather than as a string of points). + // + // As used by the shape mask methods. + QPolygon cardinallyAdjacentPointsLoop () const; + + +// +// Position & Dimensions +// + +public: + // Implements kpAbstractSelection interface - same as + // cardinallyAdjacentPointsLoop (). + // This implementation is fast. + virtual QPolygon calculatePoints () const; + + +// +// Shape Mask +// + +public: + virtual QRegion shapeRegion () const; + + +// +// Point Testing +// + +public: + virtual bool contains (const QPoint &point) const; + + +// +// Mutation +// + +public: + virtual void moveBy (int dx, int dy); + + virtual void flip (bool horiz, bool vert); + + +// +// Rendering +// + +public: + virtual void paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const; + + +private: + struct kpFreeFormImageSelectionPrivate * const d; +}; + + +#endif // kpFreeFormImageSelection_H diff --git a/kolourpaint/layers/selections/image/kpImageSelectionTransparency.cpp b/kolourpaint/layers/selections/image/kpImageSelectionTransparency.cpp new file mode 100644 index 00000000..736de9b2 --- /dev/null +++ b/kolourpaint/layers/selections/image/kpImageSelectionTransparency.cpp @@ -0,0 +1,208 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION_TRANSPARENCY 0 + + +#include + +#include + +#include +#include + + +//--------------------------------------------------------------------- + +kpImageSelectionTransparency::kpImageSelectionTransparency () + : m_isOpaque (true) +{ + setColorSimilarity (0); +} + +//--------------------------------------------------------------------- + +kpImageSelectionTransparency::kpImageSelectionTransparency (const kpColor &transparentColor, double colorSimilarity) + : m_isOpaque (false), + m_transparentColor (transparentColor) +{ + setColorSimilarity (colorSimilarity); +} + +//--------------------------------------------------------------------- + +kpImageSelectionTransparency::kpImageSelectionTransparency (bool isOpaque, const kpColor &transparentColor, + double colorSimilarity) + : m_isOpaque (isOpaque), + m_transparentColor (transparentColor) +{ + setColorSimilarity (colorSimilarity); +} + +//--------------------------------------------------------------------- + +bool kpImageSelectionTransparency::operator== (const kpImageSelectionTransparency &rhs) const +{ +#if DEBUG_KP_SELECTION_TRANSPARENCY && 0 + kDebug () << "kpImageSelectionTransparency::operator==()"; +#endif + + if (m_isOpaque != rhs.m_isOpaque) + { + #if DEBUG_KP_SELECTION_TRANSPARENCY && 0 + kDebug () << "\tdifferent opacity: lhs=" << m_isOpaque + << " rhs=" << rhs.m_isOpaque + << endl; + #endif + return false; + } + + if (m_isOpaque) + { + #if DEBUG_KP_SELECTION_TRANSPARENCY && 0 + kDebug () << "\tboth opaque - eq"; + #endif + return true; + } + +#if DEBUG_KP_SELECTION_TRANSPARENCY && 0 + kDebug () << "\tcolours: lhs=" << (int *) m_transparentColor.toQRgb () + << " rhs=" << (int *) rhs.m_transparentColor.toQRgb () + << endl; + kDebug () << "\tcolour similarity: lhs=" << m_colorSimilarity + << " rhs=" << rhs.m_colorSimilarity + << endl; +#endif + + return (m_transparentColor == rhs.m_transparentColor && + m_colorSimilarity == rhs.m_colorSimilarity); +} + +//--------------------------------------------------------------------- + +bool kpImageSelectionTransparency::operator!= (const kpImageSelectionTransparency &rhs) const +{ + return !(*this == rhs); +} + +//--------------------------------------------------------------------- + +kpImageSelectionTransparency::~kpImageSelectionTransparency () +{ +} + +//--------------------------------------------------------------------- + +// public +bool kpImageSelectionTransparency::isOpaque () const +{ + return m_isOpaque; +} + +//--------------------------------------------------------------------- + +// public +bool kpImageSelectionTransparency::isTransparent () const +{ + return !isOpaque (); +} + +//--------------------------------------------------------------------- + +// public +void kpImageSelectionTransparency::setOpaque (bool yes) +{ + m_isOpaque = yes; +} + +//--------------------------------------------------------------------- + +// public +void kpImageSelectionTransparency::setTransparent (bool yes) +{ + setOpaque (!yes); +} + +//--------------------------------------------------------------------- + + +// public +kpColor kpImageSelectionTransparency::transparentColor () const +{ + if (m_isOpaque) + { + // There are legitimate uses for this so no kError() + kDebug () << "kpImageSelectionTransparency::transparentColor() " + "getting transparent color even though opaque" << endl; + } + + return m_transparentColor; +} + +//--------------------------------------------------------------------- + +// public +void kpImageSelectionTransparency::setTransparentColor (const kpColor &transparentColor) +{ + m_transparentColor = transparentColor; +} + +//--------------------------------------------------------------------- + + +// public +double kpImageSelectionTransparency::colorSimilarity () const +{ + if (m_colorSimilarity < 0 || + m_colorSimilarity > kpColorSimilarityHolder::MaxColorSimilarity) + { + kError () << "kpImageSelectionTransparency::colorSimilarity() invalid colorSimilarity" << endl; + return 0; + } + + return m_colorSimilarity; +} + +//--------------------------------------------------------------------- + +// pubulic +void kpImageSelectionTransparency::setColorSimilarity (double colorSimilarity) +{ + m_colorSimilarity = colorSimilarity; + m_processedColorSimilarity = kpColor::processSimilarity (colorSimilarity); +} + +//--------------------------------------------------------------------- + +// public +int kpImageSelectionTransparency::processedColorSimilarity () const +{ + return m_processedColorSimilarity; +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/layers/selections/image/kpImageSelectionTransparency.h b/kolourpaint/layers/selections/image/kpImageSelectionTransparency.h new file mode 100644 index 00000000..b110b39c --- /dev/null +++ b/kolourpaint/layers/selections/image/kpImageSelectionTransparency.h @@ -0,0 +1,82 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_SELECTION_TRANSPARENCY_H +#define KP_SELECTION_TRANSPARENCY_H + + +#include + + +// This does not apply to the Text Tool. Use kpTextStyle for that. +class kpImageSelectionTransparency +{ +public: + // Opaque selection + kpImageSelectionTransparency (); + // Selection that's transparent at pixels with + kpImageSelectionTransparency (const kpColor &transparentColor, double colorSimilarity); + // If , is allowed to be anything + // (including invalid) as the color would have no effect. + // However, you are encouraged to set it as you would if !, + // because setTransparent(true) might be called later, after which + // the would suddenly become important. + // + // It is a similar case with , although + // must be in-range (see kpColorSimilarityHolder). + kpImageSelectionTransparency (bool isOpaque, const kpColor &transparentColor, double colorSimilarity); + // Returns whether they are visually equivalent. + // This is the same as a memcmp() except that if they are both opaque, + // this function will return true regardless of the transparentColor's. + bool operator== (const kpImageSelectionTransparency &rhs) const; + bool operator!= (const kpImageSelectionTransparency &rhs) const; + ~kpImageSelectionTransparency (); + + bool isOpaque () const; + bool isTransparent () const; + void setOpaque (bool yes = true); + void setTransparent (bool yes = true); + + // If isOpaque(), transparentColor() is generally not called because + // the transparent color would have no effect. + kpColor transparentColor () const; + void setTransparentColor (const kpColor &transparentColor); + + double colorSimilarity () const; + void setColorSimilarity (double colorSimilarity); + int processedColorSimilarity () const; + +private: + bool m_isOpaque; + kpColor m_transparentColor; + double m_colorSimilarity; + int m_processedColorSimilarity; +}; + + +#endif // KP_SELECTION_TRANSPARENCY_H diff --git a/kolourpaint/layers/selections/image/kpRectangularImageSelection.cpp b/kolourpaint/layers/selections/image/kpRectangularImageSelection.cpp new file mode 100644 index 00000000..728ea0e5 --- /dev/null +++ b/kolourpaint/layers/selections/image/kpRectangularImageSelection.cpp @@ -0,0 +1,149 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include + +#include +#include + + +struct kpRectangularImageSelectionPrivate +{ +}; + + +kpRectangularImageSelection::kpRectangularImageSelection ( + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (transparency), + d (new kpRectangularImageSelectionPrivate ()) +{ +} + +kpRectangularImageSelection::kpRectangularImageSelection (const QRect &rect, + const kpImage &baseImage, + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (rect, baseImage, transparency), + d (new kpRectangularImageSelectionPrivate ()) +{ +} + +kpRectangularImageSelection::kpRectangularImageSelection (const QRect &rect, + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (rect, transparency), + d (new kpRectangularImageSelectionPrivate ()) +{ +} + +kpRectangularImageSelection::kpRectangularImageSelection (const kpRectangularImageSelection &rhs) + : kpAbstractImageSelection (), + d (new kpRectangularImageSelectionPrivate ()) +{ + *this = rhs; +} + +kpRectangularImageSelection &kpRectangularImageSelection::operator= ( + const kpRectangularImageSelection &rhs) +{ + kpAbstractImageSelection::operator= (rhs); + + return *this; +} + +kpRectangularImageSelection *kpRectangularImageSelection::clone () const +{ + kpRectangularImageSelection *sel = new kpRectangularImageSelection (); + *sel = *this; + return sel; +} + +kpRectangularImageSelection::~kpRectangularImageSelection () +{ + delete d; +} + + +// public virtual [kpAbstractSelection] +int kpRectangularImageSelection::serialID () const +{ + return SerialID; +} + + +// public virtual [kpAbstractSelection] +bool kpRectangularImageSelection::isRectangular () const +{ + return true; +} + + +// public virtual [kpAbstractSelection] +QPolygon kpRectangularImageSelection::calculatePoints () const +{ + return kpAbstractImageSelection::CalculatePointsForRectangle (boundingRect ()); +} + + +// public virtual [base kpAbstractImageSelection] +QBitmap kpRectangularImageSelection::shapeBitmap (bool nullForRectangular) const +{ + Q_ASSERT (boundingRect ().isValid ()); + + if (nullForRectangular) + return QBitmap (); + + QBitmap maskBitmap (width (), height ()); + maskBitmap.fill (Qt::color1/*opaque*/); + return maskBitmap; +} + +// public virtual [kpAbstractImageSelection] +QRegion kpRectangularImageSelection::shapeRegion () const +{ + return QRegion (boundingRect (), QRegion::Rectangle); +} + + +// public virtual [kpAbstractSelection] +bool kpRectangularImageSelection::contains (const QPoint &point) const +{ + return boundingRect ().contains (point); +} + + +// public virtual [kpAbstractSelection] +void kpRectangularImageSelection::paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const +{ + paintRectangularBorder (destPixmap, docRect, selectionFinished); +} + + +#include diff --git a/kolourpaint/layers/selections/image/kpRectangularImageSelection.h b/kolourpaint/layers/selections/image/kpRectangularImageSelection.h new file mode 100644 index 00000000..b486bd84 --- /dev/null +++ b/kolourpaint/layers/selections/image/kpRectangularImageSelection.h @@ -0,0 +1,119 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpRectangularImageSelection_H +#define kpRectangularImageSelection_H + + +#include + + +class kpRectangularImageSelection : public kpAbstractImageSelection +{ +Q_OBJECT + +public: + kpRectangularImageSelection (const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpRectangularImageSelection (const QRect &rect, + const kpImage &baseImage = kpImage (), + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpRectangularImageSelection (const QRect &rect, + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpRectangularImageSelection (const kpRectangularImageSelection &rhs); + + kpRectangularImageSelection &operator= (const kpRectangularImageSelection &rhs); + + virtual kpRectangularImageSelection *clone () const; + + virtual ~kpRectangularImageSelection (); + + +// +// Marshalling +// + +public: + static const int SerialID = 0; + virtual int serialID () const; + + +// +// General Queries +// + +public: + virtual bool isRectangular () const; + + +// +// Position & Dimensions +// + +public: + virtual QPolygon calculatePoints () const; + + +// +// Shape Mask +// + +public: + virtual QBitmap shapeBitmap (bool nullForRectangular = false) const; + + virtual QRegion shapeRegion () const; + + +// +// Point Testing +// + +public: + virtual bool contains (const QPoint &point) const; + + +// +// Rendering +// + +public: + virtual void paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const; + + +private: + struct kpRectangularImageSelectionPrivate * const d; +}; + + +#endif // kpRectangularImageSelection_H diff --git a/kolourpaint/layers/selections/kpAbstractSelection.cpp b/kolourpaint/layers/selections/kpAbstractSelection.cpp new file mode 100644 index 00000000..8b47c3cd --- /dev/null +++ b/kolourpaint/layers/selections/kpAbstractSelection.cpp @@ -0,0 +1,312 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include + +#include +#include + +#include + +struct kpAbstractSelectionPrivate +{ + QRect rect; +}; + + +// protected +kpAbstractSelection::kpAbstractSelection () + : QObject (), + d (new kpAbstractSelectionPrivate ()) +{ + d->rect = QRect (); +} + +// protected +kpAbstractSelection::kpAbstractSelection (const QRect &rect) + : QObject (), + d (new kpAbstractSelectionPrivate ()) +{ + d->rect = rect; +} + +// protected +kpAbstractSelection &kpAbstractSelection::operator= (const kpAbstractSelection &rhs) +{ + if (this == &rhs) + return *this; + + d->rect = rhs.d->rect; + + return *this; +} + +// protected +kpAbstractSelection::~kpAbstractSelection () +{ + delete d; +} + + +// public virtual +bool kpAbstractSelection::readFromStream (QDataStream &stream) +{ + stream >> d->rect; + + return true; +} + +// public virtual +void kpAbstractSelection::writeToStream (QDataStream &stream) const +{ + stream << d->rect; +} + +// friend +QDataStream &operator<< (QDataStream &stream, const kpAbstractSelection &selection) +{ +#if DEBUG_KP_SELECTION && 1 + kDebug () << "kpAbstractSelection::operator<<(sel: rect=" << + selection.boundingRect () << endl; +#endif + stream << selection.serialID (); + selection.writeToStream (stream); + return stream; +} + + +// public virtual +kpCommandSize::SizeType kpAbstractSelection::size () const +{ + return 0/*constant size*/; +} + + +// public +QSize kpAbstractSelection::minimumSize () const +{ + return QSize (minimumWidth (), minimumHeight ()); +} + + +// public +int kpAbstractSelection::x () const +{ + return d->rect.x (); +} + +// public +int kpAbstractSelection::y () const +{ + return d->rect.y (); +} + +// public +QPoint kpAbstractSelection::topLeft () const +{ + return d->rect.topLeft (); +} + + +// public +int kpAbstractSelection::width () const +{ + return boundingRect ().width (); +} + +// public +int kpAbstractSelection::height () const +{ + return boundingRect ().height (); +} + +// public +QRect kpAbstractSelection::boundingRect () const +{ + return d->rect; +} + +// public static +QPolygon kpAbstractSelection::CalculatePointsForRectangle (const QRect &rect) +{ + QPolygon points; + + // OPT: not space optimal - current code adds duplicate corner points. + + // top + for (int x = 0; x < rect.width (); x++) + points.append (QPoint (rect.x () + x, rect.top ())); + + // right + for (int y = 0; y < rect.height (); y++) + points.append (QPoint (rect.right (), rect.y () + y)); + + // bottom + for (int x = rect.width () - 1; x >= 0; x--) + points.append (QPoint (rect.x () + x, rect.bottom ())); + + // left + for (int y = rect.height () - 1; y >= 0; y--) + points.append (QPoint (rect.left (), rect.y () + y)); + + return points; +} + + +// public +bool kpAbstractSelection::contains (int x, int y) const +{ + return contains (QPoint (x, y)); +} + + +// public virtual +void kpAbstractSelection::moveBy (int dx, int dy) +{ +#if DEBUG_KP_SELECTION && 1 + kDebug () << "kpAbstractSelection::moveBy(" << dx << "," << dy << ")"; +#endif + + if (dx == 0 && dy == 0) + return; + + QRect oldRect = boundingRect (); + +#if DEBUG_KP_SELECTION && 1 + kDebug () << "\toldRect=" << oldRect; +#endif + + d->rect.translate (dx, dy); +#if DEBUG_KP_SELECTION && 1 + kDebug () << "\tnewRect=" << d->rect; +#endif + + emit changed (oldRect); + emit changed (boundingRect ()); +} + +// public +void kpAbstractSelection::moveTo (int dx, int dy) +{ + moveTo (QPoint (dx, dy)); +} + +// public +void kpAbstractSelection::moveTo (const QPoint &topLeftPoint) +{ +#if DEBUG_KP_SELECTION && 1 + kDebug () << "kpAbstractSelection::moveTo(" << topLeftPoint << ")"; +#endif + QRect oldBoundingRect = boundingRect (); +#if DEBUG_KP_SELECTION && 1 + kDebug () << "\toldBoundingRect=" << oldBoundingRect; +#endif + if (topLeftPoint == oldBoundingRect.topLeft ()) + return; + + QPoint delta (topLeftPoint - oldBoundingRect.topLeft ()); + moveBy (delta.x (), delta.y ()); +} + +//--------------------------------------------------------------------- + +// protected +void kpAbstractSelection::paintRectangularBorder (QImage *destPixmap, + const QRect &docRect, + bool selectionFinished) const +{ + (void) selectionFinished; + +#if DEBUG_KP_SELECTION && 1 + kDebug () << "kpAbstractSelection::paintRectangularBorder() boundingRect=" + << boundingRect () << endl; +#endif + +#if DEBUG_KP_SELECTION && 1 + kDebug () << "\tselection border = rectangle"; + kDebug () << "\t\tx=" << boundingRect ().x () - docRect.x () + << " y=" << boundingRect ().y () - docRect.y () + << " w=" << boundingRect ().width () + << " h=" << boundingRect ().height () + << endl; +#endif + kpPixmapFX::drawRect(destPixmap, + boundingRect ().x () - docRect.x (), + boundingRect ().y () - docRect.y (), + boundingRect ().width (), + boundingRect ().height (), + kpColor::Blue, 1/*pen width*/, + kpColor::Invalid/*no background*/, + kpColor::Yellow); +} + +//--------------------------------------------------------------------- + +// protected +void kpAbstractSelection::paintPolygonalBorder (const QPolygon &points, + QImage *destPixmap, + const QRect &docRect, + bool selectionFinished) const +{ +#if DEBUG_KP_SELECTION && 1 + kDebug () << "kpAbstractSelection::paintPolygonalBorder() boundingRect=" + << boundingRect () << endl; +#endif + + QPolygon pointsTranslated = points; + pointsTranslated.translate (-docRect.x (), -docRect.y ()); + + if ( !selectionFinished ) + { + kpPixmapFX::drawPolyline(destPixmap, + pointsTranslated, kpColor::Blue, 1/*pen width*/, kpColor::Yellow); + } + else + { + kpPixmapFX::drawPolygon(destPixmap, + pointsTranslated, + kpColor::Blue, 1/*pen width*/, + kpColor::Invalid/*no background*/, + true/*is final*/, + kpColor::Yellow); + + kpPixmapFX::drawRect(destPixmap, + boundingRect ().x () - docRect.x (), + boundingRect ().y () - docRect.y (), + boundingRect ().width (), + boundingRect ().height (), + kpColor::LightGray, 1/*pen width*/, + kpColor::Invalid/*no background*/, + kpColor::DarkGray); + } +} + +#include diff --git a/kolourpaint/layers/selections/kpAbstractSelection.h b/kolourpaint/layers/selections/kpAbstractSelection.h new file mode 100644 index 00000000..260dc600 --- /dev/null +++ b/kolourpaint/layers/selections/kpAbstractSelection.h @@ -0,0 +1,278 @@ + +// OPT: The selection classes should use copy-on-write. + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpAstractSelection_H +#define kpAstractSelection_H + + +#include + +#include +#include + + +class QImage; +class QPolygon; +class QPoint; +class QRect; +class QSize; + + +// +// Abstract base class for selections. +// +// Selections consist of: +// +// 1. Bounding rectangle (provided by this base class) relative to the +// source document +// 2. Border (must be on, or inside, the bounding rectangle and does not +// have to be rectangular) +// 3. Optional content (e.g. image or text). +// +// A selection without content is a selection border. +// +// Any content outside the border should not be rendered i.e. the selection +// is transparent in all areas outside of the border. Pixels on, or inside, +// the border are considered to be renderable content. Parts, or all, of +// this content can be transparent. +// +class kpAbstractSelection : public QObject +{ +Q_OBJECT + +// +// Initialization +// + +protected: + // (Call these in subclass constructors) + kpAbstractSelection (); + kpAbstractSelection (const QRect &rect); + + // (Call this in subclass implementations of operator=) + kpAbstractSelection &operator= (const kpAbstractSelection &rhs); + +public: + // To implement, create an instance of your type and then call your + // implementation of operator=(). + virtual kpAbstractSelection *clone () const = 0; + + virtual ~kpAbstractSelection (); + + +// +// Marshalling +// + +public: + // Returns a unique ID for this type of selection, use for marshalling. + virtual int serialID () const = 0; + + // This is called after your object has been created with the default + // constructor. + // + // Reads the object marshalled in the and returns whether it + // succeeded. This is called by kpSelectionFactory so the serialID() + // has already been read and removed from the . + // + // You must override this. Remember to call this base implementation + // before your code. + virtual bool readFromStream (QDataStream &stream); + + // Marshalls the object into the . This is called by + // operator<<() so the serialID() has already been written into the + // . + // + // You must override this. Remember to call this base implementation + // before your code. + virtual void writeToStream (QDataStream &stream) const; + + // Writes the serialID() of the to the and then + // calls writeToStream() to do the remaining marshalling. + // + // (kpSelectionFactory::FromStream() is the ">>" replacement) + friend QDataStream &operator<< (QDataStream &stream, + const kpAbstractSelection &selection); + + +// +// General Queries +// + +public: + // Returns e.g. i18n ("Selection") or i18n ("Text"). + virtual QString name () const = 0; + + // Returns the memory usage of the selection (like kpCommand's), + // _not_ its dimensions. + // + // You must override this and add the size returned by this implementation. + virtual kpCommandSize::SizeType size () const; + +public: + // e.g. return false for an elliptical selection. + virtual bool isRectangular () const = 0; + + +// +// Position & Dimensions +// + +public: + // Returns the minimum allowed dimensions of your selection type. + // Usually this is 1x1 pixels by pixels. + virtual int minimumWidth () const = 0; + virtual int minimumHeight () const = 0; + QSize minimumSize () const; + +public: + // (in document coordinates) + int x () const; + int y () const; + QPoint topLeft () const; + +public: + // (in document coordinates) + + // Returns the width of the bounding rectangle. + int width () const; + + // Returns the height of the bounding rectangle. + int height () const; + + // Returns the bounding rectangle. + QRect boundingRect () const; + +public: + // Use this to implement calculatePoints() for rectangular selections. + static QPolygon CalculatePointsForRectangle (const QRect &rect); + + // Returns the border. This may be recalculated for every call so + // may be slow. + virtual QPolygon calculatePoints () const = 0; + + +// +// Point Testing +// + +public: + // Returns whether the given is on or inside the -- possibly, + // non-rectangular -- border of the selection. + // + // (for non-rectangular selections, may return false even if + // kpView::onSelectionResizeHandle()) + virtual bool contains (const QPoint &point) const = 0; + bool contains (int x, int y) const; + + +// +// Content +// + +public: + // i.e. Has an image or text - not just a border. + virtual bool hasContent () const = 0; + + // Deletes the content, changing the selection back into a border. + // If the selection has no content, it does nothing. + virtual void deleteContent () = 0; + + +// +// Mutation - Movement +// + +public: + // (You only need to override this if you store your own border + // coordinates) + virtual void moveBy (int dx, int dy); + + // (These call moveBy() so if you only reimplement moveBy(), that should + // be sufficient) + void moveTo (int dx, int dy); + void moveTo (const QPoint &topLeftPoint); + + +// +// Rendering +// + +public: + // Renders the selection on top of <*destPixmap>. This does not render + // the border. + // + // is the document rectangle that <*destPixmap> represents. + // + // You need to clip to boundingRect() or if you are a non-rectangular + // selection, an even smaller region, + // + // However, there is no need to do any explicit clipping to , + // since any drawing outside the bounds of is discarded. + // However, you may choose to clip for whatever reason e.g. performance. + virtual void paint (QImage *destPixmap, const QRect &docRect) const = 0; + + +protected: + // Use this to implement paintBorder() for rectangular selections. + void paintRectangularBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const; + + // Use this to implement paintBorder() for non-rectangular selections + // (this calls calculatePoints()). + // + // If , this also draws a bounding rectangular box. + void paintPolygonalBorder (const QPolygon &points, + QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const; + +public: + // Renders the selection border on top of <*destPixmap>. + // + // is the same as for paint(). + // + // If is false, the user is still dragging out the + // selection border so it may be drawn differently. + virtual void paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const = 0; + + +signals: + // Signals that a view update is required in the document region , + // due to the selection changing. + void changed (const QRect &docRect); + + +private: + struct kpAbstractSelectionPrivate * const d; +}; + + +#endif // kpAstractSelection_H diff --git a/kolourpaint/layers/selections/kpSelectionDrag.cpp b/kolourpaint/layers/selections/kpSelectionDrag.cpp new file mode 100644 index 00000000..89ef8ce5 --- /dev/null +++ b/kolourpaint/layers/selections/kpSelectionDrag.cpp @@ -0,0 +1,154 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION_DRAG 0 + + +#include + +#include +#include + +#include + +#include +#include +#include + +//--------------------------------------------------------------------- + +// public static +const char * const kpSelectionDrag::SelectionMimeType = + "application/x-kolourpaint-selection-400"; + +//--------------------------------------------------------------------- + +kpSelectionDrag::kpSelectionDrag (const kpAbstractImageSelection &sel) +{ +#if DEBUG_KP_SELECTION_DRAG && 1 + kDebug () << "kpSelectionDrag() w=" << sel.width () + << " h=" << sel.height () + << endl; +#endif + + Q_ASSERT (sel.hasContent ()); + + // Store as selection. + QByteArray ba; + { + QDataStream stream (&ba, QIODevice::WriteOnly); + stream << sel; + } + setData (kpSelectionDrag::SelectionMimeType, ba); + + // Store as image (so that QMimeData::hasImage()) works). + // OPT: an awful waste of memory storing image in both selection and QImage + const QImage image = sel.baseImage (); +#if DEBUG_KP_SELECTION_DRAG && 1 + kDebug () << "\timage: w=" << image.width () + << " h=" << image.height () + << endl; +#endif + if (image.isNull ()) + { + // TODO: proper error handling. + kError () << "kpSelectionDrag::setSelection() could not convert to image" + << endl; + } + else + setImageData (image); +} + +//--------------------------------------------------------------------- +// public static + +bool kpSelectionDrag::canDecode(const QMimeData *mimeData) +{ + Q_ASSERT(mimeData); + +#if DEBUG_KP_SELECTION_DRAG + kDebug() << "kpSelectionDrag::canDecode()" + << "hasSel=" << mimeData->hasFormat(kpSelectionDrag::SelectionMimeType) + << "hasImage=" << mimeData->hasImage(); +#endif + + // mimeData->hasImage() would not check if the data is a valid image + return mimeData->hasFormat(kpSelectionDrag::SelectionMimeType) || + !qvariant_cast(mimeData->imageData()).isNull(); +} + +//--------------------------------------------------------------------- +// public static + +kpAbstractImageSelection *kpSelectionDrag::decode(const QMimeData *mimeData) +{ +#if DEBUG_KP_SELECTION_DRAG + kDebug () << "kpSelectionDrag::decode(kpAbstractSelection)"; +#endif + Q_ASSERT (mimeData); + + if (mimeData->hasFormat (kpSelectionDrag::SelectionMimeType)) + { + #if DEBUG_KP_SELECTION_DRAG + kDebug () << "\tmimeSource hasFormat selection - just return it in QByteArray"; + #endif + QByteArray data = mimeData->data (kpSelectionDrag::SelectionMimeType); + QDataStream stream (&data, QIODevice::ReadOnly); + + return kpSelectionFactory::FromStream (stream); + } + else + { + #if DEBUG_KP_SELECTION_DRAG + kDebug () << "\tmimeSource doesn't provide selection - try image"; + #endif + + QImage image = qvariant_cast (mimeData->imageData ()); + if (!image.isNull ()) + { + #if DEBUG_KP_SELECTION_DRAG + kDebug () << "\tok w=" << image.width () << " h=" << image.height (); + #endif + + return new kpRectangularImageSelection ( + QRect (0, 0, image.width (), image.height ()), image); + } + else + { + #if DEBUG_KP_SELECTION_DRAG + kDebug () << "kpSelectionDrag::decode(kpAbstractSelection) mimeSource had no sel " + "and could not decode to image" << endl; + #endif + return 0; + } + } +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/layers/selections/kpSelectionDrag.h b/kolourpaint/layers/selections/kpSelectionDrag.h new file mode 100644 index 00000000..1da2d3f2 --- /dev/null +++ b/kolourpaint/layers/selections/kpSelectionDrag.h @@ -0,0 +1,52 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_SELECTION_DRAG_H +#define KP_SELECTION_DRAG_H + +#include + +class kpAbstractImageSelection; + + +class kpSelectionDrag : public QMimeData +{ + Q_OBJECT + + public: + static const char * const SelectionMimeType; + + // ASSUMPTION: has content (is not just a border). + kpSelectionDrag(const kpAbstractImageSelection &sel); + + public: + static bool canDecode(const QMimeData *mimeData); + static kpAbstractImageSelection *decode(const QMimeData *mimeData); +}; + + +#endif // KP_SELECTION_DRAG_H diff --git a/kolourpaint/layers/selections/kpSelectionFactory.cpp b/kolourpaint/layers/selections/kpSelectionFactory.cpp new file mode 100644 index 00000000..aa4a9de0 --- /dev/null +++ b/kolourpaint/layers/selections/kpSelectionFactory.cpp @@ -0,0 +1,93 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include + +#include + +#include +#include +#include + +//--------------------------------------------------------------------- + +// public static +// TODO: KolourPaint has not been tested against invalid or malicious +// clipboard data [Bug #28]. +kpAbstractImageSelection *kpSelectionFactory::FromStream (QDataStream &stream) +{ +#if DEBUG_KP_SELECTION && 1 + kDebug () << "kpSelectionFactory::FromStream()"; +#endif + int serialID; + stream >> serialID; + +#if DEBUG_KP_SELECTION && 1 + kDebug () << "\tserialID=" << serialID; +#endif + + // Only image selections are marshalled. + // + // Text selections are only ever seen in the clipboard as ordinary text, + // not selections, since copying text formatting over the clipboard doesn't + // seem compelling. + kpAbstractImageSelection *imageSel = 0; + switch (serialID) + { + case kpRectangularImageSelection::SerialID: + imageSel = new kpRectangularImageSelection (); + break; + + case kpEllipticalImageSelection::SerialID: + imageSel = new kpEllipticalImageSelection (); + break; + + case kpFreeFormImageSelection::SerialID: + imageSel = new kpFreeFormImageSelection (); + break; + } + + // Unknown selection type? + if (imageSel == 0) + { + return 0; + } + + if (!imageSel->readFromStream (stream)) + { + delete imageSel; + return 0; + } + + return imageSel; +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/layers/selections/kpSelectionFactory.h b/kolourpaint/layers/selections/kpSelectionFactory.h new file mode 100644 index 00000000..79c00232 --- /dev/null +++ b/kolourpaint/layers/selections/kpSelectionFactory.h @@ -0,0 +1,48 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpSelectionFactory_H +#define kpSelectionFactory_H + + +#include + + +class QDataStream; + +class kpAbstractImageSelection; + + +class kpSelectionFactory +{ +public: + static kpAbstractImageSelection *FromStream (QDataStream &stream); +}; + + +#endif // kpSelectionFactory_H diff --git a/kolourpaint/layers/selections/text/kpPreeditText.cpp b/kolourpaint/layers/selections/text/kpPreeditText.cpp new file mode 100644 index 00000000..4f6ae5bd --- /dev/null +++ b/kolourpaint/layers/selections/text/kpPreeditText.cpp @@ -0,0 +1,142 @@ + +/* + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +//--------------------------------------------------------------------- + +bool attributeLessThan (const QInputMethodEvent::Attribute &a1, const QInputMethodEvent::Attribute &a2) +{ + return a1.start < a2.start; +} + +//--------------------------------------------------------------------- + +kpPreeditText::kpPreeditText () + : m_cursorPosition (0), m_cursorColor (Qt::transparent), + m_selectionStart (0), m_selectionLength (0), + m_position (-1, -1) +{ +} + +//--------------------------------------------------------------------- + +kpPreeditText::kpPreeditText (const QInputMethodEvent *event) + : m_cursorPosition (0), m_cursorColor (Qt::transparent), + m_selectionStart (0), m_selectionLength (0), + m_position (-1, -1) +{ + m_preeditString = event->preeditString (); + foreach (const QInputMethodEvent::Attribute &attr, event->attributes ()) + { + switch (attr.type) + { + case QInputMethodEvent::TextFormat: + m_textFormatList.append (attr); + break; + case QInputMethodEvent::Cursor: + m_cursorPosition = attr.start; + if (attr.length > 0) + { + m_cursorColor = attr.value.value (); + } + break; + case QInputMethodEvent::Selection: + m_selectionStart = attr.start; + m_selectionLength = attr.length; + break; + default: + break; + } + } + qSort (m_textFormatList.begin (), m_textFormatList.end (), attributeLessThan); +} + +//--------------------------------------------------------------------- + +bool kpPreeditText::isEmpty () const +{ + return m_preeditString.isEmpty (); +} + +//--------------------------------------------------------------------- + +const QString &kpPreeditText::preeditString () const +{ + return m_preeditString; +} + +//--------------------------------------------------------------------- + +int kpPreeditText::cursorPosition () const +{ + return m_cursorPosition; +} + +//--------------------------------------------------------------------- + +const QColor &kpPreeditText::cursorColor () const +{ + return m_cursorColor; +} + +//--------------------------------------------------------------------- + +int kpPreeditText::selectionStart () const +{ + return m_selectionStart; +} + +//--------------------------------------------------------------------- + +int kpPreeditText::selectionLength () const +{ + return m_selectionLength; +} + +//--------------------------------------------------------------------- + +const QList &kpPreeditText::textFormatList () const +{ + return m_textFormatList; +} + +//--------------------------------------------------------------------- + +const QPoint &kpPreeditText::position () const +{ + return m_position; +} + +//--------------------------------------------------------------------- + +void kpPreeditText::setPosition (const QPoint &position) +{ + m_position = position; +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/layers/selections/text/kpPreeditText.h b/kolourpaint/layers/selections/text/kpPreeditText.h new file mode 100644 index 00000000..2870a19b --- /dev/null +++ b/kolourpaint/layers/selections/text/kpPreeditText.h @@ -0,0 +1,65 @@ + +/* + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpPreeditText_H +#define kpPreeditText_H + +#include +#include +#include + +class kpPreeditText +{ +public: + kpPreeditText (); + kpPreeditText (const QInputMethodEvent *event); + + bool isEmpty () const; + + const QString &preeditString () const; + int cursorPosition () const; + bool cursorVisible () const; + const QColor &cursorColor () const; + int selectionStart () const; + int selectionLength () const; + const QList &textFormatList () const; + + const QPoint &position () const; + void setPosition (const QPoint &position); + +private: + QString m_preeditString; + int m_cursorPosition; + QColor m_cursorColor; + int m_selectionStart; + int m_selectionLength; + QList m_textFormatList; + QPoint m_position; +}; + +#endif // kpPreeditText_H diff --git a/kolourpaint/layers/selections/text/kpTextSelection.cpp b/kolourpaint/layers/selections/text/kpTextSelection.cpp new file mode 100644 index 00000000..fcaf6a8f --- /dev/null +++ b/kolourpaint/layers/selections/text/kpTextSelection.cpp @@ -0,0 +1,346 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include +#include + +#include +#include + +#include + +#include +#include + + +// public +kpTextSelection::kpTextSelection (const QRect &rect, + const kpTextStyle &textStyle) + : kpAbstractSelection (rect), + d (new kpTextSelectionPrivate ()) +{ + d->textStyle = textStyle; +} + +// public +kpTextSelection::kpTextSelection (const QRect &rect, + const QList &textLines, + const kpTextStyle &textStyle) + : kpAbstractSelection (rect), + d (new kpTextSelectionPrivate ()) +{ + d->textLines = textLines; + d->textStyle = textStyle; +} + +// public +kpTextSelection::kpTextSelection (const kpTextSelection &rhs) + : kpAbstractSelection (), + d (new kpTextSelectionPrivate ()) +{ + *this = rhs; +} + +// public +kpTextSelection &kpTextSelection::operator= (const kpTextSelection &rhs) +{ + kpAbstractSelection::operator= (rhs); + + d->textLines = rhs.d->textLines; + d->textStyle = rhs.d->textStyle; + d->preeditText = rhs.d->preeditText; + + return *this; +} + +// public virtual [base kpAbstractSelection] +kpTextSelection *kpTextSelection::clone () const +{ + kpTextSelection *sel = new kpTextSelection (); + *sel = *this; + return sel; +} + +// public +kpTextSelection *kpTextSelection::resized (int newWidth, int newHeight) const +{ + return new kpTextSelection (QRect (x (), y (), newWidth, newHeight), + d->textLines, + d->textStyle); +} + +// public +kpTextSelection::~kpTextSelection () +{ + delete d; +} + + +// public virtual [kpAbstractSelection] +int kpTextSelection::serialID () const +{ + Q_ASSERT (!"Marshalling not supported"); + return -1; +} + +// public virtual [base kpAbstractSelection] +bool kpTextSelection::readFromStream (QDataStream &stream) +{ + (void) stream; + + Q_ASSERT (!"Marshalling not supported"); + return false; +} + +// public virtual [base kpAbstractSelection] +void kpTextSelection::writeToStream (QDataStream &stream) const +{ + (void) stream; + + Q_ASSERT (!"Marshalling not supported"); +} + + +// public virtual [kpAbstractSelection] +QString kpTextSelection::name () const +{ + return i18n ("Text"); +} + + +// public virtual [base kpAbstractSelection] +kpCommandSize::SizeType kpTextSelection::size () const +{ + return kpAbstractSelection::size () + + kpCommandSize::StringSize (text ()); +} + + +// public virtual [kpAbstractSelection] +bool kpTextSelection::isRectangular () const +{ + return true; +} + + +// public static +int kpTextSelection::MinimumWidthForTextStyle (const kpTextStyle &) +{ + return (kpTextSelection::TextBorderSize () * 2 + 5); +} + +// public static +int kpTextSelection::MinimumHeightForTextStyle (const kpTextStyle &) +{ + return (kpTextSelection::TextBorderSize () * 2 + 5); +} + +// public static +QSize kpTextSelection::MinimumSizeForTextStyle (const kpTextStyle &textStyle) +{ + return QSize (kpTextSelection::MinimumWidthForTextStyle (textStyle), + kpTextSelection::MinimumHeightForTextStyle (textStyle)); +} + + +// public virtual [kpAbstractSelection] +int kpTextSelection::minimumWidth () const +{ + return kpTextSelection::MinimumWidthForTextStyle (textStyle ()); +} + +// public virtual [kpAbstractSelection] +int kpTextSelection::minimumHeight () const +{ + return kpTextSelection::MinimumHeightForTextStyle (textStyle ()); +} + + +// public static +int kpTextSelection::PreferredMinimumWidthForTextStyle (const kpTextStyle &textStyle) +{ + const int about15CharsWidth = + textStyle.fontMetrics ().width ( + QLatin1String ("1234567890abcde")); + + const int preferredMinWidth = + qMax (150, + kpTextSelection::TextBorderSize () * 2 + about15CharsWidth); + + return qMax (kpTextSelection::MinimumWidthForTextStyle (textStyle), + qMin (250, preferredMinWidth)); +} + +// public static +int kpTextSelection::PreferredMinimumHeightForTextStyle (const kpTextStyle &textStyle) +{ + const int preferredMinHeight = + kpTextSelection::TextBorderSize () * 2 + textStyle.fontMetrics ().height (); + + return qMax (kpTextSelection::MinimumHeightForTextStyle (textStyle), + qMin (150, preferredMinHeight)); +} + +// public static +QSize kpTextSelection::PreferredMinimumSizeForTextStyle (const kpTextStyle &textStyle) +{ + return QSize (kpTextSelection::PreferredMinimumWidthForTextStyle (textStyle), + kpTextSelection::PreferredMinimumHeightForTextStyle (textStyle)); +} + + +// public static +int kpTextSelection::TextBorderSize () +{ + return 1; +} + +// public +QRect kpTextSelection::textAreaRect () const +{ + return QRect (x () + kpTextSelection::TextBorderSize (), + y () + kpTextSelection::TextBorderSize (), + width () - kpTextSelection::TextBorderSize () * 2, + height () - kpTextSelection::TextBorderSize () * 2); +} + + +// public virtual [kpAbstractSelection] +QPolygon kpTextSelection::calculatePoints () const +{ + return kpAbstractSelection::CalculatePointsForRectangle (boundingRect ()); +} + + +// public virtual [kpAbstractSelection] +bool kpTextSelection::contains (const QPoint &point) const +{ + return boundingRect ().contains (point); +} + + +// public +bool kpTextSelection::pointIsInTextBorderArea (const QPoint &point) const +{ + return (boundingRect ().contains (point) && !pointIsInTextArea (point)); +} + +// public +bool kpTextSelection::pointIsInTextArea (const QPoint &point) const +{ + return textAreaRect ().contains (point); +} + + +// public virtual [kpAbstractSelection] +bool kpTextSelection::hasContent () const +{ + return !d->textLines.isEmpty (); +} + +// public virtual [kpAbstractSelection] +void kpTextSelection::deleteContent () +{ + if (!hasContent ()) + return; + + setTextLines (QList ()); +} + + +// public +QList kpTextSelection::textLines () const +{ + return d->textLines; +} + +// public +void kpTextSelection::setTextLines (const QList &textLines_) +{ + d->textLines = textLines_; + + emit changed (boundingRect ()); +} + + +// public static +QString kpTextSelection::TextForTextLines (const QList &textLines) +{ + if (textLines.isEmpty ()) + return QString (); + + QString bigString = textLines [0]; + + for (QList ::const_iterator it = textLines.begin () + 1; + it != textLines.end (); + ++it) + { + bigString += QLatin1String ("\n"); + bigString += (*it); + } + + return bigString; +} + +// public +QString kpTextSelection::text () const +{ + return kpTextSelection::TextForTextLines (d->textLines); +} + + +// public +kpTextStyle kpTextSelection::textStyle () const +{ + return d->textStyle; +} + +// public +void kpTextSelection::setTextStyle (const kpTextStyle &textStyle) +{ + d->textStyle = textStyle; + + emit changed (boundingRect ()); +} + +kpPreeditText kpTextSelection::preeditText () const +{ + return d->preeditText; +} + +void kpTextSelection::setPreeditText (const kpPreeditText &preeditText) +{ + d->preeditText = preeditText; + emit changed (boundingRect ()); +} + +#include diff --git a/kolourpaint/layers/selections/text/kpTextSelection.h b/kolourpaint/layers/selections/text/kpTextSelection.h new file mode 100644 index 00000000..49b61494 --- /dev/null +++ b/kolourpaint/layers/selections/text/kpTextSelection.h @@ -0,0 +1,294 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTextSelection_H +#define kpTextSelection_H + + +#include +#include +#include +#include + +// +// A rectangular text box containing lines of text, rendered in a given text +// style. +// +// A text selection with an empty list of text lines is just a text box +// border and contains no content. +// +// The minimal text selection that is considered to contain content consists +// of a single empty text line. +// +// The rendered elements are as follows: +// +// ############### +// # # +// # * *---------- Text Border +// # | # +// #####|######### +// | +// | +// Text Area +// (text lines are rendered on top of here; the parts of the lines +// that don't fit here are not rendered) +// +// The text style determines how the text box is drawn. +// +// The foreground color determines the color of the text. Transparent +// foreground text means that the text box is see-through for the pixels +// of the text, exposing the document pixels below. It does not mean +// that the transparent color is drawn onto the document. In other +// words, we are "painting" transparent pixels -- not "setting" them. +// +// If the background color is opaque, the text is drawn on top of a +// filled-in rectangle and the rectangle completely overwrites any +// document pixels below. +// +// Consistent with the behavior of a transparent foreground color, a +// transparent background color does not mean that a transparent-colored +// rectangle is drawn onto the document. Instead, it means that the +// text box background is see-through so that text is drawn directly on +// top of the document pixels. No rectangle is drawn in this case. +// +// A text box with transparent foreground and background colors is +// completely invisible since both the text and background are see-through. +// +// A rendered text cursor is controlled separately by kpViewManager, as +// cursors are only a view concept so do not belong in this document-based +// class. +// +// Marshalling, for copying text selections to the clipboard, is not +// currently supported. This is because generally, users are only interested +// in the text itself, not the border nor formatting. +// +class kpTextSelection : public kpAbstractSelection +{ +Q_OBJECT + +// +// Initialization +// + +public: + kpTextSelection (const QRect &rect = QRect (), + const kpTextStyle &textStyle = kpTextStyle ()); + kpTextSelection (const QRect &rect, const QList &textLines, + const kpTextStyle &textStyle); + kpTextSelection (const kpTextSelection &rhs); + + kpTextSelection &operator= (const kpTextSelection &rhs); + + virtual kpTextSelection *clone () const; + + // Returns a copy of the text selection but with new dimensions + // x . + kpTextSelection *resized (int newWidth, int newHeight) const; + + virtual ~kpTextSelection (); + + +// +// Marshalling +// + +public: + virtual int serialID () const; + + virtual bool readFromStream (QDataStream &stream); + + virtual void writeToStream (QDataStream &stream) const; + + +// +// General Queries +// + +public: + virtual QString name () const; + + virtual kpCommandSize::SizeType size () const; + +public: + virtual bool isRectangular () const; + + +// +// Position & Dimensions +// + +public: + // Returns the absolute minimum size that a textbox must be if it is of + // the given . + // + // This leaves enough room for the border on all 4 sides and also a + // text area big enough to fit a character in an extremely small font. + static int MinimumWidthForTextStyle (const kpTextStyle &textStyle); + static int MinimumHeightForTextStyle (const kpTextStyle &textStyle); + static QSize MinimumSizeForTextStyle (const kpTextStyle &textStyle); + + // REFACTOR: Enforce in kpTextSelection, not just in kpToolSelection & + // when pasting (in kpMainWindow). + // + // Otherwise, if enforcement fails, e.g. textAreaRect() will + // not work. + virtual int minimumWidth () const; + virtual int minimumHeight () const; + +public: + // Returns the suggested minimum size that a textbox should be if it is of + // the given . + // + // This leaves enough room for the border on all 4 sides and also for + // a small line of the text in the given text style. + static int PreferredMinimumWidthForTextStyle (const kpTextStyle &textStyle); + static int PreferredMinimumHeightForTextStyle (const kpTextStyle &textStyle); + static QSize PreferredMinimumSizeForTextStyle (const kpTextStyle &textStyle); + +public: + // Returns the size of the text border. Constant. + static int TextBorderSize (); + + // Returns the rectangle that text lines are drawn on top of. + // This will be a sub-rectangle of boundingRect() and is therefore, + // in document coordinates like everything else in this class. + QRect textAreaRect () const; + +public: + virtual QPolygon calculatePoints () const; + + +// +// Point Testing +// + +public: + virtual bool contains (const QPoint &point) const; + +public: + bool pointIsInTextBorderArea (const QPoint &point) const; + bool pointIsInTextArea (const QPoint &point) const; + + +// +// Content +// + +public: + // (see class header comment) + virtual bool hasContent () const; + + virtual void deleteContent (); + +public: + QList textLines () const; + void setTextLines (const QList &textLines); + + static QString TextForTextLines (const QList &textLines); + // Returns textLines() as one long newline-separated string. + // If the last text line is not empty, there is no trailing newline. + QString text () const; + + +// +// Text Style +// + +public: + kpTextStyle textStyle () const; + void setTextStyle (const kpTextStyle &textStyle); + + +// +// Preedit Text +// + +public: + kpPreeditText preeditText () const; + void setPreeditText (const kpPreeditText &preeditText); + +// +// Cursor +// +// A text cursor position is the row and column of a character in +// textLines(), that it is to the left of. As a result, a column value +// of 1 character past the last character of a text line is allowed. +// + +public: + // If the given point is in the text area, it returns the closest + // row/column (in textLines()) for the point. + // + // If the given point is not in the text area, it returns -1. + int closestTextRowForPoint (const QPoint &point) const; + int closestTextColForPoint (const QPoint &point) const; + + // Given a valid row and column in textLines(), returns the top-left + // point of where the text cursor should be rendered. + // TODO: Code is not symmetric to closestTest{Row,Col}ForPoint() + // [look at the Y/row value calculations] + // + // If the row and column is not inside textLines(), it returns + // KP_INVALID_POINT. + QPoint pointForTextRowCol (int row, int col) const; + + +// +// Rendering +// + +private: + void drawPreeditString(QPainter &painter, int &x, int y, const kpPreeditText &preeditText) const; + +public: + virtual void paint(QImage *destPixmap, const QRect &docRect) const; + + virtual void paintBorder(QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const; + +public: + // Returns an image that contains the painted text (without a border). + // + // If the text box has a see-through background, the image will be given + // an arbitrarily neutral background (currently, the transparent color). + // As a result, the returned image will be an approximation since text + // boxes are normally rendered -- and antialiased with -- a different + // background, namely the document image. Therefore, it is invalid to + // stamp the returned image onto the document image and expect it to look + // like stamping this text selection onto the document image (the latter + // is achieved via kpDocument::selectionPushOntoDocument(), antialiases + // and is more correct). + kpImage approximateImage () const; + + +private: + struct kpTextSelectionPrivate * const d; +}; + + +#endif // kpTextSelection_H diff --git a/kolourpaint/layers/selections/text/kpTextSelectionPrivate.h b/kolourpaint/layers/selections/text/kpTextSelectionPrivate.h new file mode 100644 index 00000000..47392252 --- /dev/null +++ b/kolourpaint/layers/selections/text/kpTextSelectionPrivate.h @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTextSelectionPrivate_H +#define kpTextSelectionPrivate_H + + +#include + +#include +#include +#include + +struct kpTextSelectionPrivate +{ + QList textLines; + kpTextStyle textStyle; + kpPreeditText preeditText; +}; + + +#endif // kpTextSelectionPrivate_H diff --git a/kolourpaint/layers/selections/text/kpTextSelection_Cursor.cpp b/kolourpaint/layers/selections/text/kpTextSelection_Cursor.cpp new file mode 100644 index 00000000..0f327261 --- /dev/null +++ b/kolourpaint/layers/selections/text/kpTextSelection_Cursor.cpp @@ -0,0 +1,126 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include +#include + +#include +#include + +#include + +#include +#include +#include + + +// public +int kpTextSelection::closestTextRowForPoint (const QPoint &point) const +{ + if (!pointIsInTextArea (point)) + return -1; + + const QFontMetrics fontMetrics (d->textStyle.fontMetrics ()); + + int row = (point.y () - textAreaRect ().y ()) / + fontMetrics.lineSpacing (); + if (row >= (int) d->textLines.size ()) + row = d->textLines.size () - 1; + + return row; +} + +// public +int kpTextSelection::closestTextColForPoint (const QPoint &point) const +{ + int row = closestTextRowForPoint (point); + if (row < 0 || row >= (int) d->textLines.size ()) + return -1; + + const int localX = point.x () - textAreaRect ().x (); + + const QFontMetrics fontMetrics (d->textStyle.fontMetrics ()); + + // (should be 0 but call just in case) + int charLocalLeft = fontMetrics.width (d->textLines [row], 0); + + // OPT: binary search or guess location then move + for (int col = 0; col < (int) d->textLines [row].length (); col++) + { + // OPT: fontMetrics::charWidth() might be faster + const int nextCharLocalLeft = fontMetrics.width (d->textLines [row], col + 1); + if (localX <= (charLocalLeft + nextCharLocalLeft) / 2) + return col; + + charLocalLeft = nextCharLocalLeft; + } + + return d->textLines [row].length ()/*past end of line*/; +} + +//--------------------------------------------------------------------- + +// public +QPoint kpTextSelection::pointForTextRowCol (int row, int col) const +{ + kpPreeditText preeditText = d->preeditText; + if ((row < 0 || col < 0) || + (preeditText.isEmpty () && + (row >= (int) d->textLines.size () || col > (int) d->textLines [row].length ()))) + { +#if DEBUG_KP_SELECTION && 1 + kDebug () << "kpTextSelection::pointForTextRowCol(" + << row << "," + << col << ") out of range" + << " textLines='" + << text () + << "'" + << endl; +#endif + return KP_INVALID_POINT; + } + + const QFontMetrics fontMetrics (d->textStyle.fontMetrics ()); + + QString line = (d->textLines.count () > row) ? d->textLines[row] : QString (); + if (row == preeditText.position ().y ()) + { + line.insert (preeditText.position ().x (), preeditText.preeditString ()); + } + const int x = fontMetrics.width (line.left (col)); + const int y = row * fontMetrics.height () + + (row >= 1 ? row * fontMetrics.leading () : 0); + + return textAreaRect ().topLeft () + QPoint (x, y); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/layers/selections/text/kpTextSelection_Paint.cpp b/kolourpaint/layers/selections/text/kpTextSelection_Paint.cpp new file mode 100644 index 00000000..94a8ec9c --- /dev/null +++ b/kolourpaint/layers/selections/text/kpTextSelection_Paint.cpp @@ -0,0 +1,269 @@ + +// REFACTOR: Move into kpPainter + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +//--------------------------------------------------------------------- + +void kpTextSelection::drawPreeditString(QPainter &painter, int &x, int y, const kpPreeditText &preeditText) const +{ + int i = 0; + QString preeditString = preeditText.preeditString (); + QString str; + foreach (const QInputMethodEvent::Attribute &attr, preeditText.textFormatList ()) + { + int start = attr.start; + int length = attr.length; + QTextCharFormat format = qvariant_cast (attr.value).toCharFormat (); + + if (i > start) + { + length = length - i + start; + start = i; + } + if (length <= 0) continue; + + if (i < start) + { + str = preeditString.mid (i, start - i); + painter.drawText (x, y, str); + x += painter.fontMetrics ().width (str); + } + + painter.save(); + str = preeditString.mid (start, length); + int width = painter.fontMetrics().width (str); + if (format.background ().color () != Qt::black) + { + painter.save (); + painter.setPen (format.background ().color ()); + painter.setBrush (format.background()); + painter.drawRect (x, y - painter.fontMetrics ().ascent (), width, painter.fontMetrics ().height ()); + painter.restore (); + } + if (format.foreground ().color () != Qt::black) + { + painter.setBrush (format.foreground ()); + painter.setPen (format.foreground ().color ()); + } + if (format.underlineStyle ()) + { + painter.drawLine (x, y + painter.fontMetrics ().descent (), x + width, y + painter.fontMetrics ().descent ()); + } + painter.drawText (x, y, str); + + x += width; + painter.restore (); + + i = start + length; + } + if (i < preeditString.length ()) + { + str = preeditString.mid (i); + painter.drawText (x, y, str); + x += painter.fontMetrics ().width (str); + } +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +void kpTextSelection::paint(QImage *destPixmap, const QRect &docRect) const +{ +#if DEBUG_KP_SELECTION + kDebug () << "kpTextSelection::paint() textStyle: fcol=" + << (int *) d->textStyle.foregroundColor ().toQRgb () + << " bcol=" + << (int *) d->textStyle.backgroundColor ().toQRgb () + << endl; +#endif + + // Drawing text is slow so if the text box will be rendered completely + // outside of , don't bother rendering it at all. + const QRect modifyingRect = docRect.intersect (boundingRect ()); + if (modifyingRect.isEmpty ()) + return; + + + // Is the text box completely invisible? + if (textStyle ().foregroundColor ().isTransparent () && + textStyle ().backgroundColor ().isTransparent ()) + { + return; + } + + kpImage floatImage(modifyingRect.size(), QImage::Format_ARGB32_Premultiplied); + floatImage.fill(0); + + QRect theWholeAreaRect, theTextAreaRect; + theWholeAreaRect = boundingRect ().translated (-modifyingRect.topLeft ()); + theTextAreaRect = textAreaRect ().translated (-modifyingRect.topLeft ()); + + QList theTextLines = textLines(); + kpTextStyle theTextStyle = textStyle(); + + const QFontMetrics fontMetrics (theTextStyle.font ()); + +#if DEBUG_KP_SELECTION + kDebug () << "kpTextSelection_Paint.cpp:DrawTextHelper"; + kDebug () << "\theight=" << fontMetrics.height () + << " leading=" << fontMetrics.leading () + << " ascent=" << fontMetrics.ascent () + << " descent=" << fontMetrics.descent () + << " lineSpacing=" << fontMetrics.lineSpacing () + << endl; +#endif + + QPainter painter(&floatImage); + + // Fill in the background using the transparent/opaque tool setting + if ( theTextStyle.isBackgroundTransparent() ) + painter.fillRect(theWholeAreaRect, Qt::transparent); + else + painter.fillRect(theWholeAreaRect, theTextStyle.backgroundColor().toQColor()); + + painter.setClipRect(theWholeAreaRect); + painter.setPen(theTextStyle.foregroundColor().toQColor()); + painter.setFont(theTextStyle.font()); + + if ( theTextStyle.foregroundColor().toQColor().alpha() < 255 ) + { + // if the foreground color has an alpha channel, we want to + // see through the background, so we first need to punch holes + // into the background where the text is + painter.setCompositionMode(QPainter::CompositionMode_Clear); + + int baseLine = theTextAreaRect.y () + fontMetrics.ascent (); + foreach (const QString &str, theTextLines) + { + painter.drawText (theTextAreaRect.x (), baseLine, str); + baseLine += fontMetrics.lineSpacing (); + + // if the next textline would already be below the visible text area, stop drawing + if ( (baseLine - fontMetrics.ascent()) > (theTextAreaRect.y() + theTextAreaRect.height()) ) + break; + } + // the next text drawing will now blend the text foreground color with + // what is really below the text background + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + } + + // Draw a line at a time instead of using QPainter::drawText(QRect,...). + // Else, the line heights become >QFontMetrics::height() if you type Chinese + // characters (!) and then the cursor gets out of sync. + int baseLine = theTextAreaRect.y () + fontMetrics.ascent (); + + kpPreeditText thePreeditText = preeditText(); + + if ( theTextLines.isEmpty() ) + { + if ( ! thePreeditText.isEmpty() ) + { + int x = theTextAreaRect.x(); + drawPreeditString(painter, x, baseLine, thePreeditText); + } + } + else + { + int i = 0; + int row = thePreeditText.position().y(); + int col = thePreeditText.position().x(); + foreach (const QString &str, theTextLines) + { + if (row == i && !thePreeditText.isEmpty()) + { + QString left = str.left(col); + QString right = str.mid(col); + int x = theTextAreaRect.x(); + painter.drawText(x, baseLine, left); + x += fontMetrics.width(left); + drawPreeditString(painter, x, baseLine, thePreeditText); + + painter.drawText(x, baseLine, right); + } + else + { + painter.drawText(theTextAreaRect.x (), baseLine, str); + } + baseLine += fontMetrics.lineSpacing(); + i++; + + // if the next textline would already be below the visible text area, stop drawing + if ( (baseLine - fontMetrics.ascent()) > (theTextAreaRect.y() + theTextAreaRect.height()) ) + break; + } + } + + // ... convert that into "painting" transparent pixels on top of + // the document. + kpPixmapFX::paintPixmapAt (destPixmap, + modifyingRect.topLeft () - docRect.topLeft (), + floatImage); +} + +//--------------------------------------------------------------------- + + +// public virtual [kpAbstractSelection] +void kpTextSelection::paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const +{ + paintRectangularBorder (destPixmap, docRect, selectionFinished); +} + +//--------------------------------------------------------------------- + +// public +kpImage kpTextSelection::approximateImage () const +{ + kpImage retImage (width (), height (), QImage::Format_ARGB32_Premultiplied); + retImage.fill(0); + paint (&retImage, boundingRect ()); + return retImage; +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/layers/selections/text/kpTextStyle.cpp b/kolourpaint/layers/selections/text/kpTextStyle.cpp new file mode 100644 index 00000000..ce0b0ec0 --- /dev/null +++ b/kolourpaint/layers/selections/text/kpTextStyle.cpp @@ -0,0 +1,269 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include +#include + + +kpTextStyle::kpTextStyle () + : m_fontSize (0), + m_isBold (false), m_isItalic (false), + m_isUnderline (false), m_isStrikeThru (false), + m_isBackgroundOpaque (true) +{ +} + +kpTextStyle::kpTextStyle (const QString &fontFamily, + int fontSize, + bool isBold, bool isItalic, + bool isUnderline, bool isStrikeThru, + const kpColor &fcolor, const kpColor &bcolor, + bool isBackgroundOpaque) + : m_fontFamily (fontFamily), + m_fontSize (fontSize), + m_isBold (isBold), m_isItalic (isItalic), + m_isUnderline (isUnderline), m_isStrikeThru (isStrikeThru), + m_foregroundColor (fcolor), m_backgroundColor (bcolor), + m_isBackgroundOpaque (isBackgroundOpaque) +{ +} + +kpTextStyle::~kpTextStyle () +{ +} + + +// friend +QDataStream &operator<< (QDataStream &stream, const kpTextStyle &textStyle) +{ + stream << textStyle.m_fontFamily; + stream << textStyle.m_fontSize; + + stream << int (textStyle.m_isBold) << int (textStyle.m_isItalic) + << int (textStyle.m_isUnderline) << int (textStyle.m_isStrikeThru); + + stream << textStyle.m_foregroundColor << textStyle.m_backgroundColor; + + stream << int (textStyle.m_isBackgroundOpaque); + + return stream; +} + +// friend +QDataStream &operator>> (QDataStream &stream, kpTextStyle &textStyle) +{ + stream >> textStyle.m_fontFamily; + stream >> textStyle.m_fontSize; + + int a, b, c, d; + stream >> a >> b >> c >> d; + textStyle.m_isBold = a; + textStyle.m_isItalic = b; + textStyle.m_isUnderline = c; + textStyle.m_isStrikeThru = d; + + stream >> textStyle.m_foregroundColor >> textStyle.m_backgroundColor; + + int e; + stream >> e; + textStyle.m_isBackgroundOpaque = e; + + return stream; +} + +// public +bool kpTextStyle::operator== (const kpTextStyle &rhs) const +{ + return (m_fontFamily == rhs.m_fontFamily && + m_fontSize == rhs.m_fontSize && + m_isBold == rhs.m_isBold && + m_isItalic == rhs.m_isItalic && + m_isUnderline == rhs.m_isUnderline && + m_isStrikeThru == rhs.m_isStrikeThru && + m_foregroundColor == rhs.m_foregroundColor && + m_backgroundColor == rhs.m_backgroundColor && + m_isBackgroundOpaque == rhs.m_isBackgroundOpaque); +} + +// public +bool kpTextStyle::operator!= (const kpTextStyle &rhs) const +{ + return !(*this == rhs); +} + + +// public +QString kpTextStyle::fontFamily () const +{ + return m_fontFamily; +} + +// public +void kpTextStyle::setFontFamily (const QString &f) +{ + m_fontFamily = f; +} + + +// public +int kpTextStyle::fontSize () const +{ + return m_fontSize; +} + +// public +void kpTextStyle::setFontSize (int s) +{ + m_fontSize = s; +} + + +// public +bool kpTextStyle::isBold () const +{ + return m_isBold; +} + +// public +void kpTextStyle::setBold (bool yes) +{ + m_isBold = yes; +} + + +// public +bool kpTextStyle::isItalic () const +{ + return m_isItalic; +} + +// public +void kpTextStyle::setItalic (bool yes) +{ + m_isItalic = yes; +} + + +// public +bool kpTextStyle::isUnderline () const +{ + return m_isUnderline; +} + +// public +void kpTextStyle::setUnderline (bool yes) +{ + m_isUnderline = yes; +} + + +// public +bool kpTextStyle::isStrikeThru () const +{ + return m_isStrikeThru; +} + +// public +void kpTextStyle::setStrikeThru (bool yes) +{ + m_isStrikeThru = yes; +} + + +// public +kpColor kpTextStyle::foregroundColor () const +{ + return m_foregroundColor; +} + +// public +void kpTextStyle::setForegroundColor (const kpColor &fcolor) +{ + m_foregroundColor = fcolor; +} + + +// public +kpColor kpTextStyle::backgroundColor () const +{ + return m_backgroundColor; +} + +// public +void kpTextStyle::setBackgroundColor (const kpColor &bcolor) +{ + m_backgroundColor = bcolor; +} + + +// public +bool kpTextStyle::isBackgroundOpaque () const +{ + return m_isBackgroundOpaque; +} + +// public +void kpTextStyle::setBackgroundOpaque (bool yes) +{ + m_isBackgroundOpaque = yes; +} + + +// public +bool kpTextStyle::isBackgroundTransparent () const +{ + return !m_isBackgroundOpaque; +} + +// public +void kpTextStyle::setBackgroundTransparent (bool yes) +{ + m_isBackgroundOpaque = !yes; +} + + +// public +QFont kpTextStyle::font () const +{ + QFont fnt (m_fontFamily, m_fontSize); + fnt.setBold (m_isBold); + fnt.setItalic (m_isItalic); + fnt.setUnderline (m_isUnderline); + fnt.setStrikeOut (m_isStrikeThru); + + return fnt; +} + +// public +QFontMetrics kpTextStyle::fontMetrics () const +{ + return QFontMetrics (font ()); +} diff --git a/kolourpaint/layers/selections/text/kpTextStyle.h b/kolourpaint/layers/selections/text/kpTextStyle.h new file mode 100644 index 00000000..f64e3d0d --- /dev/null +++ b/kolourpaint/layers/selections/text/kpTextStyle.h @@ -0,0 +1,107 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TEXT_STYLE_H +#define KP_TEXT_STYLE_H + + +#include + +#include + + +class QDataStream; +class QFont; +class QFontMetrics; + + +class kpTextStyle +{ +public: + kpTextStyle (); + kpTextStyle (const QString &fontFamily, + int fontSize, + bool isBold, bool isItalic, + bool isUnderline, bool isStrikeThru, + const kpColor &fcolor, + const kpColor &bcolor, + bool isBackgroundOpaque); + ~kpTextStyle (); + + + friend QDataStream &operator<< (QDataStream &stream, const kpTextStyle &textStyle); + friend QDataStream &operator>> (QDataStream &stream, kpTextStyle &textStyle); + bool operator== (const kpTextStyle &rhs) const; + bool operator!= (const kpTextStyle &rhs) const; + + + QString fontFamily () const; + void setFontFamily (const QString &f); + + int fontSize () const; + void setFontSize (int s); + + bool isBold () const; + void setBold (bool yes = true); + + bool isItalic () const; + void setItalic (bool yes = true); + + bool isUnderline () const; + void setUnderline (bool yes = true); + + bool isStrikeThru () const; + void setStrikeThru (bool yes = true); + + kpColor foregroundColor () const; + void setForegroundColor (const kpColor &fcolor); + + // Note: This is the _input_ backgroundColor + kpColor backgroundColor () const; + void setBackgroundColor (const kpColor &bcolor); + + bool isBackgroundOpaque () const; + void setBackgroundOpaque (bool yes = true); + + bool isBackgroundTransparent () const; + void setBackgroundTransparent (bool yes = true); + + + QFont font () const; + QFontMetrics fontMetrics () const; + +private: + QString m_fontFamily; + int m_fontSize; + bool m_isBold, m_isItalic, m_isUnderline, m_isStrikeThru; + kpColor m_foregroundColor, m_backgroundColor; + bool m_isBackgroundOpaque; +}; + + +#endif // KP_TEXT_STYLE_H diff --git a/kolourpaint/layers/tempImage/kpTempImage.cpp b/kolourpaint/layers/tempImage/kpTempImage.cpp new file mode 100644 index 00000000..f9ea76ca --- /dev/null +++ b/kolourpaint/layers/tempImage/kpTempImage.cpp @@ -0,0 +1,217 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include + +#include + +//--------------------------------------------------------------------- + +kpTempImage::kpTempImage (bool isBrush, RenderMode renderMode, + const QPoint &topLeft, const kpImage &image) + : m_isBrush (isBrush), + m_renderMode (renderMode), + m_topLeft (topLeft), + m_image (image), + m_width (image.width ()), m_height (image.height ()), + m_userFunction (0), + m_userData (0) +{ + // Use below constructor for that. + Q_ASSERT (renderMode != UserFunction); +} + +//--------------------------------------------------------------------- + +kpTempImage::kpTempImage (bool isBrush, const QPoint &topLeft, + UserFunctionType userFunction, void *userData, + int width, int height) + : m_isBrush (isBrush), + m_renderMode (UserFunction), + m_topLeft (topLeft), + m_width (width), m_height (height), + m_userFunction (userFunction), + m_userData (userData) +{ + Q_ASSERT (m_userFunction); +} + +//--------------------------------------------------------------------- + +kpTempImage::kpTempImage (const kpTempImage &rhs) + : m_isBrush (rhs.m_isBrush), + m_renderMode (rhs.m_renderMode), + m_topLeft (rhs.m_topLeft), + m_image (rhs.m_image), + m_width (rhs.m_width), m_height (rhs.m_height), + m_userFunction (rhs.m_userFunction), + m_userData (rhs.m_userData) +{ +} + +//--------------------------------------------------------------------- + +kpTempImage &kpTempImage::operator= (const kpTempImage &rhs) +{ + if (this == &rhs) + return *this; + + m_isBrush = rhs.m_isBrush; + m_renderMode = rhs.m_renderMode; + m_topLeft = rhs.m_topLeft; + m_image = rhs.m_image; + m_width = rhs.m_width, m_height = rhs.m_height; + m_userFunction = rhs.m_userFunction; + m_userData = rhs.m_userData; + + return *this; +} + +//--------------------------------------------------------------------- + +// public +bool kpTempImage::isBrush () const +{ + return m_isBrush; +} + +//--------------------------------------------------------------------- + +// public +kpTempImage::RenderMode kpTempImage::renderMode () const +{ + return m_renderMode; +} + +//--------------------------------------------------------------------- + +// public +QPoint kpTempImage::topLeft () const +{ + return m_topLeft; +} + +//--------------------------------------------------------------------- + +// public +kpImage kpTempImage::image () const +{ + return m_image; +} + +//--------------------------------------------------------------------- + +// public +kpTempImage::UserFunctionType kpTempImage::userFunction () const +{ + return m_userFunction; +} + +//--------------------------------------------------------------------- + +// public +void *kpTempImage::userData () const +{ + return m_userData; +} + +//--------------------------------------------------------------------- + +// public +bool kpTempImage::isVisible (const kpViewManager *vm) const +{ + return m_isBrush ? (bool) vm->viewUnderCursor () : true; +} + +//--------------------------------------------------------------------- + +// public +QRect kpTempImage::rect () const +{ + return QRect (m_topLeft.x (), m_topLeft.y (), + m_width, m_height); +} + +//--------------------------------------------------------------------- + +// public +int kpTempImage::width () const +{ + return m_width; +} + +//--------------------------------------------------------------------- + +// public +int kpTempImage::height () const +{ + return m_height; +} + +//--------------------------------------------------------------------- + +// public +bool kpTempImage::paintMayAddMask () const +{ + return (m_renderMode == SetImage || + m_renderMode == UserFunction); +} + +//--------------------------------------------------------------------- + +// public +void kpTempImage::paint (kpImage *destImage, const QRect &docRect) const +{ + const QPoint REL_TOP_LEFT = m_topLeft - docRect.topLeft (); + + switch (m_renderMode) + { + case SetImage: + { + kpPixmapFX::setPixmapAt(destImage, REL_TOP_LEFT, m_image); + break; + } + + case PaintImage: + { + kpPixmapFX::paintPixmapAt(destImage, REL_TOP_LEFT, m_image); + break; + } + + case UserFunction: + { + m_userFunction(destImage, REL_TOP_LEFT, m_userData); + break; + } + } +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/layers/tempImage/kpTempImage.h b/kolourpaint/layers/tempImage/kpTempImage.h new file mode 100644 index 00000000..bc70c069 --- /dev/null +++ b/kolourpaint/layers/tempImage/kpTempImage.h @@ -0,0 +1,110 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// REFACTOR: maybe make us and kpAbstractSelection share a new kpLayer base? + + +#ifndef kpTempImage_H +#define kpTempImage_H + + +#include + +#include + + +class kpViewManager; + + +class kpTempImage +{ +public: + // REFACTOR: Extract into class hierarchy. + enum RenderMode + { + SetImage, + PaintImage, + UserFunction + }; + + // REFACTOR: Function pointers imply a need for a proper class hierarchy. + typedef void (*UserFunctionType) (kpImage * /*destImage*/, + const QPoint & /*topLeft*/, + void * /*userData*/); + + /* + * Specifies that its visibility is dependent on whether or + * not the mouse cursor is inside a view. If false, the + * image is always displayed. + * + * This is the only way of specifying the "UserFunction" + * . must not draw outside + * the claimed rectangle. + */ + kpTempImage (bool isBrush, RenderMode renderMode, const QPoint &topLeft, const kpImage &image); + kpTempImage (bool isBrush, const QPoint &topLeft, + UserFunctionType userFunction, void *userData, + int width, int height); + kpTempImage (const kpTempImage &rhs); + kpTempImage &operator= (const kpTempImage &rhs); + + bool isBrush () const; + RenderMode renderMode () const; + QPoint topLeft () const; + kpImage image () const; + UserFunctionType userFunction () const; + void *userData () const; + + bool isVisible (const kpViewManager *vm) const; + QRect rect () const; + int width () const; + int height () const; + + + // Returns whether a call to paint() may add a mask to <*destImage>. + bool paintMayAddMask () const; + + /* + * Draws itself onto <*destImage>, given that <*destImage> represents + * the unzoomed of the kpDocument. You should check for + * visibility before calling this function. + */ + void paint (kpImage *destImage, const QRect &docRect) const; + +private: + bool m_isBrush; + RenderMode m_renderMode; + QPoint m_topLeft; + kpImage m_image; + // == m_image.{width,height}() unless m_renderMode == UserFunction. + int m_width, m_height; + UserFunctionType m_userFunction; + void *m_userData; +}; + + +#endif // kpTempImage_H diff --git a/kolourpaint/lgpl/generic/kpColorCollection.cpp b/kolourpaint/lgpl/generic/kpColorCollection.cpp new file mode 100644 index 00000000..7aa1df47 --- /dev/null +++ b/kolourpaint/lgpl/generic/kpColorCollection.cpp @@ -0,0 +1,537 @@ + +// REFACT0R: Remote open/save file logic is duplicated in kpDocument. +// HITODO: Test when remote file support in KDE 4 stabilizes + +/* This file is part of the KDE libraries + Copyright (C) 1999 Waldo Bastian (bastian@kde.org) + Copyright (C) 2007 Clarence Dang (dang@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +//----------------------------------------------------------------------------- +// KDE color collection + +#define DEBUG_KP_COLOR_COLLECTION 0 + +#include "kpColorCollection.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct ColorNode +{ + ColorNode(const QColor &c, const QString &n) + : color(c), name(n) {} + + QColor color; + QString name; +}; + +//--------------------------------------------------------------------- + +//BEGIN kpColorCollectionPrivate +class kpColorCollectionPrivate +{ +public: + kpColorCollectionPrivate(); + kpColorCollectionPrivate(const kpColorCollectionPrivate&); + + QList colorList; + QString name; + QString desc; + kpColorCollection::Editable editable; +}; + +kpColorCollectionPrivate::kpColorCollectionPrivate() + : editable(kpColorCollection::Yes) +{ +} + +kpColorCollectionPrivate::kpColorCollectionPrivate(const kpColorCollectionPrivate& p) + : colorList(p.colorList), name(p.name), desc(p.desc), editable(p.editable) +{ +} +//END kpColorCollectionPrivate + +//--------------------------------------------------------------------- + +QStringList +kpColorCollection::installedCollections() +{ + QStringList paletteList; + KGlobal::dirs()->findAllResources("config", "colors/*", KStandardDirs::NoDuplicates, paletteList); + + int strip = strlen("colors/"); + for(QStringList::Iterator it = paletteList.begin(); + it != paletteList.end(); + ++it) + { + (*it) = (*it).mid(strip); + } + + return paletteList; +} + +kpColorCollection::kpColorCollection() +{ + d = new kpColorCollectionPrivate(); +} + +kpColorCollection::kpColorCollection(const kpColorCollection &p) +{ + d = new kpColorCollectionPrivate(*p.d); +} + +kpColorCollection::~kpColorCollection() +{ + // Need auto-save? + delete d; +} + +static void CouldNotOpenDialog (const KUrl &url, QWidget *parent) +{ + KMessageBox::sorry (parent, + i18n ("Could not open color palette \"%1\".", + kpUrlFormatter::PrettyFilename (url))); +} + +// TODO: Set d->editable? +bool +kpColorCollection::open(const KUrl &url, QWidget *parent) +{ + QString tempPaletteFilePath; + if (url.isEmpty () || !KIO::NetAccess::download (url, tempPaletteFilePath, parent)) + { + #if DEBUG_KP_COLOR_COLLECTION + kDebug () << "\tcould not download"; + #endif + ::CouldNotOpenDialog (url, parent); + return false; + } + + // sync: remember to "KIO::NetAccess::removeTempFile (tempPaletteFilePath)" in all exit paths + + QFile paletteFile(tempPaletteFilePath); + if (!paletteFile.exists() || + !paletteFile.open(QIODevice::ReadOnly)) + { + #if DEBUG_KP_COLOR_COLLECTION + kDebug () << "\tcould not open qfile"; + #endif + KIO::NetAccess::removeTempFile (tempPaletteFilePath); + ::CouldNotOpenDialog (url, parent); + return false; + } + + // Read first line + // Expected "GIMP Palette" + QString line = QString::fromLocal8Bit(paletteFile.readLine()); + if (line.indexOf(" Palette") == -1) + { + KIO::NetAccess::removeTempFile (tempPaletteFilePath); + KMessageBox::sorry (parent, + i18n ("Could not open color palette \"%1\" - unsupported format.\n" + "The file may be corrupt.", + kpUrlFormatter::PrettyFilename (url))); + return false; + } + + QList newColorList; + QString newDesc; + + while( !paletteFile.atEnd() ) + { + line = QString::fromLocal8Bit(paletteFile.readLine()); + if (line[0] == '#') + { + // This is a comment line + line = line.mid(1); // Strip '#' + line = line.trimmed(); // Strip remaining white space.. + if (!line.isEmpty()) + { + newDesc += line+'\n'; // Add comment to description + } + } + else + { + // This is a color line, hopefully + line = line.trimmed(); + if (line.isEmpty()) continue; + int r, g, b; + int pos = 0; + if (sscanf(line.toAscii(), "%d %d %d%n", &r, &g, &b, &pos) >= 3) + { + r = qBound(0, r, 255); + g = qBound(0, g, 255); + b = qBound(0, b, 255); + QString name = line.mid(pos).trimmed(); + newColorList.append(ColorNode(QColor(r, g, b), name)); + } + } + } + + d->colorList = newColorList; + d->name.clear (); + d->desc = newDesc; + + KIO::NetAccess::removeTempFile (tempPaletteFilePath); + return true; +} + +static void CouldNotOpenKDEDialog (const QString &name, QWidget *parent) +{ + KMessageBox::sorry (parent, + i18n ("Could not open KDE color palette \"%1\".", name)); +} + +bool +kpColorCollection::openKDE(const QString &name, QWidget *parent) +{ +#if DEBUG_KP_COLOR_COLLECTION + kDebug () << "name=" << name; +#endif + + if (name.isEmpty()) + { + #if DEBUG_KP_COLOR_COLLECTION + kDebug () << "name.isEmpty"; + #endif + ::CouldNotOpenKDEDialog (name, parent); + return false; + } + + QString filename = KStandardDirs::locate("config", "colors/"+name); + if (filename.isEmpty()) + { + #if DEBUG_KP_COLOR_COLLECTION + kDebug () << "could not find file"; + #endif + ::CouldNotOpenKDEDialog (name, parent); + return false; + } + + // (this will pop up an error dialog on failure) + if (!open (KUrl (filename), parent)) + { + #if DEBUG_KP_COLOR_COLLECTION + kDebug () << "could not open"; + #endif + return false; + } + + d->name = name; +#if DEBUG_KP_COLOR_COLLECTION + kDebug () << "opened"; +#endif + return true; +} + +static void CouldNotSaveDialog (const KUrl &url, QWidget *parent) +{ + // TODO: use file.errorString() + KMessageBox::error (parent, + i18n ("Could not save color palette as \"%1\".", + kpUrlFormatter::PrettyFilename (url))); +} + +static void SaveToFile (kpColorCollectionPrivate *d, QIODevice *device) +{ + // HITODO: QTextStream can fail but does not report errors. + // Bug in KColorCollection too. + QTextStream str (device); + + QString description = d->desc.trimmed(); + description = '#'+description.split( "\n", QString::KeepEmptyParts).join("\n#"); + + str << "KDE RGB Palette\n"; + str << description << "\n"; + foreach (const ColorNode &node, d->colorList) + { + // Added for KolourPaint. + if(!node.color.isValid ()) + continue; + + int r,g,b; + node.color.getRgb(&r, &g, &b); + str << r << " " << g << " " << b << " " << node.name << "\n"; + } + + str.flush(); +} + +bool +kpColorCollection::saveAs(const KUrl &url, bool showOverwritePrompt, + QWidget *parent) const +{ + if (showOverwritePrompt && + KIO::NetAccess::exists (url, KIO::NetAccess::DestinationSide/*write*/, parent)) + { + int result = KMessageBox::warningContinueCancel (parent, + i18n ("A color palette called \"%1\" already exists.\n" + "Do you want to overwrite it?", + kpUrlFormatter::PrettyFilename (url)), + QString (), + KGuiItem (i18n ("Overwrite"))); + if (result != KMessageBox::Continue) + return false; + } + + if (url.isLocalFile ()) + { + const QString filename = url.toLocalFile (); + + // sync: All failure exit paths _must_ call KSaveFile::abort() or + // else, the KSaveFile destructor will overwrite the file, + // , despite the failure. + KSaveFile atomicFileWriter (filename); + { + if (!atomicFileWriter.open ()) + { + // We probably don't need this as has not been + // opened. + atomicFileWriter.abort (); + + #if DEBUG_KP_COLOR_COLLECTION + kDebug () << "\treturning false because could not open KSaveFile" + << " error=" << atomicFileWriter.error () << endl; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + + // Write to local temporary file. + ::SaveToFile (d, &atomicFileWriter); + + // Atomically overwrite local file with the temporary file + // we saved to. + if (!atomicFileWriter.finalize ()) + { + atomicFileWriter.abort (); + + #if DEBUG_KP_COLOR_COLLECTION + kDebug () << "\tcould not close KSaveFile"; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + } // sync KSaveFile.abort() + } + // Remote file? + else + { + // Create temporary file that is deleted when the variable goes + // out of scope. + KTemporaryFile tempFile; + if (!tempFile.open ()) + { + #if DEBUG_KP_COLOR_COLLECTION + kDebug () << "\treturning false because could not open tempFile"; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + + // Write to local temporary file. + ::SaveToFile (d, &tempFile); + + // Collect name of temporary file now, as QTemporaryFile::fileName() + // stops working after close() is called. + const QString tempFileName = tempFile.fileName (); + #if DEBUG_KP_COLOR_COLLECTION + kDebug () << "\ttempFileName='" << tempFileName << "'"; + #endif + Q_ASSERT (!tempFileName.isEmpty ()); + + tempFile.close (); + if (tempFile.error () != QFile::NoError) + { + #if DEBUG_KP_COLOR_COLLECTION + kDebug () << "\treturning false because could not close"; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + + // Copy local temporary file to overwrite remote. + // TODO: No one seems to know how to do this atomically + // [http://lists.kde.org/?l=kde-core-devel&m=117845162728484&w=2]. + // At least, fish:// (ssh) is definitely not atomic. + if (!KIO::NetAccess::upload (tempFileName, url, parent)) + { + #if DEBUG_KP_COLOR_COLLECTION + kDebug () << "\treturning false because could not upload"; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + } + + d->name.clear (); + return true; +} + +bool +kpColorCollection::saveKDE(QWidget *parent) const +{ + const QString name = d->name; + QString filename = KStandardDirs::locateLocal("config", "colors/" + name); + const bool ret = saveAs (KUrl (filename), false/*no overwite prompt*/, parent); + // (d->name is wiped by saveAs()). + d->name = name; + return ret; +} + +QString kpColorCollection::description() const +{ + return d->desc; +} + +void kpColorCollection::setDescription(const QString &desc) +{ + d->desc = desc; +} + +QString kpColorCollection::name() const +{ + return d->name; +} + +void kpColorCollection::setName(const QString &name) +{ + d->name = name; +} + +kpColorCollection::Editable kpColorCollection::editable() const +{ + return d->editable; +} + +void kpColorCollection::setEditable(Editable editable) +{ + d->editable = editable; +} + +int kpColorCollection::count() const +{ + return (int) d->colorList.count(); +} + +void kpColorCollection::resize(int newCount) +{ + if (newCount == count()) + return; + else if (newCount < count()) + { + d->colorList.erase(d->colorList.begin() + newCount, d->colorList.end()); + } + else if (newCount > count()) + { + while(newCount > count()) + { + const int ret = addColor(QColor(), QString()/*color name*/); + Q_ASSERT(ret == count() - 1); + } + } +} + +kpColorCollection& +kpColorCollection::operator=( const kpColorCollection &p) +{ + if (&p == this) return *this; + d->colorList = p.d->colorList; + d->name = p.d->name; + d->desc = p.d->desc; + d->editable = p.d->editable; + return *this; +} + +QColor +kpColorCollection::color(int index) const +{ + if ((index < 0) || (index >= count())) + return QColor(); + + return d->colorList[index].color; +} + +int +kpColorCollection::findColor(const QColor &color) const +{ + for (int i = 0; i < d->colorList.size(); ++i) + { + if (d->colorList[i].color == color) + return i; + } + return -1; +} + +QString +kpColorCollection::name(int index) const +{ + if ((index < 0) || (index >= count())) + return QString(); + + return d->colorList[index].name; +} + +QString kpColorCollection::name(const QColor &color) const +{ + return name(findColor(color)); +} + +int +kpColorCollection::addColor(const QColor &newColor, const QString &newColorName) +{ + d->colorList.append(ColorNode(newColor, newColorName)); + return count() - 1; +} + +int +kpColorCollection::changeColor(int index, + const QColor &newColor, + const QString &newColorName) +{ + if ((index < 0) || (index >= count())) + return -1; + + ColorNode& node = d->colorList[index]; + node.color = newColor; + node.name = newColorName; + + return index; +} + +int kpColorCollection::changeColor(const QColor &oldColor, + const QColor &newColor, + const QString &newColorName) +{ + return changeColor( findColor(oldColor), newColor, newColorName); +} + diff --git a/kolourpaint/lgpl/generic/kpColorCollection.h b/kolourpaint/lgpl/generic/kpColorCollection.h new file mode 100644 index 00000000..b904caf7 --- /dev/null +++ b/kolourpaint/lgpl/generic/kpColorCollection.h @@ -0,0 +1,264 @@ + +// SYNC: Periodically merge in changes from: +// +// trunk/KDE/kdelibs/kdeui/colors/kcolorcollection.{h,cpp} +// +// which this is a fork of. +// +// Our changes can be merged back into KDE (grep for "Added for KolourPaint" and similar). + +/* This file is part of the KDE libraries + Copyright (C) 1999 Waldo Bastian (bastian@kde.org) + Copyright (C) 2007 Clarence Dang (dang@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; version + 2 of the License. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +//----------------------------------------------------------------------------- +// KDE color collection. + +#ifndef kpColorCollection_H +#define kpColorCollection_H + +#include + +#include +#include +#include +#include + + +class KUrl; + +/** + * Class for handling color collections ("palettes"). + * + * This class makes it easy to handle color collections, sometimes referred to + * as "palettes". This class can read and write collections from and to a file. + * + * Collections that are managed by KDE have a non-empty name(). Collections + * stored in regular files have an empty name(). + * + * This class uses the "GIMP" palette file format. + * + * @author Waldo Bastian (bastian@kde.org), Clarence Dang (dang@kde.org) + **/ +class KOLOURPAINT_LGPL_EXPORT kpColorCollection +{ +public: + /** + * Query which KDE color collections are installed. + * + * @return A list with installed color collection names. + */ + static QStringList installedCollections(); + + /** + * kpColorCollection constructor + * + * argument removed for KolourPaint. + * Use openKDE() instead, which also has error handling. + **/ + explicit kpColorCollection(); + + /** + * kpColorCollection copy constructor. + **/ + kpColorCollection(const kpColorCollection &); + + /** + * kpColorCollection destructor. + **/ + ~kpColorCollection(); + + /** + * kpColorCollection assignment operator + **/ + kpColorCollection& operator=( const kpColorCollection &); + + // On failure, this prints an error dialog and returns false. + // On success, it sets the name() to an empty string and returns true. + // + // Added for KolourPaint. + bool open(const KUrl &url, QWidget *parent); + + // Same as open() but is given the name of a KDE palette, not a filename. + // + // @param name The name of collection as returned by installedCollections(). + // name() is set to this. + // + // Added for KolourPaint. + bool openKDE(const QString &name, QWidget *parent); + + // On failure, this prints an error dialog and returns false. + // If the user cancels any presented overwrite dialog, it also returns false. + // On success, it returns true. + // + // The file can be overwritten without displaying any warning dialog, if + // is set to false. + // + // name() is set to an empty string. + // + // Added for KolourPaint. + bool saveAs(const KUrl &url, bool showOverwritePrompt, QWidget *parent) const; + + /** + * Save the collection to the KDE-local store + * (usually $HOME/.kde/share/config/colors) using name(). + * + * @return 'true' if successful + * + * Renamed from save() for KolourPaint. + **/ + bool saveKDE(QWidget *parent) const; + + /** + * Get the description of the collection. + * @return the description of the collection. + **/ + QString description() const; + + /** + * Set the description of the collection. + * @param desc the new description + **/ + void setDescription(const QString &desc); + + /** + * Get the name of the collection. + * @return the name of the collection + **/ + QString name() const; + + /** + * Set the name of the collection. + * @param name the name of the collection + **/ + void setName(const QString &name); + + /** + * Used to specify whether a collection may be edited. + * @see editable() + * @see setEditable() + */ + enum Editable { Yes, ///< Collection may be edited + No, ///< Collection may not be edited + Ask ///< Ask user before editing + }; + + /** + * Returns whether the collection may be edited. + * @return the state of the collection + **/ + Editable editable() const; + + /** + * Change whether the collection may be edited. + * @param editable the state of the collection + **/ + void setEditable(Editable editable); + + /** + * Return the number of colors in the collection. + * @return the number of colors + **/ + int count() const; + + /** + * Adds invalid colors or removes colors so that there will be @p newCount + * colors in the color collection. + * + * @param target number of colors + * + * Added for KolourPaint. + */ + void resize(int newCount); + + /** + * Find color by index. + * @param index the index of the desired color + * @return The @p index -th color of the collection, null if not found. + **/ + QColor color(int index) const; + + /** + * Find index by @p color. + * @param color the color to find + * @return The index of the color in the collection or -1 if the + * color is not found. + **/ + int findColor(const QColor &color) const; + + /** + * Find color name by @p index. + * @param index the index of the color + * @return The name of the @p index -th color. + * Note that not all collections have named the colors. Null is + * returned if the color does not exist or has no name. + **/ + QString name(int index) const; + + /** + * Find color name by @p color. + * @return The name of color according to this collection. + * Note that not all collections have named the colors. + * Note also that each collection can give the same color + * a different name. + **/ + QString name(const QColor &color) const; + + /** + * Add a color. + * @param newColor The color to add. + * @param newColorName The name of the color, null to remove + * the name. + * @return The index of the added color. + **/ + int addColor(const QColor &newColor, + const QString &newColorName = QString()); + + /** + * Change a color. + * @param index Index of the color to change + * @param newColor The new color. + * @param newColorName The new color name, null to remove + * the name. + * @return The index of the new color or -1 if the color couldn't + * be changed. + **/ + int changeColor(int index, + const QColor &newColor, + const QString &newColorName = QString()); + + /** + * Change a color. + * @param oldColor The original color + * @param newColor The new color. + * @param newColorName The new color name, null to remove + * the name. + * @return The index of the new color or -1 if the color couldn't + * be changed. + **/ + int changeColor(const QColor &oldColor, + const QColor &newColor, + const QString &newColorName = QString()); + +private: + class kpColorCollectionPrivate *d; +}; + + +#endif // kpColorCollection_H + diff --git a/kolourpaint/lgpl/generic/kpUrlFormatter.cpp b/kolourpaint/lgpl/generic/kpUrlFormatter.cpp new file mode 100644 index 00000000..b898df39 --- /dev/null +++ b/kolourpaint/lgpl/generic/kpUrlFormatter.cpp @@ -0,0 +1,58 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include + +//--------------------------------------------------------------------- + +// public static +QString kpUrlFormatter::PrettyUrl (const KUrl &url) +{ + if (url.isEmpty ()) + return i18n ("Untitled"); + else + return url.pathOrUrl (); +} + +//--------------------------------------------------------------------- + +// public static +QString kpUrlFormatter::PrettyFilename (const KUrl &url) +{ + if (url.isEmpty ()) + return i18n ("Untitled"); + else if (url.fileName ().isEmpty ()) + return kpUrlFormatter::PrettyUrl (url); // better than the name "" + else + return url.fileName (); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/lgpl/generic/kpUrlFormatter.h b/kolourpaint/lgpl/generic/kpUrlFormatter.h new file mode 100644 index 00000000..f006c4b3 --- /dev/null +++ b/kolourpaint/lgpl/generic/kpUrlFormatter.h @@ -0,0 +1,57 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +// +// This class is in the lpgl/ folder because other code in the folder needs +// to access it. But it is not under the LPGL. +// + + +#ifndef kpUrlFormatter_H +#define kpUrlFormatter_H + +#include + + +class QString; + +class KUrl; + + +class KOLOURPAINT_LGPL_EXPORT kpUrlFormatter +{ +public: + // (will convert: empty Url --> "Untitled") + static QString PrettyUrl (const KUrl &url); + + // (will convert: empty Url --> "Untitled") + static QString PrettyFilename (const KUrl &url); +}; + + +#endif // kpUrlFormatter_H diff --git a/kolourpaint/lgpl/generic/widgets/kpColorCellsBase.cpp b/kolourpaint/lgpl/generic/widgets/kpColorCellsBase.cpp new file mode 100644 index 00000000..83675e27 --- /dev/null +++ b/kolourpaint/lgpl/generic/widgets/kpColorCellsBase.cpp @@ -0,0 +1,563 @@ + +/* This file is part of the KDE libraries + Copyright (C) 1997 Martin Jones (mjones@kde.org) + Copyright (C) 2007 Roberto Raggi (roberto@kdevelop.org) + Copyright (C) 2007 Clarence Dang (dang@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +//----------------------------------------------------------------------------- + +#define DEBUG_KP_COLOR_CELLS_BASE 0 + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +class kpColorCellsBase::kpColorCellsBasePrivate +{ +public: + kpColorCellsBasePrivate(kpColorCellsBase *q): q(q) + { + colors = 0; + inMouse = false; + selected = -1; + shade = false; + acceptDrags = false; + cellsResizable = true; + } + + kpColorCellsBase *q; + + // Note: This is a good thing and is _not_ data duplication with the + // colors of QTableWidget cells, for the following reasons: + // + // 1. QColor in Qt4 is full-quality RGB. However, QTableWidget + // cells are lossy as their colors may be dithered on the screen. + // + // Eventually, this field will be changed to a kpColor. + // + // 2. We change the QTableWidget cells' colors when the widget is + // disabled (see changeEvent()). + // + // Therefore, do not remove this field without better reasons. + QColor *colors; + + QPoint mousePos; + int selected; + bool shade; + bool acceptDrags; + bool cellsResizable; + bool inMouse; +}; + +kpColorCellsBase::kpColorCellsBase( QWidget *parent, int rows, int cols ) + : QTableWidget( parent ), d(new kpColorCellsBasePrivate(this)) +{ + setItemDelegate(new QItemDelegate(this)); + + setFrameShape(QFrame::NoFrame); + d->shade = true; + setRowCount( rows ); + setColumnCount( cols ); + + verticalHeader()->hide(); + horizontalHeader()->hide(); + + d->colors = new QColor [ rows * cols ]; + + d->selected = 0; + d->inMouse = false; + + // Drag'n'Drop + setAcceptDrops( true); + + setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + viewport()->setBackgroundRole( QPalette::Background ); + setBackgroundRole( QPalette::Background ); +} + +kpColorCellsBase::~kpColorCellsBase() +{ + delete [] d->colors; + + delete d; +} + +void kpColorCellsBase::invalidateAllColors () +{ + for (int r = 0; r < rowCount (); r++) + for (int c = 0; c < columnCount (); c++) + d->colors [r * columnCount () + c] = QColor (); +} + +void kpColorCellsBase::clear() +{ + invalidateAllColors (); + QTableWidget::clear (); +} + +void kpColorCellsBase::clearContents() +{ + invalidateAllColors (); + QTableWidget::clearContents (); +} + +void kpColorCellsBase::setRowColumnCounts (int rows, int columns) +{ + const int oldRows = rowCount (), oldCols = columnCount (); + const int newRows = rows, newCols = columns; +#if DEBUG_KP_COLOR_CELLS_BASE + kDebug () << "oldRows=" << oldRows << "oldCols=" << oldCols + << "newRows=" << newRows << "newCols=" << newCols; +#endif + + if (oldRows == newRows && oldCols == newCols) + return; + + QTableWidget::setColumnCount (newCols); + QTableWidget::setRowCount (newRows); + + QColor *oldColors = d->colors; + d->colors = new QColor [newRows * newCols]; + + for (int r = 0; r < qMin (oldRows, newRows); r++) + for (int c = 0; c < qMin (oldCols, newCols); c++) + d->colors [r * newCols + c] = oldColors [r * oldCols + c]; + + delete [] oldColors; +} + +void kpColorCellsBase::setColumnCount (int newColumns) +{ + setRowColumnCounts (rowCount (), newColumns); +} + +void kpColorCellsBase::setRowCount (int newRows) +{ + setRowColumnCounts (newRows, columnCount ()); +} + +QColor kpColorCellsBase::color(int index) const +{ + return d->colors[index]; +} + +int kpColorCellsBase::count() const +{ + return rowCount() * columnCount(); +} + +void kpColorCellsBase::setShading(bool _shade) +{ + d->shade = _shade; +} + +void kpColorCellsBase::setAcceptDrags(bool _acceptDrags) +{ + d->acceptDrags = _acceptDrags; +} + +void kpColorCellsBase::setCellsResizable(bool yes) +{ + d->cellsResizable = yes; +} + +void kpColorCellsBase::setSelected(int index) +{ + Q_ASSERT( index >= 0 && index < count() ); + + d->selected = index; +} + +int kpColorCellsBase::selectedIndex() const +{ + return d->selected; +} + +//--------------------------------------------------------------------- + +static void TableWidgetItemSetColor (QTableWidgetItem *tableItem, const QColor &color) +{ + Q_ASSERT (tableItem); + QImage image(16, 16, QImage::Format_ARGB32_Premultiplied); + QPainter painter(&image); + const int StippleSize = 4; + QColor useColor; + + for (int dy = 0; dy < 16; dy += StippleSize) + { + for (int dx = 0; dx < 16; dx += StippleSize) + { + const bool parity = ((dy + dx) / StippleSize) % 2; + + if (!parity) + useColor = Qt::white; + else + useColor = Qt::lightGray; + + painter.fillRect(dx, dy, StippleSize, StippleSize, useColor); + } + } + + painter.fillRect(image.rect(), color); + painter.end(); + + tableItem->setData(Qt::BackgroundRole , QBrush(image)); +} + +//--------------------------------------------------------------------- + +void kpColorCellsBase::setColor( int column, const QColor &colorIn ) +{ + const int tableRow = column / columnCount(); + const int tableColumn = column % columnCount(); + + Q_ASSERT( tableRow >= 0 && tableRow < rowCount() ); + Q_ASSERT( tableColumn >= 0 && tableColumn < columnCount() ); + + QColor color = colorIn; + + d->colors[column] = color; + + QTableWidgetItem* tableItem = item(tableRow,tableColumn); + + if (color.isValid ()) + { + if ( tableItem == 0 ) { + tableItem = new QTableWidgetItem(); + setItem(tableRow,tableColumn,tableItem); + } + + if (isEnabled ()) + ::TableWidgetItemSetColor (tableItem, color); + } + else + { + delete tableItem; + } + + emit colorChanged (column, color); +} + +void kpColorCellsBase::changeEvent( QEvent* event ) +{ + QTableWidget::changeEvent (event); + + if (event->type () != QEvent::EnabledChange) + return; + + for (int r = 0; r < rowCount (); r++) + { + for (int c = 0; c < columnCount (); c++) + { + const int index = r * columnCount () + c; + + QTableWidgetItem* tableItem = item(r, c); + + // See API Doc for this invariant. + Q_ASSERT (!!tableItem == d->colors [index].isValid ()); + + if (!tableItem) + continue; + + + QColor color; + if (isEnabled ()) + color = d->colors [index]; + else + color = palette ().color (backgroundRole ()); + + ::TableWidgetItemSetColor (tableItem, color); + } + } +} + +/*void kpColorCellsBase::paintCell( QPainter *painter, int row, int col ) +{ + painter->setRenderHint( QPainter::Antialiasing , true ); + + QBrush brush; + int w = 1; + + if (shade) + { + qDrawShadePanel( painter, 1, 1, cellWidth()-2, + cellHeight()-2, palette(), true, 1, &brush ); + w = 2; + } + QColor color = colors[ row * numCols() + col ]; + if (!color.isValid()) + { + if (!shade) return; + color = palette().color(backgroundRole()); + } + + const QRect colorRect( w, w, cellWidth()-w*2, cellHeight()-w*2 ); + painter->fillRect( colorRect, color ); + + if ( row * numCols() + col == selected ) { + painter->setPen( qGray(color.rgb())>=127 ? Qt::black : Qt::white ); + painter->drawLine( colorRect.topLeft(), colorRect.bottomRight() ); + painter->drawLine( colorRect.topRight(), colorRect.bottomLeft() ); + } +}*/ + +void kpColorCellsBase::resizeEvent( QResizeEvent* e ) +{ + if (d->cellsResizable) + { + // According to the Qt doc: + // If you need to set the width of a given column to a fixed value, call + // QHeaderView::resizeSection() on the table's {horizontal,vertical} + // header. + // Therefore we iterate over each row and column and set the header section + // size, as the sizeHint does indeed appear to be ignored in favor of a + // minimum size that is larger than what we want. + for ( int index = 0 ; index < columnCount() ; index++ ) + horizontalHeader()->resizeSection( index, sizeHintForColumn(index) ); + for ( int index = 0 ; index < rowCount() ; index++ ) + verticalHeader()->resizeSection( index, sizeHintForRow(index) ); + } + else + { + // Update scrollbars if they're forced on by a subclass. + // TODO: Should the d->cellsResizable path (from kdelibs) do this as well? + QTableWidget::resizeEvent (e); + } +} + +int kpColorCellsBase::sizeHintForColumn(int /*column*/) const +{ + // TODO: Should it be "(width() - frameWidth() * 2) / columnCount()"? + return width() / columnCount() ; +} + +int kpColorCellsBase::sizeHintForRow(int /*row*/) const +{ + // TODO: Should be "(height() - frameWidth() * 2) / rowCount()"? + return height() / rowCount() ; +} + +void kpColorCellsBase::mousePressEvent( QMouseEvent *e ) +{ + d->inMouse = true; + d->mousePos = e->pos(); +} + + +int kpColorCellsBase::positionToCell(const QPoint &pos, bool ignoreBorders, + bool allowEmptyCell) const +{ + //TODO ignoreBorders not yet handled + Q_UNUSED( ignoreBorders ) + + const int r = indexAt (pos).row (), c = indexAt (pos).column (); +#if DEBUG_KP_COLOR_CELLS_BASE + kDebug () << "r=" << r << "c=" << c; +#endif + + if (r == -1 || c == -1) + return -1; + + if (!allowEmptyCell && !itemAt(pos)) + return -1; + + const int cell = r * columnCount() + c; + + /*if (!ignoreBorders) + { + int border = 2; + int x = pos.x() - col * cellWidth(); + int y = pos.y() - row * cellHeight(); + if ( (x < border) || (x > cellWidth()-border) || + (y < border) || (y > cellHeight()-border)) + return -1; + }*/ + + return cell; +} + + +void kpColorCellsBase::mouseMoveEvent( QMouseEvent *e ) +{ + if( !(e->buttons() & Qt::LeftButton)) return; + + if(d->inMouse) { + int delay = KGlobalSettings::dndEventDelay(); + if(e->x() > d->mousePos.x()+delay || e->x() < d->mousePos.x()-delay || + e->y() > d->mousePos.y()+delay || e->y() < d->mousePos.y()-delay){ + // Drag color object + int cell = positionToCell(d->mousePos); + if (cell != -1) + { + #if DEBUG_KP_COLOR_CELLS_BASE + kDebug () << "beginning drag from cell=" << cell + << "color: isValid=" << d->colors [cell].isValid () + << " rgba=" << (int *) d->colors [cell].rgba(); + #endif + Q_ASSERT (d->colors[cell].isValid()); + KColorMimeData::createDrag(d->colors[cell], this)->start(Qt::CopyAction | Qt::MoveAction); + #if DEBUG_KP_COLOR_CELLS_BASE + kDebug () << "finished drag"; + #endif + } + } + } +} + + +// LOTODO: I'm not quite clear on how the drop actions logic is supposed +// to be done e.g.: +// +// 1. Who is supposed to call setDropAction(). +// 2. Which variant of accept(), setAccepted(), acceptProposedAction() etc. +// is supposed to be called to accept a move -- rather than copy -- +// action. +// +// Nevertheless, it appears to work -- probably because we restrict +// the non-Qt-default move/swap action to be intrawidget. +static void SetDropAction (QWidget *self, QDropEvent *event) +{ + // TODO: Would be nice to default to CopyAction if the destination cell + // is null. + if (event->source () == self && (event->keyboardModifiers () & Qt::ControlModifier) == 0) + event->setDropAction(Qt::MoveAction); + else + event->setDropAction(Qt::CopyAction); +} + +void kpColorCellsBase::dragEnterEvent( QDragEnterEvent *event) +{ +#if DEBUG_KP_COLOR_CELLS_BASE + kDebug () << "kpColorCellsBase::dragEnterEvent() acceptDrags=" + << d->acceptDrags + << " canDecode=" << KColorMimeData::canDecode(event->mimeData()) + << endl; +#endif + event->setAccepted( d->acceptDrags && KColorMimeData::canDecode( event->mimeData())); + if (event->isAccepted ()) + ::SetDropAction (this, event); +} + +// Reimplemented to override QTableWidget's override. Else dropping doesn't work. +void kpColorCellsBase::dragMoveEvent (QDragMoveEvent *event) +{ +#if DEBUG_KP_COLOR_CELLS_BASE + kDebug () << "kpColorCellsBase::dragMoveEvent() acceptDrags=" + << d->acceptDrags + << " canDecode=" << KColorMimeData::canDecode(event->mimeData()) + << endl; +#endif + // TODO: Disallow drag that isn't onto a cell. + event->setAccepted( d->acceptDrags && KColorMimeData::canDecode( event->mimeData())); + if (event->isAccepted ()) + ::SetDropAction (this, event); +} + +void kpColorCellsBase::dropEvent( QDropEvent *event) +{ + QColor c=KColorMimeData::fromMimeData(event->mimeData()); + + const int dragSourceCell = event->source () == this ? + positionToCell (d->mousePos, true) : + -1; +#if DEBUG_KP_COLOR_CELLS_BASE + kDebug () << "kpColorCellsBase::dropEvent()" + << "color: rgba=" << (const int *) c.rgba () << "isValid=" << c.isValid() + << "source=" << event->source () << "dragSourceCell=" << dragSourceCell; +#endif + if( c.isValid()) { + ::SetDropAction (this, event); + + int cell = positionToCell(event->pos(), true, true/*allow empty cell*/); + #if DEBUG_KP_COLOR_CELLS_BASE + kDebug () << "\tcell=" << cell; + #endif + // TODO: I believe kdelibs forgets to do this. + if (cell == -1) + return; + + // Avoid NOP. + if (cell == dragSourceCell) + return; + + QColor destOldColor = d->colors [cell]; + setColor(cell,c); + + #if DEBUG_KP_COLOR_CELLS_BASE + kDebug () << "\tdropAction=" << event->dropAction () + << "destOldColor.rgba=" << (const int *) destOldColor.rgba (); + #endif + if (event->dropAction () == Qt::MoveAction && dragSourceCell != -1) { + setColor(dragSourceCell, destOldColor); + } + } +} + +void kpColorCellsBase::mouseReleaseEvent( QMouseEvent *e ) +{ + int cell = positionToCell(d->mousePos); + int currentCell = positionToCell(e->pos()); + + // If we release the mouse in another cell and we don't have + // a drag we should ignore this event. + if (currentCell != cell) + cell = -1; + + if ( (cell != -1) && (d->selected != cell) ) + { + d->selected = cell; + + const int newRow = cell/columnCount(); + const int newColumn = cell%columnCount(); + + clearSelection(); // we do not want old violet selected cells + + item(newRow,newColumn)->setSelected(true); + } + + d->inMouse = false; + if (cell != -1) + { + emit colorSelected( cell , color(cell) ); + emit colorSelected( cell , color(cell), e->button() ); + } +} + +void kpColorCellsBase::mouseDoubleClickEvent( QMouseEvent * /*e*/ ) +{ + int cell = positionToCell(d->mousePos, false, true/*allow empty cell*/); + + if (cell != -1) + emit colorDoubleClicked( cell , color(cell) ); +} + + +#include "kpColorCellsBase.moc" diff --git a/kolourpaint/lgpl/generic/widgets/kpColorCellsBase.h b/kolourpaint/lgpl/generic/widgets/kpColorCellsBase.h new file mode 100644 index 00000000..3fa257dd --- /dev/null +++ b/kolourpaint/lgpl/generic/widgets/kpColorCellsBase.h @@ -0,0 +1,185 @@ + +// SYNC: Periodically merge in changes from: +// +// trunk/KDE/kdelibs/kdeui/colors/kcolordialog.{h,cpp} +// +// which this is a fork of. +// +// Our changes can be merged back into KDE (grep for "Added for KolourPaint" and similar). + +/* This file is part of the KDE libraries + Copyright (C) 1997 Martin Jones (mjones@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +//---------------------------------------------------------------------- +// KDE color selection dialog. + +// layout management added Oct 1997 by Mario Weilguni +// + +#ifndef kpColorCellsBase_H +#define kpColorCellsBase_H + +#include + +#include + +/** +* A table of editable color cells. +* +* @author Martin Jones +* +* Added for KolourPaint: +* +* If you have not called setColor() for a cell, its widget will not exist. +* So it is possible to have "holes" in this rectangular table of cells. +* You can delete a cell widget by calling setColor() with an invalid QColor. +* +* An invariant is that color() returns an invalid color iff the cells' widget +* does not exist. Note that: +* +* 1. You can double click on cells that don't contain a widget +* 2. You can drop onto -- but not drag from -- a cell that doesn't contain a +* widget +* +* If a color is dragged and dropped to-and-from the same instance of this +* widget, then the colors in the source and destination cells are swapped +* (this is a "move action"). +* +* If CTRL is held or they are not from the same instance, then the source +* cell's color is copied into the destination cell, without any change to +* the source cell (this is a "copy action"). +*/ +class KOLOURPAINT_LGPL_EXPORT kpColorCellsBase : public QTableWidget +{ + Q_OBJECT +public: + /** + * Constructs a new table of color cells, consisting of + * @p rows * @p columns colors. + * + * @param parent The parent of the new widget + * @param rows The number of rows in the table + * @param columns The number of columns in the table + * + * Specifying and was made optional for KolourPaint. + */ + kpColorCellsBase( QWidget *parent, int rows = 0, int columns = 0 ); + ~kpColorCellsBase(); + +private: + /** Added for KolourPaint. */ + void invalidateAllColors (); + +public: + /** Added for KolourPaint. + WARNING: These are not virtual in QTableWidget. + */ + void clear (); + void clearContents (); + + /** Added for KolourPaint. */ + void setRowColumnCounts (int rows, int columns); + + /** Added for KolourPaint. + WARNING: These are not virtual in QTableWidget. + */ + void setColumnCount (int columns); + void setRowCount (int rows); + + /** Sets the color in the given index in the table. + + The following behavior change was added for KolourPaint: + + If is not valid, the cell widget at is deleted. + */ + void setColor( int index, const QColor &col ); + /** Returns the color at a given index in the table. + If a cell widget does not exist at , the invalid color is + returned. + */ + QColor color( int index ) const; + /** Returns the total number of color cells in the table */ + int count() const; + + void setShading(bool shade); + void setAcceptDrags(bool acceptDrags); + + /** Whether component cells should resize with the entire widget. + Default is true. + + Added for KolourPaint. + */ + void setCellsResizable(bool yes); + + /** Sets the currently selected cell to @p index */ + void setSelected(int index); + /** Returns the index of the cell which is currently selected */ + int selectedIndex() const; + +Q_SIGNALS: + /** Emitted when a color is selected in the table */ + void colorSelected( int index , const QColor& color ); + /** Emitted with the above. + + Added for KolourPaint. + */ + void colorSelected( int index , const QColor& color, Qt::MouseButton button ); + + /** Emitted when a color in the table is double-clicked */ + void colorDoubleClicked( int index , const QColor& color ); + + /** Emitted when setColor() is called. + This includes when a color is dropped onto the table, via drag-and-drop. + + Added for KolourPaint. + */ + void colorChanged( int index , const QColor& color ); + +protected: + /** Grays out the cells, when the object is disabled. + Added for KolourPaint. + */ + virtual void changeEvent( QEvent* event ); + + // the three methods below are used to ensure equal column widths and row heights + // for all cells and to update the widths/heights when the widget is resized + virtual int sizeHintForColumn(int column) const; + virtual int sizeHintForRow(int column) const; + virtual void resizeEvent( QResizeEvent* event ); + + virtual void mouseReleaseEvent( QMouseEvent * ); + virtual void mousePressEvent( QMouseEvent * ); + virtual void mouseMoveEvent( QMouseEvent * ); + virtual void dragEnterEvent( QDragEnterEvent * ); + virtual void dragMoveEvent( QDragMoveEvent * ); + virtual void dropEvent( QDropEvent *); + virtual void mouseDoubleClickEvent( QMouseEvent * ); + + /** was added for KolourPaint. */ + int positionToCell(const QPoint &pos, bool ignoreBorders=false, + bool allowEmptyCell=false) const; + +private: + class kpColorCellsBasePrivate; + friend class kpColorCellsBasePrivate; + kpColorCellsBasePrivate *const d; + + Q_DISABLE_COPY(kpColorCellsBase) +}; + +#endif // kpColorCellsBase_H diff --git a/kolourpaint/lgpl/kolourpaint_lgpl_export.h b/kolourpaint/lgpl/kolourpaint_lgpl_export.h new file mode 100644 index 00000000..fdfb8bca --- /dev/null +++ b/kolourpaint/lgpl/kolourpaint_lgpl_export.h @@ -0,0 +1,40 @@ +/* This file is part of the KDE project + Copyright (C) 2007 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KOLOURPAINT_LGPL_EXPORT_H +#define KOLOURPAINT_LGPL_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include + +#ifndef KOLOURPAINT_LGPL_EXPORT +# if defined(MAKE_KOLOURPAINT4_LGPL_LIB) + /* We are building this library */ +# define KOLOURPAINT_LGPL_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define KOLOURPAINT_LGPL_EXPORT KDE_IMPORT +# endif +#endif + +# ifndef KOLOURPAINT_LGPL_EXPORT_DEPRECATED +# define KOLOURPAINT_LGPL_EXPORT_DEPRECATED KDE_DEPRECATED KOLOURPAINT_LGPL_EXPORT +# endif + +#endif diff --git a/kolourpaint/mainWindow/kpMainWindow.cpp b/kolourpaint/mainWindow/kpMainWindow.cpp new file mode 100644 index 00000000..bd7e3741 --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow.cpp @@ -0,0 +1,975 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if DEBUG_KP_MAIN_WINDOW + #include +#endif + + +//--------------------------------------------------------------------- + +kpMainWindow::kpMainWindow () + : KXmlGuiWindow (0/*parent*/) +{ + init (); + open (KUrl (), true/*create an empty doc*/); + + d->isFullyConstructed = true; +} + +//--------------------------------------------------------------------- + +kpMainWindow::kpMainWindow (const KUrl &url) + : KXmlGuiWindow (0/*parent*/) +{ + init (); + open (url, true/*create an empty doc with the same url if url !exist*/); + + d->isFullyConstructed = true; +} + +//--------------------------------------------------------------------- + +kpMainWindow::kpMainWindow (kpDocument *newDoc) + : KXmlGuiWindow (0/*parent*/) +{ + init (); + setDocument (newDoc); + + d->isFullyConstructed = true; +} + +//--------------------------------------------------------------------- + + +// TODO: Move into appropriate kpMainWindow_*.cpp or another class + +// private +void kpMainWindow::readGeneralSettings () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tkpMainWindow(" << objectName () << ")::readGeneralSettings()"; +#endif + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupGeneral); + + d->configFirstTime = cfg.readEntry (kpSettingFirstTime, true); + d->configShowGrid = cfg.readEntry (kpSettingShowGrid, false); + d->configShowPath = cfg.readEntry (kpSettingShowPath, false); + d->moreEffectsDialogLastEffect = cfg.readEntry (kpSettingMoreEffectsLastEffect, 0); + + if (cfg.hasKey (kpSettingOpenImagesInSameWindow)) + { + d->configOpenImagesInSameWindow = cfg.readEntry (kpSettingOpenImagesInSameWindow, false); + } + else + { + d->configOpenImagesInSameWindow = false; +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tconfigOpenImagesInSameWindow: first time" + << " - writing default: " << d->configOpenImagesInSameWindow + << endl; +#endif + // TODO: More hidden options have to write themselves out on startup, + // not on use, to be discoverable (e.g. printing centered on page). + cfg.writeEntry (kpSettingOpenImagesInSameWindow, + d->configOpenImagesInSameWindow); + cfg.sync (); + } + + d->configPrintImageCenteredOnPage = cfg.readEntry (kpSettingPrintImageCenteredOnPage, true); + + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tGeneral Settings: firstTime=" << d->configFirstTime + << " showGrid=" << d->configShowGrid + << " showPath=" << d->configShowPath + << " moreEffectsDialogLastEffect=" << d->moreEffectsDialogLastEffect + << " openImagesInSameWindow=" << d->configOpenImagesInSameWindow + << " printImageCenteredOnPage=" << d->configPrintImageCenteredOnPage; +#endif +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::readThumbnailSettings () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tkpMainWindow(" << objectName () << ")::readThumbnailSettings()"; +#endif + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupThumbnail); + + d->configThumbnailShown = cfg.readEntry (kpSettingThumbnailShown, false); + d->configThumbnailGeometry = cfg.readEntry (kpSettingThumbnailGeometry, QRect ()); + d->configZoomedThumbnail = cfg.readEntry (kpSettingThumbnailZoomed, true); + d->configThumbnailShowRectangle = cfg.readEntry (kpSettingThumbnailShowRectangle, true); + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tThumbnail Settings: shown=" << d->configThumbnailShown + << " geometry=" << d->configThumbnailGeometry + << " zoomed=" << d->configZoomedThumbnail + << " showRectangle=" << d->configThumbnailShowRectangle + << endl; +#endif +} + +//--------------------------------------------------------------------- + +void kpMainWindow::finalizeGUI(KXMLGUIClient *client) +{ + if ( client == this ) + { + const QList menuToHide = findChildren("toolToolBarHiddenMenu"); + // should only contain one but... + foreach (KMenu *menu, menuToHide) + { + menu->menuAction()->setVisible(false); + } + } +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::init () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow(" << objectName () << ")::init()"; + QTime totalTime; totalTime.start (); +#endif + + d = new kpMainWindowPrivate; + + // + // set mainwindow properties + // + + setMinimumSize (320, 260); + setAcceptDrops (true); + + // + // read config + // + + // KConfig::readEntry() does not actually reread from disk, hence doesn't + // realize what other processes have done e.g. Settings / Show Path + KGlobal::config ()->reparseConfiguration (); + + readGeneralSettings (); + readThumbnailSettings (); + + // + // create GUI + // + setupActions (); + createStatusBar (); + createGUI (); + + createColorBox (); + createToolBox (); + + + // Let the Tool Box take all the vertical space, since it can be quite + // tall with all its tool option widgets. This also avoids occasional + // bugs like the Tool Box overlapping the Color Tool Bar. + setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); + setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); + + // no tabbed docks; does not make sense with only 2 dock widgets + setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks); + + addDockWidget(Qt::BottomDockWidgetArea, d->colorToolBar, Qt::Horizontal); + + d->scrollView = new kpViewScrollableContainer (this); + d->scrollView->setObjectName ( QLatin1String("scrollView" )); + connect (d->scrollView, SIGNAL (beganDocResize ()), + this, SLOT (slotBeganDocResize ())); + connect (d->scrollView, SIGNAL (continuedDocResize (const QSize &)), + this, SLOT (slotContinuedDocResize (const QSize &))); + connect (d->scrollView, SIGNAL (cancelledDocResize ()), + this, SLOT (slotCancelledDocResize ())); + connect (d->scrollView, SIGNAL (endedDocResize (const QSize &)), + this, SLOT (slotEndedDocResize (const QSize &))); + + connect (d->scrollView, SIGNAL (statusMessageChanged (const QString &)), + this, SLOT (slotDocResizeMessageChanged (const QString &))); + + connect (d->scrollView, SIGNAL(contentsMoved()), + this, SLOT(slotScrollViewAfterScroll())); + + setCentralWidget (d->scrollView); + + // + // set initial pos/size of GUI + // + + setAutoSaveSettings (); + + // our non-XMLGUI tools-toolbar will get initially the toolButtonStyle as + // all other toolbars, but we want to show only icons for the tools by default + // (have to do this _after_ setAutoSaveSettings as that applies the default settings) + if (d->configFirstTime) + { + d->toolToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); + + KConfigGroup cfg(KGlobal::config(), kpSettingsGroupGeneral); + + cfg.writeEntry(kpSettingFirstTime, d->configFirstTime = false); + cfg.sync(); + } + + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tall done in " << totalTime.elapsed () << "msec"; +#endif +} + +//--------------------------------------------------------------------- + +// private virtual [base KMainWindow] +void kpMainWindow::readProperties (const KConfigGroup &configGroup) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow<" << this << ">::readProperties()"; +#endif + + // No document at all? + if (!configGroup.hasKey (kpSessionSettingDocumentUrl)) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tno url - no document"; + #endif + setDocument (0); + } + // Have a document. + else + { + const KUrl url = configGroup.readEntry (kpSessionSettingDocumentUrl, + QString ()); + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\turl=" << url; + #endif + + const QSize notFromURLDocSize = + configGroup.readEntry (kpSessionSettingNotFromUrlDocumentSize, + QSize ()); + + // Is from URL? + if (notFromURLDocSize.isEmpty ()) + { + // If this fails, the empty document that kpMainWindow::kpMainWindow() + // created is left untouched. + openInternal (url, defaultDocSize (), + false/*show error message if url !exist*/); + } + // Not from URL? + else + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tnot from url; doc size=" << notFromURLDocSize; + #endif + // Either we have an empty URL or we have a "kolourpaint doesnotexist.png" + // URL. Regarding the latter case, if a file now actually exists at that + // URL, we do open it - ignoring notFromURLDocSize - to avoid putting + // the user in a situation where he might accidentally overwrite an + // existing file. + openInternal (url, notFromURLDocSize, + true/*create an empty doc with the same url if url !exist*/); + } + } + +} + +//--------------------------------------------------------------------- + +// private virtual [base KMainWindow] +// WARNING: KMainWindow API Doc says "No user interaction is allowed +// in this function!" +void kpMainWindow::saveProperties (KConfigGroup &configGroup) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow<" << this << ">::saveProperties()"; +#endif + + // No document at all? + if (!d->document) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tno url - no document"; + #endif + } + // Have a document. + else + { + // Save URL in all cases: + // + // a) d->document->isFromURL() + // b) !d->document->isFromURL() [save size in this case] + // i) No URL + // ii) URL (from "kolourpaint doesnotexist.png") + + const KUrl url = d->document->url (); + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\turl=" << url; + #endif + configGroup.writeEntry (kpSessionSettingDocumentUrl, url.url ()); + + // Not from URL e.g. "kolourpaint doesnotexist.png"? + // + // Note that "kolourpaint doesexist.png" is considered to be from + // a URL even if it was deleted in the background (hence the + // "false" arg to isFromURL()). This is because the user expects + // it to be from a URL, so when we session restore, we pop up a + // "cannot find file" dialog, instead of silently creating a new, + // blank document. + if (!d->document->isFromURL (false/*don't bother checking exists*/)) + { + // If we don't have a URL either: + // + // a) it was not modified - so we can use either width() or + // constructorWidth() (they'll be equal). + // b) the changes were discarded so we use the initial width, + // constructorWidth(). + // + // Similarly for height() and constructorHeight(). + const QSize docSize (d->document->constructorWidth (), + d->document->constructorHeight ()); + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tnot from url; doc size=" << docSize; + #endif + configGroup.writeEntry (kpSessionSettingNotFromUrlDocumentSize, docSize); + } + + + // Local session save i.e. queryClose() was not called beforehand + // (see QApplication::saveState())? + #if 0 + if (d->document->isModified ()) + { + // TODO: Implement by saving the current image to a persistent file. + // We do this instead of saving/mutating the backing image file + // as no one expects a file save on a session save without a + // "do you want to save" dialog first. + // + // I don't think any KDE application implements local session saving. + // + // --- The below code does not compile but shows you want to do --- + + // Create unique name for the document in this main window. + const KUrl tempURL = homeDir + + "kolourpaint session " + sessionID + + mainWindowPtrToString + ".png"; + // TODO: Use lossless PNG saving options. + kpDocumentSaveOptions pngSaveOptions; + + if (kpDocument::savePixmapToFile (d->document->pixmapWithSelection (), + tempURL, + pngSaveOptions, *d->document->metaInfo (), + false/*no overwrite prompt*/, + false/*no lossy prompt*/, + this)) + { + // readProperties() will still open kpSessionSettingDocumentUrl + // (as that's the expected URL) and will then add commands to: + // + // 1. Resize the document to the size of image at + // kpSessionSettingDocumentUnsavedContentsUrl, if the sizes + // differ. + // 2. Paste the kpSessionSettingDocumentUnsavedContentsUrl image + // (setting the main window's selection mode to opaque beforehand). + // + // It will then delete the file at + // kpSessionSettingDocumentUnsavedContentsUrl. + configGroup.writeEntry (kpSessionSettingDocumentUnsavedContentsUrl, + tempURL.url ()); + } + else + { + // Not much we can do - we aren't allowed to throw up a dialog. + } + } + #endif + } +} + +//--------------------------------------------------------------------- + + +kpMainWindow::~kpMainWindow () +{ + d->isFullyConstructed = false; + + // Get the kpTool to finish up. This makes sure that the kpTool destructor + // will not need to access any other class (that might be deleted before + // the destructor is called by the QObject child-deletion mechanism). + if (tool ()) + tool ()->endInternal (); + + // Delete document & views. + // Note: This will disconnects signals from the current kpTool, so kpTool + // must not be destructed yet. + setDocument (0); + + delete d->commandHistory; d->commandHistory = 0; + delete d->scrollView; d->scrollView = 0; + + delete d; d = 0; +} + +//--------------------------------------------------------------------- + + +// public +kpDocument *kpMainWindow::document () const +{ + return d->document; +} + +//--------------------------------------------------------------------- + +// public +kpDocumentEnvironment *kpMainWindow::documentEnvironment () +{ + if (!d->documentEnvironment) + d->documentEnvironment = new kpDocumentEnvironment (this); + + return d->documentEnvironment; +} + +//--------------------------------------------------------------------- + +// public +kpViewManager *kpMainWindow::viewManager () const +{ + return d->viewManager; +} + +//--------------------------------------------------------------------- + +// public +kpColorToolBar *kpMainWindow::colorToolBar () const +{ + return d->colorToolBar; +} + +//--------------------------------------------------------------------- + +// public +kpColorCells *kpMainWindow::colorCells () const +{ + return d->colorToolBar ? d->colorToolBar->colorCells () : 0; +} + +//--------------------------------------------------------------------- + +// public +kpToolToolBar *kpMainWindow::toolToolBar () const +{ + return d->toolToolBar; +} + +//--------------------------------------------------------------------- + +// public +kpCommandHistory *kpMainWindow::commandHistory () const +{ + return d->commandHistory; +} + +//--------------------------------------------------------------------- + +kpCommandEnvironment *kpMainWindow::commandEnvironment () +{ + if (!d->commandEnvironment) + d->commandEnvironment = new kpCommandEnvironment (this); + + return d->commandEnvironment; +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupActions () +{ + setupFileMenuActions (); + setupEditMenuActions (); + setupViewMenuActions (); + setupImageMenuActions (); + setupColorsMenuActions (); + setupSettingsMenuActions (); + + setupTextToolBarActions (); + setupToolActions (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableDocumentActions (bool enable) +{ + enableFileMenuDocumentActions (enable); + enableEditMenuDocumentActions (enable); + enableViewMenuDocumentActions (enable); + enableImageMenuDocumentActions (enable); + enableColorsMenuDocumentActions (enable); + enableSettingsMenuDocumentActions (enable); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setDocument (kpDocument *newDoc) +{ + //kDebug () << newDoc; + + // is it a close operation? + if (!newDoc) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tdisabling actions"; + #endif + + // sync with the bit marked "sync" below + + // TODO: Never disable the Color Box because the user should be + // able to manipulate the colors, even without a currently + // open document. + // + // We just have to make sure that signals from the Color + // Box aren't fired and received unexpectedly when there's + // no document. + Q_ASSERT (d->colorToolBar); + d->colorToolBar->setEnabled (false); + + enableTextToolBarActions (false); + } + + // Always disable the tools. + // If we decide to open a new document/mainView we want + // kpTool::begin() to be called again e.g. in case it sets the cursor. + // kpViewManager won't do this because we nuke it to avoid stale state. + enableToolsDocumentActions (false); + + if (!newDoc) + { + enableDocumentActions (false); + } + + delete d->mainView; d->mainView = 0; + slotDestroyThumbnail (); + + // viewManager will die and so will the selection + d->actionCopy->setEnabled (false); + d->actionCut->setEnabled (false); + d->actionDelete->setEnabled (false); + d->actionDeselect->setEnabled (false); + d->actionCopyToFile->setEnabled (false); + + delete d->viewManager; d->viewManager = 0; + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tdestroying document"; + kDebug () << "\t\td->document=" << d->document; +#endif + // destroy current document + delete d->document; + d->document = newDoc; + + + if (!d->lastCopyToURL.isEmpty ()) + d->lastCopyToURL.setFileName (QString()); + d->copyToFirstTime = true; + + if (!d->lastExportURL.isEmpty ()) + d->lastExportURL.setFileName (QString()); + d->exportFirstTime = true; + + + // not a close operation? + if (d->document) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\treparenting doc that may have been created into a" + << " different mainWindiow" << endl; + #endif + d->document->setEnviron (documentEnvironment ()); + + d->viewManager = new kpViewManager (this); + + d->mainView = new kpZoomedView (d->document, d->toolToolBar, d->viewManager, + 0/*buddyView*/, + d->scrollView, + d->scrollView->viewport ()); + d->mainView->setObjectName ( QLatin1String("mainView" )); + + d->viewManager->registerView (d->mainView); + d->scrollView->setView (d->mainView); + d->mainView->show (); + + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\thooking up document signals"; + #endif + + // Copy/Cut/Deselect/Delete + connect (d->document, SIGNAL (selectionEnabled (bool)), + d->actionCut, SLOT (setEnabled (bool))); + connect (d->document, SIGNAL (selectionEnabled (bool)), + d->actionCopy, SLOT (setEnabled (bool))); + connect (d->document, SIGNAL (selectionEnabled (bool)), + d->actionDelete, SLOT (setEnabled (bool))); + connect (d->document, SIGNAL (selectionEnabled (bool)), + d->actionDeselect, SLOT (setEnabled (bool))); + connect (d->document, SIGNAL (selectionEnabled (bool)), + d->actionCopyToFile, SLOT (setEnabled (bool))); + + // this code won't actually enable any actions at this stage + // (fresh document) but better safe than sorry + d->actionCopy->setEnabled (d->document->selection ()); + d->actionCut->setEnabled (d->document->selection ()); + d->actionDeselect->setEnabled (d->document->selection ()); + d->actionDelete->setEnabled (d->document->selection ()); + d->actionCopyToFile->setEnabled (d->document->selection ()); + + connect (d->document, SIGNAL (selectionEnabled (bool)), + this, SLOT (slotImageMenuUpdateDueToSelection ())); + connect (d->document, SIGNAL (selectionIsTextChanged (bool)), + this, SLOT (slotImageMenuUpdateDueToSelection ())); + + // Status bar + connect (d->document, SIGNAL (documentOpened ()), + this, SLOT (recalculateStatusBar ())); + + connect (d->document, SIGNAL (sizeChanged (const QSize &)), + this, SLOT (setStatusBarDocSize (const QSize &))); + + // Caption (url, modified) + connect (d->document, SIGNAL (documentModified ()), + this, SLOT (slotUpdateCaption ())); + connect (d->document, SIGNAL (documentOpened ()), + this, SLOT (slotUpdateCaption ())); + connect (d->document, SIGNAL (documentSaved ()), + this, SLOT (slotUpdateCaption ())); + + // File/Reload action only available with non-empty URL + connect (d->document, SIGNAL (documentSaved ()), + this, SLOT (slotEnableReload ())); + + connect (d->document, SIGNAL (documentSaved ()), + this, SLOT (slotEnableSettingsShowPath ())); + + // Command history + Q_ASSERT (d->commandHistory); + connect (d->commandHistory, SIGNAL (documentRestored ()), + this, SLOT (slotDocumentRestored ())); // caption "!modified" + connect (d->document, SIGNAL (documentSaved ()), + d->commandHistory, SLOT (documentSaved ())); + + // Sync document -> views + connect (d->document, SIGNAL (contentsChanged (const QRect &)), + d->viewManager, SLOT (updateViews (const QRect &))); + connect (d->document, SIGNAL (sizeChanged (int, int)), + d->viewManager, SLOT (adjustViewsToEnvironment ())); + + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tenabling actions"; + #endif + + // sync with the bit marked "sync" above + + Q_ASSERT (d->colorToolBar); + d->colorToolBar->setEnabled (true); + + + // Hide the text toolbar - it will be shown by kpToolText::begin() + enableTextToolBarActions (false); + + enableToolsDocumentActions (true); + + enableDocumentActions (true); + + // TODO: The thumbnail auto zoom doesn't work because it thinks its + // width == 1 when !this->isShown(). So for consistency, + // never create the thumbnail. + #if 0 + if (d->configThumbnailShown) + { + if (isShown ()) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tcreating thumbnail immediately"; + #endif + slotCreateThumbnail (); + } + // this' geometry is weird ATM + else + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tcreating thumbnail LATER"; + #endif + QTimer::singleShot (0, this, SLOT (slotCreateThumbnail ())); + } + } + #endif + } + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tupdating mainWindow elements"; +#endif + + slotImageMenuUpdateDueToSelection (); + recalculateStatusBar (); + slotUpdateCaption (); // Untitled to start with + slotEnableReload (); + slotEnableSettingsShowPath (); + + if (d->commandHistory) + d->commandHistory->clear (); + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tdocument and views ready to go!"; +#endif +} + +//--------------------------------------------------------------------- + +// private virtual [base QWidget] +void kpMainWindow::dragEnterEvent (QDragEnterEvent *e) +{ + // It's faster to test for QMimeData::hasText() first due to the + // lazy evaluation of the '||' operator. + e->setAccepted (e->mimeData ()->hasText () || + KUrl::List::canDecode (e->mimeData ()) || + kpSelectionDrag::canDecode (e->mimeData ())); +} + +//--------------------------------------------------------------------- + +// private virtual [base QWidget] +void kpMainWindow::dropEvent (QDropEvent *e) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::dropEvent" << e->pos (); +#endif + + KUrl::List urls; + + kpAbstractImageSelection *sel = kpSelectionDrag::decode (e->mimeData ()); + if (sel) + { + // TODO: How do you actually drop a selection or ordinary images on + // the clipboard)? Will this code path _ever_ execute? + sel->setTransparency (imageSelectionTransparency ()); + // TODO: drop at point like with QTextDrag below? + paste (*sel); + delete sel; + } + else if (!(urls = KUrl::List::fromMimeData (e->mimeData ())).isEmpty ()) + { + // LOTODO: kpSetOverrideCursorSaver cursorSaver (Qt::waitCursor); + // + // However, you would need to prefix all possible error/warning + // dialogs that might be called, with Qt::arrowCursor e.g. in + // kpDocument and probably a lot more places. + foreach (const KUrl &u, urls) + open (u); + } + else if (e->mimeData ()->hasText ()) + { + const QString text = e->mimeData ()->text (); + + QPoint selTopLeft = KP_INVALID_POINT; + const QPoint globalPos = QWidget::mapToGlobal (e->pos ()); + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tpos toGlobal=" << globalPos; + #endif + + kpView *view = 0; + + if (d->viewManager) + { + view = d->viewManager->viewUnderCursor (); + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tviewUnderCursor=" << view; + #endif + if (!view) + { + // HACK: see kpViewManager::setViewUnderCursor() to see why + // it's not reliable + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tattempting to discover view"; + + if (d->mainView && d->scrollView) + { + kDebug () << "\t\t\tmainView->globalRect=" + << kpWidgetMapper::toGlobal (d->mainView, d->mainView->rect ()) + << " scrollView->globalRect=" + << kpWidgetMapper::toGlobal (d->scrollView, + QRect (0, 0, + d->scrollView->viewport()->width (), + d->scrollView->viewport()->height ())) + << endl; + } + #endif + if (d->thumbnailView && + kpWidgetMapper::toGlobal (d->thumbnailView, d->thumbnailView->rect ()) + .contains (globalPos)) + { + // TODO: Code will never get executed. + // Thumbnail doesn't accept drops. + view = d->thumbnailView; + } + else if (d->mainView && + kpWidgetMapper::toGlobal (d->mainView, d->mainView->rect ()) + .contains (globalPos) && + d->scrollView && + kpWidgetMapper::toGlobal (d->scrollView, + QRect (0, 0, + d->scrollView->viewport()->width (), + d->scrollView->viewport()->height ())) + .contains (globalPos)) + { + view = d->mainView; + } + } + } + + if (view) + { + const QPoint viewPos = view->mapFromGlobal (globalPos); + const QPoint docPoint = view->transformViewToDoc (viewPos); + + // viewUnderCursor() is hacky and can return a view when we aren't + // over one thanks to drags. + if (d->document && d->document->rect ().contains (docPoint)) + { + selTopLeft = docPoint; + + // TODO: In terms of doc pixels, would be inconsistent behaviour + // based on zoomLevel of view. + // selTopLeft -= QPoint (-view->selectionResizeHandleAtomicSize (), + // -view->selectionResizeHandleAtomicSize ()); + } + } + + pasteText (text, true/*force new text selection*/, selTopLeft); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotScrollViewAfterScroll () +{ + // OPT: Why can't this be moved into kpViewScrollableContainer::slotDragScroll(), + // grouping all drag-scroll-related repaints, which would potentially avoid + // double repainting? + if (tool ()) + { + tool ()->somethingBelowTheCursorChanged (); + } +} + +//--------------------------------------------------------------------- + +// private virtual [base QWidget] +void kpMainWindow::moveEvent (QMoveEvent * /*e*/) +{ + if (d->thumbnail) + { + // Disabled because it lags too far behind the mainWindow + // d->thumbnail->move (d->thumbnail->pos () + (e->pos () - e->oldPos ())); + + notifyThumbnailGeometryChanged (); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotUpdateCaption () +{ + if (d->document) + { + setCaption (d->configShowPath ? d->document->prettyUrl () + : d->document->prettyFilename (), + d->document->isModified ()); + } + else + { + setCaption (QString(), false); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotDocumentRestored () +{ + if (d->document) + d->document->setModified (false); + slotUpdateCaption (); +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/mainWindow/kpMainWindow.h b/kolourpaint/mainWindow/kpMainWindow.h new file mode 100644 index 00000000..d8a2b47a --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow.h @@ -0,0 +1,698 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_MAIN_WINDOW_H +#define KP_MAIN_WINDOW_H + + +#include +#include + +#include +#include +#include + + +class QAction; +class QActionGroup; +class QDragEnterEvent; +class QDropEvent; +class QMenu; +class QMoveEvent; +class QPoint; +class QRect; +class QSize; +class QStringList; + +class KConfigGroup; +class KFontAction; +class KFontSizeAction; +class KSelectAction; +class KToggleAction; +class KToolBar; +class QPrinter; +class KRecentFilesAction; +class KScanDialog; +class KToggleFullScreenAction; + +class kpColor; +class kpColorCells; +class kpColorToolBar; +class kpCommand; +class kpCommandEnvironment; +class kpCommandHistory; +class kpDocument; +class kpDocumentEnvironment; +class kpDocumentMetaInfo; +class kpDocumentSaveOptions; +class kpViewManager; +class kpViewScrollableContainer; +class kpImageSelectionTransparency; +class kpSqueezedTextLabel; +class kpTextStyle; +class kpThumbnail; +class kpThumbnailView; +class kpTool; +class kpToolEnvironment; +class kpToolSelectionEnvironment; +class kpToolText; +class kpToolToolBar; +class kpTransformDialogEnvironment; +class kpZoomedView; + + +class kpMainWindow : public KXmlGuiWindow +{ +Q_OBJECT + +public: + // Opens a new window with a blank document. + kpMainWindow (); + + // Opens a new window with the document specified by + // or creates a blank document if could not be opened. + kpMainWindow (const KUrl &url); + + // Opens a new window with the document + // ( can be 0 although this would result in a new + // window without a document at all). + kpMainWindow (kpDocument *newDoc); + + virtual void finalizeGUI(KXMLGUIClient *client); + +private: + void readGeneralSettings (); + void readThumbnailSettings (); + + void init (); + + // (only called for restoring a previous session e.g. starting KDE with + // a previously saved session; it's not called on normal KolourPaint + // startup) + virtual void readProperties (const KConfigGroup &configGroup); + // (only called for saving the current session e.g. logging out of KDE + // with the KolourPaint window open; it's not called on normal KolourPaint + // exit) + virtual void saveProperties (KConfigGroup &configGroup); + +public: + ~kpMainWindow (); + +public: + kpDocument *document () const; + kpDocumentEnvironment *documentEnvironment (); + kpViewManager *viewManager () const; + kpColorToolBar *colorToolBar () const; + kpColorCells *colorCells () const; + kpToolToolBar *toolToolBar () const; + kpCommandHistory *commandHistory () const; + kpCommandEnvironment *commandEnvironment (); + +private: + void setupActions (); + void enableDocumentActions (bool enable = true); + + void setDocument (kpDocument *newDoc); + + virtual void dragEnterEvent (QDragEnterEvent *e); + virtual void dropEvent (QDropEvent *e); + virtual void moveEvent (QMoveEvent *e); + +private slots: + void slotScrollViewAfterScroll (); + void slotUpdateCaption (); + void slotDocumentRestored (); + + +// +// Tools +// + +private: + kpToolSelectionEnvironment *toolSelectionEnvironment (); + kpToolEnvironment *toolEnvironment (); + + void setupToolActions (); + void createToolBox (); + void enableToolsDocumentActions (bool enable = true); + +private slots: + void updateToolOptionPrevNextActionsEnabled (); + void updateActionDrawOpaqueChecked (); +private: + void updateActionDrawOpaqueEnabled (); + +public: + QActionGroup *toolsActionGroup (); + + kpTool *tool () const; + + bool toolHasBegunShape () const; + bool toolIsASelectionTool (bool includingTextTool = true) const; + bool toolIsTextTool () const; + +private: + // Ends the current shape. If there is no shape currently being drawn, + // it does nothing. + // + // In general, call this at the start of every kpMainWindow slot, + // directly invoked by the _user_ (by activating an action or via another + // way), so that: + // + // 1. The document contains the pixels of that shape: + // + // Most tools have the shape, currently being drawn, layered above the + // document as a kpTempImage. In other words, the document does not + // yet contain the pixels of that shape. By ending the shape, the layer + // is pushed down onto the document so that it now contains those + // pixels. Your slot can now safely read the document as it's now + // consistent with what's on the screen. + // + // For example, consider the case where a line is being dragged out and + // CTRL+I is pressed to invert the image, while the mouse is still held + // down. The CTRL+I invert code (kpMainWindow::slotInvertColors()) must + // push the line kpTempImage onto the document before the invert can + // meaningfully proceed (else the invert will see the state of the document + // before the line was dragged out). + // + // Note that selection layers are not pushed down by this method. + // This is a feature, not a bug. The user would be annoyed if e.g. + // slotSave() happened to push down the selection. Use + // kpDocument::imageWithSelection() to get around this problem. You + // should still call toolEndShape() even if a selection is active + // -- this ends selection "shapes", which are actually things like + // selection moves or smearing operations, rather than the selections + // themselves. + // + // AND/OR: + // + // 2. The current tool is no longer in a drawing state: + // + // If your slot is going to bring up a new main window or modal dialog + // or at least some widget that acquires mouse or keyboard focus, this + // could confuse the tool if the tool is in the middle of a drawing + // operation. + // + // Do not call this in slots not invoked by the user. For instance, + // calling this method in response to an internal timer tick would be + // wrong. The user's drawing operation would unexpectedly finish and + // this would bewilder and irritate the user. + // + // TODO: Help / KolourPaint Handbook does not call this. I'm sure there + // are a few other actions that don't call this but should. + void toolEndShape (); + +public: + kpImageSelectionTransparency imageSelectionTransparency () const; + // The drawing background color is set to .transparentColor() + // if the is in Transparent mode or if + // is true (not the default). [x] + // + // If is in Opaque mode and is false, + // the background color is not changed because: + // + // 1. It is ignored by the selection in Opaque mode anyway. + // 2. This avoids irritating the user with an unnecessary background + // color change. + // + // The only case where you should set to true is in + // kpToolImageSelectionTransparencyCommand to ensure that the state + // is identical to when the command was constructed. + // Later: I don't think setting it to true is ever necessary since: + // + // 1. The background color only counts in Transparent mode. + // + // 2. Any kpToolImageSelectionTransparencyCommand that switches to + // Transparent mode will automatically set the background + // color due to the first part of [x] anyway. + // + // The other fields of are copied into the main window + // as expected. + void setImageSelectionTransparency (const kpImageSelectionTransparency &transparency, + bool forceColorChange = false); + int settingImageSelectionTransparency () const; + +private slots: + void slotToolSelected (kpTool *tool); + +private: + void readLastTool (); + int toolNumber () const; + void saveLastTool (); + +private: + bool maybeDragScrollingMainView () const; +private slots: + bool slotDragScroll (const QPoint &docPoint, + const QPoint &docLastPoint, + int zoomLevel, + bool *didSomething); + bool slotEndDragScroll (); + +private slots: + void slotBeganDocResize (); + void slotContinuedDocResize (const QSize &size); + void slotCancelledDocResize (); + void slotEndedDocResize (const QSize &size); + + void slotDocResizeMessageChanged (const QString &string); + +private slots: + void slotActionPrevToolOptionGroup1 (); + void slotActionNextToolOptionGroup1 (); + void slotActionPrevToolOptionGroup2 (); + void slotActionNextToolOptionGroup2 (); + + void slotActionDrawOpaqueToggled (); + void slotActionDrawColorSimilarity (); + +public slots: + void slotToolRectSelection(); + void slotToolEllipticalSelection(); + void slotToolFreeFormSelection(); + void slotToolText(); + +// +// File Menu +// + +private: + void setupFileMenuActions (); + void enableFileMenuDocumentActions (bool enable = true); + + void addRecentURL (const KUrl &url); + +private slots: + void slotNew (); + +private: + QSize defaultDocSize () const; + void saveDefaultDocSize (const QSize &size); + +private: + bool shouldOpen (); + void setDocumentChoosingWindow (kpDocument *doc); + +private: + kpDocument *openInternal (const KUrl &url, + const QSize &fallbackDocSize, + bool newDocSameNameIfNotExist); + // Same as above except that it: + // + // 1. Assumes a default fallback document size. + // 2. If the URL is successfully opened (with the special exception of + // the "kolourpaint doesnotexist.png" case), it is bubbled up to the + // top in the Recent Files Action. + // + // As a result of this behavior, this should only be called in response + // to a user open request e.g. File / Open or "kolourpaint doesexist.png". + // It should not be used for session restore - in that case, it does not + // make sense to bubble the Recent Files list. + bool open (const KUrl &url, bool newDocSameNameIfNotExist = false); + + KUrl::List askForOpenURLs(const QString &caption, + bool allowMultipleURLs = true); + +private slots: + void slotOpen (); + void slotOpenRecent (const KUrl &url); + + void slotScan (); + void slotScanned (const QImage &image, int); + + void slotScreenshot(); + void slotMakeScreenshot(); + + void slotProperties (); + + bool save (bool localOnly = false); + bool slotSave (); + +private: + KUrl askForSaveURL (const QString &caption, + const QString &startURL, + const kpImage &imageToBeSaved, + const kpDocumentSaveOptions &startSaveOptions, + const kpDocumentMetaInfo &docMetaInfo, + const QString &forcedSaveOptionsGroup, + bool localOnly, + kpDocumentSaveOptions *chosenSaveOptions, + bool isSavingForFirstTime, + bool *allowOverwritePrompt, + bool *allowLossyPrompt); + +private slots: + bool saveAs (bool localOnly = false); + bool slotSaveAs (); + + bool slotExport (); + + void slotEnableReload (); + bool slotReload (); + +private: + void sendDocumentNameToPrinter (QPrinter *printer); + void sendImageToPrinter (QPrinter *printer, bool showPrinterSetupDialog); + +private slots: + void slotPrint (); + void slotPrintPreview (); + + void slotMail (); + + bool queryCloseDocument (); + virtual bool queryClose (); + + void slotClose (); + void slotQuit (); + + +// +// Edit Menu +// + +private: + void setupEditMenuActions (); + void enableEditMenuDocumentActions (bool enable = true); + +public: + QMenu *selectionToolRMBMenu (); + +private slots: + void slotCut (); + void slotCopy (); + void slotEnablePaste (); +private: + QRect calcUsefulPasteRect (int imageWidth, int imageHeight); + // (it is possible to paste a selection border i.e. a selection with no content) + void paste (const kpAbstractSelection &sel, + bool forceTopLeft = false); +public: + // ( is ignored if is empty) + void pasteText (const QString &text, + bool forceNewTextSelection = false, + const QPoint &newTextSelectionTopLeft = KP_INVALID_POINT); + void pasteTextAt (const QString &text, const QPoint &point, + // Allow tiny adjustment of so that mouse + // pointer is not exactly on top of the topLeft of + // any new text selection (so that it doesn't look + // weird by being on top of a resize handle just after + // a paste). + bool allowNewTextSelectionPointShift = false); +public slots: + void slotPaste (); +private slots: + void slotPasteInNewWindow (); +public slots: + void slotDelete (); + + void slotSelectAll (); +private: + void addDeselectFirstCommand (kpCommand *cmd); +public slots: + void slotDeselect (); +private slots: + void slotCopyToFile (); + void slotPasteFromFile (); + + +// +// View Menu +// + +private: + void setupViewMenuActions (); + + bool viewMenuDocumentActionsEnabled () const; + void enableViewMenuDocumentActions (bool enable = true); + void actionShowGridUpdate (); + void updateMainViewGrid (); + QRect mapToGlobal (const QRect &rect) const; + QRect mapFromGlobal (const QRect &rect) const; + +private slots: + void slotShowGridToggled (); + + +// +// View Menu - Zoom +// + +private: + void setupViewMenuZoomActions (); + void enableViewMenuZoomDocumentActions (bool enable); + + void sendZoomListToActionZoom (); + + void zoomToPre (int zoomLevel); + void zoomToPost (); + +public: + void zoomTo (int zoomLevel, bool centerUnderCursor = false); + void zoomToRect (const QRect &normalizedDocRect, + bool accountForGrips, + bool careAboutWidth, bool careAboutHeight); + +public slots: + void slotActualSize (); + void slotFitToPage (); + void slotFitToWidth (); + void slotFitToHeight (); + +public: + void zoomIn (bool centerUnderCursor = false); + void zoomOut (bool centerUnderCursor = false); + +public slots: + void slotZoomIn (); + void slotZoomOut (); + +private: + void zoomAccordingToZoomAction (bool centerUnderCursor = false); + +private slots: + void slotZoom (); + + +// +// View Menu - Thumbnail +// + +private: + void setupViewMenuThumbnailActions (); + void enableViewMenuThumbnailDocumentActions (bool enable); + +private slots: + void slotDestroyThumbnail (); + void slotDestroyThumbnailInitatedByUser (); + void slotCreateThumbnail (); + +public: + void notifyThumbnailGeometryChanged (); + +private slots: + void slotSaveThumbnailGeometry (); + void slotShowThumbnailToggled (); + void updateThumbnailZoomed (); + void slotZoomedThumbnailToggled (); + void slotThumbnailShowRectangleToggled (); + +private: + void enableViewZoomedThumbnail (bool enable = true); + void enableViewShowThumbnailRectangle (bool enable = true); + void enableThumbnailOptionActions (bool enable = true); + void createThumbnailView (); + void destroyThumbnailView (); + void updateThumbnail (); + + +// +// Image Menu +// + +private: + kpTransformDialogEnvironment *transformDialogEnvironment (); + + bool isSelectionActive () const; + bool isTextSelection () const; + + QString autoCropText () const; + + void setupImageMenuActions (); + void enableImageMenuDocumentActions (bool enable = true); + +private slots: + void slotImageMenuUpdateDueToSelection (); + +public: + kpColor backgroundColor (bool ofSelection = false) const; + void addImageOrSelectionCommand (kpCommand *cmd, + bool addSelCreateCmdIfSelAvail = true, + bool addSelContentCmdIfSelAvail = true); + +private slots: + void slotResizeScale (); +public slots: + void slotCrop (); +private slots: + void slotAutoCrop (); + void slotFlip (); + void slotMirror (); + + void slotRotate (); + void slotRotate270 (); + void slotRotate90 (); + + void slotSkew (); + void slotConvertToBlackAndWhite (); + void slotConvertToGrayscale (); + void slotInvertColors (); + void slotClear (); + void slotMoreEffects (); + + +// +// Colors Menu +// + +private: + void setupColorsMenuActions (); + void createColorBox (); + void enableColorsMenuDocumentActions (bool enable); +private slots: + void slotUpdateColorsDeleteRowActionEnabled (); + +private: + void deselectActionColorsKDE (); + + bool queryCloseColors (); + +private: + void openDefaultColors (); +private slots: + void slotColorsDefault (); + +private: + bool openKDEColors (const QString &name); +private slots: + void slotColorsKDE (); + +private: + bool openColors (const KUrl &url); +private slots: + void slotColorsOpen (); + + void slotColorsReload (); + + bool slotColorsSave (); + bool slotColorsSaveAs (); + + void slotColorsAppendRow (); + void slotColorsDeleteRow (); + + +// +// Settings Menu +// + +private: + void setupSettingsMenuActions (); + void enableSettingsMenuDocumentActions (bool enable = true); + +private slots: + void slotFullScreen (); + + void slotEnableSettingsShowPath (); + void slotShowPathToggled (); + + void slotKeyBindings (); + +// +// Status Bar +// + +private: + enum + { + StatusBarItemShapePoints, + StatusBarItemShapeSize, + StatusBarItemDocSize, + StatusBarItemDocDepth, + StatusBarItemZoom + }; + + void addPermanentStatusBarItem (int id, int maxTextLen); + void createStatusBar (); + + void setStatusBarDocDepth (int depth = 0); + +private slots: + void setStatusBarMessage (const QString &message = QString()); + void setStatusBarShapePoints (const QPoint &startPoint = KP_INVALID_POINT, + const QPoint &endPoint = KP_INVALID_POINT); + void setStatusBarShapeSize (const QSize &size = KP_INVALID_SIZE); + void setStatusBarDocSize (const QSize &size = KP_INVALID_SIZE); + void setStatusBarZoom (int zoom = 0); + + void recalculateStatusBarMessage (); + void recalculateStatusBarShape (); + + void recalculateStatusBar (); + + +// +// Text ToolBar +// + +private: + void setupTextToolBarActions (); + void readAndApplyTextSettings (); + +public: + void enableTextToolBarActions (bool enable = true); + +private slots: + void slotTextFontFamilyChanged (); + void slotTextFontSizeChanged (); + void slotTextBoldChanged (); + void slotTextItalicChanged (); + void slotTextUnderlineChanged (); + void slotTextStrikeThruChanged (); + +public: + KToolBar *textToolBar (); + bool isTextStyleBackgroundOpaque () const; + kpTextStyle textStyle () const; + void setTextStyle (const kpTextStyle &textStyle_); + int settingTextStyle () const; + +private: + struct kpMainWindowPrivate *d; +}; + +#endif // KP_MAIN_WINDOW_H diff --git a/kolourpaint/mainWindow/kpMainWindowPrivate.h b/kolourpaint/mainWindow/kpMainWindowPrivate.h new file mode 100644 index 00000000..17112c00 --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindowPrivate.h @@ -0,0 +1,428 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2014 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpMainWindowPrivate_H +#define kpMainWindowPrivate_H + + +#define DEBUG_KP_MAIN_WINDOW 0 + + +#include + + +class QAction; +class QActionGroup; + +class KSelectAction; +class KToggleAction; +class KAction; +class KSqueezedTextLabel; + +class kpCommandEnvironment; +class kpDocumentEnvironment; +class kpToolSelectionEnvironment; +class kpTransformDialogEnvironment; + + +struct kpMainWindowPrivate +{ + kpMainWindowPrivate () + : isFullyConstructed(false), + scrollView(0), + mainView(0), + thumbnail(0), + thumbnailView(0), + document(0), + viewManager(0), + colorToolBar(0), + toolToolBar(0), + commandHistory(0), + + configFirstTime(false), + configShowGrid(false), + configShowPath(false), + configThumbnailShown(false), + configZoomedThumbnail(false), + + documentEnvironment(0), + commandEnvironment(0), + + // Tools + + toolSelectionEnvironment(0), + toolsActionGroup(0), + + toolSpraycan(0), + toolBrush(0), + toolColorEraser(0), + toolColorPicker(0), + toolCurve(0), + toolEllipse(0), + toolEllipticalSelection(0), + toolEraser(0), + toolFloodFill(0), + toolFreeFormSelection(0), + toolLine(0), + toolPen(0), + toolPolygon(0), + toolPolyline(0), + toolRectangle(0), + toolRectSelection(0), + toolRoundedRectangle(0), + toolZoom(0), + toolText(0), + + lastToolNumber(0), + toolActionsEnabled(false), + actionPrevToolOptionGroup1(0), + actionNextToolOptionGroup1(0), + actionPrevToolOptionGroup2(0), + actionNextToolOptionGroup2(0), + + settingImageSelectionTransparency(0), + + docResizeWidth(0), + docResizeHeight(0), + docResizeToBeCompleted(false), + + configOpenImagesInSameWindow(false), + configPrintImageCenteredOnPage(false), + + actionNew(0), + actionOpen(0), + actionOpenRecent(0), + actionScan(0), + actionScreenshot(0), + actionProperties(0), + actionSave(0), + actionSaveAs(0), + actionExport(0), + actionReload(0), + actionPrint(0), + actionPrintPreview(0), + actionMail(0), + actionClose(0), + actionQuit(0), + + scanDialog(0), + + exportFirstTime(false), + + // Edit Menu + + editMenuDocumentActionsEnabled(false), + + actionUndo(0), + actionRedo(0), + actionCut(0), + actionCopy(0), + actionPaste(0), + actionPasteInNewWindow(0), + actionDelete(0), + actionSelectAll(0), + actionDeselect(0), + actionCopyToFile(0), + actionPasteFromFile(0), + + copyToFirstTime(false), + + // View Menu + + configThumbnailShowRectangle(false), + actionShowThumbnailRectangle(0), + + viewMenuDocumentActionsEnabled(false), + + actionActualSize(0), + actionFitToPage(0), + actionFitToWidth(0), + actionFitToHeight(0), + actionZoomIn(0), + actionZoomOut(0), + actionZoom(0), + actionShowGrid(0), + actionShowThumbnail(0), + actionZoomedThumbnail(0), + + thumbnailSaveConfigTimer(0), + + // Image Menu + + transformDialogEnvironment(0), + + imageMenuDocumentActionsEnabled(false), + + actionResizeScale(0), + actionCrop(0), + actionAutoCrop(0), + actionFlip(0), + actionMirror(0), + actionRotate(0), + actionRotateLeft(0), + actionRotateRight(0), + actionSkew(0), + actionConvertToBlackAndWhite(0), + actionConvertToGrayscale(0), + actionMoreEffects(0), + actionInvertColors(0), + actionClear(0), + + actionDrawOpaque(0), + actionDrawColorSimilarity(0), + + moreEffectsDialogLastEffect(0), + + // Colors Menu + + colorMenuDocumentActionsEnabled(false), + + actionColorsDefault(0), + actionColorsKDE(0), + actionColorsOpen(0), + actionColorsReload(0), + actionColorsSave(0), + actionColorsSaveAs(0), + actionColorsAppendRow(0), + actionColorsDeleteRow(0), + + // Settings Menu + + actionShowPath(0), + actionKeyBindings(0), + actionConfigureToolbars(0), + actionConfigure(0), + actionFullScreen(0), + + // Status Bar + + statusBarCreated(false), + statusBarMessageLabel(0), + statusBarShapeLastPointsInitialised(false), + statusBarShapeLastSizeInitialised(false), + + // Text ToolBar + + actionTextFontFamily(0), + actionTextFontSize(0), + actionTextBold(0), + actionTextItalic(0), + actionTextUnderline(0), + actionTextStrikeThru(0), + settingTextStyle(0), + textOldFontSize(0) + { + } + + bool isFullyConstructed; + + kpViewScrollableContainer *scrollView; + kpZoomedView *mainView; + kpThumbnail *thumbnail; + kpThumbnailView *thumbnailView; + kpDocument *document; + kpViewManager *viewManager; + kpColorToolBar *colorToolBar; + kpToolToolBar *toolToolBar; + kpCommandHistory *commandHistory; + + bool configFirstTime; + bool configShowGrid; + bool configShowPath; + + bool configThumbnailShown; + QRect configThumbnailGeometry; + bool configZoomedThumbnail; + + kpDocumentEnvironment *documentEnvironment; + kpCommandEnvironment *commandEnvironment; + + // + // Tools + // + + kpToolSelectionEnvironment *toolSelectionEnvironment; + QActionGroup *toolsActionGroup; + + kpTool *toolSpraycan, *toolBrush, + *toolColorEraser, *toolColorPicker, + *toolCurve, *toolEllipse, + *toolEllipticalSelection, *toolEraser, + *toolFloodFill, *toolFreeFormSelection, + *toolLine, *toolPen, *toolPolygon, + *toolPolyline, *toolRectangle, *toolRectSelection, + *toolRoundedRectangle, *toolZoom; + kpToolText *toolText; + + QList tools; + int lastToolNumber; + + bool toolActionsEnabled; + KAction *actionPrevToolOptionGroup1, + *actionNextToolOptionGroup1, + *actionPrevToolOptionGroup2, + *actionNextToolOptionGroup2; + + int settingImageSelectionTransparency; + + int docResizeWidth, docResizeHeight; + bool docResizeToBeCompleted; + + // + // File Menu + // + + bool configOpenImagesInSameWindow, configPrintImageCenteredOnPage; + + QAction *actionNew, *actionOpen; + KRecentFilesAction *actionOpenRecent; + KAction *actionScan, *actionScreenshot, *actionProperties, + *actionSave, *actionSaveAs, *actionExport, + *actionReload, + *actionPrint, *actionPrintPreview, + *actionMail, + *actionClose, *actionQuit; + + KScanDialog *scanDialog; + + KUrl lastExportURL; + kpDocumentSaveOptions lastExportSaveOptions; + bool exportFirstTime; + + // + // Edit Menu + // + + bool editMenuDocumentActionsEnabled; + + KAction *actionUndo, *actionRedo, + *actionCut, *actionCopy, + *actionPaste, *actionPasteInNewWindow, + *actionDelete, + *actionSelectAll, *actionDeselect, + *actionCopyToFile, *actionPasteFromFile; + + KUrl lastCopyToURL; + kpDocumentSaveOptions lastCopyToSaveOptions; + bool copyToFirstTime; + + // + // View Menu + // + + bool configThumbnailShowRectangle; + KToggleAction *actionShowThumbnailRectangle; + + bool viewMenuDocumentActionsEnabled; + + QAction *actionActualSize, + *actionFitToPage, *actionFitToWidth, *actionFitToHeight, + *actionZoomIn, *actionZoomOut; + KSelectAction *actionZoom; + KToggleAction *actionShowGrid, + *actionShowThumbnail, *actionZoomedThumbnail; + + QList zoomList; + + QTimer *thumbnailSaveConfigTimer; + + // + // Image Menu + // + + kpTransformDialogEnvironment *transformDialogEnvironment; + + bool imageMenuDocumentActionsEnabled; + + KAction *actionResizeScale, + *actionCrop, *actionAutoCrop, + *actionFlip, *actionMirror, + *actionRotate, *actionRotateLeft, *actionRotateRight, + *actionSkew, + *actionConvertToBlackAndWhite, *actionConvertToGrayscale, + *actionMoreEffects, + *actionInvertColors, *actionClear; + + // Implemented in kpMainWindow_Tools.cpp, not kpImageWindow_Image.cpp + // since they're really setting tool options. + KToggleAction *actionDrawOpaque; + QAction *actionDrawColorSimilarity; + + int moreEffectsDialogLastEffect; + + // + // Colors Menu + // + + bool colorMenuDocumentActionsEnabled; + + QAction *actionColorsDefault; + KSelectAction *actionColorsKDE; + QAction *actionColorsOpen, *actionColorsReload; + + QAction *actionColorsSave, *actionColorsSaveAs; + + QAction *actionColorsAppendRow; + QAction *actionColorsDeleteRow; + + // + // Settings Menu + // + + KToggleAction *actionShowPath; + QAction *actionKeyBindings, *actionConfigureToolbars, *actionConfigure; + KToggleFullScreenAction *actionFullScreen; + + // + // Status Bar + // + + bool statusBarCreated; + KSqueezedTextLabel *statusBarMessageLabel; + + bool statusBarShapeLastPointsInitialised; + QPoint statusBarShapeLastStartPoint, statusBarShapeLastEndPoint; + bool statusBarShapeLastSizeInitialised; + QSize statusBarShapeLastSize; + + // + // Text ToolBar + // + + KFontAction *actionTextFontFamily; + KFontSizeAction *actionTextFontSize; + KToggleAction *actionTextBold, *actionTextItalic, + *actionTextUnderline, *actionTextStrikeThru; + + int settingTextStyle; + QString textOldFontFamily; + int textOldFontSize; +}; + + +#endif // kpMainWindowPrivate_H diff --git a/kolourpaint/mainWindow/kpMainWindow_Colors.cpp b/kolourpaint/mainWindow/kpMainWindow_Colors.cpp new file mode 100644 index 00000000..8a4890ef --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow_Colors.cpp @@ -0,0 +1,454 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +static QStringList KDEColorCollectionNames () +{ + return kpColorCollection::installedCollections (); +} + + +// private +void kpMainWindow::setupColorsMenuActions () +{ + KActionCollection *ac = actionCollection (); + + + d->actionColorsDefault = ac->addAction ("colors_default"); + d->actionColorsDefault->setText (i18n ("Use KolourPaint Defaults")); + connect (d->actionColorsDefault, SIGNAL (triggered (bool)), + SLOT (slotColorsDefault ())); + + d->actionColorsKDE = ac->add ("colors_kde"); + d->actionColorsKDE->setText (i18nc ("@item:inmenu colors", "Use KDE's")); + // TODO: Will this slot be called spuriously if there are no colors + // installed? + connect (d->actionColorsKDE, SIGNAL (triggered (QAction *)), + SLOT (slotColorsKDE ())); + foreach (const QString &colName, ::KDEColorCollectionNames ()) + d->actionColorsKDE->addAction (colName); + + d->actionColorsOpen = ac->addAction ("colors_open"); + d->actionColorsOpen->setText (i18nc ("@item:inmenu colors", "&Open...")); + connect (d->actionColorsOpen, SIGNAL (triggered (bool)), + SLOT (slotColorsOpen ())); + + d->actionColorsReload = ac->addAction ("colors_reload"); + d->actionColorsReload->setText (i18nc ("@item:inmenu colors", "Reloa&d")); + connect (d->actionColorsReload, SIGNAL (triggered (bool)), + SLOT (slotColorsReload ())); + + + d->actionColorsSave = ac->addAction ("colors_save"); + d->actionColorsSave->setText (i18nc ("@item:inmenu colors", "&Save")); + connect (d->actionColorsSave, SIGNAL (triggered (bool)), + SLOT (slotColorsSave ())); + + d->actionColorsSaveAs = ac->addAction ("colors_save_as"); + d->actionColorsSaveAs->setText (i18nc ("@item:inmenu colors", "Save &As...")); + connect (d->actionColorsSaveAs, SIGNAL (triggered (bool)), + SLOT (slotColorsSaveAs ())); + + + d->actionColorsAppendRow = ac->addAction ("colors_append_row"); + d->actionColorsAppendRow->setText (i18nc ("@item:inmenu colors", "Add Row")); + connect (d->actionColorsAppendRow, SIGNAL (triggered (bool)), + SLOT (slotColorsAppendRow ())); + + d->actionColorsDeleteRow = ac->addAction ("colors_delete_row"); + d->actionColorsDeleteRow->setText (i18nc ("@item:inmenu colors", "Delete Last Row")); + connect (d->actionColorsDeleteRow, SIGNAL (triggered (bool)), + SLOT (slotColorsDeleteRow ())); + + + enableColorsMenuDocumentActions (false); +} + +// private +void kpMainWindow::createColorBox () +{ + d->colorToolBar = new kpColorToolBar (i18n ("Color Box"), this); + + // (needed for QMainWindow::saveState()) + d->colorToolBar->setObjectName ( QLatin1String("Color Box" )); + + connect (colorCells (), SIGNAL (rowCountChanged (int)), + SLOT (slotUpdateColorsDeleteRowActionEnabled ())); +} + +// private +void kpMainWindow::enableColorsMenuDocumentActions (bool enable) +{ + d->actionColorsDefault->setEnabled (enable); + d->actionColorsKDE->setEnabled (enable); + d->actionColorsOpen->setEnabled (enable); + d->actionColorsReload->setEnabled (enable); + + d->actionColorsSave->setEnabled (enable); + d->actionColorsSaveAs->setEnabled (enable); + + d->actionColorsAppendRow->setEnabled (enable); + + d->colorMenuDocumentActionsEnabled = enable; + + slotUpdateColorsDeleteRowActionEnabled (); +} + +// private slot +void kpMainWindow::slotUpdateColorsDeleteRowActionEnabled () +{ + // Currently, this is always enabled since kpColorCells guarantees that + // there will be at least one row of cells (which might all be of the + // invalid color). + // + // But this method is left here for future extensibility. + d->actionColorsDeleteRow->setEnabled ( + d->colorMenuDocumentActionsEnabled && (colorCells ()->rowCount () > 0)); +} + + +// Used in 2 situations: +// +// 1. User opens a color without using the "Use KDE's" submenu. +// 2. User attempts to open a color using the "Use KDE's" submenu but the +// opening fails. +// +// TODO: Maybe we could put the 3 actions (for different ways of opening +// colors) in an exclusive group -- this might elminate the need for +// this hack. +// +// private +void kpMainWindow::deselectActionColorsKDE () +{ + d->actionColorsKDE->setCurrentItem (-1); +} + + +// private +bool kpMainWindow::queryCloseColors () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::queryCloseColors() colorCells.modified=" + << colorCells ()->isModified (); +#endif + + toolEndShape (); + + if (!colorCells ()->isModified ()) + return true; // ok to close + + int result = KMessageBox::Cancel; + + + if (!colorCells ()->url ().isEmpty ()) + { + result = KMessageBox::warningYesNoCancel (this, + i18n ("The color palette \"%1\" has been modified.\n" + "Do you want to save it?", + kpUrlFormatter::PrettyFilename (colorCells ()->url ())), + QString ()/*caption*/, + KStandardGuiItem::save (), KStandardGuiItem::discard ()); + } + else + { + const QString name = colorCells ()->colorCollection ()->name (); + if (!name.isEmpty ()) + { + result = KMessageBox::warningYesNoCancel (this, + i18n ("The KDE color palette \"%1\" has been modified.\n" + "Do you want to save it to a file?", + name), + QString ()/*caption*/, + KStandardGuiItem::save (), KStandardGuiItem::discard ()); + } + else + { + result = KMessageBox::warningYesNoCancel (this, + i18n ("The default color palette has been modified.\n" + "Do you want to save it to a file?"), + QString ()/*caption*/, + KStandardGuiItem::save (), KStandardGuiItem::discard ()); + } + } + + switch (result) + { + case KMessageBox::Yes: + return slotColorsSave (); // close only if save succeeds + case KMessageBox::No: + return true; // close without saving + default: + return false; // don't close current doc + } +} + + +// private +void kpMainWindow::openDefaultColors () +{ + colorCells ()->setColorCollection ( + kpColorCells::DefaultColorCollection ()); +} + +// private slot +void kpMainWindow::slotColorsDefault () +{ + // Call just in case. + toolEndShape (); + + if (!queryCloseColors ()) + return; + + openDefaultColors (); + + deselectActionColorsKDE (); +} + +// private +bool kpMainWindow::openKDEColors (const QString &name) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::openKDEColors(" << name << ")"; +#endif + + kpColorCollection colorCol; + if (colorCol.openKDE (name, this)) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "opened"; + #endif + colorCells ()->setColorCollection (colorCol); + return true; + } + else + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "failed to open"; + #endif + return false; + } +} + +// private slot +void kpMainWindow::slotColorsKDE () +{ + // Call in case an error dialog appears. + toolEndShape (); + + const int curItem = d->actionColorsKDE->currentItem (); + + if (!queryCloseColors ()) + { + deselectActionColorsKDE (); + return; + } + else + { + // queryCloseColors() calls slotColorSave(), which can call + // slotColorSaveAs(), which can call deselectActionColorsKDE(). + d->actionColorsKDE->setCurrentItem (curItem); + } + + const QStringList colNames = ::KDEColorCollectionNames (); + const int selected = d->actionColorsKDE->currentItem (); + Q_ASSERT (selected >= 0 && selected < colNames.size ()); + + if (!openKDEColors (colNames [selected])) + deselectActionColorsKDE (); +} + +// private +bool kpMainWindow::openColors (const KUrl &url) +{ + if (!colorCells ()->openColorCollection (url)) + return false; + + return true; +} + +// private slot +void kpMainWindow::slotColorsOpen () +{ + // Call due to dialog. + toolEndShape (); + + KFileDialog fd (colorCells ()->url (), QString()/*filter*/, this); + fd.setCaption (i18nc ("@title:window", "Open Color Palette")); + fd.setOperationMode (KFileDialog::Opening); + + if (fd.exec ()) + { + if (!queryCloseColors ()) + return; + + if (openColors (fd.selectedUrl ())) + deselectActionColorsKDE (); + } +} + +// private slot +void kpMainWindow::slotColorsReload () +{ + toolEndShape (); + + if (colorCells ()->isModified ()) + { + int result = KMessageBox::Cancel; + + if (!colorCells ()->url ().isEmpty ()) + { + result = KMessageBox::warningContinueCancel (this, + i18n ("The color palette \"%1\" has been modified.\n" + "Reloading will lose all changes since you last saved it.\n" + "Are you sure?", + kpUrlFormatter::PrettyFilename (colorCells ()->url ())), + QString ()/*caption*/, + KGuiItem(i18n ("&Reload"))); + } + else + { + const QString name = colorCells ()->colorCollection ()->name (); + if (!name.isEmpty ()) + { + result = KMessageBox::warningContinueCancel (this, + i18n ("The KDE color palette \"%1\" has been modified.\n" + "Reloading will lose all changes.\n" + "Are you sure?", + colorCells ()->colorCollection ()->name ()), + QString ()/*caption*/, + KGuiItem (i18n ("&Reload"))); + } + else + { + result = KMessageBox::warningContinueCancel (this, + i18n ("The default color palette has been modified.\n" + "Reloading will lose all changes.\n" + "Are you sure?"), + QString ()/*caption*/, + KGuiItem (i18n ("&Reload"))); + } + } + + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "result=" << result + << "vs KMessageBox::Continue" << KMessageBox::Continue; + #endif + if (result != KMessageBox::Continue) + return; + } + + + if (!colorCells ()->url ().isEmpty ()) + { + openColors (colorCells ()->url ()); + } + else + { + const QString name = colorCells ()->colorCollection ()->name (); + if (!name.isEmpty ()) + openKDEColors (name); + else + openDefaultColors (); + } +} + + +// private slot +bool kpMainWindow::slotColorsSave () +{ + // Call due to dialog. + toolEndShape (); + + if (colorCells ()->url ().isEmpty ()) + { + return slotColorsSaveAs (); + } + + return colorCells ()->saveColorCollection (); +} + +// private slot +bool kpMainWindow::slotColorsSaveAs () +{ + // Call due to dialog. + toolEndShape (); + + KFileDialog fd (colorCells ()->url (), QString()/*filter*/, this); + fd.setCaption (i18n ("Save Color Palette As")); + fd.setOperationMode (KFileDialog::Saving); + + if (fd.exec ()) + { + if (!colorCells ()->saveColorCollectionAs (fd.selectedUrl ())) + return false; + + // We're definitely using our own color collection now. + deselectActionColorsKDE (); + + return true; + } + else + return false; +} + + +// private slot +void kpMainWindow::slotColorsAppendRow () +{ + // Call just in case. + toolEndShape (); + + kpColorCells *colorCells = d->colorToolBar->colorCells (); + colorCells->appendRow (); +} + +// private slot +void kpMainWindow::slotColorsDeleteRow () +{ + // Call just in case. + toolEndShape (); + + kpColorCells *colorCells = d->colorToolBar->colorCells (); + colorCells->deleteLastRow (); +} diff --git a/kolourpaint/mainWindow/kpMainWindow_Edit.cpp b/kolourpaint/mainWindow/kpMainWindow_Edit.cpp new file mode 100644 index 00000000..1c5e955b --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow_Edit.cpp @@ -0,0 +1,921 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupEditMenuActions () +{ + KActionCollection *ac = actionCollection (); + + + // Undo/Redo + // CONFIG: Need GUI for config history size. + d->commandHistory = new kpCommandHistory (true/*read config*/, this); + + if (d->configFirstTime) + { + // (so that cfg-file-editing user can modify in the meantime) + d->commandHistory->writeConfig (); + } + + + d->actionCut = KStandardAction::cut (this, SLOT (slotCut ()), ac); + d->actionCopy = KStandardAction::copy (this, SLOT (slotCopy ()), ac); + d->actionPaste = KStandardAction::paste (this, SLOT (slotPaste ()), ac); + d->actionPasteInNewWindow = ac->addAction ("edit_paste_in_new_window"); + d->actionPasteInNewWindow->setText (i18n ("Paste in &New Window")); + connect (d->actionPasteInNewWindow, SIGNAL (triggered (bool)), + SLOT (slotPasteInNewWindow ())); + d->actionPasteInNewWindow->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_V); + + //d->actionDelete = KStandardAction::clear (this, SLOT (slotDelete ()), ac); + d->actionDelete = ac->addAction ("edit_clear"); + d->actionDelete->setText (i18n ("&Delete Selection")); + connect (d->actionDelete, SIGNAL (triggered (bool)), SLOT (slotDelete ())); + + d->actionSelectAll = KStandardAction::selectAll (this, SLOT (slotSelectAll ()), ac); + d->actionDeselect = KStandardAction::deselect (this, SLOT (slotDeselect ()), ac); + + + d->actionCopyToFile = ac->addAction ("edit_copy_to_file"); + d->actionCopyToFile->setText (i18n ("C&opy to File...")); + connect (d->actionCopyToFile, SIGNAL (triggered (bool)), + SLOT (slotCopyToFile ())); + d->actionPasteFromFile = ac->addAction ("edit_paste_from_file"); + d->actionPasteFromFile->setText (i18n ("Paste &From File...")); + connect (d->actionPasteFromFile, SIGNAL (triggered (bool)), + SLOT (slotPasteFromFile ())); + + + d->editMenuDocumentActionsEnabled = false; + enableEditMenuDocumentActions (false); + + // Paste should always be enabled, as long as there is something to paste + // (independent of whether we have a document or not) + connect (QApplication::clipboard (), SIGNAL (dataChanged ()), + this, SLOT (slotEnablePaste ())); + slotEnablePaste (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableEditMenuDocumentActions (bool enable) +{ + // d->actionCut + // d->actionCopy + // d->actionPaste + // d->actionPasteInNewWindow + + // d->actionDelete + + d->actionSelectAll->setEnabled (enable); + // d->actionDeselect + + d->editMenuDocumentActionsEnabled = enable; + + // d->actionCopyToFile + + // Unlike d->actionPaste, we disable this if there is no document. + // This is because "File / Open" would do the same thing, if there is + // no document. + d->actionPasteFromFile->setEnabled (enable); +} + +//--------------------------------------------------------------------- + +// public +QMenu *kpMainWindow::selectionToolRMBMenu () +{ + return qobject_cast (guiFactory ()->container ("selectionToolRMBMenu", this)); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotCut () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::slotCut() CALLED"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + Q_ASSERT (d->document && d->document->selection ()); + + toolEndShape (); + + slotCopy (); + slotDelete (); +} + +//--------------------------------------------------------------------- + +static QMimeData *NewTextMimeData (const QString &text) +{ + QMimeData *md = new QMimeData (); + md->setText (text); + return md; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotCopy () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::slotCopy() CALLED"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + Q_ASSERT (d->document && d->document->selection ()); + + toolEndShape (); + + kpAbstractSelection *sel = d->document->selection ()->clone (); + + if (dynamic_cast (sel)) + { + kpTextSelection *textSel = static_cast (sel); + if (!textSel->text ().isEmpty ()) + { + QApplication::clipboard ()->setMimeData ( + ::NewTextMimeData (textSel->text ()), + QClipboard::Clipboard); + + // SYNC: Normally, users highlight text and press CTRL+C. + // Highlighting text copies it to the X11 "middle + // mouse button" clipboard. CTRL+C copies it to the + // separate, Windows-like "CTRL+V" clipboard. + // + // However, KolourPaint doesn't support highlighting. + // So when they press CTRL+C to copy all text, simulate + // the highlighting by copying the text to the "middle + // mouse button" clipboard. We don't do this for images + // as no one ever middle-mouse-pastes images. + // + // Note that we don't share the QMimeData pointer with + // the above in case Qt doesn't expect it. + // + // Once we change KolourPaint to support highlighted text + // and CTRL+C to copy only the highlighted text, delete + // this code. + QApplication::clipboard ()->setMimeData ( + ::NewTextMimeData (textSel->text ()), + QClipboard::Selection); + } + } + else if (dynamic_cast (sel)) + { + kpAbstractImageSelection *imageSel = + static_cast (sel); + + // Transparency doesn't get sent across the aether so nuke it now + // so that transparency mask doesn't get needlessly recalculated + // if we ever call sel.setBaseImage(). + imageSel->setTransparency (kpImageSelectionTransparency ()); + + kpImage rawImage; + + if (imageSel->hasContent ()) + rawImage = imageSel->baseImage (); + else + rawImage = d->document->getSelectedBaseImage (); + + imageSel->setBaseImage ( rawImage ); + + QApplication::clipboard ()->setMimeData ( + new kpSelectionDrag (*imageSel), + QClipboard::Clipboard); + } + else + Q_ASSERT (!"Unknown selection type"); + + delete sel; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotEnablePaste () +{ + const QMimeData *md = + QApplication::clipboard()->mimeData(QClipboard::Clipboard); + + // It's faster to test for QMimeData::hasText() first due to the + // lazy evaluation of the '||' operator. + const bool shouldEnable = (md->hasText() || kpSelectionDrag::canDecode(md)); + + d->actionPasteInNewWindow->setEnabled(shouldEnable); + d->actionPaste->setEnabled(shouldEnable); +} + +//--------------------------------------------------------------------- + +// private +QRect kpMainWindow::calcUsefulPasteRect (int imageWidth, int imageHeight) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::calcUsefulPasteRect(" + << imageWidth << "," << imageHeight + << ")" + << endl; +#endif + Q_ASSERT (d->document); + + // TODO: 1st choice is to paste sel near but not overlapping last deselect point + + if (d->mainView && d->scrollView) + { + const QPoint viewTopLeft (d->scrollView->horizontalScrollBar()->value (), + d->scrollView->verticalScrollBar()->value ()); + + const QPoint docTopLeft = d->mainView->transformViewToDoc (viewTopLeft); + + if ((docTopLeft.x () + imageWidth <= d->document->width () && + docTopLeft.y () + imageHeight <= d->document->height ()) || + imageWidth <= docTopLeft.x () || + imageHeight <= docTopLeft.y ()) + { + return QRect (docTopLeft.x (), docTopLeft.y (), + imageWidth, imageHeight); + } + } + + return QRect (0, 0, imageWidth, imageHeight); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::paste(const kpAbstractSelection &sel, bool forceTopLeft) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::paste(forceTopLeft=" << forceTopLeft << ")" + << endl; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + toolEndShape (); + + // + // Make sure we've got a document (esp. with File/Close) + // + + if (!d->document) + { + kpDocument *newDoc = new kpDocument ( + sel.width (), sel.height (), documentEnvironment ()); + + // will also create viewManager + setDocument (newDoc); + } + + // + // Paste as new selection + // + + const kpAbstractImageSelection *imageSel = + dynamic_cast (&sel); + if (imageSel && imageSel->hasContent () && imageSel->transparency ().isTransparent ()) + { + d->colorToolBar->flashColorSimilarityToolBarItem (); + } + + kpAbstractSelection *selInUsefulPos = sel.clone (); + if (!forceTopLeft) + selInUsefulPos->moveTo (calcUsefulPasteRect (sel.width (), sel.height ()).topLeft ()); + // TODO: Should use kpCommandHistory::addCreateSelectionCommand(), + // as well, to really support pasting selection borders. + addDeselectFirstCommand (new kpToolSelectionCreateCommand ( + dynamic_cast (selInUsefulPos) ? + i18n ("Text: Create Box") : + i18n ("Selection: Create"), + *selInUsefulPos, + commandEnvironment ())); + delete selInUsefulPos; + + +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "sel.size=" << QSize (sel.width (), sel.height ()) + << " document.size=" + << QSize (d->document->width (), d->document->height ()) + << endl; +#endif + + // If the selection is bigger than the document, automatically + // resize the document (with the option of Undo'ing) to fit + // the selection. + // + // No annoying dialog necessary. + // + if (sel.width () > d->document->width () || + sel.height () > d->document->height ()) + { + d->commandHistory->addCommand ( + new kpTransformResizeScaleCommand ( + false/*act on doc, not sel*/, + qMax (sel.width (), d->document->width ()), + qMax (sel.height (), d->document->height ()), + kpTransformResizeScaleCommand::Resize, + commandEnvironment ())); + } +} + +//--------------------------------------------------------------------- + +// public +void kpMainWindow::pasteText (const QString &text, + bool forceNewTextSelection, + const QPoint &newTextSelectionTopLeft) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::pasteText(" << text + << ",forceNewTextSelection=" << forceNewTextSelection + << ",newTextSelectionTopLeft=" << newTextSelectionTopLeft + << ")" << endl; +#endif + + if ( text.isEmpty() ) + return; + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + toolEndShape (); + + QStringList textLines = text.split('\n'); + + if (!forceNewTextSelection && + d->document && d->document->textSelection () && + d->commandHistory && d->viewManager) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "\treusing existing Text Selection"; + #endif + + d->viewManager->setQueueUpdates(); + + kpTextSelection *textSel = d->document->textSelection (); + if (!textSel->hasContent ()) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "\t\tneeds content"; + #endif + commandHistory ()->addCreateSelectionCommand ( + new kpToolSelectionCreateCommand ( + i18n ("Text: Create Box"), + *textSel, + commandEnvironment ()), + false/*no exec*/); + } + + kpMacroCommand *macroCmd = new kpMacroCommand (i18n ("Text: Paste"), + commandEnvironment ()); + // (yes, this is the same check as the previous "if") + if (!textSel->hasContent ()) + { + kpCommand *giveContentCmd = new kpToolTextGiveContentCommand ( + *textSel, + QString ()/*uninteresting child of macro cmd*/, + commandEnvironment ()); + giveContentCmd->execute (); + + macroCmd->addCommand (giveContentCmd); + } + + for (int i = 0; i < (int) textLines.size (); i++) + { + if (i > 0) + { + macroCmd->addCommand ( + new kpToolTextEnterCommand ( + QString()/*uninteresting child of macroCmd*/, + d->viewManager->textCursorRow (), + d->viewManager->textCursorCol (), + kpToolTextEnterCommand::AddEnterNow, + commandEnvironment ())); + } + + macroCmd->addCommand ( + new kpToolTextInsertCommand ( + QString()/*uninteresting child of macroCmd*/, + d->viewManager->textCursorRow (), + d->viewManager->textCursorCol (), + textLines [i], + commandEnvironment ())); + } + + d->commandHistory->addCommand (macroCmd, false/*no exec*/); + + d->viewManager->restoreQueueUpdates(); + } + else + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "\tcreating Text Selection"; + #endif + + const kpTextStyle ts = textStyle (); + const QFontMetrics fontMetrics = ts.fontMetrics (); + + int height = textLines.size () * fontMetrics.height (); + if (textLines.size () >= 1) + height += (textLines.size () - 1) * fontMetrics.leading (); + + int width = 0; + for (QList ::const_iterator it = textLines.constBegin (); + it != textLines.constEnd (); + ++it) + { + const int w = fontMetrics.width (*it); + if (w > width) + width = w; + } + + // limit the size to avoid memory overflow + width = qMin(qMax(QApplication::desktop()->width(), d->document ? d->document->width() : 0), width); + height = qMin(qMax(QApplication::desktop()->height(), d->document ? d->document->height() : 0), height); + + const int selWidth = qMax (kpTextSelection::MinimumWidthForTextStyle (ts), + width + kpTextSelection::TextBorderSize () * 2); + const int selHeight = qMax (kpTextSelection::MinimumHeightForTextStyle (ts), + height + kpTextSelection::TextBorderSize () * 2); + kpTextSelection newTextSel (QRect (0, 0, selWidth, selHeight), + textLines, + ts); + + if (newTextSelectionTopLeft != KP_INVALID_POINT) + { + newTextSel.moveTo (newTextSelectionTopLeft); + paste (newTextSel, true/*force topLeft*/); + } + else + { + paste (newTextSel); + } + } +} + +//--------------------------------------------------------------------- + +// public +void kpMainWindow::pasteTextAt (const QString &text, const QPoint &point, + bool allowNewTextSelectionPointShift) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::pasteTextAt(" << text + << ",point=" << point + << ",allowNewTextSelectionPointShift=" + << allowNewTextSelectionPointShift + << ")" << endl; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + toolEndShape (); + + + if (d->document && + d->document->textSelection () && + d->document->textSelection ()->pointIsInTextArea (point)) + { + kpTextSelection *textSel = d->document->textSelection (); + + int row, col; + + if (textSel->hasContent ()) + { + row = textSel->closestTextRowForPoint (point); + col = textSel->closestTextColForPoint (point); + } + else + { + row = col = 0; + } + + d->viewManager->setTextCursorPosition (row, col); + + pasteText (text); + } + else + { + QPoint pointToUse = point; + + if (allowNewTextSelectionPointShift) + { + // TODO: In terms of doc pixels, would be inconsistent behaviour + // based on zoomLevel of view. + // pointToUse -= QPoint (-view->selectionResizeHandleAtomicSize (), + // -view->selectionResizeHandleAtomicSize ()); + } + + pasteText (text, true/*force new text selection*/, pointToUse); + } +} + +//--------------------------------------------------------------------- +// public slot + +void kpMainWindow::slotPaste() +{ + kpSetOverrideCursorSaver cursorSaver(Qt::WaitCursor); + + toolEndShape(); + + const QMimeData *mimeData = QApplication::clipboard()->mimeData(QClipboard::Clipboard); + + kpAbstractImageSelection *sel = kpSelectionDrag::decode(mimeData); + if ( sel ) + { + sel->setTransparency(imageSelectionTransparency()); + paste(*sel); + delete sel; + } + else if ( mimeData->hasText() ) + { + pasteText(mimeData->text()); + } + else + { + kpSetOverrideCursorSaver cursorSaver(Qt::ArrowCursor); + + KMessageBox::sorry(this, + i18n("KolourPaint cannot paste the contents of" + " the clipboard as it has an unknown format."), + i18n("Cannot Paste")); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotPasteInNewWindow () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::slotPasteInNewWindow() CALLED"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + toolEndShape (); + + // + // Pasting must ensure that: + // + // Requirement 1. the document is the same size as the image to be pasted. + // Requirement 2. transparent pixels in the image must remain as transparent. + // + + kpMainWindow *win = new kpMainWindow (0/*no document*/); + win->show (); + + // Make "Edit / Paste in New Window" always paste white pixels as white. + // Don't let selection transparency get in the way and paste them as + // transparent. + kpImageSelectionTransparency transparency = win->imageSelectionTransparency (); + if (transparency.isTransparent ()) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "\tchanging image selection transparency to opaque"; + #endif + transparency.setOpaque (); + // Since we are setting selection transparency programmatically + // -- as opposed to in response to user input -- this will not + // affect the selection transparency tool option widget's "last used" + // config setting. + win->setImageSelectionTransparency (transparency); + } + + // (this handles Requirement 1. above) + win->slotPaste (); + + // if slotPaste could not decode clipboard data, no document was created + if ( win->document() ) + { + // (this handles Requirement 2. above; + // slotDeselect() is not enough unless the document is filled with the + // transparent color in advance) + win->slotCrop(); + } +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotDelete () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::slotDelete() CALLED"; +#endif + if (!d->actionDelete->isEnabled ()) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "\taction not enabled - was probably called from kpTool::keyPressEvent()"; + #endif + return; + } + + Q_ASSERT (d->document && d->document->selection ()); + + toolEndShape (); + + addImageOrSelectionCommand (new kpToolSelectionDestroyCommand ( + d->document->textSelection () ? + i18n ("Text: Delete Box") : // not to be confused with i18n ("Text: Delete") + i18n ("Selection: Delete"), + false/*no push onto doc*/, + commandEnvironment ())); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotSelectAll () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::slotSelectAll() CALLED"; +#endif + Q_ASSERT (d->document); + + toolEndShape (); + + if (d->document->selection ()) + slotDeselect (); + + // just the border - don't actually pull image from doc yet + d->document->setSelection ( + kpRectangularImageSelection (d->document->rect (), + imageSelectionTransparency ())); + + if (tool ()) + tool ()->somethingBelowTheCursorChanged (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::addDeselectFirstCommand (kpCommand *cmd) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::addDeselectFirstCommand(" + << cmd + << ")" + << endl; +#endif + + + kpAbstractSelection *sel = d->document->selection (); + +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "\tsel=" << sel; +#endif + + if (sel) + { + // if you just dragged out something with no action then + // forget the drag + if (!sel->hasContent ()) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "\tjust a fresh border - was nop - delete"; + #endif + d->document->selectionDelete (); + if (tool ()) + tool ()->somethingBelowTheCursorChanged (); + + if (cmd) + d->commandHistory->addCommand (cmd); + } + else + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "\treal selection with image - push onto doc cmd"; + #endif + kpCommand *deselectCommand = new kpToolSelectionDestroyCommand ( + dynamic_cast (sel) ? + i18n ("Text: Finish") : + i18n ("Selection: Deselect"), + true/*push onto document*/, + commandEnvironment ()); + + if (cmd) + { + kpMacroCommand *macroCmd = new kpMacroCommand (cmd->name (), + commandEnvironment ()); + macroCmd->addCommand (deselectCommand); + macroCmd->addCommand (cmd); + d->commandHistory->addCommand (macroCmd); + } + else + d->commandHistory->addCommand (deselectCommand); + } + } + else + { + if (cmd) + d->commandHistory->addCommand (cmd); + } +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotDeselect () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::slotDeselect() CALLED"; +#endif + Q_ASSERT (d->document && d->document->selection ()); + + toolEndShape (); + + addDeselectFirstCommand (0); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotCopyToFile () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotCopyToFile()"; +#endif + + toolEndShape (); + + + if (!d->document->selection ()) + return; + + kpImage imageToSave; + + if (d->document->imageSelection ()) + { + kpAbstractImageSelection *imageSel = d->document->imageSelection (); + if (!imageSel->hasContent ()) + { + // Not a floating selection - user has just selected a region; + // haven't pulled it off yet so probably don't expect and can't + // visualize selection transparency so give opaque, not transparent + // image. + imageToSave = d->document->getSelectedBaseImage (); + } + else + imageToSave = imageSel->transparentImage (); + } + else if (d->document->textSelection ()) + { + imageToSave = d->document->textSelection ()->approximateImage (); + } + else + Q_ASSERT (!"Unknown selection type"); + + + kpDocumentSaveOptions chosenSaveOptions; + bool allowOverwritePrompt, allowLossyPrompt; + KUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Copy to File"), + d->lastCopyToURL.url (), + imageToSave, + d->lastCopyToSaveOptions, + kpDocumentMetaInfo (), + kpSettingsGroupEditCopyTo, + false/*allow remote files*/, + &chosenSaveOptions, + d->copyToFirstTime, + &allowOverwritePrompt, + &allowLossyPrompt); + + if (chosenURL.isEmpty ()) + return; + + + if (!kpDocument::savePixmapToFile (imageToSave, + chosenURL, + chosenSaveOptions, kpDocumentMetaInfo (), + allowOverwritePrompt, + allowLossyPrompt, + this)) + { + return; + } + + + addRecentURL (chosenURL); + + + d->lastCopyToURL = chosenURL; + d->lastCopyToSaveOptions = chosenSaveOptions; + + d->copyToFirstTime = false; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotPasteFromFile () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotPasteFromFile()"; +#endif + + toolEndShape (); + + + KUrl::List urls = askForOpenURLs(i18nc ("@title:window", "Paste From File"), + false/*only 1 URL*/); + + if (urls.count () != 1) + return; + + KUrl url = urls.first (); + + kpImage image = kpDocument::getPixmapFromFile (url, + false/*show error message if doesn't exist*/, + this); + + if (image.isNull ()) + return; + + addRecentURL (url); + + paste (kpRectangularImageSelection ( + QRect (0, 0, image.width (), image.height ()), + image, + imageSelectionTransparency ())); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/mainWindow/kpMainWindow_File.cpp b/kolourpaint/mainWindow/kpMainWindow_File.cpp new file mode 100644 index 00000000..702a3534 --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow_File.cpp @@ -0,0 +1,1498 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2007 Martin Koller + Copyright (c) 2007 John Layt + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// private +void kpMainWindow::setupFileMenuActions () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::setupFileMenuActions()"; +#endif + KActionCollection *ac = actionCollection (); + + d->actionNew = KStandardAction::openNew (this, SLOT (slotNew ()), ac); + d->actionOpen = KStandardAction::open (this, SLOT (slotOpen ()), ac); + + d->actionOpenRecent = KStandardAction::openRecent (this, SLOT (slotOpenRecent (const KUrl &)), ac); + d->actionOpenRecent->loadEntries (KGlobal::config ()->group (kpSettingsGroupRecentFiles)); +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\trecent URLs=" << d->actionOpenRecent->items (); +#endif + + d->actionSave = KStandardAction::save (this, SLOT (slotSave ()), ac); + d->actionSaveAs = KStandardAction::saveAs (this, SLOT (slotSaveAs ()), ac); + + d->actionExport = ac->addAction("file_export"); + d->actionExport->setText (i18n ("E&xport...")); + d->actionExport->setIcon (KIcon ("document-export")); + connect(d->actionExport, SIGNAL(triggered(bool) ), SLOT (slotExport ())); + + d->actionScan = ac->addAction("file_scan"); + d->actionScan->setText(i18n ("Scan...")); + d->actionScan->setIcon(SmallIcon("scanner")); + connect(d->actionScan, SIGNAL(triggered(bool)), SLOT(slotScan())); + + d->actionScreenshot = ac->addAction("file_screenshot"); + d->actionScreenshot->setText(i18n("Acquire Screenshot")); + connect(d->actionScreenshot, SIGNAL(triggered(bool)), SLOT(slotScreenshot())); + + d->actionProperties = ac->addAction ("file_properties"); + d->actionProperties->setText (i18n ("Properties")); + d->actionProperties->setIcon (KIcon ("document-properties")); + connect (d->actionProperties, SIGNAL (triggered (bool)), SLOT (slotProperties ())); + + //d->actionRevert = KStandardAction::revert (this, SLOT (slotRevert ()), ac); + d->actionReload = ac->addAction ("file_revert"); + d->actionReload->setText (i18n ("Reloa&d")); + d->actionReload->setIcon (KIcon ("view-refresh")); + connect(d->actionReload, SIGNAL(triggered(bool) ), SLOT (slotReload ())); + d->actionReload->setShortcuts(KStandardShortcut::reload ()); + slotEnableReload (); + + d->actionPrint = KStandardAction::print (this, SLOT (slotPrint ()), ac); + d->actionPrintPreview = KStandardAction::printPreview (this, SLOT (slotPrintPreview ()), ac); + + d->actionMail = KStandardAction::mail (this, SLOT (slotMail ()), ac); + + d->actionClose = KStandardAction::close (this, SLOT (slotClose ()), ac); + d->actionQuit = KStandardAction::quit (this, SLOT (slotQuit ()), ac); + + d->scanDialog = 0; + + enableFileMenuDocumentActions (false); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableFileMenuDocumentActions (bool enable) +{ + // d->actionNew + // d->actionOpen + + // d->actionOpenRecent + + d->actionSave->setEnabled (enable); + d->actionSaveAs->setEnabled (enable); + + d->actionExport->setEnabled (enable); + + // d->actionScan + + d->actionProperties->setEnabled (enable); + + // d->actionReload + + d->actionPrint->setEnabled (enable); + d->actionPrintPreview->setEnabled (enable); + + d->actionMail->setEnabled (enable); + + d->actionClose->setEnabled (enable); + // d->actionQuit->setEnabled (enable); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::addRecentURL (const KUrl &url_) +{ + // HACK: KRecentFilesAction::loadEntries() clears the KRecentFilesAction::d->urls + // map. + // + // So afterwards, the URL ref, our method is given, points to an + // element in this now-cleared map (see KRecentFilesAction::urlSelected(QAction*)). + // Accessing it would result in a crash. + // + // To avoid the crash, make a copy of it before calling + // loadEntries() and use this copy, instead of the to-be-dangling + // ref. + const KUrl url = url_; + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::addRecentURL(" << url << ")"; +#endif + if (url.isEmpty ()) + return; + + + KSharedConfig::Ptr cfg = KGlobal::config(); + + // KConfig::readEntry() does not actually reread from disk, hence doesn't + // realize what other processes have done e.g. Settings / Show Path + cfg->reparseConfiguration (); + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\trecent URLs=" << d->actionOpenRecent->items (); +#endif + // HACK: Something might have changed interprocess. + // If we could PROPAGATE: interprocess, then this wouldn't be required. + d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles)); +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tafter loading config=" << d->actionOpenRecent->items (); +#endif + + d->actionOpenRecent->addUrl (url); + + d->actionOpenRecent->saveEntries (cfg->group (kpSettingsGroupRecentFiles)); + cfg->sync (); + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tnew recent URLs=" << d->actionOpenRecent->items (); +#endif + + + // TODO: PROPAGATE: interprocess + // TODO: Is this loop safe since a KMainWindow later along in the list, + // could be closed as the code in the body almost certainly re-enters + // the event loop? Problem for KDE 3 as well, I think. + foreach (KMainWindow *kmw, KMainWindow::memberList ()) + { + Q_ASSERT (dynamic_cast (kmw)); + kpMainWindow *mw = static_cast (kmw); + + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tmw=" << mw; + #endif + + if (mw != this) + { + // WARNING: Do not use KRecentFilesAction::setItems() + // - it does not work since only its superclass, + // KSelectAction, implements setItems() and can't + // update KRecentFilesAction's URL list. + + // Avoid URL memory leak in KRecentFilesAction::loadEntries(). + mw->d->actionOpenRecent->clear (); + + mw->d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles)); + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\t\tcheck recent URLs=" + << mw->d->actionOpenRecent->items () << endl; + #endif + } + } +} + +//--------------------------------------------------------------------- + + +// private slot +// TODO: Disable action if +// (d->configOpenImagesInSameWindow && d->document && d->document->isEmpty()) +// as it does nothing if this is true. +void kpMainWindow::slotNew () +{ + toolEndShape (); + + if (d->document && !d->configOpenImagesInSameWindow) + { + // A document -- empty or otherwise -- is open. + // Force open a new window. In contrast, open() might not open + // a new window in this case. + kpMainWindow *win = new kpMainWindow (); + win->show (); + } + else + { + open (KUrl (), true/*create an empty doc*/); + } +} + +//--------------------------------------------------------------------- + + +// private +QSize kpMainWindow::defaultDocSize () const +{ + // KConfig::readEntry() does not actually reread from disk, hence doesn't + // realize what other processes have done e.g. Settings / Show Path + KGlobal::config ()->reparseConfiguration (); + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupGeneral); + + QSize docSize = cfg.readEntry (kpSettingLastDocSize, QSize ()); + + if (docSize.isEmpty ()) + { + docSize = QSize (400, 300); + } + else + { + // Don't get too big or you'll thrash (or even lock up) the computer + // just by opening a window + docSize = QSize (qMin (2048, docSize.width ()), + qMin (2048, docSize.height ())); + } + + return docSize; +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::saveDefaultDocSize (const QSize &size) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tCONFIG: saving Last Doc Size = " << size; +#endif + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupGeneral); + + cfg.writeEntry (kpSettingLastDocSize, size); + cfg.sync (); +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::shouldOpen () +{ + if (d->configOpenImagesInSameWindow) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\topenImagesInSameWindow"; + #endif + // (this brings up a dialog and might save the current doc) + if (!queryCloseDocument ()) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tqueryCloseDocument() aborts open"; + #endif + return false; + } + } + + return true; +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setDocumentChoosingWindow (kpDocument *doc) +{ + // Want new window? + if (d->document && !d->document->isEmpty () && + !d->configOpenImagesInSameWindow) + { + // Send doc to new window. + kpMainWindow *win = new kpMainWindow (doc); + win->show (); + } + else + { + // (sets up views, doc signals) + setDocument (doc); + } +} + +//--------------------------------------------------------------------- + +// private +kpDocument *kpMainWindow::openInternal (const KUrl &url, + const QSize &fallbackDocSize, + bool newDocSameNameIfNotExist) +{ + // If using OpenImagesInSameWindow mode, ask whether to close the + // current document. + if (!shouldOpen ()) + return 0; + + // Create/open doc. + kpDocument *newDoc = new kpDocument (fallbackDocSize.width (), + fallbackDocSize.height (), + documentEnvironment ()); + if (!newDoc->open (url, newDocSameNameIfNotExist)) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\topen failed"; + #endif + delete newDoc; + return 0; + } + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\topen OK"; +#endif + // Send document to current or new window. + setDocumentChoosingWindow (newDoc); + + return newDoc; +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::open (const KUrl &url, bool newDocSameNameIfNotExist) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::open(" << url + << ",newDocSameNameIfNotExist=" << newDocSameNameIfNotExist + << ")" << endl; +#endif + + kpDocument *newDoc = openInternal (url, + defaultDocSize (), + newDocSameNameIfNotExist); + if (newDoc) + { + if (newDoc->isFromURL (false/*don't bother checking exists*/)) + addRecentURL (url); + return true; + } + else + { + return false; + } +} + +//--------------------------------------------------------------------- + +// private +KUrl::List kpMainWindow::askForOpenURLs(const QString &caption, bool allowMultipleURLs) +{ + QStringList mimeTypes = KImageIO::mimeTypes (KImageIO::Reading); +#if DEBUG_KP_MAIN_WINDOW + QStringList sortedMimeTypes = mimeTypes; + sortedMimeTypes.sort (); + kDebug () << "kpMainWindow::askForURLs(allowMultiple=" + << allowMultipleURLs + << ")" << endl + << "\tmimeTypes=" << mimeTypes << endl + << "\tsortedMimeTypes=" << sortedMimeTypes << endl; +#endif + QString filter = mimeTypes.join (" "); + + KFileDialog fd(KUrl("kfiledialog:///dir/"), filter, this); + fd.setCaption(caption); + fd.setOperationMode(KFileDialog::Opening); + + if (allowMultipleURLs) + fd.setMode (KFile::Files); + + if (fd.exec ()) + return fd.selectedUrls (); + else + return KUrl::List (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotOpen () +{ + toolEndShape (); + + const KUrl::List urls = askForOpenURLs(i18nc("@title:window", "Open Image")); + + for (KUrl::List::const_iterator it = urls.begin (); + it != urls.end (); + ++it) + { + open (*it); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotOpenRecent (const KUrl &url) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotOpenRecent(" << url << ")"; + kDebug () << "\titems=" << d->actionOpenRecent->items (); +#endif + + toolEndShape (); + + open (url); + + // If the open is successful, addRecentURL() would have bubbled up the + // URL in the File / Open Recent action. As a side effect, the URL is + // deselected. + // + // If the open fails, we should deselect the URL: + // + // 1. for consistency + // + // 2. because it has not been opened. + // + d->actionOpenRecent->setCurrentItem (-1); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotScan () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotScan() scanDialog=" << d->scanDialog; +#endif + + toolEndShape (); + + if (!d->scanDialog) + { + // Create scan dialog by looking for plugin. + // [takes about 500ms on 350Mhz] + d->scanDialog = KScanDialog::getScanDialog (this); + + // No scanning support (kdegraphics/libkscan) installed? + // [Remove $KDEDIR/share/servicetypes/kscan.desktop and + // $KDEDIR/share/services/scanservice.desktop to simulate this] + if (!d->scanDialog) + { + // Instead, we could try to create the scan dialog in the ctor + // and just disable the action in the first place, removing + // the need for this dialog. + // + // But this increases startup time and is a bit risky e.g. if + // the scan support hangs, KolourPaint would not be able to be + // started at all. + // + // Also, disabling the action is bad because the scan support + // can be installed while KolourPaint is still running. + KMessageBox::sorry (this, + i18n ("No plugin was found which provides the scanner dialog.\n" + "This usually means that the package providing the ksaneplugin is not installed."), + i18n ("No Scanning Support")); + return; + } + + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tcreated scanDialog=" << d->scanDialog; + #endif + connect (d->scanDialog, SIGNAL (finalImage (const QImage &, int)), + SLOT (slotScanned (const QImage &, int))); + } + + + // If using OpenImagesInSameWindow mode, ask whether to close the + // current document. + // + // Do this after scan support is detected. Because if it's not, what + // would be the point of closing the document? + // + // Ideally, we would do this after the user presses "Final Scan" in + // the scan dialog and before the scan begins (if the user wants to + // cancel the scan operation, it would be annoying to offer this choice + // only after the slow scan is completed) but the KScanDialog API does + // not allow this. So we settle for doing this before any + // scan dialogs are shown. We don't do this between KScanDialog::setup() + // and KScanDialog::exec() as it could be confusing alternating between + // scanning and KolourPaint dialogs. + if (!shouldOpen ()) + return; + + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tcalling setup"; +#endif + // Bring up dialog to select scan device. + // If there is no scanner, we find that this does not bring up a dialog + // but still returns true. + if (d->scanDialog->setup ()) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tOK - showing dialog"; + #endif + // Called only if scanner configured/available. + // + // In reality, this seems to be called even if you press "Cancel" in + // the KScanDialog::setup() dialog! + // + // We use exec() to make sure it's modal. show() seems to work too + // but better safe than sorry. + d->scanDialog->exec (); + } + else + { + // Have never seen this code path execute even if "Cancel" is pressed. + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tFAIL"; + #endif + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotScanned (const QImage &image, int) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotScanned() image.rect=" << image.rect (); +#endif + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\thiding dialog"; +#endif + // (KScanDialog does not close itself after a scan is made) + // + // Close the dialog, first thing: + // + // 1. This means that any dialogs we bring up won't be nested on top. + // + // 2. We don't want to return from this method but forget to close + // the dialog. So do it before anything else. + d->scanDialog->hide (); + + // (just in case there's some drawing between slotScan() exiting and + // us being called) + toolEndShape (); + + + // TODO: Maybe this code should be moved into kpdocument.cpp - + // since it resembles the responsibilities of kpDocument::open(). + + kpDocumentSaveOptions saveOptions; + kpDocumentMetaInfo metaInfo; + + kpDocument::getDataFromImage(image, saveOptions, metaInfo); + + // Create document from image and meta info. + kpDocument *doc = new kpDocument (image.width (), image.height (), + documentEnvironment ()); + doc->setImage (image); + doc->setSaveOptions (saveOptions); + doc->setMetaInfo (metaInfo); + + // Send document to current or new window. + setDocumentChoosingWindow (doc); +} + +//--------------------------------------------------------------------- + +void kpMainWindow::slotScreenshot() +{ + toolEndShape(); + + KDialog *dialog = new KDialog(this); + dialog->setButtons(KDialog::Ok | KDialog::Cancel); + + QLabel *label = new QLabel(i18n("Snapshot Delay")); + KIntSpinBox *seconds = new KIntSpinBox; + seconds->setRange(0, 99); + seconds->setSuffix(ki18np(" second", " seconds")); + seconds->setSpecialValueText(i18n("No delay")); + + QCheckBox *hideWindow = new QCheckBox(i18n("Hide Main Window")); + hideWindow->setChecked(true); + + QVBoxLayout *vbox = new QVBoxLayout(dialog->mainWidget()); + vbox->addWidget(label); + vbox->addWidget(seconds); + vbox->addWidget(hideWindow); + + if ( dialog->exec() == KDialog::Rejected ) + { + delete dialog; + return; + } + + if ( hideWindow->isChecked() ) + hide(); + + // at least 1 seconds to make sure the window is hidden and the hide effect already stopped + QTimer::singleShot((seconds->value() + 1) * 1000, this, SLOT(slotMakeScreenshot())); + + delete dialog; +} + +//--------------------------------------------------------------------- + +void kpMainWindow::slotMakeScreenshot() +{ + QCoreApplication::processEvents(); + QPixmap pixmap = QPixmap::grabWindow(QApplication::desktop()->winId()); + + kpDocument *doc = new kpDocument(pixmap.width(), pixmap.height(), + documentEnvironment()); + doc->setImage(pixmap.toImage()); + + // Send document to current or new window. + setDocumentChoosingWindow(doc); + + show(); // in case we hid the mainwindow, show it again +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotProperties () +{ + toolEndShape (); + + kpDocumentMetaInfoDialog dialog (document ()->metaInfo (), this); + + if (dialog.exec () && !dialog.isNoOp ()) + { + commandHistory ()->addCommand ( + new kpDocumentMetaInfoCommand ( + i18n ("Document Properties"), + dialog.metaInfo ()/*new*/, *document ()->metaInfo ()/*old*/, + commandEnvironment ())); + } +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::save (bool localOnly) +{ + if (d->document->url ().isEmpty () || + !KImageIO::mimeTypes (KImageIO::Writing) + .contains (d->document->saveOptions ()->mimeType ()) || + // SYNC: kpDocument::getPixmapFromFile() can't determine quality + // from file so it has been set initially to an invalid value. + (d->document->saveOptions ()->mimeTypeHasConfigurableQuality () && + d->document->saveOptions ()->qualityIsInvalid ()) || + (localOnly && !d->document->url ().isLocalFile ())) + { + return saveAs (localOnly); + } + else + { + if (d->document->save (false/*no overwrite prompt*/, + !d->document->savedAtLeastOnceBefore ()/*lossy prompt*/)) + { + addRecentURL (d->document->url ()); + return true; + } + else + return false; + } +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotSave () +{ + toolEndShape (); + + return save (); +} + +//--------------------------------------------------------------------- + +// private +KUrl kpMainWindow::askForSaveURL (const QString &caption, + const QString &startURL, + const kpImage &imageToBeSaved, + const kpDocumentSaveOptions &startSaveOptions, + const kpDocumentMetaInfo &docMetaInfo, + const QString &forcedSaveOptionsGroup, + bool localOnly, + kpDocumentSaveOptions *chosenSaveOptions, + bool isSavingForFirstTime, + bool *allowOverwritePrompt, + bool *allowLossyPrompt) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::askForURL() startURL=" << startURL; + startSaveOptions.printDebug ("\tstartSaveOptions"); +#endif + + bool reparsedConfiguration = false; + + // KConfig::readEntry() does not actually reread from disk, hence doesn't + // realize what other processes have done e.g. Settings / Show Path + // so reparseConfiguration() must be called +#define SETUP_READ_CFG() \ + if (!reparsedConfiguration) \ + { \ + KGlobal::config ()->reparseConfiguration (); \ + reparsedConfiguration = true; \ + } \ + \ + KConfigGroup cfg (KGlobal::config (), forcedSaveOptionsGroup); \ + + + if (chosenSaveOptions) + *chosenSaveOptions = kpDocumentSaveOptions (); + + if (allowOverwritePrompt) + *allowOverwritePrompt = true; // play it safe for now + + if (allowLossyPrompt) + *allowLossyPrompt = true; // play it safe for now + + + kpDocumentSaveOptions fdSaveOptions = startSaveOptions; + + QStringList mimeTypes = KImageIO::mimeTypes (KImageIO::Writing); +#if DEBUG_KP_MAIN_WINDOW + QStringList sortedMimeTypes = mimeTypes; + sortedMimeTypes.sort (); + kDebug () << "\tmimeTypes=" << mimeTypes + << "\tsortedMimeTypes=" << sortedMimeTypes << endl; +#endif + if (mimeTypes.isEmpty ()) + { + kError () << "No KImageIO output mimetypes!" << endl; + return KUrl (); + } + +#define MIME_TYPE_IS_VALID() (!fdSaveOptions.mimeTypeIsInvalid () && \ + mimeTypes.contains (fdSaveOptions.mimeType ())) + if (!MIME_TYPE_IS_VALID ()) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tmimeType=" << fdSaveOptions.mimeType () + << " not valid, get default" << endl; + #endif + + SETUP_READ_CFG (); + + fdSaveOptions.setMimeType (kpDocumentSaveOptions::defaultMimeType (cfg)); + + + if (!MIME_TYPE_IS_VALID ()) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tmimeType=" << fdSaveOptions.mimeType () + << " not valid, get hardcoded" << endl; + #endif + if (mimeTypes.contains ("image/png")) + fdSaveOptions.setMimeType ("image/png"); + else if (mimeTypes.contains ("image/bmp")) + fdSaveOptions.setMimeType ("image/bmp"); + else + fdSaveOptions.setMimeType (mimeTypes.first ()); + } + } +#undef MIME_TYPE_IS_VALID + + if (fdSaveOptions.colorDepthIsInvalid ()) + { + SETUP_READ_CFG (); + + fdSaveOptions.setColorDepth (kpDocumentSaveOptions::defaultColorDepth (cfg)); + fdSaveOptions.setDither (kpDocumentSaveOptions::defaultDither (cfg)); + } + + if (fdSaveOptions.qualityIsInvalid ()) + { + SETUP_READ_CFG (); + + fdSaveOptions.setQuality (kpDocumentSaveOptions::defaultQuality (cfg)); + } +#if DEBUG_KP_MAIN_WINDOW + fdSaveOptions.printDebug ("\tcorrected saveOptions passed to fileDialog"); +#endif + + kpDocumentSaveOptionsWidget *saveOptionsWidget = + new kpDocumentSaveOptionsWidget (imageToBeSaved, + fdSaveOptions, + docMetaInfo, + this); + + KFileDialog fd (startURL, QString(), this, + saveOptionsWidget); + saveOptionsWidget->setVisualParent (&fd); + fd.setCaption (caption); + fd.setOperationMode (KFileDialog::Saving); + fd.setMimeFilter (mimeTypes, fdSaveOptions.mimeType ()); + if (localOnly) + fd.setMode (KFile::File | KFile::LocalOnly); + + connect (&fd, SIGNAL (filterChanged (const QString &)), + saveOptionsWidget, SLOT (setMimeType (const QString &))); + + if ( fd.exec() == QDialog::Accepted ) + { + kpDocumentSaveOptions newSaveOptions = saveOptionsWidget->documentSaveOptions (); + #if DEBUG_KP_MAIN_WINDOW + newSaveOptions.printDebug ("\tnewSaveOptions"); + #endif + + KConfigGroup cfg (KGlobal::config (), forcedSaveOptionsGroup); + + // Save options user forced - probably want to use them in future + kpDocumentSaveOptions::saveDefaultDifferences (cfg, + fdSaveOptions, newSaveOptions); + cfg.sync (); + + + if (chosenSaveOptions) + *chosenSaveOptions = newSaveOptions; + + + bool shouldAllowOverwritePrompt = + (fd.selectedUrl () != startURL || + newSaveOptions.mimeType () != startSaveOptions.mimeType ()); + if (allowOverwritePrompt) + { + *allowOverwritePrompt = shouldAllowOverwritePrompt; + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tallowOverwritePrompt=" << *allowOverwritePrompt; + #endif + } + + if (allowLossyPrompt) + { + // SYNC: kpDocumentSaveOptions elements - everything except quality + // (one quality setting is "just as lossy" as another so no + // need to continually warn due to quality change) + *allowLossyPrompt = + (isSavingForFirstTime || + shouldAllowOverwritePrompt || + newSaveOptions.mimeType () != startSaveOptions.mimeType () || + newSaveOptions.colorDepth () != startSaveOptions.colorDepth () || + newSaveOptions.dither () != startSaveOptions.dither ()); + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tallowLossyPrompt=" << *allowLossyPrompt; + #endif + } + + + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tselectedUrl=" << fd.selectedUrl (); + #endif + return fd.selectedUrl (); + } + else + return KUrl (); +#undef SETUP_READ_CFG +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::saveAs (bool localOnly) +{ + kpDocumentSaveOptions chosenSaveOptions; + bool allowOverwritePrompt, allowLossyPrompt; + KUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Save Image As"), + d->document->url ().url (), + d->document->imageWithSelection (), + *d->document->saveOptions (), + *d->document->metaInfo (), + kpSettingsGroupFileSaveAs, + localOnly, + &chosenSaveOptions, + !d->document->savedAtLeastOnceBefore (), + &allowOverwritePrompt, + &allowLossyPrompt); + + + if (chosenURL.isEmpty ()) + return false; + + + if (!d->document->saveAs (chosenURL, chosenSaveOptions, + allowOverwritePrompt, + allowLossyPrompt)) + { + return false; + } + + + addRecentURL (chosenURL); + + return true; +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotSaveAs () +{ + toolEndShape (); + + return saveAs (); +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotExport () +{ + toolEndShape (); + + kpDocumentSaveOptions chosenSaveOptions; + bool allowOverwritePrompt, allowLossyPrompt; + KUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Export"), + d->lastExportURL.url (), + d->document->imageWithSelection (), + d->lastExportSaveOptions, + *d->document->metaInfo (), + kpSettingsGroupFileExport, + false/*allow remote files*/, + &chosenSaveOptions, + d->exportFirstTime, + &allowOverwritePrompt, + &allowLossyPrompt); + + + if (chosenURL.isEmpty ()) + return false; + + if (!kpDocument::savePixmapToFile (d->document->imageWithSelection (), + chosenURL, + chosenSaveOptions, *d->document->metaInfo (), + allowOverwritePrompt, + allowLossyPrompt, + this)) + { + return false; + } + + + addRecentURL (chosenURL); + + d->lastExportURL = chosenURL; + d->lastExportSaveOptions = chosenSaveOptions; + + d->exportFirstTime = false; + + return true; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotEnableReload () +{ + d->actionReload->setEnabled (d->document); +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotReload () +{ + toolEndShape (); + + Q_ASSERT (d->document); + + + KUrl oldURL = d->document->url (); + + + if (d->document->isModified ()) + { + int result = KMessageBox::Cancel; + + if (d->document->isFromURL (false/*don't bother checking exists*/) && !oldURL.isEmpty ()) + { + result = KMessageBox::warningContinueCancel (this, + i18n ("The document \"%1\" has been modified.\n" + "Reloading will lose all changes since you last saved it.\n" + "Are you sure?", + d->document->prettyFilename ()), + QString()/*caption*/, + KGuiItem(i18n ("&Reload"))); + } + else + { + result = KMessageBox::warningContinueCancel (this, + i18n ("The document \"%1\" has been modified.\n" + "Reloading will lose all changes.\n" + "Are you sure?", + d->document->prettyFilename ()), + QString()/*caption*/, + KGuiItem(i18n ("&Reload"))); + } + + if (result != KMessageBox::Continue) + return false; + } + + + kpDocument *doc = 0; + + // If it's _supposed to_ come from a URL or it exists + if (d->document->isFromURL (false/*don't bother checking exists*/) || + (!oldURL.isEmpty () && KIO::NetAccess::exists (oldURL, KIO::NetAccess::SourceSide/*open*/, this))) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotReload() reloading from disk!"; + #endif + + doc = new kpDocument (1, 1, documentEnvironment ()); + if (!doc->open (oldURL)) + { + delete doc; doc = 0; + return false; + } + + addRecentURL (oldURL); + } + else + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotReload() create doc"; + #endif + + doc = new kpDocument (d->document->constructorWidth (), + d->document->constructorHeight (), + documentEnvironment ()); + doc->setURL (oldURL, false/*not from URL*/); + } + + + setDocument (doc); + + return true; +} + + +// private +void kpMainWindow::sendDocumentNameToPrinter (QPrinter *printer) +{ + KUrl url = d->document->url (); + if (!url.isEmpty ()) + { + int dot; + + QString fileName = url.fileName (); + dot = fileName.lastIndexOf ('.'); + + // file.ext but not .hidden-file? + if (dot > 0) + fileName.truncate (dot); + + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::sendDocumentNameToPrinter() fileName=" + << fileName + << " dir=" + << url.directory () + << endl; + #endif + printer->setDocName (fileName); + } +} + + +// private +void kpMainWindow::sendImageToPrinter (QPrinter *printer, + bool showPrinterSetupDialog) +{ + // Get image to be printed. + kpImage image = d->document->imageWithSelection (); + + + // Get image DPI. + double imageDotsPerMeterX = + double (d->document->metaInfo ()->dotsPerMeterX ()); + double imageDotsPerMeterY = + double (d->document->metaInfo ()->dotsPerMeterY ()); +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::sendImageToPrinter() image:" + << " width=" << image.width () + << " height=" << image.height () + << " dotsPerMeterX=" << imageDotsPerMeterX + << " dotsPerMeterY=" << imageDotsPerMeterY + << endl; +#endif + + // Image DPI invalid (e.g. new image, could not read from file + // or Qt3 doesn't implement DPI for JPEG)? + if (imageDotsPerMeterX <= 0 || imageDotsPerMeterY <= 0) + { + // Even if just one DPI dimension is invalid, mutate both DPI + // dimensions as we have no information about the intended + // aspect ratio anyway (and other dimension likely to be invalid). + + // When rendering text onto a document, the fonts are rasterised + // according to the screen's DPI. + // TODO: I think we should use the image's DPI. Technically + // possible? + // + // So no matter what computer you draw text on, you get + // the same pixels. + // + // So we must print at the screen's DPI to get the right text size. + // + // Unfortunately, this means that moving to a different screen DPI + // affects printing. If you edited the image at a different screen + // DPI than when you print, you get incorrect results. Furthermore, + // this is bogus if you don't have text in your image. Worse still, + // what if you have multiple screens connected to the same computer + // with different DPIs? + // TODO: mysteriously, someone else is setting this to 96dpi always. + QPixmap arbitraryScreenElement(1, 1); + const QPaintDevice *screenDevice = &arbitraryScreenElement; + const int dpiX = screenDevice->logicalDpiX (), + dpiY = screenDevice->logicalDpiY (); + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tusing screen dpi: x=" << dpiX << " y=" << dpiY; + #endif + + imageDotsPerMeterX = dpiX * KP_INCHES_PER_METER; + imageDotsPerMeterY = dpiY * KP_INCHES_PER_METER; + } + + + // Get page size (excluding margins). + // Coordinate (0,0) is the X here: + // mmmmm + // mX m + // m m m = margin + // m m + // mmmmm + const int printerWidthMM = printer->widthMM (); + const int printerHeightMM = printer->heightMM (); +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tprinter: widthMM=" << printerWidthMM + << " heightMM=" << printerHeightMM + << endl; +#endif + + + double dpiX = imageDotsPerMeterX / KP_INCHES_PER_METER; + double dpiY = imageDotsPerMeterY / KP_INCHES_PER_METER; +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\timage: dpiX=" << dpiX << " dpiY=" << dpiY; +#endif + + + // + // If image doesn't fit on page at intended DPI, change the DPI. + // + + const double scaleDpiX = + (image.width () / (printerWidthMM / KP_MILLIMETERS_PER_INCH)) + / dpiX; + const double scaleDpiY = + (image.height () / (printerHeightMM / KP_MILLIMETERS_PER_INCH)) + / dpiY; + const double scaleDpi = qMax (scaleDpiX, scaleDpiY); +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tscaleDpi: x=" << scaleDpiX << " y=" << scaleDpiY + << " --> scale at " << scaleDpi << " to fit?" + << endl; +#endif + + // Need to increase resolution to fit page? + if (scaleDpi > 1.0) + { + dpiX *= scaleDpi; + dpiY *= scaleDpi; + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\t\tto fit page, scaled to:" + << " dpiX=" << dpiX << " dpiY=" << dpiY << endl; + #endif + } + + + // Make sure DPIs are equal as that's all QPrinter::setResolution() + // supports. We do this in such a way that we only ever stretch an + // image, to avoid losing information. Don't antialias as the printer + // will do that to translate our DPI to its physical resolution and + // double-antialiasing looks bad. + if (dpiX > dpiY) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tdpiX > dpiY; stretching image height to equalise DPIs to dpiX=" + << dpiX << endl; + #endif + kpPixmapFX::scale (&image, + image.width (), + qMax (1, qRound (image.height () * dpiX / dpiY)), + false/*don't antialias*/); + + dpiY = dpiX; + } + else if (dpiY > dpiX) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tdpiY > dpiX; stretching image width to equalise DPIs to dpiY=" + << dpiY << endl; + #endif + kpPixmapFX::scale (&image, + qMax (1, qRound (image.width () * dpiY / dpiX)), + image.height (), + false/*don't antialias*/); + + dpiX = dpiY; + } + + Q_ASSERT (dpiX == dpiY); + + + // QPrinter::setResolution() has to be called before QPrinter::setup(). + printer->setResolution (qMax (1, qRound (dpiX))); + + + sendDocumentNameToPrinter (printer); + + + if (showPrinterSetupDialog) + { + kpPrintDialogPage *optionsPage = new kpPrintDialogPage (this); + optionsPage->setPrintImageCenteredOnPage (d->configPrintImageCenteredOnPage); + + QPrintDialog *printDialog = + KdePrint::createPrintDialog ( + printer, + QList () << optionsPage, + this); + printDialog->setWindowTitle (i18nc ("@title:window", "Print Image")); + + // Display dialog. + const bool wantToPrint = printDialog->exec (); + + if (optionsPage->printImageCenteredOnPage () != + d->configPrintImageCenteredOnPage) + { + // Save config option even if the dialog was cancelled. + d->configPrintImageCenteredOnPage = optionsPage->printImageCenteredOnPage (); + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupGeneral); + cfg.writeEntry (kpSettingPrintImageCenteredOnPage, + d->configPrintImageCenteredOnPage); + cfg.sync (); + } + + delete printDialog; + + if (!wantToPrint) + return; + } + + + double originX = 0, originY = 0; + + // Center image on page? + if (d->configPrintImageCenteredOnPage) + { + originX = + (printerWidthMM * dpiX / KP_MILLIMETERS_PER_INCH - image.width ()) + / 2; + originY = + (printerHeightMM * dpiY / KP_MILLIMETERS_PER_INCH - image.height ()) + / 2; + } + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\torigin: x=" << originX << " y=" << originY; +#endif + + + // Send image to printer. + QPainter painter; + painter.begin (printer); + painter.drawImage (qRound (originX), qRound (originY), image); + painter.end (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotPrint () +{ + toolEndShape (); + + QPrinter printer; + + sendImageToPrinter (&printer, true/*showPrinterSetupDialog*/); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotPrintPreview () +{ + toolEndShape (); + + QPrinter printer; + + KPrintPreview printPreview (&printer); + + sendImageToPrinter (&printer, false/*don't showPrinterSetupDialog*/); + + printPreview.exec (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotMail () +{ + toolEndShape (); + + if (d->document->url ().isEmpty ()/*no name*/ || + !d->document->isFromURL () || + d->document->isModified ()/*needs to be saved*/) + { + int result = KMessageBox::questionYesNo (this, + i18n ("You must save this image before sending it.\n" + "Do you want to save it?"), + QString(), + KStandardGuiItem::save (), KStandardGuiItem::cancel ()); + + if (result == KMessageBox::Yes) + { + if (!save ()) + { + // save failed or aborted - don't email + return; + } + } + else + { + // don't want to save - don't email + return; + } + } + + KToolInvocation::invokeMailer ( + QString()/*to*/, + QString()/*cc*/, + QString()/*bcc*/, + d->document->prettyFilename()/*subject*/, + QString()/*body*/, + QString()/*messageFile*/, + QStringList(d->document->url().url())/*attachments*/); +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::queryCloseDocument () +{ + toolEndShape (); + + if (!d->document || !d->document->isModified ()) + return true; // ok to close current doc + + int result = KMessageBox::warningYesNoCancel (this, + i18n ("The document \"%1\" has been modified.\n" + "Do you want to save it?", + d->document->prettyFilename ()), + QString()/*caption*/, + KStandardGuiItem::save (), KStandardGuiItem::discard ()); + + switch (result) + { + case KMessageBox::Yes: + return slotSave (); // close only if save succeeds + case KMessageBox::No: + return true; // close without saving + default: + return false; // don't close current doc + } +} + +//--------------------------------------------------------------------- + +// private virtual [base KMainWindow] +bool kpMainWindow::queryClose () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::queryClose()"; +#endif + toolEndShape (); + + if (!queryCloseDocument ()) + return false; + + if (!queryCloseColors ()) + return false; + + return true; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotClose () +{ + toolEndShape (); + + if (!queryCloseDocument ()) + return; + + setDocument (0); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotQuit () +{ + toolEndShape (); + + close (); // will call queryClose() +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/mainWindow/kpMainWindow_Image.cpp b/kolourpaint/mainWindow/kpMainWindow_Image.cpp new file mode 100644 index 00000000..51e3cc58 --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow_Image.cpp @@ -0,0 +1,595 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +//--------------------------------------------------------------------- + +// private +kpTransformDialogEnvironment *kpMainWindow::transformDialogEnvironment () +{ + if (!d->transformDialogEnvironment) + d->transformDialogEnvironment = new kpTransformDialogEnvironment (this); + + return d->transformDialogEnvironment; +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::isSelectionActive () const +{ + return (d->document ? bool (d->document->selection ()) : false); +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::isTextSelection () const +{ + return (d->document && d->document->textSelection ()); +} + +//--------------------------------------------------------------------- + +// private +QString kpMainWindow::autoCropText () const +{ + return kpTransformAutoCropCommand::text(isSelectionActive(), + kpTransformAutoCropCommand::ShowAccel); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupImageMenuActions () +{ + KActionCollection *ac = actionCollection (); + + d->actionResizeScale = ac->addAction ("image_resize_scale"); + d->actionResizeScale->setText (i18n ("R&esize / Scale...")); + connect (d->actionResizeScale, SIGNAL (triggered (bool)), SLOT (slotResizeScale ())); + d->actionResizeScale->setShortcut(Qt::CTRL + Qt::Key_E); + + d->actionCrop = ac->addAction ("image_crop"); + d->actionCrop->setText (i18n ("Se&t as Image (Crop)")); + connect (d->actionCrop, SIGNAL (triggered (bool)), SLOT (slotCrop ())); + d->actionCrop->setShortcut(Qt::CTRL + Qt::Key_T); + + d->actionAutoCrop = ac->addAction ("image_auto_crop"); + d->actionAutoCrop->setText (autoCropText ()); + connect (d->actionAutoCrop, SIGNAL (triggered (bool)), SLOT (slotAutoCrop ())); + d->actionAutoCrop->setShortcut(Qt::CTRL + Qt::Key_U); + + d->actionFlip = ac->addAction ("image_flip"); + d->actionFlip->setText (i18n ("&Flip (upside down)")); + connect (d->actionFlip, SIGNAL (triggered (bool)), SLOT (slotFlip ())); + d->actionFlip->setShortcut(Qt::CTRL + Qt::Key_F); + + d->actionMirror = ac->addAction ("image_mirror"); + d->actionMirror->setText (i18n ("Mirror (horizontally)")); + connect (d->actionMirror, SIGNAL (triggered (bool)), SLOT (slotMirror ())); + //d->actionMirror->setShortcut(Qt::CTRL + Qt::Key_M); + + d->actionRotate = ac->addAction ("image_rotate"); + d->actionRotate->setText (i18n ("&Rotate...")); + d->actionRotate->setIcon (KIcon ("transform-rotate")); + connect (d->actionRotate, SIGNAL (triggered (bool)), SLOT (slotRotate ())); + d->actionRotate->setShortcut(Qt::CTRL + Qt::Key_R); + + d->actionRotateLeft = ac->addAction ("image_rotate_270deg"); + d->actionRotateLeft->setText (i18n ("Rotate &Left")); + d->actionRotateLeft->setIcon (KIcon ("object-rotate-left")); + connect (d->actionRotateLeft, SIGNAL (triggered (bool)), SLOT (slotRotate270 ())); + d->actionRotateLeft->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Left); + + d->actionRotateRight = ac->addAction ("image_rotate_90deg"); + d->actionRotateRight->setText (i18n ("Rotate Righ&t")); + d->actionRotateRight->setIcon (KIcon ("object-rotate-right")); + connect (d->actionRotateRight, SIGNAL (triggered (bool)), SLOT (slotRotate90 ())); + d->actionRotateRight->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Right); + + d->actionSkew = ac->addAction ("image_skew"); + d->actionSkew->setText (i18n ("S&kew...")); + connect (d->actionSkew, SIGNAL (triggered (bool)), SLOT (slotSkew ())); + d->actionSkew->setShortcut(Qt::CTRL + Qt::Key_K); + + d->actionConvertToBlackAndWhite = ac->addAction ("image_convert_to_black_and_white"); + d->actionConvertToBlackAndWhite->setText (i18n ("Reduce to Mo&nochrome (Dithered)")); + connect (d->actionConvertToBlackAndWhite, SIGNAL (triggered (bool)), SLOT (slotConvertToBlackAndWhite ())); + + d->actionConvertToGrayscale = ac->addAction ("image_convert_to_grayscale"); + d->actionConvertToGrayscale->setText (i18n ("Reduce to &Grayscale")); + connect (d->actionConvertToGrayscale, SIGNAL (triggered (bool)), SLOT (slotConvertToGrayscale ())); + + d->actionInvertColors = ac->addAction ("image_invert_colors"); + d->actionInvertColors->setText (i18n ("&Invert Colors")); + connect (d->actionInvertColors, SIGNAL (triggered (bool)), SLOT (slotInvertColors ())); + d->actionInvertColors->setShortcut(Qt::CTRL + Qt::Key_I); + + d->actionClear = ac->addAction ("image_clear"); + d->actionClear->setText (i18n ("C&lear")); + connect (d->actionClear, SIGNAL (triggered (bool)), SLOT (slotClear ())); + d->actionClear->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_N); + + d->actionMoreEffects = ac->addAction ("image_more_effects"); + d->actionMoreEffects->setText (i18n ("&More Effects...")); + connect (d->actionMoreEffects, SIGNAL (triggered (bool)), SLOT (slotMoreEffects ())); + d->actionMoreEffects->setShortcut(Qt::CTRL + Qt::Key_M); + + + enableImageMenuDocumentActions (false); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableImageMenuDocumentActions (bool enable) +{ + d->actionResizeScale->setEnabled (enable); + d->actionCrop->setEnabled (enable); + d->actionAutoCrop->setEnabled (enable); + d->actionFlip->setEnabled (enable); + d->actionMirror->setEnabled (enable); + d->actionRotate->setEnabled (enable); + d->actionRotateLeft->setEnabled (enable); + d->actionRotateRight->setEnabled (enable); + d->actionSkew->setEnabled (enable); + d->actionConvertToBlackAndWhite->setEnabled (enable); + d->actionConvertToGrayscale->setEnabled (enable); + d->actionInvertColors->setEnabled (enable); + d->actionClear->setEnabled (enable); + d->actionMoreEffects->setEnabled (enable); + + d->imageMenuDocumentActionsEnabled = enable; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotImageMenuUpdateDueToSelection () +{ + // SYNC: kolourpaintui.rc + const QString MenuBarItemTextImage = i18nc ( + "Image/Selection Menu caption - make sure the translation has" + " the same accel as the Select&ion translation", + "&Image"); + const QString MenuBarItemTextSelection = i18nc ( + "Image/Selection Menu caption - make sure that translation has" + " the same accel as the &Image translation", + "Select&ion"); + + Q_ASSERT (menuBar ()); + foreach (QAction *action, menuBar ()->actions ()) + { + if (action->text () == MenuBarItemTextImage || + action->text () == MenuBarItemTextSelection) + { + if (isSelectionActive ()) + action->setText (MenuBarItemTextSelection); + else + action->setText (MenuBarItemTextImage); + + break; + } + } + + + d->actionResizeScale->setEnabled (d->imageMenuDocumentActionsEnabled); + d->actionCrop->setEnabled (d->imageMenuDocumentActionsEnabled && + isSelectionActive ()); + + const bool enable = (d->imageMenuDocumentActionsEnabled && !isTextSelection ()); + d->actionAutoCrop->setText (autoCropText ()); + d->actionAutoCrop->setEnabled (enable); + d->actionFlip->setEnabled (enable); + d->actionMirror->setEnabled (enable); + d->actionRotate->setEnabled (enable); + d->actionRotateLeft->setEnabled (enable); + d->actionRotateRight->setEnabled (enable); + d->actionSkew->setEnabled (enable); + d->actionConvertToBlackAndWhite->setEnabled (enable); + d->actionConvertToGrayscale->setEnabled (enable); + d->actionInvertColors->setEnabled (enable); + d->actionClear->setEnabled (enable); + d->actionMoreEffects->setEnabled (enable); +} + +//--------------------------------------------------------------------- + +// public +kpColor kpMainWindow::backgroundColor (bool ofSelection) const +{ + if (ofSelection) + return kpColor::Transparent; + else + { + Q_ASSERT (d->colorToolBar); + return d->colorToolBar->backgroundColor (); + } +} + +//--------------------------------------------------------------------- + +// public +// REFACTOR: sync: Code dup with kpAbstractSelectionTool::addNeedingContentCommand(). +void kpMainWindow::addImageOrSelectionCommand (kpCommand *cmd, + bool addSelCreateCmdIfSelAvail, + bool addSelContentCmdIfSelAvail) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::addImageOrSelectionCommand()" + << " addSelCreateCmdIfSelAvail=" << addSelCreateCmdIfSelAvail + << " addSelContentCmdIfSelAvail=" << addSelContentCmdIfSelAvail + << endl; +#endif + + Q_ASSERT (d->document); + + + if (d->viewManager) + d->viewManager->setQueueUpdates (); + + + kpAbstractSelection *sel = d->document->selection (); +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "\timage sel=" << sel + << " sel->hasContent=" << (sel ? sel->hasContent () : 0) + << endl; +#endif + if (addSelCreateCmdIfSelAvail && sel && !sel->hasContent ()) + { + QString createCmdName; + + if (dynamic_cast (sel)) + createCmdName = i18n ("Selection: Create"); + else if (dynamic_cast (sel)) + createCmdName = i18n ("Text: Create Box"); + else + Q_ASSERT (!"Unknown selection type"); + + // create selection region + commandHistory ()->addCreateSelectionCommand ( + new kpToolSelectionCreateCommand ( + createCmdName, + *sel, + commandEnvironment ()), + false/*no exec - user already dragged out sel*/); + } + + + if (addSelContentCmdIfSelAvail && sel && !sel->hasContent ()) + { + kpAbstractImageSelection *imageSel = + dynamic_cast (sel); + kpTextSelection *textSel = + dynamic_cast (sel); + + if (imageSel && imageSel->transparency ().isTransparent ()) + d->colorToolBar->flashColorSimilarityToolBarItem (); + + kpMacroCommand *macroCmd = new kpMacroCommand (cmd->name (), + commandEnvironment ()); + + if (imageSel) + { + macroCmd->addCommand ( + new kpToolSelectionPullFromDocumentCommand ( + *imageSel, + backgroundColor (), + QString()/*uninteresting child of macro cmd*/, + commandEnvironment ())); + } + else if (textSel) + { + macroCmd->addCommand ( + new kpToolTextGiveContentCommand ( + *textSel, + QString()/*uninteresting child of macro cmd*/, + commandEnvironment ())); + } + else + Q_ASSERT (!"Unknown selection type"); + + macroCmd->addCommand (cmd); + + d->commandHistory->addCommand (macroCmd); + } + else + { + d->commandHistory->addCommand (cmd); + } + + + if (d->viewManager) + d->viewManager->restoreQueueUpdates (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotResizeScale () +{ + toolEndShape (); + + kpTransformResizeScaleDialog dialog(transformDialogEnvironment(), this); + + if (dialog.exec () && !dialog.isNoOp ()) + { + kpTransformResizeScaleCommand *cmd = new kpTransformResizeScaleCommand ( + dialog.actOnSelection (), + dialog.imageWidth (), dialog.imageHeight (), + dialog.type (), + commandEnvironment ()); + + bool addSelCreateCommand = (dialog.actOnSelection () || + cmd->scaleSelectionWithImage ()); + bool addSelContentCommand = dialog.actOnSelection (); + + addImageOrSelectionCommand ( + cmd, + addSelCreateCommand, + addSelContentCommand); + + // Resized document? + if (!dialog.actOnSelection () && + dialog.type () == kpTransformResizeScaleCommand::Resize) + { + // TODO: this should be the responsibility of kpDocument + saveDefaultDocSize (QSize (dialog.imageWidth (), dialog.imageHeight ())); + } + } +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotCrop () +{ + toolEndShape (); + + Q_ASSERT (d->document && d->document->selection ()); + + + ::kpTransformCrop (this); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotAutoCrop () +{ + toolEndShape (); + + ::kpTransformAutoCrop (this); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotFlip() +{ + toolEndShape(); + + addImageOrSelectionCommand( + new kpTransformFlipCommand(d->document->selection(), + false, true, commandEnvironment())); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotMirror() +{ + toolEndShape(); + + addImageOrSelectionCommand( + new kpTransformFlipCommand(d->document->selection(), + true, false, commandEnvironment())); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotRotate () +{ + toolEndShape (); + + kpTransformRotateDialog dialog ((bool) d->document->selection (), + transformDialogEnvironment (), this); + + if (dialog.exec () && !dialog.isNoOp ()) + { + addImageOrSelectionCommand ( + new kpTransformRotateCommand (d->document->selection (), + dialog.angle (), + commandEnvironment ())); + } +} + +// private slot +void kpMainWindow::slotRotate270 () +{ + toolEndShape (); + + // TODO: Special command name instead of just "Rotate"? + addImageOrSelectionCommand ( + new kpTransformRotateCommand ( + d->document->selection (), + 270, + commandEnvironment ())); +} + +// private slot +void kpMainWindow::slotRotate90 () +{ + toolEndShape (); + + // TODO: Special command name instead of just "Rotate"? + addImageOrSelectionCommand ( + new kpTransformRotateCommand ( + d->document->selection (), + 90, + commandEnvironment ())); +} + + +// private slot +void kpMainWindow::slotSkew () +{ + toolEndShape (); + + kpTransformSkewDialog dialog ((bool) d->document->selection (), + transformDialogEnvironment (), this); + + if (dialog.exec () && !dialog.isNoOp ()) + { + addImageOrSelectionCommand ( + new kpTransformSkewCommand (d->document->selection (), + dialog.horizontalAngle (), dialog.verticalAngle (), + commandEnvironment ())); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotConvertToBlackAndWhite () +{ + toolEndShape (); + + addImageOrSelectionCommand ( + new kpEffectReduceColorsCommand (1/*depth*/, true/*dither*/, + d->document->selection (), + commandEnvironment ())); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotConvertToGrayscale () +{ + toolEndShape (); + + addImageOrSelectionCommand ( + new kpEffectGrayscaleCommand (d->document->selection (), + commandEnvironment ())); +} + +// private slot +void kpMainWindow::slotInvertColors () +{ + toolEndShape (); + + addImageOrSelectionCommand ( + new kpEffectInvertCommand (d->document->selection (), + commandEnvironment ())); +} + +// private slot +void kpMainWindow::slotClear () +{ + toolEndShape (); + + addImageOrSelectionCommand ( + new kpEffectClearCommand ( + d->document->selection (), + backgroundColor (), + commandEnvironment ())); +} + +// private slot +void kpMainWindow::slotMoreEffects () +{ + toolEndShape (); + + kpEffectsDialog dialog ((bool) d->document->selection (), + transformDialogEnvironment (), this, + d->moreEffectsDialogLastEffect); + + if (dialog.exec () && !dialog.isNoOp ()) + { + addImageOrSelectionCommand (dialog.createCommand ()); + } + + + if (d->moreEffectsDialogLastEffect != dialog.selectedEffect ()) + { + d->moreEffectsDialogLastEffect = dialog.selectedEffect (); + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupGeneral); + + cfg.writeEntry (kpSettingMoreEffectsLastEffect, + d->moreEffectsDialogLastEffect); + cfg.sync (); + } +} diff --git a/kolourpaint/mainWindow/kpMainWindow_Settings.cpp b/kolourpaint/mainWindow/kpMainWindow_Settings.cpp new file mode 100644 index 00000000..889306ff --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow_Settings.cpp @@ -0,0 +1,145 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupSettingsMenuActions () +{ + KActionCollection *ac = actionCollection (); + + + // Settings/Toolbars |> %s + setStandardToolBarMenuEnabled (true); + + // Settings/Show Statusbar + createStandardStatusBarAction (); + + + d->actionFullScreen = KStandardAction::fullScreen (this, SLOT (slotFullScreen ()), + this/*window*/, ac); + + + d->actionShowPath = ac->add ("settings_show_path"); + d->actionShowPath->setText (i18n ("Show &Path")); + connect(d->actionShowPath, SIGNAL(triggered(bool) ), SLOT (slotShowPathToggled ())); + //d->actionShowPath->setCheckedState (KGuiItem(i18n ("Hide &Path"))); + slotEnableSettingsShowPath (); + + + d->actionKeyBindings = KStandardAction::keyBindings (this, SLOT (slotKeyBindings ()), ac); + + KStandardAction::configureToolbars(this, SLOT(configureToolbars()), actionCollection()); + + enableSettingsMenuDocumentActions (false); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableSettingsMenuDocumentActions (bool /*enable*/) +{ +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotFullScreen () +{ + KToggleFullScreenAction::setFullScreen( this, d->actionFullScreen->isChecked ()); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotEnableSettingsShowPath () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotEnableSettingsShowPath()"; +#endif + + const bool enable = (d->document && !d->document->url ().isEmpty ()); + + d->actionShowPath->setEnabled (enable); + d->actionShowPath->setChecked (enable && d->configShowPath); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotShowPathToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotShowPathToggled()"; +#endif + + d->configShowPath = d->actionShowPath->isChecked (); + + slotUpdateCaption (); + + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupGeneral); + + cfg.writeEntry (kpSettingShowPath, d->configShowPath); + cfg.sync (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotKeyBindings () +{ + toolEndShape (); + + if (KShortcutsDialog::configure (actionCollection (), + KShortcutsEditor::LetterShortcutsAllowed, + this)) + { + // TODO: PROPAGATE: thru mainWindow's and interprocess + } +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/mainWindow/kpMainWindow_StatusBar.cpp b/kolourpaint/mainWindow/kpMainWindow_StatusBar.cpp new file mode 100644 index 00000000..d91e4db2 --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow_StatusBar.cpp @@ -0,0 +1,438 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_STATUS_BAR (DEBUG_KP_MAIN_WINDOW && 0) + + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::addPermanentStatusBarItem (int id, int maxTextLen) +{ + KStatusBar *sb = statusBar (); + + QString textWithMaxLen; + textWithMaxLen.fill (QString::number (8/*big fat*/).at (0), + maxTextLen); //+ 2/*spaces on either side*/); + + // Permanent --> place on the right + sb->insertPermanentFixedItem (textWithMaxLen, id); + sb->changeItem (QString(), id); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::createStatusBar () +{ + KStatusBar *sb = statusBar (); + + // 9999 pixels "ought to be enough for anybody" + const int maxDimenLength = 4; + + d->statusBarMessageLabel = new KSqueezedTextLabel(sb); + // this is done to have the same height as the other labels in status bar; done like in kstatusbar.cpp + d->statusBarMessageLabel->setFixedHeight(d->statusBarMessageLabel->fontMetrics().height() + 2); + d->statusBarMessageLabel->setTextElideMode(Qt::ElideRight); // this is the reason why we explicitly set a widget + sb->addWidget(d->statusBarMessageLabel, 1/*stretch*/); + + addPermanentStatusBarItem (StatusBarItemShapePoints, + (maxDimenLength + 1/*,*/ + maxDimenLength) * 2 + 3/* - */); + addPermanentStatusBarItem (StatusBarItemShapeSize, + (1/*+/-*/ + maxDimenLength) * 2 + 1/*x*/); + + QString numSample = i18n("%1 x %2", 5000, 5000); // localized string; can e.g. be "5 000" + addPermanentStatusBarItem(StatusBarItemDocSize, numSample.length()); + + addPermanentStatusBarItem(StatusBarItemDocDepth, 5/*XXbpp*/); + + addPermanentStatusBarItem (StatusBarItemZoom, + 5/*1600%*/); + + d->statusBarShapeLastPointsInitialised = false; + d->statusBarShapeLastSizeInitialised = false; + d->statusBarCreated = true; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::setStatusBarMessage (const QString &message) +{ +#if DEBUG_STATUS_BAR && 1 + kDebug () << "kpMainWindow::setStatusBarMessage(" + << message + << ") ok=" << d->statusBarCreated + << endl; +#endif + + if (!d->statusBarCreated) + return; + + d->statusBarMessageLabel->setText (message); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::setStatusBarShapePoints (const QPoint &startPoint, + const QPoint &endPoint) +{ +#if DEBUG_STATUS_BAR && 0 + kDebug () << "kpMainWindow::setStatusBarShapePoints(" + << startPoint << "," << endPoint + << ") ok=" << d->statusBarCreated + << endl; +#endif + + if (!d->statusBarCreated) + return; + + if (d->statusBarShapeLastPointsInitialised && + startPoint == d->statusBarShapeLastStartPoint && + endPoint == d->statusBarShapeLastEndPoint) + { + #if DEBUG_STATUS_BAR && 0 + kDebug () << "\tNOP"; + #endif + return; + } + + if (startPoint == KP_INVALID_POINT) + { + statusBar ()->changeItem (QString(), StatusBarItemShapePoints); + } + else if (endPoint == KP_INVALID_POINT) + { + statusBar ()->changeItem (i18n ("%1,%2", + startPoint.x (), + startPoint.y ()), + StatusBarItemShapePoints); + } + else + { + statusBar ()->changeItem (i18n ("%1,%2 - %3,%4", + startPoint.x (), + startPoint.y (), + endPoint.x (), + endPoint.y ()), + StatusBarItemShapePoints); + } + + d->statusBarShapeLastStartPoint = startPoint; + d->statusBarShapeLastEndPoint = endPoint; + d->statusBarShapeLastPointsInitialised = true; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::setStatusBarShapeSize (const QSize &size) +{ +#if DEBUG_STATUS_BAR && 0 + kDebug () << "kpMainWindow::setStatusBarShapeSize(" + << size + << ") ok=" << d->statusBarCreated + << endl; +#endif + + if (!d->statusBarCreated) + return; + + if (d->statusBarShapeLastSizeInitialised && + size == d->statusBarShapeLastSize) + { + #if DEBUG_STATUS_BAR && 0 + kDebug () << "\tNOP"; + #endif + return; + } + + if (size == KP_INVALID_SIZE) + { + statusBar ()->changeItem (QString(), StatusBarItemShapeSize); + } + else + { + statusBar ()->changeItem (i18n ("%1x%2", + size.width (), + size.height ()), + StatusBarItemShapeSize); + } + + d->statusBarShapeLastSize = size; + d->statusBarShapeLastSizeInitialised = true; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::setStatusBarDocSize (const QSize &size) +{ +#if DEBUG_STATUS_BAR && 0 + kDebug () << "kpMainWindow::setStatusBarDocSize(" + << size + << ") ok=" << d->statusBarCreated + << endl; +#endif + + if (!d->statusBarCreated) + return; + + if (size == KP_INVALID_SIZE) + { + statusBar ()->changeItem (QString(), StatusBarItemDocSize); + } + else + { + statusBar ()->changeItem (i18n ("%1 x %2", + size.width (), + size.height ()), + StatusBarItemDocSize); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::setStatusBarDocDepth (int depth) +{ +#if DEBUG_STATUS_BAR && 0 + kDebug () << "kpMainWindow::setStatusBarDocDepth(" + << depth + << ") ok=" << d->statusBarCreated + << endl; +#endif + + if (!d->statusBarCreated) + return; + + if (depth <= 0) + { + statusBar ()->changeItem (QString(), StatusBarItemDocDepth); + } + else + { + statusBar ()->changeItem (i18n ("%1bpp", depth), + StatusBarItemDocDepth); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::setStatusBarZoom (int zoom) +{ +#if DEBUG_STATUS_BAR && 0 + kDebug () << "kpMainWindow::setStatusBarZoom(" + << zoom + << ") ok=" << d->statusBarCreated + << endl; +#endif + + if (!d->statusBarCreated) + return; + + if (zoom <= 0) + { + statusBar ()->changeItem (QString(), StatusBarItemZoom); + } + else + { + statusBar ()->changeItem (i18n ("%1%", zoom), + StatusBarItemZoom); + } +} + +//--------------------------------------------------------------------- + +void kpMainWindow::recalculateStatusBarMessage () +{ +#if DEBUG_STATUS_BAR && 1 + kDebug () << "kpMainWindow::recalculateStatusBarMessage()"; +#endif + QString scrollViewMessage = d->scrollView->statusMessage (); +#if DEBUG_STATUS_BAR && 1 + kDebug () << "\tscrollViewMessage=" << scrollViewMessage; + kDebug () << "\tresizing doc? " << !d->scrollView->newDocSize ().isEmpty () + << endl; + kDebug () << "\tviewUnderCursor? " + << (d->viewManager && d->viewManager->viewUnderCursor ()) + << endl; +#endif + + // HACK: To work around kpViewScrollableContainer's unreliable + // status messages (which in turn is due to Qt not updating + // QWidget::underMouse() on drags and we needing to hack around it) + if (!scrollViewMessage.isEmpty () && + d->scrollView->newDocSize ().isEmpty () && + d->viewManager && d->viewManager->viewUnderCursor ()) + { + #if DEBUG_STATUS_BAR && 1 + kDebug () << "\t\tnot resizing & viewUnderCursor - message is wrong - clearing" + << endl; + #endif + d->scrollView->blockSignals (true); + d->scrollView->clearStatusMessage (); + d->scrollView->blockSignals (false); + + scrollViewMessage.clear (); + #if DEBUG_STATUS_BAR && 1 + kDebug () << "\t\t\tdone"; + #endif + } + + if (!scrollViewMessage.isEmpty ()) + { + setStatusBarMessage (scrollViewMessage); + } + else + { + const kpTool *t = tool (); + if (t) + { + setStatusBarMessage (t->userMessage ()); + } + else + { + setStatusBarMessage (); + } + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::recalculateStatusBarShape () +{ +#if DEBUG_STATUS_BAR && 0 + kDebug () << "kpMainWindow::recalculateStatusBarShape()"; +#endif + + QSize docResizeTo = d->scrollView->newDocSize (); +#if DEBUG_STATUS_BAR && 0 + kDebug () << "\tdocResizeTo=" << docResizeTo; +#endif + if (docResizeTo.isValid ()) + { + const QPoint startPoint (d->document->width (), d->document->height ()); + #if DEBUG_STATUS_BAR && 0 + kDebug () << "\thavedMovedFromOrgSize=" + << d->scrollView->haveMovedFromOriginalDocSize () << endl; + #endif + if (!d->scrollView->haveMovedFromOriginalDocSize ()) + { + setStatusBarShapePoints (startPoint); + setStatusBarShapeSize (); + } + else + { + const int newWidth = docResizeTo.width (); + const int newHeight = docResizeTo.height (); + + setStatusBarShapePoints (startPoint, QPoint (newWidth, newHeight)); + const QPoint sizeAsPoint (QPoint (newWidth, newHeight) - startPoint); + setStatusBarShapeSize (QSize (sizeAsPoint.x (), sizeAsPoint.y ())); + } + } + else + { + const kpTool *t = tool (); + #if DEBUG_STATUS_BAR && 0 + kDebug () << "\ttool=" << t; + #endif + if (t) + { + setStatusBarShapePoints (t->userShapeStartPoint (), + t->userShapeEndPoint ()); + setStatusBarShapeSize (t->userShapeSize ()); + } + else + { + setStatusBarShapePoints (); + setStatusBarShapeSize (); + } + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::recalculateStatusBar () +{ +#if DEBUG_STATUS_BAR && 1 + kDebug () << "kpMainWindow::recalculateStatusBar() ok=" + << d->statusBarCreated + << endl; +#endif + + if (!d->statusBarCreated) + return; + + recalculateStatusBarMessage (); + recalculateStatusBarShape (); + + if (d->document) + { + setStatusBarDocSize (QSize (d->document->width (), d->document->height ())); + setStatusBarDocDepth (d->document->image ().depth ()); + } + else + { + setStatusBarDocSize (); + setStatusBarDocDepth (); + } + + if (d->mainView) + { + setStatusBarZoom (d->mainView->zoomLevelX ()); + } + else + { + setStatusBarZoom (); + } +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/mainWindow/kpMainWindow_Text.cpp b/kolourpaint/mainWindow/kpMainWindow_Text.cpp new file mode 100644 index 00000000..588dd04e --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow_Text.cpp @@ -0,0 +1,435 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +// private +void kpMainWindow::setupTextToolBarActions () +{ + KActionCollection *ac = actionCollection (); + + d->actionTextFontFamily = ac->add ("text_font_family"); + d->actionTextFontFamily->setText (i18n ("Font Family")); + connect (d->actionTextFontFamily, SIGNAL (triggered (const QString &)), + this, SLOT (slotTextFontFamilyChanged ())); + + d->actionTextFontSize = ac->add ("text_font_size"); + d->actionTextFontSize->setText (i18n ("Font Size")); + connect (d->actionTextFontSize, SIGNAL (fontSizeChanged (int)), + this, SLOT (slotTextFontSizeChanged ())); + + d->actionTextBold = ac->add ("text_bold"); + d->actionTextBold->setIcon (KIcon ("format-text-bold")); + d->actionTextBold->setText (i18n ("Bold")); + connect (d->actionTextBold, SIGNAL (triggered (bool)), + SLOT (slotTextBoldChanged ())); + + d->actionTextItalic = ac->add ("text_italic"); + d->actionTextItalic->setIcon (KIcon ("format-text-italic")); + d->actionTextItalic->setText (i18n ("Italic")); + connect (d->actionTextItalic, SIGNAL (triggered (bool)), + SLOT (slotTextItalicChanged ())); + + d->actionTextUnderline = ac->add ("text_underline"); + d->actionTextUnderline->setIcon (KIcon ("format-text-underline")); + d->actionTextUnderline->setText (i18n ("Underline")); + connect (d->actionTextUnderline, SIGNAL (triggered (bool)), + SLOT (slotTextUnderlineChanged ())); + + d->actionTextStrikeThru = ac->add ("text_strike_thru"); + d->actionTextStrikeThru->setIcon (KIcon ("format-text-strikethrough")); + d->actionTextStrikeThru->setText (i18n ("Strike Through")); + connect (d->actionTextStrikeThru, SIGNAL (triggered (bool)), + SLOT (slotTextStrikeThruChanged ())); + + + readAndApplyTextSettings (); + + + enableTextToolBarActions (false); +} + +// private +void kpMainWindow::readAndApplyTextSettings () +{ + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupText); + + const QString font (cfg.readEntry (kpSettingFontFamily, QString::fromLatin1 ("Times"))); + d->actionTextFontFamily->setFont (font); +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "asked setFont to set to=" << font + << "- got back=" << d->actionTextFontFamily->font (); +#endif + d->actionTextFontSize->setFontSize (cfg.readEntry (kpSettingFontSize, 14)); + d->actionTextBold->setChecked (cfg.readEntry (kpSettingBold, false)); + d->actionTextItalic->setChecked (cfg.readEntry (kpSettingItalic, false)); + d->actionTextUnderline->setChecked (cfg.readEntry (kpSettingUnderline, false)); + d->actionTextStrikeThru->setChecked (cfg.readEntry (kpSettingStrikeThru, false)); + + d->textOldFontFamily = d->actionTextFontFamily->font (); + d->textOldFontSize = d->actionTextFontSize->fontSize (); +} + + +// public +void kpMainWindow::enableTextToolBarActions (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::enableTextToolBarActions(" << enable << ")"; +#endif + + d->actionTextFontFamily->setEnabled (enable); + d->actionTextFontSize->setEnabled (enable); + d->actionTextBold->setEnabled (enable); + d->actionTextItalic->setEnabled (enable); + d->actionTextUnderline->setEnabled (enable); + d->actionTextStrikeThru->setEnabled (enable); + + if (textToolBar ()) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\thave toolbar - setShown"; + #endif + // COMPAT: KDE4 does not place the Text Tool Bar in a new row, underneath + // the Main Tool Bar, if there isn't enough room. This makes + // accessing the Text Tool Bar's buttons difficult. + textToolBar ()->setVisible (enable); + } +} + + +// private slot +void kpMainWindow::slotTextFontFamilyChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotTextFontFamilyChanged() alive=" + << d->isFullyConstructed + << "fontFamily=" + << d->actionTextFontFamily->font () + << "action.currentItem=" + << d->actionTextFontFamily->currentItem () + << endl; +#endif + + if (!d->isFullyConstructed) + return; + + if (d->toolText && d->toolText->hasBegun ()) + { + toolEndShape (); + d->toolText->slotFontFamilyChanged (d->actionTextFontFamily->font (), + d->textOldFontFamily); + } + + // Since editable KSelectAction's steal focus from view, switch back to mainView + // TODO: back to the last view + if (d->mainView) + d->mainView->setFocus (); + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupText); + cfg.writeEntry (kpSettingFontFamily, d->actionTextFontFamily->font ()); + cfg.sync (); + + d->textOldFontFamily = d->actionTextFontFamily->font (); +} + +// private slot +void kpMainWindow::slotTextFontSizeChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotTextFontSizeChanged() alive=" + << d->isFullyConstructed + << " fontSize=" + << d->actionTextFontSize->fontSize () + << endl; +#endif + + if (!d->isFullyConstructed) + return; + + if (d->toolText && d->toolText->hasBegun ()) + { + toolEndShape (); + d->toolText->slotFontSizeChanged (d->actionTextFontSize->fontSize (), + d->textOldFontSize); + } + + // Since editable KSelectAction's steal focus from view, switch back to mainView + // TODO: back to the last view + if (d->mainView) + d->mainView->setFocus (); + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupText); + cfg.writeEntry (kpSettingFontSize, d->actionTextFontSize->fontSize ()); + cfg.sync (); + + d->textOldFontSize = d->actionTextFontSize->fontSize (); +} + +// private slot +void kpMainWindow::slotTextBoldChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotTextFontBoldChanged() alive=" + << d->isFullyConstructed + << " bold=" + << d->actionTextBold->isChecked () + << endl; +#endif + + if (!d->isFullyConstructed) + return; + + if (d->toolText && d->toolText->hasBegun ()) + { + toolEndShape (); + d->toolText->slotBoldChanged (d->actionTextBold->isChecked ()); + } + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupText); + cfg.writeEntry (kpSettingBold, d->actionTextBold->isChecked ()); + cfg.sync (); +} + +// private slot +void kpMainWindow::slotTextItalicChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotTextFontItalicChanged() alive=" + << d->isFullyConstructed + << " bold=" + << d->actionTextItalic->isChecked () + << endl; +#endif + + if (!d->isFullyConstructed) + return; + + if (d->toolText && d->toolText->hasBegun ()) + { + toolEndShape (); + d->toolText->slotItalicChanged (d->actionTextItalic->isChecked ()); + } + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupText); + cfg.writeEntry (kpSettingItalic, d->actionTextItalic->isChecked ()); + cfg.sync (); +} + +// private slot +void kpMainWindow::slotTextUnderlineChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotTextFontUnderlineChanged() alive=" + << d->isFullyConstructed + << " underline=" + << d->actionTextUnderline->isChecked () + << endl; +#endif + + if (!d->isFullyConstructed) + return; + + if (d->toolText && d->toolText->hasBegun ()) + { + toolEndShape (); + d->toolText->slotUnderlineChanged (d->actionTextUnderline->isChecked ()); + } + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupText); + cfg.writeEntry (kpSettingUnderline, d->actionTextUnderline->isChecked ()); + cfg.sync (); +} + +// private slot +void kpMainWindow::slotTextStrikeThruChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotTextStrikeThruChanged() alive=" + << d->isFullyConstructed + << " strikeThru=" + << d->actionTextStrikeThru->isChecked () + << endl; +#endif + + if (!d->isFullyConstructed) + return; + + if (d->toolText && d->toolText->hasBegun ()) + { + toolEndShape (); + d->toolText->slotStrikeThruChanged (d->actionTextStrikeThru->isChecked ()); + } + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupText); + cfg.writeEntry (kpSettingStrikeThru, d->actionTextStrikeThru->isChecked ()); + cfg.sync (); +} + + +// public +KToolBar *kpMainWindow::textToolBar () +{ + return toolBar ("textToolBar"); +} + +bool kpMainWindow::isTextStyleBackgroundOpaque () const +{ + if (d->toolToolBar) + { + kpToolWidgetOpaqueOrTransparent *oot = + d->toolToolBar->toolWidgetOpaqueOrTransparent (); + + if (oot) + { + return oot->isOpaque (); + } + } + + return true; +} + +// public +kpTextStyle kpMainWindow::textStyle () const +{ + return kpTextStyle (d->actionTextFontFamily->font (), + d->actionTextFontSize->fontSize (), + d->actionTextBold->isChecked (), + d->actionTextItalic->isChecked (), + d->actionTextUnderline->isChecked (), + d->actionTextStrikeThru->isChecked (), + d->colorToolBar ? d->colorToolBar->foregroundColor () : kpColor::Invalid, + d->colorToolBar ? d->colorToolBar->backgroundColor () : kpColor::Invalid, + isTextStyleBackgroundOpaque ()); +} + +// public +void kpMainWindow::setTextStyle (const kpTextStyle &textStyle_) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::setTextStyle()"; +#endif + + d->settingTextStyle++; + + + if (textStyle_.fontFamily () != d->actionTextFontFamily->font ()) + { + d->actionTextFontFamily->setFont (textStyle_.fontFamily ()); + slotTextFontFamilyChanged (); + } + + if (textStyle_.fontSize () != d->actionTextFontSize->fontSize ()) + { + d->actionTextFontSize->setFontSize (textStyle_.fontSize ()); + slotTextFontSizeChanged (); + } + + if (textStyle_.isBold () != d->actionTextBold->isChecked ()) + { + d->actionTextBold->setChecked (textStyle_.isBold ()); + slotTextBoldChanged (); + } + + if (textStyle_.isItalic () != d->actionTextItalic->isChecked ()) + { + d->actionTextItalic->setChecked (textStyle_.isItalic ()); + slotTextItalicChanged (); + } + + if (textStyle_.isUnderline () != d->actionTextUnderline->isChecked ()) + { + d->actionTextUnderline->setChecked (textStyle_.isUnderline ()); + slotTextUnderlineChanged (); + } + + if (textStyle_.isStrikeThru () != d->actionTextStrikeThru->isChecked ()) + { + d->actionTextStrikeThru->setChecked (textStyle_.isStrikeThru ()); + slotTextStrikeThruChanged (); + } + + + if (textStyle_.foregroundColor () != d->colorToolBar->foregroundColor ()) + { + d->colorToolBar->setForegroundColor (textStyle_.foregroundColor ()); + } + + if (textStyle_.backgroundColor () != d->colorToolBar->backgroundColor ()) + { + d->colorToolBar->setBackgroundColor (textStyle_.backgroundColor ()); + } + + + if (textStyle_.isBackgroundOpaque () != isTextStyleBackgroundOpaque ()) + { + if (d->toolToolBar) + { + kpToolWidgetOpaqueOrTransparent *oot = + d->toolToolBar->toolWidgetOpaqueOrTransparent (); + + if (oot) + { + oot->setOpaque (textStyle_.isBackgroundOpaque ()); + } + } + } + + + d->settingTextStyle--; +} + +// public +int kpMainWindow::settingTextStyle () const +{ + return d->settingTextStyle; +} + diff --git a/kolourpaint/mainWindow/kpMainWindow_Tools.cpp b/kolourpaint/mainWindow/kpMainWindow_Tools.cpp new file mode 100644 index 00000000..109da786 --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow_Tools.cpp @@ -0,0 +1,807 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +// private +kpToolSelectionEnvironment *kpMainWindow::toolSelectionEnvironment () +{ + if (!d->toolSelectionEnvironment) + d->toolSelectionEnvironment = new kpToolSelectionEnvironment (this); + + return d->toolSelectionEnvironment; +} + +//--------------------------------------------------------------------- + +// private +kpToolEnvironment *kpMainWindow::toolEnvironment () +{ + // It's fine to return a more complex environment than required. + return toolSelectionEnvironment (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupToolActions () +{ + kpToolSelectionEnvironment *toolSelEnv = toolSelectionEnvironment (); + kpToolEnvironment *toolEnv = toolEnvironment (); + + d->tools.append (d->toolFreeFormSelection = new kpToolFreeFormSelection (toolSelEnv, this)); + d->tools.append (d->toolRectSelection = new kpToolRectSelection (toolSelEnv, this)); + + d->tools.append (d->toolEllipticalSelection = new kpToolEllipticalSelection (toolSelEnv, this)); + d->tools.append (d->toolText = new kpToolText (toolSelEnv, this)); + + d->tools.append (d->toolLine = new kpToolLine (toolEnv, this)); + d->tools.append (d->toolPen = new kpToolPen (toolEnv, this)); + + d->tools.append (d->toolEraser = new kpToolEraser (toolEnv, this)); + d->tools.append (d->toolBrush = new kpToolBrush (toolEnv, this)); + + d->tools.append (d->toolFloodFill = new kpToolFloodFill (toolEnv, this)); + d->tools.append (d->toolColorPicker = new kpToolColorPicker (toolEnv, this)); + + d->tools.append (d->toolColorEraser = new kpToolColorEraser (toolEnv, this)); + d->tools.append (d->toolSpraycan = new kpToolSpraycan (toolEnv, this)); + + d->tools.append (d->toolRoundedRectangle = new kpToolRoundedRectangle (toolEnv, this)); + d->tools.append (d->toolRectangle = new kpToolRectangle (toolEnv, this)); + + d->tools.append (d->toolPolygon = new kpToolPolygon (toolEnv, this)); + d->tools.append (d->toolEllipse = new kpToolEllipse (toolEnv, this)); + + d->tools.append (d->toolPolyline = new kpToolPolyline (toolEnv, this)); + d->tools.append (d->toolCurve = new kpToolCurve (toolEnv, this)); + + d->tools.append (d->toolZoom = new kpToolZoom (toolEnv, this)); + + + KActionCollection *ac = actionCollection (); + + d->actionPrevToolOptionGroup1 = ac->addAction ("prev_tool_option_group_1"); + d->actionPrevToolOptionGroup1->setText (i18n ("Previous Tool Option (Group #1)")); + d->actionPrevToolOptionGroup1->setShortcuts (kpTool::shortcutForKey (Qt::Key_1)); + connect (d->actionPrevToolOptionGroup1, SIGNAL (triggered (bool)), + SLOT (slotActionPrevToolOptionGroup1 ())); + + d->actionNextToolOptionGroup1 = ac->addAction ("next_tool_option_group_1"); + d->actionNextToolOptionGroup1->setText (i18n ("Next Tool Option (Group #1)")); + d->actionNextToolOptionGroup1->setShortcuts (kpTool::shortcutForKey (Qt::Key_2)); + connect (d->actionNextToolOptionGroup1, SIGNAL (triggered (bool)), + SLOT (slotActionNextToolOptionGroup1 ())); + + d->actionPrevToolOptionGroup2 = ac->addAction ("prev_tool_option_group_2"); + d->actionPrevToolOptionGroup2->setText (i18n ("Previous Tool Option (Group #2)")); + d->actionPrevToolOptionGroup2->setShortcuts (kpTool::shortcutForKey (Qt::Key_3)); + connect (d->actionPrevToolOptionGroup2, SIGNAL (triggered (bool)), + SLOT (slotActionPrevToolOptionGroup2 ())); + + d->actionNextToolOptionGroup2 = ac->addAction ("next_tool_option_group_2"); + d->actionNextToolOptionGroup2->setText (i18n ("Next Tool Option (Group #2)")); + d->actionNextToolOptionGroup2->setShortcuts (kpTool::shortcutForKey (Qt::Key_4)); + connect (d->actionNextToolOptionGroup2, SIGNAL (triggered (bool)), + SLOT (slotActionNextToolOptionGroup2 ())); + + + // + // Implemented in this file (kpMainWindow_Tools.cpp), not + // kpImageWindow_Image.cpp since they're really setting tool options. + // + + d->actionDrawOpaque = ac->add ("image_draw_opaque"); + d->actionDrawOpaque->setText (i18n ("&Draw Opaque")); + connect (d->actionDrawOpaque, SIGNAL (triggered (bool)), + SLOT (slotActionDrawOpaqueToggled ())); + + d->actionDrawColorSimilarity = ac->addAction ("image_draw_color_similarity"); + d->actionDrawColorSimilarity->setText (i18n ("Draw With Color Similarity...")); + connect (d->actionDrawColorSimilarity, SIGNAL (triggered (bool)), + SLOT (slotActionDrawColorSimilarity ())); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::createToolBox () +{ + d->toolToolBar = new kpToolToolBar(QLatin1String("Tool Box"), 2/*columns/rows*/, this); + d->toolToolBar->setWindowTitle(i18n("Tool Box")); + + connect (d->toolToolBar, SIGNAL (sigToolSelected (kpTool *)), + this, SLOT (slotToolSelected (kpTool *))); + connect (d->toolToolBar, SIGNAL (toolWidgetOptionSelected ()), + this, SLOT (updateToolOptionPrevNextActionsEnabled ())); + + connect (d->toolToolBar->toolWidgetOpaqueOrTransparent (), + SIGNAL (isOpaqueChanged (bool)), + SLOT (updateActionDrawOpaqueChecked ())); + updateActionDrawOpaqueChecked (); + + foreach (kpTool *tool, d->tools) + d->toolToolBar->registerTool(tool); + + // (from config file) + readLastTool (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableToolsDocumentActions (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::enableToolsDocumentsAction(" << enable << ")"; +#endif + + d->toolActionsEnabled = enable; + + if (enable && !d->toolToolBar->isEnabled ()) + { + kpTool *previousTool = d->toolToolBar->previousTool (); + + // select tool for enabled Tool Box + + if (previousTool) + d->toolToolBar->selectPreviousTool (); + else + { + if (d->lastToolNumber >= 0 && d->lastToolNumber < (int) d->tools.count ()) + d->toolToolBar->selectTool (d->tools.at (d->lastToolNumber)); + else + d->toolToolBar->selectTool (d->toolPen); + } + } + else if (!enable && d->toolToolBar->isEnabled ()) + { + // don't have a disabled Tool Box with a checked Tool + d->toolToolBar->selectTool (0); + } + + + d->toolToolBar->setEnabled (enable); + + + foreach (kpTool *tool, d->tools) + { + kpToolAction *action = tool->action(); + if (!enable && action->isChecked()) + action->setChecked(false); + + action->setEnabled(enable); + } + + + updateToolOptionPrevNextActionsEnabled (); + updateActionDrawOpaqueEnabled (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::updateToolOptionPrevNextActionsEnabled () +{ + const bool enable = d->toolActionsEnabled; + + + d->actionPrevToolOptionGroup1->setEnabled (enable && + d->toolToolBar->shownToolWidget (0) && + d->toolToolBar->shownToolWidget (0)->hasPreviousOption ()); + d->actionNextToolOptionGroup1->setEnabled (enable && + d->toolToolBar->shownToolWidget (0) && + d->toolToolBar->shownToolWidget (0)->hasNextOption ()); + + d->actionPrevToolOptionGroup2->setEnabled (enable && + d->toolToolBar->shownToolWidget (1) && + d->toolToolBar->shownToolWidget (1)->hasPreviousOption ()); + d->actionNextToolOptionGroup2->setEnabled (enable && + d->toolToolBar->shownToolWidget (1) && + d->toolToolBar->shownToolWidget (1)->hasNextOption ()); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::updateActionDrawOpaqueChecked () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::updateActionDrawOpaqueChecked()"; +#endif + + const bool drawOpaque = + (d->toolToolBar->toolWidgetOpaqueOrTransparent ()->selectedRow () == 0); +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tdrawOpaque=" << drawOpaque; +#endif + + d->actionDrawOpaque->setChecked (drawOpaque); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::updateActionDrawOpaqueEnabled () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::updateActionDrawOpaqueEnabled()"; +#endif + + const bool enable = d->toolActionsEnabled; + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tenable=" << enable + << " tool=" << (tool () ? tool ()->objectName () : 0) + << " (is selection=" << toolIsASelectionTool () << ")" + << endl; +#endif + + d->actionDrawOpaque->setEnabled (enable && toolIsASelectionTool ()); +} + +//--------------------------------------------------------------------- + +// public +QActionGroup *kpMainWindow::toolsActionGroup () +{ + if (!d->toolsActionGroup) + d->toolsActionGroup = new QActionGroup (this); + + return d->toolsActionGroup; +} + +//--------------------------------------------------------------------- + +// public +kpTool *kpMainWindow::tool () const +{ + return d->toolToolBar ? d->toolToolBar->tool () : 0; +} + +//--------------------------------------------------------------------- + +// public +bool kpMainWindow::toolHasBegunShape () const +{ + kpTool *currentTool = tool (); + return (currentTool && currentTool->hasBegunShape ()); +} + +//--------------------------------------------------------------------- + +// public +bool kpMainWindow::toolIsASelectionTool (bool includingTextTool) const +{ + kpTool *currentTool = tool (); + + return ((currentTool == d->toolFreeFormSelection) || + (currentTool == d->toolRectSelection) || + (currentTool == d->toolEllipticalSelection) || + (currentTool == d->toolText && includingTextTool)); +} + +//--------------------------------------------------------------------- + +// public +bool kpMainWindow::toolIsTextTool () const +{ + return (tool () == d->toolText); +} + +//--------------------------------------------------------------------- + + +// private +void kpMainWindow::toolEndShape () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); +} + +//--------------------------------------------------------------------- + +// public +kpImageSelectionTransparency kpMainWindow::imageSelectionTransparency () const +{ + kpToolWidgetOpaqueOrTransparent *oot = d->toolToolBar->toolWidgetOpaqueOrTransparent (); + Q_ASSERT (oot); + + return kpImageSelectionTransparency (oot->isOpaque (), backgroundColor (), d->colorToolBar->colorSimilarity ()); +} + +//--------------------------------------------------------------------- + +// public +void kpMainWindow::setImageSelectionTransparency (const kpImageSelectionTransparency &transparency, bool forceColorChange) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::setImageSelectionTransparency() isOpaque=" << transparency.isOpaque () + << " color=" << (transparency.transparentColor ().isValid () ? (int *) transparency.transparentColor ().toQRgb () : 0) + << " forceColorChange=" << forceColorChange + << endl; +#endif + + kpToolWidgetOpaqueOrTransparent *oot = d->toolToolBar->toolWidgetOpaqueOrTransparent (); + Q_ASSERT (oot); + + d->settingImageSelectionTransparency++; + + oot->setOpaque (transparency.isOpaque ()); + if (transparency.isTransparent () || forceColorChange) + { + d->colorToolBar->setColor (1, transparency.transparentColor ()); + d->colorToolBar->setColorSimilarity (transparency.colorSimilarity ()); + } + + d->settingImageSelectionTransparency--; +} + +//--------------------------------------------------------------------- + +// public +int kpMainWindow::settingImageSelectionTransparency () const +{ + return d->settingImageSelectionTransparency; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotToolSelected (kpTool *tool) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotToolSelected (" << tool << ")"; +#endif + + kpTool *previousTool = d->toolToolBar ? d->toolToolBar->previousTool () : 0; + + if (previousTool) + { + disconnect (previousTool, SIGNAL (movedAndAboutToDraw (const QPoint &, const QPoint &, int, bool *)), + this, SLOT (slotDragScroll (const QPoint &, const QPoint &, int, bool *))); + disconnect (previousTool, SIGNAL (endedDraw (const QPoint &)), + this, SLOT (slotEndDragScroll ())); + disconnect (previousTool, SIGNAL (cancelledShape (const QPoint &)), + this, SLOT (slotEndDragScroll ())); + + disconnect (previousTool, SIGNAL (userMessageChanged (const QString &)), + this, SLOT (recalculateStatusBarMessage ())); + disconnect (previousTool, SIGNAL (userShapePointsChanged (const QPoint &, const QPoint &)), + this, SLOT (recalculateStatusBarShape ())); + disconnect (previousTool, SIGNAL (userShapeSizeChanged (const QSize &)), + this, SLOT (recalculateStatusBarShape ())); + + disconnect (d->colorToolBar, SIGNAL (colorsSwapped (const kpColor &, const kpColor &)), + previousTool, SLOT (slotColorsSwappedInternal (const kpColor &, const kpColor &))); + disconnect (d->colorToolBar, SIGNAL (foregroundColorChanged (const kpColor &)), + previousTool, SLOT (slotForegroundColorChangedInternal (const kpColor &))); + disconnect (d->colorToolBar, SIGNAL (backgroundColorChanged (const kpColor &)), + previousTool, SLOT (slotBackgroundColorChangedInternal (const kpColor &))); + disconnect (d->colorToolBar, SIGNAL (colorSimilarityChanged (double, int)), + previousTool, SLOT (slotColorSimilarityChangedInternal (double, int))); + } + + if (tool) + { + connect (tool, SIGNAL (movedAndAboutToDraw (const QPoint &, const QPoint &, int, bool *)), + this, SLOT (slotDragScroll (const QPoint &, const QPoint &, int, bool *))); + connect (tool, SIGNAL (endedDraw (const QPoint &)), + this, SLOT (slotEndDragScroll ())); + connect (tool, SIGNAL (cancelledShape (const QPoint &)), + this, SLOT (slotEndDragScroll ())); + + connect (tool, SIGNAL (userMessageChanged (const QString &)), + this, SLOT (recalculateStatusBarMessage ())); + connect (tool, SIGNAL (userShapePointsChanged (const QPoint &, const QPoint &)), + this, SLOT (recalculateStatusBarShape ())); + connect (tool, SIGNAL (userShapeSizeChanged (const QSize &)), + this, SLOT (recalculateStatusBarShape ())); + recalculateStatusBar (); + + connect (d->colorToolBar, SIGNAL (colorsSwapped (const kpColor &, const kpColor &)), + tool, SLOT (slotColorsSwappedInternal (const kpColor &, const kpColor &))); + connect (d->colorToolBar, SIGNAL (foregroundColorChanged (const kpColor &)), + tool, SLOT (slotForegroundColorChangedInternal (const kpColor &))); + connect (d->colorToolBar, SIGNAL (backgroundColorChanged (const kpColor &)), + tool, SLOT (slotBackgroundColorChangedInternal (const kpColor &))); + connect (d->colorToolBar, SIGNAL (colorSimilarityChanged (double, int)), + tool, SLOT (slotColorSimilarityChangedInternal (double, int))); + + + saveLastTool (); + } + + updateToolOptionPrevNextActionsEnabled (); + updateActionDrawOpaqueEnabled (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::readLastTool () +{ + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupTools); + + d->lastToolNumber = cfg.readEntry (kpSettingLastTool, -1); +} + +//--------------------------------------------------------------------- + +// private +int kpMainWindow::toolNumber () const +{ + int number = 0; + for (QList ::const_iterator it = d->tools.constBegin (); + it != d->tools.constEnd (); + ++it) + { + if (*it == tool ()) + return number; + + number++; + } + + return -1; +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::saveLastTool () +{ + int number = toolNumber (); + if (number < 0 || number >= (int) d->tools.count ()) + return; + + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupTools); + + cfg.writeEntry (kpSettingLastTool, number); + cfg.sync (); +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::maybeDragScrollingMainView () const +{ + return (tool () && d->mainView && + tool ()->viewUnderStartPoint () == d->mainView); +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotDragScroll (const QPoint &docPoint, + const QPoint &docLastPoint, + int zoomLevel, + bool *scrolled) +{ + Q_UNUSED(docPoint) + Q_UNUSED(docLastPoint) + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotDragScroll() maybeDragScrolling=" + << maybeDragScrollingMainView () + << endl; +#endif + + if (maybeDragScrollingMainView ()) + { + return d->scrollView->beginDragScroll(zoomLevel, scrolled); + } + else + { + return false; + } +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotEndDragScroll () +{ + // (harmless if haven't started drag scroll) + return d->scrollView->endDragScroll (); +} + +//--------------------------------------------------------------------- + + +// private slot +void kpMainWindow::slotBeganDocResize () +{ + toolEndShape (); + + recalculateStatusBarShape (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotContinuedDocResize (const QSize &) +{ + recalculateStatusBarShape (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotCancelledDocResize () +{ + recalculateStatusBar (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotEndedDocResize (const QSize &size) +{ +#define DOC_RESIZE_COMPLETED() \ +{ \ + d->docResizeToBeCompleted = false; \ + recalculateStatusBar (); \ +} + + // Prevent statusbar updates + d->docResizeToBeCompleted = true; + + d->docResizeWidth = (size.width () > 0 ? size.width () : 1), + d->docResizeHeight = (size.height () > 0 ? size.height () : 1); + + if (d->docResizeWidth == d->document->width () && + d->docResizeHeight == d->document->height ()) + { + DOC_RESIZE_COMPLETED (); + return; + } + + + // Blank status to avoid confusion if dialog comes up + setStatusBarMessage (); + setStatusBarShapePoints (); + setStatusBarShapeSize (); + + + if (kpTool::warnIfBigImageSize (d->document->width (), + d->document->height (), + d->docResizeWidth, d->docResizeHeight, + i18n ("

Resizing the image to" + " %1x%2 may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure want to resize the" + " image?

", + d->docResizeWidth, + d->docResizeHeight), + i18nc ("@title:window", "Resize Image?"), + i18n ("R&esize Image"), + this)) + { + d->commandHistory->addCommand ( + new kpTransformResizeScaleCommand ( + false/*doc, not sel*/, + d->docResizeWidth, d->docResizeHeight, + kpTransformResizeScaleCommand::Resize, + commandEnvironment ())); + + saveDefaultDocSize (QSize (d->docResizeWidth, d->docResizeHeight)); + } + + + DOC_RESIZE_COMPLETED (); + +#undef DOC_RESIZE_COMPLETED +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotDocResizeMessageChanged (const QString &string) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotDocResizeMessageChanged(" << string + << ") docResizeToBeCompleted=" << d->docResizeToBeCompleted + << endl; +#else + (void) string; +#endif + + if (d->docResizeToBeCompleted) + return; + + recalculateStatusBarMessage (); +} + +//--------------------------------------------------------------------- + + +// private slot +void kpMainWindow::slotActionPrevToolOptionGroup1 () +{ + if (!d->toolToolBar->shownToolWidget (0)) + return; + + // We don't call toolEndShape() here because we want #23 in the file BUGS + // to later work. + + d->toolToolBar->shownToolWidget (0)->selectPreviousOption (); + updateToolOptionPrevNextActionsEnabled (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotActionNextToolOptionGroup1 () +{ + if (!d->toolToolBar->shownToolWidget (0)) + return; + + // We don't call toolEndShape() here because we want #23 in the file BUGS + // to later work. + + d->toolToolBar->shownToolWidget (0)->selectNextOption (); + updateToolOptionPrevNextActionsEnabled (); +} + +//--------------------------------------------------------------------- + + +// private slot +void kpMainWindow::slotActionPrevToolOptionGroup2 () +{ + if (!d->toolToolBar->shownToolWidget (1)) + return; + + // We don't call toolEndShape() here because we want #23 in the file BUGS + // to later work. + + d->toolToolBar->shownToolWidget (1)->selectPreviousOption (); + updateToolOptionPrevNextActionsEnabled (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotActionNextToolOptionGroup2 () +{ + if (!d->toolToolBar->shownToolWidget (1)) + return; + + // We don't call toolEndShape() here because we want #23 in the file BUGS + // to later work. + + d->toolToolBar->shownToolWidget (1)->selectNextOption (); + updateToolOptionPrevNextActionsEnabled (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotActionDrawOpaqueToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotActionDrawOpaqueToggled()"; +#endif + toolEndShape (); + + // TODO: How does this differ to setImageSelectionTransparency()? + + // ("kpToolWidgetBase::" is to access one overload shadowed by the override + // of the other overload) + d->toolToolBar->toolWidgetOpaqueOrTransparent ()->kpToolWidgetBase::setSelected ( + (d->actionDrawOpaque->isChecked () ? + 0/*row 0 = opaque*/ : + 1/*row 1 = transparent*/), + 0/*column*/); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotActionDrawColorSimilarity () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotActionDrawColorSimilarity()"; +#endif + toolEndShape (); + + d->colorToolBar->openColorSimilarityDialog (); +} + +//--------------------------------------------------------------------- + + +// public slots + +#define SLOT_TOOL(toolName) \ +void kpMainWindow::slotTool##toolName () \ +{ \ + if (!d->toolToolBar) \ + return; \ + \ + if (tool () == d->tool##toolName) \ + return; \ + \ + d->toolToolBar->selectTool (d->tool##toolName); \ +} + + +SLOT_TOOL (RectSelection) +SLOT_TOOL (EllipticalSelection) +SLOT_TOOL (FreeFormSelection) +SLOT_TOOL (Text) diff --git a/kolourpaint/mainWindow/kpMainWindow_View.cpp b/kolourpaint/mainWindow/kpMainWindow_View.cpp new file mode 100644 index 00000000..6b5f4c9a --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow_View.cpp @@ -0,0 +1,170 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// private +void kpMainWindow::setupViewMenuActions () +{ + KActionCollection *ac = actionCollection (); + + /*d->actionFullScreen = KStandardAction::fullScreen (0, 0, ac); + d->actionFullScreen->setEnabled (false);*/ + + + setupViewMenuZoomActions (); + + + d->actionShowGrid = ac->add ("view_show_grid"); + d->actionShowGrid->setText (i18n ("Show &Grid")); + d->actionShowGrid->setShortcut (Qt::CTRL + Qt::Key_G); + //d->actionShowGrid->setCheckedState (KGuiItem(i18n ("Hide &Grid"))); + connect (d->actionShowGrid, SIGNAL (triggered (bool)), + SLOT (slotShowGridToggled ())); + + + setupViewMenuThumbnailActions (); + + + enableViewMenuDocumentActions (false); +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::viewMenuDocumentActionsEnabled () const +{ + return d->viewMenuDocumentActionsEnabled; +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableViewMenuDocumentActions (bool enable) +{ + d->viewMenuDocumentActionsEnabled = enable; + + + enableViewMenuZoomDocumentActions (enable); + + actionShowGridUpdate (); + + enableViewMenuThumbnailDocumentActions (enable); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::actionShowGridUpdate () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::actionShowGridUpdate()"; +#endif + const bool enable = (viewMenuDocumentActionsEnabled () && + d->mainView && d->mainView->canShowGrid ()); + + d->actionShowGrid->setEnabled (enable); + d->actionShowGrid->setChecked (enable && d->configShowGrid); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotShowGridToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotActionShowGridToggled()"; +#endif + + updateMainViewGrid (); + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupGeneral); + + cfg.writeEntry (kpSettingShowGrid, d->configShowGrid = d->actionShowGrid->isChecked ()); + cfg.sync (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::updateMainViewGrid () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::updateMainViewGrid ()"; +#endif + + if (d->mainView) + d->mainView->showGrid (d->actionShowGrid->isChecked ()); +} + +//--------------------------------------------------------------------- + +// private +QRect kpMainWindow::mapToGlobal (const QRect &rect) const +{ + return kpWidgetMapper::toGlobal (this, rect); +} + +//--------------------------------------------------------------------- + +// private +QRect kpMainWindow::mapFromGlobal (const QRect &rect) const +{ + return kpWidgetMapper::fromGlobal (this, rect); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/mainWindow/kpMainWindow_View_Thumbnail.cpp b/kolourpaint/mainWindow/kpMainWindow_View_Thumbnail.cpp new file mode 100644 index 00000000..3f34154d --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow_View_Thumbnail.cpp @@ -0,0 +1,477 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// private +void kpMainWindow::setupViewMenuThumbnailActions () +{ + d->thumbnailSaveConfigTimer = 0; + + KActionCollection *ac = actionCollection (); + + + d->actionShowThumbnail = ac->add ("view_show_thumbnail"); + d->actionShowThumbnail->setText (i18n ("Show T&humbnail")); + // TODO: This doesn't work when the thumbnail has focus. + // Testcase: Press CTRL+H twice on a fresh KolourPaint. + // The second CTRL+H doesn't close the thumbnail. + d->actionShowThumbnail->setShortcut (Qt::CTRL + Qt::Key_H); + //d->actionShowThumbnail->setCheckedState (KGuiItem(i18n ("Hide T&humbnail"))); + connect (d->actionShowThumbnail, SIGNAL (triggered (bool)), + SLOT (slotShowThumbnailToggled ())); + + // Please do not use setCheckedState() here - it wouldn't make sense + d->actionZoomedThumbnail = ac->add ("view_zoomed_thumbnail"); + d->actionZoomedThumbnail->setText (i18n ("Zoo&med Thumbnail Mode")); + connect (d->actionZoomedThumbnail, SIGNAL (triggered (bool)), + SLOT (slotZoomedThumbnailToggled ())); + + // For consistency with the above action, don't use setCheckedState() + // + // Also, don't use "Show Thumbnail Rectangle" because if entire doc + // can be seen in scrollView, checking option won't "Show" anything + // since rect _surrounds_ entire doc (hence, won't be rendered). + d->actionShowThumbnailRectangle = ac->add ("view_show_thumbnail_rectangle"); + d->actionShowThumbnailRectangle->setText (i18n ("Enable Thumbnail &Rectangle")); + connect (d->actionShowThumbnailRectangle, SIGNAL (triggered (bool)), + SLOT (slotThumbnailShowRectangleToggled ())); +} + +// private +void kpMainWindow::enableViewMenuThumbnailDocumentActions (bool enable) +{ + d->actionShowThumbnail->setEnabled (enable); + enableThumbnailOptionActions (enable); +} + +// private slot +void kpMainWindow::slotDestroyThumbnail () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotDestroyThumbnail()"; +#endif + + d->actionShowThumbnail->setChecked (false); + enableThumbnailOptionActions (false); + updateThumbnail (); +} + +// private slot +void kpMainWindow::slotDestroyThumbnailInitatedByUser () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotDestroyThumbnailInitiatedByUser()"; +#endif + + d->actionShowThumbnail->setChecked (false); + slotShowThumbnailToggled (); +} + +// private slot +void kpMainWindow::slotCreateThumbnail () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotCreateThumbnail()"; +#endif + + d->actionShowThumbnail->setChecked (true); + enableThumbnailOptionActions (true); + updateThumbnail (); +} + +// public +void kpMainWindow::notifyThumbnailGeometryChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::notifyThumbnailGeometryChanged()"; +#endif + + if (!d->thumbnailSaveConfigTimer) + { + d->thumbnailSaveConfigTimer = new QTimer (this); + d->thumbnailSaveConfigTimer->setSingleShot (true); + connect (d->thumbnailSaveConfigTimer, SIGNAL (timeout ()), + this, SLOT (slotSaveThumbnailGeometry ())); + } + + // (single shot) + d->thumbnailSaveConfigTimer->start (500/*msec*/); +} + +// private slot +void kpMainWindow::slotSaveThumbnailGeometry () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::saveThumbnailGeometry()"; +#endif + + if (!d->thumbnail) + return; + + QRect rect (d->thumbnail->x (), d->thumbnail->y (), + d->thumbnail->width (), d->thumbnail->height ()); +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tthumbnail relative geometry=" << rect; +#endif + + d->configThumbnailGeometry = mapFromGlobal (rect); + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tCONFIG: saving thumbnail geometry " + << d->configThumbnailGeometry + << endl; +#endif + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupThumbnail); + + cfg.writeEntry (kpSettingThumbnailGeometry, d->configThumbnailGeometry); + cfg.sync (); +} + +// private slot +void kpMainWindow::slotShowThumbnailToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotShowThumbnailToggled()"; +#endif + + d->configThumbnailShown = d->actionShowThumbnail->isChecked (); + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupThumbnail); + + cfg.writeEntry (kpSettingThumbnailShown, d->configThumbnailShown); + cfg.sync (); + + + enableThumbnailOptionActions (d->actionShowThumbnail->isChecked ()); + updateThumbnail (); +} + +// private slot +void kpMainWindow::updateThumbnailZoomed () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::updateThumbnailZoomed() zoomed=" + << d->actionZoomedThumbnail->isChecked () << endl; +#endif + + if (!d->thumbnailView) + return; + + destroyThumbnailView (); + createThumbnailView (); +} + +// private slot +void kpMainWindow::slotZoomedThumbnailToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotZoomedThumbnailToggled()"; +#endif + + d->configZoomedThumbnail = d->actionZoomedThumbnail->isChecked (); + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupThumbnail); + + cfg.writeEntry (kpSettingThumbnailZoomed, d->configZoomedThumbnail); + cfg.sync (); + + + updateThumbnailZoomed (); +} + +// private slot +void kpMainWindow::slotThumbnailShowRectangleToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotThumbnailShowRectangleToggled()"; +#endif + + d->configThumbnailShowRectangle = d->actionShowThumbnailRectangle->isChecked (); + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupThumbnail); + + cfg.writeEntry (kpSettingThumbnailShowRectangle, d->configThumbnailShowRectangle); + cfg.sync (); + + + if (d->thumbnailView) + { + d->thumbnailView->showBuddyViewScrollableContainerRectangle ( + d->actionShowThumbnailRectangle->isChecked ()); + } +} + +// private +void kpMainWindow::enableViewZoomedThumbnail (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::enableSettingsViewZoomedThumbnail()"; +#endif + + d->actionZoomedThumbnail->setEnabled (enable && + d->actionShowThumbnail->isChecked ()); + + // Note: Don't uncheck if disabled - being able to see the zoomed state + // before turning on the thumbnail can be useful. + d->actionZoomedThumbnail->setChecked (d->configZoomedThumbnail); +} + +// private +void kpMainWindow::enableViewShowThumbnailRectangle (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::enableViewShowThumbnailRectangle()"; +#endif + + d->actionShowThumbnailRectangle->setEnabled (enable && + d->actionShowThumbnail->isChecked ()); + + // Note: Don't uncheck if disabled for consistency with + // enableViewZoomedThumbnail() + d->actionShowThumbnailRectangle->setChecked ( + d->configThumbnailShowRectangle); +} + +// private +void kpMainWindow::enableThumbnailOptionActions (bool enable) +{ + enableViewZoomedThumbnail (enable); + enableViewShowThumbnailRectangle (enable); +} + + +// private +void kpMainWindow::createThumbnailView () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tcreating new kpView:"; +#endif + + if (d->thumbnailView) + { + kDebug () << "kpMainWindow::createThumbnailView() had to destroy view"; + destroyThumbnailView (); + } + + if (d->actionZoomedThumbnail->isChecked ()) + { + d->thumbnailView = new kpZoomedThumbnailView ( + d->document, d->toolToolBar, d->viewManager, + d->mainView, + 0/*scrollableContainer*/, + d->thumbnail); + d->thumbnailView->setObjectName ( QLatin1String("thumbnailView" )); + } + else + { + d->thumbnailView = new kpUnzoomedThumbnailView ( + d->document, d->toolToolBar, d->viewManager, + d->mainView, + 0/*scrollableContainer*/, + d->thumbnail); + d->thumbnailView->setObjectName ( QLatin1String("thumbnailView" )); + } + + d->thumbnailView->showBuddyViewScrollableContainerRectangle ( + d->actionShowThumbnailRectangle->isChecked ()); + + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tgive kpThumbnail the kpView:"; +#endif + if (d->thumbnail) + d->thumbnail->setView (d->thumbnailView); + else + kError () << "kpMainWindow::createThumbnailView() no thumbnail" << endl; + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tregistering the kpView:"; +#endif + if (d->viewManager) + d->viewManager->registerView (d->thumbnailView); +} + +// private +void kpMainWindow::destroyThumbnailView () +{ + if (!d->thumbnailView) + return; + + if (d->viewManager) + d->viewManager->unregisterView (d->thumbnailView); + + if (d->thumbnail) + d->thumbnail->setView (0); + + d->thumbnailView->deleteLater (); d->thumbnailView = 0; +} + + +// private +void kpMainWindow::updateThumbnail () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::updateThumbnail()"; +#endif + bool enable = d->actionShowThumbnail->isChecked (); + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tthumbnail=" + << bool (d->thumbnail) + << " action_isChecked=" + << enable + << endl; +#endif + + if (bool (d->thumbnail) == enable) + return; + + if (!d->thumbnail) + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tcreating thumbnail"; + #endif + + // Read last saved geometry before creating thumbnail & friends + // in case they call notifyThumbnailGeometryChanged() + QRect thumbnailGeometry = d->configThumbnailGeometry; + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tlast used geometry=" << thumbnailGeometry; + #endif + + d->thumbnail = new kpThumbnail (this); + + createThumbnailView (); + + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tmoving thumbnail to right place"; + #endif + if (!thumbnailGeometry.isEmpty () && + QRect (0, 0, width (), height ()).intersects (thumbnailGeometry)) + { + const QRect geometry = mapToGlobal (thumbnailGeometry); + d->thumbnail->resize (geometry.size ()); + d->thumbnail->move (geometry.topLeft ()); + } + else + { + if (d->scrollView) + { + const int margin = 20; + const int initialWidth = 160, initialHeight = 120; + + QRect geometryRect (width () - initialWidth - margin * 2, + d->scrollView->y () + margin, + initialWidth, + initialHeight); + + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tcreating geometry=" << geometryRect; + #endif + + geometryRect = mapToGlobal (geometryRect); + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tmap to global=" << geometryRect; + #endif + d->thumbnail->resize (geometryRect.size ()); + d->thumbnail->move (geometryRect.topLeft ()); + } + } + + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tshowing thumbnail"; + #endif + d->thumbnail->show (); + + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tconnecting signal thumbnail::windowClosed to destroy slot"; + #endif + connect (d->thumbnail, SIGNAL (windowClosed ()), + this, SLOT (slotDestroyThumbnailInitatedByUser ())); + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tDONE"; + #endif + } + else + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tdestroying thumbnail d->thumbnail=" + << d->thumbnail << endl; + #endif + + if (d->thumbnailSaveConfigTimer && d->thumbnailSaveConfigTimer->isActive ()) + { + d->thumbnailSaveConfigTimer->stop (); + slotSaveThumbnailGeometry (); + } + + // Must be done before hiding the thumbnail to avoid triggering + // this signal - re-entering this code. + disconnect (d->thumbnail, SIGNAL (windowClosed ()), + this, SLOT (slotDestroyThumbnailInitatedByUser ())); + + // Avoid change/flicker of caption due to view delete + // (destroyThumbnailView()) + d->thumbnail->hide (); + + destroyThumbnailView (); + + d->thumbnail->deleteLater (); d->thumbnail = 0; + } +} diff --git a/kolourpaint/mainWindow/kpMainWindow_View_Zoom.cpp b/kolourpaint/mainWindow/kpMainWindow_View_Zoom.cpp new file mode 100644 index 00000000..67392499 --- /dev/null +++ b/kolourpaint/mainWindow/kpMainWindow_View_Zoom.cpp @@ -0,0 +1,708 @@ +// REFACTOR: Clean up bits of this file + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int ZoomLevelFromString (const QString &stringIn) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow_View.cpp:ZoomLevelFromString(" << stringIn << ")"; +#endif + + // Remove any non-digits kdelibs sometimes adds behind our back :( e.g.: + // + // 1. kdelibs adds accelerators to actions' text directly + // 2. ',' is automatically added to change "1000%" to "1,000%" + QString string = stringIn; + string.remove (QRegExp ("[^0-9]")); +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\twithout non-digits='" << string << "'"; +#endif + + // Convert zoom level to number. + bool ok = false; + int zoomLevel = string.toInt (&ok); +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tzoomLevel=" << zoomLevel; +#endif + + if (!ok || zoomLevel < kpView::MinZoomLevel || zoomLevel > kpView::MaxZoomLevel) + return 0; // error + else + return zoomLevel; +} + +//--------------------------------------------------------------------- + +static QString ZoomLevelToString (int zoomLevel) +{ + return i18n ("%1%", zoomLevel); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupViewMenuZoomActions () +{ + KActionCollection *ac = actionCollection (); + + + d->actionActualSize = KStandardAction::actualSize (this, SLOT (slotActualSize ()), ac); + d->actionFitToPage = KStandardAction::fitToPage (this, SLOT (slotFitToPage ()), ac); + d->actionFitToWidth = KStandardAction::fitToWidth (this, SLOT (slotFitToWidth ()), ac); + d->actionFitToHeight = KStandardAction::fitToHeight (this, SLOT (slotFitToHeight ()), ac); + + + d->actionZoomIn = KStandardAction::zoomIn (this, SLOT (slotZoomIn ()), ac); + d->actionZoomOut = KStandardAction::zoomOut (this, SLOT (slotZoomOut ()), ac); + + + d->actionZoom = ac->add ("view_zoom_to"); + d->actionZoom->setText (i18n ("&Zoom")); + connect (d->actionZoom, SIGNAL (triggered (QAction *)), SLOT (slotZoom ())); + d->actionZoom->setEditable (true); + + // create the zoom list for the 1st call to zoomTo() below + d->zoomList.append (10); d->zoomList.append (25); d->zoomList.append (33); + d->zoomList.append (50); d->zoomList.append (67); d->zoomList.append (75); + d->zoomList.append (100); + d->zoomList.append (200); d->zoomList.append (300); + d->zoomList.append (400); d->zoomList.append (600); d->zoomList.append (800); + d->zoomList.append (1000); d->zoomList.append (1200); d->zoomList.append (1600); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableViewMenuZoomDocumentActions (bool enable) +{ + d->actionActualSize->setEnabled (enable); + d->actionFitToPage->setEnabled (enable); + d->actionFitToWidth->setEnabled (enable); + d->actionFitToHeight->setEnabled (enable); + + d->actionZoomIn->setEnabled (enable); + d->actionZoomOut->setEnabled (enable); + d->actionZoom->setEnabled (enable); + + + // TODO: for the time being, assume that we start at zoom 100% + // with no grid + + // This function is only called when a new document is created + // or an existing document is closed. So the following will + // always be correct: + + zoomTo (100); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::sendZoomListToActionZoom () +{ + QStringList items; + + const QList ::ConstIterator zoomListEnd (d->zoomList.end ()); + for (QList ::ConstIterator it = d->zoomList.constBegin (); + it != zoomListEnd; + ++it) + { + items << ::ZoomLevelToString (*it); + } + + // Work around a KDE bug - KSelectAction::setItems() enables the action. + // David Faure said it won't be fixed because it's a feature used by + // KRecentFilesAction. + bool e = d->actionZoom->isEnabled (); + d->actionZoom->setItems (items); + if (e != d->actionZoom->isEnabled ()) + d->actionZoom->setEnabled (e); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::zoomToPre (int zoomLevel) +{ + // We're called quite early in the init process and/or when there might + // not be a document or a view so we have a lot of "if (ptr)" guards. + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::zoomToPre(" << zoomLevel << ")"; +#endif + + zoomLevel = qBound (kpView::MinZoomLevel, zoomLevel, kpView::MaxZoomLevel); + +// mute point since the thumbnail suffers from this too +#if 0 + else if (d->mainView && d->mainView->zoomLevelX () % 100 == 0 && zoomLevel % 100) + { + if (KMessageBox::warningContinueCancel (this, + i18n ("Setting the zoom level to a value that is not a multiple of 100% " + "results in imprecise editing and redraw glitches.\n" + "Do you really want to set to zoom level to %1%?", + zoomLevel), + QString()/*caption*/, + i18n ("Set Zoom Level to %1%", zoomLevel), + "DoNotAskAgain_ZoomLevelNotMultipleOf100") != KMessageBox::Continue) + { + zoomLevel = d->mainView->zoomLevelX (); + } + } +#endif + + int index = 0; + QList ::Iterator it = d->zoomList.begin (); + + while (index < (int) d->zoomList.count () && zoomLevel > *it) + it++, index++; + + if (zoomLevel != *it) + d->zoomList.insert (it, zoomLevel); + + // OPT: We get called twice on startup. sendZoomListToActionZoom() is very slow. + sendZoomListToActionZoom (); + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tsetCurrentItem(" << index << ")"; +#endif + d->actionZoom->setCurrentItem (index); +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tcurrentItem=" + << d->actionZoom->currentItem () + << " action=" + << d->actionZoom->action (d->actionZoom->currentItem ()) + << " checkedAction" + << d->actionZoom->selectableActionGroup ()->checkedAction () + << endl;; +#endif + + + if (viewMenuDocumentActionsEnabled ()) + { + d->actionActualSize->setEnabled (zoomLevel != 100); + + d->actionZoomIn->setEnabled (d->actionZoom->currentItem () < (int) d->zoomList.count () - 1); + d->actionZoomOut->setEnabled (d->actionZoom->currentItem () > 0); + } + + + // TODO: Is this actually needed? + if (d->viewManager) + d->viewManager->setQueueUpdates (); + + if (d->scrollView) + { + d->scrollView->setUpdatesEnabled (false); + } +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::zoomToPost () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::zoomToPost()"; +#endif + + if (d->mainView) + { + actionShowGridUpdate (); + updateMainViewGrid (); + + // Since Zoom Level KSelectAction on ToolBar grabs focus after changing + // Zoom, switch back to the Main View. + // TODO: back to the last view + d->mainView->setFocus (); + + } + + + // The view magnified and moved beneath the cursor + if (tool ()) + tool ()->somethingBelowTheCursorChanged (); + + + if (d->scrollView) + { + // TODO: setUpdatesEnabled() should really return to old value + // - not necessarily "true" + d->scrollView->setUpdatesEnabled (true); + } + + if (d->viewManager && d->viewManager->queueUpdates ()/*just in case*/) + d->viewManager->restoreQueueUpdates (); + + setStatusBarZoom (d->mainView ? d->mainView->zoomLevelX () : 0); + +#if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "kpMainWindow::zoomToPost() done"; +#endif +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::zoomTo (int zoomLevel, bool centerUnderCursor) +{ + zoomToPre (zoomLevel); + + + if (d->scrollView && d->mainView) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "\tscrollView contentsX=" << d->scrollView->horizontalScrollBar()->value () + << " contentsY=" << d->scrollView->verticalScrollBar()->value () + << " contentsWidth=" << d->scrollView->widget()->width () + << " contentsHeight=" << d->scrollView->widget()->height () + << " visibleWidth=" << d->scrollView->viewport()->width () + << " visibleHeight=" << d->scrollView->viewport()->height () + << " oldZoomX=" << d->mainView->zoomLevelX () + << " oldZoomY=" << d->mainView->zoomLevelY () + << " newZoom=" << zoomLevel + << endl; + #endif + + // TODO: when changing from no scrollbars to scrollbars, Qt lies about + // visibleWidth() & visibleHeight() (doesn't take into account the + // space taken by the would-be scrollbars) until it updates the + // scrollview; hence the centering is off by about 5-10 pixels. + + // TODO: Use visibleRect() for greater accuracy? + // Or use kpAbstractScrollAreaUtils::EstimateUsableArea() + // instead of ScrollView::visible{Width,Height}(), as + // per zoomToRect()? + + int viewX, viewY; + + bool targetDocAvail = false; + double targetDocX = -1, targetDocY = -1; + + if (centerUnderCursor && + d->viewManager && d->viewManager->viewUnderCursor ()) + { + kpView *const vuc = d->viewManager->viewUnderCursor (); + QPoint viewPoint = vuc->mouseViewPoint (); + + // vuc->transformViewToDoc() returns QPoint which only has int + // accuracy so we do X and Y manually. + targetDocX = vuc->transformViewToDocX (viewPoint.x ()); + targetDocY = vuc->transformViewToDocY (viewPoint.y ()); + targetDocAvail = true; + + if (vuc != d->mainView) + viewPoint = vuc->transformViewToOtherView (viewPoint, d->mainView); + + viewX = viewPoint.x (); + viewY = viewPoint.y (); + } + else + { + viewX = d->scrollView->horizontalScrollBar()->value () + + qMin (d->mainView->width (), + d->scrollView->viewport()->width ()) / 2; + viewY = d->scrollView->verticalScrollBar()->value () + + qMin (d->mainView->height (), + d->scrollView->viewport()->height ()) / 2; + } + + + int newCenterX = viewX * zoomLevel / d->mainView->zoomLevelX (); + int newCenterY = viewY * zoomLevel / d->mainView->zoomLevelY (); + + // Do the zoom. + d->mainView->setZoomLevel (zoomLevel, zoomLevel); + + #if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "\tvisibleWidth=" << d->scrollView->viewport()->width () + << " visibleHeight=" << d->scrollView->viewport()->height () + << endl; + kDebug () << "\tnewCenterX=" << newCenterX + << " newCenterY=" << newCenterY << endl; + #endif + + d->scrollView->horizontalScrollBar()->setValue(newCenterX - (d->scrollView->viewport()->width() / 2)); + d->scrollView->verticalScrollBar()->setValue(newCenterY - (d->scrollView->viewport()->height() / 2)); + + if (centerUnderCursor && + targetDocAvail && + d->viewManager && d->viewManager->viewUnderCursor ()) + { + // Move the mouse cursor so that it is still above the same + // document pixel as before the zoom. + + kpView *const vuc = d->viewManager->viewUnderCursor (); + + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tcenterUnderCursor: reposition cursor; viewUnderCursor=" + << vuc->objectName () << endl; + #endif + + const double viewX = vuc->transformDocToViewX (targetDocX); + const double viewY = vuc->transformDocToViewY (targetDocY); + // Rounding error from zooming in and out :( + // TODO: do everything in terms of tool doc points in type "double". + const QPoint viewPoint ((int) viewX, (int) viewY); + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tdoc: (" << targetDocX << "," << targetDocY << ")" + << " viewUnderCursor: (" << viewX << "," << viewY << ")" + << endl; + #endif + + if (vuc->visibleRegion ().contains (viewPoint)) + { + const QPoint globalPoint = + kpWidgetMapper::toGlobal (vuc, viewPoint); + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\tglobalPoint=" << globalPoint; + #endif + + // TODO: Determine some sane cursor flashing indication - + // cursor movement is convenient but not conventional. + // + // Major problem: if using QApplication::setOverrideCursor() + // and in some stage of flash and window quits. + // + // Or if using kpView::setCursor() and change tool. + QCursor::setPos (globalPoint); + } + // e.g. Zoom to 200%, scroll mainView to bottom-right. + // Unzoomed Thumbnail shows top-left portion of bottom-right of + // mainView. + // + // Aim cursor at bottom-right of thumbnail and zoom out with + // CTRL+Wheel. + // + // If mainView is now small enough to largely not need scrollbars, + // Unzoomed Thumbnail scrolls to show _top-left_ portion + // _of top-left_ of mainView. + // + // Unzoomed Thumbnail no longer contains the point we zoomed out + // on top of. + else + { + #if DEBUG_KP_MAIN_WINDOW + kDebug () << "\t\twon't move cursor - would get outside view" + << endl; + #endif + + // TODO: Sane cursor flashing indication that indicates + // that the normal cursor movement didn't happen. + } + } + + #if DEBUG_KP_MAIN_WINDOW && 1 + kDebug () << "\t\tcheck (contentsX=" << d->scrollView->horizontalScrollBar()->value () + << ",contentsY=" << d->scrollView->verticalScrollBar()->value () + << ")" << endl; + #endif + } + + + zoomToPost (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::zoomToRect (const QRect &normalizedDocRect, + bool accountForGrips, + bool careAboutWidth, bool careAboutHeight) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::zoomToRect(normalizedDocRect=" + << normalizedDocRect + << ",accountForGrips=" << accountForGrips + << ",careAboutWidth=" << careAboutWidth + << ",careAboutHeight=" << careAboutHeight + << ")"; +#endif + // You can't care about nothing. + Q_ASSERT (careAboutWidth || careAboutHeight); + + // The size of the scroll view minus the current or future scrollbars. + const QSize usableScrollArea + (d->scrollView->maximumViewportSize().width() - d->scrollView->verticalScrollBar()->sizeHint().width(), + d->scrollView->maximumViewportSize().height() - d->scrollView->horizontalScrollBar()->sizeHint().height()); + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "size=" << d->scrollView->maximumViewportSize() + << "scrollbar w=" << d->scrollView->verticalScrollBar()->sizeHint().width() + << "usableSize=" << usableScrollArea; +#endif + // Handle rounding error, mis-estimating the scroll view size and + // cosmic rays. We do this because we really don't want unnecessary + // scrollbars. This seems to need to be at least 2 for slotFitToWidth() + // and friends. + // least 2. + // TODO: I might have fixed this but check later. + const int slack = 0; + + // The grip and slack are in view coordinates but are never zoomed. + const int viewWidth = + usableScrollArea.width () - + (accountForGrips ? kpGrip::Size : 0) - + slack; + const int viewHeight = + usableScrollArea.height () - + (accountForGrips ? kpGrip::Size : 0) - + slack; + + // We want the selected document rectangle to fill the scroll view. + // + // The integer arithmetic rounds down, rather than to the nearest zoom + // level, as rounding down guarantees that the view, at the zoom level, + // will fit inside x . + const int zoomX = + careAboutWidth ? + qMax (1, viewWidth * 100 / normalizedDocRect.width ()) : + INT_MAX; + const int zoomY = + careAboutHeight ? + qMax (1, viewHeight * 100 / normalizedDocRect.height ()) : + INT_MAX; + + // Since kpView only supports identical horizontal and vertical zooms, + // choose the one that will show the greatest amount of document + // content. + const int zoomLevel = qMin (zoomX, zoomY); + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tzoomX=" << zoomX + << " zoomY=" << zoomY + << " -> zoomLevel=" << zoomLevel << endl; +#endif + + zoomToPre (zoomLevel); + { + d->mainView->setZoomLevel (zoomLevel, zoomLevel); + + const QPoint viewPoint = + d->mainView->transformDocToView (normalizedDocRect.topLeft ()); + + d->scrollView->horizontalScrollBar()->setValue(viewPoint.x()); + d->scrollView->verticalScrollBar()->setValue(viewPoint.y()); + } + zoomToPost (); +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotActualSize () +{ + zoomTo (100); +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotFitToPage () +{ + if ( d->document ) + { + zoomToRect ( + d->document->rect (), + true/*account for grips*/, + true/*care about width*/, true/*care about height*/); + } +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotFitToWidth () +{ + if ( d->document ) + { + const QRect docRect ( + 0/*x*/, + (int) d->mainView->transformViewToDocY (d->scrollView->verticalScrollBar()->value ())/*maintain y*/, + d->document->width (), + 1/*don't care about height*/); + zoomToRect ( + docRect, + true/*account for grips*/, + true/*care about width*/, false/*don't care about height*/); + } +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotFitToHeight () +{ + if ( d->document ) + { + const QRect docRect ( + (int) d->mainView->transformViewToDocX (d->scrollView->horizontalScrollBar()->value ())/*maintain x*/, + 0/*y*/, + 1/*don't care about width*/, + d->document->height ()); + zoomToRect ( + docRect, + true/*account for grips*/, + false/*don't care about width*/, true/*care about height*/); + } +} + +//--------------------------------------------------------------------- + +// public +void kpMainWindow::zoomIn (bool centerUnderCursor) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::zoomIn(centerUnderCursor=" + << centerUnderCursor << ") currentItem=" + << d->actionZoom->currentItem () + << endl; +#endif + const int targetItem = d->actionZoom->currentItem () + 1; + + if (targetItem >= (int) d->zoomList.count ()) + return; + + d->actionZoom->setCurrentItem (targetItem); + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tnew currentItem=" << d->actionZoom->currentItem (); +#endif + + zoomAccordingToZoomAction (centerUnderCursor); +} + +//--------------------------------------------------------------------- + +// public +void kpMainWindow::zoomOut (bool centerUnderCursor) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::zoomOut(centerUnderCursor=" + << centerUnderCursor << ") currentItem=" + << d->actionZoom->currentItem () + << endl; +#endif + const int targetItem = d->actionZoom->currentItem () - 1; + + if (targetItem < 0) + return; + + d->actionZoom->setCurrentItem (targetItem); + +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "\tnew currentItem=" << d->actionZoom->currentItem (); +#endif + + zoomAccordingToZoomAction (centerUnderCursor); +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotZoomIn () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotZoomIn ()"; +#endif + + zoomIn (false/*don't center under cursor*/); +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotZoomOut () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotZoomOut ()"; +#endif + + zoomOut (false/*don't center under cursor*/); +} + +//--------------------------------------------------------------------- + +// public +void kpMainWindow::zoomAccordingToZoomAction (bool centerUnderCursor) +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::zoomAccordingToZoomAction(centerUnderCursor=" + << centerUnderCursor + << ") currentItem=" << d->actionZoom->currentItem () + << " currentText=" << d->actionZoom->currentText () + << endl; +#endif + + // This might be a new zoom level the user has typed in. + zoomTo (::ZoomLevelFromString (d->actionZoom->currentText ()), + centerUnderCursor); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotZoom () +{ +#if DEBUG_KP_MAIN_WINDOW + kDebug () << "kpMainWindow::slotZoom () index=" << d->actionZoom->currentItem () + << " text='" << d->actionZoom->currentText () << "'" << endl; +#endif + + zoomAccordingToZoomAction (false/*don't center under cursor*/); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/patches/checkerboard-faster-render.diff b/kolourpaint/patches/checkerboard-faster-render.diff new file mode 100644 index 00000000..7cc5c8b5 --- /dev/null +++ b/kolourpaint/patches/checkerboard-faster-render.diff @@ -0,0 +1,147 @@ +At 100% zoom: kpMainWindow::drawTransparentBackground() accounts for +about 75% of kpView::paintEvent()'s time. Bottleneck is +QPainter::fillRect(). QPainter::drawPixmap() seems much faster. For +800x600, renderer goes from 10ms to 1ms. + +2007-10-12: +Have not reprofiled KolourPaint under Qt4 to determine whether this patch +is still worthwhile (I suspect it still is since QPainter/X11 could not +magically have gotten faster). In any case, the patch needs to be updated +before being applied. + +--- kpmainwindow.cpp 2004-08-05 02:10:38.000000000 +1000 ++++ kpmainwindow.cpp 2004-09-29 11:24:45.000000000 +1000 +@@ -838,12 +838,116 @@ + } + + ++#if 1 ++// (indexed by [isPreview][parity]) ++static QPixmap *checkerBoardCache [2][2] = {{0, 0}, {0, 0}}; ++ ++ ++static int checkerBoardCellSize (bool isPreview) ++{ ++ return !isPreview ? 16 : 10; ++} ++ ++ ++// public ++static QPixmap *createCheckerBoardCache (bool isPreview, bool parity) ++{ ++ int cellSize = checkerBoardCellSize (isPreview); ++ const int rep = 2; // must be multiple of 2 ++ ++ QPixmap *newPixmap = new QPixmap (cellSize * rep, cellSize * rep); ++ QPainter painter (newPixmap); ++ ++ int parityAsInt = parity ? 1 : 0; ++ for (int y = 0; y < rep; y++) ++ { ++ for (int x = 0; x < rep; x++) ++ { ++ QColor col; ++ ++ if ((parityAsInt + x + y) % 2) ++ { ++ if (!isPreview) ++ col = QColor (213, 213, 213); ++ else ++ col = QColor (224, 224, 224); ++ } ++ else ++ col = Qt::white; ++ ++ painter.fillRect (x * cellSize, y * cellSize, ++ cellSize, cellSize, ++ col); ++ } ++ } ++ ++ painter.end (); ++ return newPixmap; ++} ++ ++void kpMainWindow::drawTransparentBackground (QPainter *painter, ++ int /*viewWidth*/, int /*viewHeight*/, ++ const QRect &rect, ++ bool isPreview) ++{ ++#if DEBUG_KP_MAIN_WINDOW && 1 || 1 ++ kDebug () << "\tkpMainWindow::drawTransparentBackground(rect=" ++ << rect << ")" << endl; ++ QTime totalTimer; totalTimer.start (); ++#endif ++ ++ int cellSize = checkerBoardCellSize (isPreview); ++ ++ ++ int starty = rect.y (); ++ if (starty % cellSize) ++ starty -= (starty % cellSize); ++ ++ int startx = rect.x (); ++ if (startx % cellSize) ++ startx -= (startx % cellSize); ++ ++ ++ int parity = ((startx / cellSize + starty / cellSize) % 2) ? 1 : 0; ++ ++ if (!checkerBoardCache [isPreview][parity]) ++ { ++ checkerBoardCache [isPreview][parity] = createCheckerBoardCache (isPreview, parity); ++ } ++ ++ QPixmap *tilePixmap = checkerBoardCache [isPreview][parity]; ++ for (int y = starty; y <= rect.bottom (); y += tilePixmap->height ()) ++ { ++ for (int x = startx; x <= rect.right (); x += tilePixmap->width ()) ++ { ++ painter->drawPixmap (x - rect.x (), y - rect.y (), *tilePixmap); ++ } ++ } ++ ++#if DEBUG_KP_MAIN_WINDOW && 1 || 1 ++{ ++ const int totalTimerElapsed = totalTimer.elapsed (); ++ kDebug () << "\t\ttotal=" << totalTimerElapsed << endl; ++} ++#endif ++} ++ ++ ++#else ++ + // public + void kpMainWindow::drawTransparentBackground (QPainter *painter, + int /*viewWidth*/, int /*viewHeight*/, + const QRect &rect, + bool isPreview) + { ++#if DEBUG_KP_MAIN_WINDOW && 1 ++ kDebug () << "\tkpMainWindow::drawTransparentBackground(rect=" ++ << rect << ")" << endl; ++ QTime totalTimer; totalTimer.start (); ++#endif ++ ++ + const int cellSize = !isPreview ? 16 : 10; + + int starty = rect.y (); +@@ -877,8 +982,15 @@ + } + } + painter->restore (); +-} + ++#if DEBUG_KP_MAIN_WINDOW && 1 || 1 ++{ ++ const int totalTimerElapsed = totalTimer.elapsed (); ++ kDebug () << "\t\ttotal=" << totalTimerElapsed << endl; ++} ++#endif ++} ++#endif + + // private slot + void kpMainWindow::slotUpdateCaption () diff --git a/kolourpaint/patches/linear-sharpen-effect.diff b/kolourpaint/patches/linear-sharpen-effect.diff new file mode 100644 index 00000000..605852f0 --- /dev/null +++ b/kolourpaint/patches/linear-sharpen-effect.diff @@ -0,0 +1,150 @@ +Changes the "Sharpen" effect to Blitz::sharpen() instead of +Blitz::gaussianSharpen(), in order to avoid parameters that are currently +set in a dangerously adhoc fashion (radius, sigma, repeat). + +Unfortunately, the results do not look good. + +2007-08-14 20:35 + + +Index: imagelib/effects/kpEffectBlurSharpen.cpp +=================================================================== +--- imagelib/effects/kpEffectBlurSharpen.cpp (revision 699849) ++++ imagelib/effects/kpEffectBlurSharpen.cpp (working copy) +@@ -26,7 +26,7 @@ + */ + + +-#define DEBUG_KP_EFFECT_BLUR_SHARPEN 0 ++#define DEBUG_KP_EFFECT_BLUR_SHARPEN 1 + + + #include +@@ -46,18 +46,10 @@ + #endif + + +-static QImage BlurQImage (const QImage qimage_, int strength) ++static int RadiusForStrength (int strength) + { +- QImage qimage = qimage_; +- if (strength == 0) +- return qimage; +- +- +- // The numbers that follow were picked by experimentation to try to get +- // an effect linearly proportional to and at the same time, +- // be fast enough. +- // +- // I still have no idea what "radius" means. ++ // (must be in range and not 0) ++ Q_ASSERT (strength > 0 && strength <= kpEffectBlurSharpen::MaxStrength); + + const double RadiusMin = 1; + const double RadiusMax = 10; +@@ -67,92 +59,36 @@ static QImage BlurQImage (const QImage q + (kpEffectBlurSharpen::MaxStrength - 1); + + #if DEBUG_KP_EFFECT_BLUR_SHARPEN +- kDebug () << "kpEffectBlurSharpen.cpp:BlurQImage(strength=" << strength << ")" ++ kDebug () << "kpEffectBlurSharpen.cpp:RadiusForStrength(strength=" << strength << ")" + << " radius=" << radius + << endl; + #endif + +- +- qimage = Blitz::blur (qimage, qRound (radius)); +- +- +- return qimage; ++ return qRound (radius); + } + +-static QImage SharpenQImage (const QImage &qimage_, int strength) ++ ++static QImage BlurQImage (const QImage qimage_, int strength) + { + QImage qimage = qimage_; + if (strength == 0) + return qimage; + + +- // The numbers that follow were picked by experimentation to try to get +- // an effect linearly proportional to and at the same time, +- // be fast enough. +- // +- // I still have no idea what "radius" and "sigma" mean. +- +- const double RadiusMin = .1; +- const double RadiusMax = 2.5; +- const double radius = RadiusMin + +- (strength - 1) * +- (RadiusMax - RadiusMin) / +- (kpEffectBlurSharpen::MaxStrength - 1); +- +- const double SigmaMin = .5; +- const double SigmaMax = 3.0; +- const double sigma = SigmaMin + +- (strength - 1) * +- (SigmaMax - SigmaMin) / +- (kpEffectBlurSharpen::MaxStrength - 1); +- +- const double RepeatMin = 1; +- const double RepeatMax = 2; +- const double repeat = qRound (RepeatMin + +- (strength - 1) * +- (RepeatMax - RepeatMin) / +- (kpEffectBlurSharpen::MaxStrength - 1)); ++ qimage = Blitz::blur (qimage, ::RadiusForStrength (strength)); + +-// I guess these values are more proper as they use an auto-calculated +-// radius but they cause sharpen() to be too slow. +-#if 0 +- const double radius = 0/*auto-calculate*/; +- +- const double SigmaMin = .6; +- const double SigmaMax = 1.0; +- const double sigma = SigmaMin + +- (strength - 1) * +- (SigmaMax - SigmaMin) / +- (kpEffectBlurSharpen::MaxStrength - 1); + +- const double RepeatMin = 1; +- const double RepeatMax = 3; +- const double repeat = qRound (RepeatMin + +- (strength - 1) * +- (RepeatMax - RepeatMin) / +- (kpEffectBlurSharpen::MaxStrength - 1)); +-#endif ++ return qimage; ++} + +-#if DEBUG_KP_EFFECT_BLUR_SHARPEN +- kDebug () << "kpEffectBlurSharpen.cpp:SharpenQImage(strength=" << strength << ")" +- << " radius=" << radius +- << " sigma=" << sigma +- << " repeat=" << repeat +- << endl; +-#endif ++static QImage SharpenQImage (const QImage &qimage_, int strength) ++{ ++ QImage qimage = qimage_; ++ if (strength == 0) ++ return qimage; + + +- for (int i = 0; i < repeat; i++) +- { +- #if DEBUG_KP_EFFECT_BLUR_SHARPEN +- QTime timer; timer.start (); +- #endif +- qimage = Blitz::gaussianSharpen (qimage, radius, sigma); +- #if DEBUG_KP_EFFECT_BLUR_SHARPEN +- kDebug () << "\titeration #" + QString::number (i) +- << ": " + QString::number (timer.elapsed ()) << "ms" << endl; +- #endif +- } ++ qimage = Blitz::sharpen (qimage, ::RadiusForStrength (strength) * 10); + + + return qimage; diff --git a/kolourpaint/pics/CMakeLists.txt b/kolourpaint/pics/CMakeLists.txt new file mode 100644 index 00000000..cfdfb885 --- /dev/null +++ b/kolourpaint/pics/CMakeLists.txt @@ -0,0 +1,11 @@ + +# Change this number every time you add an icon to force the build system +# to rebuild and pick the icon up: +# +# 3 +# + +add_subdirectory( action ) +add_subdirectory( app ) +add_subdirectory( custom ) + diff --git a/kolourpaint/pics/action/CMakeLists.txt b/kolourpaint/pics/action/CMakeLists.txt new file mode 100644 index 00000000..9c426b31 --- /dev/null +++ b/kolourpaint/pics/action/CMakeLists.txt @@ -0,0 +1,9 @@ + +# Change this number every time you add an icon to force the build system +# to rebuild and pick the icon up: +# +# 4 +# + +kde4_install_icons( ${DATA_INSTALL_DIR}/kolourpaint/icons ) + diff --git a/kolourpaint/pics/action/hi16-action-tool_brush.png b/kolourpaint/pics/action/hi16-action-tool_brush.png new file mode 100644 index 0000000000000000000000000000000000000000..372ff624e715c8ce737123076fa599eea96a6c23 GIT binary patch literal 675 zcmV;U0$lxxP)#dEwU9+cA2$^g!Eln6 z*LFU3S#P)3LMMMyqXHB~se*U92TV+$DG|pD0B^Ad6)NC8*3z-LZEurEWkj063S?9? z*niO4O_yTe`Gxd{@Aw88iX%82r>ncVT#sgEq7N_H8XB2ZKXNn{pM;Scfed*l9?!iC zfk3#xaU5aUC2z-RFXX>2Yw5q%8*JUvZmz4Z(TidUqL>3IWTDb*1c3(me1VHZLiWBsH&>Q{nWE^Y zJus|Hw_;K_lQg#2^)y2p2r)oQ1nBiTWV0C~$K^7LA)eZe<2(1kRIU9=9TB|x&(Y>z z9}^u(b%Rx#&#P;Ao{-h0dCup%&MO)mJ&CjW?Vun65CB_Ul(7i|47a?kFurvDhc5Mw z2|XVlM$eV7gS0;e*lr|LN&z&*|B^bw5QmSO+uEjkVjf4O1ip{E3NR^umyM;RQq`F?ypW#%PQ} zGB3RF!ejyw2?k~cC?U9o(lMYEMmvXF$@;&pw52WmIpzHIcxuGVJD%jto4ik+C-3_{ zKvmTzDh<%5yDgiw4zHa*k@%U7EVFd{>pNm@wZ1$tzVgW~`;NyPThHyet8JZqCQ(i?Ly6)JGL&rvMUO00^ambPj-?UQDe6>nxUt zI>wOpi!ftqIbvyRYly4GVx<BtoND^mwUdXa07N;m3Vr01r{j^gwo;lLQ zWmbsAj<(7SYy9c;bMd$S%9KJd&>%=kP10@$5L9Pb7LvqTOSxQ0(4u;SX{^;^HHwl_NvC-aO-EniIF6NNnSufI03&d6Ih!wC zX6#FsrOtafb-r5@%iv$Y`wxBpEkWpuk_1k_6I!D-iR~ zMUHJfG=8SLy9-4SLIe7By@ZCy^-m) z+34x%OYNfYDD*#rfbY_((>at^RnQ>FTBWv%^G%c9>0b-7EdCQ!EE+x4)zzB}EiJyM dCE#Cc@Ha^a*X4y~GcW)E002ovPDHLkV1k-A*mVE^ literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi16-action-tool_color_picker.png b/kolourpaint/pics/action/hi16-action-tool_color_picker.png new file mode 100644 index 0000000000000000000000000000000000000000..2aba49a76593a87bc5dd3aaaec3aa68b38b960cf GIT binary patch literal 634 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJbFq_W2nPqp?T7vkfLzHE*NBqf z{Irtt#G+J&^73-M%)IR4v>btgLi%b8~lh_w@Ai_V)Ji z@$vKXtEi|1lG)igtXa$%85x{qoQ&lR0(AnyRYH30dK@{dKyGDa6+;b!=@b)|7Us#5 zr_7o)$8V`GV<+RDJ$qaxJ1<_mbjgxsOP4OsoSQXk^Q=vqHUq(yEnBv2+rEAK4u*LQ z3`-dpRxvWHW@K2)$T*jQaVaD7QWoZw%q-KHSr#z!)N%5)af@~c*=@6PSZ*J>DJ1N0 zSj5V(h|OVb7uq^Ebx*l5Wyg-)Cr_R}dGgGuQ)f?~2BLFk&H&N*bLTFeKY!`Mh07N& zUb=Yk@})~xE?>TS`SP_ZSFT^ZdgI~eho3H8m;nq~%aS0!U8c3Fj1|8puZwV4 zvH8s72(#vZRQWRtQ0X+9>t6Ok3Hr8@KvR5f7x4Dlfi<5!oZv;jTA@DlkBVW3+pyM3rLyiHpgD>^un8}Z z@JY__X4yu#S2)a%QX&5t9&`$98X?`S@oHIP0tkz0>VUe8iaHS|7N~KSrE9dGou6hN z?YaYu>=_hiNH#hgKSH=ktjn#qp@!~4A)iBBH^IhRRcld1Z8Vt9outbK{QqtI1~IH> U^@~7-xc~qF07*qoM6N<$f+x1ndjJ3c literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi16-action-tool_ellipse.png b/kolourpaint/pics/action/hi16-action-tool_ellipse.png new file mode 100644 index 0000000000000000000000000000000000000000..4e2fe52462b19e9d30faaeae7888c85b418b79f7 GIT binary patch literal 807 zcmV+?1K9kDP)sz zAz?4A-5`4LlCfe$fyt6)uC1fH&V9~YPG=@IP^uRX9De5<&gc8PoZlf3LV=A8@VCRJ zy)7&@b$lq&=8xX}j zh?gZltt>B`{V+Z{wMGDYB$Aa|)4En{@9pVDot?*Zf@Cddxe!{&GNB74Nw?DJr^1Wp z1LZU;3`@nuE*uf^Q>aK(Jxo-@j^4O_&j5gf(8i$Qc%W%|0;UU18(0U9oG#QibbPzt zcjb^}#qVJB0?NWV@ilb*ftBVVHsFk1~ zOHzE0q!^l@7>1-sGC*YWQ?(ReM}Z=*_Pu#E*k851byk%5rKvBfXxYdufdWr~5Kq#0 z4T8*X5vUj0`I*=Cu5H}(``~cfvAc-mI+8oD4pVg4&od%WG@eYDr)ZudX)Z|8EEaHV zl^J?`tx1*(LnH5oFaBvH5`-Ti7oLbF+cLGgPR*3X!yppE0wId^1ON%y$?>78_ao1$ zXKyZ zDpXLRQe3LIMxzyDtys0Kp{7Zzwx*fMo3}WrC?fsyz~%DZyXW5X;2w@K2L1P?M}En+MNTxFG&^%WHrvnHw){ytRI*J~1@9fF4}o)!ux*EOu_(0wQ~ch17) zJs-N~FI~C1ha$2i8hz09oAc)j?|Od}Gi2@WxOcO(tbAD_GjUK`a{)6Cr9kY->Cau&f-hOe_Ben5G9h z9r1f0&%>j&bq#TkN6t8tOiY-W&o+jpO_-B`g%C3-WjKeJ)jMD%@5V+x7@?Id9OSCF z<0sD^*}82<*yR>Yf=OO3A`IA$)N#pxa(G}kmrkUtZ&nuCqL;ZvFDF0+?(#P zjUEZs5CX;tgb~|za%PNv|JauuOr+U&MWP;`#*4BDJzkgKcDpzMp{)Drl_thNkKMl> z42DyIDdAy+X)ZH{N89dF-s9# z?q*7H^Y|i_RaUDnlV{Mh!dr`qj7077n(bXBOCo}y@|vbXU6FY;{iQ$j>eZv!wRIcf zyd<8F6scW*Ht{#g%a_e(Jh%)1D=zR)d35;1#bzs;44yfCXy;IRaE2^votBqh8*OVF zozM%h*yFc(FXfH%^=o48P`}y-8W$dvtjzCoc_Ja+u(-9CL^ zubRNI)j$knKfQoUe?h{N1W_gc8RsHI5p3lijm-$Rx@xuBI_vez&kh}EsZKA*f_|J*RaJc?JgyE&ag9v4A(j}8#w$HNJ%;2DAM(|B&fOk&HE;re zI0sOY0!XR=5?I?eRe1=7m1P~m0tiKDPGGTED$Qo|Om}zp=}<5zSaBm@k@2r&!;sZyHTt`|@Y%JRmMzI63PV(`^ce1X?8`34}B$v}c(@>nb;M^W^< zsxmYVpC4PUI@wIQ3s`;HgWPynG)scfE@n0RTk*^&;2gX0^zBH=JMIXUSY z9O%CW01BSS-_p7@wzyueKQlf){&{|WK4dbPUTZX(_I#YTi+f@A5uOzWgW)275}U5u camU~JH?K^VLN}qmng9R*07*qoM6N<$f|D6(3jhEB literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi16-action-tool_flood_fill.png b/kolourpaint/pics/action/hi16-action-tool_flood_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..f0900a77b4c17526d0e8a481c64944b9509573a6 GIT binary patch literal 636 zcmV-?0)zdDP)^+L%VuxQdP7M-hd<#zIh8jlm+O2qY5G1ng`U3u}!CNhL|C zh=ms7$7aEQKm|cW!9puR3$q6E-puvf*<@J~S;c|N&fb~xd*|HuSVXAP;?ju4&tB9U zdAqRTvgsu5IXuz5DRnhweucje0CWEg*wH`P!%Vd_as6px36Rrh&jVuq3fNVi+U|_0 zJ8KRk)==UsDFo|0Y7P)yySu?sU-`;T5vf_ryJeNX7F@7d$?`F58>lJm#UUQ4OefzP zdC3Rb&&FI2fqFeIdo=POl;?pmA0YOWA7w9Fbnn(kS95z4D^T3rzZO~eSL!HbKA=@_wDhKnv0 z1eCgfi5SCcc~Lhi)XK8VpNrUHAsxW^p31@EW|DA|2pBCjf*TGSt7Vy7^R28T&JF!~ zefQxXzr@n<$ysJI9-RmZJ>Qy%EW_E*=lbo(F;3sW_#oiJnU_l+Yjce@$UGn;8YK?4 z8Ruv2rSj>;%#+rKlv=)%SRQqj$J$+-JyaYO3HPJC8D|O6PPU69fSI*G4udI@?N`$y{!7w_HD_!p?dGI#^0kZ<3+XR~$skM$dm WNd2~UPoo0>00005r00004b3#c}2nYxW zddg1vx2*%t%L50bqY zC8FR*n;y{75dv8QIJ?{ zvy6^NISvLq9xre@=P|%xevTLXEc!wyaB_K!naW1m8k%Ihx83B1XMW z9}*&oot(0Z9@pJBj#`H0=~>1hh837O)UDsLYMS>)7rGliFy$)!%_P;`K2Zn-V zS*B8}J8_|{r9sVcZ1?ok#5Jqk>okvpNyuWt-e^)95g8mRli-ry1K%`V-`kTAe0qIi zTZ~jvf)Uo)!Y3)Y4?iYoRFrCuSgN3dWF#2~t**LwbH(rbI?&rwv7--Mu527*=Sv-y z*98$_VtN&p(zxtHdqXxF8zF*x)viwL?Cb<+`=@uomL;Qb2~FcP9`Ox^)7m}fSd z|81-%v28*^D=Dd{V{T<*WovI`=V<5XV(;wg;Ns@u;RR7?Xku<;Y7SIv0#);Gj z>FDa=;^FP??FTXws?5sF(%Re_sLbBl&e7HpsLajD&C}J>$HT|}QufhRK*z|H1o;Is zh>D4ei%UpINJ>gcNl9P2eC6uZYuB&exOwZ=?K^i0s=o^XRVI15IEGZ*iamdl$w7g^ zHIS<_plw2>+`s?Kx$*Db>8wvy{dK+QjxDp5QThC)%IZ>1iyS}Y18eMVNyxm|+j*dF z>s6UIQ&;mH+!CS1ruH+2EyGmQ?#9&QcQ?aC*f#|VvH$+fY{bi2)Ett#8)zGYr>mdK II;Vst00`WD6aWAK literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi16-action-tool_pen.png b/kolourpaint/pics/action/hi16-action-tool_pen.png new file mode 100644 index 0000000000000000000000000000000000000000..467b0d511edca61f86661d0ec46bd41cffa60f5e GIT binary patch literal 572 zcmV-C0>k}@P)1jIsl>gY{kZhA9$h6bYV|m1o{mv5g!QpzeIjh?Y7CnN?&d$mh4QI~3;M zK%hr!c6%aC#00EkB&{|(@P1*a>_GiR?-R$mKx!72!pu$cvVTgfvI6tHn#=U0&JjHT z_HiZa84a}74zNbK@to{ok6*@b(RhEmJ>b~}O1EnIf>093Rh zs};;GTKMx*+3cbWL!GwOowc2= zxi(8Fpjeo7qXRJ$!o~Oc5H%6OfUd12q)4c(XnO2pVLGXB2qDVaTUHe_TGlWlnl6TB z%v@J7C#tql14I@3lF6p;E;&E3@5s#{l#m^A4%$OQzHAW|hD~9WWx||jDx-`sU^W5B zIAn8*p8WnjBryQTp^lR+k#N6Fm@^#!147dpp)F0f%6mHM%76)ig(aE{4yTs-23HrA zI7tJdxS(VjK>&cRMP({CmBy5DL@+V#%2=Scv3MmuyB1%Lx)$ENu;&!i%sK2Pl;=dU zEJK)_j&0{$eFw!aSrvDg9FO?3RZNJ{`uKPlg@-(Qh@Lvf8L8HmJjb)pv^&pfmfg8# ztJ`I^5xkhq84W`qM5yMO80~i^k_>w2u?UZS-k`sJEoX)`}Y3b$a`RL$|nX%4YcrEHOTWoRRA?pCK zNOJ%KdHnTNfSk9#`g?wE4re@ZtHXV{yR)HWW@;ecBHN=z9c~z)L>mAgO~KEeIF!y@f#p-{?9Fl)`~5x7`@WM}Yw;hLq69ka&Ml~2NO^PhfZ*WB}%Is1CCLn*y+`O`Ee? zeXjSX+3IjrKnpZRvEj)$pa&K^C<)fpB?Ewu1`9lgHHT<8^uo4B$=p>^O}RY-W>Ems z8!*qXT!CLHCM#m{KwG355{bk_Q2@U#^ZkZtDSJ(c0))@c9?=iEPu{PaouA(2VBQGn zzE~vmkv+dqR1vhEC!nda$}(o=hl^F}Di*oi&O8b1g!~5NJL<_$#;lon#$BVd+gZAP zoL6W*-{^IFoM@A#SvE|*g^&Xi^C@Bn-TqiO6w8~{5W1k&diwhJ+47h&jApK%Im1RW zrPqT2|FenUrqY&p!E_~C(bH^&TaXU5PE7{`$Ns`sS;`>oHT2#T9#h_8Y=hrY$<$yd gc=9fO!N32`pXr%sj}tV+?*IS*07*qoM6N<$f@f~|+5i9m literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi16-action-tool_rect_selection.png b/kolourpaint/pics/action/hi16-action-tool_rect_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..35d9f18d89fbcf211618b158a0a428175dd09a35 GIT binary patch literal 910 zcmV;919AL`P)5fCdL?0X$(cNU9>1F6m?NSTog)hqi*cJV!J3x zw9qcp){PWOgBur01ffOS#;+&Qik~5QnwU?Y`F!`zojWs~n<}_>;Bc6k^E?0l85q!7 zqxV^`{=<#COr|C-C=H(i()AdFBuHJB5;rKK+AzYg!sxn^l3{?4Kx2upzEiw$qi+H9 zy!?X`T|aL;b@J$Elamv9Q;4>0+q322!@jz~(z8YxOIXv|ek}+qR{yhqHz=iGaiQ+- z4ecGBi35H5^e?xXn_IY6D2%1z%4E}nD}=ZZic6`(I8N>m=dp~0(BbXdMRoS4uaDB| z+7G^3+1RvSN);N=6dFJy1O4Ab#F8(-O7lpdkaHRFNQVy|4JZKQ(V407O;gjeq3e3{ z38WBytC}bp7>93F6EKQ^54?=Vd;WMDduDzd26OvLrIHd-%^UlbC{pv0kn`MRZgLpO zN)RM_84isE<2}8vpIj@>jJ1FK<%H1gR*KAur4EEZl{c#4JE}b7dTwY$OpcZLEWv=A z>eGHpgT!N_&r^?3Wf!&VRCm6rHc_23vvMZ5Q(xySO55Pr z7PT-|WO{6(fIxjtQ*OS;NoCgxffpX?#Y_I;hhHb|FRQg!TVp1-ye^gVU7XEku*(=> z7DWsIlfvT6qQpI(DG#6RyH|9~uyYr$zUtk1;7PCND%xDnyrnfXSMaf)_pQ92we!A9 zYP<|hVo6);YNj>)F?j>1R7B8yX{z`5xslP8D{O1|lC)D@Mbf@e&7`U8I*?-~9ELrM zptNBuEtxWCks^S>KX1PN=lqqC(ZtelZd*(?}`EI`y!nJJ+ zb#@-S+tu~`(sFrx{laft*U{006>Qd^2$uLhKzv65-D~a`!a7*h(C}OT;NW(xq1u## kjO+`PMV}bM`n|vNe^7AD0S=6RcK`qY07*qoM6N<$f|5hGX8-^I literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi16-action-tool_rectangle.png b/kolourpaint/pics/action/hi16-action-tool_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..b373fa46bfd8edc9296f14a0afd259fb9803b848 GIT binary patch literal 548 zcmV+<0^9wGP)r0`JA~9X>ay*I1zq-n03{VM2qKjP8ZL3{ zbD7RW)QJREB{&+-v8DOG|18GJdk;@3^>X#~yUopt63@89EVTG|o z!V*7ukDbGI_e~_S_hV-{3e@)2mJdBc@=WZR1z1=SAv1d5*mDB*qNpf57bMIq{C;!& zMHD~)qLnBJP!ux>V}Qg5U@Qg303uV6DJ~djaK*#Ojs*n@-b*E!N(GK04x%gj$^kC}wfUet}QNNXr23UwhZX!S;x=WuwelD z91B+`x|%lgvnFZOwRKjR{eOa=?cwQ{-K&!-P#dk57EEoSXOrn)=+Vc5@9=tIwFkIW=aj2k@P5AKr!#{ovTnn4YsXNsNi9)|&8Wjl>*Ts{l6e mW`6}MCKy3=1vY*1@B9}*x^o$e&Z-vx00008+~FEE4<7arcn`7WIAeCIyy!2p1c6Xrg-c=-y} zDXuY~aq^IQM`Kmv`^wTH>^F!mfBE|0Y&>zRu=C66?Tynwutg{ZN(1x<9`|zPcbRPF z)4@*mzk=B)HN-0fI3ph31Td)@v{jlnco@lHI5K_wu`U@oZSOaSLXahGay4xBa+Z!qADAzo`Jn##HbW?lO%=Zwe+nj-+;#Tr z+>_P0MJKbl&1>k0Aci2G)(p0sa59+`smbo#>_R!GCXvss#5$t!@xRM-{_5m_pX8o{taX^pom&R6>;J~K z2x+oR)8``ugljvce8g{uoEkedyfF80#cOb^Wjgjc{{c{=m9|*cr11a%002ovPDHLk FV1lJDSy})9 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi16-action-tool_spraycan.png b/kolourpaint/pics/action/hi16-action-tool_spraycan.png new file mode 100644 index 0000000000000000000000000000000000000000..f34b2a1d4923f60bf549024b59f7347ef91e8f13 GIT binary patch literal 776 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 F0RT4*sEhyr literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi22-action-tool_curve.png b/kolourpaint/pics/action/hi22-action-tool_curve.png new file mode 100644 index 0000000000000000000000000000000000000000..9723e2093fa098cea3f1ceb6ab55fb9622255f34 GIT binary patch literal 639 zcmV-_0)YLAP)b zmA`L|VHk$5>pe{+jnD*xAS4VTv3&9QPF0bx5z~v|7qqi3gPSWfGa7G1q1AG8?D&N5!kH;RpTqyWY6iJ$u zfz#@H;9!h#^ONz>qH+vqLax>AI7zc+;F-D}*gtdZ9mQQELJ{3QG4k+vJ&up78-TvN zEQNj)p=+Uz84p7rO=rFcI!t_yJvkggr4bDaJ7E0 z_NyO52#0o;Iw}B3nuSv5z7rvYF5vaoFQ5A$XS@ANt)+5DAruDCg7Qpl*04#9u~s`g zTphf=C@?@7!KzAYtxdB~);?y;7M7j-*mFw z>o!_`sFY0YeCb8g)wNyL*EdRpneY3?SAGB$`7W0lHo!fnzR8_AeS8g}tU?HT_v}wS zM^=Cz+tS%Bxck|2J9d~?UwKW{)Yi!mqI3{Lky2_AE+LhI3OPA3Mvi^`c|+l1Umc;= zo?p-P9bN)M`%e=>zT35PmwsdS9`0)dfEWmCfG7p)2CrmNvaffvcaQ@IKB#De6aZqSPyr|v@?)?P0F*a@B7&~ZZM`||yb z)03BLfJ8l|gTUA!lch6NFXWq=d`yy_$4#s&D_eu(d(}B zd~tTcrz|5vgcxiqz)+F@o=)XJoYl}QpS4eMkA zS!Yfh?5H?J8=>H>r2zq3PByhXaV6WdyI1QN8LP~uDN6$+Qfy-emBD7zVuUb?2&g7K z$3JboG}yC#X+VOQv9bN}Hv{7zPFFuTba(U1{fSIdIh`alm!VcFQbt)S8Nx9@!49y3 zF-)ELiFEJxU$p!=bhcr|NJGT%t*?VWhY-?Lchzk!Hdnpq5uy*+$?PVKQ5#0^!T1lsoql8dORHV<|G)pde*yS2 V{NZ{Oo^JpE002ovPDHLkV1jpD6LJ6m literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi22-action-tool_elliptical_selection.png b/kolourpaint/pics/action/hi22-action-tool_elliptical_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..10e8ed3c16a1dc56335459294033fa1a9808ea66 GIT binary patch literal 1316 zcmXYv2~g5`6vzMNjA5HQJJaM^ZM*GuIqfW4T*{$Bf=ZT09%-p*0cB<$V5F^Sy55?K z<`fu`VI_GN>ybx!q>@%(nu>~ffvBW_iMpHntKB!B-}m=^^E=-Ay?Lpw&JL!VcWj0r z$du|xJ^^O4F9T)-RClq!1WZQEBUBF<3?~2JG6t60Srl)Udnkj&4ve5fY&IJnbUB!L zE-;J^4~;m_(-3w*(8dtgV;+?C^>qk}6N%mtiNxk+MN+cx<;!t69JhHd?=T7jfcC$- zTdMl2Z2>ya*(h1uitXNRD?@rpExl!q7lvFfzn2X!^^Pq0M~?)?jMAcq;?v85V&##k zAEbkym>HwjvJ|no6E|*6l#AsF`4h=^CR2D*8GO}E!A#b@sVu=XM>w5VJbg!`epJ!U zE1fMUo8_0Q3M*8F)w4y_vj`04{*yW3)45{t+zW~JQO$fw&HUq<`2qPSQLTDdu~N~f zL1J)~O`56(O?9K@X|o38EPk#Lw=RlX=4x9%HoVkYlc@D=U!2AcZByq$lSGR^VHn|D z;JDE$@d$xA#T6=VDJqv9;y7t$L9a3HA8(k+*sSZo9?UF5a$#mT^9gaYh z^y>W>$&%j1?mm4_pZ;~f9s~u7w8Vp7O9%C`q5m11G=QzQ1KK|M@_&%Xj{Wkb{^6xb z<;uY5a^H|{aCCW4@rB5kTde5ZA;r?r*wXO$GGOE@cpE$L$jF32F=?*R8hb|Dm8 z2?~jDargG|4Mbq@mIR`wx1TkUOrkh~D&ee&ID#$8(u!b1LZCquD{%f05)Blv$7yjp z=+4)y6VC3?Mq?Pv#Kg?pe8C?V`e*XUcfoIRrXf!&5aUR%(py1$3At9GT!@|Ou zz*wxv$jB>KqoSgt|A@IB8ygoFcOyO_Au%y2IXNXYEj>LW^JZ3d4u`|Z&C9=io6F@D z6z~fR1%i9`?hA__J}eQHmR43)KB<4!@cczfOUJ9XQd!@7xkB+_VtRI7v!GvIURhoH z`yadXOb!HXpi#*r5A$2+0(Ki6;=#GQ;bv91lwCfk8AIJxIm6k9X&4^d7Dh5#E4$YJ zhL0(!xF4mS&XgSc&aJVQ;SnqSTij?0mb5NiP)L&oS%0( zGZA5TakWsYhtGM+H zdz*w&VUx2!R#c$Zd$`z{?lMAt^K0;r#@K8bCw?x^d#5Mb4;g%b{d=fwP?CCIK=J2O zh_oU5E9IksZsDo;8swaf$G5Dwrj$I+&ANn>SA7}&Qa!VAOP!J-+~f$OTBvU0*C-Fa z_vo*gx9)0x9u=LN6DN37`ruB&nM+>bF%IjKd+5Y7_1FlViIYerkD}1?QIRfucZmct z&iH|;+{AnK=Uy{R>9DvxKXZd|ej%1EKqG=js+b+U=GoA-w+Dan|IDX6=-xtupo7R% USrj%Y0dx{Xr8twzj|9a33z$u_*8l(j literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi22-action-tool_eraser.png b/kolourpaint/pics/action/hi22-action-tool_eraser.png new file mode 100644 index 0000000000000000000000000000000000000000..e3fa0282011d9e7bac45bf87978979d4f8b57a9d GIT binary patch literal 1218 zcmV;z1U>tSP)E!QQ?Ln%kTH`z$&=IbzE6Jd|NTGB zb0}3+;b8;~|05`h(tKW-u?L^rmPM#|+ZksbE>77w+H!1ub+}g=JpJL~@%@?4<2v)7 z+6RDYnvOj6_58UOUcAw#N<+PBtnY&A{O!lc&u{I!ke6aKZ>+zs#}vjB_T;QDY7X|4 zlsg`^aD<4lD8Q-+oFbxD5hC|QJnNGsEk1yCk3c{OLU>{Yify*r^BhS}GH7>^%~XDLXz%gz zvV&h9C@wOwvzNes?mTEmr$A>^fwuzqE&=3lXnGF7tvP^HMJw5!-E&PW{M}AfOM?P5*kcsf zOcbg?aKIoJ9!17@;dF24Ij_EGBC>jzRdE1(GCLyULuq<%03^J(75MvcDr`VSNn2x^NJu=&$P`TOI^ffdz9O>!l zxhbN2v&9m>I&cU>pB1zO*!ACVv44PXlal^A@#ob3?h#iv@&cPdG1iCBzjM`ATU%Rq zsJ#5`#|jEE%w{u%Ryc7zXTjx)oo#O)Zf$KnIXO8wglijs+nC1~Mw7|3tG>Se_xASo zfXCxm?C9uNuB)p%je9#WHQODto6TJYGr6LoqPnrMakR9wv@-sVN!<^&oq3$FM~5jS gI{qN>&_1UB0Q%IMrkzBxVgLXD07*qoM6N<$g5eEMbpQYW literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi22-action-tool_flood_fill.png b/kolourpaint/pics/action/hi22-action-tool_flood_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..0b98f3ac27a7a9b687262e2a78982ca57011e512 GIT binary patch literal 836 zcmV-K1H1f*P)M5}RQdo-(@hH(fuI;_AB&(UT4ale7$wS0Mu8}`h=OP`!-z<5 z)h;klEEEbMh`y*%g9uthg%Jf|Iq%-v|IEDC8_cOO>foPqcW)<$zBBUX~ZQdRu24X%MU!q%Wpe=Ov3iULuj;p@}z@ z-%@s;Q`BvpVn{j2;9wz>C3!>xNly7j&PnTBBOdf)8eG*J--~OV#s$_`MTO|6q@8tP zhOA_POB2APQ2-a20FJGBin1ozV|kDc*xI1T0?BC@$%({m*3c{=yWkhVlzgY36Af;^ z3GmATmUssR6u>DOy%QFiNMuq5?vS%OhVZaKEeE_8DhRo}NCjC6C=G`gB2i}TvNt7M z#h9nR$70^8wBy2M+&4FGssbBHW?RI}CxPe4DB-1pCZPq2jB3aDMst||?#~aejpqE9 zu4!#Qfx@$j7(5E^A`dCbJgSYh1rPfbY3JK}{o}vg31*`D;9P+2Ejh+=#9DhJ7)z)RfNz-@ zx%gOW76Gdj(E_Scl_U0SE;$6}g8&-nHQY8%k6wD7tDcQhGb&o*$y|lKy}b_34;FY- zy1${UB018Bzsnt-G46u)s1{>*;xXM`ZM<06YOEKS5|bnvBI6GmnKQ(( z{=m>Nq%E}msI^$N%7zp+B)W1zL_l6tl2^IB+{=AEZ@-=^t2tJHJ=?Q$_WS*w&+~nr z-#HJal%nMqzh-N^-i2n_SU?X9elRiGaj?@>@hvVb-j^yPRH=#v){qJ+}yOYzRckJwe^LthW z#+a07sv3}%e!!&D2~Jg|o`}fUv9R#yX4t+J6&Dxzd_q#gz8A9-8HU*mYfS(@0nR+^ zaZJo}@ft=)5JtfS<6wA-sVOg)4Gi9V)9rR{t^VZV*N9?SRgtWyn(Xr6?!25m1_(V* z2xju$IdNLAGde%3Kb32rKml3gyxjwgddx(*%w@REPP2D>aH&7&yN z*{YK5l5;GBAv6|4(;ox}Z$js58-CIrs>6#wP^PI!@s>^j>{SYKWm%yzLK=cZ8k9`! zPGlRpYn%S+^W%H1mcI93tqXi73uSgqO^qiivgQG`UZ6rXD*aPB1)!=bkU=Jhq28X# zGLbA+d3JPKnRD4SspW@?G=RPfz#i}vv#H%pDAl?oNzx4t4e&Fwv*=vSnTUfqnYjbE zdwJ-AM^+G2i6pPO8LMjP>bDQJcRTw>Cp>coou=Ecj`f+I?As0d?cm7(wIDDS{0bpV zk;fy;MTKv?lzf5D%11YgU%k?I0EzcV}-Q=tSIoTUQe)|AqrP` ztp2s^jF5ixh_R62a?PjTCKJ(b!fCg6U`#D=iMwWIrqb_Ec>ZkdwvV|TR1IFQ69d-h zc^{DoB9A$qV>wB(il@qRjyfF+UD+A!8IOYhb5=yB)f$McrF2`=+rNc9`!osk*9(3= zL@;v1U@$PeUWfVWk4yLW-Rej{lopw<|53fFUs{>EutjwZmyh(GJG7{j3PzrYqJcH= z91%NAKW_!-E@=I)ogJpLtDY4%Or{=0QMY}5G~Ml*@HGl7Zx9%s6wDGN4h5Q}M**=(L(60-8;6~5p9lz|&ah>D4KTgGl|{_=~; z!wa^#5IAkc6jhk7w^uGFid`~)!zjfcV8VnWs)j%h^*2q=9HU5@wb|zUF-Da#%be-C zc5QYEXT?^;0Bj$k%3XMG1n-?3zg+|0y30EX(PP+OUE@~L)e-U100000NkvXXu0mjf D6Ms#E literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi22-action-tool_line.png b/kolourpaint/pics/action/hi22-action-tool_line.png new file mode 100644 index 0000000000000000000000000000000000000000..22f5aa594bd64e41c713d7752b57fab316562409 GIT binary patch literal 395 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)6%*9TgAsieWw;%dH0CFWuTq8aw=A z0BW%XYjJS42WfE!YH{)KcJuc0^bJT*6`uffgIGzBUob;KVNr2OX<2ziMO9UGO-*fG zeM4gtm(809kSjf19780g=ALjDI%FW?a`3yMWpl%Cd4cFBKBDv9cEpthcxf)WQ@T5K z{jIaE`dh2tw{AODwJvwVyiDV3!EYH~cYf>m8c}_yYKd*K?UlFOQ+><& zTI^fq*IvJm?DBG{OT2s2p)OfAI>WBw{epkLR{htpzb39;l^fr~2Xq62r>mdKI;Vst E0Fm*DX#fBK literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi22-action-tool_pen.png b/kolourpaint/pics/action/hi22-action-tool_pen.png new file mode 100644 index 0000000000000000000000000000000000000000..fcedd23f1600f922f7fa04538a2099300b5389b6 GIT binary patch literal 827 zcmV-B1H}A^P)w~o5YY_(4OIRRC8X2ht`Qk%CU|1s3LJ--LkVplI zL}FP%WR8~0G_F@uQPE~bof&3EXZd$-j1)M`rTc^L>D==>-~FG%5fQ{NKF;e2OD@z| zW+r=#ov+J_Ud|aI1``L($0Fo=96M;;@`Re7JtF?ob8qv7gJmN7#KqIE_8Z6tqQxwt zrL#oCmDG3t=$24f}A;x+PEf687IQB^9T~(9;SdC(0 z0JHO0apcli-^^{vGuD?tYo9#qAy+ifxRQw9OVkE>SDoD{tVZ;WQpi%?XQ3zNtc&&BDayFMZjzVC<5RIF#9XT?ppJ-Nfr-X=CY%*R#RZx^XGYC zJp<%bpu2;=Q~jMtvN-nWz6T2VC}36sObC7g7H^h!e#YDgh9vRmRW2qvM}8cxZNTy% zTg?GkEg0xsc~PY&I|9IQoSb6>6^HKLbxRd8$ib2Xcmd3Vh+S#N1crB_e14=soLL8!y?CKgpnvViDi+=X`HqcSYqYao5koLX07q2OP`_l zH`M(7UK|BBP-(S@ECw=DfyHlFOIt!$Q+thH7z|=r7eYkd&6g(+4TnHnKJ0OYXzv)V zFL~>15fQprtW4+>qyZcA7GHd<%?_Evgbxi>b!VRbs6}8r{)TR1h>pUhOx#*pQG?Tw z6ZPksMrwoo?tjfVIWGL?`SgW`Q*|RZ|B?AvyqnvJ^BZXj>PW4^xbXl0002ovPDHLk FV1i<>dny0` literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi22-action-tool_polygon.png b/kolourpaint/pics/action/hi22-action-tool_polygon.png new file mode 100644 index 0000000000000000000000000000000000000000..9bf2d40bf9f6bb6840330d0ab5aa239e3e9f13f1 GIT binary patch literal 1303 zcmV+y1?c*TP)Q;(&=k;iWOU*Ri{te>`s{=|Ps4f@^ud1tBWN4~UH^{1 zpc{KR+B38=Tp(zj42?{%KnAbi*%pmXbisNCbo6z=2pS6dDWpi)ovw`)mVOOLIuNoDJmDwov_ZB~`2#iFckwzdmpW<|N!+)^T1lwV0} z)cFO*8EClkCdwGp3-WbrKtYwWcOs^`b0KxG3e88O5o*DQVdG!U4j?*9zbj2aI6<5*d?Z zMo}J=dX3!NQ6)3CRY;av1$Cz8qI=Jp^6%_GCD|25$q%LT;w*u&A){!n-ZwB|8lr!U zO2K8jd9R}eLFyVvy+^9FcGtIAyF|^F&bv+Kc6*d1qGxi5@;(os`NGAxCYHpF+zdB9-!@V(b&*oQKseVRO$SY4t}*s#;XBA8F*C-vh?D)y6Du$ z6+Y)D_+Doxcr=Cw3A|efd=wn9c_l?KWp5CQvQoOFC~GjapeV0%Rch$P7K81%&J~O+ zaw?6I_)LQ^ApTL2Ps{}W)WzS50^_GjPA5JrMP3KRB5Obcnc9HhSkvApQG;#?4RRNc zQ=}8lngTs%aDokPkq-;}VkZj`EHr6G5OHm$3`HHD&?!OLgWT;t{+7;k-KcC_+Og5? zT9dr+9@G-j4T?yagpB@#J)b$!7$=aWT>WnJhbK>$M%|bxzmWd4G9l|})i*b1?}lBd z?AZl>Xa^JMhqT;ojon7j^~SLH#+IGb=*$$m@o5ZqJ{+_a94l_V#GS3Wnro;@<PMkbzM1i0NqZ0a@47bpkoQ}Oi=rYBL*m&1bpYe%vaWw^9Puwl}py`FPJcCjB zViI}d=td&Xqx%5vxw#ko1=@HF&lOSVZX^|VA49b|Y&1G^{A*#HZsiFyL*h15@9wNP zw-;+;F5}#}0wo|ML#h)q9rX>AzMY)cm~b2=;!1zG3Gf?t1Zc-xL^w zbDU1F|8Vp2+t>xazaN+d%6ZfGo5zq N002ovPDHLkV1j(QeVzaS literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi22-action-tool_polyline.png b/kolourpaint/pics/action/hi22-action-tool_polyline.png new file mode 100644 index 0000000000000000000000000000000000000000..e5ef4efb47b24a0cb671cc8ae120445b14ffd47d GIT binary patch literal 772 zcmV+f1N;1mP)T~# z3cC5zn4~ekZQ6)~ii=KNT+&4l&3it-BsV1Fg(O3J!g=T2d+zz(x#u2kgl*f}YKYNl z!Tv$-2Fy5$h#$FZCfW{Iwz1|qI;{yYErd2R=sVE4Qs7PvqEI`ebaEw1fe1@M;fvo= z>XwF(`DP}<_`0zL?D4r&VzDjoKF^RLr5shzqPZ}iPW)Bwt?Xl0X}aw5po?vl0_Y{o z_99ft&#D)!g7S05w&;c+&;mA50w6WqFFa?MT`F>=5bN6l!dZJHJ#Ag zs(r3}T$s1Y9z|}FHMPD4yx2*T4zRB47XkDU{(d2odR;xpGPYSYlCkI;{GZs>9u9F_ zRu9bAO=OT*li}sM9;qH)b9pXVqLZ~z%}U;2ZX<8^vYF)9y4EW*8(gsm1BZLr{#mKZ zQ@YgqRRX<+$LrrgXg}i#W|k?|wFW)iRji*aw7m@9aTaoo+kF(^Io&Wu@pni7$Y(C8 zr)2O@&;`Gka;qEc$$UC_x)$m?6!6nVsY;=FMgZ+bH=#_3JfN2(bUzjeaO(9HyRmZ{t2l`J5sFX54qqQazv*D#y_wfi3 zJJtA=qxloDSq|tVupWj5m#gDZH1yWgTF9#Xnf?d$v#L7%uOf#400006YCy=gj9fhc;;%qEAfn?aX`+zxnT( zlU<33a65*ix;=31{JTG+c@83L{dLmXwX-fZt!1d60l*M|DFpVT@~a!|eCKvRr~v>) z6#(Z=A+WzKjJDpSm;%&2k+N2tIrDSYBwo%K@0m7Zj^6iv{imOAzclriBZsRa9RWrN zRpV>?*Vnx5Y<;SDLPzeNNLddd57e7CX%MIR}hL;;oX#}M;27`0OmMvS4N3@SL1Uf<$M)6wRv*7;iq1lJ1 zf)ImE@2q*Oc30D819#L+m;eZ9!WbxsK}{ecp)srs7!hEwC`Mx}2qRD`L@FI9h3cQ) zY&DrP4LtXy6{|Pt2x>;8jM3pKX-z1^X@{pIFvhQ53;X)>0|kScT7TfXeHBmD*B6H1 zTcwOq0!OB#eOL@I=*vd~pX}_;AmUTMuUH>gqEw`u{0oRBIh61xWj-^Rl{f z_~`X)X(^NWlwCIA5+c&P4Z%Z{c)u^wryiviv^*_J=w@ZQCC z(_1jxcPmR|qBJd>RMJZLo^%Lw4Vth-k`jBA*pr+)LV!(aInbD0JO9s?9kA~vOk+5j z@;M(pO2~AITfWC^b4dxCB?wDcoVijm*S5JY7$ymKiF7#Q#?thow2_?8Zf6;ev1d(n#)Ej@JSU6YI3wl+S3h!*uB>HB)a zhK=oe_Iw?l&zzkWU*4(XNA{UVq?fY{m6 zyf={c=|nJ4!vNA`Sio5D;_{U%ITY#$NKtRUH2R4XCkAMG4sJ32qyP9VdcRXO-q!!T Ze*uL(04rsqT#o<%002ovPDHLkV1m)PNQeLc literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi22-action-tool_rectangle.png b/kolourpaint/pics/action/hi22-action-tool_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..8f94c8bcd581e2e2acd4a2380e96179c28429f92 GIT binary patch literal 795 zcmV+$1LXXPP)?3AiiIP}2CyF2=J{M%2vYgszy#6^S>7ZmdT%EM>Ei8V!`J*`hwxUwsi zQ%NbfKhGQaU}e=U-?(#d@9u41Ybkji9o0}Eawq7SjfFVVi?q^UrgipO!H;MfkDnS$i*I6p3ikwxD1(c1X8Cd2OU_^p~vxQ3(GneQH0~RIC3dl>B zj$FDqm%K>fnssRICjAo31=hMVU4m1T6Veq6o^qMz4wDHSf8Ct)CX;-*1T!$FBCrew z0*fRA212KA$E|ic+=oT{(cJ{Mmr!^hMJv*MJ7RAY^ zz+jokd_$3K>O8N1^tDKzgB3+Vg}O)s27R9&c=p zQ0YdbO+A#RR#RRr@U$^P8&B$PGzZ>^$AjbUV$gbBd6(+iZ$zG9cn}Cv3lzCl+9;`` znrBLId+#v0dUG-8<%SNH8pbR|%8x>2Xgf^kE8`midxExR4m{|6xRp-gYf_#KRZ2=@ zv?c&GO%UEwrDjcQ#UW&~xh)E?6Y{ypx@$)VJ$?RZ6}_V~0~(>ys)0Xgq@a_1@&Env Zegk~5o=R9~)QA88002ovPDHLkV1h5GVR--m literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi22-action-tool_rounded_rectangle.png b/kolourpaint/pics/action/hi22-action-tool_rounded_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..758e6175d7bc14291087972c4bef992aa24043f4 GIT binary patch literal 1096 zcmV-O1h@N%P)7mQFC0Y> zbfBVBrfj3=L>Phyy0IZEtEc zd!CoW!AdD~GmM~{WNgXUdjI%m>rS2cWLu+A>ka&c4N@r!H4|!OwS0oHZ<_X%jzsGF zM;?7_r!ay1E;_Ea_8{yksizVD}fZhk1c-x7SpY9$UdTmvI|J~uKm-p>^?v+h_ zcZK_39H z*wkck)y9pRe8)*ddSi=HB2dMe2Y`tthE$pdE~C`t(g!uJi^Z`VnK5J8PVW7BC#%=1 zUD=*3^N~=^^dM;vhWaTWYt<5nAjX?f2_1n$T2NGKe6Cpb-9pKqgAyt;wt9BK%WY<` z%ZSI45eV=w<8Q;gQxrVkxET@#|Mhe(g%$uQxm%{_C5^g;3CdTS=glLCE=N zE+~zTd%pvl`cGGy5E{a5)6`sxmZ!sOUw_vqx(yMuD`HxN+7=-O#bmw`BoM+rgL_^q zPF&4|p%RyJ3*~bcy-GV`7$Y2u5gM~8vn2zm4pTFFF_F`#z zEXUdswQWK>V$_J+*l--e9E(xIK$M3KyHU$`QI`0YJo(hq$ATbKpPwAOqaD$BoUn9~ zTInRSGAYwaC7GQ{5+{>l&iSw2*sP==na=(p^7z&VM_7FQA>aG?_Tr_3Tf)T|=SHF{ zI;fRQ80LxuwGwfng)Jg53Aeo;{)WI|Vu}>-9ixje3LHxJFxV40_Pnr3H$?cQ+w<9CKy-{}AM=KTRg+SQa*V(pj! O0000~y#2hV2x?rVGkyBt(R7eo|sB}R^^F_pjE)SwY zLSZkJ`P4($qeuiol7S!R+GqcZiU)H{?9*#3*h&q4x!+Q9y4?kF}wKSy^ zcdH8*F6z_MlRrr*A9b9T!vg~^9OpjDyw^afRN80T)&|#|qqD1f-L?s zx7vXHfH+p!*4F&zi-RMryHv7|m&=|pIXOoX16T`aE}sJwZEZ`|9X6;gFPJ_yEB~d3L#lSKte(MHENp-T9&}FEG$c)X&|M<_XDcc8lLB)GmD*T z(I4r{5A59eQOE7@&)SoOtU@->`P8eIegB~lVyuBTdOo%5R`9(+3e81AV-+-YcK7u? zaPnXH2d$Nm{{MpWy(0JHM3U&|48u_K$u&)fNB3Yx?F4EC8N3Pu<32Y|o&1iZC$1#ax&uk=~;yHY} z34s7(A+3*jYpr(g-d+h+{`3=VeT zM&nf^c`{rhq8C98n;8_#$uN#U0o<(SJ{U4LyuQ>LT^o2IqtzBBD>jak#c_OUwR-Bh z;ctb4u|>yc*R50zHP#T)ilR)FXGkd`NeCM7fdf0@;%xTtgt5L{G#?|-+Y!PMgnSf?$v=foepYg$iZ+%-Tg%^b|Bg+y9 zVb(k^9xN7XC&$K0oA>YEUT?g|l35JK2-%3iI5Dfx@kr@IFoDG+KDRyOGc)yWg)ZAMd{u!)3^QOu&zfld~|-+8QhdND*i}1^j`*H4J{m;xao`x)}H$;D8A@&_f}J P00000NkvXXu0mjf08%BE literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi22-action-tool_text.png b/kolourpaint/pics/action/hi22-action-tool_text.png new file mode 100644 index 0000000000000000000000000000000000000000..98602e3630c2795d7626e8cd7369a3d37db27b20 GIT binary patch literal 728 zcmV;}0w?{6P)tzv#bre*9;AqiU6ta&KcRZ^;6=oXA2*Lh zMA1vJ!cx1Xwe_RwNqP{`tKhBRD$MJX$(U~QQ5Kdac`_fF_cw2zOcG*du@jr!OYqU- zxm_r7k?z0HvfX21pP|DE{ZA6Xx#2>CnNPu_HA(W$>hj{Bq;W*_1>>S%1+B|C)SE)Y zBLtm;ZYfsx($f9#c4Zue1I?JOr%-n7-Rcb)@r+2)GzIAh(gj^Qk$3yk;DEF-Mwup~ zt?zCNIO90u7<3R49dHY^YcXQnmarZJ1Ji&|(V7e4MCWlgvI{uHzu5hn;F1oFgRKc>7Gzc(Cc4Q2I~)1ItO@xccVqzme~mf8y4bX$RH>dM zq61KH#mLxVButcc@p2bV2-~vcMX@>21~Y#Qn>twy?nh%k2Z#xrrlaeZjU^!%Hk5sW zQaH1E{<`_99?hsw9J`9xhk=W70Be5m&Emp(2O2wX@mP{1i%7y}J?Z_#hc4+cLN`KN zSXkjl%k>=e*~Uf-U&f4%DOYP}0d6Rz=9_b~cSXupD2<)L@GH!!wX`sQ+q|tWQB+08 zLpJr9o_c490000< KMNUMnLSTYCL00+z literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi32-action-tool_brush.png b/kolourpaint/pics/action/hi32-action-tool_brush.png new file mode 100644 index 0000000000000000000000000000000000000000..1f60a55847eb89344f30610b1870ff440769edef GIT binary patch literal 1557 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi32-action-tool_curve.png b/kolourpaint/pics/action/hi32-action-tool_curve.png new file mode 100644 index 0000000000000000000000000000000000000000..ed69479eb0090c376b1f3b5b7fc7e37b97bc510b GIT binary patch literal 907 zcmV;619bd}P)?*+Rna1B%# z^7m{{4a6f3BO^nx2~<0nH8mm3Nq79t` zl>G>*#LV?j`eY`j_(0!6jmI~LW0)X{&|aorWFzwiL;Fw%l?b)799B$M)wu64d3Twe zn~Ja%S>?4d4`^3b@<3?2RywY=eu#d626-{f+F4L&UM1CNVzD%b03txjC#cG1jM+8^ zP{LupLRzWa%bQS)a+bx##bF$#RLZj=s{>Zc$>muH^}5N8m_CW)C}WS?7j$OfUqLRE z8jPore*-^3R>5ruAWHLcUnbl6xw%g?T@SQNhJ1m~Hza@naHXKCS*+x+na;5gF_V(O zlS9SM8FP_nv^R1op95z`PFb*Su?T=07sLy_E98i{$@X$_vrWKq%bY z(A1nSYjxp9XNlAKi1P0>{hyebp1PNIC)~J@`5DN&l{uM^8Pr#T+7AlMF+ZD3E>*pX zJo{jK03w0 zNH9@C2=L$!5QszxK@%cUs0h+hY>@(O>C$bxZMWU-?##V&$M5W&Wy=a!`@(}xdV21; zcV@otJLlZpyQj(wc4cMSm@O31b>q6$-QA_%yxFaX!#$ovVmy66NZ6{X?GnP?3L<@zA^_lN zP_PjXzY7H(UDuV>t5%D3>(*PPC1o6dHW{}a+qNA6qnNv6fqC8=?$r(**i(G=%ugXl z2$eDd5uX8TJBY;ZNdVx*9*_4VW7eY-H$K}aUVrTktD>@sfB~S`5)dYV0Vv0ujLY@R zTvyjM&*6h#l(%0#S0qbgvxW)`^^@kpP}vy3 zYZX=7J23;K{!NT`hC(4{&+dKW1%*XWDb| z0^;d;hFlIs`}G5R-)>}9A`f_PAq$_Y3!hx|g3 zZn3*}wyd*|nE@)3IdUFG z3FHHmGShM_dy<_^+c6xm#%_eo(b2fg?gpf)ilXbPs;L+vpuhCIt=-=Klz~q8dVG8; z)&3$<%H4WH{hgf+0z94&R(IHpb>A|_Kv?908iuMGx~c&f9+FA^r}W=TS8enW4c{-7plD z2pCu+a6?lxVxWOgrUZT6r)$X*HW4_2TcgpzTg9h;{-LnGVMQOerE=`s_@yg}Xd)$e zZf*0tn(hU#*Q3EyU6LU6@NNkRl*%F1%S^Pl{!pGAj~3u9QDoqVhL1z$IARmM_}I}e z=L`(=Yl)<=E_Wsdnp@%#0?(~+z^@zmehsF3@&lSDKw}>~Y?_~9ImSI+zdKKJf<^N$ zH0@vLrhgM*Py9Cg9&Tl@aOP-D!}~`Qe8xy_gOOrio~A=ROig>LxrZ`9-q5H7TBZqg zje0<;kB|1}eY^j|MX}+&AaZl;x8|mmxrfqM@JPp4EK<;VYU}gy==suIaH!G!AOLTW z`9Q!2k2#7xFgcbUv9w6IrTCk@?>$Z6^mUAkufOMk1e{JNigro<|GC=AFBG>bOJhrRm+MgFnat|c;J(6Mi0}S6moqQOJ!ji{3 zY8I?*Eh>E|g%W^>YPd}ZlV0usNn&KEH~-{`eY4wIPL)Beiju+7#j`;M~3_I$t*KzxUaX-1-l&gP&(yf zIofC&j{j>=8ox4$3yM?tcNoAQP3BlWr5`4EZay;dn2Nx*t=6gjF!}$-=>sS57dxJT UJgZ;Z#sB~S07*qoM6N<$g4-A{ssI20 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi32-action-tool_elliptical_selection.png b/kolourpaint/pics/action/hi32-action-tool_elliptical_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc8eef889f3c156e2a1f31ba64ec49504f9610e GIT binary patch literal 1855 zcmV-F2f+A=P)RCwC$ zmw8N+XB@}t%uE({I9=xU$F_gAnavEbEiDv~V?%MCfCnCcVC9&@gG0q*I2}$Mf~bg8 zKtzgKytLmEVb zf7G|Af3Ip#2619iqn+1pESzI)BePrXRKypm39bH4A((WOiw0rn` zhfEVMkqw_E7V|7M5f5#;rw*xe}QsjtHM2)Y1P&O@xDp z@IE3OSYEH%T~@2wSz4{ycDqvLS6r^#a;vn@JHKS$r<9x)SE4ga>=7|b=<3AZA=ZkV zol8i$LBwB3Eoy&C-5^pQ9*erDT)FvXsq$wc?sdJm&plJn!)CJPNVWBGS>J&t#g=_t zm^LQ0NPTcD>VmTVO`zrr`#d3@TR6bZDd=@`bn@mwM;nRh5T8snV#;xIt;>d1P@~XB zqejTbq9)>-Zkz_}!_x@AJ14gInQ5RzbfTH&yO%I7%}2 z;Ax3XNNm^K(6f?x9=@mDX-rcY(T$_JT467n zmmyykme6Gz7S~}Lp3u4A^yThFF-g)TXOg8$V^gGd=hEbMvB{nGk&z>BQ>vOrJ|8Fb zE`4h9?RTegU>gzk7nBZo#9xuygA|03JlTS%#O}oq;-e{Bk1EWxrFe`l?QxhjL1PYg9=R4*HoofGHCk#r6^0H(r zA(on_aKitx@_SvgZ}zSy-2h|5l5*OoQAi`cRarl9U_8_aG5Q(;0byc`U7;e&;25b5 z!mJYp>6Ki?T8KgnEl9e+3mt)iM0=h|5bQIznk%6i8FMKp1Y=VR#f3yZXEbUQmf+5S zM`(;>p7XY5hWGwf*014hR-oowk}X82L5#3w3VPRp4x(@t-GJxu9)RGTOB~jeYr8-K z2P$yku)?|WMbz**g4^+<4ZGgZ(Z7kVFOK4hzbdy+%(fe~*59hM9An0YdL{@SYi}9R-J+quA1KD4apA6Gyl~UDG(Y zAKe6DbQszkLoPb@Ql?hC6$_xg1D4vn4AT~;&4Rd&e&NO%s{5Ws6I48+f`M{@j8(K3 za*nV=9H~PMA>5z_F$NnJG_tnDU}+b3sKXo_U6*3%f6;z`MT}0d5BcBOhScs-jjekg_ zGR;J0*7iIX&ydX=-aqKs=xxjfv@CLtu$NUzPaFx0y5_iMU8}W??LhdcbD6a*>Ur4a zz4WGZHjPoHzri#zF_$g0b4m-1%=W|bs-s8VgOA2uBo$syXPP|Fe+C=dSKbw4OO35H zaYTTvw(g}2mbomiNbasHypR)d(fXoffm71RRvugW*LE<$VFQN_?crlL?qw9q-_I#g td|+U}`WS~599nRA!6D|&+37EAyD002ovPDHLkV1k)Jf^7f* literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi32-action-tool_eraser.png b/kolourpaint/pics/action/hi32-action-tool_eraser.png new file mode 100644 index 0000000000000000000000000000000000000000..dab639103c81416c1cafe795961be58d0e2c1cb7 GIT binary patch literal 1939 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi32-action-tool_free_form_selection.png b/kolourpaint/pics/action/hi32-action-tool_free_form_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..854692317f4e4fbfe6fbf93b94171260e7ba9ec4 GIT binary patch literal 1821 zcmV+&2jcjNP)dq4;-fx6vw#aP^yRyM3iex1Ucmr z6c1EHkxMFq97?r7xo;?k+)!vs5uNV$lfDMYjwoG|C;9q)@B2N^^Sj<}$*Whd-Wg0b zy#e5^y4C~q0J;HrfMxIUuJ<8d33v{8m2%-$BH#N&4qnfL?f)&{1`^R+{n@HoNm^&` zbllmvi#diyCSxiA#s8N8Bj)tKqqR#J+R~{EYHU}W3Donmb9zw`{k0m{ri>l3zNpmyl=P8?#E!88y>4^|$zex!IM$;%Y77 z=&sH+Si63@qO$Q;|M1L({^8k_-ocr~o`Gj^=o-i)fjpdq99*9Q*Z$zz7hE3&a&KYX zBtPLoVGxtec?lw(Ie8pUEq*lRfMFL@OxWj_PwdGq8MRHjHDpHXn`FIq&!$Q-^;ldZ zc;oO<{~91q?j4v(0OGi={+TFJ<^L3NQKS5@uuksvNG$UxubOm=O3FKV`KH*rL@0GB z7D}B36;j6{q154e{;)+zg2Y(IVAJh4sbz6eh=5|eVPS12=^2=fpWoZ<5+%A0nO|rm z}8*aTr$RSdN z*rG(h8N3N;n8J|%oof>Eg|3lYC{}pDwI=|C$lUTDOzb(IJ-R(2?ditIi%(6WFAZ#3 zXJVdB$e~D!xLoe1NKORUk%z+So;Xryw{t6FciB0Lc3RrjV#uqS)t-+-j&t_^QaxjDG);+@eQ(P=e5<8)MUl(1N=&j>jYz#szR zldoURDXfY$GvD3Fg!Uu%1H+P%vCxnVT@%MQ?~PjrC-fTy#rLg8;)LrXTaffh-q=q! z1Y?$1`8mboyNx%Q2ZHlcnwmT^hTjtKAwYseNom<>MjN+u5n=+pHHIS<)lyF^G|Y46 z-7zb_m~KPAv%S32=ZB2Z@dXI`)x0rt6osVN`$dnW4(T^U{B!Bz75{n|0m)aP;i<>U zL{hdko8yPWgu$lyFet1sd@zh&WEHgXGYW<+JwiHG?+@tK^^fjdi+N7GF=C3njXjRU z(0cawE2WO5BAKh3*U3w;|4%^9d!L3(iZU>>aJ97e>7mU$n%GW$ZEwhvGY7X~xU-7J zZLs*j@vlgX=}Em(9E>wT6~(MY9mlJ<+^?CG&JdexC_EC@Y>35%&XFAR4BC$rnY!!S;Mk0CgXZ^`&YNpiNQc#Fc}4 zNDvkrRylSCwkVS00Tq{c?W$^>REs8GM(Pn@K?H2Kbhxexy)%wu98x$f%Y>4Ms`@c& zDBLwLaK6EP>Jvcg2zfY`Vj$o{U-q)Jlw61t7V1SjWnMNYS>?xN=)8KK#fqw{Z|G zARb~+9CuAk&5sr>0A9Ep;K*ciquDyV3Kp$5M93_6AMlSpUuHOucpQk2XlZFJBm31M zpoS1E0FG5N)tcSV;AU literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi32-action-tool_line.png b/kolourpaint/pics/action/hi32-action-tool_line.png new file mode 100644 index 0000000000000000000000000000000000000000..4dfcbdcc51e942bc16b15133c3066ac8511d0150 GIT binary patch literal 602 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy%*9TgAsieWw;%dH0CFWuTq81h5 zGfNY5OH&IgQwwV|OKWp08*^)03u`+|8+$8T2P-=V8+#`k2WMMH7kejH2NyR-R}UvQ zPiJ>87Y}bYFCVahP@{n6n3x02u>zW7hH8$rog>g3TSsR*Cl`BXH%AwDCpQnEA+DZ2 zZeG3~KK`D5fk2}mP9n}6SCBcbKyy4@JW$M;qq%Gb(AQ=qL4Lsu(J`@c@d*iuiAl-H zDJiL`X=&-{8JU?`Sy|aRe~`dmWDvJ%(nFvLTRmMILn?07o^cmyb`Wq5Y&1M)X?WxN zeo3BlmhDBnN!nGv=UX|PoOz=nRl!IwfGKTVx|Gl7bq8B)0~5~7b7~OW-OAwnjfaWn z#&m%N=l3-+*nTr*d9f*9!J)mDoAK5gYmN&^_njNmels$e+~^lraIUV2!StIk3&%}; z0fnB=b*&7c@A3r{9{l5D?E2ThF!j&=S|Sx izfQ~t)a+mUX1;$Sd|A0&_idmD7(8A5T-G@yGywovzS8Fa literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi32-action-tool_pen.png b/kolourpaint/pics/action/hi32-action-tool_pen.png new file mode 100644 index 0000000000000000000000000000000000000000..ba35f90067aeafe9ef269b91db5475a5ec6de747 GIT binary patch literal 1192 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 QDgXcg07*qoM6N<$g4kDiwg3PC literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi32-action-tool_polyline.png b/kolourpaint/pics/action/hi32-action-tool_polyline.png new file mode 100644 index 0000000000000000000000000000000000000000..dd4ec9be5f25af962da7fb53a9eac4994d4b624a GIT binary patch literal 1061 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 QTL1t607*qoM6N<$g8mG$Y5)KL literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi32-action-tool_rounded_rectangle.png b/kolourpaint/pics/action/hi32-action-tool_rounded_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..2937e29702ceb84ea82362822b8c9e853b417f21 GIT binary patch literal 1514 zcmVRCwC$ zm)TQOR~*Ngj`gvBL*F`e+G#CYup$VwP|!-L(h4FVb*W(y0Y!mA6qmrbA)5&Z24p88 z3xN;=cKL}%=S58s)bn|tr)_c`aDbI)~ITU+~} z`B=ak5AJmH*aqSSf(9uE;erUZ81Bn!(J$|{ZL}OOz|} zDfx(=lKeZ12+#(0iBq zXzZn)yt}Pqt{z_hKvrtTom^W1LZK)iATVSV9=wVzFam4!maNABe1o-t3#~i)^@HvG zhJJd_QXjQj*VEFe>u&CN+I1o!S8kI?eKJ9K~EVAn9i_B{B9Mfx0@DgNxDY*ML)#*= zmiCm9Uq0J-EJJ=T{EWCT@G!1ry#SAX7x2_uV1qAQP;6p7?r7Zu;2W&_mNr-unL3+w zS>pmNw`8XFc$ORo!AQbR594eFxOtdekcDC^)?FO^M(b`}FSLeWeEmE%r%+uJmnlV{ zZw$h<7ce1luzqY3zO6MhF0I#6GHF)k5SzH7y<1%IUPGjU{Nmms2Q}d=P&)-lIE&{38 zr0P!lLIhq!4`AcW`Fu5)UIQhy+7@t=B4^ z;WYK((b+=#QXC!;oeCQPL7~e91hvy}IKHtLq@~a}`Py^^1n1mUKZMY{GEKwHirJ<+ zggN3Jz}Vxz-)MgsvAX)c_V)d81s>QFoWgZ>oMCszUsjZ-5e_<&@=#*Qc@FNLx-g^VB z$fopAWFj+UfBN?S{4V%DFzJ9R%3u4wx8*+m!G^emyx0%=Ve;|Y@PAprzoM>6rDR+( QH~;_u07*qoM6N<$g5BHK761SM literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi32-action-tool_spraycan.png b/kolourpaint/pics/action/hi32-action-tool_spraycan.png new file mode 100644 index 0000000000000000000000000000000000000000..215be802b043f40809c4cefa7884aec80d917842 GIT binary patch literal 1912 zcmV-;2Z#8HP)zjp&h_>EzC60N;v}@| zTEctv=)C7#$2!a6D8z)Y@b62TU{H^Evt(unH`@sD>eu*25>jrrH?e{)2 zUoM_*%jM+Z;bHXj-lQ89w|nQ#`{s@vdrf$rzt(lV%L4;nvi~mt$B)1HlB(jnQ&Urb z5NK~-ysd5XV|ulZ8Yg3DYtOV8Z9ql5U3Y zd$25rvf(%`Sc_OBqWpAK0e0-zuI93@pXL^WXj2pm3uRo)=P^D$P8b{9J>`UQT?cJ# z%?}&vs8-EIbAkX|)igDeNJNjU3P7pU zdrP6v^<333Mb)SxqNr$6qv-7HMy1j)=Mex5mLKd2(bbjSa^S$>&;4%z6%n_L)+daL ztva@cxw&}^4Gps*Ma<9Zb@RsgXR{f|vH~Fl2$IR=nqS_(|L4+;1NeBbR9w5=GHeha zG^&@nZcNqJIy!Q!hymAi5sO8`vAa8)Bh0sM44@#k!o`Nb4J6n0;X3Y0bB?v0x3;#R zP?%e+h%8HB>#`$|>gsA8eDcW`;@1a|7p?GcF9DE<7S4(Qa4a7*q;bPs(=;TLaeVaA z1$IWIeiRPn@?8}C7T6K-R3>2#esX|!g+%k6aG+9Hll=RsOe$d};u@Nonh=dPamOiS zGHFaqX@z8<{w@H+ae~K_BLDeWU2ZnLCYHMjvrln37ieAS07NjP_*7m;WZiG%}HRe1yv3vG=ICtO6X&tDQdZZZ>Zee& zJ1|k%fKwN4$JxnG!7yv>sq2+`XV~VMGv{A;>Z$LXQZS19z|Mk#A^2DaiDl2E%xZGp)(7asE%?Z)9iZgj$OfM=iG6O>BTNBB|BFYShU zzSx%Ka&>NKXzU=(9|jQA06dIP?-QV#OPAoo#MC2q)H$kY33R{ z+=U3v6XYz{EIt9Q5V~%CWpvbjUenaC0)E6W981^TGrH~_IePTK@2>pE~? zsawDnrO_CGPjh}5Dk~8XMj%6o(|l`j{Up8~yoHa6M;|>PjvUz^;2I$zzy};VsXO3d zlJgn3xD7rWY5_Jr8nWbAE7o7A{N>>;gLjWsxgam z-bZmnn(Hc7DI^MbJUBzs-86a~3fc&9kx=7cWteBUp5pj#n$L0kkWjN&5v%j>_*Eg0 y$GtSj0W=oa#?j(@h9}DgFbBHU93fs5Dk!2yrIPofNgvXSmWoou2d(u9dXb{|I#dvP^5E5jcv6d^ zh_t1x)fih11@Yvg6$BM;DnU{D*o@yKJ7%3GyIVB$;-owO%+BBc`~JsFCWJAjEM%j~ z0yjso4A**mOBKdCFmy>vBIM)G$CJZFNx^21uL5=_FkE4w+HETZZeMUit=nrmS^zIo z6asL|w#GC9o#smAmhRC_gT_8>xM8a9w6X#1hm1vWedEAf=-%ir;Q;VHm# zvLFF(Al$4FZpni z`$8hI+Tn0C*oV*gU`c>d+6MLV40MilH{?g7707m*;Bsx5*8tY{32NU-eB0998DyxV!$$8o(l=3>JF6NGT;C> zM}jJ(Hai8NSk%G)B%yRdEomMDmhGh+Q2p>}=SpI`@Q*PaQeozk+k=rC;ASYq73_JT-uKdmu~13CS4OU_T&Ix_J?5Ch`MOifJV=qyvrHxmD== zh2dNxj0BMXt>VHVNNDNw`S^puONG1%*2UI=#-yj>aFph1J(m8eEi;jpj!XmofnyuYv%{vb+QDKPlGjs#Fu)y;MPb0WPs{ zq|@mh6k!kEmjiqf^V5orrfJ$`RaLj)|AI*DEVk|Sr-}G0aVd2=orf_zfvRyqXE{7W zsyI!y6PMja%-=JnUrOt@G?#$d+L6KjHzt^7_3-&geB`Tm=MUA@0Q3VO^@E4$A}6zg zy)4QPlRzX|2{8e*Dl-z014a9B>I+3GP~k@avlw6kQxHMN-p$O+T>Jm)_!q5UN~Yb! Raa#ZY002ovPDHLkV1h|<-9P{U literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi48-action-tool_brush.png b/kolourpaint/pics/action/hi48-action-tool_brush.png new file mode 100644 index 0000000000000000000000000000000000000000..71c5a8e5026a78bae279231514259fe6e7c130d2 GIT binary patch literal 2662 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 UN$0~ZIsgCw07*qoM6N<$f(Nhfp8x;= literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi48-action-tool_color_eraser.png b/kolourpaint/pics/action/hi48-action-tool_color_eraser.png new file mode 100644 index 0000000000000000000000000000000000000000..118fcfe9c9c0ef09a39b602b07d5ca7fa841b80e GIT binary patch literal 4381 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 XG2cTF>mUck00000NkvXXu0mjfKCMkc literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi48-action-tool_color_picker.png b/kolourpaint/pics/action/hi48-action-tool_color_picker.png new file mode 100644 index 0000000000000000000000000000000000000000..a630c5a95f04213d7a1d3e287b50d6df60acb90e GIT binary patch literal 2422 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi48-action-tool_curve.png b/kolourpaint/pics/action/hi48-action-tool_curve.png new file mode 100644 index 0000000000000000000000000000000000000000..98e220fa2f892a1305ce5a1fd188d5d512b57916 GIT binary patch literal 1310 zcmV+(1>yRMP)_!}m3>xtG$__F>-(Nl$v- zxvy{Uyw5r3-uv!uiJ6VtSp>8mKs|tZ0JpLDjVk2X)vfcHIY>kyoCcvy(BGgZq0!0l z^nlv|R5XpQYFXoh*^NJckI1;IjJpwojK0ZCYSQ}v6#O!ngU}WtD(A|uFV8@GvYFJ7 z=K%n|3uXX%XGQQzF5$b*Y-TL$*&qh|(=dlh!2b!|3*7)+09}vEUl#>*FJk(_>U!=D zVg>&w%umqy8azWkgT95qdf8BN7CPL#Ch!65+kh{8miT1K52FeZ*8%{%fZ10B zKf%m9D{+RI=LF1;HoWzL(q=a+&gLr6n(e-`RI>g+68GhngQY?L?_7xyw3yuxiwbRIc z+KE#}H?rXi-6&M600{l_N!T9fcIYy_FAE`Fok)*OSL<|jpluQ0f7iOv3=!RzPL5oy z)>m+CM~@(J09m*nxcP+G2h(8wT%Z+$eq00(p_e4{`-TGG5blJNEz6SaW1B~1##IymY=<=`zVY6K5ol(HKI(-a7W8>?rh67b53^6<_h z!i;ks_n%Q-$Y(GI9JsnP7d+&OK6kH&^_eykv` z;On1_+DN(pZ|Ba@KsIM*R|+BS27I9^XO70Axti|_NtPKHlho4mv0x~?(7`MewBc>T zCtcat8Xp_As(VVW0tmJ^n9*7keEx9U&7HY#6nq}$WhVguUY{+;-3x%ZACHw<6`pAe zM;29j``XT~b|PA=;K^j>lxqQ`Mvt9E6dA1w7RJ_!*ZU-PXz);uWA?e|_U7(a7I%Rk zP_MUlbUsi)_E(Y+y?Xay$p<{xou)7G{26Uk`8eAB(oXh1d ze&>1s$z$;gc=#v!!i)sH5Q?nZHAm*>nWp?+2wJXpAIbaJ(y-_zL6zRGLDf|^U z3$(S3$p%{)J&5zuIDZK3un^)k1h_px<`@ZUB#+SG+J&hHkQu zzfE&?d15$v-qT-bMiWO8EV&!aT&3{LoRv~GD|nB8Vj4-rMgY|YvO!RC8F&WxepDc7<21E>d355QZw4tv{a UCrMGwjQ{`u07*qoM6N<$g3yX^82|tP literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi48-action-tool_ellipse.png b/kolourpaint/pics/action/hi48-action-tool_ellipse.png new file mode 100644 index 0000000000000000000000000000000000000000..e23579f4e713a87794eff0f05118bddd89eae951 GIT binary patch literal 2590 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi48-action-tool_free_form_selection.png b/kolourpaint/pics/action/hi48-action-tool_free_form_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..7027d2dba2d7c82b40a1b82b2dca0fb780b14096 GIT binary patch literal 2940 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 N002ovPDHLkV1iB}dT9Uv literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi48-action-tool_polyline.png b/kolourpaint/pics/action/hi48-action-tool_polyline.png new file mode 100644 index 0000000000000000000000000000000000000000..135ba328b69c69b7e545d533da3f51d9777fe1a4 GIT binary patch literal 1394 zcmV-&1&#WNP)0AC<=cOl@v1>OhV2i}j!SdJehk+{>{b^)MoAirqfKPL&@XMsPdgD2v6 z%Ss5mtbrGKP5zutKm$))(q;j`?;w}z!^ayCuup2??UfODPYWL{CRiYjfCfHhd*Zl# z0>HC(Z@E8I=lR04LD)7WXz;;7z z5n%Lp*IBST82z!3fq3xW?#L1Y(wmkpLq;B;n&C zZsH2(_qS94_^dMW3*_GNlXYCehYbZ}06tZQ_boRm+V@)~z|`3c?4Q-U4S7pklLBRU znZw2cw)dojFUCtQa=x`C0zgkGTje?04b=h_cv}Pkm^xv= z3(7vfN2zVbE47WefDBXU83}JEQofT&=MlQSDFML0u7+o{PcvF;Q~{;7S(X6MYb`I8 zE5jGV@sVWO64DY+Cj1W0&ECBp|1%81%pefD{l;LGwZ{}&wh>uHnFV!#d2Yoj; zEWp(1LExfN9=sh;oE?zx(H02A9q%Rt&X?dxdO*m$(y##V?<(-;xf>e;YMX4Nrk+;? z%)_*KRl7sIBMSrUh8c8@zP?Uq$%LU@@GJH6RfG zq88qo<{h;wSis3j_!yy`5dF=DY4fIpPxcoxN7e4DnfKFj+LYjH1ZL_j+-|@xRKbh9V^Gf@tgh$D8wQKD z3-H};s{d2K7X`QuK0Wx*KlJUY_f6TP0)LV87zs#tw=n^>#4uasB;e=dRv7^QE9VpB zKJi)cc6BGCzNe4vk?d!oE>^({krxf>_)U!dM`d`D6gk21^JJ^?gzW}=l==OWD<4vN z_&moS()WlcwQEWVpwFngO}LVI_Vw!w-lV>&hOe&Y$t(|`HB^{G)HfgVbj^$MAP_z? z_#>Ygq5l@`8<`&77CCEJ!{_S4C;qGHf0Kn-0Q@{V&JnI{Z|K`;4ZN)9%?0%EegOOe z(8uX}oW8Y2U$5)o%>@|!oC!SbY5K>0T A7XSbN literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi48-action-tool_rect_selection.png b/kolourpaint/pics/action/hi48-action-tool_rect_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..fe228d09dbf3644bcfce094fd3fc4e1b17dbb819 GIT binary patch literal 3509 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi48-action-tool_rounded_rectangle.png b/kolourpaint/pics/action/hi48-action-tool_rounded_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..3787d0208193b2f6900edabe64cd898f71ab2d2b GIT binary patch literal 2237 zcmV;u2txOXP)~{Ul;JHsrm0v{(`a(<&#~?KDZ`ad$$V!;NPRTqokvVQPzwc@vpz-4!`{_ z1^fxc6D1-gBcDOGwp)PksgEK%qvopSkn-<9hj zi~26Q-;A(kd&KdL>rt;)F7BSMsP3LWQ{9fQ#~F+3dRClVkF7YlY{b#!nA(xL5r_47 zs2!X=)b`FE%MK_MTl=5)n^~;t`_0p50(k&RP#9~69P4FoWIN&<1bIVp>BX5MBoDm9KpBs zDSKLaHsBjO7dn|8iVg-#!K!OlwC9zwyN;domFe95)eL%39AZ6>@;7_|RVtOL$C2v( z8gD&4vtWKoyfQ5it-P!C;jOGUbuDx?Y{0V^2)wRM(O%uEXsvFZZ>emWZw`H!{*pk{ z-VKVa8G)#2Yj2-RJ)35s+WO{xG=Ma>DmI>`z+2}DS9Z&Kk6!C2cl;Dz%TX-<76O-`AlI<0WF{OdcML`$1f_*M#s0&1`ilG6$#Tq9er0eC&ui|Y`)9_yJe<|@Kt zh1s89XUBS88jC*U5m|*Rpau#U#XhKE_D}D&b%5Wg^=HU>R*9tQVf<7M29Cw~%ja}l z0i#fWX3NksDCXg<1H2*YT?VW-cN?(YqG;WY^`!jS())43?9<aSQ9v9^a>m@-IE)rJlGv7K4W{ zN&#D0NAP;B!v+k$n!=nN^8F-D7ux6r{Fn8C?OTWb{wYIL;Cq{sxCMam3g~$U>)B|P zyz@ku_1P6|Uw^Oywy@rTw+{V%6Sc{X0gPF|cC5oZjux66tiO#NuV8k}Ni=IgyswSN zp1M9BXRrW$)?ovNS%*~;E?{h63>H>nSSQZjnuzxe zapnYmhJocCdfiyYF>q)N|9jjd4EmeL8^E`t1esxIhPxLdq~DL5%6{;4D(4ITm{y(b zwDy7KPA!-%%P5+w$Sjdm!dB%;N=J(+?qJK~ zGru<7<8?DzFTamX2(}W^w6wOh}K^q5Uz^%wX=zTw6Mmg<_ZqG0Hm5yL_g5&1POnH|T6$UKy zJk~aNcxV~ey+WUZ_4QUMmQ-Tk$+;s4kIUV4Yqfu8oYY>O6ZPbP|7{ z3n5VsslCtM4BFN!CM#;tA1x@yoA4LF4|B)AhuxG!%3-yk7aT{u&W!|I`{B(_cqLMjO{Ig|`00000 LNkvXXu0mjfo83lw literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hi48-action-tool_spraycan.png b/kolourpaint/pics/action/hi48-action-tool_spraycan.png new file mode 100644 index 0000000000000000000000000000000000000000..51b397b746a5d1e538c3afeb2f647cc9e728a3c5 GIT binary patch literal 3253 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

fsa{o*q9Izj!SEe)+Q9FCT8--CvC6FD^bU zd4Kt;if%^oo_it8%u}FKqLZn_$-~E+)%k~$n}=V1Qgs^OzN!QOr_-^=l0UB0*@x>% zhVLCrKl*9!lk|7ff13&$4ad2@nrbfF&2n}6`@j7DbY1!c+U4rL-`cRPh&oXp~so^6&NG@-3_KRv`W zYF)lKp87}0=)d1AuU@}dy*QR=&_8#h_4ikot2euN)9LcV-R=GAyNftc0m`>;Z?3b@z`6a-w zJGz}#!qw{I8IB=M`1rBz?W(*Jf75M5Tj8t$K#{Z=k)$^`OMy3Owb7Tcc0Je ze)>GCd;I*#)28>|oNiCo56Yqc!5w|*l=k|V}g-CAth4SSA)Pok;Kk;1oFcQ9=d>Hu!ad7Nq*UN{8ufCT>{=N4y zQr6(0z1=5#&Alw~r_bTb>vxy;=ijbw-@mz9EjP`-o{RqV=4v(h=64Uv`#+cW{I8q8 zK0KECfG>`j8#4jmhJKtVLSxVgKbEDvWha{01l(s^wST5k`@+h=N@T}iu_!a>6P zW+gc}{&4U3*`zuDgje?i3FmFp{sWtQ?I#QJf)r=^`gulFwx+^gG{ z*XI}Cjvvno-SP5Zp<4+2S+P?tfescsg&=>rjD62r-9BHy>2ojv89|@9`NYM+1Y`t1 z^o_v&Fu5_hpSe?D4SwDhwkh?OX%Zi%T+Q1T$8XP9_g5c&1r0SlU~!^N0!(FjLJMoE zkeT&;D&eFUixX>Z0Ei33MnVYRZ0(VR(UL55Yma1YNSGO0dn5(4R3O;eBRw0LsMKmb zpsYLDh|h~_2NgHOa-mI(%&7D{nJA(GE}$reEjXH4$aIXLQKHDUvRKL+L~JRm)B=@Z zTUo8kNK~J=k7#P?VFy-rH^hEk+c>DUgk4(_RgR6eMg*m8Ama~fE8I-QG`F-3Xqsl~ zTiVw0^gz=sZ3P>cQIx0o3c(=pV48OMARs1D!90>nRB0BhruE&MIa_qGQ5q_!FSt=g zif;?6)G{odSZ2bS8OCj4Bb%fW$(FF}($sbS){jwE4!K&MdQ|VLJ?D({Lx=4g=DhAUwa8qJBU{Jvc4v znkNVFy5?yD5Yv}nCxMNk@I8t$H>Y&4?KP^+P$g-5%aX>ZB)Yvut(i-5JStj4KEM7l zMR#+9+rn=5!3KChVaXL(7ugUL76d_+R*zd+E~n-zDmqBki*Jthzn{4<|tiNY@T8Y zLvB(+;`)x-K-g(s^%C-T3%ZA1&~p;<2iCD4GUOVjRD4xN%dw8RfeHc4&Q?IU2i{bp zfL&R~DkhLd7C>98m<*dns&DA4-LggZmc8%JLDMJ?to2UD^0Sg795N=GAPyfEY#tSp z$ulcBCnU0bc9$!yrKTvrU|VZ%7$EkEs=H;2?v@=|>%<%p4{IiNGh3ez9LR?Z98B)A zphkeoI)`H#q=j|m)T+$>+Sv<(O(`@x%I(H4=C z+uB+*hDem{4a^$2tJ`)UpcVqDL)%L9*4s|~+vdfudME4hZ&%Crzt~p!{ngEM2YfP_ z4>;a`=)?CPTE8o1;onnYerpVYcG`G-e|35C^5yN+{_6hS^2yxZ3s7w_)xtJ?MX$K~Xn`X9JcXgXi^=3GaR5tLy7OR41Q4N;#beTA+Sz&ZB;;&$9n|y|eUg-`%~vyd*^4jxYz4CFi%cvxbb^06;%H3a;tOo724r^h zyCe3Ntd`iR2zwUOV2YfWaGHY`V^+f|XG91PRoj{w2k3$tq9qv=17-$W@brb4B92%a z2RPe88aki>R&8nEs^JT3A{wM}#MFX-+{)R9qy!!@R{RXk1#&Y&ufB`2iE{V-f$W~e z2+S*hdP@vID~3u=Q@L!Bu5L$vJR)txZW2`zA;e^&7!5rUa6!;At5#8KrXm;%^70bE zF;jyG?UW*DrV;{GHXDg2)tO@ThK#&mX)fd*N62NF$#fyq5LhSe5E~#6OiUV!B21$y znvr6%5N(MfXhpfk4XH&XP*esDOcY#*HP>|WqT1M9l@}5X0wkoUH)N$rf0@1|Bf?CR zld>qII_pdWR0nl7_CZBF0g6Vj=9(Ik4@zojv$mu>X)D>-P(X`j(~MxRkwto74r-tb zQZ$JTVNi8~)M^2cjbzY3Rc5m9(hMR1LUYF%E+R@9rI zlXGu@9D)Zm(Lh|NGz?-=mDT;anDFKUe8afOb7D-vuGm(1)%0{XL zLv@r4JQ8jKZbWqjab!+sR>lh`MHiec#iXzE={P!?N>P`P#G(}=FsxtesWqxWmB9=a ztPRB6vS`$m?P&{&M(IjWlp3U~?1afKt8!3W#u?#T6ilV3*Kt}>ilRbG+u@H?jYn<4W zfEelq!99(wSk|;tCEP{P42p$hnl4J_P)#4T1EiGJ9tu^Yn5K6{Tc{9t7Gp+cUr@`? zkeK@hRW+R`gtLa#$Q3-1YKJrMWZb(Kes^^Ii|uzUUQJeF+N5umzpiheyrby~{WJLe zyX)oI@^8y&JHMP>#<;)zcDZ?%c>EmR_Ke1D0NEPXEyv^{&R)J-tv+S{dwX?rHW`nb z%QcGYWm|$8f-WII4aFoeNwM2uMGqV_&S!g#N252~phqAM0Hy%pFRBQrj){TdnSawFL=5@&=8&C}6 zAqbLOB~dJK-W*x6Ux-)%8(oH(2y(JvF$5)Hrp(UNsX4_=O)X5i@d#t*VLSu`HM{W$ zB!;pGqKru0cnAS8Hq(L!HkFTU_|lRVkkQdi$Q26 z+Nq?b4r;*$+P6#hj2yN>6A+`yhN-d+8W^Tlwt)!TV5T}@DT-YB&oo_~iV~nF4C6r{ zs8ZC`0c=J+&C=cVopup74gt1tsweB&|OPwTPlcGFPBH=0c9%xjYlXE7R|b{O|Xmyv!+u~>iK7kZXNnHGmW*=OprlIB8o9`C=G@r+8HrW zb{48IpT85i@`AdtN})js-II!7 z8EDn6O)`wEmYxaE84nj`lrkQ`vd(y*gvW|PF-8Zj4@%v2rJh0wDb z4-eX8JODJIXFM`;o$&w-Chf)p(GUkkv@|cuEW|ofU`^{Z3E2l_hvX=lH8E=%!jMHn z1lJi4kYLVkJUnxg@gNM>ZZXi9U`>OCIu$~ksiY!vHy%t#GN`i|Xi?$FO~wNV0=w}L zjdjMul!UtRfXG~DJk&yLQm3~SK2uKs(r!G2eUtGZfaug5e4X(CL$h(`oGR#Jjip&| z6irH7XFP=2)B47!0qcwh3oue|K~#eWRf~`uWYPy+&uqKM$hGZlA}P6LBu&{SdZ zqRtNMj0dKGNH-n;fIg^)3ujSwQNuygfvi*0aDlZ+F$*s$9*%2jDVm*TpxdBmnkL)wqjun z9A!L=&8#zbt}MtLGS{`8JcG-4P`HZCIg|t{hDp?idf~vr={fQORi-4+ja*qL9f!#aKx|ilw4M z6hjcFDrfEnD8qONQ)1RcI|-SU$p|1Js3j2|lu`^OL$Wd+q-Nm5c!Y@erKf;Qsuhkj zk`u~!2#AD~bugn;?#9D?V?2=A#JlmZq<{)>=^8BEcsMIsXN1Aljfe8acmS9iRY^1+ z#-nOU5NbCb5*yeZtg56jtjP>ZfbQDxg04~-zz3Y&qtF&-#pHZmTpv+=MV+F@io zBn)w;#uG2fj>I}sNpmhLl*lpkB6Pq`Yu0j2891b91Y+8tks>$@0@}uSuz4zUi=oT} zGj$P+PCXIVRFH6FJS;cHLxU`^sJL#7he@DXHy%M@G>nI2C`iwEkZp{IpeT=whjaK$ zjST_2@rd>I&SfXjI(2YI95lgwwirws;}J-S+Ns&bc#vl}_l+@iV>~d7!>epfV920? zlBC^uum{9UC6Ku&ihJ@*lS8N|VQh>?00?#JE*s+k0!Yw}2RmR4YRrmOR1i1DBhXm1 zsHM8VOoQC0sG>T~G*OkcQ&x1J?FbSGbmNh!fJLz-AOm*e5uj^}kscV*jfbEh6{Vtz zRAobQV?3Ob*Gk&Pcm!e)b*i{A9>xK}MMGs{Jb;AfJd?w0JkZTqyYUd6jfXmc=0czW z;A}icD18QiHMM3y7;}9KA|x!l z;ArRyW#gr}C?qb}T4Eqa%@mjhM{m(^_@;_w=4M#FLls~)=fLTe`K_Y?-Xo^FTi+vq zLy_m`Dz4h|^+u)yMa%t%saJ#tPOvB{4al6dw-6EvbKDn9ii+i_8bOZ_^p&fH2oa8X zKj9R^YJo8$aZqq3m1+TzO?}?!xCE@7F2&q=Q1XCrW~pv0V-MyYr21L75XQV;D{<<0 zl3+nnlxqw@*eR%c3`!9!(%pdzo6VlV(cF4sLtTRh6|1=~f01grPJ$?VNLE>f5VqO3 zxLI|2G*u>O z=x~-v%!K7E9jmq3g+_3Al|>OCFIo{d!>Amqx}!?@-R_bIyn9EdxD$!j6!N&vvI|X;|DXu{CWMaB`?IP;T>ce}gXqYURnz2)A(cN~RH*NO( z>$I+qr`IIS{^2XQfL-}r^ojgfCffs`t|ehG$XU`bPw0{&0q`uT7-f{C!Ki@q_2C%Y zuMbB_rit!p_j&U3&s+ZQzPq^iZhw}`pFUo*MxlX|tkXBi(D?ybrjk=pOR|Zww*V!@ zV!=ouJw1zp9u@Y~gOt{HiHy*viYJCy%siD~QOQycdrm`OHKY$Bu- zy`&L1bmkO3n6p3@3t>qZ`8govv}%)+5V`v*cMhrXq=2){kKEX?MlO-8QMI9k5lcVs zGz*vY`vtF$G1xw@ktWAGJv!xKmam)nbaIYzva+AmmWYK+L!pq{>i@nq+WL z(LmPHSLz%fvszR}N8dYRyNmmio19ET3+7)xF8JNQTKjWZ@IQRbj4@H!H0*3ohC3rpG-%O6w1)AN79mTT#sxM?vI|LCr>Ib?{#Le z=!0ir>34W0A-Nzc`B~>UgrdMwG?7!IYC{B6i{6NNqW#?@b9AV3PJ-(5+ld+zFNz7s z-0$rIO|(YIk`%so5h#d_Tz(ok zOVRqy5jb8$h-4kJ5-BLY5N;W4!4uun?(_7QpS}JM(`U~dCi&!9-kvDU}j? zup)*C*!bu0=r>+22_S2E_o5-`{dEg6Rub&z=m2##jX4B~#?FkvQ!gc&hV{Oq0=5cG6p<&G zrLRw)fT=c+I%V|dD5BzCy%0Ov{1irzVCuUJJ%O_qGtf+1Xe03;wayd)wa$hN8Rpt9 z2+E6WH8t#1jG0xk6aug?-Z$URx;DaoN-{_zc5{8fDCY2U$p$_AUw7x0<3c1elMW`Z7RN(XL~uEQp8YKh_@fS|{YP_F_biGAL@J zV157nl53Ty5pUoAF!Hv{wT++6S0}BDk(@%2YL%QqfQ3W2irzRD?VaJTS8JX`i}&{h zXNDVGwPM#lBA|VwWMkXj8Et}=8=S*6bAb~r`b6&xj|As$;J9U-CH@df>!am5i%Y;Xp2>Xyi*sS~|3JQ7^J zfg8z=CAkWtV!8Du-vWzHX`{5tUxs=#!sl30Q){Akh9`nkxxq0Y%cWDJ%IWfJLC10u zEfswbkwL=(SE14qy)!%#+}n@CapR0E z!TB3F`-FRA8JR?BV*44{*4svrgz{LH5rJ_Tbw0M1PV~<3NN{`uHyV#1R(FIIAhng7 zG*{%h$Mx{uIGIQ+S;azfL%uUS5?o#25RLY^>`Y4N_K)BxU1O{yL)jZqm=}G4DO}7hj|8W0;Ci*lAeZr$!DVsV z7?G%S$TOcu~)MIq-?d8gQwteSA!0Md=)?Mb?6RXZY{IeFJcHgM+%}e)f`@ z#=z)LsEOco=t^RP(+Z?4aD-4MdS`eZIG3)=uyM}A1^3U7pI`rC{`K(4ua{rqs~g%reEOVY&g{`w_AjY+mRZnZ48DOa~2+&Uxy@ZenF;^Lxz`uBhRa&hp_)%DH!)#a;0 zEO2?|JN$I#!|rac4UVhSGqxkG+BU7g+3EB&?l7q<)DrLIk@ zlk5MyJ~=&KUEVgVwgc21lcC)&7 z*I%zsE^p4Ru77!T_{+)d_4&X42qn{q_2@UghA&tE*gcPqxo{$C93dA$Mf>sa;KdzV zwd9kFb++Dg-6v5bLVRHB&BqS4k|<*Q3BB%U!D8;I!>wnt1)R+l&?uZX1Azi|$zgB; z&vS^z29?9CpN>P?g2Q9t5}Fn?Au+WOA*YW?jG~I-rUPB`C7jKaFvp^&8 zbUn?#4P(HjP#Mb<^xt7DDB0jy2u~L)Ji1ff(uBv06&~Fw?^y^BmnA&1BH~!hcbU4= zT#)Xjgl!``(vnJoFj1XSk#vg~s}7}*12BwL*9stZnK~NiIup9K|%zx>V-%_ z2a|`HXi_m@gP5AA7Ew2yL`0Tbk4h)!vrGzY!p zp2fmd7Q24nWv(B1vFitZHtPpo*!lr&(X%TrYXLD&*JjB@fdmK5=|EQ$fQV@n8V{qdKY3;-LyYt)C?Nw`b*MyH>D7m3yw^dx|R*E~-Q#qCS%C}cs z*fxrrQh3#M*xS`FSJxelA6FMwAGV?pP63{VLNdoKjh1t}%3Vy*2q~kX@)R;2Pu|VDlhdmY2wP59&^}MY z9t-Is*n67UiiNfxJEl{;cHEKPUilYK_u`_Ef!mH8p7;dQJ!x?L%+{`-tzMs?dW7Gt z9zXx`{H25c-RRN(u~jd|-=E3~gkZZdL{5R8Y77yA{6Z@j4I#UCKgdnKnZrwN`Wf;z zEv2j|im*S;zF9Pqt*Lt0cSG>%%&Byg%SE>0YTygoQXfPfIx=N2FiPD zcs8UVQ*p|q32Ee6#1&*hT1Ww41)7qEP{f#R18KQ?y3TxDb6vPCZQpZwQ*<}NDx>LOcWJD#6kU(smG*4E0G-+(bz&xg; zNr@05osMr5#gsj9LYjCXk+e-l>hq-C3o<+zby&#z5KZ9{~hBdtR{I6hCB;jU!|B^;!3qa`R(r?SI>65Z#EWfxH( z&8G~aa~r{MYPgn#!DPxH5fny7+rS|2RCX9ZQe`IwVE0Bl#svHVmn4dXm`$e$!U77S z6Veh&sxrfbG<7Lv8L)vgohNM+k$XzgjKG@M*kq=$e<;fr2XOYdVyYD&QL^^?6GBINj3=(HY45 z1pN^7_3jC@ zl#iDnW(i=(wQ09QnUdGMy)0k~e9zlcG1??So#$;PF8hS!F%VVPg$l3im(4B|0b}pJp%!xme^%_~qS=*_^^QKZheH*ffLsDD zo-$q>-=P+Wu|_Tw2(op5JWG=ChGk&jw;-1kG-x(sipWjyE4h~>g_J$*UJwy9bV_Se zCr6GoJGyrRtvg-jFr!Dz(us~qHkqJ$I`csjlu&_yuvjBZ+Y$#RghiAf6wIlSI~BkH z-9XrmqhLgUvxG4+cs!aglq@a*Ivo`Wim}Gps3=m9P1hr?RwU4D17ZFw6t?JnIg7xJ zE9?E{(o}jOu$i>|4#||XMB`^c+VT%zV1BEE@Fv4O!`LB*jw+b5qDz4=ZyRYD;8<7uB$l}JagG2m zmo?5I6(=TH=r~8Tq;YPBu7&@EK;v+iaVHfbgZbm%f{avm!abCsnXO5pR1O#NqtGb6 z|54~K{84CeS@>^4FMd-@-1cYH9(7htJoZP`w%kpWfc;6eN1arYp#4F$Ew^DMWPeWW zQRmboY=2B`%VTO1vDi~;SOUyZR>!E-OM&L#F723_By=^eYAgFp7uPV{R=u6sThktwYH>qwGwQ! z%YCGJoikcoqV=B*5ipD!1m6Qr)Y%n2e& zuvrdMnHB54M~IN>QI>GSh$SVVsmCtaViD(gwggq7vPI5l{Yg<2%w?tQ$Ax4?aZ=(Y)>!TSW!Z4yL77d2lloG*&qj{T$<{5yQHn&y!g4JYxA zE-93RvLJJ$2-vAPa!}eVT_g^yGjuIzmZS*Y=`1Os+O{Hr1(ViCeBj_u3ghlRE+*-+ zph=Qqiver_lr6yLr-*nCDqE1tPmwzw$DF;J3$v)PrLT^8LK%UO&rr6Yl{iK6;%CZ* zN6jd9{G&+7?rjAi5hT1G0TMKsv(yCz%_uT9%5DjiEhh|43EyOM3=;@o-eNtCa7bYZD~^{RpKP8%B3)n}Zdf6Sux>ZlM^cW7`w+ zOtfdjEyAL?wW7ol()M!2F=GR_3(x4FMBa5q2MQ9lIV-^=?$235IChtyFfiwWGKwhC zcAHTIPTbG)LQZgxGegW7&K8uqMv1xSY+KHVo&{$M%G9N#9q%bd5>?7LBv{rdb9B+| zeaYoy+f!2PKHJ_5%-VjxX0rMktqG>A)zwK9w3cMWU_lGYv!~?UhHXqGyebiuz;RuP zK^Zi&7+lbKB1-1nYc0(KYF#++XNFw=;*U0hnVxE}oGraKr^nxcZmUfkp`7KTaroi=y6Y zYxTW9xqa8Zw@-KdF9%jI07&o%vA7Z@9=tvfDuU?7BlPD3k;2r0FhWI3H~_>Ea&aT^ z7-LpQQ;oVZ2UM{Hnt*j>j!-F#ERmyn)Cv>3`_Q5h_IhajfYve#nV_1CP%2L79;12& zY}BIH13GH`&jUKV=TOhU$1I=!cW^kceslZrV)g3qs#D|n?Z=l;JVnen{&aSJaq;p8 z0E#!({x}|P!hlSb_|whp_0>OC-EHl2_vi74^V8dR-EA<1s|CF|dH?eI>etKDG5Eh% z=a*CTmu~F2zBupxdnxP1oSxjgyE;3&S>4{NV$?)nr4;PFM4s}BD4`r?m2-2XAIAE!Nz1JBQ{!)xb;i*+5oA977WM&T&A%kXx)5xeQWO^Y=m^*Z> zn_eI&;Ft+rrD16%)PYz)f>gWr12qDW5!S{(A!i$-QV7J5eJFCkM)kiQqf*dZKs&T; zhJ~gyG|`y5nyeU-r)5P+M1>E$urYu&k%wwzc9;i}lU58p&8yi)!AxcEP$~*6${j`+ ztkTFb2@mO>4hFKYN_VP|S7Y)bQtyR|lBQcV9ES{Y>N+vlB_X<&Cozji#Z+9gd&@ax z%$is~le4g}{+{_X`1;q2)yvgCSC?0(zv=(w<<;eCYE4=++vjPm>3*YT?8Pm8gS{}8 za1`Li3JP1(6BG*x^NL!4Y93G)M`CD};mqPvG0Q~kh0gBUSxZAhll#6G0?}3?JCZgv zQKCkxo7fB+CZwEIjZhN9u%TpZ0d;W3;-Km6c}4`4Hn^)IH{B&lAXF^OleuEVWGeM( zE)?JtO?L{C-mnBTZ@NoH?w?MQq>2HcN2j}1PfB1@BO?e~YmbUr}KSSyxf z0oF}6vkZU#y3-B16D{hj(0gn|g5FQ-0F+2QY7|nZby1guilretR}3^1yQj6VtKeNP zo2H6X)FVZ;Vj=*kby{W#Bg38>~xqu@v)wV%X2mFwC^(!#A`8i5(Lmo>;u`d&6r zjI1byyJW=(83ihaEUe89caF4dm?TQ}b(4vwH`F*K+S@=VaK%_GMJvYX09r98akT!u z2@`-S8qOiTy#_dCdz*07W`!9R8NH4jfqHw3WUzLphJ$3oge|aQ;cU!zn5a}P(2}vu z5)CD?#*j&dHH?M3J4p&NfaP3|eeXIJ2HB)zDWTm5jz~)qka^#qA+D%R$K;|=6(ZyW zZB$f8VC_c{VCu0z?A6eEOhg)*h-r0X5;HXFErU~`VJ?`~>Q-~hegsspWN$q$(fo^s zx^i$uNzA=H&+MX2i{{*VzKxopcsByFN8cjFAhC*qkULcjfQ5UED+z6^i(sVoG^h3Y z)Pl};mSw`GP#H>6Bxvn{G{S}n#a+vhNV_G#9K&10AHE|g+JoJLDxk~+9J>b|DSC{0p)hA0ml%bNGHs}PguNadkrCQNkLJnJqpA!Q zbJP&77_b@mozp;xwQeIv;yZ&Qa?Xkpx(QUwB`Bh{-3*m=a9D=-8M`q9pJO+sF^`Vs zgB#O&NDavTtf>~*oqEjEv-L6ra-Dh`n&#ANge0ObF*u_t3?nkIuR8+nu!MXM)-MY3XIh~gD%UQ?&qiUwte*LI2nsNP}9-JMzt z$=Lg+A+^KWK<;Aw(~!h&6{UV$>Dz;VjUnbV?OPre)-Wf+ZsM9__`BDgD?T7Nr)FTi z`|I5IQ&Cnh^@b({x8Pi)2?JKIjey9k12w@cgDR*lCU?QPL`#Sh4nQ)zK zMN%Rgj1hriTH$nHfP>f53oM14w2gh!3tCv!1Zo3?GBvnvV-T}>)c336!W^&`oMSYw zfDu|N@D$~t$N?MG|9U`2&26#jZ~&J6-7vD235FnUjRq!1=;}5_3-!jBCQ$qqE@Wb; zD2B3F*LDk0RB65738Az7^?DBd{WS%Dx84m6Qx!-DFk#~+loaY$#7lUO2sIga;$CTqHM%AXnVlM+8)qBu|tsq zHq<}X_R!q1wkyg(@QN|HpjXV)44aRg6m<#6P(VMo|Cei0WSJ&PXwA9Feke-y#iw?Ahc*#)<(E zWzRZkiqH1X>pAp$*Yf|jcP%?|+(__Wd=t>7^BtM6zs8rnF0kIke!i8-2GpY|b*rJ) zJm7&20s<@T^o|@k_0aYFs=C`pz0GH;s4TQPq6Z6|BSV5QOcVi)!=663$ z_SF3Dek?A{?|vVyOY=K#F2ED>d&KZRAn>Ga1RmQWcd0p4Gf$b>j-{y&y!Li>wz{6URa68i@hW`l}Prnp;{`Rli)rqvCf~yXz6G17KT}B`X z=aXWkl|3s>lobBu#=)|gI{N=th=q(o=Q`(QKBnzg^+jave1=gjpW*J>1=6dsYLAd$ zI>mT|n3ABmFsCyI{_0DLj6FAg{#Ww_Zb?e-srl$tz8M#z@7EQ4@EcKaTuGD5;HIiS zDKKb}wvtphI=K?k&Pg9CT~`Uq*~?j8Nu?xwsB}}MSP@EydNq|k#?oz-e1X(U@meY| zf2ed*C0j6Krg$}#^h2eaD(R|1TJWo>-8Fb(8tjUKc+v>%Xu zaFamF^3&bx1r?lBSCUxjW*t>bvq@}X zYIL6#hwUC~aZI7I_i<^e668KDriD_}R9;~$YNH43 z-lK))qqH~}7ZS{ndT}j`lUj=h5>%FLG{oF$vI{Gb2*>#t|L57pn#EhOxEGE!FL)HETKxbEb3 zP?$(pN;$cyHG0tQJ=}7=18$R4-Xpj1KDfOWw~oc^L^CluM_Dv8xg1q8MTG$gnhy=+ zA}S~vElbvH4#k928cPUQHrpAc;rnTrwO-N*O;TgBgo_DpWt0)Ats<-OTyhwd*nvhZ zSeYABtz>OwDZ`BV0Kbx4xiMukKU-r6GcYDtToX@wij@Sl+&!fcr!{J`ns2NE;nY|a zkQl5&-R9z2!kx}BD9y=45LHEP_K3XEj=`*ip4k%@_C^_*QH^O)W%cj$N_J{2DcGVh zMG1GC2Rhxgh-G~bv5LxT^H)wqoK{B=7qLzrpoI>rxfm9e4B7QRfppjji&e8Kw?&YG z0;l%rM%pSKw!67KzLsK6UXuR%mDE6wpj(!RaaF#!lPHgCN~kS$3t)Tzfz$U015py4 zxR$UBqdBjIAS6!Jfek5d6dl`Me9EO78Q$nYyFaP~;k_$?tHzs_fr|G>6$nh)?8-_8 znVf`{va?3#5M3yiK<5x6QP6qMlZn9dxd9b#Gkw(V4=Z8sUWvT!t|L8VvPfpZ@iQ1mrH+ys{=f_=Ps}C(7Ev>Wj5PGDWY=^vpqr@c4(10KKISO2sPf)mNBHL!z$G( zYHv~y=Jv@BF$N~AOgXpZ|aojSRi)dg6B1T^g;UM8#*}R)T-u_nvk| zKoakJ+SR6ErrnNwj9smFU literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hisc-action-tool_color_picker.svgz b/kolourpaint/pics/action/hisc-action-tool_color_picker.svgz new file mode 100644 index 0000000000000000000000000000000000000000..e680a1a70a6f4ba19c5625f0935c32ebea3521c3 GIT binary patch literal 10939 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hisc-action-tool_curve.svgz b/kolourpaint/pics/action/hisc-action-tool_curve.svgz new file mode 100644 index 0000000000000000000000000000000000000000..9aaa3e7f9377d58c6b3730785ead3511c645bb32 GIT binary patch literal 2380 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hisc-action-tool_elliptical_selection.svgz b/kolourpaint/pics/action/hisc-action-tool_elliptical_selection.svgz new file mode 100644 index 0000000000000000000000000000000000000000..63c328029d90d4b9dd3417074cbd9dab3facb118 GIT binary patch literal 3597 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 TFXI37h>!mPR#^u3q&WZpSxGBB literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hisc-action-tool_eraser.svgz b/kolourpaint/pics/action/hisc-action-tool_eraser.svgz new file mode 100644 index 0000000000000000000000000000000000000000..d7415948df9a70b22dbbd097373a2657db97b168 GIT binary patch literal 6959 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hisc-action-tool_line.svgz b/kolourpaint/pics/action/hisc-action-tool_line.svgz new file mode 100644 index 0000000000000000000000000000000000000000..622248262bf864d58d2f3e9beb3b6b63a158d6e5 GIT binary patch literal 1333 zcmV-51ykJ~m7zUNn{+Dn42B$9eqQr5{KL5cur12ntnnHFhV zU6~XpdTr;|cO;8?df`RUzO;q3-@kix_lx6L9~7sWGwYD+t@T+vRv9h1^?IvynR?#gaB%Zh z=t{Nm-dK(Umetedm1SArR#Yj=mUfFT_o`@cEc1B5cUMeu`{A@ zr5(gBYOYc@;w{fNTRoTOY-vNjI6z7BZX!y%o z5v&lSII^eS?-dmlG~RA$@2FP!@im5;h9pF{sLii`!0_NN7Rjv>y0zzU0U`n`2g#de zWt09m<0QnSQ#YF;-6{^7_@<~EFF{%&qwmSiWKr-8+L9Irda7*8Yw4_4rNzIaQvq+- zjUb1x=VE;V-Xb@_K@j=9^w{i(AdZ7YkIgP(vkCS(#{|k5`IkAyksqO3XW=G`m_*Sn zOn?wC0uqi%9`k?^iP3+^^1Nw{-@$TcETAhbOL!ijY6*@LWcOmZURNAknOujuN}mdT zwsIgQsnQ!C`+*<5ZA21End?*9nZAWcuX`sKyyKU7k5`GGBvbDsiD7RMzjuG)p<3a zZSyf`c|W^n2^z$5#M6mjdd4^6QO`s(>4{9R*NfLCedpB&b@SAHY1E$#>?!Am{xo0F zuG}Tv@Mc0luIuJ%6)UM2S4I^z*GSb4k!ApaVnl7dYNtn+_0^U#d05(Hs{Ae`v|Q+h zhJZ=gj^_@D2u#zdvS}p*oe;yYohkQ90fA0IPaKkJZf4e0TY1>1nw&K)Dn2!3GTZNJ z+x?mS@;F7*#?ERbAv&%0vjq;hfNY)iixne+o~o{wG|s5!CvJmAG-*}QE`_NLrZ;tIOr z#nS##D=j=Ps@#`5$#`pSh%2eyz8askYY4PE$Io#5~_%=F5Vc#i5PeY%5sI$2R z#QkoCfeY;j0a?fZtS^?+zx04F0|-pE4jj5ZCgDWdKO6l}I(Bz4i0{^DD%WKpC=8)j rt`rJ-?e)!kh`*Uu?*E$ojGAGeB;5(Nfj_*!M1k8s=lLR@@eu$3j7FK6 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hisc-action-tool_pen.svgz b/kolourpaint/pics/action/hisc-action-tool_pen.svgz new file mode 100644 index 0000000000000000000000000000000000000000..0dfdd297e9f956d63130d127581318ce73281afe GIT binary patch literal 4924 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hisc-action-tool_rectangle.svgz b/kolourpaint/pics/action/hisc-action-tool_rectangle.svgz new file mode 100644 index 0000000000000000000000000000000000000000..a4f8b15c7c2b0ece006205eed92bd5202246f029 GIT binary patch literal 1960 zcmV;Z2UqwXiwFP!000000PR}aZrex_ea}~L+=0Fwm!Op`5% zV~P|=%ChqHTTO{-Qli(BK!dpwHb~Z`tNPUGZo1%`@1ItA@Mx<#D~sC+L3k3_A}Q0X zSlmuNfBGTRlb~+mB8~I1u(y+RFoHP7e0#O_jHHX%gAou58iN5kVxHc=N=Y zZ|yy*Y+Y`uWIue8^ewA&4yHbnEo+U4F($$^qBI1Hu-+9-{1g`TRnHX?9CM`@;|O%# z^4D3-o^pWU*<@O6KRQ6ahTo&Z5ySml=iq5kVVmgPr*~0j9b%L=X>XX$%f~$}M-i`V zy^a%GA49bM+bnICw-c@rZm)IIikCK9ESqzAmflWazJz1bzIPeTj-0}X(N@)k>8m&$ zdZlG@AJ>p}v@BOPO5>u~MccA^h#pcKy}$eE$B)rNnFAE-I4hbnXP6Z@Csmzv{{ZE9P6bVd8A_*~mbXSJiZ6DE;XOcHaH zJ~^pDS}077wZdb)@Sy@=cSGi-J!A*%fy$3rf^V}xD z%Q)Z0yLu8t2RPlRasF*^R)8Bp66PR07V{n8CXpeWOXj6d?ULYHbLDN5QQPFi_GOGI zlvA>CjEQ6tPeX-_(p1W6h>;+QQ!xa2hy%e?ltDk7z=<9-$vbhBK`+&`%*Ql`&I+7?%WN&%T$}+Biok*5)^(=QjWP+ z<8U%ci~u+RMy4s5DugMOjC>26!x>X-=s$2?3{EEHMK;iXc7Tx`z++7RJ{WUFsTu!} zB1#JlM>`Q<1yS4~&zQoRTm)qCm)sWwmq6NaJS8(mO+!wRrkFU95|Z)D>c3}S>*C5G#ix*bj$MZE1^Dg={^{2q+u_$$!7 z#1SVECW;s{g$JdnqC>_C0gTp)^C4pmlAI8(JvT>f=Y88h3}YdiufX^MpK1azAvniV z=|9y*kAU&xV*F?sJ%$c&-bAkb(movU^pMRTJb^>|!gFXOb_!m>M6ekse9gMm@4xjIujb}|M$me{Z} zCB&l&NrIJ8XUa`gK{3NT-dUJbd+QicFU#$sa^TG4ytb#VX_;(R&Udrt%lhfm!a0Zc z_a$W4RGYH_wpjtQ4G)b-f+>E+qU%XYt)8)f2+uh?v^y%SY!jz(6Z^7v65h4vpj|xs z`P~o4ZbFjG{tInM-yLuU@qM|0d|ID_U7F0?Vu+i!*$OJ@dc*&M7VS;s6$9gDx3(Y{ z4BPd&$9m=Pv&x)P^s&kE{Jjg(?|MXsM7utOp9j2jvuxiMe)hiHecemYDV*UNoV=sbi^5i>yzC%tc6K=D6; z(1>tBB%Ugyji!|2;6qO;C?c3BB|%D%7F=^YE*&4CFZ~h>_Hze2k<9Pnev^Y5Il{ow zOx)G7<>gW-!?~19?@I*)e0_C(X1r;&=Z&~OU+EMwf^cJ6h?Ox$u|4pZYpi5XdKXBH z2%(bGDIeR6vf3V{UqJ1VvbKCaKS;C;Q;D z_fi;>J9-&T`%*{*+$k15j+gZ^`rxI#?C-qP&(p{8c#PsXPFeAP33=$jW4Jml7G+_d zJw94_waM+wK3brDdZZ#F3=kMnks&3(ji|4o;wr8lY}KZ5cn<3(u9|^lm8FAsTkPL2 zMP$F!9X$wZ4G=9e}SVxcv`KHYeM(AOHZagT0~v literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hisc-action-tool_rounded_rectangle.svgz b/kolourpaint/pics/action/hisc-action-tool_rounded_rectangle.svgz new file mode 100644 index 0000000000000000000000000000000000000000..359eed2400eb85821e776c9b70f3988b61797829 GIT binary patch literal 2220 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 R{MY?|{{x@Cp;+6S0RY)p*Rucs literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/action/hisc-action-tool_text.svgz b/kolourpaint/pics/action/hisc-action-tool_text.svgz new file mode 100644 index 0000000000000000000000000000000000000000..52d87f8124ad68c888e6f6c3f682289bbbf37a48 GIT binary patch literal 2666 zcmbV_cQ_l08phj0ksh_TqxRN{Y9mIeQ>sSwMzuzTqRLTw#R_e;(uz?lsM335R!}oU zjAO-#5vv+RsIldB69QMe>~bF(r=o11SWdxZw|S zSvg|fW(+tksX*b@Yq@LD&X+BRJ+E0eR0-#a+%&*$(+%;hTFNO2Ai<2R!w7~helgCw zV`g?OL)&XSfW8{rc?Wy5-xnH)otQY7n@5lbV$&O-5dS17GhmXc-zDED!$z<4j;yG^%`^zV=ZI7#QyCy%QY+^;QmFL1n-X_4n|o+ zz&1-UR)e}WWm57)8P{*b^5>nwrAQ#BliyzSfLyE>r8Fv)cv^zn0e%~ z>gVCQZggyYB25ko)mv01?fs_Hao1Po>KN8@oKCHF@vP}(R7d)Vo2|M({Ns?(r@2@^?|eNSc=v;(a4n`PLC` zjrsU_%$=Oxp$KLTi{6{RFwDYEXXP)Dp|!_vTjM)q>^fOTD7CyL7axL9wzCYclD4q2 zT|)Lep~z0h(+?f#tE8KxYLGpqqgmXS3z$~P_IWh&=zWNlZ|0=^!ZaZNwqxPK`%7mV zdu=EsR@1IAJBrXQ2twEXU2iu8@mSR>+2{d$VMEAkyi+JeLwwYyv&f7QwHng9gp08( z@cg#Da>e?JvCLw1RE4t2#v|P0bm!{S24z+~OAS_K=b0KpHV|C`GM{{vm-V*`R6}=` zl63S^Uk&bDS;CC^?qtRca)*heyQ{-8G_UlMRBe*=Z>)FwCdb)S<|l9Uun2!F9|3}S zq^^Fg0Tq#7qT`Wy;A{+7?{ml9$rP}gd^GX4ir}bGUo|H95v~9z=xyjb4C`F!8)RB1 zt1>VW`W|m`hX6-!4pE!2gM3Im^ZwDqAkwB}|7cZ6m-?XPrLxL8fW!4@W7v}CxoM%s z#$YapEJ{p)rZAKzAQlde;L{C1FD$Qi@{nK@OqEh;-i)XL;s&k~aKQcpHBNGpB7vY0 zoZm0NwM0au5D(XGFY_n57obSaZamdmIbx=H((=WG-|XIBz5(PvYn+;DTQ=tzL0H6H zdJ|)$CpB+{YDe{-b?;XuU}6__oR)hL6&lhozT2Xoes_8?M!WoaU#qPlY=8dVL1$K% z0kTAz!*Zvxp=;P=`CiP&kV;|eUw@rW3NiyKilD^U9?v3vFI`V~$_9+-iolYDrP^YU z1fNoHDmqoVS7)3}f~$}$8IaTigel29;4RP?GWc}SD0qCt!?g7Df*%i2_S*jJc~OIN zUj%*a=-i6VJUB|$)XdZuYf1T(-CR2xEBb)CmbjGyD1`-VGnFRii4a&T=Q2Mzra6)F zh;W8WvkTp<**#YP*)C|_XZ2iT5fxk_Osrv}^mQ=!1$78a{f9`&>Tw+`0kUkzZY4*k)RHoCf2$F5l_q8h#Z6bN{*6$YV?B+OYZYXbT-;+-wHr3u( z7%OtrC~59f8hByX?oHx(RGfDBkm4?@njeg8-%$&-1x(4R_d6BxnAGf-AC(Tju>S?G z*avQws4Bwl+@E+E1e7jdGXPIzCc4n(v%>WrqYLw+4p zkJ3gqi~!%Cqs0j{PE(L4MfIL3r@xFAA@jKCVIr&k zNnPhI@ARze8g@N=>K6JUivH;OMI9AnBh(kW^m{{W1q_S55BKIoc~XJulgT{Pb`}G<#0bp)3=&bZsxyO Y!XumEy+4%1q<5TV2$)LEDV#d>Z|0#cCjbBd literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/app/CMakeLists.txt b/kolourpaint/pics/app/CMakeLists.txt new file mode 100644 index 00000000..8bf119c3 --- /dev/null +++ b/kolourpaint/pics/app/CMakeLists.txt @@ -0,0 +1,9 @@ + +# Change this number every time you add an icon to force the build system +# to rebuild and pick the icon up: +# +# 3 +# + +kde4_install_icons( ${ICON_INSTALL_DIR} ) + diff --git a/kolourpaint/pics/app/hi16-app-kolourpaint.png b/kolourpaint/pics/app/hi16-app-kolourpaint.png new file mode 100644 index 0000000000000000000000000000000000000000..5951eac7bd425151f07681759f552cb8b8295511 GIT binary patch literal 812 zcmV+{1JnG8P)jQE*}J$*~0=+utFgfG{o5-3E6{Kkf7uPMgJ

Hs&B~!7laTvy9mm>m}@rds$KA!SbuSL@s`mL0sv{?B;2(K?fu+wuQyvs3$xS> zv?n^8Z zMIDab;gGy1MKy>_{OSlLxYGRzu@?QjD`p^=4U%@c|;S-ukm4%L^B zSA>cpZU}^Y4(%s_KuIj4{m|EQ>|#;cJ_QaxCcnzZKB+ndq8LQt2&RXWg!E*3s=B>l zG(VEd*{8rkCiNhF{DqzICz8(XSxJy2BmnIl0@E7J=f=|Fgl97h`e%V%!-&bcpqoEG zs8kNnUL@$o#$j=45QG6fu}K-(FaJ~EkQ+wO!XV!^%O0G`y;(|_R;K}{4lq5$T>!FzkhketkxW&%4rf6`B`RX--Z18e?`#Y&$ChO;>_|x&Tj=dYPsO1_{Y~Yo z3)=T+Le+vYrGimh;O%{M+A;+uHv^wY27f)60r=<5Ry zjRy8sR$RAjfajGrWdeZxJGTgSg}t`ox>^%=Fy3`=ZLn55tlH+fut8Db{$dx#c)t&! q0U3r>u`HYZD=ci}`225Z)#{6CY0000mR~QEH=k)t}-{N8qj91ks35tx!)ph9@9E#N_upb#6C%$?jTlvYdhrUPe3IZ%LD$3WwyuErQCNXMSPYMcC#ty{8_$&Ns>B2kN+}+zKcLDD zh!ll37c)O7NsEq~s#Ubxd|t-c@ooFvo4bpzbn_Dk9J$C%U|+8>Vt4NfF*0T z(B4j~^ZL0H+jf>!l}|YMqh)F}&q(*o&dlDZ)2k;1MubSsSq6TD3DsUWb*|>Zcllo% z`sp}>$9!3k`>R@`dtu?qP4GIcP$cLemL?EG<#V$8N&<>IIG`ctWy8HvIu6=r1Rpv_bd{oH5Zu=o2aN)N5s z_d~-k|JV4R&1?HBtu6Np?={})CBy}cy`KK>u%jCeI1a=N95YH%roy~DO3J3it%`RF1IQ^F`?Me>(acq zb~!@46sqYefMwBCb<1kJ^7EpymSB%yw004hCf+anUicZa7Jnknr}IVm^gO$k&VGDX z+T=Scy4t=sxnp>+*Ca={s#ah!v_BoQ#AMMC>L+GjrbJ{Toi5lgn#52BN3T^e3mMqN%bPoCF93 zyjg{d)H?)>Sg>c)f{htckrb;l@}@I^NPj}6#EAP3fAC6_Rw@SGSjx{4(t!<;djWt0 zSp@}Zp+tf?3zkF~4j=td9;Y2#y6$zj9Dc-4Q%c)9ZCeG5Ot5ScC&Qh=+wijiFtLJk z0A7_+l)*^|x?O-WhGjU$l@|phiU`SOcpgJ06BH~fVN5;vc>9jb+lHG4pLDXnZz!71 z(j@e-M2M&p_T!izoeh(x2~;?`P;btMMsa;NpyYr~H^50kECoUgrD8{EDLAbbL4yH7 zVA3@^hOQktoi{5QDf3d3GH=}LDP{To@J}tS7F|c!Dv1j#5%!1Oi0JEXx!l^5tm&1- zuz;V^kjw6*w09jLSF;-$)-z@^;bUXNgFrU`Hvoazg9dZ0#Ry=S<@b*iu|-UrPN&a| zg<>S~F>FCZSF3T`RRkqZr|HuoEd#cWmaV`7)B&3bz-H@ox**578f~^hN@*B>^E>|y VBx3I1pDX|X002ovPDHLkV1jJ9U8?{9 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/app/hi32-app-kolourpaint.png b/kolourpaint/pics/app/hi32-app-kolourpaint.png new file mode 100644 index 0000000000000000000000000000000000000000..19ae9bf21852df0decb860547318ffe647dbb35d GIT binary patch literal 2139 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 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/custom/CMakeLists.txt b/kolourpaint/pics/custom/CMakeLists.txt new file mode 100644 index 00000000..7c83b3be --- /dev/null +++ b/kolourpaint/pics/custom/CMakeLists.txt @@ -0,0 +1,14 @@ +install(FILES + tool_spraycan_9x9.png + tool_spraycan_17x17.png + tool_spraycan_29x29.png + color_transparent_26x26.png + colorbutton_swap_16x16.png + option_opaque.png option_transparent.png + resize.png scale.png + smooth_scale.png + image_skew_horizontal.png + image_skew_vertical.png + image_rotate_anticlockwise.png + image_rotate_clockwise.png + DESTINATION ${DATA_INSTALL_DIR}/kolourpaint/pics) diff --git a/kolourpaint/pics/custom/color_transparent_26x26.png b/kolourpaint/pics/custom/color_transparent_26x26.png new file mode 100644 index 0000000000000000000000000000000000000000..7dafc0c60153f4bd3e63127f4d6f12b8a4193939 GIT binary patch literal 972 zcmV;-12g=IP)UBVV$#1}J}8#|yJRk$5mydXE5Axf(v zJD(;>t0zdPC`6(uTevA)w=7q(EN8+jXu~a4vMqAWFK59pbqVsx;}EoKVYywY`;H#(Liy*KzGVPYq&#&)0i5iqc$};9Z*FW0cuupyOzy8mR zuIhKI=6I~)RMB*lcK_usK=G6$(FR&nYY-Sy4jw< z>7b^+puF3mtHYwN$D**=qO!@N!|S8J+@!eRrM={(!sw;N?54uqsL1WA#NVsS@2$t+ zud>6h&G4|d$FR!bve5Cf%;UAb-?h{8w#MeS$?3PwGrz|E=UC8UO$Q0b)x>L;#2d9Y_EG0bNN%K~yM_V_33e*)k~Z zG%A9#ftUeF%D_+$MGC>Giq}u9L?~Q_B&9n`cc})l6x@K8h{ZZngBN16OMB5Gt;vfN zvDnq&(%-K>zu&GM(=N4ny}hbHERA8;%<%5Hb5(#ir~=(C<+iptbCiI%O&raxCimI1 z8yXaVc(!*Vs$KH6)2G+g$^r58=>jNr71~azshKiG28e5FteTPSlASoQx_aV7scJCI zgJjo?(DL%WzVdR(J}~Vy4PlpLPgz+{Pnns7DVQ$ffZMgezND+G#8)d{EKl041jx3j zhuI}wT-@rZms46QQd*iV;MCe$%nG$@l0}`XQDy-U2!V(UPU|}J2@tzPosH6S!GIq^ zr7~MGLF^Ka%7Owu7$blIYFCO;LK+zGLa10qKSUTNYlbBO0S|}>WcEjmuSgl6=xFZf uXb+Yc%(M|A=*Z>39)~?0ny?uor$Yd|0AUIJ82=Oi0000 literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/custom/colorbutton_swap_16x16.png b/kolourpaint/pics/custom/colorbutton_swap_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..b1352721596fcf87421e2f2b884df41b12c973d9 GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6>^xl@Ln>}9Jr~HvV8Fw2aqs`7 z%TH-7m0kKw`H(QD?u6iM4GylRrUI`07tYq*IB>?n_`<)2N>vxn?b4MsMGRgR{~5Ic SEkl7OF?hQAxvXiJ}#2xCc* zUoeBivm0qZPO+zpV@SoVt5*&A4k!pPJHG#KKUc{0IBWD0hVlqz5rqKGPt2XF8&#$q z=##ki<;EFLq6?Z1P^~lD#NZ<+{7>T95m^mCuER6U+*?O5fP)ksYJ{ecCOF zvXGK#%Y3qSt;_oVbCKtdj$Jl?!*tdMe`eUF&ct?L*7=h_>lr*<{an^LB{Ts5oz6{| literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/custom/image_rotate_clockwise.png b/kolourpaint/pics/custom/image_rotate_clockwise.png new file mode 100644 index 0000000000000000000000000000000000000000..cd1e00b7f75fb7f4dbda85265b8f3e7dbab3422c GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^IzX()!3-o*CUIK;DYgKg5ZC`ez`*c2>iJ}#2xCc* zUoeBivm0qZPL8LGV@SoVr&kQQ7z}xs9pC@ApBq(>_V`eNz|>0_oWE4Svxp|$RBP95&MYu9$N$DqkIKQZt8nT>Zd@)q50bvv#Q sm9^${>o$+QAA5i8f4|2>@&63weK(lc7W%u@0_|n+boFyt=akR{05dsHxBvhE literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/custom/image_skew_horizontal.png b/kolourpaint/pics/custom/image_skew_horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..b6c39068aadd83f53230e60fccda1577cb790b3c GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^Za{3p!3-oBr1sSSNuB_o5ZC|z{{xvo3}ncB>z@qd zFqQ=Q1v5B2yO9RugnGI-hE&{od(n{ZfCC4!a5k~V^=FseQ{!oiRtO#ocsA4Ll;O-N$#pZYm@iqfWtVu} XT&~GW&seSkTEO7x>gTe~DWM4fC!Ipp literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/custom/image_skew_vertical.png b/kolourpaint/pics/custom/image_skew_vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..7c6fad9a6ac95e78513ee43d6fb943aaa34a2733 GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^CP3`Q!3-p~__zxLDV_kI5ZC|z{{xvo3}ncB>z@qd zFqQ=Q1v5B2yO9Ru^n1EEhE&{od(M!H*-(JxVD#Pp|8v$XVeK|8d6r-;%k^yLvh3`& z^4^spPXjqwDpgmlKe(Zji^;gE5o z0;6ftMeB?U{}%>vC=046bEqkII8W#>Vw{=2z)z-$MTNt|mf2C}mWx7%gN<;8u(->@ d^jRKX7cO}1c`Dh4`@!PC{xWt~$(699L4PEG&- literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/custom/option_opaque.png b/kolourpaint/pics/custom/option_opaque.png new file mode 100644 index 0000000000000000000000000000000000000000..6c88cb4174f5cbbbde53e7f80687bba3e4c5c522 GIT binary patch literal 717 zcmV;;0y6!HP)hR}V~hkMr~qhJVB4+%?X-UM3h7X$=XP5F-L$)KeN0H9iP<$5ps@(VGI?rd zm98~wc5rY!0|NMTs);U+-B>4$R_`25j8ZbB04=DjLGd1Zet~Lm9FARs>X7bVNZOLT9bc;HufVmTU=_`F; zk5?i$oMGQUhT=TnV^#g9ZS|5#Ut1hFI0U7S@VyRREvPll9^B&ot0AQAJEuoA5z&T_-!hZ6PyLHkDWGstqfH60%pbpf%FS2o*%0O_oI3c<~I{{yKRbDEP7g4-PXxL!$c>3|j^L?H@-|hRy=lf-nPAOfz zVKo4$S`}{)JV?+cu}GL&t8al|t3Fbt`4$-@{fDl^5rCjj5Od2;0(4duNV5L<>xiaTld%T9hW?_iFvN5gRMN`iu=mJ z3YVt3hgLDHt8jXqxb8Nap7FdWg)I)Y1lW|YsUV!S{{njp>}{~yVQ+`M1NO^s1p7Kd zd>wCuIve5q8O~-n+wdei`bo}CR~TF-t;?iiIJjNJSSjPA)ca8{qd`XF78(PX8o(Sm z5A9?#cnNF;e+7R7?O;3D0bT+B03F~}uoLV8yTNPVb?^pw6YK$d!9K7b8~_KwTi|VQ z2pk4Sz)|o|@GtOha10y=?|>8FBsc|5gLlDu;C=7`=mZ~vbKpGq2m(n>!+50t)1$zk z+r2{dqB*@vFt>LTmGL<0xltwL!|M2$Jw>BdOV}>=Fh7%h%qQtR|Cp5CO~zx6QzhY* z_s>WiR}_9;J9u33t@+i!5_3sv>Sv0I3Y#L(Yi*NtZE@as=J7Wt?##tLt(vJ?=o)C& zHu{(y^y@&h zEiZk4H0RlK%Dld8eeem-;jDslZ!K-kxKLSLI9l58Sh`TM?(Gi;&1U1a-M?G1B7(LR zy6awOkjbMzkSId~)pIY7RZlB|_3x*t!aHS|yTW^qO-?2ichA;F^ew8QZ`AslJ>sk* z`vGnXD8$`~=GPvxeB zNImY=>k|9xUk;L&c_beQ7%tkE=6}8Gn?(cr+&k3M3sI)>Be~;cEwn7MH8R`fZjorK TruMDbvGOE!f-e4itg-liW|x%w literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/custom/resize.png b/kolourpaint/pics/custom/resize.png new file mode 100644 index 0000000000000000000000000000000000000000..ef25380f7269572576139284f2c331ed942b8545 GIT binary patch literal 4009 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 P00000NkvXXu0mjfQ8cil literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/custom/scale.png b/kolourpaint/pics/custom/scale.png new file mode 100644 index 0000000000000000000000000000000000000000..6941475741702ae62c6943493f8204ffd2740725 GIT binary patch literal 1724 zcmXYx4^$IJ9>*7`-U4dz4u}8(R;8 zJwsZ!nuXIgo+9xSS+E{kOU0FfZjj0X)eaz-MXC(7lU>LSgl@LcrZLVZdxR>OgyY8OBFi(B*_8svEA*E88n3zZqgifc+ zc4IQQk-XXD#%wa%&2Fq_v)$^3v|6RWL6TO|jh!SNlG*K&Bph}-C7Ht^NrG}XXorJw zP%K4Y6oFH2Xo_MenxzSx)?l>8kJhcGDF^L_p`|>{(G16Ea7KqQI-D`c7^%Pw%Sd?! zFf3rRG1e5pnq;g=$yy1PC0REd%L2}fb5@*VNsgsCH-O`KU{?Z%5>N)fm;pxvjs-xH z1P=fNKma`C%{WhDJZI&BQ~-Ehk_6;=0a|fL;*i!sRtY%+X&AZ@AVh*4 z7aX`iYXn9ouo{8Q7C46hC;`v{ZxEnefYMshR6&5EATWX(DL{hA3nCI7GLgbXN+!~n z$VgI%EGDuMBC8ZRg~(|{fQi6Q1PT$DL>?1)oya>xNQsaYAtyp!{NK8w8zf4%>%b5t zDFRV4L}Q4-5GzT8I1F(z#3>QL5Wo>wjervI7~*A!mx64>lZdAg#1O<0R3fNB(25|1 z1Plo)kYGZB83|ICM#uV5u*R^jUhxJq(O*6h(U;#j^o^1WRsqthmlaf zg>8mInHc8vOckfxQ9SG#u%rgX2RkPlQbx`vpWFKI=*k9}CBXaXg2@d^nPtZ5`^f3# zTR4#FQLU!6F_WDp;=APfUEg`8chvJUk29t?&pTPRg(_c5^@k@z`*Mn34f&|MV%?sL zyHZNhq6TGKgEp)&tcn?Z_H>c)`oZhRV#gb+QdM9;d#yVbtyLc(cPQ)$+s98sdu>?d zK4QLXAch#q%l*o9{OfgfFQ=sTegJE2R>texnon)*E#p);Db}2WI6BE z=)2!rVw-vTl#A%UXt}tnr_|3gZe+AHyk%mW{&m;Ig0}wq4UU`Nj#sw!41F!kO%N|u ze{$&Od9C}?FQer#fz{y}${xSIHAu8A{Wdz|*GJn6r`9Wt!1qq%1#3~RX6ZX0n+qRW z&IC_fX>UJNTKd^?pTn`Scw{)OpYELqo9Uf0KD)Iuv(OF#eO9IP1Zv96g&P~oZD;Ip z*%J@@eigS6NonNgCDE7i?hl(|h@;)i+q;I#VP=gY;%45X>NkG=A!Otq|Cye-+ftpQ z8Cmh*R_BQ#@5?J(d-Y!_BcZXruCA`3AtEho!-3j_!O5BHpMIf#Y4c}tCkh~zVWL6th4(l z>7O&Is9@eoDbQ-QdcEFgq}$qpf&%at*6k^4v4YB$$L$@vZ(d115W1uT$*(9pc7Hrt zmi$5M!0M?!|BYY3yO+VJ{AO=Qc3;E1W(z30eL8fpYVXaSY`*D)@l5;6dEd#*nW1{@T|M^=CAn%JQEL bnElPpd&c=yYtI);Ujn9zSI0HSYRmr%eQVWq literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/custom/smooth_scale.png b/kolourpaint/pics/custom/smooth_scale.png new file mode 100644 index 0000000000000000000000000000000000000000..e2b590c982dc8f5f0b330b8ebe712d1f5e8a6d38 GIT binary patch literal 5097 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 Jmvv4FO#r`lRkZ*B literal 0 HcmV?d00001 diff --git a/kolourpaint/pics/custom/tool_spraycan_9x9.png b/kolourpaint/pics/custom/tool_spraycan_9x9.png new file mode 100644 index 0000000000000000000000000000000000000000..06c8639ef59741dd8e7584134b1379fe84739381 GIT binary patch literal 92 zcmeAS@N?(olHy`uVBq!ia0vp^oFL2rBp8nViaZ0Pls#P>LpWqv4>B?Wc?}c(%@*eW2gm)GFJ0-+fo1q`09elF{r5}E)wuoRL2 literal 0 HcmV?d00001 diff --git a/kolourpaint/pixmapfx/kpPixmapFX.h b/kolourpaint/pixmapfx/kpPixmapFX.h new file mode 100644 index 00000000..bbb1c0c9 --- /dev/null +++ b/kolourpaint/pixmapfx/kpPixmapFX.h @@ -0,0 +1,281 @@ + +// REFACTOR: Split this class into one for each distinct functionality category +// (e.g. effects, mask operations)? + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_PIXMAP_FX_H +#define KP_PIXMAP_FX_H + + +#include +#include + +#include + +#include + + +class QColor; +class QImage; +class QMatrix; +class QPen; +class QImage; +class QPoint; +class QPolygon; +class QRect; + + +class kpAbstractSelection; + + +// +// QPixmap (view) Manipulation. +// +// Anything that is supposed to be manipulating the document contents +// (i.e. kpImage), should be moved to kpPainter. +// +// kpPainter uses us for its Qt backend but we don't use kpPainter. +// TODO: We should not use kpColor nor kpImage for the same reason. +// +class kpPixmapFX +{ +// +// Get/Set Parts of Pixmap +// + +public: + + // + // Returns the pixel and mask data found at the in . + // + static QImage getPixmapAt (const QImage &pm, const QRect &rect); + + // + // Sets the pixel and mask data at in <*destPixmapPtr> + // to . Neither 's width nor height are allowed + // to be bigger than 's (you can't copy more than you have). + // On the other hand, you can copy less than the size of + // - no scaling is done. + // + static void setPixmapAt (QImage *destPixmapPtr, const QRect &destRect, + const QImage &srcPixmap); + + // + // Sets the pixel and mask data at the rectangle in <*destPixmapPtr>, + // with the top-left and dimensions , + // to . + // + static void setPixmapAt (QImage *destPixmapPtr, const QPoint &destAt, + const QImage &srcPixmap); + static void setPixmapAt (QImage *destPixmapPtr, int destX, int destY, + const QImage &srcPixmap); + + // + // Draws on top of <*destPixmapPtr> at . + // The mask of <*destPixmapPtr> is adjusted so that all opaque + // pixels in will be opaque in <*destPixmapPtr>. + // + static void paintPixmapAt (QImage *destPixmapPtr, const QPoint &destAt, + const QImage &srcPixmap); + static void paintPixmapAt (QImage *destPixmapPtr, int destX, int destY, + const QImage &srcPixmap); + + // + // Returns the colour of the pixel at in . + // If the pixel is transparent, a value is returned such that + // kpTool::isColorTransparent() will return true. + // + static kpColor getColorAtPixel (const QImage &pm, const QPoint &at); + static kpColor getColorAtPixel (const QImage &pm, int x, int y); + +// +// Transforms +// + +public: + + // + // Resizes an image to the given width and height, + // filling any new areas with . + // + static void resize (QImage *destPtr, int w, int h, + const kpColor &backgroundColor); + static QImage resize (const QImage &pm, int w, int h, + const kpColor &backgroundColor); + + // + // Scales an image to the given width and height. + // If is true, a smooth scale will be used. + // + static void scale (QImage *destPtr, int w, int h, bool pretty = false); + static QImage scale (const QImage &pm, int w, int h, bool pretty = false); + + + // The minimum difference between 2 angles (in degrees) such that they are + // considered different. This gives you at least enough precision to + // rotate an image whose width <= 10000 such that its height increases + // by just 1 (and similarly with height <= 10000 and width). + // + // Currently used for skew & rotate operations. + static const double AngleInDegreesEpsilon; + + + // + // Skews an image. + // + // horizontal angle clockwise (-90 < x < 90) + // vertical angle clockwise (-90 < x < 90) + // color to fill new areas with + // if > 0, the desired width of the resultant pixmap + // if > 0, the desired height of the resultant pixmap + // + // Using & to generate preview pixmaps is + // significantly more efficient than skewing and then scaling yourself. + // + static QMatrix skewMatrix (int width, int height, double hangle, double vangle); + static QMatrix skewMatrix (const QImage &pixmap, double hangle, double vangle); + + static void skew (QImage *destPixmapPtr, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + static QImage skew (const QImage &pm, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + + // + // Rotates an image. + // + // clockwise angle to rotate by + // color to fill new areas with + // if > 0, the desired width of the resultant pixmap + // if > 0, the desired height of the resultant pixmap + // + // Using & to generate preview pixmaps is + // significantly more efficient than rotating and then scaling yourself. + // + static QMatrix rotateMatrix (int width, int height, double angle); + static QMatrix rotateMatrix (const QImage &pixmap, double angle); + + static bool isLosslessRotation (double angle); + + static void rotate (QImage *destPixmapPtr, double angle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + static QImage rotate (const QImage &pm, double angle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + +// +// Drawing Shapes +// + +public: + + // Returns a pen suitable for drawing a rectangle with 90 degree + // corners ("MiterJoin"). This is necessary since Qt4 defaults to + // "BevelJoin". is passed straight to QPen without modification. + static QPen QPainterDrawRectPen (const QColor &color, int qtWidth); + + // Returns a pen suitable for drawing lines / polylines / polygons / + // curves with rounded corners. This is necessary since Qt4 defaults + // to square corners ("SquareCap") and "BevelJoin". + // is passed straight to QPen without modification. + static QPen QPainterDrawLinePen (const QColor &color, int qtWidth); + + + // Draws a line from (x1,y1) to (x2,y2) onto , with + // and . The corners are rounded and centred at those + // coordinates so if > 1, the line is likely to extend past + // a rectangle with those corners. + // + // If is valid, it draws a stippled line alternating + // between long strips of and short strips of . + static void drawPolyline (QImage *image, + const QPolygon &points, + const kpColor &color, int penWidth, + const kpColor &stippleColor = kpColor::Invalid); + + static void drawLine (QImage *image, + int x1, int y1, int x2, int y2, + const kpColor &color, int penWidth, + const kpColor &stippleColor = kpColor::Invalid); + + // = shape completed else drawing but haven't finalised. + // If not , the edge that would form the closure, if the + // shape were finalised now, is highlighted specially. + // + // Odd-even fill. + static void drawPolygon (QImage *image, + const QPolygon &points, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor = kpColor::Invalid, + bool isFinal = true, + const kpColor &fStippleColor = kpColor::Invalid); + // Cubic Beizer. + static void drawCurve (QImage *image, + const QPoint &startPoint, + const QPoint &controlPointP, const QPoint &controlPointQ, + const QPoint &endPoint, + const kpColor &color, int penWidth); + + static void fillRect (QImage *image, + int x, int y, int width, int height, + const kpColor &color, + const kpColor &stippleColor = kpColor::Invalid); + + // Draws a rectangle / rounded rectangle / ellipse with top-left at + // (x, y) with width and height . Unlike QPainter, + // this rectangle will really fit inside x and won't + // be one pixel higher or wider etc. + // + // and must be >= 0. + // + // must not be invalid. However, may be invalid + // to signify an unfilled rectangle / rounded rectangle /ellipse. + static void drawRect (QImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, int penWidth = 1, + const kpColor &bcolor = kpColor::Invalid, + const kpColor &fStippleColor = kpColor::Invalid); + + static void drawRoundedRect (QImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, int penWidth = 1, + const kpColor &bcolor = kpColor::Invalid, + const kpColor &fStippleColor = kpColor::Invalid); + + static void drawEllipse (QImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, int penWidth = 1, + const kpColor &bcolor = kpColor::Invalid, + const kpColor &fStippleColor = kpColor::Invalid); +}; + + +#endif // KP_PIXMAP_FX_H diff --git a/kolourpaint/pixmapfx/kpPixmapFX_DrawShapes.cpp b/kolourpaint/pixmapfx/kpPixmapFX_DrawShapes.cpp new file mode 100644 index 00000000..13aff76f --- /dev/null +++ b/kolourpaint/pixmapfx/kpPixmapFX_DrawShapes.cpp @@ -0,0 +1,492 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_PIXMAP_FX 0 + + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +//--------------------------------------------------------------------- + +// Returns whether there is only 1 distinct point in . +static bool Only1PixelInPointArray (const QPolygon &points) +{ + if (points.count () == 0) + return false; + + for (int i = 1; i < (int) points.count (); i++) + { + if (points [i] != points [0]) + return false; + } + + return true; +} + +//--------------------------------------------------------------------- + +// Warp the given from 1 to 0. +// This is not always done (specifically if ) because +// width 0 sometimes looks worse. +// +// Qt lines of width 1 look like they have a width between 1-2 i.e.: +// +// # +// ## +// # +// # +// +// compared to Qt's special "width 0" which just means a "proper" width 1: +// +// # +// # +// # +// # +// +static int WidthToQPenWidth (int width, bool drawingEllipse = false) +{ + if (width == 1) + { + // 3x10 ellipse with Qt width 0 looks like rectangle. + // Therefore, do not apply this 1 -> 0 transformations for ellipses. + if (!drawingEllipse) + { + // Closer to looking width 1, for lines at least. + return 0; + } + } + + return width; +} + +//--------------------------------------------------------------------- + +static void QPainterSetPenWithStipple (QPainter *p, + const kpColor &fColor, + int penWidth, + const kpColor &fStippleColor = kpColor::Invalid, + bool isEllipseLike = false) +{ + if (!fStippleColor.isValid ()) + { + p->setPen ( + kpPixmapFX::QPainterDrawLinePen ( + fColor.toQColor(), + ::WidthToQPenWidth (penWidth, isEllipseLike))); + } + else + { + QPen usePen = kpPixmapFX::QPainterDrawLinePen ( + fColor.toQColor(), + ::WidthToQPenWidth (penWidth, isEllipseLike)); + usePen.setStyle (Qt::DashLine); + p->setPen (usePen); + + p->setBackground (fStippleColor.toQColor()); + p->setBackgroundMode (Qt::OpaqueMode); + } +} + +//--------------------------------------------------------------------- + +// public static +QPen kpPixmapFX::QPainterDrawRectPen (const QColor &color, int qtWidth) +{ + return QPen (color, qtWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin); +} + +//--------------------------------------------------------------------- + +// public static +QPen kpPixmapFX::QPainterDrawLinePen (const QColor &color, int qtWidth) +{ + return QPen (color, qtWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); +} + +//--------------------------------------------------------------------- +// +// drawPolyline() / drawLine() +// + +// public static +void kpPixmapFX::drawPolyline (QImage *image, + const QPolygon &points, + const kpColor &color, int penWidth, + const kpColor &stippleColor) +{ + QPainter painter(image); + + ::QPainterSetPenWithStipple(&painter, + color, penWidth, + stippleColor); + + // Qt bug: single point doesn't show up depending on penWidth. + if (Only1PixelInPointArray(points)) + { + #if DEBUG_KP_PIXMAP_FX + kDebug () << "\tinvoking single point hack"; + #endif + painter.drawPoint(points[0]); + return; + } + + painter.drawPolyline(points); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::drawLine (QImage *image, + int x1, int y1, int x2, int y2, + const kpColor &color, int penWidth, + const kpColor &stippleColor) +{ + QPolygon points; + points.append (QPoint (x1, y1)); + points.append (QPoint (x2, y2)); + + drawPolyline (image, + points, + color, penWidth, + stippleColor); +} + +//--------------------------------------------------------------------- +// +// drawPolygon() +// + +// public static +void kpPixmapFX::drawPolygon (QImage *image, + const QPolygon &points, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + bool isFinal, + const kpColor &fStippleColor) +{ + QPainter p(image); + + ::QPainterSetPenWithStipple (&p, + fcolor, penWidth, + fStippleColor); + + if (bcolor.isValid ()) + p.setBrush (QBrush (bcolor.toQColor())); + // HACK: seems to be needed if set_Pen_(Qt::color0) else fills with Qt::color0. + else + p.setBrush (Qt::NoBrush); + + // Qt bug: single point doesn't show up depending on penWidth. + if (Only1PixelInPointArray (points)) + { + #if DEBUG_KP_PIXMAP_FX + kDebug () << "\tinvoking single point hack"; + #endif + p.drawPoint(points [0]); + return; + } + + // TODO: why aren't the ends rounded? + p.drawPolygon(points, Qt::OddEvenFill); + + if ( isFinal ) + return; + + if ( points.count() <= 2 ) + return; + + p.setCompositionMode(QPainter::RasterOp_SourceXorDestination); + p.setPen(QPen(Qt::white)); + p.drawLine(points[0], points[points.count() - 1]); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::drawCurve (QImage *image, + const QPoint &startPoint, + const QPoint &controlPointP, const QPoint &controlPointQ, + const QPoint &endPoint, + const kpColor &color, int penWidth) +{ + QPainter p(image); + ::QPainterSetPenWithStipple (&p, + color, penWidth); + + // SYNC: Qt bug: single point doesn't show up depending on penWidth. + if (startPoint == controlPointP && + controlPointP == controlPointQ && + controlPointQ == endPoint) + { + #if DEBUG_KP_PIXMAP_FX + kDebug () << "\tinvoking single point hack"; + #endif + p.drawPoint (startPoint); + return; + } + + QPainterPath curvePath; + curvePath.moveTo(startPoint); + curvePath.cubicTo(controlPointP, controlPointQ, endPoint); + + p.strokePath(curvePath, p.pen()); +} + +//--------------------------------------------------------------------- +// public static +void kpPixmapFX::fillRect (QImage *image, + int x, int y, int width, int height, + const kpColor &color, + const kpColor &stippleColor) +{ + QPainter painter(image); + + if (!stippleColor.isValid ()) + { + painter.fillRect (x, y, width, height, color.toQColor()); + } + else + { + const int StippleSize = 4; + + painter.setClipRect (x, y, width, height); + + for (int dy = 0; dy < height; dy += StippleSize) + { + for (int dx = 0; dx < width; dx += StippleSize) + { + const bool parity = ((dy + dx) / StippleSize) % 2; + + kpColor useColor; + if (!parity) + useColor = color; + else + useColor = stippleColor; + + painter.fillRect (x + dx, y + dy, StippleSize, StippleSize, useColor.toQColor()); + } + } + + } +} + +//--------------------------------------------------------------------- +// Calls to drawRect(), drawRoundedRect() and drawEllipse() are +// forwarded here. is the respective QPainter function and +// may or may not be called. +static void DrawGenericRect (QImage *image, + int x, int y, int width, int height, + void (*func) (QPainter * /*p*/, int /*x*/, int /*y*/, + int /*width*/, int/*height*/), + const kpColor &fcolor, int penWidth, + kpColor bcolor, + const kpColor &fStippleColor, + bool isEllipseLike) +{ +#if DEBUG_KP_PIXMAP_FX + kDebug () << "kppixmapfx.cpp:DrawGenericRect(" << x << "," << y << "," + << width << "," << height << ",func=" << func << ")" + << " pen.color=" << (int *) fcolor.toQRgb () + << " penWidth=" << penWidth + << " bcolor=" + << (int *) (bcolor.isValid () ? + bcolor.toQRgb () : + 0xabadcafe) + << " isEllipseLike=" << isEllipseLike + << endl; + #endif + + + if ( (width == 0) || (height == 0) ) + return; + + Q_ASSERT (func); + + // Check foreground colour valid. + // Background is allowed to be invalid (no fill). + Q_ASSERT (fcolor.isValid ()); + + + if (width == 1 || height == 1) + { + #if DEBUG_KP_PIXMAP_FX + kDebug () << "\twidth=1 or height=1 - draw line"; + #endif + + kpPixmapFX::drawLine (image, + x, y, x + width - 1, y + height - 1, + fcolor, 1/*force pen width to 1*/, + fStippleColor); + return; + } + + + // Outline is so big that fill won't be seen? + if (penWidth * 2 >= width || penWidth * 2 >= height) + { + #if DEBUG_KP_PIXMAP_FX + kDebug () << "\toutline dominates fill - fill with outline"; + #endif + + // Fill with outline. + // TODO: doesn't emulate non-Qt::SolidLine pens + // TODO: Transition from this hack to normal drawing makes the + // ellipse look like it moves 1 pixel to the right due to + // Qt missing a pixel on the left of some sizes of ellipses. + penWidth = 1; + bcolor = fcolor; // Outline colour. + } + + QPainter painter(image); + + ::QPainterSetPenWithStipple(&painter, + fcolor, penWidth, + fStippleColor, + isEllipseLike); + + QPen pen = painter.pen(); + pen.setJoinStyle(Qt::MiterJoin); // rectangle shall always have square corners + painter.setPen(pen); + + if (bcolor.isValid ()) + painter.setBrush (QBrush (bcolor.toQColor())); + // HACK: seems to be needed if set_Pen_(Qt::color0) else fills with Qt::color0. + else + painter.setBrush (Qt::NoBrush); + + // Fight Qt behaviour of painting width = fill width + pen width + // and height = fill height + pen height. Get rid of pen width. + (*func) (&painter, + x + penWidth / 2, + y + penWidth / 2, + width - penWidth, + height - penWidth); +} + +//--------------------------------------------------------------------- + + +static void DrawRectHelper (QPainter *p, + int x, int y, int width, int height) +{ + // workaround for QTBUG-38617 + QPainterPath path; + path.addRect(x, y, width, height); + p->drawPath(path); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::drawRect (QImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + const kpColor &fStippleColor) +{ + ::DrawGenericRect (image, + x, y, width, height, + &::DrawRectHelper, + fcolor, penWidth, + bcolor, + fStippleColor, + false/*not ellipse-like*/); +} + +//--------------------------------------------------------------------- + + +static void DrawRoundedRectHelper (QPainter *p, + int x, int y, int width, int height) +{ + // (has default arguments for the roundness i.e. different prototype + // to QPainter::draw{Rect,Ellipse}(), therefore need pointer to these + // helpers instead of directly to a QPainter member function) + p->drawRoundedRect(x, y, width, height, 25, 25, Qt::RelativeSize); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::drawRoundedRect (QImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + const kpColor &fStippleColor) +{ + ::DrawGenericRect (image, + x, y, width, height, + &::DrawRoundedRectHelper, + fcolor, penWidth, + bcolor, + fStippleColor, + true/*ellipse like*/); +} + +//--------------------------------------------------------------------- + + +static void DrawEllipseHelper (QPainter *p, + int x, int y, int width, int height) +{ + p->drawEllipse (x, y, width, height); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::drawEllipse (QImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + const kpColor &fStippleColor) +{ + ::DrawGenericRect (image, + x, y, width, height, + &::DrawEllipseHelper, + fcolor, penWidth, + bcolor, + fStippleColor, + true/*ellipse like*/); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp b/kolourpaint/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp new file mode 100644 index 00000000..58dfe27e --- /dev/null +++ b/kolourpaint/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp @@ -0,0 +1,143 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_PIXMAP_FX 0 + + +#include + +#include + +#include +#include +#include +#include + +#include + +#include + +//--------------------------------------------------------------------- + +// public static +QImage kpPixmapFX::getPixmapAt (const QImage &image, const QRect &rect) +{ + return image.copy(rect); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::setPixmapAt(QImage *destPtr, const QRect &destRect, + const QImage &src) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + kDebug () << "kpPixmapFX::setPixmapAt(destPixmap->rect=" + << destPtr->rect () + << ",destRect=" + << destRect + << ",src.rect=" + << src.rect () + << ")" + << endl; +#endif + + Q_ASSERT (destPtr); + + // You cannot copy more than what you have. + Q_ASSERT (destRect.width () <= src.width () && + destRect.height () <= src.height ()); + + QPainter painter(destPtr); + // destination shall be source only + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.drawImage(destRect.topLeft(), src, QRect(0, 0, destRect.width(), destRect.height())); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::setPixmapAt (QImage *destPtr, const QPoint &destAt, + const QImage &src) +{ + kpPixmapFX::setPixmapAt (destPtr, + QRect (destAt.x (), destAt.y (), + src.width (), src.height ()), + src); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::setPixmapAt (QImage *destPtr, int destX, int destY, + const QImage &src) +{ + kpPixmapFX::setPixmapAt (destPtr, QPoint (destX, destY), src); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::paintPixmapAt (QImage *destPtr, const QPoint &destAt, + const QImage &src) +{ + // draw image with SourceOver composition mode + QPainter painter(destPtr); + painter.drawImage(destAt, src); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::paintPixmapAt (QImage *destPtr, int destX, int destY, + const QImage &src) +{ + kpPixmapFX::paintPixmapAt(destPtr, QPoint (destX, destY), src); +} + +//--------------------------------------------------------------------- + +// public static +kpColor kpPixmapFX::getColorAtPixel (const QImage &img, const QPoint &at) +{ + if (!img.valid (at.x (), at.y ())) + return kpColor::Invalid; + + QRgb rgba = img.pixel(at); + return kpColor (rgba); +} + +//--------------------------------------------------------------------- + +// public static +kpColor kpPixmapFX::getColorAtPixel (const QImage &img, int x, int y) +{ + return kpPixmapFX::getColorAtPixel (img, QPoint (x, y)); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/pixmapfx/kpPixmapFX_Transforms.cpp b/kolourpaint/pixmapfx/kpPixmapFX_Transforms.cpp new file mode 100644 index 00000000..400f9f35 --- /dev/null +++ b/kolourpaint/pixmapfx/kpPixmapFX_Transforms.cpp @@ -0,0 +1,674 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_PIXMAP_FX 0 + + +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::resize (QImage *destPtr, int w, int h, + const kpColor &backgroundColor) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + kDebug () << "kpPixmapFX::resize()"; +#endif + + if (!destPtr) + return; + + const int oldWidth = destPtr->width (); + const int oldHeight = destPtr->height (); + + if (w == oldWidth && h == oldHeight) + return; + + QImage newImage (w, h, QImage::Format_ARGB32_Premultiplied); + + // Would have new undefined areas? + if (w > oldWidth || h > oldHeight) + newImage.fill (backgroundColor.toQRgb ()); + + // Copy over old pixmap. + QPainter painter(&newImage); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.drawImage(0, 0, *destPtr); + painter.end(); + + // Replace pixmap with new one. + *destPtr = newImage; +} + +//--------------------------------------------------------------------- + +// public static +QImage kpPixmapFX::resize (const QImage &image, int w, int h, + const kpColor &backgroundColor) +{ + QImage ret = image; + kpPixmapFX::resize (&ret, w, h, backgroundColor); + return ret; +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::scale (QImage *destPtr, int w, int h, bool pretty) +{ + if (!destPtr) + return; + + *destPtr = kpPixmapFX::scale (*destPtr, w, h, pretty); +} + +//--------------------------------------------------------------------- + +// public static +QImage kpPixmapFX::scale (const QImage &image, int w, int h, bool pretty) +{ +#if DEBUG_KP_PIXMAP_FX && 0 + kDebug () << "kpPixmapFX::scale(oldRect=" << image.rect () + << ",w=" << w + << ",h=" << h + << ",pretty=" << pretty + << ")" + << endl; +#endif + + if (w == image.width () && h == image.height ()) + return image; + + return image.scaled(w, h, Qt::IgnoreAspectRatio, + pretty ? Qt::SmoothTransformation : Qt::FastTransformation); +} + +//--------------------------------------------------------------------- + +// public static +const double kpPixmapFX::AngleInDegreesEpsilon = + KP_RADIANS_TO_DEGREES (atan (1.0 / 10000.0)) + / (2.0/*max error allowed*/ * 2.0/*for good measure*/); + + +static void MatrixDebug (const QString matrixName, const QMatrix &matrix, + int srcPixmapWidth = -1, int srcPixmapHeight = -1) +{ +#if DEBUG_KP_PIXMAP_FX + const int w = srcPixmapWidth, h = srcPixmapHeight; + + kDebug () << matrixName << "=" << matrix; + // Sometimes this precision lets us see unexpected rounding errors. + fprintf (stderr, "m11=%.24f m12=%.24f m21=%.24f m22=%.24f dx=%.24f dy=%.24f\n", + matrix.m11 (), matrix.m12 (), + matrix.m21 (), matrix.m22 (), + matrix.dx (), matrix.dy ()); + if (w > 0 && h > 0) + { + kDebug () << "(0,0) ->" << matrix.map (QPoint (0, 0)); + kDebug () << "(w-1,0) ->" << matrix.map (QPoint (w - 1, 0)); + kDebug () << "(0,h-1) ->" << matrix.map (QPoint (0, h - 1)); + kDebug () << "(w-1,h-1) ->" << matrix.map (QPoint (w - 1, h - 1)); + } + +#if 0 + QMatrix trueMatrix = QPixmap::trueMatrix (matrix, w, h); + kDebug () << matrixName << "trueMatrix=" << trueMatrix; + if (w > 0 && h > 0) + { + kDebug () << "(0,0) ->" << trueMatrix.map (QPoint (0, 0)); + kDebug () << "(w-1,0) ->" << trueMatrix.map (QPoint (w - 1, 0)); + kDebug () << "(0,h-1) ->" << trueMatrix.map (QPoint (0, h - 1)); + kDebug () << "(w-1,h-1) ->" << trueMatrix.map (QPoint (w - 1, h - 1)); + } +#endif + +#else + + Q_UNUSED (matrixName); + Q_UNUSED (matrix); + Q_UNUSED (srcPixmapWidth); + Q_UNUSED (srcPixmapHeight); + +#endif // DEBUG_KP_PIXMAP_FX +} + +//--------------------------------------------------------------------- + +// Theoretically, this should act the same as QPixmap::trueMatrix() but +// it doesn't. As an example, if you rotate tests/transforms.png by 90 +// degrees clockwise, this returns the correct of 26 but +// QPixmap::trueMatrix() returns 27. +// +// You should use the returned matrix to map points accurately (e.g. selection +// borders). For QPainter::drawPixmap()/drawImage() + setWorldMatrix() +// rendering accuracy, pass the returned matrix through QPixmap::trueMatrix() +// and use that. +// +// TODO: If you put the flipMatrix() of tests/transforms.png through this, +// the output is the same as QPixmap::trueMatrix(): is one off +// (dy=27 instead of 26). +// SYNC: I bet this is a Qt4 bug. +static QMatrix MatrixWithZeroOrigin (const QMatrix &matrix, int width, int height) +{ +#if DEBUG_KP_PIXMAP_FX + kDebug () << "matrixWithZeroOrigin(w=" << width << ",h=" << height << ")"; + kDebug () << "\tmatrix: m11=" << matrix.m11 () + << "m12=" << matrix.m12 () + << "m21=" << matrix.m21 () + << "m22=" << matrix.m22 () + << "dx=" << matrix.dx () + << "dy=" << matrix.dy (); +#endif + + QRect mappedRect = matrix.mapRect (QRect (0, 0, width, height)); +#if DEBUG_KP_PIXMAP_FX + kDebug () << "\tmappedRect=" << mappedRect; +#endif + + QMatrix translatedMatrix ( + matrix.m11 (), matrix.m12 (), + matrix.m21 (), matrix.m22 (), + matrix.dx () - mappedRect.left (), matrix.dy () - mappedRect.top ()); + +#if DEBUG_KP_PIXMAP_FX + kDebug () << "\treturning" << translatedMatrix; + kDebug () << "(0,0) ->" << translatedMatrix.map (QPoint (0, 0)); + kDebug () << "(w-1,0) ->" << translatedMatrix.map (QPoint (width - 1, 0)); + kDebug () << "(0,h-1) ->" << translatedMatrix.map (QPoint (0, height - 1)); + kDebug () << "(w-1,h-1) ->" << translatedMatrix.map (QPoint (width - 1, height - 1)); +#endif + + return translatedMatrix; +} + +//--------------------------------------------------------------------- + +static double TrueMatrixEpsilon = 0.000001; + +// An attempt to reverse tiny rounding errors introduced by QPixmap::trueMatrix() +// when skewing tests/transforms.png by 45% horizontally (with TransformPixmap() +// using a QPixmap painter, prior to the 2007-10-09 change -- did not test after +// the change). +// Unfortunately, this does not work enough to stop the rendering errors +// that follow. But it was worth a try and might still help us given the +// sometimes excessive aliasing QPainter::draw{Pixmap,Image}() gives us, when +// QPainter::SmoothPixmapTransform is disabled. +static double TrueMatrixFixInts (double x) +{ + if (fabs (x - qRound (x)) < TrueMatrixEpsilon) + return qRound (x); + else + return x; +} + +//--------------------------------------------------------------------- + +static QMatrix TrueMatrix (const QMatrix &matrix, int srcPixmapWidth, int srcPixmapHeight) +{ + ::MatrixDebug ("TrueMatrix(): org", matrix); + + const QMatrix truMat = QPixmap::trueMatrix (matrix, srcPixmapWidth, srcPixmapHeight); + ::MatrixDebug ("TrueMatrix(): passed through QPixmap::trueMatrix()", truMat); + + const QMatrix retMat ( + ::TrueMatrixFixInts (truMat.m11 ()), + ::TrueMatrixFixInts (truMat.m12 ()), + ::TrueMatrixFixInts (truMat.m21 ()), + ::TrueMatrixFixInts (truMat.m22 ()), + ::TrueMatrixFixInts (truMat.dx ()), + ::TrueMatrixFixInts (truMat.dy ())); + ::MatrixDebug ("TrueMatrix(): fixed ints", retMat); + + return retMat; +} + +//--------------------------------------------------------------------- + +// Like QPixmap::transformed() but fills new areas with +// (unless is invalid) and works around internal QMatrix +// floating point -> integer oddities, that would otherwise give fatally +// incorrect results. If you don't believe me on this latter point, compare +// QPixmap::transformed() to us using a flip matrix or a rotate-by-multiple-of-90 +// matrix on tests/transforms.png -- QPixmap::transformed()'s output is 1 +// pixel too high or low depending on whether the matrix is passed through +// QPixmap::trueMatrix(). +// +// Use and to specify the intended output size +// of the pixmap. -1 if don't care. +static QImage TransformPixmap (const QImage &pm, const QMatrix &transformMatrix_, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + QMatrix transformMatrix = transformMatrix_; + +#if DEBUG_KP_PIXMAP_FX && 1 + kDebug () << "kppixmapfx.cpp: TransformPixmap(pm.size=" << pm.size () + << ",targetWidth=" << targetWidth + << ",targetHeight=" << targetHeight + << ")" + << endl; +#endif + + QRect newRect = transformMatrix.mapRect (pm.rect ()); +#if DEBUG_KP_PIXMAP_FX && 1 + kDebug () << "\tmappedRect=" << newRect; + +#endif + + QMatrix scaleMatrix; + if (targetWidth > 0 && targetWidth != newRect.width ()) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kDebug () << "\tadjusting for targetWidth"; + #endif + scaleMatrix.scale (double (targetWidth) / double (newRect.width ()), 1); + } + + if (targetHeight > 0 && targetHeight != newRect.height ()) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kDebug () << "\tadjusting for targetHeight"; + #endif + scaleMatrix.scale (1, double (targetHeight) / double (newRect.height ())); + } + + if (!scaleMatrix.isIdentity ()) + { + #if DEBUG_KP_PIXMAP_FX && 1 + // TODO: What is going on here??? Why isn't matrix * working properly? + QMatrix wrongMatrix = transformMatrix * scaleMatrix; + QMatrix oldHat = transformMatrix; + if (targetWidth > 0 && targetWidth != newRect.width ()) + oldHat.scale (double (targetWidth) / double (newRect.width ()), 1); + if (targetHeight > 0 && targetHeight != newRect.height ()) + oldHat.scale (1, double (targetHeight) / double (newRect.height ())); + QMatrix altHat = transformMatrix; + altHat.scale ((targetWidth > 0 && targetWidth != newRect.width ()) ? double (targetWidth) / double (newRect.width ()) : 1, + (targetHeight > 0 && targetHeight != newRect.height ()) ? double (targetHeight) / double (newRect.height ()) : 1); + QMatrix correctMatrix = scaleMatrix * transformMatrix; + + kDebug () << "\tsupposedlyWrongMatrix: m11=" << wrongMatrix.m11 () // <<<---- this is the correct matrix??? + << " m12=" << wrongMatrix.m12 () + << " m21=" << wrongMatrix.m21 () + << " m22=" << wrongMatrix.m22 () + << " dx=" << wrongMatrix.dx () + << " dy=" << wrongMatrix.dy () + << " rect=" << wrongMatrix.mapRect (pm.rect ()) + << endl + << "\ti_used_to_use_thisMatrix: m11=" << oldHat.m11 () + << " m12=" << oldHat.m12 () + << " m21=" << oldHat.m21 () + << " m22=" << oldHat.m22 () + << " dx=" << oldHat.dx () + << " dy=" << oldHat.dy () + << " rect=" << oldHat.mapRect (pm.rect ()) + << endl + << "\tabove but scaled at the same time: m11=" << altHat.m11 () + << " m12=" << altHat.m12 () + << " m21=" << altHat.m21 () + << " m22=" << altHat.m22 () + << " dx=" << altHat.dx () + << " dy=" << altHat.dy () + << " rect=" << altHat.mapRect (pm.rect ()) + << endl + << "\tsupposedlyCorrectMatrix: m11=" << correctMatrix.m11 () + << " m12=" << correctMatrix.m12 () + << " m21=" << correctMatrix.m21 () + << " m22=" << correctMatrix.m22 () + << " dx=" << correctMatrix.dx () + << " dy=" << correctMatrix.dy () + << " rect=" << correctMatrix.mapRect (pm.rect ()) + << endl; + #endif + + transformMatrix = transformMatrix * scaleMatrix; + + newRect = transformMatrix.mapRect (pm.rect ()); + #if DEBUG_KP_PIXMAP_FX && 1 + kDebug () << "\tnewRect after targetWidth,targetHeight adjust=" << newRect; + #endif + } + + + ::MatrixDebug ("TransformPixmap(): before trueMatrix", transformMatrix, + pm.width (), pm.height ()); +#if DEBUG_KP_PIXMAP_FX && 1 + QMatrix oldMatrix = transformMatrix; +#endif + + // Translate the matrix to account for Qt rounding errors, + // so that flipping (if it used this method) and rotating by a multiple + // of 90 degrees actually work as expected (try tests/transforms.png). + // + // SYNC: This was not required with Qt3 so we are actually working + // around a Qt4 bug/feature. + // + // COMPAT: Qt4's rendering with a matrix enabled is low quality anyway + // but does this reduce quality even further? + // + // With or without it, skews by 45 degrees with the QImage + // painter below look bad (with it, you get an extra transparent + // line on the right; without, you get only about 1/4 of a source + // line on the left). In Qt3, with TrueMatrix(), the source + // image is translated 1 pixel off the destination image. + // + // Also, if you skew a rectangular selection, the skewed selection + // border does not line up with the skewed image data. + // TODO: do we need to pass through this new matrix? + transformMatrix = ::TrueMatrix (transformMatrix, + pm.width (), pm.height ()); + +#if DEBUG_KP_PIXMAP_FX && 1 + kDebug () << "trueMatrix changed matrix?" << (oldMatrix == transformMatrix); +#endif + ::MatrixDebug ("TransformPixmap(): after trueMatrix", transformMatrix, + pm.width (), pm.height ()); + + + QImage newQImage (targetWidth > 0 ? targetWidth : newRect.width (), + targetHeight > 0 ? targetHeight : newRect.height (), + QImage::Format_ARGB32_Premultiplied); + + if ((targetWidth > 0 && targetWidth != newRect.width ()) || + (targetHeight > 0 && targetHeight != newRect.height ())) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kDebug () << "kppixmapfx.cpp: TransformPixmap(pm.size=" << pm.size () + << ",targetWidth=" << targetWidth + << ",targetHeight=" << targetHeight + << ") newRect=" << newRect + << " (you are a victim of rounding error)" + << endl; + #endif + } + + +#if DEBUG_KP_PIXMAP_FX && 0 + kDebug () << "\ttranslate top=" << painter.xForm (QPoint (0, 0)); + kDebug () << "\tmatrix: m11=" << painter.worldMatrix ().m11 () + << " m12=" << painter.worldMatrix ().m12 () + << " m21=" << painter.worldMatrix ().m21 () + << " m22=" << painter.worldMatrix ().m22 () + << " dx=" << painter.worldMatrix ().dx () + << " dy=" << painter.worldMatrix ().dy () + << endl; +#endif + + + // Note: Do _not_ use "p.setRenderHints (QPainter::SmoothPixmapTransform);" + // as the user does not want their image to get blurier every + // time they e.g. rotate it (especially important for multiples + // of 90 degrees but also true for every other angle). Being a + // pixel-based program, we generally like to preserve RGB values + // and avoid unnecessary blurs -- in the worst case, we'd rather + // drop pixels, than blur. + QPainter p (&newQImage); + { + // Make sure transparent pixels are drawn into the destination image. + p.setCompositionMode (QPainter::CompositionMode_Source); + + // Fill the entire new image with the background color. + if (backgroundColor.isValid ()) + { + p.fillRect (newQImage.rect (), backgroundColor.toQColor ()); + } + + p.setMatrix (transformMatrix); + p.drawImage (QPoint (0, 0), pm); + } + p.end (); + +#if DEBUG_KP_PIXMAP_FX && 1 + kDebug () << "Done" << endl << endl; +#endif + + return newQImage; +} + +//--------------------------------------------------------------------- + +// public static +QMatrix kpPixmapFX::skewMatrix (int width, int height, double hangle, double vangle) +{ + if (fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon) + { + return QMatrix (); + } + + + /* Diagram for completeness :) + * + * |---------- w ----------| + * (0,0) + * _ _______________________ (w,0) + * | |\~_ va | + * | | \ ~_ | + * | |ha\ ~__ | + * | \ ~__ | dy + * h | \ ~___ | + * | \ ~___ | + * | | \ ~___| (w,w*tan(va)=dy) + * | | \ * \ + * _ |________\________|_____|\ vertical shear factor + * (0,h) dx ^~_ | \ | + * | ~_ \________\________ General Point (x,y) V + * | ~__ \ Skewed Point (x + y*tan(ha),y + x*tan(va)) + * (h*tan(ha)=dx,h) ~__ \ ^ + * ~___ \ | + * ~___ \ horizontal shear factor + * Key: ~___\ + * ha = hangle (w + h*tan(ha)=w+dx,h + w*tan(va)=w+dy) + * va = vangle + * + * Skewing really just twists a rectangle into a parallelogram. + * + */ + + //QMatrix matrix (1, tan (KP_DEGREES_TO_RADIANS (vangle)), tan (KP_DEGREES_TO_RADIANS (hangle)), 1, 0, 0); + // I think this is clearer than above :) + QMatrix matrix; + matrix.shear (tan (KP_DEGREES_TO_RADIANS (hangle)), + tan (KP_DEGREES_TO_RADIANS (vangle))); + + return ::MatrixWithZeroOrigin (matrix, width, height); +} + +//--------------------------------------------------------------------- + +// public static +QMatrix kpPixmapFX::skewMatrix (const QImage &pixmap, double hangle, double vangle) +{ + return kpPixmapFX::skewMatrix (pixmap.width (), pixmap.height (), hangle, vangle); +} + +//--------------------------------------------------------------------- + + +// public static +void kpPixmapFX::skew (QImage *destPtr, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + if (!destPtr) + return; + + *destPtr = kpPixmapFX::skew (*destPtr, hangle, vangle, + backgroundColor, + targetWidth, targetHeight); +} + +//--------------------------------------------------------------------- + +// public static +QImage kpPixmapFX::skew (const QImage &pm, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ +#if DEBUG_KP_PIXMAP_FX + kDebug () << "kpPixmapFX::skew() pm.width=" << pm.width () + << " pm.height=" << pm.height () + << " hangle=" << hangle + << " vangle=" << vangle + << " targetWidth=" << targetWidth + << " targetHeight=" << targetHeight + << endl; +#endif + + if (fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/) + { + return pm; + } + + if (fabs (hangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon || + fabs (vangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon) + { + kError () << "kpPixmapFX::skew() passed hangle and/or vangle out of range (-90 < x < 90)" << endl; + return pm; + } + + + QMatrix matrix = skewMatrix (pm, hangle, vangle); + + return ::TransformPixmap (pm, matrix, backgroundColor, targetWidth, targetHeight); +} + +//--------------------------------------------------------------------- + + +// public static +QMatrix kpPixmapFX::rotateMatrix (int width, int height, double angle) +{ + if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon) + { + return QMatrix (); + } + + QMatrix matrix; + matrix.translate (width / 2, height / 2); + matrix.rotate (angle); + + return ::MatrixWithZeroOrigin (matrix, width, height); +} + +//--------------------------------------------------------------------- + +// public static +QMatrix kpPixmapFX::rotateMatrix (const QImage &pixmap, double angle) +{ + return kpPixmapFX::rotateMatrix (pixmap.width (), pixmap.height (), angle); +} + +//--------------------------------------------------------------------- + + +// public static +bool kpPixmapFX::isLosslessRotation (double angle) +{ + const double angleIn = angle; + + // Reflect angle into positive if negative + if (angle < 0) + angle = -angle; + + // Remove multiples of 90 to make sure 0 <= angle <= 90 + angle -= ((int) angle) / 90 * 90; + + // "Impossible" situation? + if (angle < 0 || angle > 90) + { + kError () << "kpPixmapFX::isLosslessRotation(" << angleIn + << ") result=" << angle + << endl; + return false; // better safe than sorry + } + + const bool ret = (angle < kpPixmapFX::AngleInDegreesEpsilon || + 90 - angle < kpPixmapFX::AngleInDegreesEpsilon); +#if DEBUG_KP_PIXMAP_FX + kDebug () << "kpPixmapFX::isLosslessRotation(" << angleIn << ")" + << " residual angle=" << angle + << " returning " << ret + << endl; +#endif + return ret; +} + +//--------------------------------------------------------------------- + + +// public static +void kpPixmapFX::rotate (QImage *destPtr, double angle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + if (!destPtr) + return; + + *destPtr = kpPixmapFX::rotate (*destPtr, angle, + backgroundColor, + targetWidth, targetHeight); +} + +//--------------------------------------------------------------------- + +// public static +QImage kpPixmapFX::rotate (const QImage &pm, double angle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/) + { + return pm; + } + + + QMatrix matrix = rotateMatrix (pm, angle); + + return ::TransformPixmap (pm, matrix, backgroundColor, targetWidth, targetHeight); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/stamp b/kolourpaint/stamp new file mode 100644 index 00000000..0b48b482 --- /dev/null +++ b/kolourpaint/stamp @@ -0,0 +1,4 @@ +Commit this file to make an arbitrary comment in SVN history. + +Increase this number to give you something to commit: 2 + diff --git a/kolourpaint/tests/45deg_line.png b/kolourpaint/tests/45deg_line.png new file mode 100644 index 0000000000000000000000000000000000000000..cab600cbdaa38aefe5f2f372fd1e51910ac1e9b9 GIT binary patch literal 94 zcmeAS@N?(olHy`uVBq!ia0vp^96-#@$P6U;r_YlGQfvV}A+G-!82Ko3tNOMs@R`hP`I=*$^7NTlG{Uko6j){_0?*mC;k z?4Z#~Z6t3@2OE5Z@5NFVw-S*xrc+a}`R0{SatjyJul kFPI$#e8}a_&xUOK=D7Un#QjKN4m-jA=!5@{G@DLc0k1A!DzfKP}kLo)+IHv_|eAYgct z_BD-xftkzG#WAGf*4x{LeYXunS|0X#g*ST1J5Ilmw$RD_qN*5E1%s?3TSUO+CmcKd zSFw0ZRoXrEV1??Z+yxg`KYJfzD7FTB5j_{f7OI^s%eM<@m3+<0IiQcQ6i6Le$f!iP~+|LHJQwKRO#%K zuB6U1qmZ@>K2}o}&+O3^^A^iNEZ-CMXIfs;-RLSHPNT)iqcGz6{dwgY*)Hu zCTnM@_MHrnKVdFD`|@Qk~Cvk&OVnq?+V)iVYmW>YJK5)!2ny_$MDdtWr5+jhxyCkQR_A|Kt~mn<{1irLKacRQM;WfTQi^ z60w6v`Vx}b{mYd1tTyj%DgT;gT>Jlt{?kZ{@`Ky*^X<3YpQNnzH@)qM@Hff*`~NQ7 z^ZwLLXQssW*QK0p_M5kT`2P5_z(2{(w*sFc7kQ_i=q=s1TA%0PJm0rm&MVS)v>y_9 z72BRxe!^?t>kZ~A8{f|_xvgmb^1FZA+X?soSUcWU-0!zv_{*nR;fMAupIX0mUZ>j5 zj~~zfnVKT^zxrM>g)z4*}Q$iB}rvhyI literal 0 HcmV?d00001 diff --git a/kolourpaint/tests/freeform-selection.png b/kolourpaint/tests/freeform-selection.png new file mode 100644 index 0000000000000000000000000000000000000000..d630279bb8407edada9beeba6cf0cd119afdedc7 GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^20*ON!VDyL|GzdJNO2Z;L>4nJ@ErkR#;MwT(m+9> z0G|+7hK2@){|pU43WWdvXZWA7-PZvqTkh%N7*cWT?SzAz4F)`}#!VN}8gl}cGIPAV zd*PjZcc;P@p+KA8`6o{+e@HAe6255cp%mmk;Yb?~Ln6!01(ICr*w(i<&bq+-q{*@O znDiAnleBeNF7px$7BQXoD0jQI_piX|ggKk<{hs=QU0&havG%f)r}^tXaJ{)wG2!ly Tm-E|zPGInK^>bP0l+XkKJ(^Zp literal 0 HcmV?d00001 diff --git a/kolourpaint/tests/kolourcircles.png b/kolourpaint/tests/kolourcircles.png new file mode 100644 index 0000000000000000000000000000000000000000..9f544453d598cada3d4108f2c78f42d9cac9b631 GIT binary patch literal 4889 zcmXX~2{_c<_n!?jljUo&#gv&bvV^jQUNRd-C~IY_*YL{HPY8v?cn5=M3=t|BX(7s% z5Q>qpM_IE(qwFGOOMcVu|9|dt@45GW&bjA4&$-W8Zldi;3o)cT5(EN?SyCzXAP|@( z=o&DnU`H`~V?{tTZLJ;61QZ0?1;xY&2GH(qj9~Vs@BV3j0Krzw?*GW{?yl>xujfG^ z)FVp@(ediY;>X4Sg|-(hlO<_UnkT}@uKfGP=^&iuBw4S+oQ%qW*W!C5X{Eyb~DSb&Q60nxzahit$Yw zk1_&EqUr9k0B*AoOIa-@Gx>vYXj66(oz zgUYfYy8YVu`?XX8(DtGAZ4^wd_yi#YUvN~og=lrFYF<&%T9`qN@j5fN-1@cHIrHDm zJQ&4d_0myp9QNx%y4ap+MNtnNGt~=?e~^#O3%EaHI7N+_p_&t1+Ti_L{I_DYAgk5aP6VKLo0_aEQ8J6RM!hO)?1lx%Y$Y8+0^-k!J&9OTF1 zB?(&}`lh$8z`5me^8qKJbhmC?;<69#{F^u1E z3#iA8fbp8zX$cYD&!)V6sqthY%W+Hc5IEnhjlzxfNnl_sCqlKg8QNgU7PffRJXK@% zZ#K|wRxO2Z&Bs!giYa9<%J`G#xPqjA=8buEMI|UYoY~;e+tMd4Mx>#ddjDB(R?2qM zD4Q>GBgkR)nu_~d7{;ht<_cvymLgjESXZ2GL9^|(6y$iv_QXCY5hpJ#JEJfwLkye2 z76`6YBm8rnY@*H9+DCKgWU`e7%?UQ_grK@gi@^l;mSU8tjE0~kDW|qR+k@Lw&-``$ zJ$)uBdd<#`JVK+-xDTfJz&*{NV2)BY+_#ub|DWMmQhQ*$HEO)nV|96WUFrWrpx(3_ z2{X~Yd!b;REL8DZQb^56awj2YQKC3>2PnT&=iDiK)12mjUHm|}So}CTb}qsl8YFJ2 zm09@mb05oYy8Ts$U(T1Wr+)ZJi)&(%r)jghnQZHX%KkMci9|ie&K*%MxHh0h@${au zrKw}M4*i{lACe#5l)045$@8w|@@Q3Voh=CCsrK%nHG=%$BPS}Jn)BFYk8I+${`n*- zckG$!;F`)0r+Os)Lotgw6p9PNDKeF2A6;27X79Dk9eny$sPEF%&&p_*vD6KrAm^76 zo1vDW9eBz~{m_1YMT*0AUQVft9j+rhA#nb<>{t%Ow>Ys2nS$K^UA!~_%bxDLdxofc zB^g&6rxg2GTawve5=7sSBcT^+6_;m9;~P8=iCBgfr=W*fFLE|?I@Hklu9+jxOjx#B zim^~w-}5tWihGx<7+%*ZsNAogBApWa(x?&$(>g^It3q884wxJ*an?#reW!P>atXV@ zObD5to4a+pBA0?eFtr9-K6ztToEhW3-pC0XK7>!VXXDbogF(oNA)6EEw37#-L%jiTDAK7Ni|yL1ayAD=u6|D*bb+c z&2vkio0X3XZ90NI%wj|pa?5d1_^L+UGWpeOqtWwYrH}T8i@*{XT!-GLZq(%&no;Ha zcM+A_pYd1&LZBCUM6!!ETyuA#i8Hg5i|ULTo}1)7z~A&hrO0GvR^-+i`&~&QP$!32dB4A@>t~@%+Bf;g>l)PU z>XhLh4`&BP)zA-z;~3SdcN!jZ+189}^@ew1en7o0H07rMUTxUp92h-K`F&s-4m{e3 zunqcMm^Y3h=PZc@E)t5}nbF&IGd8JZF27hTd*PyY2vHNR&I_o4DCP9LxY9F&-?^M9p+aB* zB={r42R&0HqlQ<%UmOYw|A6Lh10&o}idI_)e0s}o8-$rS`XbWz*}u(-uRR89d_z)3 z*oH}aNH60I(3Z?BTTPYW-!CCeh4m^mO4g)+d%~oeS4v_;6tW-WJ9n2-8@Yac(D`RD zkkh}st(4l6I;mWda7odAV*C?XQsPb8P?+G+Oi6cRF~lYlPe{3F|48W;&G|qdWTl=B z;Kh34uZKyLeCz&jN_Ch-yr=_9h+p$`8{W%W+Ivsr*-L?ij=@?xZq~X`UYhk^68W<6G zW(66X{#Zr|jAb2ph@teM@Aw32__7IT(_U{PR?35U7{(drbdbKRD_N9tfY z4s$W$a2g9^Nw}KbUw_+`nivl|p$}hORq~wEL3xMCHR;xEOPfdAIS}*;Qy}?n$Z`Se zZDh2oC}R}PoY+1=AYrI529=$-VIHTVzCMU&-Um(cXx$=2jUDNW%kXSQhrn`DkzQt^ z&}lZSO9L@m-5h>;Wy~@P)Wn9vGvFl54FpxriWJd8+urYRoGsDQYNLy68dt%A}Pb z)V3BzVN)gC=QQxNnF5$PiNlhF;9W@_h1iOG)`-G^On)7Qb{)Ly#0_7bfsjXpS&9;r zqo#MrP76FN8W$2fvU{eBW4e#uUzMG}3${tnn-pVLy!798AdmPPcppwjIRLexMMX{q zZryuXxoWK9;VqdHQi%*Xph_tY>u+G$ejZic3p8AFK+&RFx*TiHDaLmSlTWu%x(|{% ziH4?KsA&lM9Q)%f#>ReGmLHz)RE_JzCaE#OX@gKoZAF3)SS!pY*qfep0`g`&Z4gL*TDmPZzRQbXdMCEldOOEq<+R1o9JD( zp0S5oiEkD!SycQMibA*22}XI4e3yW0^7{POfCsRR264S9LeC~UX#HTsGt890^fhs(SH>@T$WpRke zW#wlE(7nFDlxXe-Kf1^Bq!T`YR%=uUE7_v48_I}@Z*$l8D+JelcY1sH{K9`IPpq+5 z32QYWH*%!aB;J7ey3?^VGIp|HY{cTSHN1u6nvuNuz5cFvab|RlwEnyt>*FO!MwH0v zWYi(G&_$^OWyoH%Y`Lb)*fB?~?QfG_*VT}xuhhM1&DSe7X3)Svo+6m#Q(t2H7UNsL z-OWJ(^|K+#!QjomFPnPPhK*3h^Iu&zNPUZ(f_I~rwaVf zW7mv5%*M3D!TTB6F%+RvcMSsF#6sF5ZeVvK;OO(-u}g2E=|JExz|?hZb4 zH^5E{BTTUMIUP>5zArcdK9;2Rh~q4T;W!I!y|dX%o(h;%T5#XT43HU{qCpq|mdGx3 z3Cm+M1F}$3b)VGUt{hMes>?p@*PDO-fb@)n(c?&?M{Wd8K$}vhIcZJW&LBDT!o+z< zzH3fXk#2uFS%WB0iI zTJj9Ezh)%A&r;q;F*3(JWu|h&uqtHim5HuUK$;VP8 z$OAxFaSTlE`60RG*c(F3ymbgJsSvxB+X&&b|JWqUDTtP3eBR!{^Q;Ms2O_qm>wR{@ z!6tj_wAx#eMamXAXOWyN{s{tup&1RP7_KFu70ba>?SQ$_Y3G9z56sy=8 zPQOZ(i|oys$8g6-ZQ_1iMfZiVh7FlJ3?K=dI0YQypn6&U9kI{nE?3Xr2+fdPRuT@9 zV!Xn!-I!g~fNH7p3)iC8?>yHFPHATHBc%8+xR9=H`H=!gKXoDE8l3M3^U{@I4WZ1v zv=5MQD{KG(?57U>fUoYHym3|%yt$&vSC5?`*t7mRgv?gKJd5S(U~|*;_}jVHm|}E* zCxOwhWO`+al!eO&8*tqEZK-f2FcyV5#?s?2*+|0aqtM~=C|pgHi6N__pRPTzVX%3K zgN3ciF`}YPyxzZOb^oo5fkE(dnF>%`vNC3Px=5G+rw~fL$=IV=tYtHdDGI2KgE2!o zPrYaT)p02^zLQ)b#UF`b(h$s{^)vtiMqSy{wn(tpTIKw}6~0lo-vF^`Kb6@uWaE>=@(OwsmJ7T1()GlbJYc4M_UJ^6S!2s>2!kMPD~ zl(7uaSSb089I|&B^4FjEzA*6f%RtlpcR($r#;Zb*9DjI~grRXuFWf4&2Ow>6Lq5DZ z!x#ex3=ZfoDPCbgyTS`{l(8`OzPA3hv%LJVqmoR4AE7Jv)|CpPF%B5kR6ux`tu?&Y z5W})Y15)Df=aiBNQhX75##y#Q4Q^puk7q*Xhxz0AHbtf!XCw(DR2!<6qknPY5O}iN zZ{}=3wN_|FNKJzk+@Z)d3!2k%+b=UdV{V`@AXDp%6R6KSY~E4ND#HqtFx{4G5^SL2HeTkBvo0ukD#KoCU8@pr9My> z*4~c*1m)B1YllUAL-7QPpm+~61=$~GzrR6{OBe9o7BNHg(kaul{yhY4MO{2aJWBfs zlE96`A|~ZIt?&mR{I1hjp=##ndXcKY6MMn&%etWuH6biYvqnn1YS{_Q+}JolP#yRd zEJ@u5PxEPe63C2tCWv3_9sk@UjSsv3d{ws10koD~f{(p@V^*)hpjPswEPkJzq%bK~ zCxdRlAC#mDlEds~#WL3~_by-VlZg+6CEIFQw7%(eMUn6;w-*WHk``Ya<1i5u`9lL9 zM}k|Il8I{#4$qGqF~7Lgi^(`-@)>KG3iVaYlG2i4&3Z!YpP%jAg^CZhBZrHv>3_bs NEX__*N=bjm{uhr&t(yP< literal 0 HcmV?d00001 diff --git a/kolourpaint/tests/paste_in_new_window.png b/kolourpaint/tests/paste_in_new_window.png new file mode 100644 index 0000000000000000000000000000000000000000..37f38ef0bb59eb5391c3caeab0378ec016fa5496 GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58JL)Xl%kBoS0Ke%;1OBOz`%C|gc+x5^GSmQ z1AIbUfiw{O|Np=F?)ghV7Gp_}UoeBivm0qZPKT$9V@SoVw>LNPHYfJ ze)jB39<@`WCVc+OyRlOAMVEH^klI14d+IQL{OC#nRH$J)SsPEPaEZLLjr PLCQT{{an^LB{Ts55xq>Q literal 0 HcmV?d00001 diff --git a/kolourpaint/tests/paste_in_new_window.txt b/kolourpaint/tests/paste_in_new_window.txt new file mode 100644 index 00000000..ef26b369 --- /dev/null +++ b/kolourpaint/tests/paste_in_new_window.txt @@ -0,0 +1,15 @@ +paste_in_new_window.png consists of white lines on black ("Hermann illusion") +with a transparent rectangle in the middle. + +Use this to test "Edit / Paste in New Window". + +Prior to 2007-10-07, a troublesome sequence of steps with this image was: + +1. Set the selection transparency mode to Transparent +2. Select All (Ctrl+A) +3. Copy (Ctrl+C) +4. Paste in New Window (Ctrl+Shift+V) + +The white lines became transparent. + +This testcase was originally by Thurston Dang. diff --git a/kolourpaint/tests/rotate.png b/kolourpaint/tests/rotate.png new file mode 100644 index 0000000000000000000000000000000000000000..d54e2cfb437ed2a1bc62fc78e37e2e695ec1d3de GIT binary patch literal 79 zcmeAS@N?(olHy`uVBq!ia0vp^OhC-c0V2&hJg)&MF;5rA5Q(YDzs?`HaOS`P7A1!e c-VSaCwRz0k%VtWs097z}y85}Sb4q9e0FN>gasU7T literal 0 HcmV?d00001 diff --git a/kolourpaint/tests/selections.txt b/kolourpaint/tests/selections.txt new file mode 100644 index 00000000..4b700073 --- /dev/null +++ b/kolourpaint/tests/selections.txt @@ -0,0 +1,12 @@ +An obscure testcase with the selection tool is: + +1. RMB-drag to create a selection and popup a menu. +2. LMB-drag outside of the menu, to close it. +3. While still holding down the LMB, hold down the RMB and continue + moving the mouse. +4. Let go of the LMB and continue moving the mouse. + +etc. + +KolourPaint should not crash or anything like that. + diff --git a/kolourpaint/tests/small16x16.png b/kolourpaint/tests/small16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..7eb1bc2fa09de2b7abb0a07aed946a4c78aa24ee GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9>wgA@|Ns9x%9;oQMRYw~ z978x{Sq~aAGAM8`9L#qT`R(sw!1PYzy=<4!LGN7|JB=DUTU%Z8&N*G=U+E$fTyQcK QsE>ic)78&qol`;+05w7&K>z>% literal 0 HcmV?d00001 diff --git a/kolourpaint/tests/tool_fill_xlimit.png b/kolourpaint/tests/tool_fill_xlimit.png new file mode 100644 index 0000000000000000000000000000000000000000..97eb0e11c9c3c8d395effe02efb20b5e6ad170e7 GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#=`Bp4pvH+~MJ>^xl@Ln>|^z2L~jpupjBvHZXH zbuG4K3qqebef%7F(c)Mt5P~- Rj#z_C^mO%eS?83{1OQ0%DCz(J literal 0 HcmV?d00001 diff --git a/kolourpaint/tests/transforms-rotate-me-90-clockwise.png b/kolourpaint/tests/transforms-rotate-me-90-clockwise.png new file mode 100644 index 0000000000000000000000000000000000000000..3830865de8837ea197f539446b078b5527058907 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^(m<@i!VDz;ip)s`Qk(@Ik;M!Q+?^oIXnykaTA-j} zfKP}kkY-?LU|{$UgbfT04GusI;`|4aAnO0pGi#0ml`xhB`2{mLJiCzw1>|niyubv=lvNW@e#s#tEle zCucWI&+f^ZVt7I6$^{2*?L~(o1sIR08Hi{qHgYnk@u}guOX|rw6TFiGvlNM8yT6IjUz5_c}{3#4AsnR>fXq? t?ogVbhi2R474iyMnGy=kiE3gD3{N+R*tfGxOadCr;OXk;vd$@?2>?3lKPvzL literal 0 HcmV?d00001 diff --git a/kolourpaint/tests/transparent.png b/kolourpaint/tests/transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..699536654869f12098fa2759ead39427317f768f GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_>3?$zOPHh5GLIFM@uK)l4XJGgbh9CmSldaxf z50qsr3GxeOaCmkj4ahO{ba4!^IGrqzbbv2JQl#L7fng6*R|7YX#e&lo2?lPdZEZa7 lEM!@kxeBHy`B|h)VSMkwx6*CKWgDPo22WQ%mvv4FO#mqdEiM26 literal 0 HcmV?d00001 diff --git a/kolourpaint/tests/transparent_selection.png b/kolourpaint/tests/transparent_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..467ca16f3453b78302c57b172bec5eb251668a49 GIT binary patch literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^tw6k!i5W;%^N3UeDb50q$YKTtZXpn6ymYtj4@fY; zC&ZQEKM4N+&#)=9y9Fp%@9E+gQgQ3;mED333L-9nvp1MPSOTwMNVvfek0@|V{; R5D0V%gQu&X%Q~loCIHFKQCa{1 literal 0 HcmV?d00001 diff --git a/kolourpaint/tools/flow/kpToolBrush.cpp b/kolourpaint/tools/flow/kpToolBrush.cpp new file mode 100644 index 00000000..8ae79b55 --- /dev/null +++ b/kolourpaint/tools/flow/kpToolBrush.cpp @@ -0,0 +1,57 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include + +//--------------------------------------------------------------------- + +kpToolBrush::kpToolBrush (kpToolEnvironment *environ, QObject *parent) + : kpToolFlowPixmapBase (i18n ("Brush"), + i18n ("Draw using brushes of different shapes and sizes"), + Qt::Key_B, + environ, parent, "tool_brush") +{ +} + +//--------------------------------------------------------------------- + +// protected virtual [base kpToolFlowBase] +QString kpToolBrush::haventBegunDrawUserMessage () const +{ + return i18n ("Click to draw dots or drag to draw strokes."); +} + +//--------------------------------------------------------------------- + +// See the our corresponding .h for brush selection. + +// Logic is in kpToolFlowPixmapBase. + + +#include diff --git a/kolourpaint/tools/flow/kpToolBrush.h b/kolourpaint/tools/flow/kpToolBrush.h new file mode 100644 index 00000000..eedfbc81 --- /dev/null +++ b/kolourpaint/tools/flow/kpToolBrush.h @@ -0,0 +1,50 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_BRUSH_H +#define KP_TOOL_BRUSH_H + + +#include + + +// Brush = draws pixmaps, "interpolates" by "sweeping" pixmaps along a line (interesting brushes) +class kpToolBrush : public kpToolFlowPixmapBase +{ +Q_OBJECT + +public: + kpToolBrush (kpToolEnvironment *environ, QObject *parent); + +protected: + virtual QString haventBegunDrawUserMessage () const; + virtual bool haveDiverseBrushes () const { return true; } +}; + + +#endif // KP_TOOL_BRUSH_H diff --git a/kolourpaint/tools/flow/kpToolColorEraser.cpp b/kolourpaint/tools/flow/kpToolColorEraser.cpp new file mode 100644 index 00000000..5b5280d7 --- /dev/null +++ b/kolourpaint/tools/flow/kpToolColorEraser.cpp @@ -0,0 +1,163 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_COLOR_ERASER 0 + + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +kpToolColorEraser::kpToolColorEraser (kpToolEnvironment *environ, QObject *parent) + : kpToolFlowBase (i18n ("Color Eraser"), + i18n ("Replaces pixels of the foreground color with the background color"), + Qt::Key_O, + environ, parent, + "tool_color_eraser") +{ +} + +kpToolColorEraser::~kpToolColorEraser () +{ +} + + +// public virtual [base kpTool] +void kpToolColorEraser::globalDraw () +{ +#if DEBUG_KP_TOOL_COLOR_ERASER + kDebug () << "kpToolColorEraser::globalDraw()"; +#endif + if (!drawShouldProceed (QPoint ()/*unused*/, QPoint ()/*unused*/, QRect ()/*unused*/)) + return; + + QApplication::setOverrideCursor (Qt::WaitCursor); + + environ ()->flashColorSimilarityToolBarItem (); + + kpToolFlowCommand *cmd = new kpToolFlowCommand ( + i18n ("Color Eraser"), environ ()->commandEnvironment ()); + + const QRect dirtyRect = kpPainter::washRect (document ()->imagePointer (), + 0, 0, document ()->width (), document ()->height (), + backgroundColor ()/*color to draw in*/, + foregroundColor ()/*color to replace*/, + processedColorSimilarity ()); + + if (!dirtyRect.isEmpty ()) + { + document ()->slotContentsChanged (dirtyRect); + + + cmd->updateBoundingRect (dirtyRect); + cmd->finalize (); + + commandHistory ()->addCommand (cmd, false /* don't exec */); + + // don't delete - it's up to the commandHistory + cmd = 0; + } + else + { + #if DEBUG_KP_TOOL_COLOR_ERASER + kDebug () << "\tisNOP"; + #endif + delete cmd; + cmd = 0; + } + + QApplication::restoreOverrideCursor (); +} + +QString kpToolColorEraser::haventBegunDrawUserMessage () const +{ + return i18n ("Click or drag to erase pixels of the foreground color."); +} + + +bool kpToolColorEraser::drawShouldProceed (const QPoint & /*thisPoint*/, + const QPoint & /*lastPoint*/, + const QRect & /*normalizedRect*/) +{ + if (foregroundColor () == backgroundColor () && + processedColorSimilarity () == 0) + { + return false; + } + + return true; +} + + +QRect kpToolColorEraser::drawLine (const QPoint &thisPoint, const QPoint &lastPoint) +{ +#if DEBUG_KP_TOOL_COLOR_ERASER + kDebug () << "kpToolColorEraser::drawLine(thisPoint=" << thisPoint + << ",lastPoint=" << lastPoint << ")" << endl; +#endif + + environ ()->flashColorSimilarityToolBarItem (); + + const QRect dirtyRect = kpPainter::washLine (document ()->imagePointer (), + lastPoint.x (), lastPoint.y (), + thisPoint.x (), thisPoint.y (), + color (mouseButton ())/*color to draw in*/, + brushWidth (), brushHeight (), + color (1 - mouseButton ())/*color to replace*/, + processedColorSimilarity ()); + +#if DEBUG_KP_TOOL_COLOR_ERASER + kDebug () << "\tdirtyRect=" << dirtyRect; +#endif + + if (!dirtyRect.isEmpty ()) + { + document ()->slotContentsChanged (dirtyRect); + return dirtyRect; + } + + return QRect (); +} + +#include diff --git a/kolourpaint/tools/flow/kpToolColorEraser.h b/kolourpaint/tools/flow/kpToolColorEraser.h new file mode 100644 index 00000000..f44eaf29 --- /dev/null +++ b/kolourpaint/tools/flow/kpToolColorEraser.h @@ -0,0 +1,65 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_COLOR_ERASER_H +#define KP_TOOL_COLOR_ERASER_H + + +#include + + +// Color Eraser = Brush that replaces/washes the foreground color with the background color +class kpToolColorEraser : public kpToolFlowBase +{ +Q_OBJECT + +public: + kpToolColorEraser (kpToolEnvironment *environ, QObject *parent); + virtual ~kpToolColorEraser (); + + +public: + virtual void globalDraw (); + + +protected: + virtual QString haventBegunDrawUserMessage () const; + + virtual bool drawShouldProceed (const QPoint &thisPoint, + const QPoint &lastPoint, + const QRect &normalizedRect); + + virtual bool haveSquareBrushes () const { return true; } + virtual bool colorsAreSwapped () const { return true; } + + + virtual QRect drawLine (const QPoint &thisPoint, const QPoint &lastPoint); +}; + + +#endif // KP_TOOL_COLOR_ERASER_H diff --git a/kolourpaint/tools/flow/kpToolEraser.cpp b/kolourpaint/tools/flow/kpToolEraser.cpp new file mode 100644 index 00000000..511006a1 --- /dev/null +++ b/kolourpaint/tools/flow/kpToolEraser.cpp @@ -0,0 +1,80 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_ERASER 0 + +#include + +#include + +#include +#include +#include + +//--------------------------------------------------------------------- + +kpToolEraser::kpToolEraser (kpToolEnvironment *environ, QObject *parent) + : kpToolFlowPixmapBase (i18n ("Eraser"), + i18n ("Lets you rub out mistakes"), + Qt::Key_A, + environ, parent, "tool_eraser") +{ +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpToolEraser::globalDraw () +{ +#if DEBUG_KP_TOOL_ERASER + kDebug () << "kpToolEraser::globalDraw()"; +#endif + + commandHistory ()->addCommand ( + new kpEffectClearCommand ( + false/*act on doc, not sel*/, + backgroundColor (), + environ ()->commandEnvironment ())); +} + +//--------------------------------------------------------------------- + +// protected virtual [base kpToolFlowBase] +QString kpToolEraser::haventBegunDrawUserMessage () const +{ + return i18n ("Click or drag to erase."); +} + +//--------------------------------------------------------------------- + +// See the our corresponding .h for brush selection. + +// Logic is in kpToolFlowPixmapBase. + + +#include diff --git a/kolourpaint/tools/flow/kpToolEraser.h b/kolourpaint/tools/flow/kpToolEraser.h new file mode 100644 index 00000000..5f187921 --- /dev/null +++ b/kolourpaint/tools/flow/kpToolEraser.h @@ -0,0 +1,55 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_ERASER_H +#define KP_TOOL_ERASER_H + + +#include + + +// Eraser = Brush but with foreground & background colors swapped (a few square brushes) +class kpToolEraser : public kpToolFlowPixmapBase +{ +Q_OBJECT + +public: + kpToolEraser (kpToolEnvironment *environ, QObject *parent); + + virtual void globalDraw (); + + +protected: + virtual QString haventBegunDrawUserMessage () const; + + virtual bool haveSquareBrushes () const { return true; } + virtual bool colorsAreSwapped () const { return true; } +}; + + +#endif // KP_TOOL_ERASER_H diff --git a/kolourpaint/tools/flow/kpToolFlowBase.cpp b/kolourpaint/tools/flow/kpToolFlowBase.cpp new file mode 100644 index 00000000..d88c3a24 --- /dev/null +++ b/kolourpaint/tools/flow/kpToolFlowBase.cpp @@ -0,0 +1,492 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_FLOW_BASE 0 + +#include + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +struct kpToolFlowBasePrivate +{ + kpToolWidgetBrush *toolWidgetBrush; + kpToolWidgetEraserSize *toolWidgetEraserSize; + + + // + // Cursor and Brush Data + // (must be zero if unused) + // + + kpTempImage::UserFunctionType brushDrawFunc, cursorDrawFunc; + + // Can't use union since package types contain fields requiring + // constructors. + kpToolWidgetBrush::DrawPackage brushDrawPackageForMouseButton [2]; + kpToolWidgetEraserSize::DrawPackage eraserDrawPackageForMouseButton [2]; + + // Each element points to one of the above (both elements from the same + // array). + void *drawPackageForMouseButton [2]; + + int brushWidth, brushHeight; + int cursorWidth, cursorHeight; + + bool brushIsDiagonalLine; + + + kpToolFlowCommand *currentCommand; +}; + +//--------------------------------------------------------------------- + +kpToolFlowBase::kpToolFlowBase (const QString &text, const QString &description, + int key, + kpToolEnvironment *environ, QObject *parent, const QString &name) + + : kpTool (text, description, key, environ, parent, name), + d (new kpToolFlowBasePrivate ()) +{ + d->toolWidgetBrush = 0; + d->toolWidgetEraserSize = 0; + + clearBrushCursorData (); + + d->currentCommand = 0; +} + +//--------------------------------------------------------------------- + +kpToolFlowBase::~kpToolFlowBase () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// private +void kpToolFlowBase::clearBrushCursorData () +{ + d->brushDrawFunc = d->cursorDrawFunc = 0; + + memset (&d->brushDrawPackageForMouseButton, + 0, + sizeof (d->brushDrawPackageForMouseButton)); + memset (&d->eraserDrawPackageForMouseButton, + 0, + sizeof (d->eraserDrawPackageForMouseButton)); + + memset (&d->drawPackageForMouseButton, + 0, + sizeof (d->drawPackageForMouseButton)); + + d->brushWidth = d->brushHeight = 0; + d->cursorWidth = d->cursorHeight = 0; + + d->brushIsDiagonalLine = false; +} + +//--------------------------------------------------------------------- + +// virtual +void kpToolFlowBase::begin () +{ + kpToolToolBar *tb = toolToolBar (); + Q_ASSERT (tb); + + // TODO: Bad smell. Mutually exclusive. Use inheritance. + if (haveSquareBrushes ()) + { + d->toolWidgetEraserSize = tb->toolWidgetEraserSize (); + connect (d->toolWidgetEraserSize, SIGNAL (eraserSizeChanged (int)), + this, SLOT (updateBrushAndCursor ())); + d->toolWidgetEraserSize->show (); + + updateBrushAndCursor (); + + viewManager ()->setCursor (kpCursorProvider::lightCross ()); + } + else if (haveDiverseBrushes ()) + { + d->toolWidgetBrush = tb->toolWidgetBrush (); + connect (d->toolWidgetBrush, SIGNAL (brushChanged ()), + this, SLOT (updateBrushAndCursor ())); + d->toolWidgetBrush->show (); + + updateBrushAndCursor (); + + viewManager ()->setCursor (kpCursorProvider::lightCross ()); + } + + setUserMessage (haventBegunDrawUserMessage ()); +} + +//--------------------------------------------------------------------- + +// virtual +void kpToolFlowBase::end () +{ + if (d->toolWidgetEraserSize) + { + disconnect (d->toolWidgetEraserSize, SIGNAL (eraserSizeChanged (int)), + this, SLOT (updateBrushAndCursor ())); + d->toolWidgetEraserSize = 0; + } + else if (d->toolWidgetBrush) + { + disconnect (d->toolWidgetBrush, SIGNAL (brushChanged ()), + this, SLOT (updateBrushAndCursor ())); + d->toolWidgetBrush = 0; + } + + kpViewManager *vm = viewManager (); + Q_ASSERT (vm); + + if (vm->tempImage () && vm->tempImage ()->isBrush ()) + vm->invalidateTempImage (); + + if (haveAnyBrushes ()) + vm->unsetCursor (); + + clearBrushCursorData (); +} + +//--------------------------------------------------------------------- + +// virtual +void kpToolFlowBase::beginDraw () +{ + d->currentCommand = new kpToolFlowCommand (text (), environ ()->commandEnvironment ()); + + // We normally show the brush cursor in the foreground colour but if the + // user starts drawing in the background color, we don't want to leave + // the brush cursor in the foreground colour -- just hide it in all cases + // to avoid confusion. + viewManager ()->invalidateTempImage (); + + setUserMessage (cancelUserMessage ()); +} + +//--------------------------------------------------------------------- + +// virtual +void kpToolFlowBase::hover (const QPoint &point) +{ +#if DEBUG_KP_TOOL_FLOW_BASE && 0 + kDebug () << "kpToolFlowBase::hover(" << point << ")" + << " hasBegun=" << hasBegun () + << " hasBegunDraw=" << hasBegunDraw () + << " cursorPixmap.isNull=" << m_cursorPixmap.isNull () + << endl; +#endif + if (point != KP_INVALID_POINT && d->cursorDrawFunc) + { + viewManager ()->setFastUpdates (); + + viewManager ()->setTempImage ( + kpTempImage (true/*brush*/, + hotRect ().topLeft (), + d->cursorDrawFunc, d->drawPackageForMouseButton [0/*left button*/], + d->cursorWidth, d->cursorHeight)); + + viewManager ()->restoreFastUpdates (); + } + + setUserShapePoints (point); +} + +//--------------------------------------------------------------------- + +// virtual +QRect kpToolFlowBase::drawPoint (const QPoint &point) +{ + return drawLine (point, point); +} + +//--------------------------------------------------------------------- + +// virtual +void kpToolFlowBase::draw (const QPoint &thisPoint, const QPoint &lastPoint, const QRect &normalizedRect) +{ + if (!/*virtual*/drawShouldProceed (thisPoint, lastPoint, normalizedRect)) + return; + + // sync: remember to restoreFastUpdates() in all exit paths + viewManager ()->setFastUpdates (); + + QRect dirtyRect; + + // TODO: I'm beginning to wonder this drawPoint() "optimization" actually + // optimises. Is it worth the complexity? Hence drawPoint() impl above. + if (d->brushIsDiagonalLine ? + currentPointCardinallyNextToLast () : + currentPointNextToLast ()) + { + dirtyRect = drawPoint (thisPoint); + } + // in reality, the system is too slow to give us all the MouseMove events + // so we "interpolate" the missing points :) + else + { + dirtyRect = drawLine (thisPoint, lastPoint); + } + + d->currentCommand->updateBoundingRect (dirtyRect); + + viewManager ()->restoreFastUpdates (); + setUserShapePoints (thisPoint); +} + +//--------------------------------------------------------------------- + +// virtual +void kpToolFlowBase::cancelShape () +{ + d->currentCommand->finalize (); + d->currentCommand->cancel (); + + delete d->currentCommand; + d->currentCommand = 0; + + updateBrushAndCursor (); + + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +//--------------------------------------------------------------------- + +void kpToolFlowBase::releasedAllButtons () +{ + setUserMessage (haventBegunDrawUserMessage ()); +} + +//--------------------------------------------------------------------- + +// virtual +void kpToolFlowBase::endDraw (const QPoint &, const QRect &) +{ + d->currentCommand->finalize (); + environ ()->commandHistory ()->addCommand (d->currentCommand, + false/*don't exec*/); + + // don't delete - it's up to the commandHistory + d->currentCommand = 0; + + updateBrushAndCursor (); + + setUserMessage (haventBegunDrawUserMessage ()); +} + +//--------------------------------------------------------------------- + +// TODO: maybe the base should be virtual? +kpColor kpToolFlowBase::color (int which) +{ +#if DEBUG_KP_TOOL_FLOW_BASE && 0 + kDebug () << "kpToolFlowBase::color (" << which << ")"; +#endif + + // Pen & Brush + if (!colorsAreSwapped ()) + return kpTool::color (which); + // only the (Color) Eraser uses the opposite color + else + return kpTool::color (which ? 0 : 1); // don't trust !0 == 1 +} + +//--------------------------------------------------------------------- + +// protected +kpTempImage::UserFunctionType kpToolFlowBase::brushDrawFunction () const +{ + return d->brushDrawFunc; +} + +//--------------------------------------------------------------------- + +// protected +void *kpToolFlowBase::brushDrawFunctionData () const +{ + return d->drawPackageForMouseButton [mouseButton ()]; +} + + +// protected +int kpToolFlowBase::brushWidth () const +{ + return d->brushWidth; +} + +// protected +int kpToolFlowBase::brushHeight () const +{ + return d->brushHeight; +} + +// protected +bool kpToolFlowBase::brushIsDiagonalLine () const +{ + return d->brushIsDiagonalLine; +} + + +// protected +kpToolFlowCommand *kpToolFlowBase::currentCommand () const +{ + return d->currentCommand; +} + +//--------------------------------------------------------------------- + +// protected slot +void kpToolFlowBase::updateBrushAndCursor () +{ +#if DEBUG_KP_TOOL_FLOW_BASE && 1 + kDebug () << "kpToolFlowBase::updateBrushAndCursor()"; +#endif + + if (haveSquareBrushes ()) + { + d->brushDrawFunc = d->toolWidgetEraserSize->drawFunction (); + d->cursorDrawFunc = d->toolWidgetEraserSize->drawCursorFunction (); + + for (int i = 0; i < 2; i++) + { + d->drawPackageForMouseButton [i] = + &(d->eraserDrawPackageForMouseButton [i] = + d->toolWidgetEraserSize->drawFunctionData (color (i))); + } + + d->brushWidth = d->brushHeight = + d->cursorWidth = d->cursorHeight = + d->toolWidgetEraserSize->eraserSize (); + + d->brushIsDiagonalLine = false; + } + else if (haveDiverseBrushes ()) + { + d->brushDrawFunc = d->cursorDrawFunc = d->toolWidgetBrush->drawFunction (); + + for (int i = 0; i < 2; i++) + { + d->drawPackageForMouseButton [i] = + &(d->brushDrawPackageForMouseButton [i] = + d->toolWidgetBrush->drawFunctionData (color (i))); + } + + d->brushWidth = d->brushHeight = + d->cursorWidth = d->cursorHeight = + d->toolWidgetBrush->brushSize (); + + d->brushIsDiagonalLine = d->toolWidgetBrush->brushIsDiagonalLine (); + } + + hover (hasBegun () ? currentPoint () : calculateCurrentPoint ()); +} + +//--------------------------------------------------------------------- + +// virtual private slot +void kpToolFlowBase::slotForegroundColorChanged (const kpColor & /*col*/) +{ +#if DEBUG_KP_TOOL_FLOW_BASE + kDebug () << "kpToolFlowBase::slotForegroundColorChanged()"; +#endif + + updateBrushAndCursor (); +} + +//--------------------------------------------------------------------- + +// virtual private slot +void kpToolFlowBase::slotBackgroundColorChanged (const kpColor & /*col*/) +{ +#if DEBUG_KP_TOOL_FLOW_BASE + kDebug () << "kpToolFlowBase::slotBackgroundColorChanged()"; +#endif + + updateBrushAndCursor (); +} + +//--------------------------------------------------------------------- + +// public static +QRect kpToolFlowBase::hotRectForMousePointAndBrushWidthHeight ( + const QPoint &mousePoint, + int brushWidth, int brushHeight) +{ + /* + * e.g. + * Width 5: + * 0 1 2 3 4 + * ^ + * | + * Center + */ + return QRect (mousePoint.x () - brushWidth / 2, + mousePoint.y () - brushHeight / 2, + brushWidth, + brushHeight); +} + +//--------------------------------------------------------------------- + +// protected +QRect kpToolFlowBase::hotRect () const +{ + return hotRectForMousePointAndBrushWidthHeight (currentPoint (), + d->brushWidth, d->brushHeight); +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/tools/flow/kpToolFlowBase.h b/kolourpaint/tools/flow/kpToolFlowBase.h new file mode 100644 index 00000000..79d63502 --- /dev/null +++ b/kolourpaint/tools/flow/kpToolFlowBase.h @@ -0,0 +1,120 @@ + +/* + Copyright(c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_FLOW_BASE_H +#define KP_TOOL_FLOW_BASE_H + + +#include + +#include +#include + + +class QPoint; +class QString; + +class kpColor; +class kpToolFlowCommand; + + +class kpToolFlowBase : public kpTool +{ + Q_OBJECT + + public: + kpToolFlowBase(const QString &text, const QString &description, + int key, + kpToolEnvironment *environ, QObject *parent, const QString &name); + + virtual ~kpToolFlowBase(); + + // Returns the dirty rectangle for drawing a brush(of size + // x) at . will end + // up being the midpoint of the returned rectangle(subject to integer + // precision). + static QRect hotRectForMousePointAndBrushWidthHeight( + const QPoint &mousePoint, + int brushWidth, int brushHeight); + + virtual void begin(); + virtual void end(); + + virtual void beginDraw(); + virtual void hover(const QPoint &point); + + // drawPoint() normally calls drawLine(point,point). Override drawPoint() + // if you think you can be more efficient. + virtual QRect drawPoint(const QPoint &point); + virtual QRect drawLine(const QPoint &thisPoint, const QPoint &lastPoint) = 0; + + virtual bool drawShouldProceed(const QPoint & /*thisPoint*/, const QPoint & /*lastPoint*/, const QRect & /*normalizedRect*/) { return true; } + virtual void draw(const QPoint &thisPoint, const QPoint &lastPoint, const QRect &normalizedRect); + virtual void cancelShape(); + virtual void releasedAllButtons(); + virtual void endDraw(const QPoint &, const QRect &); + + protected: + virtual QString haventBegunDrawUserMessage() const = 0; + + virtual bool haveSquareBrushes() const { return false; } + virtual bool haveDiverseBrushes() const { return false; } + bool haveAnyBrushes() const + { + return(haveSquareBrushes() || haveDiverseBrushes()); + } + + virtual bool colorsAreSwapped() const { return false; } + + kpTempImage::UserFunctionType brushDrawFunction() const; + void *brushDrawFunctionData() const; + + int brushWidth() const; + int brushHeight() const; + + bool brushIsDiagonalLine() const; + + kpToolFlowCommand *currentCommand() const; + virtual kpColor color(int which); + QRect hotRect() const; + + protected slots: + void updateBrushAndCursor(); + + virtual void slotForegroundColorChanged(const kpColor &col); + virtual void slotBackgroundColorChanged(const kpColor &col); + + private: + void clearBrushCursorData(); + + private: + struct kpToolFlowBasePrivate *d; +}; + + +#endif // KP_TOOL_FLOW_BASE_H diff --git a/kolourpaint/tools/flow/kpToolFlowPixmapBase.cpp b/kolourpaint/tools/flow/kpToolFlowPixmapBase.cpp new file mode 100644 index 00000000..71e5fa96 --- /dev/null +++ b/kolourpaint/tools/flow/kpToolFlowPixmapBase.cpp @@ -0,0 +1,87 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include + +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +kpToolFlowPixmapBase::kpToolFlowPixmapBase (const QString &text, const QString &description, + int key, + kpToolEnvironment *environ, QObject *parent, const QString &name) + : kpToolFlowBase (text, description, key, environ, parent, name) +{ +} + +//--------------------------------------------------------------------- + +QRect kpToolFlowPixmapBase::drawLine (const QPoint &thisPoint, const QPoint &lastPoint) +{ + QRect docRect = kpPainter::normalizedRect(thisPoint, lastPoint); + docRect = neededRect (docRect, qMax (brushWidth (), brushHeight ())); + kpImage image = document ()->getImageAt (docRect); + + + QList points = kpPainter::interpolatePoints (lastPoint, thisPoint, + brushIsDiagonalLine ()); + + for (QList ::const_iterator pit = points.constBegin (); + pit != points.constEnd (); + ++pit) + { + const QPoint point = + hotRectForMousePointAndBrushWidthHeight ( + (*pit), brushWidth (), brushHeight ()) + .topLeft () - docRect.topLeft (); + + // OPT: This may be redrawing pixels that were drawn on a previous + // iteration, since the brush is usually bigger than 1 pixel. + // Maybe we could use QRegion to determine all the non-intersecting + // regions and only draw each region once. + // + // Try this at least for the easy case of the Eraser, which has + // square, simply-filled brushes. Profiling needs to be done as + // QRegion is known to be a CPU hog. + brushDrawFunction () (&image, point, brushDrawFunctionData ()); + } + + + document ()->setImageAt (image, docRect.topLeft ()); + return docRect; +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/tools/flow/kpToolFlowPixmapBase.h b/kolourpaint/tools/flow/kpToolFlowPixmapBase.h new file mode 100644 index 00000000..a8743f0a --- /dev/null +++ b/kolourpaint/tools/flow/kpToolFlowPixmapBase.h @@ -0,0 +1,58 @@ + +// REFACTOR: Rename to kpAbstractFlowImageTool, and use kpImage instead of QPixmap + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_FLOW_PIXMAP_BASE_H +#define KP_TOOL_FLOW_PIXMAP_BASE_H + + +#include + + +/** + * @short Abstract base call for all continuous tools that draw pixmaps + * (e.g. Brush, Eraser). + * + * @author Clarence Dang + */ +class kpToolFlowPixmapBase : public kpToolFlowBase +{ +Q_OBJECT + +public: + kpToolFlowPixmapBase (const QString &text, const QString &description, + int key, + kpToolEnvironment *environ, QObject *parent, const QString &name); + +protected: + virtual QRect drawLine (const QPoint &thisPoint, const QPoint &lastPoint); +}; + + +#endif // KP_TOOL_FLOW_PIXMAP_BASE_H diff --git a/kolourpaint/tools/flow/kpToolPen.cpp b/kolourpaint/tools/flow/kpToolPen.cpp new file mode 100644 index 00000000..14b6744d --- /dev/null +++ b/kolourpaint/tools/flow/kpToolPen.cpp @@ -0,0 +1,96 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +struct kpToolPenPrivate +{ +}; + +//--------------------------------------------------------------------- + +kpToolPen::kpToolPen (kpToolEnvironment *environ, QObject *parent) + : kpToolFlowBase (i18n ("Pen"), i18n ("Draws dots and freehand strokes"), + Qt::Key_P, + environ, parent, "tool_pen"), + d (new kpToolPenPrivate ()) +{ +} + +//--------------------------------------------------------------------- + +kpToolPen::~kpToolPen () +{ + delete d; +} + +//--------------------------------------------------------------------- + + +// protected virtual [base kpToolFlowBase] +QString kpToolPen::haventBegunDrawUserMessage () const +{ + return i18n ("Click to draw dots or drag to draw strokes."); +} + +//--------------------------------------------------------------------- + +// protected virtual [base kpToolFlowBase] +QRect kpToolPen::drawLine (const QPoint &thisPoint, const QPoint &lastPoint) +{ + QRect docRect = kpPainter::normalizedRect(thisPoint, lastPoint); + docRect = neededRect (docRect, 1/*pen width*/); + kpImage image = document ()->getImageAt (docRect); + + const QPoint sp = lastPoint - docRect.topLeft (), + ep = thisPoint - docRect.topLeft (); + + kpPainter::drawLine (&image, + sp.x (), sp.y (), + ep.x (), ep.y (), + color (mouseButton ()), + 1/*pen width*/); + + document ()->setImageAt (image, docRect.topLeft ()); + return docRect; +} + + +#include diff --git a/kolourpaint/tools/flow/kpToolPen.h b/kolourpaint/tools/flow/kpToolPen.h new file mode 100644 index 00000000..1e22a986 --- /dev/null +++ b/kolourpaint/tools/flow/kpToolPen.h @@ -0,0 +1,54 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_PEN_H +#define KP_TOOL_PEN_H + + +#include + + +// Pen = draws pixels, "interpolates" by "sweeping" pixels along a line (no brushes) +class kpToolPen : public kpToolFlowBase +{ +Q_OBJECT + +public: + kpToolPen (kpToolEnvironment *environ, QObject *parent); + virtual ~kpToolPen (); + +protected: + virtual QString haventBegunDrawUserMessage () const; + virtual QRect drawLine (const QPoint &thisPoint, const QPoint &lastPoint); + +private: + struct kpToolPenPrivate *d; +}; + + +#endif // KP_TOOL_PEN_H diff --git a/kolourpaint/tools/flow/kpToolSpraycan.cpp b/kolourpaint/tools/flow/kpToolSpraycan.cpp new file mode 100644 index 00000000..95886394 --- /dev/null +++ b/kolourpaint/tools/flow/kpToolSpraycan.cpp @@ -0,0 +1,264 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SPRAYCAN 0 + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +kpToolSpraycan::kpToolSpraycan (kpToolEnvironment *environ, QObject *parent) + : kpToolFlowBase (i18n ("Spraycan"), i18n ("Sprays graffiti"), + Qt::Key_Y, + environ, parent, "tool_spraycan"), + m_toolWidgetSpraycanSize(0) +{ + m_timer = new QTimer (this); + m_timer->setInterval (25/*ms*/); + connect (m_timer, SIGNAL (timeout ()), this, SLOT (timeoutDraw ())); +} + +//--------------------------------------------------------------------- + +// protected virtual [base kpToolFlowBase] +QString kpToolSpraycan::haventBegunDrawUserMessage () const +{ + return i18n ("Click or drag to spray graffiti."); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpToolFlowBase] +void kpToolSpraycan::begin () +{ + kpToolToolBar *tb = toolToolBar (); + Q_ASSERT (tb); + + m_toolWidgetSpraycanSize = tb->toolWidgetSpraycanSize (); + connect (m_toolWidgetSpraycanSize, SIGNAL (spraycanSizeChanged (int)), + this, SLOT (slotSpraycanSizeChanged (int))); + m_toolWidgetSpraycanSize->show (); + + kpToolFlowBase::begin (); +} + +// public virtual [base kpToolFlowBase] +void kpToolSpraycan::end () +{ + kpToolFlowBase::end (); + + disconnect (m_toolWidgetSpraycanSize, SIGNAL (spraycanSizeChanged (int)), + this, SLOT (slotSpraycanSizeChanged (int))); + m_toolWidgetSpraycanSize = 0; +} + + +// public virtual [base kpToolFlowBase] +void kpToolSpraycan::beginDraw () +{ +#if DEBUG_KP_TOOL_SPRAYCAN + kDebug () << "kpToolSpraycan::beginDraw()"; +#endif + + kpToolFlowBase::beginDraw (); + + // We draw even if the user doesn't move the mouse. + // We still timeout-draw even if the user _does_ move the mouse. + m_timer->start (); +} + + +// protected +QRect kpToolSpraycan::drawLineWithProbability (const QPoint &thisPoint, + const QPoint &lastPoint, + double probability) +{ +#if DEBUG_KP_TOOL_SPRAYCAN + kDebug () << "CALL(thisPoint=" << thisPoint + << ",lastPoint=" << lastPoint + << ")" << endl; +#endif + + QList docPoints = kpPainter::interpolatePoints (lastPoint, thisPoint, + false/*no need for cardinally adjacency points*/, + probability); +#if DEBUG_KP_TOOL_SPRAYCAN + kDebug () << "\tdocPoints=" << docPoints; +#endif + + + // By chance no points to draw? + if (docPoints.empty ()) + return QRect (); + + + // For efficiency, only get image after NOP check above. + QRect docRect = kpPainter::normalizedRect(thisPoint, lastPoint); + docRect = neededRect (docRect, spraycanSize ()); + kpImage image = document ()->getImageAt (docRect); + + + // Spray at each point, onto the image. + // + // Note in passing: Unlike other tools such as the Brush, drawing + // over the same point does result in a different + // appearance. + + QList imagePoints; + foreach (const QPoint &dp, docPoints) + imagePoints.append (dp - docRect.topLeft ()); + + kpPainter::sprayPoints (&image, + imagePoints, + color (mouseButton ()), + spraycanSize ()); + + + viewManager ()->setFastUpdates (); + document ()->setImageAt (image, docRect.topLeft ()); + viewManager ()->restoreFastUpdates (); + + + return docRect; +} + +// public virtual [base kpToolFlowBase] +QRect kpToolSpraycan::drawPoint (const QPoint &point) +{ +#if DEBUG_KP_TOOL_SPRAYCAN + kDebug () << "kpToolSpraycan::drawPoint" << point + << " lastPoint=" << lastPoint () + << endl; +#endif + + // If this is the first in the flow or if the user is moving the spray, + // make the spray line continuous. + if (point != lastPoint ()) + { + // Draw at this single point without delay. + return drawLineWithProbability (point, point, + 1.0/*100% chance of drawing*/); + } + + return QRect (); +} + +// public virtual [base kpToolFlowBase] +QRect kpToolSpraycan::drawLine (const QPoint &thisPoint, const QPoint &lastPoint) +{ +#if DEBUG_KP_TOOL_SPRAYCAN + kDebug () << "CALL(thisPoint=" << thisPoint << ",lastPoint=" << lastPoint; +#endif + + // Draw only every so often in response to movement. + return drawLineWithProbability (thisPoint, lastPoint, + 0.05/*less dense: select 5% of adjacent pixels - not all*/); +} + +// protected slot +void kpToolSpraycan::timeoutDraw () +{ +#if DEBUG_KP_TOOL_SPRAYCAN + kDebug () << "kpToolSpraycan::timeoutDraw()"; +#endif + + // Draw at this single point without delay. + const QRect drawnRect = drawLineWithProbability (currentPoint (), currentPoint (), + 1.0/*100% chance of drawing*/); + + // kpToolFlowBase() does this after calling drawPoint() and drawLine() so + // we need to do it too. + currentCommand ()->updateBoundingRect (drawnRect); +} + + +// public virtual [base kpToolFlowBase] +void kpToolSpraycan::cancelShape () +{ +#if DEBUG_KP_TOOL_SPRAYCAN + kDebug () << "kpToolSpraycan::cancelShape()"; +#endif + + m_timer->stop (); + kpToolFlowBase::cancelShape (); +} + +// public virtual [base kpToolFlowBase] +void kpToolSpraycan::endDraw (const QPoint &thisPoint, + const QRect &normalizedRect) +{ +#if DEBUG_KP_TOOL_SPRAYCAN + kDebug () << "kpToolSpraycan::endDraw(thisPoint=" << thisPoint + << ")" << endl; +#endif + + m_timer->stop (); + kpToolFlowBase::endDraw (thisPoint, normalizedRect); +} + + +// protected +int kpToolSpraycan::spraycanSize () const +{ + return m_toolWidgetSpraycanSize->spraycanSize (); +} + +// protected slot +void kpToolSpraycan::slotSpraycanSizeChanged (int size) +{ + (void) size; +} + + +#include + diff --git a/kolourpaint/tools/flow/kpToolSpraycan.h b/kolourpaint/tools/flow/kpToolSpraycan.h new file mode 100644 index 00000000..142b9745 --- /dev/null +++ b/kolourpaint/tools/flow/kpToolSpraycan.h @@ -0,0 +1,90 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_SPRAYCAN_H +#define KP_TOOL_SPRAYCAN_H + + +#include + + +class QPoint; +class QRect; +class QString; +class QTimer; + +class kpToolWidgetSpraycanSize; + + +class kpToolSpraycan : public kpToolFlowBase +{ +Q_OBJECT + +public: + kpToolSpraycan (kpToolEnvironment *environ, QObject *parent); + +protected: + virtual QString haventBegunDrawUserMessage () const; + + +public: + virtual void begin (); + virtual void end (); + + +public: + virtual void beginDraw (); +protected: + // (ASSUMPTION: is between 0.0 and 1.0 inclusive) + QRect drawLineWithProbability (const QPoint &thisPoint, + const QPoint &lastPoint, + double probability); +public: + virtual QRect drawPoint (const QPoint &point); + virtual QRect drawLine (const QPoint &thisPoint, const QPoint &lastPoint); + virtual void cancelShape (); + virtual void endDraw (const QPoint &thisPoint, + const QRect &normalizedRect); + +protected slots: + void timeoutDraw (); + + +protected: + int spraycanSize () const; +protected slots: + void slotSpraycanSizeChanged (int size); + + +protected: + QTimer *m_timer; + kpToolWidgetSpraycanSize *m_toolWidgetSpraycanSize; +}; + + +#endif // KP_TOOL_SPRAYCAN_H diff --git a/kolourpaint/tools/kpTool.cpp b/kolourpaint/tools/kpTool.cpp new file mode 100644 index 00000000..802ee0bf --- /dev/null +++ b/kolourpaint/tools/kpTool.cpp @@ -0,0 +1,264 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// Tool initialisation and basic accessors. +// + +#define DEBUG_KP_TOOL 0 + +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#undef environ // macro on win32 + +//--------------------------------------------------------------------- + +kpTool::kpTool(const QString &text, const QString &description, + int key, + kpToolEnvironment *environ, + QObject *parent, const QString &name) + : QObject(parent), + d(new kpToolPrivate()) +{ + d->key = key; + d->action = 0; + d->ignoreColorSignals = 0; + d->shiftPressed = false, d->controlPressed = false, d->altPressed = false; // set in beginInternal() + d->beganDraw = false; + d->text = text, d->description = description; + d->began = false; + d->viewUnderStartPoint = 0; + d->userShapeStartPoint = KP_INVALID_POINT; + d->userShapeEndPoint = KP_INVALID_POINT; + d->userShapeSize = KP_INVALID_SIZE; + + d->environ = environ; + + setObjectName(name); + initAction(); +} + +//--------------------------------------------------------------------- + +kpTool::~kpTool () +{ + // before destructing, stop using the tool + if (d->began) + endInternal (); + + delete d->action; + + delete d; +} + +//--------------------------------------------------------------------- + +// private +void kpTool::initAction () +{ + KActionCollection *ac = d->environ->actionCollection (); + Q_ASSERT (ac); + + d->action = new kpToolAction(text(), objectName(), shortcutForKey(d->key), + this, SIGNAL(actionActivated()), + ac, objectName()); + + // Make tools mutually exclusive by placing them in the same group. + d->action->setActionGroup(d->environ->toolsActionGroup ()); + + d->action->setWhatsThis(d->description); + + connect(d->action, SIGNAL(changed()), this, SIGNAL (actionToolTipChanged())); +} + +//--------------------------------------------------------------------- +// public + +QString kpTool::text () const +{ + return d->text; +} + +//--------------------------------------------------------------------- + +static bool KeyIsText (int key) +{ + // TODO: should work like !QKeyEvent::text().isEmpty() + return !(key & (Qt::KeyboardModifierMask ^ Qt::ShiftModifier)); +} + +//--------------------------------------------------------------------- + +// public static +QString kpTool::toolTipForTextAndShortcut (const QString &text, + const KShortcut &shortcut) +{ + foreach(const QKeySequence &seq, shortcut.toList()) + { + if (seq.count () == 1 && ::KeyIsText (seq [0])) + return i18nc (" ()", + "%1 (%2)", text, seq.toString ().toUpper ()); + } + + return text; +} + +//--------------------------------------------------------------------- + +QString kpTool::toolTip () const +{ + return toolTipForTextAndShortcut(d->text, d->action->shortcut()); +} + +//--------------------------------------------------------------------- +// public static + +KShortcut kpTool::shortcutForKey (int key) +{ + KShortcut shortcut; + + if (key) + { + shortcut.setPrimary (key); + // (CTRL+, ALT+, CTRL+ALT+, CTRL+SHIFT+ + // all clash with global KDE shortcuts) + shortcut.setAlternate (Qt::ALT + Qt::SHIFT + key); + } + + return shortcut; +} + +//--------------------------------------------------------------------- +// public + +kpToolAction *kpTool::action () const +{ + return d->action; +} + +//--------------------------------------------------------------------- + +kpDocument *kpTool::document () const +{ + return d->environ->document (); +} + +//--------------------------------------------------------------------- + +kpViewManager *kpTool::viewManager () const +{ + return d->environ->viewManager (); +} + +//--------------------------------------------------------------------- + +kpToolToolBar *kpTool::toolToolBar () const +{ + return d->environ->toolToolBar (); +} + +//--------------------------------------------------------------------- + +kpColor kpTool::color (int which) const +{ + return d->environ->color (which); +} + +//--------------------------------------------------------------------- + +kpColor kpTool::foregroundColor () const +{ + return color (0); +} + +//--------------------------------------------------------------------- + +kpColor kpTool::backgroundColor () const +{ + return color (1); +} + +//--------------------------------------------------------------------- + +// TODO: Some of these might not be common enough. +// Just put in kpToolEnvironment? + +double kpTool::colorSimilarity () const +{ + return d->environ->colorSimilarity (); +} + +int kpTool::processedColorSimilarity () const +{ + return d->environ->processedColorSimilarity (); +} + + +kpColor kpTool::oldForegroundColor () const +{ + return d->environ->oldForegroundColor (); +} + +kpColor kpTool::oldBackgroundColor () const +{ + return d->environ->oldBackgroundColor (); +} + +double kpTool::oldColorSimilarity () const +{ + return d->environ->oldColorSimilarity (); +} + +kpCommandHistory *kpTool::commandHistory () const +{ + return d->environ->commandHistory (); +} + + +kpToolEnvironment *kpTool::environ () const +{ + return d->environ; +} + + +#include diff --git a/kolourpaint/tools/kpTool.h b/kolourpaint/tools/kpTool.h new file mode 100644 index 00000000..fd836e69 --- /dev/null +++ b/kolourpaint/tools/kpTool.h @@ -0,0 +1,468 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_H +#define KP_TOOL_H + + +#include +#include +#include +#include +#include + +#include +#ifdef Q_OS_WIN + #include + #undef environ // macro on win32 +#endif + + +class QFocusEvent; +class QIcon; +class QInputMethodEvent; +class QKeyEvent; +class QMouseEvent; +class QImage; +class QWheelEvent; + +class KShortcut; + +class kpColor; +class kpCommandHistory; +class kpDocument; +class kpView; +class kpViewManager; +class kpToolAction; +class kpToolEnvironment; +class kpToolToolBar; + + +struct kpToolPrivate; + +// Base class for all tools. +// REFACTOR: rearrange method order to make sense and reflect kpTool_*.cpp split. +class kpTool : public QObject +{ +Q_OBJECT + +public: + // = user-visible name of the tool e.g. "Color Picker" + // = user-visible description used for tooltips + // e.g. "Lets you select a color from the image" + // = optional shortcut key for switching to the tool, or 0 otherwise + // e.g. Qt::Key_C + // = internal QObject name (not user-visible) e.g. "tool_color_picker" + // used for fetching the icon(), the name of the action() and + // debug printing. + kpTool (const QString &text, const QString &description, + int key, + kpToolEnvironment *environ, + QObject *parent, const QString &name); + virtual ~kpTool (); + + kpToolAction *action () const; + + QString text () const; + + static QString toolTipForTextAndShortcut (const QString &text, const KShortcut &shortcut); + QString toolTip () const; + + // Given a single , returns a shortcut with + // (disabled when the user is editing text) and as an alternate, + // +. + static KShortcut shortcutForKey (int key); + + static QRect neededRect (const QRect &rect, int lineWidth); + static QImage neededPixmap (const QImage &image, const QRect &boundingRect); + + bool hasCurrentPoint () const; + // Returns the position of the cursor relative to the topleft point of + // the current view (viewUnderStartPoint() or viewUnderCursor() otherwise). + // + // If neither viewUnderStartPoint() nor viewUnderCursor() + // (i.e. !hasCurrentPoint()), then it returns KP_INVALID_POINT. + // + // If is set (default), then it returns the position in the + // document. This theoretically == m_currentPoint (when m_currentPoint + // is defined) but I wouldn't bet on it. This function is useful when + // m_currentPoint isn't necessarily defined (outside of beginDraw(),draw() + // and hover()). + // + // If is not set, then it returns an unzoomed view coordinate. + // + // Keep in mind that if viewUnderStartPoint(), this can return coordinates + // outside of the document/view. + QPoint calculateCurrentPoint (bool zoomToDoc = true) const; + +private: + // Only called by ctor to create action(). + void initAction (); + +signals: + void actionToolTipChanged(); + +public slots: + // Call this when something below the mouse cursor may have changed + // and/or if the view has moved relative to the cursor (as opposed to + // the cursor moving relative to the view, which would trigger a + // mouseMoveEvent and all would be well without such hacks) + // e.g. when zooming or scrolling the view or when deleting a selection. + // + // This calls hover() or draw() to let the tool know. The Brush Tool + // can then update the position of the Brush Cursor. The Selection + // Tool can update the real cursor. The Line Tool can update the current + // line. The statubar gets correct coordinates. etc. etc. + void somethingBelowTheCursorChanged (); + +private: + // Same as above except that you claim you know better than currentPoint() + void somethingBelowTheCursorChanged (const QPoint ¤tPoint_, + const QPoint ¤tViewPoint_); + +protected: + int mouseButton () const; + + bool shiftPressed () const; + bool controlPressed () const; + bool altPressed () const; + + QPoint startPoint () const; + + QPoint currentPoint () const; + QPoint currentViewPoint () const; + + QRect normalizedRect () const; + + QPoint lastPoint () const; + +public: // for kpMainWindow + kpView *viewUnderStartPoint () const; + +protected: + kpView *viewUnderCursor () const; + +public: + // Called when the tool is selected. + virtual void begin (); + + // Called when the tool is deselected. + virtual void end (); + + // Returns true after begin() has been called but returns false after end() + // after end() has been called. + bool hasBegun () const; + + bool hasBegunDraw () const; + + virtual bool hasBegunShape () const; + + // Called when user double-left-clicks on a tool in the Tool Box. + virtual void globalDraw (); + + // Called when the user clicks on a tool in the Tool Box even though it's + // already the current tool (used by the selection tools to deselect). + virtual void reselect (); + +signals: + // emitted after beginDraw() has been called + void beganDraw (const QPoint &point); + + // Emitted just before draw() is called in mouseMoveEvent(). Slots + // connected to this signal should return in whether the + // mouse pos may have changed. Used by drag scrolling. + void movedAndAboutToDraw (const QPoint ¤tPoint, const QPoint &lastPoint, + int zoomLevel, + bool *scrolled); + + // emitted after endDraw() has been called + void endedDraw (const QPoint &point); + + // emitted after cancelShape() has been called + void cancelledShape (const QPoint &point); + +signals: + // User clicked on the tool's action - i.e. select this tool + void actionActivated(); + +protected: + // (this method is called by kpTool just as it is needed - its value + // is not cached, so it is allowed to return different things at + // different times) + // REFACTOR: Misleadingly named as it's also called in cancelShapeInternal(). + // And it seems to be called in endShapeInternal() as well? + virtual bool returnToPreviousToolAfterEndDraw () const { return false; } + + virtual bool careAboutModifierState () const { return false; } + virtual bool careAboutColorsSwapped () const { return false; } + + virtual void beginDraw (); + + // mouse move without button pressed + // (only m_currentPoint & m_currentViewPoint is defined) + virtual void hover (const QPoint &point); + + // this is useful for "instant" tools like the Pen & Eraser + virtual void draw (const QPoint &thisPoint, const QPoint &lastPoint, + const QRect &normalizedRect); + +private: + void drawInternal (); + +protected: + // (m_mouseButton will not change from beginDraw()) + virtual void cancelShape (); + virtual void releasedAllButtons (); + + virtual void endDraw (const QPoint &thisPoint, const QRect &normalizedRect); + + // TODO: I think reimplementations of this should be calling this base + // implementation, so that endDraw() happens before the custom + // endShape() logic of the reimplementation. + virtual void endShape (const QPoint &thisPoint = QPoint (), + const QRect &normalizedRect = QRect ()) + { + endDraw (thisPoint, normalizedRect); + } + + kpDocument *document () const; + kpViewManager *viewManager () const; + kpToolToolBar *toolToolBar () const; + kpCommandHistory *commandHistory () const; + kpToolEnvironment *environ () const; + + kpColor color (int which) const; + + kpColor foregroundColor () const; + kpColor backgroundColor () const; + + double colorSimilarity () const; + int processedColorSimilarity () const; + +protected slots: + void slotColorsSwappedInternal (const kpColor &newForegroundColor, + const kpColor &newBackgroundColor); + void slotForegroundColorChangedInternal (const kpColor &color); + void slotBackgroundColorChangedInternal (const kpColor &color); + void slotColorSimilarityChangedInternal (double similarity, int processedSimilarity); + +protected slots: // TODO: there is no reason why these should be slots + virtual void slotColorsSwapped (const kpColor & /*newForegroundColor*/, const kpColor & /*newBackgroundColor*/) {} + virtual void slotForegroundColorChanged (const kpColor & /*color*/) {} + virtual void slotBackgroundColorChanged (const kpColor & /*color*/) {} + virtual void slotColorSimilarityChanged (double /*similarity*/, int /*processedSimilarity*/) {} + +protected: + // (only valid in slots connected to the respective signals above) + kpColor oldForegroundColor () const; + kpColor oldBackgroundColor () const; + double oldColorSimilarity () const; + +protected: + // returns true if m_currentPoint <= 1 pixel away from m_lastPoint + // or if there was no lastPoint + bool currentPointNextToLast () const; // (includes diagonal adjacency) + bool currentPointCardinallyNextToLast () const; // (only cardinally adjacent i.e. horiz & vert; no diag) + +// TODO: We should rename these. +// These are accessed from kpTool logic and our friends, kpCommandHistory, +// kpMainWindow, kpToolToolBar and kpView. +public: + void beginInternal (); + void endInternal (); + + void beginDrawInternal (); + void endDrawInternal (const QPoint &thisPoint, const QRect &normalizedRect, + bool wantEndShape = false); + void cancelShapeInternal (); + // TODO: Who is actually calling endShapeInternal()? + // Tools seem to call endShape() directly. + void endShapeInternal (const QPoint &thisPoint = QPoint (), + const QRect &normalizedRect = QRect ()); + + +// +// Mouse Events +// + +public: + // Note: _All_ events are forwarded from a kpView. + // The existence of a kpView implies the existence of a kpDocument. + + // If you're reimplementing any of these, you probably don't know what + // you're doing - reimplement begin(),beginDraw(),draw(),cancelShape(), + // endDraw() etc. instead. + virtual void mousePressEvent (QMouseEvent *e); + virtual void mouseMoveEvent (QMouseEvent *e); + virtual void mouseReleaseEvent (QMouseEvent *e); + + virtual void wheelEvent (QWheelEvent *e); + + +// +// Keyboard Events +// + +protected: + void seeIfAndHandleModifierKey (QKeyEvent *e); + + void arrowKeyPressDirection (const QKeyEvent *e, int *dx, int *dy); + void seeIfAndHandleArrowKeyPress (QKeyEvent *e); + + bool isDrawKey (int key); + void seeIfAndHandleBeginDrawKeyPress (QKeyEvent *e); + void seeIfAndHandleEndDrawKeyPress (QKeyEvent *e); + +public: + virtual void keyPressEvent (QKeyEvent *e); + virtual void keyReleaseEvent (QKeyEvent *e); + + virtual void inputMethodEvent (QInputMethodEvent *) {} + +private: + void keyUpdateModifierState (QKeyEvent *e); + void notifyModifierStateChanged (); + +protected: + virtual void setShiftPressed (bool pressed); + virtual void setControlPressed (bool pressed); + + virtual void setAltPressed (bool pressed); + + +// +// Other Events - 1. View Events +// + +public: + // WARNING: Do not call this "event()" as our QObject parent has a + // virtual function called that, that will pass us + // QObject events. We only care about events forwarded by + // kpView. + // REFACTOR: rename mousePressEvent() -> viewMousePressEvent() etc. + // to remind us that events are coming from the view - the tool + // is not a visible object. + virtual bool viewEvent (QEvent *e); + +public: + virtual void focusInEvent (QFocusEvent *e); + virtual void focusOutEvent (QFocusEvent *e); + +public: + virtual void enterEvent (QEvent *e); + virtual void leaveEvent (QEvent *e); + + +// +// Other Events - 2. Non-view Events +// REFACTOR: Group methods under this. +// + +protected: + // 0 = left, 1 = right, -1 = other (none, left+right, mid) + static int mouseButton (Qt::MouseButtons mouseButtons); + +public: + static int calculateLength (int start, int end); + + // + // User Notifications (Status Bar) + // + +public: + // Returns "(Left|Right) click to cancel." where Left or Right is chosen + // depending on which one is the _opposite_ of + static QString cancelUserMessage (int mouseButton); + QString cancelUserMessage () const; + + QString userMessage () const; + // WARNING: setUserMessage() will store a message different to , + // in unspecified ways (e.g. the name of the tool, followed + // by a colon and a space, will be prepended). userMessage() + // will return this different string. + void setUserMessage (const QString &userMessage = QString ()); + + QPoint userShapeStartPoint () const; + QPoint userShapeEndPoint () const; + void setUserShapePoints (const QPoint &startPoint = KP_INVALID_POINT, + const QPoint &endPoint = KP_INVALID_POINT, + bool setSize = true); + + QSize userShapeSize () const; + int userShapeWidth () const; + int userShapeHeight () const; + void setUserShapeSize (const QSize &size = KP_INVALID_SIZE); + void setUserShapeSize (int width, int height); + +signals: + void userMessageChanged (const QString &userMessage); + void userShapePointsChanged (const QPoint &startPoint = KP_INVALID_POINT, + const QPoint &endPoint = KP_INVALID_POINT); + void userShapeSizeChanged (const QSize &size); + void userShapeSizeChanged (int width, int height); + + +public: + // Call this before the user tries to cause the document or selection + // to resize from x to x. + // If at least one dimension increases, the new dimensions will take a + // large amount of memory (which causes thrashing, instability) and + // the old dimensions did not take a large amount of memory, ask the + // user if s/he really wants to perform the operation. + // + // Returns true if the operation should proceed, false otherwise. + // + // In order to make the translators' lives possible, this function cannot + // generate the , nor (without + // concantenating sentences and words with tense). However, it is + // recommended that you give them the following values: + // + // e.g.: + // text = i18n ("

(Rotating|Skewing) the (image|selection) to" + // " %1x%2 may take a substantial amount of memory." + // " This can reduce system" + // " responsiveness and cause other application resource" + // " problems.

").arg (newWidth, newHeight) + // + // "

Are you sure want to (rotate|skew) the" + // " (image|selection)?

"); + // caption = i18n ("Rotate (Image|Selection)?"); + // continueButtonText = i18n ("Rotat&e (Image|Selection)"); + static bool warnIfBigImageSize (int oldWidth, int oldHeight, + int newWidth, int newHeight, + const QString &text, + const QString &caption, + const QString &continueButtonText, + QWidget *parent); + +private: + kpToolPrivate *d; +}; + + +#endif // KP_TOOL_H diff --git a/kolourpaint/tools/kpToolAction.cpp b/kolourpaint/tools/kpToolAction.cpp new file mode 100644 index 00000000..41c395ee --- /dev/null +++ b/kolourpaint/tools/kpToolAction.cpp @@ -0,0 +1,70 @@ +/* + Copyright(c) 2003-2007 Clarence Dang + Copyright(c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include +#include + +#include + +//--------------------------------------------------------------------- + +kpToolAction::kpToolAction(const QString &text, + const QString &pic, const KShortcut &shortcut, + const QObject *receiver, const char *slot, + KActionCollection *ac, const QString &name) + : KToggleAction(KIcon(pic), text, ac) +{ + KToggleAction::setShortcut(shortcut); + + if ( receiver && slot ) + connect(this, SIGNAL(triggered(bool)), receiver, slot); + + updateToolTip(); + connect(this, SIGNAL(changed()), this, SLOT(updateToolTip())); + + ac->addAction(name, this); +} + +//--------------------------------------------------------------------- + +// protected +void kpToolAction::updateToolTip() +{ + const QString newToolTip = + kpTool::toolTipForTextAndShortcut(text(), shortcut()); + + if ( newToolTip == toolTip() ) + return; + + setToolTip(newToolTip); +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/tools/kpToolAction.h b/kolourpaint/tools/kpToolAction.h new file mode 100644 index 00000000..c02285c7 --- /dev/null +++ b/kolourpaint/tools/kpToolAction.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_ACTION_H +#define KP_TOOL_ACTION_H + +#include + +class KActionCollection; + +// Same as KToggleAction but shows the first single key trigger in the tooltip. + +class kpToolAction : public KToggleAction +{ + Q_OBJECT + + public: + kpToolAction(const QString &text, + const QString &pic, const KShortcut &shortcut, + const QObject *receiver, const char *slot, + KActionCollection *ac, const QString &name); + + private slots: + void updateToolTip(); +}; + + +#endif // KP_TOOL_ACTION_H diff --git a/kolourpaint/tools/kpToolColorPicker.cpp b/kolourpaint/tools/kpToolColorPicker.cpp new file mode 100644 index 00000000..9d2254fc --- /dev/null +++ b/kolourpaint/tools/kpToolColorPicker.cpp @@ -0,0 +1,145 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_COLOR_PICKER 0 + + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +kpToolColorPicker::kpToolColorPicker (kpToolEnvironment *environ, QObject *parent) + : kpTool (i18n ("Color Picker"), i18n ("Lets you select a color from the image"), + Qt::Key_C, + environ, parent, "tool_color_picker") +{ +} + +kpToolColorPicker::~kpToolColorPicker () +{ +} + + +// private +kpColor kpToolColorPicker::colorAtPixel (const QPoint &p) +{ +#if DEBUG_KP_TOOL_COLOR_PICKER && 0 + kDebug () << "kpToolColorPicker::colorAtPixel" << p; +#endif + + return kpPixmapFX::getColorAtPixel (document ()->image (), p); +} + + +// private +QString kpToolColorPicker::haventBegunDrawUserMessage () const +{ + return i18n ("Click to select a color."); +} + + +// public virtual [base kpTool] +void kpToolColorPicker::begin () +{ + setUserMessage (haventBegunDrawUserMessage ()); +} + +// public virtual [base kpTool] +void kpToolColorPicker::beginDraw () +{ + m_oldColor = color (mouseButton ()); + + setUserMessage (cancelUserMessage ()); +} + +// public virtual [base kpTool] +void kpToolColorPicker::draw (const QPoint &thisPoint, const QPoint &, const QRect &) +{ + const kpColor color = colorAtPixel (thisPoint); + + if (color.isValid ()) + { + environ ()->setColor (mouseButton (), color); + setUserShapePoints (thisPoint); + } + else + { + environ ()->setColor (mouseButton (), m_oldColor); + setUserShapePoints (); + } +} + +// public virtual [base kpTool] +void kpToolColorPicker::cancelShape () +{ + environ ()->setColor (mouseButton (), m_oldColor); + + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +// public virtual [base kpTool] +void kpToolColorPicker::releasedAllButtons () +{ + setUserMessage (haventBegunDrawUserMessage ()); + +} + +// public virtual [base kpTool] +void kpToolColorPicker::endDraw (const QPoint &thisPoint, const QRect &) +{ + const kpColor color = colorAtPixel (thisPoint); + + if (color.isValid ()) + { + kpToolColorPickerCommand *cmd = + new kpToolColorPickerCommand ( + mouseButton (), + color, m_oldColor, + environ ()->commandEnvironment ()); + + environ ()->commandHistory ()->addCommand (cmd, false/*no exec*/); + setUserMessage (haventBegunDrawUserMessage ()); + } + else + { + cancelShape (); + } +} + + +#include diff --git a/kolourpaint/tools/kpToolColorPicker.h b/kolourpaint/tools/kpToolColorPicker.h new file mode 100644 index 00000000..8075d228 --- /dev/null +++ b/kolourpaint/tools/kpToolColorPicker.h @@ -0,0 +1,71 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_COLOR_PICKER_H +#define KP_TOOL_COLOR_PICKER_H + + +#include +#include + + +class QPoint; +class QRect; + + +class kpToolColorPicker : public kpTool +{ +Q_OBJECT + +public: + kpToolColorPicker (kpToolEnvironment *environ, QObject *parent); + virtual ~kpToolColorPicker (); + + // generally the user goes to pick a color but wants to return to using + // his/her previous drawing tool + virtual bool returnToPreviousToolAfterEndDraw () const { return true; } + +private: + kpColor colorAtPixel (const QPoint &p); + + QString haventBegunDrawUserMessage () const; + +public: + virtual void begin (); + virtual void beginDraw (); + virtual void draw (const QPoint &thisPoint, const QPoint &, const QRect &); + virtual void cancelShape (); + virtual void releasedAllButtons (); + virtual void endDraw (const QPoint &thisPoint, const QRect &); + +private: + kpColor m_oldColor; +}; + + +#endif // KP_TOOL_COLOR_PICKER_H diff --git a/kolourpaint/tools/kpToolFloodFill.cpp b/kolourpaint/tools/kpToolFloodFill.cpp new file mode 100644 index 00000000..b6273127 --- /dev/null +++ b/kolourpaint/tools/kpToolFloodFill.cpp @@ -0,0 +1,170 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_FLOOD_FILL 0 + + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +struct kpToolFloodFillPrivate +{ + kpToolFloodFillCommand *currentCommand; +}; + +//--------------------------------------------------------------------- + +kpToolFloodFill::kpToolFloodFill (kpToolEnvironment *environ, QObject *parent) + : kpTool (i18n ("Flood Fill"), i18n ("Fills regions in the image"), + Qt::Key_F, + environ, parent, "tool_flood_fill"), + d (new kpToolFloodFillPrivate ()) +{ + d->currentCommand = 0; +} + +//--------------------------------------------------------------------- + +kpToolFloodFill::~kpToolFloodFill () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// private +QString kpToolFloodFill::haventBegunDrawUserMessage () const +{ + return i18n ("Click to fill a region."); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpToolFloodFill::begin () +{ + setUserMessage (haventBegunDrawUserMessage ()); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpToolFloodFill::beginDraw () +{ +#if DEBUG_KP_TOOL_FLOOD_FILL && 1 + kDebug () << "kpToolFloodFill::beginDraw()"; +#endif + + QApplication::setOverrideCursor (Qt::WaitCursor); + { + environ ()->flashColorSimilarityToolBarItem (); + + // Flood Fill is an expensive CPU operation so we only fill at a + // mouse click (beginDraw ()), not on mouse move (virtually draw()) + d->currentCommand = new kpToolFloodFillCommand ( + currentPoint ().x (), currentPoint ().y (), + color (mouseButton ()), processedColorSimilarity (), + environ ()->commandEnvironment ()); + + #if DEBUG_KP_TOOL_FLOOD_FILL && 1 + kDebug () << "\tperforming new-doc-corner-case check"; + #endif + + if (document ()->url ().isEmpty () && !document ()->isModified ()) + { + // Collect the colour that gets changed before we change the pixels + // (execute() below). Needed in unexecute(). + d->currentCommand->prepareColorToChange (); + + d->currentCommand->setFillEntireImage (); + } + + d->currentCommand->execute (); + } + QApplication::restoreOverrideCursor (); + + setUserMessage (cancelUserMessage ()); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpToolFloodFill::draw (const QPoint &thisPoint, const QPoint &, const QRect &) +{ + setUserShapePoints (thisPoint); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpToolFloodFill::cancelShape () +{ + d->currentCommand->unexecute (); + + delete d->currentCommand; + d->currentCommand = 0; + + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpToolFloodFill::releasedAllButtons () +{ + setUserMessage (haventBegunDrawUserMessage ()); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpToolFloodFill::endDraw (const QPoint &, const QRect &) +{ + environ ()->commandHistory ()->addCommand (d->currentCommand, + false/*no exec - we already did it up there*/); + + // Don't delete - it just got added to the history. + d->currentCommand = 0; + setUserMessage (haventBegunDrawUserMessage ()); +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/tools/kpToolFloodFill.h b/kolourpaint/tools/kpToolFloodFill.h new file mode 100644 index 00000000..49691e92 --- /dev/null +++ b/kolourpaint/tools/kpToolFloodFill.h @@ -0,0 +1,60 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_FLOOD_FILL_H +#define KP_TOOL_FLOOD_FILL_H + + +#include + + +class kpToolFloodFill : public kpTool +{ +Q_OBJECT + +public: + kpToolFloodFill (kpToolEnvironment *environ, QObject *parent); + virtual ~kpToolFloodFill (); + +private: + QString haventBegunDrawUserMessage () const; + +public: + virtual void begin (); + virtual void beginDraw (); + virtual void draw (const QPoint &thisPoint, const QPoint &, const QRect &); + virtual void cancelShape (); + virtual void releasedAllButtons (); + virtual void endDraw (const QPoint &, const QRect &); + +private: + struct kpToolFloodFillPrivate * const d; +}; + + +#endif // KP_TOOL_FLOOD_FILL_H diff --git a/kolourpaint/tools/kpToolPrivate.h b/kolourpaint/tools/kpToolPrivate.h new file mode 100644 index 00000000..86609c56 --- /dev/null +++ b/kolourpaint/tools/kpToolPrivate.h @@ -0,0 +1,83 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolPrivate_H +#define kpToolPrivate_H + + +#include +#include + +#include +#ifdef Q_OS_WIN + #include + #undef environ // macro on win32 +#endif + +class kpToolAction; +class kpToolEnvironment; + + +struct kpToolPrivate +{ + // Initialisation / properties. + QString text; + QString description; + int key; + + kpToolAction *action; + + // Drawing state. + bool began; + bool beganDraw; // set after beginDraw() is called, unset before endDraw() is called + int mouseButton; /* 0 = left, 1 = right */ + bool shiftPressed, controlPressed, altPressed; // m_altPressed is unreliable + QPoint startPoint, + currentPoint, currentViewPoint, + lastPoint; + + kpView *viewUnderStartPoint; + + + // Set to 2 when the user swaps the foreground and background color. + // + // When nonzero, it suppresses the foreground and background "color changed" + // signals and is decremented back down to 0 separately by the foreground + // code and background code. + int ignoreColorSignals; + + // Statusbar. + QString userMessage; + QPoint userShapeStartPoint, userShapeEndPoint; + QSize userShapeSize; + + kpToolEnvironment *environ; +}; + + +#endif // kpToolPrivate_H diff --git a/kolourpaint/tools/kpToolZoom.cpp b/kolourpaint/tools/kpToolZoom.cpp new file mode 100644 index 00000000..edd0eece --- /dev/null +++ b/kolourpaint/tools/kpToolZoom.cpp @@ -0,0 +1,252 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_ZOOM 0 + + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +struct DrawZoomRectPackage +{ + QRect normalizedRect; +}; + +static void DrawZoomRect (kpImage *destImage, + const QPoint &topLeft, + void *userData) +{ + DrawZoomRectPackage *pack = static_cast (userData); + + kpPixmapFX::drawRect(destImage, + topLeft.x (), topLeft.y (), pack->normalizedRect.width (), pack->normalizedRect.height (), + kpColor::Yellow, 1/*pen width*/, + kpColor::Invalid/*no background*/, + kpColor::Green); +} + + +struct kpToolZoomPrivate +{ + bool dragHasBegun, dragCompleted; + DrawZoomRectPackage drawPackage; +}; + +kpToolZoom::kpToolZoom (kpToolEnvironment *environ, QWidget *parent) + : kpTool (i18n ("Zoom"), i18n ("Zooms in and out of the image"), + Qt::Key_Z, + environ, parent, "tool_zoom"), + d (new kpToolZoomPrivate ()) +{ + // different from objectName() + action()->setIcon(KIcon("zoom-original")); +} + +//--------------------------------------------------------------------- + +kpToolZoom::~kpToolZoom () +{ + delete d; +} + +//--------------------------------------------------------------------- +// public virtual [base kpTool] + +bool kpToolZoom::returnToPreviousToolAfterEndDraw () const +{ + // If the user clicks to zoom in or out, s/he generally wants to click + // some more to get the exact zoom level wanted. + // + // However, if they drag out a rectangle to zoom into a particular area, + // they probably don't need to do any further zooming so we can return + // them to their previous tool. + // + // Note that if they cancel a drag (cancelShape()), we do _not_ return + // them to their previous tool, unlike the Color Picker. This is because + // cancelling a drag generally means that the user got the top-left of + // the drag wrong and wants to try a different top-left. In contrast, + // with the Color Picket, if you've made a mistake while pressing the + // mouse, you can just keep holding down the mouse and drag to the intended + // color -- a cancel with a Color Picker really means "I've decided not + // to pick another color after all", not "I got the start of the drag wrong" + // because you can correct that drag. + return d->dragCompleted; +} + + +// private +QString kpToolZoom::haventBegunDrawUserMessage () const +{ + return i18n ("Click to zoom in/out or left drag to zoom into a specific area."); +} + + +// public virtual [base kpTool] +void kpToolZoom::begin () +{ + viewManager ()->setCursor (Qt::CrossCursor); + + setUserMessage (haventBegunDrawUserMessage ()); +} + +// public virtual [base kpTool] +void kpToolZoom::end () +{ + viewManager ()->unsetCursor (); +} + + +// public virtual [base kpTool] +void kpToolZoom::globalDraw () +{ +#if DEBUG_KP_TOOL_ZOOM + kDebug () << "CALL"; +#endif + environ ()->fitToPage (); +} + + +// public virtual [base kpTool] +void kpToolZoom::beginDraw () +{ + d->dragHasBegun = false; + d->dragCompleted = false; + + setUserMessage (cancelUserMessage ()); +} + +// public virtual [base kpTool] +void kpToolZoom::draw (const QPoint &thisPoint, const QPoint &, const QRect &normalizedRect) +{ +#if DEBUG_KP_TOOL_ZOOM + kDebug () << "kpToomZoom::draw() currentPoint=" << currentPoint () + << " lastPoint=" << lastPoint () + << endl; +#endif + + // TODO: Need accidental drag detection from selection tool (when dragging + // out new selection) + + if (!d->dragHasBegun) + { + if (thisPoint == startPoint ()) + return; + + // Left mouse drags select an area to zoom into. + // However, it wouldn't make sense to select an area to "zoom out of" + // (using the right mouse button). Therefore, make RMB drags do the + // same as RMB clicks i.e. a simple zoom out, with no "area" to worry + // about. + if (mouseButton () == 1/*RMB*/) + return; + + d->dragHasBegun = true; + } + + + d->drawPackage.normalizedRect = normalizedRect; + + kpTempImage newTempImage (false/*always display*/, + normalizedRect.topLeft (), + &::DrawZoomRect, &d->drawPackage, + normalizedRect.width (), normalizedRect.height ()); + + viewManager ()->setFastUpdates (); + { + viewManager ()->setTempImage (newTempImage); + } + viewManager ()->restoreFastUpdates (); +} + +// public virtual [base kpTool] +void kpToolZoom::cancelShape () +{ + viewManager ()->invalidateTempImage (); + + // LOREFACTOR: A lot of tools use this - push up to kpTool? + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +// public virtual [base kpTool] +void kpToolZoom::releasedAllButtons () +{ + setUserMessage (haventBegunDrawUserMessage ()); +} + +// public virtual [base kpTool] +void kpToolZoom::endDraw (const QPoint &, const QRect &normalizedRect) +{ +#if DEBUG_KP_TOOL_ZOOM + kDebug () << "kpToolZoom::endDraw(rect=" << normalizedRect << ")" + << " dragHasBegun=" << d->dragHasBegun << endl; +#endif + + // TODO: This cursor doesn't stay on for long enough because zooming uses + // event loop tricks. + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + viewManager ()->invalidateTempImage (); + + // Click? + if (!d->dragHasBegun) + { + if (mouseButton () == 0/*LMB*/) + environ ()->zoomIn (true/*center under cursor*/); + else + environ ()->zoomOut (false/*don't center under cursor - as is + confusing behaviour when zooming + out*/); + } + // Drag? + else if (normalizedRect.isValid()) + { + environ ()->zoomToRect ( + normalizedRect, + false/*don't account for grips*/, + true/*care about width*/, true/*care about height*/); + + d->dragCompleted = true; + } +} + + +#include diff --git a/kolourpaint/tools/kpToolZoom.h b/kolourpaint/tools/kpToolZoom.h new file mode 100644 index 00000000..6d24c604 --- /dev/null +++ b/kolourpaint/tools/kpToolZoom.h @@ -0,0 +1,66 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_ZOOM_H +#define KP_TOOL_ZOOM_H + + +#include + + +class kpToolZoom : public kpTool +{ +Q_OBJECT + +public: + kpToolZoom (kpToolEnvironment *environ, QWidget *parent); + virtual ~kpToolZoom (); + + virtual bool returnToPreviousToolAfterEndDraw () const; + +private: + QString haventBegunDrawUserMessage () const; + +public: + virtual void begin (); + virtual void end (); + + virtual void globalDraw (); + + virtual void beginDraw (); + virtual void draw (const QPoint &thisPoint, const QPoint &, const QRect &); + virtual void cancelShape (); + virtual void releasedAllButtons (); + virtual void endDraw (const QPoint &thisPoint, const QRect &); + +private: + struct kpToolZoomPrivate *d; +}; + + +#endif // KP_TOOL_ZOOM_H diff --git a/kolourpaint/tools/kpTool_Drawing.cpp b/kolourpaint/tools/kpTool_Drawing.cpp new file mode 100644 index 00000000..101bbdba --- /dev/null +++ b/kolourpaint/tools/kpTool_Drawing.cpp @@ -0,0 +1,418 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// Tool methods for drawing shapes (subclasses reimplement most of these). +// + + +#define DEBUG_KP_TOOL 0 + + +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#undef environ // macro on win32 + +//--------------------------------------------------------------------- + +// protected +int kpTool::mouseButton () const +{ + return d->mouseButton; +} + +//--------------------------------------------------------------------- + +// protected +bool kpTool::shiftPressed () const +{ + return d->shiftPressed; +} + +//--------------------------------------------------------------------- + +// protected +bool kpTool::controlPressed () const +{ + return d->controlPressed; +} + +//--------------------------------------------------------------------- + +// protected +bool kpTool::altPressed () const +{ + return d->altPressed; +} + + +// protected +QPoint kpTool::startPoint () const +{ + return d->startPoint; +} + +//--------------------------------------------------------------------- + +// protected +QPoint kpTool::currentPoint () const +{ + // TODO: Q_ASSERT (hasBegun()) and similar in other accessors. + // We currently violate these kinds of invariants. + return d->currentPoint; +} + +//--------------------------------------------------------------------- + +// protected +QPoint kpTool::currentViewPoint () const +{ + return d->currentViewPoint; +} + +//--------------------------------------------------------------------- + +// protected +QRect kpTool::normalizedRect () const +{ + return kpPainter::normalizedRect(d->startPoint, d->currentPoint); +} + +//--------------------------------------------------------------------- + +// protected +QPoint kpTool::lastPoint () const +{ + return d->lastPoint; +} + +//--------------------------------------------------------------------- + +// protected +kpView *kpTool::viewUnderStartPoint () const +{ + return d->viewUnderStartPoint; +} + +//--------------------------------------------------------------------- + +// protected +kpView *kpTool::viewUnderCursor () const +{ + kpViewManager *vm = viewManager (); + return vm ? vm->viewUnderCursor () : 0; +} + +//--------------------------------------------------------------------- + +void kpTool::beginInternal () +{ +#if DEBUG_KP_TOOL + kDebug () << "kpTool::beginInternal()"; +#endif + + if (!d->began) + { + // clear leftover statusbar messages + setUserMessage (); + d->currentPoint = calculateCurrentPoint (); + d->currentViewPoint = calculateCurrentPoint (false/*view point*/); + setUserShapePoints (d->currentPoint); + + // TODO: Audit all the code in this file - states like "d->began" & + // "d->beganDraw" should be set before calling user func. + // Also, d->currentPoint should be more frequently initialised. + + // call user virtual func + begin (); + + // we've starting using the tool... + d->began = true; + + // but we haven't started drawing with it + d->beganDraw = false; + + + uint keyState = QApplication::keyboardModifiers (); + + d->shiftPressed = (keyState & Qt::ShiftModifier); + d->controlPressed = (keyState & Qt::ControlModifier); + + // TODO: Can't do much about ALT - unless it's always KApplication::Modifier1? + // Ditto for everywhere else where I set SHIFT & CTRL but not alt. + // COMPAT: Later: This is now supported by Qt. + d->altPressed = false; + } +} + +//--------------------------------------------------------------------- + +void kpTool::endInternal () +{ + if (d->began) + { + // before we can stop using the tool, we must stop the current drawing operation (if any) + if (hasBegunShape ()) + endShapeInternal (d->currentPoint, normalizedRect ()); + + // call user virtual func + end (); + + // clear leftover statusbar messages + setUserMessage (); + setUserShapePoints (calculateCurrentPoint ()); + + // we've stopped using the tool... + d->began = false; + + // and so we can't be drawing with it + d->beganDraw = false; + + d->environ->hideAllToolWidgets (); + } +} + +//--------------------------------------------------------------------- + +// virtual +void kpTool::begin () +{ +#if DEBUG_KP_TOOL + kDebug () << "kpTool::begin() base implementation"; +#endif +} + +//--------------------------------------------------------------------- + +// virtual +void kpTool::end () +{ +#if DEBUG_KP_TOOL + kDebug () << "kpTool::end() base implementation"; +#endif +} + +//--------------------------------------------------------------------- + + +bool kpTool::hasBegun () const { return d->began; } + +//--------------------------------------------------------------------- + +bool kpTool::hasBegunDraw () const { return d->beganDraw; } + +//--------------------------------------------------------------------- + +// virtual +bool kpTool::hasBegunShape () const { return hasBegunDraw (); } + +//--------------------------------------------------------------------- + + +void kpTool::beginDrawInternal () +{ + if (!d->beganDraw) + { + beginDraw (); + + d->beganDraw = true; + emit beganDraw (d->currentPoint); + } +} + +//--------------------------------------------------------------------- + +// virtual +void kpTool::beginDraw () +{ +} + +//--------------------------------------------------------------------- + +// virtual +void kpTool::hover (const QPoint &point) +{ +#if DEBUG_KP_TOOL + kDebug () << "kpTool::hover" << point + << " base implementation" + << endl; +#endif + + setUserShapePoints (point); +} + +//--------------------------------------------------------------------- + +// virtual +void kpTool::globalDraw () +{ +} + +//--------------------------------------------------------------------- + +// virtual +void kpTool::reselect () +{ +#if DEBUG_KP_TOOL + kDebug () << "kpTool::reselect() base implementation"; +#endif +} + +//--------------------------------------------------------------------- + + +// virtual +void kpTool::draw (const QPoint &, const QPoint &, const QRect &) +{ +} + +//--------------------------------------------------------------------- + +// private +void kpTool::drawInternal () +{ + draw (d->currentPoint, d->lastPoint, normalizedRect ()); +} + +//--------------------------------------------------------------------- + + +// also called by kpView +void kpTool::cancelShapeInternal () +{ + if (hasBegunShape ()) + { + d->beganDraw = false; + cancelShape (); + d->viewUnderStartPoint = 0; + + emit cancelledShape (viewUnderCursor () ? d->currentPoint : KP_INVALID_POINT); + + if (viewUnderCursor ()) + hover (d->currentPoint); + else + { + d->currentPoint = KP_INVALID_POINT; + d->currentViewPoint = KP_INVALID_POINT; + hover (d->currentPoint); + } + + if (returnToPreviousToolAfterEndDraw ()) + { + d->environ->selectPreviousTool (); + } + } +} + +//--------------------------------------------------------------------- + +// virtual +void kpTool::cancelShape () +{ + kWarning () << "Tool cannot cancel operation!" ; +} + +//--------------------------------------------------------------------- + +void kpTool::releasedAllButtons () +{ +} + +//--------------------------------------------------------------------- + +void kpTool::endDrawInternal (const QPoint &thisPoint, const QRect &normalizedRect, + bool wantEndShape) +{ +#if DEBUG_KP_TOOL && 1 + kDebug () << "kpTool::endDrawInternal() wantEndShape=" << wantEndShape; +#endif + + if (wantEndShape && !hasBegunShape ()) + return; + else if (!wantEndShape && !hasBegunDraw ()) + return; + + d->beganDraw = false; + + if (wantEndShape) + { + #if DEBUG_KP_TOOL && 0 + kDebug () << "\tcalling endShape()"; + #endif + endShape (thisPoint, normalizedRect); + } + else + { + #if DEBUG_KP_TOOL && 0 + kDebug () << "\tcalling endDraw()"; + #endif + endDraw (thisPoint, normalizedRect); + } + d->viewUnderStartPoint = 0; + + emit endedDraw (d->currentPoint); + if (viewUnderCursor ()) + hover (d->currentPoint); + else + { + d->currentPoint = KP_INVALID_POINT; + d->currentViewPoint = KP_INVALID_POINT; + hover (d->currentPoint); + } + + if (returnToPreviousToolAfterEndDraw ()) + { + d->environ->selectPreviousTool (); + } +} + +//--------------------------------------------------------------------- + +// private +void kpTool::endShapeInternal (const QPoint &thisPoint, const QRect &normalizedRect) +{ + endDrawInternal (thisPoint, normalizedRect, true/*end shape*/); +} + +//--------------------------------------------------------------------- + +// virtual +void kpTool::endDraw (const QPoint &, const QRect &) +{ +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/tools/kpTool_KeyboardEvents.cpp b/kolourpaint/tools/kpTool_KeyboardEvents.cpp new file mode 100644 index 00000000..f81dcfab --- /dev/null +++ b/kolourpaint/tools/kpTool_KeyboardEvents.cpp @@ -0,0 +1,412 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// Tool reaction to view keyboard input. +// + + +#define DEBUG_KP_TOOL 0 + + +// TODO: reduce number of includes +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +void kpTool::seeIfAndHandleModifierKey (QKeyEvent *e) +{ + switch (e->key ()) + { + case 0: + case Qt::Key_unknown: + #if DEBUG_KP_TOOL && 0 + kDebug () << "kpTool::seeIfAndHandleModifierKey() picked up unknown key!"; + #endif + // HACK: around Qt bug: if you hold a modifier before you start the + // program and then release it over the view, + // Qt reports it as the release of an unknown key + // Qt4 update: I don't think this happens anymore... + // --- fall thru and update all modifiers --- + + case Qt::Key_Alt: + case Qt::Key_Shift: + case Qt::Key_Control: + #if DEBUG_KP_TOOL && 0 + kDebug () << "kpTool::setIfAndHandleModifierKey() accepting"; + #endif + keyUpdateModifierState (e); + + e->accept (); + break; + } +} + +//--------------------------------------------------------------------- + +// Returns in and the direction the arrow key "e->key()" is +// pointing in or (0,0) if it's not a recognised arrow key. +void kpTool::arrowKeyPressDirection (const QKeyEvent *e, int *dx, int *dy) +{ + int dxLocal = 0, dyLocal = 0; + + switch (e->key ()) + { + case Qt::Key_Home: dxLocal = -1, dyLocal = -1; break; + case Qt::Key_Up: dyLocal = -1; break; + case Qt::Key_PageUp: dxLocal = +1, dyLocal = -1; break; + + case Qt::Key_Left: dxLocal = -1; break; + case Qt::Key_Right: dxLocal = +1; break; + + case Qt::Key_End: dxLocal = -1, dyLocal = +1; break; + case Qt::Key_Down: dyLocal = +1; break; + case Qt::Key_PageDown: dxLocal = +1, dyLocal = +1; break; + } + + if (dx) + *dx = dxLocal; + if (dy) + *dy = dyLocal; +} + +//--------------------------------------------------------------------- + +void kpTool::seeIfAndHandleArrowKeyPress (QKeyEvent *e) +{ + int dx, dy; + + arrowKeyPressDirection (e, &dx, &dy); + if (dx == 0 && dy == 0) + return; + + + kpView * const view = viewUnderCursor (); + if (!view) + return; + + + const QPoint oldPoint = view->mapFromGlobal (QCursor::pos ()); +#if DEBUG_KP_TOOL && 0 + kDebug () << "\toldPoint=" << oldPoint + << " dx=" << dx << " dy=" << dy << endl; +#endif + + + const int viewIncX = (dx ? qMax (1, view->zoomLevelX () / 100) * dx : 0); + const int viewIncY = (dy ? qMax (1, view->zoomLevelY () / 100) * dy : 0); + + int newViewX = oldPoint.x () + viewIncX; + int newViewY = oldPoint.y () + viewIncY; + + +#if DEBUG_KP_TOOL && 0 + kDebug () << "\tnewPoint=" << QPoint (newViewX, newViewY); +#endif + + // Make sure we really moved at least one doc point (needed due to + // rounding error). + + if (view->transformViewToDoc (QPoint (newViewX, newViewY)) == + view->transformViewToDoc (oldPoint)) + { + newViewX += viewIncX, newViewY += viewIncY; + + #if DEBUG_KP_TOOL && 0 + kDebug () << "\tneed adjust for doc - newPoint=" + << QPoint (newViewX, newViewY) << endl; + #endif + } + + + // TODO: visible width/height (e.g. with scrollbars) + const int x = qMin (qMax (newViewX, 0), view->width () - 1); + const int y = qMin (qMax (newViewY, 0), view->height () - 1); + + // QCursor::setPos conveniently causes mouseMoveEvents + QCursor::setPos (view->mapToGlobal (QPoint (x, y))); + e->accept (); +} + +//--------------------------------------------------------------------- + +bool kpTool::isDrawKey (int key) +{ + return (key == Qt::Key_Enter || + key == Qt::Key_Return || + key == Qt::Key_Insert || + key == Qt::Key_Clear/*Numpad 5 Key*/ || + key == Qt::Key_L); +} + +//--------------------------------------------------------------------- + +void kpTool::seeIfAndHandleBeginDrawKeyPress (QKeyEvent *e) +{ + if (e->isAutoRepeat ()) + return; + + if (!isDrawKey (e->key ())) + return; + +#if DEBUG_KP_TOOL && 0 + kDebug () << "kpTool::seeIfAndHandleBeginDrawKeyPress() accept"; +#endif + + + // TODO: wrong for dragging lines outside of view (for e.g.) + kpView * const view = viewUnderCursor (); + if (!view) + return; + + + // TODO: what about the modifiers? + QMouseEvent me (QEvent::MouseButtonPress, + view->mapFromGlobal (QCursor::pos ()), + Qt::LeftButton, + Qt::LeftButton/*button state after event*/, + Qt::NoModifier); + mousePressEvent (&me); + e->accept (); +} + +void kpTool::seeIfAndHandleEndDrawKeyPress (QKeyEvent *e) +{ +#if DEBUG_KP_TOOL && 0 + kDebug () << "kpTool::setIfAndHandleEndDrawKeyPress() key=" << e->key () + << " isAutoRepeat=" << e->isAutoRepeat () + << " isDrawKey=" << isDrawKey (e->key ()) + << " view=" << viewUnderCursor () + << endl; +#endif + + if (e->isAutoRepeat ()) + return; + + if (!isDrawKey (e->key ())) + return; + +#if DEBUG_KP_TOOL && 0 + kDebug () << "kpTool::seeIfAndHandleEndDrawKeyPress() accept"; +#endif + + + kpView * const view = viewUnderCursor (); + if (!view) + return; + + + // TODO: what about the modifiers? + QMouseEvent me (QEvent::MouseButtonRelease, + view->mapFromGlobal (QCursor::pos ()), + Qt::LeftButton, + Qt::NoButton/*button state after event*/, + Qt::NoModifier); + mouseReleaseEvent (&me); + + e->accept (); +} + +//--------------------------------------------------------------------- + +void kpTool::keyPressEvent (QKeyEvent *e) +{ +#if DEBUG_KP_TOOL && 0 + kDebug () << "kpTool::keyPressEvent() key=" << (int *) e->key () + << " stateAfter: modifiers=" << (int *) (int) e->modifiers () + << " isAutoRep=" << e->isAutoRepeat () + << endl; +#endif + + e->ignore (); + + + seeIfAndHandleModifierKey (e); + if (e->isAccepted ()) + return; + + seeIfAndHandleArrowKeyPress (e); + if (e->isAccepted ()) + return; + + seeIfAndHandleBeginDrawKeyPress (e); + if (e->isAccepted ()) + return; + + + switch (e->key ()) + { + case Qt::Key_Delete: + d->environ->deleteSelection (); + break; + + case Qt::Key_Escape: + if (hasBegunDraw ()) + { + cancelShapeInternal (); + e->accept (); + } + + break; + } +} + +//--------------------------------------------------------------------- + +void kpTool::keyReleaseEvent (QKeyEvent *e) +{ +#if DEBUG_KP_TOOL && 0 + kDebug () << "kpTool::keyReleaseEvent() key=" << (int *) e->key () + << " stateAfter: modifiers=" << (int *) (int) e->modifiers () + << " isAutoRep=" << e->isAutoRepeat () + << endl; +#endif + + e->ignore (); + + seeIfAndHandleModifierKey (e); + if (e->isAccepted ()) + return; + + seeIfAndHandleEndDrawKeyPress (e); + if (e->isAccepted ()) + return; +} + +//--------------------------------------------------------------------- + +// private +void kpTool::keyUpdateModifierState (QKeyEvent *e) +{ +#if DEBUG_KP_TOOL && 0 + kDebug () << "kpTool::keyUpdateModifierState() e->key=" << (int *) e->key (); + kDebug () << "\tshift=" + << (e->modifiers () & Qt::ShiftModifier) + << " control=" + << (e->modifiers () & Qt::ControlModifier) + << " alt=" + << (e->modifiers () & Qt::AltModifier) + << endl; +#endif + if (e->key () & (Qt::Key_Alt | Qt::Key_Shift | Qt::Key_Control)) + { + #if DEBUG_KP_TOOL && 0 + kDebug () << "\t\tmodifier changed - use e's claims"; + #endif + setShiftPressed (e->modifiers () & Qt::ShiftModifier); + setControlPressed (e->modifiers () & Qt::ControlModifier); + setAltPressed (e->modifiers () & Qt::AltModifier); + } + // See seeIfAndHandleModifierKey() for why this code path exists. + else + { + #if DEBUG_KP_TOOL && 0 + kDebug () << "\t\tmodifiers not changed - figure out the truth"; + #endif + const Qt::KeyboardModifiers keyState = QApplication::keyboardModifiers (); + + setShiftPressed (keyState & Qt::ShiftModifier); + setControlPressed (keyState & Qt::ControlModifier); + setAltPressed (keyState & Qt::AltModifier); + } +} + +//--------------------------------------------------------------------- + +void kpTool::notifyModifierStateChanged () +{ + if (careAboutModifierState ()) + { + if (d->beganDraw) + draw (d->currentPoint, d->lastPoint, normalizedRect ()); + else + { + d->currentPoint = calculateCurrentPoint (); + d->currentViewPoint = calculateCurrentPoint (false/*view point*/); + hover (d->currentPoint); + } + } +} + +//--------------------------------------------------------------------- + +void kpTool::setShiftPressed (bool pressed) +{ + if (pressed == d->shiftPressed) + return; + + d->shiftPressed = pressed; + + notifyModifierStateChanged (); +} + +//--------------------------------------------------------------------- + +void kpTool::setControlPressed (bool pressed) +{ + if (pressed == d->controlPressed) + return; + + d->controlPressed = pressed; + + notifyModifierStateChanged (); +} + +//--------------------------------------------------------------------- + +void kpTool::setAltPressed (bool pressed) +{ + if (pressed == d->altPressed) + return; + + d->altPressed = pressed; + + notifyModifierStateChanged (); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/tools/kpTool_MouseEvents.cpp b/kolourpaint/tools/kpTool_MouseEvents.cpp new file mode 100644 index 00000000..83f106c1 --- /dev/null +++ b/kolourpaint/tools/kpTool_MouseEvents.cpp @@ -0,0 +1,337 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// Tool reaction to view mouse input. +// + +#define DEBUG_KP_TOOL 0 + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +//--------------------------------------------------------------------- + +// HITODO: If you press a mouse button and move it out of the view _really_ fast +// and let go of the mouse button outside of the view, a mouseRelease +// event will not be generated, so the tool will still be in drawing mode +// (this is especially noticeable with the spraycan). +// +// When you move the mouse back into the view, it will still continue +// continue drawing even though no mouse button is held down. +// +// It is somewhat hard to reproduce so the best way is to position the +// mouse close to an edge of the view. If you do it right, no mouseMoveEvent +// is generated at _all_, until you move it back into the view. +void kpTool::mousePressEvent (QMouseEvent *e) +{ +#if DEBUG_KP_TOOL && 1 + kDebug () << "kpTool::mousePressEvent pos=" << e->pos () + << " button=" << (int) e->button () + << " stateAfter: buttons=" << (int *) (int) e->buttons () + << " modifiers=" << (int *) (int) e->modifiers () + << " beganDraw=" << d->beganDraw << endl; +#endif + + if (e->button () == Qt::MidButton) + { + const QString text = QApplication::clipboard ()->text (QClipboard::Selection); + #if DEBUG_KP_TOOL && 1 + kDebug () << "\tMMB pasteText='" << text << "'"; + #endif + if (!text.isEmpty ()) + { + if (hasBegunShape ()) + { + #if DEBUG_KP_TOOL && 1 + kDebug () << "\t\thasBegunShape - end"; + #endif + endShapeInternal (d->currentPoint, normalizedRect ()); + } + + if (viewUnderCursor ()) + { + d->environ->pasteTextAt (text, + viewUnderCursor ()->transformViewToDoc (e->pos ()), + true/*adjust topLeft so that cursor isn't + on top of resize handle*/); + } + + return; + } + } + + int mb = mouseButton (e->buttons ()); +#if DEBUG_KP_TOOL && 1 + kDebug () << "\tmb=" << mb << " d->beganDraw=" << d->beganDraw; +#endif + + if (mb == -1 && !d->beganDraw) + { + // Ignore mouse press. + return; + } + + if (d->beganDraw) + { + if (mb == -1 || mb != d->mouseButton) + { + #if DEBUG_KP_TOOL && 1 + kDebug () << "\tCancelling operation as " << mb << " == -1 or != " << d->mouseButton; + #endif + + kpView *view = viewUnderStartPoint (); + Q_ASSERT (view); + + // if we get a mousePressEvent when we're drawing, then the other + // mouse button must have been pressed + d->currentPoint = view->transformViewToDoc (e->pos ()); + d->currentViewPoint = e->pos (); + cancelShapeInternal (); + } + + return; + } + + kpView *view = viewUnderCursor (); + Q_ASSERT (view); + +#if DEBUG_KP_TOOL && 1 + if (view) + kDebug () << "\tview=" << view->objectName (); +#endif + + + // let user know what mouse button is being used for entire draw + d->mouseButton = mouseButton (e->buttons ()); + d->shiftPressed = (e->modifiers () & Qt::ShiftModifier); + d->controlPressed = (e->modifiers () & Qt::ControlModifier); + d->altPressed = (e->modifiers () & Qt::AltModifier); + d->startPoint = d->currentPoint = view->transformViewToDoc (e->pos ()); + d->currentViewPoint = e->pos (); + d->viewUnderStartPoint = view; + d->lastPoint = QPoint (-1, -1); + +#if DEBUG_KP_TOOL && 1 + kDebug () << "\tBeginning draw @ " << d->currentPoint; +#endif + + beginDrawInternal (); + + draw (d->currentPoint, d->lastPoint, QRect (d->currentPoint, d->currentPoint)); + d->lastPoint = d->currentPoint; +} + +//--------------------------------------------------------------------- + +// OPT: If the mouse is moving in terms of view pixels, it still might +// not be moving in terms of document pixels (when zoomed in). +// +// So we should detect this and not call draw() or hover(). +// +// However, kpToolSelection needs hover() to be called on all view +// point changes, not just document points, since the selection resize +// handles may be smaller than document points. Also, I wonder if +// selections' accidental drag detection feature cares? +void kpTool::mouseMoveEvent (QMouseEvent *e) +{ +#if DEBUG_KP_TOOL && 0 + kDebug () << "kpTool::mouseMoveEvent pos=" << e->pos () + << " stateAfter: buttons=" << (int *) (int) e->buttons () + << " modifiers=" << (int *) (int) e->modifiers (); + kpView *v0 = viewUnderCursor (), + *v1 = viewManager ()->viewUnderCursor (true/*use Qt*/), + *v2 = viewUnderStartPoint (); + kDebug () << "\tviewUnderCursor=" << (v0 ? v0->objectName () : "(none)") + << " viewUnderCursorQt=" << (v1 ? v1->objectName () : "(none)") + << " viewUnderStartPoint=" << (v2 ? v2->objectName () : "(none)"); + kDebug () << "\tfocusWidget=" << kapp->focusWidget (); + kDebug () << "\tbeganDraw=" << d->beganDraw; +#endif + + d->shiftPressed = (e->modifiers () & Qt::ShiftModifier); + d->controlPressed = (e->modifiers () & Qt::ControlModifier); + d->altPressed = (e->modifiers () & Qt::AltModifier); + + if (d->beganDraw) + { + kpView *view = viewUnderStartPoint (); + Q_ASSERT (view); + + d->currentPoint = view->transformViewToDoc (e->pos ()); + d->currentViewPoint = e->pos (); + + #if DEBUG_KP_TOOL && 0 + kDebug () << "\tDraw!"; + #endif + + bool dragScrolled = false; + movedAndAboutToDraw (d->currentPoint, d->lastPoint, view->zoomLevelX (), &dragScrolled); + + if (dragScrolled) + { + d->currentPoint = calculateCurrentPoint (); + d->currentViewPoint = calculateCurrentPoint (false/*view point*/); + + // Scrollview has scrolled contents and has scheduled an update + // for the newly exposed region. If draw() schedules an update + // as well (instead of immediately updating), the scrollview's + // update will be executed first and it'll only update part of + // the screen resulting in ugly tearing of the viewManager's + // tempImage. + viewManager ()->setFastUpdates (); + } + + drawInternal (); + + if (dragScrolled) + viewManager ()->restoreFastUpdates (); + + d->lastPoint = d->currentPoint; + } + else + { + kpView *view = viewUnderCursor (); + if (!view) // possible if cancelShape()'ed but still holding down initial mousebtn + { + d->currentPoint = KP_INVALID_POINT; + d->currentViewPoint = KP_INVALID_POINT; + return; + } + + d->currentPoint = view->transformViewToDoc (e->pos ()); + d->currentViewPoint = e->pos (); + hover (d->currentPoint); + } +} + +//--------------------------------------------------------------------- + +void kpTool::mouseReleaseEvent (QMouseEvent *e) +{ +#if DEBUG_KP_TOOL && 1 + kDebug () << "kpTool::mouseReleaseEvent pos=" << e->pos () + << " button=" << (int) e->button () + << " stateAfter: buttons=" << (int *) (int) e->buttons () + << " modifiers=" << (int *) (int) e->modifiers () + << " beganDraw=" << d->beganDraw << endl; +#endif + + // Have _not_ already cancelShape()'ed by pressing other mouse button? + // (e.g. you can cancel a line dragged out with the LMB, by pressing + // the RMB) + if (d->beganDraw) + { + kpView *view = viewUnderStartPoint (); + Q_ASSERT (view); + + d->currentPoint = view->transformViewToDoc (e->pos ()); + d->currentViewPoint = e->pos (); + + drawInternal (); + + endDrawInternal (d->currentPoint, normalizedRect ()); + } + + if ((e->buttons () & Qt::MouseButtonMask) == 0) + { + releasedAllButtons (); + } +} + +//--------------------------------------------------------------------- + +void kpTool::wheelEvent (QWheelEvent *e) +{ +#if DEBUG_KP_TOOL + kDebug () << "kpTool::wheelEvent() modifiers=" << (int *) (int) e->modifiers () + << " hasBegunDraw=" << hasBegunDraw () + << " delta=" << e->delta () + << endl; +#endif + + e->ignore (); + + // If CTRL not pressed, bye. + if ((e->modifiers () & Qt::ControlModifier) == 0) + { + #if DEBUG_KP_TOOL + kDebug () << "\tno CTRL -> bye"; + #endif + return; + } + + // If drawing, bye; don't care if a shape in progress though. + if (hasBegunDraw ()) + { + #if DEBUG_KP_TOOL + kDebug () << "\thasBegunDraw() -> bye"; + #endif + return; + } + + + // Zoom in/out depending on wheel direction. + + // Moved wheel away from user? + if (e->delta () > 0) + { + #if DEBUG_KP_TOOL + kDebug () << "\tzoom in"; + #endif + d->environ->zoomIn (true/*center under cursor*/); + e->accept (); + } + // Moved wheel towards user? + else if (e->delta () < 0) + { + #if DEBUG_KP_TOOL + kDebug () << "\tzoom out"; + #endif + #if 1 + d->environ->zoomOut (true/*center under cursor - make zoom in/out + stay under same doc pos*/); + #else + d->environ->zoomOut (false/*don't center under cursor - as is + confusing behaviour when zooming + out*/); + #endif + e->accept (); + } +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/tools/kpTool_OtherEvents.cpp b/kolourpaint/tools/kpTool_OtherEvents.cpp new file mode 100644 index 00000000..2ccd54eb --- /dev/null +++ b/kolourpaint/tools/kpTool_OtherEvents.cpp @@ -0,0 +1,166 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// Tool reaction to all remaining events. +// +// 1. View events +// 2. Non-view events +// + + +#define DEBUG_KP_TOOL 0 + + +#include +#include + +#include + +#include + +//--------------------------------------------------------------------- + +// +// 1. View Events +// + +bool kpTool::viewEvent (QEvent *e) +{ +#if DEBUG_KP_TOOL + kDebug () << "kpTool<" << objectName () + << "," << this << ">::viewEvent(type=" << e->type () + << ") returning false" << endl; +#else + (void) e; +#endif + + // Don't handle. + return false; +} + +//--------------------------------------------------------------------- + +void kpTool::focusInEvent (QFocusEvent *) +{ +} + +//--------------------------------------------------------------------- + +void kpTool::focusOutEvent (QFocusEvent *) +{ +#if DEBUG_KP_TOOL && 0 + kDebug () << "kpTool::focusOutEvent() beganDraw=" << d->beganDraw; +#endif + + if (d->beganDraw) + endDrawInternal (d->currentPoint, normalizedRect ()); +} + +//--------------------------------------------------------------------- + +void kpTool::enterEvent (QEvent *) +{ +#if DEBUG_KP_TOOL && 1 + kDebug () << "kpTool::enterEvent() beganDraw=" << d->beganDraw; +#endif +} + +//--------------------------------------------------------------------- + +void kpTool::leaveEvent (QEvent *) +{ +#if DEBUG_KP_TOOL && 1 + kDebug () << "kpTool::leaveEvent() beganDraw=" << d->beganDraw; +#endif + + // if we haven't started drawing (e.g. dragging a rectangle)... + if (!d->beganDraw) + { + d->currentPoint = KP_INVALID_POINT; + d->currentViewPoint = KP_INVALID_POINT; + hover (d->currentPoint); + } +} + +//--------------------------------------------------------------------- +// +// 2. Non-view events +// + +void kpTool::slotColorsSwappedInternal (const kpColor &newForegroundColor, + const kpColor &newBackgroundColor) +{ + if (careAboutColorsSwapped ()) + { + slotColorsSwapped (newForegroundColor, newBackgroundColor); + d->ignoreColorSignals = 2; + } + else + d->ignoreColorSignals = 0; +} + +//--------------------------------------------------------------------- + +void kpTool::slotForegroundColorChangedInternal (const kpColor &color) +{ + if (d->ignoreColorSignals > 0) + { + #if DEBUG_KP_TOOL && 1 + kDebug () << "kpTool::slotForegroundColorChangedInternal() ignoreColorSignals=" << d->ignoreColorSignals; + #endif + d->ignoreColorSignals--; + return; + } + + slotForegroundColorChanged (color); +} + +//--------------------------------------------------------------------- + +void kpTool::slotBackgroundColorChangedInternal (const kpColor &color) +{ + if (d->ignoreColorSignals > 0) + { + #if DEBUG_KP_TOOL && 1 + kDebug () << "kpTool::slotBackgroundColorChangedInternal() ignoreColorSignals=" << d->ignoreColorSignals; + #endif + d->ignoreColorSignals--; + return; + } + + slotBackgroundColorChanged (color); +} + +//--------------------------------------------------------------------- + +void kpTool::slotColorSimilarityChangedInternal (double similarity, int processedSimilarity) +{ + slotColorSimilarityChanged (similarity, processedSimilarity); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/tools/kpTool_UserNotifications.cpp b/kolourpaint/tools/kpTool_UserNotifications.cpp new file mode 100644 index 00000000..2c65063c --- /dev/null +++ b/kolourpaint/tools/kpTool_UserNotifications.cpp @@ -0,0 +1,164 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// Tools' statusbar updates. +// + +#include +#include + +#include + +//--------------------------------------------------------------------- + +// public static +QString kpTool::cancelUserMessage (int mouseButton) +{ + if (mouseButton == 0) + return i18n ("Right click to cancel."); + else + return i18n ("Left click to cancel."); +} + +//--------------------------------------------------------------------- + +// public +QString kpTool::cancelUserMessage () const +{ + return cancelUserMessage (d->mouseButton); +} + +//--------------------------------------------------------------------- + +// public +QString kpTool::userMessage () const +{ + return d->userMessage; +} + +//--------------------------------------------------------------------- + +// public +void kpTool::setUserMessage (const QString &userMessage) +{ + d->userMessage = userMessage; + + if (d->userMessage.isEmpty ()) + d->userMessage = text (); + else + d->userMessage.prepend (i18n ("%1: ", text ())); + + emit userMessageChanged (d->userMessage); +} + +//--------------------------------------------------------------------- + +// public +QPoint kpTool::userShapeStartPoint () const +{ + return d->userShapeStartPoint; +} + +//--------------------------------------------------------------------- + +// public +QPoint kpTool::userShapeEndPoint () const +{ + return d->userShapeEndPoint; +} + +//--------------------------------------------------------------------- + +// public +void kpTool::setUserShapePoints (const QPoint &startPoint, + const QPoint &endPoint, + bool setSize) +{ + d->userShapeStartPoint = startPoint; + d->userShapeEndPoint = endPoint; + emit userShapePointsChanged (d->userShapeStartPoint, d->userShapeEndPoint); + + if (setSize) + { + if (startPoint != KP_INVALID_POINT && + endPoint != KP_INVALID_POINT) + { + setUserShapeSize (calculateLength (startPoint.x (), endPoint.x ()), + calculateLength (startPoint.y (), endPoint.y ())); + } + else + { + setUserShapeSize (); + } + } +} + +//--------------------------------------------------------------------- + +// public +QSize kpTool::userShapeSize () const +{ + return d->userShapeSize; +} + +//--------------------------------------------------------------------- + +// public +int kpTool::userShapeWidth () const +{ + return d->userShapeSize.width (); +} + +//--------------------------------------------------------------------- + +// public +int kpTool::userShapeHeight () const +{ + return d->userShapeSize.height (); +} + +//--------------------------------------------------------------------- + +// public +void kpTool::setUserShapeSize (const QSize &size) +{ + d->userShapeSize = size; + + emit userShapeSizeChanged (d->userShapeSize); + emit userShapeSizeChanged (d->userShapeSize.width (), + d->userShapeSize.height ()); +} + +//--------------------------------------------------------------------- + +// public +void kpTool::setUserShapeSize (int width, int height) +{ + setUserShapeSize (QSize (width, height)); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/tools/kpTool_Utilities.cpp b/kolourpaint/tools/kpTool_Utilities.cpp new file mode 100644 index 00000000..21c5605a --- /dev/null +++ b/kolourpaint/tools/kpTool_Utilities.cpp @@ -0,0 +1,291 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// Tool utility methods - mainly for subclasses' convenience. +// + + +#define DEBUG_KP_TOOL 0 + + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +// static +QRect kpTool::neededRect (const QRect &rect, int lineWidth) +{ + int x1, y1, x2, y2; + rect.getCoords (&x1, &y1, &x2, &y2); + + if (lineWidth < 1) + lineWidth = 1; + + // TODO: why not divide by 2? + return QRect (QPoint (x1 - lineWidth + 1, y1 - lineWidth + 1), + QPoint (x2 + lineWidth - 1, y2 + lineWidth - 1)); +} + +//--------------------------------------------------------------------- + +// static +QImage kpTool::neededPixmap (const QImage &image, const QRect &boundingRect) +{ + return kpPixmapFX::getPixmapAt (image, boundingRect); +} + +//--------------------------------------------------------------------- + +// public +bool kpTool::hasCurrentPoint () const +{ + return (viewUnderStartPoint () || viewUnderCursor ()); +} + +//--------------------------------------------------------------------- + +// public +QPoint kpTool::calculateCurrentPoint (bool zoomToDoc) const +{ +#if DEBUG_KP_TOOL && 0 + kDebug () << "kpTool::currentPoint(zoomToDoc=" << zoomToDoc << ")"; + kDebug () << "\tviewUnderStartPoint=" + << (viewUnderStartPoint () ? viewUnderStartPoint ()->objectName () : "(none)") + << " viewUnderCursor=" + << (viewUnderCursor () ? viewUnderCursor ()->objectName () : "(none)") + << endl; +#endif + + kpView *v = viewUnderStartPoint (); + if (!v) + { + v = viewUnderCursor (); + if (!v) + { + #if DEBUG_KP_TOOL && 0 + kDebug () << "\tno view - returning sentinel"; + #endif + return KP_INVALID_POINT; + } + } + + + const QPoint globalPos = QCursor::pos (); + const QPoint viewPos = v->mapFromGlobal (globalPos); +#if DEBUG_KP_TOOL && 0 + kDebug () << "\tglobalPos=" << globalPos + << " viewPos=" << viewPos + << endl; +#endif + if (!zoomToDoc) + return viewPos; + + + const QPoint docPos = v->transformViewToDoc (viewPos); +#if DEBUG_KP_TOOL && 0 + kDebug () << "\tdocPos=" << docPos; +#endif + return docPos; +} + +//--------------------------------------------------------------------- + +// public slot +void kpTool::somethingBelowTheCursorChanged () +{ + somethingBelowTheCursorChanged (calculateCurrentPoint (), + calculateCurrentPoint (false/*view point*/)); +} + +//--------------------------------------------------------------------- + +// private +// TODO: don't dup code from mouseMoveEvent() +void kpTool::somethingBelowTheCursorChanged (const QPoint ¤tPoint_, + const QPoint ¤tViewPoint_) +{ +#if DEBUG_KP_TOOL && 1 + kDebug () << "kpTool::somethingBelowTheCursorChanged(docPoint=" + << currentPoint_ + << " viewPoint=" + << currentViewPoint_ + << ")" << endl; + kDebug () << "\tviewUnderStartPoint=" + << (viewUnderStartPoint () ? viewUnderStartPoint ()->objectName () : "(none)") + << " viewUnderCursor=" + << (viewUnderCursor () ? viewUnderCursor ()->objectName () : "(none)") + << endl; + kDebug () << "\tbegan draw=" << d->beganDraw; +#endif + + d->currentPoint = currentPoint_; + d->currentViewPoint = currentViewPoint_; + + if (d->beganDraw) + { + if (d->currentPoint != KP_INVALID_POINT) + { + draw (d->currentPoint, d->lastPoint, normalizedRect ()); + d->lastPoint = d->currentPoint; + } + } + else + { + hover (d->currentPoint); + } +} + +//--------------------------------------------------------------------- + +bool kpTool::currentPointNextToLast () const +{ + if (d->lastPoint == QPoint (-1, -1)) + return true; + + int dx = qAbs (d->currentPoint.x () - d->lastPoint.x ()); + int dy = qAbs (d->currentPoint.y () - d->lastPoint.y ()); + + return (dx <= 1 && dy <= 1); +} + +//--------------------------------------------------------------------- + +bool kpTool::currentPointCardinallyNextToLast () const +{ + if (d->lastPoint == QPoint (-1, -1)) + return true; + + return (d->currentPoint == d->lastPoint || + kpPainter::pointsAreCardinallyAdjacent (d->currentPoint, d->lastPoint)); +} + +//--------------------------------------------------------------------- + +// static +// TODO: we don't handle Qt::XButton1 and Qt::XButton2 at the moment. +int kpTool::mouseButton (Qt::MouseButtons mouseButtons) +{ + // we have nothing to do with mid-buttons + if (mouseButtons & Qt::MidButton) + return -1; + + // both left & right together is quite meaningless... + const Qt::MouseButtons bothButtons = (Qt::LeftButton | Qt::RightButton); + if ((mouseButtons & bothButtons) == bothButtons) + return -1; + + if (mouseButtons & Qt::LeftButton) + return 0; + else if (mouseButtons & Qt::RightButton) + return 1; + else + return -1; +} + +//--------------------------------------------------------------------- + +// public static +int kpTool::calculateLength (int start, int end) +{ + if (start <= end) + { + return end - start + 1; + } + else + { + return end - start - 1; + } +} + +//--------------------------------------------------------------------- + +// public static +bool kpTool::warnIfBigImageSize (int oldWidth, int oldHeight, + int newWidth, int newHeight, + const QString &text, + const QString &caption, + const QString &continueButtonText, + QWidget *parent) +{ +#if DEBUG_KP_TOOL + kDebug () << "kpTool::warnIfBigImageSize()" + << " old: w=" << oldWidth << " h=" << oldWidth + << " new: w=" << newWidth << " h=" << newHeight + << " pixmapSize=" + << kpPixmapFX::pixmapSize (newWidth, + newHeight, + QPixmap::defaultDepth ()) + << " vs BigImageSize=" << KP_BIG_IMAGE_SIZE + << endl; +#endif + + // Only got smaller or unchanged - don't complain + if (!(newWidth > oldWidth || newHeight > oldHeight)) + { + return true; + } + + // Was already large - user was warned before, don't annoy him/her again + if (kpCommandSize::PixmapSize (oldWidth, oldHeight, QPixmap::defaultDepth ()) >= + KP_BIG_IMAGE_SIZE) + { + return true; + } + + if (kpCommandSize::PixmapSize (newWidth, newHeight, QPixmap::defaultDepth ()) >= + KP_BIG_IMAGE_SIZE) + { + int accept = KMessageBox::warningContinueCancel (parent, + text, + caption, + KGuiItem (continueButtonText), + KStandardGuiItem::cancel(), + QLatin1String ("BigImageDontAskAgain")); + + return (accept == KMessageBox::Continue); + } + else + { + return true; + } +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/tools/polygonal/kpToolCurve.cpp b/kolourpaint/tools/polygonal/kpToolCurve.cpp new file mode 100644 index 00000000..c9fec9d3 --- /dev/null +++ b/kolourpaint/tools/polygonal/kpToolCurve.cpp @@ -0,0 +1,182 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_CURVE 0 + + +#include + +#include +#include + +#include + + +static void DrawCurveShape (kpImage *image, + const QPolygon &points, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + bool isFinal) +{ + (void) bcolor; + (void) isFinal; + + Q_ASSERT (points.count () >= 2 && points.count () <= 4); + + const QPoint startPoint = points [0]; + const QPoint endPoint = points [1]; + + QPoint controlPointP, controlPointQ; + + switch (points.count ()) + { + // Just a line? + case 2: + controlPointP = startPoint; + controlPointQ = endPoint; + break; + + // Single control point? + case 3: + controlPointP = controlPointQ = points [2]; + break; + + // Two control points? + case 4: + controlPointP = points [2]; + controlPointQ = points [3]; + break; + } + + kpPainter::drawCurve (image, + startPoint, + controlPointP, controlPointQ, + endPoint, + fcolor, penWidth); +} + + +kpToolCurve::kpToolCurve (kpToolEnvironment *environ, QObject *parent) + : kpToolPolygonalBase ( + i18n ("Curve"), + i18n ("Draws curves"), + &::DrawCurveShape, + Qt::Key_V, + environ, parent, + "tool_curve") +{ +} + +kpToolCurve::~kpToolCurve () +{ +} + + +// protected virtual [base kpToolPolygonalBase] +QString kpToolCurve::haventBegunShapeUserMessage () const +{ + return i18n ("Drag out the start and end points."); +} + + +// protected virtual [base kpToolPolygonalBase] +bool kpToolCurve::drawingALine () const +{ + // On the initial drag (consisting of 2 points) creates a line. + // Future drags are for control points. + return (points ()->count () == 2); +} + + +// public virtual [base kpTool] +void kpToolCurve::endDraw (const QPoint &, const QRect &) +{ +#if DEBUG_KP_TOOL_CURVE + kDebug () << "kpToolCurve::endDraw() points=" + << points ()->toList () << endl; +#endif + + switch (points ()->count ()) + { + // A click of the other mouse button (to finish shape, instead of adding + // another control point) would have caused endShape() to have been + // called in kpToolPolygonalBase::beginDraw(). The points list would now + // be empty. We are being called by kpTool::mouseReleaseEvent(). + case 0: + break; + + case 1: + Q_ASSERT (!"kpToolPolygonalBase::beginDraw() ensures we have >= 2 ctrl points"); + break; + + // Just completed initial line? + case 2: + if (originatingMouseButton () == 0) + { + setUserMessage ( + i18n ("Left drag to set the first control point or right click to finish.")); + } + else + { + setUserMessage ( + i18n ("Right drag to set the first control point or left click to finish.")); + } + + break; + + // Have initial line and first control point? + case 3: + if (originatingMouseButton () == 0) + { + setUserMessage ( + i18n ("Left drag to set the last control point or right click to finish.")); + } + else + { + setUserMessage ( + i18n ("Right drag to set the last control point or left click to finish.")); + } + + break; + + // Have initial line and both control points? + case 4: + #if DEBUG_KP_TOOL_CURVE + kDebug () << "\tending shape"; + #endif + endShape (); + break; + + default: + Q_ASSERT (!"Impossible number of points"); + break; + } +} + + +#include diff --git a/kolourpaint/tools/polygonal/kpToolCurve.h b/kolourpaint/tools/polygonal/kpToolCurve.h new file mode 100644 index 00000000..892873be --- /dev/null +++ b/kolourpaint/tools/polygonal/kpToolCurve.h @@ -0,0 +1,54 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_CURVE_H +#define KP_TOOL_CURVE_H + + +#include + + +class kpToolCurve : public kpToolPolygonalBase +{ +Q_OBJECT + +public: + kpToolCurve (kpToolEnvironment *environ, QObject *parent); + virtual ~kpToolCurve (); + +protected: + virtual QString haventBegunShapeUserMessage () const; + + virtual bool drawingALine () const; + +public: + virtual void endDraw (const QPoint &, const QRect &); +}; + + +#endif // KP_TOOL_CURVE_H diff --git a/kolourpaint/tools/polygonal/kpToolLine.cpp b/kolourpaint/tools/polygonal/kpToolLine.cpp new file mode 100644 index 00000000..4d62b467 --- /dev/null +++ b/kolourpaint/tools/polygonal/kpToolLine.cpp @@ -0,0 +1,93 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_LINE 0 + + +#include + +#include +#include + +#include + + +static void DrawLineShape (kpImage *image, + const QPolygon &points, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + bool isFinal) +{ + Q_ASSERT (points.count () == 2); + + kpToolPolyline::DrawShape (image, + points, + fcolor, penWidth, + bcolor, + isFinal); +} + + +kpToolLine::kpToolLine (kpToolEnvironment *environ, QObject *parent) + : kpToolPolygonalBase ( + i18n ("Line"), + i18n ("Draws lines"), + &::DrawLineShape, + Qt::Key_L, + environ, parent, + "tool_line") +{ +} + +kpToolLine::~kpToolLine () +{ +} + + +// private virtual [base kpToolPolygonalBase] +QString kpToolLine::haventBegunShapeUserMessage () const +{ + return i18n ("Drag to draw."); +} + + +// public virtual [base kpTool] +void kpToolLine::endDraw (const QPoint &, const QRect &) +{ +#if DEBUG_KP_TOOL_LINE + kDebug () << "kpToolLine::endDraw() points=" + << points ()->toList () << endl; +#endif + + // After the first drag, we should have a line. + Q_ASSERT (points ()->count () == 2); + endShape (); +} + + +#include diff --git a/kolourpaint/tools/polygonal/kpToolLine.h b/kolourpaint/tools/polygonal/kpToolLine.h new file mode 100644 index 00000000..435f373d --- /dev/null +++ b/kolourpaint/tools/polygonal/kpToolLine.h @@ -0,0 +1,52 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_LINE_H +#define KP_TOOL_LINE_H + + +#include + + +class kpToolLine : public kpToolPolygonalBase +{ +Q_OBJECT + +public: + kpToolLine (kpToolEnvironment *environ, QObject *parent); + virtual ~kpToolLine (); + +private: + virtual QString haventBegunShapeUserMessage () const; + +public: + virtual void endDraw (const QPoint &, const QRect &); +}; + + +#endif // KP_TOOL_LINE_H diff --git a/kolourpaint/tools/polygonal/kpToolPolygon.cpp b/kolourpaint/tools/polygonal/kpToolPolygon.cpp new file mode 100644 index 00000000..5370369c --- /dev/null +++ b/kolourpaint/tools/polygonal/kpToolPolygon.cpp @@ -0,0 +1,162 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_POLYGON 0 + + +#include + +#include + +#include +#include + + +static void DrawPolygonShape (kpImage *image, + const QPolygon &points, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + bool isFinal) +{ + kpPainter::drawPolygon (image, + points, + fcolor, penWidth, + bcolor, + isFinal); +} + + +struct kpToolPolygonPrivate +{ + kpToolWidgetFillStyle *toolWidgetFillStyle; +}; + +kpToolPolygon::kpToolPolygon (kpToolEnvironment *environ, QObject *parent) + : kpToolPolygonalBase ( + i18n ("Polygon"), + i18n ("Draws polygons"), + &::DrawPolygonShape, + Qt::Key_G, + environ, parent, + "tool_polygon"), + d (new kpToolPolygonPrivate ()) +{ +} + +kpToolPolygon::~kpToolPolygon () +{ + delete d; +} + + +// private virtual [base kpToolPolygonBase] +QString kpToolPolygon::haventBegunShapeUserMessage () const +{ + return i18n ("Drag to draw the first line."); +} + + +// public virtual [base kpToolPolygonalBase] +void kpToolPolygon::begin () +{ + kpToolPolygonalBase::begin (); + + kpToolToolBar *tb = toolToolBar (); + Q_ASSERT (tb); + + d->toolWidgetFillStyle = tb->toolWidgetFillStyle (); + connect (d->toolWidgetFillStyle, + SIGNAL (fillStyleChanged (kpToolWidgetFillStyle::FillStyle)), + this, + SLOT (updateShape ())); + d->toolWidgetFillStyle->show (); +} + +// public virtual [base kpToolPolygonalBase] +void kpToolPolygon::end () +{ + kpToolPolygonalBase::end (); + + disconnect (d->toolWidgetFillStyle, + SIGNAL (fillStyleChanged (kpToolWidgetFillStyle::FillStyle)), + this, + SLOT (updateShape ())); + d->toolWidgetFillStyle = 0; +} + + +// TODO: code dup with kpToolRectangle +// protected virtual [base kpToolPolygonalBase] +kpColor kpToolPolygon::drawingBackgroundColor () const +{ + const kpColor foregroundColor = color (originatingMouseButton ()); + const kpColor backgroundColor = color (1 - originatingMouseButton ()); + + return d->toolWidgetFillStyle->drawingBackgroundColor ( + foregroundColor, backgroundColor); +} + + +// public virtual [base kpTool] +// TODO: dup with kpToolPolyline but we don't want to create another level of +// inheritance and readability. +void kpToolPolygon::endDraw (const QPoint &, const QRect &) +{ +#if DEBUG_KP_TOOL_POLYGON + kDebug () << "kpToolPolygon::endDraw() points=" + << points ()->toList () << endl; +#endif + + // A click of the other mouse button (to finish shape, instead of adding + // another control point) would have caused endShape() to have been + // called in kpToolPolygonalBase::beginDraw(). The points list would now + // be empty. We are being called by kpTool::mouseReleaseEvent(). + if (points ()->count () == 0) + return; + + if (points ()->count () >= kpToolPolygonalBase::MaxPoints) + { + #if DEBUG_KP_TOOL_POLYGON + kDebug () << "\tending shape"; + #endif + endShape (); + return; + } + + if (originatingMouseButton () == 0) + { + setUserMessage (i18n ("Left drag another line or right click to finish.")); + } + else + { + setUserMessage (i18n ("Right drag another line or left click to finish.")); + } +} + + +#include diff --git a/kolourpaint/tools/polygonal/kpToolPolygon.h b/kolourpaint/tools/polygonal/kpToolPolygon.h new file mode 100644 index 00000000..939921ca --- /dev/null +++ b/kolourpaint/tools/polygonal/kpToolPolygon.h @@ -0,0 +1,62 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_POLYGON_H +#define KP_TOOL_POLYGON_H + + +#include + + +class kpToolPolygon : public kpToolPolygonalBase +{ +Q_OBJECT + +public: + kpToolPolygon (kpToolEnvironment *environ, QObject *parent); + virtual ~kpToolPolygon (); + +private: + virtual QString haventBegunShapeUserMessage () const; + +public: + virtual void begin (); + virtual void end (); + +protected: + virtual kpColor drawingBackgroundColor () const; + +public: + virtual void endDraw (const QPoint &, const QRect &); + +private: + struct kpToolPolygonPrivate *d; +}; + + +#endif // KP_TOOL_POLYGON_H diff --git a/kolourpaint/tools/polygonal/kpToolPolygonalBase.cpp b/kolourpaint/tools/polygonal/kpToolPolygonalBase.cpp new file mode 100644 index 00000000..d463e3a5 --- /dev/null +++ b/kolourpaint/tools/polygonal/kpToolPolygonalBase.cpp @@ -0,0 +1,501 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_POLYGON 0 + + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct kpToolPolygonalBasePrivate +{ + kpToolPolygonalBasePrivate () + : drawShapeFunc(0), toolWidgetLineWidth(0), originatingMouseButton(-1) + { + } + + kpToolPolygonalBase::DrawShapeFunc drawShapeFunc; + + kpToolWidgetLineWidth *toolWidgetLineWidth; + + int originatingMouseButton; + + QPolygon points; +}; + +//--------------------------------------------------------------------- + +kpToolPolygonalBase::kpToolPolygonalBase ( + const QString &text, + const QString &description, + DrawShapeFunc drawShapeFunc, + int key, + kpToolEnvironment *environ, QObject *parent, + const QString &name) + + : kpTool (text, description, key, environ, parent, name), + d (new kpToolPolygonalBasePrivate ()) +{ + d->drawShapeFunc = drawShapeFunc; + + d->toolWidgetLineWidth = 0; + + // (hopefully cause crash if we use it before initialising it) + d->originatingMouseButton = -1; +} + +//--------------------------------------------------------------------- + +kpToolPolygonalBase::~kpToolPolygonalBase () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// virtual +void kpToolPolygonalBase::begin () +{ + kpToolToolBar *tb = toolToolBar (); + Q_ASSERT (tb); + +#if DEBUG_KP_TOOL_POLYGON + kDebug () << "kpToolPolygonalBase::begin() tb=" << tb; +#endif + + d->toolWidgetLineWidth = tb->toolWidgetLineWidth (); + connect (d->toolWidgetLineWidth, SIGNAL (lineWidthChanged (int)), + this, SLOT (updateShape ())); + d->toolWidgetLineWidth->show (); + + viewManager ()->setCursor (QCursor (Qt::ArrowCursor)); + + d->originatingMouseButton = -1; + + setUserMessage (/*virtual*/haventBegunShapeUserMessage ()); +} + +//--------------------------------------------------------------------- + +// virtual +void kpToolPolygonalBase::end () +{ + // TODO: needed? + endShape (); + + disconnect (d->toolWidgetLineWidth, + SIGNAL (lineWidthChanged (int)), + this, + SLOT (updateShape ())); + d->toolWidgetLineWidth = 0; + + viewManager ()->unsetCursor (); +} + + +void kpToolPolygonalBase::beginDraw () +{ +#if DEBUG_KP_TOOL_POLYGON + kDebug () << "kpToolPolygonalBase::beginDraw() d->points=" << d->points.toList () + << ", startPoint=" << startPoint () << endl; +#endif + + bool endedShape = false; + + // We now need to start with dragging out the initial line? + if (d->points.count () == 0) + { + d->originatingMouseButton = mouseButton (); + + // The line starts and ends at the start point of the drag. + // draw() will modify the last point in d->points to reflect the + // mouse drag, as the drag proceeds. + d->points.append (startPoint ()); + d->points.append (startPoint ()); + } + // Already have control points - not dragging out initial line. + else + { + // Clicking the other mouse button? + if (mouseButton () != d->originatingMouseButton) + { + // Finish shape. TODO: I suspect we need to call endShapeInternal instead. + endShape (); + endedShape = true; + } + // Are we dragging out an extra control point? + else + { + // Add another control point. + d->points.append (startPoint ()); + } + } + +#if DEBUG_KP_TOOL_POLYGON + kDebug () << "\tafterwards, d->points=" << d->points.toList (); +#endif + + if (!endedShape) + { + // We've started dragging. Print instructions on how to cancel shape. + setUserMessage (cancelUserMessage ()); + } +} + + +// protected +void kpToolPolygonalBase::applyModifiers () +{ + const int count = d->points.count (); + + QPoint &lineStartPoint = d->points [count - 2]; + QPoint &lineEndPoint = d->points [count - 1]; + +#if DEBUG_KP_TOOL_POLYGON && 1 + kDebug () << "kpToolPolygonalBase::applyModifiers() #pts=" << count + << " line: startPt=" << lineStartPoint + << " endPt=" << lineEndPoint + << " modifiers: shift=" << shiftPressed () + << " alt=" << altPressed () + << " ctrl=" << controlPressed () + << endl; +#endif + + // angles + if (shiftPressed () || controlPressed ()) + { + int diffx = lineEndPoint.x () - lineStartPoint.x (); + int diffy = lineEndPoint.y () - lineStartPoint.y (); + + double ratio; + if (diffx == 0) + ratio = DBL_MAX; + else + ratio = fabs (double (diffy) / double (diffx)); + #if DEBUG_KP_TOOL_POLYGON && 1 + kDebug () << "\tdiffx=" << diffx << " diffy=" << diffy + << " ratio=" << ratio + << endl; + #endif + + // Shift = 0, 45, 90 + // Ctrl = 0, 30, 60, 90 + // Shift + Ctrl = 0, 30, 45, 60, 90 + double angles [10]; // "ought to be enough for anybody" + int numAngles = 0; + angles [numAngles++] = 0; + if (controlPressed ()) + angles [numAngles++] = KP_PI / 6; + if (shiftPressed ()) + angles [numAngles++] = KP_PI / 4; + if (controlPressed ()) + angles [numAngles++] = KP_PI / 3; + angles [numAngles++] = KP_PI / 2; + Q_ASSERT (numAngles <= int (sizeof (angles) / sizeof (angles [0]))); + + double angle = angles [numAngles - 1]; + for (int i = 0; i < numAngles - 1; i++) + { + double acceptingRatio = tan ((angles [i] + angles [i + 1]) / 2.0); + if (ratio < acceptingRatio) + { + angle = angles [i]; + break; + } + } + + // horizontal (dist from start not maintained) + if (fabs (KP_RADIANS_TO_DEGREES (angle) - 0) + < kpPixmapFX::AngleInDegreesEpsilon) + { + lineEndPoint = + QPoint (lineEndPoint.x (), lineStartPoint.y ()); + } + // vertical (dist from start not maintained) + else if (fabs (KP_RADIANS_TO_DEGREES (angle) - 90) + < kpPixmapFX::AngleInDegreesEpsilon) + { + lineEndPoint = + QPoint (lineStartPoint.x (), lineEndPoint.y ()); + } + // diagonal (dist from start maintained) + else + { + const double dist = sqrt ((double)(diffx * diffx + diffy * diffy)); + + #define sgn(a) ((a)<0?-1:1) + // Round distances _before_ adding to any coordinate + // (ensures consistent rounding behaviour in x & y directions) + const int newdx = qRound (dist * cos (angle) * sgn (diffx)); + const int newdy = qRound (dist * sin (angle) * sgn (diffy)); + #undef sgn + + lineEndPoint = QPoint (lineStartPoint.x () + newdx, + lineStartPoint.y () + newdy); + + #if DEBUG_KP_TOOL_POLYGON && 1 + kDebug () << "\t\tdiagonal line: dist=" << dist + << " angle=" << (angle * 180 / KP_PI) + << " endPoint=" << lineEndPoint + << endl; + #endif + } + } // if (shiftPressed () || controlPressed ()) { + + // centring + if (altPressed () && 0/*ALT is unreliable*/) + { + // start = start - diff + // = start - (end - start) + // = start - end + start + // = 2 * start - end + if (count == 2) + lineStartPoint += (lineStartPoint - lineEndPoint); + else + lineEndPoint += (lineEndPoint - lineStartPoint); + } // if (altPressed ()) { +} + + +// protected +QPolygon *kpToolPolygonalBase::points () const +{ + return &d->points; +} + + +// protected +int kpToolPolygonalBase::originatingMouseButton () const +{ + Q_ASSERT (hasBegunShape ()); + return d->originatingMouseButton; +} + + +// virtual +void kpToolPolygonalBase::draw (const QPoint &, const QPoint &, const QRect &) +{ + // A click of the other mouse button (to finish shape, instead of adding + // another control point) would have caused endShape() to have been + // called in kpToolPolygonalBase::beginDraw(). The points list would now + // be empty. We are being called by kpTool::mouseReleaseEvent(). + if (d->points.count () == 0) + return; + +#if DEBUG_KP_TOOL_POLYGON + kDebug () << "kpToolPolygonalBase::draw() d->points=" << d->points.toList () + << ", endPoint=" << currentPoint () << endl; +#endif + + // Update points() so that last point reflects current mouse position. + const int count = d->points.count (); + d->points [count - 1] = currentPoint (); + +#if DEBUG_KP_TOOL_POLYGON + kDebug () << "\tafterwards, d->points=" << d->points.toList (); +#endif + + // Are we drawing a line? + if (/*virtual*/drawingALine ()) + { + // Adjust the line (end points given by the last 2 points of points()) + // in response to keyboard modifiers. + applyModifiers (); + + // Update the preview of the shape. + updateShape (); + + // Inform the user that we're dragging out a line with 2 control points. + setUserShapePoints (d->points [count - 2], d->points [count - 1]); + } + // We're modifying a point. + else + { + // Update the preview of the shape. + updateShape (); + + // Informs the user that we're just modifying a point (perhaps, a control + // point of a Bezier). + setUserShapePoints (d->points [count - 1]); + } +} + + +// TODO: code dup with kpToolRectangle +// private +kpColor kpToolPolygonalBase::drawingForegroundColor () const +{ + return color (originatingMouseButton ()); +} + +// protected virtual +kpColor kpToolPolygonalBase::drawingBackgroundColor () const +{ + return kpColor::Invalid; +} + +// TODO: code dup with kpToolRectangle +// protected slot +void kpToolPolygonalBase::updateShape () +{ + if (d->points.count () == 0) + return; + + const QRect boundingRect = kpTool::neededRect ( + d->points.boundingRect (), + d->toolWidgetLineWidth->lineWidth ()); + +#if DEBUG_KP_TOOL_POLYGON + kDebug () << "kpToolPolygonalBase::updateShape() boundingRect=" + << boundingRect + << " lineWidth=" + << d->toolWidgetLineWidth->lineWidth () + << endl; +#endif + + kpImage image = document ()->getImageAt (boundingRect); + + QPolygon pointsTranslated = d->points; + pointsTranslated.translate (-boundingRect.x (), -boundingRect.y ()); + + (*d->drawShapeFunc) (&image, + pointsTranslated, + drawingForegroundColor (), d->toolWidgetLineWidth->lineWidth (), + /*virtual*/drawingBackgroundColor (), + false/*not final*/); + + kpTempImage newTempImage (false/*always display*/, + kpTempImage::SetImage/*render mode*/, + boundingRect.topLeft (), + image); + + viewManager ()->setFastUpdates (); + { + viewManager ()->setTempImage (newTempImage); + } + viewManager ()->restoreFastUpdates (); +} + +// virtual +void kpToolPolygonalBase::cancelShape () +{ + viewManager ()->invalidateTempImage (); + d->points.resize (0); + + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +void kpToolPolygonalBase::releasedAllButtons () +{ + if (!hasBegunShape ()) + setUserMessage (/*virtual*/haventBegunShapeUserMessage ()); + + // --- else case already handled by endDraw() --- +} + +// public virtual [base kpTool] +void kpToolPolygonalBase::endShape (const QPoint &, const QRect &) +{ +#if DEBUG_KP_TOOL_POLYGON + kDebug () << "kpToolPolygonalBase::endShape() d->points=" + << d->points.toList () << endl; +#endif + + if (!hasBegunShape ()) + return; + + viewManager ()->invalidateTempImage (); + + QRect boundingRect = kpTool::neededRect ( + d->points.boundingRect (), + d->toolWidgetLineWidth->lineWidth ()); + + commandHistory ()->addCommand ( + new kpToolPolygonalCommand ( + text (), + d->drawShapeFunc, + d->points, boundingRect, + drawingForegroundColor (), d->toolWidgetLineWidth->lineWidth (), + /*virtual*/drawingBackgroundColor (), + environ ()->commandEnvironment ())); + + d->points.resize (0); + setUserMessage (/*virtual*/haventBegunShapeUserMessage ()); + +} + +// public virtual [base kpTool] +bool kpToolPolygonalBase::hasBegunShape () const +{ + return (d->points.count () > 0); +} + + +// virtual protected slot [base kpTool] +void kpToolPolygonalBase::slotForegroundColorChanged (const kpColor &) +{ + updateShape (); +} + +// virtual protected slot [base kpTool] +void kpToolPolygonalBase::slotBackgroundColorChanged (const kpColor &) +{ + updateShape (); +} + + +#include diff --git a/kolourpaint/tools/polygonal/kpToolPolygonalBase.h b/kolourpaint/tools/polygonal/kpToolPolygonalBase.h new file mode 100644 index 00000000..4d64b1e4 --- /dev/null +++ b/kolourpaint/tools/polygonal/kpToolPolygonalBase.h @@ -0,0 +1,209 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolPolygonalBase_H +#define kpToolPolygonalBase_H + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +class QPoint; +class QPolygon; +class QRect; +class QString; + +class kpView; + +class kpToolWidgetFillStyle; + + +struct kpToolPolygonalBasePrivate; + +// +// This tool base class is for shapes that contain at least an initial line that +// is dragged out (i.e. at least 2 control points). +// +// The tool can also choose to allow an additional point to be added for each +// additional drag or click. +// +// To specify whichever behavior, subclasses must implement endDraw() from +// kpTool and use points(): +// +// 1. If the shape is incomplete, call setUserMessage() with a message +// telling the user what can be done next. +// 2. If the shape is complete, call endShape(). See also MaxPoints. +// +// If additional points are supported by the user's implementation of endDraw(), +// beginDraw() will enforce the following behavior: +// +// Clicking the mouse button not used for the initial line drag will +// end the shape. +// +// This behavior cannot be altered by a subclass. +// +// beginDraw() will ensure that points() contains 2 points on the initial line +// drag. It will add an extra point for each additional point that is dragged. +// +// You may wish to reimplement drawingALine() if your shape does not consist of +// just connected lines e.g. while the Curve tool, on the initial drag, creates +// a line (consisting of the 2 points returned by points()), future drags do not +// create extra lines - they actually modify the Bezier control points. +// +// The actual rendering is performed by the function passed in +// the constructor. +// +class kpToolPolygonalBase : public kpTool +{ +Q_OBJECT + +public: + // (all arguments are as per kpPainter::drawPolygon()) + typedef void (*DrawShapeFunc) (kpImage * /*image*/, + const QPolygon &/*points*/, + const kpColor &/*fcolor*/, int /*penWidth = 1*/, + const kpColor &/*bcolor = kpColor::Invalid*/, + bool /*isFinal*/); + + // + kpToolPolygonalBase (const QString &text, const QString &description, + DrawShapeFunc drawShapeFunc, + int key, + kpToolEnvironment *environ, QObject *parent, + const QString &name); + virtual ~kpToolPolygonalBase (); + + virtual bool careAboutModifierState () const { return true; } + +protected: + // The maximum number of points() we should allow (mainly, to ensure + // good performance). Enforced by implementors of endShape(). + static const int MaxPoints = 50; + + virtual QString haventBegunShapeUserMessage () const = 0; + +public: + virtual void begin (); + virtual void end (); + + virtual void beginDraw (); + +protected: + // Adjusts the current line (end points given by the last 2 points of points()) + // in response to keyboard modifiers: + // + // No modifiers: Does nothing + // Shift : Clamps the line to 45 degrees increments + // Ctrl : Clamps the line to 30 degrees increments + // Alt : [currently disabled] Makes the starting point the center + // point of the line. + // + // It is possible to depress multiple modifiers for combined effects e.g. + // Ctrl+Shift clamps the line to 30 and 45 degree increments i.e. + // 0, 30, 45, 60, 90, 120, 135, 150, 180, 210, ... degrees. + // + // This really only makes sense if drawingALine() returns true, where draw() + // will call applyModifiers() automatically. Otherwise, if it returns false, + // it doesn't really make sense to call applyModifiers() (in a hypothetical + // reimplementation of draw()) because you're not manipulating a line - but + // you can still call applyModifiers() if you want. + void applyModifiers (); + + // Returns the current points in the shape. It is updated by beginDraw() + // (see the class description). + // + // draw() sets the last point to the currentPoint(). If drawingALine(), + // draw() then calls applyModifiers(). + QPolygon *points () const; + + // Returns the mouse button for the drag that created the initial line. + // Use this - instead of mouseButton() - for determining whether you should + // use the left mouse button's or right mouse button's color. This is because + // the user presses the other mouse button to finish the shape (so mouseButton() + // will return the wrong one, after the initial line). + // + // Only valid if kpTool::hasBegunShape() returns true. + int originatingMouseButton () const; + + // Returns true if the current drag is visually a line from the 2nd last point + // of points() to the last point of points() e.g. the initial line drag for + // a Curve and all drags for a Polygon or Polyline. draw() will call + // applyModifiers() and update the statusbar with those 2 points. + // + // Returns false if the current drag only draws something based on the last + // point of points() e.g. a control point of a Bezier curve. draw() will + // _not_ call applyModifiers(). It will update the statubar with just that + // point. + // + // Reimplement this if not all points are used to construct connected lines. + // For instance, the Curve tool will return "true" to construct a line, on + // the initial drag. However, for the following 2 control points, it returns + // "false". The Curve tool realises it is an initial drag if points() only + // returns 2 points. + virtual bool drawingALine () const { return true; } +public: + virtual void draw (const QPoint &, const QPoint &, const QRect &); +private: + kpColor drawingForegroundColor () const; +protected: + // This returns the invalid color so that there is never a fill. + // This is in contrast to kpToolRectangularBase, which sometimes fills by + // returning a valid color. + // + // Reimplemented in the Polygon tool for a fill. + virtual kpColor drawingBackgroundColor () const; +protected slots: + void updateShape (); +public: + virtual void cancelShape (); + virtual void releasedAllButtons (); + virtual void endShape (const QPoint & = QPoint (), const QRect & = QRect ()); + + virtual bool hasBegunShape () const; + +protected slots: + virtual void slotForegroundColorChanged (const kpColor &); + virtual void slotBackgroundColorChanged (const kpColor &); + +private: + kpToolPolygonalBasePrivate * const d; +}; + + +#endif // kpToolPolygonalBase_H diff --git a/kolourpaint/tools/polygonal/kpToolPolyline.cpp b/kolourpaint/tools/polygonal/kpToolPolyline.cpp new file mode 100644 index 00000000..ad01af54 --- /dev/null +++ b/kolourpaint/tools/polygonal/kpToolPolyline.cpp @@ -0,0 +1,114 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_POLYLINE 0 + + +#include + +#include +#include + +#include + + +kpToolPolyline::kpToolPolyline (kpToolEnvironment *environ, QObject *parent) + : kpToolPolygonalBase ( + i18n ("Connected Lines"), + i18n ("Draws connected lines"), + &DrawShape, + Qt::Key_N, + environ, parent, + "tool_polyline") +{ +} + +kpToolPolyline::~kpToolPolyline () +{ +} + + +// private virtual [base kpToolPolygonalBase] +QString kpToolPolyline::haventBegunShapeUserMessage () const +{ + return i18n ("Drag to draw the first line."); +} + + +// public static +void kpToolPolyline::DrawShape (kpImage *image, + const QPolygon &points, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + bool isFinal) +{ + (void) bcolor; + (void) isFinal; + + kpPainter::drawPolyline (image, + points, + fcolor, penWidth); +} + + +// public virtual [base kpTool] +void kpToolPolyline::endDraw (const QPoint &, const QRect &) +{ +#if DEBUG_KP_TOOL_POLYLINE + kDebug () << "kpToolPolyline::endDraw() points=" + << points ()->toList () << endl; +#endif + + // A click of the other mouse button (to finish shape, instead of adding + // another control point) would have caused endShape() to have been + // called in kpToolPolygonalBase::beginDraw(). The points list would now + // be empty. We are being called by kpTool::mouseReleaseEvent(). + if (points ()->count () == 0) + return; + + if (points ()->count () >= kpToolPolygonalBase::MaxPoints) + { + #if DEBUG_KP_TOOL_POLYLINE + kDebug () << "\tending shape"; + #endif + endShape (); + return; + } + + if (originatingMouseButton () == 0) + { + setUserMessage (i18n ("Left drag another line or right click to finish.")); + } + else + { + setUserMessage (i18n ("Right drag another line or left click to finish.")); + } +} + + +#include diff --git a/kolourpaint/tools/polygonal/kpToolPolyline.h b/kolourpaint/tools/polygonal/kpToolPolyline.h new file mode 100644 index 00000000..feb9c69e --- /dev/null +++ b/kolourpaint/tools/polygonal/kpToolPolyline.h @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_POLYLINE_H +#define KP_TOOL_POLYLINE_H + + +#include + + +class kpToolPolyline : public kpToolPolygonalBase +{ +Q_OBJECT + +public: + kpToolPolyline (kpToolEnvironment *environ, QObject *parent); + virtual ~kpToolPolyline (); + +private: + virtual QString haventBegunShapeUserMessage () const; + +public: + // (used by kpToolLine) + static void DrawShape (kpImage *image, + const QPolygon &points, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + bool isFinal); + + virtual void endDraw (const QPoint &, const QRect &); +}; + + +#endif // KP_TOOL_POLYLINE_H diff --git a/kolourpaint/tools/rectangular/kpToolEllipse.cpp b/kolourpaint/tools/rectangular/kpToolEllipse.cpp new file mode 100644 index 00000000..419359aa --- /dev/null +++ b/kolourpaint/tools/rectangular/kpToolEllipse.cpp @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include + +//--------------------------------------------------------------------- + +kpToolEllipse::kpToolEllipse (kpToolEnvironment *environ, QObject *parent) + : kpToolRectangularBase (i18n ("Ellipse"), + i18n ("Draws ellipses and circles"), + &kpPainter::drawEllipse, + Qt::Key_E, + environ, parent, "tool_ellipse") +{ +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/tools/rectangular/kpToolEllipse.h b/kolourpaint/tools/rectangular/kpToolEllipse.h new file mode 100644 index 00000000..970d1617 --- /dev/null +++ b/kolourpaint/tools/rectangular/kpToolEllipse.h @@ -0,0 +1,45 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_ELLIPSE_H +#define KP_TOOL_ELLIPSE_H + + +#include + + +class kpToolEllipse : public kpToolRectangularBase +{ +Q_OBJECT + +public: + kpToolEllipse (kpToolEnvironment *environ, QObject *parent); +}; + + +#endif // KP_TOOL_ELLIPSE_H diff --git a/kolourpaint/tools/rectangular/kpToolRectangle.cpp b/kolourpaint/tools/rectangular/kpToolRectangle.cpp new file mode 100644 index 00000000..dfe3d0d6 --- /dev/null +++ b/kolourpaint/tools/rectangular/kpToolRectangle.cpp @@ -0,0 +1,48 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include + +#include + +//--------------------------------------------------------------------- + + +kpToolRectangle::kpToolRectangle (kpToolEnvironment *environ, QObject *parent) + : kpToolRectangularBase (i18n ("Rectangle"), + i18n ("Draws rectangles and squares"), + &kpPainter::drawRect, + Qt::Key_R, + environ, parent, "tool_rectangle") +{ +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/tools/rectangular/kpToolRectangle.h b/kolourpaint/tools/rectangular/kpToolRectangle.h new file mode 100644 index 00000000..60a0608a --- /dev/null +++ b/kolourpaint/tools/rectangular/kpToolRectangle.h @@ -0,0 +1,45 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_RECTANGLE_H +#define KP_TOOL_RECTANGLE_H + + +#include + + +class kpToolRectangle : public kpToolRectangularBase +{ +Q_OBJECT + +public: + kpToolRectangle (kpToolEnvironment *environ, QObject *parent); +}; + + +#endif // KP_TOOL_RECTANGLE_H diff --git a/kolourpaint/tools/rectangular/kpToolRectangularBase.cpp b/kolourpaint/tools/rectangular/kpToolRectangularBase.cpp new file mode 100644 index 00000000..2b11b732 --- /dev/null +++ b/kolourpaint/tools/rectangular/kpToolRectangularBase.cpp @@ -0,0 +1,394 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_RECTANGULAR_BASE 0 + + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +//--------------------------------------------------------------------- + +struct kpToolRectangularBasePrivate +{ + kpToolRectangularBase::DrawShapeFunc drawShapeFunc; + + kpToolWidgetLineWidth *toolWidgetLineWidth; + kpToolWidgetFillStyle *toolWidgetFillStyle; + + QRect toolRectangleRect; +}; + +//--------------------------------------------------------------------- + +kpToolRectangularBase::kpToolRectangularBase ( + const QString &text, + const QString &description, + DrawShapeFunc drawShapeFunc, + int key, + kpToolEnvironment *environ, QObject *parent, + const QString &name) + + : kpTool (text, description, key, environ, parent, name), + d (new kpToolRectangularBasePrivate ()) +{ + d->drawShapeFunc = drawShapeFunc; + + d->toolWidgetLineWidth = 0, d->toolWidgetFillStyle = 0; +} + +//--------------------------------------------------------------------- + +kpToolRectangularBase::~kpToolRectangularBase () +{ + delete d; +} + +//--------------------------------------------------------------------- + + +// private slot virtual +void kpToolRectangularBase::slotLineWidthChanged () +{ + if (hasBegunDraw ()) + updateShape (); +} + +//--------------------------------------------------------------------- + +// private slot virtual +void kpToolRectangularBase::slotFillStyleChanged () +{ + if (hasBegunDraw ()) + updateShape (); +} + +//--------------------------------------------------------------------- + +// private +QString kpToolRectangularBase::haventBegunDrawUserMessage () const +{ + return i18n ("Drag to draw."); +} + +//--------------------------------------------------------------------- + +// virtual +void kpToolRectangularBase::begin () +{ +#if DEBUG_KP_TOOL_RECTANGULAR_BASE + kDebug () << "kpToolRectangularBase::begin ()"; +#endif + + kpToolToolBar *tb = toolToolBar (); + Q_ASSERT (tb); + +#if DEBUG_KP_TOOL_RECTANGULAR_BASE + kDebug () << "\ttoolToolBar=" << tb; +#endif + + d->toolWidgetLineWidth = tb->toolWidgetLineWidth (); + connect (d->toolWidgetLineWidth, + SIGNAL (lineWidthChanged (int)), + this, + SLOT (slotLineWidthChanged ())); + d->toolWidgetLineWidth->show (); + + d->toolWidgetFillStyle = tb->toolWidgetFillStyle (); + connect (d->toolWidgetFillStyle, + SIGNAL (fillStyleChanged (kpToolWidgetFillStyle::FillStyle)), + this, + SLOT (slotFillStyleChanged ())); + d->toolWidgetFillStyle->show (); + + viewManager ()->setCursor (QCursor (Qt::ArrowCursor)); + + setUserMessage (haventBegunDrawUserMessage ()); +} + +//--------------------------------------------------------------------- + +// virtual +void kpToolRectangularBase::end () +{ +#if DEBUG_KP_TOOL_RECTANGULAR_BASE + kDebug () << "kpToolRectangularBase::end ()"; +#endif + + if (d->toolWidgetLineWidth) + { + disconnect (d->toolWidgetLineWidth, + SIGNAL (lineWidthChanged (int)), + this, + SLOT (slotLineWidthChanged ())); + d->toolWidgetLineWidth = 0; + } + + if (d->toolWidgetFillStyle) + { + disconnect (d->toolWidgetFillStyle, + SIGNAL (fillStyleChanged (kpToolWidgetFillStyle::FillStyle)), + this, + SLOT (slotFillStyleChanged ())); + d->toolWidgetFillStyle = 0; + } + + viewManager ()->unsetCursor (); +} + +//--------------------------------------------------------------------- + +void kpToolRectangularBase::applyModifiers () +{ + QRect rect = normalizedRect (); + +#if DEBUG_KP_TOOL_RECTANGULAR_BASE + kDebug () << "kpToolRectangularBase::applyModifiers(" << rect + << ") shift=" << shiftPressed () + << " ctrl=" << controlPressed () + << endl; +#endif + + // user wants to startPoint () == center + if (controlPressed ()) + { + int xdiff = qAbs (startPoint ().x () - currentPoint ().x ()); + int ydiff = qAbs (startPoint ().y () - currentPoint ().y ()); + rect = QRect (startPoint ().x () - xdiff, startPoint ().y () - ydiff, + xdiff * 2 + 1, ydiff * 2 + 1); + } + + // user wants major axis == minor axis: + // rectangle --> square + // rounded rectangle --> rounded square + // ellipse --> circle + if (shiftPressed ()) + { + if (!controlPressed ()) + { + if (rect.width () < rect.height ()) + { + if (startPoint ().y () == rect.y ()) + rect.setHeight (rect.width ()); + else + rect.setY (rect.bottom () - rect.width () + 1); + } + else + { + if (startPoint ().x () == rect.x ()) + rect.setWidth (rect.height ()); + else + rect.setX (rect.right () - rect.height () + 1); + } + } + // have to maintain the center + else + { + if (rect.width () < rect.height ()) + { + QPoint center = rect.center (); + rect.setHeight (rect.width ()); + rect.moveCenter (center); + } + else + { + QPoint center = rect.center (); + rect.setWidth (rect.height ()); + rect.moveCenter (center); + } + } + } + + d->toolRectangleRect = rect; +} + +//--------------------------------------------------------------------- + +void kpToolRectangularBase::beginDraw () +{ + setUserMessage (cancelUserMessage ()); +} + +//--------------------------------------------------------------------- + + +// private +kpColor kpToolRectangularBase::drawingForegroundColor () const +{ + return color (mouseButton ()); +} + +//--------------------------------------------------------------------- + +// private +kpColor kpToolRectangularBase::drawingBackgroundColor () const +{ + const kpColor foregroundColor = color (mouseButton ()); + const kpColor backgroundColor = color (1 - mouseButton ()); + + return d->toolWidgetFillStyle->drawingBackgroundColor ( + foregroundColor, backgroundColor); +} + +//--------------------------------------------------------------------- + +// private +void kpToolRectangularBase::updateShape () +{ + kpImage image = document ()->getImageAt (d->toolRectangleRect); + + // Invoke shape drawing function passed in ctor. + (*d->drawShapeFunc) (&image, + 0, 0, d->toolRectangleRect.width (), d->toolRectangleRect.height (), + drawingForegroundColor (), d->toolWidgetLineWidth->lineWidth (), + drawingBackgroundColor ()); + + kpTempImage newTempImage (false/*always display*/, + kpTempImage::SetImage/*render mode*/, + d->toolRectangleRect.topLeft (), + image); + + viewManager ()->setFastUpdates (); + viewManager ()->setTempImage (newTempImage); + viewManager ()->restoreFastUpdates (); +} + +//--------------------------------------------------------------------- + +void kpToolRectangularBase::draw (const QPoint &, const QPoint &, const QRect &) +{ + applyModifiers (); + + + updateShape (); + + + // Recover the start and end points from the transformed & normalized d->toolRectangleRect + + // S. or S or SC or S == C + // .C C + if (currentPoint ().x () >= startPoint ().x () && + currentPoint ().y () >= startPoint ().y ()) + { + setUserShapePoints (d->toolRectangleRect.topLeft (), + d->toolRectangleRect.bottomRight ()); + } + // .C or C + // S. S + else if (currentPoint ().x () >= startPoint ().x () && + currentPoint ().y () < startPoint ().y ()) + { + setUserShapePoints (d->toolRectangleRect.bottomLeft (), + d->toolRectangleRect.topRight ()); + } + // .S or CS + // C. + else if (currentPoint ().x () < startPoint ().x () && + currentPoint ().y () >= startPoint ().y ()) + { + setUserShapePoints (d->toolRectangleRect.topRight (), + d->toolRectangleRect.bottomLeft ()); + } + // C. + // .S + else + { + setUserShapePoints (d->toolRectangleRect.bottomRight (), + d->toolRectangleRect.topLeft ()); + } +} + +//--------------------------------------------------------------------- + +void kpToolRectangularBase::cancelShape () +{ + viewManager ()->invalidateTempImage (); + + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +//--------------------------------------------------------------------- + +void kpToolRectangularBase::releasedAllButtons () +{ + setUserMessage (haventBegunDrawUserMessage ()); +} + +//--------------------------------------------------------------------- + +void kpToolRectangularBase::endDraw (const QPoint &, const QRect &) +{ + applyModifiers (); + + // TODO: flicker + // Later: So why can't we use kpViewManager::setQueueUpdates()? Check SVN + // log to see if this method was not available at the time of the + // TODO, hence justifying the TODO. + // Later2: kpToolPolygonalBase, and perhaps, other shapes will have the + // same problem. + viewManager ()->invalidateTempImage (); + + environ ()->commandHistory ()->addCommand ( + new kpToolRectangularCommand ( + text (), + d->drawShapeFunc, d->toolRectangleRect, + drawingForegroundColor (), d->toolWidgetLineWidth->lineWidth (), + drawingBackgroundColor (), + environ ()->commandEnvironment ())); + + setUserMessage (haventBegunDrawUserMessage ()); +} + +//--------------------------------------------------------------------- + + +#include diff --git a/kolourpaint/tools/rectangular/kpToolRectangularBase.h b/kolourpaint/tools/rectangular/kpToolRectangularBase.h new file mode 100644 index 00000000..e748a464 --- /dev/null +++ b/kolourpaint/tools/rectangular/kpToolRectangularBase.h @@ -0,0 +1,98 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_RECTANGULAR_BASE_H +#define KP_TOOL_RECTANGULAR_BASE_H + + +#include +#include + + +class QPoint; +class QRect; +class QString; + +class kpColor; + + +struct kpToolRectangularBasePrivate; + + +// it turns out that these shapes are all really the same thing +// (same options, same feel) - the only real difference is the +// drawing function i.e. drawShape(). +class kpToolRectangularBase : public kpTool +{ +Q_OBJECT + +public: + typedef void (*DrawShapeFunc) (kpImage * /*image*/, + int /*x*/, int /*y*/, int /*width*/, int /*height*/, + const kpColor &/*fcolor*/, int /*penWidth = 1*/, + const kpColor &/*bcolor = kpColor::Invalid*/); + + kpToolRectangularBase (const QString &text, const QString &description, + DrawShapeFunc drawShapeFunc, + int key, + kpToolEnvironment *environ, QObject *parent, + const QString &name); + virtual ~kpToolRectangularBase (); + + virtual bool careAboutModifierState () const { return true; } + +private slots: + virtual void slotLineWidthChanged (); + virtual void slotFillStyleChanged (); + +private: + QString haventBegunDrawUserMessage () const; + +public: + virtual void begin (); + virtual void end (); + +private: + void applyModifiers (); + virtual void beginDraw (); +private: + kpColor drawingForegroundColor () const; + kpColor drawingBackgroundColor () const; + void updateShape (); +public: + virtual void draw (const QPoint &, const QPoint &, const QRect &); + virtual void cancelShape (); + virtual void releasedAllButtons (); + virtual void endDraw (const QPoint &, const QRect &); + +private: + kpToolRectangularBasePrivate * const d; +}; + + +#endif // KP_TOOL_RECTANGULAR_BASE_H diff --git a/kolourpaint/tools/rectangular/kpToolRoundedRectangle.cpp b/kolourpaint/tools/rectangular/kpToolRoundedRectangle.cpp new file mode 100644 index 00000000..5ac6f8f6 --- /dev/null +++ b/kolourpaint/tools/rectangular/kpToolRoundedRectangle.cpp @@ -0,0 +1,46 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include + +//--------------------------------------------------------------------- + +kpToolRoundedRectangle::kpToolRoundedRectangle (kpToolEnvironment *environ, + QObject *parent) + : kpToolRectangularBase (i18n ("Rounded Rectangle"), + i18n ("Draws rectangles and squares with rounded corners"), + &kpPainter::drawRoundedRect, + Qt::Key_U, + environ, parent, "tool_rounded_rectangle") +{ +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/tools/rectangular/kpToolRoundedRectangle.h b/kolourpaint/tools/rectangular/kpToolRoundedRectangle.h new file mode 100644 index 00000000..58b270d5 --- /dev/null +++ b/kolourpaint/tools/rectangular/kpToolRoundedRectangle.h @@ -0,0 +1,45 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_ROUNDED_RECTANGLE_H +#define KP_TOOL_ROUNDED_RECTANGLE_H + + +#include + + +class kpToolRoundedRectangle : public kpToolRectangularBase +{ +Q_OBJECT + +public: + kpToolRoundedRectangle (kpToolEnvironment *environ, QObject *parent); +}; + + +#endif // KP_TOOL_ROUNDED_RECTANGLE_H diff --git a/kolourpaint/tools/selection/image/kpAbstractImageSelectionTool.cpp b/kolourpaint/tools/selection/image/kpAbstractImageSelectionTool.cpp new file mode 100644 index 00000000..c016ab97 --- /dev/null +++ b/kolourpaint/tools/selection/image/kpAbstractImageSelectionTool.cpp @@ -0,0 +1,103 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include + +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +kpAbstractImageSelectionTool::kpAbstractImageSelectionTool ( + const QString &text, const QString &description, + int key, + kpToolSelectionEnvironment *environ, QObject *parent, + const QString &name) + : kpAbstractSelectionTool (text, description, + key, environ, parent, name) +{ +} + +//--------------------------------------------------------------------- + +// protected virtual [kpAbstractSelectionTool] +kpAbstractSelectionContentCommand *kpAbstractImageSelectionTool::newGiveContentCommand () const +{ + kpAbstractImageSelection *imageSel = document ()->imageSelection (); + Q_ASSERT (imageSel && !imageSel->hasContent ()); + + if (imageSel->transparency ().isTransparent ()) + environ ()->flashColorSimilarityToolBarItem (); + + return new kpToolSelectionPullFromDocumentCommand ( + *imageSel, + environ ()->backgroundColor (), + QString()/*uninteresting child of macro cmd*/, + environ ()->commandEnvironment ()); +} + +//--------------------------------------------------------------------- +// protected virtual [kpAbstractSelectionTool] + +QString kpAbstractImageSelectionTool::nameOfCreateCommand () const +{ + return i18n ("Selection: Create"); +} + +//--------------------------------------------------------------------- +// protected virtual [kpAbstractSelectionTool] + +QString kpAbstractImageSelectionTool::haventBegunDrawUserMessageCreate () const +{ + // TODO: This is wrong because you can still use RMB. + return i18n ("Left drag to create selection."); +} + +//--------------------------------------------------------------------- +// protected virtual [kpAbstractSelectionTool] + +QString kpAbstractImageSelectionTool::haventBegunDrawUserMessageMove () const +{ + return i18n ("Left drag to move selection."); +} + +//--------------------------------------------------------------------- +// protected virtual [kpAbstractSelectionTool] + +QString kpAbstractImageSelectionTool::haventBegunDrawUserMessageResizeScale () const +{ + return i18n ("Left drag to scale selection."); +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/tools/selection/image/kpAbstractImageSelectionTool.h b/kolourpaint/tools/selection/image/kpAbstractImageSelectionTool.h new file mode 100644 index 00000000..e7296158 --- /dev/null +++ b/kolourpaint/tools/selection/image/kpAbstractImageSelectionTool.h @@ -0,0 +1,106 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpAbstractImageSelectionTool_H +#define kpAbstractImageSelectionTool_H + + +#include + + +class kpImageSelectionTransparency; + + +// The only difference between the various subclasses of us is the kind of +// selection that they create e.g. elliptical vs rectangular. +// +// For every other operation, they act identically so, for instance, it is +// possible to move an elliptical selection while using the rectangular +// selection tool (this situation can arise when you paste an elliptical +// selection while using the rectangular selection tool; a tool change +// does not occur out of convenience to the user - see +// kpDocumentEnvironment::switchToCompatibleTool()). +class kpAbstractImageSelectionTool : public kpAbstractSelectionTool +{ +Q_OBJECT + +public: + kpAbstractImageSelectionTool (const QString &text, const QString &description, + int key, + kpToolSelectionEnvironment *environ, QObject *parent, + const QString &name); + + +// +// Drawing +// + +protected: + virtual kpAbstractSelectionContentCommand *newGiveContentCommand () const; + + virtual QString nameOfCreateCommand () const; + + +// +// Create, Move, Resize/Scale +// + +protected: + virtual QString haventBegunDrawUserMessageCreate () const; + virtual QString haventBegunDrawUserMessageMove () const; + virtual QString haventBegunDrawUserMessageResizeScale () const; + + +// +// User Changing Selection Transparency +// + +protected: + bool shouldChangeImageSelectionTransparency () const; + // You must derive , the old selection transparency, from the + // one obtained from the user's current settings, as given by the + // kpToolSelectionEnvironment. + // + // You must _not_ simply get the old selection transparency just by + // querying the selection i.e. do _not_ pass in + // "document()->imageSelection().transparency()". The reason is that + // transparency().transparentColor() might not be defined in Opaque + // Mode. + void changeImageSelectionTransparency ( + const QString &name, + const kpImageSelectionTransparency &newTrans, + const kpImageSelectionTransparency &oldTrans); + +protected slots: + virtual void slotIsOpaqueChanged (bool isOpaque); + virtual void slotBackgroundColorChanged (const kpColor &color); + virtual void slotColorSimilarityChanged (double similarity, int); +}; + + +#endif // kpAbstractImageSelectionTool_H diff --git a/kolourpaint/tools/selection/image/kpAbstractImageSelectionTool_Transparency.cpp b/kolourpaint/tools/selection/image/kpAbstractImageSelectionTool_Transparency.cpp new file mode 100644 index 00000000..e111949d --- /dev/null +++ b/kolourpaint/tools/selection/image/kpAbstractImageSelectionTool_Transparency.cpp @@ -0,0 +1,212 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include + +// LOREFACTOR: Remove unneeded #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// protected +bool kpAbstractImageSelectionTool::shouldChangeImageSelectionTransparency () const +{ + if (environ ()->settingImageSelectionTransparency ()) + { + #if DEBUG_KP_TOOL_SELECTION + kDebug () << "\trecursion - abort setting selection transparency: " + << environ ()->settingImageSelectionTransparency () << endl; + #endif + return false; + } + + if (!document ()->imageSelection ()) + return false; + + // TODO: Can probably return false if the selection transparency mode + // is Opaque, since neither background color nor color similarity + // would matter. + + return true; +} + +// protected +void kpAbstractImageSelectionTool::changeImageSelectionTransparency ( + const QString &name, + const kpImageSelectionTransparency &newTrans, + const kpImageSelectionTransparency &oldTrans) +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "CALL(" << name << ")"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + if (hasBegunShape ()) + endShapeInternal (); + + kpAbstractImageSelection *imageSel = document ()->imageSelection (); + + if (imageSel->hasContent () && newTrans.isTransparent ()) + environ ()->flashColorSimilarityToolBarItem (); + + imageSel->setTransparency (newTrans); + + // We _must_ add the command even if kpAbstractImageSelection::setTransparency() + // above did not change the selection transparency mask at all. + // Consider the following case: + // + // 0. Ensure that selection transparency is opaque and any + // color other than red is the background color. Ensure that + // the color similarity setting is 0. + // + // 1. Select a solid red rectangle and pull it off. + // + // 2. Switch to transparent and set red as the background color. + // [the selection is now invisible as red is the background + // color, which is the same as the contents of the selection] + // + // 3. Invert Colors. + // [the selection is now cyan, red is still the background color] + // + // 4. Change the background color to green. + // [the selection transparency mask does not change so the + // selection is still cyan; green is the background color] + // + // 5. Undo + // + // If no transparency command were added for Step 4., the Undo + // in Step 5. would take us straight to the state after Step 2., + // where we would expect the red selection to be invisible. However, + // as the background color was changed to green in Step 4. and was not + // undone, the red selection is not invisible when it should be -- Undo + // has moved us to an incorrect state. + // + // KDE3: Copy this comment into the KDE 3 branch. + commandHistory ()->addCommand (new kpToolImageSelectionTransparencyCommand ( + name, + newTrans, oldTrans, + environ ()->commandEnvironment ()), + false/*no exec*/); +} + + +// protected slot virtual [kpAbstractSelectionTool] +void kpAbstractImageSelectionTool::slotIsOpaqueChanged (bool /*isOpaque*/) +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpAbstractImageSelectionTool::slotIsOpaqueChanged()"; +#endif + + if (!shouldChangeImageSelectionTransparency ()) + return; + + kpImageSelectionTransparency st = environ ()->imageSelectionTransparency (); + + kpImageSelectionTransparency oldST = st; + oldST.setOpaque (!oldST.isOpaque ()); + + changeImageSelectionTransparency ( + st.isOpaque () ? + i18n ("Selection: Opaque") : + i18n ("Selection: Transparent"), + st, oldST); +} + +// protected slot virtual [base kpTool] +void kpAbstractImageSelectionTool::slotBackgroundColorChanged (const kpColor &) +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpAbstractImageSelectionTool::slotBackgroundColorChanged()"; +#endif + + if (!shouldChangeImageSelectionTransparency ()) + return; + + kpImageSelectionTransparency st = environ ()->imageSelectionTransparency (); + + kpImageSelectionTransparency oldST = st; + oldST.setTransparentColor (oldBackgroundColor ()); + + changeImageSelectionTransparency ( + i18n ("Selection: Transparency Color"), + st, oldST); +} + +// protected slot virtual [base kpTool] +void kpAbstractImageSelectionTool::slotColorSimilarityChanged (double, int) +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpAbstractImageSelectionTool::slotColorSimilarityChanged()"; +#endif + + if (!shouldChangeImageSelectionTransparency ()) + return; + + kpImageSelectionTransparency st = environ ()->imageSelectionTransparency (); + + kpImageSelectionTransparency oldST = st; + oldST.setColorSimilarity (oldColorSimilarity ()); + + changeImageSelectionTransparency ( + i18n ("Selection: Transparency Color Similarity"), + st, oldST); +} diff --git a/kolourpaint/tools/selection/image/kpToolEllipticalSelection.cpp b/kolourpaint/tools/selection/image/kpToolEllipticalSelection.cpp new file mode 100644 index 00000000..804d56a1 --- /dev/null +++ b/kolourpaint/tools/selection/image/kpToolEllipticalSelection.cpp @@ -0,0 +1,82 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_ELLIPTICAL_SELECTION 0 + + +#include + +#include +#include + +#include +#include +#include + + +kpToolEllipticalSelection::kpToolEllipticalSelection (kpToolSelectionEnvironment *environ, + QObject *parent) + : kpAbstractImageSelectionTool (i18n ("Selection (Elliptical)"), + i18n ("Makes an elliptical or circular selection"), + Qt::Key_I, + environ, parent, + "tool_elliptical_selection") +{ +} + +kpToolEllipticalSelection::~kpToolEllipticalSelection () +{ +} + + +// protected virtual [base kpAbstractSelectionTool] +bool kpToolEllipticalSelection::drawCreateMoreSelectionAndUpdateStatusBar ( + bool dragAccepted, + const QPoint &accidentalDragAdjustedPoint, + const QRect &normalizedRect) +{ + // Prevent unintentional creation of 1-pixel selections. + if (!dragAccepted && accidentalDragAdjustedPoint == startPoint ()) + { + #if DEBUG_KP_TOOL_ELLIPTICAL_SELECTION && 1 + kDebug () << "\tnon-text NOP - return"; + #endif + setUserShapePoints (accidentalDragAdjustedPoint); + return false; + } + + Q_ASSERT (accidentalDragAdjustedPoint == currentPoint ()); + + document ()->setSelection ( + kpEllipticalImageSelection ( + normalizedRect, + environ ()->imageSelectionTransparency ())); + + setUserShapePoints (startPoint (), currentPoint ()); + + return true; +} diff --git a/kolourpaint/tools/selection/image/kpToolEllipticalSelection.h b/kolourpaint/tools/selection/image/kpToolEllipticalSelection.h new file mode 100644 index 00000000..583b4113 --- /dev/null +++ b/kolourpaint/tools/selection/image/kpToolEllipticalSelection.h @@ -0,0 +1,50 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_ELLIPTICAL_SELECTION_H +#define KP_TOOL_ELLIPTICAL_SELECTION_H + + +#include + + +class kpToolEllipticalSelection : public kpAbstractImageSelectionTool +{ +public: + kpToolEllipticalSelection (kpToolSelectionEnvironment *environ, QObject *parent); + virtual ~kpToolEllipticalSelection (); + +protected: + virtual bool drawCreateMoreSelectionAndUpdateStatusBar ( + bool dragAccepted, + const QPoint &accidentalDragAdjustedPoint, + const QRect &normalizedRect); +}; + + +#endif // KP_TOOL_ELLIPTICAL_SELECTION_H diff --git a/kolourpaint/tools/selection/image/kpToolFreeFormSelection.cpp b/kolourpaint/tools/selection/image/kpToolFreeFormSelection.cpp new file mode 100644 index 00000000..70dbf900 --- /dev/null +++ b/kolourpaint/tools/selection/image/kpToolFreeFormSelection.cpp @@ -0,0 +1,141 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_FREE_FROM_SELECTION 0 + + +#include + +#include +#include + +#include +#include +#include + +//--------------------------------------------------------------------- + +kpToolFreeFormSelection::kpToolFreeFormSelection (kpToolSelectionEnvironment *environ, + QObject *parent) + : kpAbstractImageSelectionTool (i18n ("Selection (Free-Form)"), + i18n ("Makes a free-form selection"), + Qt::Key_M, + environ, parent, + "tool_free_form_selection") +{ +} + +//--------------------------------------------------------------------- + +kpToolFreeFormSelection::~kpToolFreeFormSelection () +{ +} + +//--------------------------------------------------------------------- + +// protected virtual [base kpAbstractSelectionTool] +bool kpToolFreeFormSelection::drawCreateMoreSelectionAndUpdateStatusBar ( + bool dragAccepted, + const QPoint &accidentalDragAdjustedPoint, + const QRect &/*normalizedRect*/) +{ +#if DEBUG_KP_TOOL_FREE_FROM_SELECTION + kDebug () << "kpToolFreeFormSelection::createMoreSelectionAndUpdateStatusBar(" + << "dragAccepted=" << dragAccepted + << ",accidentalDragAdjustedPoint=" << accidentalDragAdjustedPoint + << ")" << endl; +#endif + + // Prevent unintentional creation of 1-pixel selections. + if (!dragAccepted && accidentalDragAdjustedPoint == startPoint ()) + { + #if DEBUG_KP_TOOL_FREE_FROM_SELECTION && 1 + kDebug () << "\tnon-text NOP - return"; + #endif + setUserShapePoints (accidentalDragAdjustedPoint); + return false; + } + + Q_ASSERT (accidentalDragAdjustedPoint == currentPoint ()); + Q_ASSERT (dragAccepted == (bool) document ()->selection ()); + + const kpFreeFormImageSelection *oldPointsSel = 0; + if (document ()->selection ()) + { + kpAbstractSelection *sel = document ()->selection (); + Q_ASSERT (dynamic_cast (sel)); + oldPointsSel = static_cast (sel); + } + + + QPolygon points; + + // First point in drag? + if (!dragAccepted) + { + points.append (startPoint ()); + } + // Not first point in drag. + else + { + if ( !oldPointsSel ) // assert above says we never reach this, but let's make coverity happy + return false; + + // Get existing points in selection. + points = oldPointsSel->cardinallyAdjacentPoints (); + } + + +#if DEBUG_KP_TOOL_FREE_FROM_SELECTION + kDebug () << "\tlast old point=" << points.last (); +#endif + + // TODO: There should be an upper limit on this before drawing the + // polygon becomes too slow. + points.append (accidentalDragAdjustedPoint); + + + document ()->setSelection ( + kpFreeFormImageSelection (points, environ ()->imageSelectionTransparency ())); + + // Prevent accidental usage of dangling pointer to old selection + // (deleted by kpDocument::setSelection()). + oldPointsSel = 0; + +#if DEBUG_KP_TOOL_FREE_FROM_SELECTION && 1 + kDebug () << "\t\tfreeform; #points=" + << document ()->selection ()->calculatePoints ().count () + << endl; +#endif + + setUserShapePoints (accidentalDragAdjustedPoint); + + return true; +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/tools/selection/image/kpToolFreeFormSelection.h b/kolourpaint/tools/selection/image/kpToolFreeFormSelection.h new file mode 100644 index 00000000..7ad1a9d7 --- /dev/null +++ b/kolourpaint/tools/selection/image/kpToolFreeFormSelection.h @@ -0,0 +1,50 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_FREE_FORM_SELECTION_H +#define KP_TOOL_FREE_FORM_SELECTION_H + + +#include + + +class kpToolFreeFormSelection : public kpAbstractImageSelectionTool +{ +public: + kpToolFreeFormSelection (kpToolSelectionEnvironment *environ, QObject *parent); + virtual ~kpToolFreeFormSelection (); + +protected: + virtual bool drawCreateMoreSelectionAndUpdateStatusBar ( + bool dragAccepted, + const QPoint &accidentalDragAdjustedPoint, + const QRect &normalizedRect); +}; + + +#endif // KP_TOOL_FREE_FORM_SELECTION_H diff --git a/kolourpaint/tools/selection/image/kpToolRectSelection.cpp b/kolourpaint/tools/selection/image/kpToolRectSelection.cpp new file mode 100644 index 00000000..8640f445 --- /dev/null +++ b/kolourpaint/tools/selection/image/kpToolRectSelection.cpp @@ -0,0 +1,85 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_RECT_SELECTION 0 + +#include + +#include +#include + +#include +#include +#include + + +kpToolRectSelection::kpToolRectSelection (kpToolSelectionEnvironment *environ, + QObject *parent) + : kpAbstractImageSelectionTool (i18n ("Selection (Rectangular)"), + i18n ("Makes a rectangular selection"), + Qt::Key_S, + environ, parent, + "tool_rect_selection") +{ +} + +kpToolRectSelection::~kpToolRectSelection () +{ +} + + +// protected virtual [base kpAbstractSelectionTool] +bool kpToolRectSelection::drawCreateMoreSelectionAndUpdateStatusBar ( + bool dragAccepted, + const QPoint &accidentalDragAdjustedPoint, + const QRect &normalizedRect) +{ + // Prevent unintentional creation of 1-pixel selections. + // REFACTOR: This line is duplicated code with other tools. + if (!dragAccepted && accidentalDragAdjustedPoint == startPoint ()) + { + #if DEBUG_KP_TOOL_RECT_SELECTION && 1 + kDebug () << "\tnon-text NOP - return"; + #endif + setUserShapePoints (accidentalDragAdjustedPoint); + return false; + } + + Q_ASSERT (accidentalDragAdjustedPoint == currentPoint ()); + + const QRect usefulRect = normalizedRect.intersect (document ()->rect ()); + document ()->setSelection ( + kpRectangularImageSelection ( + usefulRect, + environ ()->imageSelectionTransparency ())); + + setUserShapePoints (startPoint (), + QPoint (qMax (0, qMin (currentPoint ().x (), document ()->width () - 1)), + qMax (0, qMin (currentPoint ().y (), document ()->height () - 1)))); + + return true; +} diff --git a/kolourpaint/tools/selection/image/kpToolRectSelection.h b/kolourpaint/tools/selection/image/kpToolRectSelection.h new file mode 100644 index 00000000..e55bf7fc --- /dev/null +++ b/kolourpaint/tools/selection/image/kpToolRectSelection.h @@ -0,0 +1,50 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_RECT_SELECTION_H +#define KP_TOOL_RECT_SELECTION_H + + +#include + + +class kpToolRectSelection : public kpAbstractImageSelectionTool +{ +public: + kpToolRectSelection (kpToolSelectionEnvironment *environ, QObject *parent); + virtual ~kpToolRectSelection (); + +protected: + virtual bool drawCreateMoreSelectionAndUpdateStatusBar ( + bool dragAccepted, + const QPoint &accidentalDragAdjustedPoint, + const QRect &normalizedRect); +}; + + +#endif // KP_TOOL_RECT_SELECTION_H diff --git a/kolourpaint/tools/selection/kpAbstractSelectionTool.cpp b/kolourpaint/tools/selection/kpAbstractSelectionTool.cpp new file mode 100644 index 00000000..01acf61c --- /dev/null +++ b/kolourpaint/tools/selection/kpAbstractSelectionTool.cpp @@ -0,0 +1,631 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +// For either of these timers, they are only active during the "drawing" phase +// of kpTool. +static void AssertAllTimersInactive (struct kpAbstractSelectionToolPrivate *d) +{ + Q_ASSERT (!d->createNOPTimer->isActive ()); + Q_ASSERT (!d->RMBMoveUpdateGUITimer->isActive ()); +} + +//--------------------------------------------------------------------- + +kpAbstractSelectionTool::kpAbstractSelectionTool ( + const QString &text, + const QString &description, + int key, + kpToolSelectionEnvironment *environ, QObject *parent, + const QString &name) + + : kpTool (text, description, key, environ, parent, name), + d (new kpAbstractSelectionToolPrivate ()) +{ + d->drawType = None; + d->currentSelContentCommand = 0; + + // d->dragAccepted + // d->hadSelectionBeforeDrag + + // d->cancelledShapeButStillHoldingButtons + + d->toolWidgetOpaqueOrTransparent = 0; + + + initCreate (); + initMove (); + initResizeScale (); + + // It would be bad practice to have timers ticking even when this tool + // is not in use. + ::AssertAllTimersInactive (d); +} + +//--------------------------------------------------------------------- + +kpAbstractSelectionTool::~kpAbstractSelectionTool () +{ + uninitCreate (); + uninitMove (); + uninitResizeScale (); + + + // (state must be after construction, or after some time after end()) + Q_ASSERT (d->drawType == None); + Q_ASSERT (!d->currentSelContentCommand); + + // d->dragAccepted + // d->hadSelectionBeforeDraw + + // d->cancelledShapeButStillHoldingButtons + + // d->toolWidgetOpaqueOrTransparent + + + delete d; +} + +//--------------------------------------------------------------------- + +// protected +kpAbstractSelectionTool::DrawType kpAbstractSelectionTool::drawType () const +{ + return d->drawType; +} + +//--------------------------------------------------------------------- + +// protected +bool kpAbstractSelectionTool::hadSelectionBeforeDraw () const +{ + return d->hadSelectionBeforeDraw; +} + +//--------------------------------------------------------------------- + +// protected overrides [base kpTool] +kpToolSelectionEnvironment *kpAbstractSelectionTool::environ () const +{ + kpToolEnvironment *e = kpTool::environ (); + Q_ASSERT (dynamic_cast (e)); + return static_cast (e); +} + +//--------------------------------------------------------------------- + +// protected +bool kpAbstractSelectionTool::controlOrShiftPressed () const +{ + return (controlPressed () || shiftPressed ()); +} + +//--------------------------------------------------------------------- + +// protected +void kpAbstractSelectionTool::pushOntoDocument () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpAbstractSelectionTool::pushOntoDocument() selection=" + << document ()->selection () << endl; +#endif + Q_ASSERT (document ()->selection ()); + environ ()->deselectSelection (); +} + +//--------------------------------------------------------------------- + +// protected +void kpAbstractSelectionTool::giveContentIfNeeded () +{ + kpAbstractSelection *sel = document ()->selection (); + Q_ASSERT (sel); + + if (sel->hasContent ()) + return; + + if (d->currentSelContentCommand) + return; + + d->currentSelContentCommand = /*virtual*/newGiveContentCommand (); + d->currentSelContentCommand->execute (); +} + +//--------------------------------------------------------------------- + +// protected +// REFACTOR: sync: Code dup with kpMainWindow::addImageOrSelectionCommand (). +void kpAbstractSelectionTool::addNeedingContentCommand (kpCommand *cmd) +{ + Q_ASSERT (cmd); + + // Did we fill the selection with content? + if (d->currentSelContentCommand) + { + // Make the border creation a command. + #if DEBUG_KP_TOOL_SELECTION + kDebug () << "\thave currentSelContentCommand"; + #endif + commandHistory ()->addCreateSelectionCommand ( + new kpToolSelectionCreateCommand ( + /*virtual*/nameOfCreateCommand (), + *d->currentSelContentCommand->originalSelection (), + environ ()->commandEnvironment ()), + false/*no exec - user already dragged out sel*/); + } + + // Do we have a content setting command we need to commit? + // (yes, this is the same check as the previous "if") + if (d->currentSelContentCommand) + { + // Put the content command + given command (e.g. movement) together + // as a macro command, in the command history. + kpMacroCommand *macroCmd = new kpMacroCommand ( + cmd->name (), environ ()->commandEnvironment ()); + + macroCmd->addCommand (d->currentSelContentCommand); + d->currentSelContentCommand = 0; + + macroCmd->addCommand (cmd); + + commandHistory ()->addCommand (macroCmd, false/*no exec*/); + } + else + { + // Put the given command into the command history. + commandHistory ()->addCommand (cmd, false/*no exec*/); + } +} + +//--------------------------------------------------------------------- + + +// protected virtual +void kpAbstractSelectionTool::setSelectionBorderForHaventBegunDraw () +{ + viewManager ()->setQueueUpdates (); + { + viewManager ()->setSelectionBorderVisible (true); + viewManager ()->setSelectionBorderFinished (true); + } + viewManager ()->restoreQueueUpdates (); +} + +//--------------------------------------------------------------------- + +// private +QString kpAbstractSelectionTool::haventBegunDrawUserMessage () +{ +#if DEBUG_KP_TOOL_SELECTION && 0 + kDebug () << "kpAbstractSelectionTool::haventBegunDrawUserMessage()" + " cancelledShapeButStillHoldingButtons=" + << d->cancelledShapeButStillHoldingButtons + << endl; +#endif + + if (d->cancelledShapeButStillHoldingButtons) + return i18n ("Let go of all the mouse buttons."); + + return operation (calculateDrawType (), HaventBegunDrawUserMessage).toString (); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpAbstractSelectionTool::begin () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpAbstractSelectionTool<" << objectName () << ">::begin()"; +#endif + + ::AssertAllTimersInactive (d); + + // (state must be after construction, or after some time after end()) + Q_ASSERT (d->drawType == None); + Q_ASSERT (!d->currentSelContentCommand); + + d->dragAccepted = false; + // d->hadSelectionBeforeDraw + + d->cancelledShapeButStillHoldingButtons = false; + + + kpToolToolBar *tb = toolToolBar (); + Q_ASSERT (tb); + + d->toolWidgetOpaqueOrTransparent = tb->toolWidgetOpaqueOrTransparent (); + Q_ASSERT (d->toolWidgetOpaqueOrTransparent); + connect (d->toolWidgetOpaqueOrTransparent, SIGNAL (isOpaqueChanged (bool)), + this, SLOT (slotIsOpaqueChanged (bool))); + d->toolWidgetOpaqueOrTransparent->show (); + + /*virtual*/setSelectionBorderForHaventBegunDraw (); + + + beginCreate (); + beginMove (); + beginResizeScale (); + + + setUserMessage (haventBegunDrawUserMessage ()); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpAbstractSelectionTool::end () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpAbstractSelectionTool<" << objectName () << ">::end()"; +#endif + + if (document ()->selection ()) + pushOntoDocument (); + + + endCreate (); + endMove (); + endResizeScale (); + + + // (should have been killed by cancelShape() or endDraw()) + Q_ASSERT (d->drawType == None); + Q_ASSERT (!d->currentSelContentCommand); + + // d->dragAccepted + // d->hadSelectionBeforeDraw + + // d->cancelledShapeButStillHoldingButtons + + + Q_ASSERT (d->toolWidgetOpaqueOrTransparent); + disconnect (d->toolWidgetOpaqueOrTransparent, SIGNAL (isOpaqueChanged (bool)), + this, SLOT (slotIsOpaqueChanged (bool))); + d->toolWidgetOpaqueOrTransparent = 0; + + + viewManager ()->unsetCursor (); + + ::AssertAllTimersInactive (d); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpAbstractSelectionTool::reselect () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpAbstractSelectionTool::reselect()"; +#endif + + if (document ()->selection ()) + pushOntoDocument (); +} + +//--------------------------------------------------------------------- + +// protected virtual +kpAbstractSelectionTool::DrawType kpAbstractSelectionTool::calculateDrawTypeInsideSelection () const +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "\t\tis move"; +#endif + return kpAbstractSelectionTool::Move; +} + +//--------------------------------------------------------------------- + +// protected virtual +kpAbstractSelectionTool::DrawType kpAbstractSelectionTool::calculateDrawType () const +{ + kpAbstractSelection *sel = document ()->selection (); + if (!sel) + return Create; +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "\thas sel region rect=" << sel->boundingRect (); +#endif + + if (onSelectionResizeHandle () && !controlOrShiftPressed ()) + return ResizeScale; + else if (sel->contains (currentPoint ())) + return /*virtual*/calculateDrawTypeInsideSelection (); + else + return Create; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpAbstractSelectionTool::beginDraw () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpAbstractSelectionTool::beginDraw() startPoint ()=" + << startPoint () + << " QCursor::pos() view startPoint=" + << viewUnderStartPoint ()->mapFromGlobal (QCursor::pos ()) + << endl; +#endif + + // endDraw() and cancelShape() should have taken care of these. + ::AssertAllTimersInactive (d); + + // In case the cursor was wrong to start with + // (forgot to call kpTool::somethingBelowTheCursorChanged()), + // make sure it is correct during this operation. + hover (currentPoint ()); + + // Currently used only to end the current text + if (hasBegunShape ()) + { + endShape(currentPoint(), + kpPainter::normalizedRect(startPoint()/* TODO: wrong */, currentPoint())); + } + + d->drawType = calculateDrawType (); + d->dragAccepted = false; + + kpAbstractSelection *sel = document ()->selection (); + d->hadSelectionBeforeDraw = bool (sel); + + operation (d->drawType, BeginDraw); +} + +//--------------------------------------------------------------------- + + +// public virtual [base kpTool] +void kpAbstractSelectionTool::hover (const QPoint &point) +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpAbstractSelectionTool::hover" << point; +#endif + + operation (calculateDrawType (), SetCursor); + + setUserShapePoints (point, KP_INVALID_POINT, false/*don't set size*/); + if (document () && document ()->selection ()) + { + setUserShapeSize (document ()->selection ()->width (), + document ()->selection ()->height ()); + } + else + { + setUserShapeSize (KP_INVALID_SIZE); + } + + QString mess = haventBegunDrawUserMessage (); + if (mess != userMessage ()) + setUserMessage (mess); +} + +//--------------------------------------------------------------------- + + +// public virtual [base kpTool] +void kpAbstractSelectionTool::draw (const QPoint &thisPoint, const QPoint & /*lastPoint*/, + const QRect &normalizedRect) +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpAbstractSelectionTool::draw (" << thisPoint + << ",startPoint=" << startPoint () + << ",normalizedRect=" << normalizedRect << ")" << endl; +#else + Q_UNUSED (thisPoint); + Q_UNUSED (normalizedRect); +#endif + + + // OPT: return when thisPoint == lastPoint () so that e.g. when creating + // Points sel, press modifiers doesn't add multiple points in same + // place + + + operation (d->drawType, Draw); +} + +//--------------------------------------------------------------------- + + +// public virtual [base kpTool] +void kpAbstractSelectionTool::cancelShape () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpAbstractSelectionTool::cancelShape() mouseButton=" + << mouseButton () << endl; +#endif + + const DrawType oldDrawType = d->drawType; + // kpTool::hasBegunDraw() returns false in this method so be consistent + // and clear "drawType" before dispatching the operation() below. + d->drawType = None; + + + viewManager ()->setQueueUpdates (); + { + operation (oldDrawType, Cancel); + + + if (d->currentSelContentCommand) + { + #if DEBUG_KP_TOOL_SELECTION + kDebug () << "\t\tundo sel content"; + #endif + d->currentSelContentCommand->unexecute (); + delete d->currentSelContentCommand; + d->currentSelContentCommand = 0; + } + + + /*virtual*/setSelectionBorderForHaventBegunDraw (); + } + viewManager ()->restoreQueueUpdates (); + + + d->cancelledShapeButStillHoldingButtons = true; + setUserMessage (i18n ("Let go of all the mouse buttons.")); + + + ::AssertAllTimersInactive (d); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpAbstractSelectionTool::releasedAllButtons () +{ + d->cancelledShapeButStillHoldingButtons = false; + setUserMessage (haventBegunDrawUserMessage ()); +} + +//--------------------------------------------------------------------- + +// protected +void kpAbstractSelectionTool::popupRMBMenu () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "CALL - exec'ing menu"; +#endif + + QMenu *pop = environ ()->selectionToolRMBMenu (); + Q_ASSERT (pop); + + // Blocks until the menu closes. + // WARNING: Enters event loop - may re-enter view/tool event handlers. + pop->exec (QCursor::pos ()); +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "calling somethingBelowTheCursorChanged()"; +#endif + + // Cursor may have moved while the menu was up, triggering QMouseMoveEvents + // for the menu -- but not the view -- so we may have missed cursor moves. + // Update cursor position now. + somethingBelowTheCursorChanged (); +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "DONE"; +#endif +} + +//--------------------------------------------------------------------- + +// public virtual [base kpTool] +void kpAbstractSelectionTool::endDraw (const QPoint & /*thisPoint*/, + const QRect & /*normalizedRect*/) +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "kpAbstractSelectionTool::endDraw()"; +#endif + + const DrawType oldDrawType = d->drawType; + // kpTool::hasBegunDraw() returns false in this method so be consistent + // and clear "drawType" before dispatching the operation() below. + d->drawType = None; + + + viewManager ()->setQueueUpdates (); + { + operation (oldDrawType, EndDraw); + + /*virtual*/setSelectionBorderForHaventBegunDraw (); + } + viewManager ()->restoreQueueUpdates (); + + + setUserMessage (haventBegunDrawUserMessage ()); + + + ::AssertAllTimersInactive (d); + + + if (mouseButton () == 1/*right*/) + popupRMBMenu (); + + + // WARNING: Do not place any code after the popupRMBMenu() call + // (see the popupRMBMenu() API). +} + +//--------------------------------------------------------------------- + +// protected virtual +QVariant kpAbstractSelectionTool::operation (DrawType drawType, Operation op, + const QVariant &data1, const QVariant &data2) +{ + switch (drawType) + { + case None: + // NOP. + return QVariant (); + + case Create: + return operationCreate (op, data1, data2); + + case Move: + return operationMove (op, data1, data2); + + case ResizeScale: + return operationResizeScale (op, data1, data2); + + default: + Q_ASSERT (!"Unhandled draw type"); + return QVariant (); + } +} + +//--------------------------------------------------------------------- + + +#include + diff --git a/kolourpaint/tools/selection/kpAbstractSelectionTool.h b/kolourpaint/tools/selection/kpAbstractSelectionTool.h new file mode 100644 index 00000000..1f670163 --- /dev/null +++ b/kolourpaint/tools/selection/kpAbstractSelectionTool.h @@ -0,0 +1,600 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpAbstractSelectionTool_H +#define kpAbstractSelectionTool_H + + +#include + +#include + + +class QKeyEvent; +class QPoint; +class QRect; + +class kpAbstractSelection; +class kpAbstractSelectionContentCommand; +class kpCommand; +class kpToolSelectionEnvironment; + + +// The abstract base for all selection tools. +// +// +// This provides methods to: +// +// 1. handle selection commands and the command history. +// +// 2. implement the kpTool drawing methods. +// +// 3. implement the "Create", "Move" and "Resize/Scale" selection +// draw types. +// +// "Drags" that consist of a single click generally have no effect in +// order to prevent accidently creating a 1x1 selection, doing a NOP move +// or doing a NOP resize/scale. However, doing a bigger drag and then +// dragging the mouse back to create a 1x1 selection or the NOP +// effects are allowed and are recorded in the command history. +// +// Additionally, the "Create" draw type is fitted with "accidental drag +// detection" which will not create a selection in response in small and +// quick drags. +// +// +// The internal architecture is as follows and is of interest for subclasses +// and for changing the existing implementation of the above selection +// draw types: +// +// beginDraw() initiates the action by determining the current draw type +// by calling the virtual calculateDrawType(). Later, all of this class' +// implementations of kpTool drawing methods (e.g. beginDraw(), draw(), +// endShape() etc.) call the virtual operation(), which is passed: +// +// 1. the current draw type (e.g. "Create" or "Move") +// +// 2. the operation, corresponding to the calling method (e.g. +// "BeginDraw" of called by beginDraw()). +// +// [Note: the documentation in these source files sometimes uses "operation" +// where "draw type" is meant and vice-versa] +// +// Note that these kpTool drawing methods do some other work before and after +// calling operation(). +// +// The default implementation of operation() is to dispatch the operation +// to draw-type-specific methods e.g. createOperation() handles all +// operations for the "Create" draw type. createOperation() will then +// call the method that corresponds to the operation e.g. beginDrawCreate() +// corresponds to the "BeginDraw" operation. +// +// For each draw type, all methods are grouped in a single source file +// e.g. kpAbstractSelectionTool_Create.cpp. +// +// To introduce a custom draw type, not implemented by code in this +// class (e.g. "SelectText"), you must: +// +// 1. Add it to "enum DrawType" below. +// +// 2. Override calculateDrawType() to determine the situations in which +// the new draw type is active. +// +// 3. Override operation() to catch all situations in which the new draw +// type is being used, and to implement the appropriate logic. +// +class kpAbstractSelectionTool : public kpTool +{ +Q_OBJECT + +public: + kpAbstractSelectionTool (const QString &text, const QString &description, + int key, + kpToolSelectionEnvironment *environ, QObject *parent, + const QString &name); + + virtual ~kpAbstractSelectionTool (); + + + // Inform kpTool to call draw() when CTRL, SHIFT and friends are + // pressed. CTRL is used for copying, instead of moving, the + // selection. SHIFT is used for sweeping. + virtual bool careAboutModifierState () const { return true; } + + +// +// Drawing - Subclass Accessors +// + +protected: + friend struct kpAbstractSelectionToolPrivate; + enum DrawType + { + None, Create, Move, SelectText, ResizeScale + }; + + + // The return value is not "None" during a drawing operation. + // + // The returned value is set by beginDraw(), after being determined + // by calculateDrawType(). It is cleared in cancelShape() and endDraw(). + DrawType drawType () const; + + bool hadSelectionBeforeDraw () const; + + +// +// Drawing +// + +protected: + // (overrides non-virtual method in kpTool) + kpToolSelectionEnvironment *environ () const; + + // Returns whether a CTRL or SHIFT key is currently pressed. + // Convenience method. + bool controlOrShiftPressed () const; + + +protected: + // Deselects the current selection: + // + // 1. If it has no content, it is simply deleted. + // 2. If it has content, it pushes it onto the document, adding the + // necessary commands to the command history. + // + // ASSUMPTIONS: + // 1. There is a current selection. + // 2. You have not called giveContentIfNeeded() nor + // addNeedingContentCommand() on the current selection. + void pushOntoDocument (); + + +// +// The command lifecycle is as follows: +// +// 1. Ensure that the document has a selection, with or without content. +// +// 2. Call giveContentIfNeeded(). +// +// 3. Create the command. +// +// 4. Process user input, mutate the selection directly and update the +// command with the user's input. +// +// 5. When the drawing operatinon is complete, call addNeedingContentCommand() +// with the command created in Step 3. +// +protected: + // Returns a new instance of the give-the-selection-content command + // that matches the current selection type. The command should not be + // executed by this method. + virtual kpAbstractSelectionContentCommand *newGiveContentCommand () const = 0; + + // Before changing a selection (e.g. moving or resizing), you must + // ensure that it has content. Call this method to ensure that. + // + // If the selection has no content, this calls newGiveContentCommand() + // and executes it. If the selection already has content, this does + // nothing. + // + // ASSUMPTION: There is a selection. + void giveContentIfNeeded (); + + // The name that should be given to command that is constructed in + // response to a drag that creates a new selection. + virtual QString nameOfCreateCommand () const = 0; + + // Add a command to the command history. + // The command is not executed. + // + // If the prior call to giveContentIfNeeded() created content, this + // will, in line with KolourPaint selection convention: + // + // 1. Adds a selection border creation command (this is a bit clever + // and may overwrite the last "Undo" command instead -- see + // kpCommandHistory::addCreateSelectionCommand()). + // + // 2. Group the content command created by giveContentIfNeeded() + // with , as a kpMacroCommand also named . + // + // ASSUMPTION: giveContentIfNeeded() must have been called before + // creating . + void addNeedingContentCommand (kpCommand *cmd); + + +protected: + // Sets the selection border mode when no drawing operation is active. + // + // Subclasses may wish to reimplement but should still call the base + // implementation. Reimplementations should wrap the whole + // reimplementation in a kpViewManager::setQueueUpdates() block. + virtual void setSelectionBorderForHaventBegunDraw (); +private: + // Returns the statusbar message from when no draw operation is in + // progress. Calls operation() with "HaventBegunDrawUserMessage". + // + // (not const due to purely syntactic issue: it calls the non-const + // operation(); it really acts like a const method though) + QString haventBegunDrawUserMessage (); + + +public: + virtual void begin (); + virtual void end (); + + +public: + virtual void reselect (); + + +// +// Drawing - Beginning a Drag +// + +protected: + // Called by calculateDrawType() to determine what type of draw type + // is being started in response to a drag inside the bounding rectangle of + // a selection. + // + // This implementation returns "Move". + // + // You are free to reimplement this and may choose to call this base + // implementation or not. + virtual DrawType calculateDrawTypeInsideSelection () const; + + // Called by beginDraw() to determine what type of draw type is + // being started. The returned draw type is passed to operation(). + // + // This implementation behaves according to the first rule that matches: + // + // 1. If the cursor is on top of a selection resize handle and no modifiers + // are held (i.e. not a smearing move draw type), it returns "ResizeScale". + // + // 2. If the cursor is inside the bounding rectangle of a selection, it + // calls calculateDrawTypeInsideSelection(). + // + // 3. Otherwise, it returns "Create". + // + // You are free to reimplement this and may choose to call this base + // implementation or not. Reimplementing allows you to support new + // draw types for different types of selections (e.g. kpToolText + // supports "SelectText"). It also allows you to make certain + // drags (e.g. dragging in the middle of a selection) do nothing by + // returning "None" instead of calling the base implementation. + virtual DrawType calculateDrawType () const; +public: + virtual void beginDraw (); + + +// +// Drawing - Mouse Movement +// + +public: + virtual void hover (const QPoint &point); + virtual void draw (const QPoint &thisPoint, const QPoint &lastPoint, + const QRect &normalizedRect); + + +// +// Drawing - Ending a Drag +// + +public: + virtual void cancelShape (); + virtual void releasedAllButtons (); + + +protected: + // Displays the right-mouse-button-triggered selection menu, re-entering + // the event loop and blocking until the menu closes. + // + // This menu is a subset of the main window's Edit and Selection menus. + // + // WARNING: This may cause a re-entry of view/tool event handlers. + // If you are calling this from a view/tool event handler, + // either make all your handlers re-entrant or do not put any + // code in your handler after the call. + void popupRMBMenu (); +public: + virtual void endDraw (const QPoint &thisPoint, const QRect &normalizedRect); + + +// +// Drawing - Operation Dispatching +// + +protected: + enum Operation + { + // + // These may be called outside of a drawing operation where + // drawType() will return None. + // + + // Returns the message for the given draw type and operation. + HaventBegunDrawUserMessage, + + SetCursor, + + + // + // Called to start, to end, or inside, a drawing operation. + // + + BeginDraw, Draw, Cancel, EndDraw + }; + + // (See the class API Doc for a description). + virtual QVariant operation (DrawType drawType, Operation op, + const QVariant &data1 = QVariant (), const QVariant &data2 = QVariant ()); + + +// +// Create +// + +private: + // Called by constructor to initialize the "Create" draw type. + void initCreate (); + // Called by destructor to uninitialize the "Create" draw type. + void uninitCreate (); + + +private: + void beginCreate (); + void endCreate (); + + +protected: + virtual QString haventBegunDrawUserMessageCreate () const = 0; +private: + void setCursorCreate (); + + +protected: + // Sets the selection border mode when beginning to drag to create a + // selection. + // + // Subclasses may wish to reimplement but should still call the base + // implementation. Reimplementations should wrap the whole + // reimplementation in a kpViewManager::setQueueUpdates() block. + virtual void setSelectionBorderForBeginDrawCreate (); +private: + void beginDrawCreate (); + + +protected: + // + // If the drag has already been substantial enough to be considered as a + // non-NOP drag (), you must return "true". + // + // If it has not, you should return whether you think the drag should + // be started. This criteria usually includes "if + // is not equal to startPoint()". + // + // If you are returning true, you must: + // + // 1. Set the document's selection (which may not have previously + // existed) to the specified size. + // + // 2. Update the status bar by calling kpTool::setUserShapePoints(). + // + // If you return false, you are still permitted to do the above, + // although it would be unusual (kpToolText does the above to allow a + // single click -- with no dragging -- to create a new text box). + // + // The return value will be fed into the next call as . + // + // Arguments: + // + // 1. : + // This is the same as currentPoint() but is set to startPoint() + // if the mouse has not been moved much (6 manhatten length pixels + // from startPoint() within a short period of time (200ms)). + // This provides the accidental drag detection, referred to in the + // class' API Doc. + // + // 2. : + // This is as passed to kpTool::draw(). + // + virtual bool drawCreateMoreSelectionAndUpdateStatusBar ( + bool dragAccepted, + const QPoint &accidentalDragAdjustedPoint, + const QRect &normalizedRect) = 0; + void drawCreate (const QPoint &thisPoint, const QRect &normalizedRect); +private slots: + void delayedDrawCreate (); + + +private: + void cancelCreate (); + void endDrawCreate (); + + +private: + QVariant operationCreate (Operation op, + const QVariant &data1, const QVariant &data2); + + +// +// Move +// + +private: + // Called by constructor to initialize the "Move" draw type. + void initMove (); + // Called by destructor to uninitialize the "Move" draw type. + void uninitMove (); + + +private: + void beginMove (); + void endMove (); + + +protected: + virtual QString haventBegunDrawUserMessageMove () const = 0; +private: + void setCursorMove (); + + +protected: + // Sets the selection border mode when beginning to drag to move a + // selection. + // + // Subclasses may wish to reimplement but should still call the base + // implementation. Reimplementations should wrap the whole + // reimplementation in a kpViewManager::setQueueUpdates() block. + virtual void setSelectionBorderForBeginDrawMove (); +private: + void beginDrawMove (); +private slots: + void slotRMBMoveUpdateGUI (); + + +private: + void drawMove (const QPoint &thisPoint, const QRect &normalizedRect); + + +private: + void cancelMove (); +protected: + // Returns what the name of the command that moves -- but does not smear + // (not holding SHIFT) -- the selection, should be. + virtual QString nonSmearMoveCommandName () const; +private: + void endDrawMove (); + + +private: + QVariant operationMove (Operation op, + const QVariant &data1, const QVariant &data2); + + +// +// Resize/Scale +// + +private: + int onSelectionResizeHandle () const; + + +private: + // Called by constructor to initialize the "Resize/Scale" draw type. + void initResizeScale (); + // Called by destructor to uninitialize the "Resize/Scale" draw type. + void uninitResizeScale (); + + +private: + void beginResizeScale (); + void endResizeScale (); + + +protected: + virtual QString haventBegunDrawUserMessageResizeScale () const = 0; +private: + void setCursorResizeScale (); + + +protected: + // Sets the selection border mode when beginning to drag to resize or + // scale a selection. + // + // Subclasses may wish to reimplement but should still call the base + // implementation. Reimplementations should wrap the whole + // reimplementation in a kpViewManager::setQueueUpdates() block. + virtual void setSelectionBorderForBeginDrawResizeScale (); +private: + void beginDrawResizeScale (); + + +private: + // drawResizeScaleCalculateNewSelectionPosSize() calls us with what the + // x should be, but before any aspect maintenance + // operations. + // + // specifies whether a horizontal grip is being + // dragged. specifies whether a vertical grip is + // being dragged. + // + // The selection before any resizing/scaling (before the sequence of + // drags, where the mouse has been held down) is . + // + // The method should output its attempt at maintaining the aspect ratio. + // We say "attempt" because it is constrained by the minimum allowed + // size of the selection. + void drawResizeScaleTryKeepAspect (int newWidth, int newHeight, + bool horizontalGripDragged, bool verticalGripDragged, + const kpAbstractSelection &originalSelection, + int *newWidthOut, int *newHeightOut); + + void drawResizeScaleCalculateNewSelectionPosSize ( + const kpAbstractSelection &originalSelection, + int *newX, int *newY, + int *newWidth, int *newHeight); + + void drawResizeScale (const QPoint &thisPoint, const QRect &normalizedRect); + + +private: + void cancelResizeScale (); + void endDrawResizeScale (); + + +private: + QVariant operationResizeScale (Operation op, + const QVariant &data1, const QVariant &data2); + + +// +// User Setting Selection Options +// + +protected slots: + virtual void slotIsOpaqueChanged (bool isOpaque) = 0; + + +// +// Keyboard Events +// + +protected: + // Reimplemented to trap Esc presses for deselecting the selection. + // All other keypresses are passed to the base implementation. + virtual void keyPressEvent (QKeyEvent *e); + + +private: + struct kpAbstractSelectionToolPrivate * const d; +}; + + +#endif // kpAbstractSelectionTool_H diff --git a/kolourpaint/tools/selection/kpAbstractSelectionToolPrivate.h b/kolourpaint/tools/selection/kpAbstractSelectionToolPrivate.h new file mode 100644 index 00000000..1eb52cf8 --- /dev/null +++ b/kolourpaint/tools/selection/kpAbstractSelectionToolPrivate.h @@ -0,0 +1,92 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpAbstractSelectionToolPrivate_H +#define kpAbstractSelectionToolPrivate_H + + +#include + +#include + + +class QTimer; + +class kpAbstractSelectionContentCommand; +class kpToolSelectionMoveCommand; +class kpToolSelectionResizeScaleCommand; +class kpToolWidgetOpaqueOrTransparent; + + +struct kpAbstractSelectionToolPrivate +{ + kpAbstractSelectionTool::DrawType drawType; + kpAbstractSelectionContentCommand *currentSelContentCommand; + + // Whether the drag has been substantial enough to be considered as a + // non-NOP drag. The "substantial enough" critera is draw-type + // dependent and is usually based on how far the mouse has been + // dragged. See kpAbstractSelectionTool's API Doc for details. + bool dragAccepted; + + bool hadSelectionBeforeDraw; + + bool cancelledShapeButStillHoldingButtons; + + kpToolWidgetOpaqueOrTransparent *toolWidgetOpaqueOrTransparent; + + + // + // Create + // + + QTimer *createNOPTimer; + + + // + // Move + // + + kpToolSelectionMoveCommand *currentMoveCommand; + bool currentMoveCommandIsSmear; + + QPoint startMoveDragFromSelectionTopLeft; + + QTimer *RMBMoveUpdateGUITimer; + + + // + // Resize / Scale + // + + kpToolSelectionResizeScaleCommand *currentResizeScaleCommand; + int resizeScaleType; +}; + + +#endif // kpAbstractSelectionToolPrivate_H diff --git a/kolourpaint/tools/selection/kpAbstractSelectionTool_Create.cpp b/kolourpaint/tools/selection/kpAbstractSelectionTool_Create.cpp new file mode 100644 index 00000000..32ca20bb --- /dev/null +++ b/kolourpaint/tools/selection/kpAbstractSelectionTool_Create.cpp @@ -0,0 +1,305 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// private +void kpAbstractSelectionTool::initCreate () +{ + d->createNOPTimer = new QTimer (this); + d->createNOPTimer->setSingleShot (true); + connect (d->createNOPTimer, SIGNAL (timeout ()), + this, SLOT (delayedDrawCreate ())); +} + +// private +void kpAbstractSelectionTool::uninitCreate () +{ + // d->createNOPTimer (deleted by QObject mechanism) +} + + +// private +void kpAbstractSelectionTool::beginCreate () +{ + // d->createNOPTimer +} + +// private +void kpAbstractSelectionTool::endCreate () +{ + // d->createNOPTimer +} + +//--------------------------------------------------------------------- +// use a crosshair cursor which is really always exactly 1 pixel wide +// to the contrary of the "themed" crosshair cursors which might look nice +// but does not allow to exactly position the hot-spot. +/* XPM */ +static const char *crosshair[]={ +"17 17 3 1", +". c None", +"x c #FFFFFF", +"# c #000000", +".......xxx.......", +".......x#x.......", +".......x#x.......", +".......x#x.......", +".......x#x.......", +".......x#x.......", +".......x#x.......", +"xxxxxxxx#xxxxxxxx", +"x#######.#######x", +"xxxxxxxx#xxxxxxxx", +".......x#x.......", +".......x#x.......", +".......x#x.......", +".......x#x.......", +".......x#x.......", +".......x#x.......", +".......xxx......."}; + +// private +void kpAbstractSelectionTool::setCursorCreate () +{ + viewManager()->setCursor(QCursor(QPixmap(crosshair), 8, 8)); +} + +//--------------------------------------------------------------------- + +// protected virtual +void kpAbstractSelectionTool::setSelectionBorderForBeginDrawCreate () +{ + viewManager ()->setQueueUpdates (); + { + // LOREFACTOR: I suspect some calls to viewManager() in this + // file (including this) are redundant since any + // code that tweaks such settings, returns them to + // their original state, after the code is complete. + viewManager ()->setSelectionBorderVisible (true); + + viewManager ()->setSelectionBorderFinished (false); + } + viewManager ()->restoreQueueUpdates (); +} + +// private +void kpAbstractSelectionTool::beginDrawCreate () +{ + if (document ()->selection ()) + pushOntoDocument (); + + /*virtual*/setSelectionBorderForBeginDrawCreate (); + + // (single shot) + d->createNOPTimer->start (200/*ms*/); + + setUserMessage (cancelUserMessage ()); +} + + +// private +void kpAbstractSelectionTool::drawCreate (const QPoint &thisPoint, + const QRect &normalizedRect) +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "\tnot moving - resizing rect to" << normalizedRect + << endl; + kDebug () << "\t\tcreateNOPTimer->isActive()=" + << d->createNOPTimer->isActive () + << " viewManhattanLength from startPoint=" + << viewUnderStartPoint ()->transformDocToViewX ((thisPoint - startPoint ()).manhattanLength ()) + << endl; +#endif + + QPoint accidentalDragAdjustedPoint = thisPoint; + if (d->createNOPTimer->isActive ()) + { + // See below "d->createNOPTimer->stop()". + Q_ASSERT (!d->dragAccepted); + + if (viewUnderStartPoint ()->transformDocToViewX ( + (accidentalDragAdjustedPoint - startPoint ()).manhattanLength ()) <= 6) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "\t\tsuppress accidental movement"; + #endif + accidentalDragAdjustedPoint = startPoint (); + } + else + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "\t\tit's a \"big\" intended move - stop timer"; + #endif + d->createNOPTimer->stop (); + } + } + + + const bool hadSelection = document ()->selection (); + + const bool oldDrawAcceptedAsDrag = d->dragAccepted; + d->dragAccepted = /*virtual*/drawCreateMoreSelectionAndUpdateStatusBar ( + d->dragAccepted, + accidentalDragAdjustedPoint, + normalizedRect); + if (oldDrawAcceptedAsDrag) + Q_ASSERT (d->dragAccepted); + if (d->dragAccepted) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "\t\tdrawHasDoneSomething - kill create timer"; + #endif + // No longer a NOP. + d->createNOPTimer->stop (); + } + + // Did we just create a selection? + if (!hadSelection && document ()->selection ()) + viewManager ()->setSelectionBorderVisible (true); +} + +// private slot +void kpAbstractSelectionTool::delayedDrawCreate () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "kpAbstractSelectionTool::delayedDrawCreate() hasBegunDraw=" + << hasBegunDraw () + << " currentPoint=" << currentPoint () + << " lastPoint=" << lastPoint () + << " startPoint=" << startPoint () + << endl; +#endif + + // (just in case not called from single shot) + d->createNOPTimer->stop (); + + if (hasBegunDraw ()) + { + draw (currentPoint (), lastPoint (), normalizedRect ()); + } +} + + +// private +void kpAbstractSelectionTool::cancelCreate () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "\twas creating sel - kill"; +#endif + + d->createNOPTimer->stop (); + + // TODO: should we give the user back the selection s/he had before (if any)? + if (document ()->selection ()) + document ()->selectionDelete (); +} + +// private +void kpAbstractSelectionTool::endDrawCreate () +{ + d->createNOPTimer->stop (); +} + + +// private +QVariant kpAbstractSelectionTool::operationCreate (Operation op, + const QVariant &data1, const QVariant &data2) +{ + (void) data1; + (void) data2; + + + switch (op) + { + case HaventBegunDrawUserMessage: + return /*virtual*/haventBegunDrawUserMessageCreate (); + + case SetCursor: + setCursorCreate (); + break; + + case BeginDraw: + beginDrawCreate (); + break; + + case Draw: + drawCreate (currentPoint (), normalizedRect ()); + break; + + case Cancel: + cancelCreate (); + break; + + case EndDraw: + endDrawCreate (); + break; + + default: + Q_ASSERT (!"Unhandled operation"); + break; + } + + + return QVariant (); +} diff --git a/kolourpaint/tools/selection/kpAbstractSelectionTool_KeyboardEvents.cpp b/kolourpaint/tools/selection/kpAbstractSelectionTool_KeyboardEvents.cpp new file mode 100644 index 00000000..038124c2 --- /dev/null +++ b/kolourpaint/tools/selection/kpAbstractSelectionTool_KeyboardEvents.cpp @@ -0,0 +1,103 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include +#include +#include +#include +#include +#include + +#include + +#include + +//--------------------------------------------------------------------- + +// protected virtual [base kpTool] +void kpAbstractSelectionTool::keyPressEvent (QKeyEvent *e) +{ +#if DEBUG_KP_TOOL_SELECTION && 0 + kDebug () << "kpAbstractSelectionTool::keyPressEvent(e->text='" + << e->text () << "')" << endl; +#endif + + e->ignore (); + + if (document ()->selection () && + !hasBegunDraw () && + e->key () == Qt::Key_Escape) + { + #if DEBUG_KP_TOOL_SELECTION && 0 + kDebug () << "\tescape pressed with sel when not begun draw - deselecting" + << endl; + #endif + + pushOntoDocument (); + e->accept (); + } + else + { + #if DEBUG_KP_TOOL_SELECTION && 0 + kDebug () << "\tkey processing did not accept (text was '" + << e->text () + << "') - passing on event to kpTool" + << endl; + #endif + + if ( document()->selection() && !hasBegunDraw() && + ((e->key() == Qt::Key_Left) || + (e->key() == Qt::Key_Right) || + (e->key() == Qt::Key_Up) || + (e->key() == Qt::Key_Down)) ) + { + // move selection with cursor keys pixel-wise + giveContentIfNeeded(); + + if ( !d->currentMoveCommand ) + { + d->currentMoveCommand = new kpToolSelectionMoveCommand( + QString()/*uninteresting child of macro cmd*/, + environ()->commandEnvironment()); + d->currentMoveCommandIsSmear = false; + } + + int dx, dy; + arrowKeyPressDirection(e, &dx, &dy); + d->currentMoveCommand->moveTo(document()->selection()->topLeft() + QPoint(dx, dy)); + endDrawMove(); + } + else + kpTool::keyPressEvent(e); + } +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/tools/selection/kpAbstractSelectionTool_Move.cpp b/kolourpaint/tools/selection/kpAbstractSelectionTool_Move.cpp new file mode 100644 index 00000000..65ee111e --- /dev/null +++ b/kolourpaint/tools/selection/kpAbstractSelectionTool_Move.cpp @@ -0,0 +1,401 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::initMove () +{ + d->currentMoveCommand = 0; + + // d->currentMoveCommandIsSmear + + // d->startMoveDragFromSelectionTopLeft + + d->RMBMoveUpdateGUITimer = new QTimer (this); + d->RMBMoveUpdateGUITimer->setSingleShot (true); + connect (d->RMBMoveUpdateGUITimer, SIGNAL (timeout ()), + this, SLOT (slotRMBMoveUpdateGUI ())); +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::uninitMove () +{ + // (state must be after construction, or after some time after endMove()) + Q_ASSERT (!d->currentMoveCommand); + + // d->currentMoveCommandIsSmear + + // d->startMoveDragFromSelectionTopLeft + + // d->RMBMoveUpdateGUITimer (deleted by QObject mechanism) +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::beginMove () +{ + // (state must be after construction, or after some time after endMove()) + Q_ASSERT (!d->currentMoveCommand); + + // d->currentMoveCommandIsSmear + + // d->startMoveDragFromSelectionTopLeft; + + // d->RMBMoveUpdateGUITimer +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::endMove () +{ + // (should have been killed by cancelMove() or endDrawMove()) + Q_ASSERT (!d->currentMoveCommand); + + // d->currentMoveCommandIsSmear + + // d->startMoveDragFromSelectionTopLeft + + // d->RMBMoveUpdateGUITimer +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::setCursorMove () +{ + viewManager ()->setCursor (Qt::SizeAllCursor); +} + +//--------------------------------------------------------------------- + +// protected virtual +void kpAbstractSelectionTool::setSelectionBorderForBeginDrawMove () +{ + // don't show border while moving + viewManager ()->setQueueUpdates (); + { + viewManager ()->setSelectionBorderVisible (false); + viewManager ()->setSelectionBorderFinished (true); + } + viewManager ()->restoreQueueUpdates (); +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::beginDrawMove () +{ + d->startMoveDragFromSelectionTopLeft = + currentPoint () - document ()->selection ()->topLeft (); + + if (mouseButton () == 0) + { + /*virtual*/setSelectionBorderForBeginDrawMove (); + } + else + { + // Don't hide sel border momentarily if user is just + // right _clicking_ selection. + // (single shot timer) + d->RMBMoveUpdateGUITimer->start (100/*ms*/); + } + + setUserMessage (cancelUserMessage ()); +} + +//--------------------------------------------------------------------- + +// private slot +void kpAbstractSelectionTool::slotRMBMoveUpdateGUI () +{ + // (just in case not called from single shot) + d->RMBMoveUpdateGUITimer->stop (); + + /*virtual*/setSelectionBorderForBeginDrawMove (); + + kpAbstractSelection * const sel = document ()->selection (); + if (sel) + setUserShapePoints (sel->topLeft ()); +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::drawMove (const QPoint &thisPoint, const QRect &/*normalizedRect*/) +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "\tmoving selection"; +#endif + + kpAbstractSelection *sel = document ()->selection (); + + QRect targetSelRect (thisPoint.x () - d->startMoveDragFromSelectionTopLeft.x (), + thisPoint.y () - d->startMoveDragFromSelectionTopLeft.y (), + sel->width (), + sel->height ()); + +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "\t\tstartPoint=" << startPoint () + << " thisPoint=" << thisPoint + << " startDragFromSel=" << d->startMoveDragFromSelectionTopLeft + << " targetSelRect=" << targetSelRect + << endl; +#endif + + // Try to make sure selection still intersects document so that it's + // reachable. + + if (targetSelRect.right () < 0) + targetSelRect.translate (-targetSelRect.right (), 0); + else if (targetSelRect.left () >= document ()->width ()) + targetSelRect.translate (document ()->width () - targetSelRect.left () - 1, 0); + + if (targetSelRect.bottom () < 0) + targetSelRect.translate (0, -targetSelRect.bottom ()); + else if (targetSelRect.top () >= document ()->height ()) + targetSelRect.translate (0, document ()->height () - targetSelRect.top () - 1); + +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "\t\t\tafter ensure sel rect clickable=" << targetSelRect; +#endif + + + if (!d->dragAccepted && + targetSelRect.topLeft () + d->startMoveDragFromSelectionTopLeft == startPoint ()) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "\t\t\t\tnop"; + #endif + + + if (!d->RMBMoveUpdateGUITimer->isActive ()) + { + // (slotRMBMoveUpdateGUI() calls similar line) + setUserShapePoints (sel->topLeft ()); + } + + // Prevent both NOP drag-moves + return; + } + + + if (d->RMBMoveUpdateGUITimer->isActive ()) + { + d->RMBMoveUpdateGUITimer->stop (); + slotRMBMoveUpdateGUI (); + } + + + giveContentIfNeeded (); + + + if (!d->currentMoveCommand) + { + d->currentMoveCommand = new kpToolSelectionMoveCommand ( + QString()/*uninteresting child of macro cmd*/, + environ ()->commandEnvironment ()); + d->currentMoveCommandIsSmear = false; + } + + + //viewManager ()->setQueueUpdates (); + //viewManager ()->setFastUpdates (); + + if (shiftPressed ()) + d->currentMoveCommandIsSmear = true; + + if (!d->dragAccepted && (controlPressed () || shiftPressed ())) + d->currentMoveCommand->copyOntoDocument (); + + d->currentMoveCommand->moveTo (targetSelRect.topLeft ()); + + if (shiftPressed ()) + d->currentMoveCommand->copyOntoDocument (); + + //viewManager ()->restoreFastUpdates (); + //viewManager ()->restoreQueueUpdates (); + + // REFACTOR: yuck, yuck + kpAbstractSelection *orgSel = d->currentMoveCommand->originalSelectionClone (); + QPoint start = orgSel->topLeft (); + delete orgSel; + QPoint end = targetSelRect.topLeft (); + setUserShapePoints (start, end, false/*don't set size*/); + setUserShapeSize (end.x () - start.x (), end.y () - start.y ()); + + + d->dragAccepted = true; +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::cancelMove () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "\twas drag moving - undo drag and undo acquire"; +#endif + + d->RMBMoveUpdateGUITimer->stop (); + + // NOP drag? + if (!d->currentMoveCommand) + return; + +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "\t\tundo currentMoveCommand"; +#endif + d->currentMoveCommand->finalize (); + d->currentMoveCommand->unexecute (); + delete d->currentMoveCommand; + d->currentMoveCommand = 0; +} + +//--------------------------------------------------------------------- + +// protected virtual +QString kpAbstractSelectionTool::nonSmearMoveCommandName () const +{ + return i18n ("Selection: Move"); +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::endDrawMove () +{ + d->RMBMoveUpdateGUITimer->stop (); + + // NOP drag? + if (!d->currentMoveCommand) + return; + + d->currentMoveCommand->finalize (); + + kpMacroCommand *renamedCmd = 0; +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "\thave moveCommand"; +#endif + if (d->currentMoveCommandIsSmear) + { + renamedCmd = new kpMacroCommand (i18n ("%1: Smear", + document ()->selection ()->name ()), + environ ()->commandEnvironment ()); + } + else + { + renamedCmd = new kpMacroCommand ( + /*virtual*/nonSmearMoveCommandName (), + environ ()->commandEnvironment ()); + } + + renamedCmd->addCommand (d->currentMoveCommand); + d->currentMoveCommand = 0; + + addNeedingContentCommand (renamedCmd); +} + +//--------------------------------------------------------------------- + +// private +QVariant kpAbstractSelectionTool::operationMove (Operation op, + const QVariant &data1, const QVariant &data2) +{ + (void) data1; + (void) data2; + + + switch (op) + { + case HaventBegunDrawUserMessage: + return /*virtual*/haventBegunDrawUserMessageMove (); + + case SetCursor: + setCursorMove (); + break; + + case BeginDraw: + beginDrawMove (); + break; + + case Draw: + drawMove (currentPoint (), normalizedRect ()); + break; + + case Cancel: + cancelMove (); + break; + + case EndDraw: + endDrawMove (); + break; + + default: + Q_ASSERT (!"Unhandled operation"); + break; + } + + return QVariant (); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/tools/selection/kpAbstractSelectionTool_ResizeScale.cpp b/kolourpaint/tools/selection/kpAbstractSelectionTool_ResizeScale.cpp new file mode 100644 index 00000000..69e3f933 --- /dev/null +++ b/kolourpaint/tools/selection/kpAbstractSelectionTool_ResizeScale.cpp @@ -0,0 +1,454 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// private +int kpAbstractSelectionTool::onSelectionResizeHandle () const +{ + kpView *v = viewManager ()->viewUnderCursor (); + if (!v) + return 0; + + return v->mouseOnSelectionResizeHandle (currentViewPoint ()); +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::initResizeScale () +{ + d->currentResizeScaleCommand = 0; + + // d->resizeScaleType +} + +// private +void kpAbstractSelectionTool::uninitResizeScale () +{ + // (state must be after construction, or after some time after endResizeScale()) + Q_ASSERT (!d->currentResizeScaleCommand); + + // d->resizeScaleType +} + + +// private +void kpAbstractSelectionTool::beginResizeScale () +{ + // (state must be after construction, or after some time after endResizeScale()) + Q_ASSERT (!d->currentResizeScaleCommand); + + // d->resizeScaleType +} + +// private +void kpAbstractSelectionTool::endResizeScale () +{ + // (should have been killed by cancelResizeScale() or endResizeScale()) + Q_ASSERT (!d->currentResizeScaleCommand); + + // d->resizeScaleType +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::setCursorResizeScale () +{ +#if DEBUG_KP_TOOL_SELECTION && 0 + kDebug () << "\tonSelectionResizeHandle=" + << onSelectionResizeHandle () << endl; +#endif + Qt::CursorShape shape = Qt::ArrowCursor; + + switch (onSelectionResizeHandle ()) + { + case (kpView::Top | kpView::Left): + case (kpView::Bottom | kpView::Right): + shape = Qt::SizeFDiagCursor; + break; + + case (kpView::Bottom | kpView::Left): + case (kpView::Top | kpView::Right): + shape = Qt::SizeBDiagCursor; + break; + + case kpView::Top: + case kpView::Bottom: + shape = Qt::SizeVerCursor; + break; + + case kpView::Left: + case kpView::Right: + shape = Qt::SizeHorCursor; + break; + } + + viewManager ()->setCursor (shape); +} + +//--------------------------------------------------------------------- + +// protected virtual +void kpAbstractSelectionTool::setSelectionBorderForBeginDrawResizeScale () +{ + viewManager ()->setQueueUpdates (); + { + viewManager ()->setSelectionBorderVisible (true); + viewManager ()->setSelectionBorderFinished (true); + } + viewManager ()->restoreQueueUpdates (); +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::beginDrawResizeScale () +{ + d->resizeScaleType = onSelectionResizeHandle (); + + /*virtual*/setSelectionBorderForBeginDrawResizeScale (); + + setUserMessage (cancelUserMessage ()); +} + +//--------------------------------------------------------------------- + + +// private +void kpAbstractSelectionTool::drawResizeScaleTryKeepAspect ( + int newWidth, int newHeight, + bool horizontalGripDragged, bool verticalGripDragged, + const kpAbstractSelection &originalSelection, + int *newWidthOut, int *newHeightOut) +{ + const int oldWidth = originalSelection.width (), + oldHeight = originalSelection.height (); + + // Width changed more than height? At equality, favor width. + // Fix width, change height. + // + // We use and to prevent + // e.g. the situation where we've dragged such that newWidth < oldWidth but + // we're not dragging a vertical grip. We certainly don't want this + // code to modify the width - we want to fix the width and change the + // height. + if ((horizontalGripDragged ? double (newWidth) / oldWidth : 0) >= + (verticalGripDragged ? double (newHeight) / oldHeight : 0)) + { + *newHeightOut = newWidth * oldHeight / oldWidth; + *newHeightOut = qMax (originalSelection.minimumHeight (), *newHeightOut); + } + // Height changed more than width? + // Fix height, change width. + else + { + *newWidthOut = newHeight * oldWidth / oldHeight; + *newWidthOut = qMax (originalSelection.minimumWidth (), *newWidthOut); + } +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::drawResizeScaleCalculateNewSelectionPosSize ( + const kpAbstractSelection &originalSelection, + int *newX, int *newY, + int *newWidth, int *newHeight) +{ + // + // Determine new width. + // + + // Dragging left or right grip? + // If left, positive X drags decrease width. + // If right, positive X drags increase width. + int userXSign = 0; + if (d->resizeScaleType & kpView::Left) + userXSign = -1; + else if (d->resizeScaleType & kpView::Right) + userXSign = +1; + + // Calcluate new width. + *newWidth = originalSelection.width () + + userXSign * (currentPoint ().x () - startPoint ().x ()); + + // Don't allow new width to be less than that kind of selection type's + // minimum. + *newWidth = qMax (originalSelection.minimumWidth (), *newWidth); + + + // + // Determine new height. + // + + // Dragging top or bottom grip? + // If top, positive Y drags decrease height. + // If bottom, positive Y drags increase height. + int userYSign = 0; + if (d->resizeScaleType & kpView::Top) + userYSign = -1; + else if (d->resizeScaleType & kpView::Bottom) + userYSign = +1; + + // Calcluate new height. + *newHeight = originalSelection.height () + + userYSign * (currentPoint ().y () - startPoint ().y ()); + + // Don't allow new height to be less than that kind of selection type's + // minimum. + *newHeight = qMax (originalSelection.minimumHeight (), *newHeight); + + + // Keep aspect ratio? + if (shiftPressed ()) + { + drawResizeScaleTryKeepAspect (*newWidth, *newHeight, + (userXSign != 0)/*X or XY grip dragged*/, + (userYSign != 0)/*Y or XY grip dragged*/, + originalSelection, + newWidth/*ptr*/, newHeight/*ptr*/); + } + + + *newX = originalSelection.x (); + *newY = originalSelection.y (); + + + // + // Adjust x/y to new width/height for left/top resizes. + // + + if (d->resizeScaleType & kpView::Left) + { + *newX -= (*newWidth - originalSelection.width ()); + } + + if (d->resizeScaleType & kpView::Top) + { + *newY -= (*newHeight - originalSelection.height ()); + } + +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "\t\tnewX=" << *newX + << " newY=" << *newY + << " newWidth=" << *newWidth + << " newHeight=" << *newHeight + << endl; +#endif +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::drawResizeScale ( + const QPoint &thisPoint, + const QRect &/*normalizedRect*/) +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "\tresize/scale"; +#endif + + kpAbstractSelection *sel = document ()->selection (); + + if (!d->dragAccepted && thisPoint == startPoint ()) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kDebug () << "\t\tnop"; + #endif + + setUserShapePoints (QPoint (sel->width (), sel->height ())); + return; + } + + + giveContentIfNeeded (); + + + if (!d->currentResizeScaleCommand) + { + d->currentResizeScaleCommand + = new kpToolSelectionResizeScaleCommand (environ ()->commandEnvironment ()); + } + + + const kpAbstractSelection *originalSelection = + d->currentResizeScaleCommand->originalSelection (); + + + // There is nothing illegal about position (-1,-1) but why not. + int newX = -1, newY = -1, + newWidth = 0, newHeight = 0; + + // This should change all of the above values. + drawResizeScaleCalculateNewSelectionPosSize ( + *originalSelection, + &newX, &newY, + &newWidth, &newHeight); + + + viewManager ()->setFastUpdates (); + { + d->currentResizeScaleCommand->resizeAndMoveTo ( + newWidth, newHeight, + QPoint (newX, newY), + true/*smooth scale delayed*/); + } + viewManager ()->restoreFastUpdates (); + + setUserShapePoints (QPoint (originalSelection->width (), + originalSelection->height ()), + QPoint (newWidth, + newHeight), + false/*don't set size*/); + setUserShapeSize (newWidth - originalSelection->width (), + newHeight - originalSelection->height ()); + + + d->dragAccepted = true; +} + +//--------------------------------------------------------------------- + + +// private +void kpAbstractSelectionTool::cancelResizeScale () +{ +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "\twas resize/scale sel - kill"; +#endif + + // NOP drag? + if (!d->currentResizeScaleCommand) + return; + +#if DEBUG_KP_TOOL_SELECTION + kDebug () << "\t\tundo currentResizeScaleCommand"; +#endif + d->currentResizeScaleCommand->finalize (); // (unneeded but let's be safe) + d->currentResizeScaleCommand->unexecute (); + delete d->currentResizeScaleCommand; + d->currentResizeScaleCommand = 0; +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractSelectionTool::endDrawResizeScale () +{ + // NOP drag? + if (!d->currentResizeScaleCommand) + return; + + d->currentResizeScaleCommand->finalize (); + + addNeedingContentCommand (d->currentResizeScaleCommand); + d->currentResizeScaleCommand = 0; +} + +//--------------------------------------------------------------------- + +// private +QVariant kpAbstractSelectionTool::operationResizeScale (Operation op, + const QVariant &data1, const QVariant &data2) +{ + (void) data1; + (void) data2; + + + switch (op) + { + case HaventBegunDrawUserMessage: + return /*virtual*/haventBegunDrawUserMessageResizeScale (); + + case SetCursor: + setCursorResizeScale (); + break; + + case BeginDraw: + beginDrawResizeScale (); + break; + + case Draw: + drawResizeScale (currentPoint (), normalizedRect ()); + break; + + case Cancel: + cancelResizeScale (); + break; + + case EndDraw: + endDrawResizeScale (); + break; + + default: + Q_ASSERT (!"Unhandled operation"); + break; + } + + + return QVariant (); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/tools/selection/text/kpToolText.cpp b/kolourpaint/tools/selection/text/kpToolText.cpp new file mode 100644 index 00000000..95616671 --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText.cpp @@ -0,0 +1,221 @@ + +// REFACTOR: For all files involved in the class, refactor remaining bits and pieces and add APIDoc + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +kpToolText::kpToolText (kpToolSelectionEnvironment *environ, QObject *parent) + : kpAbstractSelectionTool (i18n ("Text"), i18n ("Writes text"), + Qt::Key_T, + environ, parent, "tool_text"), + d (new kpToolTextPrivate ()) +{ +} + +kpToolText::~kpToolText () +{ + delete d; +} + + +// protected virtual [kpAbstractSelectionTool] +kpAbstractSelectionContentCommand *kpToolText::newGiveContentCommand () const +{ + kpTextSelection *textSel = document ()->textSelection (); +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::newGiveContentCommand()" + << " textSel=" << textSel + << "; hasContent=" << textSel->hasContent () << endl; +#endif + Q_ASSERT (textSel && !textSel->hasContent ()); + + return new kpToolTextGiveContentCommand ( + *textSel, + QString()/*uninteresting child of macro cmd*/, + environ ()->commandEnvironment ()); +} + + +// protected virtual [kpAbstractSelectionTool] +QString kpToolText::nameOfCreateCommand () const +{ + return i18n ("Text: Create Box"); +} + + +// protected virtual [base kpAbstractSelectionTool] +void kpToolText::setSelectionBorderForHaventBegunDraw () +{ + viewManager ()->setQueueUpdates (); + { + kpAbstractSelectionTool::setSelectionBorderForHaventBegunDraw (); + viewManager ()->setTextCursorEnabled (true); + } + viewManager ()->restoreQueueUpdates (); +} + + +// public virtual [base kpAbstractSelectionTool] +void kpToolText::begin () +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kDebug () << "kpToolText::begin()"; +#endif + + environ ()->enableTextToolBarActions (true); + + // We don't actually need this since begin() already calls it via + // setSelectionBorderForHaventBegunDraw(). We leave this in for + // consistency with end(). + viewManager ()->setTextCursorEnabled (true); + viewManager()->setInputMethodEnabled (true); + + endTypingCommands (); + + kpAbstractSelectionTool::begin (); +} + +// public virtual [base kpAbstractSelectionTool] +void kpToolText::end () +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kDebug () << "kpToolText::end()"; +#endif + + kpAbstractSelectionTool::end (); + + viewManager()->setInputMethodEnabled (false); + viewManager ()->setTextCursorEnabled (false); + environ ()->enableTextToolBarActions (false); +} + + +// public +bool kpToolText::hasBegunText () const +{ + return (d->insertCommand || + d->enterCommand || + d->backspaceCommand || + d->backspaceWordCommand || + d->deleteCommand || + d->deleteWordCommand); +} + +// public virtual [base kpTool] +bool kpToolText::hasBegunShape () const +{ + return (hasBegunDraw () || hasBegunText ()); +} + + +// protected virtual [base kpAbstractSelectionTool] +kpAbstractSelectionTool::DrawType kpToolText::calculateDrawTypeInsideSelection () const +{ + if (onSelectionToSelectText () && !controlOrShiftPressed ()) + { + return kpAbstractSelectionTool::SelectText; + } + + return kpAbstractSelectionTool::calculateDrawTypeInsideSelection (); +} + + +// public virtual [base kpAbstractSelectionTool] +void kpToolText::cancelShape () +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::cancelShape()"; +#endif + + if (drawType () != None) + kpAbstractSelectionTool::cancelShape (); + else if (hasBegunText ()) + { + endTypingCommands (); + + commandHistory ()->undo (); + } + else + kpAbstractSelectionTool::cancelShape (); +} + + +// public virtual [base kpTool] +void kpToolText::endShape (const QPoint &thisPoint, const QRect &normalizedRect) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::endShape()"; +#endif + + if (drawType () != None) + kpAbstractSelectionTool::endDraw (thisPoint, normalizedRect); + else if (hasBegunText ()) + endTypingCommands (); + else + kpAbstractSelectionTool::endDraw (thisPoint, normalizedRect); +} + + +// protected virtual [base kpAbstractSelectionTool] +QVariant kpToolText::operation (DrawType drawType, Operation op, + const QVariant &data1, const QVariant &data2) +{ + if (drawType == SelectText) + return selectTextOperation (op, data1, data2); + else + return kpAbstractSelectionTool::operation (drawType, op, data1, data2); +} + + +#include diff --git a/kolourpaint/tools/selection/text/kpToolText.h b/kolourpaint/tools/selection/text/kpToolText.h new file mode 100644 index 00000000..6b4fa5f8 --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText.h @@ -0,0 +1,725 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_TEXT_H +#define KP_TOOL_TEXT_H + + +#include + + +class QKeyEvent; + +class kpColor; +class kpTextStyle; + +class kpToolTextInsertCommand; +class kpToolTextEnterCommand; +class kpToolTextBackspaceCommand; +class kpToolTextDeleteCommand; + + +// +// kpAbstractSelectionTool considers a drawing operation to be a mouse +// drag that creates, moves or resize/scales a selection. +// +// kpToolText also considers any such drawing operation and alternatively, +// any active text command (e.g. inserting text), to be the "current +// shape". kpTool's shape methods (e.g. hasBegunShape() and endShape()) +// have been overloaded to ensure that they operate on whatever operation +// (drawing or text) is active. +// +// It is not possible to have a drawing command and text command active +// simultaneously. However, it is possible to have neither active. +// +// Text commands do not end until a different kind of key is pressed or +// a drawing command commences. For instance, if you were to +// type a character of text, a kpToolTextInsertCommand would be added to +// the command history but stays active so that future typed characters +// would simply be added to this command. As soon as the user presses +// a different kind of key (e.g. arrow key, backspace) or drags the mouse, +// the command is no longer kept active. +// +// +// kpToolText implements a new drawing type, "SelectText", for moving the +// text cursor to the clicked location. +// +// +// As an exception to the standard kpAbstractSelectionTool behavior, +// a single click -- with no dragging -- can be used to create a new text +// box. +// +class kpToolText : public kpAbstractSelectionTool +{ +Q_OBJECT + +public: + kpToolText (kpToolSelectionEnvironment *environ, QObject *parent); + virtual ~kpToolText (); + + +// +// Text Command Handling +// + +private: + /** + * Indicates that no current text editing command is active. + * You must call this, via endShape(), when ending the current command + * (e.g. changing from backspacing to inserting text + * e.g.2. changing from moving/resizing to inserting text). + * + * It achieves this by zero'ing out all the d->.+Command pointers. + * It does not delete the pointers as they should be owned by the + * commandHistory(). + * + * If you call this excessively, you will break up commands into many + * smaller commands e.g. a text insertion command with 10 characters + * might be split into 10 text insertion commands, each containing 1 + * character. + */ + void endTypingCommands (); + + + /** + * Ends the current text editing command by eventually calling + * endTypingCommands(), returns a new + * kpToolTextBackspaceCommand and adds it to the commandHistory(). + * + * @param cmd A Pointer to one of the d->backspace.*Command pointers. + * On function return, the pointed-to d->backspace.*Command + * pointer will point to a new kpToolTextBackspaceCommand. + */ + void addNewBackspaceCommand (kpToolTextBackspaceCommand **cmd); + + /** + * Ends the current text editing command by eventually calling + * endTypingCommands(), returns a new + * kpToolTextDeleteCommand and adds it to the commandHistory(). + * + * @param cmd A Pointer to one of the d->delete.*Command pointers. On + * function return, the pointed-to d->delete.*Command pointer + * will point to a new kpToolTextDeleteCommand. + */ + void addNewDeleteCommand (kpToolTextDeleteCommand **cmd); + + void addNewEnterCommand (kpToolTextEnterCommand **cmd); + + void addNewInsertCommand (kpToolTextInsertCommand **cmd); + + +// +// Drawing +// + +protected: + virtual kpAbstractSelectionContentCommand *newGiveContentCommand () const; + + virtual QString nameOfCreateCommand () const; + + +protected: + virtual void setSelectionBorderForHaventBegunDraw (); + + +public: + virtual void begin (); + virtual void end (); + + +public: + bool hasBegunText () const; + virtual bool hasBegunShape () const; + + +// +// Drawing - Beginning a Drag +// + +protected: + virtual DrawType calculateDrawTypeInsideSelection () const; + + +public: + virtual void cancelShape (); + + +public: + virtual void endShape (const QPoint &thisPoint, const QRect &normalizedRect); + + +// +// Drawing - Operation Dispatching +// + +protected: + virtual QVariant operation (DrawType drawType, Operation op, + const QVariant &data1 = QVariant (), const QVariant &data2 = QVariant ()); + + +// +// Create +// + +protected: + virtual QString haventBegunDrawUserMessageCreate () const; + + +protected: + virtual void setSelectionBorderForBeginDrawCreate (); + + +private: + // Returns the suggested width/height of a click-created text box: + // + // = starting X/Y of the entire drag + // = current ending X/Y of the entire drag + // + // = the preferred minimum width/height of the selection + // = the legal minimum width/height of the selection + // + // = the width/height of the document + int calcClickCreateDimension (int mouseStart, int mouseEnd, + int preferredMin, int smallestMin, + int docSize); + bool shouldCreate ( + bool drawAcceptedAsDrag, + const QPoint &accidentalDragAdjustedPoint, + const kpTextStyle &textStyle, + int *minimumWidthOut, int *minimumHeightOut, + bool *newDragHasBegun); +protected: + virtual bool drawCreateMoreSelectionAndUpdateStatusBar ( + bool drawAcceptedAsDrag, + const QPoint &accidentalDragAdjustedPoint, + const QRect &normalizedRectIn); + + +// +// Move +// + +protected: + virtual QString haventBegunDrawUserMessageMove () const; + + +protected: + virtual void setSelectionBorderForBeginDrawMove (); + + +protected: + virtual QString nonSmearMoveCommandName () const; + + +// +// Resize/Scale +// + +protected: + virtual QString haventBegunDrawUserMessageResizeScale () const; + + +protected: + virtual void setSelectionBorderForBeginDrawResizeScale (); + + +// +// Select Text +// + +private: + bool onSelectionToSelectText () const; + + +private: + QString haventBegunDrawUserMessageSelectText () const; + + void setCursorSelectText (); + + +private: + void beginDrawSelectText (); + + +protected: + virtual QVariant selectTextOperation (Operation op, + const QVariant &data1 = QVariant (), const QVariant &data2 = QVariant ()); + + +// +// User Changing Text Style Elements +// + +protected: + bool shouldChangeTextStyle () const; + + /** + * Adds a kpToolTextChangeStyleCommand to commandHistory(). + * + * Call this when an element of the text style changes (e.g. user + * changes font size, boldness, color etc.). + * + * @param name Name of the command in the command history. + * @param newTextStyle The new and current text style. + * @param oldTextStyle The old and previous text style. + * + * You should only call this if shouldChangeTextStyle() returns true. + */ + void changeTextStyle (const QString &name, + const kpTextStyle &newTextStyle, + const kpTextStyle &oldTextStyle); + +protected slots: + virtual void slotIsOpaqueChanged (bool isOpaque); + + +protected: + /** + * Asks kpTool to call slotColorsSwapped() when the foreground and + * background color are swapped. + * + * Re-implemented from kpTool. + */ + virtual bool careAboutColorsSwapped () const { return true; } + +protected slots: + virtual void slotColorsSwapped (const kpColor &newForegroundColor, + const kpColor &newBackgroundColor); + + virtual void slotForegroundColorChanged (const kpColor &color); + virtual void slotBackgroundColorChanged (const kpColor &color); + virtual void slotColorSimilarityChanged (double, int); + +public slots: + void slotFontFamilyChanged (const QString &fontFamily, const QString &oldFontFamily); + void slotFontSizeChanged (int fontSize, int oldFontSize); + void slotBoldChanged (bool isBold); + void slotItalicChanged (bool isItalic); + void slotUnderlineChanged (bool isUnderline); + void slotStrikeThruChanged (bool isStrikeThru); + + +// +// Text Cursor Calculations (all static, no mutations) +// + +protected: + /** + * @param textLines One or more lines of text. + * @param cursorRow Current row of the cursor. + * @param cursorCol Current column of the cursor. + * + * @returns whether the cursor is currently on a word character + * (not a space). + */ + static bool CursorIsOnWordChar (const QList &textLines, + int cursorRow, int cursorCol); + + + /** + * @param textLines One or more lines of text. + * @param cursorRow Current row of the cursor. + * @param cursorCol Current column of the cursor. + * + * @returns whether the given cursor position is at the start of + * textLines (on the first character of the first line) + * i.e. when moveCursorLeft() won't do anything. + */ + static bool CursorIsAtStart (const QList &textLines, + int cursorRow, int cursorCol); + + /** + * @param textLines One or more lines of text. + * @param cursorRow Current row of the cursor. + * @param cursorCol Current column of the cursor. + * + * @returns whether the given cursor position is at the end of + * textLines (after the last character of the last line) + * i.e. when moveCursorRight() won't do anything. + */ + static bool CursorIsAtEnd (const QList &textLines, + int cursorRow, int cursorCol); + + + /** + * Moves the given cursor position one character to the left, if + * this is possible (i.e. if not cursorIsAtStart()). This may move the + * cursor one line up. + * + * @param textLines One or more lines of text. + * @param cursorRow Value-result parameter, initially containing + * the current row of the cursor and modified on + * return to indicate the new row. + * @param cursorCol Value-result parameter, initially containing + * the current column of the cursor and modified on + * return to indicate the new column. + */ + static void MoveCursorLeft (const QList &textLines, + int *cursorRow, int *cursorCol); + + /** + * Moves the given cursor position one character to the right, if + * this is possible (i.e. if not cursorIsAtEnd()). This may move the + * cursor one line down. + * + * @param textLines One or more lines of text. + * @param cursorRow Value-result parameter, initially containing + * the current row of the cursor and modified on + * return to indicate the new row. + * @param cursorCol Value-result parameter, initially containing + * the current column of the cursor and modified on + * return to indicate the new column. + */ + static void MoveCursorRight (const QList &textLines, + int *cursorRow, int *cursorCol); + + + /** + * Moves the row and column values, representing the current cursor + * location, to the start of the current word. If the cursor is + * on a space, it will move to the start of the previous word. + * + * This is normally used for a CTRL+Left or CTRL+Backspace behaviour. + * + * @param textLines One or more lines of text. + * @param cursorRow Value-result parameter, initially containing + * the current row of the cursor and modified on + * return to indicate the new row. + * @param cursorCol Value-result parameter, initially containing + * the current column of the cursor and modified on + * return to indicate the new column. + * + * @returns the number of times, it attempted to move left. + * Note: Attempting to moving left when cursorIsAtStart() + * may still be counted as a move. + */ + static int MoveCursorToWordStart (const QList &textLines, + int *cursorRow, int *cursorCol); + + /** + * Moves the row and column values, representing the current cursor + * location, to the start of the next word. This is regardless of + * whether the cursor starts on a space or not. + * + * This is normally used for a CTRL+Right or CTRL+Delete behaviour. + * + * @param textLines One or more lines of text. + * @param cursorRow Value-result parameter, initially containing + * the current row of the cursor and modified on + * return to indicate the new row. + * @param cursorCol Value-result parameter, initially containing + * the current column of the cursor and modified on + * return to indicate the new column. + * + * @returns the number of times, it attempted to move right. + * Note: Attempting to moving right when cursorIsAtEnd() + * may still be counted as a move. + */ + static int MoveCursorToNextWordStart (const QList &textLines, + int *cursorRow, int *cursorCol); + + +// +// Keyboard Events - Handling Arrow Keys +// +// These methods always: +// +// 1. Before doing anything, end the current shape (e.g. a text editing command or +// selection move command). This is done for 2 reasons: +// +// a) The user has interrupted the current command e.g. if I were to +// type some text, press an arrow key and then type text again, the two +// periods of text typing should be separate commands due to the arrow +// key interruption. +// +// b) To simplify the code by avoiding interference with the current command +// esp. since commands do not expect the cursor to move in the middle. +// +// 2. Accept the event as it is always intended for the method. This is even +// if the operation could not complete e.g. an attempt to move the cursor +// left when it is already at column 0. +// +protected: + /** + * Moves the text cursor up one character. Accepts the key event @p e. + * + * If there was an active text editing or selection command, it ends it first. + * + * @param e Mutable key event information. + * @param textLines One or more lines of text (convenience parameter). + * @param cursorRow Current row of the cursor (convenience parameter). + * @param cursorCol Current column of the cursor (convenience parameter). + * + * Called by keyPressEvent(). + */ + void handleUpKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol); + + /** + * Moves the text cursor down one character. Accepts the key event @p e. + * + * If there was an active text editing or selection command, it ends it first. + * + * @param e Mutable key event information. + * @param textLines One or more lines of text (convenience parameter). + * @param cursorRow Current row of the cursor (convenience parameter). + * @param cursorCol Current column of the cursor (convenience parameter). + * + * Called by keyPressEvent(). + */ + void handleDownKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol); + + /** + * Moves the text cursor left one character or if CTRL is held, one + * word. Accepts the key event @p e. + * + * If there was an active text editing or selection command, it ends it first. + * + * @param e Mutable key event information. + * @param textLines One or more lines of text (convenience parameter). + * @param cursorRow Current row of the cursor (convenience parameter). + * @param cursorCol Current column of the cursor (convenience parameter). + * + * Called by keyPressEvent(). + */ + void handleLeftKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol); + + /** + * Moves the text cursor right one character or if CTRL is held, one + * word. Accepts the key event @p e. + * + * If there was an active text editing or selection command, it ends it first. + * + * @param e Mutable key event information. + * @param textLines One or more lines of text (convenience parameter). + * @param cursorRow Current row of the cursor (convenience parameter). + * @param cursorCol Current column of the cursor (convenience parameter). + * + * Called by keyPressEvent(). + */ + void handleRightKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol); + + + /** + * Moves the text cursor to the start of the line and if CTRL is held, + * to the first line. Accepts the key event @p e. + * + * If there was an active text editing or selection command, it ends it first. + * + * @param e Mutable key event information. + * @param textLines One or more lines of text (convenience parameter). + * @param cursorRow Current row of the cursor (convenience parameter). + * @param cursorCol Current column of the cursor (convenience parameter). + * + * Called by keyPressEvent(). + */ + void handleHomeKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol); + + /** + * Moves the text cursor to after the last character of the current + * text line or if CTRL is held, after the last character of the last + * line. Accepts the key event @p e. + * + * If there was an active text editing or selection command, it ends it first. + * + * @param e Mutable key event information. + * @param textLines One or more lines of text (convenience parameter). + * @param cursorRow Current row of the cursor (convenience parameter). + * @param cursorCol Current column of the cursor (convenience parameter). + * + * Called by keyPressEvent(). + */ + void handleEndKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol); + + +// +// Keyboard Events - Handling Typing Keys +// +// For each method, if the respective event was actually intended for the +// method: +// +// 1. If the event will not be a NOP: +// +// If the current command is not the same as what this method would produce, +// it starts a new one, ending the current one (using the addNew*Command() +// methods). If the current command is the same, it simply extends the +// current command e.g. if the current command was a kpToolTextInsertCommand +// and the user typed another character of text, that character would just be +// added to that command. +// +// 2. Accept the event. This is even if the operation could not complete e.g. +// an attempt backspace when the cursor is at column 0. +// +// If the event was not intended for the method (e.g. pressing CTRL, Caps Lock +// or any other key that does not produce text, in handleTextTyped()), nothing +// happens. +// +protected: + /** + * Backspaces and if the active text editing command is not + * d->backspaceCommand, it calls addNewBackspaceCommand() on + * d->backspaceCommand first. + * + * If CTRL is held, it backspaces to the start of the active word + * and if the current text editing command was not + * d->backspaceWordCommand, it calls addNewBackspaceCommand() on + * d->backspaceWordCommand first. + * + * In this way, Backspace and CTRL+Backspace are separate entries + * in the commandHistory(). + * + * Accepts the key event @p e. + * + * @param e Mutable key event information. + * @param textLines One or more lines of text (convenience parameter). + * @param cursorRow Current row of the cursor (convenience parameter). + * @param cursorCol Current column of the cursor (convenience parameter). + * + * Called by keyPressEvent(). + */ + void handleBackspaceKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol); + + /** + * Deletes and if the active text editing command is not + * d->deleteCommand, it calls addNewDeleteCommand() on + * d->deleteCommand first. + * + * If CTRL is held, it deletes to the start of the next word + * and if the active text editing command was not + * d->deleteWordCommand, it calls addNewDeleteCommand() on + * d->deleteWordCommand first. + * + * In this way, Delete and CTRL+Delete are separate entries + * in the commandHistory(). + * + * Accepts the key event @p e. + * + * @param e Mutable key event information. + * @param textLines One or more lines of text (convenience parameter). + * @param cursorRow Current row of the cursor (convenience parameter). + * @param cursorCol Current column of the cursor (convenience parameter). + * + * Called by keyPressEvent(). + */ + void handleDeleteKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol); + + + /** + * Enters and if the active text editing command is not + * d->enterCommand, it ends the command, constructs d->enterCommand adding + * it to commandHistory(), first. + * + * Accepts the key event @p e. + * + * @param e Mutable key event information. + * @param textLines One or more lines of text (convenience parameter). + * @param cursorRow Current row of the cursor (convenience parameter). + * @param cursorCol Current column of the cursor (convenience parameter). + * + * Called by keyPressEvent(). + */ + void handleEnterKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol); + + + /** + * Inserts the printable characters of e->text() and accepts the key + * event @p e. If the active text editing command is not + * d->insertCommand, it ends the command, constructs d->insertCommand + * adding it to commandHistory(), first. + * + * If e->text() does not contain any printable characters, it does not + * do anything. As a result, it would not accept the key event @e. + * This printability restriction prevents control characters from being + * typed when they should be trapped by a keyPressEvent() that is lower + * in the call chain (for e.g. CTRL+Z for Undo). + * + * @param e Mutable key event information. + * @param textLines One or more lines of text (convenience parameter). + * @param cursorRow Current row of the cursor (convenience parameter). + * @param cursorCol Current column of the cursor (convenience parameter). + * + * Called by keyPressEvent(). + */ + void handleTextTyped (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol); + + +// +// Keyboard Events +// + +protected: + // Prevents actions with single letter/number shortcuts from eating + // keystrokes while a text selection is active. This is important + // because the Tool Box actions default to single letter/number + // shortcuts. + virtual bool viewEvent (QEvent *e); + + /** + * Handles key press events. + * + * If the user is currently drawing/resizing something or if the + * document doesn't have a text selection, it passes control to the + * otherwise overridden kpAbstractSelectionTool::keyPressEvent(). + * + * Else, for a recognised key it calls handle.*Press(). If a + * recognised key was not pressed, it assumes that one or more text + * characters was typed, and calls handleTextTyped(). If none of the + * handle.*() methods call e->accept(), it passes control to the + * otherwise overridden kpAbstractSelectionTool::keyPressEvent(). + * + * @param e Mutable key event information. + * + * Re-implemented from kpAbstractSelectionTool. + */ + + virtual void keyPressEvent (QKeyEvent *e); + + +// +// Input Method Text Entry +// + +protected: + virtual void inputMethodEvent (QInputMethodEvent *e); + + +private: + struct kpToolTextPrivate * const d; +}; + + +#endif // KP_TOOL_TEXT_H diff --git a/kolourpaint/tools/selection/text/kpToolTextPrivate.h b/kolourpaint/tools/selection/text/kpToolTextPrivate.h new file mode 100644 index 00000000..4a74e253 --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolTextPrivate.h @@ -0,0 +1,51 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolTextPrivate_H +#define kpToolTextPrivate_H + + +#include + + +class kpToolTextInsertCommand; +class kpToolTextEnterCommand; +class kpToolTextBackspaceCommand; +class kpToolTextDeleteCommand; + + +struct kpToolTextPrivate +{ + kpToolTextInsertCommand *insertCommand; + kpToolTextEnterCommand *enterCommand; + kpToolTextBackspaceCommand *backspaceCommand, *backspaceWordCommand; + kpToolTextDeleteCommand *deleteCommand, *deleteWordCommand; +}; + + +#endif // kpToolTextPrivate_H diff --git a/kolourpaint/tools/selection/text/kpToolText_Commands.cpp b/kolourpaint/tools/selection/text/kpToolText_Commands.cpp new file mode 100644 index 00000000..d87ab573 --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText_Commands.cpp @@ -0,0 +1,126 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + + +// private +void kpToolText::endTypingCommands () +{ + d->insertCommand = 0; + d->enterCommand = 0; + + d->backspaceCommand = 0; + d->backspaceWordCommand = 0; + + d->deleteCommand = 0; + d->deleteWordCommand = 0; +} + + +// private +void kpToolText::addNewBackspaceCommand (kpToolTextBackspaceCommand **cmd) +{ + // TODO: why not endShapeInternal(); ditto for everywhere else in kpToolText*.cpp? + if (hasBegunShape ()) + { + endShape (currentPoint (), normalizedRect ()); + } + + giveContentIfNeeded (); + + *cmd = new kpToolTextBackspaceCommand (i18n ("Text: Backspace"), + viewManager ()->textCursorRow (), viewManager ()->textCursorCol (), + kpToolTextBackspaceCommand::DontAddBackspaceYet, + environ ()->commandEnvironment ()); + addNeedingContentCommand (*cmd); +} + +// private +void kpToolText::addNewDeleteCommand (kpToolTextDeleteCommand **cmd) +{ + if (hasBegunShape ()) + { + endShape (currentPoint (), normalizedRect ()); + } + + giveContentIfNeeded (); + + *cmd = new kpToolTextDeleteCommand (i18n ("Text: Delete"), + viewManager ()->textCursorRow (), viewManager ()->textCursorCol (), + kpToolTextDeleteCommand::DontAddDeleteYet, + environ ()->commandEnvironment ()); + addNeedingContentCommand (*cmd); +} + +// private +void kpToolText::addNewEnterCommand (kpToolTextEnterCommand **cmd) +{ + if (hasBegunShape ()) + { + endShape (currentPoint (), normalizedRect ()); + } + + giveContentIfNeeded (); + + *cmd = new kpToolTextEnterCommand (i18n ("Text: New Line"), + viewManager ()->textCursorRow (), viewManager ()->textCursorCol (), + kpToolTextEnterCommand::DontAddEnterYet, + environ ()->commandEnvironment ()); + addNeedingContentCommand (*cmd); +} + +// private +void kpToolText::addNewInsertCommand (kpToolTextInsertCommand **cmd) +{ + if (hasBegunShape ()) + { + endShape (currentPoint (), normalizedRect ()); + } + + giveContentIfNeeded (); + + *cmd = new kpToolTextInsertCommand (i18n ("Text: Write"), + viewManager ()->textCursorRow (), viewManager ()->textCursorCol (), + QString (), + environ ()->commandEnvironment ()); + addNeedingContentCommand (*cmd); +} diff --git a/kolourpaint/tools/selection/text/kpToolText_Create.cpp b/kolourpaint/tools/selection/text/kpToolText_Create.cpp new file mode 100644 index 00000000..c2c85681 --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText_Create.cpp @@ -0,0 +1,288 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + + +// protected virtual [kpAbstractSelectionTool] +QString kpToolText::haventBegunDrawUserMessageCreate () const +{ + return i18n ("Left drag to create text box."); +} + + +// protected virtual [base kpAbstractSelectionTool] +void kpToolText::setSelectionBorderForBeginDrawCreate () +{ + viewManager ()->setQueueUpdates (); + { + kpAbstractSelectionTool::setSelectionBorderForBeginDrawCreate (); + viewManager ()->setTextCursorEnabled (false); + } + viewManager ()->restoreQueueUpdates (); +} + + +// private +int kpToolText::calcClickCreateDimension (int mouseStart, int mouseEnd, + int preferredMin, int smallestMin, + int docSize) +{ + Q_ASSERT (preferredMin >= smallestMin); + Q_ASSERT (docSize > 0); + + // Get reasonable width/height for a text box. + int ret = preferredMin; + + // X or Y increasing? + if (mouseEnd >= mouseStart) + { + // Text box extends past document width/height? + if (mouseStart + ret - 1 >= docSize) + { + // Cap width/height to not extend past but not below smallest + // possible selection width/height + ret = qMax (smallestMin, docSize - mouseStart); + } + } + // X or Y decreasing + else + { + // Text box extends past document start? + // TODO: I doubt this code can be invoked for a click. + // Maybe very tricky interplay with accidental drag detection? + if (mouseStart - ret + 1 < 0) + { + // Cap width/height to not extend past but not below smallest + // possible selection width/height. + ret = qMax (smallestMin, mouseStart + 1); + } + } + + return ret; +} + +// private +bool kpToolText::shouldCreate (bool dragAccepted, + const QPoint &accidentalDragAdjustedPoint, + const kpTextStyle &textStyle, + int *minimumWidthOut, int *minimumHeightOut, + bool *newDragAccepted) +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kDebug () << "CALL(dragAccepted=" << dragAccepted + << ",accidentalDragAdjustedPoint=" << accidentalDragAdjustedPoint + << ")"; +#endif + *newDragAccepted = dragAccepted; + + // Is the drag so short that we're essentially just clicking? + // Basically, we're trying to prevent unintentional creation of 1-pixel + // selections. + if (!dragAccepted && accidentalDragAdjustedPoint == startPoint ()) + { + // We had an existing text box before the click? + if (hadSelectionBeforeDraw ()) + { + #if DEBUG_KP_TOOL_TEXT && 1 + kDebug () << "\ttext box deselect - NOP - return"; + #endif + // We must be attempting to deselect the text box. + // This deselection has already been done by kpAbstractSelectionTool::beginDraw(). + // Therefore, we are not doing a drag. + return false; + } + // We are probably creating a new box. + else + { + // This drag is currently a click -- not a drag. + // As a special case, allow user to create a text box, + // of reasonable ("preferred minimum") size, using a single + // click. + // + // If the user drags further, the normal drag-to-create-a-textbox + // branch [x] will execute and the size will be determined based on + // the size of the drag instead. + + #if DEBUG_KP_TOOL_TEXT && 1 + kDebug () << "\tclick creating text box"; + #endif + + // (Click creating text box with RMB would not be obvious + // since RMB menu most likely hides text box immediately + // afterwards) + // TODO: I suspect this logic is simply too late + // TODO: We setUserShapePoints() on return but didn't before. + if (mouseButton () == 1) + return false/*do not create text box*/; + + + // Calculate suggested width. + *minimumWidthOut = calcClickCreateDimension ( + startPoint ().x (), + accidentalDragAdjustedPoint.x (), + kpTextSelection::PreferredMinimumWidthForTextStyle (textStyle), + kpTextSelection::MinimumWidthForTextStyle (textStyle), + document ()->width ()); + + // Calculate suggested height. + *minimumHeightOut = calcClickCreateDimension ( + startPoint ().y (), + accidentalDragAdjustedPoint.y (), + kpTextSelection::PreferredMinimumHeightForTextStyle (textStyle), + kpTextSelection::MinimumHeightForTextStyle (textStyle), + document ()->height ()); + + + // Do _not_ set "newDragAccepted" to true as we want + // this text box to remain at the click-given size, in the absence + // of any dragging. In other words, if draw() is called again + // and therefore, we are called again, but the mouse has not + // moved, we do want this branch to execute again, not + // Branch [x]. + return true/*do create text box*/; + } + } + // Dragging to create a text box [x]. + // + // The size will be determined based on the size of the drag. + else + { + #if DEBUG_KP_TOOL_TEXT && 1 + kDebug () << "\tdrag creating text box"; + #endif + *minimumWidthOut = kpTextSelection::MinimumWidthForTextStyle (textStyle); + *minimumHeightOut = kpTextSelection::MinimumHeightForTextStyle (textStyle); + + *newDragAccepted = true; + return true/*do create text box*/; + } + +} + +// protected virtual [kpAbstractSelectionTool] +bool kpToolText::drawCreateMoreSelectionAndUpdateStatusBar ( + bool dragAccepted, + const QPoint &accidentalDragAdjustedPoint, + const QRect &normalizedRectIn) +{ + // (will mutate this) + QRect normalizedRect = normalizedRectIn; + + const kpTextStyle textStyle = environ ()->textStyle (); + + + // + // Calculate Text Box Rectangle. + // + + bool newDragAccepted = dragAccepted; + + // (will set both variables) + int minimumWidth = 0, minimumHeight = 0; + if (!shouldCreate (dragAccepted, accidentalDragAdjustedPoint, textStyle, + &minimumWidth, &minimumHeight, &newDragAccepted)) + { + setUserShapePoints (accidentalDragAdjustedPoint); + return newDragAccepted; + } + + + // Make sure the dragged out rectangle is of the minimum width we just + // calculated. + if (normalizedRect.width () < minimumWidth) + { + if (accidentalDragAdjustedPoint.x () >= startPoint ().x ()) + normalizedRect.setWidth (minimumWidth); + else + normalizedRect.setX (normalizedRect.right () - minimumWidth + 1); + } + + // Make sure the dragged out rectangle is of the minimum height we just + // calculated. + if (normalizedRect.height () < minimumHeight) + { + if (accidentalDragAdjustedPoint.y () >= startPoint ().y ()) + normalizedRect.setHeight (minimumHeight); + else + normalizedRect.setY (normalizedRect.bottom () - minimumHeight + 1); + } + +#if DEBUG_KP_TOOL_TEXT && 1 + kDebug () << "\t\tnormalizedRect=" << normalizedRect + << " kpTextSelection::preferredMinimumSize=" + << QSize (minimumWidth, minimumHeight) + << endl; +#endif + + + // + // Construct and Deploy Text Box. + // + + // Create empty text box. + QList textLines; + kpTextSelection textSel (normalizedRect, textLines, textStyle); + + // Render. + viewManager ()->setTextCursorPosition (0, 0); + document ()->setSelection (textSel); + + + // + // Update Status Bar. + // + + QPoint actualEndPoint = KP_INVALID_POINT; + if (startPoint () == normalizedRect.topLeft ()) + actualEndPoint = normalizedRect.bottomRight (); + else if (startPoint () == normalizedRect.bottomRight ()) + actualEndPoint = normalizedRect.topLeft (); + else if (startPoint () == normalizedRect.topRight ()) + actualEndPoint = normalizedRect.bottomLeft (); + else if (startPoint () == normalizedRect.bottomLeft ()) + actualEndPoint = normalizedRect.topRight (); + + setUserShapePoints (startPoint (), actualEndPoint); + + return newDragAccepted; +} + diff --git a/kolourpaint/tools/selection/text/kpToolText_CursorCalc.cpp b/kolourpaint/tools/selection/text/kpToolText_CursorCalc.cpp new file mode 100644 index 00000000..aa636369 --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText_CursorCalc.cpp @@ -0,0 +1,215 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// protected static +bool kpToolText::CursorIsOnWordChar (const QList &textLines, + int cursorRow, int cursorCol) +{ + return (cursorRow >= 0 && cursorRow < (int) textLines.size () && + cursorCol >= 0 && cursorCol < (int) textLines [cursorRow].length () && + !textLines [cursorRow][cursorCol].isSpace ()); +} + + +// protected static +bool kpToolText::CursorIsAtStart (const QList &, + int cursorRow, int cursorCol) +{ + return (cursorRow == 0 && cursorCol == 0); +} + +// protected static +bool kpToolText::CursorIsAtEnd (const QList &textLines, + int cursorRow, int cursorCol) +{ + if (textLines.isEmpty ()) + return (cursorRow == 0 && cursorCol == 0); + + return (cursorRow == (int) textLines.size () - 1 && + cursorCol == (int) textLines [cursorRow].length ()); +} + + +// protected static +void kpToolText::MoveCursorLeft (const QList &textLines, + int *cursorRow, int *cursorCol) +{ + if (textLines.isEmpty ()) + return; + + (*cursorCol)--; + + if (*cursorCol < 0) + { + (*cursorRow)--; + if (*cursorRow < 0) + { + *cursorRow = 0; + *cursorCol = 0; + } + else + *cursorCol = textLines [*cursorRow].length (); + } +} + +// protected static +void kpToolText::MoveCursorRight (const QList &textLines, + int *cursorRow, int *cursorCol) +{ + if (textLines.isEmpty ()) + return; + + (*cursorCol)++; + + if (*cursorCol > (int) textLines [*cursorRow].length ()) + { + (*cursorRow)++; + if (*cursorRow > (int) textLines.size () - 1) + { + *cursorRow = textLines.size () - 1; + *cursorCol = textLines [*cursorRow].length (); + } + else + *cursorCol = 0; + } +} + + +#define IS_ON_SPACE_OR_EOL() !CursorIsOnWordChar (textLines, *cursorRow, *cursorCol) + +// protected static +int kpToolText::MoveCursorToWordStart (const QList &textLines, + int *cursorRow, int *cursorCol) +{ + if (textLines.isEmpty ()) + return 0; + + int numMoves = 0; + +#define IS_ON_ANCHOR() \ + (CursorIsOnWordChar (textLines, *cursorRow, *cursorCol) && \ + (cursorCol == 0 || \ + !CursorIsOnWordChar (textLines, *cursorRow, *cursorCol - 1))) +#define MOVE_CURSOR_LEFT() \ + (MoveCursorLeft (textLines, cursorRow, cursorCol), ++numMoves) + + + // (these comments will exclude the row=0,col=0 boundary case) + + if (IS_ON_ANCHOR ()) + MOVE_CURSOR_LEFT (); + + // --- now we're not on an anchor point (start of word) --- + + // End up on a letter... + while (!(*cursorRow == 0 && *cursorCol == 0) && + (IS_ON_SPACE_OR_EOL ())) + { + MOVE_CURSOR_LEFT (); + } + + // --- now we're on a letter --- + + // Find anchor point + while (!(*cursorRow == 0 && *cursorCol == 0) && !IS_ON_ANCHOR ()) + { + MOVE_CURSOR_LEFT (); + } + + +#undef IS_ON_ANCHOR +#undef MOVE_CURSOR_LEFT + + return numMoves; +} + +// protected static +int kpToolText::MoveCursorToNextWordStart (const QList &textLines, + int *cursorRow, int *cursorCol) +{ + if (textLines.isEmpty ()) + return 0; + + int numMoves = 0; + +#define IS_AT_END() CursorIsAtEnd (textLines, *cursorRow, *cursorCol) +#define MOVE_CURSOR_RIGHT() \ + (MoveCursorRight (textLines, cursorRow, cursorCol), ++numMoves) + + + // (these comments will exclude the last row,end col boundary case) + + // Find space + while (!IS_AT_END () && !IS_ON_SPACE_OR_EOL ()) + { + MOVE_CURSOR_RIGHT (); + } + + // --- now we're on a space --- + + // Find letter + while (!IS_AT_END () && IS_ON_SPACE_OR_EOL ()) + { + MOVE_CURSOR_RIGHT (); + } + + // --- now we're on a letter --- + + +#undef IS_AT_END +#undef MOVE_CURSOR_RIGHT + + return numMoves; +} + +#undef IS_ON_SPACE_OR_EOL + diff --git a/kolourpaint/tools/selection/text/kpToolText_InputMethodEvents.cpp b/kolourpaint/tools/selection/text/kpToolText_InputMethodEvents.cpp new file mode 100644 index 00000000..d7f8b443 --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText_InputMethodEvents.cpp @@ -0,0 +1,96 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2005 Kazuki Ohta + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +void kpToolText::inputMethodEvent (QInputMethodEvent *e) +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kDebug () << "kpToolText::inputMethodEvent() preeditString='" << e->preeditString () + << "commitString = " << e->commitString () + << " replacementStart=" << e->replacementStart () + << " replacementLength=" << e->replacementLength () + << endl; +#endif + kpTextSelection *textSel = document ()->textSelection (); + if (hasBegunDraw() || !textSel) + { + e->ignore(); + return; + } + + kpPreeditText previous = textSel->preeditText (); + kpPreeditText next (e); + + int textCursorRow = viewManager ()->textCursorRow (); + int textCursorCol = viewManager ()->textCursorCol (); + if (!next.isEmpty ()) + { + if (previous.position().x () < 0 && previous.position().y () < 0) + { + next.setPosition (QPoint(textCursorCol, textCursorRow)); + } + else + { + next.setPosition(previous.position ()); + } + } + textSel->setPreeditText (next); + textCursorCol = textCursorCol - previous.cursorPosition () + next.cursorPosition (); + viewManager ()->setTextCursorPosition (textCursorRow, textCursorCol); + + QString commitString = e->commitString (); + if (!commitString.isEmpty ()) + { + // commit string + if (!d->insertCommand) + addNewInsertCommand (&d->insertCommand); + + d->insertCommand->addText (commitString); + } + e->accept (); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/tools/selection/text/kpToolText_KeyboardEvents.cpp b/kolourpaint/tools/selection/text/kpToolText_KeyboardEvents.cpp new file mode 100644 index 00000000..02eb8834 --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText_KeyboardEvents.cpp @@ -0,0 +1,215 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// protected virtual [base kpTool] +bool kpToolText::viewEvent (QEvent *e) +{ + const bool isShortcutOverrideEvent = + (e->type () == QEvent::ShortcutOverride); + const bool haveTextSelection = document ()->textSelection (); + +#if DEBUG_KP_TOOL_TEXT && 0 + kDebug () << "kpToolText::viewEvent() type=" << e->type () + << " isShortcutOverrideEvent=" << isShortcutOverrideEvent + << " haveTextSel=" << haveTextSelection + << endl; +#endif + + if (!isShortcutOverrideEvent || !haveTextSelection) + return kpAbstractSelectionTool::viewEvent (e); + + QKeyEvent *ke = static_cast (e); +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::viewEvent() key=" << ke->key () + << " modifiers=" << ke->modifiers () + << " QChar.isPrint()=" << QChar (ke->key ()).isPrint () + << endl; +#endif + + // Can't be shortcut? + if (ke->key () == 0 && ke->key () == Qt::Key_unknown) + { + #if DEBUG_KP_TOOL_TEXT + kDebug () << "\tcan't be shortcut - safe to not react"; + #endif + } + // Normal letter (w/ or w/o shift, keypad button ok)? + // TODO: don't like this check + else if ((ke->modifiers () & + (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)) == 0 && + !( ke->text ().isEmpty ())) + { + #if DEBUG_KP_TOOL_TEXT + kDebug () << "\tis text - grab"; + #endif + e->accept (); + } + else + { + // Strickly speaking, we should grab stuff like the arrow keys + // and enter. In any case, should be done down in kpTool (as that + // uses arrow keys too). + } + + return kpAbstractSelectionTool::event (e); +} + + +// protected virtual [base kpAbstractSelectionTool] +void kpToolText::keyPressEvent (QKeyEvent *e) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::keyPressEvent(e->text='" << e->text () << "')"; +#endif + + + e->ignore (); + + + if (hasBegunDraw ()) + { + #if DEBUG_KP_TOOL_TEXT + kDebug () << "\talready began draw with mouse - passing on event to kpTool"; + #endif + kpAbstractSelectionTool::keyPressEvent (e); + return; + } + + + kpTextSelection * const textSel = document ()->textSelection (); + + if (!textSel) + { + #if DEBUG_KP_TOOL_TEXT + kDebug () << "\tno text sel - passing on event to kpTool"; + #endif + //if (hasBegunShape ()) + // endShape (currentPoint (), normalizedRect ()); + + kpAbstractSelectionTool::keyPressEvent (e); + return; + } + + + // (All handle.+()'s require this info) + const QList textLines = textSel->textLines (); + const int cursorRow = viewManager ()->textCursorRow (); + const int cursorCol = viewManager ()->textCursorCol (); + + + // TODO: KTextEdit::keyPressEvent() uses KStandardShortcut instead of hardcoding; same fix for kpTool? + switch (e->key ()) + { + case Qt::Key_Up: + handleUpKeyPress (e, textLines, cursorRow, cursorCol); + break; + + case Qt::Key_Down: + handleDownKeyPress (e, textLines, cursorRow, cursorCol); + break; + + case Qt::Key_Left: + handleLeftKeyPress (e, textLines, cursorRow, cursorCol); + break; + + case Qt::Key_Right: + handleRightKeyPress (e, textLines, cursorRow, cursorCol); + break; + + + case Qt::Key_Home: + handleHomeKeyPress (e, textLines, cursorRow, cursorCol); + break; + + case Qt::Key_End: + handleEndKeyPress (e, textLines, cursorRow, cursorCol); + break; + + + case Qt::Key_Backspace: + handleBackspaceKeyPress (e, textLines, cursorRow, cursorCol); + break; + + case Qt::Key_Delete: + handleDeleteKeyPress (e, textLines, cursorRow, cursorCol); + break; + + + case Qt::Key_Enter: + case Qt::Key_Return: + handleEnterKeyPress (e, textLines, cursorRow, cursorCol); + break; + + + default: + handleTextTyped (e, textLines, cursorRow, cursorCol); + break; + } + + + if (!e->isAccepted ()) + { + #if DEBUG_KP_TOOL_TEXT + kDebug () << "\tkey processing did not accept (text was '" + << e->text () + << "') - passing on event to kpAbstractSelectionTool" + << endl; + #endif + //if (hasBegunShape ()) + // endShape (currentPoint (), normalizedRect ()); + + kpAbstractSelectionTool::keyPressEvent (e); + return; + } +} diff --git a/kolourpaint/tools/selection/text/kpToolText_KeyboardEvents_HandleArrowKeys.cpp b/kolourpaint/tools/selection/text/kpToolText_KeyboardEvents_HandleArrowKeys.cpp new file mode 100644 index 00000000..e989ff68 --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText_KeyboardEvents_HandleArrowKeys.cpp @@ -0,0 +1,216 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// protected +void kpToolText::handleUpKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\tup pressed"; +#endif + + if (hasBegunShape ()) + endShape (currentPoint (), normalizedRect ()); + + if (!textLines.isEmpty () && cursorRow > 0) + { + cursorRow--; + cursorCol = qMin (cursorCol, (int) textLines [cursorRow].length ()); + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + + e->accept (); +} + +// protected +void kpToolText::handleDownKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\tdown pressed"; +#endif + + if (hasBegunShape ()) + endShape (currentPoint (), normalizedRect ()); + + if (!textLines.isEmpty () && cursorRow < (int) textLines.size () - 1) + { + cursorRow++; + cursorCol = qMin (cursorCol, (int) textLines [cursorRow].length ()); + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + + e->accept (); +} + +// protected +void kpToolText::handleLeftKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\tleft pressed"; +#endif + + if (hasBegunShape ()) + endShape (currentPoint (), normalizedRect ()); + + if (!textLines.isEmpty ()) + { + if ((e->modifiers () & Qt::ControlModifier) == 0) + { + #if DEBUG_KP_TOOL_TEXT + kDebug () << "\tmove single char"; + #endif + + MoveCursorLeft (textLines, &cursorRow, &cursorCol); + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + else + { + #if DEBUG_KP_TOOL_TEXT + kDebug () << "\tmove to start of word"; + #endif + + MoveCursorToWordStart (textLines, &cursorRow, &cursorCol); + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + } + + e->accept (); +} + +// protected +void kpToolText::handleRightKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\tright pressed"; +#endif + + if (hasBegunShape ()) + endShape (currentPoint (), normalizedRect ()); + + if (!textLines.isEmpty ()) + { + if ((e->modifiers () & Qt::ControlModifier) == 0) + { + #if DEBUG_KP_TOOL_TEXT + kDebug () << "\tmove single char"; + #endif + + MoveCursorRight (textLines, &cursorRow, &cursorCol); + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + else + { + #if DEBUG_KP_TOOL_TEXT + kDebug () << "\tmove to start of next word"; + #endif + + MoveCursorToNextWordStart (textLines, &cursorRow, &cursorCol); + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + } + + e->accept (); +} + + +// protected +void kpToolText::handleHomeKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\thome pressed"; +#endif + + if (hasBegunShape ()) + endShape (currentPoint (), normalizedRect ()); + + if (!textLines.isEmpty ()) + { + if (e->modifiers () & Qt::ControlModifier) + cursorRow = 0; + + cursorCol = 0; + + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + + e->accept (); +} + +// protected +void kpToolText::handleEndKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\tend pressed"; +#endif + + if (hasBegunShape ()) + endShape (currentPoint (), normalizedRect ()); + + if (!textLines.isEmpty ()) + { + if (e->modifiers () & Qt::ControlModifier) + cursorRow = textLines.size () - 1; + + cursorCol = textLines [cursorRow].length (); + + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + + e->accept (); +} diff --git a/kolourpaint/tools/selection/text/kpToolText_KeyboardEvents_HandleTypingKeys.cpp b/kolourpaint/tools/selection/text/kpToolText_KeyboardEvents_HandleTypingKeys.cpp new file mode 100644 index 00000000..95ca2565 --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText_KeyboardEvents_HandleTypingKeys.cpp @@ -0,0 +1,198 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +//--------------------------------------------------------------------- + +// protected +void kpToolText::handleBackspaceKeyPress (QKeyEvent *e, + const QList &textLines, int cursorRow, int cursorCol) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\tbackspace pressed"; +#endif + + if (!textLines.isEmpty ()) + { + if ((e->modifiers () & Qt::ControlModifier) == 0) + { + if (!d->backspaceCommand) + addNewBackspaceCommand (&d->backspaceCommand); + + d->backspaceCommand->addBackspace (); + } + else + { + if (!d->backspaceWordCommand) + addNewBackspaceCommand (&d->backspaceWordCommand); + + const int numMoves = MoveCursorToWordStart (textLines, + &cursorRow, &cursorCol); + + viewManager ()->setQueueUpdates (); + { + for (int i = 0; i < numMoves; i++) + d->backspaceWordCommand->addBackspace (); + } + viewManager ()->restoreQueueUpdates (); + + Q_ASSERT (cursorRow == viewManager ()->textCursorRow ()); + Q_ASSERT (cursorCol == viewManager ()->textCursorCol ()); + } + } + + e->accept (); +} + +//--------------------------------------------------------------------- + +// protected +void kpToolText::handleDeleteKeyPress (QKeyEvent *e, + const QList & textLines, int cursorRow, int cursorCol) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\tdelete pressed"; +#endif + + if (!textLines.isEmpty ()) + { + if ((e->modifiers () & Qt::ControlModifier) == 0) + { + if (!d->deleteCommand) + addNewDeleteCommand (&d->deleteCommand); + + d->deleteCommand->addDelete (); + } + else + { + if (!d->deleteWordCommand) + addNewDeleteCommand (&d->deleteWordCommand); + + // We don't want to know the cursor pos of the next word start + // as delete should keep cursor in same pos. + int cursorRowThrowAway = cursorRow, + cursorColThrowAway = cursorCol; + const int numMoves = MoveCursorToNextWordStart (textLines, + &cursorRowThrowAway, &cursorColThrowAway); + + viewManager ()->setQueueUpdates (); + { + for (int i = 0; i < numMoves; i++) + d->deleteWordCommand->addDelete (); + } + viewManager ()->restoreQueueUpdates (); + + // Assert unchanged as delete should keep cursor in same pos. + Q_ASSERT (cursorRow == viewManager ()->textCursorRow ()); + Q_ASSERT (cursorCol == viewManager ()->textCursorCol ()); + } + } + + e->accept (); +} + +//--------------------------------------------------------------------- + +// protected +void kpToolText::handleEnterKeyPress (QKeyEvent *e, + const QList & /*textLines*/, int /*cursorRow*/, int /*cursorCol*/) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\tenter pressed"; +#endif + + // It's OK for to be empty. + + if (!d->enterCommand) + addNewEnterCommand (&d->enterCommand); + + d->enterCommand->addEnter (); + + e->accept (); +} + +//--------------------------------------------------------------------- + +// protected +void kpToolText::handleTextTyped (QKeyEvent *e, + const QList & /*textLines*/, int /*cursorRow*/, int /*cursorCol*/) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\ttext=" << e->text(); +#endif + QString usableText; + for (int i = 0; i < (int) e->text ().length (); i++) + { + if (e->text ().at (i).isPrint ()) + usableText += e->text ().at (i); + } +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\tusableText=" << usableText; +#endif + + if (usableText.isEmpty ()) + { + // Don't end the current shape nor accept the event -- the event + // wasn't for us. + return; + } + + // --- It's OK for to be empty. --- + + if (!d->insertCommand) + addNewInsertCommand (&d->insertCommand); + + d->insertCommand->addText (usableText); + + e->accept (); +} diff --git a/kolourpaint/tools/selection/text/kpToolText_Move.cpp b/kolourpaint/tools/selection/text/kpToolText_Move.cpp new file mode 100644 index 00000000..aec93ffe --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText_Move.cpp @@ -0,0 +1,63 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include +#include + +#include + +#include + + +// protected virtual [kpAbstractSelectionTool] +QString kpToolText::haventBegunDrawUserMessageMove () const +{ + return i18n ("Left drag to move text box."); +} + + +// protected virtual [base kpAbstractSelectionTool] +void kpToolText::setSelectionBorderForBeginDrawMove () +{ + viewManager ()->setQueueUpdates (); + { + kpAbstractSelectionTool::setSelectionBorderForBeginDrawMove (); + viewManager ()->setTextCursorEnabled (false); + } + viewManager ()->restoreQueueUpdates (); +} + + +// protected virtual [kpAbstractSelectionTool] +QString kpToolText::nonSmearMoveCommandName () const +{ + return i18n ("Text: Move Box"); +} + diff --git a/kolourpaint/tools/selection/text/kpToolText_ResizeScale.cpp b/kolourpaint/tools/selection/text/kpToolText_ResizeScale.cpp new file mode 100644 index 00000000..75ecd93e --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText_ResizeScale.cpp @@ -0,0 +1,55 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include +#include + +#include + +#include + + +// protected virtual [kpAbstractSelectionTool] +QString kpToolText::haventBegunDrawUserMessageResizeScale () const +{ + return i18n ("Left drag to resize text box."); +} + + +// protected virtual [base kpAbstractSelectionTool] +void kpToolText::setSelectionBorderForBeginDrawResizeScale () +{ + viewManager ()->setQueueUpdates (); + { + kpAbstractSelectionTool::setSelectionBorderForBeginDrawResizeScale (); + viewManager ()->setTextCursorEnabled (false); + } + viewManager ()->restoreQueueUpdates (); +} diff --git a/kolourpaint/tools/selection/text/kpToolText_SelectText.cpp b/kolourpaint/tools/selection/text/kpToolText_SelectText.cpp new file mode 100644 index 00000000..aa292b44 --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText_SelectText.cpp @@ -0,0 +1,136 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include +#include + +#include + +#include +#include +#include +#include + + +// private +bool kpToolText::onSelectionToSelectText () const +{ + kpView *v = viewManager ()->viewUnderCursor (); + if (!v) + return 0; + + return v->mouseOnSelectionToSelectText (currentViewPoint ()); +} + + +// private +QString kpToolText::haventBegunDrawUserMessageSelectText () const +{ + return i18n ("Left click to change cursor position."); +} + +// private +void kpToolText::setCursorSelectText () +{ + viewManager ()->setCursor (Qt::IBeamCursor); +} + + +// private +void kpToolText::beginDrawSelectText () +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\t\tis select cursor pos"; +#endif + kpTextSelection *textSel = document ()->textSelection (); + Q_ASSERT (textSel); + + int newRow, newCol; + + if (textSel->hasContent ()) + { + newRow = textSel->closestTextRowForPoint (currentPoint ()); + newCol = textSel->closestTextColForPoint (currentPoint ()); + } + else + { + newRow = newCol = 0; + } + +#if DEBUG_KP_TOOL_TEXT + kDebug () << "\t\t\told: row=" << viewManager ()->textCursorRow () + << "col=" << viewManager ()->textCursorCol (); + kDebug () << "\t\t\tnew: row=" << newRow << "col=" << newCol; +#endif + viewManager ()->setTextCursorPosition (newRow, newCol); +} + + +// protected virtual +QVariant kpToolText::selectTextOperation (Operation op, + const QVariant &data1, const QVariant &data2) +{ + (void) data1; + (void) data2; + + + switch (op) + { + case HaventBegunDrawUserMessage: + return haventBegunDrawUserMessageSelectText (); + + case SetCursor: + setCursorSelectText (); + break; + + case BeginDraw: + beginDrawSelectText (); + break; + + case Draw: + // Do nothing. + break; + + case Cancel: + // Not called. REFACTOR: Change this? + break; + + case EndDraw: + // Do nothing. + break; + + default: + Q_ASSERT (!"Unhandled operation"); + break; + } + + + return QVariant (); +} diff --git a/kolourpaint/tools/selection/text/kpToolText_TextStyle.cpp b/kolourpaint/tools/selection/text/kpToolText_TextStyle.cpp new file mode 100644 index 00000000..0c6f8806 --- /dev/null +++ b/kolourpaint/tools/selection/text/kpToolText_TextStyle.cpp @@ -0,0 +1,331 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// protected +bool kpToolText::shouldChangeTextStyle () const +{ + if (environ ()->settingTextStyle ()) + { + #if DEBUG_KP_TOOL_TEXT + kDebug () << "\trecursion - abort setting text style: " + << environ ()->settingTextStyle () + << endl; + #endif + return false; + } + + if (!document ()->textSelection ()) + { + #if DEBUG_KP_TOOL_TEXT + kDebug () << "\tno text selection - abort setting text style"; + #endif + return false; + } + + return true; +} + +// protected +void kpToolText::changeTextStyle (const QString &name, + const kpTextStyle &newTextStyle, + const kpTextStyle &oldTextStyle) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::changeTextStyle(" << name << ")"; +#endif + + if (hasBegunShape ()) + endShape (currentPoint (), normalizedRect ()); + + commandHistory ()->addCommand ( + new kpToolTextChangeStyleCommand ( + name, + newTextStyle, + oldTextStyle, + environ ()->commandEnvironment ())); +} + + +// protected slot virtual [base kpAbstractSelectionTool] +void kpToolText::slotIsOpaqueChanged (bool isOpaque) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::slotIsOpaqueChanged()"; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = environ ()->textStyle (); + + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setBackgroundOpaque (!isOpaque); + + changeTextStyle (newTextStyle.isBackgroundOpaque () ? + i18n ("Text: Opaque Background") : + i18n ("Text: Transparent Background"), + newTextStyle, + oldTextStyle); +} + +// protected slot virtual [base kpTool] +void kpToolText::slotColorsSwapped (const kpColor &newForegroundColor, + const kpColor &newBackgroundColor) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::slotColorsSwapped()"; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = environ ()->textStyle (); + + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setForegroundColor (newBackgroundColor); + oldTextStyle.setBackgroundColor (newForegroundColor); + + changeTextStyle (i18n ("Text: Swap Colors"), + newTextStyle, + oldTextStyle); +} + +// protected slot virtual [base kpTool] +void kpToolText::slotForegroundColorChanged (const kpColor & /*color*/) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::slotForegroundColorChanged()"; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = environ ()->textStyle (); + + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setForegroundColor (oldForegroundColor ()); + + changeTextStyle (i18n ("Text: Foreground Color"), + newTextStyle, + oldTextStyle); +} + +// protected slot virtual [base kpAbstractSelectionTool] +void kpToolText::slotBackgroundColorChanged (const kpColor & /*color*/) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::slotBackgroundColorChanged()"; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = environ ()->textStyle (); + + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setBackgroundColor (oldBackgroundColor ()); + + changeTextStyle (i18n ("Text: Background Color"), + newTextStyle, + oldTextStyle); +} + +// protected slot virtual [base kpAbstractSelectionTool] +void kpToolText::slotColorSimilarityChanged (double, int) +{ + // --- don't pass on event to kpAbstractSelectionTool which would have set the + // SelectionTransparency - not relevant to the Text Tool --- +} + + +// public slot +void kpToolText::slotFontFamilyChanged (const QString &fontFamily, + const QString &oldFontFamily) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::slotFontFamilyChanged() new=" + << fontFamily + << " old=" + << oldFontFamily + << endl; +#else + (void) fontFamily; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = environ ()->textStyle (); + + // Figure out old text style. + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setFontFamily (oldFontFamily); + + changeTextStyle (i18n ("Text: Font"), + newTextStyle, + oldTextStyle); +} + +// public slot +void kpToolText::slotFontSizeChanged (int fontSize, int oldFontSize) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::slotFontSizeChanged() new=" + << fontSize + << " old=" + << oldFontSize + << endl; +#else + (void) fontSize; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = environ ()->textStyle (); + + // Figure out old text style. + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setFontSize (oldFontSize); + + changeTextStyle (i18n ("Text: Font Size"), + newTextStyle, + oldTextStyle); +} + + +// public slot +void kpToolText::slotBoldChanged (bool isBold) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::slotBoldChanged(" << isBold << ")"; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = environ ()->textStyle (); + + // Figure out old text style. + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setBold (!isBold); + + changeTextStyle (i18n ("Text: Bold"), + newTextStyle, + oldTextStyle); +} + +// public slot +void kpToolText::slotItalicChanged (bool isItalic) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::slotItalicChanged(" << isItalic << ")"; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = environ ()->textStyle (); + + // Figure out old text style. + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setItalic (!isItalic); + + changeTextStyle (i18n ("Text: Italic"), + newTextStyle, + oldTextStyle); +} + +// public slot +void kpToolText::slotUnderlineChanged (bool isUnderline) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::slotUnderlineChanged(" << isUnderline << ")"; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = environ ()->textStyle (); + + // Figure out old text style. + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setUnderline (!isUnderline); + + changeTextStyle (i18n ("Text: Underline"), + newTextStyle, + oldTextStyle); +} + +// public slot +void kpToolText::slotStrikeThruChanged (bool isStrikeThru) +{ +#if DEBUG_KP_TOOL_TEXT + kDebug () << "kpToolText::slotStrikeThruChanged(" << isStrikeThru << ")"; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = environ ()->textStyle (); + + // Figure out old text style. + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setStrikeThru (!isStrikeThru); + + changeTextStyle (i18n ("Text: Strike Through"), + newTextStyle, + oldTextStyle); +} diff --git a/kolourpaint/views/kpThumbnailView.cpp b/kolourpaint/views/kpThumbnailView.cpp new file mode 100644 index 00000000..6713cd0b --- /dev/null +++ b/kolourpaint/views/kpThumbnailView.cpp @@ -0,0 +1,99 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_THUMBNAIL_VIEW 0 + + +#include + +#include + + +kpThumbnailView::kpThumbnailView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent) + + : kpView (document, toolToolBar, viewManager, + buddyView, + scrollableContainer, + parent) +{ +} + +kpThumbnailView::~kpThumbnailView () +{ +} + + +// protected +void kpThumbnailView::setMaskToCoverDocument () +{ +#if DEBUG_KP_THUMBNAIL_VIEW + kDebug () << "kpThumbnailView::setMaskToCoverDocument()" + << " origin=" << origin () + << " zoomedDoc: width=" << zoomedDocWidth () + << " height=" << zoomedDocHeight () + << endl; +#endif + + setMask (QRegion (QRect (origin ().x (), origin ().y (), + zoomedDocWidth (), zoomedDocHeight ()))); +} + + +// protected virtual [base kpView] +void kpThumbnailView::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_THUMBNAIL_VIEW + kDebug () << "kpThumbnailView(" << name () << ")::resizeEvent()" + << endl; +#endif + + // For QResizeEvent's, Qt already throws an entire widget repaint into + // the event loop. So eat useless update() calls that can only slow + // things down. + // TODO: this doesn't seem to work. + // Later: In Qt4, setUpdatesEnabled(true) calls update(). + const bool oldIsUpdatesEnabled = updatesEnabled (); + setUpdatesEnabled (false); + + { + kpView::resizeEvent (e); + + adjustToEnvironment (); + } + + setUpdatesEnabled (oldIsUpdatesEnabled); +} + + +#include + diff --git a/kolourpaint/views/kpThumbnailView.h b/kolourpaint/views/kpThumbnailView.h new file mode 100644 index 00000000..735a6e05 --- /dev/null +++ b/kolourpaint/views/kpThumbnailView.h @@ -0,0 +1,90 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_THUMBNAIL_VIEW_H +#define KP_THUMBNAIL_VIEW_H + + +#include + + +/** + * @short Abstract base class for all thumbnail views. + * + * @author Clarence Dang + */ +class kpThumbnailView : public kpView +{ +Q_OBJECT + +public: + /** + * Constructs a thumbnail view. + * + * You must call adjustEnvironment() at the end of your constructor. + */ + kpThumbnailView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent); + + /** + * Destructs this thumbnail view. + */ + virtual ~kpThumbnailView (); + + + /** + * @returns the caption to display in an enclosing thumbnail window. + */ + virtual QString caption () const = 0; + + +protected: + /** + * Sets the mask to cover the rectangle with top-left, origin() and + * dimensions equal to or slightly less than (in case of rounding + * error) the size of the document in view coordinates. This ensures + * that all pixels are initialised with either document pixels or the + * standard widget background. + */ + void setMaskToCoverDocument (); + + + /** + * Calls adjustToEnvironment() in response to a resize event. + * + * Extends @ref kpView. + */ + virtual void resizeEvent (QResizeEvent *e); +}; + + +#endif // KP_THUMBNAIL_VIEW_H diff --git a/kolourpaint/views/kpUnzoomedThumbnailView.cpp b/kolourpaint/views/kpUnzoomedThumbnailView.cpp new file mode 100644 index 00000000..6c4517d6 --- /dev/null +++ b/kolourpaint/views/kpUnzoomedThumbnailView.cpp @@ -0,0 +1,218 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_UNZOOMED_THUMBNAIL_VIEW 0 + + +#include + +#include +#include + +#include +#include +#include + +#include + +//--------------------------------------------------------------------- + +struct kpUnzoomedThumbnailViewPrivate +{ +}; + + +kpUnzoomedThumbnailView::kpUnzoomedThumbnailView ( + kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent) + + : kpThumbnailView (document, toolToolBar, viewManager, + buddyView, + scrollableContainer, + parent), + d (new kpUnzoomedThumbnailViewPrivate ()) +{ + if (buddyViewScrollableContainer ()) + { + connect (buddyViewScrollableContainer (), + SIGNAL (contentsMoved()), + this, + SLOT (adjustToEnvironment ())); + } + + // Call to virtual function - this is why the class is sealed + adjustToEnvironment (); +} + +//--------------------------------------------------------------------- + +kpUnzoomedThumbnailView::~kpUnzoomedThumbnailView () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpThumbnailView] +QString kpUnzoomedThumbnailView::caption () const +{ + return i18n ("Unzoomed Mode - Thumbnail"); +} + +//--------------------------------------------------------------------- + +// public slot virtual [base kpView] +void kpUnzoomedThumbnailView::adjustToEnvironment () +{ + if (!buddyView () || !buddyViewScrollableContainer () || !document ()) + return; + + const int scrollViewContentsX = + buddyViewScrollableContainer()->horizontalScrollBar()->value(); + const int scrollViewContentsY = + buddyViewScrollableContainer ()->verticalScrollBar()->value(); + +#if DEBUG_KP_UNZOOMED_THUMBNAIL_VIEW + kDebug () << "kpUnzoomedThumbnailView(" << name () + << ")::adjustToEnvironment(" + << scrollViewContentsX + << "," + << scrollViewContentsY + << ") width=" << width () + << " height=" << height () + << endl; +#endif + + +#if 1 + int x; + if (document ()->width () > width ()) + { + x = (int) buddyView ()->transformViewToDocX (scrollViewContentsX); + const int rightMostAllowedX = qMax (0, document ()->width () - width ()); + #if DEBUG_KP_UNZOOMED_THUMBNAIL_VIEW + kDebug () << "\tdocX=" << x + << " docWidth=" << document ()->width () + << " rightMostAllowedX=" << rightMostAllowedX + << endl; + #endif + if (x > rightMostAllowedX) + x = rightMostAllowedX; + } + // Thumbnail width <= doc width + else + { + // Center X (rather than flush left to be consistent with + // kpZoomedThumbnailView) + x = -(width () - document ()->width ()) / 2; + } + + + int y; + if (document ()->height () > height ()) + { + y = (int) buddyView ()->transformViewToDocY (scrollViewContentsY); + const int bottomMostAllowedY = qMax (0, document ()->height () - height ()); + #if DEBUG_KP_UNZOOMED_THUMBNAIL_VIEW + kDebug () << "\tdocY=" << y + << " docHeight=" << document ()->height () + << " bottomMostAllowedY=" << bottomMostAllowedY + << endl; + #endif + if (y > bottomMostAllowedY) + y = bottomMostAllowedY; + } + // Thumbnail height <= doc height + else + { + // Center Y (rather than flush top to be consistent with + // kpZoomedThumbnailView) + y = -(height () - document ()->height ()) / 2; + } +// Prefer to keep visible area centred in thumbnail instead of flushed left. +// Gives more editing context to the left and top. +// But feels awkward for left-to-right users. So disabled for now. +// Not totally tested. +#else + if (!buddyViewScrollableContainer ()) + return; + + QRect docRect = buddyView ()->transformViewToDoc ( + QRect (buddyViewScrollableContainer ()->horizontalScrollBar()->value(), + buddyViewScrollableContainer ()->verticalScrollBar()->value(), + qMin (buddyView ()->width (), buddyViewScrollableContainer ()->viewport()->width ()), + qMin (buddyView ()->height (), buddyViewScrollableContainer ()->viewport()->height ()))); + + x = docRect.x () - (width () - docRect.width ()) / 2; + kDebug () << "\tnew suggest x=" << x; + const int rightMostAllowedX = qMax (0, document ()->width () - width ()); + if (x < 0) + x = 0; + if (x > rightMostAllowedX) + x = rightMostAllowedX; + + y = docRect.y () - (height () - docRect.height ()) / 2; + kDebug () << "\tnew suggest y=" << y; + const int bottomMostAllowedY = qMax (0, document ()->height () - height ()); + if (y < 0) + y = 0; + if (y > bottomMostAllowedY) + y = bottomMostAllowedY; +#endif + + + if (viewManager ()) + { + viewManager ()->setFastUpdates (); + viewManager ()->setQueueUpdates (); + } + + { + // OPT: scrollView impl would be much, much faster + setOrigin (QPoint (-x, -y)); + setMaskToCoverDocument (); + + // Above might be a NOP even if e.g. doc size changed so force + // update + if (viewManager ()) + viewManager ()->updateView (this); + } + + if (viewManager ()) + { + viewManager ()->restoreQueueUpdates (); + viewManager ()->restoreFastUpdates (); + } +} + + +#include diff --git a/kolourpaint/views/kpUnzoomedThumbnailView.h b/kolourpaint/views/kpUnzoomedThumbnailView.h new file mode 100644 index 00000000..024bc728 --- /dev/null +++ b/kolourpaint/views/kpUnzoomedThumbnailView.h @@ -0,0 +1,106 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_UNZOOMED_THUMBNAIL_VIEW_H +#define KP_UNZOOMED_THUMBNAIL_VIEW_H + + +#include + + +class kpViewScrollableContainer; + + +/** + * @short Unzoomed thumbnail view of a document. + * + * This is an unzoomed thumbnail view of a document. Unlike + * @ref kpZoomedThumbnailView, it never changes the zoom level. And unlike + * @ref kpZoomedView, it never resizes itself. Instead, it changes its + * origin according to the main view's scrollable container so that the + * top-left most document pixel displayed in the scrollable container will + * be visible. + * + * Do not call setZoomLevel() nor setOrigin(). + * + * This class is sealed. Do not derive from it. + * + * @author Clarence Dang + */ +/*sealed*/ class kpUnzoomedThumbnailView : public kpThumbnailView +{ +Q_OBJECT + +public: + /** + * Constructs an unzoomed thumbnail view. + */ + kpUnzoomedThumbnailView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent); + + /** + * Destructs an unzoomed thumbnail view. + */ + virtual ~kpUnzoomedThumbnailView (); + + + /** + * Implements @ref kpThumbnailView. + */ + QString caption () const; + + +public slots: + /** + * Changes its origin according to the main view's scrollable container + * so that the top-left most document pixel displayed in the scrollable + * container will be visible. + * + * It tries to maximise the used area of this view. Unused areas will + * be set to the widget background thanks to the mask. + * + * Call this if the size of the document changes. + * Already connected to buddyViewScrollableContainer()'s + * contentsMoved() signal. + * Already called by @ref kpThumbnailView resizeEvent(). + * + * Implements @ref kpView. + */ + virtual void adjustToEnvironment (); + + +private: + struct kpUnzoomedThumbnailViewPrivate *d; +}; + + +#endif // KP_UNZOOMED_THUMBNAIL_VIEW_H diff --git a/kolourpaint/views/kpView.cpp b/kolourpaint/views/kpView.cpp new file mode 100644 index 00000000..c496f570 --- /dev/null +++ b/kolourpaint/views/kpView.cpp @@ -0,0 +1,672 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2005 Kazuki Ohta + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_VIEW 0 +#define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0) + + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +// public static +const int kpView::MinZoomLevel = 1; +const int kpView::MaxZoomLevel = 3200; + +//--------------------------------------------------------------------- + +kpView::kpView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent) + : QWidget (parent), + d (new kpViewPrivate ()) +{ + d->document = document; + d->toolToolBar = toolToolBar; + d->viewManager = viewManager; + d->buddyView = buddyView; + d->scrollableContainer = scrollableContainer; + + d->hzoom = 100, d->vzoom = 100; + d->origin = QPoint (0, 0); + d->showGrid = false; + d->isBuddyViewScrollableContainerRectangleShown = false; + + // Don't waste CPU drawing default background since its overridden by + // our fully opaque drawing. In reality, this seems to make no + // difference in performance. + setAttribute(Qt::WA_OpaquePaintEvent, true); + + setFocusPolicy (Qt::WheelFocus); + setMouseTracking (true); // mouseMoveEvent's even when no mousebtn down + setAttribute (Qt::WA_KeyCompression, true); +} + +//--------------------------------------------------------------------- + +kpView::~kpView () +{ + setHasMouse (false); + + delete d; +} + +//--------------------------------------------------------------------- + +// public +kpDocument *kpView::document () const +{ + return d->document; +} + +//--------------------------------------------------------------------- + +// protected +kpAbstractSelection *kpView::selection () const +{ + return document () ? document ()->selection () : 0; +} + +//--------------------------------------------------------------------- + +// protected +kpTextSelection *kpView::textSelection () const +{ + return document () ? document ()->textSelection () : 0; +} + +//--------------------------------------------------------------------- + +// public +kpToolToolBar *kpView::toolToolBar () const +{ + return d->toolToolBar; +} + +// protected +kpTool *kpView::tool () const +{ + return toolToolBar () ? toolToolBar ()->tool () : 0; +} + +// public +kpViewManager *kpView::viewManager () const +{ + return d->viewManager; +} + +// public +kpView *kpView::buddyView () const +{ + return d->buddyView; +} + +// public +kpViewScrollableContainer *kpView::buddyViewScrollableContainer () const +{ + return (buddyView () ? buddyView ()->scrollableContainer () : 0); +} + +// public +kpViewScrollableContainer *kpView::scrollableContainer () const +{ + return d->scrollableContainer; +} + + +// public +int kpView::zoomLevelX (void) const +{ + return d->hzoom; +} + +// public +int kpView::zoomLevelY (void) const +{ + return d->vzoom; +} + +// public virtual +void kpView::setZoomLevel (int hzoom, int vzoom) +{ + hzoom = qBound (MinZoomLevel, hzoom, MaxZoomLevel); + vzoom = qBound (MinZoomLevel, vzoom, MaxZoomLevel); + + if (hzoom == d->hzoom && vzoom == d->vzoom) + return; + + d->hzoom = hzoom; + d->vzoom = vzoom; + + if (viewManager ()) + viewManager ()->updateView (this); + + emit zoomLevelChanged (hzoom, vzoom); +} + + +// public +QPoint kpView::origin () const +{ + return d->origin; +} + +// public virtual +void kpView::setOrigin (const QPoint &origin) +{ +#if DEBUG_KP_VIEW + kDebug () << "kpView(" << objectName () << ")::setOrigin" << origin; +#endif + + if (origin == d->origin) + { + #if DEBUG_KP_VIEW + kDebug () << "\tNOP"; + #endif + return; + } + + d->origin = origin; + + if (viewManager ()) + viewManager ()->updateView (this); + + emit originChanged (origin); +} + + +// public +bool kpView::canShowGrid () const +{ + // (minimum zoom level < 400% would probably be reported as a bug by + // users who thought that the grid was a part of the image!) + return ((zoomLevelX () >= 400 && zoomLevelX () % 100 == 0) && + (zoomLevelY () >= 400 && zoomLevelY () % 100 == 0)); +} + +// public +bool kpView::isGridShown () const +{ + return d->showGrid; +} + +// public +void kpView::showGrid (bool yes) +{ + if (d->showGrid == yes) + return; + + if (yes && !canShowGrid ()) + return; + + d->showGrid = yes; + + if (viewManager ()) + viewManager ()->updateView (this); +} + + +// public +bool kpView::isBuddyViewScrollableContainerRectangleShown () const +{ + return d->isBuddyViewScrollableContainerRectangleShown; +} + +// public +void kpView::showBuddyViewScrollableContainerRectangle (bool yes) +{ + if (yes == d->isBuddyViewScrollableContainerRectangleShown) + return; + + d->isBuddyViewScrollableContainerRectangleShown = yes; + + if (d->isBuddyViewScrollableContainerRectangleShown) + { + // Got these connect statements by analysing deps of + // updateBuddyViewScrollableContainerRectangle() rect update code. + + connect (this, SIGNAL (zoomLevelChanged (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + connect (this, SIGNAL (originChanged (const QPoint &)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + + if (buddyViewScrollableContainer ()) + { + connect (buddyViewScrollableContainer (), SIGNAL (contentsMoved()), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + connect (buddyViewScrollableContainer (), SIGNAL (resized ()), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + } + + if (buddyView ()) + { + connect (buddyView (), SIGNAL (zoomLevelChanged (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + connect (buddyView (), SIGNAL (originChanged (const QPoint &)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + + connect (buddyView (), SIGNAL (sizeChanged (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + } + + } + else + { + disconnect (this, SIGNAL (zoomLevelChanged (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + disconnect (this, SIGNAL (originChanged (const QPoint &)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + + if (buddyViewScrollableContainer ()) + { + disconnect (buddyViewScrollableContainer (), SIGNAL (contentsMoved()), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + disconnect (buddyViewScrollableContainer (), SIGNAL (resized ()), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + } + + if (buddyView ()) + { + disconnect (buddyView (), SIGNAL (zoomLevelChanged (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + disconnect (buddyView (), SIGNAL (originChanged (const QPoint &)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + + disconnect (buddyView (), SIGNAL (sizeChanged (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + } + + } + + updateBuddyViewScrollableContainerRectangle (); +} + + +// protected +QRect kpView::buddyViewScrollableContainerRectangle () const +{ + return d->buddyViewScrollableContainerRectangle; +} + +// protected slot +void kpView::updateBuddyViewScrollableContainerRectangle () +{ + if (viewManager ()) + viewManager ()->setQueueUpdates (); + + { + if (d->buddyViewScrollableContainerRectangle.isValid ()) + { + if (viewManager ()) + { + // Erase last + viewManager ()->updateViewRectangleEdges (this, + d->buddyViewScrollableContainerRectangle); + } + } + + + QRect newRect; + if (isBuddyViewScrollableContainerRectangleShown () && + buddyViewScrollableContainer () && buddyView ()) + { + QRect docRect = buddyView ()->transformViewToDoc ( + QRect (buddyViewScrollableContainer ()->horizontalScrollBar()->value(), + buddyViewScrollableContainer ()->verticalScrollBar()->value(), + qMin (buddyView ()->width (), + buddyViewScrollableContainer ()->viewport()->width ()), + qMin (buddyView ()->height (), + buddyViewScrollableContainer ()->viewport()->height ()))); + + + QRect viewRect = this->transformDocToView (docRect); + + + // (Surround the area of interest by moving outwards by 1 pixel in each + // direction - don't overlap area) + newRect = QRect (viewRect.x () - 1, + viewRect.y () - 1, + viewRect.width () + 2, + viewRect.height () + 2); + } + else + { + newRect = QRect (); + } + + if (newRect != d->buddyViewScrollableContainerRectangle) + { + // (must set before updateView() for paintEvent() to see new + // rect) + d->buddyViewScrollableContainerRectangle = newRect; + + if (newRect.isValid ()) + { + if (viewManager ()) + { + viewManager ()->updateViewRectangleEdges (this, + d->buddyViewScrollableContainerRectangle); + } + } + } + } + + if (viewManager ()) + viewManager ()->restoreQueueUpdates (); +} + +//--------------------------------------------------------------------- + +// public +double kpView::transformViewToDocX (double viewX) const +{ + return (viewX - origin ().x ()) * 100.0 / zoomLevelX (); +} + +//--------------------------------------------------------------------- + +// public +double kpView::transformViewToDocY (double viewY) const +{ + return (viewY - origin ().y ()) * 100.0 / zoomLevelY (); +} + +//--------------------------------------------------------------------- + +// public +QPoint kpView::transformViewToDoc (const QPoint &viewPoint) const +{ + return QPoint ((int) transformViewToDocX (viewPoint.x ()), + (int) transformViewToDocY (viewPoint.y ())); +} + +//--------------------------------------------------------------------- + +// public +QRect kpView::transformViewToDoc (const QRect &viewRect) const +{ + if (zoomLevelX () == 100 && zoomLevelY () == 100) + { + return QRect (viewRect.x () - origin ().x (), + viewRect.y () - origin ().y (), + viewRect.width (), + viewRect.height ()); + } + else + { + const QPoint docTopLeft = transformViewToDoc (viewRect.topLeft ()); + + // (don't call transformViewToDoc[XY]() - need to round up dimensions) + const int docWidth = qRound (double (viewRect.width ()) * 100.0 / double (zoomLevelX ())); + const int docHeight = qRound (double (viewRect.height ()) * 100.0 / double (zoomLevelY ())); + + // (like QWMatrix::Areas) + return QRect (docTopLeft.x (), docTopLeft.y (), docWidth, docHeight); + } +} + +//--------------------------------------------------------------------- + +// public +double kpView::transformDocToViewX (double docX) const +{ + return (docX * zoomLevelX () / 100.0) + origin ().x (); +} + +// public +double kpView::transformDocToViewY (double docY) const +{ + return (docY * zoomLevelY () / 100.0) + origin ().y (); +} + +// public +QPoint kpView::transformDocToView (const QPoint &docPoint) const +{ + return QPoint ((int) transformDocToViewX (docPoint.x ()), + (int) transformDocToViewY (docPoint.y ())); +} + +// public +QRect kpView::transformDocToView (const QRect &docRect) const +{ + if (zoomLevelX () == 100 && zoomLevelY () == 100) + { + return QRect (docRect.x () + origin ().x (), + docRect.y () + origin ().y (), + docRect.width (), + docRect.height ()); + } + else + { + const QPoint viewTopLeft = transformDocToView (docRect.topLeft ()); + + // (don't call transformDocToView[XY]() - need to round up dimensions) + const int viewWidth = qRound (double (docRect.width ()) * double (zoomLevelX ()) / 100.0); + const int viewHeight = qRound (double (docRect.height ()) * double (zoomLevelY ()) / 100.0); + + // (like QWMatrix::Areas) + return QRect (viewTopLeft.x (), viewTopLeft.y (), viewWidth, viewHeight); + } +} + + +// public +QPoint kpView::transformViewToOtherView (const QPoint &viewPoint, + const kpView *otherView) +{ + if (this == otherView) + return viewPoint; + + const double docX = transformViewToDocX (viewPoint.x ()); + const double docY = transformViewToDocY (viewPoint.y ()); + + const double otherViewX = otherView->transformDocToViewX (docX); + const double otherViewY = otherView->transformDocToViewY (docY); + + return QPoint ((int) otherViewX, (int) otherViewY); +} + + +// public +int kpView::zoomedDocWidth () const +{ + return document () ? document ()->width () * zoomLevelX () / 100 : 0; +} + +// public +int kpView::zoomedDocHeight () const +{ + return document () ? document ()->height () * zoomLevelY () / 100 : 0; +} + + +// public +void kpView::setHasMouse (bool yes) +{ + kpViewManager *vm = viewManager (); + if (!vm) + return; + +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView(" << objectName () + << ")::setHasMouse(" << yes + << ") existing viewUnderCursor=" + << (vm->viewUnderCursor () ? vm->viewUnderCursor ()->objectName () : "(none)") + << endl; +#endif + if (yes && vm->viewUnderCursor () != this) + vm->setViewUnderCursor (this); + else if (!yes && vm->viewUnderCursor () == this) + vm->setViewUnderCursor (0); +} + +//--------------------------------------------------------------------- + +// public +void kpView::addToQueuedArea (const QRegion ®ion) +{ +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView(" << objectName () + << ")::addToQueuedArea() already=" << d->queuedUpdateArea + << " - plus - " << region + << endl; +#endif + d->queuedUpdateArea += region; +} + +//--------------------------------------------------------------------- + +// public +void kpView::addToQueuedArea (const QRect &rect) +{ +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView(" << objectName () + << ")::addToQueuedArea() already=" << d->queuedUpdateArea + << " - plus - " << rect + << endl; +#endif + d->queuedUpdateArea += rect; +} + +//--------------------------------------------------------------------- + +// public +void kpView::invalidateQueuedArea () +{ +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView::invalidateQueuedArea()"; +#endif + + d->queuedUpdateArea = QRegion (); +} + +//--------------------------------------------------------------------- + +// public +void kpView::updateQueuedArea () +{ + kpViewManager *vm = viewManager (); +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView(" << objectName () + << ")::updateQueuedArea() vm=" << (bool) vm + << " queueUpdates=" << (vm && vm->queueUpdates ()) + << " fastUpdates=" << (vm && vm->fastUpdates ()) + << " area=" << d->queuedUpdateArea + << endl; +#endif + + if (!vm) + return; + + if (vm->queueUpdates ()) + return; + + if (!d->queuedUpdateArea.isEmpty ()) + vm->updateView (this, d->queuedUpdateArea); + + invalidateQueuedArea (); +} + +//--------------------------------------------------------------------- + +// public +QPoint kpView::mouseViewPoint (const QPoint &returnViewPoint) const +{ + if (returnViewPoint != KP_INVALID_POINT) + return returnViewPoint; + else + { + // TODO: I don't think this is right for the main view since that's + // inside the scrollview (which can scroll). + return mapFromGlobal (QCursor::pos ()); + } +} + +//--------------------------------------------------------------------- + +// public virtual +QVariant kpView::inputMethodQuery (Qt::InputMethodQuery query) const +{ +#if DEBUG_KP_VIEW && 1 + kDebug () << "kpView(" << objectName () << ")::inputMethodQuery()"; +#endif + QVariant ret; + switch (query) + { + case Qt::ImMicroFocus: + { + QRect r = d->viewManager->textCursorRect (); + r.setTopLeft (r.topLeft () + origin ()); + r.setHeight (r.height() + 2); + r = transformDocToView (r); + ret = r; + break; + } + case Qt::ImFont: + { + if (textSelection ()) + { + ret = textSelection ()->textStyle ().font (); + } + break; + } + default: + break; + } + return ret; +} + +//--------------------------------------------------------------------- + +#include + diff --git a/kolourpaint/views/kpView.h b/kolourpaint/views/kpView.h new file mode 100644 index 00000000..1e54bb50 --- /dev/null +++ b/kolourpaint/views/kpView.h @@ -0,0 +1,599 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_VIEW_H +#define KP_VIEW_H + + +#include + +#include + + +class QDragEnterEvent; +class QDragLeaveEvent; +class QEvent; +class QFocusEvent; +class QKeyEvent; +class QMouseEvent; +class QPaintEvent; +class QPixmap; +class QResizeEvent; + +class kpAbstractSelection; +class kpDocument; +class kpTextSelection; +class kpTool; +class kpToolToolBar; +class kpViewManager; +class kpViewScrollableContainer; + + +/** + * @short Abstract base class for all views. + * + * This is the abstract base class for all views. A view is a widget that + * renders a possibly zoomed onscreen representation of a document. + * + * 1 view corresponds to 1 document. + * 1 document corresponds to 0 or more views. + * + * @see kpViewManager + * + * @author Clarence Dang + */ +class kpView : public QWidget +{ +Q_OBJECT + +public: + /** + * Constructs a view. + * + * @param document The document this view is representing. + * @param toolToolBar The tool tool bar. + * @param viewManager The view manager. + * @param buddyView The view this view watches over (e.g. a thumbnail + * view would watch over the main view). May be 0. + * See for example, highlightBuddyViewRectangle(). + * @param scrollableContainer This view's scrollable container. + * May be 0. + * + * You must call adjustEnvironment() at the end of your constructor. + */ + kpView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent); + + /** + * Destructs this view. Informs the viewManager() that the mouse + * cursor is no longer above this view. + */ + virtual ~kpView (); + + + // + // Constants (enforced by methods) + // + static const int MinZoomLevel, MaxZoomLevel; + + + /** + * @returns the document. + */ + kpDocument *document () const; + +protected: + /** + * @returns the document's selection. + */ + kpAbstractSelection *selection () const; + + kpTextSelection *textSelection () const; + +public: + /** + * @returns the tool tool bar. + */ + kpToolToolBar *toolToolBar () const; + +protected: + /** + * @returns the currently selected tool. + */ + kpTool *tool () const; + +public: + /** + * @returns the view manager. + */ + kpViewManager *viewManager () const; + + /** + * @returns the buddy view. + */ + kpView *buddyView () const; + + /** + * @returns the buddyView()'s scrollable container. + */ + kpViewScrollableContainer *buddyViewScrollableContainer () const; + + /** + * @returns this view's scrollable container. + */ + kpViewScrollableContainer *scrollableContainer () const; + + + /** + * @returns the horizontal zoom level (100 is unzoomed). + */ + int zoomLevelX (void) const; + + /** + * @returns the vertical zoom level (100 is unzoomed). + */ + int zoomLevelY (void) const; + + /** + * Sets the horizontal and vertical zoom levels. + * + * @param hzoom Horizontal zoom level. + * @param vzoom Vertical zoom level. + * + * This method automatically bounds and to be between + * MinZoomLevel and MaxZoomLevel inclusive. + * + * If reimplementing, you must call this base implementation. + */ + virtual void setZoomLevel (int hzoom, int vzoom); + + + /** + * @returns in views coordinates, where the top-left document() pixel + * will be rendered (default: (0,0)). + */ + QPoint origin () const; + + /** + * Sets the origin. + * + * @param origin New origin. + * + * If reimplementing, you must call this base implementation. + */ + virtual void setOrigin (const QPoint &origin); + + + /** + * @returns whether at this zoom level, the grid can be enabled. + * This is based on whether the grid can be sensibly rendered. + */ + bool canShowGrid () const; + + /** + * @returns whether the grid is currently shown. + */ + bool isGridShown () const; + + /** + * Turns on/off the grid. + * + * @param yes Whether to enable the grid. + */ + void showGrid (bool yes = true); + + + /** + * @returns whether to draw a rectangle highlighting the area of + * buddyView() visible through buddyViewScrollableContainer(). + */ + bool isBuddyViewScrollableContainerRectangleShown () const; + + /** + * Turns on/off the rectangle highlighting the area of buddyView() + * visible through buddyViewScrollableContainer() and redraws. + * + * @param yes Whether to turn on the rectangle. + */ + void showBuddyViewScrollableContainerRectangle (bool yes = true); + +protected: + /** + * @returns the current rectangle highlighting the area of buddyView() + * visible through buddyViewScrollableContainer(), that is being + * rendered by this view. + */ + QRect buddyViewScrollableContainerRectangle () const; + +protected slots: + /** + * Updates the buddyViewScrollableContainerRectangle() and redraws + * appropriately. + * + * This is already connected to zoomLevelChanged() and originChanged(); + * buddyView() and buddyViewScrollableContainer() signals. There is probably no + * need to call this function directly. + */ + void updateBuddyViewScrollableContainerRectangle (); + + +public: + + /** + * @param viewX Horizontal position in view coordinates. + * + * @returns viewX transformed to document coordinates, based on the + * origin() and zoomLevelX(). + */ + double transformViewToDocX (double viewX) const; + + /** + * @param viewY Vertical position in view coordinates. + * + * @returns viewY transformed to document coordinates, based on the + * origin() and zoomLevelY(). + */ + double transformViewToDocY (double viewY) const; + + /** + * @param viewPoint Position in view coordinates. + * + * @returns viewPoint transformed to document coordinates, based on the + * origin(), zoomLevelX() and zoomLevelY(). + */ + QPoint transformViewToDoc (const QPoint &viewPoint) const; + + /** + * @param viewRect Rectangle in view coordinates. + * + * @returns viewRect transformed to document coordinates based on the + * origin(), zoomLevelX() and zoomLevelY(). + * + * For bounding rectangles, you should use this function instead of + * transformViewToDocX(), transformViewToDocY() or + * transformViewToDoc(const QPoint &) which act on coordinates only. + */ + QRect transformViewToDoc (const QRect &viewRect) const; + + + /** + * @param docX Horizontal position in document coordinates. + * + * @returns docX transformed to view coordinates, based on the origin() + * and zoomLevelX(). + */ + double transformDocToViewX (double docX) const; + + /** + * @param docY Vertical position in document coordinates. + * + * @returns docY transformed to view coordinates, based on the origin() + * and zoomLevelY(). + */ + double transformDocToViewY (double docY) const; + + /** + * @param docPoint Position in document coordinates. + * + * @returns docPoint transformed to view coordinates, based on the + * origin(), zoomLevelX(), zoomLevelY(). + */ + QPoint transformDocToView (const QPoint &docPoint) const; + + /** + * @param docRect Rectangle in document coordinates. + * + * @return docRect transformed to view coordinates, based on the + * origin(), zoomLevelX() and zoomLevelY(). + * + * For bounding rectangles, you should use this function instead of + * transformDocToViewX(), transformDocToViewY() or + * transformDocToView(const QPoint &) which act on coordinates only. + */ + QRect transformDocToView (const QRect &docRect) const; + + + /** + * @param viewPoint Position in view coordinates. + * @param otherView View whose coordinate system the return value will + * be in. + * + * @returns viewPoint transformed to the coordinate system of + * @param otherView based on this and otherView's origin(), + * zoomLevelX() and zoomLevelY(). This has less rounding + * error than otherView->transformDocToView (transformViewToDoc (viewPoint)). + */ + QPoint transformViewToOtherView (const QPoint &viewPoint, + const kpView *otherView); + + + /** + * @returns the approximate view width required to display the entire + * document(), based on the zoom level only. + */ + int zoomedDocWidth () const; + + /** + * @returns the approximate view height required to display the entire + * document(), based on the zoom level only. + */ + int zoomedDocHeight () const; + + +protected: + /** + * Updates the viewManager() on whether or not the mouse is directly + * above this view. Among other things, this ensures the brush cursor + * is updated. + * + * This should be called in event handlers. + * + * @param yes Whether the mouse is directly above this view. + */ + void setHasMouse (bool yes = true); + + +public: + /** + * Adds a region (in view coordinates) to the dirty area that is + * repainted when the parent @ref kpViewManager is set not to queue + * updates. + * + * @param region Region (in view coordinates) that needs repainting. + */ + void addToQueuedArea (const QRegion ®ion); + + /** + * Convenience function. Same as above. + * + * Adds a rectangle (in view coordinates) to the dirty area that is + * repainted when the parent @ref kpViewManager is set not to queue + * updates. + * + * @param rect Rectangle (in view coordinates) that needs repainting. + */ + void addToQueuedArea (const QRect &rect); + + /** + * Removes the dirty region that has been queued for updating. + * Does not update the view. + */ + void invalidateQueuedArea (); + + /** + * Updates the part of the view described by dirty region and then + * calls invalidateQueuedArea(). Does nothing if @ref kpViewManager + * is set to queue updates. + */ + void updateQueuedArea (); + + QVariant inputMethodQuery (Qt::InputMethodQuery query) const; + +public slots: + /** + * Call this when the "environment" (e.g. document size) changes. The + * environment is defined by the caller and should be based on the type + * of view. For instance, an unzoomed thumbnail view would also + * include in its environment the contents position of an associated + * scrollable container. + * + * This is never called by the kpView base class. + * + * Implementors should change whatever state is necessary for their + * type of view. For instance, an unzoomed view would resize itself; + * a zoomed thumbnail would change the zoom level. + */ + virtual void adjustToEnvironment () = 0; + + +public: + // If is not KP_INVALID_POINT, it spits it back. + // Else, it returns the current mouse position in view coordinates. + // REFACTOR: Seems like a bad API. + QPoint mouseViewPoint (const QPoint &returnViewPoint = KP_INVALID_POINT) const; + + +signals: + /** + * Emitted after all zooming code has been executed. + * + * @param zoomLevelX New zoomLevelX() + * @param zoomLevelY New zoomLevelY() + */ + void zoomLevelChanged (int zoomLevelX, int zoomLevelY); + + /** + * Emitted after all resizing code has been executed. + * + * @param size New view size. + */ + void sizeChanged (const QSize &size); + + /** + * Convenience signal - same as above. + * + * Emitted after all resizing code has been executed. + * + * @param width New view width. + * @param height New view height. + */ + void sizeChanged (int width, int height); + + /** + * Emitted after all origin changing code has been executed. + * + * @param origin The new origin. + */ + void originChanged (const QPoint &origin); + + + +// +// Selections +// + +public: + QRect selectionViewRect () const; + + // (if is KP_INVALID_POINT, it uses QCursor::pos()) + + QPoint mouseViewPointRelativeToSelection (const QPoint &viewPoint = KP_INVALID_POINT) const; + bool mouseOnSelection (const QPoint &viewPoint = KP_INVALID_POINT) const; + + int textSelectionMoveBorderAtomicSize () const; + bool mouseOnSelectionToMove (const QPoint &viewPoint = KP_INVALID_POINT) const; + +protected: + bool selectionLargeEnoughToHaveResizeHandlesIfAtomicSize (int atomicSize) const; +public: + int selectionResizeHandleAtomicSize () const; + bool selectionLargeEnoughToHaveResizeHandles () const; + + QRegion selectionResizeHandlesViewRegion (bool forRenderer = false) const; + + enum SelectionResizeType + { + None = 0, + Left = 1, + Right = 2, + Top = 4, + Bottom = 8 + }; + + // Returns a bitwise OR of the SelectionResizeType's + int mouseOnSelectionResizeHandle (const QPoint &viewPoint = KP_INVALID_POINT) const; + + bool mouseOnSelectionToSelectText (const QPoint &viewPoint = KP_INVALID_POINT) const; + + +// +// Events +// + +protected: + virtual void mouseMoveEvent (QMouseEvent *e); + virtual void mousePressEvent (QMouseEvent *e); + virtual void mouseReleaseEvent (QMouseEvent *e); +public: + // (needs to be public as it may also get event from + // QScrollView::contentsWheelEvent()) + virtual void wheelEvent (QWheelEvent *e); + + +protected: + virtual void keyPressEvent (QKeyEvent *e); + virtual void keyReleaseEvent (QKeyEvent *e); + +protected: + virtual void inputMethodEvent (QInputMethodEvent *e); + +protected: + virtual bool event (QEvent *e); + + +protected: + virtual void focusInEvent (QFocusEvent *e); + virtual void focusOutEvent (QFocusEvent *e); + + +protected: + virtual void enterEvent (QEvent *e); + virtual void leaveEvent (QEvent *e); + + +protected: + virtual void dragEnterEvent (QDragEnterEvent *); + virtual void dragLeaveEvent (QDragLeaveEvent *); + + +protected: + virtual void resizeEvent (QResizeEvent *e); + + +// +// Painting +// + +protected: + // Returns the document rectangle that, when scaled to the view, + // is "guaranteed" to at least cover and possibly more + // ("guaranteed" in quotes because it doesn't seem so reliable for + // zoom levels that aren't multiples of 100%). + QRect paintEventGetDocRect (const QRect &viewRect) const; +public: + /** + * Draws an opaque background representing transparency. + * + * Currently, it draws a checkerboard which, if it were to be drawn + * in its entirety, is tiled from . + * + * @param painter Painter. + * @param patternOrigin Logical top-left point of the checkerboard, + * relative to the painter, if it were to be + * drawn in its entirety. + * @param viewRect Rectangle to paint in relative to the painter. + * @param isPreview Whether the view is for a preview as opposed to + * e.g. editing. If set, this function may render + * slightly differently. + */ + static void drawTransparentBackground (QPainter *painter, + const QPoint &patternOrigin, + const QRect &viewRect, + bool isPreview = false); +protected: + // Draws a checkerboard that looks static even if the view is scrollable. + void paintEventDrawCheckerBoard (QPainter *painter, + const QRect &viewRect); + + // Draws the selection and its border onto . + // is the part of the document given by . + void paintEventDrawSelection (QImage *destPixmap, const QRect &docRect); + + // Draws the parts of the selection's resize handles that are inside + // onto the view + void paintEventDrawSelectionResizeHandles (const QRect &clipRect); + void paintEventDrawTempImage (QImage *destPixmap, const QRect &docRect); + + // Draws the parts of the grid lines that are inside on + // . + void paintEventDrawGridLines (QPainter *painter, const QRect &viewRect); + + void paintEventDrawDoc_Unclipped (const QRect &viewRect); + virtual void paintEvent (QPaintEvent *e); + + +private: + struct kpViewPrivate *d; +}; + + +#endif // KP_VIEW_H diff --git a/kolourpaint/views/kpViewPrivate.h b/kolourpaint/views/kpViewPrivate.h new file mode 100644 index 00000000..3d3a82ad --- /dev/null +++ b/kolourpaint/views/kpViewPrivate.h @@ -0,0 +1,78 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpViewPrivate_H +#define kpViewPrivate_H + + +#include +#include +#include +#include + + +class kpDocument; +class kpToolToolBar; +class kpViewmanager; +class kpView; +class kpViewScrollableContainer; + + +struct kpViewPrivate +{ + // sync: kpView::paintEvent() + // + // Normally, these pointers must be valid while the kpView is alive. + // Generally, the objects they point to are deleted only after kpView + // is deleted. + // + // However, sometimes we use deleteLater() for the kpView. + // Before the delayed deletion is executed, those objects are deleted + // and then our paintEvent() is called. paintEvent() must therefore + // have some way of realising that those objects have been deleted so + // we use guardded pointers. + // + // For more details, see SVN commit: + // "r385274 | dang | 2005-02-02 22:08:27 +1100 (Wed, 02 Feb 2005) | 21 lines". + QPointer document; + QPointer toolToolBar; + QPointer viewManager; + QPointer buddyView; + QPointer scrollableContainer; + + int hzoom, vzoom; + QPoint origin; + bool showGrid; + bool isBuddyViewScrollableContainerRectangleShown; + QRect buddyViewScrollableContainerRectangle; + + QRegion queuedUpdateArea; +}; + + +#endif // kpViewPrivate_H diff --git a/kolourpaint/views/kpView_Events.cpp b/kolourpaint/views/kpView_Events.cpp new file mode 100644 index 00000000..95325615 --- /dev/null +++ b/kolourpaint/views/kpView_Events.cpp @@ -0,0 +1,272 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2005 Kazuki Ohta + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_VIEW 0 +#define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0) + + +#include +#include + +#if DEBUG_KP_VIEW +#include +#endif + +#include +#include + +#include + +#include + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpView::mouseMoveEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView(" << objectName () << ")::mouseMoveEvent (" + << e->x () << "," << e->y () << ")" + << endl; +#endif + + // TODO: This is wrong if you leaveEvent the mainView by mouseMoving on the + // mainView, landing on top of the thumbnailView cleverly put on top + // of the mainView. + setHasMouse (rect ().contains (e->pos ())); + + if (tool ()) + tool ()->mouseMoveEvent (e); + + e->accept (); +} + +// protected virtual [base QWidget] +void kpView::mousePressEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView(" << objectName () << ")::mousePressEvent (" + << e->x () << "," << e->y () << ")" + << endl; +#endif + + setHasMouse (true); + + if (tool ()) + tool ()->mousePressEvent (e); + + e->accept (); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpView::mouseReleaseEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView(" << objectName () << ")::mouseReleaseEvent (" + << e->x () << "," << e->y () << ")" + << endl; +#endif + + setHasMouse (rect ().contains (e->pos ())); + + if (tool ()) + tool ()->mouseReleaseEvent (e); + + e->accept (); +} + +//--------------------------------------------------------------------- + +// public virtual [base QWidget] +void kpView::wheelEvent (QWheelEvent *e) +{ + if (tool ()) + tool ()->wheelEvent (e); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpView::keyPressEvent (QKeyEvent *e) +{ +#if DEBUG_KP_VIEW + kDebug () << "kpView(" << objectName () << ")::keyPressEvent()" << e->text(); +#endif + + if (tool ()) + tool ()->keyPressEvent (e); + + e->accept (); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpView::keyReleaseEvent (QKeyEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView(" << objectName () << ")::keyReleaseEvent()"; +#endif + + if (tool ()) + tool ()->keyReleaseEvent (e); + + e->accept (); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpView::inputMethodEvent (QInputMethodEvent *e) +{ +#if DEBUG_KP_VIEW && 1 + kDebug () << "kpView(" << objectName () << ")::inputMethodEvent()"; +#endif + + if (tool ()) + tool ()->inputMethodEvent (e); + e->accept (); +} + +// protected virtual [base QWidget] +bool kpView::event (QEvent *e) +{ +#if DEBUG_KP_VIEW + kDebug () << "kpView::event() invoking kpTool::event()"; +#endif + if (tool () && tool ()->viewEvent (e)) + { + #if DEBUG_KP_VIEW + kDebug () << "\tkpView::event() - tool said eat event, ret true"; + #endif + return true; + } + +#if DEBUG_KP_VIEW + kDebug () << "\tkpView::event() - no tool or said false, call QWidget::event()"; +#endif + return QWidget::event (e); +} + + +// protected virtual [base QWidget] +void kpView::focusInEvent (QFocusEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView(" << objectName () << ")::focusInEvent()"; +#endif + if (tool ()) + tool ()->focusInEvent (e); +} + +// protected virtual [base QWidget] +void kpView::focusOutEvent (QFocusEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView(" << objectName () << ")::focusOutEvent()"; +#endif + if (tool ()) + tool ()->focusOutEvent (e); +} + + +// protected virtual [base QWidget] +void kpView::enterEvent (QEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView(" << objectName () << ")::enterEvent()"; +#endif + + // Don't call setHasMouse(true) as it displays the brush cursor (if + // active) when dragging open a menu and then dragging + // past the extents of the menu due to Qt sending us an EnterEvent. + // We're already covered by MouseMoveEvent anyway. + // + // But disabling this causes a more serious problem: RMB on a text + // box and Esc. We have no other reliable way to determine if the + // mouse is still above the view (user could have moved mouse out + // while RMB menu was up) and hence the cursor is not updated. + setHasMouse (true); + + if (tool ()) + tool ()->enterEvent (e); +} + +// protected virtual [base QWidget] +void kpView::leaveEvent (QEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kDebug () << "kpView(" << objectName () << ")::leaveEvent()"; +#endif + + setHasMouse (false); + if (tool ()) + tool ()->leaveEvent (e); +} + + +// protected virtual [base QWidget] +void kpView::dragEnterEvent (QDragEnterEvent *) +{ +#if DEBUG_KP_VIEW && 1 + kDebug () << "kpView(" << objectName () << ")::dragEnterEvent()"; +#endif + + setHasMouse (true); +} + +// protected virtual [base QWidget] +void kpView::dragLeaveEvent (QDragLeaveEvent *) +{ +#if DEBUG_KP_VIEW && 1 + kDebug () << "kpView(" << objectName () << ")::dragLeaveEvent"; +#endif + + setHasMouse (false); +} + + +// protected virtual [base QWidget] +void kpView::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_VIEW && 1 + kDebug () << "kpView(" << objectName () << ")::resizeEvent(" + << e->size () + << " vs actual=" << size () + << ") old=" << e->oldSize () << endl; +#endif + + QWidget::resizeEvent (e); + + emit sizeChanged (width (), height ()); + emit sizeChanged (size ()); +} diff --git a/kolourpaint/views/kpView_Paint.cpp b/kolourpaint/views/kpView_Paint.cpp new file mode 100644 index 00000000..55468095 --- /dev/null +++ b/kolourpaint/views/kpView_Paint.cpp @@ -0,0 +1,628 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_VIEW 0 +#define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0) + + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +// protected +QRect kpView::paintEventGetDocRect (const QRect &viewRect) const +{ +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "kpView::paintEventGetDocRect(" << viewRect << ")"; +#endif + + QRect docRect; + + // From the "we aren't sure whether to round up or round down" department: + + if (zoomLevelX () < 100 || zoomLevelY () < 100) + docRect = transformViewToDoc (viewRect); + else + { + // think of a grid - you need to fully cover the zoomed-in pixels + // when docRect is zoomed back to the view later + docRect = QRect (transformViewToDoc (viewRect.topLeft ()), // round down + transformViewToDoc (viewRect.bottomRight ())); // round down + } + + if (zoomLevelX () % 100 || zoomLevelY () % 100) + { + // at least round up the bottom-right point and deal with matrix weirdness: + // - helpful because it ensures we at least cover the required area + // at e.g. 67% or 573% + docRect.setBottomRight (docRect.bottomRight () + QPoint (2, 2)); + } + +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tdocRect=" << docRect; +#endif + kpDocument *doc = document (); + if (doc) + { + docRect = docRect.intersect (doc->rect ()); + #if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tintersect with doc=" << docRect; + #endif + } + + return docRect; +} + +//--------------------------------------------------------------------- + +// public static +void kpView::drawTransparentBackground (QPainter *painter, + const QPoint &patternOrigin, + const QRect &viewRect, + bool isPreview) +{ +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "kpView::drawTransparentBackground() patternOrigin=" + << patternOrigin + << " viewRect=" << viewRect + << " isPreview=" << isPreview + << endl; +#endif + + const int cellSize = !isPreview ? 16 : 10; + + // TODO: % is unpredictable with negatives. + + int starty = viewRect.y (); + if ((starty - patternOrigin.y ()) % cellSize) + starty -= ((starty - patternOrigin.y ()) % cellSize); + + int startx = viewRect.x (); + if ((startx - patternOrigin.x ()) % cellSize) + startx -= ((startx - patternOrigin.x ()) % cellSize); + +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tstartXY=" << QPoint (startx, starty); +#endif + + painter->save (); + + // Clip to as we may draw outside it on all sides. + painter->setClipRect (viewRect, Qt::IntersectClip/*honor existing clip*/); + + for (int y = starty; y <= viewRect.bottom (); y += cellSize) + { + for (int x = startx; x <= viewRect.right (); x += cellSize) + { + bool parity = ((x - patternOrigin.x ()) / cellSize + + (y - patternOrigin.y ()) / cellSize) % 2; + QColor col; + + if (parity) + { + if (!isPreview) + col = QColor (213, 213, 213); + else + col = QColor (224, 224, 224); + } + else + col = Qt::white; + + painter->fillRect (x, y, cellSize, cellSize, col); + } + } + + painter->restore (); +} + +//--------------------------------------------------------------------- + +// protected +void kpView::paintEventDrawCheckerBoard (QPainter *painter, const QRect &viewRect) +{ +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "kpView(" << objectName () + << ")::paintEventDrawCheckerBoard(viewRect=" << viewRect + << ") origin=" << origin () << endl; +#endif + + kpDocument *doc = document (); + if (!doc) + return; + + QPoint patternOrigin = origin (); + + if (scrollableContainer ()) + { + #if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tscrollableContainer: contents[XY]=" + << QPoint (scrollableContainer ()->horizontalScrollBar()->value (), + scrollableContainer ()->verticalScrollBar()->value ()) + << endl; + #endif + // Make checkerboard appear static relative to the scroll view. + // This makes it more obvious that any visible bits of the + // checkboard represent transparent pixels and not gray and white + // squares. + patternOrigin = QPoint (scrollableContainer ()->horizontalScrollBar()->value(), + scrollableContainer ()->verticalScrollBar()->value()); + #if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\t\tpatternOrigin=" << patternOrigin; + #endif + } + + // TODO: this static business doesn't work yet + patternOrigin = QPoint (0, 0); + + drawTransparentBackground (painter, patternOrigin, viewRect); +} + +//--------------------------------------------------------------------- + +// protected +void kpView::paintEventDrawSelection (QImage *destPixmap, const QRect &docRect) +{ +#if DEBUG_KP_VIEW_RENDERER && 1 || 0 + kDebug () << "kpView::paintEventDrawSelection() docRect=" << docRect; +#endif + + kpDocument *doc = document (); + if (!doc) + { + #if DEBUG_KP_VIEW_RENDERER && 1 || 0 + kDebug () << "\tno doc - abort"; + #endif + return; + } + + kpAbstractSelection *sel = doc->selection (); + if (!sel) + { + #if DEBUG_KP_VIEW_RENDERER && 1 || 0 + kDebug () << "\tno sel - abort"; + #endif + return; + } + + + // + // Draw selection pixmap (if there is one) + // +#if DEBUG_KP_VIEW_RENDERER && 1 || 0 + kDebug () << "\tdraw sel pixmap @ " << sel->topLeft (); +#endif + sel->paint (destPixmap, docRect); + + + // + // Draw selection border + // + + kpViewManager *vm = viewManager (); +#if DEBUG_KP_VIEW_RENDERER && 1 || 0 + kDebug () << "\tsel border visible=" + << vm->selectionBorderVisible () + << endl; +#endif + if (vm->selectionBorderVisible ()) + { + sel->paintBorder (destPixmap, docRect, vm->selectionBorderFinished ()); + } + + + // + // Draw text cursor + // + + // TODO: It would be nice to display the text cursor even if it's not + // within the text box (this can happen if the text box is too + // small for the text it contains). + // + // However, too much selection repaint code assumes that it + // only paints inside its kpAbstractSelection::boundingRect(). + kpTextSelection *textSel = dynamic_cast (sel); + if (textSel && + vm->textCursorEnabled () && + (vm->textCursorBlinkState () || + // For the current main window: + // As long as _any_ view has focus, blink _all_ views not just the + // one with focus. + !vm->hasAViewWithFocus ())) // sync: call will break when vm is not held by 1 mainWindow + { + QRect rect = vm->textCursorRect (); + rect = rect.intersect (textSel->textAreaRect ()); + if (!rect.isEmpty ()) + { + kpPixmapFX::fillRect(destPixmap, + rect.x () - docRect.x (), rect.y () - docRect.y (), + rect.width (), rect.height (), + kpColor::LightGray, kpColor::DarkGray); + } + } +} + +//--------------------------------------------------------------------- + +// protected +void kpView::paintEventDrawSelectionResizeHandles (const QRect &clipRect) +{ +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "kpView::paintEventDrawSelectionResizeHandles(" + << clipRect << ")" << endl; +#endif + + if (!selectionLargeEnoughToHaveResizeHandles ()) + { + #if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tsel not large enough to have resize handles"; + #endif + return; + } + + kpViewManager *vm = viewManager (); + if (!vm || !vm->selectionBorderVisible () || !vm->selectionBorderFinished ()) + { + #if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tsel border not visible or not finished"; + #endif + + return; + } + + const QRect selViewRect = selectionViewRect (); +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tselViewRect=" << selViewRect; +#endif + if (!selViewRect.intersects (clipRect)) + { + #if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tdoesn't intersect viewRect"; + #endif + return; + } + + QRegion selResizeHandlesRegion = selectionResizeHandlesViewRegion (true/*for renderer*/); +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tsel resize handles view region=" + << selResizeHandlesRegion << endl; +#endif + + QPainter painter(this); + painter.setPen(Qt::black); + painter.setBrush(Qt::cyan); + + foreach (const QRect &r, selResizeHandlesRegion.rects()) + painter.drawRect(r); +} + +//--------------------------------------------------------------------- + +// protected +void kpView::paintEventDrawTempImage (QImage *destPixmap, const QRect &docRect) +{ + kpViewManager *vm = viewManager (); + if (!vm) + return; + + const kpTempImage *tpi = vm->tempImage (); +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "kpView::paintEventDrawTempImage() tempImage=" + << tpi + << " isVisible=" + << (tpi ? tpi->isVisible (vm) : false) + << endl; +#endif + + if (!tpi || !tpi->isVisible (vm)) + return; + + tpi->paint (destPixmap, docRect); +} + +//--------------------------------------------------------------------- + +// protected +void kpView::paintEventDrawGridLines (QPainter *painter, const QRect &viewRect) +{ + int hzoomMultiple = zoomLevelX () / 100; + int vzoomMultiple = zoomLevelY () / 100; + + painter->setPen(Qt::gray); + + // horizontal lines + int starty = viewRect.top(); + if (starty % vzoomMultiple) + starty = (starty + vzoomMultiple) / vzoomMultiple * vzoomMultiple; + + for (int y = starty; y <= viewRect.bottom(); y += vzoomMultiple) + painter->drawLine(viewRect.left(), y, viewRect.right(), y); + + // vertical lines + int startx = viewRect.left(); + if (startx % hzoomMultiple) + startx = (startx + hzoomMultiple) / hzoomMultiple * hzoomMultiple; + + for (int x = startx; x <= viewRect.right(); x += hzoomMultiple) + painter->drawLine(x, viewRect.top (), x, viewRect.bottom()); +} + +//--------------------------------------------------------------------- + +// This is called "_Unclipped" because it may draw outside of +// . +// +// There are 2 reasons for doing so: +// +// A. If, for instance: +// +// 1. = QRect (0, 0, 2, 3) [top-left of the view] +// 2. zoomLevelX() == 800 +// 3. zoomLevelY() == 800 +// +// Then, the local variable will be QRect (0, 0, 1, 1). +// When the part of the document corresponding to +// (a single document pixel) is drawn with QPainter::scale(), the +// view rectangle QRect (0, 0, 7, 7) will be overwritten due to the +// 8x zoom. This view rectangle is bigger than . +// +// We can't use QPainter::setClipRect() since it is buggy in Qt 4.3.1 +// and clips too many pixels when used in combination with scale() +// [qt-bugs@trolltech.com issue N181038]. ==> MK 10.2.2011 - fixed since Qt-4.4.4 +// +// B. paintEventGetDocRect() may, by design, return a larger document +// rectangle than what corresponds to, if the zoom levels +// are not perfectly divisible by 100. +// +// This over-drawing is dangerous -- see the comments in paintEvent(). +// This over-drawing is only safe from Qt's perspective since Qt +// automatically clips all drawing in paintEvent() (which calls us) to +// QPaintEvent::region(). +void kpView::paintEventDrawDoc_Unclipped (const QRect &viewRect) +{ +#if DEBUG_KP_VIEW_RENDERER + QTime timer; + timer.start (); + kDebug () << "\tviewRect=" << viewRect; +#endif + + kpViewManager *vm = viewManager (); + const kpDocument *doc = document (); + + Q_ASSERT (vm); + Q_ASSERT (doc); + + if (viewRect.isEmpty ()) + return; + + QRect docRect = paintEventGetDocRect (viewRect); + +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tdocRect=" << docRect; +#endif + + QPainter painter (this); + //painter.setCompositionMode(QPainter::CompositionMode_Source); + + QImage docPixmap; + bool tempImageWillBeRendered = false; + + // LOTODO: I think being empty would be a bug. + if (!docRect.isEmpty ()) + { + docPixmap = doc->getImageAt (docRect); + + #if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tdocPixmap.hasAlphaChannel()=" + << docPixmap.hasAlphaChannel () << endl; + #endif + + tempImageWillBeRendered = + (!doc->selection () && + vm->tempImage () && + vm->tempImage ()->isVisible (vm) && + docRect.intersects (vm->tempImage ()->rect ())); + + #if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\ttempImageWillBeRendered=" << tempImageWillBeRendered + << " (sel=" << doc->selection () + << " tempImage=" << vm->tempImage () + << " tempImage.isVisible=" << (vm->tempImage () ? vm->tempImage ()->isVisible (vm) : false) + << " docRect.intersects(tempImage.rect)=" << (vm->tempImage () ? docRect.intersects (vm->tempImage ()->rect ()) : false) + << ")" + << endl; + #endif + } + + + // + // Draw checkboard for transparent images and/or views with borders + // + + if (docPixmap.hasAlphaChannel() || + (tempImageWillBeRendered && vm->tempImage ()->paintMayAddMask ())) + { + paintEventDrawCheckerBoard (&painter, viewRect); + } + + if (!docRect.isEmpty ()) + { + // + // Draw docPixmap + tempImage + // + + if (doc->selection ()) + { + paintEventDrawSelection (&docPixmap, docRect); + } + else if (tempImageWillBeRendered) + { + paintEventDrawTempImage (&docPixmap, docRect); + } + + #if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\torigin=" << origin (); + #endif + // Blit scaled version of docPixmap + tempImage. + #if DEBUG_KP_VIEW_RENDERER && 1 + QTime scaleTimer; scaleTimer.start (); + #endif + // This is the only troublesome part of the method that draws unclipped. + painter.translate (origin ().x (), origin ().y ()); + painter.scale (double (zoomLevelX ()) / 100.0, + double (zoomLevelY ()) / 100.0); + painter.drawImage (docRect, docPixmap); + //painter.resetMatrix (); // back to 1-1 scaling + #if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tscale time=" << scaleTimer.elapsed (); + #endif + + } // if (!docRect.isEmpty ()) { + +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tdrawDocRect done in: " << timer.restart () << "ms"; +#endif +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpView::paintEvent (QPaintEvent *e) +{ + // sync: kpViewPrivate + // WARNING: document(), viewManager() and friends might be 0 in this method. + // TODO: I'm not 100% convinced that we always check if their friends are 0. + +#if DEBUG_KP_VIEW_RENDERER && 1 + QTime timer; + timer.start (); +#endif + + kpViewManager *vm = viewManager (); + +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "kpView(" << objectName () << ")::paintEvent() vm=" << (bool) vm + << " queueUpdates=" << (vm && vm->queueUpdates ()) + << " fastUpdates=" << (vm && vm->fastUpdates ()) + << " viewRect=" << e->rect () + << " topLeft=" << QPoint (x (), y ()) + << endl; +#endif + + if (!vm) + return; + + if (vm->queueUpdates ()) + { + // OPT: if this update was due to the document, + // use document coordinates (in case of a zoom change in + // which view coordinates become out of date) + addToQueuedArea (e->region ()); + return; + } + + kpDocument *doc = document (); + if (!doc) + return; + + + // It seems that e->region() is already clipped by Qt to the visible + // part of the view (which could be quite small inside a scrollview). + QRegion viewRegion = e->region (); + QVector rects = viewRegion.rects (); +#if DEBUG_KP_VIEW_RENDERER + kDebug () << "\t#rects = " << rects.count (); +#endif + + // Draw all of the requested regions of the document _before_ drawing + // the grid lines, buddy rectangle and selection resize handles. + // This ordering is important since paintEventDrawDoc_Unclipped() + // may draw outside of the view rectangle passed to it. + // + // To illustrate this, suppose we changed each iteration of the loop + // to call paintEventDrawDoc_Unclipped() _and_ then, + // paintEventDrawGridLines(). If there are 2 or more iterations of this + // loop, paintEventDrawDoc_Unclipped() in one iteration may draw over + // parts of nearby grid lines (which were drawn in a previous iteration) + // with document pixels. Those grid line parts are probably not going to + // be redrawn, so will appear to be missing. + foreach (const QRect &r, rects) + { + paintEventDrawDoc_Unclipped (r); + } + + // + // Draw Grid Lines + // + + if ( isGridShown() ) + { + QPainter painter(this); + foreach (const QRect &r, rects) + paintEventDrawGridLines(&painter, r); + } + + const QRect r = buddyViewScrollableContainerRectangle(); + if ( !r.isEmpty() ) + { + QPainter painter(this); + + painter.setPen(QPen(Qt::lightGray, 1/*width*/, Qt::DotLine)); + painter.setBackground(Qt::darkGray); + painter.setBackgroundMode(Qt::OpaqueMode); + + painter.drawRect(r.x(), r.y(), r.width() - 1, r.height() - 1); + } + + if (doc->selection ()) + { + // Draw resize handles on top of possible grid lines + paintEventDrawSelectionResizeHandles (e->rect ()); + } + +#if DEBUG_KP_VIEW_RENDERER && 1 + kDebug () << "\tall done in: " << timer.restart () << "ms"; +#endif +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/views/kpView_Selections.cpp b/kolourpaint/views/kpView_Selections.cpp new file mode 100644 index 00000000..e865135e --- /dev/null +++ b/kolourpaint/views/kpView_Selections.cpp @@ -0,0 +1,362 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_VIEW 0 +#define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0) + + +#include +#include + +#include +#include +#include + + +// public +QRect kpView::selectionViewRect () const +{ + return selection () ? + transformDocToView (selection ()->boundingRect ()) : + QRect (); + +} + + +// public +QPoint kpView::mouseViewPointRelativeToSelection (const QPoint &viewPoint) const +{ + if (!selection ()) + return KP_INVALID_POINT; + + return mouseViewPoint (viewPoint) - transformDocToView (selection ()->topLeft ()); +} + +// public +bool kpView::mouseOnSelection (const QPoint &viewPoint) const +{ + const QRect selViewRect = selectionViewRect (); + if (!selViewRect.isValid ()) + return false; + + return selViewRect.contains (mouseViewPoint (viewPoint)); +} + + +// public +int kpView::textSelectionMoveBorderAtomicSize () const +{ + if (!textSelection ()) + return 0; + + return qMax (4, zoomLevelX () / 100); +} + +// public +bool kpView::mouseOnSelectionToMove (const QPoint &viewPoint) const +{ + if (!mouseOnSelection (viewPoint)) + return false; + + if (!textSelection ()) + return true; + + if (mouseOnSelectionResizeHandle (viewPoint)) + return false; + + + const QPoint viewPointRelSel = mouseViewPointRelativeToSelection (viewPoint); + + // Middle point should always be selectable + const QPoint selCenterDocPoint = selection ()->boundingRect ().center (); + if (tool () && + tool ()->calculateCurrentPoint () == selCenterDocPoint) + { + return false; + } + + + const int atomicSize = textSelectionMoveBorderAtomicSize (); + const QRect selViewRect = selectionViewRect (); + + return (viewPointRelSel.x () < atomicSize || + viewPointRelSel.x () >= selViewRect.width () - atomicSize || + viewPointRelSel.y () < atomicSize || + viewPointRelSel.y () >= selViewRect.height () - atomicSize); +} + +//--------------------------------------------------------------------- + +// protected +bool kpView::selectionLargeEnoughToHaveResizeHandlesIfAtomicSize (int atomicSize) const +{ + if (!selection ()) + return false; + + const QRect selViewRect = selectionViewRect (); + + return (selViewRect.width () >= atomicSize * 5 || + selViewRect.height () >= atomicSize * 5); +} + +//--------------------------------------------------------------------- + +// public +int kpView::selectionResizeHandleAtomicSize () const +{ + int atomicSize = qMin (13, qMax (9, zoomLevelX () / 100)); + while (atomicSize > 0 && + !selectionLargeEnoughToHaveResizeHandlesIfAtomicSize (atomicSize)) + { + atomicSize--; + } + + return atomicSize; +} + +//--------------------------------------------------------------------- + +// public +bool kpView::selectionLargeEnoughToHaveResizeHandles () const +{ + return (selectionResizeHandleAtomicSize () > 0); +} + +//--------------------------------------------------------------------- + +// public +QRegion kpView::selectionResizeHandlesViewRegion (bool forRenderer) const +{ + const int atomicLength = selectionResizeHandleAtomicSize (); + if (atomicLength <= 0) + return QRegion (); + + + // HACK: At low zoom (e.g. 100%), resize handles will probably be too + // big and overlap text / cursor / too much of selection. + // + // So limit the _visual_ size of handles at low zoom. The + // handles' grab area remains the same for usability; so yes, + // there are a few pixels that don't look grabable but they are. + // + // The real solution is to be able to partially render the + // handles outside of the selection view rect. If not possible, + // at least for text boxes, render text on top of handles. + int normalAtomicLength = atomicLength; + int vertEdgeAtomicLength = atomicLength; + if (forRenderer && selection ()) + { + if (zoomLevelX () <= 150) + { + if (normalAtomicLength > 1) + normalAtomicLength--; + + if (vertEdgeAtomicLength > 1) + vertEdgeAtomicLength--; + } + + // 1 line of text? + if (textSelection () && textSelection ()->textLines ().size () == 1) + { + if (zoomLevelX () <= 150) + vertEdgeAtomicLength = qMin (vertEdgeAtomicLength, qMax (2, zoomLevelX () / 100)); + else if (zoomLevelX () <= 250) + vertEdgeAtomicLength = qMin (vertEdgeAtomicLength, qMax (3, zoomLevelX () / 100)); + } + } + + + const QRect selViewRect = selectionViewRect (); + QRegion ret; + + // top left + ret += QRect(0, 0, normalAtomicLength, normalAtomicLength); + + // top middle + ret += QRect((selViewRect.width() - normalAtomicLength) / 2, 0, + normalAtomicLength, normalAtomicLength); + + // top right + ret += QRect(selViewRect.width() - normalAtomicLength - 1, 0, + normalAtomicLength, normalAtomicLength); + + // left middle + ret += QRect(0, (selViewRect.height() - vertEdgeAtomicLength) / 2, + vertEdgeAtomicLength, vertEdgeAtomicLength); + + // right middle + ret += QRect(selViewRect.width() - vertEdgeAtomicLength - 1, (selViewRect.height() - vertEdgeAtomicLength) / 2, + vertEdgeAtomicLength, vertEdgeAtomicLength); + + // bottom left + ret += QRect(0, selViewRect.height() - normalAtomicLength - 1, + normalAtomicLength, normalAtomicLength); + + // bottom middle + ret += QRect((selViewRect.width() - normalAtomicLength) / 2, selViewRect.height() - normalAtomicLength - 1, + normalAtomicLength, normalAtomicLength); + + // bottom right + ret += QRect(selViewRect.width() - normalAtomicLength - 1, selViewRect.height() - normalAtomicLength - 1, + normalAtomicLength, normalAtomicLength); + + ret.translate (selViewRect.x (), selViewRect.y ()); + ret = ret.intersect (selViewRect); + + return ret; +} + +//--------------------------------------------------------------------- + +// public +// REFACTOR: use QFlags as the return type for better type safety. +int kpView::mouseOnSelectionResizeHandle (const QPoint &viewPoint) const +{ +#if DEBUG_KP_VIEW + kDebug () << "kpView::mouseOnSelectionResizeHandle(viewPoint=" + << viewPoint << ")" << endl; +#endif + + if (!mouseOnSelection (viewPoint)) + { + #if DEBUG_KP_VIEW + kDebug () << "\tmouse not on sel"; + #endif + return 0; + } + + + const QRect selViewRect = selectionViewRect (); +#if DEBUG_KP_VIEW + kDebug () << "\tselViewRect=" << selViewRect; +#endif + + + const int atomicLength = selectionResizeHandleAtomicSize (); +#if DEBUG_KP_VIEW + kDebug () << "\tatomicLength=" << atomicLength; +#endif + + if (atomicLength <= 0) + { + #if DEBUG_KP_VIEW + kDebug () << "\tsel not large enough to have resize handles"; + #endif + // Want to make it possible to move a small selection + return 0; + } + + + const QPoint viewPointRelSel = mouseViewPointRelativeToSelection (viewPoint); +#if DEBUG_KP_VIEW + kDebug () << "\tviewPointRelSel=" << viewPointRelSel; +#endif + + +#define LOCAL_POINT_IN_BOX_AT(x,y) \ + QRect ((x), (y), atomicLength, atomicLength).contains (viewPointRelSel) + + // Favour the bottom & right and the corners. + if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, + selViewRect.height () - atomicLength)) + { + return kpView::Bottom | kpView::Right; + } + else if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, 0)) + { + return kpView::Top | kpView::Right; + } + else if (LOCAL_POINT_IN_BOX_AT (0, selViewRect.height () - atomicLength)) + { + return kpView::Bottom | kpView::Left; + } + else if (LOCAL_POINT_IN_BOX_AT (0, 0)) + { + return kpView::Top | kpView::Left; + } + else if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, + (selViewRect.height () - atomicLength) / 2)) + { + return kpView::Right; + } + else if (LOCAL_POINT_IN_BOX_AT ((selViewRect.width () - atomicLength) / 2, + selViewRect.height () - atomicLength)) + { + return kpView::Bottom; + } + else if (LOCAL_POINT_IN_BOX_AT ((selViewRect.width () - atomicLength) / 2, 0)) + { + return kpView::Top; + } + else if (LOCAL_POINT_IN_BOX_AT (0, (selViewRect.height () - atomicLength) / 2)) + { + return kpView::Left; + } + else + { + #if DEBUG_KP_VIEW + kDebug () << "\tnot on sel resize handle"; + #endif + return 0; + } +#undef LOCAL_POINT_IN_BOX_AT +} + +// public +bool kpView::mouseOnSelectionToSelectText (const QPoint &viewPoint) const +{ +#if DEBUG_KP_VIEW + kDebug () << "kpView::mouseOnSelectionToSelectText(viewPoint=" + << viewPoint << ")" << endl; +#endif + + if (!mouseOnSelection (viewPoint)) + { + #if DEBUG_KP_VIEW + kDebug () << "\tmouse non on sel"; + #endif + return false; + } + + if (!textSelection ()) + { + #if DEBUG_KP_VIEW + kDebug () << "\tsel not text"; + #endif + return false; + } + +#if DEBUG_KP_VIEW + kDebug () << "\tmouse on sel: to move=" << mouseOnSelectionToMove () + << " to resize=" << mouseOnSelectionResizeHandle () + << endl; +#endif + + return (!mouseOnSelectionToMove (viewPoint) && + !mouseOnSelectionResizeHandle (viewPoint)); +} diff --git a/kolourpaint/views/kpZoomedThumbnailView.cpp b/kolourpaint/views/kpZoomedThumbnailView.cpp new file mode 100644 index 00000000..cd76d595 --- /dev/null +++ b/kolourpaint/views/kpZoomedThumbnailView.cpp @@ -0,0 +1,140 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_ZOOMED_THUMBNAIL_VIEW 0 + + +#include + +#include +#include + +#include +#include + + +kpZoomedThumbnailView::kpZoomedThumbnailView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent ) + + : kpThumbnailView (document, toolToolBar, viewManager, + buddyView, + scrollableContainer, + parent) +{ + // Call to virtual function - this is why the class is sealed + adjustToEnvironment (); +} + + +kpZoomedThumbnailView::~kpZoomedThumbnailView () +{ +} + + +// public virtual [base kpThumbnailView] +QString kpZoomedThumbnailView::caption () const +{ + return i18n ("%1% - Thumbnail", zoomLevelX ()); +} + + +// public slot virtual [base kpView] +void kpZoomedThumbnailView::adjustToEnvironment () +{ +#if DEBUG_KP_ZOOMED_THUMBNAIL_VIEW + kDebug () << "kpZoomedThumbnailView(" << name () + << ")::adjustToEnvironment()" + << " width=" << width () + << " height=" << height () + << endl; +#endif + + if (!document ()) + return; + +#if DEBUG_KP_ZOOMED_THUMBNAIL_VIEW + kDebug () << "\tdoc: width=" << document ()->width () + << " height=" << document ()->height () + << endl; +#endif + + if (document ()->width () <= 0 || document ()->height () <= 0) + { + kError () << "kpZoomedThumbnailView::adjustToEnvironment() doc:" + << " width=" << document ()->width () + << " height=" << document ()->height () + << endl; + return; + } + + + int hzoom = qMax (1, width () * 100 / document ()->width ()); + int vzoom = qMax (1, height () * 100 / document ()->height ()); + + // keep aspect ratio + if (hzoom < vzoom) + vzoom = hzoom; + else + hzoom = vzoom; + +#if DEBUG_KP_ZOOMED_THUMBNAIL_VIEW && 1 + kDebug () << "\tproposed zoom=" << hzoom; +#endif + if (hzoom > 100 || vzoom > 100) + { + #if DEBUG_KP_ZOOMED_THUMBNAIL_VIEW && 1 + kDebug () << "\twon't magnify - setting zoom to 100%"; + #endif + hzoom = 100, vzoom = 100; + } + + + if (viewManager ()) + viewManager ()->setQueueUpdates (); + + { + setZoomLevel (hzoom, vzoom); + + setOrigin (QPoint ((width () - zoomedDocWidth ()) / 2, + (height () - zoomedDocHeight ()) / 2)); + setMaskToCoverDocument (); + + if (viewManager ()) + viewManager ()->updateView (this); + } + + if (viewManager ()) + viewManager ()->restoreQueueUpdates (); +} + + +#include diff --git a/kolourpaint/views/kpZoomedThumbnailView.h b/kolourpaint/views/kpZoomedThumbnailView.h new file mode 100644 index 00000000..30f67bd4 --- /dev/null +++ b/kolourpaint/views/kpZoomedThumbnailView.h @@ -0,0 +1,95 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_ZOOMED_THUMBNAIL_VIEW_H +#define KP_ZOOMED_THUMBNAIL_VIEW_H + + +#include + + +/** + * @short Zoomed thumbnail view of a document. + * + * This is a zoomed thumbnail view of a document. Unlike @ref kpZoomedView, + * it never resizes itself. Instead, it changes its zoom level to + * accommodate the display of entire document in the view, while + * maintaining aspect. + * + * Do not call setZoomLevel() nor setOrigin(). + * + * This class is sealed. Do not derive from it. + * + * @author Clarence Dang + */ +/*sealed*/ class kpZoomedThumbnailView : public kpThumbnailView +{ +Q_OBJECT + +public: + /** + * Constructs a zoomed thumbnail view. + */ + kpZoomedThumbnailView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent); + + /** + * Destructs a zoomed thumbnail view. + */ + virtual ~kpZoomedThumbnailView (); + + + /** + * Implements @ref kpThumbnailView. + */ + QString caption () const; + + +public slots: + /** + * Changes its zoom level to accommodate the display of entire document + * in the view. It maintains aspect by changing the origin and mask. + * + * Call this if the size of the document changes. + * Already called by @ref kpThumbnailView resizeEvent(). + * + * Implements @ref kpView. + */ + virtual void adjustToEnvironment (); + + +private: + struct kpZoomedThumbnailViewPrivate *d; +}; + + +#endif // KP_ZOOMED_THUMBNAIL_VIEW_H diff --git a/kolourpaint/views/kpZoomedView.cpp b/kolourpaint/views/kpZoomedView.cpp new file mode 100644 index 00000000..b5d800e8 --- /dev/null +++ b/kolourpaint/views/kpZoomedView.cpp @@ -0,0 +1,103 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_ZOOMED_VIEW 0 + + +#include + +#include + +#include +#include +#include + + +kpZoomedView::kpZoomedView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent) + + : kpView (document, toolToolBar, viewManager, + buddyView, + scrollableContainer, + parent) +{ + // Call to virtual function - this is why the class is sealed + adjustToEnvironment (); +} + +kpZoomedView::~kpZoomedView () +{ +} + + +// public virtual [base kpView] +void kpZoomedView::setZoomLevel (int hzoom, int vzoom) +{ +#if DEBUG_KP_ZOOMED_VIEW + kDebug () << "kpZoomedView(" << name () << ")::setZoomLevel(" + << hzoom << "," << vzoom << ")" << endl; +#endif + + if (viewManager ()) + viewManager ()->setQueueUpdates (); + + { + kpView::setZoomLevel (hzoom, vzoom); + + adjustToEnvironment (); + } + + if (viewManager ()) + viewManager ()->restoreQueueUpdates (); +} + + +// public slot virtual [base kpView] +void kpZoomedView::adjustToEnvironment () +{ +#if DEBUG_KP_ZOOMED_VIEW + kDebug () << "kpZoomedView(" << name () << ")::adjustToEnvironment()" + << " doc: width=" << document ()->width () + << " height=" << document ()->height () + << endl; +#endif + + if (document ()) + { + // TODO: use zoomedDocWidth() & zoomedDocHeight()? + resize ((int) transformDocToViewX (document ()->width ()), + (int) transformDocToViewY (document ()->height ())); + } +} + + +#include diff --git a/kolourpaint/views/kpZoomedView.h b/kolourpaint/views/kpZoomedView.h new file mode 100644 index 00000000..4bd9b307 --- /dev/null +++ b/kolourpaint/views/kpZoomedView.h @@ -0,0 +1,96 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_ZOOMED_VIEW_H +#define KP_ZOOMED_VIEW_H + + +#include + + +/** + * @short Zoomed view of a document. Suitable as an ordinary editing view. + * + * This is a zoomed view of a document. It resizes according to the size + * of the document and the zoom level. Do not manually call resize() for + * this reason. + * + * It is suitable as an ordinary editing view. + * + * Do not call setOrigin(). REFACTOR: this is bad class design - derived classes should only add functionality - not remove + * + * This class is sealed. Do not derive from it. REFACTOR: this is also bad class design + * + * @author Clarence Dang + */ +/*sealed*/ class kpZoomedView : public kpView +{ +Q_OBJECT + +public: + /** + * Constructs a zoomed view. + */ + kpZoomedView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent); + + /** + * Destructs an unzoomed view. + */ + virtual ~kpZoomedView (); + + + /** + * Extends @kpView. Calls adjustToEnvironment(). + */ + virtual void setZoomLevel (int hzoom, int vzoom); + + +public slots: + /** + * Resizes itself so that the entire document in the zoom level fits + * almost perfectly. + * + * Call this if the size of the document changes. + * Already called by setZoomLevel(). + * + * Implements @ref kpView. + */ + virtual void adjustToEnvironment (); + + +private: + struct kpZoomedViewPrivate *d; +}; + + +#endif // KP_ZOOMED_VIEW_H diff --git a/kolourpaint/views/manager/kpViewManager.cpp b/kolourpaint/views/manager/kpViewManager.cpp new file mode 100644 index 00000000..258a0c76 --- /dev/null +++ b/kolourpaint/views/manager/kpViewManager.cpp @@ -0,0 +1,365 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_VIEW_MANAGER 0 + + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +kpViewManager::kpViewManager (kpMainWindow *mainWindow) + : d (new kpViewManagerPrivate ()) +{ + Q_ASSERT (mainWindow); + + d->mainWindow = mainWindow; + + // d->views + d->viewUnderCursor = 0; + + // d->cursor + + d->tempImage = 0; + + d->selectionBorderVisible = false; + d->selectionBorderFinished = false; + + + d->textCursorBlinkTimer = 0; + + d->textCursorRow = -1; + d->textCursorCol = -1; + + d->textCursorBlinkState = true; + + + d->queueUpdatesCounter = d->fastUpdatesCounter = 0; + + d->inputMethodEnabled = false; +} + +//--------------------------------------------------------------------- + +kpViewManager::~kpViewManager () +{ + unregisterAllViews (); + + delete d->tempImage; + delete d; +} + +//--------------------------------------------------------------------- + +// private +kpDocument *kpViewManager::document () const +{ + return d->mainWindow->document (); +} + +//--------------------------------------------------------------------- + +// public +void kpViewManager::registerView (kpView *view) +{ +#if DEBUG_KP_VIEW_MANAGER && 1 + kDebug () << "kpViewManager::registerView (" << view << ")"; +#endif + Q_ASSERT (view); + Q_ASSERT (!d->views.contains (view)); + +#if DEBUG_KP_VIEW_MANAGER && 1 + kDebug () << "\tadded view"; +#endif + view->setCursor (d->cursor); + d->views.append (view); +} + +//--------------------------------------------------------------------- + +// public +void kpViewManager::unregisterView (kpView *view) +{ + Q_ASSERT (view); + Q_ASSERT (d->views.contains (view)); + + if (view == d->viewUnderCursor) + d->viewUnderCursor = 0; + + view->unsetCursor (); + d->views.removeAll (view); +} + +//--------------------------------------------------------------------- + +// public +void kpViewManager::unregisterAllViews () +{ + d->views.clear (); +} + +//--------------------------------------------------------------------- + +// public +kpView *kpViewManager::viewUnderCursor (bool usingQt) const +{ + if (!usingQt) + { + Q_ASSERT (!d->viewUnderCursor || d->views.contains (d->viewUnderCursor)); + return d->viewUnderCursor; + } + else + { + for (QLinkedList ::const_iterator it = d->views.begin (); + it != d->views.end (); + ++it) + { + if ((*it)->underMouse ()) + return (*it); + } + + return 0; + } +} + +//--------------------------------------------------------------------- + +// public +void kpViewManager::setViewUnderCursor (kpView *view) +{ +#if DEBUG_KP_VIEW_MANAGER && 1 + kDebug () << "kpViewManager::setViewUnderCursor (" + << (view ? view->objectName () : "(none)") << ")" + << " old=" << (d->viewUnderCursor ? d->viewUnderCursor->objectName () : "(none)") + << endl; +#endif + if (view == d->viewUnderCursor) + return; + + d->viewUnderCursor = view; + + if (d->viewUnderCursor) + d->viewUnderCursor->setAttribute (Qt::WA_InputMethodEnabled, d->inputMethodEnabled); + + if (!d->viewUnderCursor) + { + // Hide the brush if the mouse cursor just left the view. + if (d->tempImage && d->tempImage->isBrush ()) + { + #if DEBUG_KP_VIEW_MANAGER && 1 + kDebug () << "\thiding brush pixmap since cursor left view"; + #endif + updateViews (d->tempImage->rect ()); + } + } + else + { + if (d->mainWindow->tool ()) + { + #if DEBUG_KP_VIEW_MANAGER && 1 + kDebug () << "\tnotify tool that something changed below cursor"; + #endif + d->mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + } +} + +//--------------------------------------------------------------------- + +// public +bool kpViewManager::hasAViewWithFocus () const +{ + for (QLinkedList ::const_iterator it = d->views.begin (); + it != d->views.end (); + ++it) + { + if ((*it)->isActiveWindow ()) + return true; + } + + return false; +} + +//--------------------------------------------------------------------- + +// public +void kpViewManager::setCursor (const QCursor &cursor) +{ + for (QLinkedList ::const_iterator it = d->views.begin (); + it != d->views.end (); + ++it) + { + (*it)->setCursor (cursor); + } + + d->cursor = cursor; +} + +//--------------------------------------------------------------------- + +// public +void kpViewManager::unsetCursor () +{ + for (QLinkedList ::const_iterator it = d->views.begin (); + it != d->views.end (); + ++it) + { + (*it)->unsetCursor (); + } + + d->cursor = QCursor (); +} + +//--------------------------------------------------------------------- + +// public +const kpTempImage *kpViewManager::tempImage () const +{ + return d->tempImage; +} + +//--------------------------------------------------------------------- + +// public +void kpViewManager::setTempImage (const kpTempImage &tempImage) +{ +#if DEBUG_KP_VIEW_MANAGER + kDebug () << "kpViewManager::setTempImage(isBrush=" + << tempImage.isBrush () + << ",topLeft=" << tempImage.topLeft () + << ",image.rect=" << tempImage.image ().rect () + << ")" << endl; +#endif + + QRect oldRect; + + if (d->tempImage) + { + oldRect = d->tempImage->rect (); + delete d->tempImage; + d->tempImage = 0; + } + + d->tempImage = new kpTempImage (tempImage); + + setQueueUpdates (); + { + if (oldRect.isValid ()) + updateViews (oldRect); + updateViews (d->tempImage->rect ()); + } + restoreQueueUpdates (); +} + +//--------------------------------------------------------------------- + +// public +void kpViewManager::invalidateTempImage () +{ + if (!d->tempImage) + return; + + QRect oldRect = d->tempImage->rect (); + + delete d->tempImage; + d->tempImage = 0; + + updateViews (oldRect); +} + +//--------------------------------------------------------------------- + +// public +bool kpViewManager::selectionBorderVisible () const +{ + return d->selectionBorderVisible; +} + +//--------------------------------------------------------------------- + +// public +void kpViewManager::setSelectionBorderVisible (bool yes) +{ + if (d->selectionBorderVisible == yes) + return; + + d->selectionBorderVisible = yes; + + if (document ()->selection ()) + updateViews (document ()->selection ()->boundingRect ()); +} + +//--------------------------------------------------------------------- + +// public +bool kpViewManager::selectionBorderFinished () const +{ + return d->selectionBorderFinished; +} + +//--------------------------------------------------------------------- + +// public +void kpViewManager::setSelectionBorderFinished (bool yes) +{ + if (d->selectionBorderFinished == yes) + return; + + d->selectionBorderFinished = yes; + + if (document ()->selection ()) + updateViews (document ()->selection ()->boundingRect ()); +} + +//--------------------------------------------------------------------- + +void kpViewManager::setInputMethodEnabled (bool inputMethodEnabled) +{ + d->inputMethodEnabled = inputMethodEnabled; + if (d->viewUnderCursor) + d->viewUnderCursor->setAttribute (Qt::WA_InputMethodEnabled, inputMethodEnabled); +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/views/manager/kpViewManager.h b/kolourpaint/views/manager/kpViewManager.h new file mode 100644 index 00000000..56d20781 --- /dev/null +++ b/kolourpaint/views/manager/kpViewManager.h @@ -0,0 +1,256 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_VIEW_MANAGER_H +#define KP_VIEW_MANAGER_H + + +#include + + +class QCursor; +class QRegion; +class QRect; +class QTimer; + +class kpDocument; +class kpView; +class kpMainWindow; +class kpTempImage; + + +class kpViewManager : public QObject +{ +Q_OBJECT + +public: + kpViewManager (kpMainWindow *mainWindow); + ~kpViewManager (); + + +private: + kpDocument *document () const; + + +// +// Registering views +// + +public: + void registerView (kpView *view); + void unregisterView (kpView *view); + void unregisterAllViews (); + + +// +// View +// + +public: + kpView *viewUnderCursor (bool usingQt = false) const; + + // + // QWidget::hasMouse() is unreliable: + // + // "bool QWidget::hasMouse () const + // ... See the "underMouse" property for details. + // . + // . + // . + // bool underMouse + // ... This value is not updated properly during drag and drop operations." + // + // i.e. it's possible that hasMouse() returns false in a mousePressEvent()! + // + // This hack needs to be called from kpView so that viewUnderCursor() works + // as a reasonable replacement (although there is at least one case where + // it still won't work - just after a fake drag onto the view). + // + void setViewUnderCursor (kpView *view); + + +public: + // Returns whether at least 1 view has keyboard focus. + // A pointer is not returned to such a view because more than one can + // have focus at the same time (see QWidget::isActiveWindow()). + bool hasAViewWithFocus () const; + + +// +// Mouse Cursors +// + +public: + void setCursor (const QCursor &cursor); + void unsetCursor (); + + +// +// Temp Image +// + +public: + const kpTempImage *tempImage () const; + void setTempImage (const kpTempImage &tempImage); + void invalidateTempImage (); + + +// +// Selections +// + +public: + bool selectionBorderVisible () const; + void setSelectionBorderVisible (bool yes = true); + + bool selectionBorderFinished () const; + void setSelectionBorderFinished (bool yes = true); + + +// +// Text Cursor +// + +public: + // If you enable the text cursor, a timer will start ticking to update + // the text cursor. If no text selection is active, the update will + // be a NOP but the timer will still tick and textCursorEnabled() will + // still return true. + bool textCursorEnabled () const; + void setTextCursorEnabled (bool yes = true); + + bool textCursorBlinkState () const; + void setTextCursorBlinkState (bool on = true); + + // By convention, a text box with no content (i.e. no text lines) should + // have a cursor position of (row=0,col=0). Some code assumes this. + // + // (no error checking is performed - the row and col are as per + // setTextCursorPosition() so may be out of the bounds of the + // text selection) + int textCursorRow () const; + int textCursorCol () const; + // See kpToolText::beginDrawSelectText() for a correct use of this + // method, satisfying the above convention. + // + // WARNING: If the previous row and column are invalid (e.g. you just + // called kpTextSelection::setTextLines() such that the previous + // row and column now point outside the lines), this will not + // be able to erase the cursor at the old position. + // + // Always ensure that the text cursor position is valid. + // TODO: We need to check this in all source files. + // e.g. It's definitely wrong for kpToolTextBackspaceCommand. + void setTextCursorPosition (int row, int col); + + // Returns the document rectangle where cursor would be placed, using + // textCursorRow() and textCursorCol (). + // + // For a text selection without any content, this returns a rectangle + // corresponding to the first text row and column. + // + // If there is no text selection or textCursorRow() or + // textCursorCol() are invalid, it returns an empty rectangle. + QRect textCursorRect () const; + +protected: + // If textCursorRect() is valid, updates all views at that rectangle. + // The cursor blink state and timer are not affected at all. + // TODO: This and other methods will happily execute even if + // !textCursorEnabled(). We should fix this. + void updateTextCursor (); + +protected slots: + void slotTextCursorBlink (); + + +// +// View Updates +// + +public: + // Specifies whether to queue _all_ paint events + // (generated by you or the window system), until the + // corresponding call to restoreQueueUpdates(). Use this + // before multiple, big, non-interactive changes to the + // document to eliminate virtually all flicker. + // + // This is better than QWidget::setUpdatesEnabled() because + // restoreQueueUpdates() automatically restores, for each view, + // only the _regions_ that need to be repainted. + // + // You can nest blocks of setQueueUpdates()/restoreQueueUpdates(). + bool queueUpdates () const; + void setQueueUpdates (); + void restoreQueueUpdates (); + +public: + // Controls behaviour of updateViews(): + // + // Slow: Let Qt buffer paint events via QWidget::update(). + // Results in less flicker. Paint events are probably merged + // so long-term efficiency is increased at the expense of + // reduced responsiveness (default). Generally, the paint + // event happens a while later -- when you return to the event + // loop. + // Fast: Force Qt to redraw immediately. No paint events + // are merged so there is great potential for flicker, + // if used inappropriately. Use this when the redraw + // area is small and responsiveness is critical. + // Continual use of this mode can result in + // unnecessary redraws and incredibly slugish performance. + // + // You can nest blocks of setFastUpdates()/restoreFastUpdates(). + bool fastUpdates () const; + void setFastUpdates (); + void restoreFastUpdates (); + +public slots: + void updateView (kpView *v); + void updateView (kpView *v, const QRect &viewRect); + void updateView (kpView *v, int x, int y, int w, int h); + void updateView (kpView *v, const QRegion &viewRegion); + + void updateViewRectangleEdges (kpView *v, const QRect &viewRect); + + void updateViews (); + void updateViews (const QRect &docRect); + void updateViews (int x, int y, int w, int h); + +public slots: + void adjustViewsToEnvironment (); + +public slots: + void setInputMethodEnabled (bool inputMethodEnabled); + +private: + struct kpViewManagerPrivate * const d; +}; + + +#endif // KP_VIEW_MANAGER_H diff --git a/kolourpaint/views/manager/kpViewManagerPrivate.h b/kolourpaint/views/manager/kpViewManagerPrivate.h new file mode 100644 index 00000000..19565ced --- /dev/null +++ b/kolourpaint/views/manager/kpViewManagerPrivate.h @@ -0,0 +1,86 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpViewManagerPrivate_H +#define kpViewManagerPrivate_H + + +#include +#include + + +class kpMainWindow; +class kpTempPixmap; +class QTimer; +class kpView; + + +struct kpViewManagerPrivate +{ + kpMainWindow *mainWindow; + + QLinkedList views; + kpView *viewUnderCursor; + + QCursor cursor; + + kpTempImage *tempImage; + + bool selectionBorderVisible; + bool selectionBorderFinished; + + + // + // Text Cursor + // + + // Exists only if the text cursor is enabled. 0 otherwise. + QTimer *textCursorBlinkTimer; + + // (undefined if there is not currently a text selection) + int textCursorRow, textCursorCol; + + // (undefined if there is not currently a text selection) + bool textCursorBlinkState; + + + // + // View Updates + // + + int queueUpdatesCounter, fastUpdatesCounter; + + // + // Input Method + // + + bool inputMethodEnabled; +}; + + +#endif // kpViewManagerPrivate_H diff --git a/kolourpaint/views/manager/kpViewManager_TextCursor.cpp b/kolourpaint/views/manager/kpViewManager_TextCursor.cpp new file mode 100644 index 00000000..509bfd73 --- /dev/null +++ b/kolourpaint/views/manager/kpViewManager_TextCursor.cpp @@ -0,0 +1,238 @@ + +// TODO: This is bad design as it's easy to get out of sync with the selection. +// e.g. You could have textCursorEnabled() but no text selection or +// vice versa. And the cursor could be outside of the selection. +// +// In fact, our text commands momentarily violate these "invariants": +// +// 1. A text box with content must have the cursor somewhere on an +// existing text line, possibly 1 position after the last character +// on a line. +// +// 2. Special case: A content-less text box (i.e. no text lines) must +// have the cursor at (0,0). +// +// We don't assert-check them at the moment. We should when we fix +// the design so that the invariants are always maintained. + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2005 Kazuki Ohta + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_VIEW_MANAGER 0 + + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + + +// public +bool kpViewManager::textCursorEnabled () const +{ + return (bool) d->textCursorBlinkTimer; +} + +// public +void kpViewManager::setTextCursorEnabled (bool yes) +{ +#if DEBUG_KP_VIEW_MANAGER && 1 + kDebug () << "kpViewManager::setTextCursorEnabled(" << yes << ")"; +#endif + + if (yes == textCursorEnabled ()) + return; + + delete d->textCursorBlinkTimer; + d->textCursorBlinkTimer = 0; + + setFastUpdates (); + setQueueUpdates (); + { + if (yes) + { + d->textCursorBlinkTimer = new QTimer (this); + d->textCursorBlinkTimer->setSingleShot (true); + connect (d->textCursorBlinkTimer, SIGNAL (timeout ()), + this, SLOT (slotTextCursorBlink ())); + + d->textCursorBlinkState = true; + slotTextCursorBlink (); + } + else + { + d->textCursorBlinkState = false; + updateTextCursor (); + } + } + restoreQueueUpdates (); + restoreFastUpdates (); +} + + +// public +bool kpViewManager::textCursorBlinkState () const +{ + return d->textCursorBlinkState; +} + +// public +void kpViewManager::setTextCursorBlinkState (bool on) +{ + if (on == d->textCursorBlinkState) + return; + + d->textCursorBlinkState = on; + + updateTextCursor (); +} + + +// public +int kpViewManager::textCursorRow () const +{ + return d->textCursorRow; +} + +// public +int kpViewManager::textCursorCol () const +{ + return d->textCursorCol; +} + +// public +void kpViewManager::setTextCursorPosition (int row, int col) +{ + if (row == d->textCursorRow && col == d->textCursorCol) + return; + + setFastUpdates (); + setQueueUpdates (); + { + // Clear the old cursor. + d->textCursorBlinkState = false; + updateTextCursor (); + + d->textCursorRow = row; + d->textCursorCol = col; + + // Render the new cursor. + d->textCursorBlinkState = true; + updateTextCursor (); + } + restoreQueueUpdates (); + restoreFastUpdates (); + + if (d->viewUnderCursor) { + QInputContext *inputContext = d->viewUnderCursor->inputContext (); + if (inputContext) { + inputContext->update (); + } + } +} + + +// public +QRect kpViewManager::textCursorRect () const +{ + kpTextSelection *textSel = document ()->textSelection (); + if (!textSel) + return QRect (); + + QPoint topLeft = textSel->pointForTextRowCol (d->textCursorRow, d->textCursorCol); + if (topLeft == KP_INVALID_POINT) + { + // Text cursor row/col hasn't been specified yet? + if (textSel->hasContent ()) + return QRect (); + + // Empty text box should still display a cursor so that the user + // knows where typed text will go. + topLeft = textSel->textAreaRect ().topLeft (); + } + + Q_ASSERT (topLeft != KP_INVALID_POINT); + + return QRect (topLeft.x (), topLeft.y (), + 1, textSel->textStyle ().fontMetrics ().height ()); +} + + +// protected +void kpViewManager::updateTextCursor () +{ +#if DEBUG_KP_VIEW_MANAGER && 0 + kDebug () << "kpViewManager::updateTextCursor()"; +#endif + + const QRect r = textCursorRect (); + if (!r.isValid ()) + return; + + setFastUpdates (); + { + // If !textCursorEnabled(), this will clear. + updateViews (r); + } + restoreFastUpdates (); +} + +// protected slot +void kpViewManager::slotTextCursorBlink () +{ +#if DEBUG_KP_VIEW_MANAGER && 0 + kDebug () << "kpViewManager::slotTextCursorBlink() cursorBlinkState=" + << d->textCursorBlinkState << endl; +#endif + + if (d->textCursorBlinkTimer) + { + // (single shot) + d->textCursorBlinkTimer->start (QApplication::cursorFlashTime () / 2); + } + + updateTextCursor (); + // TODO: Shouldn't this be done _before_ updating the text cursor + // because textCursorBlinkState() is supposed to reflect what + // updateTextCursor() just rendered, until the next timer tick? + d->textCursorBlinkState = !d->textCursorBlinkState; +} diff --git a/kolourpaint/views/manager/kpViewManager_ViewUpdates.cpp b/kolourpaint/views/manager/kpViewManager_ViewUpdates.cpp new file mode 100644 index 00000000..58e728dc --- /dev/null +++ b/kolourpaint/views/manager/kpViewManager_ViewUpdates.cpp @@ -0,0 +1,268 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_VIEW_MANAGER 0 + + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + + +// public slot +bool kpViewManager::queueUpdates () const +{ + return (d->queueUpdatesCounter > 0); +} + +// public slot +void kpViewManager::setQueueUpdates () +{ + d->queueUpdatesCounter++; +#if DEBUG_KP_VIEW_MANAGER && 1 + kDebug () << "kpViewManager::setQueueUpdates() counter=" + << d->queueUpdatesCounter << endl; +#endif +} + +// public slot +void kpViewManager::restoreQueueUpdates () +{ + d->queueUpdatesCounter--; +#if DEBUG_KP_VIEW_MANAGER && 1 + kDebug () << "kpViewManager::restoreQueueUpdates() counter=" + << d->queueUpdatesCounter << endl; +#endif + Q_ASSERT (d->queueUpdatesCounter >= 0); + + if (d->queueUpdatesCounter == 0) + { + for (QLinkedList ::const_iterator it = d->views.begin (); + it != d->views.end (); + ++it) + { + (*it)->updateQueuedArea (); + } + } +} + + +// public slot +bool kpViewManager::fastUpdates () const +{ + return (d->fastUpdatesCounter > 0); +} + +// public slot +void kpViewManager::setFastUpdates () +{ + d->fastUpdatesCounter++; +#if DEBUG_KP_VIEW_MANAGER && 0 + kDebug () << "kpViewManager::setFastUpdates() counter=" + << d->fastUpdatesCounter << endl; +#endif +} + +// public slot +void kpViewManager::restoreFastUpdates () +{ + d->fastUpdatesCounter--; +#if DEBUG_KP_VIEW_MANAGER && 0 + kDebug () << "kpViewManager::restoreFastUpdates() counter=" + << d->fastUpdatesCounter << endl; +#endif + Q_ASSERT (d->fastUpdatesCounter >= 0); +} + + +// public slot +void kpViewManager::updateView (kpView *v) +{ + updateView (v, QRect (0, 0, v->width (), v->height ())); +} + +// public slot +void kpViewManager::updateView (kpView *v, const QRect &viewRect) +{ + if (!queueUpdates ()) + { + if (fastUpdates ()) + v->repaint (viewRect); + else + v->update (viewRect); + } + else + v->addToQueuedArea (viewRect); +} + +// public slot +void kpViewManager::updateView (kpView *v, int x, int y, int w, int h) +{ + updateView (v, QRect (x, y, w, h)); +} + +// public slot +void kpViewManager::updateView (kpView *v, const QRegion &viewRegion) +{ + if (!queueUpdates ()) + { + if (fastUpdates ()) + v->repaint (viewRegion); + else + v->update (viewRegion.boundingRect ()); + } + else + v->addToQueuedArea (viewRegion); +} + + +// public slot +void kpViewManager::updateViewRectangleEdges (kpView *v, const QRect &viewRect) +{ + if (viewRect.height () <= 0 || viewRect.width () <= 0) + return; + + // Top line + updateView (v, QRect (viewRect.x (), viewRect.y (), + viewRect.width (), 1)); + + if (viewRect.height () >= 2) + { + // Bottom line + updateView (v, QRect (viewRect.x (), viewRect.bottom (), + viewRect.width (), 1)); + + if (viewRect.height () > 2) + { + // Left line + updateView (v, QRect (viewRect.x (), viewRect.y () + 1, + 1, viewRect.height () - 2)); + + if (viewRect.width () >= 2) + { + // Right line + updateView (v, QRect (viewRect.right (), viewRect.y () + 1, + 1, viewRect.height () - 2)); + } + } + } +} + + +// public slot +void kpViewManager::updateViews () +{ + kpDocument *doc = document (); + if (doc) + updateViews (QRect (0, 0, doc->width (), doc->height ())); +} + +// public slot +void kpViewManager::updateViews (const QRect &docRect) +{ +#if DEBUG_KP_VIEW_MANAGER && 0 + kDebug () << "kpViewManager::updateViews (" << docRect << ")"; +#endif + + for (QLinkedList ::const_iterator it = d->views.begin (); + it != d->views.end (); + ++it) + { + kpView *view = *it; + + #if DEBUG_KP_VIEW_MANAGER && 0 + kDebug () << "\tupdating view " << view->name (); + #endif + if (view->zoomLevelX () % 100 == 0 && view->zoomLevelY () % 100 == 0) + { + #if DEBUG_KP_VIEW_MANAGER && 0 + kDebug () << "\t\tviewRect=" << view->transformDocToView (docRect); + #endif + updateView (view, view->transformDocToView (docRect)); + } + else + { + QRect viewRect = view->transformDocToView (docRect); + + int diff = qRound (double (qMax (view->zoomLevelX (), view->zoomLevelY ())) / 100.0) + 1; + + QRect newRect = QRect (viewRect.x () - diff, + viewRect.y () - diff, + viewRect.width () + 2 * diff, + viewRect.height () + 2 * diff) + .intersect (QRect (0, 0, view->width (), view->height ())); + + #if DEBUG_KP_VIEW_MANAGER && 0 + kDebug () << "\t\tviewRect (+compensate)=" << newRect; + #endif + updateView (view, newRect); + } + } +} + +// public slot +void kpViewManager::updateViews (int x, int y, int w, int h) +{ + updateViews (QRect (x, y, w, h)); +} + + +// public slot +void kpViewManager::adjustViewsToEnvironment () +{ +#if DEBUG_KP_VIEW_MANAGER && 1 + kDebug () << "kpViewManager::adjustViewsToEnvironment()" + << " numViews=" << d->views.count () + << endl; +#endif + for (QLinkedList ::const_iterator it = d->views.begin (); + it != d->views.end (); + ++it) + { + kpView *view = *it; + + #if DEBUG_KP_VIEW_MANAGER && 1 + kDebug () << "\tview: " << view->name () + << endl; + #endif + view->adjustToEnvironment (); + } +} diff --git a/kolourpaint/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp new file mode 100644 index 00000000..32b472ab --- /dev/null +++ b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp @@ -0,0 +1,234 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COLOR_SIMILARITY_CUBE 0 + + +#include + +#include + +#include +#include + +#include + +#include +#include + +//--------------------------------------------------------------------- + +static QColor Color (int redOrGreenOrBlue, + int baseBrightness, + double colorSimilarity, + int similarityDirection, + int highlight) +{ + int brightness = int (baseBrightness + + similarityDirection * + .5 * colorSimilarity * kpColorSimilarityHolder::ColorCubeDiagonalDistance); + + if (brightness < 0) + brightness = 0; + else if (brightness > 255) + brightness = 255; + + switch (redOrGreenOrBlue) + { + default: + case 0: return QColor (brightness, highlight, highlight); + case 1: return QColor (highlight, brightness, highlight); + case 2: return QColor (highlight, highlight, brightness); + } +} + +//--------------------------------------------------------------------- + +static QPointF PointBetween(const QPointF &p, const QPointF &q) +{ + return QPointF((p.x() + q.x()) / 2.0, (p.y() + q.y()) / 2.0); +} + +//--------------------------------------------------------------------- + +static void DrawQuadrant(QPaintDevice *target, + const QColor &col, + const QPointF &p1, const QPointF &p2, const QPointF &p3, + const QPointF &pointNotOnOutline) +{ + QPolygonF points (4); + points [0] = p1; + points [1] = p2; + points [2] = p3; + points [3] = pointNotOnOutline; + + QPainter p(target); + p.setRenderHints(QPainter::Antialiasing, true); + + // Polygon fill. + p.setPen(col); + p.setBrush(col); + p.drawPolygon(points); + + // do not draw a black border. It looks ugly +} + +//--------------------------------------------------------------------- + +static void DrawFace (QPaintDevice *target, + double colorSimilarity, + int redOrGreenOrBlue, + const QPointF &tl, const QPointF &tr, + const QPointF &bl, const QPointF &br, + int highlight) +{ +#if DEBUG_KP_COLOR_SIMILARITY_CUBE + kDebug () << "kpColorSimilarityCubeRenderer.cpp:DrawFace(RorGorB=" << redOrGreenOrBlue + << ",tl=" << tl + << ",tr=" << tr + << ",bl=" << bl + << ",br=" << br + << ")" + << endl; +#endif + + // tl --- tm --- tr + // | | | + // | | | + // ml --- mm --- mr + // | | | + // | | | + // bl --- bm --- br + + const QPointF tm (::PointBetween (tl, tr)); + const QPointF bm (::PointBetween (bl, br)); + + const QPointF ml (::PointBetween (tl, bl)); + const QPointF mr (::PointBetween (tr, br)); + const QPointF mm (::PointBetween (ml, mr)); + + + const int baseBrightness = + qMax (127, + 255 - int (kpColorSimilarityHolder::MaxColorSimilarity * + kpColorSimilarityHolder::ColorCubeDiagonalDistance / 2)); + QColor colors [2] = + { + ::Color (redOrGreenOrBlue, baseBrightness, colorSimilarity, -1, highlight), + ::Color (redOrGreenOrBlue, baseBrightness, colorSimilarity, +1, highlight) + }; + +#if DEBUG_KP_COLOR_SIMILARITY_CUBE + kDebug () << "\tmaxColorSimilarity=" << kpColorSimilarityHolder::MaxColorSimilarity + << " colorCubeDiagDist=" << kpColorSimilarityHolder::ColorCubeDiagonalDistance + << endl + << "\tbaseBrightness=" << baseBrightness + << " color[0]=" << ((colors [0].rgba() & RGB_MASK) >> ((2 - redOrGreenOrBlue) * 8)) + << " color[1]=" << ((colors [1].rgba() & RGB_MASK) >> ((2 - redOrGreenOrBlue) * 8)) + << endl; +#endif + + + ::DrawQuadrant(target, colors [0], tm, tl, ml, mm); + ::DrawQuadrant(target, colors [1], tm, tr, mr, mm); + ::DrawQuadrant(target, colors [1], ml, bl, bm, mm); + ::DrawQuadrant(target, colors [0], bm, br, mr, mm); +} + +//--------------------------------------------------------------------- + +// public static +void kpColorSimilarityCubeRenderer::Paint(QPaintDevice *target, + int x, int y, int cubeRectSize, + double colorSimilarity, + int highlight) +{ + Q_ASSERT (highlight >= 0 && highlight <= 255); + + // + // P------- Q --- --- + // / / | | | + // /A / | side | + // R-------S T --- cubeRectSize + // | | / / | + // S | | / side | + // U-------V --- --- + // |-------| + // side + // |-----------| + // cubeRectSize + // + // + + const double angle = KP_DEGREES_TO_RADIANS (45); + // S + S sin A = cubeRectSize + // (1 + sin A) x S = cubeRectSize + const double side = double(cubeRectSize) / (1.0 + sin(angle)); + + + const QPointF pointP(x + (side * cos (angle)), + y); + const QPointF pointQ(x + cubeRectSize - 1, + y); + const QPointF pointR(x, + y + (side * sin (angle))); + const QPointF pointS(x + (side), + y + (side * sin (angle))); + const QPointF pointT(x + cubeRectSize - 1, + y + (side)); + const QPointF pointU(x, + y + cubeRectSize - 1); + const QPointF pointV(x + (side), + y + cubeRectSize - 1); + + + // Top Face + ::DrawFace(target, + colorSimilarity, 0/*red*/, + pointP, pointQ, + pointR, pointS, + highlight); + + + // Front Face + ::DrawFace(target, + colorSimilarity, 1/*green*/, + pointR, pointS, + pointU, pointV, + highlight); + + + // Right Face + ::DrawFace(target, + colorSimilarity, 2/*blue*/, + pointS, pointQ, + pointV, pointT, + highlight); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.h b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.h new file mode 100644 index 00000000..8b458534 --- /dev/null +++ b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.h @@ -0,0 +1,54 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpColorSimilarityCubeRenderer_H +#define kpColorSimilarityCubeRenderer_H + + +class QPaintDevice; + + +class kpColorSimilarityCubeRenderer +{ +public: + // is used for animations: + // + // 0 = no highlight + // . + // . + // . + // 255 = full highlight + + static void Paint(QPaintDevice *target, + int x, int y, int size, + double colorSimilarity, + int highlight = 0); +}; + + +#endif // kpColorSimilarityCubeRenderer_H diff --git a/kolourpaint/widgets/colorSimilarity/kpColorSimilarityFrame.cpp b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityFrame.cpp new file mode 100644 index 00000000..727f02da --- /dev/null +++ b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityFrame.cpp @@ -0,0 +1,78 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COLOR_SIMILARITY_CUBE 0 + + +#include + +#include + +#include + +//--------------------------------------------------------------------- + +kpColorSimilarityFrame::kpColorSimilarityFrame(QWidget *parent) + : QWidget(parent) +{ + setWhatsThis (WhatsThis ()); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpColorSimilarityHolder] +void kpColorSimilarityFrame::setColorSimilarity (double similarity) +{ + kpColorSimilarityHolder::setColorSimilarity (similarity); + + repaint (); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +QSize kpColorSimilarityFrame::sizeHint () const +{ + return QSize (52, 52); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpColorSimilarityFrame::paintEvent (QPaintEvent *) +{ + int cubeRectSize = qMin(width() * 6 / 8, height() * 6 / 8); + int x = (width() - cubeRectSize) / 2; + int y = (height() - cubeRectSize) / 2; + + kpColorSimilarityCubeRenderer::Paint(this, + x, y, cubeRectSize, + colorSimilarity()); +} + +//--------------------------------------------------------------------- diff --git a/kolourpaint/widgets/colorSimilarity/kpColorSimilarityFrame.h b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityFrame.h new file mode 100644 index 00000000..cbeec8d1 --- /dev/null +++ b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityFrame.h @@ -0,0 +1,52 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpColorSimilarityFrame_H +#define kpColorSimilarityFrame_H + + +#include + +#include + + +class kpColorSimilarityFrame : public QWidget, public kpColorSimilarityHolder +{ +public: + kpColorSimilarityFrame(QWidget *parent); + + virtual void setColorSimilarity (double similarity); + + virtual QSize sizeHint () const; + +protected: + virtual void paintEvent (QPaintEvent *e); +}; + + +#endif // kpColorSimilarityFrame_H diff --git a/kolourpaint/widgets/colorSimilarity/kpColorSimilarityHolder.cpp b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityHolder.cpp new file mode 100644 index 00000000..c32051d7 --- /dev/null +++ b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityHolder.cpp @@ -0,0 +1,189 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COLOR_SIMILARITY_CUBE 0 + + +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + + +// public static +const double kpColorSimilarityHolder::ColorCubeDiagonalDistance = + sqrt ((double)255 * 255 * 3); + +// public static +const double kpColorSimilarityHolder::MaxColorSimilarity = .30; + + +kpColorSimilarityHolder::kpColorSimilarityHolder () + : m_colorSimilarity (0) +{ +} + +kpColorSimilarityHolder::~kpColorSimilarityHolder () +{ +} + + +// Don't cause the translators grief by appending strings etc. +// - duplicate text with 2 cases + +// public static +QString kpColorSimilarityHolder::WhatsThisWithClickInstructions () +{ + return i18n ("" + "

Color Similarity is how similar the colors of different pixels" + " must be, for operations to consider them to be the same.

" + + "

If you set it to something other than Exact Match," + " you can work more effectively with dithered" + " images and photos, in a comparable manner to the \"Magic Wand\"" + " feature of other paint programs.

" + + "

This feature applies to:

" + + "
    " + + "
  • Selections: In Transparent mode, any color in the" + " selection that is similar to the background color will" + " be made transparent.
  • " + + "
  • Flood Fill: For regions with similar - but not" + " identical - colored pixels, a higher setting is likely to" + " fill more pixels.
  • " + + "
  • Color Eraser: Any pixel whose color is similar" + " to the foreground color will be replaced with the background" + " color.
  • " + + "
  • Autocrop and Remove Internal Border: For" + " borders with similar - but not identical - colored pixels," + " a higher setting is more likely to crop the whole border.
  • " + + "
" + + "

Higher settings mean that operations consider an increased range" + " of colors to be sufficiently similar so as to be the same. Therefore," + " you should increase the setting if the above operations are not" + " affecting pixels whose colors you consider to be similar enough.

" + + "

However, if they are having too much of an effect and are changing" + " pixels whose colors you do not consider to be similar" + " (e.g. if Flood Fill is changing too many pixels), you" + " should decrease this setting.

" + + // sync: Compared to the other string below, we've added this line. + "

To configure it, click on the cube.

" + + "
"); +} + +// public static +QString kpColorSimilarityHolder::WhatsThis () +{ + return i18n ("" + "

Color Similarity is how similar the colors of different pixels" + " must be, for operations to consider them to be the same.

" + + "

If you set it to something other than Exact Match," + " you can work more effectively with dithered" + " images and photos, in a comparable manner to the \"Magic Wand\"" + " feature of other paint programs.

" + + "

This feature applies to:

" + + "
    " + + "
  • Selections: In Transparent mode, any color in the" + " selection that is similar to the background color will" + " be made transparent.
  • " + + "
  • Flood Fill: For regions with similar - but not" + " identical - colored pixels, a higher setting is likely to" + " fill more pixels.
  • " + + "
  • Color Eraser: Any pixel whose color is similar" + " to the foreground color will be replaced with the background" + " color.
  • " + + "
  • Autocrop and Remove Internal Border: For" + " borders with similar - but not identical - colored pixels," + " a higher setting is more likely to crop the whole border.
  • " + + "
" + + "

Higher settings mean that operations consider an increased range" + " of colors to be sufficiently similar so as to be the same. Therefore," + " you should increase the setting if the above operations are not" + " affecting pixels whose colors you consider to be similar enough.

" + + "

However, if they are having too much of an effect and are changing" + " pixels whose colors you do not consider to be similar" + " (e.g. if Flood Fill is changing too many pixels), you" + " should decrease this setting.

" + + "
"); +} + + +// public +double kpColorSimilarityHolder::colorSimilarity () const +{ + return m_colorSimilarity; +} + +// public virtual +void kpColorSimilarityHolder::setColorSimilarity (double similarity) +{ +#if DEBUG_KP_COLOR_SIMILARITY_CUBE + kDebug () << "kpColorSimilarityHolder::setColorSimilarity(" << similarity << ")"; +#endif + + if (m_colorSimilarity == similarity) + return; + + if (similarity < 0) + similarity = 0; + else if (similarity > MaxColorSimilarity) + similarity = MaxColorSimilarity; + + m_colorSimilarity = similarity; +} diff --git a/kolourpaint/widgets/colorSimilarity/kpColorSimilarityHolder.h b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityHolder.h new file mode 100644 index 00000000..f49304db --- /dev/null +++ b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityHolder.h @@ -0,0 +1,65 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpColorSimilarityHolder_H +#define kpColorSimilarityHolder_H + + +class QString; + + +class kpColorSimilarityHolder +{ +public: + kpColorSimilarityHolder (); + virtual ~kpColorSimilarityHolder (); + + static const double ColorCubeDiagonalDistance; + static const double MaxColorSimilarity; + + static QString WhatsThisWithClickInstructions (); + static QString WhatsThis (); + + double colorSimilarity () const; + + // This automatically restricts the given to the range + // 0 .. MaxColorSimilarity inclusive. + // + // Override this if you need to act on mutations. + // Remember to call this base implementation though. + // + // WARNING: The base constructor does not call this as virtual method + // calls in constructors do not invoke overrides anyway. + virtual void setColorSimilarity (double similarity); + +private: + double m_colorSimilarity; +}; + + +#endif // kpColorSimilarityHolder_H diff --git a/kolourpaint/widgets/colorSimilarity/kpColorSimilarityToolBarItem.cpp b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityToolBarItem.cpp new file mode 100644 index 00000000..19dc688f --- /dev/null +++ b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityToolBarItem.cpp @@ -0,0 +1,282 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM 0 + + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +kpColorSimilarityToolBarItem::kpColorSimilarityToolBarItem (QWidget *parent) + : QToolButton (parent), + kpColorSimilarityHolder (), + + m_oldColorSimilarity (0), + m_processedColorSimilarity (kpColor::Exact), + m_flashTimer (new QTimer (this)), + m_flashHighlight (0), + m_suppressingFlashCounter (0) +{ + setAutoRaise (true); + setFixedSize (52, 52); + + setWhatsThis (WhatsThisWithClickInstructions ()); + + connect (this, SIGNAL (clicked ()), SLOT (openDialog ())); + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupGeneral); + setColorSimilarityInternal (cfg.readEntry (kpSettingColorSimilarity, 0.0), + false/*don't write config*/); + + m_flashTimer->setInterval (100/*ms*/); + connect (m_flashTimer, SIGNAL (timeout ()), + SLOT (slotFlashTimerTimeout ())); +} + +//--------------------------------------------------------------------- + +// public +int kpColorSimilarityToolBarItem::processedColorSimilarity () const +{ + return m_processedColorSimilarity; +} + +//--------------------------------------------------------------------- + +// private +void kpColorSimilarityToolBarItem::setColorSimilarityInternal (double similarity, + bool writeConfig) +{ +#if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM + kDebug () << "kpColorSimilarityToolBarItem::setColorSimilarityInternal(" + << "similarity=" << similarity << ",writeConfig=" << writeConfig + << ")" << endl; +#endif + + m_oldColorSimilarity = colorSimilarity (); + + kpColorSimilarityHolder::setColorSimilarity (similarity); + m_processedColorSimilarity = kpColor::processSimilarity (colorSimilarity ()); + + updateIcon (); + updateToolTip (); + + if (writeConfig) + { + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupGeneral); + cfg.writeEntry (kpSettingColorSimilarity, colorSimilarity ()); + cfg.sync (); + } + + emit colorSimilarityChanged (colorSimilarity (), m_processedColorSimilarity); +} + +//--------------------------------------------------------------------- + +// public virtual [base kopColorSimilarityHolder] +void kpColorSimilarityToolBarItem::setColorSimilarity (double similarity) +{ + // (this calls the base setColorSimilarity() as required by base) + setColorSimilarityInternal (similarity, true/*write config*/); +} + +//--------------------------------------------------------------------- + +// public +double kpColorSimilarityToolBarItem::oldColorSimilarity () const +{ + return m_oldColorSimilarity; +} + +//--------------------------------------------------------------------- + +// public +void kpColorSimilarityToolBarItem::openDialog () +{ + kpColorSimilarityDialog dialog (this); + dialog.setColorSimilarity (colorSimilarity ()); + if (dialog.exec ()) + { + setColorSimilarity (dialog.colorSimilarity ()); + } +} + +//--------------------------------------------------------------------- + +// private slot: +void kpColorSimilarityToolBarItem::slotFlashTimerTimeout () +{ +#if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM + kDebug () << "kpColorSimilarityToolBarItem::slotFlashTimerTimeout()" + << " highlight=" << m_flashHighlight << endl; +#endif + int newHigh = m_flashHighlight - 20; + if (newHigh < 0) + newHigh = 0; + + m_flashHighlight = newHigh; + + updateIcon (); + + if (newHigh == 0) + m_flashTimer->stop (); +} + +//--------------------------------------------------------------------- + +// public +void kpColorSimilarityToolBarItem::flash () +{ +#if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM + kDebug () << "kpColorSimilarityToolBarItem::flash()"; +#endif + if (isSuppressingFlash ()) + return; + + if (m_flashHighlight == 255) + { + #if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM + kDebug () << "\tNOP"; + #endif + } + else + { + m_flashHighlight = 255; + + updateIcon (); + } + + m_flashTimer->start (); +} + +//--------------------------------------------------------------------- + +// public +bool kpColorSimilarityToolBarItem::isSuppressingFlash () const +{ + return (m_suppressingFlashCounter > 0); +} + +//--------------------------------------------------------------------- + +// public +void kpColorSimilarityToolBarItem::suppressFlash () +{ + m_suppressingFlashCounter++; +} + +//--------------------------------------------------------------------- + +// public +void kpColorSimilarityToolBarItem::unsupressFlash () +{ + m_suppressingFlashCounter--; + Q_ASSERT (m_suppressingFlashCounter >= 0); +} + +//--------------------------------------------------------------------- + +// private +void kpColorSimilarityToolBarItem::updateToolTip () +{ +#if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM + kDebug () << "kpColorSimilarityToolBarItem::updateToolTip()"; +#endif + + if (colorSimilarity () > 0) + { + setToolTip ( + i18n ("

Color Similarity: %1%

" + "

Click to configure.

", + qRound (colorSimilarity () * 100))); + } + else + { + setToolTip ( + i18n ("

Color Similarity: Exact Match

" + "

Click to configure.

")); + } +} + +//--------------------------------------------------------------------- + +// private +// LOOPT: This gets called twice on KolourPaint startup by: +// +// 1. setColorSimilarityInternal() called by the ctor +// 2. resizeEvent() when it's first shown() +// +// We could get rid of the first and save a few milliseconds. +void kpColorSimilarityToolBarItem::updateIcon () +{ + const int side = width () * 6 / 8; +#if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM + kDebug () << "kpColorSimilarityToolBarItem::updateIcon() width=" << width () + << " side=" << side << endl; +#endif + + QPixmap icon(side, side); + icon.fill(Qt::transparent); + + kpColorSimilarityCubeRenderer::Paint (&icon, + 0/*x*/, 0/*y*/, side, + colorSimilarity (), m_flashHighlight); + + setIconSize(QSize(side, side)); + setIcon(icon); +} + +//--------------------------------------------------------------------- + +// private virtual [base QWidget] +void kpColorSimilarityToolBarItem::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_COLOR_SIMILARITY_TOOL_BAR_ITEM + kDebug () << "kpColorSimilarityToolBarItem::resizeEvent() size=" << size () + << " oldSize=" << e->oldSize () << endl; +#endif + QToolButton::resizeEvent (e); + + updateIcon (); +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/widgets/colorSimilarity/kpColorSimilarityToolBarItem.h b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityToolBarItem.h new file mode 100644 index 00000000..bf94f9e7 --- /dev/null +++ b/kolourpaint/widgets/colorSimilarity/kpColorSimilarityToolBarItem.h @@ -0,0 +1,106 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpColorSimilarityToolBarItem_H +#define kpColorSimilarityToolBarItem_H + + +#include + +#include + + +class QKeyEvent; +class QMouseEvent; +class QTimer; + + +class kpColorSimilarityToolBarItem : public QToolButton, + public kpColorSimilarityHolder +{ +Q_OBJECT + +public: + // (reads the color similarity config setting) + kpColorSimilarityToolBarItem (QWidget *parent); + + int processedColorSimilarity () const; + +private: + // specifies whether to write the color similarity config + // setting. + void setColorSimilarityInternal (double similarity, bool writeConfig); +public: + virtual void setColorSimilarity (double similarity); + +signals: + void colorSimilarityChanged (double similarity, int processedSimilarity); + +public: + // (only valid in slots connected to colorSimilarityChanged()); + double oldColorSimilarity () const; + + +public slots: + // Open configuration dialog for color similarity. + void openDialog (); + +private slots: + void slotFlashTimerTimeout (); + +public: + // Animates the cube, so that the user is aware of its existence. + // Call this whenever a tool or command uses color similarity. + void flash (); + +public: + // Whether to ignore calls to flash(). + // You can nest blocks of suppressFlash()/unsuppressFlash(). + bool isSuppressingFlash () const; + void suppressFlash (); + void unsupressFlash (); + + +private: + void updateToolTip (); + void updateIcon (); + + virtual void resizeEvent (QResizeEvent *e); + + +private: + double m_oldColorSimilarity; + int m_processedColorSimilarity; + + QTimer *m_flashTimer; + int m_flashHighlight; + int m_suppressingFlashCounter; +}; + + +#endif // kpColorSimilarityToolBarItem_H diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectBalanceWidget.cpp b/kolourpaint/widgets/imagelib/effects/kpEffectBalanceWidget.cpp new file mode 100644 index 00000000..452be1cf --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectBalanceWidget.cpp @@ -0,0 +1,325 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_BALANCE 0 + + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + + +#if DEBUG_KP_EFFECT_BALANCE + #include +#endif + + +kpEffectBalanceWidget::kpEffectBalanceWidget (bool actOnSelection, + QWidget *parent) + : kpEffectWidgetBase (actOnSelection, parent) +{ + QGridLayout *lay = new QGridLayout (this); + lay->setMargin (marginHint ()); + lay->setSpacing (spacingHint ()); + + + QLabel *brightnessLabel = new QLabel (i18n ("&Brightness:"), this); + m_brightnessInput = new KIntNumInput (0/*value*/, this); + m_brightnessInput->setRange (-50, 50); + m_brightnessInput->setSliderEnabled(); + QPushButton *brightnessResetPushButton = new QPushButton (i18n ("Re&set"), this); + + QLabel *contrastLabel = new QLabel (i18n ("Co&ntrast:"), this); + m_contrastInput = new KIntNumInput (0/*value*/, this); + m_contrastInput->setRange (-50, 50); + m_contrastInput->setSliderEnabled(); + QPushButton *contrastResetPushButton = new QPushButton (i18n ("&Reset"), this); + + QLabel *gammaLabel = new QLabel (i18n ("&Gamma:"), this); + m_gammaInput = new KIntNumInput (0/*value*/, this); + m_gammaInput->setRange (-50, 50); + m_gammaInput->setSliderEnabled(); + // TODO: This is what should be shown in the m_gammaInput spinbox + m_gammaLabel = new QLabel (this); + // TODO: This doesn't seem to be wide enough with some fonts so the + // whole layout moves when we drag the gamma slider. + m_gammaLabel->setMinimumWidth (m_gammaLabel->fontMetrics ().width (QLatin1String (" 10.00 "))); + m_gammaLabel->setAlignment (m_gammaLabel->alignment () | Qt::AlignRight); + QPushButton *gammaResetPushButton = new QPushButton (i18n ("Rese&t"), this); + + + QWidget *spaceWidget = new QLabel (this); + spaceWidget->setFixedSize (1, spacingHint ()); + + + QLabel *channelLabel = new QLabel (i18n ("C&hannels:"), this); + m_channelsComboBox = new KComboBox (this); + m_channelsComboBox->addItem (i18n ("All")); + m_channelsComboBox->addItem (i18n ("Red")); + m_channelsComboBox->addItem (i18n ("Green")); + m_channelsComboBox->addItem (i18n ("Blue")); + + + QPushButton *resetPushButton = new QPushButton (i18n ("Reset &All Values"), this); + + + brightnessLabel->setBuddy (m_brightnessInput); + contrastLabel->setBuddy (m_contrastInput); + gammaLabel->setBuddy (m_gammaInput); + + channelLabel->setBuddy (m_channelsComboBox); + + + lay->addWidget (brightnessLabel, 0, 0); + lay->addWidget (m_brightnessInput, 0, 1, 1, 2); + lay->addWidget (brightnessResetPushButton, 0, 4); + + lay->addWidget (contrastLabel, 1, 0); + lay->addWidget (m_contrastInput, 1, 1, 1, 2); + lay->addWidget (contrastResetPushButton, 1, 4); + + lay->addWidget (gammaLabel, 2, 0); + lay->addWidget (m_gammaInput, 2, 1, 1, 2); + lay->addWidget (m_gammaLabel, 2, 3); + lay->addWidget (gammaResetPushButton, 2, 4); + + lay->addWidget (spaceWidget, 3, 0, 1, 5); + lay->addWidget (resetPushButton, 4, 2, 1, 3, Qt::AlignRight); + + lay->addWidget (channelLabel, 4, 0); + lay->addWidget (m_channelsComboBox, 4, 1, Qt::AlignLeft); + //lay->addWidget (resetPushButton, 4, 2, Qt::AlignRight); + + lay->setColumnStretch (1, 1); + + + // (no need for settingsChangedDelayed() since BCG effect is so fast :)) + connect (m_brightnessInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChangedNoWaitCursor ())); + connect (m_contrastInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChangedNoWaitCursor ())); + + connect (m_gammaInput, SIGNAL (valueChanged (int)), + this, SLOT (recalculateGammaLabel ())); + connect (m_gammaInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChangedNoWaitCursor ())); + + connect (m_channelsComboBox, SIGNAL (activated (int)), + this, SIGNAL (settingsChanged ())); + + connect (brightnessResetPushButton, SIGNAL (clicked ()), + this, SLOT (resetBrightness ())); + connect (contrastResetPushButton, SIGNAL (clicked ()), + this, SLOT (resetContrast ())); + connect (gammaResetPushButton, SIGNAL (clicked ()), + this, SLOT (resetGamma ())); + + connect (resetPushButton, SIGNAL (clicked ()), + this, SLOT (resetAll ())); + + + recalculateGammaLabel (); +} + +kpEffectBalanceWidget::~kpEffectBalanceWidget () +{ +} + + +// public virtual [base kpEffectWidgetBase] +QString kpEffectBalanceWidget::caption () const +{ + return i18n ("Settings"); +} + + +// public virtual [base kpEffectWidgetBase] +bool kpEffectBalanceWidget::isNoOp () const +{ + return (brightness () == 0 && contrast () == 0 && gamma () == 0); +} + +// public virtual [base kpEffectWidgetBase] +kpImage kpEffectBalanceWidget::applyEffect (const kpImage &image) +{ + return kpEffectBalance::applyEffect (image, + channels (), brightness (), contrast (), gamma ()); +} + +// public virtual [base kpEffectWidgetBase] +kpEffectCommandBase *kpEffectBalanceWidget::createCommand ( + kpCommandEnvironment *cmdEnviron) const +{ + return new kpEffectBalanceCommand (channels (), + brightness (), contrast (), gamma (), + m_actOnSelection, + cmdEnviron); +} + + +// protected +int kpEffectBalanceWidget::channels () const +{ + switch (m_channelsComboBox->currentIndex ()) + { + default: + case 0: + return kpEffectBalance::RGB; + + case 1: + return kpEffectBalance::Red; + + case 2: + return kpEffectBalance::Green; + + case 3: + return kpEffectBalance::Blue; + } +} + + +// protected +int kpEffectBalanceWidget::brightness () const +{ + return m_brightnessInput->value (); +} + +// protected +int kpEffectBalanceWidget::contrast () const +{ + return m_contrastInput->value (); +} + +// protected +int kpEffectBalanceWidget::gamma () const +{ + return m_gammaInput->value (); +} + + +// protected slot +void kpEffectBalanceWidget::recalculateGammaLabel () +{ + m_gammaLabel->setText ( + QLatin1String (" ") + + QString::number (pow (10, gamma () / 50.0), + 'f'/*[-]9.9*/, + 2/*precision*/) + + QLatin1String (" ")); + m_gammaLabel->repaint (); +} + + +// protected slot +void kpEffectBalanceWidget::resetBrightness () +{ + if (brightness () == 0) + return; + + bool sb = signalsBlocked (); + + if (!sb) blockSignals (true); + m_brightnessInput->setValue (0); + if (!sb) blockSignals (false); + + // Immediate update (if signals aren't blocked) + emit settingsChanged (); +} + +// protected slot +void kpEffectBalanceWidget::resetContrast () +{ + if (contrast () == 0) + return; + + bool sb = signalsBlocked (); + + if (!sb) blockSignals (true); + m_contrastInput->setValue (0); + if (!sb) blockSignals (false); + + // Immediate update (if signals aren't blocked) + emit settingsChanged (); +} + +// protected slot +void kpEffectBalanceWidget::resetGamma () +{ + if (gamma () == 0) + return; + + bool sb = signalsBlocked (); + + if (!sb) blockSignals (true); + m_gammaInput->setValue (0); + recalculateGammaLabel (); + if (!sb) blockSignals (false); + + // Immediate update (if signals aren't blocked) + emit settingsChanged (); +} + + +// protected slot +void kpEffectBalanceWidget::resetAll () +{ + if (isNoOp ()) + return; + + // Prevent multiple settingsChanged() which would normally result in + // redundant, expensive preview repaints + blockSignals (true); + + resetBrightness (); + resetContrast (); + resetGamma (); + + recalculateGammaLabel (); + + blockSignals (false); + + emit settingsChanged (); +} + + +#include diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectBalanceWidget.h b/kolourpaint/widgets/imagelib/effects/kpEffectBalanceWidget.h new file mode 100644 index 00000000..209d87e6 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectBalanceWidget.h @@ -0,0 +1,85 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectBalanceWidget_H +#define kpEffectBalanceWidget_H + + +#include +#include + + +class QLabel; + +class KComboBox; +class KIntNumInput; + + + +class kpEffectBalanceWidget : public kpEffectWidgetBase +{ +Q_OBJECT + +public: + kpEffectBalanceWidget (bool actOnSelection, QWidget *parent); + virtual ~kpEffectBalanceWidget (); + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual kpImage applyEffect (const kpImage &image); + + virtual kpEffectCommandBase *createCommand ( + kpCommandEnvironment *cmdEnviron) const; + +protected: + int channels () const; + + int brightness () const; + int contrast () const; + int gamma () const; + +protected slots: + void recalculateGammaLabel (); + + void resetBrightness (); + void resetContrast (); + void resetGamma (); + + void resetAll (); + +protected: + KIntNumInput *m_brightnessInput, + *m_contrastInput, + *m_gammaInput; + QLabel *m_gammaLabel; + KComboBox *m_channelsComboBox; +}; + + +#endif // kpEffectBalanceWidget_H diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectBlurSharpenWidget.cpp b/kolourpaint/widgets/imagelib/effects/kpEffectBlurSharpenWidget.cpp new file mode 100644 index 00000000..407766e5 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectBlurSharpenWidget.cpp @@ -0,0 +1,185 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_BLUR_SHARPEN 0 + + +#include + +#include +#include + +#include +#include +#include + +#include + + +kpEffectBlurSharpenWidget::kpEffectBlurSharpenWidget (bool actOnSelection, + QWidget *parent) + : kpEffectWidgetBase (actOnSelection, parent) +{ + QGridLayout *lay = new QGridLayout (this); + lay->setSpacing (spacingHint ()); + lay->setMargin (marginHint ()); + + + QLabel *amountLabel = new QLabel (i18n ("&Amount:"), this); + m_amountInput = new KIntNumInput (this); + m_amountInput->setRange (-kpEffectBlurSharpen::MaxStrength/*- for blur*/, + +kpEffectBlurSharpen::MaxStrength/*+ for sharpen*/); + + m_typeLabel = new QLabel (this); + + // Make sure doesn't expand when the effect type changes, + // as otherwise, that would cause the preview pixmap label in the + // "More Effects" dialog (which our widget is inside) to contract, + // which would look a bit weird. + // + // We do this by setting the label to every possible string it could + // contain and fixing its height to the maximum seen size hint height. + + int h = m_typeLabel->sizeHint ().height (); +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + kDebug () << "initial size hint height=" << h; +#endif + + m_typeLabel->setText ( + kpEffectBlurSharpenCommand::nameForType (kpEffectBlurSharpen::Blur)); + h = qMax (h, m_typeLabel->sizeHint ().height ()); + + m_typeLabel->setText ( + kpEffectBlurSharpenCommand::nameForType (kpEffectBlurSharpen::Sharpen)); + h = qMax (h, m_typeLabel->sizeHint ().height ()); + + // Set this text last as the label's text needs to reflect the default + // effect of "None". + m_typeLabel->setText ( + kpEffectBlurSharpenCommand::nameForType (kpEffectBlurSharpen::None)); + h = qMax (h, m_typeLabel->sizeHint ().height ()); + +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + kDebug () << "maximum size hint height" << h; +#endif + m_typeLabel->setFixedHeight (h); + m_typeLabel->setAlignment (Qt::AlignCenter); + + + amountLabel->setBuddy (m_amountInput); + + + lay->addWidget (amountLabel, 0, 0); + lay->addWidget (m_amountInput, 0, 1); + + lay->addWidget (m_typeLabel, 1, 0, 1, 2, Qt::AlignCenter); + + lay->setColumnStretch (1, 1); + + + connect (m_amountInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChangedDelayed ())); + + connect (m_amountInput, SIGNAL (valueChanged (int)), + this, SLOT (slotUpdateTypeLabel ())); +} + +kpEffectBlurSharpenWidget::~kpEffectBlurSharpenWidget () +{ +} + + +// public virtual [base kpEffectWidgetBase] +QString kpEffectBlurSharpenWidget::caption () const +{ + return QString(); +} + + +// public virtual [base kpEffectWidgetBase] +bool kpEffectBlurSharpenWidget::isNoOp () const +{ + return (type () == kpEffectBlurSharpen::None); +} + +// public virtual [base kpEffectWidgetBase] +kpImage kpEffectBlurSharpenWidget::applyEffect (const kpImage &image) +{ + return kpEffectBlurSharpen::applyEffect (image, + type (), strength ()); +} + +// public virtual [base kpEffectWidgetBase] +kpEffectCommandBase *kpEffectBlurSharpenWidget::createCommand ( + kpCommandEnvironment *cmdEnviron) const +{ + return new kpEffectBlurSharpenCommand (type (), strength (), + m_actOnSelection, + cmdEnviron); +} + + +// protected slot +void kpEffectBlurSharpenWidget::slotUpdateTypeLabel () +{ + QString text = kpEffectBlurSharpenCommand::nameForType (type ()); + +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + kDebug () << "kpEffectBlurSharpenWidget::slotUpdateTypeLabel() text=" + << text << endl; +#endif + const int h = m_typeLabel->height (); + m_typeLabel->setText (text); + if (m_typeLabel->height () != h) + { + kError () << "Label changed height despite the hack in ctor:" + << "was=" << h + << "now=" << m_typeLabel->height (); + } +} + + +// protected +kpEffectBlurSharpen::Type kpEffectBlurSharpenWidget::type () const +{ + if (m_amountInput->value () == 0) + return kpEffectBlurSharpen::None; + else if (m_amountInput->value () < 0) + return kpEffectBlurSharpen::Blur; + else + return kpEffectBlurSharpen::Sharpen; +} + +// protected +int kpEffectBlurSharpenWidget::strength () const +{ + return qAbs (m_amountInput->value ()); +} + + +#include diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectBlurSharpenWidget.h b/kolourpaint/widgets/imagelib/effects/kpEffectBlurSharpenWidget.h new file mode 100644 index 00000000..4533e242 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectBlurSharpenWidget.h @@ -0,0 +1,72 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectBlurSharpenWidget_H +#define kpEffectBlurSharpenWidget_H + + +#include + +#include +#include + + +class QLabel; + +class KIntNumInput; + + +class kpEffectBlurSharpenWidget : public kpEffectWidgetBase +{ +Q_OBJECT + +public: + kpEffectBlurSharpenWidget (bool actOnSelection, QWidget *parent); + virtual ~kpEffectBlurSharpenWidget (); + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual kpImage applyEffect (const kpImage &image); + + virtual kpEffectCommandBase *createCommand ( + kpCommandEnvironment *cmdEnviron) const; + +protected slots: + void slotUpdateTypeLabel (); + +protected: + kpEffectBlurSharpen::Type type () const; + int strength () const; + + KIntNumInput *m_amountInput; + QLabel *m_typeLabel; +}; + + +#endif // kpEffectBlurSharpenWidget_H diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectEmbossWidget.cpp b/kolourpaint/widgets/imagelib/effects/kpEffectEmbossWidget.cpp new file mode 100644 index 00000000..31e1bac6 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectEmbossWidget.cpp @@ -0,0 +1,131 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_EMBOSS 0 + + +#include + +#include +#include + +#include +#include + +#include +#include + + +kpEffectEmbossWidget::kpEffectEmbossWidget (bool actOnSelection, + QWidget *parent) + : kpEffectWidgetBase (actOnSelection, parent) +{ + QGridLayout *lay = new QGridLayout (this); + lay->setSpacing (spacingHint ()); + lay->setMargin (marginHint ()); + + +#if 0 + QLabel *amountLabel = new QLabel (i18n ("&Amount:"), this); + m_amountInput = new KIntNumInput (this); + m_amountInput->setRange (kpEffectEmboss::MinStrength, + kpEffectEmboss::MaxStrength, 1/*step*/, true/*slider*/); + m_amountInput->setSpecialValueText (i18n ("None")); + + + amountLabel->setBuddy (m_amountInput); + + + lay->addWidget (amountLabel, 0, 0); + lay->addWidget (m_amountInput, 0, 1); + + lay->setColumnStretch (1, 1); + + + connect (m_amountInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChanged ())); +#endif + + m_enableCheckBox = new QCheckBox (i18n ("E&nable"), this); + + + lay->addWidget (m_enableCheckBox, 0, 0, 1, 2, Qt::AlignCenter); + + + // (settingsChangedDelayed() instead of settingsChanged() so that the + // user can quickly press OK to apply effect to document directly and + // not have to wait for the also slow preview) + connect (m_enableCheckBox, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChangedDelayed ())); +} + +kpEffectEmbossWidget::~kpEffectEmbossWidget () +{ +} + + +// public virtual [base kpEffectWidgetBase] +QString kpEffectEmbossWidget::caption () const +{ + return QString(); +} + + +// public virtual [base kpEffectWidgetBase] +bool kpEffectEmbossWidget::isNoOp () const +{ + //return (m_amountInput->value () == 0); + return !m_enableCheckBox->isChecked (); +} + +// public virtual [base kpEffectWidgetBase] +kpImage kpEffectEmbossWidget::applyEffect (const kpImage &image) +{ + if (isNoOp ()) + return image; + + return kpEffectEmboss::applyEffect (image, strength ()); +} + +// public virtual [base kpEffectWidgetBase] +kpEffectCommandBase *kpEffectEmbossWidget::createCommand ( + kpCommandEnvironment *cmdEnviron) const +{ + return new kpEffectEmbossCommand (strength (), + m_actOnSelection, + cmdEnviron); +} + +// protected +int kpEffectEmbossWidget::strength () const +{ + return kpEffectEmboss::MaxStrength; +} + + +#include diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectEmbossWidget.h b/kolourpaint/widgets/imagelib/effects/kpEffectEmbossWidget.h new file mode 100644 index 00000000..08314b8e --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectEmbossWidget.h @@ -0,0 +1,66 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectEmbossWidget_H +#define kpEffectEmbossWidget_H + + +#include +#include + + +class QCheckBox; + +class KIntNumInput; + + +class kpEffectEmbossWidget : public kpEffectWidgetBase +{ +Q_OBJECT + +public: + kpEffectEmbossWidget (bool actOnSelection, QWidget *parent); + virtual ~kpEffectEmbossWidget (); + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual kpImage applyEffect (const kpImage &image); + + virtual kpEffectCommandBase *createCommand ( + kpCommandEnvironment *cmdEnviron) const; + +protected: + int strength () const; + + //KIntNumInput *m_amountInput; + QCheckBox *m_enableCheckBox; +}; + + +#endif // kpEffectEmbossWidget_H diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectFlattenWidget.cpp b/kolourpaint/widgets/imagelib/effects/kpEffectFlattenWidget.cpp new file mode 100644 index 00000000..a97aef1a --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectFlattenWidget.cpp @@ -0,0 +1,187 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_FLATTEN 0 + + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +// public static +QColor kpEffectFlattenWidget::s_lastColor1; +QColor kpEffectFlattenWidget::s_lastColor2; + +kpEffectFlattenWidget::kpEffectFlattenWidget (bool actOnSelection, + QWidget *parent) + : kpEffectWidgetBase (actOnSelection, parent) +{ + if (!s_lastColor1.isValid () || !s_lastColor2.isValid ()) + { + KConfigGroup cfgGroupSaver (KGlobal::config (), kpSettingsGroupFlattenEffect); + + s_lastColor1 = cfgGroupSaver.readEntry (kpSettingFlattenEffectColor1, QColor ()); + if (!s_lastColor1.isValid ()) + s_lastColor1 = Qt::red; + + s_lastColor2 = cfgGroupSaver.readEntry (kpSettingFlattenEffectColor2, QColor ()); + if (!s_lastColor2.isValid ()) + s_lastColor2 = Qt::blue; + } + + + m_enableCheckBox = new QCheckBox (i18n ("E&nable"), this); + + KVBox *colorButtonContainer = new KVBox (this); + colorButtonContainer->setMargin (KDialog::marginHint () / 2); + colorButtonContainer->setSpacing (spacingHint ()); + m_color1Button = new KColorButton (s_lastColor1, colorButtonContainer); + m_color2Button = new KColorButton (s_lastColor2, colorButtonContainer); + + + m_color1Button->setEnabled (false); + m_color2Button->setEnabled (false); + + + QVBoxLayout *lay = new QVBoxLayout (this); + lay->setSpacing(spacingHint ()); + lay->setMargin(marginHint ()); + lay->addWidget (m_enableCheckBox); + lay->addWidget (colorButtonContainer); + + + connect (m_enableCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotEnableChanged (bool))); + + connect (m_color1Button, SIGNAL (changed (const QColor &)), + this, SIGNAL (settingsChanged ())); + connect (m_color2Button, SIGNAL (changed (const QColor &)), + this, SIGNAL (settingsChanged ())); +} + +kpEffectFlattenWidget::~kpEffectFlattenWidget () +{ + s_lastColor1 = color1 (); + s_lastColor2 = color2 (); + + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupFlattenEffect); + + cfg.writeEntry (kpSettingFlattenEffectColor1, s_lastColor1); + cfg.writeEntry (kpSettingFlattenEffectColor2, s_lastColor2); + cfg.sync (); +} + + +// public +QColor kpEffectFlattenWidget::color1 () const +{ + return m_color1Button->color (); +} + +// public +QColor kpEffectFlattenWidget::color2 () const +{ + return m_color2Button->color (); +} + + +// +// kpEffectFlattenWidget implements kpEffectWidgetBase interface +// + +// public virtual [base kpEffectWidgetBase] +QString kpEffectFlattenWidget::caption () const +{ + return i18n ("Colors"); +} + + +// public virtual [base kpEffectWidgetBase] +bool kpEffectFlattenWidget::isNoOp () const +{ + return !m_enableCheckBox->isChecked (); +} + +// public virtual [base kpEffectWidgetBase] +kpImage kpEffectFlattenWidget::applyEffect (const kpImage &image) +{ +#if DEBUG_KP_EFFECT_FLATTEN + kDebug () << "kpEffectFlattenWidget::applyEffect() nop=" + << isNoOp () << endl; +#endif + + if (isNoOp ()) + return image; + + return kpEffectFlatten::applyEffect (image, color1 (), color2 ()); +} + + +// public virtual [base kpEffectWidgetBase] +kpEffectCommandBase *kpEffectFlattenWidget::createCommand ( + kpCommandEnvironment *cmdEnviron) const +{ + return new kpEffectFlattenCommand (color1 (), color2 (), + m_actOnSelection, + cmdEnviron); +} + + +// protected slot: +void kpEffectFlattenWidget::slotEnableChanged (bool enable) +{ +#if DEBUG_KP_EFFECT_FLATTEN + kDebug () << "kpEffectFlattenWidget::slotEnableChanged(" << enable + << ") enableButton=" << m_enableCheckBox->isChecked () + << endl; +#endif + + m_color1Button->setEnabled (enable); + m_color2Button->setEnabled (enable); + + emit settingsChanged (); +} + + +#include diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectFlattenWidget.h b/kolourpaint/widgets/imagelib/effects/kpEffectFlattenWidget.h new file mode 100644 index 00000000..4fa29c52 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectFlattenWidget.h @@ -0,0 +1,80 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectFlattenWidget_H +#define kpEffectFlattenWidget_H + + +#include + +#include + + +class QCheckBox; + +class KColorButton; + + +class kpEffectFlattenWidget : public kpEffectWidgetBase +{ +Q_OBJECT + +public: + kpEffectFlattenWidget (bool actOnSelection, QWidget *parent); + virtual ~kpEffectFlattenWidget (); + + + static QColor s_lastColor1, s_lastColor2; + + + QColor color1 () const; + QColor color2 () const; + + + // + // kpEffectWidgetBase interface + // + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual kpImage applyEffect (const kpImage &image); + + virtual kpEffectCommandBase *createCommand ( + kpCommandEnvironment *cmdEnviron) const; + +protected slots: + void slotEnableChanged (bool enable); + +protected: + QCheckBox *m_enableCheckBox; + KColorButton *m_color1Button, *m_color2Button; +}; + + +#endif // kpEffectFlattenWidget_H diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectHSVWidget.cpp b/kolourpaint/widgets/imagelib/effects/kpEffectHSVWidget.cpp new file mode 100644 index 00000000..45156a88 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectHSVWidget.cpp @@ -0,0 +1,128 @@ + +/* + Copyright (c) 2007 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include + +#include +#include +#include + +#include +#include + + +kpEffectHSVWidget::kpEffectHSVWidget (bool actOnSelection, QWidget *parent) + : kpEffectWidgetBase (actOnSelection, parent) +{ + QGridLayout *lay = new QGridLayout (this); + lay->setSpacing (spacingHint ()); + lay->setMargin (marginHint ()); + + QLabel *hueLabel = new QLabel (i18n ("&Hue:"), this); + QLabel *saturationLabel = new QLabel (i18n ("&Saturation:"), this); + QLabel *valueLabel = new QLabel (i18nc ("The V of HSV", "&Value:"), this); + + m_hueInput = new KDoubleNumInput (this); + m_hueInput->setRange (-180, 180, 15/*step*/, true/*slider*/); + + m_saturationInput = new KDoubleNumInput (this); + m_saturationInput->setRange (-1, 1, .1/*step*/, true/*slider*/); + + m_valueInput = new KDoubleNumInput (this); + m_valueInput->setRange (-1, 1, .1/*step*/, true/*slider*/); + + hueLabel->setBuddy (m_hueInput); + saturationLabel->setBuddy (m_saturationInput); + valueLabel->setBuddy (m_valueInput); + + lay->addWidget (hueLabel, 0, 0); + lay->addWidget (m_hueInput, 0, 1); + + lay->addWidget (saturationLabel, 1, 0); + lay->addWidget (m_saturationInput, 1, 1); + + lay->addWidget (valueLabel, 2, 0); + lay->addWidget (m_valueInput, 2, 1); + + lay->setColumnStretch (1, 1); + + + connect (m_hueInput, SIGNAL (valueChanged (double)), + this, SIGNAL (settingsChangedDelayed ())); + + connect (m_saturationInput, SIGNAL (valueChanged (double)), + this, SIGNAL (settingsChangedDelayed ())); + + connect (m_valueInput, SIGNAL (valueChanged (double)), + this, SIGNAL (settingsChangedDelayed ())); +} + +kpEffectHSVWidget::~kpEffectHSVWidget () +{ +} + + +// public virtual [base kpEffectWidgetBase] +QString kpEffectHSVWidget::caption () const +{ + // TODO: Why doesn't this have a caption? Ditto for the other effects. + return QString(); +} + + +// public virtual [base kpEffectWidgetBase] +bool kpEffectHSVWidget::isNoOp () const +{ + if (m_hueInput->value () == 0 && m_saturationInput->value () == 0 && m_valueInput->value () == 0) + return true; + else + return false; +} + +// public virtual [base kpEffectWidgetBase] +kpImage kpEffectHSVWidget::applyEffect (const kpImage &image) +{ + return kpEffectHSV::applyEffect (image, + m_hueInput->value (), m_saturationInput->value (), m_valueInput->value ()); +} + +// public virtual [base kpEffectWidgetBase] +kpEffectCommandBase *kpEffectHSVWidget::createCommand ( + kpCommandEnvironment *cmdEnviron) const +{ + return new kpEffectHSVCommand ( + m_hueInput->value (), m_saturationInput->value (), m_valueInput->value (), + m_actOnSelection, + cmdEnviron); +} + + +#include diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectHSVWidget.h b/kolourpaint/widgets/imagelib/effects/kpEffectHSVWidget.h new file mode 100644 index 00000000..54baa202 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectHSVWidget.h @@ -0,0 +1,62 @@ + +/* + Copyright (c) 2007 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectHSVWidget_H +#define kpEffectHSVWidget_H + + +#include + + +class KDoubleNumInput; + + +class kpEffectHSVWidget : public kpEffectWidgetBase +{ +Q_OBJECT + +public: + kpEffectHSVWidget (bool actOnSelection, QWidget *parent); + virtual ~kpEffectHSVWidget (); + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual kpImage applyEffect (const kpImage &image); + + virtual kpEffectCommandBase *createCommand ( + kpCommandEnvironment *cmdEnviron) const; + +protected: + KDoubleNumInput *m_hueInput; + KDoubleNumInput *m_saturationInput; + KDoubleNumInput *m_valueInput; +}; + + +#endif // kpEffectHSVWidget_H diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectInvertWidget.cpp b/kolourpaint/widgets/imagelib/effects/kpEffectInvertWidget.cpp new file mode 100644 index 00000000..6f4a716e --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectInvertWidget.cpp @@ -0,0 +1,211 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_INVERT 0 + + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +kpEffectInvertWidget::kpEffectInvertWidget (bool actOnSelection, + QWidget *parent) + : kpEffectWidgetBase (actOnSelection, parent) +{ + QVBoxLayout *topLevelLay = new QVBoxLayout (this); + topLevelLay->setSpacing(spacingHint ()); + topLevelLay->setMargin(marginHint ()); + + + QWidget *centerWidget = new QWidget (this); + topLevelLay->addWidget (centerWidget, 0/*stretch*/, Qt::AlignCenter); + + + QVBoxLayout *centerWidgetLay = new QVBoxLayout (centerWidget ); + centerWidgetLay->setSpacing( spacingHint() ); + + m_redCheckBox = new QCheckBox (i18n ("&Red"), centerWidget); + m_greenCheckBox = new QCheckBox (i18n ("&Green"), centerWidget); + m_blueCheckBox = new QCheckBox (i18n ("&Blue"), centerWidget); + + QWidget *spaceWidget = new QWidget (centerWidget); + spaceWidget->setFixedSize (1, spacingHint ()); + + m_allCheckBox = new QCheckBox (i18n ("&All"), centerWidget); + + + m_redCheckBox->setChecked (false); + m_greenCheckBox->setChecked (false); + m_blueCheckBox->setChecked (false); + + m_allCheckBox->setChecked (false); + + + centerWidgetLay->addWidget (m_redCheckBox); + centerWidgetLay->addWidget (m_greenCheckBox); + centerWidgetLay->addWidget (m_blueCheckBox); + + centerWidgetLay->addWidget (spaceWidget); + + centerWidgetLay->addWidget (m_allCheckBox); + + + m_inSignalHandler = false; + connect (m_redCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotRGBCheckBoxToggled ())); + connect (m_greenCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotRGBCheckBoxToggled ())); + connect (m_blueCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotRGBCheckBoxToggled ())); + + connect (m_allCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotAllCheckBoxToggled ())); +} + +kpEffectInvertWidget::~kpEffectInvertWidget () +{ +} + + +// public +int kpEffectInvertWidget::channels () const +{ +#if DEBUG_KP_EFFECT_INVERT + kDebug () << "kpEffectInvertWidget::channels()" + << " isChecked: r=" << m_redCheckBox->isChecked () + << " g=" << m_greenCheckBox->isChecked () + << " b=" << m_blueCheckBox->isChecked () + << endl; +#endif + + int channels = 0; + + + if (m_redCheckBox->isChecked ()) + channels |= kpEffectInvert::Red; + + if (m_greenCheckBox->isChecked ()) + channels |= kpEffectInvert::Green; + + if (m_blueCheckBox->isChecked ()) + channels |= kpEffectInvert::Blue; + + +#if DEBUG_KP_EFFECT_INVERT + kDebug () << "\treturning channels=" << (int *) channels; +#endif + return channels; +} + + +// +// kpEffectInvertWidget implements kpEffectWidgetBase interface +// + +// public virtual [base kpEffectWidgetBase] +QString kpEffectInvertWidget::caption () const +{ + return i18n ("Channels"); +} + + +// public virtual [base kpEffectWidgetBase] +bool kpEffectInvertWidget::isNoOp () const +{ + return (channels () == kpEffectInvert::None); +} + +// public virtual [base kpEffectWidgetBase] +kpImage kpEffectInvertWidget::applyEffect (const kpImage &image) +{ + return kpEffectInvert::applyEffect (image, channels ()); +} + + +// public virtual [base kpEffectWidgetBase] +kpEffectCommandBase *kpEffectInvertWidget::createCommand ( + kpCommandEnvironment *cmdEnviron) const +{ + return new kpEffectInvertCommand (channels (), + m_actOnSelection, + cmdEnviron); +} + + +// protected slots +void kpEffectInvertWidget::slotRGBCheckBoxToggled () +{ + if (m_inSignalHandler) + return; + + m_inSignalHandler = true; + + //blockSignals (true); + m_allCheckBox->setChecked (m_redCheckBox->isChecked () && + m_blueCheckBox->isChecked () && + m_greenCheckBox->isChecked ()); + //blockSignals (false); + + emit settingsChanged (); + + m_inSignalHandler = false; +} + +// protected slot +void kpEffectInvertWidget::slotAllCheckBoxToggled () +{ + if (m_inSignalHandler) + return; + + m_inSignalHandler = true; + + //blockSignals (true); + m_redCheckBox->setChecked (m_allCheckBox->isChecked ()); + m_greenCheckBox->setChecked (m_allCheckBox->isChecked ()); + m_blueCheckBox->setChecked (m_allCheckBox->isChecked ()); + //blockSignals (false); + + emit settingsChanged (); + + m_inSignalHandler = false; +} + + +#include + diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectInvertWidget.h b/kolourpaint/widgets/imagelib/effects/kpEffectInvertWidget.h new file mode 100644 index 00000000..df18a128 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectInvertWidget.h @@ -0,0 +1,77 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectInvertWidget_H +#define kpEffectInvertWidget_H + + +#include + + +class QCheckBox; + + +class kpEffectInvertWidget : public kpEffectWidgetBase +{ +Q_OBJECT + +public: + kpEffectInvertWidget (bool actOnSelection, + QWidget *parent); + virtual ~kpEffectInvertWidget (); + + + int channels () const; + + + // + // kpEffectWidgetBase interface + // + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual kpImage applyEffect (const kpImage &image); + + virtual kpEffectCommandBase *createCommand ( + kpCommandEnvironment *cmdEnviron) const; + +protected slots: + void slotRGBCheckBoxToggled (); + void slotAllCheckBoxToggled (); + +protected: + QCheckBox *m_redCheckBox, *m_greenCheckBox, *m_blueCheckBox, + *m_allCheckBox; + + // blockSignals() didn't seem to work + bool m_inSignalHandler; +}; + + +#endif // kpEffectInvertWidget_H diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectReduceColorsWidget.cpp b/kolourpaint/widgets/imagelib/effects/kpEffectReduceColorsWidget.cpp new file mode 100644 index 00000000..ef7de891 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectReduceColorsWidget.cpp @@ -0,0 +1,177 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_REDUCE_COLORS 0 + + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +kpEffectReduceColorsWidget::kpEffectReduceColorsWidget (bool actOnSelection, + QWidget *parent) + : kpEffectWidgetBase (actOnSelection, parent) +{ + QVBoxLayout *lay = new QVBoxLayout (this); + lay->setSpacing(spacingHint ()); + lay->setMargin(marginHint ()); + + + m_blackAndWhiteRadioButton = + new QRadioButton (i18n ("&Monochrome"), this); + + m_blackAndWhiteDitheredRadioButton = + new QRadioButton (i18n ("Mo&nochrome (dithered)"), this); + + m_8BitRadioButton = new QRadioButton (i18n ("256 co&lor"), this); + + m_8BitDitheredRadioButton = new QRadioButton (i18n ("256 colo&r (dithered)"), this); + + m_24BitRadioButton = new QRadioButton (i18n ("24-&bit color"), this); + + + // LOCOMPAT: don't think this is needed + QButtonGroup *buttonGroup = new QButtonGroup (this); + buttonGroup->addButton (m_blackAndWhiteRadioButton); + buttonGroup->addButton (m_blackAndWhiteDitheredRadioButton); + buttonGroup->addButton (m_8BitRadioButton); + buttonGroup->addButton (m_8BitDitheredRadioButton); + buttonGroup->addButton (m_24BitRadioButton); + + m_defaultRadioButton = m_24BitRadioButton; + m_defaultRadioButton->setChecked (true); + + lay->addWidget (m_blackAndWhiteRadioButton); + lay->addWidget (m_blackAndWhiteDitheredRadioButton); + lay->addWidget (m_8BitRadioButton); + lay->addWidget (m_8BitDitheredRadioButton); + lay->addWidget (m_24BitRadioButton); + + connect (m_blackAndWhiteRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); + connect (m_blackAndWhiteDitheredRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); + connect (m_8BitRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); + connect (m_8BitDitheredRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); + connect (m_24BitRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); +} + +//--------------------------------------------------------------------- + +// public +int kpEffectReduceColorsWidget::depth () const +{ + // These values (1, 8, 32) are QImage's supported depths. + // TODO: Qt-4.7.1: 1, 8, 16, 24 and 32 + if (m_blackAndWhiteRadioButton->isChecked () || + m_blackAndWhiteDitheredRadioButton->isChecked ()) + { + return 1; + } + else if (m_8BitRadioButton->isChecked () || + m_8BitDitheredRadioButton->isChecked ()) + { + return 8; + } + else if (m_24BitRadioButton->isChecked ()) + { + return 32; + } + else + { + return 0; + } +} + +//--------------------------------------------------------------------- + +// public +bool kpEffectReduceColorsWidget::dither () const +{ + return (m_blackAndWhiteDitheredRadioButton->isChecked () || + m_8BitDitheredRadioButton->isChecked ()); +} + +//--------------------------------------------------------------------- +// +// kpEffectReduceColorsWidget implements kpEffectWidgetBase interface +// + +// public virtual [base kpEffectWidgetBase] +QString kpEffectReduceColorsWidget::caption () const +{ + return i18n ("Reduce To"); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpEffectWidgetBase] +bool kpEffectReduceColorsWidget::isNoOp () const +{ + return (!m_defaultRadioButton || m_defaultRadioButton->isChecked ()); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpEffectWidgetBase] +kpImage kpEffectReduceColorsWidget::applyEffect (const kpImage &image) +{ + return kpEffectReduceColors::applyEffect (image, depth (), dither ()); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpEffectWidgetBase] +kpEffectCommandBase *kpEffectReduceColorsWidget::createCommand ( + kpCommandEnvironment *cmdEnviron) const +{ + return new kpEffectReduceColorsCommand (depth (), dither (), + m_actOnSelection, + cmdEnviron); +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectReduceColorsWidget.h b/kolourpaint/widgets/imagelib/effects/kpEffectReduceColorsWidget.h new file mode 100644 index 00000000..02597dd8 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectReduceColorsWidget.h @@ -0,0 +1,73 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectReduceColorsWidget_H +#define kpEffectReduceColorsWidget_H + + +#include + + +class QRadioButton; + + +class kpEffectReduceColorsWidget : public kpEffectWidgetBase +{ +Q_OBJECT + +public: + kpEffectReduceColorsWidget (bool actOnSelection, + QWidget *parent); + + int depth () const; + bool dither () const; + + + // + // kpEffectWidgetBase interface + // + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual kpImage applyEffect (const kpImage &image); + + virtual kpEffectCommandBase *createCommand ( + kpCommandEnvironment *cmdEnviron) const; + +protected: + QRadioButton *m_blackAndWhiteRadioButton, + *m_blackAndWhiteDitheredRadioButton, + *m_8BitRadioButton, + *m_8BitDitheredRadioButton, + *m_24BitRadioButton; + QRadioButton *m_defaultRadioButton; +}; + + +#endif // kpEffectReduceColorsWidget_H diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectToneEnhanceWidget.cpp b/kolourpaint/widgets/imagelib/effects/kpEffectToneEnhanceWidget.cpp new file mode 100644 index 00000000..3dd9acc3 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectToneEnhanceWidget.cpp @@ -0,0 +1,144 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2006 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + + +kpEffectToneEnhanceWidget::kpEffectToneEnhanceWidget (bool actOnSelection, + QWidget *parent) + : kpEffectWidgetBase (actOnSelection, parent), + m_granularityInput (0), + m_amountInput (0) + +{ + QGridLayout *lay = new QGridLayout (this); + lay->setSpacing (spacingHint ()); + lay->setMargin (marginHint ()); + + + // See kpEffectToneEnhance::applyEffect(). + { + QLabel *granularityLabel = new QLabel (i18n ("&Granularity:"), this); + + QLabel *amountLabel = new QLabel (i18n ("&Amount:"), this); + + m_granularityInput = new KDoubleNumInput (this); + m_granularityInput->setRange (0, 1, .1/*step*/, true/*slider*/); + + m_amountInput = new KDoubleNumInput (this); + m_amountInput->setRange (0, 1, .1/*step*/, true/*slider*/); + + granularityLabel->setBuddy (m_granularityInput); + amountLabel->setBuddy (m_amountInput); + + + lay->addWidget (granularityLabel, 0, 0); + lay->addWidget (m_granularityInput, 0, 1); + + lay->addWidget (amountLabel, 1, 0); + lay->addWidget (m_amountInput, 1, 1); + + lay->setColumnStretch (1, 1); + + + connect (m_granularityInput, SIGNAL (valueChanged (double)), + this, SIGNAL (settingsChangedDelayed ())); + + connect (m_amountInput, SIGNAL (valueChanged (double)), + this, SIGNAL (settingsChangedDelayed ())); + } +} + +kpEffectToneEnhanceWidget::~kpEffectToneEnhanceWidget () +{ +} + + +// public virtual [base kpEffectWidgetBase] +QString kpEffectToneEnhanceWidget::caption () const +{ + // TODO: Why doesn't this have a caption? Ditto for the other effects. + return QString(); +} + + +// private +double kpEffectToneEnhanceWidget::amount () const +{ + return m_amountInput ? m_amountInput->value () : 0; +} + +// private +double kpEffectToneEnhanceWidget::granularity () const +{ + return m_granularityInput ? m_granularityInput->value () : 0; +} + + +// public virtual [base kpEffectWidgetBase] +bool kpEffectToneEnhanceWidget::isNoOp () const +{ + // If the "amount" is 0, nothing happens regardless of the granularity. + // Note that if "granularity" is 0 but "amount" > 0, the effect _is_ active. + // Therefore, "granularity" should have no involvement in this check. + if (amount () == 0) + return true; + else + return false; +} + +// public virtual [base kpEffectWidgetBase] +kpImage kpEffectToneEnhanceWidget::applyEffect (const kpImage &image) +{ + return kpEffectToneEnhance::applyEffect (image, + granularity (), amount ()); +} + +// public virtual [base kpEffectWidgetBase] +kpEffectCommandBase *kpEffectToneEnhanceWidget::createCommand ( + kpCommandEnvironment *cmdEnviron) const +{ + return new kpEffectToneEnhanceCommand (granularity (), amount (), + m_actOnSelection, + cmdEnviron); +} + + +#include diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectToneEnhanceWidget.h b/kolourpaint/widgets/imagelib/effects/kpEffectToneEnhanceWidget.h new file mode 100644 index 00000000..825f2d03 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectToneEnhanceWidget.h @@ -0,0 +1,68 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2006 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectToneEnhanceWidget_H +#define kpEffectToneEnhanceWidget_H + + +#include + + +class KDoubleNumInput; + + +class kpEffectToneEnhanceWidget : public kpEffectWidgetBase +{ +Q_OBJECT + +public: + kpEffectToneEnhanceWidget (bool actOnSelection, + QWidget *parent); + virtual ~kpEffectToneEnhanceWidget (); + + virtual QString caption () const; + +private: + double amount () const; + double granularity () const; + +public: + virtual bool isNoOp () const; + virtual kpImage applyEffect (const kpImage &image); + + virtual kpEffectCommandBase *createCommand ( + kpCommandEnvironment *cmdEnviron) const; + +protected: + KDoubleNumInput *m_granularityInput; + KDoubleNumInput *m_amountInput; +}; + + +#endif // kpEffectToneEnhanceWidget_H diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectWidgetBase.cpp b/kolourpaint/widgets/imagelib/effects/kpEffectWidgetBase.cpp new file mode 100644 index 00000000..21e33c1c --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectWidgetBase.cpp @@ -0,0 +1,66 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include + + +kpEffectWidgetBase::kpEffectWidgetBase (bool actOnSelection, + QWidget *parent) + : QWidget (parent), + m_actOnSelection (actOnSelection) +{ +} + +kpEffectWidgetBase::~kpEffectWidgetBase () +{ +} + + +// public +QString kpEffectWidgetBase::caption () const +{ + return QString(); +} + + +// protected +int kpEffectWidgetBase::marginHint () const +{ + return 0; +} + +// protected +int kpEffectWidgetBase::spacingHint () const +{ + return KDialog::spacingHint (); +} + + +#include diff --git a/kolourpaint/widgets/imagelib/effects/kpEffectWidgetBase.h b/kolourpaint/widgets/imagelib/effects/kpEffectWidgetBase.h new file mode 100644 index 00000000..311e7718 --- /dev/null +++ b/kolourpaint/widgets/imagelib/effects/kpEffectWidgetBase.h @@ -0,0 +1,77 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectWidgetBase_H +#define kpEffectWidgetBase_H + + +#include + +#include + + +class kpCommandEnvironment; +class kpEffectCommandBase; + + +class kpEffectWidgetBase : public QWidget +{ +Q_OBJECT + +public: + kpEffectWidgetBase (bool actOnSelection, QWidget *parent); + virtual ~kpEffectWidgetBase (); + +signals: + void settingsChangedNoWaitCursor (); + + void settingsChanged (); + + // (same as settingsChanged() but preview doesn't update until there + // has been no activity for a while - used for sliders in slow effects) + void settingsChangedDelayed (); + +public: + virtual QString caption () const; + + virtual bool isNoOp () const = 0; + virtual kpImage applyEffect (const kpImage &image) = 0; + + virtual kpEffectCommandBase *createCommand ( + kpCommandEnvironment *cmdEnviron) const = 0; + +protected: + int marginHint () const; + int spacingHint () const; + +protected: + bool m_actOnSelection; +}; + + +#endif // kpEffectWidgetBase_H diff --git a/kolourpaint/widgets/kpColorCells.cpp b/kolourpaint/widgets/kpColorCells.cpp new file mode 100644 index 00000000..96b7d51d --- /dev/null +++ b/kolourpaint/widgets/kpColorCells.cpp @@ -0,0 +1,597 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COLOR_CELLS 0 + + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +//--------------------------------------------------------------------- +// +// Table Geometry +// + + +// The number of columns that the table normally has. +const int TableDefaultNumColumns = 11; + +const int TableDefaultWidth = ::TableDefaultNumColumns * 26; + +const int TableDefaultHeight = 52; + + +static int TableNumColumns (const kpColorCollection &colorCol) +{ + if (colorCol.count () == 0) + return 0; + + return ::TableDefaultNumColumns; +} + +static int TableNumRows (const kpColorCollection &colorCol) +{ + const int cols = ::TableNumColumns (colorCol); + if (cols == 0) + return 0; + + return (colorCol.count () + (cols - 1)) / cols; +} + + +static int TableCellWidth (const kpColorCollection &colorCol) +{ + Q_UNUSED (colorCol); + + return ::TableDefaultWidth / ::TableDefaultNumColumns; +} + +static int TableCellHeight (const kpColorCollection &colorCol) +{ + if (::TableNumRows (colorCol) <= 2) + return ::TableDefaultHeight / 2; + else + return ::TableDefaultHeight / 3; +} + + +// +// kpColorCells +// + + +struct kpColorCellsPrivate +{ + Qt::Orientation orientation; + + // REFACTOR: This is data duplication with kpColorCellsBase::color[]. + // We've probably forgotten to synchronize them in some points. + // + // Calls to kpColorCellsBase::setColor() (which also come from + // kpColorCellsBase itself) will automatically update both + // kpColorCellsBase::d->color[] and the table cells. setColor() emits + // colorChanged(), which is caught by our slotColorChanged(), + // which synchronizes this color collection and updates the modified flag. + // + // Avoid calling our grandparent's, QTableWidget's, mutating methods as we + // don't override enough of them, to fire signals that we can catch to update + // this color collection. + // + // If you modify this color collection directly (e.g. in setColorCollection(), + // openColorCollection(), appendRow(), deleteLastRow(), ...), you must work + // the other way and call makeCellsMatchColorCollection() to synchronize + // kpColorCellsBase::d->color[] and the table cells. You still need to update + // the modified flag. + kpColorCollection colorCol; + + KUrl url; + bool isModified; + + bool blockColorChangedSig; +}; + +//--------------------------------------------------------------------- + +kpColorCells::kpColorCells (QWidget *parent, + Qt::Orientation o) + : kpColorCellsBase (parent, 0/*rows for now*/, 0/*cols for now*/), + d (new kpColorCellsPrivate ()) +{ + d->orientation = o; + d->isModified = false; + d->blockColorChangedSig = false; + + + // When a text box is active, clicking to change the background color + // should not move the keyboard focus away from the text box. + setFocusPolicy (Qt::TabFocus); + + setShading (false); // no 3D look + + setAcceptDrops (true); + setAcceptDrags (true); + + + setCellsResizable (false); + + if (o == Qt::Horizontal) + { + // Reserve enough room for the default color collection's cells _and_ + // a vertical scrollbar, which only appears when it's required. + // This ensures that if the vertical scrollbar appears, it does not obscure + // any cells or require the addition of a horizontal scrollbar, which would + // look ugly and take even more precious room. + // + // We do not dynamically reserve room based on the actual number of rows + // of cells, as that would make our containing widgets too big. + setMinimumSize (::TableDefaultWidth + frameWidth () * 2 + + verticalScrollBar()->sizeHint().width(), + ::TableDefaultHeight + frameWidth () * 2); + } + else + { + Q_ASSERT (!"implemented"); + } + + setVerticalScrollBarPolicy (Qt::ScrollBarAsNeeded); + + // The default QTableWidget policy of QSizePolicy::Expanding forces our + // containing widgets to get too big. Override it. + setSizePolicy (QSizePolicy::Minimum, QSizePolicy::Minimum); + + + connect (this, SIGNAL (colorSelected (int, const QColor &, Qt::MouseButton)), + SLOT (slotColorSelected (int, const QColor &, Qt::MouseButton))); + connect (this, SIGNAL (colorDoubleClicked (int, const QColor &)), + SLOT (slotColorDoubleClicked (int, const QColor &))); + connect (this, SIGNAL (colorChanged (int, const QColor &)), + SLOT (slotColorChanged (int, const QColor &))); + + + setColorCollection (DefaultColorCollection ()); + + + setWhatsThis ( + i18n ( + "" + + "

To select the foreground color that tools use to draw," + " left-click on a filled-in color cell." + " To select the background color, right-click instead.

" + + "

To change the color of a color cell itself, double-click on it.

" + + "

You can also swap the color of a filled-in cell with any other" + " cell using drag and drop." + " Also, if you hold down the Ctrl key, the destination" + " cell's color will be" + " overwritten, instead of being swapped with the color of the source cell.

" + + "
")); +} + +//--------------------------------------------------------------------- + +kpColorCells::~kpColorCells () +{ + delete d; +} + +//--------------------------------------------------------------------- + + +// public static +kpColorCollection kpColorCells::DefaultColorCollection () +{ + return kpDefaultColorCollection (); +} + +//--------------------------------------------------------------------- + + +// public +Qt::Orientation kpColorCells::orientation () const +{ + return d->orientation; +} + +//--------------------------------------------------------------------- + +// public +void kpColorCells::setOrientation (Qt::Orientation o) +{ + d->orientation = o; + + makeCellsMatchColorCollection (); +} + +//--------------------------------------------------------------------- + + +// protected +// OPT: Find out why this is being called multiple times on startup. +void kpColorCells::makeCellsMatchColorCollection () +{ + int c, r; + + if (orientation () == Qt::Horizontal) + { + c = ::TableNumColumns (d->colorCol); + r = ::TableNumRows (d->colorCol); + } + else + { + c = ::TableNumRows (d->colorCol); + r = ::TableNumColumns (d->colorCol);; + } + +#if DEBUG_KP_COLOR_CELLS + kDebug () << "kpColorCells::makeCellsMatchColorCollection():" + << "r=" << r << "c=" << c; + kDebug () << "verticalScrollBar=" << verticalScrollBar () + << " sizeHint=" + << (verticalScrollBar () ? + verticalScrollBar ()->sizeHint () : + QSize (-12, -34)); +#endif + + // Delete all cell widgets. This ensures that there will be no left-over + // cell widgets, for the colors in the new color collection that are + // actually invalid (which should not have cell widgets). + clearContents (); + + setRowCount (r); + setColumnCount (c); + + + int CellWidth = ::TableCellWidth (d->colorCol), + CellHeight = ::TableCellHeight (d->colorCol); + + // TODO: Take a screenshot of KolourPaint, magnify it and you'll find the + // cells don't have exactly the sizes requested here. e.g. the + // top row of cells is 1 pixel shorter than the bottom row. There + // are probably other glitches. + for (int y = 0; y < r; y++) + setRowHeight (y, CellHeight); + for (int x = 0; x < c; x++) + setColumnWidth (x, CellWidth); + + + const bool oldBlockColorChangedSig = d->blockColorChangedSig; + d->blockColorChangedSig = true; + // The last "(rowCount() * columnCount()) - d->colorCol.count()" cells + // will be empty because we did not initialize them. + for (int i = 0; i < d->colorCol.count (); i++) + { + int y, x; + int pos; + + if (orientation () == Qt::Horizontal) + { + y = i / c; + x = i % c; + pos = i; + } + else + { + y = i % r; + x = i / r; + // int x = c - 1 - i / r; + pos = y * c + x; + } + #if DEBUG_KP_COLOR_CELLS && 0 + kDebug () << "\tSetting cell " << i << ": y=" << y << " x=" << x + << " pos=" << pos << endl; + kDebug () << "\t\tcolor=" << (int *) d->colorCol.color (i).rgba() + << "isValid=" << d->colorCol.color (i).isValid (); + #endif + + // (color may be invalid resulting in a hole in the middle of the table) + setColor (pos, d->colorCol.color (i)); + //this->setToolTip( cellGeometry (y, x), colors [i].name ()); + } + d->blockColorChangedSig = oldBlockColorChangedSig; +} + +//--------------------------------------------------------------------- + + +bool kpColorCells::isModified () const +{ + return d->isModified; +} + +//--------------------------------------------------------------------- + +void kpColorCells::setModified (bool yes) +{ +#if DEBUG_KP_COLOR_CELLS + kDebug () << "kpColorCells::setModified(" << yes << ")"; +#endif + + if (yes == d->isModified) + return; + + d->isModified = yes; + + emit isModifiedChanged (yes); +} + +//--------------------------------------------------------------------- + +void kpColorCells::setModified () +{ + setModified (true); +} + +//--------------------------------------------------------------------- + + +KUrl kpColorCells::url () const +{ + return d->url; +} + +//--------------------------------------------------------------------- + +QString kpColorCells::name () const +{ + return d->colorCol.name (); +} + +//--------------------------------------------------------------------- + + +const kpColorCollection *kpColorCells::colorCollection () const +{ + return &d->colorCol; +} + +//--------------------------------------------------------------------- + + +void kpColorCells::ensureHaveAtLeastOneRow () +{ + if (d->colorCol.count () == 0) + d->colorCol.resize (::TableDefaultNumColumns); +} + +//--------------------------------------------------------------------- + +void kpColorCells::setColorCollection (const kpColorCollection &colorCol, const KUrl &url) +{ + d->colorCol = colorCol; + ensureHaveAtLeastOneRow (); + + d->url = url; + setModified (false); + + makeCellsMatchColorCollection (); + + emit rowCountChanged (rowCount ()); + emit urlChanged (d->url); + emit nameChanged (name ()); +} + +//--------------------------------------------------------------------- + + +bool kpColorCells::openColorCollection (const KUrl &url) +{ + // (this will pop up an error dialog on failure) + if (d->colorCol.open (url, this)) + { + ensureHaveAtLeastOneRow (); + + d->url = url; + setModified (false); + + makeCellsMatchColorCollection (); + + emit rowCountChanged (rowCount ()); + emit urlChanged (d->url); + emit nameChanged (name ()); + + return true; + } + + return false; +} + +//--------------------------------------------------------------------- + +bool kpColorCells::saveColorCollectionAs (const KUrl &url) +{ + // (this will pop up an error dialog on failure) + if (d->colorCol.saveAs (url, true/*show overwrite prompt*/, this)) + { + d->url = url; + setModified (false); + + emit urlChanged (d->url); + return true; + } + + return false; +} + +//--------------------------------------------------------------------- + +bool kpColorCells::saveColorCollection () +{ + // (this will pop up an error dialog on failure) + if (d->colorCol.saveAs (d->url, false/*no overwrite prompt*/, this)) + { + setModified (false); + return true; + } + + return false; +} + +//--------------------------------------------------------------------- + + +void kpColorCells::appendRow () +{ + // This is the easiest implementation: change the color collection + // and then synchronize the table cells. The other way is to call + // setRowCount() and then, synchronize the color collection. + + const int targetNumCells = (rowCount () + 1) * ::TableDefaultNumColumns; + d->colorCol.resize (targetNumCells); + + setModified (true); + + makeCellsMatchColorCollection (); + + emit rowCountChanged (rowCount ()); +} + +//--------------------------------------------------------------------- + +void kpColorCells::deleteLastRow () +{ + // This is the easiest implementation: change the color collection + // and then synchronize the table cells. The other way is to call + // setRowCount() and then, synchronize the color collection. + + const int targetNumCells = + qMax (0, (rowCount () - 1) * ::TableDefaultNumColumns); + d->colorCol.resize (targetNumCells); + + // If there was only one row of colors to start with, the effect of this + // line (after the above resize()) is to change that row to a row of + // invalid colors. + ensureHaveAtLeastOneRow (); + + setModified (true); + + makeCellsMatchColorCollection (); + + emit rowCountChanged (rowCount ()); +} + +//--------------------------------------------------------------------- + + +// protected virtual [base QWidget] +void kpColorCells::contextMenuEvent (QContextMenuEvent *e) +{ + // Eat right-mouse press to prevent it from getting to the toolbar. + e->accept (); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpColorCells::slotColorSelected (int cell, const QColor &color, + Qt::MouseButton button) +{ +#if DEBUG_KP_COLOR_CELLS + kDebug () << "kpColorCells::slotColorSelected(cell=" << cell + << ") mouseButton = " << button + << " rgb=" << (int *) color.rgba() + << endl; +#else + Q_UNUSED (cell); +#endif + + if (button == Qt::LeftButton) + { + emit foregroundColorChanged (kpColor (color.rgba())); + } + else if (button == Qt::RightButton) + { + emit backgroundColorChanged (kpColor (color.rgba())); + } + + // REFACTOR: Make selectedness configurable inside kpColorCellsBase? + // + // Deselect the selected cell (selected by above kpColorCellsBase::mouseReleaseEvent()). + // KolourPaint's palette has no concept of a current cell/color: you can + // pick a color but you can't mark a cell as selected. In any case, a + // selected cell would be rendered as violet, which would ruin the cell. + // + // setSelectionMode (kpColorCellsBase::NoSelection); does not work so we + // clearSelection(). I think setSelectionMode() concerns when the user + // directly selects a cell - not when kpColorCellsBase::mouseReleaseEvent() + // selects a cell programmatically. + clearSelection (); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpColorCells::slotColorDoubleClicked (int cell, const QColor &) +{ + KColorDialog dialog(this); + dialog.setColor(kpColorCellsBase::color(cell)); + dialog.setAlphaChannelEnabled(true); + dialog.setButtons(KDialog::Ok | KDialog::Cancel); + if ( dialog.exec() == QDialog::Accepted ) + setColor (cell, dialog.color()); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpColorCells::slotColorChanged (int cell, const QColor &color) +{ +#if DEBUG_KP_COLOR_CELLS + kDebug () << "cell=" << cell << "color=" << (const int *) color.rgba() + << "d->colorCol.count()=" << d->colorCol.count (); +#endif + + if (d->blockColorChangedSig) + return; + + // Cater for adding new colors to the end. + if (cell >= d->colorCol.count ()) + d->colorCol.resize (cell + 1); + + // TODO: We lose color names on a color swap (during drag-and-drop). + const int ret = d->colorCol.changeColor (cell, color, + QString ()/*color name*/); + Q_ASSERT (ret == cell); + + setModified (true); +} + +#include diff --git a/kolourpaint/widgets/kpColorCells.h b/kolourpaint/widgets/kpColorCells.h new file mode 100644 index 00000000..f17bb5b6 --- /dev/null +++ b/kolourpaint/widgets/kpColorCells.h @@ -0,0 +1,169 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpColorCells_H +#define kpColorCells_H + + +#include + +#include + + +class QDropEvent; + +class kpColorCollection; + +class kpColor; + + +// This widget consists of rows of 11 cells of colors. The cells become +// shorter as soon as there are 3 rows. After that, a vertical scrollbar +// is usually displayed. +// +// By default, it is set to the DefaultColorCollection(), with 2 rows. +// +// +// Cell widgets might not exist for 2 reasons: +// +// 1. The respective colors in the color collection are invalid. +// An easy way to create this situation is to appendRow(). +// +// 2. The number of colors in the color collection is not divisible by +// the columnCount() [currently fixed at 11], so some cells in the +// last row might not be linked to colors in the color collection. +// +// Currently, this class always ensures that there is at least one +// visual/table row (which might contain no cell widgets). +// +// +// To determine where the color collection came from: +// +// 1. If url() is non-empty, it came from a file. +// +// 2. If url() is empty: +// a) If name() is non-empty, it came from KDE-managed color collection. +// b) If name() is empty, it came from DefaultColorCollection(). +// +// +// See also the API documentation for kpColorCellsBase. +// +// TODO: For now, only horizontal orientation is supported. +class kpColorCells : public kpColorCellsBase +{ +Q_OBJECT + +public: + kpColorCells (QWidget *parent, + Qt::Orientation o = Qt::Horizontal); + virtual ~kpColorCells (); + + static kpColorCollection DefaultColorCollection (); + + Qt::Orientation orientation () const; + void setOrientation (Qt::Orientation o); + +protected: + void makeCellsMatchColorCollection (); + +public: + bool isModified () const; + // (this emits isModifiedChanged() if the modified state changes) + void setModified (bool yes); +public slots: + // (this emits isModifiedChanged() if the modified state changes) + void setModified (); + +public: + // The source URL of the kpColorCollection. Empty for color + // collections that did not come from files. + KUrl url () const; + + // The name of the kpColorCollection. Empty for color collections + // managed by KDE. + QString name () const; + + const kpColorCollection *colorCollection () const; + +private: + // Ensures there's a least one row of cells, to avoid a confusing UI. + void ensureHaveAtLeastOneRow (); +public: + void setColorCollection (const kpColorCollection &colorCol, + const KUrl &url = KUrl ()); + + bool openColorCollection (const KUrl &url); + bool saveColorCollectionAs (const KUrl &url); + bool saveColorCollection (); + + // These add and delete visual/table rows, independent of whether the number + // of colors in the color collection is divisible by the columnCount() + // [currently fixed at 11]. + // + // For instance, if you only had 15 colors in the color collection, there are + // visually 2 rows (22 cells in total): + // + // 1. appendRow() will add a visual row so that there will be 3 visual rows + // (33 cells in total). (22 - 15) + 11 invalid colors will be added to + // the back of the color collection. Note that invalid colors are not + // saved by kpColorCollection, so those new cells not initialized by the + // user will not be saved. + // 2. deleteRow() will delete a visual row so that there will be 1 visual + // row (11 cells in total) remaining. (15 - 11) colors will be deleted + // from the back of the color collection. + void appendRow (); + void deleteLastRow (); + +signals: + void foregroundColorChanged (const kpColor &color); + void backgroundColorChanged (const kpColor &color); + + void rowCountChanged (int rowCount); + + void nameChanged (const QString &name); + void urlChanged (const KUrl &url); + + // Emitted when setModified() is called and the modified state changes. + // It may be called at other times, even when the modified state did + // not change. + void isModifiedChanged (bool isModified); + +protected: + virtual void contextMenuEvent (QContextMenuEvent *e); + +protected slots: + void slotColorSelected (int cell, const QColor &color, Qt::MouseButton button); + void slotColorDoubleClicked (int cell, const QColor &color); + void slotColorChanged (int cell, const QColor &color); + +private: + struct kpColorCellsPrivate * const d; +}; + + +#endif // kpColorCells_H diff --git a/kolourpaint/widgets/kpColorPalette.cpp b/kolourpaint/widgets/kpColorPalette.cpp new file mode 100644 index 00000000..6772e50e --- /dev/null +++ b/kolourpaint/widgets/kpColorPalette.cpp @@ -0,0 +1,125 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COLOR_PALETTE 0 + + +#include + +#include + +#include +#include + +//--------------------------------------------------------------------- + +struct kpColorPalettePrivate +{ + Qt::Orientation orientation; + + QBoxLayout *boxLayout; + + kpTransparentColorCell *transparentColorCell; + + kpColorCells *colorCells; +}; + +//--------------------------------------------------------------------- + +kpColorPalette::kpColorPalette (QWidget *parent, Qt::Orientation o) + : QWidget (parent), + d (new kpColorPalettePrivate ()) +{ + d->boxLayout = 0; + + d->transparentColorCell = new kpTransparentColorCell (this); + connect (d->transparentColorCell, SIGNAL (foregroundColorChanged (const kpColor &)), + this, SIGNAL (foregroundColorChanged (const kpColor &))); + connect (d->transparentColorCell, SIGNAL (backgroundColorChanged (const kpColor &)), + this, SIGNAL (backgroundColorChanged (const kpColor &))); + + d->colorCells = new kpColorCells (this); + connect (d->colorCells, SIGNAL (foregroundColorChanged (const kpColor &)), + this, SIGNAL (foregroundColorChanged (const kpColor &))); + connect (d->colorCells, SIGNAL (backgroundColorChanged (const kpColor &)), + this, SIGNAL (backgroundColorChanged (const kpColor &))); + + setOrientation (o); +} + +//--------------------------------------------------------------------- + +kpColorPalette::~kpColorPalette () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// public +Qt::Orientation kpColorPalette::orientation () const +{ + return d->orientation; +} + +//--------------------------------------------------------------------- + +void kpColorPalette::setOrientation (Qt::Orientation o) +{ + d->colorCells->setOrientation (o); + + delete d->boxLayout; + + if (o == Qt::Horizontal) + { + d->boxLayout = new QBoxLayout (QBoxLayout::LeftToRight, this); + d->boxLayout->addWidget (d->transparentColorCell, 0/*stretch*/, Qt::AlignTop); + d->boxLayout->addWidget (d->colorCells); + } + else + { + d->boxLayout = new QBoxLayout (QBoxLayout::TopToBottom, this); + d->boxLayout->addWidget (d->transparentColorCell, 0/*stretch*/, Qt::AlignTop); + d->boxLayout->addWidget (d->colorCells); + } + d->boxLayout->setSpacing (5); + + d->orientation = o; +} + +//--------------------------------------------------------------------- + +// public +kpColorCells *kpColorPalette::colorCells () const +{ + return d->colorCells; +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/widgets/kpColorPalette.h b/kolourpaint/widgets/kpColorPalette.h new file mode 100644 index 00000000..fd68cf07 --- /dev/null +++ b/kolourpaint/widgets/kpColorPalette.h @@ -0,0 +1,63 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpColorPalette_H +#define kpColorPalette_H + + +#include + + +class kpColor; +class kpColorCells; + + +class kpColorPalette : public QWidget +{ +Q_OBJECT + +public: + kpColorPalette (QWidget *parent, + Qt::Orientation o = Qt::Horizontal); + virtual ~kpColorPalette (); + + Qt::Orientation orientation () const; + void setOrientation (Qt::Orientation o); + + kpColorCells *colorCells () const; + +signals: + void foregroundColorChanged (const kpColor &color); + void backgroundColorChanged (const kpColor &color); + +private: + struct kpColorPalettePrivate * const d; +}; + + +#endif // kpColorPalette_H diff --git a/kolourpaint/widgets/kpDefaultColorCollection.cpp b/kolourpaint/widgets/kpDefaultColorCollection.cpp new file mode 100644 index 00000000..2548710b --- /dev/null +++ b/kolourpaint/widgets/kpDefaultColorCollection.cpp @@ -0,0 +1,71 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include + + +kpDefaultColorCollection::kpDefaultColorCollection () +{ + kpColor colors [] = + { + kpColor::Black, + kpColor::Gray, + kpColor::Red, + kpColor::Orange, + kpColor::Yellow, + kpColor::Green, + kpColor::Aqua, + kpColor::Blue, + kpColor::Purple, + kpColor::Pink, + kpColor::LightGreen, + + kpColor::White, + kpColor::LightGray, + kpColor::DarkRed, + kpColor::DarkOrange, + kpColor::DarkYellow, + kpColor::DarkGreen, + kpColor::DarkAqua, + kpColor::DarkBlue, + kpColor::DarkPurple, + kpColor::LightBlue, + kpColor::Tan + }; + + for (int i = 0; i < (int) (sizeof (colors) / sizeof (colors [0])); i++) + { + addColor (colors [i].toQColor ()); + } +} + +kpDefaultColorCollection::~kpDefaultColorCollection () +{ +} diff --git a/kolourpaint/widgets/kpDefaultColorCollection.h b/kolourpaint/widgets/kpDefaultColorCollection.h new file mode 100644 index 00000000..d34704dc --- /dev/null +++ b/kolourpaint/widgets/kpDefaultColorCollection.h @@ -0,0 +1,50 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDefaultColorCollection_H +#define kpDefaultColorCollection_H + + +#include + + +// +// The default set of colors offered by KolourPaint to the user. +// +// It contains all of the ordinary colors (black, white, gray, colors of +// the rainbow etc.) and a few others. +// +class kpDefaultColorCollection : public kpColorCollection +{ +public: + kpDefaultColorCollection (); + ~kpDefaultColorCollection (); +}; + + +#endif // kpDefaultColorCollection_H diff --git a/kolourpaint/widgets/kpDocumentSaveOptionsWidget.cpp b/kolourpaint/widgets/kpDocumentSaveOptionsWidget.cpp new file mode 100644 index 00000000..3a1e2807 --- /dev/null +++ b/kolourpaint/widgets/kpDocumentSaveOptionsWidget.cpp @@ -0,0 +1,769 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET 0 + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +kpDocumentSaveOptionsWidget::kpDocumentSaveOptionsWidget ( + const QImage &docPixmap, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + QWidget *parent) + : QWidget (parent), + m_visualParent (parent) +{ + init (); + setDocumentSaveOptions (saveOptions); + setDocumentPixmap (docPixmap); + setDocumentMetaInfo (metaInfo); +} + +kpDocumentSaveOptionsWidget::kpDocumentSaveOptionsWidget ( + QWidget *parent) + : QWidget (parent), + m_visualParent (parent) +{ + init (); +} + +// private +void kpDocumentSaveOptionsWidget::init () +{ + m_documentPixmap = 0; + m_previewDialog = 0; + m_visualParent = 0; + + + m_colorDepthLabel = new QLabel (i18n ("Convert &to:"), this); + m_colorDepthCombo = new KComboBox (this); + + m_colorDepthSpaceWidget = new QWidget (this); + + m_qualityLabel = new QLabel (i18n ("Quali&ty:"), this); + m_qualityInput = new KIntNumInput (this); + // Note that we set min to 1 not 0 since "0 Quality" is a bit misleading + // and 101 quality settings would be weird. So we lose 1 quality setting + // according to QImage::save(). + // TODO: 100 quality is also misleading since that implies perfect quality. + m_qualityInput->setRange (1, 100); + + m_previewButton = new KPushButton (i18n ("&Preview"), this); + m_previewButton->setCheckable (true); + + + m_colorDepthLabel->setBuddy (m_colorDepthCombo); + + m_qualityLabel->setBuddy (m_qualityInput); + + + QHBoxLayout *lay = new QHBoxLayout (this); + lay->setSpacing(KDialog::spacingHint ()); + lay->setMargin(0/*margin*/); + + lay->addWidget (m_colorDepthLabel, 0/*stretch*/, Qt::AlignLeft); + lay->addWidget (m_colorDepthCombo, 0/*stretch*/); + + lay->addWidget (m_colorDepthSpaceWidget, 1/*stretch*/); + + lay->addWidget (m_qualityLabel, 0/*stretch*/, Qt::AlignLeft); + lay->addWidget (m_qualityInput, 2/*stretch*/); + + lay->addWidget (m_previewButton, 0/*stretch*/, Qt::AlignRight); + + + connect (m_colorDepthCombo, SIGNAL (activated (int)), + this, SLOT (slotColorDepthSelected ())); + connect (m_colorDepthCombo, SIGNAL (activated (int)), + this, SLOT (updatePreview ())); + + connect (m_qualityInput, SIGNAL (valueChanged (int)), + this, SLOT (updatePreviewDelayed ())); + + connect (m_previewButton, SIGNAL (toggled (bool)), + this, SLOT (showPreview (bool))); + + + m_updatePreviewDelay = 200/*ms*/; + + m_updatePreviewTimer = new QTimer (this); + m_updatePreviewTimer->setSingleShot (true); + connect (m_updatePreviewTimer, SIGNAL (timeout ()), + this, SLOT (updatePreview ())); + + m_updatePreviewDialogLastRelativeGeometryTimer = new QTimer (this); + connect (m_updatePreviewDialogLastRelativeGeometryTimer, SIGNAL (timeout ()), + this, SLOT (updatePreviewDialogLastRelativeGeometry ())); + + + setMode (None); + + slotColorDepthSelected (); +} + +kpDocumentSaveOptionsWidget::~kpDocumentSaveOptionsWidget () +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsWidget::()"; +#endif + hidePreview (); + + delete m_documentPixmap; +} + + +// public +void kpDocumentSaveOptionsWidget::setVisualParent (QWidget *visualParent) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsWidget::setVisualParent(" + << visualParent << ")" << endl; +#endif + + m_visualParent = visualParent; +} + + +// protected +bool kpDocumentSaveOptionsWidget::mimeTypeHasConfigurableColorDepth () const +{ + return kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth (mimeType ()); +} + +// protected +bool kpDocumentSaveOptionsWidget::mimeTypeHasConfigurableQuality () const +{ + return kpDocumentSaveOptions::mimeTypeHasConfigurableQuality (mimeType ()); +} + + +// public +QString kpDocumentSaveOptionsWidget::mimeType () const +{ + return m_baseDocumentSaveOptions.mimeType (); +} + +// public slots +void kpDocumentSaveOptionsWidget::setMimeType (const QString &string) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsWidget::setMimeType(" << string + << ") maxColorDepth=" + << kpDocumentSaveOptions::mimeTypeMaximumColorDepth (string) + << endl; +#endif + + const int newMimeTypeMaxDepth = + kpDocumentSaveOptions::mimeTypeMaximumColorDepth (string); + +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\toldMimeType=" << mimeType () + << " maxColorDepth=" + << kpDocumentSaveOptions::mimeTypeMaximumColorDepth ( + mimeType ()) + << endl; +#endif + + if (mimeType ().isEmpty () || + kpDocumentSaveOptions::mimeTypeMaximumColorDepth (mimeType ()) != + newMimeTypeMaxDepth) + { + m_colorDepthCombo->clear (); + + m_colorDepthCombo->insertItem (0, i18n ("Monochrome")); + m_colorDepthCombo->insertItem (1, i18n ("Monochrome (Dithered)")); + + if (newMimeTypeMaxDepth >= 8) + { + m_colorDepthCombo->insertItem (2, i18n ("256 Color")); + m_colorDepthCombo->insertItem (3, i18n ("256 Color (Dithered)")); + } + + if (newMimeTypeMaxDepth >= 24) + { + m_colorDepthCombo->insertItem (4, i18n ("24-bit Color")); + } + + if (m_colorDepthComboLastSelectedItem >= 0 && + m_colorDepthComboLastSelectedItem < m_colorDepthCombo->count ()) + { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\tsetting colorDepthCombo to " + << m_colorDepthComboLastSelectedItem << endl; + #endif + + m_colorDepthCombo->setCurrentIndex (m_colorDepthComboLastSelectedItem); + } + else + { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\tsetting colorDepthCombo to max item since" + << " m_colorDepthComboLastSelectedItem=" + << m_colorDepthComboLastSelectedItem + << " out of range" << endl; + #endif + + m_colorDepthCombo->setCurrentIndex (m_colorDepthCombo->count () - 1); + } + } + + + m_baseDocumentSaveOptions.setMimeType (string); + + if (mimeTypeHasConfigurableColorDepth ()) + setMode (ColorDepth); + else if (mimeTypeHasConfigurableQuality ()) + setMode (Quality); + else + setMode (None); + + updatePreview (); +} + + +// public +int kpDocumentSaveOptionsWidget::colorDepth () const +{ + if (mode () & ColorDepth) + { + // The returned values match QImage's supported depths. + switch (m_colorDepthCombo->currentIndex ()) + { + case 0: + case 1: + return 1; + + case 2: + case 3: + return 8; + + case 4: + // 24-bit is known as 32-bit with QImage. + return 32; + + default: + return kpDocumentSaveOptions::invalidColorDepth (); + } + } + else + { + return m_baseDocumentSaveOptions.colorDepth (); + } +} + +// public +bool kpDocumentSaveOptionsWidget::dither () const +{ + if (mode () & ColorDepth) + { + return (m_colorDepthCombo->currentIndex () == 1 || + m_colorDepthCombo->currentIndex () == 3); + } + else + { + return m_baseDocumentSaveOptions.dither (); + } +} + +// protected static +int kpDocumentSaveOptionsWidget::colorDepthComboItemFromColorDepthAndDither ( + int depth, bool dither) +{ + if (depth == 1) + { + if (!dither) + { + return 0; + } + else + { + return 1; + } + } + else if (depth == 8) + { + if (!dither) + { + return 2; + } + else + { + return 3; + } + } + else if (depth == 32) + { + return 4; + } + else + { + return -1; + } +} + +// public slots +void kpDocumentSaveOptionsWidget::setColorDepthDither (int newDepth, bool newDither) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsWidget::setColorDepthDither(" + << "depth=" << newDepth + << ",dither=" << newDither + << ")" << endl; +#endif + + m_baseDocumentSaveOptions.setColorDepth (newDepth); + m_baseDocumentSaveOptions.setDither (newDither); + + + const int comboItem = colorDepthComboItemFromColorDepthAndDither ( + newDepth, newDither); + // TODO: Ignoring when comboItem >= m_colorDepthCombo->count() is wrong. + // This happens if this mimeType has configurable colour depth + // and an incorrect maximum colour depth (less than a QImage of + // this mimeType, opened by kpDocument). + if (comboItem >= 0 && comboItem < m_colorDepthCombo->count ()) + m_colorDepthCombo->setCurrentIndex (comboItem); + + + slotColorDepthSelected (); +} + + +// protected slot +void kpDocumentSaveOptionsWidget::slotColorDepthSelected () +{ + if (mode () & ColorDepth) + { + m_colorDepthComboLastSelectedItem = m_colorDepthCombo->currentIndex (); + } + else + { + m_colorDepthComboLastSelectedItem = + colorDepthComboItemFromColorDepthAndDither ( + m_baseDocumentSaveOptions.colorDepth (), + m_baseDocumentSaveOptions.dither ()); + } + +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsWidget::slotColorDepthSelected()" + << " mode&ColorDepth=" << (mode () & ColorDepth) + << " colorDepthComboLastSelectedItem=" + << m_colorDepthComboLastSelectedItem + << endl; +#endif +} + + +// public +int kpDocumentSaveOptionsWidget::quality () const +{ + if (mode () & Quality) + { + return m_qualityInput->value (); + } + else + { + return m_baseDocumentSaveOptions.quality (); + } +} + +// public +void kpDocumentSaveOptionsWidget::setQuality (int newQuality) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsWidget::setQuality(" + << newQuality << ")" << endl; +#endif + + m_baseDocumentSaveOptions.setQuality (newQuality); + m_qualityInput->setValue (newQuality == -1/*QImage::save() default*/ ? + 75 : + newQuality); +} + + +// public +kpDocumentSaveOptions kpDocumentSaveOptionsWidget::documentSaveOptions () const +{ + return kpDocumentSaveOptions (mimeType (), colorDepth (), dither (), quality ()); +} + +// public +void kpDocumentSaveOptionsWidget::setDocumentSaveOptions ( + const kpDocumentSaveOptions &saveOptions) +{ + setMimeType (saveOptions.mimeType ()); + setColorDepthDither (saveOptions.colorDepth (), saveOptions.dither ()); + setQuality (saveOptions.quality ()); +} + + +// public +void kpDocumentSaveOptionsWidget::setDocumentPixmap (const QImage &documentPixmap) +{ + delete m_documentPixmap; + m_documentPixmap = new QImage (documentPixmap); + + updatePreview (); +} + +// public +void kpDocumentSaveOptionsWidget::setDocumentMetaInfo ( + const kpDocumentMetaInfo &metaInfo) +{ + m_documentMetaInfo = metaInfo; + + updatePreview (); +} + + +// public +kpDocumentSaveOptionsWidget::Mode kpDocumentSaveOptionsWidget::mode () const +{ + return m_mode; +} + +// public +void kpDocumentSaveOptionsWidget::setMode (Mode mode) +{ + m_mode = mode; + + + // If mode == None, we show still show the Color Depth widgets but disabled + m_colorDepthLabel->setVisible (mode != Quality); + m_colorDepthCombo->setVisible (mode != Quality); + m_colorDepthSpaceWidget->setVisible (mode != Quality); + + m_qualityLabel->setVisible (mode == Quality); + m_qualityInput->setVisible (mode == Quality); + + + m_colorDepthLabel->setEnabled (mode == ColorDepth); + m_colorDepthCombo->setEnabled (mode == ColorDepth); + + m_qualityLabel->setEnabled (mode == Quality); + m_qualityInput->setEnabled (mode == Quality); + + + // SYNC: HACK: When changing between color depth and quality widgets, + // we change the height of "this", causing the text on the labels + // to move but the first instance of the text doesn't get erased. + // Qt bug. + QTimer::singleShot (0, this, SLOT (repaintLabels ())); +} + +// protected slot +void kpDocumentSaveOptionsWidget::repaintLabels () +{ + if (mode () != Quality) + m_colorDepthLabel->repaint (); + if (mode () == Quality) + m_qualityLabel->repaint (); +} + + +// protected slot +void kpDocumentSaveOptionsWidget::showPreview (bool yes) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsWidget::showPreview(" << yes << ")" + << " m_previewDialog=" << bool (m_previewDialog) + << endl; +#endif + + if (yes == bool (m_previewDialog)) + return; + + if (!m_visualParent) + return; + + if (yes) + { + m_previewDialog = new kpDocumentSaveOptionsPreviewDialog( m_visualParent ); + m_previewDialog->setObjectName( QLatin1String( "previewSaveDialog" ) ); + updatePreview (); + + connect (m_previewDialog, SIGNAL (finished ()), + this, SLOT (hidePreview ())); + + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupPreviewSave); + + if (cfg.hasKey (kpSettingPreviewSaveUpdateDelay)) + { + m_updatePreviewDelay = cfg.readEntry (kpSettingPreviewSaveUpdateDelay, 0); + } + else + { + cfg.writeEntry (kpSettingPreviewSaveUpdateDelay, m_updatePreviewDelay); + cfg.sync (); + } + + if (m_updatePreviewDelay < 0) + m_updatePreviewDelay = 0; + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\tread cfg preview dialog update delay=" + << m_updatePreviewDelay + << endl; + #endif + + + if (m_previewDialogLastRelativeGeometry.isEmpty ()) + { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\tread cfg preview dialog last rel geometry"; + #endif + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupPreviewSave); + + m_previewDialogLastRelativeGeometry = cfg.readEntry ( + kpSettingPreviewSaveGeometry, QRect ()); + } + + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\tpreviewDialogLastRelativeGeometry=" + << m_previewDialogLastRelativeGeometry + << " visualParent->rect()=" << m_visualParent->rect () + << endl; + #endif + + QRect relativeGeometry; + if (!m_previewDialogLastRelativeGeometry.isEmpty () && + m_visualParent->rect ().intersects (m_previewDialogLastRelativeGeometry)) + { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\tok"; + #endif + relativeGeometry = m_previewDialogLastRelativeGeometry; + } + else + { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\t\tinvalid"; + #endif + const int margin = 20; + + relativeGeometry = + QRect (m_visualParent->width () - + m_previewDialog->preferredMinimumSize ().width () - + margin, + margin * 2, // Avoid folder combo + m_previewDialog->preferredMinimumSize ().width (), + m_previewDialog->preferredMinimumSize ().height ()); + } + + + const QRect globalGeometry = + kpWidgetMapper::toGlobal (m_visualParent, + relativeGeometry); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\trelativeGeometry=" << relativeGeometry + << " globalGeometry=" << globalGeometry + << endl; + #endif + + m_previewDialog->resize (globalGeometry.size ()); + m_previewDialog->move (globalGeometry.topLeft ()); + + + m_previewDialog->show (); + + + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\tgeometry after show=" + << QRect (m_previewDialog->x (), m_previewDialog->y (), + m_previewDialog->width (), m_previewDialog->height ()) + << endl; + #endif + + updatePreviewDialogLastRelativeGeometry (); + + connect (m_previewDialog, SIGNAL (moved ()), + this, SLOT (updatePreviewDialogLastRelativeGeometry ())); + connect (m_previewDialog, SIGNAL (resized ()), + this, SLOT (updatePreviewDialogLastRelativeGeometry ())); + + m_updatePreviewDialogLastRelativeGeometryTimer->start (200/*ms*/); + } + else + { + m_updatePreviewDialogLastRelativeGeometryTimer->stop (); + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupPreviewSave); + + cfg.writeEntry (kpSettingPreviewSaveGeometry, m_previewDialogLastRelativeGeometry); + cfg.sync (); + + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\tsaving preview geometry " + << m_previewDialogLastRelativeGeometry + << " (Qt would have us believe " + << kpWidgetMapper::fromGlobal (m_visualParent, + QRect (m_previewDialog->x (), m_previewDialog->y (), + m_previewDialog->width (), m_previewDialog->height ())) + << ")" + << endl; + #endif + + m_previewDialog->deleteLater (); + m_previewDialog = 0; + } +} + +// protected slot +void kpDocumentSaveOptionsWidget::hidePreview () +{ + if (m_previewButton->isChecked ()) + m_previewButton->toggle (); +} + + +// protected slot +void kpDocumentSaveOptionsWidget::updatePreviewDelayed () +{ + // (single shot) + m_updatePreviewTimer->start (m_updatePreviewDelay); +} + +// protected slot +void kpDocumentSaveOptionsWidget::updatePreview () +{ + if (!m_previewDialog || !m_documentPixmap) + return; + + + m_updatePreviewTimer->stop (); + + + QApplication::setOverrideCursor (Qt::WaitCursor); + + QByteArray data; + + QBuffer buffer (&data); + buffer.open (QIODevice::WriteOnly); + bool savedOK = kpDocument::savePixmapToDevice (*m_documentPixmap, + &buffer, + documentSaveOptions (), + m_documentMetaInfo, + false/*no lossy prompt*/, + this); + buffer.close (); + + + QImage image; + + // Ignore any failed saves. + // + // Failed saves might literally have written half a file. The final + // save (when the user clicks OK), _will_ fail so we shouldn't have a + // preview even if this "half a file" is actually loadable by + // QImage::loadFormData(). + if (savedOK) + { + const QStringList types = KImageIO::typeForMime (mimeType ()); + // kpDocument::savePixmapToDevice() would have failed otherwise. + Q_ASSERT (!types.isEmpty ()); + + // It's safe to arbitrarily choose the 0th type as any type in the list + // should invoke the same KImageIO image loader. + image.loadFromData (data, types [0].toLatin1 ()); + } + else + { + // Leave as invalid. + // TODO: This code path has not been well tested. + // Will we trigger divide by zero errors in "m_previewDialog"? + // KDE3: Backport any fixes to KDE 3. + } + + // REFACTOR: merge with kpDocument::getPixmapFromFile() + m_previewDialog->setFilePixmapAndSize (image, data.size ()); + + QApplication::restoreOverrideCursor (); +} + +// protected slot +void kpDocumentSaveOptionsWidget::updatePreviewDialogLastRelativeGeometry () +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "kpDocumentSaveOptionsWidget::" + << "updatePreviewDialogLastRelativeGeometry()" + << endl; +#endif + + if (m_previewDialog && m_previewDialog->isVisible ()) + { + m_previewDialogLastRelativeGeometry = + kpWidgetMapper::fromGlobal (m_visualParent, + QRect (m_previewDialog->x (), m_previewDialog->y (), + m_previewDialog->width (), m_previewDialog->height ())); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\tcaching pos = " + << m_previewDialogLastRelativeGeometry + << endl; + #endif + } + else + { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kDebug () << "\tnot visible - ignoring geometry"; + #endif + } +} + + +#include diff --git a/kolourpaint/widgets/kpDocumentSaveOptionsWidget.h b/kolourpaint/widgets/kpDocumentSaveOptionsWidget.h new file mode 100644 index 00000000..5592b430 --- /dev/null +++ b/kolourpaint/widgets/kpDocumentSaveOptionsWidget.h @@ -0,0 +1,157 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDocumentSaveOptionsWidget_H +#define kpDocumentSaveOptionsWidget_H + + +#include +#include +#include + +#include +#include + + +class KComboBox; +class QImage; +class QLabel; +class QTimer; + +class KIntNumInput; +class KPushButton; + +class kpDocumentSaveOptionsPreviewDialog; + + +class kpDocumentSaveOptionsWidget : public QWidget +{ +Q_OBJECT + +public: + kpDocumentSaveOptionsWidget (const QImage &docPixmap, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + QWidget *parent); + kpDocumentSaveOptionsWidget (QWidget *parent); +private: + void init (); +public: + virtual ~kpDocumentSaveOptionsWidget (); + + + // is usually the filedialog + void setVisualParent (QWidget *visualParent); + + +protected: + bool mimeTypeHasConfigurableColorDepth () const; + bool mimeTypeHasConfigurableQuality () const; + +public: + QString mimeType () const; +public slots: + void setMimeType (const QString &string); + +public: + int colorDepth () const; + bool dither () const; +protected: + static int colorDepthComboItemFromColorDepthAndDither (int depth, bool dither); +public slots: + void setColorDepthDither (int depth, + bool dither = kpDocumentSaveOptions::initialDither ()); +protected slots: + void slotColorDepthSelected (); + +public: + int quality () const; +public slots: + void setQuality (int newQuality); + +public: + kpDocumentSaveOptions documentSaveOptions () const; +public slots: + void setDocumentSaveOptions (const kpDocumentSaveOptions &saveOptions); + + +public: + void setDocumentPixmap (const QImage &documentPixmap); + void setDocumentMetaInfo (const kpDocumentMetaInfo &metaInfo); + + +protected: + enum Mode + { + // (mutually exclusive) + None, ColorDepth, Quality + }; + + Mode mode () const; + void setMode (Mode mode); + +protected slots: + void repaintLabels (); + + +protected slots: + void showPreview (bool yes = true); + void hidePreview (); + void updatePreviewDelayed (); + void updatePreview (); + void updatePreviewDialogLastRelativeGeometry (); + + +protected: + QWidget *m_visualParent; + + Mode m_mode; + + QImage *m_documentPixmap; + + kpDocumentSaveOptions m_baseDocumentSaveOptions; + kpDocumentMetaInfo m_documentMetaInfo; + + QLabel *m_colorDepthLabel; + KComboBox *m_colorDepthCombo; + int m_colorDepthComboLastSelectedItem; + QWidget *m_colorDepthSpaceWidget; + + QLabel *m_qualityLabel; + KIntNumInput *m_qualityInput; + + KPushButton *m_previewButton; + kpDocumentSaveOptionsPreviewDialog *m_previewDialog; + QRect m_previewDialogLastRelativeGeometry; + QTimer *m_updatePreviewTimer; + int m_updatePreviewDelay; + QTimer *m_updatePreviewDialogLastRelativeGeometryTimer; +}; + + +#endif // kpDocumentSaveOptionsWidget_H diff --git a/kolourpaint/widgets/kpDualColorButton.cpp b/kolourpaint/widgets/kpDualColorButton.cpp new file mode 100644 index 00000000..0f43c382 --- /dev/null +++ b/kolourpaint/widgets/kpDualColorButton.cpp @@ -0,0 +1,464 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DUAL_COLOR_BUTTON 0 + + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +//--------------------------------------------------------------------- + +kpDualColorButton::kpDualColorButton (QWidget *parent) + : QFrame (parent), + m_dragStartPoint (KP_INVALID_POINT) +{ + setSizePolicy (QSizePolicy::Fixed/*horizontal*/, + QSizePolicy::Fixed/*vertical*/); + setFrameStyle (QFrame::Panel | QFrame::Sunken); + + m_color [0] = kpColor (0, 0, 0); // black + m_color [1] = kpColor (255, 255, 255); // white + + setAcceptDrops (true); +} + +//--------------------------------------------------------------------- + +kpColor kpDualColorButton::color (int which) const +{ + Q_ASSERT (which == 0 || which == 1); + + return m_color [which]; +} + +//--------------------------------------------------------------------- + +kpColor kpDualColorButton::foregroundColor () const +{ + return color (0); +} + +//--------------------------------------------------------------------- + +kpColor kpDualColorButton::backgroundColor () const +{ + return color (1); +} + +//--------------------------------------------------------------------- + +void kpDualColorButton::setColor (int which, const kpColor &color) +{ + Q_ASSERT (which == 0 || which == 1); + + if (m_color [which] == color) + return; + + m_oldColor [which] = m_color [which]; + m_color [which] = color; + update (); + + if (which == 0) + emit foregroundColorChanged (color); + else + emit backgroundColorChanged (color); +} + +//--------------------------------------------------------------------- + +void kpDualColorButton::setForegroundColor (const kpColor &color) +{ + setColor (0, color); +} + +//--------------------------------------------------------------------- + +void kpDualColorButton::setBackgroundColor (const kpColor &color) +{ + setColor (1, color); +} + +//--------------------------------------------------------------------- + + +// public +kpColor kpDualColorButton::oldForegroundColor () const +{ + return m_oldColor [0]; +} + +//--------------------------------------------------------------------- + +// public +kpColor kpDualColorButton::oldBackgroundColor () const +{ + return m_oldColor [1]; +} + +//--------------------------------------------------------------------- + + +// public virtual [base QWidget] +QSize kpDualColorButton::sizeHint () const +{ + return QSize (52, 52); +} + +//--------------------------------------------------------------------- + + +// protected +QRect kpDualColorButton::swapPixmapRect () const +{ + QPixmap swapPixmap = UserIcon ("colorbutton_swap_16x16"); + + return QRect (contentsRect ().width () - swapPixmap.width (), + 0, + swapPixmap.width (), + swapPixmap.height ()); +} + +//--------------------------------------------------------------------- + +// protected +QRect kpDualColorButton::foregroundBackgroundRect () const +{ + QRect cr (contentsRect ()); + return QRect (cr.width () / 8, + cr.height () / 8, + cr.width () * 6 / 8, + cr.height () * 6 / 8); +} + +//--------------------------------------------------------------------- + +// protected +QRect kpDualColorButton::foregroundRect () const +{ + QRect fbr (foregroundBackgroundRect ()); + return QRect (fbr.x (), + fbr.y (), + fbr.width () * 3 / 4, + fbr.height () * 3 / 4); +} + +//--------------------------------------------------------------------- + +// protected +QRect kpDualColorButton::backgroundRect () const +{ + QRect fbr (foregroundBackgroundRect ()); + return QRect (fbr.x () + fbr.width () / 4, + fbr.y () + fbr.height () / 4, + fbr.width () * 3 / 4, + fbr.height () * 3 / 4); +} + +//--------------------------------------------------------------------- + + +// protected virtual +void kpDualColorButton::dragEnterEvent (QDragEnterEvent *e) +{ +#if DEBUG_KP_DUAL_COLOR_BUTTON + kDebug () << "kpDualColorButton::dragEnterEvent() canDecode=" + << KColorMimeData::canDecode (e->mimeData ()) + << endl; +#endif + e->accept (); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpDualColorButton::dragMoveEvent (QDragMoveEvent *e) +{ +#if DEBUG_KP_DUAL_COLOR_BUTTON + kDebug () << "kpDualColorButton::dragMoveEvent() canDecode=" + << KColorMimeData::canDecode (e->mimeData ()) + << endl; +#endif + e->setAccepted ( + (foregroundRect ().contains (e->pos ()) || + backgroundRect ().contains (e->pos ())) && + KColorMimeData::canDecode (e->mimeData ())); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpDualColorButton::dropEvent (QDropEvent *e) +{ + QColor col = KColorMimeData::fromMimeData (e->mimeData ()); +#if DEBUG_KP_DUAL_COLOR_BUTTON + kDebug () << "kpDualColorButton::dropEvent() col=" + << (int *) col.rgba() + << " (with alpha=" << (int *) col.rgba () << ")" << endl; +#endif + + if (col.isValid ()) + { + if (foregroundRect ().contains (e->pos ())) + setForegroundColor (kpColor (col.rgba())); + else if (backgroundRect ().contains (e->pos ())) + setBackgroundColor (kpColor (col.rgba())); + } +} + +//--------------------------------------------------------------------- + + +// protected virtual [base QWidget] +void kpDualColorButton::mousePressEvent (QMouseEvent *e) +{ +#if DEBUG_KP_DUAL_COLOR_BUTTON + kDebug () << "kpDualColorButton::mousePressEvent() pos=" << e->pos (); +#endif + + m_dragStartPoint = KP_INVALID_POINT; + + if (e->button () == Qt::LeftButton) + m_dragStartPoint = e->pos (); +} + +//--------------------------------------------------------------------- + +void kpDualColorButton::mouseMoveEvent (QMouseEvent *e) +{ +#if DEBUG_KP_DUAL_COLOR_BUTTON + kDebug () << "kpDualColorButton::mouseMoveEvent() pos=" << e->pos () + << " buttons=" << e->buttons () + << " dragStartPoint=" << m_dragStartPoint << endl; +#endif + + if (m_dragStartPoint == KP_INVALID_POINT) + return; + + if (!(e->buttons () & Qt::LeftButton)) + { + m_dragStartPoint = KP_INVALID_POINT; + return; + } + + const int delay = KGlobalSettings::dndEventDelay (); + if (e->x () < m_dragStartPoint.x () - delay || + e->x () > m_dragStartPoint.x () + delay || + e->y () < m_dragStartPoint.y () - delay || + e->y () > m_dragStartPoint.y () + delay) + { + #if DEBUG_KP_DUAL_COLOR_BUTTON + kDebug () << "\tstarting drag as long as it's in a rectangle"; + #endif + + kpColor color; + + if (foregroundRect ().contains (m_dragStartPoint)) + color = foregroundColor (); + else if (backgroundRect ().contains (m_dragStartPoint)) + color = backgroundColor (); + else + { + // "color" is left as invalid. + } + + #if DEBUG_KP_DUAL_COLOR_BUTTON + kDebug () << "\tcolor.isValid=" << color.isValid () + << " rgb=" << (color.isValid () ? (int *) color.toQRgb () : 0) + << endl; + #endif + + if (color.isValid ()) + { + if (!color.isTransparent ()) + KColorMimeData::createDrag (color.toQColor (), this)->exec (); + } + + m_dragStartPoint = KP_INVALID_POINT; + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpDualColorButton::mouseReleaseEvent (QMouseEvent *e) +{ + m_dragStartPoint = KP_INVALID_POINT; + + if (swapPixmapRect ().contains (e->pos ()) && + m_color [0] != m_color [1]) + { + #if DEBUG_KP_DUAL_COLOR_BUTTON && 1 + kDebug () << "kpDualColorButton::mouseReleaseEvent() swap colors:"; + #endif + m_oldColor [0] = m_color [0]; + m_oldColor [1] = m_color [1]; + + kpColor temp = m_color [0]; + m_color [0] = m_color [1]; + m_color [1] = temp; + + update (); + + emit colorsSwapped (m_color [0], m_color [1]); + emit foregroundColorChanged (m_color [0]); + emit backgroundColorChanged (m_color [1]); + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpDualColorButton::mouseDoubleClickEvent (QMouseEvent *e) +{ + int whichColor = -1; + + if (foregroundRect ().contains (e->pos ())) + whichColor = 0; + else if (backgroundRect ().contains (e->pos ())) + whichColor = 1; + + if (whichColor == 0 || whichColor == 1) + { + KColorDialog dialog(this); + dialog.setColor(color(whichColor).toQColor()); + dialog.setAlphaChannelEnabled(true); + dialog.setButtons(KDialog::Ok | KDialog::Cancel); + if ( dialog.exec() == QDialog::Accepted ) + setColor(whichColor, kpColor(dialog.color().rgba())); + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpDualColorButton::paintEvent (QPaintEvent *e) +{ +#if DEBUG_KP_DUAL_COLOR_BUTTON && 1 + kDebug () << "kpDualColorButton::draw() rect=" << rect () + << " contentsRect=" << contentsRect () + << endl; +#endif + + // Draw frame first. + QFrame::paintEvent (e); + + QPainter painter (this); + + // Fill with background. + if (isEnabled ()) + { + kpView::drawTransparentBackground (&painter, + contentsRect ().topLeft ()/*checkerboard top-left*/, + contentsRect (), + true/*preview*/); + } + else + { + // Use default widget background. + } + + + painter.translate (contentsRect ().x (), contentsRect ().y ()); + + + // Draw "Swap Colours" button (top-right). + QPixmap swapPixmap = UserIcon ("colorbutton_swap_16x16"); + if (!isEnabled ()) + { + // Don't let the fill() touch the mask. + QBitmap swapBitmapMask = swapPixmap.mask (); + swapPixmap.setMask (QBitmap ()); + + // Grey out the opaque parts of "swapPixmap". + swapPixmap.fill (palette ().color (QPalette::Dark)); + + swapPixmap.setMask (swapBitmapMask); + } + painter.drawPixmap (swapPixmapRect ().topLeft (), swapPixmap); + + + // Draw background colour patch. + QRect bgRect = backgroundRect (); + QRect bgRectInside = QRect (bgRect.x () + 2, bgRect.y () + 2, + bgRect.width () - 4, bgRect.height () - 4); + if (isEnabled ()) + { + #if DEBUG_KP_DUAL_COLOR_BUTTON && 1 + kDebug () << "\tbackgroundColor=" << (int *) m_color [1].toQRgb () + << endl; + #endif + if (m_color [1].isTransparent ()) // only if fully transparent + painter.drawPixmap (bgRectInside, UserIcon ("color_transparent_26x26")); + else + painter.fillRect (bgRectInside, m_color [1].toQColor ()); + } + else + painter.fillRect (bgRectInside, palette().color (QPalette::Button)); + qDrawShadePanel (&painter, bgRect, palette(), + false/*not sunken*/, 2/*lineWidth*/, + 0/*never fill*/); + + + // Draw foreground colour patch. + // Must be drawn after background patch since we're on top. + QRect fgRect = foregroundRect (); + QRect fgRectInside = QRect (fgRect.x () + 2, fgRect.y () + 2, + fgRect.width () - 4, fgRect.height () - 4); + if (isEnabled ()) + { + #if DEBUG_KP_DUAL_COLOR_BUTTON && 1 + kDebug () << "\tforegroundColor=" << (int *) m_color [0].toQRgb () + << endl; + #endif + if (m_color [0].isTransparent ()) // only if fully transparent + painter.drawPixmap (fgRectInside, UserIcon ("color_transparent_26x26")); + else + painter.fillRect (fgRectInside, m_color [0].toQColor ()); + } + else + painter.fillRect (fgRectInside, palette ().color (QPalette::Button)); + + qDrawShadePanel (&painter, fgRect, palette (), + false/*not sunken*/, 2/*lineWidth*/, + 0/*never fill*/); +} + + +#include diff --git a/kolourpaint/widgets/kpDualColorButton.h b/kolourpaint/widgets/kpDualColorButton.h new file mode 100644 index 00000000..65000652 --- /dev/null +++ b/kolourpaint/widgets/kpDualColorButton.h @@ -0,0 +1,96 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDualColorButton_H +#define kpDualColorButton_H + + +#include + +#include + + +class kpDualColorButton : public QFrame +{ +Q_OBJECT + +public: + kpDualColorButton (QWidget *parent); + + kpColor color (int which) const; + kpColor foregroundColor () const; + kpColor backgroundColor () const; + +public slots: + void setColor (int which, const kpColor &color); + void setForegroundColor (const kpColor &color); + void setBackgroundColor (const kpColor &color); + +signals: + // If you connect to this signal, ignore the following + // foregroundColorChanged() and backgroundColorChanged() signals + void colorsSwapped (const kpColor &newForegroundColor, + const kpColor &newBackgroundColor); + + void foregroundColorChanged (const kpColor &color); + void backgroundColorChanged (const kpColor &color); + +public: + // (only valid in slots connected to foregroundColorChanged()) + kpColor oldForegroundColor () const; + // (only valid in slots connected to backgroundColorChanged()) + kpColor oldBackgroundColor () const; + +public: + virtual QSize sizeHint () const; + +protected: + QRect swapPixmapRect () const; + QRect foregroundBackgroundRect () const; + QRect foregroundRect () const; + QRect backgroundRect () const; + + virtual void dragEnterEvent (QDragEnterEvent *e); + virtual void dragMoveEvent (QDragMoveEvent *e); + virtual void dropEvent (QDropEvent *e); + + virtual void mousePressEvent (QMouseEvent *e); + virtual void mouseMoveEvent (QMouseEvent *e); + virtual void mouseReleaseEvent (QMouseEvent *e); + + virtual void mouseDoubleClickEvent (QMouseEvent *e); + + virtual void paintEvent (QPaintEvent *e); + + QPoint m_dragStartPoint; + kpColor m_color [2]; + kpColor m_oldColor [2]; +}; + + +#endif // kpDualColorButton_H diff --git a/kolourpaint/widgets/kpPrintDialogPage.cpp b/kolourpaint/widgets/kpPrintDialogPage.cpp new file mode 100644 index 00000000..53ed4b1d --- /dev/null +++ b/kolourpaint/widgets/kpPrintDialogPage.cpp @@ -0,0 +1,101 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2007 John Layt + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_PRINT_DIALOG_PAGE 0 + + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + + +struct kpPrintDialogPagePrivate +{ + QRadioButton *printCenteredRadio, *printTopLeftRadio; +}; + +kpPrintDialogPage::kpPrintDialogPage (QWidget *parent) + : QWidget (parent), + d (new kpPrintDialogPagePrivate ()) +{ +#if DEBUG_KP_PRINT_DIALOG_PAGE + kDebug () << "kpPrintDialogPage::()"; +#endif + + setWindowTitle (i18nc ("@title:tab", "I&mage Position")); + + KVBox *base = new KVBox (this); + base->setMargin (KDialog::marginHint ()); + + d->printCenteredRadio = new QRadioButton (i18n ("&Center of the page"), + base); + d->printTopLeftRadio = new QRadioButton (i18n ("Top-&left of the page"), + base); + + setPrintImageCenteredOnPage (true); +} + +kpPrintDialogPage::~kpPrintDialogPage () +{ + delete d; +} + + +bool kpPrintDialogPage::printImageCenteredOnPage () +{ +#if DEBUG_KP_PRINT_DIALOG_PAGE + kDebug () << "kpPrintDialogPage::printImageCenteredOnPage()" + << " returning " << d->printCenteredRadio->isChecked() << endl; +#endif + return d->printCenteredRadio->isChecked (); +} + + +void kpPrintDialogPage::setPrintImageCenteredOnPage (bool printCentered) +{ +#if DEBUG_KP_PRINT_DIALOG_PAGE + kDebug () << "kpPrintDialogPage::setOptions(" << printCentered << ")"; +#endif + if (printCentered) + d->printCenteredRadio->setChecked (true); + else + d->printTopLeftRadio->setChecked (true); +} + + +#include diff --git a/kolourpaint/widgets/kpPrintDialogPage.h b/kolourpaint/widgets/kpPrintDialogPage.h new file mode 100644 index 00000000..beb16cf2 --- /dev/null +++ b/kolourpaint/widgets/kpPrintDialogPage.h @@ -0,0 +1,52 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2007 John Layt + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpPrintDialogPage_H +#define kpPrintDialogPage_H + +#include + + +class kpPrintDialogPage : public QWidget +{ +Q_OBJECT + +public: + kpPrintDialogPage (QWidget *parent); + virtual ~kpPrintDialogPage (); + + bool printImageCenteredOnPage (); + void setPrintImageCenteredOnPage (bool printCentered); + +private: + struct kpPrintDialogPagePrivate * const d; +}; + + +#endif // kpPrintDialogPage_H diff --git a/kolourpaint/widgets/kpTransparentColorCell.cpp b/kolourpaint/widgets/kpTransparentColorCell.cpp new file mode 100644 index 00000000..178241d4 --- /dev/null +++ b/kolourpaint/widgets/kpTransparentColorCell.cpp @@ -0,0 +1,129 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TRANSPARENT_COLOR_CELL 0 + + +#include + +#include +#include +#include + +#include +#include + +#include + +//--------------------------------------------------------------------- + +kpTransparentColorCell::kpTransparentColorCell (QWidget *parent) + : QFrame (parent) +{ + setSizePolicy (QSizePolicy::Fixed/*horizontal*/, + QSizePolicy::Fixed/*vertical*/); + setFrameStyle (QFrame::Panel | QFrame::Sunken); + + m_pixmap = UserIcon ("color_transparent_26x26"); + + this->setToolTip( i18n ("Transparent")); +} + +//--------------------------------------------------------------------- + +// public virtual [base QWidget] +QSize kpTransparentColorCell::sizeHint () const +{ + return QSize (m_pixmap.width () + frameWidth () * 2, + m_pixmap.height () + frameWidth () * 2); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpTransparentColorCell::mousePressEvent (QMouseEvent * /*e*/) +{ + // Eat press so that we own the mouseReleaseEvent(). + // [http://blogs.qtdeveloper.net/archives/2006/05/27/mouse-event-propagation/] + // + // However, contrary to that blog, it doesn't seem to be needed? +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpTransparentColorCell::contextMenuEvent (QContextMenuEvent *e) +{ + // Eat right-mouse press to prevent it from getting to the toolbar. + e->accept (); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpTransparentColorCell::mouseReleaseEvent (QMouseEvent *e) +{ + if (rect ().contains (e->pos ())) + { + if (e->button () == Qt::LeftButton) + { + emit transparentColorSelected (0); + emit foregroundColorChanged (kpColor::Transparent); + } + else if (e->button () == Qt::RightButton) + { + emit transparentColorSelected (1); + emit backgroundColorChanged (kpColor::Transparent); + } + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpTransparentColorCell::paintEvent (QPaintEvent *e) +{ + // Draw frame first. + QFrame::paintEvent (e); + + if (isEnabled ()) + { + #if DEBUG_KP_TRANSPARENT_COLOR_CELL + kDebug () << "kpTransparentColorCell::paintEvent() contentsRect=" + << contentsRect () + << endl; + #endif + QPainter p (this); + p.drawPixmap (contentsRect (), m_pixmap); + } +} + +//--------------------------------------------------------------------- + + +#include diff --git a/kolourpaint/widgets/kpTransparentColorCell.h b/kolourpaint/widgets/kpTransparentColorCell.h new file mode 100644 index 00000000..31396036 --- /dev/null +++ b/kolourpaint/widgets/kpTransparentColorCell.h @@ -0,0 +1,66 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransparentColorCell_H +#define kpTransparentColorCell_H + + +#include + + +class kpColor; + + +class kpTransparentColorCell : public QFrame +{ +Q_OBJECT + +public: + kpTransparentColorCell (QWidget *parent); + + virtual QSize sizeHint () const; + +signals: + void transparentColorSelected (int mouseButton); + + // lazy + void foregroundColorChanged (const kpColor &color); + void backgroundColorChanged (const kpColor &color); + +protected: + virtual void mousePressEvent (QMouseEvent *e); + virtual void contextMenuEvent (QContextMenuEvent *e); + virtual void mouseReleaseEvent (QMouseEvent *e); + + virtual void paintEvent (QPaintEvent *e); + + QPixmap m_pixmap; +}; + + +#endif // kpTransparentColorCell_H diff --git a/kolourpaint/widgets/toolbars/kpColorToolBar.cpp b/kolourpaint/widgets/toolbars/kpColorToolBar.cpp new file mode 100644 index 00000000..4af1a0db --- /dev/null +++ b/kolourpaint/widgets/toolbars/kpColorToolBar.cpp @@ -0,0 +1,329 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COLOR_TOOL_BAR 0 + + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +kpColorToolBar::kpColorToolBar (const QString &label, QWidget *parent) + : QDockWidget (parent) +{ + setWindowTitle (label); + + // not closable, as it's not a KDE toolbar yet and can not be made shown easily again + setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + + setAcceptDrops (true); + + QWidget *base = new QWidget (this); + m_boxLayout = new QBoxLayout (QBoxLayout::LeftToRight, base); + m_boxLayout->setMargin (5); + m_boxLayout->setSpacing (10 * 3); + + // This holds the current global foreground and background colors, for + // tools. + m_dualColorButton = new kpDualColorButton (base); + connect (m_dualColorButton, SIGNAL (colorsSwapped (const kpColor &, const kpColor &)), + this, SIGNAL (colorsSwapped (const kpColor &, const kpColor &))); + connect (m_dualColorButton, SIGNAL (foregroundColorChanged (const kpColor &)), + this, SIGNAL (foregroundColorChanged (const kpColor &))); + connect (m_dualColorButton, SIGNAL (backgroundColorChanged (const kpColor &)), + this, SIGNAL (backgroundColorChanged (const kpColor &))); + m_boxLayout->addWidget (m_dualColorButton, 0/*stretch*/, Qt::AlignVCenter); + + m_colorPalette = new kpColorPalette (base); + connect (m_colorPalette, SIGNAL (foregroundColorChanged (const kpColor &)), + m_dualColorButton, SLOT (setForegroundColor (const kpColor &))); + connect (m_colorPalette, SIGNAL (backgroundColorChanged (const kpColor &)), + m_dualColorButton, SLOT (setBackgroundColor (const kpColor &))); + + connect (m_colorPalette->colorCells (), SIGNAL (isModifiedChanged (bool)), + SLOT (updateNameOrUrlLabel ())); + connect (m_colorPalette->colorCells (), SIGNAL (urlChanged (const KUrl &)), + SLOT (updateNameOrUrlLabel ())); + connect (m_colorPalette->colorCells (), SIGNAL (nameChanged (const QString &)), + SLOT (updateNameOrUrlLabel ())); + updateNameOrUrlLabel (); + + m_boxLayout->addWidget (m_colorPalette, 0/*stretch*/); + + m_colorSimilarityToolBarItem = new kpColorSimilarityToolBarItem (base); + connect (m_colorSimilarityToolBarItem, SIGNAL (colorSimilarityChanged (double, int)), + this, SIGNAL (colorSimilarityChanged (double, int))); + m_boxLayout->addWidget (m_colorSimilarityToolBarItem, 0/*stretch*/); + + // Pad out all the horizontal space on the right of the Color Tool Bar so that + // that the real Color Tool Bar widgets aren't placed in the center of the + // Color Tool Bar. + m_boxLayout->addItem ( + new QSpacerItem (1, 1, QSizePolicy::Expanding, QSizePolicy::Preferred)); + + adjustToOrientation (Qt::Horizontal); + + setWidget (base); +} + +//--------------------------------------------------------------------- + +void kpColorToolBar::adjustToOrientation (Qt::Orientation o) +{ +#if DEBUG_KP_COLOR_TOOL_BAR + kDebug () << "kpColorToolBar::adjustToOrientation(" + << (o == Qt::Vertical ? "vertical" : "horizontal") + << ") called!"; +#endif + + Q_ASSERT (o == Qt::Horizontal); + + if (o == Qt::Horizontal) + { + m_boxLayout->setDirection (QBoxLayout::LeftToRight); + } + else + { + m_boxLayout->setDirection (QBoxLayout::TopToBottom); + } + + m_colorPalette->setOrientation (o); +} + +//--------------------------------------------------------------------- + +// public +kpColorCells *kpColorToolBar::colorCells () const +{ + return m_colorPalette->colorCells (); +} + +//--------------------------------------------------------------------- + +kpColor kpColorToolBar::color (int which) const +{ + Q_ASSERT (which == 0 || which == 1); + + return m_dualColorButton->color (which); +} + +//--------------------------------------------------------------------- + +void kpColorToolBar::setColor (int which, const kpColor &color) +{ + Q_ASSERT (which == 0 || which == 1); + + m_dualColorButton->setColor (which, color); +} + +//--------------------------------------------------------------------- + +kpColor kpColorToolBar::foregroundColor () const +{ + return m_dualColorButton->foregroundColor (); +} + +//--------------------------------------------------------------------- + +void kpColorToolBar::setForegroundColor (const kpColor &color) +{ +#if DEBUG_KP_COLOR_TOOL_BAR + kDebug () << "kpColorToolBar::setForegroundColor(" + << (int *) color.toQRgb () << ")" << endl; +#endif + m_dualColorButton->setForegroundColor (color); +} + +//--------------------------------------------------------------------- + +kpColor kpColorToolBar::backgroundColor () const +{ + return m_dualColorButton->backgroundColor (); +} + +//--------------------------------------------------------------------- + +void kpColorToolBar::setBackgroundColor (const kpColor &color) +{ +#if DEBUG_KP_COLOR_TOOL_BAR + kDebug () << "kpColorToolBar::setBackgroundColor(" + << (int *) color.toQRgb () << ")" << endl; +#endif + m_dualColorButton->setBackgroundColor (color); +} + +//--------------------------------------------------------------------- + + +kpColor kpColorToolBar::oldForegroundColor () const +{ + return m_dualColorButton->oldForegroundColor (); +} + +//--------------------------------------------------------------------- + +kpColor kpColorToolBar::oldBackgroundColor () const +{ + return m_dualColorButton->oldBackgroundColor (); +} + +//--------------------------------------------------------------------- + +double kpColorToolBar::oldColorSimilarity () const +{ + return m_colorSimilarityToolBarItem->oldColorSimilarity (); +} + +//--------------------------------------------------------------------- + +double kpColorToolBar::colorSimilarity () const +{ + return m_colorSimilarityToolBarItem->colorSimilarity (); +} + +//--------------------------------------------------------------------- + +void kpColorToolBar::setColorSimilarity (double similarity) +{ + m_colorSimilarityToolBarItem->setColorSimilarity (similarity); +} + +//--------------------------------------------------------------------- + +int kpColorToolBar::processedColorSimilarity () const +{ + return m_colorSimilarityToolBarItem->processedColorSimilarity (); +} + +//--------------------------------------------------------------------- + +void kpColorToolBar::openColorSimilarityDialog () +{ + m_colorSimilarityToolBarItem->openDialog (); +} + +//--------------------------------------------------------------------- + +void kpColorToolBar::flashColorSimilarityToolBarItem () +{ + m_colorSimilarityToolBarItem->flash (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpColorToolBar::updateNameOrUrlLabel () +{ + QString name; + + kpColorCells *colorCells = m_colorPalette->colorCells (); + if (!colorCells->url ().isEmpty ()) + name = kpUrlFormatter::PrettyFilename (colorCells->url ()); + else + { + if (!colorCells->name ().isEmpty ()) + name = colorCells->name (); + else + name = i18n ("KolourPaint Defaults"); + } + + if (name.isEmpty ()) + name = i18n ("Untitled"); + + + KLocalizedString labelStr; + + if (!m_colorPalette->colorCells ()->isModified ()) + { + labelStr = + ki18nc ("Colors: name_or_url_of_color_palette", + "Colors: %1") + .subs (name); + } + else + { + labelStr = + ki18nc ("Colors: name_or_url_of_color_palette [modified]", + "Colors: %1 [modified]") + .subs (name); + } + + // Kill 2 birds with 1 stone: + // + // 1. Hide the windowTitle() when it's docked. + // 2. Add a label containing the name of the open color palette. + // + // TODO: This currently hides the windowTitle() even when it's not docked, + // because we've abused it to show the name of open color palette + // instead. + setWindowTitle (labelStr.toString ()); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpColorToolBar::dragEnterEvent (QDragEnterEvent *e) +{ + // Grab the color drag for this widget, preventing it from being + // handled by our parent, the main window. + e->setAccepted (KColorMimeData::canDecode (e->mimeData ()) == true); +#if DEBUG_KP_COLOR_TOOL_BAR + kDebug () << "isAccepted=" << e->isAccepted (); +#endif +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpColorToolBar::dragMoveEvent (QDragMoveEvent *e) +{ + // Stop the grabbed drag from being dropped. + e->setAccepted (KColorMimeData::canDecode (e->mimeData ()) == false); +#if DEBUG_KP_COLOR_TOOL_BAR + kDebug () << "isAccepted=" << e->isAccepted (); +#endif +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/widgets/toolbars/kpColorToolBar.h b/kolourpaint/widgets/toolbars/kpColorToolBar.h new file mode 100644 index 00000000..e36abf64 --- /dev/null +++ b/kolourpaint/widgets/toolbars/kpColorToolBar.h @@ -0,0 +1,124 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_COLOR_TOOLBAR_H +#define KP_COLOR_TOOLBAR_H + + +#include +#include +#include + +#include + + +class QBoxLayout; + +class kpColorCells; +class kpColorPalette; +class kpColorSimilarityToolBarItem; +class kpDualColorButton; + + +// COMPAT: Vertical orientation and undocking were broken by the Qt4 port +// so kpMainWindow::init() keeps this tool bar in a constant position for +// the time being. To help make this workaround possible, we use QDockWidget, +// instead of KToolBar, to prevent XMLGUI from managing the tool +// bar position. This also allows us to use QMainWindow::setCorner(). +// +// A nice must-have side-effect is that we are now resizeable, which +// is good for configurable (and potentially large) color collections. +// So we should probably keep it as a QDockWidget in the long-term +// and once we fix orientation and undocking, we should put XMLGUI +// support back in, somehow (create a "KDockWidget" class?). +class kpColorToolBar : public QDockWidget +{ +Q_OBJECT + +public: + kpColorToolBar (const QString &label, QWidget *parent); + + kpColorCells *colorCells () const; + + kpColor color (int which) const; + void setColor (int which, const kpColor &color); + + kpColor foregroundColor () const; + kpColor backgroundColor () const; + + double colorSimilarity () const; + void setColorSimilarity (double similarity); + int processedColorSimilarity () const; + + void openColorSimilarityDialog (); + void flashColorSimilarityToolBarItem (); + +signals: + // If you connect to this signal, ignore the following + // foregroundColorChanged() and backgroundColorChanged() signals + void colorsSwapped (const kpColor &newForegroundColor, + const kpColor &newBackgroundColor); + + void foregroundColorChanged (const kpColor &color); + void backgroundColorChanged (const kpColor &color); + void colorSimilarityChanged (double similarity, int processedSimilarity); + +public: + // (only valid in slots connected to foregroundColorChanged()) + kpColor oldForegroundColor () const; + // (only valid in slots connected to backgroundColorChanged()) + kpColor oldBackgroundColor () const; + + // (only valid in slots connected to colorSimilarityChanged()) + double oldColorSimilarity () const; + +public slots: + void setForegroundColor (const kpColor &color); + void setBackgroundColor (const kpColor &color); + +private slots: + void updateNameOrUrlLabel (); + +protected: + // Eat color drops (which are usually accidental drags from one of our + // child widgets) to prevent them from being pasted as text in the + // main window (by kpMainWindow::dropEvent()). + virtual void dragEnterEvent (QDragEnterEvent *e); + virtual void dragMoveEvent (QDragMoveEvent *e); + +private: + void adjustToOrientation (Qt::Orientation o); + + QBoxLayout *m_boxLayout; + kpDualColorButton *m_dualColorButton; + kpColorPalette *m_colorPalette; + kpColorSimilarityToolBarItem *m_colorSimilarityToolBarItem; +}; + + +#endif // KP_COLOR_TOOLBAR_H diff --git a/kolourpaint/widgets/toolbars/kpToolToolBar.cpp b/kolourpaint/widgets/toolbars/kpToolToolBar.cpp new file mode 100644 index 00000000..804c8947 --- /dev/null +++ b/kolourpaint/widgets/toolbars/kpToolToolBar.cpp @@ -0,0 +1,459 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_TOOL_BAR 0 + + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +class kpToolButton : public QToolButton +{ +public: + kpToolButton (kpTool *tool, QWidget *parent) + : QToolButton (parent), + m_tool (tool) + { + } + + kpTool *tool() const { return m_tool; } + +protected: + virtual void mouseDoubleClickEvent(QMouseEvent *e) + { + if (e->button () == Qt::LeftButton && m_tool) + m_tool->globalDraw (); + } + + kpTool *m_tool; +}; + +//--------------------------------------------------------------------- + +kpToolToolBar::kpToolToolBar(const QString &name, int colsOrRows, QMainWindow *parent) + : KToolBar(name, parent, Qt::LeftToolBarArea), + m_vertCols (colsOrRows), + m_buttonGroup (0), + m_baseWidget (0), + m_baseLayout (0), + m_toolLayout (0), + m_previousTool (0), m_currentTool (0) +{ + m_baseWidget = new QWidget(this); + + m_toolWidgets.append (m_toolWidgetBrush = + new kpToolWidgetBrush (m_baseWidget, "Tool Widget Brush")); + m_toolWidgets.append (m_toolWidgetEraserSize = + new kpToolWidgetEraserSize (m_baseWidget, "Tool Widget Eraser Size")); + m_toolWidgets.append (m_toolWidgetFillStyle = + new kpToolWidgetFillStyle (m_baseWidget, "Tool Widget Fill Style")); + m_toolWidgets.append (m_toolWidgetLineWidth = + new kpToolWidgetLineWidth (m_baseWidget, "Tool Widget Line Width")); + m_toolWidgets.append (m_toolWidgetOpaqueOrTransparent = + new kpToolWidgetOpaqueOrTransparent (m_baseWidget, "Tool Widget Opaque/Transparent")); + m_toolWidgets.append (m_toolWidgetSpraycanSize = + new kpToolWidgetSpraycanSize (m_baseWidget, "Tool Widget Spraycan Size")); + + foreach(kpToolWidgetBase *w, m_toolWidgets) + { + connect(w, SIGNAL(optionSelected(int, int)), + this, SIGNAL(toolWidgetOptionSelected())); + } + + adjustToOrientation(orientation()); + connect(this, SIGNAL(orientationChanged(Qt::Orientation)), + this, SLOT(adjustToOrientation(Qt::Orientation))); + + m_buttonGroup = new QButtonGroup (this); + connect (m_buttonGroup, SIGNAL (buttonClicked (int)), SLOT (slotToolButtonClicked ())); + + hideAllToolWidgets (); + + addWidget(m_baseWidget); + + connect(this, SIGNAL(iconSizeChanged(const QSize &)), + this, SLOT(slotIconSizeChanged(const QSize &))); + + connect(this, SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)), + this, SLOT(slotToolButtonStyleChanged(Qt::ToolButtonStyle))); +} + +//--------------------------------------------------------------------- + +kpToolToolBar::~kpToolToolBar() +{ + while ( !m_toolButtons.isEmpty() ) + delete m_toolButtons.takeFirst(); +} + +//--------------------------------------------------------------------- + +// public +void kpToolToolBar::registerTool (kpTool *tool) +{ + foreach (const kpToolButton *b, m_toolButtons) + { + if ( b->tool() == tool ) // already given + return; + } + + kpToolButton *b = new kpToolButton(tool, m_baseWidget); + + b->setToolButtonStyle(toolButtonStyle()); + b->setIconSize(iconSize()); + b->setAutoRaise(true); + + // tell layout to make all with equal width (much better when text-below-icon) + b->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + b->setDefaultAction(tool->action()); + + m_buttonGroup->addButton(b); + addButton(b, orientation(), m_toolButtons.count()); + + m_toolButtons.append(b); + + connect (tool, SIGNAL (actionActivated ()), + this, SLOT (slotToolActionActivated ())); + + adjustSizeConstraint(); +} + +//--------------------------------------------------------------------- +// public + +void kpToolToolBar::unregisterTool(kpTool *tool) +{ + for (int i = 0; i < m_toolButtons.count(); i++) + { + if ( m_toolButtons[i]->tool() == tool ) + { + delete m_toolButtons.takeAt(i); + disconnect(tool, SIGNAL(actionActivated()), + this, SLOT(slotToolActionActivated())); + return; + } + } +} + +//--------------------------------------------------------------------- +// public + +kpTool *kpToolToolBar::tool () const +{ + return m_currentTool; +} + +//--------------------------------------------------------------------- + +// public +void kpToolToolBar::selectTool (const kpTool *tool, bool reselectIfSameTool) +{ +#if DEBUG_KP_TOOL_TOOL_BAR + kDebug () << "kpToolToolBar::selectTool (tool=" << tool + << ") currentTool=" << m_currentTool + << endl; +#endif + + if (!reselectIfSameTool && tool == m_currentTool) + return; + + if (tool) + { + tool->action()->setChecked(true); + slotToolButtonClicked(); + } + else + { + QAbstractButton *b = m_buttonGroup->checkedButton(); + if (b) + { + // HACK: qbuttongroup.html says the following about exclusive + // button groups: + // + // "to untoggle a button you must click on another button + // in the group" + // + // But we don't want any button to be selected. + // So don't be an exclusive button group temporarily. + m_buttonGroup->setExclusive (false); + b->setChecked (false); + m_buttonGroup->setExclusive (true); + + slotToolButtonClicked (); + } + } +} + +//--------------------------------------------------------------------- + +// public +kpTool *kpToolToolBar::previousTool () const +{ + return m_previousTool; +} + +//--------------------------------------------------------------------- + +// public +void kpToolToolBar::selectPreviousTool () +{ + selectTool (m_previousTool); +} + +//--------------------------------------------------------------------- + +// public +void kpToolToolBar::hideAllToolWidgets () +{ + foreach(kpToolWidgetBase *w, m_toolWidgets) + w->hide (); +} + +//--------------------------------------------------------------------- + +// public +kpToolWidgetBase *kpToolToolBar::shownToolWidget (int which) const +{ + int uptoVisibleWidget = 0; + + foreach(kpToolWidgetBase *w, m_toolWidgets) + { + if ( !w->isHidden() ) + { + if (which == uptoVisibleWidget) + return w; + + uptoVisibleWidget++; + } + } + + return 0; +} + +//--------------------------------------------------------------------- + +// private slot +void kpToolToolBar::slotToolButtonClicked () +{ + QAbstractButton *b = m_buttonGroup->checkedButton(); + +#if DEBUG_KP_TOOL_TOOL_BAR + kDebug () << "kpToolToolBar::slotToolButtonClicked() button=" << b; +#endif + + kpTool *tool = 0; + foreach (const kpToolButton *button, m_toolButtons) + { + if ( button == b ) + { + tool = button->tool(); + break; + } + } + +#if DEBUG_KP_TOOL_TOOL_BAR + kDebug () << "\ttool=" << tool + << " currentTool=" << m_currentTool + << endl; +#endif + + if (tool == m_currentTool) + { + if (m_currentTool) + m_currentTool->reselect (); + + return; + } + + if (m_currentTool) + m_currentTool->endInternal (); + + m_previousTool = m_currentTool; + m_currentTool = tool; + + if (m_currentTool) + { + kpToolAction *action = m_currentTool->action (); + if (action) + { + action->setChecked (true); + } + + m_currentTool->beginInternal (); + } + + emit sigToolSelected (m_currentTool); + m_baseLayout->activate(); + adjustSizeConstraint(); +} + +//--------------------------------------------------------------------- + +// private slot +void kpToolToolBar::slotToolActionActivated () +{ + const kpTool *tool = dynamic_cast(sender()); + +#if DEBUG_KP_TOOL_TOOL_BAR + kDebug () << "kpToolToolBar::slotToolActionActivated() tool=" + << (tool ? tool->objectName () : "null") + << endl; +#endif + + selectTool (tool, true/*reselect if same tool*/); +} + +//--------------------------------------------------------------------- + +// public +void kpToolToolBar::adjustToOrientation(Qt::Orientation o) +{ +#if DEBUG_KP_TOOL_TOOL_BAR + kDebug () << "kpToolToolBar::adjustToOrientation(" + << (o == Qt::Vertical ? "vertical" : "horizontal") + << ") called!" << endl; +#endif + + delete m_baseLayout; + if (o == Qt::Vertical) + { + m_baseLayout = new QBoxLayout (QBoxLayout::TopToBottom, m_baseWidget); + } + else // if (o == Qt::Horizontal) + { + m_baseLayout = new QBoxLayout (QBoxLayout::LeftToRight, m_baseWidget); + } + m_baseLayout->setSizeConstraint(QLayout::SetFixedSize); + m_baseLayout->setContentsMargins(0, 0, 0, 0); + + m_toolLayout = new QGridLayout(); + m_toolLayout->setContentsMargins(0, 0, 0, 0); + + // (ownership is transferred to m_baseLayout) + m_baseLayout->addItem (m_toolLayout); + + int num = 0; + + foreach (kpToolButton *b, m_toolButtons) + { + addButton(b, o, num); + num++; + } + + foreach(kpToolWidgetBase *w, m_toolWidgets) + { + m_baseLayout->addWidget(w, + 0/*stretch*/, + o == Qt::Vertical ? Qt::AlignHCenter : Qt::AlignVCenter); + } + + adjustSizeConstraint(); +} + +//--------------------------------------------------------------------- +// this makes the size handled correctly during dragging/undocking the toolbar + +void kpToolToolBar::adjustSizeConstraint() +{ + // remove constraints + setFixedSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); + + if ( orientation() == Qt::Vertical ) + { + setFixedWidth(m_baseLayout->sizeHint().width() + + layout()->contentsMargins().left() + + layout()->contentsMargins().right()); + } + else + { + setFixedHeight(m_baseLayout->sizeHint().height() + + layout()->contentsMargins().top() + + layout()->contentsMargins().bottom()); + } +} + +//--------------------------------------------------------------------- + +// private +void kpToolToolBar::addButton(QAbstractButton *button, Qt::Orientation o, int num) +{ + if (o == Qt::Vertical) + m_toolLayout->addWidget (button, num / m_vertCols, num % m_vertCols); + else + { + // maps Left (o = vertical) to Bottom (o = horizontal) + int row = (m_vertCols - 1) - (num % m_vertCols); + m_toolLayout->addWidget (button, row, num / m_vertCols); + } +} + +//--------------------------------------------------------------------- + +void kpToolToolBar::slotIconSizeChanged(const QSize &size) +{ + foreach (kpToolButton *b, m_toolButtons) + b->setIconSize(size); + + m_baseLayout->activate(); + adjustSizeConstraint(); +} + +//--------------------------------------------------------------------- + +void kpToolToolBar::slotToolButtonStyleChanged(Qt::ToolButtonStyle style) +{ + foreach (kpToolButton *b, m_toolButtons) + b->setToolButtonStyle(style); + + m_baseLayout->activate(); + adjustSizeConstraint(); +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/widgets/toolbars/kpToolToolBar.h b/kolourpaint/widgets/toolbars/kpToolToolBar.h new file mode 100644 index 00000000..d693487a --- /dev/null +++ b/kolourpaint/widgets/toolbars/kpToolToolBar.h @@ -0,0 +1,123 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_TOOL_BAR_H +#define KP_TOOL_TOOL_BAR_H + + +#include + +#include + + +class QAbstractButton; +class QBoxLayout; +class QButtonGroup; +class QGridLayout; +class QWidget; + +class kpTool; +class kpToolButton; + +class kpToolWidgetBase; +class kpToolWidgetBrush; +class kpToolWidgetEraserSize; +class kpToolWidgetFillStyle; +class kpToolWidgetLineWidth; +class kpToolWidgetOpaqueOrTransparent; +class kpToolWidgetSpraycanSize; + +class kpToolToolBar : public KToolBar +{ +Q_OBJECT + +public: + kpToolToolBar (const QString &name, int colsOrRows, QMainWindow *parent); + virtual ~kpToolToolBar (); + + void registerTool(kpTool *tool); + void unregisterTool(kpTool *tool); + + kpTool *tool () const; + void selectTool (const kpTool *tool, bool reselectIfSameTool = false); + + kpTool *previousTool () const; + void selectPreviousTool (); + + void hideAllToolWidgets (); + // could this be cleaner (the tools have to access them individually somehow)? + kpToolWidgetBrush *toolWidgetBrush () const { return m_toolWidgetBrush; } + kpToolWidgetEraserSize *toolWidgetEraserSize () const { return m_toolWidgetEraserSize; } + kpToolWidgetFillStyle *toolWidgetFillStyle () const { return m_toolWidgetFillStyle; } + kpToolWidgetLineWidth *toolWidgetLineWidth () const { return m_toolWidgetLineWidth; } + kpToolWidgetOpaqueOrTransparent *toolWidgetOpaqueOrTransparent () const { return m_toolWidgetOpaqueOrTransparent; } + kpToolWidgetSpraycanSize *toolWidgetSpraycanSize () const { return m_toolWidgetSpraycanSize; } + + kpToolWidgetBase *shownToolWidget (int which) const; + +signals: + void sigToolSelected (kpTool *tool); // tool may be 0 + void toolWidgetOptionSelected (); + +private slots: + void slotToolButtonClicked (); + + void slotToolActionActivated (); + + void adjustToOrientation(Qt::Orientation o); + void slotIconSizeChanged(const QSize &); + void slotToolButtonStyleChanged(Qt::ToolButtonStyle style); + +private: + void addButton (QAbstractButton *button, Qt::Orientation o, int num); + void adjustSizeConstraint(); + + int m_vertCols; + + QButtonGroup *m_buttonGroup; + QWidget *m_baseWidget; + QBoxLayout *m_baseLayout; + QGridLayout *m_toolLayout; + + kpToolWidgetBrush *m_toolWidgetBrush; + kpToolWidgetEraserSize *m_toolWidgetEraserSize; + kpToolWidgetFillStyle *m_toolWidgetFillStyle; + kpToolWidgetLineWidth *m_toolWidgetLineWidth; + kpToolWidgetOpaqueOrTransparent *m_toolWidgetOpaqueOrTransparent; + kpToolWidgetSpraycanSize *m_toolWidgetSpraycanSize; + + QList m_toolWidgets; + + QList m_toolButtons; + + kpTool *m_previousTool, *m_currentTool; +}; + + +#endif // KP_TOOL_TOOL_BAR_H diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetBase.cpp b/kolourpaint/widgets/toolbars/options/kpToolWidgetBase.cpp new file mode 100644 index 00000000..f2ae24a6 --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetBase.cpp @@ -0,0 +1,723 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_WIDGET_BASE 0 + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + + +//--------------------------------------------------------------------- + +kpToolWidgetBase::kpToolWidgetBase (QWidget *parent, const QString &name) + : QFrame(parent), m_baseWidget(0), + m_selectedRow(-1), m_selectedCol(-1) +{ + setObjectName (name); + + setFrameStyle (QFrame::Panel | QFrame::Sunken); + + setFixedSize (44, 66); + setSizePolicy (QSizePolicy::Minimum, QSizePolicy::Minimum); +} + +//--------------------------------------------------------------------- + +kpToolWidgetBase::~kpToolWidgetBase () +{ +} + +//--------------------------------------------------------------------- + +// public +void kpToolWidgetBase::addOption (const QPixmap &pixmap, const QString &toolTip) +{ + if (m_pixmaps.isEmpty ()) + startNewOptionRow (); + + m_pixmaps.last ().append (pixmap); + m_pixmapRects.last ().append (QRect ()); + m_toolTips.last ().append (toolTip); +} + +//--------------------------------------------------------------------- + +// public +void kpToolWidgetBase::startNewOptionRow () +{ + m_pixmaps.append (QList ()); + m_pixmapRects.append (QList ()); + m_toolTips.append (QList ()); +} + +//--------------------------------------------------------------------- + +// public +void kpToolWidgetBase::finishConstruction (int fallBackRow, int fallBackCol) +{ +#if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "kpToolWidgetBase(" << objectName () + << ")::kpToolWidgetBase(fallBack:row=" << fallBackRow + << ",col=" << fallBackCol + << ")" + << endl; +#endif + + relayoutOptions (); + + // HACK: Undo the maximum half of setFixedSize() in the ctor to avoid + // bizarre redraw errors when tool widgets are hidden and others + // are shown. + // + // The reason why we didn't just use setMinimumSize() in the ctor is + // because all tool widgets construct pixmaps whose sizes are dependent + // on the size() in the ctor, so we needed to get the correct size + // in there. This is bad design because it means that tool widgets + // can't really be resized. + setMaximumSize (QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + + const QPair rowColPair = defaultSelectedRowAndCol (); + if (!setSelected (rowColPair.first, rowColPair.second, false/*don't save*/)) + { + if (!setSelected (fallBackRow, fallBackCol)) + { + if (!setSelected (0, 0)) + { + kError () << "kpToolWidgetBase::finishConstruction() " + "can't even fall back to setSelected(row=0,col=0)" << endl; + } + } + } +} + +//--------------------------------------------------------------------- + +// private +QList kpToolWidgetBase::spreadOutElements (const QList &sizes, int max) +{ + if (sizes.count () == 0) + return QList (); + else if (sizes.count () == 1) + { + QList ret; + ret.append (sizes.first () > max ? 0 : 1/*margin*/); + return ret; + } + + QList retOffsets; + for (int i = 0; i < sizes.count (); i++) + retOffsets.append (0); + + int totalSize = 0; + for (int i = 0; i < (int) sizes.count (); i++) + totalSize += sizes [i]; + + int margin = 1; + + // if don't fit with margin, then just return elements + // packed right next to each other + if (totalSize + margin * 2 > max) + { + retOffsets [0] = 0; + for (int i = 1; i < (int) sizes.count (); i++) + retOffsets [i] = retOffsets [i - 1] + sizes [i - 1]; + + return retOffsets; + } + + int maxLeftOver = max - (totalSize + margin * 2 * sizes.count()); + + int startCompensating = -1; + int numCompensate = 0; + + int spacing = 0; + + spacing = maxLeftOver / (sizes.count () - 1); + if (spacing * int (sizes.count () - 1) < maxLeftOver) + { + numCompensate = maxLeftOver - spacing * (sizes.count () - 1); + startCompensating = ((sizes.count () - 1) - numCompensate) / 2; + } + + retOffsets [0] = margin; + for (int i = 1; i < (int) sizes.count (); i++) + { + retOffsets [i] += retOffsets [i - 1] + + sizes [i - 1] + + spacing + + ((numCompensate && + i >= startCompensating && + i < startCompensating + numCompensate) ? 1 : 0); + } + + return retOffsets; +} + +//--------------------------------------------------------------------- + +// public +QPair kpToolWidgetBase::defaultSelectedRowAndCol () const +{ + int row = -1, col = -1; + + if (!objectName ().isEmpty ()) + { + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupTools); + + row = cfg.readEntry (objectName () + QLatin1String (" Row"), -1); + col = cfg.readEntry (objectName () + QLatin1String (" Col"), -1); + } + +#if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "kpToolWidgetBase(" << objectName () + << ")::defaultSelectedRowAndCol() returning row=" << row + << " col=" << col + << endl; +#endif + + return qMakePair (row, col); +} + +//--------------------------------------------------------------------- + +// public +int kpToolWidgetBase::defaultSelectedRow () const +{ + return defaultSelectedRowAndCol ().first; +} + +//--------------------------------------------------------------------- + +// public +int kpToolWidgetBase::defaultSelectedCol () const +{ + return defaultSelectedRowAndCol ().second; +} + +//--------------------------------------------------------------------- + +// public +void kpToolWidgetBase::saveSelectedAsDefault () const +{ +#if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "kpToolWidgetBase(" << objectName () + << ")::saveSelectedAsDefault() row=" << m_selectedRow + << " col=" << m_selectedCol << endl; +#endif + + if (objectName ().isEmpty ()) + return; + + KConfigGroup cfg (KGlobal::config (), kpSettingsGroupTools); + + cfg.writeEntry (objectName () + QLatin1String (" Row"), m_selectedRow); + cfg.writeEntry (objectName () + QLatin1String (" Col"), m_selectedCol); + cfg.sync (); +} + +//--------------------------------------------------------------------- + +// public +void kpToolWidgetBase::relayoutOptions () +{ +#if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "kpToolWidgetBase::relayoutOptions() size=" << size (); +#endif + + while (!m_pixmaps.isEmpty () && m_pixmaps.last ().count () == 0) + { + #if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "\tkilling #" << m_pixmaps.count () - 1; + #endif + m_pixmaps.removeLast (); + m_pixmapRects.removeLast (); + m_toolTips.removeLast (); + } + + if (m_pixmaps.isEmpty ()) + return; + +#if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "\tsurvived killing of empty rows"; + kDebug () << "\tfinding heights of rows:"; +#endif + + QList maxHeightOfRow; + for (int r = 0; r < m_pixmaps.count (); r++) + maxHeightOfRow.append (0); + + for (int r = 0; r < (int) m_pixmaps.count (); r++) + { + for (int c = 0; c < (int) m_pixmaps [r].count (); c++) + { + if (c == 0 || m_pixmaps [r][c].height () > maxHeightOfRow [r]) + maxHeightOfRow [r] = m_pixmaps [r][c].height (); + } + #if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "\t\t" << r << ": " << maxHeightOfRow [r]; + #endif + } + + QList rowYOffset = spreadOutElements (maxHeightOfRow, height ()); +#if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "\tspread out offsets of rows:"; + for (int r = 0; r < (int) rowYOffset.count (); r++) + kDebug () << "\t\t" << r << ": " << rowYOffset [r]; +#endif + + for (int r = 0; r < (int) m_pixmaps.count (); r++) + { + #if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "\tlaying out row " << r << ":"; + #endif + + QList widths; + for (int c = 0; c < (int) m_pixmaps [r].count (); c++) + widths.append (m_pixmaps [r][c].width ()); + #if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "\t\twidths of cols:"; + for (int c = 0; c < (int) m_pixmaps [r].count (); c++) + kDebug () << "\t\t\t" << c << ": " << widths [c]; + #endif + + QList colXOffset = spreadOutElements (widths, width ()); + #if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "\t\tspread out offsets of cols:"; + for (int c = 0; c < (int) colXOffset.count (); c++) + kDebug () << "\t\t\t" << c << ": " << colXOffset [c]; + #endif + + for (int c = 0; c < (int) colXOffset.count (); c++) + { + int x = colXOffset [c]; + int y = rowYOffset [r]; + int w, h; + + if (c == (int) colXOffset.count () - 1) + { + if (x + m_pixmaps [r][c].width () >= width ()) + w = m_pixmaps [r][c].width (); + else + w = width () - 1 - x; + } + else + w = colXOffset [c + 1] - x; + + if (r == (int) m_pixmaps.count () - 1) + { + if (y + m_pixmaps [r][c].height () >= height ()) + h = m_pixmaps [r][c].height (); + else + h = height () - 1 - y; + } + else + h = rowYOffset [r + 1] - y; + + m_pixmapRects [r][c] = QRect (x, y, w, h); + } + } + + update (); +} + +//--------------------------------------------------------------------- + + +// public +int kpToolWidgetBase::selectedRow () const +{ + return m_selectedRow; +} + +//--------------------------------------------------------------------- + +// public +int kpToolWidgetBase::selectedCol () const +{ + return m_selectedCol; +} + +//--------------------------------------------------------------------- + +// public +int kpToolWidgetBase::selected () const +{ + if (m_selectedRow < 0 || + m_selectedRow >= (int) m_pixmaps.count () || + m_selectedCol < 0) + { + return -1; + } + + int upto = 0; + for (int y = 0; y < m_selectedRow; y++) + upto += m_pixmaps [y].count (); + + if (m_selectedCol >= (int) m_pixmaps [m_selectedRow].count ()) + return -1; + + upto += m_selectedCol; + + return upto; +} + +//--------------------------------------------------------------------- + + +// public +bool kpToolWidgetBase::hasPreviousOption (int *row, int *col) const +{ +#if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "kpToolWidgetBase(" << objectName () + << ")::hasPreviousOption() current row=" << m_selectedRow + << " col=" << m_selectedCol + << endl; +#endif + if (row) + *row = -1; + if (col) + *col = -1; + + + if (m_selectedRow < 0 || m_selectedCol < 0) + return false; + + int newRow = m_selectedRow, + newCol = m_selectedCol; + + newCol--; + if (newCol < 0) + { + newRow--; + if (newRow < 0) + return false; + + newCol = m_pixmaps [newRow].count () - 1; + if (newCol < 0) + return false; + } + + + if (row) + *row = newRow; + if (col) + *col = newCol; + + return true; +} + +//--------------------------------------------------------------------- + +// public +bool kpToolWidgetBase::hasNextOption (int *row, int *col) const +{ +#if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "kpToolWidgetBase(" << objectName () + << ")::hasNextOption() current row=" << m_selectedRow + << " col=" << m_selectedCol + << endl; +#endif + + if (row) + *row = -1; + if (col) + *col = -1; + + + if (m_selectedRow < 0 || m_selectedCol < 0) + return false; + + int newRow = m_selectedRow, + newCol = m_selectedCol; + + newCol++; + if (newCol >= (int) m_pixmaps [newRow].count ()) + { + newRow++; + if (newRow >= (int) m_pixmaps.count ()) + return false; + + newCol = 0; + if (newCol >= (int) m_pixmaps [newRow].count ()) + return false; + } + + + if (row) + *row = newRow; + if (col) + *col = newCol; + + return true; +} + +//--------------------------------------------------------------------- + + +// public slot virtual +bool kpToolWidgetBase::setSelected (int row, int col, bool saveAsDefault) +{ +#if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "kpToolWidgetBase::setSelected(row=" << row + << ",col=" << col + << ",saveAsDefault=" << saveAsDefault + << ")" + << endl; +#endif + + if (row < 0 || col < 0 || + row >= (int) m_pixmapRects.count () || col >= (int) m_pixmapRects [row].count ()) + { + #if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "\tout of range"; + #endif + return false; + } + + if (row == m_selectedRow && col == m_selectedCol) + { + #if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "\tNOP"; + #endif + + if (saveAsDefault) + saveSelectedAsDefault (); + + return true; + } + + const int wasSelectedRow = m_selectedRow; + const int wasSelectedCol = m_selectedCol; + + m_selectedRow = row, m_selectedCol = col; + + if (wasSelectedRow >= 0 && wasSelectedCol >= 0) + { + // unhighlight old option + update (m_pixmapRects [wasSelectedRow][wasSelectedCol]); + } + + // highlight new option + update (m_pixmapRects [row][col]); + +#if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "\tOK"; +#endif + + if (saveAsDefault) + saveSelectedAsDefault (); + + emit optionSelected (row, col); + return true; +} + +//--------------------------------------------------------------------- + +// public slot +bool kpToolWidgetBase::setSelected (int row, int col) +{ + return setSelected (row, col, true/*set as default*/); +} + +//--------------------------------------------------------------------- + + +// public slot +bool kpToolWidgetBase::selectPreviousOption () +{ + int newRow, newCol; + if (!hasPreviousOption (&newRow, &newCol)) + return false; + + return setSelected (newRow, newCol); +} + +//--------------------------------------------------------------------- + +// public slot +bool kpToolWidgetBase::selectNextOption () +{ + int newRow, newCol; + if (!hasNextOption (&newRow, &newCol)) + return false; + + return setSelected (newRow, newCol); +} + +//--------------------------------------------------------------------- + + +// protected virtual [base QWidget] +bool kpToolWidgetBase::event (QEvent *e) +{ + // TODO: It's unclear when we should call the base, call accept() and + // return true or false. Look at other event() handlers. The + // kpToolText one is wrong since after calling accept(), it calls + // its base which calls ignore() :) + if (e->type () == QEvent::ToolTip) + { + QHelpEvent *he = (QHelpEvent *) e; + #if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "kpToolWidgetBase::event() QHelpEvent pos=" << he->pos (); + #endif + + bool showedText = false; + for (int r = 0; r < (int) m_pixmapRects.count (); r++) + { + for (int c = 0; c < (int) m_pixmapRects [r].count (); c++) + { + if (m_pixmapRects [r][c].contains (he->pos ())) + { + const QString tip = m_toolTips [r][c]; + #if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "\tin option: r=" << r << "c=" << c + << "tip='" << tip << "'" << endl; + #endif + if (!tip.isEmpty ()) + { + QToolTip::showText (he->globalPos (), tip, this); + showedText = true; + } + + e->accept (); + goto exit_loops; + } + } + } + + exit_loops: + if (!showedText) + { + #if DEBUG_KP_TOOL_WIDGET_BASE + kDebug () << "\thiding text"; + #endif + QToolTip::hideText (); + } + + return true; + } + else + return QWidget::event (e); +} + +//--------------------------------------------------------------------- + + +// protected virtual [base QWidget] +void kpToolWidgetBase::mousePressEvent (QMouseEvent *e) +{ + e->ignore (); + + if (e->button () != Qt::LeftButton) + return; + + + for (int i = 0; i < (int) m_pixmapRects.count (); i++) + { + for (int j = 0; j < (int) m_pixmapRects [i].count (); j++) + { + if (m_pixmapRects [i][j].contains (e->pos ())) + { + setSelected (i, j); + e->accept (); + return; + } + } + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpToolWidgetBase::paintEvent (QPaintEvent *e) +{ +#if DEBUG_KP_TOOL_WIDGET_BASE && 1 + kDebug () << "kpToolWidgetBase::paintEvent(): rect=" << contentsRect (); +#endif + + // Draw frame first. + QFrame::paintEvent (e); + + QPainter painter (this); + + for (int i = 0; i < (int) m_pixmaps.count (); i++) + { + #if DEBUG_KP_TOOL_WIDGET_BASE && 1 + kDebug () << "\tRow: " << i; + #endif + + for (int j = 0; j < (int) m_pixmaps [i].count (); j++) + { + QRect rect = m_pixmapRects [i][j]; + QPixmap pixmap = m_pixmaps [i][j]; + + #if DEBUG_KP_TOOL_WIDGET_BASE && 1 + kDebug () << "\t\tCol: " << j << " rect=" << rect; + #endif + + if (i == m_selectedRow && j == m_selectedCol) + { + painter.fillRect(rect, palette().color(QPalette::Highlight).rgb()); + } + + #if DEBUG_KP_TOOL_WIDGET_BASE && 1 + kDebug () << "\t\t\tdraw pixmap @ x=" + << rect.x () + (rect.width () - pixmap.width ()) / 2 + << " y=" + << rect.y () + (rect.height () - pixmap.height ()) / 2 + << endl; + + #endif + + painter.drawPixmap(QPoint(rect.x () + (rect.width () - pixmap.width ()) / 2, + rect.y () + (rect.height () - pixmap.height ()) / 2), + pixmap); + } + } +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetBase.h b/kolourpaint/widgets/toolbars/options/kpToolWidgetBase.h new file mode 100644 index 00000000..28109d16 --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetBase.h @@ -0,0 +1,114 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_WIDGET_BASE_H +#define KP_TOOL_WIDGET_BASE_H + + +#include +#include +#include +#include +#include +#include + + +class QMouseEvent; + + +// TODO: This is a crazy and overcomplicated class that invents its own (buggy) +// layout management. It should be simplified or removed. +class kpToolWidgetBase : public QFrame +{ +Q_OBJECT + +public: + // (must provide a for config to work) + kpToolWidgetBase (QWidget *parent, const QString &name); + virtual ~kpToolWidgetBase (); + +public: + void addOption (const QPixmap &pixmap, const QString &toolTip = QString()); + void startNewOptionRow (); + + // Call this at the end of your constructor. + // If the default row & col could not be read from the config, + // & are passed to setSelected(). + void finishConstruction (int fallBackRow, int fallBackCol); + +private: + QList spreadOutElements (const QList &sizes, int maxSize); + +public: // (only have to use these if you don't use finishConstruction()) + // (rereads from config file) + QPair defaultSelectedRowAndCol () const; + int defaultSelectedRow () const; + int defaultSelectedCol () const; + + void saveSelectedAsDefault () const; + + void relayoutOptions (); + +public: + int selectedRow () const; + int selectedCol () const; + + int selected () const; + + bool hasPreviousOption (int *row = 0, int *col = 0) const; + bool hasNextOption (int *row = 0, int *col = 0) const; + +public slots: + // (returns whether and were in range) + virtual bool setSelected (int row, int col, bool saveAsDefault); + bool setSelected (int row, int col); + + bool selectPreviousOption (); + bool selectNextOption (); + +signals: + void optionSelected (int row, int col); + +protected: + virtual bool event (QEvent *e); + + virtual void mousePressEvent (QMouseEvent *e); + virtual void paintEvent (QPaintEvent *e); + + QWidget *m_baseWidget; + + QList < QList > m_pixmaps; + QList < QList > m_toolTips; + + QList < QList > m_pixmapRects; + + int m_selectedRow, m_selectedCol; +}; + + +#endif // KP_TOOL_WIDGET_BASE_H diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetBrush.cpp b/kolourpaint/widgets/toolbars/options/kpToolWidgetBrush.cpp new file mode 100644 index 00000000..acbf41cd --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetBrush.cpp @@ -0,0 +1,299 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_WIDGET_BRUSH 0 + + +#include + +#include +#include + +#include +#include + +#include + +//--------------------------------------------------------------------- + +// LOREFACTOR: more OO, no arrays (use safer structs). +/* sync: */ +static int BrushSizes [][3] = +{ + {8, 4, 1/*like Pen*/}, + {9, 5, 2}, + {9, 5, 2}, + {9, 5, 2} +}; + +#define BRUSH_SIZE_NUM_COLS (int (sizeof (::BrushSizes [0]) / sizeof (::BrushSizes [0][0]))) +#define BRUSH_SIZE_NUM_ROWS (int (sizeof (::BrushSizes) / sizeof (::BrushSizes [0]))) + + +//--------------------------------------------------------------------- + +static void Draw (kpImage *destImage, const QPoint &topLeft, void *userData) +{ + kpToolWidgetBrush::DrawPackage *pack = + static_cast (userData); + +#if DEBUG_KP_TOOL_WIDGET_BRUSH + kDebug () << "kptoolwidgetbrush.cpp:Draw(destImage,topLeft=" + << topLeft << " pack: row=" << pack->row << " col=" << pack->col + << " color=" << (int *) pack->color.toQRgb () + << endl; +#endif + const int size = ::BrushSizes [pack->row][pack->col]; +#if DEBUG_KP_TOOL_WIDGET_BRUSH + kDebug () << "\tsize=" << size; +#endif + + QPainter painter(destImage); + + if ( size == 1 ) + { + painter.setPen(pack->color.toQColor()); + painter.drawPoint(topLeft); + return; + } + + // sync: + switch (pack->row/*shape*/) + { + case 0: + { + // work around ugly circle when using QPainter on QImage + if ( size == 4 ) + { + // do not draw a pixel twice, as with an alpha color it will become darker + painter.setPen(Qt::NoPen); + painter.setBrush(pack->color.toQColor()); + painter.drawRect(topLeft.x() + 1, topLeft.y(), 2, size); + painter.setPen(pack->color.toQColor()); + painter.drawLine(topLeft.x(), topLeft.y() + 1, topLeft.x(), topLeft.y() + 2); + painter.drawLine(topLeft.x() + 3, topLeft.y() + 1, topLeft.x() + 3, topLeft.y() + 2); + } + else if ( size == 8 ) // size defined in BrushSizes above + { + // do not draw a pixel twice, as with an alpha color it will become darker + painter.setPen(Qt::NoPen); + painter.setBrush(pack->color.toQColor()); + painter.drawRect(topLeft.x() + 2, topLeft.y(), 4, size); + painter.drawRect(topLeft.x(), topLeft.y() + 2, 2, 4); + painter.drawRect(topLeft.x() + 6, topLeft.y() + 2, 2, 4); + painter.setPen(pack->color.toQColor()); + painter.drawPoint(topLeft.x() + 1, topLeft.y() + 1); + painter.drawPoint(topLeft.x() + 6, topLeft.y() + 1); + painter.drawPoint(topLeft.x() + 1, topLeft.y() + 6); + painter.drawPoint(topLeft.x() + 6, topLeft.y() + 6); + } + else + { + Q_ASSERT(!"illegal size"); + } + break; + } + + case 1: + { + // only paint filling so that a color with an alpha channel does not + // create a darker border due to drawing some pixels twice with composition + painter.setPen(Qt::NoPen); + painter.setBrush(pack->color.toQColor()); + painter.drawRect(topLeft.x(), topLeft.y(), size, size); + break; + } + + case 2: + { + painter.setPen(pack->color.toQColor()); + painter.drawLine(topLeft.x() + size - 1, topLeft.y(), + topLeft.x(), topLeft.y() + size - 1); + break; + } + + case 3: + { + painter.setPen(pack->color.toQColor()); + painter.drawLine(topLeft.x(), topLeft.y(), + topLeft.x() + size - 1, topLeft.y() + size - 1); + break; + } + + default: + Q_ASSERT (!"Unknown row"); + break; + } +} + +//--------------------------------------------------------------------- + +kpToolWidgetBrush::kpToolWidgetBrush (QWidget *parent, const QString &name) + : kpToolWidgetBase (parent, name) +{ + for (int shape = 0; shape < BRUSH_SIZE_NUM_ROWS; shape++) + { + for (int i = 0; i < BRUSH_SIZE_NUM_COLS; i++) + { + const int s = ::BrushSizes [shape][i]; + + + const int w = (width () - 2/*margin*/ - 2/*spacing*/) + / BRUSH_SIZE_NUM_COLS; + const int h = (height () - 2/*margin*/ - 3/*spacing*/) + / BRUSH_SIZE_NUM_ROWS; + Q_ASSERT (w >= s && h >= s); + QImage previewPixmap (w, h, QImage::Format_ARGB32_Premultiplied); + previewPixmap.fill(0); + + DrawPackage pack = drawFunctionDataForRowCol (kpColor::Black, shape, i); + ::Draw (&previewPixmap, + QPoint ((previewPixmap.width () - s) / 2, + (previewPixmap.height () - s) / 2), + &pack); + + + addOption (QPixmap::fromImage(previewPixmap), brushName (shape, i)/*tooltip*/); + } + + startNewOptionRow (); + } + + finishConstruction (0, 0); +} + +//--------------------------------------------------------------------- + +kpToolWidgetBrush::~kpToolWidgetBrush () +{ +} + +//--------------------------------------------------------------------- + +// private +QString kpToolWidgetBrush::brushName (int shape, int whichSize) const +{ + int s = ::BrushSizes [shape][whichSize]; + + if (s == 1) + return i18n ("1x1"); + + QString shapeName; + + // sync: + switch (shape) + { + case 0: + shapeName = i18n ("Circle"); + break; + case 1: + shapeName = i18n ("Square"); + break; + case 2: + // TODO: is this really the name of a shape? :) + shapeName = i18n ("Slash"); + break; + case 3: + // TODO: is this really the name of a shape? :) + shapeName = i18n ("Backslash"); + break; + } + + if (shapeName.isEmpty ()) + return QString(); + + return i18n ("%1x%2 %3", s, s, shapeName); +} + +//--------------------------------------------------------------------- + +// public +int kpToolWidgetBrush::brushSize () const +{ + return ::BrushSizes [selectedRow ()][selectedCol ()]; +} + +//--------------------------------------------------------------------- + +// public +bool kpToolWidgetBrush::brushIsDiagonalLine () const +{ + // sync: + return (selectedRow () >= 2); +} + +//--------------------------------------------------------------------- + + +// public +kpTempImage::UserFunctionType kpToolWidgetBrush::drawFunction () const +{ + return &::Draw; +} + +//--------------------------------------------------------------------- + + +// public static +kpToolWidgetBrush::DrawPackage kpToolWidgetBrush::drawFunctionDataForRowCol ( + const kpColor &color, int row, int col) +{ + Q_ASSERT (row >= 0 && col >= 0); + + DrawPackage pack; + + pack.row = row; + pack.col = col; + pack.color = color; + + return pack; +} + +//--------------------------------------------------------------------- + +// public +kpToolWidgetBrush::DrawPackage kpToolWidgetBrush::drawFunctionData ( + const kpColor &color) const +{ + return drawFunctionDataForRowCol (color, selectedRow (), selectedCol ()); +} + +//--------------------------------------------------------------------- + +// protected slot virtual [base kpToolWidgetBase] +bool kpToolWidgetBrush::setSelected (int row, int col, bool saveAsDefault) +{ + const bool ret = kpToolWidgetBase::setSelected (row, col, saveAsDefault); + if (ret) + emit brushChanged (); + return ret; +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetBrush.h b/kolourpaint/widgets/toolbars/options/kpToolWidgetBrush.h new file mode 100644 index 00000000..1ab63948 --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetBrush.h @@ -0,0 +1,81 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_WIDGET_BRUSH_H +#define KP_TOOL_WIDGET_BRUSH_H + + +#include + +#include +#include +#include + + +class kpToolWidgetBrush : public kpToolWidgetBase +{ +Q_OBJECT + +public: + kpToolWidgetBrush (QWidget *parent, const QString &name); + virtual ~kpToolWidgetBrush (); + +private: + QString brushName (int shape, int whichSize) const; + +public: + int brushSize () const; + bool brushIsDiagonalLine () const; + + struct DrawPackage + { + int row; + int col; + kpColor color; + }; + + // Call the function returned by to render the current + // brush onto an image/document, in . Pass the pointer returned by + // to it. + // + // TODO: change function + data -> object + kpTempImage::UserFunctionType drawFunction () const; + + static DrawPackage drawFunctionDataForRowCol (const kpColor &color, + int row, int col); + DrawPackage drawFunctionData (const kpColor &color) const; + +signals: + void brushChanged (); + +protected slots: + virtual bool setSelected (int row, int col, bool saveAsDefault); +}; + + +#endif // KP_TOOL_WIDGET_BRUSH_H diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetEraserSize.cpp b/kolourpaint/widgets/toolbars/options/kpToolWidgetEraserSize.cpp new file mode 100644 index 00000000..7616c8fd --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetEraserSize.cpp @@ -0,0 +1,188 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_WIDGET_ERASER_SIZE 0 + + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + + +static int EraserSizes [] = {2, 3, 5, 9, 17, 29}; +static const int NumEraserSizes = + int (sizeof (::EraserSizes) / sizeof (::EraserSizes [0])); + + +static void DrawImage (kpImage *destImage, const QPoint &topLeft, void *userData) +{ + kpToolWidgetEraserSize::DrawPackage *pack = + static_cast (userData); + + const int size = ::EraserSizes [pack->selected]; + + kpPainter::fillRect (destImage, + topLeft.x (), topLeft.y (), size, size, + pack->color); +} + +static void DrawCursor (kpImage *destImage, const QPoint &topLeft, void *userData) +{ + ::DrawImage (destImage, topLeft, userData); + + + kpToolWidgetEraserSize::DrawPackage *pack = + static_cast (userData); + + const int size = ::EraserSizes [pack->selected]; + + // Would 1-pixel border on all sides completely cover the color of the + // eraser? + if (size <= 2) + return; + + // Draw 1-pixel border on all sides. + kpPainter::drawRect (destImage, + topLeft.x (), topLeft.y (), size, size, + kpColor::Black); +} + +//--------------------------------------------------------------------- + +kpToolWidgetEraserSize::kpToolWidgetEraserSize (QWidget *parent, const QString &name) + : kpToolWidgetBase (parent, name) +{ + for (int i = 0; i < ::NumEraserSizes; i++) + { + if (i == 3 || i == 5) + startNewOptionRow (); + + const int s = ::EraserSizes [i]; + + QImage previewPixmap (s, s, QImage::Format_ARGB32_Premultiplied); + if (i < 3) + { + // HACK: kpToolWidgetBase's layout code sucks and gives uneven spacing + previewPixmap = QImage ((width () - 4) / 3, 9, QImage::Format_ARGB32_Premultiplied); + Q_ASSERT (previewPixmap.width () >= s && + previewPixmap.height () >= s); + } + + previewPixmap.fill(0); + + DrawPackage pack = drawFunctionDataForSelected (kpColor::Black, i); + ::DrawImage (&previewPixmap, + QPoint ((previewPixmap.width () - s) / 2, + (previewPixmap.height () - s) / 2), + &pack); + + + addOption (QPixmap::fromImage(previewPixmap), i18n ("%1x%2", s, s)/*tooltip*/); + } + + finishConstruction (1, 0); +} + +//--------------------------------------------------------------------- + +kpToolWidgetEraserSize::~kpToolWidgetEraserSize () +{ +} + +//--------------------------------------------------------------------- + + +// public +int kpToolWidgetEraserSize::eraserSize () const +{ + return ::EraserSizes[selected() < 0 ? 0 : selected()]; +} + + +// public +kpTempImage::UserFunctionType kpToolWidgetEraserSize::drawFunction () const +{ + return &::DrawImage; +} + +// public +kpTempImage::UserFunctionType kpToolWidgetEraserSize::drawCursorFunction () const +{ + return &::DrawCursor; +} + +//--------------------------------------------------------------------- + + +// public static +kpToolWidgetEraserSize::DrawPackage kpToolWidgetEraserSize::drawFunctionDataForSelected ( + const kpColor &color, int selectedIndex) +{ + DrawPackage pack; + + pack.selected = selectedIndex; + pack.color = color; + + return pack; +} + +//--------------------------------------------------------------------- + +// public +kpToolWidgetEraserSize::DrawPackage kpToolWidgetEraserSize::drawFunctionData ( + const kpColor &color) const +{ + return drawFunctionDataForSelected (color, selected ()); +} + +//--------------------------------------------------------------------- + + +// protected slot virtual [base kpToolWidgetBase] +bool kpToolWidgetEraserSize::setSelected (int row, int col, bool saveAsDefault) +{ + const bool ret = kpToolWidgetBase::setSelected (row, col, saveAsDefault); + if (ret) + emit eraserSizeChanged (eraserSize ()); + return ret; +} + +//--------------------------------------------------------------------- + + +#include diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetEraserSize.h b/kolourpaint/widgets/toolbars/options/kpToolWidgetEraserSize.h new file mode 100644 index 00000000..df8f2208 --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetEraserSize.h @@ -0,0 +1,82 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_WIDGET_ERASER_SIZE_H +#define KP_TOOL_WIDGET_ERASER_SIZE_H + + +#include + +#include +#include +#include + + +class kpColor; + + +class kpToolWidgetEraserSize : public kpToolWidgetBase +{ +Q_OBJECT + +public: + kpToolWidgetEraserSize (QWidget *parent, const QString &name); + virtual ~kpToolWidgetEraserSize (); + + int eraserSize () const; + + struct DrawPackage + { + int selected; + kpColor color; + }; + + // Call the function returned by to render the current + // brush onto an image/document, in . Pass the pointer returned by + // to it. + // + // is to same as but adds a black + // border suitable as a cursor only. + // + // TODO: change function + data -> object + kpTempImage::UserFunctionType drawFunction () const; + kpTempImage::UserFunctionType drawCursorFunction () const; + + static DrawPackage drawFunctionDataForSelected (const kpColor &color, + int selectedIndex); + DrawPackage drawFunctionData (const kpColor &color) const; + +signals: + void eraserSizeChanged (int size); + +protected slots: + virtual bool setSelected (int row, int col, bool saveAsDefault); +}; + + +#endif // KP_TOOL_WIDGET_ERASER_SIZE_H diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetFillStyle.cpp b/kolourpaint/widgets/toolbars/options/kpToolWidgetFillStyle.cpp new file mode 100644 index 00000000..9df41757 --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetFillStyle.cpp @@ -0,0 +1,182 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_WIDGET_FILL_STYLE 0 + + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +kpToolWidgetFillStyle::kpToolWidgetFillStyle (QWidget *parent, const QString &name) + : kpToolWidgetBase (parent, name) +{ + for (int i = 0; i < (int) FillStyleNum; i++) + { + QPixmap pixmap; + + pixmap = fillStylePixmap ((FillStyle) i, + (width () - 2/*margin*/) * 3 / 4, + (height () - 2/*margin*/ - 2/*spacing*/) * 3 / (3 * 4)); + addOption (pixmap, fillStyleName ((FillStyle) i)/*tooltip*/); + + startNewOptionRow (); + } + + finishConstruction (0, 0); +} + +//--------------------------------------------------------------------- + +kpToolWidgetFillStyle::~kpToolWidgetFillStyle () +{ +} + +//--------------------------------------------------------------------- + +// private +QPixmap kpToolWidgetFillStyle::fillStylePixmap (FillStyle fs, int w, int h) +{ + QPixmap pixmap ((w <= 0 ? width () : w), (h <= 0 ? height () : h)); + pixmap.fill(palette().color(QPalette::Window)); + + const int penWidth = 2; + + const QRect rectRect(1, 1, w - 2, h - 2); + + QPainter painter(&pixmap); + painter.setPen(kpPixmapFX::QPainterDrawRectPen(Qt::black, penWidth)); + + switch ( fs ) + { + case NoFill: + { + painter.setBrush(Qt::NoBrush); + break; + } + case FillWithBackground: + { + painter.setBrush(Qt::gray); + break; + } + case FillWithForeground: + { + painter.setBrush(Qt::black); + break; + } + default: ; + } + + painter.drawRect(rectRect); + painter.end(); + + return pixmap; +} + +//--------------------------------------------------------------------- + +// private +QString kpToolWidgetFillStyle::fillStyleName (FillStyle fs) const +{ + // do not complain about the "useless" breaks + // as the return statements might not be return statements one day + + switch (fs) + { + case NoFill: + return i18n ("No Fill"); + break; + case FillWithBackground: + return i18n ("Fill with Background Color"); + break; + case FillWithForeground: + return i18n ("Fill with Foreground Color"); + break; + default: + return QString(); + break; + } +} + +//--------------------------------------------------------------------- + +// public +kpToolWidgetFillStyle::FillStyle kpToolWidgetFillStyle::fillStyle () const +{ +#if DEBUG_KP_TOOL_WIDGET_FILL_STYLE + kDebug () << "kpToolWidgetFillStyle::fillStyle() selected=" + << selectedRow () + << endl; +#endif + return (FillStyle) selectedRow (); +} + +//--------------------------------------------------------------------- + +kpColor kpToolWidgetFillStyle::drawingBackgroundColor ( + const kpColor &foregroundColor, const kpColor &backgroundColor) const +{ + switch (fillStyle ()) + { + default: + case NoFill: + return kpColor::Invalid; + + case FillWithBackground: + return backgroundColor; + + case FillWithForeground: + return foregroundColor; + } +} + +//--------------------------------------------------------------------- + +// virtual protected slot [base kpToolWidgetBase] +bool kpToolWidgetFillStyle::setSelected (int row, int col, bool saveAsDefault) +{ + const bool ret = kpToolWidgetBase::setSelected (row, col, saveAsDefault); + if (ret) + emit fillStyleChanged (fillStyle ()); + return ret; +} + +//--------------------------------------------------------------------- + +#include diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetFillStyle.h b/kolourpaint/widgets/toolbars/options/kpToolWidgetFillStyle.h new file mode 100644 index 00000000..3238d689 --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetFillStyle.h @@ -0,0 +1,79 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_WIDGET_FILL_STYLE_H +#define KP_TOOL_WIDGET_FILL_STYLE_H + + +#include + + +class QBrush; +class QPixmap; + +class kpColor; + + +class kpToolWidgetFillStyle : public kpToolWidgetBase +{ +Q_OBJECT + +public: + kpToolWidgetFillStyle (QWidget *parent, const QString &name); + virtual ~kpToolWidgetFillStyle (); + + enum FillStyle + { + NoFill, + FillWithBackground, + FillWithForeground, + FillStyleNum /* not (a valid FillStyle) */ + }; + +private: + QPixmap fillStylePixmap (FillStyle fs, int width, int height); + QString fillStyleName (FillStyle fs) const; + +public: + FillStyle fillStyle () const; + + // Returns the actual fill colour. + // e.g. "FillWithBackground" fillStyle() -> , + // "FillWithForeground" fillStyle() -> . + kpColor drawingBackgroundColor ( + const kpColor &foregroundColor, const kpColor &backgroundColor) const; + +signals: + void fillStyleChanged (kpToolWidgetFillStyle::FillStyle fillStyle); + +protected slots: + virtual bool setSelected (int row, int col, bool saveAsDefault); +}; + + +#endif // KP_TOOL_WIDGET_FILL_STYLE_H diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetLineWidth.cpp b/kolourpaint/widgets/toolbars/options/kpToolWidgetLineWidth.cpp new file mode 100644 index 00000000..9ed12832 --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetLineWidth.cpp @@ -0,0 +1,90 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include +#include + +#include + +#include +#include + + +static int lineWidths [] = {1, 2, 3, 5, 8}; + +kpToolWidgetLineWidth::kpToolWidgetLineWidth (QWidget *parent, const QString &name) + : kpToolWidgetBase (parent, name) +{ + int numLineWidths = sizeof (lineWidths) / sizeof (lineWidths [0]); + + int w = (width () - 2/*margin*/) * 3 / 4; + int h = (height () - 2/*margin*/ - (numLineWidths - 1)/*spacing*/) * 3 / (numLineWidths * 4); + + for (int i = 0; i < numLineWidths; i++) + { + QImage image ((w <= 0 ? width () : w), + (h <= 0 ? height () : h), QImage::Format_ARGB32_Premultiplied); + image.fill(QColor(Qt::transparent).rgba()); + + + kpPixmapFX::fillRect (&image, + 0, (image.height () - lineWidths [i]) / 2, + image.width (), lineWidths [i], + kpColor::Black); + + + addOption (QPixmap::fromImage(image), QString::number (lineWidths [i])); + startNewOptionRow (); + } + + finishConstruction (0, 0); +} + +kpToolWidgetLineWidth::~kpToolWidgetLineWidth () +{ +} + +int kpToolWidgetLineWidth::lineWidth () const +{ + return lineWidths [selectedRow ()]; +} + +// virtual protected slot [base kpToolWidgetBase] +bool kpToolWidgetLineWidth::setSelected (int row, int col, bool saveAsDefault) +{ + const bool ret = kpToolWidgetBase::setSelected (row, col, saveAsDefault); + if (ret) + emit lineWidthChanged (lineWidth ()); + return ret; +} + + +#include diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetLineWidth.h b/kolourpaint/widgets/toolbars/options/kpToolWidgetLineWidth.h new file mode 100644 index 00000000..14398b31 --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetLineWidth.h @@ -0,0 +1,54 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_WIDGET_LINE_WIDTH_H +#define KP_TOOL_WIDGET_LINE_WIDTH_H + + +#include + + +class kpToolWidgetLineWidth : public kpToolWidgetBase +{ +Q_OBJECT + +public: + kpToolWidgetLineWidth (QWidget *parent, const QString &name); + virtual ~kpToolWidgetLineWidth (); + + int lineWidth () const; + +signals: + void lineWidthChanged (int width); + +protected slots: + virtual bool setSelected (int row, int col, bool saveAsDefault); +}; + + +#endif // KP_TOOL_WIDGET_LINE_WIDTH_H diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.cpp b/kolourpaint/widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.cpp new file mode 100644 index 00000000..bae61887 --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.cpp @@ -0,0 +1,104 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_WIDGET_OPAQUE_OR_TRANSPARENT 0 + + +#include + +#include +#include +#include + + +//--------------------------------------------------------------------- + +kpToolWidgetOpaqueOrTransparent::kpToolWidgetOpaqueOrTransparent (QWidget *parent, const QString &name) + : kpToolWidgetBase (parent, name) +{ + addOption (UserIcon ("option_opaque"), i18n ("Opaque")/*tooltip*/); + startNewOptionRow (); + addOption (UserIcon ("option_transparent"), i18n ("Transparent")/*tooltip*/); + + finishConstruction (0, 0); +} + +//--------------------------------------------------------------------- + +kpToolWidgetOpaqueOrTransparent::~kpToolWidgetOpaqueOrTransparent () +{ +} + +//--------------------------------------------------------------------- + + +// public +bool kpToolWidgetOpaqueOrTransparent::isOpaque () const +{ + return (selected () == 0); +} + +// public +bool kpToolWidgetOpaqueOrTransparent::isTransparent () const +{ + return (!isOpaque ()); +} + +// public +void kpToolWidgetOpaqueOrTransparent::setOpaque (bool yes) +{ +#if DEBUG_KP_TOOL_WIDGET_OPAQUE_OR_TRANSPARENT && 1 + kDebug () << "kpToolWidgetOpaqueOrTransparent::setOpaque(" << yes << ")"; +#endif + setSelected (yes ? 0 : 1, 0, false/*don't save*/); +} + +// public +void kpToolWidgetOpaqueOrTransparent::setTransparent (bool yes) +{ +#if DEBUG_KP_TOOL_WIDGET_OPAQUE_OR_TRANSPARENT && 1 + kDebug () << "kpToolWidgetOpaqueOrTransparent::setTransparent(" << yes << ")"; +#endif + setSelected (yes ? 1 : 0, 0, false/*don't save*/); +} + + +// protected slot virtual [base kpToolWidgetBase] +bool kpToolWidgetOpaqueOrTransparent::setSelected (int row, int col, bool saveAsDefault) +{ +#if DEBUG_KP_TOOL_WIDGET_OPAQUE_OR_TRANSPARENT && 1 + kDebug () << "kpToolWidgetOpaqueOrTransparent::setSelected(" + << row << "," << col << ")" << endl; +#endif + const bool ret = kpToolWidgetBase::setSelected (row, col, saveAsDefault); + if (ret) + emit isOpaqueChanged (isOpaque ()); + return ret; +} + + +#include diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h b/kolourpaint/widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h new file mode 100644 index 00000000..bca63f9a --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h @@ -0,0 +1,57 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_WIDGET_OPAQUE_OR_TRANSPARENT_H +#define KP_TOOL_WIDGET_OPAQUE_OR_TRANSPARENT_H + + +#include + + +class kpToolWidgetOpaqueOrTransparent : public kpToolWidgetBase +{ +Q_OBJECT + +public: + kpToolWidgetOpaqueOrTransparent (QWidget *parent, const QString &name); + virtual ~kpToolWidgetOpaqueOrTransparent (); + + bool isOpaque () const; + bool isTransparent () const; + void setOpaque (bool yes = true); + void setTransparent (bool yes = true); + +signals: + void isOpaqueChanged (bool isOpaque); + +protected slots: + virtual bool setSelected (int row, int col, bool saveAsDefault); +}; + + +#endif // KP_TOOL_WIDGET_OPAQUE_OR_TRANSPARENT_H diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetSpraycanSize.cpp b/kolourpaint/widgets/toolbars/options/kpToolWidgetSpraycanSize.cpp new file mode 100644 index 00000000..811780fa --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetSpraycanSize.cpp @@ -0,0 +1,120 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_WIDGET_SPRAYCAN_SIZE 0 + + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + + +static int spraycanSizes [] = {9, 17, 29}; + +kpToolWidgetSpraycanSize::kpToolWidgetSpraycanSize (QWidget *parent, const QString &name) + : kpToolWidgetBase (parent, name) +{ +#if DEBUG_KP_TOOL_WIDGET_SPRAYCAN_SIZE + kDebug () << "kpToolWidgetSpraycanSize::kpToolWidgetSpraycanSize() CALLED!"; +#endif + + for (int i = 0; i < int (sizeof (spraycanSizes) / sizeof (spraycanSizes [0])); i++) + { + int s = spraycanSizes [i]; + QString iconName = QString ("tool_spraycan_%1x%2").arg (s).arg(s); + + #if DEBUG_KP_TOOL_WIDGET_SPRAYCAN_SIZE + kDebug () << "\ticonName=" << iconName; + #endif + + QPixmap pixmap (s, s); + pixmap.fill (Qt::white); + + QPainter painter (&pixmap); + painter.drawPixmap (0, 0, UserIcon (iconName)); + painter.end (); + + QImage image = pixmap.toImage(); + + QBitmap mask (pixmap.width (), pixmap.height ()); + mask.fill (Qt::color0); + + painter.begin (&mask); + painter.setPen (Qt::color1); + + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + if ((image.pixel (x, y) & RGB_MASK) == 0/*black*/) + painter.drawPoint (x, y); // mark as opaque + } + } + + painter.end (); + + pixmap.setMask (mask); + + addOption (pixmap, i18n ("%1x%2", s, s)/*tooltip*/); + if (i == 1) + startNewOptionRow (); + } + + finishConstruction (0, 0); +} + +kpToolWidgetSpraycanSize::~kpToolWidgetSpraycanSize () +{ +} + + +// public +int kpToolWidgetSpraycanSize::spraycanSize () const +{ + return spraycanSizes[selected() < 0 ? 0 : selected()]; +} + +// protected slot virtual [base kpToolWidgetBase] +bool kpToolWidgetSpraycanSize::setSelected (int row, int col, bool saveAsDefault) +{ + const bool ret = kpToolWidgetBase::setSelected (row, col, saveAsDefault); + if (ret) + emit spraycanSizeChanged (spraycanSize ()); + return ret; +} + +#include diff --git a/kolourpaint/widgets/toolbars/options/kpToolWidgetSpraycanSize.h b/kolourpaint/widgets/toolbars/options/kpToolWidgetSpraycanSize.h new file mode 100644 index 00000000..b604efb2 --- /dev/null +++ b/kolourpaint/widgets/toolbars/options/kpToolWidgetSpraycanSize.h @@ -0,0 +1,54 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_WIDGET_SPRAYCAN_SIZE_H +#define KP_TOOL_WIDGET_SPRAYCAN_SIZE_H + + +#include + + +class kpToolWidgetSpraycanSize : public kpToolWidgetBase +{ +Q_OBJECT + +public: + kpToolWidgetSpraycanSize (QWidget *parent, const QString &name); + virtual ~kpToolWidgetSpraycanSize (); + + int spraycanSize () const; + +signals: + void spraycanSizeChanged (int size); + +protected slots: + virtual bool setSelected (int row, int col, bool saveAsDefault); +}; + + +#endif // KP_TOOL_WIDGET_SPRAYCAN_SIZE_H diff --git a/ksnapshot/CMakeLists.txt b/ksnapshot/CMakeLists.txt new file mode 100644 index 00000000..9b2dc6ec --- /dev/null +++ b/ksnapshot/CMakeLists.txt @@ -0,0 +1,95 @@ +project(ksnapshot) + +find_package(KDE4 REQUIRED) + +include(KDE4Defaults) +include(MacroLibrary) + +macro_optional_find_package(Kipi) + +add_definitions (${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS} ${KDE4_DEFINITIONS}) +add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) +include_directories (${QDBUS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}) + +set(HAVE_X11_EXTENSIONS_SHAPE_H ${X11_Xshape_FOUND}) +set(HAVE_X11_EXTENSIONS_XFIXES_H ${X11_Xfixes_FOUND}) +macro_log_feature(HAVE_X11_EXTENSIONS_XFIXES_H "X11 Fixes Extension (xfixes.h)" "Support for capturing the cursor" "http://www.x.org/" FALSE "" "") + +if (KIPI_FOUND) + include_directories(${KIPI_INCLUDE_DIR}) +endif (KIPI_FOUND) +macro_log_feature(KIPI_FOUND "KIPI plugins" "Provides various image export features, such as printing, emailing and uploading" "http://www.kipi-plugins.org/" FALSE "" "") + +configure_file(config-ksnapshot.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ksnapshot.h) + +########### next target ############### + +add_subdirectory( doc ) + +set(ksnapshot_file_SRCS + expblur.cpp + regiongrabber.cpp + freeregiongrabber.cpp + snapshottimer.cpp + windowgrabber.cpp + ksnapshotobject.cpp + ksnapshotpreview.cpp) + +set(ksnapshot_SRCS + main.cpp + ksnapshot.cpp + ${ksnapshot_file_SRCS}) + +if (KIPI_FOUND) +set(ksnapshot_SRCS + kipiinterface.cpp + ksnapshotimagecollectionshared.cpp + ksnapshotinfoshared.cpp + kipiimagecollectionselector.cpp + ${ksnapshot_SRCS}) +endif (KIPI_FOUND) + +qt4_add_dbus_adaptor(ksnapshot_SRCS org.kde.ksnapshot.xml ksnapshot.h KSnapshot) + + +kde4_add_ui_files(ksnapshot_SRCS ksnapshotwidget.ui) + +kde4_add_app_icon(ksnapshot_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/hi*-app-ksnapshot.png") + +kde4_add_executable(ksnapshot ${ksnapshot_SRCS}) + +target_link_libraries(ksnapshot ${KDE4_KIO_LIBS} ${X11_LIBRARIES}) + +if (X11_Xfixes_FOUND) + target_link_libraries(ksnapshot ${X11_Xfixes_LIB}) +endif (X11_Xfixes_FOUND) + +if (KIPI_FOUND) + target_link_libraries(ksnapshot ${KIPI_LIBRARIES}) +endif (KIPI_FOUND) + +install(TARGETS ksnapshot ${INSTALL_TARGETS_DEFAULT_ARGS}) + + +########### next target ############### + +set(kbackgroundsnapshot_SRCS + kbackgroundsnapshot.cpp + ${ksnapshot_file_SRCS}) + + +kde4_add_executable(kbackgroundsnapshot ${kbackgroundsnapshot_SRCS}) + +target_link_libraries(kbackgroundsnapshot ${KDE4_KIO_LIBS} ${X11_LIBRARIES}) + +install(TARGETS kbackgroundsnapshot ${INSTALL_TARGETS_DEFAULT_ARGS}) + +########### install files ############### + + + +install(PROGRAMS ksnapshot.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) +install(FILES org.kde.ksnapshot.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR} ) +kde4_install_icons(${ICON_INSTALL_DIR} ) + +macro_display_feature_log() diff --git a/ksnapshot/COPYING b/ksnapshot/COPYING new file mode 100644 index 00000000..d511905c --- /dev/null +++ b/ksnapshot/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/ksnapshot/COPYING.DOC b/ksnapshot/COPYING.DOC new file mode 100644 index 00000000..4a0fe1c8 --- /dev/null +++ b/ksnapshot/COPYING.DOC @@ -0,0 +1,397 @@ + GNU Free Documentation License + Version 1.2, November 2002 + + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/ksnapshot/COPYING.LIB b/ksnapshot/COPYING.LIB new file mode 100644 index 00000000..2676d08a --- /dev/null +++ b/ksnapshot/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/ksnapshot/Messages.sh b/ksnapshot/Messages.sh new file mode 100644 index 00000000..d242e38b --- /dev/null +++ b/ksnapshot/Messages.sh @@ -0,0 +1,3 @@ +#! /bin/sh +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT *.h *.cpp -o $podir/ksnapshot.pot diff --git a/ksnapshot/config-ksnapshot.h.cmake b/ksnapshot/config-ksnapshot.h.cmake new file mode 100644 index 00000000..deca85f1 --- /dev/null +++ b/ksnapshot/config-ksnapshot.h.cmake @@ -0,0 +1,8 @@ +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_X11_EXTENSIONS_SHAPE_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_X11_EXTENSIONS_XFIXES_H 1 + +/* Define to 1 if you have libkipi */ +#cmakedefine KIPI_FOUND 1 diff --git a/ksnapshot/doc/CMakeLists.txt b/ksnapshot/doc/CMakeLists.txt new file mode 100644 index 00000000..b8e70937 --- /dev/null +++ b/ksnapshot/doc/CMakeLists.txt @@ -0,0 +1,4 @@ +########### install files ############### +# +# +kde4_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en SUBDIR ksnapshot) diff --git a/ksnapshot/doc/index.docbook b/ksnapshot/doc/index.docbook new file mode 100644 index 00000000..ad185718 --- /dev/null +++ b/ksnapshot/doc/index.docbook @@ -0,0 +1,687 @@ + + + + + +]> + + + + +The &ksnapshot; Handbook + + + +Richard +J. +Moore + +
&Richard.J.Moore.mail;
+
+
+ + +Robert +L. +McCormick + +
&Robert.L.McCormick.mail;
+
+
+ + +Brad +Hards + +
&Brad.Hards.mail;
+
+
+ + +Lauri +Watts + +
&Lauri.Watts.mail;
+
+Reviewer +
+ + +Richard +J +Moore + +
&Richard.J.Moore.mail;
+
+Developer +
+ + +Matthias +Ettrich + +
&Matthias.Ettrich.mail;
+
+Developer +
+ + +
+ + +1997-2000 +&Richard.J.Moore; + + + +2000 +&Matthias.Ettrich; + + +&FDLNotice; + +2013-05-22 +0.8.2 (&kde; 4.11) + + +&ksnapshot; is a simple application for taking screenshots. It is capable +of capturing images of the whole desktop, a single window, a section of a window, a selected +rectangular region or a freehand region. The images can then be saved in a variety of formats. + + + +KDE +KSnapshot +kdegraphics +screen capture +screen grab + + +
+ + +Introduction + +&ksnapshot; is a simple application for taking screenshots. It is capable +of capturing images of the whole desktop, a single window, a section of a window, a selected +rectangular region or a freehand region. The images can then be saved in a variety of formats. + +Please report any problems or feature requests to the &kde; Bug Tracking System. + + + + +Using &ksnapshot; + +This chapter describes the use of &ksnapshot; for capturing screen +images. + + +Starting &ksnapshot; + +&ksnapshot; may be started by one of several ways as described +below. + + + +You may start &ksnapshot; by selecting it from the application launcher menu + +ApplicationsGraphics +Screen Capture Program &ksnapshot;. + + +You may start &ksnapshot; by entering the following at the command +prompt: + +% ksnapshot & + + +The mini command line &krunner; (invoked with +&Alt;F2) may +also be used to start &ksnapshot;. + + +Once &ksnapshot; starts, you will see a window like the following: + + + + +&ksnapshot; Preview Window + + + + + + + +Taking A Snapshot + + &ksnapshot; grabs an image of your entire desktop immediately after it is +started, but before it displays itself on screen. This allows you to quickly +create full-desktop screenshot images. + +The snapshot taken by &ksnapshot; is displayed in the preview window, +which is located on top of the &ksnapshot; application window. +Below is an example of the preview window from &ksnapshot;. Your preview +will differ depending on what you have displayed on the desktop. + + + + + +&ksnapshot; Preview Window + + + +The snapshot can be saved by clicking on the Save +As... (&Ctrl;S) +button. This opens the standard &kde; save dialog, +where you can choose the filename, the folder location, and the format that your snapshot +will be saved in. If multiple snapshots are taken, the +filename is automatically incremented to prevent you from overwriting previous +snapshots. You may however edit the filename to anything you wish, including the name +of a previously saved snapshot. + +To take a snapshot of a single window, select the Window +Under Cursor combo box entry (next to the Capture mode +label), and then click on the Take a New Snapshot +(&Ctrl;N) button. + +Depending on your Snapshot delay settings you +get either a cross as the mouse pointer (for No delay), +or a standard mouse cursor which you can use to work with a program until +the delay is over and a snapshot is taken. + +With No delay, the snapshot is taken immediately when you +click into a window. + +&ksnapshot; will display the new snapshot in the preview +area, at which time you can choose to save the new image (by pressing +Save As...) +or to grab a new one, by pressing the +Take a New Snapshot button. + +To take a new snapshot of the entire desktop, select the +Full Screen combo box entry and then click on the +Take a New Snapshot button. +&ksnapshot; will now capture the entire desktop if you press +Take a New Snapshot. + +Similarly, to take a snapshot of a rectangular region, select the +Rectangular Region combo box entry and set the +Snapshot delay to No delay, +and then click on the Take a New Snapshot button. The +mouse cursor will then change into a cross, and you can then use the +mouse to select the region you want to capture. +To take the snapshot press the &Enter; key or double click. Press &Esc; to quit. + +You might want to take a snapshot of a non rectangular region. You can do so by selecting +the Freehand Region combo box entry and set the +Snapshot delay to No delay, +and then click on the Take a New Snapshot button. The +mouse cursor will then change into a cross, and you can then use the +mouse to draw the region you want to capture. +To take the snapshot press the &Enter; key or double click. Press &Esc; to quit. + +To take a new snapshot of a section of a window, select the +Section of Window combo box entry and then click on the +Take a New Snapshot button. With No delay +you get a cross as the mouse pointer and you have to click once with the &LMB; into +the window. The section of the window under the mouse cursor is now highlighted +with a red border. Move the mouse to the wanted section and click the &LMB; +to capture the screenshot. + + +If you have multiple screens, Current Screen captures +the screen containing the mouse cursor when the screenshot is taken. + +When keeping &ksnapshot; open to take several snapshots using +Rectangular Region or Freehand Region, +the subsequent snapshots will be initialized with the last shape used with this +function (since you launched &ksnapshot;). You have then the possibility to adjust the +handles of the rectangular shape, to move the freehand region, or to completely replace the +shape by starting to draw a new one at a different place of the screen. + + + +Additional Features + + +Snapshot Delay + +The Snapshot delay box allows you to enter an +arbitrary time delay, in seconds, between the time that you press the +Take a New Snapshot button and the time that the snapshot is +taken. + +When a delay time has been set, you do not have to click the mouse +button to capture a screenshot. This enables you to open a drop down menu, +and take a picture of it. + + + + +Exclude Window decorations + +Include window decorations is enabled by default in +Window Under Cursor mode. + +When you only want to capture the application itself without the surrounding +window decoration, disable this option and take a new snapshot. + + + + +Include mouse pointer + +Include mouse pointer is disabled by default. This setting is not +available in Rectangular Region and Freehand Region +modes. + +When you want to include the mouse pointer in your snapshot, enable this option and take +a new snapshot. + + + + + + + +Buttons + +There are four buttons located at the bottom of the +&ksnapshot; window. Their function is described below. + + + +Help +Gives you a menu where you can open the +&ksnapshot; Handbook, report a bug, +switch the language for &ksnapshot; or +get some more information About &ksnapshot; +and About &kde;. + + + + +Send To... +This will allow to directly open the snapshot with all programs +that are associated with your PNG (Portable Network Graphics) MIME type. Depending on what programs are installed, you +will be able to open and edit the snapshot in your graphics applications or viewers. +Furthermore, if you have the KIPI plugins installed you will be able to print your snapshots, +e-mail them and export directly to some social networks and websites, as shown on the screenshot below. + + + + +&ksnapshot; Send To... menu with kipi-plugins installed + + + + + + +Copy +When you want to edit your snapshot in a graphics application without saving +the snapshot, just click Copy +(&Ctrl;C) and insert the image +into a viewer or graphics application. + + + +Save As... +Saves the screenshot to a file in the selected format. + + + + + + +Drag and Drop +A captured image can be dragged to another application or document. If the application is able +to handle images, a copy of the full image is inserted there. + +If you drag a screenshot into a file manager window, a dialog pops up where you can edit the filename +and select the image format and the file will be inserted into the actual folder. + +If you drag the screenshot to a text box, the path to the temporary saved file is inserted. This is useful +for example to upload a screenshot through web forms or to attach screenshots into bug reports +on the &kde; bugtracker. + +This works with all clients that do not pick up the image data, but only look for a &URL; +in the dragged mimedata. + + + + + + + + +&DBus; Interface + +&ksnapshot; can be scripted using its &DBus; interface. + +There are two ways to use the &DBus; interface: &Qt;'s &GUI; +qdbusviewer +and the command line +qdbus. + + + +Examples: + + + +% +qdbus +will display all services available. + + + +% +qdbus + +will display the &DBus; interface for &ksnapshot;. + + + +% +qdbus + +will display methods for controlling &ksnapshot;. + + + + + + +For more information, please visit +&DBus; tutorial. + + + + + + + + + + +Credits and License + +Program copyright + +1997-2000 &Richard.J.Moore; &Richard.J.Moore.mail; +2000 &Matthias.Ettrich; &Matthias.Ettrich.mail; + + +Documentation based on the original, copyright 1997-2000 &Richard.J.Moore; +&Richard.J.Moore.mail; + + +&underFDL; +&underGPL; + + + +&documentation.index; +
+ + + + + + diff --git a/ksnapshot/doc/preview.png b/ksnapshot/doc/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..fb02e4f14a281fbd8ecfc0fafec03caaf73379f6 GIT binary patch literal 59204 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

g8VU;w;{e51XX0Esv@$27iQT?FZe=c_0Aw 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 m38@khNznnT7XOE2Ca(M5=(~Kk`>?3tkVb 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 BFNFXA literal 0 HcmV?d00001 diff --git a/ksnapshot/doc/send-to-menu.png b/ksnapshot/doc/send-to-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..cd447c39c39f4acb7457fe57aa8e6ae3a741822f GIT binary patch literal 41025 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 z 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 literal 0 HcmV?d00001 diff --git a/ksnapshot/doc/window.png b/ksnapshot/doc/window.png new file mode 100644 index 0000000000000000000000000000000000000000..926b1caae04f8679450ae283287dda93b38909dd GIT binary patch literal 94932 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

G%N`>3Z_D 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

jkBwK( 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

(TU(Y|-FDqd&ex`F_xo@z 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 BjB)?~ literal 0 HcmV?d00001 diff --git a/ksnapshot/expblur.cpp b/ksnapshot/expblur.cpp new file mode 100644 index 00000000..d304785c --- /dev/null +++ b/ksnapshot/expblur.cpp @@ -0,0 +1,156 @@ +#ifndef BLUR_CPP +#define BLUR_CPP + +/* + * Copyright 2007 Jani Huhtanen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 as + * published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include + +// Exponential blur, Jani Huhtanen, 2006 +// +template +static inline void blurinner(unsigned char *bptr, int &zR, int &zG, int &zB, int &zA, int alpha); + +template +static inline void blurrow(QImage &im, int line, int alpha); + +template +static inline void blurcol(QImage &im, int col, int alpha); + +/* +* expblur(QImage &img, int radius) +* +* In-place blur of image 'img' with kernel +* of approximate radius 'radius'. +* +* Blurs with two sided exponential impulse +* response. +* +* aprec = precision of alpha parameter +* in fixed-point format 0.aprec +* +* zprec = precision of state parameters +* zR,zG,zB and zA in fp format 8.zprec +*/ +template +void expblur(QImage &img, int radius) +{ + if (radius < 1) { + return; + } + + img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); + + /* Calculate the alpha such that 90% of + the kernel is within the radius. + (Kernel extends to infinity) + */ + int alpha = (int)((1 << aprec) * (1.0f - std::exp(-2.3f / (radius + 1.f)))); + + int height = img.height(); + int width = img.width(); + for (int row=0; row(img, row, alpha); + } + + for (int col=0; col(img, col, alpha); + } + return; +} + +template +static inline void blurinner(unsigned char *bptr, int &zR, int &zG, int &zB, int &zA, int alpha) +{ + int R, G, B, A; + R = *bptr; + G = *(bptr + 1); + B = *(bptr + 2); + A = *(bptr + 3); + + zR += (alpha * ((R << zprec) - zR)) >> aprec; + zG += (alpha * ((G << zprec) - zG)) >> aprec; + zB += (alpha * ((B << zprec) - zB)) >> aprec; + zA += (alpha * ((A << zprec) - zA)) >> aprec; + + *bptr = zR >> zprec; + *(bptr+1) = zG >> zprec; + *(bptr+2) = zB >> zprec; + *(bptr+3) = zA >> zprec; +} + +template +static inline void blurrow(QImage &im, int line, int alpha) +{ + int zR, zG, zB, zA; + + QRgb *ptr = (QRgb *)im.scanLine(line); + int width = im.width(); + + zR = *((unsigned char *)ptr ) << zprec; + zG = *((unsigned char *)ptr + 1) << zprec; + zB = *((unsigned char *)ptr + 2) << zprec; + zA = *((unsigned char *)ptr + 3) << zprec; + + for (int index=1; index((unsigned char *)&ptr[index],zR,zG,zB,zA,alpha); + } + for (int index=width-2; index>=0; index--) { + blurinner((unsigned char *)&ptr[index],zR,zG,zB,zA,alpha); + } +} + +template +static inline void blurcol(QImage &im, int col, int alpha) +{ + int zR, zG, zB, zA; + + QRgb *ptr = (QRgb *)im.bits(); + ptr += col; + int height = im.height(); + int width = im.width(); + + zR = *((unsigned char *)ptr ) << zprec; + zG = *((unsigned char *)ptr + 1) << zprec; + zB = *((unsigned char *)ptr + 2) << zprec; + zA = *((unsigned char *)ptr + 3) << zprec; + + for (int index=width; index<(height-1)*width; index+=width) { + blurinner((unsigned char *)&ptr[index], zR, zG, zB, zA, alpha); + } + + for (int index=(height-2)*width; index>=0; index-=width) { + blurinner((unsigned char *)&ptr[index], zR, zG, zB, zA, alpha); + } +} + +template +inline const T &qClamp(const T &x, const T &low, const T &high) +{ + if (x < low) { + return low; + } else if (x > high) { + return high; + } else { + return x; + } +} + +#endif diff --git a/ksnapshot/freeregiongrabber.cpp b/ksnapshot/freeregiongrabber.cpp new file mode 100644 index 00000000..f7f1924f --- /dev/null +++ b/ksnapshot/freeregiongrabber.cpp @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2010 Pau Garcia i Quiles , + * based on the region grabber code by Luca Gugelmann + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2 as + * published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "freeregiongrabber.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +FreeRegionGrabber::FreeRegionGrabber( const QPolygon &startFreeRegion ) : + QWidget( 0, Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool ), + selection( startFreeRegion ), mouseDown( false ), newSelection( false ), + handleSize( 10 ), mouseOverHandle( 0 ), + showHelp( true ), grabbing( false ) +{ + setMouseTracking( true ); + int timeout = KWindowSystem::compositingActive() ? 200 : 50; + QTimer::singleShot( timeout, this, SLOT(init()) ); +} + +FreeRegionGrabber::~FreeRegionGrabber() +{ +} + +void FreeRegionGrabber::init() +{ + pixmap = QPixmap::grabWindow( QApplication::desktop()->winId() ); + resize( pixmap.size() ); + move( 0, 0 ); + setCursor( Qt::CrossCursor ); + show(); + grabMouse(); + grabKeyboard(); +} + +static void drawPolygon( QPainter *painter, const QPolygon &p, const QColor &outline, const QColor &fill = QColor() ) +{ + QRegion clip( p ); + clip = clip - p; + QPen pen(outline, 1, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin); + + painter->save(); + painter->setClipRegion( clip ); + painter->setPen(pen); + painter->drawPolygon( p ); + if ( fill.isValid() ) { + painter->setClipping( false ); + painter->setBrush( fill ); + painter->drawPolygon( p /*.translated( 1, -1 ) */); + } + painter->restore(); +} + +void FreeRegionGrabber::paintEvent( QPaintEvent* e ) +{ + Q_UNUSED( e ); + if ( grabbing ) // grabWindow() should just get the background + return; + + QPainter painter( this ); + + QPalette pal(QToolTip::palette()); + QFont font = QToolTip::font(); + + QColor handleColor = pal.color( QPalette::Active, QPalette::Highlight ); + handleColor.setAlpha( 160 ); + QColor overlayColor( 0, 0, 0, 160 ); + QColor textColor = pal.color( QPalette::Active, QPalette::Text ); + QColor textBackgroundColor = pal.color( QPalette::Active, QPalette::Base ); + painter.drawPixmap(0, 0, pixmap); + painter.setFont(font); + + QPolygon pol = selection; + if ( !selection.boundingRect().isNull() ) + { + // Draw outline around selection. + // Important: the 1px-wide outline is *also* part of the captured free-region because + // I found no way to draw the outline *around* the selection because I found no way + // to create a QPolygon which is smaller than the selection by 1px (QPolygon::translated + // is NOT equivalent to QRect::adjusted) + QPen pen(handleColor, 1, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin); + painter.setPen(pen); + painter.drawPolygon( pol ); + + // Draw the grey area around the selection + QRegion grey( rect() ); + grey = grey - pol; + painter.setClipRegion( grey ); + painter.setPen( Qt::NoPen ); + painter.setBrush( overlayColor ); + painter.drawRect( rect() ); + painter.setClipRect( rect() ); + drawPolygon( &painter, pol, handleColor); + } + + if ( showHelp ) + { + painter.setPen( textColor ); + painter.setBrush( textBackgroundColor ); + QString helpText = i18n( "Select a region using the mouse. To take the snapshot, press the Enter key or double click. Press Esc to quit." ); + helpTextRect = painter.boundingRect( rect().adjusted( 2, 2, -2, -2 ), Qt::TextWordWrap, helpText ); + helpTextRect.adjust( -2, -2, 4, 2 ); + drawPolygon( &painter, helpTextRect, textColor, textBackgroundColor ); + painter.drawText( helpTextRect.adjusted( 3, 3, -3, -3 ), helpText ); + } + + if ( selection.isEmpty() ) + { + return; + } + + // The grabbed region is everything which is covered by the drawn + // rectangles (border included). This means that there is no 0px + // selection, since a 0px wide rectangle will always be drawn as a line. + QString txt = QString( "%1x%2" ).arg( selection.boundingRect().width() ) + .arg( selection.boundingRect().height() ); + QRect textRect = painter.boundingRect( rect(), Qt::AlignLeft, txt ); + QRect boundingRect = textRect.adjusted( -4, 0, 0, 0); + + if ( textRect.width() < pol.boundingRect().width() - 2*handleSize && + textRect.height() < pol.boundingRect().height() - 2*handleSize && + ( pol.boundingRect().width() > 100 && pol.boundingRect().height() > 100 ) ) // center, unsuitable for small selections + { + boundingRect.moveCenter( pol.boundingRect().center() ); + textRect.moveCenter( pol.boundingRect().center() ); + } + else if ( pol.boundingRect().y() - 3 > textRect.height() && + pol.boundingRect().x() + textRect.width() < rect().right() ) // on top, left aligned + { + boundingRect.moveBottomLeft( QPoint( pol.boundingRect().x(), pol.boundingRect().y() - 3 ) ); + textRect.moveBottomLeft( QPoint( pol.boundingRect().x() + 2, pol.boundingRect().y() - 3 ) ); + } + else if ( pol.boundingRect().x() - 3 > textRect.width() ) // left, top aligned + { + boundingRect.moveTopRight( QPoint( pol.boundingRect().x() - 3, pol.boundingRect().y() ) ); + textRect.moveTopRight( QPoint( pol.boundingRect().x() - 5, pol.boundingRect().y() ) ); + } + else if ( pol.boundingRect().bottom() + 3 + textRect.height() < rect().bottom() && + pol.boundingRect().right() > textRect.width() ) // at bottom, right aligned + { + boundingRect.moveTopRight( QPoint( pol.boundingRect().right(), pol.boundingRect().bottom() + 3 ) ); + textRect.moveTopRight( QPoint( pol.boundingRect().right() - 2, pol.boundingRect().bottom() + 3 ) ); + } + else if ( pol.boundingRect().right() + textRect.width() + 3 < rect().width() ) // right, bottom aligned + { + boundingRect.moveBottomLeft( QPoint( pol.boundingRect().right() + 3, pol.boundingRect().bottom() ) ); + textRect.moveBottomLeft( QPoint( pol.boundingRect().right() + 5, pol.boundingRect().bottom() ) ); + } + // if the above didn't catch it, you are running on a very tiny screen... + drawPolygon( &painter, boundingRect, textColor, textBackgroundColor ); + + painter.drawText( textRect, txt ); + + if ( ( pol.boundingRect().height() > handleSize*2 && pol.boundingRect().width() > handleSize*2 ) + || !mouseDown ) + { + painter.setBrush(QBrush(Qt::transparent)); + painter.setClipRegion( QRegion(pol)); + painter.drawPolygon( rect() ); + } +} + +void FreeRegionGrabber::mousePressEvent( QMouseEvent* e ) +{ + pBefore = e->pos(); + showHelp = !helpTextRect.contains( e->pos() ); + if ( e->button() == Qt::LeftButton ) + { + mouseDown = true; + dragStartPoint = e->pos(); + selectionBeforeDrag = selection; + if ( !selection.containsPoint( e->pos(), Qt::WindingFill ) ) + { + newSelection = true; + selection = QPolygon(); + } + else + { + setCursor( Qt::ClosedHandCursor ); + } + } + else if ( e->button() == Qt::RightButton ) + { + newSelection = false; + selection = QPolygon(); + setCursor( Qt::CrossCursor ); + } + update(); +} + +void FreeRegionGrabber::mouseMoveEvent( QMouseEvent* e ) +{ + bool shouldShowHelp = !helpTextRect.contains( e->pos() ); + if (shouldShowHelp != showHelp) { + showHelp = shouldShowHelp; + update(); + } + + if ( mouseDown ) + { + if ( newSelection ) + { + QPoint p = e->pos(); + selection << p; + } + else // moving the whole selection + { + QPoint p = e->pos() - pBefore; // Offset + pBefore = e->pos(); // Save position for next iteration + selection.translate(p); + } + + update(); + } + else + { + if ( selection.boundingRect().isEmpty() ) + return; + + if ( selection.containsPoint( e->pos(), Qt::WindingFill ) ) + setCursor( Qt::OpenHandCursor ); + else + setCursor( Qt::CrossCursor ); + + } +} + +void FreeRegionGrabber::mouseReleaseEvent( QMouseEvent* e ) +{ + mouseDown = false; + newSelection = false; + if ( mouseOverHandle == 0 && selection.containsPoint( e->pos(), Qt::WindingFill ) ) + setCursor( Qt::OpenHandCursor ); + update(); +} + +void FreeRegionGrabber::mouseDoubleClickEvent( QMouseEvent* ) +{ + grabRect(); +} + +void FreeRegionGrabber::keyPressEvent( QKeyEvent* e ) +{ + QPolygon pol = selection; + if ( e->key() == Qt::Key_Escape ) + { + emit freeRegionUpdated( pol ); + emit freeRegionGrabbed( QPixmap() ); + } + else if ( e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return ) + { + grabRect(); + } + else + { + e->ignore(); + } +} + +void FreeRegionGrabber::grabRect() +{ + QPolygon pol = selection; + if ( !pol.isEmpty() ) + { + grabbing = true; + + int xOffset = pixmap.rect().x() - pol.boundingRect().x(); + int yOffset = pixmap.rect().y() - pol.boundingRect().y(); + QPolygon translatedPol = pol.translated(xOffset, yOffset); + + QPixmap pixmap2(pol.boundingRect().size()); + pixmap2.fill(Qt::transparent); + + QPainter pt; + pt.begin(&pixmap2); + if (pt.paintEngine()->hasFeature(QPaintEngine::PorterDuff)) { + pt.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing | QPainter::SmoothPixmapTransform, true); + pt.setBrush(Qt::black); + pt.setPen(QPen(QBrush(Qt::black), 0.5)); + pt.drawPolygon(translatedPol); + pt.setCompositionMode(QPainter::CompositionMode_SourceIn); + } else { + pt.setClipRegion(QRegion(translatedPol)); + pt.setCompositionMode(QPainter::CompositionMode_Source); + } + + pt.drawPixmap(pixmap2.rect(), pixmap, pol.boundingRect()); + pt.end(); + + emit freeRegionUpdated(pol); + emit freeRegionGrabbed(pixmap2); + } +} + +#include "freeregiongrabber.moc" diff --git a/ksnapshot/freeregiongrabber.h b/ksnapshot/freeregiongrabber.h new file mode 100644 index 00000000..0a327c3a --- /dev/null +++ b/ksnapshot/freeregiongrabber.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 Pau Garcia i Quiles , + * based on the region grabber code by Luca Gugelmann + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2 as + * published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FREEREGIONGRABBER_H +#define FREEREGIONGRABBER_H + +#include +#include +#include +#include + +class QPaintEvent; +class QMouseEvent; + +class FreeRegionGrabber : public QWidget +{ + Q_OBJECT + +public: + FreeRegionGrabber( const QPolygon &startFreeRegion ); + ~FreeRegionGrabber(); + +protected slots: + void init(); + +signals: + void freeRegionGrabbed( const QPixmap & ); + void freeRegionUpdated( const QPolygon & ); + +protected: + void paintEvent( QPaintEvent* e ); + void mousePressEvent( QMouseEvent* e ); + void mouseMoveEvent( QMouseEvent* e ); + void mouseReleaseEvent( QMouseEvent* e ); + void mouseDoubleClickEvent( QMouseEvent* ); + void keyPressEvent( QKeyEvent* e ); + void grabRect(); + + QPolygon selection; + bool mouseDown; + bool newSelection; + const int handleSize; + QRect* mouseOverHandle; + QPoint dragStartPoint; + QPolygon selectionBeforeDrag; + bool showHelp; + bool grabbing; + + QRect helpTextRect; + + QPixmap pixmap; + QPoint pBefore; +}; + +#endif diff --git a/ksnapshot/hi16-app-ksnapshot.png b/ksnapshot/hi16-app-ksnapshot.png new file mode 100644 index 0000000000000000000000000000000000000000..45253316a7f161798186cf176a25b16d1d8d2866 GIT binary patch literal 846 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 + * Copyright (C) 2010 Pau Garcia i Quiles + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kbackgroundsnapshot.h" +#include "kbackgroundsnapshot.moc" +#include "regiongrabber.h" +#include "ksnapshot_options.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +KBackgroundSnapshot::KBackgroundSnapshot(KSnapshotObject::CaptureMode mode) + :KSnapshotObject() +{ + modeCapture=mode; + grabber = new QWidget( 0, Qt::X11BypassWindowManagerHint ); + grabber->move( -1000, -1000 ); + grabber->installEventFilter( this ); + grabber->show(); + grabber->grabMouse( Qt::WaitCursor ); + + if ( mode == KSnapshotObject::FullScreen ) + { + snapshot = QPixmap::grabWindow( QApplication::desktop()->winId() ); + savePictureOnDesktop(); + } + else { + switch(mode) + { + case KSnapshotObject::WindowUnderCursor: + { + performGrab(); + break; + } + case KSnapshotObject::ChildWindow: + { + slotGrab(); + break; + } + case KSnapshotObject::Region: + { + grabRegion(); + break; + } + default: + break; + } + } + + //When we use argument to take snapshot we mustn't hide it. + if(mode != KSnapshotObject::ChildWindow) + { + grabber->releaseMouse(); + grabber->hide(); + } + +} + +KBackgroundSnapshot::~KBackgroundSnapshot() +{ + //kDebug()<<" KBackgroundSnapshot::~KBackgroundSnapshot()\n"; +} + +void KBackgroundSnapshot::savePictureOnDesktop() +{ + filename = KUrl( KGlobalSettings::desktopPath()+'/'+i18n("snapshot")+"1.png" ); + // Make sure the name is not already being used + while(KIO::NetAccess::exists( filename, KIO::NetAccess::DestinationSide, 0L )) { + autoincFilename(); + } + save( filename, 0L); + exit( 0 ); +} + +void KBackgroundSnapshot::performGrab() +{ + //kDebug()<<"KBackgroundSnapshot::performGrab()\n"; + grabber->releaseMouse(); + grabber->hide(); + if ( modeCapture == ChildWindow ) { + WindowGrabber wndGrab; + connect( &wndGrab, SIGNAL(windowGrabbed(QPixmap)), + SLOT(slotWindowGrabbed(QPixmap)) ); + wndGrab.exec(); + savePictureOnDesktop(); + } + else if ( modeCapture == WindowUnderCursor ) { + snapshot = WindowGrabber::grabCurrent( true ); + savePictureOnDesktop(); + } + else { + snapshot = QPixmap::grabWindow( QApplication::desktop()->winId() ); + savePictureOnDesktop(); + } +} + +void KBackgroundSnapshot::slotWindowGrabbed( const QPixmap &pix ) +{ + //kDebug()<<" KBackgroundSnapshot::slotWindowGrabbed( const QPixmap &pix )\n"; + if ( !pix.isNull() ) + snapshot = pix; + QApplication::restoreOverrideCursor(); +} + + +void KBackgroundSnapshot::slotGrab() +{ + //kDebug()<<"KBackgroundSnapshot::slotGrab()\n"; + grabber->show(); + grabber->grabMouse( Qt::CrossCursor ); +} + + +void KBackgroundSnapshot::grabRegion() +{ + QRect emptySelection; + rgnGrab = new RegionGrabber(emptySelection); + connect( rgnGrab, SIGNAL(regionGrabbed(QPixmap)), + SLOT(slotRegionGrabbed(QPixmap)) ); + +} + + +void KBackgroundSnapshot::slotRegionGrabbed( const QPixmap &pix ) +{ + if ( !pix.isNull() ) + snapshot = pix; + rgnGrab->deleteLater(); + QApplication::restoreOverrideCursor(); + savePictureOnDesktop(); +} + +bool KBackgroundSnapshot::eventFilter( QObject* o, QEvent* e) +{ + if ( o == grabber && e->type() == QEvent::MouseButtonPress ) { + QMouseEvent* me = (QMouseEvent*) e; + if ( QWidget::mouseGrabber() != grabber ) + return false; + if ( me->button() == Qt::LeftButton ) + performGrab(); + } + return false; +} + + +#define KBACKGROUNDSNAPVERSION "0.1" + +static const char description[] = I18N_NOOP("KDE Background Screenshot Utility"); + +int main(int argc, char **argv) +{ + KAboutData aboutData( "kbackgroundsnapshot", "ksnapshot", ki18n("KBackgroundSnapshot"), + KBACKGROUNDSNAPVERSION, ki18n(description), KAboutData::License_GPL, + ki18n("(c) 2007, Montel Laurent")); + + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::addCmdLineOptions( ksnapshot_options() ); // Add our own options. + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + KApplication app; + + if ( args->isSet( "current" ) ) + new KBackgroundSnapshot( KSnapshotObject::WindowUnderCursor ); + else if(args->isSet( "fullscreen" )) + new KBackgroundSnapshot( KSnapshotObject::FullScreen ); + else if(args->isSet( "region" )) + new KBackgroundSnapshot( KSnapshotObject::Region ); + else if(args->isSet( "freeregion" )) + new KBackgroundSnapshot( KSnapshotObject::FreeRegion ); + else if(args->isSet( "child" )) + new KBackgroundSnapshot( KSnapshotObject::ChildWindow ); + else + new KBackgroundSnapshot(); + args->clear(); + return app.exec(); +} diff --git a/ksnapshot/kbackgroundsnapshot.h b/ksnapshot/kbackgroundsnapshot.h new file mode 100644 index 00000000..f58f3171 --- /dev/null +++ b/ksnapshot/kbackgroundsnapshot.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2007 Montel Laurent + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KBACKGROUNDSNAPSHOT_H +#define KBACKGROUNDSNAPSHOT_H + +#include "ksnapshotobject.h" +#include + +class QPixmap; + +class KBackgroundSnapshot: public QObject, public KSnapshotObject +{ + Q_OBJECT +public: + KBackgroundSnapshot(KSnapshotObject::CaptureMode mode = FullScreen); + virtual ~KBackgroundSnapshot(); + +private slots: + void slotWindowGrabbed( const QPixmap &pix ); + void slotGrab(); + void slotRegionGrabbed( const QPixmap &pix ); + +protected: + void performGrab(); + void grabRegion(); + bool eventFilter( QObject*, QEvent* ); + void savePictureOnDesktop(); + +private: + KSnapshotObject::CaptureMode modeCapture; + +}; + +#endif diff --git a/ksnapshot/kipiimagecollectionselector.cpp b/ksnapshot/kipiimagecollectionselector.cpp new file mode 100644 index 00000000..5fe09d80 --- /dev/null +++ b/ksnapshot/kipiimagecollectionselector.cpp @@ -0,0 +1,87 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab: +/* +Copyright 2010 Pau Garcia i Quiles +based on code for Gwenview by +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "kipiimagecollectionselector.moc" + +// Qt +#include +#include + +// KDE +#include + +// Local + +struct KIPIImageCollectionSelectorPrivate { + KIPIInterface* mInterface; + QListWidget* mListWidget; +}; + + +KIPIImageCollectionSelector::KIPIImageCollectionSelector(KIPIInterface* interface, QWidget* parent) +: KIPI::ImageCollectionSelector(parent) +, d(new KIPIImageCollectionSelectorPrivate) { + d->mInterface = interface; + + d->mListWidget = new QListWidget; + QList list = interface->allAlbums(); + Q_FOREACH(const KIPI::ImageCollection& collection, list) { + QListWidgetItem* item = new QListWidgetItem(d->mListWidget); + QString name = collection.name(); + int imageCount = collection.images().size(); + QString title = i18ncp("%1 is collection name, %2 is image count in collection", + "%1 (%2 image)", "%1 (%2 images)", name, imageCount); + + item->setText(title); + item->setData(Qt::UserRole, name); + } + + connect(d->mListWidget, SIGNAL(currentRowChanged(int)), + SIGNAL(selectionChanged()) ); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->addWidget(d->mListWidget); + layout->setMargin(0); +} + + +KIPIImageCollectionSelector::~KIPIImageCollectionSelector() { + delete d; +} + + + +QList KIPIImageCollectionSelector::selectedImageCollections() const { + QListWidgetItem* item = d->mListWidget->currentItem(); + QList selectedList; + if (item) { + QString name = item->data(Qt::UserRole).toString(); + QList list = d->mInterface->allAlbums(); + Q_FOREACH(const KIPI::ImageCollection& collection, list) { + if (collection.name() == name) { + selectedList << collection; + break; + } + } + } + return selectedList; +} diff --git a/ksnapshot/kipiimagecollectionselector.h b/ksnapshot/kipiimagecollectionselector.h new file mode 100644 index 00000000..10afedc2 --- /dev/null +++ b/ksnapshot/kipiimagecollectionselector.h @@ -0,0 +1,45 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab: +/* +Copyright 2010 Pau Garcia i Quiles +based on code for Gwenview by +Copyright 2008 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef KIPIIMAGECOLLECTIONSELECTOR_H +#define KIPIIMAGECOLLECTIONSELECTOR_H + +// KIPI +#include +#include + +// Local +#include "kipiinterface.h" + +struct KIPIImageCollectionSelectorPrivate; +class KIPIImageCollectionSelector : public KIPI::ImageCollectionSelector { + Q_OBJECT +public: + KIPIImageCollectionSelector(KIPIInterface*, QWidget* parent); + ~KIPIImageCollectionSelector(); + + virtual QList selectedImageCollections() const; + +private: + KIPIImageCollectionSelectorPrivate* const d; +}; + +#endif /* KIPIIMAGECOLLECTIONSELECTOR_H */ diff --git a/ksnapshot/kipiinterface.cpp b/ksnapshot/kipiinterface.cpp new file mode 100644 index 00000000..ea6541f2 --- /dev/null +++ b/ksnapshot/kipiinterface.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2010 Pau Garcia i Quiles + * Essentially a rip-off of code for Kamoso by: + * Copyright (C) 2008-2009 by Aleix Pol + * Copyright (C) 2008-2009 by Alex Fiestas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kipiinterface.h" +#include "ksnapshotinfoshared.h" +#include "ksnapshotimagecollectionshared.h" +#include "ksnapshot.h" +#include "kipiimagecollectionselector.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct KIPIInterfacePrivate { + KSnapshot *ksnapshot; + KIPI::PluginLoader* pluginLoader; +}; + +KIPIInterface::KIPIInterface(KSnapshot* ksnapshot) +:KIPI::Interface(ksnapshot) +, d(new KIPIInterfacePrivate) { + d->ksnapshot = ksnapshot; +} + +KIPIInterface::~KIPIInterface() { + delete d; +} + +KIPI::ImageCollection KIPIInterface::currentAlbum() { + return KIPI::ImageCollection(new KSnapshotImageCollectionShared(d->ksnapshot)); +} + +KIPI::ImageCollection KIPIInterface::currentSelection() { + return KIPI::ImageCollection(new KSnapshotImageCollectionShared(d->ksnapshot)); +} + +QList KIPIInterface::allAlbums() { + QList list; + list << currentSelection(); + return list; +} + +KIPI::ImageInfo KIPIInterface::info(const KUrl& url) { + return KIPI::ImageInfo(new KSnapshotInfoShared(this,url)); +} + +bool KIPIInterface::addImage(const KUrl&, QString&) +{ + return true; +} +void KIPIInterface::delImage( const KUrl& ) +{ + +} +void KIPIInterface::refreshImages( const KUrl::List& ) +{ +// TODO Implement? +} + +KIPI::ImageCollectionSelector* KIPIInterface::imageCollectionSelector(QWidget *parent) { + return new KIPIImageCollectionSelector(this, parent); +} + +KIPI::UploadWidget* KIPIInterface::uploadWidget(QWidget *parent) { + return (new KIPI::UploadWidget(parent)); +} + +int KIPIInterface::features() const { + return KIPI::HostAcceptNewImages; +} diff --git a/ksnapshot/kipiinterface.h b/ksnapshot/kipiinterface.h new file mode 100644 index 00000000..1c18039c --- /dev/null +++ b/ksnapshot/kipiinterface.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010 Pau Garcia i Quiles + * Essentially a rip-off of code for Kamoso by: + * Copyright (C) 2008-2009 by Aleix Pol + * Copyright (C) 2008-2009 by Alex Fiestas + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KIPIINTERFACE_H +#define KIPIINTERFACE_H + +#include +#include +#include +#include + +class KSnapshot; +struct KIPIInterfacePrivate; + +class KIPIInterface :public KIPI::Interface { + Q_OBJECT + +public: + KIPIInterface(KSnapshot* ksnapshot); + virtual ~KIPIInterface(); + + virtual bool addImage(const KUrl&, QString& err); + virtual void delImage( const KUrl& ); + virtual void refreshImages( const KUrl::List& urls ); + + virtual KIPI::ImageCollectionSelector* imageCollectionSelector(QWidget *parent); + virtual KIPI::UploadWidget* uploadWidget(QWidget *parent); + + virtual QList allAlbums(); + virtual KIPI::ImageCollection currentAlbum(); + virtual KIPI::ImageCollection currentSelection(); + virtual int features() const; + virtual KIPI::ImageInfo info(const KUrl& ); + +private: + KIPIInterfacePrivate* const d; +}; + +#endif // KIPIINTERFACE_H diff --git a/ksnapshot/ksnapshot.cpp b/ksnapshot/ksnapshot.cpp new file mode 100644 index 00000000..0d8e9966 --- /dev/null +++ b/ksnapshot/ksnapshot.cpp @@ -0,0 +1,939 @@ +/* + * Copyright (C) 1997-2008 Richard J. Moore + * Copyright (C) 2000 Matthias Ettrich + * Copyright (C) 2002 Aaron J. Seigo + * Copyright (C) 2003 Nadeem Hasan + * Copyright (C) 2004 Bernd Brandstetter + * Copyright (C) 2006 Urs Wolfer + * Copyright (C) 2010 Martin Gräßlin + * Copyright (C) 2010, 2011 Pau Garcia i Quiles + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "ksnapshot.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "regiongrabber.h" +#include "freeregiongrabber.h" +#include "windowgrabber.h" +#include "ksnapshotpreview.h" +#include "ui_ksnapshotwidget.h" + +#ifdef KIPI_FOUND +#include +#include +#include "kipiinterface.h" +#include +#endif + +#ifdef HAVE_X11_EXTENSIONS_XFIXES_H +#include +#include +#include +#endif + +class KSnapshotWidget : public QWidget, public Ui::KSnapshotWidget +{ + public: + KSnapshotWidget(QWidget *parent = 0) + : QWidget(parent) + { + setupUi(this); + btnNew->setIcon(KIcon("ksnapshot")); + } +}; + +KSnapshot::KSnapshot(QWidget *parent, KSnapshotObject::CaptureMode mode ) + : KDialog(parent), KSnapshotObject(), modified(true), savedPosition(QPoint(-1, -1)), haveXFixes(false) +{ + // TEMPORARY Make sure "untitled" enters the string freeze for 4.6, + // as explained in http://lists.kde.org/?l=kde-graphics-devel&m=128942871430175&w=2 + const QString untitled = QString(i18n("untitled")); + + setCaption( "" ); + showButtonSeparator( true ); + setButtons(Help | Apply | User1 | User2); + setButtonGuiItem(Apply, KStandardGuiItem::saveAs()); + setButtonGuiItem(User1, KGuiItem(i18n("Copy"), "edit-copy")); + setButtonGuiItem(User2, KGuiItem(i18n("Send To..."), "document-open")); + setDefaultButton(Apply); + grabber = new QWidget( 0, Qt::X11BypassWindowManagerHint ); + + // TODO X11 (Xinerama and Twinview, actually) and Windows use different coordinates for the two monitors case + // + // On Windows, there are two displays. The origin (0, 0) ('o') is the top left of display 1. If display 2 is to the left, then coordinates in display 2 are negative: + // .-------. + // | |o-----. + // | 2 | | + // | | 1 | + // ._______.._____. + // + // On Xinerama and Twinview, there is only one display and two screens. The origin (0, 0) ('o') is the top left of the display: + // o-------. + // | |.-----. + // | 2 | | + // | | 1 | + // ._______.._____. + // + // Instead of moving to (-10000, -10000), we should compute how many displays are and make sure we move to somewhere out of the total coordinates. + // - Windows: use GetSystemMetrics ( http://msdn.microsoft.com/en-us/library/ms724385(v=vs.85).aspx ) + + // If moving to a negative position, we need to count the size of the dialog; moving to a positive position avoids having to compute the size of the dialog + + grabber->move( -10000, -10000 ); // FIXME Read above + + grabber->installEventFilter( this ); + + KStartupInfo::appStarted(); + + KVBox *vbox = new KVBox( this ); + vbox->setSpacing( spacingHint() ); + setMainWidget( vbox ); + + mainWidget = new KSnapshotWidget( vbox ); + + connect(mainWidget->lblImage, SIGNAL(startDrag()), SLOT(slotDragSnapshot())); + connect(mainWidget->btnNew, SIGNAL(clicked()), SLOT(slotGrab())); + connect(this, SIGNAL(applyClicked()), SLOT(slotSaveAs())); + connect(this, SIGNAL(user1Clicked()), SLOT(slotCopy())); + connect(mainWidget->comboMode, SIGNAL(activated(int)), SLOT(slotModeChanged(int))); + + if (qApp->desktop()->numScreens() < 2) { + mainWidget->comboMode->removeItem(CurrentScreen); + } + + openMenu = new QMenu(this); + button(User2)->setMenu(openMenu); + connect(openMenu, SIGNAL(aboutToShow()), this, SLOT(slotPopulateOpenMenu())); + connect(openMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotOpen(QAction*))); + + mainWidget->spinDelay->setSuffix(ki18np(" second", " seconds")); + + grabber->show(); + grabber->grabMouse(); + + KConfigGroup conf(KGlobal::config(), "GENERAL"); + +#ifdef KIPI_FOUND +#if(KIPI_VERSION >= 0x020000) + mPluginLoader = new KIPI::PluginLoader(); + mPluginLoader->setInterface(new KIPIInterface(this)); + mPluginLoader->init(); +#else + mPluginLoader = new KIPI::PluginLoader(QStringList(), new KIPIInterface(this), ""); +#endif +#endif + +#ifdef HAVE_X11_EXTENSIONS_XFIXES_H + { + int tmp1, tmp2; + //Check whether the XFixes extension is available + Display *dpy = QX11Info::display(); + if (!XFixesQueryExtension( dpy, &tmp1, &tmp2 )) { + mainWidget->cbIncludePointer->hide(); + mainWidget->lblIncludePointer->hide(); + } else { + haveXFixes = true; + } + + // actually not depending on XFixes, but to simplify the ifdefs put here + // we can safely assume that XFixes is present for this functionality + // it's supposed to prevent that KWin animates the window in the compositor + // and XFixes is a requirement for the compositor. So if XFixes is not present + // KWin cannot be compiled at all. + Atom atom = XInternAtom(dpy, "_KDE_NET_WM_SKIP_CLOSE_ANIMATION", False); + long d = 1; + XChangeProperty(dpy, winId(), atom, XA_CARDINAL, 32, + PropModeReplace, (unsigned char *) &d, 1); + } +#elif !defined(Q_WS_WIN) + mainWidget->cbIncludePointer->hide(); + mainWidget->lblIncludePointer->hide(); +#endif + setIncludePointer(conf.readEntry("includePointer", false)); + setMode( conf.readEntry("mode", 0) ); + + // check if kwin screenshot effect is available + includeAlpha = false; + if ( QDBusConnection::sessionBus().interface()->isServiceRegistered( "org.kde.kwin" ) ) { + QDBusInterface kwinInterface( "org.kde.kwin", "/", "org.freedesktop.DBus.Introspectable" ); + QDBusReply reply = kwinInterface.call( "Introspect" ); + if ( reply.isValid() ) { + QXmlStreamReader xml( reply.value() ); + while ( !xml.atEnd() ) { + xml.readNext(); + if ( xml.tokenType() == QXmlStreamReader::StartElement && + xml.name().toString() == "node" ) { + if ( xml.attributes().value( "name" ).toString() == "Screenshot" ) { + includeAlpha = true; + break; + } + } + } + } + } + + kDebug() << "Mode = " << mode; + if ( mode == KSnapshotObject::FullScreen ) { + snapshot = QPixmap::grabWindow( QApplication::desktop()->winId() ); +#ifdef HAVE_X11_EXTENSIONS_XFIXES_H + if ( haveXFixes && includePointer() ) + grabPointerImage(0, 0); +#endif + } + else if ( mode == KSnapshotObject::CurrentScreen ) { + kDebug() << "Desktop Geom = " << QApplication::desktop()->geometry(); + QDesktopWidget *desktop = QApplication::desktop(); + int screenId = desktop->screenNumber( QCursor::pos() ); + kDebug() << "Screenid = " << screenId; + QRect geom = desktop->screenGeometry( screenId ); + kDebug() << "Geometry = " << screenId; + snapshot = QPixmap::grabWindow( desktop->winId(), + geom.x(), geom.y(), geom.width(), geom.height() ); + } + else { + setMode( mode ); + switch(mode) + { + case KSnapshotObject::WindowUnderCursor: + { + setIncludeDecorations( true ); + performGrab(); + break; + } + case KSnapshotObject::ChildWindow: + { + slotGrab(); + break; + } + case KSnapshotObject::Region: + { + grabRegion(); + break; + } + case KSnapshotObject::FreeRegion: + { + grabFreeRegion(); + break; + } + default: + break; + } + } + + //When we use argument to take snapshot we mustn't hide it. + if (mode != KSnapshotObject::ChildWindow) { + grabber->releaseMouse(); + grabber->hide(); + } + + setDelay( conf.readEntry("delay", 0) ); + setIncludeDecorations(conf.readEntry("includeDecorations",true)); + filename = KUrl( conf.readPathEntry( "filename", QDir::currentPath()+'/'+i18n("snapshot")+"1.png" )); + + connect( &grabTimer, SIGNAL(timeout()), this, SLOT(grabTimerDone()) ); + connect( &updateTimer, SIGNAL(timeout()), this, SLOT(updatePreview()) ); + QTimer::singleShot( 0, this, SLOT(updateCaption()) ); + + KHelpMenu *helpMenu = new KHelpMenu(this, KGlobal::mainComponent().aboutData(), true); + setButtonMenu( Help, helpMenu->menu() ); +#if 0 + accel->insert( "QuickSave", i18n("Quick Save Snapshot &As..."), + i18n("Save the snapshot to the file specified by the user without showing the file dialog."), + Qt::CTRL+Qt::SHIFT+Qt::Key_S, this, SLOT(slotSave())); + accel->insert( "SaveAs", i18n("Save Snapshot &As..."), + i18n("Save the snapshot to the file specified by the user."), + Qt::CTRL+Qt::Key_A, this, SLOT(slotSaveAs())); +#endif + + new QShortcut( KStandardShortcut::shortcut( KStandardShortcut::Quit ).primary(), this, SLOT(reject())); + + new QShortcut( Qt::Key_Q, this, SLOT(slotSave())); + + new QShortcut( KStandardShortcut::shortcut( KStandardShortcut::Copy ).primary(), button(User1), SLOT(animateClick())); + + new QShortcut( KStandardShortcut::shortcut( KStandardShortcut::Save ).primary(), button(Apply), SLOT(animateClick())); + new QShortcut( Qt::Key_S, button(Apply), SLOT(animateClick())); + + new QShortcut( KStandardShortcut::shortcut( KStandardShortcut::New ).primary(), mainWidget->btnNew, SLOT(animateClick()) ); + new QShortcut( Qt::Key_N, mainWidget->btnNew, SLOT(animateClick()) ); + new QShortcut( Qt::Key_Space, mainWidget->btnNew, SLOT(animateClick()) ); + + mainWidget->btnNew->setFocus(); + setInitialSize(QSize(250, 500)); + + KConfigGroup cg(KGlobal::config(), "MainWindow"); + restoreDialogSize(cg); +} + +KSnapshot::~KSnapshot() +{ + delete mainWidget; +} + +void KSnapshot::resizeEvent( QResizeEvent * ) +{ + updateTimer.setSingleShot( true ); + updateTimer.start( 200 ); +} + +void KSnapshot::slotSave() +{ + // Make sure the name is not already being used + while(KIO::NetAccess::exists( filename, KIO::NetAccess::DestinationSide, this )) { + autoincFilename(); + } + + if ( save(filename, this) ) { + modified = false; + autoincFilename(); + updateCaption(); + } +} + +void KSnapshot::slotSaveAs() +{ + QStringList mimetypes = KImageIO::mimeTypes( KImageIO::Writing ); + + // Make sure the name is not already being used + while(KIO::NetAccess::exists( filename, KIO::NetAccess::DestinationSide, this )) { + autoincFilename(); + } + + QPointer dlg = new KFileDialog( filename.url(), mimetypes.join(" "), this); + + dlg->setOperationMode( KFileDialog::Saving ); + dlg->setCaption( i18n("Save As") ); + dlg->setSelection( filename.url() ); + + if ( !dlg->exec() ) { + delete dlg; + return; + } + + KUrl url = dlg->selectedUrl(); + if ( !url.isValid() ){ + delete dlg; + return; + } + + if ( save(url,this) ) { + filename = url; + modified = false; + autoincFilename(); + updateCaption(); + } + + delete dlg; +} + +void KSnapshot::slotCopy() +{ + QClipboard *cb = QApplication::clipboard(); + cb->setPixmap( snapshot ); +} + +void KSnapshot::slotDragSnapshot() +{ + QDrag *drag = new QDrag(this); + + drag->setMimeData(new QMimeData); + drag->mimeData()->setImageData(snapshot); + drag->mimeData()->setData("application/x-kde-suggestedfilename", filename.fileName().toUtf8()); + drag->setPixmap(preview()); + QList urls; + urls << urlToOpen(); + drag->mimeData()->setUrls(urls); + drag->start(); +} + +void KSnapshot::slotGrab() +{ + savedPosition = pos(); + hide(); + + if (delay()) { + //kDebug() << "starting timer with time of" << delay(); + grabTimer.start(delay()); + } + else { + QTimer::singleShot(0, this, SLOT(startUndelayedGrab())); + } +} + +void KSnapshot::startUndelayedGrab() +{ + if (mode() == Region) { + grabRegion(); + } + else if ( mode() == FreeRegion ) { + grabFreeRegion(); + } + else { + grabber->show(); + grabber->grabMouse(Qt::CrossCursor); + } +} + +KUrl KSnapshot::urlToOpen(bool *isTempfile) +{ + if (isTempfile) { + *isTempfile = false; + } + + if (!modified && filename.isValid()) + { + return filename; + } + + const QString fileopen = KStandardDirs::locateLocal("tmp", filename.fileName()); + + if (saveEqual(fileopen, this)) + { + if (isTempfile) { + *isTempfile = true; + } + + return fileopen; + } + + return KUrl(); +} + +void KSnapshot::slotOpen(const QString& application) +{ + KUrl url = urlToOpen(); + if (!url.isValid()) + { + return; + } + + KUrl::List list; + list.append(url); + KRun::run(application, list, this); +} + +void KSnapshot::slotOpen(QAction* action) +{ + KSnapshotServiceAction* serviceAction = + qobject_cast(action); + + if (!serviceAction) + { + return; + } + + bool isTempfile = false; + KUrl url = urlToOpen(&isTempfile); + if (!url.isValid()) + { + return; + } + + KUrl::List list; + list.append(url); + + KService::Ptr service = serviceAction->service; + if (!service) + { + QPointer dlg = new KOpenWithDialog(list, this); + if (!dlg->exec()) + { + delete dlg; + return; + } + + service = dlg->service(); + + if (!service && !dlg->text().isEmpty()) + { + KRun::run(dlg->text(), list, this); + delete dlg; + return; + } + delete dlg; + } + + // we have an action with a service, run it! + KRun::run(*service, list, this, isTempfile); +} + +void KSnapshot::slotPopulateOpenMenu() +{ + QList currentActions = openMenu->actions(); + foreach (QAction* currentAction, currentActions) + { + openMenu->removeAction(currentAction); + currentAction->deleteLater(); + } + + const KService::List services = KMimeTypeTrader::self()->query("image/png"); + QMap apps; + + foreach (const KService::Ptr &service, services) + { + apps.insert(service->name(), service); + } + + foreach (const KService::Ptr &service, apps) + { + QString name = service->name().replace( '&', "&&" ); + openMenu->addAction(new KSnapshotServiceAction(service, + KIcon(service->icon()), + name, this)); + } + + +#ifdef KIPI_FOUND + KIPI::PluginLoader::PluginList pluginList = mPluginLoader->pluginList(); + + Q_FOREACH(KIPI::PluginLoader::Info* pluginInfo, pluginList) { + if (!pluginInfo->shouldLoad()) { + continue; + } + KIPI::Plugin* plugin = pluginInfo->plugin(); + if (!plugin) { + kWarning() << "Plugin from library" << pluginInfo->library() << "failed to load"; + continue; + } + + plugin->setup(this); + + QList actions = plugin->actions(); + QSet exportActions; + Q_FOREACH(KAction* action, actions) { + KIPI::Category category = plugin->category(action); + if(category == KIPI::ExportPlugin) { + exportActions << action; + } else if (category == KIPI::ImagesPlugin) { + // Horrible hack. Why are the print images and the e-mail images plugins in the same category as rotate and edit metadata!? + if( pluginInfo->library().contains("kipiplugin_printimages") || pluginInfo->library().contains("kipiplugin_sendimages")) { + exportActions << action; + } + } + } + + Q_FOREACH(KAction* action, exportActions) { + openMenu->addAction(action); + } + +// FIXME: Port +// plugin->actionCollection()->readShortcutSettings(); + } +#endif + + openMenu->addSeparator(); + KService::Ptr none; + openMenu->addAction(new KSnapshotServiceAction(none, + i18n("Other Application..."), + this)); +} + +void KSnapshot::slotRegionGrabbed( const QPixmap &pix ) +{ + if ( !pix.isNull() ) + { + snapshot = pix; + updatePreview(); + modified = true; + updateCaption(); + } + + if( mode() == KSnapshotObject::Region ) + { + rgnGrab->deleteLater(); + } + else if( mode() == KSnapshotObject::FreeRegion ) { + freeRgnGrab->deleteLater(); + } + + QApplication::restoreOverrideCursor(); + show(); +} + +void KSnapshot::slotRegionUpdated( const QRect &selection ) +{ + lastRegion = selection; +} + +void KSnapshot::slotFreeRegionUpdated( const QPolygon &selection ) +{ + lastFreeRegion = selection; +} + +void KSnapshot::slotWindowGrabbed( const QPixmap &pix ) +{ + if ( !pix.isNull() ) + { + snapshot = pix; + updatePreview(); + modified = true; + updateCaption(); + } + + QApplication::restoreOverrideCursor(); + show(); +} + +void KSnapshot::slotScreenshotReceived( qulonglong handle ) +{ +#ifdef Q_WS_X11 + slotWindowGrabbed( QPixmap::fromX11Pixmap( handle ) ); +#endif +} + +void KSnapshot::closeEvent( QCloseEvent * e ) +{ + KConfigGroup conf(KGlobal::config(), "GENERAL"); + conf.writeEntry("delay", delay()); + conf.writeEntry("mode", mode()); + conf.writeEntry("includeDecorations", includeDecorations()); + conf.writeEntry("includePointer", includePointer()); + + KConfigGroup cg(KGlobal::config(), "MainWindow"); + saveDialogSize(cg); + + KUrl url = filename; + url.setPass(QString::null); //krazy:exclude=nullstrassign for old broken gcc + conf.writePathEntry("filename", url.url()); + + conf.sync(); + e->accept(); +} + +bool KSnapshot::eventFilter( QObject* o, QEvent* e) +{ + if ( o == grabber && e->type() == QEvent::MouseButtonPress ) { + QMouseEvent* me = (QMouseEvent*) e; + if ( QWidget::mouseGrabber() != grabber ) + return false; + if ( me->button() == Qt::LeftButton ) + performGrab(); + } + return false; +} + +void KSnapshot::updatePreview() +{ + setPreview( snapshot ); +} + +void KSnapshot::grabRegion() +{ + rgnGrab = new RegionGrabber(lastRegion); + connect( rgnGrab, SIGNAL(regionGrabbed(QPixmap)), + SLOT(slotRegionGrabbed(QPixmap)) ); + connect( rgnGrab, SIGNAL(regionUpdated(QRect)), + SLOT(slotRegionUpdated(QRect)) ); + +} + +void KSnapshot::grabFreeRegion() +{ + freeRgnGrab = new FreeRegionGrabber(lastFreeRegion); + connect( freeRgnGrab, SIGNAL(freeRegionGrabbed(QPixmap)), + SLOT(slotRegionGrabbed(QPixmap)) ); + connect( freeRgnGrab, SIGNAL(freeRegionUpdated(QPolygon)), + SLOT(slotFreeRegionUpdated(QPolygon)) ); + +} + +void KSnapshot::grabTimerDone() +{ + if ( mode() == Region ) { + grabRegion(); + } + else if ( mode() == FreeRegion ) { + grabFreeRegion(); + } + else { + performGrab(); + } + KNotification::beep(i18n("The screen has been successfully grabbed.")); +} + +void KSnapshot::performGrab() +{ + int x = 0; + int y = 0; + + grabber->releaseMouse(); + grabber->hide(); + grabTimer.stop(); + + title.clear(); + windowClass.clear(); + + if ( mode() == ChildWindow ) { + WindowGrabber wndGrab; + connect( &wndGrab, SIGNAL(windowGrabbed(QPixmap)), + SLOT(slotWindowGrabbed(QPixmap)) ); + wndGrab.exec(); + QPoint offset = wndGrab.lastWindowPosition(); + x = offset.x(); + y = offset.y(); + qDebug() << "last window position is" << offset; + } + else if ( mode() == WindowUnderCursor ) { + if ( includeAlpha ) { + // use kwin effect + QDBusConnection::sessionBus().connect("org.kde.kwin", "/Screenshot", + "org.kde.kwin.Screenshot", "screenshotCreated", + this, SLOT(slotScreenshotReceived(qulonglong))); + QDBusInterface interface( "org.kde.kwin", "/Screenshot", "org.kde.kwin.Screenshot" ); + int mask = 0; + if ( includeDecorations() ) + { + mask |= 1 << 0; + } + if ( includePointer() ) + { + mask |= 1 << 1; + } + interface.call( "screenshotWindowUnderCursor", mask ); + } else { + snapshot = WindowGrabber::grabCurrent( includeDecorations() ); + + QPoint offset = WindowGrabber::lastWindowPosition(); + x = offset.x(); + y = offset.y(); + + // If we're showing decorations anyway then we'll add the title and window + // class to the output image meta data. + if ( includeDecorations() ) { + title = WindowGrabber::lastWindowTitle(); + windowClass = WindowGrabber::lastWindowClass(); + } + } + } + else if ( mode() == CurrentScreen ) { + kDebug() << "Desktop Geom2 = " << QApplication::desktop()->geometry(); + QDesktopWidget *desktop = QApplication::desktop(); + int screenId = desktop->screenNumber( QCursor::pos() ); + kDebug() << "Screenid2 = " << screenId; + QRect geom = desktop->screenGeometry( screenId ); + kDebug() << "Geometry2 = " << geom; + x = geom.x(); + y = geom.y(); + snapshot = QPixmap::grabWindow( desktop->winId(), + x, y, geom.width(), geom.height() ); + } + else { + snapshot = QPixmap::grabWindow( QApplication::desktop()->winId() ); + } +#ifdef HAVE_X11_EXTENSIONS_XFIXES_H + if (haveXFixes && includePointer()) { + grabPointerImage(x, y); + } +#endif // HAVE_X11_EXTENSIONS_XFIXES_H + + updatePreview(); + QApplication::restoreOverrideCursor(); + modified = true; + updateCaption(); + if (savedPosition != QPoint(-1, -1)) { + move(savedPosition); + } + show(); +} + +void KSnapshot::grabPointerImage(int offsetx, int offsety) +// Uses the X11_EXTENSIONS_XFIXES_H extension to grab the pointer image, and overlays it onto the snapshot. +{ +#ifdef HAVE_X11_EXTENSIONS_XFIXES_H + XFixesCursorImage *xcursorimg = XFixesGetCursorImage( QX11Info::display() ); + if ( !xcursorimg ) + return; + + //Annoyingly, xfixes specifies the data to be 32bit, but places it in an unsigned long * + //which can be 64 bit. So we need to iterate over a 64bit structure to put it in a 32bit + //structure. + QVarLengthArray< quint32 > pixels( xcursorimg->width * xcursorimg->height ); + for (int i = 0; i < xcursorimg->width * xcursorimg->height; ++i) + pixels[i] = xcursorimg->pixels[i] & 0xffffffff; + + QImage qcursorimg((uchar *) pixels.data(), xcursorimg->width, xcursorimg->height, + QImage::Format_ARGB32_Premultiplied); + + QPainter painter(&snapshot); + painter.drawImage(QPointF(xcursorimg->x - xcursorimg->xhot - offsetx, xcursorimg->y - xcursorimg ->yhot - offsety), qcursorimg); + + XFree(xcursorimg); +#else // HAVE_X11_EXTENSIONS_XFIXES_H + Q_UNUSED(offsetx); + Q_UNUSED(offsety); + return; +#endif // HAVE_X11_EXTENSIONS_XFIXES_H +} + +void KSnapshot::setTime(int newTime) +{ + setDelay(newTime); +} + +int KSnapshot::timeout() const +{ + return delay(); +} + +void KSnapshot::setURL( const QString &url ) +{ + changeUrl( url ); +} + +void KSnapshot::setGrabMode( int m ) +{ + setMode( m ); +} + +int KSnapshot::grabMode() const +{ + return mode(); +} + +void KSnapshot::refreshCaption() +{ + updateCaption(); +} + +void KSnapshot::updateCaption() +{ + setCaption( filename.fileName(), modified ); +} + +void KSnapshot::slotMovePointer(int x, int y) +{ + QCursor::setPos( x, y ); +} + +void KSnapshot::exit() +{ + reject(); +} + +void KSnapshot::slotModeChanged(int mode) +{ + mainWidget->cbIncludePointer->setEnabled(!(mode == Region || mode == FreeRegion)); + mainWidget->lblIncludePointer->setEnabled(!(mode == Region || mode == FreeRegion)); + mainWidget->cbIncludeDecorations->setEnabled(mode == WindowUnderCursor); + mainWidget->lblIncludeDecorations->setEnabled(mode == WindowUnderCursor); +} + +void KSnapshot::setPreview( const QPixmap &pm ) +{ + mainWidget->lblImage->setToolTip( + i18n( "Preview of the snapshot image (%1 x %2)" , + pm.width(), pm.height() ) ); + + mainWidget->lblImage->setPreview(pm); + mainWidget->lblImage->adjustSize(); +} + +void KSnapshot::setDelay( int i ) +{ + mainWidget->spinDelay->setValue(i); +} + +void KSnapshot::setIncludeDecorations( bool b ) +{ + mainWidget->cbIncludeDecorations->setChecked(b); +} + +void KSnapshot::setIncludePointer(bool enabled) +{ + mainWidget->cbIncludePointer->setChecked(enabled); +} + +bool KSnapshot::includePointer() const +{ + return mainWidget->cbIncludePointer->isChecked(); +} + +void KSnapshot::setMode( int mode ) +{ + mainWidget->comboMode->setCurrentIndex(mode); + slotModeChanged(mode); +} + +int KSnapshot::delay() const +{ + return mainWidget->spinDelay->value(); +} + +bool KSnapshot::includeDecorations() const +{ + return mainWidget->cbIncludeDecorations->isChecked(); +} + +int KSnapshot::mode() const +{ + return mainWidget->comboMode->currentIndex(); +} + +QPixmap KSnapshot::preview() +{ + return *mainWidget->lblImage->pixmap(); +} + +int KSnapshot::previewWidth() const +{ + return mainWidget->lblImage->width(); +} + +int KSnapshot::previewHeight() const +{ + return mainWidget->lblImage->height(); +} + +#include "ksnapshot.moc" diff --git a/ksnapshot/ksnapshot.desktop b/ksnapshot/ksnapshot.desktop new file mode 100755 index 00000000..eadc9a74 --- /dev/null +++ b/ksnapshot/ksnapshot.desktop @@ -0,0 +1,161 @@ +[Desktop Entry] +GenericName=Screen Capture Program +GenericName[af]=Skerm Vang Program +GenericName[ar]=برنامج التقاط الشاشة +GenericName[ast]=Capturador de pantalla +GenericName[bg]=Снимки на екрана +GenericName[bs]=Program za snimanje ekrana +GenericName[ca]=Programa de captura de pantalla +GenericName[ca@valencia]=Programa de captura de pantalla +GenericName[cs]=Snímač obrazovky +GenericName[cy]=Rhaglen Cipio'r Sgrîn +GenericName[da]=Program til øjebliksbilleder +GenericName[de]=Bildschirmfotos +GenericName[el]=Πρόγραμμα σύλληψης οθόνης +GenericName[en_GB]=Screen Capture Program +GenericName[eo]=Ekrankopia programo +GenericName[es]=Capturador de pantalla +GenericName[et]=Töölaua pildistamine +GenericName[eu]=Pantailari argazkiak ateratzeko programa +GenericName[fa]=برنامه گیراندازی پرده +GenericName[fi]=Kuvankaappausohjelma +GenericName[fr]=Programme pour capture d'écran +GenericName[ga]=Clár gabhála scáileáin +GenericName[gl]=Programa para facer capturas de pantalla +GenericName[he]=תוכנית לצילום המסך +GenericName[hi]=स्क्रीन केप्चर प्रोग्राम +GenericName[hne]=स्क्रीन केप्चर प्रोग्राम +GenericName[hr]=Program za snimanje zaslona +GenericName[hu]=Képlopó +GenericName[ia]=Programma pro capturar le schermo +GenericName[is]=Forrit sem grípur skjámyndir +GenericName[it]=Scatta foto allo schermo +GenericName[ja]=スクリーンキャプチャプログラム +GenericName[kk]=Экраннан түсіріп алу бағдарламасы +GenericName[km]=កម្មវិធី​ចាប់យក​អេក្រង់ +GenericName[ko]=화면 캡처 프로그램 +GenericName[ku]=Bernameya Wêne Girtina Dîmenderê +GenericName[lt]=Ekrano kopijos programa +GenericName[lv]=Ekrāna tveršanas programma +GenericName[mr]=स्क्रीनचे दृश्य पकडणारा कार्यक्रम +GenericName[ms]=Program Cekupan Skrin +GenericName[nb]=Program for skjermbilde +GenericName[nds]=Schirmfotos opnehmen +GenericName[ne]=पर्दा समात्ने कार्यक्रम +GenericName[nl]=Schermafdrukprogramma +GenericName[nn]=Program for skjermbilete +GenericName[pa]=ਸਕਰੀਨ ਕੈਪਚਰ ਪ੍ਰੋਗਰਾਮ +GenericName[pl]=Program do zrzutów ekranu +GenericName[pt]=Programa de Captura do Ecrã +GenericName[pt_BR]=Programa de captura de tela +GenericName[ro]=Program de captură de ecran +GenericName[ru]=Создание снимков экрана +GenericName[se]=Šearbmagovvenprográmma +GenericName[si]=තිරපිටපත් ගැණීමේ වැඩසටහන +GenericName[sk]=Zachytenie obrazovky +GenericName[sl]=Program za zajem zaslona +GenericName[sr]=Програм за снимање екрана +GenericName[sr@ijekavian]=Програм за снимање екрана +GenericName[sr@ijekavianlatin]=Program za snimanje ekrana +GenericName[sr@latin]=Program za snimanje ekrana +GenericName[sv]=Ta en skärmdump +GenericName[ta]=திரை கைப்பற்றும் நிரலி +GenericName[tg]=Эҷоди суратҳои экран +GenericName[th]=เครื่องมือจับภาพหน้าจอ +GenericName[tr]=Ekran Görüntüsü Yakalayıcı +GenericName[ug]=ئېكران كۆرۈنۈشىنى تۇتۇش پروگراممىسى +GenericName[uk]=Створення знімків вікон +GenericName[uz]=Skrinshot olish dasturi +GenericName[uz@cyrillic]=Скриншот олиш дастури +GenericName[vi]=Chương trình chụp màn hình +GenericName[wa]=Programe po fé des waitroûlêyes +GenericName[xh]=Iinkcazelo Ezigcina Ikhusi +GenericName[x-test]=xxScreen Capture Programxx +GenericName[zh_CN]=屏幕截图程序 +GenericName[zh_HK]=螢幕擷取程式 +GenericName[zh_TW]=畫面擷取程式 +Name=KSnapshot +Name[af]=K-kiekie +Name[ar]=كيسنابشوت +Name[ast]=KSnapshot +Name[bg]=KSnapshot +Name[br]=KSnapshot +Name[bs]=KSnapshot +Name[ca]=KSnapshot +Name[ca@valencia]=KSnapshot +Name[cs]=KSnapshot +Name[cy]=KCipluniau +Name[da]=KSnapshot +Name[de]=KSnapshot +Name[el]=KSnapshot +Name[en_GB]=KSnapshot +Name[eo]=KSnapshot +Name[es]=KSnapshot +Name[et]=KSnapshot +Name[eu]=KSnapshot +Name[fi]=KSnapshot +Name[fr]=KSnapshot +Name[ga]=KSnapshot +Name[gl]=KSnapshot +Name[he]=KSnapshot +Name[hi]=के-स्नेपशॉट +Name[hne]=के-स्नेपसाट +Name[hr]=KSnapshot +Name[hu]=KSnapshot +Name[ia]=KSnapshot +Name[is]=KSnapshot +Name[it]=KSnapshot +Name[ja]=KSnapshot +Name[kk]=KSnapshot +Name[km]=KSnapshot +Name[ko]=KSnapshot +Name[ku]=KSnapshot +Name[lt]=KSnapshot +Name[lv]=KSnapshot +Name[mr]=के-स्नँपशॉट +Name[ms]=KSnapshot +Name[nb]=KSnapshot +Name[nds]=KSnapshot +Name[ne]=केडीई स्न्यापसट +Name[nl]=KSnapshot +Name[nn]=KSnapshot +Name[pa]=ਕੇ-ਸਨੈਪਸ਼ਾਂਟ +Name[pl]=Zrzuty ekranu +Name[pt]=KSnapshot +Name[pt_BR]=KSnapshot +Name[ro]=KSnapshot +Name[ru]=KSnapshot +Name[se]=KSnapshot +Name[si]=KSnapshot +Name[sk]=KSnapshot +Name[sl]=KSnapshot +Name[sr]=К‑снимак +Name[sr@ijekavian]=К‑снимак +Name[sr@ijekavianlatin]=K‑snimak +Name[sr@latin]=K‑snimak +Name[sv]=Ksnapshot +Name[ta]=கேதிரையை நகலெடுத்தல் +Name[tg]=KSnapshot +Name[th]=จับภาพ-K +Name[tr]=KSnapshot +Name[ug]=KSnapshot +Name[uk]=KSnapshot +Name[uz]=KSnapshot +Name[uz@cyrillic]=KSnapshot +Name[vi]=KSnapshot +Name[wa]=KWaitroûlêye +Name[xh]=KSnapshot +Name[x-test]=xxKSnapshotxx +Name[zh_CN]=KSnapshot +Name[zh_HK]=KSnapshot +Name[zh_TW]=影像擷取_KSnapshot +MimeType= +Exec=ksnapshot -caption %c +Icon=ksnapshot +Type=Application +Terminal=false +StartupNotify=false +X-DocPath=ksnapshot/index.html +X-KDE-StartupNotify=false +X-DBUS-StartupType=Multi +Categories=Qt;KDE;Graphics; diff --git a/ksnapshot/ksnapshot.h b/ksnapshot/ksnapshot.h new file mode 100644 index 00000000..8b5fe299 --- /dev/null +++ b/ksnapshot/ksnapshot.h @@ -0,0 +1,153 @@ +/* + * Copyright (C) 1997-2002 Richard J. Moore + * Copyright (C) 2000 Matthias Ettrich + * Copyright (C) 2002 Aaron J. Seigo + * Copyright (C) 2003 Nadeem Hasan + * Copyright (C) 2004 Bernd Brandstetter + * Copyright (C) 2006 Urs Wolfer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KSNAPSHOT_H +#define KSNAPSHOT_H + +#include +#include +#include + +#include +#include +#include +#include +#include "ksnapshotobject.h" +#include "snapshottimer.h" + +#include + +class KSnapshotWidget; +class QMenu; + +#ifdef KIPI_FOUND +#include "ksnapshotimagecollectionshared.h" +#include +#endif + +class KSnapshotServiceAction : public QAction +{ + Q_OBJECT + public: + KSnapshotServiceAction(KService::Ptr s, QObject * parent) + : QAction(parent), service(s) {} + KSnapshotServiceAction(KService::Ptr s, + const QString & text, + QObject * parent) + : QAction(text, parent), service(s) {} + KSnapshotServiceAction(KService::Ptr s, + const QIcon & icon, + const QString & text, + QObject * parent) + : QAction(icon, text, parent), service(s) {} + + KService::Ptr service; +}; + +class KSnapshot : public KDialog, public KSnapshotObject +{ + Q_OBJECT + +public: + explicit KSnapshot(QWidget *parent= 0, KSnapshotObject::CaptureMode mode = FullScreen); + ~KSnapshot(); + + + QString url() const { return filename.url(); } + +public slots: + void slotGrab(); + void slotSave(); + void slotSaveAs(); + void slotCopy(); + void slotOpen(const QString& application); + void slotMovePointer( int x, int y ); + void setTime( int newTime ); + void setURL( const QString &newURL ); + void setGrabMode( int m ); + void exit(); + +protected: + void reject() { close(); } + virtual void closeEvent( QCloseEvent * e ); + void resizeEvent(QResizeEvent*); + bool eventFilter( QObject*, QEvent* ); + virtual void refreshCaption(); + +private slots: + void slotOpen(QAction*); + void slotPopulateOpenMenu(); + void grabTimerDone(); + void slotDragSnapshot(); + void updateCaption(); + void updatePreview(); + void slotRegionGrabbed( const QPixmap & ); + void slotRegionUpdated( const QRect & ); + void slotFreeRegionUpdated( const QPolygon & ); + void slotWindowGrabbed( const QPixmap & ); + void slotModeChanged( int mode ); + void setPreview( const QPixmap &pm ); + void setDelay( int i ); + void setIncludeDecorations( bool b ); + void setIncludePointer( bool b ); + bool includePointer() const; + void setMode( int mode ); + int delay() const; + bool includeDecorations() const; + int mode() const; + QPixmap preview(); + int previewWidth() const; + int previewHeight() const; + void startUndelayedGrab(); + void slotScreenshotReceived(qulonglong handle); + +public: + int grabMode() const; + int timeout() const; + +private: + KUrl urlToOpen(bool *isTempfile = 0); + void performGrab(); + void grabPointerImage(int offsetx, int offsety); + void grabRegion(); + void grabFreeRegion(); + + SnapshotTimer grabTimer; + QTimer updateTimer; + QMenu* openMenu; + KSnapshotWidget *mainWidget; + bool modified; + QPoint savedPosition; + bool haveXFixes; + bool includeAlpha; + QPolygon lastFreeRegion; + QRect lastRegion; + +#ifdef KIPI_FOUND + KIPI::PluginLoader* mPluginLoader; + friend KUrl::List KSnapshotImageCollectionShared::images(); +#endif +}; + +#endif // KSNAPSHOT_H diff --git a/ksnapshot/ksnapshot_options.h b/ksnapshot/ksnapshot_options.h new file mode 100644 index 00000000..1e3f1189 --- /dev/null +++ b/ksnapshot/ksnapshot_options.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 Montel Laurent + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KSNAPSHOTOPTIONS_H +#define KSNAPSHOTOPTIONS_H + +#include +#include + +static KCmdLineOptions ksnapshot_options () +{ + KCmdLineOptions options; + options.add("c"); + options.add("current", ki18n("Captures the window under the mouse on startup (instead of the desktop)")); + options.add("fullscreen", ki18n("Captures the desktop")); + options.add("region", ki18n("Captures a region")); + options.add("freeregion", ki18n("Captures a free region (not rectangular)")); + options.add("child", ki18n("Captures a part of windows")); + return options; +} + +#endif diff --git a/ksnapshot/ksnapshotimagecollectionshared.cpp b/ksnapshot/ksnapshotimagecollectionshared.cpp new file mode 100644 index 00000000..8ef1b0ec --- /dev/null +++ b/ksnapshot/ksnapshotimagecollectionshared.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 Pau Garcia i Quiles + * Essentially a rip-off of code for Kamoso by: + * Copyright (C) 2008-2009 by Aleix Pol + * Copyright (C) 2008-2009 by Alex Fiestas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "ksnapshotimagecollectionshared.h" +#include +#include +#include +#include "ksnapshot.h" + +struct KSnapshotImageCollectionShared::Private +{ + KSnapshot* ksnapshot; +}; + +KSnapshotImageCollectionShared::KSnapshotImageCollectionShared(KSnapshot* ksnapshot): d(new Private), mImages(KUrl::List()) +{ + d->ksnapshot = ksnapshot; +} + +KSnapshotImageCollectionShared::~KSnapshotImageCollectionShared() +{ + delete d; +} + +KUrl::List KSnapshotImageCollectionShared::images() +{ + // TODO (pgquiles) Clean up this mess. I'm not even sure all this is required! + bool isTempfile = false; + KUrl url = d->ksnapshot->urlToOpen(&isTempfile); + if (!url.isValid()) + { + return KUrl::List(); + } + return KUrl::List(KUrl(url)); +} diff --git a/ksnapshot/ksnapshotimagecollectionshared.h b/ksnapshot/ksnapshotimagecollectionshared.h new file mode 100644 index 00000000..a45d9775 --- /dev/null +++ b/ksnapshot/ksnapshotimagecollectionshared.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 Pau Garcia i Quiles + * Essentially a rip-off of code for Kamoso by: + * Copyright (C) 2008-2009 by Aleix Pol + * Copyright (C) 2008-2009 by Alex Fiestas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KSNAPSHOTIMAGECOLLECTIONSHARED_H +#define KSNAPSHOTIMAGECOLLECTIONSHARED_H + +#include +class KSnapshot; + +class KSnapshotImageCollectionShared : public KIPI::ImageCollectionShared +{ + +public: + KSnapshotImageCollectionShared(KSnapshot* ksnapshot); + ~KSnapshotImageCollectionShared(); + QString name() { return "KSnapshot"; } + QString comment() { return QString(); } + KUrl::List images(); + KUrl uploadRoot() { return KUrl("/"); } +// KUrl uploadPath() { return mDirURL; } + QString uploadRootName() { return "/"; } + bool isDirectory() { return false; } + +private: + struct Private; + Private* d; + KUrl::List mImages; +}; + +#endif diff --git a/ksnapshot/ksnapshotinfoshared.cpp b/ksnapshot/ksnapshotinfoshared.cpp new file mode 100644 index 00000000..0e0d840e --- /dev/null +++ b/ksnapshot/ksnapshotinfoshared.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2010 Pau Garcia i Quiles + * Essentially a rip-off of code for Kamoso by: + * Copyright (C) 2008-2009 by Aleix Pol + * Copyright (C) 2008-2009 by Alex Fiestas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "ksnapshotinfoshared.h" + +KSnapshotInfoShared::KSnapshotInfoShared(KIPIInterface* interface, const KUrl& url) : KIPI::ImageInfoShared(interface,url) +{ + +} + +void KSnapshotInfoShared::delAttributes(const QStringList& ) +{ + +} + +void KSnapshotInfoShared::addAttributes(const QMap< QString, QVariant >& ) +{ + +} + +void KSnapshotInfoShared::clearAttributes() +{ + +} + +QMap< QString, QVariant > KSnapshotInfoShared::attributes() +{ + return QMap(); +} + +void KSnapshotInfoShared::setDescription(const QString& ) +{ + +} + +QString KSnapshotInfoShared::description() +{ + return "Taken with KSnapshot"; +} diff --git a/ksnapshot/ksnapshotinfoshared.h b/ksnapshot/ksnapshotinfoshared.h new file mode 100644 index 00000000..98e8906d --- /dev/null +++ b/ksnapshot/ksnapshotinfoshared.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2010 Pau Garcia i Quiles + * Essentially a rip-off of code for Kamoso by: + * Copyright (C) 2008-2009 by Aleix Pol + * Copyright (C) 2008-2009 by Alex Fiestas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KSNAPSHOTINFOSHARED_H +#define KSNAPSHOTINFOSHARED_H + +#include +#include "kipiinterface.h" + +class KSnapshotInfoShared : public KIPI::ImageInfoShared +{ +public: + KSnapshotInfoShared(KIPIInterface* interface, const KUrl& url); + virtual void delAttributes(const QStringList& ); + virtual void addAttributes(const QMap< QString, QVariant >& ); + virtual void clearAttributes(); + virtual QMap< QString, QVariant > attributes(); + virtual void setDescription(const QString& ); + virtual QString description(); +}; + +#endif diff --git a/ksnapshot/ksnapshotobject.cpp b/ksnapshot/ksnapshotobject.cpp new file mode 100644 index 00000000..608f7dcf --- /dev/null +++ b/ksnapshot/ksnapshotobject.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 1997-2008 Richard J. Moore + * Copyright (C) 2000 Matthias Ettrich + * Copyright (C) 2002 Aaron J. Seigo + * Copyright (C) 2003 Nadeem Hasan + * Copyright (C) 2004 Bernd Brandstetter + * Copyright (C) 2006-2008 Urs Wolfer + * Copyright (C) 2007 Montel Laurent + * Copyright (C) 2010 Pau Garcia i Quiles + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "ksnapshotobject.h" + +#ifdef Q_WS_X11 +#include +#endif + +//kde include +#include +#include +#include +#include +#include +#include +#include + +//Qt include +#include +#include +#include + +KSnapshotObject::KSnapshotObject() +: rgnGrab( 0 ), + freeRgnGrab( 0 ), + grabber( 0 ) +{ +} + +KSnapshotObject::~KSnapshotObject() +{ + delete grabber; +} + +void KSnapshotObject::autoincFilename() +{ + // Extract the filename from the path + QString name= filename.fileName(); + + // If the name contains a number then increment it + QRegExp numSearch( "(^|[^\\d])(\\d+)" ); // we want to match as far left as possible, and when the number is at the start of the name + + // Does it have a number? + int start = numSearch.lastIndexIn( name ); + if (start != -1) { + // It has a number, increment it + start = numSearch.pos( 2 ); // we are only interested in the second group + QString numAsStr = numSearch.capturedTexts()[ 2 ]; + QString number = QString::number( numAsStr.toInt() + 1 ); + number = number.rightJustified( numAsStr.length(), '0' ); + name.replace( start, numAsStr.length(), number ); + } + else { + // no number + start = name.lastIndexOf('.'); + if (start != -1) { + // has a . somewhere, e.g. it has an extension + name.insert(start, '1'); + } + else { + // no extension, just tack it on to the end + name += '1'; + } + } + + //Rebuild the path + KUrl newUrl = filename; + newUrl.setFileName( name ); + changeUrl( newUrl.url() ); +} + + +void KSnapshotObject::changeUrl( const QString &url ) +{ + KUrl newURL = KUrl( url ); + if ( newURL == filename ) + return; + + filename = newURL; + refreshCaption(); +} + + +// NOTE: widget == NULL if called from dbus interface +bool KSnapshotObject::save( const QString &filename, QWidget* widget ) +{ + return save( KUrl( filename ), widget); +} + +bool KSnapshotObject::save( const KUrl& url, QWidget *widget ) +{ + if ( KIO::NetAccess::exists( url, KIO::NetAccess::DestinationSide, widget ) ) { + // NOTE: widget == NULL if called from dbus interface + const QString title = i18n( "File Exists" ); + const QString text = i18n( "Do you really want to overwrite %1?" , url.pathOrUrl()); + if (KMessageBox::Continue != KMessageBox::warningContinueCancel( widget, text, title, KGuiItem(i18n("Overwrite")) ) ) + { + return false; + } + } + return saveEqual( url,widget ); +} + +bool KSnapshotObject::saveEqual( const KUrl& url,QWidget *widget ) +{ + QByteArray type = "PNG"; + QString mime = KMimeType::findByUrl( url.fileName(), 0, url.isLocalFile(), true )->name(); + const QStringList types = KImageIO::typeForMime(mime); + if ( !types.isEmpty() ) + type = types.first().toLatin1(); + + bool ok = false; + + if ( url.isLocalFile() ) { + QFile output( url.toLocalFile() ); + if ( output.open( QFile::WriteOnly ) ) + ok = saveImage( &output, type ); + } + else { + KTemporaryFile tmpFile; + if ( tmpFile.open() ) { + if ( saveImage( &tmpFile, type ) ) { + ok = KIO::NetAccess::upload( tmpFile.fileName(), url, widget ); + } + } + } + + QApplication::restoreOverrideCursor(); + if ( !ok ) { + kWarning() << "KSnapshot was unable to save the snapshot" ; + + const QString caption = i18n("Unable to Save Image"); + const QString text = i18n("KSnapshot was unable to save the image to\n%1.", url.prettyUrl()); + KMessageBox::error(widget, text, caption); + } + + return ok; +} + +bool KSnapshotObject::saveImage( QIODevice *device, const QByteArray &format ) +{ + QImageWriter imgWriter( device, format ); + + if ( !imgWriter.canWrite() ) { + kDebug() << "Cannot write format " << format; + return false; + } + + // For jpeg use 85% quality not the default + if ( 0 == qstricmp(format.constData(), "jpeg") || 0 == qstricmp(format.constData(), "jpg") ) { + imgWriter.setQuality( 85 ); + } + + if ( !title.isEmpty() ) + imgWriter.setText( i18n("Title"), title ); + if ( !windowClass.isEmpty() ) + imgWriter.setText( i18n("Window Class"), windowClass ); + + QImage snap = snapshot.toImage(); + return imgWriter.write( snap ); +} + diff --git a/ksnapshot/ksnapshotobject.h b/ksnapshot/ksnapshotobject.h new file mode 100644 index 00000000..c918fac4 --- /dev/null +++ b/ksnapshot/ksnapshotobject.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 1997-2002 Richard J. Moore + * Copyright (C) 2000 Matthias Ettrich + * Copyright (C) 2002 Aaron J. Seigo + * Copyright (C) 2003 Nadeem Hasan + * Copyright (C) 2004 Bernd Brandstetter + * Copyright (C) 2006 Urs Wolfer + * Copyright (C) 2007 Montel Laurent + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KSNAPSHOTOBJECT_H +#define KSNAPSHOTOBJECT_H + +#include +#include +class QWidget; +class RegionGrabber; +class FreeRegionGrabber; +class QString; + +class KSnapshotObject +{ +public: + enum CaptureMode { FullScreen=0, WindowUnderCursor=1, Region=2, FreeRegion=3, ChildWindow=4, CurrentScreen=5 }; + KSnapshotObject(); + virtual ~KSnapshotObject(); + + bool save( const QString &filename, QWidget* widget = 0); + bool save( const KUrl& url, QWidget *widget ); + bool saveEqual( const KUrl& url,QWidget *widget ); + bool saveImage( QIODevice *device, const QByteArray &format ); + +protected: + void autoincFilename(); + virtual void refreshCaption(){} + + void changeUrl(const QString &newUrl); + + KUrl filename; + RegionGrabber *rgnGrab; + FreeRegionGrabber *freeRgnGrab; + QWidget* grabber; + QPixmap snapshot; + QString title; + QString windowClass; +}; + +#endif diff --git a/ksnapshot/ksnapshotpreview.cpp b/ksnapshot/ksnapshotpreview.cpp new file mode 100644 index 00000000..db4fd101 --- /dev/null +++ b/ksnapshot/ksnapshotpreview.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 1997-2002 Richard J. Moore + * Copyright (C) 2002-2010 Aaron J. Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "ksnapshotpreview.h" + +#include +#include +#include + +#include +#include + +#include "expblur.cpp" + +KSnapshotPreview::KSnapshotPreview(QWidget *parent) + : QLabel(parent) +{ + setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + setCursor(Qt::OpenHandCursor); +} + +KSnapshotPreview::~KSnapshotPreview() +{ +} + +void KSnapshotPreview::setPreview(const QPixmap &pm) +{ + static const int BLUR_PAD = 6; + static const int BLUR_RADIUS = 2; + QPixmap pixmap = pm.scaled(width() - BLUR_PAD, height() - BLUR_PAD, Qt::KeepAspectRatio, Qt::SmoothTransformation ); + + QImage blur(pixmap.size() + QSize(BLUR_PAD, BLUR_PAD), QImage::Format_ARGB32); + QRect blurRect = QRect(QPoint(BLUR_PAD / 2, BLUR_PAD / 2), pixmap.size()); + blur.fill(Qt::transparent); + //kDebug() << blur.size() << blurRect << pixmap.size(); + + const QColor color = qGray(palette().color(QPalette::Base).rgb()) < 192 ? Qt::white : Qt::black; + + { + QPainter p(&blur); + p.fillRect(blurRect, color); + p.end(); + } + + // apply blur for the thumbnail shadow + expblur<16, 7>(blur, BLUR_RADIUS); + + { + QPainter p(&blur); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(blur.rect(), color); + p.fillRect(QRect(blurRect.topLeft(), pixmap.size()), Qt::transparent); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + p.drawPixmap(QRect(blurRect.topLeft(), pixmap.size()), pixmap); + p.end(); + } + + setPixmap(QPixmap::fromImage(blur)); +} + +void KSnapshotPreview::mousePressEvent(QMouseEvent * e) +{ + if ( e->button() == Qt::LeftButton ) + mClickPt = e->pos(); +} + +void KSnapshotPreview::mouseMoveEvent(QMouseEvent * e) +{ + if (mClickPt != QPoint(0, 0) && + (e->pos() - mClickPt).manhattanLength() > KGlobalSettings::dndEventDelay()) + { + mClickPt = QPoint(0, 0); + emit startDrag(); + } +} + +void KSnapshotPreview::mouseReleaseEvent(QMouseEvent * /*e*/) +{ + mClickPt = QPoint(0, 0); +} + +#include "ksnapshotpreview.moc" + diff --git a/ksnapshot/ksnapshotpreview.h b/ksnapshot/ksnapshotpreview.h new file mode 100644 index 00000000..4b53e595 --- /dev/null +++ b/ksnapshot/ksnapshotpreview.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 1997-2002 Richard J. Moore + * Copyright (C) 2002-2010 Aaron J. Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KSNAPSHOTPREVIEW_H +#define KSNAPSHOTPREVIEW_H + +#include + +class KSnapshotPreview : public QLabel +{ + Q_OBJECT + +public: + explicit KSnapshotPreview(QWidget *parent = 0); + ~KSnapshotPreview(); + + void setPreview(const QPixmap &pm); + +signals: + void startDrag(); + +protected: + void mousePressEvent(QMouseEvent * e); + void mouseMoveEvent(QMouseEvent * e); + void mouseReleaseEvent(QMouseEvent * e); + +private: + QPoint mClickPt; +}; + +#endif // KSNAPSHOTPREVIEW_H diff --git a/ksnapshot/ksnapshotwidget.ui b/ksnapshot/ksnapshotwidget.ui new file mode 100644 index 00000000..e7211bdd --- /dev/null +++ b/ksnapshot/ksnapshotwidget.ui @@ -0,0 +1,270 @@ + + + KSnapshotWidget + + + + 0 + 0 + 436 + 374 + + + + + + + + 0 + 0 + + + + + 200 + 130 + + + + This is a preview of the current snapshot. + +The image can be dragged to another application or document to copy the full screenshot there. Try it with the file manager. + +You can also copy the image to the clipboard by pressing Ctrl+C. + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Click this button to take a new snapshot. + + + Take a &New Snapshot + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QFrame::HLine + + + QFrame::Sunken + + + Qt::Horizontal + + + + + + + + + Cap&ture mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + comboMode + + + + + + + <qt>Using this menu, you can select from the six following snapshot modes: +<p> +<b>Full Screen</b> - captures the entire desktop.<br/> +<b>Window Under Cursor</b> - captures only the window (or menu) that is under the mouse cursor when the snapshot is taken.<br/> +<b>Rectangular Region</b> - captures only the rectangular region of the desktop that you specify. When taking a new snapshot in this mode you will be able to select any area of the screen by clicking and dragging the mouse.<br/> +<b>Freehand Region</b> - captures arbitrary shapes that you specify. When taking a new snapshot in this mode you will be able to select any shape by dragging the mouse.<br/> +<b>Section of Window</b> - captures only a section of the window. When taking a new snapshot in this mode you will be able to select any child window by moving the mouse over it.<br/> +<b>Current Screen</b> - if you have multiple screens, this captures the screen containing the mouse cursor when the snapshot is taken. +</p></qt> + + + + Full Screen + + + + + Window Under Cursor + + + + + Rectangular Region + + + + + Freehand Region + + + + + Section of Window + + + + + Current Screen + + + + + + + + Snapshot &delay: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + spinDelay + + + + + + + + 0 + 0 + + + + Snapshot delay in seconds + + + <qt><p> +This is the number of seconds to wait after clicking the <i>New Snapshot</i> button before taking the snapshot. +</p><p> +This is very useful for getting windows, menus and other items on the screen set up just the way you want. +</p><p> +If <i>no delay</i> is set, the program will wait for a mouse click before taking a snapshot. +</p> +</qt> + + + No delay + + + + + + + + + + Include &window decorations: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + cbIncludeDecorations + + + + + + + When enabled, snapshot of a window will also include the window decorations + + + + + + true + + + + + + + Include mouse &pointer: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + cbIncludePointer + + + + + + + + + + + + + + + + + KIntSpinBox + QSpinBox +

knuminput.h
+ + + KPushButton + QPushButton +
kpushbutton.h
+
+ + KSnapshotPreview + QWidget +
ksnapshotpreview.h
+
+ + + btnNew + comboMode + spinDelay + cbIncludeDecorations + + + kpushbutton.h + + + + diff --git a/ksnapshot/main.cpp b/ksnapshot/main.cpp new file mode 100644 index 00000000..4b4f097c --- /dev/null +++ b/ksnapshot/main.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 1997-2002 Richard J. Moore + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#include +#include +#include +#include +#include +#include + +#include "ksnapshotadaptor.h" +#include "ksnapshot.h" +#include "ksnapshot_options.h" + +#define KSNAPVERSION "0.8.2" + +static const char description[] = I18N_NOOP("KDE Screenshot Utility"); + +int main(int argc, char **argv) +{ + KAboutData aboutData( "ksnapshot", 0, ki18n("KSnapshot"), + KSNAPVERSION, ki18n(description), KAboutData::License_GPL, + ki18n("(c) 1997-2008, Richard J. Moore,\n(c) 2000, Matthias Ettrich,\n(c) 2002-2003 Aaron J. Seigo")); + aboutData.addAuthor(ki18n("Richard J. Moore"),KLocalizedString(), "rich@kde.org"); + aboutData.addAuthor(ki18n("Matthias Ettrich"),KLocalizedString(), "ettrich@kde.org"); + aboutData.addAuthor(ki18n("Aaron J. Seigo"), KLocalizedString(), "aseigo@kde.org"); + aboutData.addCredit( ki18n("Nadeem Hasan"), ki18n("Region Grabbing\nReworked GUI"), + "nhasan@kde.org" ); + aboutData.addCredit( ki18n("Marcus Hufgard"), ki18n("\"Open With\" function"), + "Marcus.Hufgard@hufgard.de" ); + aboutData.addCredit( ki18n("Pau Garcia i Quiles"), ki18n("Free region grabbing, KIPI plugins support, port to Windows"), + "pgquiles@elpauer.org" ); + + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::addCmdLineOptions( ksnapshot_options() ); // Add our own options. + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + // This is one of the applications that requires the "native" / X11 graphics backend to work. + QApplication::setGraphicsSystem("native"); + KApplication app; + + // Create top level window + KSnapshot *toplevel; + bool showTopLevel = false; + + if ( args->isSet( "current" ) ) + toplevel = new KSnapshot( 0, KSnapshotObject::WindowUnderCursor ); + else if(args->isSet( "fullscreen" )) + { + //we grad directly desktop => show dialogbox + showTopLevel = true; + toplevel = new KSnapshot( 0, KSnapshotObject::FullScreen ); + } + else if(args->isSet( "region" )) + toplevel = new KSnapshot( 0, KSnapshotObject::Region ); + else if(args->isSet( "freeregion" )) + toplevel = new KSnapshot( 0, KSnapshotObject::FreeRegion ); + else if(args->isSet( "child" )) + toplevel = new KSnapshot( 0, KSnapshotObject::ChildWindow ); + else + { + showTopLevel = true; + toplevel = new KSnapshot(); + } + + args->clear(); + new KsnapshotAdaptor(toplevel); + QDBusConnection::sessionBus().registerObject("/KSnapshot", toplevel); + + if(showTopLevel) + toplevel->show(); + return app.exec(); +} + diff --git a/ksnapshot/org.kde.ksnapshot.xml b/ksnapshot/org.kde.ksnapshot.xml new file mode 100644 index 00000000..eb1d8e07 --- /dev/null +++ b/ksnapshot/org.kde.ksnapshot.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ksnapshot/regiongrabber.cpp b/ksnapshot/regiongrabber.cpp new file mode 100644 index 00000000..fb038a38 --- /dev/null +++ b/ksnapshot/regiongrabber.cpp @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2007 Luca Gugelmann + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2 as + * published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "regiongrabber.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +RegionGrabber::RegionGrabber( const QRect &startSelection ) : + QWidget( 0, Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool ), + selection( startSelection ), mouseDown( false ), newSelection( false ), + handleSize( 10 ), mouseOverHandle( 0 ), + showHelp( true ), grabbing( false ), + TLHandle(0,0,handleSize,handleSize), TRHandle(0,0,handleSize,handleSize), + BLHandle(0,0,handleSize,handleSize), BRHandle(0,0,handleSize,handleSize), + LHandle(0,0,handleSize,handleSize), THandle(0,0,handleSize,handleSize), + RHandle(0,0,handleSize,handleSize), BHandle(0,0,handleSize,handleSize) +{ + handles << &TLHandle << &TRHandle << &BLHandle << &BRHandle + << &LHandle << &THandle << &RHandle << &BHandle; + setMouseTracking( true ); + int timeout = KWindowSystem::compositingActive() ? 200 : 50; + QTimer::singleShot( timeout, this, SLOT(init()) ); +} + +RegionGrabber::~RegionGrabber() +{ +} + +void RegionGrabber::init() +{ + pixmap = QPixmap::grabWindow( QApplication::desktop()->winId() ); + resize( pixmap.size() ); + move( 0, 0 ); + setCursor( Qt::CrossCursor ); + show(); + grabMouse(); + grabKeyboard(); +} + +static void drawRect( QPainter *painter, const QRect &r, const QColor &outline, const QColor &fill = QColor() ) +{ + QRegion clip( r ); + clip = clip.subtracted( r.adjusted( 1, 1, -1, -1 ) ); + + painter->save(); + painter->setClipRegion( clip ); + painter->setPen( Qt::NoPen ); + painter->setBrush( outline ); + painter->drawRect( r ); + if ( fill.isValid() ) { + painter->setClipping( false ); + painter->setBrush( fill ); + painter->drawRect( r.adjusted( 1, 1, -1, -1 ) ); + } + painter->restore(); +} + +void RegionGrabber::paintEvent( QPaintEvent* e ) +{ + Q_UNUSED( e ); + if ( grabbing ) // grabWindow() should just get the background + return; + + QPainter painter( this ); + + QPalette pal(QToolTip::palette()); + QFont font = QToolTip::font(); + + QColor handleColor = pal.color( QPalette::Active, QPalette::Highlight ); + handleColor.setAlpha( 160 ); + QColor overlayColor( 0, 0, 0, 160 ); + QColor textColor = pal.color( QPalette::Active, QPalette::Text ); + QColor textBackgroundColor = pal.color( QPalette::Active, QPalette::Base ); + painter.drawPixmap(0, 0, pixmap); + painter.setFont(font); + + QRect r = selection; + if ( !selection.isNull() ) + { + QRegion grey( rect() ); + grey = grey.subtracted( r ); + painter.setClipRegion( grey ); + painter.setPen( Qt::NoPen ); + painter.setBrush( overlayColor ); + painter.drawRect( rect() ); + painter.setClipRect( rect() ); + drawRect( &painter, r, handleColor ); + } + + if ( showHelp ) + { + painter.setPen( textColor ); + painter.setBrush( textBackgroundColor ); + QString helpText = i18n( "Select a region using the mouse. To take the snapshot, press the Enter key or double click. Press Esc to quit." ); + helpTextRect = painter.boundingRect( rect().adjusted( 2, 2, -2, -2 ), Qt::TextWordWrap, helpText ); + helpTextRect.adjust( -2, -2, 4, 2 ); + drawRect( &painter, helpTextRect, textColor, textBackgroundColor ); + painter.drawText( helpTextRect.adjusted( 3, 3, -3, -3 ), helpText ); + } + + if ( selection.isNull() ) + { + return; + } + + // The grabbed region is everything which is covered by the drawn + // rectangles (border included). This means that there is no 0px + // selection, since a 0px wide rectangle will always be drawn as a line. + QString txt = QString( "%1x%2" ).arg( selection.width() ) + .arg( selection.height() ); + QRect textRect = painter.boundingRect( rect(), Qt::AlignLeft, txt ); + QRect boundingRect = textRect.adjusted( -4, 0, 0, 0); + + if ( textRect.width() < r.width() - 2*handleSize && + textRect.height() < r.height() - 2*handleSize && + ( r.width() > 100 && r.height() > 100 ) ) // center, unsuitable for small selections + { + boundingRect.moveCenter( r.center() ); + textRect.moveCenter( r.center() ); + } + else if ( r.y() - 3 > textRect.height() && + r.x() + textRect.width() < rect().right() ) // on top, left aligned + { + boundingRect.moveBottomLeft( QPoint( r.x(), r.y() - 3 ) ); + textRect.moveBottomLeft( QPoint( r.x() + 2, r.y() - 3 ) ); + } + else if ( r.x() - 3 > textRect.width() ) // left, top aligned + { + boundingRect.moveTopRight( QPoint( r.x() - 3, r.y() ) ); + textRect.moveTopRight( QPoint( r.x() - 5, r.y() ) ); + } + else if ( r.bottom() + 3 + textRect.height() < rect().bottom() && + r.right() > textRect.width() ) // at bottom, right aligned + { + boundingRect.moveTopRight( QPoint( r.right(), r.bottom() + 3 ) ); + textRect.moveTopRight( QPoint( r.right() - 2, r.bottom() + 3 ) ); + } + else if ( r.right() + textRect.width() + 3 < rect().width() ) // right, bottom aligned + { + boundingRect.moveBottomLeft( QPoint( r.right() + 3, r.bottom() ) ); + textRect.moveBottomLeft( QPoint( r.right() + 5, r.bottom() ) ); + } + // if the above didn't catch it, you are running on a very tiny screen... + drawRect( &painter, boundingRect, textColor, textBackgroundColor ); + + painter.drawText( textRect, txt ); + + if ( ( r.height() > handleSize*2 && r.width() > handleSize*2 ) + || !mouseDown ) + { + updateHandles(); + painter.setPen( Qt::NoPen ); + painter.setBrush( handleColor ); + painter.setClipRegion( handleMask( StrokeMask ) ); + painter.drawRect( rect() ); + handleColor.setAlpha( 60 ); + painter.setBrush( handleColor ); + painter.setClipRegion( handleMask( FillMask ) ); + painter.drawRect( rect() ); + } +} + +void RegionGrabber::resizeEvent( QResizeEvent* e ) +{ + Q_UNUSED( e ); + if ( selection.isNull() ) + return; + QRect r = selection; + r.setTopLeft( limitPointToRect( r.topLeft(), rect() ) ); + r.setBottomRight( limitPointToRect( r.bottomRight(), rect() ) ); + if ( r.width() <= 1 || r.height() <= 1 ) //this just results in ugly drawing... + selection = QRect(); + else + selection = normalizeSelection(r); +} + +void RegionGrabber::mousePressEvent( QMouseEvent* e ) +{ + showHelp = !helpTextRect.contains( e->pos() ); + if ( e->button() == Qt::LeftButton ) + { + mouseDown = true; + dragStartPoint = e->pos(); + selectionBeforeDrag = selection; + if ( !selection.contains( e->pos() ) ) + { + newSelection = true; + selection = QRect(); + } + else + { + setCursor( Qt::ClosedHandCursor ); + } + } + else if ( e->button() == Qt::RightButton ) + { + newSelection = false; + selection = QRect(); + setCursor( Qt::CrossCursor ); + } + update(); +} + +void RegionGrabber::mouseMoveEvent( QMouseEvent* e ) +{ + bool shouldShowHelp = !helpTextRect.contains( e->pos() ); + if (shouldShowHelp != showHelp) { + showHelp = shouldShowHelp; + update(); + } + + if ( mouseDown ) + { + if ( newSelection ) + { + QPoint p = e->pos(); + QRect r = rect(); + selection = normalizeSelection(QRect( dragStartPoint, limitPointToRect( p, r ) )); + } + else if ( mouseOverHandle == 0 ) // moving the whole selection + { + QRect r = rect().normalized(), s = selectionBeforeDrag.normalized(); + QPoint p = s.topLeft() + e->pos() - dragStartPoint; + r.setBottomRight( r.bottomRight() - QPoint( s.width(), s.height() ) + QPoint( 1, 1 ) ); + if ( !r.isNull() && r.isValid() ) + selection.moveTo( limitPointToRect( p, r ) ); + } + else // dragging a handle + { + QRect r = selectionBeforeDrag; + QPoint offset = e->pos() - dragStartPoint; + + if ( mouseOverHandle == &TLHandle || mouseOverHandle == &THandle + || mouseOverHandle == &TRHandle ) // dragging one of the top handles + { + r.setTop( r.top() + offset.y() ); + } + + if ( mouseOverHandle == &TLHandle || mouseOverHandle == &LHandle + || mouseOverHandle == &BLHandle ) // dragging one of the left handles + { + r.setLeft( r.left() + offset.x() ); + } + + if ( mouseOverHandle == &BLHandle || mouseOverHandle == &BHandle + || mouseOverHandle == &BRHandle ) // dragging one of the bottom handles + { + r.setBottom( r.bottom() + offset.y() ); + } + + if ( mouseOverHandle == &TRHandle || mouseOverHandle == &RHandle + || mouseOverHandle == &BRHandle ) // dragging one of the right handles + { + r.setRight( r.right() + offset.x() ); + } + r.setTopLeft( limitPointToRect( r.topLeft(), rect() ) ); + r.setBottomRight( limitPointToRect( r.bottomRight(), rect() ) ); + selection = normalizeSelection(r); + } + update(); + } + else + { + if ( selection.isNull() ) + return; + bool found = false; + foreach( QRect* r, handles ) + { + if ( r->contains( e->pos() ) ) + { + mouseOverHandle = r; + found = true; + break; + } + } + if ( !found ) + { + mouseOverHandle = 0; + if ( selection.contains( e->pos() ) ) + setCursor( Qt::OpenHandCursor ); + else + setCursor( Qt::CrossCursor ); + } + else + { + if ( mouseOverHandle == &TLHandle || mouseOverHandle == &BRHandle ) + setCursor( Qt::SizeFDiagCursor ); + if ( mouseOverHandle == &TRHandle || mouseOverHandle == &BLHandle ) + setCursor( Qt::SizeBDiagCursor ); + if ( mouseOverHandle == &LHandle || mouseOverHandle == &RHandle ) + setCursor( Qt::SizeHorCursor ); + if ( mouseOverHandle == &THandle || mouseOverHandle == &BHandle ) + setCursor( Qt::SizeVerCursor ); + } + } +} + +void RegionGrabber::mouseReleaseEvent( QMouseEvent* e ) +{ + mouseDown = false; + newSelection = false; + if ( mouseOverHandle == 0 && selection.contains( e->pos() ) ) + setCursor( Qt::OpenHandCursor ); + update(); +} + +void RegionGrabber::mouseDoubleClickEvent( QMouseEvent* ) +{ + grabRect(); +} + +void RegionGrabber::keyPressEvent( QKeyEvent* e ) +{ + QRect r = selection; + if ( e->key() == Qt::Key_Escape ) + { + emit regionUpdated( r ); + emit regionGrabbed( QPixmap() ); + } + else if ( e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return ) + { + grabRect(); + } + else + { + e->ignore(); + } +} + +void RegionGrabber::grabRect() +{ + QRect r = selection; + if ( !r.isNull() && r.isValid() ) + { + grabbing = true; + emit regionUpdated( r ); + emit regionGrabbed( pixmap.copy(r) ); + } +} + +void RegionGrabber::updateHandles() +{ + QRect r = selection; + int s2 = handleSize / 2; + + TLHandle.moveTopLeft( r.topLeft() ); + TRHandle.moveTopRight( r.topRight() ); + BLHandle.moveBottomLeft( r.bottomLeft() ); + BRHandle.moveBottomRight( r.bottomRight() ); + + LHandle.moveTopLeft( QPoint( r.x(), r.y() + r.height() / 2 - s2) ); + THandle.moveTopLeft( QPoint( r.x() + r.width() / 2 - s2, r.y() ) ); + RHandle.moveTopRight( QPoint( r.right(), r.y() + r.height() / 2 - s2 ) ); + BHandle.moveBottomLeft( QPoint( r.x() + r.width() / 2 - s2, r.bottom() ) ); +} + +QRegion RegionGrabber::handleMask( MaskType type ) const +{ + // note: not normalized QRects are bad here, since they will not be drawn + QRegion mask; + foreach( QRect* rect, handles ) { + if ( type == StrokeMask ) { + QRegion r( *rect ); + mask += r.subtracted( rect->adjusted( 1, 1, -1, -1 ) ); + } else { + mask += QRegion( rect->adjusted( 1, 1, -1, -1 ) ); + } + } + return mask; +} + +QPoint RegionGrabber::limitPointToRect( const QPoint &p, const QRect &r ) const +{ + QPoint q; + q.setX( p.x() < r.x() ? r.x() : p.x() < r.right() ? p.x() : r.right() ); + q.setY( p.y() < r.y() ? r.y() : p.y() < r.bottom() ? p.y() : r.bottom() ); + return q; +} + +QRect RegionGrabber::normalizeSelection( const QRect &s ) const +{ + QRect r = s; + if (r.width() <= 0) { + int l = r.left(); + int w = r.width(); + r.setLeft(l + w - 1); + r.setRight(l); + } + if (r.height() <= 0) { + int t = r.top(); + int h = r.height(); + r.setTop(t + h - 1); + r.setBottom(t); + } + return r; +} + +#include "regiongrabber.moc" diff --git a/ksnapshot/regiongrabber.h b/ksnapshot/regiongrabber.h new file mode 100644 index 00000000..3ade77ba --- /dev/null +++ b/ksnapshot/regiongrabber.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2007 Luca Gugelmann + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2 as + * published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef REGIONGRABBER_H +#define REGIONGRABBER_H + +#include +#include +#include +#include +#include + +class QPaintEvent; +class QResizeEvent; +class QMouseEvent; + +class RegionGrabber : public QWidget +{ + Q_OBJECT + + enum MaskType { StrokeMask, FillMask }; + +public: + RegionGrabber( const QRect &startSelection ); + ~RegionGrabber(); + +protected slots: + void init(); + +signals: + void regionGrabbed( const QPixmap & ); + void regionUpdated( const QRect & ); + +protected: + void paintEvent( QPaintEvent* e ); + void resizeEvent( QResizeEvent* e ); + void mousePressEvent( QMouseEvent* e ); + void mouseMoveEvent( QMouseEvent* e ); + void mouseReleaseEvent( QMouseEvent* e ); + void mouseDoubleClickEvent( QMouseEvent* ); + void keyPressEvent( QKeyEvent* e ); + void updateHandles(); + QRegion handleMask( MaskType type ) const; + QPoint limitPointToRect( const QPoint &p, const QRect &r ) const; + QRect normalizeSelection( const QRect &s ) const; + void grabRect(); + + QRect selection; + bool mouseDown; + bool newSelection; + const int handleSize; + QRect* mouseOverHandle; + QPoint dragStartPoint; + QRect selectionBeforeDrag; + bool showHelp; + bool grabbing; + + // naming convention for handles + // T top, B bottom, R Right, L left + // 2 letters: a corner + // 1 letter: the handle on the middle of the corresponding side + QRect TLHandle, TRHandle, BLHandle, BRHandle; + QRect LHandle, THandle, RHandle, BHandle; + QRect helpTextRect; + + QVector handles; + QPixmap pixmap; +}; + +#endif diff --git a/ksnapshot/snapshottimer.cpp b/ksnapshot/snapshottimer.cpp new file mode 100644 index 00000000..843f06df --- /dev/null +++ b/ksnapshot/snapshottimer.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2007777777 Aaron J. Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "snapshottimer.h" +#include +#include +#include +#include +#include + +#include +#include + +SnapshotTimer::SnapshotTimer() + : QWidget(0), + toggle(true) +{ + setWindowFlags( Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint); + // The text is copied from paintEvent and the maximum number is used as %1 argument + margins + resize(fontMetrics().width(i18np("Snapshot will be taken in 1 second", + "Snapshot will be taken in %1 seconds", 99) ) + 6, fontMetrics().height() + 4); + connect(&timer, SIGNAL(timeout()), this, SLOT(bell())); +} + +SnapshotTimer::~SnapshotTimer() +{ +} + +void SnapshotTimer::start(int seconds) +{ + const QRect screenGeom = qApp->desktop()->screenGeometry(); + move(screenGeom.width() / 2 - size().width() / 2, screenGeom.top()); + toggle = true; + time = 0; + length = seconds; + timer.start(1000); + show(); +} + +void SnapshotTimer::stop() +{ + setVisible(false); + hide(); + timer.stop(); +} + +void SnapshotTimer::bell() +{ + if (time == length - 1) { + hide(); + } + else { + if (time == length) { + emit timeout(); + timer.stop(); + } + } + ++time; + toggle = !toggle; + update(); +} + +void SnapshotTimer::enterEvent(QEvent *) +{ + const QRect screenGeom = qApp->desktop()->screenGeometry(); + if (x() == screenGeom.left()) { + move(screenGeom.x() + (screenGeom.width() / 2 - size().width() / 2), screenGeom.top()); + } + else { + move(screenGeom.topLeft()); + } +} + +void SnapshotTimer::paintEvent( QPaintEvent* e ) +{ + Q_UNUSED( e ); + + QPainter painter( this ); + + if (time < length) { + QPalette pal(QToolTip::palette()); + QColor handleColor = pal.color( QPalette::Active, QPalette::Highlight ); + handleColor.setAlpha( 160 ); + QColor overlayColor( 0, 0, 0, 160 ); + QColor textColor = pal.color( QPalette::Active, QPalette::Text ); + QColor textBackgroundColor = pal.color( QPalette::Active, QPalette::Base ); + if (toggle){ + textColor = pal.color( QPalette::Active, QPalette::Text); + } + else { + textColor = pal.color( QPalette::Active, QPalette::Base); + } + painter.setPen( textColor ); + painter.setBrush( textBackgroundColor ); + const QString helpText = i18np("Snapshot will be taken in 1 second", + "Snapshot will be taken in %1 seconds", length - time); + textRect = painter.boundingRect(rect().adjusted(2, 2, -2, -2), Qt::AlignHCenter | Qt::TextSingleLine, helpText); + painter.drawText(textRect, helpText); + } +} + diff --git a/ksnapshot/snapshottimer.h b/ksnapshot/snapshottimer.h new file mode 100644 index 00000000..66ab6778 --- /dev/null +++ b/ksnapshot/snapshottimer.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2007777777 Aaron J. Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef SNAPSHOTTIMER_H +#define SNAPSHOTTIMER_H + +#include +#include + +class SnapshotTimer : public QWidget +{ + Q_OBJECT + + public: + SnapshotTimer(); + ~SnapshotTimer(); + void start(int seconds); + void stop(); + + signals: + void timeout(); + + protected slots: + void bell(); + + protected: + void paintEvent(QPaintEvent *e); + void enterEvent(QEvent *e); + + private: + QTimer timer; + QRect textRect; + int time; + int length; + bool toggle; +}; + +#endif diff --git a/ksnapshot/windowgrabber.cpp b/ksnapshot/windowgrabber.cpp new file mode 100644 index 00000000..21a9531d --- /dev/null +++ b/ksnapshot/windowgrabber.cpp @@ -0,0 +1,581 @@ +/* + Copyright (C) 2004 Bernd Brandstetter + Copyright (C) 2010, 2011 Pau Garcia i Quiles + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "windowgrabber.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef Q_WS_X11 +#include +#include +#ifdef HAVE_X11_EXTENSIONS_SHAPE_H +#include +#endif // HAVE_X11_EXTENSIONS_SHAPE_H +#include +#endif // Q_WS_X11 + +#ifdef Q_WS_WIN +#include + +static UINT cxWindowBorder, cyWindowBorder; +#endif // Q_WS_WIN + +static +const int minSize = 8; + +static +bool operator< ( const QRect& r1, const QRect& r2 ) +{ + return r1.width() * r1.height() < r2.width() * r2.height(); +} + +// Recursively iterates over the window w and its children, thereby building +// a tree of window descriptors. Windows in non-viewable state or with height +// or width smaller than minSize will be ignored. +#ifdef Q_WS_X11 +static +void getWindowsRecursive( std::vector *windows, Window w, + int rx = 0, int ry = 0, int depth = 0 ) +{ + XWindowAttributes atts; + XGetWindowAttributes( QX11Info::display(), w, &atts ); + + if ( atts.map_state == IsViewable && + atts.width >= minSize && atts.height >= minSize ) { + int x = 0, y = 0; + if ( depth ) { + x = atts.x + rx; + y = atts.y + ry; + } + + QRect r( x, y, atts.width, atts.height ); + if ( std::find( windows->begin(), windows->end(), r ) == windows->end() ) { + windows->push_back( r ); + } + + Window root, parent; + Window* children; + unsigned int nchildren; + + if( XQueryTree( QX11Info::display(), w, &root, &parent, &children, &nchildren ) != 0 ) { + for( unsigned int i = 0; i < nchildren; ++i ) { + getWindowsRecursive( windows, children[ i ], x, y, depth + 1 ); + } + + if( children != NULL ) { + XFree( children ); + } + } + } + + if ( depth == 0 ) { + std::sort( windows->begin(), windows->end() ); + } +} +#elif defined(Q_WS_WIN) +static +bool maybeAddWindow(HWND hwnd, std::vector *windows) { + WINDOWINFO wi; + GetWindowInfo( hwnd, &wi ); + RECT rect = wi.rcClient; + +#if 0 + RECT rect; + GetWindowRect( hwnd, &rect ); +#endif + + int width = rect.right - rect.left; + int height = rect.bottom - rect.top; + + // For some reason, rect.left and rect.top are shifted by cxWindowBorders and cyWindowBorders pixels respectively + // in *every* case (for every window), but cxWindowBorders and cyWindowBorders are non-zero only in the + // biggest-window case, therefore we need to save the biggest cxWindowBorders and cyWindowBorders to adjust the rect later + cxWindowBorder = qMax(cxWindowBorder, wi.cxWindowBorders); + cyWindowBorder = qMax(cyWindowBorder, wi.cyWindowBorders); + + if ( ( ( wi.dwStyle & WS_VISIBLE ) != 0 ) && (width >= minSize ) && (height >= minSize ) ) + { + //QRect r( rect.left + 4, rect.top + 4, width, height); // 4 = max(wi.cxWindowBorders) = max(wi.cyWindowBorders) + QRect r(rect.left + cxWindowBorder, rect.top + cyWindowBorder, width, height); + if ( std::find( windows->begin(), windows->end(), r ) == windows->end() ) { + windows->push_back( r ); + return true; + } + } + return false; +} + +static +BOOL CALLBACK getWindowsRecursiveHelper( HWND hwnd, LPARAM lParam ) { + maybeAddWindow(hwnd, reinterpret_cast< std::vector* >(lParam) ); + return TRUE; +} + +static +void getWindowsRecursive( std::vector *windows, HWND hwnd, + int rx = 0, int ry = 0, int depth = 0 ) +{ + + maybeAddWindow(hwnd, windows); + + EnumChildWindows( hwnd, getWindowsRecursiveHelper, (LPARAM) windows ); + + std::sort( windows->begin(), windows->end() ); +} +#endif // Q_WS_X11 + +#ifdef Q_WS_X11 +static +Window findRealWindow( Window w, int depth = 0 ) +{ + if( depth > 5 ) { + return None; + } + + static Atom wm_state = XInternAtom( QX11Info::display(), "WM_STATE", False ); + Atom type; + int format; + unsigned long nitems, after; + unsigned char* prop; + + if( XGetWindowProperty( QX11Info::display(), w, wm_state, 0, 0, False, AnyPropertyType, + &type, &format, &nitems, &after, &prop ) == Success ) { + if( prop != NULL ) { + XFree( prop ); + } + + if( type != None ) { + return w; + } + } + + Window root, parent; + Window* children; + unsigned int nchildren; + Window ret = None; + + if( XQueryTree( QX11Info::display(), w, &root, &parent, &children, &nchildren ) != 0 ) { + for( unsigned int i = 0; + i < nchildren && ret == None; + ++i ) { + ret = findRealWindow( children[ i ], depth + 1 ); + } + + if( children != NULL ) { + XFree( children ); + } + } + + return ret; +} +#elif defined(Q_WS_WIN) +static +HWND findRealWindow( HWND w, int depth = 0 ) +{ + // TODO Implement + return w; // This is WRONG but makes code compile for now +} +#endif // Q_WS_X11 + +#ifdef Q_WS_X11 +static +Window windowUnderCursor( bool includeDecorations = true ) +{ + Window root; + Window child; + uint mask; + int rootX, rootY, winX, winY; + + XGrabServer( QX11Info::display() ); + XQueryPointer( QX11Info::display(), QX11Info::appRootWindow(), &root, &child, + &rootX, &rootY, &winX, &winY, &mask ); + + if( child == None ) { + child = QX11Info::appRootWindow(); + } + + if( !includeDecorations ) { + Window real_child = findRealWindow( child ); + + if( real_child != None ) { // test just in case + child = real_child; + } + } + + return child; +} +#elif defined(Q_WS_WIN) +static +HWND windowUnderCursor(bool includeDecorations = true) +{ + POINT pointCursor; + QPoint qpointCursor = QCursor::pos(); + pointCursor.x = qpointCursor.x(); + pointCursor.y = qpointCursor.y(); + HWND windowUnderCursor = WindowFromPoint(pointCursor); + + if( includeDecorations ) { + LONG_PTR style = GetWindowLongPtr( windowUnderCursor, GWL_STYLE ); + if( ( style & WS_CHILD ) != 0 ) { + windowUnderCursor = GetAncestor( windowUnderCursor, GA_ROOT ); + } + } + return windowUnderCursor; +} +#endif + +#ifdef Q_WS_X11 +static +QPixmap grabWindow( Window child, int x, int y, uint w, uint h, uint border, + QString *title=0, QString *windowClass=0 ) +{ + QPixmap pm( QPixmap::grabWindow( QX11Info::appRootWindow(), x, y, w, h ) ); + + KWindowInfo winInfo( findRealWindow(child), NET::WMVisibleName, NET::WM2WindowClass ); + + if ( title ) { + (*title) = winInfo.visibleName(); + } + + if ( windowClass ) { + (*windowClass) = winInfo.windowClassName(); + } + +#ifdef HAVE_X11_EXTENSIONS_SHAPE_H + int tmp1, tmp2; + //Check whether the extension is available + if ( XShapeQueryExtension( QX11Info::display(), &tmp1, &tmp2 ) ) { + QBitmap mask( w, h ); + //As the first step, get the mask from XShape. + int count, order; + XRectangle* rects = XShapeGetRectangles( QX11Info::display(), child, + ShapeBounding, &count, &order ); + //The ShapeBounding region is the outermost shape of the window; + //ShapeBounding - ShapeClipping is defined to be the border. + //Since the border area is part of the window, we use bounding + // to limit our work region + if (rects) { + //Create a QRegion from the rectangles describing the bounding mask. + QRegion contents; + for ( int pos = 0; pos < count; pos++ ) + contents += QRegion( rects[pos].x, rects[pos].y, + rects[pos].width, rects[pos].height ); + XFree( rects ); + + //Create the bounding box. + QRegion bbox( 0, 0, w, h ); + + if( border > 0 ) { + contents.translate( border, border ); + contents += QRegion( 0, 0, border, h ); + contents += QRegion( 0, 0, w, border ); + contents += QRegion( 0, h - border, w, border ); + contents += QRegion( w - border, 0, border, h ); + } + + //Get the masked away area. + QRegion maskedAway = bbox - contents; + QVector maskedAwayRects = maskedAway.rects(); + + //Construct a bitmap mask from the rectangles + QPainter p(&mask); + p.fillRect(0, 0, w, h, Qt::color1); + for (int pos = 0; pos < maskedAwayRects.count(); pos++) + p.fillRect(maskedAwayRects[pos], Qt::color0); + p.end(); + + pm.setMask(mask); + } + } +#endif // HAVE_X11_EXTENSIONS_SHAPE_H + + return pm; +} +#elif defined(Q_WS_WIN) +static +QPixmap grabWindow( HWND hWnd, QString *title=0, QString *windowClass=0 ) +{ + RECT windowRect; + GetWindowRect(hWnd, &windowRect); + int w = windowRect.right - windowRect.left; + int h = windowRect.bottom - windowRect.top; + HDC targetDC = GetWindowDC(hWnd); + HDC hDC = CreateCompatibleDC(targetDC); + HBITMAP tempPict = CreateCompatibleBitmap(targetDC, w, h); + HGDIOBJ oldPict = SelectObject(hDC, tempPict); + BitBlt(hDC, 0, 0, w, h, targetDC, 0, 0, SRCCOPY); + tempPict = (HBITMAP) SelectObject(hDC, oldPict); + QPixmap pm = QPixmap::fromWinHBITMAP(tempPict); + + DeleteDC(hDC); + ReleaseDC(hWnd, targetDC); + + KWindowInfo winInfo( findRealWindow(hWnd), NET::WMVisibleName, NET::WM2WindowClass ); + if ( title ) { + (*title) = winInfo.visibleName(); + } + + if ( windowClass ) { + (*windowClass) = winInfo.windowClassName(); + } + return pm; +} +#endif // Q_WS_X11 + +QString WindowGrabber::title; +QString WindowGrabber::windowClass; +QPoint WindowGrabber::windowPosition; + +WindowGrabber::WindowGrabber() +: QDialog( 0, Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint ), + current( -1 ), yPos( -1 ) +{ + setWindowModality( Qt::WindowModal ); + int y,x; + uint w, h; + +#ifdef Q_WS_X11 + uint border, depth; + Window root; + XGrabServer( QX11Info::display() ); + Window child = windowUnderCursor(); + XGetGeometry( QX11Info::display(), child, &root, &x, &y, &w, &h, &border, &depth ); + XUngrabServer( QX11Info::display() ); + + QPixmap pm( grabWindow( child, x, y, w, h, border, &title, &windowClass ) ); +#elif defined(Q_WS_WIN) + HWND child = windowUnderCursor(); + + WINDOWINFO wi; + GetWindowInfo( child, &wi); + + RECT r; + GetWindowRect( child, &r); + x = r.left; + y = r.top; + w = r.right - r.left; + h = r.bottom - r.top; + cxWindowBorder = wi.cxWindowBorders; + cyWindowBorder = wi.cyWindowBorders; + + HDC childDC = GetDC(child); + + QPixmap pm( grabWindow( child, &title, &windowClass ) ); +#endif // Q_WS_X11 + + getWindowsRecursive( &windows, child ); + + QPalette p = palette(); + p.setBrush( backgroundRole(), QBrush( pm ) ); + setPalette( p ); + setFixedSize( pm.size() ); + setMouseTracking( true ); + setGeometry( x, y, w, h ); + current = windowIndex( mapFromGlobal(QCursor::pos()) ); +} + +WindowGrabber::~WindowGrabber() +{ +} + +QPixmap WindowGrabber::grabCurrent( bool includeDecorations ) +{ + int x, y; +#ifdef Q_WS_X11 + Window root; + uint w, h, border, depth; + + XGrabServer( QX11Info::display() ); + Window child = windowUnderCursor( includeDecorations ); + XGetGeometry( QX11Info::display(), child, &root, &x, &y, &w, &h, &border, &depth ); + + Window parent; + Window* children; + unsigned int nchildren; + + if( XQueryTree( QX11Info::display(), child, &root, &parent, + &children, &nchildren ) != 0 ) { + if( children != NULL ) { + XFree( children ); + } + + int newx, newy; + Window dummy; + + if( XTranslateCoordinates( QX11Info::display(), parent, QX11Info::appRootWindow(), + x, y, &newx, &newy, &dummy )) { + x = newx; + y = newy; + } + } + + windowPosition = QPoint(x,y); + QPixmap pm( grabWindow( child, x, y, w, h, border, &title, &windowClass ) ); + XUngrabServer( QX11Info::display() ); + return pm; +#elif defined(Q_WS_WIN) + HWND hWindow; + hWindow = windowUnderCursor(includeDecorations); + Q_ASSERT(hWindow); + + HWND hParent; + +// Now find the top-most window + do { + hParent = hWindow; + } while( (hWindow = GetParent(hWindow)) != NULL ); + Q_ASSERT(hParent); + + RECT r; + GetWindowRect(hParent, &r); + + x = r.left; + y = r.top; + + windowPosition = QPoint(x,y); + QPixmap pm( grabWindow( hParent, &title, &windowClass) ); + return pm; +#endif // Q_WS_X11 + return QPixmap(); +} + + +void WindowGrabber::mousePressEvent( QMouseEvent *e ) +{ + if ( e->button() == Qt::RightButton ) { + yPos = e->globalY(); + } else { + if ( current != -1 ) { + windowPosition = e->globalPos() - e->pos() + windows[current].topLeft(); + emit windowGrabbed( palette().brush( backgroundRole() ).texture().copy( windows[ current ] ) ); + } else { + windowPosition = QPoint(0,0); + emit windowGrabbed( QPixmap() ); + } + accept(); + } +} + +void WindowGrabber::mouseReleaseEvent( QMouseEvent *e ) +{ + if ( e->button() == Qt::RightButton ) { + yPos = -1; + } +} + +static +const int minDistance = 10; + +void WindowGrabber::mouseMoveEvent( QMouseEvent *e ) +{ + if ( yPos == -1 ) { + int w = windowIndex( e->pos() ); + if ( w != -1 && w != current ) { + current = w; + repaint(); + } + } else { + int y = e->globalY(); + if ( y > yPos + minDistance ) { + decreaseScope( e->pos() ); + yPos = y; + } else if ( y < yPos - minDistance ) { + increaseScope( e->pos() ); + yPos = y; + } + } +} + +void WindowGrabber::wheelEvent( QWheelEvent *e ) +{ + if ( e->delta() > 0 ) { + increaseScope( e->pos() ); + } else if ( e->delta() < 0 ) { + decreaseScope( e->pos() ); + } else { + e->ignore(); + } +} + +// Increases the scope to the next-bigger window containing the mouse pointer. +// This method is activated by either rotating the mouse wheel forwards or by +// dragging the mouse forwards while keeping the right mouse button pressed. +void WindowGrabber::increaseScope( const QPoint &pos ) +{ + for ( uint i = current + 1; i < windows.size(); i++ ) { + if ( windows[ i ].contains( pos ) ) { + current = i; + break; + } + } + repaint(); +} + +// Decreases the scope to the next-smaller window containing the mouse pointer. +// This method is activated by either rotating the mouse wheel backwards or by +// dragging the mouse backwards while keeping the right mouse button pressed. +void WindowGrabber::decreaseScope( const QPoint &pos ) +{ + for ( int i = current - 1; i >= 0; i-- ) { + if ( windows[ i ].contains( pos ) ) { + current = i; + break; + } + } + repaint(); +} + +// Searches and returns the index of the first (=smallest) window +// containing the mouse pointer. +int WindowGrabber::windowIndex( const QPoint &pos ) const +{ + for ( uint i = 0; i < windows.size(); i++ ) { + if ( windows[ i ].contains( pos ) ) { + return i; + } + } + return -1; +} + +// Draws a border around the (child) window currently containing the pointer +void WindowGrabber::paintEvent( QPaintEvent * ) +{ + if ( current >= 0 ) { + QPainter p; + p.begin( this ); + p.fillRect(rect(), palette().brush( backgroundRole())); + p.setPen( QPen( Qt::red, 3 ) ); + p.drawRect( windows[ current ].adjusted( 0, 0, -1, -1 ) ); + p.end(); + } +} + +#include "windowgrabber.moc" diff --git a/ksnapshot/windowgrabber.h b/ksnapshot/windowgrabber.h new file mode 100644 index 00000000..ec4cba90 --- /dev/null +++ b/ksnapshot/windowgrabber.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 2004 Bernd Brandstetter + Copyright (C) 2010, 2011 Pau Garcia i Quiles + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or ( at your option ) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef WINDOWGRABBER_H +#define WINDOWGRABBER_H + +#include +#include + +class WindowGrabber : public QDialog +{ + Q_OBJECT + +public: + WindowGrabber(); + ~WindowGrabber(); + + /* Grab a screenshot of the current window. x and y are set to the position of the window */ + static QPixmap grabCurrent( bool includeDecorations ); + static QString lastWindowTitle() { return WindowGrabber::title; } + static QString lastWindowClass() { return WindowGrabber::windowClass; } + static QPoint lastWindowPosition() { return WindowGrabber::windowPosition; } + +signals: + void windowGrabbed( const QPixmap & ); + +protected: + void mousePressEvent( QMouseEvent * ); + void mouseReleaseEvent( QMouseEvent * ); + void mouseMoveEvent( QMouseEvent * ); + void wheelEvent( QWheelEvent * ); + void paintEvent( QPaintEvent * ); + +private: + void increaseScope( const QPoint & ); + void decreaseScope( const QPoint & ); + int windowIndex( const QPoint & ) const; + std::vector windows; + int current; + int yPos; + static QString title; + static QString windowClass; + static QPoint windowPosition; +}; + +#endif // WINDOWGRABBER_H diff --git a/ksystemlog/.cproject b/ksystemlog/.cproject new file mode 100644 index 00000000..2a5a35ed --- /dev/null +++ b/ksystemlog/.cproject @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ksystemlog/.project b/ksystemlog/.project new file mode 100644 index 00000000..ff3e8f99 --- /dev/null +++ b/ksystemlog/.project @@ -0,0 +1,78 @@ + + + ksystemlog + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + org.eclipse.cdt.make.core.cleanBuildTarget + clean + + + org.eclipse.cdt.make.core.enableCleanBuild + true + + + ?name? + + + + org.eclipse.cdt.make.core.append_environment + true + + + org.eclipse.cdt.make.core.stopOnError + true + + + org.eclipse.cdt.make.core.buildCommand + make + + + org.eclipse.cdt.make.core.contents + org.eclipse.cdt.make.core.activeConfigSettings + + + org.eclipse.cdt.make.core.useDefaultBuildCmd + true + + + org.eclipse.cdt.make.core.enableAutoBuild + false + + + org.eclipse.cdt.make.core.enableFullBuild + true + + + org.eclipse.cdt.make.core.buildArguments + + + + org.eclipse.cdt.make.core.fullBuildTarget + all + + + org.eclipse.cdt.make.core.autoBuildTarget + all + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + + + + + + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.core.cnature + + diff --git a/ksystemlog/.settings/org.eclipse.ltk.core.refactoring.prefs b/ksystemlog/.settings/org.eclipse.ltk.core.refactoring.prefs new file mode 100644 index 00000000..a00eb57e --- /dev/null +++ b/ksystemlog/.settings/org.eclipse.ltk.core.refactoring.prefs @@ -0,0 +1,3 @@ +#Thu Aug 09 18:39:06 CEST 2007 +eclipse.preferences.version=1 +org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false diff --git a/ksystemlog/CMakeLists.txt b/ksystemlog/CMakeLists.txt new file mode 100644 index 00000000..aad1d7de --- /dev/null +++ b/ksystemlog/CMakeLists.txt @@ -0,0 +1,71 @@ +project(KSystemLog) + +find_package(KDE4 REQUIRED) + +include(KDE4Defaults) +include(MacroLibrary) + +include(CheckIncludeFile) +include(CheckIncludeFiles) +include(CheckSymbolExists) +include(CheckFunctionExists) +include(CheckLibraryExists) +include(CheckPrototypeExists) +include(CheckTypeSize) + +set(CMAKE_REQUIRED_DEFINITIONS ${_KDE4_PLATFORM_DEFINITIONS}) +if(WIN32) + set(CMAKE_REQUIRED_LIBRARIES ${KDEWIN32_LIBRARIES}) + set(CMAKE_REQUIRED_INCLUDES ${KDEWIN32_INCLUDES}) +endif(WIN32) +add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS} ${KDE4_DEFINITIONS}) +add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) +include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}) + +# Help Eclipse to parse errors more efficiently +if(CMAKE_COMPILER_IS_GNUCC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fmessage-length=0") +endif(CMAKE_COMPILER_IS_GNUCC) +if(CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmessage-length=0") +endif(CMAKE_COMPILER_IS_GNUCXX) + + +add_custom_target( + package + COMMAND ./build-package.sh +) + +## +# To specify a different install prefix, use : +# cmake -D CMAKE_INSTALL_PREFIX=build . +# +# To help Eclipse discover include paths, use : +# cmake -D CMAKE_VERBOSE_MAKEFILE=true . +## + +########### Subfolders ########## + +add_subdirectory(src) +add_subdirectory(src/lib) +add_subdirectory(src/config) + +add_subdirectory(src/modes/base) +add_subdirectory(src/modes/open) + +add_subdirectory(src/modes/system) +add_subdirectory(src/modes/kernel) +add_subdirectory(src/modes/xorg) +add_subdirectory(src/modes/cron) +add_subdirectory(src/modes/authentication) +add_subdirectory(src/modes/daemon) +add_subdirectory(src/modes/acpid) +add_subdirectory(src/modes/xsession) +add_subdirectory(src/modes/apache) +add_subdirectory(src/modes/postfix) +add_subdirectory(src/modes/samba) +add_subdirectory(src/modes/cups) + +add_subdirectory(tests) + +add_subdirectory(doc) diff --git a/ksystemlog/COPYING b/ksystemlog/COPYING new file mode 100644 index 00000000..8efe11aa --- /dev/null +++ b/ksystemlog/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ksystemlog/Changelog b/ksystemlog/Changelog new file mode 100644 index 00000000..1cfb89d4 --- /dev/null +++ b/ksystemlog/Changelog @@ -0,0 +1,217 @@ + +Current Development Version (0.4.1) +=================================== + + - Compatibility with KDE 4 beta4 + - Fix icon position in search bar + - Add test coverage for Kernel logs + - Support for Suse Kernel logs + - Add joker to file list in configuration + - Auto-recognizition of the opened files + +Previous Releases +=========================== + +KSystemLog 0.4 Release +---------------------- + +This major rewrite of KSystemLog took between 3 and 4 months of development. + + - **Major rewrite** of KSystemLog. All core parts have been totally rewritten. + - Compatible with KDE 4. + - Use of Oxygen icons. + - New search bar (like the Firefox search bar). + - Integrated Progress dialog into the main window. + - History on log messages. + - No more crash each 5 minutes. + - Highly decrease the number of memory leaks. + - Modularization of log modes (kernel, system, authentication,...). + - New XSession log mode. + - Vastly improved version of Apache, Samba, Cups and ACPID log modes. + - No more layout problem in Details dialog. + - No more layout problem in Configuration Dialog. + - Kernel log mode is now using the "dmesg" command output + - Remove Boot log (no interested log file in distributions) + - Remove the "Group By" [Date/User/...] feature in contextual menu during refactoring. This feature could be reimplemented in future release. + +KSystemLog 0.3.3 Release +------------------------ + +Major Features and Improvements +............................... + + - Writing documentation (still miss a lot of things). + - Support for a system tray notifier (thanks to Jean-Rémy Falleri). + - Adding Printing (many thanks to Bojan). + - Adding SSH mode (many thanks to Bojan). + +Minor Fixes +........... + + - Do not use kdesu by default, a distrib/desktop dependent solution (thanks to Jonathan Riddell). + - Displaying KSystemLog in Gnome (thanks to Jonathan Riddell). + - Fix problems detected by http://www.icefox.net/kde/tests website. (pre-incrementation, TRUE->true, KConfigXT XML Doctype...) + - Updating French translation. + - Fix documentation and translations compilations. + - Optimization + - Minor fixes... + +KSystemLog 0.3.2 Release +------------------------ + +Major Features and Improvements +............................... + + - Improve a little bit the starting of KSystemLog. + - Add the Apache log. + - Add the Postfix log. + - Add the Samba log. + - Dialog box for sending log message manually from a dialog box. + - Log Toolbar is now visible by default, but only main logs are displayed. + - Colorize log lines by default. + +Minor Fixes +........... + + - Add a grayed "Type your filter here" in the filter (like Amarok). + - Remove shortcut on each log mode. They were to annoying to manage. The user always can bind the key he wants for a log. + - A double-click on an item opens the Detail Dialog. + - Fix a bug which decreased the log file reading. + - Add the Group By action to the Toolbar. + - Using sub-folders to sort source files + - Remove a useless space in most of logs + - Improve the saving of log views layout (column order is now saved) + - Remove tons of memory leaks + - Definetly fix the Detail Dialog by drawing it with Qt Designer + +KSystemLog 0.3.1 Release +------------------------ + +Major Features and Improvements +............................... + + - Fix the compilation problem on KDE 3.3. KSystemLog should now even compile on KDE 3.2 + - Add an option to remove the process identifier from the process column + - Add an option to colorize log lines depending on their log level + - Correctly save the window size on exit + +Minor Fixes +........... + + - New icon ! (made by a non-artist developer ;-) + - KUbuntu, Mandriva, Debian and Gentoo packages for 0.3 + +KSystemLog 0.3 Release +---------------------- + +Major Features and Improvements +............................... + + - Group By <Column> menu item to sort items in a tree (with root nodes of the selected column). + - Progress bar Dialog when loading (and reloading) logs + - New option finding same entries and remove them + - Management of a character limit per line (from configuration dialog) + - Save the sorting and sort column state and reload it at startup + - Add a menu item to hide the filter + - Cool Group By main entries' look + - Middle button on a log icon opens it in a new tab (And Ctrl+Left click and Shift+Left Click) + +Minor Fixes +........... + + - Fixing KDE 3.2 Compilation problem + - Updated translations for : Breton, Danish, German, Greek, English, Estonian, French, Irish, Italian, Panjabi, Polish, Kinyarwanda, Swedish and Turkish + - Same size for Next and Previous button in detail dialog + - Reload (an resize) correctly the content of the detail dialog + - Add more log levels managed in Cups log + - Fix a bug which strips message of the Acpid log + - Save the group by sorting in the config file + - Manage log lines with more than 1024 characters + - Fix many typography errors (thanks to Andrew Coles) + - Splitting of KSystemLog and LogManager classes, to allow displaying other kind of view in the center of KSystemLog + - Remove source code automatically managed by KDE Libs (and respective slot/signal pairs) + - Saving size of the windows, and Toolbar/menu configuration between KSystemLog launching + - Mandriva Package (KSystemLog 0.2.2) + - KUbuntu Package (KSystemLog 0.2.2) + - Hide the log toolbar by default + - Fix the limit ignoring problem + - Fix the unknown and mysterious crash bug + - A lot of bug fixes (reloading and detail dialog, NULL pointers...) + +KSystemLog 0.2.1 and 0.2.2 Release +---------------------------------- + + - French translation updated + - KDE 3.3 compilation error fixed + - English typography errors fixed + +KSystemLog 0.2 Release +---------------------- + +The new version of KSystemLog is now available ! 0.2 version adds several major features to KSystemLog. +The translations are not updated, but the strings are freezed for 4-5 days, so do not hesitate to help +me translating KSystemLog ! + +Major Features and Improvements +............................... + + - KSystemLog can open multiple log files thanks to its tabbed interface + - Using KConfigXT (yeah!) + - Implements the Find / Find next features + - A Send to a friend button + - Add support for ACPId Log file + - Add support for Cups log files + - Smaller popup in the main view + - Tooltip and What this in configuration dialog + - Saving the opened tabs list on exit + - KSystemLog Web site ! + +Minor Fixes +........... + + - KSystemlog.desktop fixed (thanks to EliasP) + - Debian Packages for Sarge, SID, and SID with KDE 3.4 + - SuSe 9.3 package (checkinstall package) + - Conectiva 10 package + - Gentoo ebuild + - Number of line added in other logs + - Many bugs fixed + - Replace old email address to new one + - Number of lines added in other logs + +KSystemLog 0.1.1 Release (05/06/2005) +------------------------------------- + +This version is a bugfixed version of KSystemLog 0.1. + + - Correctly parse internal messages of Syslog Deamon + - Fix columns problem when we switch between Kernel and System logs + - Fix parsing problem of Kernel/Boot/Authentication logs (damned Copy/Paste) + - Reimplement the Dialog Box, to have the Next and Previous features, non modal behavior... + - Improve (a little) the parsing speed of SysLog and Xorg logs. + - Add a little vertical line between each column (what an eye candy feature ;-) + - Fix KSystemLog compilation problems with KDE 3.3 (and maybe older version) + - French translation of KSystemLog + +Initial Release of KSystemLog 0.1 (05/05/2005) +---------------------------------------------- + +I am proud to announce the first public release of KSystemLog. I worked on the version since 2 months, and I hope you will like it. + +**Here is a list of features :** + + - Open multiple log files and append them to the list + - Auto display new log lines (with a bold font to better see them) + - Quick sort of each column + - Easily con figuration of each source file (to don't have any problem with different management of syslog in each distribution) + - Stop and resume the log analysis + - Copy to clipboard feature and Save to file, to help newbie sending report to developer + - X.org and all Syslog file format supported. (and many more in the next version...) + - Display advanced informations about each lines (level, date, message, user, process, host name, etc.), with a specific popup or a dialog box. + + + + +*This file use the reStructuredText format.* + +*More information at http://en.wikipedia.org/wiki/ReStructuredText* \ No newline at end of file diff --git a/ksystemlog/Mainpage.dox b/ksystemlog/Mainpage.dox new file mode 100644 index 00000000..5a283332 --- /dev/null +++ b/ksystemlog/Mainpage.dox @@ -0,0 +1,5 @@ +/** @mainpage Playground - Sysadmin + +This is the KDE playground for sysadmin tools. + +*/ diff --git a/ksystemlog/Messages.sh b/ksystemlog/Messages.sh new file mode 100755 index 00000000..00f2d91e --- /dev/null +++ b/ksystemlog/Messages.sh @@ -0,0 +1,4 @@ +#! /bin/sh +$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` >> rc.cpp +$XGETTEXT `find . -name "*.cpp" -o -name "*.h"` -o $podir/ksystemlog.pot +rm -f rc.cpp diff --git a/ksystemlog/build-package.sh b/ksystemlog/build-package.sh new file mode 100755 index 00000000..f511eab3 --- /dev/null +++ b/ksystemlog/build-package.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +APPLICATION_NAME="ksystemlog-0.4.1" + +echo $APPLICATION_NAME +PACKAGE_NAME="${APPLICATION_NAME}.tar.bz2" + +CURRENT_FOLDER=`pwd` + +echo "Building ${APPLICATION_NAME} package..." + +make clean + +mkdir -p /tmp/$APPLICATION_NAME +rm -rf /tmp/$APPLICATION_NAME/* +cp -R . /tmp/$APPLICATION_NAME + +rm -rf /tmp/$APPLICATION_NAME/tmp +rm -rf /tmp/$APPLICATION_NAME/build +rm -f /tmp/$APPLICATION_NAME/install_manifest.txt +rm -f /tmp/$APPLICATION_NAME/$PACKAGE_NAME + +find /tmp/$APPLICATION_NAME -name .svn -exec rm -rf {} \; + +for tempFile in CMakeCache.txt CMakeTmp CMakeFiles Makefile *_automoc.cpp* moc_* *.moc ui_*.h Testing cmake_install.cmake cmake_uninstall.cmake DartTestfile.txt ; do + echo "Removing $tempFile" + + find /tmp/$APPLICATION_NAME -printf "%p\n" -name $tempFile -exec rm -rf {} \; +done + +cd /tmp +tar cjf $CURRENT_FOLDER/$PACKAGE_NAME $APPLICATION_NAME +cd - + +echo "Package built : $PACKAGE_NAME." diff --git a/ksystemlog/doc/CMakeLists.txt b/ksystemlog/doc/CMakeLists.txt new file mode 100644 index 00000000..371fee41 --- /dev/null +++ b/ksystemlog/doc/CMakeLists.txt @@ -0,0 +1,3 @@ +########### install files ############### + +kde4_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en SUBDIR ksystemlog) diff --git a/ksystemlog/doc/filter-process.png b/ksystemlog/doc/filter-process.png new file mode 100644 index 0000000000000000000000000000000000000000..1c13f570b24a8b099e9ed04e49cdb76464706033 GIT binary patch literal 101472 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
oGzp!taHyu{!c6sOdri8fhh(%GxH&}jOUm$ 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 literal 0 HcmV?d00001 diff --git a/ksystemlog/doc/index.docbook b/ksystemlog/doc/index.docbook new file mode 100644 index 00000000..737ffcdb --- /dev/null +++ b/ksystemlog/doc/index.docbook @@ -0,0 +1,317 @@ + +KSystemLog"> + + + + +]> + + + + +The &ksystemlog; Handbook + + + + NicolasTernisien + +
nicolas.ternisien@gmail.com
+
+
+
+ + + + + 2008 + Nicolas Ternisien + + +&FDLNotice; + +2013-06-08 +0.4 (&kde; 4.11) + + +&ksystemlog; is a system log viewer tool for &kde;. This program is developed for beginner users +who don't know how to find information about their system or where the log files are located. +It is also designed for advanced users who want to quickly see problems occuring on their server. + + + + + + KDE + logs + ksystemlog + security + cron + boot + ssh + postfix + apache + samba + + +
+ + + Using &ksystemlog; + + + Introduction + + + What is &ksystemlog; ? + &ksystemlog; is a system log viewer tool. + + &ksystemlog; helps users understand what their machine does in the background. &ksystemlog; aims to simplify the reading of system log files. This program is developed for beginner users + who don't know how to find information about their system or where the log files are located in their computer. + + + + But it is also designed for advanced users who want to quickly see problems occuring on their server. + &ksystemlog; tries to provide some advanced features to allow sorting and reading logs from specifical programs. + + + + &ksystemlog; main screen + + + + + + &ksystemlog; main screen + + + + + + + + Features + + + In its current version, &ksystemlog; 0.4 has quite a number of helpful + features, such as: + + + + Support for many different log files type, with the support of Syslog server formating, Samba + Tabbed view to display several logs at the same time + Reading one log mode from multiple sources + Auto-display of newly logged lines in bold + Group by different criteria (log level, log file, process, hour, ...) + Detailed information for each log lines + Adding a log entry manually + + + + It supports the following log files from your system : + + + + Syslog logs (system messages) + X.org logs + Kernel logs + Authentication logs + ACPID logs + Cups logs + Postfix logs + Apache logs + Samba logs + Daemons logs + Cron logs + XSessions logs + + + + + Many other features are included and will be discussed in the appropriate chapters of this manual. + + + + + + + + + Reading logs with &ksystemlog; + + + As you will see in the following screenshots, &ksystemlog; provides features to easily sort and filter log lines. + We are now going to describe them in the next parts of this documentation. + + + + Getting started + + + When you start &ksystemlog;, by default it tries to open the most useful log, the System Log. + If it does not display it and pops up a message box, you probably forgot to launch &ksystemlog; as an administrator user (commonly named root). + The log files are usually available in the /var/log folder, which is often protected against normal users. + In the settings dialog you can select another log to open at startup. + + + + &ksystemlog; first launch + + + + + + &ksystemlog; first launch + + + + + + + Reading log files easily + + + Sorting log lines + + Every log lines are displayed in a list view, which can be sorted by clicking on the wanted column. Another click will sort it in the other order. + If you want to sort the list as it was on the first loading, you can use the EditReload menu item, or simply + click on the first column of the list, generally named Date, to sort the list in the ascending order. Even if your log mode + does not use time to describe each log lines, for example the X.org Log, the list will be correctly sorted, + because &ksystemlog; keeps an internal order of the list. + + + + + Filtering log lines + + + Filtering on a specific process + + + + + + Filtering on a specific process + + + + + + You can also use the filter bar to filter according to the selected criteria and the typed string. Simply type something in the filter, and the list will automatically + be modified to only display lines which match your filter string. By default, the combo box next to the filter selects All, which means that + a line will be displayed only if one of its columns contains the filter string. + + + + You can select another field to filter only this column of the list. For example, in the System Log, by selecting + the Process column, and writing a process name in the filter bar, &ksystemlog; will display every log lines sent by this process. + + + + + + + Other features + + + Colorizing log lines + + + This option is activated by default and helps you see which lines have a higher level than the others. For example, Error level will be highlighted in a different color than the Notice one. This feature can be disabled in the configuration dialog. + + + + + + Hiding the process identifier + + + If you are only interested in a specific process (for example in the System Log or the Cron Log), you can hide its PID + in the Process column. This can be useful if you are trying to analyze the output of a specific command, like a Samba server. In this case, please use the Filter Bar. + + + + + + Monitoring Management + + + &ksystemlog; provides an easy way to control the reading and the monitoring of log files. Indeed, sometimes you only need to analyze some existing log lines. In this case, + you cannot tolerate the appearance of a new line. You can deactivate the monitoring of log files by clicking on the Stop + button. This stops the opened log files from being updated while still letting them get filled by other processes. Of course, you can reactivate the monitoring by + clicking on Resume, which will display the log lines added since the last pause. + + + + Furthermore, to focus on the newly appeared log lines, you can toggle the Scroll to New Lines option. + + + + + Management of several opened tabs + + + &ksystemlog; allows you to open several log modes by opening multiple tabs. For this, simply use the Window menu to manage and open + new tabs. Once you have selected the right tab, simply choose the desired log mode using the dedicated menu. + + + + + + + + + + Credits and License + + &ksystemlog; + + &ksystemlog;, Copyright 2008 by Nicolas Ternisien + + + Contributors: + + Patrick Dreker : Ideas, Code Improvements. + Bojan : SSH Log mode, Printing. + + + + Special thanks to all translators of &ksystemlog;. + + + + + &underFDL; + &underGPL; + + + + + How to obtain and install &ksystemlog; + + + &ksystemlog; has its own home page http://ksystemlog.forum-software.org. + Please refer to the instructions on the home page since they will contain up-to-date information about how to obtain &ksystemlog;. + + + + &ksystemlog; can also be found in the following folder of the &kde; Git Repository : + &ksystemlog; in the &kde; repository + + + + +&documentation.index; +
+ + diff --git a/ksystemlog/doc/main-screen.png b/ksystemlog/doc/main-screen.png new file mode 100644 index 0000000000000000000000000000000000000000..9c55be5c8a46fdb2ec195ebb80986d525c7cb085 GIT binary patch literal 88483 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 literal 0 HcmV?d00001 diff --git a/ksystemlog/src/CMakeLists.txt b/ksystemlog/src/CMakeLists.txt new file mode 100644 index 00000000..cc1e2e15 --- /dev/null +++ b/ksystemlog/src/CMakeLists.txt @@ -0,0 +1,74 @@ + +########### Build ############### + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/config + ${CMAKE_CURRENT_SOURCE_DIR}/lib + ${CMAKE_CURRENT_BINARY_DIR}/config + ${CMAKE_CURRENT_BINARY_DIR}/lib + ${CMAKE_CURRENT_SOURCE_DIR} + + ${CMAKE_CURRENT_SOURCE_DIR}/modes/open + + ${CMAKE_CURRENT_SOURCE_DIR}/modes/system + ${CMAKE_CURRENT_SOURCE_DIR}/modes/kernel + ${CMAKE_CURRENT_SOURCE_DIR}/modes/daemon + ${CMAKE_CURRENT_SOURCE_DIR}/modes/xorg + ${CMAKE_CURRENT_SOURCE_DIR}/modes/cron + ${CMAKE_CURRENT_SOURCE_DIR}/modes/acpid + ${CMAKE_CURRENT_SOURCE_DIR}/modes/xsession + ${CMAKE_CURRENT_SOURCE_DIR}/modes/apache + ${CMAKE_CURRENT_SOURCE_DIR}/modes/cups + ${CMAKE_CURRENT_SOURCE_DIR}/modes/samba + ${CMAKE_CURRENT_SOURCE_DIR}/modes/authentication + ${CMAKE_CURRENT_SOURCE_DIR}/modes/postfix +) + +set(ksystemlog_sources + main.cpp + mainWindow.cpp + logModePluginsLoader.cpp + loggerDialog.cpp + detailDialog.cpp + generalConfigurationWidget.cpp + configurationDialog.cpp + tabLogViewsWidget.cpp + tabLogManager.cpp + statusBar.cpp + +) + +kde4_add_ui_files(ksystemlog_sources + detailDialogBase.ui + loggerDialogBase.ui + generalConfigurationWidgetBase.ui +) + +kde4_add_executable(ksystemlog ${ksystemlog_sources}) + +target_link_libraries(ksystemlog + ${KDE4_KIO_LIBS} + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_config + ksystemlog_open + ksystemlog_system + ksystemlog_kernel + ksystemlog_xorg + ksystemlog_cron + ksystemlog_apache + ksystemlog_authentication + ksystemlog_daemon + ksystemlog_acpid + ksystemlog_xsession + ksystemlog_postfix + ksystemlog_cups + ksystemlog_samba +) + +########### Installation ############### + +install( TARGETS ksystemlog ${INSTALL_TARGETS_DEFAULT_ARGS}) + +install(PROGRAMS ksystemlog.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) +install(FILES ksystemlogui.rc DESTINATION ${DATA_INSTALL_DIR}/ksystemlog) diff --git a/ksystemlog/src/config/CMakeLists.txt b/ksystemlog/src/config/CMakeLists.txt new file mode 100644 index 00000000..a1578960 --- /dev/null +++ b/ksystemlog/src/config/CMakeLists.txt @@ -0,0 +1,16 @@ +include_directories( + ${KDE4_INCLUDE_DIR} + ${QT_INCLUDES} +) + +set(ksystemlog_config_SRCS + dummyConfig.cpp +) + +kde4_add_kcfg_files(ksystemlog_config_SRCS ksystemlogConfig.kcfgc) + +kde4_add_library(ksystemlog_config STATIC ${ksystemlog_config_SRCS}) + +target_link_libraries(ksystemlog_config + ${KDE4_KDEUI_LIBS} +) diff --git a/ksystemlog/src/config/dummyConfig.cpp b/ksystemlog/src/config/dummyConfig.cpp new file mode 100644 index 00000000..e103ea6f --- /dev/null +++ b/ksystemlog/src/config/dummyConfig.cpp @@ -0,0 +1,20 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ diff --git a/ksystemlog/src/config/ksystemlog.kcfg b/ksystemlog/src/config/ksystemlog.kcfg new file mode 100644 index 00000000..8df3c2ae --- /dev/null +++ b/ksystemlog/src/config/ksystemlog.kcfg @@ -0,0 +1,78 @@ + + + + + + klocale.h + + + + + systemLogMode + + + + + 1000 + + + + + false + + + + + false + + + + + true + + + + + + true + + + + + true + + + + + true + + + + + KLocale::ShortDate + + + + + + + + diff --git a/ksystemlog/src/config/ksystemlogConfig.kcfgc b/ksystemlog/src/config/ksystemlogConfig.kcfgc new file mode 100644 index 00000000..6d6954d2 --- /dev/null +++ b/ksystemlog/src/config/ksystemlogConfig.kcfgc @@ -0,0 +1,4 @@ +File=ksystemlog.kcfg +ClassName=KSystemLogConfig +Singleton=true +Mutators=true diff --git a/ksystemlog/src/configurationDialog.cpp b/ksystemlog/src/configurationDialog.cpp new file mode 100644 index 00000000..6bc4534f --- /dev/null +++ b/ksystemlog/src/configurationDialog.cpp @@ -0,0 +1,200 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "configurationDialog.h" + + +#include + +#include +#include + + +#include "generalConfigurationWidget.h" + +#include "logModeConfigurationWidget.h" + +#include "mainWindow.h" + +#include "globals.h" +#include "ksystemlogConfig.h" + +#include "logging.h" +#include "defaults.h" + +class ConfigurationDialogPrivate { +public: + + GeneralConfigurationWidget* generalConfiguration; + + QList logModeConfigurations; + + bool changed; +}; + +ConfigurationDialog::ConfigurationDialog(QWidget* parent) : + KConfigDialog(parent, i18n("Settings"), KSystemLogConfig::self()), + d(new ConfigurationDialogPrivate()) + { + + d->changed = false; + + setupGeneralConfiguration(); + + setupLogModeConfigurations(); + +} + +ConfigurationDialog::~ConfigurationDialog() { + //All configuration pages are managed by KConfigDialog + delete d; +} + +void ConfigurationDialog::setupLogModeConfigurations() { + logDebug() << "Setup Log Mode Configurations..." << endl; + + + foreach(LogMode* logMode, Globals::instance()->logModes()) { + //Some Log mode does not need a configuration widget + if (logMode->logModeConfigurationWidget() == NULL) { + continue; + } + + //The configuration widget could be shared between Log Modes + if (d->logModeConfigurations.contains(logMode->logModeConfigurationWidget()) == true) { + continue; + } + + d->logModeConfigurations.append(logMode->logModeConfigurationWidget()); + } + + + foreach (LogModeConfigurationWidget* logModeConfigurationWidget, d->logModeConfigurations ) { + logDebug() << "Adding " << logModeConfigurationWidget->itemName() << " configuration..." << endl; + + addPage(logModeConfigurationWidget, logModeConfigurationWidget->itemName(), logModeConfigurationWidget->iconName(), logModeConfigurationWidget->header(), false); + + connect(logModeConfigurationWidget, SIGNAL(configurationChanged()), this, SLOT(updateConfiguration())); + } +} + +void ConfigurationDialog::showConfiguration() { + logDebug() << "Showing Configuration dialog..." << endl; + + show(); +} + +void ConfigurationDialog::setupGeneralConfiguration() { + d->generalConfiguration = new GeneralConfigurationWidget(); + + addPage(d->generalConfiguration, i18n("General"), QLatin1String( KSYSTEMLOG_ICON ), i18n("General"), false); + + connect(d->generalConfiguration, SIGNAL(configurationChanged()), this, SLOT(updateConfiguration())); +} + +void ConfigurationDialog::updateSettings() { + logDebug() << "Saving configuration..." << endl; + + d->changed = false; + + d->generalConfiguration->saveConfig(); + + foreach (LogModeConfigurationWidget* logModeConfigurationWidget, d->logModeConfigurations ) { + logModeConfigurationWidget->saveConfig(); + } + + KSystemLogConfig::self()->writeConfig(); + + emit configurationSaved(); + + logDebug() << "Configuration saved" << endl; +} + +bool ConfigurationDialog::hasChanged() { + logDebug() << "Current change status : " << d->changed << endl; + return d->changed; +} + +void ConfigurationDialog::updateConfiguration() { + logDebug() << "Updating configuration..." << endl; + + bool valid = d->generalConfiguration->isValid(); + if (valid) { + foreach (LogModeConfigurationWidget* logModeConfigurationWidget, d->logModeConfigurations ) { + if (logModeConfigurationWidget->isValid() == false) { + valid = false; + break; + } + } + } + + if (valid == true) { + enableButtonOk(true); + + updateButtons(); + } + else { + enableButtonOk(false); + enableButtonApply(false); + } + +} + +void ConfigurationDialog::updateButtons() { + logDebug() << "Updating configuration buttons..." << endl; + + d->changed = true; + +} + +void ConfigurationDialog::updateWidgets() { + logDebug() << "Reading configuration..." << endl; + + d->generalConfiguration->readConfig(); + foreach (LogModeConfigurationWidget* logModeConfigurationWidget, d->logModeConfigurations ) { + logModeConfigurationWidget->readConfig(); + } + + d->changed = false; +} + +void ConfigurationDialog::updateWidgetsDefault() { + logDebug() << "Loading default configuration..." << endl; + + d->generalConfiguration->defaultConfig(); + foreach (LogModeConfigurationWidget* logModeConfigurationWidget, d->logModeConfigurations ) { + logModeConfigurationWidget->defaultConfig(); + } + + d->changed = false; +} + +bool ConfigurationDialog::isDefault() { + /** + * TODO Set this to true and find a way to retrieve defaults value + * of the configuration (see defaultConfig() methods of LogModeConfigurations) + */ + + return true; +} + +#include "configurationDialog.moc" + diff --git a/ksystemlog/src/configurationDialog.h b/ksystemlog/src/configurationDialog.h new file mode 100644 index 00000000..807dc8e5 --- /dev/null +++ b/ksystemlog/src/configurationDialog.h @@ -0,0 +1,65 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CONFIGURATION_DIALOG_H_ +#define _CONFIGURATION_DIALOG_H_ + +#include + + +class ConfigurationDialogPrivate; + +class ConfigurationDialog: public KConfigDialog { + + Q_OBJECT + + public: + ConfigurationDialog(QWidget* parent); + + ~ConfigurationDialog(); + + void showConfiguration(); + + signals: + void configurationSaved(); + + protected slots: + + void updateWidgets(); + void updateWidgetsDefault(); + void updateSettings(); + void updateButtons(); + + private slots: + void updateConfiguration(); + + private: + bool hasChanged(); + bool isDefault(); + + ConfigurationDialogPrivate* const d; + + void setupGeneralConfiguration(); + + void setupLogModeConfigurations(); +}; + +#endif //_CONFIGURATION_DIALOG_H_ diff --git a/ksystemlog/src/detailDialog.cpp b/ksystemlog/src/detailDialog.cpp new file mode 100644 index 00000000..833a08db --- /dev/null +++ b/ksystemlog/src/detailDialog.cpp @@ -0,0 +1,135 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "detailDialog.h" + +//Qt includes + +#include +#include + +#include "logViewWidget.h" +#include "logViewWidgetItem.h" +#include "logLine.h" + +#include "logging.h" + +DetailDialog::DetailDialog(QWidget* parent) : + QDialog(parent), + logViewWidget(NULL) { + + setupUi(this); + + previous->setText(i18n("&Previous")); + previous->setIcon(KIcon( QLatin1String( "arrow-up" ))); + connect(previous, SIGNAL(clicked()), this, SLOT(previousItem())); + + next->setText(i18n("&Next")); + next->setIcon(KIcon( QLatin1String( "arrow-down" ))); + connect(next, SIGNAL(clicked()), this, SLOT(nextItem())); + + closeButton->setText(KStandardGuiItem::close().text()); + closeButton->setIcon(KStandardGuiItem::close().icon()); + connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); + +} + +DetailDialog::~DetailDialog() { + +} + +void DetailDialog::selectionChanged(LogViewWidget* logViewWidget) { + this->logViewWidget=logViewWidget; + + updateDetails(); +} + +//TODO Try to find a method that reload (an resize) correctly the content of the detail dialog +void DetailDialog::updateDetails() { + //logDebug() << "Updating Detail Dialog..." << endl; + + //Get the current-last item selected + LogViewWidgetItem* item=logViewWidget->lastSelectedItem(); + if (item==NULL) { + logDebug() << "No item found." << endl; + return; + } + + icon->setPixmap(DesktopIcon(item->logLine()->logLevel()->icon())); + + header->setText(item->logLine()->formattedText()); + + message->setText(item->logLine()->logItems().last()); + + if (logViewWidget->topLevelItem(logViewWidget->indexOfTopLevelItem(item) - 1)==NULL) + previous->setEnabled(false); + else + previous->setEnabled(true); + + if (logViewWidget->topLevelItem(logViewWidget->indexOfTopLevelItem(item) + 1 )==NULL) + next->setEnabled(false); + else + next->setEnabled(true); + + /* + header->adjustSize(); + this->adjustSize(); + */ +} + +void DetailDialog::moveToItem(int direction) { + if (direction < 0) + logDebug() << "Go to previous item..." << endl; + else + logDebug() << "Go to next item..." << endl; + + //Get the current-last item selected + LogViewWidgetItem* item=logViewWidget->lastSelectedItem(); + if (item==NULL) { + logDebug() << "No item found." << endl; + return; + } + + QTreeWidgetItem* destinationItem = logViewWidget->topLevelItem( logViewWidget->indexOfTopLevelItem(item) + direction ); + if (destinationItem==NULL) { + if (direction < 0) + logDebug() << "No previous item found." << endl; + else + logDebug() << "No next item found." << endl; + return; + } + + logViewWidget->setItemSelected(item, false); + logViewWidget->setItemSelected(destinationItem, true); + logViewWidget->scrollToItem(destinationItem); + + updateDetails(); +} + +void DetailDialog::previousItem() { + moveToItem(-1); +} + +void DetailDialog::nextItem() { + moveToItem(1); +} + +#include "detailDialog.moc" diff --git a/ksystemlog/src/detailDialog.h b/ksystemlog/src/detailDialog.h new file mode 100644 index 00000000..d135a88f --- /dev/null +++ b/ksystemlog/src/detailDialog.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _DETAIL_DIALOG_H_ +#define _DETAIL_DIALOG_H_ + +#include + +class LogViewWidget; + +#include "ui_detailDialogBase.h" + +class DetailDialog : public QDialog, public Ui::DetailDialogBase { + + Q_OBJECT + + public: + explicit DetailDialog(QWidget *parent); + + ~DetailDialog(); + + public slots: + void selectionChanged(LogViewWidget* logViewWidget); + + private slots: + void previousItem(); + void nextItem(); + + private: + void updateDetails(); + + /** + * Method that replaces the bugged itemAbove() and itemBelow() methods + */ + void moveToItem(int direction); + + LogViewWidget* logViewWidget; + +}; + +#endif //_DETAIL_DIALOG_H_ diff --git a/ksystemlog/src/detailDialogBase.ui b/ksystemlog/src/detailDialogBase.ui new file mode 100644 index 00000000..8dd164ec --- /dev/null +++ b/ksystemlog/src/detailDialogBase.ui @@ -0,0 +1,190 @@ + + DetailDialogBase + + + + 0 + 0 + 411 + 248 + + + + Log Line Details + + + This dialog displays detailed information about the currently selected log line. + + + + + + + 0 + 0 + + + + Message + + + true + + + + + + + + 0 + 0 + + + + Icon + + + false + + + + + + + + 2 + 0 + + + + Main information + + + false + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Move to the previous line + + + Moves to the previous line. This button is deactivated if there is no previous log line. + + + &Back + + + 14 + + + + + + + + 0 + 0 + + + + Move to the next line + + + Moves to the next line. This button is deactivated if there is no next log line. + + + &Forward + + + 15 + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 100 + 31 + + + + + + + + + 0 + 0 + + + + Close the Detail dialog. + + + Closes this Detail dialog. + + + &Close + + + 13 + + + + + + + + + + KTextEdit + QWidget +

ktextedit.h
+ + + KPushButton + QWidget +
kpushbutton.h
+
+ + + message + + + ktextedit.h + kpushbutton.h + kpushbutton.h + kpushbutton.h + + + + diff --git a/ksystemlog/src/generalConfigurationWidget.cpp b/ksystemlog/src/generalConfigurationWidget.cpp new file mode 100644 index 00000000..fcfd3fa9 --- /dev/null +++ b/ksystemlog/src/generalConfigurationWidget.cpp @@ -0,0 +1,149 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "generalConfigurationWidget.h" + + +#include +#include +#include + +#include +#include +#include + +#include "logging.h" +#include "defaults.h" +#include "globals.h" +#include "ksystemlogConfig.h" + +class GeneralConfigurationWidgetPrivate { +public: + QButtonGroup* dateFormatGroup; +}; + +GeneralConfigurationWidget::GeneralConfigurationWidget() : + QWidget(), + d(new GeneralConfigurationWidgetPrivate()) + { + + setupUi(this); + + startupLogMode->addItem(KIcon( QLatin1String( NO_MODE_ICON) ), i18n("No Log Mode"), QVariant(QLatin1String( "" ) )); + foreach(LogMode* logMode, Globals::instance()->logModes()) { + //Ignore this special case + if (logMode->id() == QLatin1String( "openLogMode" )) + continue; + + startupLogMode->addItem(KIcon(logMode->icon()), logMode->name(), QVariant(logMode->id())); + } + + connect(startupLogMode, SIGNAL(currentIndexChanged(int)), this, SIGNAL(configurationChanged())); + + connect(maxLines, SIGNAL(valueChanged(int)), this, SIGNAL(configurationChanged())); + + connect(deleteDuplicatedLines, SIGNAL(clicked()), this, SIGNAL(configurationChanged())); + + connect(deleteProcessId, SIGNAL(clicked()), this, SIGNAL(configurationChanged())); + + connect(colorizeLogLines, SIGNAL(clicked()), this, SIGNAL(configurationChanged())); + + d->dateFormatGroup = new QButtonGroup(this); + d->dateFormatGroup->addButton(formatShortDate, KLocale::ShortDate); + d->dateFormatGroup->addButton(formatLongDate, KLocale::LongDate); + d->dateFormatGroup->addButton(formatFancyShortDate, KLocale::FancyShortDate); + d->dateFormatGroup->addButton(formatFancyLongDate, KLocale::FancyLongDate); + + connect(d->dateFormatGroup, SIGNAL(buttonClicked(int)), this, SIGNAL(configurationChanged())); + + addDateFormatExample(); +} + + +GeneralConfigurationWidget::~GeneralConfigurationWidget() { + //dateFormatGroup is automatically deleted by Qt + + delete d; +} + +void GeneralConfigurationWidget::addDateFormatExample() { + foreach(QAbstractButton* button, d->dateFormatGroup->buttons()) { + QDateTime currentDateTime(QDateTime::currentDateTime()); + + KLocale::DateFormat currentButtonFormat = (KLocale::DateFormat) d->dateFormatGroup->id(button); + + QString formattedDate = KGlobal::locale()->formatDateTime(currentDateTime, currentButtonFormat, true); + + button->setText( i18nc("Date format Option (Date example)", "%1 (%2)", button->text(), formattedDate) ); + } +} + +void GeneralConfigurationWidget::readConfig() { + for (int i=0; icount(); ++i) { + if (KSystemLogConfig::startupLogMode() == startupLogMode->itemData(i)) { + startupLogMode->setCurrentIndex(i); + break; + } + } + + maxLines->setValue(KSystemLogConfig::maxLines()); + + deleteDuplicatedLines->setChecked(KSystemLogConfig::deleteDuplicatedLines()); + + deleteProcessId->setChecked(KSystemLogConfig::deleteProcessIdentifier()); + + colorizeLogLines->setChecked(KSystemLogConfig::colorizeLogLines()); + + KLocale::DateFormat dateFormat = (KLocale::DateFormat) KSystemLogConfig::dateFormat(); + QAbstractButton* selectedButton = d->dateFormatGroup->button(dateFormat); + selectedButton->setChecked(true); +} + +void GeneralConfigurationWidget::saveConfig() const { + logDebug() << "Save config from General preferences" << endl; + + KSystemLogConfig::setStartupLogMode(startupLogMode->itemData(startupLogMode->currentIndex()).toString()); + + KSystemLogConfig::setMaxLines(maxLines->value()); + KSystemLogConfig::setDeleteDuplicatedLines(deleteDuplicatedLines->isChecked()); + KSystemLogConfig::setDeleteProcessIdentifier(deleteProcessId->isChecked()); + KSystemLogConfig::setColorizeLogLines(colorizeLogLines->isChecked()); + + KSystemLogConfig::setDateFormat(d->dateFormatGroup->checkedId()); + +} + +void GeneralConfigurationWidget::defaultConfig() { + //TODO Find a way to read the configuration per default + readConfig(); +} + +bool GeneralConfigurationWidget::isValid() const { + if (maxLines->value()>0) { + logDebug() << "General configuration valid" << endl; + return true; + } + + logDebug() << "General configuration not valid" << endl; + return false; +} + +#include "generalConfigurationWidget.moc" diff --git a/ksystemlog/src/generalConfigurationWidget.h b/ksystemlog/src/generalConfigurationWidget.h new file mode 100644 index 00000000..51127f81 --- /dev/null +++ b/ksystemlog/src/generalConfigurationWidget.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _GENERAL_CONFIGURATION_WIDGET_H_ +#define _GENERAL_CONFIGURATION_WIDGET_H_ + +#include + +#include "ui_generalConfigurationWidgetBase.h" + +class GeneralConfigurationWidgetPrivate; + +class GeneralConfigurationWidget : public QWidget, public Ui::GeneralConfigurationWidgetBase { + + Q_OBJECT + + public: + GeneralConfigurationWidget(); + + ~GeneralConfigurationWidget(); + + bool isValid() const; + + public slots: + void saveConfig() const; + + void defaultConfig(); + + void readConfig(); + + signals: + void configurationChanged(); + + private: + void addDateFormatExample(); + + GeneralConfigurationWidgetPrivate* const d; +}; + +#endif // _GENERAL_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/generalConfigurationWidgetBase.ui b/ksystemlog/src/generalConfigurationWidgetBase.ui new file mode 100644 index 00000000..79f35074 --- /dev/null +++ b/ksystemlog/src/generalConfigurationWidgetBase.ui @@ -0,0 +1,203 @@ + + Nicolas Ternisien + GeneralConfigurationWidgetBase + + + + 0 + 0 + 368 + 497 + + + + + + + Startup + + + + + + Load this log mode at startup: + + + startupLogMode + + + + + + + The log mode which is loaded by default at startup + + + The log mode loaded by default at startup. Choose 'No Log Mode' if you do not want this to happen. + + + + + + + + + + Log Lines List + + + + + + + + Maximum lines displayed: + + + maxLines + + + + + + + + 0 + 0 + + + + Choose here the maximum number of log lines displayed in the main view. + + + You can choose here the maximum number of log lines displayed in the main view. + + + 10 + + + 30000 + + + 10 + + + + + + + + + Select this option if you want to delete duplicate log lines <b>(may be slow)</b>. + + + Select this option if you want to delete duplicate log lines. <b>This option can slow log reading</b>. + + + Remove &duplicate log lines (may be slower) + + + + + + + + + + Options + + + + + + Remove process identifier from process name. + + + Select this option if you want to remove the process identifier from the process name. For example, the <b>Process</b> column may contain entries such as <i>cron<b>[3433]</b></i>. If this option is activated, the bold part will be removed. + + + Remove &identifier from process name + + + + + + + This option allows log lines to be colored depending on their log level. + + + This option allows log lines to be colored depending on their log level. For example, errors will be shown in red, warnings in orange, and so on. This will help you to see problems more easily. + + + &Colored log lines + + + + + + + + + + Date Format + + + + + + + + + &Short date format + + + + + + + &Fancy short date format + + + + + + + &Long date format + + + + + + + Fanc&y long date format + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + KIntSpinBox + QSpinBox +
knuminput.h
+
+
+ + +
diff --git a/ksystemlog/src/ksystemlog.desktop b/ksystemlog/src/ksystemlog.desktop new file mode 100755 index 00000000..6c845fbc --- /dev/null +++ b/ksystemlog/src/ksystemlog.desktop @@ -0,0 +1,174 @@ +[Desktop Entry] +Name=KSystemLog +Name[ast]=KSystemLog +Name[bg]=Системни дневници +Name[bs]=KSystemLog +Name[ca]=KSystemLog +Name[ca@valencia]=KSystemLog +Name[cs]=KSystemLog +Name[da]=KSystemLog +Name[de]=KSystemLog +Name[el]=KSystemLog +Name[en_GB]=KSystemLog +Name[es]=KSystemLog +Name[et]=KSystemLog +Name[eu]=KSystemLog +Name[fi]=KSystemLog +Name[fr]=KSystemLog +Name[ga]=KSystemLog +Name[gl]=KSystemLog +Name[hne]=के-सिस्टम-लाग +Name[hr]=KSystemLog +Name[hu]=Rendszernapló-megjelenítő +Name[ia]=KSystemLog +Name[id]=KSystemLog +Name[is]=KSystemLog +Name[it]=KSystemLog +Name[ja]=KSystemLog +Name[kk]=KSystemLog +Name[ko]=KSystemLog +Name[lt]=KSystemLog +Name[lv]=KSystemLog +Name[ml]=കെസിസ്റ്റംലോഗ് +Name[mr]=के-सिस्टीम-लॉग +Name[nb]=KSystemLog +Name[nds]=KSystemLog +Name[nl]=KSystemLog +Name[nn]=KSystemlogg +Name[pa]=ਕੇ-ਸਿਸਟਮ-ਲਾਗ +Name[pl]=KSystemLog +Name[pt]=KSystemLog +Name[pt_BR]=KSystemLog +Name[ro]=KSystemLog +Name[ru]=KSystemLog +Name[sk]=KSystemLog +Name[sl]=KSystemLog +Name[sq]=KSystemLog +Name[sr]=К‑системски-дневник +Name[sr@ijekavian]=К‑системски-дневник +Name[sr@ijekavianlatin]=K‑sistemski-dnevnik +Name[sr@latin]=K‑sistemski-dnevnik +Name[sv]=Ksystemlog +Name[th]=ปูมบันทึกระบบ +Name[tr]=KSystemLog +Name[ug]=KSystemLog +Name[uk]=KSystemLog +Name[x-test]=xxKSystemLogxx +Name[zh_CN]=KSystemLog +Name[zh_TW]=KSystemLog +Categories=Qt;KDE;System;Monitor; +X-KDE-SubstituteUID=true +Exec=ksystemlog %i -caption %c +Icon=utilities-log-viewer +Type=Application +X-DocPath=ksystemlog/index.html +Comment=System log viewer tool +Comment[ast]=Ferramienta de visor de rexistru de sistema +Comment[bg]=Инструмент за преглеждане на системните дневници +Comment[bs]=Alat za pregled sistemski zabilješki +Comment[ca]=Eina de visualització dels registres del sistema +Comment[ca@valencia]=Eina de visualització dels registres del sistema +Comment[cs]=Nástroj na prohlížení záznamů systému +Comment[da]=Fremviserværktøj til systemlog +Comment[de]=Betrachter für Systemprotokolle +Comment[el]=Εργαλείο εμφάνισης της καταγραφής του συστήματος +Comment[en_GB]=System log viewer tool +Comment[es]=Herramienta de visualización de registros del sistema +Comment[et]=Süsteemilogi näitaja +Comment[eu]=Sistema egunkariak erakusteko tresna +Comment[fi]=Järjestelmälokien katselin +Comment[fr]=Outil d'affichage des journaux du système +Comment[ga]=Amharcán ar logchomhaid an chórais +Comment[gl]=Visor dos rexistros do sistema +Comment[hne]=तंत्र लाग प्रदर्सक औजार +Comment[hr]=Alat za prikaz dnevnika sustava +Comment[hu]=Rendszernapló-megjelenítő eszköz +Comment[ia]=Instrumento pro vider registro de systema +Comment[id]=Alat pembaca Catatan sistem +Comment[is]=Tól til að skoða kerfisannála (system logs) +Comment[it]=Strumento di visione dei registri di sistema +Comment[ja]=システムログビューア +Comment[kk]=Жүйелік журналын қарау құралы +Comment[ko]=시스템 로그 뷰어 도구 +Comment[lt]=Sistemos žurnalų žiūryklė +Comment[lv]=Sistēmas žurnāla skatīšanas rīks +Comment[ml]=സിസ്റ്റം ലോഗ് കാണാന്നുള്ള ടൂള്‍ +Comment[mr]=प्रणाली लॉग प्रदर्शक साधन +Comment[nb]=Verktøy for å vise systemlogger +Comment[nds]=Kieker för Systeem-Logböker +Comment[nl]=Systeemlogs bekijken +Comment[nn]=Verktøy for vising av systemloggar +Comment[pa]=ਸਿਸਟਮ ਲਾਗ ਦਰਸ਼ਕ ਟੂਲ +Comment[pl]=Narzędzie do przeglądania dziennika systemowego +Comment[pt]=Ferramenta de visualização de registos do sistema +Comment[pt_BR]=Ferramenta de visualização de registros do sistema +Comment[ro]=Unealtă de vizualizare a jurnalelor de sistem +Comment[ru]=Программа просмотра системных журналов +Comment[sk]=Nástroj na prehliadanie záznamov systému +Comment[sl]=Orodje za pregledovanje sistemskih dnevnikov +Comment[sq]=Mjeti shfaqës i hyrjeve në sistem +Comment[sr]=Приказивач системских дневника +Comment[sr@ijekavian]=Приказивач системских дневника +Comment[sr@ijekavianlatin]=Prikazivač sistemskih dnevnika +Comment[sr@latin]=Prikazivač sistemskih dnevnika +Comment[sv]=Visningsverktyg för systemloggar +Comment[th]=เครื่องมือแสดงปูมบันทึกของระบบ +Comment[tr]=Sistem günlük kayıtlarını gösterme aracı +Comment[ug]=سىستېما خاتىرىسىنى كۆرۈش قورالى +Comment[uk]=Засіб перегляду системних журналів +Comment[x-test]=xxSystem log viewer toolxx +Comment[zh_CN]=系统日志查看工具 +Comment[zh_TW]=系統紀錄檢視工具 +Terminal=false +GenericName=System Log Viewer +GenericName[bg]=Преглед на системните дневници +GenericName[bs]=Preglednik sistemskih bilješki +GenericName[ca]=Visor dels registres del sistema +GenericName[ca@valencia]=Visor dels registres del sistema +GenericName[cs]=Prohlížeč záznamů systému +GenericName[da]=Systemlogfremviser +GenericName[de]=Systemprotokoll-Betrachter +GenericName[el]=Εμφάνιση καταγραφών του συστήματος +GenericName[en_GB]=System Log Viewer +GenericName[es]=Visor de registros del sistema +GenericName[et]=Süsteemi logide jälgija +GenericName[eu]=Sistema egunkarien erakuslea +GenericName[fi]=Järjestelmän lokitiedostojen katselin +GenericName[fr]=Afficheur des journaux du système +GenericName[ga]=Amharcán ar Logchomhaid an Chórais +GenericName[gl]=Visor dos rexistros do sistema +GenericName[hr]=Preglednik sistemskih zapisa +GenericName[hu]=Rendszernapló-megjelenítő +GenericName[ia]=Visor de registro de systema +GenericName[id]=Penampil Catatan Sistem +GenericName[is]=Kerfisannálabirtir +GenericName[it]=Visore dei registri di sistema +GenericName[ja]=システムログビューア +GenericName[kk]=Жүйелік журналын қарау +GenericName[ko]=시스템 로그 뷰어 +GenericName[lt]=Sistemos žurnalų žiūryklė +GenericName[mr]=प्रणाली लॉग प्रदर्शक +GenericName[nb]=Systemloggviser +GenericName[nds]=Kieker för't Systeemlogbook +GenericName[nl]=Systeemlog-weergaveprogramma +GenericName[nn]=Systemloggvisar +GenericName[pa]=ਸਿਸਟਮ ਲਾਗ ਦਰਸ਼ਕ +GenericName[pl]=Przeglądarka dziennika systemowego +GenericName[pt]=Visualizador de Registos do Sistema +GenericName[pt_BR]=Visualizador do registros do sistema +GenericName[ro]=Vizualizator jurnale de sistem +GenericName[ru]=Просмотр системных журналов +GenericName[sk]=Prehliadač záznamov systému +GenericName[sl]=Pregledovalnik sistemskih dnevnikov +GenericName[sr]=Приказивач системског дневника +GenericName[sr@ijekavian]=Приказивач системског дневника +GenericName[sr@ijekavianlatin]=Prikazivač sistemskog dnevnika +GenericName[sr@latin]=Prikazivač sistemskog dnevnika +GenericName[sv]=Visning av systemloggar +GenericName[th]=แสดงปูมบันทึกของระบบ +GenericName[tr]=Sistem Günlüğü İzleyici +GenericName[ug]=سىستېما خاتىرىسى كۆرگۈچ +GenericName[uk]=Переглядач системних журналів +GenericName[x-test]=xxSystem Log Viewerxx +GenericName[zh_CN]=系统日志查看器 +GenericName[zh_TW]=系統紀錄檔檢視器 diff --git a/ksystemlog/src/ksystemlog.lsm b/ksystemlog/src/ksystemlog.lsm new file mode 100644 index 00000000..a9bdfe6a --- /dev/null +++ b/ksystemlog/src/ksystemlog.lsm @@ -0,0 +1,16 @@ +Begin3 +Title: KSystemLog -- System Log Viewer Tool +Version: 0.1.1 +Entered-date: +Description: System Log Viewer Tool for KDE +Keywords: KDE Qt System Log +Author: Nicolas Ternisien +Maintained-by: Nicolas Ternisien +Home-page: http://www.kde-apps.org/content/show.php?content=23761 +Alternate-site: +Primary-site: http://annivernet.free.fr/images/divers/ksystemlog/ + 0.1.1 ksystemlog-0.1.1.tar.gz + 0.1.1 ksystemlog-0.1.1.lsm +Platform: Linux. Needs KDE +Copying-policy: GPL +End diff --git a/ksystemlog/src/ksystemlogui.rc b/ksystemlog/src/ksystemlogui.rc new file mode 100644 index 00000000..777a672b --- /dev/null +++ b/ksystemlog/src/ksystemlogui.rc @@ -0,0 +1,70 @@ + + + + + + + + + + + + + &Edit + + + + + + + + + + + + + + Logs + + + + + &Window + + + + + + + + + + + + + + + + + + + Logs Toolbar + + + + + Main Toolbar + + + + + + + + + + diff --git a/ksystemlog/src/lib/CMakeLists.txt b/ksystemlog/src/lib/CMakeLists.txt new file mode 100644 index 00000000..561e69cf --- /dev/null +++ b/ksystemlog/src/lib/CMakeLists.txt @@ -0,0 +1,55 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_BINARY_DIR}/../config +) + +set(ksystemlog_lib_sources + analyzer.cpp + logModeFactory.cpp + logModeConfiguration.cpp + logModeConfigurationWidget.cpp + logModeItemBuilder.cpp + logManager.cpp + view.cpp + globals.cpp + logMode.cpp + logLevel.cpp + logFile.cpp + logFileReader.cpp + localLogFileReader.cpp + processOutputLogFileReader.cpp + kioLogFileReader.cpp + logLine.cpp + logViewModel.cpp + logViewColumn.cpp + logViewColumns.cpp + logViewWidgetItem.cpp + logViewWidget.cpp + logViewExport.cpp + logViewFilterWidget.cpp + logViewSearchWidget.cpp + loadingBar.cpp + logModeAction.cpp + simpleAction.cpp + multipleActions.cpp + + levelPrintPage.cpp + +) + +kde4_add_ui_files(ksystemlog_lib_sources logViewSearchWidgetBase.ui) + +kde4_add_library(ksystemlog_lib STATIC ${ksystemlog_lib_sources}) + +add_dependencies( + ksystemlog_lib + ksystemlog_config +) + +target_link_libraries(ksystemlog_lib + ${KDE4_KDEUI_LIBS} + ${KDE4_KIO_LIBS} + ksystemlog_config +) diff --git a/ksystemlog/src/lib/analyzer.cpp b/ksystemlog/src/lib/analyzer.cpp new file mode 100644 index 00000000..fc738d62 --- /dev/null +++ b/ksystemlog/src/lib/analyzer.cpp @@ -0,0 +1,256 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "analyzer.h" + +#include + +#include "logging.h" +#include "ksystemlogConfig.h" + +#include "logViewModel.h" + +#include "logMode.h" +#include "logFileReader.h" + +#include "logFile.h" + +Analyzer::Analyzer(LogMode* logMode) : + QObject(NULL), + logViewModel(NULL), + logMode(logMode), + logLineInternalIdGenerator(0) { + + parsingPaused = false; + + insertionLocking = new QMutex(QMutex::Recursive); +} + +Analyzer::~Analyzer() { + deleteLogFiles(); + + //logMode is managed by Globals + //logViewModel is managed by LogViewWidget +} + +void Analyzer::watchLogFiles(bool enabled) { + //Enable the log file watching, by revert order to read the most top file at last, and be sure its line will be kept + + QListIterator it(logFileReaders); + it.toBack(); + while (it.hasPrevious()) { + LogFileReader* logFileReader = it.previous(); + logFileReader->watchFile(enabled); + } + +} + +void Analyzer::deleteLogFiles() { + watchLogFiles(false); + + // Remove the watching on the monitored files + foreach (LogFileReader* logFileReader, logFileReaders) { + logDebug() << "Remove file : " << logFileReader->logFile().url().path() << endl; + delete logFileReader; + } + + logFileReaders.clear(); +} + +bool Analyzer::isParsingPaused() const { + return parsingPaused; +} + +void Analyzer::setParsingPaused(bool paused) { + parsingPaused = paused; + + bool watching; + //If we resume the parsing, then parse files to know if new lines have been appended + if (parsingPaused==true) { + logDebug() << "Pausing reading" << endl; + watching=false; + } + else { + logDebug() << "Relaunch reading" << endl; + watching=true; + } + + watchLogFiles(watching); + +} + +void Analyzer::setLogViewModel(LogViewModel* logViewModel) { + this->logViewModel = logViewModel; +} + +void Analyzer::setLogFiles(const QList& logFiles) { + //Remove previous files + deleteLogFiles(); + + foreach(const LogFile &logFile, logFiles) { + LogFileReader* logFileReader = createLogFileReader(logFile); + logFileReaders.append(logFileReader); + + connect(logFileReader, SIGNAL(contentChanged(LogFileReader*,Analyzer::ReadingMode,QStringList)), this, SLOT(logFileChanged(LogFileReader*,Analyzer::ReadingMode,QStringList))); + connect(logFileReader, SIGNAL(statusBarChanged(QString)), this, SIGNAL(statusBarChanged(QString))); + connect(logFileReader, SIGNAL(errorOccured(QString,QString)), this, SIGNAL(errorOccured(QString,QString))); + } +} + +void Analyzer::logFileChanged(LogFileReader* logFileReader, ReadingMode readingMode, const QStringList& content) { + if (readingMode == Analyzer::FullRead) + logDebug() << "File " << logFileReader->logFile().url().path() << " has been modified on full read." << endl; + else + logDebug() << "File " << logFileReader->logFile().url().path() << " has been modified on partial read" << endl; + + if (parsingPaused == true) { + logDebug() << "Pause enabled. Nothing read." << endl; + return; + } + + logDebug() << "Locking file modifications of " << logFileReader->logFile().url().path() << endl; + insertionLocking->lock(); + logDebug() << "Unlocking file modifications of " << logFileReader->logFile().url().path() << endl; + + QTime benchmark; + benchmark.start(); + + int insertedLogLineCount; + + logViewModel->startingMultipleInsertions(readingMode); + + if (readingMode == Analyzer::UpdatingRead) { + insertedLogLineCount = insertLines(content, logFileReader->logFile(), Analyzer::UpdatingRead); + } + else { + logDebug() << "Reading file "<< logFileReader->logFile().url().path() << endl; + + emit statusBarChanged( i18n("Opening '%1'...", logFileReader->logFile().url().path()) ); + + //Inform that we are now reading the "index" file + emit readFileStarted(*logMode, logFileReader->logFile(), logFileReaders.count() - logFileReaders.indexOf(logFileReader), logFileReaders.count()); + + insertedLogLineCount = insertLines(content, logFileReader->logFile(), Analyzer::FullRead); + + emit statusBarChanged( i18n("Log file '%1' loaded successfully.", logFileReader->logFile().url().path()) ); + + + } + + logViewModel->endingMultipleInsertions(readingMode, insertedLogLineCount); + + //Inform connected LoadingBar that the reading is now finished + emit readEnded(); + + //Inform LogManager that new lines have been added + emit logUpdated(insertedLogLineCount); + + //Inform MainWindow status bar + emit statusBarChanged( i18n("Log file '%1' has changed.", logFileReader->logFile().url().path()) ); + + logDebug() << "Updating log files in " << benchmark.elapsed() << " ms" << endl; + + insertionLocking->unlock(); +} + +int Analyzer::insertLines(const QStringList& bufferedLines, const LogFile& logFile, ReadingMode readingMode) { + logDebug() << "Inserting lines..." << endl; + + //If there is no line + if (bufferedLines.size()==0) { + logWarning() << "File is empty : " << logFile.url().path() << endl; + } + + + + int stop = 0; + int currentPosition = 0; + + QListIterator it(bufferedLines); + /** + * If the log file is sorted, then we can ignore the first lines + * if there are more lines in the log file than the max lines + * + * TODO Read revertly the file and ignore last lines if we are in Descending mode + */ + logDebug() << "Log file Sort mode is " << logFileSortMode() << endl; + if (logFileSortMode() == Analyzer::AscendingSortedLogFile) { + //Calculate how many lines we will ignore + if (bufferedLines.size() > KSystemLogConfig::maxLines()) { + stop=bufferedLines.size() - KSystemLogConfig::maxLines(); + } + + //Ignore those lines + while (currentPosition < stop) { + it.next(); + ++currentPosition; + } + } + + int insertedLogLineCount = 0; + while (currentPosition < bufferedLines.size()) { + QString buffer(it.next()); + + bool inserted = insertLine(buffer, logFile, readingMode); + if (inserted) { + insertedLogLineCount++; + } + + if (readingMode == Analyzer::FullRead) { + informOpeningProgress(currentPosition, (bufferedLines.size()-1) - stop); + } + + ++currentPosition; + } + + logDebug() << "Total read lines :"<< (bufferedLines.size() - stop) << "(" << logFile.url().path() << ")" << endl; + + return insertedLogLineCount; +} + +bool Analyzer::insertLine(const QString& buffer, const LogFile& originalFile, ReadingMode readingMode) { + LogLine* line=parseMessage(buffer, originalFile); + + //Invalid log line + if (line==NULL) { + return false; + } + + //On full reading, it is not needed to display the recent status + if (readingMode == Analyzer::FullRead) { + line->setRecent(false); + } + + return logViewModel->insertNewLogLine(line); +} + +inline void Analyzer::informOpeningProgress(int currentPosition, int total) { + int each=total / 100; + if (each==0) { + return; + } + + if (currentPosition%each==0) { + emit openingProgressed(); + } +} + +#include "analyzer.moc" diff --git a/ksystemlog/src/lib/analyzer.h b/ksystemlog/src/lib/analyzer.h new file mode 100644 index 00000000..3475e121 --- /dev/null +++ b/ksystemlog/src/lib/analyzer.h @@ -0,0 +1,133 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _ANALYZER_H_ +#define _ANALYZER_H_ + +#include +#include +#include +#include + +#include + +#include "globals.h" + +#include "logLine.h" + +#include "logFile.h" +#include "logViewColumn.h" +#include "logViewColumns.h" + +class LogViewModel; +class LogFileReader; +class LogMode; + +class Analyzer : public QObject { + + Q_OBJECT + + public: + enum ReadingMode { + UpdatingRead, + FullRead + }; + Q_DECLARE_FLAGS(ReadingModes, ReadingMode) + + enum LogFileSortMode { + AscendingSortedLogFile, + FilteredLogFile, + UnsortedLogFile + }; + Q_DECLARE_FLAGS(LogFileSortModes, LogFileSortMode) + + explicit Analyzer(LogMode* logMode); + + ~Analyzer(); + + void watchLogFiles(bool enabled); + + + void setLogFiles(const QList& logFiles); + void setLogViewModel(LogViewModel* logViewModel); + + bool isParsingPaused() const; + + virtual LogViewColumns initColumns() = 0; + + public slots: + void setParsingPaused(bool paused); + + protected: + virtual LogFileReader* createLogFileReader(const LogFile& logFile) = 0; + virtual Analyzer::LogFileSortMode logFileSortMode() = 0; + + virtual LogLine* parseMessage(const QString& logLine, const LogFile& originalFile) = 0; + + private: + + inline void informOpeningProgress(int currentPosition, int total); + + void deleteLogFiles(); + + /** + * Parse and insert the buffered lines in the model + * Returns the count of inserted lines + */ + int insertLines(const QStringList& bufferedLines, const LogFile& logFile, ReadingMode readingMode); + + /** + * Parse and insert a line in the model + * Returns false if it was not inserted, true if it was + */ + bool insertLine(const QString& buffer, const LogFile& originalFile, ReadingMode readingMode); + + private slots: + void logFileChanged(LogFileReader* logFileReader, Analyzer::ReadingMode readingMode, const QStringList& content); + + signals: + void statusBarChanged(const QString& message); + void errorOccured(const QString& title, const QString& message); + + void openingProgressed(); + + void logUpdated(int lineTotal); + + void readFileStarted(const LogMode& logMode, const LogFile& logFile, int fileIndex, int fileCount); + void readEnded(); + + protected: + //TODO Move those members to a D-pointer (and think about subclasses accesses) + bool parsingPaused; + + LogViewModel* logViewModel; + + LogMode* logMode; + + QList logFileReaders; + + QMutex* insertionLocking; + + long logLineInternalIdGenerator; + +}; + +#endif diff --git a/ksystemlog/src/lib/defaults.h b/ksystemlog/src/lib/defaults.h new file mode 100644 index 00000000..05666eaf --- /dev/null +++ b/ksystemlog/src/lib/defaults.h @@ -0,0 +1,77 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _DEFAULTS_H_ +#define _DEFAULTS_H_ + +#define DEFAULT_LOG_FOLDER "/var/log" + +/** + * Icon name of KSystemLog + */ +#define KSYSTEMLOG_ICON "utilities-log-viewer" + +enum LogLevelIds { + NONE_LOG_LEVEL_ID=0, + DEBUG_LOG_LEVEL_ID, + INFORMATION_LOG_LEVEL_ID, + NOTICE_LOG_LEVEL_ID, + WARNING_LOG_LEVEL_ID, + ERROR_LOG_LEVEL_ID, + CRITICAL_LOG_LEVEL_ID, + ALERT_LOG_LEVEL_ID, + EMERGENCY_LOG_LEVEL_ID +}; + +//TODO Need a rewriting +/* +enum GroupByType { + NO_GROUP_BY=0, + GROUP_BY_LOG_LEVEL, + GROUP_BY_DAY, + GROUP_BY_HOUR, + GROUP_BY_LOG_FILE, + GROUP_BY_COLUMN +}; + +//This variable only counts GROUP_BY methods != than GROUP_BY_COLUMN +#define DEFAULT_GROUP_BY_COUNT 5 + +#define NO_GROUP_BY_ICON "process-stop" +#define GROUP_BY_LOG_LEVEL_ICON INFORMATION_LOG_LEVEL_ICON +#define GROUP_BY_DAY_ICON "go-jump-today" +#define GROUP_BY_HOUR_ICON "chronometer" +#define GROUP_BY_LOG_FILE_ICON "utilities-log-viewer" + +#define GROUP_BY_COLUMN_ICON "view-list-text" + + +//Icon of the Group By action +#define GROUP_BY_ICON "view-list-tree" +*/ + +/** + * Icon name of No Mode icon + */ +#define NO_MODE_ICON "text-x-generic" + + +#endif // _DEFAULTS_H_ diff --git a/ksystemlog/src/lib/globals.cpp b/ksystemlog/src/lib/globals.cpp new file mode 100644 index 00000000..ed562581 --- /dev/null +++ b/ksystemlog/src/lib/globals.cpp @@ -0,0 +1,225 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "globals.h" + +#include +#include +#include + +#include + +#include "defaults.h" +#include "logLevel.h" +#include "logMode.h" +#include "logFile.h" + +#include "analyzer.h" +#include "logModeAction.h" +#include "logModeFactory.h" +#include "logModeConfiguration.h" +#include "logModeConfigurationWidget.h" + +Globals* Globals::self = NULL; + +Globals* Globals::instance() { + if (Globals::self == NULL) { + Globals::self = new Globals(); + } + + return Globals::self; +} + +class GlobalsPrivate { +public: + + /** + * Existing Log modes. + */ + QMap logModes; + + QList logModeActions; + + /* + LogMode* noMode; + */ + + /** + * Existing Log levels. The id value corresponds to the index in the vector + */ + QList logLevels; + + /** + * These value are only pointers to item of the previous vector, + * they are provided for convenience + */ + LogLevel* noLogLevel; + LogLevel* debugLogLevel; + LogLevel* informationLogLevel; + LogLevel* noticeLogLevel; + LogLevel* warningLogLevel; + LogLevel* errorLogLevel; + LogLevel* criticalLogLevel; + LogLevel* alertLogLevel; + LogLevel* emergencyLogLevel; + +}; + +Globals::Globals() : + d(new GlobalsPrivate()) { + + setupLogLevels(); + +} + +Globals::~Globals() { + + foreach(LogMode* logMode, d->logModes) { + delete logMode; + } + d->logModes.clear(); + + foreach(LogModeAction* logModeAction, d->logModeActions) { + delete logModeAction; + } + d->logModeActions.clear(); + + foreach(LogLevel* logLevel, d->logLevels) { + delete logLevel; + } + d->logLevels.clear(); + + delete d; + +} + +/* +void Globals::setupLogModes() { + d->noMode=new LogMode("noLogMode", i18n("No Log"), NO_MODE_ICON); + d->logModes.append(d->noMode); + + d->sambaMode=new LogMode("sambaLogMode", i18n("Samba Log"), SAMBA_MODE_ICON); + d->logModes.append(d->sambaMode); + + d->cupsMode=new LogMode("cupsLogMode", i18n("CUPS Log"), CUPS_MODE_ICON); + d->logModes.append(d->cupsMode); + + d->cupsAccessMode=new LogMode("cupsAccessLogMode", i18n("CUPS Access Log"), CUPS_ACCESS_MODE_ICON); + d->logModes.append(d->cupsAccessMode); + + d->postfixMode=new LogMode("postfixLogMode", i18n("Postfix Log"), POSTFIX_MODE_ICON); + d->logModes.append(d->postfixMode); + +} +*/ + +void Globals::setupLogLevels() { + d->noLogLevel=new LogLevel(NONE_LOG_LEVEL_ID, i18n("None"), QLatin1String( "help-contents" ), QColor(208, 210, 220)); + d->logLevels.append(d->noLogLevel); + + d->debugLogLevel=new LogLevel(DEBUG_LOG_LEVEL_ID, i18n("Debug"), QLatin1String( "attach" ), QColor(156, 157, 165)); + d->logLevels.append(d->debugLogLevel); + + d->informationLogLevel=new LogLevel(INFORMATION_LOG_LEVEL_ID, i18n("Information"), QLatin1String( "dialog-information" ), QColor(36, 49, 103) /*QColor(0, 0, 0)*/); + d->logLevels.append(d->informationLogLevel); + + d->noticeLogLevel=new LogLevel(NOTICE_LOG_LEVEL_ID, i18n("Notice"), QLatin1String( "book2" ), QColor(36, 138, 22)); + d->logLevels.append(d->noticeLogLevel); + + d->warningLogLevel=new LogLevel(WARNING_LOG_LEVEL_ID, i18n("Warning"), QLatin1String( "dialog-warning" ), QColor(238, 144, 21)); + d->logLevels.append(d->warningLogLevel); + + d->errorLogLevel=new LogLevel(ERROR_LOG_LEVEL_ID, i18n("Error"), QLatin1String( "dialog-close" ), QColor(173, 28, 28)); + d->logLevels.append(d->errorLogLevel); + + d->criticalLogLevel=new LogLevel(CRITICAL_LOG_LEVEL_ID, i18n("Critical"), QLatin1String( "exec" ), QColor(214, 26, 26)); + d->logLevels.append(d->criticalLogLevel); + + d->alertLogLevel=new LogLevel(ALERT_LOG_LEVEL_ID, i18n("Alert"), QLatin1String( "bell" ), QColor(214, 0, 0)); + d->logLevels.append(d->alertLogLevel); + + d->emergencyLogLevel=new LogLevel(EMERGENCY_LOG_LEVEL_ID, i18n("Emergency"), QLatin1String( "application-exit" ), QColor(255, 0, 0)); + d->logLevels.append(d->emergencyLogLevel); + +} + + +QList Globals::logModes() { + return d->logModes.values(); +} + +QList Globals::logLevels() { + return d->logLevels; +} + + +LogLevel* Globals::noLogLevel() { + return d->noLogLevel; +} +LogLevel* Globals::debugLogLevel() { + return d->debugLogLevel; +} +LogLevel* Globals::informationLogLevel() { + return d->informationLogLevel; +} +LogLevel* Globals::noticeLogLevel() { + return d->noticeLogLevel; +} +LogLevel* Globals::warningLogLevel() { + return d->warningLogLevel; +} +LogLevel* Globals::errorLogLevel() { + return d->errorLogLevel; +} +LogLevel* Globals::criticalLogLevel() { + return d->criticalLogLevel; +} +LogLevel* Globals::alertLogLevel() { + return d->alertLogLevel; +} +LogLevel* Globals::emergencyLogLevel() { + return d->emergencyLogLevel; +} + +void Globals::registerLogModeFactory(LogModeFactory* logModeFactory) { + QList logModes = logModeFactory->createLogModes(); + + foreach (LogMode* logMode, logModes) { + //Log mode + d->logModes.insert(logMode->id(), logMode); + } + + //Log mode Actions + LogModeAction* logModeAction = logModeFactory->createLogModeAction(); + if (logModeAction != NULL) { + d->logModeActions.append(logModeAction); + } + + delete logModeFactory; +} + +LogMode* Globals::findLogMode(const QString& logModeName) { + return d->logModes.value(logModeName); +} + +QList Globals::logModeActions() { + return d->logModeActions; +} diff --git a/ksystemlog/src/lib/globals.h b/ksystemlog/src/lib/globals.h new file mode 100644 index 00000000..fc4003cc --- /dev/null +++ b/ksystemlog/src/lib/globals.h @@ -0,0 +1,83 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _GLOBALS_H_ +#define _GLOBALS_H_ + +#include +#include + +#include "logMode.h" +#include "logModeFactory.h" + + + +class LogModeFactory; + +class LogMode; +class LogModeAction; +class LogLevel; + +class Reader; + +class GlobalsPrivate; + +class Globals { + public: + static Globals* instance(); + + ~Globals(); + + QList logLevels(); + + LogLevel* noLogLevel(); + LogLevel* debugLogLevel(); + LogLevel* informationLogLevel(); + LogLevel* noticeLogLevel(); + LogLevel* warningLogLevel(); + LogLevel* errorLogLevel(); + LogLevel* criticalLogLevel(); + LogLevel* alertLogLevel(); + LogLevel* emergencyLogLevel(); + + /** + * Allow to add a new Reader for a new log mode + */ + void registerLogModeFactory(LogModeFactory* logModeFactory); + + QList logModes(); + + QList logModeActions(); + + LogMode* findLogMode(const QString& logModeName); + + private: + explicit Globals(); + + static Globals* self; + + void setupLogLevels(); + + GlobalsPrivate* const d; + +}; + +#endif diff --git a/ksystemlog/src/lib/kioLogFileReader.cpp b/ksystemlog/src/lib/kioLogFileReader.cpp new file mode 100644 index 00000000..bd05c08b --- /dev/null +++ b/ksystemlog/src/lib/kioLogFileReader.cpp @@ -0,0 +1,172 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "kioLogFileReader.h" + +#include + +#include +#include + +#include + +#include "logFile.h" +#include "logging.h" + +#define READ_SIZE 10 + +class KioLogFileReaderPrivate { + +public: + + KioLogFileReaderPrivate(const LogFile& file) : + logFile(file) { + + } + + LogFile logFile; + + KIO::FileJob* fileJob; + + QString buffer; + qulonglong totalRead; + + KDirWatch* fileWatch; + +}; + +KioLogFileReader::KioLogFileReader(const LogFile& logFile) : + d(new KioLogFileReaderPrivate(logFile)) { + + d->fileJob = NULL; + d->totalRead = 0; + + d->fileWatch = new KDirWatch(this); + + connect(d->fileWatch, SIGNAL(dirty(QString)), this, SLOT(watchFile(QString))); + d->fileWatch->addFile(logFile.url().path()); + /* + d->fileWatch.setInterval(1000); + connect(& (d->fileWatch), SIGNAL(timeout()), this, SLOT(watchFile())); + */ + + logDebug() << "Starting " << logFile.url().path() << endl; + + +} + + +KioLogFileReader::~KioLogFileReader() { + delete d; +} + +void KioLogFileReader::open() { + logDebug() << "Opening..." << endl; + d->fileJob = KIO::open(d->logFile.url(), QIODevice::ReadOnly | QIODevice::Text); + + connect(d->fileJob, SIGNAL(open(KIO::Job*)), this, SLOT(openDone(KIO::Job*))); + connect(d->fileJob, SIGNAL(close(KIO::Job*)), this, SLOT(closeDone(KIO::Job*))); + + connect(d->fileJob, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(dataReceived(KIO::Job*,QByteArray))); + connect(d->fileJob, SIGNAL(mimetype(KIO::Job*,QString)), this, SLOT(mimetypeReceived(KIO::Job*,QString))); + + logDebug() << "File opened." << endl; +} + +void KioLogFileReader::close() { + d->fileJob->close(); +} + +void KioLogFileReader::openDone(KIO::Job* job) { + logDebug() << "Opening done..." << endl; + + d->fileJob->read(READ_SIZE); + +} +void KioLogFileReader::closeDone(KIO::Job* job) { + logDebug() << "Closing done..." << endl; +} + +void KioLogFileReader::dataReceived(KIO::Job* job, const QByteArray& data) { + if (job != d->fileJob) { + logDebug() << "Not the good job" << endl; + return; + } + + if (data.isEmpty()) { + return; + } + + //logDebug() << "Receiving data... (" << d->totalRead << ")" << endl; + d->buffer.append(QLatin1String( data )); + d->totalRead += data.size(); + + emitCompleteLines(); + + logDebug() << "Total read : " << d->totalRead << " of " << d->fileJob->size() << endl; + if (d->totalRead < d->fileJob->size()) { + d->fileJob->read(READ_SIZE); + } + else { + logDebug() << "Entire file read, beginning file watching..." << endl; + d->fileWatch->startScan(); + + } + + + //logDebug() << "Data received : " << d->buffer << endl; + + //d->totalRead++; +} + +void KioLogFileReader::emitCompleteLines() { + + int endLinePos = d->buffer.indexOf(QLatin1String( "\n" )); + forever { + if (endLinePos==-1) + break; + + emit lineRead(d->buffer.left(endLinePos)); + + //Remove the emitted line and the end line character + d->buffer.remove(0, endLinePos+1); + + endLinePos = d->buffer.indexOf(QLatin1String( "\n" )); + } + + //If this is the end line and it does not terminate by a \n, we return it + if (d->totalRead == d->fileJob->size()) { + emit lineRead(d->buffer); + d->buffer.clear(); + + } + +} + +void KioLogFileReader::mimetypeReceived(KIO::Job* job, const QString& type) { + logDebug() << "Mimetype received " << type << endl; +} + +void KioLogFileReader::watchFile(const QString& path) { + logDebug() << "Watch file : size : " << path << endl; +} + +#include "kioLogFileReader.moc" diff --git a/ksystemlog/src/lib/kioLogFileReader.h b/ksystemlog/src/lib/kioLogFileReader.h new file mode 100644 index 00000000..2982a69c --- /dev/null +++ b/ksystemlog/src/lib/kioLogFileReader.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _KIO_LOG_FILE_READER_H_ +#define _KIO_LOG_FILE_READER_H_ + +#include +#include +#include + +#include "logFile.h" + +namespace KIO { + class Job; +} + +class KioLogFileReaderPrivate; + +/** + * TODO Inherits from LogFileReader + */ +class KioLogFileReader : public QObject { + + Q_OBJECT + + public: + + KioLogFileReader(const LogFile& logFile); + + virtual ~KioLogFileReader(); + + void open(); + void close(); + + signals: + void lineRead(const QString&); + + private slots: + void openDone(KIO::Job* job); + void closeDone(KIO::Job* job); + void dataReceived(KIO::Job* job, const QByteArray& data); + void mimetypeReceived(KIO::Job* job, const QString& type); + + void watchFile(const QString& path); + private: + void emitCompleteLines(); + + KioLogFileReaderPrivate* const d; +}; + +#endif // _KIO_LOG_FILE_READER_H_ diff --git a/ksystemlog/src/lib/levelPrintPage.cpp b/ksystemlog/src/lib/levelPrintPage.cpp new file mode 100644 index 00000000..63de4bfc --- /dev/null +++ b/ksystemlog/src/lib/levelPrintPage.cpp @@ -0,0 +1,131 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "levelPrintPage.h" + +#include +#include + +#include +#include +#include +#include + +#include "logLevel.h" +#include "logViewWidgetItem.h" +#include "logging.h" + +#include "globals.h" + +LevelPrintPage::LevelPrintPage(QWidget* parent) + : QWidget( parent) +{ + setWindowTitle(i18n("Log Level Printing")); + + //m_pageLayout = new QVBoxLayout(this, 3, 3); + m_pageLayout = new QVBoxLayout(this); + + m_lblChoose = new QLabel(this); + //m_lblChoose->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)5, (QSizePolicy::SizeType)0, 0, 0, m_lblChoose->sizePolicy().hasHeightForWidth() ) ); + m_lblChoose->setText( i18n( "Choose which log levels you wish to print in color." ) ); + m_pageLayout->addWidget( m_lblChoose ); + + m_btnGroup = new QButtonGroup(this); + /* + i18n("Log Levels"), + m_btnGroup->setColumnLayout(0, Qt::Vertical ); + m_btnGroup->layout()->setSpacing( 6 ); + m_btnGroup->layout()->setMargin( 11 ); + m_btnGroupLayout = new QGridLayout( m_btnGroup->layout() ); + m_btnGroupLayout->setAlignment( Qt::AlignTop ); + */ + + int row = 0, col = 0; + + foreach(LogLevel* level, Globals::instance()->logLevels()) { + + QCheckBox* button = new QCheckBox(level->name());//, m_btnGroup, 0 + + levelCheckBoxes.append(button); + m_btnGroup->addButton(button, level->id()); + m_btnGroupLayout->addWidget(button, row, col); + + logDebug() << "name: " << level->name() << " id: " << level->id() << endl; + + row++; + if(row >= 4) { + row = 0; + col++; + } + } + + //m_pageLayout->addWidget(m_btnGroup); +} + +LevelPrintPage::~LevelPrintPage() { + // no need to delete child widgets, Qt does it all for us +} + +/* QPrinter Port: comment out as dialog page is not currently being used, so not ported + +void LevelPrintPage::getOptions( QMap& opts, bool incldef ) { + foreach(LogLevel* level, Globals::instance()->logLevels()) { + QString key = "kde-ksystemlog-print_" + level->name(); + + + QCheckBox* checkBox = static_cast(m_btnGroup->find(level->id())); + if(checkBox) { + if (checkBox->isChecked()) + opts[ key ] = "1"; + else + opts[ key ] = "0"; + } + + } +} + +void LevelPrintPage::setOptions( const QMap& opts ) { + foreach(LogLevel* level, Globals::instance()->logLevels()) { + QString key = "kde-ksystemlog-print_" + level->name(); + QString use = opts[ key ]; + + int chk = use.toInt(); + + + QCheckBox* checkBox = static_cast(m_btnGroup->find(level->id())); + if(checkBox) { + if(chk) + checkBox->setChecked(true); + else + checkBox->setChecked(false); + } + + + } +} + +*/ + +bool LevelPrintPage::isValid( QString& msg) { + return true; +} + + diff --git a/ksystemlog/src/lib/levelPrintPage.h b/ksystemlog/src/lib/levelPrintPage.h new file mode 100644 index 00000000..3473eafe --- /dev/null +++ b/ksystemlog/src/lib/levelPrintPage.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef LEVELPRINTPAGE_H +#define LEVELPRINTPAGE_H + +#include +#include + +class QButtonGroup; +class QVBoxLayout; +class QGridLayout; +class QCheckBox; +class QLabel; + +/** + @author Bojan Djurkovic + */ +class LevelPrintPage : public QWidget { +public: + explicit LevelPrintPage(QWidget *parent = 0); + ~LevelPrintPage(); + + bool isValid(QString& msg); + +private: + QButtonGroup* m_btnGroup; + QGridLayout* m_btnGroupLayout; + QVBoxLayout* m_pageLayout; + QLabel* m_lblChoose; + QList levelCheckBoxes; +}; + +#endif diff --git a/ksystemlog/src/lib/loadingBar.cpp b/ksystemlog/src/lib/loadingBar.cpp new file mode 100644 index 00000000..c7c55809 --- /dev/null +++ b/ksystemlog/src/lib/loadingBar.cpp @@ -0,0 +1,128 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "loadingBar.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class LoadingBarPrivate { +public: + //Attributes managing the position in the files loading of each log + int fileCount; + int currentFileIndex; + + bool firstLoading; + + QLabel* label; + + QProgressBar* progressBar; + +}; + +LoadingBar::LoadingBar(QWidget* parent) : + QWidget(parent), + d(new LoadingBarPrivate()) + { + + d->firstLoading = true; + + QHBoxLayout* widgetLayout = new QHBoxLayout(); + setLayout(widgetLayout); + + widgetLayout->addStretch(); + + QVBoxLayout* layout = new QVBoxLayout(); + widgetLayout->addLayout(layout); + + widgetLayout->addStretch(); + + d->label = new QLabel(i18n("Loading Progress...")); + d->label->setMinimumWidth(250); + layout->addWidget(d->label, 1, Qt::AlignBottom); + + d->progressBar = new QProgressBar(); + d->progressBar->setRange(0, 100); + d->progressBar->setMinimumWidth(250); + layout->addWidget(d->progressBar, 1, Qt::AlignCenter | Qt::AlignTop); + +} + +LoadingBar::~LoadingBar() { + delete d; +} + + +QProgressBar* LoadingBar::progressBar() { + return d->progressBar; +} + +void LoadingBar::startLoading(const LogMode& logMode, const LogFile& logFile, int fileIndex, int fileCount) { + emit displayed(true); + + d->progressBar->setValue(0); + + //Several files to load + if (fileCount>1 && fileIndex>=1) { + if (d->firstLoading) + d->label->setText(i18np("Loading %2...
%3 - (%4)", + "Loading %2...
%3 - (%4/%1 files)", + fileCount, logMode.name(), logFile.url().path(), fileIndex)); + else + d->label->setText(i18np("Reloading %2...
%3 - (%4)", + "Reloading %2...
%3 - (%4/%1 files)", + fileCount, logMode.name(), logFile.url().path(), fileIndex)); + } + //Only one file + else { + if (d->firstLoading) + d->label->setText(i18n("Loading %1...
%2", logMode.name(), logFile.url().path())); + else + d->label->setText(i18n("Reloading %1...
%2", logMode.name(), logFile.url().path())); + } + +} + +void LoadingBar::endLoading() { + emit displayed(false); + + d->progressBar->setValue(100); + + //If the endLoading has been called one time, it means it has already been loaded + d->firstLoading=false; + +} + +void LoadingBar::progressLoading() { + d->progressBar->setValue( d->progressBar->value() + 1 ); + + kapp->processEvents(); +} + +#include "loadingBar.moc" diff --git a/ksystemlog/src/lib/loadingBar.h b/ksystemlog/src/lib/loadingBar.h new file mode 100644 index 00000000..babd00e0 --- /dev/null +++ b/ksystemlog/src/lib/loadingBar.h @@ -0,0 +1,63 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOADING_BAR_H_ +#define _LOADING_BAR_H_ + +#include + +#include "logMode.h" +#include "logFile.h" + +class QProgressBar; +class LoadingBarPrivate; + +/** + * TODO Try to have a working Cancel button (for the moment, it only hide the Loading Dialog) + */ +class LoadingBar : public QWidget { + + Q_OBJECT + + public: + LoadingBar(QWidget* parent=NULL); + + ~LoadingBar(); + + QProgressBar* progressBar(); + + public slots: + + void startLoading(const LogMode& logMode, const LogFile& logFile, int fileIndex, int fileCount); + void endLoading(); + + void progressLoading(); + + signals: + void displayed(bool displayed); + + private: + LoadingBarPrivate* const d; + + +}; + +#endif // _LOADING_BAR_H_ diff --git a/ksystemlog/src/lib/localLogFileReader.cpp b/ksystemlog/src/lib/localLogFileReader.cpp new file mode 100644 index 00000000..92f9d9e1 --- /dev/null +++ b/ksystemlog/src/lib/localLogFileReader.cpp @@ -0,0 +1,239 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "localLogFileReader.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "logFileReader.h" +#include "logFileReaderPrivate.h" + +#include "logging.h" + + +class LocalLogFileReaderPrivate : public LogFileReaderPrivate { + +public: + + KDirWatch* watch; + + long previousFilePosition; + + /** + * Mutex avoiding multiple logFileModified() calls + */ + QMutex insertionLocking; + +}; + + +LocalLogFileReader::LocalLogFileReader(const LogFile& logFile) : + LogFileReader(*new LocalLogFileReaderPrivate(), logFile) { + + init(); +} + + +LocalLogFileReader::LocalLogFileReader(LocalLogFileReaderPrivate& dd, const LogFile& logFile) : + LogFileReader(dd, logFile) { + + init(); +} + +LocalLogFileReader::~LocalLogFileReader() { + Q_D(LocalLogFileReader); + + //Delete the watching object + delete d->watch; + + //d pointer is deleted by the parent class +} + +void LocalLogFileReader::init() { + Q_D(LocalLogFileReader); + + d->watch=new KDirWatch(); + connect(d->watch, SIGNAL(dirty(QString)), this, SLOT(logFileModified())); + + //Init current file position + d->previousFilePosition = 0; + + logDebug() << "Reading local file " << d->logFile.url().path() << endl; + +} + +void LocalLogFileReader::watchFile(bool enable) { + Q_D(LocalLogFileReader); + + if (enable == true) { + logDebug() << "Monitoring file : " << d->logFile.url().path() << endl; + + if (d->watch->contains(d->logFile.url().path()) == false) { + d->watch->addFile(d->logFile.url().path()); + } + + //Reinit current file position + d->previousFilePosition = 0; + + //If we enable the watching, then we first try to see if new lines have appeared + logFileModified(); + } + else { + d->watch->removeFile(d->logFile.url().path()); + } +} + +QIODevice* LocalLogFileReader::open() { + Q_D(LocalLogFileReader); + + if (d->logFile.url().isValid()==false) { + QString message(i18n("This file is not valid. Please adjust it in the settings of KSystemLog.")); + emit errorOccured(i18n("File Does Not Exist"), message); + emit statusBarChanged(message); + } + + QString mimeType = KMimeType::findByFileContent( d->logFile.url().path() )->name(); + + logDebug() << d->logFile.url().path() << " : " << mimeType << endl; + QIODevice* inputDevice; + + //Try to see if this file exists + QFile* file = new QFile(d->logFile.url().path()); + //If the file does not exist + if (! file->exists()) { + QString message(i18n("The file '%1' does not exist.", d->logFile.url().path())); + emit errorOccured(i18n("File Does Not Exist"), message); + emit statusBarChanged(message); + delete file; + return NULL; + } + + //Plain text file : we use a QFile object + if (mimeType == QLatin1String( "text/plain" ) || mimeType == QLatin1String( "application/octet-stream" )) { + logDebug() << "Using QFile input device" << endl; + + inputDevice = file; + } + //Compressed file : we use the KFilterDev helper + else { + logDebug() << "Using KFilterDev input device" << endl; + + inputDevice = KFilterDev::deviceForFile(d->logFile.url().path(), mimeType); + + if (inputDevice == NULL) { + QString message(i18n("Unable to uncompress the '%2' format of '%1'.", d->logFile.url().path(), mimeType)); + emit errorOccured(i18n("Unable to Uncompress File"), message); + emit statusBarChanged(message); + return NULL; + } + } + + if ( ! inputDevice->open( QIODevice::ReadOnly ) ) { + QString message(i18n("You do not have sufficient permissions to read '%1'.", d->logFile.url().path())); + emit errorOccured(i18n("Insufficient Permissions"), message); + emit statusBarChanged(message); + delete inputDevice; + return NULL; + } + + return inputDevice; +} + +void LocalLogFileReader::close(QIODevice* inputDevice) { + inputDevice->close(); + delete inputDevice; +} + +QStringList LocalLogFileReader::readContent(QIODevice* inputDevice) { + logDebug() << "Retrieving raw buffer..."<< endl; + + Q_D(LocalLogFileReader); + + QStringList rawBuffer; + + QTextStream inputStream(inputDevice); + while (inputStream.atEnd() == false) { + rawBuffer.append(inputStream.readLine()); + } + + logDebug() << "Raw buffer retrieved."<< endl; + + //Get the size file for the next calculation + d->previousFilePosition = inputDevice->size(); + logDebug() << "New file position : " << d->previousFilePosition << " (" << d->logFile.url().path() << ")" << endl; + + return rawBuffer; +} + +void LocalLogFileReader::logFileModified() { + Q_D(LocalLogFileReader); + + logDebug() << "Locking log file modification..." << endl; + if (d->insertionLocking.tryLock() == false) { + logDebug() << "Log file modification already detected." << endl; + return; + } + + QIODevice* inputDevice = open(); + if (inputDevice == NULL) { + logError() << "Could not open file " << d->logFile.url().path() << endl; + return; + } + + //If there are new lines in the file, insert only them or this is the first time we read this file + if (d->previousFilePosition!=0 && d->previousFilePosition <= inputDevice->size()) { + logDebug() << "Reading from position " << d->previousFilePosition << " (" << d->logFile.url().path() << ")" << endl; + + if (inputDevice->isSequential()) { + logError() << "The file current position could not be modified" << endl; + } + else { + //Place the cursor to the last line opened + inputDevice->seek(d->previousFilePosition); + } + + logDebug() << "Retrieving a part of the file..."<< endl; + + emit contentChanged(this, Analyzer::UpdatingRead, readContent(inputDevice)); + + } + //Else reread all lines, clear log list + else { + logDebug() << "New file or file truncated. (Re-)Loading log file" << endl; + + emit contentChanged(this, Analyzer::FullRead, readContent(inputDevice)); + + } + + close(inputDevice); + + logDebug() << "Unlocking log file modification..." << endl; + d->insertionLocking.unlock(); +} + +#include "localLogFileReader.moc" diff --git a/ksystemlog/src/lib/localLogFileReader.h b/ksystemlog/src/lib/localLogFileReader.h new file mode 100644 index 00000000..22edf693 --- /dev/null +++ b/ksystemlog/src/lib/localLogFileReader.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOCAL_LOG_FILE_READER_H_ +#define _LOCAL_LOG_FILE_READER_H_ + +#include +#include + +#include "logFile.h" + +#include "logFileReader.h" + + +class LocalLogFileReaderPrivate; + +class LocalLogFileReader : public LogFileReader { + + Q_OBJECT + + public: + + LocalLogFileReader(const LogFile& logFile); + + virtual ~LocalLogFileReader(); + + void watchFile(bool enable); + + void setPreviousFilePosition(long previousFilePosition); + + private slots: + void logFileModified(); + + private: + void init(); + + QIODevice* open(); + void close(QIODevice* inputDevice); + + QStringList readContent(QIODevice* inputDevice); + + protected: + LocalLogFileReader(LocalLogFileReaderPrivate& dd, const LogFile& logFile); + + private: + Q_DECLARE_PRIVATE(LocalLogFileReader) + +}; + +#endif // _LOCAL_LOG_FILE_READER_H_ diff --git a/ksystemlog/src/lib/logFile.cpp b/ksystemlog/src/lib/logFile.cpp new file mode 100644 index 00000000..df924966 --- /dev/null +++ b/ksystemlog/src/lib/logFile.cpp @@ -0,0 +1,94 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logFile.h" + + +#include "logging.h" + +class LogFilePrivate { + +public: + KUrl url; + + LogLevel* defaultLogLevel; + +}; + +LogFile::LogFile() : + d(new LogFilePrivate()) { + //Nothing to do +} + +LogFile::LogFile(const LogFile& logFile) : + QObject(), + d(new LogFilePrivate()) { + + d->url = logFile.url(); + d->defaultLogLevel = logFile.defaultLogLevel(); +} + +LogFile::LogFile(const KUrl& url, LogLevel* defaultLogLevel) : + d(new LogFilePrivate()) { + + d->url = url; + d->defaultLogLevel = defaultLogLevel; +} + +LogFile::~LogFile() { + //defaultLogLevel is managed by Globals + + delete d; +} + +bool LogFile::operator==(const LogFile& other) { + if (d->url == other.url() && d->defaultLogLevel == other.defaultLogLevel()) + return true; + + return false; +} + +LogFile& LogFile::operator=(const LogFile& logFile) { + d->url = logFile.url(); + d->defaultLogLevel = logFile.defaultLogLevel(); + + return *this; +} + +KUrl LogFile::url() const { + return d->url; +} + +LogLevel* LogFile::defaultLogLevel() const { + return d->defaultLogLevel; +} + +QDataStream& operator<< (QDataStream& out, const LogFile& logFile) { + out << logFile.url().path(); + return out; +} + +QDebug& operator<< (QDebug& out, const LogFile& logFile) { + out << logFile.url().path(); + return out; +} + +#include "logFile.moc" diff --git a/ksystemlog/src/lib/logFile.h b/ksystemlog/src/lib/logFile.h new file mode 100644 index 00000000..c7bfa8d8 --- /dev/null +++ b/ksystemlog/src/lib/logFile.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_FILE_H_ +#define _LOG_FILE_H_ + +#include +#include +#include + +#include + +class LogLevel; + +class LogFilePrivate; + +class LogFile : public QObject { + + Q_OBJECT + + public: + LogFile(); + + LogFile(const LogFile& logFile); + LogFile(const KUrl& url, LogLevel* defaultLogLevel); + + virtual ~LogFile(); + + bool operator==(const LogFile& other); + + LogFile& operator=(const LogFile& column); + + KUrl url() const; + + LogLevel* defaultLogLevel() const; + + private: + LogFilePrivate* const d; +}; + +QDataStream & operator<< (QDataStream& out, const LogFile& column); +QDebug & operator<< (QDebug& out, const LogFile& column); + +#endif // _LOG_FILE_H_ diff --git a/ksystemlog/src/lib/logFileReader.cpp b/ksystemlog/src/lib/logFileReader.cpp new file mode 100644 index 00000000..a891dffa --- /dev/null +++ b/ksystemlog/src/lib/logFileReader.cpp @@ -0,0 +1,54 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logFileReader.h" + +#include "logFileReaderPrivate.h" + +#include "logging.h" + +LogFileReader::LogFileReader(const LogFile& logFile) : + d_ptr(new LogFileReaderPrivate) { + + Q_D(LogFileReader); + d->logFile = logFile; + +} + +LogFileReader::LogFileReader(LogFileReaderPrivate& dd, const LogFile& logFile) : + d_ptr(&dd) { + + Q_D(LogFileReader); + d->logFile = logFile; + +} + +LogFileReader::~LogFileReader() { + delete d_ptr; +} + +LogFile LogFileReader::logFile() const { + //const LogFileReaderPrivate * const d = d_func(); + Q_D(const LogFileReader); + return d->logFile; +} + +#include "logFileReader.moc" diff --git a/ksystemlog/src/lib/logFileReader.h b/ksystemlog/src/lib/logFileReader.h new file mode 100644 index 00000000..11d9719b --- /dev/null +++ b/ksystemlog/src/lib/logFileReader.h @@ -0,0 +1,63 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_FILE_READER_H_ +#define _LOG_FILE_READER_H_ + +#include +#include +#include + +#include "analyzer.h" +#include "logFile.h" + +class LogFileReaderPrivate; + +class LogFileReader : public QObject { + + Q_OBJECT + + public: + + LogFileReader(const LogFile& logFile); + + virtual ~LogFileReader(); + + virtual void watchFile(bool enable) = 0; + + LogFile logFile() const; + + signals: + void contentChanged(LogFileReader* origin, Analyzer::ReadingMode readingMode, const QStringList& newLines); + + void statusBarChanged(const QString& message); + void errorOccured(const QString& title, const QString& message); + + protected: + LogFileReaderPrivate* const d_ptr; + LogFileReader(LogFileReaderPrivate& dd, const LogFile& logFile); + + private: + Q_DECLARE_PRIVATE(LogFileReader) + +}; + +#endif // _LOG_FILE_READER_H_ diff --git a/ksystemlog/src/lib/logFileReaderPrivate.h b/ksystemlog/src/lib/logFileReaderPrivate.h new file mode 100644 index 00000000..be7dd5dd --- /dev/null +++ b/ksystemlog/src/lib/logFileReaderPrivate.h @@ -0,0 +1,37 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_FILE_READER_PRIVATE_H_ +#define _LOG_FILE_READER_PRIVATE_H_ + +#include "logFile.h" + +class LogFileReaderPrivate { + +public: + virtual ~LogFileReaderPrivate() { + + } + + LogFile logFile; +}; + +#endif // _LOG_FILE_READER_PRIVATE_H_ diff --git a/ksystemlog/src/lib/logLevel.cpp b/ksystemlog/src/lib/logLevel.cpp new file mode 100644 index 00000000..db3c389b --- /dev/null +++ b/ksystemlog/src/lib/logLevel.cpp @@ -0,0 +1,74 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logLevel.h" + +#include + +class LogLevelPrivate { +public: + int id; + QString name; + + QString icon; + + QColor color; + + QPixmap pixmap; + +}; + + +LogLevel::LogLevel(int id, const QString& nm, const QString& ic, const QColor& col) : + d(new LogLevelPrivate()) { + + d->id = id; + d->name = nm; + d->icon = ic; + d->color = col; + d->pixmap = SmallIcon(ic); + +} + + +LogLevel::~LogLevel() { + delete d; +} + +int LogLevel::id() { + return d->id; +} + +QString LogLevel::name() { + return d->name; +} + +QString LogLevel::icon() { + return d->icon; +} + +QColor LogLevel::color() { + return d->color; +} + +QPixmap LogLevel::pixmap() { + return d->pixmap; +} diff --git a/ksystemlog/src/lib/logLevel.h b/ksystemlog/src/lib/logLevel.h new file mode 100644 index 00000000..ed28540c --- /dev/null +++ b/ksystemlog/src/lib/logLevel.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_LEVEL_H_ +#define _LOG_LEVEL_H_ + +#include +#include +#include + +class LogLevelPrivate; + +class LogLevel { + + public: + explicit LogLevel(int id, const QString& name, const QString& icon, const QColor& color); + + virtual ~LogLevel(); + + int id(); + QString name(); + + QString icon(); + + QColor color(); + + QPixmap pixmap(); + + private: + LogLevelPrivate* const d; +}; + + +#endif diff --git a/ksystemlog/src/lib/logLine.cpp b/ksystemlog/src/lib/logLine.cpp new file mode 100644 index 00000000..7ce1c679 --- /dev/null +++ b/ksystemlog/src/lib/logLine.cpp @@ -0,0 +1,218 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logLine.h" + +#include +#include + +#include "logMode.h" +#include "logModeItemBuilder.h" +#include "logViewWidgetItem.h" + +#include "logging.h" +#include "globals.h" +#include "ksystemlogConfig.h" + +class LogLinePrivate { +public: + + long internalId; + + QDateTime time; + + QStringList logItems; + + QString originalFile; + + LogLevel* logLevel; + + LogMode* logMode; + + bool recent; + + LogViewWidgetItem* item; + +}; + +LogLine::LogLine( + long internalId, + const QDateTime& dateTime, + const QStringList& logItems, + const QString& file, + LogLevel* logLevel, + LogMode* logMode) : + + d(new LogLinePrivate()) { + + d->internalId = internalId; + d->time=dateTime; + d->logItems = logItems; + d->originalFile = file; + d->logLevel = logLevel; + d->logMode = logMode; + + //No linked item when constructs this LogLine + d->item = NULL; + + //By default in newly created item has the recent state + setRecent(true); + +} + +LogLine::~LogLine() { + //logLevel is managed by Globals + + //item is managed by LogMode + //itemBuilder is managed by LogMode + + delete d; +} + + +LogMode* LogLine::logMode() const { + return d->logMode; +} + +void LogLine::setLogMode(LogMode* logMode) { + d->logMode = logMode; +} + +bool LogLine::equals(const LogLine& other) const { + if (logMode()->id() != other.logMode()->id()) + return false; + + if (time() != other.time()) + return false; + + if (logLevel()->id() != other.logLevel()->id()) + return false; + + if (d->logItems != other.d->logItems) + return false; + + return true; +} + +LogLevel* LogLine::logLevel() const { + return d->logLevel; +} + +void LogLine::setLogLevel(LogLevel* level) { + d->logLevel=level; +} + +QDateTime LogLine::time() const { + return d->time; +} + +void LogLine::setLogItems(const QStringList& logItems) { + d->logItems = logItems; +} + +QStringList LogLine::logItems() const { + return d->logItems; +} + +QString LogLine::sourceFileName() const { + return d->originalFile; +} + +bool LogLine::isOlderThan(const LogLine& other) const { + if (d->time == other.time()) + return d->internalId < other.internalId(); + + return d->time < other.time(); +} + +bool LogLine::isNewerThan(const LogLine& other) const { + if (d->time == other.time()) + return d->internalId > other.internalId(); + + return d->time > other.time(); +} + +bool LogLine::isSameTime(const LogLine& other) const { + return d->time==other.time(); +} + +long LogLine::internalId() const { + return d->internalId; +} + +void LogLine::setRecent(bool recent) { + d->recent = recent; + + if (d->item!=NULL) { + QFont currentFont = d->item->font(d->item->columnCount()-1); + + //We avoid doing the same process + if (d->recent != currentFont.bold()) { + currentFont.setBold(recent); + d->item->setFont(d->item->columnCount()-1, currentFont); + } + } +} + + +QString LogLine::exportToText() const { + + QString exporting; + + if (d->item == NULL) { + logError() << "Trying to export text from NULL item" << endl; + return exporting; + } + + for (int i=0; i < d->item->columnCount(); ++i) { + if (i>0) + exporting.append(QLatin1Char( '\t' )); + + exporting.append(d->item->text(i)); + } + + return exporting; +} + +QString LogLine::formattedText() { + return d->logMode->itemBuilder()->createFormattedText(this); +} + +void LogLine::setItem(LogViewWidgetItem* item) { + d->item = item; + + initializeItem(); +} + +void LogLine::initializeItem() { + d->logMode->itemBuilder()->prepareItem(d->item); + + //Call methods that change the look of the item + setRecent(d->recent); + + if (KSystemLogConfig::colorizeLogLines()) { + //Last column index = d->logItems.count() = (d->logItems.count() -1) +1 (the date column) + d->item->setForeground(d->logItems.count(), QBrush(d->logLevel->color())); + } + + d->item->toggleToolTip(KSystemLogConfig::tooltipEnabled()); + +} diff --git a/ksystemlog/src/lib/logLine.h b/ksystemlog/src/lib/logLine.h new file mode 100644 index 00000000..e0e9f8a0 --- /dev/null +++ b/ksystemlog/src/lib/logLine.h @@ -0,0 +1,92 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef LOG_LINE_H +#define LOG_LINE_H + +#include +#include + +#include + +#include "globals.h" + +#include "logLevel.h" + +class LogViewWidget; +class QDateTime; +class QStringList; +class LogViewWidgetItem; + +class LogLinePrivate; + +class LogLine { + + public: + LogLine( + long internalId, + const QDateTime& dateTime, + const QStringList& logItems, + const QString& originalFile, + LogLevel* level, + LogMode* logMode + ); + + virtual ~LogLine(); + + bool isOlderThan(const LogLine& other) const; + bool isNewerThan(const LogLine& other) const; + + bool equals(const LogLine& other) const; + + bool isSameTime(const LogLine& other) const; + + LogLevel* logLevel() const; + + long internalId() const; + QDateTime time() const; + QStringList logItems() const; + QString sourceFileName() const; + + LogMode* logMode() const; + + bool itemExists() const; + + void setLogItems(const QStringList& logItems); + void setLogLevel(LogLevel* level); + void setLogMode(LogMode* logMode); + + void setRecent(bool recent); + void setItem(LogViewWidgetItem* item); + + QString formattedText(); + + QString exportToText() const; + + protected: + LogLinePrivate* const d; + + private: + void initializeItem(); + +}; + +#endif diff --git a/ksystemlog/src/lib/logManager.cpp b/ksystemlog/src/lib/logManager.cpp new file mode 100644 index 00000000..4e35373c --- /dev/null +++ b/ksystemlog/src/lib/logManager.cpp @@ -0,0 +1,218 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logManager.h" + +#include + +#include + +#include "logging.h" +#include "analyzer.h" +#include "view.h" + +#include "logViewWidget.h" + +#include "loadingBar.h" + +class LogManagerPrivate { + friend class LogManager; + + QTime lastUpdate; + + LogMode* logMode; + + Analyzer* analyzer; + View* usedView; + +}; + +LogManager::LogManager(View* view) : + d(new LogManagerPrivate()) + { + + d->lastUpdate = QTime::currentTime(); + + d->logMode = NULL; + d->analyzer = NULL; + + d->usedView = view; + connect(d->usedView, SIGNAL(droppedUrls(KUrl::List)), this, SLOT(loadDroppedUrls(KUrl::List))); +} + +LogManager::~LogManager() { + cleanPreviousLogMode(); + + //usedView is managed by MainWindow + //logMode is managed by Globals + + delete d; +} + +View* LogManager::usedView() const { + return d->usedView; +} + +void LogManager::reload() { + if (d->logMode == NULL) { + logWarning() << "Log manager is not yet initialized" << endl; + return; + } + + logDebug() << "Reloading with log mode " << d->logMode->name() << "..." << endl; + + emit statusBarChanged(i18n("Loading log...")); + + // Change part of the main interface + emit tabTitleChanged(d->usedView, d->logMode->icon(), d->logMode->name()); + emit windowTitleChanged(d->logMode->name()); + + logDebug() << "Emptying view..." << endl; + + // Empty the current list, to better fill it + d->usedView->logViewWidget()->model()->clear(); + + logDebug() << "Initializing view..." << endl; + + // Init the Log View + logDebug() << "Initializing columns view..." << endl; + + d->usedView->logViewWidget()->setColumns(d->analyzer->initColumns()); + + logDebug() << "Reading log..." << endl; + + // Read the log files + d->analyzer->watchLogFiles(true); + + emit statusBarChanged(i18n("Log successfully loaded.")); + + // Log List has been totally reloaded + emit reloaded(); + + logDebug() << "Log mode " << d->logMode->name() << " reloaded" << endl; + +} + +LogMode* LogManager::logMode() { + return d->logMode; +} + +const QTime& LogManager::lastUpdate() const { + return d->lastUpdate; +} + +void LogManager::updateLog(int lineCount) { + logDebug() << "Updating log " << lineCount << " new lines" << endl; + + if (lineCount==0) + return; + + d->lastUpdate = QTime::currentTime(); + + emit logUpdated(d->usedView, lineCount); +} + +void LogManager::cleanPreviousLogMode() { + logDebug() << "Cleaning previous LogMode..." << endl; + + d->logMode = NULL; + + delete d->analyzer; + d->analyzer = NULL; +} + +void LogManager::initialize(LogMode* mode) { + internalInitialize(mode, mode->createLogFiles()); +} + +void LogManager::internalInitialize(LogMode* mode, const QList& logFiles) { + logDebug() << "Initializing LogManager..." << endl; + + logDebug() << "Using files" << logFiles << endl; + + cleanPreviousLogMode(); + + //Use the new mode + d->logMode=mode; + + //Find the Analyzer instance used for this new mode + d->analyzer=mode->createAnalyzer(); + d->analyzer->setLogViewModel(d->usedView->logViewWidget()->model()); + + connect(d->analyzer, SIGNAL(statusBarChanged(QString)), this, SIGNAL(statusBarChanged(QString))); + connect(d->analyzer, SIGNAL(errorOccured(QString,QString)), this, SLOT(showErrorMessage(QString,QString))); + connect(d->analyzer, SIGNAL(logUpdated(int)), this, SLOT(updateLog(int))); + + connect(d->analyzer, SIGNAL(readFileStarted(LogMode,LogFile,int,int)), d->usedView->loadingBar(), SLOT(startLoading(LogMode,LogFile,int,int))); + connect(d->analyzer, SIGNAL(openingProgressed()), d->usedView->loadingBar(), SLOT(progressLoading())); + connect(d->analyzer, SIGNAL(readEnded()), d->usedView->loadingBar(), SLOT(endLoading())); + + //Find the log files used for this kind of mode, and set them to our log manager + d->analyzer->setLogFiles(logFiles); + + logDebug() << "LogManager initialized" << endl; + +} + +void LogManager::showErrorMessage(const QString& title, const QString& message) { + KMessageBox::error( + d->usedView, + message, + title, + KMessageBox::Notify + ); +} + +void LogManager::setParsingPaused(bool paused) { + if (d->logMode == NULL) { + logWarning() << "Log manager is not yet initialized" << endl; + return; + } + + d->analyzer->setParsingPaused(paused); +} + +bool LogManager::isParsingPaused() const { + if (d->logMode == NULL) { + logWarning() << "Log manager is not yet initialized" << endl; + return false; + } + + return d->analyzer->isParsingPaused(); +} + +void LogManager::loadDroppedUrls(const KUrl::List& urls) { + logDebug() << "Drop " << urls << endl; + + QList logFiles; + + foreach (const KUrl &url, urls) { + logFiles.append(LogFile(url, Globals::instance()->informationLogLevel())); + } + + if (logFiles.isEmpty() == false) { + internalInitialize(Globals::instance()->findLogMode(QLatin1String( "openLogMode" )), logFiles); + + reload(); + } +} + +#include "logManager.moc" diff --git a/ksystemlog/src/lib/logManager.h b/ksystemlog/src/lib/logManager.h new file mode 100644 index 00000000..f68a3187 --- /dev/null +++ b/ksystemlog/src/lib/logManager.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef LOG_MANAGER_H +#define LOG_MANAGER_H + +#include + +#include + +#include "globals.h" + +#include "logMode.h" +#include "logFile.h" + +#include "logViewModel.h" +#include "logViewColumns.h" + +class View; + + +class LogManagerPrivate; + +class LogManager : public QObject { + Q_OBJECT + + public: + + explicit LogManager(View* view); + + ~LogManager(); + + View* usedView() const; + + const QTime& lastUpdate() const; + + LogMode* logMode(); + + void initialize(LogMode* mode); + + void setParsingPaused(bool paused); + bool isParsingPaused() const; + + void reload(); + + protected slots: + + void updateLog(int lineCount); + + void showErrorMessage(const QString& title, const QString& message); + + signals: + void tabTitleChanged(View* view, const QIcon& icon, const QString& label); + + void windowTitleChanged(const QString& caption); + void statusBarChanged(const QString& message); + + void reloaded(); + void logUpdated(View* view, int addedLines); + + private slots: + void loadDroppedUrls(const KUrl::List& urls); + + private: + void internalInitialize(LogMode* mode, const QList& logFiles); + + void cleanPreviousLogMode(); + + LogManagerPrivate* d; + +}; + +#endif //LOG_MANAGER_H diff --git a/ksystemlog/src/lib/logMode.cpp b/ksystemlog/src/lib/logMode.cpp new file mode 100644 index 00000000..1382735c --- /dev/null +++ b/ksystemlog/src/lib/logMode.cpp @@ -0,0 +1,82 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logMode.h" + +#include + +#include + +#include "multipleActions.h" +#include "logModeItemBuilder.h" + +LogMode::LogMode(const QString& id, const QString& name, const QString& iconName) : + d(new LogModePrivate()) { + + d->id = id; + d->name = name; + d->icon = SmallIcon(iconName); +} + +LogMode::~LogMode() { + delete d->action; + + delete d->itemBuilder; + + delete d; +} + +QString LogMode::id() const { + return d->id; +} + +QString LogMode::name() const { + return d->name; +} + +QPixmap LogMode::icon() const { + return d->icon; +} + +QAction* LogMode::action() const { + return d->action; +} + +LogModeItemBuilder* LogMode::itemBuilder() const { + return d->itemBuilder; +} + +LogModeConfigurationWidget* LogMode::logModeConfigurationWidget() const { + return d->logModeConfigurationWidget; +} + +LogModeConfiguration* LogMode::innerConfiguration() const { + return d->logModeConfiguration; +} + +QAction* LogMode::createDefaultAction() { + QAction* action = new QAction(d->icon, d->name, this); + action->setData(QVariant(d->id)); + + return action; +} + +#include "logMode.moc" diff --git a/ksystemlog/src/lib/logMode.h b/ksystemlog/src/lib/logMode.h new file mode 100644 index 00000000..8cbb2c62 --- /dev/null +++ b/ksystemlog/src/lib/logMode.h @@ -0,0 +1,112 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef LOG_MODE_H +#define LOG_MODE_H + +#include +#include +#include +#include + +#include "logFile.h" + +class Analyzer; +class LogModeItemBuilder; +class LogModeConfiguration; +class LogModeConfigurationWidget; + +class QAction; + + +//TODO Do not let this class visible to other classes (except sub-classes) +class LogModePrivate { +public: + QString id; + + QString name; + + QString iconName; + + QPixmap icon; + + QAction* action; + + LogModeItemBuilder* itemBuilder; + + LogModeConfigurationWidget* logModeConfigurationWidget; + + LogModeConfiguration* logModeConfiguration; +}; + +class LogMode : public QObject { + + Q_OBJECT + + public: + LogMode(const QString& id, const QString& name, const QString& iconName); + + virtual ~LogMode(); + + QString id() const; + + QString name() const; + + QPixmap icon() const; + + QAction* action() const; + + LogModeItemBuilder* itemBuilder() const; + + /** + * Log mode configuration widget + */ + LogModeConfigurationWidget* logModeConfigurationWidget() const; + + template + T logModeConfiguration() { + return static_cast( innerConfiguration() ); + } + /** + * Create the Analyzer used to parse the log file + */ + virtual Analyzer* createAnalyzer() = 0; + + /** + * Create the log file list which will be read + */ + virtual QList createLogFiles() = 0; + + protected: + QAction* createDefaultAction(); + + LogModePrivate* const d; + + private: + /** + * Log Mode Configuration + */ + LogModeConfiguration* innerConfiguration() const; + +}; + + +#endif //LOG_MODE_H diff --git a/ksystemlog/src/lib/logModeAction.cpp b/ksystemlog/src/lib/logModeAction.cpp new file mode 100644 index 00000000..e4f58662 --- /dev/null +++ b/ksystemlog/src/lib/logModeAction.cpp @@ -0,0 +1,56 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logModeAction.h" + +class LogModeActionPrivate { +public: + bool inToolBar; + + LogModeAction::Category category; +}; + +LogModeAction::LogModeAction() : + d(new LogModeActionPrivate()) { + + d->inToolBar = true; + d->category = LogModeAction::RootCategory; +} + +LogModeAction::~LogModeAction() { + delete d; +} + +void LogModeAction::setInToolBar(bool inToolBar) { + d->inToolBar = inToolBar; +} + +bool LogModeAction::isInToolBar() { + return d->inToolBar; +} + +void LogModeAction::setCategory(LogModeAction::Category category) { + d->category = category; +} + +LogModeAction::Category LogModeAction::category() { + return d->category; +} diff --git a/ksystemlog/src/lib/logModeAction.h b/ksystemlog/src/lib/logModeAction.h new file mode 100644 index 00000000..bd0ac8ff --- /dev/null +++ b/ksystemlog/src/lib/logModeAction.h @@ -0,0 +1,65 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_MODE_ACTION_H_ +#define _LOG_MODE_ACTION_H_ + +#include +#include +#include + +class LogModeActionPrivate; + +class LogModeAction : public QObject { + + Q_OBJECT + +public: + + enum Category { + RootCategory, + ServicesCategory, + OthersCategory + }; + Q_DECLARE_FLAGS(Categories, Category) + + LogModeAction(); + + virtual ~LogModeAction(); + + virtual QList innerActions() = 0; + + virtual QAction* actionMenu() = 0; + + void setInToolBar(bool inToolBar); + + bool isInToolBar(); + + void setCategory(Category category); + + Category category(); + +private: + LogModeActionPrivate* const d; + +}; + +#endif // _LOG_MODE_ACTION_H_ diff --git a/ksystemlog/src/lib/logModeConfiguration.cpp b/ksystemlog/src/lib/logModeConfiguration.cpp new file mode 100644 index 00000000..457a8d05 --- /dev/null +++ b/ksystemlog/src/lib/logModeConfiguration.cpp @@ -0,0 +1,256 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logModeConfiguration.h" + +#include "logging.h" +#include "defaults.h" + +#include +#include + +#include "logFile.h" +#include "logLevel.h" + +#include "globals.h" + +#include "ksystemlogConfig.h" + +LogModeConfiguration::LogModeConfiguration() { + configuration = KSystemLogConfig::self(); +} + +LogModeConfiguration::~LogModeConfiguration() { + //configuration is managed by KDE +} + +//TODO Need a rewritting +/* +Reader* ReaderFactory::createReader(LogMode* logMode) { + + //Using Boot Mode in the current view + else if (logMode==Globals::bootMode) { + Reader* reader=new SystemReader(NULL, "boot_reader"); + return(reader); + } + + //Using Authentication Mode in the current view + else if (logMode==Globals::authenticationMode) { + Reader* reader=new SystemReader(NULL, "authentication_reader"); + return(reader); + } + + //Using Daemon Mode in the current view + else if (logMode==Globals::daemonMode) { + Reader* reader=new SystemReader(NULL, "daemon_reader"); + return(reader); + } + + //Using Cups Mode in the current view + else if (logMode==Globals::cupsMode) { + Reader* reader=new CupsReader(NULL, "cups_reader"); + return(reader); + } + + //Using Cups Access Mode in the current view + else if (logMode==Globals::cupsAccessMode) { + Reader* reader=new CupsAccessReader(NULL, "cups_access_reader"); + return(reader); + } + + //Using Postfix Mode in the current view + else if (logMode==Globals::postfixMode) { + Reader* reader=new SystemReader(NULL, "postfix_reader"); + return(reader); + } + + //Using Samba Mode in the current view + else if (logMode==Globals::sambaMode) { + Reader* reader=new SambaReader(NULL, "samba_reader"); + return(reader); + } + + //Using SSH Mode in the current view + else if (logMode==Globals::sshMode) { + Reader* reader=new SSHReader(NULL, "ssh_reader"); + return(reader); + } + + //Using X Session Mode in the current view + else if (logMode==Globals::xsessionMode) { + Reader* reader=new XSessionReader(NULL, "xsession_reader"); + return reader; + } + + logError() << "LogMode not found : returns NULL Reader" << endl; + return NULL; +} +*/ + +//TODO Move this method in LogModeFactory subclasses +/* +QList LogFilesFactory::createLogFiles(LogMode* logMode) { + + else if (logMode==Globals::instance()->bootMode()) { + QList list; + list.append(LogFilesFactory::instance()->getBootLogFile()); + return list; + } + + else if (logMode==Globals::instance()->authenticationMode()) { + QList list; + list.append(LogFilesFactory::instance()->getAuthenticationLogFile()); + return list; + } + + else if (logMode==Globals::instance()->daemonMode()) { + return LogFilesFactory::getDaemonLogFiles(); + } + + else if (logMode==Globals::instance()->cupsMode()) { + return LogFilesFactory::getCupsLogFiles(); + } + + else if (logMode==Globals::instance()->cupsAccessMode()) { + return LogFilesFactory::getCupsAccessLogFiles(); + + } + + else if (logMode==Globals::instance()->postfixMode()) { + return LogFilesFactory::getPostfixLogFiles(); + } + + else if (logMode==Globals::instance()->sambaMode()) { + return LogFilesFactory::getSambaLogFiles(); + } + + else if (logMode==Globals::instance()->sshMode()) { + return LogFilesFactory::getSSHLogFiles(); + } + + + else if (logMode==Globals::instance()->xsessionMode()) { + return LogFilesFactory::getXSessionLogFiles(); + } + + logError() << "LogFiles not found : returns NULL Reader" << endl; + + return QList(); + +} + + +LogFile LogFilesFactory::getBootLogFile() { + QString file=KSystemLogConfig::bootPath(); + return getGenericLogFile(file); +} + +LogFile LogFilesFactory::getAuthenticationLogFile() { + QString file=KSystemLogConfig::authenticationPath(); + return getGenericLogFile(file); +} + +QList LogFilesFactory::getDaemonLogFiles() { + QStringList files=KSystemLogConfig::daemonPaths(); + QList levels=KSystemLogConfig::daemonLevels(); + return LogFilesFactory::getGenericLogFiles(files, levels); +} + +QList LogFilesFactory::getCupsLogFiles() { + QStringList stringList=KSystemLogConfig::cupsPaths(); + return getNoModeLogFiles(stringList); +} + +QList LogFilesFactory::getCupsAccessLogFiles() { + QStringList stringList=KSystemLogConfig::cupsAccessPaths(); + return getNoModeLogFiles(stringList); +} + +QList LogFilesFactory::getPostfixLogFiles() { + QStringList files=KSystemLogConfig::postfixPaths(); + QList levels=KSystemLogConfig::postfixLevels(); + return LogFilesFactory::getGenericLogFiles(files, levels); +} + +QList LogFilesFactory::getSambaLogFiles() { + QStringList stringList=KSystemLogConfig::sambaPaths(); + return getNoModeLogFiles(stringList); +} + +QList LogFilesFactory::getSSHLogFiles() { + QStringList stringList=KSystemLogConfig::sshPaths(); + return getNoModeLogFiles(stringList); +} + +QList LogFilesFactory::getXSessionLogFiles() { + QStringList stringList=KSystemLogConfig::xSessionPaths(); + return getNoModeLogFiles(stringList); +} + +*/ + + +LogFile LogModeConfiguration::findGenericLogFile(const QString& file) { + + LogLevel* level=Globals::instance()->informationLogLevel(); + + KUrl url(file); + if (!url.isValid()) { + logWarning() << i18n("URL '%1' is not valid, skipping this URL.", url.path()) << endl; + return LogFile(KUrl(), Globals::instance()->noLogLevel()); + } + + return LogFile(url, level); +} + +QList LogModeConfiguration::findGenericLogFiles(const QStringList& files) { + QList logFiles; + + foreach(const QString &file, files) { + logFiles.append(findGenericLogFile(file)); + } + + return logFiles; +} + +QList LogModeConfiguration::findNoModeLogFiles(const QStringList& stringList) { + + QList logFiles; + + //Default level used for No Mode logs + LogLevel* level=Globals::instance()->noLogLevel(); + + foreach (const QString &string, stringList) { + + KUrl url(string); + if (!url.isValid()) { + logWarning() << i18n("URL '%1' is not valid, skipping this URL.", url.path()) << endl; + continue; + } + + logFiles.append(LogFile(url, level)); + + } + + return logFiles; +} + +#include "logModeConfiguration.moc" diff --git a/ksystemlog/src/lib/logModeConfiguration.h b/ksystemlog/src/lib/logModeConfiguration.h new file mode 100644 index 00000000..79341044 --- /dev/null +++ b/ksystemlog/src/lib/logModeConfiguration.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_MODE_CONFIGURATION_H_ +#define _LOG_MODE_CONFIGURATION_H_ + +#include +#include +#include + +#include "logFile.h" + + +class KSystemLogConfig; + +class LogModeConfiguration : public QObject { + + Q_OBJECT + + public: + LogModeConfiguration(); + + virtual ~LogModeConfiguration(); + + LogFile findGenericLogFile(const QString& file); + QList findGenericLogFiles(const QStringList& files); + + QList findNoModeLogFiles(const QStringList& stringList); + + protected: + KSystemLogConfig* configuration; +}; + +#endif // _LOG_MODE_CONFIGURATION_H_ diff --git a/ksystemlog/src/lib/logModeConfigurationWidget.cpp b/ksystemlog/src/lib/logModeConfigurationWidget.cpp new file mode 100644 index 00000000..135174af --- /dev/null +++ b/ksystemlog/src/lib/logModeConfigurationWidget.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logModeConfigurationWidget.h" + +class LogModeConfigurationWidgetPrivate { +public: + QString itemName; + QString iconName; + QString header; +}; + +LogModeConfigurationWidget::LogModeConfigurationWidget(const QString& itemName, const QString& iconName, const QString& header) : + QWidget(), + d(new LogModeConfigurationWidgetPrivate()) + { + + d->itemName = itemName; + d->iconName = iconName; + d->header = header; +} + + +LogModeConfigurationWidget::~LogModeConfigurationWidget() { + delete d; +} + +QString LogModeConfigurationWidget::itemName() const { + return d->itemName; +} +QString LogModeConfigurationWidget::iconName() const { + return d->iconName; +} +QString LogModeConfigurationWidget::header() const { + return d->header; +} + +/** + * Default implementation + */ +bool LogModeConfigurationWidget::isValid() const { + return true; +} + +#include "logModeConfigurationWidget.moc" diff --git a/ksystemlog/src/lib/logModeConfigurationWidget.h b/ksystemlog/src/lib/logModeConfigurationWidget.h new file mode 100644 index 00000000..954a6b48 --- /dev/null +++ b/ksystemlog/src/lib/logModeConfigurationWidget.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_MODE_CONFIGURATION_WIDGET_H_ +#define _LOG_MODE_CONFIGURATION_WIDGET_H_ + +#include +#include + +class LogModeConfigurationWidgetPrivate; + +class LogModeConfigurationWidget : public QWidget { + + Q_OBJECT + + public: + LogModeConfigurationWidget(const QString& itemName, const QString& iconName, const QString& header); + + virtual ~LogModeConfigurationWidget(); + + virtual bool isValid() const; + + virtual void saveConfig() = 0; + virtual void defaultConfig() = 0; + virtual void readConfig() = 0; + + signals: + void configurationChanged(); + + public: + QString itemName() const; + QString iconName() const; + QString header() const; + + private: + + LogModeConfigurationWidgetPrivate* const d; +}; + +#endif // _LOG_MODE_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/lib/logModeFactory.cpp b/ksystemlog/src/lib/logModeFactory.cpp new file mode 100644 index 00000000..57b339f6 --- /dev/null +++ b/ksystemlog/src/lib/logModeFactory.cpp @@ -0,0 +1,36 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logModeFactory.h" + +#include "logging.h" + +LogModeFactory::LogModeFactory() { + +} + + +LogModeFactory::~LogModeFactory() { + +} + + +#include "logModeFactory.moc" diff --git a/ksystemlog/src/lib/logModeFactory.h b/ksystemlog/src/lib/logModeFactory.h new file mode 100644 index 00000000..5d09c52e --- /dev/null +++ b/ksystemlog/src/lib/logModeFactory.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_MODE_FACTORY_H_ +#define _LOG_MODE_FACTORY_H_ + +#include +#include + +class LogModeAction; +class LogMode; + +class LogModeFactory : public QObject { + + Q_OBJECT + + public: + explicit LogModeFactory(); + + virtual ~LogModeFactory(); + + virtual LogModeAction* createLogModeAction() const = 0; + + /** + * Create the log mode + */ + virtual QList createLogModes() const = 0; + +}; + + +#endif // _LOG_MODE_FACTORY_H_ + diff --git a/ksystemlog/src/lib/logModeItemBuilder.cpp b/ksystemlog/src/lib/logModeItemBuilder.cpp new file mode 100644 index 00000000..5e760244 --- /dev/null +++ b/ksystemlog/src/lib/logModeItemBuilder.cpp @@ -0,0 +1,105 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logModeItemBuilder.h" + +#include +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +#include "ksystemlogConfig.h" + +LogModeItemBuilder::LogModeItemBuilder() { + +} + +LogModeItemBuilder::~LogModeItemBuilder() { + +} + +QString LogModeItemBuilder::formatDate(const QDateTime& dateTime) const { + return KGlobal::locale()->formatDateTime(dateTime, (KLocale::DateFormat) KSystemLogConfig::dateFormat(), true); +} + +void LogModeItemBuilder::prepareItem(LogViewWidgetItem* item) const { + LogLine* line=item->logLine(); + + item->setText(0, formatDate(line->time())); + + int i=1; + foreach(const QString &label, line->logItems()) { + item->setText(i, label); + i++; + } + + item->setIcon(0, line->logLevel()->pixmap()); +} + +QString LogModeItemBuilder::createFormattedText(LogLine* line) const { + QString result; + + QListIterator it(line->logItems()); + + result.append(QLatin1String( "" )); + + result.append(labelMessageFormat(i18n("Date:"), formatDate(line->time()))); + result.append(labelMessageFormat(i18n("Hostname:"), it.next() )); + result.append(labelMessageFormat(i18n("Process:"), it.next() )); + result.append(labelMessageFormat(i18n("Level:"), line->logLevel()->name())); + result.append(labelMessageFormat(i18n("Original file:"), line->sourceFileName())); + + result.append(QLatin1String( "
" )); + + return result; + +} + +QString LogModeItemBuilder::createToolTipText(LogLine* line) const { + QString result; + + result.append(QLatin1String( "" )); + + result.append(labelMessageFormat(i18n("Date:"), formatDate(line->time()))); + result.append(labelMessageFormat(i18n("Level:"), line->logLevel()->name())); + result.append(labelMessageFormat(i18n("Original file:"), line->sourceFileName())); + + result.append(QLatin1String( "
" )); + + return result; +} + + +QString LogModeItemBuilder::labelMessageFormat(const QString& label, const QString& value) const { + return (QLatin1String( "" ) + label + QLatin1String( "" ) + messageFormat(value) + QLatin1String( "" )); +} + +QString LogModeItemBuilder::messageFormat(const QString& message) const { + QString transformation(message); + transformation.replace(QRegExp(QLatin1String( "&" )), QLatin1String( "&" )); + transformation.replace(QRegExp(QLatin1String( "<" )), QLatin1String( "<" )); + transformation.replace(QRegExp(QLatin1String( ">" )), QLatin1String( ">" )); + return transformation; +} diff --git a/ksystemlog/src/lib/logModeItemBuilder.h b/ksystemlog/src/lib/logModeItemBuilder.h new file mode 100644 index 00000000..1222d1bf --- /dev/null +++ b/ksystemlog/src/lib/logModeItemBuilder.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_MODE_ITEM_BUILDER_H_ +#define _LOG_MODE_ITEM_BUILDER_H_ + +#include +#include + +class LogViewWidgetItem; +class LogLine; + +class LogModeItemBuilder { + + public: + LogModeItemBuilder(); + + virtual ~LogModeItemBuilder(); + + virtual void prepareItem(LogViewWidgetItem* item) const; + + virtual QString createFormattedText(LogLine* line) const; + virtual QString createToolTipText(LogLine* line) const; + + protected: + QString labelMessageFormat(const QString& label, const QString& value) const; + QString messageFormat(const QString& message) const; + + QString formatDate(const QDateTime& dateTime) const; + +}; + +#endif // _LOG_MODE_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/lib/logViewColumn.cpp b/ksystemlog/src/lib/logViewColumn.cpp new file mode 100644 index 00000000..45ed168a --- /dev/null +++ b/ksystemlog/src/lib/logViewColumn.cpp @@ -0,0 +1,90 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + + +#include "logViewColumn.h" + + +#include +#include + + +#include +#include +#include + +class LogViewColumnPrivate { +public: + QString columnName; + + bool filtered; + bool grouped; + +}; + +LogViewColumn::LogViewColumn(const QString& name, bool filtered, bool grouped) : + d(new LogViewColumnPrivate()) { + + d->columnName = name; + d->filtered = filtered; + d->grouped = grouped; +} + +LogViewColumn::LogViewColumn(const LogViewColumn& column) : + d(new LogViewColumnPrivate()) { + + d->columnName = column.columnName(); + d->filtered = column.isFiltered(); + d->grouped = column.isGrouped(); +} + +LogViewColumn::~LogViewColumn() { + delete d; +} + +QString LogViewColumn::columnName() const { + return d->columnName; +} + +bool LogViewColumn::isGrouped() const { + return d->grouped; +} +bool LogViewColumn::isFiltered() const { + return d->filtered; +} + +LogViewColumn& LogViewColumn::operator=(const LogViewColumn& column) { + d->columnName = column.columnName(); + d->grouped = column.isGrouped(); + d->filtered = column.isFiltered(); + + return *this; +} + +QDataStream& operator<< (QDataStream& out, const LogViewColumn& column) { + out << column.columnName(); + return out; +} + +QDebug& operator<< (QDebug& out, const LogViewColumn& column) { + out << column.columnName(); + return out; +} diff --git a/ksystemlog/src/lib/logViewColumn.h b/ksystemlog/src/lib/logViewColumn.h new file mode 100644 index 00000000..cdce8a2f --- /dev/null +++ b/ksystemlog/src/lib/logViewColumn.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef LOG_VIEW_COLUMN_H +#define LOG_VIEW_COLUMN_H + +#include +#include +#include + +#include "globals.h" + +class LogViewColumnPrivate; + +class LogViewColumn { + + public: + LogViewColumn(const LogViewColumn& column); + + /* + * TODO For the moment, filtered and grouped parameters are useless : Use them or remove them + */ + explicit LogViewColumn(const QString& name, bool filtered=true, bool grouped=true); + + virtual ~LogViewColumn(); + + QString columnName() const; + bool isGrouped() const; + bool isFiltered() const; + + LogViewColumn& operator=(const LogViewColumn& column); + + private: + LogViewColumnPrivate* const d; + +}; + +QDataStream & operator<< (QDataStream& out, const LogViewColumn& column); +QDebug & operator<< (QDebug& out, const LogViewColumn& column); + +#endif //LOG_VIEW_COLUMN_H diff --git a/ksystemlog/src/lib/logViewColumns.cpp b/ksystemlog/src/lib/logViewColumns.cpp new file mode 100644 index 00000000..431a81a3 --- /dev/null +++ b/ksystemlog/src/lib/logViewColumns.cpp @@ -0,0 +1,127 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logViewColumns.h" + +#include +#include + +#include "logging.h" +#include + +#include "globals.h" +#include "logViewColumn.h" + +class LogViewColumnsPrivate { +public: + + QList columns; + bool groupByLogLevel; + bool groupByDay; + bool groupByHour; + bool groupByLogFile; + +}; + +LogViewColumns::LogViewColumns() : + d(new LogViewColumnsPrivate()) { + + d->groupByLogLevel = true; + d->groupByDay = true; + d->groupByHour = true; + d->groupByLogFile = true; + + +} + +LogViewColumns::~LogViewColumns() { + delete d; +} + +void LogViewColumns::setGroupByLogLevel(bool value) { + d->groupByLogLevel=value; +} + +void LogViewColumns::setGroupByDay(bool value) { + d->groupByDay=value; +} + +void LogViewColumns::setGroupByHour(bool value) { + d->groupByHour=value; +} + +void LogViewColumns::setGroupByLogFile(bool value) { + d->groupByLogFile=value; +} + + +bool LogViewColumns::isGroupByLogLevel() const { + return d->groupByLogLevel; +} + +bool LogViewColumns::isGroupByDay() const { + return d->groupByDay; +} + +bool LogViewColumns::isGroupByHour() const { + return d->groupByHour; +} + +bool LogViewColumns::isGroupByLogFile() const { + return d->groupByLogFile; +} + +QStringList LogViewColumns::toStringList() const { + QStringList columnNames; + + foreach (const LogViewColumn& column, d->columns) { + columnNames.append(column.columnName()); + } + + return columnNames; +} + +void LogViewColumns::addColumn(const LogViewColumn& column) { + d->columns.append(column); +} + +QList LogViewColumns::columns() const { + return d->columns; +} + +LogViewColumns& LogViewColumns::operator=(const LogViewColumns& columns) { + d->columns = columns.columns(); + d->groupByLogLevel = columns.isGroupByLogLevel(); + d->groupByDay = columns.isGroupByDay(); + d->groupByHour = columns.isGroupByHour(); + d->groupByLogFile = columns.isGroupByLogFile(); + + return *this; +} + +QDataStream & operator<< (QDataStream& out, const LogViewColumns& columns) { + out << columns.columns(); + return out; +} +QDebug & operator<< (QDebug& out, const LogViewColumns& columns) { + out << columns.columns(); + return out; +} diff --git a/ksystemlog/src/lib/logViewColumns.h b/ksystemlog/src/lib/logViewColumns.h new file mode 100644 index 00000000..1e082ad5 --- /dev/null +++ b/ksystemlog/src/lib/logViewColumns.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_VIEW_COLUMNS_H_ +#define _LOG_VIEW_COLUMNS_H_ + +#include + +class LogViewColumn; +class QStringList; +class LogViewColumnsPrivate; + +class LogViewColumns { + + public: + LogViewColumns(); + + ~LogViewColumns(); + + bool isGroupByLogLevel() const; + bool isGroupByDay() const; + bool isGroupByHour() const; + bool isGroupByLogFile() const; + + void setGroupByLogLevel(bool value); + void setGroupByDay(bool value); + void setGroupByHour(bool value); + void setGroupByLogFile(bool value); + + QList columns() const; + + void addColumn(const LogViewColumn& column); + QStringList toStringList() const; + + LogViewColumns& operator=(const LogViewColumns& columns); + + private: + LogViewColumnsPrivate* const d; + +}; + + +QDataStream & operator<< (QDataStream& out, const LogViewColumns& columns); +QDebug & operator<< (QDebug& out, const LogViewColumns& columns); + +#endif diff --git a/ksystemlog/src/lib/logViewExport.cpp b/ksystemlog/src/lib/logViewExport.cpp new file mode 100644 index 00000000..70a70469 --- /dev/null +++ b/ksystemlog/src/lib/logViewExport.cpp @@ -0,0 +1,274 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logViewExport.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "logging.h" + +#include "logViewWidgetItem.h" +#include "logViewWidget.h" +#include "logLine.h" + +#include "levelPrintPage.h" + +LogViewExport::LogViewExport(QWidget* parent, LogViewWidget* logViewWidget) : + parent(parent), logViewWidget(logViewWidget) { + +} + +LogViewExport::~LogViewExport() { + +} + +void LogViewExport::sendMail() { + logDebug() << "Exporting to mail..." << endl; + + QString body(i18n("Here are my logs:\n")); + + body+=i18n("---------------------------------------\n"); + + int i=0; + QTreeWidgetItemIterator it(logViewWidget, QTreeWidgetItemIterator::Selected); + while ( *it != NULL) { + LogViewWidgetItem* item=static_cast (*it); + + body+= item->logLine()->exportToText(); + body+=QLatin1Char( '\n' ); + + ++it; + ++i; + } + + body+=i18n("---------------------------------------\n"); + + //Too much lines selected + if (i>1000) { + KMessageBox::sorry(parent, i18n("You have selected too many lines. Please only select important log lines."), i18n("Too Many Lines Selected")); + return; + } + + // Parameters list of this method + //const QString & to, + //const QString & cc, + //const QString & bcc, + //const QString & subject, + //const QString & body, + //const QString & messageFile, + //const QStringList & attachURLs, + //const QCString & startup_id + KToolInvocation::invokeMailer(QLatin1String( "" ), QLatin1String( "" ), QLatin1String( "" ), i18n("Log Lines of my problem"), body, QLatin1String( "" ), QStringList(), kapp->startupId()); +} + +void LogViewExport::printSelection() { + logDebug() << "Printing selection..." << endl; + + QPrinter printer; + + // do some printer initialization + printer.setFullPage( true); + + /* + LevelPrintPage* dialogPage = new LevelPrintPage(parent); + printer.addDialogPage(dialogPage); + */ + + // initialize the printer using the print dialog + QPrintDialog *printDialog = KdePrint::createPrintDialog(&printer, parent); + if (printDialog->exec() == false) { + delete printDialog; + return; + } + delete printDialog; + + // create a painter to paint on the printer object + QPainter painter; + + // start painting + painter.begin( &printer ); + + QPen originalPen(painter.pen()); + + int dpiy = painter.device()->logicalDpiY(); + int margin = (int) ( (2/2.54)*dpiy ); // 2 cm margins + QRect printView(margin, margin, painter.device()->width() - 2*margin, painter.device()->height() - 2*margin ); + + int page = 1; + + int i = 0; + int movement = 0; + + QTreeWidgetItemIterator it(logViewWidget, QTreeWidgetItemIterator::Selected); + while ( *it != NULL) { + LogViewWidgetItem* item=static_cast (*it); + + /* + if(qtItem==NULL) + { + painter.setPen(originalPen); + printPageNumber(painter, printView, movement, page); + break; + }*/ + + QString body = item->logLine()->exportToText(); + //body+= "\n"; + + /* QPrinter Port: comment out as dialog page is not currently being used, so not ported + QString strUseColor = printer.option("kde-ksystemlog-print_"+ item->logLine()->logLevel()->name()); + int use = strUseColor.toInt(); + if (use) { + QPen pen(originalPen); + pen.setColor(item->logLine()->logLevel()->color()); + painter.setPen(pen); + } else {*/ + painter.setPen(originalPen); + //} + + painter.drawText(printView, Qt::AlignLeft | Qt::TextWordWrap, body); + + int fontHeight = painter.fontMetrics().height(); + int lines = painter.fontMetrics().width(body) / printView.width() + 1; + int moveBy = (fontHeight + 2) * lines; + painter.translate(0, moveBy); + + movement = movement + moveBy; + if (movement + margin >= printView.height()) { + painter.setPen(originalPen); + printPageNumber(painter, printView, movement, page); + printer.newPage(); + page++; + movement = 0; + } + + ++it; + ++i; + } + + // stop painting, this will automatically send the print data to the printer + painter.end(); + +} + +void LogViewExport::printPageNumber(QPainter& painter, QRect& printView, int movement, int page) { + logDebug() << "Printing page number..." << endl; + + painter.translate(0, -movement); + printView.moveTo(QPoint(0, printView.height()) ); + painter.translate( 0, -printView.height() ); + painter.drawText(printView.right() - painter.fontMetrics().width(QString::number(page) ), printView.bottom()+ painter.fontMetrics().ascent() + 5, QString::number(page) ); + +} + +void LogViewExport::copyToClipboard() { + logDebug() << "Copying to clipboard..." << endl; + + int nbCopied=0; + QString text; + + QTreeWidgetItemIterator it(logViewWidget, QTreeWidgetItemIterator::Selected); + while ( *it != NULL) { + LogViewWidgetItem* item=static_cast (*it); + + //Copy the item content to the text string + text.append(item->logLine()->exportToText()); + text.append(QLatin1Char( '\n' )); + + it++; + nbCopied++; + + } + + //Copy text value only if it is not empty + if (nbCopied==0) { + emit statusBarChanged(i18n("No items selected. Nothing copied to clipboard.")); + } else { + //Copy both to clipboard and X11-selection + QApplication::clipboard()->setText(text, QClipboard::Clipboard); + QApplication::clipboard()->setText(text, QClipboard::Selection); + + emit statusBarChanged(i18np("1 log line copied to clipboard.", "%1 log lines copied to clipboard.", nbCopied)); + } + + logDebug() << "Copied " << nbCopied << " log lines" << endl; + +} + +void LogViewExport::fileSave() { + logDebug() << "Saving to a file..." << endl; + + QTreeWidgetItemIterator it(logViewWidget, QTreeWidgetItemIterator::Selected); + + //No item selected + if ( *it == NULL) { + emit statusBarChanged(i18n("No items selected. Please select items to be able to save them.")); + return; + } + + QString filename = KFileDialog::getSaveFileName(KUrl(), QString(), parent); + if (filename.isEmpty() == true) { + return; + } + + QIODevice* ioDev = KFilterDev::deviceForFile(filename); + if (ioDev->open(QIODevice::WriteOnly)) { + QTextStream stream(ioDev); + + int nbCopied=0; + + while ( *it != NULL) { + LogViewWidgetItem* item=static_cast (*it); + + //Copy the item content to the stream + stream << item->logLine()->exportToText() << '\n'; + + //Retrieve the next item + it++; + nbCopied++; + + } + + ioDev->close(); + + delete ioDev; + + emit statusBarChanged(i18np("1 log line saved to '%2'.", "%1 log lines saved to '%2'.", nbCopied, filename)); + } + else { + QString message(i18n("Unable to save file '%1': Permission Denied.", filename)); + KMessageBox::error(parent, message, i18n("Unable to save file.")); + } + + +} + +#include "logViewExport.moc" diff --git a/ksystemlog/src/lib/logViewExport.h b/ksystemlog/src/lib/logViewExport.h new file mode 100644 index 00000000..ec804575 --- /dev/null +++ b/ksystemlog/src/lib/logViewExport.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_VIEW_EXPORT_H_ +#define _LOG_VIEW_EXPORT_H_ + +#include +#include +#include + +class LogViewWidget; + +class LogViewExport : public QObject { + + Q_OBJECT + + public: + LogViewExport(QWidget* parent, LogViewWidget* logViewWidget); + + virtual ~LogViewExport(); + + void copyToClipboard(); + + void fileSave(); + + void sendMail(); + + void printSelection(); + + signals: + void statusBarChanged(const QString& message); + + private: + + void printPageNumber(QPainter& painter, QRect& printView, int movement, int page); + + QWidget* parent; + + LogViewWidget* logViewWidget; +}; + + +#endif //_LOG_VIEW_EXPORT_H_ diff --git a/ksystemlog/src/lib/logViewFilterWidget.cpp b/ksystemlog/src/lib/logViewFilterWidget.cpp new file mode 100644 index 00000000..fea458a4 --- /dev/null +++ b/ksystemlog/src/lib/logViewFilterWidget.cpp @@ -0,0 +1,167 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logViewFilterWidget.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "logViewWidget.h" + +#include "logViewColumn.h" +#include "logging.h" + + +class LogViewFilterWidgetPrivate { +public: + + LogViewWidgetSearchLine* filterLine; + + /** + * Filter of the column list + */ + KComboBox* filterList; + +}; + + +LogViewWidgetSearchLine::LogViewWidgetSearchLine() : + KTreeWidgetSearchLine() { + +} + +LogViewWidgetSearchLine::~LogViewWidgetSearchLine() { + +} + +void LogViewWidgetSearchLine::updateSearch(const QString& pattern) { + KTreeWidgetSearchLine::updateSearch(pattern); + + emit treeWidgetUpdated(); +} + + +LogViewFilterWidget::LogViewFilterWidget() : + d(new LogViewFilterWidgetPrivate()) { + + + QHBoxLayout* filterBarLayout = new QHBoxLayout(); + filterBarLayout->setMargin(0); + //filterBarLayout->setSpacing(-1); + setLayout(filterBarLayout); + + d->filterLine = new LogViewWidgetSearchLine(); + + d->filterLine->setToolTip(i18n("Type your filter here")); + d->filterLine->setWhatsThis(i18n("Allows you to only list items that match the content of this text.")); + d->filterLine->setClickMessage(i18n("Enter your search here...")); + + QLabel* filterIcon = new QLabel(); + filterIcon->setPixmap(SmallIcon(QLatin1String( "view-filter" ))); + filterIcon->setBuddy(d->filterLine); + filterBarLayout->addWidget(filterIcon); + + QLabel* filterLabel = new QLabel(i18n("Filter:")); + filterLabel->setBuddy(d->filterLine); + filterBarLayout->addWidget(filterLabel); + + filterBarLayout->addWidget(d->filterLine); + + initSearchListFilter(); + + filterBarLayout->addWidget(d->filterList); + +} + +LogViewFilterWidget::~LogViewFilterWidget() { + delete d; +} + + +void LogViewFilterWidget::initSearchListFilter() { + d->filterList=new KComboBox(); + + d->filterList->setToolTip(i18n("Choose the filtered column here")); + d->filterList->setWhatsThis(i18n("Allows you to apply the item filter only on the specified column here. \"All\" column means no specific filter.")); + + d->filterList->addItem(i18n("All")); + + d->filterList->setSizeAdjustPolicy(QComboBox::AdjustToContents); + + connect(d->filterList, SIGNAL(activated(int)), d->filterLine, SLOT(setFocus())); + connect(d->filterList, SIGNAL(activated(int)), this, SLOT(changeColumnFilter(int))); + connect(d->filterList, SIGNAL(activated(int)), d->filterLine, SLOT(updateSearch())); + +} + +void LogViewFilterWidget::updateFilterColumns(const LogViewColumns& columns) { + logDebug() << "Changing columns..." << endl; + + //We first delete all items + d->filterList->clear(); + + //Then we insert the default items + d->filterList->addItem(i18n("All")); + + foreach(const LogViewColumn& column, columns.columns()) { + if (column.isFiltered()==true) { + d->filterList->addItem(column.columnName()); + } + } + + d->filterList->setCurrentIndex(0); +} + +void LogViewFilterWidget::changeColumnFilter(int column) { + //The user select all columns + if (column==0) { + logDebug() << "Searching on all columns" << endl; + + d->filterLine->setSearchColumns(QList()); + return; + } + + logDebug() << "Searching on " << d->filterList->currentIndex() << " column" << endl; + + QList filterColumns; + //currentIndex() - 1 to do not count the "All" columns item + filterColumns.append(d->filterList->currentIndex() - 1); + + d->filterLine->setSearchColumns(filterColumns); + +} + +KComboBox* LogViewFilterWidget::filterList() { + return d->filterList; +} + +LogViewWidgetSearchLine* LogViewFilterWidget::filterLine() { + return d->filterLine; +} + +#include "logViewFilterWidget.moc" diff --git a/ksystemlog/src/lib/logViewFilterWidget.h b/ksystemlog/src/lib/logViewFilterWidget.h new file mode 100644 index 00000000..f26ec0df --- /dev/null +++ b/ksystemlog/src/lib/logViewFilterWidget.h @@ -0,0 +1,87 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef LOG_VIEW_FILTER_WIDGET_H +#define LOG_VIEW_FILTER_WIDGET_H + +#include + +#include + +#include "logViewColumns.h" + +class LogViewWidget; +class LogViewWidgetSearchLine; +class KComboBox; + +class LogViewFilterWidgetPrivate; + +class LogViewFilterWidget : public QWidget { + + Q_OBJECT + +public: + LogViewFilterWidget(); + + ~LogViewFilterWidget(); + + KComboBox* filterList(); + LogViewWidgetSearchLine* filterLine(); + +public slots: + void updateFilterColumns(const LogViewColumns& list); + +private slots: + void changeColumnFilter(int column); + +signals: + void treeWidgetUpdated(); + +private: + void initSearchListFilter(); + + LogViewFilterWidgetPrivate* const d; + + +}; + + +class LogViewWidgetSearchLine : public KTreeWidgetSearchLine { + + Q_OBJECT + +public: + LogViewWidgetSearchLine(); + + ~LogViewWidgetSearchLine(); + +public: + /** + * Reimplemented just to send a signal _AFTER_ the tree updating + */ + void updateSearch(const QString& pattern = QString()); + +signals: + void treeWidgetUpdated(); + +}; + +#endif //LOG_VIEW_FILTER_WIDGET_H diff --git a/ksystemlog/src/lib/logViewModel.cpp b/ksystemlog/src/lib/logViewModel.cpp new file mode 100644 index 00000000..6068b553 --- /dev/null +++ b/ksystemlog/src/lib/logViewModel.cpp @@ -0,0 +1,255 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logViewModel.h" + + +#include "view.h" +#include "logLine.h" + +#include "logViewWidgetItem.h" +#include "logViewWidget.h" +#include "logModeItemBuilder.h" + +#include "logging.h" + +#include "ksystemlogConfig.h" + +class LogViewModelPrivate { +public: + + LogViewWidget* logViewWidget; + + LogViewWidgetItem* oldestItem; + + int concurrentMultipleInsertions; + +}; + +LogViewModel::LogViewModel(LogViewWidget* logViewWidget) : + QObject(logViewWidget), + d(new LogViewModelPrivate()) + { + + d->logViewWidget = logViewWidget; + d->oldestItem = NULL; + + d->concurrentMultipleInsertions = 0; + +} + +LogViewModel::~LogViewModel() { + delete d; +} + + +bool LogViewModel::logLineAlreadyExists(LogLine* line) const { + LogViewWidgetItem* item = d->logViewWidget->findItem(line); + if (item!=NULL) + return true; + + return false; +} + +QList LogViewModel::logLines() { + return d->logViewWidget->logLines(); +} + +int LogViewModel::itemCount() const { + return d->logViewWidget->itemCount(); +} + +bool LogViewModel::isEmpty() const { + if (d->logViewWidget->itemCount() == 0) + return true; + + return false; +} + +void LogViewModel::removeRecentStatusOfLogLines() { + //The older lines are no longer recent + foreach(LogViewWidgetItem* item, d->logViewWidget->items()) { + item->logLine()->setRecent(false); + } +} + + +void LogViewModel::startingMultipleInsertions(Analyzer::ReadingMode /*readingMode*/) { + bool hasLocked = false; + + //Check the lock before adding this as locker + if (lockMultipleInsertions() == true) { + hasLocked = true; + } + + //Add a lock + d->concurrentMultipleInsertions++; + + if (hasLocked == true) { + logDebug() << "Starting multiple insertions..." << endl; + + emit( processingMultipleInsertions(true) ); + + d->logViewWidget->setUpdatesEnabled(false); + + //Remove all recent states of previous log lines + removeRecentStatusOfLogLines(); + } + +} + +void LogViewModel::endingMultipleInsertions(Analyzer::ReadingMode readingMode, int insertedLogLineCount) { + //Remove a lock + d->concurrentMultipleInsertions--; + + if (lockMultipleInsertions() == true) { + logDebug() << "Ending multiple insertions..." << endl; + + //Scroll to the newest item if some lines have been added + if (insertedLogLineCount>0) { + d->logViewWidget->scrollToNewestItem(); + } + + if (readingMode == Analyzer::FullRead) { + d->logViewWidget->resizeColumns(); + } + + logDebug() << "Enabling log view widget refresh..." << endl; + d->logViewWidget->setUpdatesEnabled(true); + + emit( processingMultipleInsertions(false) ); + } +} + +bool LogViewModel::lockMultipleInsertions() { + if (d->concurrentMultipleInsertions == 0) { + return true; + } + + //Debug messages + if (d->concurrentMultipleInsertions > 0) { + logDebug() << "Existing multiple insertions request is still active" << endl; + } + else if (d->concurrentMultipleInsertions < 0) { + logError() << "Existing multiple insertions forgot to call this method" << endl; + } + + return false; +} + +bool LogViewModel::isProcessingMultipleInsertions() const { + if (d->concurrentMultipleInsertions == 0) + return false; + else + return true; +} + +void LogViewModel::clear() { + d->logViewWidget->clear(); + + //Reinit Oldest item + d->oldestItem = NULL; +} + +bool LogViewModel::isNewer(LogLine* newLine) const { + //No element in the list in this case + if (d->oldestItem==NULL) + return true; + + if (newLine->isNewerThan( *(d->oldestItem->logLine()) ) ) + return true; + + return false; +} + +void LogViewModel::removeOldestLogLine() { + //logDebug() << "Removing oldest log line" << endl; + + if (isEmpty()==true) { + return; + } + + if (d->oldestItem==NULL) { + logWarning() << "Oldest item is null" << endl; + return; + } + + //Remove the oldest item from the list + d->logViewWidget->takeTopLevelItem(d->logViewWidget->indexOfTopLevelItem(d->oldestItem)); + + delete d->oldestItem; + d->oldestItem = NULL; + + //Find the next oldest item + foreach(LogViewWidgetItem* item, d->logViewWidget->items()) { + if (d->oldestItem==NULL) { + d->oldestItem = item; + continue; + } + + if (d->oldestItem->logLine()->isNewerThan( *(item->logLine()) )) { + d->oldestItem = item; + } + } +} + +void LogViewModel::insert(LogLine* line) { + //The item is automatically added to the LogViewWidget + LogViewWidgetItem* item = new LogViewWidgetItem(d->logViewWidget, line); + + //Update the oldest item + if (d->oldestItem==NULL) { + d->oldestItem = item; + } + else if (d->oldestItem->logLine()->isNewerThan(*line)) { + d->oldestItem = item; + } +} + +bool LogViewModel::insertNewLogLine(LogLine* line) { + //If the Delete Duplicated Line option is checked + if (KSystemLogConfig::deleteDuplicatedLines()==true) { + if (logLineAlreadyExists(line)==true) { + delete line; + return false; + } + } + + //If there is still space in the buffer + if (itemCount()logItems() << endl; + + return false; +} + +#include "logViewModel.moc" diff --git a/ksystemlog/src/lib/logViewModel.h b/ksystemlog/src/lib/logViewModel.h new file mode 100644 index 00000000..f9e465e0 --- /dev/null +++ b/ksystemlog/src/lib/logViewModel.h @@ -0,0 +1,95 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_VIEW_MODEL_H_ +#define _LOG_VIEW_MODEL_H_ + +#include + +#include "globals.h" +#include "analyzer.h" + +class LogLine; +class LogViewWidget; + +class LogViewModelPrivate; + +class LogViewModel : public QObject { + + Q_OBJECT + + public: + LogViewModel(LogViewWidget* logViewWidget); + + virtual ~LogViewModel(); + + /** + * Clear the model + */ + void clear(); + + bool insertNewLogLine(LogLine* line); + + int itemCount() const; + bool isEmpty() const; + + bool isProcessingMultipleInsertions() const; + + void startingMultipleInsertions(Analyzer::ReadingMode readingMode); + void endingMultipleInsertions(Analyzer::ReadingMode readingMode, int insertedLogLineCount); + + QList logLines(); + + signals: + void processingMultipleInsertions(bool currentlyInserting); + + private: + /** + * Prevent crossed multiple insertions between each LogFileReaders + */ + bool lockMultipleInsertions(); + + void setFirstReadProcessed(); + + bool logLineAlreadyExists(LogLine* line) const; + + bool isNewer(LogLine* line) const; + + /** + * Remove the oldest line + */ + void removeOldestLogLine(); + + /** + * Insert this line + */ + void insert(LogLine* line); + + /** + * Remove recent status on previously new log lines + */ + void removeRecentStatusOfLogLines(); + + LogViewModelPrivate* const d; + +}; + +#endif //_LOG_VIEW_MODEL_H_ diff --git a/ksystemlog/src/lib/logViewSearchWidget.cpp b/ksystemlog/src/lib/logViewSearchWidget.cpp new file mode 100644 index 00000000..eeadbbeb --- /dev/null +++ b/ksystemlog/src/lib/logViewSearchWidget.cpp @@ -0,0 +1,336 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logViewSearchWidget.h" + +#include +#include +#include +#include +#include +#include + + +#include +#include +#include + +#include "logging.h" +#include "logViewWidget.h" +#include "logViewWidgetItem.h" + +class LogViewSearchWidgetPrivate { +public: + + LogViewWidget* logViewWidget; + + QColor searchLineBaseColor; + QColor searchLineTextColor; + + QTimer* messageHidingTimer; +}; + +LogViewSearchWidget::LogViewSearchWidget() : + d(new LogViewSearchWidgetPrivate()) { + + d->logViewWidget = NULL; + + setupUi(this); + + //Get the searchLine base color to be able to restore it later + d->searchLineBaseColor = searchLine->palette().color(QPalette::Base); + d->searchLineTextColor = searchLine->palette().color(QPalette::Text); + + //Default configuration of the hiding message timer + d->messageHidingTimer = new QTimer(this); + d->messageHidingTimer->setSingleShot(true); + d->messageHidingTimer->setInterval(2000); + connect(d->messageHidingTimer, SIGNAL(timeout()), this, SLOT(hideMessage())); + + //The message widget is hidden by default + hideMessage(); + + closeButton->setIcon(KIcon( QLatin1String( "dialog-close" ))); + connect(closeButton, SIGNAL(clicked()), this, SLOT(hide())); + + next->setIcon(KIcon( QLatin1String( "arrow-down" ))); + connect(next, SIGNAL(clicked()), this, SLOT(findNext())); + + previous->setIcon(KIcon( QLatin1String( "arrow-up" ))); + connect(previous, SIGNAL(clicked()), this, SLOT(findPrevious())); + + searchLabel->setBuddy(searchLine); + + connect(searchLine, SIGNAL(textEdited(QString)), this, SLOT(findFirst(QString))); + connect(searchLine, SIGNAL(textEdited(QString)), this, SLOT(highlightAll())); + + connect(searchLine, SIGNAL(returnPressed()), this, SLOT(findNext())); + + connect(caseSensitive, SIGNAL(clicked()), this, SLOT(findFirst())); + + connect(caseSensitive, SIGNAL(clicked()), this, SLOT(highlightAll())); + connect(highlightAllButton, SIGNAL(clicked()), this, SLOT(highlightAll())); + + findFirst(searchLine->text()); + +} + +LogViewSearchWidget::~LogViewSearchWidget() { + //widgets and timer are automatically deleted by Qt + + delete d; +} + + +void LogViewSearchWidget::displaySearch() { + searchLine->setFocus(); + searchLine->setSelection(0, searchLine->text().length()); + show(); +} + +void LogViewSearchWidget::setTreeWidget(LogViewWidget* logViewWidget) { + d->logViewWidget = logViewWidget; +} + +void LogViewSearchWidget::findFirst(const QString & text) { + bool textIsNotEmpty = !text.isEmpty(); + next->setEnabled(textIsNotEmpty); + previous->setEnabled(textIsNotEmpty); + if(textIsNotEmpty) + findFirst(); +} + +void LogViewSearchWidget::findFirst() { + internalFind(NULL, LogViewSearchWidget::Next); +} + +void LogViewSearchWidget::findNext() { + logDebug() << "Finding next" << endl; + + LogViewWidgetItem* lastSelectedItem = d->logViewWidget->lastSelectedItem(); + internalFind(lastSelectedItem, LogViewSearchWidget::Next); +} + +void LogViewSearchWidget::findPrevious() { + logDebug() << "Finding previous" << endl; + + LogViewWidgetItem* firstSelectedItem = d->logViewWidget->firstSelectedItem(); + internalFind(firstSelectedItem, LogViewSearchWidget::Previous); +} + +void LogViewSearchWidget::internalFind(LogViewWidgetItem* fromItem, Direction direction) { + if (searchLine->text().isEmpty()) { + return; + } + + QTreeWidgetItemIterator it(d->logViewWidget, QTreeWidgetItemIterator::NotHidden); + initIterator(it, direction); + + //Go to the selected position + 1 (if we already found an item) + if (fromItem != NULL) { + + while ( *it != NULL) { + LogViewWidgetItem* item=static_cast (*it); + + if (item == fromItem) { + iteratorJump(it, direction); + break; + } + + iteratorJump(it, direction); + } + + } + + //Iterates to fromItem +1 to the last item of the list + while ( *it != NULL) { + LogViewWidgetItem* item=static_cast (*it); + + bool found = findItem(item); + if (found == true) + return; + + iteratorJump(it, direction); + } + + //If we do not begin the search from the beginning, we do it now + if (fromItem != NULL) { + it = QTreeWidgetItemIterator(d->logViewWidget, QTreeWidgetItemIterator::NotHidden); + initIterator(it, direction); + + LogViewWidgetItem* item = NULL; + while ( *it != NULL && item != fromItem) { + item=static_cast (*it); + + bool found = findItem(item); + if (found == true) { + showMessage(i18n("Reached end of list."), QLatin1String( "dialog-information" )); + return; + } + + iteratorJump(it, direction); + } + + } + + setSearchLineNotFound(true); + +} + +inline void LogViewSearchWidget::initIterator(QTreeWidgetItemIterator& it, Direction direction) { + //Previous direction : Go to the last item + if (direction == LogViewSearchWidget::Previous) { + QTreeWidgetItemIterator testedIterator(it); + while (true) { + ++testedIterator; + if ( *testedIterator == NULL ) { + break; + } + + ++it; + + } + } +} + +inline void LogViewSearchWidget::iteratorJump(QTreeWidgetItemIterator& it, Direction direction) { + if (direction == LogViewSearchWidget::Next) { + ++it; + } + else { + --it; + } +} + +bool LogViewSearchWidget::compareItem(LogViewWidgetItem* item) { + Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; + if (caseSensitive->isChecked()) { + caseSensitivity = Qt::CaseSensitive; + } + + if (searchLine->text().isEmpty()) + return false; + + if (item->logLine()->exportToText().contains(searchLine->text(), caseSensitivity)) { + return true; + } + + return false; +} + +bool LogViewSearchWidget::findItem(LogViewWidgetItem* item) { + if (compareItem(item) == true) { + unselectAll(); + + setSearchLineNotFound(false); + item->setSelected(true); + d->logViewWidget->setCurrentItem(item); + d->logViewWidget->scrollToItem(item); + return true; + } + + return false; +} + +void LogViewSearchWidget::setSearchLineNotFound(bool notFound) { + QPalette palette = searchLine->palette(); + if (notFound == true) { + palette.setColor(QPalette::Base, QColor(255, 102, 102)); //or Qt::red or QColor(235, 0, 0) + palette.setColor(QPalette::Text, QColor(255, 255, 255)); + } + else { + palette.setColor(QPalette::Base, d->searchLineBaseColor); + palette.setColor(QPalette::Text, d->searchLineTextColor); + } + + searchLine->setPalette(palette); + + if (notFound==true) + showMessage(i18n("Phrase not found."), QLatin1String( "dialog-error" )); + else + hideMessage(); + +} + +void LogViewSearchWidget::unselectAll() { + QList selectedItems = d->logViewWidget->selectedItems(); + foreach(QTreeWidgetItem* item, selectedItems) { + item->setSelected(false); + } +} + +void LogViewSearchWidget::showMessage(const QString& text, const QString& iconText) { + message->setText(text); + message->show(); + + messageIcon->setPixmap(SmallIcon(iconText)); + messageIcon->show(); + + d->messageHidingTimer->start(); + +} + +void LogViewSearchWidget::hideMessage() { + message->hide(); + messageIcon->hide(); + + d->messageHidingTimer->stop(); +} + +void LogViewSearchWidget::highlightAll() { + if (highlightAllButton->isChecked()) { + unlightAll(); + + logDebug() << "Highlighting all" << endl; + QTreeWidgetItemIterator it(d->logViewWidget, QTreeWidgetItemIterator::All); + while ( *it != NULL ) { + LogViewWidgetItem* item=static_cast (*it); + + if (compareItem(item) == true) { + item->setBackgroundColor(item->columnCount()-1, QColor(255, 255, 16*8+11)); + } + + ++it; + } + + } + else { + unlightAll(); + + } + +} + +void LogViewSearchWidget::unlightAll() { + logDebug() << "Unlighting all" << endl; + + QTreeWidgetItemIterator it(d->logViewWidget, QTreeWidgetItemIterator::All); + while ( *it != NULL ) { + LogViewWidgetItem* item=static_cast (*it); + + //We retrieve the default column background using the first column data, where the background never changes + item->setBackground(item->columnCount()-1, qvariant_cast(item->data(0, Qt::BackgroundRole))); + + ++it; + } +} + +#include "logViewSearchWidget.moc" diff --git a/ksystemlog/src/lib/logViewSearchWidget.h b/ksystemlog/src/lib/logViewSearchWidget.h new file mode 100644 index 00000000..94f709f2 --- /dev/null +++ b/ksystemlog/src/lib/logViewSearchWidget.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_VIEW_SEARCH_WIDGET_H_ +#define _LOG_VIEW_SEARCH_WIDGET_H_ + +#include +#include + +#include "ui_logViewSearchWidgetBase.h" + +class LogViewWidget; +class LogViewWidgetItem; + +class LogViewSearchWidgetPrivate; + +class LogViewSearchWidget : public QWidget, public Ui::LogViewSearchWidgetBase { + + Q_OBJECT + +public: + LogViewSearchWidget(); + + ~LogViewSearchWidget(); + + void setTreeWidget(LogViewWidget* logViewWidget); + +public slots: + void displaySearch(); + + void findFirst(); + + void findFirst(const QString &); + void findNext(); + void findPrevious(); + +private slots: + void hideMessage(); + + void highlightAll(); + +private: + enum Direction { + Next, + Previous + }; + Q_DECLARE_FLAGS(Directions, Direction) + + void showMessage(const QString& text, const QString& iconText); + + void internalFind(LogViewWidgetItem* fromItem, Direction direction); + + void setSearchLineNotFound(bool notFound); + void unselectAll(); + bool findItem(LogViewWidgetItem* item); + + bool compareItem(LogViewWidgetItem* item); + + void unlightAll(); + + inline void initIterator(QTreeWidgetItemIterator& it, Direction direction); + inline void iteratorJump(QTreeWidgetItemIterator& it, Direction direction); + + LogViewSearchWidgetPrivate* const d; + +}; + +#endif //_LOG_VIEW_SEARCH_WIDGET_H_ diff --git a/ksystemlog/src/lib/logViewSearchWidgetBase.ui b/ksystemlog/src/lib/logViewSearchWidgetBase.ui new file mode 100644 index 00000000..0f1d1db0 --- /dev/null +++ b/ksystemlog/src/lib/logViewSearchWidgetBase.ui @@ -0,0 +1,129 @@ + + LogViewSearchWidgetBase + + + + 0 + 0 + 705 + 27 + + + + + -1 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + true + + + + + + + Find: + + + + + + + + 0 + 0 + + + + + + + + &Next + + + true + + + + + + + &Previous + + + true + + + + + + + Match &case + + + + + + + &Highlight all + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 40 + 20 + + + + + + + + + diff --git a/ksystemlog/src/lib/logViewWidget.cpp b/ksystemlog/src/lib/logViewWidget.cpp new file mode 100644 index 00000000..68396669 --- /dev/null +++ b/ksystemlog/src/lib/logViewWidget.cpp @@ -0,0 +1,306 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logViewWidget.h" + +#include +#include + +#include +#include + +#include "logging.h" + +#include "logViewColumn.h" + +#include "logViewWidgetItem.h" +#include "logLine.h" +#include "logViewModel.h" + +#include "ksystemlogConfig.h" + +class LogViewWidgetPrivate { +public: + LogViewModel* logViewModel; + + QActionGroup* headersTogglingActions; + +}; + +LogViewWidget::LogViewWidget(QWidget* parent) : + QTreeWidget(parent), + d(new LogViewWidgetPrivate()) { + + //TODO Add this setWhatsThis() to all columns each time they change + //setWhatThis(i18n("

This is the main view of KSystemLog. It displays the last lines of the selected log. Please see the documentation to discovers the meaning of each icons and existing log.

Log lines in bold are the last added to the list.

")); + + QStringList headerLabels; + headerLabels.append(QLatin1String( "Date" )); + headerLabels.append(QLatin1String( "Message" )); + + d->logViewModel = new LogViewModel(this); + d->headersTogglingActions = new QActionGroup(this); + d->headersTogglingActions->setExclusive(false); + connect(d->headersTogglingActions, SIGNAL(triggered(QAction*)), this, SLOT(toggleHeader(QAction*))); + + setHeaderLabels(headerLabels); + + //Header + header()->setContextMenuPolicy(Qt::ActionsContextMenu); + header()->setMovable(true); + + setSortingEnabled(true); + sortItems(0, Qt::AscendingOrder); + + setAnimated(true); + + setRootIsDecorated(false); + + setAllColumnsShowFocus(true); + + setAlternatingRowColors(true); + + setSelectionMode(QAbstractItemView::ExtendedSelection); + + setContextMenuPolicy(Qt::ActionsContextMenu); +} + +LogViewWidget::~LogViewWidget() { + delete d->logViewModel; + + delete d; +} + +void LogViewWidget::setColumns(const LogViewColumns& columns) { + logDebug() << "Updating columns using " << columns << "..." << endl; + + //First, delete all current columns + setColumnCount(0); + + setHeaderLabels(columns.toStringList()); + + sortItems(0, Qt::AscendingOrder); + + //Remove previous header actions + QListIterator it(d->headersTogglingActions->actions()); + it.toBack(); + while (it.hasPrevious()) { + QAction* action = it.previous(); + + header()->removeAction( action ); + d->headersTogglingActions->removeAction( action ); + + delete action; + } + + //Add new actions + int columnIndex = 0; + + foreach(const LogViewColumn &column, columns.columns()) { + QAction* action = new QAction(this); + action->setText(column.columnName()); + //helloAction->setIcon(KIcon( QLatin1String( "media-playback-start" ))); + //helloAction->setShortcut(Qt::CTRL + Qt::Key_M); + action->setCheckable(true); + action->setChecked(true); + action->setToolTip(i18n("Display/Hide the '%1' column", column.columnName())); + action->setData(QVariant(columnIndex)); + + d->headersTogglingActions->addAction(action); + + ++columnIndex; + } + + header()->addActions(d->headersTogglingActions->actions()); + + + emit columnsChanged(columns); + + logDebug() << "Log View Widget updated..." << endl; + +} + +void LogViewWidget::resizeColumns() { + //Resize all columns except the last one (which always take the last available space) + for (int i=0; i0 ) + QTreeWidget::selectAll(); +} + +int LogViewWidget::itemCount() const { + return topLevelItemCount(); +} + +QList LogViewWidget::logLines() { + QList logLines; + + QTreeWidgetItemIterator it(this); + while (*it != NULL) { + LogViewWidgetItem* item = static_cast(*it); + logLines.append(item->logLine()); + ++it; + } + + return logLines; +} + +LogViewWidgetItem* LogViewWidget::findNewestItem() { + LogViewWidgetItem* newestItem = NULL; + + QTreeWidgetItemIterator it(this); + while (*it != NULL) { + LogViewWidgetItem* item = static_cast(*it); + if (newestItem==NULL || newestItem->logLine()->isOlderThan( *(item->logLine()) )) { + newestItem = item; + } + + ++it; + } + + return newestItem; + +} + +LogViewWidgetItem* LogViewWidget::findItem(LogLine* searchedLogLine) { + QTreeWidgetItemIterator it(this); + while (*it != NULL) { + LogViewWidgetItem* item = static_cast(*it); + if (item->logLine()->equals(*searchedLogLine)) + return item; + + ++it; + } + + return NULL; +} + +QList LogViewWidget::items() { + QList items; + + QTreeWidgetItemIterator it(this); + while (*it != NULL) { + items.append(static_cast(*it)); + ++it; + } + + return items; +} + +LogViewModel* LogViewWidget::model() const { + return d->logViewModel; +} + +bool LogViewWidget::hasItemsSelected() { + return ( !selectedItems ().isEmpty() ); +} + +LogViewWidgetItem* LogViewWidget::firstSelectedItem() { + QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected); + + //Returns the first selected item or NULL is there is no item selected + return static_cast (*it); +} + +LogViewWidgetItem* LogViewWidget::lastSelectedItem() { + QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected); + + QTreeWidgetItem* item=NULL; + while (*it) { + item=(*it); + + it++; + } + + //Returns the last selected item or NULL is there is no item selected + return static_cast(item); +} + + +void LogViewWidget::expandAll() { + QTreeWidgetItemIterator it(this); + while (*it != NULL) { + expandItem(*it); + ++it; + } +} + +void LogViewWidget::collapseAll() { + QTreeWidgetItemIterator it(this); + while (*it != NULL) { + collapseItem(*it); + ++it; + } +} + +void LogViewWidget::toggleToolTip(bool enabled) { + logDebug() << "Toggle tool tip " << enabled << endl; + + QTreeWidgetItemIterator it(this); + while (*it != NULL) { + LogViewWidgetItem* item = static_cast(*it); + item->toggleToolTip(enabled); + + ++it; + } + +} + +void LogViewWidget::scrollToNewestItem() { + logDebug() << "Scrolling to the newest item..." << endl; + + //Scroll to last item if requested + if (KSystemLogConfig::newLinesDisplayed() == true) { + LogViewWidgetItem* newestItem = findNewestItem(); + if (newestItem!=NULL) { + scrollToItem(newestItem); + } + } +} + +int LogViewWidget::notHiddenItemCount() { + int count = 0; + + QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::NotHidden); + while (*it != NULL) { + count++; + ++it; + } + + return count; +} + +void LogViewWidget::toggleHeader(QAction* action) { + logDebug() << "Toggling header" << endl; + + int columnIndex = action->data().toInt(); + if (header()->isSectionHidden(columnIndex) == true) + header()->setSectionHidden(columnIndex, false); + else + header()->setSectionHidden(columnIndex, true); +} + + +#include "logViewWidget.moc" diff --git a/ksystemlog/src/lib/logViewWidget.h b/ksystemlog/src/lib/logViewWidget.h new file mode 100644 index 00000000..b4c55ca6 --- /dev/null +++ b/ksystemlog/src/lib/logViewWidget.h @@ -0,0 +1,87 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef LOG_VIEW_WIDGET_H +#define LOG_VIEW_WIDGET_H + +#include +#include + +#include "logViewColumns.h" + +class LogViewWidgetItem; +class LogViewModel; +class LogLine; + +class LogViewWidgetPrivate; + +class LogViewWidget : public QTreeWidget { + + Q_OBJECT + + public: + LogViewWidget(QWidget* parent = NULL); + + virtual ~LogViewWidget(); + virtual void selectAll(); + void setColumns(const LogViewColumns& columns); + + int itemCount() const; + int notHiddenItemCount(); + + QList items(); + QList logLines(); + + /** + * Return the related widget item of this logLine or NULL if it has not been found + */ + LogViewWidgetItem* findItem(LogLine* logLine); + + LogViewWidgetItem* findNewestItem(); + + LogViewModel* model() const; + + bool hasItemsSelected(); + LogViewWidgetItem* firstSelectedItem(); + LogViewWidgetItem* lastSelectedItem(); + + void scrollToNewestItem(); + + void expandAll(); + void collapseAll(); + + void resizeColumns(); + + public slots: + void toggleToolTip(bool enabled); + + signals: + void columnsChanged(const LogViewColumns& columns); + + private slots: + void toggleHeader(QAction* action); + + private: + LogViewWidgetPrivate* const d; +}; + + +#endif //LOG_VIEW_WIDGET_H diff --git a/ksystemlog/src/lib/logViewWidgetItem.cpp b/ksystemlog/src/lib/logViewWidgetItem.cpp new file mode 100644 index 00000000..ad467abe --- /dev/null +++ b/ksystemlog/src/lib/logViewWidgetItem.cpp @@ -0,0 +1,78 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logViewWidgetItem.h" + +//Qt includes + +#include + + +#include + +//KDE includes +#include +#include +#include "logging.h" + +#include "logModeItemBuilder.h" +#include "logViewWidget.h" +#include "logViewWidgetItem.h" + +#include "logMode.h" + +LogViewWidgetItem::LogViewWidgetItem(LogViewWidget* list, LogLine* l) : + QTreeWidgetItem(list), + line(l) + { + + //Add this item to the LogLine, to let the LogLine initialize it + line->setItem(this); +} + +LogViewWidgetItem::~LogViewWidgetItem() { + delete line; +} + +LogLine* LogViewWidgetItem::logLine() const { + return line; +} + +void LogViewWidgetItem::toggleToolTip(bool displayed) { + if (displayed == true) + setToolTip(columnCount()-1, line->logMode()->itemBuilder()->createToolTipText(line)); + else + setToolTip(columnCount()-1, QLatin1String( "" )); +} + +bool LogViewWidgetItem::operator<(const QTreeWidgetItem & other) const { + int sortedColumn = treeWidget()->sortColumn(); + + //If we sort items by date (always the first column) + if (sortedColumn == 0) { + const LogViewWidgetItem& otherItem=static_cast (other); + return line->isOlderThan( *(otherItem.logLine()) ); + } + //Default sorting + else { + return text(sortedColumn) < other.text(sortedColumn); + } +} diff --git a/ksystemlog/src/lib/logViewWidgetItem.h b/ksystemlog/src/lib/logViewWidgetItem.h new file mode 100644 index 00000000..e13eae9c --- /dev/null +++ b/ksystemlog/src/lib/logViewWidgetItem.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef LOG_VIEW_WIDGET_ITEM_H +#define LOG_VIEW_WIDGET_ITEM_H + + +#include + + +#include "globals.h" + +#include "logLine.h" + +class LogViewWidget; + +class LogLine; + +class LogViewWidgetItem: public QTreeWidgetItem { + + public: + LogViewWidgetItem(LogViewWidget* logViewWidget, LogLine* line); + + ~LogViewWidgetItem(); + + bool operator<(const QTreeWidgetItem & other) const; + + LogLine* logLine() const; + + void toggleToolTip(bool displayed); + + + private: + //TODO Move this log line to QTreeWidgetItem::data() method + LogLine* line; + +}; + +#endif // LOG_VIEW_WIDGET_ITEM_H diff --git a/ksystemlog/src/lib/logging.h b/ksystemlog/src/lib/logging.h new file mode 100644 index 00000000..f1c9baf4 --- /dev/null +++ b/ksystemlog/src/lib/logging.h @@ -0,0 +1,89 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + + +#ifndef _KSYSTEMLOG_LOGGING_H_ +#define _KSYSTEMLOG_LOGGING_H_ + + +#include + +/** + * Existing logging functions are : + * - logDebug() + * - logWarning() + * - logError() + * - logFatal() + * + * Use it like kDebug() function : + * logDebug() << "Debug message" << list.size() << endl; + */ + +/* + * Log prefix of any log message + * The regexp removes the parameters and return type of the method prototype + * + * Example : + * [LogManager::synchronizeLogView(547)] Synchronizing the model... + * + */ + +#define KSYSTEM_LOG_KDEBUG_ID 87400 + +/** + * Colored logging + +#define LOG_DEFAULT_COLOR "\033[m" + +#define LOG_DEBUG_COLOR "\033[37m" +#define LOG_ERROR_COLOR "\033[31m" +#define LOG_FATAL_COLOR "\033[31m" +#define LOG_WARNING_COLOR "\033[33m" + +#define LOG_PREFIX(logColor) logColor << "[" << __LINE__ << "]" << LOG_DEFAULT_COLOR + + */ + +#define LOG_DEFAULT_COLOR "" + +#define LOG_DEBUG_COLOR "" +#define LOG_ERROR_COLOR "" +#define LOG_FATAL_COLOR "" +#define LOG_WARNING_COLOR "" + +#define LOG_PREFIX(logColor) "" + +#if !defined(KDE_NO_DEBUG_OUTPUT) + #define logDebug() kDebug(KSYSTEM_LOG_KDEBUG_ID) << LOG_PREFIX(LOG_DEBUG_COLOR) +#else // KDE_NO_DEBUG_OUTPUT + #define logDebug() kDebug(KSYSTEM_LOG_KDEBUG_ID) +#endif + +#if !defined(KDE_NO_WARNING_OUTPUT) + #define logWarning() kWarning(KSYSTEM_LOG_KDEBUG_ID) << LOG_PREFIX(LOG_WARNING_COLOR) +#else // KDE_NO_WARNING_OUTPUT + #define logWarning() kWarning(KSYSTEM_LOG_KDEBUG_ID) +#endif + +#define logError() kError(KSYSTEM_LOG_KDEBUG_ID) << LOG_PREFIX(LOG_ERROR_COLOR) +#define logFatal() kFatal(KSYSTEM_LOG_KDEBUG_ID) << LOG_PREFIX(LOG_FATAL_COLOR) + +#endif // _KSYSTEMLOG_LOGGING_H_ diff --git a/ksystemlog/src/lib/multipleActions.cpp b/ksystemlog/src/lib/multipleActions.cpp new file mode 100644 index 00000000..c892fc90 --- /dev/null +++ b/ksystemlog/src/lib/multipleActions.cpp @@ -0,0 +1,51 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "multipleActions.h" + +#include + +#include "logging.h" + +MultipleActions::MultipleActions(const KIcon& icon, const QString& text, QObject* parent) { + action = new KActionMenu(icon, text, parent); + +} + +MultipleActions::~MultipleActions() { + delete action; +} + +QList MultipleActions::innerActions() { + return actions; +} + +QAction* MultipleActions::actionMenu() { + return action; +} + +void MultipleActions::addInnerAction(QAction* innerAction) { + action->addAction(innerAction); + actions.append(innerAction); + +} + +#include "multipleActions.moc" diff --git a/ksystemlog/src/lib/multipleActions.h b/ksystemlog/src/lib/multipleActions.h new file mode 100644 index 00000000..73df9778 --- /dev/null +++ b/ksystemlog/src/lib/multipleActions.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _MULTIPLE_ACTIONS_H_ +#define _MULTIPLE_ACTIONS_H_ + +#include +#include +#include + +#include +#include + +#include "globals.h" + +#include "logModeAction.h" + +class QAction; + +/** + * This class is used by LogModeFactory to provide multiple actions + * and be able to retrieve them when necessary, using actions(). + * This method will return QAction added using addInnerAction(). + */ +class MultipleActions : public LogModeAction { + + Q_OBJECT + + public: + MultipleActions(const KIcon& icon, const QString& text, QObject* parent); + + virtual ~MultipleActions(); + + QList innerActions(); + + QAction* actionMenu(); + + /** + * This method is not called addAction() to avoid name collision with + * parent class + */ + void addInnerAction(QAction* action); + + private: + QList actions; + + KActionMenu* action; +}; + +#endif // _MULTIPLE_ACTIONS_H_ diff --git a/ksystemlog/src/lib/processOutputLogFileReader.cpp b/ksystemlog/src/lib/processOutputLogFileReader.cpp new file mode 100644 index 00000000..2ec5f0f1 --- /dev/null +++ b/ksystemlog/src/lib/processOutputLogFileReader.cpp @@ -0,0 +1,238 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "processOutputLogFileReader.h" + +#include +#include +#include + +#include +#include + +#include "logFileReader.h" +#include "logFileReaderPrivate.h" + +#include "logging.h" + + +class ProcessOutputLogFileReaderPrivate : public LogFileReaderPrivate { + +public: + long previousLinesCount; + + QTimer processUpdater; + + QProcess* process; + + QString buffer; + QStringList availableStandardOutput; +}; + + +ProcessOutputLogFileReader::ProcessOutputLogFileReader(const LogFile& logFile) : + LogFileReader(*new ProcessOutputLogFileReaderPrivate(), logFile) { + + init(); +} + + +ProcessOutputLogFileReader::ProcessOutputLogFileReader(ProcessOutputLogFileReaderPrivate& dd, const LogFile& logFile) : + LogFileReader(dd, logFile) { + + init(); +} + +ProcessOutputLogFileReader::~ProcessOutputLogFileReader() { + //d pointer is deleted by the parent class +} + +void ProcessOutputLogFileReader::init() { + Q_D(ProcessOutputLogFileReader); + + //Init current file position + d->previousLinesCount = 0; + d->availableStandardOutput.clear(); + d->process = NULL; + + d->processUpdater.setInterval(PROCESS_OUTPUT_UPDATER_INTERVAL); + connect(&(d->processUpdater), SIGNAL(timeout()), this, SLOT(startProcess())); + + logDebug() << "Using process name " << d->logFile.url().path() << endl; +} + +void ProcessOutputLogFileReader::watchFile(bool enable) { + Q_D(ProcessOutputLogFileReader); + + if (enable == true) { + logDebug() << "Monitoring process : " << d->logFile.url().path() << endl; + + //Reinit current file position + d->previousLinesCount = 0; + + //Launch the timer + d->processUpdater.start(); + + //Launch immediately the process updater + startProcess(); + } + else { + //Stop regularly start process + d->processUpdater.stop(); + } +} + +void ProcessOutputLogFileReader::startProcess() { + logDebug() << "Starting process..." << endl; + + Q_D(ProcessOutputLogFileReader); + + if (d->logFile.url().isValid()==false) { + QString message(i18n("This file is not valid. Please adjust it in the settings of KSystemLog.")); + emit errorOccured(i18n("File Does Not Exist"), message); + emit statusBarChanged(message); + } + + logDebug() << "Starting process..." << endl; + + d->process = new QProcess(); + connect(d->process, SIGNAL(readyReadStandardOutput()), this, SLOT(logFileModified())); + connect(d->process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(emitProcessOutput(int,QProcess::ExitStatus))); + + d->process->start(d->logFile.url().path(), QIODevice::ReadOnly | QIODevice::Text); + + d->process->waitForStarted(); + + logDebug() << "Process started" << endl; +} + +void ProcessOutputLogFileReader::closeProcess() { + logDebug() << "Closing process..." << endl; + + Q_D(ProcessOutputLogFileReader); + + //Get the size file for the next calculation + d->previousLinesCount = d->availableStandardOutput.count(); + logDebug() << "New lines count : " << d->previousLinesCount << " (" << d->logFile.url().path() << ")" << endl; + + d->availableStandardOutput.clear(); + + if(d->process) { + d->process->close(); + delete d->process; + d->process = NULL; + } + + logDebug() << "Process closed" << endl; +} + +void ProcessOutputLogFileReader::emitProcessOutput(int /*exitCode*/, QProcess::ExitStatus exitStatus) { + Q_D(ProcessOutputLogFileReader); + + //First commit last lines of the buffer to the line list + emptyBuffer(); + + logDebug() << "Process terminated" << d->previousLinesCount << "previously /" << d->availableStandardOutput.count() << "currently" << endl; + + if (exitStatus==QProcess::CrashExit) { + QString message(i18n("The process '%1' crashed.", d->logFile.url().path())); + emit errorOccured(i18n("Process Crashed"), message); + emit statusBarChanged(message); + } + + // If there is no new lines + if (d->previousLinesCount == d->availableStandardOutput.count()) { + /* + //Emit an empty log lines list + emit contentChanged(this, false, QStringList()); + */ + } + // If there are new lines in the file, insert only them or this is the first time we read this file + else if (d->previousLinesCount!=0 && d->previousLinesCount <= d->availableStandardOutput.count()) { + logDebug() << "Reading from line " << d->previousLinesCount << " (" << d->logFile.url().path() << ")" << endl; + + QStringList newOutputs; + + int index = d->previousLinesCount - 1; + while (index < d->availableStandardOutput.count()) { + newOutputs.append(d->availableStandardOutput.at(index)); + + ++index; + } + + logDebug() << "Retrieving a part of the file..."<< endl; + + emit contentChanged(this, Analyzer::UpdatingRead, newOutputs); + + } + // Else reread all lines, clear log list + else { + logDebug() << "New process or process already read. Reading entire content" << endl; + + emit contentChanged(this, Analyzer::FullRead, d->availableStandardOutput); + + } + + closeProcess(); + +} + +void ProcessOutputLogFileReader::logFileModified() { + Q_D(ProcessOutputLogFileReader); + + logDebug() << "Content available on process output..." << endl; + + //New added lines + QByteArray bytesOutput = d->process->readAllStandardOutput(); + d->buffer.append(QLatin1String(bytesOutput)); + + //Parse buffer + int endLinePos = d->buffer.indexOf(QLatin1String( "\n" )); + forever { + if (endLinePos==-1) + break; + + //Add the new found lines and + d->availableStandardOutput.append(d->buffer.left(endLinePos)); + d->buffer.remove(0, endLinePos+1); + + endLinePos = d->buffer.indexOf(QLatin1String( "\n" )); + } + + logDebug() << "Received a total of" << d->availableStandardOutput.count() << "new lines" << endl; +} + + +/** + * The buffer could contains some last characters that are added at last + * (Normally useless) + */ +void ProcessOutputLogFileReader::emptyBuffer() { + Q_D(ProcessOutputLogFileReader); + + if (d->buffer.isEmpty() == false) { + logWarning() << "Buffer was not empty !!" << endl; + d->availableStandardOutput.append(d->buffer); + d->buffer.clear(); + } +} + +#include "processOutputLogFileReader.moc" diff --git a/ksystemlog/src/lib/processOutputLogFileReader.h b/ksystemlog/src/lib/processOutputLogFileReader.h new file mode 100644 index 00000000..a18b1712 --- /dev/null +++ b/ksystemlog/src/lib/processOutputLogFileReader.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _PROCESS_OUTPUT_LOG_FILE_READER_H_ +#define _PROCESS_OUTPUT_LOG_FILE_READER_H_ + +#define PROCESS_OUTPUT_UPDATER_INTERVAL 10000 + +#include + +#include "logFile.h" + +#include "logFileReader.h" + +class QProcess; + +class ProcessOutputLogFileReaderPrivate; + +class ProcessOutputLogFileReader : public LogFileReader { + + Q_OBJECT + + public: + + ProcessOutputLogFileReader(const LogFile& logFile); + + virtual ~ProcessOutputLogFileReader(); + + void watchFile(bool enable); + + void setPreviousFilePosition(long previousFilePosition); + + private slots: + void startProcess(); + + void logFileModified(); + + void emitProcessOutput(int, QProcess::ExitStatus); + + private: + void init(); + + void closeProcess(); + + void emptyBuffer(); + + protected: + ProcessOutputLogFileReader(ProcessOutputLogFileReaderPrivate& dd, const LogFile& logFile); + + private: + Q_DECLARE_PRIVATE(ProcessOutputLogFileReader) + +}; + +#endif // _PROCESS_OUTPUT_LOG_FILE_READER_H_ diff --git a/ksystemlog/src/lib/simpleAction.cpp b/ksystemlog/src/lib/simpleAction.cpp new file mode 100644 index 00000000..5b84f01e --- /dev/null +++ b/ksystemlog/src/lib/simpleAction.cpp @@ -0,0 +1,57 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "simpleAction.h" + +#include + +#include "logging.h" + +SimpleAction::SimpleAction(const QIcon& icon, const QString& text, QObject* parent) { + action = new QAction(icon, text, parent); +} + +SimpleAction::SimpleAction(QAction* originAction, QObject* parent) { + + action = new QAction(parent); + action->setIcon(originAction->icon()); + action->setText(originAction->text()); + action->setData(originAction->data()); + action->setToolTip(originAction->toolTip()); + action->setWhatsThis(originAction->whatsThis()); +} + +SimpleAction::~SimpleAction() { + delete action; +} + +QList SimpleAction::innerActions() { + QList actions; + actions.append(action); + + return actions; +} + +QAction* SimpleAction::actionMenu() { + return action; +} + +#include "simpleAction.moc" diff --git a/ksystemlog/src/lib/simpleAction.h b/ksystemlog/src/lib/simpleAction.h new file mode 100644 index 00000000..74f4a120 --- /dev/null +++ b/ksystemlog/src/lib/simpleAction.h @@ -0,0 +1,54 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SIMPLE_ACTIONS_H_ +#define _SIMPLE_ACTIONS_H_ + +#include +#include +#include + +#include "globals.h" + +#include "logModeAction.h" + +class QAction; + +class SimpleAction : public LogModeAction { + + Q_OBJECT + + public: + SimpleAction(const QIcon& icon, const QString& text, QObject* parent); + + SimpleAction(QAction* action, QObject* parent); + + virtual ~SimpleAction(); + + QList innerActions(); + + QAction* actionMenu(); + + private: + QAction* action; +}; + +#endif // _SIMPLE_ACTIONS_H_ diff --git a/ksystemlog/src/lib/view.cpp b/ksystemlog/src/lib/view.cpp new file mode 100644 index 00000000..c3d91666 --- /dev/null +++ b/ksystemlog/src/lib/view.cpp @@ -0,0 +1,230 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "view.h" + +#include + +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include "logging.h" + +#include "logViewWidget.h" + +#include "logViewFilterWidget.h" +#include "logViewSearchWidget.h" + +#include "logLine.h" +#include "logViewColumn.h" +#include "logViewColumns.h" + +#include "ksystemlogConfig.h" + +#include "loadingBar.h" + +class ViewPrivate { + +public: + /* + * Log view + */ + LogViewWidget* logViewWidget; + + /** + * Filter widget + */ + LogViewFilterWidget* logViewFilterWidget; + + /** + * Search widget + */ + LogViewSearchWidget* logViewSearchWidget; + + LoadingBar* loadingBar; +}; + +View::View(QWidget *parent) : + QWidget(parent), + d(new ViewPrivate()) { + + d->logViewWidget = NULL; + + QVBoxLayout* topLayout = new QVBoxLayout(); + topLayout->setMargin(2); + topLayout->setSpacing(2); + this->setLayout(topLayout); + + d->logViewFilterWidget = new LogViewFilterWidget(); + connect(d->logViewFilterWidget->filterLine(), SIGNAL(treeWidgetUpdated()), this, SIGNAL(searchFilterChanged())); + connect(d->logViewFilterWidget->filterLine(), SIGNAL(treeWidgetUpdated()), this, SLOT(unselectHiddenItems())); + + d->logViewFilterWidget->setVisible(KSystemLogConfig::toggleFilterBar()); + + topLayout->addWidget(d->logViewFilterWidget); + + d->logViewWidget=new LogViewWidget(this); + connect(d->logViewWidget, SIGNAL(columnsChanged(LogViewColumns)), d->logViewFilterWidget, SLOT(updateFilterColumns(LogViewColumns))); + + d->logViewFilterWidget->filterLine()->setTreeWidget(d->logViewWidget); + topLayout->addWidget(d->logViewWidget); + + + d->logViewSearchWidget = new LogViewSearchWidget(); + d->logViewSearchWidget->setTreeWidget(d->logViewWidget); + + //The search line is hidden by default + d->logViewSearchWidget->hide(); + + topLayout->addWidget(d->logViewSearchWidget); + + d->loadingBar = new LoadingBar(); + connect(d->loadingBar, SIGNAL(displayed(bool)), this, SLOT(displayLoadingBar(bool))); + + topLayout->addWidget(d->loadingBar); + d->loadingBar->hide(); + + //Accept Drag and Drop + setAcceptDrops(true); + +} + +View::~View() { + //All widgets are deleted automatically by Qt + + delete d; +} + +LogViewWidget* View::logViewWidget() const { + return d->logViewWidget; +} + +LoadingBar* View::loadingBar() const { + return d->loadingBar; +} + +void View::displayLoadingBar(bool display) { + if (display == true) { + d->logViewWidget->hide(); + d->logViewSearchWidget->hide(); + d->logViewFilterWidget->hide(); + + d->loadingBar->show(); + } + else { + d->logViewWidget->show(); + d->logViewFilterWidget->setVisible(KSystemLogConfig::toggleFilterBar()); + d->logViewFilterWidget->filterLine()->updateSearch(); + //No need to redisplay the search bar + + d->loadingBar->hide(); + } + +} + +void View::toggleLogViewFilter(bool display) { + if (display == true) { + d->logViewFilterWidget->show(); + } else { + d->logViewFilterWidget->filterLine()->clear(); + d->logViewFilterWidget->hide(); + } +} + +void View::toggleLogViewSearch(bool display) { + if (display == true) + d->logViewSearchWidget->displaySearch(); + else + d->logViewSearchWidget->hide(); +} + +LogViewSearchWidget* View::logViewSearch() const { + return d->logViewSearchWidget; +} + +void View::unselectHiddenItems() { + QTreeWidgetItemIterator it(d->logViewWidget, QTreeWidgetItemIterator::Selected); + + while ( *it != NULL) { + QTreeWidgetItem* item = *it; + + if (item->isHidden()) { + item->setSelected(false); + } + + ++it; + } +} + +QSize View::sizeHint() const { + return QSize(500, 500); +} + +void View::dropEvent(QDropEvent* event) { + KUrl::List urls = KUrl::List::fromMimeData( event->mimeData() ); + + //If URLs have been dropped + if ( ! urls.isEmpty() ) { + + emit droppedUrls(urls); + } +} + +void View::dragEnterEvent(QDragEnterEvent* event) { + KUrl::List urls = KUrl::List::fromMimeData( event->mimeData() ); + + //If URLs have been dropped + if (urls.isEmpty() ) { + logWarning() << "Empty drag and drop" << endl; + return; + } + + foreach (const KUrl &url, urls) { + QFileInfo fileInfo(url.path()); + + //TODO Add a recognition of binary files (using the Url mimetype) and refuse them + + if (fileInfo.isReadable() == false) { + logWarning() << "The drag and dropped file is not readable " << url.path() << endl; + return; + } + + if (fileInfo.isDir()) { + logWarning() << "Tried to drag and drop a directory " << url.path() << endl; + return; + } + } + + //Accept those urls + event->acceptProposedAction(); + +} + +#include "view.moc" diff --git a/ksystemlog/src/lib/view.h b/ksystemlog/src/lib/view.h new file mode 100644 index 00000000..7e3672f3 --- /dev/null +++ b/ksystemlog/src/lib/view.h @@ -0,0 +1,92 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef VIEW_H +#define VIEW_H + +//Qt includes +#include + +#include "globals.h" + +#include "logViewColumn.h" +#include "logViewColumns.h" + +class QDropEvent; +class QDragEnterEvent; + + +class LoadingBar; + +class LogViewWidget; +class LogViewSearchWidget; + +class ViewPrivate; + +class View : public QWidget { + + Q_OBJECT + + public: + explicit View(QWidget *parent); + + virtual ~View(); + + LogViewWidget* logViewWidget() const; + + LoadingBar* loadingBar() const; + + LogViewSearchWidget* logViewSearch() const; + + QSize sizeHint() const; + + public slots: + void displayLoadingBar(bool display); + + void toggleLogViewFilter(bool display); + void toggleLogViewSearch(bool display); + + protected: + /** + * Method which contains the action to do when receiving a drag and drop event + */ + void dragEnterEvent(QDragEnterEvent* event); + + /** + * Method which accepts + */ + void dropEvent(QDropEvent *event); + + private slots: + void unselectHiddenItems(); + + signals: + void searchFilterChanged(); + + void droppedUrls(const KUrl::List& urls); + + private: + ViewPrivate* const d; + +}; + +#endif // VIEW_H + diff --git a/ksystemlog/src/logModePluginsLoader.cpp b/ksystemlog/src/logModePluginsLoader.cpp new file mode 100644 index 00000000..aff74b60 --- /dev/null +++ b/ksystemlog/src/logModePluginsLoader.cpp @@ -0,0 +1,96 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logModePluginsLoader.h" + +#include "globals.h" + +#include "logging.h" +#include "defaults.h" + +//Includes existing log modes +#include "openFactory.h" + +#include "systemFactory.h" + +#include "kernelFactory.h" +#include "xorgFactory.h" +#include "cronFactory.h" +#include "daemonFactory.h" +#include "authenticationFactory.h" +#include "apacheFactory.h" +#include "cupsFactory.h" +#include "postfixFactory.h" +#include "acpidFactory.h" +#include "xsessionFactory.h" +#include "sambaFactory.h" + + +LogModePluginsLoader::LogModePluginsLoader(QWidget* parent) : + parent(parent) { + +} + +LogModePluginsLoader::~LogModePluginsLoader() { + +} + +void LogModePluginsLoader::loadPlugins() { + //Open Log mode needs the current window to display error messages and open dialogs + Globals::instance()->registerLogModeFactory(new OpenLogModeFactory(parent)); + + //System + Globals::instance()->registerLogModeFactory(new SystemLogModeFactory()); + + //Kernel + Globals::instance()->registerLogModeFactory(new KernelLogModeFactory()); + + //Authentication + Globals::instance()->registerLogModeFactory(new AuthenticationLogModeFactory()); + + //Daemon + Globals::instance()->registerLogModeFactory(new DaemonLogModeFactory()); + + //Xorg + Globals::instance()->registerLogModeFactory(new XorgLogModeFactory()); + + //Cron + Globals::instance()->registerLogModeFactory(new CronLogModeFactory()); + + //Apache + Globals::instance()->registerLogModeFactory(new ApacheLogModeFactory()); + + //Postfix + Globals::instance()->registerLogModeFactory(new PostfixLogModeFactory()); + + //Cups + Globals::instance()->registerLogModeFactory(new CupsLogModeFactory()); + + //Samba + Globals::instance()->registerLogModeFactory(new SambaLogModeFactory()); + + //Acpid + Globals::instance()->registerLogModeFactory(new AcpidLogModeFactory()); + + //XSession + Globals::instance()->registerLogModeFactory(new XSessionLogModeFactory()); + +} diff --git a/ksystemlog/src/logModePluginsLoader.h b/ksystemlog/src/logModePluginsLoader.h new file mode 100644 index 00000000..b24127f7 --- /dev/null +++ b/ksystemlog/src/logModePluginsLoader.h @@ -0,0 +1,42 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_MODE_PLUGINS_LOADER_H_ +#define _LOG_MODE_PLUGINS_LOADER_H_ + +#include + +class LogModePluginsLoader: public QObject { + + Q_OBJECT + + public: + LogModePluginsLoader(QWidget* parent); + + ~LogModePluginsLoader(); + + void loadPlugins(); + + private: + QWidget* parent; +}; + +#endif //_LOG_MODE_PLUGINS_LOADER_H_ diff --git a/ksystemlog/src/loggerDialog.cpp b/ksystemlog/src/loggerDialog.cpp new file mode 100644 index 00000000..72bd4dc2 --- /dev/null +++ b/ksystemlog/src/loggerDialog.cpp @@ -0,0 +1,262 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "loggerDialog.h" + +#include + +#include +#include + +//Project includes +#include "logging.h" + +#include "logLevel.h" +#include "globals.h" + +class LoggerDialogPrivate { +public: + QMap facilities; + + QMap priorities; + QMap priorityIcons; + +}; + +LoggerDialog::LoggerDialog(QWidget *parent) : + QDialog(parent), + d(new LoggerDialogPrivate()) { + + setupUi(this); + + connect(buttonOK, SIGNAL(clicked()), this, SLOT(sendMessage())); + connect(buttonCancel, SIGNAL(clicked()), this, SLOT(hide())); + + connect(tagActivation, SIGNAL(toggled(bool)), this, SLOT(changeTagActivation(bool))); + connect(fileActivation, SIGNAL(toggled(bool)), this, SLOT(changeFileActivation(bool))); + connect(messageActivation, SIGNAL(toggled(bool)), this, SLOT(changeMessageActivation(bool))); + + + connect(file, SIGNAL(textChanged(QString)), this, SLOT(textChanged())); + connect(message, SIGNAL(textChanged(QString)), this, SLOT(textChanged())); + connect(tag, SIGNAL(textChanged(QString)), this, SLOT(textChanged())); + + buildMaps(); + + //Fill the priority ComboBox + QList prioKeys(d->priorities.keys()); + + QList::Iterator itPriority; + for (itPriority=prioKeys.begin(); itPriority!=prioKeys.end(); ++itPriority) { + priority->addItem(d->priorityIcons[*itPriority], *itPriority); + } + + + //Select the right priority + for (int i=0; icount(); ++i) { + if (priority->itemText(i)==Globals::instance()->noticeLogLevel()->name()) { + priority->setCurrentIndex(i); + break; + } + } + + //Fill the priority ComboBox + QList keys(d->facilities.keys()); + + QList::Iterator itFacility; + for (itFacility=keys.begin(); itFacility!=keys.end(); ++itFacility) { + facility->addItem(*itFacility); + } + + //Select the right facility + for (int i=0; icount(); ++i) { + if (facility->itemText(i)==i18n("User")) { + facility->setCurrentIndex(i); + break; + } + } + tag->setEnabled(false); + +} + +LoggerDialog::~LoggerDialog() { + delete d; +} + +void LoggerDialog::initialize() { + logDebug() << "Initializing Logger dialog..." << endl; + + message->clear(); + message->setFocus(); +} + +void LoggerDialog::buildMaps() { + + //Fill the facility map + d->facilities[i18n("Authentication")]=QLatin1String( "auth" ); + d->facilities[i18n("Private Authentication")]=QLatin1String( "authpriv" ); + d->facilities[i18n("Cron")]=QLatin1String( "cron" ); + d->facilities[i18n("Daemon")]=QLatin1String( "daemon" ); + d->facilities[i18n("FTP")]=QLatin1String( "ftp" ); + d->facilities[i18n("Kernel")]=QLatin1String( "kern" ); + d->facilities[i18n("LPR")]=QLatin1String( "lpr" ); + d->facilities[i18n("Mail")]=QLatin1String( "mail" ); + d->facilities[i18n("News")]=QLatin1String( "news" ); + d->facilities[i18n("Syslog")]=QLatin1String( "syslog" ); + d->facilities[i18n("User")]=QLatin1String( "user" ); + d->facilities[i18n("UUCP")]=QLatin1String( "uucp" ); + + d->facilities[i18n("Local 0")]=QLatin1String( "local0" ); + d->facilities[i18n("Local 1")]=QLatin1String( "local1" ); + d->facilities[i18n("Local 2")]=QLatin1String( "local2" ); + d->facilities[i18n("Local 3")]=QLatin1String( "local3" ); + d->facilities[i18n("Local 4")]=QLatin1String( "local4" ); + d->facilities[i18n("Local 5")]=QLatin1String( "local5" ); + d->facilities[i18n("Local 6")]=QLatin1String( "local6" ); + d->facilities[i18n("Local 7")]=QLatin1String( "local7" ); + + //Fill the priority map + d->priorities[Globals::instance()->debugLogLevel()->name()]=QLatin1String( "debug" ); + d->priorities[Globals::instance()->informationLogLevel()->name()]=QLatin1String( "info" ); + d->priorities[Globals::instance()->noticeLogLevel()->name()]=QLatin1String( "notice" ); + d->priorities[Globals::instance()->warningLogLevel()->name()]=QLatin1String( "warning" ); + d->priorities[Globals::instance()->errorLogLevel()->name()]=QLatin1String( "err" ); + d->priorities[Globals::instance()->criticalLogLevel()->name()]=QLatin1String( "crit" ); + d->priorities[Globals::instance()->alertLogLevel()->name()]=QLatin1String( "alert" ); + d->priorities[Globals::instance()->emergencyLogLevel()->name()]=QLatin1String( "emerg" ); + + //Fill the priority icon map + d->priorityIcons[Globals::instance()->debugLogLevel()->name()]=Globals::instance()->debugLogLevel()->icon(); + d->priorityIcons[Globals::instance()->informationLogLevel()->name()]=Globals::instance()->informationLogLevel()->icon(); + d->priorityIcons[Globals::instance()->noticeLogLevel()->name()]=Globals::instance()->noticeLogLevel()->icon(); + d->priorityIcons[Globals::instance()->warningLogLevel()->name()]=Globals::instance()->warningLogLevel()->icon(); + d->priorityIcons[Globals::instance()->errorLogLevel()->name()]=Globals::instance()->errorLogLevel()->icon(); + d->priorityIcons[Globals::instance()->criticalLogLevel()->name()]=Globals::instance()->criticalLogLevel()->icon(); + d->priorityIcons[Globals::instance()->alertLogLevel()->name()]=Globals::instance()->alertLogLevel()->icon(); + d->priorityIcons[Globals::instance()->emergencyLogLevel()->name()]=Globals::instance()->emergencyLogLevel()->icon(); + +} + +void LoggerDialog::textChanged() { + if (fileActivation->isChecked() && file->url().isEmpty()) { + buttonOK->setEnabled(false); + return; + } + + if (tagActivation->isChecked() && tag->text().isEmpty()) { + buttonOK->setEnabled(false); + return; + } + + if (messageActivation->isChecked() && message->text().isEmpty()) { + buttonOK->setEnabled(false); + return; + } + + buttonOK->setEnabled(true); + +} + +void LoggerDialog::changeTagActivation(bool activation) { + tag->setEnabled(activation); + + textChanged(); +} + +void LoggerDialog::changeFileActivation(bool activation) { + file->setEnabled(activation); + + textChanged(); +} + +void LoggerDialog::changeMessageActivation(bool activation) { + message->setEnabled(activation); + + textChanged(); +} + + +void LoggerDialog::sendMessage() { + + QProcess process; + + QStringList arguments; + + if (useProcessIdentifier->isChecked()) { + arguments << QLatin1String( "-i" ); + } + + if (tagActivation->isChecked()) { + arguments << QLatin1String( "-t" ); + + arguments << tag->text(); + } + + QString prioritySelected=priority->currentText(); + + if (prioritySelected!=Globals::instance()->noLogLevel()->name()) { + arguments << QLatin1String( "-p" ); + + QString p(d->facilities[facility->currentText()]); + p+=QLatin1Char( '.' ); + p+=d->priorities[priority->currentText()]; + + arguments << p; + } + + //If we read the content of a file + if (fileActivation->isChecked()) { + arguments << QLatin1String( "-f" ); + + arguments << file->url().path(); + } + //Else, the user types the content of its message + else { + //Remove bad "\n" characters + arguments << message->text().replace(QLatin1String( "\n" ), QLatin1String( " " )); + } + + // QProcess::Block, QProcess::Stdout + process.start(QLatin1String( "logger" ), arguments); + + //If the launching of the command failed + if (process.error() == QProcess::FailedToStart) { + KMessageBox::error(this, i18n("Unable to find the 'logger' command on your system. Please type 'logger' in a Konsole to determine whether this command is installed."), i18n("Command not found")); + return; + } + + if (process.exitStatus() == QProcess::CrashExit) { + KMessageBox::error(this, i18n("The 'logger' command has not been properly exited."), i18n("Execution problem")); + return; + } + + //No such file or directory + if (process.exitCode()==1) { + KMessageBox::error(this, i18n("This file does not exist, please choose another."), i18n("File not valid")); + return; + } + + //Hide the Logger Dialog + hide(); +} + + +#include "loggerDialog.moc" diff --git a/ksystemlog/src/loggerDialog.h b/ksystemlog/src/loggerDialog.h new file mode 100644 index 00000000..1991612b --- /dev/null +++ b/ksystemlog/src/loggerDialog.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOGGER_DIALOG_H_ +#define _LOGGER_DIALOG_H_ + +#include + + +#include "ui_loggerDialogBase.h" + +class LoggerDialogPrivate; + +class LoggerDialog: public QDialog, public Ui::LoggerDialogBase { + Q_OBJECT + public: + explicit LoggerDialog(QWidget* parent = NULL); + + virtual ~LoggerDialog(); + + void initialize(); + + protected slots: + void sendMessage(); + + void textChanged(); + + void changeTagActivation(bool activation); + void changeFileActivation(bool activation); + void changeMessageActivation(bool activation); + + private: + void buildMaps(); + + LoggerDialogPrivate* const d; + +}; + +#endif // _LOGGER_DIALOG_H_ diff --git a/ksystemlog/src/loggerDialogBase.ui b/ksystemlog/src/loggerDialogBase.ui new file mode 100644 index 00000000..31559b80 --- /dev/null +++ b/ksystemlog/src/loggerDialogBase.ui @@ -0,0 +1,381 @@ + + Nicolas Ternisien + Dialog that allows the user to send a message with the logger command + LoggerDialogBase + + + + 0 + 0 + 440 + 429 + + + + + 300 + 300 + + + + true + + + Log Message + + + false + + + + + + Message + + + + + + &Message: + + + true + + + + + + + + + + &File content: + + + + + + + false + + + + 1 + 0 + + + + + + + + + + + Properties + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + &Priority: + + + false + + + priority + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 84 + 24 + + + + + + + + + 1 + 0 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + &Facility: + + + false + + + facility + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 84 + 24 + + + + + + + + + 1 + 0 + + + + + + + + + + + + + Options + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::TabFocus + + + &Tag: + + + + + + + + + + + + Log process &identifier + + + true + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 31 + 31 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Open the 'logger' command manual. + + + This link opens the 'logger' command manual. + + + <a href="man:/logger">Logger Manual</a> + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 155 + 26 + + + + + + + + false + + + &OK + + + + + + true + + + true + + + + + + + &Cancel + + + + + + true + + + + + + + + + + KComboBox + QComboBox +
kcombobox.h
+
+ + KUrlRequester + QWidget +
kurlrequester.h
+
+
+ + buttonOK + buttonCancel + loggerManual + + + klineedit.h + kurlrequester.h + klineedit.h + kpushbutton.h + kcombobox.h + kcombobox.h + klineedit.h + QLabel + + + +
diff --git a/ksystemlog/src/main.cpp b/ksystemlog/src/main.cpp new file mode 100644 index 00000000..70bad89d --- /dev/null +++ b/ksystemlog/src/main.cpp @@ -0,0 +1,92 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include +#include +#include +#include +#include + +#include "mainWindow.h" +#include "logging.h" + +int main(int argc, char** argv) { + + KAboutData about( + "ksystemlog", + 0, + ki18n("KSystemlog"), + "0.4", + ki18n("System Logs Viewer for KDE"), + KAboutData::License_GPL_V2, + ki18n("(C) 2007, Nicolas Ternisien"), + ki18n("Do not hesitate to report bugs and problems to Nicolas Ternisien
nicolas.ternisien@gmail.com"), + "http://ksystemlog.forum-software.org", + "nicolas.ternisien@gmail.com" + ); + + about.addAuthor( + ki18n("Nicolas Ternisien"), + ki18n("Main developer"), + "nicolas.ternisien@gmail.com", + "http://www.forum-software.org" + ); + + about.setProgramIconName(QLatin1String( "utilities-log-viewer" )); + about.addCredit(ki18n("Bojan Djurkovic"), ki18n("Log Printing"), "dbojan@gmail.com"); + + KCmdLineArgs::init(argc, argv, &about); + + KCmdLineOptions options; + options.add("+[URL]", ki18n("Document to open")); + + KCmdLineArgs::addCmdLineOptions( options ); + + KApplication app; + + //See if we are starting with session management + if (app.isSessionRestored()) { + RESTORE(KSystemLog::MainWindow); + } + else { + //No session... Just start up normally + KCmdLineArgs* args=KCmdLineArgs::parsedArgs(); + if (args->count()==0) { + new KSystemLog::MainWindow(); + } + else { + /*KSystemLog::MainWindow* mainWindow;*/ + new KSystemLog::MainWindow(); + for (int i = 0; i < args->count(); i++) { + logDebug() << "Loading file " << args->url(i) << endl; + //TODO Implement this kind of loading + //LogManager* firstLogManager = d->tabs->createTab(); + //d->tabs->load(Globals::instance()->findLogMode("openLogMode"), firstLogManager); + //Open log mode need to automatically find the passed url : args->url(i) + + } + } + + args->clear(); + } + + return app.exec(); +} diff --git a/ksystemlog/src/mainWindow.cpp b/ksystemlog/src/mainWindow.cpp new file mode 100644 index 00000000..7cfbc8a2 --- /dev/null +++ b/ksystemlog/src/mainWindow.cpp @@ -0,0 +1,814 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "mainWindow.h" + +//Qt includes + +#include +#include +#include +#include + +//KDE includes +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include + +#include +#include + + +#include + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +//Project includes +#include "ksystemlogConfig.h" + +#include "statusBar.h" + +#include "logViewWidget.h" +#include "tabLogViewsWidget.h" +#include "configurationDialog.h" + +#include "loggerDialog.h" + +#include "detailDialog.h" + +#include "logModeConfiguration.h" +#include "logManager.h" + +#include "logMode.h" +#include "logLevel.h" +#include "logFile.h" + +#include "logging.h" + +#include "view.h" +#include "logViewSearchWidget.h" +#include "logModePluginsLoader.h" + +#include "globals.h" +#include "logModeAction.h" + +namespace KSystemLog { + +class MainWindowPrivate { + +public: + + KAction* saveAction; + KAction* copyAction; + + KAction* reloadAction; + + KAction* sendMailAction; + KAction* logMessageAction; + + KAction* filterBarAction; + + KAction* selectAllAction; + + KAction* expandAllAction; + KAction* collapseAllAction; + + KAction* resumePauseAction; + KAction* detailAction; + KAction* printAction; + + KAction* findAction; + KAction* findNextAction; + KAction* findPreviousAction; + + KAction* tooltipEnabledAction; + KAction* newLinesDisplayedAction; + + /** + * Action groups which stores all Log Mode Actions + */ + QActionGroup* logModesActionGroup; + + + QPrinter* printer; + + /** + * Detail dialog + */ + DetailDialog* detailDialog; + + /** + * Logged Dialog + */ + LoggerDialog* loggedDialog; + + ConfigurationDialog* configurationDialog; + + /** + * Tab widget managing different views + */ + TabLogViewsWidget* tabs; + + KSystemLog::StatusBar* statusBar; + +}; + +MainWindow::MainWindow() : + KXmlGuiWindow(0), + d(new MainWindowPrivate()) + { + + d->printer = NULL; + d->detailDialog = NULL; + d->configurationDialog = NULL; + d->tabs = NULL; + d->statusBar = NULL; + + logDebug() << "Starting KSystemLog..." << endl; + + //Load log modes plugins + loadLogModePlugins(); + + //Create the GUI from XML configuration + logDebug() << "Creating Gui..." << endl; + createGUI(); + + //TODO Improve the status bar to add a custom widget which shows an history of latest message, and add a LogLevel for each ones + //Initialize the Status Bar + setupStatusBar(); + + //Setup the main tab bar + setupTabLogViews(); + + //Setup the Actions + setupActions(); + + // Apply the create the main window and ask the mainwindow to + // automatically save settings if changed: window size, toolbar + // position, icon size, etc. Also to add actions for the statusbar + // toolbar, and keybindings if necessary. + logDebug() << "Setup Gui..." << endl; + setupGUI(); + + setupLogActions(); + + //Apply the saved mainwindow settings, if any, and ask the main window + //to automatically save settings if changed: window size, tool bar + //position, icon size, etc. + setAutoSaveSettings(); + + //TODO Try to find an improvement of this _big_ hack to set its default size, the first time KSystemLog is loaded + + //Show KSystemLog before loading the first log file + show(); + + LogManager* firstLogManager = d->tabs->createTab(); + + if (KSystemLogConfig::startupLogMode().isEmpty() == false) { + d->tabs->load(Globals::instance()->findLogMode(KSystemLogConfig::startupLogMode()), firstLogManager); + } + + //Set focus to the list + firstLogManager->usedView()->logViewWidget()->setFocus(); +} + +void MainWindow::loadLogModePlugins() { + LogModePluginsLoader pluginsLoader(this); + pluginsLoader.loadPlugins(); +} + +void MainWindow::setupTabLogViews() { + logDebug() << "Creating tab widget..." << endl; + + d->tabs=new TabLogViewsWidget(); + + connect(d->tabs, SIGNAL(statusBarChanged(QString)), this, SLOT(changeStatusBar(QString))); + connect(d->tabs, SIGNAL(logManagerCreated(LogManager*)), this, SLOT(prepareCreatedLogManager(LogManager*))); + connect(d->tabs, SIGNAL(currentChanged(int)), this, SLOT(changeCurrentTab())); + + setCentralWidget(d->tabs); +} + +MainWindow::~MainWindow() { + delete d->loggedDialog; + + delete d->detailDialog; + + delete d->configurationDialog; + + delete d; +} + + +void MainWindow::setupStatusBar() { + d->statusBar = new KSystemLog::StatusBar(this); + + setStatusBar(d->statusBar); +} + +void MainWindow::prepareCreatedLogManager(LogManager* manager) { + logDebug() << "Connecting to actions the new log manager and view..." << endl; + + //Contextual menu Log Manager signals + QAction* separator; + + manager->usedView()->logViewWidget()->addAction(d->reloadAction); + manager->usedView()->logViewWidget()->addAction(d->selectAllAction); + + separator = new QAction(this); + separator->setSeparator(true); + manager->usedView()->logViewWidget()->addAction(separator); + + manager->usedView()->logViewWidget()->addAction(d->copyAction); + manager->usedView()->logViewWidget()->addAction(d->saveAction); + + separator = new QAction(this); + separator->setSeparator(true); + manager->usedView()->logViewWidget()->addAction(separator); + + manager->usedView()->logViewWidget()->addAction(d->tooltipEnabledAction); + manager->usedView()->logViewWidget()->addAction(d->newLinesDisplayedAction); + + separator = new QAction(this); + separator->setSeparator(true); + manager->usedView()->logViewWidget()->addAction(separator); + + manager->usedView()->logViewWidget()->addAction(d->detailAction); + + //Log Manager and View signals + connect(manager, SIGNAL(windowTitleChanged(QString)), this, SLOT(changeWindowTitle(QString))); + connect(manager, SIGNAL(statusBarChanged(QString)), this, SLOT(changeStatusBar(QString))); + connect(manager, SIGNAL(logUpdated(View*,int)), this, SLOT(updateStatusBar())); + connect(manager, SIGNAL(reloaded()), this, SLOT(changeCurrentTab())); + + connect(manager->usedView(), SIGNAL(searchFilterChanged()), this, SLOT(updateStatusBar())); + connect(manager->usedView()->logViewWidget(), SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(showDetailsDialog())); + connect(manager->usedView()->logViewWidget(), SIGNAL(itemSelectionChanged()), this, SLOT(updateSelection())); + connect(manager->usedView()->logViewWidget()->model(), SIGNAL(processingMultipleInsertions(bool)), this, SLOT(updateReloading())); + + //Correctly initialize the Status Bar + updateStatusBar(); + +} + +void MainWindow::updateDetailDialog() { + LogManager* currentManager=d->tabs->activeLogManager(); + if (d->detailDialog!=NULL) { + d->detailDialog->selectionChanged(currentManager->usedView()->logViewWidget()); + } +} + +void MainWindow::updateSelection() { + //logDebug() << "Updating selection..." << endl; + + LogManager* currentLogManager = d->tabs->activeLogManager(); + + updateDetailDialog(); + + bool selection=currentLogManager->usedView()->logViewWidget()->hasItemsSelected(); + + d->copyAction->setEnabled(selection); + d->saveAction->setEnabled(selection); + d->detailAction->setEnabled(selection); + d->sendMailAction->setEnabled(selection); + d->printAction->setEnabled(selection); + +} + +void MainWindow::updateReloading() { + View* currentView = d->tabs->activeLogManager()->usedView(); + + bool enabled = ! currentView->logViewWidget()->model()->isProcessingMultipleInsertions(); + + d->reloadAction->setEnabled(enabled); + d->resumePauseAction->setEnabled(enabled); + d->findAction->setEnabled(enabled); + d->findNextAction->setEnabled(enabled); + d->findPreviousAction->setEnabled(enabled); + + //Enables/Disables all Log Mode actions + d->logModesActionGroup->setEnabled(enabled); + + d->tabs->changeReloadingTab(currentView, !enabled); + + //Enables/Disables all Log Mode menus (useful for multiple actions menus) + foreach(LogModeAction* logModeAction, Globals::instance()->logModeActions()) { + logModeAction->actionMenu()->setEnabled(enabled); + } + +} + +void MainWindow::closeEvent(QCloseEvent *event) { + logDebug() << "Saving configuration before exit..." << endl; + //Write the config to the file + KSystemLogConfig::self()->writeConfig(); + KXmlGuiWindow::closeEvent(event); +} + +TabLogViewsWidget* MainWindow::tabs() { + return d->tabs; +} + +void MainWindow::showDetailsDialog() { + //Create the Detail dialog if it was not created + if (d->detailDialog==NULL) { + d->detailDialog=new DetailDialog(this); + updateDetailDialog(); + } + + d->detailDialog->show(); + +} + +void MainWindow::toggleItemTooltip(bool enabled) { + KSystemLogConfig::setTooltipEnabled(enabled); + + foreach (LogManager* manager, d->tabs->logManagers()) { + manager->usedView()->logViewWidget()->toggleToolTip(enabled); + } +} + +void MainWindow::toggleNewLinesDisplaying(bool displayed) { + KSystemLogConfig::setNewLinesDisplayed(displayed); +} + +void MainWindow::updateStatusBar() { + logDebug() << "Updating status bar..." << endl; + + LogManager* currentManager=d->tabs->activeLogManager(); + + int itemCount = currentManager->usedView()->logViewWidget()->itemCount(); + int notHiddenItemCount = currentManager->usedView()->logViewWidget()->notHiddenItemCount(); + + if (itemCount == notHiddenItemCount) { + d->statusBar->changeLineCountMessage(i18ncp("Total displayed lines", "1 line.", "%1 lines.", currentManager->usedView()->logViewWidget()->itemCount())); + } + else { + d->statusBar->changeLineCountMessage(i18ncp("Line not hidden by search / Total displayed lines", "1 line / %2 total.", "%1 lines / %2 total.", notHiddenItemCount, itemCount)); + } + + d->statusBar->changeLastModification(currentManager->lastUpdate()); + +} + +void MainWindow::toggleResumePauseParsing(bool paused) { + logDebug() << "Pausing parsing : " << paused << endl; + + LogManager* currentLogManager = d->tabs->activeLogManager(); + if (currentLogManager != NULL) { + currentLogManager->setParsingPaused(paused); + } + + logDebug() << "Parsing paused : " << paused << endl; +} + +void MainWindow::changeResumePauseAction(bool paused) { + + if (paused == true) { + d->resumePauseAction->setText(i18n("Resu&me")); + d->resumePauseAction->setIcon(KIcon( QLatin1String( "media-playback-start" ))); + d->resumePauseAction->setShortcut(Qt::CTRL + Qt::Key_M); + d->resumePauseAction->setToolTip(i18n("Resume the watching of the current log")); + d->resumePauseAction->setWhatsThis(i18n("Resumes the watching of the current log. This action is only available when the user has already paused the reading.")); + d->resumePauseAction->setChecked(true); + } + else { + d->resumePauseAction->setText(i18n("S&top")); + d->resumePauseAction->setIcon(KIcon( QLatin1String( "media-playback-stop" ))); + d->resumePauseAction->setShortcut(Qt::CTRL + Qt::Key_P); + d->resumePauseAction->setToolTip(i18n("Pause the watching of the current log")); + d->resumePauseAction->setWhatsThis(i18n("Pauses the watching of the current log. This action is particularly useful when the system is writing too many lines to log files, causing KSystemLog to reload too frequently.")); + d->resumePauseAction->setChecked(false); + } + + //Be sur that the button will always have a good size + foreach (QWidget* widget, d->resumePauseAction->associatedWidgets()) { + if (widget->sizeHint().width() > widget->size().width()) { + widget->setMinimumSize(widget->sizeHint()); + } + } +} + +void MainWindow::fileOpen() { + //Launch the actualizing + d->tabs->load(Globals::instance()->findLogMode(QLatin1String( "openLogMode" )), d->tabs->activeLogManager()); +} + +void MainWindow::showConfigurationDialog() { + logDebug() << "Showing configuration dialog..." << endl; + + if (d->configurationDialog == NULL) { + d->configurationDialog = new ConfigurationDialog(this); + connect(d->configurationDialog, SIGNAL(configurationSaved()), d->tabs, SLOT(reloadAll())); + } + + d->configurationDialog->showConfiguration(); +} + +void MainWindow::showLogMessageDialog() { + logDebug() << "Launching the Send message dialog box..." << endl; + + if (d->loggedDialog == NULL) { + d->loggedDialog = new LoggerDialog(this); + } + + d->loggedDialog->initialize(); + d->loggedDialog->exec(); + +} + +void MainWindow::changeStatusBar(const QString& text) { + d->statusBar->changeMessage(text); +} + +void MainWindow::changeWindowTitle(const QString& text) { + //Display this text on the caption + setCaption(text); +} + + +void MainWindow::changeCurrentTab() { + logDebug() << "Tab has changed" << endl; + + LogManager* currentManager=d->tabs->activeLogManager(); + + //If the tab changed, the selection changes too + updateSelection(); + + //Update the status bar + updateStatusBar(); + + //Updating the current reloading status + updateReloading(); + + bool enabledReloading = ! currentManager->usedView()->logViewWidget()->model()->isProcessingMultipleInsertions(); + + bool enabledAction; + //Change the title of the window + if (currentManager->logMode() == NULL) { + changeWindowTitle(i18nc("Newly created tab", "Empty Log")); + enabledAction = false; + } + else { + changeWindowTitle(currentManager->logMode()->name()); + enabledAction = true; + } + + if (enabledReloading==true && enabledAction==true) { + d->reloadAction->setEnabled(true); + d->resumePauseAction->setEnabled(true); + } + else { + d->reloadAction->setEnabled(false); + d->resumePauseAction->setEnabled(false); + } + + + //Update Resume/Pause action state + if (currentManager->isParsingPaused()) + changeResumePauseAction(true); + else + changeResumePauseAction(false); + + //Updating Detail dialog + updateDetailDialog(); + + logDebug() << "Tab changing done" << endl; +} + +/** + * TODO Implements the session restoring + * + * The 'config' object points to the session managed + * config file. anything you write here will be available + * later when this app is restored + */ +void MainWindow::saveProperties(KConfigGroup & /*configuration*/) { + logDebug() << "Saving properties..." << endl; +} + +/** + * TODO Implements the session restoring + * + * The 'config' object points to the session managed + * config file. this function is automatically called whenever + * the app is being restored. read in here whatever you wrote + * in 'saveProperties' + */ +void MainWindow::readProperties( const KConfigGroup& /*configuration*/) { + logDebug() << "Reading properties..." << endl; +} + +void MainWindow::toggleFilterBar() { + logDebug() << "Toggling filter bar..." << d->filterBarAction->isChecked() << endl; + + foreach (LogManager* manager, d->tabs->logManagers()) { + manager->usedView()->toggleLogViewFilter(d->filterBarAction->isChecked()); + } + + KSystemLogConfig::setToggleFilterBar(d->filterBarAction->isChecked()); +} + +void MainWindow::findNext() { + showSearchBar(); + d->tabs->activeLogManager()->usedView()->logViewSearch()->findNext(); +} + +void MainWindow::findPrevious() { + showSearchBar(); + d->tabs->activeLogManager()->usedView()->logViewSearch()->findPrevious(); +} + +void MainWindow::showSearchBar() { + logDebug() << "Showing search bar..." << endl; + + LogManager* activeLogManager = d->tabs->activeLogManager(); + + foreach (LogManager* manager, d->tabs->logManagers()) { + if (manager != activeLogManager) { + manager->usedView()->toggleLogViewSearch(true); + } + } + + //Be sure to display the view search of the active LogManager at last, and focus to it + d->tabs->activeLogManager()->usedView()->toggleLogViewSearch(true); + +} + +void MainWindow::setupActions() { + logDebug() << "Creating actions..." << endl; + + QAction* fileOpenAction = actionCollection()->addAction(KStandardAction::Open, this, SLOT(fileOpen())); + fileOpenAction->setToolTip(i18n("Open a file in KSystemLog")); + fileOpenAction->setWhatsThis(i18n("Opens a file in KSystemLog and displays its content in the current tab.")); + + d->printAction = actionCollection()->addAction(KStandardAction::Print); + d->printAction->setText(i18n("&Print Selection...")); + d->printAction->setToolTip(i18n("Print the selection")); + d->printAction->setWhatsThis(i18n("Prints the selection. Simply select the important lines and click on this menu entry to print the selection.")); + d->printAction->setEnabled(false); + + d->saveAction = actionCollection()->addAction(KStandardAction::SaveAs); + //TODO Retrieve the system's shortcut of the save action (and not Save as...) + d->saveAction->setShortcut(Qt::CTRL + Qt::Key_S); + d->saveAction->setToolTip(i18n("Save the selection to a file")); + d->saveAction->setWhatsThis(i18n("Saves the selection to a file. This action is useful if you want to create an attachment or a backup of a particular log.")); + d->saveAction->setEnabled(false); + + QAction* fileQuitAction = actionCollection()->addAction(KStandardAction::Quit, kapp, SLOT(quit())); + fileQuitAction->setToolTip(i18n("Quit KSystemLog")); + fileQuitAction->setWhatsThis(i18n("Quits KSystemLog.")); + + d->copyAction = actionCollection()->addAction(KStandardAction::Copy); + d->copyAction->setToolTip(i18n("Copy the selection to the clipboard")); + d->copyAction->setWhatsThis(i18n("Copies the selection to the clipboard. This action is useful if you want to paste the selection in a chat or an email.")); + d->copyAction->setEnabled(false); + + d->expandAllAction = actionCollection()->addAction( QLatin1String( "expand_all" )); + d->expandAllAction->setText(i18n("Ex&pand All")); + d->expandAllAction->setShortcut(Qt::CTRL + Qt::Key_X); + d->expandAllAction->setToolTip(i18n("Expand all categories")); + d->expandAllAction->setWhatsThis(i18n("This action opens all main categories. This is enabled only if an option has been selected in the Group By menu.")); + d->expandAllAction->setEnabled(false); + + d->collapseAllAction = actionCollection()->addAction( QLatin1String( "collapse_all" )); + d->collapseAllAction->setText(i18n("Col&lapse All")); + d->collapseAllAction->setShortcut(Qt::CTRL + Qt::Key_L); + d->collapseAllAction->setToolTip(i18n("Collapse all categories")); + d->collapseAllAction->setWhatsThis(i18n("This action closes all main categories. This is enabled only if an option has been selected in the Group By menu.")); + d->collapseAllAction->setEnabled(false); + + d->sendMailAction = actionCollection()->addAction( QLatin1String( "send_mail" )); + d->sendMailAction->setText(i18n("&Email Selection...")); + d->sendMailAction->setIcon(KIcon( QLatin1String( "mail-message-new" ))); + d->sendMailAction->setShortcut(Qt::CTRL + Qt::Key_M); + d->sendMailAction->setToolTip(i18n("Send the selection by mail")); + d->sendMailAction->setWhatsThis(i18n("Sends the selection by mail. Simply select the important lines and click on this menu entry to send the selection to a friend or a mailing list.")); + d->sendMailAction->setEnabled(false); + + d->logMessageAction = actionCollection()->addAction( QLatin1String( "log_message" ), this, SLOT(showLogMessageDialog())); + d->logMessageAction->setText(i18n("&Add Log Entry...")); + d->logMessageAction->setIcon(KIcon( QLatin1String( "document-new" ))); + d->logMessageAction->setShortcut(Qt::CTRL + Qt::Key_L); + d->logMessageAction->setToolTip(i18n("Add a log entry to the log system")); + d->logMessageAction->setWhatsThis(i18n("This action will open a dialog which lets you send a message to the log system.")); + + d->selectAllAction = actionCollection()->addAction(KStandardAction::SelectAll); + d->selectAllAction->setToolTip(i18n("Select all lines of the current log")); + d->selectAllAction->setWhatsThis(i18n("Selects all lines of the current log. This action is useful if you want, for example, to save all the content of the current log in a file.")); + + d->findAction = actionCollection()->addAction(KStandardAction::Find, this, SLOT(showSearchBar())); + d->findNextAction = actionCollection()->addAction(KStandardAction::FindNext, this, SLOT(findNext())); + d->findPreviousAction = actionCollection()->addAction(KStandardAction::FindPrev, this, SLOT(findPrevious())); + + + actionCollection()->addAction(KStandardAction::Preferences, this, SLOT(showConfigurationDialog())); + + //TODO Find a solution to display at the right place this action (see Akregator interface) + d->filterBarAction = actionCollection()->addAction( QLatin1String( "show_quick_filter" ), this, SLOT(toggleFilterBar())); + d->filterBarAction->setText(i18n("Show &Filter Bar")); + d->filterBarAction->setEnabled(true); + d->filterBarAction->setCheckable(true); + d->filterBarAction->setChecked(KSystemLogConfig::toggleFilterBar()); + + KAction* newTabAction = actionCollection()->addAction( QLatin1String( "new_tab" ), d->tabs, SLOT(createTab())); + newTabAction->setText(i18n("&New Tab")); + newTabAction->setIcon(KIcon( QLatin1String( "tab-new" ))); + newTabAction->setShortcut(Qt::CTRL + Qt::Key_T); + newTabAction->setToolTip(i18n("Create a new tab")); + newTabAction->setWhatsThis(i18n("Creates a new tab which can display another log.")); + d->tabs->addAction(newTabAction); + + KAction* closeTabAction = actionCollection()->addAction( QLatin1String( "close_tab" ), d->tabs, SLOT(closeTab())); + closeTabAction->setText(i18n("&Close Tab")); + closeTabAction->setIcon(KIcon( QLatin1String( "tab-close" ))); + closeTabAction->setShortcut(Qt::CTRL+Qt::Key_W); + closeTabAction->setToolTip(i18n("Close the current tab")); + closeTabAction->setWhatsThis(i18n("Closes the current tab.")); + d->tabs->addAction(closeTabAction); + + KAction* duplicateTabAction = actionCollection()->addAction( QLatin1String( "duplicate_tab" ), d->tabs, SLOT(duplicateTab())); + duplicateTabAction->setText(i18n("&Duplicate Tab")); + duplicateTabAction->setIcon(KIcon( QLatin1String( "tab-duplicate" ))); + duplicateTabAction->setShortcut(Qt::SHIFT + Qt::CTRL + Qt::Key_N); + duplicateTabAction->setToolTip(i18n("Duplicate the current tab")); + duplicateTabAction->setWhatsThis(i18n("Duplicates the current tab.")); + d->tabs->addAction(duplicateTabAction); + + QAction* separatorAction = new QAction(this); + separatorAction->setSeparator(true); + d->tabs->addAction(separatorAction); + + KAction* moveTabLeftAction = actionCollection()->addAction( QLatin1String( "move_tab_left" ), d->tabs, SLOT(moveTabLeft())); + moveTabLeftAction->setText(i18n("Move Tab &Left")); + moveTabLeftAction->setIcon(KIcon( QLatin1String( "arrow-left" ))); + moveTabLeftAction->setShortcut(Qt::SHIFT+Qt::CTRL+Qt::Key_Left); + moveTabLeftAction->setToolTip(i18n("Move the current tab to the left")); + moveTabLeftAction->setWhatsThis(i18n("Moves the current tab to the left.")); + d->tabs->addAction(moveTabLeftAction); + + KAction* moveTabRightAction = actionCollection()->addAction( QLatin1String( "move_tab_right" ), d->tabs, SLOT(moveTabRight())); + moveTabRightAction->setText(i18n("Move Tab &Right")); + moveTabRightAction->setIcon(KIcon( QLatin1String( "arrow-right" ))); + moveTabRightAction->setShortcut(Qt::SHIFT+Qt::CTRL+Qt::Key_Right); + moveTabRightAction->setToolTip(i18n("Move the current tab to the right")); + moveTabRightAction->setWhatsThis(i18n("Moves the current tab to the right.")); + d->tabs->addAction(moveTabRightAction); + + d->reloadAction = actionCollection()->addAction( QLatin1String( "reload" ), d->tabs, SLOT(reloadCurrent())); + d->reloadAction->setText(i18n("&Reload")); + d->reloadAction->setIcon(KIcon( QLatin1String( "view-refresh" ))); + d->reloadAction->setShortcut(Qt::Key_F5); + d->reloadAction->setToolTip(i18n("Reload the current log")); + d->reloadAction->setWhatsThis(i18n("Reloads the current log, if you want to be sure that the view is correctly updated.")); + + d->resumePauseAction = actionCollection()->addAction( QLatin1String( "resume_pause_parsing" )); + d->resumePauseAction->setCheckable(true); + connect(d->resumePauseAction, SIGNAL(toggled(bool)), this, SLOT(changeResumePauseAction(bool))); + connect(d->resumePauseAction, SIGNAL(toggled(bool)), this, SLOT(toggleResumePauseParsing(bool))); + changeResumePauseAction(false); + + d->detailAction = actionCollection()->addAction( QLatin1String( "details" ), this, SLOT(showDetailsDialog())); + d->detailAction->setText(i18n("&Details")); + d->detailAction->setIcon(KIcon( QLatin1String( "document-preview" ))); + d->detailAction->setShortcut(Qt::ALT + Qt::Key_Return); + d->detailAction->setToolTip(i18n("Display details on the selected line")); + d->detailAction->setWhatsThis(i18n("Displays a dialog box which contains details on the selected line. You are able to navigate through the logs from this dialog box with the Previous and Next buttons.")); + d->detailAction->setEnabled(false); + + d->tooltipEnabledAction = actionCollection()->addAction( QLatin1String( "tooltipEnabled" )); + d->tooltipEnabledAction->setText(i18n("&Enable Detailed Tooltips")); + d->tooltipEnabledAction->setToolTip(i18n("Disable/Enable the tooltip on the current view")); + d->tooltipEnabledAction->setWhatsThis(i18n("Disables/Enables the tooltip displayed when the cursor hovers a log line.")); + d->tooltipEnabledAction->setCheckable(true); + d->tooltipEnabledAction->setChecked(KSystemLogConfig::tooltipEnabled()); + connect(d->tooltipEnabledAction, SIGNAL(toggled(bool)), this, SLOT(toggleItemTooltip(bool))); + + d->newLinesDisplayedAction = actionCollection()->addAction( QLatin1String( "newLinesDisplayed" )); + d->newLinesDisplayedAction->setText(i18n("&Scroll to New Lines")); + d->newLinesDisplayedAction->setToolTip(i18n("Scrolls or not to the new lines when the log changes")); + d->newLinesDisplayedAction->setWhatsThis(i18n("Scrolls or not to the new lines when the log changes. Check this option if you do not want the application to scroll automatically at the bottom of the log each time it is refreshed.")); + d->newLinesDisplayedAction->setCheckable(true); + d->newLinesDisplayedAction->setChecked(KSystemLogConfig::newLinesDisplayed()); + connect(d->newLinesDisplayedAction, SIGNAL(toggled(bool)), this, SLOT(toggleNewLinesDisplaying(bool))); + + //Toolbar and Menu signals + connect(d->expandAllAction, SIGNAL(triggered()), d->tabs, SLOT(expandAllCurrentView())); + connect(d->collapseAllAction, SIGNAL(triggered()), d->tabs, SLOT(collapseAllCurrentView())); + connect(d->saveAction, SIGNAL(triggered()), d->tabs, SLOT(fileSaveCurrentView())); + connect(d->copyAction, SIGNAL(triggered()), d->tabs, SLOT(copyToClipboardCurrentView())); + connect(d->sendMailAction, SIGNAL(triggered()), d->tabs, SLOT(sendMailCurrentView())); + connect(d->printAction, SIGNAL(triggered()), d->tabs, SLOT(printSelectionCurrentView())); + connect(d->selectAllAction, SIGNAL(triggered()), d->tabs, SLOT(selectAllCurrentView())); + +} + +void MainWindow::selectLogModeAction(QAction* action) { + QString selectedModeId = action->data().toString(); + + LogMode* currentMode = NULL; + foreach(LogMode* logMode, Globals::instance()->logModes()) { + if (logMode->id() == selectedModeId) { + currentMode = logMode; + break; + } + } + + if (currentMode==NULL) { + logError() << "The selected mode does not exist" << endl; + return; + } + + logDebug() << "Selecting " << currentMode->name() << " (" << currentMode->id() << ")" << endl; + + /* + //If the user uses the middle button OR left button + shift OR left button + control : = it opens the log in a new tab + if (state==Qt::MidButton || (state==Qt::ControlModifier+Qt::LeftButton) || (state==Qt::ShiftModifier+Qt::LeftButton)) + createTab(); + */ + + d->tabs->load(currentMode, d->tabs->activeLogManager()); +} + +void MainWindow::setupLogActions() { + QList menuLogModeActions; + QList toolBarLogModeActions; + + KActionMenu* servicesAction = new KActionMenu(KIcon( QLatin1String( "preferences-system-session-services") ), i18n("Services"), this); + KActionMenu* othersAction = new KActionMenu(KIcon( QLatin1String( "preferences-other")), i18n("Others"), this); + + d->logModesActionGroup=new QActionGroup(actionCollection()); + + connect(d->logModesActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(selectLogModeAction(QAction*))); + + foreach(LogModeAction* logModeAction, Globals::instance()->logModeActions()) { + foreach(QAction* action, logModeAction->innerActions()) { + d->logModesActionGroup->addAction(action); + } + + if (logModeAction->category() == LogModeAction::RootCategory) + menuLogModeActions.append( logModeAction->actionMenu() ); + else if (logModeAction->category() == LogModeAction::ServicesCategory) + servicesAction->addAction( logModeAction->actionMenu() ); + else if (logModeAction->category() == LogModeAction::OthersCategory) + othersAction->addAction( logModeAction->actionMenu() ); + + if (logModeAction->isInToolBar() == true) { + toolBarLogModeActions.append( logModeAction->actionMenu() ); + } + } + + menuLogModeActions.append( servicesAction ); + menuLogModeActions.append( othersAction ); + + //Menu dynamic action list + unplugActionList(QLatin1String( "log_mode_list" )); + plugActionList(QLatin1String( "log_mode_list" ), menuLogModeActions); + + //ToolBar dynamic action list + unplugActionList(QLatin1String( "tool_bar_log_mode_list" )); + plugActionList(QLatin1String( "tool_bar_log_mode_list" ), toolBarLogModeActions); + +} + +} + +#include "mainWindow.moc" diff --git a/ksystemlog/src/mainWindow.h b/ksystemlog/src/mainWindow.h new file mode 100644 index 00000000..38904cc4 --- /dev/null +++ b/ksystemlog/src/mainWindow.h @@ -0,0 +1,142 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef MAIN_WINDOW_H +#define MAIN_WINDOW_H + +#include + +//KDE includes +#include +#include +#include +#include + +#include + +class QAction; + +class LogManager; +class LogMode; + +class View; + +class TabLogViewsWidget; + +namespace KSystemLog { + +class MainWindowPrivate; + +/** + * This class serves as the main window for ksystemlog. It handles the + * menus, toolbars, and status bars. + */ +class MainWindow : public KXmlGuiWindow { + + Q_OBJECT + + public: + /** + * Default Constructor + */ + MainWindow(); + + /** + * Default Destructor + */ + virtual ~MainWindow(); + + TabLogViewsWidget* tabs(); + + protected: + /** + * This function is called when it is time for the app to save its + * properties for session management purposes. + */ + void saveProperties(KConfigGroup& configuration); + + /** + * This function is called when this app is restored. The KConfig + * object points to the session management config file that was saved + * with @ref saveProperties + */ + void readProperties(const KConfigGroup& configuration); + + /** + * Reimplemented to save configuration when closing. + */ + void closeEvent(QCloseEvent *event); + + public slots: + void changeStatusBar(const QString& text); + void changeWindowTitle(const QString& text); + + void updateStatusBar(); + + void prepareCreatedLogManager(LogManager* logManager); + + private slots: + void fileOpen(); + + void showConfigurationDialog(); + void showDetailsDialog(); + void showLogMessageDialog(); + + //Transmits signals to active LogManager + void showSearchBar(); + void findNext(); + void findPrevious(); + + void updateSelection(); + void updateReloading(); + + void toggleFilterBar(); + + void toggleItemTooltip(bool enabled); + void toggleNewLinesDisplaying(bool displayed); + void toggleResumePauseParsing(bool paused); + + void changeCurrentTab(); + + void changeResumePauseAction(bool paused); + void selectLogModeAction(QAction* action); + + private: + + void loadLogModePlugins(); + + void setupStatusBar(); + + void setupTabLogViews(); + + void setupActions(); + void setupLogActions(); + + void updateDetailDialog(); + + MainWindowPrivate* const d; + +}; + +} + +#endif //MAIN_WINDOW_H + diff --git a/ksystemlog/src/modes/acpid/CMakeLists.txt b/ksystemlog/src/modes/acpid/CMakeLists.txt new file mode 100644 index 00000000..ec86124c --- /dev/null +++ b/ksystemlog/src/modes/acpid/CMakeLists.txt @@ -0,0 +1,35 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../base + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../config + ${CMAKE_CURRENT_BINARY_DIR}/../base + ${CMAKE_CURRENT_BINARY_DIR}/../../config +) + +set(ksystemlog_acpid_sources + acpidConfigurationWidget.cpp + acpidConfiguration.cpp + acpidAnalyzer.cpp + acpidItemBuilder.cpp + acpidLogMode.cpp + acpidFactory.cpp +) + +kde4_add_library(ksystemlog_acpid STATIC ${ksystemlog_acpid_sources}) + +add_dependencies( + ksystemlog_acpid + + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_acpid + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config +) diff --git a/ksystemlog/src/modes/acpid/acpidAnalyzer.cpp b/ksystemlog/src/modes/acpid/acpidAnalyzer.cpp new file mode 100644 index 00000000..ba64b933 --- /dev/null +++ b/ksystemlog/src/modes/acpid/acpidAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "acpidAnalyzer.h" diff --git a/ksystemlog/src/modes/acpid/acpidAnalyzer.h b/ksystemlog/src/modes/acpid/acpidAnalyzer.h new file mode 100644 index 00000000..6d15b0cd --- /dev/null +++ b/ksystemlog/src/modes/acpid/acpidAnalyzer.h @@ -0,0 +1,140 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _ACPID_ANALYZER_H_ +#define _ACPID_ANALYZER_H_ + +#include + +#include "analyzer.h" + +#include "localLogFileReader.h" +#include "parsingHelper.h" +#include "acpidLogMode.h" + + +class AcpidAnalyzer : public Analyzer { + Q_OBJECT + + public: + AcpidAnalyzer(LogMode* logMode) : + Analyzer(logMode) { + + } + + virtual ~AcpidAnalyzer() { + + } + + LogViewColumns initColumns() { + LogViewColumns columns; + columns.addColumn(LogViewColumn(i18n("Date"), true, false)); + columns.addColumn(LogViewColumn(i18n("Type"), true, true)); + columns.addColumn(LogViewColumn(i18n("Message"), true, false)); + + return columns; + } + + + protected: + LogFileReader* createLogFileReader(const LogFile& logFile) { + return new LocalLogFileReader(logFile); + } + + Analyzer::LogFileSortMode logFileSortMode() { + return Analyzer::AscendingSortedLogFile; + } + + LogLine* parseMessage(const QString& logLine, const LogFile& originalFile) { + QString line(logLine); + + int dateBegin=line.indexOf(QLatin1String( "[" )); + int dateEnd=line.indexOf(QLatin1String( "]" )); + + QString type; + QString message; + QDate date; + QTime time; + + //If there is a format problem in the line + if (dateBegin==-1 || dateEnd==-1) { + type=QLatin1String( "" ); //No type + message=line; + date=QDate::currentDate(); + time=QTime::currentTime(); + } + else { + + QString strDate=line.mid(dateBegin+1, dateEnd-dateBegin-1); + + QString month=strDate.mid(4, 3); + + QString day=strDate.mid(8, 2); + + QString hour=strDate.mid(11, 2); + QString min=strDate.mid(14, 2); + QString sec=strDate.mid(17, 2); + + QString year=strDate.mid(20, 4); + + date=QDate(year.toInt(), ParsingHelper::instance()->parseSyslogMonth(month), day.toInt()); + time=QTime(hour.toInt(), min.toInt(), sec.toInt()); + + //logDebug() << "Date=" << date.toString() << endl; + //logDebug() << "Time=" << time.toString() << endl; + + line=line.remove(0, dateEnd+2); + + int endType=line.indexOf(QLatin1String( "\"" )); + + //If the " character does not exist, it means that there is no Type category + if (endType==-1) { + type=QLatin1String( "" ); //No type + message=line; + } + else { + type=line.left(endType-1); + line=line.remove(0, endType+1); + + message=line.left(line.length()-2); + } + + } + + + QStringList list; + + list.append(type); + list.append(message); + + return new LogLine( + logLineInternalIdGenerator++, + QDateTime(date, time), + list, + originalFile.url().path(), + Globals::instance()->informationLogLevel(), + logMode + ); + } + +}; + +#endif // _ACPID_ANALYZER_H_ diff --git a/ksystemlog/src/modes/acpid/acpidConfiguration.cpp b/ksystemlog/src/modes/acpid/acpidConfiguration.cpp new file mode 100644 index 00000000..83760589 --- /dev/null +++ b/ksystemlog/src/modes/acpid/acpidConfiguration.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "acpidConfiguration.h" diff --git a/ksystemlog/src/modes/acpid/acpidConfiguration.h b/ksystemlog/src/modes/acpid/acpidConfiguration.h new file mode 100644 index 00000000..e12bd57f --- /dev/null +++ b/ksystemlog/src/modes/acpid/acpidConfiguration.h @@ -0,0 +1,74 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _ACPID_CONFIGURATION_H_ +#define _ACPID_CONFIGURATION_H_ + +#include + +#include "logModeConfiguration.h" + +#include "logging.h" +#include "defaults.h" + +#include "acpidLogMode.h" + +#include "ksystemlogConfig.h" + +class AcpidConfigurationPrivate { +public: + QStringList acpidPaths; +}; + +class AcpidConfiguration : public LogModeConfiguration { + + Q_OBJECT + + public: + AcpidConfiguration() : + d(new AcpidConfigurationPrivate()) { + + configuration->setCurrentGroup(QLatin1String( "AcpidLogMode" )); + + QStringList defaultAcpidPaths; + defaultAcpidPaths << QLatin1String( "/var/log/acpid" ); + configuration->addItemStringList(QLatin1String( "LogFilesPaths" ), d->acpidPaths, defaultAcpidPaths, QLatin1String( "LogFilesPaths" )); + + } + + virtual ~AcpidConfiguration() { + delete d; + } + + QStringList acpidPaths() const { + return d->acpidPaths; + } + + void setAcpidPaths(const QStringList& acpidPaths) { + d->acpidPaths = acpidPaths; + } + + private: + AcpidConfigurationPrivate* const d; + +}; + +#endif // _ACPID_CONFIGURATION_H_ diff --git a/ksystemlog/src/modes/acpid/acpidConfigurationWidget.cpp b/ksystemlog/src/modes/acpid/acpidConfigurationWidget.cpp new file mode 100644 index 00000000..dcd3dd4e --- /dev/null +++ b/ksystemlog/src/modes/acpid/acpidConfigurationWidget.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KApacheLog, a apache log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "acpidConfigurationWidget.h" diff --git a/ksystemlog/src/modes/acpid/acpidConfigurationWidget.h b/ksystemlog/src/modes/acpid/acpidConfigurationWidget.h new file mode 100644 index 00000000..63513cf3 --- /dev/null +++ b/ksystemlog/src/modes/acpid/acpidConfigurationWidget.h @@ -0,0 +1,101 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _ACPID_CONFIGURATION_WIDGET_H_ +#define _ACPID_CONFIGURATION_WIDGET_H_ + + +#include + +#include "globals.h" +#include "logging.h" +#include "fileList.h" + +#include "logLevel.h" + +#include "acpidConfiguration.h" +#include "acpidLogMode.h" + +#include "logModeConfigurationWidget.h" + +class FileList; + +class AcpidConfigurationWidget : public LogModeConfigurationWidget { + + Q_OBJECT + + public: + AcpidConfigurationWidget() : + LogModeConfigurationWidget(i18n("Acpid Log"), QLatin1String( ACPID_MODE_ICON ), i18n("Acpid Log")) + { + + QHBoxLayout* layout = new QHBoxLayout(); + this->setLayout(layout); + + fileList=new FileList(this, i18n("

These files will be analyzed to show the Acpid log.

") + ); + connect(fileList, SIGNAL(fileListChanged()), this, SIGNAL(configurationChanged())); + layout->addWidget(fileList); + } + + + + ~AcpidConfigurationWidget() { + + } + + public slots: + + void saveConfig() { + AcpidConfiguration* acpidConfiguration = Globals::instance()->findLogMode(QLatin1String(ACPID_LOG_MODE_ID ))->logModeConfiguration(); + + acpidConfiguration->setAcpidPaths(fileList->paths()); + } + + void readConfig() { + AcpidConfiguration* acpidConfiguration = Globals::instance()->findLogMode(QLatin1String( ACPID_LOG_MODE_ID ))->logModeConfiguration(); + + fileList->removeAllItems(); + + fileList->addPaths(acpidConfiguration->acpidPaths()); + } + + void defaultConfig() { + //TODO Find a way to read the configuration per default + readConfig(); + } + + protected: + bool isValid() const { + if (fileList->isEmpty()==false) { + return true; + } + + return false; + + } + + private: + FileList* fileList; + +}; + +#endif // _ACPID_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/modes/acpid/acpidFactory.cpp b/ksystemlog/src/modes/acpid/acpidFactory.cpp new file mode 100644 index 00000000..88a1f812 --- /dev/null +++ b/ksystemlog/src/modes/acpid/acpidFactory.cpp @@ -0,0 +1,48 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "acpidFactory.h" + +#include + + +#include "logMode.h" +#include "logging.h" + +#include "simpleAction.h" +#include "acpidLogMode.h" + +#include "logModeFactory.h" + +QList AcpidLogModeFactory::createLogModes() const { + QList logModes; + logModes.append(new AcpidLogMode()); + return logModes; +} + +LogModeAction* AcpidLogModeFactory::createLogModeAction() const { + LogMode* logMode = Globals::instance()->findLogMode(QLatin1String( ACPID_LOG_MODE_ID )); + SimpleAction* logModeAction = new SimpleAction(logMode->action(), logMode); + logModeAction->setInToolBar(false); + logModeAction->setCategory(LogModeAction::OthersCategory); + + return logModeAction; +} diff --git a/ksystemlog/src/modes/acpid/acpidFactory.h b/ksystemlog/src/modes/acpid/acpidFactory.h new file mode 100644 index 00000000..ac4e483e --- /dev/null +++ b/ksystemlog/src/modes/acpid/acpidFactory.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _ACPID_FACTORY_H_ +#define _ACPID_FACTORY_H_ + +#include + +#include "logModeFactory.h" + +class LogModeAction; +class LogMode; + +class AcpidLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; + +}; + +#endif // _ACPID_FACTORY_H_ + diff --git a/ksystemlog/src/modes/acpid/acpidItemBuilder.cpp b/ksystemlog/src/modes/acpid/acpidItemBuilder.cpp new file mode 100644 index 00000000..4d3d5607 --- /dev/null +++ b/ksystemlog/src/modes/acpid/acpidItemBuilder.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "acpidItemBuilder.h" diff --git a/ksystemlog/src/modes/acpid/acpidItemBuilder.h b/ksystemlog/src/modes/acpid/acpidItemBuilder.h new file mode 100644 index 00000000..6834cfcd --- /dev/null +++ b/ksystemlog/src/modes/acpid/acpidItemBuilder.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _ACPID_ITEM_BUILDER_H_ +#define _ACPID_ITEM_BUILDER_H_ + +#include + +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +#include "logModeItemBuilder.h" + +class LogLine; + +class AcpidItemBuilder : public LogModeItemBuilder { + + public: + AcpidItemBuilder() { + + } + + virtual ~AcpidItemBuilder() { + + } + + QString createFormattedText(LogLine* line) const { + QString result; + + QListIterator it(line->logItems()); + + result.append(QLatin1String( "" )); + + result.append(labelMessageFormat(i18n("Date:"), formatDate(line->time()))); + result.append(labelMessageFormat(i18n("Level:"), line->logLevel()->name())); + result.append(labelMessageFormat(i18n("Type:"), it.next())); + + result.append(QLatin1String( "
" )); + + return result; + } + +}; + +#endif // _ACPID_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/modes/acpid/acpidLogMode.cpp b/ksystemlog/src/modes/acpid/acpidLogMode.cpp new file mode 100644 index 00000000..c2b9eeb7 --- /dev/null +++ b/ksystemlog/src/modes/acpid/acpidLogMode.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "acpidLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "acpidAnalyzer.h" +#include "acpidItemBuilder.h" +#include "acpidConfigurationWidget.h" +#include "acpidConfiguration.h" + +AcpidLogMode::AcpidLogMode() : + LogMode(QLatin1String( ACPID_LOG_MODE_ID ), i18n("ACPI Log"),QLatin1String( ACPID_MODE_ICON )) { + + d->logModeConfiguration = new AcpidConfiguration(); + + d->logModeConfigurationWidget = new AcpidConfigurationWidget(); + + d->itemBuilder = new AcpidItemBuilder(); + + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the ACPI log.")); + d->action->setWhatsThis(i18n("Displays the ACPI log in the current tab. ACPI is used to manage the hardware components of your computer, like notebook batteries, reset buttons...")); + +} + +AcpidLogMode::~AcpidLogMode() { + +} + +Analyzer* AcpidLogMode::createAnalyzer() { + return new AcpidAnalyzer(this); +} + +QList AcpidLogMode::createLogFiles() { + AcpidConfiguration* configuration = logModeConfiguration(); + return configuration->findNoModeLogFiles(configuration->acpidPaths()); +} diff --git a/ksystemlog/src/modes/acpid/acpidLogMode.h b/ksystemlog/src/modes/acpid/acpidLogMode.h new file mode 100644 index 00000000..cc407998 --- /dev/null +++ b/ksystemlog/src/modes/acpid/acpidLogMode.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _ACPID_LOG_MODE_H_ +#define _ACPID_LOG_MODE_H_ + +/** + * Acpid Log Mode Identifier + */ +#define ACPID_LOG_MODE_ID "acpidLogMode" + +/** + * Acpid Log Icon + */ +#define ACPID_MODE_ICON "battery" + +#include + +#include "logFile.h" + +#include "logMode.h" + +class AcpidLogMode : public LogMode { + + Q_OBJECT + +public: + explicit AcpidLogMode(); + + ~AcpidLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _ACPID_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/apache/CMakeLists.txt b/ksystemlog/src/modes/apache/CMakeLists.txt new file mode 100644 index 00000000..5b1964f9 --- /dev/null +++ b/ksystemlog/src/modes/apache/CMakeLists.txt @@ -0,0 +1,38 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../config + ${CMAKE_CURRENT_SOURCE_DIR}/../base + ${CMAKE_CURRENT_BINARY_DIR}/../../config + ${CMAKE_CURRENT_BINARY_DIR}/../base +) + +set(ksystemlog_apache_sources + apacheConfiguration.cpp + apacheConfigurationWidget.cpp + apacheAnalyzer.cpp + apacheItemBuilder.cpp + apacheAccessAnalyzer.cpp + apacheAccessItemBuilder.cpp + apacheFactory.cpp + apacheLogMode.cpp + apacheAccessLogMode.cpp +) + +kde4_add_library(ksystemlog_apache STATIC ${ksystemlog_apache_sources}) + +add_dependencies( + ksystemlog_apache + + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_apache + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config +) diff --git a/ksystemlog/src/modes/apache/apacheAccessAnalyzer.cpp b/ksystemlog/src/modes/apache/apacheAccessAnalyzer.cpp new file mode 100644 index 00000000..353a334a --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheAccessAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "apacheAccessAnalyzer.h" diff --git a/ksystemlog/src/modes/apache/apacheAccessAnalyzer.h b/ksystemlog/src/modes/apache/apacheAccessAnalyzer.h new file mode 100644 index 00000000..75f51ab5 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheAccessAnalyzer.h @@ -0,0 +1,149 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _APACHE_ACCESS_ANALYZER_H_ +#define _APACHE_ACCESS_ANALYZER_H_ + + +#include + +#include "analyzer.h" + +#include "localLogFileReader.h" +#include "logging.h" +#include "parsingHelper.h" + +#include "apacheAccessLogMode.h" + +class ApacheAccessAnalyzer : public Analyzer { + + Q_OBJECT + + public: + explicit ApacheAccessAnalyzer(LogMode* logMode) : + Analyzer(logMode) { + + } + + virtual ~ApacheAccessAnalyzer() { + + } + + LogViewColumns initColumns() { + LogViewColumns columns; + + columns.addColumn(LogViewColumn(i18n("Date"), true, false)); + columns.addColumn(LogViewColumn(i18n("Host Name"), true, true)); + columns.addColumn(LogViewColumn(i18n("Id."), true, true)); //=Identification protocol [From RFC1413 (see Google for more infos)] + columns.addColumn(LogViewColumn(i18n("User"), true, true)); + columns.addColumn(LogViewColumn(i18n("Response"), true, true)); + columns.addColumn(LogViewColumn(i18n("Bytes Sent"), true, false)); + columns.addColumn(LogViewColumn(i18n("Agent Identity"), true, true)); + columns.addColumn(LogViewColumn(i18n("HTTP Request"), true, false)); + columns.addColumn(LogViewColumn(i18n("URL"), true, true)); + + return columns; + } + + + protected: + + LogFileReader* createLogFileReader(const LogFile& logFile) { + return new LocalLogFileReader(logFile); + } + + Analyzer::LogFileSortMode logFileSortMode() { + return Analyzer::AscendingSortedLogFile; + } + + LogLine* parseMessage(const QString& logLine, const LogFile& originalLogFile) { + QString line(logLine); + + int spacePos=line.indexOf(QLatin1Char( ' ' )); + + QString hostName=line.left(spacePos); + line=line.remove(0, spacePos+1); + + spacePos=line.indexOf(QLatin1Char( ' ' )); + QString identd=line.left(spacePos); + line=line.remove(0, spacePos+1); + + spacePos=line.indexOf(QLatin1Char( ' ' )); + QString userName=line.left(spacePos); + line=line.remove(0, spacePos+1); + + int endDate=line.indexOf(QLatin1Char( ']' )); + QString strDateTime=line.left(endDate); + line=line.remove(0, endDate+3); + + QDateTime dateTime=ParsingHelper::instance()->parseHttpDateTime(strDateTime.mid(1, strDateTime.count()-2)); + + int endQuote=line.indexOf(QLatin1Char( '\"' )); + QString message=line.left(endQuote); + line=line.remove(0, endQuote+2); + + spacePos=line.indexOf(QLatin1Char( ' ' )); + QString httpResponse=ParsingHelper::instance()->parseHttpResponse(line.left(spacePos)); + line=line.remove(0, spacePos+1); + + spacePos=line.indexOf(QLatin1Char( ' ' )); + QString bytesSent=ParsingHelper::instance()->parseSize(line.left(spacePos)); + line=line.remove(0, spacePos+2); + + QString url; + + endQuote=line.indexOf(QLatin1Char( '\"' )); + if (endQuote!=-1) { + url=line.left(endQuote); + line=line.remove(0, endQuote+3); + } + + QString agent; + + //TODO Convert this value to find a more simple name for the Agent + endQuote=line.indexOf(QLatin1Char( '\"' )); + if (endQuote!=-1) { + agent=ParsingHelper::instance()->parseAgent(line.left(endQuote)); + } + + QStringList list; + list.append(hostName); + list.append(identd); + list.append(userName); + list.append(httpResponse); + list.append(bytesSent); + list.append(agent); + list.append(message); + list.append(url); + + return new LogLine( + logLineInternalIdGenerator++, + dateTime, + list, + originalLogFile.url().path(), + Globals::instance()->informationLogLevel(), + logMode + ); + } + +}; + +#endif // _APACHE_ACCESS_ANALYZER_H_ diff --git a/ksystemlog/src/modes/apache/apacheAccessItemBuilder.cpp b/ksystemlog/src/modes/apache/apacheAccessItemBuilder.cpp new file mode 100644 index 00000000..c6a5d581 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheAccessItemBuilder.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "apacheAccessItemBuilder.h" diff --git a/ksystemlog/src/modes/apache/apacheAccessItemBuilder.h b/ksystemlog/src/modes/apache/apacheAccessItemBuilder.h new file mode 100644 index 00000000..ddabf069 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheAccessItemBuilder.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _APACHE_ACCESS_ITEM_BUILDER_H_ +#define _APACHE_ACCESS_ITEM_BUILDER_H_ + +#include + +#include "logModeItemBuilder.h" + +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +class ApacheAccessItemBuilder : public LogModeItemBuilder { + + public: + ApacheAccessItemBuilder() { + + } + + virtual ~ApacheAccessItemBuilder() { + + } + + QString createFormattedText(LogLine* line) const { + QString result; + + QListIterator it(line->logItems()); + + result.append(QLatin1String( "" )); + + result.append(labelMessageFormat(i18n("Date:"), formatDate(line->time()))); + result.append(labelMessageFormat(i18n("Level:"), line->logLevel()->name())); + result.append(labelMessageFormat(i18n("Host Name:"), it.next() )); + result.append(labelMessageFormat(i18n("Identification:"), it.next() )); + result.append(labelMessageFormat(i18n("Username:"), it.next() )); + result.append(labelMessageFormat(i18n("HTTP Response:"), it.next() )); + result.append(labelMessageFormat(i18n("Bytes Sent:"), it.next() )); + result.append(labelMessageFormat(i18n("Agent Identity:"), it.next() )); + result.append(labelMessageFormat(i18n("HTTP Request:"), it.next() )); + + result.append(QLatin1String( "
" )); + + return result; + } + +}; + +#endif // _APACHE_ACCESS_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/modes/apache/apacheAccessLogMode.cpp b/ksystemlog/src/modes/apache/apacheAccessLogMode.cpp new file mode 100644 index 00000000..904d1991 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheAccessLogMode.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "apacheAccessLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "apacheAccessAnalyzer.h" +#include "apacheAccessItemBuilder.h" +#include "apacheConfigurationWidget.h" +#include "apacheConfiguration.h" + + +ApacheAccessLogMode::ApacheAccessLogMode(ApacheConfiguration* apacheConfiguration, ApacheConfigurationWidget* apacheConfigurationWidget) : + LogMode(QLatin1String( APACHE_ACCESS_LOG_MODE_ID ), i18n("Apache Access Log"), QLatin1String( APACHE_ACCESS_MODE_ICON )) { + + d->logModeConfiguration = apacheConfiguration; + d->logModeConfigurationWidget = apacheConfigurationWidget; + + d->itemBuilder = new ApacheAccessItemBuilder(); + + //Apache Log Action + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the Apache Access log.")); + d->action->setWhatsThis(i18n("Displays the Apache Access log in the current tab. Apache is the main used Web server in the world. This log saves all requests performed by the Apache web server.")); + +} + +ApacheAccessLogMode::~ApacheAccessLogMode() { + +} + +Analyzer* ApacheAccessLogMode::createAnalyzer() { + return new ApacheAccessAnalyzer(this); +} + +QList ApacheAccessLogMode::createLogFiles() { + ApacheConfiguration* apacheConfiguration = logModeConfiguration(); + return apacheConfiguration->findNoModeLogFiles(apacheConfiguration->apacheAccessPaths()); +} diff --git a/ksystemlog/src/modes/apache/apacheAccessLogMode.h b/ksystemlog/src/modes/apache/apacheAccessLogMode.h new file mode 100644 index 00000000..0d0228f9 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheAccessLogMode.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _APACHE_ACCESS_LOG_MODE_H_ +#define _APACHE_ACCESS_LOG_MODE_H_ + +/** + * Apache Access Log Mode Identifier + */ +#define APACHE_ACCESS_LOG_MODE_ID "apacheAccessLogMode" + +/** + * Apache Access Log Icon + */ +#define APACHE_ACCESS_MODE_ICON "network-server" + + +#include + +#include "logFile.h" + +#include "logMode.h" + +class ApacheConfiguration; +class ApacheConfigurationWidget; + +class ApacheAccessLogMode : public LogMode { + + Q_OBJECT + +public: + explicit ApacheAccessLogMode(ApacheConfiguration* apacheConfiguration, ApacheConfigurationWidget* apacheConfigurationWidget); + + ~ApacheAccessLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _APACHE_ACCESS_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/apache/apacheAnalyzer.cpp b/ksystemlog/src/modes/apache/apacheAnalyzer.cpp new file mode 100644 index 00000000..15864582 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "apacheAnalyzer.h" diff --git a/ksystemlog/src/modes/apache/apacheAnalyzer.h b/ksystemlog/src/modes/apache/apacheAnalyzer.h new file mode 100644 index 00000000..77240fa1 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheAnalyzer.h @@ -0,0 +1,183 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _APACHE_ANALYZER_H_ +#define _APACHE_ANALYZER_H_ + + +#include + +#include "analyzer.h" + +#include "logging.h" + +#include "localLogFileReader.h" +#include "apacheLogMode.h" +#include "parsingHelper.h" + +class ApacheAnalyzer : public Analyzer { + + Q_OBJECT + + public: + explicit ApacheAnalyzer(LogMode* logMode) : + Analyzer(logMode) { + + initializeTypeLevels(); + } + + virtual ~ApacheAnalyzer() { + + } + + LogViewColumns initColumns() { + LogViewColumns columns; + columns.addColumn(LogViewColumn(i18n("Date"), true, false)); + columns.addColumn(LogViewColumn(i18n("Client"), true, false)); + columns.addColumn(LogViewColumn(i18n("Message"), true, false)); + + return columns; + } + + + + protected: + LogFileReader* createLogFileReader(const LogFile& logFile) { + return new LocalLogFileReader(logFile); + } + + Analyzer::LogFileSortMode logFileSortMode() { + return Analyzer::AscendingSortedLogFile; + } + + /* + * Log line examples : + * [Wed May 18 22:16:02 2005] [error] [client 127.0.0.1] File does not exist: /var/www/html/ksystemlog/screenshots/small/kernel-view.png, referer: http://localhost.localdomain/ksystemlog/screenshots.php + * [Wed May 18 22:16:02 2005] [error] [client 127.0.0.1] File does not exist: /var/www/html/ksystemlog/screenshots/small/system-filter.png, referer: http://localhost.localdomain/ksystemlog/screenshots.php + * [Thu May 19 18:00:19 2005] [notice] mod_jk2.post_config() first invocation + * [Thu May 19 18:00:19 2005] [notice] Digest: generating secret for digest authentication ... + * [client 127.0.0.1] PHP Parse error: parse error, unexpected T_PRIVATE, expecting T_STRING in /mnt/boulot/web/annivernet/src/fonctions/formulaire.inc.php on line 25 + */ + LogLine* parseMessage(const QString& logLine, const LogFile& originalLogFile) { + + QString line(logLine); + + QDate date; + QTime time; + + QString level; + + //Temporary variable + int squareBracket; + + //Special case which sometimes happens + if (line.indexOf(QLatin1String( "[client" ))==0) { + date=QDate::currentDate(); + time=QTime::currentTime(); + level=QLatin1String( "notice" ); + } + else { + + //The Date + int dateBegin=line.indexOf(QLatin1String( "[" )); + int dateEnd=line.indexOf(QLatin1String( "]" )); + + QString type; + QString message; + + + QString strDate=line.mid(dateBegin+1, dateEnd-dateBegin-1); + + QString month=strDate.mid(4, 3); + + QString day=strDate.mid(8, 2); + + QString hour=strDate.mid(11, 2); + QString min=strDate.mid(14, 2); + QString sec=strDate.mid(17, 2); + + QString year=strDate.mid(20, 4); + + date=QDate(year.toInt(), ParsingHelper::instance()->parseSyslogMonth(month), day.toInt()); + time=QTime(hour.toInt(), min.toInt(), sec.toInt()); + + line=line.remove(0, dateEnd+3); + + + //The log level + squareBracket=line.indexOf(QLatin1String( "]" )); + level=line.left(squareBracket); + line=line.remove(0, squareBracket+2); + } + + //The client + int beginSquareBracket=line.indexOf(QLatin1String( "[client" )); + squareBracket=line.indexOf(QLatin1String( "]" )); + QString client; + if (beginSquareBracket==-1 || squareBracket==-1) { + client=QLatin1String( "" ); + } + else { + client=line.mid(8, squareBracket-8); //8=strlen("[client ") + line=line.remove(0, squareBracket+2); + } + + + QStringList list; + list.append(client); + list.append(line); + + return new LogLine( + logLineInternalIdGenerator++, + QDateTime(date, time), + list, + originalLogFile.url().path(), + findLogLevel(level), + logMode + ); + } + + private: + QMap mapTypeLevels; + + void initializeTypeLevels() { + mapTypeLevels[QLatin1String( "notice" )]=Globals::instance()->informationLogLevel(); + mapTypeLevels[QLatin1String( "warn" )]=Globals::instance()->warningLogLevel(); + mapTypeLevels[QLatin1String( "error" )]=Globals::instance()->errorLogLevel(); + } + + LogLevel* findLogLevel(const QString& type) { + QMap::iterator it; + + it=mapTypeLevels.find(type); + if (it!=mapTypeLevels.end()) { + return (*it); + } + else { + logError() << "New Log Level detected: Please send this log file to the KSystemLog developer to add it (" << type << ")" << endl; + return Globals::instance()->noLogLevel(); + } + } + + +}; + +#endif // _APACHE_ANALYZER_H_ diff --git a/ksystemlog/src/modes/apache/apacheConfiguration.cpp b/ksystemlog/src/modes/apache/apacheConfiguration.cpp new file mode 100644 index 00000000..eac17be8 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheConfiguration.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "apacheConfiguration.h" diff --git a/ksystemlog/src/modes/apache/apacheConfiguration.h b/ksystemlog/src/modes/apache/apacheConfiguration.h new file mode 100644 index 00000000..7980580e --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheConfiguration.h @@ -0,0 +1,87 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _APACHE_CONFIGURATION_H_ +#define _APACHE_CONFIGURATION_H_ + +#include + +#include "logModeConfiguration.h" + +#include "logging.h" +#include "defaults.h" + +#include "apacheLogMode.h" + +#include "ksystemlogConfig.h" + +class ApacheConfigurationPrivate { +public: + QStringList apachePaths; + + QStringList apacheAccessPaths; +}; + +class ApacheConfiguration : public LogModeConfiguration { + + Q_OBJECT + + public: + ApacheConfiguration() : + d(new ApacheConfigurationPrivate()) { + + configuration->setCurrentGroup(QLatin1String( "ApacheLogMode" )); + + QStringList defaultApachePaths; + defaultApachePaths << QLatin1String( "/var/log/apache2/error.log" ); + configuration->addItemStringList(QLatin1String( "ApacheLogFilesPaths" ), d->apachePaths, defaultApachePaths, QLatin1String( "ApacheLogFilesPaths" )); + + QStringList defaultApacheAccessPaths; + defaultApacheAccessPaths << QLatin1String( "/var/log/apache2/access.log" ); + configuration->addItemStringList(QLatin1String( "ApacheAccessLogFilesPaths" ), d->apacheAccessPaths, defaultApacheAccessPaths, QLatin1String( "ApacheAccessLogFilesPaths" )); + } + + virtual ~ApacheConfiguration() { + delete d; + } + + QStringList apachePaths() const { + return d->apachePaths; + } + + QStringList apacheAccessPaths() const { + return d->apacheAccessPaths; + } + + void setApachePaths(const QStringList& apachePaths) { + d->apachePaths = apachePaths; + } + + void setApacheAccessPaths(const QStringList& apacheAccessPaths) { + d->apacheAccessPaths = apacheAccessPaths; + } + + private: + ApacheConfigurationPrivate* const d; + +}; + +#endif // _APACHE_CONFIGURATION_H_ diff --git a/ksystemlog/src/modes/apache/apacheConfigurationWidget.cpp b/ksystemlog/src/modes/apache/apacheConfigurationWidget.cpp new file mode 100644 index 00000000..39546807 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheConfigurationWidget.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KApacheLog, a apache log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "apacheConfigurationWidget.h" diff --git a/ksystemlog/src/modes/apache/apacheConfigurationWidget.h b/ksystemlog/src/modes/apache/apacheConfigurationWidget.h new file mode 100644 index 00000000..5286bfb9 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheConfigurationWidget.h @@ -0,0 +1,111 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _APACHE_CONFIGURATION_WIDGET_H_ +#define _APACHE_CONFIGURATION_WIDGET_H_ + +#include "logModeConfigurationWidget.h" + + +#include + +#include "globals.h" +#include "logging.h" +#include "multipleFileList.h" + +#include "logLevel.h" + +#include "apacheConfiguration.h" +#include "apacheLogMode.h" + +class ApacheConfigurationWidget : public LogModeConfigurationWidget { + + Q_OBJECT + + public: + ApacheConfigurationWidget() : + LogModeConfigurationWidget(i18n("Apache Log"),QLatin1String( APACHE_MODE_ICON ), i18n("Apache Log")) + { + + QHBoxLayout* layout = new QHBoxLayout(); + this->setLayout(layout); + + apacheFileList=new MultipleFileList(this, i18n("

These files will be analyzed to show the Apache log and the Apache Access log.

")); + + apachePathsId = apacheFileList->addCategory(i18n("Apache Log Files"), i18n("Add Apache File...")); + apacheAccessPathsId = apacheFileList->addCategory(i18n("Apache Access Log Files"), i18n("Add Apache Access File...")); + + connect(apacheFileList, SIGNAL(fileListChanged()), this, SIGNAL(configurationChanged())); + + layout->addWidget(apacheFileList); + } + + ~ApacheConfigurationWidget() { + + } + + + public slots: + + void saveConfig() { + logDebug() << "Saving config from Apache Options..." << endl; + + ApacheConfiguration* apacheConfiguration = Globals::instance()->findLogMode(QLatin1String( APACHE_LOG_MODE_ID ))->logModeConfiguration(); + apacheConfiguration->setApachePaths(apacheFileList->paths(apachePathsId)); + apacheConfiguration->setApacheAccessPaths(apacheFileList->paths(apacheAccessPathsId)); + } + + void defaultConfig() { + //TODO Find a way to read the configuration per default + readConfig(); + } + + void readConfig() { + ApacheConfiguration* apacheConfiguration = Globals::instance()->findLogMode(QLatin1String( APACHE_LOG_MODE_ID ))->logModeConfiguration(); + + apacheFileList->removeAllItems(); + + apacheFileList->addPaths(apachePathsId, apacheConfiguration->apachePaths()); + apacheFileList->addPaths(apacheAccessPathsId, apacheConfiguration->apacheAccessPaths()); + } + + protected: + bool isValid() const { + if (apacheFileList->isOneOfCategoryEmpty()==true) { + logDebug() << "Apache configuration not valid" << endl; + return false; + } + + logDebug() << "Apache configuration valid" << endl; + return true; + + } + + private: + + MultipleFileList* apacheFileList; + + int apachePathsId; + int apacheAccessPathsId; + +}; + +#endif // _APACHE_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/modes/apache/apacheFactory.cpp b/ksystemlog/src/modes/apache/apacheFactory.cpp new file mode 100644 index 00000000..b9db7354 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheFactory.cpp @@ -0,0 +1,62 @@ +/*************************************************************************** + * KApacheLog, a apache log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "apacheFactory.h" + + +#include + +#include "multipleActions.h" +#include "logMode.h" +#include "logging.h" + +#include "apacheLogMode.h" +#include "apacheAccessLogMode.h" + +#include "apacheConfigurationWidget.h" +#include "apacheConfiguration.h" + +QList ApacheLogModeFactory::createLogModes() const { + + //Create the shared configuration and configuration widget between the logModes + + ApacheConfiguration* logModeConfiguration = new ApacheConfiguration(); + ApacheConfigurationWidget* logModeConfigurationWidget = new ApacheConfigurationWidget(); + + QList logModes; + logModes.append(new ApacheLogMode(logModeConfiguration, logModeConfigurationWidget)); + logModes.append(new ApacheAccessLogMode(logModeConfiguration, logModeConfigurationWidget)); + + return logModes; +} + +LogModeAction* ApacheLogModeFactory::createLogModeAction() const { + LogMode* apacheLogMode = Globals::instance()->findLogMode(QLatin1String( APACHE_LOG_MODE_ID )); + + MultipleActions* multipleActions = new MultipleActions(KIcon( QLatin1String(APACHE_MODE_ICON) ), i18n("Apache"), apacheLogMode); + multipleActions->addInnerAction(apacheLogMode->action()); + multipleActions->addInnerAction(Globals::instance()->findLogMode(QLatin1String( APACHE_ACCESS_LOG_MODE_ID ))->action()); + + multipleActions->setInToolBar(false); + multipleActions->setCategory(LogModeAction::ServicesCategory); + + return multipleActions; +} diff --git a/ksystemlog/src/modes/apache/apacheFactory.h b/ksystemlog/src/modes/apache/apacheFactory.h new file mode 100644 index 00000000..1aaade48 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheFactory.h @@ -0,0 +1,41 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _APACHE_LOG_MODE_FACTORY_H_ +#define _APACHE_LOG_MODE_FACTORY_H_ + +#include + +#include "logModeFactory.h" + +class ApacheLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; +}; + +#endif // _APACHE_LOG_MODE_FACTORY_H_ + diff --git a/ksystemlog/src/modes/apache/apacheItemBuilder.cpp b/ksystemlog/src/modes/apache/apacheItemBuilder.cpp new file mode 100644 index 00000000..9f8f5e9b --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheItemBuilder.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "apacheItemBuilder.h" diff --git a/ksystemlog/src/modes/apache/apacheItemBuilder.h b/ksystemlog/src/modes/apache/apacheItemBuilder.h new file mode 100644 index 00000000..2d83f006 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheItemBuilder.h @@ -0,0 +1,67 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _APACHE_ITEM_BUILDER_H_ +#define _APACHE_ITEM_BUILDER_H_ + +#include + +#include "logModeItemBuilder.h" + +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +class ApacheItemBuilder : public LogModeItemBuilder { + + public: + ApacheItemBuilder() { + + } + + virtual ~ApacheItemBuilder() { + + } + + QString createFormattedText(LogLine* line) const { + QString result; + + QListIterator it(line->logItems()); + + result.append(QLatin1String( "" )); + + result.append(labelMessageFormat(i18n("Date:"), formatDate(line->time()))); + result.append(labelMessageFormat(i18n("Level:"), line->logLevel()->name())); + result.append(labelMessageFormat(i18n("Client:"), it.next() )); + + result.append(QLatin1String( "
" )); + + return result; + + } +}; + + +#endif // _APACHE_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/modes/apache/apacheLogMode.cpp b/ksystemlog/src/modes/apache/apacheLogMode.cpp new file mode 100644 index 00000000..800663b4 --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheLogMode.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "apacheLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "apacheAnalyzer.h" +#include "apacheItemBuilder.h" +#include "apacheConfigurationWidget.h" +#include "apacheConfiguration.h" + + +ApacheLogMode::ApacheLogMode(ApacheConfiguration* apacheConfiguration, ApacheConfigurationWidget* apacheConfigurationWidget) : + LogMode(QLatin1String( APACHE_LOG_MODE_ID ), i18n("Apache Log"),QLatin1String( APACHE_MODE_ICON )) { + + d->logModeConfiguration = apacheConfiguration; + d->logModeConfigurationWidget = apacheConfigurationWidget; + + d->itemBuilder = new ApacheItemBuilder(); + + //Apache Log Action + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the Apache log.")); + d->action->setWhatsThis(i18n("Displays the Apache log in the current tab. Apache is the main used Web server in the world.")); + +} + +ApacheLogMode::~ApacheLogMode() { + +} + +Analyzer* ApacheLogMode::createAnalyzer() { + return new ApacheAnalyzer(this); +} + +QList ApacheLogMode::createLogFiles() { + ApacheConfiguration* apacheConfiguration = logModeConfiguration(); + return apacheConfiguration->findNoModeLogFiles(apacheConfiguration->apachePaths()); +} diff --git a/ksystemlog/src/modes/apache/apacheLogMode.h b/ksystemlog/src/modes/apache/apacheLogMode.h new file mode 100644 index 00000000..29a8cb0c --- /dev/null +++ b/ksystemlog/src/modes/apache/apacheLogMode.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _APACHE_LOG_MODE_H_ +#define _APACHE_LOG_MODE_H_ + +/** + * Apache Log Mode Identifier + */ +#define APACHE_LOG_MODE_ID "apacheLogMode" + +/** + * Apache Log Icon + */ +#define APACHE_MODE_ICON "network-server" + + +#include + +#include "logFile.h" + +#include "logMode.h" + +class ApacheConfiguration; +class ApacheConfigurationWidget; + +class ApacheLogMode : public LogMode { + + Q_OBJECT + +public: + explicit ApacheLogMode(ApacheConfiguration* apacheConfiguration, ApacheConfigurationWidget* apacheConfigurationWidget); + + ~ApacheLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _APACHE_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/authentication/CMakeLists.txt b/ksystemlog/src/modes/authentication/CMakeLists.txt new file mode 100644 index 00000000..12a8ad6e --- /dev/null +++ b/ksystemlog/src/modes/authentication/CMakeLists.txt @@ -0,0 +1,33 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../base + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../config + ${CMAKE_CURRENT_BINARY_DIR}/../base + ${CMAKE_CURRENT_BINARY_DIR}/../../config +) + +set(ksystemlog_authentication_sources + authenticationConfigurationWidget.cpp + authenticationConfiguration.cpp + authenticationAnalyzer.cpp + authenticationLogMode.cpp + authenticationFactory.cpp +) + +kde4_add_library(ksystemlog_authentication STATIC ${ksystemlog_authentication_sources}) + +add_dependencies( + ksystemlog_authentication + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_authentication + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config +) diff --git a/ksystemlog/src/modes/authentication/authenticationAnalyzer.cpp b/ksystemlog/src/modes/authentication/authenticationAnalyzer.cpp new file mode 100644 index 00000000..4ac0ba40 --- /dev/null +++ b/ksystemlog/src/modes/authentication/authenticationAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "authenticationAnalyzer.h" diff --git a/ksystemlog/src/modes/authentication/authenticationAnalyzer.h b/ksystemlog/src/modes/authentication/authenticationAnalyzer.h new file mode 100644 index 00000000..99a35908 --- /dev/null +++ b/ksystemlog/src/modes/authentication/authenticationAnalyzer.h @@ -0,0 +1,84 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _AUTHENTICATION_ANALYZER_H_ +#define _AUTHENTICATION_ANALYZER_H_ + +#include + +#include "syslogAnalyzer.h" + +#include "authenticationLogMode.h" +#include "authenticationConfiguration.h" + +class AuthenticationAnalyzer : public SyslogAnalyzer { + Q_OBJECT + + public: + + AuthenticationAnalyzer(LogMode* logMode) : + SyslogAnalyzer(logMode) { + + } + + virtual ~AuthenticationAnalyzer() { + + } + + LogLine* parseMessage(const QString& logLine, const LogFile& originalLogFile) { + LogLine* syslogLine = SyslogAnalyzer::parseMessage(logLine, originalLogFile); + + QString message = syslogLine->logItems().at(syslogLine->logItems().count() - 1); + + if (hasErrorKeywords(message)) + syslogLine->setLogLevel(Globals::instance()->errorLogLevel()); + else if (hasWarningKeywords(message)) + syslogLine->setLogLevel(Globals::instance()->warningLogLevel()); + + return syslogLine; + } + + + private: + bool hasWarningKeywords(const QString& message) { + AuthenticationConfiguration* configuration = logMode->logModeConfiguration(); + return hasKeywords(message, configuration->warningKeywords()); + } + + bool hasErrorKeywords(const QString& message) { + AuthenticationConfiguration* configuration = logMode->logModeConfiguration(); + return hasKeywords(message, configuration->errorKeywords()); + } + + bool hasKeywords(const QString& message, const QStringList& keywords) { + foreach(const QString& keyword, keywords) { + if (message.contains(keyword, Qt::CaseInsensitive)) { + return true; + } + } + + return false; + } + + +}; + +#endif diff --git a/ksystemlog/src/modes/authentication/authenticationConfiguration.cpp b/ksystemlog/src/modes/authentication/authenticationConfiguration.cpp new file mode 100644 index 00000000..b4950a61 --- /dev/null +++ b/ksystemlog/src/modes/authentication/authenticationConfiguration.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "authenticationConfiguration.h" diff --git a/ksystemlog/src/modes/authentication/authenticationConfiguration.h b/ksystemlog/src/modes/authentication/authenticationConfiguration.h new file mode 100644 index 00000000..10c7cde4 --- /dev/null +++ b/ksystemlog/src/modes/authentication/authenticationConfiguration.h @@ -0,0 +1,92 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _AUTHENTICATION_CONFIGURATION_H_ +#define _AUTHENTICATION_CONFIGURATION_H_ + +#include + +#include "logModeConfiguration.h" + +#include "logging.h" +#include "defaults.h" + +#include "authenticationLogMode.h" + +#include "ksystemlogConfig.h" + +class AuthenticationConfigurationPrivate { +public: + QString authenticationPath; + + QStringList warningKeywords; + QStringList errorKeywords; +}; + +class AuthenticationConfiguration : public LogModeConfiguration { + + Q_OBJECT + + public: + AuthenticationConfiguration() : + d(new AuthenticationConfigurationPrivate()) { + + configuration->setCurrentGroup(QLatin1String( "AuthenticationLogMode" )); + + QString defaultAuthenticationPath(QLatin1String( "/var/log/auth.log" )); + configuration->addItemString(QLatin1String( "LogFilePath" ), d->authenticationPath, defaultAuthenticationPath, QLatin1String( "LogFilePath" )); + + QStringList defaultWarningKeywords; + defaultWarningKeywords.append(QLatin1String( "failed" )); + configuration->addItemStringList(QLatin1String( "WarningKeywords" ), d->warningKeywords, defaultWarningKeywords, QLatin1String( "WarningKeywords" )); + + QStringList defaultErrorKeywords; + defaultErrorKeywords.append(QLatin1String( "error" )); + configuration->addItemStringList(QLatin1String( "ErrorKeywords" ), d->errorKeywords, defaultErrorKeywords, QLatin1String( "ErrorKeywords" )); + + } + + virtual ~AuthenticationConfiguration() { + delete d; + } + + QString authenticationPath() const { + return d->authenticationPath; + } + + void setAuthenticationPath(const QString& authenticationPath) { + d->authenticationPath = authenticationPath; + } + + QStringList warningKeywords() const { + return d->warningKeywords; + } + + QStringList errorKeywords() const { + return d->errorKeywords; + } + + private: + AuthenticationConfigurationPrivate* const d; + +}; + +#endif // _AUTHENTICATION_CONFIGURATION_H_ diff --git a/ksystemlog/src/modes/authentication/authenticationConfigurationWidget.cpp b/ksystemlog/src/modes/authentication/authenticationConfigurationWidget.cpp new file mode 100644 index 00000000..ec9d1178 --- /dev/null +++ b/ksystemlog/src/modes/authentication/authenticationConfigurationWidget.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KApacheLog, a apache log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "authenticationConfigurationWidget.h" diff --git a/ksystemlog/src/modes/authentication/authenticationConfigurationWidget.h b/ksystemlog/src/modes/authentication/authenticationConfigurationWidget.h new file mode 100644 index 00000000..d708d200 --- /dev/null +++ b/ksystemlog/src/modes/authentication/authenticationConfigurationWidget.h @@ -0,0 +1,115 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _AUTHENTICATION_CONFIGURATION_WIDGET_H_ +#define _AUTHENTICATION_CONFIGURATION_WIDGET_H_ + +#include +#include + +#include +#include + +#include "globals.h" +#include "logging.h" +#include "fileList.h" + +#include "logLevel.h" + +#include "authenticationConfiguration.h" +#include "authenticationLogMode.h" + +#include "logModeConfigurationWidget.h" + + +class AuthenticationConfigurationWidget : public LogModeConfigurationWidget { + + Q_OBJECT + + public: + AuthenticationConfigurationWidget() : + LogModeConfigurationWidget(i18n("Authentication Log"),QLatin1String( AUTHENTICATION_MODE_ICON ), i18n("Authentication Log")) + { + + QVBoxLayout* layout = new QVBoxLayout(); + this->setLayout(layout); + + //Authentication log file + QGroupBox* authenticationBox=new QGroupBox(i18n("Authentication Log File")); + QHBoxLayout* authenticationLayout = new QHBoxLayout(); + authenticationBox->setLayout(authenticationLayout); + + layout->addWidget(authenticationBox); + + authenticationLayout->addWidget(new QLabel(i18n("Authentication log file:"))); + + authenticationUrlRequester=new KUrlRequester(authenticationBox); + authenticationUrlRequester->setMode(KFile::File); + + authenticationUrlRequester->setToolTip(i18n("You can type or choose the authentication log file (example: /var/log/auth.log).")); + authenticationUrlRequester->setWhatsThis(i18n("You can type or choose here the authentication log file. This file will be analyzed when you select the Authentication log menu. Generally, its name is /var/log/auth.log")); + authenticationLayout->addWidget(authenticationUrlRequester); + + connect(authenticationUrlRequester, SIGNAL(textChanged(const QString&)), this, SIGNAL(configurationChanged())); + + layout->addStretch(); + + } + + ~AuthenticationConfigurationWidget() { + + } + + public slots: + + void saveConfig() { + AuthenticationConfiguration* authenticationConfiguration = Globals::instance()->findLogMode(QLatin1String( AUTHENTICATION_LOG_MODE_ID ))->logModeConfiguration(); + + authenticationConfiguration->setAuthenticationPath(authenticationUrlRequester->url().path()); + } + + void readConfig() { + AuthenticationConfiguration* authenticationConfiguration = Globals::instance()->findLogMode(QLatin1String( AUTHENTICATION_LOG_MODE_ID ))->logModeConfiguration(); + + authenticationUrlRequester->setUrl(KUrl(authenticationConfiguration->authenticationPath())); + } + + void defaultConfig() { + //TODO Find a way to read the configuration per default + readConfig(); + } + + protected: + bool isValid() const { + if (authenticationUrlRequester->url().path().isEmpty()==false) { + return true; + } + + return false; + + } + + private: + KUrlRequester* authenticationUrlRequester; + +}; + +#endif // _AUTHENTICATION_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/modes/authentication/authenticationFactory.cpp b/ksystemlog/src/modes/authentication/authenticationFactory.cpp new file mode 100644 index 00000000..80bbad81 --- /dev/null +++ b/ksystemlog/src/modes/authentication/authenticationFactory.cpp @@ -0,0 +1,46 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "authenticationFactory.h" + +#include + + +#include "logMode.h" +#include "logging.h" + +#include "simpleAction.h" +#include "authenticationLogMode.h" + +#include "logModeFactory.h" + +QList AuthenticationLogModeFactory::createLogModes() const { + QList logModes; + logModes.append(new AuthenticationLogMode()); + return logModes; +} + +LogModeAction* AuthenticationLogModeFactory::createLogModeAction() const { + LogMode* logMode = Globals::instance()->findLogMode(QLatin1String( AUTHENTICATION_LOG_MODE_ID )); + SimpleAction* logModeAction = new SimpleAction(logMode->action(), logMode); + + return logModeAction; +} diff --git a/ksystemlog/src/modes/authentication/authenticationFactory.h b/ksystemlog/src/modes/authentication/authenticationFactory.h new file mode 100644 index 00000000..5cb15afa --- /dev/null +++ b/ksystemlog/src/modes/authentication/authenticationFactory.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _AUTHENTICATION_FACTORY_H_ +#define _AUTHENTICATION_FACTORY_H_ + +#include + +#include "logModeFactory.h" + +class LogModeAction; +class LogMode; + +class AuthenticationLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; + +}; + +#endif // _AUTHENTICATION_FACTORY_H_ + diff --git a/ksystemlog/src/modes/authentication/authenticationLogMode.cpp b/ksystemlog/src/modes/authentication/authenticationLogMode.cpp new file mode 100644 index 00000000..bcaad5e9 --- /dev/null +++ b/ksystemlog/src/modes/authentication/authenticationLogMode.cpp @@ -0,0 +1,66 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "authenticationLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "authenticationAnalyzer.h" +#include "logModeItemBuilder.h" +#include "authenticationConfigurationWidget.h" +#include "authenticationConfiguration.h" + +AuthenticationLogMode::AuthenticationLogMode() : + LogMode(QLatin1String( AUTHENTICATION_LOG_MODE_ID ), i18n("Authentication Log"),QLatin1String( AUTHENTICATION_MODE_ICON )) { + + d->logModeConfiguration = new AuthenticationConfiguration(); + + d->logModeConfigurationWidget = new AuthenticationConfigurationWidget(); + + d->itemBuilder = new LogModeItemBuilder(); + + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the authentication log.")); + d->action->setWhatsThis(i18n("Displays the authentication log in the current tab. This log displays all logins made by each user of the system, and can help you to know if someone tried to crack your system.")); + +} + +AuthenticationLogMode::~AuthenticationLogMode() { + +} + +Analyzer* AuthenticationLogMode::createAnalyzer() { + return new AuthenticationAnalyzer(this); +} + +QList AuthenticationLogMode::createLogFiles() { + AuthenticationConfiguration* configuration = logModeConfiguration(); + + QList logFiles; + logFiles.append(configuration->findGenericLogFile(configuration->authenticationPath())); + return logFiles; +} diff --git a/ksystemlog/src/modes/authentication/authenticationLogMode.h b/ksystemlog/src/modes/authentication/authenticationLogMode.h new file mode 100644 index 00000000..6a66cdff --- /dev/null +++ b/ksystemlog/src/modes/authentication/authenticationLogMode.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _AUTHENTICATION_LOG_MODE_H_ +#define _AUTHENTICATION_LOG_MODE_H_ + +/** + * Acpid Log Mode Identifier + */ +#define AUTHENTICATION_LOG_MODE_ID "authenticationLogMode" + +/** + * Authentication Log Icon + */ +#define AUTHENTICATION_MODE_ICON "document-encrypt" + +#include + +#include "logFile.h" + +#include "logMode.h" + +class AuthenticationLogMode : public LogMode { + + Q_OBJECT + +public: + explicit AuthenticationLogMode(); + + ~AuthenticationLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _AUTHENTICATION_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/base/CMakeLists.txt b/ksystemlog/src/modes/base/CMakeLists.txt new file mode 100644 index 00000000..eb10d93a --- /dev/null +++ b/ksystemlog/src/modes/base/CMakeLists.txt @@ -0,0 +1,36 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_BINARY_DIR}/../../config +) + +set(ksystemlog_base_mode_sources + parsingHelper.cpp + fileList.cpp + logLevelFileList.cpp + multipleFileList.cpp + fileListHelper.cpp + logLevelSelectionDialog.cpp + genericConfiguration.cpp + syslogAnalyzer.cpp +) + +kde4_add_ui_files(ksystemlog_base_mode_sources fileListBase.ui ) +kde4_add_ui_files(ksystemlog_base_mode_sources multipleFileListBase.ui ) + +kde4_add_ui_files(ksystemlog_base_mode_sources logLevelSelectionDialogBase.ui ) + +kde4_add_library(ksystemlog_base_mode STATIC ${ksystemlog_base_mode_sources}) + +add_dependencies( + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_base_mode + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_config +) diff --git a/ksystemlog/src/modes/base/fileList.cpp b/ksystemlog/src/modes/base/fileList.cpp new file mode 100644 index 00000000..b663671a --- /dev/null +++ b/ksystemlog/src/modes/base/fileList.cpp @@ -0,0 +1,233 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "fileList.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "defaults.h" + +#include "logging.h" + +FileList::FileList(QWidget* parent, const QString& descriptionText) : + QWidget(parent), + fileListHelper(this) + { + + logDebug() << "Initializing file list..." << endl; + + setupUi(this); + + description->setText(descriptionText); + + fileListHelper.prepareButton(add, KIcon( QLatin1String( "document-new" )), this, SLOT(addItem()), fileList); + + fileListHelper.prepareButton(modify, KIcon( QLatin1String( "document-open" )), this, SLOT(modifyItem()), fileList); + + //Add a separator in the FileList + QAction* separator = new QAction(this); + separator->setSeparator(true); + fileList->addAction(separator); + + fileListHelper.prepareButton(remove, KIcon( QLatin1String( "list-remove" )), this, SLOT(removeSelectedItem()), fileList); + + fileListHelper.prepareButton(up, KIcon( QLatin1String( "go-up" )), this, SLOT(moveUpItem()), fileList); + + fileListHelper.prepareButton(down, KIcon( QLatin1String( "go-down" )), this, SLOT(moveDownItem()), fileList); + + fileListHelper.prepareButton(removeAll, KIcon( QLatin1String( "trash-empty" )), this, SLOT(removeAllItems()), fileList); + + connect(fileList, SIGNAL(itemSelectionChanged()), this, SLOT(updateButtons())); + connect(fileList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(modifyItem(QListWidgetItem*))); + connect(this, SIGNAL(fileListChanged()), this, SLOT(updateButtons())); + + updateButtons(); + + logDebug() << "File list initialized" << endl; + +} + +FileList::~FileList() { + +} + +int FileList::count() const { + return fileList->count(); +} + +bool FileList::isEmpty() const { + return (fileList->count() == 0); +} + +void FileList::addItem() { + //Open a standard Filedialog + KUrl::List urls=fileListHelper.openUrls(); + + QStringList paths=fileListHelper.findPaths(urls); + foreach(const QString &path, paths) { + fileList->addItem(path); + } + + emit fileListChanged(); +} + +void FileList::modifyItem() { + modifyItem(fileList->item(fileList->currentRow())); +} + +void FileList::modifyItem(QListWidgetItem* item) { + QString previousPath = item->text(); + + //Open a standard Filedialog + KUrl url=fileListHelper.openUrl(previousPath); + + KUrl::List urls; + urls.append(url); + QStringList paths=fileListHelper.findPaths(urls); + + //We only take the first path + if (paths.count() >= 1) { + item->setText(paths.at(0)); + } + + emit fileListChanged(); +} + +void FileList::removeSelectedItem() { + QList selectedItems = fileList->selectedItems(); + + foreach(QListWidgetItem* item, selectedItems) { + delete fileList->takeItem(fileList->row(item)); + } + + //fileList->setCurrentRow(fileList->count()-1); + + emit fileListChanged(); +} + +void FileList::unselectAllItems() { + QList selectedItems = fileList->selectedItems(); + foreach(QListWidgetItem* item, selectedItems) { + item->setSelected(false); + } +} + +void FileList::moveItem(int direction) { + QList selectedItems = fileList->selectedItems(); + + QListWidgetItem* item = selectedItems.at(0); + + int itemIndex = fileList->row(item); + + fileList->takeItem(itemIndex); + + unselectAllItems(); + + fileList->insertItem(itemIndex + direction, item); + + fileList->setCurrentRow(fileList->row(item)); + + emit fileListChanged(); +} + +void FileList::moveUpItem() { + moveItem(-1); +} + +void FileList::moveDownItem() { + moveItem(+1); +} + +void FileList::removeAllItems() { + fileList->clear(); + + emit fileListChanged(); +} + + +void FileList::updateButtons() { + if (fileList->count()==0) + fileListHelper.setEnabledAction(removeAll, false); + else + fileListHelper.setEnabledAction(removeAll, true); + + QList selectedItems = fileList->selectedItems(); + if (selectedItems.isEmpty()==false) { + + fileListHelper.setEnabledAction(remove, true); + fileListHelper.setEnabledAction(modify, true); + + QListWidgetItem* selection = selectedItems.at(0); + + //If the item is at the top of the list, it could not be upped anymore + if (fileList->row(selection)==0) + fileListHelper.setEnabledAction(up, false); + else + fileListHelper.setEnabledAction(up, true); + + //If the item is at bottom of the list, it could not be downed anymore + if (fileList->row(selection)==fileList->count()-1) + fileListHelper.setEnabledAction(down, false); + else + fileListHelper.setEnabledAction(down, true); + } + //If nothing is selected, disabled special buttons + else { + fileListHelper.setEnabledAction(remove, false); + fileListHelper.setEnabledAction(modify, false); + fileListHelper.setEnabledAction(up, false); + fileListHelper.setEnabledAction(down, false); + } + +} + +QVBoxLayout* FileList::buttonsLayout() { + return vboxLayout1; +} + +void FileList::addPaths(const QStringList& paths) { + foreach(const QString &path, paths) { + fileList->addItem(path); + } + + updateButtons(); +} + +QStringList FileList::paths() { + QStringList paths; + for (int i=0; icount(); i++) { + paths.append(fileList->item(i)->text()); + } + + return paths; +} + +#include "fileList.moc" diff --git a/ksystemlog/src/modes/base/fileList.h b/ksystemlog/src/modes/base/fileList.h new file mode 100644 index 00000000..46ae9783 --- /dev/null +++ b/ksystemlog/src/modes/base/fileList.h @@ -0,0 +1,84 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _FILE_LIST_H_ +#define _FILE_LIST_H_ + +#include +#include + +#include "fileListHelper.h" + +#include "ui_fileListBase.h" + +class QVBoxLayout; + +class FileList : public QWidget, public Ui::FileListBase { + + Q_OBJECT + + public: + FileList(QWidget* parent, const QString& descriptionText); + virtual ~FileList(); + + int count() const; + + bool isEmpty() const; + + QStringList paths(); + + void addPaths(const QStringList& paths); + + public slots: + void removeAllItems(); + + signals: + void fileListChanged(); + + private slots: + + void updateButtons(); + + void removeSelectedItem(); + void moveUpItem(); + void moveDownItem(); + + protected slots: + virtual void addItem(); + + void modifyItem(); + void modifyItem(QListWidgetItem* item); + + protected: + void removeItem(int id); + + void moveItem(int direction); + void unselectAllItems(); + + /** + * Convenient method which returns the layout which manage the button list + */ + QVBoxLayout* buttonsLayout(); + + FileListHelper fileListHelper; +}; + +#endif //_FILE_LIST_H_ diff --git a/ksystemlog/src/modes/base/fileListBase.ui b/ksystemlog/src/modes/base/fileListBase.ui new file mode 100644 index 00000000..8bd229cf --- /dev/null +++ b/ksystemlog/src/modes/base/fileListBase.ui @@ -0,0 +1,216 @@ + + FileListBase + + + + 0 + 0 + 487 + 476 + + + + + 200 + 400 + + + + + 0 + + + 0 + + + 0 + + + + + File List Description + + + Qt::AlignJustify|Qt::AlignVCenter + + + true + + + + + + + Log Files + + + + + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Choose a new file</p></body></html> + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Opens a dialog box to choose a new file to be added to the list.</p></body></html> + + + &Add File... + + + + + + + &Modify File... + + + + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Delete the current file(s)</p></body></html> + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Deletes the files selected on the list.</p></body></html> + + + &Remove + + + + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Remove all files</p></body></html> + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Remove all the files on the list, even if they are not selected.</p></body></html> + + + Rem&ove All + + + + + + + true + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Move up the current file(s)</p></body></html> + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Moves the selected files up in the list. This option allows the files to be read <span style=" font-weight:600;">in first</span> by KSystemLog.</p></body></html> + + + Move &Up + + + + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Move down the current file(s)</p></body></html> + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Moves the selected files down in the list. This option allows the files to be read <span style=" font-weight:600;">in last</span> by KSystemLog.</p></body></html> + + + Move &Down + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 40 + + + + + + + + + + <p style='margin-top:0px;margin-bottom:0px;'><b>Notes:</b></p><ul style='margin-top:0px'><li>Files are read using the order of this list.</li><li>Compressed and plain text files are accepted <i>(*.log, *.gz, *.bz2,...)</i>.</li><li>Use the <b>'*'</b> wildcard to select multiple log files when adding files.</li></ul> + + + true + + + + + + + Qt::ActionsContextMenu + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">List of files used by this log type.</p></body></html> + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Here is a list of every file that will be read by KSystemLog to display the current log lines.</p></body></html> + + + QAbstractItemView::InternalMove + + + + + + + + + + add + + + + + + diff --git a/ksystemlog/src/modes/base/fileListHelper.cpp b/ksystemlog/src/modes/base/fileListHelper.cpp new file mode 100644 index 00000000..f2080f8f --- /dev/null +++ b/ksystemlog/src/modes/base/fileListHelper.cpp @@ -0,0 +1,172 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "fileListHelper.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "defaults.h" + +#include "logging.h" + +FileListHelper::FileListHelper(QWidget* p) : + parent(p) { + +} + +FileListHelper::~FileListHelper() { + +} + +QAction* FileListHelper::prepareButtonAndAction(QPushButton* button, const QIcon& icon) { + //Initialize action + QAction* action = new QAction(button); + action->setIcon(icon); + action->setText(button->text()); + + //Initialize button + button->setIcon(icon); + //Allow the disabling of the matching action when disabling this button + button->addAction(action); + + //Assert that when an action is triggered, the related button sends a clicked() event + //(the button is the QObject which is connected to custom slots) + connect(action, SIGNAL(triggered(bool)), button, SLOT(click())); + + return action; +} + +QAction* FileListHelper::prepareButtonAndAction(QPushButton* button, const QIcon& icon, const QObject* receiver, const char* member) { + QAction* action = prepareButtonAndAction(button, icon); + connect(button, SIGNAL(clicked(bool)), receiver, member); + + return action; +} + +void FileListHelper::prepareButton(QPushButton* button, const QIcon& icon, const QObject* receiver, const char* member, QWidget* fileList) { + //Initialize action + QAction* action = prepareButtonAndAction(button, icon, receiver, member); + fileList->addAction(action); +} + +QStringList FileListHelper::findPaths(KUrl::List urls) { + QStringList paths; + + for (KUrl::List::ConstIterator it=urls.constBegin(); it!=urls.constEnd(); ++it) { + KUrl url(*it); + + if (isValidFile(url)) { + + //If this Url uses a joker (i.e. : "/var/log/apache2/*") + if (url.fileName().contains(QLatin1Char( '*' ))) { + const QStringList foundPaths = expandJoker(url.path()); + logDebug() << "Found paths of " << url.path() << ":" << foundPaths << endl; + foreach(const QString &foundPath, foundPaths) { + paths.append(foundPath); + } + } + else { + paths.append(url.path()); + } + } + } + + return paths; +} + +bool FileListHelper::isValidFile(const KUrl& url) { + QString message; + + //If it is not valid + if (!url.isValid()) { + return false; + } + + //If it is not a local file + if (!url.isLocalFile()) { + message=i18n("'%1' is not a local file.", url.path()); + KMessageBox::error(parent, message, i18n("File selection failed"), KMessageBox::Notify); + return false; + } + + //If it's a directory, it's not valid + if (QDir(url.path()).exists()) { + return false; + } + + return true; +} + +KUrl::List FileListHelper::openUrls() { + KFileDialog fileDialog(KUrl(DEFAULT_LOG_FOLDER), QLatin1String( "*|" ) + i18n("All Files (*)") + QLatin1String( "\n*.log|" ) + i18n("Log Files (*.log)"), parent); + fileDialog.setCaption(i18n("Choose Log File")); + fileDialog.setMode(KFile::Files); + + fileDialog.exec(); + return fileDialog.selectedUrls(); +} + +KUrl FileListHelper::openUrl(const QString& originPath) { + KFileDialog fileDialog(KUrl(originPath), QLatin1String( "*|" ) + i18n("All Files (*)") + QLatin1String( "\n*.log|" ) + i18n("Log Files (*.log)"), parent); + fileDialog.setCaption(i18n("Choose Log File")); + fileDialog.setMode(KFile::File); + + fileDialog.exec(); + return fileDialog.selectedUrl(); +} + +QStringList FileListHelper::expandJoker(const KUrl& url) { + QDir directory = QDir(url.path().left(url.path().count() - url.fileName().count())); + + logDebug() << "Dir " << directory.path() << endl; + QString filename = url.fileName(); + + if (filename.isEmpty()) { + return QStringList(); + } + + QStringList foundPaths; + const QStringList files = directory.entryList(QStringList(filename), QDir::Files | QDir::NoSymLinks); + foreach(const QString &file, files) { + foundPaths.append(directory.absoluteFilePath(file)); + } + + return foundPaths; +} + +void FileListHelper::setEnabledAction(QPushButton* button, bool enabled) { + button->setEnabled(enabled); + + QList actions = button->actions(); + foreach (QAction* action, actions) { + action->setEnabled(enabled); + } +} + +#include "fileListHelper.moc" diff --git a/ksystemlog/src/modes/base/fileListHelper.h b/ksystemlog/src/modes/base/fileListHelper.h new file mode 100644 index 00000000..c930e42e --- /dev/null +++ b/ksystemlog/src/modes/base/fileListHelper.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _FILE_LIST_HELPER_H_ +#define _FILE_LIST_HELPER_H_ + +#include +#include + +#include + +class QAction; +class QPushButton; + +/** + * Class which maintains common behavior between + * FileList classes + */ +class FileListHelper : public QObject { + + Q_OBJECT + + public: + FileListHelper(QWidget* parent); + virtual ~FileListHelper(); + + QStringList findPaths(KUrl::List urls); + bool isValidFile(const KUrl& url); + QStringList expandJoker(const KUrl& url); + + KUrl::List openUrls(); + KUrl openUrl(const QString& originPath); + + /** + * Change the enabled status of the button and of its QActions + */ + void setEnabledAction(QPushButton* button, bool enabled); + + /** + * TODO Do this inline (and remove this method) + */ + void prepareButton(QPushButton* button, const QIcon& icon, const QObject* receiver, const char* member, QWidget* fileList); + + QAction* prepareButtonAndAction(QPushButton* button, const QIcon& icon); + QAction* prepareButtonAndAction(QPushButton* button, const QIcon& icon, const QObject* receiver, const char* member); + + private: + QWidget* parent; +}; + +#endif //_FILE_LIST_HELPER_H_ diff --git a/ksystemlog/src/modes/base/genericConfiguration.cpp b/ksystemlog/src/modes/base/genericConfiguration.cpp new file mode 100644 index 00000000..ef1ad10c --- /dev/null +++ b/ksystemlog/src/modes/base/genericConfiguration.cpp @@ -0,0 +1,112 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "genericConfiguration.h" + +#include + +#include "logging.h" +#include "defaults.h" + +#include "globals.h" + +#include "ksystemlogConfig.h" + +class GenericLogModeConfigurationPrivate { +public: + QStringList logFilesPaths; + + QList logFilesLevels; + +}; + +GenericLogModeConfiguration::GenericLogModeConfiguration(const QString& configurationGroup, const QStringList& defaultLogFilesPaths, const QList defaultLogFilesLevels) : + d(new GenericLogModeConfigurationPrivate()) { + + logDebug() << "Using Configuration Group : " << configurationGroup << endl; + configuration->setCurrentGroup(configurationGroup); + + configuration->addItemStringList(QLatin1String( "LogFilesPaths" ), d->logFilesPaths, defaultLogFilesPaths, QLatin1String( "LogFilesPaths" )); + + configuration->addItemIntList(QLatin1String( "LogFilesLevels" ), d->logFilesLevels, defaultLogFilesLevels, QLatin1String( "LogFilesLevels" )); + +} + +GenericLogModeConfiguration::~GenericLogModeConfiguration() { + delete d; +} + + +QStringList GenericLogModeConfiguration::logFilesPaths() const { + return d->logFilesPaths; +} + +QList GenericLogModeConfiguration::logFilesLevels() const { + return d->logFilesLevels; +} + +void GenericLogModeConfiguration::setLogFilesPaths(const QStringList& logFilesPaths) { + d->logFilesPaths = logFilesPaths; +} + +void GenericLogModeConfiguration::setLogFilesLevels(const QList& logFilesLevels) { + d->logFilesLevels = logFilesLevels; +} + + +QList GenericLogModeConfiguration::findGenericLogFiles() { + + QList logFiles; + + if (d->logFilesPaths.size() != d->logFilesLevels.size()) { + logDebug() << i18n("The two arrays size are different, skipping the reading of log files.") << endl; + return logFiles; + } + + LogLevel* level; + + QListIterator itString(d->logFilesPaths); + QListIterator itInt(d->logFilesLevels); + + while (itString.hasNext()) { + int intValue=itInt.next(); + QString stringValue=itString.next(); + + if (intValue>=0 && intValue<(int) Globals::instance()->logLevels().count()) + level=Globals::instance()->logLevels().at(intValue); + else + level=Globals::instance()->informationLogLevel(); + + KUrl url(stringValue); + if (!url.isValid()) { + logWarning() << i18n("URL '%1' is not valid, skipping this URL.", url.path()) << endl; + continue; + } + + logFiles.append(LogFile(url, level)); + + } + + return logFiles; +} + + +#include "genericConfiguration.moc" diff --git a/ksystemlog/src/modes/base/genericConfiguration.h b/ksystemlog/src/modes/base/genericConfiguration.h new file mode 100644 index 00000000..ab616c33 --- /dev/null +++ b/ksystemlog/src/modes/base/genericConfiguration.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _GENERIC_LOG_MODE_CONFIGURATION_H_ +#define _GENERIC_LOG_MODE_CONFIGURATION_H_ + +#include +#include + +#include "logFile.h" + +#include "logModeConfiguration.h" + +class GenericLogModeConfigurationPrivate; + +class GenericLogModeConfiguration : public LogModeConfiguration { + + Q_OBJECT + + public: + GenericLogModeConfiguration(const QString& configurationGroup, const QStringList& defaultLogFilesPaths, const QList defaultLogFilesLevels); + virtual ~GenericLogModeConfiguration(); + + QStringList logFilesPaths() const; + + QList logFilesLevels() const; + + void setLogFilesPaths(const QStringList& logFilesPaths); + + void setLogFilesLevels(const QList& logFilesLevels); + + QList findGenericLogFiles(); + + private: + GenericLogModeConfigurationPrivate* const d; +}; + +#endif // _GENERIC_LOG_MODE_CONFIGURATION_H_ diff --git a/ksystemlog/src/modes/base/logLevelFileList.cpp b/ksystemlog/src/modes/base/logLevelFileList.cpp new file mode 100644 index 00000000..7be16ed3 --- /dev/null +++ b/ksystemlog/src/modes/base/logLevelFileList.cpp @@ -0,0 +1,193 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logLevelFileList.h" + +#include +#include +#include + +#include + +// KDE includes +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "defaults.h" + +#include "logging.h" +#include "globals.h" + +#include "logLevel.h" +#include "logLevelSelectionDialog.h" + +int LogLevelFileList::LogLevelRole = 33; + +LogLevelFileList::LogLevelFileList(QWidget* parent, const QString& description) : + FileList(parent, description) + { + + logDebug() << "Initializing specific file list..." << endl; + + changeItem=new QPushButton(i18n("&Change Status...")); + changeItem->setToolTip(i18n("Change the level of the current file(s)")); + changeItem->setWhatsThis(i18n("Changes the level of the current file(s). See KSystemLog documentation for more information about each log level.")); + + //Insert the button just after the "Modify File" button + buttonsLayout()->insertWidget(2, changeItem); + + QAction* action = fileListHelper.prepareButtonAndAction(changeItem, KIcon( QLatin1String( "favorites" )), this, SLOT(changeItemType())); + + //Insert the action just after the "Modify File" action + fileList->insertAction(fileList->actions().at(2), action); + + changeItem->setEnabled(false); + + connect(fileList, SIGNAL(itemSelectionChanged()), this, SLOT(updateSpecificButtons())); + connect(this, SIGNAL(fileListChanged()), this, SLOT(updateSpecificButtons())); + + disconnect(fileList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(modifyItem(QListWidgetItem*))); + connect(fileList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(changeItemType())); + + updateSpecificButtons(); + + logDebug() << "Specific file list initialized" << endl; + +} + + +LogLevelFileList::~LogLevelFileList() { + //changeItem is managed automatically +} + +void LogLevelFileList::insertItem(LogLevel* level, const QString& itemText) { + QListWidgetItem* item = new QListWidgetItem(QIcon(level->pixmap()), itemText, fileList); + item->setData(LogLevelFileList::LogLevelRole, level->id()); +} + +void LogLevelFileList::addItem() { + //Open a standard Filedialog + KUrl::List urls=fileListHelper.openUrls(); + + QStringList paths=fileListHelper.findPaths(urls); + foreach(const QString &path, paths) { + insertItem(Globals::instance()->informationLogLevel(), path); + } + + emit fileListChanged(); +} + +void LogLevelFileList::updateSpecificButtons() { + if (fileList->selectedItems().count()>0) + changeItem->setEnabled(true); + else + changeItem->setEnabled(false); + +} + +void LogLevelFileList::changeItemType() { + logDebug() << "Changing item type..." << endl; + + LogLevelSelectionDialog logLevelSelectionDialog(this); + + foreach (LogLevel* level, Globals::instance()->logLevels()) { + logLevelSelectionDialog.logLevels()->addItem(new QListWidgetItem(QIcon(level->pixmap()), level->name())); + } + + int choice=logLevelSelectionDialog.exec(); + + if (choice==QDialog::Accepted) { + + QList selectedLogLevels = logLevelSelectionDialog.logLevels()->selectedItems(); + if (selectedLogLevels.isEmpty()==false) { + QListWidgetItem* logLevel = selectedLogLevels.at(0); + int selectedLogLevel=logLevelSelectionDialog.logLevels()->row(logLevel); + + + QList selectedItems = fileList->selectedItems(); + foreach (QListWidgetItem* item, selectedItems) { + item->setIcon(logLevel->icon()); + item->setData(LogLevelFileList::LogLevelRole, selectedLogLevel); + } + + emit fileListChanged(); + } + + } + +} + + +LogLevel* LogLevelFileList::level(int i) { + return Globals::instance()->logLevels().at( fileList->item(i)->data(LogLevelFileList::LogLevelRole).toInt() ); +} + + +QList LogLevelFileList::levels() { + QList levels; + int count=fileList->count(); + + for (int i=0; ilevel(i)->id()); + } + + return levels; + +} + +void LogLevelFileList::addPaths(const QStringList& stringList, const QList& valueList) { + //A little security test + if (stringList.size() != valueList.size()) { + logDebug() << i18n("The two arrays size are different, skipping the reading of generic paths.") << endl; + return; + } + + QListIterator itString(stringList); + QListIterator itInt=(valueList); + + while (itString.hasNext()) { + int valueInt = itInt.next(); + QString valueString = itString.next(); + + LogLevel* level; + if (valueInt>=0 && valueInt<(int) Globals::instance()->logLevels().count()) + level=Globals::instance()->logLevels().at(valueInt); + else + level=Globals::instance()->informationLogLevel(); + + insertItem(level, valueString); + + } + + emit fileListChanged(); +} + + +#include "logLevelFileList.moc" diff --git a/ksystemlog/src/modes/base/logLevelFileList.h b/ksystemlog/src/modes/base/logLevelFileList.h new file mode 100644 index 00000000..22147a55 --- /dev/null +++ b/ksystemlog/src/modes/base/logLevelFileList.h @@ -0,0 +1,66 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_LEVEL_FILE_LIST_H_ +#define _LOG_LEVEL_FILE_LIST_H_ + +#include "fileList.h" + +class QPushButton; +class LogLevel; + + +class LogLevelFileList : public FileList { + + Q_OBJECT + + public: + LogLevelFileList(QWidget* parent, const QString& description); + + virtual ~LogLevelFileList(); + + QList levels(); + + void addPaths(const QStringList& filePaths, const QList& fileLevels); + + private: + LogLevel* level(int i); + + void insertItem(LogLevel* level, const QString& itemText); + + static int LogLevelRole; + + QPushButton* changeItem; + + protected slots: + void addItem(); + + private slots: + void updateSpecificButtons(); + + + void changeItemType(); + + + +}; + +#endif //_LOG_LEVEL_FILE_LIST_H_ diff --git a/ksystemlog/src/modes/base/logLevelSelectionDialog.cpp b/ksystemlog/src/modes/base/logLevelSelectionDialog.cpp new file mode 100644 index 00000000..822eb0b7 --- /dev/null +++ b/ksystemlog/src/modes/base/logLevelSelectionDialog.cpp @@ -0,0 +1,51 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "logLevelSelectionDialog.h" + +#include + +#include +#include + + +LogLevelSelectionDialog::LogLevelSelectionDialog(QWidget* parent) : + QDialog(parent) { + + setupUi(this); + + QPushButton* okButton = buttonBox->button(QDialogButtonBox::Ok); + okButton->setIcon(KStandardGuiItem::ok().icon()); + + QPushButton* cancelButton = buttonBox->button(QDialogButtonBox::Cancel); + cancelButton->setIcon(KStandardGuiItem::cancel().icon()); + +} + + +LogLevelSelectionDialog::~LogLevelSelectionDialog() { + +} + +QListWidget* LogLevelSelectionDialog::logLevels() const { + return logLevelsList; +} + diff --git a/ksystemlog/src/modes/base/logLevelSelectionDialog.h b/ksystemlog/src/modes/base/logLevelSelectionDialog.h new file mode 100644 index 00000000..85e2c417 --- /dev/null +++ b/ksystemlog/src/modes/base/logLevelSelectionDialog.h @@ -0,0 +1,41 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _LOG_LEVEL_SELECTION_DIALOG_H_ +#define _LOG_LEVEL_SELECTION_DIALOG_H_ + +#include + +#include "ui_logLevelSelectionDialogBase.h" + +class LogLevelSelectionDialog : public QDialog, public Ui::LogLevelSelectionDialogBase { + + public: + explicit LogLevelSelectionDialog(QWidget* parent = NULL); + + virtual ~LogLevelSelectionDialog(); + + QListWidget* logLevels() const; + +}; + + +#endif //_LOG_LEVEL_SELECTION_DIALOG_H_ diff --git a/ksystemlog/src/modes/base/logLevelSelectionDialogBase.ui b/ksystemlog/src/modes/base/logLevelSelectionDialogBase.ui new file mode 100644 index 00000000..3360d6d2 --- /dev/null +++ b/ksystemlog/src/modes/base/logLevelSelectionDialogBase.ui @@ -0,0 +1,112 @@ + + LogLevelSelectionDialogBase + + + + 0 + 0 + 227 + 292 + + + + Selecting File Type + + + true + + + + + + Please select the type of this file: + + + logLevelsList + + + + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">List of existing log levels</p></body></html> + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of all existing log levels. </p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Please select one of them to be used for the files selected on the list.</p></body></html> + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + true + + + + + + + + + buttonBox + accepted() + LogLevelSelectionDialogBase + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + LogLevelSelectionDialogBase + reject() + + + 316 + 260 + + + 286 + 274 + + + + + logLevelsList + itemDoubleClicked(QListWidgetItem*) + LogLevelSelectionDialogBase + accept() + + + 189 + 138 + + + 189 + 145 + + + + + diff --git a/ksystemlog/src/modes/base/multipleFileList.cpp b/ksystemlog/src/modes/base/multipleFileList.cpp new file mode 100644 index 00000000..fa782188 --- /dev/null +++ b/ksystemlog/src/modes/base/multipleFileList.cpp @@ -0,0 +1,432 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "multipleFileList.h" + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "defaults.h" + +#include "logging.h" + +MultipleFileList::MultipleFileList(QWidget* parent, const QString& descriptionText) : + QWidget(parent), + fileListHelper(this) + { + + logDebug() << "Initializing multiple file list..." << endl; + + setupUi(this); + + description->setText(descriptionText); + + fileListHelper.prepareButton(modify, KIcon( QLatin1String( "document-open" )), this, SLOT(modifyItem()), fileList); + + fileList->header()->setVisible(false); + + //Add a separator in the MultipleFileList + QAction* separator = new QAction(this); + separator->setSeparator(true); + fileList->addAction(separator); + + fileListHelper.prepareButton(remove, KIcon( QLatin1String( "list-remove" )), this, SLOT(removeSelectedItem()), fileList); + + fileListHelper.prepareButton(up, KIcon( QLatin1String( "go-up" )), this, SLOT(moveUpItem()), fileList); + + fileListHelper.prepareButton(down, KIcon( QLatin1String( "go-down" )), this, SLOT(moveDownItem()), fileList); + + fileListHelper.prepareButton(removeAll, KIcon( QLatin1String( "trash-empty" )), this, SLOT(removeAllItems()), fileList); + + connect(fileList, SIGNAL(itemSelectionChanged()), this, SLOT(updateButtons())); + connect(fileList, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(modifyItem(QTreeWidgetItem*))); + connect(this, SIGNAL(fileListChanged()), this, SLOT(updateButtons())); + + connect(&addButtons, SIGNAL(buttonClicked(int)), this, SLOT(addItem(int))); + + updateButtons(); + + logDebug() << "Multiple File list initialized" << endl; + +} + +MultipleFileList::~MultipleFileList() { + +} + +void MultipleFileList::updateButtons() { + logDebug() << "Updating buttons..." << endl; + + if (isFileListsEmpty() == true) + fileListHelper.setEnabledAction(removeAll, false); + else + fileListHelper.setEnabledAction(removeAll, true); + + + QList selectedItems = fileList->selectedItems(); + + //If the selection is not empty and a empty item is not selected + QTreeWidgetItem* categoryItem = NULL; + if (selectedItems.isEmpty()==false && isEmptyItem(selectedItems.at(0)) == false ) { + categoryItem = findCategoryOfChild( selectedItems.at(0) ); + } + + if (categoryItem!=NULL) { + int categoryIndex = fileList->indexOfTopLevelItem(categoryItem); + + fileListHelper.setEnabledAction(remove, true); + fileListHelper.setEnabledAction(modify, true); + + QTreeWidgetItem* selectedItem = selectedItems.at(0); + + //If the item is at the top of the list, it could not be upped anymore + if (categoryItem->indexOfChild(selectedItem)==0) + fileListHelper.setEnabledAction(up, false); + else + fileListHelper.setEnabledAction(up, true); + + //If the item is at bottom of the list, it could not be downed anymore + if (categoryItem->indexOfChild(selectedItem) == categoryCount(categoryIndex)-1) + fileListHelper.setEnabledAction(down, false); + else + fileListHelper.setEnabledAction(down, true); + + } + //If nothing is selected, disabled special buttons + else { + fileListHelper.setEnabledAction(remove, false); + fileListHelper.setEnabledAction(modify, false); + fileListHelper.setEnabledAction(up, false); + fileListHelper.setEnabledAction(down, false); + } + + logDebug() << "Buttons updated" << endl; +} + +bool MultipleFileList::isFileListsEmpty() const { + for (int i=0; itopLevelItemCount(); ++i) { + if (categoryCount(i) != 0) { + logDebug() << "Is not empty" << endl; + return false; + } + } + + logDebug() << "Is empty" << endl; + return true; + +} + +bool MultipleFileList::isOneOfCategoryEmpty() const { + for (int i=0; itopLevelItemCount(); ++i) { + if (categoryCount(i) == 0) { + logDebug() << "A category is empty" << endl; + return true; + } + } + + logDebug() << "No category empty" << endl; + return false; + +} + +int MultipleFileList::categoryCount(int index) const { + QTreeWidgetItem* item = fileList->topLevelItem(index); + if (item==NULL) { + logError() << "Index out of range" << index << endl; + return 0; + } + + int count = 0; + for(int i=0; ichildCount(); ++i) { + QTreeWidgetItem* childItem = item->child(i); + if (isEmptyItem(childItem)==false) + count++; + } + + return count; +} + +int MultipleFileList::addCategory(const QString& itemName, const QString& buttonName) { + QTreeWidgetItem* item = new QTreeWidgetItem(fileList, QStringList(itemName)); + item->setExpanded(true); + QFont font = item->font(0); + font.setBold(true); + item->setFont(0, font); + + int index = fileList->indexOfTopLevelItem(item); + + QPushButton* addButton = new QPushButton(buttonName, this); + QAction* action = fileListHelper.prepareButtonAndAction(addButton, KIcon( QLatin1String( "document-new" ))); + + //Insert the action in first position + fileList->insertAction(fileList->actions().at(addButtons.buttons().size()), action); + + addButtons.addButton(addButton, index); + + vboxLayout1->insertWidget(index, addButton); + + updateEmptyItems(); + + updateButtons(); + + return index; +} + +void MultipleFileList::addItem(int category) { + logDebug() << "Adding item" << category << endl; + + //Open a standard Filedialog + KUrl::List urls=fileListHelper.openUrls(); + + QTreeWidgetItem* categoryItem = fileList->topLevelItem(category); + + QStringList paths=fileListHelper.findPaths(urls); + foreach(const QString &path, paths) { + addItemInternal(categoryItem, path); + } + + updateEmptyItems(); + + emit fileListChanged(); +} + +void MultipleFileList::addItemInternal(QTreeWidgetItem* categoryItem, const QString& path) { + logDebug() << "Adding" << path << "to" << categoryItem->text(0) << endl; + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(path)); + categoryItem->addChild(item); + categoryItem->setExpanded(true); +} + +QTreeWidgetItem* MultipleFileList::findCategoryOfChild(QTreeWidgetItem* childItem) { + logDebug() << "Finding Category of" << childItem->text(0) << endl; + + for(int i=0; itopLevelItemCount(); ++i) { + QTreeWidgetItem* item = fileList->topLevelItem(i); + + if (item->indexOfChild(childItem) != -1) { + logDebug() << "Category of" << childItem->text(0) << "is" << item->text(0) << endl; + return item; + } + + } + + logDebug() << "No Category of" << childItem->text(0) << endl; + return NULL; +} + +void MultipleFileList::modifyItem() { + QList selectedItems = fileList->selectedItems(); + modifyItem(selectedItems.at(0)); +} + +void MultipleFileList::modifyItem(QTreeWidgetItem* item) { + //If the user tries to modify a category item, we do nothing + if (findCategoryOfChild(item) == NULL || isEmptyItem(item) == true) + return; + + QString previousPath = item->text(0); + + //Open a standard Filedialog + KUrl url=fileListHelper.openUrl(previousPath); + + KUrl::List urls; + urls.append(url); + QStringList paths=fileListHelper.findPaths(urls); + + //We only take the first path + if (paths.count() >= 1) { + item->setText(0, paths.at(0)); + } + + emit fileListChanged(); +} + +void MultipleFileList::removeSelectedItem() { + QList selectedItems = fileList->selectedItems(); + + foreach(QTreeWidgetItem* item, selectedItems) { + QTreeWidgetItem* categoryItem = findCategoryOfChild(item); + delete categoryItem->takeChild(categoryItem->indexOfChild(item)); + } + + updateEmptyItems(); + + emit fileListChanged(); +} + +void MultipleFileList::moveItem(int direction) { + QList selectedItems = fileList->selectedItems(); + + QTreeWidgetItem* item = selectedItems.at(0); + + QTreeWidgetItem* categoryItem = findCategoryOfChild(item); + int itemIndex = categoryItem->indexOfChild(item); + + categoryItem->takeChild(itemIndex); + + unselectAllItems(); + + categoryItem->insertChild(itemIndex + direction, item); + + fileList->setCurrentItem(item); + //item->setSelected(true); + + emit fileListChanged(); +} + +void MultipleFileList::moveUpItem() { + moveItem(-1); +} + +void MultipleFileList::moveDownItem() { + moveItem(1); +} + +void MultipleFileList::removeAllItems() { + QTreeWidgetItemIterator it(fileList, QTreeWidgetItemIterator::All); + while ( *it != NULL ) { + QTreeWidgetItem* item = *it; + + QList children = item->takeChildren(); + foreach(QTreeWidgetItem* childItem, children) { + delete childItem; + } + + it++; + } + + updateEmptyItems(); + + emit fileListChanged(); +} + +void MultipleFileList::unselectAllItems() { + QList selectedItems = fileList->selectedItems(); + foreach(QTreeWidgetItem* item, selectedItems) { + item->setSelected(false); + } + +} + +void MultipleFileList::updateEmptyItems() { + logDebug() << "Updating empty items..." << endl; + + logDebug() << "Adding empty items..." << endl; + + for(int i=0; itopLevelItemCount(); ++i) { + QTreeWidgetItem* categoryItem = fileList->topLevelItem(i); + + //If it's a category item and it's empty + if (categoryItem->childCount() == 0) { + addEmptyItem(categoryItem); + } + } + + removeEmptyItems(); + + logDebug() << "Empty items updated" << endl; +} + +void MultipleFileList::removeEmptyItems() { + logDebug() << "Removing empty items..." << endl; + + //Remove empty items of lists + for(int categoryIndex=0; categoryIndextopLevelItemCount(); ++categoryIndex) { + QTreeWidgetItem* categoryItem = fileList->topLevelItem(categoryIndex); + + logDebug() << "Removing empty items of " << categoryItem->text(0) << endl; + + for(int i=0; ichildCount(); ++i) { + QTreeWidgetItem* childItem = categoryItem->child(i); + + if (isEmptyItem(childItem) == true && categoryItem->childCount() > 1) { + logDebug() << "Remove a child item" << endl; + delete categoryItem->takeChild(i); + break; + } + } + + logDebug() << "Empty items of " << categoryItem->text(0) << "removed" << endl; + + + } + + +} + +bool MultipleFileList::isEmptyItem(QTreeWidgetItem* item) const { + if (item->font(0).italic() == true) + return true; + else + return false; +} + +void MultipleFileList::addEmptyItem(QTreeWidgetItem* item) { + logDebug() << "Adding an empty item..." << endl; + + QTreeWidgetItem* emptyItem = new QTreeWidgetItem(item, QStringList(i18n("No log file..."))); + item->setExpanded(true); + QFont font = emptyItem->font(0); + font.setItalic(true); + emptyItem->setFont(0, font); +} + +void MultipleFileList::addPaths(int category, const QStringList& paths) { + QTreeWidgetItem* categoryItem = fileList->topLevelItem(category); + + foreach(const QString &path, paths) { + addItemInternal(categoryItem, path); + } + + updateEmptyItems(); + + updateButtons(); +} + +QStringList MultipleFileList::paths(int category) { + QTreeWidgetItem* categoryItem = fileList->topLevelItem(category); + QTreeWidgetItemIterator it(fileList, QTreeWidgetItemIterator::All); + + QStringList paths; + while ( *it != NULL ) { + QTreeWidgetItem* item = *it; + + if (categoryItem->indexOfChild(item) != -1) { + paths.append(item->text(0)); + } + + it++; + } + + return paths; +} + +#include "multipleFileList.moc" diff --git a/ksystemlog/src/modes/base/multipleFileList.h b/ksystemlog/src/modes/base/multipleFileList.h new file mode 100644 index 00000000..24ad4868 --- /dev/null +++ b/ksystemlog/src/modes/base/multipleFileList.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _MULTIPLE_FILE_LIST_H_ +#define _MULTIPLE_FILE_LIST_H_ + +#include +#include + +#include "fileListHelper.h" + +#include "ui_multipleFileListBase.h" + + +class MultipleFileList : public QWidget, public Ui::MultipleFileListBase { + + Q_OBJECT + + public: + MultipleFileList(QWidget* parent, const QString& descriptionText); + virtual ~MultipleFileList(); + + bool isOneOfCategoryEmpty() const; + + QStringList paths(int categoryIndex); + + void addPaths(int categoryIndex, const QStringList& paths); + + int addCategory(const QString& itemName, const QString& buttonName); + + public slots: + void removeAllItems(); + + signals: + void fileListChanged(); + + private slots: + + void updateButtons(); + + void removeSelectedItem(); + void moveUpItem(); + void moveDownItem(); + + protected slots: + virtual void addItem(int category); + + void modifyItem(); + void modifyItem(QTreeWidgetItem* item); + + protected: + void addItemInternal(QTreeWidgetItem* categoryItem, const QString& path); + void moveItem(int direction); + void unselectAllItems(); + + void updateEmptyItems(); + void removeEmptyItems(); + + void addEmptyItem(QTreeWidgetItem* item); + bool isEmptyItem(QTreeWidgetItem* item) const; + + bool isFileListsEmpty() const; + int categoryCount(int index) const; + + QTreeWidgetItem* findCategoryOfChild(QTreeWidgetItem* childItem); + + FileListHelper fileListHelper; + + QButtonGroup addButtons; +}; + +#endif //_MULTIPLE_FILE_LIST_H_ diff --git a/ksystemlog/src/modes/base/multipleFileListBase.ui b/ksystemlog/src/modes/base/multipleFileListBase.ui new file mode 100644 index 00000000..3054e207 --- /dev/null +++ b/ksystemlog/src/modes/base/multipleFileListBase.ui @@ -0,0 +1,188 @@ + + MultipleFileListBase + + + + 0 + 0 + 398 + 442 + + + + + 200 + 400 + + + + + 0 + + + 0 + + + 0 + + + + + File List Description + + + Qt::AlignJustify|Qt::AlignVCenter + + + true + + + + + + + Log Files + + + + + + Qt::ActionsContextMenu + + + false + + + false + + + + 1 + + + + + + + + + + &Modify File... + + + + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Delete the current file(s)</p></body></html> + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Deletes the selected files of the list.</p></body></html> + + + &Remove + + + + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Remove all files</p></body></html> + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Remove all files of the list, even if they are not selected.</p></body></html> + + + Rem&ove All + + + + + + + true + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Move up the current file(s)</p></body></html> + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Moves up the selected files in the list. This option allows the files to be read <span style=" font-weight:600;">in first</span> by KSystemLog.</p></body></html> + + + Move &Up + + + + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Move down the current file(s)</p></body></html> + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Moves down the selected files in the list. This option allows the files to be read <span style=" font-weight:600;">at last</span> by KSystemLog.</p></body></html> + + + Move &Down + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 40 + + + + + + + + + + <p style='margin-top:0px;margin-bottom:0px;'><b>Notes:</b></p><ul style='margin-top:0px'><li>Files are read using the order of this list.</li><li>Compressed and plain text files are accepted <i>(*.log, *.gz, *.bz2,...)</i>.</li><li>Use the <b>'*'</b> joker to select multiple log files when adding files.</li></ul> + + + true + + + + + + + + + + + diff --git a/ksystemlog/src/modes/base/parsingHelper.cpp b/ksystemlog/src/modes/base/parsingHelper.cpp new file mode 100644 index 00000000..5e670bb0 --- /dev/null +++ b/ksystemlog/src/modes/base/parsingHelper.cpp @@ -0,0 +1,172 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "parsingHelper.h" + +#include +#include + +#include "logging.h" + + +ParsingHelper* ParsingHelper::self = NULL; + +ParsingHelper* ParsingHelper::instance() { + if (ParsingHelper::self == NULL) { + ParsingHelper::self = new ParsingHelper(); + } + + return ParsingHelper::self; +} + +ParsingHelper::ParsingHelper() { + //Initialize Existing months + mapMonths[QLatin1String( "Jan" )]=1; + mapMonths[QLatin1String( "Feb" )]=2; + mapMonths[QLatin1String( "Mar" )]=3; + mapMonths[QLatin1String( "Apr" )]=4; + mapMonths[QLatin1String( "May" )]=5; + mapMonths[QLatin1String( "Jun" )]=6; + mapMonths[QLatin1String( "Jul" )]=7; + mapMonths[QLatin1String( "Aug" )]=8; + mapMonths[QLatin1String( "Sep" )]=9; + mapMonths[QLatin1String( "Oct" )]=10; + mapMonths[QLatin1String( "Nov" )]=11; + mapMonths[QLatin1String( "Dec" )]=12; + + //Initialize HTTP Responses + //1xx Responses + mapHTTPResponse[QLatin1String( "100" )]=QLatin1String( "Continue" ); + mapHTTPResponse[QLatin1String( "101" )]=QLatin1String( "Switching Protocols" ); + + //2xx Responses + mapHTTPResponse[QLatin1String( "200" )]=QLatin1String( "OK" ); + mapHTTPResponse[QLatin1String( "201" )]=QLatin1String( "Created" ); + mapHTTPResponse[QLatin1String( "202" )]=QLatin1String( "Accepted" ); + mapHTTPResponse[QLatin1String( "203" )]=QLatin1String( "Non-Authoritative Information" ); + mapHTTPResponse[QLatin1String( "204" )]=QLatin1String( "No Content" ); + mapHTTPResponse[QLatin1String( "205" )]=QLatin1String( "Reset Content" ); + mapHTTPResponse[QLatin1String( "206" )]=QLatin1String( "Partial Content" ); + + //3xx Responses + mapHTTPResponse[QLatin1String( "300" )]=QLatin1String( "OK" ); + mapHTTPResponse[QLatin1String( "301" )]=QLatin1String( "Moved Permanently" ); + mapHTTPResponse[QLatin1String( "302" )]=QLatin1String( "Found" ); + mapHTTPResponse[QLatin1String( "303" )]=QLatin1String( "See Other" ); + mapHTTPResponse[QLatin1String( "304" )]=QLatin1String( "Not Modified" ); + mapHTTPResponse[QLatin1String( "305" )]=QLatin1String( "Use Proxy" ); + mapHTTPResponse[QLatin1String( "306" )]=QLatin1String( "(Unused)" ); + mapHTTPResponse[QLatin1String( "307" )]=QLatin1String( "Temporary Redirect" ); + + //4xx Responses + mapHTTPResponse[QLatin1String( "400" )]=QLatin1String( "Bad Request" ); + mapHTTPResponse[QLatin1String( "401" )]=QLatin1String( "Unauthorized" ); + mapHTTPResponse[QLatin1String( "402" )]=QLatin1String( "Payment Required" ); + mapHTTPResponse[QLatin1String( "403" )]=QLatin1String( "Forbidden" ); + mapHTTPResponse[QLatin1String( "404" )]=QLatin1String( "Not Found" ); + mapHTTPResponse[QLatin1String( "405" )]=QLatin1String( "Method Not Allowed" ); + mapHTTPResponse[QLatin1String( "406" )]=QLatin1String( "Not Acceptable" ); + mapHTTPResponse[QLatin1String( "407" )]=QLatin1String( "Proxy Authentication Required" ); + mapHTTPResponse[QLatin1String( "408" )]=QLatin1String( "Request Timeout" ); + mapHTTPResponse[QLatin1String( "409" )]=QLatin1String( "Conflict" ); + mapHTTPResponse[QLatin1String( "410" )]=QLatin1String( "Gone" ); + mapHTTPResponse[QLatin1String( "411" )]=QLatin1String( "Length Required" ); + mapHTTPResponse[QLatin1String( "412" )]=QLatin1String( "Precondition Failed" ); + mapHTTPResponse[QLatin1String( "413" )]=QLatin1String( "Request Entity Too Large" ); + mapHTTPResponse[QLatin1String( "414" )]=QLatin1String( "Request-URI Too Long" ); + mapHTTPResponse[QLatin1String( "415" )]=QLatin1String( "Unsupported Media Type" ); + mapHTTPResponse[QLatin1String( "416" )]=QLatin1String( "Requested Range Not Satisfiable" ); + mapHTTPResponse[QLatin1String( "417" )]=QLatin1String( "Expectation Failed" ); + + //5xx Responses + mapHTTPResponse[QLatin1String( "500" )]=QLatin1String( "Internal Server Error" ); + mapHTTPResponse[QLatin1String( "501" )]=QLatin1String( "Not Implemented" ); + mapHTTPResponse[QLatin1String( "502" )]=QLatin1String( "Bad Gateway" ); + mapHTTPResponse[QLatin1String( "503" )]=QLatin1String( "Service Unavailable" ); + mapHTTPResponse[QLatin1String( "504" )]=QLatin1String( "Gateway Timeout" ); + mapHTTPResponse[QLatin1String( "505" )]=QLatin1String( "HTTP Version Not Supported" ); + +} + +ParsingHelper::~ParsingHelper() { + +} + +QDateTime ParsingHelper::parseHttpDateTime(const QString& logLine) { + //Format example : 22/May/2005:01:50:34 +0200 + + QString day=logLine.mid(0,2); + QString month=logLine.mid(3,3); + QString year=logLine.mid(7,4); + + QString hour=logLine.mid(12,2); + QString min=logLine.mid(15,2); + QString sec=logLine.mid(18,2); + + //QString zone=logLine.mid(22,5); + + return QDateTime(QDate(year.toInt(), parseSyslogMonth(month), day.toInt()), QTime(hour.toInt(), min.toInt(), sec.toInt())); +} + +/** + * Example : "Oct 1 09:11:45 2005" + */ +QDateTime ParsingHelper::parseSyslogDateTime(const QString& dateTime) { + //TODO Create this regexp in constructor + QRegExp regex(QLatin1String( "(\\S*)[ ]+(\\d*) (\\d*):(\\d*):(\\d*) (\\d*)" )); + + int firstPosition = regex.indexIn(dateTime); + if (firstPosition == -1) { + logDebug() << "Unable to parse date " << dateTime << endl; + return QDateTime::currentDateTime(); + } + + return QDateTime(QDate(regex.cap(6).toInt(), parseSyslogMonth(regex.cap(1)), regex.cap(2).toInt()), QTime(regex.cap(3).toInt(), regex.cap(4).toInt(), regex.cap(5).toInt(), 0)); +} + +QString ParsingHelper::syslogDateTimeRegexp() { + return QLatin1String( "(\\S*[ ]+\\d* \\d*:\\d*:\\d* \\d*)" ); +} + +int ParsingHelper::parseSyslogMonth(const QString& string) { + return mapMonths.value(string, 1); + +} + +QString ParsingHelper::parseSize(const QString& stringSize) { + qint64 size=stringSize.toLongLong(); + + return KGlobal::locale()->formatByteSize(size); +} + +QString ParsingHelper::parseHttpResponse(const QString& response) { + //Search the response string in the map + QMap::Iterator it=mapHTTPResponse.find(response); + if (it!=mapHTTPResponse.end()) + return i18nc("HttpResponseNumber HttpResponseDescription", "%1 %2", response, *it); + else + return response; + +} + +QString ParsingHelper::parseAgent(const QString& agent) { + return agent; +} diff --git a/ksystemlog/src/modes/base/parsingHelper.h b/ksystemlog/src/modes/base/parsingHelper.h new file mode 100644 index 00000000..9b3285ba --- /dev/null +++ b/ksystemlog/src/modes/base/parsingHelper.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _PARSING_HELPER_H_ +#define _PARSING_HELPER_H_ + +#include + +#include +#include + +/** + * TODO Fork this class in SyslogParsingHelper and HttpParsingHelper + */ +class ParsingHelper { + + public: + static ParsingHelper* instance(); + + ~ParsingHelper(); + + /** + * Returns the months number represented by the 3 first letters in the QString parameter + */ + int parseSyslogMonth(const QString& month); + + QDateTime parseHttpDateTime(const QString& dateTime); + + /* + * TODO Use this method in SyslogAnalyzer, and add a parameter to use the current year instead + * of trying to search it in string + */ + QDateTime parseSyslogDateTime(const QString& dateTime); + QString syslogDateTimeRegexp(); + + QString parseSize(const QString& size); + + QString parseHttpResponse(const QString& response); + + QString parseAgent(const QString& agent); + + private: + explicit ParsingHelper(); + + static ParsingHelper* self; + + QMap mapMonths; + + QMap mapHTTPResponse; +}; + +#endif diff --git a/ksystemlog/src/modes/base/syslogAnalyzer.cpp b/ksystemlog/src/modes/base/syslogAnalyzer.cpp new file mode 100644 index 00000000..cc74cc3d --- /dev/null +++ b/ksystemlog/src/modes/base/syslogAnalyzer.cpp @@ -0,0 +1,212 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "syslogAnalyzer.h" + + +#include +#include + +#include + +#include + +#include "globals.h" +#include "logging.h" + +#include "localLogFileReader.h" +#include "logLine.h" +#include "logMode.h" +#include "logLevel.h" +#include "logViewWidget.h" + +#include "logViewModel.h" + +#include "parsingHelper.h" + +#include "ksystemlogConfig.h" + +SyslogAnalyzer::SyslogAnalyzer(LogMode* logMode) : + Analyzer(logMode) { + +} + +SyslogAnalyzer::~SyslogAnalyzer() { + +} + +LogViewColumns SyslogAnalyzer::initColumns() { + LogViewColumns columns; + columns.addColumn(LogViewColumn(i18n("Date"), true, false)); + columns.addColumn(LogViewColumn(i18n("Host"), true, true)); + columns.addColumn(LogViewColumn(i18n("Process"), true, true)); + columns.addColumn(LogViewColumn(i18n("Message"), true, false)); + + return columns; +} + +LogFileReader* SyslogAnalyzer::createLogFileReader(const LogFile& logFile) { + return new LocalLogFileReader(logFile); +} + +Analyzer::LogFileSortMode SyslogAnalyzer::logFileSortMode() { + return Analyzer::AscendingSortedLogFile; +} + +/** + * TODO Improve speed of this method (with KRegExp class for example) + */ +LogLine* SyslogAnalyzer::parseMessage(const QString& logLine, const LogFile& originalFile) { + //logDebug() << QTime::currentTime() << " : Reading line : " << logLine << " from " << originalFile.url.path() << endl; + + //15 is the default date size format + if (logLine.length()<15) { + logDebug() << "Too short line" << endl; + return undefinedLogLine(logLine, originalFile); + } + + int year=QDate::currentDate().year(); + + //Month number + QString month(logLine.left(3)); + + QString line(logLine); + + line=line.remove(0, 4); + int monthNum=ParsingHelper::instance()->parseSyslogMonth(month); + + //Day number + QString day(line.left(2)); + int dayNum=day.toInt(); + + line=line.remove(0, 3); + + //Time + QString stringTime(line.left(8)); + int h=stringTime.left(2).toInt(); + stringTime.remove(0, 3); + int m=stringTime.left(2).toInt(); + stringTime.remove(0, 3); + int s=stringTime.left(2).toInt(); + stringTime.remove(0, 3); + + QDateTime dateTime(QDate(year, monthNum, dayNum), QTime(h, m, s)); + if (dateTime.isValid() == false) { + logDebug() << "Malformed date and time" << endl; + return undefinedLogLine(logLine, originalFile); + } + + line=line.remove(0, 9); + + QString hostname; + + int nextSpace = line.indexOf(QLatin1Char( ' ' )); + int nextDoubleDot=line.indexOf(QLatin1Char( ':' )); + + //Normal case or no process name + if (nextSpace < nextDoubleDot || nextDoubleDot == -1) { + //Host name + hostname = line.left(nextSpace); + line=line.remove(0, nextSpace+1); + } + //No host name case (very rare) + else { + //Host name + hostname = undefinedHostName(); + } + + //Refresh double dot once the line has been substr'ed + nextDoubleDot=line.indexOf(QLatin1Char( ':' )); + + QString process; + QString message; + + //Process name + if (nextDoubleDot!=-1) { + process=line.left(nextDoubleDot); + + //If the delete process identifier option is enabled + if (KSystemLogConfig::deleteProcessIdentifier()==true) { + int squareBracket=process.indexOf(QLatin1Char( '[' )); + + //If we find a bracket, we remove the useless part + if (squareBracket!=-1) { + process=process.left(squareBracket); + } + + } + line=line.remove(0, nextDoubleDot+1); + + message=line.remove(0, 1); + } + //If we can't find any ':' character, it means that this line is a + //internal message of syslogd + else { + if (line.contains(QLatin1String( "last message repeated" )) || line.contains(QLatin1String( "-- MARK --" ))) { + process=QLatin1String( "syslog" ); + } + else { + process=undefinedProcess(); + } + + message=line; + } + + QStringList list; + list.append(hostname); + list.append(process); + list.append(message); + + return new LogLine( + logLineInternalIdGenerator++, + dateTime, + list, + originalFile.url().path(), + originalFile.defaultLogLevel(), + logMode + ); +} + +inline LogLine* SyslogAnalyzer::undefinedLogLine(const QString& message, const LogFile& originalFile) { + QStringList items; + items << undefinedHostName() << undefinedProcess() << message; + return new LogLine( + logLineInternalIdGenerator++, + QDateTime::currentDateTime(), + items, + originalFile.url().path(), + originalFile.defaultLogLevel(), + logMode + ); + +} + +inline QString SyslogAnalyzer::undefinedHostName() { + //i18nc("Undefined host name", "undefined"); + return QLatin1String( "" ); +} + +inline QString SyslogAnalyzer::undefinedProcess() { + //i18nc("Undefined process", "undefined"); + return QLatin1String( "" ); +} + +#include "syslogAnalyzer.moc" diff --git a/ksystemlog/src/modes/base/syslogAnalyzer.h b/ksystemlog/src/modes/base/syslogAnalyzer.h new file mode 100644 index 00000000..fc080258 --- /dev/null +++ b/ksystemlog/src/modes/base/syslogAnalyzer.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SYSLOG_ANALYZER_H_ +#define _SYSLOG_ANALYZER_H_ + +#include + +#include "analyzer.h" + +#include "logFile.h" + +class LogFileReader; + +class LogMode; +class LogLine; + +class SyslogAnalyzer : public Analyzer { + + Q_OBJECT + + public: + explicit SyslogAnalyzer(LogMode* logMode); + + virtual ~SyslogAnalyzer(); + + virtual LogViewColumns initColumns(); + + protected: + virtual LogFileReader* createLogFileReader(const LogFile& logFile); + virtual Analyzer::LogFileSortMode logFileSortMode(); + virtual LogLine* parseMessage(const QString& logLine, const LogFile& originalFile); + + private: + inline QString undefinedHostName(); + inline QString undefinedProcess(); + inline LogLine* undefinedLogLine(const QString& message, const LogFile& originalFile); +}; + +#endif // _SYSLOG_ANALYZER_H_ diff --git a/ksystemlog/src/modes/cron/CMakeLists.txt b/ksystemlog/src/modes/cron/CMakeLists.txt new file mode 100644 index 00000000..9da2d904 --- /dev/null +++ b/ksystemlog/src/modes/cron/CMakeLists.txt @@ -0,0 +1,35 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../base + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../config + ${CMAKE_CURRENT_BINARY_DIR}/../base + ${CMAKE_CURRENT_BINARY_DIR}/../../config +) + +set(ksystemlog_cron_sources + cronConfigurationWidget.cpp + cronConfiguration.cpp + cronAnalyzer.cpp + cronItemBuilder.cpp + cronLogMode.cpp + cronFactory.cpp +) + +kde4_add_library(ksystemlog_cron STATIC ${ksystemlog_cron_sources}) + +add_dependencies( + ksystemlog_cron + + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_cron + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config +) diff --git a/ksystemlog/src/modes/cron/cronAnalyzer.cpp b/ksystemlog/src/modes/cron/cronAnalyzer.cpp new file mode 100644 index 00000000..7bed1dcf --- /dev/null +++ b/ksystemlog/src/modes/cron/cronAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cronAnalyzer.h" diff --git a/ksystemlog/src/modes/cron/cronAnalyzer.h b/ksystemlog/src/modes/cron/cronAnalyzer.h new file mode 100644 index 00000000..e66f4b66 --- /dev/null +++ b/ksystemlog/src/modes/cron/cronAnalyzer.h @@ -0,0 +1,129 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CRON_ANALYZER_H_ +#define _CRON_ANALYZER_H_ + +#include + +#include "syslogAnalyzer.h" + +#include "cronLogMode.h" +#include "cronConfiguration.h" + +class LogMode; + +class CronAnalyzer : public SyslogAnalyzer { + + Q_OBJECT + + public: + + CronAnalyzer(LogMode* logMode) : + SyslogAnalyzer(logMode) { + + } + + virtual ~CronAnalyzer() { + + } + + LogViewColumns initColumns() { + LogViewColumns columns; + columns.addColumn(LogViewColumn(i18n("Date"), true, false)); + columns.addColumn(LogViewColumn(i18n("Host"), true, true)); + columns.addColumn(LogViewColumn(i18n("Process"), true, true)); + columns.addColumn(LogViewColumn(i18n("User"), true, true)); + columns.addColumn(LogViewColumn(i18n("Command"), true, false)); + return columns; + } + + Analyzer::LogFileSortMode logFileSortMode() { + return Analyzer::FilteredLogFile; + } + + /* + * Cron line example : + * Sep 16 01:39:01 localhost /USR/SBIN/CRON[11069]: (root) CMD ( [ -d /var/lib/php5 ] && find /var/lib/php5/ -type f -cmin +$(/usr/lib/php5/maxlifetime) -print0 | xargs -r -0 rm) + * Sep 16 18:39:05 localhost /usr/sbin/cron[5479]: (CRON) INFO (pidfile fd = 3) + * Sep 16 18:39:05 localhost /usr/sbin/cron[5480]: (CRON) STARTUP (fork ok) + * Sep 16 18:39:05 localhost /usr/sbin/cron[5480]: (CRON) INFO (Running @reboot jobs) + * + */ + LogLine* parseMessage(const QString& logLine, const LogFile& originalFile) { + + //Use the default parsing + LogLine* syslogLine=SyslogAnalyzer::parseMessage(logLine, originalFile); + + QStringList list=syslogLine->logItems(); + + if (isCronLine(syslogLine) == false) { + delete syslogLine; + return NULL; + } + + //Gets the message column (last item) and deletes it + QString message=list.takeLast(); + + int leftBracket=message.indexOf(QLatin1Char( '(' )); + int rightBracket=message.indexOf(QLatin1Char( ')' )); + + QString user=message.mid(leftBracket+1, rightBracket-leftBracket-1); + + list.append(user); + + if (message.indexOf(QLatin1String( "CMD" )) != -1) { + // Ignore this : ") CMD (" (length = 7) + message=message.right(message.length() - rightBracket - 7); + message=message.simplified(); + syslogLine->setLogLevel(Globals::instance()->informationLogLevel()); + } + else { + // Ignore this : ") " (for INFO and STARTUP cases) + message=message.right(message.length() - rightBracket - 2); + syslogLine->setLogLevel(Globals::instance()->noticeLogLevel()); + } + + list.append(message); + + syslogLine->setLogItems(list); + + return syslogLine; + } + + inline bool isCronLine(LogLine* syslogLine) { + CronConfiguration* cronConfiguration = logMode->logModeConfiguration(); + if (cronConfiguration->processFilter().isEmpty()) { + return true; + } + + //If the process line does not match the cron process, then ignore this line + const QStringList list = syslogLine->logItems(); + QString processLine = list.at(1); + if (processLine.contains(cronConfiguration->processFilter(), Qt::CaseInsensitive) == true) { + return true; + } + + return false; + } +}; + +#endif diff --git a/ksystemlog/src/modes/cron/cronConfiguration.cpp b/ksystemlog/src/modes/cron/cronConfiguration.cpp new file mode 100644 index 00000000..118fa45a --- /dev/null +++ b/ksystemlog/src/modes/cron/cronConfiguration.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cronConfiguration.h" diff --git a/ksystemlog/src/modes/cron/cronConfiguration.h b/ksystemlog/src/modes/cron/cronConfiguration.h new file mode 100644 index 00000000..c7f14f57 --- /dev/null +++ b/ksystemlog/src/modes/cron/cronConfiguration.h @@ -0,0 +1,88 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CRON_CONFIGURATION_H_ +#define _CRON_CONFIGURATION_H_ + +#include + +#include "logModeConfiguration.h" + +#include "logging.h" +#include "defaults.h" + +#include "cronLogMode.h" + +#include "ksystemlogConfig.h" + +class CronConfigurationPrivate { +public: + QStringList cronPaths; + + QString processFilter; +}; + +class CronConfiguration : public LogModeConfiguration { + + Q_OBJECT + + public: + CronConfiguration() : + d(new CronConfigurationPrivate()) { + + configuration->setCurrentGroup(QLatin1String( "CronLogMode" )); + + QStringList defaultCronPaths; + defaultCronPaths << QLatin1String( "/var/log/syslog" ); + configuration->addItemStringList(QLatin1String( "LogFilesPaths" ), d->cronPaths, defaultCronPaths, QLatin1String( "LogFilesPaths" )); + + QString defaultProcessFilter(QLatin1String( "/usr/sbin/cron" )); + configuration->addItemString(QLatin1String( "ProcessFilter" ), d->processFilter, defaultProcessFilter, QLatin1String( "ProcessFilter" )); + + + } + + virtual ~CronConfiguration() { + delete d; + } + + QString processFilter() const { + return d->processFilter; + } + + void setProcessFilter(const QString& processFilter) { + d->processFilter = processFilter; + } + + QStringList cronPaths() const { + return d->cronPaths; + } + + void setCronPaths(const QStringList& cronPaths) { + d->cronPaths = cronPaths; + } + + private: + CronConfigurationPrivate* const d; + +}; + +#endif // _CRON_CONFIGURATION_H_ diff --git a/ksystemlog/src/modes/cron/cronConfigurationWidget.cpp b/ksystemlog/src/modes/cron/cronConfigurationWidget.cpp new file mode 100644 index 00000000..068ba81b --- /dev/null +++ b/ksystemlog/src/modes/cron/cronConfigurationWidget.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cronConfigurationWidget.h" diff --git a/ksystemlog/src/modes/cron/cronConfigurationWidget.h b/ksystemlog/src/modes/cron/cronConfigurationWidget.h new file mode 100644 index 00000000..8eeede99 --- /dev/null +++ b/ksystemlog/src/modes/cron/cronConfigurationWidget.h @@ -0,0 +1,159 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CRON_CONFIGURATION_WIDGET_H_ +#define _CRON_CONFIGURATION_WIDGET_H_ + +#include "logModeConfigurationWidget.h" + +#include +#include +#include + +#include + +#include "globals.h" +#include "logging.h" + +#include "fileList.h" + +#include "logLevel.h" + +#include "cronConfiguration.h" +#include "cronLogMode.h" + +class CronConfigurationWidget : public LogModeConfigurationWidget { + + Q_OBJECT + + public: + CronConfigurationWidget() : + LogModeConfigurationWidget(i18n("Cron Log"),QLatin1String( CRON_MODE_ICON ), i18n("Cron Log")) + { + + QVBoxLayout* layout = new QVBoxLayout(); + this->setLayout(layout); + + QString description = i18n("

These files will be analyzed to show the Cron Logs (i.e. planned tasks logs). More information...

"); + + fileList = new FileList(this, description); + + connect(fileList, SIGNAL(fileListChanged()), this, SIGNAL(configurationChanged())); + + layout->addWidget(fileList); + + processFilterGroup = new QGroupBox(i18n("Enable Process Filtering")); + processFilterGroup->setCheckable(true); + + connect(processFilterGroup, SIGNAL(clicked(bool)), this, SLOT(toggleProcessFilterEnabling(bool))); + connect(processFilterGroup, SIGNAL(clicked(bool)), this, SIGNAL(configurationChanged())); + + layout->addWidget(processFilterGroup); + + QHBoxLayout* processFilterLayout = new QHBoxLayout(); + + processFilterGroup->setLayout(processFilterLayout); + + processFilterLabel = new QLabel(i18n("Only keeps lines which matches this process :")); + processFilter = new QLineEdit(this); + + processFilterLabel->setBuddy(processFilter); + connect(processFilter, SIGNAL(textEdited(const QString&)), this, SIGNAL(configurationChanged())); + + processFilterLayout->addWidget(processFilterLabel); + processFilterLayout->addWidget(processFilter); + + } + + virtual ~CronConfigurationWidget() { + + } + + bool isValid() const { + if (fileList->isEmpty() == true) { + logDebug() << "Cron configuration not valid" << endl; + return false; + } + + if (processFilterGroup->isChecked() && processFilter->text().isEmpty()) { + logDebug() << "Cron configuration not valid" << endl; + return false; + } + + logDebug() << "Cron configuration valid" << endl; + return true; + } + + void saveConfig() { + logDebug() << "Saving config from Cron Options..." << endl; + + CronConfiguration* cronConfiguration = Globals::instance()->findLogMode(QLatin1String( CRON_LOG_MODE_ID ))->logModeConfiguration(); + cronConfiguration->setCronPaths(fileList->paths()); + + if (processFilterGroup->isChecked() == false) { + cronConfiguration->setProcessFilter(QLatin1String( "" )); + } + else { + cronConfiguration->setProcessFilter(processFilter->text()); + } + } + + void readConfig() { + CronConfiguration* cronConfiguration = Globals::instance()->findLogMode(QLatin1String( CRON_LOG_MODE_ID ))->logModeConfiguration(); + + fileList->removeAllItems(); + + fileList->addPaths(cronConfiguration->cronPaths()); + + if (cronConfiguration->processFilter().isEmpty()) { + processFilterGroup->setChecked(false); + } + else { + processFilterGroup->setChecked(true); + processFilter->setText(cronConfiguration->processFilter()); + } + + + } + + void defaultConfig() { + //TODO Find a way to read the configuration per default + readConfig(); + } + + private slots: + void toggleProcessFilterEnabling(bool enabled) { + processFilter->setEnabled(enabled); + processFilterLabel->setEnabled(enabled); + } + + private: + FileList* fileList; + + QGroupBox* processFilterGroup; + + QLineEdit* processFilter; + QLabel* processFilterLabel; + + +}; + +#endif // _CRON_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/modes/cron/cronFactory.cpp b/ksystemlog/src/modes/cron/cronFactory.cpp new file mode 100644 index 00000000..264758ef --- /dev/null +++ b/ksystemlog/src/modes/cron/cronFactory.cpp @@ -0,0 +1,48 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cronFactory.h" + +#include + + +#include "logMode.h" +#include "logging.h" + +#include "simpleAction.h" +#include "cronLogMode.h" + +#include "logModeFactory.h" + +QList CronLogModeFactory::createLogModes() const { + QList logModes; + logModes.append(new CronLogMode()); + return logModes; +} + +LogModeAction* CronLogModeFactory::createLogModeAction() const { + LogMode* logMode = Globals::instance()->findLogMode(QLatin1String( CRON_LOG_MODE_ID )); + SimpleAction* logModeAction = new SimpleAction(logMode->action(), logMode); + logModeAction->setInToolBar(false); + logModeAction->setCategory(LogModeAction::ServicesCategory); + + return logModeAction; +} diff --git a/ksystemlog/src/modes/cron/cronFactory.h b/ksystemlog/src/modes/cron/cronFactory.h new file mode 100644 index 00000000..c9cdfd53 --- /dev/null +++ b/ksystemlog/src/modes/cron/cronFactory.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CRON_FACTORY_H_ +#define _CRON_FACTORY_H_ + +#include + +#include "logModeFactory.h" + +class LogModeAction; +class LogMode; + +class CronLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; + +}; + +#endif // _CRON_FACTORY_H_ + diff --git a/ksystemlog/src/modes/cron/cronItemBuilder.cpp b/ksystemlog/src/modes/cron/cronItemBuilder.cpp new file mode 100644 index 00000000..558d72f3 --- /dev/null +++ b/ksystemlog/src/modes/cron/cronItemBuilder.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cronItemBuilder.h" diff --git a/ksystemlog/src/modes/cron/cronItemBuilder.h b/ksystemlog/src/modes/cron/cronItemBuilder.h new file mode 100644 index 00000000..233b860b --- /dev/null +++ b/ksystemlog/src/modes/cron/cronItemBuilder.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CRON_ITEM_BUILDER_H_ +#define _CRON_ITEM_BUILDER_H_ + +#include + +#include "logModeItemBuilder.h" + +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +class CronItemBuilder : public LogModeItemBuilder { + + public: + CronItemBuilder() { + + } + + virtual ~CronItemBuilder() { + + } + + QString createFormattedText(LogLine* line) const { + QString result; + + result.append(QLatin1String( "" )); + + QListIterator it(line->logItems()); + + result.append(labelMessageFormat(i18n("Date:"), formatDate(line->time()))); + result.append(labelMessageFormat(i18n("Hostname:"), it.next() )); + result.append(labelMessageFormat(i18n("Process:"), it.next() )); + result.append(labelMessageFormat(i18n("User:"), it.next() )); + result.append(labelMessageFormat(i18n("Level:"), line->logLevel()->name())); + result.append(labelMessageFormat(i18n("Original file:"), line->sourceFileName())); + + result.append(QLatin1String( "
" )); + + return result; + + } +}; + + +#endif // _CRON_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/modes/cron/cronLogMode.cpp b/ksystemlog/src/modes/cron/cronLogMode.cpp new file mode 100644 index 00000000..000b9776 --- /dev/null +++ b/ksystemlog/src/modes/cron/cronLogMode.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cronLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "cronAnalyzer.h" +#include "cronConfigurationWidget.h" +#include "cronConfiguration.h" +#include "cronItemBuilder.h" + +CronLogMode::CronLogMode() : + LogMode(QLatin1String( CRON_LOG_MODE_ID ), i18n("Cron Log"),QLatin1String( CRON_MODE_ICON )) { + + d->logModeConfiguration = new CronConfiguration(); + + d->logModeConfigurationWidget = new CronConfigurationWidget(); + + d->itemBuilder = new CronItemBuilder(); + + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the planned tasks log (Cron log).")); + d->action->setWhatsThis(i18n("Displays the planned tasks log in the current tab. Cron process is a program in charge of launching planned tasks on your system, like security checks, or auto-restarting of some services. Use this menu to see the recently launched processes.")); + +} + +CronLogMode::~CronLogMode() { + +} + +Analyzer* CronLogMode::createAnalyzer() { + return new CronAnalyzer(this); +} + +QList CronLogMode::createLogFiles() { + CronConfiguration* cronConfiguration = logModeConfiguration(); + return cronConfiguration->findNoModeLogFiles(cronConfiguration->cronPaths()); +} + diff --git a/ksystemlog/src/modes/cron/cronLogMode.h b/ksystemlog/src/modes/cron/cronLogMode.h new file mode 100644 index 00000000..0c66994e --- /dev/null +++ b/ksystemlog/src/modes/cron/cronLogMode.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CRON_LOG_MODE_H_ +#define _CRON_LOG_MODE_H_ + +/** + * Cron Log Mode Identifier + */ +#define CRON_LOG_MODE_ID "cronLogMode" + +/** + * Cron Log Icon + */ +#define CRON_MODE_ICON "chronometer" + +#include + +#include "logFile.h" +#include "logMode.h" + +class CronLogMode : public LogMode { + + Q_OBJECT + +public: + explicit CronLogMode(); + + ~CronLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _CRON_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/cups/CMakeLists.txt b/ksystemlog/src/modes/cups/CMakeLists.txt new file mode 100644 index 00000000..fb95f3c1 --- /dev/null +++ b/ksystemlog/src/modes/cups/CMakeLists.txt @@ -0,0 +1,50 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../base + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../config + ${CMAKE_CURRENT_BINARY_DIR}/../base + ${CMAKE_CURRENT_BINARY_DIR}/../../config +) + +set(ksystemlog_cups_sources + cupsConfiguration.cpp + cupsConfigurationWidget.cpp + + cupsAnalyzer.cpp + cupsItemBuilder.cpp + cupsLogMode.cpp + + cupsAccessAnalyzer.cpp + cupsAccessItemBuilder.cpp + cupsAccessLogMode.cpp + + cupsPageAnalyzer.cpp + cupsPageItemBuilder.cpp + cupsPageLogMode.cpp + + cupsPdfAnalyzer.cpp + cupsPdfItemBuilder.cpp + cupsPdfLogMode.cpp + + cupsFactory.cpp + +) + +kde4_add_library(ksystemlog_cups STATIC ${ksystemlog_cups_sources}) + +add_dependencies( + ksystemlog_cups + + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_cups + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config +) diff --git a/ksystemlog/src/modes/cups/cupsAccessAnalyzer.cpp b/ksystemlog/src/modes/cups/cupsAccessAnalyzer.cpp new file mode 100644 index 00000000..6da515c0 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsAccessAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsAccessAnalyzer.h" diff --git a/ksystemlog/src/modes/cups/cupsAccessAnalyzer.h b/ksystemlog/src/modes/cups/cupsAccessAnalyzer.h new file mode 100644 index 00000000..59438508 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsAccessAnalyzer.h @@ -0,0 +1,132 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_ACCESS_ANALYZER_H_ +#define _CUPS_ACCESS_ANALYZER_H_ + + +#include + +#include "analyzer.h" + +#include "localLogFileReader.h" +#include "logging.h" +#include "parsingHelper.h" + +#include "cupsAccessLogMode.h" + +class CupsAccessAnalyzer : public Analyzer { + + Q_OBJECT + + public: + explicit CupsAccessAnalyzer(LogMode* logMode) : + Analyzer(logMode), + cupsAccessRegex(QLatin1String( "(\\S*) (\\S*) (\\S*) \\[(.*)\\] \"(.*)\" (\\S*) (\\S*) (\\S*) (\\S*)" )) { + } + + virtual ~CupsAccessAnalyzer() { + + } + + LogViewColumns initColumns() { + LogViewColumns columns; + + columns.addColumn(LogViewColumn(i18n("Date"), true, false)); + columns.addColumn(LogViewColumn(i18n("Host"), true, true)); + columns.addColumn(LogViewColumn(i18n("Group"), true, true)); + columns.addColumn(LogViewColumn(i18n("User"), true, true)); + columns.addColumn(LogViewColumn(i18n("HTTP Request"), true, false)); + columns.addColumn(LogViewColumn(i18n("Status"), true, true)); + columns.addColumn(LogViewColumn(i18n("Bytes"), true, false)); + columns.addColumn(LogViewColumn(i18n("IPP Operation"), true, false)); + columns.addColumn(LogViewColumn(i18n("IPP Status"), true, false)); + + return columns; + } + + + protected: + + QRegExp cupsAccessRegex; + + LogFileReader* createLogFileReader(const LogFile& logFile) { + return new LocalLogFileReader(logFile); + } + + Analyzer::LogFileSortMode logFileSortMode() { + return Analyzer::AscendingSortedLogFile; + } + + /* + * http://www.cups.org/documentation.php/ref-access_log.html + * + * host group user date-time \"method resource version\" status bytes ipp-operation ipp-status + * 10.0.1.2 - - [01/Dec/2005:21:50:28 +0000] "POST / HTTP/1.1" 200 317 CUPS-Get-Printers successful-ok-ignored-or-substituted-attributes + * localhost - - [01/Dec/2005:21:50:32 +0000] "GET /admin HTTP/1.1" 200 0 - - + * localhost - - [01/Dec/2005:21:50:32 +0000] "POST / HTTP/1.1" 200 157 CUPS-Get-Printers successful-ok-ignored-or-substituted-attributes + * localhost - - [01/Dec/2005:21:50:32 +0000] "POST / HTTP/1.1" 200 1411 CUPS-Get-Devices - + * localhost - - [01/Dec/2005:21:50:32 +0000] "GET /admin HTTP/1.1" 200 6667 - - + * + */ + LogLine* parseMessage(const QString& logLine, const LogFile& originalLogFile) { + + QString line(logLine); + + int firstPosition = cupsAccessRegex.indexIn(logLine); + if (firstPosition == -1) { + logDebug() << "Unable to parse line " << logLine << endl; + return NULL; + } + + QStringList capturedTexts = cupsAccessRegex.capturedTexts(); + + //Remove full line + capturedTexts.removeAt(0); + + capturedTexts.replace(5, ParsingHelper::instance()->parseHttpResponse(capturedTexts.at(5))); + capturedTexts.replace(6, ParsingHelper::instance()->parseSize(capturedTexts.at(6))); + + QDateTime dateTime=ParsingHelper::instance()->parseHttpDateTime(capturedTexts.takeAt(3)); + + LogLevel* logLevel = findLevel(capturedTexts.at(capturedTexts.count()-1)); + + return new LogLine( + logLineInternalIdGenerator++, + dateTime, + capturedTexts, + originalLogFile.url().path(), + logLevel, + logMode + ); + } + + inline LogLevel* findLevel(const QString& status) const { + if (status == QLatin1String( "successful-ok" )) + return Globals::instance()->informationLogLevel(); + else if (status == QLatin1String( "ignored" )) + return Globals::instance()->warningLogLevel(); + + return Globals::instance()->noticeLogLevel(); + } +}; + +#endif // _CUPS_ACCESS_ANALYZER_H_ diff --git a/ksystemlog/src/modes/cups/cupsAccessItemBuilder.cpp b/ksystemlog/src/modes/cups/cupsAccessItemBuilder.cpp new file mode 100644 index 00000000..d1b9616c --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsAccessItemBuilder.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsAccessItemBuilder.h" diff --git a/ksystemlog/src/modes/cups/cupsAccessItemBuilder.h b/ksystemlog/src/modes/cups/cupsAccessItemBuilder.h new file mode 100644 index 00000000..7fb89527 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsAccessItemBuilder.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_ACCESS_ITEM_BUILDER_H_ +#define _CUPS_ACCESS_ITEM_BUILDER_H_ + +#include + +#include "logModeItemBuilder.h" + +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +class CupsAccessItemBuilder : public LogModeItemBuilder { + + public: + CupsAccessItemBuilder() { + + } + + virtual ~CupsAccessItemBuilder() { + + } + + QString createFormattedText(LogLine* line) const { + QString result; + + QListIterator it(line->logItems()); + + result.append(QLatin1String( "" )); + + result.append(labelMessageFormat(i18n("Date:"), formatDate(line->time()))); + result.append(labelMessageFormat(i18n("Level:"), line->logLevel()->name())); + result.append(labelMessageFormat(i18n("Hostname:"), it.next() )); + result.append(labelMessageFormat(i18n("Identification:"), it.next() )); + result.append(labelMessageFormat(i18n("Username:"), it.next() )); + result.append(labelMessageFormat(i18n("HTTP Response:"), it.next() )); + result.append(labelMessageFormat(i18n("Bytes Sent:"), it.next() )); + + result.append(QLatin1String( "
" )); + + return result; + } + +}; + +#endif // _CUPS_ACCESS_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/modes/cups/cupsAccessLogMode.cpp b/ksystemlog/src/modes/cups/cupsAccessLogMode.cpp new file mode 100644 index 00000000..38725d64 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsAccessLogMode.cpp @@ -0,0 +1,66 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsAccessLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "cupsAccessAnalyzer.h" +#include "cupsAccessItemBuilder.h" +#include "cupsConfigurationWidget.h" +#include "cupsConfiguration.h" + + +CupsAccessLogMode::CupsAccessLogMode(CupsConfiguration* cupsConfiguration, CupsConfigurationWidget* cupsConfigurationWidget) : + LogMode(QLatin1String( CUPS_ACCESS_LOG_MODE_ID ), i18n("Cups Web Log"),QLatin1String( CUPS_ACCESS_MODE_ICON )) { + + d->logModeConfiguration = cupsConfiguration; + d->logModeConfigurationWidget = cupsConfigurationWidget; + + d->itemBuilder = new CupsAccessItemBuilder(); + + //Cups Log Action + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the CUPS Web Server Access log.")); + d->action->setWhatsThis(i18n("Displays the CUPS Web Server Access log in the current tab. CUPS is the program which manages printing on your computer. This log saves all requests performed to the CUPS embedded web server (default: http://localhost:631).")); + +} + + + +CupsAccessLogMode::~CupsAccessLogMode() { + +} + +Analyzer* CupsAccessLogMode::createAnalyzer() { + return new CupsAccessAnalyzer(this); +} + +QList CupsAccessLogMode::createLogFiles() { + CupsConfiguration* cupsConfiguration = logModeConfiguration(); + return cupsConfiguration->findNoModeLogFiles(cupsConfiguration->cupsAccessPaths()); +} diff --git a/ksystemlog/src/modes/cups/cupsAccessLogMode.h b/ksystemlog/src/modes/cups/cupsAccessLogMode.h new file mode 100644 index 00000000..9714e94f --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsAccessLogMode.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_ACCESS_LOG_MODE_H_ +#define _CUPS_ACCESS_LOG_MODE_H_ + +/** + * Cups Access Log Mode Identifier + */ +#define CUPS_ACCESS_LOG_MODE_ID "cupsAccessLogMode" + +/** + * Cups Access Log Icon + */ +#define CUPS_ACCESS_MODE_ICON "printer" + + +#include + +#include "logFile.h" + +#include "logMode.h" + +class CupsConfiguration; +class CupsConfigurationWidget; + +class CupsAccessLogMode : public LogMode { + + Q_OBJECT + +public: + explicit CupsAccessLogMode(CupsConfiguration* cupsConfiguration, CupsConfigurationWidget* cupsConfigurationWidget); + + ~CupsAccessLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _CUPS_ACCESS_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/cups/cupsAnalyzer.cpp b/ksystemlog/src/modes/cups/cupsAnalyzer.cpp new file mode 100644 index 00000000..5e5995e7 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsAnalyzer.h" diff --git a/ksystemlog/src/modes/cups/cupsAnalyzer.h b/ksystemlog/src/modes/cups/cupsAnalyzer.h new file mode 100644 index 00000000..9b377a7e --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsAnalyzer.h @@ -0,0 +1,149 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_ANALYZER_H_ +#define _CUPS_ANALYZER_H_ + + +#include + +#include "analyzer.h" + +#include "logging.h" + +#include "localLogFileReader.h" +#include "cupsLogMode.h" +#include "parsingHelper.h" + +#define DEBUG2_LOG_LEVEL_ICON "source" + +class CupsAnalyzer : public Analyzer { + + Q_OBJECT + + public: + explicit CupsAnalyzer(LogMode* logMode) : + Analyzer(logMode) { + + initializeTypeLevels(); + } + + virtual ~CupsAnalyzer() { + + } + + LogViewColumns initColumns() { + LogViewColumns columns; + columns.addColumn(LogViewColumn(i18n("Date"), true, false)); + columns.addColumn(LogViewColumn(i18n("Message"), true, false)); + + return columns; + } + + + + protected: + LogFileReader* createLogFileReader(const LogFile& logFile) { + return new LocalLogFileReader(logFile); + } + + Analyzer::LogFileSortMode logFileSortMode() { + return Analyzer::AscendingSortedLogFile; + } + + /* + * Also sees : + * http://www.cups.org/documentation.php/ref-error_log.html + * level date-time message + * + * Levels : + * A - Alert message (LogLevel alert) + * C - Critical error message (LogLevel crit) + * D - Debugging message (LogLevel debug) + * d - Detailed debugging message (LogLevel debug2) + * E - Normal error message (LogLevel error) + * I - Informational message (LogLevel info) + * N - Notice message (LogLevel notice) + * W - Warning message (LogLevel warn) + * X - Emergency error message (LogLevel emerg) + * + * Log line examples : + * I [15/Feb/2004:01:29:32 +0100] LoadPPDs: No new or changed PPDs... + * E [15/Feb/2004:01:43:15 +0100] Scheduler shutting down due to SIGTERM. + * + */ + LogLine* parseMessage(const QString& logLine, const LogFile& originalLogFile) { + QString line(logLine); + + QChar level=logLine[0]; + + QDateTime dateTime=ParsingHelper::instance()->parseHttpDateTime( logLine.mid(3, 26) ); + + QString message=line.remove(0, 31); + + QStringList list; + list.append(message); + + return new LogLine( + logLineInternalIdGenerator++, + dateTime, + list, + originalLogFile.url().path(), + findLogLevel(level), + logMode + ); + } + + private: + QMap mapTypeLevels; + + void initializeTypeLevels() { + mapTypeLevels[QLatin1Char( 'd' )]=new LogLevel(20, i18n("debug 2"), QLatin1String( DEBUG2_LOG_LEVEL_ICON ), QColor(169, 189, 165)); + mapTypeLevels[QLatin1Char( 'D' )]=Globals::instance()->debugLogLevel(); + mapTypeLevels[QLatin1Char( 'I' )]=Globals::instance()->informationLogLevel(); + mapTypeLevels[QLatin1Char( 'N' )]=Globals::instance()->noticeLogLevel(); + mapTypeLevels[QLatin1Char( 'W' )]=Globals::instance()->warningLogLevel(); + mapTypeLevels[QLatin1Char( 'E' )]=Globals::instance()->errorLogLevel(); + mapTypeLevels[QLatin1Char( 'C' )]=Globals::instance()->criticalLogLevel(); + mapTypeLevels[QLatin1Char( 'A' )]=Globals::instance()->alertLogLevel(); + mapTypeLevels[QLatin1Char( 'X' )]=Globals::instance()->emergencyLogLevel(); + mapTypeLevels[QLatin1Char( ' ' )]=Globals::instance()->noLogLevel(); + } + + LogLevel* findLogLevel(const QChar& type) { + QMap::iterator it; + + it=mapTypeLevels.find(type); + if (it!=mapTypeLevels.end()) { + return(*it); + } + else { + logError() << i18n("New Log Level detected: Please send this log file to the KSystemLog developer to add it.") << endl; + return(Globals::instance()->noLogLevel()); + } + } + + + + +}; + +#endif // _CUPS_ANALYZER_H_ diff --git a/ksystemlog/src/modes/cups/cupsConfiguration.cpp b/ksystemlog/src/modes/cups/cupsConfiguration.cpp new file mode 100644 index 00000000..9487698c --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsConfiguration.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsConfiguration.h" diff --git a/ksystemlog/src/modes/cups/cupsConfiguration.h b/ksystemlog/src/modes/cups/cupsConfiguration.h new file mode 100644 index 00000000..0f5d0c1b --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsConfiguration.h @@ -0,0 +1,115 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_CONFIGURATION_H_ +#define _CUPS_CONFIGURATION_H_ + +#include + +#include "logModeConfiguration.h" + +#include "logging.h" +#include "defaults.h" + +#include "cupsLogMode.h" + +#include "ksystemlogConfig.h" + +class CupsConfigurationPrivate { +public: + QStringList cupsPaths; + + QStringList cupsAccessPaths; + + QStringList cupsPagePaths; + + QStringList cupsPdfPaths; +}; + +class CupsConfiguration : public LogModeConfiguration { + + Q_OBJECT + + public: + CupsConfiguration() : + d(new CupsConfigurationPrivate()) { + + configuration->setCurrentGroup(QLatin1String( "CupsLogMode" )); + + QStringList defaultCupsPaths; + defaultCupsPaths << QLatin1String( "/var/log/cups/error_log" ); + configuration->addItemStringList(QLatin1String( "CupsLogFilesPaths" ), d->cupsPaths, defaultCupsPaths, QLatin1String( "CupsLogFilesPaths" )); + + QStringList defaultCupsAccessPaths; + defaultCupsAccessPaths << QLatin1String( "/var/log/cups/access_log" ); + configuration->addItemStringList(QLatin1String( "CupsAccessLogFilesPaths" ), d->cupsAccessPaths, defaultCupsAccessPaths, QLatin1String( "CupsAccessLogFilesPaths" )); + + QStringList defaultCupsPagePaths; + defaultCupsPagePaths << QLatin1String( "/var/log/cups/page_log" ); + configuration->addItemStringList(QLatin1String( "CupsPageLogFilesPaths" ), d->cupsPagePaths, defaultCupsPagePaths, QLatin1String( "CupsPageLogFilesPaths" )); + + QStringList defaultCupsPdfPaths; + defaultCupsPdfPaths << QLatin1String( "/var/log/cups/cups-pdf_log" ); + configuration->addItemStringList(QLatin1String( "CupsPdfLogFilesPaths" ), d->cupsPdfPaths, defaultCupsPdfPaths, QLatin1String( "CupsPdfLogFilesPaths" )); + } + + virtual ~CupsConfiguration() { + delete d; + } + + QStringList cupsPaths() const { + return d->cupsPaths; + } + + QStringList cupsAccessPaths() const { + return d->cupsAccessPaths; + } + + QStringList cupsPagePaths() const { + return d->cupsPagePaths; + } + + QStringList cupsPdfPaths() const { + return d->cupsPdfPaths; + } + + void setCupsPaths(const QStringList& cupsPaths) { + d->cupsPaths = cupsPaths; + } + + void setCupsAccessPaths(const QStringList& cupsAccessPaths) { + d->cupsAccessPaths = cupsAccessPaths; + } + + void setCupsPagePaths(const QStringList& cupsPagePaths) { + d->cupsPagePaths = cupsPagePaths; + } + + void setCupsPdfPaths(const QStringList& cupsPdfPaths) { + d->cupsPdfPaths = cupsPdfPaths; + } + + private: + CupsConfigurationPrivate* const d; + +}; + +#endif // _CUPS_CONFIGURATION_H_ diff --git a/ksystemlog/src/modes/cups/cupsConfigurationWidget.cpp b/ksystemlog/src/modes/cups/cupsConfigurationWidget.cpp new file mode 100644 index 00000000..388a9845 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsConfigurationWidget.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KApacheLog, a apache log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsConfigurationWidget.h" diff --git a/ksystemlog/src/modes/cups/cupsConfigurationWidget.h b/ksystemlog/src/modes/cups/cupsConfigurationWidget.h new file mode 100644 index 00000000..b8c3d294 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsConfigurationWidget.h @@ -0,0 +1,120 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_CONFIGURATION_WIDGET_H_ +#define _CUPS_CONFIGURATION_WIDGET_H_ + +#include "logModeConfigurationWidget.h" + + +#include + +#include "globals.h" +#include "logging.h" +#include "multipleFileList.h" + +#include "logLevel.h" + +#include "cupsConfiguration.h" +#include "cupsLogMode.h" + +class CupsConfigurationWidget : public LogModeConfigurationWidget { + + Q_OBJECT + + public: + CupsConfigurationWidget() : + LogModeConfigurationWidget(i18n("Cups Log"),QLatin1String( CUPS_MODE_ICON ), i18n("Cups & Cups Web Server Log")) + { + + QHBoxLayout* layout = new QHBoxLayout(); + this->setLayout(layout); + + cupsFileList=new MultipleFileList(this, i18n("

These files will be analyzed to show the Cups log and the Cups Web Access log.

")); + + cupsPathsId = cupsFileList->addCategory(i18n("Cups Log Files"), i18n("Add Cups File...")); + cupsAccessPathsId = cupsFileList->addCategory(i18n("Cups Access Log Files"), i18n("Add Cups Access File...")); + cupsPagePathsId = cupsFileList->addCategory(i18n("Cups Page Log Files"), i18n("Add Cups Page File...")); + cupsPdfPathsId = cupsFileList->addCategory(i18n("Cups PDF Log Files"), i18n("Add Cups PDF File...")); + + connect(cupsFileList, SIGNAL(fileListChanged()), this, SIGNAL(configurationChanged())); + + layout->addWidget(cupsFileList); + } + + ~CupsConfigurationWidget() { + + } + + + public slots: + + void saveConfig() { + logDebug() << "Saving config from Cups Options..." << endl; + + CupsConfiguration* cupsConfiguration = Globals::instance()->findLogMode(QLatin1String( CUPS_LOG_MODE_ID ))->logModeConfiguration(); + cupsConfiguration->setCupsPaths(cupsFileList->paths(cupsPathsId)); + cupsConfiguration->setCupsAccessPaths(cupsFileList->paths(cupsAccessPathsId)); + cupsConfiguration->setCupsPagePaths(cupsFileList->paths(cupsPagePathsId)); + cupsConfiguration->setCupsPdfPaths(cupsFileList->paths(cupsPdfPathsId)); + + } + + void defaultConfig() { + //TODO Find a way to read the configuration per default + readConfig(); + } + + void readConfig() { + CupsConfiguration* cupsConfiguration = Globals::instance()->findLogMode(QLatin1String( CUPS_LOG_MODE_ID ))->logModeConfiguration(); + + cupsFileList->removeAllItems(); + + cupsFileList->addPaths(cupsPathsId, cupsConfiguration->cupsPaths()); + cupsFileList->addPaths(cupsAccessPathsId, cupsConfiguration->cupsAccessPaths()); + cupsFileList->addPaths(cupsPagePathsId, cupsConfiguration->cupsPagePaths()); + cupsFileList->addPaths(cupsPdfPathsId, cupsConfiguration->cupsPdfPaths()); + } + + protected: + bool isValid() const { + if (cupsFileList->isOneOfCategoryEmpty()==true) { + logDebug() << "Cups configuration not valid" << endl; + return false; + } + + logDebug() << "Cups configuration valid" << endl; + return true; + + } + + private: + + MultipleFileList* cupsFileList; + + int cupsPathsId; + int cupsAccessPathsId; + int cupsPagePathsId; + int cupsPdfPathsId; + +}; + +#endif // _CUPS_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/modes/cups/cupsFactory.cpp b/ksystemlog/src/modes/cups/cupsFactory.cpp new file mode 100644 index 00000000..fb3fafa6 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsFactory.cpp @@ -0,0 +1,68 @@ +/*************************************************************************** + * KCupsLog, a cups log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsFactory.h" + + +#include + +#include "multipleActions.h" +#include "logMode.h" +#include "logging.h" + +#include "cupsLogMode.h" +#include "cupsAccessLogMode.h" +#include "cupsPageLogMode.h" +#include "cupsPdfLogMode.h" + +#include "cupsConfigurationWidget.h" +#include "cupsConfiguration.h" + +QList CupsLogModeFactory::createLogModes() const { + + //Create the shared configuration and configuration widget between the logModes + + CupsConfiguration* logModeConfiguration = new CupsConfiguration(); + CupsConfigurationWidget* logModeConfigurationWidget = new CupsConfigurationWidget(); + + QList logModes; + logModes.append(new CupsLogMode(logModeConfiguration, logModeConfigurationWidget)); + logModes.append(new CupsAccessLogMode(logModeConfiguration, logModeConfigurationWidget)); + logModes.append(new CupsPageLogMode(logModeConfiguration, logModeConfigurationWidget)); + logModes.append(new CupsPdfLogMode(logModeConfiguration, logModeConfigurationWidget)); + + return logModes; +} + +LogModeAction* CupsLogModeFactory::createLogModeAction() const { + LogMode* cupsLogMode = Globals::instance()->findLogMode(QLatin1String( CUPS_LOG_MODE_ID )); + + MultipleActions* multipleActions = new MultipleActions(KIcon( QLatin1String( CUPS_MODE_ICON) ), i18n("Cups"), cupsLogMode); + multipleActions->addInnerAction(cupsLogMode->action()); + multipleActions->addInnerAction(Globals::instance()->findLogMode(QLatin1String( CUPS_ACCESS_LOG_MODE_ID ))->action()); + multipleActions->addInnerAction(Globals::instance()->findLogMode(QLatin1String( CUPS_PAGE_LOG_MODE_ID ))->action()); + multipleActions->addInnerAction(Globals::instance()->findLogMode(QLatin1String( CUPS_PDF_LOG_MODE_ID ))->action()); + + multipleActions->setInToolBar(false); + multipleActions->setCategory(LogModeAction::ServicesCategory); + + return multipleActions; +} diff --git a/ksystemlog/src/modes/cups/cupsFactory.h b/ksystemlog/src/modes/cups/cupsFactory.h new file mode 100644 index 00000000..a1490385 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsFactory.h @@ -0,0 +1,41 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_LOG_MODE_FACTORY_H_ +#define _CUPS_LOG_MODE_FACTORY_H_ + +#include + +#include "logModeFactory.h" + +class CupsLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; +}; + +#endif // _CUPS_LOG_MODE_FACTORY_H_ + diff --git a/ksystemlog/src/modes/cups/cupsItemBuilder.cpp b/ksystemlog/src/modes/cups/cupsItemBuilder.cpp new file mode 100644 index 00000000..b67fb563 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsItemBuilder.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsItemBuilder.h" diff --git a/ksystemlog/src/modes/cups/cupsItemBuilder.h b/ksystemlog/src/modes/cups/cupsItemBuilder.h new file mode 100644 index 00000000..f55ce771 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsItemBuilder.h @@ -0,0 +1,63 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_ITEM_BUILDER_H_ +#define _CUPS_ITEM_BUILDER_H_ + +#include + +#include "logModeItemBuilder.h" + +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +class CupsItemBuilder : public LogModeItemBuilder { + + public: + CupsItemBuilder() { + + } + + virtual ~CupsItemBuilder() { + + } + + QString createFormattedText(LogLine* line) const { + QString result; + + result.append(QLatin1String( "" )); + + result.append(labelMessageFormat(i18n("Date:"), formatDate(line->time()))); + result.append(labelMessageFormat(i18n("Level:"), line->logLevel()->name())); + + result.append(QLatin1String( "
" )); + + return result; + } +}; + + +#endif // _CUPS_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/modes/cups/cupsLogMode.cpp b/ksystemlog/src/modes/cups/cupsLogMode.cpp new file mode 100644 index 00000000..7e98e34e --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsLogMode.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "cupsAnalyzer.h" +#include "cupsItemBuilder.h" +#include "cupsConfigurationWidget.h" +#include "cupsConfiguration.h" + + +CupsLogMode::CupsLogMode(CupsConfiguration* cupsConfiguration, CupsConfigurationWidget* cupsConfigurationWidget) : + LogMode(QLatin1String( CUPS_LOG_MODE_ID ), i18n("Cups Log"),QLatin1String( CUPS_MODE_ICON )) { + + d->logModeConfiguration = cupsConfiguration; + d->logModeConfigurationWidget = cupsConfigurationWidget; + + d->itemBuilder = new CupsItemBuilder(); + + //Cups Log Action + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the Cups log.")); + d->action->setWhatsThis(i18n("Displays the CUPS log in the current tab. CUPS is the program which manages printing on your computer.")); + +} + +CupsLogMode::~CupsLogMode() { + +} + +Analyzer* CupsLogMode::createAnalyzer() { + return new CupsAnalyzer(this); +} + +QList CupsLogMode::createLogFiles() { + CupsConfiguration* cupsConfiguration = logModeConfiguration(); + return cupsConfiguration->findNoModeLogFiles(cupsConfiguration->cupsPaths()); +} diff --git a/ksystemlog/src/modes/cups/cupsLogMode.h b/ksystemlog/src/modes/cups/cupsLogMode.h new file mode 100644 index 00000000..72d7c740 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsLogMode.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_LOG_MODE_H_ +#define _CUPS_LOG_MODE_H_ + +/** + * Cups Log Mode Identifier + */ +#define CUPS_LOG_MODE_ID "cupsLogMode" + +/** + * Cups Log Icon + */ +#define CUPS_MODE_ICON "document-print" + + +#include + +#include "logFile.h" + +#include "logMode.h" + +class CupsConfiguration; +class CupsConfigurationWidget; + +class CupsLogMode : public LogMode { + + Q_OBJECT + +public: + explicit CupsLogMode(CupsConfiguration* cupsConfiguration, CupsConfigurationWidget* cupsConfigurationWidget); + + ~CupsLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _CUPS_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/cups/cupsPageAnalyzer.cpp b/ksystemlog/src/modes/cups/cupsPageAnalyzer.cpp new file mode 100644 index 00000000..0fe2e47c --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsPageAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsPageAnalyzer.h" diff --git a/ksystemlog/src/modes/cups/cupsPageAnalyzer.h b/ksystemlog/src/modes/cups/cupsPageAnalyzer.h new file mode 100644 index 00000000..01229a46 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsPageAnalyzer.h @@ -0,0 +1,112 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_PAGE_ANALYZER_H_ +#define _CUPS_PAGE_ANALYZER_H_ + + +#include + +#include "analyzer.h" + +#include "localLogFileReader.h" +#include "logging.h" +#include "parsingHelper.h" + +#include "cupsPageLogMode.h" + +class CupsPageAnalyzer : public Analyzer { + + Q_OBJECT + + public: + explicit CupsPageAnalyzer(LogMode* logMode) : + Analyzer(logMode), + cupsPageRegex(QLatin1String( "(\\S*) (\\S*) (\\S*) \\[(.*)\\] (\\S*) (\\S*) (\\S*)" )) { + } + + virtual ~CupsPageAnalyzer() { + + } + + LogViewColumns initColumns() { + LogViewColumns columns; + + columns.addColumn(LogViewColumn(i18n("Date"), true, false)); + columns.addColumn(LogViewColumn(i18n("Printer"), true, true)); + columns.addColumn(LogViewColumn(i18n("User"), true, true)); + columns.addColumn(LogViewColumn(i18n("Job Id"), true, true)); + columns.addColumn(LogViewColumn(i18n("Page Number"), true, false)); + columns.addColumn(LogViewColumn(i18n("Num Copies"), true, true)); + columns.addColumn(LogViewColumn(i18n("Job Billing"), true, false)); + + return columns; + } + + + protected: + + QRegExp cupsPageRegex; + + LogFileReader* createLogFileReader(const LogFile& logFile) { + return new LocalLogFileReader(logFile); + } + + Analyzer::LogFileSortMode logFileSortMode() { + return Analyzer::AscendingSortedLogFile; + } + + /* + * http://www.cups.org/documentation.php/ref-page_log.html + * Format : printer user job-id date-time page-number num-copies job-billing + * + * DeskJet root 2 [20/May/1999:19:21:05 +0000] 1 1 acme-123 + * DeskJet root 2 [20/May/1999:19:21:05 +0000] 2 1 acme-123 + */ + LogLine* parseMessage(const QString& logLine, const LogFile& originalLogFile) { + + QString line(logLine); + + int firstPosition = cupsPageRegex.indexIn(logLine); + if (firstPosition == -1) { + logDebug() << "Unable to parse line " << logLine << endl; + return NULL; + } + + QStringList capturedTexts = cupsPageRegex.capturedTexts(); + + //Remove full line + capturedTexts.removeAt(0); + + QDateTime dateTime=ParsingHelper::instance()->parseHttpDateTime(capturedTexts.takeAt(3)); + + return new LogLine( + logLineInternalIdGenerator++, + dateTime, + capturedTexts, + originalLogFile.url().path(), + Globals::instance()->informationLogLevel(), + logMode + ); + } +}; + +#endif // _CUPS_PAGE_ANALYZER_H_ diff --git a/ksystemlog/src/modes/cups/cupsPageItemBuilder.cpp b/ksystemlog/src/modes/cups/cupsPageItemBuilder.cpp new file mode 100644 index 00000000..77dd6d66 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsPageItemBuilder.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsPageItemBuilder.h" diff --git a/ksystemlog/src/modes/cups/cupsPageItemBuilder.h b/ksystemlog/src/modes/cups/cupsPageItemBuilder.h new file mode 100644 index 00000000..e06ab8d9 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsPageItemBuilder.h @@ -0,0 +1,71 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_PAGE_ITEM_BUILDER_H_ +#define _CUPS_PAGE_ITEM_BUILDER_H_ + +#include + +#include "logModeItemBuilder.h" + +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +class CupsPageItemBuilder : public LogModeItemBuilder { + + public: + CupsPageItemBuilder() { + + } + + virtual ~CupsPageItemBuilder() { + + } + + QString createFormattedText(LogLine* line) const { + QString result; + + QListIterator it(line->logItems()); + + result.append(QLatin1String( "" )); + + result.append(labelMessageFormat(i18n("Date:"), formatDate(line->time()))); + result.append(labelMessageFormat(i18n("Level:"), line->logLevel()->name())); + result.append(labelMessageFormat(i18n("Printer:"), it.next() )); + result.append(labelMessageFormat(i18n("Username:"), it.next() )); + result.append(labelMessageFormat(i18n("Job Id:"), it.next() )); + result.append(labelMessageFormat(i18n("Page Number:"), it.next() )); + result.append(labelMessageFormat(i18n("Num Copies:"), it.next() )); + result.append(labelMessageFormat(i18n("Job Billing:"), it.next() )); + + result.append(QLatin1String( "
" )); + + return result; + } + +}; + +#endif // _CUPS_PAGE_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/modes/cups/cupsPageLogMode.cpp b/ksystemlog/src/modes/cups/cupsPageLogMode.cpp new file mode 100644 index 00000000..0508953c --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsPageLogMode.cpp @@ -0,0 +1,66 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsPageLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "cupsPageAnalyzer.h" +#include "cupsPageItemBuilder.h" +#include "cupsConfigurationWidget.h" +#include "cupsConfiguration.h" + + +CupsPageLogMode::CupsPageLogMode(CupsConfiguration* cupsConfiguration, CupsConfigurationWidget* cupsConfigurationWidget) : + LogMode(QLatin1String( CUPS_PAGE_LOG_MODE_ID ), i18n("Cups Page Log"), QLatin1String( CUPS_PAGE_MODE_ICON )) { + + d->logModeConfiguration = cupsConfiguration; + d->logModeConfigurationWidget = cupsConfigurationWidget; + + d->itemBuilder = new CupsPageItemBuilder(); + + //Cups Log Action + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the CUPS Page log.")); + d->action->setWhatsThis(i18n("Displays the CUPS Page log in the current tab. CUPS is the program which manages printing on your computer. This log saves all requests performed to the CUPS embedded web server (default: http://localhost:631).")); + +} + + + +CupsPageLogMode::~CupsPageLogMode() { + +} + +Analyzer* CupsPageLogMode::createAnalyzer() { + return new CupsPageAnalyzer(this); +} + +QList CupsPageLogMode::createLogFiles() { + CupsConfiguration* cupsConfiguration = logModeConfiguration(); + return cupsConfiguration->findNoModeLogFiles(cupsConfiguration->cupsPagePaths()); +} diff --git a/ksystemlog/src/modes/cups/cupsPageLogMode.h b/ksystemlog/src/modes/cups/cupsPageLogMode.h new file mode 100644 index 00000000..0eb37cbb --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsPageLogMode.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_PAGE_LOG_MODE_H_ +#define _CUPS_PAGE_LOG_MODE_H_ + +/** + * Cups Page Log Mode Identifier + */ +#define CUPS_PAGE_LOG_MODE_ID "cupsPageLogMode" + +/** + * Cups Page Log Icon + */ +#define CUPS_PAGE_MODE_ICON "document-new" + + +#include + +#include "logFile.h" + +#include "logMode.h" + +class CupsConfiguration; +class CupsConfigurationWidget; + +class CupsPageLogMode : public LogMode { + + Q_OBJECT + +public: + explicit CupsPageLogMode(CupsConfiguration* cupsConfiguration, CupsConfigurationWidget* cupsConfigurationWidget); + + ~CupsPageLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _CUPS_PAGE_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/cups/cupsPdfAnalyzer.cpp b/ksystemlog/src/modes/cups/cupsPdfAnalyzer.cpp new file mode 100644 index 00000000..3003a3d4 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsPdfAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsPdfAnalyzer.h" diff --git a/ksystemlog/src/modes/cups/cupsPdfAnalyzer.h b/ksystemlog/src/modes/cups/cupsPdfAnalyzer.h new file mode 100644 index 00000000..b601836e --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsPdfAnalyzer.h @@ -0,0 +1,127 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_PDF_ANALYZER_H_ +#define _CUPS_PDF_ANALYZER_H_ + + +#include + +#include "analyzer.h" + +#include "localLogFileReader.h" +#include "logging.h" +#include "parsingHelper.h" + +#include "cupsPdfLogMode.h" + +class CupsPdfAnalyzer : public Analyzer { + + Q_OBJECT + + public: + //Fri Sep 30 21:58:37 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) + explicit CupsPdfAnalyzer(LogMode* logMode) : + Analyzer(logMode), + cupsPdfRegex(QLatin1String( "\\S* " ) + ParsingHelper::instance()->syslogDateTimeRegexp() + QLatin1String( "[ ]+\\[(\\w*)\\][ ]+(.*)" )) { // \\[(.*)\\] (\\S*) (\\S*) (\\S*) + } + + virtual ~CupsPdfAnalyzer() { + + } + + LogViewColumns initColumns() { + LogViewColumns columns; + + columns.addColumn(LogViewColumn(i18n("Date"), true, false)); + columns.addColumn(LogViewColumn(i18n("Message"), true, false)); + + return columns; + } + + + protected: + + QRegExp cupsPdfRegex; + + LogFileReader* createLogFileReader(const LogFile& logFile) { + return new LocalLogFileReader(logFile); + } + + Analyzer::LogFileSortMode logFileSortMode() { + return Analyzer::AscendingSortedLogFile; + } + + /* + * http://www.physik.uni-wuerzburg.de/~vrbehr/cups-pdf/documentation.shtml (cups-pdf_log) + * + * Thu Jun 14 12:40:35 2007 [STATUS] identification string sent + * Thu Jun 14 12:43:07 2007 [ERROR] failed to set file mode for PDF file (non fatal) (/var/spool/cups-pdf/root/Test_Pdf.pdf) + * Thu Jun 14 12:43:07 2007 [STATUS] PDF creation successfully finished (root) + * Fri Sep 30 21:58:37 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) + * Sat Oct 1 09:11:45 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) + */ + LogLine* parseMessage(const QString& logLine, const LogFile& originalLogFile) { + + QString line(logLine); + + int firstPosition = cupsPdfRegex.indexIn(logLine); + if (firstPosition == -1) { + logDebug() << "Unable to parse line " << logLine << endl; + return NULL; + } + + QStringList capturedTexts = cupsPdfRegex.capturedTexts(); + + /* + logDebug() << "------------------------------------------" << endl; + foreach(QString cap, capturedTexts) { + logDebug() << cap << endl; + } + logDebug() << "------------------------------------------" << endl; + */ + + //Remove full line + capturedTexts.removeAt(0); + + QDateTime dateTime=ParsingHelper::instance()->parseSyslogDateTime(capturedTexts.takeAt(0)); + LogLevel* logLevel = findLogLevel(capturedTexts.takeAt(0)); + + return new LogLine( + logLineInternalIdGenerator++, + dateTime, + capturedTexts, + originalLogFile.url().path(), + logLevel, + logMode + ); + } + + LogLevel* findLogLevel(const QString& level) { + if ( level == QLatin1String( "ERROR" )) + return Globals::instance()->errorLogLevel(); + + //level == "STATUS" + return Globals::instance()->informationLogLevel(); + } +}; + +#endif // _CUPS_PDF_ANALYZER_H_ diff --git a/ksystemlog/src/modes/cups/cupsPdfItemBuilder.cpp b/ksystemlog/src/modes/cups/cupsPdfItemBuilder.cpp new file mode 100644 index 00000000..c1422881 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsPdfItemBuilder.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsPdfItemBuilder.h" diff --git a/ksystemlog/src/modes/cups/cupsPdfItemBuilder.h b/ksystemlog/src/modes/cups/cupsPdfItemBuilder.h new file mode 100644 index 00000000..b7373dbe --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsPdfItemBuilder.h @@ -0,0 +1,66 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_PDF_ITEM_BUILDER_H_ +#define _CUPS_PDF_ITEM_BUILDER_H_ + +#include + +#include "logModeItemBuilder.h" + +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +class CupsPdfItemBuilder : public LogModeItemBuilder { + + public: + CupsPdfItemBuilder() { + + } + + virtual ~CupsPdfItemBuilder() { + + } + + QString createFormattedText(LogLine* line) const { + QString result; + + QListIterator it(line->logItems()); + + result.append(QLatin1String( "" )); + + result.append(labelMessageFormat(i18n("Date:"), formatDate(line->time()))); + result.append(labelMessageFormat(i18n("Level:"), line->logLevel()->name())); + result.append(labelMessageFormat(i18n("Message:"), it.next() )); + + result.append(QLatin1String( "
" )); + + return result; + } + +}; + +#endif // _CUPS_PDF_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/modes/cups/cupsPdfLogMode.cpp b/ksystemlog/src/modes/cups/cupsPdfLogMode.cpp new file mode 100644 index 00000000..7a978125 --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsPdfLogMode.cpp @@ -0,0 +1,66 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "cupsPdfLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "cupsPdfAnalyzer.h" +#include "cupsPdfItemBuilder.h" +#include "cupsConfigurationWidget.h" +#include "cupsConfiguration.h" + + +CupsPdfLogMode::CupsPdfLogMode(CupsConfiguration* cupsConfiguration, CupsConfigurationWidget* cupsConfigurationWidget) : + LogMode(QLatin1String( CUPS_PDF_LOG_MODE_ID ), i18n("Cups PDF Log"), QLatin1String( CUPS_PDF_MODE_ICON )) { + + d->logModeConfiguration = cupsConfiguration; + d->logModeConfigurationWidget = cupsConfigurationWidget; + + d->itemBuilder = new CupsPdfItemBuilder(); + + //Cups Log Action + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the CUPS PDF log.")); + d->action->setWhatsThis(i18n("Displays the CUPS PDF log in the current tab. CUPS is the program which manages printing on your computer. This log saves all requests performed to the CUPS embedded web server (default: http://localhost:631).")); + +} + + + +CupsPdfLogMode::~CupsPdfLogMode() { + +} + +Analyzer* CupsPdfLogMode::createAnalyzer() { + return new CupsPdfAnalyzer(this); +} + +QList CupsPdfLogMode::createLogFiles() { + CupsConfiguration* cupsConfiguration = logModeConfiguration(); + return cupsConfiguration->findNoModeLogFiles(cupsConfiguration->cupsPdfPaths()); +} diff --git a/ksystemlog/src/modes/cups/cupsPdfLogMode.h b/ksystemlog/src/modes/cups/cupsPdfLogMode.h new file mode 100644 index 00000000..d915e53c --- /dev/null +++ b/ksystemlog/src/modes/cups/cupsPdfLogMode.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _CUPS_PDF_LOG_MODE_H_ +#define _CUPS_PDF_LOG_MODE_H_ + +/** + * Cups Pdf Log Mode Identifier + */ +#define CUPS_PDF_LOG_MODE_ID "cupsPdfLogMode" + +/** + * Cups Pdf Log Icon + */ +#define CUPS_PDF_MODE_ICON "application-pdf" + + +#include + +#include "logFile.h" + +#include "logMode.h" + +class CupsConfiguration; +class CupsConfigurationWidget; + +class CupsPdfLogMode : public LogMode { + + Q_OBJECT + +public: + explicit CupsPdfLogMode(CupsConfiguration* cupsConfiguration, CupsConfigurationWidget* cupsConfigurationWidget); + + ~CupsPdfLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _CUPS_PDF_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/daemon/CMakeLists.txt b/ksystemlog/src/modes/daemon/CMakeLists.txt new file mode 100644 index 00000000..b037ae3f --- /dev/null +++ b/ksystemlog/src/modes/daemon/CMakeLists.txt @@ -0,0 +1,32 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../base + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../config + ${CMAKE_CURRENT_BINARY_DIR}/../base + ${CMAKE_CURRENT_BINARY_DIR}/../../config +) + +set(ksystemlog_daemon_sources + daemonConfigurationWidget.cpp + daemonConfiguration.cpp + daemonLogMode.cpp + daemonFactory.cpp +) + +kde4_add_library(ksystemlog_daemon STATIC ${ksystemlog_daemon_sources}) + +add_dependencies( + ksystemlog_daemon + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_daemon + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config +) diff --git a/ksystemlog/src/modes/daemon/daemonConfiguration.cpp b/ksystemlog/src/modes/daemon/daemonConfiguration.cpp new file mode 100644 index 00000000..76103221 --- /dev/null +++ b/ksystemlog/src/modes/daemon/daemonConfiguration.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "daemonConfiguration.h" diff --git a/ksystemlog/src/modes/daemon/daemonConfiguration.h b/ksystemlog/src/modes/daemon/daemonConfiguration.h new file mode 100644 index 00000000..26c8e25c --- /dev/null +++ b/ksystemlog/src/modes/daemon/daemonConfiguration.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _DAEMON_CONFIGURATION_H_ +#define _DAEMON_CONFIGURATION_H_ + +#include + +#include "logModeConfiguration.h" + +#include "logging.h" +#include "defaults.h" + +#include "ksystemlogConfig.h" + +class DaemonConfigurationPrivate { +public: + QStringList daemonPaths; +}; + +class DaemonConfiguration : public LogModeConfiguration { + + Q_OBJECT + + public: + DaemonConfiguration() : + d(new DaemonConfigurationPrivate()) { + + configuration->setCurrentGroup(QLatin1String( "DaemonLogMode" )); + + QStringList defaultDaemonPaths; + defaultDaemonPaths << QLatin1String( "/var/log/daemon.log" ); + configuration->addItemStringList(QLatin1String( "LogFilesPaths" ), d->daemonPaths, defaultDaemonPaths, QLatin1String( "LogFilesPaths" )); + + } + + virtual ~DaemonConfiguration() { + delete d; + } + + QStringList daemonPaths() const { + return d->daemonPaths; + } + + void setDaemonPaths(const QStringList& daemonPaths) { + d->daemonPaths = daemonPaths; + } + + private: + DaemonConfigurationPrivate* const d; + +}; + +#endif // _DAEMON_CONFIGURATION_H_ diff --git a/ksystemlog/src/modes/daemon/daemonConfigurationWidget.cpp b/ksystemlog/src/modes/daemon/daemonConfigurationWidget.cpp new file mode 100644 index 00000000..8ab61664 --- /dev/null +++ b/ksystemlog/src/modes/daemon/daemonConfigurationWidget.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KApacheLog, a apache log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "daemonConfigurationWidget.h" diff --git a/ksystemlog/src/modes/daemon/daemonConfigurationWidget.h b/ksystemlog/src/modes/daemon/daemonConfigurationWidget.h new file mode 100644 index 00000000..58c76f8f --- /dev/null +++ b/ksystemlog/src/modes/daemon/daemonConfigurationWidget.h @@ -0,0 +1,99 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _DAEMON_CONFIGURATION_WIDGET_H_ +#define _DAEMON_CONFIGURATION_WIDGET_H_ + + +#include + +#include "globals.h" +#include "logging.h" +#include "fileList.h" + +#include "logLevel.h" + +#include "daemonConfiguration.h" +#include "daemonLogMode.h" + +#include "logModeConfigurationWidget.h" + +class FileList; + +class DaemonConfigurationWidget : public LogModeConfigurationWidget { + + Q_OBJECT + + public: + DaemonConfigurationWidget() : + LogModeConfigurationWidget(i18n("Daemons' Logs"),QLatin1String( DAEMON_MODE_ICON ), i18n("Daemons' Logs")) + { + + QHBoxLayout* layout = new QHBoxLayout(); + this->setLayout(layout); + + fileList=new FileList(this, i18n("

These files will be analyzed to show the Daemons' Logs.

")); + connect(fileList, SIGNAL(fileListChanged()), this, SIGNAL(configurationChanged())); + layout->addWidget(fileList); + + } + + ~DaemonConfigurationWidget() { + + } + + public slots: + + void saveConfig() { + DaemonConfiguration* daemonConfiguration = Globals::instance()->findLogMode(QLatin1String( DAEMON_LOG_MODE_ID ))->logModeConfiguration(); + + daemonConfiguration->setDaemonPaths(fileList->paths()); + } + + void readConfig() { + DaemonConfiguration* daemonConfiguration = Globals::instance()->findLogMode(QLatin1String( DAEMON_LOG_MODE_ID ))->logModeConfiguration(); + + fileList->removeAllItems(); + + fileList->addPaths(daemonConfiguration->daemonPaths()); + } + + void defaultConfig() { + //TODO Find a way to read the configuration per default + readConfig(); + } + + protected: + bool isValid() const { + if (fileList->isEmpty()==false) { + return true; + } + + return false; + + } + + private: + FileList* fileList; + +}; + +#endif // _DAEMON_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/modes/daemon/daemonFactory.cpp b/ksystemlog/src/modes/daemon/daemonFactory.cpp new file mode 100644 index 00000000..06b3f4bd --- /dev/null +++ b/ksystemlog/src/modes/daemon/daemonFactory.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "daemonFactory.h" + +#include + + +#include "logMode.h" +#include "logging.h" + +#include "simpleAction.h" +#include "daemonLogMode.h" + +#include "logModeFactory.h" + +QList DaemonLogModeFactory::createLogModes() const { + QList logModes; + logModes.append(new DaemonLogMode()); + return logModes; +} + +LogModeAction* DaemonLogModeFactory::createLogModeAction() const { + LogMode* logMode = Globals::instance()->findLogMode(QLatin1String( DAEMON_LOG_MODE_ID )); + return new SimpleAction(logMode->action(), logMode); +} diff --git a/ksystemlog/src/modes/daemon/daemonFactory.h b/ksystemlog/src/modes/daemon/daemonFactory.h new file mode 100644 index 00000000..578c7719 --- /dev/null +++ b/ksystemlog/src/modes/daemon/daemonFactory.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _DAEMON_FACTORY_H_ +#define _DAEMON_FACTORY_H_ + +#include + +#include "logModeFactory.h" + +class LogModeAction; +class LogMode; + +class DaemonLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; + +}; + +#endif // _DAEMON_FACTORY_H_ + diff --git a/ksystemlog/src/modes/daemon/daemonLogMode.cpp b/ksystemlog/src/modes/daemon/daemonLogMode.cpp new file mode 100644 index 00000000..7ab0fb1d --- /dev/null +++ b/ksystemlog/src/modes/daemon/daemonLogMode.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "daemonLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "syslogAnalyzer.h" +#include "daemonConfigurationWidget.h" +#include "daemonConfiguration.h" + +#include "logModeItemBuilder.h" + +DaemonLogMode::DaemonLogMode() : + LogMode(QLatin1String( DAEMON_LOG_MODE_ID ), i18n("Daemons' Logs"), QLatin1String( DAEMON_MODE_ICON )) { + + d->logModeConfiguration = new DaemonConfiguration(); + + d->logModeConfigurationWidget = new DaemonConfigurationWidget(); + + d->itemBuilder = new LogModeItemBuilder(); + + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the daemons' logs.")); + d->action->setWhatsThis(i18n("Displays the daemons' logs in the current tab. The daemons are all processes launched in the background of the system. See this log if you want to know what occurs in the background of your system.")); + +} + +DaemonLogMode::~DaemonLogMode() { + +} + +Analyzer* DaemonLogMode::createAnalyzer() { + return new SyslogAnalyzer(this); +} + +QList DaemonLogMode::createLogFiles() { + DaemonConfiguration* configuration = logModeConfiguration(); + return configuration->findGenericLogFiles(configuration->daemonPaths()); +} diff --git a/ksystemlog/src/modes/daemon/daemonLogMode.h b/ksystemlog/src/modes/daemon/daemonLogMode.h new file mode 100644 index 00000000..0e7ea10e --- /dev/null +++ b/ksystemlog/src/modes/daemon/daemonLogMode.h @@ -0,0 +1,67 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _DAEMON_LOG_MODE_H_ +#define _DAEMON_LOG_MODE_H_ + +/** + * Daemon Log Mode Identifier + */ +#define DAEMON_LOG_MODE_ID "daemonLogMode" + +/** + * Daemon Log Icon + */ +#define DAEMON_MODE_ICON "utilities-terminal" + + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "syslogAnalyzer.h" +#include "daemonConfigurationWidget.h" +#include "daemonConfiguration.h" + +#include "logModeItemBuilder.h" + +class DaemonLogMode : public LogMode { + + Q_OBJECT + +public: + explicit DaemonLogMode(); + + + ~DaemonLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _DAEMON_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/kernel/CMakeLists.txt b/ksystemlog/src/modes/kernel/CMakeLists.txt new file mode 100644 index 00000000..10050553 --- /dev/null +++ b/ksystemlog/src/modes/kernel/CMakeLists.txt @@ -0,0 +1,30 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../config + ${CMAKE_CURRENT_SOURCE_DIR}/../base +) + +set(ksystemlog_kernel_sources + kernelFactory.cpp + kernelAnalyzer.cpp + kernelLogMode.cpp + kernelItemBuilder.cpp +) + +kde4_add_library(ksystemlog_kernel STATIC ${ksystemlog_kernel_sources}) + +add_dependencies( + ksystemlog_kernel + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_kernel + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config +) diff --git a/ksystemlog/src/modes/kernel/kernelAnalyzer.cpp b/ksystemlog/src/modes/kernel/kernelAnalyzer.cpp new file mode 100644 index 00000000..ebbb7d51 --- /dev/null +++ b/ksystemlog/src/modes/kernel/kernelAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "kernelAnalyzer.h" diff --git a/ksystemlog/src/modes/kernel/kernelAnalyzer.h b/ksystemlog/src/modes/kernel/kernelAnalyzer.h new file mode 100644 index 00000000..608e7157 --- /dev/null +++ b/ksystemlog/src/modes/kernel/kernelAnalyzer.h @@ -0,0 +1,166 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _KERNEL_ANALYZER_H_ +#define _KERNEL_ANALYZER_H_ + +#include +#include + +#include + +#include "logging.h" + +#include "analyzer.h" + +#include "localLogFileReader.h" +#include "processOutputLogFileReader.h" +#include "kernelLogMode.h" + +class LogMode; + +class KernelAnalyzer : public Analyzer { + + Q_OBJECT + + public: + + KernelAnalyzer(LogMode* logMode) : + Analyzer(logMode) { + + startupTime(); + } + + virtual ~KernelAnalyzer() { + + } + + LogViewColumns initColumns() { + LogViewColumns columns; + columns.addColumn(LogViewColumn(i18n("Date"), true, false)); + columns.addColumn(LogViewColumn(i18n("Component"), true, false)); + columns.addColumn(LogViewColumn(i18n("Message"), true, false)); + + return columns; + } + + protected: + LogFileReader* createLogFileReader(const LogFile& logFile) { + return new ProcessOutputLogFileReader(logFile); + } + + Analyzer::LogFileSortMode logFileSortMode() { + return Analyzer::AscendingSortedLogFile; + } + + void startupTime() { + QFile file(QLatin1String( UPTIME_FILE )); + + file.open(QIODevice::ReadOnly | QIODevice::Text); + + QTextStream in(&file); + QString line = in.readLine(); + + //Format : 1618.72 1382.98 (uptime / something) + QStringList times = line.split(QLatin1String( " " )); + + QString secondsString = times.at(0); + QString pureSecondsString = secondsString.left(secondsString.indexOf(QLatin1String( "." ))); + long updateSeconds = pureSecondsString.toLong(); + + startupDateTime = QDateTime::currentDateTime().addSecs(- updateSeconds); + logDebug() << "Startup time : " << startupDateTime << endl; + + } + + LogLine* parseMessage(const QString& logLine, const LogFile& originalLogFile) { + QRegExp timeRegex(QLatin1String( "\\[\\ *(\\d*)\\.(\\d*)\\]\\s+(.*)" )); + +// QRegExp componentRegexp(timeRegex + "([^\\s:]{,20})[:\\s\\t]+(.*)"); +// QRegExp messageRegexp(timeRegex + "(.*)"); + + QDateTime dateTime(startupDateTime); + QStringList messages; + + int timeExists = timeRegex.indexIn(logLine); + + //If we have the date, we are able to update the start date + if (timeExists != -1) { + + //logDebug() << componentRegexp.cap(1).toInt() << "and" << componentRegexp.cap(2).toInt() << endl; + dateTime = dateTime.addSecs( timeRegex.cap(1).toInt() ); + dateTime = dateTime.addMSecs( timeRegex.cap(2).toInt()/1000 ); + + parseComponentMessage(timeRegex.cap(3), messages); + + } + //Else, the date will never change + else { + parseComponentMessage(logLine, messages); + } + + /* + logDebug() << "--------------------------------" << endl; + logDebug() << logLine << endl; + logDebug() << "Secs : " << dateTime.time().second() << endl; + logDebug() << "MSec : " << dateTime.time().msec() << endl; + logDebug() << "Comp : " << messages.at(0) << endl; + logDebug() << "Msg : " << messages.at(1) << endl; + logDebug() << "--------------------------------" << endl; + */ + + LogLine* line = new LogLine( + logLineInternalIdGenerator++, + dateTime, + messages, + originalLogFile.url().path(), + Globals::instance()->informationLogLevel(), + logMode + ); + + return line; + } + + inline void parseComponentMessage(const QString& logLine, QStringList& messages) { + QString message(logLine); + QString component; + + int doublePointPosition = message.indexOf(QLatin1String( ":" )); + + //Estimate the max size of a component + if (doublePointPosition != -1 && doublePointPosition < 20) { + component = message.left(doublePointPosition); + //Remove component length + ": " + message = message.remove(0, doublePointPosition +2); + + } + + messages.append( component ); + messages.append( message.simplified() ); + + } + + protected: + QDateTime startupDateTime; + +}; + +#endif diff --git a/ksystemlog/src/modes/kernel/kernelFactory.cpp b/ksystemlog/src/modes/kernel/kernelFactory.cpp new file mode 100644 index 00000000..6cf50fdd --- /dev/null +++ b/ksystemlog/src/modes/kernel/kernelFactory.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "kernelFactory.h" + +#include + + +#include "logMode.h" +#include "logging.h" + +#include "simpleAction.h" +#include "kernelLogMode.h" + +#include "logModeFactory.h" + +QList KernelLogModeFactory::createLogModes() const { + QList logModes; + logModes.append(new KernelLogMode()); + return logModes; +} + +LogModeAction* KernelLogModeFactory::createLogModeAction() const { + LogMode* logMode = Globals::instance()->findLogMode(QLatin1String( KERNEL_LOG_MODE_ID )); + return new SimpleAction(logMode->action(), logMode); +} diff --git a/ksystemlog/src/modes/kernel/kernelFactory.h b/ksystemlog/src/modes/kernel/kernelFactory.h new file mode 100644 index 00000000..3b31535a --- /dev/null +++ b/ksystemlog/src/modes/kernel/kernelFactory.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _KERNEL_FACTORY_H_ +#define _KERNEL_FACTORY_H_ + +#include + +#include "logModeFactory.h" + +class LogModeAction; +class LogMode; + +class KernelLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; + +}; + +#endif // _KERNEL_FACTORY_H_ + diff --git a/ksystemlog/src/modes/kernel/kernelItemBuilder.cpp b/ksystemlog/src/modes/kernel/kernelItemBuilder.cpp new file mode 100644 index 00000000..6bb300a3 --- /dev/null +++ b/ksystemlog/src/modes/kernel/kernelItemBuilder.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "kernelItemBuilder.h" diff --git a/ksystemlog/src/modes/kernel/kernelItemBuilder.h b/ksystemlog/src/modes/kernel/kernelItemBuilder.h new file mode 100644 index 00000000..3d092d9f --- /dev/null +++ b/ksystemlog/src/modes/kernel/kernelItemBuilder.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _KERNEL_ITEM_BUILDER_H_ +#define _KERNEL_ITEM_BUILDER_H_ + +#include + +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +#include "logModeItemBuilder.h" + +class LogLine; + +class KernelItemBuilder : public LogModeItemBuilder { + + public: + KernelItemBuilder() { + + } + + virtual ~KernelItemBuilder() { + + } + + QString createFormattedText(LogLine* line) const { + QString result; + + QListIterator it(line->logItems()); + + result.append(QLatin1String( "" )); + + result.append(labelMessageFormat(i18n("Date:"), formatDate(line->time()))); + result.append(labelMessageFormat(i18n("Level:"), line->logLevel()->name())); + result.append(labelMessageFormat(i18n("Component:"), it.next())); + + result.append(QLatin1String( "
" )); + + return result; + } + +}; + +#endif // _KERNEL_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/modes/kernel/kernelLogMode.cpp b/ksystemlog/src/modes/kernel/kernelLogMode.cpp new file mode 100644 index 00000000..9d58c41d --- /dev/null +++ b/ksystemlog/src/modes/kernel/kernelLogMode.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "kernelLogMode.h" + +#include +#include + +#include +#include + +#include "logging.h" + +#include "logMode.h" + +#include "kernelAnalyzer.h" +#include "kernelItemBuilder.h" + +KernelLogMode::KernelLogMode() : + LogMode(QLatin1String( KERNEL_LOG_MODE_ID ), i18n("Kernel Log"),QLatin1String( KERNEL_MODE_ICON )) { + + d->logModeConfiguration = NULL; + + d->logModeConfigurationWidget = NULL; + + d->itemBuilder = new KernelItemBuilder(); + + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the kernel log.")); + d->action->setWhatsThis(i18n("Displays the kernel log in the current tab. This log is only useful for users who want to know why the Kernel does not detect their hardware or what is the cause of the last kernel panic/oops.")); + +} + +KernelLogMode::~KernelLogMode() { + +} + +Analyzer* KernelLogMode::createAnalyzer() { + return new KernelAnalyzer(this); +} + +QList KernelLogMode::createLogFiles() { + QList logFiles; + logFiles.append(LogFile(KUrl("/bin/dmesg"), Globals::instance()->informationLogLevel())); + return logFiles; +} diff --git a/ksystemlog/src/modes/kernel/kernelLogMode.h b/ksystemlog/src/modes/kernel/kernelLogMode.h new file mode 100644 index 00000000..08923fe5 --- /dev/null +++ b/ksystemlog/src/modes/kernel/kernelLogMode.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _KERNEL_LOG_MODE_H_ +#define _KERNEL_LOG_MODE_H_ + +/** + * Kernel Log Mode Identifier + */ +#define KERNEL_LOG_MODE_ID "kernelLogMode" + +/** + * Kernel Log Icon + */ +#define KERNEL_MODE_ICON "utilities-system-monitor" + +#define UPTIME_FILE "/proc/uptime" + +#include + +#include "logFile.h" +#include "logMode.h" + +class KernelLogMode : public LogMode { + + Q_OBJECT + +public: + explicit KernelLogMode(); + + ~KernelLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _KERNEL_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/open/CMakeLists.txt b/ksystemlog/src/modes/open/CMakeLists.txt new file mode 100644 index 00000000..f6aad5ed --- /dev/null +++ b/ksystemlog/src/modes/open/CMakeLists.txt @@ -0,0 +1,29 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../base + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_BINARY_DIR}/../../config +) + +set(ksystemlog_open_sources + openAnalyzer.cpp + openFactory.cpp + openLogMode.cpp +) + + +kde4_add_library(ksystemlog_open STATIC ${ksystemlog_open_sources}) + +add_dependencies( + ksystemlog_open + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_open + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_config + ksystemlog_base_mode +) diff --git a/ksystemlog/src/modes/open/openAnalyzer.cpp b/ksystemlog/src/modes/open/openAnalyzer.cpp new file mode 100644 index 00000000..2fe6e566 --- /dev/null +++ b/ksystemlog/src/modes/open/openAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "openAnalyzer.h" diff --git a/ksystemlog/src/modes/open/openAnalyzer.h b/ksystemlog/src/modes/open/openAnalyzer.h new file mode 100644 index 00000000..e190ea52 --- /dev/null +++ b/ksystemlog/src/modes/open/openAnalyzer.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _OPEN_ANALYZER_H_ +#define _OPEN_ANALYZER_H_ + + +#include + +#include "syslogAnalyzer.h" + +#include "logging.h" +#include "logFile.h" + +#include "openLogMode.h" + +class OpenAnalyzer : public SyslogAnalyzer { + + Q_OBJECT + + public: + OpenAnalyzer(LogMode* logMode) : + SyslogAnalyzer(logMode) { + + } + + virtual ~OpenAnalyzer() { + + } + +}; + +#endif diff --git a/ksystemlog/src/modes/open/openFactory.cpp b/ksystemlog/src/modes/open/openFactory.cpp new file mode 100644 index 00000000..8a6f6ab6 --- /dev/null +++ b/ksystemlog/src/modes/open/openFactory.cpp @@ -0,0 +1,51 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "openFactory.h" + +#include "logMode.h" +#include "defaults.h" +#include "logFile.h" +#include "logging.h" +#include "ksystemlogConfig.h" + +#include "openLogMode.h" +#include "logModeItemBuilder.h" + + +OpenLogModeFactory::OpenLogModeFactory(QWidget* parent) : + parent(parent) { + +} + +OpenLogModeFactory::~OpenLogModeFactory() { + +} + +QList OpenLogModeFactory::createLogModes() const { + QList logModes; + logModes.append(new OpenLogMode(parent)); + return logModes; +} + +LogModeAction* OpenLogModeFactory::createLogModeAction() const { + return NULL; +} diff --git a/ksystemlog/src/modes/open/openFactory.h b/ksystemlog/src/modes/open/openFactory.h new file mode 100644 index 00000000..86e8d4e7 --- /dev/null +++ b/ksystemlog/src/modes/open/openFactory.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _OPEN_LOG_MODE_FACTORY_H_ +#define _OPEN_LOG_MODE_FACTORY_H_ + + +#include "logModeFactory.h" +#include "logModeConfiguration.h" + +class OpenLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + explicit OpenLogModeFactory(QWidget* parent); + + ~OpenLogModeFactory(); + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; + + private: + + QWidget* parent; +}; + +#endif // _OPEN_LOG_MODE_FACTORY_H_ + diff --git a/ksystemlog/src/modes/open/openLogMode.cpp b/ksystemlog/src/modes/open/openLogMode.cpp new file mode 100644 index 00000000..6a6dfe1a --- /dev/null +++ b/ksystemlog/src/modes/open/openLogMode.cpp @@ -0,0 +1,86 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "openLogMode.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "logModeItemBuilder.h" +#include "openAnalyzer.h" + +OpenLogMode::OpenLogMode(QWidget* parent) : + LogMode(QLatin1String( OPEN_LOG_MODE_ID ), i18n("Log File"),QLatin1String( OPEN_MODE_ICON )), + parent(parent) { + + d->logModeConfiguration = NULL; + + d->logModeConfigurationWidget = NULL; + + d->itemBuilder = new LogModeItemBuilder(); + + d->action = NULL; + +} + +OpenLogMode::~OpenLogMode() { + +} + +Analyzer* OpenLogMode::createAnalyzer() { + return new OpenAnalyzer(this); +} + +QList OpenLogMode::createLogFiles() { + //Open a standard Filedialog + KUrl openingFileName(KFileDialog::getOpenUrl(KUrl(), QString(), parent, i18n("Open Location"))); + logDebug() << "Opening file : " << openingFileName.url() << endl; + + if (openingFileName.isEmpty()) { + return QList(); + } + + if (openingFileName.isValid()) { + LogFile logFile(openingFileName, Globals::instance()->informationLogLevel()); + QList logFiles; + logFiles.append(logFile); + + return logFiles; + + } + + QString message(i18n("Malformed URL. Unable to open this file.")); + KMessageBox::error(parent, message, i18n("Unable to open this file."), KMessageBox::Notify); + + return QList(); +} diff --git a/ksystemlog/src/modes/open/openLogMode.h b/ksystemlog/src/modes/open/openLogMode.h new file mode 100644 index 00000000..f05e0860 --- /dev/null +++ b/ksystemlog/src/modes/open/openLogMode.h @@ -0,0 +1,63 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _OPEN_LOG_MODE_H_ +#define _OPEN_LOG_MODE_H_ + +/** + * Open Log Mode Identifier + */ +#define OPEN_LOG_MODE_ID "openLogMode" + +/** + * System Log Icon + */ +#define OPEN_MODE_ICON "document-open" + + +#include + + +#include "logFile.h" + +#include "logMode.h" + +class QWidget; + +class OpenLogMode : public LogMode { + + Q_OBJECT + +public: + explicit OpenLogMode(QWidget* parent); + + ~OpenLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +private: + QWidget* parent; +}; + +#endif // _OPEN_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/postfix/CMakeLists.txt b/ksystemlog/src/modes/postfix/CMakeLists.txt new file mode 100644 index 00000000..404decb1 --- /dev/null +++ b/ksystemlog/src/modes/postfix/CMakeLists.txt @@ -0,0 +1,33 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../base + ${CMAKE_CURRENT_BINARY_DIR}/../../config + ${CMAKE_CURRENT_BINARY_DIR}/../base +) + +set(ksystemlog_postfix_sources + postfixFactory.cpp + postfixConfigurationWidget.cpp + postfixConfiguration.cpp + postfixAnalyzer.cpp + postfixLogMode.cpp +) + +kde4_add_library(ksystemlog_postfix STATIC ${ksystemlog_postfix_sources}) + +add_dependencies( + ksystemlog_postfix + + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_postfix + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config +) diff --git a/ksystemlog/src/modes/postfix/postfixAnalyzer.cpp b/ksystemlog/src/modes/postfix/postfixAnalyzer.cpp new file mode 100644 index 00000000..1dd93bae --- /dev/null +++ b/ksystemlog/src/modes/postfix/postfixAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "postfixAnalyzer.h" diff --git a/ksystemlog/src/modes/postfix/postfixAnalyzer.h b/ksystemlog/src/modes/postfix/postfixAnalyzer.h new file mode 100644 index 00000000..af9c89f4 --- /dev/null +++ b/ksystemlog/src/modes/postfix/postfixAnalyzer.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _POSTFIX_ANALYZER_H_ +#define _POSTFIX_ANALYZER_H_ + +#include + +#include "syslogAnalyzer.h" + +#include "logMode.h" +#include "logging.h" + +class PostfixAnalyzer : public SyslogAnalyzer { + + Q_OBJECT + + public: + + PostfixAnalyzer(LogMode* logMode) : + SyslogAnalyzer(logMode) { + + } + + virtual ~PostfixAnalyzer() { + + } + + /* + * Just a test of multilines log lines (and it works well !) + */ + /* + LogLine* parseMessage(const QString& logLine, const LogFile& originalFile) { + LogLine* syslogLine = SyslogAnalyzer::parseMessage(logLine, originalFile); + + QStringList items = syslogLine->logItems(); + QString message = items.takeLast(); + + items.append(message + "\n" + message); + + logDebug() << "Coucou" << items.at(items.count()-1) << endl; + + syslogLine->setLogItems(items); + return syslogLine; + } + */ + +}; + +#endif diff --git a/ksystemlog/src/modes/postfix/postfixConfiguration.cpp b/ksystemlog/src/modes/postfix/postfixConfiguration.cpp new file mode 100644 index 00000000..be48b8f9 --- /dev/null +++ b/ksystemlog/src/modes/postfix/postfixConfiguration.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "postfixConfiguration.h" diff --git a/ksystemlog/src/modes/postfix/postfixConfiguration.h b/ksystemlog/src/modes/postfix/postfixConfiguration.h new file mode 100644 index 00000000..cfbd1988 --- /dev/null +++ b/ksystemlog/src/modes/postfix/postfixConfiguration.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _POSTFIX_CONFIGURATION_H_ +#define _POSTFIX_CONFIGURATION_H_ + +#include +#include + +#include "genericConfiguration.h" +#include "globals.h" +#include "defaults.h" + +#include "postfixLogMode.h" + +class PostfixConfiguration : public GenericLogModeConfiguration { + + Q_OBJECT + + public: + PostfixConfiguration() : + GenericLogModeConfiguration( + QLatin1String( POSTFIX_LOG_MODE_ID ), + QStringList() << QLatin1String( "/var/log/mail.log" ) << QLatin1String( "/var/log/mail.info" ) << QLatin1String( "/var/log/mail.warn" ) << QLatin1String( "/var/log/mail.err" ), + QList() << NOTICE_LOG_LEVEL_ID << INFORMATION_LOG_LEVEL_ID << WARNING_LOG_LEVEL_ID << ERROR_LOG_LEVEL_ID + ) { + + } + + virtual ~PostfixConfiguration() { + + } + +}; + +#endif // _POSTFIX_CONFIGURATION_H_ + diff --git a/ksystemlog/src/modes/postfix/postfixConfigurationWidget.cpp b/ksystemlog/src/modes/postfix/postfixConfigurationWidget.cpp new file mode 100644 index 00000000..a5c7accd --- /dev/null +++ b/ksystemlog/src/modes/postfix/postfixConfigurationWidget.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "postfixConfigurationWidget.h" diff --git a/ksystemlog/src/modes/postfix/postfixConfigurationWidget.h b/ksystemlog/src/modes/postfix/postfixConfigurationWidget.h new file mode 100644 index 00000000..80e3db07 --- /dev/null +++ b/ksystemlog/src/modes/postfix/postfixConfigurationWidget.h @@ -0,0 +1,105 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _POSTFIX_CONFIGURATION_WIDGET_H_ +#define _POSTFIX_CONFIGURATION_WIDGET_H_ + +#include "logModeConfigurationWidget.h" + +#include + +#include + +#include "globals.h" +#include "logging.h" + +#include "logLevelFileList.h" + +#include "logLevel.h" + +#include "postfixConfiguration.h" + +#include "postfixLogMode.h" + +class PostfixConfigurationWidget : public LogModeConfigurationWidget { + + Q_OBJECT + + public: + PostfixConfigurationWidget() : + LogModeConfigurationWidget(i18n("Postfix Log"),QLatin1String( POSTFIX_MODE_ICON ), i18n("Postfix Log")) + { + + QVBoxLayout* layout = new QVBoxLayout(); + this->setLayout(layout); + + QString description = i18n("

These files will be analyzed to show the Postfix Logs.

"); + + fileList = new LogLevelFileList(this, description); + + connect(fileList, SIGNAL(fileListChanged()), this, SIGNAL(configurationChanged())); + + layout->addWidget(fileList); + + } + + virtual ~PostfixConfigurationWidget() { + + } + + bool isValid() const { + if (fileList->isEmpty() == false) { + logDebug() << "Postfix configuration valid" << endl; + return true; + } + + logDebug() << "Postfix configuration not valid" << endl; + return false; + } + + void saveConfig() { + logDebug() << "Saving config from Postfix Options..." << endl; + + PostfixConfiguration* configuration = Globals::instance()->findLogMode(QLatin1String( POSTFIX_LOG_MODE_ID ))->logModeConfiguration(); + configuration->setLogFilesPaths(fileList->paths()); + configuration->setLogFilesLevels(fileList->levels()); + } + + void readConfig() { + PostfixConfiguration* configuration = Globals::instance()->findLogMode(QLatin1String( POSTFIX_LOG_MODE_ID ))->logModeConfiguration(); + + fileList->removeAllItems(); + + fileList->addPaths(configuration->logFilesPaths(), configuration->logFilesLevels()); + } + + void defaultConfig() { + //TODO Find a way to read the configuration per default + readConfig(); + } + + private: + + LogLevelFileList* fileList; + +}; + +#endif // _POSTFIX_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/modes/postfix/postfixFactory.cpp b/ksystemlog/src/modes/postfix/postfixFactory.cpp new file mode 100644 index 00000000..d2329183 --- /dev/null +++ b/ksystemlog/src/modes/postfix/postfixFactory.cpp @@ -0,0 +1,49 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "postfixFactory.h" + +#include + + +#include "logMode.h" +#include "logging.h" + +#include "simpleAction.h" +#include "postfixLogMode.h" + +#include "logModeFactory.h" + +QList PostfixLogModeFactory::createLogModes() const { + QList logModes; + logModes.append(new PostfixLogMode()); + return logModes; +} + +LogModeAction* PostfixLogModeFactory::createLogModeAction() const { + LogMode* logMode = Globals::instance()->findLogMode(QLatin1String( POSTFIX_LOG_MODE_ID )); + SimpleAction* logModeAction = new SimpleAction(logMode->action(), logMode); + + logModeAction->setInToolBar(false); + logModeAction->setCategory(LogModeAction::ServicesCategory); + + return logModeAction; +} diff --git a/ksystemlog/src/modes/postfix/postfixFactory.h b/ksystemlog/src/modes/postfix/postfixFactory.h new file mode 100644 index 00000000..57493104 --- /dev/null +++ b/ksystemlog/src/modes/postfix/postfixFactory.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _POSTFIX_FACTORY_H_ +#define _POSTFIX_FACTORY_H_ + +#include + +#include "logModeFactory.h" + +class LogModeAction; +class LogMode; + +class PostfixLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; + +}; + +#endif // _POSTFIX_FACTORY_H_ + diff --git a/ksystemlog/src/modes/postfix/postfixLogMode.cpp b/ksystemlog/src/modes/postfix/postfixLogMode.cpp new file mode 100644 index 00000000..17fdc3eb --- /dev/null +++ b/ksystemlog/src/modes/postfix/postfixLogMode.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "postfixLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "postfixAnalyzer.h" +#include "postfixConfigurationWidget.h" +#include "postfixConfiguration.h" + +#include "logModeItemBuilder.h" + +PostfixLogMode::PostfixLogMode() : + LogMode(QLatin1String( POSTFIX_LOG_MODE_ID ), i18n("Postfix Log"),QLatin1String( POSTFIX_MODE_ICON )) { + + d->logModeConfiguration = new PostfixConfiguration(); + + d->logModeConfigurationWidget = new PostfixConfigurationWidget(); + + d->itemBuilder = new LogModeItemBuilder(); + + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the Postfix log.")); + d->action->setWhatsThis(i18n("Displays the Postfix log in the current tab. Postfix is the most known and used mail server in the Linux world.")); + +} + +PostfixLogMode::~PostfixLogMode() { + +} + +Analyzer* PostfixLogMode::createAnalyzer() { + return new PostfixAnalyzer(this); +} + +QList PostfixLogMode::createLogFiles() { + return logModeConfiguration()->findGenericLogFiles(); +} diff --git a/ksystemlog/src/modes/postfix/postfixLogMode.h b/ksystemlog/src/modes/postfix/postfixLogMode.h new file mode 100644 index 00000000..8981f368 --- /dev/null +++ b/ksystemlog/src/modes/postfix/postfixLogMode.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _POSTFIX_LOG_MODE_H_ +#define _POSTFIX_LOG_MODE_H_ + +/** + * Postfix Log Mode Identifier + */ +#define POSTFIX_LOG_MODE_ID "postfixLogMode" + +/** + * Postfix Log Icon + */ +#define POSTFIX_MODE_ICON "mail-message" + +#include + +#include "logFile.h" +#include "logMode.h" + +class PostfixLogMode : public LogMode { + + Q_OBJECT + +public: + explicit PostfixLogMode(); + + ~PostfixLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _POSTFIX_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/samba/CMakeLists.txt b/ksystemlog/src/modes/samba/CMakeLists.txt new file mode 100644 index 00000000..6d777aa7 --- /dev/null +++ b/ksystemlog/src/modes/samba/CMakeLists.txt @@ -0,0 +1,37 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../config + ${CMAKE_CURRENT_SOURCE_DIR}/../base + ${CMAKE_CURRENT_BINARY_DIR}/../../config + ${CMAKE_CURRENT_BINARY_DIR}/../base +) + +set(ksystemlog_samba_sources + sambaConfiguration.cpp + sambaConfigurationWidget.cpp + sambaAnalyzer.cpp + sambaLogMode.cpp + sambaItemBuilder.cpp + sambaAccessLogMode.cpp + netbiosLogMode.cpp + sambaFactory.cpp + +) + +kde4_add_library(ksystemlog_samba STATIC ${ksystemlog_samba_sources}) + +add_dependencies( + ksystemlog_samba + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_samba + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config +) diff --git a/ksystemlog/src/modes/samba/netbiosLogMode.cpp b/ksystemlog/src/modes/samba/netbiosLogMode.cpp new file mode 100644 index 00000000..8fabf63d --- /dev/null +++ b/ksystemlog/src/modes/samba/netbiosLogMode.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "netbiosLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "sambaAnalyzer.h" +#include "sambaItemBuilder.h" +#include "sambaConfigurationWidget.h" +#include "sambaConfiguration.h" + + +NetbiosLogMode::NetbiosLogMode(SambaConfiguration* sambaConfiguration, SambaConfigurationWidget* sambaConfigurationWidget, SambaItemBuilder* itemBuilder) : + LogMode(QLatin1String( NETBIOS_LOG_MODE_ID ), i18n("Netbios Log"), QLatin1String( NETBIOS_MODE_ICON )) { + + d->logModeConfiguration = sambaConfiguration; + d->logModeConfigurationWidget = sambaConfigurationWidget; + d->itemBuilder = itemBuilder; + + //Netbios Log Action + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the Netbios log.")); + d->action->setWhatsThis(i18n("Displays the Netbios log in the current tab. Netbios is the file sharing protocol developed by Microsoft.")); + +} + +NetbiosLogMode::~NetbiosLogMode() { + +} + +Analyzer* NetbiosLogMode::createAnalyzer() { + return new SambaAnalyzer(this); +} + +QList NetbiosLogMode::createLogFiles() { + SambaConfiguration* sambaConfiguration = logModeConfiguration(); + return sambaConfiguration->findNoModeLogFiles(sambaConfiguration->netbiosPaths()); +} diff --git a/ksystemlog/src/modes/samba/netbiosLogMode.h b/ksystemlog/src/modes/samba/netbiosLogMode.h new file mode 100644 index 00000000..af3f6cf5 --- /dev/null +++ b/ksystemlog/src/modes/samba/netbiosLogMode.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _NETBIOS_LOG_MODE_H_ +#define _NETBIOS_LOG_MODE_H_ + +/** + * Netbios Log Mode Identifier + */ +#define NETBIOS_LOG_MODE_ID "netbiosLogMode" + +/** + * Netbios Log Icon + */ +#define NETBIOS_MODE_ICON "folder-remote" + + +#include + +#include "logFile.h" + +#include "logMode.h" + +class SambaConfiguration; +class SambaConfigurationWidget; +class SambaItemBuilder; + +class NetbiosLogMode : public LogMode { + + Q_OBJECT + +public: + explicit NetbiosLogMode(SambaConfiguration* sambaConfiguration, SambaConfigurationWidget* sambaConfigurationWidget, SambaItemBuilder* itemBuilder); + + ~NetbiosLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _NETBIOS_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/samba/sambaAccessLogMode.cpp b/ksystemlog/src/modes/samba/sambaAccessLogMode.cpp new file mode 100644 index 00000000..ba6f4a5b --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaAccessLogMode.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "sambaAccessLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "sambaAnalyzer.h" +#include "sambaItemBuilder.h" +#include "sambaConfigurationWidget.h" +#include "sambaConfiguration.h" + + +SambaAccessLogMode::SambaAccessLogMode(SambaConfiguration* sambaConfiguration, SambaConfigurationWidget* sambaConfigurationWidget, SambaItemBuilder* itemBuilder) : + LogMode(QLatin1String( SAMBA_ACCESS_LOG_MODE_ID ), i18n("Samba Access Log"),QLatin1String( SAMBA_ACCESS_MODE_ICON )) { + + d->logModeConfiguration = sambaConfiguration; + d->logModeConfigurationWidget = sambaConfigurationWidget; + d->itemBuilder = itemBuilder; + + //Samba Log Action + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the Samba Access log.")); + d->action->setWhatsThis(i18n("Displays the Samba Access log in the current tab. This log mode allows you to see connections between your shares and remote hosts.")); + +} + +SambaAccessLogMode::~SambaAccessLogMode() { + +} + +Analyzer* SambaAccessLogMode::createAnalyzer() { + return new SambaAnalyzer(this); +} + +QList SambaAccessLogMode::createLogFiles() { + SambaConfiguration* sambaConfiguration = logModeConfiguration(); + return sambaConfiguration->findNoModeLogFiles(sambaConfiguration->sambaAccessPaths()); +} diff --git a/ksystemlog/src/modes/samba/sambaAccessLogMode.h b/ksystemlog/src/modes/samba/sambaAccessLogMode.h new file mode 100644 index 00000000..16cee319 --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaAccessLogMode.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SAMBA_ACCESS_LOG_MODE_H_ +#define _SAMBA_ACCESS_LOG_MODE_H_ + +/** + * Samba Access Log Mode Identifier + */ +#define SAMBA_ACCESS_LOG_MODE_ID "sambaAccessLogMode" + +/** + * Samba Access Log Icon + */ +#define SAMBA_ACCESS_MODE_ICON "network-workgroup" + + +#include + +#include "logFile.h" + +#include "logMode.h" + +class SambaConfiguration; +class SambaConfigurationWidget; +class SambaItemBuilder; + +class SambaAccessLogMode : public LogMode { + + Q_OBJECT + +public: + explicit SambaAccessLogMode(SambaConfiguration* sambaConfiguration, SambaConfigurationWidget* sambaConfigurationWidget, SambaItemBuilder* itemBuilder); + + ~SambaAccessLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _SAMBA_ACCESS_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/samba/sambaAnalyzer.cpp b/ksystemlog/src/modes/samba/sambaAnalyzer.cpp new file mode 100644 index 00000000..59bcaa46 --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "sambaAnalyzer.h" diff --git a/ksystemlog/src/modes/samba/sambaAnalyzer.h b/ksystemlog/src/modes/samba/sambaAnalyzer.h new file mode 100644 index 00000000..2ba4d435 --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaAnalyzer.h @@ -0,0 +1,189 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SAMBA_ANALYZER_H_ +#define _SAMBA_ANALYZER_H_ + + +#include + +#include "analyzer.h" + +#include "logging.h" + +#include "localLogFileReader.h" +#include "sambaLogMode.h" +#include "parsingHelper.h" + +class SambaAnalyzer : public Analyzer { + + Q_OBJECT + + public: + explicit SambaAnalyzer(LogMode* logMode) : + Analyzer(logMode) { + + currentLogLine = NULL; + } + + virtual ~SambaAnalyzer() { + + } + + LogViewColumns initColumns() { + LogViewColumns columns; + + columns.addColumn(LogViewColumn(i18n("Date"), true, false)); + columns.addColumn(LogViewColumn(i18n("Source File"), true, true)); + columns.addColumn(LogViewColumn(i18n("Function"), true, true)); + columns.addColumn(LogViewColumn(i18n("Line"), true, true)); + columns.addColumn(LogViewColumn(i18n("Message"), true, false)); + + return columns; + } + + + + protected: + LogFileReader* createLogFileReader(const LogFile& logFile) { + return new LocalLogFileReader(logFile); + } + + Analyzer::LogFileSortMode logFileSortMode() { + return Analyzer::AscendingSortedLogFile; + } + + + /* + * Log line examples : + * [2005/06/27 21:06:01, 0] nmbd/nmbd.c:main(668) + * Netbios nameserver version 3.0.14a started. + * Copyright Andrew Tridgell and the Samba Team 1994-2004 + * [2005/06/27 21:11:46, 0] nmbd/nmbd_become_lmb.c:become_local_master_stage2(396) + * ***** + * Samba name server STEAKHACHE is now a local master browser for workgroup MAISON on subnet 192.168.1.33 + * + * ***** + * [2005/06/28 06:41:03, 0] nmbd/nmbd.c:terminate(56) + * Got SIGTERM: going down... + * [2005/06/28 18:08:11, 0] nmbd/nmbd.c:main(668) + * Netbios nameserver version 3.0.14a started. + * Copyright Andrew Tridgell and the Samba Team 1994-2004 + * + * Note: + * This analyzer nevers return the last line of a log file because it's never sure + * that the last file line is the last message of the current log line. + * So the previous last line will be returned at the next file update, + */ + LogLine* parseMessage(const QString& logLine, const LogFile& originalLogFile) { + QString line(logLine); + + + //The Date + int dateBegin=line.indexOf(QLatin1String( "[" )); + int dateEnd=line.indexOf(QLatin1String( "]" )); + + if (dateBegin != -1) { + + QString strDate=line.mid(dateBegin+1, dateEnd-dateBegin-1); + + QString year=strDate.mid(0, 4); + QString month=strDate.mid(5, 2); + QString day=strDate.mid(8, 2); + + QString hour=strDate.mid(11, 2); + QString min=strDate.mid(14, 2); + QString sec=strDate.mid(17, 2); + + QDate date=QDate(year.toInt(), month.toInt(), day.toInt()); + QTime time=QTime(hour.toInt(), min.toInt(), sec.toInt()); + + line=line.remove(0, dateEnd+2); + + + //The source file + int doubleDot; + doubleDot=line.indexOf(QLatin1String( ":" )); + QString file=line.left(doubleDot); + line=line.remove(0, doubleDot+1); + + //The function + int bracket=line.indexOf(QLatin1String( "(" )); + QString function=line.left(bracket); + line=line.remove(0, bracket+1); + + //The line number + bracket=line.indexOf(QLatin1String( ")" )); + QString lineNumber=line.left(bracket); + + //Remove the first return character and the two useless space of the first message line + line=line.remove(0, bracket+4); + + QStringList list; + list.append(file); + list.append(function); + list.append(lineNumber); + + logDebug() << "Creating new line " << endl; + + LogLine* returnedLogLine = currentLogLine; + + currentLogLine = new LogLine( + logLineInternalIdGenerator++, + QDateTime(date, time), + list, + originalLogFile.url().path(), + Globals::instance()->informationLogLevel(), + logMode + ); + + return returnedLogLine; + } + + if (line.indexOf(QLatin1String( " " )) != -1) { + if (currentLogLine != NULL) { + + QStringList list = currentLogLine->logItems(); + + + //A line has already been added + if (list.count() == 4) { + QString currentMessage = list.takeLast(); + list.append(currentMessage + QLatin1String( "\n" ) + line.simplified()); + } + //First time we add a line for the current Log line + else { + list.append(line.simplified()); + } + + currentLogLine->setLogItems(list); + + } + } + + return NULL; + } + + LogLine* currentLogLine; + +}; + +#endif // _SAMBA_ANALYZER_H_ diff --git a/ksystemlog/src/modes/samba/sambaConfiguration.cpp b/ksystemlog/src/modes/samba/sambaConfiguration.cpp new file mode 100644 index 00000000..93621323 --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaConfiguration.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "sambaConfiguration.h" diff --git a/ksystemlog/src/modes/samba/sambaConfiguration.h b/ksystemlog/src/modes/samba/sambaConfiguration.h new file mode 100644 index 00000000..ed2f91ed --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaConfiguration.h @@ -0,0 +1,101 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SAMBA_CONFIGURATION_H_ +#define _SAMBA_CONFIGURATION_H_ + +#include + +#include "logModeConfiguration.h" + +#include "logging.h" +#include "defaults.h" + +#include "sambaLogMode.h" + +#include "ksystemlogConfig.h" + +class SambaConfigurationPrivate { +public: + QStringList sambaPaths; + + QStringList sambaAccessPaths; + + QStringList netbiosPaths; +}; + +class SambaConfiguration : public LogModeConfiguration { + + Q_OBJECT + + public: + SambaConfiguration() : + d(new SambaConfigurationPrivate()) { + + configuration->setCurrentGroup(QLatin1String( "SambaLogMode" )); + + QStringList defaultSambaPaths; + defaultSambaPaths << QLatin1String( "/var/log/samba/log.smbd" ); + configuration->addItemStringList(QLatin1String( "SambaLogFilesPaths" ), d->sambaPaths, defaultSambaPaths, QLatin1String( "SambaLogFilesPaths" )); + + QStringList defaultSambaAccessPaths; + defaultSambaAccessPaths << QLatin1String( "/var/log/samba/log.localhost" ) << QLatin1String( "/var/log/samba/log.127.0.0.1" ); + configuration->addItemStringList(QLatin1String( "SambaAccessLogFilesPaths" ), d->sambaAccessPaths, defaultSambaAccessPaths, QLatin1String( "SambaAccessLogFilesPaths" )); + + QStringList defaultNetbiosPaths; + defaultNetbiosPaths << QLatin1String( "/var/log/samba/log.nmbd" ); + configuration->addItemStringList(QLatin1String( "NetbiosLogFilesPaths" ), d->netbiosPaths, defaultNetbiosPaths, QLatin1String( "NetbiosLogFilesPaths" )); + } + + virtual ~SambaConfiguration() { + delete d; + } + + QStringList sambaPaths() const { + return d->sambaPaths; + } + + QStringList sambaAccessPaths() const { + return d->sambaAccessPaths; + } + + QStringList netbiosPaths() const { + return d->netbiosPaths; + } + + void setSambaPaths(const QStringList& sambaPaths) { + d->sambaPaths = sambaPaths; + } + + void setNetbiosPaths(const QStringList& netbiosPaths) { + d->netbiosPaths = netbiosPaths; + } + + void setSambaAccessPaths(const QStringList& sambaAccessPaths) { + d->sambaAccessPaths = sambaAccessPaths; + } + + private: + SambaConfigurationPrivate* const d; + +}; + +#endif // _SAMBA_CONFIGURATION_H_ diff --git a/ksystemlog/src/modes/samba/sambaConfigurationWidget.cpp b/ksystemlog/src/modes/samba/sambaConfigurationWidget.cpp new file mode 100644 index 00000000..f041c095 --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaConfigurationWidget.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KApacheLog, a apache log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "sambaConfigurationWidget.h" diff --git a/ksystemlog/src/modes/samba/sambaConfigurationWidget.h b/ksystemlog/src/modes/samba/sambaConfigurationWidget.h new file mode 100644 index 00000000..b6ccba09 --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaConfigurationWidget.h @@ -0,0 +1,115 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SAMBA_CONFIGURATION_WIDGET_H_ +#define _SAMBA_CONFIGURATION_WIDGET_H_ + +#include "logModeConfigurationWidget.h" + + +#include + +#include "globals.h" +#include "logging.h" +#include "multipleFileList.h" + +#include "logLevel.h" + +#include "sambaConfiguration.h" +#include "sambaLogMode.h" + +class SambaConfigurationWidget : public LogModeConfigurationWidget { + + Q_OBJECT + + public: + SambaConfigurationWidget() : + LogModeConfigurationWidget(i18n("Samba Log"),QLatin1String( SAMBA_MODE_ICON ), i18n("Samba Log")) + { + + QHBoxLayout* layout = new QHBoxLayout(); + this->setLayout(layout); + + sambaFileList=new MultipleFileList(this, i18n("

These files will be analyzed to show Samba log, Samba Access log and Netbios log.

")); + + sambaPathsId = sambaFileList->addCategory(i18n("Samba Log Files"), i18n("Add Samba File...")); + sambaAccessPathsId = sambaFileList->addCategory(i18n("Samba Access Log Files"), i18n("Add Samba Access File...")); + netbiosPathsId = sambaFileList->addCategory(i18n("Netbios Log Files"), i18n("Add Netbios File...")); + + connect(sambaFileList, SIGNAL(fileListChanged()), this, SIGNAL(configurationChanged())); + + layout->addWidget(sambaFileList); + } + + ~SambaConfigurationWidget() { + + } + + + public slots: + + void saveConfig() { + logDebug() << "Saving config from Samba Options..." << endl; + + SambaConfiguration* sambaConfiguration = Globals::instance()->findLogMode(QLatin1String( SAMBA_LOG_MODE_ID ))->logModeConfiguration(); + sambaConfiguration->setSambaPaths(sambaFileList->paths(sambaPathsId)); + sambaConfiguration->setSambaAccessPaths(sambaFileList->paths(sambaAccessPathsId)); + sambaConfiguration->setNetbiosPaths(sambaFileList->paths(netbiosPathsId)); + } + + void defaultConfig() { + //TODO Find a way to read the configuration per default + readConfig(); + } + + void readConfig() { + SambaConfiguration* sambaConfiguration = Globals::instance()->findLogMode(QLatin1String( SAMBA_LOG_MODE_ID ))->logModeConfiguration(); + + sambaFileList->removeAllItems(); + + sambaFileList->addPaths(sambaPathsId, sambaConfiguration->sambaPaths()); + sambaFileList->addPaths(sambaAccessPathsId, sambaConfiguration->sambaAccessPaths()); + sambaFileList->addPaths(netbiosPathsId, sambaConfiguration->netbiosPaths()); + } + + protected: + bool isValid() const { + if (sambaFileList->isOneOfCategoryEmpty()==true) { + logDebug() << "Samba configuration not valid" << endl; + return false; + } + + logDebug() << "Samba configuration valid" << endl; + return true; + + } + + private: + + MultipleFileList* sambaFileList; + + int sambaPathsId; + int sambaAccessPathsId; + int netbiosPathsId; + +}; + +#endif // _SAMBA_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/modes/samba/sambaFactory.cpp b/ksystemlog/src/modes/samba/sambaFactory.cpp new file mode 100644 index 00000000..f7908d19 --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaFactory.cpp @@ -0,0 +1,67 @@ +/*************************************************************************** + * KSambaLog, a samba log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "sambaFactory.h" + + +#include + +#include "multipleActions.h" +#include "logMode.h" +#include "logging.h" + +#include "sambaLogMode.h" +#include "sambaAccessLogMode.h" +#include "netbiosLogMode.h" + +#include "sambaConfigurationWidget.h" +#include "sambaConfiguration.h" +#include "sambaItemBuilder.h" + +QList SambaLogModeFactory::createLogModes() const { + + //Create the shared configuration and configuration widget between the logModes + + SambaConfiguration* logModeConfiguration = new SambaConfiguration(); + SambaConfigurationWidget* logModeConfigurationWidget = new SambaConfigurationWidget(); + SambaItemBuilder* itemBuilder = new SambaItemBuilder(); + + QList logModes; + logModes.append(new SambaLogMode(logModeConfiguration, logModeConfigurationWidget, itemBuilder)); + logModes.append(new SambaAccessLogMode(logModeConfiguration, logModeConfigurationWidget, itemBuilder)); + logModes.append(new NetbiosLogMode(logModeConfiguration, logModeConfigurationWidget, itemBuilder)); + + return logModes; +} + +LogModeAction* SambaLogModeFactory::createLogModeAction() const { + LogMode* sambaLogMode = Globals::instance()->findLogMode(QLatin1String( SAMBA_LOG_MODE_ID )); + + MultipleActions* multipleActions = new MultipleActions(KIcon( QLatin1String( SAMBA_MODE_ICON) ), i18n("Samba"), sambaLogMode); + multipleActions->addInnerAction(sambaLogMode->action()); + multipleActions->addInnerAction(Globals::instance()->findLogMode(QLatin1String( SAMBA_ACCESS_LOG_MODE_ID ))->action()); + multipleActions->addInnerAction(Globals::instance()->findLogMode(QLatin1String( NETBIOS_LOG_MODE_ID ))->action()); + + multipleActions->setInToolBar(false); + multipleActions->setCategory(LogModeAction::ServicesCategory); + + return multipleActions; +} diff --git a/ksystemlog/src/modes/samba/sambaFactory.h b/ksystemlog/src/modes/samba/sambaFactory.h new file mode 100644 index 00000000..ab140b45 --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaFactory.h @@ -0,0 +1,41 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SAMBA_LOG_MODE_FACTORY_H_ +#define _SAMBA_LOG_MODE_FACTORY_H_ + +#include + +#include "logModeFactory.h" + +class SambaLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; +}; + +#endif // _SAMBA_LOG_MODE_FACTORY_H_ + diff --git a/ksystemlog/src/modes/samba/sambaItemBuilder.cpp b/ksystemlog/src/modes/samba/sambaItemBuilder.cpp new file mode 100644 index 00000000..e0b218db --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaItemBuilder.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "sambaItemBuilder.h" diff --git a/ksystemlog/src/modes/samba/sambaItemBuilder.h b/ksystemlog/src/modes/samba/sambaItemBuilder.h new file mode 100644 index 00000000..7410e5b7 --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaItemBuilder.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SAMBA_ITEM_BUILDER_H_ +#define _SAMBA_ITEM_BUILDER_H_ + +#include + +#include "logModeItemBuilder.h" + +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +class SambaItemBuilder : public LogModeItemBuilder { + + public: + SambaItemBuilder() { + + } + + virtual ~SambaItemBuilder() { + + } + + QString createFormattedText(LogLine* line) const { + QString result; + + QListIterator it(line->logItems()); + + result.append(QLatin1String( "" )); + + result.append(labelMessageFormat(i18n("Date:"), formatDate(line->time()))); + result.append(labelMessageFormat(i18n("Level:"), line->logLevel()->name())); + result.append(labelMessageFormat(i18n("Source File:"), it.next() )); + result.append(labelMessageFormat(i18n("Function:"), it.next() )); + result.append(labelMessageFormat(i18n("Line:"), it.next() )); + + result.append(QLatin1String( "
" )); + + return result; + + } +}; + + +#endif // _SAMBA_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/modes/samba/sambaLogMode.cpp b/ksystemlog/src/modes/samba/sambaLogMode.cpp new file mode 100644 index 00000000..950c0697 --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaLogMode.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "sambaLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "sambaAnalyzer.h" +#include "sambaItemBuilder.h" +#include "sambaConfigurationWidget.h" +#include "sambaConfiguration.h" + + +SambaLogMode::SambaLogMode(SambaConfiguration* sambaConfiguration, SambaConfigurationWidget* sambaConfigurationWidget, SambaItemBuilder* itemBuilder) : + LogMode(QLatin1String( SAMBA_LOG_MODE_ID ), i18n("Samba Log"),QLatin1String( SAMBA_MODE_ICON )) { + + d->logModeConfiguration = sambaConfiguration; + d->logModeConfigurationWidget = sambaConfigurationWidget; + d->itemBuilder = itemBuilder; + + //Samba Log Action + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the Samba log.")); + d->action->setWhatsThis(i18n("Displays the Samba log in the current tab. Samba is the file sharing server which interacts with Microsoft Windows network.")); + +} + +SambaLogMode::~SambaLogMode() { + +} + +Analyzer* SambaLogMode::createAnalyzer() { + return new SambaAnalyzer(this); +} + +QList SambaLogMode::createLogFiles() { + SambaConfiguration* sambaConfiguration = logModeConfiguration(); + return sambaConfiguration->findNoModeLogFiles(sambaConfiguration->sambaPaths()); +} diff --git a/ksystemlog/src/modes/samba/sambaLogMode.h b/ksystemlog/src/modes/samba/sambaLogMode.h new file mode 100644 index 00000000..35bfabe7 --- /dev/null +++ b/ksystemlog/src/modes/samba/sambaLogMode.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SAMBA_LOG_MODE_H_ +#define _SAMBA_LOG_MODE_H_ + +/** + * Samba Log Mode Identifier + */ +#define SAMBA_LOG_MODE_ID "sambaLogMode" + +/** + * Samba Log Icon + */ +#define SAMBA_MODE_ICON "folder-remote" + + +#include + +#include "logFile.h" + +#include "logMode.h" + +class SambaConfiguration; +class SambaConfigurationWidget; +class SambaItemBuilder; + +class SambaLogMode : public LogMode { + + Q_OBJECT + +public: + explicit SambaLogMode(SambaConfiguration* sambaConfiguration, SambaConfigurationWidget* sambaConfigurationWidget, SambaItemBuilder* itemBuilder); + + ~SambaLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _SAMBA_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/system/CMakeLists.txt b/ksystemlog/src/modes/system/CMakeLists.txt new file mode 100644 index 00000000..00c11d56 --- /dev/null +++ b/ksystemlog/src/modes/system/CMakeLists.txt @@ -0,0 +1,33 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_BINARY_DIR}/../../config + ${CMAKE_CURRENT_SOURCE_DIR}/../base + ${CMAKE_CURRENT_BINARY_DIR}/../base +) + +set(ksystemlog_system_sources + systemFactory.cpp + systemConfigurationWidget.cpp + systemConfiguration.cpp + systemAnalyzer.cpp + systemLogMode.cpp +) + +kde4_add_library(ksystemlog_system STATIC ${ksystemlog_system_sources}) + +add_dependencies( + ksystemlog_system + + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_system + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config +) diff --git a/ksystemlog/src/modes/system/systemAnalyzer.cpp b/ksystemlog/src/modes/system/systemAnalyzer.cpp new file mode 100644 index 00000000..44561694 --- /dev/null +++ b/ksystemlog/src/modes/system/systemAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "systemAnalyzer.h" diff --git a/ksystemlog/src/modes/system/systemAnalyzer.h b/ksystemlog/src/modes/system/systemAnalyzer.h new file mode 100644 index 00000000..3e5a4365 --- /dev/null +++ b/ksystemlog/src/modes/system/systemAnalyzer.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SYSTEM_ANALYZER_H_ +#define _SYSTEM_ANALYZER_H_ + +#include + +#include "syslogAnalyzer.h" + +#include "systemLogMode.h" + +class SystemAnalyzer : public SyslogAnalyzer { + + Q_OBJECT + + public: + + SystemAnalyzer(LogMode* logMode) : + SyslogAnalyzer(logMode) { + + } + + virtual ~SystemAnalyzer() { + + } + +}; + +#endif diff --git a/ksystemlog/src/modes/system/systemConfiguration.cpp b/ksystemlog/src/modes/system/systemConfiguration.cpp new file mode 100644 index 00000000..ef2fe02a --- /dev/null +++ b/ksystemlog/src/modes/system/systemConfiguration.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "systemConfiguration.h" diff --git a/ksystemlog/src/modes/system/systemConfiguration.h b/ksystemlog/src/modes/system/systemConfiguration.h new file mode 100644 index 00000000..67260e92 --- /dev/null +++ b/ksystemlog/src/modes/system/systemConfiguration.h @@ -0,0 +1,60 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SYSTEM_CONFIGURATION_H_ +#define _SYSTEM_CONFIGURATION_H_ + +#include +#include + +#include "genericConfiguration.h" +#include "globals.h" +#include "defaults.h" + +#include "systemLogMode.h" + +class SystemConfiguration : public GenericLogModeConfiguration { + + Q_OBJECT + + public: + SystemConfiguration() : + GenericLogModeConfiguration( + QLatin1String( SYSTEM_LOG_MODE_ID ), + QStringList() << QLatin1String( "/var/log/syslog" ), + QList() << INFORMATION_LOG_LEVEL_ID + ) { + + /* + /var/log/messages,/var/log/syslog,/var/log/debug + 2,2,1 + */ + + } + + virtual ~SystemConfiguration() { + + } + +}; + +#endif // _SYSTEM_CONFIGURATION_H_ + diff --git a/ksystemlog/src/modes/system/systemConfigurationWidget.cpp b/ksystemlog/src/modes/system/systemConfigurationWidget.cpp new file mode 100644 index 00000000..18e77a87 --- /dev/null +++ b/ksystemlog/src/modes/system/systemConfigurationWidget.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "systemConfigurationWidget.h" diff --git a/ksystemlog/src/modes/system/systemConfigurationWidget.h b/ksystemlog/src/modes/system/systemConfigurationWidget.h new file mode 100644 index 00000000..eb984689 --- /dev/null +++ b/ksystemlog/src/modes/system/systemConfigurationWidget.h @@ -0,0 +1,105 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SYSTEM_CONFIGURATION_WIDGET_H_ +#define _SYSTEM_CONFIGURATION_WIDGET_H_ + +#include "logModeConfigurationWidget.h" + +#include + +#include + +#include "globals.h" +#include "logging.h" + +#include "logLevelFileList.h" + +#include "logLevel.h" + +#include "systemConfiguration.h" + +#include "systemLogMode.h" + +class SystemConfigurationWidget : public LogModeConfigurationWidget { + + Q_OBJECT + + public: + SystemConfigurationWidget() : + LogModeConfigurationWidget(i18n("System Log"),QLatin1String( SYSTEM_MODE_ICON ), i18n("System Log")) + { + + QVBoxLayout* layout = new QVBoxLayout(); + this->setLayout(layout); + + QString description = i18n("

These files will be analyzed to show the System logs.

"); + + fileList = new LogLevelFileList(this, description); + + connect(fileList, SIGNAL(fileListChanged()), this, SIGNAL(configurationChanged())); + + layout->addWidget(fileList); + + } + + virtual ~SystemConfigurationWidget() { + + } + + bool isValid() const { + if (fileList->isEmpty() == false) { + logDebug() << "System configuration valid" << endl; + return true; + } + + logDebug() << "System configuration not valid" << endl; + return false; + } + + void saveConfig() { + logDebug() << "Saving config from System Options..." << endl; + + SystemConfiguration* systemConfiguration = Globals::instance()->findLogMode(QLatin1String( SYSTEM_LOG_MODE_ID ))->logModeConfiguration(); + systemConfiguration->setLogFilesPaths(fileList->paths()); + systemConfiguration->setLogFilesLevels(fileList->levels()); + } + + void readConfig() { + SystemConfiguration* systemConfiguration = Globals::instance()->findLogMode(QLatin1String( SYSTEM_LOG_MODE_ID ))->logModeConfiguration(); + + fileList->removeAllItems(); + + fileList->addPaths(systemConfiguration->logFilesPaths(), systemConfiguration->logFilesLevels()); + } + + void defaultConfig() { + //TODO Find a way to read the configuration per default + readConfig(); + } + + private: + + LogLevelFileList* fileList; + +}; + +#endif // _SYSTEM_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/modes/system/systemFactory.cpp b/ksystemlog/src/modes/system/systemFactory.cpp new file mode 100644 index 00000000..80259e30 --- /dev/null +++ b/ksystemlog/src/modes/system/systemFactory.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "systemFactory.h" + +#include + + +#include "logMode.h" +#include "logging.h" + +#include "simpleAction.h" +#include "systemLogMode.h" + +#include "logModeFactory.h" + +QList SystemLogModeFactory::createLogModes() const { + QList logModes; + logModes.append(new SystemLogMode()); + return logModes; +} + +LogModeAction* SystemLogModeFactory::createLogModeAction() const { + LogMode* logMode = Globals::instance()->findLogMode(QLatin1String( SYSTEM_LOG_MODE_ID )); + return new SimpleAction(logMode->action(), logMode); +} diff --git a/ksystemlog/src/modes/system/systemFactory.h b/ksystemlog/src/modes/system/systemFactory.h new file mode 100644 index 00000000..06b2fc97 --- /dev/null +++ b/ksystemlog/src/modes/system/systemFactory.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SYSTEM_FACTORY_H_ +#define _SYSTEM_FACTORY_H_ + +#include + +#include "logModeFactory.h" + +class LogModeAction; +class LogMode; + +class SystemLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; + +}; + +#endif // _SYSTEM_FACTORY_H_ + diff --git a/ksystemlog/src/modes/system/systemLogMode.cpp b/ksystemlog/src/modes/system/systemLogMode.cpp new file mode 100644 index 00000000..b7cc636a --- /dev/null +++ b/ksystemlog/src/modes/system/systemLogMode.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "systemLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "systemAnalyzer.h" +#include "systemConfigurationWidget.h" +#include "systemConfiguration.h" + +#include "logModeItemBuilder.h" + +SystemLogMode::SystemLogMode() : + LogMode(QLatin1String( SYSTEM_LOG_MODE_ID ), i18n("System Log"),QLatin1String( SYSTEM_MODE_ICON )) { + + d->logModeConfiguration = new SystemConfiguration(); + + d->logModeConfigurationWidget = new SystemConfigurationWidget(); + + d->itemBuilder = new LogModeItemBuilder(); + + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the system log.")); + d->action->setWhatsThis(i18n("Displays the system log in the current tab. This log is generally used by non-specialized processes (like \"sudo\" or \"fsck\" commands)")); + +} + +SystemLogMode::~SystemLogMode() { + +} + +Analyzer* SystemLogMode::createAnalyzer() { + return new SystemAnalyzer(this); +} + +QList SystemLogMode::createLogFiles() { + return logModeConfiguration()->findGenericLogFiles(); +} diff --git a/ksystemlog/src/modes/system/systemLogMode.h b/ksystemlog/src/modes/system/systemLogMode.h new file mode 100644 index 00000000..c183040e --- /dev/null +++ b/ksystemlog/src/modes/system/systemLogMode.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _SYSTEM_LOG_MODE_H_ +#define _SYSTEM_LOG_MODE_H_ + +/** + * System Log Mode Identifier + */ +#define SYSTEM_LOG_MODE_ID "systemLogMode" + +/** + * System Log Icon + */ +#define SYSTEM_MODE_ICON "computer" + +#include + +#include "logFile.h" +#include "logMode.h" + +class SystemLogMode : public LogMode { + + Q_OBJECT + +public: + explicit SystemLogMode(); + + ~SystemLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _SYSTEM_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/xorg/CMakeLists.txt b/ksystemlog/src/modes/xorg/CMakeLists.txt new file mode 100644 index 00000000..07110071 --- /dev/null +++ b/ksystemlog/src/modes/xorg/CMakeLists.txt @@ -0,0 +1,35 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../base + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../config + ${CMAKE_CURRENT_BINARY_DIR}/../base + ${CMAKE_CURRENT_BINARY_DIR}/../../config +) + +set(ksystemlog_xorg_sources + xorgConfigurationWidget.cpp + xorgConfiguration.cpp + xorgAnalyzer.cpp + xorgItemBuilder.cpp + xorgLogMode.cpp + xorgFactory.cpp +) + +kde4_add_library(ksystemlog_xorg STATIC ${ksystemlog_xorg_sources}) + +add_dependencies( + ksystemlog_xorg + + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_xorg + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config +) diff --git a/ksystemlog/src/modes/xorg/xorgAnalyzer.cpp b/ksystemlog/src/modes/xorg/xorgAnalyzer.cpp new file mode 100644 index 00000000..662fb075 --- /dev/null +++ b/ksystemlog/src/modes/xorg/xorgAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "xorgAnalyzer.h" diff --git a/ksystemlog/src/modes/xorg/xorgAnalyzer.h b/ksystemlog/src/modes/xorg/xorgAnalyzer.h new file mode 100644 index 00000000..72c2bc79 --- /dev/null +++ b/ksystemlog/src/modes/xorg/xorgAnalyzer.h @@ -0,0 +1,144 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _XORG_ANALYZER_H_ +#define _XORG_ANALYZER_H_ + +#include + +#include "analyzer.h" + +#include "localLogFileReader.h" +#include "parsingHelper.h" +#include "xorgLogMode.h" + + +#define CONFIG_FILE_LOG_LEVEL_ICON "configure" +#define DEFAULT_SETTING_LOG_LEVEL_ICON "configure-toolbars" +#define COMMAND_LINE_LOG_LEVEL_ICON "konsole" +#define PROBED_LOG_LEVEL_ICON "favorites" +#define NOT_IMPLEMENTED_LOG_LEVEL_ICON "document-new" + +class XorgAnalyzer : public Analyzer { + Q_OBJECT + + public: + XorgAnalyzer(LogMode* logMode) : + Analyzer(logMode), + currentDateTime(QDateTime::currentDateTime()) + { + + initializeTypeName(); + } + + virtual ~XorgAnalyzer() { + + } + + LogViewColumns initColumns() { + LogViewColumns columns; + + columns.addColumn(LogViewColumn(i18n("Line"), false, false)); + columns.addColumn(LogViewColumn(i18n("Type"), false, false)); + columns.addColumn(LogViewColumn(i18n("Message"), false, false)); + + columns.setGroupByDay(false); + columns.setGroupByHour(false); + + return columns; + } + + + protected: + LogFileReader* createLogFileReader(const LogFile& logFile) { + return new LocalLogFileReader(logFile); + } + + Analyzer::LogFileSortMode logFileSortMode() { + return Analyzer::AscendingSortedLogFile; + } + + LogLine* parseMessage(const QString& logLine, const LogFile& originalFile) { + + QString string(logLine); + + QString type; + + type=string.left(4); + + LogLevel* logLineType=findTypeName(type); + + //If the type is not empty, the log message has a type, so we can delete it + if (logLineType!=NULL) { + string=string.remove(0, 5); + } + else { + logLineType=Globals::instance()->informationLogLevel(); + } + + QStringList list; + list.append(logLineType->name()); + list.append(string); + + return new LogLine( + logLineInternalIdGenerator++, + currentDateTime, + list, + originalFile.url().path(), + logLineType, + logMode + ); + } + + private: + + QMap xorgLevels; + + void initializeTypeName() { + xorgLevels[QLatin1String( "(--)" )]=new LogLevel(1001, i18n("Probed"),QLatin1String( PROBED_LOG_LEVEL_ICON ), QColor(246, 206, 30)); + xorgLevels[QLatin1String( "(**)" )]=new LogLevel(1002, i18n("From config file"),QLatin1String( CONFIG_FILE_LOG_LEVEL_ICON ), QColor(161, 133, 240)); + xorgLevels[QLatin1String( "(==)" )]=new LogLevel(1003, i18n("Default setting"),QLatin1String( DEFAULT_SETTING_LOG_LEVEL_ICON ), QColor(169, 189, 165)); + xorgLevels[QLatin1String( "(++)" )]=new LogLevel(1004, i18n("From command Line"),QLatin1String( COMMAND_LINE_LOG_LEVEL_ICON ), QColor(179, 181, 214)); + xorgLevels[QLatin1String( "(!!)" )]=Globals::instance()->noticeLogLevel(); + xorgLevels[QLatin1String( "(II)" )]=Globals::instance()->informationLogLevel(); + xorgLevels[QLatin1String( "(WW)" )]=Globals::instance()->warningLogLevel(); + xorgLevels[QLatin1String( "(EE)" )]=Globals::instance()->errorLogLevel(); + xorgLevels[QLatin1String( "(NI)" )]=new LogLevel(1005, i18n("Not implemented"),QLatin1String( NOT_IMPLEMENTED_LOG_LEVEL_ICON ), QColor(136, 146, 240)); + xorgLevels[QLatin1String( "(\?\?)" )]=Globals::instance()->noLogLevel(); + + } + + LogLevel* findTypeName(const QString& type) { + QMap::iterator it; + + it=xorgLevels.find(type); + if (it!=xorgLevels.end()) + return *it; + else + return NULL; + + } + + QDateTime currentDateTime; + +}; + +#endif // _XORG_ANALYZER_H_ diff --git a/ksystemlog/src/modes/xorg/xorgConfiguration.cpp b/ksystemlog/src/modes/xorg/xorgConfiguration.cpp new file mode 100644 index 00000000..c81466d0 --- /dev/null +++ b/ksystemlog/src/modes/xorg/xorgConfiguration.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "xorgConfiguration.h" diff --git a/ksystemlog/src/modes/xorg/xorgConfiguration.h b/ksystemlog/src/modes/xorg/xorgConfiguration.h new file mode 100644 index 00000000..11871522 --- /dev/null +++ b/ksystemlog/src/modes/xorg/xorgConfiguration.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _XORG_CONFIGURATION_H_ +#define _XORG_CONFIGURATION_H_ + +#include + +#include "logModeConfiguration.h" + +#include "logging.h" +#include "defaults.h" +#include "ksystemlogConfig.h" + +#include "xorgLogMode.h" + +class XorgConfigurationPrivate { +public: + QStringList xorgPaths; +}; + +class XorgConfiguration : public LogModeConfiguration { + + Q_OBJECT + + public: + XorgConfiguration() : + d(new XorgConfigurationPrivate()) { + + configuration->setCurrentGroup(QLatin1String( "XorgLogMode" )); + + QStringList defaultXorgPaths; + defaultXorgPaths << QLatin1String( "/var/log/Xorg.0.log" ); + configuration->addItemStringList(QLatin1String( "LogFilesPaths" ), d->xorgPaths, defaultXorgPaths, QLatin1String( "LogFilesPaths" )); + + } + + virtual ~XorgConfiguration() { + delete d; + } + + QStringList xorgPaths() const { + return d->xorgPaths; + } + + void setXorgPaths(const QStringList& xorgPaths) { + d->xorgPaths = xorgPaths; + } + + private: + XorgConfigurationPrivate* const d; + +}; + +#endif // _XORG_CONFIGURATION_H_ diff --git a/ksystemlog/src/modes/xorg/xorgConfigurationWidget.cpp b/ksystemlog/src/modes/xorg/xorgConfigurationWidget.cpp new file mode 100644 index 00000000..4d3dd72e --- /dev/null +++ b/ksystemlog/src/modes/xorg/xorgConfigurationWidget.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KApacheLog, a apache log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "xorgConfigurationWidget.h" diff --git a/ksystemlog/src/modes/xorg/xorgConfigurationWidget.h b/ksystemlog/src/modes/xorg/xorgConfigurationWidget.h new file mode 100644 index 00000000..39a89fb1 --- /dev/null +++ b/ksystemlog/src/modes/xorg/xorgConfigurationWidget.h @@ -0,0 +1,98 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _XORG_CONFIGURATION_WIDGET_H_ +#define _XORG_CONFIGURATION_WIDGET_H_ + + +#include + +#include "globals.h" +#include "logging.h" +#include "fileList.h" + +#include "logLevel.h" +#include "logModeConfigurationWidget.h" + +#include "xorgConfiguration.h" +#include "xorgLogMode.h" + +class FileList; + +class XorgConfigurationWidget : public LogModeConfigurationWidget { + + Q_OBJECT + + public: + XorgConfigurationWidget() : + LogModeConfigurationWidget(i18n("X.org Log"),QLatin1String( XORG_MODE_ICON ), i18n("X.org Log")) + { + + QHBoxLayout* layout = new QHBoxLayout(); + this->setLayout(layout); + + fileList=new FileList(this, i18n("

These files will be analyzed to show the X.org log.

")); + connect(fileList, SIGNAL(fileListChanged()), this, SIGNAL(configurationChanged())); + layout->addWidget(fileList); + + } + + ~XorgConfigurationWidget() { + + } + + public slots: + + void saveConfig() { + XorgConfiguration* xorgConfiguration = Globals::instance()->findLogMode(QLatin1String( XORG_LOG_MODE_ID ))->logModeConfiguration(); + + xorgConfiguration->setXorgPaths(fileList->paths()); + } + + void readConfig() { + XorgConfiguration* xorgConfiguration = Globals::instance()->findLogMode(QLatin1String( XORG_LOG_MODE_ID ))->logModeConfiguration(); + + fileList->removeAllItems(); + + fileList->addPaths(xorgConfiguration->xorgPaths()); + } + + void defaultConfig() { + //TODO Find a way to read the configuration per default + readConfig(); + } + + protected: + bool isValid() const { + if (fileList->isEmpty()==false) { + return true; + } + + return false; + + } + + private: + FileList* fileList; + +}; + +#endif // _XORG_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/modes/xorg/xorgFactory.cpp b/ksystemlog/src/modes/xorg/xorgFactory.cpp new file mode 100644 index 00000000..e4f8fabd --- /dev/null +++ b/ksystemlog/src/modes/xorg/xorgFactory.cpp @@ -0,0 +1,47 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "xorgFactory.h" + +#include + + +#include "logMode.h" +#include "logging.h" + +#include "simpleAction.h" +#include "xorgLogMode.h" + +#include "logModeFactory.h" + +QList XorgLogModeFactory::createLogModes() const { + QList logModes; + logModes.append(new XorgLogMode()); + return logModes; +} + +LogModeAction* XorgLogModeFactory::createLogModeAction() const { + LogMode* logMode = Globals::instance()->findLogMode(QLatin1String( XORG_LOG_MODE_ID )); + SimpleAction* logModeAction = new SimpleAction(logMode->action(), logMode); + logModeAction->setCategory(LogModeAction::ServicesCategory); + + return logModeAction; +} diff --git a/ksystemlog/src/modes/xorg/xorgFactory.h b/ksystemlog/src/modes/xorg/xorgFactory.h new file mode 100644 index 00000000..c6f6daa4 --- /dev/null +++ b/ksystemlog/src/modes/xorg/xorgFactory.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _XORG_FACTORY_H_ +#define _XORG_FACTORY_H_ + +#include + +#include "logModeFactory.h" + +class LogModeAction; +class LogMode; + +class XorgLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; + +}; + +#endif // _XORG_FACTORY_H_ + diff --git a/ksystemlog/src/modes/xorg/xorgItemBuilder.cpp b/ksystemlog/src/modes/xorg/xorgItemBuilder.cpp new file mode 100644 index 00000000..00a20ec3 --- /dev/null +++ b/ksystemlog/src/modes/xorg/xorgItemBuilder.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "xorgItemBuilder.h" diff --git a/ksystemlog/src/modes/xorg/xorgItemBuilder.h b/ksystemlog/src/modes/xorg/xorgItemBuilder.h new file mode 100644 index 00000000..db62d52f --- /dev/null +++ b/ksystemlog/src/modes/xorg/xorgItemBuilder.h @@ -0,0 +1,93 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _XORG_ITEM_BUILDER_H_ +#define _XORG_ITEM_BUILDER_H_ + +#include + +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +#include "logModeItemBuilder.h" + +class LogLine; + +class XorgItemBuilder : public LogModeItemBuilder { + + public: + XorgItemBuilder() { + + } + + virtual ~XorgItemBuilder() { + + } + + + void prepareItem(LogViewWidgetItem* item) const { + LogLine* line=item->logLine(); + + item->setText(0, QLatin1String( "" )); + + int i=1; + foreach(const QString &label, line->logItems()) { + item->setText(i, label); + i++; + } + + item->setIcon(0, line->logLevel()->pixmap()); + } + + QString createToolTipText(LogLine* line) const { + QString result; + + QListIterator it(line->logItems()); + + result.append(QLatin1String( "" )); + + QString type=it.next(); + if (type.isEmpty()) + result.append(labelMessageFormat(i18n("Type:"), i18n("none"))); + else + result.append(labelMessageFormat(i18n("Type:"), type )); + + result.append(labelMessageFormat(i18n("Original file:"), line->sourceFileName())); + + result.append(QLatin1String( "
" )); + + return result; + } + + + QString createFormattedText(LogLine* line) const { + //It uses the same formating than the tool tip + return createToolTipText(line); + } + +}; + +#endif // _XORG_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/modes/xorg/xorgLogMode.cpp b/ksystemlog/src/modes/xorg/xorgLogMode.cpp new file mode 100644 index 00000000..68c751e0 --- /dev/null +++ b/ksystemlog/src/modes/xorg/xorgLogMode.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "xorgLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "xorgAnalyzer.h" +#include "xorgItemBuilder.h" +#include "xorgConfigurationWidget.h" +#include "xorgConfiguration.h" + +XorgLogMode::XorgLogMode() : + LogMode(QLatin1String( XORG_LOG_MODE_ID ), i18n("X.org Log"),QLatin1String( XORG_MODE_ICON )) { + + d->logModeConfiguration = new XorgConfiguration(); + + d->logModeConfigurationWidget = new XorgConfigurationWidget(); + + d->itemBuilder = new XorgItemBuilder(); + + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the X.org log.")); + d->action->setWhatsThis(i18n("Displays the X.org log in the current tab. X.org is the service which displays on screen your desktop and manage your graphical hardware. See this log if you want to know why you do not have 3D accelerations or why your input device is not recognized.")); + +} + +XorgLogMode::~XorgLogMode() { + +} + +Analyzer* XorgLogMode::createAnalyzer() { + return new XorgAnalyzer(this); +} + +QList XorgLogMode::createLogFiles() { + XorgConfiguration* configuration = logModeConfiguration(); + return configuration->findNoModeLogFiles(configuration->xorgPaths()); +} diff --git a/ksystemlog/src/modes/xorg/xorgLogMode.h b/ksystemlog/src/modes/xorg/xorgLogMode.h new file mode 100644 index 00000000..2c2e045f --- /dev/null +++ b/ksystemlog/src/modes/xorg/xorgLogMode.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _XORG_LOG_MODE_H_ +#define _XORG_LOG_MODE_H_ + +/** + * Xorg Log Mode Identifier + */ +#define XORG_LOG_MODE_ID "xorgLogMode" + +/** + * Xorg Log Icon + */ +#define XORG_MODE_ICON "xorg" + +#include + +#include "logFile.h" + +#include "logMode.h" + +class XorgLogMode : public LogMode { + + Q_OBJECT + +public: + explicit XorgLogMode(); + + ~XorgLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _XORG_LOG_MODE_H_ + diff --git a/ksystemlog/src/modes/xsession/CMakeLists.txt b/ksystemlog/src/modes/xsession/CMakeLists.txt new file mode 100644 index 00000000..cf17e7ca --- /dev/null +++ b/ksystemlog/src/modes/xsession/CMakeLists.txt @@ -0,0 +1,36 @@ + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../base + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../config + ${CMAKE_CURRENT_BINARY_DIR}/../base + ${CMAKE_CURRENT_BINARY_DIR}/../../config +) + +set(ksystemlog_xsession_sources + xsessionConfigurationWidget.cpp + xsessionConfiguration.cpp + xsessionAnalyzer.cpp + xsessionItemBuilder.cpp + xsessionLogMode.cpp + xsessionFactory.cpp +) + +kde4_add_ui_files(ksystemlog_xsession_sources xsessionConfigurationWidgetBase.ui ) + +kde4_add_library(ksystemlog_xsession STATIC ${ksystemlog_xsession_sources}) + +add_dependencies( + ksystemlog_xsession + ksystemlog_base_mode + ksystemlog_lib +) + +target_link_libraries( + ksystemlog_xsession + ${KDE4_KDEUI_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config +) diff --git a/ksystemlog/src/modes/xsession/xsessionAnalyzer.cpp b/ksystemlog/src/modes/xsession/xsessionAnalyzer.cpp new file mode 100644 index 00000000..3c239fcc --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionAnalyzer.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "xsessionAnalyzer.h" diff --git a/ksystemlog/src/modes/xsession/xsessionAnalyzer.h b/ksystemlog/src/modes/xsession/xsessionAnalyzer.h new file mode 100644 index 00000000..741d148a --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionAnalyzer.h @@ -0,0 +1,159 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _X_SESSION_ANALYZER_H_ +#define _X_SESSION_ANALYZER_H_ + +#include + +#include "analyzer.h" + +#include "localLogFileReader.h" +#include "parsingHelper.h" + +#include "xsessionLogMode.h" +#include "xsessionConfiguration.h" + +class XSessionAnalyzer : public Analyzer { + Q_OBJECT + + public: + XSessionAnalyzer(LogMode* logMode) : + Analyzer(logMode), + currentDateTime(QDateTime::currentDateTime()) + { + + } + + virtual ~XSessionAnalyzer() { + + } + + LogViewColumns initColumns() { + LogViewColumns columns; + + columns.addColumn(LogViewColumn(i18n("Line"), true, false)); + columns.addColumn(LogViewColumn(i18n("Program"), true, false)); + columns.addColumn(LogViewColumn(i18n("Message"), true, false)); + + columns.setGroupByDay(false); + columns.setGroupByHour(false); + + return columns; + } + + + protected: + LogFileReader* createLogFileReader(const LogFile& logFile) { + return new LocalLogFileReader(logFile); + } + + Analyzer::LogFileSortMode logFileSortMode() { + XSessionConfiguration* configuration = logMode->logModeConfiguration(); + if (configuration->isIgnoreXorgErrors()) + return Analyzer::FilteredLogFile; + else + return Analyzer::AscendingSortedLogFile; + } + + LogLine* parseMessage(const QString& logLine, const LogFile& originalFile) { + int classPrototypePosition=logLine.indexOf(QLatin1String( "::" )); + int programPos=logLine.indexOf(QLatin1Char( ':' )); + + //If the first found : is the begin of a :: (example: QFile::at:) then we move to the next : + if (classPrototypePosition != -1 && programPos==classPrototypePosition) { + programPos=logLine.indexOf(QLatin1Char( ':' ), classPrototypePosition+2); + } + + QString program; + QString message; + if (programPos==-1) { + program = QLatin1String( "" ); + message = logLine.simplified(); + } + else { + program = logLine.left(programPos); + message = logLine.right(logLine.length()-programPos-1); + } + + message = message.simplified(); + + //Do not add this line if this is a X error that the user wants to ignore + if (isXorgError(program) == true) { + return NULL; + } + + //Find the right log level + LogLevel* logLevel; + if (hasErrorKeywords(message)) + logLevel = Globals::instance()->errorLogLevel(); + else if (hasWarningKeywords(message)) + logLevel = Globals::instance()->warningLogLevel(); + else + logLevel = Globals::instance()->informationLogLevel(); + + + + return new LogLine( + logLineInternalIdGenerator++, + currentDateTime, + QStringList() << program << message, + originalFile.url().path(), + logLevel, + logMode + ); + } + + private: + bool isXorgError(const QString& program) { + XSessionConfiguration* configuration = logMode->logModeConfiguration(); + if (configuration->isIgnoreXorgErrors() && configuration->xorgErrorKeywords().contains(program)) + return true; + + return false; + } + + bool hasWarningKeywords(const QString& message) { + XSessionConfiguration* configuration = logMode->logModeConfiguration(); + return hasKeywords(message, configuration->warningKeywords()); + } + + bool hasErrorKeywords(const QString& message) { + XSessionConfiguration* configuration = logMode->logModeConfiguration(); + return hasKeywords(message, configuration->errorKeywords()); + } + + bool hasKeywords(const QString& message, const QStringList& keywords) { + foreach(const QString& keyword, keywords) { + if (message.contains(keyword, Qt::CaseInsensitive)) { + return true; + } + } + + return false; + } + + + QDateTime currentDateTime; + +}; + +#endif // _X_SESSION_ANALYZER_H_ diff --git a/ksystemlog/src/modes/xsession/xsessionConfiguration.cpp b/ksystemlog/src/modes/xsession/xsessionConfiguration.cpp new file mode 100644 index 00000000..2d2728e5 --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionConfiguration.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "xsessionConfiguration.h" diff --git a/ksystemlog/src/modes/xsession/xsessionConfiguration.h b/ksystemlog/src/modes/xsession/xsessionConfiguration.h new file mode 100644 index 00000000..90e0e18a --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionConfiguration.h @@ -0,0 +1,116 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _X_SESSION_CONFIGURATION_H_ +#define _X_SESSION_CONFIGURATION_H_ + +#include + +#include "logModeConfiguration.h" + +#include "logging.h" +#include "defaults.h" + +#include "xsessionLogMode.h" + +#include "ksystemlogConfig.h" + +class XSessionConfigurationPrivate { +public: + QString xsessionPath; + + bool ignoreXorgErrors; + + QStringList xorgErrorKeywords; + + QStringList warningKeywords; + QStringList errorKeywords; +}; + +class XSessionConfiguration : public LogModeConfiguration { + + Q_OBJECT + + public: + XSessionConfiguration() : + d(new XSessionConfigurationPrivate()) { + + configuration->setCurrentGroup(QLatin1String( "XSessionLogMode" )); + + configuration->addItemString(QLatin1String( "LogFilePath" ), d->xsessionPath, QLatin1String( "~/.xsession-errors" ), QLatin1String( "LogFilePath" )); + + configuration->addItemBool(QLatin1String( "IgnoreXorgErrors" ), d->ignoreXorgErrors, false, QLatin1String( "IgnoreXorgErrors" )); + + QStringList defaultXorgErrorKeywords; + defaultXorgErrorKeywords.append(QLatin1String( "X Error" )); + defaultXorgErrorKeywords.append(QLatin1String( " Major opcode" )); + defaultXorgErrorKeywords.append(QLatin1String( " Minor opcode" )); + defaultXorgErrorKeywords.append(QLatin1String( " Resource id" )); + configuration->addItemStringList(QLatin1String( "XorgErrorKeywords" ), d->xorgErrorKeywords, defaultXorgErrorKeywords, QLatin1String( "XorgErrorKeywords" )); + + QStringList defaultWarningKeywords; + defaultWarningKeywords.append(QLatin1String( "WARNING" )); + configuration->addItemStringList(QLatin1String( "WarningKeywords" ), d->warningKeywords, defaultWarningKeywords, QLatin1String( "WarningKeywords" )); + + QStringList defaultErrorKeywords; + defaultErrorKeywords.append(QLatin1String( "ERROR" )); + configuration->addItemStringList(QLatin1String( "ErrorKeywords" ), d->errorKeywords, defaultErrorKeywords, QLatin1String( "ErrorKeywords" )); + + } + + virtual ~XSessionConfiguration() { + delete d; + } + + QStringList xorgErrorKeywords() const { + return d->xorgErrorKeywords; + } + + bool isIgnoreXorgErrors() const { + return d->ignoreXorgErrors; + } + + void setIgnoreXorgErrors(bool ignore) { + d->ignoreXorgErrors = ignore; + } + + QString xsessionPath() const { + return d->xsessionPath; + } + + void setXSessionPath(const QString& xsessionPath) { + d->xsessionPath = xsessionPath; + } + + QStringList warningKeywords() const { + return d->warningKeywords; + } + + QStringList errorKeywords() const { + return d->errorKeywords; + } + + private: + XSessionConfigurationPrivate* const d; + +}; + +#endif // _X_SESSION_CONFIGURATION_H_ diff --git a/ksystemlog/src/modes/xsession/xsessionConfigurationWidget.cpp b/ksystemlog/src/modes/xsession/xsessionConfigurationWidget.cpp new file mode 100644 index 00000000..5f71f37b --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionConfigurationWidget.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KApacheLog, a apache log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "xsessionConfigurationWidget.h" diff --git a/ksystemlog/src/modes/xsession/xsessionConfigurationWidget.h b/ksystemlog/src/modes/xsession/xsessionConfigurationWidget.h new file mode 100644 index 00000000..6144eb8e --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionConfigurationWidget.h @@ -0,0 +1,153 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _X_SESSION_CONFIGURATION_WIDGET_H_ +#define _X_SESSION_CONFIGURATION_WIDGET_H_ + +#include +#include + +#include +#include + +#include "globals.h" +#include "logging.h" +#include "fileList.h" + +#include "logLevel.h" + +#include "xsessionConfiguration.h" +#include "xsessionLogMode.h" + +#include "logModeConfigurationWidget.h" + +#include "ui_xsessionConfigurationWidgetBase.h" + + +class XSessionConfigurationWidget : public LogModeConfigurationWidget, public Ui::XSessionConfigurationWidgetBase { + + Q_OBJECT + + public: + XSessionConfigurationWidget() : + LogModeConfigurationWidget(i18n("X Session Log"), QLatin1String( X_SESSION_MODE_ICON ), i18n("X Session Log")) + { + + setupUi(this); + /* + + QVBoxLayout* layout = new QVBoxLayout(); + this->setLayout(layout); + + //Authentication log file + QGroupBox* xsessionBox=new QGroupBox(i18n("X Session Log File")); + QGridLayout* xsessionLayout = new QGridLayout(); + xsessionBox->setLayout(xsessionLayout); + + layout->addWidget(xsessionBox); + + xsessionLayout->addWidget(new QLabel(i18n("Log file:")), 0, 0); + + xsessionUrlRequester=new KUrlRequester(xsessionBox); + + xsessionLayout->addWidget(xsessionUrlRequester, 0, 1); + + ignoreXorgErrors = new QCheckBox(i18n("Ignore Xorg Errors"), this); + xsessionLayout->addWidget(xsessionUrlRequester, 1, 0, 1, 2); + + layout->addStretch(); + */ + + xsessionUrlRequester->setToolTip(i18n("You can type or choose the X Session log file (example: ~/.xsession-errors).")); + xsessionUrlRequester->setWhatsThis(i18n("You can type or choose here the X Session log file. This file will be analyzed when you select the X Session log menu. Generally, its name is ~/.xsession-errors")); + xsessionUrlRequester->setMode(KFile::File); + xsessionUrlRequester->setEnabled(true); + + connect(xsessionUrlRequester, SIGNAL(textChanged(const QString&)), this, SIGNAL(configurationChanged())); + connect(ignoreXorgErrors, SIGNAL(stateChanged(int)), this, SIGNAL(configurationChanged())); + + connect(ignoreXorgErrors, SIGNAL(toggled(bool)), xorgErrorsDescription, SLOT(setEnabled(bool))); + + xorgErrorsDescriptionDefined = false; + + } + + ~XSessionConfigurationWidget() { + + } + + public slots: + + void saveConfig() { + XSessionConfiguration* configuration = Globals::instance()->findLogMode(QLatin1String( X_SESSION_LOG_MODE_ID ))->logModeConfiguration(); + + configuration->setXSessionPath(xsessionUrlRequester->url().path()); + configuration->setIgnoreXorgErrors(ignoreXorgErrors->isChecked()); + } + + void readConfig() { + XSessionConfiguration* configuration = Globals::instance()->findLogMode(QLatin1String( X_SESSION_LOG_MODE_ID ))->logModeConfiguration(); + + xsessionUrlRequester->setUrl(KUrl(configuration->xsessionPath())); + ignoreXorgErrors->setChecked(configuration->isIgnoreXorgErrors()); + + prepareXorgErrorsDescription(); + } + + void defaultConfig() { + //TODO Find a way to read the configuration per default + readConfig(); + } + + protected: + bool isValid() const { + if (xsessionUrlRequester->url().path().isEmpty()==false) { + return true; + } + + return false; + + } + + private: + void prepareXorgErrorsDescription() { + XSessionConfiguration* configuration = Globals::instance()->findLogMode(QLatin1String( X_SESSION_LOG_MODE_ID ))->logModeConfiguration(); + + //Prepare Ignore Xorg Errors description + if (xorgErrorsDescriptionDefined == false) { + QString text = xorgErrorsDescription->text(); + text.append(QLatin1String( "
    " )); + + foreach(const QString &xorgErrorKeyword, configuration->xorgErrorKeywords()) { + text.append(i18n("
  • %1: ...
  • ", xorgErrorKeyword)); + } + text.append(QLatin1String( "
" )); + xorgErrorsDescription->setText(text); + + xorgErrorsDescriptionDefined = true; + } + + } + + bool xorgErrorsDescriptionDefined; +}; + +#endif // _X_SESSION_CONFIGURATION_WIDGET_H_ diff --git a/ksystemlog/src/modes/xsession/xsessionConfigurationWidgetBase.ui b/ksystemlog/src/modes/xsession/xsessionConfigurationWidgetBase.ui new file mode 100644 index 00000000..e6e0500b --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionConfigurationWidgetBase.ui @@ -0,0 +1,92 @@ + + XSessionConfigurationWidgetBase + + + + 0 + 0 + 400 + 300 + + + + + + + X Session Log File + + + + + + Log file: + + + xsessionUrlRequester + + + + + + + false + + + + 1 + 0 + + + + + + + + Check this option to remove Xorg errors + + + Check this option to remove Xorg errors + + + Ignore Xorg errors + + + + + + + <p><b>Note:</b> Check this option to remove X.org errors.</p><p style="margin-bottom:0px">The following lines will be ignored:</p> + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 31 + + + + + + + + + KUrlRequester + QWidget +
kurlrequester.h
+
+
+ + +
diff --git a/ksystemlog/src/modes/xsession/xsessionFactory.cpp b/ksystemlog/src/modes/xsession/xsessionFactory.cpp new file mode 100644 index 00000000..4c696ba0 --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionFactory.cpp @@ -0,0 +1,49 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "xsessionFactory.h" + +#include + + +#include "logMode.h" +#include "logging.h" + +#include "simpleAction.h" +#include "xsessionLogMode.h" + +#include "logModeFactory.h" + +QList XSessionLogModeFactory::createLogModes() const { + QList logModes; + logModes.append(new XSessionLogMode()); + return logModes; +} + +LogModeAction* XSessionLogModeFactory::createLogModeAction() const { + LogMode* logMode = Globals::instance()->findLogMode(QLatin1String( X_SESSION_LOG_MODE_ID )); + SimpleAction* logModeAction = new SimpleAction(logMode->action(), logMode); + + logModeAction->setInToolBar(false); + logModeAction->setCategory(LogModeAction::OthersCategory); + + return logModeAction; +} diff --git a/ksystemlog/src/modes/xsession/xsessionFactory.h b/ksystemlog/src/modes/xsession/xsessionFactory.h new file mode 100644 index 00000000..6ddbef2a --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionFactory.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _X_SESSION_FACTORY_H_ +#define _X_SESSION_FACTORY_H_ + +#include + +#include "logModeFactory.h" + +class LogModeAction; +class LogMode; + +class XSessionLogModeFactory : public LogModeFactory { + + Q_OBJECT + + public: + + QList createLogModes() const; + + LogModeAction* createLogModeAction() const; + +}; + +#endif // _X_SESSION_FACTORY_H_ + diff --git a/ksystemlog/src/modes/xsession/xsessionItemBuilder.cpp b/ksystemlog/src/modes/xsession/xsessionItemBuilder.cpp new file mode 100644 index 00000000..4cbc0ab0 --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionItemBuilder.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "xsessionItemBuilder.h" diff --git a/ksystemlog/src/modes/xsession/xsessionItemBuilder.h b/ksystemlog/src/modes/xsession/xsessionItemBuilder.h new file mode 100644 index 00000000..877e0d89 --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionItemBuilder.h @@ -0,0 +1,93 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _X_SESSION_ITEM_BUILDER_H_ +#define _X_SESSION_ITEM_BUILDER_H_ + +#include + +#include + +#include "logging.h" + +#include "logLine.h" +#include "logViewWidgetItem.h" +#include "logMode.h" + +#include "logModeItemBuilder.h" + +class LogLine; + +class XSessionItemBuilder : public LogModeItemBuilder { + + public: + XSessionItemBuilder() { + + } + + virtual ~XSessionItemBuilder() { + + } + + + void prepareItem(LogViewWidgetItem* item) const { + LogLine* line=item->logLine(); + + item->setText(0, QLatin1String( "" )); + + int i=1; + foreach(const QString &label, line->logItems()) { + item->setText(i, label); + i++; + } + + item->setIcon(0, QIcon(line->logLevel()->pixmap())); + } + + QString createToolTipText(LogLine* line) const { + QString result; + + QListIterator it(line->logItems()); + + result.append(QLatin1String( "" )); + + QString type=it.next(); + if (type.isEmpty()) + result.append(labelMessageFormat(i18n("Program:"), i18n("none"))); + else + result.append(labelMessageFormat(i18n("Program:"), type )); + + result.append(labelMessageFormat(i18n("Original file:"), line->sourceFileName())); + + result.append(QLatin1String( "
" )); + + return result; + } + + + QString createFormattedText(LogLine* line) const { + //It uses the same formating than the tool tip + return createToolTipText(line); + } + +}; + +#endif // _X_SESSION_ITEM_BUILDER_H_ diff --git a/ksystemlog/src/modes/xsession/xsessionLogMode.cpp b/ksystemlog/src/modes/xsession/xsessionLogMode.cpp new file mode 100644 index 00000000..d623e863 --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionLogMode.cpp @@ -0,0 +1,66 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "xsessionLogMode.h" + +#include + +#include +#include + +#include "logging.h" +#include "logMode.h" + +#include "xsessionAnalyzer.h" +#include "xsessionItemBuilder.h" +#include "xsessionConfigurationWidget.h" +#include "xsessionConfiguration.h" + +XSessionLogMode::XSessionLogMode() : + LogMode(QLatin1String( X_SESSION_LOG_MODE_ID ), i18n("X Session Log"), QLatin1String( X_SESSION_MODE_ICON )) { + + d->logModeConfiguration = new XSessionConfiguration(); + + d->logModeConfigurationWidget = new XSessionConfigurationWidget(); + + d->itemBuilder = new XSessionItemBuilder(); + + d->action = createDefaultAction(); + d->action->setToolTip(i18n("Display the X Session log.")); + d->action->setWhatsThis(i18n("Displays the X Session log in the current tab. X Session log is the place where graphical programs write their output. See this log if you want to know why a program has crashed, or why your display manager (KDE, Gnome,...) has not started.")); + +} + +XSessionLogMode::~XSessionLogMode() { + +} + +Analyzer* XSessionLogMode::createAnalyzer() { + return new XSessionAnalyzer(this); +} + +QList XSessionLogMode::createLogFiles() { + XSessionConfiguration* configuration = Globals::instance()->findLogMode(QLatin1String( X_SESSION_LOG_MODE_ID ))->logModeConfiguration(); + + QList logFiles; + logFiles.append(configuration->findGenericLogFile(configuration->xsessionPath())); + return logFiles; +} diff --git a/ksystemlog/src/modes/xsession/xsessionLogMode.h b/ksystemlog/src/modes/xsession/xsessionLogMode.h new file mode 100644 index 00000000..35d8d57e --- /dev/null +++ b/ksystemlog/src/modes/xsession/xsessionLogMode.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _X_SESSION_LOG_MODE_H_ +#define _X_SESSION_LOG_MODE_H_ + +/** + * Xorg Log Mode Identifier + */ +#define X_SESSION_LOG_MODE_ID "xsessionLogMode" + +/** + * Xorg Log Icon + */ +#define X_SESSION_MODE_ICON "utilities-log-viewer" + +#include + +#include "logFile.h" + +#include "logMode.h" + +class XSessionLogMode : public LogMode { + + Q_OBJECT + +public: + explicit XSessionLogMode(); + + ~XSessionLogMode(); + + Analyzer* createAnalyzer(); + + QList createLogFiles(); + +}; + +#endif // _X_SESSION_LOG_MODE_H_ + diff --git a/ksystemlog/src/statusBar.cpp b/ksystemlog/src/statusBar.cpp new file mode 100644 index 00000000..27ab0e70 --- /dev/null +++ b/ksystemlog/src/statusBar.cpp @@ -0,0 +1,143 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "statusBar.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "logging.h" + +namespace KSystemLog { + +class StatusBarPrivate { + +public: + + QLabel* lineCountLabel; + + //KSqueezedTextLabel* messageLabel; + + KComboBox* messageList; + + QLabel* lastModificationLabel; + + QPushButton* toggleHistory; + +}; + +StatusBar::StatusBar(QWidget* parent) : + KStatusBar(parent), + d(new StatusBarPrivate()) { + + d->lineCountLabel = new QLabel(QLatin1String( "" ), this); + d->lineCountLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + + //TODO Set a vertical right border to separate each labels + /* + d->lineCountLabel->setFrameStyle(QFrame::Box | QFrame::Sunken); + d->lineCountLabel->setLineWidth(2); + d->lineCountLabel->setMidLineWidth(2); + */ + addPermanentWidget(d->lineCountLabel, 1); + + /* + d->toggleHistory = new QPushButton(this); + d->toggleHistory->setIcon(KIcon( QLatin1String( "view-history" ))); + d->toggleHistory->setFlat(true); + addPermanentWidget(d->toggleHistory, 0); + + connect(d->toggleHistory, SIGNAL(clicked()), this, SLOT(toggleHistory())); + */ + +/* + d->messageLabel = new KSqueezedTextLabel("", this); + d->messageLabel->setAlignment(Qt::AlignLeft); + d->messageLabel->setTextElideMode(Qt::ElideRight); + addPermanentWidget(d->messageLabel, 4); +*/ + d->messageList = new KComboBox(this); + d->messageList->setInsertPolicy(QComboBox::InsertAtTop); + d->messageList->setMaxVisibleItems(5); + connect(d->messageList, SIGNAL(currentIndexChanged(int)), this, SLOT(selectLastHistory())); +/* + //TODO Define a specifical palette (and make it works !) + QPalette palette(d->messageList->palette()); + palette.setColor(QPalette::HighlightedText, Qt::red); //palette.color(QPalette::Base) + palette.setColor(QPalette::Base, Qt::red); //palette.color(QPalette::Base) + palette.setColor(QPalette::Text, QColor(212, 140, 95)); //palette.color(QPalette::Base) + d->messageList->setPalette(palette); + //d->messageList->repaint(); +*/ + addPermanentWidget(d->messageList, 4); + + d->lastModificationLabel = new QLabel(QLatin1String( "" ), this); + d->lastModificationLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + addPermanentWidget(d->lastModificationLabel, 1); + + +} + +StatusBar::~StatusBar() { + //QLabels are automatically deleted by Qt + delete d; +} + +void StatusBar::changeLineCountMessage(const QString& lineCountMessage) { + d->lineCountLabel->setText(lineCountMessage); +} + +void StatusBar::changeLastModification(const QTime& lastModification) { + d->lastModificationLabel->setText(i18n("Last updated: %1.", KGlobal::locale()->formatTime(lastModification, true, false) )); +} + +void StatusBar::changeMessage(const QString& message) { + //d->messageLabel->setText(message); + d->messageList->insertItem(0, i18n("%1: %2", KGlobal::locale()->formatTime(QTime::currentTime(), true, false), message)); + + //100 log history message max. + if (d->messageList->count() > 100) { + d->messageList->removeItem(d->messageList->count() -1); + } + +} + +void StatusBar::selectLastHistory() { + d->messageList->setCurrentIndex(0); +} + +void StatusBar::toggleHistory() { + logDebug() << "Toggling History..." << endl; + d->messageList->showPopup(); +} + +} + +#include "statusBar.moc" diff --git a/ksystemlog/src/statusBar.h b/ksystemlog/src/statusBar.h new file mode 100644 index 00000000..c886a0d7 --- /dev/null +++ b/ksystemlog/src/statusBar.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _STATUS_BAR_ +#define _STATUS_BAR_ + + +#include + +class QString; + + + +namespace KSystemLog { + +class StatusBarPrivate; + +/** + * Status Bar + */ +class StatusBar : public KStatusBar { + + Q_OBJECT + +public: + explicit StatusBar(QWidget* parent); + + virtual ~StatusBar(); + + void changeLineCountMessage(const QString& lineCountMessage); + void changeLastModification(const QTime& lastModification); + + void changeMessage(const QString& message); + +private slots: + void toggleHistory(); + void selectLastHistory(); +private: + + StatusBarPrivate* const d; + +}; + +} + +#endif // _STATUS_BAR_ diff --git a/ksystemlog/src/tabLogManager.cpp b/ksystemlog/src/tabLogManager.cpp new file mode 100644 index 00000000..7a9e24eb --- /dev/null +++ b/ksystemlog/src/tabLogManager.cpp @@ -0,0 +1,87 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "tabLogManager.h" + + + +#include + +#include "logging.h" + +#include "view.h" + +#include "logMode.h" +#include "defaults.h" +#include "logManager.h" + +class TabLogManagerPrivate { +public: + LogManager* logManager; + int newLinesCount; + +}; + +TabLogManager::TabLogManager(LogManager* logManager) : + d(new TabLogManagerPrivate()) { + + d->logManager = logManager; + + d->newLinesCount = 0; +} + +TabLogManager::~TabLogManager() { + + delete d->logManager->usedView(); + + delete d->logManager; + + delete d; +} + + +LogManager* TabLogManager::logManager() { + return d->logManager; +} + +void TabLogManager::addNewLinesCount(int newLines) { + d->newLinesCount += newLines; +} + +void TabLogManager::initNewLinesCount() { + d->newLinesCount = 0; +} + +QString TabLogManager::title() { + if (d->newLinesCount == 0) + return logModeName(); + else + return i18nc("Log mode name (added lines count)", "%1 (%2)", d->logManager->logMode()->name(), d->newLinesCount); +} + +QString TabLogManager::logModeName() { + if (d->logManager->logMode() == NULL) + return i18nc("Newly created tab", "Empty Log"); + else + return d->logManager->logMode()->name(); +} + +#include "tabLogManager.moc" diff --git a/ksystemlog/src/tabLogManager.h b/ksystemlog/src/tabLogManager.h new file mode 100644 index 00000000..e7864a31 --- /dev/null +++ b/ksystemlog/src/tabLogManager.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _TAB_LOG_MANAGER_H_ +#define _TAB_LOG_MANAGER_H_ + + +#include + +class QString; + +class LogManager; + + +class TabLogManagerPrivate; + +/** + * Class that wrap a LogManager inside a tabbed view + */ +class TabLogManager : public QObject { + + Q_OBJECT + +public: + TabLogManager(LogManager* logManager); + + virtual ~TabLogManager(); + + LogManager* logManager(); + + void addNewLinesCount(int newLines); + void initNewLinesCount(); + + QString title(); + +private: + QString logModeName(); + + TabLogManagerPrivate* const d; + +}; + + +#endif // _TAB_LOG_MANAGER_H_ diff --git a/ksystemlog/src/tabLogViewsWidget.cpp b/ksystemlog/src/tabLogViewsWidget.cpp new file mode 100644 index 00000000..0e3502ea --- /dev/null +++ b/ksystemlog/src/tabLogViewsWidget.cpp @@ -0,0 +1,421 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "tabLogViewsWidget.h" + +#include +#include +#include + +#include +#include +#include + +#include "logging.h" + +#include "view.h" +#include "logViewExport.h" + +#include "logMode.h" +#include "defaults.h" +#include "logManager.h" +#include "tabLogManager.h" +#include "logViewWidget.h" + +class TabLogViewsWidgetPrivate { +public: + QList tabLogManagers; + + QMenu* contextMenu; +}; + +TabLogViewsWidget::TabLogViewsWidget(QWidget* parent) : + KTabWidget(parent), + d(new TabLogViewsWidgetPrivate()) { + + d->contextMenu = NULL; + + QPushButton* tabNewTabButton=new QPushButton(SmallIcon(QLatin1String( "tab-new" )), QLatin1String( "" ), this); + connect(tabNewTabButton, SIGNAL(clicked()), this, SLOT(createTab())); + + tabNewTabButton->setToolTip(i18n("Create a new tab")); + tabNewTabButton->setWhatsThis(i18n("Creates a new tab which can display another log.")); + + QPushButton* tabCloseTabButton=new QPushButton(SmallIcon(QLatin1String( "tab-close" )), QLatin1String( "" ), this); + connect(tabCloseTabButton, SIGNAL(clicked()), this, SLOT(closeTab())); + + tabCloseTabButton->setToolTip(i18n("Close the current tab")); + tabCloseTabButton->setWhatsThis(i18n("Closes the current tab.")); + + setCornerWidget(tabNewTabButton, Qt::TopLeftCorner); + setCornerWidget(tabCloseTabButton, Qt::TopRightCorner); + + setUsesScrollButtons(true); + + //The context menu is managed manually + //setContextMenuPolicy(Qt::ActionsContextMenu); + + connect(this, SIGNAL(mouseDoubleClick()), this, SLOT(createTab())); + connect(this, SIGNAL(contextMenu(QPoint)), this, SLOT(showContextMenu(QPoint))); + connect(this, SIGNAL(contextMenu(QWidget*,QPoint)), this, SLOT(showContextMenu(QWidget*,QPoint))); + + //TODO Use this (need to connect to movedTab(int, int) signal and update the QList + //setTabReorderingEnabled(true); + + connect(this, SIGNAL(currentChanged(int)), this, SLOT(changeCurrentTab())); +} + +TabLogViewsWidget::~TabLogViewsWidget() { + //TODO Try to do not crash KSystemLog at exitting + /* + QList copy = d->tabLogManagers; + + //Delete existing tabs and related tabLogManagers + foreach(TabLogManager* tabLogManager, copy) { + logDebug() << "Deleting " << tabLogManager->logManager()->logMode()->name() << endl; + removePage(tabLogManager->logManager()->usedView()); + + d->tabLogManagers.removeAll(tabLogManager); + delete tabLogManager; + logDebug() << tabLogManager->logManager()->logMode()->name() << " deleted" << endl; + } + */ + + delete d; +} + +void TabLogViewsWidget::newTab(View* view) { + logDebug() << "Inserting to a new tab the view " << endl; + + //Add a tab at the end of the widget + insertTab(count(), view, SmallIcon(QLatin1String( NO_MODE_ICON )), i18n("No Log")); + + if (count()>1) + setTabBarHidden(false); + else + setTabBarHidden(true); + +} + +void TabLogViewsWidget::changeTab(View* view, const QIcon& icon, const QString& label) { + logDebug() << "Changing tab " << label << endl; + int index = indexOf(view); + setTabIcon(index, icon); + setTabText(index, label); +} + +QList TabLogViewsWidget::logManagers() { + QList logManagers; + foreach(TabLogManager* tabLogManager, d->tabLogManagers) { + logManagers.append(tabLogManager->logManager()); + } + + return logManagers; +} + +LogManager* TabLogViewsWidget::findRelatedLogManager(View* view) { + return findRelatedTabLogManager(view)->logManager(); +} + +TabLogManager* TabLogViewsWidget::findRelatedTabLogManager(View* view) { + foreach (TabLogManager* tabLogManager, d->tabLogManagers) { + if (tabLogManager->logManager()->usedView()==view) { + return tabLogManager; + } + } + + logError() << "No log manager found" << endl; + return NULL; +} + +TabLogManager* TabLogViewsWidget::activeTabLogManager() { + View* currentView=static_cast (currentWidget()); + + return findRelatedTabLogManager(currentView); +} + +LogManager* TabLogViewsWidget::activeLogManager() { + return activeTabLogManager()->logManager(); +} + +LogManager* TabLogViewsWidget::createTab() { + logDebug() << "Creating a new tab" << endl; + + return newTabLogManager()->logManager(); +} + +void TabLogViewsWidget::moveTabLeft() { + logDebug() << "Duplicate tab to the left" << endl; + + TabLogManager* currentTabLogManager=activeTabLogManager(); + int position=indexOf(currentTabLogManager->logManager()->usedView()); + + if (position<=0) { + logError() << "Tab Position <= 0 : " << position << endl; + return; + } + + d->tabLogManagers.removeAt(position); + d->tabLogManagers.insert(position-1, currentTabLogManager); + + moveTab(position, position-1); + +} + +void TabLogViewsWidget::moveTabRight() { + logDebug() << "Duplicate tab to the right" << endl; + + TabLogManager* currentTabLogManager=activeTabLogManager(); + int position=indexOf(currentTabLogManager->logManager()->usedView()); + + if (position>=count()-1) { + logError() << "Tab Position >= count()-1 : " << position << endl; + return; + } + + d->tabLogManagers.removeAt(position); + d->tabLogManagers.insert(position+1, currentTabLogManager); + + moveTab(position, position+1); + +} + +LogManager* TabLogViewsWidget::duplicateTab() { + logDebug() << "Duplicate current tab" << endl; + + TabLogManager* currentManager=activeTabLogManager(); + + TabLogManager* tabLogManager=newTabLogManager(); + + LogMode* mode=currentManager->logManager()->logMode(); + + load(mode, tabLogManager->logManager()); + + //Returns the newly created LogManager + return tabLogManager->logManager(); +} + +TabLogManager* TabLogViewsWidget::newTabLogManager() { + logDebug() << "Creating new View..." << endl; + + View* view = new View(this); + + logDebug() << "Creating new LogManager..." << endl; + + LogManager* logManager=new LogManager(view); + + //Signals from LogManager to Main Class + connect(logManager, SIGNAL(tabTitleChanged(View*,QIcon,QString)), this, SLOT(changeTab(View*,QIcon,QString))); + connect(logManager, SIGNAL(logUpdated(View*,int)), this, SLOT(changeTitleAddedLines(View*,int))); + + TabLogManager* tabLogManager = new TabLogManager(logManager); + d->tabLogManagers.append(tabLogManager); + + logDebug() << "New LogManager created" << endl; + + //Finally add the view to the tabs + newTab(view); + + emit logManagerCreated(logManager); + + setCurrentIndex(count()-1); + + //Set focus to the list + view->logViewWidget()->setFocus(); + + //Returns the newly created TabLogManager + return tabLogManager; + +} + +void TabLogViewsWidget::closeTab() { + if (count()==1) { + logError() << "Cannot close tab, one tab left" << endl; + return; + } + + TabLogManager* currentTabLogManager=activeTabLogManager(); + + d->tabLogManagers.removeAll(currentTabLogManager); + + removePage(currentTabLogManager->logManager()->usedView()); + if (count()==1) { + setTabBarHidden(true); + } + + delete currentTabLogManager; + +} + +void TabLogViewsWidget::load(LogMode* logMode, LogManager* manager) { + logDebug() << "Loading a new mode : " << logMode->name() << endl; + + if (manager==NULL || logMode==NULL) { + logError() << "Error while loading a manager " << endl; + return; + } + + //The manager is now using the Log mode passed in parameter + manager->initialize(logMode); + + //Launch the reading + manager->reload(); +} + +void TabLogViewsWidget::reloadCurrent() { + logDebug() << "Reloading current log manager..." << endl; + + LogManager* manager=activeLogManager(); + + if (manager!=NULL) { + manager->reload(); + } + +} + +void TabLogViewsWidget::reloadAll() { + logDebug() << "Reloading all tabs..." << endl; + + foreach (TabLogManager* tabLogManager, d->tabLogManagers) { + //Log manager without log mode does not need to be reloaded + if (tabLogManager->logManager()->logMode() == NULL) { + continue; + } + + //Do a simple reload if it is an open log mode + if (tabLogManager->logManager()->logMode()->id()==QLatin1String( "openLogMode" )) { + tabLogManager->logManager()->reload(); + continue; + } + + //Do a full loading of other log modes (needed if log files have been modified) + load(tabLogManager->logManager()->logMode(), tabLogManager->logManager()); + + } + + +} + +void TabLogViewsWidget::changeCurrentTab() { + logDebug() << "Changing current tab..." << endl; + + TabLogManager* tabLogManager=activeTabLogManager(); + + //Reinit the new lines count since last selection + tabLogManager->initNewLinesCount(); + + //If the tab displayed the new added line count, rename it to the default log mode name + changeTab(tabLogManager->logManager()->usedView(), logModeIcon(tabLogManager->logManager()->logMode()), tabLogManager->title()); + + logDebug() << "Current tab changed" << endl; +} + +void TabLogViewsWidget::changeReloadingTab(View* view, bool reloading) { + TabLogManager* tabLogManager = findRelatedTabLogManager(view); + + if (reloading == true) + changeTab(tabLogManager->logManager()->usedView(), KIcon( QLatin1String( "view-refresh" )), tabLogManager->title()); + else + changeTab(tabLogManager->logManager()->usedView(), logModeIcon(tabLogManager->logManager()->logMode()), tabLogManager->title()); +} + +void TabLogViewsWidget::changeTitleAddedLines(View* view, int addedLinesSinceLastUpdate) { + logDebug() << "Changing title" << addedLinesSinceLastUpdate << " added lines..." << endl; + LogManager* currentManager=activeLogManager(); + + //Only display added line on tab title if this is not an update of the current tab + if (currentManager->usedView() != view) { + TabLogManager* tabLogManager = findRelatedTabLogManager(view); + tabLogManager->addNewLinesCount(addedLinesSinceLastUpdate); + + //Update the tab title + changeTab(tabLogManager->logManager()->usedView(), logModeIcon(tabLogManager->logManager()->logMode()), tabLogManager->title()); + } +} +void TabLogViewsWidget::expandAllCurrentView() { + activeLogManager()->usedView()->logViewWidget()->expandAll(); +} + +void TabLogViewsWidget::collapseAllCurrentView() { + activeLogManager()->usedView()->logViewWidget()->collapseAll(); +} + +void TabLogViewsWidget::selectAllCurrentView() { + activeLogManager()->usedView()->logViewWidget()->selectAll(); +} + +void TabLogViewsWidget::fileSaveCurrentView() { + LogViewExport logViewExport(this, activeLogManager()->usedView()->logViewWidget()); + connect(&logViewExport, SIGNAL(statusBarChanged(QString)), this, SIGNAL(statusBarChanged(QString))); + logViewExport.fileSave(); +} + +void TabLogViewsWidget::copyToClipboardCurrentView() { + LogViewExport logViewExport(this, activeLogManager()->usedView()->logViewWidget()); + connect(&logViewExport, SIGNAL(statusBarChanged(QString)), this, SIGNAL(statusBarChanged(QString))); + logViewExport.copyToClipboard(); +} +void TabLogViewsWidget::sendMailCurrentView() { + LogViewExport logViewExport(this, activeLogManager()->usedView()->logViewWidget()); + connect(&logViewExport, SIGNAL(statusBarChanged(QString)), this, SIGNAL(statusBarChanged(QString))); + logViewExport.sendMail(); +} +void TabLogViewsWidget::printSelectionCurrentView() { + LogViewExport logViewExport(this, activeLogManager()->usedView()->logViewWidget()); + connect(&logViewExport, SIGNAL(statusBarChanged(QString)), this, SIGNAL(statusBarChanged(QString))); + logViewExport.printSelection(); +} + +QIcon TabLogViewsWidget::logModeIcon(LogMode* logMode) { + if ( logMode == NULL) + return KIcon(QLatin1String( NO_MODE_ICON )); + else + return logMode->icon(); + +} + +void TabLogViewsWidget::prepareContextMenu(bool /*onTab*/) { + if (d->contextMenu == NULL) { + d->contextMenu = new QMenu(this); + d->contextMenu->addActions(actions()); + } + + //TODO Disable some actions, depending of the onTab value +} + +void TabLogViewsWidget::showContextMenu(const QPoint& cursorPosition) { + logDebug() << "Showing context menu at " << cursorPosition << endl; + + prepareContextMenu(false); + + d->contextMenu->popup(cursorPosition); + +} + +void TabLogViewsWidget::showContextMenu(QWidget* tab, const QPoint& cursorPosition) { + logDebug() << "Showing context menu at " << cursorPosition << " at " << tab->objectName() << endl; + + prepareContextMenu(true); + + d->contextMenu->popup(cursorPosition); + +} +#include "tabLogViewsWidget.moc" diff --git a/ksystemlog/src/tabLogViewsWidget.h b/ksystemlog/src/tabLogViewsWidget.h new file mode 100644 index 00000000..85d368e5 --- /dev/null +++ b/ksystemlog/src/tabLogViewsWidget.h @@ -0,0 +1,116 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _TAB_LOG_VIEWS_WIDGET_H_ +#define _TAB_LOG_VIEWS_WIDGET_H_ + +#include +#include + +class QString; +class QIcon; + +class LogManager; +class TabLogManager; +class View; +class LogMode; + +class TabLogViewsWidgetPrivate; + +class TabLogViewsWidget : public KTabWidget { + + Q_OBJECT + +public: + TabLogViewsWidget(QWidget* parent = NULL); + + virtual ~TabLogViewsWidget(); + + QList logManagers(); + + LogManager* activeLogManager(); + + void load(LogMode* logMode, LogManager* manager); + +public slots: + + LogManager* createTab(); + LogManager* duplicateTab(); + + void closeTab(); + + void moveTabLeft(); + void moveTabRight(); + + void reloadCurrent(); + void reloadAll(); + + /** + * Display a reload icon on the specified view tab + */ + void changeReloadingTab(View* view, bool reloading); + + //Methods that transmit a signal to the current logViewWidget + void expandAllCurrentView(); + void collapseAllCurrentView(); + void selectAllCurrentView(); + + void fileSaveCurrentView(); + void copyToClipboardCurrentView(); + void sendMailCurrentView(); + void printSelectionCurrentView(); + +private slots: + void changeTab(View* view, const QIcon& icon, const QString& label); + + void changeCurrentTab(); + void changeTitleAddedLines(View*, int); + + void showContextMenu(const QPoint& cursorPosition); + void showContextMenu(QWidget* tab, const QPoint& cursorPosition); + +signals: + void tabCreationRequested(); + void tabClosingRequested(); + + void logManagerCreated(LogManager* manager); + + void statusBarChanged(const QString& message); + +private: + TabLogManager* newTabLogManager(); + + void newTab(View* view); + + LogManager* findRelatedLogManager(View* view); + + QIcon logModeIcon(LogMode* icon); + + TabLogManager* activeTabLogManager(); + TabLogManager* findRelatedTabLogManager(View* view); + + void prepareContextMenu(bool onTab); + + TabLogViewsWidgetPrivate* const d; + +}; + +#endif // _TAB_LOG_VIEWS_WIDGET_H_ diff --git a/ksystemlog/tests/CMakeLists.txt b/ksystemlog/tests/CMakeLists.txt new file mode 100644 index 00000000..3e593ccd --- /dev/null +++ b/ksystemlog/tests/CMakeLists.txt @@ -0,0 +1,53 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${CMAKE_CURRENT_SOURCE_DIR}/../src/config + ${CMAKE_CURRENT_BINARY_DIR}/../src/config + ${CMAKE_CURRENT_SOURCE_DIR}/../src/lib + ${CMAKE_CURRENT_SOURCE_DIR}/../src/modes/system + ${CMAKE_CURRENT_SOURCE_DIR}/../src/modes/kernel +) + +macro(ksystemlog_unit_tests) + foreach(unitTest ${ARGN}) + set(unitTest_sources + ${unitTest}.cpp + testUtil.cpp + ) + + qt4_add_resources( unitTest_sources testResources.qrc ) + + kde4_add_unit_test( + ${unitTest} + TESTNAME ksystemlog-${unitTest} + + ${unitTest_sources} + ) + + target_link_libraries( + ${unitTest} + ${QT_AND_KDECORE_LIBS} + ${QT_QTTEST_LIBRARY} + ${KDE4_KDEUI_LIBS} + ${KDE4_KIO_LIBS} + ${KDE4_KDECORE_LIBS} + ksystemlog_lib + ksystemlog_base_mode + ksystemlog_config + ksystemlog_system + ksystemlog_kernel + ) + endforeach(unitTest) +endmacro(ksystemlog_unit_tests) + +ksystemlog_unit_tests( + logModeFactoryTest + systemAnalyzerTest + kernelAnalyzerTest + + # Not real unit tests + kioLogFileReaderTest + findIncompatibleKioTest +) diff --git a/ksystemlog/tests/findIncompatibleKioTest.cpp b/ksystemlog/tests/findIncompatibleKioTest.cpp new file mode 100644 index 00000000..49adaac6 --- /dev/null +++ b/ksystemlog/tests/findIncompatibleKioTest.cpp @@ -0,0 +1,295 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "logging.h" + +class FindIncompatibleKioTest : public QObject { + +Q_OBJECT + +private slots: + + void initTestCase(); + + void find(); + +private: + void displayFoundMethods(const QStringList& methods, const KUrl& url, QTextStream& out); + + QStringList headerContent(const KUrl& url); + QString convertMethod(const QString& method); + + QMap findMethods(const QStringList& methods, const KUrl& url); + +}; + +void FindIncompatibleKioTest::initTestCase() { + +} + + +void FindIncompatibleKioTest::find() { + QString kdelibs = QLatin1String("/home/nicolas/workspace/kdelibs"); + QString kdebase = QLatin1String("/home/nicolas/workspace/kdebase"); + + QString outputPath = QLatin1String("/home/nicolas/compatibleKioSlaves.html"); + + QStringList existingMethods; + existingMethods << QLatin1String("void SlaveBase::openConnection(void)"); + existingMethods << QLatin1String("void SlaveBase::closeConnection(void)"); + existingMethods << QLatin1String("void SlaveBase::stat(KUrl const &)"); + existingMethods << QLatin1String("void SlaveBase::put(KUrl const &, int, bool, bool)"); + existingMethods << QLatin1String("void SlaveBase::special(const QByteArray &)"); + existingMethods << QLatin1String("void SlaveBase::listDir(KUrl const &)"); + existingMethods << QLatin1String("void SlaveBase::get(KUrl const & )"); + existingMethods << QLatin1String("void SlaveBase::open(KUrl const &, QIODevice::OpenMode)"); + existingMethods << QLatin1String("void SlaveBase::read(KIO::filesize_t)"); + existingMethods << QLatin1String("void SlaveBase::write(const QByteArray &)"); + existingMethods << QLatin1String("void SlaveBase::seek(KIO::filesize_t)"); + existingMethods << QLatin1String("void SlaveBase::close()"); + existingMethods << QLatin1String("void SlaveBase::mimetype(KUrl const &url)"); + existingMethods << QLatin1String("void SlaveBase::rename(KUrl const &, KUrl const &, bool)"); + existingMethods << QLatin1String("void SlaveBase::symlink(QString const &, KUrl const &, bool)"); + existingMethods << QLatin1String("void SlaveBase::copy(KUrl const &, KUrl const &, int, bool)"); + existingMethods << QLatin1String("void SlaveBase::del(KUrl const &, bool)"); + existingMethods << QLatin1String("void SlaveBase::setLinkDest(const KUrl &, const QString&)"); + existingMethods << QLatin1String("void SlaveBase::mkdir(KUrl const &, int)"); + existingMethods << QLatin1String("void SlaveBase::chmod(KUrl const &, int)"); + existingMethods << QLatin1String("void SlaveBase::setModificationTime(KUrl const &, const QDateTime&)"); + existingMethods << QLatin1String("void SlaveBase::chown(KUrl const &, const QString &, const QString &)"); + existingMethods << QLatin1String("void SlaveBase::setSubUrl(KUrl const &)"); + existingMethods << QLatin1String("void SlaveBase::multiGet(const QByteArray &)"); + + //logDebug() << existingMethods << endl; + + QFile file(outputPath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + logError() << "Unable to open the output file" << outputPath << endl; + return; + } + + QTextStream out(&file); + + out << "" << endl; + out << "" << endl; + out << "" << endl; + out << "" << endl; + out << "" << endl; + + out << "
KIO Slaves Analyzing
" << endl; + out << "" << endl; + + QList parsingUrls; + + parsingUrls.append(KUrl(kdelibs + QLatin1String("/kioslave/http/http.h"))); + parsingUrls.append(KUrl(kdelibs + QLatin1String("/kioslave/ftp/ftp.h"))); + parsingUrls.append(KUrl(kdelibs + QLatin1String("/kioslave/file/file.h"))); + parsingUrls.append(KUrl(kdelibs + QLatin1String("/kioslave/metainfo/metainfo.h"))); + + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/about/kio_about.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/cgi/cgi.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/filter/filter.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/finger/kio_finger.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/fish/fish.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/floppy/kio_floppy.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/info/info.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/man/kio_man.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/media/kio_media.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/nfs/kio_nfs.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/remote/kio_remote.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/settings/kio_settings.cc"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/sftp/kio_sftp.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/smb/kio_smb.h"))); + //parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/tar/tar.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/thumbnail/thumbnail.h"))); + parsingUrls.append(KUrl(kdebase + QLatin1String("/runtime/kioslave/trash/kio_trash.h"))); + + + out << "
" << endl; + out << "

Analyzed KIO Slaves

" << endl; + out << "
    " << endl; + foreach(const KUrl &url, parsingUrls) { + out << "
  • " << url.path() << "
  • " << endl; + } + out << "
" << endl; + + + foreach(const KUrl &url, parsingUrls) { + displayFoundMethods(existingMethods, url, out); + } + + out << "
" << endl; + out << "

SlaveBase methods to implement

" << endl; + out << "
    " << endl; + foreach(const QString &method, existingMethods) { + out << "
  • " << method << "
  • " << endl; + } + out << "
" << endl; + + out << "" << endl; + out << "" << endl; + + file.close(); +} + +void FindIncompatibleKioTest::displayFoundMethods(const QStringList& methods, const KUrl& url, QTextStream& out) { + QMap foundMethods = findMethods(methods, url); + + + out << endl; + out << "
" << endl; + out << "

" << url.path() << "

" << endl; + + if (foundMethods.isEmpty()) { + out << "No file found" << endl; + out << "
" << endl; + return; + } + + out << "
" << endl; + out << "

Found and Not Found methods :

" << endl; + out << "
    " << endl; + + //Found methods + QMapIterator i(foundMethods); + while (i.hasNext()) { + i.next(); + + if (i.value() == true) { + out << "
  • " << i.key() << "
  • " << endl; + } + } + + + //Found methods + i = foundMethods; + while (i.hasNext()) { + i.next(); + + if (i.value() == false) { + out << "
  • " << i.key() << "
  • " << endl; + } + } + + out << "
" << endl; + +} + +QMap FindIncompatibleKioTest::findMethods(const QStringList& methods, const KUrl& url) { + + QStringList lines = headerContent(url); + if (lines.isEmpty()) + return QMap(); + + QMap foundMethods; + foreach(const QString &method, methods) { + foundMethods.insert(method, false); + } + + foreach(QString line, lines) { + line = convertMethod(line); + + if (line.contains(QLatin1String("void")) == false) + continue; + + //logDebug() << "Line" << convertMethod(line) << endl; + + foreach(const QString &method, methods) { + if (line == convertMethod(method)) { + foundMethods.insert(method, true); + } + /* + else { + logDebug() << method << " != " << line << endl; + } + */ + } + + } + + logDebug() << endl; + + return foundMethods; +} + +QString FindIncompatibleKioTest::convertMethod(const QString& method) { + QString result(method); + result = result.remove(QLatin1String("SlaveBase::")); + result = result.remove(QLatin1String("virtual")); + // result = result.remove(QLatin1String(" ")); + // result = result.replace('\t', ' '); + result = result.remove(QLatin1String(";")); + result = result.simplified(); + + int firstParenthesis = result.indexOf(QLatin1String("(")); + int lastParenthesis = result.indexOf(QLatin1String(")")); + if (firstParenthesis != -1&& lastParenthesis != -1) + result = result.remove(firstParenthesis+1, lastParenthesis-firstParenthesis-1); + + return result; +} + +QStringList FindIncompatibleKioTest::headerContent(const KUrl& url) { + QStringList kioHeaderContent; + + QString tmpFile; + if (KIO::NetAccess::download(url, tmpFile, new QWidget()) ) { + + QFile file(tmpFile); + file.open(QIODevice::ReadOnly); + + //If the file does not exist + if (!file.exists()) { + return QStringList(); + } + + QTextStream inputStream(&file); + while (inputStream.atEnd() == false) { + kioHeaderContent.append(inputStream.readLine()); + } + + file.close(); + + KIO::NetAccess::removeTempFile(tmpFile ); + } else { + logDebug() << KIO::NetAccess::lastErrorString() << endl;; + } + + return kioHeaderContent; +} + +QTEST_KDEMAIN(FindIncompatibleKioTest, GUI) + +#include "FindIncompatibleKioTest.moc" diff --git a/ksystemlog/tests/kernelAnalyzerTest.cpp b/ksystemlog/tests/kernelAnalyzerTest.cpp new file mode 100644 index 00000000..f7beb2ef --- /dev/null +++ b/ksystemlog/tests/kernelAnalyzerTest.cpp @@ -0,0 +1,178 @@ +/*************************************************************************** + * KKernelLog, a kernel log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include +#include +#include + +#include + +#include +#include + +#include "testUtil.h" + +#include "analyzer.h" +#include "globals.h" + +#include "logLevel.h" +#include "logFile.h" +#include "logViewModel.h" +#include "logViewWidget.h" + +#include "ksystemlogConfig.h" + +#include "logging.h" + +#include "kernelAnalyzer.h" +#include "localLogFileReader.h" + +/** + * Reimplements the Kernel Analyzer using a Local File Reader + */ +class KernelAnalyzerLocalReader : public KernelAnalyzer { + public: + + KernelAnalyzerLocalReader(LogMode* logMode) : + KernelAnalyzer(logMode) { + + } + + virtual ~KernelAnalyzerLocalReader() { + + } + + QDateTime findStartupTime() { + return startupDateTime; + } + + protected: + LogFileReader* createLogFileReader(const LogFile& logFile) { + return new LocalLogFileReader(logFile); + } + +}; + + +class KernelAnalyzerTest : public QObject { + + Q_OBJECT + +private slots: + + void initTestCase(); + + void testUbuntuDmesg(); + void testSuseDmesg(); + +private: + void compareWithMinTime(QList lines, const QDateTime& minTime); + +private: + TestUtil testUtil; + +}; + +void KernelAnalyzerTest::initTestCase() { + testUtil.registerLogModeFactories(); +} + +void KernelAnalyzerTest::testUbuntuDmesg() { + + //Specifical configuration + KSystemLogConfig::setMaxLines(1000); + KSystemLogConfig::setDeleteDuplicatedLines(false); + + LogMode* logMode = Globals::instance()->findLogMode(QLatin1String("kernelLogMode")); + KernelAnalyzerLocalReader* kernelAnalyzer = new KernelAnalyzerLocalReader(logMode); + LogViewModel* model = testUtil.defineLogViewModel(kernelAnalyzer); + + QVERIFY(kernelAnalyzer); + QVERIFY(model); + + QList logFiles = testUtil.createLogFiles(QLatin1String(":/testFiles/kernel/ubuntu.dmesg")); + + kernelAnalyzer->setLogFiles(logFiles); + + kernelAnalyzer->watchLogFiles(true); + + QCOMPARE(model->itemCount(), 25); + QCOMPARE(model->isEmpty(), false); + + QList logLines = model->logLines(); + + QStringList items = QStringList() << QLatin1String("ADDRCONF(NETDEV_UP)") << QLatin1String("eth0: link is not ready"); + QDateTime assertedDateTime = kernelAnalyzer->findStartupTime(); + assertedDateTime = assertedDateTime.addSecs(22); + assertedDateTime = assertedDateTime.addMSecs(232); + + testUtil.testLine( + logLines.at(0), + logFiles.at(0).url().path(), + Globals::instance()->informationLogLevel(), + assertedDateTime, + items + ); + + testUtil.destroyReader(kernelAnalyzer); +} + +void KernelAnalyzerTest::testSuseDmesg() { + + //Specifical configuration + KSystemLogConfig::setMaxLines(1000); + KSystemLogConfig::setDeleteDuplicatedLines(false); + + LogMode* logMode = Globals::instance()->findLogMode(QLatin1String("kernelLogMode")); + KernelAnalyzerLocalReader* kernelAnalyzer = new KernelAnalyzerLocalReader(logMode); + LogViewModel* model = testUtil.defineLogViewModel(kernelAnalyzer); + + QVERIFY(kernelAnalyzer); + QVERIFY(model); + + QList logFiles = testUtil.createLogFiles(QLatin1String(":/testFiles/kernel/suse.dmesg")); + + kernelAnalyzer->setLogFiles(logFiles); + + kernelAnalyzer->watchLogFiles(true); + + QCOMPARE(model->itemCount(), 23); + QCOMPARE(model->isEmpty(), false); + + QList logLines = model->logLines(); + + QStringList items = QStringList() << QLatin1String("r8169") << QLatin1String("eth0: link down"); + + testUtil.testLine( + logLines.at(0), + logFiles.at(0).url().path(), + Globals::instance()->informationLogLevel(), + kernelAnalyzer->findStartupTime(), + items + ); + + testUtil.destroyReader(kernelAnalyzer); +} + + +QTEST_KDEMAIN(KernelAnalyzerTest, GUI) + +#include "kernelAnalyzerTest.moc" diff --git a/ksystemlog/tests/kioLogFileReaderTest.cpp b/ksystemlog/tests/kioLogFileReaderTest.cpp new file mode 100644 index 00000000..90b5f1f5 --- /dev/null +++ b/ksystemlog/tests/kioLogFileReaderTest.cpp @@ -0,0 +1,85 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include + +#include +#include + +#include "logFile.h" +#include "kioLogFileReader.h" + +#include "testUtil.h" + +#include "logging.h" + +class KioLogFileReaderTest: public QObject { + + Q_OBJECT + +private slots: + + void initTestCase(); + + void testKioLogFileReader(); + + void readLine(const QString& line); + +private: + TestUtil testUtil; + +}; + + +void KioLogFileReaderTest::initTestCase() { + logDebug() << "Hello" << endl; +} + + +void KioLogFileReaderTest::testKioLogFileReader() { + /* + QList logFiles = testUtil.createLogFiles(":/logs/logFileReader/file.txt"); + LogFile logFile = logFiles.first(); + */ + + ///home/nicolas/test.txt + LogFile logFile(KUrl("http://localhost/test.txt"), Globals::instance()->informationLogLevel()); + + KioLogFileReader* logFileReader = new KioLogFileReader(logFile); + + logFileReader->open(); + + + connect(logFileReader, SIGNAL(lineRead(QString)), this, SLOT(readLine(QString))); + + QTest::qWait(100000); + + + +} + +void KioLogFileReaderTest::readLine(const QString& line) { + logDebug() << "Line " << line << endl; +} + +QTEST_KDEMAIN(KioLogFileReaderTest, GUI) + +#include "logFileReaderTest.moc" diff --git a/ksystemlog/tests/logModeFactoryTest.cpp b/ksystemlog/tests/logModeFactoryTest.cpp new file mode 100644 index 00000000..607df8fb --- /dev/null +++ b/ksystemlog/tests/logModeFactoryTest.cpp @@ -0,0 +1,82 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + + +#include +#include + +#include "testUtil.h" + +#include "analyzer.h" +#include "globals.h" + +#include "logLevel.h" +#include "logMode.h" +#include "logFile.h" +#include "logViewModel.h" +#include "logViewWidget.h" + +#include "logging.h" + +class LogModeFactoryTest: public QObject { + + Q_OBJECT + +private slots: + + void initTestCase(); + + void testLogModes(); + + void testReaderFactory(); +private: + TestUtil testUtil; + +}; + + +void LogModeFactoryTest::initTestCase() { + testUtil.registerLogModeFactories(); +} + + +void LogModeFactoryTest::testLogModes() { + + LogMode* systemLogMode = Globals::instance()->findLogMode(QLatin1String("systemLogMode")); + QVERIFY(systemLogMode); + + QCOMPARE(systemLogMode->id(), QString::fromLatin1("systemLogMode")); + +} + +void LogModeFactoryTest::testReaderFactory() { + LogViewModel* model = NULL; + Analyzer* systemAnalyzer = testUtil.createAnalyzer(QLatin1String("systemLogMode"), &model); + + QVERIFY(systemAnalyzer); + QVERIFY(model); + + +} + +QTEST_KDEMAIN(LogModeFactoryTest, GUI) + +#include "logModeFactoryTest.moc" diff --git a/ksystemlog/tests/systemAnalyzerTest.cpp b/ksystemlog/tests/systemAnalyzerTest.cpp new file mode 100644 index 00000000..8ea2b04c --- /dev/null +++ b/ksystemlog/tests/systemAnalyzerTest.cpp @@ -0,0 +1,365 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include +#include +#include + +#include + +#include +#include + +#include "testUtil.h" + +#include "analyzer.h" +#include "globals.h" + +#include "logLevel.h" +#include "logFile.h" +#include "logViewModel.h" +#include "logViewWidget.h" + +#include "ksystemlogConfig.h" + +#include "logging.h" + +class SystemAnalyzerTest : public QObject { + + Q_OBJECT + +private slots: + + void initTestCase(); + + void testMultipleLines(); + void testOneLine(); + void testTwoLines(); + void testStrangeLines(); + + void testDeleteProcessIdentifier(); + void testMaxLines(); + void testRemoveDuplicates(); + +private: + void compareWithMinTime(QList lines, const QDateTime& minTime); + +private: + TestUtil testUtil; + +}; + +void SystemAnalyzerTest::initTestCase() { + testUtil.registerLogModeFactories(); +} + +void SystemAnalyzerTest::testOneLine() { + + LogViewModel* model= NULL; + + Analyzer* systemAnalyzer = testUtil.createAnalyzer(QLatin1String("systemLogMode"), &model); + QVERIFY(systemAnalyzer); + QVERIFY(model); + + QList logFiles = testUtil.createLogFiles(QLatin1String(":/testFiles/default/one-line.log")); + + systemAnalyzer->setLogFiles(logFiles); + + systemAnalyzer->watchLogFiles(true); + + QCOMPARE(model->itemCount(), 1); + QCOMPARE(model->isEmpty(), false); + + QList logLines = model->logLines(); + + QStringList items = QStringList() << QLatin1String("localhost") << QLatin1String("kernel") << QLatin1String("[11663.656000] eth1: no IPv6 routers present"); + + const int year = QDate::currentDate().year(); + testUtil.testLine(logLines.at(0), logFiles.at(0).url().path(), logFiles.at(0).defaultLogLevel(), QDateTime(QDate(year, 8, 21), QTime(22, 52, 44)), items + + ); + + testUtil.destroyReader(systemAnalyzer); +} + +void SystemAnalyzerTest::testTwoLines() { + + LogViewModel* model= NULL; + + Analyzer* systemAnalyzer = testUtil.createAnalyzer(QLatin1String("systemLogMode"), &model); + QVERIFY(systemAnalyzer); + QVERIFY(model); + + //Specifical configuration + KSystemLogConfig::setMaxLines(1000); + + QList logFiles = testUtil.createLogFiles(QLatin1String(":/testFiles/default/two-lines.log")); + + systemAnalyzer->setLogFiles(logFiles); + + systemAnalyzer->watchLogFiles(true); + + QCOMPARE(model->itemCount(), 2); + QCOMPARE(model->isEmpty(), false); + + testUtil.destroyReader(systemAnalyzer); +} + +void SystemAnalyzerTest::testMultipleLines() { + LogViewModel* model= NULL; + + Analyzer* systemAnalyzer = testUtil.createAnalyzer(QLatin1String("systemLogMode"), &model); + QVERIFY(systemAnalyzer); + QVERIFY(model); + + //Specifical configuration + KSystemLogConfig::setMaxLines(1000); + + QList logFiles = testUtil.createLogFiles(QLatin1String(":/testFiles/system/system.log")); + LogFile logFile = logFiles.at(0); + + systemAnalyzer->setLogFiles(logFiles); + + + QSignalSpy stateSpy(systemAnalyzer, SIGNAL(logUpdated(int))); + QList arguments; + + //Each watching relaunch a reading + systemAnalyzer->watchLogFiles(true); + + //Assert that the model has been updated + QCOMPARE(model->itemCount(), 24); + + //Assert that the logUpdated signal emits the right count + QCOMPARE(stateSpy.count(), 1); + arguments = stateSpy.takeFirst(); + QCOMPARE(arguments.at(0).toInt(), 24); + + //Each watching relaunch a reading + systemAnalyzer->watchLogFiles(true); + + //Assert that the model has been updated + QCOMPARE(model->itemCount(), 48); + + //Assert that the logUpdated signal emits the right count + QCOMPARE(stateSpy.count(), 1); + arguments = stateSpy.takeFirst(); + QCOMPARE(arguments.at(0).toInt(), 24); + + QStringList addedLines; + addedLines << QLatin1String("Aug 18 17:04:28 localhost test: Test line 1"); + addedLines << QLatin1String("Aug 18 17:04:30 localhost test: Test line 2"); + + testUtil.addLogLines(logFile.url().path(), addedLines); + + //Assert that the model has been updated + QCOMPARE(model->itemCount(), 50); + + //Assert that the logUpdated signal emits the right count + QCOMPARE(stateSpy.count(), 1); + arguments = stateSpy.takeFirst(); + QCOMPARE(arguments.at(0).toInt(), 2); + + testUtil.destroyReader(systemAnalyzer); +} + +void SystemAnalyzerTest::testStrangeLines() { + LogViewModel* model= NULL; + + Analyzer* systemAnalyzer = testUtil.createAnalyzer(QLatin1String("systemLogMode"), &model); + QVERIFY(systemAnalyzer); + QVERIFY(model); + + //Specifical configuration + KSystemLogConfig::setMaxLines(1000); + KSystemLogConfig::setDeleteProcessIdentifier(false); + + QList logFiles = testUtil.createLogFiles(QLatin1String(":/testFiles/system/strange-lines.log")); + + systemAnalyzer->setLogFiles(logFiles); + + systemAnalyzer->watchLogFiles(true); + + QCOMPARE(model->itemCount(), 8); + + //i18n("undefined") + QString undefined = QLatin1String(""); + + QStringList items; + + const int year = QDate::currentDate().year(); + QSKIP("This test/code is broken", SkipAll); + + //Classical log line + items = QStringList() << QLatin1String("localhost") << QLatin1String("kernel") << QLatin1String("Kernel panic"); + testUtil.testLine(model->logLines().at(0), logFiles.at(0).url().path(), logFiles.at(0).defaultLogLevel(), QDateTime(QDate(year, 8, 10), QTime(17, 04, 28)), items); + + //-- MARK -- log line + items = QStringList() << QLatin1String("localhost") << QLatin1String("syslog") << QLatin1String("-- MARK --"); + testUtil.testLine(model->logLines().at(1), logFiles.at(0).url().path(), logFiles.at(0).defaultLogLevel(), QDateTime(QDate(year, 8, 11), QTime(13, 49, 38)), items); + + //Last message repeated n time log line + items = QStringList() << QLatin1String("localhost") << QLatin1String("syslog") << QLatin1String("last message repeated 4 times"); + testUtil.testLine(model->logLines().at(2), logFiles.at(0).url().path(), logFiles.at(0).defaultLogLevel(), QDateTime(QDate(year, 8, 12), QTime(18, 10, 32)), items); + + //"Aug 13 17:04:28 testprocess: Say ouhou " -> No host name + items = QStringList() << undefined << QLatin1String("testprocess") << QLatin1String("Say ouhou "); + testUtil.testLine(model->logLines().at(3), logFiles.at(0).url().path(), logFiles.at(0).defaultLogLevel(), QDateTime(QDate(year, 8, 13), QTime(17, 04, 28)), items); + + //"Aug 14 17:04:28 localhost kernel say ouhou" -> No process name and not a syslog message + items = QStringList() << QLatin1String("localhost") << undefined << QLatin1String("kernel say ouhou"); + testUtil.testLine(model->logLines().at(4), logFiles.at(0).url().path(), logFiles.at(0).defaultLogLevel(), QDateTime(QDate(year, 8, 14), QTime(17, 04, 28)), items); + + //"Aug 15 22:39:01 localhost /USR/SBIN/CRON[9433]: (root) CMD ( [ -d /var/lib/php5 ] && find /var/lib/php5/ -type f -cmin +$(/usr/lib/php5/maxlifetime) -print0 | xargs -r -0 rm)" -> Long log line + items = QStringList() << QLatin1String("localhost") << QLatin1String("/USR/SBIN/CRON[9433]") + << QLatin1String("(root) CMD ( [ -d /var/lib/php5 ] && find /var/lib/php5/ -type f -cmin +$(/usr/lib/php5/maxlifetime) -print0 | xargs -r -0 rm)"); + testUtil.testLine(model->logLines().at(5), logFiles.at(0).url().path(), logFiles.at(0).defaultLogLevel(), QDateTime(QDate(year, 8, 15), QTime(22, 39, 01)), items); + + //"blablalbla" -> Invalid line + items = QStringList() << undefined << undefined << QLatin1String(""); + QCOMPARE(model->logLines().at(6)->logItems(), items); + + //"" -> Empty line + items = QStringList() << undefined << undefined << QLatin1String("blablalbla"); + QCOMPARE(model->logLines().at(7)->logItems(), items); + + testUtil.destroyReader(systemAnalyzer); +} + +void SystemAnalyzerTest::testDeleteProcessIdentifier() { + LogViewModel* model= NULL; + + Analyzer* systemAnalyzer = testUtil.createAnalyzer(QLatin1String("systemLogMode"), &model); + QVERIFY(systemAnalyzer); + QVERIFY(model); + + //Specifical configuration + KSystemLogConfig::setMaxLines(1000); + KSystemLogConfig::setDeleteProcessIdentifier(true); + + QList logFiles = testUtil.createLogFiles(QLatin1String(":/testFiles/system/delete-process-identifier.log")); + + systemAnalyzer->setLogFiles(logFiles); + + systemAnalyzer->watchLogFiles(true); + + QCOMPARE(model->itemCount(), 2); + + QStringList items; + + //Cron log line + items = QStringList() << QLatin1String("localhost") << QLatin1String("/USR/SBIN/CRON") << QLatin1String("Hello"); + QCOMPARE(model->logLines().at(0)->logItems(), items); + + //"f" process + items = QStringList() << QLatin1String("localhost") << QLatin1String("f") <logLines().at(1)->logItems(), items); + + testUtil.destroyReader(systemAnalyzer); +} + +void SystemAnalyzerTest::testMaxLines() { + LogViewModel* model= NULL; + + Analyzer* systemAnalyzer = testUtil.createAnalyzer(QLatin1String("systemLogMode"), &model); + QVERIFY(systemAnalyzer); + QVERIFY(model); + + //Specifical configuration + KSystemLogConfig::setMaxLines(5); + + QList logFiles = testUtil.createLogFiles(QLatin1String(":/testFiles/system/max-lines.log")); + LogFile logFile = logFiles.at(0); + + systemAnalyzer->setLogFiles(logFiles); + + systemAnalyzer->watchLogFiles(true); + + QCOMPARE(model->itemCount(), 5); + + //Asserts that there is no line before the oldest one's time + compareWithMinTime(model->logLines(), QDateTime(QDate(2007, 8, 18), QTime(11, 0, 0))); + + QStringList addedLines; + + addedLines << QLatin1String("Aug 18 10:00:00 localhost test: Line 8"); + testUtil.addLogLines(logFile.url().path(), addedLines); + + QCOMPARE(model->itemCount(), 5); + compareWithMinTime(model->logLines(), QDateTime(QDate(2007, 8, 18), QTime(11, 0, 0))); + + //Specifical configuration + KSystemLogConfig::setMaxLines(6); + + addedLines.clear(); + addedLines << QLatin1String("Aug 18 10:00:00 localhost test: Line 9"); + addedLines << QLatin1String("Aug 18 19:00:00 localhost test: Line 10"); + testUtil.addLogLines(logFile.url().path(), addedLines); + + QCOMPARE(model->itemCount(), 6); + compareWithMinTime(model->logLines(), QDateTime(QDate(2007, 8, 18), QTime(11, 0, 0))); + + addedLines.clear(); + addedLines << QLatin1String("Aug 18 20:00:00 localhost test: Line 11"); + addedLines << QLatin1String("Aug 18 21:00:00 localhost test: Line 12"); + testUtil.addLogLines(logFile.url().path(), addedLines); + + QCOMPARE(model->itemCount(), 6); + compareWithMinTime(model->logLines(), QDateTime(QDate(2007, 8, 18), QTime(13, 0, 0))); + + testUtil.destroyReader(systemAnalyzer); +} + +void SystemAnalyzerTest::compareWithMinTime(QList logLines, const QDateTime& minTime) { + logDebug() << "Min time : " << minTime.toString() << endl; + + foreach (LogLine* logLine, logLines) { + if (logLine->time() < minTime) { + QFAIL( QString::fromLatin1("The line '%1' has a lesser time than the required min time (%2)").arg(logLine->logItems().join(QLatin1String(" "))).arg(logLine->time().toString()).toUtf8() ); + } + } + +} + +void SystemAnalyzerTest::testRemoveDuplicates() { + LogViewModel* model= NULL; + + Analyzer* systemAnalyzer = testUtil.createAnalyzer(QLatin1String("systemLogMode"), &model); + QVERIFY(systemAnalyzer); + QVERIFY(model); + + //Specifical configuration + KSystemLogConfig::setMaxLines(1000); + KSystemLogConfig::setDeleteDuplicatedLines(true); + + QList logFiles = testUtil.createLogFiles(QLatin1String(":/testFiles/system/duplicate-lines.log")); + + systemAnalyzer->setLogFiles(logFiles); + + systemAnalyzer->watchLogFiles(true); + + QCOMPARE(model->itemCount(), 5); +} + + +QTEST_KDEMAIN(SystemAnalyzerTest, GUI) + +#include "systemAnalyzerTest.moc" diff --git a/ksystemlog/tests/testFiles/apache/access.log b/ksystemlog/tests/testFiles/apache/access.log new file mode 100644 index 00000000..b49a2e79 --- /dev/null +++ b/ksystemlog/tests/testFiles/apache/access.log @@ -0,0 +1,19 @@ +127.0.0.1 - - [11/Sep/2007:07:52:25 +0200] "GET /favicon.ico HTTP/1.1" 404 318 "-" "Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.7 (like Gecko)" +127.0.0.1 - - [11/Sep/2007:07:52:25 +0200] "GET /favicon.ico HTTP/1.1" 404 318 "-" "Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.7 (like Gecko)" +::1 - - [11/Sep/2007:07:53:02 +0200] "GET / HTTP/1.0" 200 1128 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +::1 - - [11/Sep/2007:07:53:02 +0200] "GET / HTTP/1.0" 200 1128 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +::1 - - [11/Sep/2007:07:53:02 +0200] "GET / HTTP/1.0" 200 1128 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +::1 - - [11/Sep/2007:07:53:02 +0200] "GET / HTTP/1.0" 200 1128 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +::1 - - [11/Sep/2007:07:53:02 +0200] "GET / HTTP/1.0" 200 1128 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +::1 - - [11/Sep/2007:07:53:02 +0200] "GET / HTTP/1.0" 200 1128 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +::1 - - [11/Sep/2007:07:53:02 +0200] "GET / HTTP/1.0" 200 1128 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +::1 - - [11/Sep/2007:07:53:02 +0200] "GET / HTTP/1.0" 200 1128 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +127.0.0.1 - - [11/Sep/2007:07:53:56 +0200] "GET / HTTP/1.0" 200 1130 "-" "Wget/1.10.2" +127.0.0.1 - - [11/Sep/2007:07:54:48 +0200] "GET /file.txt HTTP/1.1" 200 219 "-" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070827 Ubuntu/7.10 (gutsy) Firefox/2.0.0.6" +127.0.0.1 - - [11/Sep/2007:07:54:48 +0200] "GET /favicon.ico HTTP/1.1" 404 318 "-" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070827 Ubuntu/7.10 (gutsy) Firefox/2.0.0.6" +::1 - - [11/Sep/2007:09:00:20 +0200] "GET / HTTP/1.0" 200 1308 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +::1 - - [11/Sep/2007:09:00:20 +0200] "GET / HTTP/1.0" 200 1308 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +::1 - - [11/Sep/2007:09:00:20 +0200] "GET / HTTP/1.0" 200 1308 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +::1 - - [11/Sep/2007:09:00:20 +0200] "GET / HTTP/1.0" 200 1308 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +::1 - - [11/Sep/2007:09:00:20 +0200] "GET / HTTP/1.0" 200 1308 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" +::1 - - [11/Sep/2007:09:00:20 +0200] "GET / HTTP/1.0" 200 1308 "-" "Apache/2.2.4 (Ubuntu) DAV/2 SVN/1.4.4 PHP/5.2.3-1ubuntu5 (internal dummy connection)" diff --git a/ksystemlog/tests/testFiles/apache/error.log b/ksystemlog/tests/testFiles/apache/error.log new file mode 100644 index 00000000..cdb1a0f3 --- /dev/null +++ b/ksystemlog/tests/testFiles/apache/error.log @@ -0,0 +1,34 @@ +PHP Warning: Module 'PDO' already loaded in Unknown on line 0 +PHP Warning: Module 'pdo_mysql' already loaded in Unknown on line 0 +[Sun Sep 02 06:25:34 2007] [notice] Apache/2.2.3 (Ubuntu) mod_jk/1.2.18 PHP/5.2.1 configured -- resuming normal operations +[Mon Sep 03 09:48:06 2007] [error] [client 192.168.2.209] File does not exist: /home/www/favicon.ico +[Sun Sep 16 07:35:19 2007] [notice] Apache/2.2.4 (Ubuntu) PHP/5.2.3-1ubuntu5 configured -- resuming normal operations +[Tue Sep 18 13:32:21 2007] [notice] caught SIGWINCH, shutting down gracefully +[Tue Sep 18 13:33:15 2007] [notice] Apache/2.2.4 (Ubuntu) PHP/5.2.3-1ubuntu5 configured -- resuming normal operations +[Tue Sep 18 17:08:35 2007] [error] [client 127.0.0.1] File does not exist: /var/www/timeout.ph +[Tue Sep 18 17:08:36 2007] [error] [client 127.0.0.1] File does not exist: /var/www/favicon.ico +[Tue Sep 18 17:08:39 2007] [error] [client 127.0.0.1] File does not exist: /var/www/favicon.ico +[Tue Sep 18 17:08:40 2007] [error] [client 127.0.0.1] File does not exist: /var/www/timeout.phop +[Wed Sep 19 10:00:19 2007] [notice] caught SIGWINCH, shutting down gracefully +[Wed Sep 19 10:01:34 2007] [notice] Apache/2.2.4 (Ubuntu) PHP/5.2.3-1ubuntu5 configured -- resuming normal operations +[Wed Sep 19 10:02:37 2007] [error] [client 127.0.0.1] File does not exist: /var/www/favicon.ico +[Wed Sep 19 10:02:37 2007] [error] [client 127.0.0.1] File does not exist: /var/www/favicon.ico +[Wed Sep 19 10:35:22 2007] [error] [client 127.0.0.1] File does not exist: /var/www/favicon.ico +[Wed Sep 19 10:40:28 2007] [error] [client 127.0.0.1] File does not exist: /var/www/favicon.ico +[Wed Sep 19 10:54:47 2007] [error] [client 127.0.0.1] File does not exist: /var/www/favicon.ico +[Wed Sep 19 10:57:22 2007] [error] [client 127.0.0.1] File does not exist: /var/www/favicon.ico +[Wed Sep 19 10:58:48 2007] [error] [client 127.0.0.1] File does not exist: /var/www/favicon.ico +[Wed Sep 19 10:59:31 2007] [error] [client 127.0.0.1] File does not exist: /var/www/favicon.ico +[Wed Sep 19 12:06:07 2007] [notice] caught SIGWINCH, shutting down gracefully +[Wed Sep 19 12:06:18 2007] [notice] Apache/2.2.4 (Ubuntu) PHP/5.2.3-1ubuntu5 configured -- resuming normal operations +[Wed Sep 19 12:06:33 2007] [error] [client 127.0.0.1] client denied by server configuration: /var/www/sdf +[Wed Sep 19 12:06:38 2007] [error] [client 127.0.0.1] client denied by server configuration: /var/www/timeout.php +[Wed Sep 19 12:06:42 2007] [error] [client 127.0.0.1] client denied by server configuration: /var/www/timeout-flusher.php +[Wed Sep 19 12:07:50 2007] [alert] [client 127.0.0.1] /var/www/.htaccess: Invalid command 'mod_gzip_on', perhaps misspelled or defined by a module not included in the server configuration +[Wed Sep 19 12:07:52 2007] [alert] [client 127.0.0.1] /var/www/.htaccess: Invalid command 'mod_gzip_on', perhaps misspelled or defined by a module not included in the server configuration +[Wed Sep 19 12:07:56 2007] [alert] [client 127.0.0.1] /var/www/.htaccess: Invalid command 'mod_gzip_on', perhaps misspelled or defined by a module not included in the server configuration +[Wed Sep 19 12:07:57 2007] [alert] [client 127.0.0.1] /var/www/.htaccess: Invalid command 'mod_gzip_on', perhaps misspelled or defined by a module not included in the server configuration +[Wed Sep 19 12:07:58 2007] [alert] [client 127.0.0.1] /var/www/.htaccess: Invalid command 'mod_gzip_on', perhaps misspelled or defined by a module not included in the server configuration +[Wed Sep 19 12:07:58 2007] [alert] [client 127.0.0.1] /var/www/.htaccess: Invalid command 'mod_gzip_on', perhaps misspelled or defined by a module not included in the server configuration +[Wed Sep 19 12:07:59 2007] [alert] [client 127.0.0.1] /var/www/.htaccess: Invalid command 'mod_gzip_on', perhaps misspelled or defined by a module not included in the server configuration +[Wed Sep 19 12:07:59 2007] [alert] [client 127.0.0.1] /var/www/.htaccess: Invalid command 'mod_gzip_on', perhaps misspelled or defined by a module not included in the server configuration diff --git a/ksystemlog/tests/testFiles/cups/access_log b/ksystemlog/tests/testFiles/cups/access_log new file mode 100644 index 00000000..cb31d87f --- /dev/null +++ b/ksystemlog/tests/testFiles/cups/access_log @@ -0,0 +1,233 @@ +localhost - - [14/Sep/2007:12:12:34 +0200] "POST / HTTP/1.1" 200 419 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:12:13:03 +0200] "POST / HTTP/1.1" 200 419 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:12:13:30 +0200] "POST / HTTP/1.1" 200 419 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:12:14:03 +0200] "POST / HTTP/1.1" 200 419 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:12:14:17 +0200] "POST / HTTP/1.1" 200 419 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:12:05 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:12:08 +0200] "GET /ppd/HPLaserJet4100.ppd HTTP/1.1" 200 21119 - - +localhost - - [14/Sep/2007:15:12:08 +0200] "POST /printers/HPLaserJet4100 HTTP/1.1" 200 2308742 Print-Job successful-ok +localhost - - [14/Sep/2007:15:12:09 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:12:09 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:12:09 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:12:09 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:12:14 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:12:14 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:12:19 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes ignored +localhost - - [14/Sep/2007:15:12:19 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs ignored +localhost - - [14/Sep/2007:15:12:24 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes substituted-attributes +localhost - - [14/Sep/2007:15:12:24 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs substituted-attributes +localhost - - [14/Sep/2007:15:12:29 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes - +localhost - - [14/Sep/2007:15:12:29 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs - +localhost - - [14/Sep/2007:15:12:34 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:12:34 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:12:39 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:12:39 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:12:44 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:12:44 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:12:49 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:12:49 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:12:54 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:12:54 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:12:59 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:12:59 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:13:04 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:13:04 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:13:09 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:13:09 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:13:14 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:13:14 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:13:19 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:13:19 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:13:24 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:13:24 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:13:29 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:13:29 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:13:34 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:13:34 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:13:39 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:13:39 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:13:44 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:13:44 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:13:49 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:13:49 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:13:54 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:13:54 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:13:59 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:13:59 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:14:04 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:14:04 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:14:09 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:14:09 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:14:14 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:14:14 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:14:19 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:14:19 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:14:24 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:14:24 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:14:29 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:14:29 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:14:34 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:14:34 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:14:39 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:14:39 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:14:44 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:14:44 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:14:49 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:14:49 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:14:54 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:14:54 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:20:19 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:20:21 +0200] "GET /ppd/HPLaserJet4100.ppd HTTP/1.1" 200 21119 - - +localhost - - [14/Sep/2007:15:20:21 +0200] "POST /printers/HPLaserJet4100 HTTP/1.1" 200 2306438 Print-Job successful-ok +localhost - - [14/Sep/2007:15:20:22 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:20:22 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:20:22 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:20:22 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:20:27 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:20:27 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:20:32 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:20:32 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:20:37 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:20:37 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:20:42 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:20:42 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:20:47 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:20:47 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:20:52 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:20:52 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:20:57 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:20:57 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:21:02 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:21:02 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:21:07 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:21:07 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:21:12 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:21:12 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:21:17 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:21:17 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:21:22 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:21:22 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:21:27 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:21:27 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:21:32 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:21:32 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:21:37 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:21:37 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:21:42 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:21:42 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:21:47 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:21:47 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:21:52 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:21:52 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:21:57 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:21:57 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:22:02 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:22:02 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:22:07 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:22:07 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:22:12 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:22:12 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:22:17 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:22:17 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:22:22 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:22:22 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:22:27 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:22:27 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:22:32 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:22:32 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:22:37 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:22:37 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:22:42 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:22:42 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:22:47 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:22:47 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:22:52 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:22:52 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:22:57 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:22:57 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:23:02 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:23:02 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:23:07 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:23:07 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:23:12 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:23:12 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:23:17 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:23:17 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:23:22 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:23:22 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:23:27 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:23:27 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:23:32 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:23:32 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:23:37 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:23:37 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:23:42 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:23:42 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:23:47 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:23:47 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:23:52 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:23:52 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:23:57 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:23:57 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:24:02 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:24:02 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:24:07 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:24:07 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:24:12 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:24:12 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:24:17 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:24:17 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:24:22 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:24:22 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:24:27 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:24:27 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:24:32 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:24:32 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:24:37 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:24:37 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:24:42 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:24:42 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:24:47 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:24:47 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:24:52 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:24:52 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:24:57 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:24:57 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:25:02 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:25:02 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:25:07 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:25:07 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:25:12 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:25:12 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:25:17 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:25:17 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:25:22 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:25:22 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:25:27 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:25:27 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:25:32 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:25:32 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:25:37 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:25:37 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:25:42 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:25:42 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:25:47 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:25:47 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:25:52 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:25:52 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:25:57 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:25:57 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:26:02 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:26:02 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:26:07 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:26:07 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:26:12 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:26:12 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:26:17 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:26:17 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:26:22 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:26:22 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:26:27 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:26:27 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok +localhost - - [14/Sep/2007:15:26:32 +0200] "POST /classes/ HTTP/1.1" 200 221 CUPS-Get-Classes successful-ok +localhost - - [14/Sep/2007:15:26:32 +0200] "POST / HTTP/1.1" 200 380 Get-Jobs successful-ok diff --git a/ksystemlog/tests/testFiles/cups/cups-pdf_log b/ksystemlog/tests/testFiles/cups/cups-pdf_log new file mode 100644 index 00000000..d791e82b --- /dev/null +++ b/ksystemlog/tests/testFiles/cups/cups-pdf_log @@ -0,0 +1,26 @@ +Thu Jun 14 21:00:38 2007 [STATUS] directory created (/pdf) +Thu Jun 14 21:00:38 2007 [ERROR] failed to set file mode for PDF file (non fatal) (/pdf/Test_Page. +Thu Jun 14 21:00:38 2007 [STATUS] PDF creation successfully finished (root) +Thu Jun 14 21:00:55 2007 [ERROR] failed to set file mode for PDF file (non fatal) (/pdf/Test_Page. +Thu Jun 14 21:00:55 2007 [STATUS] PDF creation successfully finished (root) +Thu Jun 14 12:38:14 2007 [STATUS] identification string sent +Thu Jun 14 12:38:14 2007 [STATUS] identification string sent +Thu Jun 14 12:40:35 2007 [STATUS] identification string sent +Thu Jun 14 12:40:35 2007 [STATUS] identification string sent +Thu Jun 14 12:43:07 2007 [ERROR] failed to set file mode for PDF file (non fatal) (/var/spool/cups-pdf/root/Test_Page.pdf) +Thu Jun 14 12:43:07 2007 [STATUS] PDF creation successfully finished (root) +Thu Jun 14 12:48:36 2007 [ERROR] failed to set file mode for PDF file (non fatal) (/var/spool/cups-pdf/ANONYMOUS/Test_Page.pdf +Thu Jun 14 12:48:36 2007 [STATUS] PDF creation successfully finished (nobody) +Fri Sep 30 21:58:37 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) +Fri Sep 30 22:31:46 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) +Fri Sep 30 22:35:11 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) +Sat Oct 1 09:11:45 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) +Sat Oct 1 09:16:42 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) +Sat Oct 1 09:21:16 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) +Sat Oct 1 12:12:17 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) +Sat Oct 1 12:18:50 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) +Sat Oct 1 12:30:53 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) +Sat Oct 1 16:31:29 2005 [ERROR] failed to create spool directory (/var/spool/cups-pdf/SPOOL) +Sun Oct 2 12:07:19 2005 [STATUS] identification string sent +Sun Oct 2 13:15:56 2005 [STATUS] identification string sent +Sun Oct 2 13:49:33 2005 [STATUS] identification string sent \ No newline at end of file diff --git a/ksystemlog/tests/testFiles/cups/error_log b/ksystemlog/tests/testFiles/cups/error_log new file mode 100644 index 00000000..26de0487 --- /dev/null +++ b/ksystemlog/tests/testFiles/cups/error_log @@ -0,0 +1,4 @@ +E [29/Jun/2007:11:39:56 +0200] Creating missing directory "/var/run/cups/certs" +I [20/May/1999:19:18:28 +0000] Job 1 queued on 'DeskJet' by 'mike'. +I [20/May/1999:19:21:02 +0000] Job 2 queued on 'DeskJet' by 'mike'. +I [20/May/1999:19:22:24 +0000] Job 2 was cancelled by 'mike'. \ No newline at end of file diff --git a/ksystemlog/tests/testFiles/cups/page_log b/ksystemlog/tests/testFiles/cups/page_log new file mode 100644 index 00000000..70b204f3 --- /dev/null +++ b/ksystemlog/tests/testFiles/cups/page_log @@ -0,0 +1,3 @@ +DeskJet root 2 [20/May/1999:19:21:05 +0000] 1 1 acme-123 +DeskJet root 2 [20/May/1999:19:21:05 +0000] 2 1 acme-123 + diff --git a/ksystemlog/tests/testFiles/default/one-line.log b/ksystemlog/tests/testFiles/default/one-line.log new file mode 100644 index 00000000..e0c928e3 --- /dev/null +++ b/ksystemlog/tests/testFiles/default/one-line.log @@ -0,0 +1 @@ +Aug 21 22:52:44 localhost kernel: [11663.656000] eth1: no IPv6 routers present \ No newline at end of file diff --git a/ksystemlog/tests/testFiles/default/two-lines.log b/ksystemlog/tests/testFiles/default/two-lines.log new file mode 100644 index 00000000..7daf2a76 --- /dev/null +++ b/ksystemlog/tests/testFiles/default/two-lines.log @@ -0,0 +1,2 @@ +Aug 21 22:52:44 localhost kernel: [11663.656000] eth1: no IPv6 routers present +Aug 21 22:53:44 localhost kernel: [11663.656000] eth0: no IPv6 routers present again... \ No newline at end of file diff --git a/ksystemlog/tests/testFiles/kernel/suse.dmesg b/ksystemlog/tests/testFiles/kernel/suse.dmesg new file mode 100644 index 00000000..438fdae1 --- /dev/null +++ b/ksystemlog/tests/testFiles/kernel/suse.dmesg @@ -0,0 +1,23 @@ +r8169: eth0: link down +NET: Registered protocol family 10 +lo: Disabled Privacy Extensions +ADDRCONF(NETDEV_UP): eth0: link is not ready +ADDRCONF(NETDEV_UP): eth1: link is not ready +ppdev: user-space parallel port driver +audit(1194102016.839:3): type=1503 operation="inode_permission" requested_mask="a" denied_mask="a" name="/dev/tty" pid=5184 profile="/usr/sbin/cupsd" +apm: BIOS not found. +fglrx: module license 'Proprietary. (C) 2002 - ATI Technologies, Starnberg, GERMANY' taints kernel. +[fglrx] Maximum main memory to use for locked dma buffers: 1414 MBytes. +[fglrx] module loaded - fglrx 8.37.6 [May 25 2007] on minor 0 +ACPI: PCI Interrupt 0000:01:00.0[A] -> GSI 16 (level, low) -> IRQ 16 +Failure registering capabilities with primary security module. +[fglrx] total GART = 130023424 +[fglrx] free GART = 114032640 +[fglrx] max single GART = 114032640 +[fglrx] total LFB = 268304384 +[fglrx] free LFB = 253915136 +[fglrx] max single LFB = 253915136 +[fglrx] total Inv = 0 +[fglrx] free Inv = 0 +[fglrx] max single Inv = 0 +[fglrx] total TIM = 0 diff --git a/ksystemlog/tests/testFiles/kernel/ubuntu.dmesg b/ksystemlog/tests/testFiles/kernel/ubuntu.dmesg new file mode 100644 index 00000000..b40ab9aa --- /dev/null +++ b/ksystemlog/tests/testFiles/kernel/ubuntu.dmesg @@ -0,0 +1,25 @@ +[ 22.232000] ADDRCONF(NETDEV_UP): eth0: link is not ready +[ 22.232000] ADDRCONF(NETDEV_UP): eth1: link is not ready +[ 22.636000] ppdev: user-space parallel port driver +[ 23.044000] audit(1194105334.184:3): type=1503 operation="inode_permission" requested_mask="a" denied_mask="a" name="/dev/tty" pid=5172 profile="/usr/sbin/cupsd" +[ 23.116000] apm: BIOS not found. +[ 23.836000] fglrx: module license 'Proprietary. (C) 2002 - ATI Technologies, Starnberg, GERMANY' taints kernel. +[ 23.840000] [fglrx] Maximum main memory to use for locked dma buffers: 1414 MBytes. +[ 23.840000] [fglrx] module loaded - fglrx 8.37.6 [May 25 2007] on minor 0 +[ 23.964000] ACPI: PCI Interrupt 0000:01:00.0[A] -> GSI 16 (level, low) -> IRQ 16 +[ 24.924000] Failure registering capabilities with primary security module. +[ 27.076000] [fglrx] total GART = 130023424 +[ 27.076000] [fglrx] free GART = 114032640 +[ 27.076000] [fglrx] max single GART = 114032640 +[ 27.076000] [fglrx] total LFB = 268304384 +[ 27.076000] [fglrx] free LFB = 253915136 +[ 27.076000] [fglrx] max single LFB = 253915136 +[ 27.076000] [fglrx] total Inv = 0 +[ 27.076000] [fglrx] free Inv = 0 +[ 27.076000] [fglrx] max single Inv = 0 +[ 27.076000] [fglrx] total TIM = 0 +[ 77.252000] NET: Registered protocol family 17 +[ 80.416000] ADDRCONF(NETDEV_CHANGE): eth1: link becomes ready +[ 91.128000] eth1: no IPv6 routers present +[ 96.452000] ieee80211_crypt: registered algorithm 'CCMP' +[ 97.424000] ieee80211_crypt: registered algorithm 'T diff --git a/ksystemlog/tests/testFiles/logFileReader/file.txt b/ksystemlog/tests/testFiles/logFileReader/file.txt new file mode 100644 index 00000000..04546dc7 --- /dev/null +++ b/ksystemlog/tests/testFiles/logFileReader/file.txt @@ -0,0 +1,15 @@ +sdf +sdf2 +sdf3 +sdf4 +sdf5 +sdf6 +sdf +TestTestTestTestTestTest1 +TestTestTestTestTestTest2 +TestTestTestTestTestTest4 +TestTestTestTestTestTest3 +TestTestTestTestTestTest5 +TestTestTestTestTestTest6 +TestTestTestTestTestTest7 +sdf diff --git a/ksystemlog/tests/testFiles/mysql/mysql-slow.log b/ksystemlog/tests/testFiles/mysql/mysql-slow.log new file mode 100644 index 00000000..0b37e44d --- /dev/null +++ b/ksystemlog/tests/testFiles/mysql/mysql-slow.log @@ -0,0 +1,94 @@ +/usr/sbin/mysqld, Version: 5.0.45-Debian_1ubuntu2-log (Debian etch distribution). started with: +Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock +Time Id Command Argument +# Time: 070920 11:33:41 +# User@Host: debian-sys-maint[debian-sys-maint] @ localhost [] +# Query_time: 0 Lock_time: 0 Rows_sent: 1 Rows_examined: 4 +SELECT count(*) FROM mysql.user WHERE user='root' and password=''; +# Time: 070920 11:39:28 +# User@Host: root[root] @ localhost [] +# Query_time: 0 Lock_time: 0 Rows_sent: 62 Rows_examined: 62 +use alfresco; +SELECT *, + `TABLE_SCHEMA` AS `Db`, + `TABLE_NAME` AS `Name`, + `ENGINE` AS `Engine`, + `ENGINE` AS `Type`, + `VERSION` AS `Version`, + `ROW_FORMAT` AS `Row_format`, + `TABLE_ROWS` AS `Rows`, + `AVG_ROW_LENGTH` AS `Avg_row_length`, + `DATA_LENGTH` AS `Data_length`, + `MAX_DATA_LENGTH` AS `Max_data_length`, + `INDEX_LENGTH` AS `Index_length`, + `DATA_FREE` AS `Data_free`, + `AUTO_INCREMENT` AS `Auto_increment`, + `CREATE_TIME` AS `Create_time`, + `UPDATE_TIME` AS `Update_time`, + `CHECK_TIME` AS `Check_time`, + `TABLE_COLLATION` AS `Collation`, + `CHECKSUM` AS `Checksum`, + `CREATE_OPTIONS` AS `Create_options`, + `TABLE_COMMENT` AS `Comment` + FROM `information_schema`.`TABLES` + WHERE BINARY `TABLE_SCHEMA` IN ('alfresco'); +# User@Host: root[root] @ localhost [] +# Query_time: 0 Lock_time: 0 Rows_sent: 1 Rows_examined: 2 +SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = 'alfresco' LIMIT 1; +# Time: 070920 11:39:29 +# User@Host: root[root] @ localhost [] +# Query_time: 0 Lock_time: 0 Rows_sent: 62 Rows_examined: 62 +SELECT *, + `TABLE_SCHEMA` AS `Db`, + `TABLE_NAME` AS `Name`, + `ENGINE` AS `Engine`, + `ENGINE` AS `Type`, + `VERSION` AS `Version`, + `ROW_FORMAT` AS `Row_format`, + `TABLE_ROWS` AS `Rows`, + `AVG_ROW_LENGTH` AS `Avg_row_length`, + `DATA_LENGTH` AS `Data_length`, + `MAX_DATA_LENGTH` AS `Max_data_length`, + `INDEX_LENGTH` AS `Index_length`, + `DATA_FREE` AS `Data_free`, + `AUTO_INCREMENT` AS `Auto_increment`, + `CREATE_TIME` AS `Create_time`, + `UPDATE_TIME` AS `Update_time`, + `CHECK_TIME` AS `Check_time`, + `TABLE_COLLATION` AS `Collation`, + `CHECKSUM` AS `Checksum`, + `CREATE_OPTIONS` AS `Create_options`, + `TABLE_COMMENT` AS `Comment` + FROM `information_schema`.`TABLES` + WHERE BINARY `TABLE_SCHEMA` IN ('alfresco'); +# Time: 070920 11:39:31 +# User@Host: root[root] @ localhost [] +# Query_time: 0 Lock_time: 0 Rows_sent: 62 Rows_examined: 62 +SELECT *, + `TABLE_SCHEMA` AS `Db`, + `TABLE_NAME` AS `Name`, + `ENGINE` AS `Engine`, + `ENGINE` AS `Type`, + `VERSION` AS `Version`, + `ROW_FORMAT` AS `Row_format`, + `TABLE_ROWS` AS `Rows`, + `AVG_ROW_LENGTH` AS `Avg_row_length`, + `DATA_LENGTH` AS `Data_length`, + `MAX_DATA_LENGTH` AS `Max_data_length`, + `INDEX_LENGTH` AS `Index_length`, + `DATA_FREE` AS `Data_free`, + `AUTO_INCREMENT` AS `Auto_increment`, + `CREATE_TIME` AS `Create_time`, + `UPDATE_TIME` AS `Update_time`, + `CHECK_TIME` AS `Check_time`, + `TABLE_COLLATION` AS `Collation`, + `CHECKSUM` AS `Checksum`, + `CREATE_OPTIONS` AS `Create_options`, + `TABLE_COMMENT` AS `Comment` + FROM `information_schema`.`TABLES` + WHERE BINARY `TABLE_SCHEMA` IN ('alfresco'); +# Time: 070920 11:39:43 +# User@Host: root[root] @ localhost [] +# Query_time: 0 Lock_time: 0 Rows_sent: 30 Rows_examined: 30 +select * from node + LIMIT 0, 30; diff --git a/ksystemlog/tests/testFiles/mysql/mysql.log b/ksystemlog/tests/testFiles/mysql/mysql.log new file mode 100644 index 00000000..dd6be77b --- /dev/null +++ b/ksystemlog/tests/testFiles/mysql/mysql.log @@ -0,0 +1,194 @@ +/usr/sbin/mysqld, Version: 5.0.45-Debian_1ubuntu2-log (Debian etch distribution). started with: +Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock +Time Id Command Argument +070920 11:33:41 1 Connect debian-sys-maint@localhost on + 1 Quit + 2 Connect debian-sys-maint@localhost on + 2 Quit + 3 Connect debian-sys-maint@localhost on + 3 Query show /*!40003 GLOBAL */ variables + 3 Quit + 4 Connect debian-sys-maint@localhost on mysql + 4 Query select @@version_comment limit 1 + 4 Query show variables like 'datadir' + 4 Quit + 5 Connect debian-sys-maint@localhost on + 5 Query select @@version_comment limit 1 + 5 Query SELECT count(*) FROM mysql.user WHERE user='root' and password='' + 5 Quit + 6 Connect debian-sys-maint@localhost on + 6 Query SHOW DATABASES + 6 Init DB alfresco + 6 Query SHOW /*!50002 FULL*/ TABLES +070920 11:33:42 6 Query CHECK TABLE `access_control_entry` FAST + 6 Query CHECK TABLE `access_control_list` FAST + 6 Query CHECK TABLE `applied_patch` FAST + 6 Query CHECK TABLE `auth_ext_keys` FAST + 6 Query CHECK TABLE `authority` FAST + 6 Query CHECK TABLE `child_assoc` FAST + 6 Query CHECK TABLE `gu_adress` FAST + 6 Query CHECK TABLE `gu_banner` FAST + 6 Query CHECK TABLE `gu_bannerclient` FAST + 6 Query CHECK TABLE `gu_bannerfinish` FAST + 6 Query CHECK TABLE `gu_categories` FAST + 6 Query CHECK TABLE `gu_components` FAST + 6 Query CHECK TABLE `gu_contact_details` FAST + 6 Query CHECK TABLE `gu_content` FAST + 6 Query CHECK TABLE `gu_content_frontpage` FAST + 6 Query CHECK TABLE `gu_content_rating` FAST + 6 Query CHECK TABLE `gu_core_acl_aro` FAST + 6 Query CHECK TABLE `gu_core_acl_aro_groups` FAST + 6 Query CHECK TABLE `gu_core_acl_aro_sections` FAST + 6 Query CHECK TABLE `gu_core_acl_groups_aro_map` FAST + 6 Query CHECK TABLE `gu_core_log_items` FAST + 6 Query CHECK TABLE `gu_core_log_searches` FAST + 6 Query CHECK TABLE `gu_extcal_categories` FAST + 6 Query CHECK TABLE `gu_extcal_config` FAST + 6 Query CHECK TABLE `gu_extcal_events` FAST + 6 Query CHECK TABLE `gu_extcal_plugins` FAST + 6 Query CHECK TABLE `gu_gm_edit` FAST + 6 Query CHECK TABLE `gu_gm_editown` FAST + 6 Query CHECK TABLE `gu_gm_groupe` FAST + 6 Query CHECK TABLE `gu_gm_groupeimbrique` FAST + 6 Query CHECK TABLE `gu_gm_membre` FAST + 6 Query CHECK TABLE `gu_gm_publish` FAST + 6 Query CHECK TABLE `gu_groups` FAST + 6 Query CHECK TABLE `gu_letterman` FAST + 6 Query CHECK TABLE `gu_letterman_subscribers` FAST + 6 Query CHECK TABLE `gu_mambots` FAST + 6 Query CHECK TABLE `gu_menu` FAST + 6 Query CHECK TABLE `gu_messages` FAST + 6 Query CHECK TABLE `gu_messages_cfg` FAST + 6 Query CHECK TABLE `gu_modules` FAST + 6 Query CHECK TABLE `gu_modules_menu` FAST + 6 Query CHECK TABLE `gu_newsfeeds` FAST + 6 Query CHECK TABLE `gu_poll_data` FAST + 6 Query CHECK TABLE `gu_poll_date` FAST + 6 Query CHECK TABLE `gu_poll_menu` FAST + 6 Query CHECK TABLE `gu_polls` FAST + 6 Query CHECK TABLE `gu_sections` FAST + 6 Query CHECK TABLE `gu_session` FAST + 6 Query CHECK TABLE `gu_stats_agents` FAST + 6 Query CHECK TABLE `gu_template_positions` FAST + 6 Query CHECK TABLE `gu_templates_menu` FAST + 6 Query CHECK TABLE `gu_users` FAST + 6 Query CHECK TABLE `gu_usertypes` FAST + 6 Query CHECK TABLE `gu_weblinks` FAST + 6 Query CHECK TABLE `node` FAST + 6 Query CHECK TABLE `node_aspects` FAST + 6 Query CHECK TABLE `node_assoc` FAST + 6 Query CHECK TABLE `node_properties` FAST + 6 Query CHECK TABLE `node_status` FAST + 6 Query CHECK TABLE `permission` FAST + 6 Query CHECK TABLE `store` FAST + 6 Query CHECK TABLE `version_count` FAST + 6 Init DB mysql + 6 Query SHOW /*!50002 FULL*/ TABLES + 6 Query CHECK TABLE `columns_priv` FAST + 6 Query CHECK TABLE `db` FAST + 6 Query CHECK TABLE `func` FAST + 6 Query CHECK TABLE `help_category` FAST + 6 Query CHECK TABLE `help_keyword` FAST + 6 Query CHECK TABLE `help_relation` FAST + 6 Query CHECK TABLE `help_topic` FAST + 6 Query CHECK TABLE `host` FAST + 6 Query CHECK TABLE `proc` FAST + 6 Query CHECK TABLE `procs_priv` FAST + 6 Query CHECK TABLE `tables_priv` FAST + 6 Query CHECK TABLE `time_zone` FAST + 6 Query CHECK TABLE `time_zone_leap_second` FAST + 6 Query CHECK TABLE `time_zone_name` FAST + 6 Query CHECK TABLE `time_zone_transition` FAST + 6 Query CHECK TABLE `time_zone_transition_type` FAST + 6 Query CHECK TABLE `user` FAST + 6 Quit +070920 11:37:24 7 Connect root@localhost on + 7 Query SELECT VERSION() + 7 Query SET NAMES utf8 + 7 Query SET collation_connection = 'utf8_unicode_ci' + 7 Query SET NAMES utf8 + 7 Query SET collation_connection = 'utf8_unicode_ci' + 7 Query SHOW SESSION VARIABLES LIKE 'collation_connection' + 7 Query SHOW SESSION VARIABLES LIKE 'character_set_connection' + 7 Query SHOW CHARACTER SET + 7 Query SHOW COLLATION + 7 Query SHOW DATABASES + 7 Quit + 8 Connect root@localhost on + 8 Query SELECT VERSION() + 8 Query SET NAMES utf8 + 8 Query SET collation_connection = 'utf8_unicode_ci' + 8 Query SET NAMES utf8 + 8 Query SET collation_connection = 'utf8_unicode_ci' + 8 Query SHOW SESSION VARIABLES LIKE 'collation_connection' + 8 Query SHOW SESSION VARIABLES LIKE 'character_set_connection' + 8 Query SHOW CHARACTER SET + 8 Query SHOW COLLATION + 8 Query SHOW DATABASES + 8 Query SELECT COUNT(*) FROM mysql.user + 8 Query SELECT COUNT(*) FROM mysql.user + 8 Query SHOW GRANTS + 8 Query SELECT COUNT(*) FROM mysql.user + 8 Query SELECT USER() + 8 Query SHOW MASTER LOGS + 8 Quit + 9 Connect root@localhost on + 9 Query SELECT VERSION() + 9 Query SET NAMES utf8 + 9 Query SET collation_connection = 'utf8_unicode_ci' + 9 Query SET NAMES utf8 + 9 Query SET collation_connection = 'utf8_unicode_ci' + 9 Query SHOW SESSION VARIABLES LIKE 'collation_connection' + 9 Query SHOW SESSION VARIABLES LIKE 'character_set_connection' + 9 Query SHOW CHARACTER SET + 9 Query SHOW COLLATION + 9 Query SHOW DATABASES + 9 Query SHOW TABLES FROM `alfresco` + 9 Query SHOW TABLES FROM `information_schema` + 9 Query SHOW TABLES FROM `mysql` + 9 Quit +070920 11:37:48 10 Connect root@localhost on + 10 Query SELECT VERSION() + 10 Query SET NAMES utf8 + 10 Query SET collation_connection = 'utf8_unicode_ci' + 10 Query SET NAMES utf8 + 10 Query SET collation_connection = 'utf8_unicode_ci' + 10 Query SHOW SESSION VARIABLES LIKE 'collation_connection' + 10 Query SHOW SESSION VARIABLES LIKE 'character_set_connection' + 10 Query SHOW CHARACTER SET + 10 Query SHOW COLLATION + 10 Query SHOW DATABASES + 10 Quit + 11 Connect root@localhost on + 11 Query SELECT VERSION() + 11 Query SET NAMES utf8 + 11 Query SET collation_connection = 'utf8_unicode_ci' + 11 Query SET NAMES utf8 + 11 Query SET collation_connection = 'utf8_unicode_ci' + 11 Query SHOW SESSION VARIABLES LIKE 'collation_connection' + 11 Query SHOW SESSION VARIABLES LIKE 'character_set_connection' + 11 Query SHOW CHARACTER SET + 11 Query SHOW COLLATION + 11 Query SHOW DATABASES + 11 Query SHOW TABLES FROM `alfresco` + 11 Query SHOW TABLES FROM `information_schema` + 11 Query SHOW TABLES FROM `mysql` + 11 Quit + 12 Connect root@localhost on + 12 Query SELECT VERSION() + 12 Query SET NAMES utf8 + 12 Query SET collation_connection = 'utf8_unicode_ci' + 12 Query SET NAMES utf8 + 12 Query SET collation_connection = 'utf8_unicode_ci' + 12 Query SHOW SESSION VARIABLES LIKE 'collation_connection' + 12 Query SHOW SESSION VARIABLES LIKE 'character_set_connection' + 12 Query SHOW CHARACTER SET + 12 Query SHOW COLLATION + 12 Query SHOW DATABASES + 12 Query SELECT COUNT(*) FROM mysql.user + 12 Query SELECT COUNT(*) FROM mysql.user + 12 Query SHOW GRANTS + 12 Query SELECT COUNT(*) FROM mysql.user + 12 Query SELECT USER() + 12 Query SHOW MASTER LOGS +070920 11:37:49 12 Quit diff --git a/ksystemlog/tests/testFiles/samba/log.hostname1 b/ksystemlog/tests/testFiles/samba/log.hostname1 new file mode 100644 index 00000000..4ff511ba --- /dev/null +++ b/ksystemlog/tests/testFiles/samba/log.hostname1 @@ -0,0 +1,50 @@ +[2007/02/23 15:11:42, 0] lib/util_sock.c:write_data(562) + write_data: write failure in writing to client 192.168.2.243. Error Connexion ré-initialisée par le correspondant +[2007/02/23 15:11:42, 0] lib/util_sock.c:send_smb(769) + Error writing 4 bytes to client. -1. (Connexion ré-initialisée par le correspondant) +[2007/02/24 01:51:47, 0] lib/util_sock.c:write_data(562) + write_data: write failure in writing to client 192.168.2.243. Error Connexion ré-initialisée par le correspondant +[2007/02/24 01:51:47, 0] lib/util_sock.c:send_smb(769) + Error writing 4 bytes to client. -1. (Connexion ré-initialisée par le correspondant) +[2007/02/24 02:55:47, 0] lib/util_sock.c:write_data(562) + write_data: write failure in writing to client 192.168.2.243. Error Connexion ré-initialisée par le correspondant +[2007/02/24 02:55:47, 0] lib/util_sock.c:send_smb(769) + Error writing 4 bytes to client. -1. (Connexion ré-initialisée par le correspondant) +[2007/02/24 03:27:47, 0] lib/util_sock.c:read_data(534) + read_data: read failure for 4 bytes to client 192.168.2.243. Error = Connexion ré-initialisée par le correspondant +[2007/02/24 03:59:48, 0] lib/util_sock.c:write_data(562) + write_data: write failure in writing to client 192.168.2.243. Error Connexion ré-initialisée par le correspondant +[2007/02/24 03:59:48, 0] lib/util_sock.c:send_smb(769) + Error writing 4 bytes to client. -1. (Connexion ré-initialisée par le correspondant) +[2007/02/24 04:31:48, 0] lib/util_sock.c:read_data(534) + read_data: read failure for 4 bytes to client 192.168.2.243. Error = Connexion ré-initialisée par le correspondant +[2007/02/24 05:03:48, 0] lib/util_sock.c:write_data(562) + write_data: write failure in writing to client 192.168.2.243. Error Connexion ré-initialisée par le correspondant +[2007/02/24 05:03:48, 0] lib/util_sock.c:send_smb(769) + Error writing 4 bytes to client. -1. (Connexion ré-initialisée par le correspondant) +[2007/02/24 07:43:49, 0] lib/util_sock.c:write_data(562) + write_data: write failure in writing to client 192.168.2.243. Error Connexion ré-initialisée par le correspondant +[2007/02/24 07:43:49, 0] lib/util_sock.c:send_smb(769) + Error writing 4 bytes to client. -1. (Connexion ré-initialisée par le correspondant) +[2007/02/24 11:27:50, 0] lib/util_sock.c:write_data(562) + write_data: write failure in writing to client 192.168.2.243. Error Connexion ré-initialisée par le correspondant +[2007/02/24 11:27:50, 0] lib/util_sock.c:send_smb(769) + Error writing 4 bytes to client. -1. (Connexion ré-initialisée par le correspondant) +[2007/02/24 11:59:50, 0] lib/util_sock.c:write_data(562) + write_data: write failure in writing to client 192.168.2.243. Error Connexion ré-initialisée par le correspondant +[2007/02/24 11:59:50, 0] lib/util_sock.c:send_smb(769) + Error writing 4 bytes to client. -1. (Connexion ré-initialisée par le correspondant) +[2007/02/24 12:31:50, 0] lib/util_sock.c:write_data(562) + write_data: write failure in writing to client 192.168.2.243. Error Connexion ré-initialisée par le correspondant +[2007/02/24 12:31:50, 0] lib/util_sock.c:send_smb(769) + Error writing 4 bytes to client. -1. (Connexion ré-initialisée par le correspondant) +[2007/02/24 14:39:56, 0] lib/util_sock.c:read_data(534) + read_data: read failure for 4 bytes to client 192.168.2.243. Error = Connexion ré-initialisée par le correspondant +[2007/02/24 15:43:57, 0] lib/util_sock.c:write_data(562) + write_data: write failure in writing to client 0.0.0.0. Error Connexion ré-initialisée par le correspondant +[2007/02/24 15:43:57, 0] lib/util_sock.c:send_smb(769) + Error writing 4 bytes to client. -1. (Connexion ré-initialisée par le correspondant) +[2007/02/26 15:25:49, 0] lib/util_sock.c:write_data(562) + write_data: write failure in writing to client 192.168.2.243. Error Connexion ré-initialisée par le correspondant +[2007/02/26 15:25:49, 0] lib/util_sock.c:send_smb(769) + Error writing 4 bytes to client. -1. (Connexion ré-initialisée par le correspondant) diff --git a/ksystemlog/tests/testFiles/samba/log.ip2 b/ksystemlog/tests/testFiles/samba/log.ip2 new file mode 100644 index 00000000..ef7d42a7 --- /dev/null +++ b/ksystemlog/tests/testFiles/samba/log.ip2 @@ -0,0 +1,4 @@ +[2007/08/21 17:44:17, 1] smbd/service.c:make_connection_snum(950) + 192.168.2.137 (192.168.2.137) connect to service TESTS initially as user nobody (uid=65534, gid=65534) (pid 14256) +[2007/08/21 17:44:34, 1] smbd/service.c:close_cnum(1150) + 192.168.2.137 (192.168.2.137) closed connection to service TESTS diff --git a/ksystemlog/tests/testFiles/samba/log.nmbd b/ksystemlog/tests/testFiles/samba/log.nmbd new file mode 100644 index 00000000..af3da380 --- /dev/null +++ b/ksystemlog/tests/testFiles/samba/log.nmbd @@ -0,0 +1,33 @@ +[2007/09/18 13:32:21, 0] nmbd/nmbd.c:terminate(58) + Got SIGTERM: going down... +[2007/09/18 13:33:08, 0] nmbd/nmbd.c:main(697) + Netbios nameserver version 3.0.25b started. + Copyright Andrew Tridgell and the Samba Team 1992-2007 +[2007/09/18 13:38:37, 0] nmbd/nmbd_become_lmb.c:become_local_master_stage2(396) + ***** + + Samba name server NICOLAS is now a local master browser for workgroup BOULOT on subnet 192.168.2.2 + + ***** +[2007/09/19 10:00:20, 0] nmbd/nmbd.c:terminate(58) + Got SIGTERM: going down... +[2007/09/19 10:01:27, 0] nmbd/nmbd.c:main(697) + Netbios nameserver version 3.0.25b started. + Copyright Andrew Tridgell and the Samba Team 1992-2007 +[2007/09/19 10:06:53, 0] nmbd/nmbd_become_lmb.c:become_local_master_stage2(396) + ***** + + Samba name server NICOLAS is now a local master browser for workgroup BOULOT on subnet 192.168.2.2 + + ***** +[2007/09/20 10:05:59, 0] nmbd/nmbd.c:terminate(58) + Got SIGTERM: going down... +[2007/09/20 10:07:10, 0] nmbd/nmbd.c:main(697) + Netbios nameserver version 3.0.26a started. + Copyright Andrew Tridgell and the Samba Team 1992-2007 +[2007/09/20 10:12:36, 0] nmbd/nmbd_become_lmb.c:become_local_master_stage2(396) + ***** + + Samba name server NICOLAS is now a local master browser for workgroup BOULOT on subnet 192.168.2.2 + + ***** diff --git a/ksystemlog/tests/testFiles/samba/log.smbd b/ksystemlog/tests/testFiles/samba/log.smbd new file mode 100644 index 00000000..0843172e --- /dev/null +++ b/ksystemlog/tests/testFiles/samba/log.smbd @@ -0,0 +1,13 @@ +[2007/09/18 13:33:09, 0] smbd/server.c:main(944) + smbd version 3.0.25b started. + Copyright Andrew Tridgell and the Samba Team 1992-2007 +[2007/09/18 18:17:57, 0] lib/util_sock.c:get_peer_addr(1232) + getpeername failed. Error was Transport endpoint is not connected +[2007/09/19 10:01:28, 0] smbd/server.c:main(944) + smbd version 3.0.25b started. + Copyright Andrew Tridgell and the Samba Team 1992-2007 +[2007/09/19 13:43:46, 0] lib/util_sock.c:get_peer_addr(1232) + getpeername failed. Error was Transport endpoint is not connected +[2007/09/20 10:07:10, 0] smbd/server.c:main(944) + smbd version 3.0.26a started. + Copyright Andrew Tridgell and the Samba Team 1992-2007 diff --git a/ksystemlog/tests/testFiles/system/delete-process-identifier.log b/ksystemlog/tests/testFiles/system/delete-process-identifier.log new file mode 100644 index 00000000..cceba7fd --- /dev/null +++ b/ksystemlog/tests/testFiles/system/delete-process-identifier.log @@ -0,0 +1,2 @@ +Aug 15 22:39:01 localhost /USR/SBIN/CRON[9433]: Hello +Aug 15 22:39:02 localhost f[9433]: Ola \ No newline at end of file diff --git a/ksystemlog/tests/testFiles/system/duplicate-lines.log b/ksystemlog/tests/testFiles/system/duplicate-lines.log new file mode 100644 index 00000000..8bfbb04a --- /dev/null +++ b/ksystemlog/tests/testFiles/system/duplicate-lines.log @@ -0,0 +1,9 @@ +Aug 18 10:00:00 localhost test: Line 1 +Aug 18 10:00:00 localhost test: Line 1 +Aug 18 10:00:00 localhost test: Line 1 +Aug 18 11:00:00 localhost test: Line 1 +Aug 18 14:00:00 localhost test: Line 2 +Aug 18 14:00:00 localhost test: Line 2 +Aug 18 15:00:00 localhost test: Line 10 +Aug 18 15:00:00 localhost test: Line 10 +Aug 18 15:00:00 localhost test: Line 11 diff --git a/ksystemlog/tests/testFiles/system/max-lines.log b/ksystemlog/tests/testFiles/system/max-lines.log new file mode 100644 index 00000000..e2e602ac --- /dev/null +++ b/ksystemlog/tests/testFiles/system/max-lines.log @@ -0,0 +1,7 @@ +Aug 18 09:00:00 localhost test: Line 1 +Aug 18 10:00:00 localhost test: Line 2 +Aug 18 11:00:00 localhost test: Line 3 +Aug 18 12:00:00 localhost test: Line 4 +Aug 18 13:00:00 localhost test: Line 5 +Aug 18 14:00:00 localhost test: Line 6 +Aug 18 15:00:00 localhost test: Line 7 diff --git a/ksystemlog/tests/testFiles/system/strange-lines.log b/ksystemlog/tests/testFiles/system/strange-lines.log new file mode 100644 index 00000000..2c23e34f --- /dev/null +++ b/ksystemlog/tests/testFiles/system/strange-lines.log @@ -0,0 +1,8 @@ +Aug 10 17:04:28 localhost kernel: Kernel panic +Aug 11 13:49:38 localhost -- MARK -- +Aug 12 18:10:32 localhost last message repeated 4 times +Aug 13 17:04:28 testprocess: Say ouhou +Aug 14 17:04:28 localhost kernel say ouhou +Aug 15 22:39:01 localhost /USR/SBIN/CRON[9433]: (root) CMD ( [ -d /var/lib/php5 ] && find /var/lib/php5/ -type f -cmin +$(/usr/lib/php5/maxlifetime) -print0 | xargs -r -0 rm) + +blablalbla diff --git a/ksystemlog/tests/testFiles/system/system.log b/ksystemlog/tests/testFiles/system/system.log new file mode 100644 index 00000000..fec5bcb4 --- /dev/null +++ b/ksystemlog/tests/testFiles/system/system.log @@ -0,0 +1,24 @@ +Aug 18 17:04:28 localhost kernel: [ 17.092000] ieee80211_crypt: registered algorithm 'NULL' +Aug 18 17:04:28 localhost kernel: [ 17.232000] PCI: Setting latency timer of device 0000:03:00.0 to 64 +Aug 18 17:04:28 localhost kernel: [ 17.364000] PCI: Setting latency timer of device 0000:00:1b.0 to 64 +Aug 18 17:04:28 localhost kernel: [ 100.596000] set_level status: 0 +Aug 18 17:04:29 localhost NetworkManager: [1187449469.325281] nm_hal_device_added(): New device added (hal udi is '/org/freedesktop/Hal/devices/storage_model_CD/DVDW_TS_L632D'). +Aug 18 17:04:42 localhost kernel: [ 115.084000] eth1: no IPv6 routers present +Aug 18 17:05:23 localhost NetworkManager: [1187449523.399765] nm_hal_device_added(): New device added (hal udi is '/org/freedesktop/Hal/devices/usb_device_46d_c016_noserial'). +Aug 18 17:05:23 localhost NetworkManager: [1187449523.521428] nm_hal_device_added(): New device added (hal udi is '/org/freedesktop/Hal/devices/usb_device_46d_c016_noserial_if0'). +Aug 18 17:05:23 localhost NetworkManager: [1187449523.554409] nm_hal_device_added(): New device added (hal udi is '/org/freedesktop/Hal/devices/usb_device_46d_c016_noserial_usbraw'). +Aug 18 17:05:23 localhost NetworkManager: [1187449523.576442] nm_hal_device_added(): New device added (hal udi is '/org/freedesktop/Hal/devices/usb_device_46d_c016_noserial_if0_logicaldev_input'). +Aug 18 17:06:13 localhost NetworkManager: [1187449573.852443] nm_device_802_11_wireless_get_activation_ap(): Forcing AP '3Com' +Aug 18 17:06:23 localhost NetworkManager: [1187449583.277856] nm_device_802_11_wireless_get_activation_ap(): Forcing AP '3Com' +Aug 18 17:06:33 localhost kernel: [ 226.604000] eth1: no IPv6 routers present +Aug 18 17:08:40 localhost kernel: [ 353.700000] eth1: no IPv6 routers present +Aug 18 17:11:52 localhost NetworkManager: [1187449912.758477] nm_device_802_11_wireless_get_activation_ap(): Forcing AP '3Com' +Aug 18 17:12:05 localhost kernel: [ 559.152000] eth1: no IPv6 routers present +Aug 18 17:14:15 localhost kernel: [ 688.808000] eth1: no IPv6 routers present +Aug 19 10:45:51 localhost NetworkManager: [1187513151.426711] nm_device_802_11_wireless_get_activation_ap(): Forcing AP '3Com' +Aug 19 10:46:03 localhost kernel: [63797.588000] eth1: no IPv6 routers present +Aug 19 10:48:10 localhost kernel: [63924.112000] eth1: no IPv6 routers present +Aug 19 16:22:32 localhost NetworkManager: [1187533352.507843] nm_print_open_socks(): Open Sockets List: +Aug 19 16:22:32 localhost NetworkManager: [1187533352.507872] nm_print_open_socks(): Open Sockets List Done. +Aug 19 18:55:53 localhost kernel: [ 0.000000] Entering add_active_range(0, 0, 393136) 0 entries of 256 used +Aug 19 18:55:53 localhost kernel: [ 0.000000] On node 0 totalpages: 393136 \ No newline at end of file diff --git a/ksystemlog/tests/testFiles/user/user.log b/ksystemlog/tests/testFiles/user/user.log new file mode 100644 index 00000000..1b9dfd7c --- /dev/null +++ b/ksystemlog/tests/testFiles/user/user.log @@ -0,0 +1,88 @@ +Sep 20 10:47:28 testhost gconfd (root-8801): Sortie +Sep 20 11:36:42 testhost gconfd (user-5838): Failed to send buffer +Sep 13 17:34:40 testhost gconfd (user-5822): Sortie +Sep 14 09:23:29 testhost dhcdbd: Started up. +Sep 14 09:23:32 testhost hpiod: 1.7.3 accepting connections at 2208... +Sep 14 09:30:51 testhost gconfd (user-6112): démarrage (version 2.18.0.1), pid 6112 utilisateur « user » +Sep 14 09:30:51 testhost gconfd (user-6112): Adresse « xml:readonly:/etc/gconf/gconf.xml.mandatory » résolue vers une source de configuration en lecture seule à la position 0 +Sep 14 09:30:51 testhost gconfd (user-6112): Adresse « xml:readwrite:/home/user/.gconf » résolue vers une source de configuration accessible en écriture à la position 1 +Sep 14 09:30:51 testhost gconfd (user-6112): Adresse « xml:readonly:/etc/gconf/gconf.xml.defaults » résolue vers une source de configuration en lecture seule à la position 2 +Sep 14 09:30:51 testhost gconfd (user-6112): Adresse « xml:readonly:/var/lib/gconf/debian.defaults » résolue vers une source de configuration en lecture seule à la position 3 +Sep 14 09:30:51 testhost gconfd (user-6112): Adresse « xml:readonly:/var/lib/gconf/defaults » résolue vers une source de configuration en lecture seule à la position 4 +Sep 14 09:30:57 testhost gconfd (user-6112): Adresse « xml:readwrite:/home/user/.gconf » résolue vers une source de configuration accessible en écriture à la position 0 +Sep 14 09:31:41 testhost gconfd (root-6402): démarrage (version 2.18.0.1), pid 6402 utilisateur « root » +Sep 14 09:31:41 testhost gconfd (root-6402): Adresse « xml:readonly:/etc/gconf/gconf.xml.mandatory » résolue vers une source de configuration en lecture seule à la position 0 +Sep 14 09:31:41 testhost gconfd (root-6402): Adresse « xml:readwrite:/root/.gconf » résolue vers une source de configuration accessible en écriture à la position 1 +Sep 14 09:31:41 testhost gconfd (root-6402): Adresse « xml:readonly:/etc/gconf/gconf.xml.defaults » résolue vers une source de configuration en lecture seule à la position 2 +Sep 14 09:31:41 testhost gconfd (root-6402): Adresse « xml:readonly:/var/lib/gconf/debian.defaults » résolue vers une source de configuration en lecture seule à la position 3 +Sep 14 09:31:41 testhost gconfd (root-6402): Adresse « xml:readonly:/var/lib/gconf/defaults » résolue vers une source de configuration en lecture seule à la position 4 +Sep 14 09:32:11 testhost gconfd (root-6402): SIGHUP reçu, rechargement de toutes les bases de données +Sep 14 09:32:11 testhost gconfd (root-6402): Adresse « xml:readonly:/etc/gconf/gconf.xml.mandatory » résolue vers une source de configuration en lecture seule à la position 0 +Sep 14 09:32:11 testhost gconfd (root-6402): Adresse « xml:readwrite:/root/.gconf » résolue vers une source de configuration accessible en écriture à la position 1 +Sep 14 09:32:11 testhost gconfd (root-6402): Adresse « xml:readonly:/etc/gconf/gconf.xml.defaults » résolue vers une source de configuration en lecture seule à la position 2 +Sep 14 09:32:11 testhost gconfd (root-6402): Adresse « xml:readonly:/var/lib/gconf/debian.defaults » résolue vers une source de configuration en lecture seule à la position 3 +Sep 14 09:32:11 testhost gconfd (root-6402): Adresse « xml:readonly:/var/lib/gconf/defaults » résolue vers une source de configuration en lecture seule à la position 4 +Sep 14 09:32:11 testhost gconfd (root-6402): Le serveur GConf n'est pas en cours d'utilisation, arrêt. +Sep 14 09:32:11 testhost gconfd (root-6402): Sortie +Sep 14 14:12:33 testhost gconfd (root-17578): démarrage (version 2.18.0.1), pid 17578 utilisateur « root » +Sep 14 14:12:33 testhost gconfd (root-17578): Adresse « xml:readonly:/etc/gconf/gconf.xml.mandatory » résolue vers une source de configuration en lecture seule à la position 0 +Sep 14 14:12:33 testhost gconfd (root-17578): Adresse « xml:readwrite:/root/.gconf » résolue vers une source de configuration accessible en écriture à la position 1 +Sep 14 14:12:33 testhost gconfd (root-17578): Adresse « xml:readonly:/etc/gconf/gconf.xml.defaults » résolue vers une source de configuration en lecture seule à la position 2 +Sep 14 14:12:33 testhost gconfd (root-17578): Adresse « xml:readonly:/var/lib/gconf/debian.defaults » résolue vers une source de configuration en lecture seule à la position 3 +Sep 14 14:12:33 testhost gconfd (root-17578): Adresse « xml:readonly:/var/lib/gconf/defaults » résolue vers une source de configuration en lecture seule à la position 4 +Sep 14 14:13:03 testhost gconfd (root-17578): SIGHUP reçu, rechargement de toutes les bases de données +Sep 14 14:13:03 testhost gconfd (root-17578): Adresse « xml:readonly:/etc/gconf/gconf.xml.mandatory » résolue vers une source de configuration en lecture seule à la position 0 +Sep 14 14:13:03 testhost gconfd (root-17578): Adresse « xml:readwrite:/root/.gconf » résolue vers une source de configuration accessible en écriture à la position 1 +Sep 14 14:13:03 testhost gconfd (root-17578): Adresse « xml:readonly:/etc/gconf/gconf.xml.defaults » résolue vers une source de configuration en lecture seule à la position 2 +Sep 14 14:13:03 testhost gconfd (root-17578): Adresse « xml:readonly:/var/lib/gconf/debian.defaults » résolue vers une source de configuration en lecture seule à la position 3 +Sep 14 14:13:03 testhost gconfd (root-17578): Adresse « xml:readonly:/var/lib/gconf/defaults » résolue vers une source de configuration en lecture seule à la position 4 +Sep 14 14:13:03 testhost gconfd (root-17578): Le serveur GConf n'est pas en cours d'utilisation, arrêt. +Sep 14 14:13:03 testhost gconfd (root-17578): Sortie +Sep 14 17:00:11 testhost gconfd (user-6112): Sortie +Sep 17 09:22:23 testhost dhcdbd: Started up. +Sep 17 09:22:26 testhost hpiod: 1.7.3 accepting connections at 2208... +Sep 17 09:25:42 testhost gconfd (user-5841): démarrage (version 2.18.0.1), pid 5841 utilisateur « user » +Sep 17 09:25:42 testhost gconfd (user-5841): Adresse « xml:readonly:/etc/gconf/gconf.xml.mandatory » résolue vers une source de configuration en lecture seule à la position 0 +Sep 17 09:25:42 testhost gconfd (user-5841): Adresse « xml:readwrite:/home/user/.gconf » résolue vers une source de configuration accessible en écriture à la position 1 +Sep 17 09:25:42 testhost gconfd (user-5841): Adresse « xml:readonly:/etc/gconf/gconf.xml.defaults » résolue vers une source de configuration en lecture seule à la position 2 +Sep 17 09:25:42 testhost gconfd (user-5841): Adresse « xml:readonly:/var/lib/gconf/debian.defaults » résolue vers une source de configuration en lecture seule à la position 3 +Sep 17 09:25:42 testhost gconfd (user-5841): Adresse « xml:readonly:/var/lib/gconf/defaults » résolue vers une source de configuration en lecture seule à la position 4 +Sep 17 09:25:50 testhost gconfd (user-5841): Adresse « xml:readwrite:/home/user/.gconf » résolue vers une source de configuration accessible en écriture à la position 0 +Sep 17 10:40:45 testhost gconfd (root-9025): démarrage (version 2.18.0.1), pid 9025 utilisateur « root » +Sep 17 10:40:45 testhost gconfd (root-9025): Adresse « xml:readonly:/etc/gconf/gconf.xml.mandatory » résolue vers une source de configuration en lecture seule à la position 0 +Sep 17 10:40:45 testhost gconfd (root-9025): Adresse « xml:readwrite:/root/.gconf » résolue vers une source de configuration accessible en écriture à la position 1 +Sep 17 10:40:45 testhost gconfd (root-9025): Adresse « xml:readonly:/etc/gconf/gconf.xml.defaults » résolue vers une source de configuration en lecture seule à la position 2 +Sep 17 10:40:45 testhost gconfd (root-9025): Adresse « xml:readonly:/var/lib/gconf/debian.defaults » résolue vers une source de configuration en lecture seule à la position 3 +Sep 17 10:40:45 testhost gconfd (root-9025): Adresse « xml:readonly:/var/lib/gconf/defaults » résolue vers une source de configuration en lecture seule à la position 4 +Sep 17 10:40:54 testhost gconfd (root-9025): Sortie +Sep 17 10:41:13 testhost gconfd (user-5841): SIGHUP reçu, rechargement de toutes les bases de données +Sep 17 10:41:13 testhost gconfd (user-5841): Adresse « xml:readonly:/etc/gconf/gconf.xml.mandatory » résolue vers une source de configuration en lecture seule à la position 0 +Sep 17 10:41:13 testhost gconfd (user-5841): Adresse « xml:readwrite:/home/user/.gconf » résolue vers une source de configuration accessible en écriture à la position 1 +Sep 17 10:41:13 testhost gconfd (user-5841): Adresse « xml:readonly:/etc/gconf/gconf.xml.defaults » résolue vers une source de configuration en lecture seule à la position 2 +Sep 17 10:41:13 testhost gconfd (user-5841): Adresse « xml:readonly:/var/lib/gconf/debian.defaults » résolue vers une source de configuration en lecture seule à la position 3 +Sep 17 10:41:13 testhost gconfd (user-5841): Adresse « xml:readonly:/var/lib/gconf/defaults » résolue vers une source de configuration en lecture seule à la position 4 +Sep 17 18:00:10 testhost gconfd (root-22226): démarrage (version 2.18.0.1), pid 22226 utilisateur « root » +Sep 17 18:00:10 testhost gconfd (root-22226): Adresse « xml:readonly:/etc/gconf/gconf.xml.mandatory » résolue vers une source de configuration en lecture seule à la position 0 +Sep 17 18:00:10 testhost gconfd (root-22226): Adresse « xml:readwrite:/root/.gconf » résolue vers une source de configuration accessible en écriture à la position 1 +Sep 17 18:00:10 testhost gconfd (root-22226): Adresse « xml:readonly:/etc/gconf/gconf.xml.defaults » résolue vers une source de configuration en lecture seule à la position 2 +Sep 17 18:00:10 testhost gconfd (root-22226): Adresse « xml:readonly:/var/lib/gconf/debian.defaults » résolue vers une source de configuration en lecture seule à la position 3 +Sep 17 18:00:10 testhost gconfd (root-22226): Adresse « xml:readonly:/var/lib/gconf/defaults » résolue vers une source de configuration en lecture seule à la position 4 +Sep 17 18:00:40 testhost gconfd (root-22226): Le serveur GConf n'est pas en cours d'utilisation, arrêt. +Sep 17 18:00:40 testhost gconfd (root-22226): Sortie +Sep 18 09:29:34 testhost gnome-power-manager: (user) Déconnexion interactive GNOME car le bouton de mise sous tension a été appuyé +Sep 18 15:52:55 testhost gnome-power-manager: (user) Déconnexion interactive GNOME car le bouton de mise sous tension a été appuyé +Sep 18 15:54:01 testhost dhcdbd: Started up. +Sep 18 15:54:04 testhost hpiod: 1.7.3 accepting connections at 2208... +Sep 18 15:55:33 testhost gconfd (user-6085): démarrage (version 2.18.0.1), pid 6085 utilisateur « user » +Sep 18 15:55:33 testhost gconfd (user-6085): Adresse « xml:readonly:/etc/gconf/gconf.xml.mandatory » résolue vers une source de configuration en lecture seule à la position 0 +Sep 18 15:55:33 testhost gconfd (user-6085): Adresse « xml:readwrite:/home/user/.gconf » résolue vers une source de configuration accessible en écriture à la position 1 +Sep 18 15:55:33 testhost gconfd (user-6085): Adresse « xml:readonly:/etc/gconf/gconf.xml.defaults » résolue vers une source de configuration en lecture seule à la position 2 +Sep 18 15:55:33 testhost gconfd (user-6085): Adresse « xml:readonly:/var/lib/gconf/debian.defaults » résolue vers une source de configuration en lecture seule à la position 3 +Sep 18 15:55:33 testhost gconfd (user-6085): Adresse « xml:readonly:/var/lib/gconf/defaults » résolue vers une source de configuration en lecture seule à la position 4 +Sep 18 15:55:41 testhost gconfd (user-6085): Adresse « xml:readwrite:/home/user/.gconf » résolue vers une source de configuration accessible en écriture à la position 0 +Sep 18 17:33:42 testhost gconfd (user-6085): Sortie +Sep 19 09:21:51 testhost dhcdbd: Started up. +Sep 19 09:21:54 testhost hpiod: 1.7.3 accepting connections at 2208... +Sep 19 09:22:22 testhost gconfd (user-5989): démarrage (version 2.18.0.1), pid 5989 utilisateur « user » +Sep 19 09:22:22 testhost gconfd (user-5989): Adresse « xml:readonly:/etc/gconf/gconf.xml.mandatory » résolue vers une source de configuration en lecture seule à la position 0 +Sep 19 09:22:22 testhost gconfd (user-5989): Adresse « xml:readwrite:/home/user/.gconf » résolue vers une source de configuration accessible en écriture à la position 1 diff --git a/ksystemlog/tests/testFiles/xsession/xsession-error b/ksystemlog/tests/testFiles/xsession/xsession-error new file mode 100644 index 00000000..8af3b7d2 --- /dev/null +++ b/ksystemlog/tests/testFiles/xsession/xsession-error @@ -0,0 +1,46 @@ +X Error: BadPixmap (invalid Pixmap parameter) 4 + Major opcode: 54 + Minor opcode: 0 + Resource id: 0x1604e8b +kbuildsycoca running... +Reusing existing ksycoca +kio (KService*): WARNING: The desktop entry file .hidden/dirfilterplugin.desktop has Type=Service but is located under "apps" instead of "services" +kio (KService*): WARNING: Invalid Service : .hidden/dirfilterplugin.desktop +kbuildsycoca running... +Reusing existing ksycoca +kio (KService*): WARNING: The desktop entry file .hidden/dirfilterplugin.desktop has Type=Service but is located under "apps" instead of "services" +kio (KService*): WARNING: Invalid Service : .hidden/dirfilterplugin.desktop +kbuildsycoca running... +Reusing existing ksycoca +kio (KService*): WARNING: The desktop entry file .hidden/dirfilterplugin.desktop has Type=Service but is located under "apps" instead of "services" +kio (KService*): WARNING: Invalid Service : .hidden/dirfilterplugin.desktop +kbuildsycoca running... +Reusing existing ksycoca +kio (KService*): WARNING: The desktop entry file .hidden/dirfilterplugin.desktop has Type=Service but is located under "apps" instead of "services" +kio (KService*): WARNING: Invalid Service : .hidden/dirfilterplugin.desktop +DCOP Cleaning up dead connections. +kbuildsycoca running... +Reusing existing ksycoca +kio (KService*): WARNING: The desktop entry file .hidden/dirfilterplugin.desktop has Type=Service but is located under "apps" instead of "services" +kio (KService*): WARNING: Invalid Service : .hidden/dirfilterplugin.desktop +kbuildsycoca running... +Reusing existing ksycoca +kio (KService*): WARNING: The desktop entry file .hidden/dirfilterplugin.desktop has Type=Service but is located under "apps" instead of "services" +kio (KService*): WARNING: Invalid Service : .hidden/dirfilterplugin.desktop +DCOP Cleaning up dead connections. +X Error: BadPixmap (invalid Pixmap parameter) 4 + Major opcode: 54 + Minor opcode: 0 + Resource id: 0x1604e27 +X Error: BadMatch (invalid parameter attributes) 8 + Major opcode: 161 + Minor opcode: 6 + Resource id: 0x1604e27 +X Error: BadPixmap (invalid Pixmap parameter) 4 + Major opcode: 54 + Minor opcode: 0 + Resource id: 0x160533a +X Error: BadMatch (invalid parameter attributes) 8 + Major opcode: 161 + Minor opcode: 6 + Resource id: 0x160533a diff --git a/ksystemlog/tests/testResources.qrc b/ksystemlog/tests/testResources.qrc new file mode 100644 index 00000000..d62009ba --- /dev/null +++ b/ksystemlog/tests/testResources.qrc @@ -0,0 +1,19 @@ + + + testFiles/default/one-line.log + testFiles/default/two-lines.log + testFiles/system/system.log + testFiles/system/strange-lines.log + testFiles/system/delete-process-identifier.log + testFiles/system/max-lines.log + testFiles/system/duplicate-lines.log + + testFiles/logFileReader/file.txt + + testFiles/apache/access.log + + testFiles/kernel/ubuntu.dmesg + testFiles/kernel/suse.dmesg + + + diff --git a/ksystemlog/tests/testUtil.cpp b/ksystemlog/tests/testUtil.cpp new file mode 100644 index 00000000..4bae2bff --- /dev/null +++ b/ksystemlog/tests/testUtil.cpp @@ -0,0 +1,133 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "testUtil.h" +#include +#include + +#include +#include + +#include "systemFactory.h" +#include "kernelFactory.h" + +#include "logging.h" + +#include "logViewModel.h" +#include "logViewWidget.h" + +#include "analyzer.h" + +TestUtil::TestUtil() { + +} + +TestUtil::~TestUtil() { + +} + +void TestUtil::registerLogModeFactories() const { + logDebug() << "Registering existing log mode factories" << endl; + Globals::instance()->registerLogModeFactory(new SystemLogModeFactory()); + Globals::instance()->registerLogModeFactory(new KernelLogModeFactory()); +} + +LogViewModel* TestUtil::defineLogViewModel(Analyzer* analyzer) const { + LogViewWidget* logViewWidget = new LogViewWidget(); + LogViewModel* model = new LogViewModel(logViewWidget); + + analyzer->setLogViewModel(model); + + return model; +} + +Analyzer* TestUtil::createAnalyzer(const QString& logModeName, LogViewModel** model) const { + Analyzer* analyzer = Globals::instance()->findLogMode(logModeName)->createAnalyzer(); + + *model = defineLogViewModel(analyzer); + + return analyzer; +} + +void TestUtil::destroyReader(Analyzer* analyzer) const { + //TODO Unable to delete the created LogViewWidget, because the LogViewModel is not accessible from Reader + delete analyzer; +} + +QList TestUtil::createLogFiles(const QString& resourceFileName) const { + QTemporaryFile* tempFile = QTemporaryFile::createLocalFile(resourceFileName); + logDebug() << "Using log file name " << tempFile << endl; + tempFile->setPermissions(QFile::WriteUser | QFile::ReadUser | QFile::ReadOwner | QFile::WriteOwner); + + LogLevel* informationLogLevel = Globals::instance()->informationLogLevel(); + + QList logFiles; + LogFile logFile(KUrl(tempFile->fileName()), informationLogLevel); + logFiles.append(logFile); + + return logFiles; +} + + +void TestUtil::testLine( + LogLine* line, + const QString& originalFileName, + LogLevel* logLevel, + const QDateTime& time, + const QStringList& items) const { + + QCOMPARE (line->time(), time); + QCOMPARE (line->sourceFileName(), originalFileName); + QCOMPARE (line->logLevel()->id(), logLevel->id()); + + //Test log line items + QStringList logItems = line->logItems(); + QCOMPARE (logItems.count(), items.count()); + QCOMPARE (logItems, items); + +} + + +void TestUtil::addLogLines(const QString& fileName, const QStringList& addedLines) const { + //Wait 1 sec to be sure the dirty signal will be emitted + QTest::qWait(1000); + + QFile data(fileName); + if (data.open(QFile::Append | QIODevice::Text)) { + logDebug() << "Opening "<< fileName << " for writing " << addedLines.count() << " line(s)."<< endl; + + QTextStream out(&data); + foreach (const QString &line, addedLines) { + out << line << endl; + } + + out.flush(); + data.close(); + } + else { + QFAIL( QString::fromLatin1("Unable to open the test log file %1").arg(fileName).toUtf8() ); + } + + //Wait 3 secs to be sure the log file changed have been processed + QTest::qWait(1000); +} + +#include "testUtil.moc" diff --git a/ksystemlog/tests/testUtil.h b/ksystemlog/tests/testUtil.h new file mode 100644 index 00000000..4c1b553d --- /dev/null +++ b/ksystemlog/tests/testUtil.h @@ -0,0 +1,89 @@ +/*************************************************************************** + * KSystemLog, a system log viewer tool * + * Copyright (C) 2007 by Nicolas Ternisien * + * nicolas.ternisien@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef TEST_UTIL_H +#define TEST_UTIL_H + +#include + +#include +#include + +class LogViewModel; +class Analyzer; + +#include "logFile.h" +#include "logLine.h" + +#include "globals.h" + +class TestUtil : public QObject { + + Q_OBJECT + + public: + TestUtil(); + + virtual ~TestUtil(); + + /* + * Init method, used to register existing Log Modes + */ + void registerLogModeFactories() const; + + + LogViewModel* defineLogViewModel(Analyzer* analyzer) const; + + /** + * Find a reader and initialize it. + * If the model == NULL, then a new model is created and the pointer is update + */ + Analyzer* createAnalyzer(const QString& logModeName, LogViewModel** model) const; + + /* + * Delete the reader provided in parameter + */ + void destroyReader(Analyzer* analyzer) const; + + /* + * Create a LogFile list from a single resourceFileName + */ + QList createLogFiles(const QString& resourceFileName) const; + + /* + * Add the "addedLines" list to the file "fileName" + */ + void addLogLines(const QString& fileName, const QStringList& addedLines) const; + + /* + * Test every fields of the logLine with the other parameters + */ + void testLine( + LogLine* line, + const QString& originalFileName, + LogLevel* logLevel, + const QDateTime& time, + const QStringList& items) const; + +}; + + +#endif //TEST_UTIL_H diff --git a/libbluedevil/CMakeLists.txt b/libbluedevil/CMakeLists.txt new file mode 100644 index 00000000..5509d61d --- /dev/null +++ b/libbluedevil/CMakeLists.txt @@ -0,0 +1,61 @@ +project(libbluedevil) + +cmake_minimum_required(VERSION 2.6.4) +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) + +find_package(Qt4 REQUIRED) + +include(CheckCXXCompilerFlag) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +check_cxx_compiler_flag(-fvisibility=hidden _HAVE_VISIBILITY) +if (_HAVE_VISIBILITY AND NOT WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") + + check_cxx_compiler_flag(-fvisibility-inlines-hidden _HAVE_VISIBILITY_INLINES) + if (_HAVE_VISIBILITY_INLINES) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden") + endif (_HAVE_VISIBILITY_INLINES) +endif (_HAVE_VISIBILITY AND NOT WIN32) + +# +### lib64/32 support kde like +# +set(LIB_SUFFIX "" CACHE STRING "Define suffix for lib directory (32/64)") + +set(BIN_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/bin) +set(LIB_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}) + +set(INSTALL_TARGETS_DEFAULT_ARGS RUNTIME DESTINATION "${BIN_INSTALL_DIR}" + LIBRARY DESTINATION "${LIB_INSTALL_DIR}" + ARCHIVE DESTINATION "${LIB_INSTALL_DIR}" COMPONENT Devel ) + + +set(GENERIC_LIB_VERSION "2.0") +set(GENERIC_LIB_SOVERSION "2") +set(VERSION ${GENERIC_LIB_VERSION}) + +add_subdirectory(bluedevil) + +option(LIBBLUEDEVIL_BUILD_API_DOCS "Build libbluedevil API documentation") + +################## apidox ################################ +if(LIBBLUEDEVIL_BUILD_API_DOCS) + find_package(Doxygen) + + if(DOXYGEN_EXECUTABLE) + configure_file(${libbluedevil_SOURCE_DIR}/.Doxyfile.cmake ${libbluedevil_BINARY_DIR}/Doxyfile) + + if(EXISTS ${QT_DOC_DIR}/html) + set(QTDOCS "${QT_DOC_DIR}/html") + else(EXISTS ${QT_DOC_DIR}/html) + set(QTDOCS "http://doc.qt.nokia.com/latest/") + endif(EXISTS ${QT_DOC_DIR}/html) + + add_custom_target( + apidox ALL + COMMAND ${DOXYGEN_EXECUTABLE} Doxyfile + COMMAND doc/html/installdox -l qt4.tag@${QTDOCS} doc/html/*.html) + endif(DOXYGEN_EXECUTABLE) +endif(LIBBLUEDEVIL_BUILD_API_DOCS) diff --git a/libbluedevil/HACKING b/libbluedevil/HACKING new file mode 100644 index 00000000..246aee5e --- /dev/null +++ b/libbluedevil/HACKING @@ -0,0 +1,27 @@ +libbluedevil follows the same style as kdelibs. + +You have a reference to this style on this website: +http://techbase.kde.org/Policies/Kdelibs_Coding_Style + +All copyright headers are unified with the following style: + +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 AuthorName Surname Surname * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ diff --git a/libbluedevil/bluedevil/CMakeLists.txt b/libbluedevil/bluedevil/CMakeLists.txt new file mode 100644 index 00000000..d3ac5fda --- /dev/null +++ b/libbluedevil/bluedevil/CMakeLists.txt @@ -0,0 +1,46 @@ +add_subdirectory(test) + +include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${QT_INCLUDES}) + +set(libbluedevil_SRCS + bluedevilmanager.cpp + bluedevilmanager_p.cpp + bluedeviladapter.cpp + bluedevildevice.cpp + bluedevilutils.cpp +) + +set(dbusobjectmanager_xml ${CMAKE_CURRENT_SOURCE_DIR}/bluez/org.freedesktop.DBus.ObjectManager.xml) +set_source_files_properties(${dbusobjectmanager_xml} PROPERTIES INCLUDE "bluedevil/bluedevildbustypes.h") +QT4_ADD_DBUS_INTERFACE(libbluedevil_SRCS ${dbusobjectmanager_xml} dbusobjectmanager) + +QT4_ADD_DBUS_INTERFACE(libbluedevil_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/bluez/org.freedesktop.DBus.Properties.xml dbusproperties) + +QT4_ADD_DBUS_INTERFACE(libbluedevil_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/bluez/org.bluez.Adapter1.xml bluezadapter1) +QT4_ADD_DBUS_INTERFACE(libbluedevil_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/bluez/org.bluez.AgentManager1.xml bluezagentmanager1) +QT4_ADD_DBUS_INTERFACE(libbluedevil_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/bluez/org.bluez.Device1.xml bluezdevice1) + +QT4_AUTOMOC(${libbluedevil_SRCS}) + +add_library(bluedevil SHARED ${libbluedevil_SRCS}) + +target_link_libraries(bluedevil ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY}) + +set_target_properties(bluedevil PROPERTIES + VERSION ${GENERIC_LIB_VERSION} + SOVERSION ${GENERIC_LIB_SOVERSION} +) + +install(TARGETS bluedevil ${INSTALL_TARGETS_DEFAULT_ARGS}) + +install(FILES bluedevilmanager.h + bluedeviladapter.h + bluedevildevice.h + bluedevil_export.h + bluedevil.h + bluedevilutils.h DESTINATION include/bluedevil) + +if(NOT WIN32) # pkgconfig file + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/bluedevil.pc.in ${CMAKE_CURRENT_BINARY_DIR}/bluedevil.pc @ONLY) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/bluedevil.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) +endif(NOT WIN32) diff --git a/libbluedevil/bluedevil/bluedevil.h b/libbluedevil/bluedevil/bluedevil.h new file mode 100644 index 00000000..6dbf5715 --- /dev/null +++ b/libbluedevil/bluedevil/bluedevil.h @@ -0,0 +1,145 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +/*! + * @mainpage libbluedevil + * + * libbluedevil is a Qt-based library, written in C++ that makes it very easy and straight-forward + * to handle almost all Bluetooth related operations. It consists of several key classes, following: + * + * - Manager + * - Entry point to the library functionality. It is a singleton class, and it basically gives + * you access to the connected adapters. It will also inform through its signals when new + * adapters have been connected or removed. + * + * - Adapter + * - It gives you all kind of information (and also a way to modify it) about an adapter. With + * this class you can start scanning for remote devices, and being notified through its + * signals when new devices have been found. Here you can also retrieve devices given that + * you know their hardware address (MAC) or their UBI, both being unique for each device. + * + * - Device + * - With this class you are given information about a particular remote device. You can also + * set certain properties like whether the device is trusted, blocked, or provide an alias + * for it. + * + * - Utils + * - Contains general usage routines. + * + * All the libbluedevil classes are wrapped into a namespace called BlueDevil. + * + * You can have a look at some @ref examples. + */ + +/*! + * @page examples Examples + * + * @section manager Manager + * + * The Manager task is to serve as entry point to the library, being a singleton class. It will also + * inform about the service state (operational or not), as well as notify of adapters being + * connected or disconnected. + * + * For brevity, we will directly include the whole namespace: + * + * @code + * using namespace BlueDevil; + * @endcode + * + * In order to include the Manager API, you have to perform: + * + * @code + * #include + * @endcode + * + * So, all the dance usually starts as: + * + * @code + * Manager *const manager = Manager::self(); + * + * // If Bluetooth is operational, we can directly retrieve the first usable adapter, and start working + * // with it. Otherwise, we can connect to the usableAdapterChanged signal, so we will be notified + * // when we have an adapter ready to be used. + * if (manager->isBluetoothOperational()) { + * Adapter *const adapter = manager->usableAdapter(); + * // Do something interesting with the adapter... + * } else { + * connect(manager, SIGNAL(usableAdapterChanged(Adapter*)), this, SLOT(usableAdapterChanged(Adapter*))); + * } + * @endcode + * + * It is very common that in some few calls we will be using the Manager, Adapter and Device APIs. + * In order to decrease the number of includes that you need to do, you have a handy trick: + * + * @code + * #include + * @endcode + * + * @section adapter Adapter + * + * An adapter is a device physically connected to the system. Its main job is to let other devices + * discover it as well as discover remote devices. + * + * We can perform a very typical task as discover remote devices. + * + * @code + * Adapter *const adapter = Manager::self()->usableAdapter(); + * connect(adapter, SIGNAL(deviceFound(Device*)), this, SLOT(deviceFound(Device*))); + * adapter->startDiscovery(); + * QTimer::singleShot(10000, adapter, SLOT(stopDiscovery()))); + * @endcode + * + * This snippet will discover devices for 10 seconds. For each device discovered, the slot + * deviceFound() will be called. + * + * The Adapter API also allows you to set up other Adapter settings such as if the adapter is powered + * or not, its visibility, the visibility timeout... + * + * @section device Device + * + * This class represents a remote device. This class basically retrieves information, but it is also + * possible to set certain properties for this device, such as whether this device is trusted or not, + * or blocked, or the local alias for this device. + * + * We can have a look at the slot deviceFound() that we named early before on the Adapter example: + * + * @code + * void MyClass::deviceFound(Device *device) + * { + * qDebug() << "Device found: " << device->name() << " (" << device->address() << ")"; + * qDebug() << "\tServices: " << device->UUIDs(); + * } + * @endcode + * + * The UUIDs are the services supported by this device, so for each found device, we ask (and print) + * its name, its hardware address (MAC) and the supported services by this device. + */ + +#ifndef BLUEDEVIL_H +#define BLUEDEVIL_H + +#include +#include +#include +#include + +#endif // BLUEDEVIL_H diff --git a/libbluedevil/bluedevil/bluedevil.pc.in b/libbluedevil/bluedevil/bluedevil.pc.in new file mode 100644 index 00000000..e6742bd0 --- /dev/null +++ b/libbluedevil/bluedevil/bluedevil.pc.in @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@LIB_INSTALL_DIR@ +includedir=@CMAKE_INSTALL_PREFIX@/include + +Name: bluedevil +Description: BlueDevil library +Version: @VERSION@ + +Libs: -L${libdir} -lbluedevil +Cflags: -I${includedir} diff --git a/libbluedevil/bluedevil/bluedevil_export.h b/libbluedevil/bluedevil/bluedevil_export.h new file mode 100644 index 00000000..6859fe7c --- /dev/null +++ b/libbluedevil/bluedevil/bluedevil_export.h @@ -0,0 +1,30 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef BLUEDEVIL_EXPORT_H +#define BLUEDEVIL_EXPORT_H + +#include + +#define BLUEDEVIL_EXPORT Q_DECL_EXPORT + +#endif // BLUEDEVIL_EXPORT_H diff --git a/libbluedevil/bluedevil/bluedeviladapter.cpp b/libbluedevil/bluedevil/bluedeviladapter.cpp new file mode 100644 index 00000000..066cd09c --- /dev/null +++ b/libbluedevil/bluedevil/bluedeviladapter.cpp @@ -0,0 +1,288 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "bluedeviladapter.h" +#include "bluedevildevice.h" + +#include "bluedevil/bluezadapter1.h" +#include "bluedevil/dbusproperties.h" + +namespace BlueDevil { + +/** + * @internal + */ +class Adapter::Private +{ +public: + Private(Adapter *q); + ~Private(); + + void startDiscovery(); + + void _k_deviceRemoved(const QString &objectPath); + void _k_propertyChanged(const QString &property, const QVariantMap &changed_properties, const QStringList &invalidated_properties); + void _k_devicePropertyChanged(const QString &property, const QVariant &value); + + org::bluez::Adapter1 *m_bluezAdapterInterface; + org::freedesktop::DBus::Properties *m_dbusPropertiesInterface; + + QMap m_devicesMap; + QMap m_devicesMapUBIKey; + QMap m_unpairedDevices; + + bool m_stableDiscovering; + + Adapter *const m_q; +}; + +Adapter::Private::Private(Adapter *q) + : m_stableDiscovering(false) + , m_q(q) +{ +} + +Adapter::Private::~Private() +{ + delete m_bluezAdapterInterface; + delete m_dbusPropertiesInterface; +} + +void Adapter::Private::startDiscovery() +{ + m_bluezAdapterInterface->StartDiscovery(); +} + +void Adapter::Private::_k_deviceRemoved(const QString &objectPath) +{ + Device *const device = m_devicesMapUBIKey.take(objectPath); + if (device) { + m_devicesMap.remove(m_devicesMap.key(device)); + m_unpairedDevices.remove(objectPath); + emit m_q->deviceRemoved(device); + delete device; + } +} + +void Adapter::Private::_k_propertyChanged(const QString &interface_name, const QVariantMap &changed_properties, const QStringList &invalidated_properties) +{ + QVariantMap::const_iterator i; + for(i = changed_properties.constBegin(); i != changed_properties.constEnd(); ++i) { + QVariant value = i.value(); + QString property = i.key(); + if (property == "Name") { + emit m_q->nameChanged(value.toString()); + } else if (property == "Powered") { + emit m_q->poweredChanged(value.toBool()); + } else if (property == "Discoverable") { + emit m_q->discoverableChanged(value.toBool()); + } else if (property == "Pairable") { + emit m_q->pairableChanged(value.toBool()); + } else if (property == "PairableTimeout") { + emit m_q->pairableTimeoutChanged(value.toUInt()); + } else if (property == "DiscoverableTimeout") { + emit m_q->discoverableTimeoutChanged(value.toUInt()); + } else if (property == "Discovering") { + emit m_q->discoveringChanged(value.toBool()); + } + emit m_q->propertyChanged(property, value); + } +} + +void Adapter::Private::_k_devicePropertyChanged(const QString& property, const QVariant& value) +{ + Device *device = qobject_cast(m_q->sender()); + Q_ASSERT(device); + + emit m_q->deviceChanged(device); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +Adapter::Adapter(const QString &adapterPath, QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ + d->m_bluezAdapterInterface = new org::bluez::Adapter1("org.bluez", adapterPath, QDBusConnection::systemBus(), this); + d->m_dbusPropertiesInterface = new org::freedesktop::DBus::Properties("org.bluez", adapterPath, QDBusConnection::systemBus(), this); + + connect(d->m_dbusPropertiesInterface, SIGNAL(PropertiesChanged(QString,QVariantMap,QStringList)), + this, SLOT(_k_propertyChanged(QString,QVariantMap,QStringList))); + + setPowered(true); // TODO: remember powered setting. +} + +Adapter::~Adapter() +{ + delete d; +} + +QString Adapter::address() const +{ + return d->m_bluezAdapterInterface->address(); +} + +QString Adapter::name() const +{ + return d->m_bluezAdapterInterface->name(); +} + +quint32 Adapter::adapterClass() const +{ + return d->m_bluezAdapterInterface->adapterClass(); +} + +bool Adapter::isPowered() const +{ + return d->m_bluezAdapterInterface->powered(); +} + +bool Adapter::isDiscoverable() const +{ + return d->m_bluezAdapterInterface->discoverable(); +} + +bool Adapter::isPairable() const +{ + return d->m_bluezAdapterInterface->pairable(); +} + +quint32 Adapter::paireableTimeout() const +{ + return d->m_bluezAdapterInterface->pairableTimeout(); +} + +quint32 Adapter::discoverableTimeout() const +{ + return d->m_bluezAdapterInterface->discoverableTimeout(); +} + +bool Adapter::isDiscovering() const +{ + return d->m_bluezAdapterInterface->discovering(); +} + +QList Adapter::unpairedDevices() const +{ + return d->m_unpairedDevices.values(); +} + +Device *Adapter::deviceForAddress(const QString &address) +{ + if (d->m_devicesMap.contains(address)) { + return d->m_devicesMap[address]; + } + return 0; +} + +Device *Adapter::deviceForUBI(const QString &UBI) +{ + if (d->m_devicesMapUBIKey.contains(UBI)) { + return d->m_devicesMapUBIKey[UBI]; + } + return 0; +} + +QStringList Adapter::UUIDs() +{ + QStringList UUIDs = d->m_bluezAdapterInterface->uUIDs(); + for(int i=0;im_bluezAdapterInterface->setPowered(powered); +} + +void Adapter::setDiscoverable(bool discoverable) +{ + d->m_bluezAdapterInterface->setDiscoverable(discoverable); +} + +void Adapter::setPairable(bool pairable) +{ + d->m_bluezAdapterInterface->setPairable(pairable); +} + +void Adapter::setPaireableTimeout(quint32 paireableTimeout) +{ + d->m_bluezAdapterInterface->setPairableTimeout(paireableTimeout); +} + +void Adapter::setDiscoverableTimeout(quint32 discoverableTimeout) +{ + d->m_bluezAdapterInterface->setDiscoverableTimeout(discoverableTimeout); +} + +void Adapter::removeDevice(Device *device) +{ + d->m_bluezAdapterInterface->RemoveDevice(QDBusObjectPath(device->UBI())); +} + +void Adapter::startDiscovery() const +{ + d->m_stableDiscovering = false; + d->startDiscovery(); +} + +void Adapter::startStableDiscovery() const +{ + d->m_stableDiscovering = true; + d->startDiscovery(); +} + +void Adapter::stopDiscovery() const +{ + d->m_stableDiscovering = false; + d->m_bluezAdapterInterface->StopDiscovery(); +} + +QList< Device* > Adapter::devices() +{ + return d->m_devicesMap.values(); +} + +void Adapter::addDevice(const QString &objectPath) +{ + Device * device = new Device(objectPath,this); + d->m_devicesMap.insert(device->address(),device); + d->m_devicesMapUBIKey.insert(objectPath,device); + emit deviceFound(device); + if(!device->isPaired()) { + d->m_unpairedDevices.insert(objectPath,device); + emit unpairedDeviceFound(device); + } + + connect(device, SIGNAL(propertyChanged(QString,QVariant)), SLOT(_k_devicePropertyChanged(QString,QVariant))); +} + +void Adapter::removeDevice(const QString &objectPath) +{ + d->_k_deviceRemoved(objectPath); +} + +} + +#include "bluedeviladapter.moc" diff --git a/libbluedevil/bluedevil/bluedeviladapter.h b/libbluedevil/bluedevil/bluedeviladapter.h new file mode 100644 index 00000000..9fce2f51 --- /dev/null +++ b/libbluedevil/bluedevil/bluedeviladapter.h @@ -0,0 +1,241 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef BLUEDEVILADAPTER_H +#define BLUEDEVILADAPTER_H + +#include + +#include +#include +#include + +namespace BlueDevil { + +class Device; +class Manager; + +/** + * @class Adapter bluedeviladapter.h bluedevil/bluedeviladapter.h + * + * This class represents an adapter. + * + * The task of an adapter is basically to discover remote devices. + * + * @author Rafael Fernández López + */ +class BLUEDEVIL_EXPORT Adapter + : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString address READ address) + Q_PROPERTY(QString name READ name) + Q_PROPERTY(quint32 adapterClass READ adapterClass) + Q_PROPERTY(bool powered READ isPowered WRITE setPowered) + Q_PROPERTY(bool discoverable READ isDiscoverable WRITE setDiscoverable) + Q_PROPERTY(bool pairable READ isPairable WRITE setPairable) + Q_PROPERTY(quint32 paireableTimeout READ paireableTimeout WRITE setPaireableTimeout) + Q_PROPERTY(quint32 discoverableTimeout READ discoverableTimeout WRITE setDiscoverableTimeout) + Q_PROPERTY(bool isDiscovering READ isDiscovering) + Q_PROPERTY(QList unpairedDevices READ unpairedDevices) + Q_PROPERTY(QList devices READ devices) + Q_PROPERTY(QStringList UUIDs READ UUIDs) + + friend class Manager; + friend class ManagerPrivate; + friend class Device; + +public: + virtual ~Adapter(); + + /** + * @return The address of the adapter. + */ + QString address() const; + + /** + * @return The friendly name of the adapter. + */ + QString name() const; + + /** + * @return The class of the adapter. + */ + quint32 adapterClass() const; + + /** + * @return Whether this adapter is consuming energy or not. + */ + bool isPowered() const; + + /** + * @return Whether this adapter is discoverable or not. + */ + bool isDiscoverable() const; + + /** + * @return Whether this adapter is pairable or not. + */ + bool isPairable() const; + + /** + * @return The timeout for the pairing process for this adapter in seconds. + */ + quint32 paireableTimeout() const; + + /** + * @return The timeout for this adapter to be discovered in seconds. + */ + quint32 discoverableTimeout() const; + + /** + * @return Whether this adapter is discovering at the moment or not. + */ + bool isDiscovering() const; + + /** + * @return A list with all unpaired devices found on the discovery phase. + */ + QList unpairedDevices() const; + + /** + * @return A device defined by its hardware address. + */ + Device *deviceForAddress(const QString &address); + + /** + * @return A device defined by its UBI. + */ + Device *deviceForUBI(const QString &UBI); + + /** + * @return All known devices by this adapter. They haven't been necessarily discovered in this + * session. + */ + QList devices(); + + /** + * @return Services provided by this adapter. + */ + QStringList UUIDs(); + +public Q_SLOTS: + /** + * Sets whether this adapter is consuming energy or not. + */ + void setPowered(bool powered); + + /** + * Sets whether this adapter is discoverable or not. + */ + void setDiscoverable(bool discoverable); + + /** + * Sets whether this adapter is pairable or not. + */ + void setPairable(bool pairable); + + /** + * Sets the timeout for the pairing process for this adapter in seconds. + * + * @note A @p paireableTimeout of 0 means that this adapter can be paired forever. + */ + void setPaireableTimeout(quint32 paireableTimeout); + + /** + * Sets the timeout for this adapter to be discovered in seconds. + * + * @note A @p discoverableTimeout of 0 means that this adapter can be discovered forever. + */ + void setDiscoverableTimeout(quint32 discoverableTimeout); + + /** + * Removes device. + */ + void removeDevice(Device *device); + + /** + * Starts device discovery. deviceFound signal will be emitted for each device found. + * + * @note It is possible that when discovering devices appear back and forth. This call will also + * allow the adapter to signal when devices have disappeared when discovering, what could + * not be exactly what you want. If the desired behavior is to only be notified of new + * discovered devices, please see startStableDiscovery. + */ + void startDiscovery() const; + + /** + * Starts device discovery. deviceFound signal will be emitted for each device found. + * + * @note This discovery type will never trigger deviceDisappeared signal while discovering, so + * you will only get deviceFound signals emitted. This also ensures that you will never get + * deviceFound repeated emissions for the same devices, in this sense is more stable. + */ + void startStableDiscovery() const; + + /** + * Stops device discovery. + */ + void stopDiscovery() const; + +Q_SIGNALS: + void deviceRemoved(Device *device); + void deviceFound(Device *device); + void unpairedDeviceFound(Device *device); + void nameChanged(const QString &name); + void poweredChanged(bool powered); + void discoverableChanged(bool discoverable); + void pairableChanged(bool pairable); + void pairableTimeoutChanged(quint32 pairableTimeout); + void discoverableTimeoutChanged(quint32 discoverableTimeout); + void deviceChanged(Device* device); + void discoveringChanged(bool discovering); + void propertyChanged(const QString &property, const QVariant &value); + +private: + /** + * @internal + */ + Adapter(const QString &adapterPath, QObject *parent = 0); + + /** + * @internal + */ + void addDevice(const QString &objectPath); + + /** + * @internal + */ + void removeDevice(const QString &objectPath); + + class Private; + Private *const d; + + Q_PRIVATE_SLOT(d, void _k_deviceRemoved(QString)) + Q_PRIVATE_SLOT(d, void _k_propertyChanged(QString,QVariantMap,QStringList)) + Q_PRIVATE_SLOT(d, void _k_devicePropertyChanged(QString,QVariant)) +}; + +} + +#endif // BLUEDEVILADAPTER_H diff --git a/libbluedevil/bluedevil/bluedevildbustypes.h b/libbluedevil/bluedevil/bluedevildbustypes.h new file mode 100644 index 00000000..971dbbde --- /dev/null +++ b/libbluedevil/bluedevil/bluedevildbustypes.h @@ -0,0 +1,34 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2013 Daniel Schaal * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef dbustypes_H +#define dbustypes_H + +#include +#include + +typedef QMap QVariantMapMap; +Q_DECLARE_METATYPE(QVariantMapMap) + +typedef QMap DBusManagerStruct; +Q_DECLARE_METATYPE(DBusManagerStruct) + +#endif // dbustypes_H diff --git a/libbluedevil/bluedevil/bluedevildevice.cpp b/libbluedevil/bluedevil/bluedevildevice.cpp new file mode 100644 index 00000000..884d0158 --- /dev/null +++ b/libbluedevil/bluedevil/bluedevildevice.cpp @@ -0,0 +1,288 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "bluedevildevice.h" +#include "bluedeviladapter.h" + +#include "bluedevil/bluezdevice1.h" +#include "bluedevil/dbusproperties.h" + +#include +#include + +namespace BlueDevil { + +class Task + : public QRunnable +{ +public: + Task(Device *device, const char *slot) + : m_device(device) + , m_slot(slot) + { + } + + void run() { + // We happen to be cool, so asyncCall is called: asyncCall(device, SLOT(method())). This + // makes slot to be "1method()", and invokeMethod does not like this, so we have to transform + // it to "method". + QMetaObject::invokeMethod(m_device, m_slot.mid(1, m_slot.count() - 3).toLatin1().data(), Qt::DirectConnection); + } + +private: + Device *m_device; + QString m_slot; +}; + +void asyncCall(Device *device, const char *slot) +{ + QThreadPool::globalInstance()->start(new Task(device, slot)); +} + +/** + * @internal + */ +class Device::Private +{ +public: + Private(BlueDevil::Device *q, const QString &path); + ~Private(); + + void _k_propertyChanged(const QString &interface_name, const QVariantMap &changed_values, const QStringList &invalidated_values); + QStringList _k_stringListToUpper(const QStringList & list); + + org::bluez::Device1 *m_bluezDeviceInterface; + org::freedesktop::DBus::Properties *m_dbuspropertiesInterface; + Adapter *m_adapter; + + // Bluez cached properties + bool m_registrationOnBusRejected; // used for avoid trying to register this device more + // than one time on the bus. + + Device *const m_q; +}; + +Device::Private::Private(Device *q, const QString &path) + : m_bluezDeviceInterface(0) + , m_dbuspropertiesInterface(0) + , m_registrationOnBusRejected(false) + , m_q(q) +{ + m_bluezDeviceInterface = new org::bluez::Device1("org.bluez", + path, + QDBusConnection::systemBus(), + m_q); + + m_dbuspropertiesInterface = new org::freedesktop::DBus::Properties("org.bluez",path,QDBusConnection::systemBus(),m_q); +} + +Device::Private::~Private() +{ + delete m_bluezDeviceInterface; + delete m_dbuspropertiesInterface; +} + +QStringList Device::Private::_k_stringListToUpper(const QStringList& list) +{ + QStringList upperList(list); + for(int i=0;ipairedChanged(value.toBool()); + } else if (property == "Connected") { + emit m_q->connectedChanged(value.toBool()); + } else if (property == "Trusted") { + emit m_q->trustedChanged(value.toBool()); + } else if (property == "Blocked") { + emit m_q->blockedChanged(value.toBool()); + } else if (property == "Alias") { + emit m_q->aliasChanged(value.toString()); + } else if (property == "Name") { + emit m_q->nameChanged(value.toString()); + } else if (property == "UUIDs") { + emit m_q->UUIDsChanged(_k_stringListToUpper(value.toStringList())); + } + emit m_q->propertyChanged(property, value); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +Device::Device(const QString &path, Adapter *adapter) + : QObject(adapter) + , d(new Private(this,path)) +{ + d->m_adapter = adapter; + qRegisterMetaType("BlueDevil::QUInt32StringMap"); + qDBusRegisterMetaType(); + + connect(d->m_dbuspropertiesInterface,SIGNAL(PropertiesChanged(QString,QVariantMap,QStringList)),this,SLOT(_k_propertyChanged(QString,QVariantMap,QStringList))); + +} + +Device::~Device() +{ + delete d; +} + +void Device::pair() const +{ + d->m_bluezDeviceInterface->Pair(); +} + +Adapter *Device::adapter() const +{ + return d->m_adapter; +} + +QString Device::address() const +{ + return d->m_bluezDeviceInterface->address(); +} + +QString Device::name() const +{ + return d->m_bluezDeviceInterface->name(); +} + +QString Device::friendlyName() const +{ + QString alias = d->m_bluezDeviceInterface->alias(); + QString name = d->m_bluezDeviceInterface->name(); + if (alias.isEmpty() || alias == name) { + return name; + } + return QString("%1 (%2)").arg(alias).arg(name); +} + +QString Device::icon() const +{ + QString icon = d->m_bluezDeviceInterface->icon(); + if (icon.isEmpty()) { + return "preferences-system-bluetooth"; + } + return icon; +} + +quint32 Device::deviceClass() const +{ + return d->m_bluezDeviceInterface->deviceClass(); +} + +bool Device::isPaired() const +{ + return d->m_bluezDeviceInterface->paired(); +} + +QString Device::alias() const +{ + return d->m_bluezDeviceInterface->alias(); +} + +bool Device::hasLegacyPairing() const +{ + return d->m_bluezDeviceInterface->legacyPairing(); +} + +QStringList Device::UUIDs() +{ + QStringList UUIDs = d->_k_stringListToUpper(d->m_bluezDeviceInterface->uUIDs()); + if (sender()) { + emit UUIDsResult(this, UUIDs); + } + return UUIDs; +} + +QString Device::UBI() +{ + const QString path = d->m_bluezDeviceInterface->path(); + if (sender()) { + emit UBIResult(this, path); + } + return path; +} + +bool Device::isConnected() +{ + bool connected = d->m_bluezDeviceInterface->connected(); + if (sender()) { + emit isConnectedResult(this, connected); + } + return connected; +} + +bool Device::isTrusted() +{ + bool trusted = d->m_bluezDeviceInterface->trusted(); + if (sender()) { + emit isTrustedResult(this, trusted); + } + return trusted; +} + +bool Device::isBlocked() +{ + bool blocked = d->m_bluezDeviceInterface->blocked(); + if (sender()) { + emit isBlockedResult(this, blocked); + } + return blocked; +} + +void Device::setTrusted(bool trusted) +{ + d->m_bluezDeviceInterface->setTrusted(trusted); +} + +void Device::setBlocked(bool blocked) +{ + d->m_bluezDeviceInterface->setBlocked(blocked); +} + +void Device::setAlias(const QString &alias) +{ + d->m_bluezDeviceInterface->setAlias(alias); +} + +void Device::disconnect() +{ + d->m_bluezDeviceInterface->Disconnect(); +} + +void Device::connectDevice() +{ + d->m_bluezDeviceInterface->Connect(); +} + +} + +#include "bluedevildevice.moc" diff --git a/libbluedevil/bluedevil/bluedevildevice.h b/libbluedevil/bluedevil/bluedevildevice.h new file mode 100644 index 00000000..c18f10ca --- /dev/null +++ b/libbluedevil/bluedevil/bluedevildevice.h @@ -0,0 +1,325 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef BLUEDEVILDEVICE_H +#define BLUEDEVILDEVICE_H + +#include + +#include "bluedeviladapter.h" +#include "bluedevilmanager.h" + +#include +#include +#include + +namespace BlueDevil { + +class Device; + +/** + * Generates an asynchronous call on any method of the Device class. Only some methods allow the + * option of returning the result in form of signal, so not all methods can return information + * in an asynchronous way. + * + * A typical usage follows: + * + * @code + * connect(device, SIGNAL(registerDeviceResult(Device*,bool)), this, SLOT(deviceRegistered(Device*,bool))); + * BlueDevil::asyncCall(device, SLOT(registerDevice())); + * @endcode + * + * We will later receive on our deviceRegistered slot the information when the fetching of + * information has finished. + * + * @warning Only documented methods (they are always slots) can be called asynchronously. They have + * been carefully chosen beforehand for those which are blocking. + */ +void BLUEDEVIL_EXPORT asyncCall(Device *device, const char *slot); + +/** + * @internal + */ +typedef QMap QUInt32StringMap; + +class Adapter; + +/** + * @class Device bluedevildevice.h bluedevil/bluedevildevice.h + * + * This class represents a remote device, discovered by an Adapter. + * + * This device has some information for free (this meaning there is no need to register the device + * on the bus). This properties that do not need connection are explicitly marked on their + * respective documentation. + * + * Since this values are cached, you will not get updates on their state until you make a call that + * forces the device to be registered on the bus (and thus, created, in D-Bus terminology), or you + * explicitly call to registerDevice. + * + * After the Device has been registered, it will automatically update its properties internally, and + * additionally signals like pairedChanged will be emitted when this properties are updated. + * + * Please note that since some functions here are blocking, there exists a way to asynchronous + * perform certain operations that are known to be expensive. This way your GUI will not block + * itself when waiting for a response from the remote device. + * + * @author Rafael Fernández López + */ +class BLUEDEVIL_EXPORT Device + : public QObject +{ + Q_OBJECT + + Q_PROPERTY(Adapter* adapter READ adapter) + Q_PROPERTY(QString address READ address) + Q_PROPERTY(QString name READ name) + Q_PROPERTY(QString friendlyName READ friendlyName) + Q_PROPERTY(QString icon READ icon) + Q_PROPERTY(quint32 deviceClass READ deviceClass) + Q_PROPERTY(bool isPaired READ isPaired) + Q_PROPERTY(QString alias READ alias WRITE setAlias) + Q_PROPERTY(bool hasLegacyPairing READ hasLegacyPairing) + Q_PROPERTY(QStringList UUIDs READ UUIDs) + Q_PROPERTY(QString UBI READ UBI) + Q_PROPERTY(bool isConnected READ isConnected) + Q_PROPERTY(bool trusted READ isTrusted WRITE setTrusted) + Q_PROPERTY(bool blocked READ isBlocked WRITE setBlocked) + + friend class Adapter; + friend class Manager; + +public: + virtual ~Device(); + + /** + * Starts the pairing process, the pairedChanged signal will be emitted if succeeded. + * + * @note If the device is registered moments before this function is called, then it might + * do not work in some devices. + */ + void pair() const; + + /** + * @return The adapter that discovered this remote device. + */ + Adapter *adapter() const; + + /** + * @return The physical address of the remote device. + * + * @note This request will not trigger a connection to the device. + */ + QString address() const; + + /** + * @return The name of the remote device. + * + * @note This request will not trigger a connection to the device, unless the name couldn't + * yet be retrieved (is empty). + */ + QString name() const; + + /** + * @return If there is any alias set, it returns the alias for this device along with its name + * in the form of "Alias (Name)". If no alias for this device was set, the name is + * directly returned. + * + * @note If the name and the alias are the same, "Name" is returned instead of "Name (Name)". + * + * @note This request will not trigger a connection to the device, unless the name couldn't + * yet be retrieved (is empty). + */ + QString friendlyName() const; + + /** + * @return The suggested icon for the remote device. + * + * @note This request will not trigger a connection to the device. + */ + QString icon() const; + + /** + * @return The class of the remote device. + * + * @note This request will not trigger a connection to the device. + */ + quint32 deviceClass() const; + + /** + * @return Whether this remote device is paired or not. + * + * @note This request will not trigger a connection to the device. + */ + bool isPaired() const; + + /** + * @return The alias of the remote device. + * + * @note This request will not trigger a connection to the device. + */ + QString alias() const; + + /** + * @return Whether this remote device supports legacy pairing or not. + * + * @note This request will not trigger a connection to the device. + */ + bool hasLegacyPairing() const; + + /** + * @return The list of supported services by the remote device always in uppercase. + * + * @note This request will trigger a connection to the device with the consequent registration + * on the bus. + * + * @note Allows being called with the asynchronous API through asyncCall. UUIDsResult signal + * will be emitted with the result. + */ + QStringList UUIDs(); + + /** + * @return UBI for this device. In case that the connection with the device fails, an empty + * string will be returned. + * + * @note This request will trigger a connection to the device with the consequent registration + * on the bus. + * + * @note Allows being called with the asynchronous API through asyncCall. UBIResult signal + * will be emitted with the result. + */ + QString UBI(); + + /** + * @return Whether this remote device is connected or not. + * + * @note This request will trigger a connection to the device with the consequent registration + * on the bus. + * + * @note Allows being called with the asynchronous API through asyncCall. isConnectedResult + * signal will be emitted with the result. + */ + bool isConnected(); + + /** + * @return Whether this remote device is trusted or not. + * + * @note This request will trigger a connection to the device with the consequent registration + * on the bus. + * + * @note Allows being called with the asynchronous API through asyncCall. isTrustedResult + * signal will be emitted with the result. + */ + bool isTrusted(); + + /** + * @return Whether this remote device is blocked or not. + * + * @note This request will trigger a connection to the device with the consequent registration + * on the bus. + * + * @note Allows being called with the asynchronous API through asyncCall. isBlockedResult + * signal will be emitted with the result. + */ + bool isBlocked(); + +public Q_SLOTS: + /** + * Sets whether this remote device is trusted or not. + * + * @note This request will trigger a connection to the device with the consequent registration + * on the bus. + * + * @note Allows being called with the asynchronous API through asyncCall. + */ + void setTrusted(bool trusted); + + /** + * Sets whether this remote device is blocked or not. + * + * @note This request will trigger a connection to the device with the consequent registration + * on the bus. + * + * @note Allows being called with the asynchronous API through asyncCall. + */ + void setBlocked(bool blocked); + + /** + * Sets the alias of the remote device. + * + * @note This request will trigger a connection to the device with the consequent registration + * on the bus. + * + * @note Allows being called with the asynchronous API through asyncCall. + */ + void setAlias(const QString &alias); + + /** + * Disconnect from this remote device. + * + * @note Allows being called with the asynchronous API through asyncCall. + */ + void disconnect(); + + /** + * Connect all profiles marked auto-connectable of this device. + */ + void connectDevice(); + +Q_SIGNALS: + void pairedChanged(bool paired); + void connectedChanged(bool connected); + void trustedChanged(bool trusted); + void blockedChanged(bool blocked); + void aliasChanged(const QString &alias); + void nameChanged(const QString &name); + void UUIDsChanged(const QStringList &UUIDs); + void propertyChanged(const QString &property, const QVariant &value); + void disconnectRequested(); + +/* + * Signals coming from asynchronous API. + */ +Q_SIGNALS: + void UUIDsResult(Device *device, const QStringList &UUIDs); + void UBIResult(Device *device, const QString &UBI); + void isConnectedResult(Device *device, bool connected); + void isTrustedResult(Device *device, bool trusted); + void isBlockedResult(Device *device, bool blocked); + +private: + /** + * @internal + */ + Device(const QString &path, Adapter *adapter); + + class Private; + Private *const d; + + Q_PRIVATE_SLOT(d, void _k_propertyChanged(QString,QVariantMap,QStringList)) +}; + +} + +Q_DECLARE_METATYPE(BlueDevil::QUInt32StringMap) + +#endif // BLUEDEVILDEVICE_H diff --git a/libbluedevil/bluedevil/bluedevilmanager.cpp b/libbluedevil/bluedevil/bluedevilmanager.cpp new file mode 100644 index 00000000..e5848be7 --- /dev/null +++ b/libbluedevil/bluedevil/bluedevilmanager.cpp @@ -0,0 +1,165 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "bluedevilmanager.h" +#include "bluedeviladapter.h" +#include "bluedevildevice.h" +#include "bluedevilmanager_p.h" +#include "bluedevildbustypes.h" + +#include "bluedevil/dbusobjectmanager.h" +#include "bluedevil/bluezagentmanager1.h" + +#include +#include + +#include + +namespace BlueDevil { + +static Manager *instance = 0; + +void Manager::registerAgent(const QString &agentPath, RegisterCapability registerCapability) +{ + QString capability; + + switch (registerCapability) { + case DisplayOnly: + capability = "DisplayOnly"; + break; + case DisplayYesNo: + capability = "DisplayYesNo"; + break; + case KeyboardOnly: + capability = "KeyboardOnly"; + break; + case NoInputNoOutput: + capability = "NoInputNoOutput"; + break; + default: + return; + } + + QDBusObjectPath agentObjectPath = QDBusObjectPath(agentPath); + d->m_bluezAgentManager->RegisterAgent(agentObjectPath, capability); +} + +void Manager::requestDefaultAgent(const QString& agentPath) +{ + QDBusObjectPath agentObjectPath = QDBusObjectPath(agentPath); + d->m_bluezAgentManager->RequestDefaultAgent(agentObjectPath); +} + +void Manager::unregisterAgent(const QString &agentPath) +{ + d->m_bluezAgentManager->UnregisterAgent(QDBusObjectPath(agentPath)); +} + + + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +Manager::Manager(QObject *parent) + : QObject(parent) + , d(new ManagerPrivate(this)) +{ + // Keep an eye open if bluez stops running + QDBusServiceWatcher *serviceWatcher = new QDBusServiceWatcher("org.bluez", QDBusConnection::systemBus(), + QDBusServiceWatcher::WatchForRegistration | + QDBusServiceWatcher::WatchForUnregistration, this); + connect(serviceWatcher, SIGNAL(serviceRegistered(QString)), d, SLOT(_k_bluezServiceRegistered())); + connect(serviceWatcher, SIGNAL(serviceUnregistered(QString)), d, SLOT(_k_bluezServiceUnregistered())); + + d->initialize(); +} + +Manager::~Manager() +{ + delete d; +} + +Manager *Manager::self() +{ + if (!instance) { + instance = new Manager; + } + return instance; +} + +void Manager::release() +{ + delete instance; + instance = 0; +} + +Adapter *Manager::usableAdapter() const +{ + if (!QDBusConnection::systemBus().isConnected() || !d->m_bluezServiceRunning) { + return 0; + } + + if (d->m_usableAdapter && d->m_usableAdapter->isPowered()) { + return d->m_usableAdapter; + } + return d->findUsableAdapter(); +} + +QList Manager::adapters() const +{ + if (!QDBusConnection::systemBus().isConnected() || !d->m_bluezServiceRunning) { + return QList(); + } + + return d->m_adapters.values(); +} + +Device* Manager::deviceForUBI(const QString& UBI) const +{ + Device *device = 0; + Q_FOREACH(Adapter *adapter, d->m_adapters) { + device = adapter->deviceForUBI(UBI); + if (device) { + return device; + } + } + + return 0; +} + +QList Manager::devices() const +{ + QList devices; + Q_FOREACH(Adapter *adapter, d->m_adapters) { + devices << adapter->devices(); + } + + return devices; +} + +bool Manager::isBluetoothOperational() const +{ + return QDBusConnection::systemBus().isConnected() && d->m_bluezServiceRunning && usableAdapter(); +} + +} + +#include "bluedevilmanager.moc" diff --git a/libbluedevil/bluedevil/bluedevilmanager.h b/libbluedevil/bluedevil/bluedevilmanager.h new file mode 100644 index 00000000..681fb2ba --- /dev/null +++ b/libbluedevil/bluedevil/bluedevilmanager.h @@ -0,0 +1,181 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef BLUEDEVILMANAGER_H +#define BLUEDEVILMANAGER_H + +#include + +#include +#include + +namespace BlueDevil { + +class Device; +class Adapter; +class ManagerPrivate; + +/** + * @class Manager bluedevilmanager.h bluedevil/bluedevilmanager.h + * + * Manager class. The entry point to BlueDevil exposed services. + * + * The typical way to proceed is to work with the first adapter, but you can also list all + * bluetooth adapters and work with the one you want. + * + * The interface is a singleton with release-when-you-want capability. + * + * All adapters and devices are created by BlueDevil, and the ownership is always of BlueDevil. + * + * @author Rafael Fernández López + */ +class BLUEDEVIL_EXPORT Manager + : public QObject +{ + Q_OBJECT + + Q_PROPERTY(Manager* self READ self) + Q_PROPERTY(QList adapters READ adapters) + Q_PROPERTY(bool isBluetoothOperational READ isBluetoothOperational) + + friend class ManagerPrivate; +public: + enum RegisterCapability { + DisplayOnly = 0, + DisplayYesNo = 1, + KeyboardOnly = 2, + NoInputNoOutput = 3 + }; + + virtual ~Manager(); + + /** + * @return The Manager instance. + */ + static Manager *self(); + + /** + * When you consider you have finished working with BlueDevil you can immediatly release the + * memory by calling this method. It will automatically delete all Adapters and Devices that + * were still on memory. + */ + static void release(); + + /** + * @return The first adapter that is ready to be used (is powered). If there are no usable + * adapters, NULL will be returned. + */ + Adapter *usableAdapter() const; + + /** + * @return A list with all the connected adapters. + */ + QList adapters() const; + + /** + * Returns a device for a given UBI independently of the adapter they are in + * + * All devices belong to an adapter so in order to find a device when we have more than + * one adapter is iterating on all adapters and call deviceForUBI. This method basically + * does that so application developers doesn't have to do it. + * + * @param Device UBI to find + * @return A device for the given UBI or null if none is found + */ + Device *deviceForUBI(const QString &UBI) const; + + /** + * Return a list of all known devices by all connected adaptors + * @return a list of all known devices + */ + QList devices() const; + /** + * @return Whether the bluetooth system is ready to be used, and there is a usable adapter + * connected and turned on at the system. + * + * @note After this check, if succeeded, you can freely access to all libbluedevil functionality + * by retrieving the an adapter through a call to usableAdapter(). + * + * @note If this method returns false, you can connect to the usableAdapterChanged signal, so + * you can be notified when bluetooth is operational. + */ + bool isBluetoothOperational() const; + +public Q_SLOTS: + /** + * Registers agent. + */ + void registerAgent(const QString &agentPath, RegisterCapability registerCapability); + + /** + * Unregisters agent. + */ + void unregisterAgent(const QString &agentPath); + + /** + * Request to set Agent with agentPath as default agent. + */ + void requestDefaultAgent(const QString &agentPath); + +Q_SIGNALS: + /** + * This signal will be emitted when an adapter has been connected. + */ + void adapterAdded(Adapter *adapter); + + /** + * This signal will be emitted when an adapter has been disconnected. + */ + void adapterRemoved(Adapter *adapter); + + /** + * This signal will be emitted when the current usable adapter has changed. This basically + * means two cases: + * + * - There were no usable adapters (powered off, or not present), and a new one has been + * connected and is powered on. + * - The adapter that was considered usable has been removed or powered off. + * + * If any of those cases happen, and it was possible to find a usable adapter, this signal + * will report the new adapter. If no usable adapter could be found, 0 will be placed at @p + * adapter. + * + */ + void usableAdapterChanged(Adapter *adapter); + + /** + * This signal will be emitted when all adapters have been disconnected. + */ + void allAdaptersRemoved(); + +private: + /** + * @internal + */ + Manager(QObject *parent = 0); + + ManagerPrivate *const d; +}; + +} + +#endif // BLUEDEVILMANAGER_H diff --git a/libbluedevil/bluedevil/bluedevilmanager_p.cpp b/libbluedevil/bluedevil/bluedevilmanager_p.cpp new file mode 100644 index 00000000..785b0ca5 --- /dev/null +++ b/libbluedevil/bluedevil/bluedevilmanager_p.cpp @@ -0,0 +1,198 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "bluedevilmanager.h" +#include "bluedevilmanager_p.h" +#include "bluedeviladapter.h" + +namespace BlueDevil { + +ManagerPrivate::ManagerPrivate(Manager *q) + : QObject(q) + , m_dbusObjectManager(0) + , m_bluezAgentManager(0) + , m_usableAdapter(0) + , m_q(q) +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + m_bluezServiceRunning = false; + if (QDBusConnection::systemBus().isConnected()) { + QDBusReply reply = QDBusConnection::systemBus().interface()->isServiceRegistered("org.bluez"); + + if (reply.isValid()) { + m_bluezServiceRunning = reply.value(); + } + } +} + +ManagerPrivate::~ManagerPrivate() +{ + delete m_dbusObjectManager; + delete m_bluezAgentManager; +} + +void ManagerPrivate::initialize() +{ + if (QDBusConnection::systemBus().isConnected() && m_bluezServiceRunning) { + m_dbusObjectManager = new org::freedesktop::DBus::ObjectManager("org.bluez", "/", QDBusConnection::systemBus(), m_q); + + connect(m_dbusObjectManager, SIGNAL(InterfacesAdded(QDBusObjectPath,QVariantMapMap)), + SLOT(_k_interfacesAdded(QDBusObjectPath,QVariantMapMap))); + connect(m_dbusObjectManager, SIGNAL(InterfacesRemoved(QDBusObjectPath,QStringList)), + SLOT(_k_interfacesRemoved(QDBusObjectPath,QStringList))); + + QDBusPendingReply reply = m_dbusObjectManager->GetManagedObjects(); + reply.waitForFinished(); + if (!reply.isError()) { + QHash devices; + DBusManagerStruct managedObjects = reply.value(); + DBusManagerStruct::const_iterator managedObjectIt; + for(managedObjectIt = managedObjects.constBegin(); managedObjectIt != managedObjects.constEnd(); ++managedObjectIt) { + QString path = managedObjectIt.key().path(); + QVariantMapMap interfaces = managedObjectIt.value(); + if(interfaces.contains("org.bluez.Adapter1")) { + Adapter *const adapter = new Adapter(path, m_q); + m_adapters.insert(managedObjectIt.key().path(), adapter); + } else if(interfaces.contains("org.bluez.Device1")) { + QString adapterPath = managedObjectIt.value().value("org.bluez.Device1").value("Adapter").value().path(); + devices.insert(path,adapterPath); + } else if(interfaces.contains("org.bluez.AgentManager1")) { + m_bluezAgentManager = new org::bluez::AgentManager1("org.bluez",path,QDBusConnection::systemBus(), m_q); + } + } + + QHash::const_iterator deviceIt; + for(deviceIt = devices.constBegin(); deviceIt != devices.constEnd(); ++deviceIt) { + QString devicePath = deviceIt.key(); + QString adapterPath = deviceIt.value(); + + Adapter * const adapter = m_adapters.value(adapterPath); + adapter->addDevice(devicePath); + m_devAdapter.insert(devicePath,adapter); + } + } else { + //TODO: error handling + } + m_usableAdapter = findUsableAdapter(); + emit m_q->usableAdapterChanged(m_usableAdapter); + } +} + +void ManagerPrivate::clean() +{ + qDebug() << "Private::clean"; + delete m_dbusObjectManager; + delete m_bluezAgentManager; + QMapIterator i(m_adapters); + while (i.hasNext()) { + i.next(); + Adapter *adapter = m_adapters.take(i.key()); + emit m_q->adapterRemoved(adapter); + delete adapter; + } + + m_usableAdapter = 0; + + emit m_q->usableAdapterChanged(0); +} + +Adapter *ManagerPrivate::findUsableAdapter() +{ + Q_FOREACH (Adapter *const adapter, m_q->adapters()) { + if (adapter->isPowered()) { + m_usableAdapter = adapter; + return adapter; + } + } + return 0; +} + +void ManagerPrivate::_k_interfacesAdded(const QDBusObjectPath &objectPath, const QVariantMapMap &interfaces) +{ + QVariantMapMap::const_iterator i; + for(i = interfaces.constBegin(); i != interfaces.constEnd(); ++i) { + if(i.key() == "org.bluez.Adapter1") { + Adapter * const adapter = new Adapter(objectPath.path(), m_q); + m_adapters.insert(objectPath.path(), adapter); + if (!m_usableAdapter || !m_usableAdapter->isPowered()) { + Adapter *const oldUsableAdapter = m_usableAdapter; + m_usableAdapter = findUsableAdapter(); + if (m_usableAdapter != oldUsableAdapter) { + emit m_q->usableAdapterChanged(m_usableAdapter); + } + } + emit m_q->adapterAdded(adapter); + } else if(i.key() == "org.bluez.Device1") { + QString adapterPath = i.value().value("Adapter").value().path(); + Adapter * const adapter = m_adapters.value(adapterPath); + adapter->addDevice(objectPath.path()); + m_devAdapter.insert(objectPath.path(),adapter); + } + } +} + +void ManagerPrivate::_k_interfacesRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces) +{ + QString object = objectPath.path(); + Q_FOREACH(QString interface, interfaces) { + if(interface == "org.bluez.Adapter1") { + Adapter *const adapter = m_adapters.take(object); // return and remove it from the map + if (m_adapters.isEmpty()) { + m_usableAdapter = 0; + } + if (adapter) { + emit m_q->adapterRemoved(adapter); + delete adapter; + } + if (m_adapters.isEmpty()) { + emit m_q->usableAdapterChanged(0); + emit m_q->allAdaptersRemoved(); + } else { + if (m_usableAdapter) { + Adapter *const oldUsableAdapter = m_usableAdapter; + m_usableAdapter = findUsableAdapter(); + if (m_usableAdapter != oldUsableAdapter) { + emit m_q->usableAdapterChanged(m_usableAdapter); + } + } + } + } else if(interface == "org.bluez.Device1") { + Adapter * const adapter = m_devAdapter.take(object); + adapter->removeDevice(object); + } + } +} + +void ManagerPrivate::_k_bluezServiceRegistered() +{ + m_bluezServiceRunning = true; + initialize(); +} + +void ManagerPrivate::_k_bluezServiceUnregistered() +{ + m_bluezServiceRunning = false; + clean(); +} + + +} + +#include "bluedevilmanager_p.moc" \ No newline at end of file diff --git a/libbluedevil/bluedevil/bluedevilmanager_p.h b/libbluedevil/bluedevil/bluedevilmanager_p.h new file mode 100644 index 00000000..3bbf7dca --- /dev/null +++ b/libbluedevil/bluedevil/bluedevilmanager_p.h @@ -0,0 +1,66 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Fiestas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#ifndef VISHESH_PAYS_DINNER +#define VISHESH_PAYS_DINNER + +#include "dbusobjectmanager.h" +#include "bluezagentmanager1.h" +#include "bluedevildbustypes.h" + +#include +#include + +namespace BlueDevil { +class Adapter; +class Manager; +class Device; + +class ManagerPrivate : public QObject +{ + Q_OBJECT +public: + ManagerPrivate(Manager *q); + virtual ~ManagerPrivate(); + + void initialize(); + void clean(); + Adapter *findUsableAdapter(); + Device *deviceForUBI(const QString &UBI); + + + org::freedesktop::DBus::ObjectManager *m_dbusObjectManager; + org::bluez::AgentManager1 *m_bluezAgentManager; + Adapter *m_usableAdapter; + QMap m_adapters; + QHash m_devAdapter; + bool m_bluezServiceRunning; + + Manager *const m_q; + +public Q_SLOTS: + void _k_bluezServiceRegistered(); + void _k_bluezServiceUnregistered(); + + void _k_interfacesAdded(const QDBusObjectPath &objectPath, const QVariantMapMap &interfaces); + void _k_interfacesRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces); +}; + +} + +#endif //VISHESH_PAYS_DINNER \ No newline at end of file diff --git a/libbluedevil/bluedevil/bluedevilutils.cpp b/libbluedevil/bluedevil/bluedevilutils.cpp new file mode 100644 index 00000000..bb9b88e6 --- /dev/null +++ b/libbluedevil/bluedevil/bluedevilutils.cpp @@ -0,0 +1,111 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "bluedevilutils.h" + +#include + +namespace BlueDevil { + +quint32 stringToType(const QString &stringType) +{ + if (stringType == "any") { + return BLUETOOTH_TYPE_ANY; + } else if (stringType == "mouse") { + return BLUETOOTH_TYPE_MOUSE; + } else if (stringType == "keyboard") { + return BLUETOOTH_TYPE_KEYBOARD; + } else if (stringType == "headset") { + return BLUETOOTH_TYPE_HEADSET; + } else if (stringType == "headphones") { + return BLUETOOTH_TYPE_HEADPHONES; + } else if (stringType == "audio") { + return BLUETOOTH_TYPE_OTHER_AUDIO; + } else if (stringType == "printer") { + return BLUETOOTH_TYPE_PRINTER; + } else if (stringType == "network") { + return BLUETOOTH_TYPE_NETWORK; + } + return BLUETOOTH_TYPE_ANY; +} + +quint32 classToType(quint32 classNum) +{ + switch ((classNum & 0x1f00) >> 8) { + case 0x01: + return BLUETOOTH_TYPE_COMPUTER; + case 0x02: + switch ((classNum & 0xfc) >> 2) { + case 0x01: + case 0x02: + case 0x03: + case 0x05: + return BLUETOOTH_TYPE_PHONE; + case 0x04: + return BLUETOOTH_TYPE_MODEM; + } + break; + case 0x03: + return BLUETOOTH_TYPE_NETWORK; + case 0x04: + switch ((classNum & 0xfc) >> 2) { + case 0x01: + case 0x02: + return BLUETOOTH_TYPE_HEADSET; + case 0x06: + return BLUETOOTH_TYPE_HEADPHONES; + default: + return BLUETOOTH_TYPE_OTHER_AUDIO; + } + break; + case 0x05: + switch ((classNum & 0xc0) >> 6) { + case 0x00: + switch ((classNum & 0x1e) >> 2) { + case 0x01: + case 0x02: + return BLUETOOTH_TYPE_JOYPAD; + } + break; + case 0x01: + return BLUETOOTH_TYPE_KEYBOARD; + case 0x02: + switch ((classNum & 0x1e) >> 2) { + case 0x05: + return BLUETOOTH_TYPE_TABLET; + default: + return BLUETOOTH_TYPE_MOUSE; + } + } + break; + case 0x06: + if (classNum & 0x80) + return BLUETOOTH_TYPE_PRINTER; + if (classNum & 0x20) + return BLUETOOTH_TYPE_CAMERA; + break; + } + + return 0; +} + +} diff --git a/libbluedevil/bluedevil/bluedevilutils.h b/libbluedevil/bluedevil/bluedevilutils.h new file mode 100644 index 00000000..2d224a0a --- /dev/null +++ b/libbluedevil/bluedevil/bluedevilutils.h @@ -0,0 +1,54 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef BLUEDEVILUTILS_H +#define BLUEDEVILUTILS_H + +#include + +#include + +namespace BlueDevil { + + quint32 BLUEDEVIL_EXPORT classToType(quint32 classNum); + quint32 BLUEDEVIL_EXPORT stringToType(const QString& stringType); + + enum BluetoothType { + BLUETOOTH_TYPE_ANY = 1 << 0, + BLUETOOTH_TYPE_PHONE = 1 << 1, + BLUETOOTH_TYPE_MODEM = 1 << 2, + BLUETOOTH_TYPE_COMPUTER = 1 << 3, + BLUETOOTH_TYPE_NETWORK = 1 << 4, + BLUETOOTH_TYPE_HEADSET = 1 << 5, + BLUETOOTH_TYPE_HEADPHONES = 1 << 6, + BLUETOOTH_TYPE_OTHER_AUDIO = 1 << 7, + BLUETOOTH_TYPE_KEYBOARD = 1 << 8, + BLUETOOTH_TYPE_MOUSE = 1 << 9, + BLUETOOTH_TYPE_CAMERA = 1 << 10, + BLUETOOTH_TYPE_PRINTER = 1 << 11, + BLUETOOTH_TYPE_JOYPAD = 1 << 12, + BLUETOOTH_TYPE_TABLET = 1 << 13 + }; + +} + +#endif // BLUEDEVILUTILS_H diff --git a/libbluedevil/bluedevil/bluez/org.bluez.Adapter1.xml b/libbluedevil/bluedevil/bluez/org.bluez.Adapter1.xml new file mode 100644 index 00000000..ad0cba83 --- /dev/null +++ b/libbluedevil/bluedevil/bluez/org.bluez.Adapter1.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libbluedevil/bluedevil/bluez/org.bluez.AgentManager1.xml b/libbluedevil/bluedevil/bluez/org.bluez.AgentManager1.xml new file mode 100644 index 00000000..de9fbdc6 --- /dev/null +++ b/libbluedevil/bluedevil/bluez/org.bluez.AgentManager1.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/libbluedevil/bluedevil/bluez/org.bluez.Device1.xml b/libbluedevil/bluedevil/bluez/org.bluez.Device1.xml new file mode 100644 index 00000000..bffa7a04 --- /dev/null +++ b/libbluedevil/bluedevil/bluez/org.bluez.Device1.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libbluedevil/bluedevil/bluez/org.freedesktop.DBus.ObjectManager.xml b/libbluedevil/bluedevil/bluez/org.freedesktop.DBus.ObjectManager.xml new file mode 100644 index 00000000..5a962c30 --- /dev/null +++ b/libbluedevil/bluedevil/bluez/org.freedesktop.DBus.ObjectManager.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/libbluedevil/bluedevil/bluez/org.freedesktop.DBus.Properties.xml b/libbluedevil/bluedevil/bluez/org.freedesktop.DBus.Properties.xml new file mode 100644 index 00000000..20ddc110 --- /dev/null +++ b/libbluedevil/bluedevil/bluez/org.freedesktop.DBus.Properties.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libbluedevil/bluedevil/test/CMakeLists.txt b/libbluedevil/bluedevil/test/CMakeLists.txt new file mode 100644 index 00000000..8f127854 --- /dev/null +++ b/libbluedevil/bluedevil/test/CMakeLists.txt @@ -0,0 +1,11 @@ +include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${QT_INCLUDES}) + +set (bluedeviltest_SRCS bluedeviltest.cpp) +qt4_automoc(${bluedeviltest_SRCS}) +add_executable(bluedeviltest ${bluedeviltest_SRCS}) +target_link_libraries(bluedeviltest ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} bluedevil) + +set (adaptertest_SRCS adaptertest.cpp) +qt4_automoc(${adaptertest_SRCS}) +add_executable(adaptertest ${adaptertest_SRCS}) +target_link_libraries(adaptertest ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} bluedevil) diff --git a/libbluedevil/bluedevil/test/adaptertest.cpp b/libbluedevil/bluedevil/test/adaptertest.cpp new file mode 100644 index 00000000..15ddf43d --- /dev/null +++ b/libbluedevil/bluedevil/test/adaptertest.cpp @@ -0,0 +1,104 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2011 Rafael Fernández López * + * Copyright (C) 2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "adaptertest.h" + +#include +#include + +#include +#include +#include + +using namespace BlueDevil; + +AdapterTest::AdapterTest(QObject *parent) + : QThread(parent) +{ +} + +AdapterTest::~AdapterTest() +{ +} + +void AdapterTest::adapterAdded(Adapter *adapter) +{ + qDebug() << "Adapter added: " << adapter; + Manager *const manager = Manager::self(); + qDebug() << "\tBluetooth Operational: " << manager->isBluetoothOperational(); + qDebug() << "\tUsable Adapter: " << manager->usableAdapter(); +} + +void AdapterTest::adapterRemoved(Adapter *adapter) +{ + qDebug() << "Adapter removed: " << adapter; + Manager *const manager = Manager::self(); + qDebug() << "\tBluetooth Operational: " << manager->isBluetoothOperational(); + qDebug() << "\tUsable Adapter: " << manager->usableAdapter(); +} + +void AdapterTest::usableAdapterChanged(Adapter *adapter) +{ + qDebug() << "Usable adapter changed: " << adapter; + Manager *const manager = Manager::self(); + qDebug() << "\tBluetooth Operational: " << manager->isBluetoothOperational(); + qDebug() << "\tUsable Adapter: " << manager->usableAdapter(); +} + +void AdapterTest::allAdaptersRemoved() +{ + qDebug() << "All adapters have been removed"; + Manager *const manager = Manager::self(); + qDebug() << "\tBluetooth Operational: " << manager->isBluetoothOperational(); + qDebug() << "\tUsable Adapter: " << manager->usableAdapter(); +} + +void AdapterTest::run() +{ + Manager *const manager = Manager::self(); + while (true) { + qDebug() << "Bluetooth Operational: " << manager->isBluetoothOperational(); + qDebug() << "Usable Adapter: " << manager->usableAdapter(); + sleep(5); + } +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + qDebug() << "Looping until stopped"; + + AdapterTest *const adapterTest = new AdapterTest; + + Manager *const manager = Manager::self(); + QObject::connect(manager, SIGNAL(adapterAdded(Adapter*)), adapterTest, SLOT(adapterAdded(Adapter*))); + QObject::connect(manager, SIGNAL(adapterRemoved(Adapter*)), adapterTest, SLOT(adapterRemoved(Adapter*))); + QObject::connect(manager, SIGNAL(usableAdapterChanged(Adapter*)), adapterTest, SLOT(usableAdapterChanged(Adapter*))); + QObject::connect(manager, SIGNAL(allAdaptersRemoved()), adapterTest, SLOT(allAdaptersRemoved())); + + adapterTest->start(); + + return app.exec(); +} + +#include "adaptertest.moc" diff --git a/libbluedevil/bluedevil/test/adaptertest.h b/libbluedevil/bluedevil/test/adaptertest.h new file mode 100644 index 00000000..d531d966 --- /dev/null +++ b/libbluedevil/bluedevil/test/adaptertest.h @@ -0,0 +1,55 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2011 Rafael Fernández López * + * Copyright (C) 2011 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef ADAPTERTEST_H +#define ADAPTERTEST_H + +#include +#include + +namespace BlueDevil { + class Adapter; + class Device; +} + +using namespace BlueDevil; + +class AdapterTest + : public QThread +{ + Q_OBJECT + +public: + AdapterTest(QObject *parent = 0); + virtual ~AdapterTest(); + +private Q_SLOTS: + void adapterAdded(Adapter *adapter); + void adapterRemoved(Adapter *adapter); + void usableAdapterChanged(Adapter *adapter); + void allAdaptersRemoved(); + +protected: + virtual void run(); +}; + +#endif // ADAPTERTEST_H diff --git a/libbluedevil/bluedevil/test/bluedeviltest.cpp b/libbluedevil/bluedevil/test/bluedeviltest.cpp new file mode 100644 index 00000000..cb123147 --- /dev/null +++ b/libbluedevil/bluedevil/test/bluedeviltest.cpp @@ -0,0 +1,116 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "bluedeviltest.h" + +#include +#include +#include + +#include +#include +#include + +using namespace BlueDevil; + +DeviceReceiver::DeviceReceiver(QObject *parent) + : QObject(parent) +{ +} + +DeviceReceiver::~DeviceReceiver() +{ +} + +void DeviceReceiver::scanDevices() +{ + qDebug() << "*** Will scan devices until stopped..."; + qDebug(); + + Adapter *usableAdapter = Manager::self()->usableAdapter(); + + QObject::connect(usableAdapter, SIGNAL(deviceFound(Device*)), this, SLOT(deviceFound(Device*))); + + usableAdapter->startStableDiscovery(); +} + +void DeviceReceiver::deviceFound(Device *device) +{ + qDebug() << "*** Remote device found:" << device->name() << "(" << device->address() << ")"; + qDebug(); + connect(device, SIGNAL(propertyChanged(QString,QVariant)), this, SLOT(devicePropertyChanged(QString,QVariant))); +} + +void DeviceReceiver::devicePropertyChanged(const QString &property, const QVariant &value) +{ + Device *device = static_cast(sender()); + + qDebug() << "*** Device with address" << device->address() << "changed some property (" << property << "):"; + qDebug() << "\tAddress:\t" << device->address(); + qDebug() << "\tAlias:\t\t" << device->alias(); + qDebug() << "\tClass:\t\t" << device->deviceClass(); + qDebug() << "\tIcon:\t\t" << device->icon(); + qDebug() << "\tLegacy Pairing:\t" << (device->hasLegacyPairing() ? "yes" : "no"); + qDebug() << "\tName:\t\t" << device->name(); + qDebug() << "\tPaired:\t\t" << (device->isPaired() ? "yes" : "no"); + qDebug() << "\tTrusted:\t" << (device->isTrusted() ? "yes" : "no"); + qDebug() << "\tServices:\n" << device->UUIDs(); + qDebug(); +} + +void DeviceReceiver::adapterAdded(Adapter *adapter) +{ + qDebug() << "*** An adapter has been connected."; + qDebug(); + scanDevices(); +} + +static void stopDiscovering() +{ + foreach (Adapter *const adapter, Manager::self()->adapters()) { + adapter->stopDiscovery(); + } +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + qAddPostRoutine(stopDiscovering); + + DeviceReceiver *deviceReceiver = new DeviceReceiver; + + Adapter *usableAdapter = Manager::self()->usableAdapter(); + if (usableAdapter) { + deviceReceiver->scanDevices(); + } else { + qDebug() << "!!! No bluetooth adapters were found. Waiting for bluetooth adapters. Ctrl + C to cancel..."; + qDebug(); + + QObject::connect(Manager::self(), SIGNAL(adapterAdded(Adapter*)), deviceReceiver, + SLOT(adapterAdded(Adapter*))); + } + + return app.exec(); +} + +#include "bluedeviltest.moc" diff --git a/libbluedevil/bluedevil/test/bluedeviltest.h b/libbluedevil/bluedevil/test/bluedeviltest.h new file mode 100644 index 00000000..89b21c43 --- /dev/null +++ b/libbluedevil/bluedevil/test/bluedeviltest.h @@ -0,0 +1,52 @@ +/***************************************************************************** + * This file is part of the BlueDevil project * + * * + * Copyright (C) 2010 Rafael Fernández López * + * Copyright (C) 2010 UFO Coders * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef BLUEDEVILTEST_H +#define BLUEDEVILTEST_H + +#include + +namespace BlueDevil { + class Adapter; + class Device; +} + +using namespace BlueDevil; + +class DeviceReceiver + : public QObject +{ + Q_OBJECT + +public: + DeviceReceiver(QObject *parent = 0); + virtual ~DeviceReceiver(); + + void scanDevices(); + +public Q_SLOTS: + void deviceFound(Device *device); + void devicePropertyChanged(const QString &property, const QVariant &value); + void adapterAdded(Adapter *adapter); +}; + +#endif // BLUEDEVILTEST_H diff --git a/libbluedevil/cmake/modules/FindQt4.cmake b/libbluedevil/cmake/modules/FindQt4.cmake new file mode 100644 index 00000000..081e996e --- /dev/null +++ b/libbluedevil/cmake/modules/FindQt4.cmake @@ -0,0 +1,1250 @@ +# - Find QT 4 +# This module can be used to find Qt4. +# The most important issue is that the Qt4 qmake is available via the system path. +# This qmake is then used to detect basically everything else. +# This module defines a number of key variables and macros. +# The variable QT_USE_FILE is set which is the path to a CMake file that can be included +# to compile Qt 4 applications and libraries. It sets up the compilation +# environment for include directories, preprocessor defines and populates a +# QT_LIBRARIES variable. +# +# Typical usage could be something like: +# find_package(Qt4 4.4.3 COMPONENTS QtCore QtGui QtXml REQUIRED ) +# include(${QT_USE_FILE}) +# add_executable(myexe main.cpp) +# target_link_libraries(myexe ${QT_LIBRARIES}) +# +# The minimum required version can be specified using the standard find_package()-syntax +# (see example above). +# For compatibility with older versions of FindQt4.cmake it is also possible to +# set the variable QT_MIN_VERSION to the minimum required version of Qt4 before the +# find_package(Qt4) command. +# If both are used, the version used in the find_package() command overrides the +# one from QT_MIN_VERSION. +# +# When using the components argument, QT_USE_QT* variables are automatically set +# for the QT_USE_FILE to pick up. If one wishes to manually set them, the +# available ones to set include: +# QT_DONT_USE_QTCORE +# QT_DONT_USE_QTGUI +# QT_USE_QT3SUPPORT +# QT_USE_QTASSISTANT +# QT_USE_QAXCONTAINER +# QT_USE_QAXSERVER +# QT_USE_QTDESIGNER +# QT_USE_QTMOTIF +# QT_USE_QTMAIN +# QT_USE_QTMULTIMEDIA +# QT_USE_QTNETWORK +# QT_USE_QTNSPLUGIN +# QT_USE_QTOPENGL +# QT_USE_QTSQL +# QT_USE_QTXML +# QT_USE_QTSVG +# QT_USE_QTTEST +# QT_USE_QTUITOOLS +# QT_USE_QTDBUS +# QT_USE_QTSCRIPT +# QT_USE_QTASSISTANTCLIENT +# QT_USE_QTHELP +# QT_USE_QTWEBKIT +# QT_USE_QTXMLPATTERNS +# QT_USE_PHONON +# QT_USE_QTSCRIPTTOOLS +# +# QT_USE_IMPORTED_TARGETS +# If this variable is set to TRUE, FindQt4.cmake will create imported +# library targets for the various Qt libraries and set the +# library variables like QT_QTCORE_LIBRARY to point at these imported +# targets instead of the library file on disk. This provides much better +# handling of the release and debug versions of the Qt libraries and is +# also always backwards compatible, except for the case that dependencies +# of libraries are exported, these will then also list the names of the +# imported targets as dependency and not the file location on disk. This +# is much more flexible, but requires that FindQt4.cmake is executed before +# such an exported dependency file is processed. +# +# There are also some files that need processing by some Qt tools such as moc +# and uic. Listed below are macros that may be used to process those files. +# +# macro QT4_WRAP_CPP(outfiles inputfile ... OPTIONS ...) +# create moc code from a list of files containing Qt class with +# the Q_OBJECT declaration. Per-direcotry preprocessor definitions +# are also added. Options may be given to moc, such as those found +# when executing "moc -help". +# +# macro QT4_WRAP_UI(outfiles inputfile ... OPTIONS ...) +# create code from a list of Qt designer ui files. +# Options may be given to uic, such as those found +# when executing "uic -help" +# +# macro QT4_ADD_RESOURCES(outfiles inputfile ... OPTIONS ...) +# create code from a list of Qt resource files. +# Options may be given to rcc, such as those found +# when executing "rcc -help" +# +# macro QT4_GENERATE_MOC(inputfile outputfile ) +# creates a rule to run moc on infile and create outfile. +# Use this if for some reason QT4_WRAP_CPP() isn't appropriate, e.g. +# because you need a custom filename for the moc file or something similar. +# +# macro QT4_AUTOMOC(sourcefile1 sourcefile2 ... ) +# This macro is still experimental. +# It can be used to have moc automatically handled. +# So if you have the files foo.h and foo.cpp, and in foo.h a +# a class uses the Q_OBJECT macro, moc has to run on it. If you don't +# want to use QT4_WRAP_CPP() (which is reliable and mature), you can insert +# #include "foo.moc" +# in foo.cpp and then give foo.cpp as argument to QT4_AUTOMOC(). This will the +# scan all listed files at cmake-time for such included moc files and if it finds +# them cause a rule to be generated to run moc at build time on the +# accompanying header file foo.h. +# If a source file has the SKIP_AUTOMOC property set it will be ignored by this macro. +# +# macro QT4_ADD_DBUS_INTERFACE(outfiles interface basename) +# create a the interface header and implementation files with the +# given basename from the given interface xml file and add it to +# the list of sources. +# To disable generating a namespace header, set the source file property +# NO_NAMESPACE to TRUE on the interface file. +# To include a header in the interface header, set the source file property +# INCLUDE to the name of the header. +# To specify a class name to use, set the source file property CLASSNAME +# to the name of the class. +# +# macro QT4_ADD_DBUS_INTERFACES(outfiles inputfile ... ) +# create the interface header and implementation files +# for all listed interface xml files +# the name will be automatically determined from the name of the xml file +# To disable generating namespace headers, set the source file property +# NO_NAMESPACE to TRUE for these inputfiles. +# To include a header in the interface header, set the source file property +# INCLUDE to the name of the header. +# To specify a class name to use, set the source file property CLASSNAME +# to the name of the class. +# +# macro QT4_ADD_DBUS_ADAPTOR(outfiles xmlfile parentheader parentclassname [basename] [classname]) +# create a dbus adaptor (header and implementation file) from the xml file +# describing the interface, and add it to the list of sources. The adaptor +# forwards the calls to a parent class, defined in parentheader and named +# parentclassname. The name of the generated files will be +# adaptor.{cpp,h} where basename defaults to the basename of the xml file. +# If is provided, then it will be used as the classname of the +# adaptor itself. +# +# macro QT4_GENERATE_DBUS_INTERFACE( header [interfacename] OPTIONS ...) +# generate the xml interface file from the given header. +# If the optional argument interfacename is omitted, the name of the +# interface file is constructed from the basename of the header with +# the suffix .xml appended. +# Options may be given to qdbuscpp2xml, such as those found when executing "qdbuscpp2xml --help" +# +# macro QT4_CREATE_TRANSLATION( qm_files directories ... sources ... +# ts_files ... OPTIONS ...) +# out: qm_files +# in: directories sources ts_files +# options: flags to pass to lupdate, such as -extensions to specify +# extensions for a directory scan. +# generates commands to create .ts (vie lupdate) and .qm +# (via lrelease) - files from directories and/or sources. The ts files are +# created and/or updated in the source tree (unless given with full paths). +# The qm files are generated in the build tree. +# Updating the translations can be done by adding the qm_files +# to the source list of your library/executable, so they are +# always updated, or by adding a custom target to control when +# they get updated/generated. +# +# macro QT4_ADD_TRANSLATION( qm_files ts_files ... ) +# out: qm_files +# in: ts_files +# generates commands to create .qm from .ts - files. The generated +# filenames can be found in qm_files. The ts_files +# must exists and are not updated in any way. +# +# +# Below is a detailed list of variables that FindQt4.cmake sets. +# QT_FOUND If false, don't try to use Qt. +# QT4_FOUND If false, don't try to use Qt 4. +# +# QT_VERSION_MAJOR The major version of Qt found. +# QT_VERSION_MINOR The minor version of Qt found. +# QT_VERSION_PATCH The patch version of Qt found. +# +# QT_EDITION Set to the edition of Qt (i.e. DesktopLight) +# QT_EDITION_DESKTOPLIGHT True if QT_EDITION == DesktopLight +# QT_QTCORE_FOUND True if QtCore was found. +# QT_QTGUI_FOUND True if QtGui was found. +# QT_QT3SUPPORT_FOUND True if Qt3Support was found. +# QT_QTASSISTANT_FOUND True if QtAssistant was found. +# QT_QTASSISTANTCLIENT_FOUND True if QtAssistantClient was found. +# QT_QAXCONTAINER_FOUND True if QAxContainer was found (Windows only). +# QT_QAXSERVER_FOUND True if QAxServer was found (Windows only). +# QT_QTDBUS_FOUND True if QtDBus was found. +# QT_QTDESIGNER_FOUND True if QtDesigner was found. +# QT_QTDESIGNERCOMPONENTS True if QtDesignerComponents was found. +# QT_QTHELP_FOUND True if QtHelp was found. +# QT_QTMOTIF_FOUND True if QtMotif was found. +# QT_QTMULTIMEDIA_FOUND True if QtMultimedia was found (since Qt 4.6.0). +# QT_QTNETWORK_FOUND True if QtNetwork was found. +# QT_QTNSPLUGIN_FOUND True if QtNsPlugin was found. +# QT_QTOPENGL_FOUND True if QtOpenGL was found. +# QT_QTSQL_FOUND True if QtSql was found. +# QT_QTSVG_FOUND True if QtSvg was found. +# QT_QTSCRIPT_FOUND True if QtScript was found. +# QT_QTSCRIPTTOOLS_FOUND True if QtScriptTools was found. +# QT_QTTEST_FOUND True if QtTest was found. +# QT_QTUITOOLS_FOUND True if QtUiTools was found. +# QT_QTWEBKIT_FOUND True if QtWebKit was found. +# QT_QTXML_FOUND True if QtXml was found. +# QT_QTXMLPATTERNS_FOUND True if QtXmlPatterns was found. +# QT_PHONON_FOUND True if phonon was found. +# +# QT_MAC_USE_COCOA For Mac OS X, its whether Cocoa or Carbon is used. +# In general, this should not be used, but its useful +# when having platform specific code. +# +# QT_DEFINITIONS Definitions to use when compiling code that uses Qt. +# You do not need to use this if you include QT_USE_FILE. +# The QT_USE_FILE will also define QT_DEBUG and QT_NO_DEBUG +# to fit your current build type. Those are not contained +# in QT_DEFINITIONS. +# +# QT_INCLUDES List of paths to all include directories of +# Qt4 QT_INCLUDE_DIR and QT_QTCORE_INCLUDE_DIR are +# always in this variable even if NOTFOUND, +# all other INCLUDE_DIRS are +# only added if they are found. +# You do not need to use this if you include QT_USE_FILE. +# +# +# Include directories for the Qt modules are listed here. +# You do not need to use these variables if you include QT_USE_FILE. +# +# QT_INCLUDE_DIR Path to "include" of Qt4 +# QT_QT_INCLUDE_DIR Path to "include/Qt" +# QT_QT3SUPPORT_INCLUDE_DIR Path to "include/Qt3Support" +# QT_QTASSISTANT_INCLUDE_DIR Path to "include/QtAssistant" +# QT_QTASSISTANTCLIENT_INCLUDE_DIR Path to "include/QtAssistant" +# QT_QAXCONTAINER_INCLUDE_DIR Path to "include/ActiveQt" (Windows only) +# QT_QAXSERVER_INCLUDE_DIR Path to "include/ActiveQt" (Windows only) +# QT_QTCORE_INCLUDE_DIR Path to "include/QtCore" +# QT_QTDBUS_INCLUDE_DIR Path to "include/QtDBus" +# QT_QTDESIGNER_INCLUDE_DIR Path to "include/QtDesigner" +# QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR Path to "include/QtDesigner" +# QT_QTGUI_INCLUDE_DIR Path to "include/QtGui" +# QT_QTHELP_INCLUDE_DIR Path to "include/QtHelp" +# QT_QTMOTIF_INCLUDE_DIR Path to "include/QtMotif" +# QT_QTMULTIMEDIA_INCLUDE_DIR Path to "include/QtMultimedia" +# QT_QTNETWORK_INCLUDE_DIR Path to "include/QtNetwork" +# QT_QTNSPLUGIN_INCLUDE_DIR Path to "include/QtNsPlugin" +# QT_QTOPENGL_INCLUDE_DIR Path to "include/QtOpenGL" +# QT_QTSCRIPT_INCLUDE_DIR Path to "include/QtScript" +# QT_QTSQL_INCLUDE_DIR Path to "include/QtSql" +# QT_QTSVG_INCLUDE_DIR Path to "include/QtSvg" +# QT_QTTEST_INCLUDE_DIR Path to "include/QtTest" +# QT_QTWEBKIT_INCLUDE_DIR Path to "include/QtWebKit" +# QT_QTXML_INCLUDE_DIR Path to "include/QtXml" +# QT_QTXMLPATTERNS_INCLUDE_DIR Path to "include/QtXmlPatterns" +# QT_PHONON_INCLUDE_DIR Path to "include/phonon" +# QT_QTSCRIPTTOOLS_INCLUDE_DIR Path to "include/QtScriptTools" +# +# QT_BINARY_DIR Path to "bin" of Qt4 +# QT_LIBRARY_DIR Path to "lib" of Qt4 +# QT_PLUGINS_DIR Path to "plugins" for Qt4 +# QT_TRANSLATIONS_DIR Path to "translations" of Qt4 +# QT_DOC_DIR Path to "doc" of Qt4 +# QT_MKSPECS_DIR Path to "mkspecs" of Qt4 +# +# +# For every library of Qt, a QT_QTFOO_LIBRARY variable is defined, with the full path to the library. +# +# So there are the following variables: +# The Qt3Support library: QT_QT3SUPPORT_LIBRARY +# +# The QtAssistant library: QT_QTASSISTANT_LIBRARY +# +# The QtAssistantClient library: QT_QTASSISTANTCLIENT_LIBRARY +# +# The QAxServer library: QT_QAXSERVER_LIBRARY +# +# The QAxContainer library: QT_QAXCONTAINER_LIBRARY +# +# The QtCore library: QT_QTCORE_LIBRARY +# +# The QtDBus library: QT_QTDBUS_LIBRARY +# +# The QtDesigner library: QT_QTDESIGNER_LIBRARY +# +# The QtDesignerComponents library: QT_QTDESIGNERCOMPONENTS_LIBRARY +# +# The QtGui library: QT_QTGUI_LIBRARY +# +# The QtHelp library: QT_QTHELP_LIBRARY +# +# The QtMotif library: QT_QTMOTIF_LIBRARY +# +# The QtMultimedia library: QT_QTMULTIMEDIA_LIBRARY +# +# The QtNetwork library: QT_QTNETWORK_LIBRARY +# +# The QtNsPLugin library: QT_QTNSPLUGIN_LIBRARY +# +# The QtOpenGL library: QT_QTOPENGL_LIBRARY +# +# The QtScript library: QT_QTSCRIPT_LIBRARY +# +# The QtScriptTools library: QT_QTSCRIPTTOOLS_LIBRARY +# +# The QtSql library: QT_QTSQL_LIBRARY +# +# The QtSvg library: QT_QTSVG_LIBRARY +# +# The QtTest library: QT_QTTEST_LIBRARY +# +# The QtUiTools library: QT_QTUITOOLS_LIBRARY +# +# The QtWebKit library: QT_QTWEBKIT_LIBRARY +# +# The QtXml library: QT_QTXML_LIBRARY +# +# The QtXmlPatterns library: QT_QTXMLPATTERNS_LIBRARY +# +# The qtmain library for Windows QT_QTMAIN_LIBRARY +# +# The Phonon library: QT_PHONON_LIBRARY +# +# also defined, but NOT for general use are +# QT_MOC_EXECUTABLE Where to find the moc tool. +# QT_UIC_EXECUTABLE Where to find the uic tool. +# QT_UIC3_EXECUTABLE Where to find the uic3 tool. +# QT_RCC_EXECUTABLE Where to find the rcc tool +# QT_DBUSCPP2XML_EXECUTABLE Where to find the qdbuscpp2xml tool. +# QT_DBUSXML2CPP_EXECUTABLE Where to find the qdbusxml2cpp tool. +# QT_LUPDATE_EXECUTABLE Where to find the lupdate tool. +# QT_LRELEASE_EXECUTABLE Where to find the lrelease tool. +# QT_QCOLLECTIONGENERATOR_EXECUTABLE Where to find the qcollectiongenerator tool. +# QT_DESIGNER_EXECUTABLE Where to find the Qt designer tool. +# QT_LINGUIST_EXECUTABLE Where to find the Qt linguist tool. +# +# +# These are around for backwards compatibility +# they will be set +# QT_WRAP_CPP Set true if QT_MOC_EXECUTABLE is found +# QT_WRAP_UI Set true if QT_UIC_EXECUTABLE is found +# +# These variables do _NOT_ have any effect anymore (compared to FindQt.cmake) +# QT_MT_REQUIRED Qt4 is now always multithreaded +# +# These variables are set to "" Because Qt structure changed +# (They make no sense in Qt4) +# QT_QT_LIBRARY Qt-Library is now split + +# Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. +# See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + +# Use FIND_PACKAGE( Qt4 COMPONENTS ... ) to enable modules +IF( Qt4_FIND_COMPONENTS ) + FOREACH( component ${Qt4_FIND_COMPONENTS} ) + STRING( TOUPPER ${component} _COMPONENT ) + SET( QT_USE_${_COMPONENT} 1 ) + ENDFOREACH( component ) + + # To make sure we don't use QtCore or QtGui when not in COMPONENTS + IF(NOT QT_USE_QTCORE) + SET( QT_DONT_USE_QTCORE 1 ) + ENDIF(NOT QT_USE_QTCORE) + + IF(NOT QT_USE_QTGUI) + SET( QT_DONT_USE_QTGUI 1 ) + ENDIF(NOT QT_USE_QTGUI) + +ENDIF( Qt4_FIND_COMPONENTS ) + +# If Qt3 has already been found, fail. +IF(QT_QT_LIBRARY) + IF(Qt4_FIND_REQUIRED) + MESSAGE( FATAL_ERROR "Qt3 and Qt4 cannot be used together in one project. If switching to Qt4, the CMakeCache.txt needs to be cleaned.") + ELSE(Qt4_FIND_REQUIRED) + IF(NOT Qt4_FIND_QUIETLY) + MESSAGE( STATUS "Qt3 and Qt4 cannot be used together in one project. If switching to Qt4, the CMakeCache.txt needs to be cleaned.") + ENDIF(NOT Qt4_FIND_QUIETLY) + RETURN() + ENDIF(Qt4_FIND_REQUIRED) +ENDIF(QT_QT_LIBRARY) + + +IF (QT4_QMAKE_FOUND AND Qt4::QtCore) + # Check already done in this cmake run, nothing more to do + RETURN() +ENDIF (QT4_QMAKE_FOUND AND Qt4::QtCore) + +# check that QT_NO_DEBUG is defined for release configurations +MACRO(QT_CHECK_FLAG_EXISTS FLAG VAR DOC) + IF(NOT ${VAR} MATCHES "${FLAG}") + SET(${VAR} "${${VAR}} ${FLAG}" + CACHE STRING "Flags used by the compiler during ${DOC} builds." FORCE) + ENDIF(NOT ${VAR} MATCHES "${FLAG}") +ENDMACRO(QT_CHECK_FLAG_EXISTS FLAG VAR) + +QT_CHECK_FLAG_EXISTS(-DQT_NO_DEBUG CMAKE_CXX_FLAGS_RELWITHDEBINFO "Release with Debug Info") +QT_CHECK_FLAG_EXISTS(-DQT_NO_DEBUG CMAKE_CXX_FLAGS_RELEASE "release") +QT_CHECK_FLAG_EXISTS(-DQT_NO_DEBUG CMAKE_CXX_FLAGS_MINSIZEREL "release minsize") + +INCLUDE(MacroPushRequiredVars) +INCLUDE(CheckSymbolExists) +INCLUDE(MacroAddFileDependencies) + +SET(QT_USE_FILE ${CMAKE_ROOT}/Modules/UseQt4.cmake) + +SET( QT_DEFINITIONS "") + +SET(QT4_INSTALLED_VERSION_TOO_OLD FALSE) + +# macro for asking qmake to process pro files +MACRO(QT_QUERY_QMAKE outvar invar) + IF(QT_QMAKE_EXECUTABLE) + FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmpQmake/tmp.pro + "message(CMAKE_MESSAGE<$$${invar}>)") + + # Invoke qmake with the tmp.pro program to get the desired + # information. Use the same variable for both stdout and stderr + # to make sure we get the output on all platforms. + EXECUTE_PROCESS(COMMAND ${QT_QMAKE_EXECUTABLE} + WORKING_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmpQmake + OUTPUT_VARIABLE _qmake_query_output + RESULT_VARIABLE _qmake_result + ERROR_VARIABLE _qmake_query_output ) + + FILE(REMOVE_RECURSE + "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmpQmake") + + IF(_qmake_result) + MESSAGE(WARNING " querying qmake for ${invar}. qmake reported:\n${_qmake_query_output}") + ELSE(_qmake_result) + STRING(REGEX REPLACE ".*CMAKE_MESSAGE<([^>]*).*" "\\1" ${outvar} "${_qmake_query_output}") + ENDIF(_qmake_result) + + ENDIF(QT_QMAKE_EXECUTABLE) +ENDMACRO(QT_QUERY_QMAKE) + +GET_FILENAME_COMPONENT(qt_install_version "[HKEY_CURRENT_USER\\Software\\trolltech\\Versions;DefaultQtVersion]" NAME) +# check for qmake +# Debian uses qmake-qt4 +# macports' Qt uses qmake-mac +FIND_PROGRAM(QT_QMAKE_EXECUTABLE NAMES qmake qmake4 qmake-qt4 qmake-mac PATHS + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Qt3Versions\\4.0.0;InstallDir]/bin" + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Versions\\4.0.0;InstallDir]/bin" + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Versions\\${qt_install_version};InstallDir]/bin" + $ENV{QTDIR}/bin +) + +IF (QT_QMAKE_EXECUTABLE) + + IF(QT_QMAKE_EXECUTABLE_LAST) + STRING(COMPARE NOTEQUAL "${QT_QMAKE_EXECUTABLE_LAST}" "${QT_QMAKE_EXECUTABLE}" QT_QMAKE_CHANGED) + ENDIF(QT_QMAKE_EXECUTABLE_LAST) + + SET(QT_QMAKE_EXECUTABLE_LAST "${QT_QMAKE_EXECUTABLE}" CACHE INTERNAL "" FORCE) + + SET(QT4_QMAKE_FOUND FALSE) + + EXEC_PROGRAM(${QT_QMAKE_EXECUTABLE} ARGS "-query QT_VERSION" OUTPUT_VARIABLE QTVERSION) + + # check for qt3 qmake and then try and find qmake4 or qmake-qt4 in the path + IF("${QTVERSION}" MATCHES "Unknown") + SET(QT_QMAKE_EXECUTABLE NOTFOUND CACHE FILEPATH "" FORCE) + FIND_PROGRAM(QT_QMAKE_EXECUTABLE NAMES qmake4 qmake-qt4 PATHS + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Qt3Versions\\4.0.0;InstallDir]/bin" + "[HKEY_CURRENT_USER\\Software\\Trolltech\\Versions\\4.0.0;InstallDir]/bin" + $ENV{QTDIR}/bin + ) + IF(QT_QMAKE_EXECUTABLE) + EXEC_PROGRAM(${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_VERSION" OUTPUT_VARIABLE QTVERSION) + ENDIF(QT_QMAKE_EXECUTABLE) + ENDIF("${QTVERSION}" MATCHES "Unknown") + + # check that we found the Qt4 qmake, Qt3 qmake output won't match here + STRING(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" qt_version_tmp "${QTVERSION}") + IF (qt_version_tmp) + + # we need at least version 4.0.0 + IF (NOT QT_MIN_VERSION) + SET(QT_MIN_VERSION "4.0.0") + ENDIF (NOT QT_MIN_VERSION) + + #now parse the parts of the user given version string into variables + STRING(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" req_qt_major_vers "${QT_MIN_VERSION}") + IF (NOT req_qt_major_vers) + MESSAGE( FATAL_ERROR "Invalid Qt version string given: \"${QT_MIN_VERSION}\", expected e.g. \"4.0.1\"") + ENDIF (NOT req_qt_major_vers) + + # now parse the parts of the user given version string into variables + STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" req_qt_major_vers "${QT_MIN_VERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.([0-9])+\\.[0-9]+" "\\1" req_qt_minor_vers "${QT_MIN_VERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" req_qt_patch_vers "${QT_MIN_VERSION}") + + # Suppport finding at least a particular version, for instance FIND_PACKAGE( Qt4 4.4.3 ) + # This implementation is a hack to avoid duplicating code and make sure we stay + # source-compatible with CMake 2.6.x + IF( Qt4_FIND_VERSION ) + SET( QT_MIN_VERSION ${Qt4_FIND_VERSION} ) + SET( req_qt_major_vers ${Qt4_FIND_VERSION_MAJOR} ) + SET( req_qt_minor_vers ${Qt4_FIND_VERSION_MINOR} ) + SET( req_qt_patch_vers ${Qt4_FIND_VERSION_PATCH} ) + ENDIF( Qt4_FIND_VERSION ) + + IF (NOT req_qt_major_vers EQUAL 4) + MESSAGE( FATAL_ERROR "Invalid Qt version string given: \"${QT_MIN_VERSION}\", major version 4 is required, e.g. \"4.0.1\"") + ENDIF (NOT req_qt_major_vers EQUAL 4) + + # and now the version string given by qmake + STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" QT_VERSION_MAJOR "${QTVERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.([0-9])+\\.[0-9]+.*" "\\1" QT_VERSION_MINOR "${QTVERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" QT_VERSION_PATCH "${QTVERSION}") + + # compute an overall version number which can be compared at once + MATH(EXPR req_vers "${req_qt_major_vers}*10000 + ${req_qt_minor_vers}*100 + ${req_qt_patch_vers}") + MATH(EXPR found_vers "${QT_VERSION_MAJOR}*10000 + ${QT_VERSION_MINOR}*100 + ${QT_VERSION_PATCH}") + + # Support finding *exactly* a particular version, for instance FIND_PACKAGE( Qt4 4.4.3 EXACT ) + IF( Qt4_FIND_VERSION_EXACT ) + IF(found_vers EQUAL req_vers) + SET( QT4_QMAKE_FOUND TRUE ) + ELSE(found_vers EQUAL req_vers) + SET( QT4_QMAKE_FOUND FALSE ) + IF (found_vers LESS req_vers) + SET(QT4_INSTALLED_VERSION_TOO_OLD TRUE) + ELSE (found_vers LESS req_vers) + SET(QT4_INSTALLED_VERSION_TOO_NEW TRUE) + ENDIF (found_vers LESS req_vers) + ENDIF(found_vers EQUAL req_vers) + ELSE( Qt4_FIND_VERSION_EXACT ) + IF (found_vers LESS req_vers) + SET(QT4_QMAKE_FOUND FALSE) + SET(QT4_INSTALLED_VERSION_TOO_OLD TRUE) + ELSE (found_vers LESS req_vers) + SET(QT4_QMAKE_FOUND TRUE) + ENDIF (found_vers LESS req_vers) + ENDIF( Qt4_FIND_VERSION_EXACT ) + ENDIF (qt_version_tmp) + +ENDIF (QT_QMAKE_EXECUTABLE) + +IF (QT4_QMAKE_FOUND) + + if (WIN32) + # get qt install dir + get_filename_component(_DIR ${QT_QMAKE_EXECUTABLE} PATH ) + get_filename_component(QT_INSTALL_DIR ${_DIR} PATH ) + endif (WIN32) + + # ask qmake for the library dir + # Set QT_LIBRARY_DIR + IF (NOT QT_LIBRARY_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_LIBS" + OUTPUT_VARIABLE QT_LIBRARY_DIR_TMP ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${QT_LIBRARY_DIR_TMP}" QT_LIBRARY_DIR_TMP) + IF(EXISTS "${QT_LIBRARY_DIR_TMP}") + SET(QT_LIBRARY_DIR ${QT_LIBRARY_DIR_TMP} CACHE PATH "Qt library dir" FORCE) + ELSE(EXISTS "${QT_LIBRARY_DIR_TMP}") + MESSAGE("Warning: QT_QMAKE_EXECUTABLE reported QT_INSTALL_LIBS as ${QT_LIBRARY_DIR_TMP}") + MESSAGE("Warning: ${QT_LIBRARY_DIR_TMP} does NOT exist, Qt must NOT be installed correctly.") + ENDIF(EXISTS "${QT_LIBRARY_DIR_TMP}") + ENDIF(NOT QT_LIBRARY_DIR OR QT_QMAKE_CHANGED) + + IF (APPLE) + IF (EXISTS ${QT_LIBRARY_DIR}/QtCore.framework) + SET(QT_USE_FRAMEWORKS ON + CACHE BOOL "Set to ON if Qt build uses frameworks." FORCE) + ELSE (EXISTS ${QT_LIBRARY_DIR}/QtCore.framework) + SET(QT_USE_FRAMEWORKS OFF + CACHE BOOL "Set to ON if Qt build uses frameworks." FORCE) + ENDIF (EXISTS ${QT_LIBRARY_DIR}/QtCore.framework) + + MARK_AS_ADVANCED(QT_USE_FRAMEWORKS) + ENDIF (APPLE) + + # ask qmake for the binary dir + IF (QT_LIBRARY_DIR AND NOT QT_BINARY_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM(${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_BINS" + OUTPUT_VARIABLE qt_bins ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_bins}" qt_bins) + SET(QT_BINARY_DIR ${qt_bins} CACHE INTERNAL "" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_BINARY_DIR OR QT_QMAKE_CHANGED) + + # ask qmake for the include dir + IF (QT_LIBRARY_DIR AND NOT QT_HEADERS_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_HEADERS" + OUTPUT_VARIABLE qt_headers ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_headers}" qt_headers) + SET(QT_HEADERS_DIR ${qt_headers} CACHE INTERNAL "" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_HEADERS_DIR OR QT_QMAKE_CHANGED) + + + # ask qmake for the documentation directory + IF (QT_LIBRARY_DIR AND NOT QT_DOC_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_DOCS" + OUTPUT_VARIABLE qt_doc_dir ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_doc_dir}" qt_doc_dir) + SET(QT_DOC_DIR ${qt_doc_dir} CACHE PATH "The location of the Qt docs" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_DOC_DIR OR QT_QMAKE_CHANGED) + + # ask qmake for the mkspecs directory + IF (QT_LIBRARY_DIR AND NOT QT_MKSPECS_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QMAKE_MKSPECS" + OUTPUT_VARIABLE qt_mkspecs_dirs ) + # do not replace : on windows as it might be a drive letter + # and windows should already use ; as a separator + IF(UNIX) + STRING(REPLACE ":" ";" qt_mkspecs_dirs "${qt_mkspecs_dirs}") + ENDIF(UNIX) + SET(QT_MKSPECS_DIR NOTFOUND) + FIND_PATH(QT_MKSPECS_DIR qconfig.pri PATHS ${qt_mkspecs_dirs} + DOC "The location of the Qt mkspecs containing qconfig.pri" + NO_DEFAULT_PATH ) + ENDIF (QT_LIBRARY_DIR AND NOT QT_MKSPECS_DIR OR QT_QMAKE_CHANGED) + + # ask qmake for the plugins directory + IF (QT_LIBRARY_DIR AND NOT QT_PLUGINS_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_PLUGINS" + OUTPUT_VARIABLE qt_plugins_dir ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_plugins_dir}" qt_plugins_dir) + SET(QT_PLUGINS_DIR ${qt_plugins_dir} CACHE PATH "The location of the Qt plugins" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_PLUGINS_DIR OR QT_QMAKE_CHANGED) + + # ask qmake for the translations directory + IF (QT_LIBRARY_DIR AND NOT QT_TRANSLATIONS_DIR OR QT_QMAKE_CHANGED) + EXEC_PROGRAM( ${QT_QMAKE_EXECUTABLE} + ARGS "-query QT_INSTALL_TRANSLATIONS" + OUTPUT_VARIABLE qt_translations_dir ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_translations_dir}" qt_translations_dir) + SET(QT_TRANSLATIONS_DIR ${qt_translations_dir} CACHE PATH "The location of the Qt translations" FORCE) + ENDIF (QT_LIBRARY_DIR AND NOT QT_TRANSLATIONS_DIR OR QT_QMAKE_CHANGED) + + # Make variables changeble to the advanced user + MARK_AS_ADVANCED( QT_LIBRARY_DIR QT_DOC_DIR QT_MKSPECS_DIR + QT_PLUGINS_DIR QT_TRANSLATIONS_DIR) + + + ############################################# + # + # Find out what window system we're using + # + ############################################# + # Save required includes and required_flags variables + MACRO_PUSH_REQUIRED_VARS() + # Add QT_INCLUDE_DIR to CMAKE_REQUIRED_INCLUDES + SET(CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES};${QT_HEADERS_DIR}") + # On Mac OS X when Qt has framework support, also add the framework path + IF( QT_USE_FRAMEWORKS ) + SET(CMAKE_REQUIRED_FLAGS "-F${QT_LIBRARY_DIR} ") + ENDIF( QT_USE_FRAMEWORKS ) + # Check for Window system symbols (note: only one should end up being set) + CHECK_SYMBOL_EXISTS(Q_WS_X11 "QtCore/qglobal.h" Q_WS_X11) + CHECK_SYMBOL_EXISTS(Q_WS_WIN "QtCore/qglobal.h" Q_WS_WIN) + CHECK_SYMBOL_EXISTS(Q_WS_QWS "QtCore/qglobal.h" Q_WS_QWS) + CHECK_SYMBOL_EXISTS(Q_WS_MAC "QtCore/qglobal.h" Q_WS_MAC) + IF(Q_WS_MAC) + IF(QT_QMAKE_CHANGED) + SET(QT_MAC_USE_COCOA "" CACHE BOOL "Use Cocoa on Mac" FORCE) + ENDIF(QT_QMAKE_CHANGED) + CHECK_SYMBOL_EXISTS(QT_MAC_USE_COCOA "QtCore/qconfig.h" QT_MAC_USE_COCOA) + ENDIF(Q_WS_MAC) + + IF (QT_QTCOPY_REQUIRED) + CHECK_SYMBOL_EXISTS(QT_IS_QTCOPY "QtCore/qglobal.h" QT_KDE_QT_COPY) + IF (NOT QT_IS_QTCOPY) + MESSAGE(FATAL_ERROR "qt-copy is required, but hasn't been found") + ENDIF (NOT QT_IS_QTCOPY) + ENDIF (QT_QTCOPY_REQUIRED) + + # Restore CMAKE_REQUIRED_INCLUDES+CMAKE_REQUIRED_FLAGS variables + MACRO_POP_REQUIRED_VARS() + # + ############################################# + + + + ######################################## + # + # Setting the INCLUDE-Variables + # + ######################################## + + SET(QT_MODULES QtCore QtGui Qt3Support QtSvg QtScript QtTest QtUiTools + QtHelp QtWebKit QtXmlPatterns QtNetwork QtMultimedia + QtNsPlugin QtOpenGL QtSql QtXml QtDesigner QtDBus QtScriptTools) + + IF(Q_WS_X11) + SET(QT_MODULES ${QT_MODULES} QtMotif) + ENDIF(Q_WS_X11) + + IF(QT_QMAKE_CHANGED) + FOREACH(QT_MODULE ${QT_MODULES}) + STRING(TOUPPER ${QT_MODULE} _upper_qt_module) + SET(QT_${_upper_qt_module}_INCLUDE_DIR NOTFOUND) + SET(QT_${_upper_qt_module}_LIBRARY_RELEASE NOTFOUND) + SET(QT_${_upper_qt_module}_LIBRARY_DEBUG NOTFOUND) + ENDFOREACH(QT_MODULE) + SET(QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR NOTFOUND) + SET(QT_QTDESIGNERCOMPONENTS_LIBRARY_RELEASE NOTFOUND) + SET(QT_QTDESIGNERCOMPONENTS_LIBRARY_DEBUG NOTFOUND) + SET(QT_QTASSISTANTCLIENT_INCLUDE_DIR NOTFOUND) + SET(QT_QTASSISTANTCLIENT_LIBRARY_RELEASE NOTFOUND) + SET(QT_QTASSISTANTCLIENT_LIBRARY_DEBUG NOTFOUND) + SET(QT_QTASSISTANT_INCLUDE_DIR NOTFOUND) + SET(QT_QTASSISTANT_LIBRARY_RELEASE NOTFOUND) + SET(QT_QTASSISTANT_LIBRARY_DEBUG NOTFOUND) + SET(QT_QTCLUCENE_LIBRARY_RELEASE NOTFOUND) + SET(QT_QTCLUCENE_LIBRARY_DEBUG NOTFOUND) + SET(QT_QAXCONTAINER_INCLUDE_DIR NOTFOUND) + SET(QT_QAXCONTAINER_LIBRARY_RELEASE NOTFOUND) + SET(QT_QAXCONTAINER_LIBRARY_DEBUG NOTFOUND) + SET(QT_QAXSERVER_INCLUDE_DIR NOTFOUND) + SET(QT_QAXSERVER_LIBRARY_RELEASE NOTFOUND) + SET(QT_QAXSERVER_LIBRARY_DEBUG NOTFOUND) + IF(WIN32) + SET(QT_QTMAIN_LIBRARY_DEBUG NOTFOUND) + SET(QT_QTMAIN_LIBRARY_RELEASE NOTFOUND) + ENDIF(WIN32) + SET(QT_PHONON_INCLUDE_DIR NOTFOUND) + ENDIF(QT_QMAKE_CHANGED) + + FOREACH(QT_MODULE ${QT_MODULES}) + STRING(TOUPPER ${QT_MODULE} _upper_qt_module) + FIND_PATH(QT_${_upper_qt_module}_INCLUDE_DIR ${QT_MODULE} + PATHS + ${QT_HEADERS_DIR}/${QT_MODULE} + ${QT_LIBRARY_DIR}/${QT_MODULE}.framework/Headers + NO_DEFAULT_PATH + ) + ENDFOREACH(QT_MODULE) + + IF(WIN32) + SET(QT_MODULES ${QT_MODULES} QAxContainer QAxServer) + # Set QT_AXCONTAINER_INCLUDE_DIR and QT_AXSERVER_INCLUDE_DIR + FIND_PATH(QT_QAXCONTAINER_INCLUDE_DIR ActiveQt + PATHS + ${QT_HEADERS_DIR}/ActiveQt + NO_DEFAULT_PATH + ) + FIND_PATH(QT_QAXSERVER_INCLUDE_DIR ActiveQt + PATHS + ${QT_HEADERS_DIR}/ActiveQt + NO_DEFAULT_PATH + ) + ENDIF(WIN32) + + # Set QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR + FIND_PATH(QT_QTDESIGNERCOMPONENTS_INCLUDE_DIR QDesignerComponents + PATHS + ${QT_HEADERS_DIR}/QtDesigner + ${QT_LIBRARY_DIR}/QtDesigner.framework/Headers + NO_DEFAULT_PATH + ) + + # Set QT_QTASSISTANT_INCLUDE_DIR + FIND_PATH(QT_QTASSISTANT_INCLUDE_DIR QtAssistant + PATHS + ${QT_HEADERS_DIR}/QtAssistant + ${QT_LIBRARY_DIR}/QtAssistant.framework/Headers + NO_DEFAULT_PATH + ) + + # Set QT_QTASSISTANTCLIENT_INCLUDE_DIR + FIND_PATH(QT_QTASSISTANTCLIENT_INCLUDE_DIR QAssistantClient + PATHS + ${QT_HEADERS_DIR}/QtAssistant + ${QT_LIBRARY_DIR}/QtAssistant.framework/Headers + NO_DEFAULT_PATH + ) + + # Set QT_QT_INCLUDE_DIR + FIND_PATH(QT_QT_INCLUDE_DIR qglobal.h + PATHS + ${QT_HEADERS_DIR}/Qt + ${QT_LIBRARY_DIR}/QtCore.framework/Headers + NO_DEFAULT_PATH + ) + + # Set QT_PHONON_INCLUDE_DIR + # Qt >= 4.5.3 (or kde-qt-4.5.2 which has the fix too) : Phonon/ClassName is inside include/phonon + # With previous versions of Qt, this could not work; upgrade Qt or use a standalone phonon + FIND_PATH(QT_PHONON_INCLUDE_DIR Phonon + PATHS + ${QT_HEADERS_DIR}/phonon + NO_DEFAULT_PATH + ) + SET(QT_MODULES ${QT_MODULES} phonon) + + # Set QT_INCLUDE_DIR by removine "/QtCore" in the string ${QT_QTCORE_INCLUDE_DIR} + IF( QT_QTCORE_INCLUDE_DIR AND NOT QT_INCLUDE_DIR) + IF (QT_USE_FRAMEWORKS) + SET(QT_INCLUDE_DIR ${QT_HEADERS_DIR}) + ELSE (QT_USE_FRAMEWORKS) + STRING( REGEX REPLACE "/QtCore$" "" qt4_include_dir ${QT_QTCORE_INCLUDE_DIR}) + SET( QT_INCLUDE_DIR ${qt4_include_dir} CACHE PATH "") + ENDIF (QT_USE_FRAMEWORKS) + ENDIF( QT_QTCORE_INCLUDE_DIR AND NOT QT_INCLUDE_DIR) + + IF( NOT QT_INCLUDE_DIR) + IF(Qt4_FIND_REQUIRED) + MESSAGE( FATAL_ERROR "Could NOT find QtCore header") + ENDIF(Qt4_FIND_REQUIRED) + ENDIF( NOT QT_INCLUDE_DIR) + + # Make variables changeble to the advanced user + MARK_AS_ADVANCED( QT_INCLUDE_DIR QT_QT_INCLUDE_DIR) + + # Set QT_INCLUDES + SET( QT_INCLUDES ${QT_QT_INCLUDE_DIR} ${QT_MKSPECS_DIR}/default ${QT_INCLUDE_DIR} ) + + + ####################################### + # + # Qt configuration + # + ####################################### + IF(EXISTS "${QT_MKSPECS_DIR}/qconfig.pri") + FILE(READ ${QT_MKSPECS_DIR}/qconfig.pri _qconfig_FILE_contents) + STRING(REGEX MATCH "QT_CONFIG[^\n]+" QT_QCONFIG "${_qconfig_FILE_contents}") + STRING(REGEX MATCH "CONFIG[^\n]+" QT_CONFIG "${_qconfig_FILE_contents}") + STRING(REGEX MATCH "EDITION[^\n]+" QT_EDITION "${_qconfig_FILE_contents}") + STRING(REGEX MATCH "QT_LIBINFIX[^\n]+" _qconfig_qt_libinfix "${_qconfig_FILE_contents}") + STRING(REGEX REPLACE "QT_LIBINFIX *= *([^\n]*)" "\\1" QT_LIBINFIX "${_qconfig_qt_libinfix}") + ENDIF(EXISTS "${QT_MKSPECS_DIR}/qconfig.pri") + IF("${QT_EDITION}" MATCHES "DesktopLight") + SET(QT_EDITION_DESKTOPLIGHT 1) + ENDIF("${QT_EDITION}" MATCHES "DesktopLight") + + ######################################## + # + # Setting the LIBRARY-Variables + # + ######################################## + + # find the libraries + FOREACH(QT_MODULE ${QT_MODULES}) + STRING(TOUPPER ${QT_MODULE} _upper_qt_module) + FIND_LIBRARY(QT_${_upper_qt_module}_LIBRARY_RELEASE + NAMES ${QT_MODULE}${QT_LIBINFIX} ${QT_MODULE}${QT_LIBINFIX}4 + PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH + ) + FIND_LIBRARY(QT_${_upper_qt_module}_LIBRARY_DEBUG + NAMES ${QT_MODULE}${QT_LIBINFIX}_debug ${QT_MODULE}${QT_LIBINFIX}d ${QT_MODULE}${QT_LIBINFIX}d4 + PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH + ) + ENDFOREACH(QT_MODULE) + + # QtUiTools not with other frameworks with binary installation (in /usr/lib) + IF(Q_WS_MAC AND QT_QTCORE_LIBRARY_RELEASE AND NOT QT_QTUITOOLS_LIBRARY_RELEASE) + FIND_LIBRARY(QT_QTUITOOLS_LIBRARY_RELEASE NAMES QtUiTools${QT_LIBINFIX} PATHS ${QT_LIBRARY_DIR}) + ENDIF(Q_WS_MAC AND QT_QTCORE_LIBRARY_RELEASE AND NOT QT_QTUITOOLS_LIBRARY_RELEASE) + + IF( NOT QT_QTCORE_LIBRARY_DEBUG AND NOT QT_QTCORE_LIBRARY_RELEASE ) + + # try dropping a hint if trying to use Visual Studio with Qt built by mingw + IF(QT_LIBRARY_DIR AND MSVC) + IF(EXISTS ${QT_LIBRARY_DIR}/libqtmain.a) + MESSAGE( FATAL_ERROR "It appears you're trying to use Visual Studio with Qt built by mingw") + ENDIF(EXISTS ${QT_LIBRARY_DIR}/libqtmain.a) + ENDIF(QT_LIBRARY_DIR AND MSVC) + + IF(Qt4_FIND_REQUIRED) + MESSAGE( FATAL_ERROR "Could NOT find QtCore. Check ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log for more details.") + ENDIF(Qt4_FIND_REQUIRED) + ENDIF( NOT QT_QTCORE_LIBRARY_DEBUG AND NOT QT_QTCORE_LIBRARY_RELEASE ) + + # Set QT_QTDESIGNERCOMPONENTS_LIBRARY + FIND_LIBRARY(QT_QTDESIGNERCOMPONENTS_LIBRARY_RELEASE NAMES QtDesignerComponents${QT_LIBINFIX} QtDesignerComponents${QT_LIBINFIX}4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTDESIGNERCOMPONENTS_LIBRARY_DEBUG NAMES QtDesignerComponents${QT_LIBINFIX}_debug QtDesignerComponents${QT_LIBINFIX}d QtDesignerComponents${QT_LIBINFIX}d4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + + # Set QT_QTMAIN_LIBRARY + IF(WIN32) + FIND_LIBRARY(QT_QTMAIN_LIBRARY_RELEASE NAMES qtmain${QT_LIBINFIX} PATHS ${QT_LIBRARY_DIR} + NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTMAIN_LIBRARY_DEBUG NAMES qtmain${QT_LIBINFIX}d PATHS ${QT_LIBRARY_DIR} + NO_DEFAULT_PATH) + ENDIF(WIN32) + + # Set QT_QTASSISTANTCLIENT_LIBRARY + FIND_LIBRARY(QT_QTASSISTANTCLIENT_LIBRARY_RELEASE NAMES QtAssistantClient${QT_LIBINFIX} QtAssistantClient${QT_LIBINFIX}4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTASSISTANTCLIENT_LIBRARY_DEBUG NAMES QtAssistantClient${QT_LIBINFIX}_debug QtAssistantClient${QT_LIBINFIX}d QtAssistantClient${QT_LIBINFIX}d4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + + # Set QT_QTASSISTANT_LIBRARY + FIND_LIBRARY(QT_QTASSISTANT_LIBRARY_RELEASE NAMES QtAssistantClient${QT_LIBINFIX} QtAssistantClient${QT_LIBINFIX}4 QtAssistant${QT_LIBINFIX} QtAssistant${QT_LIBINFIX}4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTASSISTANT_LIBRARY_DEBUG NAMES QtAssistantClient${QT_LIBINFIX}_debug QtAssistantClient${QT_LIBINFIX}d QtAssistantClient${QT_LIBINFIX}d4 QtAssistant${QT_LIBINFIX}_debug QtAssistant${QT_LIBINFIX}d4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + + # Set QT_QTHELP_LIBRARY + FIND_LIBRARY(QT_QTCLUCENE_LIBRARY_RELEASE NAMES QtCLucene${QT_LIBINFIX} QtCLucene${QT_LIBINFIX}4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + FIND_LIBRARY(QT_QTCLUCENE_LIBRARY_DEBUG NAMES QtCLucene${QT_LIBINFIX}_debug QtCLucene${QT_LIBINFIX}d QtCLucene${QT_LIBINFIX}d4 PATHS ${QT_LIBRARY_DIR} NO_DEFAULT_PATH) + # QtCLucene not with other frameworks with binary installation (in /usr/lib) + IF(Q_WS_MAC AND QT_QTCORE_LIBRARY_RELEASE AND NOT QT_QTCLUCENE_LIBRARY_RELEASE) + FIND_LIBRARY(QT_QTCLUCENE_LIBRARY_RELEASE NAMES QtCLucene${QT_LIBINFIX} PATHS ${QT_LIBRARY_DIR}) + ENDIF(Q_WS_MAC AND QT_QTCORE_LIBRARY_RELEASE AND NOT QT_QTCLUCENE_LIBRARY_RELEASE) + + ############################################ + # + # Check the existence of the libraries. + # + ############################################ + + # On OSX when Qt is found as framework, never use the imported targets for now, since + # in this case the handling of the framework directory currently does not work correctly. + IF(QT_USE_FRAMEWORKS) + SET(QT_USE_IMPORTED_TARGETS FALSE) + ENDIF(QT_USE_FRAMEWORKS) + + + MACRO (_QT4_ADJUST_LIB_VARS _camelCaseBasename) + + STRING(TOUPPER "${_camelCaseBasename}" basename) + + # The name of the imported targets, i.e. the prefix "Qt4::" must not change, + # since it is stored in EXPORT-files as name of a required library. If the name would change + # here, this would lead to the imported Qt4-library targets not being resolved by cmake anymore. + IF (QT_${basename}_LIBRARY_RELEASE OR QT_${basename}_LIBRARY_DEBUG) + + IF(NOT TARGET Qt4::${_camelCaseBasename}) + ADD_LIBRARY(Qt4::${_camelCaseBasename} UNKNOWN IMPORTED ) + + IF (QT_${basename}_LIBRARY_RELEASE) + SET_PROPERTY(TARGET Qt4::${_camelCaseBasename} APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) + SET_PROPERTY(TARGET Qt4::${_camelCaseBasename} PROPERTY IMPORTED_LOCATION_RELEASE "${QT_${basename}_LIBRARY_RELEASE}" ) + ENDIF (QT_${basename}_LIBRARY_RELEASE) + + IF (QT_${basename}_LIBRARY_DEBUG) + SET_PROPERTY(TARGET Qt4::${_camelCaseBasename} APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) + SET_PROPERTY(TARGET Qt4::${_camelCaseBasename} PROPERTY IMPORTED_LOCATION_DEBUG "${QT_${basename}_LIBRARY_DEBUG}" ) + ENDIF (QT_${basename}_LIBRARY_DEBUG) + ENDIF(NOT TARGET Qt4::${_camelCaseBasename}) + + # If QT_USE_IMPORTED_TARGETS is enabled, the QT_QTFOO_LIBRARY variables are set to point at these + # imported targets. This works better in general, and is also in almost all cases fully + # backward compatible. The only issue is when a project A which had this enabled then exports its + # libraries via export or EXPORT_LIBRARY_DEPENDENCIES(). In this case the libraries from project + # A will depend on the imported Qt targets, and the names of these imported targets will be stored + # in the dependency files on disk. This means when a project B then uses project A, these imported + # targets must be created again, otherwise e.g. "Qt4__QtCore" will be interpreted as name of a + # library file on disk, and not as a target, and linking will fail: + IF(QT_USE_IMPORTED_TARGETS) + SET(QT_${basename}_LIBRARY Qt4::${_camelCaseBasename} ) + SET(QT_${basename}_LIBRARIES Qt4::${_camelCaseBasename} ) + ELSE(QT_USE_IMPORTED_TARGETS) + + # if the release- as well as the debug-version of the library have been found: + IF (QT_${basename}_LIBRARY_DEBUG AND QT_${basename}_LIBRARY_RELEASE) + # if the generator supports configuration types then set + # optimized and debug libraries, or if the CMAKE_BUILD_TYPE has a value + IF (CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) + SET(QT_${basename}_LIBRARY optimized ${QT_${basename}_LIBRARY_RELEASE} debug ${QT_${basename}_LIBRARY_DEBUG}) + ELSE(CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) + # if there are no configuration types and CMAKE_BUILD_TYPE has no value + # then just use the release libraries + SET(QT_${basename}_LIBRARY ${QT_${basename}_LIBRARY_RELEASE} ) + ENDIF(CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) + SET(QT_${basename}_LIBRARIES optimized ${QT_${basename}_LIBRARY_RELEASE} debug ${QT_${basename}_LIBRARY_DEBUG}) + ENDIF (QT_${basename}_LIBRARY_DEBUG AND QT_${basename}_LIBRARY_RELEASE) + + # if only the release version was found, set the debug variable also to the release version + IF (QT_${basename}_LIBRARY_RELEASE AND NOT QT_${basename}_LIBRARY_DEBUG) + SET(QT_${basename}_LIBRARY_DEBUG ${QT_${basename}_LIBRARY_RELEASE}) + SET(QT_${basename}_LIBRARY ${QT_${basename}_LIBRARY_RELEASE}) + SET(QT_${basename}_LIBRARIES ${QT_${basename}_LIBRARY_RELEASE}) + ENDIF (QT_${basename}_LIBRARY_RELEASE AND NOT QT_${basename}_LIBRARY_DEBUG) + + # if only the debug version was found, set the release variable also to the debug version + IF (QT_${basename}_LIBRARY_DEBUG AND NOT QT_${basename}_LIBRARY_RELEASE) + SET(QT_${basename}_LIBRARY_RELEASE ${QT_${basename}_LIBRARY_DEBUG}) + SET(QT_${basename}_LIBRARY ${QT_${basename}_LIBRARY_DEBUG}) + SET(QT_${basename}_LIBRARIES ${QT_${basename}_LIBRARY_DEBUG}) + ENDIF (QT_${basename}_LIBRARY_DEBUG AND NOT QT_${basename}_LIBRARY_RELEASE) + + # put the value in the cache: + SET(QT_${basename}_LIBRARY ${QT_${basename}_LIBRARY} CACHE STRING "The Qt ${basename} library" FORCE) + + ENDIF(QT_USE_IMPORTED_TARGETS) + +#message(STATUS "QT_${basename}_LIBRARY: ${QT_${basename}_LIBRARY}") + + SET(QT_${basename}_FOUND 1) + + ENDIF (QT_${basename}_LIBRARY_RELEASE OR QT_${basename}_LIBRARY_DEBUG) + + IF (QT_${basename}_INCLUDE_DIR) + #add the include directory to QT_INCLUDES + SET(QT_INCLUDES "${QT_${basename}_INCLUDE_DIR}" ${QT_INCLUDES}) + ENDIF (QT_${basename}_INCLUDE_DIR) + + # Make variables changeble to the advanced user + MARK_AS_ADVANCED(QT_${basename}_LIBRARY QT_${basename}_LIBRARY_RELEASE QT_${basename}_LIBRARY_DEBUG QT_${basename}_INCLUDE_DIR) + ENDMACRO (_QT4_ADJUST_LIB_VARS) + + + # Set QT_xyz_LIBRARY variable and add + # library include path to QT_INCLUDES + _QT4_ADJUST_LIB_VARS(QtCore) + _QT4_ADJUST_LIB_VARS(QtGui) + _QT4_ADJUST_LIB_VARS(Qt3Support) + _QT4_ADJUST_LIB_VARS(QtAssistant) + _QT4_ADJUST_LIB_VARS(QtAssistantClient) + _QT4_ADJUST_LIB_VARS(QtCLucene) + _QT4_ADJUST_LIB_VARS(QtDBus) + _QT4_ADJUST_LIB_VARS(QtDesigner) + _QT4_ADJUST_LIB_VARS(QtDesignerComponents) + _QT4_ADJUST_LIB_VARS(QtHelp) + _QT4_ADJUST_LIB_VARS(QtMultimedia) + _QT4_ADJUST_LIB_VARS(QtNetwork) + _QT4_ADJUST_LIB_VARS(QtNsPlugin) + _QT4_ADJUST_LIB_VARS(QtOpenGL) + _QT4_ADJUST_LIB_VARS(QtScript) + _QT4_ADJUST_LIB_VARS(QtScriptTools) + _QT4_ADJUST_LIB_VARS(QtSql) + _QT4_ADJUST_LIB_VARS(QtSvg) + _QT4_ADJUST_LIB_VARS(QtTest) + _QT4_ADJUST_LIB_VARS(QtUiTools) + _QT4_ADJUST_LIB_VARS(QtWebKit) + _QT4_ADJUST_LIB_VARS(QtXml) + _QT4_ADJUST_LIB_VARS(QtXmlPatterns) + _QT4_ADJUST_LIB_VARS(phonon) + + # platform dependent libraries + IF(Q_WS_X11) + _QT4_ADJUST_LIB_VARS(QtMotif) + ENDIF(Q_WS_X11) + IF(WIN32) + _QT4_ADJUST_LIB_VARS(qtmain) + _QT4_ADJUST_LIB_VARS(QAxServer) + _QT4_ADJUST_LIB_VARS(QAxContainer) + ENDIF(WIN32) + + # If Qt is installed as a framework, we need to add QT_QTCORE_LIBRARY here (which + # is the framework directory in that case), since this will make the cmake include_directories() + # command recognize that we need the framework flag with the respective directory (-F) + IF(QT_USE_FRAMEWORKS) + SET(QT_INCLUDES ${QT_INCLUDES} ${QT_QTCORE_LIBRARY} ) + SET(QT_INCLUDE_DIR ${QT_INCLUDE_DIR} ${QT_QTCORE_LIBRARY} ) + ENDIF(QT_USE_FRAMEWORKS) + + + + ####################################### + # + # Check the executables of Qt + # ( moc, uic, rcc ) + # + ####################################### + + + IF(QT_QMAKE_CHANGED) + SET(QT_UIC_EXECUTABLE NOTFOUND) + SET(QT_MOC_EXECUTABLE NOTFOUND) + SET(QT_UIC3_EXECUTABLE NOTFOUND) + SET(QT_RCC_EXECUTABLE NOTFOUND) + SET(QT_DBUSCPP2XML_EXECUTABLE NOTFOUND) + SET(QT_DBUSXML2CPP_EXECUTABLE NOTFOUND) + SET(QT_LUPDATE_EXECUTABLE NOTFOUND) + SET(QT_LRELEASE_EXECUTABLE NOTFOUND) + SET(QT_QCOLLECTIONGENERATOR_EXECUTABLE NOTFOUND) + SET(QT_DESIGNER_EXECUTABLE NOTFOUND) + SET(QT_LINGUIST_EXECUTABLE NOTFOUND) + ENDIF(QT_QMAKE_CHANGED) + + FIND_PROGRAM(QT_MOC_EXECUTABLE + NAMES moc-qt4 moc + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_UIC_EXECUTABLE + NAMES uic-qt4 uic + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_UIC3_EXECUTABLE + NAMES uic3 + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_RCC_EXECUTABLE + NAMES rcc + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_DBUSCPP2XML_EXECUTABLE + NAMES qdbuscpp2xml + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_DBUSXML2CPP_EXECUTABLE + NAMES qdbusxml2cpp + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_LUPDATE_EXECUTABLE + NAMES lupdate-qt4 lupdate + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_LRELEASE_EXECUTABLE + NAMES lrelease-qt4 lrelease + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_QCOLLECTIONGENERATOR_EXECUTABLE + NAMES qcollectiongenerator-qt4 qcollectiongenerator + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_DESIGNER_EXECUTABLE + NAMES designer-qt4 designer + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + FIND_PROGRAM(QT_LINGUIST_EXECUTABLE + NAMES linguist-qt4 linguist + PATHS ${QT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + IF (QT_MOC_EXECUTABLE) + SET(QT_WRAP_CPP "YES") + ENDIF (QT_MOC_EXECUTABLE) + + IF (QT_UIC_EXECUTABLE) + SET(QT_WRAP_UI "YES") + ENDIF (QT_UIC_EXECUTABLE) + + + + MARK_AS_ADVANCED( QT_UIC_EXECUTABLE QT_UIC3_EXECUTABLE QT_MOC_EXECUTABLE + QT_RCC_EXECUTABLE QT_DBUSXML2CPP_EXECUTABLE QT_DBUSCPP2XML_EXECUTABLE + QT_LUPDATE_EXECUTABLE QT_LRELEASE_EXECUTABLE QT_QCOLLECTIONGENERATOR_EXECUTABLE + QT_DESIGNER_EXECUTABLE QT_LINGUIST_EXECUTABLE) + + + # get the directory of the current file, used later on in the file + GET_FILENAME_COMPONENT( _qt4_current_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) + + ###################################### + # + # Macros for building Qt files + # + ###################################### + + INCLUDE("${_qt4_current_dir}/Qt4Macros.cmake") + + + ###################################### + # + # decide if Qt got found + # + ###################################### + + # if the includes,libraries,moc,uic and rcc are found then we have it + IF( QT_LIBRARY_DIR AND QT_INCLUDE_DIR AND QT_MOC_EXECUTABLE AND + QT_UIC_EXECUTABLE AND QT_RCC_EXECUTABLE AND QT_QTCORE_LIBRARY) + SET( QT4_FOUND "YES" ) + IF( NOT Qt4_FIND_QUIETLY) + MESSAGE(STATUS "Found Qt-Version ${QTVERSION} (using ${QT_QMAKE_EXECUTABLE})") + ENDIF( NOT Qt4_FIND_QUIETLY) + ELSE( QT_LIBRARY_DIR AND QT_INCLUDE_DIR AND QT_MOC_EXECUTABLE AND + QT_UIC_EXECUTABLE AND QT_RCC_EXECUTABLE AND QT_QTCORE_LIBRARY) + SET( QT4_FOUND "NO") + SET(QT_QMAKE_EXECUTABLE "${QT_QMAKE_EXECUTABLE}-NOTFOUND" CACHE FILEPATH "Invalid qmake found" FORCE) + IF( Qt4_FIND_REQUIRED) + IF ( NOT QT_LIBRARY_DIR ) + MESSAGE(STATUS "Qt libraries NOT found!") + ENDIF(NOT QT_LIBRARY_DIR ) + IF ( NOT QT_INCLUDE_DIR ) + MESSAGE(STATUS "Qt includes NOT found!") + ENDIF( NOT QT_INCLUDE_DIR ) + IF ( NOT QT_MOC_EXECUTABLE ) + MESSAGE(STATUS "Qt's moc NOT found!") + ENDIF( NOT QT_MOC_EXECUTABLE ) + IF ( NOT QT_UIC_EXECUTABLE ) + MESSAGE(STATUS "Qt's uic NOT found!") + ENDIF( NOT QT_UIC_EXECUTABLE ) + IF ( NOT QT_RCC_EXECUTABLE ) + MESSAGE(STATUS "Qt's rcc NOT found!") + ENDIF( NOT QT_RCC_EXECUTABLE ) + MESSAGE( FATAL_ERROR "Qt libraries, includes, moc, uic or/and rcc NOT found!") + ENDIF( Qt4_FIND_REQUIRED) + ENDIF( QT_LIBRARY_DIR AND QT_INCLUDE_DIR AND QT_MOC_EXECUTABLE AND + QT_UIC_EXECUTABLE AND QT_RCC_EXECUTABLE AND QT_QTCORE_LIBRARY) + + SET(QT_FOUND ${QT4_FOUND}) + + + ############################################### + # + # configuration/system dependent settings + # + ############################################### + + INCLUDE("${_qt4_current_dir}/Qt4ConfigDependentSettings.cmake") + + + ####################################### + # + # compatibility settings + # + ####################################### + # Backwards compatibility for CMake1.4 and 1.2 + SET (QT_MOC_EXE ${QT_MOC_EXECUTABLE} ) + SET (QT_UIC_EXE ${QT_UIC_EXECUTABLE} ) + + SET( QT_QT_LIBRARY "") + +ELSE(QT4_QMAKE_FOUND) + + SET(QT_QMAKE_EXECUTABLE "${QT_QMAKE_EXECUTABLE}-NOTFOUND" CACHE FILEPATH "Invalid qmake found" FORCE) + + # The code below is overly complex to make sure we do not break compatibility with CMake 2.6.x + # For CMake 2.8, it should be simplified by getting rid of QT4_INSTALLED_VERSION_TOO_OLD and + # QT4_INSTALLED_VERSION_TOO_NEW + IF(Qt4_FIND_REQUIRED) + IF(QT4_INSTALLED_VERSION_TOO_OLD) + IF( Qt4_FIND_VERSION_EXACT ) + MESSAGE(FATAL_ERROR "The installed Qt version ${QTVERSION} is too old, version ${QT_MIN_VERSION} is required") + ELSE( Qt4_FIND_VERSION_EXACT ) + MESSAGE(FATAL_ERROR "The installed Qt version ${QTVERSION} is too old, at least version ${QT_MIN_VERSION} is required") + ENDIF( Qt4_FIND_VERSION_EXACT ) + ELSE(QT4_INSTALLED_VERSION_TOO_OLD) + IF( Qt4_FIND_VERSION_EXACT AND QT4_INSTALLED_VERSION_TOO_NEW ) + MESSAGE(FATAL_ERROR "The installed Qt version ${QTVERSION} is too new, version ${QT_MIN_VERSION} is required") + ELSE( Qt4_FIND_VERSION_EXACT AND QT4_INSTALLED_VERSION_TOO_NEW ) + MESSAGE( FATAL_ERROR "Qt qmake not found!") + ENDIF( Qt4_FIND_VERSION_EXACT AND QT4_INSTALLED_VERSION_TOO_NEW ) + ENDIF(QT4_INSTALLED_VERSION_TOO_OLD) + ELSE(Qt4_FIND_REQUIRED) + IF(QT4_INSTALLED_VERSION_TOO_OLD AND NOT Qt4_FIND_QUIETLY) + MESSAGE(STATUS "The installed Qt version ${QTVERSION} is too old, at least version ${QT_MIN_VERSION} is required") + ENDIF(QT4_INSTALLED_VERSION_TOO_OLD AND NOT Qt4_FIND_QUIETLY) + ENDIF(Qt4_FIND_REQUIRED) + +ENDIF (QT4_QMAKE_FOUND) + diff --git a/libbluedevil/cmake/modules/MacroPushRequiredVars.cmake b/libbluedevil/cmake/modules/MacroPushRequiredVars.cmake new file mode 100644 index 00000000..650b566e --- /dev/null +++ b/libbluedevil/cmake/modules/MacroPushRequiredVars.cmake @@ -0,0 +1,47 @@ +# this module defines two macros: +# MACRO_PUSH_REQUIRED_VARS() +# and +# MACRO_POP_REQUIRED_VARS() +# use these if you call cmake macros which use +# any of the CMAKE_REQUIRED_XXX variables +# +# Usage: +# MACRO_PUSH_REQUIRED_VARS() +# SET(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -DSOME_MORE_DEF) +# CHECK_FUNCTION_EXISTS(...) +# MACRO_POP_REQUIRED_VARS() + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +MACRO(MACRO_PUSH_REQUIRED_VARS) + + IF(NOT DEFINED _PUSH_REQUIRED_VARS_COUNTER) + SET(_PUSH_REQUIRED_VARS_COUNTER 0) + ENDIF(NOT DEFINED _PUSH_REQUIRED_VARS_COUNTER) + + MATH(EXPR _PUSH_REQUIRED_VARS_COUNTER "${_PUSH_REQUIRED_VARS_COUNTER}+1") + + SET(_CMAKE_REQUIRED_INCLUDES_SAVE_${_PUSH_REQUIRED_VARS_COUNTER} ${CMAKE_REQUIRED_INCLUDES}) + SET(_CMAKE_REQUIRED_DEFINITIONS_SAVE_${_PUSH_REQUIRED_VARS_COUNTER} ${CMAKE_REQUIRED_DEFINITIONS}) + SET(_CMAKE_REQUIRED_LIBRARIES_SAVE_${_PUSH_REQUIRED_VARS_COUNTER} ${CMAKE_REQUIRED_LIBRARIES}) + SET(_CMAKE_REQUIRED_FLAGS_SAVE_${_PUSH_REQUIRED_VARS_COUNTER} ${CMAKE_REQUIRED_FLAGS}) +ENDMACRO(MACRO_PUSH_REQUIRED_VARS) + +MACRO(MACRO_POP_REQUIRED_VARS) + +# don't pop more than we pushed + IF("${_PUSH_REQUIRED_VARS_COUNTER}" GREATER "0") + + SET(CMAKE_REQUIRED_INCLUDES ${_CMAKE_REQUIRED_INCLUDES_SAVE_${_PUSH_REQUIRED_VARS_COUNTER}}) + SET(CMAKE_REQUIRED_DEFINITIONS ${_CMAKE_REQUIRED_DEFINITIONS_SAVE_${_PUSH_REQUIRED_VARS_COUNTER}}) + SET(CMAKE_REQUIRED_LIBRARIES ${_CMAKE_REQUIRED_LIBRARIES_SAVE_${_PUSH_REQUIRED_VARS_COUNTER}}) + SET(CMAKE_REQUIRED_FLAGS ${_CMAKE_REQUIRED_FLAGS_SAVE_${_PUSH_REQUIRED_VARS_COUNTER}}) + + MATH(EXPR _PUSH_REQUIRED_VARS_COUNTER "${_PUSH_REQUIRED_VARS_COUNTER}-1") + ENDIF("${_PUSH_REQUIRED_VARS_COUNTER}" GREATER "0") + +ENDMACRO(MACRO_POP_REQUIRED_VARS) + diff --git a/libbluedevil/cmake/modules/Qt4ConfigDependentSettings.cmake b/libbluedevil/cmake/modules/Qt4ConfigDependentSettings.cmake new file mode 100644 index 00000000..b5462e7b --- /dev/null +++ b/libbluedevil/cmake/modules/Qt4ConfigDependentSettings.cmake @@ -0,0 +1,384 @@ +# This file is included by FindQt4.cmake, don't include it directly. + +#============================================================================= +# Copyright 2005-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distributed this file outside of CMake, substitute the full +# License text for the above reference.) + + +############################################### +# +# configuration/system dependent settings +# +############################################### + +# this check for X11 and threads may not be necessary, since it is not +# contained in the cmake version of FindQt4.cmake: + +# for unix add X11 stuff +IF(UNIX) + # on OS X X11 may not be required + IF (Q_WS_X11) + FIND_PACKAGE(X11 REQUIRED) + ENDIF (Q_WS_X11) + FIND_PACKAGE(Threads) + SET(QT_QTCORE_LIBRARY ${QT_QTCORE_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) +ENDIF(UNIX) + + +# find dependencies for some Qt modules +# when doing builds against a static Qt, they are required +# when doing builds against a shared Qt, they are not required +# if a user needs the dependencies, and they couldn't be found, they can set +# the variables themselves. + +SET(QT_QTGUI_LIB_DEPENDENCIES "") +SET(QT_QTCORE_LIB_DEPENDENCIES "") +SET(QT_QTNETWORK_LIB_DEPENDENCIES "") +SET(QT_QTOPENGL_LIB_DEPENDENCIES "") +SET(QT_QTDBUS_LIB_DEPENDENCIES "") +SET(QT_QTHELP_LIB_DEPENDENCIES ${QT_QTCLUCENE_LIBRARY}) + + +IF(WIN32) + # On Windows, qconfig.pri has "static" for static library builds + IF(QT_CONFIG MATCHES "static") + SET(QT_IS_STATIC 1) + ENDIF(QT_CONFIG MATCHES "static") +ELSE(WIN32) + # On other platforms, check file extension to know if its static + IF(QT_QTCORE_LIBRARY_RELEASE) + GET_FILENAME_COMPONENT(qtcore_lib_ext "${QT_QTCORE_LIBRARY_RELEASE}" EXT) + IF("${qtcore_lib_ext}" STREQUAL "${CMAKE_STATIC_LIBRARY_SUFFIX}") + SET(QT_IS_STATIC 1) + ENDIF("${qtcore_lib_ext}" STREQUAL "${CMAKE_STATIC_LIBRARY_SUFFIX}") + ENDIF(QT_QTCORE_LIBRARY_RELEASE) + IF(QT_QTCORE_LIBRARY_DEBUG) + GET_FILENAME_COMPONENT(qtcore_lib_ext "${QT_QTCORE_LIBRARY_DEBUG}" EXT) + IF(${qtcore_lib_ext} STREQUAL ${CMAKE_STATIC_LIBRARY_SUFFIX}) + SET(QT_IS_STATIC 1) + ENDIF(${qtcore_lib_ext} STREQUAL ${CMAKE_STATIC_LIBRARY_SUFFIX}) + ENDIF(QT_QTCORE_LIBRARY_DEBUG) +ENDIF(WIN32) + +# build using shared Qt needs -DQT_DLL on Windows +IF(WIN32 AND NOT QT_IS_STATIC) + SET(QT_DEFINITIONS ${QT_DEFINITIONS} -DQT_DLL) +ENDIF(WIN32 AND NOT QT_IS_STATIC) + + +# QtOpenGL dependencies +QT_QUERY_QMAKE(QMAKE_LIBS_OPENGL "QMAKE_LIBS_OPENGL") +IF(Q_WS_MAC) +# On the Mac OpenGL is probably frameworks and QMAKE_LIBS_OPENGL can be e.g. "-framework OpenGL -framework AGL". +# The separate_arguments() call in the other branch makes "-framework;-OpenGL;-framework;-lAGL" appear in the +# linker command. So we need to protect the "-framework foo" as non-separatable strings. +# We do this by replacing the space after "-framework" with an underscore, then calling separate_arguments(), +# and then we replace the underscores again with spaces. So we get proper linker commands. Alex + STRING(REGEX REPLACE "-framework +" "-framework_" QMAKE_LIBS_OPENGL "${QMAKE_LIBS_OPENGL}") + SEPARATE_ARGUMENTS(QMAKE_LIBS_OPENGL) + STRING(REGEX REPLACE "-framework_" "-framework " QMAKE_LIBS_OPENGL "${QMAKE_LIBS_OPENGL}") +ELSE(Q_WS_MAC) + SEPARATE_ARGUMENTS(QMAKE_LIBS_OPENGL) +ENDIF(Q_WS_MAC) +SET (QT_QTOPENGL_LIB_DEPENDENCIES ${QT_QTOPENGL_LIB_DEPENDENCIES} ${QMAKE_LIBS_OPENGL}) + + +## system png +IF(QT_QCONFIG MATCHES "system-png") + FIND_LIBRARY(QT_PNG_LIBRARY NAMES png) + MARK_AS_ADVANCED(QT_PNG_LIBRARY) + IF(QT_PNG_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_PNG_LIBRARY}) + ENDIF(QT_PNG_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "system-png") + + +# for X11, get X11 library directory +IF(Q_WS_X11) + QT_QUERY_QMAKE(QMAKE_LIBDIR_X11 "QMAKE_LIBDIR_X11") +ENDIF(Q_WS_X11) + + +## X11 SM +IF(QT_QCONFIG MATCHES "x11sm") + # ask qmake where the x11 libs are + FIND_LIBRARY(QT_X11_SM_LIBRARY NAMES SM PATHS ${QMAKE_LIBDIR_X11}) + FIND_LIBRARY(QT_X11_ICE_LIBRARY NAMES ICE PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_X11_SM_LIBRARY) + MARK_AS_ADVANCED(QT_X11_ICE_LIBRARY) + IF(QT_X11_SM_LIBRARY AND QT_X11_ICE_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_X11_SM_LIBRARY} ${QT_X11_ICE_LIBRARY}) + ENDIF(QT_X11_SM_LIBRARY AND QT_X11_ICE_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "x11sm") + + +## Xi +IF(QT_QCONFIG MATCHES "tablet") + FIND_LIBRARY(QT_XI_LIBRARY NAMES Xi PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XI_LIBRARY) + IF(QT_XI_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XI_LIBRARY}) + ENDIF(QT_XI_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "tablet") + + +## Xrender +IF(QT_QCONFIG MATCHES "xrender") + FIND_LIBRARY(QT_XRENDER_LIBRARY NAMES Xrender PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XRENDER_LIBRARY) + IF(QT_XRENDER_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XRENDER_LIBRARY}) + ENDIF(QT_XRENDER_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xrender") + + +## Xrandr +IF(QT_QCONFIG MATCHES "xrandr") + FIND_LIBRARY(QT_XRANDR_LIBRARY NAMES Xrandr PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XRANDR_LIBRARY) + IF(QT_XRANDR_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XRANDR_LIBRARY}) + ENDIF(QT_XRANDR_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xrandr") + + +## Xcursor +IF(QT_QCONFIG MATCHES "xcursor") + FIND_LIBRARY(QT_XCURSOR_LIBRARY NAMES Xcursor PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XCURSOR_LIBRARY) + IF(QT_XCURSOR_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XCURSOR_LIBRARY}) + ENDIF(QT_XCURSOR_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xcursor") + + +## Xinerama +IF(QT_QCONFIG MATCHES "xinerama") + FIND_LIBRARY(QT_XINERAMA_LIBRARY NAMES Xinerama PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XINERAMA_LIBRARY) + IF(QT_XINERAMA_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XINERAMA_LIBRARY}) + ENDIF(QT_XINERAMA_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xinerama") + + +## Xfixes +IF(QT_QCONFIG MATCHES "xfixes") + FIND_LIBRARY(QT_XFIXES_LIBRARY NAMES Xfixes PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(QT_XFIXES_LIBRARY) + IF(QT_XFIXES_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_XFIXES_LIBRARY}) + ENDIF(QT_XFIXES_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "xfixes") + + +## system-freetype +IF(QT_QCONFIG MATCHES "system-freetype") + FIND_LIBRARY(QT_FREETYPE_LIBRARY NAMES freetype) + MARK_AS_ADVANCED(QT_FREETYPE_LIBRARY) + IF(QT_FREETYPE_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_FREETYPE_LIBRARY}) + ENDIF(QT_FREETYPE_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "system-freetype") + + +## fontconfig +IF(QT_QCONFIG MATCHES "fontconfig") + FIND_LIBRARY(QT_FONTCONFIG_LIBRARY NAMES fontconfig) + MARK_AS_ADVANCED(QT_FONTCONFIG_LIBRARY) + IF(QT_FONTCONFIG_LIBRARY) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${QT_FONTCONFIG_LIBRARY}) + ENDIF(QT_FONTCONFIG_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "fontconfig") + + +## system-zlib +IF(QT_QCONFIG MATCHES "system-zlib") + FIND_LIBRARY(QT_ZLIB_LIBRARY NAMES z) + MARK_AS_ADVANCED(QT_ZLIB_LIBRARY) + IF(QT_ZLIB_LIBRARY) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${QT_ZLIB_LIBRARY}) + ENDIF(QT_ZLIB_LIBRARY) +ENDIF(QT_QCONFIG MATCHES "system-zlib") + + +## openssl +IF(NOT Q_WS_WIN) + SET(_QT_NEED_OPENSSL 0) + IF(QT_VERSION_MINOR LESS 4 AND QT_QCONFIG MATCHES "openssl") + SET(_QT_NEED_OPENSSL 1) + ENDIF(QT_VERSION_MINOR LESS 4 AND QT_QCONFIG MATCHES "openssl") + IF(QT_VERSION_MINOR GREATER 3 AND QT_QCONFIG MATCHES "openssl-linked") + SET(_QT_NEED_OPENSSL 1) + ENDIF(QT_VERSION_MINOR GREATER 3 AND QT_QCONFIG MATCHES "openssl-linked") + IF(_QT_NEED_OPENSSL) + FIND_PACKAGE(OpenSSL) + IF(OPENSSL_LIBRARIES) + SET(QT_QTNETWORK_LIB_DEPENDENCIES ${QT_QTNETWORK_LIB_DEPENDENCIES} ${OPENSSL_LIBRARIES}) + ENDIF(OPENSSL_LIBRARIES) + ENDIF(_QT_NEED_OPENSSL) +ENDIF(NOT Q_WS_WIN) + + +## dbus +IF(QT_QCONFIG MATCHES "dbus") + + # if the dbus library isn't found, we'll assume its not required to build + # shared Qt on Linux doesn't require it + IF(NOT QT_DBUS_LIBRARY) + EXECUTE_PROCESS(COMMAND pkg-config --libs-only-L dbus-1 + OUTPUT_VARIABLE _dbus_query_output + RESULT_VARIABLE _dbus_result + ERROR_VARIABLE _dbus_query_output ) + + IF(_dbus_result MATCHES 0) + STRING(REPLACE "-L" "" _dbus_query_output "${_dbus_query_output}") + SEPARATE_ARGUMENTS(_dbus_query_output) + ELSE(_dbus_result MATCHES 0) + SET(_dbus_query_output) + ENDIF(_dbus_result MATCHES 0) + + FIND_LIBRARY(QT_DBUS_LIBRARY NAMES dbus-1 PATHS ${_dbus_query_output} ) + + IF(QT_DBUS_LIBRARY) + SET(QT_QTDBUS_LIB_DEPENDENCIES ${QT_QTDBUS_LIB_DEPENDENCIES} ${QT_DBUS_LIBRARY}) + ENDIF(QT_DBUS_LIBRARY) + + MARK_AS_ADVANCED(QT_DBUS_LIBRARY) + ENDIF(NOT QT_DBUS_LIBRARY) + +ENDIF(QT_QCONFIG MATCHES "dbus") + + +## glib +IF(QT_QCONFIG MATCHES "glib") + + # if the glib libraries aren't found, we'll assume its not required to build + # shared Qt on Linux doesn't require it + + # Qt 4.2.0+ uses glib-2.0 + IF(NOT QT_GLIB_LIBRARY OR NOT QT_GTHREAD_LIBRARY) + EXECUTE_PROCESS(COMMAND pkg-config --libs-only-L glib-2.0 gthread-2.0 + OUTPUT_VARIABLE _glib_query_output + RESULT_VARIABLE _glib_result + ERROR_VARIABLE _glib_query_output ) + + IF(_glib_result MATCHES 0) + STRING(REPLACE "-L" "" _glib_query_output "${_glib_query_output}") + SEPARATE_ARGUMENTS(_glib_query_output) + ELSE(_glib_result MATCHES 0) + SET(_glib_query_output) + ENDIF(_glib_result MATCHES 0) + + FIND_LIBRARY(QT_GLIB_LIBRARY NAMES glib-2.0 PATHS ${_glib_query_output} ) + FIND_LIBRARY(QT_GTHREAD_LIBRARY NAMES gthread-2.0 PATHS ${_glib_query_output} ) + + MARK_AS_ADVANCED(QT_GLIB_LIBRARY) + MARK_AS_ADVANCED(QT_GTHREAD_LIBRARY) + ENDIF(NOT QT_GLIB_LIBRARY OR NOT QT_GTHREAD_LIBRARY) + + IF(QT_GLIB_LIBRARY AND QT_GTHREAD_LIBRARY) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} + ${QT_GTHREAD_LIBRARY} ${QT_GLIB_LIBRARY}) + ENDIF(QT_GLIB_LIBRARY AND QT_GTHREAD_LIBRARY) + + + # Qt 4.5+ also links to gobject-2.0 + IF(QT_VERSION_MINOR GREATER 4) + IF(NOT QT_GOBJECT_LIBRARY) + EXECUTE_PROCESS(COMMAND pkg-config --libs-only-L gobject-2.0 + OUTPUT_VARIABLE _glib_query_output + RESULT_VARIABLE _glib_result + ERROR_VARIABLE _glib_query_output ) + + IF(_glib_result MATCHES 0) + STRING(REPLACE "-L" "" _glib_query_output "${_glib_query_output}") + SEPARATE_ARGUMENTS(_glib_query_output) + ELSE(_glib_result MATCHES 0) + SET(_glib_query_output) + ENDIF(_glib_result MATCHES 0) + + FIND_LIBRARY(QT_GOBJECT_LIBRARY NAMES gobject-2.0 PATHS ${_glib_query_output} ) + + MARK_AS_ADVANCED(QT_GOBJECT_LIBRARY) + ENDIF(NOT QT_GOBJECT_LIBRARY) + + IF(QT_GOBJECT_LIBRARY) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} + ${QT_GOBJECT_LIBRARY}) + ENDIF(QT_GOBJECT_LIBRARY) + ENDIF(QT_VERSION_MINOR GREATER 4) + +ENDIF(QT_QCONFIG MATCHES "glib") + + +## clock-monotonic, just see if we need to link with rt +IF(QT_QCONFIG MATCHES "clock-monotonic") + SET(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) + SET(CMAKE_REQUIRED_LIBRARIES rt) + CHECK_SYMBOL_EXISTS(_POSIX_TIMERS "unistd.h;time.h" QT_POSIX_TIMERS) + SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES_SAVE}) + IF(QT_POSIX_TIMERS) + FIND_LIBRARY(QT_RT_LIBRARY NAMES rt) + MARK_AS_ADVANCED(QT_RT_LIBRARY) + IF(QT_RT_LIBRARY) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${QT_RT_LIBRARY}) + ENDIF(QT_RT_LIBRARY) + ENDIF(QT_POSIX_TIMERS) +ENDIF(QT_QCONFIG MATCHES "clock-monotonic") + + +IF(Q_WS_X11) + # X11 libraries Qt absolutely depends on + QT_QUERY_QMAKE(QT_LIBS_X11 "QMAKE_LIBS_X11") + SEPARATE_ARGUMENTS(QT_LIBS_X11) + FOREACH(QT_X11_LIB ${QT_LIBS_X11}) + STRING(REGEX REPLACE "-l" "" QT_X11_LIB "${QT_X11_LIB}") + SET(QT_TMP_STR "QT_X11_${QT_X11_LIB}_LIBRARY") + FIND_LIBRARY(${QT_TMP_STR} NAMES "${QT_X11_LIB}" PATHS ${QMAKE_LIBDIR_X11}) + MARK_AS_ADVANCED(${QT_TMP_STR}) + IF(${QT_TMP_STR}) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} ${${QT_TMP_STR}}) + ENDIF(${QT_TMP_STR}) + ENDFOREACH(QT_X11_LIB) + + QT_QUERY_QMAKE(QT_LIBS_THREAD "QMAKE_LIBS_THREAD") + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${QT_LIBS_THREAD}) + + QT_QUERY_QMAKE(QMAKE_LIBS_DYNLOAD "QMAKE_LIBS_DYNLOAD") + SET (QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${QMAKE_LIBS_DYNLOAD}) + +ENDIF(Q_WS_X11) + + +IF(Q_WS_WIN) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} imm32 winmm) + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ws2_32) +ENDIF(Q_WS_WIN) + + +IF(Q_WS_MAC) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} "-framework Carbon") + + # Qt 4.0, 4.1, 4.2 use QuickTime + IF(QT_VERSION_MINOR LESS 3) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} "-framework QuickTime") + ENDIF(QT_VERSION_MINOR LESS 3) + + # Qt 4.2+ use AppKit + IF(QT_VERSION_MINOR GREATER 1) + SET(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} "-framework AppKit") + ENDIF(QT_VERSION_MINOR GREATER 1) + + SET(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} "-framework ApplicationServices") +ENDIF(Q_WS_MAC) + diff --git a/libbluedevil/cmake/modules/Qt4Macros.cmake b/libbluedevil/cmake/modules/Qt4Macros.cmake new file mode 100644 index 00000000..1422c592 --- /dev/null +++ b/libbluedevil/cmake/modules/Qt4Macros.cmake @@ -0,0 +1,414 @@ +# This file is included by FindQt4.cmake, don't include it directly. + +#============================================================================= +# Copyright 2005-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distributed this file outside of CMake, substitute the full +# License text for the above reference.) + + +###################################### +# +# Macros for building Qt files +# +###################################### + + +MACRO (QT4_EXTRACT_OPTIONS _qt4_files _qt4_options) + SET(${_qt4_files}) + SET(${_qt4_options}) + SET(_QT4_DOING_OPTIONS FALSE) + FOREACH(_currentArg ${ARGN}) + IF ("${_currentArg}" STREQUAL "OPTIONS") + SET(_QT4_DOING_OPTIONS TRUE) + ELSE ("${_currentArg}" STREQUAL "OPTIONS") + IF(_QT4_DOING_OPTIONS) + LIST(APPEND ${_qt4_options} "${_currentArg}") + ELSE(_QT4_DOING_OPTIONS) + LIST(APPEND ${_qt4_files} "${_currentArg}") + ENDIF(_QT4_DOING_OPTIONS) + ENDIF ("${_currentArg}" STREQUAL "OPTIONS") + ENDFOREACH(_currentArg) +ENDMACRO (QT4_EXTRACT_OPTIONS) + + +# macro used to create the names of output files preserving relative dirs +MACRO (QT4_MAKE_OUTPUT_FILE infile prefix ext outfile ) + STRING(LENGTH ${CMAKE_CURRENT_BINARY_DIR} _binlength) + STRING(LENGTH ${infile} _infileLength) + SET(_checkinfile ${CMAKE_CURRENT_SOURCE_DIR}) + IF(_infileLength GREATER _binlength) + STRING(SUBSTRING "${infile}" 0 ${_binlength} _checkinfile) + IF(_checkinfile STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + FILE(RELATIVE_PATH rel ${CMAKE_CURRENT_BINARY_DIR} ${infile}) + ELSE(_checkinfile STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + FILE(RELATIVE_PATH rel ${CMAKE_CURRENT_SOURCE_DIR} ${infile}) + ENDIF(_checkinfile STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + ELSE(_infileLength GREATER _binlength) + FILE(RELATIVE_PATH rel ${CMAKE_CURRENT_SOURCE_DIR} ${infile}) + ENDIF(_infileLength GREATER _binlength) + IF(WIN32 AND rel MATCHES "^[a-zA-Z]:") # absolute path + STRING(REGEX REPLACE "^([a-zA-Z]):(.*)$" "\\1_\\2" rel "${rel}") + ENDIF(WIN32 AND rel MATCHES "^[a-zA-Z]:") + SET(_outfile "${CMAKE_CURRENT_BINARY_DIR}/${rel}") + STRING(REPLACE ".." "__" _outfile ${_outfile}) + GET_FILENAME_COMPONENT(outpath ${_outfile} PATH) + GET_FILENAME_COMPONENT(_outfile ${_outfile} NAME_WE) + FILE(MAKE_DIRECTORY ${outpath}) + SET(${outfile} ${outpath}/${prefix}${_outfile}.${ext}) +ENDMACRO (QT4_MAKE_OUTPUT_FILE ) + + +MACRO (QT4_GET_MOC_FLAGS _moc_flags) + SET(${_moc_flags}) + GET_DIRECTORY_PROPERTY(_inc_DIRS INCLUDE_DIRECTORIES) + + FOREACH(_current ${_inc_DIRS}) + IF("${_current}" MATCHES ".framework/?$") + STRING(REGEX REPLACE "/[^/]+.framework" "" framework_path "${_current}") + SET(${_moc_flags} ${${_moc_flags}} "-F${framework_path}") + ELSE("${_current}" MATCHES ".framework/?$") + SET(${_moc_flags} ${${_moc_flags}} "-I${_current}") + ENDIF("${_current}" MATCHES ".framework/?$") + ENDFOREACH(_current ${_inc_DIRS}) + + GET_DIRECTORY_PROPERTY(_defines COMPILE_DEFINITIONS) + FOREACH(_current ${_defines}) + SET(${_moc_flags} ${${_moc_flags}} "-D${_current}") + ENDFOREACH(_current ${_defines}) + + IF(Q_WS_WIN) + SET(${_moc_flags} ${${_moc_flags}} -DWIN32) + ENDIF(Q_WS_WIN) + +ENDMACRO(QT4_GET_MOC_FLAGS) + + +# helper macro to set up a moc rule +MACRO (QT4_CREATE_MOC_COMMAND infile outfile moc_flags moc_options) + # For Windows, create a parameters file to work around command line length limit + IF (WIN32) + # Pass the parameters in a file. Set the working directory to + # be that containing the parameters file and reference it by + # just the file name. This is necessary because the moc tool on + # MinGW builds does not seem to handle spaces in the path to the + # file given with the @ syntax. + GET_FILENAME_COMPONENT(_moc_outfile_name "${outfile}" NAME) + GET_FILENAME_COMPONENT(_moc_outfile_dir "${outfile}" PATH) + IF(_moc_outfile_dir) + SET(_moc_working_dir WORKING_DIRECTORY ${_moc_outfile_dir}) + ENDIF(_moc_outfile_dir) + SET (_moc_parameters_file ${outfile}_parameters) + SET (_moc_parameters ${moc_flags} ${moc_options} -o "${outfile}" "${infile}") + FILE (REMOVE ${_moc_parameters_file}) + FOREACH(arg ${_moc_parameters}) + FILE (APPEND ${_moc_parameters_file} "${arg}\n") + ENDFOREACH(arg) + ADD_CUSTOM_COMMAND(OUTPUT ${outfile} + COMMAND ${QT_MOC_EXECUTABLE} @${_moc_outfile_name}_parameters + DEPENDS ${infile} + ${_moc_working_dir} + VERBATIM) + ELSE (WIN32) + ADD_CUSTOM_COMMAND(OUTPUT ${outfile} + COMMAND ${QT_MOC_EXECUTABLE} + ARGS ${moc_flags} ${moc_options} -o ${outfile} ${infile} + DEPENDS ${infile}) + ENDIF (WIN32) +ENDMACRO (QT4_CREATE_MOC_COMMAND) + + +MACRO (QT4_GENERATE_MOC infile outfile ) +# get include dirs and flags + QT4_GET_MOC_FLAGS(moc_flags) + GET_FILENAME_COMPONENT(abs_infile ${infile} ABSOLUTE) + QT4_CREATE_MOC_COMMAND(${abs_infile} ${outfile} "${moc_flags}" "") + SET_SOURCE_FILES_PROPERTIES(${outfile} PROPERTIES SKIP_AUTOMOC TRUE) # dont run automoc on this file + + MACRO_ADD_FILE_DEPENDENCIES(${abs_infile} ${outfile}) +ENDMACRO (QT4_GENERATE_MOC) + + +# QT4_WRAP_CPP(outfiles inputfile ... ) + +MACRO (QT4_WRAP_CPP outfiles ) + # get include dirs + QT4_GET_MOC_FLAGS(moc_flags) + QT4_EXTRACT_OPTIONS(moc_files moc_options ${ARGN}) + + FOREACH (it ${moc_files}) + GET_FILENAME_COMPONENT(it ${it} ABSOLUTE) + QT4_MAKE_OUTPUT_FILE(${it} moc_ cxx outfile) + QT4_CREATE_MOC_COMMAND(${it} ${outfile} "${moc_flags}" "${moc_options}") + SET(${outfiles} ${${outfiles}} ${outfile}) + ENDFOREACH(it) + +ENDMACRO (QT4_WRAP_CPP) + + +# QT4_WRAP_UI(outfiles inputfile ... ) + +MACRO (QT4_WRAP_UI outfiles ) + QT4_EXTRACT_OPTIONS(ui_files ui_options ${ARGN}) + + FOREACH (it ${ui_files}) + GET_FILENAME_COMPONENT(outfile ${it} NAME_WE) + GET_FILENAME_COMPONENT(infile ${it} ABSOLUTE) + SET(outfile ${CMAKE_CURRENT_BINARY_DIR}/ui_${outfile}.h) + ADD_CUSTOM_COMMAND(OUTPUT ${outfile} + COMMAND ${QT_UIC_EXECUTABLE} + ARGS ${ui_options} -o ${outfile} ${infile} + MAIN_DEPENDENCY ${infile}) + SET(${outfiles} ${${outfiles}} ${outfile}) + ENDFOREACH (it) + +ENDMACRO (QT4_WRAP_UI) + + +# QT4_ADD_RESOURCES(outfiles inputfile ... ) + +MACRO (QT4_ADD_RESOURCES outfiles ) + QT4_EXTRACT_OPTIONS(rcc_files rcc_options ${ARGN}) + + FOREACH (it ${rcc_files}) + GET_FILENAME_COMPONENT(outfilename ${it} NAME_WE) + GET_FILENAME_COMPONENT(infile ${it} ABSOLUTE) + GET_FILENAME_COMPONENT(rc_path ${infile} PATH) + SET(outfile ${CMAKE_CURRENT_BINARY_DIR}/qrc_${outfilename}.cxx) + # parse file for dependencies + # all files are absolute paths or relative to the location of the qrc file + FILE(READ "${infile}" _RC_FILE_CONTENTS) + STRING(REGEX MATCHALL "]*>" "" _RC_FILE "${_RC_FILE}") + STRING(REGEX MATCH "^/|([A-Za-z]:/)" _ABS_PATH_INDICATOR "${_RC_FILE}") + IF(NOT _ABS_PATH_INDICATOR) + SET(_RC_FILE "${rc_path}/${_RC_FILE}") + ENDIF(NOT _ABS_PATH_INDICATOR) + SET(_RC_DEPENDS ${_RC_DEPENDS} "${_RC_FILE}") + ENDFOREACH(_RC_FILE) + ADD_CUSTOM_COMMAND(OUTPUT ${outfile} + COMMAND ${QT_RCC_EXECUTABLE} + ARGS ${rcc_options} -name ${outfilename} -o ${outfile} ${infile} + MAIN_DEPENDENCY ${infile} + DEPENDS ${_RC_DEPENDS}) + SET(${outfiles} ${${outfiles}} ${outfile}) + ENDFOREACH (it) + +ENDMACRO (QT4_ADD_RESOURCES) + + +MACRO(QT4_ADD_DBUS_INTERFACE _sources _interface _basename) + GET_FILENAME_COMPONENT(_infile ${_interface} ABSOLUTE) + SET(_header ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.h) + SET(_impl ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.cpp) + SET(_moc ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.moc) + + GET_SOURCE_FILE_PROPERTY(_nonamespace ${_interface} NO_NAMESPACE) + IF ( _nonamespace ) + SET(_params -N -m) + ELSE ( _nonamespace ) + SET(_params -m) + ENDIF ( _nonamespace ) + + GET_SOURCE_FILE_PROPERTY(_classname ${_interface} CLASSNAME) + IF ( _classname ) + SET(_params ${_params} -c ${_classname}) + ENDIF ( _classname ) + + GET_SOURCE_FILE_PROPERTY(_include ${_interface} INCLUDE) + IF ( _include ) + SET(_params ${_params} -i ${_include}) + ENDIF ( _include ) + + ADD_CUSTOM_COMMAND(OUTPUT ${_impl} ${_header} + COMMAND ${QT_DBUSXML2CPP_EXECUTABLE} ${_params} -p ${_basename} ${_infile} + DEPENDS ${_infile}) + + SET_SOURCE_FILES_PROPERTIES(${_impl} PROPERTIES SKIP_AUTOMOC TRUE) + + QT4_GENERATE_MOC(${_header} ${_moc}) + + SET(${_sources} ${${_sources}} ${_impl} ${_header} ${_moc}) + MACRO_ADD_FILE_DEPENDENCIES(${_impl} ${_moc}) + +ENDMACRO(QT4_ADD_DBUS_INTERFACE) + + +MACRO(QT4_ADD_DBUS_INTERFACES _sources) + FOREACH (_current_FILE ${ARGN}) + GET_FILENAME_COMPONENT(_infile ${_current_FILE} ABSOLUTE) + # get the part before the ".xml" suffix + STRING(REGEX REPLACE "(.*[/\\.])?([^\\.]+)\\.xml" "\\2" _basename ${_current_FILE}) + STRING(TOLOWER ${_basename} _basename) + QT4_ADD_DBUS_INTERFACE(${_sources} ${_infile} ${_basename}interface) + ENDFOREACH (_current_FILE) +ENDMACRO(QT4_ADD_DBUS_INTERFACES) + + +MACRO(QT4_GENERATE_DBUS_INTERFACE _header) # _customName OPTIONS -some -options ) + QT4_EXTRACT_OPTIONS(_customName _qt4_dbus_options ${ARGN}) + + GET_FILENAME_COMPONENT(_in_file ${_header} ABSOLUTE) + GET_FILENAME_COMPONENT(_basename ${_header} NAME_WE) + + IF (_customName) + SET(_target ${CMAKE_CURRENT_BINARY_DIR}/${_customName}) + ELSE (_customName) + SET(_target ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.xml) + ENDIF (_customName) + + ADD_CUSTOM_COMMAND(OUTPUT ${_target} + COMMAND ${QT_DBUSCPP2XML_EXECUTABLE} ${_qt4_dbus_options} ${_in_file} -o ${_target} + DEPENDS ${_in_file} + ) +ENDMACRO(QT4_GENERATE_DBUS_INTERFACE) + + +MACRO(QT4_ADD_DBUS_ADAPTOR _sources _xml_file _include _parentClass) # _optionalBasename _optionalClassName) + GET_FILENAME_COMPONENT(_infile ${_xml_file} ABSOLUTE) + + SET(_optionalBasename "${ARGV4}") + IF (_optionalBasename) + SET(_basename ${_optionalBasename} ) + ELSE (_optionalBasename) + STRING(REGEX REPLACE "(.*[/\\.])?([^\\.]+)\\.xml" "\\2adaptor" _basename ${_infile}) + STRING(TOLOWER ${_basename} _basename) + ENDIF (_optionalBasename) + + SET(_optionalClassName "${ARGV5}") + SET(_header ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.h) + SET(_impl ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.cpp) + SET(_moc ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.moc) + + IF(_optionalClassName) + ADD_CUSTOM_COMMAND(OUTPUT ${_impl} ${_header} + COMMAND ${QT_DBUSXML2CPP_EXECUTABLE} -m -a ${_basename} -c ${_optionalClassName} -i ${_include} -l ${_parentClass} ${_infile} + DEPENDS ${_infile} + ) + ELSE(_optionalClassName) + ADD_CUSTOM_COMMAND(OUTPUT ${_impl} ${_header} + COMMAND ${QT_DBUSXML2CPP_EXECUTABLE} -m -a ${_basename} -i ${_include} -l ${_parentClass} ${_infile} + DEPENDS ${_infile} + ) + ENDIF(_optionalClassName) + + QT4_GENERATE_MOC(${_header} ${_moc}) + SET_SOURCE_FILES_PROPERTIES(${_impl} PROPERTIES SKIP_AUTOMOC TRUE) + MACRO_ADD_FILE_DEPENDENCIES(${_impl} ${_moc}) + + SET(${_sources} ${${_sources}} ${_impl} ${_header} ${_moc}) +ENDMACRO(QT4_ADD_DBUS_ADAPTOR) + + +MACRO(QT4_AUTOMOC) + QT4_GET_MOC_FLAGS(_moc_INCS) + + SET(_matching_FILES ) + FOREACH (_current_FILE ${ARGN}) + + GET_FILENAME_COMPONENT(_abs_FILE ${_current_FILE} ABSOLUTE) + # if "SKIP_AUTOMOC" is set to true, we will not handle this file here. + # This is required to make uic work correctly: + # we need to add generated .cpp files to the sources (to compile them), + # but we cannot let automoc handle them, as the .cpp files don't exist yet when + # cmake is run for the very first time on them -> however the .cpp files might + # exist at a later run. at that time we need to skip them, so that we don't add two + # different rules for the same moc file + GET_SOURCE_FILE_PROPERTY(_skip ${_abs_FILE} SKIP_AUTOMOC) + + IF ( NOT _skip AND EXISTS ${_abs_FILE} ) + + FILE(READ ${_abs_FILE} _contents) + + GET_FILENAME_COMPONENT(_abs_PATH ${_abs_FILE} PATH) + + STRING(REGEX MATCHALL "# *include +[^ ]+\\.moc[\">]" _match "${_contents}") + IF(_match) + FOREACH (_current_MOC_INC ${_match}) + STRING(REGEX MATCH "[^ <\"]+\\.moc" _current_MOC "${_current_MOC_INC}") + + GET_FILENAME_COMPONENT(_basename ${_current_MOC} NAME_WE) + IF(EXISTS ${_abs_PATH}/${_basename}.hpp) + SET(_header ${_abs_PATH}/${_basename}.hpp) + ELSE(EXISTS ${_abs_PATH}/${_basename}.hpp) + SET(_header ${_abs_PATH}/${_basename}.h) + ENDIF(EXISTS ${_abs_PATH}/${_basename}.hpp) + SET(_moc ${CMAKE_CURRENT_BINARY_DIR}/${_current_MOC}) + QT4_CREATE_MOC_COMMAND(${_header} ${_moc} "${_moc_INCS}" "") + MACRO_ADD_FILE_DEPENDENCIES(${_abs_FILE} ${_moc}) + ENDFOREACH (_current_MOC_INC) + ENDIF(_match) + ENDIF ( NOT _skip AND EXISTS ${_abs_FILE} ) + ENDFOREACH (_current_FILE) +ENDMACRO(QT4_AUTOMOC) + + +MACRO(QT4_CREATE_TRANSLATION _qm_files) + QT4_EXTRACT_OPTIONS(_lupdate_files _lupdate_options ${ARGN}) + SET(_my_sources) + SET(_my_dirs) + SET(_my_tsfiles) + SET(_ts_pro) + FOREACH (_file ${_lupdate_files}) + GET_FILENAME_COMPONENT(_ext ${_file} EXT) + GET_FILENAME_COMPONENT(_abs_FILE ${_file} ABSOLUTE) + IF(_ext MATCHES "ts") + LIST(APPEND _my_tsfiles ${_abs_FILE}) + ELSE(_ext MATCHES "ts") + IF(NOT _ext) + LIST(APPEND _my_dirs ${_abs_FILE}) + ELSE(NOT _ext) + LIST(APPEND _my_sources ${_abs_FILE}) + ENDIF(NOT _ext) + ENDIF(_ext MATCHES "ts") + ENDFOREACH(_file) + FOREACH(_ts_file ${_my_tsfiles}) + IF(_my_sources) + # make a .pro file to call lupdate on, so we don't make our commands too + # long for some systems + GET_FILENAME_COMPONENT(_ts_name ${_ts_file} NAME_WE) + SET(_ts_pro ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_ts_name}_lupdate.pro) + SET(_pro_srcs) + FOREACH(_pro_src ${_my_sources}) + SET(_pro_srcs "${_pro_srcs} \"${_pro_src}\"") + ENDFOREACH(_pro_src ${_my_sources}) + FILE(WRITE ${_ts_pro} "SOURCES = ${_pro_srcs}") + ENDIF(_my_sources) + ADD_CUSTOM_COMMAND(OUTPUT ${_ts_file} + COMMAND ${QT_LUPDATE_EXECUTABLE} + ARGS ${_lupdate_options} ${_ts_pro} ${_my_dirs} -ts ${_ts_file} + DEPENDS ${_my_sources} ${_ts_pro}) + ENDFOREACH(_ts_file) + QT4_ADD_TRANSLATION(${_qm_files} ${_my_tsfiles}) +ENDMACRO(QT4_CREATE_TRANSLATION) + + +MACRO(QT4_ADD_TRANSLATION _qm_files) + FOREACH (_current_FILE ${ARGN}) + GET_FILENAME_COMPONENT(_abs_FILE ${_current_FILE} ABSOLUTE) + GET_FILENAME_COMPONENT(qm ${_abs_FILE} NAME_WE) + GET_SOURCE_FILE_PROPERTY(output_location ${_abs_FILE} OUTPUT_LOCATION) + IF(output_location) + FILE(MAKE_DIRECTORY "${output_location}") + SET(qm "${output_location}/${qm}.qm") + ELSE(output_location) + SET(qm "${CMAKE_CURRENT_BINARY_DIR}/${qm}.qm") + ENDIF(output_location) + + ADD_CUSTOM_COMMAND(OUTPUT ${qm} + COMMAND ${QT_LRELEASE_EXECUTABLE} + ARGS ${_abs_FILE} -qm ${qm} + DEPENDS ${_abs_FILE} + ) + SET(${_qm_files} ${${_qm_files}} ${qm}) + ENDFOREACH (_current_FILE) +ENDMACRO(QT4_ADD_TRANSLATION) diff --git a/libbluedevil/qt4.tag b/libbluedevil/qt4.tag new file mode 100644 index 00000000..4f1a04b3 --- /dev/null +++ b/libbluedevil/qt4.tag @@ -0,0 +1,85910 @@ + + + Q3Accel + q3accel.html + + Q3Accel + Q3Accel + ( QWidget * parent, const char * name = 0 ) + + + Q3Accel + Q3Accel-2 + ( QWidget * watch, QObject * parent, const char * name = 0 ) + + + activated + activated + ( int id ) + + + activatedAmbiguously + activatedAmbiguously + ( int id ) + + + clear + clear + () + + + connectItem + connectItem + ( int id, const QObject * receiver, const char * member ) + + + count + count + () + + + disconnectItem + disconnectItem + ( int id, const QObject * receiver, const char * member ) + + + findKey + findKey + ( const QKeySequence & key ) + + + insertItem + insertItem + ( const QKeySequence & key, int id = -1 ) + + + isEnabled + isEnabled + () + + + isItemEnabled + isItemEnabled + ( int id ) + + + key + key + ( int id ) + + + removeItem + removeItem + ( int id ) + + + setEnabled + setEnabled + ( bool enable ) + + + setItemEnabled + setItemEnabled + ( int id, bool enable ) + + + setWhatsThis + setWhatsThis + ( int id, const QString & text ) + + + shortcutKey + shortcutKey + ( const QString & str ) + + + whatsThis + whatsThis + ( int id ) + + + + Q3Action + q3action.html + + Q3Action + Q3Action + ( QObject * parent, const char * name = 0 ) + + + Q3Action + Q3Action-2 + ( const QString & menuText, QKeySequence accel, QObject * parent, const char * name = 0 ) + + + Q3Action + Q3Action-3 + ( const QIcon & icon, const QString & menuText, QKeySequence accel, QObject * parent, const char * name = 0 ) + + + Q3Action + Q3Action-4 + ( const QString & text, const QIcon & icon, const QString & menuText, QKeySequence accel, QObject * parent, const char * name = 0, bool toggle = false ) + + + Q3Action + Q3Action-5 + ( const QString & text, const QString & menuText, QKeySequence accel, QObject * parent, const char * name = 0, bool toggle = false ) + + + Q3Action + Q3Action-6 + ( QObject * parent, const char * name, bool toggle ) + + + activate + activate + () + + + activated + activated + () + + + addTo + addTo + ( QWidget * w ) + + + addedTo + addedTo + ( QWidget * actionWidget, QWidget * container ) + + + addedTo + addedTo-2 + ( int index, Q3PopupMenu * menu ) + + + removeFrom + removeFrom + ( QWidget * w ) + + + setDisabled + setDisabled + ( bool disable ) + + + toggle + toggle + () + + + toggled + toggled + ( bool on ) + + + + Q3ActionGroup + q3actiongroup.html + + Q3ActionGroup + Q3ActionGroup + ( QObject * parent, const char * name = 0 ) + + + Q3ActionGroup + Q3ActionGroup-2 + ( QObject * parent, const char * name, bool exclusive ) + + + activated + activated + ( Q3Action * action ) + + + add + add + ( Q3Action * action ) + + + addSeparator + addSeparator + () + + + addTo + addTo + ( QWidget * w ) + + + addedTo + addedTo + ( QWidget * actionWidget, QWidget * container, Q3Action * a ) + + + addedTo + addedTo-2 + ( int index, Q3PopupMenu * menu, Q3Action * a ) + + + insert + insert + ( Q3Action * action ) + + + selected + selected + ( Q3Action * action ) + + + + Q3AsciiCache + q3asciicache.html + + Q3AsciiCache + Q3AsciiCache + ( int maxCost = 100, int size = 17, bool caseSensitive = true, bool copyKeys = true ) + + + clear + clear + () + + + count + count + () + + + find + find + ( const char * k, bool ref = true ) + + + insert + insert + ( const char * k, const type * d, int c = 1, int p = 0 ) + + + isEmpty + isEmpty + () + + + maxCost + maxCost + () + + + remove + remove + ( const char * k ) + + + setMaxCost + setMaxCost + ( int m ) + + + size + size + () + + + statistics + statistics + () + + + take + take + ( const char * k ) + + + totalCost + totalCost + () + + + operator[] + operator-5b-5d + ( const char * k ) + + + + Q3AsciiCacheIterator + q3asciicacheiterator.html + + Q3AsciiCacheIterator + Q3AsciiCacheIterator + ( const Q3AsciiCache<type> & cache ) + + + Q3AsciiCacheIterator + Q3AsciiCacheIterator-2 + ( const Q3AsciiCacheIterator<type> & ci ) + + + atFirst + atFirst + () + + + atLast + atLast + () + + + count + count + () + + + current + current + () + + + currentKey + currentKey + () + + + isEmpty + isEmpty + () + + + toFirst + toFirst + () + + + toLast + toLast + () + + + operator + operator-type--2a + type *() + + + operator() + operator-28-29 + () + + + operator++ + operator-2b-2b + () + + + operator+= + operator-2b-eq + ( uint jump ) + + + operator-- + operator-- + () + + + operator-= + operator--eq + ( uint jump ) + + + operator= + operator-eq + ( const Q3AsciiCacheIterator<type> & ci ) + + + + Q3AsciiDict + q3asciidict.html + + Q3AsciiDict + Q3AsciiDict + ( int size = 17, bool caseSensitive = true, bool copyKeys = true ) + + + Q3AsciiDict + Q3AsciiDict-2 + ( const Q3AsciiDict<type> & dict ) + + + clear + clear + () + + + count + count + () + + + find + find + ( const char * key ) + + + insert + insert + ( const char * key, const type * item ) + + + isEmpty + isEmpty + () + + + read + read + ( QDataStream & s, Q3PtrCollection::Item & item ) + + + remove + remove + ( const char * key ) + + + replace + replace + ( const char * key, const type * item ) + + + resize + resize + ( uint newsize ) + + + size + size + () + + + statistics + statistics + () + + + take + take + ( const char * key ) + + + write + write + ( QDataStream & s, Q3PtrCollection::Item item ) + + + operator= + operator-eq + ( const Q3AsciiDict<type> & dict ) + + + operator[] + operator-5b-5d + ( const char * key ) + + + + Q3AsciiDictIterator + q3asciidictiterator.html + + Q3AsciiDictIterator + Q3AsciiDictIterator + ( const Q3AsciiDict<type> & dict ) + + + count + count + () + + + current + current + () + + + currentKey + currentKey + () + + + isEmpty + isEmpty + () + + + toFirst + toFirst + () + + + operator + operator-type--2a + type *() + + + operator() + operator-28-29 + () + + + operator++ + operator-2b-2b + () + + + operator+= + operator-2b-eq + ( uint jump ) + + + + Q3Button + q3button.html + + Q3Button + Q3Button + ( QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + drawButton + drawButton + ( QPainter * painter ) + + + drawButtonLabel + drawButtonLabel + ( QPainter * painter ) + + + paintEvent + paintEvent + ( QPaintEvent * event ) + + + + Q3ButtonGroup + q3buttongroup.html + + Q3ButtonGroup + Q3ButtonGroup + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3ButtonGroup + Q3ButtonGroup-2 + ( const QString & title, QWidget * parent = 0, const char * name = 0 ) + + + Q3ButtonGroup + Q3ButtonGroup-3 + ( int strips, Qt::Orientation orientation, QWidget * parent = 0, const char * name = 0 ) + + + Q3ButtonGroup + Q3ButtonGroup-4 + ( int strips, Qt::Orientation orientation, const QString & title, QWidget * parent = 0, const char * name = 0 ) + + + clicked + clicked + ( int id ) + + + count + count + () + + + find + find + ( int id ) + + + id + id + ( QAbstractButton * button ) + + + insert + insert + ( QAbstractButton * button, int id = -1 ) + + + pressed + pressed + ( int id ) + + + released + released + ( int id ) + + + remove + remove + ( QAbstractButton * button ) + + + selected + selected + () + + + + Q3Cache + q3cache.html + + Q3Cache + Q3Cache + ( int maxCost = 100, int size = 17, bool caseSensitive = true ) + + + clear + clear + () + + + count + count + () + + + find + find + ( const QString & k, bool ref = true ) + + + insert + insert + ( const QString & k, const type * d, int c = 1, int p = 0 ) + + + isEmpty + isEmpty + () + + + maxCost + maxCost + () + + + remove + remove + ( const QString & k ) + + + setMaxCost + setMaxCost + ( int m ) + + + size + size + () + + + statistics + statistics + () + + + take + take + ( const QString & k ) + + + totalCost + totalCost + () + + + operator[] + operator-5b-5d + ( const QString & k ) + + + + Q3CacheIterator + q3cacheiterator.html + + Q3CacheIterator + Q3CacheIterator + ( const Q3Cache<type> & cache ) + + + Q3CacheIterator + Q3CacheIterator-2 + ( const Q3CacheIterator<type> & ci ) + + + atFirst + atFirst + () + + + atLast + atLast + () + + + count + count + () + + + current + current + () + + + currentKey + currentKey + () + + + isEmpty + isEmpty + () + + + toFirst + toFirst + () + + + toLast + toLast + () + + + operator + operator-type--2a + type *() + + + operator() + operator-28-29 + () + + + operator++ + operator-2b-2b + () + + + operator+= + operator-2b-eq + ( uint jump ) + + + operator-- + operator-- + () + + + operator-= + operator--eq + ( uint jump ) + + + operator= + operator-eq + ( const Q3CacheIterator<type> & ci ) + + + + Q3Canvas + q3canvas.html + + Q3Canvas + Q3Canvas + ( QObject * parent = 0, const char * name = 0 ) + + + Q3Canvas + Q3Canvas-2 + ( int w, int h ) + + + Q3Canvas + Q3Canvas-3 + ( QPixmap p, int h, int v, int tilewidth, int tileheight ) + + + advance + advance + () + + + allItems + allItems + () + + + backgroundColor + backgroundColor + () + + + backgroundPixmap + backgroundPixmap + () + + + chunkSize + chunkSize + () + + + collisions + collisions + ( const QPoint & p ) + + + collisions + collisions-2 + ( const QRect & r ) + + + collisions + collisions-3 + ( const Q3PointArray & chunklist, const Q3CanvasItem * item, bool exact ) + + + drawArea + drawArea + ( const QRect & clip, QPainter * painter, bool dbuf = false ) + + + drawBackground + drawBackground + ( QPainter & painter, const QRect & clip ) + + + drawForeground + drawForeground + ( QPainter & painter, const QRect & clip ) + + + height + height + () + + + onCanvas + onCanvas + ( int x, int y ) + + + onCanvas + onCanvas-2 + ( const QPoint & p ) + + + rect + rect + () + + + resize + resize + ( int w, int h ) + + + resized + resized + () + + + retune + retune + ( int chunksze, int mxclusters = 100 ) + + + setAdvancePeriod + setAdvancePeriod + ( int ms ) + + + setAllChanged + setAllChanged + () + + + setBackgroundColor + setBackgroundColor + ( const QColor & c ) + + + setBackgroundPixmap + setBackgroundPixmap + ( const QPixmap & p ) + + + setChanged + setChanged + ( const QRect & area ) + + + setDoubleBuffering + setDoubleBuffering + ( bool y ) + + + setTile + setTile + ( int x, int y, int tilenum ) + + + setTiles + setTiles + ( QPixmap p, int h, int v, int tilewidth, int tileheight ) + + + setUnchanged + setUnchanged + ( const QRect & area ) + + + setUpdatePeriod + setUpdatePeriod + ( int ms ) + + + size + size + () + + + tile + tile + ( int x, int y ) + + + tileHeight + tileHeight + () + + + tileWidth + tileWidth + () + + + tilesHorizontally + tilesHorizontally + () + + + tilesVertically + tilesVertically + () + + + update + update + () + + + validChunk + validChunk + ( int x, int y ) + + + validChunk + validChunk-2 + ( const QPoint & p ) + + + width + width + () + + + + Q3CanvasEllipse + q3canvasellipse.html + + Q3CanvasEllipse + Q3CanvasEllipse + ( Q3Canvas * canvas ) + + + Q3CanvasEllipse + Q3CanvasEllipse-2 + ( int width, int height, Q3Canvas * canvas ) + + + Q3CanvasEllipse + Q3CanvasEllipse-3 + ( int width, int height, int startangle, int angle, Q3Canvas * canvas ) + + + angleLength + angleLength + () + + + angleStart + angleStart + () + + + drawShape + drawShape + ( QPainter & p ) + + + height + height + () + + + rtti + rtti + () + + + setAngles + setAngles + ( int start, int length ) + + + setSize + setSize + ( int width, int height ) + + + width + width + () + + + active + active + () + + + enabled + enabled + () + + + selected + selected + () + + + visible + visible + () + + + + Q3CanvasItem + q3canvasitem.html + + RttiValues + RttiValues-enum + + + + Q3CanvasItem + Q3CanvasItem + ( Q3Canvas * canvas ) + + + advance + advance + ( int phase ) + + + animated + animated + () + + + boundingRect + boundingRect + () + + + boundingRectAdvanced + boundingRectAdvanced + () + + + canvas + canvas + () + + + collidesWith + collidesWith + ( const Q3CanvasItem * other ) + + + collisions + collisions + ( bool exact ) + + + draw + draw + ( QPainter & painter ) + + + hide + hide + () + + + isActive + isActive + () + + + isEnabled + isEnabled + () + + + isSelected + isSelected + () + + + isVisible + isVisible + () + + + move + move + ( double x, double y ) + + + moveBy + moveBy + ( double dx, double dy ) + + + rtti + rtti + () + + + setActive + setActive + ( bool yes ) + + + setAnimated + setAnimated + ( bool y ) + + + setCanvas + setCanvas + ( Q3Canvas * c ) + + + setEnabled + setEnabled + ( bool yes ) + + + setSelected + setSelected + ( bool yes ) + + + setVelocity + setVelocity + ( double vx, double vy ) + + + setVisible + setVisible + ( bool yes ) + + + setX + setX + ( double x ) + + + setXVelocity + setXVelocity + ( double vx ) + + + setY + setY + ( double y ) + + + setYVelocity + setYVelocity + ( double vy ) + + + setZ + setZ + ( double z ) + + + show + show + () + + + update + update + () + + + x + x + () + + + xVelocity + xVelocity + () + + + y + y + () + + + yVelocity + yVelocity + () + + + z + z + () + + + + Q3CanvasItemList + q3canvasitemlist.html + + operator+ + operator-2b + ( const Q3CanvasItemList & l ) + + + + Q3CanvasLine + q3canvasline.html + + Q3CanvasLine + Q3CanvasLine + ( Q3Canvas * canvas ) + + + endPoint + endPoint + () + + + rtti + rtti + () + + + setPoints + setPoints + ( int xa, int ya, int xb, int yb ) + + + startPoint + startPoint + () + + + + Q3CanvasPixmap + q3canvaspixmap.html + + Q3CanvasPixmap + Q3CanvasPixmap + ( const QString & datafilename ) + + + Q3CanvasPixmap + Q3CanvasPixmap-2 + ( const QImage & image ) + + + Q3CanvasPixmap + Q3CanvasPixmap-3 + ( const QPixmap & pm, const QPoint & offset ) + + + offsetX + offsetX + () + + + offsetY + offsetY + () + + + setOffset + setOffset + ( int x, int y ) + + + Q3CanvasPixmapArray + Q3CanvasPixmapArray-3 + ( Q3PtrList<QPixmap> list, Q3PtrList<QPoint> hotspots ) + + + operator! + operator-not + () + + + + Q3CanvasPixmapArray + q3canvaspixmaparray.html + + Q3CanvasPixmapArray + Q3CanvasPixmapArray + () + + + Q3CanvasPixmapArray + Q3CanvasPixmapArray-2 + ( const QString & datafilenamepattern, int fc = 0 ) + + + Q3CanvasPixmapArray + Q3CanvasPixmapArray-4 + ( Q3ValueList<QPixmap> list, Q3PointArray hotspots = Q3PointArray() + + + count + count + () + + + image + image + ( int i ) + + + isValid + isValid + () + + + readCollisionMasks + readCollisionMasks + ( const QString & filename ) + + + readPixmaps + readPixmaps + ( const QString & filenamepattern, int fc = 0 ) + + + setImage + setImage + ( int i, Q3CanvasPixmap * p ) + + + + Q3CanvasPolygon + q3canvaspolygon.html + + Q3CanvasPolygon + Q3CanvasPolygon + ( Q3Canvas * canvas ) + + + areaPoints + areaPoints + () + + + drawShape + drawShape + ( QPainter & p ) + + + points + points + () + + + rtti + rtti + () + + + setPoints + setPoints + ( Q3PointArray pa ) + + + + Q3CanvasPolygonalItem + q3canvaspolygonalitem.html + + Q3CanvasPolygonalItem + Q3CanvasPolygonalItem + ( Q3Canvas * canvas ) + + + areaPoints + areaPoints + () + + + areaPointsAdvanced + areaPointsAdvanced + () + + + boundingRect + boundingRect + () + + + brush + brush + () + + + draw + draw + ( QPainter & p ) + + + drawShape + drawShape + ( QPainter & p ) + + + invalidate + invalidate + () + + + isValid + isValid + () + + + pen + pen + () + + + rtti + rtti + () + + + setBrush + setBrush + ( QBrush b ) + + + setPen + setPen + ( QPen p ) + + + setWinding + setWinding + ( bool enable ) + + + winding + winding + () + + + + Q3CanvasRectangle + q3canvasrectangle.html + + Q3CanvasRectangle + Q3CanvasRectangle + ( Q3Canvas * canvas ) + + + Q3CanvasRectangle + Q3CanvasRectangle-2 + ( const QRect & r, Q3Canvas * canvas ) + + + Q3CanvasRectangle + Q3CanvasRectangle-3 + ( int x, int y, int width, int height, Q3Canvas * canvas ) + + + chunks + chunks + () + + + drawShape + drawShape + ( QPainter & p ) + + + height + height + () + + + rect + rect + () + + + rtti + rtti + () + + + setSize + setSize + ( int width, int height ) + + + size + size + () + + + width + width + () + + + + Q3CanvasSpline + q3canvasspline.html + + Q3CanvasSpline + Q3CanvasSpline + ( Q3Canvas * canvas ) + + + closed + closed + () + + + controlPoints + controlPoints + () + + + rtti + rtti + () + + + setControlPoints + setControlPoints + ( Q3PointArray ctrl, bool close = true ) + + + + Q3CanvasSprite + q3canvassprite.html + + FrameAnimationType + FrameAnimationType-enum + + + + Q3CanvasSprite + Q3CanvasSprite + ( Q3CanvasPixmapArray * a, Q3Canvas * canvas ) + + + advance + advance + ( int phase ) + + + bottomEdge + bottomEdge + () + + + bottomEdge + bottomEdge-2 + ( int ny ) + + + boundingRect + boundingRect + () + + + draw + draw + ( QPainter & painter ) + + + frame + frame + () + + + frameCount + frameCount + () + + + height + height + () + + + image + image + () + + + image + image-2 + ( int f ) + + + imageAdvanced + imageAdvanced + () + + + leftEdge + leftEdge + () + + + leftEdge + leftEdge-2 + ( int nx ) + + + move + move + ( double x, double y ) + + + move + move-2 + ( double nx, double ny, int nf ) + + + rightEdge + rightEdge + () + + + rightEdge + rightEdge-2 + ( int nx ) + + + rtti + rtti + () + + + setFrame + setFrame + ( int f ) + + + setFrameAnimation + setFrameAnimation + ( FrameAnimationType type = Cycle, int step = 1, int state = 0 ) + + + setSequence + setSequence + ( Q3CanvasPixmapArray * a ) + + + topEdge + topEdge + () + + + topEdge + topEdge-2 + ( int ny ) + + + width + width + () + + + + Q3CanvasText + q3canvastext.html + + Q3CanvasText + Q3CanvasText + ( Q3Canvas * canvas ) + + + Q3CanvasText + Q3CanvasText-2 + ( const QString & t, Q3Canvas * canvas ) + + + Q3CanvasText + Q3CanvasText-3 + ( const QString & t, QFont f, Q3Canvas * canvas ) + + + boundingRect + boundingRect + () + + + color + color + () + + + draw + draw + ( QPainter & painter ) + + + font + font + () + + + rtti + rtti + () + + + setColor + setColor + ( const QColor & c ) + + + setFont + setFont + ( const QFont & f ) + + + setText + setText + ( const QString & t ) + + + setTextFlags + setTextFlags + ( int f ) + + + text + text + () + + + textFlags + textFlags + () + + + + Q3CanvasView + q3canvasview.html + + Q3CanvasView + Q3CanvasView + ( QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + Q3CanvasView + Q3CanvasView-2 + ( Q3Canvas * canvas, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + canvas + canvas + () + + + drawContents + drawContents + ( QPainter * p, int cx, int cy, int cw, int ch ) + + + inverseWorldMatrix + inverseWorldMatrix + () + + + setCanvas + setCanvas + ( Q3Canvas * canvas ) + + + setWorldMatrix + setWorldMatrix + ( const QMatrix & wm ) + + + sizeHint + sizeHint + () + + + worldMatrix + worldMatrix + () + + + + Q3CheckListItem + q3checklistitem.html + + ToggleState + ToggleState-enum + + + + Type + Type-enum + + + + Q3CheckListItem + Q3CheckListItem + ( Q3CheckListItem * parent, const QString & text, Type tt = RadioButtonController ) + + + Q3CheckListItem + Q3CheckListItem-2 + ( Q3CheckListItem * parent, Q3ListViewItem * after, const QString & text, Type tt = RadioButtonController ) + + + Q3CheckListItem + Q3CheckListItem-3 + ( Q3ListViewItem * parent, const QString & text, Type tt = RadioButtonController ) + + + Q3CheckListItem + Q3CheckListItem-4 + ( Q3ListViewItem * parent, Q3ListViewItem * after, const QString & text, Type tt = RadioButtonController ) + + + Q3CheckListItem + Q3CheckListItem-5 + ( Q3ListView * parent, const QString & text, Type tt = RadioButtonController ) + + + Q3CheckListItem + Q3CheckListItem-6 + ( Q3ListView * parent, Q3ListViewItem * after, const QString & text, Type tt = RadioButtonController ) + + + Q3CheckListItem + Q3CheckListItem-7 + ( Q3ListViewItem * parent, const QString & text, const QPixmap & p ) + + + Q3CheckListItem + Q3CheckListItem-8 + ( Q3ListView * parent, const QString & text, const QPixmap & p ) + + + activate + activate + () + + + isOn + isOn + () + + + isTristate + isTristate + () + + + paintCell + paintCell + ( QPainter * p, const QColorGroup & cg, int column, int width, int align ) + + + paintFocus + paintFocus + ( QPainter * p, const QColorGroup & cg, const QRect & r ) + + + rtti + rtti + () + + + setOn + setOn + ( bool b ) + + + setState + setState + ( ToggleState s ) + + + setTristate + setTristate + ( bool b ) + + + state + state + () + + + stateChange + stateChange + ( bool b ) + + + text + text + () + + + turnOffChild + turnOffChild + () + + + type + type + () + + + + Q3CheckTableItem + q3checktableitem.html + + Q3CheckTableItem + Q3CheckTableItem + ( Q3Table * table, const QString & txt ) + + + isChecked + isChecked + () + + + rtti + rtti + () + + + setChecked + setChecked + ( bool b ) + + + + Q3ColorDrag + q3colordrag.html + + Q3ColorDrag + Q3ColorDrag + ( const QColor & col, QWidget * dragsource = 0, const char * name = 0 ) + + + Q3ColorDrag + Q3ColorDrag-2 + ( QWidget * dragsource = 0, const char * name = 0 ) + + + canDecode + canDecode + ( QMimeSource * source ) + + + decode + decode + ( QMimeSource * source, QColor & color ) + + + setColor + setColor + ( const QColor & color ) + + + + Q3ComboBox + q3combobox.html + + Policy + Policy-enum + + + + Q3ComboBox + Q3ComboBox + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3ComboBox + Q3ComboBox-2 + ( bool rw, QWidget * parent = 0, const char * name = 0 ) + + + activated + activated + ( int index ) + + + activated + activated-2 + ( const QString & string ) + + + autoResize + autoResize + () + + + changeItem + changeItem + ( const QString & t, int index ) + + + changeItem + changeItem-2 + ( const QPixmap & im, int index ) + + + changeItem + changeItem-3 + ( const QPixmap & im, const QString & t, int index ) + + + clear + clear + () + + + clearEdit + clearEdit + () + + + clearValidator + clearValidator + () + + + hide + hide + () + + + highlighted + highlighted + ( int index ) + + + highlighted + highlighted-2 + ( const QString & string ) + + + insertItem + insertItem + ( const QString & t, int index = -1 ) + + + insertItem + insertItem-2 + ( const QPixmap & pixmap, int index = -1 ) + + + insertItem + insertItem-3 + ( const QPixmap & pixmap, const QString & text, int index = -1 ) + + + insertStrList + insertStrList + ( const char ** strings, int numStrings = -1, int index = -1 ) + + + insertStrList + insertStrList-2 + ( const Q3StrList & list, int index = -1 ) + + + insertStrList + insertStrList-3 + ( const Q3StrList * list, int index = -1 ) + + + insertStringList + insertStringList + ( const QStringList & list, int index = -1 ) + + + lineEdit + lineEdit + () + + + listBox + listBox + () + + + pixmap + pixmap + ( int index ) + + + popup + popup + () + + + removeItem + removeItem + ( int index ) + + + setAutoResize + setAutoResize + ( bool enable ) + + + setEditText + setEditText + ( const QString & newText ) + + + setEnabled + setEnabled + ( bool enable ) + + + setFont + setFont + ( const QFont & font ) + + + setLineEdit + setLineEdit + ( QLineEdit * edit ) + + + setListBox + setListBox + ( Q3ListBox * newListBox ) + + + setPalette + setPalette + ( const QPalette & palette ) + + + setValidator + setValidator + ( const QValidator * v ) + + + text + text + ( int index ) + + + textChanged + textChanged + ( const QString & string ) + + + updateMask + updateMask + () + + + validator + validator + () + + + + Q3ComboTableItem + q3combotableitem.html + + Q3ComboTableItem + Q3ComboTableItem + ( Q3Table * table, const QStringList & list, bool editable = false ) + + + count + count + () + + + currentItem + currentItem + () + + + currentText + currentText + () + + + isEditable + isEditable + () + + + rtti + rtti + () + + + setCurrentItem + setCurrentItem + ( int i ) + + + setCurrentItem + setCurrentItem-2 + ( const QString & s ) + + + setEditable + setEditable + ( bool b ) + + + setStringList + setStringList + ( const QStringList & l ) + + + text + text + ( int i ) + + + + Q3CString + q3cstring.html + + Q3CString + Q3CString + () + + + Q3CString + Q3CString-2 + ( int size ) + + + Q3CString + Q3CString-3 + ( const Q3CString & s ) + + + Q3CString + Q3CString-4 + ( const QByteArray & ba ) + + + Q3CString + Q3CString-5 + ( const char * str ) + + + Q3CString + Q3CString-6 + ( const char * str, uint maxsize ) + + + append + append + ( const char * str ) + + + copy + copy + () + + + leftJustify + leftJustify + ( uint width, char fill = ' ', bool truncate = false ) + + + lower + lower + () + + + rightJustify + rightJustify + ( uint width, char fill = ' ', bool truncate = false ) + + + setExpand + setExpand + ( uint index, char c ) + + + setNum + setNum + ( double n, char f = 'g', int prec = 6 ) + + + setNum + setNum-2 + ( long n ) + + + setNum + setNum-3 + ( ulong n ) + + + setNum + setNum-4 + ( int n ) + + + setNum + setNum-5 + ( uint n ) + + + setNum + setNum-6 + ( short n ) + + + setNum + setNum-7 + ( ushort n ) + + + setNum + setNum-8 + ( float n, char f = 'g', int prec = 6 ) + + + setStr + setStr + ( const char * str ) + + + simplifyWhiteSpace + simplifyWhiteSpace + () + + + sprintf + sprintf + ( const char * format, ... ) + + + stripWhiteSpace + stripWhiteSpace + () + + + toDouble + toDouble + ( bool * ok = 0 ) + + + toFloat + toFloat + ( bool * ok = 0 ) + + + toInt + toInt + ( bool * ok = 0 ) + + + toLong + toLong + ( bool * ok = 0 ) + + + toShort + toShort + ( bool * ok = 0 ) + + + toUInt + toUInt + ( bool * ok = 0 ) + + + toULong + toULong + ( bool * ok = 0 ) + + + toUShort + toUShort + ( bool * ok = 0 ) + + + upper + upper + () + + + operator= + operator-eq + ( const Q3CString & s ) + + + operator= + operator-eq-2 + ( const QByteArray & ba ) + + + operator= + operator-eq-3 + ( const char * str ) + + + + Q3DataBrowser + q3databrowser.html + + Boundary + Boundary-enum + + + + Q3DataBrowser + Q3DataBrowser + ( QWidget * parent = 0, const char * name = 0, Qt::WindowFlags fl = 0 ) + + + beforeDelete + beforeDelete + ( QSqlRecord * buf ) + + + beforeInsert + beforeInsert + ( QSqlRecord * buf ) + + + beforeUpdate + beforeUpdate + ( QSqlRecord * buf ) + + + boundary + boundary + () + + + clearValues + clearValues + () + + + Confirm + confirmCancel + Q3DataBrowser::confirmCancel( QSql::Op m ) + + + Confirm + confirmEdit + Q3DataBrowser::confirmEdit( QSql::Op m ) + + + currentChanged + currentChanged + ( const QSqlRecord * record ) + + + currentEdited + currentEdited + () + + + cursorChanged + cursorChanged + ( Q3SqlCursor::Mode mode ) + + + del + del + () + + + deleteCurrent + deleteCurrent + () + + + first + first + () + + + firstRecordAvailable + firstRecordAvailable + ( bool available ) + + + form + form + () + + + handleError + handleError + ( const QSqlError & error ) + + + insert + insert + () + + + insertCurrent + insertCurrent + () + + + last + last + () + + + lastRecordAvailable + lastRecordAvailable + ( bool available ) + + + next + next + () + + + nextRecordAvailable + nextRecordAvailable + ( bool available ) + + + prev + prev + () + + + prevRecordAvailable + prevRecordAvailable + ( bool available ) + + + primeDelete + primeDelete + ( QSqlRecord * buf ) + + + primeInsert + primeInsert + ( QSqlRecord * buf ) + + + primeUpdate + primeUpdate + ( QSqlRecord * buf ) + + + readFields + readFields + () + + + refresh + refresh + () + + + seek + seek + ( int i, bool relative = false ) + + + setForm + setForm + ( Q3SqlForm * form ) + + + setSqlCursor + setSqlCursor + ( Q3SqlCursor * cursor, bool autoDelete = false ) + + + sqlCursor + sqlCursor + () + + + update + update + () + + + updateBoundary + updateBoundary + () + + + updateCurrent + updateCurrent + () + + + writeFields + writeFields + () + + + + Q3DataTable + q3datatable.html + + Refresh + Refresh-enum + + + + DateFormat + dateFormat-prop + + + + Q3DataTable + Q3DataTable + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3DataTable + Q3DataTable-2 + ( Q3SqlCursor * cursor, bool autoPopulate = false, QWidget * parent = 0, const char * name = 0 ) + + + addColumn + addColumn + ( const QString & fieldName, const QString & label = QString() + + + adjustColumn + adjustColumn + ( int col ) + + + autoDelete + autoDelete + () + + + beforeDelete + beforeDelete + ( QSqlRecord * buf ) + + + beforeInsert + beforeInsert + ( QSqlRecord * buf ) + + + beforeUpdate + beforeUpdate + ( QSqlRecord * buf ) + + + beginInsert + beginInsert + () + + + beginUpdate + beginUpdate + ( int row, int col, bool replace ) + + + Confirm + confirmCancel + Q3DataTable::confirmCancel( QSql::Op m ) + + + Confirm + confirmEdit + Q3DataTable::confirmEdit( QSql::Op m ) + + + currentChanged + currentChanged + ( QSqlRecord * record ) + + + currentRecord + currentRecord + () + + + cursorChanged + cursorChanged + ( QSql::Op mode ) + + + deleteCurrent + deleteCurrent + () + + + fieldAlignment + fieldAlignment + ( const QSqlField * field ) + + + find + find + ( const QString & str, bool caseSensitive, bool backwards ) + + + handleError + handleError + ( const QSqlError & e ) + + + indexOf + indexOf + ( uint i ) + + + insertCurrent + insertCurrent + () + + + installEditorFactory + installEditorFactory + ( Q3SqlEditorFactory * f ) + + + installPropertyMap + installPropertyMap + ( Q3SqlPropertyMap * m ) + + + paintField + paintField + ( QPainter * p, const QSqlField * field, const QRect & cr, bool selected ) + + + primeDelete + primeDelete + ( QSqlRecord * buf ) + + + primeInsert + primeInsert + ( QSqlRecord * buf ) + + + primeUpdate + primeUpdate + ( QSqlRecord * buf ) + + + refresh + refresh + ( Refresh mode ) + + + refresh + refresh-2 + () + + + removeColumn + removeColumn + ( int col ) + + + repaintCell + repaintCell + ( int row, int col ) + + + reset + reset + () + + + setAutoDelete + setAutoDelete + ( bool enable ) + + + setColumn + setColumn + ( uint col, const QString & fieldName, const QString & label = QString() + + + setColumnWidth + setColumnWidth + ( int col, int w ) + + + setSize + setSize + ( Q3SqlCursor * sql ) + + + setSqlCursor + setSqlCursor + ( Q3SqlCursor * cursor = 0, bool autoPopulate = false, bool autoDelete = false ) + + + sortAscending + sortAscending + ( int col ) + + + sortColumn + sortColumn + ( int col, bool ascending = true, bool wholeRows = false ) + + + sortDescending + sortDescending + ( int col ) + + + sqlCursor + sqlCursor + () + + + text + text + ( int row, int col ) + + + updateCurrent + updateCurrent + () + + + value + value + ( int row, int col ) + + + + Q3DataView + q3dataview.html + + Q3DataView + Q3DataView + ( QWidget * parent = 0, const char * name = 0, Qt::WindowFlags fl = 0 ) + + + clearValues + clearValues + () + + + form + form + () + + + readFields + readFields + () + + + record + record + () + + + refresh + refresh + ( QSqlRecord * buf ) + + + setForm + setForm + ( Q3SqlForm * form ) + + + setRecord + setRecord + ( QSqlRecord * record ) + + + writeFields + writeFields + () + + + + Q3DateEdit + q3dateedit.html + + Order + Order-enum + + + + Q3DateEdit + Q3DateEdit + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3DateEdit + Q3DateEdit-2 + ( const QDate & date, QWidget * parent = 0, const char * name = 0 ) + + + fix + fix + () + + + sectionFormattedText + sectionFormattedText + ( int sec ) + + + separator + separator + () + + + setDay + setDay + ( int day ) + + + setMonth + setMonth + ( int month ) + + + setRange + setRange + ( const QDate & min, const QDate & max ) + + + setSeparator + setSeparator + ( const QString & s ) + + + setYear + setYear + ( int year ) + + + updateButtons + updateButtons + () + + + valueChanged + valueChanged + ( const QDate & date ) + + + + Q3DateTimeEdit + q3datetimeedit.html + + Q3DateTimeEdit + Q3DateTimeEdit + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3DateTimeEdit + Q3DateTimeEdit-2 + ( const QDateTime & datetime, QWidget * parent = 0, const char * name = 0 ) + + + autoAdvance + autoAdvance + () + + + dateEdit + dateEdit + () + + + setAutoAdvance + setAutoAdvance + ( bool advance ) + + + timeEdit + timeEdit + () + + + valueChanged + valueChanged + ( const QDateTime & datetime ) + + + + Q3DateTimeEditBase + q3datetimeeditbase.html + + + Q3DeepCopy + q3deepcopy.html + + Q3DeepCopy + Q3DeepCopy + () + + + Q3DeepCopy + Q3DeepCopy-2 + ( const T & t ) + + + operator + operator-T + T() + + + operator= + operator-eq + ( const T & t ) + + + + Q3Dict + q3dict.html + + Q3Dict + Q3Dict + ( int size = 17, bool caseSensitive = true ) + + + Q3Dict + Q3Dict-2 + ( const Q3Dict<type> & dict ) + + + clear + clear + () + + + count + count + () + + + find + find + ( const QString & key ) + + + insert + insert + ( const QString & key, const type * item ) + + + isEmpty + isEmpty + () + + + read + read + ( QDataStream & s, Q3PtrCollection::Item & item ) + + + remove + remove + ( const QString & key ) + + + replace + replace + ( const QString & key, const type * item ) + + + resize + resize + ( uint newsize ) + + + size + size + () + + + statistics + statistics + () + + + take + take + ( const QString & key ) + + + write + write + ( QDataStream & s, Q3PtrCollection::Item item ) + + + operator= + operator-eq + ( const Q3Dict<type> & dict ) + + + operator[] + operator-5b-5d + ( const QString & key ) + + + + Q3DictIterator + q3dictiterator.html + + Q3DictIterator + Q3DictIterator + ( const Q3Dict<type> & dict ) + + + count + count + () + + + current + current + () + + + currentKey + currentKey + () + + + isEmpty + isEmpty + () + + + toFirst + toFirst + () + + + operator + operator-type--2a + type *() + + + operator() + operator-28-29 + () + + + operator++ + operator-2b-2b + () + + + + Q3Dns + q3dns.html + + RecordType + RecordType-enum + + + + Q3Dns + Q3Dns + () + + + Q3Dns + Q3Dns-2 + ( const QString & label, RecordType rr = A ) + + + Q3Dns + Q3Dns-3 + ( const QHostAddress & address, RecordType rr = Ptr ) + + + addresses + addresses + () + + + canonicalName + canonicalName + () + + + hostNames + hostNames + () + + + isWorking + isWorking + () + + + label + label + () + + + mailServers + mailServers + () + + + qualifiedNames + qualifiedNames + () + + + recordType + recordType + () + + + resultsReady + resultsReady + () + + + servers + servers + () + + + setLabel + setLabel + ( const QString & label ) + + + setLabel + setLabel-2 + ( const QHostAddress & address ) + + + setRecordType + setRecordType + ( RecordType rr = A ) + + + texts + texts + () + + + + Q3DockArea + q3dockarea.html + + HandlePosition + HandlePosition-enum + + + + Orientation + orientation-prop + + + + Q3DockArea + Q3DockArea + ( Qt::Orientation o, HandlePosition h = Normal, QWidget * parent = 0, const char * name = 0 ) + + + dockWindowList + dockWindowList + () + + + hasDockWindow + hasDockWindow + ( Q3DockWindow * w, int * index = 0 ) + + + isDockWindowAccepted + isDockWindowAccepted + ( Q3DockWindow * dw ) + + + lineUp + lineUp + ( bool keepNewLines ) + + + moveDockWindow + moveDockWindow + ( Q3DockWindow * w, int index = -1 ) + + + moveDockWindow + moveDockWindow-2 + ( Q3DockWindow * w, const QPoint & p, const QRect & r, bool swap ) + + + removeDockWindow + removeDockWindow + ( Q3DockWindow * w, bool makeFloating, bool swap, bool fixNewLines = true ) + + + setAcceptDockWindow + setAcceptDockWindow + ( Q3DockWindow * dw, bool accept ) + + + + Q3DockWindow + q3dockwindow.html + + CloseMode + CloseMode-enum + + + + Place + Place-enum + + + + Q3DockWindow + Q3DockWindow + ( Place p = InDock, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + Q3DockWindow + Q3DockWindow-2 + ( QWidget * parent, const char * name = 0, Qt::WindowFlags f = 0 ) + + + area + area + () + + + boxLayout + boxLayout + () + + + dock + dock + () + + + fixedExtent + fixedExtent + () + + + isCloseEnabled + isCloseEnabled + () + + + isHorizontalStretchable + isHorizontalStretchable + () + + + isVerticalStretchable + isVerticalStretchable + () + + + Orientation + orientation + Q3DockWindow::orientation() + + + orientationChanged + orientationChanged + ( Qt::Orientation o ) + + + placeChanged + placeChanged + ( Q3DockWindow::Place p ) + + + setFixedExtentHeight + setFixedExtentHeight + ( int h ) + + + setFixedExtentWidth + setFixedExtentWidth + ( int w ) + + + setHorizontalStretchable + setHorizontalStretchable + ( bool b ) + + + setOrientation + setOrientation + ( Qt::Orientation o ) + + + setVerticalStretchable + setVerticalStretchable + ( bool b ) + + + setWidget + setWidget + ( QWidget * w ) + + + undock + undock + ( QWidget * widget ) + + + undock + undock-2 + () + + + visibilityChanged + visibilityChanged + ( bool visible ) + + + widget + widget + () + + + windowTitle + windowTitle + () + + + + Q3DragObject + q3dragobject.html + + DragMode + DragMode-enum + + + + Q3DragObject + Q3DragObject + ( QWidget * dragSource = 0, const char * name = 0 ) + + + drag + drag + () + + + drag + drag-2 + ( DragMode mode ) + + + dragCopy + dragCopy + () + + + dragLink + dragLink + () + + + dragMove + dragMove + () + + + pixmap + pixmap + () + + + pixmapHotSpot + pixmapHotSpot + () + + + setPixmap + setPixmap + ( QPixmap pm, const QPoint & hotspot ) + + + setPixmap + setPixmap-2 + ( QPixmap pm ) + + + source + source + () + + + target + target + () + + + + Q3DropSite + q3dropsite.html + + Q3DropSite + Q3DropSite + ( QWidget * self ) + + + + Q3EditorFactory + q3editorfactory.html + + Q3EditorFactory + Q3EditorFactory + ( QObject * parent = 0 ) + + + createEditor + createEditor + ( QWidget * parent, const QVariant & v ) + + + defaultFactory + defaultFactory + () + + + installDefaultFactory + installDefaultFactory + ( Q3EditorFactory * factory ) + + + + Q3FileDialog + q3filedialog.html + + Mode + Mode-enum + + + + PreviewMode + PreviewMode-enum + + + + ViewMode + ViewMode-enum + + + + Q3FileDialog + Q3FileDialog + ( const QString & dirName, const QString & filter = QString() + + + Q3FileDialog + Q3FileDialog-2 + ( QWidget * parent = 0, const char * name = 0, bool modal = false ) + + + addFilter + addFilter + ( const QString & filter ) + + + addLeftWidget + addLeftWidget + ( QWidget * w ) + + + addRightWidget + addRightWidget + ( QWidget * w ) + + + addToolButton + addToolButton + ( QAbstractButton * b, bool separator = false ) + + + addWidgets + addWidgets + ( QLabel * l, QWidget * w, QPushButton * b ) + + + dir + dir + () + + + dirEntered + dirEntered + ( const QString & directory ) + + + fileHighlighted + fileHighlighted + ( const QString & file ) + + + fileSelected + fileSelected + ( const QString & file ) + + + filesSelected + filesSelected + ( const QStringList & files ) + + + filterSelected + filterSelected + ( const QString & filter ) + + + getExistingDirectory + getExistingDirectory + ( const QString & dir = QString() + + + getOpenFileName + getOpenFileName + ( const QString & startWith = QString() + + + getOpenFileNames + getOpenFileNames + ( const QString & filter = QString() + + + getSaveFileName + getSaveFileName + ( const QString & startWith = QString() + + + iconProvider + iconProvider + () + + + rereadDir + rereadDir + () + + + resortDir + resortDir + () + + + selectAll + selectAll + ( bool b ) + + + setContentsPreview + setContentsPreview + ( QWidget * w, Q3FilePreview * preview ) + + + setDir + setDir + ( const QDir & dir ) + + + setDir + setDir-2 + ( const QString & pathstr ) + + + setFilter + setFilter + ( const QString & newFilter ) + + + setFilters + setFilters + ( const QString & filters ) + + + setFilters + setFilters-2 + ( const char ** types ) + + + setFilters + setFilters-3 + ( const QStringList & types ) + + + setIconProvider + setIconProvider + ( Q3FileIconProvider * provider ) + + + setInfoPreview + setInfoPreview + ( QWidget * w, Q3FilePreview * preview ) + + + setSelectedFilter + setSelectedFilter + ( const QString & mask ) + + + setSelectedFilter + setSelectedFilter-2 + ( int n ) + + + setSelection + setSelection + ( const QString & filename ) + + + setUrl + setUrl + ( const Q3UrlOperator & url ) + + + url + url + () + + + + Q3FileIconProvider + q3fileiconprovider.html + + Q3FileIconProvider + Q3FileIconProvider + ( QObject * parent = 0, const char * name = 0 ) + + + pixmap + pixmap + ( const QFileInfo & info ) + + + + Q3FilePreview + q3filepreview.html + + Q3FilePreview + Q3FilePreview + () + + + previewUrl + previewUrl + ( const Q3Url & url ) + + + + Q3Frame + q3frame.html + + Q3Frame + Q3Frame + ( QWidget * parent, const char * name = 0, Qt::WindowFlags f = 0 ) + + + drawContents + drawContents + ( QPainter * painter ) + + + drawFrame + drawFrame + ( QPainter * p ) + + + frameChanged + frameChanged + () + + + frameWidth + frameWidth + () + + + paintEvent + paintEvent + ( QPaintEvent * event ) + + + resizeEvent + resizeEvent + ( QResizeEvent * event ) + + + + Q3Ftp + q3ftp.html + + Command + Command-enum + + + + Error + Error-enum + + + + State + State-enum + + + + Q3Ftp + Q3Ftp + () + + + Q3Ftp + Q3Ftp-2 + ( QObject * parent, const char * name = 0 ) + + + abort + abort + () + + + bytesAvailable + bytesAvailable + () + + + cd + cd + ( const QString & dir ) + + + clearPendingCommands + clearPendingCommands + () + + + close + close + () + + + commandFinished + commandFinished + ( int id, bool error ) + + + commandStarted + commandStarted + ( int id ) + + + connectToHost + connectToHost + ( const QString & host, Q_UINT16 port = 21 ) + + + currentCommand + currentCommand + () + + + currentDevice + currentDevice + () + + + currentId + currentId + () + + + dataTransferProgress + dataTransferProgress + ( int done, int total ) + + + done + done + ( bool error ) + + + error + error + () + + + errorString + errorString + () + + + get + get + ( const QString & file, QIODevice * dev = 0 ) + + + hasPendingCommands + hasPendingCommands + () + + + list + list + ( const QString & dir = QString() + + + listInfo + listInfo + ( const QUrlInfo & i ) + + + login + login + ( const QString & user = QString() + + + mkdir + mkdir + ( const QString & dir ) + + + put + put + ( QIODevice * dev, const QString & file ) + + + put + put-2 + ( const QByteArray & data, const QString & file ) + + + rawCommand + rawCommand + ( const QString & command ) + + + rawCommandReply + rawCommandReply + ( int replyCode, const QString & detail ) + + + readAll + readAll + () + + + readBlock + readBlock + ( char * data, Q_ULONG maxlen ) + + + readyRead + readyRead + () + + + remove + remove + ( const QString & file ) + + + rename + rename + ( const QString & oldname, const QString & newname ) + + + rmdir + rmdir + ( const QString & dir ) + + + state + state + () + + + stateChanged + stateChanged + ( int state ) + + + + Q3Grid + q3grid.html + + Q3Grid + Q3Grid + ( int n, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + Q3Grid + Q3Grid-2 + ( int n, Qt::Orientation orient, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + setSpacing + setSpacing + ( int space ) + + + + Q3GridView + q3gridview.html + + Q3GridView + Q3GridView + ( QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + cellGeometry + cellGeometry + ( int row, int column ) + + + cellRect + cellRect + () + + + columnAt + columnAt + ( int x ) + + + dimensionChange + dimensionChange + ( int oldNumRows, int oldNumCols ) + + + ensureCellVisible + ensureCellVisible + ( int row, int column ) + + + gridSize + gridSize + () + + + paintCell + paintCell + ( QPainter * p, int row, int col ) + + + paintEmptyArea + paintEmptyArea + ( QPainter * p, int cx, int cy, int cw, int ch ) + + + repaintCell + repaintCell + ( int row, int column, bool erase = true ) + + + rowAt + rowAt + ( int y ) + + + updateCell + updateCell + ( int row, int column ) + + + + Q3GroupBox + q3groupbox.html + + Orientation + orientation-prop + + + + Q3GroupBox + Q3GroupBox + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3GroupBox + Q3GroupBox-2 + ( const QString & title, QWidget * parent = 0, const char * name = 0 ) + + + Q3GroupBox + Q3GroupBox-3 + ( int strips, Qt::Orientation orientation, QWidget * parent = 0, const char * name = 0 ) + + + Q3GroupBox + Q3GroupBox-4 + ( int strips, Qt::Orientation orientation, const QString & title, QWidget * parent = 0, const char * name = 0 ) + + + addSpace + addSpace + ( int size ) + + + frameStyle + frameStyle + () + + + insideMargin + insideMargin + () + + + insideSpacing + insideSpacing + () + + + setColumnLayout + setColumnLayout + ( int strips, Qt::Orientation direction ) + + + setFrameStyle + setFrameStyle + ( int style ) + + + setInsideMargin + setInsideMargin + ( int m ) + + + setInsideSpacing + setInsideSpacing + ( int s ) + + + + Q3HBox + q3hbox.html + + Q3HBox + Q3HBox + ( QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + Q3HBox + Q3HBox-2 + ( bool horizontal, QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + setSpacing + setSpacing + ( int space ) + + + setStretchFactor + setStretchFactor + ( QWidget * w, int stretch ) + + + + Q3HButtonGroup + q3hbuttongroup.html + + Q3HButtonGroup + Q3HButtonGroup + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3HButtonGroup + Q3HButtonGroup-2 + ( const QString & title, QWidget * parent = 0, const char * name = 0 ) + + + + Q3Header + q3header.html + + Orientation + orientation-prop + + + + Q3Header + Q3Header + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3Header + Q3Header-2 + ( int n, QWidget * parent = 0, const char * name = 0 ) + + + addLabel + addLabel + ( const QString & s, int size = -1 ) + + + addLabel + addLabel-2 + ( const QIcon & icon, const QString & s, int size = -1 ) + + + adjustHeaderSize + adjustHeaderSize + () + + + cellAt + cellAt + ( int pos ) + + + cellPos + cellPos + ( int i ) + + + cellSize + cellSize + ( int i ) + + + clicked + clicked + ( int section ) + + + headerWidth + headerWidth + () + + + iconSet + iconSet + ( int section ) + + + indexChange + indexChange + ( int section, int fromIndex, int toIndex ) + + + isClickEnabled + isClickEnabled + ( int section = -1 ) + + + isResizeEnabled + isResizeEnabled + ( int section = -1 ) + + + label + label + ( int section ) + + + mapToActual + mapToActual + ( int l ) + + + mapToIndex + mapToIndex + ( int section ) + + + mapToLogical + mapToLogical + ( int a ) + + + mapToSection + mapToSection + ( int index ) + + + moveCell + moveCell + ( int fromIdx, int toIdx ) + + + moveSection + moveSection + ( int section, int toIndex ) + + + moved + moved + ( int fromIndex, int toIndex ) + + + paintSection + paintSection + ( QPainter * p, int index, const QRect & fr ) + + + paintSectionLabel + paintSectionLabel + ( QPainter * p, int index, const QRect & fr ) + + + pressed + pressed + ( int section ) + + + released + released + ( int section ) + + + removeLabel + removeLabel + ( int section ) + + + resizeSection + resizeSection + ( int section, int s ) + + + sRect + sRect + ( int index ) + + + sectionAt + sectionAt + ( int pos ) + + + sectionClicked + sectionClicked + ( int index ) + + + sectionHandleDoubleClicked + sectionHandleDoubleClicked + ( int section ) + + + sectionPos + sectionPos + ( int section ) + + + sectionRect + sectionRect + ( int section ) + + + sectionSize + sectionSize + ( int section ) + + + setCellSize + setCellSize + ( int section, int s ) + + + setClickEnabled + setClickEnabled + ( bool enable, int section = -1 ) + + + setLabel + setLabel + ( int section, const QString & s, int size = -1 ) + + + setLabel + setLabel-2 + ( int section, const QIcon & icon, const QString & s, int size = -1 ) + + + setResizeEnabled + setResizeEnabled + ( bool enable, int section = -1 ) + + + setSortIndicator + setSortIndicator + ( int section, Qt::SortOrder order ) + + + setSortIndicator + setSortIndicator-2 + ( int section, bool ascending = true ) + + + sizeChange + sizeChange + ( int section, int oldSize, int newSize ) + + + SortOrder + sortIndicatorOrder + Q3Header::sortIndicatorOrder() + + + sortIndicatorSection + sortIndicatorSection + () + + + + Q3HGroupBox + q3hgroupbox.html + + Q3HGroupBox + Q3HGroupBox + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3HGroupBox + Q3HGroupBox-2 + ( const QString & title, QWidget * parent = 0, const char * name = 0 ) + + + + Q3Http + q3http.html + + Error + Error-enum + + + + State + State-enum + + + + Q3Http + Q3Http + () + + + Q3Http + Q3Http-2 + ( QObject * parent, const char * name = 0 ) + + + Q3Http + Q3Http-3 + ( const QString & hostname, Q_UINT16 port = 80, QObject * parent = 0, const char * name = 0 ) + + + abort + abort + () + + + bytesAvailable + bytesAvailable + () + + + clearPendingRequests + clearPendingRequests + () + + + closeConnection + closeConnection + () + + + currentDestinationDevice + currentDestinationDevice + () + + + currentId + currentId + () + + + currentRequest + currentRequest + () + + + currentSourceDevice + currentSourceDevice + () + + + dataReadProgress + dataReadProgress + ( int done, int total ) + + + dataSendProgress + dataSendProgress + ( int done, int total ) + + + done + done + ( bool error ) + + + error + error + () + + + errorString + errorString + () + + + get + get + ( const QString & path, QIODevice * to = 0 ) + + + hasPendingRequests + hasPendingRequests + () + + + head + head + ( const QString & path ) + + + post + post + ( const QString & path, QIODevice * data, QIODevice * to = 0 ) + + + post + post-2 + ( const QString & path, const QByteArray & data, QIODevice * to = 0 ) + + + readAll + readAll + () + + + readBlock + readBlock + ( char * data, Q_ULONG maxlen ) + + + readyRead + readyRead + ( const Q3HttpResponseHeader & resp ) + + + request + request + ( const Q3HttpRequestHeader & header, QIODevice * data = 0, QIODevice * to = 0 ) + + + request + request-2 + ( const Q3HttpRequestHeader & header, const QByteArray & data, QIODevice * to = 0 ) + + + requestFinished + requestFinished + ( int id, bool error ) + + + requestStarted + requestStarted + ( int id ) + + + responseHeaderReceived + responseHeaderReceived + ( const Q3HttpResponseHeader & resp ) + + + setHost + setHost + ( const QString & hostname, Q_UINT16 port = 80 ) + + + state + state + () + + + stateChanged + stateChanged + ( int state ) + + + + Q3HttpHeader + q3httpheader.html + + Q3HttpHeader + Q3HttpHeader + () + + + Q3HttpHeader + Q3HttpHeader-2 + ( const Q3HttpHeader & header ) + + + Q3HttpHeader + Q3HttpHeader-3 + ( const QString & str ) + + + contentLength + contentLength + () + + + contentType + contentType + () + + + hasContentLength + hasContentLength + () + + + hasContentType + hasContentType + () + + + hasKey + hasKey + ( const QString & key ) + + + isValid + isValid + () + + + keys + keys + () + + + majorVersion + majorVersion + () + + + minorVersion + minorVersion + () + + + removeValue + removeValue + ( const QString & key ) + + + setContentLength + setContentLength + ( int len ) + + + setContentType + setContentType + ( const QString & type ) + + + setValue + setValue + ( const QString & key, const QString & value ) + + + toString + toString + () + + + value + value + ( const QString & key ) + + + operator= + operator-eq + ( const Q3HttpHeader & h ) + + + + Q3HttpRequestHeader + q3httprequestheader.html + + Q3HttpRequestHeader + Q3HttpRequestHeader + () + + + Q3HttpRequestHeader + Q3HttpRequestHeader-2 + ( const QString & method, const QString & path, int majorVer = 1, int minorVer = 1 ) + + + Q3HttpRequestHeader + Q3HttpRequestHeader-3 + ( const Q3HttpRequestHeader & header ) + + + Q3HttpRequestHeader + Q3HttpRequestHeader-4 + ( const QString & str ) + + + majorVersion + majorVersion + () + + + method + method + () + + + minorVersion + minorVersion + () + + + path + path + () + + + setRequest + setRequest + ( const QString & method, const QString & path, int majorVer = 1, int minorVer = 1 ) + + + + Q3HttpResponseHeader + q3httpresponseheader.html + + Q3HttpResponseHeader + Q3HttpResponseHeader + () + + + Q3HttpResponseHeader + Q3HttpResponseHeader-4 + ( const Q3HttpResponseHeader & header ) + + + majorVersion + majorVersion + () + + + minorVersion + minorVersion + () + + + reasonPhrase + reasonPhrase + () + + + statusCode + statusCode + () + + + + Q3IconDrag + q3icondrag.html + + Q3IconDrag + Q3IconDrag + ( QWidget * dragSource, const char * name = 0 ) + + + append + append + ( const Q3IconDragItem & i, const QRect & pr, const QRect & tr ) + + + canDecode + canDecode + ( QMimeSource * e ) + + + encodedData + encodedData + ( const char * mime ) + + + + Q3IconDragItem + q3icondragitem.html + + Q3IconDragItem + Q3IconDragItem + () + + + data + data + () + + + setData + setData + ( const QByteArray & d ) + + + + Q3IconView + q3iconview.html + + Arrangement + Arrangement-enum + + + + ComparisonFlags + ComparisonFlags-typedef + + + + ItemTextPos + ItemTextPos-enum + + + + ResizeMode + ResizeMode-enum + + + + SelectionMode + SelectionMode-enum + + + + StringComparisonMode + StringComparisonMode-enum + + + + Q3IconView + Q3IconView + ( QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + adjustItems + adjustItems + () + + + arrangeItemsInGrid + arrangeItemsInGrid + ( const QSize & grid, bool update = true ) + + + arrangeItemsInGrid + arrangeItemsInGrid-2 + ( bool update = true ) + + + clear + clear + () + + + clearSelection + clearSelection + () + + + clicked + clicked + ( Q3IconViewItem * item ) + + + clicked + clicked-2 + ( Q3IconViewItem * item, const QPoint & pos ) + + + contextMenuRequested + contextMenuRequested + ( Q3IconViewItem * item, const QPoint & pos ) + + + currentChanged + currentChanged + ( Q3IconViewItem * item ) + + + currentItem + currentItem + () + + + doAutoScroll + doAutoScroll + () + + + doubleClicked + doubleClicked + ( Q3IconViewItem * item ) + + + dragObject + dragObject + () + + + drawBackground + drawBackground + ( QPainter * p, const QRect & r ) + + + drawRubber + drawRubber + ( QPainter * p ) + + + dropped + dropped + ( QDropEvent * e, const Q3ValueList<Q3IconDragItem> & lst ) + + + emitSelectionChanged + emitSelectionChanged + ( Q3IconViewItem * i = 0 ) + + + ensureItemVisible + ensureItemVisible + ( Q3IconViewItem * item ) + + + findFirstVisibleItem + findFirstVisibleItem + ( const QRect & r ) + + + findItem + findItem + ( const QPoint & pos ) + + + findItem + findItem-3 + ( const QString & text, ComparisonFlags compare = BeginsWith | Qt::CaseSensitive ) + + + findLastVisibleItem + findLastVisibleItem + ( const QRect & r ) + + + firstItem + firstItem + () + + + index + index + ( const Q3IconViewItem * item ) + + + insertInGrid + insertInGrid + ( Q3IconViewItem * item ) + + + insertItem + insertItem + ( Q3IconViewItem * item, Q3IconViewItem * after = 0L ) + + + invertSelection + invertSelection + () + + + isRenaming + isRenaming + () + + + itemRenamed + itemRenamed + ( Q3IconViewItem * item, const QString & name ) + + + itemRenamed + itemRenamed-2 + ( Q3IconViewItem * item ) + + + lastItem + lastItem + () + + + makeRowLayout + makeRowLayout + ( Q3IconViewItem * begin, int & y, bool & changed ) + + + mouseButtonClicked + mouseButtonClicked + ( int button, Q3IconViewItem * item, const QPoint & pos ) + + + mouseButtonPressed + mouseButtonPressed + ( int button, Q3IconViewItem * item, const QPoint & pos ) + + + moved + moved + () + + + onItem + onItem + ( Q3IconViewItem * item ) + + + onViewport + onViewport + () + + + pressed + pressed + ( Q3IconViewItem * item ) + + + pressed + pressed-2 + ( Q3IconViewItem * item, const QPoint & pos ) + + + repaintItem + repaintItem + ( Q3IconViewItem * item ) + + + repaintSelectedItems + repaintSelectedItems + () + + + returnPressed + returnPressed + ( Q3IconViewItem * item ) + + + rightButtonClicked + rightButtonClicked + ( Q3IconViewItem * item, const QPoint & pos ) + + + rightButtonPressed + rightButtonPressed + ( Q3IconViewItem * item, const QPoint & pos ) + + + selectAll + selectAll + ( bool select ) + + + selectionChanged + selectionChanged + () + + + selectionChanged + selectionChanged-2 + ( Q3IconViewItem * item ) + + + setCurrentItem + setCurrentItem + ( Q3IconViewItem * item ) + + + setSelected + setSelected + ( Q3IconViewItem * item, bool s, bool cb = false ) + + + setSorting + setSorting + ( bool sort, bool ascending = true ) + + + slotUpdate + slotUpdate + () + + + sort + sort + ( bool ascending = true ) + + + startDrag + startDrag + () + + + takeItem + takeItem + ( Q3IconViewItem * item ) + + + + Q3IconViewItem + q3iconviewitem.html + + Q3IconViewItem + Q3IconViewItem + ( Q3IconView * parent ) + + + Q3IconViewItem + Q3IconViewItem-2 + ( Q3IconView * parent, Q3IconViewItem * after ) + + + Q3IconViewItem + Q3IconViewItem-3 + ( Q3IconView * parent, const QString & text ) + + + Q3IconViewItem + Q3IconViewItem-4 + ( Q3IconView * parent, Q3IconViewItem * after, const QString & text ) + + + Q3IconViewItem + Q3IconViewItem-5 + ( Q3IconView * parent, const QString & text, const QPixmap & icon ) + + + Q3IconViewItem + Q3IconViewItem-6 + ( Q3IconView * parent, Q3IconViewItem * after, const QString & text, const QPixmap & icon ) + + + Q3IconViewItem + Q3IconViewItem-7 + ( Q3IconView * parent, const QString & text, const QPicture & picture ) + + + Q3IconViewItem + Q3IconViewItem-8 + ( Q3IconView * parent, Q3IconViewItem * after, const QString & text, const QPicture & picture ) + + + acceptDrop + acceptDrop + ( const QMimeSource * mime ) + + + calcRect + calcRect + ( const QString & text_ = QString() + + + compare + compare + ( Q3IconViewItem * i ) + + + contains + contains + ( const QPoint & pnt ) + + + dragEnabled + dragEnabled + () + + + dragEntered + dragEntered + () + + + dragLeft + dragLeft + () + + + dropEnabled + dropEnabled + () + + + dropped + dropped + ( QDropEvent * e, const Q3ValueList<Q3IconDragItem> & lst ) + + + height + height + () + + + iconView + iconView + () + + + index + index + () + + + intersects + intersects + ( const QRect & r ) + + + isSelectable + isSelectable + () + + + isSelected + isSelected + () + + + key + key + () + + + move + move + ( int x, int y ) + + + move + move-2 + ( const QPoint & pnt ) + + + moveBy + moveBy + ( int dx, int dy ) + + + moveBy + moveBy-2 + ( const QPoint & pnt ) + + + nextItem + nextItem + () + + + paintFocus + paintFocus + ( QPainter * p, const QColorGroup & cg ) + + + paintItem + paintItem + ( QPainter * p, const QColorGroup & cg ) + + + picture + picture + () + + + pixmap + pixmap + () + + + pixmapRect + pixmapRect + ( bool relative = true ) + + + pos + pos + () + + + prevItem + prevItem + () + + + rect + rect + () + + + removeRenameBox + removeRenameBox + () + + + rename + rename + () + + + renameEnabled + renameEnabled + () + + + repaint + repaint + () + + + rtti + rtti + () + + + setDragEnabled + setDragEnabled + ( bool allow ) + + + setDropEnabled + setDropEnabled + ( bool allow ) + + + setItemRect + setItemRect + ( const QRect & r ) + + + setKey + setKey + ( const QString & k ) + + + setPicture + setPicture + ( const QPicture & icon ) + + + setPixmap + setPixmap + ( const QPixmap & icon ) + + + setPixmap + setPixmap-2 + ( const QPixmap & icon, bool recalc, bool redraw = true ) + + + setPixmapRect + setPixmapRect + ( const QRect & r ) + + + setRenameEnabled + setRenameEnabled + ( bool allow ) + + + setSelectable + setSelectable + ( bool enable ) + + + setSelected + setSelected + ( bool s, bool cb ) + + + setSelected + setSelected-2 + ( bool s ) + + + setText + setText + ( const QString & text ) + + + setText + setText-2 + ( const QString & text, bool recalc, bool redraw = true ) + + + setTextRect + setTextRect + ( const QRect & r ) + + + size + size + () + + + text + text + () + + + textRect + textRect + ( bool relative = true ) + + + width + width + () + + + x + x + () + + + y + y + () + + + + Q3ImageDrag + q3imagedrag.html + + Q3ImageDrag + Q3ImageDrag + ( QImage image, QWidget * dragSource = 0, const char * name = 0 ) + + + Q3ImageDrag + Q3ImageDrag-2 + ( QWidget * dragSource = 0, const char * name = 0 ) + + + canDecode + canDecode + ( const QMimeSource * source ) + + + decode + decode + ( const QMimeSource * source, QImage & image ) + + + decode + decode-2 + ( const QMimeSource * source, QPixmap & pixmap ) + + + setImage + setImage + ( QImage image ) + + + + Q3IntCache + q3intcache.html + + Q3IntCache + Q3IntCache + ( int maxCost = 100, int size = 17 ) + + + clear + clear + () + + + count + count + () + + + find + find + ( long k, bool ref = true ) + + + insert + insert + ( long k, const type * d, int c = 1, int p = 0 ) + + + isEmpty + isEmpty + () + + + maxCost + maxCost + () + + + remove + remove + ( long k ) + + + setMaxCost + setMaxCost + ( int m ) + + + size + size + () + + + statistics + statistics + () + + + take + take + ( long k ) + + + totalCost + totalCost + () + + + operator[] + operator-5b-5d + ( long k ) + + + + Q3IntCacheIterator + q3intcacheiterator.html + + Q3IntCacheIterator + Q3IntCacheIterator + ( const Q3IntCache<type> & cache ) + + + Q3IntCacheIterator + Q3IntCacheIterator-2 + ( const Q3IntCacheIterator<type> & ci ) + + + atFirst + atFirst + () + + + atLast + atLast + () + + + count + count + () + + + current + current + () + + + currentKey + currentKey + () + + + isEmpty + isEmpty + () + + + toFirst + toFirst + () + + + toLast + toLast + () + + + operator + operator-type--2a + type *() + + + operator() + operator-28-29 + () + + + operator++ + operator-2b-2b + () + + + operator+= + operator-2b-eq + ( uint jump ) + + + operator-- + operator-- + () + + + operator-= + operator--eq + ( uint jump ) + + + operator= + operator-eq + ( const Q3IntCacheIterator<type> & ci ) + + + + Q3IntDict + q3intdict.html + + Q3IntDict + Q3IntDict + ( int size = 17 ) + + + Q3IntDict + Q3IntDict-2 + ( const Q3IntDict<type> & dict ) + + + clear + clear + () + + + count + count + () + + + find + find + ( long key ) + + + insert + insert + ( long key, const type * item ) + + + isEmpty + isEmpty + () + + + read + read + ( QDataStream & s, Q3PtrCollection::Item & item ) + + + remove + remove + ( long key ) + + + replace + replace + ( long key, const type * item ) + + + resize + resize + ( uint newsize ) + + + size + size + () + + + statistics + statistics + () + + + take + take + ( long key ) + + + write + write + ( QDataStream & s, Q3PtrCollection::Item item ) + + + operator= + operator-eq + ( const Q3IntDict<type> & dict ) + + + operator[] + operator-5b-5d + ( long key ) + + + + Q3IntDictIterator + q3intdictiterator.html + + Q3IntDictIterator + Q3IntDictIterator + ( const Q3IntDict<type> & dict ) + + + count + count + () + + + current + current + () + + + currentKey + currentKey + () + + + isEmpty + isEmpty + () + + + toFirst + toFirst + () + + + operator + operator-type--2a + type *() + + + operator() + operator-28-29 + () + + + operator++ + operator-2b-2b + () + + + operator+= + operator-2b-eq + ( uint jump ) + + + + Q3ListBox + q3listbox.html + + ComparisonFlags + ComparisonFlags-typedef + + + + LayoutMode + LayoutMode-enum + + + + SelectionMode + SelectionMode-enum + + + + StringComparisonMode + StringComparisonMode-enum + + + + Q3ListBox + Q3ListBox + ( QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + autoBottomScrollBar + autoBottomScrollBar + () + + + autoScroll + autoScroll + () + + + autoScrollBar + autoScrollBar + () + + + autoUpdate + autoUpdate + () + + + bottomScrollBar + bottomScrollBar + () + + + cellHeight + cellHeight + ( int i ) + + + cellHeight + cellHeight-2 + () + + + cellWidth + cellWidth + () + + + cellWidth + cellWidth-2 + ( int i ) + + + centerCurrentItem + centerCurrentItem + () + + + changeItem + changeItem + ( const Q3ListBoxItem * lbi, int index ) + + + changeItem + changeItem-2 + ( const QString & text, int index ) + + + changeItem + changeItem-3 + ( const QPixmap & pixmap, int index ) + + + changeItem + changeItem-4 + ( const QPixmap & pixmap, const QString & text, int index ) + + + clear + clear + () + + + clearSelection + clearSelection + () + + + clicked + clicked + ( Q3ListBoxItem * item ) + + + clicked + clicked-2 + ( Q3ListBoxItem * item, const QPoint & pnt ) + + + contextMenuRequested + contextMenuRequested + ( Q3ListBoxItem * item, const QPoint & pos ) + + + currentChanged + currentChanged + ( Q3ListBoxItem * item ) + + + doLayout + doLayout + () + + + doubleClicked + doubleClicked + ( Q3ListBoxItem * item ) + + + dragSelect + dragSelect + () + + + ensureCurrentVisible + ensureCurrentVisible + () + + + findItem + findItem + ( const QString & text, ComparisonFlags compare = BeginsWith ) + + + findItem + findItem-2 + ( int yPos ) + + + firstItem + firstItem + () + + + highlighted + highlighted + ( int index ) + + + highlighted + highlighted-2 + ( Q3ListBoxItem * item ) + + + highlighted + highlighted-3 + ( const QString & text ) + + + inSort + inSort + ( const Q3ListBoxItem * lbi ) + + + inSort + inSort-2 + ( const QString & text ) + + + index + index + ( const Q3ListBoxItem * lbi ) + + + insertItem + insertItem + ( const Q3ListBoxItem * lbi, int index = -1 ) + + + insertItem + insertItem-2 + ( const Q3ListBoxItem * lbi, const Q3ListBoxItem * after ) + + + insertItem + insertItem-3 + ( const QString & text, int index = -1 ) + + + insertItem + insertItem-4 + ( const QPixmap & pixmap, int index = -1 ) + + + insertItem + insertItem-5 + ( const QPixmap & pixmap, const QString & text, int index = -1 ) + + + insertStrList + insertStrList + ( const char ** strings, int numStrings = -1, int index = -1 ) + + + insertStringList + insertStringList + ( const QStringList & list, int index = -1 ) + + + invertSelection + invertSelection + () + + + isRubberSelecting + isRubberSelecting + () + + + isSelected + isSelected + ( int i ) + + + isSelected + isSelected-2 + ( const Q3ListBoxItem * i ) + + + item + item + ( int index ) + + + itemAt + itemAt + ( const QPoint & p ) + + + itemHeight + itemHeight + ( int index = 0 ) + + + itemRect + itemRect + ( Q3ListBoxItem * item ) + + + itemVisible + itemVisible + ( int index ) + + + itemVisible + itemVisible-2 + ( const Q3ListBoxItem * item ) + + + maxItemWidth + maxItemWidth + () + + + mouseButtonClicked + mouseButtonClicked + ( int button, Q3ListBoxItem * item, const QPoint & pos ) + + + mouseButtonPressed + mouseButtonPressed + ( int button, Q3ListBoxItem * item, const QPoint & pos ) + + + numCols + numCols + () + + + onItem + onItem + ( Q3ListBoxItem * i ) + + + onViewport + onViewport + () + + + paintCell + paintCell + ( QPainter * p, int row, int col ) + + + pixmap + pixmap + ( int index ) + + + pressed + pressed + ( Q3ListBoxItem * item ) + + + pressed + pressed-2 + ( Q3ListBoxItem * item, const QPoint & pnt ) + + + removeItem + removeItem + ( int index ) + + + returnPressed + returnPressed + ( Q3ListBoxItem * item ) + + + rightButtonClicked + rightButtonClicked + ( Q3ListBoxItem * item, const QPoint & point ) + + + rightButtonPressed + rightButtonPressed + ( Q3ListBoxItem * item, const QPoint & point ) + + + scrollBar + scrollBar + () + + + selectAll + selectAll + ( bool select ) + + + selected + selected + ( int index ) + + + selected + selected-2 + ( Q3ListBoxItem * item ) + + + selected + selected-3 + ( const QString & text ) + + + selectedItem + selectedItem + () + + + selectionChanged + selectionChanged + () + + + selectionChanged + selectionChanged-2 + ( Q3ListBoxItem * item ) + + + setAutoBottomScrollBar + setAutoBottomScrollBar + ( bool enable ) + + + setAutoScroll + setAutoScroll + ( bool b ) + + + setAutoScrollBar + setAutoScrollBar + ( bool enable ) + + + setAutoUpdate + setAutoUpdate + ( bool b ) + + + setBottomItem + setBottomItem + ( int index ) + + + setBottomScrollBar + setBottomScrollBar + ( bool enable ) + + + setDragSelect + setDragSelect + ( bool b ) + + + setFixedVisibleLines + setFixedVisibleLines + ( int lines ) + + + setScrollBar + setScrollBar + ( bool enable ) + + + setSelected + setSelected + ( Q3ListBoxItem * item, bool select ) + + + setSelected + setSelected-2 + ( int index, bool select ) + + + setSmoothScrolling + setSmoothScrolling + ( bool b ) + + + smoothScrolling + smoothScrolling + () + + + sort + sort + ( bool ascending = true ) + + + takeItem + takeItem + ( const Q3ListBoxItem * item ) + + + text + text + ( int index ) + + + toggleCurrentItem + toggleCurrentItem + () + + + totalHeight + totalHeight + () + + + totalWidth + totalWidth + () + + + triggerUpdate + triggerUpdate + ( bool doLayout ) + + + updateCellWidth + updateCellWidth + () + + + updateItem + updateItem + ( int index ) + + + updateItem + updateItem-2 + ( Q3ListBoxItem * i ) + + + + Q3ListBoxItem + q3listboxitem.html + + Q3ListBoxItem + Q3ListBoxItem + ( Q3ListBox * listbox = 0 ) + + + Q3ListBoxItem + Q3ListBoxItem-2 + ( Q3ListBox * listbox, Q3ListBoxItem * after ) + + + current + current + () + + + height + height + ( const Q3ListBox * lb ) + + + isCurrent + isCurrent + () + + + isSelectable + isSelectable + () + + + isSelected + isSelected + () + + + listBox + listBox + () + + + next + next + () + + + paint + paint + ( QPainter * p ) + + + pixmap + pixmap + () + + + prev + prev + () + + + rtti + rtti + () + + + selected + selected + () + + + setCustomHighlighting + setCustomHighlighting + ( bool b ) + + + setSelectable + setSelectable + ( bool b ) + + + setText + setText + ( const QString & text ) + + + text + text + () + + + width + width + ( const Q3ListBox * lb ) + + + + Q3ListBoxPixmap + q3listboxpixmap.html + + Q3ListBoxPixmap + Q3ListBoxPixmap + ( Q3ListBox * listbox, const QPixmap & pixmap ) + + + Q3ListBoxPixmap + Q3ListBoxPixmap-2 + ( const QPixmap & pixmap ) + + + Q3ListBoxPixmap + Q3ListBoxPixmap-3 + ( Q3ListBox * listbox, const QPixmap & pixmap, Q3ListBoxItem * after ) + + + Q3ListBoxPixmap + Q3ListBoxPixmap-4 + ( Q3ListBox * listbox, const QPixmap & pix, const QString & text ) + + + Q3ListBoxPixmap + Q3ListBoxPixmap-5 + ( const QPixmap & pix, const QString & text ) + + + Q3ListBoxPixmap + Q3ListBoxPixmap-6 + ( Q3ListBox * listbox, const QPixmap & pix, const QString & text, Q3ListBoxItem * after ) + + + height + height + ( const Q3ListBox * lb ) + + + paint + paint + ( QPainter * painter ) + + + pixmap + pixmap + () + + + width + width + ( const Q3ListBox * lb ) + + + + Q3ListBoxText + q3listboxtext.html + + Q3ListBoxText + Q3ListBoxText + ( Q3ListBox * listbox, const QString & text = QString() + + + Q3ListBoxText + Q3ListBoxText-2 + ( const QString & text = QString() + + + Q3ListBoxText + Q3ListBoxText-3 + ( Q3ListBox * listbox, const QString & text, Q3ListBoxItem * after ) + + + height + height + ( const Q3ListBox * lb ) + + + paint + paint + ( QPainter * painter ) + + + width + width + ( const Q3ListBox * lb ) + + + + Q3ListView + q3listview.html + + ComparisonFlags + ComparisonFlags-typedef + + + + RenameAction + RenameAction-enum + + + + ResizeMode + ResizeMode-enum + + + + SelectionMode + SelectionMode-enum + + + + StringComparisonMode + StringComparisonMode-enum + + + + WidthMode + WidthMode-enum + + + + Q3ListView + Q3ListView + ( QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + addColumn + addColumn + ( const QString & label, int width = -1 ) + + + addColumn + addColumn-2 + ( const QIcon & icon, const QString & label, int width = -1 ) + + + adjustColumn + adjustColumn + ( int col ) + + + clear + clear + () + + + clearSelection + clearSelection + () + + + clicked + clicked + ( Q3ListViewItem * item ) + + + clicked + clicked-2 + ( Q3ListViewItem * item, const QPoint & pnt, int c ) + + + collapsed + collapsed + ( Q3ListViewItem * item ) + + + columnAlignment + columnAlignment + ( int column ) + + + columnText + columnText + ( int c ) + + + columnWidth + columnWidth + ( int c ) + + + columnWidthMode + columnWidthMode + ( int c ) + + + contentsMouseDoubleClickEvent + contentsMouseDoubleClickEvent + ( QMouseEvent * e ) + + + contentsMouseMoveEvent + contentsMouseMoveEvent + ( QMouseEvent * e ) + + + contentsMousePressEvent + contentsMousePressEvent + ( QMouseEvent * e ) + + + contentsMouseReleaseEvent + contentsMouseReleaseEvent + ( QMouseEvent * e ) + + + contextMenuRequested + contextMenuRequested + ( Q3ListViewItem * item, const QPoint & pos, int col ) + + + currentChanged + currentChanged + ( Q3ListViewItem * item ) + + + currentItem + currentItem + () + + + doAutoScroll + doAutoScroll + () + + + doubleClicked + doubleClicked + ( Q3ListViewItem * item ) + + + doubleClicked + doubleClicked-2 + ( Q3ListViewItem * item, const QPoint & point, int column ) + + + dragObject + dragObject + () + + + drawContentsOffset + drawContentsOffset + ( QPainter * p, int ox, int oy, int cx, int cy, int cw, int ch ) + + + dropped + dropped + ( QDropEvent * e ) + + + ensureItemVisible + ensureItemVisible + ( const Q3ListViewItem * i ) + + + eventFilter + eventFilter + ( QObject * o, QEvent * e ) + + + expanded + expanded + ( Q3ListViewItem * item ) + + + findItem + findItem + ( const QString & text, int column, ComparisonFlags compare = ExactMatch | Qt::CaseSensitive ) + + + firstChild + firstChild + () + + + header + header + () + + + hideColumn + hideColumn + ( int column ) + + + insertItem + insertItem + ( Q3ListViewItem * i ) + + + invertSelection + invertSelection + () + + + isOpen + isOpen + ( const Q3ListViewItem * item ) + + + isRenaming + isRenaming + () + + + isSelected + isSelected + ( const Q3ListViewItem * i ) + + + itemAt + itemAt + ( const QPoint & viewPos ) + + + itemPos + itemPos + ( const Q3ListViewItem * item ) + + + itemRect + itemRect + ( const Q3ListViewItem * item ) + + + itemRenamed + itemRenamed + ( Q3ListViewItem * item, int col, const QString & text ) + + + itemRenamed + itemRenamed-2 + ( Q3ListViewItem * item, int col ) + + + lastItem + lastItem + () + + + mouseButtonClicked + mouseButtonClicked + ( int button, Q3ListViewItem * item, const QPoint & pos, int c ) + + + mouseButtonPressed + mouseButtonPressed + ( int button, Q3ListViewItem * item, const QPoint & pos, int c ) + + + onItem + onItem + ( Q3ListViewItem * i ) + + + onViewport + onViewport + () + + + paintEmptyArea + paintEmptyArea + ( QPainter * p, const QRect & rect ) + + + pressed + pressed + ( Q3ListViewItem * item ) + + + pressed + pressed-2 + ( Q3ListViewItem * item, const QPoint & pnt, int c ) + + + removeColumn + removeColumn + ( int index ) + + + removeItem + removeItem + ( Q3ListViewItem * item ) + + + repaintItem + repaintItem + ( const Q3ListViewItem * item ) + + + resizeEvent + resizeEvent + ( QResizeEvent * e ) + + + returnPressed + returnPressed + ( Q3ListViewItem * item ) + + + rightButtonClicked + rightButtonClicked + ( Q3ListViewItem * item, const QPoint & point, int column ) + + + rightButtonPressed + rightButtonPressed + ( Q3ListViewItem * item, const QPoint & point, int column ) + + + selectAll + selectAll + ( bool select ) + + + selectedItem + selectedItem + () + + + selectionChanged + selectionChanged + () + + + selectionChanged + selectionChanged-2 + ( Q3ListViewItem * item ) + + + setColumnAlignment + setColumnAlignment + ( int column, int align ) + + + setColumnText + setColumnText + ( int column, const QString & label ) + + + setColumnText + setColumnText-2 + ( int column, const QIcon & icon, const QString & label ) + + + setColumnWidth + setColumnWidth + ( int column, int w ) + + + setColumnWidthMode + setColumnWidthMode + ( int c, WidthMode mode ) + + + setCurrentItem + setCurrentItem + ( Q3ListViewItem * i ) + + + setOpen + setOpen + ( Q3ListViewItem * item, bool open ) + + + setSelected + setSelected + ( Q3ListViewItem * item, bool selected ) + + + setSelectionAnchor + setSelectionAnchor + ( Q3ListViewItem * item ) + + + setSortColumn + setSortColumn + ( int column ) + + + setSortOrder + setSortOrder + ( Qt::SortOrder order ) + + + setSorting + setSorting + ( int column, bool ascending = true ) + + + sort + sort + () + + + sortColumn + sortColumn + () + + + SortOrder + sortOrder + Q3ListView::sortOrder() + + + spacePressed + spacePressed + ( Q3ListViewItem * item ) + + + startDrag + startDrag + () + + + takeItem + takeItem + ( Q3ListViewItem * i ) + + + triggerUpdate + triggerUpdate + () + + + updateContents + updateContents + () + + + + Q3ListViewItem + q3listviewitem.html + + Q3ListViewItem + Q3ListViewItem + ( Q3ListView * parent ) + + + Q3ListViewItem + Q3ListViewItem-2 + ( Q3ListViewItem * parent ) + + + Q3ListViewItem + Q3ListViewItem-3 + ( Q3ListView * parent, Q3ListViewItem * after ) + + + Q3ListViewItem + Q3ListViewItem-4 + ( Q3ListViewItem * parent, Q3ListViewItem * after ) + + + Q3ListViewItem + Q3ListViewItem-5 + ( Q3ListView * parent, const QString & label1, const QString & label2 = QString() + + + Q3ListViewItem + Q3ListViewItem-6 + ( Q3ListViewItem * parent, const QString & label1, const QString & label2 = QString() + + + Q3ListViewItem + Q3ListViewItem-7 + ( Q3ListView * parent, Q3ListViewItem * after, const QString & label1, const QString & label2 = QString() + + + Q3ListViewItem + Q3ListViewItem-8 + ( Q3ListViewItem * parent, Q3ListViewItem * after, const QString & label1, const QString & label2 = QString() + + + acceptDrop + acceptDrop + ( const QMimeSource * mime ) + + + activate + activate + () + + + activatedPos + activatedPos + ( QPoint & pos ) + + + cancelRename + cancelRename + ( int col ) + + + childCount + childCount + () + + + compare + compare + ( Q3ListViewItem * i, int col, bool ascending ) + + + depth + depth + () + + + dragEnabled + dragEnabled + () + + + dragEntered + dragEntered + () + + + dragLeft + dragLeft + () + + + dropEnabled + dropEnabled + () + + + dropped + dropped + ( QDropEvent * e ) + + + enforceSortOrder + enforceSortOrder + () + + + firstChild + firstChild + () + + + height + height + () + + + insertItem + insertItem + ( Q3ListViewItem * newChild ) + + + invalidateHeight + invalidateHeight + () + + + isEnabled + isEnabled + () + + + isExpandable + isExpandable + () + + + isOpen + isOpen + () + + + isSelectable + isSelectable + () + + + isSelected + isSelected + () + + + isVisible + isVisible + () + + + itemAbove + itemAbove + () + + + itemBelow + itemBelow + () + + + itemPos + itemPos + () + + + key + key + ( int column, bool ascending ) + + + listView + listView + () + + + moveItem + moveItem + ( Q3ListViewItem * after ) + + + multiLinesEnabled + multiLinesEnabled + () + + + nextSibling + nextSibling + () + + + okRename + okRename + ( int col ) + + + paintBranches + paintBranches + ( QPainter * p, const QColorGroup & cg, int w, int y, int h ) + + + paintCell + paintCell + ( QPainter * painter, const QColorGroup & cg, int column, int width, int align ) + + + paintFocus + paintFocus + ( QPainter * p, const QColorGroup & cg, const QRect & r ) + + + parent + parent + () + + + pixmap + pixmap + ( int column ) + + + removeItem + removeItem + ( Q3ListViewItem * item ) + + + renameEnabled + renameEnabled + ( int col ) + + + repaint + repaint + () + + + rtti + rtti + () + + + setDragEnabled + setDragEnabled + ( bool allow ) + + + setDropEnabled + setDropEnabled + ( bool allow ) + + + setEnabled + setEnabled + ( bool b ) + + + setExpandable + setExpandable + ( bool enable ) + + + setHeight + setHeight + ( int height ) + + + setMultiLinesEnabled + setMultiLinesEnabled + ( bool b ) + + + setOpen + setOpen + ( bool o ) + + + setPixmap + setPixmap + ( int column, const QPixmap & pm ) + + + setRenameEnabled + setRenameEnabled + ( int col, bool b ) + + + setSelectable + setSelectable + ( bool enable ) + + + setSelected + setSelected + ( bool s ) + + + setText + setText + ( int column, const QString & text ) + + + setVisible + setVisible + ( bool b ) + + + setup + setup + () + + + sort + sort + () + + + sortChildItems + sortChildItems + ( int column, bool ascending ) + + + startRename + startRename + ( int col ) + + + takeItem + takeItem + ( Q3ListViewItem * item ) + + + text + text + ( int column ) + + + totalHeight + totalHeight + () + + + width + width + ( const QFontMetrics & fm, const Q3ListView * lv, int c ) + + + widthChanged + widthChanged + ( int c = -1 ) + + + + Q3ListViewItemIterator + q3listviewitemiterator.html + + IteratorFlag + IteratorFlag-enum + + + + Q3ListViewItemIterator + Q3ListViewItemIterator + () + + + Q3ListViewItemIterator + Q3ListViewItemIterator-2 + ( Q3ListViewItem * item ) + + + Q3ListViewItemIterator + Q3ListViewItemIterator-3 + ( Q3ListViewItem * item, int iteratorFlags ) + + + Q3ListViewItemIterator + Q3ListViewItemIterator-4 + ( const Q3ListViewItemIterator & it ) + + + Q3ListViewItemIterator + Q3ListViewItemIterator-5 + ( Q3ListView * lv ) + + + Q3ListViewItemIterator + Q3ListViewItemIterator-6 + ( Q3ListView * lv, int iteratorFlags ) + + + current + current + () + + + operator* + operator-2a + () + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator+= + operator-2b-eq + ( int j ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator-= + operator--eq + ( int j ) + + + operator= + operator-eq + ( const Q3ListViewItemIterator & it ) + + + + Q3LocalFs + q3localfs.html + + Q3LocalFs + Q3LocalFs + () + + + + Q3MainWindow + q3mainwindow.html + + DockWindows + DockWindows-enum + + + + Q3MainWindow + Q3MainWindow + ( QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = Qt::WType_TopLevel ) + + + addDockWindow + addDockWindow + ( Q3DockWindow * dockWindow, Qt::Dock edge = Qt::DockTop, bool newLine = false ) + + + addDockWindow + addDockWindow-2 + ( Q3DockWindow * dockWindow, const QString & label, Qt::Dock edge = Qt::DockTop, bool newLine = false ) + + + addToolBar + addToolBar + ( Q3DockWindow * dockWindow, Qt::Dock position = Qt::DockTop, bool newLine = false ) + + + addToolBar + addToolBar-2 + ( Q3DockWindow * dockWindow, const QString & label, Qt::Dock position = Qt::DockTop, bool newLine = false ) + + + appropriate + appropriate + ( Q3DockWindow * dw ) + + + bottomDock + bottomDock + () + + + centralWidget + centralWidget + () + + + childEvent + childEvent + ( QChildEvent * e ) + + + createDockWindowMenu + createDockWindowMenu + ( DockWindows dockWindows = AllDockWindows ) + + + customize + customize + () + + + dockWindowPositionChanged + dockWindowPositionChanged + ( Q3DockWindow * dockWindow ) + + + dockWindows + dockWindows + ( Qt::Dock dock ) + + + dockWindows + dockWindows-2 + () + + + getLocation + getLocation + ( Q3DockWindow * dw, Qt::Dock & dock, int & index, bool & nl, int & extraOffset ) + + + hasDockWindow + hasDockWindow + ( Q3DockWindow * dw ) + + + isCustomizable + isCustomizable + () + + + isDockEnabled + isDockEnabled + ( Qt::Dock dock ) + + + isDockEnabled + isDockEnabled-2 + ( Q3DockArea * area ) + + + isDockEnabled + isDockEnabled-3 + ( Q3DockWindow * dw, Q3DockArea * area ) + + + isDockEnabled + isDockEnabled-4 + ( Q3DockWindow * tb, Qt::Dock dock ) + + + isDockMenuEnabled + isDockMenuEnabled + () + + + leftDock + leftDock + () + + + lineUpDockWindows + lineUpDockWindows + ( bool keepNewLines = false ) + + + lineUpToolBars + lineUpToolBars + ( bool keepNewLines = false ) + + + menuAboutToShow + menuAboutToShow + () + + + menuBar + menuBar + () + + + moveDockWindow + moveDockWindow + ( Q3DockWindow * dockWindow, Qt::Dock edge = Qt::DockTop ) + + + moveDockWindow + moveDockWindow-2 + ( Q3DockWindow * dockWindow, Qt::Dock edge, bool nl, int index, int extraOffset = -1 ) + + + moveToolBar + moveToolBar + ( Q3DockWindow * dockWindow, Qt::Dock position = Qt::DockTop ) + + + moveToolBar + moveToolBar-2 + ( Q3DockWindow * dockWindow, Qt::Dock position, bool nl, int index, int extraOffset = -1 ) + + + pixmapSizeChanged + pixmapSizeChanged + ( bool b ) + + + removeDockWindow + removeDockWindow + ( Q3DockWindow * dockWindow ) + + + removeToolBar + removeToolBar + ( Q3DockWindow * dockWindow ) + + + rightDock + rightDock + () + + + setAppropriate + setAppropriate + ( Q3DockWindow * dw, bool a ) + + + setCentralWidget + setCentralWidget + ( QWidget * w ) + + + setDockEnabled + setDockEnabled + ( Qt::Dock dock, bool enable ) + + + setDockEnabled + setDockEnabled-2 + ( Q3DockWindow * dw, Qt::Dock dock, bool enable ) + + + setDockMenuEnabled + setDockMenuEnabled + ( bool b ) + + + setToolBarsMovable + setToolBarsMovable + ( bool b ) + + + setUpLayout + setUpLayout + () + + + showDockMenu + showDockMenu + ( const QPoint & globalPos ) + + + statusBar + statusBar + () + + + toolBarPositionChanged + toolBarPositionChanged + ( Q3ToolBar * toolbar ) + + + toolBars + toolBars + ( Qt::Dock dock ) + + + toolBarsMovable + toolBarsMovable + () + + + topDock + topDock + () + + + usesTextLabelChanged + usesTextLabelChanged + ( bool b ) + + + whatsThis + whatsThis + () + + + + Q3MemArray + q3memarray.html + + ConstIterator + ConstIterator-typedef + + + + Iterator + Iterator-typedef + + + + Q3MemArray + Q3MemArray + ( int arg1, int arg2 ) + + + Q3MemArray + Q3MemArray-2 + () + + + Q3MemArray + Q3MemArray-3 + ( int size ) + + + Q3MemArray + Q3MemArray-4 + ( const Q3MemArray<type> & a ) + + + Q3MemArray + Q3MemArray-5 + ( const QVector<type> & vector ) + + + assign + assign + ( const Q3MemArray<type> & a ) + + + assign + assign-2 + ( const type * data, uint size ) + + + at + at + ( uint index ) + + + begin + begin + () + + + begin + begin-2 + () + + + bsearch + bsearch + ( const type & v ) + + + contains + contains + ( const type & v ) + + + copy + copy + () + + + count + count + () + + + data + data + () + + + detach + detach + () + + + duplicate + duplicate + ( const Q3MemArray<type> & a ) + + + duplicate + duplicate-2 + ( const type * data, uint size ) + + + end + end + () + + + end + end-2 + () + + + fill + fill + ( const type & v, int size = -1 ) + + + find + find + ( const type & v, uint index = 0 ) + + + isEmpty + isEmpty + () + + + isNull + isNull + () + + + nrefs + nrefs + () + + + resetRawData + resetRawData + ( const type * data, uint size ) + + + resize + resize + ( uint size, Optimization optim ) + + + resize + resize-2 + ( uint size ) + + + setRawData + setRawData + ( const type * data, uint size ) + + + size + size + () + + + sort + sort + () + + + truncate + truncate + ( uint pos ) + + + operator + operator-QVector-lttype-gt + QVector<type>() + + + operator + operator-const-type--2a + const type *() + + + operator!= + operator-not-eq + ( const Q3MemArray<type> & a ) + + + operator= + operator-eq + ( const Q3MemArray<type> & a ) + + + operator== + operator-eq-eq + ( const Q3MemArray<type> & a ) + + + operator[] + operator-5b-5d + ( int index ) + + + + Q3MimeSourceFactory + q3mimesourcefactory.html + + Q3MimeSourceFactory + Q3MimeSourceFactory + () + + + addFactory + addFactory + ( Q3MimeSourceFactory * f ) + + + addFilePath + addFilePath + ( const QString & p ) + + + data + data + ( const QString & abs_name ) + + + data + data-2 + ( const QString & abs_or_rel_name, const QString & context ) + + + defaultFactory + defaultFactory + () + + + filePath + filePath + () + + + makeAbsolute + makeAbsolute + ( const QString & abs_or_rel_name, const QString & context ) + + + removeFactory + removeFactory + ( Q3MimeSourceFactory * f ) + + + setData + setData + ( const QString & abs_name, QMimeSource * data ) + + + setDefaultFactory + setDefaultFactory + ( Q3MimeSourceFactory * factory ) + + + setExtensionType + setExtensionType + ( const QString & ext, const char * mimetype ) + + + setFilePath + setFilePath + ( const QStringList & path ) + + + setFilePath + setFilePath-2 + ( const QString & path ) + + + setImage + setImage + ( const QString & abs_name, const QImage & image ) + + + setPixmap + setPixmap + ( const QString & abs_name, const QPixmap & pixmap ) + + + setText + setText + ( const QString & abs_name, const QString & text ) + + + takeDefaultFactory + takeDefaultFactory + () + + + + Q3MultiLineEdit + q3multilineedit.html + + Alignment + alignment-prop + + + + Q3MultiLineEdit + Q3MultiLineEdit + ( QWidget * parent = 0, const char * name = 0 ) + + + autoUpdate + autoUpdate + () + + + backspace + backspace + () + + + cursorDown + cursorDown + ( bool mark = false ) + + + cursorLeft + cursorLeft + ( bool mark = false, bool wrap = true ) + + + cursorPoint + cursorPoint + () + + + cursorRight + cursorRight + ( bool mark = false, bool wrap = true ) + + + cursorUp + cursorUp + ( bool mark = false ) + + + cursorWordBackward + cursorWordBackward + ( bool mark ) + + + cursorWordForward + cursorWordForward + ( bool mark ) + + + deselect + deselect + () + + + end + end + ( bool mark = false ) + + + getMarkedRegion + getMarkedRegion + ( int * line1, int * col1, int * line2, int * col2 ) + + + hasMarkedText + hasMarkedText + () + + + home + home + ( bool mark = false ) + + + insertAndMark + insertAndMark + ( const QString & str, bool mark ) + + + insertAt + insertAt + ( const QString & s, int line, int col, bool mark ) + + + insertLine + insertLine + ( const QString & txt, int line = -1 ) + + + killLine + killLine + () + + + lineLength + lineLength + ( int row ) + + + markedText + markedText + () + + + maxLines + maxLines + () + + + newLine + newLine + () + + + pageDown + pageDown + ( bool mark = false ) + + + pageUp + pageUp + ( bool mark = false ) + + + removeLine + removeLine + ( int paragraph ) + + + setCursorPosition + setCursorPosition + ( int line, int col, bool mark ) + + + setMaxLines + setMaxLines + ( int max ) + + + textLine + textLine + ( int line ) + + + totalHeight + totalHeight + () + + + totalWidth + totalWidth + () + + + + Q3NetworkOperation + q3networkoperation.html + + Q3NetworkOperation + Q3NetworkOperation + ( Q3NetworkProtocol::Operation operation, const QString & arg0, const QString & arg1, const QString & arg2 ) + + + Q3NetworkOperation + Q3NetworkOperation-2 + ( Q3NetworkProtocol::Operation operation, const QByteArray & arg0, const QByteArray & arg1, const QByteArray & arg2 ) + + + arg + arg + ( int num ) + + + errorCode + errorCode + () + + + free + free + () + + + Operation + operation + Q3NetworkOperation::operation() + + + protocolDetail + protocolDetail + () + + + rawArg + rawArg + ( int num ) + + + setArg + setArg + ( int num, const QString & arg ) + + + setErrorCode + setErrorCode + ( int ec ) + + + setProtocolDetail + setProtocolDetail + ( const QString & detail ) + + + setRawArg + setRawArg + ( int num, const QByteArray & arg ) + + + setState + setState + ( Q3NetworkProtocol::State state ) + + + State + state + Q3NetworkOperation::state() + + + + Q3NetworkProtocol + q3networkprotocol.html + + ConnectionState + ConnectionState-enum + + + + Error + Error-enum + + + + Operation + Operation-enum + + + + State + State-enum + + + + Q3NetworkProtocol + Q3NetworkProtocol + () + + + addOperation + addOperation + ( Q3NetworkOperation * op ) + + + autoDelete + autoDelete + () + + + checkConnection + checkConnection + ( Q3NetworkOperation * op ) + + + clearOperationQueue + clearOperationQueue + () + + + connectionStateChanged + connectionStateChanged + ( int state, const QString & data ) + + + createdDirectory + createdDirectory + ( const QUrlInfo & i, Q3NetworkOperation * op ) + + + data + data + ( const QByteArray & data, Q3NetworkOperation * op ) + + + dataTransferProgress + dataTransferProgress + ( int bytesDone, int bytesTotal, Q3NetworkOperation * op ) + + + finished + finished + ( Q3NetworkOperation * op ) + + + getNetworkProtocol + getNetworkProtocol + ( const QString & protocol ) + + + hasOnlyLocalFileSystem + hasOnlyLocalFileSystem + () + + + itemChanged + itemChanged + ( Q3NetworkOperation * op ) + + + newChild + newChild + ( const QUrlInfo & i, Q3NetworkOperation * op ) + + + newChildren + newChildren + ( const Q3ValueList<QUrlInfo> & i, Q3NetworkOperation * op ) + + + operationGet + operationGet + ( Q3NetworkOperation * op ) + + + operationInProgress + operationInProgress + () + + + operationListChildren + operationListChildren + ( Q3NetworkOperation * op ) + + + operationMkDir + operationMkDir + ( Q3NetworkOperation * op ) + + + operationPut + operationPut + ( Q3NetworkOperation * op ) + + + operationRemove + operationRemove + ( Q3NetworkOperation * op ) + + + operationRename + operationRename + ( Q3NetworkOperation * op ) + + + registerNetworkProtocol + registerNetworkProtocol + ( const QString & protocol, Q3NetworkProtocolFactoryBase * protocolFactory ) + + + removed + removed + ( Q3NetworkOperation * op ) + + + setAutoDelete + setAutoDelete + ( bool b, int i = 10000 ) + + + setUrl + setUrl + ( Q3UrlOperator * u ) + + + start + start + ( Q3NetworkOperation * op ) + + + stop + stop + () + + + supportedOperations + supportedOperations + () + + + url + url + () + + + + Q3PaintDeviceMetrics + q3paintdevicemetrics.html + + Q3PaintDeviceMetrics + Q3PaintDeviceMetrics + ( const QPaintDevice * pd ) + + + depth + depth + () + + + height + height + () + + + heightMM + heightMM + () + + + logicalDpiX + logicalDpiX + () + + + logicalDpiY + logicalDpiY + () + + + numColors + numColors + () + + + width + width + () + + + widthMM + widthMM + () + + + + Q3Painter + q3painter.html + + Q3Painter + Q3Painter + () + + + Q3Painter + Q3Painter-2 + ( QPaintDevice * pdev ) + + + drawArc + drawArc + ( const QRect & r, int a, int alen ) + + + drawArc + drawArc-2 + ( int x, int y, int w, int h, int startAngle, int spanAngle ) + + + drawChord + drawChord + ( const QRect & r, int a, int alen ) + + + drawChord + drawChord-2 + ( int x, int y, int w, int h, int startAngle, int spanAngle ) + + + drawEllipse + drawEllipse + ( const QRect & r ) + + + drawEllipse + drawEllipse-2 + ( int x, int y, int width, int height ) + + + drawPie + drawPie + ( const QRect & r, int a, int alen ) + + + drawPie + drawPie-2 + ( int x, int y, int w, int h, int startAngle, int spanAngle ) + + + drawRect + drawRect + ( const QRect & r ) + + + drawRect + drawRect-2 + ( int x, int y, int w, int h ) + + + drawRoundRect + drawRoundRect + ( const QRect & r, int xrnd = 25, int yrnd = 25 ) + + + drawRoundRect + drawRoundRect-2 + ( int x, int y, int w, int h, int xrnd = 25, int yrnd = 25 ) + + + + Q3Picture + q3picture.html + + Q3Picture + Q3Picture + () + + + Q3Picture + Q3Picture-2 + ( const QPicture & other ) + + + load + load + ( QIODevice * device, const char * format = 0 ) + + + load + load-2 + ( const QString & fileName, const char * format = 0 ) + + + save + save + ( QIODevice * device, const char * format = 0 ) + + + save + save-2 + ( const QString & fileName, const char * format = 0 ) + + + + Q3PointArray + q3pointarray.html + + Q3PointArray + Q3PointArray + () + + + Q3PointArray + Q3PointArray-2 + ( const QRect & r, bool closed = false ) + + + Q3PointArray + Q3PointArray-3 + ( const QPolygon & other ) + + + copy + copy + () + + + cubicBezier + cubicBezier + () + + + isNull + isNull + () + + + makeArc + makeArc + ( int x, int y, int w, int h, int a1, int a2 ) + + + makeArc + makeArc-2 + ( int x, int y, int w, int h, int a1, int a2, const QMatrix & xf ) + + + makeEllipse + makeEllipse + ( int x, int y, int w, int h ) + + + + Q3PopupMenu + q3popupmenu.html + + Q3PopupMenu + Q3PopupMenu + ( QWidget * parent = 0, const char * name = 0 ) + + + exec + exec + () + + + exec + exec-2 + ( const QPoint & pos, int indexAtPoint = 0 ) + + + margin + margin + () + + + setMargin + setMargin + ( int margin ) + + + + Q3Process + q3process.html + + Communication + Communication-enum + + + + Q3Process + Q3Process + ( QObject * parent = 0, const char * name = 0 ) + + + Q3Process + Q3Process-2 + ( const QString & arg0, QObject * parent = 0, const char * name = 0 ) + + + Q3Process + Q3Process-3 + ( const QStringList & args, QObject * parent = 0, const char * name = 0 ) + + + addArgument + addArgument + ( const QString & arg ) + + + arguments + arguments + () + + + canReadLineStderr + canReadLineStderr + () + + + canReadLineStdout + canReadLineStdout + () + + + clearArguments + clearArguments + () + + + closeStdin + closeStdin + () + + + communication + communication + () + + + exitStatus + exitStatus + () + + + isRunning + isRunning + () + + + kill + kill + () + + + launch + launch + ( const QByteArray & buf, QStringList * env = 0 ) + + + launch + launch-2 + ( const QString & buf, QStringList * env = 0 ) + + + launchFinished + launchFinished + () + + + normalExit + normalExit + () + + + processExited + processExited + () + + + processIdentifier + processIdentifier + () + + + readLineStderr + readLineStderr + () + + + readLineStdout + readLineStdout + () + + + readStderr + readStderr + () + + + readStdout + readStdout + () + + + readyReadStderr + readyReadStderr + () + + + readyReadStdout + readyReadStdout + () + + + setArguments + setArguments + ( const QStringList & args ) + + + setCommunication + setCommunication + ( int commFlags ) + + + setWorkingDirectory + setWorkingDirectory + ( const QDir & dir ) + + + start + start + ( QStringList * env = 0 ) + + + tryTerminate + tryTerminate + () + + + workingDirectory + workingDirectory + () + + + writeToStdin + writeToStdin + ( const QByteArray & buf ) + + + writeToStdin + writeToStdin-2 + ( const QString & buf ) + + + wroteToStdin + wroteToStdin + () + + + Q3ProgressBar + Q3ProgressBar-2 + ( int totalSteps, QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + Q3ProgressBar + Q3ProgressBar-3 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + + Q3ProgressBar + q3progressbar.html + + Q3ProgressBar + Q3ProgressBar + ( QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + Q3ProgressBar + Q3ProgressBar-4 + ( int totalSteps, QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + margin + margin + () + + + reset + reset + () + + + setIndicator + setIndicator + ( QString & indicator, int progress, int totalSteps ) + + + setMargin + setMargin + ( int margin ) + + + + Q3ProgressDialog + q3progressdialog.html + + Q3ProgressDialog + Q3ProgressDialog + ( QWidget * creator, const char * name, bool modal = false, Qt::WindowFlags f = 0 ) + + + Q3ProgressDialog + Q3ProgressDialog-2 + ( const QString & labelText, const QString & cancelButtonText, int totalSteps, QWidget * creator = 0, const char * name = 0, bool modal = false, Qt::WindowFlags f = 0 ) + + + Q3ProgressDialog + Q3ProgressDialog-3 + ( QWidget * creator = 0, Qt::WindowFlags f = 0 ) + + + Q3ProgressDialog + Q3ProgressDialog-4 + ( const QString & labelText, const QString & cancelButtonText, int totalSteps, QWidget * creator = 0, Qt::WindowFlags f = 0 ) + + + cancel + cancel + () + + + canceled + canceled + () + + + cancelled + cancelled + () + + + forceShow + forceShow + () + + + reset + reset + () + + + setBar + setBar + ( Q3ProgressBar * bar ) + + + setCancelButton + setCancelButton + ( QPushButton * cancelButton ) + + + setCancelButtonText + setCancelButtonText + ( const QString & cancelButtonText ) + + + setLabel + setLabel + ( QLabel * label ) + + + sizeHint + sizeHint + () + + + + Q3PtrCollection + q3ptrcollection.html + + Item + Item-typedef + + + + Q3PtrCollection + Q3PtrCollection + () + + + Q3PtrCollection + Q3PtrCollection-2 + ( const Q3PtrCollection & source ) + + + autoDelete + autoDelete + () + + + clear + clear + () + + + count + count + () + + + deleteItem + deleteItem + ( Item d ) + + + newItem + newItem + ( Item d ) + + + setAutoDelete + setAutoDelete + ( bool enable ) + + + + Q3PtrDict + q3ptrdict.html + + Q3PtrDict + Q3PtrDict + ( int size = 17 ) + + + Q3PtrDict + Q3PtrDict-2 + ( const Q3PtrDict<type> & dict ) + + + clear + clear + () + + + count + count + () + + + find + find + ( void * key ) + + + insert + insert + ( void * key, const type * item ) + + + isEmpty + isEmpty + () + + + read + read + ( QDataStream & s, Q3PtrCollection::Item & item ) + + + remove + remove + ( void * key ) + + + replace + replace + ( void * key, const type * item ) + + + resize + resize + ( uint newsize ) + + + size + size + () + + + statistics + statistics + () + + + take + take + ( void * key ) + + + write + write + ( QDataStream & s, Q3PtrCollection::Item item ) + + + operator= + operator-eq + ( const Q3PtrDict<type> & dict ) + + + operator[] + operator-5b-5d + ( void * key ) + + + + Q3PtrDictIterator + q3ptrdictiterator.html + + Q3PtrDictIterator + Q3PtrDictIterator + ( const Q3PtrDict<type> & dict ) + + + count + count + () + + + current + current + () + + + currentKey + currentKey + () + + + isEmpty + isEmpty + () + + + toFirst + toFirst + () + + + operator + operator-type--2a + type *() + + + operator() + operator-28-29 + () + + + operator++ + operator-2b-2b + () + + + operator+= + operator-2b-eq + ( uint jump ) + + + + Q3PtrList + q3ptrlist.html + + Q3PtrList + Q3PtrList + () + + + Q3PtrList + Q3PtrList-2 + ( const Q3PtrList<type> & list ) + + + append + append + ( const type * item ) + + + at + at + ( uint index ) + + + at + at-2 + () + + + clear + clear + () + + + compareItems + compareItems + ( Q3PtrCollection::Item item1, Q3PtrCollection::Item item2 ) + + + contains + contains + ( const type * item ) + + + containsRef + containsRef + ( const type * item ) + + + count + count + () + + + current + current + () + + + currentNode + currentNode + () + + + find + find + ( const type * item ) + + + findNext + findNext + ( const type * item ) + + + findNextRef + findNextRef + ( const type * item ) + + + findRef + findRef + ( const type * item ) + + + first + first + () + + + getFirst + getFirst + () + + + getLast + getLast + () + + + inSort + inSort + ( const type * item ) + + + insert + insert + ( uint index, const type * item ) + + + isEmpty + isEmpty + () + + + last + last + () + + + next + next + () + + + prepend + prepend + ( const type * item ) + + + prev + prev + () + + + read + read + ( QDataStream & s, Q3PtrCollection::Item & item ) + + + remove + remove + ( uint index ) + + + remove + remove-2 + () + + + remove + remove-3 + ( const type * item ) + + + removeFirst + removeFirst + () + + + removeLast + removeLast + () + + + removeNode + removeNode + ( Q3LNode * node ) + + + removeRef + removeRef + ( const type * item ) + + + replace + replace + ( uint index, const type * item ) + + + sort + sort + () + + + take + take + ( uint index ) + + + take + take-2 + () + + + takeNode + takeNode + ( Q3LNode * node ) + + + toVector + toVector + ( Q3GVector * vec ) + + + write + write + ( QDataStream & s, Q3PtrCollection::Item item ) + + + operator!= + operator-not-eq + ( const Q3PtrList<type> & list ) + + + operator= + operator-eq + ( const Q3PtrList<type> & list ) + + + operator== + operator-eq-eq + ( const Q3PtrList<type> & list ) + + + + Q3PtrListIterator + q3ptrlistiterator.html + + Q3PtrListIterator + Q3PtrListIterator + ( const Q3PtrList<type> & list ) + + + atFirst + atFirst + () + + + atLast + atLast + () + + + count + count + () + + + current + current + () + + + isEmpty + isEmpty + () + + + toFirst + toFirst + () + + + toLast + toLast + () + + + operator + operator-type--2a + type *() + + + operator() + operator-28-29 + () + + + operator* + operator-2a + () + + + operator++ + operator-2b-2b + () + + + operator+= + operator-2b-eq + ( uint jump ) + + + operator-- + operator-- + () + + + operator-= + operator--eq + ( uint jump ) + + + operator= + operator-eq + ( const Q3PtrListIterator<type> & it ) + + + + Q3PtrQueue + q3ptrqueue.html + + Q3PtrQueue + Q3PtrQueue + () + + + Q3PtrQueue + Q3PtrQueue-2 + ( const Q3PtrQueue<type> & queue ) + + + autoDelete + autoDelete + () + + + clear + clear + () + + + count + count + () + + + current + current + () + + + dequeue + dequeue + () + + + enqueue + enqueue + ( const type * d ) + + + head + head + () + + + isEmpty + isEmpty + () + + + read + read + ( QDataStream & s, Q3PtrCollection::Item & item ) + + + remove + remove + () + + + setAutoDelete + setAutoDelete + ( bool enable ) + + + write + write + ( QDataStream & s, Q3PtrCollection::Item item ) + + + operator + operator-type--2a + type *() + + + operator= + operator-eq + ( const Q3PtrQueue<type> & queue ) + + + + Q3PtrStack + q3ptrstack.html + + Q3PtrStack + Q3PtrStack + () + + + Q3PtrStack + Q3PtrStack-2 + ( const Q3PtrStack<type> & s ) + + + autoDelete + autoDelete + () + + + clear + clear + () + + + count + count + () + + + current + current + () + + + isEmpty + isEmpty + () + + + pop + pop + () + + + push + push + ( const type * d ) + + + read + read + ( QDataStream & s, Q3PtrCollection::Item & item ) + + + remove + remove + () + + + setAutoDelete + setAutoDelete + ( bool enable ) + + + top + top + () + + + write + write + ( QDataStream & s, Q3PtrCollection::Item item ) + + + operator + operator-type--2a + type *() + + + operator= + operator-eq + ( const Q3PtrStack<type> & s ) + + + + Q3PtrVector + q3ptrvector.html + + Q3PtrVector + Q3PtrVector + () + + + Q3PtrVector + Q3PtrVector-2 + ( uint size ) + + + Q3PtrVector + Q3PtrVector-3 + ( const Q3PtrVector<type> & v ) + + + at + at + ( uint i ) + + + bsearch + bsearch + ( const type * d ) + + + clear + clear + () + + + compareItems + compareItems + ( Q3PtrCollection::Item d1, Q3PtrCollection::Item d2 ) + + + contains + contains + ( const type * d ) + + + containsRef + containsRef + ( const type * d ) + + + count + count + () + + + data + data + () + + + fill + fill + ( const type * d, int size = -1 ) + + + find + find + ( const type * d, uint i = 0 ) + + + findRef + findRef + ( const type * d, uint i = 0 ) + + + insert + insert + ( uint i, const type * d ) + + + isEmpty + isEmpty + () + + + isNull + isNull + () + + + read + read + ( QDataStream & s, Q3PtrCollection::Item & item ) + + + remove + remove + ( uint i ) + + + resize + resize + ( uint size ) + + + size + size + () + + + sort + sort + () + + + take + take + ( uint i ) + + + write + write + ( QDataStream & s, Q3PtrCollection::Item item ) + + + operator= + operator-eq + ( const Q3PtrVector<type> & v ) + + + operator== + operator-eq-eq + ( const Q3PtrVector<type> & v ) + + + operator[] + operator-5b-5d + ( int i ) + + + + Q3RangeControl + q3rangecontrol.html + + Q3RangeControl + Q3RangeControl + () + + + Q3RangeControl + Q3RangeControl-2 + ( int minValue, int maxValue, int lineStep, int pageStep, int value ) + + + addLine + addLine + () + + + addPage + addPage + () + + + bound + bound + ( int v ) + + + directSetValue + directSetValue + ( int value ) + + + lineStep + lineStepx + () + + + maxValue + maxValue + () + + + minValue + minValue + () + + + pageStep + pageStep + () + + + positionFromValue + positionFromValue + ( int logical_val, int span ) + + + prevValue + prevValue + () + + + rangeChange + rangeChange + () + + + setMaxValue + setMaxValue + ( int maxVal ) + + + setMinValue + setMinValue + ( int minVal ) + + + setRange + setRange + ( int minValue, int maxValue ) + + + setSteps + setSteps + ( int lineStep, int pageStep ) + + + setValue + setValue + ( int value ) + + + stepChange + stepChange + () + + + subtractLine + subtractLine + () + + + subtractPage + subtractPage + () + + + value + value + () + + + valueChange + valueChange + () + + + valueFromPosition + valueFromPosition + ( int pos, int span ) + + + childIsVisible + childIsVisible + ( QWidget * child ) + + + showChild + showChild + ( QWidget * child, bool y = true ) + + + + Q3ScrollView + q3scrollview.html + + ResizePolicy + ResizePolicy-enum + + + + ScrollBarMode + ScrollBarMode-enum + + + + Q3ScrollView + Q3ScrollView + ( QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + addChild + addChild + ( QWidget * child, int x = 0, int y = 0 ) + + + bottomMargin + bottomMargin + () + + + center + center + ( int x, int y ) + + + center + center-2 + ( int x, int y, float xmargin, float ymargin ) + + + childX + childX + ( QWidget * child ) + + + childY + childY + ( QWidget * child ) + + + clipper + clipper + () + + + contentsContextMenuEvent + contentsContextMenuEvent + ( QContextMenuEvent * e ) + + + contentsDragEnterEvent + contentsDragEnterEvent + ( QDragEnterEvent * event ) + + + contentsDragLeaveEvent + contentsDragLeaveEvent + ( QDragLeaveEvent * event ) + + + contentsDragMoveEvent + contentsDragMoveEvent + ( QDragMoveEvent * event ) + + + contentsDropEvent + contentsDropEvent + ( QDropEvent * event ) + + + contentsMouseDoubleClickEvent + contentsMouseDoubleClickEvent + ( QMouseEvent * e ) + + + contentsMouseMoveEvent + contentsMouseMoveEvent + ( QMouseEvent * e ) + + + contentsMousePressEvent + contentsMousePressEvent + ( QMouseEvent * e ) + + + contentsMouseReleaseEvent + contentsMouseReleaseEvent + ( QMouseEvent * e ) + + + contentsMoving + contentsMoving + ( int x, int y ) + + + contentsToViewport + contentsToViewport + ( int x, int y, int & vx, int & vy ) + + + contentsToViewport + contentsToViewport-2 + ( const QPoint & p ) + + + contentsWheelEvent + contentsWheelEvent + ( QWheelEvent * e ) + + + cornerWidget + cornerWidget + () + + + drawContents + drawContents + ( QPainter * p, int clipx, int clipy, int clipw, int cliph ) + + + drawContentsOffset + drawContentsOffset + ( QPainter * p, int offsetx, int offsety, int clipx, int clipy, int clipw, int cliph ) + + + enableClipper + enableClipper + ( bool y ) + + + ensureVisible + ensureVisible + ( int x, int y ) + + + ensureVisible + ensureVisible-2 + ( int x, int y, int xmargin, int ymargin ) + + + eventFilter + eventFilter + ( QObject * obj, QEvent * e ) + + + hasStaticBackground + hasStaticBackground + () + + + horizontalScrollBar + horizontalScrollBar + () + + + horizontalSliderPressed + horizontalSliderPressed + () + + + horizontalSliderReleased + horizontalSliderReleased + () + + + isHorizontalSliderPressed + isHorizontalSliderPressed + () + + + isVerticalSliderPressed + isVerticalSliderPressed + () + + + leftMargin + leftMargin + () + + + moveChild + moveChild + ( QWidget * child, int x, int y ) + + + removeChild + removeChild + ( QWidget * child ) + + + repaintContents + repaintContents + ( int x, int y, int w, int h, bool erase = true ) + + + repaintContents + repaintContents-2 + ( const QRect & r, bool erase = true ) + + + repaintContents + repaintContents-3 + ( bool erase = true ) + + + resizeContents + resizeContents + ( int w, int h ) + + + rightMargin + rightMargin + () + + + scrollBy + scrollBy + ( int dx, int dy ) + + + setContentsPos + setContentsPos + ( int x, int y ) + + + setCornerWidget + setCornerWidget + ( QWidget * corner ) + + + setHBarGeometry + setHBarGeometry + ( QScrollBar & hbar, int x, int y, int w, int h ) + + + setMargins + setMargins + ( int left, int top, int right, int bottom ) + + + setStaticBackground + setStaticBackground + ( bool y ) + + + setVBarGeometry + setVBarGeometry + ( QScrollBar & vbar, int x, int y, int w, int h ) + + + topMargin + topMargin + () + + + updateContents + updateContents + ( int x, int y, int w, int h ) + + + updateContents + updateContents-2 + ( const QRect & r ) + + + updateContents + updateContents-3 + () + + + updateScrollBars + updateScrollBars + () + + + verticalScrollBar + verticalScrollBar + () + + + verticalSliderPressed + verticalSliderPressed + () + + + verticalSliderReleased + verticalSliderReleased + () + + + viewport + viewport + () + + + viewportPaintEvent + viewportPaintEvent + ( QPaintEvent * pe ) + + + viewportResizeEvent + viewportResizeEvent + ( QResizeEvent * event ) + + + viewportSize + viewportSize + ( int x, int y ) + + + viewportToContents + viewportToContents + ( int vx, int vy, int & x, int & y ) + + + viewportToContents + viewportToContents-2 + ( const QPoint & vp ) + + + + Q3Semaphore + q3semaphore.html + + Q3Semaphore + Q3Semaphore + ( int maxcount ) + + + available + available + () + + + total + total + () + + + tryAccess + tryAccess + ( int n ) + + + operator++ + operator-2b-2b + ( int ) + + + operator+= + operator-2b-eq + ( int n ) + + + operator-- + operator-- + ( int ) + + + operator-= + operator--eq + ( int n ) + + + + Q3ServerSocket + q3serversocket.html + + Q3ServerSocket + Q3ServerSocket + ( Q_UINT16 port, int backlog = 1, QObject * parent = 0, const char * name = 0 ) + + + Q3ServerSocket + Q3ServerSocket-2 + ( const QHostAddress & address, Q_UINT16 port, int backlog = 1, QObject * parent = 0, const char * name = 0 ) + + + Q3ServerSocket + Q3ServerSocket-3 + ( QObject * parent = 0, const char * name = 0 ) + + + address + address + () + + + newConnection + newConnection + ( int socket ) + + + ok + ok + () + + + port + port + () + + + setSocket + setSocket + ( int socket ) + + + socket + socket + () + + + socketDevice + socketDevice + () + + + + Q3Shared + q3shared.html + + Q3Shared + Q3Shared + () + + + deref + deref + () + + + ref + ref + () + + + block + block + ( bool b ) + + + isBlocked + isBlocked + () + + + parameter + parameter + () + + + setParameter + setParameter + ( int value ) + + + + Q3Signal + q3signal.html + + Q3Signal + Q3Signal + ( QObject * parent = 0, const char * name = 0 ) + + + activate + activate + () + + + connect + connect + ( const QObject * receiver, const char * member ) + + + disconnect + disconnect + ( const QObject * receiver, const char * member = 0 ) + + + setValue + setValue + ( const QVariant & value ) + + + value + value + () + + + + Q3SimpleRichText + q3simplerichtext.html + + Q3SimpleRichText + Q3SimpleRichText + ( const QString & text, const QFont & fnt, const QString & context = QString() + + + Q3SimpleRichText + Q3SimpleRichText-2 + ( const QString & text, const QFont & fnt, const QString & context, const Q3StyleSheet * sheet, const Q3MimeSourceFactory * factory, int pageBreak = -1, const QColor & linkColor = Qt::blue, bool linkUnderline = true ) + + + adjustSize + adjustSize + () + + + anchorAt + anchorAt + ( const QPoint & pos ) + + + context + context + () + + + draw + draw + ( QPainter * p, int x, int y, const QRect & clipRect, const QColorGroup & cg, const QBrush * paper = 0 ) + + + draw + draw-2 + ( QPainter * p, int x, int y, const QRegion & clipRegion, const QColorGroup & cg, const QBrush * paper = 0 ) + + + height + height + () + + + inText + inText + ( const QPoint & pos ) + + + setDefaultFont + setDefaultFont + ( const QFont & f ) + + + setWidth + setWidth + ( QPainter * p, int w ) + + + setWidth + setWidth-2 + ( int w ) + + + width + width + () + + + widthUsed + widthUsed + () + + + + Q3Socket + q3socket.html + + Error + Error-enum + + + + State + State-enum + + + + Q3Socket + Q3Socket + ( QObject * parent = 0, const char * name = 0 ) + + + address + address + () + + + at + at + () + + + at + at-2 + ( Offset index ) + + + atEnd + atEnd + () + + + bytesAvailable + bytesAvailable + () + + + bytesToWrite + bytesToWrite + () + + + bytesWritten + bytesWritten + ( int nbytes ) + + + canReadLine + canReadLine + () + + + clearPendingData + clearPendingData + () + + + close + close + () + + + connectToHost + connectToHost + ( const QString & host, Q_UINT16 port ) + + + connected + connected + () + + + connectionClosed + connectionClosed + () + + + delayedCloseFinished + delayedCloseFinished + () + + + error + error + ( int error ) + + + flush + flush + () + + + getch + getch + () + + + hostFound + hostFound + () + + + open + open + ( OpenMode m ) + + + open + open-2 + ( int m ) + + + peerAddress + peerAddress + () + + + peerName + peerName + () + + + peerPort + peerPort + () + + + port + port + () + + + putch + putch + ( int ch ) + + + readBufferSize + readBufferSize + () + + + readData + readData + ( char * data, qint64 maxlen ) + + + readyRead + readyRead + () + + + setReadBufferSize + setReadBufferSize + ( Q_ULONG bufSize ) + + + setSocket + setSocket + ( int socket ) + + + setSocketDevice + setSocketDevice + ( Q3SocketDevice * device ) + + + size + size + () + + + socket + socket + () + + + socketDevice + socketDevice + () + + + state + state + () + + + ungetch + ungetch + ( int ch ) + + + waitForMore + waitForMore + ( int msecs, bool * timeout ) + + + waitForMore + waitForMore-2 + ( int msecs ) + + + writeData + writeData + ( const char * data, qint64 len ) + + + + Q3SocketDevice + q3socketdevice.html + + Error + Error-enum + + + + Protocol + Protocol-enum + + + + Type + Type-enum + + + + Q3SocketDevice + Q3SocketDevice + ( Type type = Stream ) + + + Q3SocketDevice + Q3SocketDevice-2 + ( Type type, Protocol protocol, int dummy ) + + + Q3SocketDevice + Q3SocketDevice-3 + ( int socket, Type type ) + + + accept + accept + () + + + address + address + () + + + addressReusable + addressReusable + () + + + at + at + () + + + at + at-2 + ( Offset offset ) + + + bind + bind + ( const QHostAddress & address, Q_UINT16 port ) + + + blocking + blocking + () + + + bytesAvailable + bytesAvailable + () + + + connect + connect + ( const QHostAddress & addr, Q_UINT16 port ) + + + error + error + () + + + flush + flush + () + + + isValid + isValid + () + + + listen + listen + ( int backlog ) + + + open + open + ( OpenMode mode ) + + + open + open-2 + ( int mode ) + + + peerAddress + peerAddress + () + + + peerPort + peerPort + () + + + port + port + () + + + protocol + protocol + () + + + readBlock + readBlock + ( char * data, Q_ULONG maxlen ) + + + readData + readData + ( char * data, qint64 maxlen ) + + + receiveBufferSize + receiveBufferSize + () + + + sendBufferSize + sendBufferSize + () + + + setAddressReusable + setAddressReusable + ( bool enable ) + + + setBlocking + setBlocking + ( bool enable ) + + + setError + setError + ( Error err ) + + + setReceiveBufferSize + setReceiveBufferSize + ( uint size ) + + + setSendBufferSize + setSendBufferSize + ( uint size ) + + + setSocket + setSocket + ( int socket, Type type ) + + + socket + socket + () + + + type + type + () + + + waitForMore + waitForMore + ( int msecs, bool * timeout = 0 ) + + + writeBlock + writeBlock + ( const char * data, Q_ULONG len ) + + + writeBlock + writeBlock-2 + ( const char * data, Q_ULONG len, const QHostAddress & host, Q_UINT16 port ) + + + writeData + writeData + ( const char * data, qint64 len ) + + + + Q3SqlCursor + q3sqlcursor.html + + Mode + Mode-enum + + + + Q3SqlCursor + Q3SqlCursor + ( const QString & name = QString() + + + Q3SqlCursor + Q3SqlCursor-2 + ( const Q3SqlCursor & other ) + + + append + append + ( const Q3SqlFieldInfo & fieldInfo ) + + + calculateField + calculateField + ( const QString & name ) + + + canDelete + canDelete + () + + + canInsert + canInsert + () + + + canUpdate + canUpdate + () + + + clear + clear + () + + + del + del + ( bool invalidate = true ) + + + del + del-2 + ( const QString & filter, bool invalidate = true ) + + + editBuffer + editBuffer + ( bool copy = false ) + + + exec + exec + ( const QString & sql ) + + + filter + filter + () + + + index + index + ( const QStringList & fieldNames ) + + + index + index-2 + ( const QString & fieldName ) + + + insert + insert + ( int pos, const Q3SqlFieldInfo & fieldInfo ) + + + insert + insert-2 + ( bool invalidate = true ) + + + isCalculated + isCalculated + ( const QString & name ) + + + isNull + isNull + ( int i ) + + + isNull + isNull-2 + ( const QString & name ) + + + isReadOnly + isReadOnly + () + + + isTrimmed + isTrimmed + ( const QString & name ) + + + mode + mode + () + + + name + name + () + + + primaryIndex + primaryIndex + ( bool setFromCursor = true ) + + + primeDelete + primeDelete + () + + + primeInsert + primeInsert + () + + + primeUpdate + primeUpdate + () + + + remove + remove + ( int pos ) + + + select + select + ( const QString & filter, const QSqlIndex & sort = QSqlIndex() + + + select + select-2 + () + + + select + select-3 + ( const QSqlIndex & sort ) + + + select + select-4 + ( const QSqlIndex & filter, const QSqlIndex & sort ) + + + setCalculated + setCalculated + ( const QString & name, bool calculated ) + + + setFilter + setFilter + ( const QString & filter ) + + + setGenerated + setGenerated + ( const QString & name, bool generated ) + + + setGenerated + setGenerated-2 + ( int i, bool generated ) + + + setMode + setMode + ( int mode ) + + + setName + setName + ( const QString & name, bool autopopulate = true ) + + + setPrimaryIndex + setPrimaryIndex + ( const QSqlIndex & idx ) + + + setSort + setSort + ( const QSqlIndex & sort ) + + + setTrimmed + setTrimmed + ( const QString & name, bool trim ) + + + setValue + setValue + ( const QString & name, const QVariant & val ) + + + sort + sort + () + + + toString + toString + ( QSqlRecord * rec, const QString & prefix, const QString & fieldSep, const QString & sep ) + + + toString + toString-3 + ( const QString & prefix, QSqlField * field, const QString & fieldSep ) + + + toString + toString-4 + ( const QSqlIndex & i, QSqlRecord * rec, const QString & prefix, const QString & fieldSep, const QString & sep ) + + + update + update + ( bool invalidate = true ) + + + update + update-2 + ( const QString & filter, bool invalidate = true ) + + + value + value + ( int i ) + + + value + value-2 + ( const QString & name ) + + + operator= + operator-eq + ( const Q3SqlCursor & other ) + + + + Q3SqlEditorFactory + q3sqleditorfactory.html + + Q3SqlEditorFactory + Q3SqlEditorFactory + ( QObject * parent = 0 ) + + + createEditor + createEditor + ( QWidget * parent, const QVariant & variant ) + + + createEditor + createEditor-2 + ( QWidget * parent, const QSqlField * field ) + + + defaultFactory + defaultFactory + () + + + installDefaultFactory + installDefaultFactory + ( Q3SqlEditorFactory * factory ) + + + + Q3SqlFieldInfo + q3sqlfieldinfo.html + + Q3SqlFieldInfo + Q3SqlFieldInfo + ( const QString & name = QString() + + + Q3SqlFieldInfo + Q3SqlFieldInfo-2 + ( const QSqlField & other ) + + + defaultValue + defaultValue + () + + + isCalculated + isCalculated + () + + + isGenerated + isGenerated + () + + + isRequired + isRequired + () + + + isTrim + isTrim + () + + + length + length + () + + + name + name + () + + + precision + precision + () + + + setCalculated + setCalculated + ( bool calculated ) + + + setGenerated + setGenerated + ( bool generated ) + + + setTrim + setTrim + ( bool trim ) + + + toField + toField + () + + + Type + type + Q3SqlFieldInfo::type() + + + typeID + typeID + () + + + operator== + operator-eq-eq + ( const Q3SqlFieldInfo & other ) + + + + Q3SqlForm + q3sqlform.html + + Q3SqlForm + Q3SqlForm + ( QObject * parent = 0 ) + + + clear + clear + () + + + clearValues + clearValues + () + + + count + count + () + + + fieldToWidget + fieldToWidget + ( QSqlField * field ) + + + insert + insert + ( QWidget * widget, const QString & field ) + + + insert + insert-2 + ( QWidget * widget, QSqlField * field ) + + + installPropertyMap + installPropertyMap + ( Q3SqlPropertyMap * pmap ) + + + readField + readField + ( QWidget * widget ) + + + readFields + readFields + () + + + remove + remove + ( QWidget * widget ) + + + remove + remove-2 + ( const QString & field ) + + + setRecord + setRecord + ( QSqlRecord * buf ) + + + widget + widget + ( int i ) + + + widgetToField + widgetToField + ( QWidget * widget ) + + + writeField + writeField + ( QWidget * widget ) + + + writeFields + writeFields + () + + + + Q3SqlPropertyMap + q3sqlpropertymap.html + + Q3SqlPropertyMap + Q3SqlPropertyMap + () + + + defaultMap + defaultMap + () + + + insert + insert + ( const QString & classname, const QString & property ) + + + installDefaultMap + installDefaultMap + ( Q3SqlPropertyMap * map ) + + + property + property + ( QWidget * widget ) + + + remove + remove + ( const QString & classname ) + + + setProperty + setProperty + ( QWidget * widget, const QVariant & value ) + + + + Q3SqlRecordInfo + q3sqlrecordinfo.html + + Q3SqlRecordInfo + Q3SqlRecordInfo + () + + + Q3SqlRecordInfo + Q3SqlRecordInfo-2 + ( const Q3SqlFieldInfoList & other ) + + + Q3SqlRecordInfo + Q3SqlRecordInfo-3 + ( const QSqlRecord & other ) + + + contains + contains + ( const QString & fieldName ) + + + find + find + ( const QString & fieldName ) + + + toRecord + toRecord + () + + + + Q3SqlSelectCursor + q3sqlselectcursor.html + + Q3SqlSelectCursor + Q3SqlSelectCursor + ( const QString & query = QString() + + + Q3SqlSelectCursor + Q3SqlSelectCursor-2 + ( const Q3SqlSelectCursor & other ) + + + + Q3StoredDrag + q3storeddrag.html + + Q3StoredDrag + Q3StoredDrag + ( const char * mimeType, QWidget * dragSource = 0, const char * name = 0 ) + + + encodedData + encodedData + ( const char * format ) + + + setEncodedData + setEncodedData + ( const QByteArray & data ) + + + + Q3StrIList + q3strilist.html + + Q3StrIList + Q3StrIList + ( bool deepCopies = true ) + + + + Q3StrList + q3strlist.html + + Q3StrList + Q3StrList + ( bool deepCopies = true ) + + + Q3StrList + Q3StrList-2 + ( const Q3StrList & list ) + + + Q3StrList + Q3StrList-3 + ( const QList<QByteArray> & list ) + + + operator + operator-QList-ltQByteArray-gt + QList<QByteArray>() + + + operator= + operator-eq + ( const Q3StrList & list ) + + + operator= + operator-eq-2 + ( const QList<QByteArray> & list ) + + + + Q3StrListIterator + q3strlistiterator.html + + + Q3StyleSheet + q3stylesheet.html + + Q3StyleSheet + Q3StyleSheet + ( QObject * parent = 0, const char * name = 0 ) + + + convertFromPlainText + convertFromPlainText + ( const QString & plain, Q3StyleSheetItem::WhiteSpaceMode mode = Q3StyleSheetItem::WhiteSpacePre ) + + + defaultSheet + defaultSheet + () + + + error + error + ( const QString & msg ) + + + escape + escape + ( const QString & plain ) + + + item + item + ( const QString & name ) + + + item + item-2 + ( const QString & name ) + + + mightBeRichText + mightBeRichText + ( const QString & text ) + + + scaleFont + scaleFont + ( QFont & font, int logicalSize ) + + + setDefaultSheet + setDefaultSheet + ( Q3StyleSheet * sheet ) + + + + Q3StyleSheetItem + q3stylesheetitem.html + + DisplayMode + DisplayMode-enum + + + + ListStyle + ListStyle-enum + + + + Margin + Margin-enum + + + + VerticalAlignment + VerticalAlignment-enum + + + + WhiteSpaceMode + WhiteSpaceMode-enum + + + + Q3StyleSheetItem + Q3StyleSheetItem + ( Q3StyleSheet * parent, const QString & name ) + + + Q3StyleSheetItem + Q3StyleSheetItem-2 + ( const Q3StyleSheetItem & other ) + + + alignment + alignment + () + + + allowedInContext + allowedInContext + ( const Q3StyleSheetItem * s ) + + + color + color + () + + + contexts + contexts + () + + + definesFontItalic + definesFontItalic + () + + + definesFontStrikeOut + definesFontStrikeOut + () + + + definesFontUnderline + definesFontUnderline + () + + + displayMode + displayMode + () + + + fontFamily + fontFamily + () + + + fontItalic + fontItalic + () + + + fontSize + fontSize + () + + + fontStrikeOut + fontStrikeOut + () + + + fontUnderline + fontUnderline + () + + + fontWeight + fontWeight + () + + + isAnchor + isAnchor + () + + + lineSpacing + lineSpacing + () + + + listStyle + listStyle + () + + + logicalFontSize + logicalFontSize + () + + + logicalFontSizeStep + logicalFontSizeStep + () + + + margin + margin + ( Margin m ) + + + name + name + () + + + numberOfColumns + numberOfColumns + () + + + selfNesting + selfNesting + () + + + setAlignment + setAlignment + ( int f ) + + + setAnchor + setAnchor + ( bool anc ) + + + setColor + setColor + ( const QColor & c ) + + + setContexts + setContexts + ( const QString & c ) + + + setDisplayMode + setDisplayMode + ( DisplayMode m ) + + + setFontFamily + setFontFamily + ( const QString & fam ) + + + setFontItalic + setFontItalic + ( bool italic ) + + + setFontSize + setFontSize + ( int s ) + + + setFontStrikeOut + setFontStrikeOut + ( bool strikeOut ) + + + setFontUnderline + setFontUnderline + ( bool underline ) + + + setFontWeight + setFontWeight + ( int w ) + + + setListStyle + setListStyle + ( ListStyle s ) + + + setLogicalFontSize + setLogicalFontSize + ( int s ) + + + setLogicalFontSizeStep + setLogicalFontSizeStep + ( int s ) + + + setMargin + setMargin + ( Margin m, int v ) + + + setNumberOfColumns + setNumberOfColumns + ( int ncols ) + + + setSelfNesting + setSelfNesting + ( bool nesting ) + + + setVerticalAlignment + setVerticalAlignment + ( VerticalAlignment valign ) + + + setWhiteSpaceMode + setWhiteSpaceMode + ( WhiteSpaceMode m ) + + + styleSheet + styleSheet + () + + + styleSheet + styleSheet-2 + () + + + verticalAlignment + verticalAlignment + () + + + whiteSpaceMode + whiteSpaceMode + () + + + operator= + operator-eq + ( const Q3StyleSheetItem & other ) + + + + Q3SyntaxHighlighter + q3syntaxhighlighter.html + + Q3SyntaxHighlighter + Q3SyntaxHighlighter + ( Q3TextEdit * textEdit ) + + + currentParagraph + currentParagraph + () + + + highlightParagraph + highlightParagraph + ( const QString & text, int endStateOfLastPara ) + + + rehighlight + rehighlight + () + + + setFormat + setFormat + ( int start, int count, const QFont & font, const QColor & color ) + + + setFormat + setFormat-2 + ( int start, int count, const QColor & color ) + + + setFormat + setFormat-3 + ( int start, int count, const QFont & font ) + + + textEdit + textEdit + () + + + isTabEnabled + isTabEnabled-2 + ( const char * name ) + + + setTabEnabled + setTabEnabled-2 + ( const char * name, bool enable ) + + + + Q3TabDialog + q3tabdialog.html + + Q3TabDialog + Q3TabDialog + ( QWidget * parent = 0, const char * name = 0, bool modal = false, Qt::WindowFlags f = 0 ) + + + aboutToShow + aboutToShow + () + + + addTab + addTab + ( QWidget * child, const QString & label ) + + + addTab + addTab-2 + ( QWidget * child, const QIcon & iconset, const QString & label ) + + + applyButtonPressed + applyButtonPressed + () + + + cancelButtonPressed + cancelButtonPressed + () + + + changeTab + changeTab + ( QWidget * w, const QIcon & iconset, const QString & label ) + + + changeTab + changeTab-2 + ( QWidget * w, const QString & label ) + + + currentChanged + currentChanged + ( QWidget * widget ) + + + currentPage + currentPage + () + + + defaultButtonPressed + defaultButtonPressed + () + + + hasApplyButton + hasApplyButton + () + + + hasCancelButton + hasCancelButton + () + + + hasDefaultButton + hasDefaultButton + () + + + hasHelpButton + hasHelpButton + () + + + hasOkButton + hasOkButton + () + + + helpButtonPressed + helpButtonPressed + () + + + insertTab + insertTab + ( QWidget * child, const QString & label, int index = -1 ) + + + insertTab + insertTab-2 + ( QWidget * child, const QIcon & iconset, const QString & label, int index = -1 ) + + + isTabEnabled + isTabEnabled + ( QWidget * w ) + + + removePage + removePage + ( QWidget * w ) + + + selected + selected + ( const QString & name ) + + + setApplyButton + setApplyButton + ( const QString & text ) + + + setApplyButton + setApplyButton-2 + () + + + setCancelButton + setCancelButton + ( const QString & text ) + + + setCancelButton + setCancelButton-2 + () + + + setDefaultButton + setDefaultButton + ( const QString & text ) + + + setDefaultButton + setDefaultButton-2 + () + + + setFont + setFont + ( const QFont & font ) + + + setHelpButton + setHelpButton + ( const QString & text ) + + + setHelpButton + setHelpButton-2 + () + + + setOkButton + setOkButton + ( const QString & text ) + + + setOkButton + setOkButton-2 + () + + + setTabBar + setTabBar + ( QTabBar * tb ) + + + setTabEnabled + setTabEnabled + ( QWidget * w, bool enable ) + + + showPage + showPage + ( QWidget * w ) + + + tabBar + tabBar + () + + + tabLabel + tabLabel + ( QWidget * w ) + + + + Q3Table + q3table.html + + EditMode + EditMode-enum + + + + FocusStyle + FocusStyle-enum + + + + SelectionMode + SelectionMode-enum + + + + Q3Table + Q3Table + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3Table + Q3Table-2 + ( int numRows, int numCols, QWidget * parent = 0, const char * name = 0 ) + + + activateNextCell + activateNextCell + () + + + addSelection + addSelection + ( const Q3TableSelection & s ) + + + adjustColumn + adjustColumn + ( int col ) + + + adjustRow + adjustRow + ( int row ) + + + beginEdit + beginEdit + ( int row, int col, bool replace ) + + + cellGeometry + cellGeometry + ( int row, int col ) + + + cellRect + cellRect + ( int row, int col ) + + + cellWidget + cellWidget + ( int row, int col ) + + + clearCell + clearCell + ( int row, int col ) + + + clearCellWidget + clearCellWidget + ( int row, int col ) + + + clearSelection + clearSelection + ( bool repaint = true ) + + + clicked + clicked + ( int row, int col, int button, const QPoint & mousePos ) + + + columnAt + columnAt + ( int x ) + + + columnClicked + columnClicked + ( int col ) + + + columnIndexChanged + columnIndexChanged + ( int section, int fromIndex, int toIndex ) + + + columnPos + columnPos + ( int col ) + + + columnWidth + columnWidth + ( int col ) + + + columnWidthChanged + columnWidthChanged + ( int col ) + + + contentsDragEnterEvent + contentsDragEnterEvent + ( QDragEnterEvent * e ) + + + contentsDragLeaveEvent + contentsDragLeaveEvent + ( QDragLeaveEvent * e ) + + + contentsDragMoveEvent + contentsDragMoveEvent + ( QDragMoveEvent * e ) + + + contentsDropEvent + contentsDropEvent + ( QDropEvent * e ) + + + contextMenuRequested + contextMenuRequested + ( int row, int col, const QPoint & pos ) + + + createEditor + createEditor + ( int row, int col, bool initFromCell ) + + + currEditCol + currEditCol + () + + + currEditRow + currEditRow + () + + + currentChanged + currentChanged + ( int row, int col ) + + + currentColumn + currentColumn + () + + + currentRow + currentRow + () + + + currentSelection + currentSelection + () + + + doubleClicked + doubleClicked + ( int row, int col, int button, const QPoint & mousePos ) + + + dragEnabled + dragEnabled + () + + + dragObject + dragObject + () + + + drawContents + drawContents + ( QPainter * p, int cx, int cy, int cw, int ch ) + + + dropped + dropped + ( QDropEvent * e ) + + + editCell + editCell + ( int row, int col, bool replace = false ) + + + editMode + editMode + () + + + endEdit + endEdit + ( int row, int col, bool accept, bool replace ) + + + ensureCellVisible + ensureCellVisible + ( int row, int col ) + + + hideColumn + hideColumn + ( int col ) + + + hideRow + hideRow + ( int row ) + + + horizontalHeader + horizontalHeader + () + + + indexOf + indexOf + ( int row, int col ) + + + insertColumns + insertColumns + ( int col, int count = 1 ) + + + insertRows + insertRows + ( int row, int count = 1 ) + + + insertWidget + insertWidget + ( int row, int col, QWidget * w ) + + + isColumnHidden + isColumnHidden + ( int col ) + + + isColumnReadOnly + isColumnReadOnly + ( int col ) + + + isColumnSelected + isColumnSelected + ( int col, bool full = false ) + + + isColumnStretchable + isColumnStretchable + ( int col ) + + + isEditing + isEditing + () + + + isRowHidden + isRowHidden + ( int row ) + + + isRowReadOnly + isRowReadOnly + ( int row ) + + + isRowSelected + isRowSelected + ( int row, bool full = false ) + + + isRowStretchable + isRowStretchable + ( int row ) + + + isSelected + isSelected + ( int row, int col ) + + + item + item + ( int row, int col ) + + + paintCell + paintCell + ( QPainter * p, int row, int col, const QRect & cr, bool selected, const QColorGroup & cg ) + + + paintCell + paintCell-2 + ( QPainter * p, int row, int col, const QRect & cr, bool selected ) + + + paintEmptyArea + paintEmptyArea + ( QPainter * p, int cx, int cy, int cw, int ch ) + + + paintFocus + paintFocus + ( QPainter * p, const QRect & cr ) + + + pixmap + pixmap + ( int row, int col ) + + + pressed + pressed + ( int row, int col, int button, const QPoint & mousePos ) + + + removeColumn + removeColumn + ( int col ) + + + removeColumns + removeColumns + ( const Q3MemArray<int> & cols ) + + + removeRow + removeRow + ( int row ) + + + removeRows + removeRows + ( const Q3MemArray<int> & rows ) + + + removeSelection + removeSelection + ( const Q3TableSelection & s ) + + + removeSelection + removeSelection-2 + ( int num ) + + + repaintSelections + repaintSelections + () + + + resizeData + resizeData + ( int len ) + + + rowAt + rowAt + ( int y ) + + + rowHeight + rowHeight + ( int row ) + + + rowHeightChanged + rowHeightChanged + ( int row ) + + + rowIndexChanged + rowIndexChanged + ( int section, int fromIndex, int toIndex ) + + + rowPos + rowPos + ( int row ) + + + selectCells + selectCells + ( int start_row, int start_col, int end_row, int end_col ) + + + selectColumn + selectColumn + ( int col ) + + + selectRow + selectRow + ( int row ) + + + selection + selection + ( int num ) + + + selectionChanged + selectionChanged + () + + + setCellContentFromEditor + setCellContentFromEditor + ( int row, int col ) + + + setCellWidget + setCellWidget + ( int row, int col, QWidget * e ) + + + setColumnLabels + setColumnLabels + ( const QStringList & labels ) + + + setColumnReadOnly + setColumnReadOnly + ( int col, bool ro ) + + + setColumnStretchable + setColumnStretchable + ( int col, bool stretch ) + + + setColumnWidth + setColumnWidth + ( int col, int w ) + + + setCurrentCell + setCurrentCell + ( int row, int col ) + + + setDragEnabled + setDragEnabled + ( bool b ) + + + setEditMode + setEditMode + ( EditMode mode, int row, int col ) + + + setItem + setItem + ( int row, int col, Q3TableItem * item ) + + + setLeftMargin + setLeftMargin + ( int m ) + + + setPixmap + setPixmap + ( int row, int col, const QPixmap & pix ) + + + setRowHeight + setRowHeight + ( int row, int h ) + + + setRowLabels + setRowLabels + ( const QStringList & labels ) + + + setRowReadOnly + setRowReadOnly + ( int row, bool ro ) + + + setRowStretchable + setRowStretchable + ( int row, bool stretch ) + + + setText + setText + ( int row, int col, const QString & text ) + + + setTopMargin + setTopMargin + ( int m ) + + + showColumn + showColumn + ( int col ) + + + showRow + showRow + ( int row ) + + + sortColumn + sortColumn + ( int col, bool ascending = true, bool wholeRows = false ) + + + startDrag + startDrag + () + + + swapCells + swapCells + ( int row1, int col1, int row2, int col2 ) + + + swapColumns + swapColumns + ( int col1, int col2, bool swapHeader = false ) + + + swapRows + swapRows + ( int row1, int row2, bool swapHeader = false ) + + + takeItem + takeItem + ( Q3TableItem * i ) + + + text + text + ( int row, int col ) + + + updateCell + updateCell + ( int row, int col ) + + + updateHeaderStates + updateHeaderStates + () + + + valueChanged + valueChanged + ( int row, int col ) + + + verticalHeader + verticalHeader + () + + + + Q3TableItem + q3tableitem.html + + EditType + EditType-enum + + + + Q3TableItem + Q3TableItem + ( Q3Table * table, EditType et ) + + + Q3TableItem + Q3TableItem-2 + ( Q3Table * table, EditType et, const QString & text ) + + + Q3TableItem + Q3TableItem-3 + ( Q3Table * table, EditType et, const QString & text, const QPixmap & p ) + + + alignment + alignment + () + + + col + col + () + + + colSpan + colSpan + () + + + createEditor + createEditor + () + + + editType + editType + () + + + isEnabled + isEnabled + () + + + isReplaceable + isReplaceable + () + + + key + key + () + + + paint + paint + ( QPainter * p, const QColorGroup & cg, const QRect & cr, bool selected ) + + + pixmap + pixmap + () + + + row + row + () + + + rowSpan + rowSpan + () + + + rtti + rtti + () + + + setCol + setCol + ( int c ) + + + setContentFromEditor + setContentFromEditor + ( QWidget * w ) + + + setEnabled + setEnabled + ( bool b ) + + + setPixmap + setPixmap + ( const QPixmap & p ) + + + setReplaceable + setReplaceable + ( bool b ) + + + setRow + setRow + ( int r ) + + + setSpan + setSpan + ( int rs, int cs ) + + + setText + setText + ( const QString & str ) + + + setWordWrap + setWordWrap + ( bool b ) + + + sizeHint + sizeHint + () + + + table + table + () + + + text + text + () + + + wordWrap + wordWrap + () + + + + Q3TableSelection + q3tableselection.html + + Q3TableSelection + Q3TableSelection + () + + + Q3TableSelection + Q3TableSelection-2 + ( int start_row, int start_col, int end_row, int end_col ) + + + anchorCol + anchorCol + () + + + anchorRow + anchorRow + () + + + bottomRow + bottomRow + () + + + expandTo + expandTo + ( int row, int col ) + + + init + init + ( int row, int col ) + + + isActive + isActive + () + + + isEmpty + isEmpty + () + + + leftCol + leftCol + () + + + numCols + numCols + () + + + numRows + numRows + () + + + rightCol + rightCol + () + + + topRow + topRow + () + + + operator!= + operator-not-eq + ( const Q3TableSelection & s ) + + + operator== + operator-eq-eq + ( const Q3TableSelection & s ) + + + + Q3TextBrowser + q3textbrowser.html + + Q3TextBrowser + Q3TextBrowser + ( QWidget * parent = 0, const char * name = 0 ) + + + anchorClicked + anchorClicked + ( const QString & name, const QString & link ) + + + backward + backward + () + + + backwardAvailable + backwardAvailable + ( bool available ) + + + forward + forward + () + + + forwardAvailable + forwardAvailable + ( bool available ) + + + highlighted + highlighted + ( const QString & link ) + + + home + home + () + + + keyPressEvent + keyPressEvent + ( QKeyEvent * e ) + + + linkClicked + linkClicked + ( const QString & link ) + + + reload + reload + () + + + setText + setText + ( const QString & txt ) + + + sourceChanged + sourceChanged + ( const QString & src ) + + + + Q3TextDrag + q3textdrag.html + + Q3TextDrag + Q3TextDrag + ( const QString & text, QWidget * dragSource = 0, const char * name = 0 ) + + + Q3TextDrag + Q3TextDrag-2 + ( QWidget * dragSource = 0, const char * name = 0 ) + + + canDecode + canDecode + ( const QMimeSource * source ) + + + decode + decode + ( const QMimeSource * source, QString & string ) + + + decode + decode-2 + ( const QMimeSource * source, QString & string, QString & subtype ) + + + setSubtype + setSubtype + ( const QString & subtype ) + + + setText + setText + ( const QString & text ) + + + + Q3TextEdit + q3textedit.html + + CursorAction + CursorAction-enum + + + + KeyboardAction + KeyboardAction-enum + + + + VerticalAlignment + VerticalAlignment-enum + + + + WordWrap + WordWrap-enum + + + + WrapPolicy + WrapPolicy-enum + + + + TextFormat + textFormat-prop + + + + Q3TextEdit + Q3TextEdit + ( const QString & text, const QString & context = QString() + + + Q3TextEdit + Q3TextEdit-2 + ( QWidget * parent = 0, const char * name = 0 ) + + + alignment + alignment + () + + + anchorAt + anchorAt + ( const QPoint & pos, Qt::AnchorAttribute attr = Qt::AnchorHref ) + + + append + append + ( const QString & text ) + + + bold + bold + () + + + charAt + charAt + ( const QPoint & pos, int * para ) + + + clear + clear + () + + + clearParagraphBackground + clearParagraphBackground + ( int para ) + + + clicked + clicked + ( int para, int pos ) + + + color + color + () + + + context + context + () + + + copy + copy + () + + + copyAvailable + copyAvailable + ( bool yes ) + + + createPopupMenu + createPopupMenu + ( const QPoint & pos ) + + + createPopupMenu + createPopupMenu-2 + () + + + currentAlignmentChanged + currentAlignmentChanged + ( int a ) + + + currentColorChanged + currentColorChanged + ( const QColor & c ) + + + currentFont + currentFont + () + + + currentFontChanged + currentFontChanged + ( const QFont & f ) + + + currentVerticalAlignmentChanged + currentVerticalAlignmentChanged + ( Q3TextEdit::VerticalAlignment a ) + + + cursorPositionChanged + cursorPositionChanged + ( int para, int pos ) + + + cut + cut + () + + + del + del + () + + + doKeyboardAction + doKeyboardAction + ( Q3TextEdit::KeyboardAction action ) + + + doubleClicked + doubleClicked + ( int para, int pos ) + + + ensureCursorVisible + ensureCursorVisible + () + + + family + family + () + + + find + find + ( const QString & expr, bool cs, bool wo, bool forward = true, int * para = 0, int * index = 0 ) + + + focusNextPrevChild + focusNextPrevChild + ( bool n ) + + + font + font + () + + + getCursorPosition + getCursorPosition + ( int * para, int * index ) + + + getSelection + getSelection + ( int * paraFrom, int * indexFrom, int * paraTo, int * indexTo, int selNum = 0 ) + + + heightForWidth + heightForWidth + ( int w ) + + + insert + insert + ( const QString & text, uint insertionFlags = CheckNewLines | RemoveSelected ) + + + insert + insert-2 + ( const QString & text, bool indent, bool checkNewLine = true, bool removeSelected = true ) + + + insertAt + insertAt + ( const QString & text, int para, int index ) + + + insertParagraph + insertParagraph + ( const QString & text, int para ) + + + isRedoAvailable + isRedoAvailable + () + + + isUndoAvailable + isUndoAvailable + () + + + italic + italic + () + + + keyPressEvent + keyPressEvent + ( QKeyEvent * e ) + + + lineOfChar + lineOfChar + ( int para, int index ) + + + lines + lines + () + + + linesOfParagraph + linesOfParagraph + ( int para ) + + + mimeSourceFactory + mimeSourceFactory + () + + + modificationChanged + modificationChanged + ( bool m ) + + + moveCursor + moveCursor + ( Q3TextEdit::CursorAction action, bool select ) + + + paragraphAt + paragraphAt + ( const QPoint & pos ) + + + paragraphBackgroundColor + paragraphBackgroundColor + ( int para ) + + + paragraphLength + paragraphLength + ( int para ) + + + paragraphRect + paragraphRect + ( int para ) + + + paragraphs + paragraphs + () + + + paste + paste + () + + + pasteSubType + pasteSubType + ( const QByteArray & subtype ) + + + placeCursor + placeCursor + ( const QPoint & pos, Q3TextCursor * c = 0 ) + + + pointSize + pointSize + () + + + redo + redo + () + + + redoAvailable + redoAvailable + ( bool yes ) + + + removeParagraph + removeParagraph + ( int para ) + + + removeSelectedText + removeSelectedText + ( int selNum = 0 ) + + + removeSelection + removeSelection + ( int selNum = 0 ) + + + repaintChanged + repaintChanged + () + + + returnPressed + returnPressed + () + + + scrollToAnchor + scrollToAnchor + ( const QString & name ) + + + scrollToBottom + scrollToBottom + () + + + selectAll + selectAll + ( bool select = true ) + + + selectionChanged + selectionChanged + () + + + setAlignment + setAlignment + ( int a ) + + + setBold + setBold + ( bool b ) + + + setColor + setColor + ( const QColor & c ) + + + setCurrentFont + setCurrentFont + ( const QFont & f ) + + + setCursorPosition + setCursorPosition + ( int para, int index ) + + + setFamily + setFamily + ( const QString & fontFamily ) + + + setItalic + setItalic + ( bool b ) + + + setMimeSourceFactory + setMimeSourceFactory + ( Q3MimeSourceFactory * factory ) + + + setParagraphBackgroundColor + setParagraphBackgroundColor + ( int para, const QColor & bg ) + + + setPointSize + setPointSize + ( int s ) + + + setSelection + setSelection + ( int paraFrom, int indexFrom, int paraTo, int indexTo, int selNum = 0 ) + + + setSelectionAttributes + setSelectionAttributes + ( int selNum, const QColor & back, bool invertText ) + + + setStyleSheet + setStyleSheet + ( Q3StyleSheet * styleSheet ) + + + setUnderline + setUnderline + ( bool b ) + + + setVerticalAlignment + setVerticalAlignment + ( Q3TextEdit::VerticalAlignment a ) + + + styleSheet + styleSheet + () + + + sync + sync + () + + + syntaxHighlighter + syntaxHighlighter + () + + + textChanged + textChanged + () + + + textCursor + textCursor + () + + + underline + underline + () + + + undo + undo + () + + + undoAvailable + undoAvailable + ( bool yes ) + + + verticalAlignment + verticalAlignment + () + + + zoomIn + zoomIn + ( int range ) + + + zoomIn + zoomIn-2 + () + + + zoomOut + zoomOut + ( int range ) + + + zoomOut + zoomOut-2 + () + + + zoomTo + zoomTo + ( int size ) + + + Q3TextStream + Q3TextStream-4 + ( QString & str, int filemode ) + + + eof + eof + () + + + + Q3TextStream + q3textstream.html + + Encoding + Encoding-enum + + + + Q3TextStream + Q3TextStream + () + + + Q3TextStream + Q3TextStream-2 + ( QIODevice * iod ) + + + Q3TextStream + Q3TextStream-3 + ( QString * str, int filemode ) + + + Q3TextStream + Q3TextStream-5 + ( QByteArray & a, int mode ) + + + Q3TextStream + Q3TextStream-6 + ( FILE * fh, int mode ) + + + atEnd + atEnd + () + + + codec + codec + () + + + device + device + () + + + fill + fill + () + + + fill + fill-2 + ( int f ) + + + flags + flags + () + + + flags + flags-2 + ( int f ) + + + precision + precision + () + + + precision + precision-2 + ( int p ) + + + read + read + () + + + readLine + readLine + () + + + readRawBytes + readRawBytes + ( char * s, uint len ) + + + reset + reset + () + + + setCodec + setCodec + ( QTextCodec * codec ) + + + setDevice + setDevice + ( QIODevice * iod ) + + + setEncoding + setEncoding + ( Encoding e ) + + + setf + setf + ( int bits ) + + + setf + setf-2 + ( int bits, int mask ) + + + skipWhiteSpace + skipWhiteSpace + () + + + unsetDevice + unsetDevice + () + + + unsetf + unsetf + ( int bits ) + + + width + width + () + + + width + width-2 + ( int w ) + + + writeRawBytes + writeRawBytes + ( const char * s, uint len ) + + + operator<< + operator-lt-lt + ( QChar c ) + + + operator<< + operator-lt-lt-2 + ( char c ) + + + operator<< + operator-lt-lt-3 + ( signed short i ) + + + operator<< + operator-lt-lt-4 + ( unsigned short i ) + + + operator<< + operator-lt-lt-5 + ( signed int i ) + + + operator<< + operator-lt-lt-6 + ( unsigned int i ) + + + operator<< + operator-lt-lt-7 + ( signed long i ) + + + operator<< + operator-lt-lt-8 + ( unsigned long i ) + + + operator<< + operator-lt-lt-9 + ( float f ) + + + operator<< + operator-lt-lt-10 + ( double f ) + + + operator<< + operator-lt-lt-11 + ( const char * s ) + + + operator<< + operator-lt-lt-12 + ( const Q3CString & s ) + + + operator<< + operator-lt-lt-13 + ( const QString & s ) + + + operator<< + operator-lt-lt-14 + ( void * ptr ) + + + operator>> + operator-gt-gt + ( QChar & c ) + + + operator>> + operator-gt-gt-2 + ( char & c ) + + + operator>> + operator-gt-gt-3 + ( signed short & i ) + + + operator>> + operator-gt-gt-4 + ( unsigned short & i ) + + + operator>> + operator-gt-gt-5 + ( signed int & i ) + + + operator>> + operator-gt-gt-6 + ( unsigned int & i ) + + + operator>> + operator-gt-gt-7 + ( signed long & i ) + + + operator>> + operator-gt-gt-8 + ( unsigned long & i ) + + + operator>> + operator-gt-gt-9 + ( float & f ) + + + operator>> + operator-gt-gt-10 + ( double & f ) + + + operator>> + operator-gt-gt-11 + ( char * s ) + + + operator>> + operator-gt-gt-12 + ( QString & str ) + + + operator>> + operator-gt-gt-13 + ( Q3CString & str ) + + + + Q3TextView + q3textview.html + + + Q3TimeEdit + q3timeedit.html + + Display + Display-enum + + + + Q3TimeEdit + Q3TimeEdit + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3TimeEdit + Q3TimeEdit-2 + ( const QTime & time, QWidget * parent = 0, const char * name = 0 ) + + + sectionFormattedText + sectionFormattedText + ( int sec ) + + + separator + separator + () + + + setHour + setHour + ( int h ) + + + setMinute + setMinute + ( int m ) + + + setRange + setRange + ( const QTime & min, const QTime & max ) + + + setSecond + setSecond + ( int s ) + + + setSeparator + setSeparator + ( const QString & s ) + + + valueChanged + valueChanged + ( const QTime & time ) + + + + Q3ToolBar + q3toolbar.html + + Q3ToolBar + Q3ToolBar + ( const QString & label, Q3MainWindow * parent, Qt::ToolBarDock dock = Qt::DockTop, bool newLine = false, const char * name = 0 ) + + + Q3ToolBar + Q3ToolBar-2 + ( const QString & label, Q3MainWindow * mainWindow, QWidget * parent, bool newLine = false, const char * name = 0, Qt::WindowFlags f = 0 ) + + + Q3ToolBar + Q3ToolBar-3 + ( Q3MainWindow * parent = 0, const char * name = 0 ) + + + addSeparator + addSeparator + () + + + clear + clear + () + + + mainWindow + mainWindow + () + + + setStretchableWidget + setStretchableWidget + ( QWidget * w ) + + + setFilenames + setFilenames-2x + ( const QStringList & list ) + + + + Q3UriDrag + q3uridrag.html + + Q3UriDrag + Q3UriDrag + ( const Q3StrList & uris, QWidget * dragSource = 0, const char * name = 0 ) + + + Q3UriDrag + Q3UriDrag-2 + ( QWidget * dragSource = 0, const char * name = 0 ) + + + canDecode + canDecode + ( const QMimeSource * source ) + + + decode + decode + ( const QMimeSource * source, Q3StrList & list ) + + + decodeLocalFiles + decodeLocalFiles + ( const QMimeSource * source, QStringList & list ) + + + decodeToUnicodeUris + decodeToUnicodeUris + ( const QMimeSource * source, QStringList & list ) + + + localFileToUri + localFileToUri + ( const QString & filename ) + + + setFileNames + setFileNames + ( const QStringList & filenames ) + + + setFileNames + setFileNames-2 + ( const QString & name ) + + + setFilenames + setFilenamesx + ( const QString & name ) + + + setUnicodeUris + setUnicodeUris + ( const QStringList & list ) + + + setUris + setUris + ( const QList<QByteArray> & list ) + + + unicodeUriToUri + unicodeUriToUri + ( const QString & string ) + + + uriToLocalFile + uriToLocalFile + ( const char * string ) + + + uriToUnicodeUri + uriToUnicodeUri + ( const char * string ) + + + + Q3Url + q3url.html + + Q3Url + Q3Url + () + + + Q3Url + Q3Url-2 + ( const QString & url ) + + + Q3Url + Q3Url-3 + ( const Q3Url & url ) + + + Q3Url + Q3Url-4 + ( const Q3Url & url, const QString & relUrl, bool checkSlash = false ) + + + addPath + addPath + ( const QString & pa ) + + + cdUp + cdUp + () + + + decode + decode + ( QString & url ) + + + dirPath + dirPath + () + + + encode + encode + ( QString & url ) + + + encodedPathAndQuery + encodedPathAndQuery + () + + + fileName + fileName + () + + + hasHost + hasHost + () + + + hasPassword + hasPassword + () + + + hasPath + hasPath + () + + + hasPort + hasPort + () + + + hasRef + hasRef + () + + + hasUser + hasUser + () + + + host + host + () + + + isLocalFile + isLocalFile + () + + + isRelativeUrl + isRelativeUrl + ( const QString & url ) + + + isValid + isValid + () + + + parse + parse + ( const QString & url ) + + + password + password + () + + + path + path + ( bool correct = true ) + + + port + port + () + + + protocol + protocol + () + + + query + query + () + + + ref + ref + () + + + reset + reset + () + + + setEncodedPathAndQuery + setEncodedPathAndQuery + ( const QString & pathAndQuery ) + + + setFileName + setFileName + ( const QString & name ) + + + setHost + setHost + ( const QString & host ) + + + setPassword + setPassword + ( const QString & pass ) + + + setPath + setPath + ( const QString & path ) + + + setPort + setPort + ( int port ) + + + setProtocol + setProtocol + ( const QString & protocol ) + + + setQuery + setQuery + ( const QString & txt ) + + + setRef + setRef + ( const QString & txt ) + + + setUser + setUser + ( const QString & user ) + + + toString + toString + ( bool encodedPath = false, bool forcePrependProtocol = true ) + + + user + user + () + + + operator + operator-QString + QString() + + + operator= + operator-eq + ( const Q3Url & url ) + + + operator= + operator-eq-2 + ( const QString & url ) + + + operator== + operator-eq-eq + ( const Q3Url & url ) + + + operator== + operator-eq-eq-2 + ( const QString & url ) + + + + Q3UrlOperator + q3urloperator.html + + Q3UrlOperator + Q3UrlOperator + () + + + Q3UrlOperator + Q3UrlOperator-2 + ( const QString & url ) + + + Q3UrlOperator + Q3UrlOperator-3 + ( const Q3UrlOperator & url ) + + + Q3UrlOperator + Q3UrlOperator-4 + ( const Q3UrlOperator & url, const QString & relUrl, bool checkSlash = false ) + + + clearEntries + clearEntries + () + + + connectionStateChanged + connectionStateChanged + ( int state, const QString & data ) + + + copy + copy + ( const QString & from, const QString & to, bool move = false, bool toPath = true ) + + + copy + copy-2 + ( const QStringList & files, const QString & dest, bool move = false ) + + + createdDirectory + createdDirectory + ( const QUrlInfo & i, Q3NetworkOperation * op ) + + + data + data + ( const QByteArray & data, Q3NetworkOperation * op ) + + + dataTransferProgress + dataTransferProgress + ( int bytesDone, int bytesTotal, Q3NetworkOperation * op ) + + + deleteNetworkProtocol + deleteNetworkProtocol + () + + + finished + finished + ( Q3NetworkOperation * op ) + + + get + get + ( const QString & location = QString() + + + getNetworkProtocol + getNetworkProtocol + () + + + info + info + ( const QString & entry ) + + + isDir + isDir + ( bool * ok = 0 ) + + + itemChanged + itemChanged + ( Q3NetworkOperation * op ) + + + listChildren + listChildren + () + + + mkdir + mkdir + ( const QString & dirname ) + + + nameFilter + nameFilter + () + + + newChildren + newChildren + ( const Q3ValueList<QUrlInfo> & i, Q3NetworkOperation * op ) + + + put + put + ( const QByteArray & data, const QString & location = QString() + + + remove + remove + ( const QString & filename ) + + + removed + removed + ( Q3NetworkOperation * op ) + + + rename + rename + ( const QString & oldname, const QString & newname ) + + + setNameFilter + setNameFilter + ( const QString & nameFilter ) + + + start + start + ( Q3NetworkOperation * op ) + + + startedNextCopy + startedNextCopy + ( const Q3PtrList<Q3NetworkOperation> & lst ) + + + stop + stop + () + + + operator= + operator-eq + ( const Q3UrlOperator & url ) + + + operator= + operator-eq-2 + ( const QString & url ) + + + + Q3ValueList + q3valuelist.html + + ConstIterator + ConstIterator-typedef + + + + Iterator + Iterator-typedef + + + + const_iterator + const_iterator-typedef + + + + const_pointer + const_pointer-typedef + + + + const_reference + const_reference-typedef + + + + iterator + iterator-typedefx + + + + pointer + pointer-typedef + + + + reference + reference-typedef + + + + size_type + size_type-typedef + + + + value_type + value_type-typedef + + + + Q3ValueList + Q3ValueList + () + + + Q3ValueList + Q3ValueList-2 + ( const Q3ValueList<T> & l ) + + + Q3ValueList + Q3ValueList-3 + ( const QLinkedList<T> & l ) + + + Q3ValueList + Q3ValueList-4 + ( const QList<T> & l ) + + + Q3ValueList + Q3ValueList-5 + ( const std::list<T> & l ) + + + append + append + ( const T & x ) + + + at + at + ( Q3ValueList<T>::size_type i ) + + + at + at-2 + ( Q3ValueList<T>::size_type i ) + + + contains + contains + ( const T & x ) + + + fromLast + fromLast + () + + + fromLast + fromLast-2 + () + + + insert + insert + ( Q3ValueList<T>::Iterator it, const T & x ) + + + insert + insert-2 + ( Q3ValueList<T>::Iterator pos, Q3ValueList<T>::size_type n, const T & x ) + + + prepend + prepend + ( const T & x ) + + + remove + remove + ( Q3ValueList<T>::Iterator it ) + + + remove + remove-2 + ( const T & x ) + + + operator + operator-QList-ltT-gt + QList<T>() + + + operator!= + operator-not-eq + ( const Q3ValueList<T> & l ) + + + operator+ + operator-2b + ( const Q3ValueList<T> & l ) + + + operator+= + operator-2b-eq + ( const Q3ValueList<T> & l ) + + + operator+= + operator-2b-eq-2 + ( const T & x ) + + + operator<< + operator-lt-lt + ( const T & x ) + + + operator= + operator-eq + ( const Q3ValueList<T> & l ) + + + operator= + operator-eq-2 + ( const QList<T> & l ) + + + operator= + operator-eq-3 + ( const std::list<T> & l ) + + + operator== + operator-eq-eq + ( const Q3ValueList<T> & l ) + + + operator== + operator-eq-eq-2 + ( const std::list<T> & l ) + + + operator[] + operator-5b-5d + ( Q3ValueList<T>::size_type i ) + + + operator[] + operator-5b-5d-2 + ( Q3ValueList<T>::size_type i ) + + + + Q3ValueListConstIterator + q3valuelistconstiterator.html + + Q3ValueListConstIterator + Q3ValueListConstIterator + () + + + Q3ValueListConstIterator + Q3ValueListConstIterator-2 + ( const Q3ValueListConstIterator & o ) + + + Q3ValueListConstIterator + Q3ValueListConstIterator-3 + ( const QLinkedList<T>::const_iterator & o ) + + + Q3ValueListConstIterator + Q3ValueListConstIterator-4 + ( const QLinkedList<T>::iterator & o ) + + + + Q3ValueListIterator + q3valuelistiterator.html + + Q3ValueListIterator + Q3ValueListIterator + () + + + Q3ValueListIterator + Q3ValueListIterator-2 + ( const Q3ValueListIterator & o ) + + + Q3ValueListIterator + Q3ValueListIterator-3 + ( const QLinkedList<T>::iterator & o ) + + + + Q3ValueStack + q3valuestack.html + + Q3ValueStack + Q3ValueStack + () + + + pop + pop + () + + + push + push + ( const T & d ) + + + top + top + () + + + top + top-2 + () + + + + Q3ValueVector + q3valuevector.html + + Q3ValueVector + Q3ValueVector + () + + + Q3ValueVector + Q3ValueVector-2 + ( const Q3ValueVector<T> & v ) + + + Q3ValueVector + Q3ValueVector-3 + ( QVector<T>::size_type n, const T & val = T() + + + Q3ValueVector + Q3ValueVector-4 + ( const std::vector<T> & v ) + + + at + at + ( int i, bool * ok = 0 ) + + + at + at-2 + ( int i, bool * ok = 0 ) + + + resize + resize + ( int n, const T & val = T() + + + operator= + operator-eq + ( const Q3ValueVector<T> & v ) + + + operator= + operator-eq-2 + ( const std::vector<T> & v ) + + + + Q3VBox + q3vbox.html + + Q3VBox + Q3VBox + ( QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + + Q3VButtonGroup + q3vbuttongroup.html + + Q3VButtonGroup + Q3VButtonGroup + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3VButtonGroup + Q3VButtonGroup-2 + ( const QString & title, QWidget * parent = 0, const char * name = 0 ) + + + + Q3VGroupBox + q3vgroupbox.html + + Q3VGroupBox + Q3VGroupBox + ( QWidget * parent = 0, const char * name = 0 ) + + + Q3VGroupBox + Q3VGroupBox-2 + ( const QString & title, QWidget * parent = 0, const char * name = 0 ) + + + + Q3WhatsThis + q3whatsthis.html + + Q3WhatsThis + Q3WhatsThis + ( QWidget * widget ) + + + add + add + ( QWidget * widget, const QString & text ) + + + clicked + clicked + ( const QString & href ) + + + display + display + ( const QString & text, const QPoint & pos = QCursor::pos() + + + enterWhatsThisMode + enterWhatsThisMode + () + + + inWhatsThisMode + inWhatsThisMode + () + + + leaveWhatsThisMode + leaveWhatsThisMode + ( const QString & text = QString() + + + remove + remove + ( QWidget * widget ) + + + text + text + ( const QPoint & pos ) + + + whatsThisButton + whatsThisButton + ( QWidget * parent ) + + + + Q3WidgetStack + q3widgetstack.html + + Q3WidgetStack + Q3WidgetStack + ( QWidget * parent, const char * name = 0, Qt::WindowFlags f = 0 ) + + + aboutToShow + aboutToShow + ( int id ) + + + aboutToShow + aboutToShow-2 + ( QWidget * widget ) + + + addWidget + addWidget + ( QWidget * w, int id = -1 ) + + + id + id + ( QWidget * widget ) + + + raiseWidget + raiseWidget + ( int id ) + + + raiseWidget + raiseWidget-2 + ( QWidget * w ) + + + removeWidget + removeWidget + ( QWidget * w ) + + + setChildGeometries + setChildGeometries + () + + + visibleWidget + visibleWidget + () + + + widget + widget + ( int id ) + + + setFinish + setFinish + ( QWidget * widget, bool finish ) + + + + Q3Wizard + q3wizard.html + + Q3Wizard + Q3Wizard + ( QWidget * parent = 0, const char * name = 0, bool modal = false, Qt::WindowFlags f = 0 ) + + + addPage + addPage + ( QWidget * page, const QString & title ) + + + appropriate + appropriate + ( QWidget * page ) + + + back + back + () + + + backButton + backButton + () + + + cancelButton + cancelButton + () + + + currentPage + currentPage + () + + + finishButton + finishButton + () + + + help + help + () + + + helpButton + helpButton + () + + + helpClicked + helpClicked + () + + + indexOf + indexOf + ( QWidget * page ) + + + insertPage + insertPage + ( QWidget * page, const QString & title, int index ) + + + layOutButtonRow + layOutButtonRow + ( QHBoxLayout * layout ) + + + layOutTitleRow + layOutTitleRow + ( QHBoxLayout * layout, const QString & title ) + + + next + next + () + + + nextButton + nextButton + () + + + page + page + ( int index ) + + + pageCount + pageCount + () + + + removePage + removePage + ( QWidget * page ) + + + selected + selected + ( const QString & title ) + + + setAppropriate + setAppropriate + ( QWidget * page, bool appropriate ) + + + setBackEnabled + setBackEnabled + ( QWidget * page, bool enable ) + + + setFinishEnabled + setFinishEnabled + ( QWidget * page, bool enable ) + + + setHelpEnabled + setHelpEnabled + ( QWidget * page, bool enable ) + + + setNextEnabled + setNextEnabled + ( QWidget * page, bool enable ) + + + setTitle + setTitle + ( QWidget * page, const QString & title ) + + + showPage + showPage + ( QWidget * page ) + + + title + title + ( QWidget * page ) + + + QAbstractButton + QAbstractButton-2 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + accel + accel + () + + + iconSet + iconSet + () + + + isOn + isOn + () + + + isToggleButton + isToggleButton + () + + + pixmap + pixmap + () + + + setAccel + setAccel + ( const QKeySequence & key ) + + + setIconSet + setIconSet + ( const QIcon & icon ) + + + setOn + setOn + ( bool b ) + + + setPixmap + setPixmap + ( const QPixmap & p ) + + + setToggleButton + setToggleButton + ( bool b ) + + + + QAbstractButton + qabstractbutton.html + + QAbstractButton + QAbstractButton + ( QWidget * parent = 0 ) + + + animateClick + animateClick + ( int msec = 100 ) + + + checkStateSet + checkStateSet + () + + + click + click + () + + + clicked + clicked + ( bool checked = false ) + + + group + group + () + + + hitButton + hitButton + ( const QPoint & pos ) + + + nextCheckState + nextCheckState + () + + + pressed + pressed + () + + + released + released + () + + + toggle + toggle + () + + + toggled + toggled + ( bool checked ) + + + QAbstractButton + QAbstractButton-2 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + accel + accel + () + + + iconSet + iconSet + () + + + isOn + isOn + () + + + isToggleButton + isToggleButton + () + + + pixmap + pixmap + () + + + setAccel + setAccel + ( const QKeySequence & key ) + + + setIconSet + setIconSet + ( const QIcon & icon ) + + + setOn + setOn + ( bool b ) + + + setPixmap + setPixmap + ( const QPixmap & p ) + + + setToggleButton + setToggleButton + ( bool b ) + + + + QAbstractEventDispatcher + qabstracteventdispatcher.html + + EventFilter + EventFilter-typedef + + + + TimerInfo + TimerInfo-typedef + + + + QAbstractEventDispatcher + QAbstractEventDispatcher + ( QObject * parent = 0 ) + + + aboutToBlock + aboutToBlock + () + + + awake + awake + () + + + filterEvent + filterEvent + ( void * message ) + + + flush + flush + () + + + hasPendingEvents + hasPendingEvents + () + + + instance + instance + ( QThread * thread = 0 ) + + + interrupt + interrupt + () + + + processEvents + processEvents + ( QEventLoop::ProcessEventsFlags flags ) + + + registerSocketNotifier + registerSocketNotifier + ( QSocketNotifier * notifier ) + + + registerTimer + registerTimer + ( int interval, QObject * object ) + + + registerTimer + registerTimer-2 + ( int timerId, int interval, QObject * object ) + + + registeredTimers + registeredTimers + ( QObject * object ) + + + setEventFilter + setEventFilter + ( EventFilter filter ) + + + unregisterSocketNotifier + unregisterSocketNotifier + ( QSocketNotifier * notifier ) + + + unregisterTimer + unregisterTimer + ( int timerId ) + + + unregisterTimers + unregisterTimers + ( QObject * object ) + + + wakeUp + wakeUp + () + + + + QAbstractExtensionFactory + qabstractextensionfactory.html + + extension + extension + ( QObject * object, const QString & iid ) + + + + QAbstractExtensionManager + qabstractextensionmanager.html + + extension + extension + ( QObject * object, const QString & iid ) + + + registerExtensions + registerExtensions + ( QAbstractExtensionFactory * factory, const QString & iid ) + + + unregisterExtensions + unregisterExtensions + ( QAbstractExtensionFactory * factory, const QString & iid ) + + + + QAbstractFileEngine::ExtensionOption + qabstractfileengine-extensionoption.html + + + QAbstractFileEngine::ExtensionReturn + qabstractfileengine-extensionreturn.html + + + QAbstractFileEngine + qabstractfileengine.html + + Extension + Extension-enum + + + + FileName + FileName-enum + + + + FileOwner + FileOwner-enum + + + + FileTime + FileTime-enum + + + + Iterator + Iterator-typedef + + + + QAbstractFileEngine + QAbstractFileEngine + () + + + atEnd + atEnd + () + + + beginEntryList + beginEntryList + ( QDir::Filters filters, const QStringList & filterNames ) + + + caseSensitive + caseSensitive + () + + + close + close + () + + + copy + copy + ( const QString & newName ) + + + create + create + ( const QString & fileName ) + + + entryList + entryList + ( QDir::Filters filters, const QStringList & filterNames ) + + + FileError + error + QAbstractFileEngine::error() + + + errorString + errorString + () + + + extension + extension + ( Extension extension, const ExtensionOption * option = 0, ExtensionReturn * output = 0 ) + + + fileFlags + fileFlags + ( FileFlags type = FileInfoAll ) + + + fileName + fileName + ( FileName file = DefaultName ) + + + fileTime + fileTime + ( FileTime time ) + + + flush + flush + () + + + handle + handle + () + + + isRelativePath + isRelativePath + () + + + isSequential + isSequential + () + + + link + link + ( const QString & newName ) + + + mkdir + mkdir + ( const QString & dirName, bool createParentDirectories ) + + + open + open + ( QIODevice::OpenMode mode ) + + + owner + owner + ( FileOwner owner ) + + + ownerId + ownerId + ( FileOwner owner ) + + + pos + pos + () + + + read + read + ( char * data, qint64 maxlen ) + + + readLine + readLine + ( char * data, qint64 maxlen ) + + + remove + remove + () + + + rename + rename + ( const QString & newName ) + + + rmdir + rmdir + ( const QString & dirName, bool recurseParentDirectories ) + + + seek + seek + ( qint64 offset ) + + + setError + setError + ( QFile::FileError error, const QString & errorString ) + + + setFileName + setFileName + ( const QString & file ) + + + setPermissions + setPermissions + ( uint perms ) + + + setSize + setSize + ( qint64 size ) + + + size + size + () + + + supportsExtension + supportsExtension + ( Extension extension ) + + + write + write + ( const char * data, qint64 len ) + + + Iterator + Iterator-typedef + + + + + QAbstractFileEngineHandler + qabstractfileenginehandler.html + + QAbstractFileEngineHandler + QAbstractFileEngineHandler + () + + + create + create + ( const QString & fileName ) + + + + QAbstractFileEngineIterator + qabstractfileengineiterator.html + + QAbstractFileEngineIterator + QAbstractFileEngineIterator + ( QDir::Filters filters, const QStringList & nameFilters ) + + + currentFileInfo + currentFileInfo + () + + + currentFileName + currentFileName + () + + + currentFilePath + currentFilePath + () + + + Filters + filters + QAbstractFileEngineIterator::filters() + + + hasNext + hasNext + () + + + nameFilters + nameFilters + () + + + next + next + () + + + path + path + () + + + + QAbstractFontEngine::FixedPoint + qabstractfontengine-fixedpoint.html + + x + x-var + + + + y + y-var + + + + + QAbstractFontEngine::GlyphMetrics + qabstractfontengine-glyphmetrics.html + + GlyphMetrics + GlyphMetricsx + () + + + advance + advance-var + + + + height + height-var + + + + width + width-var + + + + x + x-var + + + + y + y-var + + + + + QAbstractFontEngine + qabstractfontengine.html + + Fixed + Fixed-typedef + + + + FontProperty + FontProperty-enum + + + + QAbstractFontEngine + QAbstractFontEngine + ( QObject * parent = 0 ) + + + addGlyphOutlinesToPath + addGlyphOutlinesToPath + ( uint * glyphs, int numGlyphs, FixedPoint * positions, QPainterPath * path ) + + + capabilities + capabilities + () + + + convertStringToGlyphIndices + convertStringToGlyphIndices + ( const QChar * string, int length, uint * glyphs, int * numGlyphs, TextShapingFlags flags ) + + + fontProperty + fontProperty + ( FontProperty property ) + + + getGlyphAdvances + getGlyphAdvances + ( const uint * glyphs, int numGlyphs, Fixed * advances, TextShapingFlags flags ) + + + glyphMetrics + glyphMetrics + ( uint glyph ) + + + renderGlyph + renderGlyph + ( uint glyph, int depth, int bytesPerLine, int height, uchar * buffer ) + + + + QAbstractFormBuilder + qabstractformbuilder.html + + QAbstractFormBuilder + QAbstractFormBuilder + () + + + load + load + ( QIODevice * device, QWidget * parent = 0 ) + + + save + save + ( QIODevice * device, QWidget * widget ) + + + setWorkingDirectory + setWorkingDirectory + ( const QDir & directory ) + + + workingDirectory + workingDirectory + () + + + + QAbstractGraphicsShapeItem + qabstractgraphicsshapeitem.html + + QAbstractGraphicsShapeItem + QAbstractGraphicsShapeItem + ( QGraphicsItem * parent = 0 ) + + + brush + brush + () + + + pen + pen + () + + + setBrush + setBrush + ( const QBrush & brush ) + + + setPen + setPen + ( const QPen & pen ) + + + elidedText + elidedText + ( const QFontMetrics & fontMetrics, int width, Qt::TextElideMode mode, const QString & text ) + + + + QAbstractItemDelegate + qabstractitemdelegate.html + + EndEditHint + EndEditHint-enum + + + + QAbstractItemDelegate + QAbstractItemDelegate + ( QObject * parent = 0 ) + + + closeEditor + closeEditor + ( QWidget * editor, QAbstractItemDelegate::EndEditHint hint = NoHint ) + + + commitData + commitData + ( QWidget * editor ) + + + createEditor + createEditor + ( QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index ) + + + editorEvent + editorEvent + ( QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index ) + + + helpEvent + helpEvent + ( QHelpEvent * event, QAbstractItemView * view, const QStyleOptionViewItem & option, const QModelIndex & index ) + + + paint + paint + ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) + + + setEditorData + setEditorData + ( QWidget * editor, const QModelIndex & index ) + + + setModelData + setModelData + ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) + + + sizeHint + sizeHint + ( const QStyleOptionViewItem & option, const QModelIndex & index ) + + + updateEditorGeometry + updateEditorGeometry + ( QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index ) + + + createIndex + createIndex-2 + ( int row, int column, int id ) + + + + QAbstractItemModel + qabstractitemmodel.html + + QAbstractItemModel + QAbstractItemModel + ( QObject * parent = 0 ) + + + beginInsertColumns + beginInsertColumns + ( const QModelIndex & parent, int first, int last ) + + + beginInsertRows + beginInsertRows + ( const QModelIndex & parent, int first, int last ) + + + beginRemoveColumns + beginRemoveColumns + ( const QModelIndex & parent, int first, int last ) + + + beginRemoveRows + beginRemoveRows + ( const QModelIndex & parent, int first, int last ) + + + buddy + buddy + ( const QModelIndex & index ) + + + canFetchMore + canFetchMore + ( const QModelIndex & parent ) + + + changePersistentIndex + changePersistentIndex + ( const QModelIndex & from, const QModelIndex & to ) + + + changePersistentIndexList + changePersistentIndexList + ( const QModelIndexList & from, const QModelIndexList & to ) + + + columnCount + columnCount + ( const QModelIndex & parent = QModelIndex() + + + columnsAboutToBeInserted + columnsAboutToBeInserted + ( const QModelIndex & parent, int start, int end ) + + + columnsAboutToBeRemoved + columnsAboutToBeRemoved + ( const QModelIndex & parent, int start, int end ) + + + columnsInserted + columnsInserted + ( const QModelIndex & parent, int start, int end ) + + + columnsRemoved + columnsRemoved + ( const QModelIndex & parent, int start, int end ) + + + createIndex + createIndex + ( int row, int column, void * ptr = 0 ) + + + createIndex + createIndex-3 + ( int row, int column, quint32 id ) + + + data + data + ( const QModelIndex & index, int role = Qt::DisplayRole ) + + + dataChanged + dataChanged + ( const QModelIndex & topLeft, const QModelIndex & bottomRight ) + + + dropMimeData + dropMimeData + ( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ) + + + endInsertColumns + endInsertColumns + () + + + endInsertRows + endInsertRows + () + + + endRemoveColumns + endRemoveColumns + () + + + endRemoveRows + endRemoveRows + () + + + fetchMore + fetchMore + ( const QModelIndex & parent ) + + + ItemFlags + flags + QAbstractItemModel::flags( const QModelIndex & index ) + + + hasChildren + hasChildren + ( const QModelIndex & parent = QModelIndex() + + + hasIndex + hasIndex + ( int row, int column, const QModelIndex & parent = QModelIndex() + + + headerData + headerData + ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) + + + headerDataChanged + headerDataChanged + ( Qt::Orientation orientation, int first, int last ) + + + index + index + ( int row, int column, const QModelIndex & parent = QModelIndex() + + + insertColumn + insertColumn + ( int column, const QModelIndex & parent = QModelIndex() + + + insertColumns + insertColumns + ( int column, int count, const QModelIndex & parent = QModelIndex() + + + insertRow + insertRow + ( int row, const QModelIndex & parent = QModelIndex() + + + insertRows + insertRows + ( int row, int count, const QModelIndex & parent = QModelIndex() + + + itemData + itemData + ( const QModelIndex & index ) + + + layoutAboutToBeChanged + layoutAboutToBeChanged + () + + + layoutChanged + layoutChanged + () + + + match + match + ( const QModelIndex & start, int role, const QVariant & value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags( Qt::MatchStartsWith | Qt::MatchWrap ) + + + mimeData + mimeData + ( const QModelIndexList & indexes ) + + + mimeTypes + mimeTypes + () + + + modelAboutToBeReset + modelAboutToBeReset + () + + + modelReset + modelReset + () + + + parent + parent + ( const QModelIndex & index ) + + + persistentIndexList + persistentIndexList + () + + + removeColumn + removeColumn + ( int column, const QModelIndex & parent = QModelIndex() + + + removeColumns + removeColumns + ( int column, int count, const QModelIndex & parent = QModelIndex() + + + removeRow + removeRow + ( int row, const QModelIndex & parent = QModelIndex() + + + removeRows + removeRows + ( int row, int count, const QModelIndex & parent = QModelIndex() + + + reset + reset + () + + + revert + revert + () + + + rowCount + rowCount + ( const QModelIndex & parent = QModelIndex() + + + rowsAboutToBeInserted + rowsAboutToBeInserted + ( const QModelIndex & parent, int start, int end ) + + + rowsAboutToBeRemoved + rowsAboutToBeRemoved + ( const QModelIndex & parent, int start, int end ) + + + rowsInserted + rowsInserted + ( const QModelIndex & parent, int start, int end ) + + + rowsRemoved + rowsRemoved + ( const QModelIndex & parent, int start, int end ) + + + setData + setData + ( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ) + + + setHeaderData + setHeaderData + ( int section, Qt::Orientation orientation, const QVariant & value, int role = Qt::EditRole ) + + + setItemData + setItemData + ( const QModelIndex & index, const QMap<int, QVariant> & roles ) + + + setSupportedDragActions + setSupportedDragActions + ( Qt::DropActions actions ) + + + sibling + sibling + ( int row, int column, const QModelIndex & index ) + + + sort + sort + ( int column, Qt::SortOrder order = Qt::AscendingOrder ) + + + span + span + ( const QModelIndex & index ) + + + submit + submit + () + + + DropActions + supportedDragActions + QAbstractItemModel::supportedDragActions() + + + DropActions + supportedDropActions + QAbstractItemModel::supportedDropActions() + + + horizontalStepsPerItem + horizontalStepsPerItem + () + + + setHorizontalStepsPerItem + setHorizontalStepsPerItem + ( int steps ) + + + setVerticalStepsPerItem + setVerticalStepsPerItem + ( int steps ) + + + verticalStepsPerItem + verticalStepsPerItem + () + + + + QAbstractItemView + qabstractitemview.html + + CursorAction + CursorAction-enum + + + + DragDropMode + DragDropMode-enum + + + + DropIndicatorPosition + DropIndicatorPosition-enum + + + + ScrollHint + ScrollHint-enum + + + + ScrollMode + ScrollMode-enum + + + + SelectionBehavior + SelectionBehavior-enum + + + + SelectionMode + SelectionMode-enum + + + + State + State-enum + + + + TextElideMode + textElideMode-prop + + + + QAbstractItemView + QAbstractItemView + ( QWidget * parent = 0 ) + + + activated + activated + ( const QModelIndex & index ) + + + clearSelection + clearSelection + () + + + clicked + clicked + ( const QModelIndex & index ) + + + closeEditor + closeEditor + ( QWidget * editor, QAbstractItemDelegate::EndEditHint hint ) + + + closePersistentEditor + closePersistentEditor + ( const QModelIndex & index ) + + + commitData + commitData + ( QWidget * editor ) + + + currentChanged + currentChanged + ( const QModelIndex & current, const QModelIndex & previous ) + + + currentIndex + currentIndex + () + + + dataChanged + dataChanged + ( const QModelIndex & topLeft, const QModelIndex & bottomRight ) + + + dirtyRegionOffset + dirtyRegionOffset + () + + + doubleClicked + doubleClicked + ( const QModelIndex & index ) + + + dragEnterEvent + dragEnterEvent + ( QDragEnterEvent * event ) + + + dragLeaveEvent + dragLeaveEvent + ( QDragLeaveEvent * event ) + + + dragMoveEvent + dragMoveEvent + ( QDragMoveEvent * event ) + + + dropEvent + dropEvent + ( QDropEvent * event ) + + + dropIndicatorPosition + dropIndicatorPosition + () + + + edit + edit + ( const QModelIndex & index ) + + + edit + edit-2 + ( const QModelIndex & index, EditTrigger trigger, QEvent * event ) + + + editorDestroyed + editorDestroyed + ( QObject * editor ) + + + entered + entered + ( const QModelIndex & index ) + + + executeDelayedItemsLayout + executeDelayedItemsLayout + () + + + focusInEvent + focusInEvent + ( QFocusEvent * event ) + + + focusOutEvent + focusOutEvent + ( QFocusEvent * event ) + + + horizontalOffset + horizontalOffset + () + + + indexAt + indexAt + ( const QPoint & point ) + + + indexWidget + indexWidget + ( const QModelIndex & index ) + + + isIndexHidden + isIndexHidden + ( const QModelIndex & index ) + + + itemDelegate + itemDelegate + () + + + itemDelegate + itemDelegate-2 + ( const QModelIndex & index ) + + + itemDelegateForColumn + itemDelegateForColumn + ( int column ) + + + itemDelegateForRow + itemDelegateForRow + ( int row ) + + + keyPressEvent + keyPressEvent + ( QKeyEvent * event ) + + + keyboardSearch + keyboardSearch + ( const QString & search ) + + + model + model + () + + + mouseDoubleClickEvent + mouseDoubleClickEvent + ( QMouseEvent * event ) + + + mouseMoveEvent + mouseMoveEvent + ( QMouseEvent * event ) + + + mousePressEvent + mousePressEvent + ( QMouseEvent * event ) + + + mouseReleaseEvent + mouseReleaseEvent + ( QMouseEvent * event ) + + + moveCursor + moveCursor + ( CursorAction cursorAction, Qt::KeyboardModifiers modifiers ) + + + openPersistentEditor + openPersistentEditor + ( const QModelIndex & index ) + + + pressed + pressed + ( const QModelIndex & index ) + + + reset + reset + () + + + resizeEvent + resizeEvent + ( QResizeEvent * event ) + + + rootIndex + rootIndex + () + + + rowsAboutToBeRemoved + rowsAboutToBeRemoved + ( const QModelIndex & parent, int start, int end ) + + + rowsInserted + rowsInserted + ( const QModelIndex & parent, int start, int end ) + + + scheduleDelayedItemsLayout + scheduleDelayedItemsLayout + () + + + scrollDirtyRegion + scrollDirtyRegion + ( int dx, int dy ) + + + scrollTo + scrollTo + ( const QModelIndex & index, ScrollHint hint = EnsureVisible ) + + + scrollToBottom + scrollToBottom + () + + + scrollToTop + scrollToTop + () + + + selectAll + selectAll + () + + + selectedIndexes + selectedIndexes + () + + + selectionChanged + selectionChanged + ( const QItemSelection & selected, const QItemSelection & deselected ) + + + SelectionFlags + selectionCommand + QAbstractItemView::selectionCommand( const QModelIndex & index, const QEvent * event = 0 ) + + + selectionModel + selectionModel + () + + + setCurrentIndex + setCurrentIndex + ( const QModelIndex & index ) + + + setDirtyRegion + setDirtyRegion + ( const QRegion & region ) + + + setIndexWidget + setIndexWidget + ( const QModelIndex & index, QWidget * widget ) + + + setItemDelegate + setItemDelegate + ( QAbstractItemDelegate * delegate ) + + + setItemDelegateForColumn + setItemDelegateForColumn + ( int column, QAbstractItemDelegate * delegate ) + + + setItemDelegateForRow + setItemDelegateForRow + ( int row, QAbstractItemDelegate * delegate ) + + + setModel + setModel + ( QAbstractItemModel * model ) + + + setRootIndex + setRootIndex + ( const QModelIndex & index ) + + + setSelection + setSelection + ( const QRect & rect, QItemSelectionModel::SelectionFlags flags ) + + + setSelectionModel + setSelectionModel + ( QItemSelectionModel * selectionModel ) + + + setState + setState + ( State state ) + + + sizeHintForColumn + sizeHintForColumn + ( int column ) + + + sizeHintForIndex + sizeHintForIndex + ( const QModelIndex & index ) + + + sizeHintForRow + sizeHintForRow + ( int row ) + + + startDrag + startDrag + ( Qt::DropActions supportedActions ) + + + state + state + () + + + timerEvent + timerEvent + ( QTimerEvent * event ) + + + update + update + ( const QModelIndex & index ) + + + verticalOffset + verticalOffset + () + + + viewOptions + viewOptions + () + + + viewportEntered + viewportEntered + () + + + viewportEvent + viewportEvent + ( QEvent * event ) + + + visualRect + visualRect + ( const QModelIndex & index ) + + + visualRegionForSelection + visualRegionForSelection + ( const QItemSelection & selection ) + + + + QAbstractListModel + qabstractlistmodel.html + + QAbstractListModel + QAbstractListModel + ( QObject * parent = 0 ) + + + index + index + ( int row, int column = 0, const QModelIndex & parent = QModelIndex() + + + + QAbstractPrintDialog + qabstractprintdialog.html + + PrintRange + PrintRange-enum + + + + QAbstractPrintDialog + QAbstractPrintDialog + ( QPrinter * printer, QWidget * parent = 0 ) + + + addEnabledOption + addEnabledOption + ( PrintDialogOption option ) + + + enabledOptions + enabledOptions + () + + + exec + exec + () + + + fromPage + fromPage + () + + + isOptionEnabled + isOptionEnabled + ( PrintDialogOption option ) + + + maxPage + maxPage + () + + + minPage + minPage + () + + + printRange + printRange + () + + + printer + printer + () + + + setEnabledOptions + setEnabledOptions + ( PrintDialogOptions options ) + + + setFromTo + setFromTo + ( int from, int to ) + + + setMinMax + setMinMax + ( int min, int max ) + + + setPrintRange + setPrintRange + ( PrintRange range ) + + + toPage + toPage + () + + + + QAbstractProxyModel + qabstractproxymodel.html + + QAbstractProxyModel + QAbstractProxyModel + ( QObject * parent = 0 ) + + + mapFromSource + mapFromSource + ( const QModelIndex & sourceIndex ) + + + mapSelectionFromSource + mapSelectionFromSource + ( const QItemSelection & sourceSelection ) + + + mapSelectionToSource + mapSelectionToSource + ( const QItemSelection & proxySelection ) + + + mapToSource + mapToSource + ( const QModelIndex & proxyIndex ) + + + setSourceModel + setSourceModel + ( QAbstractItemModel * sourceModel ) + + + sourceModel + sourceModel + () + + + + QAbstractScrollArea + qabstractscrollarea.html + + ScrollBarPolicy + horizontalScrollBarPolicy-prop + + + + ScrollBarPolicy + verticalScrollBarPolicy-prop + + + + QAbstractScrollArea + QAbstractScrollArea + ( QWidget * parent = 0 ) + + + addScrollBarWidget + addScrollBarWidget + ( QWidget * widget, Qt::Alignment alignment ) + + + contextMenuEvent + contextMenuEvent + ( QContextMenuEvent * e ) + + + cornerWidget + cornerWidget + () + + + dragEnterEvent + dragEnterEvent + ( QDragEnterEvent * event ) + + + dragLeaveEvent + dragLeaveEvent + ( QDragLeaveEvent * event ) + + + dragMoveEvent + dragMoveEvent + ( QDragMoveEvent * event ) + + + dropEvent + dropEvent + ( QDropEvent * event ) + + + horizontalScrollBar + horizontalScrollBar + () + + + keyPressEvent + keyPressEvent + ( QKeyEvent * e ) + + + maximumViewportSize + maximumViewportSize + () + + + mouseDoubleClickEvent + mouseDoubleClickEvent + ( QMouseEvent * e ) + + + mouseMoveEvent + mouseMoveEvent + ( QMouseEvent * e ) + + + mousePressEvent + mousePressEvent + ( QMouseEvent * e ) + + + mouseReleaseEvent + mouseReleaseEvent + ( QMouseEvent * e ) + + + paintEvent + paintEvent + ( QPaintEvent * event ) + + + resizeEvent + resizeEvent + ( QResizeEvent * event ) + + + scrollBarWidgets + scrollBarWidgets + ( Qt::Alignment alignment ) + + + scrollContentsBy + scrollContentsBy + ( int dx, int dy ) + + + setCornerWidget + setCornerWidget + ( QWidget * widget ) + + + setHorizontalScrollBar + setHorizontalScrollBar + ( QScrollBar * scrollBar ) + + + setVerticalScrollBar + setVerticalScrollBar + ( QScrollBar * scrollBar ) + + + setViewport + setViewport + ( QWidget * widget ) + + + setViewportMargins + setViewportMargins + ( int left, int top, int right, int bottom ) + + + setupViewport + setupViewport + ( QWidget * viewport ) + + + verticalScrollBar + verticalScrollBar + () + + + viewport + viewport + () + + + viewportEvent + viewportEvent + ( QEvent * event ) + + + wheelEvent + wheelEvent + ( QWheelEvent * e ) + + + addLine + addLine + () + + + addPage + addPage + () + + + lineStep + lineStepx + () + + + maxValue + maxValue + () + + + minValue + minValue + () + + + setLineStep + setLineStep + ( int v ) + + + setMaxValue + setMaxValue + ( int v ) + + + setMinValue + setMinValue + ( int v ) + + + setSteps + setSteps + ( int single, int page ) + + + subtractLine + subtractLine + () + + + subtractPage + subtractPage + () + + + + QAbstractSlider + qabstractslider.html + + SliderAction + SliderAction-enum + + + + SliderChange + SliderChange-enum + + + + Orientation + orientation-prop + + + + QAbstractSlider + QAbstractSlider + ( QWidget * parent = 0 ) + + + actionTriggered + actionTriggered + ( int action ) + + + rangeChanged + rangeChanged + ( int min, int max ) + + + repeatAction + repeatAction + () + + + setRange + setRange + ( int min, int max ) + + + setRepeatAction + setRepeatAction + ( SliderAction action, int thresholdTime = 500, int repeatTime = 50 ) + + + sliderChange + sliderChange + ( SliderChange change ) + + + sliderMoved + sliderMoved + ( int value ) + + + sliderPressed + sliderPressed + () + + + sliderReleased + sliderReleased + () + + + triggerAction + triggerAction + ( SliderAction action ) + + + valueChanged + valueChanged + ( int value ) + + + addLine + addLine + () + + + addPage + addPage + () + + + lineStep + lineStepx + () + + + maxValue + maxValue + () + + + minValue + minValue + () + + + setLineStep + setLineStep + ( int v ) + + + setMaxValue + setMaxValue + ( int v ) + + + setMinValue + setMinValue + ( int v ) + + + setSteps + setSteps + ( int single, int page ) + + + subtractLine + subtractLine + () + + + subtractPage + subtractPage + () + + + Error + Error-enum + + + + State + State-typedef + + + + connectionClosed + connectionClosed + () + + + delayedCloseFinished + delayedCloseFinished + () + + + setSocket + setSocket + ( int socket ) + + + socket + socket + () + + + waitForMore + waitForMore + ( int msecs, bool * timeout = 0 ) + + + + QAbstractSocket + qabstractsocket.html + + NetworkLayerProtocol + NetworkLayerProtocol-enum + + + + SocketError + SocketError-enum + + + + SocketState + SocketState-enum + + + + SocketType + SocketType-enum + + + + QAbstractSocket + QAbstractSocket + ( SocketType socketType, QObject * parent ) + + + abort + abort + () + + + bytesAvailable + bytesAvailable + () + + + bytesToWrite + bytesToWrite + () + + + canReadLine + canReadLine + () + + + close + close + () + + + connectToHost + connectToHost + ( const QString & hostName, quint16 port, OpenMode openMode = ReadWrite ) + + + connectToHost + connectToHost-2 + ( const QHostAddress & address, quint16 port, OpenMode openMode = ReadWrite ) + + + connectToHostImplementation + connectToHostImplementation + ( const QString & hostName, quint16 port, OpenMode openMode = ReadWrite ) + + + connected + connected + () + + + disconnectFromHost + disconnectFromHost + () + + + disconnectFromHostImplementation + disconnectFromHostImplementation + () + + + disconnected + disconnected + () + + + error + error + () + + + error + error-2 + ( QAbstractSocket::SocketError socketError ) + + + flush + flush + () + + + hostFound + hostFound + () + + + isValid + isValid + () + + + localAddress + localAddress + () + + + localPort + localPort + () + + + peerAddress + peerAddress + () + + + peerName + peerName + () + + + peerPort + peerPort + () + + + proxy + proxy + () + + + proxyAuthenticationRequired + proxyAuthenticationRequired + ( const QNetworkProxy & proxy, QAuthenticator * authenticator ) + + + readBufferSize + readBufferSize + () + + + setLocalAddress + setLocalAddress + ( const QHostAddress & address ) + + + setLocalPort + setLocalPort + ( quint16 port ) + + + setPeerAddress + setPeerAddress + ( const QHostAddress & address ) + + + setPeerName + setPeerName + ( const QString & name ) + + + setPeerPort + setPeerPort + ( quint16 port ) + + + setProxy + setProxy + ( const QNetworkProxy & networkProxy ) + + + setReadBufferSize + setReadBufferSize + ( qint64 size ) + + + setSocketDescriptor + setSocketDescriptor + ( int socketDescriptor, SocketState socketState = ConnectedState, OpenMode openMode = ReadWrite ) + + + setSocketError + setSocketError + ( SocketError socketError ) + + + setSocketState + setSocketState + ( SocketState state ) + + + socketDescriptor + socketDescriptor + () + + + socketType + socketType + () + + + state + state + () + + + stateChanged + stateChanged + ( QAbstractSocket::SocketState socketState ) + + + waitForConnected + waitForConnected + ( int msecs = 30000 ) + + + waitForDisconnected + waitForDisconnected + ( int msecs = 30000 ) + + + waitForReadyRead + waitForReadyRead + ( int msecs = 30000 ) + + + Error + Error-enum + + + + State + State-typedef + + + + connectionClosed + connectionClosed + () + + + delayedCloseFinished + delayedCloseFinished + () + + + setSocket + setSocket + ( int socket ) + + + socket + socket + () + + + waitForMore + waitForMore + ( int msecs, bool * timeout = 0 ) + + + + QAbstractSpinBox + qabstractspinbox.html + + ButtonSymbols + ButtonSymbols-enum + + + + CorrectionMode + CorrectionMode-enum + + + + Alignment + alignment-prop + + + + QAbstractSpinBox + QAbstractSpinBox + ( QWidget * parent = 0 ) + + + clear + clear + () + + + editingFinished + editingFinished + () + + + fixup + fixup + ( QString & input ) + + + initStyleOption + initStyleOption + ( QStyleOptionSpinBox * option ) + + + interpretText + interpretText + () + + + lineEdit + lineEdit + () + + + selectAll + selectAll + () + + + setLineEdit + setLineEdit + ( QLineEdit * lineEdit ) + + + stepBy + stepBy + ( int steps ) + + + stepDown + stepDown + () + + + stepEnabled + stepEnabled + () + + + stepUp + stepUp + () + + + State + validate + QAbstractSpinBox::validate( QString & input, int & pos ) + + + + QAbstractTableModel + qabstracttablemodel.html + + QAbstractTableModel + QAbstractTableModel + ( QObject * parent = 0 ) + + + index + index + ( int row, int column, const QModelIndex & parent = QModelIndex() + + + + QAbstractTextDocumentLayout::PaintContext + qabstracttextdocumentlayout-paintcontext.html + + clip + clip-var + + + + cursorPosition + cursorPosition-var + + + + palette + palette-var + + + + selections + selections-var + + + + + QAbstractTextDocumentLayout::Selection + qabstracttextdocumentlayout-selection.html + + cursor + cursor-var + + + + format + format-var + + + + + QAbstractTextDocumentLayout + qabstracttextdocumentlayout.html + + QAbstractTextDocumentLayout + QAbstractTextDocumentLayout + ( QTextDocument * document ) + + + anchorAt + anchorAt + ( const QPointF & position ) + + + blockBoundingRect + blockBoundingRect + ( const QTextBlock & block ) + + + document + document + () + + + documentChanged + documentChanged + ( int position, int charsRemoved, int charsAdded ) + + + documentSize + documentSize + () + + + documentSizeChanged + documentSizeChanged + ( const QSizeF & newSize ) + + + draw + draw + ( QPainter * painter, const PaintContext & context ) + + + drawInlineObject + drawInlineObject + ( QPainter * painter, const QRectF & rect, QTextInlineObject object, int posInDocument, const QTextFormat & format ) + + + format + format + ( int position ) + + + frameBoundingRect + frameBoundingRect + ( QTextFrame * frame ) + + + handlerForObject + handlerForObject + ( int objectType ) + + + hitTest + hitTest + ( const QPointF & point, Qt::HitTestAccuracy accuracy ) + + + pageCount + pageCount + () + + + pageCountChanged + pageCountChanged + ( int newPages ) + + + paintDevice + paintDevice + () + + + positionInlineObject + positionInlineObject + ( QTextInlineObject item, int posInDocument, const QTextFormat & format ) + + + registerHandler + registerHandler + ( int formatType, QObject * component ) + + + resizeInlineObject + resizeInlineObject + ( QTextInlineObject item, int posInDocument, const QTextFormat & format ) + + + setPaintDevice + setPaintDevice + ( QPaintDevice * device ) + + + update + update + ( const QRectF & rect = QRectF( 0., 0., 1000000000., 1000000000. ) + + + + QAccessible + qaccessible.html + + Action + Action-enum + + + + Event + Event-enum + + + + InterfaceFactory + InterfaceFactory-typedef + + + + Method + Method-enum + + + + Role + Role-enum + + + + Text + Text-enum + + + + installFactory + installFactory + ( InterfaceFactory factory ) + + + installRootObjectHandler + installRootObjectHandler + ( RootObjectHandler handler ) + + + isActive + isActive + () + + + queryAccessibleInterface + queryAccessibleInterface + ( QObject * object ) + + + removeFactory + removeFactory + ( InterfaceFactory factory ) + + + updateAccessibility + updateAccessibility + ( QObject * object, int child, Event reason ) + + + BoundaryType + BoundaryType-enum + + + + CoordinateType + CoordinateType-enum + + + + InterfaceType + InterfaceType-enum + + + + + QAccessibleBridge + qaccessiblebridge.html + + notifyAccessibilityUpdate + notifyAccessibilityUpdate + ( int reason, QAccessibleInterface * interface, int child ) + + + setRootObject + setRootObject + ( QAccessibleInterface * object ) + + + + QAccessibleBridgePlugin + qaccessiblebridgeplugin.html + + QAccessibleBridgePlugin + QAccessibleBridgePlugin + ( QObject * parent = 0 ) + + + create + create + ( const QString & key ) + + + keys + keys + () + + + + QAccessibleEditableTextInterface + qaccessibleeditabletextinterface.html + + copyText + copyText + ( int startOffset, int endOffset ) + + + cutText + cutText + ( int startOffset, int endOffset ) + + + deleteText + deleteText + ( int startOffset, int endOffset ) + + + insertText + insertText + ( int offset, const QString & text ) + + + pasteText + pasteText + ( int offset ) + + + qAccessibleEditableTextCastHelper + qAccessibleEditableTextCastHelper + () + + + replaceText + replaceText + ( int startOffset, int endOffset, const QString & text ) + + + setAttributes + setAttributes + ( int startOffset, int endOffset, const QString & attributes ) + + + + QAccessibleEvent + qaccessibleevent.html + + QAccessibleEvent + QAccessibleEvent + ( Type type, int child ) + + + child + child + () + + + setValue + setValue + ( const QString & text ) + + + value + value + () + + + + QAccessibleInterface + qaccessibleinterface.html + + actionText + actionText + ( int action, Text t, int child ) + + + childAt + childAt + ( int x, int y ) + + + childCount + childCount + () + + + doAction + doAction + ( int action, int child, const QVariantList & params = QVariantList() + + + indexOfChild + indexOfChild + ( const QAccessibleInterface * child ) + + + invokeMethod + invokeMethod + ( Method method, int child = 0, const QVariantList & params = QVariantList() + + + isValid + isValid + () + + + navigate + navigate + ( RelationFlag relation, int entry, QAccessibleInterface ** target ) + + + object + object + () + + + rect + rect + ( int child ) + + + relationTo + relationTo + ( int child, const QAccessibleInterface * other, int otherChild ) + + + role + role + ( int child ) + + + setText + setText + ( Text t, int child, const QString & text ) + + + state + state + ( int child ) + + + supportedMethods + supportedMethods + () + + + text + text + ( Text t, int child ) + + + userActionCount + userActionCount + ( int child ) + + + + QAccessibleObject + qaccessibleobject.html + + QAccessibleObject + QAccessibleObject + ( QObject * object ) + + + + QAccessiblePlugin + qaccessibleplugin.html + + QAccessiblePlugin + QAccessiblePlugin + ( QObject * parent = 0 ) + + + create + create + ( const QString & key, QObject * object ) + + + keys + keys + () + + + + QAccessibleSimpleEditableTextInterface + qaccessiblesimpleeditabletextinterface.html + + QAccessibleSimpleEditableTextInterface + QAccessibleSimpleEditableTextInterface + ( QAccessibleInterface * accessibleInterface ) + + + copyText + copyText + ( int startOffset, int endOffset ) + + + cutText + cutText + ( int startOffset, int endOffset ) + + + deleteText + deleteText + ( int startOffset, int endOffset ) + + + insertText + insertText + ( int offset, const QString & text ) + + + pasteText + pasteText + ( int offset ) + + + replaceText + replaceText + ( int startOffset, int endOffset, const QString & text ) + + + setAttributes + setAttributes + ( int, int, const QString & ) + + + + QAccessibleTextInterface + qaccessibletextinterface.html + + addSelection + addSelection + ( int startOffset, int endOffset ) + + + attributes + attributes + ( int offset, int * startOffset, int * endOffset ) + + + characterCount + characterCount + () + + + characterRect + characterRect + ( int offset, QAccessible2::CoordinateType coordType ) + + + cursorPosition + cursorPosition + () + + + offsetAtPoint + offsetAtPoint + ( const QPoint & point, QAccessible2::CoordinateType coordType ) + + + qAccessibleTextCastHelper + qAccessibleTextCastHelper + () + + + removeSelection + removeSelection + ( int selectionIndex ) + + + scrollToSubstring + scrollToSubstring + ( int startIndex, int endIndex ) + + + selection + selection + ( int selectionIndex, int * startOffset, int * endOffset ) + + + selectionCount + selectionCount + () + + + setCursorPosition + setCursorPosition + ( int position ) + + + setSelection + setSelection + ( int selectionIndex, int startOffset, int endOffset ) + + + text + text + ( int startOffset, int endOffset ) + + + textAfterOffset + textAfterOffset + ( int offset, QAccessible2::BoundaryType boundaryType, int * startOffset, int * endOffset ) + + + textAtOffset + textAtOffset + ( int offset, QAccessible2::BoundaryType boundaryType, int * startOffset, int * endOffset ) + + + textBeforeOffset + textBeforeOffset + ( int offset, QAccessible2::BoundaryType boundaryType, int * startOffset, int * endOffset ) + + + + QAccessibleValueInterface + qaccessiblevalueinterface.html + + currentValue + currentValue + () + + + maximumValue + maximumValue + () + + + minimumValue + minimumValue + () + + + qAccessibleValueCastHelper + qAccessibleValueCastHelper + () + + + setCurrentValue + setCurrentValue + ( const QVariant & value ) + + + + QAccessibleWidget + qaccessiblewidget.html + + QAccessibleWidget + QAccessibleWidget + ( QWidget * w, Role role = Client, const QString & name = QString() + + + addControllingSignal + addControllingSignal + ( const QString & signal ) + + + parentObject + parentObject + () + + + setAccelerator + setAccelerator + ( const QString & accel ) + + + setDescription + setDescription + ( const QString & desc ) + + + setHelp + setHelp + ( const QString & help ) + + + setValue + setValue + ( const QString & value ) + + + widget + widget + () + + + QAction + QAction-4 + ( QObject * parent, const char * name ) + + + QAction + QAction-5 + ( const QString & text, const QKeySequence & shortcut, QObject * parent, const char * name ) + + + QAction + QAction-6 + ( const QIcon & icon, const QString & text, const QKeySequence & shortcut, QObject * parent, const char * name ) + + + accel + accel + () + + + activated + activated + ( int i = 0 ) + + + addTo + addTo + ( QWidget * w ) + + + iconSet + iconSet + () + + + isOn + isOn + () + + + isToggleAction + isToggleAction + () + + + menuText + menuText + () + + + removeFrom + removeFrom + ( QWidget * w ) + + + setAccel + setAccel + ( const QKeySequence & shortcut ) + + + setIconSet + setIconSet + ( const QIcon & i ) + + + setMenuText + setMenuText + ( const QString & text ) + + + setOn + setOn + ( bool b ) + + + setToggleAction + setToggleAction + ( bool b ) + + + + QAction + qaction.html + + ActionEvent + ActionEvent-enum + + + + MenuRole + MenuRole-enum + + + + ShortcutContext + shortcutContext-prop + + + + QAction + QAction + ( QObject * parent ) + + + QAction + QAction-2 + ( const QString & text, QObject * parent ) + + + QAction + QAction-3 + ( const QIcon & icon, const QString & text, QObject * parent ) + + + actionGroup + actionGroup + () + + + activate + activate + ( ActionEvent event ) + + + associatedWidgets + associatedWidgets + () + + + changed + changed + () + + + data + data + () + + + hover + hover + () + + + hovered + hovered + () + + + isSeparator + isSeparator + () + + + menu + menu + () + + + parentWidget + parentWidget + () + + + setActionGroup + setActionGroup + ( QActionGroup * group ) + + + setData + setData + ( const QVariant & userData ) + + + setDisabled + setDisabled + ( bool b ) + + + setMenu + setMenu + ( QMenu * menu ) + + + setSeparator + setSeparator + ( bool b ) + + + setShortcuts + setShortcuts + ( const QList<QKeySequence> & shortcuts ) + + + setShortcuts + setShortcuts-2 + ( QKeySequence::StandardKey key ) + + + shortcuts + shortcuts + () + + + showStatusText + showStatusText + ( QWidget * widget = 0 ) + + + toggle + toggle + () + + + toggled + toggled + ( bool checked ) + + + trigger + trigger + () + + + triggered + triggered + ( bool checked = false ) + + + QAction + QAction-4 + ( QObject * parent, const char * name ) + + + QAction + QAction-5 + ( const QString & text, const QKeySequence & shortcut, QObject * parent, const char * name ) + + + QAction + QAction-6 + ( const QIcon & icon, const QString & text, const QKeySequence & shortcut, QObject * parent, const char * name ) + + + accel + accel + () + + + activated + activated + ( int i = 0 ) + + + addTo + addTo + ( QWidget * w ) + + + iconSet + iconSet + () + + + isOn + isOn + () + + + isToggleAction + isToggleAction + () + + + menuText + menuText + () + + + removeFrom + removeFrom + ( QWidget * w ) + + + setAccel + setAccel + ( const QKeySequence & shortcut ) + + + setIconSet + setIconSet + ( const QIcon & i ) + + + setMenuText + setMenuText + ( const QString & text ) + + + setOn + setOn + ( bool b ) + + + setToggleAction + setToggleAction + ( bool b ) + + + + QActionEvent + qactionevent.html + + QActionEvent + QActionEvent + ( int type, QAction * action, QAction * before = 0 ) + + + action + action + () + + + before + before + () + + + add + add + ( QAction * a ) + + + addSeparator + addSeparator + () + + + addTo + addTo + ( QWidget * widget ) + + + selected + selected + ( QAction * action ) + + + + QActionGroup + qactiongroup.html + + QActionGroup + QActionGroup + ( QObject * parent ) + + + actions + actions + () + + + addAction + addAction + ( QAction * action ) + + + addAction + addAction-2 + ( const QString & text ) + + + addAction + addAction-3 + ( const QIcon & icon, const QString & text ) + + + checkedAction + checkedAction + () + + + hovered + hovered + ( QAction * action ) + + + removeAction + removeAction + ( QAction * action ) + + + setDisabled + setDisabled + ( bool b ) + + + triggered + triggered + ( QAction * action ) + + + add + add + ( QAction * a ) + + + addSeparator + addSeparator + () + + + addTo + addTo + ( QWidget * widget ) + + + selected + selected + ( QAction * action ) + + + ColorMode + ColorMode-typedef + + + + colorMode + colorMode + () + + + flushX + flushX + () + + + hasGlobalMouseTracking + hasGlobalMouseTracking + () + + + Alignment + horizontalAlignment + QApplication::horizontalAlignment( Qt::Alignment align ) + + + MacintoshVersion + macVersion + QApplication::macVersion() + + + mainWidget + mainWidget + () + + + reverseLayout + reverseLayout + () + + + setColorMode + setColorMode + ( ColorMode mode ) + + + setFont + setFont-2 + ( const QFont & font, bool b, const char * className = 0 ) + + + setGlobalMouseTracking + setGlobalMouseTracking + ( bool dummy ) + + + setMainWidget + setMainWidget + ( QWidget * mainWidget ) + + + setOverrideCursor + setOverrideCursor-2 + ( const QCursor & cursor, bool replace ) + + + setPalette + setPalette-2 + ( const QPalette & pal, bool b, const char * className = 0 ) + + + setReverseLayout + setReverseLayout + ( bool reverse ) + + + setWinStyleHighlightColor + setWinStyleHighlightColor + ( const QColor & c ) + + + widgetAt + widgetAt-2 + ( int x, int y, bool child ) + + + widgetAt + widgetAt-3 + ( const QPoint & point, bool child ) + + + winStyleHighlightColor + winStyleHighlightColor + () + + + WindowsVersion + winVersion + QApplication::winVersion() + + + CustomColors + CustomColors-var + + + + NormalColors + NormalColors-var + + + + + QApplication + qapplication.html + + ColorSpec + ColorSpec-enum + + + + Type + Type-enum + + + + LayoutDirection + layoutDirection-prop + + + + QApplication + QApplication + ( int & argc, char ** argv ) + + + QApplication + QApplication-2 + ( int & argc, char ** argv, bool GUIenabled ) + + + QApplication + QApplication-3 + ( int & argc, char ** argv, Type type ) + + + QApplication + QApplication-4 + ( Display * display, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0 ) + + + QApplication + QApplication-5 + ( Display * display, int & argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0 ) + + + aboutQt + aboutQt + () + + + activeModalWidget + activeModalWidget + () + + + activePopupWidget + activePopupWidget + () + + + activeWindow + activeWindow + () + + + alert + alert + ( QWidget * widget, int msec = 0 ) + + + allWidgets + allWidgets + () + + + beep + beep + () + + + changeOverrideCursor + changeOverrideCursor + ( const QCursor & cursor ) + + + clipboard + clipboard + () + + + closeAllWindows + closeAllWindows + () + + + colorSpec + colorSpec + () + + + commitData + commitData + ( QSessionManager & manager ) + + + commitDataRequest + commitDataRequest + ( QSessionManager & manager ) + + + desktop + desktop + () + + + desktopSettingsAware + desktopSettingsAware + () + + + exec + exec + () + + + focusChanged + focusChanged + ( QWidget * old, QWidget * now ) + + + focusWidget + focusWidget + () + + + font + font + () + + + font + font-2 + ( const QWidget * widget ) + + + font + font-3 + ( const char * className ) + + + fontMetrics + fontMetrics + () + + + inputContext + inputContext + () + + + isEffectEnabled + isEffectEnabled + ( Qt::UIEffect effect ) + + + isLeftToRight + isLeftToRight + () + + + isRightToLeft + isRightToLeft + () + + + isSessionRestored + isSessionRestored + () + + + LayoutDirection + keyboardInputDirection + QApplication::keyboardInputDirection() + + + keyboardInputLocale + keyboardInputLocale + () + + + KeyboardModifiers + keyboardModifiers + QApplication::keyboardModifiers() + + + keypadNavigationEnabled + keypadNavigationEnabled + () + + + lastWindowClosed + lastWindowClosed + () + + + macEventFilter + macEventFilter + ( EventHandlerCallRef caller, EventRef event ) + + + MouseButtons + mouseButtons + QApplication::mouseButtons() + + + overrideCursor + overrideCursor + () + + + palette + palette + () + + + palette + palette-2 + ( const QWidget * widget ) + + + palette + palette-3 + ( const char * className ) + + + qwsDecoration + qwsDecoration + () + + + qwsEventFilter + qwsEventFilter + ( QWSEvent * event ) + + + qwsSetCustomColors + qwsSetCustomColors + ( QRgb * colorTable, int start, int numColors ) + + + qwsSetDecoration + qwsSetDecoration + ( QDecoration * decoration ) + + + qwsSetDecoration + qwsSetDecoration-2 + ( const QString & decoration ) + + + restoreOverrideCursor + restoreOverrideCursor + () + + + saveState + saveState + ( QSessionManager & manager ) + + + saveStateRequest + saveStateRequest + ( QSessionManager & manager ) + + + sessionId + sessionId + () + + + sessionKey + sessionKey + () + + + setActiveWindow + setActiveWindow + ( QWidget * active ) + + + setColorSpec + setColorSpec + ( int spec ) + + + setDesktopSettingsAware + setDesktopSettingsAware + ( bool on ) + + + setEffectEnabled + setEffectEnabled + ( Qt::UIEffect effect, bool enable = true ) + + + setFont + setFont + ( const QFont & font, const char * className = 0 ) + + + setInputContext + setInputContext + ( QInputContext * inputContext ) + + + setKeypadNavigationEnabled + setKeypadNavigationEnabled + ( bool enable ) + + + setOverrideCursor + setOverrideCursor + ( const QCursor & cursor ) + + + setPalette + setPalette + ( const QPalette & palette, const char * className = 0 ) + + + setStyle + setStyle + ( QStyle * style ) + + + setStyle + setStyle-2 + ( const QString & style ) + + + style + style + () + + + syncX + syncX + () + + + topLevelAt + topLevelAt + ( const QPoint & point ) + + + topLevelAt + topLevelAt-2 + ( int x, int y ) + + + topLevelWidgets + topLevelWidgets + () + + + type + type + () + + + widgetAt + widgetAt + ( const QPoint & point ) + + + widgetAt + widgetAt-4 + ( int x, int y ) + + + x11EventFilter + x11EventFilter + ( XEvent * event ) + + + x11ProcessEvent + x11ProcessEvent + ( XEvent * event ) + + + ColorMode + ColorMode-typedef + + + + colorMode + colorMode + () + + + flushX + flushX + () + + + hasGlobalMouseTracking + hasGlobalMouseTracking + () + + + Alignment + horizontalAlignment + QApplication::horizontalAlignment( Qt::Alignment align ) + + + MacintoshVersion + macVersion + QApplication::macVersion() + + + mainWidget + mainWidget + () + + + reverseLayout + reverseLayout + () + + + setColorMode + setColorMode + ( ColorMode mode ) + + + setFont + setFont-2 + ( const QFont & font, bool b, const char * className = 0 ) + + + setGlobalMouseTracking + setGlobalMouseTracking + ( bool dummy ) + + + setMainWidget + setMainWidget + ( QWidget * mainWidget ) + + + setOverrideCursor + setOverrideCursor-2 + ( const QCursor & cursor, bool replace ) + + + setPalette + setPalette-2 + ( const QPalette & pal, bool b, const char * className = 0 ) + + + setReverseLayout + setReverseLayout + ( bool reverse ) + + + setWinStyleHighlightColor + setWinStyleHighlightColor + ( const QColor & c ) + + + widgetAt + widgetAt-2 + ( int x, int y, bool child ) + + + widgetAt + widgetAt-3 + ( const QPoint & point, bool child ) + + + winStyleHighlightColor + winStyleHighlightColor + () + + + WindowsVersion + winVersion + QApplication::winVersion() + + + CustomColors + CustomColors-var + + + + NormalColors + NormalColors-var + + + + + QAssistantClient + qassistantclient.html + + QAssistantClient + QAssistantClient + ( const QString & path, QObject * parent = 0 ) + + + assistantClosed + assistantClosed + () + + + assistantOpened + assistantOpened + () + + + closeAssistant + closeAssistant + () + + + error + error + ( const QString & message ) + + + openAssistant + openAssistant + () + + + setArguments + setArguments + ( const QStringList & arguments ) + + + showPage + showPage + ( const QString & page ) + + + + QAuthenticator + qauthenticator.html + + QAuthenticator + QAuthenticator + () + + + QAuthenticator + QAuthenticator-2 + ( const QAuthenticator & other ) + + + isNull + isNull + () + + + password + password + () + + + realm + realm + () + + + setPassword + setPassword + ( const QString & password ) + + + setUser + setUser + ( const QString & user ) + + + user + user + () + + + operator!= + operator-not-eq + ( const QAuthenticator & other ) + + + operator= + operator-eq + ( const QAuthenticator & other ) + + + operator== + operator-eq-eq + ( const QAuthenticator & other ) + + + + QAxAggregated + qaxaggregated.html + + controllingUnknown + controllingUnknown + () + + + object + object + () + + + queryInterface + queryInterface + ( const QUuid & iid, void ** iface ) + + + widget + widget + () + + + + QAxBase + qaxbase.html + + PropertyBag + PropertyBag-typedef + + + + QAxBase + QAxBase + ( IUnknown * iface = 0 ) + + + asVariant + asVariant + () + + + clear + clear + () + + + disableClassInfo + disableClassInfo + () + + + disableEventSink + disableEventSink + () + + + disableMetaObject + disableMetaObject + () + + + dynamicCall + dynamicCall + ( const char * function, const QVariant & var1 = QVariant() + + + dynamicCall + dynamicCall-2 + ( const char * function, QList<QVariant> & vars ) + + + exception + exception + ( int code, const QString & source, const QString & desc, const QString & help ) + + + generateDocumentation + generateDocumentation + () + + + initialize + initialize + ( IUnknown ** ptr ) + + + initializeActive + initializeActive + ( IUnknown ** ptr ) + + + initializeFromFile + initializeFromFile + ( IUnknown ** ptr ) + + + initializeLicensed + initializeLicensed + ( IUnknown ** ptr ) + + + initializeRemote + initializeRemote + ( IUnknown ** ptr ) + + + isNull + isNull + () + + + propertyBag + propertyBag + () + + + propertyChanged + propertyChanged + ( const QString & name ) + + + propertyWritable + propertyWritable + ( const char * prop ) + + + queryInterface + queryInterface + ( const QUuid & uuid, void ** iface ) + + + querySubObject + querySubObject + ( const char * name, const QVariant & var1 = QVariant() + + + querySubObject + querySubObject-2 + ( const char * name, QList<QVariant> & vars ) + + + setPropertyBag + setPropertyBag + ( const PropertyBag & bag ) + + + setPropertyWritable + setPropertyWritable + ( const char * prop, bool ok ) + + + signal + signal + ( const QString & name, int argc, void * argv ) + + + verbs + verbs + () + + + + QAxBindable + qaxbindable.html + + QAxBindable + QAxBindable + () + + + clientSite + clientSite + () + + + createAggregate + createAggregate + () + + + propertyChanged + propertyChanged + ( const char * property ) + + + readData + readData + ( QIODevice * source, const QString & format ) + + + reportError + reportError + ( int code, const QString & src, const QString & desc, const QString & context = QString() + + + requestPropertyChange + requestPropertyChange + ( const char * property ) + + + writeData + writeData + ( QIODevice * sink ) + + + + QAxFactory + qaxfactory.html + + ServerType + ServerType-enum + + + + QAxFactory + QAxFactory + ( const QUuid & libid, const QUuid & appid ) + + + appID + appID + () + + + classID + classID + ( const QString & key ) + + + createObject + createObject + ( const QString & key ) + + + createObjectWrapper + createObjectWrapper + ( QObject * object, IDispatch ** wrapper ) + + + eventsID + eventsID + ( const QString & key ) + + + exposeToSuperClass + exposeToSuperClass + ( const QString & key ) + + + featureList + featureList + () + + + hasStockEvents + hasStockEvents + ( const QString & key ) + + + interfaceID + interfaceID + ( const QString & key ) + + + isServer + isServer + () + + + isService + isService + () + + + metaObject + metaObject + ( const QString & key ) + + + registerActiveObject + registerActiveObject + ( QObject * object ) + + + registerClass + registerClass + ( const QString & key, QSettings * settings ) + + + serverDirPath + serverDirPath + () + + + serverFilePath + serverFilePath + () + + + startServer + startServer + ( ServerType type = MultipleInstances ) + + + stayTopLevel + stayTopLevel + ( const QString & key ) + + + stopServer + stopServer + () + + + typeLibID + typeLibID + () + + + unregisterClass + unregisterClass + ( const QString & key, QSettings * settings ) + + + validateLicenseKey + validateLicenseKey + ( const QString & key, const QString & licenseKey ) + + + + QAxObject + qaxobject.html + + QAxObject + QAxObject + ( QObject * parent = 0 ) + + + QAxObject + QAxObject-2 + ( const QString & c, QObject * parent = 0 ) + + + QAxObject + QAxObject-3 + ( IUnknown * iface, QObject * parent = 0 ) + + + doVerb + doVerb + ( const QString & verb ) + + + + QAxScript + qaxscript.html + + FunctionFlags + FunctionFlags-enum + + + + QAxScript + QAxScript + ( const QString & name, QAxScriptManager * manager ) + + + call + call + ( const QString & function, const QVariant & var1 = QVariant() + + + call + call-2 + ( const QString & function, QList<QVariant> & arguments ) + + + entered + entered + () + + + error + error + ( int code, const QString & description, int sourcePosition, const QString & sourceText ) + + + finished + finished + () + + + finished + finished-2 + ( const QVariant & result ) + + + finished + finished-3 + ( int code, const QString & source, const QString & description, const QString & help ) + + + functions + functions + ( FunctionFlags flags = FunctionNames ) + + + load + load + ( const QString & code, const QString & language = QString() + + + scriptCode + scriptCode + () + + + scriptEngine + scriptEngine + () + + + scriptName + scriptName + () + + + stateChanged + stateChanged + ( int state ) + + + + QAxScriptEngine + qaxscriptengine.html + + State + State-enum + + + + QAxScriptEngine + QAxScriptEngine + ( const QString & language, QAxScript * script ) + + + addItem + addItem + ( const QString & name ) + + + hasIntrospection + hasIntrospection + () + + + isValid + isValid + () + + + queryInterface + queryInterface + ( const QUuid & uuid, void ** iface ) + + + scriptLanguage + scriptLanguage + () + + + setState + setState + ( State st ) + + + state + state + () + + + + QAxScriptManager + qaxscriptmanager.html + + QAxScriptManager + QAxScriptManager + ( QObject * parent = 0 ) + + + addObject + addObject + ( QAxBase * object ) + + + addObject + addObject-2 + ( QObject * object ) + + + call + call + ( const QString & function, const QVariant & var1 = QVariant() + + + call + call-2 + ( const QString & function, QList<QVariant> & arguments ) + + + error + error + ( QAxScript * script, int code, const QString & description, int sourcePosition, const QString & sourceText ) + + + functions + functions + ( QAxScript::FunctionFlags flags = QAxScript::FunctionNames ) + + + load + load + ( const QString & code, const QString & name, const QString & language ) + + + load + load-2 + ( const QString & file, const QString & name ) + + + registerEngine + registerEngine + ( const QString & name, const QString & extension, const QString & code = QString() + + + script + script + ( const QString & name ) + + + scriptFileFilter + scriptFileFilter + () + + + scriptNames + scriptNames + () + + + + QAxWidget + qaxwidget.html + + QAxWidget + QAxWidget + ( QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + QAxWidget + QAxWidget-2 + ( const QString & c, QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + QAxWidget + QAxWidget-3 + ( IUnknown * iface, QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + createAggregate + createAggregate + () + + + createHostWindow + createHostWindow + ( bool initialized ) + + + doVerb + doVerb + ( const QString & verb ) + + + initialize + initialize + ( IUnknown ** ptr ) + + + translateKeyEvent + translateKeyEvent + ( int message, int keycode ) + + + + QBasicTimer + qbasictimer.html + + QBasicTimer + QBasicTimer + () + + + isActive + isActive + () + + + start + start + ( int msec, QObject * object ) + + + stop + stop + () + + + timerId + timerId + () + + + + QBitArray + qbitarray.html + + DataPtr + DataPtr-typedef + + + + QBitArray + QBitArray + () + + + QBitArray + QBitArray-2 + ( int size, bool value = false ) + + + QBitArray + QBitArray-3 + ( const QBitArray & other ) + + + at + at + ( int i ) + + + clear + clear + () + + + clearBit + clearBit + ( int i ) + + + count + count + () + + + count + count-2 + ( bool on ) + + + data_ptr + data_ptr + () + + + fill + fill + ( bool value, int size = -1 ) + + + fill + fill-2 + ( bool value, int begin, int end ) + + + isEmpty + isEmpty + () + + + isNull + isNull + () + + + resize + resize + ( int size ) + + + setBit + setBit + ( int i ) + + + setBit + setBit-2 + ( int i, bool value ) + + + size + size + () + + + testBit + testBit + ( int i ) + + + toggleBit + toggleBit + ( int i ) + + + truncate + truncate + ( int pos ) + + + operator!= + operator-not-eq + ( const QBitArray & other ) + + + operator& + operator-and-eq + amp;=( const QBitArray & other ) + + + operator= + operator-eq + ( const QBitArray & other ) + + + operator== + operator-eq-eq + ( const QBitArray & other ) + + + operator[] + operator-5b-5d + ( int i ) + + + operator[] + operator-5b-5d-2 + ( int i ) + + + operator[] + operator-5b-5d-3 + ( uint i ) + + + operator[] + operator-5b-5d-4 + ( uint i ) + + + operator^= + operator-5e-eq + ( const QBitArray & other ) + + + operator|= + operator-7c-eq + ( const QBitArray & other ) + + + operator~ + operator-7e + () + + + QBitmap + QBitmap-6 + ( int width, int height, bool clear ) + + + QBitmap + QBitmap-7 + ( const QSize & size, bool clear ) + + + QBitmap + QBitmap-8 + ( int width, int height, const uchar * bits, bool isXbitmap = false ) + + + QBitmap + QBitmap-9 + ( const QImage & image ) + + + QBitmap + QBitmap-10 + ( const QSize & size, const uchar * bits, bool isXbitmap = false ) + + + xForm + xForm + ( const QMatrix & matrix ) + + + operator= + operator-eq-2 + ( const QImage & image ) + + + + QBitmap + qbitmap.html + + QBitmap + QBitmap + () + + + QBitmap + QBitmap-2 + ( const QPixmap & pixmap ) + + + QBitmap + QBitmap-3 + ( int width, int height ) + + + QBitmap + QBitmap-4 + ( const QSize & size ) + + + QBitmap + QBitmap-5 + ( const QString & fileName, const char * format = 0 ) + + + clear + clear + () + + + fromData + fromData + ( const QSize & size, const uchar * bits, QImage::Format monoFormat = QImage::Format_MonoLSB ) + + + fromImage + fromImage + ( const QImage & image, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + transformed + transformed + ( const QMatrix & matrix ) + + + transformed + transformed-2 + ( const QTransform & matrix ) + + + operator + operator-QVariant + QVariant() + + + operator= + operator-eq + ( const QPixmap & pixmap ) + + + QBitmap + QBitmap-6 + ( int width, int height, bool clear ) + + + QBitmap + QBitmap-7 + ( const QSize & size, bool clear ) + + + QBitmap + QBitmap-8 + ( int width, int height, const uchar * bits, bool isXbitmap = false ) + + + QBitmap + QBitmap-9 + ( const QImage & image ) + + + QBitmap + QBitmap-10 + ( const QSize & size, const uchar * bits, bool isXbitmap = false ) + + + xForm + xForm + ( const QMatrix & matrix ) + + + operator= + operator-eq-2 + ( const QImage & image ) + + + QBoxLayout + QBoxLayout-2 + ( QWidget * parent, Direction dir, int margin = 0, int spacing = -1, const char * name = 0 ) + + + QBoxLayout + QBoxLayout-3 + ( QLayout * parentLayout, Direction dir, int spacing = -1, const char * name = 0 ) + + + QBoxLayout + QBoxLayout-4 + ( Direction dir, int spacing, const char * name = 0 ) + + + findWidget + findWidget + ( QWidget * widget ) + + + + QBoxLayout + qboxlayout.html + + Direction + Direction-enum + + + + QBoxLayout + QBoxLayout + ( Direction dir, QWidget * parent = 0 ) + + + addLayout + addLayout + ( QLayout * layout, int stretch = 0 ) + + + addSpacing + addSpacing + ( int size ) + + + addStretch + addStretch + ( int stretch = 0 ) + + + addStrut + addStrut + ( int size ) + + + addWidget + addWidget + ( QWidget * widget, int stretch = 0, Qt::Alignment alignment = 0 ) + + + direction + direction + () + + + insertItem + insertItem + ( int index, QLayoutItem * item ) + + + insertLayout + insertLayout + ( int index, QLayout * layout, int stretch = 0 ) + + + insertSpacing + insertSpacing + ( int index, int size ) + + + insertStretch + insertStretch + ( int index, int stretch = 0 ) + + + insertWidget + insertWidget + ( int index, QWidget * widget, int stretch = 0, Qt::Alignment alignment = 0 ) + + + invalidate + invalidate + () + + + setDirection + setDirection + ( Direction direction ) + + + setSpacing + setSpacing + ( int spacing ) + + + setStretchFactor + setStretchFactor + ( QWidget * widget, int stretch ) + + + setStretchFactor + setStretchFactor-2 + ( QLayout * layout, int stretch ) + + + spacing + spacing + () + + + QBoxLayout + QBoxLayout-2 + ( QWidget * parent, Direction dir, int margin = 0, int spacing = -1, const char * name = 0 ) + + + QBoxLayout + QBoxLayout-3 + ( QLayout * parentLayout, Direction dir, int spacing = -1, const char * name = 0 ) + + + QBoxLayout + QBoxLayout-4 + ( Direction dir, int spacing, const char * name = 0 ) + + + findWidget + findWidget + ( QWidget * widget ) + + + pixmap + pixmap + () + + + setPixmap + setPixmap + ( const QPixmap & pixmap ) + + + operator + operator-const-QColor--and + const QColor &() + + + + QBrush + qbrush.html + + DataPtr + DataPtr-typedef + + + + QBrush + QBrush + () + + + QBrush + QBrush-2 + ( Qt::BrushStyle style ) + + + QBrush + QBrush-3 + ( const QColor & color, Qt::BrushStyle style = Qt::SolidPattern ) + + + QBrush + QBrush-4 + ( Qt::GlobalColor color, Qt::BrushStyle style = Qt::SolidPattern ) + + + QBrush + QBrush-5 + ( const QColor & color, const QPixmap & pixmap ) + + + QBrush + QBrush-6 + ( Qt::GlobalColor color, const QPixmap & pixmap ) + + + QBrush + QBrush-7 + ( const QPixmap & pixmap ) + + + QBrush + QBrush-8 + ( const QImage & image ) + + + QBrush + QBrush-9 + ( const QBrush & other ) + + + QBrush + QBrush-10 + ( const QGradient & gradient ) + + + color + color + () + + + data_ptr + data_ptr + () + + + gradient + gradient + () + + + isDetached + isDetached + () + + + isOpaque + isOpaque + () + + + matrix + matrix + () + + + setColor + setColor + ( const QColor & color ) + + + setColor + setColor-2 + ( Qt::GlobalColor color ) + + + setMatrix + setMatrix + ( const QMatrix & matrix ) + + + setStyle + setStyle + ( Qt::BrushStyle style ) + + + setTexture + setTexture + ( const QPixmap & pixmap ) + + + setTextureImage + setTextureImage + ( const QImage & image ) + + + setTransform + setTransform + ( const QTransform & ) + + + BrushStyle + style + QBrush::style() + + + texture + texture + () + + + textureImage + textureImage + () + + + transform + transform + () + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QBrush & brush ) + + + operator= + operator-eq + ( const QBrush & brush ) + + + operator== + operator-eq-eq + ( const QBrush & brush ) + + + pixmap + pixmap + () + + + setPixmap + setPixmap + ( const QPixmap & pixmap ) + + + operator + operator-const-QColor--and + const QColor &() + + + + QBuffer + qbuffer.html + + QBuffer + QBuffer + ( QObject * parent = 0 ) + + + QBuffer + QBuffer-2 + ( QByteArray * byteArray, QObject * parent = 0 ) + + + buffer + buffer + () + + + buffer + buffer-2 + () + + + data + data + () + + + setBuffer + setBuffer + ( QByteArray * byteArray ) + + + setData + setData + ( const QByteArray & data ) + + + setData + setData-2 + ( const char * data, int size ) + + + insert + insert + ( QAbstractButton * b ) + + + remove + remove + ( QAbstractButton * b ) + + + + QButtonGroup + qbuttongroup.html + + QButtonGroup + QButtonGroup + ( QObject * parent = 0 ) + + + addButton + addButton + ( QAbstractButton * button ) + + + addButton + addButton-2 + ( QAbstractButton * button, int id ) + + + button + button + ( int id ) + + + buttonClicked + buttonClicked + ( QAbstractButton * button ) + + + buttonClicked + buttonClicked-2 + ( int id ) + + + buttonPressed + buttonPressed + ( QAbstractButton * button ) + + + buttonPressed + buttonPressed-2 + ( int id ) + + + buttonReleased + buttonReleased + ( QAbstractButton * button ) + + + buttonReleased + buttonReleased-2 + ( int id ) + + + buttons + buttons + () + + + checkedButton + checkedButton + () + + + checkedId + checkedId + () + + + id + id + ( QAbstractButton * button ) + + + removeButton + removeButton + ( QAbstractButton * button ) + + + setId + setId + ( QAbstractButton * button, int id ) + + + insert + insert + ( QAbstractButton * b ) + + + remove + remove + ( QAbstractButton * b ) + + + QByteArray + QByteArray-6 + ( int size ) + + + duplicate + duplicate + ( const QByteArray & a ) + + + duplicate + duplicate-2 + ( const char * a, uint n ) + + + find + find + ( char c, int from = 0 ) + + + find + find-2 + ( const char * c, int from = 0 ) + + + find + find-3 + ( const QByteArray & ba, int from = 0 ) + + + find + find-4 + ( const QString & s, int from = 0 ) + + + findRev + findRev + ( char c, int from = -1 ) + + + findRev + findRev-2 + ( const char * c, int from = -1 ) + + + findRev + findRev-3 + ( const QByteArray & ba, int from = -1 ) + + + findRev + findRev-4 + ( const QString & s, int from = -1 ) + + + leftJustify + leftJustify + ( uint width, char fill = ' ', bool truncate = false ) + + + lower + lower + () + + + resetRawData + resetRawData + ( const char * data, uint n ) + + + rightJustify + rightJustify + ( uint width, char fill = ' ', bool truncate = false ) + + + setRawData + setRawData + ( const char * a, uint n ) + + + simplifyWhiteSpace + simplifyWhiteSpace + () + + + stripWhiteSpace + stripWhiteSpace + () + + + upper + upper + () + + + + QByteArray + qbytearray.html + + DataPtr + DataPtr-typedef + + + + QByteArray + QByteArray + () + + + QByteArray + QByteArray-2 + ( const char * str ) + + + QByteArray + QByteArray-3 + ( const char * data, int size ) + + + QByteArray + QByteArray-4 + ( int size, char ch ) + + + QByteArray + QByteArray-5 + ( const QByteArray & other ) + + + append + append + ( const QByteArray & ba ) + + + append + append-2 + ( const QString & str ) + + + append + append-3 + ( const char * str ) + + + append + append-4 + ( char ch ) + + + at + at + ( int i ) + + + capacity + capacity + () + + + chop + chop + ( int n ) + + + clear + clear + () + + + constData + constData + () + + + contains + contains + ( const QByteArray & ba ) + + + contains + contains-2 + ( const char * str ) + + + contains + contains-3 + ( char ch ) + + + count + count + ( const QByteArray & ba ) + + + count + count-2 + ( const char * str ) + + + count + count-3 + ( char ch ) + + + count + count-4 + () + + + data + data + () + + + data + data-2 + () + + + data_ptr + data_ptr + () + + + endsWith + endsWith + ( const QByteArray & ba ) + + + endsWith + endsWith-2 + ( const char * str ) + + + endsWith + endsWith-3 + ( char ch ) + + + fill + fill + ( char ch, int size = -1 ) + + + fromBase64 + fromBase64 + ( const QByteArray & base64 ) + + + fromHex + fromHex + ( const QByteArray & hexEncoded ) + + + fromRawData + fromRawData + ( const char * data, int size ) + + + indexOf + indexOf + ( const QByteArray & ba, int from = 0 ) + + + indexOf + indexOf-2 + ( const QString & str, int from = 0 ) + + + indexOf + indexOf-3 + ( const char * str, int from = 0 ) + + + indexOf + indexOf-4 + ( char ch, int from = 0 ) + + + insert + insert + ( int i, const QByteArray & ba ) + + + insert + insert-2 + ( int i, const QString & str ) + + + insert + insert-3 + ( int i, const char * str ) + + + insert + insert-4 + ( int i, char ch ) + + + isEmpty + isEmpty + () + + + isNull + isNull + () + + + lastIndexOf + lastIndexOf + ( const QByteArray & ba, int from = -1 ) + + + lastIndexOf + lastIndexOf-2 + ( const QString & str, int from = -1 ) + + + lastIndexOf + lastIndexOf-3 + ( const char * str, int from = -1 ) + + + lastIndexOf + lastIndexOf-4 + ( char ch, int from = -1 ) + + + left + left + ( int len ) + + + leftJustified + leftJustified + ( int width, char fill = ' ', bool truncate = false ) + + + length + length + () + + + mid + mid + ( int pos, int len = -1 ) + + + number + number + ( int n, int base = 10 ) + + + number + number-2 + ( uint n, int base = 10 ) + + + number + number-3 + ( qlonglong n, int base = 10 ) + + + number + number-4 + ( qulonglong n, int base = 10 ) + + + number + number-5 + ( double n, char f = 'g', int prec = 6 ) + + + prepend + prepend + ( const QByteArray & ba ) + + + prepend + prepend-2 + ( const char * str ) + + + prepend + prepend-3 + ( char ch ) + + + push_back + push_back + ( const QByteArray & other ) + + + push_back + push_back-2 + ( const char * str ) + + + push_back + push_back-3 + ( char ch ) + + + push_front + push_front + ( const QByteArray & other ) + + + push_front + push_front-2 + ( const char * str ) + + + push_front + push_front-3 + ( char ch ) + + + remove + remove + ( int pos, int len ) + + + replace + replace + ( int pos, int len, const QByteArray & after ) + + + replace + replace-2 + ( int pos, int len, const char * after ) + + + replace + replace-3 + ( const QByteArray & before, const QByteArray & after ) + + + replace + replace-4 + ( const char * before, const QByteArray & after ) + + + replace + replace-5 + ( const QByteArray & before, const char * after ) + + + replace + replace-6 + ( const QString & before, const QByteArray & after ) + + + replace + replace-7 + ( const QString & before, const char * after ) + + + replace + replace-8 + ( const char * before, const char * after ) + + + replace + replace-9 + ( char before, const QByteArray & after ) + + + replace + replace-10 + ( char before, const QString & after ) + + + replace + replace-11 + ( char before, const char * after ) + + + replace + replace-12 + ( char before, char after ) + + + reserve + reserve + ( int size ) + + + resize + resize + ( int size ) + + + right + right + ( int len ) + + + rightJustified + rightJustified + ( int width, char fill = ' ', bool truncate = false ) + + + setNum + setNum + ( int n, int base = 10 ) + + + setNum + setNum-2 + ( uint n, int base = 10 ) + + + setNum + setNum-3 + ( short n, int base = 10 ) + + + setNum + setNum-4 + ( ushort n, int base = 10 ) + + + setNum + setNum-5 + ( qlonglong n, int base = 10 ) + + + setNum + setNum-6 + ( qulonglong n, int base = 10 ) + + + setNum + setNum-7 + ( double n, char f = 'g', int prec = 6 ) + + + setNum + setNum-8 + ( float n, char f = 'g', int prec = 6 ) + + + simplified + simplified + () + + + size + size + () + + + split + split + ( char sep ) + + + squeeze + squeeze + () + + + startsWith + startsWith + ( const QByteArray & ba ) + + + startsWith + startsWith-2 + ( const char * str ) + + + startsWith + startsWith-3 + ( char ch ) + + + toBase64 + toBase64 + () + + + toDouble + toDouble + ( bool * ok = 0 ) + + + toFloat + toFloat + ( bool * ok = 0 ) + + + toHex + toHex + () + + + toInt + toInt + ( bool * ok = 0, int base = 10 ) + + + toLong + toLong + ( bool * ok = 0, int base = 10 ) + + + toLongLong + toLongLong + ( bool * ok = 0, int base = 10 ) + + + toLower + toLower + () + + + toShort + toShort + ( bool * ok = 0, int base = 10 ) + + + toUInt + toUInt + ( bool * ok = 0, int base = 10 ) + + + toULong + toULong + ( bool * ok = 0, int base = 10 ) + + + toULongLong + toULongLong + ( bool * ok = 0, int base = 10 ) + + + toUShort + toUShort + ( bool * ok = 0, int base = 10 ) + + + toUpper + toUpper + () + + + trimmed + trimmed + () + + + truncate + truncate + ( int pos ) + + + operator + operator-const-char--2a + const char *() + + + operator + operator-const-void--2a + const void *() + + + operator!= + operator-not-eq + ( const QString & str ) + + + operator+= + operator-2b-eq + ( const QByteArray & ba ) + + + operator+= + operator-2b-eq-2 + ( const QString & str ) + + + operator+= + operator-2b-eq-3 + ( const char * str ) + + + operator+= + operator-2b-eq-4 + ( char ch ) + + + operator< + operator-lt + ( const QString & str ) + + + operator<= + operator-lt-eq + ( const QString & str ) + + + operator= + operator-eq + ( const QByteArray & other ) + + + operator= + operator-eq-2 + ( const char * str ) + + + operator== + operator-eq-eq + ( const QString & str ) + + + operator> + operator-gt + ( const QString & str ) + + + operator>= + operator-gt-eq + ( const QString & str ) + + + operator[] + operator-5b-5d + ( int i ) + + + operator[] + operator-5b-5d-2 + ( int i ) + + + operator[] + operator-5b-5d-3 + ( uint i ) + + + operator[] + operator-5b-5d-4 + ( uint i ) + + + QByteArray + QByteArray-6 + ( int size ) + + + duplicate + duplicate + ( const QByteArray & a ) + + + duplicate + duplicate-2 + ( const char * a, uint n ) + + + find + find + ( char c, int from = 0 ) + + + find + find-2 + ( const char * c, int from = 0 ) + + + find + find-3 + ( const QByteArray & ba, int from = 0 ) + + + find + find-4 + ( const QString & s, int from = 0 ) + + + findRev + findRev + ( char c, int from = -1 ) + + + findRev + findRev-2 + ( const char * c, int from = -1 ) + + + findRev + findRev-3 + ( const QByteArray & ba, int from = -1 ) + + + findRev + findRev-4 + ( const QString & s, int from = -1 ) + + + leftJustify + leftJustify + ( uint width, char fill = ' ', bool truncate = false ) + + + lower + lower + () + + + resetRawData + resetRawData + ( const char * data, uint n ) + + + rightJustify + rightJustify + ( uint width, char fill = ' ', bool truncate = false ) + + + setRawData + setRawData + ( const char * a, uint n ) + + + simplifyWhiteSpace + simplifyWhiteSpace + () + + + stripWhiteSpace + stripWhiteSpace + () + + + upper + upper + () + + + + QByteArrayMatcher + qbytearraymatcher.html + + QByteArrayMatcher + QByteArrayMatcher + () + + + QByteArrayMatcher + QByteArrayMatcher-2 + ( const QByteArray & pattern ) + + + QByteArrayMatcher + QByteArrayMatcher-3 + ( const QByteArrayMatcher & other ) + + + indexIn + indexIn + ( const QByteArray & ba, int from = 0 ) + + + pattern + pattern + () + + + setPattern + setPattern + ( const QByteArray & pattern ) + + + operator= + operator-eq + ( const QByteArrayMatcher & other ) + + + QCache + QCache-2 + ( int maxCost, int dummy ) + + + + QCache + qcache.html + + QCache + QCache + ( int maxCost = 100 ) + + + clear + clear + () + + + contains + contains + ( const Key & key ) + + + count + count + () + + + insert + insert + ( const Key & key, T * object, int cost = 1 ) + + + isEmpty + isEmpty + () + + + keys + keys + () + + + maxCost + maxCost + () + + + object + object + ( const Key & key ) + + + remove + remove + ( const Key & key ) + + + setMaxCost + setMaxCost + ( int cost ) + + + size + size + () + + + take + take + ( const Key & key ) + + + totalCost + totalCost + () + + + operator[] + operator-5b-5d + ( const Key & key ) + + + QCache + QCache-2 + ( int maxCost, int dummy ) + + + + QCalendarWidget + qcalendarwidget.html + + HorizontalHeaderFormat + HorizontalHeaderFormat-enum + + + + SelectionMode + SelectionMode-enum + + + + VerticalHeaderFormat + VerticalHeaderFormat-enum + + + + DayOfWeek + firstDayOfWeek-prop + + + + QCalendarWidget + QCalendarWidget + ( QWidget * parent = 0 ) + + + activated + activated + ( const QDate & date ) + + + clicked + clicked + ( const QDate & date ) + + + currentPageChanged + currentPageChanged + ( int year, int month ) + + + dateTextFormat + dateTextFormat + () + + + dateTextFormat + dateTextFormat-2 + ( const QDate & date ) + + + headerTextFormat + headerTextFormat + () + + + monthShown + monthShown + () + + + paintCell + paintCell + ( QPainter * painter, const QRect & rect, const QDate & date ) + + + selectionChanged + selectionChanged + () + + + setCurrentPage + setCurrentPage + ( int year, int month ) + + + setDateRange + setDateRange + ( const QDate & min, const QDate & max ) + + + setDateTextFormat + setDateTextFormat + ( const QDate & date, const QTextCharFormat & format ) + + + setHeaderTextFormat + setHeaderTextFormat + ( const QTextCharFormat & format ) + + + setWeekdayTextFormat + setWeekdayTextFormat + ( Qt::DayOfWeek dayOfWeek, const QTextCharFormat & format ) + + + showNextMonth + showNextMonth + () + + + showNextYear + showNextYear + () + + + showPreviousMonth + showPreviousMonth + () + + + showPreviousYear + showPreviousYear + () + + + showSelectedDate + showSelectedDate + () + + + showToday + showToday + () + + + weekdayTextFormat + weekdayTextFormat + ( Qt::DayOfWeek dayOfWeek ) + + + yearShown + yearShown + () + + + + QCDEStyle + qcdestyle.html + + QCDEStyle + QCDEStyle + ( bool useHighlightCols = false ) + + + ascii + ascii + () + + + latin1 + latin1 + () + + + lower + lower + () + + + mirrored + mirrored + () + + + networkOrdered + networkOrdered + () + + + upper + upper + () + + + + QChar + qchar.html + + Category + Category-enum + + + + Decomposition + Decomposition-enum + + + + Direction + Direction-enum + + + + Joining + Joining-enum + + + + SpecialCharacter + SpecialCharacter-enum + + + + UnicodeVersion + UnicodeVersion-enum + + + + QChar + QChar + () + + + QChar + QChar-2 + ( char ch ) + + + QChar + QChar-3 + ( uchar ch ) + + + QChar + QChar-4 + ( QLatin1Char ch ) + + + QChar + QChar-5 + ( uchar cell, uchar row ) + + + QChar + QChar-6 + ( ushort code ) + + + QChar + QChar-7 + ( short code ) + + + QChar + QChar-8 + ( uint code ) + + + QChar + QChar-9 + ( int code ) + + + QChar + QChar-10 + ( SpecialCharacter ch ) + + + category + category + () + + + category + category-2 + ( uint ucs4 ) + + + category + category-3 + ( ushort ucs2 ) + + + cell + cell + () + + + combiningClass + combiningClass + () + + + combiningClass + combiningClass-2 + ( uint ucs4 ) + + + combiningClass + combiningClass-3 + ( ushort ucs2 ) + + + decomposition + decomposition + () + + + decomposition + decomposition-2 + ( uint ucs4 ) + + + decompositionTag + decompositionTag + () + + + decompositionTag + decompositionTag-2 + ( uint ucs4 ) + + + digitValue + digitValue + () + + + digitValue + digitValue-2 + ( ushort ucs2 ) + + + digitValue + digitValue-3 + ( uint ucs4 ) + + + direction + direction + () + + + direction + direction-2 + ( uint ucs4 ) + + + direction + direction-3 + ( ushort ucs2 ) + + + fromAscii + fromAscii + ( char c ) + + + fromLatin1 + fromLatin1 + ( char c ) + + + hasMirrored + hasMirrored + () + + + highSurrogate + highSurrogate + ( uint ucs4 ) + + + isDigit + isDigit + () + + + isHighSurrogate + isHighSurrogate + () + + + isLetter + isLetter + () + + + isLetterOrNumber + isLetterOrNumber + () + + + isLowSurrogate + isLowSurrogate + () + + + isLower + isLower + () + + + isMark + isMark + () + + + isNull + isNull + () + + + isNumber + isNumber + () + + + isPrint + isPrint + () + + + isPunct + isPunct + () + + + isSpace + isSpace + () + + + isSymbol + isSymbol + () + + + isTitleCase + isTitleCase + () + + + isUpper + isUpper + () + + + joining + joining + () + + + joining + joining-2 + ( uint ucs4 ) + + + joining + joining-3 + ( ushort ucs2 ) + + + lowSurrogate + lowSurrogate + ( uint ucs4 ) + + + mirroredChar + mirroredChar + () + + + mirroredChar + mirroredChar-2 + ( uint ucs4 ) + + + mirroredChar + mirroredChar-3 + ( ushort ucs2 ) + + + row + row + () + + + surrogateToUcs4 + surrogateToUcs4 + ( ushort high, ushort low ) + + + surrogateToUcs4 + surrogateToUcs4-2 + ( QChar high, QChar low ) + + + toAscii + toAscii + () + + + toCaseFolded + toCaseFolded + () + + + toCaseFolded + toCaseFolded-2 + ( uint ucs4 ) + + + toCaseFolded + toCaseFolded-3 + ( ushort ucs2 ) + + + toLatin1 + toLatin1 + () + + + toLower + toLower + () + + + toLower + toLower-2 + ( uint ucs4 ) + + + toLower + toLower-3 + ( ushort ucs2 ) + + + toTitleCase + toTitleCase + () + + + toTitleCase + toTitleCase-2 + ( uint ucs4 ) + + + toTitleCase + toTitleCase-3 + ( ushort ucs2 ) + + + toUpper + toUpper + () + + + toUpper + toUpper-2 + ( uint ucs4 ) + + + toUpper + toUpper-3 + ( ushort ucs2 ) + + + unicode + unicode + () + + + unicode + unicode-2 + () + + + unicodeVersion + unicodeVersion + () + + + unicodeVersion + unicodeVersion-2 + ( uint ucs4 ) + + + unicodeVersion + unicodeVersion-3 + ( ushort ucs2 ) + + + ascii + ascii + () + + + latin1 + latin1 + () + + + lower + lower + () + + + mirrored + mirrored + () + + + networkOrdered + networkOrdered + () + + + upper + upper + () + + + ToggleState + ToggleState-enum + + + + QCheckBox + QCheckBox-3 + ( QWidget * parent, const char * name ) + + + QCheckBox + QCheckBox-4 + ( const QString & text, QWidget * parent, const char * name ) + + + setNoChange + setNoChange + () + + + setState + setState + ( ToggleState state ) + + + state + state + () + + + + QCheckBox + qcheckbox.html + + QCheckBox + QCheckBox + ( QWidget * parent = 0 ) + + + QCheckBox + QCheckBox-2 + ( const QString & text, QWidget * parent = 0 ) + + + CheckState + checkState + QCheckBox::checkState() + + + initStyleOption + initStyleOption + ( QStyleOptionButton * option ) + + + setCheckState + setCheckState + ( Qt::CheckState state ) + + + stateChanged + stateChanged + ( int state ) + + + ToggleState + ToggleState-enum + + + + QCheckBox + QCheckBox-3 + ( QWidget * parent, const char * name ) + + + QCheckBox + QCheckBox-4 + ( const QString & text, QWidget * parent, const char * name ) + + + setNoChange + setNoChange + () + + + setState + setState + ( ToggleState state ) + + + state + state + () + + + inserted + inserted + () + + + + QChildEvent + qchildevent.html + + QChildEvent + QChildEvent + ( Type type, QObject * child ) + + + added + added + () + + + child + child + () + + + polished + polished + () + + + removed + removed + () + + + inserted + inserted + () + + + + QCleanlooksStyle + qcleanlooksstyle.html + + QCleanlooksStyle + QCleanlooksStyle + () + + + drawItemText + drawItemText + ( QPainter * painter, const QRect & rectangle, int alignment, const QPalette & palette, bool enabled, const QString & text, QPalette::ColorRole textRole = QPalette::NoRole ) + + + data + data + ( Mode mode = Clipboard ) + + + setData + setData + ( QMimeSource * src, Mode mode = Clipboard ) + + + + QClipboard + qclipboard.html + + Mode + Mode-enum + + + + changed + changed + ( QClipboard::Mode mode ) + + + clear + clear + ( Mode mode = Clipboard ) + + + dataChanged + dataChanged + () + + + findBufferChanged + findBufferChanged + () + + + image + image + ( Mode mode = Clipboard ) + + + mimeData + mimeData + ( Mode mode = Clipboard ) + + + ownsClipboard + ownsClipboard + () + + + ownsFindBuffer + ownsFindBuffer + () + + + ownsSelection + ownsSelection + () + + + pixmap + pixmap + ( Mode mode = Clipboard ) + + + selectionChanged + selectionChanged + () + + + setImage + setImage + ( const QImage & image, Mode mode = Clipboard ) + + + setMimeData + setMimeData + ( QMimeData * src, Mode mode = Clipboard ) + + + setPixmap + setPixmap + ( const QPixmap & pixmap, Mode mode = Clipboard ) + + + setText + setText + ( const QString & text, Mode mode = Clipboard ) + + + supportsFindBuffer + supportsFindBuffer + () + + + supportsSelection + supportsSelection + () + + + text + text + ( Mode mode = Clipboard ) + + + text + text-2 + ( QString & subtype, Mode mode = Clipboard ) + + + data + data + ( Mode mode = Clipboard ) + + + setData + setData + ( QMimeSource * src, Mode mode = Clipboard ) + + + + QCloseEvent + qcloseevent.html + + QCloseEvent + QCloseEvent + () + + + dark + dark + ( int factor = 200 ) + + + light + light + ( int factor = 150 ) + + + QColor + QColor-8 + ( int x, int y, int z, Spec colorSpec ) + + + getRgba + getRgba + ( int * r, int * g, int * b, int * a ) + + + hsv + hsv + ( int * h, int * s, int * v ) + + + pixel + pixel + ( int screen = -1 ) + + + rgb + rgb-2 + ( int * r, int * g, int * b ) + + + setRgba + setRgba-2 + ( int r, int g, int b, int a ) + + + + QColor + qcolor.html + + Spec + Spec-enum + + + + QColor + QColor + () + + + QColor + QColor-2 + ( int r, int g, int b, int a = 255 ) + + + QColor + QColor-3 + ( QRgb color ) + + + QColor + QColor-4 + ( const QString & name ) + + + QColor + QColor-5 + ( const char * name ) + + + QColor + QColor-6 + ( const QColor & color ) + + + QColor + QColor-9 + ( Qt::GlobalColor color ) + + + allowX11ColorNames + allowX11ColorNames + () + + + alpha + alpha + () + + + alphaF + alphaF + () + + + black + black + () + + + blackF + blackF + () + + + blue + blue + () + + + blueF + blueF + () + + + colorNames + colorNames + () + + + convertTo + convertTo + ( Spec colorSpec ) + + + cyan + cyan + () + + + cyanF + cyanF + () + + + darker + darker + ( int factor = 200 ) + + + fromCmyk + fromCmyk + ( int c, int m, int y, int k, int a = 255 ) + + + fromCmykF + fromCmykF + ( qreal c, qreal m, qreal y, qreal k, qreal a = 1.0 ) + + + fromHsv + fromHsv + ( int h, int s, int v, int a = 255 ) + + + fromHsvF + fromHsvF + ( qreal h, qreal s, qreal v, qreal a = 1.0 ) + + + fromRgb + fromRgb + ( QRgb rgb ) + + + fromRgb + fromRgb-2 + ( int r, int g, int b, int a = 255 ) + + + fromRgbF + fromRgbF + ( qreal r, qreal g, qreal b, qreal a = 1.0 ) + + + fromRgba + fromRgba + ( QRgb rgba ) + + + getCmyk + getCmyk + ( int * c, int * m, int * y, int * k, int * a = 0 ) + + + getCmykF + getCmykF + ( qreal * c, qreal * m, qreal * y, qreal * k, qreal * a = 0 ) + + + getHsv + getHsv + ( int * h, int * s, int * v, int * a = 0 ) + + + getHsvF + getHsvF + ( qreal * h, qreal * s, qreal * v, qreal * a = 0 ) + + + getRgb + getRgb + ( int * r, int * g, int * b, int * a = 0 ) + + + getRgbF + getRgbF + ( qreal * r, qreal * g, qreal * b, qreal * a = 0 ) + + + green + green + () + + + greenF + greenF + () + + + hue + hue + () + + + hueF + hueF + () + + + isValid + isValid + () + + + lighter + lighter + ( int factor = 150 ) + + + magenta + magenta + () + + + magentaF + magentaF + () + + + name + name + () + + + red + red + () + + + redF + redF + () + + + rgb + rgb + () + + + rgba + rgba + () + + + saturation + saturation + () + + + saturationF + saturationF + () + + + setAllowX11ColorNames + setAllowX11ColorNames + ( bool enabled ) + + + setAlpha + setAlpha + ( int alpha ) + + + setAlphaF + setAlphaF + ( qreal alpha ) + + + setBlue + setBlue + ( int blue ) + + + setBlueF + setBlueF + ( qreal blue ) + + + setCmyk + setCmyk + ( int c, int m, int y, int k, int a = 255 ) + + + setCmykF + setCmykF + ( qreal c, qreal m, qreal y, qreal k, qreal a = 1.0 ) + + + setGreen + setGreen + ( int green ) + + + setGreenF + setGreenF + ( qreal green ) + + + setHsv + setHsv + ( int h, int s, int v, int a = 255 ) + + + setHsvF + setHsvF + ( qreal h, qreal s, qreal v, qreal a = 1.0 ) + + + setNamedColor + setNamedColor + ( const QString & name ) + + + setRed + setRed + ( int red ) + + + setRedF + setRedF + ( qreal red ) + + + setRgb + setRgb + ( int r, int g, int b, int a = 255 ) + + + setRgb + setRgb-2 + ( QRgb rgb ) + + + setRgbF + setRgbF + ( qreal r, qreal g, qreal b, qreal a = 1.0 ) + + + setRgba + setRgba + ( QRgb rgba ) + + + spec + spec + () + + + toCmyk + toCmyk + () + + + toHsv + toHsv + () + + + toRgb + toRgb + () + + + value + value + () + + + valueF + valueF + () + + + yellow + yellow + () + + + yellowF + yellowF + () + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QColor & color ) + + + operator= + operator-eq + ( const QColor & color ) + + + operator= + operator-eq-2 + ( Qt::GlobalColor color ) + + + operator== + operator-eq-eq + ( const QColor & color ) + + + QColor + QColor-8 + ( int x, int y, int z, Spec colorSpec ) + + + getRgba + getRgba + ( int * r, int * g, int * b, int * a ) + + + hsv + hsv + ( int * h, int * s, int * v ) + + + pixel + pixel + ( int screen = -1 ) + + + rgb + rgb-2 + ( int * r, int * g, int * b ) + + + setRgba + setRgba-2 + ( int r, int g, int b, int a ) + + + getColor + getColor-2 + ( const QColor & init, QWidget * parent, const char * name ) + + + getRgba + getRgba-2 + ( QRgb rgba, bool * ok, QWidget * parent, const char * name ) + + + + QColorDialog + qcolordialog.html + + changeEvent + changeEvent + ( QEvent * e ) + + + customColor + customColor + ( int i ) + + + customCount + customCount + () + + + getColor + getColor + ( const QColor & initial = Qt::white, QWidget * parent = 0 ) + + + getRgba + getRgba + ( QRgb initial = 0xffffffff, bool * ok = 0, QWidget * parent = 0 ) + + + setCustomColor + setCustomColor + ( int number, QRgb color ) + + + setStandardColor + setStandardColor + ( int number, QRgb color ) + + + getColor + getColor-2 + ( const QColor & init, QWidget * parent, const char * name ) + + + getRgba + getRgba-2 + ( QRgb rgba, bool * ok, QWidget * parent, const char * name ) + + + background + background + () + + + base + base + () + + + brightText + brightText + () + + + button + button + () + + + buttonText + buttonText + () + + + dark + dark + () + + + foreground + foreground + () + + + highlight + highlight + () + + + highlightedText + highlightedText + () + + + light + light + () + + + link + link + () + + + linkVisited + linkVisited + () + + + mid + mid + () + + + midlight + midlight + () + + + shadow + shadow + () + + + text + text + () + + + + QColorGroup + qcolorgroup.html + + QColorGroup + QColorGroup + () + + + QColorGroup + QColorGroup-2 + ( const QBrush & foreground, const QBrush & button, const QBrush & light, const QBrush & dark, const QBrush & mid, const QBrush & text, const QBrush & bright_text, const QBrush & base, const QBrush & background ) + + + QColorGroup + QColorGroup-3 + ( const QColor & foreground, const QColor & background, const QColor & light, const QColor & dark, const QColor & mid, const QColor & text, const QColor & base ) + + + QColorGroup + QColorGroup-4 + ( const QColorGroup & other ) + + + QColorGroup + QColorGroup-5 + ( const QPalette & pal ) + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QColorGroup & other ) + + + operator== + operator-eq-eq + ( const QColorGroup & other ) + + + background + background + () + + + base + base + () + + + brightText + brightText + () + + + button + button + () + + + buttonText + buttonText + () + + + dark + dark + () + + + foreground + foreground + () + + + highlight + highlight + () + + + highlightedText + highlightedText + () + + + light + light + () + + + link + link + () + + + linkVisited + linkVisited + () + + + mid + mid + () + + + midlight + midlight + () + + + shadow + shadow + () + + + text + text + () + + + + QColormap + qcolormap.html + + Mode + Mode-enum + + + + QColormap + QColormap + ( const QColormap & colormap ) + + + colorAt + colorAt + ( uint pixel ) + + + colormap + colormap + () + + + depth + depth + () + + + hPal + hPal + () + + + instance + instance + ( int screen = -1 ) + + + mode + mode + () + + + pixel + pixel + ( const QColor & color ) + + + size + size + () + + + operator= + operator-eq + ( const QColormap & colormap ) + + + + QColumnView + qcolumnview.html + + QColumnView + QColumnView + ( QWidget * parent = 0 ) + + + columnWidths + columnWidths + () + + + createColumn + createColumn + ( const QModelIndex & index ) + + + previewWidget + previewWidget + () + + + setColumnWidths + setColumnWidths + ( const QList<int> & list ) + + + setPreviewWidget + setPreviewWidget + ( QWidget * widget ) + + + updatePreviewWidget + updatePreviewWidget + ( const QModelIndex & index ) + + + CaseSensitivity + autoCompletionCaseSensitivity-prop + + + + Policy + Policy-typedef + + + + QComboBox + QComboBox-2 + ( QWidget * parent, const char * name ) + + + QComboBox + QComboBox-3 + ( bool rw, QWidget * parent, const char * name = 0 ) + + + changeItem + changeItem + ( const QString & text, int index ) + + + changeItem + changeItem-2 + ( const QPixmap & pixmap, int index ) + + + changeItem + changeItem-3 + ( const QPixmap & pixmap, const QString & text, int index ) + + + clearEdit + clearEdit + () + + + clearValidator + clearValidator + () + + + currentItem + currentItem + () + + + editable + editable + () + + + insertItem + insertItem-3 + ( const QString & text, int index = -1 ) + + + insertItem + insertItem-4 + ( const QPixmap & pixmap, int index = -1 ) + + + insertItem + insertItem-5 + ( const QPixmap & pixmap, const QString & text, int index = -1 ) + + + insertStringList + insertStringList + ( const QStringList & list, int index = -1 ) + + + insertionPolicy + insertionPolicy + () + + + pixmap + pixmap + ( int index ) + + + popup + popup + () + + + setCurrentItem + setCurrentItem + ( int index ) + + + setCurrentText + setCurrentText + ( const QString & text ) + + + setInsertionPolicy + setInsertionPolicy + ( InsertPolicy policy ) + + + text + text + ( int index ) + + + textChanged + textChanged + ( const QString & text ) + + + + QComboBox + qcombobox.html + + InsertPolicy + InsertPolicy-enum + + + + SizeAdjustPolicy + SizeAdjustPolicy-enum + + + + QComboBox + QComboBox + ( QWidget * parent = 0 ) + + + activated + activated + ( int index ) + + + activated + activated-2 + ( const QString & text ) + + + addItem + addItem + ( const QString & text, const QVariant & userData = QVariant() + + + addItem + addItem-2 + ( const QIcon & icon, const QString & text, const QVariant & userData = QVariant() + + + addItems + addItems + ( const QStringList & texts ) + + + clear + clear + () + + + clearEditText + clearEditText + () + + + completer + completer + () + + + currentIndexChanged + currentIndexChanged + ( int index ) + + + currentIndexChanged + currentIndexChanged-2 + ( const QString & text ) + + + editTextChanged + editTextChanged + ( const QString & text ) + + + findData + findData + ( const QVariant & data, int role = Qt::UserRole, Qt::MatchFlags flags = Qt::MatchExactly | Qt::MatchCaseSensitive ) + + + findText + findText + ( const QString & text, Qt::MatchFlags flags = Qt::MatchExactly | Qt::MatchCaseSensitive ) + + + hidePopup + hidePopup + () + + + highlighted + highlighted + ( int index ) + + + highlighted + highlighted-2 + ( const QString & text ) + + + initStyleOption + initStyleOption + ( QStyleOptionComboBox * option ) + + + insertItem + insertItem + ( int index, const QString & text, const QVariant & userData = QVariant() + + + insertItem + insertItem-2 + ( int index, const QIcon & icon, const QString & text, const QVariant & userData = QVariant() + + + insertItems + insertItems + ( int index, const QStringList & list ) + + + itemData + itemData + ( int index, int role = Qt::UserRole ) + + + itemDelegate + itemDelegate + () + + + itemIcon + itemIcon + ( int index ) + + + itemText + itemText + ( int index ) + + + lineEdit + lineEdit + () + + + model + model + () + + + removeItem + removeItem + ( int index ) + + + rootModelIndex + rootModelIndex + () + + + setCompleter + setCompleter + ( QCompleter * completer ) + + + setEditText + setEditText + ( const QString & text ) + + + setItemData + setItemData + ( int index, const QVariant & value, int role = Qt::UserRole ) + + + setItemDelegate + setItemDelegate + ( QAbstractItemDelegate * delegate ) + + + setItemIcon + setItemIcon + ( int index, const QIcon & icon ) + + + setItemText + setItemText + ( int index, const QString & text ) + + + setLineEdit + setLineEdit + ( QLineEdit * edit ) + + + setModel + setModel + ( QAbstractItemModel * model ) + + + setRootModelIndex + setRootModelIndex + ( const QModelIndex & index ) + + + setValidator + setValidator + ( const QValidator * validator ) + + + setView + setView + ( QAbstractItemView * itemView ) + + + showPopup + showPopup + () + + + validator + validator + () + + + view + view + () + + + Policy + Policy-typedef + + + + QComboBox + QComboBox-2 + ( QWidget * parent, const char * name ) + + + QComboBox + QComboBox-3 + ( bool rw, QWidget * parent, const char * name = 0 ) + + + changeItem + changeItem + ( const QString & text, int index ) + + + changeItem + changeItem-2 + ( const QPixmap & pixmap, int index ) + + + changeItem + changeItem-3 + ( const QPixmap & pixmap, const QString & text, int index ) + + + clearEdit + clearEdit + () + + + clearValidator + clearValidator + () + + + currentItem + currentItem + () + + + editable + editable + () + + + insertItem + insertItem-3 + ( const QString & text, int index = -1 ) + + + insertItem + insertItem-4 + ( const QPixmap & pixmap, int index = -1 ) + + + insertItem + insertItem-5 + ( const QPixmap & pixmap, const QString & text, int index = -1 ) + + + insertStringList + insertStringList + ( const QStringList & list, int index = -1 ) + + + insertionPolicy + insertionPolicy + () + + + pixmap + pixmap + ( int index ) + + + popup + popup + () + + + setCurrentItem + setCurrentItem + ( int index ) + + + setCurrentText + setCurrentText + ( const QString & text ) + + + setInsertionPolicy + setInsertionPolicy + ( InsertPolicy policy ) + + + text + text + ( int index ) + + + textChanged + textChanged + ( const QString & text ) + + + + QCommonStyle + qcommonstyle.html + + QCommonStyle + QCommonStyle + () + + + + QCompleter + qcompleter.html + + CompletionMode + CompletionMode-enum + + + + ModelSorting + ModelSorting-enum + + + + CaseSensitivity + caseSensitivity-prop + + + + QCompleter + QCompleter + ( QObject * parent = 0 ) + + + QCompleter + QCompleter-2 + ( QAbstractItemModel * model, QObject * parent = 0 ) + + + QCompleter + QCompleter-3 + ( const QStringList & list, QObject * parent = 0 ) + + + activated + activated + ( const QString & text ) + + + activated + activated-2 + ( const QModelIndex & index ) + + + complete + complete + ( const QRect & rect = QRect() + + + completionCount + completionCount + () + + + completionModel + completionModel + () + + + currentCompletion + currentCompletion + () + + + currentIndex + currentIndex + () + + + currentRow + currentRow + () + + + highlighted + highlighted + ( const QString & text ) + + + highlighted + highlighted-2 + ( const QModelIndex & index ) + + + model + model + () + + + pathFromIndex + pathFromIndex + ( const QModelIndex & index ) + + + popup + popup + () + + + setCurrentRow + setCurrentRow + ( int row ) + + + setModel + setModel + ( QAbstractItemModel * model ) + + + setPopup + setPopup + ( QAbstractItemView * popup ) + + + setWidget + setWidget + ( QWidget * widget ) + + + splitPath + splitPath + ( const QString & path ) + + + widget + widget + () + + + + QConicalGradient + qconicalgradient.html + + QConicalGradient + QConicalGradient + () + + + QConicalGradient + QConicalGradient-2 + ( const QPointF & center, qreal angle ) + + + QConicalGradient + QConicalGradient-3 + ( qreal cx, qreal cy, qreal angle ) + + + angle + angle + () + + + center + center + () + + + setAngle + setAngle + ( qreal angle ) + + + setCenter + setCenter + ( const QPointF & center ) + + + setCenter + setCenter-2 + ( qreal x, qreal y ) + + + QConstString + QConstString + ( const QChar * unicode, int size ) + + + string + string + () + + + + QConstString + qconststring.html + + QConstString + QConstString + ( const QChar * unicode, int size ) + + + string + string + () + + + QContextMenuEvent + QContextMenuEvent-3 + ( Reason reason, const QPoint & pos, const QPoint & globalPos, int dummy ) + + + QContextMenuEvent + QContextMenuEvent-4 + ( Reason reason, const QPoint & pos, int dummy ) + + + ButtonState + state + QContextMenuEvent::state() + + + + QContextMenuEvent + qcontextmenuevent.html + + Reason + Reason-enum + + + + QContextMenuEvent + QContextMenuEvent + ( Reason reason, const QPoint & pos, const QPoint & globalPos ) + + + QContextMenuEvent + QContextMenuEvent-2 + ( Reason reason, const QPoint & pos ) + + + globalPos + globalPos + () + + + globalX + globalX + () + + + globalY + globalY + () + + + pos + pos + () + + + reason + reason + () + + + x + x + () + + + y + y + () + + + QContextMenuEvent + QContextMenuEvent-3 + ( Reason reason, const QPoint & pos, const QPoint & globalPos, int dummy ) + + + QContextMenuEvent + QContextMenuEvent-4 + ( Reason reason, const QPoint & pos, int dummy ) + + + ButtonState + state + QContextMenuEvent::state() + + + QCopChannel + QCopChannel-2 + ( const QString & channel, QObject * parent, const char * name ) + + + + QCopChannel + qcopchannel.html + + QCopChannel + QCopChannel + ( const QString & channel, QObject * parent = 0 ) + + + channel + channel + () + + + flush + flush + () + + + isRegistered + isRegistered + ( const QString & channel ) + + + receive + receive + ( const QString & message, const QByteArray & data ) + + + received + received + ( const QString & message, const QByteArray & data ) + + + send + send + ( const QString & channel, const QString & message, const QByteArray & data ) + + + send + send-2 + ( const QString & channel, const QString & message ) + + + QCopChannel + QCopChannel-2 + ( const QString & channel, QObject * parent, const char * name ) + + + enter_loop + enter_loop + () + + + exit_loop + exit_loop + () + + + lock + lock + () + + + locked + locked + () + + + loopLevel + loopLevel + () + + + processOneEvent + processOneEvent + () + + + tryLock + tryLock + () + + + unlock + unlock + ( bool wakeUpGui = true ) + + + + QCoreApplication + qcoreapplication.html + + Encoding + Encoding-enum + + + + EventFilter + EventFilter-typedef + + + + QCoreApplication + QCoreApplication + ( int & argc, char ** argv ) + + + aboutToQuit + aboutToQuit + () + + + addLibraryPath + addLibraryPath + ( const QString & path ) + + + applicationDirPath + applicationDirPath + () + + + applicationFilePath + applicationFilePath + () + + + arguments + arguments + () + + + closingDown + closingDown + () + + + exec + exec + () + + + exit + exit + ( int returnCode = 0 ) + + + filterEvent + filterEvent + ( void * message, long * result ) + + + flush + flush + () + + + hasPendingEvents + hasPendingEvents + () + + + installTranslator + installTranslator + ( QTranslator * translationFile ) + + + instance + instance + () + + + libraryPaths + libraryPaths + () + + + notify + notify + ( QObject * receiver, QEvent * event ) + + + postEvent + postEvent + ( QObject * receiver, QEvent * event ) + + + postEvent + postEvent-2 + ( QObject * receiver, QEvent * event, int priority ) + + + processEvents + processEvents + ( QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents ) + + + processEvents + processEvents-2 + ( QEventLoop::ProcessEventsFlags flags, int maxtime ) + + + quit + quit + () + + + removeLibraryPath + removeLibraryPath + ( const QString & path ) + + + removePostedEvents + removePostedEvents + ( QObject * receiver ) + + + removePostedEvents + removePostedEvents-2 + ( QObject * receiver, int eventType ) + + + removeTranslator + removeTranslator + ( QTranslator * translationFile ) + + + sendEvent + sendEvent + ( QObject * receiver, QEvent * event ) + + + sendPostedEvents + sendPostedEvents + ( QObject * receiver, int event_type ) + + + sendPostedEvents + sendPostedEvents-2 + () + + + setAttribute + setAttribute + ( Qt::ApplicationAttribute attribute, bool on = true ) + + + setEventFilter + setEventFilter + ( EventFilter filter ) + + + setLibraryPaths + setLibraryPaths + ( const QStringList & paths ) + + + startingUp + startingUp + () + + + testAttribute + testAttribute + ( Qt::ApplicationAttribute attribute ) + + + translate + translate + ( const char * context, const char * sourceText, const char * comment, Encoding encoding, int n ) + + + translate + translate-2 + ( const char * context, const char * sourceText, const char * comment = 0, Encoding encoding = CodecForTr ) + + + winEventFilter + winEventFilter + ( MSG * msg, long * result ) + + + enter_loop + enter_loop + () + + + exit_loop + exit_loop + () + + + lock + lock + () + + + locked + locked + () + + + loopLevel + loopLevel + () + + + processOneEvent + processOneEvent + () + + + tryLock + tryLock + () + + + unlock + unlock + ( bool wakeUpGui = true ) + + + + QCryptographicHash + qcryptographichash.html + + Algorithm + Algorithm-enum + + + + QCryptographicHash + QCryptographicHash + ( Algorithm method ) + + + addData + addData + ( const char * data, int length ) + + + addData + addData-2 + ( const QByteArray & data ) + + + hash + hash + ( const QByteArray & data, Algorithm method ) + + + reset + reset + () + + + result + result + () + + + + QCursor + qcursor.html + + QCursor + QCursor + () + + + QCursor + QCursor-2 + ( Qt::CursorShape shape ) + + + QCursor + QCursor-3 + ( const QBitmap & bitmap, const QBitmap & mask, int hotX = -1, int hotY = -1 ) + + + QCursor + QCursor-4 + ( const QPixmap & pixmap, int hotX = -1, int hotY = -1 ) + + + QCursor + QCursor-5 + ( const QCursor & c ) + + + QCursor + QCursor-6 + ( HCURSOR cursor ) + + + QCursor + QCursor-7 + ( Qt::HANDLE handle ) + + + bitmap + bitmap + () + + + handle + handle + () + + + hotSpot + hotSpot + () + + + mask + mask + () + + + pixmap + pixmap + () + + + pos + pos + () + + + setPos + setPos + ( int x, int y ) + + + setPos + setPos-2 + ( const QPoint & p ) + + + setShape + setShape + ( Qt::CursorShape shape ) + + + CursorShape + shape + QCursor::shape() + + + operator + operator-QVariant + QVariant() + + + operator= + operator-eq + ( const QCursor & c ) + + + QCustomEvent + QCustomEvent + ( int type, void * data = 0 ) + + + data + data + () + + + setData + setData + ( void * data ) + + + + QCustomEvent + qcustomevent.html + + QCustomEvent + QCustomEvent + ( int type, void * data = 0 ) + + + data + data + () + + + setData + setData + ( void * data ) + + + + QCustomRasterPaintDevice + qcustomrasterpaintdevice.html + + QCustomRasterPaintDevice + QCustomRasterPaintDevice + ( QWidget * widget ) + + + bytesPerLine + bytesPerLine + () + + + Format + format + QCustomRasterPaintDevice::format() + + + memory + memory + () + + + QDataStream + QDataStream-3 + ( QByteArray * array, int mode ) + + + eof + eof + () + + + isPrintableData + isPrintableData + () + + + readRawBytes + readRawBytes + ( char * str, uint len ) + + + setPrintableData + setPrintableData + ( bool enable ) + + + writeRawBytes + writeRawBytes + ( const char * str, uint len ) + + + + QDataStream + qdatastream.html + + ByteOrder + ByteOrder-enum + + + + Status + Status-enum + + + + Version + Version-enum + + + + QDataStream + QDataStream + () + + + QDataStream + QDataStream-2 + ( QIODevice * d ) + + + QDataStream + QDataStream-4 + ( QByteArray * a, QIODevice::OpenMode mode ) + + + QDataStream + QDataStream-5 + ( const QByteArray & a ) + + + atEnd + atEnd + () + + + byteOrder + byteOrder + () + + + device + device + () + + + readBytes + readBytes + ( char *& s, uint & l ) + + + readRawData + readRawData + ( char * s, int len ) + + + resetStatus + resetStatus + () + + + setByteOrder + setByteOrder + ( ByteOrder bo ) + + + setDevice + setDevice + ( QIODevice * d ) + + + setStatus + setStatus + ( Status status ) + + + setVersion + setVersion + ( int v ) + + + skipRawData + skipRawData + ( int len ) + + + status + status + () + + + unsetDevice + unsetDevice + () + + + version + version + () + + + writeBytes + writeBytes + ( const char * s, uint len ) + + + writeRawData + writeRawData + ( const char * s, int len ) + + + operator<< + operator-lt-lt + ( qint8 i ) + + + operator<< + operator-lt-lt-2 + ( bool i ) + + + operator<< + operator-lt-lt-3 + ( quint8 i ) + + + operator<< + operator-lt-lt-4 + ( quint16 i ) + + + operator<< + operator-lt-lt-5 + ( qint16 i ) + + + operator<< + operator-lt-lt-6 + ( qint32 i ) + + + operator<< + operator-lt-lt-7 + ( quint64 i ) + + + operator<< + operator-lt-lt-8 + ( qint64 i ) + + + operator<< + operator-lt-lt-9 + ( quint32 i ) + + + operator<< + operator-lt-lt-10 + ( float f ) + + + operator<< + operator-lt-lt-11 + ( double f ) + + + operator<< + operator-lt-lt-12 + ( const char * s ) + + + operator>> + operator-gt-gt + ( qint8 & i ) + + + operator>> + operator-gt-gt-2 + ( bool & i ) + + + operator>> + operator-gt-gt-3 + ( quint8 & i ) + + + operator>> + operator-gt-gt-4 + ( quint16 & i ) + + + operator>> + operator-gt-gt-5 + ( qint16 & i ) + + + operator>> + operator-gt-gt-6 + ( quint32 & i ) + + + operator>> + operator-gt-gt-7 + ( qint32 & i ) + + + operator>> + operator-gt-gt-8 + ( quint64 & i ) + + + operator>> + operator-gt-gt-9 + ( qint64 & i ) + + + operator>> + operator-gt-gt-10 + ( float & f ) + + + operator>> + operator-gt-gt-11 + ( double & f ) + + + operator>> + operator-gt-gt-12 + ( char *& s ) + + + QDataStream + QDataStream-3 + ( QByteArray * array, int mode ) + + + eof + eof + () + + + isPrintableData + isPrintableData + () + + + readRawBytes + readRawBytes + ( char * str, uint len ) + + + setPrintableData + setPrintableData + ( bool enable ) + + + writeRawBytes + writeRawBytes + ( const char * str, uint len ) + + + + QDataWidgetMapper + qdatawidgetmapper.html + + SubmitPolicy + SubmitPolicy-enum + + + + Orientation + orientation-prop + + + + QDataWidgetMapper + QDataWidgetMapper + ( QObject * parent = 0 ) + + + addMapping + addMapping + ( QWidget * widget, int section ) + + + addMapping + addMapping-2 + ( QWidget * widget, int section, const QByteArray & propertyName ) + + + clearMapping + clearMapping + () + + + currentIndexChanged + currentIndexChanged + ( int index ) + + + itemDelegate + itemDelegate + () + + + mappedPropertyName + mappedPropertyName + ( QWidget * widget ) + + + mappedSection + mappedSection + ( QWidget * widget ) + + + mappedWidgetAt + mappedWidgetAt + ( int section ) + + + model + model + () + + + removeMapping + removeMapping + ( QWidget * widget ) + + + revert + revert + () + + + rootIndex + rootIndex + () + + + setCurrentModelIndex + setCurrentModelIndex + ( const QModelIndex & index ) + + + setItemDelegate + setItemDelegate + ( QAbstractItemDelegate * delegate ) + + + setModel + setModel + ( QAbstractItemModel * model ) + + + setRootIndex + setRootIndex + ( const QModelIndex & index ) + + + submit + submit + () + + + toFirst + toFirst + () + + + toLast + toLast + () + + + toNext + toNext + () + + + toPrevious + toPrevious + () + + + setYMD + setYMD + ( int y, int m, int d ) + + + currentDate + currentDate-2 + ( Qt::TimeSpec spec ) + + + dayName + dayName + ( int weekday ) + + + leapYear + leapYear + ( int year ) + + + monthName + monthName + ( int month ) + + + + QDate + qdate.html + + QDate + QDate + () + + + QDate + QDate-2 + ( int y, int m, int d ) + + + addDays + addDays + ( int ndays ) + + + addMonths + addMonths + ( int nmonths ) + + + addYears + addYears + ( int nyears ) + + + currentDate + currentDate + () + + + day + day + () + + + dayOfWeek + dayOfWeek + () + + + dayOfYear + dayOfYear + () + + + daysInMonth + daysInMonth + () + + + daysInYear + daysInYear + () + + + daysTo + daysTo + ( const QDate & d ) + + + fromJulianDay + fromJulianDay + ( int jd ) + + + fromString + fromString + ( const QString & string, Qt::DateFormat format = Qt::TextDate ) + + + fromString + fromString-2 + ( const QString & string, const QString & format ) + + + isLeapYear + isLeapYear + ( int year ) + + + isNull + isNull + () + + + isValid + isValid + () + + + isValid + isValid-2 + ( int year, int month, int day ) + + + longDayName + longDayName + ( int weekday ) + + + longMonthName + longMonthName + ( int month ) + + + month + month + () + + + setDate + setDate + ( int year, int month, int day ) + + + shortDayName + shortDayName + ( int weekday ) + + + shortMonthName + shortMonthName + ( int month ) + + + toJulianDay + toJulianDay + () + + + toString + toString + ( const QString & format ) + + + toString + toString-2 + ( Qt::DateFormat format = Qt::TextDate ) + + + weekNumber + weekNumber + ( int * yearNumber = 0 ) + + + year + year + () + + + operator!= + operator-not-eq + ( const QDate & d ) + + + operator< + operator-lt + ( const QDate & d ) + + + operator<= + operator-lt-eq + ( const QDate & d ) + + + operator== + operator-eq-eq + ( const QDate & d ) + + + operator> + operator-gt + ( const QDate & d ) + + + operator>= + operator-gt-eq + ( const QDate & d ) + + + currentDate + currentDate-2 + ( Qt::TimeSpec spec ) + + + dayName + dayName + ( int weekday ) + + + leapYear + leapYear + ( int year ) + + + monthName + monthName + ( int month ) + + + + QDateEdit + qdateedit.html + + QDateEdit + QDateEdit + ( QWidget * parent = 0 ) + + + QDateEdit + QDateEdit-2 + ( const QDate & date, QWidget * parent = 0 ) + + + currentDateTime + currentDateTime-2 + ( Qt::TimeSpec spec ) + + + setTime_t + setTime_t-2 + ( uint secsSince1Jan1970UTC, Qt::TimeSpec spec ) + + + + QDateTime + qdatetime.html + + QDateTime + QDateTime + () + + + QDateTime + QDateTime-2 + ( const QDate & date ) + + + QDateTime + QDateTime-3 + ( const QDate & date, const QTime & time, Qt::TimeSpec spec = Qt::LocalTime ) + + + QDateTime + QDateTime-4 + ( const QDateTime & other ) + + + addDays + addDays + ( int ndays ) + + + addMSecs + addMSecs + ( qint64 msecs ) + + + addMonths + addMonths + ( int nmonths ) + + + addSecs + addSecs + ( int s ) + + + addYears + addYears + ( int nyears ) + + + currentDateTime + currentDateTime + () + + + date + date + () + + + daysTo + daysTo + ( const QDateTime & other ) + + + fromString + fromString + ( const QString & string, Qt::DateFormat format = Qt::TextDate ) + + + fromString + fromString-2 + ( const QString & string, const QString & format ) + + + fromTime_t + fromTime_t + ( uint seconds ) + + + isNull + isNull + () + + + isValid + isValid + () + + + secsTo + secsTo + ( const QDateTime & other ) + + + setDate + setDate + ( const QDate & date ) + + + setTime + setTime + ( const QTime & time ) + + + setTimeSpec + setTimeSpec + ( Qt::TimeSpec spec ) + + + setTime_t + setTime_t + ( uint seconds ) + + + time + time + () + + + TimeSpec + timeSpec + QDateTime::timeSpec() + + + toLocalTime + toLocalTime + () + + + toString + toString + ( const QString & format ) + + + toString + toString-2 + ( Qt::DateFormat format = Qt::TextDate ) + + + toTimeSpec + toTimeSpec + ( Qt::TimeSpec specification ) + + + toTime_t + toTime_t + () + + + toUTC + toUTC + () + + + operator!= + operator-not-eq + ( const QDateTime & other ) + + + operator< + operator-lt + ( const QDateTime & other ) + + + operator<= + operator-lt-eq + ( const QDateTime & other ) + + + operator= + operator-eq + ( const QDateTime & other ) + + + operator== + operator-eq-eq + ( const QDateTime & other ) + + + operator> + operator-gt + ( const QDateTime & other ) + + + operator>= + operator-gt-eq + ( const QDateTime & other ) + + + currentDateTime + currentDateTime-2 + ( Qt::TimeSpec spec ) + + + setTime_t + setTime_t-2 + ( uint secsSince1Jan1970UTC, Qt::TimeSpec spec ) + + + + QDateTimeEdit + qdatetimeedit.html + + QDateTimeEdit + QDateTimeEdit + ( QWidget * parent = 0 ) + + + QDateTimeEdit + QDateTimeEdit-2 + ( const QDateTime & datetime, QWidget * parent = 0 ) + + + QDateTimeEdit + QDateTimeEdit-3 + ( const QDate & date, QWidget * parent = 0 ) + + + QDateTimeEdit + QDateTimeEdit-4 + ( const QTime & time, QWidget * parent = 0 ) + + + dateChanged + dateChanged + ( const QDate & date ) + + + dateTimeChanged + dateTimeChanged + ( const QDateTime & datetime ) + + + dateTimeFromText + dateTimeFromText + ( const QString & text ) + + + initStyleOption + initStyleOption + ( QStyleOptionSpinBox * option ) + + + sectionAt + sectionAt + ( int index ) + + + sectionText + sectionText + ( Section section ) + + + setDateRange + setDateRange + ( const QDate & min, const QDate & max ) + + + setSelectedSection + setSelectedSection + ( Section section ) + + + setTimeRange + setTimeRange + ( const QTime & min, const QTime & max ) + + + textFromDateTime + textFromDateTime + ( const QDateTime & dateTime ) + + + timeChanged + timeChanged + ( const QTime & time ) + + + CallMode + CallMode-enum + + + + + QDBusAbstractAdaptor + qdbusabstractadaptor.html + + QDBusAbstractAdaptor + QDBusAbstractAdaptor + ( QObject * obj ) + + + autoRelaySignals + autoRelaySignals + () + + + setAutoRelaySignals + setAutoRelaySignals + ( bool enable ) + + + + QDBusAbstractInterface + qdbusabstractinterface.html + + call + call + ( const QString & method, const QVariant & arg1 = QVariant() + + + call + call-2 + ( QDBus::CallMode mode, const QString & method, const QVariant & arg1 = QVariant() + + + callWithArgumentList + callWithArgumentList + ( QDBus::CallMode mode, const QString & method, const QList<QVariant> & args ) + + + callWithCallback + callWithCallback + ( const QString & method, const QList<QVariant> & args, QObject * receiver, const char * returnMethod, const char * errorMethod ) + + + callWithCallback + callWithCallback-2 + ( const QString & method, const QList<QVariant> & args, QObject * receiver, const char * slot ) + + + connection + connection + () + + + interface + interface + () + + + isValid + isValid + () + + + lastError + lastError + () + + + path + path + () + + + service + service + () + + + + QDBusArgument + qdbusargument.html + + QDBusArgument + QDBusArgument + () + + + QDBusArgument + QDBusArgument-2 + ( const QDBusArgument & other ) + + + atEnd + atEnd + () + + + beginArray + beginArray + ( int id ) + + + beginArray + beginArray-2 + () + + + beginMap + beginMap + ( int kid, int vid ) + + + beginMap + beginMap-2 + () + + + beginMapEntry + beginMapEntry + () + + + beginMapEntry + beginMapEntry-2 + () + + + beginStructure + beginStructure + () + + + beginStructure + beginStructure-2 + () + + + endArray + endArray + () + + + endArray + endArray-2 + () + + + endMap + endMap + () + + + endMap + endMap-2 + () + + + endMapEntry + endMapEntry + () + + + endMapEntry + endMapEntry-2 + () + + + endStructure + endStructure + () + + + endStructure + endStructure-2 + () + + + operator<< + operator-lt-lt + ( uchar arg ) + + + operator<< + operator-lt-lt-2 + ( bool arg ) + + + operator<< + operator-lt-lt-3 + ( short arg ) + + + operator<< + operator-lt-lt-4 + ( ushort arg ) + + + operator<< + operator-lt-lt-5 + ( int arg ) + + + operator<< + operator-lt-lt-6 + ( uint arg ) + + + operator<< + operator-lt-lt-7 + ( qlonglong arg ) + + + operator<< + operator-lt-lt-8 + ( qulonglong arg ) + + + operator<< + operator-lt-lt-9 + ( double arg ) + + + operator<< + operator-lt-lt-10 + ( const QString & arg ) + + + operator<< + operator-lt-lt-13 + ( const QDBusVariant & arg ) + + + operator<< + operator-lt-lt-14 + ( const QStringList & arg ) + + + operator<< + operator-lt-lt-15 + ( const QByteArray & arg ) + + + operator= + operator-eq + ( const QDBusArgument & other ) + + + operator>> + operator-gt-gt + ( uchar & arg ) + + + operator>> + operator-gt-gt-2 + ( bool & arg ) + + + operator>> + operator-gt-gt-3 + ( ushort & arg ) + + + operator>> + operator-gt-gt-4 + ( short & arg ) + + + operator>> + operator-gt-gt-5 + ( int & arg ) + + + operator>> + operator-gt-gt-6 + ( uint & arg ) + + + operator>> + operator-gt-gt-7 + ( qlonglong & arg ) + + + operator>> + operator-gt-gt-8 + ( qulonglong & arg ) + + + operator>> + operator-gt-gt-9 + ( double & arg ) + + + operator>> + operator-gt-gt-10 + ( QString & arg ) + + + operator>> + operator-gt-gt-13 + ( QDBusVariant & arg ) + + + operator>> + operator-gt-gt-14 + ( QStringList & arg ) + + + operator>> + operator-gt-gt-15 + ( QByteArray & arg ) + + + + QDBusConnection + qdbusconnection.html + + BusType + BusType-enum + + + + UnregisterMode + UnregisterMode-enum + + + + QDBusConnection + QDBusConnection + ( const QString & name ) + + + QDBusConnection + QDBusConnection-2 + ( const QDBusConnection & other ) + + + baseService + baseService + () + + + call + call + ( const QDBusMessage & message, QDBus::CallMode mode = QDBus::Block, int timeout = -1 ) + + + callWithCallback + callWithCallback + ( const QDBusMessage & message, QObject * receiver, const char * returnMethod, const char * errorMethod, int timeout = -1 ) + + + callWithCallback + callWithCallback-2 + ( const QDBusMessage & message, QObject * receiver, const char * returnMethod, int timeout = -1 ) + + + connect + connect + ( const QString & service, const QString & path, const QString & interface, const QString & name, QObject * receiver, const char * slot ) + + + connect + connect-2 + ( const QString & service, const QString & path, const QString & interface, const QString & name, const QString & signature, QObject * receiver, const char * slot ) + + + connectToBus + connectToBus + ( BusType type, const QString & name ) + + + connectToBus + connectToBus-2 + ( const QString & address, const QString & name ) + + + disconnect + disconnect + ( const QString & service, const QString & path, const QString & interface, const QString & name, QObject * receiver, const char * slot ) + + + disconnect + disconnect-2 + ( const QString & service, const QString & path, const QString & interface, const QString & name, const QString & signature, QObject * receiver, const char * slot ) + + + disconnectFromBus + disconnectFromBus + ( const QString & name ) + + + interface + interface + () + + + isConnected + isConnected + () + + + lastError + lastError + () + + + objectRegisteredAt + objectRegisteredAt + ( const QString & path ) + + + registerObject + registerObject + ( const QString & path, QObject * object, RegisterOptions options = ExportAdaptors ) + + + registerService + registerService + ( const QString & serviceName ) + + + send + send + ( const QDBusMessage & message ) + + + sender + sender + () + + + sessionBus + sessionBus + () + + + systemBus + systemBus + () + + + unregisterObject + unregisterObject + ( const QString & path, UnregisterMode mode = UnregisterNode ) + + + unregisterService + unregisterService + ( const QString & serviceName ) + + + operator= + operator-eq + ( const QDBusConnection & other ) + + + sessionBus + sessionBus + () + + + systemBus + systemBus + () + + + + QDBusConnectionInterface + qdbusconnectioninterface.html + + RegisterServiceReply + RegisterServiceReply-enum + + + + ServiceQueueOptions + ServiceQueueOptions-enum + + + + ServiceReplacementOptions + ServiceReplacementOptions-enum + + + + callWithCallbackFailed + callWithCallbackFailed + ( const QDBusError & error, const QDBusMessage & call ) + + + isServiceRegistered + isServiceRegistered + ( const QString & serviceName ) + + + RegisterServiceReply + registerService + > QDBusConnectionInterface::registerService( const QString & serviceName, ServiceQueueOptions qoption = DontQueueService, ServiceReplacementOptions roption = DontAllowReplacement ) + + + serviceOwner + serviceOwner + ( const QString & name ) + + + serviceOwnerChanged + serviceOwnerChanged + ( const QString & name, const QString & oldOwner, const QString & newOwner ) + + + servicePid + servicePid + ( const QString & serviceName ) + + + serviceRegistered + serviceRegistered + ( const QString & serviceName ) + + + serviceUid + serviceUid + ( const QString & serviceName ) + + + serviceUnregistered + serviceUnregistered + ( const QString & serviceName ) + + + startService + startService + ( const QString & name ) + + + unregisterService + unregisterService + ( const QString & serviceName ) + + + + QDBusContext + qdbuscontext.html + + QDBusContext + QDBusContext + () + + + calledFromDBus + calledFromDBus + () + + + connection + connection + () + + + isDelayedReply + isDelayedReply + () + + + message + message + () + + + sendErrorReply + sendErrorReply + ( const QString & name, const QString & msg = QString() + + + sendErrorReply + sendErrorReply-2 + ( QDBusError::ErrorType type, const QString & msg = QString() + + + setDelayedReply + setDelayedReply + ( bool enable ) + + + + QDBusError + qdbuserror.html + + ErrorType + ErrorType-enum + + + + errorString + errorString + ( ErrorType error ) + + + isValid + isValid + () + + + message + message + () + + + name + name + () + + + type + type + () + + + + QDBusInterface + qdbusinterface.html + + QDBusInterface + QDBusInterface + ( const QString & service, const QString & path, const QString & interface = QString() + + + + QDBusMessage + qdbusmessage.html + + MessageType + MessageType-enum + + + + QDBusMessage + QDBusMessage + () + + + QDBusMessage + QDBusMessage-2 + ( const QDBusMessage & other ) + + + arguments + arguments + () + + + createError + createError + ( const QString & name, const QString & msg ) + + + createError + createError-2 + ( const QDBusError & error ) + + + createError + createError-3 + ( QDBusError::ErrorType type, const QString & msg ) + + + createErrorReply + createErrorReply + ( const QString name, const QString & msg ) + + + createErrorReply + createErrorReply-2 + ( const QDBusError & error ) + + + createErrorReply + createErrorReply-3 + ( QDBusError::ErrorType type, const QString & msg ) + + + createMethodCall + createMethodCall + ( const QString & service, const QString & path, const QString & interface, const QString & method ) + + + createReply + createReply + ( const QList<QVariant> & arguments = QList<QVariant>() + + + createReply + createReply-2 + ( const QVariant & argument ) + + + createSignal + createSignal + ( const QString & path, const QString & interface, const QString & name ) + + + errorMessage + errorMessage + () + + + errorName + errorName + () + + + interface + interface + () + + + isDelayedReply + isDelayedReply + () + + + isReplyRequired + isReplyRequired + () + + + member + member + () + + + path + path + () + + + service + service + () + + + setArguments + setArguments + ( const QList<QVariant> & arguments ) + + + setDelayedReply + setDelayedReply + ( bool enable ) + + + signature + signature + () + + + type + type + () + + + operator<< + operator-lt-lt + ( const QVariant & arg ) + + + operator= + operator-eq + ( const QDBusMessage & other ) + + + + QDBusObjectPath + qdbusobjectpath.html + + QDBusObjectPath + QDBusObjectPath + () + + + QDBusObjectPath + QDBusObjectPath-2 + ( const char * path ) + + + QDBusObjectPath + QDBusObjectPath-3 + ( const QLatin1String & path ) + + + QDBusObjectPath + QDBusObjectPath-4 + ( const QString & path ) + + + path + path + () + + + setPath + setPath + ( const QString & path ) + + + + QDBusReply + qdbusreply.html + + QDBusReply + QDBusReply + ( const QDBusMessage & reply ) + + + QDBusReply + QDBusReply-2 + ( const QDBusError & error = QDBusError() + + + error + error + () + + + isValid + isValid + () + + + value + value + () + + + operator + operator-Type + Type() + + + operator= + operator-eq + ( const QDBusMessage & message ) + + + operator= + operator-eq-2 + ( const QDBusError & error ) + + + operator= + operator-eq-3 + ( const QDBusReply & other ) + + + + QDBusServer + qdbusserver.html + + QDBusServer + QDBusServer + ( const QString & address, QObject * parent = 0 ) + + + address + address + () + + + isConnected + isConnected + () + + + lastError + lastError + () + + + newConnection + newConnection + ( const QDBusConnection & connection ) + + + + QDBusSignature + qdbussignature.html + + QDBusSignature + QDBusSignature + () + + + QDBusSignature + QDBusSignature-2 + ( const char * signature ) + + + QDBusSignature + QDBusSignature-3 + ( const QLatin1String & signature ) + + + QDBusSignature + QDBusSignature-4 + ( const QString & signature ) + + + setSignature + setSignature + ( const QString & signature ) + + + signature + signature + () + + + + QDBusVariant + qdbusvariant.html + + QDBusVariant + QDBusVariant + () + + + QDBusVariant + QDBusVariant-2 + ( const QVariant & variant ) + + + setVariant + setVariant + ( const QVariant & variant ) + + + variant + variant + () + + + + QDecoration + qdecoration.html + + DecorationRegion + DecorationRegion-enum + + + + DecorationState + DecorationState-enum + + + + QDecoration + QDecoration + () + + + buildSysMenu + buildSysMenu + ( QWidget * widget, QMenu * menu ) + + + menuTriggered + menuTriggered + ( QWidget * widget, QAction * action ) + + + paint + paint + ( QPainter * painter, const QWidget * widget, int decorationRegion = All, DecorationState state = Normal ) + + + region + region + ( const QWidget * widget, const QRect & rectangle, int decorationRegion = All ) + + + region + region-2 + ( const QWidget * widget, int decorationRegion = All ) + + + regionAt + regionAt + ( const QWidget * widget, const QPoint & point ) + + + regionClicked + regionClicked + ( QWidget * widget, int region ) + + + regionDoubleClicked + regionDoubleClicked + ( QWidget * widget, int region ) + + + startMove + startMove + ( QWidget * widget ) + + + startResize + startResize + ( QWidget * widget ) + + + + QDecorationFactory + qdecorationfactory.html + + create + create + ( const QString & key ) + + + keys + keys + () + + + + QDecorationPlugin + qdecorationplugin.html + + QDecorationPlugin + QDecorationPlugin + ( QObject * parent = 0 ) + + + create + create + ( const QString & key ) + + + keys + keys + () + + + + QDesignerActionEditorInterface + qdesigneractioneditorinterface.html + + QDesignerActionEditorInterface + QDesignerActionEditorInterface + ( QWidget * parent, Qt::WindowFlags flags = 0 ) + + + core + core + () + + + manageAction + manageAction + ( QAction * action ) + + + setFormWindow + setFormWindow + ( QDesignerFormWindowInterface * formWindow ) + + + unmanageAction + unmanageAction + ( QAction * action ) + + + + QDesignerContainerExtension + qdesignercontainerextension.html + + addWidget + addWidget + ( QWidget * page ) + + + count + count + () + + + currentIndex + currentIndex + () + + + insertWidget + insertWidget + ( int index, QWidget * page ) + + + remove + remove + ( int index ) + + + setCurrentIndex + setCurrentIndex + ( int index ) + + + widget + widget + ( int index ) + + + + QDesignerCustomWidgetCollectionInterface + qdesignercustomwidgetcollectioninterface.html + + customWidgets + customWidgets + () + + + + QDesignerCustomWidgetInterface + qdesignercustomwidgetinterface.html + + codeTemplate + codeTemplate + () + + + createWidget + createWidget + ( QWidget * parent ) + + + domXml + domXml + () + + + group + group + () + + + icon + icon + () + + + includeFile + includeFile + () + + + initialize + initialize + ( QDesignerFormEditorInterface * formEditor ) + + + isContainer + isContainer + () + + + isInitialized + isInitialized + () + + + name + name + () + + + toolTip + toolTip + () + + + whatsThis + whatsThis + () + + + + QDesignerDynamicPropertySheetExtension + qdesignerdynamicpropertysheetextension.html + + addDynamicProperty + addDynamicProperty + ( const QString & propertyName, const QVariant & value ) + + + canAddDynamicProperty + canAddDynamicProperty + ( const QString & propertyName ) + + + dynamicPropertiesAllowed + dynamicPropertiesAllowed + () + + + isDynamicProperty + isDynamicProperty + ( int index ) + + + removeDynamicProperty + removeDynamicProperty + ( int index ) + + + + QDesignerFormEditorInterface + qdesignerformeditorinterface.html + + QDesignerFormEditorInterface + QDesignerFormEditorInterface + ( QObject * parent = 0 ) + + + actionEditor + actionEditor + () + + + extensionManager + extensionManager + () + + + formWindowManager + formWindowManager + () + + + objectInspector + objectInspector + () + + + propertyEditor + propertyEditor + () + + + setActionEditor + setActionEditor + ( QDesignerActionEditorInterface * actionEditor ) + + + setObjectInspector + setObjectInspector + ( QDesignerObjectInspectorInterface * objectInspector ) + + + setPropertyEditor + setPropertyEditor + ( QDesignerPropertyEditorInterface * propertyEditor ) + + + setWidgetBox + setWidgetBox + ( QDesignerWidgetBoxInterface * widgetBox ) + + + topLevel + topLevel + () + + + widgetBox + widgetBox + () + + + + QDesignerFormWindowCursorInterface + qdesignerformwindowcursorinterface.html + + MoveMode + MoveMode-enum + + + + MoveOperation + MoveOperation-enum + + + + current + current + () + + + formWindow + formWindow + () + + + hasSelection + hasSelection + () + + + isWidgetSelected + isWidgetSelected + ( QWidget * widget ) + + + movePosition + movePosition + ( MoveOperation operation, MoveMode mode = MoveAnchor ) + + + position + position + () + + + resetWidgetProperty + resetWidgetProperty + ( QWidget * widget, const QString & name ) + + + selectedWidget + selectedWidget + ( int index ) + + + selectedWidgetCount + selectedWidgetCount + () + + + setPosition + setPosition + ( int position, MoveMode mode = MoveAnchor ) + + + setProperty + setProperty + ( const QString & name, const QVariant & value ) + + + setWidgetProperty + setWidgetProperty + ( QWidget * widget, const QString & name, const QVariant & value ) + + + widget + widget + ( int index ) + + + widgetCount + widgetCount + () + + + + QDesignerFormWindowInterface + qdesignerformwindowinterface.html + + QDesignerFormWindowInterface + QDesignerFormWindowInterface + ( QWidget * parent = 0, Qt::WindowFlags flags = 0 ) + + + aboutToUnmanageWidget + aboutToUnmanageWidget + ( QWidget * widget ) + + + absoluteDir + absoluteDir + () + + + activated + activated + ( QWidget * widget ) + + + addResourceFile + addResourceFile + ( const QString & path ) + + + author + author + () + + + changed + changed + () + + + clearSelection + clearSelection + ( bool update = true ) + + + comment + comment + () + + + contents + contents + () + + + core + core + () + + + cursor + cursor + () + + + emitSelectionChanged + emitSelectionChanged + () + + + exportMacro + exportMacro + () + + + featureChanged + featureChanged + ( Feature feature ) + + + features + features + () + + + fileName + fileName + () + + + fileNameChanged + fileNameChanged + ( const QString & fileName ) + + + findFormWindow + findFormWindow + ( QWidget * widget ) + + + geometryChanged + geometryChanged + () + + + grid + grid + () + + + hasFeature + hasFeature + ( Feature feature ) + + + includeHints + includeHints + () + + + isDirty + isDirty + () + + + isManaged + isManaged + ( QWidget * widget ) + + + layoutDefault + layoutDefault + ( int * margin, int * spacing ) + + + layoutFunction + layoutFunction + ( QString * margin, QString * spacing ) + + + mainContainer + mainContainer + () + + + mainContainerChanged + mainContainerChanged + ( QWidget * mainContainer ) + + + manageWidget + manageWidget + ( QWidget * widget ) + + + pixmapFunction + pixmapFunction + () + + + removeResourceFile + removeResourceFile + ( const QString & path ) + + + resourceFiles + resourceFiles + () + + + resourceFilesChanged + resourceFilesChanged + () + + + selectWidget + selectWidget + ( QWidget * widget, bool select = true ) + + + selectionChanged + selectionChanged + () + + + setAuthor + setAuthor + ( const QString & author ) + + + setComment + setComment + ( const QString & comment ) + + + setContents + setContents + ( QIODevice * device ) + + + setContents + setContents-2 + ( const QString & contents ) + + + setDirty + setDirty + ( bool dirty ) + + + setExportMacro + setExportMacro + ( const QString & exportMacro ) + + + setFeatures + setFeatures + ( Feature features ) + + + setFileName + setFileName + ( const QString & fileName ) + + + setGrid + setGrid + ( const QPoint & grid ) + + + setIncludeHints + setIncludeHints + ( const QStringList & includeHints ) + + + setLayoutDefault + setLayoutDefault + ( int margin, int spacing ) + + + setLayoutFunction + setLayoutFunction + ( const QString & margin, const QString & spacing ) + + + setMainContainer + setMainContainer + ( QWidget * mainContainer ) + + + setPixmapFunction + setPixmapFunction + ( const QString & pixmapFunction ) + + + unmanageWidget + unmanageWidget + ( QWidget * widget ) + + + widgetManaged + widgetManaged + ( QWidget * widget ) + + + widgetRemoved + widgetRemoved + ( QWidget * widget ) + + + widgetUnmanaged + widgetUnmanaged + ( QWidget * widget ) + + + + QDesignerFormWindowManagerInterface + qdesignerformwindowmanagerinterface.html + + QDesignerFormWindowManagerInterface + QDesignerFormWindowManagerInterface + ( QObject * parent = 0 ) + + + actionAdjustSize + actionAdjustSize + () + + + actionBreakLayout + actionBreakLayout + () + + + actionCopy + actionCopy + () + + + actionCut + actionCut + () + + + actionDelete + actionDelete + () + + + actionGridLayout + actionGridLayout + () + + + actionHorizontalLayout + actionHorizontalLayout + () + + + actionLower + actionLower + () + + + actionPaste + actionPaste + () + + + actionRaise + actionRaise + () + + + actionRedo + actionRedo + () + + + actionSelectAll + actionSelectAll + () + + + actionSplitHorizontal + actionSplitHorizontal + () + + + actionSplitVertical + actionSplitVertical + () + + + actionUndo + actionUndo + () + + + actionVerticalLayout + actionVerticalLayout + () + + + activeFormWindow + activeFormWindow + () + + + activeFormWindowChanged + activeFormWindowChanged + ( QDesignerFormWindowInterface * formWindow ) + + + addFormWindow + addFormWindow + ( QDesignerFormWindowInterface * formWindow ) + + + core + core + () + + + createFormWindow + createFormWindow + ( QWidget * parent = 0, Qt::WindowFlags flags = 0 ) + + + formWindow + formWindow + ( int index ) + + + formWindowAdded + formWindowAdded + ( QDesignerFormWindowInterface * formWindow ) + + + formWindowCount + formWindowCount + () + + + formWindowRemoved + formWindowRemoved + ( QDesignerFormWindowInterface * formWindow ) + + + removeFormWindow + removeFormWindow + ( QDesignerFormWindowInterface * formWindow ) + + + setActiveFormWindow + setActiveFormWindow + ( QDesignerFormWindowInterface * formWindow ) + + + + QDesignerMemberSheetExtension + qdesignermembersheetextension.html + + count + count + () + + + declaredInClass + declaredInClass + ( int index ) + + + indexOf + indexOf + ( const QString & name ) + + + inheritedFromWidget + inheritedFromWidget + ( int index ) + + + isSignal + isSignal + ( int index ) + + + isSlot + isSlot + ( int index ) + + + isVisible + isVisible + ( int index ) + + + memberGroup + memberGroup + ( int index ) + + + memberName + memberName + ( int index ) + + + parameterNames + parameterNames + ( int index ) + + + parameterTypes + parameterTypes + ( int index ) + + + setMemberGroup + setMemberGroup + ( int index, const QString & group ) + + + setVisible + setVisible + ( int index, bool visible ) + + + signature + signature + ( int index ) + + + + QDesignerObjectInspectorInterface + qdesignerobjectinspectorinterface.html + + QDesignerObjectInspectorInterface + QDesignerObjectInspectorInterface + ( QWidget * parent, Qt::WindowFlags flags = 0 ) + + + core + core + () + + + setFormWindow + setFormWindow + ( QDesignerFormWindowInterface * formWindow ) + + + + QDesignerPropertyEditorInterface + qdesignerpropertyeditorinterface.html + + QDesignerPropertyEditorInterface + QDesignerPropertyEditorInterface + ( QWidget * parent, Qt::WindowFlags flags = 0 ) + + + core + core + () + + + currentPropertyName + currentPropertyName + () + + + isReadOnly + isReadOnly + () + + + object + object + () + + + propertyChanged + propertyChanged + ( const QString & name, const QVariant & value ) + + + setObject + setObject + ( QObject * object ) + + + setPropertyValue + setPropertyValue + ( const QString & name, const QVariant & value, bool changed = true ) + + + setReadOnly + setReadOnly + ( bool readOnly ) + + + + QDesignerPropertySheetExtension + qdesignerpropertysheetextension.html + + count + count + () + + + hasReset + hasReset + ( int index ) + + + indexOf + indexOf + ( const QString & name ) + + + isAttribute + isAttribute + ( int index ) + + + isChanged + isChanged + ( int index ) + + + isVisible + isVisible + ( int index ) + + + property + property + ( int index ) + + + propertyGroup + propertyGroup + ( int index ) + + + propertyName + propertyName + ( int index ) + + + reset + reset + ( int index ) + + + setAttribute + setAttribute + ( int index, bool attribute ) + + + setChanged + setChanged + ( int index, bool changed ) + + + setProperty + setProperty + ( int index, const QVariant & value ) + + + setPropertyGroup + setPropertyGroup + ( int index, const QString & group ) + + + setVisible + setVisible + ( int index, bool visible ) + + + + QDesignerScriptExtension + qdesignerscriptextension.html + + data + data + () + + + script + script + () + + + setData + setData + ( const QVariantMap & data ) + + + + QDesignerTaskMenuExtension + qdesignertaskmenuextension.html + + preferredEditAction + preferredEditAction + () + + + taskActions + taskActions + () + + + + QDesignerWidgetBoxInterface + qdesignerwidgetboxinterface.html + + QDesignerWidgetBoxInterface + QDesignerWidgetBoxInterface + ( QWidget * parent = 0, Qt::WindowFlags flags = 0 ) + + + fileName + fileName + () + + + load + load + () + + + save + save + () + + + setFileName + setFileName + ( const QString & fileName ) + + + + QDesktopServices + qdesktopservices.html + + openUrl + openUrl + ( const QUrl & url ) + + + setUrlHandler + setUrlHandler + ( const QString & scheme, QObject * receiver, const char * method ) + + + unsetUrlHandler + unsetUrlHandler + ( const QString & scheme ) + + + + QDesktopWidget + qdesktopwidget.html + + QDesktopWidget + QDesktopWidget + () + + + availableGeometry + availableGeometry + ( int screen = -1 ) + + + availableGeometry + availableGeometry-2 + ( const QWidget * widget ) + + + availableGeometry + availableGeometry-3 + ( const QPoint & p ) + + + isVirtualDesktop + isVirtualDesktop + () + + + numScreens + numScreens + () + + + primaryScreen + primaryScreen + () + + + resized + resized + ( int screen ) + + + screen + screen + ( int screen = -1 ) + + + screenGeometry + screenGeometry + ( int screen = -1 ) + + + screenGeometry + screenGeometry-2 + ( const QWidget * widget ) + + + screenGeometry + screenGeometry-3 + ( const QPoint & p ) + + + screenNumber + screenNumber + ( const QWidget * widget = 0 ) + + + screenNumber + screenNumber-2 + ( const QPoint & point ) + + + workAreaResized + workAreaResized + ( int screen ) + + + QDial + QDial-2 + ( int minValue, int maxValue, int pageStep, int value, QWidget * parent = 0, const char * name = 0 ) + + + QDial + QDial-3 + ( QWidget * parent, const char * name ) + + + dialMoved + dialMoved + ( int value ) + + + dialPressed + dialPressed + () + + + dialReleased + dialReleased + () + + + + QDial + qdial.html + + QDial + QDial + ( QWidget * parent = 0 ) + + + initStyleOption + initStyleOption + ( QStyleOptionSlider * option ) + + + QDial + QDial-2 + ( int minValue, int maxValue, int pageStep, int value, QWidget * parent = 0, const char * name = 0 ) + + + QDial + QDial-3 + ( QWidget * parent, const char * name ) + + + dialMoved + dialMoved + ( int value ) + + + dialPressed + dialPressed + () + + + dialReleased + dialReleased + () + + + extension + extension + () + + + Orientation + orientation + QDialog::orientation() + + + setExtension + setExtension + ( QWidget * extension ) + + + setOrientation + setOrientation + ( Qt::Orientation orientation ) + + + showExtension + showExtension + ( bool showIt ) + + + QDialog + QDialog-2 + ( QWidget * parent, const char * name, bool modal = false, Qt::WindowFlags f = 0 ) + + + + QDialog + qdialog.html + + DialogCode + DialogCode-enum + + + + QDialog + QDialog + ( QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + accept + accept + () + + + accepted + accepted + () + + + done + done + ( int r ) + + + exec + exec + () + + + finished + finished + ( int result ) + + + reject + reject + () + + + rejected + rejected + () + + + result + result + () + + + setResult + setResult + ( int i ) + + + QDialog + QDialog-2 + ( QWidget * parent, const char * name, bool modal = false, Qt::WindowFlags f = 0 ) + + + + QDialogButtonBox + qdialogbuttonbox.html + + ButtonLayout + ButtonLayout-enum + + + + ButtonRole + ButtonRole-enum + + + + Orientation + orientation-prop + + + + QDialogButtonBox + QDialogButtonBox + ( QWidget * parent = 0 ) + + + QDialogButtonBox + QDialogButtonBox-2 + ( Qt::Orientation orientation, QWidget * parent = 0 ) + + + QDialogButtonBox + QDialogButtonBox-3 + ( StandardButtons buttons, Qt::Orientation orientation = Qt::Horizontal, QWidget * parent = 0 ) + + + accepted + accepted + () + + + addButton + addButton + ( QAbstractButton * button, ButtonRole role ) + + + addButton + addButton-2 + ( const QString & text, ButtonRole role ) + + + addButton + addButton-3 + ( StandardButton button ) + + + button + button + ( StandardButton which ) + + + buttonRole + buttonRole + ( QAbstractButton * button ) + + + buttons + buttons + () + + + clear + clear + () + + + clicked + clicked + ( QAbstractButton * button ) + + + helpRequested + helpRequested + () + + + rejected + rejected + () + + + removeButton + removeButton + ( QAbstractButton * button ) + + + standardButton + standardButton + ( QAbstractButton * button ) + + + addResourceSearchPath + addResourceSearchPath + ( const QString & path ) + + + operator= + operator-eq-2 + ( const QString & path ) + + + absFilePath + absFilePath + ( const QString & fileName, bool acceptAbsPath = true ) + + + absPath + absPath + () + + + cleanDirPath + cleanDirPath + ( const QString & name ) + + + convertToAbs + convertToAbs + () + + + currentDirPath + currentDirPath + () + + + entryInfoList + entryInfoList-3 + ( const QString & nameFilter, Filters filters = NoFilter, SortFlags sort = NoSort ) + + + entryList + entryList-3 + ( const QString & nameFilter, Filters filters = NoFilter, SortFlags sort = NoSort ) + + + homeDirPath + homeDirPath + () + + + matchAllDirs + matchAllDirs + () + + + mkdir + mkdir-2 + ( const QString & dirName, bool acceptAbsPath ) + + + nameFilter + nameFilter + () + + + rmdir + rmdir-2 + ( const QString & dirName, bool acceptAbsPath ) + + + rootDirPath + rootDirPath + () + + + setMatchAllDirs + setMatchAllDirs + ( bool on ) + + + setNameFilter + setNameFilter + ( const QString & nameFilter ) + + + + QDir + qdir.html + + FilterSpec + FilterSpec-typedef + + + + SortSpec + SortSpec-typedef + + + + QDir + QDir + ( const QDir & dir ) + + + QDir + QDir-2 + ( const QString & path = QString() + + + QDir + QDir-3 + ( const QString & path, const QString & nameFilter, SortFlags sort = SortFlags( Name | IgnoreCase ) + + + absoluteFilePath + absoluteFilePath + ( const QString & fileName ) + + + absolutePath + absolutePath + () + + + addSearchPath + addSearchPath + ( const QString & prefix, const QString & path ) + + + canonicalPath + canonicalPath + () + + + cd + cd + ( const QString & dirName ) + + + cdUp + cdUp + () + + + cleanPath + cleanPath + ( const QString & path ) + + + count + count + () + + + current + current + () + + + currentPath + currentPath + () + + + dirName + dirName + () + + + drives + drives + () + + + entryInfoList + entryInfoList + ( const QStringList & nameFilters, Filters filters = NoFilter, SortFlags sort = NoSort ) + + + entryInfoList + entryInfoList-2 + ( Filters filters = NoFilter, SortFlags sort = NoSort ) + + + entryList + entryList + ( const QStringList & nameFilters, Filters filters = NoFilter, SortFlags sort = NoSort ) + + + entryList + entryList-2 + ( Filters filters = NoFilter, SortFlags sort = NoSort ) + + + exists + exists + ( const QString & name ) + + + exists + exists-2 + () + + + filePath + filePath + ( const QString & fileName ) + + + filter + filter + () + + + fromNativeSeparators + fromNativeSeparators + ( const QString & pathName ) + + + home + home + () + + + homePath + homePath + () + + + isAbsolute + isAbsolute + () + + + isAbsolutePath + isAbsolutePath + ( const QString & path ) + + + isReadable + isReadable + () + + + isRelative + isRelative + () + + + isRelativePath + isRelativePath + ( const QString & path ) + + + isRoot + isRoot + () + + + makeAbsolute + makeAbsolute + () + + + match + match + ( const QString & filter, const QString & fileName ) + + + match + match-2 + ( const QStringList & filters, const QString & fileName ) + + + mkdir + mkdir + ( const QString & dirName ) + + + mkpath + mkpath + ( const QString & dirPath ) + + + nameFilters + nameFilters + () + + + path + path + () + + + refresh + refresh + () + + + relativeFilePath + relativeFilePath + ( const QString & fileName ) + + + remove + remove + ( const QString & fileName ) + + + rename + rename + ( const QString & oldName, const QString & newName ) + + + rmdir + rmdir + ( const QString & dirName ) + + + rmpath + rmpath + ( const QString & dirPath ) + + + root + root + () + + + rootPath + rootPath + () + + + searchPaths + searchPaths + ( const QString & prefix ) + + + separator + separator + () + + + setCurrent + setCurrent + ( const QString & path ) + + + setFilter + setFilter + ( Filters filters ) + + + setNameFilters + setNameFilters + ( const QStringList & nameFilters ) + + + setPath + setPath + ( const QString & path ) + + + setSearchPaths + setSearchPaths + ( const QString & prefix, const QStringList & searchPaths ) + + + setSorting + setSorting + ( SortFlags sort ) + + + sorting + sorting + () + + + temp + temp + () + + + tempPath + tempPath + () + + + toNativeSeparators + toNativeSeparators + ( const QString & pathName ) + + + operator!= + operator-not-eq + ( const QDir & dir ) + + + operator= + operator-eq + ( const QDir & dir ) + + + operator== + operator-eq-eq + ( const QDir & dir ) + + + operator[] + operator-5b-5d + ( int pos ) + + + absFilePath + absFilePath + ( const QString & fileName, bool acceptAbsPath = true ) + + + absPath + absPath + () + + + cleanDirPath + cleanDirPath + ( const QString & name ) + + + convertToAbs + convertToAbs + () + + + currentDirPath + currentDirPath + () + + + entryInfoList + entryInfoList-3 + ( const QString & nameFilter, Filters filters = NoFilter, SortFlags sort = NoSort ) + + + entryList + entryList-3 + ( const QString & nameFilter, Filters filters = NoFilter, SortFlags sort = NoSort ) + + + homeDirPath + homeDirPath + () + + + matchAllDirs + matchAllDirs + () + + + mkdir + mkdir-2 + ( const QString & dirName, bool acceptAbsPath ) + + + nameFilter + nameFilter + () + + + rmdir + rmdir-2 + ( const QString & dirName, bool acceptAbsPath ) + + + rootDirPath + rootDirPath + () + + + setMatchAllDirs + setMatchAllDirs + ( bool on ) + + + setNameFilter + setNameFilter + ( const QString & nameFilter ) + + + region + region + () + + + reserveRegion + reserveRegion + ( const QRegion & region ) + + + reservedRegion + reservedRegion + () + + + + QDirectPainter + qdirectpainter.html + + SurfaceFlag + SurfaceFlag-enum + + + + QDirectPainter + QDirectPainter + ( QObject * parent = 0, SurfaceFlag flag = NonReserved ) + + + allocatedRegion + allocatedRegion + () + + + endPainting + endPainting + () + + + endPainting + endPainting-2 + ( const QRegion & region ) + + + flush + flush + ( const QRegion & region ) + + + frameBuffer + frameBuffer + () + + + geometry + geometry + () + + + linestep + linestep + () + + + lock + lock + () + + + lower + lower + () + + + raise + raise + () + + + regionChanged + regionChanged + ( const QRegion & newRegion ) + + + requestedRegion + requestedRegion + () + + + screenDepth + screenDepth + () + + + screenHeight + screenHeight + () + + + screenWidth + screenWidth + () + + + setGeometry + setGeometry + ( const QRect & rectangle ) + + + setRegion + setRegion + ( const QRegion & region ) + + + startPainting + startPainting + ( bool lockDisplay = false ) + + + unlock + unlock + () + + + winId + winId + () + + + + QDirIterator + qdiriterator.html + + QDirIterator + QDirIterator + ( const QDir & dir, IteratorFlags flags = NoIteratorFlags ) + + + QDirIterator + QDirIterator-2 + ( const QString & path, IteratorFlags flags = NoIteratorFlags ) + + + QDirIterator + QDirIterator-3 + ( const QString & path, QDir::Filters filters, IteratorFlags flags = NoIteratorFlags ) + + + QDirIterator + QDirIterator-4 + ( const QString & path, const QStringList & nameFilters, QDir::Filters filters = QDir::NoFilter, IteratorFlags flags = NoIteratorFlags ) + + + fileInfo + fileInfo + () + + + fileName + fileName + () + + + filePath + filePath + () + + + hasNext + hasNext + () + + + next + next + () + + + path + path + () + + + + QDirModel + qdirmodel.html + + Roles + Roles-enum + + + + QDirModel + QDirModel + ( const QStringList & nameFilters, QDir::Filters filters, QDir::SortFlags sort, QObject * parent = 0 ) + + + QDirModel + QDirModel-2 + ( QObject * parent = 0 ) + + + columnCount + columnCount + ( const QModelIndex & parent = QModelIndex() + + + data + data + ( const QModelIndex & index, int role = Qt::DisplayRole ) + + + dropMimeData + dropMimeData + ( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ) + + + fileIcon + fileIcon + ( const QModelIndex & index ) + + + fileInfo + fileInfo + ( const QModelIndex & index ) + + + fileName + fileName + ( const QModelIndex & index ) + + + filePath + filePath + ( const QModelIndex & index ) + + + Filters + filter + QDirModel::filter() + + + ItemFlags + flags + QDirModel::flags( const QModelIndex & index ) + + + hasChildren + hasChildren + ( const QModelIndex & parent = QModelIndex() + + + headerData + headerData + ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) + + + iconProvider + iconProvider + () + + + index + index + ( int row, int column, const QModelIndex & parent = QModelIndex() + + + index + index-2 + ( const QString & path, int column = 0 ) + + + isDir + isDir + ( const QModelIndex & index ) + + + mimeData + mimeData + ( const QModelIndexList & indexes ) + + + mimeTypes + mimeTypes + () + + + mkdir + mkdir + ( const QModelIndex & parent, const QString & name ) + + + nameFilters + nameFilters + () + + + parent + parent + ( const QModelIndex & child ) + + + refresh + refresh + ( const QModelIndex & parent = QModelIndex() + + + remove + remove + ( const QModelIndex & index ) + + + rmdir + rmdir + ( const QModelIndex & index ) + + + rowCount + rowCount + ( const QModelIndex & parent = QModelIndex() + + + setData + setData + ( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ) + + + setFilter + setFilter + ( QDir::Filters filters ) + + + setIconProvider + setIconProvider + ( QFileIconProvider * provider ) + + + setNameFilters + setNameFilters + ( const QStringList & filters ) + + + setSorting + setSorting + ( QDir::SortFlags sort ) + + + sort + sort + ( int column, Qt::SortOrder order = Qt::AscendingOrder ) + + + SortFlags + sorting + QDirModel::sorting() + + + DropActions + supportedDropActions + QDirModel::supportedDropActions() + + + + QDockWidget + qdockwidget.html + + DockWidgetAreas + allowedAreas-prop + + + + QDockWidget + QDockWidget + ( const QString & title, QWidget * parent = 0, Qt::WindowFlags flags = 0 ) + + + QDockWidget + QDockWidget-2 + ( QWidget * parent = 0, Qt::WindowFlags flags = 0 ) + + + allowedAreasChanged + allowedAreasChanged + ( Qt::DockWidgetAreas allowedAreas ) + + + dockLocationChanged + dockLocationChanged + ( Qt::DockWidgetArea area ) + + + featuresChanged + featuresChanged + ( QDockWidget::DockWidgetFeatures features ) + + + initStyleOption + initStyleOption + ( QStyleOptionDockWidget * option ) + + + isAreaAllowed + isAreaAllowed + ( Qt::DockWidgetArea area ) + + + setTitleBarWidget + setTitleBarWidget + ( QWidget * widget ) + + + setWidget + setWidget + ( QWidget * widget ) + + + titleBarWidget + titleBarWidget + () + + + toggleViewAction + toggleViewAction + () + + + topLevelChanged + topLevelChanged + ( bool topLevel ) + + + visibilityChanged + visibilityChanged + ( bool visible ) + + + widget + widget + () + + + + QDomAttr + qdomattr.html + + QDomAttr + QDomAttr + () + + + QDomAttr + QDomAttr-2 + ( const QDomAttr & x ) + + + name + name + () + + + NodeType + nodeType + QDomAttr::nodeType() + + + ownerElement + ownerElement + () + + + setValue + setValue + ( const QString & v ) + + + specified + specified + () + + + value + value + () + + + operator= + operator-eq + ( const QDomAttr & x ) + + + + QDomCDATASection + qdomcdatasection.html + + QDomCDATASection + QDomCDATASection + () + + + QDomCDATASection + QDomCDATASection-2 + ( const QDomCDATASection & x ) + + + NodeType + nodeType + QDomCDATASection::nodeType() + + + operator= + operator-eq + ( const QDomCDATASection & x ) + + + + QDomCharacterData + qdomcharacterdata.html + + QDomCharacterData + QDomCharacterData + () + + + QDomCharacterData + QDomCharacterData-2 + ( const QDomCharacterData & x ) + + + appendData + appendData + ( const QString & arg ) + + + data + data + () + + + deleteData + deleteData + ( unsigned long offset, unsigned long count ) + + + insertData + insertData + ( unsigned long offset, const QString & arg ) + + + length + length + () + + + NodeType + nodeType + QDomCharacterData::nodeType() + + + replaceData + replaceData + ( unsigned long offset, unsigned long count, const QString & arg ) + + + setData + setData + ( const QString & v ) + + + substringData + substringData + ( unsigned long offset, unsigned long count ) + + + operator= + operator-eq + ( const QDomCharacterData & x ) + + + + QDomComment + qdomcomment.html + + QDomComment + QDomComment + () + + + QDomComment + QDomComment-2 + ( const QDomComment & x ) + + + NodeType + nodeType + QDomComment::nodeType() + + + operator= + operator-eq + ( const QDomComment & x ) + + + + QDomDocument + qdomdocument.html + + QDomDocument + QDomDocument + () + + + QDomDocument + QDomDocument-2 + ( const QString & name ) + + + QDomDocument + QDomDocument-3 + ( const QDomDocumentType & doctype ) + + + QDomDocument + QDomDocument-4 + ( const QDomDocument & x ) + + + createAttribute + createAttribute + ( const QString & name ) + + + createAttributeNS + createAttributeNS + ( const QString & nsURI, const QString & qName ) + + + createCDATASection + createCDATASection + ( const QString & value ) + + + createComment + createComment + ( const QString & value ) + + + createDocumentFragment + createDocumentFragment + () + + + createElement + createElement + ( const QString & tagName ) + + + createElementNS + createElementNS + ( const QString & nsURI, const QString & qName ) + + + createEntityReference + createEntityReference + ( const QString & name ) + + + createProcessingInstruction + createProcessingInstruction + ( const QString & target, const QString & data ) + + + createTextNode + createTextNode + ( const QString & value ) + + + doctype + doctype + () + + + documentElement + documentElement + () + + + elementById + elementById + ( const QString & elementId ) + + + elementsByTagName + elementsByTagName + ( const QString & tagname ) + + + elementsByTagNameNS + elementsByTagNameNS + ( const QString & nsURI, const QString & localName ) + + + implementation + implementation + () + + + importNode + importNode + ( const QDomNode & importedNode, bool deep ) + + + NodeType + nodeType + QDomDocument::nodeType() + + + setContent + setContent + ( const QByteArray & data, bool namespaceProcessing, QString * errorMsg = 0, int * errorLine = 0, int * errorColumn = 0 ) + + + setContent + setContent-2 + ( const QString & text, bool namespaceProcessing, QString * errorMsg = 0, int * errorLine = 0, int * errorColumn = 0 ) + + + setContent + setContent-3 + ( QIODevice * dev, bool namespaceProcessing, QString * errorMsg = 0, int * errorLine = 0, int * errorColumn = 0 ) + + + setContent + setContent-4 + ( const QString & text, QString * errorMsg = 0, int * errorLine = 0, int * errorColumn = 0 ) + + + setContent + setContent-5 + ( const QByteArray & buffer, QString * errorMsg = 0, int * errorLine = 0, int * errorColumn = 0 ) + + + setContent + setContent-6 + ( QIODevice * dev, QString * errorMsg = 0, int * errorLine = 0, int * errorColumn = 0 ) + + + setContent + setContent-7 + ( QXmlInputSource * source, QXmlReader * reader, QString * errorMsg = 0, int * errorLine = 0, int * errorColumn = 0 ) + + + toByteArray + toByteArray + ( int indent = 1 ) + + + toString + toString + ( int indent = 1 ) + + + operator= + operator-eq + ( const QDomDocument & x ) + + + + QDomDocumentFragment + qdomdocumentfragment.html + + QDomDocumentFragment + QDomDocumentFragment + () + + + QDomDocumentFragment + QDomDocumentFragment-2 + ( const QDomDocumentFragment & x ) + + + NodeType + nodeType + QDomDocumentFragment::nodeType() + + + operator= + operator-eq + ( const QDomDocumentFragment & x ) + + + + QDomDocumentType + qdomdocumenttype.html + + QDomDocumentType + QDomDocumentType + () + + + QDomDocumentType + QDomDocumentType-2 + ( const QDomDocumentType & n ) + + + entities + entities + () + + + internalSubset + internalSubset + () + + + name + name + () + + + NodeType + nodeType + QDomDocumentType::nodeType() + + + notations + notations + () + + + publicId + publicId + () + + + systemId + systemId + () + + + operator= + operator-eq + ( const QDomDocumentType & n ) + + + + QDomElement + qdomelement.html + + QDomElement + QDomElement + () + + + QDomElement + QDomElement-2 + ( const QDomElement & x ) + + + attribute + attributex + ( const QString & name, const QString & defValue = QString() + + + attributeNS + attributeNS + ( const QString nsURI, const QString & localName, const QString & defValue = QString() + + + attributeNode + attributeNode + ( const QString & name ) + + + attributeNodeNS + attributeNodeNS + ( const QString & nsURI, const QString & localName ) + + + attributes + attributes + () + + + elementsByTagName + elementsByTagName + ( const QString & tagname ) + + + elementsByTagNameNS + elementsByTagNameNS + ( const QString & nsURI, const QString & localName ) + + + hasAttribute + hasAttribute + ( const QString & name ) + + + hasAttributeNS + hasAttributeNS + ( const QString & nsURI, const QString & localName ) + + + NodeType + nodeType + QDomElement::nodeType() + + + removeAttribute + removeAttribute + ( const QString & name ) + + + removeAttributeNS + removeAttributeNS + ( const QString & nsURI, const QString & localName ) + + + removeAttributeNode + removeAttributeNode + ( const QDomAttr & oldAttr ) + + + setAttribute + setAttribute + ( const QString & name, const QString & value ) + + + setAttribute + setAttribute-2 + ( const QString & name, int value ) + + + setAttribute + setAttribute-3 + ( const QString & name, uint value ) + + + setAttribute + setAttribute-4 + ( const QString & name, qlonglong value ) + + + setAttribute + setAttribute-5 + ( const QString & name, qulonglong value ) + + + setAttribute + setAttribute-6 + ( const QString & name, float value ) + + + setAttribute + setAttribute-7 + ( const QString & name, double value ) + + + setAttributeNS + setAttributeNS + ( const QString nsURI, const QString & qName, const QString & value ) + + + setAttributeNS + setAttributeNS-2 + ( const QString nsURI, const QString & qName, int value ) + + + setAttributeNS + setAttributeNS-3 + ( const QString nsURI, const QString & qName, uint value ) + + + setAttributeNS + setAttributeNS-4 + ( const QString nsURI, const QString & qName, qlonglong value ) + + + setAttributeNS + setAttributeNS-5 + ( const QString nsURI, const QString & qName, qulonglong value ) + + + setAttributeNS + setAttributeNS-6 + ( const QString nsURI, const QString & qName, double value ) + + + setAttributeNode + setAttributeNode + ( const QDomAttr & newAttr ) + + + setAttributeNodeNS + setAttributeNodeNS + ( const QDomAttr & newAttr ) + + + setTagName + setTagName + ( const QString & name ) + + + tagName + tagName + () + + + text + text + () + + + operator= + operator-eq + ( const QDomElement & x ) + + + + QDomEntity + qdomentity.html + + QDomEntity + QDomEntity + () + + + QDomEntity + QDomEntity-2 + ( const QDomEntity & x ) + + + NodeType + nodeType + QDomEntity::nodeType() + + + notationName + notationName + () + + + publicId + publicId + () + + + systemId + systemId + () + + + operator= + operator-eq + ( const QDomEntity & x ) + + + + QDomEntityReference + qdomentityreference.html + + QDomEntityReference + QDomEntityReference + () + + + QDomEntityReference + QDomEntityReference-2 + ( const QDomEntityReference & x ) + + + NodeType + nodeType + QDomEntityReference::nodeType() + + + operator= + operator-eq + ( const QDomEntityReference & x ) + + + + QDomImplementation + qdomimplementation.html + + InvalidDataPolicy + InvalidDataPolicy-enum + + + + QDomImplementation + QDomImplementation + () + + + QDomImplementation + QDomImplementation-2 + ( const QDomImplementation & x ) + + + createDocument + createDocument + ( const QString & nsURI, const QString & qName, const QDomDocumentType & doctype ) + + + createDocumentType + createDocumentType + ( const QString & qName, const QString & publicId, const QString & systemId ) + + + hasFeature + hasFeature + ( const QString & feature, const QString & version ) + + + invalidDataPolicy + invalidDataPolicy + () + + + isNull + isNull + () + + + setInvalidDataPolicy + setInvalidDataPolicy + ( InvalidDataPolicy policy ) + + + operator!= + operator-not-eq + ( const QDomImplementation & x ) + + + operator= + operator-eq + ( const QDomImplementation & x ) + + + operator== + operator-eq-eq + ( const QDomImplementation & x ) + + + + QDomNamedNodeMap + qdomnamednodemap.html + + QDomNamedNodeMap + QDomNamedNodeMap + () + + + QDomNamedNodeMap + QDomNamedNodeMap-2 + ( const QDomNamedNodeMap & n ) + + + contains + contains + ( const QString & name ) + + + count + count + () + + + isEmpty + isEmpty + () + + + item + item + ( int index ) + + + length + length + () + + + namedItem + namedItem + ( const QString & name ) + + + namedItemNS + namedItemNS + ( const QString & nsURI, const QString & localName ) + + + removeNamedItem + removeNamedItem + ( const QString & name ) + + + removeNamedItemNS + removeNamedItemNS + ( const QString & nsURI, const QString & localName ) + + + setNamedItem + setNamedItem + ( const QDomNode & newNode ) + + + setNamedItemNS + setNamedItemNS + ( const QDomNode & newNode ) + + + size + size + () + + + operator!= + operator-not-eq + ( const QDomNamedNodeMap & n ) + + + operator= + operator-eq + ( const QDomNamedNodeMap & n ) + + + operator== + operator-eq-eq + ( const QDomNamedNodeMap & n ) + + + + QDomNode + qdomnode.html + + EncodingPolicy + EncodingPolicy-enum + + + + NodeType + NodeType-enum + + + + QDomNode + QDomNode + () + + + QDomNode + QDomNode-2 + ( const QDomNode & n ) + + + appendChild + appendChild + ( const QDomNode & newChild ) + + + attributes + attributes + () + + + childNodes + childNodes + () + + + clear + clear + () + + + cloneNode + cloneNode + ( bool deep = true ) + + + columnNumber + columnNumber + () + + + firstChild + firstChild + () + + + firstChildElement + firstChildElement + ( const QString & tagName = QString() + + + hasAttributes + hasAttributes + () + + + hasChildNodes + hasChildNodes + () + + + insertAfter + insertAfter + ( const QDomNode & newChild, const QDomNode & refChild ) + + + insertBefore + insertBefore + ( const QDomNode & newChild, const QDomNode & refChild ) + + + isAttr + isAttr + () + + + isCDATASection + isCDATASection + () + + + isCharacterData + isCharacterData + () + + + isComment + isComment + () + + + isDocument + isDocument + () + + + isDocumentFragment + isDocumentFragment + () + + + isDocumentType + isDocumentType + () + + + isElement + isElement + () + + + isEntity + isEntity + () + + + isEntityReference + isEntityReference + () + + + isNotation + isNotation + () + + + isNull + isNull + () + + + isProcessingInstruction + isProcessingInstruction + () + + + isSupported + isSupported + ( const QString & feature, const QString & version ) + + + isText + isText + () + + + lastChild + lastChild + () + + + lastChildElement + lastChildElement + ( const QString & tagName = QString() + + + lineNumber + lineNumber + () + + + localName + localName + () + + + namedItem + namedItem + ( const QString & name ) + + + namespaceURI + namespaceURI + () + + + nextSibling + nextSibling + () + + + nextSiblingElement + nextSiblingElement + ( const QString & tagName = QString() + + + nodeName + nodeName + () + + + nodeType + nodeType + () + + + nodeValue + nodeValue + () + + + normalize + normalize + () + + + ownerDocument + ownerDocument + () + + + parentNode + parentNode + () + + + prefix + prefix + () + + + previousSibling + previousSibling + () + + + previousSiblingElement + previousSiblingElement + ( const QString & tagName = QString() + + + removeChild + removeChild + ( const QDomNode & oldChild ) + + + replaceChild + replaceChild + ( const QDomNode & newChild, const QDomNode & oldChild ) + + + save + save + ( QTextStream & str, int indent ) + + + save + save-2 + ( QTextStream & str, int indent, EncodingPolicy encodingPolicy ) + + + setNodeValue + setNodeValue + ( const QString & v ) + + + setPrefix + setPrefix + ( const QString & pre ) + + + toAttr + toAttr + () + + + toCDATASection + toCDATASection + () + + + toCharacterData + toCharacterData + () + + + toComment + toComment + () + + + toDocument + toDocument + () + + + toDocumentFragment + toDocumentFragment + () + + + toDocumentType + toDocumentType + () + + + toElement + toElement + () + + + toEntity + toEntity + () + + + toEntityReference + toEntityReference + () + + + toNotation + toNotation + () + + + toProcessingInstruction + toProcessingInstruction + () + + + toText + toText + () + + + operator!= + operator-not-eq + ( const QDomNode & n ) + + + operator= + operator-eq + ( const QDomNode & n ) + + + operator== + operator-eq-eq + ( const QDomNode & n ) + + + + QDomNodeList + qdomnodelist.html + + QDomNodeList + QDomNodeList + () + + + QDomNodeList + QDomNodeList-2 + ( const QDomNodeList & n ) + + + at + at + ( int index ) + + + count + count + () + + + isEmpty + isEmpty + () + + + item + item + ( int index ) + + + length + length + () + + + size + size + () + + + operator!= + operator-not-eq + ( const QDomNodeList & n ) + + + operator= + operator-eq + ( const QDomNodeList & n ) + + + operator== + operator-eq-eq + ( const QDomNodeList & n ) + + + + QDomNotation + qdomnotation.html + + QDomNotation + QDomNotation + () + + + QDomNotation + QDomNotation-2 + ( const QDomNotation & x ) + + + NodeType + nodeType + QDomNotation::nodeType() + + + publicId + publicId + () + + + systemId + systemId + () + + + operator= + operator-eq + ( const QDomNotation & x ) + + + + QDomProcessingInstruction + qdomprocessinginstruction.html + + QDomProcessingInstruction + QDomProcessingInstruction + () + + + QDomProcessingInstruction + QDomProcessingInstruction-2 + ( const QDomProcessingInstruction & x ) + + + data + data + () + + + NodeType + nodeType + QDomProcessingInstruction::nodeType() + + + setData + setData + ( const QString & d ) + + + target + target + () + + + operator= + operator-eq + ( const QDomProcessingInstruction & x ) + + + + QDomText + qdomtext.html + + QDomText + QDomText + () + + + QDomText + QDomText-2 + ( const QDomText & x ) + + + NodeType + nodeType + QDomText::nodeType() + + + splitText + splitText + ( int offset ) + + + operator= + operator-eq + ( const QDomText & x ) + + + + QDoubleSpinBox + qdoublespinbox.html + + QDoubleSpinBox + QDoubleSpinBox + ( QWidget * parent = 0 ) + + + setRange + setRange + ( double minimum, double maximum ) + + + textFromValue + textFromValue + ( double value ) + + + valueChanged + valueChanged + ( double d ) + + + valueChanged + valueChanged-2 + ( const QString & text ) + + + valueFromText + valueFromText + ( const QString & text ) + + + QDoubleValidator + QDoubleValidator-3 + ( QObject * parent, const char * name ) + + + QDoubleValidator + QDoubleValidator-4 + ( double bottom, double top, int decimals, QObject * parent, const char * name ) + + + + QDoubleValidator + qdoublevalidator.html + + Notation + Notation-enum + + + + QDoubleValidator + QDoubleValidator + ( QObject * parent ) + + + QDoubleValidator + QDoubleValidator-2 + ( double bottom, double top, int decimals, QObject * parent ) + + + setRange + setRange + ( double minimum, double maximum, int decimals = 0 ) + + + State + validate + QDoubleValidator::validate( QString & input, int & pos ) + + + QDoubleValidator + QDoubleValidator-3 + ( QObject * parent, const char * name ) + + + QDoubleValidator + QDoubleValidator-4 + ( double bottom, double top, int decimals, QObject * parent, const char * name ) + + + DropAction + start + QDrag::start( Qt::DropActions request = Qt::CopyAction ) + + + + QDrag + qdrag.html + + QDrag + QDrag + ( QWidget * dragSource ) + + + actionChanged + actionChanged + ( Qt::DropAction action ) + + + DropAction + exec + QDrag::exec( Qt::DropActions supportedActions = Qt::MoveAction ) + + + DropAction + exec-2 + QDrag::exec( Qt::DropActions supportedActions, Qt::DropAction defaultDropAction ) + + + hotSpot + hotSpot + () + + + mimeData + mimeData + () + + + pixmap + pixmap + () + + + setDragCursor + setDragCursor + ( const QPixmap & cursor, Qt::DropAction action ) + + + setHotSpot + setHotSpot + ( const QPoint & hotspot ) + + + setMimeData + setMimeData + ( QMimeData * data ) + + + setPixmap + setPixmap + ( const QPixmap & pixmap ) + + + source + source + () + + + target + target + () + + + targetChanged + targetChanged + ( QWidget * newTarget ) + + + + QDragEnterEvent + qdragenterevent.html + + QDragEnterEvent + QDragEnterEvent + ( const QPoint & point, Qt::DropActions actions, const QMimeData * data, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers ) + + + + QDragLeaveEvent + qdragleaveevent.html + + QDragLeaveEvent + QDragLeaveEvent + () + + + accept + accept-2 + ( bool y ) + + + + QDragMoveEvent + qdragmoveevent.html + + QDragMoveEvent + QDragMoveEvent + ( const QPoint & pos, Qt::DropActions actions, const QMimeData * data, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, Type type = DragMove ) + + + accept + accept + ( const QRect & rectangle ) + + + accept + accept-3 + () + + + answerRect + answerRect + () + + + ignore + ignore + ( const QRect & rectangle ) + + + ignore + ignore-2 + () + + + accept + accept-2 + ( bool y ) + + + Action + Action-enum + + + + accept + accept-2 + ( bool accept ) + + + acceptAction + acceptAction + ( bool accept = true ) + + + action + action + () + + + data + data + ( const char * f ) + + + encodedData + encodedData + ( const char * format ) + + + format + format + ( int n = 0 ) + + + provides + provides + ( const char * mimeType ) + + + setPoint + setPoint + ( const QPoint & point ) + + + + QDropEvent + qdropevent.html + + QDropEvent + QDropEvent + ( const QPoint & pos, Qt::DropActions actions, const QMimeData * data, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, Type type = Drop ) + + + acceptProposedAction + acceptProposedAction + () + + + DropAction + dropAction + QDropEvent::dropAction() + + + KeyboardModifiers + keyboardModifiers + QDropEvent::keyboardModifiers() + + + mimeData + mimeData + () + + + MouseButtons + mouseButtons + QDropEvent::mouseButtons() + + + pos + pos + () + + + DropActions + possibleActions + QDropEvent::possibleActions() + + + DropAction + proposedAction + QDropEvent::proposedAction() + + + setDropAction + setDropAction + ( Qt::DropAction action ) + + + source + source + () + + + Action + Action-enum + + + + accept + accept-2 + ( bool accept ) + + + acceptAction + acceptAction + ( bool accept = true ) + + + action + action + () + + + data + data + ( const char * f ) + + + encodedData + encodedData + ( const char * format ) + + + format + format + ( int n = 0 ) + + + provides + provides + ( const char * mimeType ) + + + setPoint + setPoint + ( const QPoint & point ) + + + + QDynamicPropertyChangeEvent + qdynamicpropertychangeevent.html + + QDynamicPropertyChangeEvent + QDynamicPropertyChangeEvent + ( const QByteArray & name ) + + + propertyName + propertyName + () + + + message + message + ( const QString & message ) + + + + QErrorMessage + qerrormessage.html + + QErrorMessage + QErrorMessage + ( QWidget * parent = 0 ) + + + changeEvent + changeEvent + ( QEvent * e ) + + + qtHandler + qtHandler + () + + + showMessage + showMessage + ( const QString & message ) + + + message + message + ( const QString & message ) + + + + QEvent + qevent.html + + Type + Type-enum + + + + QEvent + QEvent + ( Type type ) + + + accept + accept + () + + + ignore + ignore + () + + + spontaneous + spontaneous + () + + + type + type + () + + + + QEventLoop + qeventloop.html + + QEventLoop + QEventLoop + ( QObject * parent = 0 ) + + + exec + exec + ( ProcessEventsFlags flags = AllEvents ) + + + exit + exit + ( int returnCode = 0 ) + + + isRunning + isRunning + () + + + processEvents + processEvents + ( ProcessEventsFlags flags = AllEvents ) + + + processEvents + processEvents-2 + ( ProcessEventsFlags flags, int maxTime ) + + + quit + quit + () + + + wakeUp + wakeUp + () + + + + QExtensionFactory + qextensionfactory.html + + QExtensionFactory + QExtensionFactory + ( QExtensionManager * parent = 0 ) + + + createExtension + createExtension + ( QObject * object, const QString & iid, QObject * parent ) + + + extension + extension + ( QObject * object, const QString & iid ) + + + extensionManager + extensionManager + () + + + + QExtensionManager + qextensionmanager.html + + QExtensionManager + QExtensionManager + ( QObject * parent = 0 ) + + + extension + extension + ( QObject * object, const QString & iid ) + + + registerExtensions + registerExtensions + ( QAbstractExtensionFactory * factory, const QString & iid = QString() + + + unregisterExtensions + unregisterExtensions + ( QAbstractExtensionFactory * factory, const QString & iid = QString() + + + readLink + readLink + () + + + readLink + readLink-2 + ( const QString & fileName ) + + + name + name + () + + + open + open-2 + ( OpenMode flags, FILE * f ) + + + open + open-3 + ( OpenMode flags, int fd ) + + + setName + setName + ( const QString & name ) + + + + QFile + qfile.html + + DecoderFn + DecoderFn-typedef + + + + EncoderFn + EncoderFn-typedef + + + + FileError + FileError-enum + + + + PermissionSpec + PermissionSpec-typedef + + + + QFile + QFile + ( const QString & name ) + + + QFile + QFile-3 + ( QObject * parent ) + + + QFile + QFile-4 + ( const QString & name, QObject * parent ) + + + atEnd + atEnd + () + + + copy + copy + ( const QString & newName ) + + + copy + copy-2 + ( const QString & fileName, const QString & newName ) + + + decodeName + decodeName + ( const QByteArray & localFileName ) + + + decodeName + decodeName-2 + ( const char * localFileName ) + + + encodeName + encodeName + ( const QString & fileName ) + + + error + error + () + + + exists + exists + ( const QString & fileName ) + + + exists + exists-2 + () + + + fileName + fileName + () + + + flush + flush + () + + + handle + handle + () + + + isSequential + isSequential + () + + + link + link + ( const QString & linkName ) + + + link + link-2 + ( const QString & fileName, const QString & linkName ) + + + open + open + ( OpenMode mode ) + + + open + open-4 + ( FILE * fh, OpenMode mode ) + + + open + open-5 + ( int fd, OpenMode mode ) + + + permissions + permissions + () + + + permissions + permissions-2 + ( const QString & fileName ) + + + remove + remove + () + + + remove + remove-2 + ( const QString & fileName ) + + + rename + rename + ( const QString & newName ) + + + rename + rename-2 + ( const QString & oldName, const QString & newName ) + + + resize + resize + ( qint64 sz ) + + + resize + resize-2 + ( const QString & fileName, qint64 sz ) + + + setDecodingFunction + setDecodingFunction + ( DecoderFn function ) + + + setEncodingFunction + setEncodingFunction + ( EncoderFn function ) + + + setFileName + setFileName + ( const QString & name ) + + + setPermissions + setPermissions + ( Permissions permissions ) + + + setPermissions + setPermissions-2 + ( const QString & fileName, Permissions permissions ) + + + size + size + () + + + symLinkTarget + symLinkTarget + ( const QString & fileName ) + + + symLinkTarget + symLinkTarget-2 + () + + + unsetError + unsetError + () + + + name + name + () + + + open + open-2 + ( OpenMode flags, FILE * f ) + + + open + open-3 + ( OpenMode flags, int fd ) + + + setName + setName + ( const QString & name ) + + + getExistingDirectory + getExistingDirectory-2 + ( const QString & dir, QWidget * parent = 0, const char * name = 0, const QString & caption = QString() + + + getOpenFileName + getOpenFileName-2 + ( const QString & dir, const QString & filter = QString() + + + getOpenFileNames + getOpenFileNames-2 + ( const QString & filter, const QString & dir = QString() + + + getSaveFileName + getSaveFileName-2 + ( const QString & dir, const QString & filter = QString() + + + mode + mode + () + + + selectedFile + selectedFile + () + + + setDir + setDir + ( const QString & directory ) + + + setDir + setDir-2 + ( const QDir & directory ) + + + setMode + setMode + ( FileMode m ) + + + + QFileDialog + qfiledialog.html + + AcceptMode + AcceptMode-enum + + + + DialogLabel + DialogLabel-enum + + + + FileMode + FileMode-enum + + + + Mode + Mode-typedef + + + + ViewMode + ViewMode-enum + + + + QFileDialog + QFileDialog + ( QWidget * parent, Qt::WindowFlags flags ) + + + QFileDialog + QFileDialog-2 + ( QWidget * parent = 0, const QString & caption = QString() + + + currentChanged + currentChanged + ( const QString & path ) + + + directory + directory + () + + + directoryEntered + directoryEntered + ( const QString & directory ) + + + filesSelected + filesSelected + ( const QStringList & selected ) + + + filterSelected + filterSelected + ( const QString & filter ) + + + filters + filters + () + + + getExistingDirectory + getExistingDirectory + ( QWidget * parent = 0, const QString & caption = QString() + + + getOpenFileName + getOpenFileName + ( QWidget * parent = 0, const QString & caption = QString() + + + getOpenFileNames + getOpenFileNames + ( QWidget * parent = 0, const QString & caption = QString() + + + getSaveFileName + getSaveFileName + ( QWidget * parent = 0, const QString & caption = QString() + + + history + history + () + + + iconProvider + iconProvider + () + + + itemDelegate + itemDelegate + () + + + labelText + labelText + ( DialogLabel label ) + + + proxyModel + proxyModel + () + + + restoreState + restoreState + ( const QByteArray & state ) + + + saveState + saveState + () + + + selectFile + selectFile + ( const QString & filename ) + + + selectFilter + selectFilter + ( const QString & filter ) + + + selectedFiles + selectedFiles + () + + + selectedFilter + selectedFilter + () + + + setDirectory + setDirectory + ( const QString & directory ) + + + setDirectory + setDirectory-2 + ( const QDir & directory ) + + + setFilter + setFilter + ( const QString & filter ) + + + setFilters + setFilters + ( const QStringList & filters ) + + + setHistory + setHistory + ( const QStringList & paths ) + + + setIconProvider + setIconProvider + ( QFileIconProvider * provider ) + + + setItemDelegate + setItemDelegate + ( QAbstractItemDelegate * delegate ) + + + setLabelText + setLabelText + ( DialogLabel label, const QString & text ) + + + setProxyModel + setProxyModel + ( QAbstractProxyModel * proxyModel ) + + + setSidebarUrls + setSidebarUrls + ( const QList<QUrl> & urls ) + + + sidebarUrls + sidebarUrls + () + + + getExistingDirectory + getExistingDirectory-2 + ( const QString & dir, QWidget * parent = 0, const char * name = 0, const QString & caption = QString() + + + getOpenFileName + getOpenFileName-2 + ( const QString & dir, const QString & filter = QString() + + + getOpenFileNames + getOpenFileNames-2 + ( const QString & filter, const QString & dir = QString() + + + getSaveFileName + getSaveFileName-2 + ( const QString & dir, const QString & filter = QString() + + + mode + mode + () + + + selectedFile + selectedFile + () + + + setDir + setDir + ( const QString & directory ) + + + setDir + setDir-2 + ( const QDir & directory ) + + + setMode + setMode + ( FileMode m ) + + + + QFileIconProvider + qfileiconprovider.html + + IconType + IconType-enum + + + + QFileIconProvider + QFileIconProvider + () + + + icon + icon + ( IconType type ) + + + icon + icon-2 + ( const QFileInfo & info ) + + + type + type + ( const QFileInfo & info ) + + + readLink + readLink + () + + + absFilePath + absFilePath + () + + + baseName + baseName-2 + ( bool complete ) + + + convertToAbs + convertToAbs + () + + + dir + dir-2 + ( bool absPath ) + + + dirPath + dirPath + ( bool absPath = false ) + + + extension + extension + ( bool complete = true ) + + + permission + permission-2 + ( PermissionSpec permissions ) + + + + QFileInfo + qfileinfo.html + + QFileInfo + QFileInfo + () + + + QFileInfo + QFileInfo-2 + ( const QString & file ) + + + QFileInfo + QFileInfo-3 + ( const QFile & file ) + + + QFileInfo + QFileInfo-4 + ( const QDir & dir, const QString & file ) + + + QFileInfo + QFileInfo-5 + ( const QFileInfo & fileinfo ) + + + absoluteDir + absoluteDir + () + + + absoluteFilePath + absoluteFilePath + () + + + absolutePath + absolutePath + () + + + baseName + baseName + () + + + bundleName + bundleName + () + + + caching + caching + () + + + canonicalFilePath + canonicalFilePath + () + + + canonicalPath + canonicalPath + () + + + completeBaseName + completeBaseName + () + + + completeSuffix + completeSuffix + () + + + created + created + () + + + dir + dir + () + + + exists + exists + () + + + fileName + fileName + () + + + filePath + filePath + () + + + group + group + () + + + groupId + groupId + () + + + isAbsolute + isAbsolute + () + + + isBundle + isBundle + () + + + isDir + isDir + () + + + isExecutable + isExecutable + () + + + isFile + isFile + () + + + isHidden + isHidden + () + + + isReadable + isReadable + () + + + isRelative + isRelative + () + + + isRoot + isRoot + () + + + isSymLink + isSymLink + () + + + isWritable + isWritable + () + + + lastModified + lastModified + () + + + lastRead + lastRead + () + + + makeAbsolute + makeAbsolute + () + + + owner + owner + () + + + ownerId + ownerId + () + + + path + path + () + + + permission + permission + ( QFile::Permissions permissions ) + + + Permissions + permissions + QFileInfo::permissions() + + + refresh + refresh + () + + + setCaching + setCaching + ( bool enable ) + + + setFile + setFile + ( const QString & file ) + + + setFile + setFile-2 + ( const QFile & file ) + + + setFile + setFile-3 + ( const QDir & dir, const QString & file ) + + + size + size + () + + + suffix + suffix + () + + + symLinkTarget + symLinkTarget + () + + + operator!= + operator-not-eq + ( const QFileInfo & fileinfo ) + + + operator!= + operator-not-eq-2 + ( const QFileInfo & fileinfo ) + + + operator= + operator-eq + ( const QFileInfo & fileinfo ) + + + operator== + operator-eq-eq + ( const QFileInfo & fileinfo ) + + + operator== + operator-eq-eq-2 + ( const QFileInfo & fileinfo ) + + + absFilePath + absFilePath + () + + + baseName + baseName-2 + ( bool complete ) + + + convertToAbs + convertToAbs + () + + + dir + dir-2 + ( bool absPath ) + + + dirPath + dirPath + ( bool absPath = false ) + + + extension + extension + ( bool complete = true ) + + + permission + permission-2 + ( PermissionSpec permissions ) + + + + QFileOpenEvent + qfileopenevent.html + + file + file + () + + + + QFileSystemWatcher + qfilesystemwatcher.html + + QFileSystemWatcher + QFileSystemWatcher + ( QObject * parent = 0 ) + + + QFileSystemWatcher + QFileSystemWatcher-2 + ( const QStringList & paths, QObject * parent = 0 ) + + + addPath + addPath + ( const QString & path ) + + + addPaths + addPaths + ( const QStringList & paths ) + + + directories + directories + () + + + directoryChanged + directoryChanged + ( const QString & path ) + + + fileChanged + fileChanged + ( const QString & path ) + + + files + files + () + + + removePath + removePath + ( const QString & path ) + + + removePaths + removePaths + ( const QStringList & paths ) + + + + QFlag + qflag.html + + QFlag + QFlag + ( int value ) + + + operator + operator-int + int() + + + + QFlags + qflags.html + + enum_type + enum_type-typedef + + + + QFlags + QFlags + ( const QFlags & other ) + + + QFlags + QFlags-2 + ( Enum flag ) + + + QFlags + QFlags-3 + ( Zero zero = 0 ) + + + QFlags + QFlags-4 + ( QFlag value ) + + + testFlag + testFlag + ( Enum flag ) + + + operator + operator-int + int() + + + operator! + operator-not + () + + + operator& + operator-and + amp;( int mask ) + + + operator& + operator-and-2 + amp;( uint mask ) + + + operator& + operator-and-3 + amp;( Enum mask ) + + + operator& + operator-and-eq + amp;=( int mask ) + + + operator& + operator-and-eq-2 + amp;=( uint mask ) + + + operator= + operator-eq + ( const QFlags & other ) + + + operator^ + operator-5e + ( QFlags other ) + + + operator^ + operator-5e-2 + ( Enum other ) + + + operator^= + operator-5e-eq + ( QFlags other ) + + + operator^= + operator-5e-eq-2 + ( Enum other ) + + + operator| + operator-7c + ( QFlags other ) + + + operator| + operator-7c-2 + ( Enum other ) + + + operator|= + operator-7c-eq + ( QFlags other ) + + + operator|= + operator-7c-eq-2 + ( Enum other ) + + + operator~ + operator-7e + () + + + Reason + Reason-enum + + + + + QFocusEvent + qfocusevent.html + + QFocusEvent + QFocusEvent + ( Type type, Qt::FocusReason reason = Qt::OtherFocusReason ) + + + gotFocus + gotFocus + () + + + lostFocus + lostFocus + () + + + FocusReason + reason + QFocusEvent::reason() + + + Reason + Reason-enum + + + + + QFocusFrame + qfocusframe.html + + QFocusFrame + QFocusFrame + ( QWidget * parent = 0 ) + + + initStyleOption + initStyleOption + ( QStyleOption * option ) + + + setWidget + setWidget + ( QWidget * widget ) + + + widget + widget + () + + + defaultFont + defaultFont + () + + + pointSizeFloat + pointSizeFloat + () + + + setDefaultFont + setDefaultFont + ( const QFont & f ) + + + setPixelSizeFloat + setPixelSizeFloat + ( qreal pixelSize ) + + + setPointSizeFloat + setPointSizeFloat + ( qreal size ) + + + + QFont + qfont.html + + Stretch + Stretch-enum + + + + Style + Style-enum + + + + StyleHint + StyleHint-enum + + + + StyleStrategy + StyleStrategy-enum + + + + Weight + Weight-enum + + + + QFont + QFont + () + + + QFont + QFont-2 + ( const QString & family, int pointSize = -1, int weight = -1, bool italic = false ) + + + QFont + QFont-3 + ( const QFont & font, QPaintDevice * pd ) + + + QFont + QFont-4 + ( const QFont & font ) + + + bold + bold + () + + + defaultFamily + defaultFamily + () + + + exactMatch + exactMatch + () + + + family + family + () + + + fixedPitch + fixedPitch + () + + + freetypeFace + freetypeFace + () + + + fromString + fromString + ( const QString & descrip ) + + + handle + handle + () + + + insertSubstitution + insertSubstitution + ( const QString & familyName, const QString & substituteName ) + + + insertSubstitutions + insertSubstitutions + ( const QString & familyName, const QStringList & substituteNames ) + + + isCopyOf + isCopyOf + ( const QFont & f ) + + + italic + italic + () + + + kerning + kerning + () + + + key + key + () + + + lastResortFamily + lastResortFamily + () + + + lastResortFont + lastResortFont + () + + + macFontID + macFontID + () + + + overline + overline + () + + + pixelSize + pixelSize + () + + + pointSize + pointSize + () + + + pointSizeF + pointSizeF + () + + + rawMode + rawMode + () + + + rawName + rawName + () + + + removeSubstitution + removeSubstitution + ( const QString & familyName ) + + + resolve + resolve + ( const QFont & other ) + + + setBold + setBold + ( bool enable ) + + + setFamily + setFamily + ( const QString & family ) + + + setFixedPitch + setFixedPitch + ( bool enable ) + + + setItalic + setItalic + ( bool enable ) + + + setKerning + setKerning + ( bool enable ) + + + setOverline + setOverline + ( bool enable ) + + + setPixelSize + setPixelSize + ( int pixelSize ) + + + setPointSize + setPointSize + ( int pointSize ) + + + setPointSizeF + setPointSizeF + ( qreal pointSize ) + + + setRawMode + setRawMode + ( bool enable ) + + + setRawName + setRawName + ( const QString & name ) + + + setStretch + setStretch + ( int factor ) + + + setStrikeOut + setStrikeOut + ( bool enable ) + + + setStyle + setStyle + ( Style style ) + + + setStyleHint + setStyleHint + ( StyleHint hint, StyleStrategy strategy = PreferDefault ) + + + setStyleStrategy + setStyleStrategy + ( StyleStrategy s ) + + + setUnderline + setUnderline + ( bool enable ) + + + setWeight + setWeight + ( int weight ) + + + stretch + stretch + () + + + strikeOut + strikeOut + () + + + style + style + () + + + styleHint + styleHint + () + + + styleStrategy + styleStrategy + () + + + substitute + substitute + ( const QString & familyName ) + + + substitutes + substitutes + ( const QString & familyName ) + + + substitutions + substitutions + () + + + toString + toString + () + + + underline + underline + () + + + weight + weight + () + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QFont & f ) + + + operator< + operator-lt + ( const QFont & f ) + + + operator= + operator-eq + ( const QFont & font ) + + + operator== + operator-eq-eq + ( const QFont & f ) + + + defaultFont + defaultFont + () + + + pointSizeFloat + pointSizeFloat + () + + + setDefaultFont + setDefaultFont + ( const QFont & f ) + + + setPixelSizeFloat + setPixelSizeFloat + ( qreal pixelSize ) + + + setPointSizeFloat + setPointSizeFloat + ( qreal size ) + + + + QFontComboBox + qfontcombobox.html + + WritingSystem + writingSystem-prop + + + + QFontComboBox + QFontComboBox + ( QWidget * parent = 0 ) + + + currentFontChanged + currentFontChanged + ( const QFont & font ) + + + + QFontDatabase + qfontdatabase.html + + WritingSystem + WritingSystem-enum + + + + QFontDatabase + QFontDatabase + () + + + addApplicationFont + addApplicationFont + ( const QString & fileName ) + + + addApplicationFontFromData + addApplicationFontFromData + ( const QByteArray & fontData ) + + + applicationFontFamilies + applicationFontFamilies + ( int id ) + + + bold + bold + ( const QString & family, const QString & style ) + + + families + families + ( WritingSystem writingSystem = Any ) + + + font + font + ( const QString & family, const QString & style, int pointSize ) + + + isBitmapScalable + isBitmapScalable + ( const QString & family, const QString & style = QString() + + + isFixedPitch + isFixedPitch + ( const QString & family, const QString & style = QString() + + + isScalable + isScalable + ( const QString & family, const QString & style = QString() + + + isSmoothlyScalable + isSmoothlyScalable + ( const QString & family, const QString & style = QString() + + + italic + italic + ( const QString & family, const QString & style ) + + + pointSizes + pointSizes + ( const QString & family, const QString & style = QString() + + + removeAllApplicationFonts + removeAllApplicationFonts + () + + + removeApplicationFont + removeApplicationFont + ( int id ) + + + smoothSizes + smoothSizes + ( const QString & family, const QString & style ) + + + standardSizes + standardSizes + () + + + styleString + styleString + ( const QFont & font ) + + + styleString + styleString-2 + ( const QFontInfo & fontInfo ) + + + styles + styles + ( const QString & family ) + + + weight + weight + ( const QString & family, const QString & style ) + + + writingSystemName + writingSystemName + ( WritingSystem writingSystem ) + + + writingSystemSample + writingSystemSample + ( WritingSystem writingSystem ) + + + writingSystems + writingSystems + () + + + writingSystems + writingSystems-2 + ( const QString & family ) + + + + QFontDialog + qfontdialog.html + + changeEvent + changeEvent + ( QEvent * e ) + + + getFont + getFont + ( bool * ok, const QFont & initial, QWidget * parent, const QString & caption ) + + + getFont + getFont-2 + ( bool * ok, const QFont & def, QWidget * parent, const char * name ) + + + getFont + getFont-3 + ( bool * ok, QWidget * parent, const char * name ) + + + getFont + getFont-5 + ( bool * ok, const QFont & initial, QWidget * parent = 0 ) + + + getFont + getFont-6 + ( bool * ok, QWidget * parent = 0 ) + + + + QFontEngineInfo + qfontengineinfo.html + + Style + style-prop + + + + WritingSystem + writingSystems-prop + > + + + QFontEngineInfo + QFontEngineInfo + () + + + QFontEngineInfo + QFontEngineInfo-2 + ( const QString & family ) + + + QFontEngineInfo + QFontEngineInfo-3 + ( const QFontEngineInfo & other ) + + + operator= + operator-eq + ( const QFontEngineInfo & other ) + + + + QFontEnginePlugin + qfontengineplugin.html + + QFontEnginePlugin + QFontEnginePlugin + ( const QString & foundry, QObject * parent = 0 ) + + + availableFontEngines + availableFontEngines + () + + + create + create + ( const QFontEngineInfo & info ) + + + keys + keys + () + + + + QFontInfo + qfontinfo.html + + QFontInfo + QFontInfo + ( const QFont & font ) + + + QFontInfo + QFontInfo-2 + ( const QFontInfo & fi ) + + + bold + bold + () + + + exactMatch + exactMatch + () + + + family + family + () + + + fixedPitch + fixedPitch + () + + + italic + italic + () + + + pixelSize + pixelSize + () + + + pointSize + pointSize + () + + + pointSizeF + pointSizeF + () + + + rawMode + rawMode + () + + + Style + style + QFontInfo::style() + + + StyleHint + styleHint + QFontInfo::styleHint() + + + weight + weight + () + + + operator= + operator-eq + ( const QFontInfo & fi ) + + + boundingRect + boundingRect-3 + ( const QString & text, int len ) + + + boundingRect + boundingRect-4 + ( int x, int y, int w, int h, int flags, const QString & text, int len, int tabStops = 0, int * tabArray = 0 ) + + + size + size-2 + ( int flags, const QString & text, int len, int tabStops = 0, int * tabArray = 0 ) + + + + QFontMetrics + qfontmetrics.html + + QFontMetrics + QFontMetrics + ( const QFont & font ) + + + QFontMetrics + QFontMetrics-2 + ( const QFont & font, QPaintDevice * paintdevice ) + + + QFontMetrics + QFontMetrics-3 + ( const QFontMetrics & fm ) + + + ascent + ascent + () + + + averageCharWidth + averageCharWidth + () + + + boundingRect + boundingRect + ( QChar ch ) + + + boundingRect + boundingRect-2 + ( const QString & text ) + + + boundingRect + boundingRect-5 + ( int x, int y, int width, int height, int flags, const QString & text, int tabStops = 0, int * tabArray = 0 ) + + + boundingRect + boundingRect-6 + ( const QRect & rect, int flags, const QString & text, int tabStops = 0, int * tabArray = 0 ) + + + charWidth + charWidth + ( const QString & text, int pos ) + + + descent + descent + () + + + elidedText + elidedText + ( const QString & text, Qt::TextElideMode mode, int width, int flags = 0 ) + + + height + height + () + + + inFont + inFont + ( QChar ch ) + + + leading + leading + () + + + leftBearing + leftBearing + ( QChar ch ) + + + lineSpacing + lineSpacing + () + + + lineWidth + lineWidth + () + + + maxWidth + maxWidth + () + + + minLeftBearing + minLeftBearing + () + + + minRightBearing + minRightBearing + () + + + overlinePos + overlinePos + () + + + rightBearing + rightBearing + ( QChar ch ) + + + size + size + ( int flags, const QString & text, int tabStops = 0, int * tabArray = 0 ) + + + strikeOutPos + strikeOutPos + () + + + tightBoundingRect + tightBoundingRect + ( const QString & text ) + + + underlinePos + underlinePos + () + + + width + width + ( const QString & text, int len = -1 ) + + + width + width-2 + ( QChar ch ) + + + xHeight + xHeight + () + + + operator!= + operator-not-eq + ( const QFontMetrics & other ) + + + operator!= + operator-not-eq-2 + ( const QFontMetrics & other ) + + + operator= + operator-eq + ( const QFontMetrics & fm ) + + + operator== + operator-eq-eq + ( const QFontMetrics & other ) + + + operator== + operator-eq-eq-2 + ( const QFontMetrics & other ) + + + boundingRect + boundingRect-3 + ( const QString & text, int len ) + + + boundingRect + boundingRect-4 + ( int x, int y, int w, int h, int flags, const QString & text, int len, int tabStops = 0, int * tabArray = 0 ) + + + size + size-2 + ( int flags, const QString & text, int len, int tabStops = 0, int * tabArray = 0 ) + + + + QFontMetricsF + qfontmetricsf.html + + QFontMetricsF + QFontMetricsF + ( const QFont & font ) + + + QFontMetricsF + QFontMetricsF-2 + ( const QFont & font, QPaintDevice * paintdevice ) + + + QFontMetricsF + QFontMetricsF-3 + ( const QFontMetrics & fontMetrics ) + + + QFontMetricsF + QFontMetricsF-4 + ( const QFontMetricsF & fm ) + + + ascent + ascent + () + + + averageCharWidth + averageCharWidth + () + + + boundingRect + boundingRect + ( const QString & text ) + + + boundingRect + boundingRect-2 + ( QChar ch ) + + + boundingRect + boundingRect-3 + ( const QRectF & rect, int flags, const QString & text, int tabStops = 0, int * tabArray = 0 ) + + + descent + descent + () + + + elidedText + elidedText + ( const QString & text, Qt::TextElideMode mode, qreal width, int flags = 0 ) + + + height + height + () + + + inFont + inFont + ( QChar ch ) + + + leading + leading + () + + + leftBearing + leftBearing + ( QChar ch ) + + + lineSpacing + lineSpacing + () + + + lineWidth + lineWidth + () + + + maxWidth + maxWidth + () + + + minLeftBearing + minLeftBearing + () + + + minRightBearing + minRightBearing + () + + + overlinePos + overlinePos + () + + + rightBearing + rightBearing + ( QChar ch ) + + + size + size + ( int flags, const QString & text, int tabStops = 0, int * tabArray = 0 ) + + + strikeOutPos + strikeOutPos + () + + + tightBoundingRect + tightBoundingRect + ( const QString & text ) + + + underlinePos + underlinePos + () + + + width + width + ( const QString & text ) + + + width + width-2 + ( QChar ch ) + + + xHeight + xHeight + () + + + operator!= + operator-not-eq + ( const QFontMetricsF & other ) + + + operator!= + operator-not-eq-2 + ( const QFontMetricsF & other ) + + + operator= + operator-eq + ( const QFontMetricsF & fm ) + + + operator= + operator-eq-2 + ( const QFontMetrics & other ) + + + operator== + operator-eq-eq + ( const QFontMetricsF & other ) + + + operator== + operator-eq-eq-2 + ( const QFontMetricsF & other ) + + + + QFormBuilder + qformbuilder.html + + QFormBuilder + QFormBuilder + () + + + addPluginPath + addPluginPath + ( const QString & pluginPath ) + + + clearPluginPaths + clearPluginPaths + () + + + customWidgets + customWidgets + () + + + pluginPaths + pluginPaths + () + + + setPluginPath + setPluginPath + ( const QStringList & pluginPaths ) + + + QFrame + QFrame-2 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + + QFrame + qframe.html + + Shadow + Shadow-enum + + + + Shape + Shape-enum + + + + StyleMask + StyleMask-enum + + + + QFrame + QFrame + ( QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + frameStyle + frameStyle + () + + + setFrameStyle + setFrameStyle + ( int style ) + + + QFrame + QFrame-2 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + + QFSFileEngine + qfsfileengine.html + + QFSFileEngine + QFSFileEngine + () + + + QFSFileEngine + QFSFileEngine-2 + ( const QString & file ) + + + currentPath + currentPath + ( const QString & fileName = QString() + + + drives + drives + () + + + homePath + homePath + () + + + open + open + ( QIODevice::OpenMode openMode, FILE * fh ) + + + open + open-3 + ( QIODevice::OpenMode openMode, int fd ) + + + rootPath + rootPath + () + + + setCurrentPath + setCurrentPath + ( const QString & path ) + + + tempPath + tempPath + () + + + QFtp + QFtp-2 + ( QObject * parent, const char * name ) + + + readBlock + readBlock + ( char * data, quint64 maxlen ) + + + + QFtp + qftp.html + + Command + Command-enum + + + + Error + Error-enum + + + + State + State-enum + + + + TransferMode + TransferMode-enum + + + + TransferType + TransferType-enum + + + + QFtp + QFtp + ( QObject * parent = 0 ) + + + abort + abort + () + + + bytesAvailable + bytesAvailable + () + + + cd + cd + ( const QString & dir ) + + + clearPendingCommands + clearPendingCommands + () + + + close + close + () + + + commandFinished + commandFinished + ( int id, bool error ) + + + commandStarted + commandStarted + ( int id ) + + + connectToHost + connectToHost + ( const QString & host, quint16 port = 21 ) + + + currentCommand + currentCommand + () + + + currentDevice + currentDevice + () + + + currentId + currentId + () + + + dataTransferProgress + dataTransferProgress + ( qint64 done, qint64 total ) + + + done + done + ( bool error ) + + + error + error + () + + + errorString + errorString + () + + + get + get + ( const QString & file, QIODevice * dev = 0, TransferType type = Binary ) + + + hasPendingCommands + hasPendingCommands + () + + + list + list + ( const QString & dir = QString() + + + listInfo + listInfo + ( const QUrlInfo & i ) + + + login + login + ( const QString & user = QString() + + + mkdir + mkdir + ( const QString & dir ) + + + put + put + ( QIODevice * dev, const QString & file, TransferType type = Binary ) + + + put + put-2 + ( const QByteArray & data, const QString & file, TransferType type = Binary ) + + + rawCommand + rawCommand + ( const QString & command ) + + + rawCommandReply + rawCommandReply + ( int replyCode, const QString & detail ) + + + read + read + ( char * data, qint64 maxlen ) + + + readAll + readAll + () + + + readyRead + readyRead + () + + + remove + remove + ( const QString & file ) + + + rename + rename + ( const QString & oldname, const QString & newname ) + + + rmdir + rmdir + ( const QString & dir ) + + + setProxy + setProxy + ( const QString & host, quint16 port ) + + + setTransferMode + setTransferMode + ( TransferMode mode ) + + + state + state + () + + + stateChanged + stateChanged + ( int state ) + + + QFtp + QFtp-2 + ( QObject * parent, const char * name ) + + + readBlock + readBlock + ( char * data, quint64 maxlen ) + + + + QGenericArgument + qgenericargument.html + + QGenericArgument + QGenericArgument + ( const char * name = 0, const void * data = 0 ) + + + data + data + () + + + name + name + () + + + + QGenericReturnArgument + qgenericreturnargument.html + + QGenericReturnArgument + QGenericReturnArgument + ( const char * name = 0, void * data = 0 ) + + + + QGLColormap + qglcolormap.html + + QGLColormap + QGLColormap + () + + + QGLColormap + QGLColormap-2 + ( const QGLColormap & map ) + + + entryColor + entryColor + ( int idx ) + + + entryRgb + entryRgb + ( int idx ) + + + find + find + ( QRgb color ) + + + findNearest + findNearest + ( QRgb color ) + + + isEmpty + isEmpty + () + + + setEntries + setEntries + ( int count, const QRgb * colors, int base = 0 ) + + + setEntry + setEntry + ( int idx, QRgb color ) + + + setEntry + setEntry-2 + ( int idx, const QColor & color ) + + + size + size + () + + + operator= + operator-eq + ( const QGLColormap & map ) + + + QGLContext + QGLContext-2 + ( const QGLFormat & format, QPaintDevice * device ) + + + generateFontDisplayLists + generateFontDisplayLists + ( const QFont & font, int listBase ) + + + + QGLContext + qglcontext.html + + QGLContext + QGLContext + ( const QGLFormat & format ) + + + bindTexture + bindTexture + ( const QImage & image, GLenum target = GL_TEXTURE_2D, GLint format = GL_RGBA ) + + + bindTexture + bindTexture-2 + ( const QString & fileName ) + + + bindTexture + bindTexture-3 + ( const QPixmap & pixmap, GLenum target = GL_TEXTURE_2D, GLint format = GL_RGBA ) + + + chooseContext + chooseContext + ( const QGLContext * shareContext = 0 ) + + + chooseMacVisual + chooseMacVisual + ( GDHandle handle ) + + + choosePixelFormat + choosePixelFormat + ( void * dummyPfd, HDC pdc ) + + + chooseVisual + chooseVisual + () + + + create + create + ( const QGLContext * shareContext = 0 ) + + + currentContext + currentContext + () + + + deleteTexture + deleteTexture + ( GLuint id ) + + + device + device + () + + + deviceIsPixmap + deviceIsPixmap + () + + + doneCurrent + doneCurrent + () + + + format + format + () + + + getProcAddress + getProcAddress + ( const QString & proc ) + + + initialized + initialized + () + + + isSharing + isSharing + () + + + isValid + isValid + () + + + makeCurrent + makeCurrent + () + + + overlayTransparentColor + overlayTransparentColor + () + + + requestedFormat + requestedFormat + () + + + reset + reset + () + + + setFormat + setFormat + ( const QGLFormat & format ) + + + setInitialized + setInitialized + ( bool on ) + + + setTextureCacheLimit + setTextureCacheLimit + ( int size ) + + + setWindowCreated + setWindowCreated + ( bool on ) + + + swapBuffers + swapBuffers + () + + + textureCacheLimit + textureCacheLimit + () + + + windowCreated + windowCreated + () + + + + QGLFormat + qglformat.html + + QGLFormat + QGLFormat + () + + + QGLFormat + QGLFormat-2 + ( QGL::FormatOptions options, int plane = 0 ) + + + QGLFormat + QGLFormat-3 + ( const QGLFormat & other ) + + + accum + accum + () + + + accumBufferSize + accumBufferSize + () + + + alpha + alpha + () + + + alphaBufferSize + alphaBufferSize + () + + + blueBufferSize + blueBufferSize + () + + + defaultFormat + defaultFormat + () + + + defaultOverlayFormat + defaultOverlayFormat + () + + + depth + depth + () + + + depthBufferSize + depthBufferSize + () + + + directRendering + directRendering + () + + + doubleBuffer + doubleBuffer + () + + + greenBufferSize + greenBufferSize + () + + + hasOpenGL + hasOpenGL + () + + + hasOpenGLOverlays + hasOpenGLOverlays + () + + + hasOverlay + hasOverlay + () + + + openGLVersionFlags + openGLVersionFlags + () + + + plane + plane + () + + + redBufferSize + redBufferSize + () + + + rgba + rgba + () + + + sampleBuffers + sampleBuffers + () + + + samples + samples + () + + + setAccum + setAccum + ( bool enable ) + + + setAccumBufferSize + setAccumBufferSize + ( int size ) + + + setAlpha + setAlpha + ( bool enable ) + + + setAlphaBufferSize + setAlphaBufferSize + ( int size ) + + + setBlueBufferSize + setBlueBufferSize + ( int size ) + + + setDefaultFormat + setDefaultFormat + ( const QGLFormat & f ) + + + setDefaultOverlayFormat + setDefaultOverlayFormat + ( const QGLFormat & f ) + + + setDepth + setDepth + ( bool enable ) + + + setDepthBufferSize + setDepthBufferSize + ( int size ) + + + setDirectRendering + setDirectRendering + ( bool enable ) + + + setDoubleBuffer + setDoubleBuffer + ( bool enable ) + + + setGreenBufferSize + setGreenBufferSize + ( int size ) + + + setOption + setOption + ( QGL::FormatOptions opt ) + + + setOverlay + setOverlay + ( bool enable ) + + + setPlane + setPlane + ( int plane ) + + + setRedBufferSize + setRedBufferSize + ( int size ) + + + setRgba + setRgba + ( bool enable ) + + + setSampleBuffers + setSampleBuffers + ( bool enable ) + + + setSamples + setSamples + ( int numSamples ) + + + setStencil + setStencil + ( bool enable ) + + + setStencilBufferSize + setStencilBufferSize + ( int size ) + + + setStereo + setStereo + ( bool enable ) + + + setSwapInterval + setSwapInterval + ( int interval ) + + + stencil + stencil + () + + + stencilBufferSize + stencilBufferSize + () + + + stereo + stereo + () + + + swapInterval + swapInterval + () + + + testOption + testOption + ( QGL::FormatOptions opt ) + + + operator= + operator-eq + ( const QGLFormat & other ) + + + + QGLFramebufferObject + qglframebufferobject.html + + Attachment + Attachment-enum + + + + QGLFramebufferObject + QGLFramebufferObject + ( const QSize & size, GLenum target = GL_TEXTURE_2D ) + + + QGLFramebufferObject + QGLFramebufferObject-2 + ( int width, int height, GLenum target = GL_TEXTURE_2D ) + + + QGLFramebufferObject + QGLFramebufferObject-3 + ( int width, int height, Attachment attachment, GLenum target = GL_TEXTURE_2D, GLenum internal_format = GL_RGBA8 ) + + + QGLFramebufferObject + QGLFramebufferObject-4 + ( const QSize & size, Attachment attachment, GLenum target = GL_TEXTURE_2D, GLenum internal_format = GL_RGBA8 ) + + + attachment + attachment + () + + + bind + bind + () + + + handle + handle + () + + + hasOpenGLFramebufferObjects + hasOpenGLFramebufferObjects + () + + + isValid + isValid + () + + + release + release + () + + + size + size + () + + + texture + texture + () + + + toImage + toImage + () + + + + QGLPixelBuffer + qglpixelbuffer.html + + QGLPixelBuffer + QGLPixelBuffer + ( const QSize & size, const QGLFormat & format = QGLFormat::defaultFormat() + + + QGLPixelBuffer + QGLPixelBuffer-2 + ( int width, int height, const QGLFormat & format = QGLFormat::defaultFormat() + + + bindTexture + bindTexture + ( const QImage & image, GLenum target = GL_TEXTURE_2D ) + + + bindTexture + bindTexture-2 + ( const QPixmap & pixmap, GLenum target = GL_TEXTURE_2D ) + + + bindTexture + bindTexture-3 + ( const QString & fileName ) + + + bindToDynamicTexture + bindToDynamicTexture + ( GLuint texture_id ) + + + deleteTexture + deleteTexture + ( GLuint texture_id ) + + + doneCurrent + doneCurrent + () + + + format + format + () + + + generateDynamicTexture + generateDynamicTexture + () + + + HANDLE + handle + QGLPixelBuffer::handle() + + + hasOpenGLPbuffers + hasOpenGLPbuffers + () + + + isValid + isValid + () + + + makeCurrent + makeCurrent + () + + + releaseFromDynamicTexture + releaseFromDynamicTexture + () + + + size + size + () + + + toImage + toImage + () + + + updateDynamicTexture + updateDynamicTexture + ( GLuint texture_id ) + + + fontDisplayListBase + fontDisplayListBase + ( const QFont & font, int listBase = 2000 ) + + + setFormat + setFormat + ( const QGLFormat & format ) + + + QGLWidget + QGLWidget-4 + ( QWidget * parent, const char * name, const QGLWidget * shareWidget = 0, Qt::WindowFlags f = 0 ) + + + QGLWidget + QGLWidget-5 + ( const QGLFormat & format, QWidget * parent, const char * name, const QGLWidget * shareWidget = 0, Qt::WindowFlags f = 0 ) + + + QGLWidget + QGLWidget-6 + ( QGLContext * context, QWidget * parent, const char * name, const QGLWidget * shareWidget = 0, Qt::WindowFlags f = 0 ) + + + + QGLWidget + qglwidget.html + + QGLWidget + QGLWidget + ( QWidget * parent = 0, const QGLWidget * shareWidget = 0, Qt::WindowFlags f = 0 ) + + + QGLWidget + QGLWidget-2 + ( QGLContext * context, QWidget * parent = 0, const QGLWidget * shareWidget = 0, Qt::WindowFlags f = 0 ) + + + QGLWidget + QGLWidget-3 + ( const QGLFormat & format, QWidget * parent = 0, const QGLWidget * shareWidget = 0, Qt::WindowFlags f = 0 ) + + + autoBufferSwap + autoBufferSwap + () + + + bindTexture + bindTexture + ( const QImage & image, GLenum target = GL_TEXTURE_2D, GLint format = GL_RGBA ) + + + bindTexture + bindTexture-2 + ( const QPixmap & pixmap, GLenum target = GL_TEXTURE_2D, GLint format = GL_RGBA ) + + + bindTexture + bindTexture-3 + ( const QString & fileName ) + + + colormap + colormap + () + + + context + context + () + + + convertToGLFormat + convertToGLFormat + ( const QImage & img ) + + + deleteTexture + deleteTexture + ( GLuint id ) + + + doneCurrent + doneCurrent + () + + + doubleBuffer + doubleBuffer + () + + + format + format + () + + + glDraw + glDraw + () + + + glInit + glInit + () + + + grabFrameBuffer + grabFrameBuffer + ( bool withAlpha = false ) + + + initializeGL + initializeGL + () + + + initializeOverlayGL + initializeOverlayGL + () + + + isSharing + isSharing + () + + + isValid + isValid + () + + + makeCurrent + makeCurrent + () + + + makeOverlayCurrent + makeOverlayCurrent + () + + + overlayContext + overlayContext + () + + + paintEvent + paintEvent + ( QPaintEvent * event ) + + + paintGL + paintGL + () + + + paintOverlayGL + paintOverlayGL + () + + + qglClearColor + qglClearColor + ( const QColor & c ) + + + qglColor + qglColor + ( const QColor & c ) + + + renderPixmap + renderPixmap + ( int w = 0, int h = 0, bool useContext = false ) + + + renderText + renderText + ( int x, int y, const QString & str, const QFont & font = QFont() + + + renderText + renderText-2 + ( double x, double y, double z, const QString & str, const QFont & font = QFont() + + + resizeEvent + resizeEvent + ( QResizeEvent * event ) + + + resizeGL + resizeGL + ( int width, int height ) + + + resizeOverlayGL + resizeOverlayGL + ( int width, int height ) + + + setAutoBufferSwap + setAutoBufferSwap + ( bool on ) + + + setColormap + setColormap + ( const QGLColormap & cmap ) + + + setMouseTracking + setMouseTracking + ( bool enable ) + + + swapBuffers + swapBuffers + () + + + updateGL + updateGL + () + + + updateOverlayGL + updateOverlayGL + () + + + QGLWidget + QGLWidget-4 + ( QWidget * parent, const char * name, const QGLWidget * shareWidget = 0, Qt::WindowFlags f = 0 ) + + + QGLWidget + QGLWidget-5 + ( const QGLFormat & format, QWidget * parent, const char * name, const QGLWidget * shareWidget = 0, Qt::WindowFlags f = 0 ) + + + QGLWidget + QGLWidget-6 + ( QGLContext * context, QWidget * parent, const char * name, const QGLWidget * shareWidget = 0, Qt::WindowFlags f = 0 ) + + + + QGLWindowSurface + qglwindowsurface.html + + QGLWindowSurface + QGLWindowSurface + ( QWidget * window ) + + + QGLWindowSurface + QGLWindowSurface-2 + () + + + context + context + () + + + setContext + setContext + ( QGLContext * context ) + + + + QGradient + qgradient.html + + Spread + Spread-enum + + + + Type + Type-enum + + + + setColorAt + setColorAt + ( qreal position, const QColor & color ) + + + setSpread + setSpread + ( Spread method ) + + + setStops + setStops + ( const QGradientStops & stopPoints ) + + + spread + spread + () + + + stops + stops + () + + + type + type + () + + + operator!= + operator-not-eq + ( const QGradient & gradient ) + + + operator== + operator-eq-eq + ( const QGradient & gradient ) + + + + QGraphicsEllipseItem + qgraphicsellipseitem.html + + QGraphicsEllipseItem + QGraphicsEllipseItem + ( QGraphicsItem * parent = 0 ) + + + QGraphicsEllipseItem + QGraphicsEllipseItem-2 + ( const QRectF & rect, QGraphicsItem * parent = 0 ) + + + QGraphicsEllipseItem + QGraphicsEllipseItem-3 + ( qreal x, qreal y, qreal width, qreal height, QGraphicsItem * parent = 0 ) + + + rect + rect + () + + + setRect + setRect + ( const QRectF & rect ) + + + setRect + setRect-2 + ( qreal x, qreal y, qreal width, qreal height ) + + + setSpanAngle + setSpanAngle + ( int angle ) + + + setStartAngle + setStartAngle + ( int angle ) + + + spanAngle + spanAngle + () + + + startAngle + startAngle + () + + + matrix + matrix + () + + + resetMatrix + resetMatrix + () + + + sceneMatrix + sceneMatrix + () + + + setMatrix + setMatrix + ( const QMatrix & matrix, bool combine = false ) + + + + QGraphicsItem + qgraphicsitem.html + + GraphicsItemChange + GraphicsItemChange-enum + + + + QGraphicsItem + QGraphicsItem + ( QGraphicsItem * parent = 0 ) + + + acceptDrops + acceptDrops + () + + + MouseButtons + acceptedMouseButtons + QGraphicsItem::acceptedMouseButtons() + + + acceptsHoverEvents + acceptsHoverEvents + () + + + advance + advance + ( int phase ) + + + boundingRect + boundingRect + () + + + children + children + () + + + childrenBoundingRect + childrenBoundingRect + () + + + clearFocus + clearFocus + () + + + collidesWithItem + collidesWithItem + ( const QGraphicsItem * other, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape ) + + + collidesWithPath + collidesWithPath + ( const QPainterPath & path, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape ) + + + collidingItems + collidingItems + ( Qt::ItemSelectionMode mode = Qt::IntersectsItemShape ) + + + contains + contains + ( const QPointF & point ) + + + contextMenuEvent + contextMenuEvent + ( QGraphicsSceneContextMenuEvent * event ) + + + cursor + cursor + () + + + data + data + ( int key ) + + + deviceTransform + deviceTransform + ( const QTransform & viewportTransform ) + + + dragEnterEvent + dragEnterEvent + ( QGraphicsSceneDragDropEvent * event ) + + + dragLeaveEvent + dragLeaveEvent + ( QGraphicsSceneDragDropEvent * event ) + + + dragMoveEvent + dragMoveEvent + ( QGraphicsSceneDragDropEvent * event ) + + + dropEvent + dropEvent + ( QGraphicsSceneDragDropEvent * event ) + + + ensureVisible + ensureVisible + ( const QRectF & rect = QRectF() + + + ensureVisible + ensureVisible-2 + ( qreal x, qreal y, qreal w, qreal h, int xmargin = 50, int ymargin = 50 ) + + + flags + flags + () + + + focusInEvent + focusInEvent + ( QFocusEvent * event ) + + + focusOutEvent + focusOutEvent + ( QFocusEvent * event ) + + + group + group + () + + + handlesChildEvents + handlesChildEvents + () + + + hasCursor + hasCursor + () + + + hasFocus + hasFocus + () + + + hide + hide + () + + + hoverEnterEvent + hoverEnterEvent + ( QGraphicsSceneHoverEvent * event ) + + + hoverLeaveEvent + hoverLeaveEvent + ( QGraphicsSceneHoverEvent * event ) + + + hoverMoveEvent + hoverMoveEvent + ( QGraphicsSceneHoverEvent * event ) + + + inputMethodEvent + inputMethodEvent + ( QInputMethodEvent * event ) + + + inputMethodQuery + inputMethodQuery + ( Qt::InputMethodQuery query ) + + + installSceneEventFilter + installSceneEventFilter + ( QGraphicsItem * filterItem ) + + + isAncestorOf + isAncestorOf + ( const QGraphicsItem * child ) + + + isEnabled + isEnabled + () + + + isObscured + isObscured + () + + + isObscured + isObscured-2 + ( qreal x, qreal y, qreal w, qreal h ) + + + isObscured + isObscured-3 + ( const QRectF & rect ) + + + isObscuredBy + isObscuredBy + ( const QGraphicsItem * item ) + + + isSelected + isSelected + () + + + isVisible + isVisible + () + + + itemChange + itemChange + ( GraphicsItemChange change, const QVariant & value ) + + + keyPressEvent + keyPressEvent + ( QKeyEvent * event ) + + + keyReleaseEvent + keyReleaseEvent + ( QKeyEvent * event ) + + + mapFromItem + mapFromItem + ( const QGraphicsItem * item, const QPointF & point ) + + + mapFromItem + mapFromItem-2 + ( const QGraphicsItem * item, const QRectF & rect ) + + + mapFromItem + mapFromItem-3 + ( const QGraphicsItem * item, const QPolygonF & polygon ) + + + mapFromItem + mapFromItem-4 + ( const QGraphicsItem * item, const QPainterPath & path ) + + + mapFromItem + mapFromItem-5 + ( const QGraphicsItem * item, qreal x, qreal y, qreal w, qreal h ) + + + mapFromItem + mapFromItem-6 + ( const QGraphicsItem * item, qreal x, qreal y ) + + + mapFromParent + mapFromParent + ( const QPointF & point ) + + + mapFromParent + mapFromParent-2 + ( const QRectF & rect ) + + + mapFromParent + mapFromParent-3 + ( const QPolygonF & polygon ) + + + mapFromParent + mapFromParent-4 + ( const QPainterPath & path ) + + + mapFromParent + mapFromParent-5 + ( qreal x, qreal y, qreal w, qreal h ) + + + mapFromParent + mapFromParent-6 + ( qreal x, qreal y ) + + + mapFromScene + mapFromScene + ( const QPointF & point ) + + + mapFromScene + mapFromScene-2 + ( const QRectF & rect ) + + + mapFromScene + mapFromScene-3 + ( const QPolygonF & polygon ) + + + mapFromScene + mapFromScene-4 + ( const QPainterPath & path ) + + + mapFromScene + mapFromScene-5 + ( qreal x, qreal y, qreal w, qreal h ) + + + mapFromScene + mapFromScene-6 + ( qreal x, qreal y ) + + + mapToItem + mapToItem + ( const QGraphicsItem * item, const QPointF & point ) + + + mapToItem + mapToItem-2 + ( const QGraphicsItem * item, const QRectF & rect ) + + + mapToItem + mapToItem-3 + ( const QGraphicsItem * item, const QPolygonF & polygon ) + + + mapToItem + mapToItem-4 + ( const QGraphicsItem * item, const QPainterPath & path ) + + + mapToItem + mapToItem-5 + ( const QGraphicsItem * item, qreal x, qreal y, qreal w, qreal h ) + + + mapToItem + mapToItem-6 + ( const QGraphicsItem * item, qreal x, qreal y ) + + + mapToParent + mapToParent + ( const QPointF & point ) + + + mapToParent + mapToParent-2 + ( const QRectF & rect ) + + + mapToParent + mapToParent-3 + ( const QPolygonF & polygon ) + + + mapToParent + mapToParent-4 + ( const QPainterPath & path ) + + + mapToParent + mapToParent-5 + ( qreal x, qreal y, qreal w, qreal h ) + + + mapToParent + mapToParent-6 + ( qreal x, qreal y ) + + + mapToScene + mapToScene + ( const QPointF & point ) + + + mapToScene + mapToScene-2 + ( const QRectF & rect ) + + + mapToScene + mapToScene-3 + ( const QPolygonF & polygon ) + + + mapToScene + mapToScene-4 + ( const QPainterPath & path ) + + + mapToScene + mapToScene-5 + ( qreal x, qreal y, qreal w, qreal h ) + + + mapToScene + mapToScene-6 + ( qreal x, qreal y ) + + + mouseDoubleClickEvent + mouseDoubleClickEvent + ( QGraphicsSceneMouseEvent * event ) + + + mouseMoveEvent + mouseMoveEvent + ( QGraphicsSceneMouseEvent * event ) + + + mousePressEvent + mousePressEvent + ( QGraphicsSceneMouseEvent * event ) + + + mouseReleaseEvent + mouseReleaseEvent + ( QGraphicsSceneMouseEvent * event ) + + + moveBy + moveBy + ( qreal dx, qreal dy ) + + + opaqueArea + opaqueArea + () + + + paint + paint + ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0 ) + + + parentItem + parentItem + () + + + pos + pos + () + + + prepareGeometryChange + prepareGeometryChange + () + + + removeSceneEventFilter + removeSceneEventFilter + ( QGraphicsItem * filterItem ) + + + resetTransform + resetTransform + () + + + rotate + rotate + ( qreal angle ) + + + scale + scale + ( qreal sx, qreal sy ) + + + scene + scene + () + + + sceneBoundingRect + sceneBoundingRect + () + + + sceneEvent + sceneEvent + ( QEvent * event ) + + + sceneEventFilter + sceneEventFilter + ( QGraphicsItem * watched, QEvent * event ) + + + scenePos + scenePos + () + + + sceneTransform + sceneTransform + () + + + setAcceptDrops + setAcceptDrops + ( bool on ) + + + setAcceptedMouseButtons + setAcceptedMouseButtons + ( Qt::MouseButtons buttons ) + + + setAcceptsHoverEvents + setAcceptsHoverEvents + ( bool enabled ) + + + setCursor + setCursor + ( const QCursor & cursor ) + + + setData + setData + ( int key, const QVariant & value ) + + + setEnabled + setEnabled + ( bool enabled ) + + + setFlag + setFlag + ( GraphicsItemFlag flag, bool enabled = true ) + + + setFlags + setFlags + ( GraphicsItemFlags flags ) + + + setFocus + setFocus + ( Qt::FocusReason focusReason = Qt::OtherFocusReason ) + + + setGroup + setGroup + ( QGraphicsItemGroup * group ) + + + setHandlesChildEvents + setHandlesChildEvents + ( bool enabled ) + + + setParentItem + setParentItem + ( QGraphicsItem * parent ) + + + setPos + setPos + ( const QPointF & pos ) + + + setPos + setPos-2 + ( qreal x, qreal y ) + + + setSelected + setSelected + ( bool selected ) + + + setToolTip + setToolTip + ( const QString & toolTip ) + + + setTransform + setTransform + ( const QTransform & matrix, bool combine = false ) + + + setVisible + setVisible + ( bool visible ) + + + setZValue + setZValue + ( qreal z ) + + + shape + shape + () + + + shear + shear + ( qreal sh, qreal sv ) + + + show + show + () + + + toolTip + toolTip + () + + + topLevelItem + topLevelItem + () + + + transform + transform + () + + + translate + translate + ( qreal dx, qreal dy ) + + + type + type + () + + + unsetCursor + unsetCursor + () + + + update + update + ( const QRectF & rect = QRectF() + + + update + update-2 + ( qreal x, qreal y, qreal width, qreal height ) + + + wheelEvent + wheelEvent + ( QGraphicsSceneWheelEvent * event ) + + + x + x + () + + + y + y + () + + + zValue + zValue + () + + + UserType + UserType-var + + + + + QGraphicsItemAnimation + qgraphicsitemanimation.html + + QGraphicsItemAnimation + QGraphicsItemAnimation + ( QObject * parent = 0 ) + + + afterAnimationStep + afterAnimationStep + ( qreal step ) + + + beforeAnimationStep + beforeAnimationStep + ( qreal step ) + + + clear + clear + () + + + horizontalScaleAt + horizontalScaleAt + ( qreal step ) + + + horizontalShearAt + horizontalShearAt + ( qreal step ) + + + item + item + () + + + matrixAt + matrixAt + ( qreal step ) + + + posAt + posAt + ( qreal step ) + + + posList + posList + () + + + reset + reset + () + + + rotationAt + rotationAt + ( qreal step ) + + + rotationList + rotationList + () + + + scaleList + scaleList + () + + + setItem + setItem + ( QGraphicsItem * item ) + + + setPosAt + setPosAt + ( qreal step, const QPointF & point ) + + + setRotationAt + setRotationAt + ( qreal step, qreal angle ) + + + setScaleAt + setScaleAt + ( qreal step, qreal sx, qreal sy ) + + + setShearAt + setShearAt + ( qreal step, qreal sh, qreal sv ) + + + setStep + setStep + ( qreal step ) + + + setTimeLine + setTimeLine + ( QTimeLine * timeLine ) + + + setTranslationAt + setTranslationAt + ( qreal step, qreal dx, qreal dy ) + + + shearList + shearList + () + + + timeLine + timeLine + () + + + translationList + translationList + () + + + verticalScaleAt + verticalScaleAt + ( qreal step ) + + + verticalShearAt + verticalShearAt + ( qreal step ) + + + xTranslationAt + xTranslationAt + ( qreal step ) + + + yTranslationAt + yTranslationAt + ( qreal step ) + + + + QGraphicsItemGroup + qgraphicsitemgroup.html + + QGraphicsItemGroup + QGraphicsItemGroup + ( QGraphicsItem * parent = 0 ) + + + addToGroup + addToGroup + ( QGraphicsItem * item ) + + + removeFromGroup + removeFromGroup + ( QGraphicsItem * item ) + + + + QGraphicsLineItem + qgraphicslineitem.html + + QGraphicsLineItem + QGraphicsLineItem + ( QGraphicsItem * parent = 0 ) + + + QGraphicsLineItem + QGraphicsLineItem-2 + ( const QLineF & line, QGraphicsItem * parent = 0 ) + + + QGraphicsLineItem + QGraphicsLineItem-3 + ( qreal x1, qreal y1, qreal x2, qreal y2, QGraphicsItem * parent = 0 ) + + + line + line + () + + + pen + pen + () + + + setLine + setLine + ( const QLineF & line ) + + + setLine + setLine-2 + ( qreal x1, qreal y1, qreal x2, qreal y2 ) + + + setPen + setPen + ( const QPen & pen ) + + + + QGraphicsPathItem + qgraphicspathitem.html + + QGraphicsPathItem + QGraphicsPathItem + ( QGraphicsItem * parent = 0 ) + + + QGraphicsPathItem + QGraphicsPathItem-2 + ( const QPainterPath & path, QGraphicsItem * parent = 0 ) + + + path + path + () + + + setPath + setPath + ( const QPainterPath & path ) + + + + QGraphicsPixmapItem + qgraphicspixmapitem.html + + ShapeMode + ShapeMode-enum + + + + QGraphicsPixmapItem + QGraphicsPixmapItem + ( QGraphicsItem * parent = 0 ) + + + QGraphicsPixmapItem + QGraphicsPixmapItem-2 + ( const QPixmap & pixmap, QGraphicsItem * parent = 0 ) + + + offset + offset + () + + + pixmap + pixmap + () + + + setOffset + setOffset + ( const QPointF & offset ) + + + setOffset + setOffset-2 + ( qreal x, qreal y ) + + + setPixmap + setPixmap + ( const QPixmap & pixmap ) + + + setShapeMode + setShapeMode + ( ShapeMode mode ) + + + setTransformationMode + setTransformationMode + ( Qt::TransformationMode mode ) + + + shapeMode + shapeMode + () + + + TransformationMode + transformationMode + QGraphicsPixmapItem::transformationMode() + + + + QGraphicsPolygonItem + qgraphicspolygonitem.html + + QGraphicsPolygonItem + QGraphicsPolygonItem + ( QGraphicsItem * parent = 0 ) + + + QGraphicsPolygonItem + QGraphicsPolygonItem-2 + ( const QPolygonF & polygon, QGraphicsItem * parent = 0 ) + + + FillRule + fillRule + QGraphicsPolygonItem::fillRule() + + + polygon + polygon + () + + + setFillRule + setFillRule + ( Qt::FillRule rule ) + + + setPolygon + setPolygon + ( const QPolygonF & polygon ) + + + + QGraphicsRectItem + qgraphicsrectitem.html + + QGraphicsRectItem + QGraphicsRectItem + ( QGraphicsItem * parent = 0 ) + + + QGraphicsRectItem + QGraphicsRectItem-2 + ( const QRectF & rect, QGraphicsItem * parent = 0 ) + + + QGraphicsRectItem + QGraphicsRectItem-3 + ( qreal x, qreal y, qreal width, qreal height, QGraphicsItem * parent = 0 ) + + + rect + rect + () + + + setRect + setRect + ( const QRectF & rectangle ) + + + setRect + setRect-2 + ( qreal x, qreal y, qreal width, qreal height ) + + + + QGraphicsScene + qgraphicsscene.html + + ItemIndexMethod + ItemIndexMethod-enum + + + + QGraphicsScene + QGraphicsScene + ( QObject * parent = 0 ) + + + QGraphicsScene + QGraphicsScene-2 + ( const QRectF & sceneRect, QObject * parent = 0 ) + + + QGraphicsScene + QGraphicsScene-3 + ( qreal x, qreal y, qreal width, qreal height, QObject * parent = 0 ) + + + addEllipse + addEllipse + ( const QRectF & rect, const QPen & pen = QPen() + + + addEllipse + addEllipse-2 + ( qreal x, qreal y, qreal w, qreal h, const QPen & pen = QPen() + + + addItem + addItem + ( QGraphicsItem * item ) + + + addLine + addLine + ( const QLineF & line, const QPen & pen = QPen() + + + addLine + addLine-2 + ( qreal x1, qreal y1, qreal x2, qreal y2, const QPen & pen = QPen() + + + addPath + addPath + ( const QPainterPath & path, const QPen & pen = QPen() + + + addPixmap + addPixmap + ( const QPixmap & pixmap ) + + + addPolygon + addPolygon + ( const QPolygonF & polygon, const QPen & pen = QPen() + + + addRect + addRect + ( const QRectF & rect, const QPen & pen = QPen() + + + addRect + addRect-2 + ( qreal x, qreal y, qreal w, qreal h, const QPen & pen = QPen() + + + addSimpleText + addSimpleText + ( const QString & text, const QFont & font = QFont() + + + addText + addText + ( const QString & text, const QFont & font = QFont() + + + advance + advance + () + + + changed + changed + ( const QList<QRectF> & region ) + + + clearFocus + clearFocus + () + + + clearSelection + clearSelection + () + + + collidingItems + collidingItems + ( const QGraphicsItem * item, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape ) + + + contextMenuEvent + contextMenuEvent + ( QGraphicsSceneContextMenuEvent * contextMenuEvent ) + + + createItemGroup + createItemGroup + ( const QList<QGraphicsItem *> & items ) + + + destroyItemGroup + destroyItemGroup + ( QGraphicsItemGroup * group ) + + + dragEnterEvent + dragEnterEvent + ( QGraphicsSceneDragDropEvent * event ) + + + dragLeaveEvent + dragLeaveEvent + ( QGraphicsSceneDragDropEvent * event ) + + + dragMoveEvent + dragMoveEvent + ( QGraphicsSceneDragDropEvent * event ) + + + drawBackground + drawBackground + ( QPainter * painter, const QRectF & rect ) + + + drawForeground + drawForeground + ( QPainter * painter, const QRectF & rect ) + + + drawItems + drawItems + ( QPainter * painter, int numItems, QGraphicsItem *[] items, const QStyleOptionGraphicsItem[] options, QWidget * widget = 0 ) + + + dropEvent + dropEvent + ( QGraphicsSceneDragDropEvent * event ) + + + event + event + ( QEvent * event ) + + + focusInEvent + focusInEvent + ( QFocusEvent * focusEvent ) + + + focusItem + focusItem + () + + + focusOutEvent + focusOutEvent + ( QFocusEvent * focusEvent ) + + + hasFocus + hasFocus + () + + + height + height + () + + + helpEvent + helpEvent + ( QGraphicsSceneHelpEvent * helpEvent ) + + + inputMethodEvent + inputMethodEvent + ( QInputMethodEvent * event ) + + + inputMethodQuery + inputMethodQuery + ( Qt::InputMethodQuery query ) + + + invalidate + invalidate + ( qreal x, qreal y, qreal w, qreal h, SceneLayers layers = AllLayers ) + + + invalidate + invalidate-2 + ( const QRectF & rect = QRectF() + + + itemAt + itemAt + ( const QPointF & position ) + + + itemAt + itemAt-2 + ( qreal x, qreal y ) + + + items + items + () + + + items + items-2 + ( const QPointF & pos ) + + + items + items-3 + ( qreal x, qreal y, qreal w, qreal h, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape ) + + + items + items-4 + ( const QRectF & rectangle, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape ) + + + items + items-5 + ( const QPolygonF & polygon, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape ) + + + items + items-6 + ( const QPainterPath & path, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape ) + + + itemsBoundingRect + itemsBoundingRect + () + + + keyPressEvent + keyPressEvent + ( QKeyEvent * keyEvent ) + + + keyReleaseEvent + keyReleaseEvent + ( QKeyEvent * keyEvent ) + + + mouseDoubleClickEvent + mouseDoubleClickEvent + ( QGraphicsSceneMouseEvent * mouseEvent ) + + + mouseGrabberItem + mouseGrabberItem + () + + + mouseMoveEvent + mouseMoveEvent + ( QGraphicsSceneMouseEvent * mouseEvent ) + + + mousePressEvent + mousePressEvent + ( QGraphicsSceneMouseEvent * mouseEvent ) + + + mouseReleaseEvent + mouseReleaseEvent + ( QGraphicsSceneMouseEvent * mouseEvent ) + + + removeItem + removeItem + ( QGraphicsItem * item ) + + + render + render + ( QPainter * painter, const QRectF & target = QRectF() + + + sceneRectChanged + sceneRectChanged + ( const QRectF & rect ) + + + selectedItems + selectedItems + () + + + selectionArea + selectionArea + () + + + selectionChanged + selectionChanged + () + + + setFocus + setFocus + ( Qt::FocusReason focusReason = Qt::OtherFocusReason ) + + + setFocusItem + setFocusItem + ( QGraphicsItem * item, Qt::FocusReason focusReason = Qt::OtherFocusReason ) + + + setSelectionArea + setSelectionArea + ( const QPainterPath & path ) + + + setSelectionArea + setSelectionArea-2 + ( const QPainterPath & path, Qt::ItemSelectionMode mode ) + + + update + update + ( qreal x, qreal y, qreal w, qreal h ) + + + update + update-2 + ( const QRectF & rect = QRectF() + + + views + views + () + + + wheelEvent + wheelEvent + ( QGraphicsSceneWheelEvent * wheelEvent ) + + + width + width + () + + + + QGraphicsSceneContextMenuEvent + qgraphicsscenecontextmenuevent.html + + Reason + Reason-enum + + + + KeyboardModifiers + modifiers + QGraphicsSceneContextMenuEvent::modifiers() + + + pos + pos + () + + + reason + reason + () + + + scenePos + scenePos + () + + + screenPos + screenPos + () + + + + QGraphicsSceneDragDropEvent + qgraphicsscenedragdropevent.html + + acceptProposedAction + acceptProposedAction + () + + + MouseButtons + buttons + QGraphicsSceneDragDropEvent::buttons() + + + DropAction + dropAction + QGraphicsSceneDragDropEvent::dropAction() + + + mimeData + mimeData + () + + + KeyboardModifiers + modifiers + QGraphicsSceneDragDropEvent::modifiers() + + + pos + pos + () + + + DropActions + possibleActions + QGraphicsSceneDragDropEvent::possibleActions() + + + DropAction + proposedAction + QGraphicsSceneDragDropEvent::proposedAction() + + + scenePos + scenePos + () + + + screenPos + screenPos + () + + + setDropAction + setDropAction + ( Qt::DropAction action ) + + + source + source + () + + + + QGraphicsSceneEvent + qgraphicssceneevent.html + + widget + widget + () + + + + QGraphicsSceneHelpEvent + qgraphicsscenehelpevent.html + + scenePos + scenePos + () + + + screenPos + screenPos + () + + + + QGraphicsSceneHoverEvent + qgraphicsscenehoverevent.html + + pos + pos + () + + + scenePos + scenePos + () + + + screenPos + screenPos + () + + + + QGraphicsSceneMouseEvent + qgraphicsscenemouseevent.html + + MouseButton + button + QGraphicsSceneMouseEvent::button() + + + buttonDownPos + buttonDownPos + ( Qt::MouseButton button ) + + + buttonDownScenePos + buttonDownScenePos + ( Qt::MouseButton button ) + + + buttonDownScreenPos + buttonDownScreenPos + ( Qt::MouseButton button ) + + + MouseButtons + buttons + QGraphicsSceneMouseEvent::buttons() + + + lastPos + lastPos + () + + + lastScenePos + lastScenePos + () + + + lastScreenPos + lastScreenPos + () + + + KeyboardModifiers + modifiers + QGraphicsSceneMouseEvent::modifiers() + + + pos + pos + () + + + scenePos + scenePos + () + + + screenPos + screenPos + () + + + + QGraphicsSceneWheelEvent + qgraphicsscenewheelevent.html + + MouseButtons + buttons + QGraphicsSceneWheelEvent::buttons() + + + delta + delta + () + + + KeyboardModifiers + modifiers + QGraphicsSceneWheelEvent::modifiers() + + + Orientation + orientation + QGraphicsSceneWheelEvent::orientation() + + + pos + pos + () + + + scenePos + scenePos + () + + + screenPos + screenPos + () + + + + QGraphicsSimpleTextItem + qgraphicssimpletextitem.html + + QGraphicsSimpleTextItem + QGraphicsSimpleTextItem + ( QGraphicsItem * parent = 0 ) + + + QGraphicsSimpleTextItem + QGraphicsSimpleTextItem-2 + ( const QString & text, QGraphicsItem * parent = 0 ) + + + font + font + () + + + setFont + setFont + ( const QFont & font ) + + + setText + setText + ( const QString & text ) + + + text + text + () + + + + QGraphicsSvgItem + qgraphicssvgitem.html + + QGraphicsSvgItem + QGraphicsSvgItem + ( QGraphicsItem * parent = 0 ) + + + QGraphicsSvgItem + QGraphicsSvgItem-2 + ( const QString & fileName, QGraphicsItem * parent = 0 ) + + + boundingRect + boundingRect + () + + + elementId + elementId + () + + + isCachingEnabled + isCachingEnabled + () + + + maximumCacheSize + maximumCacheSize + () + + + renderer + renderer + () + + + setCachingEnabled + setCachingEnabled + ( bool caching ) + + + setElementId + setElementId + ( const QString & id ) + + + setMaximumCacheSize + setMaximumCacheSize + ( const QSize & size ) + + + setSharedRenderer + setSharedRenderer + ( QSvgRenderer * renderer ) + + + + QGraphicsTextItem + qgraphicstextitem.html + + QGraphicsTextItem + QGraphicsTextItem + ( QGraphicsItem * parent = 0 ) + + + QGraphicsTextItem + QGraphicsTextItem-2 + ( const QString & text, QGraphicsItem * parent = 0 ) + + + adjustSize + adjustSize + () + + + defaultTextColor + defaultTextColor + () + + + document + document + () + + + font + font + () + + + linkActivated + linkActivated + ( const QString & link ) + + + linkHovered + linkHovered + ( const QString & link ) + + + setDefaultTextColor + setDefaultTextColor + ( const QColor & col ) + + + setDocument + setDocument + ( QTextDocument * document ) + + + setFont + setFont + ( const QFont & font ) + + + setHtml + setHtml + ( const QString & text ) + + + setPlainText + setPlainText + ( const QString & text ) + + + setTextInteractionFlags + setTextInteractionFlags + ( Qt::TextInteractionFlags flags ) + + + setTextWidth + setTextWidth + ( qreal width ) + + + TextInteractionFlags + textInteractionFlags + QGraphicsTextItem::textInteractionFlags() + + + textWidth + textWidth + () + + + toHtml + toHtml + () + + + toPlainText + toPlainText + () + + + + QGraphicsView + qgraphicsview.html + + DragMode + DragMode-enum + + + + ViewportAnchor + ViewportAnchor-enum + + + + ViewportUpdateMode + ViewportUpdateMode-enum + + + + Alignment + alignment-prop + + + + RenderHints + renderHints-prop + + + + ItemSelectionMode + rubberBandSelectionMode-prop + + + + QGraphicsView + QGraphicsView + ( QWidget * parent = 0 ) + + + QGraphicsView + QGraphicsView-2 + ( QGraphicsScene * scene, QWidget * parent = 0 ) + + + centerOn + centerOn + ( const QPointF & pos ) + + + centerOn + centerOn-2 + ( qreal x, qreal y ) + + + centerOn + centerOn-3 + ( const QGraphicsItem * item ) + + + drawBackground + drawBackground + ( QPainter * painter, const QRectF & rect ) + + + drawForeground + drawForeground + ( QPainter * painter, const QRectF & rect ) + + + drawItems + drawItems + ( QPainter * painter, int numItems, QGraphicsItem *[] items, const QStyleOptionGraphicsItem[] options ) + + + ensureVisible + ensureVisible + ( const QRectF & rect, int xmargin = 50, int ymargin = 50 ) + + + ensureVisible + ensureVisible-2 + ( qreal x, qreal y, qreal w, qreal h, int xmargin = 50, int ymargin = 50 ) + + + ensureVisible + ensureVisible-3 + ( const QGraphicsItem * item, int xmargin = 50, int ymargin = 50 ) + + + fitInView + fitInView + ( const QRectF & rect, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio ) + + + fitInView + fitInView-2 + ( qreal x, qreal y, qreal w, qreal h, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio ) + + + fitInView + fitInView-3 + ( const QGraphicsItem * item, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio ) + + + invalidateScene + invalidateScene + ( const QRectF & rect = QRectF() + + + itemAt + itemAt + ( const QPoint & pos ) + + + itemAt + itemAt-2 + ( int x, int y ) + + + items + items + () + + + items + items-2 + ( const QPoint & pos ) + + + items + items-3 + ( int x, int y ) + + + items + items-4 + ( int x, int y, int w, int h, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape ) + + + items + items-5 + ( const QRect & rect, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape ) + + + items + items-6 + ( const QPolygon & polygon, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape ) + + + items + items-7 + ( const QPainterPath & path, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape ) + + + mapFromScene + mapFromScene + ( const QPointF & point ) + + + mapFromScene + mapFromScene-2 + ( const QRectF & rect ) + + + mapFromScene + mapFromScene-3 + ( const QPolygonF & polygon ) + + + mapFromScene + mapFromScene-4 + ( const QPainterPath & path ) + + + mapFromScene + mapFromScene-5 + ( qreal x, qreal y ) + + + mapFromScene + mapFromScene-6 + ( qreal x, qreal y, qreal w, qreal h ) + + + mapToScene + mapToScene + ( const QPoint & point ) + + + mapToScene + mapToScene-2 + ( const QRect & rect ) + + + mapToScene + mapToScene-3 + ( const QPolygon & polygon ) + + + mapToScene + mapToScene-4 + ( const QPainterPath & path ) + + + mapToScene + mapToScene-5 + ( int x, int y ) + + + mapToScene + mapToScene-6 + ( int x, int y, int w, int h ) + + + matrix + matrix + () + + + render + render + ( QPainter * painter, const QRectF & target = QRectF() + + + resetCachedContent + resetCachedContent + () + + + resetMatrix + resetMatrix + () + + + resetTransform + resetTransform + () + + + rotate + rotate + ( qreal angle ) + + + scale + scale + ( qreal sx, qreal sy ) + + + scene + scene + () + + + setMatrix + setMatrix + ( const QMatrix & matrix, bool combine = false ) + + + setOptimizationFlag + setOptimizationFlag + ( OptimizationFlag flag, bool enabled = true ) + + + setRenderHint + setRenderHint + ( QPainter::RenderHint hint, bool enabled = true ) + + + setScene + setScene + ( QGraphicsScene * scene ) + + + setTransform + setTransform + ( const QTransform & matrix, bool combine = false ) + + + setupViewport + setupViewport + ( QWidget * widget ) + + + shear + shear + ( qreal sh, qreal sv ) + + + transform + transform + () + + + translate + translate + ( qreal dx, qreal dy ) + + + updateScene + updateScene + ( const QList<QRectF> & rects ) + + + updateSceneRect + updateSceneRect + ( const QRectF & rect ) + + + viewportTransform + viewportTransform + () + + + QGridLayout + QGridLayout-3 + ( QWidget * parent, int nRows, int nCols = 1, int margin = 0, int space = -1, const char * name = 0 ) + + + QGridLayout + QGridLayout-4 + ( int nRows, int nCols = 1, int spacing = -1, const char * name = 0 ) + + + QGridLayout + QGridLayout-5 + ( QLayout * parentLayout, int nRows = 1, int nCols = 1, int spacing = -1, const char * name = 0 ) + + + addColSpacing + addColSpacing + ( int col, int minsize ) + + + addMultiCell + addMultiCell + ( QLayoutItem * l, int fromRow, int toRow, int fromCol, int toCol, Qt::Alignment align = 0 ) + + + addMultiCellLayout + addMultiCellLayout + ( QLayout * layout, int fromRow, int toRow, int fromCol, int toCol, Qt::Alignment align = 0 ) + + + addMultiCellWidget + addMultiCellWidget + ( QWidget * widget, int fromRow, int toRow, int fromCol, int toCol, Qt::Alignment align = 0 ) + + + addRowSpacing + addRowSpacing + ( int row, int minsize ) + + + cellGeometry + cellGeometry + ( int row, int column ) + + + colSpacing + colSpacing + ( int col ) + + + colStretch + colStretch + ( int col ) + + + expand + expand + ( int nRows, int nCols ) + + + findWidget + findWidget + ( QWidget * w, int * row, int * column ) + + + numCols + numCols + () + + + numRows + numRows + () + + + Corner + origin + QGridLayout::origin() + + + rowSpacing + rowSpacing + ( int row ) + + + setColSpacing + setColSpacing + ( int col, int minSize ) + + + setColStretch + setColStretch + ( int col, int stretch ) + + + setOrigin + setOrigin + ( Qt::Corner corner ) + + + setRowSpacing + setRowSpacing + ( int row, int minSize ) + + + + QGridLayout + qgridlayout.html + + QGridLayout + QGridLayout + ( QWidget * parent ) + + + QGridLayout + QGridLayout-2 + () + + + addItem + addItem + ( QLayoutItem * item, int row, int column, int rowSpan = 1, int columnSpan = 1, Qt::Alignment alignment = 0 ) + + + addLayout + addLayout + ( QLayout * layout, int row, int column, Qt::Alignment alignment = 0 ) + + + addLayout + addLayout-2 + ( QLayout * layout, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment = 0 ) + + + addWidget + addWidget + ( QWidget * widget, int row, int column, Qt::Alignment alignment = 0 ) + + + addWidget + addWidget-2 + ( QWidget * widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment = 0 ) + + + cellRect + cellRect + ( int row, int column ) + + + columnCount + columnCount + () + + + columnMinimumWidth + columnMinimumWidth + ( int column ) + + + columnStretch + columnStretch + ( int column ) + + + getItemPosition + getItemPosition + ( int index, int * row, int * column, int * rowSpan, int * columnSpan ) + + + Corner + originCorner + QGridLayout::originCorner() + + + rowCount + rowCount + () + + + rowMinimumHeight + rowMinimumHeight + ( int row ) + + + rowStretch + rowStretch + ( int row ) + + + setColumnMinimumWidth + setColumnMinimumWidth + ( int column, int minSize ) + + + setColumnStretch + setColumnStretch + ( int column, int stretch ) + + + setOriginCorner + setOriginCorner + ( Qt::Corner corner ) + + + setRowMinimumHeight + setRowMinimumHeight + ( int row, int minSize ) + + + setRowStretch + setRowStretch + ( int row, int stretch ) + + + setSpacing + setSpacing + ( int spacing ) + + + spacing + spacing + () + + + QGridLayout + QGridLayout-3 + ( QWidget * parent, int nRows, int nCols = 1, int margin = 0, int space = -1, const char * name = 0 ) + + + QGridLayout + QGridLayout-4 + ( int nRows, int nCols = 1, int spacing = -1, const char * name = 0 ) + + + QGridLayout + QGridLayout-5 + ( QLayout * parentLayout, int nRows = 1, int nCols = 1, int spacing = -1, const char * name = 0 ) + + + addColSpacing + addColSpacing + ( int col, int minsize ) + + + addMultiCell + addMultiCell + ( QLayoutItem * l, int fromRow, int toRow, int fromCol, int toCol, Qt::Alignment align = 0 ) + + + addMultiCellLayout + addMultiCellLayout + ( QLayout * layout, int fromRow, int toRow, int fromCol, int toCol, Qt::Alignment align = 0 ) + + + addMultiCellWidget + addMultiCellWidget + ( QWidget * widget, int fromRow, int toRow, int fromCol, int toCol, Qt::Alignment align = 0 ) + + + addRowSpacing + addRowSpacing + ( int row, int minsize ) + + + cellGeometry + cellGeometry + ( int row, int column ) + + + colSpacing + colSpacing + ( int col ) + + + colStretch + colStretch + ( int col ) + + + expand + expand + ( int nRows, int nCols ) + + + findWidget + findWidget + ( QWidget * w, int * row, int * column ) + + + numCols + numCols + () + + + numRows + numRows + () + + + Corner + origin + QGridLayout::origin() + + + rowSpacing + rowSpacing + ( int row ) + + + setColSpacing + setColSpacing + ( int col, int minSize ) + + + setColStretch + setColStretch + ( int col, int stretch ) + + + setOrigin + setOrigin + ( Qt::Corner corner ) + + + setRowSpacing + setRowSpacing + ( int row, int minSize ) + + + QGroupBox + QGroupBox-3 + ( QWidget * parent, const char * name ) + + + QGroupBox + QGroupBox-4 + ( const QString & title, QWidget * parent, const char * name ) + + + + QGroupBox + qgroupbox.html + + Alignment + alignment-prop + + + + QGroupBox + QGroupBox + ( QWidget * parent = 0 ) + + + QGroupBox + QGroupBox-2 + ( const QString & title, QWidget * parent = 0 ) + + + clicked + clicked + ( bool checked = false ) + + + initStyleOption + initStyleOption + ( QStyleOptionGroupBox * option ) + + + toggled + toggled + ( bool on ) + + + QGroupBox + QGroupBox-3 + ( QWidget * parent, const char * name ) + + + QGroupBox + QGroupBox-4 + ( const QString & title, QWidget * parent, const char * name ) + + + + QHash::const_iterator + qhash-const-iterator.html + + const_iterator + const_iterator + () + + + const_iterator + const_iterator-3 + ( const iterator & other ) + + + key + key + () + + + value + value + () + + + operator!= + operator-not-eq + ( const const_iterator & other ) + + + operator* + operator-2a + () + + + operator+ + operator-2b + ( int j ) + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator+= + operator-2b-eq + ( int j ) + + + operator- + operator- + ( int j ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator-= + operator--eq + ( int j ) + + + operator-& + operator--gt + gt;() + + + operator== + operator-eq-eq + ( const const_iterator & other ) + + + + QHash::iterator + qhash-iterator.html + + iterator + iterator + () + + + key + key + () + + + value + value + () + + + operator!= + operator-not-eq + ( const iterator & other ) + + + operator!= + operator-not-eq-2 + ( const const_iterator & other ) + + + operator* + operator-2a + () + + + operator+ + operator-2b + ( int j ) + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator+= + operator-2b-eq + ( int j ) + + + operator- + operator- + ( int j ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator-= + operator--eq + ( int j ) + + + operator-& + operator--gt + gt;() + + + operator== + operator-eq-eq + ( const iterator & other ) + + + operator== + operator-eq-eq-2 + ( const const_iterator & other ) + + + + QHash + qhash.html + + ConstIterator + ConstIterator-typedef + + + + Iterator + Iterator-typedef + + + + difference_type + difference_type-typedef + + + + key_type + key_type-typedef + + + + mapped_type + mapped_type-typedef + + + + size_type + size_type-typedef + + + + QHash + QHash + () + + + QHash + QHash-2 + ( const QHash<Key, T> & other ) + + + begin + begin + () + + + begin + begin-2 + () + + + capacity + capacity + () + + + clear + clear + () + + + constBegin + constBegin + () + + + constEnd + constEnd + () + + + constFind + constFind + ( const Key & key ) + + + contains + contains + ( const Key & key ) + + + count + count + ( const Key & key ) + + + count + count-2 + () + + + empty + empty + () + + + end + end + () + + + end + end-2 + () + + + erase + erase + ( iterator pos ) + + + find + find + ( const Key & key ) + + + find + find-2 + ( const Key & key ) + + + insert + insert + ( const Key & key, const T & value ) + + + insertMulti + insertMulti + ( const Key & key, const T & value ) + + + isEmpty + isEmpty + () + + + key + key + ( const T & value ) + + + key + key-2 + ( const T & value, const Key & defaultKey ) + + + keys + keys + () + + + keys + keys-2 + ( const T & value ) + + + remove + remove + ( const Key & key ) + + + reserve + reserve + ( int size ) + + + size + size + () + + + squeeze + squeeze + () + + + take + take + ( const Key & key ) + + + uniqueKeys + uniqueKeys + () + + + unite + unite + ( const QHash<Key, T> & other ) + + + value + value + ( const Key & key ) + + + value + value-2 + ( const Key & key, const T & defaultValue ) + + + values + values + () + + + values + values-2 + ( const Key & key ) + + + operator!= + operator-not-eq + ( const QHash<Key, T> & other ) + + + operator= + operator-eq + ( const QHash<Key, T> & other ) + + + operator== + operator-eq-eq + ( const QHash<Key, T> & other ) + + + operator[] + operator-5b-5d + ( const Key & key ) + + + operator[] + operator-5b-5d-2 + ( const Key & key ) + + + + QHashIterator + qhashiterator.html + + QHashIterator + QHashIterator + ( const QHash<Key, T> & hash ) + + + findNext + findNext + ( const T & value ) + + + findPrevious + findPrevious + ( const T & value ) + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + key + key + () + + + next + next + () + + + peekNext + peekNext + () + + + peekPrevious + peekPrevious + () + + + previous + previous + () + + + toBack + toBack + () + + + toFront + toFront + () + + + value + value + () + + + operator= + operator-eq + ( const QHash<Key, T> & hash ) + + + QHBoxLayout + QHBoxLayout-3 + ( QWidget * parent, int margin, int spacing = -1, const char * name = 0 ) + + + QHBoxLayout + QHBoxLayout-4 + ( QLayout * parentLayout, int spacing = -1, const char * name = 0 ) + + + QHBoxLayout + QHBoxLayout-5 + ( int spacing, const char * name = 0 ) + + + + QHBoxLayout + qhboxlayout.html + + QHBoxLayout + QHBoxLayout + () + + + QHBoxLayout + QHBoxLayout-2 + ( QWidget * parent ) + + + QHBoxLayout + QHBoxLayout-3 + ( QWidget * parent, int margin, int spacing = -1, const char * name = 0 ) + + + QHBoxLayout + QHBoxLayout-4 + ( QLayout * parentLayout, int spacing = -1, const char * name = 0 ) + + + QHBoxLayout + QHBoxLayout-5 + ( int spacing, const char * name = 0 ) + + + + QHeaderView + qheaderview.html + + ResizeMode + ResizeMode-enum + + + + Alignment + defaultAlignment-prop + + + + QHeaderView + QHeaderView + ( Qt::Orientation orientation, QWidget * parent = 0 ) + + + count + count + () + + + geometriesChanged + geometriesChanged + () + + + headerDataChanged + headerDataChanged + ( Qt::Orientation orientation, int logicalFirst, int logicalLast ) + + + hiddenSectionCount + hiddenSectionCount + () + + + hideSection + hideSection + ( int logicalIndex ) + + + horizontalOffset + horizontalOffset + () + + + initStyleOption + initStyleOption + ( QStyleOptionHeader * option ) + + + isClickable + isClickable + () + + + isMovable + isMovable + () + + + isSectionHidden + isSectionHidden + ( int logicalIndex ) + + + length + length + () + + + logicalIndex + logicalIndex + ( int visualIndex ) + + + logicalIndexAt + logicalIndexAt + ( int position ) + + + logicalIndexAt + logicalIndexAt-2 + ( int x, int y ) + + + logicalIndexAt + logicalIndexAt-3 + ( const QPoint & pos ) + + + moveSection + moveSection + ( int from, int to ) + + + offset + offset + () + + + Orientation + orientation + QHeaderView::orientation() + + + paintSection + paintSection + ( QPainter * painter, const QRect & rect, int logicalIndex ) + + + resizeMode + resizeMode + ( int logicalIndex ) + + + resizeSection + resizeSection + ( int logicalIndex, int size ) + + + resizeSections + resizeSections + ( QHeaderView::ResizeMode mode ) + + + resizeSections + resizeSections-2 + () + + + restoreState + restoreState + ( const QByteArray & state ) + + + saveState + saveState + () + + + sectionAutoResize + sectionAutoResize + ( int logicalIndex, QHeaderView::ResizeMode mode ) + + + sectionClicked + sectionClicked + ( int logicalIndex ) + + + sectionCountChanged + sectionCountChanged + ( int oldCount, int newCount ) + + + sectionDoubleClicked + sectionDoubleClicked + ( int logicalIndex ) + + + sectionEntered + sectionEntered + ( int logicalIndex ) + + + sectionHandleDoubleClicked + sectionHandleDoubleClicked + ( int logicalIndex ) + + + sectionMoved + sectionMoved + ( int logicalIndex, int oldVisualIndex, int newVisualIndex ) + + + sectionPosition + sectionPosition + ( int logicalIndex ) + + + sectionPressed + sectionPressed + ( int logicalIndex ) + + + sectionResized + sectionResized + ( int logicalIndex, int oldSize, int newSize ) + + + sectionSize + sectionSize + ( int logicalIndex ) + + + sectionSizeFromContents + sectionSizeFromContents + ( int logicalIndex ) + + + sectionSizeHint + sectionSizeHint + ( int logicalIndex ) + + + sectionViewportPosition + sectionViewportPosition + ( int logicalIndex ) + + + sectionsAboutToBeRemoved + sectionsAboutToBeRemoved + ( const QModelIndex & parent, int logicalFirst, int logicalLast ) + + + sectionsHidden + sectionsHidden + () + + + sectionsInserted + sectionsInserted + ( const QModelIndex & parent, int logicalFirst, int logicalLast ) + + + sectionsMoved + sectionsMoved + () + + + setClickable + setClickable + ( bool clickable ) + + + setMovable + setMovable + ( bool movable ) + + + setOffset + setOffset + ( int offset ) + + + setOffsetToLastSection + setOffsetToLastSection + () + + + setOffsetToSectionPosition + setOffsetToSectionPosition + ( int visualIndex ) + + + setResizeMode + setResizeMode + ( ResizeMode mode ) + + + setResizeMode + setResizeMode-2 + ( int logicalIndex, ResizeMode mode ) + + + setSectionHidden + setSectionHidden + ( int logicalIndex, bool hide ) + + + setSortIndicator + setSortIndicator + ( int logicalIndex, Qt::SortOrder order ) + + + showSection + showSection + ( int logicalIndex ) + + + sizeHint + sizeHint + () + + + sortIndicatorChanged + sortIndicatorChanged + ( int logicalIndex, Qt::SortOrder order ) + + + SortOrder + sortIndicatorOrder + QHeaderView::sortIndicatorOrder() + + + sortIndicatorSection + sortIndicatorSection + () + + + stretchSectionCount + stretchSectionCount + () + + + swapSections + swapSections + ( int first, int second ) + + + verticalOffset + verticalOffset + () + + + visualIndex + visualIndex + ( int logicalIndex ) + + + visualIndexAt + visualIndexAt + ( int position ) + + + + QHelpEvent + qhelpevent.html + + QHelpEvent + QHelpEvent + ( Type type, const QPoint & pos, const QPoint & globalPos ) + + + globalPos + globalPos + () + + + globalX + globalX + () + + + globalY + globalY + () + + + pos + pos + () + + + x + x + () + + + y + y + () + + + + QHideEvent + qhideevent.html + + QHideEvent + QHideEvent + () + + + ip4Addr + ip4Addr + () + + + isIPv4Address + isIPv4Address + () + + + isIPv6Address + isIPv6Address + () + + + isIp4Addr + isIp4Addr + () + + + + QHostAddress + qhostaddress.html + + SpecialAddress + SpecialAddress-enum + + + + QHostAddress + QHostAddress + () + + + QHostAddress + QHostAddress-2 + ( quint32 ip4Addr ) + + + QHostAddress + QHostAddress-3 + ( quint8 * ip6Addr ) + + + QHostAddress + QHostAddress-4 + ( const Q_IPV6ADDR & ip6Addr ) + + + QHostAddress + QHostAddress-5 + ( const sockaddr * sockaddr ) + + + QHostAddress + QHostAddress-6 + ( const QString & address ) + + + QHostAddress + QHostAddress-7 + ( const QHostAddress & address ) + + + QHostAddress + QHostAddress-8 + ( SpecialAddress address ) + + + clear + clear + () + + + isNull + isNull + () + + + NetworkLayerProtocol + protocol + QHostAddress::protocol() + + + scopeId + scopeId + () + + + setAddress + setAddress + ( quint32 ip4Addr ) + + + setAddress + setAddress-2 + ( quint8 * ip6Addr ) + + + setAddress + setAddress-3 + ( const Q_IPV6ADDR & ip6Addr ) + + + setAddress + setAddress-4 + ( const QString & address ) + + + setAddress + setAddress-5 + ( const sockaddr * sockaddr ) + + + setScopeId + setScopeId + ( const QString & id ) + + + toIPv4Address + toIPv4Address + () + + + toIPv6Address + toIPv6Address + () + + + toString + toString + () + + + operator!= + operator-not-eq + ( const QHostAddress & other ) + + + operator!= + operator-not-eq-2 + ( SpecialAddress other ) + + + operator= + operator-eq + ( const QHostAddress & address ) + + + operator= + operator-eq-2 + ( const QString & address ) + + + operator== + operator-eq-eq + ( const QHostAddress & other ) + + + operator== + operator-eq-eq-2 + ( SpecialAddress other ) + + + ip4Addr + ip4Addr + () + + + isIPv4Address + isIPv4Address + () + + + isIPv6Address + isIPv6Address + () + + + isIp4Addr + isIp4Addr + () + + + + QHostInfo + qhostinfo.html + + HostInfoError + HostInfoError-enum + + + + QHostInfo + QHostInfo + ( int id = -1 ) + + + QHostInfo + QHostInfo-2 + ( const QHostInfo & other ) + + + abortHostLookup + abortHostLookup + ( int id ) + + + addresses + addresses + () + + + error + error + () + + + errorString + errorString + () + + + fromName + fromName + ( const QString & name ) + + + hostName + hostName + () + + + localHostName + localHostName + () + + + lookupHost + lookupHost + ( const QString & name, QObject * receiver, const char * member ) + + + lookupId + lookupId + () + + + setAddresses + setAddresses + ( const QList<QHostAddress> & addresses ) + + + setError + setError + ( HostInfoError error ) + + + setErrorString + setErrorString + ( const QString & str ) + + + setHostName + setHostName + ( const QString & hostName ) + + + setLookupId + setLookupId + ( int id ) + + + operator= + operator-eq + ( const QHostInfo & other ) + + + + QHoverEvent + qhoverevent.html + + QHoverEvent + QHoverEvent + ( Type type, const QPoint & pos, const QPoint & oldPos ) + + + oldPos + oldPos + () + + + pos + pos + () + + + closeConnection + closeConnection + () + + + readBlock + readBlock + ( char * data, quint64 maxlen ) + + + + QHttp + qhttp.html + + ConnectionMode + ConnectionMode-enum + + + + Error + Error-enum + + + + State + State-enum + + + + QHttp + QHttp + ( QObject * parent = 0 ) + + + QHttp + QHttp-2 + ( const QString & hostName, quint16 port = 80, QObject * parent = 0 ) + + + QHttp + QHttp-3 + ( const QString & hostName, ConnectionMode mode, quint16 port = 0, QObject * parent = 0 ) + + + abort + abort + () + + + authenticationRequired + authenticationRequired + ( const QString & hostname, quint16 port, QAuthenticator * authenticator ) + + + bytesAvailable + bytesAvailable + () + + + clearPendingRequests + clearPendingRequests + () + + + close + close + () + + + currentDestinationDevice + currentDestinationDevice + () + + + currentId + currentId + () + + + currentRequest + currentRequest + () + + + currentSourceDevice + currentSourceDevice + () + + + dataReadProgress + dataReadProgress + ( int done, int total ) + + + dataSendProgress + dataSendProgress + ( int done, int total ) + + + done + done + ( bool error ) + + + error + error + () + + + errorString + errorString + () + + + get + get + ( const QString & path, QIODevice * to = 0 ) + + + hasPendingRequests + hasPendingRequests + () + + + head + head + ( const QString & path ) + + + ignoreSslErrors + ignoreSslErrors + () + + + lastResponse + lastResponse + () + + + post + post + ( const QString & path, QIODevice * data, QIODevice * to = 0 ) + + + post + post-2 + ( const QString & path, const QByteArray & data, QIODevice * to = 0 ) + + + proxyAuthenticationRequired + proxyAuthenticationRequired + ( const QNetworkProxy & proxy, QAuthenticator * authenticator ) + + + read + read + ( char * data, qint64 maxlen ) + + + readAll + readAll + () + + + readyRead + readyRead + ( const QHttpResponseHeader & resp ) + + + request + request + ( const QHttpRequestHeader & header, QIODevice * data = 0, QIODevice * to = 0 ) + + + request + request-2 + ( const QHttpRequestHeader & header, const QByteArray & data, QIODevice * to = 0 ) + + + requestFinished + requestFinished + ( int id, bool error ) + + + requestStarted + requestStarted + ( int id ) + + + responseHeaderReceived + responseHeaderReceived + ( const QHttpResponseHeader & resp ) + + + setHost + setHost + ( const QString & hostName, quint16 port = 80 ) + + + setHost + setHost-2 + ( const QString & hostName, ConnectionMode mode, quint16 port = 0 ) + + + setProxy + setProxy + ( const QString & host, int port, const QString & username = QString() + + + setProxy + setProxy-2 + ( const QNetworkProxy & proxy ) + + + setSocket + setSocket + ( QTcpSocket * socket ) + + + setUser + setUser + ( const QString & userName, const QString & password = QString() + + + sslErrors + sslErrors + ( const QList<QSslError> & errors ) + + + state + state + () + + + stateChanged + stateChanged + ( int state ) + + + readBlock + readBlock + ( char * data, quint64 maxlen ) + + + + QHttpHeader + qhttpheader.html + + QHttpHeader + QHttpHeader + () + + + QHttpHeader + QHttpHeader-2 + ( const QHttpHeader & header ) + + + QHttpHeader + QHttpHeader-3 + ( const QString & str ) + + + addValue + addValue + ( const QString & key, const QString & value ) + + + allValues + allValues + ( const QString & key ) + + + contentLength + contentLength + () + + + contentType + contentType + () + + + hasContentLength + hasContentLength + () + + + hasContentType + hasContentType + () + + + hasKey + hasKey + ( const QString & key ) + + + isValid + isValid + () + + + keys + keys + () + + + majorVersion + majorVersion + () + + + minorVersion + minorVersion + () + + + removeAllValues + removeAllValues + ( const QString & key ) + + + removeValue + removeValue + ( const QString & key ) + + + setContentLength + setContentLength + ( int len ) + + + setContentType + setContentType + ( const QString & type ) + + + setValue + setValue + ( const QString & key, const QString & value ) + + + setValues + setValues + ( const QList<QPair<QString, QString> > & values ) + + + toString + toString + () + + + value + value + ( const QString & key ) + + + values + values + () + + + operator= + operator-eq + ( const QHttpHeader & h ) + + + + QHttpRequestHeader + qhttprequestheader.html + + QHttpRequestHeader + QHttpRequestHeader + () + + + QHttpRequestHeader + QHttpRequestHeader-2 + ( const QString & method, const QString & path, int majorVer = 1, int minorVer = 1 ) + + + QHttpRequestHeader + QHttpRequestHeader-3 + ( const QHttpRequestHeader & header ) + + + QHttpRequestHeader + QHttpRequestHeader-4 + ( const QString & str ) + + + majorVersion + majorVersion + () + + + method + method + () + + + minorVersion + minorVersion + () + + + path + path + () + + + setRequest + setRequest + ( const QString & method, const QString & path, int majorVer = 1, int minorVer = 1 ) + + + operator= + operator-eq + ( const QHttpRequestHeader & header ) + + + + QHttpResponseHeader + qhttpresponseheader.html + + QHttpResponseHeader + QHttpResponseHeader + () + + + QHttpResponseHeader + QHttpResponseHeader-2 + ( const QHttpResponseHeader & header ) + + + QHttpResponseHeader + QHttpResponseHeader-3 + ( const QString & str ) + + + QHttpResponseHeader + QHttpResponseHeader-4 + ( int code, const QString & text = QString() + + + majorVersion + majorVersion + () + + + minorVersion + minorVersion + () + + + reasonPhrase + reasonPhrase + () + + + setStatusLine + setStatusLine + ( int code, const QString & text = QString() + + + statusCode + statusCode + () + + + operator= + operator-eq + ( const QHttpResponseHeader & header ) + + + serialNumber + serialNumber + () + + + Size + Size-enum + + + + pixmap + pixmap-2 + ( Size size, Mode mode, State state = Off ) + + + pixmap + pixmap-3 + ( Size size, bool enabled, State state = Off ) + + + pixmap + pixmap-4 + () + + + pixmapSize + pixmapSize + ( Size which ) + + + reset + reset + ( const QPixmap & pixmap, Size size ) + + + setPixmap + setPixmap + ( const QPixmap & pixmap, Size size, Mode mode = Normal, State state = Off ) + + + setPixmap + setPixmap-2 + ( const QString & fileName, Size size, Mode mode = Normal, State state = Off ) + + + setPixmapSize + setPixmapSize + ( Size which, const QSize & size ) + + + + QIcon + qicon.html + + DataPtr + DataPtr-typedef + + + + Mode + Mode-enum + + + + State + State-enum + + + + QIcon + QIcon + () + + + QIcon + QIcon-2 + ( const QPixmap & pixmap ) + + + QIcon + QIcon-3 + ( const QIcon & other ) + + + QIcon + QIcon-4 + ( const QString & fileName ) + + + QIcon + QIcon-5 + ( QIconEngine * engine ) + + + QIcon + QIcon-6 + ( QIconEngineV2 * engine ) + + + actualSize + actualSize + ( const QSize & size, Mode mode = Normal, State state = Off ) + + + addFile + addFile + ( const QString & fileName, const QSize & size = QSize() + + + addPixmap + addPixmap + ( const QPixmap & pixmap, Mode mode = Normal, State state = Off ) + + + cacheKey + cacheKey + () + + + data_ptr + data_ptr + () + + + isNull + isNull + () + + + paint + paint + ( QPainter * painter, const QRect & rect, Qt::Alignment alignment = Qt::AlignCenter, Mode mode = Normal, State state = Off ) + + + paint + paint-2 + ( QPainter * painter, int x, int y, int w, int h, Qt::Alignment alignment = Qt::AlignCenter, Mode mode = Normal, State state = Off ) + + + pixmap + pixmap + ( const QSize & size, Mode mode = Normal, State state = Off ) + + + pixmap + pixmap-5 + ( int w, int h, Mode mode = Normal, State state = Off ) + + + pixmap + pixmap-6 + ( int extent, Mode mode = Normal, State state = Off ) + + + operator + operator-QVariant + QVariant() + + + operator= + operator-eq + ( const QIcon & other ) + + + Size + Size-enum + + + + pixmap + pixmap-2 + ( Size size, Mode mode, State state = Off ) + + + pixmap + pixmap-3 + ( Size size, bool enabled, State state = Off ) + + + pixmap + pixmap-4 + () + + + pixmapSize + pixmapSize + ( Size which ) + + + reset + reset + ( const QPixmap & pixmap, Size size ) + + + setPixmap + setPixmap + ( const QPixmap & pixmap, Size size, Mode mode = Normal, State state = Off ) + + + setPixmap + setPixmap-2 + ( const QString & fileName, Size size, Mode mode = Normal, State state = Off ) + + + setPixmapSize + setPixmapSize + ( Size which, const QSize & size ) + + + + QIconDragEvent + qicondragevent.html + + QIconDragEvent + QIconDragEvent + () + + + + QIconEngine + qiconengine.html + + actualSize + actualSize + ( const QSize & size, QIcon::Mode mode, QIcon::State state ) + + + addFile + addFile + ( const QString & fileName, const QSize & size, QIcon::Mode mode, QIcon::State state ) + + + addPixmap + addPixmap + ( const QPixmap & pixmap, QIcon::Mode mode, QIcon::State state ) + + + paint + paint + ( QPainter * painter, const QRect & rect, QIcon::Mode mode, QIcon::State state ) + + + pixmap + pixmap + ( const QSize & size, QIcon::Mode mode, QIcon::State state ) + + + + QIconEnginePlugin + qiconengineplugin.html + + QIconEnginePlugin + QIconEnginePlugin + ( QObject * parent = 0 ) + + + create + create + ( const QString & filename ) + + + keys + keys + () + + + + QIconEnginePluginV2 + qiconenginepluginv2.html + + QIconEnginePluginV2 + QIconEnginePluginV2 + ( QObject * parent = 0 ) + + + create + create + ( const QString & filename = QString() + + + keys + keys + () + + + + QIconEngineV2 + qiconenginev2.html + + clone + clone + () + + + key + key + () + + + read + read + ( QDataStream & in ) + + + write + write + ( QDataStream & out ) + + + serialNumber + serialNumber + () + + + setText + setText-2 + ( const char * key, const char * language, const QString & text ) + + + text + text-2 + ( const char * key, const char * language = 0 ) + + + text + text-3 + ( const QImageTextKeyLang & keywordAndLanguage ) + + + textLanguages + textLanguages + () + + + textList + textList + () + + + Endian + Endian-enum + + + + QImage + QImage-12 + ( int width, int height, int depth, int numColors = 0, Endian bitOrder = IgnoreEndian ) + + + QImage + QImage-13 + ( const QSize & size, int depth, int numColors = 0, Endian bitOrder = IgnoreEndian ) + + + QImage + QImage-14 + ( uchar * data, int width, int height, int depth, const QRgb * colortable, int numColors, Endian bitOrder ) + + + QImage + QImage-15 + ( uchar * data, int width, int height, int depth, int bytesPerLine, const QRgb * colortable, int numColors, Endian bitOrder ) + + + QImage + QImage-16 + ( const QByteArray & data ) + + + bitOrder + bitOrder + () + + + convertBitOrder + convertBitOrder + ( Endian bitOrder ) + + + convertDepth + convertDepth + ( int depth, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + convertDepthWithPalette + convertDepthWithPalette + ( int depth, QRgb * palette, int palette_count, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + copy + copy-2 + ( int x, int y, int w, int h, Qt::ImageConversionFlags flags ) + + + copy + copy-3 + ( const QRect & rect, Qt::ImageConversionFlags flags ) + + + create + create + ( int width, int height, int depth, int numColors = 0, Endian bitOrder = IgnoreEndian ) + + + create + create-2 + ( const QSize & size, int depth, int numColors = 0, Endian bitOrder = IgnoreEndian ) + + + hasAlphaBuffer + hasAlphaBuffer + () + + + invertPixels + invertPixels-2 + ( bool invertAlpha ) + + + jumpTable + jumpTable + () + + + jumpTable + jumpTable-2 + () + + + mirror + mirror + ( bool horizontal = false, bool vertical = true ) + + + reset + reset + () + + + scaleHeight + scaleHeight + ( int h ) + + + scaleWidth + scaleWidth + ( int w ) + + + setAlphaBuffer + setAlphaBuffer + ( bool enable ) + + + smoothScale + smoothScale + ( int width, int height, Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio ) + + + smoothScale + smoothScale-2 + ( const QSize & size, Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio ) + + + swapRGB + swapRGB + () + + + systemBitOrder + systemBitOrder + () + + + systemByteOrder + systemByteOrder + () + + + xForm + xForm + ( const QMatrix & matrix ) + + + ImageConversionFlags + bitBlt + flags = Qt::AutoColor ) + + + + QImage + qimage.html + + DataPtr + DataPtr-typedef + + + + Format + Format-enum + + + + InvertMode + InvertMode-enum + + + + QImage + QImage + () + + + QImage + QImage-2 + ( const QSize & size, Format format ) + + + QImage + QImage-3 + ( int width, int height, Format format ) + + + QImage + QImage-4 + ( uchar * data, int width, int height, Format format ) + + + QImage + QImage-5 + ( const uchar * data, int width, int height, Format format ) + + + QImage + QImage-6 + ( uchar * data, int width, int height, int bytesPerLine, Format format ) + + + QImage + QImage-7 + ( const uchar * data, int width, int height, int bytesPerLine, Format format ) + + + QImage + QImage-8 + ( const char * const[] xpm ) + + + QImage + QImage-9 + ( const QString & fileName, const char * format = 0 ) + + + QImage + QImage-10 + ( const char * fileName, const char * format = 0 ) + + + QImage + QImage-11 + ( const QImage & image ) + + + allGray + allGray + () + + + alphaChannel + alphaChannel + () + + + bits + bits + () + + + bits + bits-2 + () + + + bytesPerLine + bytesPerLine + () + + + cacheKey + cacheKey + () + + + color + color + ( int i ) + + + colorTable + colorTable + () + + + convertToFormat + convertToFormat + ( Format format, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + convertToFormat + convertToFormat-2 + ( Format format, const QVector<QRgb> & colorTable, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + copy + copy + ( const QRect & rectangle = QRect() + + + copy + copy-4 + ( int x, int y, int width, int height ) + + + createAlphaMask + createAlphaMask + ( Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + createHeuristicMask + createHeuristicMask + ( bool clipTight = true ) + + + createMaskFromColor + createMaskFromColor + ( QRgb color, Qt::MaskMode mode = Qt::MaskInColor ) + + + data_ptr + data_ptr + () + + + depth + depth + () + + + dotsPerMeterX + dotsPerMeterX + () + + + dotsPerMeterY + dotsPerMeterY + () + + + fill + fill + ( uint pixelValue ) + + + format + format + () + + + fromData + fromData + ( const uchar * data, int size, const char * format = 0 ) + + + fromData + fromData-2 + ( const QByteArray & data, const char * format = 0 ) + + + hasAlphaChannel + hasAlphaChannel + () + + + height + height + () + + + invertPixels + invertPixels + ( InvertMode mode = InvertRgb ) + + + isGrayscale + isGrayscale + () + + + isNull + isNull + () + + + load + load + ( const QString & fileName, const char * format = 0 ) + + + load + load-2 + ( QIODevice * device, const char * format ) + + + loadFromData + loadFromData + ( const uchar * data, int len, const char * format = 0 ) + + + loadFromData + loadFromData-2 + ( const QByteArray & data, const char * format = 0 ) + + + mirrored + mirrored + ( bool horizontal = false, bool vertical = true ) + + + numBytes + numBytes + () + + + numColors + numColors + () + + + offset + offset + () + + + pixel + pixel + ( const QPoint & position ) + + + pixel + pixel-2 + ( int x, int y ) + + + pixelIndex + pixelIndex + ( const QPoint & position ) + + + pixelIndex + pixelIndex-2 + ( int x, int y ) + + + rect + rect + () + + + rgbSwapped + rgbSwapped + () + + + save + save + ( const QString & fileName, const char * format = 0, int quality = -1 ) + + + save + save-2 + ( QIODevice * device, const char * format = 0, int quality = -1 ) + + + scaled + scaled + ( const QSize & size, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation ) + + + scaled + scaled-2 + ( int width, int height, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation ) + + + scaledToHeight + scaledToHeight + ( int height, Qt::TransformationMode mode = Qt::FastTransformation ) + + + scaledToWidth + scaledToWidth + ( int width, Qt::TransformationMode mode = Qt::FastTransformation ) + + + scanLine + scanLine + ( int i ) + + + scanLine + scanLine-2 + ( int i ) + + + setAlphaChannel + setAlphaChannel + ( const QImage & alphaChannel ) + + + setColor + setColor + ( int index, QRgb colorValue ) + + + setColorTable + setColorTable + ( const QVector<QRgb> colors ) + + + setDotsPerMeterX + setDotsPerMeterX + ( int x ) + + + setDotsPerMeterY + setDotsPerMeterY + ( int y ) + + + setNumColors + setNumColors + ( int numColors ) + + + setOffset + setOffset + ( const QPoint & offset ) + + + setPixel + setPixel + ( const QPoint & position, uint index_or_rgb ) + + + setPixel + setPixel-2 + ( int x, int y, uint index_or_rgb ) + + + setText + setText + ( const QString & key, const QString & text ) + + + size + size + () + + + text + text + ( const QString & key = QString() + + + textKeys + textKeys + () + + + transformed + transformed + ( const QMatrix & matrix, Qt::TransformationMode mode = Qt::FastTransformation ) + + + transformed + transformed-2 + ( const QTransform & matrix, Qt::TransformationMode mode = Qt::FastTransformation ) + + + trueMatrix + trueMatrix + ( const QMatrix & matrix, int width, int height ) + + + trueMatrix + trueMatrix-2 + ( const QTransform &, int w, int h ) + + + valid + valid + ( const QPoint & pos ) + + + valid + valid-2 + ( int x, int y ) + + + width + width + () + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QImage & image ) + + + operator= + operator-eq + ( const QImage & image ) + + + operator== + operator-eq-eq + ( const QImage & image ) + + + Endian + Endian-enum + + + + QImage + QImage-12 + ( int width, int height, int depth, int numColors = 0, Endian bitOrder = IgnoreEndian ) + + + QImage + QImage-13 + ( const QSize & size, int depth, int numColors = 0, Endian bitOrder = IgnoreEndian ) + + + QImage + QImage-14 + ( uchar * data, int width, int height, int depth, const QRgb * colortable, int numColors, Endian bitOrder ) + + + QImage + QImage-15 + ( uchar * data, int width, int height, int depth, int bytesPerLine, const QRgb * colortable, int numColors, Endian bitOrder ) + + + QImage + QImage-16 + ( const QByteArray & data ) + + + bitOrder + bitOrder + () + + + convertBitOrder + convertBitOrder + ( Endian bitOrder ) + + + convertDepth + convertDepth + ( int depth, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + convertDepthWithPalette + convertDepthWithPalette + ( int depth, QRgb * palette, int palette_count, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + copy + copy-2 + ( int x, int y, int w, int h, Qt::ImageConversionFlags flags ) + + + copy + copy-3 + ( const QRect & rect, Qt::ImageConversionFlags flags ) + + + create + create + ( int width, int height, int depth, int numColors = 0, Endian bitOrder = IgnoreEndian ) + + + create + create-2 + ( const QSize & size, int depth, int numColors = 0, Endian bitOrder = IgnoreEndian ) + + + hasAlphaBuffer + hasAlphaBuffer + () + + + invertPixels + invertPixels-2 + ( bool invertAlpha ) + + + jumpTable + jumpTable + () + + + jumpTable + jumpTable-2 + () + + + mirror + mirror + ( bool horizontal = false, bool vertical = true ) + + + reset + reset + () + + + scaleHeight + scaleHeight + ( int h ) + + + scaleWidth + scaleWidth + ( int w ) + + + setAlphaBuffer + setAlphaBuffer + ( bool enable ) + + + smoothScale + smoothScale + ( int width, int height, Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio ) + + + smoothScale + smoothScale-2 + ( const QSize & size, Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio ) + + + swapRGB + swapRGB + () + + + systemBitOrder + systemBitOrder + () + + + systemByteOrder + systemByteOrder + () + + + xForm + xForm + ( const QMatrix & matrix ) + + + ImageConversionFlags + bitBlt + flags = Qt::AutoColor ) + + + name + name + () + + + + QImageIOHandler + qimageiohandler.html + + ImageOption + ImageOption-enum + + + + QImageIOHandler + QImageIOHandler + () + + + canRead + canRead + () + + + currentImageNumber + currentImageNumber + () + + + currentImageRect + currentImageRect + () + + + device + device + () + + + format + format + () + + + imageCount + imageCount + () + + + jumpToImage + jumpToImage + ( int imageNumber ) + + + jumpToNextImage + jumpToNextImage + () + + + loopCount + loopCount + () + + + nextImageDelay + nextImageDelay + () + + + option + option + ( ImageOption option ) + + + read + read + ( QImage * image ) + + + setDevice + setDevice + ( QIODevice * device ) + + + setFormat + setFormat + ( const QByteArray & format ) + + + setFormat + setFormat-2 + ( const QByteArray & format ) + + + setOption + setOption + ( ImageOption option, const QVariant & value ) + + + supportsOption + supportsOption + ( ImageOption option ) + + + write + write + ( const QImage & image ) + + + + QImageIOPlugin + qimageioplugin.html + + QImageIOPlugin + QImageIOPlugin + ( QObject * parent = 0 ) + + + capabilities + capabilities + ( QIODevice * device, const QByteArray & format ) + + + create + create + ( QIODevice * device, const QByteArray & format = QByteArray() + + + keys + keys + () + + + + QImageReader + qimagereader.html + + ImageReaderError + ImageReaderError-enum + + + + QImageReader + QImageReader + () + + + QImageReader + QImageReader-2 + ( QIODevice * device, const QByteArray & format = QByteArray() + + + QImageReader + QImageReader-3 + ( const QString & fileName, const QByteArray & format = QByteArray() + + + backgroundColor + backgroundColor + () + + + canRead + canRead + () + + + clipRect + clipRect + () + + + currentImageNumber + currentImageNumber + () + + + currentImageRect + currentImageRect + () + + + device + device + () + + + error + error + () + + + errorString + errorString + () + + + fileName + fileName + () + + + format + format + () + + + imageCount + imageCount + () + + + imageFormat + imageFormat + ( const QString & fileName ) + + + imageFormat + imageFormat-2 + ( QIODevice * device ) + + + jumpToImage + jumpToImage + ( int imageNumber ) + + + jumpToNextImage + jumpToNextImage + () + + + loopCount + loopCount + () + + + nextImageDelay + nextImageDelay + () + + + quality + quality + () + + + read + read + () + + + read + read-2 + ( QImage * image ) + + + scaledClipRect + scaledClipRect + () + + + scaledSize + scaledSize + () + + + setBackgroundColor + setBackgroundColor + ( const QColor & color ) + + + setClipRect + setClipRect + ( const QRect & rect ) + + + setDevice + setDevice + ( QIODevice * device ) + + + setFileName + setFileName + ( const QString & fileName ) + + + setFormat + setFormat + ( const QByteArray & format ) + + + setQuality + setQuality + ( int quality ) + + + setScaledClipRect + setScaledClipRect + ( const QRect & rect ) + + + setScaledSize + setScaledSize + ( const QSize & size ) + + + size + size + () + + + supportedImageFormats + supportedImageFormats + () + + + supportsAnimation + supportsAnimation + () + + + supportsOption + supportsOption + ( QImageIOHandler::ImageOption option ) + + + text + text + ( const QString & key ) + + + textKeys + textKeys + () + + + description + description + () + + + setDescription + setDescription + ( const QString & description ) + + + + QImageWriter + qimagewriter.html + + ImageWriterError + ImageWriterError-enum + + + + QImageWriter + QImageWriter + () + + + QImageWriter + QImageWriter-2 + ( QIODevice * device, const QByteArray & format ) + + + QImageWriter + QImageWriter-3 + ( const QString & fileName, const QByteArray & format = QByteArray() + + + canWrite + canWrite + () + + + compression + compression + () + + + device + device + () + + + error + error + () + + + errorString + errorString + () + + + fileName + fileName + () + + + format + format + () + + + gamma + gamma + () + + + quality + quality + () + + + setCompression + setCompression + ( int compression ) + + + setDevice + setDevice + ( QIODevice * device ) + + + setFileName + setFileName + ( const QString & fileName ) + + + setFormat + setFormat + ( const QByteArray & format ) + + + setGamma + setGamma + ( float gamma ) + + + setQuality + setQuality + ( int quality ) + + + setText + setText + ( const QString & key, const QString & text ) + + + supportedImageFormats + supportedImageFormats + () + + + supportsOption + supportsOption + ( QImageIOHandler::ImageOption option ) + + + write + write + ( const QImage & image ) + + + + QInputContext + qinputcontext.html + + StandardFormat + StandardFormat-enum + + + + QInputContext + QInputContext + ( QObject * parent = 0 ) + + + actions + actions + () + + + filterEvent + filterEvent + ( const QEvent * event ) + + + font + font + () + + + identifierName + identifierName + () + + + isComposing + isComposing + () + + + language + language + () + + + mouseHandler + mouseHandler + ( int x, QMouseEvent * event ) + + + reset + reset + () + + + sendEvent + sendEvent + ( const QInputMethodEvent & event ) + + + standardFormat + standardFormat + ( StandardFormat s ) + + + update + update + () + + + widgetDestroyed + widgetDestroyed + ( QWidget * widget ) + + + x11FilterEvent + x11FilterEvent + ( QWidget * keywidget, XEvent * event ) + + + + QInputContextFactory + qinputcontextfactory.html + + create + create + ( const QString & key, QObject * parent ) + + + description + description + ( const QString & key ) + + + displayName + displayName + ( const QString & key ) + + + keys + keys + () + + + languages + languages + ( const QString & key ) + + + + QInputContextPlugin + qinputcontextplugin.html + + QInputContextPlugin + QInputContextPlugin + ( QObject * parent = 0 ) + + + create + create + ( const QString & key ) + + + description + description + ( const QString & key ) + + + displayName + displayName + ( const QString & key ) + + + keys + keys + () + + + languages + languages + ( const QString & key ) + + + getDouble + getDouble-2 + ( const QString & title, const QString & label, double value = 0, double minValue = -2147483647, double maxValue = 2147483647, int decimals = 1, bool * ok = 0, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + getInteger + getInteger-2 + ( const QString & title, const QString & label, int value = 0, int minValue = -2147483647, int maxValue = 2147483647, int step = 1, bool * ok = 0, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + getItem + getItem-2 + ( const QString & title, const QString & label, const QStringList & list, int current = 0, bool editable = true, bool * ok = 0, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + getText + getText-2 + ( const QString & title, const QString & label, QLineEdit::EchoMode echo = QLineEdit::Normal, const QString & text = QString() + + + + QInputDialog + qinputdialog.html + + getDouble + getDouble + ( QWidget * parent, const QString & title, const QString & label, double value = 0, double minValue = -2147483647, double maxValue = 2147483647, int decimals = 1, bool * ok = 0, Qt::WindowFlags f = 0 ) + + + getInteger + getInteger + ( QWidget * parent, const QString & title, const QString & label, int value = 0, int minValue = -2147483647, int maxValue = 2147483647, int step = 1, bool * ok = 0, Qt::WindowFlags f = 0 ) + + + getItem + getItem + ( QWidget * parent, const QString & title, const QString & label, const QStringList & list, int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags f = 0 ) + + + getText + getText + ( QWidget * parent, const QString & title, const QString & label, QLineEdit::EchoMode mode = QLineEdit::Normal, const QString & text = QString() + + + getDouble + getDouble-2 + ( const QString & title, const QString & label, double value = 0, double minValue = -2147483647, double maxValue = 2147483647, int decimals = 1, bool * ok = 0, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + getInteger + getInteger-2 + ( const QString & title, const QString & label, int value = 0, int minValue = -2147483647, int maxValue = 2147483647, int step = 1, bool * ok = 0, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + getItem + getItem-2 + ( const QString & title, const QString & label, const QStringList & list, int current = 0, bool editable = true, bool * ok = 0, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + getText + getText-2 + ( const QString & title, const QString & label, QLineEdit::EchoMode echo = QLineEdit::Normal, const QString & text = QString() + + + + QInputEvent + qinputevent.html + + KeyboardModifiers + modifiers + QInputEvent::modifiers() + + + + QInputMethodEvent::Attribute + qinputmethodevent-attribute.html + + Attribute + Attribute + ( AttributeType type, int start, int length, QVariant value ) + + + + QInputMethodEvent + qinputmethodevent.html + + AttributeType + AttributeType-enum + + + + QInputMethodEvent + QInputMethodEvent + () + + + QInputMethodEvent + QInputMethodEvent-2 + ( const QString & preeditText, const QList<Attribute> & attributes ) + + + QInputMethodEvent + QInputMethodEvent-3 + ( const QInputMethodEvent & other ) + + + attributes + attributes + () + + + commitString + commitString + () + + + preeditString + preeditString + () + + + replacementLength + replacementLength + () + + + replacementStart + replacementStart + () + + + setCommitString + setCommitString + ( const QString & commitString, int replaceFrom = 0, int replaceLength = 0 ) + + + QIntValidator + QIntValidator-3 + ( QObject * parent, const char * name ) + + + QIntValidator + QIntValidator-4 + ( int minimum, int maximum, QObject * parent, const char * name ) + + + + QIntValidator + qintvalidator.html + + QIntValidator + QIntValidator + ( QObject * parent ) + + + QIntValidator + QIntValidator-2 + ( int minimum, int maximum, QObject * parent ) + + + setRange + setRange + ( int bottom, int top ) + + + State + validate + QIntValidator::validate( QString & input, int & pos ) + + + QIntValidator + QIntValidator-3 + ( QObject * parent, const char * name ) + + + QIntValidator + QIntValidator-4 + ( int minimum, int maximum, QObject * parent, const char * name ) + + + Offset + Offset-typedef + + + + Status + Status-typedef + + + + at + at + () + + + at + at-2 + ( Offset offset ) + + + flags + flags + () + + + getch + getch + () + + + isAsynchronous + isAsynchronous + () + + + isBuffered + isBuffered + () + + + isCombinedAccess + isCombinedAccess + () + + + isDirectAccess + isDirectAccess + () + + + isInactive + isInactive + () + + + isRaw + isRaw + () + + + isSequentialAccess + isSequentialAccess + () + + + isSynchronous + isSynchronous + () + + + isTranslated + isTranslated + () + + + mode + mode + () + + + putch + putch + ( int ch ) + + + readBlock + readBlock + ( char * data, quint64 size ) + + + resetStatus + resetStatus + () + + + state + state + () + + + status + status + () + + + ungetch + ungetch + ( int ch ) + + + writeBlock + writeBlock + ( const char * data, quint64 size ) + + + writeBlock + writeBlock-2 + ( const QByteArray & data ) + + + + QIODevice + qiodevice.html + + QIODevice + QIODevice + () + + + QIODevice + QIODevice-2 + ( QObject * parent ) + + + aboutToClose + aboutToClose + () + + + atEnd + atEnd + () + + + bytesAvailable + bytesAvailable + () + + + bytesToWrite + bytesToWrite + () + + + bytesWritten + bytesWritten + ( qint64 bytes ) + + + canReadLine + canReadLine + () + + + close + close + () + + + errorString + errorString + () + + + getChar + getChar + ( char * c ) + + + isOpen + isOpen + () + + + isReadable + isReadable + () + + + isSequential + isSequential + () + + + isTextModeEnabled + isTextModeEnabled + () + + + isWritable + isWritable + () + + + open + open + ( OpenMode mode ) + + + openMode + openMode + () + + + peek + peek + ( char * data, qint64 maxSize ) + + + peek + peek-2 + ( qint64 maxSize ) + + + pos + pos + () + + + putChar + putChar + ( char c ) + + + read + read + ( char * data, qint64 maxSize ) + + + read + read-2 + ( qint64 maxSize ) + + + readAll + readAll + () + + + readData + readData + ( char * data, qint64 maxSize ) + + + readLine + readLine + ( char * data, qint64 maxSize ) + + + readLine + readLine-2 + ( qint64 maxSize = 0 ) + + + readLineData + readLineData + ( char * data, qint64 maxSize ) + + + readyRead + readyRead + () + + + reset + reset + () + + + seek + seek + ( qint64 pos ) + + + setErrorString + setErrorString + ( const QString & str ) + + + setOpenMode + setOpenMode + ( OpenMode openMode ) + + + setTextModeEnabled + setTextModeEnabled + ( bool enabled ) + + + size + size + () + + + ungetChar + ungetChar + ( char c ) + + + waitForBytesWritten + waitForBytesWritten + ( int msecs ) + + + waitForReadyRead + waitForReadyRead + ( int msecs ) + + + write + write + ( const char * data, qint64 maxSize ) + + + write + write-2 + ( const QByteArray & byteArray ) + + + writeData + writeData + ( const char * data, qint64 maxSize ) + + + Offset + Offset-typedef + + + + Status + Status-typedef + + + + at + at + () + + + at + at-2 + ( Offset offset ) + + + flags + flags + () + + + getch + getch + () + + + isAsynchronous + isAsynchronous + () + + + isBuffered + isBuffered + () + + + isCombinedAccess + isCombinedAccess + () + + + isDirectAccess + isDirectAccess + () + + + isInactive + isInactive + () + + + isRaw + isRaw + () + + + isSequentialAccess + isSequentialAccess + () + + + isSynchronous + isSynchronous + () + + + isTranslated + isTranslated + () + + + mode + mode + () + + + putch + putch + ( int ch ) + + + readBlock + readBlock + ( char * data, quint64 size ) + + + resetStatus + resetStatus + () + + + state + state + () + + + status + status + () + + + ungetch + ungetch + ( int ch ) + + + writeBlock + writeBlock + ( const char * data, quint64 size ) + + + writeBlock + writeBlock-2 + ( const QByteArray & data ) + + + + QItemDelegate + qitemdelegate.html + + QItemDelegate + QItemDelegate + ( QObject * parent = 0 ) + + + createEditor + createEditor + ( QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index ) + + + drawBackground + drawBackground + ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) + + + drawCheck + drawCheck + ( QPainter * painter, const QStyleOptionViewItem & option, const QRect & rect, Qt::CheckState state ) + + + drawDecoration + drawDecoration + ( QPainter * painter, const QStyleOptionViewItem & option, const QRect & rect, const QPixmap & pixmap ) + + + drawDisplay + drawDisplay + ( QPainter * painter, const QStyleOptionViewItem & option, const QRect & rect, const QString & text ) + + + drawFocus + drawFocus + ( QPainter * painter, const QStyleOptionViewItem & option, const QRect & rect ) + + + eventFilter + eventFilter + ( QObject * editor, QEvent * event ) + + + itemEditorFactory + itemEditorFactory + () + + + paint + paint + ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) + + + setEditorData + setEditorData + ( QWidget * editor, const QModelIndex & index ) + + + setItemEditorFactory + setItemEditorFactory + ( QItemEditorFactory * factory ) + + + setModelData + setModelData + ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) + + + sizeHint + sizeHint + ( const QStyleOptionViewItem & option, const QModelIndex & index ) + + + updateEditorGeometry + updateEditorGeometry + ( QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index ) + + + + QItemEditorCreator + qitemeditorcreator.html + + QItemEditorCreator + QItemEditorCreator + ( const QByteArray & valuePropertyName ) + + + + QItemEditorCreatorBase + qitemeditorcreatorbase.html + + createWidget + createWidget + ( QWidget * parent ) + + + valuePropertyName + valuePropertyName + () + + + + QItemEditorFactory + qitemeditorfactory.html + + QItemEditorFactory + QItemEditorFactory + () + + + createEditor + createEditor + ( QVariant::Type type, QWidget * parent ) + + + defaultFactory + defaultFactory + () + + + registerEditor + registerEditor + ( QVariant::Type type, QItemEditorCreatorBase * creator ) + + + setDefaultFactory + setDefaultFactory + ( QItemEditorFactory * factory ) + + + valuePropertyName + valuePropertyName + ( QVariant::Type type ) + + + + QItemSelection + qitemselection.html + + QItemSelection + QItemSelection + () + + + QItemSelection + QItemSelection-2 + ( const QModelIndex & topLeft, const QModelIndex & bottomRight ) + + + contains + contains + ( const QModelIndex & index ) + + + indexes + indexes + () + + + merge + merge + ( const QItemSelection & other, QItemSelectionModel::SelectionFlags command ) + + + select + select + ( const QModelIndex & topLeft, const QModelIndex & bottomRight ) + + + split + split + ( const QItemSelectionRange & range, const QItemSelectionRange & other, QItemSelection * result ) + + + + QItemSelectionModel + qitemselectionmodel.html + + QItemSelectionModel + QItemSelectionModel + ( QAbstractItemModel * model ) + + + QItemSelectionModel + QItemSelectionModel-2 + ( QAbstractItemModel * model, QObject * parent ) + + + clear + clear + () + + + clearSelection + clearSelection + () + + + columnIntersectsSelection + columnIntersectsSelection + ( int column, const QModelIndex & parent ) + + + currentChanged + currentChanged + ( const QModelIndex & current, const QModelIndex & previous ) + + + currentColumnChanged + currentColumnChanged + ( const QModelIndex & current, const QModelIndex & previous ) + + + currentIndex + currentIndex + () + + + currentRowChanged + currentRowChanged + ( const QModelIndex & current, const QModelIndex & previous ) + + + emitSelectionChanged + emitSelectionChanged + ( const QItemSelection & newSelection, const QItemSelection & oldSelection ) + + + hasSelection + hasSelection + () + + + isColumnSelected + isColumnSelected + ( int column, const QModelIndex & parent ) + + + isRowSelected + isRowSelected + ( int row, const QModelIndex & parent ) + + + isSelected + isSelected + ( const QModelIndex & index ) + + + model + model + () + + + reset + reset + () + + + rowIntersectsSelection + rowIntersectsSelection + ( int row, const QModelIndex & parent ) + + + select + select + ( const QModelIndex & index, QItemSelectionModel::SelectionFlags command ) + + + select + select-2 + ( const QItemSelection & selection, QItemSelectionModel::SelectionFlags command ) + + + selectedColumns + selectedColumns + ( int row = 0 ) + + + selectedIndexes + selectedIndexes + () + + + selectedRows + selectedRows + ( int column = 0 ) + + + selection + selection + () + + + selectionChanged + selectionChanged + ( const QItemSelection & selected, const QItemSelection & deselected ) + + + setCurrentIndex + setCurrentIndex + ( const QModelIndex & index, QItemSelectionModel::SelectionFlags command ) + + + intersect + intersect + ( const QItemSelectionRange & other ) + + + + QItemSelectionRange + qitemselectionrange.html + + QItemSelectionRange + QItemSelectionRange + () + + + QItemSelectionRange + QItemSelectionRange-2 + ( const QItemSelectionRange & other ) + + + QItemSelectionRange + QItemSelectionRange-3 + ( const QModelIndex & topLeft, const QModelIndex & bottomRight ) + + + QItemSelectionRange + QItemSelectionRange-4 + ( const QModelIndex & index ) + + + bottom + bottom + () + + + bottomRight + bottomRight + () + + + contains + contains + ( const QModelIndex & index ) + + + contains + contains-2 + ( int row, int column, const QModelIndex & parentIndex ) + + + height + height + () + + + indexes + indexes + () + + + intersected + intersected + ( const QItemSelectionRange & other ) + + + intersects + intersects + ( const QItemSelectionRange & other ) + + + isValid + isValid + () + + + left + left + () + + + model + model + () + + + parent + parent + () + + + right + right + () + + + top + top + () + + + topLeft + topLeft + () + + + width + width + () + + + operator!= + operator-not-eq + ( const QItemSelectionRange & other ) + + + operator== + operator-eq-eq + ( const QItemSelectionRange & other ) + + + + QKbdDriverFactory + qkbddriverfactory.html + + create + create + ( const QString & key, const QString & device ) + + + keys + keys + () + + + + QKbdDriverPlugin + qkbddriverplugin.html + + QKbdDriverPlugin + QKbdDriverPlugin + ( QObject * parent = 0 ) + + + create + create + ( const QString & key, const QString & device ) + + + keys + keys + () + + + QKeyEvent + QKeyEvent-2 + ( Type type, int key, int ascii, int modifiers, const QString & text = QString() + + + ascii + ascii + () + + + ButtonState + state + QKeyEvent::state() + + + ButtonState + stateAfter + QKeyEvent::stateAfter() + + + + QKeyEvent + qkeyevent.html + + QKeyEvent + QKeyEvent + ( Type type, int key, Qt::KeyboardModifiers modifiers, const QString & text = QString() + + + count + count + () + + + isAutoRepeat + isAutoRepeat + () + + + key + key + () + + + matches + matches + ( QKeySequence::StandardKey key ) + + + KeyboardModifiers + modifiers + QKeyEvent::modifiers() + + + nativeModifiers + nativeModifiers + () + + + nativeScanCode + nativeScanCode + () + + + nativeVirtualKey + nativeVirtualKey + () + + + text + text + () + + + StandardKey + operator-eq-eq-46 + key ) + + + StandardKey + operator-eq-eq-47 + key, QKeyEvent * e ) + + + QKeyEvent + QKeyEvent-2 + ( Type type, int key, int ascii, int modifiers, const QString & text = QString() + + + ascii + ascii + () + + + ButtonState + state + QKeyEvent::state() + + + ButtonState + stateAfter + QKeyEvent::stateAfter() + + + operator + operator-QString + QString() + + + operator + operator-int + int() + + + + QKeySequence + qkeysequence.html + + DataPtr + DataPtr-typedef + + + + SequenceFormat + SequenceFormat-enum + + + + SequenceMatch + SequenceMatch-enum + + + + StandardKey + StandardKey-enum + + + + QKeySequence + QKeySequence + () + + + QKeySequence + QKeySequence-2 + ( const QString & key ) + + + QKeySequence + QKeySequence-3 + ( int k1, int k2 = 0, int k3 = 0, int k4 = 0 ) + + + QKeySequence + QKeySequence-4 + ( const QKeySequence & keysequence ) + + + QKeySequence + QKeySequence-5 + ( StandardKey key ) + + + count + count + () + + + data_ptr + data_ptr + () + + + fromString + fromString + ( const QString & str, SequenceFormat format = PortableText ) + + + isEmpty + isEmpty + () + + + keyBindings + keyBindings + ( StandardKey key ) + + + matches + matches + ( const QKeySequence & seq ) + + + mnemonic + mnemonic + ( const QString & text ) + + + toString + toString + ( SequenceFormat format = PortableText ) + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QKeySequence & other ) + + + operator< + operator-lt + ( const QKeySequence & other ) + + + operator<= + operator-lt-eq + ( const QKeySequence & other ) + + + operator= + operator-eq + ( const QKeySequence & other ) + + + operator== + operator-eq-eq + ( const QKeySequence & other ) + + + operator> + operator-gt + ( const QKeySequence & other ) + + + operator>= + operator-gt-eq + ( const QKeySequence & other ) + + + operator[] + operator-5b-5d + ( uint index ) + + + QLabel + QLabel-3 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + QLabel + QLabel-4 + ( const QString & text, QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + QLabel + QLabel-5 + ( QWidget * buddy, const QString & text, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + setAlignment + setAlignment-2 + ( int alignment ) + + + + QLabel + qlabel.html + + Alignment + alignment-prop + + + + TextFormat + textFormat-prop + + + + TextInteractionFlags + textInteractionFlags-prop + + + + QLabel + QLabel + ( QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + QLabel + QLabel-2 + ( const QString & text, QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + buddy + buddy + () + + + clear + clear + () + + + linkActivated + linkActivated + ( const QString & link ) + + + linkHovered + linkHovered + ( const QString & link ) + + + movie + movie + () + + + picture + picture + () + + + setBuddy + setBuddy + ( QWidget * buddy ) + + + setMovie + setMovie + ( QMovie * movie ) + + + setNum + setNum + ( int num ) + + + setNum + setNum-2 + ( double num ) + + + setPicture + setPicture + ( const QPicture & picture ) + + + QLabel + QLabel-3 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + QLabel + QLabel-4 + ( const QString & text, QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + QLabel + QLabel-5 + ( QWidget * buddy, const QString & text, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 ) + + + setAlignment + setAlignment-2 + ( int alignment ) + + + + QLatin1Char + qlatin1char.html + + QLatin1Char + QLatin1Char + ( char c ) + + + toLatin1 + toLatin1 + () + + + unicode + unicode + () + + + + QLatin1String + qlatin1string.html + + QLatin1String + QLatin1String + ( const char * str ) + + + latin1 + latin1 + () + + + operator!= + operator-not-eq + ( const QString & other ) + + + operator!= + operator-not-eq-2 + ( const char * other ) + + + operator< + operator-lt + ( const QString & other ) + + + operator< + operator-lt-2 + ( const char * other ) + + + operator<= + operator-lt-eq + ( const QString & other ) + + + operator<= + operator-lt-eq-2 + ( const char * other ) + + + operator= + operator-eq + ( const QLatin1String & other ) + + + operator== + operator-eq-eq + ( const QString & other ) + + + operator== + operator-eq-eq-2 + ( const char * other ) + + + operator> + operator-gt + ( const QString & other ) + + + operator> + operator-gt-2 + ( const char * other ) + + + operator>= + operator-gt-eq + ( const QString & other ) + + + operator>= + operator-gt-eq-2 + ( const char * other ) + + + QLayout + QLayout-4 + ( QWidget * parent, int margin, int spacing = -1, const char * name = 0 ) + + + QLayout + QLayout-5 + ( QLayout * parentLayout, int spacing = -1, const char * name = 0 ) + + + QLayout + QLayout-6 + ( int spacing, const char * name = 0 ) + + + add + add + ( QWidget * widget ) + + + autoAdd + autoAdd + () + + + defaultBorder + defaultBorder + () + + + deleteAllItems + deleteAllItems + () + + + freeze + freeze + ( int w = 0, int h = 0 ) + + + isTopLevel + isTopLevel + () + + + iterator + iterator + () + + + mainWidget + mainWidget + () + + + remove + remove + ( QWidget * widget ) + + + resizeMode + resizeMode + () + + + setAutoAdd + setAutoAdd + ( bool a ) + + + setResizeMode + setResizeMode + ( SizeConstraint constraint ) + + + + QLayout + qlayout.html + + SizeConstraint + SizeConstraint-enum + + + + QLayout + QLayout + ( QWidget * parent ) + + + QLayout + QLayout-2 + () + + + activate + activate + () + + + addChildLayout + addChildLayout + ( QLayout * l ) + + + addChildWidget + addChildWidget + ( QWidget * w ) + + + addItem + addItem + ( QLayoutItem * item ) + + + addWidget + addWidget + ( QWidget * w ) + + + alignmentRect + alignmentRect + ( const QRect & r ) + + + closestAcceptableSize + closestAcceptableSize + ( const QWidget * widget, const QSize & size ) + + + contentsRect + contentsRect + () + + + count + count + () + + + Orientations + expandingDirections + QLayout::expandingDirections() + + + getContentsMargins + getContentsMargins + ( int * left, int * top, int * right, int * bottom ) + + + indexOf + indexOf + ( QWidget * widget ) + + + isEnabled + isEnabled + () + + + itemAt + itemAt + ( int index ) + + + maximumSize + maximumSize + () + + + menuBar + menuBar + () + + + minimumSize + minimumSize + () + + + parentWidget + parentWidget + () + + + removeItem + removeItem + ( QLayoutItem * item ) + + + removeWidget + removeWidget + ( QWidget * widget ) + + + setAlignment + setAlignment + ( QWidget * w, Qt::Alignment alignment ) + + + setAlignment + setAlignment-2 + ( QLayout * l, Qt::Alignment alignment ) + + + setContentsMargins + setContentsMargins + ( int left, int top, int right, int bottom ) + + + setEnabled + setEnabled + ( bool enable ) + + + setMenuBar + setMenuBar + ( QWidget * widget ) + + + takeAt + takeAt + ( int index ) + + + update + update + () + + + QLayout + QLayout-4 + ( QWidget * parent, int margin, int spacing = -1, const char * name = 0 ) + + + QLayout + QLayout-5 + ( QLayout * parentLayout, int spacing = -1, const char * name = 0 ) + + + QLayout + QLayout-6 + ( int spacing, const char * name = 0 ) + + + add + add + ( QWidget * widget ) + + + autoAdd + autoAdd + () + + + defaultBorder + defaultBorder + () + + + deleteAllItems + deleteAllItems + () + + + freeze + freeze + ( int w = 0, int h = 0 ) + + + isTopLevel + isTopLevel + () + + + iterator + iterator + () + + + mainWidget + mainWidget + () + + + remove + remove + ( QWidget * widget ) + + + resizeMode + resizeMode + () + + + setAutoAdd + setAutoAdd + ( bool a ) + + + setResizeMode + setResizeMode + ( SizeConstraint constraint ) + + + + QLayoutItem + qlayoutitem.html + + QLayoutItem + QLayoutItem + ( Qt::Alignment alignment = 0 ) + + + Alignment + alignment + QLayoutItem::alignment() + + + ControlTypes + controlTypes + QLayoutItem::controlTypes() + + + Orientations + expandingDirections + QLayoutItem::expandingDirections() + + + geometry + geometry + () + + + hasHeightForWidth + hasHeightForWidth + () + + + heightForWidth + heightForWidth + ( int w ) + + + invalidate + invalidate + () + + + isEmpty + isEmpty + () + + + layout + layout + () + + + maximumSize + maximumSize + () + + + minimumHeightForWidth + minimumHeightForWidth + ( int w ) + + + minimumSize + minimumSize + () + + + setAlignment + setAlignment + ( Qt::Alignment alignment ) + + + setGeometry + setGeometry + ( const QRect & r ) + + + sizeHint + sizeHint + () + + + spacerItem + spacerItem + () + + + widget + widget + () + + + QLCDNumber + QLCDNumber-3 + ( QWidget * parent, const char * name ) + + + QLCDNumber + QLCDNumber-4 + ( uint numDigits, QWidget * parent, const char * name ) + + + margin + margin + () + + + setMargin + setMargin + ( int margin ) + + + + QLCDNumber + qlcdnumber.html + + Mode + Mode-enum + + + + SegmentStyle + SegmentStyle-enum + + + + QLCDNumber + QLCDNumber + ( QWidget * parent = 0 ) + + + QLCDNumber + QLCDNumber-2 + ( uint numDigits, QWidget * parent = 0 ) + + + checkOverflow + checkOverflow + ( double num ) + + + checkOverflow + checkOverflow-2 + ( int num ) + + + overflow + overflow + () + + + setBinMode + setBinMode + () + + + setDecMode + setDecMode + () + + + setHexMode + setHexMode + () + + + setOctMode + setOctMode + () + + + QLCDNumber + QLCDNumber-3 + ( QWidget * parent, const char * name ) + + + QLCDNumber + QLCDNumber-4 + ( uint numDigits, QWidget * parent, const char * name ) + + + margin + margin + () + + + setMargin + setMargin + ( int margin ) + + + library + library + () + + + setAutoUnload + setAutoUnload + ( bool b ) + + + + QLibrary + qlibrary.html + + QLibrary + QLibrary + ( QObject * parent = 0 ) + + + QLibrary + QLibrary-2 + ( const QString & fileName, QObject * parent = 0 ) + + + QLibrary + QLibrary-3 + ( const QString & fileName, int verNum, QObject * parent = 0 ) + + + errorString + errorString + () + + + isLibrary + isLibrary + ( const QString & fileName ) + + + isLoaded + isLoaded + () + + + load + load + () + + + resolve + resolve + ( const char * symbol ) + + + resolve + resolve-2 + ( const QString & fileName, const char * symbol ) + + + resolve + resolve-3 + ( const QString & fileName, int verNum, const char * symbol ) + + + setFileNameAndVersion + setFileNameAndVersion + ( const QString & fileName, int versionNumber ) + + + unload + unload + () + + + library + library + () + + + setAutoUnload + setAutoUnload + ( bool b ) + + + + QLibraryInfo + qlibraryinfo.html + + LibraryLocation + LibraryLocation-enum + + + + buildKey + buildKey + () + + + licensedProducts + licensedProducts + () + + + licensee + licensee + () + + + location + location + ( LibraryLocation loc ) + + + + QLine + qline.html + + QLine + QLine + () + + + QLine + QLine-2 + ( const QPoint & p1, const QPoint & p2 ) + + + QLine + QLine-3 + ( int x1, int y1, int x2, int y2 ) + + + p1 + p1 + () + + + p2 + p2 + () + + + x1 + x1 + () + + + x2 + x2 + () + + + y1 + y1 + () + + + y2 + y2 + () + + + dx + dx + () + + + dy + dy + () + + + isNull + isNull + () + + + translate + translate + ( const QPoint & offset ) + + + translate + translate-2 + ( int dx, int dy ) + + + operator!= + operator-not-eq + ( const QLine & line ) + + + operator== + operator-eq-eq + ( const QLine & line ) + + + + QLinearGradient + qlineargradient.html + + QLinearGradient + QLinearGradient + () + + + QLinearGradient + QLinearGradient-2 + ( const QPointF & start, const QPointF & finalStop ) + + + QLinearGradient + QLinearGradient-3 + ( qreal x1, qreal y1, qreal x2, qreal y2 ) + + + finalStop + finalStop + () + + + setFinalStop + setFinalStop + ( const QPointF & stop ) + + + setFinalStop + setFinalStop-2 + ( qreal x, qreal y ) + + + setStart + setStart + ( const QPointF & start ) + + + setStart + setStart-2 + ( qreal x, qreal y ) + + + start + start + () + + + QLineEdit + QLineEdit-3 + ( QWidget * parent, const char * name ) + + + QLineEdit + QLineEdit-4 + ( const QString & contents, QWidget * parent, const char * name ) + + + QLineEdit + QLineEdit-5 + ( const QString & contents, const QString & inputMask, QWidget * parent = 0, const char * name = 0 ) + + + characterAt + characterAt + ( int xpos, QChar * chr ) + + + clearModified + clearModified + () + + + clearValidator + clearValidator + () + + + cursorLeft + cursorLeft + ( bool mark, int steps = 1 ) + + + cursorRight + cursorRight + ( bool mark, int steps = 1 ) + + + edited + edited + () + + + frame + frame + () + + + getSelection + getSelection + ( int * start, int * end ) + + + hasMarkedText + hasMarkedText + () + + + lostFocus + lostFocus + () + + + margin + margin + () + + + markedText + markedText + () + + + repaintArea + repaintArea + ( int a, int b ) + + + setEdited + setEdited + ( bool on ) + + + setMargin + setMargin + ( int margin ) + + + validateAndSet + validateAndSet + ( const QString & newText, int newPos, int newMarkAnchor, int newMarkDrag ) + + + + QLineEdit + qlineedit.html + + EchoMode + EchoMode-enum + + + + Alignment + alignment-prop + + + + QLineEdit + QLineEdit + ( QWidget * parent = 0 ) + + + QLineEdit + QLineEdit-2 + ( const QString & contents, QWidget * parent = 0 ) + + + backspace + backspace + () + + + clear + clear + () + + + completer + completer + () + + + contextMenuEvent + contextMenuEvent + ( QContextMenuEvent * event ) + + + copy + copy + () + + + createStandardContextMenu + createStandardContextMenu + () + + + cursorBackward + cursorBackward + ( bool mark, int steps = 1 ) + + + cursorForward + cursorForward + ( bool mark, int steps = 1 ) + + + cursorPositionAt + cursorPositionAt + ( const QPoint & pos ) + + + cursorPositionChanged + cursorPositionChanged + ( int old, int new ) + + + cursorWordBackward + cursorWordBackward + ( bool mark ) + + + cursorWordForward + cursorWordForward + ( bool mark ) + + + cut + cut + () + + + del + del + () + + + deselect + deselect + () + + + editingFinished + editingFinished + () + + + end + end + ( bool mark ) + + + home + home + ( bool mark ) + + + initStyleOption + initStyleOption + ( QStyleOptionFrame * option ) + + + insert + insert + ( const QString & newText ) + + + keyPressEvent + keyPressEvent + ( QKeyEvent * event ) + + + minimumSizeHint + minimumSizeHint + () + + + paste + paste + () + + + redo + redo + () + + + returnPressed + returnPressed + () + + + selectAll + selectAll + () + + + selectionChanged + selectionChanged + () + + + selectionStart + selectionStart + () + + + setCompleter + setCompleter + ( QCompleter * c ) + + + setSelection + setSelection + ( int start, int length ) + + + setValidator + setValidator + ( const QValidator * v ) + + + sizeHint + sizeHint + () + + + textChanged + textChanged + ( const QString & text ) + + + textEdited + textEdited + ( const QString & text ) + + + undo + undo + () + + + validator + validator + () + + + QLineEdit + QLineEdit-3 + ( QWidget * parent, const char * name ) + + + QLineEdit + QLineEdit-4 + ( const QString & contents, QWidget * parent, const char * name ) + + + QLineEdit + QLineEdit-5 + ( const QString & contents, const QString & inputMask, QWidget * parent = 0, const char * name = 0 ) + + + characterAt + characterAt + ( int xpos, QChar * chr ) + + + clearModified + clearModified + () + + + clearValidator + clearValidator + () + + + cursorLeft + cursorLeft + ( bool mark, int steps = 1 ) + + + cursorRight + cursorRight + ( bool mark, int steps = 1 ) + + + edited + edited + () + + + frame + frame + () + + + getSelection + getSelection + ( int * start, int * end ) + + + hasMarkedText + hasMarkedText + () + + + lostFocus + lostFocus + () + + + margin + margin + () + + + markedText + markedText + () + + + repaintArea + repaintArea + ( int a, int b ) + + + setEdited + setEdited + ( bool on ) + + + setMargin + setMargin + ( int margin ) + + + validateAndSet + validateAndSet + ( const QString & newText, int newPos, int newMarkAnchor, int newMarkDrag ) + + + + QLineF + qlinef.html + + IntersectType + IntersectType-enum + + + + QLineF + QLineF + () + + + QLineF + QLineF-2 + ( const QPointF & p1, const QPointF & p2 ) + + + QLineF + QLineF-3 + ( qreal x1, qreal y1, qreal x2, qreal y2 ) + + + QLineF + QLineF-4 + ( const QLine & line ) + + + p1 + p1 + () + + + p2 + p2 + () + + + x1 + x1 + () + + + x2 + x2 + () + + + y1 + y1 + () + + + y2 + y2 + () + + + angle + angle + ( const QLineF & line ) + + + dx + dx + () + + + dy + dy + () + + + intersect + intersect + ( const QLineF & line, QPointF * intersectionPoint ) + + + isNull + isNull + () + + + length + length + () + + + normalVector + normalVector + () + + + pointAt + pointAt + ( qreal t ) + + + setLength + setLength + ( qreal length ) + + + toLine + toLine + () + + + translate + translate + ( const QPointF & offset ) + + + translate + translate-2 + ( qreal dx, qreal dy ) + + + unitVector + unitVector + () + + + operator!= + operator-not-eq + ( const QLineF & line ) + + + operator== + operator-eq-eq + ( const QLineF & line ) + + + + QLinkedList::const_iterator + qlinkedlist-const-iterator.html + + const_iterator + const_iterator + () + + + const_iterator + const_iterator-3 + ( const const_iterator & other ) + + + const_iterator + const_iterator-4 + ( iterator other ) + + + operator!= + operator-not-eq + ( const const_iterator & other ) + + + operator* + operator-2a + () + + + operator+ + operator-2b + ( int j ) + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator+= + operator-2b-eq + ( int j ) + + + operator- + operator- + ( int j ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator-= + operator--eq + ( int j ) + + + operator-& + operator--gt + gt;() + + + operator= + operator-eq + ( const const_iterator & other ) + + + operator== + operator-eq-eq + ( const const_iterator & other ) + + + + QLinkedList::iterator + qlinkedlist-iterator.html + + iterator + iterator + () + + + iterator + iterator-3 + ( const iterator & other ) + + + operator!= + operator-not-eq + ( const iterator & other ) + + + operator!= + operator-not-eq-2 + ( const const_iterator & other ) + + + operator* + operator-2a + () + + + operator+ + operator-2b + ( int j ) + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator+= + operator-2b-eq + ( int j ) + + + operator- + operator- + ( int j ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator-= + operator--eq + ( int j ) + + + operator-& + operator--gt + gt;() + + + operator= + operator-eq + ( const iterator & other ) + + + operator== + operator-eq-eq + ( const iterator & other ) + + + operator== + operator-eq-eq-2 + ( const const_iterator & other ) + + + find + find + ( iterator from, const T & t ) + + + find + find-2 + ( const T & t ) + + + find + find-3 + ( const_iterator from, const T & t ) + + + find + find-4 + ( const T & t ) + + + findIndex + findIndex + ( const T & t ) + + + remove + remove + ( iterator pos ) + + + + QLinkedList + qlinkedlist.html + + ConstIterator + ConstIterator-typedef + + + + Iterator + Iterator-typedef + + + + const_pointer + const_pointer-typedef + + + + const_reference + const_reference-typedef + + + + difference_type + difference_type-typedef + + + + pointer + pointer-typedef + + + + reference + reference-typedef + + + + size_type + size_type-typedef + + + + value_type + value_type-typedef + + + + QLinkedList + QLinkedList + () + + + QLinkedList + QLinkedList-2 + ( const QLinkedList<T> & other ) + + + append + append + ( const T & value ) + + + back + back + () + + + back + back-2 + () + + + begin + begin + () + + + begin + begin-2 + () + + + clear + clear + () + + + constBegin + constBegin + () + + + constEnd + constEnd + () + + + contains + contains + ( const T & value ) + + + count + count + ( const T & value ) + + + count + count-2 + () + + + empty + empty + () + + + end + end + () + + + end + end-2 + () + + + erase + erase + ( iterator pos ) + + + erase + erase-2 + ( iterator begin, iterator end ) + + + first + first + () + + + first + first-2 + () + + + fromStdList + fromStdList + ( const std::list<T> & list ) + + + front + front + () + + + front + front-2 + () + + + insert + insert + ( iterator before, const T & value ) + + + isEmpty + isEmpty + () + + + last + last + () + + + last + last-2 + () + + + pop_back + pop_back + () + + + pop_front + pop_front + () + + + prepend + prepend + ( const T & value ) + + + push_back + push_back + ( const T & value ) + + + push_front + push_front + ( const T & value ) + + + removeAll + removeAll + ( const T & value ) + + + removeFirst + removeFirst + () + + + removeLast + removeLast + () + + + size + size + () + + + takeFirst + takeFirst + () + + + takeLast + takeLast + () + + + list + toStdList + <T> QLinkedList::toStdList() + + + operator!= + operator-not-eq + ( const QLinkedList<T> & other ) + + + operator+ + operator-2b + ( const QLinkedList<T> & other ) + + + operator+= + operator-2b-eq + ( const QLinkedList<T> & other ) + + + operator+= + operator-2b-eq-2 + ( const T & value ) + + + operator<< + operator-lt-lt + ( const QLinkedList<T> & other ) + + + operator<< + operator-lt-lt-2 + ( const T & value ) + + + operator= + operator-eq + ( const QLinkedList<T> & other ) + + + operator== + operator-eq-eq + ( const QLinkedList<T> & other ) + + + find + find + ( iterator from, const T & t ) + + + find + find-2 + ( const T & t ) + + + find + find-3 + ( const_iterator from, const T & t ) + + + find + find-4 + ( const T & t ) + + + findIndex + findIndex + ( const T & t ) + + + remove + remove + ( iterator pos ) + + + + QLinkedListIterator + qlinkedlistiterator.html + + QLinkedListIterator + QLinkedListIterator + ( const QLinkedList<T> & list ) + + + findNext + findNext + ( const T & value ) + + + findPrevious + findPrevious + ( const T & value ) + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + next + next + () + + + peekNext + peekNext + () + + + peekPrevious + peekPrevious + () + + + previous + previous + () + + + toBack + toBack + () + + + toFront + toFront + () + + + operator= + operator-eq + ( const QLinkedList<T> & list ) + + + + QList::const_iterator + qlist-const-iterator.html + + const_iterator + const_iterator + () + + + const_iterator + const_iterator-3 + ( const const_iterator & other ) + + + const_iterator + const_iterator-4 + ( const iterator & other ) + + + operator!= + operator-not-eq + ( const const_iterator & other ) + + + operator* + operator-2a + () + + + operator+ + operator-2b + ( int j ) + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator+= + operator-2b-eq + ( int j ) + + + operator- + operator- + ( int j ) + + + operator- + operator--2 + ( const_iterator other ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator-= + operator--eq + ( int j ) + + + operator-& + operator--gt + gt;() + + + operator< + operator-lt + ( const const_iterator & other ) + + + operator<= + operator-lt-eq + ( const const_iterator & other ) + + + operator== + operator-eq-eq + ( const const_iterator & other ) + + + operator> + operator-gt + ( const const_iterator & other ) + + + operator>= + operator-gt-eq + ( const const_iterator & other ) + + + operator[] + operator-5b-5d + ( int j ) + + + + QList::iterator + qlist-iterator.html + + iterator + iterator + () + + + iterator + iterator-3 + ( const iterator & other ) + + + operator!= + operator-not-eq + ( const iterator & other ) + + + operator!= + operator-not-eq-2 + ( const const_iterator & other ) + + + operator* + operator-2a + () + + + operator+ + operator-2b + ( int j ) + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator+= + operator-2b-eq + ( int j ) + + + operator- + operator- + ( int j ) + + + operator- + operator--2 + ( iterator other ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator-= + operator--eq + ( int j ) + + + operator-& + operator--gt + gt;() + + + operator< + operator-lt + ( const iterator & other ) + + + operator< + operator-lt-2 + ( const const_iterator & other ) + + + operator<= + operator-lt-eq + ( const iterator & other ) + + + operator<= + operator-lt-eq-2 + ( const const_iterator & other ) + + + operator== + operator-eq-eq + ( const iterator & other ) + + + operator== + operator-eq-eq-2 + ( const const_iterator & other ) + + + operator> + operator-gt + ( const iterator & other ) + + + operator> + operator-gt-2 + ( const const_iterator & other ) + + + operator>= + operator-gt-eq + ( const iterator & other ) + + + operator>= + operator-gt-eq-2 + ( const const_iterator & other ) + + + operator[] + operator-5b-5d + ( int j ) + + + find + find + ( const T & t ) + + + find + find-2 + ( const T & t ) + + + find + find-3 + ( iterator from, const T & t ) + + + find + find-4 + ( const_iterator from, const T & t ) + + + findIndex + findIndex + ( const T & t ) + + + remove + remove + ( iterator pos ) + + + remove + remove-2 + ( const T & t ) + + + + QList + qlist.html + + ConstIterator + ConstIterator-typedef + + + + Iterator + Iterator-typedef + + + + const_pointer + const_pointer-typedef + + + + const_reference + const_reference-typedef + + + + difference_type + difference_type-typedef + + + + pointer + pointer-typedef + + + + reference + reference-typedef + + + + size_type + size_type-typedef + + + + value_type + value_type-typedef + + + + QList + QList + () + + + QList + QList-2 + ( const QList<T> & other ) + + + append + append + ( const T & value ) + + + at + at + ( int i ) + + + back + back + () + + + back + back-2 + () + + + begin + begin + () + + + begin + begin-2 + () + + + clear + clear + () + + + constBegin + constBegin + () + + + constEnd + constEnd + () + + + contains + contains + ( const T & value ) + + + count + count + ( const T & value ) + + + count + count-2 + () + + + empty + empty + () + + + end + end + () + + + end + end-2 + () + + + erase + erase + ( iterator pos ) + + + erase + erase-2 + ( iterator begin, iterator end ) + + + first + first + () + + + first + first-2 + () + + + fromSet + fromSet + ( const QSet<T> & set ) + + + fromStdList + fromStdList + ( const std::list<T> & list ) + + + fromVector + fromVector + ( const QVector<T> & vector ) + + + front + front + () + + + front + front-2 + () + + + indexOf + indexOf + ( const T & value, int from = 0 ) + + + insert + insert + ( int i, const T & value ) + + + insert + insert-2 + ( iterator before, const T & value ) + + + isEmpty + isEmpty + () + + + last + last + () + + + last + last-2 + () + + + lastIndexOf + lastIndexOf + ( const T & value, int from = -1 ) + + + mid + mid + ( int pos, int length = -1 ) + + + move + move + ( int from, int to ) + + + pop_back + pop_back + () + + + pop_front + pop_front + () + + + prepend + prepend + ( const T & value ) + + + push_back + push_back + ( const T & value ) + + + push_front + push_front + ( const T & value ) + + + removeAll + removeAll + ( const T & value ) + + + removeAt + removeAt + ( int i ) + + + removeFirst + removeFirst + () + + + removeLast + removeLast + () + + + replace + replace + ( int i, const T & value ) + + + size + size + () + + + swap + swap + ( int i, int j ) + + + takeAt + takeAt + ( int i ) + + + takeFirst + takeFirst + () + + + takeLast + takeLast + () + + + toSet + toSet + () + + + list + toStdList + <T> QList::toStdList() + + + toVector + toVector + () + + + value + value + ( int i ) + + + value + value-2 + ( int i, const T & defaultValue ) + + + operator!= + operator-not-eq + ( const QList<T> & other ) + + + operator+ + operator-2b + ( const QList<T> & other ) + + + operator+= + operator-2b-eq + ( const QList<T> & other ) + + + operator+= + operator-2b-eq-2 + ( const T & value ) + + + operator<< + operator-lt-lt + ( const QList<T> & other ) + + + operator<< + operator-lt-lt-2 + ( const T & value ) + + + operator= + operator-eq + ( const QList<T> & other ) + + + operator== + operator-eq-eq + ( const QList<T> & other ) + + + operator[] + operator-5b-5d + ( int i ) + + + operator[] + operator-5b-5d-2 + ( int i ) + + + find + find + ( const T & t ) + + + find + find-2 + ( const T & t ) + + + find + find-3 + ( iterator from, const T & t ) + + + find + find-4 + ( const_iterator from, const T & t ) + + + findIndex + findIndex + ( const T & t ) + + + remove + remove + ( iterator pos ) + + + remove + remove-2 + ( const T & t ) + + + + QListIterator + qlistiterator.html + + QListIterator + QListIterator + ( const QList<T> & list ) + + + findNext + findNext + ( const T & value ) + + + findPrevious + findPrevious + ( const T & value ) + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + next + next + () + + + peekNext + peekNext + () + + + peekPrevious + peekPrevious + () + + + previous + previous + () + + + toBack + toBack + () + + + toFront + toFront + () + + + operator= + operator-eq + ( const QList<T> & list ) + + + + QListView + qlistview.html + + Flow + Flow-enum + + + + LayoutMode + LayoutMode-enum + + + + Movement + Movement-enum + + + + ResizeMode + ResizeMode-enum + + + + ViewMode + ViewMode-enum + + + + QListView + QListView + ( QWidget * parent = 0 ) + + + clearPropertyFlags + clearPropertyFlags + () + + + currentChanged + currentChanged + ( const QModelIndex & current, const QModelIndex & previous ) + + + indexesMoved + indexesMoved + ( const QModelIndexList & indexes ) + + + isRowHidden + isRowHidden + ( int row ) + + + rectForIndex + rectForIndex + ( const QModelIndex & index ) + + + selectionChanged + selectionChanged + ( const QItemSelection & selected, const QItemSelection & deselected ) + + + setPositionForIndex + setPositionForIndex + ( const QPoint & position, const QModelIndex & index ) + + + setRowHidden + setRowHidden + ( int row, bool hide ) + + + isItemHidden + isItemHidden + ( const QListWidgetItem * item ) + + + isItemSelected + isItemSelected + ( const QListWidgetItem * item ) + + + setItemHidden + setItemHidden + ( const QListWidgetItem * item, bool hide ) + + + setItemSelected + setItemSelected + ( const QListWidgetItem * item, bool select ) + + + + QListWidget + qlistwidget.html + + QListWidget + QListWidget + ( QWidget * parent = 0 ) + + + addItem + addItem + ( const QString & label ) + + + addItem + addItem-2 + ( QListWidgetItem * item ) + + + addItems + addItems + ( const QStringList & labels ) + + + clear + clear + () + + + closePersistentEditor + closePersistentEditor + ( QListWidgetItem * item ) + + + currentItem + currentItem + () + + + currentItemChanged + currentItemChanged + ( QListWidgetItem * current, QListWidgetItem * previous ) + + + currentRowChanged + currentRowChanged + ( int currentRow ) + + + currentTextChanged + currentTextChanged + ( const QString & currentText ) + + + dropMimeData + dropMimeData + ( int index, const QMimeData * data, Qt::DropAction action ) + + + editItem + editItem + ( QListWidgetItem * item ) + + + findItems + findItems + ( const QString & text, Qt::MatchFlags flags ) + + + indexFromItem + indexFromItem + ( QListWidgetItem * item ) + + + insertItem + insertItem + ( int row, QListWidgetItem * item ) + + + insertItem + insertItem-2 + ( int row, const QString & label ) + + + insertItems + insertItems + ( int row, const QStringList & labels ) + + + item + item + ( int row ) + + + itemActivated + itemActivated + ( QListWidgetItem * item ) + + + itemAt + itemAt + ( const QPoint & p ) + + + itemAt + itemAt-2 + ( int x, int y ) + + + itemChanged + itemChanged + ( QListWidgetItem * item ) + + + itemClicked + itemClicked + ( QListWidgetItem * item ) + + + itemDoubleClicked + itemDoubleClicked + ( QListWidgetItem * item ) + + + itemEntered + itemEntered + ( QListWidgetItem * item ) + + + itemFromIndex + itemFromIndex + ( const QModelIndex & index ) + + + itemPressed + itemPressed + ( QListWidgetItem * item ) + + + itemSelectionChanged + itemSelectionChanged + () + + + itemWidget + itemWidget + ( QListWidgetItem * item ) + + + items + items + ( const QMimeData * data ) + + + mimeData + mimeData + ( const QList<QListWidgetItem *> items ) + + + mimeTypes + mimeTypes + () + + + openPersistentEditor + openPersistentEditor + ( QListWidgetItem * item ) + + + removeItemWidget + removeItemWidget + ( QListWidgetItem * item ) + + + row + row + ( const QListWidgetItem * item ) + + + scrollToItem + scrollToItem + ( const QListWidgetItem * item, QAbstractItemView::ScrollHint hint = EnsureVisible ) + + + selectedItems + selectedItems + () + + + setCurrentItem + setCurrentItem + ( QListWidgetItem * item ) + + + setItemWidget + setItemWidget + ( QListWidgetItem * item, QWidget * widget ) + + + sortItems + sortItems + ( Qt::SortOrder order = Qt::AscendingOrder ) + + + DropActions + supportedDropActions + QListWidget::supportedDropActions() + + + takeItem + takeItem + ( int row ) + + + visualItemRect + visualItemRect + ( const QListWidgetItem * item ) + + + backgroundColor + backgroundColor + () + + + setBackgroundColor + setBackgroundColor + ( const QColor & color ) + + + setTextColor + setTextColor + ( const QColor & color ) + + + textColor + textColor + () + + + + QListWidgetItem + qlistwidgetitem.html + + ItemType + ItemType-enum + + + + QListWidgetItem + QListWidgetItem + ( QListWidget * parent = 0, int type = Type ) + + + QListWidgetItem + QListWidgetItem-2 + ( const QString & text, QListWidget * parent = 0, int type = Type ) + + + QListWidgetItem + QListWidgetItem-3 + ( const QIcon & icon, const QString & text, QListWidget * parent = 0, int type = Type ) + + + QListWidgetItem + QListWidgetItem-4 + ( const QListWidgetItem & other ) + + + background + background + () + + + CheckState + checkState + QListWidgetItem::checkState() + + + clone + clone + () + + + data + data + ( int role ) + + + ItemFlags + flags + QListWidgetItem::flags() + + + font + font + () + + + foreground + foreground + () + + + icon + icon + () + + + isHidden + isHidden + () + + + isSelected + isSelected + () + + + listWidget + listWidget + () + + + read + read + ( QDataStream & in ) + + + setBackground + setBackground + ( const QBrush & brush ) + + + setCheckState + setCheckState + ( Qt::CheckState state ) + + + setData + setData + ( int role, const QVariant & value ) + + + setFlags + setFlags + ( Qt::ItemFlags flags ) + + + setFont + setFont + ( const QFont & font ) + + + setForeground + setForeground + ( const QBrush & brush ) + + + setHidden + setHidden + ( bool hide ) + + + setIcon + setIcon + ( const QIcon & icon ) + + + setSelected + setSelected + ( bool select ) + + + setSizeHint + setSizeHint + ( const QSize & size ) + + + setStatusTip + setStatusTip + ( const QString & statusTip ) + + + setText + setText + ( const QString & text ) + + + setTextAlignment + setTextAlignment + ( int alignment ) + + + setToolTip + setToolTip + ( const QString & toolTip ) + + + setWhatsThis + setWhatsThis + ( const QString & whatsThis ) + + + sizeHint + sizeHint + () + + + statusTip + statusTip + () + + + text + text + () + + + textAlignment + textAlignment + () + + + toolTip + toolTip + () + + + type + type + () + + + whatsThis + whatsThis + () + + + write + write + ( QDataStream & out ) + + + operator< + operator-lt + ( const QListWidgetItem & other ) + + + operator= + operator-eq + ( const QListWidgetItem & other ) + + + + QLocale + qlocale.html + + Country + Country-enum + + + + FormatType + FormatType-enum + + + + Language + Language-enum + + + + QLocale + QLocale + () + + + QLocale + QLocale-2 + ( const QString & name ) + + + QLocale + QLocale-3 + ( Language language, Country country = AnyCountry ) + + + QLocale + QLocale-4 + ( const QLocale & other ) + + + c + c + () + + + countriesForLanguage + countriesForLanguage + ( Language language ) + + + country + country + () + + + countryToString + countryToString + ( Country country ) + + + dateFormat + dateFormat + ( FormatType format = LongFormat ) + + + dayName + dayName + ( int day, FormatType type = LongFormat ) + + + decimalPoint + decimalPoint + () + + + exponential + exponential + () + + + groupSeparator + groupSeparator + () + + + language + language + () + + + languageToString + languageToString + ( Language language ) + + + monthName + monthName + ( int month, FormatType type = LongFormat ) + + + name + name + () + + + negativeSign + negativeSign + () + + + numberOptions + numberOptions + () + + + percent + percent + () + + + setDefault + setDefault + ( const QLocale & locale ) + + + setNumberOptions + setNumberOptions + ( NumberOptions options ) + + + system + system + () + + + timeFormat + timeFormat + ( FormatType format = LongFormat ) + + + toDouble + toDouble + ( const QString & s, bool * ok = 0 ) + + + toFloat + toFloat + ( const QString & s, bool * ok = 0 ) + + + toInt + toInt + ( const QString & s, bool * ok = 0, int base = 0 ) + + + toLongLong + toLongLong + ( const QString & s, bool * ok = 0, int base = 0 ) + + + toShort + toShort + ( const QString & s, bool * ok = 0, int base = 0 ) + + + toString + toString + ( qlonglong i ) + + + toString + toString-2 + ( const QDate & date, const QString & format ) + + + toString + toString-3 + ( const QDate & date, FormatType format = LongFormat ) + + + toString + toString-4 + ( const QTime & time, const QString & format ) + + + toString + toString-5 + ( const QTime & time, FormatType format = LongFormat ) + + + toString + toString-6 + ( qulonglong i ) + + + toString + toString-7 + ( double i, char f = 'g', int prec = 6 ) + + + toString + toString-8 + ( short i ) + + + toString + toString-9 + ( ushort i ) + + + toString + toString-10 + ( int i ) + + + toString + toString-11 + ( uint i ) + + + toString + toString-12 + ( float i, char f = 'g', int prec = 6 ) + + + toUInt + toUInt + ( const QString & s, bool * ok = 0, int base = 0 ) + + + toULongLong + toULongLong + ( const QString & s, bool * ok = 0, int base = 0 ) + + + toUShort + toUShort + ( const QString & s, bool * ok = 0, int base = 0 ) + + + zeroDigit + zeroDigit + () + + + operator!= + operator-not-eq + ( const QLocale & other ) + + + operator= + operator-eq + ( const QLocale & other ) + + + operator== + operator-eq-eq + ( const QLocale & other ) + + + + QMacPasteboardMime + qmacpasteboardmime.html + + QMacPasteboardMime + QMacPasteboardMime + ( char t ) + + + all + all + ( uchar t ) + + + canConvert + canConvert + ( const QString & mime, QString flav ) + + + convertFromMime + convertFromMime + ( const QString & mime, QVariant data, QString flav ) + + + convertToMime + convertToMime + ( const QString & mime, QList<QByteArray> data, QString flav ) + + + convertor + convertor + ( uchar t, const QString & mime, QString flav ) + + + convertorName + convertorName + () + + + flavorFor + flavorFor + ( const QString & mime ) + + + flavorToMime + flavorToMime + ( uchar t, QString flav ) + + + mimeFor + mimeFor + ( QString flav ) + + + focusRectPolicy + focusRectPolicy + ( const QWidget * w ) + + + setFocusRectPolicy + setFocusRectPolicy + ( QWidget * w, FocusRectPolicy policy ) + + + setWidgetSizePolicy + setWidgetSizePolicy + ( const QWidget * widget, WidgetSizePolicy policy ) + + + widgetSizePolicy + widgetSizePolicy + ( const QWidget * widget ) + + + + QMacStyle + qmacstyle.html + + FocusRectPolicy + FocusRectPolicy-enum + + + + WidgetSizePolicy + WidgetSizePolicy-enum + + + + QMacStyle + QMacStyle + () + + + QMainWindow + QMainWindow-2 + ( QWidget * parent, const char * name, Qt::WindowFlags flags = 0 ) + + + + QMainWindow + qmainwindow.html + + ToolButtonStyle + toolButtonStyle-prop + + + + QMainWindow + QMainWindow + ( QWidget * parent = 0, Qt::WindowFlags flags = 0 ) + + + addDockWidget + addDockWidget + ( Qt::DockWidgetArea area, QDockWidget * dockwidget ) + + + addDockWidget + addDockWidget-2 + ( Qt::DockWidgetArea area, QDockWidget * dockwidget, Qt::Orientation orientation ) + + + addToolBar + addToolBar + ( Qt::ToolBarArea area, QToolBar * toolbar ) + + + addToolBar + addToolBar-2 + ( QToolBar * toolbar ) + + + addToolBar + addToolBar-3 + ( const QString & title ) + + + addToolBarBreak + addToolBarBreak + ( Qt::ToolBarArea area = Qt::TopToolBarArea ) + + + centralWidget + centralWidget + () + + + DockWidgetArea + corner + QMainWindow::corner( Qt::Corner corner ) + + + createPopupMenu + createPopupMenu + () + + + DockWidgetArea + dockWidgetArea + QMainWindow::dockWidgetArea( QDockWidget * dockwidget ) + + + iconSizeChanged + iconSizeChanged + ( const QSize & iconSize ) + + + insertToolBar + insertToolBar + ( QToolBar * before, QToolBar * toolbar ) + + + insertToolBarBreak + insertToolBarBreak + ( QToolBar * before ) + + + menuBar + menuBar + () + + + menuWidget + menuWidget + () + + + removeDockWidget + removeDockWidget + ( QDockWidget * dockwidget ) + + + removeToolBar + removeToolBar + ( QToolBar * toolbar ) + + + removeToolBarBreak + removeToolBarBreak + ( QToolBar * before ) + + + restoreState + restoreState + ( const QByteArray & state, int version = 0 ) + + + saveState + saveState + ( int version = 0 ) + + + setCentralWidget + setCentralWidget + ( QWidget * widget ) + + + setCorner + setCorner + ( Qt::Corner corner, Qt::DockWidgetArea area ) + + + setMenuBar + setMenuBar + ( QMenuBar * menuBar ) + + + setMenuWidget + setMenuWidget + ( QWidget * menuBar ) + + + setStatusBar + setStatusBar + ( QStatusBar * statusbar ) + + + splitDockWidget + splitDockWidget + ( QDockWidget * first, QDockWidget * second, Qt::Orientation orientation ) + + + statusBar + statusBar + () + + + tabifyDockWidget + tabifyDockWidget + ( QDockWidget * first, QDockWidget * second ) + + + ToolBarArea + toolBarArea + QMainWindow::toolBarArea( QToolBar * toolbar ) + + + toolBarBreak + toolBarBreak + ( QToolBar * toolbar ) + + + toolButtonStyleChanged + toolButtonStyleChanged + ( Qt::ToolButtonStyle toolButtonStyle ) + + + QMainWindow + QMainWindow-2 + ( QWidget * parent, const char * name, Qt::WindowFlags flags = 0 ) + + + data + data + () + + + + QMap::const_iterator + qmap-const-iterator.html + + const_iterator + const_iterator + () + + + const_iterator + const_iterator-3 + ( const iterator & other ) + + + key + key + () + + + value + value + () + + + operator!= + operator-not-eq + ( const const_iterator & other ) + + + operator* + operator-2a + () + + + operator+ + operator-2b + ( int j ) + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator+= + operator-2b-eq + ( int j ) + + + operator- + operator- + ( int j ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator-= + operator--eq + ( int j ) + + + operator-& + operator--gt + gt;() + + + operator== + operator-eq-eq + ( const const_iterator & other ) + + + data + data + () + + + data + data + () + + + + QMap::iterator + qmap-iterator.html + + iterator + iterator + () + + + key + key + () + + + value + value + () + + + operator!= + operator-not-eq + ( const iterator & other ) + + + operator!= + operator-not-eq-2 + ( const const_iterator & other ) + + + operator* + operator-2a + () + + + operator+ + operator-2b + ( int j ) + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator+= + operator-2b-eq + ( int j ) + + + operator- + operator- + ( int j ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator-= + operator--eq + ( int j ) + + + operator-& + operator--gt + gt;() + + + operator== + operator-eq-eq + ( const iterator & other ) + + + operator== + operator-eq-eq-2 + ( const const_iterator & other ) + + + data + data + () + + + erase + erase-2 + ( const Key & key ) + + + insert + insert-2 + ( const Key & key, const T & value, bool overwrite ) + + + remove + remove-2 + ( iterator it ) + + + replace + replace + ( const Key & key, const T & value ) + + + + QMap + qmap.html + + ConstIterator + ConstIterator-typedef + + + + Iterator + Iterator-typedef + + + + difference_type + difference_type-typedef + + + + key_type + key_type-typedef + + + + mapped_type + mapped_type-typedef + + + + size_type + size_type-typedef + + + + QMap + QMap + () + + + QMap + QMap-2 + ( const QMap<Key, T> & other ) + + + QMap + QMap-3 + ( const std::map<Key, T> & other ) + + + begin + begin + () + + + begin + begin-2 + () + + + clear + clear + () + + + constBegin + constBegin + () + + + constEnd + constEnd + () + + + constFind + constFind + ( const Key & key ) + + + contains + contains + ( const Key & key ) + + + count + count + ( const Key & key ) + + + count + count-2 + () + + + empty + empty + () + + + end + end + () + + + end + end-2 + () + + + erase + erase + ( iterator pos ) + + + find + find + ( const Key & key ) + + + find + find-2 + ( const Key & key ) + + + insert + insert + ( const Key & key, const T & value ) + + + insertMulti + insertMulti + ( const Key & key, const T & value ) + + + isEmpty + isEmpty + () + + + key + key + ( const T & value ) + + + key + key-2 + ( const T & value, const Key & defaultKey ) + + + keys + keys + () + + + keys + keys-2 + ( const T & value ) + + + lowerBound + lowerBound + ( const Key & key ) + + + lowerBound + lowerBound-2 + ( const Key & key ) + + + remove + remove + ( const Key & key ) + + + size + size + () + + + take + take + ( const Key & key ) + + + map + toStdMap + <Key, T> QMap::toStdMap() + + + uniqueKeys + uniqueKeys + () + + + unite + unite + ( const QMap<Key, T> & other ) + + + upperBound + upperBound + ( const Key & key ) + + + upperBound + upperBound-2 + ( const Key & key ) + + + value + value + ( const Key & key ) + + + value + value-2 + ( const Key & key, const T & defaultValue ) + + + values + values + () + + + values + values-2 + ( const Key & key ) + + + operator!= + operator-not-eq + ( const QMap<Key, T> & other ) + + + operator= + operator-eq + ( const QMap<Key, T> & other ) + + + operator== + operator-eq-eq + ( const QMap<Key, T> & other ) + + + operator[] + operator-5b-5d + ( const Key & key ) + + + operator[] + operator-5b-5d-2 + ( const Key & key ) + + + erase + erase-2 + ( const Key & key ) + + + insert + insert-2 + ( const Key & key, const T & value, bool overwrite ) + + + remove + remove-2 + ( iterator it ) + + + replace + replace + ( const Key & key, const T & value ) + + + + QMapIterator + qmapiterator.html + + QMapIterator + QMapIterator + ( const QMap<Key, T> & map ) + + + findNext + findNext + ( const T & value ) + + + findPrevious + findPrevious + ( const T & value ) + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + key + key + () + + + next + next + () + + + peekNext + peekNext + () + + + peekPrevious + peekPrevious + () + + + previous + previous + () + + + toBack + toBack + () + + + toFront + toFront + () + + + value + value + () + + + operator= + operator-eq + ( const QMap<Key, T> & map ) + + + invert + invert + ( bool * invertible = 0 ) + + + map + map-2 + ( const QRect & rect ) + + + mapToRegion + mapToRegion + ( const QRect & rectangle ) + + + + QMatrix + qmatrix.html + + QMatrix + QMatrix + () + + + QMatrix + QMatrix-2 + ( qreal m11, qreal m12, qreal m21, qreal m22, qreal dx, qreal dy ) + + + QMatrix + QMatrix-3 + ( const QMatrix & matrix ) + + + m11 + m11 + () + + + m12 + m12 + () + + + m21 + m21 + () + + + m22 + m22 + () + + + det + det + () + + + dx + dx + () + + + dy + dy + () + + + inverted + inverted + ( bool * invertible = 0 ) + + + isIdentity + isIdentity + () + + + isInvertible + isInvertible + () + + + map + map + ( qreal x, qreal y, qreal * tx, qreal * ty ) + + + map + map-3 + ( int x, int y, int * tx, int * ty ) + + + map + map-4 + ( const QPointF & point ) + + + map + map-5 + ( const QPoint & point ) + + + map + map-6 + ( const QLineF & line ) + + + map + map-7 + ( const QLine & line ) + + + map + map-8 + ( const QPolygonF & polygon ) + + + map + map-9 + ( const QPolygon & polygon ) + + + map + map-10 + ( const QRegion & region ) + + + map + map-11 + ( const QPainterPath & path ) + + + mapRect + mapRect + ( const QRectF & rectangle ) + + + mapRect + mapRect-2 + ( const QRect & rectangle ) + + + mapToPolygon + mapToPolygon + ( const QRect & rectangle ) + + + reset + reset + () + + + rotate + rotate + ( qreal degrees ) + + + scale + scale + ( qreal sx, qreal sy ) + + + setMatrix + setMatrix + ( qreal m11, qreal m12, qreal m21, qreal m22, qreal dx, qreal dy ) + + + shear + shear + ( qreal sh, qreal sv ) + + + translate + translate + ( qreal dx, qreal dy ) + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QMatrix & matrix ) + + + operator* + operator-2a + ( const QMatrix & matrix ) + + + operator*= + operator-2a-eq + ( const QMatrix & matrix ) + + + operator= + operator-eq + ( const QMatrix & matrix ) + + + operator== + operator-eq-eq + ( const QMatrix & matrix ) + + + invert + invert + ( bool * invertible = 0 ) + + + map + map-2 + ( const QRect & rect ) + + + mapToRegion + mapToRegion + ( const QRect & rectangle ) + + + + QMdiArea + qmdiarea.html + + WindowOrder + WindowOrder-enum + + + + QMdiArea + QMdiArea + ( QWidget * parent = 0 ) + + + activateNextSubWindow + activateNextSubWindow + () + + + activatePreviousSubWindow + activatePreviousSubWindow + () + + + activeSubWindow + activeSubWindow + () + + + addSubWindow + addSubWindow + ( QWidget * widget, Qt::WindowFlags windowFlags = 0 ) + + + cascadeSubWindows + cascadeSubWindows + () + + + closeActiveSubWindow + closeActiveSubWindow + () + + + closeAllSubWindows + closeAllSubWindows + () + + + currentSubWindow + currentSubWindow + () + + + removeSubWindow + removeSubWindow + ( QWidget * widget ) + + + setActiveSubWindow + setActiveSubWindow + ( QMdiSubWindow * window ) + + + setOption + setOption + ( AreaOption option, bool on = true ) + + + setupViewport + setupViewport + ( QWidget * viewport ) + + + subWindowActivated + subWindowActivated + ( QMdiSubWindow * window ) + + + subWindowList + subWindowList + ( WindowOrder order = CreationOrder ) + + + testOption + testOption + ( AreaOption option ) + + + tileSubWindows + tileSubWindows + () + + + + QMdiSubWindow + qmdisubwindow.html + + QMdiSubWindow + QMdiSubWindow + ( QWidget * parent = 0, Qt::WindowFlags flags = 0 ) + + + aboutToActivate + aboutToActivate + () + + + isShaded + isShaded + () + + + setOption + setOption + ( SubWindowOption option, bool on = true ) + + + setSystemMenu + setSystemMenu + ( QMenu * systemMenu ) + + + setWidget + setWidget + ( QWidget * widget ) + + + showShaded + showShaded + () + + + showSystemMenu + showSystemMenu + () + + + systemMenu + systemMenu + () + + + testOption + testOption + ( SubWindowOption option ) + + + widget + widget + () + + + windowStateChanged + windowStateChanged + ( Qt::WindowStates oldState, Qt::WindowStates newState ) + + + accel + accel + ( int id ) + + + activateItemAt + activateItemAt + ( int index ) + + + activated + activated + ( int itemId ) + + + changeItem + changeItem + ( int id, const QString & text ) + + + changeItem + changeItem-2 + ( int id, const QPixmap & pixmap ) + + + changeItem + changeItem-3 + ( int id, const QIcon & icon, const QString & text ) + + + columns + columns + () + + + connectItem + connectItem + ( int id, const QObject * receiver, const char * member ) + + + count + count + () + + + disconnectItem + disconnectItem + ( int id, const QObject * receiver, const char * member ) + + + findItem + findItem + ( int id ) + + + findPopup + findPopup + ( QMenu * popup, int * index ) + + + frameWidth + frameWidth + () + + + highlighted + highlighted + ( int itemId ) + + + iconSet + iconSet + ( int id ) + + + idAt + idAt + ( int index ) + + + indexOf + indexOf + ( int id ) + + + insertItem + insertItem + ( const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0, int id = -1, int index = -1 ) + + + insertItem + insertItem-2 + ( const QIcon & icon, const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0, int id = -1, int index = -1 ) + + + insertItem + insertItem-3 + ( const QPixmap & pixmap, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0, int id = -1, int index = -1 ) + + + insertItem + insertItem-4 + ( const QString & text, int id = -1, int index = -1 ) + + + insertItem + insertItem-5 + ( const QIcon & icon, const QString & text, int id = -1, int index = -1 ) + + + insertItem + insertItem-6 + ( const QString & text, QMenu * popup, int id = -1, int index = -1 ) + + + insertItem + insertItem-7 + ( const QIcon & icon, const QString & text, QMenu * popup, int id = -1, int index = -1 ) + + + insertItem + insertItem-8 + ( const QPixmap & pixmap, int id = -1, int index = -1 ) + + + insertItem + insertItem-9 + ( const QPixmap & pixmap, QMenu * popup, int id = -1, int index = -1 ) + + + insertItem + insertItem-10 + ( QMenuItem * item, int id = -1, int index = -1 ) + + + insertSeparator + insertSeparator-2 + ( int index = -1 ) + + + insertTearOffHandle + insertTearOffHandle + ( int a = 0, int b = 0 ) + + + isCheckable + isCheckable + () + + + isItemActive + isItemActive + ( int id ) + + + isItemChecked + isItemChecked + ( int id ) + + + isItemEnabled + isItemEnabled + ( int id ) + + + isItemVisible + isItemVisible + ( int id ) + + + itemAtPos + itemAtPos + ( const QPoint & p, bool ignoreSeparator = true ) + + + itemFont + itemFont + ( int id ) + + + itemGeometry + itemGeometry + ( int index ) + + + itemHeight + itemHeight + ( int index ) + + + itemHeight + itemHeight-2 + ( QMenuItem * mi ) + + + itemParameter + itemParameter + ( int id ) + + + pixmap + pixmap + ( int id ) + + + popup + popup-2 + ( const QPoint & pos, int indexAtPoint ) + + + removeItem + removeItem + ( int id ) + + + removeItemAt + removeItemAt + ( int index ) + + + setAccel + setAccel + ( const QKeySequence & key, int id ) + + + setActiveItem + setActiveItem + ( int id ) + + + setCheckable + setCheckable + ( bool checkable ) + + + setId + setId + ( int index, int id ) + + + setItemChecked + setItemChecked + ( int id, bool check ) + + + setItemEnabled + setItemEnabled + ( int id, bool enable ) + + + setItemFont + setItemFont + ( int id, const QFont & font ) + + + setItemParameter + setItemParameter + ( int id, int param ) + + + setItemVisible + setItemVisible + ( int id, bool visible ) + + + setWhatsThis + setWhatsThis + ( int id, const QString & w ) + + + text + text + ( int id ) + + + whatsThis + whatsThis + ( int id ) + + + + QMenu + qmenu.html + + QMenu + QMenu + ( QWidget * parent = 0 ) + + + QMenu + QMenu-2 + ( const QString & title, QWidget * parent = 0 ) + + + aboutToHide + aboutToHide + () + + + aboutToShow + aboutToShow + () + + + actionAt + actionAt + ( const QPoint & pt ) + + + actionGeometry + actionGeometry + ( QAction * act ) + + + activeAction + activeAction + () + + + addAction + addAction + ( const QString & text ) + + + addAction + addAction-2 + ( const QIcon & icon, const QString & text ) + + + addAction + addAction-3 + ( const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0 ) + + + addAction + addAction-4 + ( const QIcon & icon, const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0 ) + + + addMenu + addMenu + ( QMenu * menu ) + + + addMenu + addMenu-2 + ( const QString & title ) + + + addMenu + addMenu-3 + ( const QIcon & icon, const QString & title ) + + + addSeparator + addSeparator + () + + + clear + clear + () + + + columnCount + columnCount + () + + + defaultAction + defaultAction + () + + + exec + exec + () + + + exec + exec-2 + ( const QPoint & p, QAction * action = 0 ) + + + exec + exec-3 + ( QList<QAction *> actions, const QPoint & pos, QAction * at = 0 ) + + + hideTearOffMenu + hideTearOffMenu + () + + + hovered + hovered + ( QAction * action ) + + + initStyleOption + initStyleOption + ( QStyleOptionMenuItem * option, const QAction * action ) + + + insertMenu + insertMenu + ( QAction * before, QMenu * menu ) + + + insertSeparator + insertSeparator + ( QAction * before ) + + + isEmpty + isEmpty + () + + + isTearOffMenuVisible + isTearOffMenuVisible + () + + + menuAction + menuAction + () + + + popup + popup + ( const QPoint & p, QAction * atAction = 0 ) + + + setActiveAction + setActiveAction + ( QAction * act ) + + + setDefaultAction + setDefaultAction + ( QAction * act ) + + + triggered + triggered + ( QAction * action ) + + + accel + accel + ( int id ) + + + activateItemAt + activateItemAt + ( int index ) + + + activated + activated + ( int itemId ) + + + changeItem + changeItem + ( int id, const QString & text ) + + + changeItem + changeItem-2 + ( int id, const QPixmap & pixmap ) + + + changeItem + changeItem-3 + ( int id, const QIcon & icon, const QString & text ) + + + columns + columns + () + + + connectItem + connectItem + ( int id, const QObject * receiver, const char * member ) + + + count + count + () + + + disconnectItem + disconnectItem + ( int id, const QObject * receiver, const char * member ) + + + findItem + findItem + ( int id ) + + + findPopup + findPopup + ( QMenu * popup, int * index ) + + + frameWidth + frameWidth + () + + + highlighted + highlighted + ( int itemId ) + + + iconSet + iconSet + ( int id ) + + + idAt + idAt + ( int index ) + + + indexOf + indexOf + ( int id ) + + + insertItem + insertItem + ( const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0, int id = -1, int index = -1 ) + + + insertItem + insertItem-2 + ( const QIcon & icon, const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0, int id = -1, int index = -1 ) + + + insertItem + insertItem-3 + ( const QPixmap & pixmap, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0, int id = -1, int index = -1 ) + + + insertItem + insertItem-4 + ( const QString & text, int id = -1, int index = -1 ) + + + insertItem + insertItem-5 + ( const QIcon & icon, const QString & text, int id = -1, int index = -1 ) + + + insertItem + insertItem-6 + ( const QString & text, QMenu * popup, int id = -1, int index = -1 ) + + + insertItem + insertItem-7 + ( const QIcon & icon, const QString & text, QMenu * popup, int id = -1, int index = -1 ) + + + insertItem + insertItem-8 + ( const QPixmap & pixmap, int id = -1, int index = -1 ) + + + insertItem + insertItem-9 + ( const QPixmap & pixmap, QMenu * popup, int id = -1, int index = -1 ) + + + insertItem + insertItem-10 + ( QMenuItem * item, int id = -1, int index = -1 ) + + + insertSeparator + insertSeparator-2 + ( int index = -1 ) + + + insertTearOffHandle + insertTearOffHandle + ( int a = 0, int b = 0 ) + + + isCheckable + isCheckable + () + + + isItemActive + isItemActive + ( int id ) + + + isItemChecked + isItemChecked + ( int id ) + + + isItemEnabled + isItemEnabled + ( int id ) + + + isItemVisible + isItemVisible + ( int id ) + + + itemAtPos + itemAtPos + ( const QPoint & p, bool ignoreSeparator = true ) + + + itemFont + itemFont + ( int id ) + + + itemGeometry + itemGeometry + ( int index ) + + + itemHeight + itemHeight + ( int index ) + + + itemHeight + itemHeight-2 + ( QMenuItem * mi ) + + + itemParameter + itemParameter + ( int id ) + + + pixmap + pixmap + ( int id ) + + + popup + popup-2 + ( const QPoint & pos, int indexAtPoint ) + + + removeItem + removeItem + ( int id ) + + + removeItemAt + removeItemAt + ( int index ) + + + setAccel + setAccel + ( const QKeySequence & key, int id ) + + + setActiveItem + setActiveItem + ( int id ) + + + setCheckable + setCheckable + ( bool checkable ) + + + setId + setId + ( int index, int id ) + + + setItemChecked + setItemChecked + ( int id, bool check ) + + + setItemEnabled + setItemEnabled + ( int id, bool enable ) + + + setItemFont + setItemFont + ( int id, const QFont & font ) + + + setItemParameter + setItemParameter + ( int id, int param ) + + + setItemVisible + setItemVisible + ( int id, bool visible ) + + + setWhatsThis + setWhatsThis + ( int id, const QString & w ) + + + text + text + ( int id ) + + + whatsThis + whatsThis + ( int id ) + + + Separator + Separator-enum + + + + QMenuBar + QMenuBar-2 + ( QWidget * parent, const char * name ) + + + accel + accel + ( int id ) + + + activateItemAt + activateItemAt + ( int index ) + + + activated + activated + ( int itemId ) + + + autoGeometry + autoGeometry + () + + + changeItem + changeItem + ( int id, const QString & text ) + + + changeItem + changeItem-2 + ( int id, const QPixmap & pixmap ) + + + changeItem + changeItem-3 + ( int id, const QIcon & icon, const QString & text ) + + + connectItem + connectItem + ( int id, const QObject * receiver, const char * member ) + + + count + count + () + + + disconnectItem + disconnectItem + ( int id, const QObject * receiver, const char * member ) + + + findItem + findItem + ( int id ) + + + frameWidth + frameWidth + () + + + highlighted + highlighted + ( int itemId ) + + + iconSet + iconSet + ( int id ) + + + idAt + idAt + ( int index ) + + + indexOf + indexOf + ( int id ) + + + insertItem + insertItem + ( const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0, int id = -1, int index = -1 ) + + + insertItem + insertItem-2 + ( const QIcon & icon, const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0, int id = -1, int index = -1 ) + + + insertItem + insertItem-3 + ( const QPixmap & pixmap, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0, int id = -1, int index = -1 ) + + + insertItem + insertItem-4 + ( const QString & text, int id = -1, int index = -1 ) + + + insertItem + insertItem-5 + ( const QIcon & icon, const QString & text, int id = -1, int index = -1 ) + + + insertItem + insertItem-6 + ( const QString & text, QMenu * popup, int id = -1, int index = -1 ) + + + insertItem + insertItem-7 + ( const QIcon & icon, const QString & text, QMenu * popup, int id = -1, int index = -1 ) + + + insertItem + insertItem-8 + ( const QPixmap & pixmap, int id = -1, int index = -1 ) + + + insertItem + insertItem-9 + ( const QPixmap & pixmap, QMenu * popup, int id = -1, int index = -1 ) + + + insertSeparator + insertSeparator-2 + ( int index = -1 ) + + + isItemActive + isItemActive + ( int id ) + + + isItemChecked + isItemChecked + ( int id ) + + + isItemEnabled + isItemEnabled + ( int id ) + + + isItemVisible + isItemVisible + ( int id ) + + + itemAtPos + itemAtPos + ( const QPoint & p ) + + + itemParameter + itemParameter + ( int id ) + + + itemRect + itemRect + ( int index ) + + + margin + margin + () + + + pixmap + pixmap + ( int id ) + + + removeItem + removeItem + ( int id ) + + + removeItemAt + removeItemAt + ( int index ) + + + separator + separator + () + + + setAccel + setAccel + ( const QKeySequence & key, int id ) + + + setAutoGeometry + setAutoGeometry + ( bool b ) + + + setItemChecked + setItemChecked + ( int id, bool check ) + + + setItemEnabled + setItemEnabled + ( int id, bool enable ) + + + setItemParameter + setItemParameter + ( int id, int param ) + + + setItemVisible + setItemVisible + ( int id, bool visible ) + + + setMargin + setMargin + ( int margin ) + + + setSeparator + setSeparator + ( Separator sep ) + + + setWhatsThis + setWhatsThis + ( int id, const QString & w ) + + + text + text + ( int id ) + + + whatsThis + whatsThis + ( int id ) + + + + QMenuBar + qmenubar.html + + QMenuBar + QMenuBar + ( QWidget * parent = 0 ) + + + activeAction + activeAction + () + + + addAction + addAction + ( const QString & text ) + + + addAction + addAction-2 + ( const QString & text, const QObject * receiver, const char * member ) + + + addMenu + addMenu + ( QMenu * menu ) + + + addMenu + addMenu-2 + ( const QString & title ) + + + addMenu + addMenu-3 + ( const QIcon & icon, const QString & title ) + + + addSeparator + addSeparator + () + + + clear + clear + () + + + hovered + hovered + ( QAction * action ) + + + initStyleOption + initStyleOption + ( QStyleOptionMenuItem * option, const QAction * action ) + + + insertMenu + insertMenu + ( QAction * before, QMenu * menu ) + + + insertSeparator + insertSeparator + ( QAction * before ) + + + setActiveAction + setActiveAction + ( QAction * act ) + + + triggered + triggered + ( QAction * action ) + + + Separator + Separator-enum + + + + QMenuBar + QMenuBar-2 + ( QWidget * parent, const char * name ) + + + accel + accel + ( int id ) + + + activateItemAt + activateItemAt + ( int index ) + + + activated + activated + ( int itemId ) + + + autoGeometry + autoGeometry + () + + + changeItem + changeItem + ( int id, const QString & text ) + + + changeItem + changeItem-2 + ( int id, const QPixmap & pixmap ) + + + changeItem + changeItem-3 + ( int id, const QIcon & icon, const QString & text ) + + + connectItem + connectItem + ( int id, const QObject * receiver, const char * member ) + + + count + count + () + + + disconnectItem + disconnectItem + ( int id, const QObject * receiver, const char * member ) + + + findItem + findItem + ( int id ) + + + frameWidth + frameWidth + () + + + highlighted + highlighted + ( int itemId ) + + + iconSet + iconSet + ( int id ) + + + idAt + idAt + ( int index ) + + + indexOf + indexOf + ( int id ) + + + insertItem + insertItem + ( const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0, int id = -1, int index = -1 ) + + + insertItem + insertItem-2 + ( const QIcon & icon, const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0, int id = -1, int index = -1 ) + + + insertItem + insertItem-3 + ( const QPixmap & pixmap, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0, int id = -1, int index = -1 ) + + + insertItem + insertItem-4 + ( const QString & text, int id = -1, int index = -1 ) + + + insertItem + insertItem-5 + ( const QIcon & icon, const QString & text, int id = -1, int index = -1 ) + + + insertItem + insertItem-6 + ( const QString & text, QMenu * popup, int id = -1, int index = -1 ) + + + insertItem + insertItem-7 + ( const QIcon & icon, const QString & text, QMenu * popup, int id = -1, int index = -1 ) + + + insertItem + insertItem-8 + ( const QPixmap & pixmap, int id = -1, int index = -1 ) + + + insertItem + insertItem-9 + ( const QPixmap & pixmap, QMenu * popup, int id = -1, int index = -1 ) + + + insertSeparator + insertSeparator-2 + ( int index = -1 ) + + + isItemActive + isItemActive + ( int id ) + + + isItemChecked + isItemChecked + ( int id ) + + + isItemEnabled + isItemEnabled + ( int id ) + + + isItemVisible + isItemVisible + ( int id ) + + + itemAtPos + itemAtPos + ( const QPoint & p ) + + + itemParameter + itemParameter + ( int id ) + + + itemRect + itemRect + ( int index ) + + + margin + margin + () + + + pixmap + pixmap + ( int id ) + + + removeItem + removeItem + ( int id ) + + + removeItemAt + removeItemAt + ( int index ) + + + separator + separator + () + + + setAccel + setAccel + ( const QKeySequence & key, int id ) + + + setAutoGeometry + setAutoGeometry + ( bool b ) + + + setItemChecked + setItemChecked + ( int id, bool check ) + + + setItemEnabled + setItemEnabled + ( int id, bool enable ) + + + setItemParameter + setItemParameter + ( int id, int param ) + + + setItemVisible + setItemVisible + ( int id, bool visible ) + + + setMargin + setMargin + ( int margin ) + + + setSeparator + setSeparator + ( Separator sep ) + + + setWhatsThis + setWhatsThis + ( int id, const QString & w ) + + + text + text + ( int id ) + + + whatsThis + whatsThis + ( int id ) + + + QMenuItem + QMenuItem + () + + + id + id + () + + + signalValue + signalValue + () + + + + QMenuItem + qmenuitem.html + + QMenuItem + QMenuItem + () + + + id + id + () + + + signalValue + signalValue + () + + + Button + Button-typedef + + + + QMessageBox + QMessageBox-3 + ( const QString & title, const QString & text, Icon icon, int button0, int button1, int button2, QWidget * parent = 0, Qt::WindowFlags f = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint ) + + + buttonText + buttonText + ( int button ) + + + critical + critical-2 + ( QWidget * parent, const QString & title, const QString & text, int button0, int button1, int button2 = 0 ) + + + critical + critical-4 + ( QWidget * parent, const QString & title, const QString & text, const QString & button0Text, const QString & button1Text = QString() + + + information + information-2 + ( QWidget * parent, const QString & title, const QString & text, int button0, int button1 = 0, int button2 = 0 ) + + + information + information-4 + ( QWidget * parent, const QString & title, const QString & text, const QString & button0Text, const QString & button1Text = QString() + + + question + question-2 + ( QWidget * parent, const QString & title, const QString & text, int button0, int button1 = 0, int button2 = 0 ) + + + question + question-4 + ( QWidget * parent, const QString & title, const QString & text, const QString & button0Text, const QString & button1Text = QString() + + + setButtonText + setButtonText + ( int button, const QString & text ) + + + standardIcon + standardIcon-2 + ( Icon icon ) + + + warning + warning-2 + ( QWidget * parent, const QString & title, const QString & text, int button0, int button1, int button2 = 0 ) + + + warning + warning-4 + ( QWidget * parent, const QString & title, const QString & text, const QString & button0Text, const QString & button1Text = QString() + + + QMessageBox + QMessageBox-4 + ( const QString & title, const QString & text, Icon icon, int button0, int button1, int button2, QWidget * parent, const char * name, bool modal, Qt::WindowFlags f = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint ) + + + QMessageBox + QMessageBox-5 + ( QWidget * parent, const char * name ) + + + message + message + ( const QString & title, const QString & text, const QString & buttonText = QString() + + + query + query + ( const QString & caption, const QString & text, const QString & yesButtonText = QString() + + + standardIcon + standardIcon + ( Icon icon, Qt::GUIStyle style ) + + + + QMessageBox + qmessagebox.html + + ButtonRole + ButtonRole-enum + + + + Icon + Icon-enum + + + + TextFormat + textFormat-prop + + + + QMessageBox + QMessageBox + ( QWidget * parent = 0 ) + + + QMessageBox + QMessageBox-2 + ( Icon icon, const QString & title, const QString & text, StandardButtons buttons = NoButton, QWidget * parent = 0, Qt::WindowFlags f = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint ) + + + about + about + ( QWidget * parent, const QString & title, const QString & text ) + + + aboutQt + aboutQt + ( QWidget * parent, const QString & title = QString() + + + addButton + addButton + ( QAbstractButton * button, ButtonRole role ) + + + addButton + addButton-2 + ( const QString & text, ButtonRole role ) + + + addButton + addButton-3 + ( StandardButton button ) + + + button + button + ( StandardButton which ) + + + clickedButton + clickedButton + () + + + critical + critical + ( QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton ) + + + defaultButton + defaultButton + () + + + escapeButton + escapeButton + () + + + exec + exec + () + + + information + information + ( QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton ) + + + question + question + ( QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton ) + + + removeButton + removeButton + ( QAbstractButton * button ) + + + setDefaultButton + setDefaultButton + ( QPushButton * button ) + + + setDefaultButton + setDefaultButton-2 + ( StandardButton button ) + + + setEscapeButton + setEscapeButton + ( QAbstractButton * button ) + + + setEscapeButton + setEscapeButton-2 + ( StandardButton button ) + + + setWindowModality + setWindowModality + ( Qt::WindowModality windowModality ) + + + setWindowTitle + setWindowTitle + ( const QString & title ) + + + standardButton + standardButton + ( QAbstractButton * button ) + + + warning + warning + ( QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton ) + + + QMessageBox + QMessageBox-4 + ( const QString & title, const QString & text, Icon icon, int button0, int button1, int button2, QWidget * parent, const char * name, bool modal, Qt::WindowFlags f = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint ) + + + QMessageBox + QMessageBox-5 + ( QWidget * parent, const char * name ) + + + message + message + ( const QString & title, const QString & text, const QString & buttonText = QString() + + + query + query + ( const QString & caption, const QString & text, const QString & yesButtonText = QString() + + + standardIcon + standardIcon + ( Icon icon, Qt::GUIStyle style ) + + + + QMetaClassInfo + qmetaclassinfo.html + + name + name + () + + + value + value + () + + + + QMetaEnum + qmetaenum.html + + isFlag + isFlag + () + + + isValid + isValid + () + + + key + key + ( int index ) + + + keyCount + keyCount + () + + + keyToValue + keyToValue + ( const char * key ) + + + keysToValue + keysToValue + ( const char * keys ) + + + name + name + () + + + scope + scope + () + + + value + value + ( int index ) + + + valueToKey + valueToKey + ( int value ) + + + valueToKeys + valueToKeys + ( int value ) + + + + QMetaMethod + qmetamethod.html + + MethodType + MethodType-enum + + + + access + access + () + + + methodType + methodType + () + + + parameterNames + parameterNames + () + + + parameterTypes + parameterTypes + () + + + signature + signature + () + + + tag + tag + () + + + typeName + typeName + () + + + + QMetaObject + qmetaobject.html + + checkConnectArgs + checkConnectArgs + ( const char * signal, const char * method ) + + + classInfo + classInfo + ( int index ) + + + classInfoCount + classInfoCount + () + + + classInfoOffset + classInfoOffset + () + + + className + className + () + + + connectSlotsByName + connectSlotsByName + ( QObject * object ) + + + enumerator + enumerator + ( int index ) + + + enumeratorCount + enumeratorCount + () + + + enumeratorOffset + enumeratorOffset + () + + + indexOfClassInfo + indexOfClassInfo + ( const char * name ) + + + indexOfEnumerator + indexOfEnumerator + ( const char * name ) + + + indexOfMethod + indexOfMethod + ( const char * method ) + + + indexOfProperty + indexOfProperty + ( const char * name ) + + + indexOfSignal + indexOfSignal + ( const char * signal ) + + + indexOfSlot + indexOfSlot + ( const char * slot ) + + + invokeMethod + invokeMethod + ( QObject * obj, const char * member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument( 0 ) + + + invokeMethod + invokeMethod-2 + ( QObject * obj, const char * member, QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument( 0 ) + + + invokeMethod + invokeMethod-3 + ( QObject * obj, const char * member, Qt::ConnectionType type, QGenericArgument val0 = QGenericArgument( 0 ) + + + invokeMethod + invokeMethod-4 + ( QObject * obj, const char * member, QGenericArgument val0 = QGenericArgument( 0 ) + + + method + method + ( int index ) + + + methodCount + methodCount + () + + + methodOffset + methodOffset + () + + + normalizedSignature + normalizedSignature + ( const char * method ) + + + normalizedType + normalizedType + ( const char * type ) + + + property + property + ( int index ) + + + propertyCount + propertyCount + () + + + propertyOffset + propertyOffset + () + + + superClass + superClass + () + + + userProperty + userProperty + () + + + isEditable + isEditable + ( const QObject * object = 0 ) + + + + QMetaProperty + qmetaproperty.html + + enumerator + enumerator + () + + + isDesignable + isDesignable + ( const QObject * object = 0 ) + + + isEnumType + isEnumType + () + + + isFlagType + isFlagType + () + + + isReadable + isReadable + () + + + isResettable + isResettable + () + + + isScriptable + isScriptable + ( const QObject * object = 0 ) + + + isStored + isStored + ( const QObject * object = 0 ) + + + isUser + isUser + ( const QObject * object = 0 ) + + + isValid + isValid + () + + + isWritable + isWritable + () + + + name + name + () + + + read + read + ( const QObject * object ) + + + reset + reset + ( QObject * object ) + + + Type + type + QMetaProperty::type() + + + typeName + typeName + () + + + userType + userType + () + + + write + write + ( QObject * object, const QVariant & value ) + + + + QMetaType + qmetatype.html + + Type + Type-enum + + + + construct + construct + ( int type, const void * copy = 0 ) + + + destroy + destroy + ( int type, void * data ) + + + isRegistered + isRegistered + ( int type ) + + + load + load + ( QDataStream & stream, int type, void * data ) + + + save + save + ( QDataStream & stream, int type, const void * data ) + + + type + type + ( const char * typeName ) + + + typeName + typeName + ( int type ) + + + + QMimeData + qmimedata.html + + QMimeData + QMimeData + () + + + clear + clear + () + + + colorData + colorData + () + + + data + data + ( const QString & mimeType ) + + + formats + formats + () + + + hasColor + hasColor + () + + + hasFormat + hasFormat + ( const QString & mimeType ) + + + hasHtml + hasHtml + () + + + hasImage + hasImage + () + + + hasText + hasText + () + + + hasUrls + hasUrls + () + + + html + html + () + + + imageData + imageData + () + + + retrieveData + retrieveData + ( const QString & mimeType, QVariant::Type type ) + + + setColorData + setColorData + ( const QVariant & color ) + + + setData + setData + ( const QString & mimeType, const QByteArray & data ) + + + setHtml + setHtml + ( const QString & html ) + + + setImageData + setImageData + ( const QVariant & image ) + + + setText + setText + ( const QString & text ) + + + setUrls + setUrls + ( const QList<QUrl> & urls ) + + + text + text + () + + + urls + urls + () + + + + QMimeSource + qmimesource.html + + encodedData + encodedData + ( const char * format ) + + + format + format + ( int i = 0 ) + + + provides + provides + ( const char * mimeType ) + + + + QModelIndex + qmodelindex.html + + QModelIndex + QModelIndex + () + + + QModelIndex + QModelIndex-2 + ( const QModelIndex & other ) + + + child + child + ( int row, int column ) + + + column + column + () + + + data + data + ( int role = Qt::DisplayRole ) + + + ItemFlags + flags + QModelIndex::flags() + + + internalId + internalId + () + + + internalPointer + internalPointer + () + + + isValid + isValid + () + + + model + model + () + + + parent + parent + () + + + row + row + () + + + sibling + sibling + ( int row, int column ) + + + operator!= + operator-not-eq + ( const QModelIndex & other ) + + + operator< + operator-lt + ( const QModelIndex & other ) + + + operator== + operator-eq-eq + ( const QModelIndex & other ) + + + + QMotifStyle + qmotifstyle.html + + QMotifStyle + QMotifStyle + ( bool useHighlightCols = false ) + + + setUseHighlightColors + setUseHighlightColors + ( bool arg ) + + + useHighlightColors + useHighlightColors + () + + + + QMouseDriverFactory + qmousedriverfactory.html + + create + create + ( const QString & key, const QString & device ) + + + keys + keys + () + + + + QMouseDriverPlugin + qmousedriverplugin.html + + QMouseDriverPlugin + QMouseDriverPlugin + ( QObject * parent = 0 ) + + + create + create + ( const QString & key, const QString & device ) + + + keys + keys + () + + + QMouseEvent + QMouseEvent-3 + ( Type type, const QPoint & pos, Qt::ButtonState button, int state ) + + + QMouseEvent + QMouseEvent-4 + ( Type type, const QPoint & pos, const QPoint & globalPos, Qt::ButtonState button, int state ) + + + ButtonState + state + QMouseEvent::state() + + + ButtonState + stateAfter + QMouseEvent::stateAfter() + + + + QMouseEvent + qmouseevent.html + + QMouseEvent + QMouseEvent + ( Type type, const QPoint & position, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers ) + + + QMouseEvent + QMouseEvent-2 + ( Type type, const QPoint & pos, const QPoint & globalPos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers ) + + + MouseButton + button + QMouseEvent::button() + + + MouseButtons + buttons + QMouseEvent::buttons() + + + globalPos + globalPos + () + + + globalX + globalX + () + + + globalY + globalY + () + + + pos + pos + () + + + x + x + () + + + y + y + () + + + QMouseEvent + QMouseEvent-3 + ( Type type, const QPoint & pos, Qt::ButtonState button, int state ) + + + QMouseEvent + QMouseEvent-4 + ( Type type, const QPoint & pos, const QPoint & globalPos, Qt::ButtonState button, int state ) + + + ButtonState + state + QMouseEvent::state() + + + ButtonState + stateAfter + QMouseEvent::stateAfter() + + + + QMoveEvent + qmoveevent.html + + QMoveEvent + QMoveEvent + ( const QPoint & pos, const QPoint & oldPos ) + + + oldPos + oldPos + () + + + pos + pos + () + + + finished + finished-2 + () + + + frameImage + frameImage + () + + + frameNumber + frameNumber + () + + + framePixmap + framePixmap + () + + + isNull + isNull + () + + + pause + pause + () + + + paused + paused + () + + + restart + restart + () + + + running + running + () + + + step + step + () + + + unpause + unpause + () + + + + QMovie + qmovie.html + + CacheMode + CacheMode-enum + + + + MovieState + MovieState-enum + + + + QMovie + QMovie + ( QObject * parent = 0 ) + + + QMovie + QMovie-2 + ( QIODevice * device, const QByteArray & format = QByteArray() + + + QMovie + QMovie-3 + ( const QString & fileName, const QByteArray & format = QByteArray() + + + backgroundColor + backgroundColor + () + + + currentFrameNumber + currentFrameNumber + () + + + currentImage + currentImage + () + + + currentPixmap + currentPixmap + () + + + device + device + () + + + error + error + ( QImageReader::ImageReaderError error ) + + + fileName + fileName + () + + + finished + finished + () + + + format + format + () + + + frameChanged + frameChanged + ( int frameNumber ) + + + frameCount + frameCount + () + + + frameRect + frameRect + () + + + isValid + isValid + () + + + jumpToFrame + jumpToFrame + ( int frameNumber ) + + + jumpToNextFrame + jumpToNextFrame + () + + + loopCount + loopCount + () + + + nextFrameDelay + nextFrameDelay + () + + + resized + resized + ( const QSize & size ) + + + scaledSize + scaledSize + () + + + setBackgroundColor + setBackgroundColor + ( const QColor & color ) + + + setDevice + setDevice + ( QIODevice * device ) + + + setFileName + setFileName + ( const QString & fileName ) + + + setFormat + setFormat + ( const QByteArray & format ) + + + setPaused + setPaused + ( bool paused ) + + + setScaledSize + setScaledSize + ( const QSize & size ) + + + start + start + () + + + started + started + () + + + state + state + () + + + stateChanged + stateChanged + ( QMovie::MovieState state ) + + + stop + stop + () + + + supportedFormats + supportedFormats + () + + + updated + updated + ( const QRect & rect ) + + + finished + finished-2 + () + + + frameImage + frameImage + () + + + frameNumber + frameNumber + () + + + framePixmap + framePixmap + () + + + isNull + isNull + () + + + pause + pause + () + + + paused + paused + () + + + restart + restart + () + + + running + running + () + + + step + step + () + + + unpause + unpause + () + + + + QMultiHash + qmultihash.html + + QMultiHash + QMultiHash + () + + + QMultiHash + QMultiHash-2 + ( const QHash<Key, T> & other ) + + + constFind + constFind + ( const Key & key, const T & value ) + + + contains + contains + ( const Key & key, const T & value ) + + + count + count + ( const Key & key, const T & value ) + + + find + find + ( const Key & key, const T & value ) + + + find + find-2 + ( const Key & key, const T & value ) + + + insert + insert + ( const Key & key, const T & value ) + + + remove + remove + ( const Key & key, const T & value ) + + + replace + replace + ( const Key & key, const T & value ) + + + operator+ + operator-2b + ( const QMultiHash & other ) + + + operator+= + operator-2b-eq + ( const QMultiHash & other ) + + + + QMultiMap + qmultimap.html + + QMultiMap + QMultiMap + () + + + QMultiMap + QMultiMap-2 + ( const QMap<Key, T> & other ) + + + constFind + constFind + ( const Key & key, const T & value ) + + + contains + contains + ( const Key & key, const T & value ) + + + count + count + ( const Key & key, const T & value ) + + + find + find + ( const Key & key, const T & value ) + + + find + find-2 + ( const Key & key, const T & value ) + + + insert + insert + ( const Key & key, const T & value ) + + + remove + remove + ( const Key & key, const T & value ) + + + replace + replace + ( const Key & key, const T & value ) + + + operator+ + operator-2b + ( const QMultiMap & other ) + + + operator+= + operator-2b-eq + ( const QMultiMap & other ) + + + + QMutableHashIterator + qmutablehashiterator.html + + QMutableHashIterator + QMutableHashIterator + ( QHash<Key, T> & hash ) + + + findNext + findNext + ( const T & value ) + + + findPrevious + findPrevious + ( const T & value ) + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + key + key + () + + + next + next + () + + + peekNext + peekNext + () + + + peekPrevious + peekPrevious + () + + + previous + previous + () + + + remove + remove + () + + + setValue + setValue + ( const T & value ) + + + toBack + toBack + () + + + toFront + toFront + () + + + value + value + () + + + value + value-2 + () + + + operator= + operator-eq + ( QHash<Key, T> & hash ) + + + + QMutableLinkedListIterator + qmutablelinkedlistiterator.html + + QMutableLinkedListIterator + QMutableLinkedListIterator + ( QLinkedList<T> & list ) + + + findNext + findNext + ( const T & value ) + + + findPrevious + findPrevious + ( const T & value ) + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + insert + insert + ( const T & value ) + + + next + next + () + + + peekNext + peekNext + () + + + peekPrevious + peekPrevious + () + + + previous + previous + () + + + remove + remove + () + + + setValue + setValue + ( const T & value ) + + + toBack + toBack + () + + + toFront + toFront + () + + + value + value + () + + + value + value-2 + () + + + operator= + operator-eq + ( QLinkedList<T> & list ) + + + + QMutableListIterator + qmutablelistiterator.html + + QMutableListIterator + QMutableListIterator + ( QList<T> & list ) + + + findNext + findNext + ( const T & value ) + + + findPrevious + findPrevious + ( const T & value ) + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + insert + insert + ( const T & value ) + + + next + next + () + + + peekNext + peekNext + () + + + peekPrevious + peekPrevious + () + + + previous + previous + () + + + remove + remove + () + + + setValue + setValue + ( const T & value ) + + + toBack + toBack + () + + + toFront + toFront + () + + + value + value + () + + + value + value-2 + () + + + operator= + operator-eq + ( QList<T> & list ) + + + + QMutableMapIterator + qmutablemapiterator.html + + QMutableMapIterator + QMutableMapIterator + ( QMap<Key, T> & map ) + + + findNext + findNext + ( const T & value ) + + + findPrevious + findPrevious + ( const T & value ) + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + key + key + () + + + next + next + () + + + peekNext + peekNext + () + + + peekPrevious + peekPrevious + () + + + previous + previous + () + + + remove + remove + () + + + setValue + setValue + ( const T & value ) + + + toBack + toBack + () + + + toFront + toFront + () + + + value + value + () + + + value + value-2 + () + + + operator= + operator-eq + ( QMap<Key, T> & map ) + + + + QMutableSetIterator + qmutablesetiterator.html + + QMutableSetIterator + QMutableSetIterator + ( QSet<T> & set ) + + + findNext + findNext + ( const T & value ) + + + findPrevious + findPrevious + ( const T & value ) + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + next + next + () + + + peekNext + peekNext + () + + + peekPrevious + peekPrevious + () + + + previous + previous + () + + + remove + remove + () + + + toBack + toBack + () + + + toFront + toFront + () + + + value + value + () + + + operator= + operator-eq + ( QSet<T> & set ) + + + + QMutableVectorIterator + qmutablevectoriterator.html + + QMutableVectorIterator + QMutableVectorIterator + ( QVector<T> & vector ) + + + findNext + findNext + ( const T & value ) + + + findPrevious + findPrevious + ( const T & value ) + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + insert + insert + ( const T & value ) + + + next + next + () + + + peekNext + peekNext + () + + + peekPrevious + peekPrevious + () + + + previous + previous + () + + + remove + remove + () + + + setValue + setValue + ( const T & value ) + + + toBack + toBack + () + + + toFront + toFront + () + + + value + value + () + + + value + value-2 + () + + + operator= + operator-eq + ( QVector<T> & vector ) + + + QMutex + QMutex-2 + ( bool recursive ) + + + locked + locked + () + + + + QMutex + qmutex.html + + RecursionMode + RecursionMode-enum + + + + QMutex + QMutex + ( RecursionMode mode = NonRecursive ) + + + lock + lock + () + + + tryLock + tryLock + () + + + tryLock + tryLock-2 + ( int timeout ) + + + unlock + unlock + () + + + QMutex + QMutex-2 + ( bool recursive ) + + + locked + locked + () + + + + QMutexLocker + qmutexlocker.html + + QMutexLocker + QMutexLocker + ( QMutex * mutex ) + + + mutex + mutex + () + + + relock + relock + () + + + unlock + unlock + () + + + + QNetworkAddressEntry + qnetworkaddressentry.html + + QNetworkAddressEntry + QNetworkAddressEntry + () + + + QNetworkAddressEntry + QNetworkAddressEntry-2 + ( const QNetworkAddressEntry & other ) + + + broadcast + broadcast + () + + + ip + ip + () + + + netmask + netmask + () + + + setBroadcast + setBroadcast + ( const QHostAddress & newBroadcast ) + + + setIp + setIp + ( const QHostAddress & newIp ) + + + setNetmask + setNetmask + ( const QHostAddress & newNetmask ) + + + operator= + operator-eq + ( const QNetworkAddressEntry & other ) + + + + QNetworkInterface + qnetworkinterface.html + + QNetworkInterface + QNetworkInterface + () + + + QNetworkInterface + QNetworkInterface-2 + ( const QNetworkInterface & other ) + + + addressEntries + addressEntries + () + + + allAddresses + allAddresses + () + + + allInterfaces + allInterfaces + () + + + flags + flags + () + + + hardwareAddress + hardwareAddress + () + + + interfaceFromIndex + interfaceFromIndex + ( int index ) + + + interfaceFromName + interfaceFromName + ( const QString & name ) + + + isValid + isValid + () + + + name + name + () + + + operator= + operator-eq + ( const QNetworkInterface & other ) + + + + QNetworkProxy + qnetworkproxy.html + + ProxyType + ProxyType-enum + + + + QNetworkProxy + QNetworkProxy + () + + + QNetworkProxy + QNetworkProxy-2 + ( ProxyType type, const QString & hostName = QString() + + + QNetworkProxy + QNetworkProxy-3 + ( const QNetworkProxy & other ) + + + applicationProxy + applicationProxy + () + + + hostName + hostName + () + + + password + password + () + + + port + port + () + + + setApplicationProxy + setApplicationProxy + ( const QNetworkProxy & networkProxy ) + + + setHostName + setHostName + ( const QString & hostName ) + + + setPassword + setPassword + ( const QString & password ) + + + setPort + setPort + ( quint16 port ) + + + setType + setType + ( QNetworkProxy::ProxyType type ) + + + setUser + setUser + ( const QString & user ) + + + ProxyType + type + QNetworkProxy::type() + + + user + user + () + + + operator= + operator-eq + ( const QNetworkProxy & other ) + + + QObject + QObject-3 + ( QObject * parent, const char * name ) + + + checkConnectArgs + checkConnectArgs + ( const char * signal, const QObject * object, const char * method ) + + + child + child + ( const char * objName, const char * inheritsClass = 0, bool recursiveSearch = true ) + + + className + className + () + + + insertChild + insertChild + ( QObject * object ) + + + isA + isA + ( const char * className ) + + + name + name + () + + + name + name-2 + ( const char * defaultName ) + + + normalizeSignalSlot + normalizeSignalSlot + ( const char * signalSlot ) + + + removeChild + removeChild + ( QObject * object ) + + + setName + setName + ( const char * name ) + + + + QObject + qobject.html + + QObject + QObject + ( QObject * parent = 0 ) + + + blockSignals + blockSignals + ( bool block ) + + + childEvent + childEvent + ( QChildEvent * event ) + + + children + children + () + + + connect + connect + ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoCompatConnection ) + + + connect + connect-2 + ( const QObject * sender, const char * signal, const char * method, Qt::ConnectionType type = Qt::AutoCompatConnection ) + + + connectNotify + connectNotify + ( const char * signal ) + + + customEvent + customEvent + ( QEvent * event ) + + + deleteLater + deleteLater + () + + + destroyed + destroyed + ( QObject * obj = 0 ) + + + disconnect + disconnect + ( const QObject * sender, const char * signal, const QObject * receiver, const char * method ) + + + disconnect + disconnect-2 + ( const char * signal = 0, const QObject * receiver = 0, const char * method = 0 ) + + + disconnect + disconnect-3 + ( const QObject * receiver, const char * method = 0 ) + + + disconnectNotify + disconnectNotify + ( const char * signal ) + + + dumpObjectInfo + dumpObjectInfo + () + + + dumpObjectTree + dumpObjectTree + () + + + dynamicPropertyNames + dynamicPropertyNames + () + + + event + event + ( QEvent * e ) + + + eventFilter + eventFilter + ( QObject * watched, QEvent * event ) + + + findChild + findChild + ( const QString & name = QString() + + + findChildren + findChildren + ( const QString & name = QString() + + + findChildren + findChildren-2 + ( const QRegExp & regExp ) + + + inherits + inherits + ( const char * className ) + + + installEventFilter + installEventFilter + ( QObject * filterObj ) + + + isWidgetType + isWidgetType + () + + + killTimer + killTimer + ( int id ) + + + metaObject + metaObject + () + + + moveToThread + moveToThread + ( QThread * targetThread ) + + + parent + parent + () + + + property + property + ( const char * name ) + + + receivers + receivers + ( const char * signal ) + + + removeEventFilter + removeEventFilter + ( QObject * obj ) + + + sender + sender + () + + + setParent + setParent + ( QObject * parent ) + + + setProperty + setProperty + ( const char * name, const QVariant & value ) + + + signalsBlocked + signalsBlocked + () + + + startTimer + startTimer + ( int interval ) + + + thread + thread + () + + + timerEvent + timerEvent + ( QTimerEvent * event ) + + + tr + tr + ( const char * sourceText, const char * comment = 0, int n = -1 ) + + + trUtf8 + trUtf8 + ( const char * sourceText, const char * comment = 0, int n = -1 ) + + + staticMetaObject + staticMetaObject-var + + + + QObject + QObject-3 + ( QObject * parent, const char * name ) + + + checkConnectArgs + checkConnectArgs + ( const char * signal, const QObject * object, const char * method ) + + + child + child + ( const char * objName, const char * inheritsClass = 0, bool recursiveSearch = true ) + + + className + className + () + + + insertChild + insertChild + ( QObject * object ) + + + isA + isA + ( const char * className ) + + + name + name + () + + + name + name-2 + ( const char * defaultName ) + + + normalizeSignalSlot + normalizeSignalSlot + ( const char * signalSlot ) + + + removeChild + removeChild + ( QObject * object ) + + + setName + setName + ( const char * name ) + + + + QObjectCleanupHandler + qobjectcleanuphandler.html + + QObjectCleanupHandler + QObjectCleanupHandler + () + + + add + add + ( QObject * object ) + + + clear + clear + () + + + isEmpty + isEmpty + () + + + remove + remove + ( QObject * object ) + + + + QPageSetupDialog + qpagesetupdialog.html + + QPageSetupDialog + QPageSetupDialog + ( QPrinter * printer, QWidget * parent = 0 ) + + + printer + printer + () + + + x11AppCells + x11AppCells + ( int screen = -1 ) + + + HANDLE + x11AppColormap + QPaintDevice::x11AppColormap( int screen = -1 ) + + + x11AppDefaultColormap + x11AppDefaultColormap + ( int screen = -1 ) + + + x11AppDefaultVisual + x11AppDefaultVisual + ( int screen = -1 ) + + + x11AppDepth + x11AppDepth + ( int screen = -1 ) + + + x11AppDisplay + x11AppDisplay + () + + + x11AppDpiX + x11AppDpiX + ( int screen = -1 ) + + + x11AppDpiY + x11AppDpiY + ( int screen = -1 ) + + + HANDLE + x11AppRootWindow + QPaintDevice::x11AppRootWindow( int screen = -1 ) + + + x11AppScreen + x11AppScreen + () + + + x11AppVisual + x11AppVisual + ( int screen = -1 ) + + + x11Cells + x11Cells + () + + + HANDLE + x11Colormap + QPaintDevice::x11Colormap() + + + x11DefaultColormap + x11DefaultColormap + () + + + x11DefaultVisual + x11DefaultVisual + () + + + x11Depth + x11Depth + () + + + x11Display + x11Display + () + + + x11Screen + x11Screen + () + + + x11SetAppDpiX + x11SetAppDpiX + ( int dpi, int screen ) + + + x11SetAppDpiY + x11SetAppDpiY + ( int dpi, int screen ) + + + x11Visual + x11Visual + () + + + + QPaintDevice + qpaintdevice.html + + PaintDeviceMetric + PaintDeviceMetric-enum + + + + QPaintDevice + QPaintDevice + () + + + depth + depth + () + + + height + height + () + + + heightMM + heightMM + () + + + logicalDpiX + logicalDpiX + () + + + logicalDpiY + logicalDpiY + () + + + metric + metric + ( PaintDeviceMetric metric ) + + + numColors + numColors + () + + + paintEngine + paintEngine + () + + + paintingActive + paintingActive + () + + + physicalDpiX + physicalDpiX + () + + + physicalDpiY + physicalDpiY + () + + + width + width + () + + + widthMM + widthMM + () + + + x11AppCells + x11AppCells + ( int screen = -1 ) + + + HANDLE + x11AppColormap + QPaintDevice::x11AppColormap( int screen = -1 ) + + + x11AppDefaultColormap + x11AppDefaultColormap + ( int screen = -1 ) + + + x11AppDefaultVisual + x11AppDefaultVisual + ( int screen = -1 ) + + + x11AppDepth + x11AppDepth + ( int screen = -1 ) + + + x11AppDisplay + x11AppDisplay + () + + + x11AppDpiX + x11AppDpiX + ( int screen = -1 ) + + + x11AppDpiY + x11AppDpiY + ( int screen = -1 ) + + + HANDLE + x11AppRootWindow + QPaintDevice::x11AppRootWindow( int screen = -1 ) + + + x11AppScreen + x11AppScreen + () + + + x11AppVisual + x11AppVisual + ( int screen = -1 ) + + + x11Cells + x11Cells + () + + + HANDLE + x11Colormap + QPaintDevice::x11Colormap() + + + x11DefaultColormap + x11DefaultColormap + () + + + x11DefaultVisual + x11DefaultVisual + () + + + x11Depth + x11Depth + () + + + x11Display + x11Display + () + + + x11Screen + x11Screen + () + + + x11SetAppDpiX + x11SetAppDpiX + ( int dpi, int screen ) + + + x11SetAppDpiY + x11SetAppDpiY + ( int dpi, int screen ) + + + x11Visual + x11Visual + () + + + + QPaintEngine + qpaintengine.html + + PolygonDrawMode + PolygonDrawMode-enum + + + + Type + Type-enum + + + + QPaintEngine + QPaintEngine + ( PaintEngineFeatures caps = 0 ) + + + begin + begin + ( QPaintDevice * pdev ) + + + drawEllipse + drawEllipse + ( const QRectF & rect ) + + + drawEllipse + drawEllipse-2 + ( const QRect & rect ) + + + drawImage + drawImage + ( const QRectF & rectangle, const QImage & image, const QRectF & sr, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + drawLines + drawLines + ( const QLineF * lines, int lineCount ) + + + drawLines + drawLines-2 + ( const QLine * lines, int lineCount ) + + + drawPath + drawPath + ( const QPainterPath & path ) + + + drawPixmap + drawPixmap + ( const QRectF & r, const QPixmap & pm, const QRectF & sr ) + + + drawPoints + drawPoints + ( const QPointF * points, int pointCount ) + + + drawPoints + drawPoints-2 + ( const QPoint * points, int pointCount ) + + + drawPolygon + drawPolygon + ( const QPointF * points, int pointCount, PolygonDrawMode mode ) + + + drawPolygon + drawPolygon-2 + ( const QPoint * points, int pointCount, PolygonDrawMode mode ) + + + drawRects + drawRects + ( const QRectF * rects, int rectCount ) + + + drawRects + drawRects-2 + ( const QRect * rects, int rectCount ) + + + drawTextItem + drawTextItem + ( const QPointF & p, const QTextItem & textItem ) + + + drawTiledPixmap + drawTiledPixmap + ( const QRectF & rect, const QPixmap & pixmap, const QPointF & p ) + + + end + end + () + + + hasFeature + hasFeature + ( PaintEngineFeatures feature ) + + + isActive + isActive + () + + + paintDevice + paintDevice + () + + + painter + painter + () + + + setActive + setActive + ( bool state ) + + + type + type + () + + + updateState + updateState + ( const QPaintEngineState & state ) + + + + QPaintEngineState + qpaintenginestate.html + + backgroundBrush + backgroundBrush + () + + + BGMode + backgroundMode + QPaintEngineState::backgroundMode() + + + brush + brush + () + + + brushNeedsResolving + brushNeedsResolving + () + + + brushOrigin + brushOrigin + () + + + ClipOperation + clipOperation + QPaintEngineState::clipOperation() + + + clipPath + clipPath + () + + + clipRegion + clipRegion + () + + + CompositionMode + compositionMode + QPaintEngineState::compositionMode() + + + font + font + () + + + isClipEnabled + isClipEnabled + () + + + matrix + matrix + () + + + opacity + opacity + () + + + painter + painter + () + + + pen + pen + () + + + penNeedsResolving + penNeedsResolving + () + + + RenderHints + renderHints + QPaintEngineState::renderHints() + + + DirtyFlags + state + QPaintEngineState::state() + + + transform + transform + () + + + matrix + matrix + () + + + matrixEnabled + matrixEnabled + () + + + setMatrix + setMatrix + ( const QMatrix & matrix, bool combine = false ) + + + setMatrixEnabled + setMatrixEnabled + ( bool enable ) + + + backgroundColor + backgroundColor + () + + + begin + begin-2 + ( QPaintDevice * device, const QWidget * init ) + + + boundingRect + boundingRect-2 + ( const QRect & rectangle, int flags, const QString & text, int length ) + + + boundingRect + boundingRect-3 + ( int x, int y, int width, int height, int flags, const QString & text, int length ) + + + drawConvexPolygon + drawConvexPolygon-5 + ( const QPolygonF & polygon, int index, int count = -1 ) + + + drawConvexPolygon + drawConvexPolygon-6 + ( const QPolygon & polygon, int index, int count = -1 ) + + + drawCubicBezier + drawCubicBezier + ( const QPolygon & controlPoints, int index = 0 ) + + + drawLineSegments + drawLineSegments + ( const QPolygon & polygon, int index = 0, int count = -1 ) + + + drawPoints + drawPoints-5 + ( const QPolygon & polygon, int index, int count = -1 ) + + + drawPolygon + drawPolygon-3 + ( const QPolygonF & polygon, bool winding, int index = 0, int count = -1 ) + + + drawPolygon + drawPolygon-4 + ( const QPolygon & polygon, bool winding, int index = 0, int count = -1 ) + + + drawPolyline + drawPolyline-3 + ( const QPolygon & polygon, int index, int count = -1 ) + + + drawText + drawText-2 + ( int x, int y, const QString & text, int pos, int length ) + + + drawText + drawText-3 + ( const QPoint & point, const QString & text, int pos, int length ) + + + drawText + drawText-4 + ( int x, int y, const QString & text, int length ) + + + drawText + drawText-5 + ( const QPoint & point, const QString & text, int length ) + + + drawText + drawText-6 + ( const QRect & rectangle, int flags, const QString & text, int length, QRect * br = 0 ) + + + drawText + drawText-7 + ( int x, int y, int width, int height, int flags, const QString & text, int length, QRect * br = 0 ) + + + hasViewXForm + hasViewXForm + () + + + hasWorldXForm + hasWorldXForm + () + + + redirect + redirect + ( QPaintDevice * pdev, QPaintDevice * replacement ) + + + redirect + redirect-2 + ( QPaintDevice * pdev ) + + + resetXForm + resetXForm + () + + + setBackgroundColor + setBackgroundColor + ( const QColor & color ) + + + setViewXForm + setViewXForm + ( bool enabled ) + + + setWorldXForm + setWorldXForm + ( bool enabled ) + + + translationX + translationX + () + + + translationY + translationY + () + + + xForm + xForm + ( const QPoint & point ) + + + xForm + xForm-2 + ( const QRect & rectangle ) + + + xForm + xForm-3 + ( const QPolygon & polygon ) + + + xForm + xForm-4 + ( const QPolygon & polygon, int index, int count ) + + + xFormDev + xFormDev + ( const QPoint & point ) + + + xFormDev + xFormDev-2 + ( const QRect & rectangle ) + + + xFormDev + xFormDev-3 + ( const QPolygon & polygon ) + + + xFormDev + xFormDev-4 + ( const QPolygon & polygon, int index, int count ) + + + + QPainter + qpainter.html + + CompositionMode + CompositionMode-enum + + + + QPainter + QPainter + () + + + QPainter + QPainter-2 + ( QPaintDevice * device ) + + + background + background + () + + + BGMode + backgroundMode + QPainter::backgroundMode() + + + begin + begin + ( QPaintDevice * device ) + + + boundingRect + boundingRect + ( const QRectF & rectangle, int flags, const QString & text ) + + + boundingRect + boundingRect-4 + ( const QRect & rectangle, int flags, const QString & text ) + + + boundingRect + boundingRect-5 + ( int x, int y, int w, int h, int flags, const QString & text ) + + + boundingRect + boundingRect-6 + ( const QRectF & rectangle, const QString & text, const QTextOption & option = QTextOption() + + + brush + brush + () + + + brushOrigin + brushOrigin + () + + + clipPath + clipPath + () + + + clipRegion + clipRegion + () + + + combinedMatrix + combinedMatrix + () + + + combinedTransform + combinedTransform + () + + + compositionMode + compositionMode + () + + + device + device + () + + + deviceMatrix + deviceMatrix + () + + + deviceTransform + deviceTransform + () + + + drawArc + drawArc + ( const QRectF & rectangle, int startAngle, int spanAngle ) + + + drawArc + drawArc-2 + ( const QRect & rectangle, int startAngle, int spanAngle ) + + + drawArc + drawArc-3 + ( int x, int y, int width, int height, int startAngle, int spanAngle ) + + + drawChord + drawChord + ( const QRectF & rectangle, int startAngle, int spanAngle ) + + + drawChord + drawChord-2 + ( const QRect & rectangle, int startAngle, int spanAngle ) + + + drawChord + drawChord-3 + ( int x, int y, int width, int height, int startAngle, int spanAngle ) + + + drawConvexPolygon + drawConvexPolygon + ( const QPointF * points, int pointCount ) + + + drawConvexPolygon + drawConvexPolygon-2 + ( const QPoint * points, int pointCount ) + + + drawConvexPolygon + drawConvexPolygon-3 + ( const QPolygonF & polygon ) + + + drawConvexPolygon + drawConvexPolygon-4 + ( const QPolygon & polygon ) + + + drawEllipse + drawEllipse + ( const QRectF & rectangle ) + + + drawEllipse + drawEllipse-2 + ( const QRect & rectangle ) + + + drawEllipse + drawEllipse-3 + ( int x, int y, int width, int height ) + + + drawImage + drawImage + ( const QRectF & target, const QImage & image, const QRectF & source, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + drawImage + drawImage-2 + ( const QRect & target, const QImage & image, const QRect & source, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + drawImage + drawImage-3 + ( const QPointF & point, const QImage & image ) + + + drawImage + drawImage-4 + ( const QPoint & point, const QImage & image ) + + + drawImage + drawImage-5 + ( const QPointF & point, const QImage & image, const QRectF & source, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + drawImage + drawImage-6 + ( const QPoint & point, const QImage & image, const QRect & source, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + drawImage + drawImage-7 + ( const QRectF & rectangle, const QImage & image ) + + + drawImage + drawImage-8 + ( const QRect & rectangle, const QImage & image ) + + + drawImage + drawImage-9 + ( int x, int y, const QImage & image, int sx = 0, int sy = 0, int sw = -1, int sh = -1, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + drawLine + drawLine + ( const QLineF & line ) + + + drawLine + drawLine-2 + ( const QLine & line ) + + + drawLine + drawLine-3 + ( const QPoint & p1, const QPoint & p2 ) + + + drawLine + drawLine-4 + ( const QPointF & p1, const QPointF & p2 ) + + + drawLine + drawLine-5 + ( int x1, int y1, int x2, int y2 ) + + + drawLines + drawLines + ( const QLineF * lines, int lineCount ) + + + drawLines + drawLines-2 + ( const QLine * lines, int lineCount ) + + + drawLines + drawLines-3 + ( const QPointF * pointPairs, int lineCount ) + + + drawLines + drawLines-4 + ( const QPoint * pointPairs, int lineCount ) + + + drawLines + drawLines-5 + ( const QVector<QPointF> & pointPairs ) + + + drawLines + drawLines-6 + ( const QVector<QPoint> & pointPairs ) + + + drawLines + drawLines-7 + ( const QVector<QLineF> & lines ) + + + drawLines + drawLines-8 + ( const QVector<QLine> & lines ) + + + drawPath + drawPath + ( const QPainterPath & path ) + + + drawPicture + drawPicture + ( const QPointF & point, const QPicture & picture ) + + + drawPicture + drawPicture-2 + ( const QPoint & point, const QPicture & picture ) + + + drawPicture + drawPicture-3 + ( int x, int y, const QPicture & picture ) + + + drawPie + drawPie + ( const QRectF & rectangle, int startAngle, int spanAngle ) + + + drawPie + drawPie-2 + ( const QRect & rectangle, int startAngle, int spanAngle ) + + + drawPie + drawPie-3 + ( int x, int y, int width, int height, int startAngle, int spanAngle ) + + + drawPixmap + drawPixmap + ( const QRectF & target, const QPixmap & pixmap, const QRectF & source ) + + + drawPixmap + drawPixmap-2 + ( const QRect & target, const QPixmap & pixmap, const QRect & source ) + + + drawPixmap + drawPixmap-3 + ( const QPointF & point, const QPixmap & pixmap, const QRectF & source ) + + + drawPixmap + drawPixmap-4 + ( const QPoint & point, const QPixmap & pixmap, const QRect & source ) + + + drawPixmap + drawPixmap-5 + ( const QPointF & point, const QPixmap & pixmap ) + + + drawPixmap + drawPixmap-6 + ( const QPoint & point, const QPixmap & pixmap ) + + + drawPixmap + drawPixmap-7 + ( int x, int y, const QPixmap & pixmap ) + + + drawPixmap + drawPixmap-8 + ( const QRect & rectangle, const QPixmap & pixmap ) + + + drawPixmap + drawPixmap-9 + ( int x, int y, int width, int height, const QPixmap & pixmap ) + + + drawPixmap + drawPixmap-10 + ( int x, int y, int w, int h, const QPixmap & pixmap, int sx, int sy, int sw, int sh ) + + + drawPixmap + drawPixmap-11 + ( int x, int y, const QPixmap & pixmap, int sx, int sy, int sw, int sh ) + + + drawPoint + drawPoint + ( const QPointF & position ) + + + drawPoint + drawPoint-2 + ( const QPoint & position ) + + + drawPoint + drawPoint-3 + ( int x, int y ) + + + drawPoints + drawPoints + ( const QPointF * points, int pointCount ) + + + drawPoints + drawPoints-2 + ( const QPoint * points, int pointCount ) + + + drawPoints + drawPoints-3 + ( const QPolygonF & points ) + + + drawPoints + drawPoints-4 + ( const QPolygon & points ) + + + drawPolygon + drawPolygon + ( const QPointF * points, int pointCount, Qt::FillRule fillRule = Qt::OddEvenFill ) + + + drawPolygon + drawPolygon-2 + ( const QPoint * points, int pointCount, Qt::FillRule fillRule = Qt::OddEvenFill ) + + + drawPolygon + drawPolygon-5 + ( const QPolygonF & points, Qt::FillRule fillRule = Qt::OddEvenFill ) + + + drawPolygon + drawPolygon-6 + ( const QPolygon & points, Qt::FillRule fillRule = Qt::OddEvenFill ) + + + drawPolyline + drawPolyline + ( const QPointF * points, int pointCount ) + + + drawPolyline + drawPolyline-2 + ( const QPoint * points, int pointCount ) + + + drawPolyline + drawPolyline-4 + ( const QPolygonF & points ) + + + drawPolyline + drawPolyline-5 + ( const QPolygon & points ) + + + drawRect + drawRect + ( const QRectF & rectangle ) + + + drawRect + drawRect-2 + ( const QRect & rectangle ) + + + drawRect + drawRect-3 + ( int x, int y, int width, int height ) + + + drawRects + drawRects + ( const QRectF * rectangles, int rectCount ) + + + drawRects + drawRects-2 + ( const QRect * rectangles, int rectCount ) + + + drawRects + drawRects-3 + ( const QVector<QRectF> & rectangles ) + + + drawRects + drawRects-4 + ( const QVector<QRect> & rectangles ) + + + drawRoundRect + drawRoundRect + ( const QRectF & r, int xRnd = 25, int yRnd = 25 ) + + + drawRoundRect + drawRoundRect-2 + ( const QRect & r, int xRnd = 25, int yRnd = 25 ) + + + drawRoundRect + drawRoundRect-3 + ( int x, int y, int w, int h, int xRnd = 25, int yRnd = 25 ) + + + drawText + drawText + ( const QPointF & position, const QString & text ) + + + drawText + drawText-8 + ( const QPoint & position, const QString & text ) + + + drawText + drawText-9 + ( const QRectF & rectangle, int flags, const QString & text, QRectF * boundingRect = 0 ) + + + drawText + drawText-10 + ( const QRect & rectangle, int flags, const QString & text, QRect * boundingRect = 0 ) + + + drawText + drawText-11 + ( int x, int y, const QString & text ) + + + drawText + drawText-12 + ( int x, int y, int width, int height, int flags, const QString & text, QRect * boundingRect = 0 ) + + + drawText + drawText-13 + ( const QRectF & rectangle, const QString & text, const QTextOption & option = QTextOption() + + + drawTiledPixmap + drawTiledPixmap + ( const QRectF & rectangle, const QPixmap & pixmap, const QPointF & position = QPointF() + + + drawTiledPixmap + drawTiledPixmap-2 + ( const QRect & rectangle, const QPixmap & pixmap, const QPoint & position = QPoint() + + + drawTiledPixmap + drawTiledPixmap-3 + ( int x, int y, int width, int height, const QPixmap & pixmap, int sx = 0, int sy = 0 ) + + + end + end + () + + + eraseRect + eraseRect + ( const QRectF & rectangle ) + + + eraseRect + eraseRect-2 + ( const QRect & rectangle ) + + + eraseRect + eraseRect-3 + ( int x, int y, int width, int height ) + + + fillPath + fillPath + ( const QPainterPath & path, const QBrush & brush ) + + + fillRect + fillRect + ( const QRectF & rectangle, const QBrush & brush ) + + + fillRect + fillRect-2 + ( const QRect & rectangle, const QBrush & brush ) + + + fillRect + fillRect-3 + ( int x, int y, int width, int height, const QBrush & brush ) + + + font + font + () + + + fontInfo + fontInfo + () + + + fontMetrics + fontMetrics + () + + + hasClipping + hasClipping + () + + + initFrom + initFrom + ( const QWidget * widget ) + + + isActive + isActive + () + + + LayoutDirection + layoutDirection + QPainter::layoutDirection() + + + opacity + opacity + () + + + paintEngine + paintEngine + () + + + pen + pen + () + + + redirected + redirected + ( const QPaintDevice * device, QPoint * offset = 0 ) + + + renderHints + renderHints + () + + + resetMatrix + resetMatrix + () + + + resetTransform + resetTransform + () + + + restore + restore + () + + + restoreRedirected + restoreRedirected + ( const QPaintDevice * device ) + + + rotate + rotate + ( qreal angle ) + + + save + save + () + + + scale + scale + ( qreal sx, qreal sy ) + + + setBackground + setBackground + ( const QBrush & brush ) + + + setBackgroundMode + setBackgroundMode + ( Qt::BGMode mode ) + + + setBrush + setBrush + ( const QBrush & brush ) + + + setBrush + setBrush-2 + ( Qt::BrushStyle style ) + + + setBrushOrigin + setBrushOrigin + ( const QPointF & position ) + + + setBrushOrigin + setBrushOrigin-2 + ( const QPoint & position ) + + + setBrushOrigin + setBrushOrigin-3 + ( int x, int y ) + + + setClipPath + setClipPath + ( const QPainterPath & path, Qt::ClipOperation operation = Qt::ReplaceClip ) + + + setClipRect + setClipRect + ( const QRectF & rectangle, Qt::ClipOperation operation = Qt::ReplaceClip ) + + + setClipRect + setClipRect-2 + ( int x, int y, int width, int height, Qt::ClipOperation operation = Qt::ReplaceClip ) + + + setClipRect + setClipRect-3 + ( const QRect & rectangle, Qt::ClipOperation operation = Qt::ReplaceClip ) + + + setClipRegion + setClipRegion + ( const QRegion & region, Qt::ClipOperation operation = Qt::ReplaceClip ) + + + setClipping + setClipping + ( bool enable ) + + + setCompositionMode + setCompositionMode + ( CompositionMode mode ) + + + setFont + setFont + ( const QFont & font ) + + + setLayoutDirection + setLayoutDirection + ( Qt::LayoutDirection direction ) + + + setOpacity + setOpacity + ( qreal opacity ) + + + setPen + setPen + ( const QPen & pen ) + + + setPen + setPen-2 + ( const QColor & color ) + + + setPen + setPen-3 + ( Qt::PenStyle style ) + + + setRedirected + setRedirected + ( const QPaintDevice * device, QPaintDevice * replacement, const QPoint & offset = QPoint() + + + setRenderHint + setRenderHint + ( RenderHint hint, bool on = true ) + + + setRenderHints + setRenderHints + ( RenderHints hints, bool on = true ) + + + setTransform + setTransform + ( const QTransform & transform, bool combine = false ) + + + setViewTransformEnabled + setViewTransformEnabled + ( bool enable ) + + + setViewport + setViewport + ( const QRect & rectangle ) + + + setViewport + setViewport-2 + ( int x, int y, int width, int height ) + + + setWindow + setWindow + ( const QRect & rectangle ) + + + setWindow + setWindow-2 + ( int x, int y, int width, int height ) + + + setWorldMatrix + setWorldMatrix + ( const QMatrix & matrix, bool combine = false ) + + + setWorldMatrixEnabled + setWorldMatrixEnabled + ( bool enable ) + + + setWorldTransform + setWorldTransform + ( const QTransform & matrix, bool combine = false ) + + + shear + shear + ( qreal sh, qreal sv ) + + + strokePath + strokePath + ( const QPainterPath & path, const QPen & pen ) + + + testRenderHint + testRenderHint + ( RenderHint hint ) + + + transform + transform + () + + + translate + translate + ( const QPointF & offset ) + + + translate + translate-2 + ( const QPoint & offset ) + + + translate + translate-3 + ( qreal dx, qreal dy ) + + + viewTransformEnabled + viewTransformEnabled + () + + + viewport + viewport + () + + + window + window + () + + + worldMatrix + worldMatrix + () + + + worldMatrixEnabled + worldMatrixEnabled + () + + + worldTransform + worldTransform + () + + + backgroundColor + backgroundColor + () + + + begin + begin-2 + ( QPaintDevice * device, const QWidget * init ) + + + boundingRect + boundingRect-2 + ( const QRect & rectangle, int flags, const QString & text, int length ) + + + boundingRect + boundingRect-3 + ( int x, int y, int width, int height, int flags, const QString & text, int length ) + + + drawConvexPolygon + drawConvexPolygon-5 + ( const QPolygonF & polygon, int index, int count = -1 ) + + + drawConvexPolygon + drawConvexPolygon-6 + ( const QPolygon & polygon, int index, int count = -1 ) + + + drawCubicBezier + drawCubicBezier + ( const QPolygon & controlPoints, int index = 0 ) + + + drawLineSegments + drawLineSegments + ( const QPolygon & polygon, int index = 0, int count = -1 ) + + + drawPoints + drawPoints-5 + ( const QPolygon & polygon, int index, int count = -1 ) + + + drawPolygon + drawPolygon-3 + ( const QPolygonF & polygon, bool winding, int index = 0, int count = -1 ) + + + drawPolygon + drawPolygon-4 + ( const QPolygon & polygon, bool winding, int index = 0, int count = -1 ) + + + drawPolyline + drawPolyline-3 + ( const QPolygon & polygon, int index, int count = -1 ) + + + drawText + drawText-2 + ( int x, int y, const QString & text, int pos, int length ) + + + drawText + drawText-3 + ( const QPoint & point, const QString & text, int pos, int length ) + + + drawText + drawText-4 + ( int x, int y, const QString & text, int length ) + + + drawText + drawText-5 + ( const QPoint & point, const QString & text, int length ) + + + drawText + drawText-6 + ( const QRect & rectangle, int flags, const QString & text, int length, QRect * br = 0 ) + + + drawText + drawText-7 + ( int x, int y, int width, int height, int flags, const QString & text, int length, QRect * br = 0 ) + + + hasViewXForm + hasViewXForm + () + + + hasWorldXForm + hasWorldXForm + () + + + redirect + redirect + ( QPaintDevice * pdev, QPaintDevice * replacement ) + + + redirect + redirect-2 + ( QPaintDevice * pdev ) + + + resetXForm + resetXForm + () + + + setBackgroundColor + setBackgroundColor + ( const QColor & color ) + + + setViewXForm + setViewXForm + ( bool enabled ) + + + setWorldXForm + setWorldXForm + ( bool enabled ) + + + translationX + translationX + () + + + translationY + translationY + () + + + xForm + xForm + ( const QPoint & point ) + + + xForm + xForm-2 + ( const QRect & rectangle ) + + + xForm + xForm-3 + ( const QPolygon & polygon ) + + + xForm + xForm-4 + ( const QPolygon & polygon, int index, int count ) + + + xFormDev + xFormDev + ( const QPoint & point ) + + + xFormDev + xFormDev-2 + ( const QRect & rectangle ) + + + xFormDev + xFormDev-3 + ( const QPolygon & polygon ) + + + xFormDev + xFormDev-4 + ( const QPolygon & polygon, int index, int count ) + + + + QPainterPath::Element + qpainterpath-element.html + + isCurveTo + isCurveTo + () + + + isLineTo + isLineTo + () + + + isMoveTo + isMoveTo + () + + + operator + operator-QPointF + QPointF() + + + operator!= + operator-not-eq + ( const Element & other ) + + + operator== + operator-eq-eq + ( const Element & other ) + + + type + type-varx + + + + x + x-var + + + + y + y-var + + + + + QPainterPath + qpainterpath.html + + ElementType + ElementType-enum + + + + QPainterPath + QPainterPath + () + + + QPainterPath + QPainterPath-2 + ( const QPointF & startPoint ) + + + QPainterPath + QPainterPath-3 + ( const QPainterPath & path ) + + + addEllipse + addEllipse + ( const QRectF & boundingRectangle ) + + + addEllipse + addEllipse-2 + ( qreal x, qreal y, qreal width, qreal height ) + + + addPath + addPath + ( const QPainterPath & path ) + + + addPolygon + addPolygon + ( const QPolygonF & polygon ) + + + addRect + addRect + ( const QRectF & rectangle ) + + + addRect + addRect-2 + ( qreal x, qreal y, qreal width, qreal height ) + + + addRegion + addRegion + ( const QRegion & region ) + + + addRoundRect + addRoundRect + ( const QRectF & r, int xRnd, int yRnd ) + + + addRoundRect + addRoundRect-2 + ( qreal x, qreal y, qreal w, qreal h, int xRnd, int yRnd ) + + + addRoundRect + addRoundRect-3 + ( const QRectF & rect, int roundness ) + + + addRoundRect + addRoundRect-4 + ( qreal x, qreal y, qreal w, qreal h, int roundness ) + + + addText + addText + ( const QPointF & point, const QFont & font, const QString & text ) + + + addText + addText-2 + ( qreal x, qreal y, const QFont & font, const QString & text ) + + + angleAtPercent + angleAtPercent + ( qreal t ) + + + arcMoveTo + arcMoveTo + ( const QRectF & rectangle, qreal angle ) + + + arcMoveTo + arcMoveTo-2 + ( qreal x, qreal y, qreal width, qreal height, qreal angle ) + + + arcTo + arcTo + ( const QRectF & rectangle, qreal startAngle, qreal sweepLength ) + + + arcTo + arcTo-2 + ( qreal x, qreal y, qreal width, qreal height, qreal startAngle, qreal sweepLength ) + + + boundingRect + boundingRect + () + + + closeSubpath + closeSubpath + () + + + connectPath + connectPath + ( const QPainterPath & path ) + + + contains + contains + ( const QPointF & point ) + + + contains + contains-2 + ( const QPainterPath & p ) + + + contains + contains-3 + ( const QRectF & rectangle ) + + + controlPointRect + controlPointRect + () + + + cubicTo + cubicTo + ( const QPointF & c1, const QPointF & c2, const QPointF & endPoint ) + + + cubicTo + cubicTo-2 + ( qreal c1X, qreal c1Y, qreal c2X, qreal c2Y, qreal endPointX, qreal endPointY ) + + + currentPosition + currentPosition + () + + + Element + elementAt + & QPainterPath::elementAt( int index ) + + + elementCount + elementCount + () + + + FillRule + fillRule + QPainterPath::fillRule() + + + intersected + intersected + ( const QPainterPath & p ) + + + intersects + intersects + ( const QRectF & rectangle ) + + + intersects + intersects-2 + ( const QPainterPath & p ) + + + isEmpty + isEmpty + () + + + length + length + () + + + lineTo + lineTo + ( const QPointF & endPoint ) + + + lineTo + lineTo-2 + ( qreal x, qreal y ) + + + moveTo + moveTo + ( const QPointF & point ) + + + moveTo + moveTo-2 + ( qreal x, qreal y ) + + + percentAtLength + percentAtLength + ( qreal len ) + + + pointAtPercent + pointAtPercent + ( qreal t ) + + + quadTo + quadTo + ( const QPointF & c, const QPointF & endPoint ) + + + quadTo + quadTo-2 + ( qreal cx, qreal cy, qreal endPointX, qreal endPointY ) + + + setElementPositionAt + setElementPositionAt + ( int index, qreal x, qreal y ) + + + setFillRule + setFillRule + ( Qt::FillRule fillRule ) + + + slopeAtPercent + slopeAtPercent + ( qreal t ) + + + subtracted + subtracted + ( const QPainterPath & p ) + + + subtractedInverted + subtractedInverted + ( const QPainterPath & p ) + + + toFillPolygon + toFillPolygon + ( const QMatrix & matrix = QMatrix() + + + toFillPolygon + toFillPolygon-2 + ( const QTransform & matrix ) + + + toFillPolygons + toFillPolygons + ( const QMatrix & matrix = QMatrix() + + + toFillPolygons + toFillPolygons-2 + ( const QTransform & matrix ) + + + toReversed + toReversed + () + + + toSubpathPolygons + toSubpathPolygons + ( const QMatrix & matrix = QMatrix() + + + toSubpathPolygons + toSubpathPolygons-2 + ( const QTransform & matrix ) + + + united + united + ( const QPainterPath & p ) + + + operator!= + operator-not-eq + ( const QPainterPath & path ) + + + operator= + operator-eq + ( const QPainterPath & path ) + + + operator== + operator-eq-eq + ( const QPainterPath & path ) + + + + QPainterPathStroker + qpainterpathstroker.html + + QPainterPathStroker + QPainterPathStroker + () + + + PenCapStyle + capStyle + QPainterPathStroker::capStyle() + + + createStroke + createStroke + ( const QPainterPath & path ) + + + curveThreshold + curveThreshold + () + + + dashOffset + dashOffset + () + + + dashPattern + dashPattern + () + + + PenJoinStyle + joinStyle + QPainterPathStroker::joinStyle() + + + miterLimit + miterLimit + () + + + setCapStyle + setCapStyle + ( Qt::PenCapStyle style ) + + + setCurveThreshold + setCurveThreshold + ( qreal threshold ) + + + setDashOffset + setDashOffset + ( qreal offset ) + + + setDashPattern + setDashPattern + ( Qt::PenStyle style ) + + + setDashPattern + setDashPattern-2 + ( const QVector<qreal> & dashPattern ) + + + setJoinStyle + setJoinStyle + ( Qt::PenJoinStyle style ) + + + setMiterLimit + setMiterLimit + ( qreal limit ) + + + setWidth + setWidth + ( qreal width ) + + + width + width + () + + + QPaintEvent + QPaintEvent-3 + ( const QRegion & paintRegion, const QRect & paintRect ) + + + erased + erased + () + + + + QPaintEvent + qpaintevent.html + + QPaintEvent + QPaintEvent + ( const QRegion & paintRegion ) + + + QPaintEvent + QPaintEvent-2 + ( const QRect & paintRect ) + + + rect + rect + () + + + region + region + () + + + QPaintEvent + QPaintEvent-3 + ( const QRegion & paintRegion, const QRect & paintRect ) + + + erased + erased + () + + + + QPair + qpair.html + + first_type + first_type-typedef + + + + second_type + second_type-typedef + + + + QPair + QPair + () + + + QPair + QPair-2 + ( const T1 & value1, const T2 & value2 ) + + + operator= + operator-eq + ( const QPair<T1, T2> & other ) + + + first + first-var + + + + second + second-var + + + + QPalette + QPalette-6 + ( const QColor & windowText, const QColor & window, const QColor & light, const QColor & dark, const QColor & mid, const QColor & text, const QColor & base ) + + + background + background + () + + + foreground + foreground + () + + + serialNumber + serialNumber + () + + + QPalette + QPalette-7 + ( const QColorGroup & active, const QColorGroup & disabled, const QColorGroup & inactive ) + + + active + active + () + + + copy + copy + () + + + disabled + disabled + () + + + inactive + inactive + () + + + normal + normal + () + + + setActive + setActive + ( const QColorGroup & colorGroup ) + + + setDisabled + setDisabled + ( const QColorGroup & colorGroup ) + + + setInactive + setInactive + ( const QColorGroup & colorGroup ) + + + setNormal + setNormal + ( const QColorGroup & colorGroup ) + + + + QPalette + qpalette.html + + ColorGroup + ColorGroup-enum + + + + ColorRole + ColorRole-enum + + + + QPalette + QPalette + () + + + QPalette + QPalette-2 + ( const QColor & button ) + + + QPalette + QPalette-3 + ( Qt::GlobalColor button ) + + + QPalette + QPalette-4 + ( const QColor & button, const QColor & window ) + + + QPalette + QPalette-5 + ( const QBrush & windowText, const QBrush & button, const QBrush & light, const QBrush & dark, const QBrush & mid, const QBrush & text, const QBrush & bright_text, const QBrush & base, const QBrush & window ) + + + QPalette + QPalette-8 + ( const QPalette & p ) + + + alternateBase + alternateBase + () + + + base + base + () + + + brightText + brightText + () + + + brush + brush + ( ColorGroup group, ColorRole role ) + + + brush + brush-2 + ( ColorRole role ) + + + button + button + () + + + buttonText + buttonText + () + + + cacheKey + cacheKey + () + + + color + color + ( ColorGroup group, ColorRole role ) + + + color + color-2 + ( ColorRole role ) + + + currentColorGroup + currentColorGroup + () + + + dark + dark + () + + + highlight + highlight + () + + + highlightedText + highlightedText + () + + + isBrushSet + isBrushSet + ( ColorGroup cg, ColorRole cr ) + + + isCopyOf + isCopyOf + ( const QPalette & p ) + + + isEqual + isEqual + ( ColorGroup cg1, ColorGroup cg2 ) + + + light + light + () + + + link + link + () + + + linkVisited + linkVisited + () + + + mid + mid + () + + + midlight + midlight + () + + + resolve + resolve + ( const QPalette & other ) + + + setBrush + setBrush + ( ColorRole role, const QBrush & brush ) + + + setBrush + setBrush-2 + ( ColorGroup group, ColorRole role, const QBrush & brush ) + + + setColor + setColor + ( ColorGroup group, ColorRole role, const QColor & color ) + + + setColor + setColor-2 + ( ColorRole role, const QColor & color ) + + + setColorGroup + setColorGroup + ( ColorGroup cg, const QBrush & windowText, const QBrush & button, const QBrush & light, const QBrush & dark, const QBrush & mid, const QBrush & text, const QBrush & bright_text, const QBrush & base, const QBrush & window ) + + + setCurrentColorGroup + setCurrentColorGroup + ( ColorGroup cg ) + + + shadow + shadow + () + + + text + text + () + + + window + window + () + + + windowText + windowText + () + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QPalette & p ) + + + operator= + operator-eq + ( const QPalette & p ) + + + operator== + operator-eq-eq + ( const QPalette & p ) + + + QPalette + QPalette-7 + ( const QColorGroup & active, const QColorGroup & disabled, const QColorGroup & inactive ) + + + active + active + () + + + copy + copy + () + + + disabled + disabled + () + + + inactive + inactive + () + + + normal + normal + () + + + setActive + setActive + ( const QColorGroup & colorGroup ) + + + setDisabled + setDisabled + ( const QColorGroup & colorGroup ) + + + setInactive + setInactive + ( const QColorGroup & colorGroup ) + + + setNormal + setNormal + ( const QColorGroup & colorGroup ) + + + + QPen + qpen.html + + DataPtr + DataPtr-typedef + + + + QPen + QPen + () + + + QPen + QPen-2 + ( Qt::PenStyle style ) + + + QPen + QPen-3 + ( const QColor & color ) + + + QPen + QPen-4 + ( const QBrush & brush, qreal width, Qt::PenStyle style = Qt::SolidLine, Qt::PenCapStyle cap = Qt::SquareCap, Qt::PenJoinStyle join = Qt::BevelJoin ) + + + QPen + QPen-5 + ( const QPen & pen ) + + + brush + brush + () + + + PenCapStyle + capStyle + QPen::capStyle() + + + color + color + () + + + dashOffset + dashOffset + () + + + dashPattern + dashPattern + () + + + data_ptr + data_ptr + () + + + isCosmetic + isCosmetic + () + + + isSolid + isSolid + () + + + PenJoinStyle + joinStyle + QPen::joinStyle() + + + miterLimit + miterLimit + () + + + setBrush + setBrush + ( const QBrush & brush ) + + + setCapStyle + setCapStyle + ( Qt::PenCapStyle style ) + + + setColor + setColor + ( const QColor & color ) + + + setCosmetic + setCosmetic + ( bool cosmetic ) + + + setDashOffset + setDashOffset + ( qreal offset ) + + + setDashPattern + setDashPattern + ( const QVector<qreal> & pattern ) + + + setJoinStyle + setJoinStyle + ( Qt::PenJoinStyle style ) + + + setMiterLimit + setMiterLimit + ( qreal limit ) + + + setStyle + setStyle + ( Qt::PenStyle style ) + + + setWidth + setWidth + ( int width ) + + + setWidthF + setWidthF + ( qreal width ) + + + PenStyle + style + QPen::style() + + + width + width + () + + + widthF + widthF + () + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QPen & pen ) + + + operator= + operator-eq + ( const QPen & pen ) + + + operator== + operator-eq-eq + ( const QPen & pen ) + + + + QPersistentModelIndex + qpersistentmodelindex.html + + QPersistentModelIndex + QPersistentModelIndex + ( const QModelIndex & index ) + + + QPersistentModelIndex + QPersistentModelIndex-3 + ( const QPersistentModelIndex & other ) + + + child + child + ( int row, int column ) + + + column + column + () + + + data + data + ( int role = Qt::DisplayRole ) + + + ItemFlags + flags + QPersistentModelIndex::flags() + + + isValid + isValid + () + + + model + model + () + + + parent + parent + () + + + row + row + () + + + sibling + sibling + ( int row, int column ) + + + operator + operator-const-QModelIndex--and + const QModelIndex &() + + + operator!= + operator-not-eq + ( const QPersistentModelIndex & other ) + + + operator!= + operator-not-eq-2 + ( const QModelIndex & other ) + + + operator< + operator-lt + ( const QPersistentModelIndex & other ) + + + operator= + operator-eq + ( const QPersistentModelIndex & other ) + + + operator= + operator-eq-2 + ( const QModelIndex & other ) + + + operator== + operator-eq-eq + ( const QPersistentModelIndex & other ) + + + operator== + operator-eq-eq-2 + ( const QModelIndex & other ) + + + inputFormatList + inputFormatList + () + + + inputFormats + inputFormats + () + + + outputFormatList + outputFormatList + () + + + outputFormats + outputFormats + () + + + pictureFormat + pictureFormat + ( const QString & fileName ) + + + copy + copy + () + + + + QPicture + qpicture.html + + DataPtr + DataPtr-typedef + + + + QPicture + QPicture + ( int formatVersion = -1 ) + + + QPicture + QPicture-2 + ( const QPicture & pic ) + + + boundingRect + boundingRect + () + + + data + data + () + + + data_ptr + data_ptr + () + + + isNull + isNull + () + + + load + load + ( const QString & fileName, const char * format = 0 ) + + + load + load-2 + ( QIODevice * dev, const char * format = 0 ) + + + metric + metric + ( PaintDeviceMetric m ) + + + play + play + ( QPainter * painter ) + + + save + save + ( const QString & fileName, const char * format = 0 ) + + + save + save-2 + ( QIODevice * dev, const char * format = 0 ) + + + setBoundingRect + setBoundingRect + ( const QRect & r ) + + + setData + setData + ( const char * data, uint size ) + + + size + size + () + + + operator= + operator-eq + ( const QPicture & p ) + + + copy + copy + () + + + + QPictureFormatPlugin + qpictureformatplugin.html + + QPictureFormatPlugin + QPictureFormatPlugin + ( QObject * parent = 0 ) + + + installIOHandler + installIOHandler + ( const QString & format ) + + + keys + keys + () + + + loadPicture + loadPicture + ( const QString & format, const QString & fileName, QPicture * picture ) + + + savePicture + savePicture + ( const QString & format, const QString & fileName, const QPicture & picture ) + + + + QPictureIO + qpictureio.html + + QPictureIO + QPictureIO + () + + + QPictureIO + QPictureIO-2 + ( QIODevice * ioDevice, const char * format ) + + + QPictureIO + QPictureIO-3 + ( const QString & fileName, const char * format ) + + + defineIOHandler + defineIOHandler + ( const char * format, const char * header, const char * flags, picture_io_handler readPicture, picture_io_handler writePicture ) + + + description + description + () + + + fileName + fileName + () + + + format + format + () + + + gamma + gamma + () + + + inputFormats + inputFormats + () + + + ioDevice + ioDevice + () + + + outputFormats + outputFormats + () + + + parameters + parameters + () + + + picture + picture + () + + + pictureFormat + pictureFormat + ( const QString & fileName ) + + + pictureFormat + pictureFormat-2 + ( QIODevice * d ) + + + quality + quality + () + + + read + read + () + + + setDescription + setDescription + ( const QString & description ) + + + setFileName + setFileName + ( const QString & fileName ) + + + setFormat + setFormat + ( const char * format ) + + + setGamma + setGamma + ( float gamma ) + + + setIODevice + setIODevice + ( QIODevice * ioDevice ) + + + setParameters + setParameters + ( const char * parameters ) + + + setPicture + setPicture + ( const QPicture & picture ) + + + setQuality + setQuality + ( int q ) + + + setStatus + setStatus + ( int status ) + + + status + status + () + + + write + write + () + + + serialNumber + serialNumber + () + + + ColorMode + ColorMode-enum + + + + QPixmap + QPixmap-6 + ( const QString & fileName, const char * format, ColorMode mode ) + + + QPixmap + QPixmap-7 + ( const QImage & image ) + + + convertFromImage + convertFromImage + ( const QImage & image, ColorMode mode ) + + + convertFromImage + convertFromImage-2 + ( const QImage & image, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + convertToImage + convertToImage + () + + + load + load-2 + ( const QString & fileName, const char * format, ColorMode mode ) + + + loadFromData + loadFromData-2 + ( const uchar * buf, uint len, const char * format, ColorMode mode ) + + + resize + resize + ( int width, int height ) + + + resize + resize-2 + ( const QSize & size ) + + + selfMask + selfMask + () + + + xForm + xForm + ( const QMatrix & matrix ) + + + operator + operator-QImage + QImage() + + + operator= + operator-eq-2 + ( const QImage & image ) + + + + QPixmap + qpixmap.html + + DataPtr + DataPtr-typedef + + + + HBitmapFormat + HBitmapFormat-enum + + + + QPixmap + QPixmap + () + + + QPixmap + QPixmap-2 + ( int width, int height ) + + + QPixmap + QPixmap-3 + ( const QString & fileName, const char * format = 0, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + QPixmap + QPixmap-4 + ( const char * const[] xpm ) + + + QPixmap + QPixmap-5 + ( const QPixmap & pixmap ) + + + QPixmap + QPixmap-9 + ( const QSize & size ) + + + alphaChannel + alphaChannel + () + + + cacheKey + cacheKey + () + + + copy + copy + ( const QRect & rectangle = QRect() + + + copy + copy-2 + ( int x, int y, int width, int height ) + + + createHeuristicMask + createHeuristicMask + ( bool clipTight = true ) + + + createMaskFromColor + createMaskFromColor + ( const QColor & maskColor, Qt::MaskMode mode ) + + + createMaskFromColor + createMaskFromColor-2 + ( const QColor & maskColor ) + + + data_ptr + data_ptr + () + + + defaultDepth + defaultDepth + () + + + depth + depth + () + + + detach + detach + () + + + fill + fill + ( const QColor & fillColor = Qt::white ) + + + fill + fill-2 + ( const QWidget * widget, const QPoint & offset ) + + + fill + fill-3 + ( const QWidget * widget, int x, int y ) + + + fromImage + fromImage + ( const QImage & image, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + fromMacCGImageRef + fromMacCGImageRef + ( CGImageRef image ) + + + fromWinHBITMAP + fromWinHBITMAP + ( HBITMAP bitmap, HBitmapFormat format = NoAlpha ) + + + grabWidget + grabWidget + ( QWidget * widget, const QRect & rectangle ) + + + grabWidget + grabWidget-2 + ( QWidget * widget, int x = 0, int y = 0, int width = -1, int height = -1 ) + + + grabWindow + grabWindow + ( WId window, int x = 0, int y = 0, int width = -1, int height = -1 ) + + + HANDLE + handle + QPixmap::handle() + + + hasAlpha + hasAlpha + () + + + hasAlphaChannel + hasAlphaChannel + () + + + height + height + () + + + isNull + isNull + () + + + isQBitmap + isQBitmap + () + + + load + load + ( const QString & fileName, const char * format = 0, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + loadFromData + loadFromData + ( const uchar * data, uint len, const char * format = 0, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + loadFromData + loadFromData-3 + ( const QByteArray & data, const char * format = 0, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + mask + mask + () + + + rect + rect + () + + + save + save + ( const QString & fileName, const char * format = 0, int quality = -1 ) + + + save + save-2 + ( QIODevice * device, const char * format = 0, int quality = -1 ) + + + scaled + scaled + ( const QSize & size, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation ) + + + scaled + scaled-2 + ( int width, int height, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation ) + + + scaledToHeight + scaledToHeight + ( int height, Qt::TransformationMode mode = Qt::FastTransformation ) + + + scaledToWidth + scaledToWidth + ( int width, Qt::TransformationMode mode = Qt::FastTransformation ) + + + setAlphaChannel + setAlphaChannel + ( const QPixmap & alphaChannel ) + + + setMask + setMask + ( const QBitmap & newmask ) + + + size + size + () + + + toImage + toImage + () + + + toMacCGImageRef + toMacCGImageRef + () + + + toWinHBITMAP + toWinHBITMAP + ( HBitmapFormat format = NoAlpha ) + + + transformed + transformed + ( const QMatrix & matrix, Qt::TransformationMode mode = Qt::FastTransformation ) + + + transformed + transformed-2 + ( const QTransform &, Qt::TransformationMode mode = Qt::FastTransformation ) + + + trueMatrix + trueMatrix + ( const QMatrix & matrix, int width, int height ) + + + trueMatrix + trueMatrix-2 + ( const QTransform & m, int w, int h ) + + + width + width + () + + + x11Info + x11Info + () + + + HANDLE + x11PictureHandle + QPixmap::x11PictureHandle() + + + operator + operator-QVariant + QVariant() + + + operator! + operator-not + () + + + operator= + operator-eq + ( const QPixmap & pixmap ) + + + ColorMode + ColorMode-enum + + + + QPixmap + QPixmap-6 + ( const QString & fileName, const char * format, ColorMode mode ) + + + QPixmap + QPixmap-7 + ( const QImage & image ) + + + convertFromImage + convertFromImage + ( const QImage & image, ColorMode mode ) + + + convertFromImage + convertFromImage-2 + ( const QImage & image, Qt::ImageConversionFlags flags = Qt::AutoColor ) + + + convertToImage + convertToImage + () + + + load + load-2 + ( const QString & fileName, const char * format, ColorMode mode ) + + + loadFromData + loadFromData-2 + ( const uchar * buf, uint len, const char * format, ColorMode mode ) + + + resize + resize + ( int width, int height ) + + + resize + resize-2 + ( const QSize & size ) + + + selfMask + selfMask + () + + + xForm + xForm + ( const QMatrix & matrix ) + + + operator + operator-QImage + QImage() + + + operator= + operator-eq-2 + ( const QImage & image ) + + + find + find-2 + ( const QString & key ) + + + + QPixmapCache + qpixmapcache.html + + cacheLimit + cacheLimit + () + + + clear + clear + () + + + find + find + ( const QString & key, QPixmap & pm ) + + + insert + insert + ( const QString & key, const QPixmap & pm ) + + + remove + remove + ( const QString & key ) + + + setCacheLimit + setCacheLimit + ( int n ) + + + + QPlastiqueStyle + qplastiquestyle.html + + QPlastiqueStyle + QPlastiqueStyle + () + + + + QPluginLoader + qpluginloader.html + + QPluginLoader + QPluginLoader + ( QObject * parent = 0 ) + + + QPluginLoader + QPluginLoader-2 + ( const QString & fileName, QObject * parent = 0 ) + + + errorString + errorString + () + + + instance + instance + () + + + isLoaded + isLoaded + () + + + load + load + () + + + staticInstances + staticInstances + () + + + unload + unload + () + + + + QPoint + qpoint.html + + QPoint + QPoint + () + + + QPoint + QPoint-2 + ( int x, int y ) + + + isNull + isNull + () + + + manhattanLength + manhattanLength + () + + + rx + rx + () + + + ry + ry + () + + + setX + setX + ( int x ) + + + setY + setY + ( int y ) + + + x + x + () + + + y + y + () + + + operator*= + operator-2a-eq + ( qreal factor ) + + + operator+= + operator-2b-eq + ( const QPoint & point ) + + + operator-= + operator--eq + ( const QPoint & point ) + + + operator/= + operator-2f-eq + ( qreal divisor ) + + + + QPointer + qpointer.html + + QPointer + QPointer + () + + + QPointer + QPointer-2 + ( T * p ) + + + QPointer + QPointer-3 + ( const QPointer<T> & p ) + + + isNull + isNull + () + + + operator + operator-T--2a + T *() + + + operator* + operator-2a + () + + + operator-& + operator--gt + gt;() + + + operator= + operator-eq + ( const QPointer<T> & p ) + + + operator= + operator-eq-2 + ( T * p ) + + + + QPointF + qpointf.html + + QPointF + QPointF + () + + + QPointF + QPointF-2 + ( const QPoint & point ) + + + QPointF + QPointF-3 + ( qreal x, qreal y ) + + + isNull + isNull + () + + + rx + rx + () + + + ry + ry + () + + + setX + setX + ( qreal x ) + + + setY + setY + ( qreal y ) + + + toPoint + toPoint + () + + + x + x + () + + + y + y + () + + + operator*= + operator-2a-eq + ( qreal factor ) + + + operator+= + operator-2b-eq + ( const QPointF & point ) + + + operator-= + operator--eq + ( const QPointF & point ) + + + operator/= + operator-2f-eq + ( qreal divisor ) + + + + QPolygon + qpolygon.html + + QPolygon + QPolygon + () + + + QPolygon + QPolygon-2 + ( int size ) + + + QPolygon + QPolygon-3 + ( const QPolygon & polygon ) + + + QPolygon + QPolygon-4 + ( const QVector<QPoint> & points ) + + + QPolygon + QPolygon-5 + ( const QRect & rectangle, bool closed = false ) + + + boundingRect + boundingRect + () + + + containsPoint + containsPoint + ( const QPoint & pt, Qt::FillRule fillRule ) + + + intersected + intersected + ( const QPolygon & r ) + + + point + point + ( int index, int * x, int * y ) + + + point + point-2 + ( int index ) + + + putPoints + putPoints + ( int index, int nPoints, int firstx, int firsty, ... ) + + + putPoints + putPoints-3 + ( int index, int nPoints, const QPolygon & fromPolygon, int fromIndex = 0 ) + + + setPoint + setPoint + ( int index, int x, int y ) + + + setPoint + setPoint-2 + ( int index, const QPoint & point ) + + + setPoints + setPoints + ( int nPoints, const int * points ) + + + setPoints + setPoints-2 + ( int nPoints, int firstx, int firsty, ... ) + + + subtracted + subtracted + ( const QPolygon & r ) + + + translate + translate + ( int dx, int dy ) + + + translate + translate-2 + ( const QPoint & offset ) + + + united + united + ( const QPolygon & r ) + + + operator + operator-QVariant + QVariant() + + + + QPolygonF + qpolygonf.html + + QPolygonF + QPolygonF + () + + + QPolygonF + QPolygonF-2 + ( int size ) + + + QPolygonF + QPolygonF-3 + ( const QPolygonF & polygon ) + + + QPolygonF + QPolygonF-4 + ( const QVector<QPointF> & points ) + + + QPolygonF + QPolygonF-5 + ( const QRectF & rectangle ) + + + QPolygonF + QPolygonF-6 + ( const QPolygon & polygon ) + + + boundingRect + boundingRect + () + + + containsPoint + containsPoint + ( const QPointF & point, Qt::FillRule fillRule ) + + + intersected + intersected + ( const QPolygonF & r ) + + + isClosed + isClosed + () + + + subtracted + subtracted + ( const QPolygonF & r ) + + + toPolygon + toPolygon + () + + + translate + translate + ( const QPointF & offset ) + + + translate + translate-2 + ( qreal dx, qreal dy ) + + + united + united + ( const QPolygonF & r ) + + + + QPrintDialog + qprintdialog.html + + QPrintDialog + QPrintDialog + ( QPrinter * printer, QWidget * parent = 0 ) + + + + QPrintEngine + qprintengine.html + + PrintEnginePropertyKey + PrintEnginePropertyKey-enum + + + + abort + abort + () + + + metric + metric + ( QPaintDevice::PaintDeviceMetric id ) + + + newPage + newPage + () + + + PrinterState + printerState + QPrintEngine::printerState() + + + property + property + ( PrintEnginePropertyKey key ) + + + setProperty + setProperty + ( PrintEnginePropertyKey key, const QVariant & value ) + + + PrinterOption + PrinterOption-enum + + + + aborted + aborted + () + + + collateCopiesEnabled + collateCopiesEnabled + () + + + isOptionEnabled + isOptionEnabled + ( PrinterOption option ) + + + margins + margins + ( uint * top, uint * left, uint * bottom, uint * right ) + + + margins + margins-2 + () + + + maxPage + maxPage + () + + + minPage + minPage + () + + + outputToFile + outputToFile + () + + + pageSetup + pageSetup + ( QWidget * parent = 0 ) + + + printSetup + printSetup + ( QWidget * parent = 0 ) + + + setCollateCopiesEnabled + setCollateCopiesEnabled + ( bool enable ) + + + setMinMax + setMinMax + ( int minPage, int maxPage ) + + + setOptionEnabled + setOptionEnabled + ( PrinterOption option, bool enable ) + + + setOutputToFile + setOutputToFile + ( bool enable ) + + + setup + setup + ( QWidget * parent = 0 ) + + + + QPrinter + qprinter.html + + ColorMode + ColorMode-enum + + + + Orientation + Orientation-enum + + + + OutputFormat + OutputFormat-enum + + + + PageOrder + PageOrder-enum + + + + PageSize + PageSize-enum + + + + PaperSource + PaperSource-enum + + + + PrintRange + PrintRange-enum + + + + PrinterMode + PrinterMode-enum + + + + PrinterState + PrinterState-enum + + + + QPrinter + QPrinter + ( PrinterMode mode = ScreenResolution ) + + + abort + abort + () + + + collateCopies + collateCopies + () + + + colorMode + colorMode + () + + + creator + creator + () + + + docName + docName + () + + + doubleSidedPrinting + doubleSidedPrinting + () + + + fontEmbeddingEnabled + fontEmbeddingEnabled + () + + + fromPage + fromPage + () + + + fullPage + fullPage + () + + + newPage + newPage + () + + + numCopies + numCopies + () + + + orientation + orientation + () + + + outputFileName + outputFileName + () + + + outputFormat + outputFormat + () + + + pageOrder + pageOrder + () + + + pageRect + pageRect + () + + + pageSize + pageSize + () + + + paintEngine + paintEngine + () + + + paperRect + paperRect + () + + + paperSource + paperSource + () + + + printEngine + printEngine + () + + + printProgram + printProgram + () + + + printRange + printRange + () + + + printerName + printerName + () + + + printerSelectionOption + printerSelectionOption + () + + + printerState + printerState + () + + + resolution + resolution + () + + + setCollateCopies + setCollateCopies + ( bool collate ) + + + setColorMode + setColorMode + ( ColorMode newColorMode ) + + + setCreator + setCreator + ( const QString & creator ) + + + setDocName + setDocName + ( const QString & name ) + + + setDoubleSidedPrinting + setDoubleSidedPrinting + ( bool doubleSided ) + + + setEngines + setEngines + ( QPrintEngine * printEngine, QPaintEngine * paintEngine ) + + + setFontEmbeddingEnabled + setFontEmbeddingEnabled + ( bool enable ) + + + setFromTo + setFromTo + ( int from, int to ) + + + setFullPage + setFullPage + ( bool fp ) + + + setNumCopies + setNumCopies + ( int numCopies ) + + + setOrientation + setOrientation + ( Orientation orientation ) + + + setOutputFileName + setOutputFileName + ( const QString & fileName ) + + + setOutputFormat + setOutputFormat + ( OutputFormat format ) + + + setPageOrder + setPageOrder + ( PageOrder pageOrder ) + + + setPageSize + setPageSize + ( PageSize newPageSize ) + + + setPaperSource + setPaperSource + ( PaperSource source ) + + + setPrintProgram + setPrintProgram + ( const QString & printProg ) + + + setPrintRange + setPrintRange + ( PrintRange range ) + + + setPrinterName + setPrinterName + ( const QString & name ) + + + setPrinterSelectionOption + setPrinterSelectionOption + ( const QString & option ) + + + setResolution + setResolution + ( int dpi ) + + + setWinPageSize + setWinPageSize + ( int pageSize ) + + + supportedPaperSources + supportedPaperSources + () + + + supportedResolutions + supportedResolutions + () + + + toPage + toPage + () + + + winPageSize + winPageSize + () + + + PrinterOption + PrinterOption-enum + + + + aborted + aborted + () + + + collateCopiesEnabled + collateCopiesEnabled + () + + + isOptionEnabled + isOptionEnabled + ( PrinterOption option ) + + + margins + margins + ( uint * top, uint * left, uint * bottom, uint * right ) + + + margins + margins-2 + () + + + maxPage + maxPage + () + + + minPage + minPage + () + + + outputToFile + outputToFile + () + + + pageSetup + pageSetup + ( QWidget * parent = 0 ) + + + printSetup + printSetup + ( QWidget * parent = 0 ) + + + setCollateCopiesEnabled + setCollateCopiesEnabled + ( bool enable ) + + + setMinMax + setMinMax + ( int minPage, int maxPage ) + + + setOptionEnabled + setOptionEnabled + ( PrinterOption option, bool enable ) + + + setOutputToFile + setOutputToFile + ( bool enable ) + + + setup + setup + ( QWidget * parent = 0 ) + + + finished + finished-2 + ( int exitCode ) + + + readChannelMode + readChannelMode + () + + + setReadChannelMode + setReadChannelMode + ( ProcessChannelMode mode ) + + + + QProcess + qprocess.html + + ExitStatus + ExitStatus-enum + + + + ProcessChannel + ProcessChannel-enum + + + + ProcessChannelMode + ProcessChannelMode-enum + + + + ProcessError + ProcessError-enum + + + + ProcessState + ProcessState-enum + + + + QProcess + QProcess + ( QObject * parent = 0 ) + + + close + close + () + + + closeReadChannel + closeReadChannel + ( ProcessChannel channel ) + + + closeWriteChannel + closeWriteChannel + () + + + environment + environment + () + + + ProcessError + error + QProcess::error() + + + error + error-2 + ( QProcess::ProcessError error ) + + + execute + execute + ( const QString & program, const QStringList & arguments ) + + + execute + execute-2 + ( const QString & program ) + + + exitCode + exitCode + () + + + ExitStatus + exitStatus + QProcess::exitStatus() + + + finished + finished + ( int exitCode, QProcess::ExitStatus exitStatus ) + + + kill + kill + () + + + pid + pid + () + + + processChannelMode + processChannelMode + () + + + readAllStandardError + readAllStandardError + () + + + readAllStandardOutput + readAllStandardOutput + () + + + readChannel + readChannel + () + + + readyReadStandardError + readyReadStandardError + () + + + readyReadStandardOutput + readyReadStandardOutput + () + + + setEnvironment + setEnvironment + ( const QStringList & environment ) + + + setProcessChannelMode + setProcessChannelMode + ( ProcessChannelMode mode ) + + + setProcessState + setProcessState + ( ProcessState state ) + + + setReadChannel + setReadChannel + ( ProcessChannel channel ) + + + setStandardErrorFile + setStandardErrorFile + ( const QString & fileName, OpenMode mode = Truncate ) + + + setStandardInputFile + setStandardInputFile + ( const QString & fileName ) + + + setStandardOutputFile + setStandardOutputFile + ( const QString & fileName, OpenMode mode = Truncate ) + + + setStandardOutputProcess + setStandardOutputProcess + ( QProcess * destination ) + + + setWorkingDirectory + setWorkingDirectory + ( const QString & dir ) + + + setupChildProcess + setupChildProcess + () + + + start + start + ( const QString & program, const QStringList & arguments, OpenMode mode = ReadWrite ) + + + start + start-2 + ( const QString & program, OpenMode mode = ReadWrite ) + + + startDetached + startDetached + ( const QString & program, const QStringList & arguments, const QString & workingDirectory, qint64 * pid = 0 ) + + + startDetached + startDetached-2 + ( const QString & program, const QStringList & arguments ) + + + startDetached + startDetached-3 + ( const QString & program ) + + + started + started + () + + + ProcessState + state + QProcess::state() + + + stateChanged + stateChanged + ( QProcess::ProcessState newState ) + + + systemEnvironment + systemEnvironment + () + + + terminate + terminate + () + + + waitForFinished + waitForFinished + ( int msecs = 30000 ) + + + waitForStarted + waitForStarted + ( int msecs = 30000 ) + + + workingDirectory + workingDirectory + () + + + + QProgressBar + qprogressbar.html + + Direction + Direction-enum + + + + Alignment + alignment-prop + + + + Orientation + orientation-prop + + + + QProgressBar + QProgressBar + ( QWidget * parent = 0 ) + + + initStyleOption + initStyleOption + ( QStyleOptionProgressBar * option ) + + + reset + reset + () + + + setRange + setRange + ( int minimum, int maximum ) + + + valueChanged + valueChanged + ( int value ) + + + + QProgressDialog + qprogressdialog.html + + QProgressDialog + QProgressDialog + ( QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + QProgressDialog + QProgressDialog-2 + ( const QString & labelText, const QString & cancelButtonText, int minimum, int maximum, QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + cancel + cancel + () + + + canceled + canceled + () + + + forceShow + forceShow + () + + + reset + reset + () + + + setBar + setBar + ( QProgressBar * bar ) + + + setCancelButton + setCancelButton + ( QPushButton * cancelButton ) + + + setCancelButtonText + setCancelButtonText + ( const QString & cancelButtonText ) + + + setLabel + setLabel + ( QLabel * label ) + + + setRange + setRange + ( int minimum, int maximum ) + + + sizeHint + sizeHint + () + + + + QProxyModel + qproxymodel.html + + QProxyModel + QProxyModel + ( QObject * parent = 0 ) + + + columnCount + columnCount + ( const QModelIndex & parent ) + + + data + data + ( const QModelIndex & index, int role ) + + + dropMimeData + dropMimeData + ( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ) + + + fetchMore + fetchMore + ( const QModelIndex & parent ) + + + ItemFlags + flags + QProxyModel::flags( const QModelIndex & index ) + + + hasChildren + hasChildren + ( const QModelIndex & parent ) + + + headerData + headerData + ( int section, Qt::Orientation orientation, int role ) + + + index + index + ( int row, int column, const QModelIndex & parent ) + + + insertColumns + insertColumns + ( int column, int count, const QModelIndex & parent ) + + + insertRows + insertRows + ( int row, int count, const QModelIndex & parent ) + + + match + match + ( const QModelIndex & start, int role, const QVariant & value, int hits, Qt::MatchFlags flags ) + + + mimeData + mimeData + ( const QModelIndexList & indexes ) + + + mimeTypes + mimeTypes + () + + + model + model + () + + + parent + parent + ( const QModelIndex & child ) + + + revert + revert + () + + + rowCount + rowCount + ( const QModelIndex & parent ) + + + setData + setData + ( const QModelIndex & index, const QVariant & value, int role ) + + + setHeaderData + setHeaderData + ( int section, Qt::Orientation orientation, const QVariant & value, int role ) + + + setModel + setModel + ( QAbstractItemModel * model ) + + + sort + sort + ( int column, Qt::SortOrder order ) + + + span + span + ( const QModelIndex & index ) + + + submit + submit + () + + + DropActions + supportedDropActions + QProxyModel::supportedDropActions() + + + QPushButton + QPushButton-4 + ( QWidget * parent, const char * name ) + + + QPushButton + QPushButton-5 + ( const QString & text, QWidget * parent, const char * name ) + + + QPushButton + QPushButton-6 + ( const QIcon & icon, const QString & text, QWidget * parent, const char * name ) + + + isMenuButton + isMenuButton + () + + + openPopup + openPopup + () + + + popup + popup + () + + + setPopup + setPopup + ( QMenu * popup ) + + + + QPushButton + qpushbutton.html + + QPushButton + QPushButton + ( QWidget * parent = 0 ) + + + QPushButton + QPushButton-2 + ( const QString & text, QWidget * parent = 0 ) + + + QPushButton + QPushButton-3 + ( const QIcon & icon, const QString & text, QWidget * parent = 0 ) + + + initStyleOption + initStyleOption + ( QStyleOptionButton * option ) + + + menu + menu + () + + + setMenu + setMenu + ( QMenu * menu ) + + + showMenu + showMenu + () + + + QPushButton + QPushButton-4 + ( QWidget * parent, const char * name ) + + + QPushButton + QPushButton-5 + ( const QString & text, QWidget * parent, const char * name ) + + + QPushButton + QPushButton-6 + ( const QIcon & icon, const QString & text, QWidget * parent, const char * name ) + + + isMenuButton + isMenuButton + () + + + openPopup + openPopup + () + + + popup + popup + () + + + setPopup + setPopup + ( QMenu * popup ) + + + + QQueue + qqueue.html + + QQueue + QQueue + () + + + dequeue + dequeue + () + + + enqueue + enqueue + ( const T & t ) + + + head + head + () + + + head + head-2 + () + + + + QRadialGradient + qradialgradient.html + + QRadialGradient + QRadialGradient + () + + + QRadialGradient + QRadialGradient-2 + ( const QPointF & center, qreal radius, const QPointF & focalPoint ) + + + QRadialGradient + QRadialGradient-3 + ( qreal cx, qreal cy, qreal radius, qreal fx, qreal fy ) + + + QRadialGradient + QRadialGradient-4 + ( const QPointF & center, qreal radius ) + + + QRadialGradient + QRadialGradient-5 + ( qreal cx, qreal cy, qreal radius ) + + + center + center + () + + + focalPoint + focalPoint + () + + + radius + radius + () + + + setCenter + setCenter + ( const QPointF & center ) + + + setCenter + setCenter-2 + ( qreal x, qreal y ) + + + setFocalPoint + setFocalPoint + ( const QPointF & focalPoint ) + + + setFocalPoint + setFocalPoint-2 + ( qreal x, qreal y ) + + + setRadius + setRadius + ( qreal radius ) + + + QRadioButton + QRadioButton-3 + ( QWidget * parent, const char * name ) + + + QRadioButton + QRadioButton-4 + ( const QString & text, QWidget * parent, const char * name ) + + + + QRadioButton + qradiobutton.html + + QRadioButton + QRadioButton + ( QWidget * parent = 0 ) + + + QRadioButton + QRadioButton-2 + ( const QString & text, QWidget * parent = 0 ) + + + initStyleOption + initStyleOption + ( QStyleOptionButton * option ) + + + QRadioButton + QRadioButton-3 + ( QWidget * parent, const char * name ) + + + QRadioButton + QRadioButton-4 + ( const QString & text, QWidget * parent, const char * name ) + + + + QRasterPaintEngine + qrasterpaintengine.html + + QRasterPaintEngine + QRasterPaintEngine + () + + + drawBufferSpan + drawBufferSpan + ( const uint * buffer, int size, int x, int y, int length, uint alpha ) + + + drawColorSpans + drawColorSpans + ( const QSpan * spans, int count, uint color ) + + + fillPolygon + fillPolygon + ( const QPointF * points, int pointCount, PolygonDrawMode mode ) + + + + QReadLocker + qreadlocker.html + + QReadLocker + QReadLocker + ( QReadWriteLock * lock ) + + + readWriteLock + readWriteLock + () + + + relock + relock + () + + + unlock + unlock + () + + + + QReadWriteLock + qreadwritelock.html + + QReadWriteLock + QReadWriteLock + () + + + lockForRead + lockForRead + () + + + lockForWrite + lockForWrite + () + + + tryLockForRead + tryLockForRead + () + + + tryLockForRead + tryLockForRead-2 + ( int timeout ) + + + tryLockForWrite + tryLockForWrite + () + + + tryLockForWrite + tryLockForWrite-2 + ( int timeout ) + + + unlock + unlock + () + + + intersect + intersect + ( const QRect & rectangle ) + + + unite + unite + ( const QRect & rectangle ) + + + addCoords + addCoords + ( int dx1, int dy1, int dx2, int dy2 ) + + + coords + coords + ( int * x1, int * y1, int * x2, int * y2 ) + + + moveBy + moveBy + ( int dx, int dy ) + + + moveBy + moveBy-2 + ( const QPoint & p ) + + + normalize + normalize + () + + + rBottom + rBottom + () + + + rLeft + rLeft + () + + + rRight + rRight + () + + + rTop + rTop + () + + + rect + rect + ( int * x, int * y, int * width, int * height ) + + + + QRect + qrect.html + + QRect + QRect + () + + + QRect + QRect-2 + ( const QPoint & topLeft, const QPoint & bottomRight ) + + + QRect + QRect-3 + ( const QPoint & topLeft, const QSize & size ) + + + QRect + QRect-4 + ( int x, int y, int width, int height ) + + + adjust + adjust + ( int dx1, int dy1, int dx2, int dy2 ) + + + adjusted + adjusted + ( int dx1, int dy1, int dx2, int dy2 ) + + + bottom + bottom + () + + + bottomLeft + bottomLeft + () + + + bottomRight + bottomRight + () + + + center + center + () + + + contains + contains + ( const QPoint & point, bool proper = false ) + + + contains + contains-2 + ( int x, int y, bool proper ) + + + contains + contains-3 + ( int x, int y ) + + + contains + contains-4 + ( const QRect & rectangle, bool proper = false ) + + + getCoords + getCoords + ( int * x1, int * y1, int * x2, int * y2 ) + + + getRect + getRect + ( int * x, int * y, int * width, int * height ) + + + height + height + () + + + intersected + intersected + ( const QRect & rectangle ) + + + intersects + intersects + ( const QRect & rectangle ) + + + isEmpty + isEmpty + () + + + isNull + isNull + () + + + isValid + isValid + () + + + left + left + () + + + moveBottom + moveBottom + ( int y ) + + + moveBottomLeft + moveBottomLeft + ( const QPoint & position ) + + + moveBottomRight + moveBottomRight + ( const QPoint & position ) + + + moveCenter + moveCenter + ( const QPoint & position ) + + + moveLeft + moveLeft + ( int x ) + + + moveRight + moveRight + ( int x ) + + + moveTo + moveTo + ( int x, int y ) + + + moveTo + moveTo-2 + ( const QPoint & position ) + + + moveTop + moveTop + ( int y ) + + + moveTopLeft + moveTopLeft + ( const QPoint & position ) + + + moveTopRight + moveTopRight + ( const QPoint & position ) + + + normalized + normalized + () + + + right + right + () + + + setBottom + setBottom + ( int y ) + + + setBottomLeft + setBottomLeft + ( const QPoint & position ) + + + setBottomRight + setBottomRight + ( const QPoint & position ) + + + setCoords + setCoords + ( int x1, int y1, int x2, int y2 ) + + + setHeight + setHeight + ( int height ) + + + setLeft + setLeft + ( int x ) + + + setRect + setRect + ( int x, int y, int width, int height ) + + + setRight + setRight + ( int x ) + + + setSize + setSize + ( const QSize & size ) + + + setTop + setTop + ( int y ) + + + setTopLeft + setTopLeft + ( const QPoint & position ) + + + setTopRight + setTopRight + ( const QPoint & position ) + + + setWidth + setWidth + ( int width ) + + + setX + setX + ( int x ) + + + setY + setY + ( int y ) + + + size + size + () + + + top + top + () + + + topLeft + topLeft + () + + + topRight + topRight + () + + + translate + translate + ( int dx, int dy ) + + + translate + translate-2 + ( const QPoint & offset ) + + + translated + translated + ( int dx, int dy ) + + + translated + translated-2 + ( const QPoint & offset ) + + + united + united + ( const QRect & rectangle ) + + + width + width + () + + + x + x + () + + + y + y + () + + + operator& + operator-and + amp;( const QRect & rectangle ) + + + operator& + operator-and-eq + amp;=( const QRect & rectangle ) + + + operator| + operator-7c + ( const QRect & rectangle ) + + + operator|= + operator-7c-eq + ( const QRect & rectangle ) + + + addCoords + addCoords + ( int dx1, int dy1, int dx2, int dy2 ) + + + coords + coords + ( int * x1, int * y1, int * x2, int * y2 ) + + + moveBy + moveBy + ( int dx, int dy ) + + + moveBy + moveBy-2 + ( const QPoint & p ) + + + normalize + normalize + () + + + rBottom + rBottom + () + + + rLeft + rLeft + () + + + rRight + rRight + () + + + rTop + rTop + () + + + rect + rect + ( int * x, int * y, int * width, int * height ) + + + intersect + intersect + ( const QRectF & rectangle ) + + + unite + unite + ( const QRectF & rectangle ) + + + + QRectF + qrectf.html + + QRectF + QRectF + () + + + QRectF + QRectF-2 + ( const QPointF & topLeft, const QSizeF & size ) + + + QRectF + QRectF-3 + ( const QPointF & topLeft, const QPointF & bottomRight ) + + + QRectF + QRectF-4 + ( qreal x, qreal y, qreal width, qreal height ) + + + QRectF + QRectF-5 + ( const QRect & rectangle ) + + + adjust + adjust + ( qreal dx1, qreal dy1, qreal dx2, qreal dy2 ) + + + adjusted + adjusted + ( qreal dx1, qreal dy1, qreal dx2, qreal dy2 ) + + + bottom + bottom + () + + + bottomLeft + bottomLeft + () + + + bottomRight + bottomRight + () + + + center + center + () + + + contains + contains + ( const QPointF & point ) + + + contains + contains-2 + ( qreal x, qreal y ) + + + contains + contains-3 + ( const QRectF & rectangle ) + + + getCoords + getCoords + ( qreal * x1, qreal * y1, qreal * x2, qreal * y2 ) + + + getRect + getRect + ( qreal * x, qreal * y, qreal * width, qreal * height ) + + + height + height + () + + + intersected + intersected + ( const QRectF & rectangle ) + + + intersects + intersects + ( const QRectF & rectangle ) + + + isEmpty + isEmpty + () + + + isNull + isNull + () + + + isValid + isValid + () + + + left + left + () + + + moveBottom + moveBottom + ( qreal y ) + + + moveBottomLeft + moveBottomLeft + ( const QPointF & position ) + + + moveBottomRight + moveBottomRight + ( const QPointF & position ) + + + moveCenter + moveCenter + ( const QPointF & position ) + + + moveLeft + moveLeft + ( qreal x ) + + + moveRight + moveRight + ( qreal x ) + + + moveTo + moveTo + ( qreal x, qreal y ) + + + moveTo + moveTo-2 + ( const QPointF & position ) + + + moveTop + moveTop + ( qreal y ) + + + moveTopLeft + moveTopLeft + ( const QPointF & position ) + + + moveTopRight + moveTopRight + ( const QPointF & position ) + + + normalized + normalized + () + + + right + right + () + + + setBottom + setBottom + ( qreal y ) + + + setBottomLeft + setBottomLeft + ( const QPointF & position ) + + + setBottomRight + setBottomRight + ( const QPointF & position ) + + + setCoords + setCoords + ( qreal x1, qreal y1, qreal x2, qreal y2 ) + + + setHeight + setHeight + ( qreal height ) + + + setLeft + setLeft + ( qreal x ) + + + setRect + setRect + ( qreal x, qreal y, qreal width, qreal height ) + + + setRight + setRight + ( qreal x ) + + + setSize + setSize + ( const QSizeF & size ) + + + setTop + setTop + ( qreal y ) + + + setTopLeft + setTopLeft + ( const QPointF & position ) + + + setTopRight + setTopRight + ( const QPointF & position ) + + + setWidth + setWidth + ( qreal width ) + + + setX + setX + ( qreal x ) + + + setY + setY + ( qreal y ) + + + size + size + () + + + toAlignedRect + toAlignedRect + () + + + toRect + toRect + () + + + top + top + () + + + topLeft + topLeft + () + + + topRight + topRight + () + + + translate + translate + ( qreal dx, qreal dy ) + + + translate + translate-2 + ( const QPointF & offset ) + + + translated + translated + ( qreal dx, qreal dy ) + + + translated + translated-2 + ( const QPointF & offset ) + + + united + united + ( const QRectF & rectangle ) + + + width + width + () + + + x + x + () + + + y + y + () + + + operator& + operator-and + amp;( const QRectF & rectangle ) + + + operator& + operator-and-eq + amp;=( const QRectF & rectangle ) + + + operator| + operator-7c + ( const QRectF & rectangle ) + + + operator|= + operator-7c-eq + ( const QRectF & rectangle ) + + + QRegExp + QRegExp-4 + ( const QString & pattern, bool cs, bool wildcard = false ) + + + caseSensitive + caseSensitive + () + + + minimal + minimal + () + + + search + search + ( const QString & str, int from = 0, CaretMode caretMode = CaretAtZero ) + + + searchRev + searchRev + ( const QString & str, int from = -1, CaretMode caretMode = CaretAtZero ) + + + setCaseSensitive + setCaseSensitive + ( bool sensitive ) + + + setWildcard + setWildcard + ( bool wildcard ) + + + wildcard + wildcard + () + + + + QRegExp + qregexp.html + + CaretMode + CaretMode-enum + + + + PatternSyntax + PatternSyntax-enum + + + + QRegExp + QRegExp + () + + + QRegExp + QRegExp-2 + ( const QString & pattern, Qt::CaseSensitivity cs = Qt::CaseSensitive, PatternSyntax syntax = RegExp ) + + + QRegExp + QRegExp-3 + ( const QRegExp & rx ) + + + cap + cap + ( int nth = 0 ) + + + capturedTexts + capturedTexts + () + + + CaseSensitivity + caseSensitivity + QRegExp::caseSensitivity() + + + errorString + errorString + () + + + escape + escape + ( const QString & str ) + + + exactMatch + exactMatch + ( const QString & str ) + + + indexIn + indexIn + ( const QString & str, int offset = 0, CaretMode caretMode = CaretAtZero ) + + + isEmpty + isEmpty + () + + + isMinimal + isMinimal + () + + + isValid + isValid + () + + + lastIndexIn + lastIndexIn + ( const QString & str, int offset = -1, CaretMode caretMode = CaretAtZero ) + + + matchedLength + matchedLength + () + + + numCaptures + numCaptures + () + + + pattern + pattern + () + + + patternSyntax + patternSyntax + () + + + pos + pos + ( int nth = 0 ) + + + setCaseSensitivity + setCaseSensitivity + ( Qt::CaseSensitivity cs ) + + + setMinimal + setMinimal + ( bool minimal ) + + + setPattern + setPattern + ( const QString & pattern ) + + + setPatternSyntax + setPatternSyntax + ( PatternSyntax syntax ) + + + operator!= + operator-not-eq + ( const QRegExp & rx ) + + + operator= + operator-eq + ( const QRegExp & rx ) + + + operator== + operator-eq-eq + ( const QRegExp & rx ) + + + QRegExp + QRegExp-4 + ( const QString & pattern, bool cs, bool wildcard = false ) + + + caseSensitive + caseSensitive + () + + + minimal + minimal + () + + + search + search + ( const QString & str, int from = 0, CaretMode caretMode = CaretAtZero ) + + + searchRev + searchRev + ( const QString & str, int from = -1, CaretMode caretMode = CaretAtZero ) + + + setCaseSensitive + setCaseSensitive + ( bool sensitive ) + + + setWildcard + setWildcard + ( bool wildcard ) + + + wildcard + wildcard + () + + + QRegExpValidator + QRegExpValidator-3 + ( QObject * parent, const char * name ) + + + QRegExpValidator + QRegExpValidator-4 + ( const QRegExp & rx, QObject * parent, const char * name ) + + + + QRegExpValidator + qregexpvalidator.html + + QRegExpValidator + QRegExpValidator + ( QObject * parent ) + + + QRegExpValidator + QRegExpValidator-2 + ( const QRegExp & rx, QObject * parent ) + + + State + validate + QRegExpValidator::validate( QString & input, int & pos ) + + + QRegExpValidator + QRegExpValidator-3 + ( QObject * parent, const char * name ) + + + QRegExpValidator + QRegExpValidator-4 + ( const QRegExp & rx, QObject * parent, const char * name ) + + + eor + eor + ( const QRegion & r ) + + + intersect + intersect + ( const QRegion & r ) + + + subtract + subtract + ( const QRegion & r ) + + + unite + unite + ( const QRegion & r ) + + + QRegion + QRegion-4 + ( const QPolygon & pa, bool winding ) + + + isNull + isNull + () + + + + QRegion + qregion.html + + RegionType + RegionType-enum + + + + QRegion + QRegion + () + + + QRegion + QRegion-2 + ( int x, int y, int w, int h, RegionType t = Rectangle ) + + + QRegion + QRegion-3 + ( const QPolygon & a, Qt::FillRule fillRule = Qt::OddEvenFill ) + + + QRegion + QRegion-5 + ( const QRegion & r ) + + + QRegion + QRegion-6 + ( const QBitmap & bm ) + + + QRegion + QRegion-7 + ( const QRect & r, RegionType t = Rectangle ) + + + boundingRect + boundingRect + () + + + contains + contains + ( const QPoint & p ) + + + contains + contains-2 + ( const QRect & r ) + + + handle + handle + () + + + intersected + intersected + ( const QRegion & r ) + + + intersects + intersects + ( const QRegion & region ) + + + intersects + intersects-2 + ( const QRect & rect ) + + + isEmpty + isEmpty + () + + + rects + rects + () + + + setRects + setRects + ( const QRect * rects, int number ) + + + subtracted + subtracted + ( const QRegion & r ) + + + translate + translate + ( int dx, int dy ) + + + translate + translate-2 + ( const QPoint & point ) + + + translated + translated + ( int dx, int dy ) + + + translated + translated-2 + ( const QPoint & p ) + + + united + united + ( const QRegion & r ) + + + xored + xored + ( const QRegion & r ) + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QRegion & other ) + + + operator& + operator-and + amp;( const QRegion & r ) + + + operator& + operator-and-eq + amp;=( const QRegion & r ) + + + operator+ + operator-2b + ( const QRegion & r ) + + + operator+= + operator-2b-eq + ( const QRegion & r ) + + + operator- + operator- + ( const QRegion & r ) + + + operator-= + operator--eq + ( const QRegion & r ) + + + operator= + operator-eq + ( const QRegion & r ) + + + operator== + operator-eq-eq + ( const QRegion & r ) + + + operator^ + operator-5e + ( const QRegion & r ) + + + operator^= + operator-5e-eq + ( const QRegion & r ) + + + operator| + operator-7c + ( const QRegion & r ) + + + operator|= + operator-7c-eq + ( const QRegion & r ) + + + QRegion + QRegion-4 + ( const QPolygon & pa, bool winding ) + + + isNull + isNull + () + + + + QResizeEvent + qresizeevent.html + + QResizeEvent + QResizeEvent + ( const QSize & size, const QSize & oldSize ) + + + oldSize + oldSize + () + + + size + size + () + + + addSearchPath + addSearchPath + ( const QString & path ) + + + + QResource + qresource.html + + QResource + QResource + ( const QString & file = QString() + + + absoluteFilePath + absoluteFilePath + () + + + children + children + () + + + data + data + () + + + fileName + fileName + () + + + isCompressed + isCompressed + () + + + isDir + isDir + () + + + isFile + isFile + () + + + isValid + isValid + () + + + locale + locale + () + + + registerResource + registerResource + ( const QString & rccFileName, const QString & mapRoot = QString() + + + registerResource + registerResource-2 + ( const uchar * rccData, const QString & mapRoot = QString() + + + searchPaths + searchPaths + () + + + setFileName + setFileName + ( const QString & file ) + + + setLocale + setLocale + ( const QLocale & locale ) + + + size + size + () + + + unregisterResource + unregisterResource + ( const QString & rccFileName, const QString & mapRoot = QString() + + + unregisterResource + unregisterResource-2 + ( const uchar * rccData, const QString & mapRoot = QString() + + + + QRubberBand + qrubberband.html + + Shape + Shape-enum + + + + QRubberBand + QRubberBand + ( Shape s, QWidget * p = 0 ) + + + initStyleOption + initStyleOption + ( QStyleOptionRubberBand * option ) + + + move + move + ( int x, int y ) + + + move + move-2 + ( const QPoint & p ) + + + resize + resize + ( int width, int height ) + + + resize + resize-2 + ( const QSize & size ) + + + setGeometry + setGeometry + ( const QRect & rect ) + + + setGeometry + setGeometry-2 + ( int x, int y, int width, int height ) + + + shape + shape + () + + + + QScreen + qscreen.html + + PixelType + PixelType-enum + + + + QScreen + QScreen + ( int displayId ) + + + alloc + alloc + ( unsigned int red, unsigned int green, unsigned int blue ) + + + base + base + () + + + blank + blank + ( bool on ) + + + blit + blit + ( const QImage & image, const QPoint & topLeft, const QRegion & region ) + + + clut + clut + () + + + connect + connect + ( const QString & displaySpec ) + + + createSurface + createSurface + ( const QString & key ) + + + createSurface + createSurface-2 + ( QWidget * widget ) + + + depth + depth + () + + + deviceHeight + deviceHeight + () + + + deviceWidth + deviceWidth + () + + + disconnect + disconnect + () + + + exposeRegion + exposeRegion + ( QRegion region, int windowIndex ) + + + height + height + () + + + initDevice + initDevice + () + + + instance + instance + () + + + isInterlaced + isInterlaced + () + + + isTransformed + isTransformed + () + + + linestep + linestep + () + + + mapFromDevice + mapFromDevice + ( const QSize & size ) + + + mapFromDevice + mapFromDevice-2 + ( const QPoint & point, const QSize & screenSize ) + + + mapFromDevice + mapFromDevice-3 + ( const QRect & rectangle, const QSize & screenSize ) + + + mapFromDevice + mapFromDevice-4 + ( const QImage & image ) + + + mapFromDevice + mapFromDevice-5 + ( const QRegion & region, const QSize & screenSize ) + + + mapToDevice + mapToDevice + ( const QSize & size ) + + + mapToDevice + mapToDevice-2 + ( const QPoint & point, const QSize & screenSize ) + + + mapToDevice + mapToDevice-3 + ( const QRect & rectangle, const QSize & screenSize ) + + + mapToDevice + mapToDevice-4 + ( const QImage & image ) + + + mapToDevice + mapToDevice-5 + ( const QRegion & region, const QSize & screenSize ) + + + numCols + numCols + () + + + offset + offset + () + + + onCard + onCard + ( const unsigned char * buffer ) + + + onCard + onCard-2 + ( const unsigned char * buffer, ulong & offset ) + + + physicalHeight + physicalHeight + () + + + physicalWidth + physicalWidth + () + + + Format + pixelFormat + QScreen::pixelFormat() + + + pixelType + pixelType + () + + + pixmapDepth + pixmapDepth + () + + + pixmapLinestepAlignment + pixmapLinestepAlignment + () + + + pixmapOffsetAlignment + pixmapOffsetAlignment + () + + + region + region + () + + + restore + restore + () + + + save + save + () + + + screenSize + screenSize + () + + + setDirty + setDirty + ( const QRect & rectangle ) + + + setMode + setMode + ( int width, int height, int depth ) + + + setPixelFormat + setPixelFormat + ( QImage::Format format ) + + + shutdownDevice + shutdownDevice + () + + + solidFill + solidFill + ( const QColor & color, const QRegion & region ) + + + subScreenIndexAt + subScreenIndexAt + ( const QPoint & position ) + + + subScreens + subScreens + () + + + supportsDepth + supportsDepth + ( int depth ) + + + totalSize + totalSize + () + + + transformOrientation + transformOrientation + () + + + width + width + () + + + + QScreenCursor + qscreencursor.html + + QScreenCursor + QScreenCursor + () + + + boundingRect + boundingRect + () + + + hide + hide + () + + + image + image + () + + + initSoftwareCursor + initSoftwareCursor + () + + + instance + instance + () + + + isAccelerated + isAccelerated + () + + + isVisible + isVisible + () + + + move + move + ( int x, int y ) + + + set + set + ( const QImage & image, int hotx, int hoty ) + + + show + show + () + + + + QScreenDriverFactory + qscreendriverfactory.html + + create + create + ( const QString & key, int displayId ) + + + keys + keys + () + + + + QScreenDriverPlugin + qscreendriverplugin.html + + QScreenDriverPlugin + QScreenDriverPlugin + ( QObject * parent = 0 ) + + + create + create + ( const QString & key, int displayId ) + + + keys + keys + () + + + + QScriptable + qscriptable.html + + argument + argument + ( int index ) + + + argumentCount + argumentCount + () + + + context + context + () + + + engine + engine + () + + + thisObject + thisObject + () + + + + QScriptContext + qscriptcontext.html + + Error + Error-enum + + + + ExecutionState + ExecutionState-enum + + + + activationObject + activationObject + () + + + argument + argument + ( int index ) + + + argumentCount + argumentCount + () + + + argumentsObject + argumentsObject + () + + + backtrace + backtrace + () + + + callee + callee + () + + + engine + engine + () + + + isCalledAsConstructor + isCalledAsConstructor + () + + + parentContext + parentContext + () + + + setActivationObject + setActivationObject + ( const QScriptValue & activation ) + + + setThisObject + setThisObject + ( const QScriptValue & thisObject ) + + + state + state + () + + + thisObject + thisObject + () + + + throwError + throwError + ( Error error, const QString & text ) + + + throwError + throwError-2 + ( const QString & text ) + + + throwValue + throwValue + ( const QScriptValue & value ) + + + + QScriptEngine + qscriptengine.html + + FunctionSignature + FunctionSignature-typedef + + + + ValueOwnership + ValueOwnership-enum + + + + QScriptEngine + QScriptEngine + () + + + QScriptEngine + QScriptEngine-2 + ( QObject * parent ) + + + canEvaluate + canEvaluate + ( const QString & program ) + + + collectGarbage + collectGarbage + () + + + currentContext + currentContext + () + + + defaultPrototype + defaultPrototype + ( int metaTypeId ) + + + evaluate + evaluate + ( const QString & program, const QString & fileName = QString() + + + fromScriptValue + fromScriptValue + ( const QScriptValue & value ) + + + globalObject + globalObject + () + + + hasUncaughtException + hasUncaughtException + () + + + importExtension + importExtension + ( const QString & extension ) + + + newArray + newArray + ( uint length = 0 ) + + + newDate + newDate + ( qsreal value ) + + + newDate + newDate-2 + ( const QDateTime & value ) + + + newFunction + newFunction + ( FunctionSignature fun, int length = 0 ) + + + newFunction + newFunction-2 + ( FunctionSignature fun, const QScriptValue & prototype, int length = 0 ) + + + newObject + newObject + () + + + newQMetaObject + newQMetaObject + ( const QMetaObject * metaObject, const QScriptValue & ctor = QScriptValue() + + + newQObject + newQObject + ( QObject * object, ValueOwnership ownership = QtOwnership, const QObjectWrapOptions & options = 0 ) + + + newRegExp + newRegExp + ( const QRegExp & regexp ) + + + newRegExp + newRegExp-2 + ( const QString & pattern, const QString & flags ) + + + newVariant + newVariant + ( const QVariant & value ) + + + nullValue + nullValue + () + + + processEventsInterval + processEventsInterval + () + + + scriptValueFromQMetaObject + scriptValueFromQMetaObject + () + + + setDefaultPrototype + setDefaultPrototype + ( int metaTypeId, const QScriptValue & prototype ) + + + setProcessEventsInterval + setProcessEventsInterval + ( int interval ) + + + toScriptValue + toScriptValue + ( const T & value ) + + + uncaughtException + uncaughtException + () + + + uncaughtExceptionBacktrace + uncaughtExceptionBacktrace + () + + + uncaughtExceptionLineNumber + uncaughtExceptionLineNumber + () + + + undefinedValue + undefinedValue + () + + + FunctionSignature + FunctionSignature-typedef + + + + + QScriptExtensionPlugin + qscriptextensionplugin.html + + QScriptExtensionPlugin + QScriptExtensionPlugin + ( QObject * parent = 0 ) + + + initialize + initialize + ( const QString & key, QScriptEngine * engine ) + + + keys + keys + () + + + setupPackage + setupPackage + ( const QString & key, QScriptEngine * engine ) + + + + QScriptValue + qscriptvalue.html + + SpecialValue + SpecialValue-enum + + + + QScriptValue + QScriptValue + () + + + QScriptValue + QScriptValue-2 + ( const QScriptValue & other ) + + + QScriptValue + QScriptValue-3 + ( QScriptEngine * engine, SpecialValue value ) + + + QScriptValue + QScriptValue-4 + ( QScriptEngine * engine, bool value ) + + + QScriptValue + QScriptValue-5 + ( QScriptEngine * engine, int value ) + + + QScriptValue + QScriptValue-6 + ( QScriptEngine * engine, uint value ) + + + QScriptValue + QScriptValue-7 + ( QScriptEngine * engine, qsreal value ) + + + QScriptValue + QScriptValue-8 + ( QScriptEngine * engine, const QString & value ) + + + QScriptValue + QScriptValue-9 + ( QScriptEngine * engine, const char * value ) + + + call + call + ( const QScriptValue & thisObject = QScriptValue() + + + call + call-2 + ( const QScriptValue & thisObject, const QScriptValue & arguments ) + + + construct + construct + ( const QScriptValueList & args = QScriptValueList() + + + construct + construct-2 + ( const QScriptValue & arguments ) + + + engine + engine + () + + + equals + equals + ( const QScriptValue & other ) + + + instanceOf + instanceOf + ( const QScriptValue & ctorValue ) + + + isArray + isArray + () + + + isBoolean + isBoolean + () + + + isDate + isDate + () + + + isError + isError + () + + + isFunction + isFunction + () + + + isNull + isNull + () + + + isNumber + isNumber + () + + + isObject + isObject + () + + + isQMetaObject + isQMetaObject + () + + + isQObject + isQObject + () + + + isRegExp + isRegExp + () + + + isString + isString + () + + + isUndefined + isUndefined + () + + + isValid + isValid + () + + + isVariant + isVariant + () + + + lessThan + lessThan + ( const QScriptValue & other ) + + + property + property + ( const QString & name, const ResolveFlags & mode = ResolvePrototype ) + + + property + property-2 + ( quint32 arrayIndex, const ResolveFlags & mode = ResolvePrototype ) + + + PropertyFlags + propertyFlags + QScriptValue::propertyFlags( const QString & name, const ResolveFlags & mode = ResolvePrototype ) + + + prototype + prototype + () + + + setProperty + setProperty + ( const QString & name, const QScriptValue & value, const PropertyFlags & flags = KeepExistingFlags ) + + + setProperty + setProperty-2 + ( quint32 arrayIndex, const QScriptValue & value, const PropertyFlags & flags = KeepExistingFlags ) + + + setPrototype + setPrototype + ( const QScriptValue & prototype ) + + + strictlyEquals + strictlyEquals + ( const QScriptValue & other ) + + + toBoolean + toBoolean + () + + + toDateTime + toDateTime + () + + + toInt32 + toInt32 + () + + + toInteger + toInteger + () + + + toNumber + toNumber + () + + + toObject + toObject + () + + + toQMetaObject + toQMetaObject + () + + + toQObject + toQObject + () + + + toRegExp + toRegExp + () + + + toString + toString + () + + + toUInt16 + toUInt16 + () + + + toUInt32 + toUInt32 + () + + + toVariant + toVariant + () + + + operator= + operator-eq + ( const QScriptValue & other ) + + + + QScriptValueIterator + qscriptvalueiterator.html + + QScriptValueIterator + QScriptValueIterator + ( const QScriptValue & object ) + + + PropertyFlags + flags + QScriptValueIterator::flags() + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + name + name + () + + + next + next + () + + + previous + previous + () + + + remove + remove + () + + + setValue + setValue + ( const QScriptValue & value ) + + + toBack + toBack + () + + + toFront + toFront + () + + + value + value + () + + + operator= + operator-eq + ( QScriptValue & object ) + + + + QScrollArea + qscrollarea.html + + Alignment + alignment-prop + + + + QScrollArea + QScrollArea + ( QWidget * parent = 0 ) + + + ensureVisible + ensureVisible + ( int x, int y, int xmargin = 50, int ymargin = 50 ) + + + ensureWidgetVisible + ensureWidgetVisible + ( QWidget * childWidget, int xmargin = 50, int ymargin = 50 ) + + + setWidget + setWidget + ( QWidget * widget ) + + + takeWidget + takeWidget + () + + + widget + widget + () + + + QScrollBar + QScrollBar-3 + ( QWidget * parent, const char * name ) + + + QScrollBar + QScrollBar-4 + ( Qt::Orientation orientation, QWidget * parent, const char * name ) + + + QScrollBar + QScrollBar-5 + ( int minimum, int maximum, int lineStep, int pageStep, int value, Qt::Orientation orientation, QWidget * parent = 0, const char * name = 0 ) + + + draggingSlider + draggingSlider + () + + + + QScrollBar + qscrollbar.html + + QScrollBar + QScrollBar + ( QWidget * parent = 0 ) + + + QScrollBar + QScrollBar-2 + ( Qt::Orientation orientation, QWidget * parent = 0 ) + + + initStyleOption + initStyleOption + ( QStyleOptionSlider * option ) + + + QScrollBar + QScrollBar-3 + ( QWidget * parent, const char * name ) + + + QScrollBar + QScrollBar-4 + ( Qt::Orientation orientation, QWidget * parent, const char * name ) + + + QScrollBar + QScrollBar-5 + ( int minimum, int maximum, int lineStep, int pageStep, int value, Qt::Orientation orientation, QWidget * parent = 0, const char * name = 0 ) + + + draggingSlider + draggingSlider + () + + + + QSemaphore + qsemaphore.html + + QSemaphore + QSemaphore + ( int n = 0 ) + + + acquire + acquire + ( int n = 1 ) + + + available + available + () + + + release + release + ( int n = 1 ) + + + tryAcquire + tryAcquire + ( int n = 1 ) + + + tryAcquire + tryAcquire-2 + ( int n, int timeout ) + + + + QSessionManager + qsessionmanager.html + + RestartHint + RestartHint-enum + + + + allowsErrorInteraction + allowsErrorInteraction + () + + + allowsInteraction + allowsInteraction + () + + + cancel + cancel + () + + + discardCommand + discardCommand + () + + + isPhase2 + isPhase2 + () + + + release + release + () + + + requestPhase2 + requestPhase2 + () + + + restartCommand + restartCommand + () + + + restartHint + restartHint + () + + + sessionId + sessionId + () + + + sessionKey + sessionKey + () + + + setDiscardCommand + setDiscardCommand + ( const QStringList & list ) + + + setManagerProperty + setManagerProperty + ( const QString & name, const QStringList & value ) + + + setManagerProperty + setManagerProperty-2 + ( const QString & name, const QString & value ) + + + setRestartCommand + setRestartCommand + ( const QStringList & command ) + + + setRestartHint + setRestartHint + ( RestartHint hint ) + + + + QSet::const_iterator + qset-const-iterator.html + + const_iterator + const_iterator + () + + + const_iterator + const_iterator-3 + ( const const_iterator & other ) + + + const_iterator + const_iterator-4 + ( const iterator & other ) + + + operator!= + operator-not-eq + ( const const_iterator & other ) + + + operator* + operator-2a + () + + + operator+ + operator-2b + ( int j ) + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator+= + operator-2b-eq + ( int j ) + + + operator- + operator- + ( int j ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator-= + operator--eq + ( int j ) + + + operator-& + operator--gt + gt;() + + + operator= + operator-eq + ( const const_iterator & other ) + + + operator== + operator-eq-eq + ( const const_iterator & other ) + + + + QSet::iterator + qset-iterator.html + + iterator + iterator + () + + + iterator + iterator-3 + ( const iterator & other ) + + + operator!= + operator-not-eq + ( const iterator & other ) + + + operator!= + operator-not-eq-2 + ( const const_iterator & other ) + + + operator* + operator-2a + () + + + operator+ + operator-2b + ( int j ) + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator+= + operator-2b-eq + ( int j ) + + + operator- + operator- + ( int j ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator-= + operator--eq + ( int j ) + + + operator-& + operator--gt + gt;() + + + operator= + operator-eq + ( const iterator & other ) + + + operator== + operator-eq-eq + ( const iterator & other ) + + + operator== + operator-eq-eq-2 + ( const const_iterator & other ) + + + + QSet + qset.html + + ConstIterator + ConstIterator-typedef + + + + Iterator + Iterator-typedef + + + + const_pointer + const_pointer-typedef + + + + const_reference + const_reference-typedef + + + + difference_type + difference_type-typedef + + + + key_type + key_type-typedef + + + + pointer + pointer-typedef + + + + reference + reference-typedef + + + + size_type + size_type-typedef + + + + value_type + value_type-typedef + + + + QSet + QSet + () + + + QSet + QSet-2 + ( const QSet<T> & other ) + + + begin + begin + () + + + begin + begin-2 + () + + + capacity + capacity + () + + + clear + clear + () + + + constBegin + constBegin + () + + + constEnd + constEnd + () + + + constFind + constFind + ( const T & value ) + + + contains + contains + ( const T & value ) + + + count + count + () + + + empty + empty + () + + + end + end + () + + + end + end-2 + () + + + erase + erase + ( iterator pos ) + + + find + find + ( const T & value ) + + + find + find-2 + ( const T & value ) + + + fromList + fromList + ( const QList<T> & list ) + + + insert + insert + ( const T & value ) + + + intersect + intersect + ( const QSet<T> & other ) + + + isEmpty + isEmpty + () + + + remove + remove + ( const T & value ) + + + reserve + reserve + ( int size ) + + + size + size + () + + + squeeze + squeeze + () + + + subtract + subtract + ( const QSet<T> & other ) + + + toList + toList + () + + + unite + unite + ( const QSet<T> & other ) + + + values + values + () + + + operator!= + operator-not-eq + ( const QSet<T> & other ) + + + operator& + operator-and + amp;( const QSet<T> & other ) + + + operator& + operator-and-eq + amp;=( const QSet<T> & other ) + + + operator& + operator-and-eq-2 + amp;=( const T & value ) + + + operator+ + operator-2b + ( const QSet<T> & other ) + + + operator+= + operator-2b-eq + ( const QSet<T> & other ) + + + operator+= + operator-2b-eq-2 + ( const T & value ) + + + operator- + operator- + ( const QSet<T> & other ) + + + operator-= + operator--eq + ( const QSet<T> & other ) + + + operator-= + operator--eq-2 + ( const T & value ) + + + operator<< + operator-lt-lt + ( const T & value ) + + + operator= + operator-eq + ( const QSet<T> & other ) + + + operator== + operator-eq-eq + ( const QSet<T> & other ) + + + operator| + operator-7c + ( const QSet<T> & other ) + + + operator|= + operator-7c-eq + ( const QSet<T> & other ) + + + operator|= + operator-7c-eq-2 + ( const T & value ) + + + + QSetIterator + qsetiterator.html + + QSetIterator + QSetIterator + ( const QSet<T> & set ) + + + findNext + findNext + ( const T & value ) + + + findPrevious + findPrevious + ( const T & value ) + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + next + next + () + + + peekNext + peekNext + () + + + peekPrevious + peekPrevious + () + + + previous + previous + () + + + toBack + toBack + () + + + toFront + toFront + () + + + operator= + operator-eq + ( const QSet<T> & set ) + + + setSystemIniPath + setSystemIniPath + ( const QString & dir ) + + + setUserIniPath + setUserIniPath + ( const QString & dir ) + + + System + System-enum + + + + entryList + entryList + ( const QString & key ) + + + insertSearchPath + insertSearchPath + ( System system, const QString & path ) + + + readBoolEntry + readBoolEntry + ( const QString & key, bool defaultValue = false, bool * ok = 0 ) + + + readDoubleEntry + readDoubleEntry + ( const QString & key, double defaultValue = 0, bool * ok = 0 ) + + + readEntry + readEntry + ( const QString & key, const QString & defaultValue = QString() + + + readListEntry + readListEntry + ( const QString & key, bool * ok = 0 ) + + + readListEntry + readListEntry-2 + ( const QString & key, QChar separator, bool * ok = 0 ) + + + readNumEntry + readNumEntry + ( const QString & key, int defaultValue = 0, bool * ok = 0 ) + + + removeEntry + removeEntry + ( const QString & key ) + + + removeSearchPath + removeSearchPath + ( System system, const QString & path ) + + + resetGroup + resetGroup + () + + + setPath + setPath-2 + ( const QString & organization, const QString & application, Scope scope = Global ) + + + subkeyList + subkeyList + ( const QString & key ) + + + writeEntry + writeEntry + ( const QString & key, bool value ) + + + writeEntry + writeEntry-2 + ( const QString & key, double value ) + + + writeEntry + writeEntry-3 + ( const QString & key, int value ) + + + writeEntry + writeEntry-4 + ( const QString & key, const char * value ) + + + writeEntry + writeEntry-5 + ( const QString & key, const QString & value ) + + + writeEntry + writeEntry-6 + ( const QString & key, const QStringList & value ) + + + writeEntry + writeEntry-7 + ( const QString & key, const QStringList & value, QChar separator ) + + + + QSettings + qsettings.html + + Format + Format-enum + + + + ReadFunc + ReadFunc-typedef + + + + Scope + Scope-enum + + + + SettingsMap + SettingsMap-typedef + + + + Status + Status-enum + + + + WriteFunc + WriteFunc-typedef + + + + QSettings + QSettings + ( const QString & organization, const QString & application = QString() + + + QSettings + QSettings-2 + ( Scope scope, const QString & organization, const QString & application = QString() + + + QSettings + QSettings-3 + ( Format format, Scope scope, const QString & organization, const QString & application = QString() + + + QSettings + QSettings-4 + ( const QString & fileName, Format format, QObject * parent = 0 ) + + + QSettings + QSettings-5 + ( QObject * parent = 0 ) + + + allKeys + allKeys + () + + + beginGroup + beginGroup + ( const QString & prefix ) + + + beginReadArray + beginReadArray + ( const QString & prefix ) + + + beginWriteArray + beginWriteArray + ( const QString & prefix, int size = -1 ) + + + childGroups + childGroups + () + + + childKeys + childKeys + () + + + clear + clear + () + + + contains + contains + ( const QString & key ) + + + endArray + endArray + () + + + endGroup + endGroup + () + + + fallbacksEnabled + fallbacksEnabled + () + + + fileName + fileName + () + + + group + group + () + + + isWritable + isWritable + () + + + registerFormat + registerFormat + ( const QString & extension, ReadFunc readFunc, WriteFunc writeFunc, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive ) + + + remove + remove + ( const QString & key ) + + + setArrayIndex + setArrayIndex + ( int i ) + + + setFallbacksEnabled + setFallbacksEnabled + ( bool b ) + + + setPath + setPath + ( Format format, Scope scope, const QString & path ) + + + setValue + setValue + ( const QString & key, const QVariant & value ) + + + status + status + () + + + sync + sync + () + + + value + value + ( const QString & key, const QVariant & defaultValue = QVariant() + + + System + System-enum + + + + entryList + entryList + ( const QString & key ) + + + insertSearchPath + insertSearchPath + ( System system, const QString & path ) + + + readBoolEntry + readBoolEntry + ( const QString & key, bool defaultValue = false, bool * ok = 0 ) + + + readDoubleEntry + readDoubleEntry + ( const QString & key, double defaultValue = 0, bool * ok = 0 ) + + + readEntry + readEntry + ( const QString & key, const QString & defaultValue = QString() + + + readListEntry + readListEntry + ( const QString & key, bool * ok = 0 ) + + + readListEntry + readListEntry-2 + ( const QString & key, QChar separator, bool * ok = 0 ) + + + readNumEntry + readNumEntry + ( const QString & key, int defaultValue = 0, bool * ok = 0 ) + + + removeEntry + removeEntry + ( const QString & key ) + + + removeSearchPath + removeSearchPath + ( System system, const QString & path ) + + + resetGroup + resetGroup + () + + + setPath + setPath-2 + ( const QString & organization, const QString & application, Scope scope = Global ) + + + subkeyList + subkeyList + ( const QString & key ) + + + writeEntry + writeEntry + ( const QString & key, bool value ) + + + writeEntry + writeEntry-2 + ( const QString & key, double value ) + + + writeEntry + writeEntry-3 + ( const QString & key, int value ) + + + writeEntry + writeEntry-4 + ( const QString & key, const char * value ) + + + writeEntry + writeEntry-5 + ( const QString & key, const QString & value ) + + + writeEntry + writeEntry-6 + ( const QString & key, const QStringList & value ) + + + writeEntry + writeEntry-7 + ( const QString & key, const QStringList & value, QChar separator ) + + + + QSharedData + qshareddata.html + + QSharedData + QSharedData + () + + + QSharedData + QSharedData-2 + ( const QSharedData & other ) + + + + QSharedDataPointer + qshareddatapointer.html + + QSharedDataPointer + QSharedDataPointer + () + + + QSharedDataPointer + QSharedDataPointer-2 + ( T * sharedData ) + + + QSharedDataPointer + QSharedDataPointer-3 + ( const QSharedDataPointer<T> & other ) + + + constData + constData + () + + + data + data + () + + + data + data-2 + () + + + detach + detach + () + + + operator + operator-T--2a + T *() + + + operator + operator-const-T--2a + const T *() + + + operator! + operator-not + () + + + operator!= + operator-not-eq + ( const QSharedDataPointer<T> & other ) + + + operator* + operator-2a + () + + + operator* + operator-2a-2 + () + + + operator-& + operator--gt + gt;() + + + operator-& + operator--gt-2 + gt;() + + + operator= + operator-eq + ( const QSharedDataPointer<T> & other ) + + + operator= + operator-eq-2 + ( T * sharedData ) + + + operator== + operator-eq-eq + ( const QSharedDataPointer<T> & other ) + + + + QShortcut + qshortcut.html + + ShortcutContext + context-prop + + + + QShortcut + QShortcut + ( QWidget * parent ) + + + QShortcut + QShortcut-2 + ( const QKeySequence & key, QWidget * parent, const char * member = 0, const char * ambiguousMember = 0, Qt::ShortcutContext context = Qt::WindowShortcut ) + + + activated + activated + () + + + activatedAmbiguously + activatedAmbiguously + () + + + id + id + () + + + parentWidget + parentWidget + () + + + + QShortcutEvent + qshortcutevent.html + + QShortcutEvent + QShortcutEvent + ( const QKeySequence & key, int id, bool ambiguous = false ) + + + isAmbiguous + isAmbiguous + () + + + key + key + () + + + shortcutId + shortcutId + () + + + + QShowEvent + qshowevent.html + + QShowEvent + QShowEvent + () + + + QSignalMapper + QSignalMapper-2 + ( QObject * parent, const char * name ) + + + + QSignalMapper + qsignalmapper.html + + QSignalMapper + QSignalMapper + ( QObject * parent = 0 ) + + + map + map + () + + + map + map-2 + ( QObject * sender ) + + + mapped + mapped + ( int i ) + + + mapped + mapped-2 + ( const QString & text ) + + + mapped + mapped-3 + ( QWidget * widget ) + + + mapped + mapped-4 + ( QObject * object ) + + + mapping + mapping + ( int id ) + + + mapping + mapping-2 + ( const QString & id ) + + + mapping + mapping-3 + ( QWidget * widget ) + + + mapping + mapping-4 + ( QObject * object ) + + + removeMappings + removeMappings + ( QObject * sender ) + + + setMapping + setMapping + ( QObject * sender, int id ) + + + setMapping + setMapping-2 + ( QObject * sender, const QString & text ) + + + setMapping + setMapping-3 + ( QObject * sender, QWidget * widget ) + + + setMapping + setMapping-4 + ( QObject * sender, QObject * object ) + + + QSignalMapper + QSignalMapper-2 + ( QObject * parent, const char * name ) + + + + QSignalSpy + qsignalspy.html + + QSignalSpy + QSignalSpy + ( QObject * object, const char * signal ) + + + isValid + isValid + () + + + signal + signal + () + + + + QSize + qsize.html + + QSize + QSize + () + + + QSize + QSize-2 + ( int width, int height ) + + + boundedTo + boundedTo + ( const QSize & otherSize ) + + + expandedTo + expandedTo + ( const QSize & otherSize ) + + + height + height + () + + + isEmpty + isEmpty + () + + + isNull + isNull + () + + + isValid + isValid + () + + + rheight + rheight + () + + + rwidth + rwidth + () + + + scale + scale + ( int width, int height, Qt::AspectRatioMode mode ) + + + scale + scale-2 + ( const QSize & size, Qt::AspectRatioMode mode ) + + + setHeight + setHeight + ( int height ) + + + setWidth + setWidth + ( int width ) + + + transpose + transpose + () + + + width + width + () + + + operator*= + operator-2a-eq + ( qreal factor ) + + + operator+= + operator-2b-eq + ( const QSize & size ) + + + operator-= + operator--eq + ( const QSize & size ) + + + operator/= + operator-2f-eq + ( qreal divisor ) + + + + QSizeF + qsizef.html + + QSizeF + QSizeF + () + + + QSizeF + QSizeF-2 + ( const QSize & size ) + + + QSizeF + QSizeF-3 + ( qreal width, qreal height ) + + + boundedTo + boundedTo + ( const QSizeF & otherSize ) + + + expandedTo + expandedTo + ( const QSizeF & otherSize ) + + + height + height + () + + + isEmpty + isEmpty + () + + + isNull + isNull + () + + + isValid + isValid + () + + + rheight + rheight + () + + + rwidth + rwidth + () + + + scale + scale + ( qreal width, qreal height, Qt::AspectRatioMode mode ) + + + scale + scale-2 + ( const QSizeF & size, Qt::AspectRatioMode mode ) + + + setHeight + setHeight + ( qreal height ) + + + setWidth + setWidth + ( qreal width ) + + + toSize + toSize + () + + + transpose + transpose + () + + + width + width + () + + + operator*= + operator-2a-eq + ( qreal factor ) + + + operator+= + operator-2b-eq + ( const QSizeF & size ) + + + operator-= + operator--eq + ( const QSizeF & size ) + + + operator/= + operator-2f-eq + ( qreal divisor ) + + + QSizeGrip + QSizeGrip-2 + ( QWidget * parent, const char * name ) + + + + QSizeGrip + qsizegrip.html + + QSizeGrip + QSizeGrip + ( QWidget * parent ) + + + mouseMoveEvent + mouseMoveEvent + ( QMouseEvent * event ) + + + mousePressEvent + mousePressEvent + ( QMouseEvent * event ) + + + paintEvent + paintEvent + ( QPaintEvent * event ) + + + QSizeGrip + QSizeGrip-2 + ( QWidget * parent, const char * name ) + + + ExpandData + ExpandData-enum + + + + SizeType + SizeType-typedef + + + + QSizePolicy + QSizePolicy-4 + ( Policy horizontal, Policy vertical, bool dependent ) + + + QSizePolicy + QSizePolicy-5 + ( Policy horizontal, Policy vertical, uchar horizontalStretch, uchar verticalStretch, bool dependent = false ) + + + Orientations + expanding + QSizePolicy::expanding() + + + horData + horData + () + + + horStretch + horStretch + () + + + mayGrowHorizontally + mayGrowHorizontally + () + + + mayGrowVertically + mayGrowVertically + () + + + mayShrinkHorizontally + mayShrinkHorizontally + () + + + mayShrinkVertically + mayShrinkVertically + () + + + setHorData + setHorData + ( Policy policy ) + + + setHorStretch + setHorStretch + ( uchar stretch ) + + + setVerData + setVerData + ( Policy policy ) + + + setVerStretch + setVerStretch + ( uchar stretch ) + + + verData + verData + () + + + verStretch + verStretch + () + + + + QSizePolicy + qsizepolicy.html + + Policy + Policy-enum + + + + PolicyFlag + PolicyFlag-enum + + + + QSizePolicy + QSizePolicy + () + + + QSizePolicy + QSizePolicy-2 + ( Policy horizontal, Policy vertical ) + + + QSizePolicy + QSizePolicy-3 + ( Policy horizontal, Policy vertical, ControlType type ) + + + controlType + controlType + () + + + Orientations + expandingDirections + QSizePolicy::expandingDirections() + + + hasHeightForWidth + hasHeightForWidth + () + + + horizontalPolicy + horizontalPolicy + () + + + horizontalStretch + horizontalStretch + () + + + setControlType + setControlType + ( ControlType type ) + + + setHeightForWidth + setHeightForWidth + ( bool dependent ) + + + setHorizontalPolicy + setHorizontalPolicy + ( Policy policy ) + + + setHorizontalStretch + setHorizontalStretch + ( uchar stretchFactor ) + + + setVerticalPolicy + setVerticalPolicy + ( Policy policy ) + + + setVerticalStretch + setVerticalStretch + ( uchar stretchFactor ) + + + transpose + transpose + () + + + verticalPolicy + verticalPolicy + () + + + verticalStretch + verticalStretch + () + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QSizePolicy & other ) + + + operator== + operator-eq-eq + ( const QSizePolicy & other ) + + + ExpandData + ExpandData-enum + + + + SizeType + SizeType-typedef + + + + QSizePolicy + QSizePolicy-4 + ( Policy horizontal, Policy vertical, bool dependent ) + + + QSizePolicy + QSizePolicy-5 + ( Policy horizontal, Policy vertical, uchar horizontalStretch, uchar verticalStretch, bool dependent = false ) + + + Orientations + expanding + QSizePolicy::expanding() + + + horData + horData + () + + + horStretch + horStretch + () + + + mayGrowHorizontally + mayGrowHorizontally + () + + + mayGrowVertically + mayGrowVertically + () + + + mayShrinkHorizontally + mayShrinkHorizontally + () + + + mayShrinkVertically + mayShrinkVertically + () + + + setHorData + setHorData + ( Policy policy ) + + + setHorStretch + setHorStretch + ( uchar stretch ) + + + setVerData + setVerData + ( Policy policy ) + + + setVerStretch + setVerStretch + ( uchar stretch ) + + + verData + verData + () + + + verStretch + verStretch + () + + + QSlider + QSlider-3 + ( QWidget * parent, const char * name ) + + + QSlider + QSlider-4 + ( Qt::Orientation orientation, QWidget * parent, const char * name ) + + + QSlider + QSlider-5 + ( int minValue, int maxValue, int pageStep, int value, Qt::Orientation orientation, QWidget * parent = 0, const char * name = 0 ) + + + addStep + addStep + () + + + setTickmarks + setTickmarks + ( TickPosition position ) + + + subtractStep + subtractStep + () + + + tickmarks + tickmarks + () + + + + QSlider + qslider.html + + TickPosition + TickPosition-enum + + + + QSlider + QSlider + ( QWidget * parent = 0 ) + + + QSlider + QSlider-2 + ( Qt::Orientation orientation, QWidget * parent = 0 ) + + + initStyleOption + initStyleOption + ( QStyleOptionSlider * option ) + + + QSlider + QSlider-3 + ( QWidget * parent, const char * name ) + + + QSlider + QSlider-4 + ( Qt::Orientation orientation, QWidget * parent, const char * name ) + + + QSlider + QSlider-5 + ( int minValue, int maxValue, int pageStep, int value, Qt::Orientation orientation, QWidget * parent = 0, const char * name = 0 ) + + + addStep + addStep + () + + + setTickmarks + setTickmarks + ( TickPosition position ) + + + subtractStep + subtractStep + () + + + tickmarks + tickmarks + () + + + QSocketNotifier + QSocketNotifier-2 + ( int socket, Type type, QObject * parent, const char * name ) + + + + QSocketNotifier + qsocketnotifier.html + + Type + Type-enum + + + + QSocketNotifier + QSocketNotifier + ( int socket, Type type, QObject * parent = 0 ) + + + activated + activated + ( int socket ) + + + isEnabled + isEnabled + () + + + setEnabled + setEnabled + ( bool enable ) + + + socket + socket + () + + + type + type + () + + + QSocketNotifier + QSocketNotifier-2 + ( int socket, Type type, QObject * parent, const char * name ) + + + clear + clear + () + + + filterChanged + filterChanged + () + + + + QSortFilterProxyModel + qsortfilterproxymodel.html + + CaseSensitivity + filterCaseSensitivity-prop + + + + CaseSensitivity + sortCaseSensitivity-prop + + + + QSortFilterProxyModel + QSortFilterProxyModel + ( QObject * parent = 0 ) + + + filterAcceptsColumn + filterAcceptsColumn + ( int source_column, const QModelIndex & source_parent ) + + + filterAcceptsRow + filterAcceptsRow + ( int source_row, const QModelIndex & source_parent ) + + + invalidate + invalidate + () + + + invalidateFilter + invalidateFilter + () + + + lessThan + lessThan + ( const QModelIndex & left, const QModelIndex & right ) + + + mapFromSource + mapFromSource + ( const QModelIndex & sourceIndex ) + + + mapToSource + mapToSource + ( const QModelIndex & proxyIndex ) + + + setFilterFixedString + setFilterFixedString + ( const QString & pattern ) + + + setFilterWildcard + setFilterWildcard + ( const QString & pattern ) + + + QSound + QSound-2 + ( const QString & filename, QObject * parent, const char * name ) + + + available + available + () + + + + QSound + qsound.html + + QSound + QSound + ( const QString & filename, QObject * parent = 0 ) + + + fileName + fileName + () + + + isAvailable + isAvailable + () + + + isFinished + isFinished + () + + + loops + loops + () + + + loopsRemaining + loopsRemaining + () + + + play + play + ( const QString & filename ) + + + play + play-2 + () + + + setLoops + setLoops + ( int number ) + + + stop + stop + () + + + QSound + QSound-2 + ( const QString & filename, QObject * parent, const char * name ) + + + available + available + () + + + + QSpacerItem + qspaceritem.html + + QSpacerItem + QSpacerItem + ( int w, int h, QSizePolicy::Policy hPolicy = QSizePolicy::Minimum, QSizePolicy::Policy vPolicy = QSizePolicy::Minimum ) + + + changeSize + changeSize + ( int w, int h, QSizePolicy::Policy hPolicy = QSizePolicy::Minimum, QSizePolicy::Policy vPolicy = QSizePolicy::Minimum ) + + + isEmpty + isEmpty + () + + + spacerItem + spacerItem + () + + + QSpinBox + QSpinBox-2 + ( QWidget * parent, const char * name ) + + + QSpinBox + QSpinBox-3 + ( int minimum, int maximum, int step, QWidget * parent, const char * name = 0 ) + + + maxValue + maxValue + () + + + minValue + minValue + () + + + setLineStep + setLineStep + ( int step ) + + + setMaxValue + setMaxValue + ( int value ) + + + setMinValue + setMinValue + ( int value ) + + + + QSpinBox + qspinbox.html + + QSpinBox + QSpinBox + ( QWidget * parent = 0 ) + + + setRange + setRange + ( int minimum, int maximum ) + + + textFromValue + textFromValue + ( int value ) + + + valueChanged + valueChanged + ( int i ) + + + valueChanged + valueChanged-2 + ( const QString & text ) + + + valueFromText + valueFromText + ( const QString & text ) + + + QSpinBox + QSpinBox-2 + ( QWidget * parent, const char * name ) + + + QSpinBox + QSpinBox-3 + ( int minimum, int maximum, int step, QWidget * parent, const char * name = 0 ) + + + maxValue + maxValue + () + + + minValue + minValue + () + + + setLineStep + setLineStep + ( int step ) + + + setMaxValue + setMaxValue + ( int value ) + + + setMinValue + setMinValue + ( int value ) + + + clear + clear + () + + + message + message + ( const QString & message, int alignment = Qt::AlignLeft, const QColor & color = Qt::black ) + + + + QSplashScreen + qsplashscreen.html + + QSplashScreen + QSplashScreen + ( const QPixmap & pixmap = QPixmap() + + + QSplashScreen + QSplashScreen-2 + ( QWidget * parent, const QPixmap & pixmap = QPixmap() + + + clearMessage + clearMessage + () + + + drawContents + drawContents + ( QPainter * painter ) + + + finish + finish + ( QWidget * mainWin ) + + + messageChanged + messageChanged + ( const QString & message ) + + + pixmap + pixmap + () + + + repaint + repaint + () + + + setPixmap + setPixmap + ( const QPixmap & pixmap ) + + + showMessage + showMessage + ( const QString & message, int alignment = Qt::AlignLeft, const QColor & color = Qt::black ) + + + clear + clear + () + + + message + message + ( const QString & message, int alignment = Qt::AlignLeft, const QColor & color = Qt::black ) + + + ResizeMode + ResizeMode-enum + + + + QSplitter + QSplitter-3 + ( QWidget * parent, const char * name ) + + + QSplitter + QSplitter-4 + ( Qt::Orientation orientation, QWidget * parent, const char * name ) + + + margin + margin + () + + + moveToFirst + moveToFirst + ( QWidget * widget ) + + + moveToLast + moveToLast + ( QWidget * widget ) + + + setCollapsible + setCollapsible-2 + ( QWidget * widget, bool collapsible ) + + + setMargin + setMargin + ( int margin ) + + + setResizeMode + setResizeMode + ( QWidget * widget, ResizeMode mode ) + + + + QSplitter + qsplitter.html + + Orientation + orientation-prop + + + + QSplitter + QSplitter + ( QWidget * parent = 0 ) + + + QSplitter + QSplitter-2 + ( Qt::Orientation orientation, QWidget * parent = 0 ) + + + addWidget + addWidget + ( QWidget * widget ) + + + closestLegalPosition + closestLegalPosition + ( int pos, int index ) + + + count + count + () + + + createHandle + createHandle + () + + + getRange + getRange + ( int index, int * min, int * max ) + + + handle + handle + ( int index ) + + + indexOf + indexOf + ( QWidget * widget ) + + + insertWidget + insertWidget + ( int index, QWidget * widget ) + + + isCollapsible + isCollapsible + ( int index ) + + + moveSplitter + moveSplitter + ( int pos, int index ) + + + refresh + refresh + () + + + restoreState + restoreState + ( const QByteArray & state ) + + + saveState + saveState + () + + + setCollapsible + setCollapsible + ( int index, bool collapse ) + + + setRubberBand + setRubberBand + ( int pos ) + + + setSizes + setSizes + ( const QList<int> & list ) + + + setStretchFactor + setStretchFactor + ( int index, int stretch ) + + + sizes + sizes + () + + + splitterMoved + splitterMoved + ( int pos, int index ) + + + widget + widget + ( int index ) + + + ResizeMode + ResizeMode-enum + + + + QSplitter + QSplitter-3 + ( QWidget * parent, const char * name ) + + + QSplitter + QSplitter-4 + ( Qt::Orientation orientation, QWidget * parent, const char * name ) + + + margin + margin + () + + + moveToFirst + moveToFirst + ( QWidget * widget ) + + + moveToLast + moveToLast + ( QWidget * widget ) + + + setCollapsible + setCollapsible-2 + ( QWidget * widget, bool collapsible ) + + + setMargin + setMargin + ( int margin ) + + + setResizeMode + setResizeMode + ( QWidget * widget, ResizeMode mode ) + + + + QSplitterHandle + qsplitterhandle.html + + QSplitterHandle + QSplitterHandle + ( Qt::Orientation orientation, QSplitter * parent ) + + + closestLegalPosition + closestLegalPosition + ( int pos ) + + + moveSplitter + moveSplitter + ( int pos ) + + + opaqueResize + opaqueResize + () + + + Orientation + orientation + QSplitterHandle::orientation() + + + setOrientation + setOrientation + ( Qt::Orientation orientation ) + + + splitter + splitter + () + + + Confirm + Confirm-enum + + + + Op + Op-enum + + + + Location + Location-enum + + + + NumericalPrecisionPolicy + NumericalPrecisionPolicy-enum + + + + TableType + TableType-enum + + + + Confirm + Confirm-enum + + + + Op + Op-enum + + + + record + record-2 + ( const QSqlQuery & query ) + + + recordInfo + recordInfo + ( const QString & tablename ) + + + recordInfo + recordInfo-2 + ( const QSqlQuery & query ) + + + + QSqlDatabase + qsqldatabase.html + + QSqlDatabase + QSqlDatabase + () + + + QSqlDatabase + QSqlDatabase-2 + ( const QSqlDatabase & other ) + + + QSqlDatabase + QSqlDatabase-3 + ( const QString & type ) + + + QSqlDatabase + QSqlDatabase-4 + ( QSqlDriver * driver ) + + + addDatabase + addDatabase + ( const QString & type, const QString & connectionName = QLatin1String( defaultConnection ) + + + addDatabase + addDatabase-2 + ( QSqlDriver * driver, const QString & connectionName = QLatin1String( defaultConnection ) + + + cloneDatabase + cloneDatabase + ( const QSqlDatabase & other, const QString & connectionName ) + + + close + close + () + + + commit + commit + () + + + connectOptions + connectOptions + () + + + connectionNames + connectionNames + () + + + contains + contains + ( const QString & connectionName = QLatin1String( defaultConnection ) + + + database + database + ( const QString & connectionName = QLatin1String( defaultConnection ) + + + databaseName + databaseName + () + + + driver + driver + () + + + driverName + driverName + () + + + drivers + drivers + () + + + exec + exec + ( const QString & query = QString() + + + hostName + hostName + () + + + isDriverAvailable + isDriverAvailable + ( const QString & name ) + + + isOpen + isOpen + () + + + isOpenError + isOpenError + () + + + isValid + isValid + () + + + lastError + lastError + () + + + open + open + () + + + open + open-2 + ( const QString & user, const QString & password ) + + + password + password + () + + + port + port + () + + + primaryIndex + primaryIndex + ( const QString & tablename ) + + + record + record + ( const QString & tablename ) + + + registerSqlDriver + registerSqlDriver + ( const QString & name, QSqlDriverCreatorBase * creator ) + + + removeDatabase + removeDatabase + ( const QString & connectionName ) + + + rollback + rollback + () + + + setConnectOptions + setConnectOptions + ( const QString & options = QString() + + + setDatabaseName + setDatabaseName + ( const QString & name ) + + + setHostName + setHostName + ( const QString & host ) + + + setPassword + setPassword + ( const QString & password ) + + + setPort + setPort + ( int port ) + + + setUserName + setUserName + ( const QString & name ) + + + tables + tables + ( QSql::TableType type = QSql::Tables ) + + + transaction + transaction + () + + + userName + userName + () + + + operator= + operator-eq + ( const QSqlDatabase & other ) + + + record + record-2 + ( const QSqlQuery & query ) + + + recordInfo + recordInfo + ( const QString & tablename ) + + + recordInfo + recordInfo-2 + ( const QSqlQuery & query ) + + + formatValue + formatValue-2 + ( const QSqlField * field, bool trimStrings = false ) + + + nullText + nullText + () + + + record + record-2 + ( const QSqlQuery & query ) + + + recordInfo + recordInfo + ( const QString & tablename ) + + + recordInfo + recordInfo-2 + ( const QSqlQuery & query ) + + + + QSqlDriver + qsqldriver.html + + DriverFeature + DriverFeature-enum + + + + IdentifierType + IdentifierType-enum + + + + StatementType + StatementType-enum + + + + QSqlDriver + QSqlDriver + ( QObject * parent = 0 ) + + + beginTransaction + beginTransaction + () + + + close + close + () + + + commitTransaction + commitTransaction + () + + + createResult + createResult + () + + + escapeIdentifier + escapeIdentifier + ( const QString & identifier, IdentifierType type ) + + + formatValue + formatValue + ( const QSqlField & field, bool trimStrings = false ) + + + handle + handle + () + + + hasFeature + hasFeature + ( DriverFeature feature ) + + + isOpen + isOpen + () + + + isOpenError + isOpenError + () + + + lastError + lastError + () + + + open + open + ( const QString & db, const QString & user = QString() + + + primaryIndex + primaryIndex + ( const QString & tableName ) + + + record + record + ( const QString & tableName ) + + + rollbackTransaction + rollbackTransaction + () + + + setLastError + setLastError + ( const QSqlError & error ) + + + setOpen + setOpen + ( bool open ) + + + setOpenError + setOpenError + ( bool error ) + + + sqlStatement + sqlStatement + ( StatementType type, const QString & tableName, const QSqlRecord & rec, bool preparedStatement ) + + + tables + tables + ( QSql::TableType tableType ) + + + formatValue + formatValue-2 + ( const QSqlField * field, bool trimStrings = false ) + + + nullText + nullText + () + + + record + record-2 + ( const QSqlQuery & query ) + + + recordInfo + recordInfo + ( const QString & tablename ) + + + recordInfo + recordInfo-2 + ( const QSqlQuery & query ) + + + + QSqlDriverCreator + qsqldrivercreator.html + + + QSqlDriverCreatorBase + qsqldrivercreatorbase.html + + createObject + createObject + () + + + + QSqlDriverPlugin + qsqldriverplugin.html + + QSqlDriverPlugin + QSqlDriverPlugin + ( QObject * parent = 0 ) + + + create + create + ( const QString & key ) + + + keys + keys + () + + + + QSqlError + qsqlerror.html + + ErrorType + ErrorType-enum + + + + QSqlError + QSqlError + ( const QString & driverText = QString() + + + QSqlError + QSqlError-2 + ( const QSqlError & other ) + + + databaseText + databaseText + () + + + driverText + driverText + () + + + isValid + isValid + () + + + number + number + () + + + setDatabaseText + setDatabaseText + ( const QString & databaseText ) + + + setDriverText + setDriverText + ( const QString & driverText ) + + + setNumber + setNumber + ( int number ) + + + setType + setType + ( ErrorType type ) + + + text + text + () + + + type + type + () + + + operator= + operator-eq + ( const QSqlError & other ) + + + setNull + setNull + () + + + + QSqlField + qsqlfield.html + + RequiredStatus + RequiredStatus-enum + + + + QSqlField + QSqlField + ( const QString & fieldName = QString() + + + QSqlField + QSqlField-2 + ( const QSqlField & other ) + + + clear + clear + () + + + defaultValue + defaultValue + () + + + isAutoValue + isAutoValue + () + + + isGenerated + isGenerated + () + + + isNull + isNull + () + + + isReadOnly + isReadOnly + () + + + isValid + isValid + () + + + length + length + () + + + name + name + () + + + precision + precision + () + + + requiredStatus + requiredStatus + () + + + setAutoValue + setAutoValue + ( bool autoVal ) + + + setDefaultValue + setDefaultValue + ( const QVariant & value ) + + + setGenerated + setGenerated + ( bool gen ) + + + setLength + setLength + ( int fieldLength ) + + + setName + setName + ( const QString & name ) + + + setPrecision + setPrecision + ( int precision ) + + + setReadOnly + setReadOnly + ( bool readOnly ) + + + setRequired + setRequired + ( bool required ) + + + setRequiredStatus + setRequiredStatus + ( RequiredStatus required ) + + + setType + setType + ( QVariant::Type type ) + + + setValue + setValue + ( const QVariant & value ) + + + Type + type + QSqlField::type() + + + value + value + () + + + operator!= + operator-not-eq + ( const QSqlField & other ) + + + operator= + operator-eq + ( const QSqlField & other ) + + + operator== + operator-eq-eq + ( const QSqlField & other ) + + + setNull + setNull + () + + + toString + toString + ( const QString & prefix = QString() + + + toStringList + toStringList + ( const QString & prefix = QString() + + + + QSqlIndex + qsqlindex.html + + QSqlIndex + QSqlIndex + ( const QString & cursorname = QString() + + + QSqlIndex + QSqlIndex-2 + ( const QSqlIndex & other ) + + + append + append + ( const QSqlField & field ) + + + append + append-2 + ( const QSqlField & field, bool desc ) + + + cursorName + cursorName + () + + + isDescending + isDescending + ( int i ) + + + name + name + () + + + setCursorName + setCursorName + ( const QString & cursorName ) + + + setDescending + setDescending + ( int i, bool desc ) + + + setName + setName + ( const QString & name ) + + + operator= + operator-eq + ( const QSqlIndex & other ) + + + toString + toString + ( const QString & prefix = QString() + + + toStringList + toStringList + ( const QString & prefix = QString() + + + prev + prev + () + + + + QSqlQuery + qsqlquery.html + + BatchExecutionMode + BatchExecutionMode-enum + + + + QSqlQuery + QSqlQuery + ( QSqlResult * result ) + + + QSqlQuery + QSqlQuery-2 + ( const QString & query = QString() + + + QSqlQuery + QSqlQuery-3 + ( QSqlDatabase db ) + + + QSqlQuery + QSqlQuery-4 + ( const QSqlQuery & other ) + + + addBindValue + addBindValue + ( const QVariant & val, QSql::ParamType paramType = QSql::In ) + + + at + at + () + + + bindValue + bindValue + ( const QString & placeholder, const QVariant & val, QSql::ParamType paramType = QSql::In ) + + + bindValue + bindValue-2 + ( int pos, const QVariant & val, QSql::ParamType paramType = QSql::In ) + + + boundValue + boundValue + ( const QString & placeholder ) + + + boundValue + boundValue-2 + ( int pos ) + + + boundValues + boundValues + () + + + clear + clear + () + + + driver + driver + () + + + exec + exec + ( const QString & query ) + + + exec + exec-2 + () + + + execBatch + execBatch + ( BatchExecutionMode mode = ValuesAsRows ) + + + executedQuery + executedQuery + () + + + first + first + () + + + isActive + isActive + () + + + isForwardOnly + isForwardOnly + () + + + isNull + isNull + ( int field ) + + + isSelect + isSelect + () + + + isValid + isValid + () + + + last + last + () + + + lastError + lastError + () + + + lastInsertId + lastInsertId + () + + + lastQuery + lastQuery + () + + + next + next + () + + + numRowsAffected + numRowsAffected + () + + + NumericalPrecisionPolicy + numericalPrecisionPolicy + QSqlQuery::numericalPrecisionPolicy() + + + prepare + prepare + ( const QString & query ) + + + previous + previous + () + + + record + record + () + + + result + result + () + + + seek + seek + ( int index, bool relative = false ) + + + setForwardOnly + setForwardOnly + ( bool forward ) + + + setNumericalPrecisionPolicy + setNumericalPrecisionPolicy + ( QSql::NumericalPrecisionPolicy precisionPolicy ) + + + size + size + () + + + value + value + ( int index ) + + + operator= + operator-eq + ( const QSqlQuery & other ) + + + prev + prev + () + + + + QSqlQueryModel + qsqlquerymodel.html + + QSqlQueryModel + QSqlQueryModel + ( QObject * parent = 0 ) + + + canFetchMore + canFetchMore + ( const QModelIndex & parent = QModelIndex() + + + clear + clear + () + + + data + data + ( const QModelIndex & item, int role = Qt::DisplayRole ) + + + fetchMore + fetchMore + ( const QModelIndex & parent = QModelIndex() + + + headerData + headerData + ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) + + + indexInQuery + indexInQuery + ( const QModelIndex & item ) + + + insertColumns + insertColumns + ( int column, int count, const QModelIndex & parent = QModelIndex() + + + lastError + lastError + () + + + query + query + () + + + queryChange + queryChange + () + + + record + record + ( int row ) + + + record + record-2 + () + + + removeColumns + removeColumns + ( int column, int count, const QModelIndex & parent = QModelIndex() + + + rowCount + rowCount + ( const QModelIndex & parent = QModelIndex() + + + setHeaderData + setHeaderData + ( int section, Qt::Orientation orientation, const QVariant & value, int role = Qt::EditRole ) + + + setLastError + setLastError + ( const QSqlError & error ) + + + setQuery + setQuery + ( const QSqlQuery & query ) + + + setQuery + setQuery-2 + ( const QString & query, const QSqlDatabase & db = QSqlDatabase() + + + fieldPtr + fieldPtr + ( int index ) + + + fieldPtr + fieldPtr-2 + ( const QString & name ) + + + position + position + ( const QString & name ) + + + toString + toString + ( const QString & prefix = QString() + + + toStringList + toStringList + ( const QString & prefix = QString() + + + + QSqlRecord + qsqlrecord.html + + QSqlRecord + QSqlRecord + () + + + QSqlRecord + QSqlRecord-2 + ( const QSqlRecord & other ) + + + append + append + ( const QSqlField & field ) + + + clear + clear + () + + + clearValues + clearValues + () + + + contains + contains + ( const QString & name ) + + + count + count + () + + + field + field + ( int index ) + + + field + field-2 + ( const QString & name ) + + + fieldName + fieldName + ( int index ) + + + indexOf + indexOf + ( const QString & name ) + + + insert + insert + ( int pos, const QSqlField & field ) + + + isEmpty + isEmpty + () + + + isGenerated + isGenerated + ( const QString & name ) + + + isGenerated + isGenerated-2 + ( int index ) + + + isNull + isNull + ( const QString & name ) + + + isNull + isNull-2 + ( int index ) + + + remove + remove + ( int pos ) + + + replace + replace + ( int pos, const QSqlField & field ) + + + setGenerated + setGenerated + ( const QString & name, bool generated ) + + + setGenerated + setGenerated-2 + ( int index, bool generated ) + + + setNull + setNull + ( int index ) + + + setNull + setNull-2 + ( const QString & name ) + + + setValue + setValue + ( int index, const QVariant & val ) + + + setValue + setValue-2 + ( const QString & name, const QVariant & val ) + + + value + value + ( int index ) + + + value + value-2 + ( const QString & name ) + + + operator!= + operator-not-eq + ( const QSqlRecord & other ) + + + operator= + operator-eq + ( const QSqlRecord & other ) + + + operator== + operator-eq-eq + ( const QSqlRecord & other ) + + + fieldPtr + fieldPtr + ( int index ) + + + fieldPtr + fieldPtr-2 + ( const QString & name ) + + + position + position + ( const QString & name ) + + + toString + toString + ( const QString & prefix = QString() + + + toStringList + toStringList + ( const QString & prefix = QString() + + + + QSqlRelation + qsqlrelation.html + + QSqlRelation + QSqlRelation + () + + + QSqlRelation + QSqlRelation-2 + ( const QString & tableName, const QString & indexColumn, const QString & displayColumn ) + + + displayColumn + displayColumn + () + + + indexColumn + indexColumn + () + + + isValid + isValid + () + + + tableName + tableName + () + + + + QSqlRelationalDelegate + qsqlrelationaldelegate.html + + QSqlRelationalDelegate + QSqlRelationalDelegate + ( QObject * parent = 0 ) + + + + QSqlRelationalTableModel + qsqlrelationaltablemodel.html + + QSqlRelationalTableModel + QSqlRelationalTableModel + ( QObject * parent = 0, QSqlDatabase db = QSqlDatabase() + + + relation + relation + ( int column ) + + + relationModel + relationModel + ( int column ) + + + setData + setData + ( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ) + + + setRelation + setRelation + ( int column, const QSqlRelation & relation ) + + + + QSqlResult + qsqlresult.html + + BindingSyntax + BindingSyntax-enum + + + + QSqlResult + QSqlResult + ( const QSqlDriver * db ) + + + addBindValue + addBindValue + ( const QVariant & val, QSql::ParamType paramType ) + + + at + at + () + + + bindValue + bindValue + ( int index, const QVariant & val, QSql::ParamType paramType ) + + + bindValue + bindValue-2 + ( const QString & placeholder, const QVariant & val, QSql::ParamType paramType ) + + + ParamType + bindValueType + QSqlResult::bindValueType( int index ) + + + ParamType + bindValueType-2 + QSqlResult::bindValueType( const QString & placeholder ) + + + bindingSyntax + bindingSyntax + () + + + boundValue + boundValue + ( int index ) + + + boundValue + boundValue-2 + ( const QString & placeholder ) + + + boundValueCount + boundValueCount + () + + + boundValueName + boundValueName + ( int index ) + + + boundValues + boundValues + () + + + clear + clear + () + + + data + data + ( int index ) + + + driver + driver + () + + + exec + exec + () + + + executedQuery + executedQuery + () + + + fetch + fetch + ( int index ) + + + fetchFirst + fetchFirst + () + + + fetchLast + fetchLast + () + + + fetchNext + fetchNext + () + + + fetchPrevious + fetchPrevious + () + + + handle + handle + () + + + hasOutValues + hasOutValues + () + + + isActive + isActive + () + + + isForwardOnly + isForwardOnly + () + + + isNull + isNull + ( int index ) + + + isSelect + isSelect + () + + + isValid + isValid + () + + + lastError + lastError + () + + + lastInsertId + lastInsertId + () + + + lastQuery + lastQuery + () + + + numRowsAffected + numRowsAffected + () + + + prepare + prepare + ( const QString & query ) + + + record + record + () + + + reset + reset + ( const QString & query ) + + + savePrepare + savePrepare + ( const QString & query ) + + + setActive + setActive + ( bool active ) + + + setAt + setAt + ( int index ) + + + setForwardOnly + setForwardOnly + ( bool forward ) + + + setLastError + setLastError + ( const QSqlError & error ) + + + setQuery + setQuery + ( const QString & query ) + + + setSelect + setSelect + ( bool select ) + + + size + size + () + + + + QSqlTableModel + qsqltablemodel.html + + EditStrategy + EditStrategy-enum + + + + QSqlTableModel + QSqlTableModel + ( QObject * parent = 0, QSqlDatabase db = QSqlDatabase() + + + beforeDelete + beforeDelete + ( int row ) + + + beforeInsert + beforeInsert + ( QSqlRecord & record ) + + + beforeUpdate + beforeUpdate + ( int row, QSqlRecord & record ) + + + database + database + () + + + deleteRowFromTable + deleteRowFromTable + ( int row ) + + + editStrategy + editStrategy + () + + + fieldIndex + fieldIndex + ( const QString & fieldName ) + + + filter + filter + () + + + indexInQuery + indexInQuery + ( const QModelIndex & item ) + + + insertRecord + insertRecord + ( int row, const QSqlRecord & record ) + + + insertRowIntoTable + insertRowIntoTable + ( const QSqlRecord & values ) + + + insertRows + insertRows + ( int row, int count, const QModelIndex & parent = QModelIndex() + + + isDirty + isDirty + ( const QModelIndex & index ) + + + orderByClause + orderByClause + () + + + primaryKey + primaryKey + () + + + primeInsert + primeInsert + ( int row, QSqlRecord & record ) + + + removeColumns + removeColumns + ( int column, int count, const QModelIndex & parent = QModelIndex() + + + removeRows + removeRows + ( int row, int count, const QModelIndex & parent = QModelIndex() + + + revert + revert + () + + + revertAll + revertAll + () + + + revertRow + revertRow + ( int row ) + + + select + select + () + + + selectStatement + selectStatement + () + + + setData + setData + ( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ) + + + setEditStrategy + setEditStrategy + ( EditStrategy strategy ) + + + setFilter + setFilter + ( const QString & filter ) + + + setPrimaryKey + setPrimaryKey + ( const QSqlIndex & key ) + + + setQuery + setQuery + ( const QSqlQuery & query ) + + + setRecord + setRecord + ( int row, const QSqlRecord & record ) + + + setSort + setSort + ( int column, Qt::SortOrder order ) + + + setTable + setTable + ( const QString & tableName ) + + + sort + sort + ( int column, Qt::SortOrder order ) + + + submit + submit + () + + + submitAll + submitAll + () + + + tableName + tableName + () + + + updateRowInTable + updateRowInTable + ( int row, const QSqlRecord & values ) + + + AlternateNameEntryType + AlternateNameEntryType-enum + + + + EncodingFormat + EncodingFormat-enum + + + + KeyAlgorithm + KeyAlgorithm-enum + + + + KeyType + KeyType-enum + + + + SslProtocol + SslProtocol-enum + + + + + QSslCertificate + qsslcertificate.html + + SubjectInfo + SubjectInfo-enum + + + + QSslCertificate + QSslCertificate + ( QIODevice * device, QSsl::EncodingFormat format = QSsl::Pem ) + + + QSslCertificate + QSslCertificate-2 + ( const QByteArray & data = QByteArray() + + + QSslCertificate + QSslCertificate-3 + ( const QSslCertificate & other ) + + + AlternateNameEntryType + alternateSubjectNames + , QString> QSslCertificate::alternateSubjectNames() + + + clear + clear + () + + + digest + digest + ( QCryptographicHash::Algorithm algorithm = QCryptographicHash::Md5 ) + + + effectiveDate + effectiveDate + () + + + expiryDate + expiryDate + () + + + fromData + fromData + ( const QByteArray & data, QSsl::EncodingFormat format = QSsl::Pem ) + + + fromDevice + fromDevice + ( QIODevice * device, QSsl::EncodingFormat format = QSsl::Pem ) + + + fromPath + fromPath + ( const QString & path, QSsl::EncodingFormat format = QSsl::Pem, QRegExp::PatternSyntax syntax = QRegExp::FixedString ) + + + HANDLE + handle + QSslCertificate::handle() + + + isNull + isNull + () + + + isValid + isValid + () + + + issuerInfo + issuerInfo + ( SubjectInfo subject ) + + + issuerInfo + issuerInfo-2 + ( const QByteArray & tag ) + + + publicKey + publicKey + () + + + serialNumber + serialNumber + () + + + subjectInfo + subjectInfo + ( SubjectInfo subject ) + + + subjectInfo + subjectInfo-2 + ( const QByteArray & tag ) + + + toDer + toDer + () + + + toPem + toPem + () + + + version + version + () + + + operator!= + operator-not-eq + ( const QSslCertificate & other ) + + + operator= + operator-eq + ( const QSslCertificate & other ) + + + operator== + operator-eq-eq + ( const QSslCertificate & other ) + + + + QSslCipher + qsslcipher.html + + QSslCipher + QSslCipher + () + + + QSslCipher + QSslCipher-2 + ( const QString & name, QSsl::SslProtocol protocol ) + + + QSslCipher + QSslCipher-3 + ( const QSslCipher & other ) + + + authenticationMethod + authenticationMethod + () + + + encryptionMethod + encryptionMethod + () + + + isNull + isNull + () + + + keyExchangeMethod + keyExchangeMethod + () + + + name + name + () + + + SslProtocol + protocol + QSslCipher::protocol() + + + protocolString + protocolString + () + + + supportedBits + supportedBits + () + + + usedBits + usedBits + () + + + operator!= + operator-not-eq + ( const QSslCipher & other ) + + + operator= + operator-eq + ( const QSslCipher & other ) + + + operator== + operator-eq-eq + ( const QSslCipher & other ) + + + + QSslError + qsslerror.html + + SslError + SslError-enum + + + + QSslError + QSslError + ( SslError error = NoError, const QSslCertificate & certificate = QSslCertificate() + + + QSslError + QSslError-2 + ( const QSslError & other ) + + + certificate + certificate + () + + + error + error + () + + + errorString + errorString + () + + + + QSslKey + qsslkey.html + + QSslKey + QSslKey + () + + + QSslKey + QSslKey-2 + ( const QByteArray & encoded, QSsl::KeyAlgorithm algorithm, QSsl::EncodingFormat encoding = QSsl::Pem, QSsl::KeyType type = QSsl::PrivateKey, const QByteArray & passPhrase = QByteArray() + + + QSslKey + QSslKey-3 + ( QIODevice * device, QSsl::KeyAlgorithm algorithm, QSsl::EncodingFormat encoding = QSsl::Pem, QSsl::KeyType type = QSsl::PrivateKey, const QByteArray & passPhrase = QByteArray() + + + QSslKey + QSslKey-4 + ( const QSslKey & other ) + + + KeyAlgorithm + algorithm + QSslKey::algorithm() + + + clear + clear + () + + + HANDLE + handle + QSslKey::handle() + + + isNull + isNull + () + + + length + length + () + + + toDer + toDer + ( const QByteArray & passPhrase = QByteArray() + + + toPem + toPem + ( const QByteArray & passPhrase = QByteArray() + + + KeyType + type + QSslKey::type() + + + operator!= + operator-not-eq + ( const QSslKey & other ) + + + operator= + operator-eq + ( const QSslKey & other ) + + + operator== + operator-eq-eq + ( const QSslKey & other ) + + + + QSslSocket + qsslsocket.html + + SslMode + SslMode-enum + + + + QSslSocket + QSslSocket + ( QObject * parent = 0 ) + + + abort + abort + () + + + addCaCertificate + addCaCertificate + ( const QSslCertificate & certificate ) + + + addCaCertificates + addCaCertificates + ( const QString & path, QSsl::EncodingFormat format = QSsl::Pem, QRegExp::PatternSyntax syntax = QRegExp::FixedString ) + + + addCaCertificates + addCaCertificates-2 + ( const QList<QSslCertificate> & certificates ) + + + addDefaultCaCertificate + addDefaultCaCertificate + ( const QSslCertificate & certificate ) + + + addDefaultCaCertificates + addDefaultCaCertificates + ( const QString & path, QSsl::EncodingFormat encoding = QSsl::Pem, QRegExp::PatternSyntax syntax = QRegExp::FixedString ) + + + addDefaultCaCertificates + addDefaultCaCertificates-2 + ( const QList<QSslCertificate> & certificates ) + + + caCertificates + caCertificates + () + + + ciphers + ciphers + () + + + connectToHostEncrypted + connectToHostEncrypted + ( const QString & hostName, quint16 port, OpenMode mode = ReadWrite ) + + + defaultCaCertificates + defaultCaCertificates + () + + + defaultCiphers + defaultCiphers + () + + + encrypted + encrypted + () + + + flush + flush + () + + + ignoreSslErrors + ignoreSslErrors + () + + + isEncrypted + isEncrypted + () + + + localCertificate + localCertificate + () + + + mode + mode + () + + + modeChanged + modeChanged + ( QSslSocket::SslMode mode ) + + + peerCertificate + peerCertificate + () + + + peerCertificateChain + peerCertificateChain + () + + + privateKey + privateKey + () + + + SslProtocol + protocol + QSslSocket::protocol() + + + sessionCipher + sessionCipher + () + + + setCaCertificates + setCaCertificates + ( const QList<QSslCertificate> & certificates ) + + + setCiphers + setCiphers + ( const QList<QSslCipher> & ciphers ) + + + setCiphers + setCiphers-2 + ( const QString & ciphers ) + + + setDefaultCaCertificates + setDefaultCaCertificates + ( const QList<QSslCertificate> & certificates ) + + + setDefaultCiphers + setDefaultCiphers + ( const QList<QSslCipher> & ciphers ) + + + setLocalCertificate + setLocalCertificate + ( const QSslCertificate & certificate ) + + + setLocalCertificate + setLocalCertificate-2 + ( const QString & path, QSsl::EncodingFormat format = QSsl::Pem ) + + + setPrivateKey + setPrivateKey + ( const QSslKey & key ) + + + setPrivateKey + setPrivateKey-2 + ( const QString & fileName, QSsl::KeyAlgorithm algorithm = QSsl::Rsa, QSsl::EncodingFormat format = QSsl::Pem, const QByteArray & passPhrase = QByteArray() + + + setProtocol + setProtocol + ( QSsl::SslProtocol protocol ) + + + setSocketDescriptor + setSocketDescriptor + ( int socketDescriptor, SocketState state = ConnectedState, OpenMode openMode = ReadWrite ) + + + sslErrors + sslErrors + () + + + sslErrors + sslErrors-2 + ( const QList<QSslError> & errors ) + + + startClientEncryption + startClientEncryption + () + + + startServerEncryption + startServerEncryption + () + + + supportedCiphers + supportedCiphers + () + + + supportsSsl + supportsSsl + () + + + systemCaCertificates + systemCaCertificates + () + + + waitForConnected + waitForConnected + ( int msecs = 30000 ) + + + waitForDisconnected + waitForDisconnected + ( int msecs = 30000 ) + + + waitForEncrypted + waitForEncrypted + ( int msecs = 30000 ) + + + + QStack + qstack.html + + QStack + QStack + () + + + pop + pop + () + + + push + push + ( const T & t ) + + + top + top + () + + + top + top-2 + () + + + + QStackedLayout + qstackedlayout.html + + QStackedLayout + QStackedLayout + () + + + QStackedLayout + QStackedLayout-2 + ( QWidget * parent ) + + + QStackedLayout + QStackedLayout-3 + ( QLayout * parentLayout ) + + + addWidget + addWidget + ( QWidget * widget ) + + + currentChanged + currentChanged + ( int index ) + + + currentWidget + currentWidget + () + + + insertWidget + insertWidget + ( int index, QWidget * widget ) + + + setCurrentWidget + setCurrentWidget + ( QWidget * widget ) + + + widget + widget + ( int index ) + + + widgetRemoved + widgetRemoved + ( int index ) + + + + QStackedWidget + qstackedwidget.html + + QStackedWidget + QStackedWidget + ( QWidget * parent = 0 ) + + + addWidget + addWidget + ( QWidget * widget ) + + + currentChanged + currentChanged + ( int index ) + + + currentWidget + currentWidget + () + + + indexOf + indexOf + ( QWidget * widget ) + + + insertWidget + insertWidget + ( int index, QWidget * widget ) + + + removeWidget + removeWidget + ( QWidget * widget ) + + + setCurrentWidget + setCurrentWidget + ( QWidget * widget ) + + + widget + widget + ( int index ) + + + widgetRemoved + widgetRemoved + ( int index ) + + + + QStandardItem + qstandarditem.html + + ItemType + ItemType-enum + + + + QStandardItem + QStandardItem + () + + + QStandardItem + QStandardItem-2 + ( const QString & text ) + + + QStandardItem + QStandardItem-3 + ( const QIcon & icon, const QString & text ) + + + QStandardItem + QStandardItem-4 + ( int rows, int columns = 1 ) + + + QStandardItem + QStandardItem-5 + ( const QStandardItem & other ) + + + accessibleDescription + accessibleDescription + () + + + accessibleText + accessibleText + () + + + appendColumn + appendColumn + ( const QList<QStandardItem *> & items ) + + + appendRow + appendRow + ( const QList<QStandardItem *> & items ) + + + appendRow + appendRow-2 + ( QStandardItem * item ) + + + appendRows + appendRows + ( const QList<QStandardItem *> & items ) + + + background + background + () + + + CheckState + checkState + QStandardItem::checkState() + + + child + child + ( int row, int column = 0 ) + + + clone + clone + () + + + column + column + () + + + columnCount + columnCount + () + + + data + data + ( int role = Qt::UserRole + 1 ) + + + ItemFlags + flags + QStandardItem::flags() + + + font + font + () + + + foreground + foreground + () + + + hasChildren + hasChildren + () + + + icon + icon + () + + + index + index + () + + + insertColumn + insertColumn + ( int column, const QList<QStandardItem *> & items ) + + + insertColumns + insertColumns + ( int column, int count ) + + + insertRow + insertRow + ( int row, const QList<QStandardItem *> & items ) + + + insertRow + insertRow-2 + ( int row, QStandardItem * item ) + + + insertRows + insertRows + ( int row, const QList<QStandardItem *> & items ) + + + insertRows + insertRows-2 + ( int row, int count ) + + + isCheckable + isCheckable + () + + + isDragEnabled + isDragEnabled + () + + + isDropEnabled + isDropEnabled + () + + + isEditable + isEditable + () + + + isEnabled + isEnabled + () + + + isSelectable + isSelectable + () + + + isTristate + isTristate + () + + + model + model + () + + + parent + parent + () + + + read + read + ( QDataStream & in ) + + + removeColumn + removeColumn + ( int column ) + + + removeColumns + removeColumns + ( int column, int count ) + + + removeRow + removeRow + ( int row ) + + + removeRows + removeRows + ( int row, int count ) + + + row + row + () + + + rowCount + rowCount + () + + + setAccessibleDescription + setAccessibleDescription + ( const QString & accessibleDescription ) + + + setAccessibleText + setAccessibleText + ( const QString & accessibleText ) + + + setBackground + setBackground + ( const QBrush & brush ) + + + setCheckState + setCheckState + ( Qt::CheckState state ) + + + setCheckable + setCheckable + ( bool checkable ) + + + setChild + setChild + ( int row, int column, QStandardItem * item ) + + + setChild + setChild-2 + ( int row, QStandardItem * item ) + + + setColumnCount + setColumnCount + ( int columns ) + + + setData + setData + ( const QVariant & value, int role = Qt::UserRole + 1 ) + + + setDragEnabled + setDragEnabled + ( bool dragEnabled ) + + + setDropEnabled + setDropEnabled + ( bool dropEnabled ) + + + setEditable + setEditable + ( bool editable ) + + + setEnabled + setEnabled + ( bool enabled ) + + + setFlags + setFlags + ( Qt::ItemFlags flags ) + + + setFont + setFont + ( const QFont & font ) + + + setForeground + setForeground + ( const QBrush & brush ) + + + setIcon + setIcon + ( const QIcon & icon ) + + + setRowCount + setRowCount + ( int rows ) + + + setSelectable + setSelectable + ( bool selectable ) + + + setSizeHint + setSizeHint + ( const QSize & size ) + + + setStatusTip + setStatusTip + ( const QString & statusTip ) + + + setText + setText + ( const QString & text ) + + + setTextAlignment + setTextAlignment + ( Qt::Alignment alignment ) + + + setToolTip + setToolTip + ( const QString & toolTip ) + + + setTristate + setTristate + ( bool tristate ) + + + setWhatsThis + setWhatsThis + ( const QString & whatsThis ) + + + sizeHint + sizeHint + () + + + sortChildren + sortChildren + ( int column, Qt::SortOrder order = Qt::AscendingOrder ) + + + statusTip + statusTip + () + + + takeChild + takeChild + ( int row, int column = 0 ) + + + takeColumn + takeColumn + ( int column ) + + + takeRow + takeRow + ( int row ) + + + text + text + () + + + Alignment + textAlignment + QStandardItem::textAlignment() + + + toolTip + toolTip + () + + + type + type + () + + + whatsThis + whatsThis + () + + + write + write + ( QDataStream & out ) + + + operator< + operator-lt + ( const QStandardItem & other ) + + + operator= + operator-eq + ( const QStandardItem & other ) + + + + QStandardItemEditorCreator + qstandarditemeditorcreator.html + + QStandardItemEditorCreator + QStandardItemEditorCreator + () + + + + QStandardItemModel + qstandarditemmodel.html + + QStandardItemModel + QStandardItemModel + ( QObject * parent = 0 ) + + + QStandardItemModel + QStandardItemModel-2 + ( int rows, int columns, QObject * parent = 0 ) + + + appendColumn + appendColumn + ( const QList<QStandardItem *> & items ) + + + appendRow + appendRow + ( const QList<QStandardItem *> & items ) + + + appendRow + appendRow-2 + ( QStandardItem * item ) + + + clear + clear + () + + + findItems + findItems + ( const QString & text, Qt::MatchFlags flags = Qt::MatchExactly, int column = 0 ) + + + horizontalHeaderItem + horizontalHeaderItem + ( int column ) + + + indexFromItem + indexFromItem + ( const QStandardItem * item ) + + + insertColumn + insertColumn + ( int column, const QList<QStandardItem *> & items ) + + + insertRow + insertRow + ( int row, const QList<QStandardItem *> & items ) + + + insertRow + insertRow-3 + ( int row, QStandardItem * item ) + + + invisibleRootItem + invisibleRootItem + () + + + item + item + ( int row, int column = 0 ) + + + itemChanged + itemChanged + ( QStandardItem * item ) + + + itemFromIndex + itemFromIndex + ( const QModelIndex & index ) + + + itemPrototype + itemPrototype + () + + + setColumnCount + setColumnCount + ( int columns ) + + + setHorizontalHeaderItem + setHorizontalHeaderItem + ( int column, QStandardItem * item ) + + + setHorizontalHeaderLabels + setHorizontalHeaderLabels + ( const QStringList & labels ) + + + setItem + setItem + ( int row, int column, QStandardItem * item ) + + + setItem + setItem-2 + ( int row, QStandardItem * item ) + + + setItemPrototype + setItemPrototype + ( const QStandardItem * item ) + + + setRowCount + setRowCount + ( int rows ) + + + setVerticalHeaderItem + setVerticalHeaderItem + ( int row, QStandardItem * item ) + + + setVerticalHeaderLabels + setVerticalHeaderLabels + ( const QStringList & labels ) + + + takeColumn + takeColumn + ( int column ) + + + takeHorizontalHeaderItem + takeHorizontalHeaderItem + ( int column ) + + + takeItem + takeItem + ( int row, int column = 0 ) + + + takeRow + takeRow + ( int row ) + + + takeVerticalHeaderItem + takeVerticalHeaderItem + ( int row ) + + + verticalHeaderItem + verticalHeaderItem + ( int row ) + + + QStatusBar + QStatusBar-2 + ( QWidget * parent, const char * name ) + + + addWidget + addWidget-2 + ( QWidget * widget, int stretch, bool permanent ) + + + clear + clear + () + + + message + message + ( const QString & message, int timeout = 0 ) + + + + QStatusBar + qstatusbar.html + + QStatusBar + QStatusBar + ( QWidget * parent = 0 ) + + + addPermanentWidget + addPermanentWidget + ( QWidget * widget, int stretch = 0 ) + + + addWidget + addWidget + ( QWidget * widget, int stretch = 0 ) + + + clearMessage + clearMessage + () + + + currentMessage + currentMessage + () + + + hideOrShow + hideOrShow + () + + + insertPermanentWidget + insertPermanentWidget + ( int index, QWidget * widget, int stretch = 0 ) + + + insertWidget + insertWidget + ( int index, QWidget * widget, int stretch = 0 ) + + + messageChanged + messageChanged + ( const QString & message ) + + + reformat + reformat + () + + + removeWidget + removeWidget + ( QWidget * widget ) + + + showMessage + showMessage + ( const QString & message, int timeout = 0 ) + + + QStatusBar + QStatusBar-2 + ( QWidget * parent, const char * name ) + + + addWidget + addWidget-2 + ( QWidget * widget, int stretch, bool permanent ) + + + clear + clear + () + + + message + message + ( const QString & message, int timeout = 0 ) + + + + QStatusTipEvent + qstatustipevent.html + + QStatusTipEvent + QStatusTipEvent + ( const QString & tip ) + + + tip + tip + () + + + + QString::Null + qstring-null.html + + ascii + ascii + () + + + constref + constref + ( uint i ) + + + contains + contains-2 + ( QChar c, bool cs ) + + + contains + contains-3 + ( const QString & s, bool cs ) + + + copy + copy + () + + + endsWith + endsWith-2 + ( const QString & s, bool cs ) + + + find + find + ( QChar c, int i = 0, bool cs = true ) + + + find + find-2 + ( const QString & s, int i = 0, bool cs = true ) + + + find + find-3 + ( const QRegExp & rx, int i = 0 ) + + + findRev + findRev + ( QChar c, int i = -1, bool cs = true ) + + + findRev + findRev-2 + ( const QString & s, int i = -1, bool cs = true ) + + + findRev + findRev-3 + ( const QRegExp & rx, int i = -1 ) + + + fromUcs2 + fromUcs2 + ( const ushort * unicode, int size = -1 ) + + + latin1 + latin1 + () + + + leftJustify + leftJustify + ( int width, QChar fill = QLatin1Char( ' ' ) + + + local8Bit + local8Bit + () + + + lower + lower + () + + + ref + ref + ( uint i ) + + + remove + remove-2 + ( QChar c, bool cs ) + + + remove + remove-3 + ( const QString & s, bool cs ) + + + replace + replace-2 + ( QChar c, const QString & after, bool cs ) + + + replace + replace-3 + ( const QString & before, const QString & after, bool cs ) + + + replace + replace-4 + ( char c, const QString & after, bool cs ) + + + replace + replace-5 + ( char c, const QString & after, Qt::CaseSensitivity cs ) + + + rightJustify + rightJustify + ( int width, QChar fill = QLatin1Char( ' ' ) + + + setAscii + setAscii + ( const char * str, int len = -1 ) + + + setLatin1 + setLatin1 + ( const char * str, int len = -1 ) + + + setLength + setLength + ( int nl ) + + + setUnicodeCodes + setUnicodeCodes + ( const ushort * unicode_as_ushorts, int size ) + + + simplifyWhiteSpace + simplifyWhiteSpace + () + + + startsWith + startsWith-2 + ( const QString & s, bool cs ) + + + stripWhiteSpace + stripWhiteSpace + () + + + ucs2 + ucs2 + () + + + upper + upper + () + + + utf8 + utf8 + () + + + operator + operator-const-char--2a + const char *() + + + + QString + qstring.html + + DataPtr + DataPtr-typedef + + + + NormalizationForm + NormalizationForm-enum + + + + SplitBehavior + SplitBehavior-enum + + + + QString + QString + () + + + QString + QString-2 + ( const QChar * unicode, int size ) + + + QString + QString-3 + ( QChar ch ) + + + QString + QString-4 + ( int size, QChar ch ) + + + QString + QString-5 + ( const QLatin1String & str ) + + + QString + QString-6 + ( const QString & other ) + + + QString + QString-7 + ( const char * str ) + + + QString + QString-8 + ( const QByteArray & ba ) + + + append + append + ( const QString & str ) + + + append + append-2 + ( const QLatin1String & str ) + + + append + append-3 + ( const QByteArray & ba ) + + + append + append-4 + ( const char * str ) + + + append + append-5 + ( QChar ch ) + + + arg + arg + ( const QString & a, int fieldWidth = 0, const QChar & fillChar = QLatin1Char( ' ' ) + + + arg + arg-2 + ( const QString & a1, const QString & a2 ) + + + arg + arg-3 + ( const QString & a1, const QString & a2, const QString & a3 ) + + + arg + arg-4 + ( const QString & a1, const QString & a2, const QString & a3, const QString & a4 ) + + + arg + arg-5 + ( const QString & a1, const QString & a2, const QString & a3, const QString & a4, const QString & a5 ) + + + arg + arg-6 + ( const QString & a1, const QString & a2, const QString & a3, const QString & a4, const QString & a5, const QString & a6 ) + + + arg + arg-7 + ( const QString & a1, const QString & a2, const QString & a3, const QString & a4, const QString & a5, const QString & a6, const QString & a7 ) + + + arg + arg-8 + ( const QString & a1, const QString & a2, const QString & a3, const QString & a4, const QString & a5, const QString & a6, const QString & a7, const QString & a8 ) + + + arg + arg-9 + ( const QString & a1, const QString & a2, const QString & a3, const QString & a4, const QString & a5, const QString & a6, const QString & a7, const QString & a8, const QString & a9 ) + + + arg + arg-10 + ( int a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' ) + + + arg + arg-11 + ( uint a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' ) + + + arg + arg-12 + ( long a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' ) + + + arg + arg-13 + ( ulong a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' ) + + + arg + arg-14 + ( qlonglong a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' ) + + + arg + arg-15 + ( qulonglong a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' ) + + + arg + arg-16 + ( short a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' ) + + + arg + arg-17 + ( ushort a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' ) + + + arg + arg-18 + ( QChar a, int fieldWidth = 0, const QChar & fillChar = QLatin1Char( ' ' ) + + + arg + arg-19 + ( char a, int fieldWidth = 0, const QChar & fillChar = QLatin1Char( ' ' ) + + + arg + arg-20 + ( double a, int fieldWidth = 0, char format = 'g', int precision = -1, const QChar & fillChar = QLatin1Char( ' ' ) + + + at + at + ( int position ) + + + capacity + capacity + () + + + chop + chop + ( int n ) + + + clear + clear + () + + + compare + compare + ( const QString & s1, const QString & s2, Qt::CaseSensitivity cs ) + + + compare + compare-2 + ( const QString & s1, const QString & s2 ) + + + compare + compare-3 + ( const QString & s1, const QLatin1String & s2, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + compare + compare-4 + ( const QLatin1String & s1, const QString & s2, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + compare + compare-5 + ( const QString & other ) + + + compare + compare-6 + ( const QString & other, Qt::CaseSensitivity cs ) + + + compare + compare-7 + ( const QLatin1String & other, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + constData + constData + () + + + contains + contains + ( const QString & str, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + contains + contains-4 + ( QChar ch, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + contains + contains-5 + ( const QRegExp & rx ) + + + count + count + ( const QString & str, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + count + count-2 + ( QChar ch, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + count + count-3 + ( const QRegExp & rx ) + + + count + count-4 + () + + + data + data + () + + + data + data-2 + () + + + data_ptr + data_ptr + () + + + endsWith + endsWith + ( const QString & s, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + endsWith + endsWith-3 + ( const QLatin1String & s, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + endsWith + endsWith-4 + ( const QChar & c, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + fill + fill + ( QChar ch, int size = -1 ) + + + fromAscii + fromAscii + ( const char * str, int size = -1 ) + + + fromLatin1 + fromLatin1 + ( const char * str, int size = -1 ) + + + fromLocal8Bit + fromLocal8Bit + ( const char * str, int size = -1 ) + + + fromRawData + fromRawData + ( const QChar * unicode, int size ) + + + fromStdString + fromStdString + ( const std::string & str ) + + + fromStdWString + fromStdWString + ( const std::wstring & str ) + + + fromUcs4 + fromUcs4 + ( const uint * unicode, int size = -1 ) + + + fromUtf8 + fromUtf8 + ( const char * str, int size = -1 ) + + + fromUtf16 + fromUtf16 + ( const ushort * unicode, int size = -1 ) + + + fromWCharArray + fromWCharArray + ( const wchar_t * string, int size = -1 ) + + + indexOf + indexOf + ( const QString & str, int from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + indexOf + indexOf-2 + ( QChar ch, int from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + indexOf + indexOf-3 + ( const QRegExp & rx, int from = 0 ) + + + insert + insert + ( int position, const QString & str ) + + + insert + insert-2 + ( int position, const QLatin1String & str ) + + + insert + insert-3 + ( int position, const QChar * unicode, int size ) + + + insert + insert-4 + ( int position, QChar ch ) + + + isEmpty + isEmpty + () + + + isNull + isNull + () + + + lastIndexOf + lastIndexOf + ( const QString & str, int from = -1, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + lastIndexOf + lastIndexOf-2 + ( QChar ch, int from = -1, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + lastIndexOf + lastIndexOf-3 + ( const QRegExp & rx, int from = -1 ) + + + left + left + ( int n ) + + + leftJustified + leftJustified + ( int width, QChar fill = QLatin1Char( ' ' ) + + + length + length + () + + + localeAwareCompare + localeAwareCompare + ( const QString & s1, const QString & s2 ) + + + localeAwareCompare + localeAwareCompare-2 + ( const QString & other ) + + + mid + mid + ( int position, int n = -1 ) + + + normalized + normalized + ( NormalizationForm mode ) + + + normalized + normalized-2 + ( NormalizationForm mode, QChar::UnicodeVersion version ) + + + number + number + ( long n, int base = 10 ) + + + number + number-2 + ( ulong n, int base = 10 ) + + + number + number-3 + ( int n, int base = 10 ) + + + number + number-4 + ( uint n, int base = 10 ) + + + number + number-5 + ( qlonglong n, int base = 10 ) + + + number + number-6 + ( qulonglong n, int base = 10 ) + + + number + number-7 + ( double n, char format = 'g', int precision = 6 ) + + + prepend + prepend + ( const QString & str ) + + + prepend + prepend-2 + ( const QLatin1String & str ) + + + prepend + prepend-3 + ( const QByteArray & ba ) + + + prepend + prepend-4 + ( const char * str ) + + + prepend + prepend-5 + ( QChar ch ) + + + push_back + push_back + ( const QString & other ) + + + push_back + push_back-2 + ( QChar ch ) + + + push_front + push_front + ( const QString & other ) + + + push_front + push_front-2 + ( QChar ch ) + + + remove + remove + ( int position, int n ) + + + remove + remove-4 + ( const QString & str, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + remove + remove-5 + ( QChar ch, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + remove + remove-6 + ( const QRegExp & rx ) + + + replace + replace + ( int position, int n, const QString & after ) + + + replace + replace-6 + ( int position, int n, const QChar * unicode, int size ) + + + replace + replace-7 + ( int position, int n, QChar after ) + + + replace + replace-8 + ( const QString & before, const QString & after, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + replace + replace-9 + ( QChar ch, const QString & after, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + replace + replace-10 + ( QChar before, QChar after, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + replace + replace-11 + ( const QRegExp & rx, const QString & after ) + + + reserve + reserve + ( int size ) + + + resize + resize + ( int size ) + + + right + right + ( int n ) + + + rightJustified + rightJustified + ( int width, QChar fill = QLatin1Char( ' ' ) + + + section + section + ( QChar sep, int start, int end = -1, SectionFlags flags = SectionDefault ) + + + section + section-2 + ( const QString & sep, int start, int end = -1, SectionFlags flags = SectionDefault ) + + + section + section-3 + ( const QRegExp & reg, int start, int end = -1, SectionFlags flags = SectionDefault ) + + + setNum + setNum + ( int n, int base = 10 ) + + + setNum + setNum-2 + ( uint n, int base = 10 ) + + + setNum + setNum-3 + ( long n, int base = 10 ) + + + setNum + setNum-4 + ( ulong n, int base = 10 ) + + + setNum + setNum-5 + ( qlonglong n, int base = 10 ) + + + setNum + setNum-6 + ( qulonglong n, int base = 10 ) + + + setNum + setNum-7 + ( short n, int base = 10 ) + + + setNum + setNum-8 + ( ushort n, int base = 10 ) + + + setNum + setNum-9 + ( double n, char format = 'g', int precision = 6 ) + + + setNum + setNum-10 + ( float n, char format = 'g', int precision = 6 ) + + + setUnicode + setUnicode + ( const QChar * unicode, int size ) + + + setUtf16 + setUtf16 + ( const ushort * unicode, int size ) + + + simplified + simplified + () + + + size + size + () + + + split + split + ( const QString & sep, SplitBehavior behavior = KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + split + split-2 + ( const QChar & sep, SplitBehavior behavior = KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + split + split-3 + ( const QRegExp & rx, SplitBehavior behavior = KeepEmptyParts ) + + + sprintf + sprintf + ( const char * cformat, ... ) + + + squeeze + squeeze + () + + + startsWith + startsWith + ( const QString & s, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + startsWith + startsWith-3 + ( const QLatin1String & s, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + startsWith + startsWith-4 + ( const QChar & c, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + toAscii + toAscii + () + + + toCaseFolded + toCaseFolded + () + + + toDouble + toDouble + ( bool * ok = 0 ) + + + toFloat + toFloat + ( bool * ok = 0 ) + + + toInt + toInt + ( bool * ok = 0, int base = 10 ) + + + toLatin1 + toLatin1 + () + + + toLocal8Bit + toLocal8Bit + () + + + toLong + toLong + ( bool * ok = 0, int base = 10 ) + + + toLongLong + toLongLong + ( bool * ok = 0, int base = 10 ) + + + toLower + toLower + () + + + toShort + toShort + ( bool * ok = 0, int base = 10 ) + + + string + toStdString + QString::toStdString() + + + wstring + toStdWString + QString::toStdWString() + + + toUInt + toUInt + ( bool * ok = 0, int base = 10 ) + + + toULong + toULong + ( bool * ok = 0, int base = 10 ) + + + toULongLong + toULongLong + ( bool * ok = 0, int base = 10 ) + + + toUShort + toUShort + ( bool * ok = 0, int base = 10 ) + + + toUcs4 + toUcs4 + () + + + toUpper + toUpper + () + + + toUtf8 + toUtf8 + () + + + toWCharArray + toWCharArray + ( wchar_t * array ) + + + trimmed + trimmed + () + + + truncate + truncate + ( int position ) + + + unicode + unicode + () + + + utf16 + utf16 + () + + + vsprintf + vsprintf + ( const char * cformat, va_list ap ) + + + operator!= + operator-not-eq + ( const QString & other ) + + + operator!= + operator-not-eq-2 + ( const QLatin1String & other ) + + + operator!= + operator-not-eq-3 + ( const QByteArray & other ) + + + operator!= + operator-not-eq-4 + ( const char * other ) + + + operator+= + operator-2b-eq + ( const QString & other ) + + + operator+= + operator-2b-eq-2 + ( const QLatin1String & str ) + + + operator+= + operator-2b-eq-3 + ( const QByteArray & ba ) + + + operator+= + operator-2b-eq-4 + ( const char * str ) + + + operator+= + operator-2b-eq-5 + ( char ch ) + + + operator+= + operator-2b-eq-6 + ( QChar ch ) + + + operator< + operator-lt + ( const QString & other ) + + + operator< + operator-lt-2 + ( const QLatin1String & other ) + + + operator< + operator-lt-3 + ( const QByteArray & other ) + + + operator< + operator-lt-4 + ( const char * other ) + + + operator<= + operator-lt-eq + ( const QString & other ) + + + operator<= + operator-lt-eq-2 + ( const QLatin1String & other ) + + + operator<= + operator-lt-eq-3 + ( const QByteArray & other ) + + + operator<= + operator-lt-eq-4 + ( const char * other ) + + + operator= + operator-eq + ( const QString & other ) + + + operator= + operator-eq-3 + ( const QLatin1String & str ) + + + operator= + operator-eq-4 + ( const QByteArray & ba ) + + + operator= + operator-eq-5 + ( const char * str ) + + + operator= + operator-eq-6 + ( char ch ) + + + operator= + operator-eq-7 + ( QChar ch ) + + + operator== + operator-eq-eq + ( const QString & other ) + + + operator== + operator-eq-eq-2 + ( const QLatin1String & other ) + + + operator== + operator-eq-eq-3 + ( const QByteArray & other ) + + + operator== + operator-eq-eq-4 + ( const char * other ) + + + operator> + operator-gt + ( const QString & other ) + + + operator> + operator-gt-2 + ( const QLatin1String & other ) + + + operator> + operator-gt-3 + ( const QByteArray & other ) + + + operator> + operator-gt-4 + ( const char * other ) + + + operator>= + operator-gt-eq + ( const QString & other ) + + + operator>= + operator-gt-eq-2 + ( const QLatin1String & other ) + + + operator>= + operator-gt-eq-3 + ( const QByteArray & other ) + + + operator>= + operator-gt-eq-4 + ( const char * other ) + + + operator[] + operator-5b-5d + ( int position ) + + + operator[] + operator-5b-5d-2 + ( int position ) + + + operator[] + operator-5b-5d-3 + ( uint position ) + + + operator[] + operator-5b-5d-4 + ( uint position ) + + + ascii + ascii + () + + + constref + constref + ( uint i ) + + + contains + contains-2 + ( QChar c, bool cs ) + + + contains + contains-3 + ( const QString & s, bool cs ) + + + copy + copy + () + + + endsWith + endsWith-2 + ( const QString & s, bool cs ) + + + find + find + ( QChar c, int i = 0, bool cs = true ) + + + find + find-2 + ( const QString & s, int i = 0, bool cs = true ) + + + find + find-3 + ( const QRegExp & rx, int i = 0 ) + + + findRev + findRev + ( QChar c, int i = -1, bool cs = true ) + + + findRev + findRev-2 + ( const QString & s, int i = -1, bool cs = true ) + + + findRev + findRev-3 + ( const QRegExp & rx, int i = -1 ) + + + fromUcs2 + fromUcs2 + ( const ushort * unicode, int size = -1 ) + + + latin1 + latin1 + () + + + leftJustify + leftJustify + ( int width, QChar fill = QLatin1Char( ' ' ) + + + local8Bit + local8Bit + () + + + lower + lower + () + + + ref + ref + ( uint i ) + + + remove + remove-2 + ( QChar c, bool cs ) + + + remove + remove-3 + ( const QString & s, bool cs ) + + + replace + replace-2 + ( QChar c, const QString & after, bool cs ) + + + replace + replace-3 + ( const QString & before, const QString & after, bool cs ) + + + replace + replace-4 + ( char c, const QString & after, bool cs ) + + + replace + replace-5 + ( char c, const QString & after, Qt::CaseSensitivity cs ) + + + rightJustify + rightJustify + ( int width, QChar fill = QLatin1Char( ' ' ) + + + setAscii + setAscii + ( const char * str, int len = -1 ) + + + setLatin1 + setLatin1 + ( const char * str, int len = -1 ) + + + setLength + setLength + ( int nl ) + + + setUnicodeCodes + setUnicodeCodes + ( const ushort * unicode_as_ushorts, int size ) + + + simplifyWhiteSpace + simplifyWhiteSpace + () + + + startsWith + startsWith-2 + ( const QString & s, bool cs ) + + + stripWhiteSpace + stripWhiteSpace + () + + + ucs2 + ucs2 + () + + + upper + upper + () + + + utf8 + utf8 + () + + + operator + operator-const-char--2a + const char *() + + + fromLast + fromLast + () + + + fromLast + fromLast-2 + () + + + grep + grep + ( const QString & str, bool cs = true ) + + + grep + grep-2 + ( const QRegExp & rx ) + + + gres + gres + ( const QRegExp & rx, const QString & after ) + + + gres + gres-2 + ( const QString & before, const QString & after, bool cs = true ) + + + split + split + ( const QRegExp & sep, const QString & str, bool allowEmptyEntries = false ) + + + split + split-2 + ( const QChar & sep, const QString & str, bool allowEmptyEntries = false ) + + + split + split-3 + ( const QString & sep, const QString & str, bool allowEmptyEntries = false ) + + + + QStringList + qstringlist.html + + QStringList + QStringList + () + + + QStringList + QStringList-2 + ( const QString & str ) + + + QStringList + QStringList-3 + ( const QStringList & other ) + + + QStringList + QStringList-4 + ( const QList<QString> & other ) + + + contains + contains + ( const QString & str, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + filter + filter + ( const QString & str, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + filter + filter-2 + ( const QRegExp & rx ) + + + indexOf + indexOf + ( const QRegExp & rx, int from = 0 ) + + + join + join + ( const QString & separator ) + + + lastIndexOf + lastIndexOf + ( const QRegExp & rx, int from = -1 ) + + + replaceInStrings + replaceInStrings + ( const QString & before, const QString & after, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + replaceInStrings + replaceInStrings-2 + ( const QRegExp & rx, const QString & after ) + + + sort + sort + () + + + operator+ + operator-2b + ( const QStringList & other ) + + + operator<< + operator-lt-lt + ( const QString & str ) + + + operator<< + operator-lt-lt-2 + ( const QStringList & other ) + + + fromLast + fromLast + () + + + fromLast + fromLast-2 + () + + + grep + grep + ( const QString & str, bool cs = true ) + + + grep + grep-2 + ( const QRegExp & rx ) + + + gres + gres + ( const QRegExp & rx, const QString & after ) + + + gres + gres-2 + ( const QString & before, const QString & after, bool cs = true ) + + + split + split + ( const QRegExp & sep, const QString & str, bool allowEmptyEntries = false ) + + + split + split-2 + ( const QChar & sep, const QString & str, bool allowEmptyEntries = false ) + + + split + split-3 + ( const QString & sep, const QString & str, bool allowEmptyEntries = false ) + + + + QStringListModel + qstringlistmodel.html + + QStringListModel + QStringListModel + ( QObject * parent = 0 ) + + + QStringListModel + QStringListModel-2 + ( const QStringList & strings, QObject * parent = 0 ) + + + data + data + ( const QModelIndex & index, int role ) + + + ItemFlags + flags + QStringListModel::flags( const QModelIndex & index ) + + + insertRows + insertRows + ( int row, int count, const QModelIndex & parent = QModelIndex() + + + removeRows + removeRows + ( int row, int count, const QModelIndex & parent = QModelIndex() + + + rowCount + rowCount + ( const QModelIndex & parent = QModelIndex() + + + setData + setData + ( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ) + + + setStringList + setStringList + ( const QStringList & strings ) + + + stringList + stringList + () + + + + QStringMatcher + qstringmatcher.html + + QStringMatcher + QStringMatcher + () + + + QStringMatcher + QStringMatcher-2 + ( const QString & pattern, Qt::CaseSensitivity cs = Qt::CaseSensitive ) + + + QStringMatcher + QStringMatcher-3 + ( const QStringMatcher & other ) + + + CaseSensitivity + caseSensitivity + QStringMatcher::caseSensitivity() + + + indexIn + indexIn + ( const QString & str, int from = 0 ) + + + pattern + pattern + () + + + setCaseSensitivity + setCaseSensitivity + ( Qt::CaseSensitivity cs ) + + + setPattern + setPattern + ( const QString & pattern ) + + + operator= + operator-eq + ( const QStringMatcher & other ) + + + + QStringRef + qstringref.html + + QStringRef + QStringRef + () + + + QStringRef + QStringRef-2 + ( const QString * string, int position, int length ) + + + QStringRef + QStringRef-3 + ( const QString * string ) + + + QStringRef + QStringRef-4 + ( const QStringRef & other ) + + + appendTo + appendTo + ( QString * string ) + + + at + at + ( int position ) + + + clear + clear + () + + + constData + constData + () + + + count + count + () + + + data + data + () + + + isEmpty + isEmpty + () + + + isNull + isNull + () + + + length + length + () + + + position + position + () + + + size + size + () + + + string + string + () + + + toString + toString + () + + + unicode + unicode + () + + + operator= + operator-eq + ( const QStringRef & other ) + + + operator= + operator-eq-2 + ( const QString * string ) + + + standardPixmap + standardPixmap + ( StandardPixmap standardPixmap, const QStyleOption * option = 0, const QWidget * widget = 0 ) + + + + QStyle + qstyle.html + + ComplexControl + ComplexControl-enum + + + + ContentsType + ContentsType-enum + + + + ControlElement + ControlElement-enum + + + + PixelMetric + PixelMetric-enum + + + + PrimitiveElement + PrimitiveElement-enum + + + + StandardPixmap + StandardPixmap-enum + + + + StyleHint + StyleHint-enum + + + + SubElement + SubElement-enum + + + + QStyle + QStyle + () + + + alignedRect + alignedRect + ( Qt::LayoutDirection direction, Qt::Alignment alignment, const QSize & size, const QRect & rectangle ) + + + combinedLayoutSpacing + combinedLayoutSpacing + ( QSizePolicy::ControlTypes controls1, QSizePolicy::ControlTypes controls2, Qt::Orientation orientation, QStyleOption * option = 0, QWidget * widget = 0 ) + + + drawComplexControl + drawComplexControl + ( ComplexControl control, const QStyleOptionComplex * option, QPainter * painter, const QWidget * widget = 0 ) + + + drawControl + drawControl + ( ControlElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget = 0 ) + + + drawItemPixmap + drawItemPixmap + ( QPainter * painter, const QRect & rectangle, int alignment, const QPixmap & pixmap ) + + + drawItemText + drawItemText + ( QPainter * painter, const QRect & rectangle, int alignment, const QPalette & palette, bool enabled, const QString & text, QPalette::ColorRole textRole = QPalette::NoRole ) + + + drawPrimitive + drawPrimitive + ( PrimitiveElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget = 0 ) + + + generatedIconPixmap + generatedIconPixmap + ( QIcon::Mode iconMode, const QPixmap & pixmap, const QStyleOption * option ) + + + hitTestComplexControl + hitTestComplexControl + ( ComplexControl control, const QStyleOptionComplex * option, const QPoint & position, const QWidget * widget = 0 ) + + + itemPixmapRect + itemPixmapRect + ( const QRect & rectangle, int alignment, const QPixmap & pixmap ) + + + itemTextRect + itemTextRect + ( const QFontMetrics & metrics, const QRect & rectangle, int alignment, bool enabled, const QString & text ) + + + layoutSpacing + layoutSpacing + ( QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, Qt::Orientation orientation, const QStyleOption * option = 0, const QWidget * widget = 0 ) + + + layoutSpacingImplementation + layoutSpacingImplementation + ( QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, Qt::Orientation orientation, const QStyleOption * option = 0, const QWidget * widget = 0 ) + + + pixelMetric + pixelMetric + ( PixelMetric metric, const QStyleOption * option = 0, const QWidget * widget = 0 ) + + + polish + polish + ( QWidget * widget ) + + + polish + polish-2 + ( QApplication * application ) + + + polish + polish-3 + ( QPalette & palette ) + + + sizeFromContents + sizeFromContents + ( ContentsType type, const QStyleOption * option, const QSize & contentsSize, const QWidget * widget = 0 ) + + + sliderPositionFromValue + sliderPositionFromValue + ( int min, int max, int logicalValue, int span, bool upsideDown = false ) + + + sliderValueFromPosition + sliderValueFromPosition + ( int min, int max, int position, int span, bool upsideDown = false ) + + + standardIcon + standardIcon + ( StandardPixmap standardIcon, const QStyleOption * option = 0, const QWidget * widget = 0 ) + + + standardIconImplementation + standardIconImplementation + ( StandardPixmap standardIcon, const QStyleOption * option = 0, const QWidget * widget = 0 ) + + + standardPalette + standardPalette + () + + + styleHint + styleHint + ( StyleHint hint, const QStyleOption * option = 0, const QWidget * widget = 0, QStyleHintReturn * returnData = 0 ) + + + subControlRect + subControlRect + ( ComplexControl control, const QStyleOptionComplex * option, SubControl subControl, const QWidget * widget = 0 ) + + + subElementRect + subElementRect + ( SubElement element, const QStyleOption * option, const QWidget * widget = 0 ) + + + unpolish + unpolish + ( QWidget * widget ) + + + unpolish + unpolish-2 + ( QApplication * application ) + + + Alignment + visualAlignment + QStyle::visualAlignment( Qt::LayoutDirection direction, Qt::Alignment alignment ) + + + visualPos + visualPos + ( Qt::LayoutDirection direction, const QRect & boundingRectangle, const QPoint & logicalPosition ) + + + visualRect + visualRect + ( Qt::LayoutDirection direction, const QRect & boundingRectangle, const QRect & logicalRectangle ) + + + + QStyleFactory + qstylefactory.html + + create + create + ( const QString & key ) + + + keys + keys + () + + + + QStyleHintReturn + qstylehintreturn.html + + HintReturnType + HintReturnType-enum + + + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleHintReturn + QStyleHintReturn + ( int version = QStyleOption::Version, int type = SH_Default ) + + + type + type-varx + + + + version + version-var + + + + + QStyleHintReturnMask + qstylehintreturnmask.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleHintReturnMask + QStyleHintReturnMask + () + + + region + region-var + + + + + QStyleHintReturnVariant + qstylehintreturnvariant.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleHintReturnVariant + QStyleHintReturnVariant + () + + + variant + variant-var + + + + init + init + ( const QWidget * widget ) + + + + QStyleOption + qstyleoption.html + + OptionType + OptionType-enum + + + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOption + QStyleOption + ( int version = QStyleOption::Version, int type = SO_Default ) + + + QStyleOption + QStyleOption-2 + ( const QStyleOption & other ) + + + initFrom + initFrom + ( const QWidget * widget ) + + + operator= + operator-eq + ( const QStyleOption & other ) + + + LayoutDirection + direction-var + QStyleOption::direction + + + fontMetrics + fontMetrics-var + + + + palette + palette-var + + + + rect + rect-var + + + + State + state-var + QStyleOption::state + + + type + type-varx + + + + version + version-var + + + + + QStyleOptionButton + qstyleoptionbutton.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionButton + QStyleOptionButton + () + + + QStyleOptionButton + QStyleOptionButton-2 + ( const QStyleOptionButton & other ) + + + features + features-var + + + + icon + icon-var + + + + iconSize + iconSize-var + + + + text + text-var + + + + + QStyleOptionComboBox + qstyleoptioncombobox.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionComboBox + QStyleOptionComboBox + () + + + QStyleOptionComboBox + QStyleOptionComboBox-2 + ( const QStyleOptionComboBox & other ) + + + currentIcon + currentIcon-var + + + + currentText + currentText-var + + + + editable + editable-var + + + + frame + frame-var + + + + iconSize + iconSize-var + + + + popupRect + popupRect-var + + + + + QStyleOptionComplex + qstyleoptioncomplex.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionComplex + QStyleOptionComplex + ( int version = QStyleOptionComplex::Version, int type = SO_Complex ) + + + QStyleOptionComplex + QStyleOptionComplex-2 + ( const QStyleOptionComplex & other ) + + + SubControls + activeSubControls-var + QStyleOptionComplex::activeSubControls + + + SubControls + subControls-var + QStyleOptionComplex::subControls + + + + QStyleOptionDockWidget + qstyleoptiondockwidget.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionDockWidget + QStyleOptionDockWidget + () + + + QStyleOptionDockWidget + QStyleOptionDockWidget-2 + ( const QStyleOptionDockWidget & other ) + + + closable + closable-var + + + + floatable + floatable-var + + + + movable + movable-var + + + + title + title-var + + + + + QStyleOptionFocusRect + qstyleoptionfocusrect.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionFocusRect + QStyleOptionFocusRect + () + + + QStyleOptionFocusRect + QStyleOptionFocusRect-2 + ( const QStyleOptionFocusRect & other ) + + + backgroundColor + backgroundColor-var + + + + + QStyleOptionFrame + qstyleoptionframe.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionFrame + QStyleOptionFrame + () + + + QStyleOptionFrame + QStyleOptionFrame-2 + ( const QStyleOptionFrame & other ) + + + lineWidth + lineWidth-var + + + + midLineWidth + midLineWidth-var + + + + + QStyleOptionFrameV2 + qstyleoptionframev2.html + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionFrameV2 + QStyleOptionFrameV2 + () + + + QStyleOptionFrameV2 + QStyleOptionFrameV2-2 + ( const QStyleOptionFrameV2 & other ) + + + QStyleOptionFrameV2 + QStyleOptionFrameV2-3 + ( const QStyleOptionFrame & other ) + + + operator= + operator-eq + ( const QStyleOptionFrame & other ) + + + + QStyleOptionGraphicsItem + qstyleoptiongraphicsitem.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionGraphicsItem + QStyleOptionGraphicsItem + () + + + QStyleOptionGraphicsItem + QStyleOptionGraphicsItem-2 + ( const QStyleOptionGraphicsItem & other ) + + + exposedRect + exposedRect-var + + + + levelOfDetail + levelOfDetail-var + + + + matrix + matrix-var + + + + + QStyleOptionGroupBox + qstyleoptiongroupbox.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionGroupBox + QStyleOptionGroupBox + () + + + QStyleOptionGroupBox + QStyleOptionGroupBox-2 + ( const QStyleOptionGroupBox & other ) + + + FrameFeatures + features-var + QStyleOptionGroupBox::features + + + lineWidth + lineWidth-var + + + + midLineWidth + midLineWidth-var + + + + text + text-var + + + + Alignment + textAlignment-var + QStyleOptionGroupBox::textAlignment + + + textColor + textColor-var + + + + + QStyleOptionHeader + qstyleoptionheader.html + + SectionPosition + SectionPosition-enum + + + + SelectedPosition + SelectedPosition-enum + + + + SortIndicator + SortIndicator-enum + + + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionHeader + QStyleOptionHeader + () + + + QStyleOptionHeader + QStyleOptionHeader-2 + ( const QStyleOptionHeader & other ) + + + icon + icon-var + + + + Alignment + iconAlignment-var + QStyleOptionHeader::iconAlignment + + + Orientation + orientation-var + QStyleOptionHeader::orientation + + + position + position-var + + + + section + section-var + + + + selectedPosition + selectedPosition-var + + + + sortIndicator + sortIndicator-var + + + + text + text-var + + + + Alignment + textAlignment-var + QStyleOptionHeader::textAlignment + + + + QStyleOptionMenuItem + qstyleoptionmenuitem.html + + CheckType + CheckType-enum + + + + MenuItemType + MenuItemType-enum + + + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionMenuItem + QStyleOptionMenuItem + () + + + QStyleOptionMenuItem + QStyleOptionMenuItem-2 + ( const QStyleOptionMenuItem & other ) + + + checkType + checkType-var + + + + checked + checked-var + + + + font + font-var + + + + icon + icon-var + + + + maxIconWidth + maxIconWidth-var + + + + menuHasCheckableItems + menuHasCheckableItems-var + + + + menuItemType + menuItemType-var + + + + menuRect + menuRect-var + + + + tabWidth + tabWidth-var + + + + text + text-var + + + + + QStyleOptionProgressBar + qstyleoptionprogressbar.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionProgressBar + QStyleOptionProgressBar + () + + + QStyleOptionProgressBar + QStyleOptionProgressBar-2 + ( const QStyleOptionProgressBar & other ) + + + maximum + maximum-var + + + + minimum + minimum-var + + + + progress + progress-var + + + + text + text-var + + + + Alignment + textAlignment-var + QStyleOptionProgressBar::textAlignment + + + textVisible + textVisible-var + + + + + QStyleOptionProgressBarV2 + qstyleoptionprogressbarv2.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionProgressBarV2 + QStyleOptionProgressBarV2 + () + + + QStyleOptionProgressBarV2 + QStyleOptionProgressBarV2-2 + ( const QStyleOptionProgressBar & other ) + + + QStyleOptionProgressBarV2 + QStyleOptionProgressBarV2-3 + ( const QStyleOptionProgressBarV2 & other ) + + + operator= + operator-eq + ( const QStyleOptionProgressBar & other ) + + + bottomToTop + bottomToTop-var + + + + invertedAppearance + invertedAppearance-var + + + + Orientation + orientation-var + QStyleOptionProgressBarV2::orientation + + + + QStyleOptionQ3DockWindow + qstyleoptionq3dockwindow.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionQ3DockWindow + QStyleOptionQ3DockWindow + () + + + QStyleOptionQ3DockWindow + QStyleOptionQ3DockWindow-2 + ( const QStyleOptionQ3DockWindow & other ) + + + closeEnabled + closeEnabled-var + + + + docked + docked-var + + + + + QStyleOptionQ3ListView + qstyleoptionq3listview.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionQ3ListView + QStyleOptionQ3ListView + () + + + QStyleOptionQ3ListView + QStyleOptionQ3ListView-2 + ( const QStyleOptionQ3ListView & other ) + + + itemMargin + itemMargin-var + + + + items + items-var + + + + rootIsDecorated + rootIsDecorated-var + + + + sortColumn + sortColumn-var + + + + treeStepSize + treeStepSize-var + + + + ColorRole + viewportBGRole-var + QStyleOptionQ3ListView::viewportBGRole + + + viewportPalette + viewportPalette-var + + + + + QStyleOptionQ3ListViewItem + qstyleoptionq3listviewitem.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionQ3ListViewItem + QStyleOptionQ3ListViewItem + () + + + QStyleOptionQ3ListViewItem + QStyleOptionQ3ListViewItem-2 + ( const QStyleOptionQ3ListViewItem & other ) + + + childCount + childCount-var + + + + features + features-var + + + + height + height-var + + + + itemY + itemY-var + + + + totalHeight + totalHeight-var + + + + + QStyleOptionRubberBand + qstyleoptionrubberband.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionRubberBand + QStyleOptionRubberBand + () + + + QStyleOptionRubberBand + QStyleOptionRubberBand-2 + ( const QStyleOptionRubberBand & other ) + + + opaque + opaque-var + + + + Shape + shape-var + QStyleOptionRubberBand::shape + + + + QStyleOptionSizeGrip + qstyleoptionsizegrip.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionSizeGrip + QStyleOptionSizeGrip + () + + + QStyleOptionSizeGrip + QStyleOptionSizeGrip-2 + ( const QStyleOptionSizeGrip & other ) + + + Corner + corner-var + QStyleOptionSizeGrip::corner + + + + QStyleOptionSlider + qstyleoptionslider.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionSlider + QStyleOptionSlider + () + + + QStyleOptionSlider + QStyleOptionSlider-2 + ( const QStyleOptionSlider & other ) + + + dialWrapping + dialWrapping-var + + + + maximum + maximum-var + + + + minimum + minimum-var + + + + notchTarget + notchTarget-var + + + + Orientation + orientation-var + QStyleOptionSlider::orientation + + + pageStep + pageStep-var + + + + singleStep + singleStep-var + + + + sliderPosition + sliderPosition-var + + + + sliderValue + sliderValue-var + + + + tickInterval + tickInterval-var + + + + TickPosition + tickPosition-var + QStyleOptionSlider::tickPosition + + + upsideDown + upsideDown-var + + + + + QStyleOptionSpinBox + qstyleoptionspinbox.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionSpinBox + QStyleOptionSpinBox + () + + + QStyleOptionSpinBox + QStyleOptionSpinBox-2 + ( const QStyleOptionSpinBox & other ) + + + ButtonSymbols + buttonSymbols-var + QStyleOptionSpinBox::buttonSymbols + + + frame + frame-var + + + + StepEnabled + stepEnabled-var + QStyleOptionSpinBox::stepEnabled + + + + QStyleOptionTab + qstyleoptiontab.html + + SelectedPosition + SelectedPosition-enum + + + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + TabPosition + TabPosition-enum + + + + QStyleOptionTab + QStyleOptionTab + () + + + QStyleOptionTab + QStyleOptionTab-2 + ( const QStyleOptionTab & other ) + + + cornerWidgets + cornerWidgets-var + + + + icon + icon-var + + + + position + position-var + + + + row + row-var + + + + selectedPosition + selectedPosition-var + + + + Shape + shape-var + QStyleOptionTab::shape + + + text + text-var + + + + + QStyleOptionTabBarBase + qstyleoptiontabbarbase.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionTabBarBase + QStyleOptionTabBarBase + () + + + QStyleOptionTabBarBase + QStyleOptionTabBarBase-2 + ( const QStyleOptionTabBarBase & other ) + + + selectedTabRect + selectedTabRect-var + + + + Shape + shape-var + QStyleOptionTabBarBase::shape + + + tabBarRect + tabBarRect-var + + + + + QStyleOptionTabV2 + qstyleoptiontabv2.html + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionTabV2 + QStyleOptionTabV2 + () + + + QStyleOptionTabV2 + QStyleOptionTabV2-2 + ( const QStyleOptionTabV2 & other ) + + + QStyleOptionTabV2 + QStyleOptionTabV2-3 + ( const QStyleOptionTab & other ) + + + operator= + operator-eq + ( const QStyleOptionTab & other ) + + + iconSize + iconSize-var + + + + + QStyleOptionTabWidgetFrame + qstyleoptiontabwidgetframe.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionTabWidgetFrame + QStyleOptionTabWidgetFrame + () + + + QStyleOptionTabWidgetFrame + QStyleOptionTabWidgetFrame-2 + ( const QStyleOptionTabWidgetFrame & other ) + + + leftCornerWidgetSize + leftCornerWidgetSize-var + + + + lineWidth + lineWidth-var + + + + midLineWidth + midLineWidth-var + + + + rightCornerWidgetSize + rightCornerWidgetSize-var + + + + Shape + shape-var + QStyleOptionTabWidgetFrame::shape + + + tabBarSize + tabBarSize-var + + + + + QStyleOptionTitleBar + qstyleoptiontitlebar.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionTitleBar + QStyleOptionTitleBar + () + + + QStyleOptionTitleBar + QStyleOptionTitleBar-2 + ( const QStyleOptionTitleBar & other ) + + + icon + icon-var + + + + text + text-var + + + + WindowFlags + titleBarFlags-var + QStyleOptionTitleBar::titleBarFlags + + + titleBarState + titleBarState-var + + + + + QStyleOptionToolBar + qstyleoptiontoolbar.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + ToolBarPosition + ToolBarPosition-enum + + + + QStyleOptionToolBar + QStyleOptionToolBar + () + + + QStyleOptionToolBar + QStyleOptionToolBar-2 + ( const QStyleOptionToolBar & other ) + + + features + features-var + + + + lineWidth + lineWidth-var + + + + midLineWidth + midLineWidth-var + + + + positionOfLine + positionOfLine-var + + + + positionWithinLine + positionWithinLine-var + + + + ToolBarArea + toolBarArea-var + QStyleOptionToolBar::toolBarArea + + + + QStyleOptionToolBox + qstyleoptiontoolbox.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionToolBox + QStyleOptionToolBox + () + + + QStyleOptionToolBox + QStyleOptionToolBox-2 + ( const QStyleOptionToolBox & other ) + + + icon + icon-var + + + + text + text-var + + + + + QStyleOptionToolBoxV2 + qstyleoptiontoolboxv2.html + + SelectedPosition + SelectedPosition-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + TabPosition + TabPosition-enum + + + + QStyleOptionToolBoxV2 + QStyleOptionToolBoxV2 + () + + + QStyleOptionToolBoxV2 + QStyleOptionToolBoxV2-2 + ( const QStyleOptionToolBoxV2 & other ) + + + QStyleOptionToolBoxV2 + QStyleOptionToolBoxV2-3 + ( const QStyleOptionToolBox & other ) + + + operator= + operator-eq + ( const QStyleOptionToolBox & other ) + + + selectedPosition + selectedPosition-var + + + + + QStyleOptionToolButton + qstyleoptiontoolbutton.html + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionToolButton + QStyleOptionToolButton + () + + + QStyleOptionToolButton + QStyleOptionToolButton-2 + ( const QStyleOptionToolButton & other ) + + + ArrowType + arrowType-var + QStyleOptionToolButton::arrowType + + + features + features-var + + + + font + font-var + + + + icon + icon-var + + + + iconSize + iconSize-var + + + + pos + pos-var + + + + text + text-var + + + + ToolButtonStyle + toolButtonStyle-var + QStyleOptionToolButton::toolButtonStyle + + + + QStyleOptionViewItem + qstyleoptionviewitem.html + + Position + Position-enum + + + + StyleOptionType + StyleOptionType-enum + + + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionViewItem + QStyleOptionViewItem + () + + + QStyleOptionViewItem + QStyleOptionViewItem-2 + ( const QStyleOptionViewItem & other ) + + + Alignment + decorationAlignment-var + QStyleOptionViewItem::decorationAlignment + + + decorationPosition + decorationPosition-var + + + + decorationSize + decorationSize-var + + + + Alignment + displayAlignment-var + QStyleOptionViewItem::displayAlignment + + + font + font-var + + + + showDecorationSelected + showDecorationSelected-var + + + + TextElideMode + textElideMode-var + QStyleOptionViewItem::textElideMode + + + + QStyleOptionViewItemV2 + qstyleoptionviewitemv2.html + + StyleOptionVersion + StyleOptionVersion-enum + + + + QStyleOptionViewItemV2 + QStyleOptionViewItemV2 + () + + + QStyleOptionViewItemV2 + QStyleOptionViewItemV2-2 + ( const QStyleOptionViewItemV2 & other ) + + + QStyleOptionViewItemV2 + QStyleOptionViewItemV2-3 + ( const QStyleOptionViewItem & other ) + + + operator= + operator-eq + ( const QStyleOptionViewItem & other ) + + + features + features-var + + + + + QStylePainter + qstylepainter.html + + QStylePainter + QStylePainter + () + + + QStylePainter + QStylePainter-2 + ( QWidget * widget ) + + + QStylePainter + QStylePainter-3 + ( QPaintDevice * pd, QWidget * widget ) + + + begin + begin + ( QWidget * widget ) + + + begin + begin-2 + ( QPaintDevice * pd, QWidget * widget ) + + + drawComplexControl + drawComplexControl + ( QStyle::ComplexControl cc, const QStyleOptionComplex & option ) + + + drawControl + drawControl + ( QStyle::ControlElement ce, const QStyleOption & option ) + + + drawItemPixmap + drawItemPixmap + ( const QRect & rect, int flags, const QPixmap & pixmap ) + + + drawItemText + drawItemText + ( const QRect & rect, int flags, const QPalette & pal, bool enabled, const QString & text, QPalette::ColorRole textRole = QPalette::NoRole ) + + + drawPrimitive + drawPrimitive + ( QStyle::PrimitiveElement pe, const QStyleOption & option ) + + + style + style + () + + + + QStylePlugin + qstyleplugin.html + + QStylePlugin + QStylePlugin + ( QObject * parent = 0 ) + + + create + create + ( const QString & key ) + + + keys + keys + () + + + + QSvgGenerator + qsvggenerator.html + + QSvgGenerator + QSvgGenerator + () + + + fileName + fileName + () + + + outputDevice + outputDevice + () + + + paintEngine + paintEngine + () + + + resolution + resolution + () + + + setFileName + setFileName + ( const QString & fileName ) + + + setOutputDevice + setOutputDevice + ( QIODevice * outputDevice ) + + + setResolution + setResolution + ( int resolution ) + + + setSize + setSize + ( const QSize & size ) + + + size + size + () + + + + QSvgRenderer + qsvgrenderer.html + + QSvgRenderer + QSvgRenderer + ( QObject * parent = 0 ) + + + QSvgRenderer + QSvgRenderer-2 + ( const QString & filename, QObject * parent = 0 ) + + + QSvgRenderer + QSvgRenderer-3 + ( const QByteArray & contents, QObject * parent = 0 ) + + + animated + animated + () + + + boundsOnElement + boundsOnElement + ( const QString & id ) + + + defaultSize + defaultSize + () + + + elementExists + elementExists + ( const QString & id ) + + + isValid + isValid + () + + + load + load + ( const QString & filename ) + + + load + load-2 + ( const QByteArray & contents ) + + + matrixForElement + matrixForElement + ( const QString & id ) + + + render + render + ( QPainter * painter ) + + + render + render-2 + ( QPainter * painter, const QRectF & bounds ) + + + render + render-3 + ( QPainter * painter, const QString & elementId, const QRectF & bounds = QRectF() + + + repaintNeeded + repaintNeeded + () + + + viewBox + viewBox + () + + + + QSvgWidget + qsvgwidget.html + + QSvgWidget + QSvgWidget + ( QWidget * parent = 0 ) + + + QSvgWidget + QSvgWidget-2 + ( const QString & file, QWidget * parent = 0 ) + + + load + load + ( const QString & file ) + + + load + load-2 + ( const QByteArray & contents ) + + + renderer + renderer + () + + + + QSyntaxHighlighter + qsyntaxhighlighter.html + + QSyntaxHighlighter + QSyntaxHighlighter + ( QObject * parent ) + + + QSyntaxHighlighter + QSyntaxHighlighter-2 + ( QTextDocument * parent ) + + + QSyntaxHighlighter + QSyntaxHighlighter-3 + ( QTextEdit * parent ) + + + currentBlockState + currentBlockState + () + + + currentBlockUserData + currentBlockUserData + () + + + document + document + () + + + format + format + ( int position ) + + + highlightBlock + highlightBlock + ( const QString & text ) + + + previousBlockState + previousBlockState + () + + + rehighlight + rehighlight + () + + + setCurrentBlockState + setCurrentBlockState + ( int newState ) + + + setCurrentBlockUserData + setCurrentBlockUserData + ( QTextBlockUserData * data ) + + + setDocument + setDocument + ( QTextDocument * doc ) + + + setFormat + setFormat + ( int start, int count, const QTextCharFormat & format ) + + + setFormat + setFormat-2 + ( int start, int count, const QColor & color ) + + + setFormat + setFormat-3 + ( int start, int count, const QFont & font ) + + + + QSysInfo + qsysinfo.html + + Endian + Endian-enum + + + + MacVersion + MacVersion-enum + + + + Sizes + Sizes-enum + + + + WinVersion + WinVersion-enum + + + + MacintoshVersion + MacintoshVersion-var + + + + WindowsVersion + WindowsVersion-var + + + + + QSystemLocale + qsystemlocale.html + + QueryType + QueryType-enum + + + + QSystemLocale + QSystemLocale + () + + + fallbackLocale + fallbackLocale + () + + + query + query + ( QueryType type, QVariant in ) + + + + QSystemTrayIcon + qsystemtrayicon.html + + ActivationReason + ActivationReason-enum + + + + MessageIcon + MessageIcon-enum + + + + QSystemTrayIcon + QSystemTrayIcon + ( QObject * parent = 0 ) + + + QSystemTrayIcon + QSystemTrayIcon-2 + ( const QIcon & icon, QObject * parent = 0 ) + + + activated + activated + ( QSystemTrayIcon::ActivationReason reason ) + + + contextMenu + contextMenu + () + + + geometry + geometry + () + + + hide + hide + () + + + isSystemTrayAvailable + isSystemTrayAvailable + () + + + messageClicked + messageClicked + () + + + setContextMenu + setContextMenu + ( QMenu * menu ) + + + show + show + () + + + showMessage + showMessage + ( const QString & title, const QString & message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000 ) + + + supportsMessages + supportsMessages + () + + + BackgroundMode + BackgroundMode-enum + + + + ButtonState + ButtonState-typedef + + + + ButtonState_enum + ButtonState_enum-enum + + + + GUIStyle + GUIStyle-enum + + + + MacintoshVersion + MacintoshVersion-enum + + + + PaintUnit + PaintUnit-enum + + + + ScaleMode + ScaleMode-typedef + + + + TextFlags + TextFlags-typedef + + + + ToolBarDock + ToolBarDock-typedef + + + + WindowsVersion + WindowsVersion-enum + + + + AnchorAttribute + AnchorAttribute-enum + + + + ApplicationAttribute + ApplicationAttribute-enum + + + + ArrowType + ArrowType-enum + + + + AspectRatioMode + AspectRatioMode-enum + + + + Axis + Axis-enum + + + + BGMode + BGMode-enum + + + + BrushStyle + BrushStyle-enum + + + + CaseSensitivity + CaseSensitivity-enum + + + + CheckState + CheckState-enum + + + + ClipOperation + ClipOperation-enum + + + + ConnectionType + ConnectionType-enum + + + + ContextMenuPolicy + ContextMenuPolicy-enum + + + + Corner + Corner-enum + + + + CursorShape + CursorShape-enum + + + + DateFormat + DateFormat-enum + + + + DayOfWeek + DayOfWeek-enum + + + + Dock + Dock-enum + + + + EventPriority + EventPriority-enum + + + + FillRule + FillRule-enum + + + + FocusPolicy + FocusPolicy-enum + + + + FocusReason + FocusReason-enum + + + + GlobalColor + GlobalColor-enum + + + + HANDLE + HANDLE-typedef + + + + HitTestAccuracy + HitTestAccuracy-enum + + + + InputMethodQuery + InputMethodQuery-enum + + + + ItemDataRole + ItemDataRole-enum + + + + ItemSelectionMode + ItemSelectionMode-enum + + + + Key + Key-enum + + + + LayoutDirection + LayoutDirection-enum + + + + MaskMode + MaskMode-enum + + + + Modifier + Modifier-enum + + + + PenCapStyle + PenCapStyle-enum + + + + PenJoinStyle + PenJoinStyle-enum + + + + PenStyle + PenStyle-enum + + + + ScrollBarPolicy + ScrollBarPolicy-enum + + + + ShortcutContext + ShortcutContext-enum + + + + SortOrder + SortOrder-enum + + + + TextElideMode + TextElideMode-enum + + + + TextFlag + TextFlag-enum + + + + TextFormat + TextFormat-enum + + + + TimeSpec + TimeSpec-enum + + + + ToolButtonStyle + ToolButtonStyle-enum + + + + TransformationMode + TransformationMode-enum + + + + UIEffect + UIEffect-enum + + + + WFlags + WFlags-typedef + + + + WhiteSpaceMode + WhiteSpaceMode-enum + + + + WidgetAttribute + WidgetAttribute-enum + + + + WindowModality + WindowModality-enum + + + + convertFromPlainText + convertFromPlainText + ( const QString & plain, WhiteSpaceMode mode = WhiteSpacePre ) + + + escape + escape + ( const QString & plain ) + + + mightBeRichText + mightBeRichText + ( const QString & text ) + + + BackgroundMode + BackgroundMode-enum + + + + ButtonState + ButtonState-typedef + + + + ButtonState_enum + ButtonState_enum-enum + + + + GUIStyle + GUIStyle-enum + + + + MacintoshVersion + MacintoshVersion-enum + + + + PaintUnit + PaintUnit-enum + + + + ScaleMode + ScaleMode-typedef + + + + TextFlags + TextFlags-typedef + + + + ToolBarDock + ToolBarDock-typedef + + + + WindowsVersion + WindowsVersion-enum + + + + selected + selected + ( int index ) + + + setCurrentTab + setCurrentTab + ( int index ) + + + + QTabBar + qtabbar.html + + Shape + Shape-enum + + + + TextElideMode + elideMode-prop + + + + QTabBar + QTabBar + ( QWidget * parent = 0 ) + + + addTab + addTab + ( const QString & text ) + + + addTab + addTab-2 + ( const QIcon & icon, const QString & text ) + + + currentChanged + currentChanged + ( int index ) + + + initStyleOption + initStyleOption + ( QStyleOptionTab * option, int tabIndex ) + + + insertTab + insertTab + ( int index, const QString & text ) + + + insertTab + insertTab-2 + ( int index, const QIcon & icon, const QString & text ) + + + isTabEnabled + isTabEnabled + ( int index ) + + + removeTab + removeTab + ( int index ) + + + setTabData + setTabData + ( int index, const QVariant & data ) + + + setTabEnabled + setTabEnabled + ( int index, bool enabled ) + + + setTabIcon + setTabIcon + ( int index, const QIcon & icon ) + + + setTabText + setTabText + ( int index, const QString & text ) + + + setTabTextColor + setTabTextColor + ( int index, const QColor & color ) + + + setTabToolTip + setTabToolTip + ( int index, const QString & tip ) + + + setTabWhatsThis + setTabWhatsThis + ( int index, const QString & text ) + + + tabAt + tabAt + ( const QPoint & position ) + + + tabData + tabData + ( int index ) + + + tabIcon + tabIcon + ( int index ) + + + tabInserted + tabInserted + ( int index ) + + + tabLayoutChange + tabLayoutChange + () + + + tabRect + tabRect + ( int index ) + + + tabRemoved + tabRemoved + ( int index ) + + + tabSizeHint + tabSizeHint + ( int index ) + + + tabText + tabText + ( int index ) + + + tabTextColor + tabTextColor + ( int index ) + + + tabToolTip + tabToolTip + ( int index ) + + + tabWhatsThis + tabWhatsThis + ( int index ) + + + selected + selected + ( int index ) + + + setCurrentTab + setCurrentTab + ( int index ) + + + + QTabletEvent + qtabletevent.html + + PointerType + PointerType-enum + + + + TabletDevice + TabletDevice-enum + + + + QTabletEvent + QTabletEvent + ( Type type, const QPoint & pos, const QPoint & globalPos, const QPointF & hiResGlobalPos, int device, int pointerType, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, Qt::KeyboardModifiers keyState, qint64 uniqueID ) + + + device + device + () + + + globalPos + globalPos + () + + + globalX + globalX + () + + + globalY + globalY + () + + + hiResGlobalPos + hiResGlobalPos + () + + + hiResGlobalX + hiResGlobalX + () + + + hiResGlobalY + hiResGlobalY + () + + + pointerType + pointerType + () + + + pos + pos + () + + + pressure + pressure + () + + + rotation + rotation + () + + + tangentialPressure + tangentialPressure + () + + + uniqueId + uniqueId + () + + + x + x + () + + + xTilt + xTilt + () + + + y + y + () + + + yTilt + yTilt + () + + + z + z + () + + + sortByColumn + sortByColumn-2 + ( int column ) + + + + QTableView + qtableview.html + + PenStyle + gridStyle-prop + + + + QTableView + QTableView + ( QWidget * parent = 0 ) + + + columnAt + columnAt + ( int x ) + + + columnCountChanged + columnCountChanged + ( int oldCount, int newCount ) + + + columnMoved + columnMoved + ( int column, int oldIndex, int newIndex ) + + + columnResized + columnResized + ( int column, int oldWidth, int newWidth ) + + + columnSpan + columnSpan + ( int row, int column ) + + + columnViewportPosition + columnViewportPosition + ( int column ) + + + columnWidth + columnWidth + ( int column ) + + + currentChanged + currentChanged + ( const QModelIndex & current, const QModelIndex & previous ) + + + hideColumn + hideColumn + ( int column ) + + + hideRow + hideRow + ( int row ) + + + horizontalHeader + horizontalHeader + () + + + horizontalOffset + horizontalOffset + () + + + indexAt + indexAt + ( const QPoint & pos ) + + + isColumnHidden + isColumnHidden + ( int column ) + + + isRowHidden + isRowHidden + ( int row ) + + + moveCursor + moveCursor + ( CursorAction cursorAction, Qt::KeyboardModifiers modifiers ) + + + paintEvent + paintEvent + ( QPaintEvent * event ) + + + resizeColumnToContents + resizeColumnToContents + ( int column ) + + + resizeColumnsToContents + resizeColumnsToContents + () + + + resizeRowToContents + resizeRowToContents + ( int row ) + + + resizeRowsToContents + resizeRowsToContents + () + + + rowAt + rowAt + ( int y ) + + + rowCountChanged + rowCountChanged + ( int oldCount, int newCount ) + + + rowHeight + rowHeight + ( int row ) + + + rowMoved + rowMoved + ( int row, int oldIndex, int newIndex ) + + + rowResized + rowResized + ( int row, int oldHeight, int newHeight ) + + + rowSpan + rowSpan + ( int row, int column ) + + + rowViewportPosition + rowViewportPosition + ( int row ) + + + selectColumn + selectColumn + ( int column ) + + + selectRow + selectRow + ( int row ) + + + selectionChanged + selectionChanged + ( const QItemSelection & selected, const QItemSelection & deselected ) + + + setColumnHidden + setColumnHidden + ( int column, bool hide ) + + + setColumnWidth + setColumnWidth + ( int column, int width ) + + + setHorizontalHeader + setHorizontalHeader + ( QHeaderView * header ) + + + setRowHeight + setRowHeight + ( int row, int height ) + + + setRowHidden + setRowHidden + ( int row, bool hide ) + + + setSelection + setSelection + ( const QRect & rect, QItemSelectionModel::SelectionFlags flags ) + + + setSpan + setSpan + ( int row, int column, int rowSpan, int columnSpan ) + + + setVerticalHeader + setVerticalHeader + ( QHeaderView * header ) + + + showColumn + showColumn + ( int column ) + + + showRow + showRow + ( int row ) + + + sizeHintForColumn + sizeHintForColumn + ( int column ) + + + sizeHintForRow + sizeHintForRow + ( int row ) + + + sortByColumn + sortByColumn + ( int column, Qt::SortOrder order ) + + + verticalHeader + verticalHeader + () + + + verticalOffset + verticalOffset + () + + + isItemSelected + isItemSelected + ( const QTableWidgetItem * item ) + + + setItemSelected + setItemSelected + ( const QTableWidgetItem * item, bool select ) + + + + QTableWidget + qtablewidget.html + + QTableWidget + QTableWidget + ( QWidget * parent = 0 ) + + + QTableWidget + QTableWidget-2 + ( int rows, int columns, QWidget * parent = 0 ) + + + cellActivated + cellActivated + ( int row, int column ) + + + cellChanged + cellChanged + ( int row, int column ) + + + cellClicked + cellClicked + ( int row, int column ) + + + cellDoubleClicked + cellDoubleClicked + ( int row, int column ) + + + cellEntered + cellEntered + ( int row, int column ) + + + cellPressed + cellPressed + ( int row, int column ) + + + cellWidget + cellWidget + ( int row, int column ) + + + clear + clear + () + + + clearContents + clearContents + () + + + closePersistentEditor + closePersistentEditor + ( QTableWidgetItem * item ) + + + column + column + ( const QTableWidgetItem * item ) + + + currentCellChanged + currentCellChanged + ( int currentRow, int currentColumn, int previousRow, int previousColumn ) + + + currentColumn + currentColumn + () + + + currentItem + currentItem + () + + + currentItemChanged + currentItemChanged + ( QTableWidgetItem * current, QTableWidgetItem * previous ) + + + currentRow + currentRow + () + + + dropMimeData + dropMimeData + ( int row, int column, const QMimeData * data, Qt::DropAction action ) + + + editItem + editItem + ( QTableWidgetItem * item ) + + + findItems + findItems + ( const QString & text, Qt::MatchFlags flags ) + + + horizontalHeaderItem + horizontalHeaderItem + ( int column ) + + + indexFromItem + indexFromItem + ( QTableWidgetItem * item ) + + + insertColumn + insertColumn + ( int column ) + + + insertRow + insertRow + ( int row ) + + + item + item + ( int row, int column ) + + + itemActivated + itemActivated + ( QTableWidgetItem * item ) + + + itemAt + itemAt + ( const QPoint & point ) + + + itemAt + itemAt-2 + ( int ax, int ay ) + + + itemChanged + itemChanged + ( QTableWidgetItem * item ) + + + itemClicked + itemClicked + ( QTableWidgetItem * item ) + + + itemDoubleClicked + itemDoubleClicked + ( QTableWidgetItem * item ) + + + itemEntered + itemEntered + ( QTableWidgetItem * item ) + + + itemFromIndex + itemFromIndex + ( const QModelIndex & index ) + + + itemPressed + itemPressed + ( QTableWidgetItem * item ) + + + itemPrototype + itemPrototype + () + + + itemSelectionChanged + itemSelectionChanged + () + + + items + items + ( const QMimeData * data ) + + + mimeData + mimeData + ( const QList<QTableWidgetItem *> items ) + + + mimeTypes + mimeTypes + () + + + openPersistentEditor + openPersistentEditor + ( QTableWidgetItem * item ) + + + removeCellWidget + removeCellWidget + ( int row, int column ) + + + removeColumn + removeColumn + ( int column ) + + + removeRow + removeRow + ( int row ) + + + row + row + ( const QTableWidgetItem * item ) + + + scrollToItem + scrollToItem + ( const QTableWidgetItem * item, QAbstractItemView::ScrollHint hint = EnsureVisible ) + + + selectedItems + selectedItems + () + + + selectedRanges + selectedRanges + () + + + setCellWidget + setCellWidget + ( int row, int column, QWidget * widget ) + + + setCurrentCell + setCurrentCell + ( int row, int column ) + + + setCurrentItem + setCurrentItem + ( QTableWidgetItem * item ) + + + setHorizontalHeaderItem + setHorizontalHeaderItem + ( int column, QTableWidgetItem * item ) + + + setHorizontalHeaderLabels + setHorizontalHeaderLabels + ( const QStringList & labels ) + + + setItem + setItem + ( int row, int column, QTableWidgetItem * item ) + + + setItemPrototype + setItemPrototype + ( const QTableWidgetItem * item ) + + + setRangeSelected + setRangeSelected + ( const QTableWidgetSelectionRange & range, bool select ) + + + setVerticalHeaderItem + setVerticalHeaderItem + ( int row, QTableWidgetItem * item ) + + + setVerticalHeaderLabels + setVerticalHeaderLabels + ( const QStringList & labels ) + + + sortItems + sortItems + ( int column, Qt::SortOrder order = Qt::AscendingOrder ) + + + DropActions + supportedDropActions + QTableWidget::supportedDropActions() + + + takeHorizontalHeaderItem + takeHorizontalHeaderItem + ( int column ) + + + takeItem + takeItem + ( int row, int column ) + + + takeVerticalHeaderItem + takeVerticalHeaderItem + ( int row ) + + + verticalHeaderItem + verticalHeaderItem + ( int row ) + + + visualColumn + visualColumn + ( int logicalColumn ) + + + visualItemRect + visualItemRect + ( const QTableWidgetItem * item ) + + + visualRow + visualRow + ( int logicalRow ) + + + backgroundColor + backgroundColor + () + + + setBackgroundColor + setBackgroundColor + ( const QColor & color ) + + + setTextColor + setTextColor + ( const QColor & color ) + + + textColor + textColor + () + + + + QTableWidgetItem + qtablewidgetitem.html + + ItemType + ItemType-enum + + + + QTableWidgetItem + QTableWidgetItem + ( int type = Type ) + + + QTableWidgetItem + QTableWidgetItem-2 + ( const QString & text, int type = Type ) + + + QTableWidgetItem + QTableWidgetItem-3 + ( const QIcon & icon, const QString & text, int type = Type ) + + + QTableWidgetItem + QTableWidgetItem-4 + ( const QTableWidgetItem & other ) + + + background + background + () + + + CheckState + checkState + QTableWidgetItem::checkState() + + + clone + clone + () + + + column + column + () + + + data + data + ( int role ) + + + ItemFlags + flags + QTableWidgetItem::flags() + + + font + font + () + + + foreground + foreground + () + + + icon + icon + () + + + isSelected + isSelected + () + + + read + read + ( QDataStream & in ) + + + row + row + () + + + setBackground + setBackground + ( const QBrush & brush ) + + + setCheckState + setCheckState + ( Qt::CheckState state ) + + + setData + setData + ( int role, const QVariant & value ) + + + setFlags + setFlags + ( Qt::ItemFlags flags ) + + + setFont + setFont + ( const QFont & font ) + + + setForeground + setForeground + ( const QBrush & brush ) + + + setIcon + setIcon + ( const QIcon & icon ) + + + setSelected + setSelected + ( bool select ) + + + setSizeHint + setSizeHint + ( const QSize & size ) + + + setStatusTip + setStatusTip + ( const QString & statusTip ) + + + setText + setText + ( const QString & text ) + + + setTextAlignment + setTextAlignment + ( int alignment ) + + + setToolTip + setToolTip + ( const QString & toolTip ) + + + setWhatsThis + setWhatsThis + ( const QString & whatsThis ) + + + sizeHint + sizeHint + () + + + statusTip + statusTip + () + + + tableWidget + tableWidget + () + + + text + text + () + + + textAlignment + textAlignment + () + + + toolTip + toolTip + () + + + type + type + () + + + whatsThis + whatsThis + () + + + write + write + ( QDataStream & out ) + + + operator< + operator-lt + ( const QTableWidgetItem & other ) + + + operator= + operator-eq + ( const QTableWidgetItem & other ) + + + + QTableWidgetSelectionRange + qtablewidgetselectionrange.html + + QTableWidgetSelectionRange + QTableWidgetSelectionRange + () + + + QTableWidgetSelectionRange + QTableWidgetSelectionRange-2 + ( int top, int left, int bottom, int right ) + + + QTableWidgetSelectionRange + QTableWidgetSelectionRange-3 + ( const QTableWidgetSelectionRange & other ) + + + bottomRow + bottomRow + () + + + columnCount + columnCount + () + + + leftColumn + leftColumn + () + + + rightColumn + rightColumn + () + + + rowCount + rowCount + () + + + topRow + topRow + () + + + QTabWidget + QTabWidget-2 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + changeTab + changeTab + ( QWidget * widget, const QString & label ) + + + changeTab + changeTab-2 + ( QWidget * widget, const QIcon & icon, const QString & label ) + + + currentChanged + currentChanged-2 + ( QWidget * widget ) + + + currentPage + currentPage + () + + + currentPageIndex + currentPageIndex + () + + + insertTab + insertTab-2 + ( QWidget * widget, const QString & label, int index = -1 ) + + + insertTab + insertTab-3 + ( QWidget * widget, const QIcon & icon, const QString & label, int index = -1 ) + + + isTabEnabled + isTabEnabled-2 + ( QWidget * widget ) + + + label + label + ( int index ) + + + margin + margin + () + + + page + page + ( int index ) + + + removePage + removePage + ( QWidget * widget ) + + + removeTabToolTip + removeTabToolTip + ( QWidget * widget ) + + + selected + selected + ( const QString & tabLabel ) + + + setCurrentPage + setCurrentPage + ( int index ) + + + setMargin + setMargin + ( int margin ) + + + setTabEnabled + setTabEnabled-2 + ( QWidget * widget, bool b ) + + + setTabIconSet + setTabIconSet + ( QWidget * widget, const QIcon & icon ) + + + setTabLabel + setTabLabel + ( QWidget * widget, const QString & label ) + + + setTabToolTip + setTabToolTip-2 + ( QWidget * widget, const QString & tip ) + + + showPage + showPage + ( QWidget * widget ) + + + tabIconSet + tabIconSet + ( QWidget * widget ) + + + tabLabel + tabLabel + ( QWidget * widget ) + + + tabToolTip + tabToolTip-2 + ( QWidget * widget ) + + + + QTabWidget + qtabwidget.html + + TabPosition + TabPosition-enum + + + + TabShape + TabShape-enum + + + + TextElideMode + elideMode-prop + + + + QTabWidget + QTabWidget + ( QWidget * parent = 0 ) + + + addTab + addTab + ( QWidget * child, const QString & label ) + + + addTab + addTab-2 + ( QWidget * child, const QIcon & icon, const QString & label ) + + + clear + clear + () + + + cornerWidget + cornerWidget + ( Qt::Corner corner = Qt::TopRightCorner ) + + + currentChanged + currentChanged + ( int index ) + + + currentWidget + currentWidget + () + + + indexOf + indexOf + ( QWidget * w ) + + + initStyleOption + initStyleOption + ( QStyleOptionTabWidgetFrame * option ) + + + insertTab + insertTab + ( int index, QWidget * widget, const QString & label ) + + + insertTab + insertTab-4 + ( int index, QWidget * widget, const QIcon & icon, const QString & label ) + + + isTabEnabled + isTabEnabled + ( int index ) + + + paintEvent + paintEvent + ( QPaintEvent * event ) + + + removeTab + removeTab + ( int index ) + + + setCornerWidget + setCornerWidget + ( QWidget * widget, Qt::Corner corner = Qt::TopRightCorner ) + + + setCurrentWidget + setCurrentWidget + ( QWidget * widget ) + + + setTabBar + setTabBar + ( QTabBar * tb ) + + + setTabEnabled + setTabEnabled + ( int index, bool enable ) + + + setTabIcon + setTabIcon + ( int index, const QIcon & icon ) + + + setTabText + setTabText + ( int index, const QString & label ) + + + setTabToolTip + setTabToolTip + ( int index, const QString & tip ) + + + setTabWhatsThis + setTabWhatsThis + ( int index, const QString & text ) + + + tabBar + tabBar + () + + + tabIcon + tabIcon + ( int index ) + + + tabInserted + tabInserted + ( int index ) + + + tabRemoved + tabRemoved + ( int index ) + + + tabText + tabText + ( int index ) + + + tabToolTip + tabToolTip + ( int index ) + + + tabWhatsThis + tabWhatsThis + ( int index ) + + + widget + widget + ( int index ) + + + QTabWidget + QTabWidget-2 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + changeTab + changeTab + ( QWidget * widget, const QString & label ) + + + changeTab + changeTab-2 + ( QWidget * widget, const QIcon & icon, const QString & label ) + + + currentChanged + currentChanged-2 + ( QWidget * widget ) + + + currentPage + currentPage + () + + + currentPageIndex + currentPageIndex + () + + + insertTab + insertTab-2 + ( QWidget * widget, const QString & label, int index = -1 ) + + + insertTab + insertTab-3 + ( QWidget * widget, const QIcon & icon, const QString & label, int index = -1 ) + + + isTabEnabled + isTabEnabled-2 + ( QWidget * widget ) + + + label + label + ( int index ) + + + margin + margin + () + + + page + page + ( int index ) + + + removePage + removePage + ( QWidget * widget ) + + + removeTabToolTip + removeTabToolTip + ( QWidget * widget ) + + + selected + selected + ( const QString & tabLabel ) + + + setCurrentPage + setCurrentPage + ( int index ) + + + setMargin + setMargin + ( int margin ) + + + setTabEnabled + setTabEnabled-2 + ( QWidget * widget, bool b ) + + + setTabIconSet + setTabIconSet + ( QWidget * widget, const QIcon & icon ) + + + setTabLabel + setTabLabel + ( QWidget * widget, const QString & label ) + + + setTabToolTip + setTabToolTip-2 + ( QWidget * widget, const QString & tip ) + + + showPage + showPage + ( QWidget * widget ) + + + tabIconSet + tabIconSet + ( QWidget * widget ) + + + tabLabel + tabLabel + ( QWidget * widget ) + + + tabToolTip + tabToolTip-2 + ( QWidget * widget ) + + + const_iterator + qBinaryFind-3 + qBinaryFind( const Container & container, const T & value ) + + + const_iterator + qFind-2 + qFind( const Container & container, const T & value ) + + + const_iterator + qLowerBound-3 + qLowerBound( const Container & container, const T & value ) + + + const_iterator + qUpperBound-3 + qUpperBound( const Container & container, const T & value ) + + + + QTcpServer + qtcpserver.html + + QTcpServer + QTcpServer + ( QObject * parent = 0 ) + + + close + close + () + + + errorString + errorString + () + + + hasPendingConnections + hasPendingConnections + () + + + incomingConnection + incomingConnection + ( int socketDescriptor ) + + + isListening + isListening + () + + + listen + listen + ( const QHostAddress & address = QHostAddress::Any, quint16 port = 0 ) + + + maxPendingConnections + maxPendingConnections + () + + + newConnection + newConnection + () + + + nextPendingConnection + nextPendingConnection + () + + + proxy + proxy + () + + + serverAddress + serverAddress + () + + + SocketError + serverError + QTcpServer::serverError() + + + serverPort + serverPort + () + + + setMaxPendingConnections + setMaxPendingConnections + ( int numConnections ) + + + setProxy + setProxy + ( const QNetworkProxy & networkProxy ) + + + setSocketDescriptor + setSocketDescriptor + ( int socketDescriptor ) + + + socketDescriptor + socketDescriptor + () + + + waitForNewConnection + waitForNewConnection + ( int msec = 0, bool * timedOut = 0 ) + + + + QTcpSocket + qtcpsocket.html + + QTcpSocket + QTcpSocket + ( QObject * parent = 0 ) + + + + QTemporaryFile + qtemporaryfile.html + + QTemporaryFile + QTemporaryFile + () + + + QTemporaryFile + QTemporaryFile-2 + ( const QString & templateName ) + + + QTemporaryFile + QTemporaryFile-3 + ( QObject * parent ) + + + QTemporaryFile + QTemporaryFile-4 + ( const QString & templateName, QObject * parent ) + + + autoRemove + autoRemove + () + + + createLocalFile + createLocalFile + ( QFile & file ) + + + createLocalFile + createLocalFile-2 + ( const QString & fileName ) + + + fileName + fileName + () + + + fileTemplate + fileTemplate + () + + + open + open + () + + + setAutoRemove + setAutoRemove + ( bool b ) + + + setFileTemplate + setFileTemplate + ( const QString & name ) + + + KeyAction + KeyAction-enum + + + + MouseAction + MouseAction-enum + + + + SkipMode + SkipMode-enum + + + + TestFailMode + TestFailMode-enum + + + + addColumn + addColumn + ( const char * name, T * dummy = 0 ) + + + currentDataTag + currentDataTag + () + + + currentTestFailed + currentTestFailed + () + + + currentTestFunction + currentTestFunction + () + + + ignoreMessage + ignoreMessage + ( QtMsgType type, const char * message ) + + + keyClick + keyClick + ( QWidget * widget, Qt::Key key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1 ) + + + keyClick + keyClick-2 + ( QWidget * widget, char key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1 ) + + + keyClicks + keyClicks + ( QWidget * widget, const QString & sequence, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1 ) + + + keyEvent + keyEvent + ( KeyAction action, QWidget * widget, Qt::Key key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1 ) + + + keyEvent + keyEvent-2 + ( KeyAction action, QWidget * widget, char ascii, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1 ) + + + keyPress + keyPress + ( QWidget * widget, Qt::Key key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1 ) + + + keyPress + keyPress-2 + ( QWidget * widget, char key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1 ) + + + keyRelease + keyRelease + ( QWidget * widget, Qt::Key key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1 ) + + + keyRelease + keyRelease-2 + ( QWidget * widget, char key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1 ) + + + mouseClick + mouseClick + ( QWidget * widget, Qt::MouseButton button, Qt::KeyboardModifiers modifier = 0, QPoint pos = QPoint() + + + mouseDClick + mouseDClick + ( QWidget * widget, Qt::MouseButton button, Qt::KeyboardModifiers modifier = 0, QPoint pos = QPoint() + + + mouseMove + mouseMove + ( QWidget * widget, QPoint pos = QPoint() + + + mousePress + mousePress + ( QWidget * widget, Qt::MouseButton button, Qt::KeyboardModifiers modifier = 0, QPoint pos = QPoint() + + + mouseRelease + mouseRelease + ( QWidget * widget, Qt::MouseButton button, Qt::KeyboardModifiers modifier = 0, QPoint pos = QPoint() + + + newRow + newRow + ( const char * dataTag ) + + + qExec + qExec + ( QObject * testObject, int argc = 0, char ** argv = 0 ) + + + qSleep + qSleep + ( int ms ) + + + qWait + qWait + ( int ms ) + + + toHexRepresentation + toHexRepresentation + ( const char * ba, int length ) + + + toString + toString + ( const QByteArray & ba ) + + + toString + toString-2 + ( const T & value ) + + + toString + toString-5 + ( const QLatin1String & string ) + + + toString + toString-6 + ( const QString & string ) + + + toString + toString-7 + ( const QTime & time ) + + + toString + toString-8 + ( const QDate & date ) + + + toString + toString-9 + ( const QDateTime & dateTime ) + + + toString + toString-10 + ( const QChar & character ) + + + toString + toString-11 + ( const QPoint & point ) + + + toString + toString-12 + ( const QSize & size ) + + + toString + toString-13 + ( const QRect & rectangle ) + + + toString + toString-14 + ( const QPointF & point ) + + + toString + toString-15 + ( const QSizeF & size ) + + + toString + toString-16 + ( const QRectF & rectangle ) + + + + QTestEventList + qtesteventlist.html + + QTestEventList + QTestEventList + () + + + QTestEventList + QTestEventList-2 + ( const QTestEventList & other ) + + + addDelay + addDelay + ( int msecs ) + + + addKeyClick + addKeyClick + ( Qt::Key qtKey, Qt::KeyboardModifiers modifiers = Qt::NoModifier, int msecs = -1 ) + + + addKeyClick + addKeyClick-2 + ( char ascii, Qt::KeyboardModifiers modifiers = Qt::NoModifier, int msecs = -1 ) + + + addKeyClicks + addKeyClicks + ( const QString & keys, Qt::KeyboardModifiers modifiers = Qt::NoModifier, int msecs = -1 ) + + + addKeyPress + addKeyPress + ( Qt::Key qtKey, Qt::KeyboardModifiers modifiers = Qt::NoModifier, int msecs = -1 ) + + + addKeyPress + addKeyPress-2 + ( char ascii, Qt::KeyboardModifiers modifiers = Qt::NoModifier, int msecs = -1 ) + + + addKeyRelease + addKeyRelease + ( Qt::Key qtKey, Qt::KeyboardModifiers modifiers = Qt::NoModifier, int msecs = -1 ) + + + addKeyRelease + addKeyRelease-2 + ( char ascii, Qt::KeyboardModifiers modifiers = Qt::NoModifier, int msecs = -1 ) + + + addMouseClick + addMouseClick + ( Qt::MouseButton button, Qt::KeyboardModifiers modifiers = 0, QPoint pos = QPoint() + + + addMouseDClick + addMouseDClick + ( Qt::MouseButton button, Qt::KeyboardModifiers modifiers = 0, QPoint pos = QPoint() + + + addMouseMove + addMouseMove + ( QPoint pos = QPoint() + + + addMousePress + addMousePress + ( Qt::MouseButton button, Qt::KeyboardModifiers modifiers = 0, QPoint pos = QPoint() + + + addMouseRelease + addMouseRelease + ( Qt::MouseButton button, Qt::KeyboardModifiers modifiers = 0, QPoint pos = QPoint() + + + clear + clear + () + + + simulate + simulate + ( QWidget * w ) + + + + QTextBlock::iterator + qtextblock-iterator.html + + iterator + iterator + () + + + iterator + iterator-3 + ( const iterator & other ) + + + atEnd + atEnd + () + + + fragment + fragment + () + + + operator!= + operator-not-eq + ( const iterator & other ) + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator== + operator-eq-eq + ( const iterator & other ) + + + + QTextBlock + qtextblock.html + + Iterator + Iterator-typedef + + + + QTextBlock + QTextBlock + ( const QTextBlock & other ) + + + begin + begin + () + + + blockFormat + blockFormat + () + + + blockFormatIndex + blockFormatIndex + () + + + charFormat + charFormat + () + + + charFormatIndex + charFormatIndex + () + + + contains + contains + ( int position ) + + + document + document + () + + + end + end + () + + + isValid + isValid + () + + + layout + layout + () + + + length + length + () + + + next + next + () + + + position + position + () + + + previous + previous + () + + + setUserData + setUserData + ( QTextBlockUserData * data ) + + + setUserState + setUserState + ( int state ) + + + text + text + () + + + textList + textList + () + + + userData + userData + () + + + userState + userState + () + + + operator!= + operator-not-eq + ( const QTextBlock & other ) + + + operator< + operator-lt + ( const QTextBlock & other ) + + + operator= + operator-eq + ( const QTextBlock & other ) + + + operator== + operator-eq-eq + ( const QTextBlock & other ) + + + + QTextBlockFormat + qtextblockformat.html + + QTextBlockFormat + QTextBlockFormat + () + + + Alignment + alignment + QTextBlockFormat::alignment() + + + bottomMargin + bottomMargin + () + + + indent + indent + () + + + isValid + isValid + () + + + leftMargin + leftMargin + () + + + nonBreakableLines + nonBreakableLines + () + + + pageBreakPolicy + pageBreakPolicy + () + + + rightMargin + rightMargin + () + + + setAlignment + setAlignment + ( Qt::Alignment alignment ) + + + setBottomMargin + setBottomMargin + ( qreal margin ) + + + setIndent + setIndent + ( int indentation ) + + + setLeftMargin + setLeftMargin + ( qreal margin ) + + + setNonBreakableLines + setNonBreakableLines + ( bool b ) + + + setPageBreakPolicy + setPageBreakPolicy + ( PageBreakFlags policy ) + + + setRightMargin + setRightMargin + ( qreal margin ) + + + setTextIndent + setTextIndent + ( qreal indent ) + + + setTopMargin + setTopMargin + ( qreal margin ) + + + textIndent + textIndent + () + + + topMargin + topMargin + () + + + + QTextBlockGroup + qtextblockgroup.html + + QTextBlockGroup + QTextBlockGroup + ( QTextDocument * document ) + + + blockFormatChanged + blockFormatChanged + ( const QTextBlock & block ) + + + blockInserted + blockInserted + ( const QTextBlock & block ) + + + blockList + blockList + () + + + blockRemoved + blockRemoved + ( const QTextBlock & block ) + + + + QTextBlockUserData + qtextblockuserdata.html + + QTextBrowser + QTextBrowser-2 + ( QWidget * parent, const char * name ) + + + + QTextBrowser + qtextbrowser.html + + QTextBrowser + QTextBrowser + ( QWidget * parent = 0 ) + + + anchorClicked + anchorClicked + ( const QUrl & link ) + + + backward + backward + () + + + backwardAvailable + backwardAvailable + ( bool available ) + + + clearHistory + clearHistory + () + + + forward + forward + () + + + forwardAvailable + forwardAvailable + ( bool available ) + + + highlighted + highlighted + ( const QUrl & link ) + + + highlighted + highlighted-2 + ( const QString & link ) + + + home + home + () + + + isBackwardAvailable + isBackwardAvailable + () + + + isForwardAvailable + isForwardAvailable + () + + + keyPressEvent + keyPressEvent + ( QKeyEvent * ev ) + + + loadResource + loadResource + ( int type, const QUrl & name ) + + + reload + reload + () + + + sourceChanged + sourceChanged + ( const QUrl & src ) + + + QTextBrowser + QTextBrowser-2 + ( QWidget * parent, const char * name ) + + + anchorName + anchorName + () + + + setAnchorName + setAnchorName + ( const QString & name ) + + + + QTextCharFormat + qtextcharformat.html + + UnderlineStyle + UnderlineStyle-enum + + + + VerticalAlignment + VerticalAlignment-enum + + + + QTextCharFormat + QTextCharFormat + () + + + anchorHref + anchorHref + () + + + anchorNames + anchorNames + () + + + font + font + () + + + fontFamily + fontFamily + () + + + fontFixedPitch + fontFixedPitch + () + + + fontItalic + fontItalic + () + + + fontOverline + fontOverline + () + + + fontPointSize + fontPointSize + () + + + fontStrikeOut + fontStrikeOut + () + + + fontUnderline + fontUnderline + () + + + fontWeight + fontWeight + () + + + isAnchor + isAnchor + () + + + isValid + isValid + () + + + setAnchor + setAnchor + ( bool anchor ) + + + setAnchorHref + setAnchorHref + ( const QString & value ) + + + setAnchorNames + setAnchorNames + ( const QStringList & names ) + + + setFont + setFont + ( const QFont & font ) + + + setFontFamily + setFontFamily + ( const QString & family ) + + + setFontFixedPitch + setFontFixedPitch + ( bool fixedPitch ) + + + setFontItalic + setFontItalic + ( bool italic ) + + + setFontOverline + setFontOverline + ( bool overline ) + + + setFontPointSize + setFontPointSize + ( qreal size ) + + + setFontStrikeOut + setFontStrikeOut + ( bool strikeOut ) + + + setFontUnderline + setFontUnderline + ( bool underline ) + + + setFontWeight + setFontWeight + ( int weight ) + + + setTextOutline + setTextOutline + ( const QPen & pen ) + + + setToolTip + setToolTip + ( const QString & text ) + + + setUnderlineColor + setUnderlineColor + ( const QColor & color ) + + + setUnderlineStyle + setUnderlineStyle + ( UnderlineStyle style ) + + + setVerticalAlignment + setVerticalAlignment + ( VerticalAlignment alignment ) + + + textOutline + textOutline + () + + + toolTip + toolTip + () + + + underlineColor + underlineColor + () + + + underlineStyle + underlineStyle + () + + + verticalAlignment + verticalAlignment + () + + + + QTextCodec::ConverterState + qtextcodec-converterstate.html + + ConverterState + ConverterState + ( ConversionFlags flags = DefaultConversion ) + + + codecForContent + codecForContent + ( const char * str, int size ) + + + codecForIndex + codecForIndex + ( int i ) + + + codecForName + codecForName-3 + ( const char * hint, int accuracy ) + + + fromUnicode + fromUnicode-3 + ( const QString & uc, int & lenInOut ) + + + locale + locale + () + + + mimeName + mimeName + () + + + toUnicode + toUnicode-3 + ( const QByteArray & a, int len ) + + + + QTextCodec + qtextcodec.html + + QTextCodec + QTextCodec + () + + + aliases + aliases + () + + + availableCodecs + availableCodecs + () + + + availableMibs + availableMibs + () + + + canEncode + canEncode + ( QChar ch ) + + + canEncode + canEncode-2 + ( const QString & s ) + + + codecForCStrings + codecForCStrings + () + + + codecForLocale + codecForLocale + () + + + codecForMib + codecForMib + ( int mib ) + + + codecForName + codecForName + ( const QByteArray & name ) + + + codecForName + codecForName-2 + ( const char * name ) + + + codecForTr + codecForTr + () + + + convertFromUnicode + convertFromUnicode + ( const QChar * input, int number, ConverterState * state ) + + + convertToUnicode + convertToUnicode + ( const char * chars, int len, ConverterState * state ) + + + fromUnicode + fromUnicode + ( const QString & str ) + + + fromUnicode + fromUnicode-2 + ( const QChar * input, int number, ConverterState * state = 0 ) + + + makeDecoder + makeDecoder + () + + + makeEncoder + makeEncoder + () + + + mibEnum + mibEnum + () + + + name + name + () + + + setCodecForCStrings + setCodecForCStrings + ( QTextCodec * codec ) + + + setCodecForLocale + setCodecForLocale + ( QTextCodec * c ) + + + setCodecForTr + setCodecForTr + ( QTextCodec * c ) + + + toUnicode + toUnicode + ( const QByteArray & a ) + + + toUnicode + toUnicode-2 + ( const char * input, int size, ConverterState * state = 0 ) + + + toUnicode + toUnicode-4 + ( const char * chars ) + + + codecForContent + codecForContent + ( const char * str, int size ) + + + codecForIndex + codecForIndex + ( int i ) + + + codecForName + codecForName-3 + ( const char * hint, int accuracy ) + + + fromUnicode + fromUnicode-3 + ( const QString & uc, int & lenInOut ) + + + locale + locale + () + + + mimeName + mimeName + () + + + toUnicode + toUnicode-3 + ( const QByteArray & a, int len ) + + + + QTextCodecPlugin + qtextcodecplugin.html + + QTextCodecPlugin + QTextCodecPlugin + ( QObject * parent = 0 ) + + + aliases + aliases + () + + + createForMib + createForMib + ( int mib ) + + + createForName + createForName + ( const QByteArray & name ) + + + mibEnums + mibEnums + () + + + names + names + () + + + + QTextCursor + qtextcursor.html + + MoveMode + MoveMode-enum + + + + MoveOperation + MoveOperation-enum + + + + SelectionType + SelectionType-enum + + + + QTextCursor + QTextCursor + () + + + QTextCursor + QTextCursor-2 + ( QTextDocument * document ) + + + QTextCursor + QTextCursor-4 + ( QTextFrame * frame ) + + + QTextCursor + QTextCursor-5 + ( const QTextBlock & block ) + + + QTextCursor + QTextCursor-7 + ( const QTextCursor & cursor ) + + + anchor + anchor + () + + + atBlockEnd + atBlockEnd + () + + + atBlockStart + atBlockStart + () + + + atEnd + atEnd + () + + + atStart + atStart + () + + + beginEditBlock + beginEditBlock + () + + + block + block + () + + + blockCharFormat + blockCharFormat + () + + + blockFormat + blockFormat + () + + + blockNumber + blockNumber + () + + + charFormat + charFormat + () + + + clearSelection + clearSelection + () + + + columnNumber + columnNumber + () + + + createList + createList + ( const QTextListFormat & format ) + + + createList + createList-2 + ( QTextListFormat::Style style ) + + + currentFrame + currentFrame + () + + + currentList + currentList + () + + + currentTable + currentTable + () + + + deleteChar + deleteChar + () + + + deletePreviousChar + deletePreviousChar + () + + + endEditBlock + endEditBlock + () + + + hasComplexSelection + hasComplexSelection + () + + + hasSelection + hasSelection + () + + + insertBlock + insertBlock + () + + + insertBlock + insertBlock-2 + ( const QTextBlockFormat & format ) + + + insertBlock + insertBlock-3 + ( const QTextBlockFormat & format, const QTextCharFormat & charFormat ) + + + insertFragment + insertFragment + ( const QTextDocumentFragment & fragment ) + + + insertFrame + insertFrame + ( const QTextFrameFormat & format ) + + + insertHtml + insertHtml + ( const QString & html ) + + + insertImage + insertImage + ( const QTextImageFormat & format ) + + + insertImage + insertImage-2 + ( const QTextImageFormat & format, QTextFrameFormat::Position alignment ) + + + insertImage + insertImage-3 + ( const QString & name ) + + + insertList + insertList + ( const QTextListFormat & format ) + + + insertList + insertList-2 + ( QTextListFormat::Style style ) + + + insertTable + insertTable + ( int rows, int columns, const QTextTableFormat & format ) + + + insertTable + insertTable-2 + ( int rows, int columns ) + + + insertText + insertText + ( const QString & text ) + + + insertText + insertText-2 + ( const QString & text, const QTextCharFormat & format ) + + + isCopyOf + isCopyOf + ( const QTextCursor & other ) + + + isNull + isNull + () + + + joinPreviousEditBlock + joinPreviousEditBlock + () + + + mergeBlockCharFormat + mergeBlockCharFormat + ( const QTextCharFormat & modifier ) + + + mergeBlockFormat + mergeBlockFormat + ( const QTextBlockFormat & modifier ) + + + mergeCharFormat + mergeCharFormat + ( const QTextCharFormat & modifier ) + + + movePosition + movePosition + ( MoveOperation operation, MoveMode mode = MoveAnchor, int n = 1 ) + + + position + position + () + + + removeSelectedText + removeSelectedText + () + + + select + select + ( SelectionType selection ) + + + selectedTableCells + selectedTableCells + ( int * firstRow, int * numRows, int * firstColumn, int * numColumns ) + + + selectedText + selectedText + () + + + selection + selection + () + + + selectionEnd + selectionEnd + () + + + selectionStart + selectionStart + () + + + setBlockCharFormat + setBlockCharFormat + ( const QTextCharFormat & format ) + + + setBlockFormat + setBlockFormat + ( const QTextBlockFormat & format ) + + + setCharFormat + setCharFormat + ( const QTextCharFormat & format ) + + + setPosition + setPosition + ( int pos, MoveMode m = MoveAnchor ) + + + operator!= + operator-not-eq + ( const QTextCursor & other ) + + + operator< + operator-lt + ( const QTextCursor & other ) + + + operator<= + operator-lt-eq + ( const QTextCursor & other ) + + + operator= + operator-eq + ( const QTextCursor & cursor ) + + + operator== + operator-eq-eq + ( const QTextCursor & other ) + + + operator> + operator-gt + ( const QTextCursor & other ) + + + operator>= + operator-gt-eq + ( const QTextCursor & other ) + + + + QTextDecoder + qtextdecoder.html + + QTextDecoder + QTextDecoder + ( const QTextCodec * codec ) + + + toUnicode + toUnicode + ( const char * chars, int len ) + + + toUnicode + toUnicode-2 + ( QString * target, const char * chars, int len ) + + + toUnicode + toUnicode-3 + ( const QByteArray & ba ) + + + + QTextDocument + qtextdocument.html + + MetaInformation + MetaInformation-enum + + + + ResourceType + ResourceType-enum + + + + QTextDocument + QTextDocument + ( QObject * parent = 0 ) + + + QTextDocument + QTextDocument-2 + ( const QString & text, QObject * parent = 0 ) + + + addResource + addResource + ( int type, const QUrl & name, const QVariant & resource ) + + + adjustSize + adjustSize + () + + + allFormats + allFormats + () + + + begin + begin + () + + + blockCountChanged + blockCountChanged + ( int newBlockCount ) + + + clear + clear + () + + + clone + clone + ( QObject * parent = 0 ) + + + contentsChange + contentsChange + ( int position, int charsRemoved, int charsAdded ) + + + contentsChanged + contentsChanged + () + + + createObject + createObject + ( const QTextFormat & format ) + + + cursorPositionChanged + cursorPositionChanged + ( const QTextCursor & cursor ) + + + documentLayout + documentLayout + () + + + drawContents + drawContents + ( QPainter * p, const QRectF & rect = QRectF() + + + end + end + () + + + find + find + ( const QString & subString, const QTextCursor & cursor, FindFlags options = 0 ) + + + find + find-2 + ( const QRegExp & expr, const QTextCursor & cursor, FindFlags options = 0 ) + + + find + find-3 + ( const QString & subString, int position = 0, FindFlags options = 0 ) + + + find + find-4 + ( const QRegExp & expr, int position = 0, FindFlags options = 0 ) + + + findBlock + findBlock + ( int pos ) + + + idealWidth + idealWidth + () + + + isEmpty + isEmpty + () + + + isRedoAvailable + isRedoAvailable + () + + + isUndoAvailable + isUndoAvailable + () + + + loadResource + loadResource + ( int type, const QUrl & name ) + + + markContentsDirty + markContentsDirty + ( int position, int length ) + + + metaInformation + metaInformation + ( MetaInformation info ) + + + modificationChanged + modificationChanged + ( bool changed ) + + + object + object + ( int objectIndex ) + + + objectForFormat + objectForFormat + ( const QTextFormat & f ) + + + pageCount + pageCount + () + + + print + print + ( QPrinter * printer ) + + + redo + redo + ( QTextCursor * cursor ) + + + redo + redo-2 + () + + + redoAvailable + redoAvailable + ( bool available ) + + + resource + resource + ( int type, const QUrl & name ) + + + rootFrame + rootFrame + () + + + setDocumentLayout + setDocumentLayout + ( QAbstractTextDocumentLayout * layout ) + + + setHtml + setHtml + ( const QString & html ) + + + setMetaInformation + setMetaInformation + ( MetaInformation info, const QString & string ) + + + setPlainText + setPlainText + ( const QString & text ) + + + toHtml + toHtml + ( const QByteArray & encoding = QByteArray() + + + toPlainText + toPlainText + () + + + undo + undo + ( QTextCursor * cursor ) + + + undo + undo-2 + () + + + undoAvailable + undoAvailable + ( bool available ) + + + + QTextDocumentFragment + qtextdocumentfragment.html + + QTextDocumentFragment + QTextDocumentFragment + () + + + QTextDocumentFragment + QTextDocumentFragment-2 + ( const QTextDocument * document ) + + + QTextDocumentFragment + QTextDocumentFragment-3 + ( const QTextCursor & cursor ) + + + QTextDocumentFragment + QTextDocumentFragment-4 + ( const QTextDocumentFragment & other ) + + + fromHtml + fromHtml + ( const QString & text ) + + + fromHtml + fromHtml-2 + ( const QString & text, const QTextDocument * resourceProvider ) + + + fromPlainText + fromPlainText + ( const QString & plainText ) + + + isEmpty + isEmpty + () + + + toHtml + toHtml + ( const QByteArray & encoding ) + + + toHtml + toHtml-2 + () + + + toPlainText + toPlainText + () + + + operator= + operator-eq + ( const QTextDocumentFragment & other ) + + + + QTextEdit::ExtraSelection + qtextedit-extraselection.html + + cursor + cursor-var + + + + format + format-var + + + + KeyboardAction + KeyboardAction-enum + + + + QTextEdit + QTextEdit-4 + ( QWidget * parent, const char * name ) + + + bold + bold + () + + + color + color + () + + + currentColorChanged + currentColorChanged + ( const QColor & color ) + + + currentFontChanged + currentFontChanged + ( const QFont & font ) + + + doKeyboardAction + doKeyboardAction + ( KeyboardAction action ) + + + family + family + () + + + find + find-2 + ( const QString & exp, bool cs, bool wo ) + + + hasSelectedText + hasSelectedText + () + + + insert + insert + ( const QString & text ) + + + isModified + isModified + () + + + isRedoAvailable + isRedoAvailable + () + + + isUndoAvailable + isUndoAvailable + () + + + italic + italic + () + + + moveCursor + moveCursor-2 + ( CursorAction action, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor ) + + + moveCursor + moveCursor-3 + ( CursorAction action, bool select ) + + + pointSize + pointSize + () + + + redo + redo-2 + () + + + selectedText + selectedText + () + + + setBold + setBold + ( bool b ) + + + setColor + setColor + ( const QColor & color ) + + + setFamily + setFamily + ( const QString & family ) + + + setItalic + setItalic + ( bool i ) + + + setModified + setModified + ( bool m = true ) + + + setPointSize + setPointSize + ( int size ) + + + setTextFormat + setTextFormat + ( Qt::TextFormat f ) + + + setUnderline + setUnderline + ( bool b ) + + + sync + sync + () + + + text + text + () + + + TextFormat + textFormat + QTextEdit::textFormat() + + + underline + underline + () + + + undo + undo-2 + () + + + + QTextEdit + qtextedit.html + + CursorAction + CursorAction-enum + + + + LineWrapMode + LineWrapMode-enum + + + + TextInteractionFlags + textInteractionFlags-prop + + + + WrapMode + wordWrapMode-prop + + + + QTextEdit + QTextEdit + ( QWidget * parent = 0 ) + + + QTextEdit + QTextEdit-2 + ( const QString & text, QWidget * parent = 0 ) + + + Alignment + alignment + QTextEdit::alignment() + + + anchorAt + anchorAt + ( const QPoint & pos ) + + + append + append + ( const QString & text ) + + + canInsertFromMimeData + canInsertFromMimeData + ( const QMimeData * source ) + + + canPaste + canPaste + () + + + clear + clear + () + + + contextMenuEvent + contextMenuEvent + ( QContextMenuEvent * event ) + + + copy + copy + () + + + copyAvailable + copyAvailable + ( bool yes ) + + + createMimeDataFromSelection + createMimeDataFromSelection + () + + + createStandardContextMenu + createStandardContextMenu + () + + + currentCharFormat + currentCharFormat + () + + + currentCharFormatChanged + currentCharFormatChanged + ( const QTextCharFormat & f ) + + + currentFont + currentFont + () + + + cursorForPosition + cursorForPosition + ( const QPoint & pos ) + + + cursorPositionChanged + cursorPositionChanged + () + + + cursorRect + cursorRect + ( const QTextCursor & cursor ) + + + cursorRect + cursorRect-2 + () + + + cut + cut + () + + + document + document + () + + + ensureCursorVisible + ensureCursorVisible + () + + + extraSelections + extraSelections + () + + + find + find + ( const QString & exp, QTextDocument::FindFlags options = 0 ) + + + fontFamily + fontFamily + () + + + fontItalic + fontItalic + () + + + fontPointSize + fontPointSize + () + + + fontUnderline + fontUnderline + () + + + fontWeight + fontWeight + () + + + insertFromMimeData + insertFromMimeData + ( const QMimeData * source ) + + + insertHtml + insertHtml + ( const QString & text ) + + + insertPlainText + insertPlainText + ( const QString & text ) + + + loadResource + loadResource + ( int type, const QUrl & name ) + + + mergeCurrentCharFormat + mergeCurrentCharFormat + ( const QTextCharFormat & modifier ) + + + moveCursor + moveCursor + ( QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor ) + + + paste + paste + () + + + print + print + ( QPrinter * printer ) + + + redo + redo + () + + + redoAvailable + redoAvailable + ( bool available ) + + + scrollToAnchor + scrollToAnchor + ( const QString & name ) + + + selectAll + selectAll + () + + + selectionChanged + selectionChanged + () + + + setAlignment + setAlignment + ( Qt::Alignment a ) + + + setCurrentCharFormat + setCurrentCharFormat + ( const QTextCharFormat & format ) + + + setCurrentFont + setCurrentFont + ( const QFont & f ) + + + setDocument + setDocument + ( QTextDocument * document ) + + + setExtraSelections + setExtraSelections + ( const QList<ExtraSelection> & selections ) + + + setFontFamily + setFontFamily + ( const QString & fontFamily ) + + + setFontItalic + setFontItalic + ( bool italic ) + + + setFontPointSize + setFontPointSize + ( qreal s ) + + + setFontUnderline + setFontUnderline + ( bool underline ) + + + setFontWeight + setFontWeight + ( int weight ) + + + setText + setText + ( const QString & text ) + + + setTextColor + setTextColor + ( const QColor & c ) + + + setTextCursor + setTextCursor + ( const QTextCursor & cursor ) + + + textChanged + textChanged + () + + + textColor + textColor + () + + + textCursor + textCursor + () + + + undo + undo + () + + + undoAvailable + undoAvailable + ( bool available ) + + + zoomIn + zoomIn + ( int range = 1 ) + + + zoomOut + zoomOut + ( int range = 1 ) + + + KeyboardAction + KeyboardAction-enum + + + + QTextEdit + QTextEdit-4 + ( QWidget * parent, const char * name ) + + + bold + bold + () + + + color + color + () + + + currentColorChanged + currentColorChanged + ( const QColor & color ) + + + currentFontChanged + currentFontChanged + ( const QFont & font ) + + + doKeyboardAction + doKeyboardAction + ( KeyboardAction action ) + + + family + family + () + + + find + find-2 + ( const QString & exp, bool cs, bool wo ) + + + hasSelectedText + hasSelectedText + () + + + insert + insert + ( const QString & text ) + + + isModified + isModified + () + + + isRedoAvailable + isRedoAvailable + () + + + isUndoAvailable + isUndoAvailable + () + + + italic + italic + () + + + moveCursor + moveCursor-2 + ( CursorAction action, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor ) + + + moveCursor + moveCursor-3 + ( CursorAction action, bool select ) + + + pointSize + pointSize + () + + + redo + redo-2 + () + + + selectedText + selectedText + () + + + setBold + setBold + ( bool b ) + + + setColor + setColor + ( const QColor & color ) + + + setFamily + setFamily + ( const QString & family ) + + + setItalic + setItalic + ( bool i ) + + + setModified + setModified + ( bool m = true ) + + + setPointSize + setPointSize + ( int size ) + + + setTextFormat + setTextFormat + ( Qt::TextFormat f ) + + + setUnderline + setUnderline + ( bool b ) + + + sync + sync + () + + + text + text + () + + + TextFormat + textFormat + QTextEdit::textFormat() + + + underline + underline + () + + + undo + undo-2 + () + + + + QTextEncoder + qtextencoder.html + + QTextEncoder + QTextEncoder + ( const QTextCodec * codec ) + + + fromUnicode + fromUnicode + ( const QString & str ) + + + fromUnicode + fromUnicode-2 + ( const QChar * uc, int len ) + + + fromUnicode + fromUnicode-3 + ( const QString & uc, int & lenInOut ) + + + + QTextFormat + qtextformat.html + + FormatType + FormatType-enum + + + + ObjectTypes + ObjectTypes-enum + + + + Property + Property-enum + + + + QTextFormat + QTextFormat + () + + + QTextFormat + QTextFormat-2 + ( int type ) + + + QTextFormat + QTextFormat-3 + ( const QTextFormat & other ) + + + background + background + () + + + boolProperty + boolProperty + ( int propertyId ) + + + brushProperty + brushProperty + ( int propertyId ) + + + clearBackground + clearBackground + () + + + clearForeground + clearForeground + () + + + clearProperty + clearProperty + ( int propertyId ) + + + colorProperty + colorProperty + ( int propertyId ) + + + doubleProperty + doubleProperty + ( int propertyId ) + + + foreground + foreground + () + + + hasProperty + hasProperty + ( int propertyId ) + + + intProperty + intProperty + ( int propertyId ) + + + isBlockFormat + isBlockFormat + () + + + isCharFormat + isCharFormat + () + + + isFrameFormat + isFrameFormat + () + + + isImageFormat + isImageFormat + () + + + isListFormat + isListFormat + () + + + isTableFormat + isTableFormat + () + + + isValid + isValid + () + + + LayoutDirection + layoutDirection + QTextFormat::layoutDirection() + + + lengthProperty + lengthProperty + ( int propertyId ) + + + lengthVectorProperty + lengthVectorProperty + ( int propertyId ) + + + merge + merge + ( const QTextFormat & other ) + + + objectIndex + objectIndex + () + + + objectType + objectType + () + + + penProperty + penProperty + ( int propertyId ) + + + properties + properties + () + + + property + property + ( int propertyId ) + + + propertyCount + propertyCount + () + + + setBackground + setBackground + ( const QBrush & brush ) + + + setForeground + setForeground + ( const QBrush & brush ) + + + setLayoutDirection + setLayoutDirection + ( Qt::LayoutDirection direction ) + + + setObjectIndex + setObjectIndex + ( int index ) + + + setObjectType + setObjectType + ( int type ) + + + setProperty + setProperty + ( int propertyId, const QVariant & value ) + + + setProperty + setProperty-2 + ( int propertyId, const QVector<QTextLength> & value ) + + + stringProperty + stringProperty + ( int propertyId ) + + + toBlockFormat + toBlockFormat + () + + + toCharFormat + toCharFormat + () + + + toFrameFormat + toFrameFormat + () + + + toImageFormat + toImageFormat + () + + + toListFormat + toListFormat + () + + + toTableFormat + toTableFormat + () + + + type + type + () + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QTextFormat & other ) + + + operator= + operator-eq + ( const QTextFormat & other ) + + + operator== + operator-eq-eq + ( const QTextFormat & other ) + + + + QTextFragment + qtextfragment.html + + QTextFragment + QTextFragment + () + + + QTextFragment + QTextFragment-3 + ( const QTextFragment & other ) + + + charFormat + charFormat + () + + + charFormatIndex + charFormatIndex + () + + + contains + contains + ( int position ) + + + isValid + isValid + () + + + length + length + () + + + position + position + () + + + text + text + () + + + operator!= + operator-not-eq + ( const QTextFragment & other ) + + + operator< + operator-lt + ( const QTextFragment & other ) + + + operator= + operator-eq + ( const QTextFragment & other ) + + + operator== + operator-eq-eq + ( const QTextFragment & other ) + + + + QTextFrame::iterator + qtextframe-iterator.html + + iterator + iterator + () + + + iterator + iterator-3 + ( const iterator & other ) + + + atEnd + atEnd + () + + + currentBlock + currentBlock + () + + + currentFrame + currentFrame + () + + + parentFrame + parentFrame + () + + + operator!= + operator-not-eq + ( const iterator & other ) + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator= + operator-eq + ( const iterator & other ) + + + operator== + operator-eq-eq + ( const iterator & other ) + + + + QTextFrame + qtextframe.html + + Iterator + Iterator-typedef + + + + QTextFrame + QTextFrame + ( QTextDocument * document ) + + + begin + begin + () + + + childFrames + childFrames + () + + + end + end + () + + + firstCursorPosition + firstCursorPosition + () + + + firstPosition + firstPosition + () + + + frameFormat + frameFormat + () + + + lastCursorPosition + lastCursorPosition + () + + + lastPosition + lastPosition + () + + + parentFrame + parentFrame + () + + + setFrameFormat + setFrameFormat + ( const QTextFrameFormat & format ) + + + + QTextFrameFormat + qtextframeformat.html + + BorderStyle + BorderStyle-enum + + + + Position + Position-enum + + + + QTextFrameFormat + QTextFrameFormat + () + + + border + border + () + + + borderBrush + borderBrush + () + + + borderStyle + borderStyle + () + + + bottomMargin + bottomMargin + () + + + height + height + () + + + isValid + isValid + () + + + leftMargin + leftMargin + () + + + margin + margin + () + + + padding + padding + () + + + pageBreakPolicy + pageBreakPolicy + () + + + position + position + () + + + rightMargin + rightMargin + () + + + setBorder + setBorder + ( qreal width ) + + + setBorderBrush + setBorderBrush + ( const QBrush & brush ) + + + setBorderStyle + setBorderStyle + ( BorderStyle style ) + + + setBottomMargin + setBottomMargin + ( qreal margin ) + + + setHeight + setHeight + ( const QTextLength & height ) + + + setHeight + setHeight-2 + ( qreal height ) + + + setLeftMargin + setLeftMargin + ( qreal margin ) + + + setMargin + setMargin + ( qreal margin ) + + + setPadding + setPadding + ( qreal width ) + + + setPageBreakPolicy + setPageBreakPolicy + ( PageBreakFlags policy ) + + + setPosition + setPosition + ( Position policy ) + + + setRightMargin + setRightMargin + ( qreal margin ) + + + setTopMargin + setTopMargin + ( qreal margin ) + + + setWidth + setWidth + ( const QTextLength & width ) + + + setWidth + setWidth-2 + ( qreal width ) + + + topMargin + topMargin + () + + + width + width + () + + + + QTextImageFormat + qtextimageformat.html + + QTextImageFormat + QTextImageFormat + () + + + height + height + () + + + isValid + isValid + () + + + name + name + () + + + setHeight + setHeight + ( qreal height ) + + + setName + setName + ( const QString & name ) + + + setWidth + setWidth + ( qreal width ) + + + width + width + () + + + + QTextInlineObject + qtextinlineobject.html + + QTextInlineObject + QTextInlineObject + ( int i, QTextEngine * e ) + + + ascent + ascent + () + + + descent + descent + () + + + format + format + () + + + formatIndex + formatIndex + () + + + height + height + () + + + isValid + isValid + () + + + rect + rect + () + + + setAscent + setAscent + ( qreal a ) + + + setDescent + setDescent + ( qreal d ) + + + setWidth + setWidth + ( qreal w ) + + + LayoutDirection + textDirection + QTextInlineObject::textDirection() + + + textPosition + textPosition + () + + + width + width + () + + + + QTextIStream + qtextistream.html + + QTextIStream + QTextIStream + ( const QString * string ) + + + QTextIStream + QTextIStream-2 + ( QByteArray * byteArray ) + + + QTextIStream + QTextIStream-3 + ( FILE * file ) + + + + QTextLayout::FormatRange + qtextlayout-formatrange.html + + format + format-var + + + + length + length-var + + + + start + start-var + + + + + QTextLayout + qtextlayout.html + + CursorMode + CursorMode-enum + + + + QTextLayout + QTextLayout + () + + + QTextLayout + QTextLayout-2 + ( const QString & text ) + + + QTextLayout + QTextLayout-3 + ( const QString & text, const QFont & font, QPaintDevice * paintdevice = 0 ) + + + additionalFormats + additionalFormats + () + + + beginLayout + beginLayout + () + + + boundingRect + boundingRect + () + + + cacheEnabled + cacheEnabled + () + + + clearAdditionalFormats + clearAdditionalFormats + () + + + createLine + createLine + () + + + draw + draw + ( QPainter * p, const QPointF & pos, const QVector<FormatRange> & selections = QVector<FormatRange>() + + + drawCursor + drawCursor + ( QPainter * painter, const QPointF & position, int cursorPosition, int width ) + + + drawCursor + drawCursor-2 + ( QPainter * painter, const QPointF & position, int cursorPosition ) + + + endLayout + endLayout + () + + + font + font + () + + + isValidCursorPosition + isValidCursorPosition + ( int pos ) + + + lineAt + lineAt + ( int i ) + + + lineCount + lineCount + () + + + lineForTextPosition + lineForTextPosition + ( int pos ) + + + maximumWidth + maximumWidth + () + + + minimumWidth + minimumWidth + () + + + nextCursorPosition + nextCursorPosition + ( int oldPos, CursorMode mode = SkipCharacters ) + + + position + position + () + + + preeditAreaPosition + preeditAreaPosition + () + + + preeditAreaText + preeditAreaText + () + + + previousCursorPosition + previousCursorPosition + ( int oldPos, CursorMode mode = SkipCharacters ) + + + setAdditionalFormats + setAdditionalFormats + ( const QList<FormatRange> & formatList ) + + + setCacheEnabled + setCacheEnabled + ( bool enable ) + + + setFont + setFont + ( const QFont & font ) + + + setPosition + setPosition + ( const QPointF & p ) + + + setPreeditArea + setPreeditArea + ( int position, const QString & text ) + + + setText + setText + ( const QString & string ) + + + setTextOption + setTextOption + ( const QTextOption & option ) + + + text + text + () + + + textOption + textOption + () + + + + QTextLength + qtextlength.html + + Type + Type-enum + + + + QTextLength + QTextLength + () + + + QTextLength + QTextLength-2 + ( Type type, qreal value ) + + + rawValue + rawValue + () + + + type + type + () + + + value + value + ( qreal maximumLength ) + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QTextLength & other ) + + + operator== + operator-eq-eq + ( const QTextLength & other ) + + + + QTextLine + qtextline.html + + CursorPosition + CursorPosition-enum + + + + Edge + Edge-enum + + + + QTextLine + QTextLine + () + + + ascent + ascent + () + + + cursorToX + cursorToX + ( int * cursorPos, Edge edge = Leading ) + + + cursorToX + cursorToX-2 + ( int cursorPos, Edge edge = Leading ) + + + descent + descent + () + + + draw + draw + ( QPainter * painter, const QPointF & position, const QTextLayout::FormatRange * selection = 0 ) + + + height + height + () + + + isValid + isValid + () + + + lineNumber + lineNumber + () + + + naturalTextRect + naturalTextRect + () + + + naturalTextWidth + naturalTextWidth + () + + + position + position + () + + + rect + rect + () + + + setLineWidth + setLineWidth + ( qreal width ) + + + setNumColumns + setNumColumns + ( int numColumns ) + + + setNumColumns + setNumColumns-2 + ( int numColumns, qreal alignmentWidth ) + + + setPosition + setPosition + ( const QPointF & pos ) + + + textLength + textLength + () + + + textStart + textStart + () + + + width + width + () + + + x + x + () + + + xToCursor + xToCursor + ( qreal x, CursorPosition cpos = CursorBetweenCharacters ) + + + y + y + () + + + isEmpty + isEmpty + () + + + + QTextList + qtextlist.html + + add + add + ( const QTextBlock & block ) + + + count + count + () + + + format + format + () + + + item + item + ( int i ) + + + itemNumber + itemNumber + ( const QTextBlock & block ) + + + itemText + itemText + ( const QTextBlock & block ) + + + remove + remove + ( const QTextBlock & block ) + + + removeItem + removeItem + ( int i ) + + + setFormat + setFormat + ( const QTextListFormat & format ) + + + + QTextListFormat + qtextlistformat.html + + Style + Style-enum + + + + QTextListFormat + QTextListFormat + () + + + indent + indent + () + + + isValid + isValid + () + + + setIndent + setIndent + ( int indentation ) + + + setStyle + setStyle + ( Style style ) + + + style + style + () + + + + QTextObject + qtextobject.html + + QTextObject + QTextObject + ( QTextDocument * document ) + + + document + document + () + + + format + format + () + + + formatIndex + formatIndex + () + + + objectIndex + objectIndex + () + + + setFormat + setFormat + ( const QTextFormat & format ) + + + + QTextOption + qtextoption.html + + WrapMode + WrapMode-enum + + + + QTextOption + QTextOption + () + + + QTextOption + QTextOption-2 + ( Qt::Alignment alignment ) + + + QTextOption + QTextOption-3 + ( const QTextOption & other ) + + + Alignment + alignment + QTextOption::alignment() + + + flags + flags + () + + + setAlignment + setAlignment + ( Qt::Alignment alignment ) + + + setFlags + setFlags + ( Flags flags ) + + + setTabArray + setTabArray + ( QList<qreal> tabStops ) + + + setTabStop + setTabStop + ( qreal tabStop ) + + + setTextDirection + setTextDirection + ( Qt::LayoutDirection direction ) + + + setUseDesignMetrics + setUseDesignMetrics + ( bool enable ) + + + setWrapMode + setWrapMode + ( WrapMode mode ) + + + tabArray + tabArray + () + + + tabStop + tabStop + () + + + LayoutDirection + textDirection + QTextOption::textDirection() + + + useDesignMetrics + useDesignMetrics + () + + + wrapMode + wrapMode + () + + + operator= + operator-eq + ( const QTextOption & other ) + + + + QTextOStream + qtextostream.html + + QTextOStream + QTextOStream + ( QString * string ) + + + QTextOStream + QTextOStream-2 + ( QByteArray * byteArray ) + + + QTextOStream + QTextOStream-3 + ( FILE * file ) + + + Encoding + Encoding-enum + + + + fill + fill + ( int f ) + + + flags + flags + () + + + flags + flags-2 + ( int f ) + + + precision + precision + ( int p ) + + + read + read-2 + () + + + setEncoding + setEncoding + ( Encoding encoding ) + + + setf + setf + ( int bits ) + + + setf + setf-2 + ( int bits, int mask ) + + + unsetDevice + unsetDevice + () + + + unsetf + unsetf + ( int bits ) + + + width + width + ( int w ) + + + adjustfield + adjustfield-var + + + + basefield + basefield-var + + + + bin + bin-var + + + + dec + dec-var + + + + fixed + fixed-var + + + + floatfield + floatfield-var + + + + hex + hex-var + + + + internal + internal-var + + + + left + left-var + + + + oct + oct-var + + + + right + right-var + + + + scientific + scientific-var + + + + showbase + showbase-var + + + + showpoint + showpoint-var + + + + showpos + showpos-var + + + + skipws + skipws-var + + + + uppercase + uppercase-var + + + + + QTextStream + qtextstream.html + + FieldAlignment + FieldAlignment-enum + + + + RealNumberNotation + RealNumberNotation-enum + + + + Status + Status-enum + + + + QTextStream + QTextStream + () + + + QTextStream + QTextStream-2 + ( QIODevice * device ) + + + QTextStream + QTextStream-3 + ( FILE * fileHandle, QIODevice::OpenMode openMode = QIODevice::ReadWrite ) + + + QTextStream + QTextStream-4 + ( QString * string, QIODevice::OpenMode openMode = QIODevice::ReadWrite ) + + + QTextStream + QTextStream-5 + ( QByteArray * array, QIODevice::OpenMode openMode = QIODevice::ReadWrite ) + + + QTextStream + QTextStream-6 + ( const QByteArray & array, QIODevice::OpenMode openMode = QIODevice::ReadOnly ) + + + atEnd + atEnd + () + + + autoDetectUnicode + autoDetectUnicode + () + + + codec + codec + () + + + device + device + () + + + fieldAlignment + fieldAlignment + () + + + fieldWidth + fieldWidth + () + + + flush + flush + () + + + generateByteOrderMark + generateByteOrderMark + () + + + integerBase + integerBase + () + + + numberFlags + numberFlags + () + + + padChar + padChar + () + + + pos + pos + () + + + read + read + ( qint64 maxlen ) + + + readAll + readAll + () + + + readLine + readLine + ( qint64 maxlen = 0 ) + + + realNumberNotation + realNumberNotation + () + + + realNumberPrecision + realNumberPrecision + () + + + reset + reset + () + + + resetStatus + resetStatus + () + + + seek + seek + ( qint64 pos ) + + + setAutoDetectUnicode + setAutoDetectUnicode + ( bool enabled ) + + + setCodec + setCodec + ( QTextCodec * codec ) + + + setCodec + setCodec-2 + ( const char * codecName ) + + + setDevice + setDevice + ( QIODevice * device ) + + + setFieldAlignment + setFieldAlignment + ( FieldAlignment mode ) + + + setFieldWidth + setFieldWidth + ( int width ) + + + setGenerateByteOrderMark + setGenerateByteOrderMark + ( bool generate ) + + + setIntegerBase + setIntegerBase + ( int base ) + + + setNumberFlags + setNumberFlags + ( NumberFlags flags ) + + + setPadChar + setPadChar + ( QChar ch ) + + + setRealNumberNotation + setRealNumberNotation + ( RealNumberNotation notation ) + + + setRealNumberPrecision + setRealNumberPrecision + ( int precision ) + + + setStatus + setStatus + ( Status status ) + + + setString + setString + ( QString * string, QIODevice::OpenMode openMode = QIODevice::ReadWrite ) + + + skipWhiteSpace + skipWhiteSpace + () + + + status + status + () + + + string + string + () + + + operator<< + operator-lt-lt + ( QChar c ) + + + operator<< + operator-lt-lt-2 + ( signed short i ) + + + operator<< + operator-lt-lt-3 + ( float f ) + + + operator<< + operator-lt-lt-4 + ( const QString & string ) + + + operator<< + operator-lt-lt-6 + ( char c ) + + + operator<< + operator-lt-lt-7 + ( unsigned short i ) + + + operator<< + operator-lt-lt-8 + ( signed int i ) + + + operator<< + operator-lt-lt-9 + ( unsigned int i ) + + + operator<< + operator-lt-lt-10 + ( signed long i ) + + + operator<< + operator-lt-lt-11 + ( unsigned long i ) + + + operator<< + operator-lt-lt-12 + ( qlonglong i ) + + + operator<< + operator-lt-lt-13 + ( qulonglong i ) + + + operator<< + operator-lt-lt-14 + ( double f ) + + + operator<< + operator-lt-lt-15 + ( const QByteArray & array ) + + + operator<< + operator-lt-lt-16 + ( const char * string ) + + + operator<< + operator-lt-lt-17 + ( const void * ptr ) + + + operator>> + operator-gt-gt + ( QChar & c ) + + + operator>> + operator-gt-gt-2 + ( signed short & i ) + + + operator>> + operator-gt-gt-3 + ( float & f ) + + + operator>> + operator-gt-gt-4 + ( QString & str ) + + + operator>> + operator-gt-gt-5 + ( char & c ) + + + operator>> + operator-gt-gt-6 + ( unsigned short & i ) + + + operator>> + operator-gt-gt-7 + ( signed int & i ) + + + operator>> + operator-gt-gt-8 + ( unsigned int & i ) + + + operator>> + operator-gt-gt-9 + ( signed long & i ) + + + operator>> + operator-gt-gt-10 + ( unsigned long & i ) + + + operator>> + operator-gt-gt-11 + ( qlonglong & i ) + + + operator>> + operator-gt-gt-12 + ( qulonglong & i ) + + + operator>> + operator-gt-gt-13 + ( double & f ) + + + operator>> + operator-gt-gt-14 + ( QByteArray & array ) + + + operator>> + operator-gt-gt-15 + ( char * c ) + + + Encoding + Encoding-enum + + + + fill + fill + ( int f ) + + + flags + flags + () + + + flags + flags-2 + ( int f ) + + + precision + precision + ( int p ) + + + read + read-2 + () + + + setEncoding + setEncoding + ( Encoding encoding ) + + + setf + setf + ( int bits ) + + + setf + setf-2 + ( int bits, int mask ) + + + unsetDevice + unsetDevice + () + + + unsetf + unsetf + ( int bits ) + + + width + width + ( int w ) + + + adjustfield + adjustfield-var + + + + basefield + basefield-var + + + + bin + bin-var + + + + dec + dec-var + + + + fixed + fixed-var + + + + floatfield + floatfield-var + + + + hex + hex-var + + + + internal + internal-var + + + + left + left-var + + + + oct + oct-var + + + + right + right-var + + + + scientific + scientific-var + + + + showbase + showbase-var + + + + showpoint + showpoint-var + + + + showpos + showpos-var + + + + skipws + skipws-var + + + + uppercase + uppercase-var + + + + + QTextTable + qtexttable.html + + cellAt + cellAt + ( int row, int column ) + + + cellAt + cellAt-2 + ( int position ) + + + cellAt + cellAt-3 + ( const QTextCursor & cursor ) + + + columns + columns + () + + + format + format + () + + + insertColumns + insertColumns + ( int index, int columns ) + + + insertRows + insertRows + ( int index, int rows ) + + + mergeCells + mergeCells + ( int row, int column, int numRows, int numCols ) + + + mergeCells + mergeCells-2 + ( const QTextCursor & cursor ) + + + removeColumns + removeColumns + ( int index, int columns ) + + + removeRows + removeRows + ( int index, int rows ) + + + resize + resize + ( int rows, int columns ) + + + rowEnd + rowEnd + ( const QTextCursor & cursor ) + + + rowStart + rowStart + ( const QTextCursor & cursor ) + + + rows + rows + () + + + setFormat + setFormat + ( const QTextTableFormat & format ) + + + splitCell + splitCell + ( int row, int column, int numRows, int numCols ) + + + + QTextTableCell + qtexttablecell.html + + QTextTableCell + QTextTableCell + () + + + QTextTableCell + QTextTableCell-2 + ( const QTextTableCell & other ) + + + iterator + begin + QTextTableCell::begin() + + + column + column + () + + + columnSpan + columnSpan + () + + + iterator + end + QTextTableCell::end() + + + firstCursorPosition + firstCursorPosition + () + + + format + format + () + + + isValid + isValid + () + + + lastCursorPosition + lastCursorPosition + () + + + row + row + () + + + rowSpan + rowSpan + () + + + setFormat + setFormat + ( const QTextCharFormat & format ) + + + operator!= + operator-not-eq + ( const QTextTableCell & other ) + + + operator= + operator-eq + ( const QTextTableCell & other ) + + + operator== + operator-eq-eq + ( const QTextTableCell & other ) + + + + QTextTableFormat + qtexttableformat.html + + QTextTableFormat + QTextTableFormat + () + + + Alignment + alignment + QTextTableFormat::alignment() + + + cellPadding + cellPadding + () + + + cellSpacing + cellSpacing + () + + + clearColumnWidthConstraints + clearColumnWidthConstraints + () + + + columnWidthConstraints + columnWidthConstraints + () + + + columns + columns + () + + + headerRowCount + headerRowCount + () + + + isValid + isValid + () + + + setAlignment + setAlignment + ( Qt::Alignment alignment ) + + + setCellPadding + setCellPadding + ( qreal padding ) + + + setCellSpacing + setCellSpacing + ( qreal spacing ) + + + setColumnWidthConstraints + setColumnWidthConstraints + ( const QVector<QTextLength> & constraints ) + + + setHeaderRowCount + setHeaderRowCount + ( int count ) + + + finished + finished-2 + () + + + running + running + () + + + + QThread + qthread.html + + Priority + Priority-enum + + + + QThread + QThread + ( QObject * parent = 0 ) + + + currentThread + currentThread + () + + + HANDLE + currentThreadId + QThread::currentThreadId() + + + exec + exec + () + + + exit + exit + ( int returnCode = 0 ) + + + finished + finished + () + + + idealThreadCount + idealThreadCount + () + + + isFinished + isFinished + () + + + isRunning + isRunning + () + + + msleep + msleep + ( unsigned long msecs ) + + + priority + priority + () + + + quit + quit + () + + + run + run + () + + + setPriority + setPriority + ( Priority priority ) + + + setStackSize + setStackSize + ( uint stackSize ) + + + setTerminationEnabled + setTerminationEnabled + ( bool enabled = true ) + + + sleep + sleep + ( unsigned long secs ) + + + stackSize + stackSize + () + + + start + start + ( Priority priority = InheritPriority ) + + + started + started + () + + + terminate + terminate + () + + + terminated + terminated + () + + + usleep + usleep + ( unsigned long usecs ) + + + wait + wait + ( unsigned long time = ULONG_MAX ) + + + finished + finished-2 + () + + + running + running + () + + + + QThreadStorage + qthreadstorage.html + + QThreadStorage + QThreadStorage + () + + + hasLocalData + hasLocalData + () + + + localData + localData + () + + + localData + localData-2 + () + + + setLocalData + setLocalData + ( T data ) + + + currentTime + currentTime-2 + ( Qt::TimeSpec specification ) + + + + QTime + qtime.html + + QTime + QTime + () + + + QTime + QTime-2 + ( int h, int m, int s = 0, int ms = 0 ) + + + addMSecs + addMSecs + ( int ms ) + + + addSecs + addSecs + ( int s ) + + + currentTime + currentTime + () + + + elapsed + elapsed + () + + + fromString + fromString + ( const QString & string, Qt::DateFormat format = Qt::TextDate ) + + + fromString + fromString-2 + ( const QString & string, const QString & format ) + + + hour + hour + () + + + isNull + isNull + () + + + isValid + isValid + () + + + isValid + isValid-2 + ( int h, int m, int s, int ms = 0 ) + + + minute + minute + () + + + msec + msec + () + + + msecsTo + msecsTo + ( const QTime & t ) + + + restart + restart + () + + + second + second + () + + + secsTo + secsTo + ( const QTime & t ) + + + setHMS + setHMS + ( int h, int m, int s, int ms = 0 ) + + + start + start + () + + + toString + toString + ( const QString & format ) + + + toString + toString-2 + ( Qt::DateFormat f = Qt::TextDate ) + + + operator!= + operator-not-eq + ( const QTime & t ) + + + operator< + operator-lt + ( const QTime & t ) + + + operator<= + operator-lt-eq + ( const QTime & t ) + + + operator== + operator-eq-eq + ( const QTime & t ) + + + operator> + operator-gt + ( const QTime & t ) + + + operator>= + operator-gt-eq + ( const QTime & t ) + + + currentTime + currentTime-2 + ( Qt::TimeSpec specification ) + + + + QTimeEdit + qtimeedit.html + + QTimeEdit + QTimeEdit + ( QWidget * parent = 0 ) + + + QTimeEdit + QTimeEdit-2 + ( const QTime & time, QWidget * parent = 0 ) + + + + QTimeLine + qtimeline.html + + CurveShape + CurveShape-enum + + + + Direction + Direction-enum + + + + State + State-enum + + + + QTimeLine + QTimeLine + ( int duration = 1000, QObject * parent = 0 ) + + + currentFrame + currentFrame + () + + + currentValue + currentValue + () + + + endFrame + endFrame + () + + + finished + finished + () + + + frameChanged + frameChanged + ( int frame ) + + + frameForTime + frameForTime + ( int msec ) + + + resume + resume + () + + + setEndFrame + setEndFrame + ( int frame ) + + + setFrameRange + setFrameRange + ( int startFrame, int endFrame ) + + + setPaused + setPaused + ( bool paused ) + + + setStartFrame + setStartFrame + ( int frame ) + + + start + start + () + + + startFrame + startFrame + () + + + state + state + () + + + stateChanged + stateChanged + ( QTimeLine::State newState ) + + + stop + stop + () + + + toggleDirection + toggleDirection + () + + + valueChanged + valueChanged + ( qreal value ) + + + valueForTime + valueForTime + ( int msec ) + + + QTimer + QTimer-2 + ( QObject * parent, const char * name ) + + + changeInterval + changeInterval + ( int msec ) + + + start + start-3 + ( int msec, bool sshot ) + + + + QTimer + qtimer.html + + QTimer + QTimer + ( QObject * parent = 0 ) + + + singleShot + singleShot + ( int msec, QObject * receiver, const char * member ) + + + start + start + ( int msec ) + + + start + start-2 + () + + + stop + stop + () + + + timeout + timeout + () + + + timerId + timerId + () + + + QTimer + QTimer-2 + ( QObject * parent, const char * name ) + + + changeInterval + changeInterval + ( int msec ) + + + start + start-3 + ( int msec, bool sshot ) + + + + QTimerEvent + qtimerevent.html + + QTimerEvent + QTimerEvent + ( int timerId ) + + + timerId + timerId + () + + + QToolBar + QToolBar-3 + ( QWidget * parent, const char * name ) + + + label + label + () + + + setLabel + setLabel + ( const QString & label ) + + + + QToolBar + qtoolbar.html + + ToolBarAreas + allowedAreas-prop + + + + Orientation + orientation-prop + + + + ToolButtonStyle + toolButtonStyle-prop + + + + QToolBar + QToolBar + ( const QString & title, QWidget * parent = 0 ) + + + QToolBar + QToolBar-2 + ( QWidget * parent = 0 ) + + + actionAt + actionAt + ( const QPoint & p ) + + + actionAt + actionAt-2 + ( int x, int y ) + + + actionTriggered + actionTriggered + ( QAction * action ) + + + addAction + addAction + ( const QString & text ) + + + addAction + addAction-2 + ( const QIcon & icon, const QString & text ) + + + addAction + addAction-3 + ( const QString & text, const QObject * receiver, const char * member ) + + + addAction + addAction-4 + ( const QIcon & icon, const QString & text, const QObject * receiver, const char * member ) + + + addSeparator + addSeparator + () + + + addWidget + addWidget + ( QWidget * widget ) + + + allowedAreasChanged + allowedAreasChanged + ( Qt::ToolBarAreas allowedAreas ) + + + clear + clear + () + + + iconSizeChanged + iconSizeChanged + ( const QSize & iconSize ) + + + insertSeparator + insertSeparator + ( QAction * before ) + + + insertWidget + insertWidget + ( QAction * before, QWidget * widget ) + + + isAreaAllowed + isAreaAllowed + ( Qt::ToolBarArea area ) + + + movableChanged + movableChanged + ( bool movable ) + + + orientationChanged + orientationChanged + ( Qt::Orientation orientation ) + + + toggleViewAction + toggleViewAction + () + + + toolButtonStyleChanged + toolButtonStyleChanged + ( Qt::ToolButtonStyle toolButtonStyle ) + + + widgetForAction + widgetForAction + ( QAction * action ) + + + QToolBar + QToolBar-3 + ( QWidget * parent, const char * name ) + + + label + label + () + + + setLabel + setLabel + ( const QString & label ) + + + QToolBox + QToolBox-2 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + currentItem + currentItem + () + + + item + item + ( int index ) + + + itemIconSet + itemIconSet + ( int index ) + + + itemLabel + itemLabel + ( int index ) + + + margin + margin + () + + + removeItem + removeItem-2 + ( QWidget * widget ) + + + setCurrentItem + setCurrentItem + ( QWidget * widget ) + + + setItemIconSet + setItemIconSet + ( int index, const QIcon & icon ) + + + setItemLabel + setItemLabel + ( int index, const QString & text ) + + + setMargin + setMargin + ( int margin ) + + + + QToolBox + qtoolbox.html + + QToolBox + QToolBox + ( QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + addItem + addItem + ( QWidget * widget, const QIcon & iconSet, const QString & text ) + + + addItem + addItem-2 + ( QWidget * w, const QString & text ) + + + currentChanged + currentChanged + ( int index ) + + + currentWidget + currentWidget + () + + + indexOf + indexOf + ( QWidget * widget ) + + + insertItem + insertItem + ( int index, QWidget * widget, const QIcon & icon, const QString & text ) + + + insertItem + insertItem-2 + ( int index, QWidget * widget, const QString & text ) + + + isItemEnabled + isItemEnabled + ( int index ) + + + itemIcon + itemIcon + ( int index ) + + + itemInserted + itemInserted + ( int index ) + + + itemRemoved + itemRemoved + ( int index ) + + + itemText + itemText + ( int index ) + + + itemToolTip + itemToolTip + ( int index ) + + + removeItem + removeItem + ( int index ) + + + setCurrentWidget + setCurrentWidget + ( QWidget * widget ) + + + setItemEnabled + setItemEnabled + ( int index, bool enabled ) + + + setItemIcon + setItemIcon + ( int index, const QIcon & icon ) + + + setItemText + setItemText + ( int index, const QString & text ) + + + setItemToolTip + setItemToolTip + ( int index, const QString & toolTip ) + + + widget + widget + ( int index ) + + + QToolBox + QToolBox-2 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + currentItem + currentItem + () + + + item + item + ( int index ) + + + itemIconSet + itemIconSet + ( int index ) + + + itemLabel + itemLabel + ( int index ) + + + margin + margin + () + + + removeItem + removeItem-2 + ( QWidget * widget ) + + + setCurrentItem + setCurrentItem + ( QWidget * widget ) + + + setItemIconSet + setItemIconSet + ( int index, const QIcon & icon ) + + + setItemLabel + setItemLabel + ( int index, const QString & text ) + + + setMargin + setMargin + ( int margin ) + + + TextPosition + TextPosition-enum + + + + QToolButton + QToolButton-3 + ( QWidget * parent, const char * name ) + + + QToolButton + QToolButton-4 + ( Qt::ArrowType type, QWidget * parent, const char * name ) + + + QToolButton + QToolButton-5 + ( const QIcon & icon, const QString & textLabel, const QString & statusTip, QObject * receiver, const char * slot, QWidget * parent, const char * name = 0 ) + + + iconSet + iconSet + () + + + iconSet + iconSet-2 + ( bool on ) + + + offIconSet + offIconSet + () + + + onIconSet + onIconSet + () + + + openPopup + openPopup + () + + + popup + popup + () + + + popupDelay + popupDelay + () + + + setIconSet + setIconSet + ( const QIcon & icon ) + + + setIconSet + setIconSet-2 + ( const QIcon & set, bool on ) + + + setOffIconSet + setOffIconSet + ( const QIcon & set ) + + + setOnIconSet + setOnIconSet + ( const QIcon & set ) + + + setPixmap + setPixmap + ( const QPixmap & pixmap ) + + + setPopup + setPopup + ( QMenu * popup ) + + + setPopupDelay + setPopupDelay + ( int delay ) + + + setTextLabel + setTextLabel + ( const QString & text, bool tooltip = true ) + + + setTextPosition + setTextPosition + ( QToolButton::TextPosition pos ) + + + setUsesBigPixmap + setUsesBigPixmap + ( bool enable ) + + + setUsesTextLabel + setUsesTextLabel + ( bool enable ) + + + textLabel + textLabel + () + + + textPosition + textPosition + () + + + usesBigPixmap + usesBigPixmap + () + + + usesTextLabel + usesTextLabel + () + + + + QToolButton + qtoolbutton.html + + ToolButtonPopupMode + ToolButtonPopupMode-enum + + + + ArrowType + arrowType-prop + + + + ToolButtonStyle + toolButtonStyle-prop + + + + QToolButton + QToolButton + ( QWidget * parent = 0 ) + + + defaultAction + defaultAction + () + + + initStyleOption + initStyleOption + ( QStyleOptionToolButton * option ) + + + menu + menu + () + + + paintEvent + paintEvent + ( QPaintEvent * event ) + + + setDefaultAction + setDefaultAction + ( QAction * action ) + + + setMenu + setMenu + ( QMenu * menu ) + + + showMenu + showMenu + () + + + triggered + triggered + ( QAction * action ) + + + TextPosition + TextPosition-enum + + + + QToolButton + QToolButton-3 + ( QWidget * parent, const char * name ) + + + QToolButton + QToolButton-4 + ( Qt::ArrowType type, QWidget * parent, const char * name ) + + + QToolButton + QToolButton-5 + ( const QIcon & icon, const QString & textLabel, const QString & statusTip, QObject * receiver, const char * slot, QWidget * parent, const char * name = 0 ) + + + iconSet + iconSet + () + + + iconSet + iconSet-2 + ( bool on ) + + + offIconSet + offIconSet + () + + + onIconSet + onIconSet + () + + + openPopup + openPopup + () + + + popup + popup + () + + + popupDelay + popupDelay + () + + + setIconSet + setIconSet + ( const QIcon & icon ) + + + setIconSet + setIconSet-2 + ( const QIcon & set, bool on ) + + + setOffIconSet + setOffIconSet + ( const QIcon & set ) + + + setOnIconSet + setOnIconSet + ( const QIcon & set ) + + + setPixmap + setPixmap + ( const QPixmap & pixmap ) + + + setPopup + setPopup + ( QMenu * popup ) + + + setPopupDelay + setPopupDelay + ( int delay ) + + + setTextLabel + setTextLabel + ( const QString & text, bool tooltip = true ) + + + setTextPosition + setTextPosition + ( QToolButton::TextPosition pos ) + + + setUsesBigPixmap + setUsesBigPixmap + ( bool enable ) + + + setUsesTextLabel + setUsesTextLabel + ( bool enable ) + + + textLabel + textLabel + () + + + textPosition + textPosition + () + + + usesBigPixmap + usesBigPixmap + () + + + usesTextLabel + usesTextLabel + () + + + add + add + ( QWidget * widget, const QString & text ) + + + add + add-2 + ( QWidget * widget, const QRect & rect, const QString & text ) + + + remove + remove + ( QWidget * widget ) + + + + QToolTip + qtooltip.html + + font + font + () + + + hideText + hideText + () + + + palette + palette + () + + + setFont + setFont + ( const QFont & font ) + + + setPalette + setPalette + ( const QPalette & palette ) + + + showText + showText + ( const QPoint & pos, const QString & text, QWidget * w, const QRect & rect ) + + + showText + showText-2 + ( const QPoint & pos, const QString & text, QWidget * w = 0 ) + + + add + add + ( QWidget * widget, const QString & text ) + + + add + add-2 + ( QWidget * widget, const QRect & rect, const QString & text ) + + + remove + remove + ( QWidget * widget ) + + + + QTransform + qtransform.html + + TransformationType + TransformationType-enum + + + + QTransform + QTransform + () + + + QTransform + QTransform-2 + ( qreal h11, qreal h12, qreal h13, qreal h21, qreal h22, qreal h23, qreal h31, qreal h32, qreal h33 = 1.0 ) + + + QTransform + QTransform-3 + ( qreal h11, qreal h12, qreal h21, qreal h22, qreal dx, qreal dy ) + + + QTransform + QTransform-4 + ( const QMatrix & matrix ) + + + m11 + m11 + () + + + m12 + m12 + () + + + m13 + m13 + () + + + m21 + m21 + () + + + m22 + m22 + () + + + m23 + m23 + () + + + m31 + m31 + () + + + m32 + m32 + () + + + m33 + m33 + () + + + adjoint + adjoint + () + + + det + det + () + + + determinant + determinant + () + + + dx + dx + () + + + dy + dy + () + + + inverted + inverted + ( bool * invertible = 0 ) + + + isAffine + isAffine + () + + + isIdentity + isIdentity + () + + + isInvertible + isInvertible + () + + + isRotating + isRotating + () + + + isScaling + isScaling + () + + + isTranslating + isTranslating + () + + + map + map + ( qreal x, qreal y, qreal * tx, qreal * ty ) + + + map + map-2 + ( const QPointF & p ) + + + map + map-3 + ( const QPoint & point ) + + + map + map-4 + ( const QLine & l ) + + + map + map-5 + ( const QLineF & line ) + + + map + map-6 + ( const QPolygonF & polygon ) + + + map + map-7 + ( const QPolygon & polygon ) + + + map + map-8 + ( const QRegion & region ) + + + map + map-9 + ( const QPainterPath & path ) + + + map + map-10 + ( int x, int y, int * tx, int * ty ) + + + mapRect + mapRect + ( const QRectF & rectangle ) + + + mapRect + mapRect-2 + ( const QRect & rectangle ) + + + mapToPolygon + mapToPolygon + ( const QRect & rectangle ) + + + quadToQuad + quadToQuad + ( const QPolygonF & one, const QPolygonF & two, QTransform & trans ) + + + quadToSquare + quadToSquare + ( const QPolygonF & quad, QTransform & trans ) + + + reset + reset + () + + + rotate + rotate + ( qreal angle, Qt::Axis axis = Qt::ZAxis ) + + + rotateRadians + rotateRadians + ( qreal angle, Qt::Axis axis = Qt::ZAxis ) + + + scale + scale + ( qreal sx, qreal sy ) + + + setMatrix + setMatrix + ( qreal m11, qreal m12, qreal m13, qreal m21, qreal m22, qreal m23, qreal m31, qreal m32, qreal m33 ) + + + shear + shear + ( qreal sh, qreal sv ) + + + squareToQuad + squareToQuad + ( const QPolygonF & quad, QTransform & trans ) + + + toAffine + toAffine + () + + + translate + translate + ( qreal dx, qreal dy ) + + + transposed + transposed + () + + + type + type + () + + + operator + operator-QVariant + QVariant() + + + operator!= + operator-not-eq + ( const QTransform & matrix ) + + + operator* + operator-2a + ( const QTransform & matrix ) + + + operator*= + operator-2a-eq + ( const QTransform & matrix ) + + + operator*= + operator-2a-eq-2 + ( qreal scalar ) + + + operator+= + operator-2b-eq + ( qreal scalar ) + + + operator-= + operator--eq + ( qreal scalar ) + + + operator/= + operator-2f-eq + ( qreal scalar ) + + + operator= + operator-eq + ( const QTransform & matrix ) + + + operator== + operator-eq-eq + ( const QTransform & matrix ) + + + QTranslator + QTranslator-2 + ( QObject * parent, const char * name ) + + + find + find + ( const char * context, const char * sourceText, const char * comment = 0 ) + + + + QTranslator + qtranslator.html + + QTranslator + QTranslator + ( QObject * parent = 0 ) + + + isEmpty + isEmpty + () + + + load + load + ( const QString & filename, const QString & directory = QString() + + + load + load-2 + ( const uchar * data, int len ) + + + translate + translate + ( const char * context, const char * sourceText, const char * comment = 0 ) + + + translate + translate-2 + ( const char * context, const char * sourceText, const char * comment, int n ) + + + QTranslator + QTranslator-2 + ( QObject * parent, const char * name ) + + + find + find + ( const char * context, const char * sourceText, const char * comment = 0 ) + + + sortByColumn + sortByColumn-2 + ( int column ) + + + + QTreeView + qtreeview.html + + QTreeView + QTreeView + ( QWidget * parent = 0 ) + + + collapse + collapse + ( const QModelIndex & index ) + + + collapseAll + collapseAll + () + + + collapsed + collapsed + ( const QModelIndex & index ) + + + columnAt + columnAt + ( int x ) + + + columnCountChanged + columnCountChanged + ( int oldCount, int newCount ) + + + columnMoved + columnMoved + () + + + columnResized + columnResized + ( int column, int oldSize, int newSize ) + + + columnViewportPosition + columnViewportPosition + ( int column ) + + + columnWidth + columnWidth + ( int column ) + + + currentChanged + currentChanged + ( const QModelIndex & current, const QModelIndex & previous ) + + + drawBranches + drawBranches + ( QPainter * painter, const QRect & rect, const QModelIndex & index ) + + + drawRow + drawRow + ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) + + + drawTree + drawTree + ( QPainter * painter, const QRegion & region ) + + + expand + expand + ( const QModelIndex & index ) + + + expandAll + expandAll + () + + + expandToDepth + expandToDepth + ( int depth ) + + + expanded + expanded + ( const QModelIndex & index ) + + + header + header + () + + + hideColumn + hideColumn + ( int column ) + + + horizontalOffset + horizontalOffset + () + + + indexAbove + indexAbove + ( const QModelIndex & index ) + + + indexBelow + indexBelow + ( const QModelIndex & index ) + + + indexRowSizeHint + indexRowSizeHint + ( const QModelIndex & index ) + + + isColumnHidden + isColumnHidden + ( int column ) + + + isExpanded + isExpanded + ( const QModelIndex & index ) + + + isFirstColumnSpanned + isFirstColumnSpanned + ( int row, const QModelIndex & parent ) + + + isRowHidden + isRowHidden + ( int row, const QModelIndex & parent ) + + + moveCursor + moveCursor + ( CursorAction cursorAction, Qt::KeyboardModifiers modifiers ) + + + resizeColumnToContents + resizeColumnToContents + ( int column ) + + + rowHeight + rowHeight + ( const QModelIndex & index ) + + + rowsAboutToBeRemoved + rowsAboutToBeRemoved + ( const QModelIndex & parent, int start, int end ) + + + rowsInserted + rowsInserted + ( const QModelIndex & parent, int start, int end ) + + + rowsRemoved + rowsRemoved + ( const QModelIndex & parent, int start, int end ) + + + scrollContentsBy + scrollContentsBy + ( int dx, int dy ) + + + scrollTo + scrollTo + ( const QModelIndex & index, ScrollHint hint = EnsureVisible ) + + + selectAll + selectAll + () + + + selectionChanged + selectionChanged + ( const QItemSelection & selected, const QItemSelection & deselected ) + + + setColumnHidden + setColumnHidden + ( int column, bool hide ) + + + setColumnWidth + setColumnWidth + ( int column, int width ) + + + setExpanded + setExpanded + ( const QModelIndex & index, bool expanded ) + + + setFirstColumnSpanned + setFirstColumnSpanned + ( int row, const QModelIndex & parent, bool span ) + + + setHeader + setHeader + ( QHeaderView * header ) + + + setRowHidden + setRowHidden + ( int row, const QModelIndex & parent, bool hide ) + + + setSelection + setSelection + ( const QRect & rect, QItemSelectionModel::SelectionFlags command ) + + + showColumn + showColumn + ( int column ) + + + sizeHintForColumn + sizeHintForColumn + ( int column ) + + + sortByColumn + sortByColumn + ( int column, Qt::SortOrder order ) + + + verticalOffset + verticalOffset + () + + + visualRect + visualRect + ( const QModelIndex & index ) + + + visualRegionForSelection + visualRegionForSelection + ( const QItemSelection & selection ) + + + isItemExpanded + isItemExpanded + ( const QTreeWidgetItem * item ) + + + isItemHidden + isItemHidden + ( const QTreeWidgetItem * item ) + + + isItemSelected + isItemSelected + ( const QTreeWidgetItem * item ) + + + items + items + ( const QMimeData * data ) + + + setItemExpanded + setItemExpanded + ( const QTreeWidgetItem * item, bool expand ) + + + setItemHidden + setItemHidden + ( const QTreeWidgetItem * item, bool hide ) + + + setItemSelected + setItemSelected + ( const QTreeWidgetItem * item, bool select ) + + + + QTreeWidget + qtreewidget.html + + QTreeWidget + QTreeWidget + ( QWidget * parent = 0 ) + + + addTopLevelItem + addTopLevelItem + ( QTreeWidgetItem * item ) + + + addTopLevelItems + addTopLevelItems + ( const QList<QTreeWidgetItem *> & items ) + + + clear + clear + () + + + closePersistentEditor + closePersistentEditor + ( QTreeWidgetItem * item, int column = 0 ) + + + collapseItem + collapseItem + ( const QTreeWidgetItem * item ) + + + currentColumn + currentColumn + () + + + currentItem + currentItem + () + + + currentItemChanged + currentItemChanged + ( QTreeWidgetItem * current, QTreeWidgetItem * previous ) + + + dropMimeData + dropMimeData + ( QTreeWidgetItem * parent, int index, const QMimeData * data, Qt::DropAction action ) + + + editItem + editItem + ( QTreeWidgetItem * item, int column = 0 ) + + + expandItem + expandItem + ( const QTreeWidgetItem * item ) + + + findItems + findItems + ( const QString & text, Qt::MatchFlags flags, int column = 0 ) + + + headerItem + headerItem + () + + + indexFromItem + indexFromItem + ( QTreeWidgetItem * item, int column = 0 ) + + + indexOfTopLevelItem + indexOfTopLevelItem + ( QTreeWidgetItem * item ) + + + insertTopLevelItem + insertTopLevelItem + ( int index, QTreeWidgetItem * item ) + + + insertTopLevelItems + insertTopLevelItems + ( int index, const QList<QTreeWidgetItem *> & items ) + + + invisibleRootItem + invisibleRootItem + () + + + isFirstItemColumnSpanned + isFirstItemColumnSpanned + ( const QTreeWidgetItem * item ) + + + itemAbove + itemAbove + ( const QTreeWidgetItem * item ) + + + itemActivated + itemActivated + ( QTreeWidgetItem * item, int column ) + + + itemAt + itemAt + ( const QPoint & p ) + + + itemAt + itemAt-2 + ( int x, int y ) + + + itemBelow + itemBelow + ( const QTreeWidgetItem * item ) + + + itemChanged + itemChanged + ( QTreeWidgetItem * item, int column ) + + + itemClicked + itemClicked + ( QTreeWidgetItem * item, int column ) + + + itemCollapsed + itemCollapsed + ( QTreeWidgetItem * item ) + + + itemDoubleClicked + itemDoubleClicked + ( QTreeWidgetItem * item, int column ) + + + itemEntered + itemEntered + ( QTreeWidgetItem * item, int column ) + + + itemExpanded + itemExpanded + ( QTreeWidgetItem * item ) + + + itemFromIndex + itemFromIndex + ( const QModelIndex & index ) + + + itemPressed + itemPressed + ( QTreeWidgetItem * item, int column ) + + + itemSelectionChanged + itemSelectionChanged + () + + + itemWidget + itemWidget + ( QTreeWidgetItem * item, int column ) + + + mimeData + mimeData + ( const QList<QTreeWidgetItem *> items ) + + + mimeTypes + mimeTypes + () + + + openPersistentEditor + openPersistentEditor + ( QTreeWidgetItem * item, int column = 0 ) + + + removeItemWidget + removeItemWidget + ( QTreeWidgetItem * item, int column ) + + + scrollToItem + scrollToItem + ( const QTreeWidgetItem * item, QAbstractItemView::ScrollHint hint = EnsureVisible ) + + + selectedItems + selectedItems + () + + + setCurrentItem + setCurrentItem + ( QTreeWidgetItem * item ) + + + setCurrentItem + setCurrentItem-2 + ( QTreeWidgetItem * item, int column ) + + + setFirstItemColumnSpanned + setFirstItemColumnSpanned + ( const QTreeWidgetItem * item, bool span ) + + + setHeaderItem + setHeaderItem + ( QTreeWidgetItem * item ) + + + setHeaderLabel + setHeaderLabel + ( const QString & label ) + + + setHeaderLabels + setHeaderLabels + ( const QStringList & labels ) + + + setItemWidget + setItemWidget + ( QTreeWidgetItem * item, int column, QWidget * widget ) + + + sortColumn + sortColumn + () + + + sortItems + sortItems + ( int column, Qt::SortOrder order ) + + + DropActions + supportedDropActions + QTreeWidget::supportedDropActions() + + + takeTopLevelItem + takeTopLevelItem + ( int index ) + + + topLevelItem + topLevelItem + ( int index ) + + + visualItemRect + visualItemRect + ( const QTreeWidgetItem * item ) + + + backgroundColor + backgroundColor + ( int column ) + + + setBackgroundColor + setBackgroundColor + ( int column, const QColor & color ) + + + setTextColor + setTextColor + ( int column, const QColor & color ) + + + textColor + textColor + ( int column ) + + + + QTreeWidgetItem + qtreewidgetitem.html + + ChildIndicatorPolicy + ChildIndicatorPolicy-enum + + + + ItemType + ItemType-enum + + + + QTreeWidgetItem + QTreeWidgetItem + ( int type = Type ) + + + QTreeWidgetItem + QTreeWidgetItem-2 + ( const QStringList & strings, int type = Type ) + + + QTreeWidgetItem + QTreeWidgetItem-3 + ( QTreeWidget * parent, int type = Type ) + + + QTreeWidgetItem + QTreeWidgetItem-4 + ( QTreeWidget * parent, const QStringList & strings, int type = Type ) + + + QTreeWidgetItem + QTreeWidgetItem-5 + ( QTreeWidget * parent, QTreeWidgetItem * preceding, int type = Type ) + + + QTreeWidgetItem + QTreeWidgetItem-6 + ( QTreeWidgetItem * parent, int type = Type ) + + + QTreeWidgetItem + QTreeWidgetItem-7 + ( QTreeWidgetItem * parent, const QStringList & strings, int type = Type ) + + + QTreeWidgetItem + QTreeWidgetItem-8 + ( QTreeWidgetItem * parent, QTreeWidgetItem * preceding, int type = Type ) + + + QTreeWidgetItem + QTreeWidgetItem-9 + ( const QTreeWidgetItem & other ) + + + addChild + addChild + ( QTreeWidgetItem * child ) + + + addChildren + addChildren + ( const QList<QTreeWidgetItem *> & children ) + + + background + background + ( int column ) + + + CheckState + checkState + QTreeWidgetItem::checkState( int column ) + + + child + child + ( int index ) + + + childCount + childCount + () + + + ChildIndicatorPolicy + childIndicatorPolicy + QTreeWidgetItem::childIndicatorPolicy() + + + clone + clone + () + + + columnCount + columnCount + () + + + data + data + ( int column, int role ) + + + ItemFlags + flags + QTreeWidgetItem::flags() + + + font + font + ( int column ) + + + foreground + foreground + ( int column ) + + + icon + icon + ( int column ) + + + indexOfChild + indexOfChild + ( QTreeWidgetItem * child ) + + + insertChild + insertChild + ( int index, QTreeWidgetItem * child ) + + + insertChildren + insertChildren + ( int index, const QList<QTreeWidgetItem *> & children ) + + + isDisabled + isDisabled + () + + + isExpanded + isExpanded + () + + + isFirstColumnSpanned + isFirstColumnSpanned + () + + + isHidden + isHidden + () + + + isSelected + isSelected + () + + + parent + parent + () + + + read + read + ( QDataStream & in ) + + + removeChild + removeChild + ( QTreeWidgetItem * child ) + + + setBackground + setBackground + ( int column, const QBrush & brush ) + + + setCheckState + setCheckState + ( int column, Qt::CheckState state ) + + + setChildIndicatorPolicy + setChildIndicatorPolicy + ( QTreeWidgetItem::ChildIndicatorPolicy policy ) + + + setData + setData + ( int column, int role, const QVariant & value ) + + + setDisabled + setDisabled + ( bool disabled ) + + + setExpanded + setExpanded + ( bool expand ) + + + setFirstColumnSpanned + setFirstColumnSpanned + ( bool span ) + + + setFlags + setFlags + ( Qt::ItemFlags flags ) + + + setFont + setFont + ( int column, const QFont & font ) + + + setForeground + setForeground + ( int column, const QBrush & brush ) + + + setHidden + setHidden + ( bool hide ) + + + setIcon + setIcon + ( int column, const QIcon & icon ) + + + setSelected + setSelected + ( bool select ) + + + setSizeHint + setSizeHint + ( int column, const QSize & size ) + + + setStatusTip + setStatusTip + ( int column, const QString & statusTip ) + + + setText + setText + ( int column, const QString & text ) + + + setTextAlignment + setTextAlignment + ( int column, int alignment ) + + + setToolTip + setToolTip + ( int column, const QString & toolTip ) + + + setWhatsThis + setWhatsThis + ( int column, const QString & whatsThis ) + + + sizeHint + sizeHint + ( int column ) + + + sortChildren + sortChildren + ( int column, Qt::SortOrder order ) + + + statusTip + statusTip + ( int column ) + + + takeChild + takeChild + ( int index ) + + + takeChildren + takeChildren + () + + + text + text + ( int column ) + + + textAlignment + textAlignment + ( int column ) + + + toolTip + toolTip + ( int column ) + + + treeWidget + treeWidget + () + + + type + type + () + + + whatsThis + whatsThis + ( int column ) + + + write + write + ( QDataStream & out ) + + + operator< + operator-lt + ( const QTreeWidgetItem & other ) + + + operator= + operator-eq + ( const QTreeWidgetItem & other ) + + + + QTreeWidgetItemIterator + qtreewidgetitemiterator.html + + QTreeWidgetItemIterator + QTreeWidgetItemIterator + ( const QTreeWidgetItemIterator & it ) + + + QTreeWidgetItemIterator + QTreeWidgetItemIterator-2 + ( QTreeWidget * widget, IteratorFlags flags = All ) + + + QTreeWidgetItemIterator + QTreeWidgetItemIterator-3 + ( QTreeWidgetItem * item, IteratorFlags flags = All ) + + + operator* + operator-2a + () + + + operator++ + operator-2b-2b + () + + + operator++ + operator-2b-2b-2 + ( int ) + + + operator+= + operator-2b-eq + ( int n ) + + + operator-- + operator-- + () + + + operator-- + operator---2 + ( int ) + + + operator-= + operator--eq + ( int n ) + + + operator= + operator-eq + ( const QTreeWidgetItemIterator & it ) + + + + QUdpSocket + qudpsocket.html + + QUdpSocket + QUdpSocket + ( QObject * parent = 0 ) + + + bind + bind + ( const QHostAddress & address, quint16 port ) + + + bind + bind-2 + ( const QHostAddress & address, quint16 port, BindMode mode ) + + + bind + bind-3 + ( quint16 port = 0 ) + + + bind + bind-4 + ( quint16 port, BindMode mode ) + + + hasPendingDatagrams + hasPendingDatagrams + () + + + pendingDatagramSize + pendingDatagramSize + () + + + readDatagram + readDatagram + ( char * data, qint64 maxSize, QHostAddress * address = 0, quint16 * port = 0 ) + + + writeDatagram + writeDatagram + ( const char * data, qint64 size, const QHostAddress & address, quint16 port ) + + + writeDatagram + writeDatagram-2 + ( const QByteArray & datagram, const QHostAddress & host, quint16 port ) + + + + QUiLoader + quiloader.html + + QUiLoader + QUiLoader + ( QObject * parent = 0 ) + + + addPluginPath + addPluginPath + ( const QString & path ) + + + availableWidgets + availableWidgets + () + + + clearPluginPaths + clearPluginPaths + () + + + createAction + createAction + ( QObject * parent = 0, const QString & name = QString() + + + createActionGroup + createActionGroup + ( QObject * parent = 0, const QString & name = QString() + + + createLayout + createLayout + ( const QString & className, QObject * parent = 0, const QString & name = QString() + + + createWidget + createWidget + ( const QString & className, QWidget * parent = 0, const QString & name = QString() + + + load + load + ( QIODevice * device, QWidget * parentWidget = 0 ) + + + pluginPaths + pluginPaths + () + + + setWorkingDirectory + setWorkingDirectory + ( const QDir & dir ) + + + workingDirectory + workingDirectory + () + + + + QUndoCommand + qundocommand.html + + QUndoCommand + QUndoCommand + ( QUndoCommand * parent = 0 ) + + + QUndoCommand + QUndoCommand-2 + ( const QString & text, QUndoCommand * parent = 0 ) + + + id + id + () + + + mergeWith + mergeWith + ( const QUndoCommand * command ) + + + redo + redo + () + + + setText + setText + ( const QString & text ) + + + text + text + () + + + undo + undo + () + + + + QUndoGroup + qundogroup.html + + QUndoGroup + QUndoGroup + ( QObject * parent = 0 ) + + + activeStack + activeStack + () + + + activeStackChanged + activeStackChanged + ( QUndoStack * stack ) + + + addStack + addStack + ( QUndoStack * stack ) + + + canRedo + canRedo + () + + + canRedoChanged + canRedoChanged + ( bool canRedo ) + + + canUndo + canUndo + () + + + canUndoChanged + canUndoChanged + ( bool canUndo ) + + + cleanChanged + cleanChanged + ( bool clean ) + + + createRedoAction + createRedoAction + ( QObject * parent, const QString & prefix = QString() + + + createUndoAction + createUndoAction + ( QObject * parent, const QString & prefix = QString() + + + indexChanged + indexChanged + ( int idx ) + + + isClean + isClean + () + + + redo + redo + () + + + redoText + redoText + () + + + redoTextChanged + redoTextChanged + ( const QString & redoText ) + + + removeStack + removeStack + ( QUndoStack * stack ) + + + setActiveStack + setActiveStack + ( QUndoStack * stack ) + + + stacks + stacks + () + + + undo + undo + () + + + undoText + undoText + () + + + undoTextChanged + undoTextChanged + ( const QString & undoText ) + + + + QUndoStack + qundostack.html + + QUndoStack + QUndoStack + ( QObject * parent = 0 ) + + + beginMacro + beginMacro + ( const QString & text ) + + + canRedo + canRedo + () + + + canRedoChanged + canRedoChanged + ( bool canUndo ) + + + canUndo + canUndo + () + + + canUndoChanged + canUndoChanged + ( bool canUndo ) + + + cleanChanged + cleanChanged + ( bool clean ) + + + cleanIndex + cleanIndex + () + + + clear + clear + () + + + count + count + () + + + createRedoAction + createRedoAction + ( QObject * parent, const QString & prefix = QString() + + + createUndoAction + createUndoAction + ( QObject * parent, const QString & prefix = QString() + + + endMacro + endMacro + () + + + index + index + () + + + indexChanged + indexChanged + ( int idx ) + + + isClean + isClean + () + + + push + push + ( QUndoCommand * cmd ) + + + redo + redo + () + + + redoText + redoText + () + + + redoTextChanged + redoTextChanged + ( const QString & redoText ) + + + setClean + setClean + () + + + setIndex + setIndex + ( int idx ) + + + text + text + ( int idx ) + + + undo + undo + () + + + undoText + undoText + () + + + undoTextChanged + undoTextChanged + ( const QString & undoText ) + + + + QUndoView + qundoview.html + + QUndoView + QUndoView + ( QWidget * parent = 0 ) + + + QUndoView + QUndoView-2 + ( QUndoStack * stack, QWidget * parent = 0 ) + + + QUndoView + QUndoView-3 + ( QUndoGroup * group, QWidget * parent = 0 ) + + + group + group + () + + + setGroup + setGroup + ( QUndoGroup * group ) + + + setStack + setStack + ( QUndoStack * stack ) + + + stack + stack + () + + + fromPunycode + fromPunycode + ( const QByteArray & pc ) + + + toPunycode + toPunycode + ( const QString & uc ) + + + addPath + addPath + ( const QString & p ) + + + cdUp + cdUp + () + + + decode + decode + ( QString & url ) + + + dirPath + dirPath + () + + + encode + encode + ( QString & url ) + + + fileName + fileName + () + + + hasHost + hasHost + () + + + hasPassword + hasPassword + () + + + hasPath + hasPath + () + + + hasPort + hasPort + () + + + hasRef + hasRef + () + + + hasUser + hasUser + () + + + isRelativeUrl + isRelativeUrl + ( const QString & url ) + + + protocol + protocol + () + + + query + query + () + + + ref + ref + () + + + reset + reset + () + + + setFileName + setFileName + ( const QString & txt ) + + + setProtocol + setProtocol + ( const QString & s ) + + + setQuery + setQuery + ( const QString & txt ) + + + setRef + setRef + ( const QString & txt ) + + + setUser + setUser + ( const QString & s ) + + + user + user + () + + + operator + operator-QString + QString() + + + + QUrl + qurl.html + + DataPtr + DataPtr-typedef + + + + ParsingMode + ParsingMode-enum + + + + QUrl + QUrl + () + + + QUrl + QUrl-2 + ( const QString & url ) + + + QUrl + QUrl-3 + ( const QUrl & other ) + + + QUrl + QUrl-4 + ( const QString & url, ParsingMode parsingMode ) + + + addQueryItem + addQueryItem + ( const QString & key, const QString & value ) + + + allQueryItemValues + allQueryItemValues + ( const QString & key ) + + + authority + authority + () + + + clear + clear + () + + + data_ptr + data_ptr + () + + + encodedQuery + encodedQuery + () + + + errorString + errorString + () + + + fragment + fragment + () + + + fromAce + fromAce + ( const QByteArray & domain ) + + + fromEncoded + fromEncoded + ( const QByteArray & input ) + + + fromEncoded + fromEncoded-2 + ( const QByteArray & input, ParsingMode parsingMode ) + + + fromLocalFile + fromLocalFile + ( const QString & localFile ) + + + fromPercentEncoding + fromPercentEncoding + ( const QByteArray & input ) + + + hasFragment + hasFragment + () + + + hasQuery + hasQuery + () + + + hasQueryItem + hasQueryItem + ( const QString & key ) + + + host + host + () + + + idnWhitelist + idnWhitelist + () + + + isEmpty + isEmpty + () + + + isParentOf + isParentOf + ( const QUrl & childUrl ) + + + isRelative + isRelative + () + + + isValid + isValid + () + + + password + password + () + + + path + path + () + + + port + port + () + + + port + port-2 + ( int defaultPort ) + + + queryItemValue + queryItemValue + ( const QString & key ) + + + queryItems + queryItems + () + + + queryPairDelimiter + queryPairDelimiter + () + + + queryValueDelimiter + queryValueDelimiter + () + + + removeAllQueryItems + removeAllQueryItems + ( const QString & key ) + + + removeQueryItem + removeQueryItem + ( const QString & key ) + + + resolved + resolved + ( const QUrl & relative ) + + + scheme + scheme + () + + + setAuthority + setAuthority + ( const QString & authority ) + + + setEncodedQuery + setEncodedQuery + ( const QByteArray & query ) + + + setEncodedUrl + setEncodedUrl + ( const QByteArray & encodedUrl ) + + + setEncodedUrl + setEncodedUrl-2 + ( const QByteArray & encodedUrl, ParsingMode parsingMode ) + + + setFragment + setFragment + ( const QString & fragment ) + + + setHost + setHost + ( const QString & host ) + + + setIdnWhitelist + setIdnWhitelist + ( const QStringList & list ) + + + setPassword + setPassword + ( const QString & password ) + + + setPath + setPath + ( const QString & path ) + + + setPort + setPort + ( int port ) + + + setQueryDelimiters + setQueryDelimiters + ( char valueDelimiter, char pairDelimiter ) + + + setQueryItems + setQueryItems + ( const QList<QPair<QString, QString> > & query ) + + + setScheme + setScheme + ( const QString & scheme ) + + + setUrl + setUrl + ( const QString & url ) + + + setUrl + setUrl-2 + ( const QString & url, ParsingMode parsingMode ) + + + setUserInfo + setUserInfo + ( const QString & userInfo ) + + + setUserName + setUserName + ( const QString & userName ) + + + toAce + toAce + ( const QString & domain ) + + + toEncoded + toEncoded + ( FormattingOptions options = None ) + + + toLocalFile + toLocalFile + () + + + toPercentEncoding + toPercentEncoding + ( const QString & input, const QByteArray & exclude = QByteArray() + + + toString + toString + ( FormattingOptions options = None ) + + + userInfo + userInfo + () + + + userName + userName + () + + + operator!= + operator-not-eq + ( const QUrl & url ) + + + operator= + operator-eq + ( const QUrl & url ) + + + operator= + operator-eq-2 + ( const QString & url ) + + + operator== + operator-eq-eq + ( const QUrl & url ) + + + addPath + addPath + ( const QString & p ) + + + cdUp + cdUp + () + + + decode + decode + ( QString & url ) + + + dirPath + dirPath + () + + + encode + encode + ( QString & url ) + + + fileName + fileName + () + + + hasHost + hasHost + () + + + hasPassword + hasPassword + () + + + hasPath + hasPath + () + + + hasPort + hasPort + () + + + hasRef + hasRef + () + + + hasUser + hasUser + () + + + isRelativeUrl + isRelativeUrl + ( const QString & url ) + + + protocol + protocol + () + + + query + query + () + + + ref + ref + () + + + reset + reset + () + + + setFileName + setFileName + ( const QString & txt ) + + + setProtocol + setProtocol + ( const QString & s ) + + + setQuery + setQuery + ( const QString & txt ) + + + setRef + setRef + ( const QString & txt ) + + + setUser + setUser + ( const QString & s ) + + + user + user + () + + + operator + operator-QString + QString() + + + + QUrlInfo + qurlinfo.html + + PermissionSpec + PermissionSpec-enum + + + + QUrlInfo + QUrlInfo + () + + + QUrlInfo + QUrlInfo-2 + ( const QUrlInfo & ui ) + + + QUrlInfo + QUrlInfo-3 + ( const QString & name, int permissions, const QString & owner, const QString & group, qint64 size, const QDateTime & lastModified, const QDateTime & lastRead, bool isDir, bool isFile, bool isSymLink, bool isWritable, bool isReadable, bool isExecutable ) + + + QUrlInfo + QUrlInfo-4 + ( const QUrl & url, int permissions, const QString & owner, const QString & group, qint64 size, const QDateTime & lastModified, const QDateTime & lastRead, bool isDir, bool isFile, bool isSymLink, bool isWritable, bool isReadable, bool isExecutable ) + + + equal + equal + ( const QUrlInfo & i1, const QUrlInfo & i2, int sortBy ) + + + greaterThan + greaterThan + ( const QUrlInfo & i1, const QUrlInfo & i2, int sortBy ) + + + group + group + () + + + isDir + isDir + () + + + isExecutable + isExecutable + () + + + isFile + isFile + () + + + isReadable + isReadable + () + + + isSymLink + isSymLink + () + + + isValid + isValid + () + + + isWritable + isWritable + () + + + lastModified + lastModified + () + + + lastRead + lastRead + () + + + lessThan + lessThan + ( const QUrlInfo & i1, const QUrlInfo & i2, int sortBy ) + + + name + name + () + + + owner + owner + () + + + permissions + permissions + () + + + setDir + setDir + ( bool b ) + + + setFile + setFile + ( bool b ) + + + setGroup + setGroup + ( const QString & s ) + + + setLastModified + setLastModified + ( const QDateTime & dt ) + + + setName + setName + ( const QString & name ) + + + setOwner + setOwner + ( const QString & s ) + + + setPermissions + setPermissions + ( int p ) + + + setReadable + setReadable + ( bool b ) + + + setSize + setSize + ( qint64 size ) + + + setSymLink + setSymLink + ( bool b ) + + + setWritable + setWritable + ( bool b ) + + + size + size + () + + + operator!= + operator-not-eq + ( const QUrlInfo & other ) + + + operator= + operator-eq + ( const QUrlInfo & ui ) + + + operator== + operator-eq-eq + ( const QUrlInfo & other ) + + + + QUuid + quuid.html + + Variant + Variant-enum + + + + Version + Version-enum + + + + QUuid + QUuid + () + + + QUuid + QUuid-2 + ( uint l, ushort w1, ushort w2, uchar b1, uchar b2, uchar b3, uchar b4, uchar b5, uchar b6, uchar b7, uchar b8 ) + + + QUuid + QUuid-3 + ( const QString & text ) + + + QUuid + QUuid-5 + ( const GUID & guid ) + + + createUuid + createUuid + () + + + isNull + isNull + () + + + toString + toString + () + + + Variant + variant + QUuid::variant() + + + Version + version + QUuid::version() + + + operator + operator-GUID + GUID() + + + operator + operator-QString + QString() + + + operator!= + operator-not-eq + ( const QUuid & other ) + + + operator!= + operator-not-eq-2 + ( const GUID & guid ) + + + operator< + operator-lt + ( const QUuid & other ) + + + operator= + operator-eq + ( const GUID & guid ) + + + operator== + operator-eq-eq + ( const QUuid & other ) + + + operator== + operator-eq-eq-2 + ( const GUID & guid ) + + + operator> + operator-gt + ( const QUuid & other ) + + + QValidator + QValidator-2 + ( QObject * parent, const char * name ) + + + + QValidator + qvalidator.html + + State + State-enum + + + + QValidator + QValidator + ( QObject * parent ) + + + fixup + fixup + ( QString & input ) + + + locale + locale + () + + + setLocale + setLocale + ( const QLocale & locale ) + + + validate + validate + ( QString & input, int & pos ) + + + QValidator + QValidator-2 + ( QObject * parent, const char * name ) + + + QVariant + QVariant-36 + ( bool b, int dummy ) + + + asBitArray + asBitArray + () + + + asBool + asBool + () + + + asByteArray + asByteArray + () + + + asCString + asCString + () + + + asDate + asDate + () + + + asDateTime + asDateTime + () + + + asDouble + asDouble + () + + + asInt + asInt + () + + + asList + asList + () + + + asLongLong + asLongLong + () + + + asMap + asMap + () + + + asPoint + asPoint + () + + + asRect + asRect + () + + + asSize + asSize + () + + + asString + asString + () + + + asStringList + asStringList + () + + + asTime + asTime + () + + + asUInt + asUInt + () + + + asULongLong + asULongLong + () + + + canCast + canCast + ( Type t ) + + + cast + cast + ( Type t ) + + + toCString + toCString + () + + + + QVariant + qvariant.html + + DataPtr + DataPtr-typedef + + + + Type + Type-enum + + + + QVariant + QVariant + () + + + QVariant + QVariant-2 + ( Type type ) + + + QVariant + QVariant-3 + ( int typeOrUserType, const void * copy ) + + + QVariant + QVariant-4 + ( const QVariant & p ) + + + QVariant + QVariant-5 + ( QDataStream & s ) + + + QVariant + QVariant-6 + ( int val ) + + + QVariant + QVariant-7 + ( uint val ) + + + QVariant + QVariant-8 + ( qlonglong val ) + + + QVariant + QVariant-9 + ( qulonglong val ) + + + QVariant + QVariant-10 + ( bool val ) + + + QVariant + QVariant-11 + ( double val ) + + + QVariant + QVariant-12 + ( const char * val ) + + + QVariant + QVariant-13 + ( const QByteArray & val ) + + + QVariant + QVariant-14 + ( const QBitArray & val ) + + + QVariant + QVariant-15 + ( const QString & val ) + + + QVariant + QVariant-16 + ( const QLatin1String & val ) + + + QVariant + QVariant-17 + ( const QStringList & val ) + + + QVariant + QVariant-18 + ( const QChar & c ) + + + QVariant + QVariant-19 + ( const QDate & val ) + + + QVariant + QVariant-20 + ( const QTime & val ) + + + QVariant + QVariant-21 + ( const QDateTime & val ) + + + QVariant + QVariant-22 + ( const QList<QVariant> & val ) + + + QVariant + QVariant-23 + ( const QMap<QString, QVariant> & val ) + + + QVariant + QVariant-24 + ( const QSize & val ) + + + QVariant + QVariant-25 + ( const QSizeF & val ) + + + QVariant + QVariant-26 + ( const QPoint & val ) + + + QVariant + QVariant-27 + ( const QPointF & val ) + + + QVariant + QVariant-28 + ( const QLine & val ) + + + QVariant + QVariant-29 + ( const QLineF & val ) + + + QVariant + QVariant-30 + ( const QRect & val ) + + + QVariant + QVariant-31 + ( const QRectF & val ) + + + QVariant + QVariant-32 + ( const QUrl & val ) + + + QVariant + QVariant-33 + ( const QLocale & l ) + + + QVariant + QVariant-34 + ( const QRegExp & regExp ) + + + QVariant + QVariant-35 + ( Qt::GlobalColor color ) + + + canConvert + canConvert + ( Type t ) + + + canConvert + canConvert-2 + () + + + clear + clear + () + + + convert + convert + ( Type t ) + + + data_ptr + data_ptr + () + + + fromValue + fromValue + ( const T & value ) + + + isNull + isNull + () + + + isValid + isValid + () + + + nameToType + nameToType + ( const char * name ) + + + setValue + setValue + ( const T & value ) + + + toBitArray + toBitArray + () + + + toBool + toBool + () + + + toByteArray + toByteArray + () + + + toChar + toChar + () + + + toDate + toDate + () + + + toDateTime + toDateTime + () + + + toDouble + toDouble + ( bool * ok = 0 ) + + + toInt + toInt + ( bool * ok = 0 ) + + + toLine + toLine + () + + + toLineF + toLineF + () + + + toList + toList + () + + + toLocale + toLocale + () + + + toLongLong + toLongLong + ( bool * ok = 0 ) + + + toMap + toMap + () + + + toPoint + toPoint + () + + + toPointF + toPointF + () + + + toRect + toRect + () + + + toRectF + toRectF + () + + + toRegExp + toRegExp + () + + + toSize + toSize + () + + + toSizeF + toSizeF + () + + + toString + toString + () + + + toStringList + toStringList + () + + + toTime + toTime + () + + + toUInt + toUInt + ( bool * ok = 0 ) + + + toULongLong + toULongLong + ( bool * ok = 0 ) + + + toUrl + toUrl + () + + + type + type + () + + + typeName + typeName + () + + + typeToName + typeToName + ( Type typ ) + + + userType + userType + () + + + value + value + () + + + operator!= + operator-not-eq + ( const QVariant & v ) + + + operator= + operator-eq + ( const QVariant & variant ) + + + operator== + operator-eq-eq + ( const QVariant & v ) + + + QVariant + QVariant-36 + ( bool b, int dummy ) + + + asBitArray + asBitArray + () + + + asBool + asBool + () + + + asByteArray + asByteArray + () + + + asCString + asCString + () + + + asDate + asDate + () + + + asDateTime + asDateTime + () + + + asDouble + asDouble + () + + + asInt + asInt + () + + + asList + asList + () + + + asLongLong + asLongLong + () + + + asMap + asMap + () + + + asPoint + asPoint + () + + + asRect + asRect + () + + + asSize + asSize + () + + + asString + asString + () + + + asStringList + asStringList + () + + + asTime + asTime + () + + + asUInt + asUInt + () + + + asULongLong + asULongLong + () + + + canCast + canCast + ( Type t ) + + + cast + cast + ( Type t ) + + + toCString + toCString + () + + + + QVarLengthArray + qvarlengtharray.html + + QVarLengthArray + QVarLengthArray + ( int size = 0 ) + + + QVarLengthArray + QVarLengthArray-2 + ( const QVarLengthArray<T, Prealloc> & other ) + + + append + append + ( const T & t ) + + + append + append-2 + ( const T * buf, int size ) + + + capacity + capacity + () + + + clear + clear + () + + + constData + constData + () + + + count + count + () + + + data + data + () + + + data + data-2 + () + + + isEmpty + isEmpty + () + + + reserve + reserve + ( int size ) + + + resize + resize + ( int size ) + + + size + size + () + + + operator= + operator-eq + ( const QVarLengthArray<T, Prealloc> & other ) + + + operator[] + operator-5b-5d + ( int i ) + + + operator[] + operator-5b-5d-2 + ( int i ) + + + QVBoxLayout + QVBoxLayout-3 + ( QWidget * parent, int margin, int spacing = -1, const char * name = 0 ) + + + QVBoxLayout + QVBoxLayout-4 + ( QLayout * parentLayout, int spacing = -1, const char * name = 0 ) + + + QVBoxLayout + QVBoxLayout-5 + ( int spacing, const char * name = 0 ) + + + + QVBoxLayout + qvboxlayout.html + + QVBoxLayout + QVBoxLayout + () + + + QVBoxLayout + QVBoxLayout-2 + ( QWidget * parent ) + + + QVBoxLayout + QVBoxLayout-3 + ( QWidget * parent, int margin, int spacing = -1, const char * name = 0 ) + + + QVBoxLayout + QVBoxLayout-4 + ( QLayout * parentLayout, int spacing = -1, const char * name = 0 ) + + + QVBoxLayout + QVBoxLayout-5 + ( int spacing, const char * name = 0 ) + + + + QVector + qvector.html + + ConstIterator + ConstIterator-typedef + + + + Iterator + Iterator-typedef + + + + const_iterator + const_iterator-typedef + + + + const_pointer + const_pointer-typedef + + + + const_reference + const_reference-typedef + + + + difference_type + difference_type-typedef + + + + iterator + iterator-typedefx + + + + pointer + pointer-typedef + + + + reference + reference-typedef + + + + size_type + size_type-typedef + + + + value_type + value_type-typedef + + + + QVector + QVector + () + + + QVector + QVector-2 + ( int size ) + + + QVector + QVector-3 + ( int size, const T & value ) + + + QVector + QVector-4 + ( const QVector<T> & other ) + + + append + append + ( const T & value ) + + + at + at + ( int i ) + + + back + back + () + + + back + back-2 + () + + + begin + begin + () + + + begin + begin-2 + () + + + capacity + capacity + () + + + clear + clear + () + + + constBegin + constBegin + () + + + constData + constData + () + + + constEnd + constEnd + () + + + contains + contains + ( const T & value ) + + + count + count + ( const T & value ) + + + count + count-2 + () + + + data + data + () + + + data + data-2 + () + + + empty + empty + () + + + end + end + () + + + end + end-2 + () + + + erase + erase + ( iterator pos ) + + + erase + erase-2 + ( iterator begin, iterator end ) + + + fill + fill + ( const T & value, int size = -1 ) + + + first + first + () + + + first + first-2 + () + + + fromList + fromList + ( const QList<T> & list ) + + + fromStdVector + fromStdVector + ( const std::vector<T> & vector ) + + + front + front + () + + + front + front-2 + () + + + indexOf + indexOf + ( const T & value, int from = 0 ) + + + insert + insert + ( int i, const T & value ) + + + insert + insert-2 + ( iterator before, int count, const T & value ) + + + insert + insert-3 + ( int i, int count, const T & value ) + + + insert + insert-4 + ( iterator before, const T & value ) + + + isEmpty + isEmpty + () + + + last + last + () + + + last + last-2 + () + + + lastIndexOf + lastIndexOf + ( const T & value, int from = -1 ) + + + mid + mid + ( int pos, int length = -1 ) + + + pop_back + pop_back + () + + + pop_front + pop_front + () + + + prepend + prepend + ( const T & value ) + + + push_back + push_back + ( const T & value ) + + + push_front + push_front + ( const T & value ) + + + remove + remove + ( int i ) + + + remove + remove-2 + ( int i, int count ) + + + replace + replace + ( int i, const T & value ) + + + reserve + reserve + ( int size ) + + + resize + resize + ( int size ) + + + size + size + () + + + squeeze + squeeze + () + + + toList + toList + () + + + vector + toStdVector + <T> QVector::toStdVector() + + + value + value + ( int i ) + + + value + value-2 + ( int i, const T & defaultValue ) + + + operator!= + operator-not-eq + ( const QVector<T> & other ) + + + operator+ + operator-2b + ( const QVector<T> & other ) + + + operator+= + operator-2b-eq + ( const QVector<T> & other ) + + + operator+= + operator-2b-eq-2 + ( const T & value ) + + + operator<< + operator-lt-lt + ( const T & value ) + + + operator<< + operator-lt-lt-2 + ( const QVector<T> & other ) + + + operator= + operator-eq + ( const QVector<T> & other ) + + + operator== + operator-eq-eq + ( const QVector<T> & other ) + + + operator[] + operator-5b-5d + ( int i ) + + + operator[] + operator-5b-5d-2 + ( int i ) + + + + QVectorIterator + qvectoriterator.html + + QVectorIterator + QVectorIterator + ( const QVector<T> & vector ) + + + findNext + findNext + ( const T & value ) + + + findPrevious + findPrevious + ( const T & value ) + + + hasNext + hasNext + () + + + hasPrevious + hasPrevious + () + + + next + next + () + + + peekNext + peekNext + () + + + peekPrevious + peekPrevious + () + + + previous + previous + () + + + toBack + toBack + () + + + toFront + toFront + () + + + operator= + operator-eq + ( const QVector<T> & vector ) + + + + QVNCScreen + qvncscreen.html + + QVNCScreen + QVNCScreen + ( int displayId ) + + + + QWaitCondition + qwaitcondition.html + + QWaitCondition + QWaitCondition + () + + + wait + wait + ( QMutex * mutex, unsigned long time = ULONG_MAX ) + + + wakeAll + wakeAll + () + + + wakeOne + wakeOne + () + + + add + add + ( QWidget * w, const QString & s ) + + + remove + remove + ( QWidget * w ) + + + whatsThisButton + whatsThisButton + ( QWidget * parent ) + + + + QWhatsThis + qwhatsthis.html + + createAction + createAction + ( QObject * parent = 0 ) + + + enterWhatsThisMode + enterWhatsThisMode + () + + + hideText + hideText + () + + + inWhatsThisMode + inWhatsThisMode + () + + + leaveWhatsThisMode + leaveWhatsThisMode + () + + + showText + showText + ( const QPoint & pos, const QString & text, QWidget * w = 0 ) + + + add + add + ( QWidget * w, const QString & s ) + + + remove + remove + ( QWidget * w ) + + + whatsThisButton + whatsThisButton + ( QWidget * parent ) + + + + QWhatsThisClickedEvent + qwhatsthisclickedevent.html + + QWhatsThisClickedEvent + QWhatsThisClickedEvent + ( const QString & href ) + + + href + href + () + + + QWheelEvent + QWheelEvent-3 + ( const QPoint & pos, int delta, int state, Qt::Orientation orient = Qt::Vertical ) + + + QWheelEvent + QWheelEvent-4 + ( const QPoint & pos, const QPoint & globalPos, int delta, int state, Qt::Orientation orient = Qt::Vertical ) + + + ButtonState + state + QWheelEvent::state() + + + + QWheelEvent + qwheelevent.html + + QWheelEvent + QWheelEvent + ( const QPoint & pos, int delta, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, Qt::Orientation orient = Qt::Vertical ) + + + QWheelEvent + QWheelEvent-2 + ( const QPoint & pos, const QPoint & globalPos, int delta, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, Qt::Orientation orient = Qt::Vertical ) + + + MouseButtons + buttons + QWheelEvent::buttons() + + + delta + delta + () + + + globalPos + globalPos + () + + + globalX + globalX + () + + + globalY + globalY + () + + + Orientation + orientation + QWheelEvent::orientation() + + + pos + pos + () + + + x + x + () + + + y + y + () + + + QWheelEvent + QWheelEvent-3 + ( const QPoint & pos, int delta, int state, Qt::Orientation orient = Qt::Vertical ) + + + QWheelEvent + QWheelEvent-4 + ( const QPoint & pos, const QPoint & globalPos, int delta, int state, Qt::Orientation orient = Qt::Vertical ) + + + ButtonState + state + QWheelEvent::state() + + + isEnabledToTLW + isEnabledToTLW + () + + + isTopLevel + isTopLevel + () + + + languageChange + languageChange + () + + + topLevelWidget + topLevelWidget + () + + + BackgroundOrigin + BackgroundOrigin-enum + + + + QWidget + QWidget-3 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + BackgroundMode + backgroundMode + QWidget::backgroundMode() + + + backgroundOffset + backgroundOffset + () + + + backgroundOrigin + backgroundOrigin + () + + + caption + caption + () + + + childAt + childAt-2 + ( int x, int y, bool includeThis ) + + + childAt + childAt-3 + ( const QPoint & p, bool includeThis ) + + + close + close-2 + ( bool alsoDelete ) + + + colorGroup + colorGroup + () + + + constPolish + constPolish + () + + + drawText + drawText + ( const QPoint & p, const QString & s ) + + + drawText + drawText-2 + ( int x, int y, const QString & s ) + + + erase + erase + () + + + erase + erase-2 + ( int x, int y, int w, int h ) + + + erase + erase-3 + ( const QRect & rect ) + + + erase + erase-4 + ( const QRegion & rgn ) + + + hasMouse + hasMouse + () + + + icon + icon + () + + + iconText + iconText + () + + + iconify + iconify + () + + + isDesktop + isDesktop + () + + + isDialog + isDialog + () + + + isInputMethodEnabled + isInputMethodEnabled + () + + + isPopup + isPopup + () + + + isShown + isShown + () + + + isUpdatesEnabled + isUpdatesEnabled + () + + + isVisibleToTLW + isVisibleToTLW + () + + + ownCursor + ownCursor + () + + + ownFont + ownFont + () + + + ownPalette + ownPalette + () + + + parentWidget + parentWidget-2 + ( bool sameWindow ) + + + polish + polish + () + + + recreate + recreate + ( QWidget * parent, Qt::WindowFlags f, const QPoint & p, bool showIt = false ) + + + repaint + repaint-2 + ( bool b ) + + + repaint + repaint-3 + ( int x, int y, int w, int h, bool b ) + + + repaint + repaint-4 + ( const QRect & r, bool b ) + + + repaint + repaint-5 + ( const QRegion & rgn, bool b ) + + + reparent + reparent + ( QWidget * parent, Qt::WindowFlags f, const QPoint & p, bool showIt = false ) + + + reparent + reparent-2 + ( QWidget * parent, const QPoint & p, bool showIt = false ) + + + setActiveWindow + setActiveWindow + () + + + setBackgroundColor + setBackgroundColor + ( const QColor & color ) + + + setBackgroundMode + setBackgroundMode + ( Qt::BackgroundMode widgetBackground, Qt::BackgroundMode paletteBackground = Qt::PaletteBackground ) + + + setBackgroundOrigin + setBackgroundOrigin + ( BackgroundOrigin background ) + + + setBackgroundPixmap + setBackgroundPixmap + ( const QPixmap & pixmap ) + + + setCaption + setCaption + ( const QString & c ) + + + setEraseColor + setEraseColor + ( const QColor & color ) + + + setErasePixmap + setErasePixmap + ( const QPixmap & pixmap ) + + + setFont + setFont-2 + ( const QFont & f, bool b ) + + + setIcon + setIcon + ( const QPixmap & i ) + + + setIconText + setIconText + ( const QString & it ) + + + setInputMethodEnabled + setInputMethodEnabled + ( bool enabled ) + + + setKeyCompression + setKeyCompression + ( bool b ) + + + setPalette + setPalette-2 + ( const QPalette & p, bool b ) + + + setPaletteBackgroundColor + setPaletteBackgroundColor + ( const QColor & color ) + + + setPaletteBackgroundPixmap + setPaletteBackgroundPixmap + ( const QPixmap & pixmap ) + + + setPaletteForegroundColor + setPaletteForegroundColor + ( const QColor & color ) + + + setShown + setShown + ( bool shown ) + + + setSizePolicy + setSizePolicy-2 + ( QSizePolicy::Policy hor, QSizePolicy::Policy ver, bool hfw ) + + + setStyle + setStyle-2 + ( const QString & style ) + + + unsetFont + unsetFont + () + + + unsetPalette + unsetPalette + () + + + visibleRect + visibleRect + () + + + wmapper + wmapper + () + + + + QWidget + qwidget.html + + ContextMenuPolicy + contextMenuPolicy-prop + + + + FocusPolicy + focusPolicy-prop + + + + LayoutDirection + layoutDirection-prop + + + + WindowFlags + windowFlags-prop + + + + WindowModality + windowModality-prop + + + + QWidget + QWidget + ( QWidget * parent = 0, Qt::WindowFlags f = 0 ) + + + actionEvent + actionEvent + ( QActionEvent * event ) + + + actions + actions + () + + + activateWindow + activateWindow + () + + + addAction + addAction + ( QAction * action ) + + + addActions + addActions + ( QList<QAction *> actions ) + + + adjustSize + adjustSize + () + + + ColorRole + backgroundRole + QWidget::backgroundRole() + + + changeEvent + changeEvent + ( QEvent * event ) + + + childAt + childAt + ( int x, int y ) + + + childAt + childAt-4 + ( const QPoint & p ) + + + clearFocus + clearFocus + () + + + clearMask + clearMask + () + + + close + close + () + + + closeEvent + closeEvent + ( QCloseEvent * event ) + + + contentsRect + contentsRect + () + + + contextMenuEvent + contextMenuEvent + ( QContextMenuEvent * event ) + + + create + create + ( WId window = 0, bool initializeWindow = true, bool destroyOldWindow = true ) + + + customContextMenuRequested + customContextMenuRequested + ( const QPoint & pos ) + + + destroy + destroy + ( bool destroyWindow = true, bool destroySubWindows = true ) + + + dragEnterEvent + dragEnterEvent + ( QDragEnterEvent * event ) + + + dragLeaveEvent + dragLeaveEvent + ( QDragLeaveEvent * event ) + + + dragMoveEvent + dragMoveEvent + ( QDragMoveEvent * event ) + + + dropEvent + dropEvent + ( QDropEvent * event ) + + + ensurePolished + ensurePolished + () + + + enterEvent + enterEvent + ( QEvent * event ) + + + event + event + ( QEvent * event ) + + + find + find + ( WId id ) + + + focusInEvent + focusInEvent + ( QFocusEvent * event ) + + + focusNextChild + focusNextChild + () + + + focusNextPrevChild + focusNextPrevChild + ( bool next ) + + + focusOutEvent + focusOutEvent + ( QFocusEvent * event ) + + + focusPreviousChild + focusPreviousChild + () + + + focusProxy + focusProxy + () + + + focusWidget + focusWidget + () + + + fontInfo + fontInfo + () + + + fontMetrics + fontMetrics + () + + + ColorRole + foregroundRole + QWidget::foregroundRole() + + + getContentsMargins + getContentsMargins + ( int * left, int * top, int * right, int * bottom ) + + + getDC + getDC + () + + + grabKeyboard + grabKeyboard + () + + + grabMouse + grabMouse + () + + + grabMouse + grabMouse-2 + ( const QCursor & cursor ) + + + grabShortcut + grabShortcut + ( const QKeySequence & key, Qt::ShortcutContext context = Qt::WindowShortcut ) + + + hasEditFocus + hasEditFocus + () + + + heightForWidth + heightForWidth + ( int w ) + + + hide + hide + () + + + hideEvent + hideEvent + ( QHideEvent * event ) + + + inputContext + inputContext + () + + + inputMethodEvent + inputMethodEvent + ( QInputMethodEvent * event ) + + + inputMethodQuery + inputMethodQuery + ( Qt::InputMethodQuery query ) + + + insertAction + insertAction + ( QAction * before, QAction * action ) + + + insertActions + insertActions + ( QAction * before, QList<QAction *> actions ) + + + isAncestorOf + isAncestorOf + ( const QWidget * child ) + + + isEnabledTo + isEnabledTo + ( QWidget * ancestor ) + + + isHidden + isHidden + () + + + isVisibleTo + isVisibleTo + ( QWidget * ancestor ) + + + isWindow + isWindow + () + + + keyPressEvent + keyPressEvent + ( QKeyEvent * event ) + + + keyReleaseEvent + keyReleaseEvent + ( QKeyEvent * event ) + + + keyboardGrabber + keyboardGrabber + () + + + layout + layout + () + + + leaveEvent + leaveEvent + ( QEvent * event ) + + + lower + lower + () + + + HANDLE + macCGHandle + QWidget::macCGHandle() + + + macEvent + macEvent + ( EventHandlerCallRef caller, EventRef event ) + + + HANDLE + macQDHandle + QWidget::macQDHandle() + + + mapFrom + mapFrom + ( QWidget * parent, const QPoint & pos ) + + + mapFromGlobal + mapFromGlobal + ( const QPoint & pos ) + + + mapFromParent + mapFromParent + ( const QPoint & pos ) + + + mapTo + mapTo + ( QWidget * parent, const QPoint & pos ) + + + mapToGlobal + mapToGlobal + ( const QPoint & pos ) + + + mapToParent + mapToParent + ( const QPoint & pos ) + + + mask + mask + () + + + metric + metric + ( PaintDeviceMetric m ) + + + mouseDoubleClickEvent + mouseDoubleClickEvent + ( QMouseEvent * event ) + + + mouseGrabber + mouseGrabber + () + + + mouseMoveEvent + mouseMoveEvent + ( QMouseEvent * event ) + + + mousePressEvent + mousePressEvent + ( QMouseEvent * event ) + + + mouseReleaseEvent + mouseReleaseEvent + ( QMouseEvent * event ) + + + moveEvent + moveEvent + ( QMoveEvent * event ) + + + nextInFocusChain + nextInFocusChain + () + + + overrideWindowFlags + overrideWindowFlags + ( Qt::WindowFlags flags ) + + + paintEngine + paintEngine + () + + + paintEvent + paintEvent + ( QPaintEvent * event ) + + + parentWidget + parentWidget + () + + + qwsEvent + qwsEvent + ( QWSEvent * event ) + + + raise + raise + () + + + releaseDC + releaseDC + ( HDC hdc ) + + + releaseKeyboard + releaseKeyboard + () + + + releaseMouse + releaseMouse + () + + + releaseShortcut + releaseShortcut + ( int id ) + + + removeAction + removeAction + ( QAction * action ) + + + render + render + ( QPaintDevice * target, const QPoint & targetOffset = QPoint() + + + repaint + repaint + () + + + repaint + repaint-6 + ( int x, int y, int w, int h ) + + + repaint + repaint-7 + ( const QRect & r ) + + + repaint + repaint-8 + ( const QRegion & rgn ) + + + resetInputContext + resetInputContext + () + + + resizeEvent + resizeEvent + ( QResizeEvent * event ) + + + restoreGeometry + restoreGeometry + ( const QByteArray & geometry ) + + + saveGeometry + saveGeometry + () + + + scroll + scroll + ( int dx, int dy ) + + + scroll + scroll-2 + ( int dx, int dy, const QRect & r ) + + + setAttribute + setAttribute + ( Qt::WidgetAttribute attribute, bool on = true ) + + + setBackgroundRole + setBackgroundRole + ( QPalette::ColorRole role ) + + + setContentsMargins + setContentsMargins + ( int left, int top, int right, int bottom ) + + + setDisabled + setDisabled + ( bool disable ) + + + setEditFocus + setEditFocus + ( bool enable ) + + + setFixedHeight + setFixedHeight + ( int h ) + + + setFixedSize + setFixedSize + ( const QSize & s ) + + + setFixedSize + setFixedSize-2 + ( int w, int h ) + + + setFixedWidth + setFixedWidth + ( int w ) + + + setFocus + setFocus + ( Qt::FocusReason reason ) + + + setFocus + setFocus-2 + () + + + setFocusProxy + setFocusProxy + ( QWidget * w ) + + + setForegroundRole + setForegroundRole + ( QPalette::ColorRole role ) + + + setHidden + setHidden + ( bool hidden ) + + + setInputContext + setInputContext + ( QInputContext * context ) + + + setLayout + setLayout + ( QLayout * layout ) + + + setMask + setMask + ( const QBitmap & bitmap ) + + + setMask + setMask-2 + ( const QRegion & region ) + + + setParent + setParent + ( QWidget * parent ) + + + setParent + setParent-2 + ( QWidget * parent, Qt::WindowFlags f ) + + + setShortcutAutoRepeat + setShortcutAutoRepeat + ( int id, bool enable = true ) + + + setShortcutEnabled + setShortcutEnabled + ( int id, bool enable = true ) + + + setStyle + setStyle + ( QStyle * style ) + + + setTabOrder + setTabOrder + ( QWidget * first, QWidget * second ) + + + setWindowRole + setWindowRole + ( const QString & role ) + + + setWindowState + setWindowState + ( Qt::WindowStates windowState ) + + + setWindowSurface + setWindowSurface + ( QWindowSurface * surface ) + + + show + show + () + + + showEvent + showEvent + ( QShowEvent * event ) + + + showFullScreen + showFullScreen + () + + + showMaximized + showMaximized + () + + + showMinimized + showMinimized + () + + + showNormal + showNormal + () + + + stackUnder + stackUnder + ( QWidget * w ) + + + style + style + () + + + tabletEvent + tabletEvent + ( QTabletEvent * event ) + + + testAttribute + testAttribute + ( Qt::WidgetAttribute attribute ) + + + underMouse + underMouse + () + + + update + update + () + + + update + update-2 + ( int x, int y, int w, int h ) + + + update + update-3 + ( const QRect & r ) + + + update + update-4 + ( const QRegion & rgn ) + + + updateGeometry + updateGeometry + () + + + updateMicroFocus + updateMicroFocus + () + + + visibleRegion + visibleRegion + () + + + wheelEvent + wheelEvent + ( QWheelEvent * event ) + + + winEvent + winEvent + ( MSG * message, long * result ) + + + winId + winId + () + + + window + window + () + + + windowRole + windowRole + () + + + WindowStates + windowState + QWidget::windowState() + + + windowSurface + windowSurface + () + + + WindowType + windowType + QWidget::windowType() + + + x11Event + x11Event + ( XEvent * event ) + + + x11Info + x11Info + () + + + HANDLE + x11PictureHandle + QWidget::x11PictureHandle() + + + BackgroundOrigin + BackgroundOrigin-enum + + + + QWidget + QWidget-3 + ( QWidget * parent, const char * name, Qt::WindowFlags f = 0 ) + + + BackgroundMode + backgroundMode + QWidget::backgroundMode() + + + backgroundOffset + backgroundOffset + () + + + backgroundOrigin + backgroundOrigin + () + + + caption + caption + () + + + childAt + childAt-2 + ( int x, int y, bool includeThis ) + + + childAt + childAt-3 + ( const QPoint & p, bool includeThis ) + + + close + close-2 + ( bool alsoDelete ) + + + colorGroup + colorGroup + () + + + constPolish + constPolish + () + + + drawText + drawText + ( const QPoint & p, const QString & s ) + + + drawText + drawText-2 + ( int x, int y, const QString & s ) + + + erase + erase + () + + + erase + erase-2 + ( int x, int y, int w, int h ) + + + erase + erase-3 + ( const QRect & rect ) + + + erase + erase-4 + ( const QRegion & rgn ) + + + hasMouse + hasMouse + () + + + icon + icon + () + + + iconText + iconText + () + + + iconify + iconify + () + + + isDesktop + isDesktop + () + + + isDialog + isDialog + () + + + isInputMethodEnabled + isInputMethodEnabled + () + + + isPopup + isPopup + () + + + isShown + isShown + () + + + isUpdatesEnabled + isUpdatesEnabled + () + + + isVisibleToTLW + isVisibleToTLW + () + + + ownCursor + ownCursor + () + + + ownFont + ownFont + () + + + ownPalette + ownPalette + () + + + parentWidget + parentWidget-2 + ( bool sameWindow ) + + + polish + polish + () + + + recreate + recreate + ( QWidget * parent, Qt::WindowFlags f, const QPoint & p, bool showIt = false ) + + + repaint + repaint-2 + ( bool b ) + + + repaint + repaint-3 + ( int x, int y, int w, int h, bool b ) + + + repaint + repaint-4 + ( const QRect & r, bool b ) + + + repaint + repaint-5 + ( const QRegion & rgn, bool b ) + + + reparent + reparent + ( QWidget * parent, Qt::WindowFlags f, const QPoint & p, bool showIt = false ) + + + reparent + reparent-2 + ( QWidget * parent, const QPoint & p, bool showIt = false ) + + + setActiveWindow + setActiveWindow + () + + + setBackgroundColor + setBackgroundColor + ( const QColor & color ) + + + setBackgroundMode + setBackgroundMode + ( Qt::BackgroundMode widgetBackground, Qt::BackgroundMode paletteBackground = Qt::PaletteBackground ) + + + setBackgroundOrigin + setBackgroundOrigin + ( BackgroundOrigin background ) + + + setBackgroundPixmap + setBackgroundPixmap + ( const QPixmap & pixmap ) + + + setCaption + setCaption + ( const QString & c ) + + + setEraseColor + setEraseColor + ( const QColor & color ) + + + setErasePixmap + setErasePixmap + ( const QPixmap & pixmap ) + + + setFont + setFont-2 + ( const QFont & f, bool b ) + + + setIcon + setIcon + ( const QPixmap & i ) + + + setIconText + setIconText + ( const QString & it ) + + + setInputMethodEnabled + setInputMethodEnabled + ( bool enabled ) + + + setKeyCompression + setKeyCompression + ( bool b ) + + + setPalette + setPalette-2 + ( const QPalette & p, bool b ) + + + setPaletteBackgroundColor + setPaletteBackgroundColor + ( const QColor & color ) + + + setPaletteBackgroundPixmap + setPaletteBackgroundPixmap + ( const QPixmap & pixmap ) + + + setPaletteForegroundColor + setPaletteForegroundColor + ( const QColor & color ) + + + setShown + setShown + ( bool shown ) + + + setSizePolicy + setSizePolicy-2 + ( QSizePolicy::Policy hor, QSizePolicy::Policy ver, bool hfw ) + + + setStyle + setStyle-2 + ( const QString & style ) + + + unsetFont + unsetFont + () + + + unsetPalette + unsetPalette + () + + + visibleRect + visibleRect + () + + + wmapper + wmapper + () + + + + QWidgetAction + qwidgetaction.html + + QWidgetAction + QWidgetAction + ( QObject * parent ) + + + createWidget + createWidget + ( QWidget * parent ) + + + createdWidgets + createdWidgets + () + + + defaultWidget + defaultWidget + () + + + deleteWidget + deleteWidget + ( QWidget * widget ) + + + releaseWidget + releaseWidget + ( QWidget * widget ) + + + requestWidget + requestWidget + ( QWidget * parent ) + + + setDefaultWidget + setDefaultWidget + ( QWidget * widget ) + + + + QWidgetItem + qwidgetitem.html + + QWidgetItem + QWidgetItem + ( QWidget * widget ) + + + isEmpty + isEmpty + () + + + widget + widget + () + + + + QWindowsMime + qwindowsmime.html + + QWindowsMime + QWindowsMime + () + + + canConvertFromMime + canConvertFromMime + ( const FORMATETC & formatetc, const QMimeData * mimeData ) + + + canConvertToMime + canConvertToMime + ( const QString & mimeType, IDataObject * pDataObj ) + + + convertFromMime + convertFromMime + ( const FORMATETC & formatetc, const QMimeData * mimeData, STGMEDIUM * pmedium ) + + + convertToMime + convertToMime + ( const QString & mimeType, IDataObject * pDataObj, QVariant::Type preferredType ) + + + formatsForMime + formatsForMime + ( const QString & mimeType, const QMimeData * mimeData ) + + + mimeForFormat + mimeForFormat + ( const FORMATETC & formatetc ) + + + registerMimeType + registerMimeType + ( const QString & mime ) + + + + QWindowsStyle + qwindowsstyle.html + + QWindowsStyle + QWindowsStyle + () + + + + QWindowStateChangeEvent + qwindowstatechangeevent.html + + WindowStates + oldState + QWindowStateChangeEvent::oldState() + + + + QWindowSurface + qwindowsurface.html + + QWindowSurface + QWindowSurface + ( QWidget * window ) + + + beginPaint + beginPaint + ( const QRegion & region ) + + + buffer + buffer + ( const QWidget * widget ) + + + endPaint + endPaint + ( const QRegion & region ) + + + flush + flush + ( QWidget * widget, const QRegion & region, const QPoint & offset ) + + + geometry + geometry + () + + + offset + offset + ( const QWidget * widget ) + + + paintDevice + paintDevice + () + + + rect + rect + ( const QWidget * widget ) + + + scroll + scroll + ( const QRegion & area, int dx, int dy ) + + + setGeometry + setGeometry + ( const QRect & rect ) + + + window + window + () + + + + QWindowsVistaStyle + qwindowsvistastyle.html + + QWindowsVistaStyle + QWindowsVistaStyle + () + + + + QWindowsXPStyle + qwindowsxpstyle.html + + QWindowsXPStyle + QWindowsXPStyle + () + + + + QWizard + qwizard.html + + WizardButton + WizardButton-enum + + + + WizardPixmap + WizardPixmap-enum + + + + WizardStyle + WizardStyle-enum + + + + TextFormat + subTitleFormat-prop + + + + TextFormat + titleFormat-prop + + + + QWizard + QWizard + ( QWidget * parent = 0, Qt::WindowFlags flags = 0 ) + + + addPage + addPage + ( QWizardPage * page ) + + + back + back + () + + + button + button + ( WizardButton which ) + + + buttonText + buttonText + ( WizardButton which ) + + + cleanupPage + cleanupPage + ( int id ) + + + currentIdChanged + currentIdChanged + ( int id ) + + + currentPage + currentPage + () + + + customButtonClicked + customButtonClicked + ( int which ) + + + field + field + ( const QString & name ) + + + hasVisitedPage + hasVisitedPage + ( int id ) + + + helpRequested + helpRequested + () + + + initializePage + initializePage + ( int id ) + + + next + next + () + + + nextId + nextId + () + + + page + page + ( int id ) + + + pixmap + pixmap + ( WizardPixmap which ) + + + restart + restart + () + + + setButton + setButton + ( WizardButton which, QAbstractButton * button ) + + + setButtonLayout + setButtonLayout + ( const QList<WizardButton> & layout ) + + + setButtonText + setButtonText + ( WizardButton which, const QString & text ) + + + setDefaultProperty + setDefaultProperty + ( const char * className, const char * property, const char * changedSignal ) + + + setField + setField + ( const QString & name, const QVariant & value ) + + + setOption + setOption + ( WizardOption option, bool on = true ) + + + setPage + setPage + ( int id, QWizardPage * page ) + + + setPixmap + setPixmap + ( WizardPixmap which, const QPixmap & pixmap ) + + + testOption + testOption + ( WizardOption option ) + + + validateCurrentPage + validateCurrentPage + () + + + visitedPages + visitedPages + () + + + + QWizardPage + qwizardpage.html + + QWizardPage + QWizardPage + ( QWidget * parent = 0 ) + + + buttonText + buttonText + ( QWizard::WizardButton which ) + + + cleanupPage + cleanupPage + () + + + completeChanged + completeChanged + () + + + field + field + ( const QString & name ) + + + initializePage + initializePage + () + + + isCommitPage + isCommitPage + () + + + isComplete + isComplete + () + + + isFinalPage + isFinalPage + () + + + nextId + nextId + () + + + pixmap + pixmap + ( QWizard::WizardPixmap which ) + + + registerField + registerField + ( const QString & name, QWidget * widget, const char * property = 0, const char * changedSignal = 0 ) + + + setButtonText + setButtonText + ( QWizard::WizardButton which, const QString & text ) + + + setCommitPage + setCommitPage + ( bool commitPage ) + + + setField + setField + ( const QString & name, const QVariant & value ) + + + setFinalPage + setFinalPage + ( bool finalPage ) + + + setPixmap + setPixmap + ( QWizard::WizardPixmap which, const QPixmap & pixmap ) + + + validatePage + validatePage + () + + + wizard + wizard + () + + + QWorkspace + QWorkspace-2 + ( QWidget * parent, const char * name ) + + + setPaletteBackgroundColor + setPaletteBackgroundColor + ( const QColor & c ) + + + setPaletteBackgroundPixmap + setPaletteBackgroundPixmap + ( const QPixmap & pm ) + + + + QWorkspace + qworkspace.html + + WindowOrder + WindowOrder-enum + + + + QWorkspace + QWorkspace + ( QWidget * parent = 0 ) + + + activateNextWindow + activateNextWindow + () + + + activatePreviousWindow + activatePreviousWindow + () + + + activeWindow + activeWindow + () + + + addWindow + addWindow + ( QWidget * w, Qt::WindowFlags flags = 0 ) + + + arrangeIcons + arrangeIcons + () + + + cascade + cascade + () + + + closeActiveWindow + closeActiveWindow + () + + + closeAllWindows + closeAllWindows + () + + + setActiveWindow + setActiveWindow + ( QWidget * w ) + + + tile + tile + () + + + windowActivated + windowActivated + ( QWidget * w ) + + + windowList + windowList + ( WindowOrder order = CreationOrder ) + + + QWorkspace + QWorkspace-2 + ( QWidget * parent, const char * name ) + + + setPaletteBackgroundColor + setPaletteBackgroundColor + ( const QColor & c ) + + + setPaletteBackgroundPixmap + setPaletteBackgroundPixmap + ( const QPixmap & pm ) + + + + QWriteLocker + qwritelocker.html + + QWriteLocker + QWriteLocker + ( QReadWriteLock * lock ) + + + readWriteLock + readWriteLock + () + + + relock + relock + () + + + unlock + unlock + () + + + + QWSCalibratedMouseHandler + qwscalibratedmousehandler.html + + calibrate + calibrate + ( const QWSPointerCalibrationData * data ) + + + clearCalibration + clearCalibration + () + + + readCalibration + readCalibration + () + + + sendFiltered + sendFiltered + ( const QPoint & position, int state ) + + + setFilterSize + setFilterSize + ( int size ) + + + transform + transform + ( const QPoint & position ) + + + writeCalibration + writeCalibration + () + + + + QWSClient + qwsclient.html + + clientId + clientId + () + + + identity + identity + () + + + + QWSEmbedWidget + qwsembedwidget.html + + QWSEmbedWidget + QWSEmbedWidget + ( WId id, QWidget * parent = 0 ) + + + + QWSEvent + qwsevent.html + + Type + Type-enum + + + + + QWSGLWindowSurface + qwsglwindowsurface.html + + QWSGLWindowSurface + QWSGLWindowSurface + ( QWidget * window ) + + + QWSGLWindowSurface + QWSGLWindowSurface-2 + () + + + context + context + () + + + setContext + setContext + ( QGLContext * context ) + + + sendIMEvent + sendIMEvent + ( QWSServer::IMState state, const QString & text, int cursorPosition, int selectionLength = 0 ) + + + + QWSInputMethod + qwsinputmethod.html + + UpdateType + UpdateType-enum + + + + QWSInputMethod + QWSInputMethod + () + + + filter + filter + ( int unicode, int keycode, int modifiers, bool isPress, bool autoRepeat ) + + + filter + filter-2 + ( const QPoint & position, int state, int wheel ) + + + inputResolutionShift + inputResolutionShift + () + + + mouseHandler + mouseHandler + ( int offset, int state ) + + + queryResponse + queryResponse + ( int property, const QVariant & result ) + + + reset + reset + () + + + sendCommitString + sendCommitString + ( const QString & commitString, int replaceFromPosition = 0, int replaceLength = 0 ) + + + sendEvent + sendEvent + ( const QInputMethodEvent * event ) + + + sendMouseEvent + sendMouseEvent + ( const QPoint & position, int state, int wheel ) + + + sendPreeditString + sendPreeditString + ( const QString & preeditString, int cursorPosition, int selectionLength = 0 ) + + + sendQuery + sendQuery + ( int property ) + + + setInputResolution + setInputResolution + ( bool isHigh ) + + + updateHandler + updateHandler + ( int type ) + + + + QWSKeyboardHandler + qwskeyboardhandler.html + + QWSKeyboardHandler + QWSKeyboardHandler + () + + + beginAutoRepeat + beginAutoRepeat + ( int unicode, int keycode, Qt::KeyboardModifiers modifier ) + + + endAutoRepeat + endAutoRepeat + () + + + processKeyEvent + processKeyEvent + ( int unicode, int keycode, Qt::KeyboardModifiers modifiers, bool isPress, bool autoRepeat ) + + + transformDirKey + transformDirKey + ( int keycode ) + + + + QWSMouseHandler + qwsmousehandler.html + + QWSMouseHandler + QWSMouseHandler + ( const QString & driver = QString() + + + calibrate + calibrate + ( const QWSPointerCalibrationData * data ) + + + clearCalibration + clearCalibration + () + + + limitToScreen + limitToScreen + ( QPoint & position ) + + + mouseChanged + mouseChanged + ( const QPoint & position, int state, int wheel = 0 ) + + + pos + pos + () + + + resume + resume + () + + + setScreen + setScreen + ( const QScreen * screen ) + + + suspend + suspend + () + + + + QWSPointerCalibrationData + qwspointercalibrationdata.html + + Location + Location-enum + + + + devPoints + devPoints-var + [5] + + + screenPoints + screenPoints-var + [5] + + + + QWSScreenSaver + qwsscreensaver.html + + restore + restore + () + + + save + save + ( int level ) + + + + QWSServer::KeyboardFilter + qwsserver-keyboardfilter.html + + filter + filter + ( int unicode, int keycode, int modifiers, bool isPress, bool autoRepeat ) + + + IMState + IMState-enum + + + + QWSServer + QWSServer-2 + ( int flags, QObject * parent, const char * name ) + + + setDesktopBackground + setDesktopBackground + ( const QImage & image ) + + + setDesktopBackground + setDesktopBackground-2 + ( const QColor & color ) + + + + QWSServer + qwsserver.html + + IMMouse + IMMouse-enum + + + + WindowEvent + WindowEvent-enum + + + + addKeyboardFilter + addKeyboardFilter + ( KeyboardFilter * filter ) + + + backgroundBrush + backgroundBrush + () + + + clientWindows + clientWindows + () + + + closeKeyboard + closeKeyboard + () + + + closeMouse + closeMouse + () + + + enablePainting + enablePainting + ( bool enable ) + + + instance + instance + () + + + isCursorVisible + isCursorVisible + () + + + keyboardHandler + keyboardHandler + () + + + markedText + markedText + ( const QString & selection ) + + + mouseHandler + mouseHandler + () + + + newChannel + newChannel + ( const QString & channel ) + + + openKeyboard + openKeyboard + () + + + openMouse + openMouse + () + + + processKeyEvent + processKeyEvent + ( int unicode, int keycode, Qt::KeyboardModifiers modifiers, bool isPress, bool autoRepeat ) + + + refresh + refresh + () + + + refresh + refresh-2 + ( QRegion & region ) + + + removeKeyboardFilter + removeKeyboardFilter + () + + + removedChannel + removedChannel + ( const QString & channel ) + + + resumeMouse + resumeMouse + () + + + screenSaverActivate + screenSaverActivate + ( bool activate ) + + + screenSaverActive + screenSaverActive + () + + + sendIMEvent + sendIMEvent + ( const QInputMethodEvent * event ) + + + sendIMQuery + sendIMQuery + ( int property ) + + + sendKeyEvent + sendKeyEvent + ( int unicode, int keycode, Qt::KeyboardModifiers modifiers, bool isPress, bool autoRepeat ) + + + setBackground + setBackground + ( const QBrush & brush ) + + + setCurrentInputMethod + setCurrentInputMethod + ( QWSInputMethod * method ) + + + setCursorVisible + setCursorVisible + ( bool visible ) + + + setDefaultKeyboard + setDefaultKeyboard + ( const char * keyboardDriver ) + + + setDefaultMouse + setDefaultMouse + ( const char * mouseDriver ) + + + setKeyboardHandler + setKeyboardHandler + ( QWSKeyboardHandler * driver ) + + + setMaxWindowRect + setMaxWindowRect + ( const QRect & rectangle ) + + + setMouseHandler + setMouseHandler + ( QWSMouseHandler * driver ) + + + setScreenSaver + setScreenSaver + ( QWSScreenSaver * screenSaver ) + + + setScreenSaverBlockLevel + setScreenSaverBlockLevel + ( int eventBlockLevel ) + + + setScreenSaverInterval + setScreenSaverInterval + ( int milliseconds ) + + + setScreenSaverIntervals + setScreenSaverIntervals + ( int * intervals ) + + + suspendMouse + suspendMouse + () + + + timerEvent + timerEvent + ( QTimerEvent * e ) + + + windowAt + windowAt + ( const QPoint & position ) + + + windowEvent + windowEvent + ( QWSWindow * window, QWSServer::WindowEvent eventType ) + + + QWSServer + QWSServer-2 + ( int flags, QObject * parent, const char * name ) + + + setDesktopBackground + setDesktopBackground + ( const QImage & image ) + + + setDesktopBackground + setDesktopBackground-2 + ( const QColor & color ) + + + + QWSWindow + qwswindow.html + + State + State-enum + + + + caption + caption + () + + + client + client + () + + + dirtyOnScreen + dirtyOnScreen + () + + + isFullyObscured + isFullyObscured + () + + + isOpaque + isOpaque + () + + + isVisible + isVisible + () + + + name + name + () + + + opacity + opacity + () + + + requestedRegion + requestedRegion + () + + + state + state + () + + + winId + winId + () + + + WindowFlags + windowFlags + QWSWindow::windowFlags() + + + + QWSWindowSurface + qwswindowsurface.html + + QWSWindowSurface + QWSWindowSurface + () + + + QWSWindowSurface + QWSWindowSurface-2 + ( QWidget * window ) + + + beginPaint + beginPaint + ( const QRegion & ) + + + clipRegion + clipRegion + () + + + dirtyRegion + dirtyRegion + () + + + endPaint + endPaint + ( const QRegion & ) + + + flush + flush + ( QWidget * widget, const QRegion & region, const QPoint & offset ) + + + image + image + () + + + isBuffered + isBuffered + () + + + isOpaque + isOpaque + () + + + isRegionReserved + isRegionReserved + () + + + isValid + isValid + () + + + key + key + () + + + lock + lock + ( int timeout = -1 ) + + + paintDevice + paintDevice + () + + + painterOffset + painterOffset + () + + + permanentState + permanentState + () + + + setClipRegion + setClipRegion + ( const QRegion & clip ) + + + setDirty + setDirty + ( const QRegion & region ) + + + setGeometry + setGeometry + ( const QRect & rect ) + + + setPermanentState + setPermanentState + ( const QByteArray & data ) + + + setSurfaceFlags + setSurfaceFlags + ( SurfaceFlags flags ) + + + setTransientState + setTransientState + ( const QByteArray & state ) + + + surfaceFlags + surfaceFlags + () + + + transientState + transientState + () + + + unlock + unlock + () + + + + QX11EmbedContainer + qx11embedcontainer.html + + Error + Error-enum + + + + QX11EmbedContainer + QX11EmbedContainer + ( QWidget * parent = 0 ) + + + clientClosed + clientClosed + () + + + clientIsEmbedded + clientIsEmbedded + () + + + clientWinId + clientWinId + () + + + discardClient + discardClient + () + + + embedClient + embedClient + ( WId id ) + + + error + error + () + + + error + error-2 + ( QX11EmbedContainer::Error error ) + + + + QX11EmbedWidget + qx11embedwidget.html + + Error + Error-enum + + + + QX11EmbedWidget + QX11EmbedWidget + ( QWidget * parent = 0 ) + + + containerClosed + containerClosed + () + + + containerWinId + containerWinId + () + + + embedInto + embedInto + ( WId id ) + + + embedded + embedded + () + + + error + error + () + + + error + error-2 + ( QX11EmbedWidget::Error error ) + + + + QX11Info + qx11info.html + + QX11Info + QX11Info + () + + + QX11Info + QX11Info-2 + ( const QX11Info & other ) + + + appCells + appCells + ( int screen = -1 ) + + + appClass + appClass + () + + + HANDLE + appColormap + QX11Info::appColormap( int screen = -1 ) + + + appDefaultColormap + appDefaultColormap + ( int screen = -1 ) + + + appDefaultVisual + appDefaultVisual + ( int screen = -1 ) + + + appDepth + appDepth + ( int screen = -1 ) + + + appDpiX + appDpiX + ( int screen = -1 ) + + + appDpiY + appDpiY + ( int screen = -1 ) + + + HANDLE + appRootWindow + QX11Info::appRootWindow( int screen = -1 ) + + + appScreen + appScreen + () + + + appTime + appTime + () + + + appUserTime + appUserTime + () + + + appVisual + appVisual + ( int screen = -1 ) + + + cells + cells + () + + + HANDLE + colormap + QX11Info::colormap() + + + defaultColormap + defaultColormap + () + + + defaultVisual + defaultVisual + () + + + depth + depth + () + + + display + display + () + + + screen + screen + () + + + setAppDpiX + setAppDpiX + ( int screen, int xdpi ) + + + setAppDpiY + setAppDpiY + ( int screen, int ydpi ) + + + setAppTime + setAppTime + ( unsigned long time ) + + + setAppUserTime + setAppUserTime + ( unsigned long time ) + + + visual + visual + () + + + operator= + operator-eq + ( const QX11Info & other ) + + + + QXmlAttributes + qxmlattributes.html + + QXmlAttributes + QXmlAttributes + () + + + append + append + ( const QString & qName, const QString & uri, const QString & localPart, const QString & value ) + + + clear + clear + () + + + count + count + () + + + index + index + ( const QString & qName ) + + + index + index-2 + ( const QLatin1String & qName ) + + + index + index-3 + ( const QString & uri, const QString & localPart ) + + + length + length + () + + + localName + localName + ( int index ) + + + qName + qName + ( int index ) + + + type + type + ( int index ) + + + type + type-2 + ( const QString & qName ) + + + type + type-3 + ( const QString & uri, const QString & localName ) + + + uri + uri + ( int index ) + + + value + value + ( int index ) + + + value + value-2 + ( const QString & qName ) + + + value + value-3 + ( const QLatin1String & qName ) + + + value + value-4 + ( const QString & uri, const QString & localName ) + + + + QXmlContentHandler + qxmlcontenthandler.html + + characters + characters + ( const QString & ch ) + + + endDocument + endDocument + () + + + endElement + endElement + ( const QString & namespaceURI, const QString & localName, const QString & qName ) + + + endPrefixMapping + endPrefixMapping + ( const QString & prefix ) + + + errorString + errorString + () + + + ignorableWhitespace + ignorableWhitespace + ( const QString & ch ) + + + processingInstruction + processingInstruction + ( const QString & target, const QString & data ) + + + setDocumentLocator + setDocumentLocator + ( QXmlLocator * locator ) + + + skippedEntity + skippedEntity + ( const QString & name ) + + + startDocument + startDocument + () + + + startElement + startElement + ( const QString & namespaceURI, const QString & localName, const QString & qName, const QXmlAttributes & atts ) + + + startPrefixMapping + startPrefixMapping + ( const QString & prefix, const QString & uri ) + + + + QXmlDeclHandler + qxmldeclhandler.html + + attributeDecl + attributeDecl + ( const QString & eName, const QString & aName, const QString & type, const QString & valueDefault, const QString & value ) + + + errorString + errorString + () + + + externalEntityDecl + externalEntityDecl + ( const QString & name, const QString & publicId, const QString & systemId ) + + + internalEntityDecl + internalEntityDecl + ( const QString & name, const QString & value ) + + + + QXmlDefaultHandler + qxmldefaulthandler.html + + QXmlDefaultHandler + QXmlDefaultHandler + () + + + + QXmlDTDHandler + qxmldtdhandler.html + + errorString + errorString + () + + + notationDecl + notationDecl + ( const QString & name, const QString & publicId, const QString & systemId ) + + + unparsedEntityDecl + unparsedEntityDecl + ( const QString & name, const QString & publicId, const QString & systemId, const QString & notationName ) + + + + QXmlEntityResolver + qxmlentityresolver.html + + errorString + errorString + () + + + resolveEntity + resolveEntity + ( const QString & publicId, const QString & systemId, QXmlInputSource *& ret ) + + + + QXmlErrorHandler + qxmlerrorhandler.html + + error + error + ( const QXmlParseException & exception ) + + + errorString + errorString + () + + + fatalError + fatalError + ( const QXmlParseException & exception ) + + + warning + warning + ( const QXmlParseException & exception ) + + + QXmlInputSource + QXmlInputSource-3 + ( QFile & file ) + + + QXmlInputSource + QXmlInputSource-4 + ( QTextStream & stream ) + + + + QXmlInputSource + qxmlinputsource.html + + QXmlInputSource + QXmlInputSource + () + + + QXmlInputSource + QXmlInputSource-2 + ( QIODevice * dev ) + + + data + data + () + + + fetchData + fetchData + () + + + fromRawData + fromRawData + ( const QByteArray & data, bool beginning = false ) + + + next + next + () + + + reset + reset + () + + + setData + setData + ( const QString & dat ) + + + setData + setData-2 + ( const QByteArray & dat ) + + + QXmlInputSource + QXmlInputSource-3 + ( QFile & file ) + + + QXmlInputSource + QXmlInputSource-4 + ( QTextStream & stream ) + + + + QXmlLexicalHandler + qxmllexicalhandler.html + + comment + comment + ( const QString & ch ) + + + endCDATA + endCDATA + () + + + endDTD + endDTD + () + + + endEntity + endEntity + ( const QString & name ) + + + errorString + errorString + () + + + startCDATA + startCDATA + () + + + startDTD + startDTD + ( const QString & name, const QString & publicId, const QString & systemId ) + + + startEntity + startEntity + ( const QString & name ) + + + + QXmlLocator + qxmllocator.html + + QXmlLocator + QXmlLocator + () + + + columnNumber + columnNumber + () + + + lineNumber + lineNumber + () + + + + QXmlNamespaceSupport + qxmlnamespacesupport.html + + QXmlNamespaceSupport + QXmlNamespaceSupport + () + + + popContext + popContext + () + + + prefix + prefix + ( const QString & uri ) + + + prefixes + prefixes + () + + + prefixes + prefixes-2 + ( const QString & uri ) + + + processName + processName + ( const QString & qname, bool isAttribute, QString & nsuri, QString & localname ) + + + pushContext + pushContext + () + + + reset + reset + () + + + setPrefix + setPrefix + ( const QString & pre, const QString & uri ) + + + splitName + splitName + ( const QString & qname, QString & prefix, QString & localname ) + + + uri + uri + ( const QString & prefix ) + + + + QXmlParseException + qxmlparseexception.html + + QXmlParseException + QXmlParseException + ( const QString & name = QString() + + + columnNumber + columnNumber + () + + + lineNumber + lineNumber + () + + + message + message + () + + + publicId + publicId + () + + + systemId + systemId + () + + + parse + parse-2 + ( const QXmlInputSource & input ) + + + + QXmlReader + qxmlreader.html + + DTDHandler + DTDHandler + () + + + contentHandler + contentHandler + () + + + declHandler + declHandler + () + + + entityResolver + entityResolver + () + + + errorHandler + errorHandler + () + + + feature + feature + ( const QString & name, bool * ok = 0 ) + + + hasFeature + hasFeature + ( const QString & name ) + + + hasProperty + hasProperty + ( const QString & name ) + + + lexicalHandler + lexicalHandler + () + + + parse + parse + ( const QXmlInputSource * input ) + + + property + property + ( const QString & name, bool * ok = 0 ) + + + setContentHandler + setContentHandler + ( QXmlContentHandler * handler ) + + + setDTDHandler + setDTDHandler + ( QXmlDTDHandler * handler ) + + + setDeclHandler + setDeclHandler + ( QXmlDeclHandler * handler ) + + + setEntityResolver + setEntityResolver + ( QXmlEntityResolver * handler ) + + + setErrorHandler + setErrorHandler + ( QXmlErrorHandler * handler ) + + + setFeature + setFeature + ( const QString & name, bool value ) + + + setLexicalHandler + setLexicalHandler + ( QXmlLexicalHandler * handler ) + + + setProperty + setProperty + ( const QString & name, void * value ) + + + + QXmlSimpleReader + qxmlsimplereader.html + + QXmlSimpleReader + QXmlSimpleReader + () + + + parse + parse + ( const QXmlInputSource * input, bool incremental ) + + + parseContinue + parseContinue + () + + + setFeature + setFeature + ( const QString & name, bool enable ) + + + + QXmlStreamAttribute + qxmlstreamattribute.html + + QXmlStreamAttribute + QXmlStreamAttribute + () + + + QXmlStreamAttribute + QXmlStreamAttribute-2 + ( const QString & qualifiedName, const QString & value ) + + + QXmlStreamAttribute + QXmlStreamAttribute-3 + ( const QString & namespaceUri, const QString & name, const QString & value ) + + + QXmlStreamAttribute + QXmlStreamAttribute-4 + ( const QXmlStreamAttribute & other ) + + + isDefault + isDefault + () + + + name + name + () + + + namespaceUri + namespaceUrix + () + + + qualifiedName + qualifiedName + () + + + value + value + () + + + operator!= + operator-not-eq + ( const QXmlStreamAttribute & other ) + + + operator= + operator-eq + ( const QXmlStreamAttribute & other ) + + + operator== + operator-eq-eq + ( const QXmlStreamAttribute & other ) + + + + QXmlStreamAttributes + qxmlstreamattributes.html + + append + append + ( const QString & namespaceUri, const QString & name, const QString & value ) + + + append + append-2 + ( const QString & qualifiedName, const QString & value ) + + + value + value + ( const QString & namespaceUri, const QString & name ) + + + value + value-2 + ( const QString & namespaceUri, const QLatin1String & name ) + + + value + value-3 + ( const QLatin1String & namespaceUri, const QLatin1String & name ) + + + value + value-4 + ( const QString & qualifiedName ) + + + value + value-5 + ( const QLatin1String & qualifiedName ) + + + + QXmlStreamEntityDeclaration + qxmlstreamentitydeclaration.html + + QXmlStreamEntityDeclaration + QXmlStreamEntityDeclaration + () + + + QXmlStreamEntityDeclaration + QXmlStreamEntityDeclaration-2 + ( const QXmlStreamEntityDeclaration & other ) + + + name + name + () + + + notationName + notationName + () + + + publicId + publicId + () + + + systemId + systemId + () + + + value + value + () + + + operator!= + operator-not-eq + ( const QXmlStreamEntityDeclaration & other ) + + + operator= + operator-eq + ( const QXmlStreamEntityDeclaration & other ) + + + operator== + operator-eq-eq + ( const QXmlStreamEntityDeclaration & other ) + + + + QXmlStreamNamespaceDeclaration + qxmlstreamnamespacedeclaration.html + + QXmlStreamNamespaceDeclaration + QXmlStreamNamespaceDeclaration + () + + + QXmlStreamNamespaceDeclaration + QXmlStreamNamespaceDeclaration-2 + ( const QXmlStreamNamespaceDeclaration & other ) + + + namespaceUri + namespaceUrix + () + + + prefix + prefix + () + + + operator!= + operator-not-eq + ( const QXmlStreamNamespaceDeclaration & other ) + + + operator= + operator-eq + ( const QXmlStreamNamespaceDeclaration & other ) + + + operator== + operator-eq-eq + ( const QXmlStreamNamespaceDeclaration & other ) + + + + QXmlStreamNotationDeclaration + qxmlstreamnotationdeclaration.html + + QXmlStreamNotationDeclaration + QXmlStreamNotationDeclaration + () + + + QXmlStreamNotationDeclaration + QXmlStreamNotationDeclaration-2 + ( const QXmlStreamNotationDeclaration & other ) + + + name + name + () + + + publicId + publicId + () + + + systemId + systemId + () + + + operator!= + operator-not-eq + ( const QXmlStreamNotationDeclaration & other ) + + + operator= + operator-eq + ( const QXmlStreamNotationDeclaration & other ) + + + operator== + operator-eq-eq + ( const QXmlStreamNotationDeclaration & other ) + + + + QXmlStreamReader + qxmlstreamreader.html + + Error + Error-enum + + + + TokenType + TokenType-enum + + + + QXmlStreamReader + QXmlStreamReader + () + + + QXmlStreamReader + QXmlStreamReader-2 + ( QIODevice * device ) + + + QXmlStreamReader + QXmlStreamReader-3 + ( const QByteArray & data ) + + + QXmlStreamReader + QXmlStreamReader-4 + ( const QString & data ) + + + QXmlStreamReader + QXmlStreamReader-5 + ( const char * data ) + + + addData + addData + ( const QByteArray & data ) + + + addData + addData-2 + ( const QString & data ) + + + addData + addData-3 + ( const char * data ) + + + atEnd + atEnd + () + + + attributes + attributes + () + + + characterOffset + characterOffset + () + + + clear + clear + () + + + columnNumber + columnNumber + () + + + device + device + () + + + entityDeclarations + entityDeclarations + () + + + error + error + () + + + errorString + errorString + () + + + hasError + hasError + () + + + isCDATA + isCDATA + () + + + isCharacters + isCharacters + () + + + isComment + isComment + () + + + isDTD + isDTD + () + + + isEndDocument + isEndDocument + () + + + isEndElement + isEndElement + () + + + isEntityReference + isEntityReference + () + + + isProcessingInstruction + isProcessingInstruction + () + + + isStandaloneDocument + isStandaloneDocument + () + + + isStartDocument + isStartDocument + () + + + isStartElement + isStartElement + () + + + isWhitespace + isWhitespace + () + + + lineNumber + lineNumber + () + + + name + name + () + + + namespaceDeclarations + namespaceDeclarations + () + + + namespaceUri + namespaceUrix + () + + + notationDeclarations + notationDeclarations + () + + + processingInstructionData + processingInstructionData + () + + + processingInstructionTarget + processingInstructionTarget + () + + + qualifiedName + qualifiedName + () + + + raiseError + raiseError + ( const QString & message = QString() + + + readElementText + readElementText + () + + + readNext + readNext + () + + + setDevice + setDevice + ( QIODevice * device ) + + + text + text + () + + + tokenString + tokenString + () + + + tokenType + tokenType + () + + + + QXmlStreamWriter + qxmlstreamwriter.html + + QXmlStreamWriter + QXmlStreamWriter + () + + + QXmlStreamWriter + QXmlStreamWriter-2 + ( QIODevice * device ) + + + QXmlStreamWriter + QXmlStreamWriter-3 + ( QByteArray * array ) + + + QXmlStreamWriter + QXmlStreamWriter-4 + ( QString * string ) + + + codec + codec + () + + + device + device + () + + + setCodec + setCodec + ( QTextCodec * codec ) + + + setCodec + setCodec-2 + ( const char * codecName ) + + + setDevice + setDevice + ( QIODevice * device ) + + + writeAttribute + writeAttribute + ( const QString & namespaceUri, const QString & name, const QString & value ) + + + writeAttribute + writeAttribute-2 + ( const QString & qualifiedName, const QString & value ) + + + writeAttribute + writeAttribute-3 + ( const QXmlStreamAttribute & attribute ) + + + writeAttributes + writeAttributes + ( const QXmlStreamAttributes & attributes ) + + + writeCDATA + writeCDATA + ( const QString & text ) + + + writeCharacters + writeCharacters + ( const QString & text ) + + + writeComment + writeComment + ( const QString & text ) + + + writeCurrentToken + writeCurrentToken + ( const QXmlStreamReader & reader ) + + + writeDTD + writeDTD + ( const QString & dtd ) + + + writeDefaultNamespace + writeDefaultNamespace + ( const QString & namespaceUri ) + + + writeEmptyElement + writeEmptyElement + ( const QString & namespaceUri, const QString & name ) + + + writeEmptyElement + writeEmptyElement-2 + ( const QString & qualifiedName ) + + + writeEndDocument + writeEndDocument + () + + + writeEndElement + writeEndElement + () + + + writeEntityReference + writeEntityReference + ( const QString & name ) + + + writeNamespace + writeNamespace + ( const QString & namespaceUri, const QString & prefix = QString() + + + writeProcessingInstruction + writeProcessingInstruction + ( const QString & target, const QString & data = QString() + + + writeStartDocument + writeStartDocument + ( const QString & version ) + + + writeStartDocument + writeStartDocument-2 + () + + + writeStartElement + writeStartElement + ( const QString & namespaceUri, const QString & name ) + + + writeStartElement + writeStartElement-2 + ( const QString & qualifiedName ) + + + writeTextElement + writeTextElement + ( const QString & namespaceUri, const QString & name, const QString & text ) + + + writeTextElement + writeTextElement-2 + ( const QString & qualifiedName, const QString & text ) + + + diff --git a/okular/.krazy b/okular/.krazy new file mode 100644 index 00000000..10e868b0 --- /dev/null +++ b/okular/.krazy @@ -0,0 +1 @@ +SKIP /generators/chm/kio-msits\|/generators/chm/lib\|/generators/dvi/\|/generators/plucker/unpluck/|/generators/poppler/synctex/ diff --git a/okular/AUTHORS b/okular/AUTHORS new file mode 100644 index 00000000..55b672e3 --- /dev/null +++ b/okular/AUTHORS @@ -0,0 +1,4 @@ +Albert Astals Cid +Enrico Ros +Wilco Greven +Christophe Devriese diff --git a/okular/CMakeLists.txt b/okular/CMakeLists.txt new file mode 100644 index 00000000..34f9f6a8 --- /dev/null +++ b/okular/CMakeLists.txt @@ -0,0 +1,239 @@ +project(okular) + +find_package(KDE4 4.6.0 REQUIRED) +include(KDE4Defaults) +include(MacroLibrary) +include(MacroOptionalAddSubdirectory) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules) + +macro_optional_find_package(QImageBlitz) +macro_log_feature(QIMAGEBLITZ_FOUND "QImageBlitz" "An image effects library" "http://sourceforge.net/projects/qimageblitz" TRUE "kdesupport" "Required to build Okular.") + +macro_optional_find_package(LibKScreen) +macro_log_feature(LibKScreen_FOUND "LibKScreen" "KDE screen management library" "https://projects.kde.org/projects/kdereview/libkscreen" FALSE "1.0.2" "DPI detection support") + +add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) +add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${KDE4_INCLUDES} + ${QIMAGEBLITZ_INCLUDES} + ${LibKScreen_INCLUDE_DIR} +) + +add_subdirectory( active ) +add_subdirectory( ui ) +add_subdirectory( shell ) +add_subdirectory( generators ) +add_subdirectory( tests ) +macro_optional_add_subdirectory(doc) + +include(OkularConfigureChecks.cmake) +include(MacroWriteBasicCMakeVersionFile) + +if(NOT WIN32) + set(MATH_LIB m) +else(NOT WIN32) + set(MATH_LIB) +endif(NOT WIN32) + +# okularcore + +set(okularcore_SRCS + core/action.cpp + core/annotations.cpp + core/area.cpp + core/audioplayer.cpp + core/bookmarkmanager.cpp + core/chooseenginedialog.cpp + core/document.cpp + core/documentcommands.cpp + core/fontinfo.cpp + core/form.cpp + core/generator.cpp + core/generator_p.cpp + core/misc.cpp + core/movie.cpp + core/observer.cpp + core/page.cpp + core/pagecontroller.cpp + core/pagesize.cpp + core/pagetransition.cpp + core/rotationjob.cpp + core/scripter.cpp + core/sound.cpp + core/sourcereference.cpp + core/textdocumentgenerator.cpp + core/textdocumentsettings.cpp + core/textpage.cpp + core/tilesmanager.cpp + core/utils.cpp + core/view.cpp + core/fileprinter.cpp + core/script/executor_kjs.cpp + core/script/kjs_app.cpp + core/script/kjs_console.cpp + core/script/kjs_data.cpp + core/script/kjs_document.cpp + core/script/kjs_fullscreen.cpp + core/script/kjs_field.cpp + core/script/kjs_spell.cpp + core/script/kjs_util.cpp +) + +kde4_add_ui_files(okularcore_SRCS + conf/textdocumentsettings.ui +) + +install( FILES + core/action.h + core/annotations.h + core/area.h + core/document.h + core/fontinfo.h + core/form.h + core/generator.h + core/global.h + core/okular_export.h + core/page.h + core/pagesize.h + core/pagetransition.h + core/sound.h + core/sourcereference.h + core/textdocumentgenerator.h + core/textdocumentsettings.h + core/textpage.h + core/tile.h + core/utils.h + core/version.h + core/fileprinter.h + core/observer.h + ${CMAKE_CURRENT_BINARY_DIR}/settings_core.h + DESTINATION ${INCLUDE_INSTALL_DIR}/okular/core COMPONENT Devel) + +install( FILES + interfaces/configinterface.h + interfaces/guiinterface.h + interfaces/printinterface.h + interfaces/saveinterface.h + interfaces/viewerinterface.h + DESTINATION ${INCLUDE_INSTALL_DIR}/okular/interfaces COMPONENT Devel) + +kde4_add_ui_files(okularcore_SRCS + core/chooseenginewidget.ui +) + +kde4_add_kcfg_files(okularcore_SRCS conf/settings_core.kcfgc ) + +kde4_add_library(okularcore SHARED ${okularcore_SRCS}) + +# Special handling for linking okularcore on OSX/Apple +IF(APPLE) + SET(OKULAR_IOKIT "-framework IOKit" CACHE STRING "Apple IOKit framework") +ENDIF(APPLE) + +target_link_libraries(okularcore ${OKULAR_IOKIT} ${KDE4_KIO_LIBS} ${KDE4_PHONON_LIBRARY} ${KDE4_KJSAPI_LIBRARY} ${MATH_LIB} ${KDE4_THREADWEAVER_LIBRARY} ) + +if(LibKScreen_FOUND) + target_link_libraries(okularcore ${LibKScreen_LIBRARY}) +endif(LibKScreen_FOUND) + +set_target_properties(okularcore PROPERTIES VERSION 5.0.0 SOVERSION 5 ) + +install(TARGETS okularcore ${INSTALL_TARGETS_DEFAULT_ARGS} ) + +install(FILES conf/okular.kcfg DESTINATION ${KCFG_INSTALL_DIR}) +install(FILES conf/okular_core.kcfg DESTINATION ${KCFG_INSTALL_DIR}) +install(FILES core/okularGenerator.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) + +# okularpart + +set(okularpart_SRCS + part.cpp + extensions.cpp + conf/preferencesdialog.cpp + conf/dlgaccessibility.cpp + conf/dlgdebug.cpp + conf/dlgeditor.cpp + conf/dlggeneral.cpp + conf/dlgannotations.cpp + conf/dlgperformance.cpp + conf/dlgpresentation.cpp + conf/widgetannottools.cpp + ui/embeddedfilesdialog.cpp + ui/annotwindow.cpp + ui/annotationmodel.cpp + ui/annotationpopup.cpp + ui/annotationpropertiesdialog.cpp + ui/annotationproxymodels.cpp + ui/annotationtools.cpp + ui/annotationwidgets.cpp + ui/bookmarklist.cpp + ui/fileprinterpreview.cpp + ui/findbar.cpp + ui/formwidgets.cpp + ui/guiutils.cpp + ui/ktreeviewsearchline.cpp + ui/latexrenderer.cpp + ui/minibar.cpp + ui/pageitemdelegate.cpp + ui/pagepainter.cpp + ui/pagesizelabel.cpp + ui/pageviewannotator.cpp + ui/pageview.cpp + ui/magnifierview.cpp + ui/pageviewutils.cpp + ui/presentationsearchbar.cpp + ui/presentationwidget.cpp + ui/propertiesdialog.cpp + ui/searchlineedit.cpp + ui/searchwidget.cpp + ui/sidebar.cpp + ui/side_reviews.cpp + ui/snapshottaker.cpp + ui/thumbnaillist.cpp + ui/toc.cpp + ui/tocmodel.cpp + ui/toolaction.cpp + ui/tts.cpp + ui/videowidget.cpp +) + +kde4_add_ui_files(okularpart_SRCS + conf/dlgaccessibilitybase.ui + conf/dlgeditorbase.ui + conf/dlggeneralbase.ui + conf/dlgannotationsbase.ui + conf/dlgperformancebase.ui + conf/dlgpresentationbase.ui +) + +kde4_add_kcfg_files(okularpart_SRCS conf/settings.kcfgc ) + +qt4_add_dbus_interfaces(okularpart_SRCS ${KDE4_DBUS_INTERFACES_DIR}/org.kde.KSpeech.xml) + +kde4_add_plugin(okularpart SHARED ${okularpart_SRCS}) + +target_link_libraries(okularpart okularcore ${KDE4_KPARTS_LIBS} ${KDE4_KPRINTUTILS_LIBS} ${MATH_LIB} ${QIMAGEBLITZ_LIBRARIES} ${KDE4_PHONON_LIBRARY} ${KDE4_SOLID_LIBRARY}) + +install(TARGETS okularpart DESTINATION ${PLUGIN_INSTALL_DIR}) + + +########### install files ############### + +install(FILES okular.upd DESTINATION ${DATA_INSTALL_DIR}/kconf_update) + +install( FILES okular_part.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +install( FILES part.rc part-viewermode.rc DESTINATION ${DATA_INSTALL_DIR}/okular ) + +########### cmake files ################# + +macro_write_basic_cmake_version_file(${CMAKE_CURRENT_BINARY_DIR}/OkularConfigVersion.cmake 0 11 80) + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/OkularConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/OkularConfigVersion.cmake + DESTINATION ${LIB_INSTALL_DIR}/cmake/Okular ) + +macro_display_feature_log() + diff --git a/okular/COPYING b/okular/COPYING new file mode 100644 index 00000000..8efe11aa --- /dev/null +++ b/okular/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/okular/COPYING.DOC b/okular/COPYING.DOC new file mode 100644 index 00000000..a988da5a --- /dev/null +++ b/okular/COPYING.DOC @@ -0,0 +1,397 @@ + GNU Free Documentation License + Version 1.2, November 2002 + + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/okular/COPYING.LIB b/okular/COPYING.LIB new file mode 100644 index 00000000..5bc8fb2c --- /dev/null +++ b/okular/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/okular/Mainpage.dox b/okular/Mainpage.dox new file mode 100644 index 00000000..f091e990 --- /dev/null +++ b/okular/Mainpage.dox @@ -0,0 +1,864 @@ +/** +\mainpage Okular, the unified document viewer + +\section okular_overview Overview + +- \ref okular_history +- \ref okular_design +- \ref okular_generators +- Website + +\authors Tobias König + +\licenses \lgpl + +\page okular_history Historical background + +Okular is the successor of kpdf, the PDF viewer in KDE 3. +kpdf was refactored and extended in a Google Summer of Code project to support not only +viewing PDF but also other types of document, e.g. PostScript files, images and many more. + +\page okular_design The Design of Okular + +To support a wide range of document formats, Okular was designed in a modular way, so you +have the following components: + + \li \ref Shell + \li \ref Part + \li \ref Okular::Document Class + \li \ref Okular::Generator + +The shell is the application which is started by the user as standalone application and +which embedds the part. The part contains all GUI elements of Okular, for example the +content list, the bookmark manager, menus and the graphical view of the document class. +The document class is an abstract presentation of the document content. It contains information +about every page of the document, its size, orientation etc. + +But somehow the document class must retrieve these information from the various types of documents. +This is the task of the Generators. Generators are plugins which are loaded at runtime and which +have the knowledge about the internal structure of the different document types. +They extract the needed information from the documents, convert the data into a common format and +pass them to the document class. + +Currently Generators for the following document types are available: + + \li Portable Document Format (PDF) + \li PostScript + \li Device Independent Format (DVI) + \li DeJaVu Format + \li Comic Books + \li Images (JPEG, PNG, GIF, and many more) + \li TIFF Image Format + \li FictionBook Format + \li Plucker Format + \li OpenDocument Text Format + \li Microsofts CHM Format + \li Microsofts XML Document Format + +Now the questions is how can these various formats be represented in a unified way? +Okular provides features like rotation, text search and extraction, zooming and many more, so how +does it match with the different capabilities of the formats? + +\section okular_design_basics Basics of Generators + +Lets start with the smallest commonness of all document formats: + + \li they have pages (one ore more) of a given size + \li pages can be represented as pictures + +So the first thing every Generator must support is to return the number of pages of a document. +Furthermore it must be able to return the picture of a page at a requested size. + +For vector based document formats (e.g. PDF or PostScript) the Generators can render the page for +the requested size, for static documents formats (e.g. images), the Generator must scale the +content according to the requested size, so when you zoom a page in Okular, the Generators are +just asked to return the page for the zoomed size. + +When the document class has retrieved the page pictures from the Generators, it can do further +image manipulation on it, for example rotating them or applying fancy effects. + +\section okular_design_text_support Generators with Text support + +Some document formats however support more functionality than just representing a page as an image. +PDF, PostScript, DVI and DeJaVu for example contains a machine readable representation of the +included text. For those document formats Okular provides additional features like text search, +text extraction and text selection. + +How is that supported by the Generators? + +To access the text from the documents the generators must extract it somehow and make it available +to the document class. However for the text selection feature the document class must also know where +the extracted text is located on the page. For a zoom factor of 100% the absolute position of +the text in the document can be used, however for larger or smaller zoom factors the position +must be recalculated. To make this calculation as easy as possible, the Generators return an +abstract represtentation (\ref Okular::TextPage) of the text which includes every character together +with its normalized position. Normalized means that the width and height of the page is +in the range of 0 to 1, so a character in the middle of the page is at x=0.5 and y=0.5. + +So when you want to know where this character is located on the page which is zoomed at 300%, you just +multiply the position by 3 * page width (and page height) and get the absolute position for this zoom level. + +This abstract text representation also allows an easy rotation of the coordinates, so that text selection +is available on rotated pages as well. + +\section okular_design_meta_information Meta Information + +Most documents have additional meta information: + + \li Name of the author + \li Date of creation + \li Version number + \li Table of Content + \li Bookmarks + \li Annotations + +These information can be retrieved by the generator as well and will be shown by Okular. + +\page okular_generators How to implement a Generator + +The power of Okular is its extensibility by Generator plugins. This section will describe how to +implement your own plugin for a new document type. + + \li \ref okular_generators_basic + \li \ref okular_generators_with_text + \li \ref okular_generators_threaded + \li \ref okular_generators_extended + +\section okular_generators_basic A Basic Generator + +To provide a short overview and don't reimplementing an existing generator we'll work on a Generator +for the Magic document format, a non existing, pure virtual format :) + +Lets assume we have some helper class (MagicDocument) which provides the following functionality for this +document format: + + \li Loading a document + \li Retrieving number of pages + \li Returning a fixed size picture representation of a page + +The class API looks like this + +\code +class MagicDocument +{ + public: + MagicDocument(); + ~MagicDocument(); + + bool loadDocument( const QString &fileName ); + + int numberOfPages() const; + + QSize pageSize( int pageNumber ) const; + + QImage pictureOfPage( int pageNumber ) const; + + private: + ... +}; +\endcode + +The methods should be self explaining, loadDocument() loads a document file and returns false on error, +numberOfPages() returns the number of pages, pageSize() returns the size of the page and pictureOfPage() +returns the picture representation of the page. + +Our first version of our Generator is a basic one which just provides page pictures to the document class. + +The API of the Generator looks like the following: + +\code +#include "magicdocument.h" + +#include + +class MagicGenerator : public Okular::Generator +{ + public: + MagicGenerator( QObject *parent, const QVariantList &args ); + ~MagicGenerator(); + + bool loadDocument( const QString &fileName, QVector &pages ); + + bool canGeneratePixmap() const; + void generatePixmap( Okular::PixmapRequest *request ); + + protected: + bool doCloseDocument(); + + private: + MagicDocument mMagicDocument; +}; +\endcode + +The implementation of the Generator looks like this: + +\code +#include + +#include "magicgenerator.h" + +static KAboutData createAboutData() +{ + KAboutData aboutData(...); + // fill the about data + return aboutData; +} + +OKULAR_EXPORT_PLUGIN(MagicGenerator, createAboutData()) + +MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args ) + : Okular::Generator( parent, args ) +{ +} + +MagicGenerator::~MagicGenerator() +{ +} + +bool MagicGenerator::loadDocument( const QString &fileName, QVector &pages ) +{ + if ( !mMagicDocument.loadDocument( fileName ) ) { + emit error( i18n( "Unable to load document" ), -1 ); + return false; + } + + pagesVector.resize( mMagicDocument.numberOfPages() ); + + for ( int i = 0; i < mMagicDocument.numberOfPages(); ++i ) { + const QSize size = mMagicDocument.pageSize( i ); + + Okular::Page * page = new Okular::Page( i, size.width(), size.height(), Okular::Rotation0 ); + pages[ i ] = page; + } + + return true; +} + +bool MagicGenerator::doCloseDocument() +{ + return true; +} + +bool MagicGenerator::canGeneratePixmap() const +{ + return true; +} + +void MagicGenerator::generatePixmap( Okular::PixmapRequest *request ) +{ + QImage image = mMagicDocument.pictureOfPage( request->pageNumber() ); + + image = image.scaled( request->width(), request->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); + + request->page()->setPixmap( request->id(), new QPixmap( QPixmap::fromImage( image ) ) ); + + signalPixmapRequestDone( request ); +} + +\endcode + +As you can see implementing a basic Generator is quite easy. The loadDocument() method opens the document file +and extracts the number of pages. For every page in the document it adds an Okular::Page object to the pages vector +which is passed in as method argument. Each page is initialized with its page number, width, height and initial rotation. +These page objects will be stored in the document object and act as a container for the picture representation +of the pages. This code is the same for nearly every Generator. On an failure the error() signal can be emitted +to inform the user about the issue. This code is the same for nearly every Generator. + +In the doCloseDocument() method you should close the document and free all resources you have allocated in openDocument(). + +Now we come to the picture creation methods. The canGeneratorPixmap() method returns whether the Generator is currently +able to handle a new pixmap generation request. For a simple Generator like our one that's always the case as it works +linear, however a multithreaded Generator might return false here if it is still waiting for one of its working +threads to finish. In this case the document class will try to request the pixmap later again. + +The generatePixmap() method does the actual fetching of the picture for a page. The page number, requested width and +height of the page is encapsulated in the passed Okular::PixmapRequest object. +So the task of the Generator is to create a pixmap of the requested page in the requested size and then store this +pixmap in the Okular::Page object which is associated with the page request. +When this task is finished, the Generator has to call signalPixmapRequestDone() with the page request object +as argument. This extra call is needed to allow the Generator to use signals and slots internally and create the +pixmap asynchronously. + +So now you have the code of a working Okular Generator, the next step is to tell Okular about the new plugin. +Like in other places in KDE that is done by .desktop files, which are installed to the services directory. + +Every Generator needs 3 .desktop files: + + \li libokularGenerator_<name>.desktop + \li okularApplication_<name>.desktop + \li okular<name>.desktop + +where <name> should be the name of the document format. So for our Magic Document Generator we +create the following 3 files: + + \li libokularGenerator_magic.desktop + \li okularApplication_magic.desktop + \li okularMagic.desktop + +with the following content: + +\verbatim +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Name=Magic Document +Comment=Magic Document backend for okular +ServiceTypes=okular/Generator +MimeType=application/x-magic; +X-KDE-Library=okularGenerator_magic +X-KDE-Priority=1 +X-KDE-okularAPIVersion=1 +X-KDE-okularHasInternalSettings=false +\endverbatim + +The first 6 fields are standard .desktop entries, the fields afterwards have a special meaning to Okular + + \li ServiceType Must be 'okular/Generator' for all Okular Generator Plugins + \li MimeType The mimetype or list of mimetypes of the supported document format(s) + \li X-KDE-Library The name of the plugin library + \li X-KDE-Priority When multiple Generators for the same mimetype exists, the one with the highest priority is used + \li X-KDE-okularAPIVersion The version of the Generator Plugin API ('1' currently) + \li X-KDE-okularHasInternalSettings Is 'true' when the Generator provides configuration dialogs + +The second .desktop file has the following content: + +\verbatim +[Desktop Entry] +Encoding=UTF-8 +MimeType=application/x-magic; +Terminal=false +Name=okular +GenericName=Document Viewer +Exec=okular %U %i -caption %c +Icon=okular +Type=Application +InitialPreference=7 +Categories=Qt;KDE;Graphics;Viewer; +NoDisplay=true +\endverbatim + +You can use the file as it is, you just have to adapt the mimetype. This file is needed to allow Okular +to handle multiple mimetypes. + +The third .desktop file looks like this: + +\verbatim +[Desktop Entry] +Encoding=UTF-8 +Icon=okular +Name=okular +ServiceTypes=KParts/ReadOnlyPart +X-KDE-Library=okularpart +Type=Service +MimeType=application/x-magic; +\endverbatim + +You can use the file as it is as well, you just have to adapt the mimetype. This file is needed to allow +the Okular part to handle multiple mimetypes. + +The last piece you need for a complete Generator is a CMakeLists.txt which compiles and installs the +Generator. Our CMakeLists.txt looks like the following: + +\verbatim +macro_optional_find_package(Okular) + +include_directories( ${OKULAR_INCLUDE_DIR} ${KDE4_INCLUDE_DIR} ${QT_INCLUDES} ) + +########### next target ############### + +set( okularGenerator_magic_SRCS generator_magic.cpp ) + +kde4_add_plugin( okularGenerator_magic ${okularGenerator_magic_SRCS} ) + +target_link_libraries( okularGenerator_magic ${OKULAR_LIBRARIES} ${KDE4_KDEUI_LIBS} ) + +install( TARGETS okularGenerator_magic DESTINATION ${PLUGIN_INSTALL_DIR} ) + +########### install files ############### + +install( FILES libokularGenerator_magic.desktop okularMagic.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +install( FILES okularApplication_magic.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) +\endverbatim + +The macro_optional_find_package(Okular) call is required to make the ${OKULAR_INCLUDE_DIR} and ${OKULAR_LIBRARIES} +variables available. + +Now you can compile the Generator plugin and install it. After a restart of Okular the new plugin is available +and you can open Magic documents. + +\section okular_generators_with_text A Generator with TextPage support + +In this section we want to extend our Generator to support text search, text extraction and selection +as well. As mentioned in \ref okular_design_text_support, the Generator must provide an Okular::TextPage +object for every page which contains readable text. + +Since we use the helper class MagicDocument to read the data from the document we have to extend it first, +so the new API looks as the following: + +\code +class MagicDocument +{ + public: + MagicDocument(); + ~MagicDocument(); + + bool loadDocument( const QString &fileName ); + + int numberOfPages() const; + + QSize pageSize( int pageNumber ) const; + + QImage pictureOfPage( int pageNumber ) const; + + class TextInfo + { + public: + typedef QList List; + + QChar character; + qreal xPos; + qreal yPos; + qreal width; + qreal height; + }; + + TextInfo::List textOfPage( int pageNumber ); + + private: + ... +}; +\endcode + +MagicDocument has the new internal class TextInfo now, which contains a character and +its absolute position on a page. Furthermore MagicDocument provides a method textOfPage() +which returns a list of all TextInfo objects for a page. + +That's really an optimistic API, in reality it is sometimes quite hard to find out +the position of single characters in a document format. + +With the extension of our helper class we can continue on extending our Generator now: + +\code +#include "magicdocument.h" + +#include + +class MagicGenerator : public Okular::Generator +{ + public: + MagicGenerator( QObject *parent, const QVariantList &args ); + ~MagicGenerator(); + + bool loadDocument( const QString &fileName, QVector &pages ); + + bool canGeneratePixmap() const; + void generatePixmap( Okular::PixmapRequest *request ); + + virtual bool canGenerateTextPage() const; + virtual void generateTextPage( Okular::Page *page, enum Okular::GenerationType type = Okular::Synchronous ); + + protected: + bool doCloseDocument(); + + private: + MagicDocument mMagicDocument; +}; +\endcode + +We have extended the MagicGenerator class by two methods canGenerateTextPage() and generateTextPage(). +The first method is equal to canGeneratePixmap(), it returns whether the Generator is currently able to +handle a new text page generation request. For linear Generators that should be always the case, however +when the generation is done in a separated worker thread, this method might return false. +In this case the document class will try to request the text page later again. + +The second method will generate the Okular::TextPage object for the passed page. Depending on the capabilities +of the Generator and the passed type parameter that is done synchronously or asynchronously. + +Let us take a look at the implementation of these methods in our MagicGenerator: + +\code +#include + +... + +MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args ) + : Okular::Generator( parent, args ) +{ + setFeature( TextExtraction ); +} + +bool MagicGenerator::canGenerateTextPage() const +{ + return true; +} + +void MagicGenerator::generateTextPage( Okular::Page *page, enum Okular::GenerationType ) +{ + MagicDocument::TextInfo::List characters = mMagicDocument.textOfPage( page->number() ); + if ( characters.isEmpty() ) + return; + + Okular::TextPage *textPage = new Okular::TextPage; + for ( int i = 0; i < characters.count(); ++i ) { + qreal left = characters[ i ].xPos / page->width(); + qreal top = characters[ i ].yPos / page->height(); + qreal right = (characters[ i ].xPos + characters[ i ].width) / page->width(); + qreal bottom = (characters[ i ].yPos + characters[ i ].height) / page->height(); + + textPage->append( characters[ i ].character, + new Okular::NormalizedRect( left, top, right, bottom ) ); + } + + page->setTextPage( textPage ); +} +\endcode + +As you can see the generateTextPage method just iterates over the list of characters returned +by our MagicDocument helper class and adds the character and its normalized bounding rect to +the Okular::TextPage object. At the end the text page is assigned to the page. We don't pay +attention to the GenerationType parameter here, if your Generator want to use threads, it should +check here whether the request shall be done asynchronously or synchronously and start the generation +according to that. Additionally we have to tell the Okular::Generator base class that we support +text handling by setting this flag in the constructor. + +In this state we can now search, select and extract text from Magic documents. + +\section okular_generators_threaded A Generator with Thread support + +Sometimes it makes sense to do the generation of page pictures or text pages asynchronously to +improve performance and don't blocking the user interface. This can be done in two ways, either +by using signals and slots or by using threads. Both have there pros and cons: + +
    +
  • Signals and Slots
  • +
      +
    • Pro: Can be used with backend libraries which are not thread safe
    • +
    • Con: Sometime difficult to implement
    • +
    +
  • Threads
  • +
      +
    • Pro: Easy to implement as you can make synchronous calls to the backend libraries
    • +
    • Con: Backend libraries must be thread safe and you must prevent race conditions by using mutexes
    • +
    +
+ +The signal and slots approach can be achieved with a normal Generator by calling Okular::Generator::signalPixmapRequestDone() +from a slot after pixmap generation has been finished. + +When using threads you should use a slightly different API, which hides most of the thread usage, to make +implementing as easy as possible. + +Let's assume the pictureOfPage() and textOfPage methods in our MagicDocument helper class are thread safe, +so we can use them in a multithreaded environment. +So nothing prevents us from changing the MagicGenerator to use threads for better performance. + +The new MagicGenerator API looks like the following: + +\code +#include "magicdocument.h" + +#include + +class MagicGenerator : public Okular::Generator +{ + public: + MagicGenerator( QObject *parent, const QVariantList &args ); + ~MagicGenerator(); + + bool loadDocument( const QString &fileName, QVector &pages ); + + protected: + bool doCloseDocument(); + + virtual QImage image( Okular::PixmapRequest *request ); + + virtual Okular::TextPage* textPage( Okular::Page *page ); + + private: + MagicDocument mMagicDocument; +}; +\endcode + +As you can see the canGeneratePixmap() generatePixmap(), canGenerateTextPage() and generateTextPage() methods have +been removed and replaced by the image() and textPage() methods. + +Before explaining why, we'll take a look at the implementation: + +\code + +MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args ) + : Okular::Generator( parent, args ) +{ + setFeature( TextExtraction ); + setFeature( Threaded ); +} + +QImage MagicGenerator::image( Okular::PixmapRequest *request ) +{ + QImage image = mMagicDocument.pictureOfPage( request->pageNumber() ); + + return image.scaled( request->width(), request->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); +} + +Okular::TextPage* textPage( Okular::Page *page ) +{ + MagicDocument::TextInfo::List characters = mMagicDocument.textOfPage( page->number() ); + if ( characters.isEmpty() ) + return 0; + + Okular::TextPage *textPage = new Okular::TextPage; + for ( int i = 0; i < characters.count(); ++i ) { + qreal left = characters[ i ].xPos / page->width(); + qreal top = characters[ i ].yPos / page->height(); + qreal right = (characters[ i ].xPos + characters[ i ].width) / page->width(); + qreal bottom = (characters[ i ].yPos + characters[ i ].height) / page->height(); + + textPage->append( characters[ i ].character, + new Okular::NormalizedRect( left, top, right, bottom ) ); + } + + return textPage; +} +\endcode + +So the first obviously thing is that both methods return a value instead of modifying the page directly. +The reason for this is that both methods are executed in its own thread, so the code executed in them can +block as long as it wants, it won't block the GUI anyway. Additionally we have to tell the Okular::Generator +base class that we can handle threads by setting the flag in the constructor. + +With only a small change we made our MagicGenerator multithreaded now! + +\section okular_generators_extended An Extended Generator + +Now we want to create a new generator with some additional functionality: + + \li Support for document information (author, creation date etc.) + \li Support for a table of content + \li Support for printing the document + \li Support for exporting the document as text + +The new Generator shall be able to handle HTML documents. We choose this format as example, because +we can use QTextDocument to load, render and print a HTML page, so a lot of code can be reused. + +The API of our HTMLGenerator looks like the following: + +\code +#include + +#include + +class HTMLGenerator : public Okular::Generator +{ + public: + HTMLGenerator( QObject *parent, const QVariantList &args ); + ~HTMLGenerator(); + + bool loadDocument( const QString &fileName, QVector &pages ); + + bool canGeneratePixmap() const; + void generatePixmap( Okular::PixmapRequest *request ); + + virtual const Okular::DocumentInfo* generateDocumentInfo(); + + virtual const Okular::DocumentSynopsis* generateDocumentSynopsis(); + + virtual bool print( KPrinter &printer ); + + virtual Okular::ExportFormat::List exportFormats() const; + + virtual bool exportTo( const QString &fileName, const Okular::ExportFormat &format ); + + protected: + bool doCloseDocument(); + + private: + QTextDocument *mTextDocument; + Okular::DocumentInfo mDocumentInfo; + Okular::DocumentSynopsis mDocumentSynopsis; +}; +\endcode + +The Generator doesn't support text search and selection, as the code would be quite complex, we'll show +how to do it in the next chapter \ref okular_generators_textdocument anyway. + +As you can see we have 5 new methods in the class: + + \li generateDocumentInfo() Creates an Okular::DocumentInfo (which is infact a QDomDocument) + which contains document information like author, creation time etc. + \li generateDocumentSynopsis() Creates an Okular::DocumentSynopsis (which is a QDomDocument as well) + which contains the table of content. + \li print() Prints the document to the passed printer. + \li exportFormats() Returns the supported export formats. + \li exportTo() Exports the document to the given file in the given format. + +Now that you know what the methods are supposed to do, let's take a look at the implementation: + +\code +#include +#include + +#include + +#include +#include + +#include "htmlgenerator.h" + +static KAboutData createAboutData() +{ + KAboutData aboutData(...); + // fill the about data + return aboutData; +} + +OKULAR_EXPORT_PLUGIN(HTMLGenerator, createAboutData()) + +HTMLGenerator::HTMLGenerator( QObject *parent, const QVariantList &args ) + : Okular::Generator( parent, args ), + mTextDocument( 0 ) +{ +} + +HTMLGenerator::~HTMLGenerator() +{ + delete mTextDocument; +} + +bool HTMLGenerator::loadDocument( const QString &fileName, QVector &pages ) +{ + QFile file( fileName ); + if ( !file.open( QIODevice::ReadOnly ) ) { + emit error( i18n( "Unable to open file" ), -1 ); + return false; + } + + const QString data = QString::fromUtf8( file.readAll() ); + + file.close(); + + mTextDocument = new QTextDocument; + mTextDocument->setHtml( data ); + mTextDocument->setPageSize( QSizeF( 600, 800 ) ); + + pages.resize( mTextDocument->pageCount() ); + + for ( int i = 0; i < mTextDocument->pageCount(); ++i ) { + Okular::Page * page = new Okular::Page( i, 600, 800, Okular::Rotation0 ); + pages[ i ] = page; + } + + mDocumentInfo.set( "author", "Tobias Koenig", i18n( "Author" ) ); + mDocumentInfo.set( "title", "The Art of Okular Plugin Development", i18n( "Title" ) ); + + Okular::DocumentViewport viewport = ... // get the viewport of the chapter + + QDomElement item = mDocumentSynopsis.createElement( "Chapter 1" ); + item.setAttribute( "Viewport", viewport.toString() ); + mDocumentSynopsis.appendChild( item ); + + viewport = ... // get the viewport of the subchapter + + QDomElement childItem = mDocumentSynopsis.createElement( "SubChapter 1.1" ); + childItem.setAttribute( "Viewport", viewport.toString() ); + item.appendChild( childItem ); + + return true; +} + +bool HTMLGenerator::doCloseDocument() +{ + delete mTextDocument; + mTextDocument = 0; + + return true; +} + +bool HTMLGenerator::canGeneratePixmap() const +{ + return true; +} + +void HTMLGenerator::generatePixmap( Okular::PixmapRequest *request ) +{ + QPixmap *pixmap = new QPixmap( request->width(), request->height() ); + pixmap->fill( Qt::white ); + + QPainter p; + p.begin( pixmap ); + + qreal width = request->width(); + qreal height = request->height(); + + p.scale( width / 600, height / 800 ); + + const QRect rect( 0, request->pageNumber() * 800, 600, 800 ); + p.translate( QPoint( 0, request->pageNumber() * -800 ) ); + d->mDocument->drawContents( &p, rect ); + p.end(); + + request->page()->setPixmap( request->id(), pixmap ); + + signalPixmapRequestDone( request ); +} + +const Okular::DocumentInfo* HTMLGenerator::generateDocumentInfo() +{ + return &mDocumentInfo; +} + +const Okular::DocumentSynopsis* HTMLGenerator::generateDocumentSynopsis() +{ + if ( !mDocumentSynopsis.hasChildNodes() ) + return 0; + else + return &mDocumentSynopsis; +} + +bool HTMLGenerator::print( KPrinter &printer ) +{ + QPainter p( &printer ); + + for ( int i = 0; i < mTextDocument->pageCount(); ++i ) { + if ( i != 0 ) + printer.newPage(); + + QRect rect( 0, i * 800, 600, 800 ); + p.translate( QPoint( 0, i * -800 ) ); + mTextDocument->drawContents( &p, rect ); + } +} + +Okular::ExportFormat::List HTMLGenerator::exportFormats() const +{ + return Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ); +} + +bool HTMLGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format ) +{ + QFile file( fileName ); + if ( !fileName.open( QIODevice::WriteOnly ) ) { + emit error( i18n( "Unable to open file" ), -1 ); + return false; + } + + if ( format.mimeType()->name() == QLatin1String( "text/plain" ) ) + file.writeBlock( mTextDocument->toPlainText().toUtf8() ); + + file.close(); + + return true; +} +\endcode + +Let's take a closer look at the single methods. In the loadDocument() method we try to open the +passed file name and read all the content into the QTextDocument object. By calling +QTextDocument::setPageSize(), the whole document is divided into pages of the given size. +In the next step we create Okular::Page objects for every page in the QTextDocument and fill +the pages vector with them. + +Afterwards we fill our Okular::DocumentInfo object with data. Since extracting the HTML meta data +would need a lot of code we work with static data here. [to be continued] +*/ + +// DOXYGEN_EXCLUDE = conf generators shell ui +// DOXYGEN_FILE_PATTERNS = core/*.h *.dox diff --git a/okular/Messages.sh b/okular/Messages.sh new file mode 100644 index 00000000..d4a47052 --- /dev/null +++ b/okular/Messages.sh @@ -0,0 +1,4 @@ +#!/bin/sh +$EXTRACTRC *.rc */*.rc >> rc.cpp || exit 11 +$EXTRACTRC $(find conf/ -name "*.ui") $(find core/ -name "*.ui") $(find ui/ -name "*.ui") $(ls . | grep -E '\.ui') >> rc.cpp || exit 12 +$XGETTEXT $(find conf/ -name "*.cpp" -o -name "*.h") $(find core/ -name "*.cpp" -o -name "*.h") $(find ui/ -name "*.cpp" -o -name "*.h") $(find shell/ -name "*.cpp" -o -name "*.h") $(ls . | grep -E '\.cpp$') $(ls . | grep -E '\.h$') -o $podir/okular.pot diff --git a/okular/OkularConfig.cmake b/okular/OkularConfig.cmake new file mode 100644 index 00000000..59537c66 --- /dev/null +++ b/okular/OkularConfig.cmake @@ -0,0 +1,24 @@ +# get from the full path to OkularConfig.cmake up to the base dir dir: +get_filename_component( _okularBaseDir ${CMAKE_CURRENT_LIST_FILE} PATH) +get_filename_component( _okularBaseDir ${_okularBaseDir} PATH) +get_filename_component( _okularBaseDir ${_okularBaseDir} PATH) +get_filename_component( _okularBaseDir ${_okularBaseDir} PATH) + + +# find the full paths to the library and the includes: +find_path(OKULAR_INCLUDE_DIR okular/core/document.h + HINTS ${_okularBaseDir}/include + NO_DEFAULT_PATH) + +find_library(OKULAR_CORE_LIBRARY okularcore + HINTS ${_okularBaseDir}/lib + NO_DEFAULT_PATH) + +set(OKULAR_LIBRARIES ${OKULAR_CORE_LIBRARY}) + +# Compat: the old FindOkular.cmake was setting OKULAR_FOUND and the +# new one sets Okular_FOUND. +if(OKULAR_INCLUDE_DIR AND OKULAR_CORE_LIBRARY) + set(OKULAR_FOUND TRUE) +endif() + diff --git a/okular/OkularConfigureChecks.cmake b/okular/OkularConfigureChecks.cmake new file mode 100644 index 00000000..70fe8768 --- /dev/null +++ b/okular/OkularConfigureChecks.cmake @@ -0,0 +1,18 @@ + +option( + OKULAR_FORCE_DRM + "Forces okular to check for DRM to decide if you can copy/print protected pdf. (default=no)" + OFF +) +if (OKULAR_FORCE_DRM) + set(_OKULAR_FORCE_DRM 1) +else (OKULAR_FORCE_DRM) + set(_OKULAR_FORCE_DRM 0) +endif (OKULAR_FORCE_DRM) + +# at the end, output the configuration +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/config-okular.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/config-okular.h +) + diff --git a/okular/README.internals.png b/okular/README.internals.png new file mode 100644 index 0000000000000000000000000000000000000000..8a9769f1b7205555bde44b158f648bdef972fe68 GIT binary patch literal 33807 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�dvn4L_%f1gDU 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 Oc`v0TSs`xx<^KR0K&v7E literal 0 HcmV?d00001 diff --git a/okular/TODO b/okular/TODO new file mode 100644 index 00000000..49925f10 --- /dev/null +++ b/okular/TODO @@ -0,0 +1,274 @@ +TODO - okular +Legend: + ADD - ADDed (new feature) + CHG - CHanGed (existing behavior) + FIX - FIXed (bug or regression) + MRG - MeRGed (code from a branch or a patch) + +Bugs and wishes to close when moving okular to kdegraphics: +-> WISH 91146: Add capability to extract plain text from a pdf file. +-> WISH 91251: "Magic marker" tool for higlighting text +-> WISH 91809: kpdf cant fill in formulars +-> WISH 93416: support for pdf sticky notes +-> BUG 97869: kpdf fails to properly hide the context menu +-> WISH 99352: Cannot change orientation of pdf file +-> WISH 99787: kpdf needs to have a bookmark list +-> WISH 100341: first page at right, in two pages mode +-> WISH 102523: wish: if opening second file, file/open dir should be dirname($current_file) +-> WISH 102704: Handle gzipped and bzipped PDFs +-> WISH 102788: Current page number visible +-> WISH 103051: make notes on pdf files +-> BUG 106546: Search with "/" and ALT+TAB +-> WISH 107998: kpdf horizontal scroll bar +-> WISH 109346: ability to select everything on page(s) and use ctrl+a to select all (text) +-> BUG 110440: Almost all options in Find dialog are always disabled +-> WISH 111651: Usability: Make find-as-you-type widget more distinguishable +-> WISH 114648: An option to deny documents' fullscreen requests is desirable. +-> WISH 115557: Automatically detect and remove white page borders +-> WISH 117045: text search: continue backwards +-> WISH 117449: kpdf should read files from stdin with the - option +-> WISH 117805: Wish: Select pages like kghostview +-> WISH 118872: An option to read all the document with Kttsd +-> WISH 118938: No way to find out the page size(s) +-> WISH 119084: pdf attachment support: saving/opening attachments in pdf... +-> WISH 119455: use poppler library for kpdf +-> WISH 119919: make it possible to disable transition in presentation mode +-> WISH 122863: Show page numbers of chapters/bookmarks in contents tab +-> BUG 130123: kpdf text selection inconsistent with the rest of KDE +-> WISH 131045: Add context menu for URLs +-> WISH 132152: Usability: contents / pdfbookmarks panel is not accessible via keyboard only +-> WISH 134115: specifing to kpdf the first-opened page from command line +-> WISH 134646: remember (and restore) zoom per file not per app +-> WISH 136820: would like a reload button or menu item in KPDF +-> WISH 137905: command-line option for presentation mode +-> WISH 145345: goto page: update slider while editing page number + +In progress [working on]: +-> search: backward text search (BR117045) +-> various backends: fill about data information +-> gui: add config for gfx (yes/no) and text (yes/no/kde) antialias +-> core: provide a binary compatible okularcore library to extend the format capabilities +-> KPDF -> okular conversion of configuration & datafiles {configuration(done), document data(in progress), kwallet passwords(missing)} +-> inverse search (BR113191) + find with an usability expert the "best" way to activate such a link + provide a configuration widget to choose the editor and configure a custom one +-> toc: highlight the row of the current page (BR127358) + (check if there's a better way to do the highlighting - in case just edit TOCModel) +-> annotations: renderers in PagePainter (for the 6 annots) +-> annotations: handlers in PageView for WindowAnnots and others.. +-> annotations: creators in PageViewAnnotator (40% done) +-> annotations: framework (BR67300,BR62793) and tools (BR67300,BR91251) +-> annotations: add the annot pane (cool plz!) +-> rotate the whole document / individual pages (on screen/print?) (BR99352) +-> link: add a Okular::Anchor link class +-> pageview: update layout when toggling the 'ebook atyle alignment' + +More items (first items will enter 'In progress list' first): +-> annotations: "move" and "delete" tools +-> rethink which buttons show in the toolbar (BR139825) +-> annotations: configurabile tools in the toolbar (BR137903) +-> pageview: add vp damaging queue and flush function. add coolfx to smoothmove using damaging +-> annotations: toolbar: display tooltip only the first time +-> annotations: provide nice wizards. Make it easy to deliver/copy/move the xml files (by Uga) +-> GHNS: get hot new ebooks on the supported formats (pdf for now) (enrico) +-> pageview: add scrollbar marks for bookmarks (like kate) +-> pageview: change document viewport after mouse scrolling ended (not every scroll frame) + this will give better mvc consistancy +-> other info on pdfs properties: number of accessed times, time spent on pdf, ... +-> part: collapsing the left panel will hide it (activate hiding action) +-> preload: add a delay when forward loading pages +-> bookmarks: fastmarks (new bookmark concept with tab-like signs, shortcuts, etc) (BR +-> viewport restoring: sometimes it seems to restore the viewport a bit under where it was +-> core: Delay TOC (DocumentSynopsis) generation (and move it on thread) +-> core: add a way to handle "named xpdf links" in Okular::Action instead of resolving all + dests when displaying a page (speedups a lot generation of page with many links) +-> toolbar: move the toolbar to the top of pageview (left panel is really at the left of the toolbar) +-> thumbnailslist: clinking on highlighted rect should bring the viewport to that search result (BR98334) +-> Dom framework to cache document metadata. It should archive those types of data: + (NOTE: already Dom'ed object is marked with 'X') + - Synopsis will go there after 1st generation (so we can edit it too) + - Document info (after the 1st gen) + - Bookmarked pages + - Current Viewport and 10 history steps + - Edited pages (rotated/with_data for example) + - Overlay editing (highlighting/notations/etc..) + - Presentation related overrides (FS mode, individual / global transitions) + - ..more stuff.. but this isn't a problem, since a QDom is flexible by design + The Object will reside into the Document and must not be accessible by Observers in + a direct way. Dom format, relations to other classes and accessing must be specified + in a separated diagram or text file. + - Plus think at storing xml data to an external shared server.. something that + has to do with versioning data.. (shared annotations, bookmarks & co.) +-> add okular manual in PDF format loaded on the first startup or on menu->help->manual + this visually explains basic usage, mouse buttons functions & more.. +-> ADD: click over image allows "save image" [60% done (activerect of type image)] +-> bookview: 3d opengl widget for viewing the document as a real book (turning pages, etc..) +-> wallet: use asynchronous interface (to prevent ui-blocking) +-> restore a location from a given url (like http:/someurl?stringForViewport) (BR99240) +-> automatic online dictionaries / translators (BR80338) +-> sidebar: evaluate wether to make the left toolbox auto-hiding (kicker like) (BR94495) +-> add OCR for building TextPages out of pure graphical (aka scanned) pages +-> presentation: provide a pageX/totalPages indicator in addition to the circle one +-> presentation: implement missing transitions (6/11 done) (BR139284) +-> presentation: save a flag (to the xml) to open a pdf in presentation mode +-> presentation: wheel not visible on black. gradient appreciated on lighter backgrounds. +-> presentation: 2 pages per view, for users reading ebooks with wide screens. +-> goto 'logical' page (usually differs from pdf's page) (req. by Luca Burrelli) +-> move some document related features from part to the document (see find, goto dialog, ...) +-> tools: ruler (BR155737), measure: distance, perimeter, ?area?, color picker +-> export: export to other formats keeping formatting (PS is basically printing. PNG is easy too) +-> export: extract images (have a look at ImageOutputDev.cc and pdfimages.cc from xpdf (not in our xpdf sources)) +-> history as a toolbox child (collecting Doc's viewport changes notifications) +-> take care of TODOs in code +-> cleanup code and update README.png + +Icons needed: +- okular (ToolBAR): 1 continuous, 2 tied-pages, 3 Normal (not scroll only, it + performs other ops), 4 Zoom, 5 Select, 6 Annotation/Review +- okular (Edit Tools): text highlighter, line, polygon, ellipse, text underline +- COMMON (here in ToolBox): Toc, Annotations + +Done (newest features come first): +-> ADD: core: forms support (BR91809) +-> ADD: kspeech TTS interface. speech {document(BR118872) / page / selection} +-> CHG: thumbnails list: refactor to do internal rendering as pageview does +-> ADD: select more than one annotation in the annotation tree (BR155668) +-> ADD: text selection: select all (BR109346) +-> ADD: text selection in wordprocessor style (BR130123) +-> ADD: open gzipped and bzipped documents (BR102704) +-> ADD: thumbnailslist: show Viewport in a blended way, allowing to be dragged (BR135521) +-> CHG: async way to read the fonts, no more blocking properties dialog +-> CHG: use ThreadWeaver for the rotating jobs +-> ADD: save/restore the values of the form fields when closing/opening a documents +-> ADD: "dummy" mode for a very simple interface when the okular part is in eg printing preview +-> ADD: bookmarks: improved interface, with easy handling of the bookmarks of all the documents (BR99787,BR136113) +-> ADD: Reload action to force the reload of the current document (BR136820) +-> CHG: renamed Okular::Link to Okular::Action +-> ADD: presentation: add page counter and a widget to manually set pages on the top bar +-> ADD: basic support for Text and Line DjVu annotations +-> FIX: regression: popup context menu when using right button in selection mode (BR99315) +-> ADD: annotations: add config option for default name change +-> ADD: presentation: add a red pencil that creates ink annotations +-> CHG: support links with shapes different than a rectangle +-> ADD: export: give the generators the ability to export all the text to plain file (if supported), the PDF generator already use this (BR91146) +-> CHG: search: use shortcut for 'find next' action (not the default one) in find-ahead +-> CHG: use shortcuts for next and prev page even in presentation mode (by Tobias Koenig) +-> ADD: bookmarks: go to next/previous actions (showing in thumbnailslist rmb popup too) +-> CHG: open dialog: switch to directory where the already opened (local) file is +-> ADD: toc: add search bar (a 'prune on type' lineedit like in thumbnails widget) (BR99349) +-> FIX: kfile-plugins/pdf: provide internal support and rewrite that plugin (drops xpdf dependancy from kdegraphics) (by ktech) +-> CHG: core: abstract TextPage generation (the last xpdf dependant class!) +-> CHG: create a DocumentInfo structure for passing the font information from generators to the document +-> ADD: presentation: link following (BR98388) +-> ADD: add ebook-friendly alignment option, better layouting (BR100341) +-> FIX: random crash when closing kpdf with kapp->quit() (don't use Settings:: in destructors) +-> FIX: implement links for starting end exiting from presentation mode (pdf FullScreen, Close actions) +-> CHG: cursor wraps on screen for dragging the page/dynZooming +-> FIX: raised scaling performance by 10x times for typ usage, removed odd cases, speedups to viewScroll and dynZoom +-> ADD: annotations: complete xml storage/retrieval of internal annotations +-> CHG: page properties (bookmarks, annotations, ...) storage moved to Page class +-> ADD: sidebar: add shortcut for showing/hiding it (BR99316) +-> ADD: annotations: PDF1.6 reader (PDF's annotations -> our data structures) +-> ADD: Internal data structures for annotations handling. +-> FIX: rmb when no doc displayed to restore menu +-> ADD: Save zoom setting on exit +-> ADD: Put fonts used by the document on the properties dialog +-> ADD: partial implementation of XYZ links +-> ADD: google-like search on thumbnails +-> ADD: use kde wallet for storing passwords of protected files +*> The branch 'kpdf_annotations' was created at this point. [2005-Feb-12] +-> FIX: trigger redraw on 'filter text' on current page (need new highlight engine first) +-> ADD: Obey DRM is now a configuration option +-> FIX: fixed viewport saving/restoring (+performance fix) on presentation mode +-> FIX: leakfix when closing document while thread was running (no more leaks now) +-> FIX: direct hi-performance pixels manipulation for highlighting (instead of the obsoleted setRasterOp) +-> CHG: new search api. supports multiple searches at once, multiple highlights per page +-> ADD: pageView moves smoothly when searching / moving in history +-> ADD: better bookmark rendering in thumbnailslist (show 'clip overlay') +-> CHG: changes and cleanups in pageView's mouse handling functions +-> ADD: KTTSD simple support: speech selection using kspeech api via pure dcop (don't break compatibility) +-> CHG: right click and drag while in 'normal' mode changes to 'selection' mode and selects +-> FIX: complete valgrind check and leakfix (2 leaks were present) [27-Jan-04] +-> ADD: history, forward/back history actions, history links and xml storage (10 steps) +-> ADD: rmb popup on thumbnailslist (the popup shared with pageView: same behavior) +-> ADD: display 'current page' / 'total pages' with analog indicator, active labels, etc +-> CHG: Presentation mode is now Ctrl+Shift+p instead of F9 because it was colliding with Konqueror's toggle sidebar +-> FIX: various in memory unallocator, preload with single pages, pageview +-> FIX: optimized pageView (removed 1 waster req on start, lowered reqs) +-> FIX: memory unloading order and hard swap avoiding +-> CHG: open and open-recent buttons unified in Shell +-> CHG: lens icon for the find-ahead messages +-> ADD: page preloading +-> FIX: smarter memory management / prioritize queries +-> ADD: type ahead search in pageview (type '/' then the word to search..) (JakubS) +-> FIX: scroll page if the searched string is not visible +-> FIX: use a global Viewport over the document (linked views, real link following, location restoring, etc) +-> FIX: wrong zoom buttons order (BR74248) (check consistancy with kdvi/kviewshell/kghostview/.. (not konq)) +-> ADD: presentation: cursor modes: hidden, visible, hidden with delay (Tobias) +-> ADD: presentation: default transition which is used when no transition is defined in document (Tobias) +-> ADD: presentation: support for automatic advance and loop on last page (Tobias) +-> ADD: presentation: add additional presentation page to settings dialog (Tobias) +-> CHG: presentation: the round wheel indicator can be clicked to change page +-> FIX: layout margins on pageView +-> ADD: restore the last active page when a file is opened again +-> ADD: Save bookmarks into a file so you they get recovered when opening the same file again (Albert) +-> FIX: searchline back to work +-> CHG: DocumentInfo is now a DomTree and the properties dialog is dynamically generated (Tobias) +-> ADD: Presentation transitions are loaded from the pdf files as well as fullscreen state (Tobias) +*> Merged on HEAD on 2005-01-02 (The branch is frozen, development continues here) +-> FIX: Fix my update cursor FIX :-D +-> ADD: Make kpdf aware of Find and GoToPage actions +-> FIX: Update cursor correctly when a link moves to a page and the cursor is over a link on that page +-> ADD: Asynchronous PDF Generator implementation (for the user: faster UI, preloading, etc..) +-> FIX: Memory manager (free cache if needed, avoid disk swap and oom) +-> ADD: Presentation View (only the 'glitter' transition implemented for now) +-> FIX: FixPack1 [dyn_zoom repaints, initial panel width, zoom_lineedit focus proxy, searchwidget refactor{thumbs restoring on clear, buttons size, less code}, highlight bookmarked thumbnails] +-> FIX: Some fullScreen loving, if we are on fullscreen put an action on RMB menu ti get out of it, if we were on fullScreen mode on exit bring back correctly if we were also seeing toolbar or menubar +-> FIX: When in non continuous mode and scrolling up a page, set the viewport at the bottom of the page (Albert) +-> ADD: Show the window maximized when the user opens the program for the very first time (Albert) +-> ADD: Use 'Generators' as providers for contents generation +-> ADD: Add properties dialog (Albert) +-> ADD: Support for show/hide menubar in rmb menu, different from HEAD so that supports Konqueror too (Albert) +-> ADD: Watch File option (Albert) +-> ADD: import Marco Martin's "another kpdf icon" (kde-look: 16146) (Albert) +-> ADD: dynamic zoom with mid mouse button (click and drag up-down to zoom in-out) +-> FIX: merge select text & select gfx, two sections on the same pop-up menu +-> ADD: reading aids (inverted display, recolor, black/white, draw link border, draw image border) +-> FIX: zoom preserved when switching modes and flickerless drawing +-> ADD: Printing as PS instead of as image (Albert) +-> ADD: Remember page on session logout and put the document in it on session restore (Albert) +-> ADD: gfx capturing tool +-> ADD: composited renderer framework (in addition to a fast light one) +-> FIX: pageview repaint done internally (speed boost and reduced memory consumption) +-> ADD: KConfigXT settings framework and Accessibility config (acc. code mostly not done) +-> FIX: workaround for scrollview bug 1/2 (painting hidden widgets under certain circumstances) +-> ADD: zoom into a rect defined by mouse (aka zoom to window) +-> FIX: sheet rotation in landscape case +-> ADD: Some dcop functions (goToPage, openDocument and give # of pages) (Albert) +-> MRG: link following ('actionMovie' kind is missing) +-> ADD: text selection (rectangular blocks) in selection mode +-> ADD: autoscroll page with Shift+Up/Dn keys (exact konqueror's behavior) +-> CHG: remake single page mode +-> FIX: zoom buttons in sync with text +-> ADD: continuous mode +-> ADD: multiple pages per view (gui selects 1 or 2 ppv) +-> MRG: the option to open password protected files (from head) +-> MRG: the Table Of Contents (from head) +-> ADD: a 'search bar' with prune-as-you-type feature +-> MRG: Albert's search ported and implemented case sensitive +-> CHG: smart handling of pixmap using an Observer ID (thumbnails are gone, only pixmaps now) +-> FIX: some toolbar/menu changes +-> ADD: outline bottom and right edges (of pages) +-> FIX: centering pages in the view +-> FIX: kpdf output at 100% has exactly the same size as acroread now +-> CHG: qsplitter layouting +-> FIX: zooming works as expected (and added 'fit to page' too) +-> ADD: new go to page dialog +-> GHG: previews sorted by visible areas (prioritize items where the scrollbar is) +-> FIX: previews speedup: 50-100% +-> CHG: use local instead of X memory for thumbnails (..) +-> MRG: merge lots of kpdf_part and part (centralview) code (to simplify/cleanup) +*> The branch 'kpdf_experiments' was created at this point. Code refactoring started. +-> ADD: Completely use xpdf code for rendering that solves most font problems (Albert) +-> MRG: Replace xpdf version with lastest one (3.00) that supports PDF 1.5 (Albert) +-> newest added features are at the top of the list diff --git a/okular/VERSION b/okular/VERSION new file mode 100644 index 00000000..d7920ff6 --- /dev/null +++ b/okular/VERSION @@ -0,0 +1 @@ +okular v0.20.3 diff --git a/okular/aboutdata.h b/okular/aboutdata.h new file mode 100644 index 00000000..8d7d5d11 --- /dev/null +++ b/okular/aboutdata.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _ABOUTDATA_H_ +#define _ABOUTDATA_H_ + +#include + +#include "core/version.h" + +inline KAboutData okularAboutData( const char* name, const char* iname ) +{ + KAboutData about( + name, //"okular", + "okular", + ki18n(iname), //I18N_NOOP("okular"), + OKULAR_VERSION_STRING, + ki18n("Okular, a universal document viewer"), + KAboutData::License_GPL, + ki18n("(C) 2002 Wilco Greven, Christophe Devriese\n" + "(C) 2004-2005 Enrico Ros\n" + "(C) 2005 Piotr Szymanski\n" + "(C) 2004-2009 Albert Astals Cid\n" + "(C) 2006-2009 Pino Toscano"), + KLocalizedString(), + "http://okular.kde.org" + ); + + about.addAuthor(ki18n("Pino Toscano"), ki18n("Former maintainer"), "pino@kde.org"); + about.addAuthor(ki18n("Tobias Koenig"), ki18n("Lots of framework work, ODT and FictionBook backends"), "tokoe@kde.org"); + about.addAuthor(ki18n("Albert Astals Cid"), ki18n("Current maintainer"), "aacid@kde.org"); + about.addAuthor(ki18n("Piotr Szymanski"), ki18n("Created Okular from KPDF codebase"), "djurban@pld-dc.org"); + about.addAuthor(ki18n("Enrico Ros"), ki18n("KPDF developer"), "eros.kde@email.it"); + about.addCredit(ki18n("Eugene Trounev"), ki18n("Annotations artwork"), "eugene.trounev@gmail.com"); + about.addCredit(ki18n("Jiri Baum - NICTA"), ki18n("Table selection tool"), "jiri@baum.com.au"); + about.addCredit(ki18n("Fabio D'Urso"), ki18n("Annotation improvements"), "fabiodurso@hotmail.it"); + + return about; +} + +#endif diff --git a/okular/active/CMakeLists.txt b/okular/active/CMakeLists.txt new file mode 100644 index 00000000..1a8ff748 --- /dev/null +++ b/okular/active/CMakeLists.txt @@ -0,0 +1,10 @@ + +macro_optional_find_package(ActiveApp) +macro_log_feature(ACTIVEAPP_FOUND "ActiveApp" "Support for developing applications for Plasma Active" "https://projects.kde.org/projects/extragear/base/plasma-mobile" FALSE "0.1") + +if (ACTIVEAPP_FOUND) + add_subdirectory( app ) +endif (ACTIVEAPP_FOUND) + +add_subdirectory( components ) + diff --git a/okular/active/app/CMakeLists.txt b/okular/active/app/CMakeLists.txt new file mode 100644 index 00000000..dd0c75da --- /dev/null +++ b/okular/active/app/CMakeLists.txt @@ -0,0 +1,11 @@ + +include_directories( ${KDE4_INCLUDES} ${QT_INCLUDES} ) + +add_subdirectory(src) + +# QML-only thing +install(DIRECTORY package/ DESTINATION ${DATA_INSTALL_DIR}/plasma/packages/org.kde.active.documentviewer) + + +install(FILES active-documentviewer.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) + diff --git a/okular/active/app/Messages.sh b/okular/active/app/Messages.sh new file mode 100644 index 00000000..3eb4c923 --- /dev/null +++ b/okular/active/app/Messages.sh @@ -0,0 +1,7 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` >> rc.cpp +$XGETTEXT `find . -name \*.qml` -L Java -o $podir/org.kde.active.documentviewer.temp.pot +$XGETTEXT `find src/ -name "*.cpp" -o -name "*.h"` -o $podir/org.kde.active.documentviewer.pot +$MSGCAT $podir/org.kde.active.documentviewer.temp.pot $podir/org.kde.active.documentviewer.pot -o $podir/org.kde.active.documentviewer.pot +rm -f $podir/org.kde.active.documentviewer.temp.pot +rm -f rc.cpp diff --git a/okular/active/app/active-documentviewer.desktop b/okular/active/app/active-documentviewer.desktop new file mode 100755 index 00000000..ba3467c3 --- /dev/null +++ b/okular/active/app/active-documentviewer.desktop @@ -0,0 +1,151 @@ +[Desktop Entry] +Name=Reader +Name[ar]=التصيير +Name[bg]=Четец +Name[bs]=Čitač +Name[ca]=Lector +Name[ca@valencia]=Lector +Name[cs]=Čtečka +Name[da]=Læser +Name[de]=Lesegerät +Name[el]=Πρόγραμμα ανάγνωσης +Name[en_GB]=Reader +Name[es]=Lector +Name[et]=Lugeja +Name[fi]=Lukija +Name[fr]=Lecteur +Name[ga]=Léitheoir +Name[gl]=Lector +Name[hu]=Olvasó +Name[ia]=Lector +Name[is]=Lesari +Name[it]=Lettore +Name[kk]=Оқу құралы +Name[ko]=리더 +Name[lt]=Skaitytuvas +Name[mr]=वाचक +Name[nb]=Leser +Name[nds]=Leser +Name[nl]=Lezer +Name[pa]=ਰੀਡਰ +Name[pl]=Czytnik +Name[pt]=Leitor +Name[pt_BR]=Leitor +Name[ro]=Cititor +Name[ru]=Просмотрщик +Name[sk]=Čítačka +Name[sl]=Bralnik +Name[sr]=Читач +Name[sr@ijekavian]=Читач +Name[sr@ijekavianlatin]=Čitač +Name[sr@latin]=Čitač +Name[sv]=Läsprogram +Name[tr]=Okuyucu +Name[ug]=ئوقۇغۇ +Name[uk]=Переглядач +Name[x-test]=xxReaderxx +Name[zh_CN]=阅读器 +Name[zh_TW]=閱讀器 +GenericName=Document viewer +GenericName[ar]=عارض المستندات +GenericName[bg]=Преглед на документи +GenericName[bs]=Prikazivač dokumenata +GenericName[ca]=Visualitzador de documents +GenericName[ca@valencia]=Visualitzador de documents +GenericName[cs]=Prohlížeč dokumentů +GenericName[da]=Dokumentfremviser +GenericName[de]=Dokumentenbetrachter +GenericName[el]=Προβολέας εγγράφων +GenericName[en_GB]=Document Viewer +GenericName[es]=Visor de documentos +GenericName[et]=Dokumendinäitaja +GenericName[fi]=Asiakirjakatselin +GenericName[fr]=Afficheur de document +GenericName[ga]=Amharcán cáipéisí +GenericName[gl]=Visor de documentos +GenericName[hu]=Dokumentummegjelenítő +GenericName[ia]=Visor de documento +GenericName[is]=Skjalaskoðari +GenericName[it]=Visore di documenti +GenericName[ja]=文書ビューア +GenericName[kk]=Құжатты қарау құралы +GenericName[ko]=문서 뷰어 +GenericName[lt]=Dokumentų žiūryklė +GenericName[mr]=दस्तऐवज प्रदर्शक +GenericName[nb]=Dokumentviser +GenericName[nds]=Dokmentkieker +GenericName[nl]=Documentenviewer +GenericName[pa]=ਡੌਕੂਮੈਂਟ ਦਰਸ਼ਕ +GenericName[pl]=Przeglądarka dokumentów +GenericName[pt]=Visualizador de documentos +GenericName[pt_BR]=Visualizador de documentos +GenericName[ro]=Vizualizor de documente +GenericName[ru]=Просмотр документов +GenericName[sk]=Prehliadač dokumentov +GenericName[sl]=Pregledovalnik dokumentov +GenericName[sr]=Приказивач докумената +GenericName[sr@ijekavian]=Приказивач докумената +GenericName[sr@ijekavianlatin]=Prikazivač dokumenata +GenericName[sr@latin]=Prikazivač dokumenata +GenericName[sv]=Dokumentvisare +GenericName[tr]=Belge görüntüleyici +GenericName[uk]=Переглядач документів +GenericName[x-test]=xxDocument viewerxx +GenericName[zh_CN]=文档查看器 +GenericName[zh_TW]=文件檢視器 +Comment=Viewer for various types of documents +Comment[ar]=عارض للعديد من أنواع المستندات +Comment[bg]=Преглед на различни видове документи +Comment[bs]=Pregledač raznih vrsta dokumenata +Comment[ca]=Visualitzador de diversos tipus de documents +Comment[ca@valencia]=Visualitzador de diversos tipus de documents +Comment[cs]=Prohlížeč různých typů dokumentů +Comment[da]=Fremviser af diverse dokumenttyper +Comment[de]=Betrachter für verschiedene Arten von Dokumenten +Comment[el]=Πρόγραμμα προβολής για διάφορους τύπους εγγράφων +Comment[en_GB]=Viewer for various types of documents +Comment[es]=Visor de diversos tipos de documentos +Comment[et]=Eri tüüpi dokumentide näitaja +Comment[fi]=Monenlaisten asiakirjojen katseluohjelma +Comment[fr]=Afficheur pour différents types de documents +Comment[ga]=Amharcán le haghaidh cáipéisí éagsúla +Comment[gl]=Visor de varios tipos de documentos. +Comment[hu]=Megjelenítő különféle típusú dokumentumokhoz +Comment[ia]=Visor pro varie typos de documento +Comment[is]=Skoðari fyrir ýmsar gerðir skjala +Comment[it]=Visore per vari tipi di documenti +Comment[kk]=Түрлі құжаттар қарау құралы +Comment[ko]=여러 형식의 문서 뷰어 +Comment[lt]=Žiūryklė įvairiems dokumentų tipams +Comment[mr]=विविध प्रकारच्या दस्तऐवजांचा प्रदर्शक +Comment[nb]=Framviser for forskjellige dokumenttyper +Comment[nds]=Kieker för en Reeg Dokmenttypen +Comment[nl]=Viewer voor verschillende typen documenten +Comment[pa]=ਕਈ ਕਿਸਮ ਦੇ ਡੌਕੂਮੈਂਟ ਵੇਖਾਉਣ ਲਈ ਦਰਸ਼ਕ +Comment[pl]=Przeglądarka dla różnych typów dokumentów +Comment[pt]=Visualizador de vários tipos de documentos +Comment[pt_BR]=Visualizador para vários tipos de documentos +Comment[ro]=Vizualizor pentru diferite tipuri de documente +Comment[ru]=Программа для просмотра различных типов документов +Comment[sk]=Prehliadač pre rôzne typy dokumentov +Comment[sl]=Pregledovalnik raznih vrst dokumentov +Comment[sr]=Приказивач различитих врста докумената +Comment[sr@ijekavian]=Приказивач различитих врста докумената +Comment[sr@ijekavianlatin]=Prikazivač različitih vrsta dokumenata +Comment[sr@latin]=Prikazivač različitih vrsta dokumenata +Comment[sv]=Visningsprogram för diverse typer av dokument +Comment[tr]=Çeşitli belge türü için görüntüleyici +Comment[ug]=ھەر خىل تىپتىكى پۈتۈكلەرنى كۆرىدىغان پروگرامما +Comment[uk]=Програма для перегляду документів різних типів +Comment[x-test]=xxViewer for various types of documentsxx +Comment[zh_CN]=可以查看多种文档的工具 +Comment[zh_TW]=多種型態文件的檢視器 + +Exec=active-documentviewer %u +Terminal=false +Icon=okular +Type=Application +Categories=Qt;KDE;Graphics;Office;Viewer; +InitialPreference=6 +NoDisplay=true +MimeType=application/vnd.kde.okular-archive; diff --git a/okular/active/app/package/contents/ui/Bookmarks.qml b/okular/active/app/package/contents/ui/Bookmarks.qml new file mode 100644 index 00000000..a51e7b3f --- /dev/null +++ b/okular/active/app/package/contents/ui/Bookmarks.qml @@ -0,0 +1,28 @@ +/* + * Copyright 2012 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 1.1 + + +ThumbnailsBase { + model: documentItem.bookmarkedPages + onPageClicked: { + pageArea.delegate.pageItem.goToBookmark(pageArea.delegate.pageItem.bookmarks[0]) + } +} \ No newline at end of file diff --git a/okular/active/app/package/contents/ui/Browser.qml b/okular/active/app/package/contents/ui/Browser.qml new file mode 100644 index 00000000..e95541c7 --- /dev/null +++ b/okular/active/app/package/contents/ui/Browser.qml @@ -0,0 +1,253 @@ +/* + * Copyright 2012 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 1.1 +import org.kde.plasma.components 0.1 as PlasmaComponents +import org.kde.plasma.extras 0.1 as PlasmaExtras +import org.kde.plasma.core 0.1 as PlasmaCore +import org.kde.plasma.mobilecomponents 0.1 as MobileComponents +import org.kde.qtextracomponents 0.1 +import org.kde.okular 0.1 as Okular + + +MobileComponents.OverlayDrawer { + id: resourceBrowser + property string currentUdi + anchors.fill: parent + + MouseEventListener { + id: pageArea + anchors.fill: parent + //enabled: !delegate.interactive + property Item delegate: delegate1 + property Item oldDelegate: delegate2 + property bool incrementing: delegate.delta > 0 + Connections { + target: pageArea.delegate + onDeltaChanged: { + pageArea.oldDelegate.delta = pageArea.delegate.delta + if (pageArea.delegate.delta > 0) { + pageArea.oldDelegate.visible = true + pageArea.oldDelegate.pageNumber = pageArea.delegate.pageNumber + 1 + documentItem.currentPage = pageArea.oldDelegate.pageNumber + pageArea.oldDelegate.visible = !(pageArea.delegate.pageNumber == documentItem.pageCount-1) + } else if (pageArea.delegate.delta < 0) { + pageArea.oldDelegate.pageNumber = pageArea.delegate.pageNumber - 1 + documentItem.currentPage = pageArea.oldDelegate.pageNumber + + pageArea.oldDelegate.visible = pageArea.delegate.pageNumber != 0 + } + } + } + + property int startMouseScreenX + property int startMouseScreenY + onPressed: { + startMouseScreenX = mouse.screenX + startMouseScreenY = mouse.screenY + } + onPositionChanged: { + if (Math.abs(mouse.screenX - startMouseScreenX) > width/5) { + delegate.pageSwitchEnabled = true + } + } + onReleased: { + delegate.pageSwitchEnabled = false + if (Math.abs(mouse.screenX - startMouseScreenX) < 20 && + Math.abs(mouse.screenY - startMouseScreenY) < 20) { + if (browserFrame.state == "Closed") { + browserFrame.state = "Hidden" + } else { + browserFrame.state = "Closed" + } + + } else if (oldDelegate.visible && delegate.delta != 0 && + (Math.abs(mouse.screenX - startMouseScreenX) > width/5) && + Math.abs(mouse.screenX - startMouseScreenX) > Math.abs(mouse.screenY - startMouseScreenY)) { + oldDelegate = delegate + delegate = (delegate == delegate1) ? delegate2 : delegate1 + switchAnimation.running = true + } + } + FullScreenDelegate { + id: delegate2 + width: parent.width + height: parent.height + } + FullScreenDelegate { + id: delegate1 + width: parent.width + height: parent.height + Component.onCompleted: pageNumber = documentItem.currentPage + } + + SequentialAnimation { + id: switchAnimation + NumberAnimation { + target: pageArea.oldDelegate + properties: "x" + to: pageArea.incrementing ? -pageArea.oldDelegate.width : pageArea.oldDelegate.width + easing.type: Easing.InQuad + duration: 250 + } + ScriptAction { + script: { + pageArea.oldDelegate.z = 0 + pageArea.delegate.z = 10 + pageArea.oldDelegate.x = 0 + pageArea.delegate.x = 0 + } + } + ScriptAction { + script: delegate1.delta = delegate2.delta = 0 + } + } + } + PlasmaComponents.ScrollBar { + flickableItem: pageArea.delegate.flickable + orientation: Qt.Vertical + anchors { + right: pageArea.right + top: pageArea.top + bottom: pageArea.bottom + left: undefined + } + } + PlasmaComponents.ScrollBar { + flickableItem: pageArea.delegate.flickable + orientation: Qt.Horizontal + anchors { + left: pageArea.left + right: pageArea.right + bottom: pageArea.bottom + top: undefined + } + } + + drawer: Item { + id: browserFrame + anchors.fill: parent + state: "Hidden" + + + PlasmaComponents.ToolBar { + id: mainToolBar + + y: pageStack.currentPage.contentY <= 0 ? 0 : -height + transform: Translate { + y: Math.max(0, -pageStack.currentPage.contentY) + } + tools: pageStack.currentPage.tools + Behavior on y { + NumberAnimation { + duration: 250 + } + } + anchors { + left: parent.left + right: parent.right + } + } + + + PlasmaComponents.PageStack { + id: pageStack + anchors { + left: parent.left + top: mainToolBar.bottom + right: parent.right + bottom: parent.bottom + } + clip: true + toolBar: mainToolBar + } + + Connections { + id: scrollConnection + property int oldContentY:0 + target: pageStack.currentPage + + onContentYChanged: { + + if (pageStack.currentPage.contentHeight <= pageStack.height || + (scrollConnection.oldContentY < pageStack.currentPage.contentY && + pageStack.currentPage.contentY > 0)) { + tabsToolbar.y = tabsToolbar.parent.height - tabsToolbar.height + } else { + tabsToolbar.y = tabsToolbar.parent.height + } + scrollConnection.oldContentY = pageStack.currentPage.contentY + } + } + + PlasmaComponents.ToolBar { + id: tabsToolbar + y: parent.height + anchors { + top: undefined + bottom: undefined + left: parent.left + right: parent.right + } + tools: Item { + width: parent.width + height: childrenRect.height + PlasmaComponents.TabBar { + id: mainTabBar + anchors.horizontalCenter: parent.horizontalCenter + PlasmaComponents.TabButton { + id: thumbnailsButton + text: i18n("Thumbnails") + onCheckedChanged: { + if (checked) { + pageStack.replace(Qt.createComponent("Thumbnails.qml")) + } + } + } + PlasmaComponents.TabButton { + id: tocButton + enabled: documentItem.tableOfContents.count > 0 + text: i18n("Table of contents") + onCheckedChanged: { + if (checked) { + pageStack.replace(Qt.createComponent("TableOfContents.qml")) + } + } + } + PlasmaComponents.TabButton { + id: bookmarksButton + enabled: documentItem.bookmarkedPages.length > 0 + text: i18n("Bookmarks") + onCheckedChanged: { + if (checked) { + pageStack.replace(Qt.createComponent("Bookmarks.qml")) + } + } + } + } + } + Behavior on y { + NumberAnimation { + duration: 250 + } + } + } + } +} + diff --git a/okular/active/app/package/contents/ui/FullScreenDelegate.qml b/okular/active/app/package/contents/ui/FullScreenDelegate.qml new file mode 100644 index 00000000..3cbf7700 --- /dev/null +++ b/okular/active/app/package/contents/ui/FullScreenDelegate.qml @@ -0,0 +1,193 @@ +/* + * Copyright 2011 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 1.1 +import org.kde.plasma.core 0.1 as PlasmaCore +import org.kde.plasma.extras 0.1 as PlasmaExtra +import org.kde.plasma.components 0.1 as PlasmaComponents +import org.kde.plasma.mobilecomponents 0.1 as MobileComponents +import org.kde.qtextracomponents 0.1 +import org.kde.okular 0.1 as Okular + +MouseEventListener { + id: root + //+1: switch to next image on mouse release + //-1: switch to previous image on mouse release + //0: do nothing + property int delta + + + property Item flickable: mainFlickable + property bool pageSwitchEnabled: false + property alias document: mainPage.document + property alias pageNumber: mainPage.pageNumber + property Item pageItem: mainPage + + onWheelMoved: { + if (wheel.modifiers == Qt.ControlModifier) { + var factor = wheel.delta > 0 ? 1.1 : 0.9 + if (scale(factor)) { + pageArea.oldDelegate.scale(mainPage.width / mainPage.implicitWidth, true) + } + } + } + + function scale(zoom, absolute) { + var newScale = absolute ? zoom : (mainPage.width / mainPage.implicitWidth) * zoom; + if (newScale < 0.3 || newScale > 3) { + return false + } + + if (imageMargin.zooming) { + // pinch is happening! + mainPage.width = imageMargin.startWidth * zoom + mainPage.height = imageMargin.startHeight * zoom + } else if (absolute) { + // we were given an absolute, not a relative, scale + mainPage.width = mainPage.implicitWidth * zoom + mainPage.height = mainPage.implicitHeight * zoom + } else { + mainPage.width *= zoom + mainPage.height *= zoom + } + + return true + } + + Rectangle { + id: backgroundRectangle + x: -mainFlickable.contentX + mainPage.x + y: 0 + anchors { + top: parent.top + bottom: parent.bottom + } + width: mainPage.width + color: "white" + + Image { + source: "image://appbackgrounds/shadow-left" + fillMode: Image.TileVertically + opacity: 0.5 + anchors { + right: parent.left + top: parent.top + bottom: parent.bottom + } + } + Image { + source: "image://appbackgrounds/shadow-right" + fillMode: Image.TileVertically + opacity: 0.5 + anchors { + left: parent.right + top: parent.top + bottom: parent.bottom + } + } + } + + Flickable { + id: mainFlickable + property real ratio : width / height + anchors.fill: parent + width: parent.width + height: parent.height + contentWidth: imageMargin.width + contentHeight: imageMargin.height + + onContentXChanged: { + if (atXBeginning && contentX < 0) { + root.delta = -1 + } else if (atXEnd) { + root.delta = +1 + } else { + root.delta = 0 + } + } + + PinchArea { + id: imageMargin + width: Math.max(mainFlickable.width + (pageSwitchEnabled ? 1: 0), mainPage.width) + height: Math.max(mainFlickable.height, mainPage.height) + + property real startWidth + property real startHeight + property real startY + property real startX + property bool zooming: false + onPinchStarted: { + startWidth = mainPage.width + startHeight = mainPage.height + zooming = true + startY = pinch.center.y + startX = pinch.center.x + pageArea.oldDelegate.visible = false + } + onPinchUpdated: { + var deltaWidth = mainPage.width < imageMargin.width ? ((startWidth * pinch.scale) - mainPage.width) : 0 + var deltaHeight = mainPage.height < imageMargin.height ? ((startHeight * pinch.scale) - mainPage.height) : 0 + if (root.scale(pinch.scale)) { + mainFlickable.contentY += pinch.previousCenter.y - pinch.center.y + startY * (pinch.scale - pinch.previousScale) - deltaHeight + mainFlickable.contentX += pinch.previousCenter.x - pinch.center.x + startX * (pinch.scale - pinch.previousScale) - deltaWidth + } + } + onPinchFinished: { + mainFlickable.returnToBounds() + pageArea.oldDelegate.scale(mainPage.width / mainPage.implicitWidth) + pageArea.oldDelegate.visible = true + zooming = false + } + + Okular.PageItem { + id: mainPage + document: documentItem + flickable: mainFlickable + property real ratio: implicitWidth / implicitHeight + + x: Math.round((parent.width - width) / 2) + y: Math.round((parent.height - height) / 2) + width: implicitWidth + height: implicitHeight + } + } + } + Image { + source: "bookmark.png" + anchors { + top: parent.top + right: backgroundRectangle.right + rightMargin: -5 + topMargin: mainPage.bookmarked ? -30 : -120 + } + Behavior on anchors.topMargin { + NumberAnimation { + duration: 250 + } + } + + MouseArea { + anchors { + fill: parent + margins: -8 + } + onClicked: mainPage.bookmarked = !mainPage.bookmarked + } + } +} diff --git a/okular/active/app/package/contents/ui/TableOfContents.qml b/okular/active/app/package/contents/ui/TableOfContents.qml new file mode 100644 index 00000000..c9fa7645 --- /dev/null +++ b/okular/active/app/package/contents/ui/TableOfContents.qml @@ -0,0 +1,59 @@ +/* + * Copyright 2012 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 1.1 +import org.kde.plasma.components 0.1 as PlasmaComponents +import org.kde.plasma.core 0.1 as PlasmaCore +import org.kde.plasma.extras 0.1 as PlasmaExtras +import org.kde.plasma.mobilecomponents 0.1 as MobileComponents + +PlasmaComponents.Page { + property alias contentY: flickable.contentY + property alias contentHeight: flickable.contentHeight + + tools: MobileComponents.ViewSearch { + id: searchField + anchors.centerIn: parent + busy: documentItem.searchInProgress + } + PlasmaExtras.ScrollArea { + anchors.fill: parent + + Flickable { + id: flickable + anchors.fill: parent + contentWidth: width + contentHeight: treeView.height + Column { + id: treeView + width: flickable.width + Repeater { + model: VisualDataModel { + id: tocModel + model: documentItem.tableOfContents + delegate: TreeDelegate { + sourceModel: tocModel + width: treeView.width + } + } + } + } + } + } +} diff --git a/okular/active/app/package/contents/ui/Thumbnails.qml b/okular/active/app/package/contents/ui/Thumbnails.qml new file mode 100644 index 00000000..0da889e7 --- /dev/null +++ b/okular/active/app/package/contents/ui/Thumbnails.qml @@ -0,0 +1,57 @@ +/* + * Copyright 2012 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 1.1 +import org.kde.plasma.components 0.1 as PlasmaComponents +import org.kde.plasma.core 0.1 as PlasmaCore +import org.kde.plasma.mobilecomponents 0.1 as MobileComponents + +ThumbnailsBase { + id: root + model: documentItem.matchingPages + + anchors.fill: parent + tools: Item { + id: toolBarContent + width: root.width + height: searchField.height + MobileComponents.ViewSearch { + id: searchField + enabled: documentItem.supportsSearch + anchors.centerIn: parent + busy: documentItem.searchInProgress + onSearchQueryChanged: { + if (searchQuery.length > 2) { + documentItem.searchText(searchQuery) + } else { + view.currentIndex = pageArea.delegate.pageNumber + documentItem.resetSearch() + } + } + } + PlasmaComponents.Label { + anchors { + left: searchField.right + verticalCenter: searchField.verticalCenter + } + visible: documentItem.matchingPages.length == 0 + text: i18n("No results found.") + } + } +} diff --git a/okular/active/app/package/contents/ui/ThumbnailsBase.qml b/okular/active/app/package/contents/ui/ThumbnailsBase.qml new file mode 100644 index 00000000..968effa5 --- /dev/null +++ b/okular/active/app/package/contents/ui/ThumbnailsBase.qml @@ -0,0 +1,103 @@ +/* + * Copyright 2012 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 1.1 +import org.kde.okular 0.1 as Okular +import org.kde.plasma.components 0.1 as PlasmaComponents +import org.kde.plasma.core 0.1 as PlasmaCore +import org.kde.plasma.extras 0.1 as PlasmaExtras +import org.kde.plasma.mobilecomponents 0.1 as MobileComponents + +PlasmaComponents.Page { + id: root + property alias contentY: resultsGrid.contentY + property alias contentHeight: resultsGrid.contentHeight + property alias model: resultsGrid.model + signal pageClicked(int pageNumber) + property Item view: resultsGrid + + anchors.fill: parent + + PlasmaExtras.ScrollArea { + anchors.fill: parent + + GridView { + id: resultsGrid + anchors.fill: parent + + cellWidth: theme.defaultFont.mSize.width * 14 + cellHeight: theme.defaultFont.mSize.height * 12 + + delegate: Item { + width: resultsGrid.cellWidth + height: resultsGrid.cellHeight + property bool current: documentItem.currentPage == modelData + onCurrentChanged: { + if (current) { + resultsGrid.currentIndex = index + } + } + PlasmaCore.FrameSvgItem { + anchors.centerIn: parent + imagePath: "widgets/media-delegate" + prefix: "picture" + width: thumbnail.width + margins.left + margins.right + //FIXME: why bindings with thumbnail.height doesn't work? + height: thumbnail.height + margins.top + margins.bottom + Okular.ThumbnailItem { + id: thumbnail + x: parent.margins.left + y: parent.margins.top + document: documentItem + pageNumber: modelData + width: theme.defaultFont.mSize.width * 10 + //value repeated to avoid binding loops + height: Math.round(theme.defaultFont.mSize.width * 10 / (implicitWidth/implicitHeight)) + Rectangle { + width: childrenRect.width + height: childrenRect.height + color: theme.backgroundColor + radius: width + smooth: true + anchors { + bottom: parent.bottom + right: parent.right + } + PlasmaComponents.Label { + text: modelData + 1 + } + } + } + MouseArea { + anchors.fill: parent + onClicked: { + resultsGrid.currentIndex = index + pageArea.delegate.pageNumber = modelData + documentItem.currentPage = modelData + + resourceBrowser.open = false + root.pageClicked(modelData) + } + } + } + } + highlight: PlasmaComponents.Highlight {} + } + } +} diff --git a/okular/active/app/package/contents/ui/TreeDelegate.qml b/okular/active/app/package/contents/ui/TreeDelegate.qml new file mode 100644 index 00000000..35886230 --- /dev/null +++ b/okular/active/app/package/contents/ui/TreeDelegate.qml @@ -0,0 +1,109 @@ +/* + * Copyright 2012 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 1.1 +import org.kde.plasma.components 0.1 as PlasmaComponents +import org.kde.plasma.core 0.1 as PlasmaCore +import org.kde.qtextracomponents 0.1 + +Column { + id: treeDelegate + property variant sourceModel + property int rowIndex: index + width: parent.width + + property bool matches: searchField.searchQuery.length < 3 || display.toLowerCase().indexOf(searchField.searchQuery.toLowerCase()) !== -1 + + + MouseArea { + id: delegateArea + width: parent.width + height: label.height + opacity: matches ? 1 : 0 + Behavior on opacity { + NumberAnimation { + duration: 250 + } + } + + + onClicked: { + pageArea.delegate.pageNumber = page-1 + documentItem.currentPage = page-1 + + resourceBrowser.open = false + } + + QIconItem { + id: icon + icon: decoration + width: theme.smallIconSize + height: width + anchors.verticalCenter: parent.verticalCenter + } + PlasmaComponents.Label { + id: label + text: display + verticalAlignment: Text.AlignBottom + anchors.left: icon.right + } + //there isn't a sane way to do a dotted line in QML1 + Rectangle { + color: theme.textColor + opacity: 0.1 + height: 1 + anchors { + bottom: parent.bottom + left: label.right + right: pageNumber.left + } + } + PlasmaComponents.Label { + id: pageNumber + text: pageLabel ? pageLabel : page + anchors.right: parent.right + verticalAlignment: Text.AlignBottom + anchors.rightMargin: 40 + } + } + Column { + id: col + x: 20 + width: parent.width - 20 + property variant model: childrenModel + Repeater { + id: rep + model: VisualDataModel { + id: childrenModel + model: documentItem.tableOfContents + } + } + } + onParentChanged: { + if (treeDelegate.parent && treeDelegate.parent.model) { + sourceModel = treeDelegate.parent.model + } + + childrenModel.rootIndex = sourceModel.modelIndex(index) + + if (model.hasModelChildren) { + childrenModel.delegate = Qt.createComponent("TreeDelegate.qml") + } + } +} diff --git a/okular/active/app/package/contents/ui/bookmark.png b/okular/active/app/package/contents/ui/bookmark.png new file mode 100644 index 0000000000000000000000000000000000000000..5e8892b36a4f928ac0c87d3ad4d7adbe290826ad GIT binary patch literal 3887 zcmV+~576+5P)bSY5+F=64AKY& z39&>7Htg838w~)dahcYfEsr$kjbN+Kcv@52i#@TUrEKm?+cpI`El zavew01b_cB{0q>5(jx$+|F{CJjIY5*Yk&6L7e~MRb!0R`LO_E+2uQhz5hQ>J5*t(m z2?8-7<#}Q-mJ&dE>(}M7GP`k|-S7Wk^Cft`0PbHM@a72EdWCYmc=iwfhE^^Ech1yMs<9 zsOBds;vlSGYBe4cpL&{j_AIl_+x0hJ0UiL4fP)@K1+=O}1>6+CXMlBpc593L>~rMZ zUD~a?=_~u@M$2~*?=xl3KW)F60cka{PyGy&f3#|j-ra@;D z;<>Xh9y8nO0FQXwZ+9ls9AVQ3Er3q$+#&DmGQD@7$<`L_{rhf!({zfQdx|h#V{+#X zYjX^TqjZ0RuTJsCkzzR=SY!&AH>dko6$Inv+RZaRsbO zxnrs7!qR{ZU{?9YY(KCBW3*w^TkoO7oVZeGH`=EEg`11X@I zjsN_q6KRly$%(A_l)OZS;?_KLSXHD14n+l21eN8D4iQuURSO&<%j+=oZ<}@^aC2*F zQM9LSORGdyAh*dtpMpxLX5-&2c`|(g3ELz8z=5BHDS;!k$=r`hVeFv8lt7d-sZA(8 zI&8^Laq@OKNm!5Y#Ye(cuBL?MQ_|s_jsL*c4D!QW;*PiiR>b%wR~@!|xQ1qjj)XDB z@`;>tmk;A+bXCF%ATCBHNSDB;lq*jM9hTzOG?z$cmjhx{AZsQR-a}}Z4(E)Do=Uc+ zVut6Nt<{QI=s-K5HGU}@|MKjjmkmrW(;=7UnOZnO7VODV+4ndW+1t=URXGQ4P(d zLGI@z+SXi;mxt(>7r<@nqY_$;t~liE6y=Pp$03~q9%jso`w{}0TC!OB?h@rHbCfI) zQj3a1Z>>hUWX`zDU0tX)WpXBk=-rn{bHFZW=iGY+=h+NQ9aB%LDJEVCy!Fc z=|&cttw-UJUUr=yu@K@d;f`c@xF4j$B7%nCA3UV&3=r|vVPd2-5d6FZCI~`+5Ks#q z9=Sx>=Mbb6i;g;sAR%@-?BR#$B;2<1Mxck3MdlkkVw|tFfEMep08KPs9K=tB1tbVL z2(gJZg8Jxi-7b^AfHe;x6(JPfei8<>8pc4TLrKB-7T&P9J{VLYNr-5$%}Acv8WPYD zjsHNt!;G$MBsvud7uV*R{1Brtm4TKqfGl+_ez^5#vAbbqjL|0Ym~J6y{4q?Rc9If*|6j!-ztN(LihY9Lyt?PzYLt5#ymfq{{%# z0z?TU38y1r3B)4d>=5a!6A`OQ;A(W{h6RaXaEoza;1D3jNP9|tSVADi=%d5*j7~}^ zGv5=v993S%*U3w0Pr{+NHBSjwo>jux%MXWG@>X$H38|r3d{n~pOzpPy9=jPGb3HCb z=ObZ)d5Kd|iFycN;OhouYfTKyLg*fQP~{^b(3EV=Cznu=&=gCa_j8v9toCWBd7uUv zf_4#L>YEPrs76KWkpd|qiq!4Zo;n;tw4C)6{BXw`rxK~N;vj^9yGDkPZNs)xH4;3t zH7sz{NEaB8p$wn^U8Jjl!{alKBH>uh6+K~N`j;Rn|CrG^ZU@nO0Lh8Y#nQP_`x#tJm~5I;8M-)Zq|H2fj8xJOFnI{JgtA zgoYd4DSC-&bRNw>6*Z%iAfO_eu52M@Qrk_7wH@UcahF3m!M>8bT-#7k(M*O zIWG!-ufBvrziD~s@iKsq;ptR_QqCe}f~u=ZgtEI;Q+BR6a)vzL?(COdYLB;LGmw<# z$qJ5Mq9jwK=?m;n9u(=M z9OXR?l~Bz{Rv)9IW@|YS>h4y20NbroLOB5Fl@FWIwR0~KhHxD~hrC4VK*qv~*VK~Bj#^l}N!Cr=kULc_L!rDUhF zR0nS+T0~MS@SKf5uDOJE>flv?3VpS3a%&l~ph#+MWLZ%QFQcOj!;oY~xAcw_V~l&^ zQ~CB?n_~ixNjDW8UIt~4OeGXjkuu)tWtmmw37AeToC1d#oh8{)5_qqp*n(O?P>yeU z?fJ3&!>J{eV`RP(2685(wstQa+?s;6T5{OAds~`v;irnW9Zw$}7M%wU*W{-L4oz-4 zY@s=&VtCFfp_ExwPF}(oD={Z>DUonAx>_Q|+?7ZRX+U~eBy9bZlUuF*15rSCzW7+c z^UYK}2|Ijb0A|T$sLQb`Ap$WXD&o z$$H9VWrgt6)5xVuaCV*PfB&4kzen2Kq=#3>MQ9o<;X#v>l9qXaF~WEiId_hD@giaU z9MgB+VRmmjZM3@aKP}h(89w?Cxchr>VK&v>R~jz=+O?0rk#~0D%B#PQtd7ymO>}S1 z4o#^V{{di!0k*~j8Y8TqMb4imUbq0NJbLR2dygi&Z%_5DkChugf_FZHU7-DUxbq2I z|8C2@ous#4-`@VM^|!7(J$~(*#1~#5ZEr(6Lu0hMZeT}=(UB~A(Q&{_3 z*!=^z`Nvazct3Od>+iq+?bV%~@%YtO3C*auwK`krWb7vawy{11c6#`bgSW48Z-0OD z-HE>WsdD4*;LYo>iK)RZa|w5Vu}RSn{uLg*2U~y9>TmBQz56S-Zhq(d{@zztf8#fi z@k%XtE8r)BeiC5q1D@Ty$>hTi@@EILcdyNK;|hHEBltyv9qj5$cY@8p2Ak46E^lIT z4G#VacK=-YRyxqV=hOb{&;REeuZ>@RB`6T)v)`Wx`Y7P~hZ#ssnT4^pv9b5zEdTU& z;`4um|N1xhv?ta59@r_Szcs#u^0>}*pSCdh8~Ew(!>vD=>4W_|{rvLA#_x`V^~M0t zI01Mc@FB3kEW%_z>j$4s^o{?8TYn8#KZkn-V3q2_BEdRnKJ^xn9=}w6KLw^ghRFup z`OCz2+LSlHHkRL6vAa`^_E^yU!P@;P`wbAk0i$u^_8ai-58=u#Y%^DbD&49EeX#vS z`a-CnTcF*AgCD~uFTrFjaqB(!=qK=55klo#$Ew>`1oXf*%xA!_VZ-ZJ%X5?$O_)!> zKj<;*r199dJ3RSo2UdG)jeB={R(My?{dIEvqrMcT<+)q{)~cxfuIg$3w%L%$V`e$1 zs+NuZRr-BtPYa+G4o}ygm9?iFR*hZ*=CGt=?`QuFwY|lnmFgP(_3Uod^^SgdNbmoZLH+rq xfGz+&cs)6~ZjaHiv@Fo0^hMWu4DkPV`ag)JkCMsG$}s={002ovPDHLkV1gS$TZRAt literal 0 HcmV?d00001 diff --git a/okular/active/app/package/contents/ui/bookmark.svgz b/okular/active/app/package/contents/ui/bookmark.svgz new file mode 100644 index 0000000000000000000000000000000000000000..06f417df47ad92c23f39a4f850cf96d5184cdee4 GIT binary patch literal 2551 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 1.1 +import org.kde.okular 0.1 as Okular +import org.kde.plasma.extras 0.1 as PlasmaExtras +import org.kde.plasma.components 0.1 as PlasmaComponents + +Image { + id: fileBrowserRoot + objectName: "fileBrowserRoot" + source: "image://appbackgrounds/contextarea" + fillMode: Image.Tile + + width: 360 + height: 360 + + PlasmaExtras.ResourceInstance { + id: resourceInstance + uri: documentItem.path + } + + Okular.DocumentItem { + id: documentItem + onWindowTitleForDocumentChanged: { + application.caption = windowTitleForDocument + } + } + + PlasmaComponents.PageStack { + id: mainStack + clip: false + anchors.fill: parent + } + + //FIXME: this is due to global vars being binded after the parse is done, do the 2 steps parsing + Timer { + interval: 100 + running: true + onTriggered: { + if (application.startupArguments.length > 0) { + documentItem.path = application.startupArguments[0] + } + + mainStack.push(Qt.createComponent("Browser.qml")) + } + } +} diff --git a/okular/active/app/package/metadata.desktop b/okular/active/app/package/metadata.desktop new file mode 100644 index 00000000..060f4792 --- /dev/null +++ b/okular/active/app/package/metadata.desktop @@ -0,0 +1,62 @@ +[Desktop Entry] +Name=Document viewer +Name[ar]=عارض المستندات +Name[bg]=Преглед на документи +Name[bs]=Prikazivač dokumenata +Name[ca]=Visualitzador de documents +Name[ca@valencia]=Visualitzador de documents +Name[cs]=Prohlížeč dokumentů +Name[da]=Dokumentfremviser +Name[de]=Dokumentenbetrachter +Name[el]=Προβολέας εγγράφων +Name[en_GB]=Document viewer +Name[es]=Visor de documentos +Name[et]=Dokumendinäitaja +Name[fi]=Asiakirjakatselin +Name[fr]=Afficheur de documents +Name[ga]=Amharcán cáipéisí +Name[gl]=Visor de documentos +Name[hu]=Dokumentummegjelenítő +Name[ia]=Visor de documento +Name[is]=Skjalaskoðari +Name[it]=Visore di documenti +Name[ja]=文書ビューア +Name[kk]=Құжатты қарау құралы +Name[ko]=문서 뷰어 +Name[lt]=Dokumentų žiūryklė +Name[mr]=दस्तऐवज प्रदर्शक +Name[nb]=Dokumentviser +Name[nds]=Dokmentkieker +Name[nl]=Documentenviewer +Name[pa]=ਡੌਕੂਮੈਂਟ ਦਰਸ਼ਕ +Name[pl]=Przeglądarka dokumentów +Name[pt]=Visualizador de documentos +Name[pt_BR]=Visualizador de documentos +Name[ro]=Vizualizor de documente +Name[ru]=Просмотр документов +Name[sk]=Prehliadač dokumentov +Name[sl]=Pregledovalnik dokumentov +Name[sr]=Приказивач докумената +Name[sr@ijekavian]=Приказивач докумената +Name[sr@ijekavianlatin]=Prikazivač dokumenata +Name[sr@latin]=Prikazivač dokumenata +Name[sv]=Dokumentvisare +Name[tr]=Belge görüntüleyici +Name[uk]=Переглядач документів +Name[x-test]=xxDocument viewerxx +Name[zh_CN]=文档查看器 +Name[zh_TW]=文件檢視器 +Encoding=UTF-8 +Type=Service +Icon=okular +X-KDE-ParentApp= +X-KDE-PluginInfo-Author=Marco Martin +X-KDE-PluginInfo-Email=mart@kde.org +X-KDE-PluginInfo-Name=org.kde.active.documentviewer +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-Website=http://kde.org +X-KDE-PluginInfo-Category=Office +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true +X-Plasma-MainScript=ui/main.qml diff --git a/okular/active/app/src/CMakeLists.txt b/okular/active/app/src/CMakeLists.txt new file mode 100644 index 00000000..1004dbd3 --- /dev/null +++ b/okular/active/app/src/CMakeLists.txt @@ -0,0 +1,21 @@ + +set(active-documentviewer_SRCS + main.cpp +) + +kde4_add_executable(active-documentviewer ${active-documentviewer_SRCS}) + +find_package(ActiveApp REQUIRED) + +include_directories( ${KDE4_INCLUDES} ${QT_INCLUDES} ${ACTIVEAPP_INCLUDE_DIR}) + +target_link_libraries(active-documentviewer + ${QT_QTDECLARATIVE_LIBRARY} + ${QT_QTOPENGL_LIBRARY} + ${KDE4_KDEUI_LIBS} + ${KDE4_PLASMA_LIBS} + ${ACTIVEAPP_LIBRARIES} +) + +install(TARGETS active-documentviewer ${INSTALL_TARGETS_DEFAULT_ARGS} ) + diff --git a/okular/active/app/src/main.cpp b/okular/active/app/src/main.cpp new file mode 100644 index 00000000..8d1c62c6 --- /dev/null +++ b/okular/active/app/src/main.cpp @@ -0,0 +1,62 @@ +/*************************************************************************** + * * + * Copyright 2011 Sebastian Kügler * + * Copyright 2012 Marco Martin * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +// KDE +#include +#include +#include +#include +#include +#include + +#include "kdeclarativemainwindow.h" +#include "kdeclarativeview.h" + +static const char description[] = I18N_NOOP("Document viewer for Plasma Active using Okular"); + +static const char version[] = "0.1"; + +int main(int argc, char **argv) +{ + KAboutData about("active-documentviewer", 0, ki18n("Reader"), version, ki18n(description), + KAboutData::License_GPL, ki18n("Copyright 2012 Marco Martin"), KLocalizedString(), 0, "mart@kde.org"); + about.addAuthor( ki18n("Marco Martin"), KLocalizedString(), "mart@kde.org" ); + about.setProgramIconName("okular"); + + KCmdLineArgs::init(argc, argv, &about); + + KCmdLineOptions options; + options.add("+[url]", ki18n( "URL of the file to open" )); + + KCmdLineArgs::addCmdLineOptions(options); + KApplication app; + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + KGlobal::locale()->insertCatalog("org.kde.okular"); + //kDebug() << "ARGS:" << args << args->count(); + + KDeclarativeMainWindow *mainWindow = new KDeclarativeMainWindow(); + mainWindow->declarativeView()->setPackageName("org.kde.active.documentviewer"); + mainWindow->show(); + args->clear(); + return app.exec(); +} diff --git a/okular/active/components/CMakeLists.txt b/okular/active/components/CMakeLists.txt new file mode 100644 index 00000000..19b759ed --- /dev/null +++ b/okular/active/components/CMakeLists.txt @@ -0,0 +1,41 @@ +project(okular) + +add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_BINARY_DIR} + ${KDE4_INCLUDES} + ${QIMAGEBLITZ_INCLUDES} +) + +set(okular_SRCS + okularplugin.cpp + ${CMAKE_SOURCE_DIR}/ui/pagepainter.cpp + ${CMAKE_SOURCE_DIR}/ui/guiutils.cpp + ${CMAKE_SOURCE_DIR}/ui/tocmodel.cpp + pageitem.cpp + documentitem.cpp + thumbnailitem.cpp + ) + +kde4_add_kcfg_files(okular_SRCS ${CMAKE_SOURCE_DIR}/conf/settings.kcfgc ) + +qt4_automoc(${okular_SRCS}) + +kde4_add_library(okularplugin SHARED ${okular_SRCS}) +target_link_libraries(okularplugin + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${QT_QTDECLARATIVE_LIBRARY} + ${QT_QTXML_LIBRARY} + ${KDE4_KDECORE_LIBRARY} + ${KDE4_KDEUI_LIBRARY} + ${KDE4_KIO_LIBRARY} + ${QIMAGEBLITZ_LIBRARIES} + okularcore + ) + +install(TARGETS okularplugin DESTINATION ${IMPORTS_INSTALL_DIR}/org/kde/okular) +install(FILES qmldir DESTINATION ${IMPORTS_INSTALL_DIR}/org/kde/okular) + +#add_subdirectory(test) diff --git a/okular/active/components/Messages.sh b/okular/active/components/Messages.sh new file mode 100644 index 00000000..4c7865c4 --- /dev/null +++ b/okular/active/components/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name "*.cpp" -o -name "*.h"` -o $podir/org.kde.okular.pot +rm -f rc.cpp diff --git a/okular/active/components/documentitem.cpp b/okular/active/components/documentitem.cpp new file mode 100644 index 00000000..e1915abe --- /dev/null +++ b/okular/active/components/documentitem.cpp @@ -0,0 +1,257 @@ +/* + * Copyright 2012 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "documentitem.h" + +#include + +#include +#include +#include + +#include "ui/tocmodel.h" + +DocumentItem::DocumentItem(QObject *parent) + : QObject(parent), + m_thumbnailObserver(0), + m_pageviewObserver(0), + m_searchInProgress(false) +{ + qmlRegisterUncreatableType("org.kde.okular", 1, 0, "TOCModel", QLatin1String("Do not create objects of this type.")); + Okular::Settings::instance("okularproviderrc"); + m_document = new Okular::Document(0); + m_tocModel = new TOCModel(m_document, this); + + connect(m_document, SIGNAL(searchFinished(int,Okular::Document::SearchStatus)), + this, SLOT(searchFinished(int,Okular::Document::SearchStatus))); + connect(m_document->bookmarkManager(), SIGNAL(bookmarksChanged(KUrl)), + this, SIGNAL(bookmarkedPagesChanged())); + connect(m_document->bookmarkManager(), SIGNAL(bookmarksChanged(KUrl)), + this, SIGNAL(bookmarksChanged())); +} + + +DocumentItem::~DocumentItem() +{ + delete m_document; +} + +void DocumentItem::setPath(const QString &path) +{ + //TODO: remote urls + //TODO: password + m_document->openDocument(path, KUrl(path), KMimeType::findByUrl(KUrl(path))); + + m_tocModel->fill(m_document->documentSynopsis()); + m_tocModel->setCurrentViewport(m_document->viewport()); + + m_matchingPages.clear(); + for (uint i = 0; i < m_document->pages(); ++i) { + m_matchingPages << (int)i; + } + emit matchingPagesChanged(); + emit pathChanged(); + emit pageCountChanged(); + emit openedChanged(); + emit supportsSearchingChanged(); + emit windowTitleForDocumentChanged(); +} + +QString DocumentItem::windowTitleForDocument() const +{ + // If 'DocumentTitle' should be used, check if the document has one. If + // either case is false, use the file name. + QString title = Okular::Settings::displayDocumentNameOrPath() == Okular::Settings::EnumDisplayDocumentNameOrPath::Path ? m_document->currentDocument().pathOrUrl() : m_document->currentDocument().fileName(); + + if (Okular::Settings::displayDocumentTitle()) { + const QString docTitle = m_document->metaData( "DocumentTitle" ).toString(); + + if (!docTitle.isEmpty() && !docTitle.trimmed().isEmpty()) { + title = docTitle; + } + } + + return title; +} + +QString DocumentItem::path() const +{ + return m_document->currentDocument().prettyUrl(); +} + +void DocumentItem::setCurrentPage(int page) +{ + m_document->setViewportPage(page); + m_tocModel->setCurrentViewport(m_document->viewport()); + emit currentPageChanged(); +} + +int DocumentItem::currentPage() const +{ + return m_document->currentPage(); +} + +bool DocumentItem::isOpened() const +{ + return m_document->isOpened(); +} + +int DocumentItem::pageCount() const +{ + return m_document->pages(); +} + +QList DocumentItem::matchingPages() const +{ + return m_matchingPages; +} + +TOCModel *DocumentItem::tableOfContents() const +{ + return m_tocModel; +} + +QList DocumentItem::bookmarkedPages() const +{ + QList list; + QSet pages; + foreach (const KBookmark &bookmark, m_document->bookmarkManager()->bookmarks()) { + Okular::DocumentViewport viewport(bookmark.url().htmlRef()); + pages << viewport.pageNumber; + } + list = pages.toList(); + qSort(list); + return list; +} + +QStringList DocumentItem::bookmarks() const +{ + QStringList list; + foreach(const KBookmark &bookmark, m_document->bookmarkManager()->bookmarks()) { + list << bookmark.url().prettyUrl(); + } + return list; +} + +bool DocumentItem::supportsSearching() const +{ + return m_document->supportsSearching(); +} + +bool DocumentItem::isSearchInProgress() const +{ + return m_searchInProgress; +} + +void DocumentItem::searchText(const QString &text) +{ + if (text.isEmpty()) { + resetSearch(); + return; + } + m_document->cancelSearch(); + m_document->resetSearch(PAGEVIEW_SEARCH_ID); + m_document->searchText(PAGEVIEW_SEARCH_ID, text, 1, Qt::CaseInsensitive, + Okular::Document::AllDocument, true, QColor(100,100,200,40)); + + if (!m_searchInProgress) { + m_searchInProgress = true; + emit searchInProgressChanged(); + } +} + +void DocumentItem::resetSearch() +{ + m_document->resetSearch(PAGEVIEW_SEARCH_ID); + m_matchingPages.clear(); + for (uint i = 0; i < m_document->pages(); ++i) { + m_matchingPages << (int)i; + } + if (m_searchInProgress) { + m_searchInProgress = false; + emit searchInProgressChanged(); + } + + emit matchingPagesChanged(); +} + +Okular::Document *DocumentItem::document() +{ + return m_document; +} + +Observer *DocumentItem::thumbnailObserver() +{ + if (!m_thumbnailObserver) + m_thumbnailObserver = new Observer(this); + + return m_thumbnailObserver; +} + +Observer *DocumentItem::pageviewObserver() +{ + if (!m_pageviewObserver) { + m_pageviewObserver = new Observer(this); + } + + return m_pageviewObserver; +} + +void DocumentItem::searchFinished(int id, Okular::Document::SearchStatus endStatus) +{ + Q_UNUSED(endStatus) + + if (id != PAGEVIEW_SEARCH_ID) { + return; + } + + m_matchingPages.clear(); + for (uint i = 0; i < m_document->pages(); ++i) { + if (m_document->page(i)->hasHighlights(id)) { + m_matchingPages << (int)i; + } + } + + if (m_searchInProgress) { + m_searchInProgress = false; + emit searchInProgressChanged(); + } + emit matchingPagesChanged(); +} + + +//Observer + +Observer::Observer(DocumentItem *parent) + : QObject(parent), + m_document(parent) +{ + parent->document()->addObserver(this); +} + +Observer::~Observer() +{ +} + +void Observer::notifyPageChanged(int page, int flags) +{ + emit pageChanged(page, flags); +} + +#include "documentitem.moc" diff --git a/okular/active/components/documentitem.h b/okular/active/components/documentitem.h new file mode 100644 index 00000000..a421a0ae --- /dev/null +++ b/okular/active/components/documentitem.h @@ -0,0 +1,185 @@ +/* + * Copyright 2012 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef QDOCUMENTITEM_H +#define QDOCUMENTITEM_H + +#include + +#include "settings.h" + +#include +#include + +namespace Okular { + class Document; +} + +class Observer; +class TOCModel; + +class DocumentItem : public QObject +{ + Q_OBJECT + + /** + * Absolute path of the document file to open + */ + Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) + + /** + * Suggested window title if a window represents this document. may be pathname or document title, dependeing from Okular settings. + */ + Q_PROPERTY(QString windowTitleForDocument READ windowTitleForDocument NOTIFY windowTitleForDocumentChanged) + + /** + * Current displaying page for the document + */ + Q_PROPERTY(int currentPage READ currentPage WRITE setCurrentPage NOTIFY currentPageChanged) + + /** + * True if this DocumentItem instance has a document file opened + */ + Q_PROPERTY(bool opened READ isOpened NOTIFY openedChanged) + + /** + * How many pages there are in the document + */ + Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged) + + /** + * True if the document is able to perform full text searches in its contents + */ + Q_PROPERTY(bool supportsSearching READ supportsSearching NOTIFY supportsSearchingChanged) + + /** + * True if a search is currently in progress and results didn't arrive yet + */ + Q_PROPERTY(bool searchInProgress READ isSearchInProgress NOTIFY searchInProgressChanged) + + /** + * A list of all pages that contain a match for the search terms. If no text has been searched, all pages are returned. + */ + Q_PROPERTY(QList matchingPages READ matchingPages NOTIFY matchingPagesChanged) + + /** + * Table of contents for the document, if available + */ + Q_PROPERTY(TOCModel *tableOfContents READ tableOfContents CONSTANT) + + /** + * List of pages that contain a bookmark + */ + Q_PROPERTY(QList bookmarkedPages READ bookmarkedPages NOTIFY bookmarkedPagesChanged) + + /** + * list of bookmarks urls valid on this page + */ + Q_PROPERTY(QStringList bookmarks READ bookmarks NOTIFY bookmarksChanged) + +public: + + DocumentItem(QObject *parent=0); + ~DocumentItem(); + + void setPath(const QString &path); + QString path() const; + + QString windowTitleForDocument() const; + + void setCurrentPage(int page); + int currentPage() const; + + bool isOpened() const; + + int pageCount() const; + + bool supportsSearching() const; + + bool isSearchInProgress() const; + + QList matchingPages() const; + + TOCModel *tableOfContents() const; + + QList bookmarkedPages() const; + + QStringList bookmarks() const; + + //This could be a property, but maybe we want to have parameter for searchText + /** + * Performs a search in the document + * + * @param text the string to search in the document + */ + Q_INVOKABLE void searchText(const QString &text); + + /** + * Reset the search over the document. + */ + Q_INVOKABLE void resetSearch(); + + //Internal, not binded to qml + Okular::Document *document(); + Observer *pageviewObserver(); + Observer *thumbnailObserver(); + +Q_SIGNALS: + void pathChanged(); + void pageCountChanged(); + void openedChanged(); + void searchInProgressChanged(); + void matchingPagesChanged(); + void currentPageChanged(); + void supportsSearchingChanged(); + void bookmarkedPagesChanged(); + void bookmarksChanged(); + void windowTitleForDocumentChanged(); + +private Q_SLOTS: + void searchFinished(int id, Okular::Document::SearchStatus endStatus); + +private: + Okular::Document *m_document; + TOCModel *m_tocModel; + Observer *m_thumbnailObserver; + Observer *m_pageviewObserver; + QList m_matchingPages; + bool m_searchInProgress; +}; + +class Observer : public QObject, public Okular::DocumentObserver +{ + Q_OBJECT + +public: + Observer(DocumentItem *parent); + ~Observer(); + + // inherited from DocumentObserver + void notifyPageChanged(int page, int flags); + +Q_SIGNALS: + void pageChanged(int page, int flags); + +private: + DocumentItem *m_document; +}; + +#endif diff --git a/okular/active/components/okularplugin.cpp b/okular/active/components/okularplugin.cpp new file mode 100644 index 00000000..21939459 --- /dev/null +++ b/okular/active/components/okularplugin.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2012 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "okularplugin.h" + +#include "documentitem.h" +#include "pageitem.h" +#include "thumbnailitem.h" + +#include +#include + +#include +#include + +void OkularPlugin::registerTypes(const char *uri) +{ + Q_ASSERT(uri == QLatin1String("org.kde.okular")); + KGlobal::locale()->insertCatalog("org.kde.okular"); + qmlRegisterType(uri, 0, 1, "DocumentItem"); + qmlRegisterType(uri, 0, 1, "PageItem"); + qmlRegisterType(uri, 0, 1, "ThumbnailItem"); +} + +#include "okularplugin.moc" + diff --git a/okular/active/components/okularplugin.h b/okular/active/components/okularplugin.h new file mode 100644 index 00000000..c229c536 --- /dev/null +++ b/okular/active/components/okularplugin.h @@ -0,0 +1,36 @@ +/* + * Copyright 2012 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef OKULARPLUGIN +#define OKULARPLUGIN + +#include + + +class OkularPlugin : public QDeclarativeExtensionPlugin +{ + Q_OBJECT + +public: + void registerTypes(const char *uri); +}; + +Q_EXPORT_PLUGIN2(okularplugin, OkularPlugin) + +#endif diff --git a/okular/active/components/pageitem.cpp b/okular/active/components/pageitem.cpp new file mode 100644 index 00000000..f9d48f0d --- /dev/null +++ b/okular/active/components/pageitem.cpp @@ -0,0 +1,412 @@ +/* + * Copyright 2012 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "pageitem.h" +#include "documentitem.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "ui/pagepainter.h" +#include "ui/priorities.h" +#include "settings.h" + +#define REDRAW_TIMEOUT 250 + +PageItem::PageItem(QDeclarativeItem *parent) + : QDeclarativeItem(parent), + Okular::View( QString::fromLatin1( "PageView" ) ), + m_page(0), + m_smooth(false), + m_intentionalDraw(false), + m_bookmarked(false), + m_isThumbnail(false) +{ + setFlag(QGraphicsItem::ItemHasNoContents, false); + + m_viewPort.rePos.enabled = true; + + m_redrawTimer = new QTimer(this); + m_redrawTimer->setInterval(REDRAW_TIMEOUT); + m_redrawTimer->setSingleShot(true); + connect(m_redrawTimer, SIGNAL(timeout()), this, SLOT(delayedRedraw())); +} + + +PageItem::~PageItem() +{ +} + +void PageItem::setFlickable(QDeclarativeItem *flickable) +{ + if (m_flickable.data() == flickable) { + return; + } + + //check the object can act as a flickable + if (!flickable->property("contentX").isValid() || + !flickable->property("contentY").isValid()) { + return; + } + + if (m_flickable) { + disconnect(m_flickable.data(), 0, this, 0); + } + + //check the object can act as a flickable + if (!flickable->property("contentX").isValid() || + !flickable->property("contentY").isValid()) { + m_flickable.clear(); + return; + } + + m_flickable = flickable; + + if (flickable) { + connect(flickable, SIGNAL(contentXChanged()), this, SLOT(contentXChanged())); + connect(flickable, SIGNAL(contentYChanged()), this, SLOT(contentYChanged())); + } + + emit flickableChanged(); +} + +QDeclarativeItem *PageItem::flickable() const +{ + return m_flickable.data(); +} + +DocumentItem *PageItem::document() const +{ + return m_documentItem.data(); +} + +void PageItem::setDocument(DocumentItem *doc) +{ + if (doc == m_documentItem.data() || !doc) { + return; + } + + m_page = 0; + disconnect(doc, 0, this, 0); + m_documentItem = doc; + Observer *observer = m_isThumbnail ? m_documentItem.data()->thumbnailObserver() : m_documentItem.data()->pageviewObserver(); + connect(observer, SIGNAL(pageChanged(int, int)), this, SLOT(pageHasChanged(int, int))); + connect(doc->document()->bookmarkManager(), SIGNAL(bookmarksChanged(KUrl)), + this, SLOT(checkBookmarksChanged())); + setPageNumber(0); + emit documentChanged(); + m_redrawTimer->start(); + + connect(doc, SIGNAL(pathChanged()), this, SLOT(documentPathChanged())); +} + +int PageItem::pageNumber() const +{ + return m_viewPort.pageNumber; +} + +void PageItem::setPageNumber(int number) +{ + if ((m_page && m_viewPort.pageNumber == number) || + !m_documentItem || + !m_documentItem.data()->isOpened() || + number < 0 || + (uint)number >= m_documentItem.data()->document()->pages()) { + return; + } + + m_viewPort.pageNumber = number; + m_page = m_documentItem.data()->document()->page(number); + + emit pageNumberChanged(); + emit implicitWidthChanged(); + emit implicitHeightChanged(); + checkBookmarksChanged(); + m_redrawTimer->start(); +} + +int PageItem::implicitWidth() const +{ + if (m_page) { + return m_page->width(); + } + return 0; +} + +int PageItem::implicitHeight() const +{ + if (m_page) { + return m_page->height(); + } + return 0; +} + +void PageItem::setSmooth(const bool smooth) +{ + if (smooth == m_smooth) { + return; + } + m_smooth = smooth; + update(); +} + +bool PageItem::smooth() const +{ + return m_smooth; +} + +bool PageItem::isBookmarked() +{ + return m_bookmarked; +} + +void PageItem::setBookmarked(bool bookmarked) +{ + if (!m_documentItem) { + return; + } + + if (bookmarked == m_bookmarked) { + return; + } + + if (bookmarked) { + m_documentItem.data()->document()->bookmarkManager()->addBookmark(m_viewPort); + } else { + m_documentItem.data()->document()->bookmarkManager()->removeBookmark(m_viewPort.pageNumber); + } + m_bookmarked = bookmarked; + emit bookmarkedChanged(); +} + +QStringList PageItem::bookmarks() const +{ + QStringList list; + foreach(const KBookmark &bookmark, m_documentItem.data()->document()->bookmarkManager()->bookmarks(m_viewPort.pageNumber)) { + list << bookmark.url().prettyUrl(); + } + return list; +} + +void PageItem::goToBookmark(const QString &bookmark) +{ + Okular::DocumentViewport viewPort(KUrl(bookmark).htmlRef()); + setPageNumber(viewPort.pageNumber); + + //Are we in a flickable? + if (m_flickable) { + //normalizedX is a proportion, so contentX will be the difference between document and viewport times normalizedX + m_flickable.data()->setProperty("contentX", qMax((qreal)0, width() - m_flickable.data()->width()) * viewPort.rePos.normalizedX); + + m_flickable.data()->setProperty("contentY", qMax((qreal)0, height() - m_flickable.data()->height()) * viewPort.rePos.normalizedY); + } +} + +QPointF PageItem::bookmarkPosition(const QString &bookmark) const +{ + Okular::DocumentViewport viewPort(KUrl(bookmark).htmlRef()); + + if (viewPort.pageNumber != m_viewPort.pageNumber) { + return QPointF(-1, -1); + } + + return QPointF(qMax((qreal)0, width() - m_flickable.data()->width()) * viewPort.rePos.normalizedX, + qMax((qreal)0, height() - m_flickable.data()->height()) * viewPort.rePos.normalizedY); +} + +void PageItem::setBookmarkAtPos(qreal x, qreal y) +{ + Okular::DocumentViewport viewPort(m_viewPort); + viewPort.rePos.normalizedX = x; + viewPort.rePos.normalizedY = y; + + m_documentItem.data()->document()->bookmarkManager()->addBookmark(viewPort); + + if (!m_bookmarked) { + m_bookmarked = true; + emit bookmarkedChanged(); + } + + emit bookmarksChanged(); +} + +void PageItem::removeBookmarkAtPos(qreal x, qreal y) +{ + Okular::DocumentViewport viewPort(m_viewPort); + viewPort.rePos.enabled = true; + viewPort.rePos.normalizedX = x; + viewPort.rePos.normalizedY = y; + + m_documentItem.data()->document()->bookmarkManager()->addBookmark(viewPort); + + if (m_bookmarked && m_documentItem.data()->document()->bookmarkManager()->bookmarks(m_viewPort.pageNumber).count() == 0) { + m_bookmarked = false; + emit bookmarkedChanged(); + } + + emit bookmarksChanged(); +} + +void PageItem::removeBookmark(const QString &bookmark) +{ + m_documentItem.data()->document()->bookmarkManager()->removeBookmark(bookmark); + emit bookmarksChanged(); +} + +//Reimplemented +void PageItem::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + if (newGeometry.size().isEmpty()) { + return; + } + + if (newGeometry.size() != oldGeometry.size()) { + m_redrawTimer->start(); + } + + QDeclarativeItem::geometryChanged(newGeometry, oldGeometry); + //Why aren't they automatically emuitted? + emit widthChanged(); + emit heightChanged(); +} + +void PageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + if (!m_documentItem || !m_page) { + QDeclarativeItem::paint(painter, option, widget); + return; + } + + const bool setAA = m_smooth && !(painter->renderHints() & QPainter::Antialiasing); + if (setAA) { + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + } + + Observer *observer = m_isThumbnail ? m_documentItem.data()->thumbnailObserver() : m_documentItem.data()->pageviewObserver(); + const int priority = m_isThumbnail ? THUMBNAILS_PRIO : PAGEVIEW_PRIO; + + if (m_intentionalDraw) { + QLinkedList requestedPixmaps; + requestedPixmaps.push_back(new Okular::PixmapRequest(observer, m_viewPort.pageNumber, width(), height(), priority, Okular::PixmapRequest::Asynchronous)); + const Okular::Document::PixmapRequestFlag prf = m_isThumbnail ? Okular::Document::NoOption : Okular::Document::RemoveAllPrevious; + m_documentItem.data()->document()->requestPixmaps(requestedPixmaps, prf); + m_intentionalDraw = false; + } + const int flags = PagePainter::Accessibility | PagePainter::Highlights | PagePainter::Annotations; + PagePainter::paintPageOnPainter(painter, m_page, observer, flags, width(), height(), option->exposedRect.toRect()); + + if (setAA) { + painter->restore(); + } +} + +//Protected slots +void PageItem::delayedRedraw() +{ + if (m_documentItem && m_page) { + m_intentionalDraw = true; + update(); + } +} + +void PageItem::pageHasChanged(int page, int flags) +{ + if (m_viewPort.pageNumber == page) { + if (flags == 32) { + // skip bounding box updates + //kDebug() << "32" << m_page->boundingBox(); + } else if (flags == Okular::DocumentObserver::Pixmap) { + // if pixmaps have updated, just repaint .. don't bother updating pixmaps AGAIN + update(); + } else { + m_redrawTimer->start(); + } + } +} + +void PageItem::checkBookmarksChanged() +{ + if (!m_documentItem) { + return; + } + + bool newBookmarked = m_documentItem.data()->document()->bookmarkManager()->isBookmarked(m_viewPort.pageNumber); + if (m_bookmarked != newBookmarked) { + m_bookmarked = newBookmarked; + emit bookmarkedChanged(); + } + + //TODO: check the page + emit bookmarksChanged(); +} + +void PageItem::contentXChanged() +{ + if (!m_flickable || !m_flickable.data()->property("contentX").isValid()) { + return; + } + + m_viewPort.rePos.normalizedX = m_flickable.data()->property("contentX").toReal() / (width() - m_flickable.data()->width()); +} + +void PageItem::contentYChanged() +{ + if (!m_flickable || !m_flickable.data()->property("contentY").isValid()) { + return; + } + + m_viewPort.rePos.normalizedY = m_flickable.data()->property("contentY").toReal() / (height() - m_flickable.data()->height()); +} + +void PageItem::documentPathChanged() +{ + m_page = 0; + setPageNumber(0); + + m_redrawTimer->start(); +} + + +void PageItem::setIsThumbnail(bool thumbnail) +{ + if (thumbnail == m_isThumbnail) { + return; + } + + m_isThumbnail = thumbnail; + + if (thumbnail) { + m_smooth = false; + } + + /* + m_redrawTimer->setInterval(thumbnail ? 0 : REDRAW_TIMEOUT); + m_redrawTimer->setSingleShot(true); + */ +} + +#include "pageitem.moc" diff --git a/okular/active/components/pageitem.h b/okular/active/components/pageitem.h new file mode 100644 index 00000000..932bde32 --- /dev/null +++ b/okular/active/components/pageitem.h @@ -0,0 +1,174 @@ +/* + * Copyright 2012 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef QPAGEITEM_H +#define QPAGEITEM_H + +#include + +#include +#include + +class QTimer; + +class DocumentItem; + +namespace Okular { + class Document; + class Page; +} + +class PageItem : public QDeclarativeItem, public Okular::View +{ + Q_OBJECT + + /** + * If this page is in a Flickable, assign it in this property, to make goToBookmark work + */ + Q_PROPERTY(QDeclarativeItem *flickable READ flickable WRITE setFlickable NOTIFY flickableChanged) + + /** + * The document this page belongs to + */ + Q_PROPERTY(DocumentItem *document READ document WRITE setDocument NOTIFY documentChanged) + + /** + * The currently displayed page + */ + Q_PROPERTY(int pageNumber READ pageNumber WRITE setPageNumber NOTIFY pageNumberChanged) + + /** + * If true, the page will be rendered with antialias + */ + Q_PROPERTY(bool smooth READ smooth WRITE setSmooth) + + /** + * "Natural" width of the page + */ + Q_PROPERTY(int implicitWidth READ implicitWidth NOTIFY implicitWidthChanged) + + /** + * "Natural" height of the page + */ + Q_PROPERTY(int implicitHeight READ implicitHeight NOTIFY implicitHeightChanged) + + /** + * True if the page contains at least a bookmark. + * Writing true to tis property idds a bookmark at the beginning of the page (if needed). + * Writing false, all bookmarks for this page will be removed + */ + Q_PROPERTY(bool bookmarked READ isBookmarked WRITE setBookmarked NOTIFY bookmarkedChanged) + + /** + * list of bookmarks urls valid on this page + */ + Q_PROPERTY(QStringList bookmarks READ bookmarks NOTIFY bookmarksChanged) + +public: + + PageItem(QDeclarativeItem *parent=0); + ~PageItem(); + + void setFlickable(QDeclarativeItem *flickable); + QDeclarativeItem *flickable() const; + + int implicitWidth() const; + int implicitHeight() const; + + DocumentItem *document() const; + void setDocument(DocumentItem *doc); + + int pageNumber() const; + void setPageNumber(int number); + + bool smooth() const; + void setSmooth(bool smooth); + + bool isBookmarked(); + void setBookmarked(bool bookmarked); + + QStringList bookmarks() const; + + /** + * loads a page bookmark and tries to ensure the bookmarked position is visible + * @param bookmark Url for the bookmark + */ + Q_INVOKABLE void goToBookmark(const QString &bookmark); + + /** + * Returns the position in the page for a bookmark + * QPointF(-1,-1) if doesn't belong to this page + * + * @param bookmark Url for the bookmark + */ + Q_INVOKABLE QPointF bookmarkPosition(const QString &bookmark) const; + + /** + * Add a new bookmark ar a given position of the current page + */ + Q_INVOKABLE void setBookmarkAtPos(qreal x, qreal y); + + /** + * Remove a bookmark ar a given position of the current page (if present) + */ + Q_INVOKABLE void removeBookmarkAtPos(qreal x, qreal y); + + /** + * Remove a bookmark at a given position, if any + */ + Q_INVOKABLE void removeBookmark(const QString &bookmark); + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + +Q_SIGNALS: + void flickableChanged(); + void implicitWidthChanged(); + void implicitHeightChanged(); + void documentChanged(); + void pageNumberChanged(); + void bookmarkedChanged(); + void bookmarksChanged(); + +protected: + void setIsThumbnail(bool thumbnail); + +private Q_SLOTS: + void delayedRedraw(); + void documentPathChanged(); + void pageHasChanged(int page, int flags); + void checkBookmarksChanged(); + void contentXChanged(); + void contentYChanged(); + +private: + const Okular::Page *m_page; + bool m_smooth; + bool m_intentionalDraw; + bool m_bookmarked; + bool m_isThumbnail; + QWeakPointer m_documentItem; + QTimer *m_redrawTimer; + QWeakPointer m_flickable; + Okular::DocumentViewport m_viewPort; +}; + +#endif diff --git a/okular/active/components/qmldir b/okular/active/components/qmldir new file mode 100644 index 00000000..d62e2aed --- /dev/null +++ b/okular/active/components/qmldir @@ -0,0 +1,2 @@ +plugin okularplugin + diff --git a/okular/active/components/thumbnailitem.cpp b/okular/active/components/thumbnailitem.cpp new file mode 100644 index 00000000..e56170c2 --- /dev/null +++ b/okular/active/components/thumbnailitem.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2012 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "thumbnailitem.h" + +ThumbnailItem::ThumbnailItem(QDeclarativeItem *parent) + : PageItem(parent) +{ + setIsThumbnail(true); +} + + +ThumbnailItem::~ThumbnailItem() +{ +} + + +#include "thumbnailitem.moc" diff --git a/okular/active/components/thumbnailitem.h b/okular/active/components/thumbnailitem.h new file mode 100644 index 00000000..ad3a341c --- /dev/null +++ b/okular/active/components/thumbnailitem.h @@ -0,0 +1,35 @@ +/* + * Copyright 2012 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef THUMBNAILITEM_H +#define THUMBNAILITEM_H + +#include "pageitem.h" + +class ThumbnailItem : public PageItem +{ + Q_OBJECT + +public: + + ThumbnailItem(QDeclarativeItem *parent=0); + ~ThumbnailItem(); +}; + +#endif diff --git a/okular/cmake/modules/COPYING-CMAKE-SCRIPTS b/okular/cmake/modules/COPYING-CMAKE-SCRIPTS new file mode 100644 index 00000000..4b417765 --- /dev/null +++ b/okular/cmake/modules/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/okular/cmake/modules/FindCHM.cmake b/okular/cmake/modules/FindCHM.cmake new file mode 100644 index 00000000..4e7ae292 --- /dev/null +++ b/okular/cmake/modules/FindCHM.cmake @@ -0,0 +1,33 @@ +# - Try to find the chm library +# Once done this will define +# +# CHM_FOUND - system has the chm library +# CHM_INCLUDE_DIR - the chm include directory +# CHM_LIBRARY - Link this to use the chm library +# +# Copyright (c) 2006, Pino Toscano, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +if (CHM_LIBRARY AND CHM_INCLUDE_DIR) + # in cache already + set(CHM_FOUND TRUE) +else (CHM_LIBRARY AND CHM_INCLUDE_DIR) + + find_path(CHM_INCLUDE_DIR chm_lib.h + ${GNUWIN32_DIR}/include + ) + + find_library(CHM_LIBRARY NAMES chm + PATHS + ${GNUWIN32_DIR}/lib + ) + + include(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(CHM DEFAULT_MSG CHM_INCLUDE_DIR CHM_LIBRARY ) + # ensure that they are cached + set(CHM_INCLUDE_DIR ${CHM_INCLUDE_DIR} CACHE INTERNAL "The chmlib include path") + set(CHM_LIBRARY ${CHM_LIBRARY} CACHE INTERNAL "The libraries needed to use chmlib") + +endif (CHM_LIBRARY AND CHM_INCLUDE_DIR) diff --git a/okular/cmake/modules/FindDjVuLibre.cmake b/okular/cmake/modules/FindDjVuLibre.cmake new file mode 100644 index 00000000..4403127e --- /dev/null +++ b/okular/cmake/modules/FindDjVuLibre.cmake @@ -0,0 +1,39 @@ +# - Try to find the DjVuLibre library +# Once done this will define +# +# DJVULIBRE_FOUND - system has the DjVuLibre library +# DJVULIBRE_INCLUDE_DIR - the DjVuLibre include directory +# DJVULIBRE_LIBRARY - Link this to use the DjVuLibre library + +if (DJVULIBRE_INCLUDE_DIR AND DJVULIBRE_LIBRARY) + + # in cache already + set(DJVULIBRE_FOUND TRUE) + +else (DJVULIBRE_INCLUDE_DIR AND DJVULIBRE_LIBRARY) + IF (NOT WIN32) + find_package(PkgConfig) + + pkg_check_modules(PC_DJVULIBRE ddjvuapi) + endif(NOT WIN32) + + find_path(DJVULIBRE_INCLUDE_DIR libdjvu/ddjvuapi.h + PATHS + ${PC_DJVULIBRE_INCLUDEDIR} + ${PC_DJVULIBRE_INCLUDE_DIRS} + ${GNUWIN32_DIR}/include + ) + + find_library(DJVULIBRE_LIBRARY NAMES djvulibre + PATHS + ${PC_DJVULIBRE_LIBDIR} + ${PC_DJVULIBRE_LIBRARY_DIRS} + ${GNUWIN32_DIR}/lib + ) + + include(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(DjVuLibre DEFAULT_MSG DJVULIBRE_INCLUDE_DIR DJVULIBRE_LIBRARY ) + + mark_as_advanced(DJVULIBRE_INCLUDE_DIR DJVULIBRE_LIBRARY) + +endif (DJVULIBRE_INCLUDE_DIR AND DJVULIBRE_LIBRARY) diff --git a/okular/cmake/modules/FindEPub.cmake b/okular/cmake/modules/FindEPub.cmake new file mode 100644 index 00000000..929ad65f --- /dev/null +++ b/okular/cmake/modules/FindEPub.cmake @@ -0,0 +1,35 @@ +# - Find EPub +# Find the EPub library. +# +# This module defines +# EPUB_FOUND - whether the EPub library was found +# EPUB_LIBRARIES - the EPub library +# EPUB_INCLUDE_DIR - the include path of the EPub library + +# Copyright (c) 2008, Pino Toscano, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +if (EPUB_INCLUDE_DIR AND EPUB_LIBRARIES) + + # Already in cache + set (EPUB_FOUND TRUE) + +else (EPUB_INCLUDE_DIR AND EPUB_LIBRARIES) + + find_library (EPUB_LIBRARIES + NAMES epub libepub + ) + + find_path (EPUB_INCLUDE_DIR + NAMES epub.h + ) + + include (FindPackageHandleStandardArgs) + find_package_handle_standard_args (EPub DEFAULT_MSG EPUB_LIBRARIES EPUB_INCLUDE_DIR) + +endif (EPUB_INCLUDE_DIR AND EPUB_LIBRARIES) + +mark_as_advanced(EPUB_INCLUDE_DIR EPUB_LIBRARIES) diff --git a/okular/cmake/modules/FindLibSpectre.cmake b/okular/cmake/modules/FindLibSpectre.cmake new file mode 100644 index 00000000..8d5dc765 --- /dev/null +++ b/okular/cmake/modules/FindLibSpectre.cmake @@ -0,0 +1,67 @@ +# - Try to find the libspectre PS library +# Once done this will define +# +# LIBSPECTRE_FOUND - system has libspectre +# LIBSPECTRE_INCLUDE_DIR - the libspectre include directory +# LIBSPECTRE_LIBRARY - Link this to use libspectre +# + +# Copyright (c) 2006-2007, Pino Toscano, +# Copyright (c) 2008, Albert Astals Cid, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +if(LIBSPECTRE_INCLUDE_DIR AND LIBSPECTRE_LIBRARY) + + # in cache already + set(LIBSPECTRE_INTERNAL_FOUND TRUE) + +else(LIBSPECTRE_INCLUDE_DIR AND LIBSPECTRE_LIBRARY) + +if(NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + INCLUDE(UsePkgConfig) + + PKGCONFIG(libspectre _SpectreIncDir _SpectreLinkDir _SpectreLinkFlags _SpectreCflags) + + if(_SpectreLinkFlags) + # find again pkg-config, to query it about libspectre version + FIND_PROGRAM(PKGCONFIG_EXECUTABLE NAMES pkg-config PATHS /usr/bin/ /usr/local/bin ) + + # query pkg-config asking for a libspectre >= LIBSPECTRE_MINIMUM_VERSION + EXEC_PROGRAM(${PKGCONFIG_EXECUTABLE} ARGS --atleast-version=${LIBSPECTRE_MINIMUM_VERSION} libspectre RETURN_VALUE _return_VALUE OUTPUT_VARIABLE _pkgconfigDevNull ) + if(_return_VALUE STREQUAL "0") + set(LIBSPECTRE_INTERNAL_FOUND TRUE) + endif(_return_VALUE STREQUAL "0") + endif(_SpectreLinkFlags) +else(NOT WIN32) + # do not use pkg-config on windows + find_library(_SpectreLinkFlags NAMES libspectre spectre PATHS ${CMAKE_LIBRARY_PATH}) + + find_path(LIBSPECTRE_INCLUDE_DIR spectre.h PATH_SUFFIXES libspectre ) + + set(LIBSPECTRE_INTERNAL_FOUND TRUE) +endif(NOT WIN32) + +if (LIBSPECTRE_INTERNAL_FOUND) + set(LIBSPECTRE_LIBRARY ${_SpectreLinkFlags}) + + # the cflags for libspectre can contain more than one include path + separate_arguments(_SpectreCflags) + foreach(_includedir ${_SpectreCflags}) + string(REGEX REPLACE "-I(.+)" "\\1" _includedir "${_includedir}") + set(LIBSPECTRE_INCLUDE_DIR ${LIBSPECTRE_INCLUDE_DIR} ${_includedir}) + endforeach(_includedir) + +endif (LIBSPECTRE_INTERNAL_FOUND) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibSpectre DEFAULT_MSG LIBSPECTRE_LIBRARY LIBSPECTRE_INTERNAL_FOUND) + +# ensure that they are cached +set(LIBSPECTRE_INCLUDE_DIR ${LIBSPECTRE_INCLUDE_DIR} CACHE INTERNAL "The libspectre include path") +set(LIBSPECTRE_LIBRARY ${LIBSPECTRE_LIBRARY} CACHE INTERNAL "The libspectre library") + +endif(LIBSPECTRE_INCLUDE_DIR AND LIBSPECTRE_LIBRARY) diff --git a/okular/cmake/modules/FindPoppler.cmake b/okular/cmake/modules/FindPoppler.cmake new file mode 100644 index 00000000..6389a625 --- /dev/null +++ b/okular/cmake/modules/FindPoppler.cmake @@ -0,0 +1,170 @@ +# - Try to find the poppler PDF library +# Once done this will define +# +# POPPLER_FOUND - system has poppler +# POPPLER_INCLUDE_DIR - the poppler include directory +# POPPLER_LIBRARY - Link this to use poppler +# + +# Copyright (c) 2006-2010, Pino Toscano, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +if(POPPLER_INCLUDE_DIR AND POPPLER_LIBRARY) + + # in cache already + set(POPPLER_FOUND TRUE) + +else(POPPLER_INCLUDE_DIR AND POPPLER_LIBRARY) + +set(_poppler_version_bad FALSE) + +if(NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + include(FindPkgConfig) + pkg_check_modules(_pc_poppler poppler-qt4) + if(_pc_poppler_FOUND) + if(NOT "${_pc_poppler_VERSION}" VERSION_GREATER 0.5.3) + set(_poppler_version_bad TRUE) + endif(NOT "${_pc_poppler_VERSION}" VERSION_GREATER 0.5.3) + endif(_pc_poppler_FOUND) +endif(NOT WIN32) + +if(NOT _poppler_version_bad) + set(POPPLER_FOUND FALSE) + + find_library(POPPLER_LIBRARY poppler-qt4 + HINTS ${_pc_poppler_LIBRARY_DIRS} + ) + + find_path(POPPLER_INCLUDE_DIR poppler-qt4.h + HINTS ${_pc_poppler_INCLUDE_DIRS} + PATH_SUFFIXES poppler/qt4 + ) + find_path(POPPLER_INCLUDE_DIR_core qt4/poppler-qt4.h + HINTS ${_pc_poppler_INCLUDE_DIRS} + PATH_SUFFIXES poppler + ) + + if(POPPLER_LIBRARY AND POPPLER_INCLUDE_DIR AND POPPLER_INCLUDE_DIR_core) + list(APPEND POPPLER_INCLUDE_DIR "${POPPLER_INCLUDE_DIR_core}") + set(POPPLER_FOUND TRUE) + endif(POPPLER_LIBRARY AND POPPLER_INCLUDE_DIR AND POPPLER_INCLUDE_DIR_core) +endif(NOT _poppler_version_bad) + +if (POPPLER_FOUND) + include(CheckCXXSourceCompiles) + + # check whether we're using poppler 0.6 + set(CMAKE_REQUIRED_INCLUDES ${POPPLER_INCLUDE_DIR} ${QT_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${POPPLER_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTXML_LIBRARY}) + +check_cxx_source_compiles(" +#include +int main() +{ + Poppler::Document::RenderHint hint = Poppler::Document::TextHinting; + return 0; +} +" HAVE_POPPLER_0_12_1) + +check_cxx_source_compiles(" +#include + +void debugFunction(const QString &message, const QVariant &closure) +{ +} + +int main() +{ + Poppler::setDebugErrorFunction(debugFunction, QVariant()); + return 0; +} +" HAVE_POPPLER_0_16) + +check_cxx_source_compiles(" +#include +#include + +int main() +{ + Poppler::ScreenAnnotation *annot = 0; + Poppler::LinkRendition *link = 0; + const Poppler::LinkMovie::Operation operation = Poppler::LinkMovie::Play; + return 0; +} +" HAVE_POPPLER_0_20) + +check_cxx_source_compiles(" +#include +#include +#include + +int main() +{ + Poppler::MovieObject *movie = 0; + Poppler::Document *doc = 0; + movie->showPosterImage(); + + const Poppler::Annotation::AdditionalActionType type = Poppler::Annotation::PageOpeningAction; + const Poppler::LinkRendition::RenditionAction action = Poppler::LinkRendition::NoRendition; + const Poppler::Document::FormType formType = doc->formType(); + + return 0; +} +" HAVE_POPPLER_0_22) + +check_cxx_source_compiles(" +#include +int main() +{ + Poppler::Document::RenderHint hint = Poppler::Document::ThinLineSolid; + return 0; +} +" HAVE_POPPLER_0_24) + +check_cxx_source_compiles(" +#include +int main() +{ + Poppler::Page *p = 0; + p->annotations( QSet() << Poppler::Annotation::ASound ); + return 0; +} +" HAVE_POPPLER_0_28) + + set(CMAKE_REQUIRED_INCLUDES) + set(CMAKE_REQUIRED_LIBRARIES) + if (HAVE_POPPLER_0_28) + set(popplerVersionMessage "0.28") + elseif (HAVE_POPPLER_0_24) + set(popplerVersionMessage "0.24") + elseif (HAVE_POPPLER_0_22) + set(popplerVersionMessage "0.22") + elseif (HAVE_POPPLER_0_20) + set(popplerVersionMessage "0.20") + elseif (HAVE_POPPLER_0_16) + set(popplerVersionMessage "0.16") + elseif (HAVE_POPPLER_0_12_1) + set(popplerVersionMessage "0.12.1") + else (HAVE_POPPLER_0_28) + set(popplerVersionMessage "0.5.4") + endif (HAVE_POPPLER_0_28) + if (NOT Poppler_FIND_QUIETLY) + message(STATUS "Found Poppler-Qt4: ${POPPLER_LIBRARY}, (>= ${popplerVersionMessage})") + endif (NOT Poppler_FIND_QUIETLY) +else (POPPLER_FOUND) + if (Poppler_FIND_REQUIRED) + message(FATAL_ERROR "Could NOT find Poppler-Qt4") + endif (Poppler_FIND_REQUIRED) + message(STATUS "Could not find OPTIONAL package Poppler-Qt4") +endif (POPPLER_FOUND) + +# ensure that they are cached +set(POPPLER_INCLUDE_DIR ${POPPLER_INCLUDE_DIR} CACHE INTERNAL "The Poppler-Qt4 include path") +set(POPPLER_LIBRARY ${POPPLER_LIBRARY} CACHE INTERNAL "The Poppler-Qt4 library") +set(HAVE_POPPLER_0_12_1 ${HAVE_POPPLER_0_12_1} CACHE INTERNAL "Whether the version of Poppler-Qt4 is >= 0.12.1") + +endif(POPPLER_INCLUDE_DIR AND POPPLER_LIBRARY) diff --git a/okular/conf/dlgaccessibility.cpp b/okular/conf/dlgaccessibility.cpp new file mode 100644 index 00000000..6446b0ac --- /dev/null +++ b/okular/conf/dlgaccessibility.cpp @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "dlgaccessibility.h" + +#include "ui_dlgaccessibilitybase.h" + +DlgAccessibility::DlgAccessibility( QWidget * parent ) + : QWidget( parent ), m_selected( 0 ) +{ + m_dlg = new Ui_DlgAccessibilityBase(); + m_dlg->setupUi( this ); + + // ### not working yet, hide for now + m_dlg->kcfg_HighlightImages->hide(); + + m_color_pages.append( m_dlg->page_invert ); + m_color_pages.append( m_dlg->page_paperColor ); + m_color_pages.append( m_dlg->page_darkLight ); + m_color_pages.append( m_dlg->page_bw ); + foreach ( QWidget * page, m_color_pages ) + page->hide(); + m_color_pages[ m_selected ]->show(); + + connect( m_dlg->kcfg_RenderMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotColorMode(int)) ); +} + +DlgAccessibility::~DlgAccessibility() +{ + delete m_dlg; +} + +void DlgAccessibility::slotColorMode( int mode ) +{ + m_color_pages[ m_selected ]->hide(); + m_color_pages[ mode ]->show(); + + m_selected = mode; +} + +#include "dlgaccessibility.moc" diff --git a/okular/conf/dlgaccessibility.h b/okular/conf/dlgaccessibility.h new file mode 100644 index 00000000..a6cf60fd --- /dev/null +++ b/okular/conf/dlgaccessibility.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _DLGACCESSIBILITY_H +#define _DLGACCESSIBILITY_H + +#include +#include + +class Ui_DlgAccessibilityBase; + +class DlgAccessibility : public QWidget +{ + Q_OBJECT + + public: + DlgAccessibility( QWidget * parent = 0 ); + ~DlgAccessibility(); + + private slots: + void slotColorMode( int mode ); + + private: + Ui_DlgAccessibilityBase * m_dlg; + QList< QWidget * > m_color_pages; + int m_selected; +}; + +#endif diff --git a/okular/conf/dlgaccessibilitybase.ui b/okular/conf/dlgaccessibilitybase.ui new file mode 100644 index 00000000..9e76a754 --- /dev/null +++ b/okular/conf/dlgaccessibilitybase.ui @@ -0,0 +1,386 @@ + + DlgAccessibilityBase + + + + 0 + 0 + 374 + 327 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Draw border around &Images + + + + + + + Draw border around &Links + + + + + + + true + + + Change &colors + + + true + + + false + + + + + + + 0 + 0 + + + + Warning: these options can badly affect drawing speed. + + + Qt::PlainText + + + true + + + + + + + + + Color mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Invert Colors + + + + + Change Paper Color + + + + + Change Dark & Light Colors + + + + + Convert to Black & White + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 8 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Paper color: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + kcfg_PaperColor + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Dark color: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Light color: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + Threshold: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + 16 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Contrast: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + 1 + + + Qt::Horizontal + + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + + + + KColorButton + QPushButton +
kcolorbutton.h
+
+ + KComboBox + QComboBox +
kcombobox.h
+
+
+ + +
diff --git a/okular/conf/dlgannotations.cpp b/okular/conf/dlgannotations.cpp new file mode 100644 index 00000000..b6fc85a5 --- /dev/null +++ b/okular/conf/dlgannotations.cpp @@ -0,0 +1,28 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "dlgannotations.h" + +#include + +#include "widgetannottools.h" +#include "ui_dlgannotationsbase.h" + +DlgAnnotations::DlgAnnotations( QWidget * parent ) + : QWidget( parent ) +{ + Ui_DlgAnnotationsBase dlg; + dlg.setupUi( this ); + + WidgetAnnotTools * kcfg_AnnotationTools = new WidgetAnnotTools( dlg.annotToolsGroup ); + dlg.annotToolsPlaceholder->addWidget( kcfg_AnnotationTools ); + kcfg_AnnotationTools->setObjectName( "kcfg_AnnotationTools" ); + + KConfigDialogManager::changedMap()->insert( "WidgetAnnotTools" , SIGNAL(changed()) ); +} diff --git a/okular/conf/dlgannotations.h b/okular/conf/dlgannotations.h new file mode 100644 index 00000000..bdc1399f --- /dev/null +++ b/okular/conf/dlgannotations.h @@ -0,0 +1,22 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _DLGANNOTATIONS_H_ +#define _DLGANNOTATIONS_H_ + +#include + + +class DlgAnnotations : public QWidget +{ + public: + DlgAnnotations( QWidget * parent = 0 ); +}; + +#endif diff --git a/okular/conf/dlgannotationsbase.ui b/okular/conf/dlgannotationsbase.ui new file mode 100644 index 00000000..616b292c --- /dev/null +++ b/okular/conf/dlgannotationsbase.ui @@ -0,0 +1,109 @@ + + DlgAnnotationsBase + + + + 0 + 0 + 381 + 155 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Identity + + + + 9 + + + 9 + + + 9 + + + 9 + + + 6 + + + 6 + + + + + &Author: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + kcfg_IdentityAuthor + + + + + + + + + + <b>Note</b>: the information here is used only for comments and reviews. Information inserted here will not be transmitted without your knowledge. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + + Annotation tools + + + + + + + + Qt::Vertical + + + + 20 + 4 + + + + + + + + + diff --git a/okular/conf/dlgdebug.cpp b/okular/conf/dlgdebug.cpp new file mode 100644 index 00000000..b5b66b17 --- /dev/null +++ b/okular/conf/dlgdebug.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "dlgdebug.h" + +#include +#include + +#define DEBUG_SIMPLE_BOOL( cfgname, layout ) \ +{ \ + QCheckBox * foo = new QCheckBox( cfgname, this ); \ + foo->setObjectName( "kcfg_" cfgname ); \ + layout->addWidget( foo ); \ +} + +DlgDebug::DlgDebug( QWidget * parent ) + : QWidget( parent ) +{ + QVBoxLayout * lay = new QVBoxLayout( this ); + lay->setMargin( 0 ); + + DEBUG_SIMPLE_BOOL( "DebugDrawBoundaries", lay ); + DEBUG_SIMPLE_BOOL( "DebugDrawAnnotationRect", lay ); + DEBUG_SIMPLE_BOOL( "TocPageColumn", lay ); + + lay->addItem( new QSpacerItem( 5, 5, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding ) ); +} diff --git a/okular/conf/dlgdebug.h b/okular/conf/dlgdebug.h new file mode 100644 index 00000000..43d87d08 --- /dev/null +++ b/okular/conf/dlgdebug.h @@ -0,0 +1,21 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _DLGDEBUG_H +#define _DLGDEBUG_H + +#include + +class DlgDebug : public QWidget +{ + public: + DlgDebug( QWidget * parent = 0 ); +}; + +#endif diff --git a/okular/conf/dlgeditor.cpp b/okular/conf/dlgeditor.cpp new file mode 100644 index 00000000..da93ae54 --- /dev/null +++ b/okular/conf/dlgeditor.cpp @@ -0,0 +1,71 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "dlgeditor.h" + +#include "core/texteditors_p.h" + +#include + +#include "ui_dlgeditorbase.h" + +DlgEditor::DlgEditor( QWidget * parent ) + : QWidget( parent ) +{ + m_dlg = new Ui_DlgEditorBase(); + m_dlg->setupUi( this ); + + m_editors = Okular::buildEditorsMap(); + + connect( m_dlg->kcfg_ExternalEditor, SIGNAL(currentIndexChanged(int)), this, SLOT(editorChanged(int)) ); + + m_dlg->kcfg_ExternalEditor->addItem( i18nc( "Text editor", "Custom Text Editor" ) ); + m_dlg->kcfg_ExternalEditor->addItem( i18nc( "Text editor", "Kate" ), 1 ); + m_dlg->kcfg_ExternalEditor->addItem( i18nc( "Text editor", "Kile" ), 2 ); + m_dlg->kcfg_ExternalEditor->addItem( i18nc( "Text editor", "SciTE" ), 3 ); + m_dlg->kcfg_ExternalEditor->addItem( i18nc( "Text editor", "Emacs client" ), 4 ); + m_dlg->kcfg_ExternalEditor->addItem( i18nc( "Text editor", "Lyx client" ), 5 ); + + m_dlg->kcfg_ExternalEditorCommand->setWhatsThis( i18nc( "@info:whatsthis", + "Set the command of a custom text editor to be launched.
\n" + "You can also put few placeholders:\n" + "
    \n" + "
  • %f - the file name
  • \n" + "
  • %l - the line of the file to be reached
  • \n" + "
  • %c - the column of the file to be reached
  • \n" + "
\n" + "If %f is not specified, then the file name is appended to the specified " + "command." ) ); +} + +DlgEditor::~DlgEditor() +{ + delete m_dlg; +} + +void DlgEditor::editorChanged( int which ) +{ + const int whichEditor = m_dlg->kcfg_ExternalEditor->itemData( which ).toInt(); + const QHash< int, QString >::const_iterator it = m_editors.constFind( whichEditor ); + QString editor; + if ( it != m_editors.constEnd() ) + editor = it.value(); + + if ( !editor.isEmpty() ) + { + m_dlg->stackCommands->setCurrentIndex( 1 ); + m_dlg->leReadOnlyCommand->setText( editor ); + } + else + { + m_dlg->stackCommands->setCurrentIndex( 0 ); + } +} + +#include "dlgeditor.moc" diff --git a/okular/conf/dlgeditor.h b/okular/conf/dlgeditor.h new file mode 100644 index 00000000..a639fbbf --- /dev/null +++ b/okular/conf/dlgeditor.h @@ -0,0 +1,34 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef DLGEDITOR_H +#define DLGEDITOR_H + +#include +#include + +class Ui_DlgEditorBase; + +class DlgEditor : public QWidget +{ + Q_OBJECT + + public: + DlgEditor( QWidget * parent = 0 ); + virtual ~DlgEditor(); + + private slots: + void editorChanged( int which ); + + private: + Ui_DlgEditorBase * m_dlg; + QHash< int, QString > m_editors; +}; + +#endif diff --git a/okular/conf/dlgeditorbase.ui b/okular/conf/dlgeditorbase.ui new file mode 100644 index 00000000..81bea5ce --- /dev/null +++ b/okular/conf/dlgeditorbase.ui @@ -0,0 +1,97 @@ + + DlgEditorBase + + + + 0 + 0 + 375 + 108 + + + + + 0 + + + + + Editor + + + + + + Editor: + + + + + + + Choose the editor you want to launch when Okular wants to open a source file. + + + + + + + 1 + + + + + 0 + + + + + + + + + + 0 + + + + + true + + + + + + + + + + + Command: + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 4 + + + + + + + + + diff --git a/okular/conf/dlggeneral.cpp b/okular/conf/dlggeneral.cpp new file mode 100644 index 00000000..ad389f39 --- /dev/null +++ b/okular/conf/dlggeneral.cpp @@ -0,0 +1,49 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "dlggeneral.h" + +#include + +#include + +#include "ui_dlggeneralbase.h" + +DlgGeneral::DlgGeneral( QWidget * parent, Okular::EmbedMode embedMode ) + : QWidget( parent ) +{ + m_dlg = new Ui_DlgGeneralBase(); + m_dlg->setupUi( this ); + + if( embedMode == Okular::ViewerWidgetMode ) + { + m_dlg->kcfg_SyncThumbnailsViewport->setVisible( false ); + m_dlg->kcfg_DisplayDocumentTitle->setVisible( false ); + m_dlg->kcfg_WatchFile->setVisible( false ); + } + m_dlg->kcfg_ShellOpenFileInTabs->setVisible( embedMode == Okular::NativeShellMode ); +} + +DlgGeneral::~DlgGeneral() +{ + delete m_dlg; +} + +void DlgGeneral::showEvent( QShowEvent * ) +{ +#if OKULAR_FORCE_DRM + m_dlg->kcfg_ObeyDRM->hide(); +#else + if ( KAuthorized::authorize( "skip_drm" ) ) + m_dlg->kcfg_ObeyDRM->show(); + else + m_dlg->kcfg_ObeyDRM->hide(); +#endif +} + diff --git a/okular/conf/dlggeneral.h b/okular/conf/dlggeneral.h new file mode 100644 index 00000000..e1b42c1b --- /dev/null +++ b/okular/conf/dlggeneral.h @@ -0,0 +1,31 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _DLGGENERAL_H +#define _DLGGENERAL_H + +#include + +#include "part.h" + +class Ui_DlgGeneralBase; + +class DlgGeneral : public QWidget +{ + public: + DlgGeneral( QWidget * parent, Okular::EmbedMode embedMode ); + virtual ~DlgGeneral(); + + protected: + virtual void showEvent( QShowEvent * ); + + Ui_DlgGeneralBase * m_dlg; +}; + +#endif diff --git a/okular/conf/dlggeneralbase.ui b/okular/conf/dlggeneralbase.ui new file mode 100755 index 00000000..1cf6ea95 --- /dev/null +++ b/okular/conf/dlggeneralbase.ui @@ -0,0 +1,406 @@ + + + DlgGeneralBase + + + + 0 + 0 + 558 + 575 + + + + + 0 + + + + + Appearance + + + + 6 + + + 9 + + + + + 6 + + + 0 + + + + + Show scroll&bars + + + + + + + true + + + Link the &thumbnails with the page + + + + + + + Show &hints and info messages + + + + + + + Display document title in titlebar if available + + + + + + + When not displaying document title: + + + + + + + KButtonGroup {border:0; } + + + true + + + 0 + + + + 8 + + + 0 + + + 0 + + + + + Display file name only + + + true + + + + + + + Display full file path + + + false + + + + + + + + + + 4 + + + 0 + + + + + + + + + 6 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 1 + + + + + + + + + + + + + Program Features + + + + 6 + + + 9 + + + + + 6 + + + 0 + + + + + Open new files in &tabs + + + + + + + &Obey DRM limitations + + + + + + + &Reload document on file change + + + + + + + Show backend selection dialog + + + + + + + + + 6 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 1 + + + + + + + + + + + + + View Options + + + + + + Overview &columns: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + kcfg_ViewColumns + + + + + + + 3 + + + 10 + + + + + + + Defines how much of the current viewing area will still be visible when pressing the Page Up/Down keys. + + + &Page Up/Down overlap: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + kcfg_ScrollOverlap + + + + + + + % + + + 50 + + + 5 + + + + + + + Defines the default zoom mode for files which were never opened before. +For files which were opened before the previous zoom is applied. + + + Defines the default zoom mode for files which were never opened before. +For files which were opened before the previous zoom is applied. + + + &Default Zoom: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + kcfg_ZoomMode + + + + + + + Defines the default zoom mode for files which were never opened before. +For files which were opened before the previous zoom is applied. + + + Defines the default zoom mode for files which were never opened before. +For files which were opened before the previous zoom is applied. + + + + 100% + + + + + Fit Width + + + + + Fit Page + + + + + Auto Fit + + + + + + + + + + + Qt::Vertical + + + + 20 + 4 + + + + + + + + + KButtonGroup + QGroupBox +
kbuttongroup.h
+ 1 +
+ + KIntSpinBox + QSpinBox +
knuminput.h
+
+
+ + kiconloader.h + + + +
diff --git a/okular/conf/dlgperformance.cpp b/okular/conf/dlgperformance.cpp new file mode 100644 index 00000000..1275975f --- /dev/null +++ b/okular/conf/dlgperformance.cpp @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "dlgperformance.h" + +#include +#include + +#include "ui_dlgperformancebase.h" + +DlgPerformance::DlgPerformance( QWidget * parent ) + : QWidget( parent ) +{ + m_dlg = new Ui_DlgPerformanceBase(); + m_dlg->setupUi( this ); + + QFont labelFont = m_dlg->descLabel->font(); + labelFont.setBold( true ); + m_dlg->descLabel->setFont( labelFont ); + + m_dlg->cpuLabel->setPixmap( BarIcon( "cpu", 32 ) ); +// m_dlg->memoryLabel->setPixmap( BarIcon( "kcmmemory", 32 ) ); // TODO: enable again when proper icon is available + + connect( m_dlg->kcfg_MemoryLevel, SIGNAL(changed(int)), this, SLOT(radioGroup_changed(int)) ); +} + +DlgPerformance::~DlgPerformance() +{ + delete m_dlg; +} + +void DlgPerformance::radioGroup_changed( int which ) +{ + switch ( which ) + { + case 0: + m_dlg->descLabel->setText( i18n("Keeps used memory as low as possible. Do not reuse anything. (For systems with low memory.)") ); + break; + case 1: + m_dlg->descLabel->setText( i18n("A good compromise between memory usage and speed gain. Preload next page and boost searches. (For systems with 256MB of memory, typically.)") ); + break; + case 2: + m_dlg->descLabel->setText( i18n("Keeps everything in memory. Preload next pages. Boost searches. (For systems with more than 512MB of memory.)") ); + break; + case 3: + // xgettext: no-c-format + m_dlg->descLabel->setText( i18n("Loads and keeps everything in memory. Preload all pages. (Will use at maximum 50% of your total memory or your free memory, whatever is bigger.)")); + break; + } +} + +#include "dlgperformance.moc" diff --git a/okular/conf/dlgperformance.h b/okular/conf/dlgperformance.h new file mode 100644 index 00000000..639e3efe --- /dev/null +++ b/okular/conf/dlgperformance.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _DLGPERFORMANCE_H +#define _DLGPERFORMANCE_H + +#include + +class Ui_DlgPerformanceBase; + +class DlgPerformance : public QWidget +{ + Q_OBJECT + + public: + DlgPerformance( QWidget * parent = 0 ); + virtual ~DlgPerformance(); + + protected slots: + void radioGroup_changed( int which ); + + protected: + Ui_DlgPerformanceBase * m_dlg; +}; + +#endif diff --git a/okular/conf/dlgperformancebase.ui b/okular/conf/dlgperformancebase.ui new file mode 100644 index 00000000..3909654e --- /dev/null +++ b/okular/conf/dlgperformancebase.ui @@ -0,0 +1,274 @@ + + + DlgPerformanceBase + + + + 0 + 0 + 316 + 344 + + + + + 6 + + + 0 + + + + + CPU Usage + + + + 6 + + + 9 + + + + + 6 + + + 0 + + + + + 6 + + + 0 + + + + + Enable &transparency effects + + + + + + + + + 6 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 21 + 1 + + + + + + + + + + + + + + + Memory Usage + + + + 6 + + + 9 + + + + + 6 + + + 0 + + + + + 6 + + + 0 + + + + + &Low + + + + + + + &Normal (default) + + + + + + + &Aggressive + + + + + + + &Greedy + + + + + + + + + 6 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 21 + 1 + + + + + + + + + + + + + + + Qt::PlainText + + + Qt::AlignVCenter + + + true + + + + + + + + + + Rendering + + + + + + Enable Text Antialias + + + + + + + Enable Graphics Antialias + + + + + + + Enable Text Hinting + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 4 + + + + + + + + + KButtonGroup + QGroupBox +
kbuttongroup.h
+ 1 +
+
+ + kiconloader.h + + + +
diff --git a/okular/conf/dlgpresentation.cpp b/okular/conf/dlgpresentation.cpp new file mode 100644 index 00000000..6c68f1e2 --- /dev/null +++ b/okular/conf/dlgpresentation.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * Copyright (C) 2006,2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "dlgpresentation.h" + +#include "ui_dlgpresentationbase.h" + +#include +#include + +#include + +#include "settings.h" + +DlgPresentation::DlgPresentation( QWidget * parent ) + : QWidget( parent ) +{ + m_dlg = new Ui_DlgPresentationBase(); + m_dlg->setupUi( this ); + + QStringList choices; + choices.append( i18nc( "@label:listbox The current screen, for the presentation mode", "Current Screen" ) ); + choices.append( i18nc( "@label:listbox The default screen for the presentation mode", "Default Screen" ) ); + const int screenCount = QApplication::desktop()->numScreens(); + for ( int i = 0; i < screenCount; ++i ) + { + choices.append( i18nc( "@label:listbox %1 is the screen number (0, 1, ...)", "Screen %1", i ) ); + } + m_dlg->screenCombo->addItems( choices ); + + const int screen = Okular::Settings::slidesScreen(); + if ( screen >= -2 && screen < screenCount ) + { + m_dlg->screenCombo->setCurrentIndex( screen + 2 ); + } + else + { + m_dlg->screenCombo->setCurrentIndex( 0 ); + Okular::Settings::setSlidesScreen( -2 ); + } + + m_dlg->kcfg_SlidesAdvanceTime->setSuffix(ki18ncp("Advance every %1 seconds", " second", " seconds")); + + connect( m_dlg->screenCombo, SIGNAL(activated(int)), this, SLOT(screenComboChanged(int)) ); +} + +DlgPresentation::~DlgPresentation() +{ + delete m_dlg; +} + +void DlgPresentation::screenComboChanged( int which ) +{ + Okular::Settings::setSlidesScreen( which - 2 ); +} + +#include "dlgpresentation.moc" diff --git a/okular/conf/dlgpresentation.h b/okular/conf/dlgpresentation.h new file mode 100644 index 00000000..3d6764b7 --- /dev/null +++ b/okular/conf/dlgpresentation.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright (C) 2006,2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _DLGPRESENTATION_H +#define _DLGPRESENTATION_H + +#include + +class Ui_DlgPresentationBase; + +class DlgPresentation : public QWidget +{ + Q_OBJECT + + public: + DlgPresentation( QWidget * parent = 0 ); + virtual ~DlgPresentation(); + + protected slots: + void screenComboChanged( int which ); + + protected: + Ui_DlgPresentationBase * m_dlg; +}; + +#endif diff --git a/okular/conf/dlgpresentationbase.ui b/okular/conf/dlgpresentationbase.ui new file mode 100644 index 00000000..31db6938 --- /dev/null +++ b/okular/conf/dlgpresentationbase.ui @@ -0,0 +1,340 @@ + + DlgPresentationBase + + + + 0 + 0 + 400 + 446 + + + + + 0 + + + + + Navigation + + + + 6 + + + 9 + + + + + 6 + + + 0 + + + + + Advance every: + + + + + + + false + + + sec. + + + 5 + + + + + + + + + Loop after last page + + + + + + + + + + Appearance + + + + + + + + Background color: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Pencil color: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Mouse cursor: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Hidden After Delay + + + + + Always Visible + + + + + Always Hidden + + + + + + + + + + Show &progress indicator + + + + + + + Show s&ummary page + + + + + + + + + + Enable transitions + + + true + + + true + + + + + + Default transition: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Blinds Vertical + + + + + Blinds Horizontal + + + + + Box In + + + + + Box Out + + + + + Dissolve + + + + + Glitter Down + + + + + Glitter Right + + + + + Glitter Right-Down + + + + + Random Transition + + + + + Replace + + + + + Split Horizontal In + + + + + Split Horizontal Out + + + + + Split Vertical In + + + + + Split Vertical Out + + + + + Wipe Down + + + + + Wipe Right + + + + + Wipe Left + + + + + Wipe Up + + + + + + + + + + + Placement + + + + + + Screen: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 4 + + + + + + + + + KColorButton + QPushButton +
kcolorbutton.h
+
+ + KIntSpinBox + KIntSpinBox +
knuminput.h
+
+
+ + + + kcfg_SlidesAdvance + toggled(bool) + kcfg_SlidesAdvanceTime + setEnabled(bool) + + + 88 + 43 + + + 280 + 49 + + + + +
diff --git a/okular/conf/okular.kcfg b/okular/conf/okular.kcfg new file mode 100644 index 00000000..f9809896 --- /dev/null +++ b/okular/conf/okular.kcfg @@ -0,0 +1,283 @@ + + + kuser.h + + + + true + + + + + false + + + false + + + + + false + + + false + + + + + false + + + false + + + + QStringList annotationTools; + // load the default tool list from the 'xml tools definition' file + QFile infoFile( KStandardDirs::locate("data", "okular/tools.xml") ); + if ( infoFile.exists() && infoFile.open( QIODevice::ReadOnly ) ) + { + QDomDocument doc; + if ( doc.setContent( &infoFile ) ) + { + QDomElement toolsDefinition = doc.elementsByTagName("annotatingTools").item( 0 ).toElement(); + // create the annotationTools list from the XML dom tree + QDomNode toolDescription = toolsDefinition.firstChild(); + while ( toolDescription.isElement() ) + { + QDomElement toolElement = toolDescription.toElement(); + if ( toolElement.tagName() == "tool" ) + { + QDomDocument temp; + temp.appendChild( temp.importNode( toolElement, true) ); + // add each <tool>...</tool> as XML string + annotationTools << temp.toString(-1); + } + toolDescription = toolDescription.nextSibling(); + } + } + else + { + kWarning() << "AnnotatingTools XML file seems to be damaged"; + } + } + else + { + kWarning() << "Unable to open AnnotatingTools XML definition"; + } + + annotationTools + + + + + 1 + 3 + + + + + false + + + true + + + true + + + Name + + + + + + + + true + + + + + Qt::black + + + Replace + + + + + + + + + + + + + + + + + + + + + + + HiddenDelay + + + + + + + + true + + + false + + + Qt::red + + + true + + + -2 + -2 + 20 + + + + + true + + + + true + + + + + false + + + true + + + true + + + false + + + true + + + true + + + true + + + 48 + + + + + 0 + + + true + + + 0 + 0 + 50 + + + 3 + 1 + 8 + + + false + + + true + + + Single + + + + + + + + + Browse + + + + + + + + + + + false + + + + + false + + + true + + + + + false + + + false + + + 0x600000 + + + 0xF0F0F0 + + + 127 + 2 + 253 + + + 2 + 2 + 6 + + + + + + KUser currentUser; + QString userString = currentUser.property( KUser::FullName ).toString(); + if ( userString.isEmpty() ) + { + userString = currentUser.loginName(); + } + + userString + + + diff --git a/okular/conf/okular_core.kcfg b/okular/conf/okular_core.kcfg new file mode 100644 index 00000000..054b5c11 --- /dev/null +++ b/okular/conf/okular_core.kcfg @@ -0,0 +1,95 @@ + + + kuser.h + + + + Normal + + + + + + + + + true + + + Enabled + + + + + + + Enabled + + + + + + + Disabled + + + + + + + + + Qt::white + + + false + + + Inverted + + + + + + + + + + + true + + + false + + + Kate + + + + + + + + + + + kate --use --line %l --column %c + + + + + false + + + 5 + 1 + 3600 + + + false + + + diff --git a/okular/conf/preferencesdialog.cpp b/okular/conf/preferencesdialog.cpp new file mode 100644 index 00000000..f0c3f2c7 --- /dev/null +++ b/okular/conf/preferencesdialog.cpp @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +// reimplementing this +#include "preferencesdialog.h" + +#include + +// single config pages +#include "dlggeneral.h" +#include "dlgperformance.h" +#include "dlgaccessibility.h" +#include "dlgpresentation.h" +#include "dlgannotations.h" +#include "dlgeditor.h" +#include "dlgdebug.h" + +PreferencesDialog::PreferencesDialog( QWidget * parent, KConfigSkeleton * skeleton, Okular::EmbedMode embedMode ) + : KConfigDialog( parent, "preferences", skeleton ) +{ + setWindowModality( Qt::ApplicationModal ); + + m_general = new DlgGeneral( this, embedMode ); + m_performance = new DlgPerformance( this ); + m_accessibility = new DlgAccessibility( this ); + m_presentation = 0; + m_annotations = 0; + m_annotationsPage = 0; + m_editor = 0; +#ifdef OKULAR_DEBUG_CONFIGPAGE + m_debug = new DlgDebug( this ); +#endif + + addPage( m_general, i18n("General"), "okular", i18n("General Options") ); + addPage( m_accessibility, i18n("Accessibility"), "preferences-desktop-accessibility", i18n("Accessibility Reading Aids") ); + addPage( m_performance, i18n("Performance"), "preferences-system-performance", i18n("Performance Tuning") ); + if( embedMode == Okular::ViewerWidgetMode ) + { + setCaption( i18n("Configure Viewer") ); + } + else + { + m_presentation = new DlgPresentation( this ); + m_annotations = new DlgAnnotations( this ); + m_editor = new DlgEditor( this ); + addPage( m_presentation, i18n("Presentation"), "view-presentation", + i18n("Options for Presentation Mode") ); + m_annotationsPage = addPage( m_annotations, i18n("Annotations"), + "draw-freehand", i18n("Annotation Options") ); + addPage( m_editor, i18n("Editor"), "accessories-text-editor", i18n("Editor Options") ); + } +#ifdef OKULAR_DEBUG_CONFIGPAGE + addPage( m_debug, "Debug", "system-run", "Debug options" ); +#endif + setHelp("configure", "okular"); +} + +void PreferencesDialog::switchToAnnotationsPage() +{ + if ( m_annotationsPage ) + setCurrentPage( m_annotationsPage ); +} diff --git a/okular/conf/preferencesdialog.h b/okular/conf/preferencesdialog.h new file mode 100644 index 00000000..dcea37e4 --- /dev/null +++ b/okular/conf/preferencesdialog.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _PREFERENCESDIALOG_H +#define _PREFERENCESDIALOG_H + +#include +#include "part.h" +#include "settings.h" + +class QWidget; +class KConfigSkeleton; + +class DlgGeneral; +class DlgPerformance; +class DlgAccessibility; +class DlgPresentation; +class DlgAnnotations; +class DlgEditor; +class DlgDebug; + +class PreferencesDialog : public KConfigDialog +{ + + public: + PreferencesDialog( QWidget * parent, KConfigSkeleton * config, Okular::EmbedMode embedMode ); + + void switchToAnnotationsPage(); + + protected: +// void updateSettings(); // Called when OK/Apply is pressed. +// void updateWidgets(); // Called upon construction or when Reset is pressed +// void updateWidgetsDefault(); // Called when Defaults button is pressed +// bool hasChanged(); // In order to correctly disable/enable Apply button +// bool isDefault(); // In order to correctly disable/enable Defaults button + + private: + DlgGeneral * m_general; + DlgPerformance * m_performance; + DlgAccessibility * m_accessibility; + DlgPresentation * m_presentation; + DlgAnnotations * m_annotations; + DlgEditor * m_editor; + DlgDebug * m_debug; + + KPageWidgetItem * m_annotationsPage; +}; + +#endif diff --git a/okular/conf/settings.kcfgc b/okular/conf/settings.kcfgc new file mode 100644 index 00000000..f6e17ad7 --- /dev/null +++ b/okular/conf/settings.kcfgc @@ -0,0 +1,10 @@ +ClassName=Settings +Namespace=Okular +File=okular.kcfg +Inherits=SettingsCore +Mutators=true +Singleton=true +Visibility=OKULAR_PART_EXPORT +IncludeFiles=settings_core.h,okular_part_export.h +SourceIncludeFiles=kstandarddirs.h,qdom.h +MemberVariables=dpointer diff --git a/okular/conf/settings_core.kcfgc b/okular/conf/settings_core.kcfgc new file mode 100644 index 00000000..4bfd6d90 --- /dev/null +++ b/okular/conf/settings_core.kcfgc @@ -0,0 +1,8 @@ +ClassName=SettingsCore +Namespace=Okular +File=okular_core.kcfg +Mutators=true +Singleton=true +Visibility=OKULAR_EXPORT +IncludeFiles=core/okular_export.h +MemberVariables=dpointer diff --git a/okular/conf/textdocumentsettings.ui b/okular/conf/textdocumentsettings.ui new file mode 100644 index 00000000..f72d014e --- /dev/null +++ b/okular/conf/textdocumentsettings.ui @@ -0,0 +1,13 @@ + + + TextDocumentSettings + + + + QFormLayout::ExpandingFieldsGrow + + + + + + diff --git a/okular/conf/widgetannottools.cpp b/okular/conf/widgetannottools.cpp new file mode 100644 index 00000000..6d05eed8 --- /dev/null +++ b/okular/conf/widgetannottools.cpp @@ -0,0 +1,704 @@ +/*************************************************************************** + * Copyright (C) 2012 by Fabio D'Urso * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "widgetannottools.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/annotations.h" +#include "ui/annotationwidgets.h" +#include "ui/pageviewannotator.h" + +// Used to store tools' XML description in m_list's items +static const int ToolXmlRole = Qt::UserRole; + +WidgetAnnotTools::WidgetAnnotTools( QWidget * parent ) + : QWidget( parent ) +{ + QHBoxLayout *hBoxLayout = new QHBoxLayout( this ); + m_list = new QListWidget( this ); + m_list->setIconSize( QSize( 64, 64 ) ); + hBoxLayout->addWidget( m_list ); + + QVBoxLayout *vBoxLayout = new QVBoxLayout(); + m_btnAdd = new KPushButton( i18n("&Add..."), this ); + m_btnAdd->setIcon( KIcon("list-add") ); + vBoxLayout->addWidget( m_btnAdd ); + m_btnEdit = new KPushButton( i18n("&Edit..."), this ); + m_btnEdit->setIcon( KIcon("edit-rename") ); + m_btnEdit->setEnabled( false ); + vBoxLayout->addWidget( m_btnEdit ); + m_btnRemove = new KPushButton( i18n("&Remove"), this ); + m_btnRemove->setIcon( KIcon("list-remove") ); + m_btnRemove->setEnabled( false ); + vBoxLayout->addWidget( m_btnRemove ); + m_btnMoveUp = new KPushButton( i18n("Move &Up"), this ); + m_btnMoveUp->setIcon( KIcon("arrow-up") ); + m_btnMoveUp->setEnabled( false ); + vBoxLayout->addWidget( m_btnMoveUp ); + m_btnMoveDown = new KPushButton( i18n("Move &Down"), this ); + m_btnMoveDown->setIcon( KIcon("arrow-down") ); + m_btnMoveDown->setEnabled( false ); + vBoxLayout->addWidget( m_btnMoveDown ); + vBoxLayout->addStretch(); + hBoxLayout->addLayout( vBoxLayout ); + + connect( m_list, SIGNAL( itemDoubleClicked(QListWidgetItem*) ), this, SLOT( slotEdit() ) ); + connect( m_list, SIGNAL( currentRowChanged(int) ), this, SLOT( updateButtons() ) ); + connect( m_btnAdd, SIGNAL( clicked(bool) ), this, SLOT( slotAdd() ) ); + connect( m_btnEdit, SIGNAL( clicked(bool) ), this, SLOT( slotEdit() ) ); + connect( m_btnRemove, SIGNAL( clicked(bool) ), this, SLOT( slotRemove() ) ); + connect( m_btnMoveUp, SIGNAL( clicked(bool) ), this, SLOT( slotMoveUp() ) ); + connect( m_btnMoveDown, SIGNAL( clicked(bool) ), this, SLOT( slotMoveDown() ) ); +} + +WidgetAnnotTools::~WidgetAnnotTools() +{ +} + +/* Before returning the XML strings, this functions updates the id and + * shortcut properties. + * Note: The shortcut is only assigned to the first nine tools */ +QStringList WidgetAnnotTools::tools() const +{ + QStringList res; + + const int count = m_list->count(); + for ( int i = 0; i < count; ++i ) + { + QListWidgetItem * listEntry = m_list->item(i); + + // Parse associated DOM data + QDomDocument doc; + doc.setContent( listEntry->data( ToolXmlRole ).value() ); + + // Set id + QDomElement toolElement = doc.documentElement(); + toolElement.setAttribute( "id", i+1 ); + + // Remove old shortcut, if any + QDomNode oldShortcut = toolElement.elementsByTagName( "shortcut" ).item( 0 ); + if ( oldShortcut.isElement() ) + toolElement.removeChild( oldShortcut ); + + // Create new shortcut element (only the first 9 tools are assigned a shortcut key) + if ( i < 9 ) + { + QDomElement newShortcut = doc.createElement( "shortcut" ); + newShortcut.appendChild( doc.createTextNode(QString::number( i+1 )) ); + toolElement.appendChild( newShortcut ); + } + + // Append to output + res << doc.toString(-1); + } + + return res; +} + +void WidgetAnnotTools::setTools(const QStringList& items) +{ + m_list->clear(); + + // Parse each string and populate the list widget + foreach ( const QString &toolXml, items ) + { + QDomDocument entryParser; + if ( !entryParser.setContent( toolXml ) ) + { + kWarning() << "Skipping malformed tool XML string"; + break; + } + + QDomElement toolElement = entryParser.documentElement(); + if ( toolElement.tagName() == "tool" ) + { + // Create list item and attach the source XML string as data + QString itemText = toolElement.attribute( "name" ); + if ( itemText.isEmpty() ) + itemText = PageViewAnnotator::defaultToolName( toolElement ); + QListWidgetItem * listEntry = new QListWidgetItem( itemText, m_list ); + listEntry->setData( ToolXmlRole, qVariantFromValue(toolXml) ); + listEntry->setIcon( PageViewAnnotator::makeToolPixmap( toolElement ) ); + } + } + + updateButtons(); +} + +void WidgetAnnotTools::updateButtons() +{ + const int row = m_list->currentRow(); + const int last = m_list->count() - 1; + + m_btnEdit->setEnabled( row != -1 ); + m_btnRemove->setEnabled( row != -1 ); + m_btnMoveUp->setEnabled( row > 0 ); + m_btnMoveDown->setEnabled( row != -1 && row != last ); +} + +void WidgetAnnotTools::slotEdit() +{ + QListWidgetItem *listEntry = m_list->currentItem(); + + QDomDocument doc; + doc.setContent( listEntry->data( ToolXmlRole ).value() ); + QDomElement toolElement = doc.documentElement(); + + EditAnnotToolDialog t( this, toolElement ); + + if ( t.exec() != QDialog::Accepted ) + return; + + doc = t.toolXml(); + toolElement = doc.documentElement(); + + QString itemText = t.name(); + + // Store name attribute only if the user specified a customized name + if ( !itemText.isEmpty() ) + toolElement.setAttribute( "name", itemText ); + else + itemText = PageViewAnnotator::defaultToolName( toolElement ); + + // Edit list entry and attach XML string as data + listEntry->setText( itemText ); + listEntry->setData( ToolXmlRole, qVariantFromValue( doc.toString(-1) ) ); + listEntry->setIcon( PageViewAnnotator::makeToolPixmap( toolElement ) ); + + // Select and scroll + m_list->setCurrentItem( listEntry ); + m_list->scrollToItem( listEntry ); + updateButtons(); + emit changed(); +} + +void WidgetAnnotTools::slotAdd() +{ + EditAnnotToolDialog t( this ); + + if ( t.exec() != QDialog::Accepted ) + return; + + QDomDocument rootDoc = t.toolXml(); + QDomElement toolElement = rootDoc.documentElement(); + + QString itemText = t.name(); + + // Store name attribute only if the user specified a customized name + if ( !itemText.isEmpty() ) + toolElement.setAttribute( "name", itemText ); + else + itemText = PageViewAnnotator::defaultToolName( toolElement ); + + // Create list entry and attach XML string as data + QListWidgetItem * listEntry = new QListWidgetItem( itemText, m_list ); + listEntry->setData( ToolXmlRole, qVariantFromValue( rootDoc.toString(-1) ) ); + listEntry->setIcon( PageViewAnnotator::makeToolPixmap( toolElement ) ); + + // Select and scroll + m_list->setCurrentItem( listEntry ); + m_list->scrollToItem( listEntry ); + updateButtons(); + emit changed(); +} + +void WidgetAnnotTools::slotRemove() +{ + const int row = m_list->currentRow(); + delete m_list->takeItem(row); + updateButtons(); + emit changed(); +} + +void WidgetAnnotTools::slotMoveUp() +{ + const int row = m_list->currentRow(); + m_list->insertItem( row, m_list->takeItem(row-1) ); + m_list->scrollToItem( m_list->currentItem() ); + updateButtons(); + emit changed(); +} + +void WidgetAnnotTools::slotMoveDown() +{ + const int row = m_list->currentRow(); + m_list->insertItem( row, m_list->takeItem(row+1) ); + m_list->scrollToItem( m_list->currentItem() ); + updateButtons(); + emit changed(); +} + +EditAnnotToolDialog::EditAnnotToolDialog( QWidget *parent, const QDomElement &initialState ) + : KDialog( parent ), m_stubann( 0 ), m_annotationWidget( 0 ) +{ + setButtons( Ok | Cancel ); + setDefaultButton( Ok ); + + QLabel * tmplabel; + QWidget *widget = new QWidget( this ); + QGridLayout * widgetLayout = new QGridLayout( widget ); + + setMainWidget(widget); + + m_name = new KLineEdit( widget ); + tmplabel = new QLabel( i18n( "&Name:" ), widget ); + tmplabel->setBuddy( m_name ); + widgetLayout->addWidget( tmplabel, 0, 0, Qt::AlignRight ); + widgetLayout->addWidget( m_name, 0, 1 ); + + m_type = new KComboBox( false, widget ); + connect( m_type, SIGNAL( currentIndexChanged(int) ), this, SLOT( slotTypeChanged() ) ); + tmplabel = new QLabel( i18n( "&Type:" ), widget ); + tmplabel->setBuddy( m_type ); + widgetLayout->addWidget( tmplabel, 1, 0, Qt::AlignRight ); + widgetLayout->addWidget( m_type, 1, 1 ); + + m_toolIcon = new QLabel( widget ); + m_toolIcon->setAlignment( Qt::AlignRight | Qt::AlignTop ); + m_toolIcon->setMinimumSize( 40, 32 ); + widgetLayout->addWidget( m_toolIcon, 0, 2, 2, 1 ); + + m_appearanceBox = new QGroupBox( i18n( "Appearance" ), widget ); + m_appearanceBox->setLayout( new QVBoxLayout( m_appearanceBox ) ); + widgetLayout->addWidget( m_appearanceBox, 2, 0, 1, 3 ); + + // Populate combobox with annotation types + m_type->addItem( i18n("Pop-up Note"), qVariantFromValue( ToolNoteLinked ) ); + m_type->addItem( i18n("Inline Note"), qVariantFromValue( ToolNoteInline ) ); + m_type->addItem( i18n("Freehand Line"), qVariantFromValue( ToolInk ) ); + m_type->addItem( i18n("Straight Line"), qVariantFromValue( ToolStraightLine ) ); + m_type->addItem( i18n("Polygon"), qVariantFromValue( ToolPolygon ) ); + m_type->addItem( i18n("Text markup"), qVariantFromValue( ToolTextMarkup ) ); + m_type->addItem( i18n("Geometrical shape"), qVariantFromValue( ToolGeometricalShape ) ); + m_type->addItem( i18n("Stamp"), qVariantFromValue( ToolStamp ) ); + + createStubAnnotation(); + + if ( initialState.isNull() ) + { + setCaption( i18n("Create annotation tool") ); + } + else + { + setCaption( i18n("Edit annotation tool") ); + loadTool( initialState ); + } + + rebuildAppearanceBox(); + updateDefaultNameAndIcon(); +} + +EditAnnotToolDialog::~EditAnnotToolDialog() +{ + delete m_annotationWidget; +} + +QString EditAnnotToolDialog::name() const +{ + return m_name->text(); +} + +QDomDocument EditAnnotToolDialog::toolXml() const +{ + const ToolType toolType = m_type->itemData( m_type->currentIndex() ).value(); + + QDomDocument doc; + QDomElement toolElement = doc.createElement( "tool" ); + QDomElement engineElement = doc.createElement( "engine" ); + QDomElement annotationElement = doc.createElement( "annotation" ); + doc.appendChild( toolElement ); + toolElement.appendChild( engineElement ); + engineElement.appendChild( annotationElement ); + + const QString color = m_stubann->style().color().name(); + const double opacity = m_stubann->style().opacity(); + const double width = m_stubann->style().width(); + + if ( toolType == ToolNoteLinked ) + { + Okular::TextAnnotation * ta = static_cast( m_stubann ); + toolElement.setAttribute( "type", "note-linked" ); + engineElement.setAttribute( "type", "PickPoint" ); + engineElement.setAttribute( "color", color ); + engineElement.setAttribute( "hoverIcon", "tool-note" ); + annotationElement.setAttribute( "type", "Text" ); + annotationElement.setAttribute( "color", color ); + annotationElement.setAttribute( "icon", ta->textIcon() ); + } + else if ( toolType == ToolNoteInline ) + { + Okular::TextAnnotation * ta = static_cast( m_stubann ); + toolElement.setAttribute( "type", "note-inline" ); + engineElement.setAttribute( "type", "PickPoint" ); + engineElement.setAttribute( "color", color ); + engineElement.setAttribute( "hoverIcon", "tool-note-inline" ); + engineElement.setAttribute( "block", "true" ); + annotationElement.setAttribute( "type", "FreeText" ); + annotationElement.setAttribute( "color", color ); + if ( ta->inplaceAlignment() != 0 ) + annotationElement.setAttribute( "align", ta->inplaceAlignment() ); + if ( ta->textFont() != QApplication::font() ) + annotationElement.setAttribute( "font", ta->textFont().toString() ); + } + else if ( toolType == ToolInk ) + { + toolElement.setAttribute( "type", "ink" ); + engineElement.setAttribute( "type", "SmoothLine" ); + engineElement.setAttribute( "color", color ); + annotationElement.setAttribute( "type", "Ink" ); + annotationElement.setAttribute( "color", color ); + annotationElement.setAttribute( "width", width ); + } + else if ( toolType == ToolStraightLine ) + { + Okular::LineAnnotation * la = static_cast( m_stubann ); + toolElement.setAttribute( "type", "straight-line" ); + engineElement.setAttribute( "type", "PolyLine" ); + engineElement.setAttribute( "color", color ); + engineElement.setAttribute( "points", "2" ); + annotationElement.setAttribute( "type", "Line" ); + annotationElement.setAttribute( "color", color ); + annotationElement.setAttribute( "width", width ); + if ( la->lineLeadingForwardPoint() != 0 || la->lineLeadingBackwardPoint() != 0 ) + { + annotationElement.setAttribute( "leadFwd", la->lineLeadingForwardPoint() ); + annotationElement.setAttribute( "leadBack", la->lineLeadingBackwardPoint() ); + } + } + else if ( toolType == ToolPolygon ) + { + Okular::LineAnnotation * la = static_cast( m_stubann ); + toolElement.setAttribute( "type", "polygon" ); + engineElement.setAttribute( "type", "PolyLine" ); + engineElement.setAttribute( "color", color ); + engineElement.setAttribute( "points", "-1" ); + annotationElement.setAttribute( "type", "Line" ); + annotationElement.setAttribute( "color", color ); + annotationElement.setAttribute( "width", width ); + if ( la->lineInnerColor().isValid() ) + { + annotationElement.setAttribute( "innerColor", la->lineInnerColor().name() ); + } + } + else if ( toolType == ToolTextMarkup ) + { + Okular::HighlightAnnotation * ha = static_cast( m_stubann ); + + switch ( ha->highlightType() ) + { + case Okular::HighlightAnnotation::Highlight: + toolElement.setAttribute( "type", "highlight" ); + annotationElement.setAttribute( "type", "Highlight" ); + break; + case Okular::HighlightAnnotation::Squiggly: + toolElement.setAttribute( "type", "squiggly" ); + annotationElement.setAttribute( "type", "Squiggly" ); + break; + case Okular::HighlightAnnotation::Underline: + toolElement.setAttribute( "type", "underline" ); + annotationElement.setAttribute( "type", "Underline" ); + break; + case Okular::HighlightAnnotation::StrikeOut: + toolElement.setAttribute( "type", "strikeout" ); + annotationElement.setAttribute( "type", "StrikeOut" ); + break; + } + + engineElement.setAttribute( "type", "TextSelector" ); + engineElement.setAttribute( "color", color ); + annotationElement.setAttribute( "color", color ); + } + else if ( toolType == ToolGeometricalShape ) + { + Okular::GeomAnnotation * ga = static_cast( m_stubann ); + + if ( ga->geometricalType() == Okular::GeomAnnotation::InscribedCircle ) + { + toolElement.setAttribute( "type", "ellipse" ); + annotationElement.setAttribute( "type", "GeomCircle" ); + } + else + { + toolElement.setAttribute( "type", "rectangle" ); + annotationElement.setAttribute( "type", "GeomSquare" ); + } + + engineElement.setAttribute( "type", "PickPoint" ); + engineElement.setAttribute( "color", color ); + engineElement.setAttribute( "block", "true" ); + annotationElement.setAttribute( "color", color ); + annotationElement.setAttribute( "width", width ); + + if ( ga->geometricalInnerColor().isValid() ) + annotationElement.setAttribute( "innerColor", ga->geometricalInnerColor().name() ); + } + else if ( toolType == ToolStamp ) + { + Okular::StampAnnotation * sa = static_cast( m_stubann ); + toolElement.setAttribute( "type", "stamp" ); + engineElement.setAttribute( "type", "PickPoint" ); + engineElement.setAttribute( "hoverIcon", sa->stampIconName() ); + engineElement.setAttribute( "size", "64" ); + engineElement.setAttribute( "block", "true" ); + annotationElement.setAttribute( "type", "Stamp" ); + annotationElement.setAttribute( "icon", sa->stampIconName() ); + } + + if ( opacity != 1 ) + annotationElement.setAttribute( "opacity", opacity ); + + return doc; +} + +void EditAnnotToolDialog::createStubAnnotation() +{ + const ToolType toolType = m_type->itemData( m_type->currentIndex() ).value(); + + // Delete previous stub annotation, if any + delete m_stubann; + + // Create stub annotation + if ( toolType == ToolNoteLinked ) + { + Okular::TextAnnotation * ta = new Okular::TextAnnotation(); + ta->setTextType( Okular::TextAnnotation::Linked ); + ta->setTextIcon( "Note" ); + ta->style().setColor( Qt::yellow ); + m_stubann = ta; + } + else if ( toolType == ToolNoteInline ) + { + Okular::TextAnnotation * ta = new Okular::TextAnnotation(); + ta->setTextType( Okular::TextAnnotation::InPlace ); + ta->style().setColor( Qt::yellow ); + m_stubann = ta; + } + else if ( toolType == ToolInk ) + { + m_stubann = new Okular::InkAnnotation(); + m_stubann->style().setWidth( 2.0 ); + m_stubann->style().setColor( Qt::green ); + } + else if ( toolType == ToolStraightLine ) + { + Okular::LineAnnotation * la = new Okular::LineAnnotation(); + la->setLinePoints( QLinkedList() << + Okular::NormalizedPoint(0,0) << + Okular::NormalizedPoint(1,0) ); + la->style().setColor( QColor( 0xff, 0xe0, 0x00 ) ); + m_stubann = la; + } + else if ( toolType == ToolPolygon ) + { + Okular::LineAnnotation * la = new Okular::LineAnnotation(); + la->setLinePoints( QLinkedList() << + Okular::NormalizedPoint(0,0) << + Okular::NormalizedPoint(1,0) << + Okular::NormalizedPoint(1,1) ); + la->setLineClosed( true ); + la->style().setColor( QColor( 0x00, 0x7e, 0xee ) ); + m_stubann = la; + } + else if ( toolType == ToolTextMarkup ) + { + m_stubann = new Okular::HighlightAnnotation(); + m_stubann->style().setColor( Qt::yellow ); + } + else if ( toolType == ToolGeometricalShape ) + { + Okular::GeomAnnotation * ga = new Okular::GeomAnnotation(); + ga->setGeometricalType( Okular::GeomAnnotation::InscribedCircle ); + ga->style().setWidth( 5.0 ); + ga->style().setColor( Qt::cyan ); + m_stubann = ga; + } + else if ( toolType == ToolStamp ) + { + Okular::StampAnnotation * sa = new Okular::StampAnnotation(); + sa->setStampIconName( "okular" ); + m_stubann = sa; + } +} + +void EditAnnotToolDialog::rebuildAppearanceBox() +{ + // Remove previous widget (if any) + if ( m_annotationWidget ) + { + delete m_annotationWidget->appearanceWidget(); + delete m_annotationWidget; + } + + m_annotationWidget = AnnotationWidgetFactory::widgetFor( m_stubann ); + m_appearanceBox->layout()->addWidget( m_annotationWidget->appearanceWidget() ); + + connect( m_annotationWidget, SIGNAL(dataChanged()), this, SLOT(slotDataChanged()) ); +} + +void EditAnnotToolDialog::updateDefaultNameAndIcon() +{ + QDomDocument doc = toolXml(); + QDomElement toolElement = doc.documentElement(); + m_name->setPlaceholderText( PageViewAnnotator::defaultToolName( toolElement ) ); + m_toolIcon->setPixmap( PageViewAnnotator::makeToolPixmap( toolElement ) ); +} + +void EditAnnotToolDialog::setToolType( ToolType newType ) +{ + int idx = -1; + + for ( int i = 0; idx == -1 && i < m_type->count(); ++i ) + { + if ( m_type->itemData( i ).value() == newType ) + idx = i; + } + + // The following call also results in createStubAnnotation being called + m_type->setCurrentIndex( idx ); +} + +void EditAnnotToolDialog::loadTool( const QDomElement &toolElement ) +{ + const QDomElement engineElement = toolElement.elementsByTagName( "engine" ).item( 0 ).toElement(); + const QDomElement annotationElement = engineElement.elementsByTagName( "annotation" ).item( 0 ).toElement(); + const QString annotType = toolElement.attribute( "type" ); + + if ( annotType == "ellipse" ) + { + setToolType( ToolGeometricalShape ); + Okular::GeomAnnotation * ga = static_cast( m_stubann ); + ga->setGeometricalType( Okular::GeomAnnotation::InscribedCircle ); + if ( annotationElement.hasAttribute( "innerColor" ) ) + ga->setGeometricalInnerColor( QColor( annotationElement.attribute( "innerColor" ) ) ); + } + else if ( annotType == "highlight" ) + { + setToolType( ToolTextMarkup ); + Okular::HighlightAnnotation * ha = static_cast( m_stubann ); + ha->setHighlightType( Okular::HighlightAnnotation::Highlight ); + } + else if ( annotType == "ink" ) + { + setToolType( ToolInk ); + } + else if ( annotType == "note-inline" ) + { + setToolType( ToolNoteInline ); + Okular::TextAnnotation * ta = static_cast( m_stubann ); + if ( annotationElement.hasAttribute( "align" ) ) + ta->setInplaceAlignment( annotationElement.attribute( "align" ).toInt() ); + if ( annotationElement.hasAttribute( "font" ) ) + { + QFont f; + f.fromString( annotationElement.attribute( "font" ) ); + ta->setTextFont( f ); + } + } + else if ( annotType == "note-linked" ) + { + setToolType( ToolNoteLinked ); + Okular::TextAnnotation * ta = static_cast( m_stubann ); + ta->setTextIcon( annotationElement.attribute( "icon" ) ); + } + else if ( annotType == "polygon" ) + { + setToolType( ToolPolygon ); + Okular::LineAnnotation * la = static_cast( m_stubann ); + if ( annotationElement.hasAttribute( "innerColor" ) ) + la->setLineInnerColor( QColor( annotationElement.attribute( "innerColor" ) ) ); + } + else if ( annotType == "rectangle" ) + { + setToolType( ToolGeometricalShape ); + Okular::GeomAnnotation * ga = static_cast( m_stubann ); + ga->setGeometricalType( Okular::GeomAnnotation::InscribedSquare ); + if ( annotationElement.hasAttribute( "innerColor" ) ) + ga->setGeometricalInnerColor( QColor( annotationElement.attribute( "innerColor" ) ) ); + } + else if ( annotType == "squiggly" ) + { + setToolType( ToolTextMarkup ); + Okular::HighlightAnnotation * ha = static_cast( m_stubann ); + ha->setHighlightType( Okular::HighlightAnnotation::Squiggly ); + } + else if ( annotType == "stamp" ) + { + setToolType( ToolStamp ); + Okular::StampAnnotation * sa = static_cast( m_stubann ); + sa->setStampIconName( annotationElement.attribute( "icon" ) ); + } + else if ( annotType == "straight-line" ) + { + setToolType( ToolStraightLine ); + Okular::LineAnnotation * la = static_cast( m_stubann ); + if ( annotationElement.hasAttribute( "leadFwd" ) ) + la->setLineLeadingForwardPoint( annotationElement.attribute( "leadFwd" ).toDouble() ); + if ( annotationElement.hasAttribute( "leadBack" ) ) + la->setLineLeadingBackwardPoint( annotationElement.attribute( "leadBack" ).toDouble() ); + } + else if ( annotType == "strikeout" ) + { + setToolType( ToolTextMarkup ); + Okular::HighlightAnnotation * ha = static_cast( m_stubann ); + ha->setHighlightType( Okular::HighlightAnnotation::StrikeOut ); + } + else if ( annotType == "underline" ) + { + setToolType( ToolTextMarkup ); + Okular::HighlightAnnotation * ha = static_cast( m_stubann ); + ha->setHighlightType( Okular::HighlightAnnotation::Underline ); + } + + // Common properties + if ( annotationElement.hasAttribute( "color" ) ) + m_stubann->style().setColor( QColor( annotationElement.attribute( "color" ) ) ); + if ( annotationElement.hasAttribute( "opacity" ) ) + m_stubann->style().setOpacity( annotationElement.attribute( "opacity" ).toDouble() ); + if ( annotationElement.hasAttribute( "width" ) ) + m_stubann->style().setWidth( annotationElement.attribute( "width" ).toDouble() ); + + if ( toolElement.hasAttribute( "name" ) ) + m_name->setText( toolElement.attribute( "name" ) ); +} + +void EditAnnotToolDialog::slotTypeChanged() +{ + createStubAnnotation(); + rebuildAppearanceBox(); + updateDefaultNameAndIcon(); +} + +void EditAnnotToolDialog::slotDataChanged() +{ + // Mirror changes back in the stub annotation + m_annotationWidget->applyChanges(); + + updateDefaultNameAndIcon(); +} + +#include "moc_widgetannottools.cpp" diff --git a/okular/conf/widgetannottools.h b/okular/conf/widgetannottools.h new file mode 100644 index 00000000..f15aa1c4 --- /dev/null +++ b/okular/conf/widgetannottools.h @@ -0,0 +1,108 @@ +/*************************************************************************** + * Copyright (C) 2012 by Fabio D'Urso * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _WIDGETANNOTTOOLS_H_ +#define _WIDGETANNOTTOOLS_H_ + +#include +#include +#include + +class KLineEdit; +class KComboBox; +class KPushButton; +class QLabel; +class QListWidget; +class QListWidgetItem; +class QGroupBox; +class AnnotationWidget; + +namespace Okular +{ +class Annotation; +} + +class WidgetAnnotTools : public QWidget +{ + Q_OBJECT + + Q_PROPERTY( QStringList tools READ tools WRITE setTools NOTIFY changed USER true ) + + public: + WidgetAnnotTools( QWidget * parent = 0 ); + ~WidgetAnnotTools(); + + QStringList tools() const; + void setTools(const QStringList& items); + + Q_SIGNALS: + void changed(); + + private: + QListWidget *m_list; + KPushButton *m_btnAdd; + KPushButton *m_btnEdit; + KPushButton *m_btnRemove; + KPushButton *m_btnMoveUp; + KPushButton *m_btnMoveDown; + + private slots: + void updateButtons(); + void slotAdd(); + void slotEdit(); + void slotRemove(); + void slotMoveUp(); + void slotMoveDown(); +}; + +class EditAnnotToolDialog : public KDialog +{ + Q_OBJECT + + public: + enum ToolType + { + ToolNoteLinked, + ToolNoteInline, + ToolInk, + ToolStraightLine, + ToolPolygon, + ToolTextMarkup, + ToolGeometricalShape, + ToolStamp + }; + + EditAnnotToolDialog( QWidget *parent = 0, const QDomElement &initialState = QDomElement() ); + ~EditAnnotToolDialog(); + QString name() const; + QDomDocument toolXml() const; + + private: + void createStubAnnotation(); + void rebuildAppearanceBox(); + void updateDefaultNameAndIcon(); + void setToolType( ToolType newType ); + void loadTool( const QDomElement &toolElement ); + + KLineEdit *m_name; + KComboBox *m_type; + QLabel *m_toolIcon; + QGroupBox *m_appearanceBox; + + Okular::Annotation *m_stubann; + AnnotationWidget *m_annotationWidget; + + private slots: + void slotTypeChanged(); + void slotDataChanged(); +}; + +Q_DECLARE_METATYPE( EditAnnotToolDialog::ToolType ) + +#endif diff --git a/okular/config-okular.h.cmake b/okular/config-okular.h.cmake new file mode 100644 index 00000000..4b4b2eec --- /dev/null +++ b/okular/config-okular.h.cmake @@ -0,0 +1,5 @@ +/* Defines if force the use DRM in okular */ +#define OKULAR_FORCE_DRM ${_OKULAR_FORCE_DRM} + +/* Defines if LibKScreen is present */ +#define HAVE_LIBKSCREEN ${LibKScreen_FOUND} diff --git a/okular/core/action.cpp b/okular/core/action.cpp new file mode 100644 index 00000000..7c7b09c3 --- /dev/null +++ b/okular/core/action.cpp @@ -0,0 +1,560 @@ +/*************************************************************************** + * Copyright (C) 2004-2005 by Enrico Ros * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "action.h" + +// kde includes +#include + +// local includes +#include "document.h" +#include "movie.h" +#include "sourcereference_p.h" +#include "sound.h" + +using namespace Okular; + +class Okular::ActionPrivate +{ + public: + ActionPrivate() + { + } + + virtual ~ActionPrivate() + { + } + + QVariant m_nativeId; +}; + +Action::Action( ActionPrivate &dd ) + : d_ptr( &dd ) +{ +} + +Action::~Action() +{ + delete d_ptr; +} + +QString Action::actionTip() const +{ + return ""; +} + +void Action::setNativeId( const QVariant &id ) +{ + Q_D( Action ); + d->m_nativeId = id; +} + +QVariant Action::nativeId() const +{ + Q_D( const Action ); + return d->m_nativeId; +} + +// GotoAction + +class Okular::GotoActionPrivate : public Okular::ActionPrivate +{ + public: + GotoActionPrivate( const QString &fileName, const DocumentViewport &viewport ) + : ActionPrivate(), m_extFileName( fileName ), m_vp( viewport ) + { + } + + GotoActionPrivate( const QString &fileName, const QString &namedDestination ) + : ActionPrivate(), m_extFileName( fileName ), m_dest( namedDestination ) + { + } + + QString m_extFileName; + DocumentViewport m_vp; + QString m_dest; +}; + +GotoAction::GotoAction( const QString& fileName, const DocumentViewport & viewport ) + : Action( *new GotoActionPrivate( fileName, viewport ) ) +{ +} + +GotoAction::GotoAction( const QString& fileName, const QString& namedDestination ) + : Action( *new GotoActionPrivate( fileName, namedDestination ) ) +{ +} + +GotoAction::~GotoAction() +{ +} + +Action::ActionType GotoAction::actionType() const +{ + return Goto; +} + +QString GotoAction::actionTip() const +{ + Q_D( const GotoAction ); + return d->m_extFileName.isEmpty() ? ( d->m_vp.isValid() ? i18n( "Go to page %1", d->m_vp.pageNumber + 1 ) : "" ) : + i18n("Open external file"); +} + +bool GotoAction::isExternal() const +{ + Q_D( const GotoAction ); + return !d->m_extFileName.isEmpty(); +} + +QString GotoAction::fileName() const +{ + Q_D( const GotoAction ); + return d->m_extFileName; +} + +DocumentViewport GotoAction::destViewport() const +{ + Q_D( const GotoAction ); + return d->m_vp; +} + +QString GotoAction::destinationName() const +{ + Q_D( const GotoAction ); + return d->m_dest; +} + +// ExecuteAction + +class Okular::ExecuteActionPrivate : public Okular::ActionPrivate +{ + public: + ExecuteActionPrivate( const QString &file, const QString & parameters ) + : ActionPrivate(), m_fileName( file ), m_parameters( parameters ) + { + } + + QString m_fileName; + QString m_parameters; +}; + +ExecuteAction::ExecuteAction( const QString &file, const QString & parameters ) + : Action( *new ExecuteActionPrivate( file, parameters ) ) +{ +} + +ExecuteAction::~ExecuteAction() +{ +} + +Action::ActionType ExecuteAction::actionType() const +{ + return Execute; +} + +QString ExecuteAction::actionTip() const +{ + Q_D( const Okular::ExecuteAction ); + return i18n( "Execute '%1'...", d->m_fileName ); +} + +QString ExecuteAction::fileName() const +{ + Q_D( const Okular::ExecuteAction ); + return d->m_fileName; +} + +QString ExecuteAction::parameters() const +{ + Q_D( const Okular::ExecuteAction ); + return d->m_parameters; +} + +// BrowseAction + +class Okular::BrowseActionPrivate : public Okular::ActionPrivate +{ + public: + BrowseActionPrivate( const QString &url ) + : ActionPrivate(), m_url( url ) + { + } + + QString m_url; +}; + +BrowseAction::BrowseAction( const QString &url ) + : Action( *new BrowseActionPrivate( url ) ) +{ +} + +BrowseAction::~BrowseAction() +{ +} + +Action::ActionType BrowseAction::actionType() const +{ + return Browse; +} + +QString BrowseAction::actionTip() const +{ + Q_D( const Okular::BrowseAction ); + QString source; + int row = 0, col = 0; + if ( extractLilyPondSourceReference( d->m_url, &source, &row, &col ) ) + { + return sourceReferenceToolTip( source, row, col ); + } + return d->m_url; +} + +QString BrowseAction::url() const +{ + Q_D( const Okular::BrowseAction ); + return d->m_url; +} + +// DocumentAction + +class Okular::DocumentActionPrivate : public Okular::ActionPrivate +{ + public: + DocumentActionPrivate( enum DocumentAction::DocumentActionType documentActionType ) + : ActionPrivate(), m_type( documentActionType ) + { + } + + DocumentAction::DocumentActionType m_type; +}; + +DocumentAction::DocumentAction( enum DocumentActionType documentActionType ) + : Action( *new DocumentActionPrivate( documentActionType ) ) +{ +} + +DocumentAction::~DocumentAction() +{ +} + +DocumentAction::DocumentActionType DocumentAction::documentActionType() const +{ + Q_D( const Okular::DocumentAction ); + return d->m_type; +} + +Action::ActionType DocumentAction::actionType() const +{ + return DocAction; +} + +QString DocumentAction::actionTip() const +{ + Q_D( const Okular::DocumentAction ); + switch ( d->m_type ) + { + case PageFirst: + return i18n( "First Page" ); + case PagePrev: + return i18n( "Previous Page" ); + case PageNext: + return i18n( "Next Page" ); + case PageLast: + return i18n( "Last Page" ); + case HistoryBack: + return i18n( "Back" ); + case HistoryForward: + return i18n( "Forward" ); + case Quit: + return i18n( "Quit" ); + case Presentation: + return i18n( "Start Presentation" ); + case EndPresentation: + return i18n( "End Presentation" ); + case Find: + return i18n( "Find..." ); + case GoToPage: + return i18n( "Go To Page..." ); + case Close: + default: ; + } + + return QString(); +} + +// SoundAction + +class Okular::SoundActionPrivate : public Okular::ActionPrivate +{ + public: + SoundActionPrivate( double volume, bool sync, bool repeat, bool mix, Okular::Sound *sound ) + : ActionPrivate(), m_volume( volume ), m_sync( sync ), + m_repeat( repeat ), m_mix( mix ), m_sound( sound ) + { + } + + ~SoundActionPrivate() + { + delete m_sound; + } + + double m_volume; + bool m_sync : 1; + bool m_repeat : 1; + bool m_mix : 1; + Okular::Sound *m_sound; +}; + +SoundAction::SoundAction( double volume, bool sync, bool repeat, bool mix, Okular::Sound *sound ) + : Action( *new SoundActionPrivate( volume, sync, repeat, mix, sound ) ) +{ +} + +SoundAction::~SoundAction() +{ +} + +Action::ActionType SoundAction::actionType() const +{ + return Sound; +} + +QString SoundAction::actionTip() const +{ + return i18n( "Play sound..." ); +} + +double SoundAction::volume() const +{ + Q_D( const Okular::SoundAction ); + return d->m_volume; +} + +bool SoundAction::synchronous() const +{ + Q_D( const Okular::SoundAction ); + return d->m_sync; +} + +bool SoundAction::repeat() const +{ + Q_D( const Okular::SoundAction ); + return d->m_repeat; +} + +bool SoundAction::mix() const +{ + Q_D( const Okular::SoundAction ); + return d->m_mix; +} + +Okular::Sound *SoundAction::sound() const +{ + Q_D( const Okular::SoundAction ); + return d->m_sound; +} + +// ScriptAction + +class Okular::ScriptActionPrivate : public Okular::ActionPrivate +{ + public: + ScriptActionPrivate( enum ScriptType type, const QString &script ) + : ActionPrivate(), m_scriptType( type ), m_script( script ) + { + } + + ScriptType m_scriptType; + QString m_script; +}; + +ScriptAction::ScriptAction( enum ScriptType type, const QString &script ) + : Action( *new ScriptActionPrivate( type, script ) ) +{ +} + +ScriptAction::~ScriptAction() +{ +} + +Action::ActionType ScriptAction::actionType() const +{ + return Script; +} + +QString ScriptAction::actionTip() const +{ + Q_D( const Okular::ScriptAction ); + switch ( d->m_scriptType ) + { + case JavaScript: + return i18n( "JavaScript Script" ); + } + + return QString(); +} + +ScriptType ScriptAction::scriptType() const +{ + Q_D( const Okular::ScriptAction ); + return d->m_scriptType; +} + +QString ScriptAction::script() const +{ + Q_D( const Okular::ScriptAction ); + return d->m_script; +} + +// MovieAction + +class Okular::MovieActionPrivate : public Okular::ActionPrivate +{ + public: + MovieActionPrivate( MovieAction::OperationType operation ) + : ActionPrivate(), m_operation( operation ), m_annotation( 0 ) + { + } + + MovieAction::OperationType m_operation; + MovieAnnotation *m_annotation; +}; + +MovieAction::MovieAction( OperationType operation ) + : Action( *new MovieActionPrivate( operation ) ) +{ +} + +MovieAction::~MovieAction() +{ +} + +Action::ActionType MovieAction::actionType() const +{ + return Movie; +} + +QString MovieAction::actionTip() const +{ + return i18n( "Play movie..." ); +} + +MovieAction::OperationType MovieAction::operation() const +{ + Q_D( const Okular::MovieAction ); + return d->m_operation; +} + +void MovieAction::setAnnotation( MovieAnnotation *annotation ) +{ + Q_D( Okular::MovieAction ); + d->m_annotation = annotation; +} + +MovieAnnotation* MovieAction::annotation() const +{ + Q_D( const Okular::MovieAction ); + return d->m_annotation; +} + +// RenditionAction + +class Okular::RenditionActionPrivate : public Okular::ActionPrivate +{ + public: + RenditionActionPrivate( RenditionAction::OperationType operation, Okular::Movie *movie, enum ScriptType scriptType, const QString &script ) + : ActionPrivate(), m_operation( operation ), m_movie( movie ), m_scriptType( scriptType ), + m_script( script ), m_annotation( 0 ) + { + } + + + RenditionAction::OperationType m_operation; + Okular::Movie *m_movie; + ScriptType m_scriptType; + QString m_script; + ScreenAnnotation *m_annotation; +}; + +RenditionAction::RenditionAction( OperationType operation, Okular::Movie *movie, enum ScriptType scriptType, const QString &script ) + : Action( *new RenditionActionPrivate( operation, movie, scriptType, script ) ) +{ +} + +RenditionAction::~RenditionAction() +{ +} + +Action::ActionType RenditionAction::actionType() const +{ + return Rendition; +} + +QString RenditionAction::actionTip() const +{ + Q_D( const Okular::RenditionAction ); + + switch ( d->m_operation ) + { + default: + case None: + switch ( d->m_scriptType ) + { + case JavaScript: + return i18n( "JavaScript Script" ); + default: + return QString(); + } + case Play: + return i18n( "Play movie" ); + case Stop: + return i18n( "Stop movie" ); + case Pause: + return i18n( "Pause movie" ); + case Resume: + return i18n( "Resume movie" ); + } +} + +RenditionAction::OperationType RenditionAction::operation() const +{ + Q_D( const Okular::RenditionAction ); + return d->m_operation; +} + +Okular::Movie* RenditionAction::movie() const +{ + Q_D( const Okular::RenditionAction ); + return d->m_movie; +} + +ScriptType RenditionAction::scriptType() const +{ + Q_D( const Okular::RenditionAction ); + return d->m_scriptType; +} + +QString RenditionAction::script() const +{ + Q_D( const Okular::RenditionAction ); + return d->m_script; +} + +void RenditionAction::setAnnotation( ScreenAnnotation *annotation ) +{ + Q_D( Okular::RenditionAction ); + d->m_annotation = annotation; +} + +ScreenAnnotation* RenditionAction::annotation() const +{ + Q_D( const Okular::RenditionAction ); + return d->m_annotation; +} diff --git a/okular/core/action.h b/okular/core/action.h new file mode 100644 index 00000000..8a9c3c26 --- /dev/null +++ b/okular/core/action.h @@ -0,0 +1,567 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_ACTION_H_ +#define _OKULAR_ACTION_H_ + +#include "global.h" +#include "okular_export.h" + +#include +#include + +namespace Okular { + +class ActionPrivate; +class GotoActionPrivate; +class ExecuteActionPrivate; +class BrowseActionPrivate; +class DocumentActionPrivate; +class SoundActionPrivate; +class ScriptActionPrivate; +class MovieActionPrivate; +class RenditionActionPrivate; +class MovieAnnotation; +class ScreenAnnotation; +class Movie; +class Sound; +class DocumentViewport; + +/** + * @short Encapsulates data that describes an action. + * + * This is the base class for actions. It makes mandatory for inherited + * widgets to reimplement the 'actionType' method and return the type of + * the action described by the reimplemented class. + */ +class OKULAR_EXPORT Action +{ + public: + /** + * Describes the type of action. + */ + enum ActionType { + Goto, ///< Goto a given page or external document + Execute, ///< Execute a command or external application + Browse, ///< Browse a given website + DocAction, ///< Start a custom action + Sound, ///< Play a sound + Movie, ///< Play a movie + Script, ///< Executes a Script code + Rendition ///< Play a movie and/or execute a Script code @since 0.16 (KDE 4.10) + }; + + /** + * Destroys the action. + */ + virtual ~Action(); + + /** + * Returns the type of the action. Every inherited class must return + * an unique identifier. + * + * @see ActionType + */ + virtual ActionType actionType() const = 0; + + /** + * Returns a i18n'ed tip of the action that is presented to + * the user. + */ + virtual QString actionTip() const; + + /** + * Sets the "native" @p id of the action. + * + * This is for use of the Generator, that can optionally store an + * handle (a pointer, an identifier, etc) of the "native" action + * object, if any. + * + * @note Okular makes no use of this + * + * @since 0.15 (KDE 4.9) + */ + void setNativeId( const QVariant &id ); + + /** + * Returns the "native" id of the action. + * + * @since 0.15 (KDE 4.9) + */ + QVariant nativeId() const; + + protected: + /// @cond PRIVATE + Action( ActionPrivate &dd ); + Q_DECLARE_PRIVATE( Action ) + ActionPrivate *d_ptr; + /// @endcond + + private: + Q_DISABLE_COPY( Action ) +}; + + +/** + * The Goto action changes the viewport to another page + * or loads an external document. + */ +class OKULAR_EXPORT GotoAction : public Action +{ + public: + /** + * Creates a new goto action. + * + * @p fileName The name of an external file that shall be loaded. + * @p viewport The target viewport information of the current document. + */ + GotoAction( const QString& fileName, const DocumentViewport & viewport ); + + /** + * Creates a new goto action. + * + * @p fileName The name of an external file that shall be loaded. + * @p namedDestination The target named destination for the target document. + * + * @since 0.9 (KDE 4.3) + */ + GotoAction( const QString& fileName, const QString& namedDestination ); + + /** + * Destroys the goto action. + */ + virtual ~GotoAction(); + + /** + * Returns the action type. + */ + ActionType actionType() const; + + /** + * Returns the action tip. + */ + QString actionTip() const; + + /** + * Returns whether the goto action points to an external document. + */ + bool isExternal() const; + + /** + * Returns the filename of the external document. + */ + QString fileName() const; + + /** + * Returns the document viewport the goto action points to. + */ + DocumentViewport destViewport() const; + + /** + * Returns the document named destination the goto action points to. + * + * @since 0.9 (KDE 4.3) + */ + QString destinationName() const; + + private: + Q_DECLARE_PRIVATE( GotoAction ) + Q_DISABLE_COPY( GotoAction ) +}; + +/** + * The Execute action executes an external application. + */ +class OKULAR_EXPORT ExecuteAction : public Action +{ + public: + /** + * Creates a new execute action. + * + * @param fileName The file name of the application to execute. + * @param parameters The parameters of the application to execute. + */ + ExecuteAction( const QString &fileName, const QString ¶meters ); + + /** + * Destroys the execute action. + */ + virtual ~ExecuteAction(); + + /** + * Returns the action type. + */ + ActionType actionType() const; + + /** + * Returns the action tip. + */ + QString actionTip() const; + + /** + * Returns the file name of the application to execute. + */ + QString fileName() const; + + /** + * Returns the parameters of the application to execute. + */ + QString parameters() const; + + private: + Q_DECLARE_PRIVATE( ExecuteAction ) + Q_DISABLE_COPY( ExecuteAction ) +}; + +/** + * The Browse action browses an url by opening a web browser or + * email client, depedning on the url protocol (e.g. http, mailto, etc.). + */ +class OKULAR_EXPORT BrowseAction : public Action +{ + public: + /** + * Creates a new browse action. + * + * @param url The url to browse. + */ + BrowseAction( const QString &url ); + + /** + * Destroys the browse action. + */ + virtual ~BrowseAction(); + + /** + * Returns the action type. + */ + ActionType actionType() const; + + /** + * Returns the action tip. + */ + QString actionTip() const; + + /** + * Returns the url to browse. + */ + QString url() const; + + private: + Q_DECLARE_PRIVATE( BrowseAction ) + Q_DISABLE_COPY( BrowseAction ) +}; + +/** + * The DocumentAction action contains an action that is performed on + * the current document. + */ +class OKULAR_EXPORT DocumentAction : public Action +{ + public: + /** + * Describes the possible action types. + * + * WARNING KEEP IN SYNC WITH POPPLER! + */ + enum DocumentActionType { + PageFirst = 1, ///< Jump to first page + PagePrev = 2, ///< Jump to previous page + PageNext = 3, ///< Jump to next page + PageLast = 4, ///< Jump to last page + HistoryBack = 5, ///< Go back in page history + HistoryForward = 6, ///< Go forward in page history + Quit = 7, ///< Quit application + Presentation = 8, ///< Start presentation + EndPresentation = 9, ///< End presentation + Find = 10, ///< Open find dialog + GoToPage = 11, ///< Goto page + Close = 12 ///< Close document + }; + + /** + * Creates a new document action. + * + * @param documentActionType The type of document action. + */ + explicit DocumentAction( enum DocumentActionType documentActionType ); + + /** + * Destroys the document action. + */ + virtual ~DocumentAction(); + + /** + * Returns the action type. + */ + ActionType actionType() const; + + /** + * Returns the action tip. + */ + QString actionTip() const; + + /** + * Returns the type of action. + */ + DocumentActionType documentActionType() const; + + private: + Q_DECLARE_PRIVATE( DocumentAction ) + Q_DISABLE_COPY( DocumentAction ) +}; + +/** + * The Sound action plays a sound on activation. + */ +class OKULAR_EXPORT SoundAction : public Action +{ + public: + /** + * Creates a new sound action. + * + * @param volume The volume of the sound. + * @param synchronous Whether the sound shall be played synchronous. + * @param repeat Whether the sound shall be repeated. + * @param mix Whether the sound shall be mixed. + * @param sound The sound object which contains the sound data. + */ + SoundAction( double volume, bool synchronous, bool repeat, bool mix, Okular::Sound *sound ); + + /** + * Destroys the sound action. + */ + virtual ~SoundAction(); + + /** + * Returns the action type. + */ + ActionType actionType() const; + + /** + * Returns the action tip. + */ + QString actionTip() const; + + /** + * Returns the volume of the sound. + */ + double volume() const; + + /** + * Returns whether the sound shall be played synchronous. + */ + bool synchronous() const; + + /** + * Returns whether the sound shall be repeated. + */ + bool repeat() const; + + /** + * Returns whether the sound shall be mixed. + */ + bool mix() const; + + /** + * Returns the sound object which contains the sound data. + */ + Okular::Sound *sound() const; + + private: + Q_DECLARE_PRIVATE( SoundAction ) + Q_DISABLE_COPY( SoundAction ) +}; + +/** + * The Script action executes a Script code. + * + * @since 0.7 (KDE 4.1) + */ +class OKULAR_EXPORT ScriptAction : public Action +{ + public: + /** + * Creates a new Script action. + * + * @param script The code to execute. + */ + ScriptAction( enum ScriptType type, const QString &script ); + + /** + * Destroys the browse action. + */ + virtual ~ScriptAction(); + + /** + * Returns the action type. + */ + ActionType actionType() const; + + /** + * Returns the action tip. + */ + QString actionTip() const; + + /** + * Returns the type of action. + */ + ScriptType scriptType() const; + + /** + * Returns the code. + */ + QString script() const; + + private: + Q_DECLARE_PRIVATE( ScriptAction ) + Q_DISABLE_COPY( ScriptAction ) +}; + +/** + * The Movie action executes an operation on a video on activation. + * + * @since 0.15 (KDE 4.9) + */ +class OKULAR_EXPORT MovieAction : public Action +{ + public: + /** + * Describes the possible operation types. + */ + enum OperationType { + Play, + Stop, + Pause, + Resume + }; + + /** + * Creates a new movie action. + */ + MovieAction( OperationType operation ); + + /** + * Destroys the movie action. + */ + virtual ~MovieAction(); + + /** + * Returns the action type. + */ + ActionType actionType() const; + + /** + * Returns the action tip. + */ + QString actionTip() const; + + /** + * Returns the operation type. + */ + OperationType operation() const; + + /** + * Sets the @p annotation that is associated with the movie action. + */ + void setAnnotation( MovieAnnotation *annotation ); + + /** + * Returns the annotation or @c 0 if no annotation has been set. + */ + MovieAnnotation* annotation() const; + + private: + Q_DECLARE_PRIVATE( MovieAction ) + Q_DISABLE_COPY( MovieAction ) +}; + +/** + * The Rendition action executes an operation on a video or + * executes some JavaScript code on activation. + * + * @since 0.16 (KDE 4.10) + */ +class OKULAR_EXPORT RenditionAction : public Action +{ + public: + /** + * Describes the possible operation types. + */ + enum OperationType { + None, ///< Execute only the JavaScript + Play, ///< Start playing the video + Stop, ///< Stop playing the video + Pause, ///< Pause the video + Resume ///< Resume playing the video + }; + + /** + * Creates a new rendition action. + * + * @param operation The type of operation the action executes. + * @param movie The movie object the action references. + * @param scriptType The type of script the action executes. + * @param script The actual script the action executes. + */ + RenditionAction( OperationType operation, Okular::Movie *movie, enum ScriptType scriptType, const QString &script ); + + /** + * Destroys the rendition action. + */ + virtual ~RenditionAction(); + + /** + * Returns the action type. + */ + ActionType actionType() const; + + /** + * Returns the action tip. + */ + QString actionTip() const; + + /** + * Returns the operation type. + */ + OperationType operation() const; + + /** + * Returns the movie object or @c 0 if no movie object was set on construction time. + */ + Okular::Movie* movie() const; + + /** + * Returns the type of script. + */ + ScriptType scriptType() const; + + /** + * Returns the script code. + */ + QString script() const; + + /** + * Sets the @p annotation that is associated with the rendition action. + */ + void setAnnotation( ScreenAnnotation *annotation ); + + /** + * Returns the annotation or @c 0 if no annotation has been set. + */ + ScreenAnnotation* annotation() const; + + private: + Q_DECLARE_PRIVATE( RenditionAction ) + Q_DISABLE_COPY( RenditionAction ) +}; + +} + +#endif diff --git a/okular/core/annotations.cpp b/okular/core/annotations.cpp new file mode 100644 index 00000000..e0299468 --- /dev/null +++ b/okular/core/annotations.cpp @@ -0,0 +1,2937 @@ +/*************************************************************************** + * Copyright (C) 2005 by Enrico Ros * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "annotations.h" +#include "annotations_p.h" + +// qt/kde includes +#include +#include + +// DBL_MAX +#include + +// local includes +#include "action.h" +#include "document.h" +#include "document_p.h" +#include "movie.h" +#include "page_p.h" +#include "sound.h" + +using namespace Okular; + +/** + * True, if point @p c lies to the left of the vector from @p a to @p b + * @internal + */ +static bool isLeftOfVector( const NormalizedPoint& a, const NormalizedPoint& b, const NormalizedPoint& c ) +{ + //cross product + return ( (b.x - a.x) * ( c.y - a.y) - ( b.y - a.y ) * ( c.x - a.x ) ) > 0; +} + +/** + * @brief Calculates distance of the given point @p x @p y @p xScale @p yScale to the @p path + * + * Does piecewise comparison and selects the distance to the closest segment + */ +static double distanceSqr( double x, double y, double xScale, double yScale, const QLinkedList& path ) +{ + double distance = DBL_MAX; + double thisDistance; + QLinkedList::const_iterator i = path.constBegin(); + NormalizedPoint lastPoint = *i; + + for (++i; i != path.constEnd(); ++i) { + thisDistance = NormalizedPoint::distanceSqr( x, y, xScale, yScale, lastPoint, (*i) ); + + if ( thisDistance < distance ) + distance = thisDistance; + + lastPoint = *i; + } + return distance; +} + +/** + * Given the squared @p distance from the idealized 0-width line and a pen width @p penWidth, + * (not squared!), returns the final distance + * + * @warning The returned distance is not exact: + * We calculate an (exact) squared distance to the ideal (centered) line, and then subtract + * the squared width of the pen: + * a^2 - b^2 where a = "distance from idealized 0-width line" b = "pen width" + * For an exact result, we would want to calculate "(a - b)^2" but that would require + * a square root operation because we only know the squared distance a^2. + * + * However, the approximation is feasible, because: + * error = (a-b)^2 - (a^2 - b^2) = -2ab + 2b^2 = 2b(b - a) + * Therefore: + * lim_{a->b} a^2 - b^2 - a^2 + 2ab - b^2 --> 0 + * + * In other words, this approximation will estimate the distance to be slightly more than it actually is + * for as long as we are far "outside" the line, becoming more accurate the closer we get to the line + * boundary. Trivially, it also fulfils (a1 < a2) => ((a1^2 - b^2) < (a2^2 - b^2)) making it monotonic. + * "Inside" of the drawn line, the distance is 0 anyway. + */ +static double strokeDistance( double distance, double penWidth ) +{ + return fmax(distance - pow( penWidth, 2 ), 0); +} + +//BEGIN AnnotationUtils implementation +Annotation * AnnotationUtils::createAnnotation( const QDomElement & annElement ) +{ + // safety check on annotation element + if ( !annElement.hasAttribute( "type" ) ) + return 0; + + // build annotation of given type + Annotation * annotation = 0; + int typeNumber = annElement.attribute( "type" ).toInt(); + switch ( typeNumber ) + { + case Annotation::AText: + annotation = new TextAnnotation( annElement ); + break; + case Annotation::ALine: + annotation = new LineAnnotation( annElement ); + break; + case Annotation::AGeom: + annotation = new GeomAnnotation( annElement ); + break; + case Annotation::AHighlight: + annotation = new HighlightAnnotation( annElement ); + break; + case Annotation::AStamp: + annotation = new StampAnnotation( annElement ); + break; + case Annotation::AInk: + annotation = new InkAnnotation( annElement ); + break; + case Annotation::ACaret: + annotation = new CaretAnnotation( annElement ); + break; + } + + // return created annotation + return annotation; +} + +void AnnotationUtils::storeAnnotation( const Annotation * ann, QDomElement & annElement, + QDomDocument & document ) +{ + // save annotation's type as element's attribute + annElement.setAttribute( "type", (uint)ann->subType() ); + + // append all annotation data as children of this node + ann->store( annElement, document ); +} + +QDomElement AnnotationUtils::findChildElement( const QDomNode & parentNode, + const QString & name ) +{ + // loop through the whole children and return a 'name' named element + QDomNode subNode = parentNode.firstChild(); + while( subNode.isElement() ) + { + QDomElement element = subNode.toElement(); + if ( element.tagName() == name ) + return element; + subNode = subNode.nextSibling(); + } + // if the name can't be found, return a dummy null element + return QDomElement(); +} + +QRect AnnotationUtils::annotationGeometry( const Annotation * ann, + double scaledWidth, double scaledHeight ) +{ + const QRect rect = ann->transformedBoundingRectangle().geometry( (int)scaledWidth, (int)scaledHeight ); + if ( ann->subType() == Annotation::AText && ( ( (TextAnnotation*)ann )->textType() == TextAnnotation::Linked ) ) + { + // To be honest i have no clue of why the 24,24 is here, maybe to make sure it's not too small? + // But why only for linked text? + const QRect rect24 = QRect( (int)( ann->transformedBoundingRectangle().left * scaledWidth ), + (int)( ann->transformedBoundingRectangle().top * scaledHeight ), 24, 24 ); + return rect24.united(rect); + } + + return rect; +} +//END AnnotationUtils implementation + +AnnotationProxy::~AnnotationProxy() +{ +} + +//BEGIN Annotation implementation + +class Annotation::Style::Private +{ + public: + Private() + : m_opacity( 1.0 ), m_width( 1.0 ), m_style( Solid ), m_xCorners( 0.0 ), + m_yCorners( 0.0 ), m_marks( 3 ), m_spaces( 0 ), m_effect( NoEffect ), + m_effectIntensity( 1.0 ) + { + } + + QColor m_color; + double m_opacity; + double m_width; + LineStyle m_style; + double m_xCorners; + double m_yCorners; + int m_marks; + int m_spaces; + LineEffect m_effect; + double m_effectIntensity; +}; + +Annotation::Style::Style() + : d( new Private ) +{ +} + +Annotation::Style::~Style() +{ + delete d; +} + +Annotation::Style::Style( const Style &other ) + : d( new Private ) +{ + *d = *other.d; +} + +Annotation::Style& Annotation::Style::operator=( const Style &other ) +{ + if ( this != &other ) + *d = *other.d; + + return *this; +} + +void Annotation::Style::setColor( const QColor &color ) +{ + d->m_color = color; +} + +QColor Annotation::Style::color() const +{ + return d->m_color; +} + +void Annotation::Style::setOpacity( double opacity ) +{ + d->m_opacity = opacity; +} + +double Annotation::Style::opacity() const +{ + return d->m_opacity; +} + +void Annotation::Style::setWidth( double width ) +{ + d->m_width = width; +} + +double Annotation::Style::width() const +{ + return d->m_width; +} + +void Annotation::Style::setLineStyle( LineStyle style ) +{ + d->m_style = style; +} + +Annotation::LineStyle Annotation::Style::lineStyle() const +{ + return d->m_style; +} + +void Annotation::Style::setXCorners( double xCorners ) +{ + d->m_xCorners = xCorners; +} + +double Annotation::Style::xCorners() const +{ + return d->m_xCorners; +} + +void Annotation::Style::setYCorners( double yCorners ) +{ + d->m_yCorners = yCorners; +} + +double Annotation::Style::yCorners() const +{ + return d->m_yCorners; +} + +void Annotation::Style::setMarks( int marks ) +{ + d->m_marks = marks; +} + +int Annotation::Style::marks() const +{ + return d->m_marks; +} + +void Annotation::Style::setSpaces( int spaces ) +{ + d->m_spaces = spaces; +} + +int Annotation::Style::spaces() const +{ + return d->m_spaces; +} + +void Annotation::Style::setLineEffect( LineEffect effect ) +{ + d->m_effect = effect; +} + +Annotation::LineEffect Annotation::Style::lineEffect() const +{ + return d->m_effect; +} + +void Annotation::Style::setEffectIntensity( double intensity ) +{ + d->m_effectIntensity = intensity; +} + +double Annotation::Style::effectIntensity() const +{ + return d->m_effectIntensity; +} + + +class Annotation::Window::Private +{ + public: + Private() + : m_flags( -1 ), m_width( 0 ), m_height( 0 ) + { + } + + int m_flags; + NormalizedPoint m_topLeft; + int m_width; + int m_height; + QString m_title; + QString m_summary; +}; + +Annotation::Window::Window() + : d( new Private ) +{ +} + +Annotation::Window::~Window() +{ + delete d; +} + +Annotation::Window::Window( const Window &other ) + : d( new Private ) +{ + *d = *other.d; +} + +Annotation::Window& Annotation::Window::operator=( const Window &other ) +{ + if ( this != &other ) + *d = *other.d; + + return *this; +} + +void Annotation::Window::setFlags( int flags ) +{ + d->m_flags = flags; +} + +int Annotation::Window::flags() const +{ + return d->m_flags; +} + +void Annotation::Window::setTopLeft( const NormalizedPoint &point ) +{ + d->m_topLeft = point; +} + +NormalizedPoint Annotation::Window::topLeft() const +{ + return d->m_topLeft; +} + +void Annotation::Window::setWidth( int width ) +{ + d->m_width = width; +} + +int Annotation::Window::width() const +{ + return d->m_width; +} + +void Annotation::Window::setHeight( int height ) +{ + d->m_height = height; +} + +int Annotation::Window::height() const +{ + return d->m_height; +} + +void Annotation::Window::setTitle( const QString &title ) +{ + d->m_title = title; +} + +QString Annotation::Window::title() const +{ + return d->m_title; +} + +void Annotation::Window::setSummary( const QString &summary ) +{ + d->m_summary = summary; +} + +QString Annotation::Window::summary() const +{ + return d->m_summary; +} + +class Annotation::Revision::Private +{ + public: + Private() + : m_annotation( 0 ), m_scope( Reply ), m_type( None ) + { + } + + Annotation *m_annotation; + RevisionScope m_scope; + RevisionType m_type; +}; + +Annotation::Revision::Revision() + : d( new Private ) +{ +} + +Annotation::Revision::~Revision() +{ + delete d; +} + +Annotation::Revision::Revision( const Revision &other ) + : d( new Private ) +{ + *d = *other.d; +} + +Annotation::Revision& Annotation::Revision::operator=( const Revision &other ) +{ + if ( this != &other ) + *d = *other.d; + + return *this; +} + +void Annotation::Revision::setAnnotation( Annotation *annotation ) +{ + d->m_annotation = annotation; +} + +Annotation *Annotation::Revision::annotation() const +{ + return d->m_annotation; +} + +void Annotation::Revision::setScope( RevisionScope scope ) +{ + d->m_scope = scope; +} + +Annotation::RevisionScope Annotation::Revision::scope() const +{ + return d->m_scope; +} + +void Annotation::Revision::setType( RevisionType type ) +{ + d->m_type = type; +} + +Annotation::RevisionType Annotation::Revision::type() const +{ + return d->m_type; +} + + +AnnotationPrivate::AnnotationPrivate() + : m_page( 0 ), m_flags( 0 ), m_disposeFunc( 0 ) +{ +} + +AnnotationPrivate::~AnnotationPrivate() +{ + // delete all children revisions + if ( m_revisions.isEmpty() ) + return; + + QLinkedList< Annotation::Revision >::iterator it = m_revisions.begin(), end = m_revisions.end(); + for ( ; it != end; ++it ) + delete (*it).annotation(); +} + +Annotation::Annotation( AnnotationPrivate &dd ) + : d_ptr( &dd ) +{ +} + +Annotation::Annotation( AnnotationPrivate &dd, const QDomNode & annNode ) + : d_ptr( &dd ) +{ + d_ptr->setAnnotationProperties( annNode ); +} + +Annotation::~Annotation() +{ + if ( d_ptr->m_disposeFunc ) + d_ptr->m_disposeFunc( this ); + + delete d_ptr; +} + +void Annotation::setAuthor( const QString &author ) +{ + Q_D( Annotation ); + d->m_author = author; +} + +QString Annotation::author() const +{ + Q_D( const Annotation ); + return d->m_author; +} + +void Annotation::setContents( const QString &contents ) +{ + Q_D( Annotation ); + d->m_contents = contents; +} + +QString Annotation::contents() const +{ + Q_D( const Annotation ); + return d->m_contents; +} + +void Annotation::setUniqueName( const QString &name ) +{ + Q_D( Annotation ); + d->m_uniqueName = name; +} + +QString Annotation::uniqueName() const +{ + Q_D( const Annotation ); + return d->m_uniqueName; +} + +void Annotation::setModificationDate( const QDateTime &date ) +{ + Q_D( Annotation ); + d->m_modifyDate = date; +} + +QDateTime Annotation::modificationDate() const +{ + Q_D( const Annotation ); + return d->m_modifyDate; +} + +void Annotation::setCreationDate( const QDateTime &date ) +{ + Q_D( Annotation ); + d->m_creationDate = date; +} + +QDateTime Annotation::creationDate() const +{ + Q_D( const Annotation ); + return d->m_creationDate; +} + +void Annotation::setFlags( int flags ) +{ + Q_D( Annotation ); + d->m_flags = flags; +} + +int Annotation::flags() const +{ + Q_D( const Annotation ); + return d->m_flags; +} + +void Annotation::setBoundingRectangle( const NormalizedRect &rectangle ) +{ + Q_D( Annotation ); + d->m_boundary = rectangle; + d->resetTransformation(); + if ( d->m_page ) + { + d->transform( d->m_page->rotationMatrix() ); + } +} + +NormalizedRect Annotation::boundingRectangle() const +{ + Q_D( const Annotation ); + return d->m_boundary; +} + +NormalizedRect Annotation::transformedBoundingRectangle() const +{ + Q_D( const Annotation ); + return d->m_transformedBoundary; +} + +void Annotation::translate( const NormalizedPoint &coord ) +{ + Q_D( Annotation ); + d->translate( coord ); + d->resetTransformation(); + if ( d->m_page ) + { + d->transform( d->m_page->rotationMatrix() ); + } +} + +bool Annotation::openDialogAfterCreation() const +{ + Q_D( const Annotation ); + return d->openDialogAfterCreation(); +} + +Annotation::Style & Annotation::style() +{ + Q_D( Annotation ); + return d->m_style; +} + +const Annotation::Style & Annotation::style() const +{ + Q_D( const Annotation ); + return d->m_style; +} + +Annotation::Window & Annotation::window() +{ + Q_D( Annotation ); + return d->m_window; +} + +const Annotation::Window & Annotation::window() const +{ + Q_D( const Annotation ); + return d->m_window; +} + +QLinkedList< Annotation::Revision > & Annotation::revisions() +{ + Q_D( Annotation ); + return d->m_revisions; +} + +const QLinkedList< Annotation::Revision > & Annotation::revisions() const +{ + Q_D( const Annotation ); + return d->m_revisions; +} + +void Annotation::setNativeId( const QVariant &id ) +{ + Q_D( Annotation ); + d->m_nativeId = id; +} + +QVariant Annotation::nativeId() const +{ + Q_D( const Annotation ); + return d->m_nativeId; +} + +void Annotation::setDisposeDataFunction( DisposeDataFunction func ) +{ + Q_D( Annotation ); + d->m_disposeFunc = func; +} + +bool Annotation::canBeMoved() const +{ + Q_D( const Annotation ); + + // Don't move annotations if they cannot be modified + if ( !d->m_page || !d->m_page->m_doc->m_parent->canModifyPageAnnotation(this) ) + return false; + + // highlight "requires" to be "bounded" to text, and that's tricky for now + if ( subType() == AHighlight ) + return false; + + return true; +} + +void Annotation::store( QDomNode & annNode, QDomDocument & document ) const +{ + Q_D( const Annotation ); + // create [base] element of the annotation node + QDomElement e = document.createElement( "base" ); + annNode.appendChild( e ); + + // store -contents- attributes + if ( !d->m_author.isEmpty() ) + e.setAttribute( "author", d->m_author ); + if ( !d->m_contents.isEmpty() ) + e.setAttribute( "contents", d->m_contents ); + if ( !d->m_uniqueName.isEmpty() ) + e.setAttribute( "uniqueName", d->m_uniqueName ); + if ( d->m_modifyDate.isValid() ) + e.setAttribute( "modifyDate", d->m_modifyDate.toString(Qt::ISODate) ); + if ( d->m_creationDate.isValid() ) + e.setAttribute( "creationDate", d->m_creationDate.toString(Qt::ISODate) ); + + // store -other- attributes + if ( d->m_flags ) // Strip internal flags + e.setAttribute( "flags", d->m_flags & ~(External | ExternallyDrawn | BeingMoved) ); + if ( d->m_style.color().isValid() ) + e.setAttribute( "color", d->m_style.color().name() ); + if ( d->m_style.opacity() != 1.0 ) + e.setAttribute( "opacity", QString::number( d->m_style.opacity() ) ); + + // Sub-Node-1 - boundary + QDomElement bE = document.createElement( "boundary" ); + e.appendChild( bE ); + bE.setAttribute( "l", QString::number( d->m_boundary.left ) ); + bE.setAttribute( "t", QString::number( d->m_boundary.top ) ); + bE.setAttribute( "r", QString::number( d->m_boundary.right ) ); + bE.setAttribute( "b", QString::number( d->m_boundary.bottom ) ); + + // Sub-Node-2 - penStyle + if ( d->m_style.width() != 1 || d->m_style.lineStyle() != Solid || d->m_style.xCorners() != 0 || + d->m_style.yCorners() != 0.0 || d->m_style.marks() != 3 || d->m_style.spaces() != 0 ) + { + QDomElement psE = document.createElement( "penStyle" ); + e.appendChild( psE ); + psE.setAttribute( "width", QString::number( d->m_style.width() ) ); + psE.setAttribute( "style", (int)d->m_style.lineStyle() ); + psE.setAttribute( "xcr", QString::number( d->m_style.xCorners() ) ); + psE.setAttribute( "ycr", QString::number( d->m_style.yCorners() ) ); + psE.setAttribute( "marks", d->m_style.marks() ); + psE.setAttribute( "spaces", d->m_style.spaces() ); + } + + // Sub-Node-3 - penEffect + if ( d->m_style.lineEffect() != NoEffect || d->m_style.effectIntensity() != 1.0 ) + { + QDomElement peE = document.createElement( "penEffect" ); + e.appendChild( peE ); + peE.setAttribute( "effect", (int)d->m_style.lineEffect() ); + peE.setAttribute( "intensity", QString::number( d->m_style.effectIntensity() ) ); + } + + // Sub-Node-4 - window + if ( d->m_window.flags() != -1 || !d->m_window.title().isEmpty() || + !d->m_window.summary().isEmpty() ) + { + QDomElement wE = document.createElement( "window" ); + e.appendChild( wE ); + wE.setAttribute( "flags", d->m_window.flags() ); + wE.setAttribute( "top", QString::number( d->m_window.topLeft().x ) ); + wE.setAttribute( "left", QString::number( d->m_window.topLeft().y ) ); + wE.setAttribute( "width", d->m_window.width() ); + wE.setAttribute( "height", d->m_window.height() ); + wE.setAttribute( "title", d->m_window.title() ); + wE.setAttribute( "summary", d->m_window.summary() ); + } + + // create [revision] element of the annotation node (if any) + if ( d->m_revisions.isEmpty() ) + return; + + // add all revisions as children of revisions element + QLinkedList< Revision >::const_iterator it = d->m_revisions.begin(), end = d->m_revisions.end(); + for ( ; it != end; ++it ) + { + // create revision element + const Revision & revision = *it; + QDomElement r = document.createElement( "revision" ); + annNode.appendChild( r ); + // set element attributes + r.setAttribute( "revScope", (int)revision.scope() ); + r.setAttribute( "revType", (int)revision.type() ); + // use revision as the annotation element, so fill it up + AnnotationUtils::storeAnnotation( revision.annotation(), r, document ); + } +} + +QDomNode Annotation::getAnnotationPropertiesDomNode() const +{ + QDomDocument doc( "documentInfo" ); + QDomElement node = doc.createElement( "annotation" ); + + store(node, doc); + return node; +} + +void Annotation::setAnnotationProperties( const QDomNode& node ) +{ + // Save off internal properties that aren't contained in node + Okular::PagePrivate *p = d_ptr->m_page; + QVariant nativeID = d_ptr->m_nativeId; + int internalFlags = d_ptr->m_flags & (External | ExternallyDrawn | BeingMoved); + Annotation::DisposeDataFunction disposeFunc = d_ptr->m_disposeFunc; + + // Replace AnnotationPrivate object with a fresh copy + AnnotationPrivate *new_d_ptr = d_ptr->getNewAnnotationPrivate(); + delete( d_ptr ); + d_ptr = new_d_ptr; + + // Set the annotations properties from node + d_ptr->setAnnotationProperties(node); + + // Restore internal properties + d_ptr->m_page = p; + d_ptr->m_nativeId = nativeID; + d_ptr->m_flags = d_ptr->m_flags | internalFlags; + d_ptr->m_disposeFunc = disposeFunc; + + // Transform annotation to current page rotation + d_ptr->transform( d_ptr->m_page->rotationMatrix() ); +} + +double AnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) +{ + return m_transformedBoundary.distanceSqr( x, y, xScale, yScale ); +} + +void AnnotationPrivate::annotationTransform( const QTransform &matrix ) +{ + resetTransformation(); + transform( matrix ); +} + +void AnnotationPrivate::transform( const QTransform &matrix ) +{ + m_transformedBoundary.transform( matrix ); +} + +void AnnotationPrivate::baseTransform( const QTransform &matrix ) +{ + m_boundary.transform( matrix ); +} + +void AnnotationPrivate::resetTransformation() +{ + m_transformedBoundary = m_boundary; +} + +void AnnotationPrivate::translate( const NormalizedPoint &coord ) +{ + m_boundary.left = m_boundary.left + coord.x; + m_boundary.right = m_boundary.right + coord.x; + m_boundary.top = m_boundary.top + coord.y; + m_boundary.bottom = m_boundary.bottom + coord.y; +} + +bool AnnotationPrivate::openDialogAfterCreation() const +{ + return false; +} + +void AnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + // get the [base] element of the annotation node + QDomElement e = AnnotationUtils::findChildElement( node, "base" ); + if ( e.isNull() ) + return; + + // parse -contents- attributes + if ( e.hasAttribute( "author" ) ) + m_author = e.attribute( "author" ); + if ( e.hasAttribute( "contents" ) ) + m_contents = e.attribute( "contents" ); + if ( e.hasAttribute( "uniqueName" ) ) + m_uniqueName = e.attribute( "uniqueName" ); + if ( e.hasAttribute( "modifyDate" ) ) + m_modifyDate = QDateTime::fromString( e.attribute("modifyDate"), Qt::ISODate ); + if ( e.hasAttribute( "creationDate" ) ) + m_creationDate = QDateTime::fromString( e.attribute("creationDate"), Qt::ISODate ); + + // parse -other- attributes + if ( e.hasAttribute( "flags" ) ) + m_flags = e.attribute( "flags" ).toInt(); + if ( e.hasAttribute( "color" ) ) + m_style.setColor( QColor( e.attribute( "color" ) ) ); + if ( e.hasAttribute( "opacity" ) ) + m_style.setOpacity( e.attribute( "opacity" ).toDouble() ); + + // parse -the-subnodes- (describing Style, Window, Revision(s) structures) + // Note: all subnodes if present must be 'attributes complete' + QDomNode eSubNode = e.firstChild(); + while ( eSubNode.isElement() ) + { + QDomElement ee = eSubNode.toElement(); + eSubNode = eSubNode.nextSibling(); + + // parse boundary + if ( ee.tagName() == "boundary" ) + { + m_boundary=NormalizedRect(ee.attribute( "l" ).toDouble(), + ee.attribute( "t" ).toDouble(), + ee.attribute( "r" ).toDouble(), + ee.attribute( "b" ).toDouble()); + } + // parse penStyle if not default + else if ( ee.tagName() == "penStyle" ) + { + m_style.setWidth( ee.attribute( "width" ).toDouble() ); + m_style.setLineStyle( (Annotation::LineStyle)ee.attribute( "style" ).toInt() ); + m_style.setXCorners( ee.attribute( "xcr" ).toDouble() ); + m_style.setYCorners( ee.attribute( "ycr" ).toDouble() ); + m_style.setMarks( ee.attribute( "marks" ).toInt() ); + m_style.setSpaces( ee.attribute( "spaces" ).toInt() ); + } + // parse effectStyle if not default + else if ( ee.tagName() == "penEffect" ) + { + m_style.setLineEffect( (Annotation::LineEffect)ee.attribute( "effect" ).toInt() ); + m_style.setEffectIntensity( ee.attribute( "intensity" ).toDouble() ); + } + // parse window if present + else if ( ee.tagName() == "window" ) + { + m_window.setFlags( ee.attribute( "flags" ).toInt() ); + m_window.setTopLeft( NormalizedPoint( ee.attribute( "top" ).toDouble(), + ee.attribute( "left" ).toDouble() ) ); + m_window.setWidth( ee.attribute( "width" ).toInt() ); + m_window.setHeight( ee.attribute( "height" ).toInt() ); + m_window.setTitle( ee.attribute( "title" ) ); + m_window.setSummary( ee.attribute( "summary" ) ); + } + } + + // get the [revisions] element of the annotation node + QDomNode revNode = node.firstChild(); + for ( ; revNode.isElement(); revNode = revNode.nextSibling() ) + { + QDomElement revElement = revNode.toElement(); + if ( revElement.tagName() != "revision" ) + continue; + + // compile the Revision structure crating annotation + Annotation::Revision revision; + revision.setScope( (Annotation::RevisionScope)revElement.attribute( "revScope" ).toInt() ); + revision.setType( (Annotation::RevisionType)revElement.attribute( "revType" ).toInt() ); + revision.setAnnotation( AnnotationUtils::createAnnotation( revElement ) ); + + // if annotation is valid, add revision to internal list + if ( revision.annotation() ) + m_revisions.append( revision ); + } + + m_transformedBoundary = m_boundary; +} + +//END Annotation implementation + + +/** TextAnnotation [Annotation] */ + +class Okular::TextAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + TextAnnotationPrivate() + : AnnotationPrivate(), m_textType( TextAnnotation::Linked ), + m_textIcon( "Comment" ), m_inplaceAlign( 0 ), + m_inplaceIntent( TextAnnotation::Unknown ) + { + } + + virtual void transform( const QTransform &matrix ); + virtual void baseTransform( const QTransform &matrix ); + virtual void resetTransformation(); + virtual void translate( const NormalizedPoint &coord ); + virtual bool openDialogAfterCreation() const; + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + + TextAnnotation::TextType m_textType; + QString m_textIcon; + QFont m_textFont; + int m_inplaceAlign; + NormalizedPoint m_inplaceCallout[3]; + NormalizedPoint m_transformedInplaceCallout[3]; + TextAnnotation::InplaceIntent m_inplaceIntent; +}; + +/* + The default textIcon for text annotation is Note as the PDF Reference says +*/ +TextAnnotation::TextAnnotation() + : Annotation( *new TextAnnotationPrivate() ) +{ +} + +TextAnnotation::TextAnnotation( const QDomNode & node ) + : Annotation( *new TextAnnotationPrivate(), node ) +{ +} + +TextAnnotation::~TextAnnotation() +{ +} + +void TextAnnotation::setTextType( TextType textType ) +{ + Q_D( TextAnnotation ); + d->m_textType = textType; +} + +TextAnnotation::TextType TextAnnotation::textType() const +{ + Q_D( const TextAnnotation ); + return d->m_textType; +} + +void TextAnnotation::setTextIcon( const QString &icon ) +{ + Q_D( TextAnnotation ); + d->m_textIcon = icon; +} + +QString TextAnnotation::textIcon() const +{ + Q_D( const TextAnnotation ); + return d->m_textIcon; +} + +void TextAnnotation::setTextFont( const QFont &font ) +{ + Q_D( TextAnnotation ); + d->m_textFont = font; +} + +QFont TextAnnotation::textFont() const +{ + Q_D( const TextAnnotation ); + return d->m_textFont; +} + +void TextAnnotation::setInplaceAlignment( int alignment ) +{ + Q_D( TextAnnotation ); + d->m_inplaceAlign = alignment; +} + +int TextAnnotation::inplaceAlignment() const +{ + Q_D( const TextAnnotation ); + return d->m_inplaceAlign; +} + +void TextAnnotation::setInplaceCallout( const NormalizedPoint &point, int index ) +{ + if ( index < 0 || index > 2 ) + return; + + Q_D( TextAnnotation ); + d->m_inplaceCallout[ index ] = point; +} + +NormalizedPoint TextAnnotation::inplaceCallout( int index ) const +{ + if ( index < 0 || index > 2 ) + return NormalizedPoint(); + + Q_D( const TextAnnotation ); + return d->m_inplaceCallout[ index ]; +} + +NormalizedPoint TextAnnotation::transformedInplaceCallout( int index ) const +{ + if ( index < 0 || index > 2 ) + return NormalizedPoint(); + + Q_D( const TextAnnotation ); + return d->m_transformedInplaceCallout[ index ]; +} + +void TextAnnotation::setInplaceIntent( InplaceIntent intent ) +{ + Q_D( TextAnnotation ); + d->m_inplaceIntent = intent; +} + +TextAnnotation::InplaceIntent TextAnnotation::inplaceIntent() const +{ + Q_D( const TextAnnotation ); + return d->m_inplaceIntent; +} + +Annotation::SubType TextAnnotation::subType() const +{ + return AText; +} + +void TextAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + Q_D( const TextAnnotation ); + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [text] element + QDomElement textElement = document.createElement( "text" ); + node.appendChild( textElement ); + + // store the optional attributes + if ( d->m_textType != Linked ) + textElement.setAttribute( "type", (int)d->m_textType ); + if ( !d->m_textIcon.isEmpty() ) + textElement.setAttribute( "icon", d->m_textIcon ); + if ( d->m_textFont != QApplication::font() ) + textElement.setAttribute( "font", d->m_textFont.toString() ); + if ( d->m_inplaceAlign ) + textElement.setAttribute( "align", d->m_inplaceAlign ); + if ( d->m_inplaceIntent != Unknown ) + textElement.setAttribute( "intent", (int)d->m_inplaceIntent ); + + // Sub-Node - callout + if ( d->m_inplaceCallout[0].x != 0.0 ) + { + QDomElement calloutElement = document.createElement( "callout" ); + textElement.appendChild( calloutElement ); + calloutElement.setAttribute( "ax", QString::number( d->m_inplaceCallout[0].x ) ); + calloutElement.setAttribute( "ay", QString::number( d->m_inplaceCallout[0].y ) ); + calloutElement.setAttribute( "bx", QString::number( d->m_inplaceCallout[1].x ) ); + calloutElement.setAttribute( "by", QString::number( d->m_inplaceCallout[1].y ) ); + calloutElement.setAttribute( "cx", QString::number( d->m_inplaceCallout[2].x ) ); + calloutElement.setAttribute( "cy", QString::number( d->m_inplaceCallout[2].y ) ); + } +} + +void TextAnnotationPrivate::transform( const QTransform &matrix ) +{ + AnnotationPrivate::transform( matrix ); + + for ( int i = 0; i < 3; ++i ) { + m_transformedInplaceCallout[i].transform( matrix ); + } +} + +void TextAnnotationPrivate::baseTransform( const QTransform &matrix ) +{ + AnnotationPrivate::baseTransform( matrix ); + + for ( int i = 0; i < 3; ++i ) { + m_inplaceCallout[i].transform( matrix ); + } +} + +void TextAnnotationPrivate::resetTransformation() +{ + AnnotationPrivate::resetTransformation(); + + for ( int i = 0; i < 3; ++i ) { + m_transformedInplaceCallout[i] = m_inplaceCallout[i]; + } +} + +void TextAnnotationPrivate::translate( const NormalizedPoint &coord ) +{ + AnnotationPrivate::translate( coord ); + +#define ADD_COORD( c1, c2 ) \ +{ \ + c1.x = c1.x + c2.x; \ + c1.y = c1.y + c2.y; \ +} + ADD_COORD( m_inplaceCallout[0], coord ) + ADD_COORD( m_inplaceCallout[1], coord ) + ADD_COORD( m_inplaceCallout[2], coord ) +#undef ADD_COORD +} + +bool TextAnnotationPrivate::openDialogAfterCreation() const +{ + return ( m_textType == Okular::TextAnnotation::Linked ); +} + +void TextAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + + // loop through the whole children looking for a 'text' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "text" ) + continue; + + // parse the attributes + if ( e.hasAttribute( "type" ) ) + m_textType = (TextAnnotation::TextType)e.attribute( "type" ).toInt(); + if ( e.hasAttribute( "icon" ) ) + m_textIcon = e.attribute( "icon" ); + if ( e.hasAttribute( "font" ) ) + m_textFont.fromString( e.attribute( "font" ) ); + if ( e.hasAttribute( "align" ) ) + m_inplaceAlign = e.attribute( "align" ).toInt(); + if ( e.hasAttribute( "intent" ) ) + m_inplaceIntent = (TextAnnotation::InplaceIntent)e.attribute( "intent" ).toInt(); + + // parse the subnodes + QDomNode eSubNode = e.firstChild(); + while ( eSubNode.isElement() ) + { + QDomElement ee = eSubNode.toElement(); + eSubNode = eSubNode.nextSibling(); + + if ( ee.tagName() == "escapedText" ) + { + m_contents = ee.firstChild().toCDATASection().data(); + } + else if ( ee.tagName() == "callout" ) + { + m_inplaceCallout[0].x = ee.attribute( "ax" ).toDouble(); + m_inplaceCallout[0].y = ee.attribute( "ay" ).toDouble(); + m_inplaceCallout[1].x = ee.attribute( "bx" ).toDouble(); + m_inplaceCallout[1].y = ee.attribute( "by" ).toDouble(); + m_inplaceCallout[2].x = ee.attribute( "cx" ).toDouble(); + m_inplaceCallout[2].y = ee.attribute( "cy" ).toDouble(); + } + } + + // loading complete + break; + } + + for ( int i = 0; i < 3; ++i ) + m_transformedInplaceCallout[i] = m_inplaceCallout[i]; +} + +AnnotationPrivate* TextAnnotationPrivate::getNewAnnotationPrivate() +{ + return new TextAnnotationPrivate(); +} + +/** LineAnnotation [Annotation] */ + +class Okular::LineAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + LineAnnotationPrivate() + : AnnotationPrivate(), + m_lineStartStyle( LineAnnotation::None ), m_lineEndStyle( LineAnnotation::None ), + m_lineClosed( false ), m_lineShowCaption( false ), m_lineLeadingFwdPt( 0 ), + m_lineLeadingBackPt( 0 ), m_lineIntent( LineAnnotation::Unknown ) + { + } + + virtual void transform( const QTransform &matrix ); + virtual void baseTransform( const QTransform &matrix ); + virtual void resetTransformation(); + virtual void translate( const NormalizedPoint &coord ); + virtual double distanceSqr( double x, double y, double xScale, double yScale ); + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + + QLinkedList m_linePoints; + QLinkedList m_transformedLinePoints; + LineAnnotation::TermStyle m_lineStartStyle; + LineAnnotation::TermStyle m_lineEndStyle; + bool m_lineClosed : 1; + bool m_lineShowCaption : 1; + QColor m_lineInnerColor; + double m_lineLeadingFwdPt; + double m_lineLeadingBackPt; + LineAnnotation::LineIntent m_lineIntent; +}; + +LineAnnotation::LineAnnotation() + : Annotation( *new LineAnnotationPrivate() ) +{ +} + +LineAnnotation::LineAnnotation( const QDomNode & node ) + : Annotation( *new LineAnnotationPrivate(), node ) +{ +} + +LineAnnotation::~LineAnnotation() +{ +} + +void LineAnnotation::setLinePoints( const QLinkedList &points ) +{ + Q_D( LineAnnotation ); + d->m_linePoints = points; +} + +QLinkedList LineAnnotation::linePoints() const +{ + Q_D( const LineAnnotation ); + return d->m_linePoints; +} + +QLinkedList LineAnnotation::transformedLinePoints() const +{ + Q_D( const LineAnnotation ); + return d->m_transformedLinePoints; +} + +void LineAnnotation::setLineStartStyle( TermStyle style ) +{ + Q_D( LineAnnotation ); + d->m_lineStartStyle = style; +} + +LineAnnotation::TermStyle LineAnnotation::lineStartStyle() const +{ + Q_D( const LineAnnotation ); + return d->m_lineStartStyle; +} + +void LineAnnotation::setLineEndStyle( TermStyle style ) +{ + Q_D( LineAnnotation ); + d->m_lineEndStyle = style; +} + +LineAnnotation::TermStyle LineAnnotation::lineEndStyle() const +{ + Q_D( const LineAnnotation ); + return d->m_lineEndStyle; +} + +void LineAnnotation::setLineClosed( bool closed ) +{ + Q_D( LineAnnotation ); + d->m_lineClosed = closed; +} + +bool LineAnnotation::lineClosed() const +{ + Q_D( const LineAnnotation ); + return d->m_lineClosed; +} + +void LineAnnotation::setLineInnerColor( const QColor &color ) +{ + Q_D( LineAnnotation ); + d->m_lineInnerColor = color; +} + +QColor LineAnnotation::lineInnerColor() const +{ + Q_D( const LineAnnotation ); + return d->m_lineInnerColor; +} + +void LineAnnotation::setLineLeadingForwardPoint( double point ) +{ + Q_D( LineAnnotation ); + d->m_lineLeadingFwdPt = point; +} + +double LineAnnotation::lineLeadingForwardPoint() const +{ + Q_D( const LineAnnotation ); + return d->m_lineLeadingFwdPt; +} + +void LineAnnotation::setLineLeadingBackwardPoint( double point ) +{ + Q_D( LineAnnotation ); + d->m_lineLeadingBackPt = point; +} + +double LineAnnotation::lineLeadingBackwardPoint() const +{ + Q_D( const LineAnnotation ); + return d->m_lineLeadingBackPt; +} + +void LineAnnotation::setShowCaption( bool show ) +{ + Q_D( LineAnnotation ); + d->m_lineShowCaption = show; +} + +bool LineAnnotation::showCaption() const +{ + Q_D( const LineAnnotation ); + return d->m_lineShowCaption; +} + +void LineAnnotation::setLineIntent( LineIntent intent ) +{ + Q_D( LineAnnotation ); + d->m_lineIntent = intent; +} + +LineAnnotation::LineIntent LineAnnotation::lineIntent() const +{ + Q_D( const LineAnnotation ); + return d->m_lineIntent; +} + +Annotation::SubType LineAnnotation::subType() const +{ + return ALine; +} + +void LineAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + Q_D( const LineAnnotation ); + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [line] element + QDomElement lineElement = document.createElement( "line" ); + node.appendChild( lineElement ); + + // store the attributes + if ( d->m_lineStartStyle != None ) + lineElement.setAttribute( "startStyle", (int)d->m_lineStartStyle ); + if ( d->m_lineEndStyle != None ) + lineElement.setAttribute( "endStyle", (int)d->m_lineEndStyle ); + if ( d->m_lineClosed ) + lineElement.setAttribute( "closed", d->m_lineClosed ); + if ( d->m_lineInnerColor.isValid() ) + lineElement.setAttribute( "innerColor", d->m_lineInnerColor.name() ); + if ( d->m_lineLeadingFwdPt != 0.0 ) + lineElement.setAttribute( "leadFwd", QString::number( d->m_lineLeadingFwdPt ) ); + if ( d->m_lineLeadingBackPt != 0.0 ) + lineElement.setAttribute( "leadBack", QString::number( d->m_lineLeadingBackPt ) ); + if ( d->m_lineShowCaption ) + lineElement.setAttribute( "showCaption", d->m_lineShowCaption ); + if ( d->m_lineIntent != Unknown ) + lineElement.setAttribute( "intent", d->m_lineIntent ); + + // append the list of points + int points = d->m_linePoints.count(); + if ( points > 1 ) + { + QLinkedList::const_iterator it = d->m_linePoints.begin(), end = d->m_linePoints.end(); + while ( it != end ) + { + const NormalizedPoint & p = *it; + QDomElement pElement = document.createElement( "point" ); + lineElement.appendChild( pElement ); + pElement.setAttribute( "x", QString::number( p.x ) ); + pElement.setAttribute( "y", QString::number( p.y ) ); + it++; //to avoid loop + } + } +} + +void LineAnnotationPrivate::transform( const QTransform &matrix ) +{ + AnnotationPrivate::transform( matrix ); + + QMutableLinkedListIterator it( m_transformedLinePoints ); + while ( it.hasNext() ) + it.next().transform( matrix ); +} + +void LineAnnotationPrivate::baseTransform( const QTransform &matrix ) +{ + AnnotationPrivate::baseTransform( matrix ); + + QMutableLinkedListIterator it( m_linePoints ); + while ( it.hasNext() ) + it.next().transform( matrix ); +} + +void LineAnnotationPrivate::resetTransformation() +{ + AnnotationPrivate::resetTransformation(); + + m_transformedLinePoints = m_linePoints; +} + +void LineAnnotationPrivate::translate( const NormalizedPoint &coord ) +{ + AnnotationPrivate::translate( coord ); + + QMutableLinkedListIterator it( m_linePoints ); + while ( it.hasNext() ) + { + NormalizedPoint& p = it.next(); + p.x = p.x + coord.x; + p.y = p.y + coord.y; + } +} + +void LineAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + + // loop through the whole children looking for a 'line' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "line" ) + continue; + + // parse the attributes + if ( e.hasAttribute( "startStyle" ) ) + m_lineStartStyle = (LineAnnotation::TermStyle)e.attribute( "startStyle" ).toInt(); + if ( e.hasAttribute( "endStyle" ) ) + m_lineEndStyle = (LineAnnotation::TermStyle)e.attribute( "endStyle" ).toInt(); + if ( e.hasAttribute( "closed" ) ) + m_lineClosed = e.attribute( "closed" ).toInt(); + if ( e.hasAttribute( "innerColor" ) ) + m_lineInnerColor = QColor( e.attribute( "innerColor" ) ); + if ( e.hasAttribute( "leadFwd" ) ) + m_lineLeadingFwdPt = e.attribute( "leadFwd" ).toDouble(); + if ( e.hasAttribute( "leadBack" ) ) + m_lineLeadingBackPt = e.attribute( "leadBack" ).toDouble(); + if ( e.hasAttribute( "showCaption" ) ) + m_lineShowCaption = e.attribute( "showCaption" ).toInt(); + if ( e.hasAttribute( "intent" ) ) + m_lineIntent = (LineAnnotation::LineIntent)e.attribute( "intent" ).toInt(); + + // parse all 'point' subnodes + QDomNode pointNode = e.firstChild(); + while ( pointNode.isElement() ) + { + QDomElement pe = pointNode.toElement(); + pointNode = pointNode.nextSibling(); + + if ( pe.tagName() != "point" ) + continue; + + NormalizedPoint p; + p.x = pe.attribute( "x", "0.0" ).toDouble(); + p.y = pe.attribute( "y", "0.0" ).toDouble(); + m_linePoints.append( p ); + } + + // loading complete + break; + } + + m_transformedLinePoints = m_linePoints; +} + +AnnotationPrivate* LineAnnotationPrivate::getNewAnnotationPrivate() +{ + return new LineAnnotationPrivate(); +} + +double LineAnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) +{ + QLinkedList transformedLinePoints = m_transformedLinePoints; + + if ( m_lineClosed ) // Close the path + transformedLinePoints.append( transformedLinePoints.first() ); + + if ( m_lineInnerColor.isValid() ) + { + QPolygonF polygon; + foreach ( const NormalizedPoint &p, transformedLinePoints ) + polygon.append( QPointF( p.x, p.y ) ); + + if ( polygon.containsPoint( QPointF( x, y ), Qt::WindingFill ) ) + return 0; + } + + return strokeDistance( ::distanceSqr( x, y, xScale, yScale, transformedLinePoints ), + m_style.width() * xScale / ( m_page->m_width * 2 ) ); +} + +/** GeomAnnotation [Annotation] */ + +class Okular::GeomAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + GeomAnnotationPrivate() + : AnnotationPrivate(), m_geomType( GeomAnnotation::InscribedSquare ) + { + } + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + virtual double distanceSqr( double x, double y, double xScale, double yScale ); + + GeomAnnotation::GeomType m_geomType; + QColor m_geomInnerColor; +}; + +GeomAnnotation::GeomAnnotation() + : Annotation( *new GeomAnnotationPrivate() ) +{ +} + +GeomAnnotation::GeomAnnotation( const QDomNode & node ) + : Annotation( *new GeomAnnotationPrivate(), node ) +{ +} + +GeomAnnotation::~GeomAnnotation() +{ +} + +void GeomAnnotation::setGeometricalType( GeomType type ) +{ + Q_D( GeomAnnotation ); + d->m_geomType = type; +} + +GeomAnnotation::GeomType GeomAnnotation::geometricalType() const +{ + Q_D( const GeomAnnotation ); + return d->m_geomType; +} + +void GeomAnnotation::setGeometricalInnerColor( const QColor &color ) +{ + Q_D( GeomAnnotation ); + d->m_geomInnerColor = color; +} + +QColor GeomAnnotation::geometricalInnerColor() const +{ + Q_D( const GeomAnnotation ); + return d->m_geomInnerColor; +} + +Annotation::SubType GeomAnnotation::subType() const +{ + return AGeom; +} + +void GeomAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + Q_D( const GeomAnnotation ); + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [geom] element + QDomElement geomElement = document.createElement( "geom" ); + node.appendChild( geomElement ); + + // append the optional attributes + if ( d->m_geomType != InscribedSquare ) + geomElement.setAttribute( "type", (int)d->m_geomType ); + if ( d->m_geomInnerColor.isValid() ) + geomElement.setAttribute( "color", d->m_geomInnerColor.name() ); +} + +void GeomAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + // loop through the whole children looking for a 'geom' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "geom" ) + continue; + + // parse the attributes + if ( e.hasAttribute( "type" ) ) + m_geomType = (GeomAnnotation::GeomType)e.attribute( "type" ).toInt(); + if ( e.hasAttribute( "color" ) ) + m_geomInnerColor = QColor( e.attribute( "color" ) ); + // compatibility + if ( e.hasAttribute( "width" ) ) + m_style.setWidth( e.attribute( "width" ).toInt() ); + + // loading complete + break; + } +} + +AnnotationPrivate* GeomAnnotationPrivate::getNewAnnotationPrivate() +{ + return new GeomAnnotationPrivate(); +} + +double GeomAnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) +{ + double distance = 0; + //the line thickness is applied unevenly (only on the "inside") - account for this + bool withinShape = false; + switch (m_geomType) { + case GeomAnnotation::InscribedCircle: + { + //calculate the center point and focus lengths of the ellipse + const double centerX = ( m_transformedBoundary.left + m_transformedBoundary.right ) / 2.0; + const double centerY = ( m_transformedBoundary.top + m_transformedBoundary.bottom ) / 2.0; + const double focusX = ( m_transformedBoundary.right - centerX); + const double focusY = ( m_transformedBoundary.bottom - centerY); + + const double focusXSqr = pow( focusX, 2 ); + const double focusYSqr = pow( focusY, 2 ); + + // to calculate the distance from the ellipse, we will first find the point "projection" + // that lies on the ellipse and is closest to the point (x,y) + // This point can obviously be written as "center + lambda(inputPoint - center)". + // Because the point lies on the ellipse, we know that: + // 1 = ((center.x - projection.x)/focusX)^2 + ((center.y - projection.y)/focusY)^2 + // After filling in projection.x = center.x + lambda * (inputPoint.x - center.x) + // and its y-equivalent, we can solve for lambda: + const double lambda = sqrt( focusXSqr * focusYSqr / + ( focusYSqr * pow( x - centerX, 2 ) + focusXSqr * pow( y - centerY, 2 ) ) ); + + // if the ellipse is filled, we treat all points within as "on" it + if ( lambda > 1 ) + { + if ( m_geomInnerColor.isValid() ) + return 0; + else + withinShape = true; + } + + //otherwise we calculate the squared distance from the projected point on the ellipse + NormalizedPoint projection( centerX, centerY ); + projection.x += lambda * ( x - centerX ); + projection.y += lambda * ( y - centerY ); + + distance = projection.distanceSqr( x, y, xScale, yScale ); + break; + } + + case GeomAnnotation::InscribedSquare: + //if the square is filled, only check the bounding box + if ( m_geomInnerColor.isValid() ) + return AnnotationPrivate::distanceSqr( x, y, xScale, yScale ); + + QLinkedList edges; + edges << NormalizedPoint( m_transformedBoundary.left, m_transformedBoundary.top ); + edges << NormalizedPoint( m_transformedBoundary.right, m_transformedBoundary.top ); + edges << NormalizedPoint( m_transformedBoundary.right, m_transformedBoundary.bottom ); + edges << NormalizedPoint( m_transformedBoundary.left, m_transformedBoundary.bottom ); + edges << NormalizedPoint( m_transformedBoundary.left, m_transformedBoundary.top ); + distance = ::distanceSqr( x, y, xScale, yScale, edges ); + + if ( m_transformedBoundary.contains( x, y ) ) + withinShape = true; + + break; + } + if ( withinShape ) + distance = strokeDistance( distance, m_style.width() * xScale / m_page->m_width ); + + return distance; +} + +/** HighlightAnnotation [Annotation] */ + +class HighlightAnnotation::Quad::Private +{ + public: + Private() + { + } + + NormalizedPoint m_points[4]; + NormalizedPoint m_transformedPoints[4]; + bool m_capStart : 1; + bool m_capEnd : 1; + double m_feather; +}; + +HighlightAnnotation::Quad::Quad() + : d( new Private ) +{ +} + +HighlightAnnotation::Quad::~Quad() +{ + delete d; +} + +HighlightAnnotation::Quad::Quad( const Quad &other ) + : d( new Private ) +{ + *d = *other.d; +} + +HighlightAnnotation::Quad& HighlightAnnotation::Quad::operator=( const Quad &other ) +{ + if ( this != &other ) + *d = *other.d; + + return *this; +} + +void HighlightAnnotation::Quad::setPoint( const NormalizedPoint &point, int index ) +{ + if ( index < 0 || index > 3 ) + return; + + d->m_points[ index ] = point; +} + +NormalizedPoint HighlightAnnotation::Quad::point( int index ) const +{ + if ( index < 0 || index > 3 ) + return NormalizedPoint(); + + return d->m_points[ index ]; +} + +NormalizedPoint HighlightAnnotation::Quad::transformedPoint( int index ) const +{ + if ( index < 0 || index > 3 ) + return NormalizedPoint(); + + return d->m_transformedPoints[ index ]; +} + +void HighlightAnnotation::Quad::setCapStart( bool value ) +{ + d->m_capStart = value; +} + +bool HighlightAnnotation::Quad::capStart() const +{ + return d->m_capStart; +} + +void HighlightAnnotation::Quad::setCapEnd( bool value ) +{ + d->m_capEnd = value; +} + +bool HighlightAnnotation::Quad::capEnd() const +{ + return d->m_capEnd; +} + +void HighlightAnnotation::Quad::setFeather( double width ) +{ + d->m_feather = width; +} + +double HighlightAnnotation::Quad::feather() const +{ + return d->m_feather; +} + +void HighlightAnnotation::Quad::transform( const QTransform &matrix ) +{ + for ( int i = 0; i < 4; ++i ) { + d->m_transformedPoints[ i ] = d->m_points[ i ]; + d->m_transformedPoints[ i ].transform( matrix ); + } +} + + +class Okular::HighlightAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + HighlightAnnotationPrivate() + : AnnotationPrivate(), m_highlightType( HighlightAnnotation::Highlight ) + { + } + + virtual void transform( const QTransform &matrix ); + virtual void baseTransform( const QTransform &matrix ); + virtual double distanceSqr( double x, double y, double xScale, double yScale ); + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + + HighlightAnnotation::HighlightType m_highlightType; + QList< HighlightAnnotation::Quad > m_highlightQuads; +}; + +HighlightAnnotation::HighlightAnnotation() + : Annotation( *new HighlightAnnotationPrivate() ) +{ +} + +HighlightAnnotation::HighlightAnnotation( const QDomNode & node ) + : Annotation( *new HighlightAnnotationPrivate(), node ) +{ +} + +HighlightAnnotation::~HighlightAnnotation() +{ +} + +void HighlightAnnotation::setHighlightType( HighlightType type ) +{ + Q_D( HighlightAnnotation ); + d->m_highlightType = type; +} + +HighlightAnnotation::HighlightType HighlightAnnotation::highlightType() const +{ + Q_D( const HighlightAnnotation ); + return d->m_highlightType; +} + +QList< HighlightAnnotation::Quad > & HighlightAnnotation::highlightQuads() +{ + Q_D( HighlightAnnotation ); + return d->m_highlightQuads; +} + +void HighlightAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + Q_D( const HighlightAnnotation ); + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [hl] element + QDomElement hlElement = document.createElement( "hl" ); + node.appendChild( hlElement ); + + // append the optional attributes + if ( d->m_highlightType != Highlight ) + hlElement.setAttribute( "type", (int)d->m_highlightType ); + if ( d->m_highlightQuads.count() < 1 ) + return; + // append highlight quads, all children describe quads + QList< Quad >::const_iterator it = d->m_highlightQuads.begin(), end = d->m_highlightQuads.end(); + for ( ; it != end; ++it ) + { + QDomElement quadElement = document.createElement( "quad" ); + hlElement.appendChild( quadElement ); + const Quad & q = *it; + quadElement.setAttribute( "ax", QString::number( q.point( 0 ).x ) ); + quadElement.setAttribute( "ay", QString::number( q.point( 0 ).y ) ); + quadElement.setAttribute( "bx", QString::number( q.point( 1 ).x ) ); + quadElement.setAttribute( "by", QString::number( q.point( 1 ).y ) ); + quadElement.setAttribute( "cx", QString::number( q.point( 2 ).x ) ); + quadElement.setAttribute( "cy", QString::number( q.point( 2 ).y ) ); + quadElement.setAttribute( "dx", QString::number( q.point( 3 ).x ) ); + quadElement.setAttribute( "dy", QString::number( q.point( 3 ).y ) ); + if ( q.capStart() ) + quadElement.setAttribute( "start", 1 ); + if ( q.capEnd() ) + quadElement.setAttribute( "end", 1 ); + quadElement.setAttribute( "feather", QString::number( q.feather() ) ); + } +} + +Annotation::SubType HighlightAnnotation::subType() const +{ + return AHighlight; +} + +void HighlightAnnotationPrivate::transform( const QTransform &matrix ) +{ + AnnotationPrivate::transform( matrix ); + + QMutableListIterator it( m_highlightQuads ); + while ( it.hasNext() ) + it.next().transform( matrix ); +} + +void HighlightAnnotationPrivate::baseTransform( const QTransform &matrix ) +{ + AnnotationPrivate::baseTransform( matrix ); + + QMutableListIterator it( m_highlightQuads ); + while ( it.hasNext() ) + it.next().transform( matrix ); +} + +void HighlightAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + m_highlightQuads.clear(); + + // loop through the whole children looking for a 'hl' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "hl" ) + continue; + + // parse the attributes + if ( e.hasAttribute( "type" ) ) + m_highlightType = (HighlightAnnotation::HighlightType)e.attribute( "type" ).toInt(); + + // parse all 'quad' subnodes + QDomNode quadNode = e.firstChild(); + for ( ; quadNode.isElement(); quadNode = quadNode.nextSibling() ) + { + QDomElement qe = quadNode.toElement(); + if ( qe.tagName() != "quad" ) + continue; + + HighlightAnnotation::Quad q; + q.setPoint( NormalizedPoint( qe.attribute( "ax", "0.0" ).toDouble(), qe.attribute( "ay", "0.0" ).toDouble() ), 0 ); + q.setPoint( NormalizedPoint( qe.attribute( "bx", "0.0" ).toDouble(), qe.attribute( "by", "0.0" ).toDouble() ), 1 ); + q.setPoint( NormalizedPoint( qe.attribute( "cx", "0.0" ).toDouble(), qe.attribute( "cy", "0.0" ).toDouble() ), 2 ); + q.setPoint( NormalizedPoint( qe.attribute( "dx", "0.0" ).toDouble(), qe.attribute( "dy", "0.0" ).toDouble() ), 3 ); + q.setCapStart( qe.hasAttribute( "start" ) ); + q.setCapEnd( qe.hasAttribute( "end" ) ); + q.setFeather( qe.attribute( "feather", "0.1" ).toDouble() ); + + q.transform( QTransform() ); + + m_highlightQuads.append( q ); + } + + // loading complete + break; + } +} + +AnnotationPrivate* HighlightAnnotationPrivate::getNewAnnotationPrivate() +{ + return new HighlightAnnotationPrivate(); +} + +double HighlightAnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) +{ + NormalizedPoint point( x, y ); + double outsideDistance = DBL_MAX; + foreach ( const HighlightAnnotation::Quad& quad, m_highlightQuads ) + { + QLinkedList pathPoints; + + //first, we check if the point is within the area described by the 4 quads + //this is the case, if the point is always on one side of each segments delimiting the polygon: + pathPoints << quad.transformedPoint( 0 ); + int directionVote = 0; + for ( int i = 1; i < 5; ++i ) + { + NormalizedPoint thisPoint = quad.transformedPoint( i % 4 ); + directionVote += (isLeftOfVector( pathPoints.back(), thisPoint, point )) ? 1 : -1; + pathPoints << thisPoint; + } + if ( abs( directionVote ) == 4 ) + return 0; + + //if that's not the case, we treat the outline as path and simply determine + //the distance from the path to the point + const double thisOutsideDistance = ::distanceSqr( x, y, xScale, yScale, pathPoints ); + if ( thisOutsideDistance < outsideDistance ) + outsideDistance = thisOutsideDistance; + } + + return outsideDistance; +} + +/** StampAnnotation [Annotation] */ + +class Okular::StampAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + StampAnnotationPrivate() + : AnnotationPrivate(), m_stampIconName( "Draft" ) + { + } + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + + QString m_stampIconName; +}; + +StampAnnotation::StampAnnotation() + : Annotation( *new StampAnnotationPrivate() ) +{ +} + +StampAnnotation::StampAnnotation( const QDomNode & node ) + : Annotation( *new StampAnnotationPrivate(), node ) +{ +} + +StampAnnotation::~StampAnnotation() +{ +} + +void StampAnnotation::setStampIconName( const QString &name ) +{ + Q_D( StampAnnotation ); + d->m_stampIconName = name; +} + +QString StampAnnotation::stampIconName() const +{ + Q_D( const StampAnnotation ); + return d->m_stampIconName; +} + +Annotation::SubType StampAnnotation::subType() const +{ + return AStamp; +} + +void StampAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + Q_D( const StampAnnotation ); + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [stamp] element + QDomElement stampElement = document.createElement( "stamp" ); + node.appendChild( stampElement ); + + // append the optional attributes + if ( d->m_stampIconName != "Draft" ) + stampElement.setAttribute( "icon", d->m_stampIconName ); +} + +void StampAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + + // loop through the whole children looking for a 'stamp' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "stamp" ) + continue; + + // parse the attributes + if ( e.hasAttribute( "icon" ) ) + m_stampIconName = e.attribute( "icon" ); + + // loading complete + break; + } +} + +AnnotationPrivate* StampAnnotationPrivate::getNewAnnotationPrivate() +{ + return new StampAnnotationPrivate(); +} + +/** InkAnnotation [Annotation] */ + +class Okular::InkAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + InkAnnotationPrivate() + : AnnotationPrivate() + { + } + + virtual void transform( const QTransform &matrix ); + virtual void baseTransform( const QTransform &matrix ); + virtual void resetTransformation(); + virtual double distanceSqr( double x, double y, double xScale, double yScale ); + virtual void translate( const NormalizedPoint &coord ); + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + + QList< QLinkedList > m_inkPaths; + QList< QLinkedList > m_transformedInkPaths; +}; + +InkAnnotation::InkAnnotation() + : Annotation( *new InkAnnotationPrivate() ) +{ +} + +InkAnnotation::InkAnnotation( const QDomNode & node ) + : Annotation( *new InkAnnotationPrivate(), node ) +{ +} + +InkAnnotation::~InkAnnotation() +{ +} + +void InkAnnotation::setInkPaths( const QList< QLinkedList > &paths ) +{ + Q_D( InkAnnotation ); + d->m_inkPaths = paths; +} + +QList< QLinkedList > InkAnnotation::inkPaths() const +{ + Q_D( const InkAnnotation ); + return d->m_inkPaths; +} + +QList< QLinkedList > InkAnnotation::transformedInkPaths() const +{ + Q_D( const InkAnnotation ); + return d->m_transformedInkPaths; +} + +Annotation::SubType InkAnnotation::subType() const +{ + return AInk; +} + +void InkAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + Q_D( const InkAnnotation ); + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [ink] element + QDomElement inkElement = document.createElement( "ink" ); + node.appendChild( inkElement ); + + // append the optional attributes + if ( d->m_inkPaths.count() < 1 ) + return; + + QList< QLinkedList >::const_iterator pIt = d->m_inkPaths.begin(), pEnd = d->m_inkPaths.end(); + for ( ; pIt != pEnd; ++pIt ) + { + QDomElement pathElement = document.createElement( "path" ); + inkElement.appendChild( pathElement ); + const QLinkedList & path = *pIt; + QLinkedList::const_iterator iIt = path.begin(), iEnd = path.end(); + for ( ; iIt != iEnd; ++iIt ) + { + const NormalizedPoint & point = *iIt; + QDomElement pointElement = document.createElement( "point" ); + pathElement.appendChild( pointElement ); + pointElement.setAttribute( "x", QString::number( point.x ) ); + pointElement.setAttribute( "y", QString::number( point.y ) ); + } + } +} + +double InkAnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) +{ + double distance = DBL_MAX; + foreach ( const QLinkedList& path, m_transformedInkPaths ) + { + const double thisDistance = ::distanceSqr( x, y, xScale, yScale, path ); + if ( thisDistance < distance ) + distance = thisDistance; + } + return strokeDistance( distance, m_style.width() * xScale / ( m_page->m_width * 2 ) ); +} + +void InkAnnotationPrivate::transform( const QTransform &matrix ) +{ + AnnotationPrivate::transform( matrix ); + + for ( int i = 0; i < m_transformedInkPaths.count(); ++i ) + { + QMutableLinkedListIterator it( m_transformedInkPaths[ i ] ); + while ( it.hasNext() ) + it.next().transform( matrix ); + } +} + +void InkAnnotationPrivate::baseTransform( const QTransform &matrix ) +{ + AnnotationPrivate::baseTransform( matrix ); + + for ( int i = 0; i < m_inkPaths.count(); ++i ) + { + QMutableLinkedListIterator it( m_inkPaths[ i ] ); + while ( it.hasNext() ) + it.next().transform( matrix ); + } +} + +void InkAnnotationPrivate::resetTransformation() +{ + AnnotationPrivate::resetTransformation(); + + m_transformedInkPaths = m_inkPaths; +} + +void InkAnnotationPrivate::translate( const NormalizedPoint &coord ) +{ + AnnotationPrivate::translate( coord ); + + for ( int i = 0; i < m_inkPaths.count(); ++i ) + { + QMutableLinkedListIterator it( m_inkPaths[ i ] ); + while ( it.hasNext() ) + { + NormalizedPoint& p = it.next(); + p.x = p.x + coord.x; + p.y = p.y + coord.y; + } + } +} + +void InkAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + m_inkPaths.clear(); + + // loop through the whole children looking for a 'ink' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "ink" ) + continue; + + // parse the 'path' subnodes + QDomNode pathNode = e.firstChild(); + while ( pathNode.isElement() ) + { + QDomElement pathElement = pathNode.toElement(); + pathNode = pathNode.nextSibling(); + + if ( pathElement.tagName() != "path" ) + continue; + + // build each path parsing 'point' subnodes + QLinkedList path; + QDomNode pointNode = pathElement.firstChild(); + while ( pointNode.isElement() ) + { + QDomElement pointElement = pointNode.toElement(); + pointNode = pointNode.nextSibling(); + + if ( pointElement.tagName() != "point" ) + continue; + + NormalizedPoint p; + p.x = pointElement.attribute( "x", "0.0" ).toDouble(); + p.y = pointElement.attribute( "y", "0.0" ).toDouble(); + path.append( p ); + } + + // add the path to the path list if it contains at least 2 nodes + if ( path.count() >= 2 ) + m_inkPaths.append( path ); + } + + // loading complete + break; + } + + m_transformedInkPaths = m_inkPaths; +} + +AnnotationPrivate* InkAnnotationPrivate::getNewAnnotationPrivate() +{ + return new InkAnnotationPrivate(); +} + +/** CaretAnnotation [Annotation] */ + +class Okular::CaretAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + CaretAnnotationPrivate() + : AnnotationPrivate(), m_symbol( CaretAnnotation::None ) + { + } + + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + + CaretAnnotation::CaretSymbol m_symbol; +}; + +static QString caretSymbolToString( CaretAnnotation::CaretSymbol symbol ) +{ + switch ( symbol ) + { + case CaretAnnotation::None: + return QString::fromLatin1( "None" ); + case CaretAnnotation::P: + return QString::fromLatin1( "P" ); + } + return QString(); +} + +static CaretAnnotation::CaretSymbol caretSymbolFromString( const QString &symbol ) +{ + if ( symbol == QLatin1String( "None" ) ) + return CaretAnnotation::None; + else if ( symbol == QLatin1String( "P" ) ) + return CaretAnnotation::P; + return CaretAnnotation::None; +} + +void CaretAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + + // loop through the whole children looking for a 'caret' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "caret" ) + continue; + + // parse the attributes + if ( e.hasAttribute( "symbol" ) ) + m_symbol = caretSymbolFromString( e.attribute( "symbol" ) ); + + // loading complete + break; + } +} + +AnnotationPrivate* CaretAnnotationPrivate::getNewAnnotationPrivate() +{ + return new CaretAnnotationPrivate(); +} + +CaretAnnotation::CaretAnnotation() + : Annotation( *new CaretAnnotationPrivate() ) +{ +} + +CaretAnnotation::CaretAnnotation( const QDomNode & node ) + : Annotation( *new CaretAnnotationPrivate(), node ) +{ +} + +CaretAnnotation::~CaretAnnotation() +{ +} + +void CaretAnnotation::setCaretSymbol( CaretAnnotation::CaretSymbol symbol ) +{ + Q_D( CaretAnnotation ); + d->m_symbol = symbol; +} + +CaretAnnotation::CaretSymbol CaretAnnotation::caretSymbol() const +{ + Q_D( const CaretAnnotation ); + return d->m_symbol; +} + +Annotation::SubType CaretAnnotation::subType() const +{ + return ACaret; +} + +void CaretAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + Q_D( const CaretAnnotation ); + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [caret] element + QDomElement caretElement = document.createElement( "caret" ); + node.appendChild( caretElement ); + + // append the optional attributes + if ( d->m_symbol != None ) + caretElement.setAttribute( "symbol", caretSymbolToString( d->m_symbol ) ); +} + +/** FileAttachmentAnnotation [Annotation] */ + +class Okular::FileAttachmentAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + FileAttachmentAnnotationPrivate() + : AnnotationPrivate(), icon( "PushPin" ), embfile( 0 ) + { + } + ~FileAttachmentAnnotationPrivate() + { + delete embfile; + } + + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + + // data fields + QString icon; + EmbeddedFile *embfile; +}; + +void FileAttachmentAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + + // loop through the whole children looking for a 'fileattachment' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "fileattachment" ) + continue; + + // loading complete + break; + } +} + +AnnotationPrivate* FileAttachmentAnnotationPrivate::getNewAnnotationPrivate() +{ + return new FileAttachmentAnnotationPrivate(); +} + +FileAttachmentAnnotation::FileAttachmentAnnotation() + : Annotation( *new FileAttachmentAnnotationPrivate() ) +{ +} + +FileAttachmentAnnotation::FileAttachmentAnnotation( const QDomNode & node ) + : Annotation( *new FileAttachmentAnnotationPrivate(), node ) +{ +} + +FileAttachmentAnnotation::~FileAttachmentAnnotation() +{ +} + +void FileAttachmentAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [fileattachment] element + QDomElement fileAttachmentElement = document.createElement( "fileattachment" ); + node.appendChild( fileAttachmentElement ); +} + +Annotation::SubType FileAttachmentAnnotation::subType() const +{ + return AFileAttachment; +} + +QString FileAttachmentAnnotation::fileIconName() const +{ + Q_D( const FileAttachmentAnnotation ); + return d->icon; +} + +void FileAttachmentAnnotation::setFileIconName( const QString &icon ) +{ + Q_D( FileAttachmentAnnotation ); + d->icon = icon; +} + +EmbeddedFile* FileAttachmentAnnotation::embeddedFile() const +{ + Q_D( const FileAttachmentAnnotation ); + return d->embfile; +} + +void FileAttachmentAnnotation::setEmbeddedFile( EmbeddedFile *ef ) +{ + Q_D( FileAttachmentAnnotation ); + d->embfile = ef; +} + + +/** SoundAnnotation [Annotation] */ + +class Okular::SoundAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + SoundAnnotationPrivate() + : AnnotationPrivate(), icon( "Speaker" ), sound( 0 ) + { + } + ~SoundAnnotationPrivate() + { + delete sound; + } + + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + + // data fields + QString icon; + Sound *sound; +}; + +void SoundAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + + // loop through the whole children looking for a 'sound' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "sound" ) + continue; + + // loading complete + break; + } +} + +AnnotationPrivate* SoundAnnotationPrivate::getNewAnnotationPrivate() +{ + return new SoundAnnotationPrivate(); +} + +SoundAnnotation::SoundAnnotation() + : Annotation( *new SoundAnnotationPrivate() ) +{ +} + +SoundAnnotation::SoundAnnotation( const QDomNode & node ) + : Annotation( *new SoundAnnotationPrivate(), node ) +{ +} + +SoundAnnotation::~SoundAnnotation() +{ +} + +void SoundAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [sound] element + QDomElement soundElement = document.createElement( "sound" ); + node.appendChild( soundElement ); +} + +Annotation::SubType SoundAnnotation::subType() const +{ + return ASound; +} + +QString SoundAnnotation::soundIconName() const +{ + Q_D( const SoundAnnotation ); + return d->icon; +} + +void SoundAnnotation::setSoundIconName( const QString &icon ) +{ + Q_D( SoundAnnotation ); + d->icon = icon; +} + +Sound* SoundAnnotation::sound() const +{ + Q_D( const SoundAnnotation ); + return d->sound; +} + +void SoundAnnotation::setSound( Sound *s ) +{ + Q_D( SoundAnnotation ); + d->sound = s; +} + +/** MovieAnnotation [Annotation] */ + +class Okular::MovieAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + MovieAnnotationPrivate() + : AnnotationPrivate(), movie( 0 ) + { + } + ~MovieAnnotationPrivate() + { + delete movie; + } + + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + + // data fields + Movie *movie; +}; + +void MovieAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + + // loop through the whole children looking for a 'movie' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "movie" ) + continue; + + // loading complete + break; + } +} + +AnnotationPrivate* MovieAnnotationPrivate::getNewAnnotationPrivate() +{ + return new MovieAnnotationPrivate(); +} + +MovieAnnotation::MovieAnnotation() + : Annotation( *new MovieAnnotationPrivate() ) +{ +} + +MovieAnnotation::MovieAnnotation( const QDomNode & node ) + : Annotation( *new MovieAnnotationPrivate(), node ) +{ +} + +MovieAnnotation::~MovieAnnotation() +{ +} + +void MovieAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [movie] element + QDomElement movieElement = document.createElement( "movie" ); + node.appendChild( movieElement ); +} + +Annotation::SubType MovieAnnotation::subType() const +{ + return AMovie; +} + +Movie* MovieAnnotation::movie() const +{ + Q_D( const MovieAnnotation ); + return d->movie; +} + +void MovieAnnotation::setMovie( Movie *movie ) +{ + Q_D( MovieAnnotation ); + d->movie = movie; +} + +/** ScreenAnnotation [Annotation] */ + +class Okular::ScreenAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + ScreenAnnotationPrivate(); + ~ScreenAnnotationPrivate(); + + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + + Okular::Action* m_action; + QMap< Okular::Annotation::AdditionalActionType, Okular::Action* > m_additionalActions; +}; + +ScreenAnnotationPrivate::ScreenAnnotationPrivate() + : m_action( 0 ) +{ +} + +ScreenAnnotationPrivate::~ScreenAnnotationPrivate() +{ + delete m_action; + qDeleteAll( m_additionalActions ); +} + +void ScreenAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + + // loop through the whole children looking for a 'screen' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "screen" ) + continue; + + // loading complete + break; + } +} + +AnnotationPrivate* ScreenAnnotationPrivate::getNewAnnotationPrivate() +{ + return new ScreenAnnotationPrivate(); +} + +ScreenAnnotation::ScreenAnnotation() + : Annotation( *new ScreenAnnotationPrivate() ) +{ +} + +ScreenAnnotation::ScreenAnnotation( const QDomNode & node ) + : Annotation( *new ScreenAnnotationPrivate(), node ) +{ +} + +ScreenAnnotation::~ScreenAnnotation() +{ +} + +void ScreenAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [screen] element + QDomElement movieElement = document.createElement( "screen" ); + node.appendChild( movieElement ); +} + +Annotation::SubType ScreenAnnotation::subType() const +{ + return AScreen; +} + +void ScreenAnnotation::setAdditionalAction( AdditionalActionType type, Action *action ) +{ + Q_D( ScreenAnnotation ); + if ( d->m_additionalActions.contains( type ) ) + delete d->m_additionalActions.value( type ); + + d->m_additionalActions.insert( type, action ); +} + +Action* ScreenAnnotation::additionalAction( AdditionalActionType type ) const +{ + Q_D( const ScreenAnnotation ); + if ( !d->m_additionalActions.contains( type ) ) + return 0; + else + return d->m_additionalActions.value( type ); +} + +void ScreenAnnotation::setAction( Action *action ) +{ + Q_D( ScreenAnnotation ); + + delete d->m_action; + d->m_action = action; +} + +Action* ScreenAnnotation::action() const +{ + Q_D( const ScreenAnnotation ); + return d->m_action; +} + +/** WidgetAnnotation [Annotation] */ + +class Okular::WidgetAnnotationPrivate : public Okular::AnnotationPrivate +{ + public: + ~WidgetAnnotationPrivate(); + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate(); + + QMap< Okular::Annotation::AdditionalActionType, Okular::Action* > m_additionalActions; +}; + +WidgetAnnotationPrivate::~WidgetAnnotationPrivate() +{ + qDeleteAll( m_additionalActions ); +} + +void WidgetAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) +{ + Okular::AnnotationPrivate::setAnnotationProperties(node); + + // loop through the whole children looking for a 'widget' element + QDomNode subNode = node.firstChild(); + while( subNode.isElement() ) + { + QDomElement e = subNode.toElement(); + subNode = subNode.nextSibling(); + if ( e.tagName() != "widget" ) + continue; + + // loading complete + break; + } +} + +AnnotationPrivate* WidgetAnnotationPrivate::getNewAnnotationPrivate() +{ + return new WidgetAnnotationPrivate(); +} + +WidgetAnnotation::WidgetAnnotation() + : Annotation( *new WidgetAnnotationPrivate() ) +{ +} + +WidgetAnnotation::WidgetAnnotation( const QDomNode & node ) + : Annotation( *new WidgetAnnotationPrivate, node ) +{ +} + +WidgetAnnotation::~WidgetAnnotation() +{ +} + +void WidgetAnnotation::store( QDomNode & node, QDomDocument & document ) const +{ + // recurse to parent objects storing properties + Annotation::store( node, document ); + + // create [widget] element + QDomElement movieElement = document.createElement( "widget" ); + node.appendChild( movieElement ); +} + +Annotation::SubType WidgetAnnotation::subType() const +{ + return AWidget; +} + +void WidgetAnnotation::setAdditionalAction( AdditionalActionType type, Action *action ) +{ + Q_D( WidgetAnnotation ); + if ( d->m_additionalActions.contains( type ) ) + delete d->m_additionalActions.value( type ); + + d->m_additionalActions.insert( type, action ); +} + +Action* WidgetAnnotation::additionalAction( AdditionalActionType type ) const +{ + Q_D( const WidgetAnnotation ); + if ( !d->m_additionalActions.contains( type ) ) + return 0; + else + return d->m_additionalActions.value( type ); +} diff --git a/okular/core/annotations.h b/okular/core/annotations.h new file mode 100644 index 00000000..4f107440 --- /dev/null +++ b/okular/core/annotations.h @@ -0,0 +1,1653 @@ +/*************************************************************************** + * Copyright (C) 2005 by Enrico Ros * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_ANNOTATIONS_H_ +#define _OKULAR_ANNOTATIONS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "okular_export.h" +#include "area.h" + +namespace Okular { + +class Action; +class Annotation; +class AnnotationObjectRect; +class AnnotationPrivate; +class Document; +class EmbeddedFile; +class Page; +class PagePrivate; +class Sound; +class Movie; +class TextAnnotationPrivate; +class LineAnnotationPrivate; +class GeomAnnotationPrivate; +class HighlightAnnotationPrivate; +class StampAnnotationPrivate; +class InkAnnotationPrivate; +class CaretAnnotationPrivate; +class FileAttachmentAnnotationPrivate; +class SoundAnnotationPrivate; +class MovieAnnotationPrivate; +class ScreenAnnotationPrivate; +class WidgetAnnotationPrivate; + +/** + * @short Helper class for (recursive) annotation retrieval/storage. + */ +class OKULAR_EXPORT AnnotationUtils +{ + public: + /** + * Restore an annotation (with revisions if needed) from the dom @p element. + * + * Returns a pointer to the complete annotation or 0 if element is invalid. + */ + static Annotation * createAnnotation( const QDomElement & element ); + + /** + * Saves the @p annotation as a child of @p element taking + * care of saving all revisions if it has any. + */ + static void storeAnnotation( const Annotation * annotation, + QDomElement & element, QDomDocument & document ); + + /** + * Returns the child element with the given @p name from the direct + * children of @p parentNode or a null element if not found. + */ + static QDomElement findChildElement( const QDomNode & parentNode, + const QString & name ); + + /** + * Returns the geometry of the given @p annotation scaled by + * @p scaleX and @p scaleY. + */ + static QRect annotationGeometry( const Annotation * annotation, + double scaleX, double scaleY ); +}; + +/** + * @short Annotation struct holds properties shared by all annotations. + * + * An Annotation is an object (text note, highlight, sound, popup window, ..) + * contained by a Page in the document. + */ +class OKULAR_EXPORT Annotation +{ + /// @cond PRIVATE + friend class AnnotationObjectRect; + friend class Document; + friend class DocumentPrivate; + friend class ObjectRect; + friend class Page; + friend class PagePrivate; + /// @endcond + + public: + /** + * Describes the type of annotation as defined in PDF standard. + */ + enum SubType + { + AText = 1, ///< A textual annotation + ALine = 2, ///< A line annotation + AGeom = 3, ///< A geometrical annotation + AHighlight = 4, ///< A highlight annotation + AStamp = 5, ///< A stamp annotation + AInk = 6, ///< An ink annotation + ACaret = 8, ///< A caret annotation + AFileAttachment = 9, ///< A file attachment annotation + ASound = 10, ///< A sound annotation + AMovie = 11, ///< A movie annotation + AScreen = 12, ///< A screen annotation + AWidget = 13, ///< A widget annotation + A_BASE = 0 ///< The annotation base class + }; + + /** + * Describes additional properties of an annotation. + */ + enum Flag + { + Hidden = 1, ///< Is not shown in the document + FixedSize = 2, ///< Has a fixed size + FixedRotation = 4, ///< Has a fixed rotation + DenyPrint = 8, ///< Cannot be printed + DenyWrite = 16, ///< Cannot be changed + DenyDelete = 32, ///< Cannot be deleted + ToggleHidingOnMouse = 64, ///< Can be hidden/shown by mouse click + External = 128, ///< Is stored external + ExternallyDrawn = 256, ///< Is drawn externally (by the generator which provided it) @since 0.10 (KDE 4.4) + BeingMoved = 512 ///< Is being moved (mouse drag and drop). If ExternallyDrawn, the generator must not draw it @since 0.15 (KDE 4.9) + }; + + /** + * Describes possible line styles for @see ALine annotation. + */ + enum LineStyle + { + Solid = 1, ///< A solid line + Dashed = 2, ///< A dashed line + Beveled = 4, ///< A beveled line + Inset = 8, ///< A inseted line + Underline = 16 ///< An underline + }; + + /** + * Describes possible line effects for @see ALine annotation. + */ + enum LineEffect + { + NoEffect = 1, ///< No effect + Cloudy = 2 ///< The cloudy effect + }; + + /** + * Describes the scope of revision information. + */ + enum RevisionScope + { + Reply = 1, ///< Belongs to a reply + Group = 2, ///< Belongs to a group + Delete = 4 ///< Belongs to a deleted paragraph + }; + + /** + * Describes the type of revision information. + */ + enum RevisionType + { + None = 1, ///< Not specified + Marked = 2, ///< Is marked + Unmarked = 4, ///< Is unmarked + Accepted = 8, ///< Has been accepted + Rejected = 16, ///< Was rejected + Cancelled = 32, ///< Has been cancelled + Completed = 64 ///< Has been completed + }; + + /** + * Describes the type of additional actions. + * + * @since 0.16 (KDE 4.10) + */ + enum AdditionalActionType + { + PageOpening, ///< Performed when the page containing the annotation is opened. + PageClosing ///< Performed when the page containing the annotation is closed. + }; + + /** + * A function to be called when the annotation is destroyed. + * + * @warning the function must *not* call any virtual function, + * nor subcast. + * + * @since 0.7 (KDE 4.1) + */ + typedef void ( * DisposeDataFunction )( const Okular::Annotation * ); + + /** + * Destroys the annotation. + */ + virtual ~Annotation(); + + /** + * Sets the @p author of the annotation. + */ + void setAuthor( const QString &author ); + + /** + * Returns the author of the annotation. + */ + QString author() const; + + /** + * Sets the @p contents of the annotation. + */ + void setContents( const QString &contents ); + + /** + * Returns the contents of the annotation. + */ + QString contents() const; + + /** + * Sets the unique @p name of the annotation. + */ + void setUniqueName( const QString &name ); + + /** + * Returns the unique name of the annotation. + */ + QString uniqueName() const; + + /** + * Sets the last modification @p date of the annotation. + * + * The date must be before or equal to QDateTime::currentDateTime() + */ + void setModificationDate( const QDateTime &date ); + + /** + * Returns the last modification date of the annotation. + */ + QDateTime modificationDate() const; + + /** + * Sets the creation @p date of the annotation. + * + * The date must be before or equal to @see modificationDate() + */ + void setCreationDate( const QDateTime &date ); + + /** + * Returns the creation date of the annotation. + */ + QDateTime creationDate() const; + + /** + * Sets the @p flags of the annotation. + * @see @ref Flag + */ + void setFlags( int flags ); + + /** + * Returns the flags of the annotation. + * @see @ref Flag + */ + int flags() const; + + /** + * Sets the bounding @p rectangle of the annotation. + */ + void setBoundingRectangle( const NormalizedRect &rectangle ); + + /** + * Returns the bounding rectangle of the annotation. + */ + NormalizedRect boundingRectangle() const; + + /** + * Returns the transformed bounding rectangle of the annotation. + * + * This rectangle must be used when showing annotations on screen + * to have them rotated correctly. + */ + NormalizedRect transformedBoundingRectangle() const; + + /** + * Move the annotation by the specified coordinates. + * + * @see canBeMoved() + */ + void translate( const NormalizedPoint &coord ); + + /** + * The Style class contains all information about style of the + * annotation. + */ + class OKULAR_EXPORT Style + { + public: + /** + * Creates a new style. + */ + Style(); + + /** + * Destroys the style. + */ + ~Style(); + + Style( const Style &other ); + Style& operator=( const Style &other ); + + /** + * Sets the @p color of the style. + */ + void setColor( const QColor &color ); + + /** + * Returns the color of the style. + */ + QColor color() const; + + /** + * Sets the @p opacity of the style. + */ + void setOpacity( double opacity ); + + /** + * Returns the opacity of the style. + */ + double opacity() const; + + /** + * Sets the @p width of the style. + */ + void setWidth( double width ); + + /** + * Returns the width of the style. + */ + double width() const; + + /** + * Sets the line @p style of the style. + */ + void setLineStyle( LineStyle style ); + + /** + * Returns the line style of the style. + */ + LineStyle lineStyle() const; + + /** + * Sets the x-corners of the style. + */ + void setXCorners( double xCorners ); + + /** + * Returns the x-corners of the style. + */ + double xCorners() const; + + /** + * Sets the y-corners of the style. + */ + void setYCorners( double yCorners ); + + /** + * Returns the y-corners of the style. + */ + double yCorners() const; + + /** + * Sets the @p marks of the style. + */ + void setMarks( int marks ); + + /** + * Returns the marks of the style. + */ + int marks() const; + + /** + * Sets the @p spaces of the style. + */ + void setSpaces( int spaces ); + + /** + * Returns the spaces of the style. + */ + int spaces() const; + + /** + * Sets the line @p effect of the style. + */ + void setLineEffect( LineEffect effect ); + + /** + * Returns the line effect of the style. + */ + LineEffect lineEffect() const; + + /** + * Sets the effect @p intensity of the style. + */ + void setEffectIntensity( double intensity ); + + /** + * Returns the effect intensity of the style. + */ + double effectIntensity() const; + + private: + class Private; + Private* const d; + }; + + /** + * Returns a reference to the style object of the annotation. + */ + Style & style(); + + /** + * Returns a const reference to the style object of the annotation. + */ + const Style & style() const; + + /** + * The Window class contains all information about the popup window + * of the annotation that is used to edit the content and properties. + */ + class OKULAR_EXPORT Window + { + public: + /** + * Creates a new window. + */ + Window(); + + /** + * Destroys the window. + */ + ~Window(); + + Window( const Window &other ); + Window& operator=( const Window &other ); + + /** + * Sets the @p flags of the window. + */ + void setFlags( int flags ); + + /** + * Returns the flags of the window. + */ + int flags() const; + + /** + * Sets the top-left @p point of the window. + */ + void setTopLeft( const NormalizedPoint &point ); + + /** + * Returns the top-left point of the window. + */ + NormalizedPoint topLeft() const; + + /** + * Sets the @p width of the window. + */ + void setWidth( int width ); + + /** + * Returns the width of the window. + */ + int width() const; + + /** + * Sets the @p height of the window. + */ + void setHeight( int height ); + + /** + * Returns the height of the window. + */ + int height() const; + + /** + * Sets the @p title of the window. + */ + void setTitle( const QString &title ); + + /** + * Returns the title of the window. + */ + QString title() const; + + /** + * Sets the @p summary of the window. + */ + void setSummary( const QString &summary ); + + /** + * Returns the summary of the window. + */ + QString summary() const; + + private: + class Private; + Private* const d; + }; + + /** + * Returns a reference to the window object of the annotation. + */ + Window & window(); + + /** + * Returns a const reference to the window object of the annotation. + */ + const Window & window() const; + + /** + * The Revision class contains all information about the revision + * of the annotation. + */ + class Revision + { + public: + /** + * Creates a new revision. + */ + Revision(); + + /** + * Destroys the revision. + */ + ~Revision(); + + Revision( const Revision &other ); + Revision& operator=( const Revision &other ); + + /** + * Sets the @p annotation the revision belongs to. + */ + void setAnnotation( Annotation *annotation ); + + /** + * Returns the annotation the revision belongs to. + */ + Annotation *annotation() const; + + /** + * Sets the @p scope of the revision. + * @see RevisionScope + */ + void setScope( RevisionScope scope ); + + /** + * Returns the scope of the revision. + */ + RevisionScope scope() const; + + /** + * Sets the @p type of the revision. + * @see RevisionType + */ + void setType( RevisionType type ); + + /** + * Returns the type of the revision. + */ + RevisionType type() const; + + private: + class Private; + Private* const d; + }; + + /** + * Returns a reference to the revision list of the annotation. + */ + QLinkedList< Revision > & revisions(); + + /** + * Returns a reference to the revision list of the annotation. + */ + const QLinkedList< Revision > & revisions() const; + + /** + * Sets the "native" @p id of the annotation. + * + * This is for use of the Generator, that can optionally store an + * handle (a pointer, an identifier, etc) of the "native" annotation + * object, if any. + * + * @note Okular makes no use of this + * + * @since 0.7 (KDE 4.1) + */ + void setNativeId( const QVariant &id ); + + /** + * Returns the "native" id of the annotation. + * + * @since 0.7 (KDE 4.1) + */ + QVariant nativeId() const; + + /** + * Sets a function to be called when the annotation is destroyed. + * + * @warning the function must *not* call any virtual function, + * nor subcast. + * + * @since 0.7 (KDE 4.1) + */ + void setDisposeDataFunction( DisposeDataFunction func ); + + /** + * Returns whether the annotation can be moved. + * + * @since 0.7 (KDE 4.1) + */ + bool canBeMoved() const; + + /** + * Returns whether the annotation dialog should be open after creation of the annotation or not + * + * @since 0.13 (KDE 4.7) + */ + bool openDialogAfterCreation() const; + + /** + * Returns the sub type of the annotation. + */ + virtual SubType subType() const = 0; + + /** + * Stores the annotation as xml in @p document under the given parent @p node. + */ + virtual void store( QDomNode & node, QDomDocument & document ) const; + + /** + * Retrieve the QDomNode representing this annotation's properties + + * @since 0.17 (KDE 4.11) + */ + QDomNode getAnnotationPropertiesDomNode() const; + + /** + * Sets annotations internal properties according to the contents of @p node + * + * @since 0.17 (KDE 4.11) + */ + void setAnnotationProperties( const QDomNode & node ); + + protected: + /// @cond PRIVATE + Annotation( AnnotationPrivate &dd ); + Annotation( AnnotationPrivate &dd, const QDomNode &description ); + Q_DECLARE_PRIVATE( Annotation ) + AnnotationPrivate *d_ptr; + /// @endcond + + private: + Q_DISABLE_COPY( Annotation ) +}; + +/** + * @short Native annotation interface + * + * Generators can subclass it to provide native annotation support. + * Generators can use Annotation::setNativeId to store per-annotation data. + * + * @since 0.15 (KDE 4.9) + */ +class OKULAR_EXPORT AnnotationProxy +{ + public: + enum Capability + { + Addition, ///< Generator can create native annotations + Modification, ///< Generator can edit native annotations + Removal ///< Generator can remove native annotations + }; + + /** + * Destroys the annotation proxy. + */ + virtual ~AnnotationProxy(); + + /** + * Query for the supported capabilities. + */ + virtual bool supports( Capability capability ) const = 0; + + /** + * Called when a new @p annotation is added to a @p page. + * + * @note Only called if supports(Addition) == true + */ + virtual void notifyAddition( Annotation *annotation, int page ) = 0; + + /** + * Called after an existing @p annotation at a given @p page is modified. + * + * Generator can call @p annotation getters to get the new values. + * @p appearanceChanged tells if a non-visible property was modifed + * + * @note Only called if supports(Modification) == true + */ + virtual void notifyModification( const Annotation *annotation, int page, bool appearanceChanged ) = 0; + + /** + * Called when an existing @p annotation at a given @p page is removed. + * + * @note Only called if supports(Removal) == true + */ + virtual void notifyRemoval( Annotation *annotation, int page ) = 0; +}; + +class OKULAR_EXPORT TextAnnotation : public Annotation +{ + public: + /** + * Describes the type of the text. + */ + enum TextType + { + Linked, ///< The annotation is linked to a text + InPlace ///< The annotation is located next to the text + }; + + /** + * Describes the style of the text. + */ + enum InplaceIntent + { + Unknown, ///< Unknown style + Callout, ///< Callout style + TypeWriter ///< Type writer style + }; + + /** + * Creates a new text annotation. + */ + TextAnnotation(); + + /** + * Creates a new text annotation from the xml @p description + */ + TextAnnotation( const QDomNode &description ); + + /** + * Destroys the text annotation. + */ + ~TextAnnotation(); + + /** + * Sets the text @p type of the text annotation. + * @see TextType + */ + void setTextType( TextType type ); + + /** + * Returns the text type of the text annotation. + */ + TextType textType() const; + + /** + * Sets the @p icon of the text annotation. + */ + void setTextIcon( const QString &icon ); + + /** + * Returns the icon of the text annotation. + */ + QString textIcon() const; + + /** + * Sets the @p font of the text annotation. + */ + void setTextFont( const QFont &font ); + + /** + * Returns the font of the text annotation. + */ + QFont textFont() const; + + /** + * Sets the inplace @p alignment of the text annotation. + */ + void setInplaceAlignment( int alignment ); + + /** + * Returns the inplace alignment of the text annotation. + */ + int inplaceAlignment() const; + + /** + * Sets the inplace callout @p point at @p index. + * + * @p index must be between 0 and 2. + */ + void setInplaceCallout( const NormalizedPoint &point, int index ); + + /** + * Returns the inplace callout point for @p index. + * + * @p index must be between 0 and 2. + */ + NormalizedPoint inplaceCallout( int index ) const; + + /** + * Returns the transformed (e.g. rotated) inplace callout point for @p index. + * + * @p index must be between 0 and 2. + */ + NormalizedPoint transformedInplaceCallout( int index ) const; + + /** + * Returns the inplace @p intent of the text annotation. + * @see InplaceIntent + */ + void setInplaceIntent( InplaceIntent intent ); + + /** + * Returns the inplace intent of the text annotation. + */ + InplaceIntent inplaceIntent() const; + + /** + * Returns the sub type of the text annotation. + */ + SubType subType() const; + + /** + * Stores the text annotation as xml in @p document under the given parent @p node. + */ + void store( QDomNode &node, QDomDocument &document ) const; + + private: + Q_DECLARE_PRIVATE( TextAnnotation ) + Q_DISABLE_COPY( TextAnnotation ) +}; + +class OKULAR_EXPORT LineAnnotation : public Annotation +{ + public: + /** + * Describes the line ending style. + */ + enum TermStyle + { + Square, ///< Using a square + Circle, ///< Using a circle + Diamond, ///< Using a diamond + OpenArrow, ///< Using an open arrow + ClosedArrow, ///< Using a closed arrow + None, ///< No special ending style + Butt, ///< Using a butt ending + ROpenArrow, ///< Using an arrow opened at the right side + RClosedArrow, ///< Using an arrow closed at the right side + Slash ///< Using a slash + }; + + /** + * Describes the line intent. + */ + enum LineIntent + { + Unknown, ///< Unknown intent + Arrow, ///< Arrow intent + Dimension, ///< Dimension intent + PolygonCloud ///< Polygon cloud intent + }; + + /** + * Creates a new line annotation. + */ + LineAnnotation(); + + /** + * Creates a new line annotation from the xml @p description + */ + explicit LineAnnotation( const QDomNode &description ); + + /** + * Destroys the line annotation. + */ + ~LineAnnotation(); + + /** + * Sets the normalized line @p points of the line annotation. + */ + void setLinePoints( const QLinkedList &points ); + + /** + * Returns the normalized line points of the line annotation. + */ + QLinkedList linePoints() const; + + /** + * Returns the transformed (e.g. rotated) normalized line points + * of the line annotation. + */ + QLinkedList transformedLinePoints() const; + + /** + * Sets the line starting @p style of the line annotation. + * @see TermStyle + */ + void setLineStartStyle( TermStyle style ); + + /** + * Returns the line starting style of the line annotation. + */ + TermStyle lineStartStyle() const; + + /** + * Sets the line ending @p style of the line annotation. + * @see TermStyle + */ + void setLineEndStyle( TermStyle style ); + + /** + * Returns the line ending style of the line annotation. + */ + TermStyle lineEndStyle() const; + + /** + * Sets whether the line shall be @p closed. + */ + void setLineClosed( bool closed ); + + /** + * Returns whether the line shall be closed. + */ + bool lineClosed() const; + + /** + * Sets the inner line @p color of the line annotation. + */ + void setLineInnerColor( const QColor &color ); + + /** + * Returns the inner line color of the line annotation. + */ + QColor lineInnerColor() const; + + /** + * Sets the leading forward @p point of the line annotation. + */ + void setLineLeadingForwardPoint( double point ); + + /** + * Returns the leading forward point of the line annotation. + */ + double lineLeadingForwardPoint() const; + + /** + * Sets the leading backward @p point of the line annotation. + */ + void setLineLeadingBackwardPoint( double point ); + + /** + * Returns the leading backward point of the line annotation. + */ + double lineLeadingBackwardPoint() const; + + /** + * Sets whether the caption shall be @p shown. + */ + void setShowCaption( bool shown ); + + /** + * Returns whether the caption shall be shown. + */ + bool showCaption() const; + + /** + * Sets the line @p intent of the line annotation. + * @see LineIntent + */ + void setLineIntent( LineIntent intent ); + + /** + * Returns the line intent of the line annotation. + */ + LineIntent lineIntent() const; + + /** + * Returns the sub type of the line annotation. + */ + SubType subType() const; + + /** + * Stores the line annotation as xml in @p document under the given parent @p node. + */ + void store( QDomNode &node, QDomDocument &document ) const; + + private: + Q_DECLARE_PRIVATE( LineAnnotation ) + Q_DISABLE_COPY( LineAnnotation ) +}; + +class OKULAR_EXPORT GeomAnnotation : public Annotation +{ + public: + // common enums + enum GeomType + { + InscribedSquare, ///< Draw a square + InscribedCircle ///< Draw a circle + }; + + /** + * Creates a new geometrical annotation. + */ + GeomAnnotation(); + + /** + * Creates a new geometrical annotation from the xml @p description + */ + GeomAnnotation( const QDomNode &description ); + + /** + * Destroys the geometrical annotation. + */ + ~GeomAnnotation(); + + /** + * Sets the geometrical @p type of the geometrical annotation. + * @see GeomType + */ + void setGeometricalType( GeomType type ); + + /** + * Returns the geometrical type of the geometrical annotation. + */ + GeomType geometricalType() const; + + /** + * Sets the inner @p color of the geometrical annotation. + */ + void setGeometricalInnerColor( const QColor &color ); + + /** + * Returns the inner color of the geometrical annotation. + */ + QColor geometricalInnerColor() const; + + /** + * Returns the sub type of the geometrical annotation. + */ + SubType subType() const; + + /** + * Stores the geometrical annotation as xml in @p document + * under the given parent @p node. + */ + void store( QDomNode &node, QDomDocument &document ) const; + + private: + Q_DECLARE_PRIVATE( GeomAnnotation ) + Q_DISABLE_COPY( GeomAnnotation ) +}; + +class OKULAR_EXPORT HighlightAnnotation : public Annotation +{ + public: + /** + * Describes the highlighting style of the annotation. + */ + enum HighlightType + { + Highlight, ///< Highlights the text + Squiggly, ///< Squiggles the text + Underline, ///< Underlines the text + StrikeOut ///< Strikes out the text + }; + + /** + * Creates a new highlight annotation. + */ + HighlightAnnotation(); + + /** + * Creates a new highlight annotation from the xml @p description + */ + explicit HighlightAnnotation( const QDomNode &description ); + + /** + * Destroys the highlight annotation. + */ + ~HighlightAnnotation(); + + /** + * Sets the @p type of the highlight annotation. + * @see HighlightType + */ + void setHighlightType( HighlightType type ); + + /** + * Returns the type of the highlight annotation. + */ + HighlightType highlightType() const; + + /** + * The Quad class contains 8 coordinates and style definitions + * which describe a line part of the whole highlight annotation. + */ + class OKULAR_EXPORT Quad + { + public: + /** + * Creates a new quad. + */ + Quad(); + + /** + * Destroys the quad. + */ + ~Quad(); + + Quad( const Quad &other ); + Quad& operator=( const Quad &other ); + + /** + * Sets the normalized @p point at @p index. + * + * @p index must be between 0 and 3. + */ + void setPoint( const NormalizedPoint &point, int index ); + + /** + * Returns the normalized point at @p index. + * + * @p index must be between 0 and 3. + */ + NormalizedPoint point( int index ) const; + + /** + * Returns the transformed (e.g. rotated) normalized point at @p index. + * + * @p index must be between 0 and 3. + */ + NormalizedPoint transformedPoint( int index ) const; + + /** + * Sets whether a cap should be used at the start. + */ + void setCapStart( bool value ); + + /** + * Returns whether a cap should be used at the start. + */ + bool capStart() const; + + /** + * Sets whether a cap should be used at the end. + */ + void setCapEnd( bool value ); + + /** + * Returns whether a cap should be used at the end. + */ + bool capEnd() const; + + /** + * Sets the @p width of the drawing feather. + */ + void setFeather( double width ); + + /** + * Returns the width of the drawing feather. + */ + double feather() const; + + /** + * Transforms the quad coordinates with the transformation defined + * by @p matrix. + */ + void transform( const QTransform &matrix ); + + private: + class Private; + Private* const d; + }; + + /** + * Returns a reference to the quad list of the highlight annotation. + */ + QList< Quad > & highlightQuads(); + + /** + * Returns the sub type of the highlight annotation. + */ + SubType subType() const; + + /** + * Stores the highlight annotation as xml in @p document + * under the given parent @p node. + */ + void store( QDomNode &node, QDomDocument &document ) const; + + private: + Q_DECLARE_PRIVATE( HighlightAnnotation ) + Q_DISABLE_COPY( HighlightAnnotation ) +}; + +class OKULAR_EXPORT StampAnnotation : public Annotation +{ + public: + /** + * Creates a new stamp annotation. + */ + StampAnnotation(); + + /** + * Creates a new stamp annotation from the xml @p description + */ + explicit StampAnnotation( const QDomNode &description ); + + /** + * Destroys the stamp annotation. + */ + ~StampAnnotation(); + + /** + * Sets the @p name of the icon for the stamp annotation. + */ + void setStampIconName( const QString &name ); + + /** + * Returns the name of the icon. + */ + QString stampIconName() const; + + /** + * Returns the sub type of the stamp annotation. + */ + SubType subType() const; + + /** + * Stores the stamp annotation as xml in @p document + * under the given parent @p node. + */ + void store( QDomNode &node, QDomDocument &document ) const; + + private: + Q_DECLARE_PRIVATE( StampAnnotation ) + Q_DISABLE_COPY( StampAnnotation ) +}; + +class OKULAR_EXPORT InkAnnotation : public Annotation +{ + public: + /** + * Creates a new ink annotation. + */ + InkAnnotation(); + + /** + * Creates a new ink annotation from the xml @p description + */ + InkAnnotation( const QDomNode &description ); + + /** + * Destroys the ink annotation. + */ + ~InkAnnotation(); + + /** + * Sets the @p paths of points for the ink annotation. + */ + void setInkPaths( const QList< QLinkedList > &paths ); + + /** + * Returns the paths of points of the ink annotation. + */ + QList< QLinkedList > inkPaths() const; + + /** + * Returns the paths of transformed (e.g. rotated) points of + * the ink annotation. + */ + QList< QLinkedList > transformedInkPaths() const; + + /** + * Returns the sub type of the ink annotation. + */ + SubType subType() const; + + /** + * Stores the ink annotation as xml in @p document + * under the given parent @p node. + */ + void store( QDomNode &node, QDomDocument &document ) const; + + private: + Q_DECLARE_PRIVATE( InkAnnotation ) + Q_DISABLE_COPY( InkAnnotation ) +}; + +class OKULAR_EXPORT CaretAnnotation : public Annotation +{ + public: + /** + * Describes the highlighting style of the annotation. + */ + enum CaretSymbol + { + None, ///< No symbol to be associated with the text + P ///< A 'paragraph' symbol + }; + + /** + * Creates a new caret annotation. + */ + CaretAnnotation(); + + /** + * Creates a new caret annotation from the xml @p description + */ + explicit CaretAnnotation( const QDomNode &description ); + + /** + * Destroys the caret annotation. + */ + ~CaretAnnotation(); + + /** + * Sets the @p symbol for the caret annotation. + */ + void setCaretSymbol( CaretAnnotation::CaretSymbol symbol ); + + /** + * Returns the symbol of the annotation. + */ + CaretAnnotation::CaretSymbol caretSymbol() const; + + /** + * Returns the sub type of the caret annotation. + */ + SubType subType() const; + + /** + * Stores the caret annotation as xml in @p document + * under the given parent @p node. + */ + void store( QDomNode &node, QDomDocument &document ) const; + + private: + Q_DECLARE_PRIVATE( CaretAnnotation ) + Q_DISABLE_COPY( CaretAnnotation ) +}; + +class OKULAR_EXPORT FileAttachmentAnnotation : public Annotation +{ + public: + /** + * Creates a new file attachment annotation. + */ + FileAttachmentAnnotation(); + /** + * Creates a new file attachment annotation from the xml @p description + */ + explicit FileAttachmentAnnotation( const QDomNode &description ); + /** + * Destroys the file attachment annotation. + */ + virtual ~FileAttachmentAnnotation(); + + /** + * Gets the name of the icon. + */ + QString fileIconName() const; + + /** + * Sets the @p name of the icon for the file attachment annotation. + */ + void setFileIconName( const QString &name ); + + /** + * Gets the embedded file object. + */ + EmbeddedFile* embeddedFile() const; + + /** + * Sets the @p object representing the embedded file of the file + * attachment annotation. + */ + void setEmbeddedFile( EmbeddedFile *object ); + + /** + * Returns the sub type of the file attachment annotation. + */ + SubType subType() const; + + /** + * Stores the file attachment annotation as xml in @p document + * under the given parent @p node. + */ + void store( QDomNode &node, QDomDocument &document ) const; + + private: + Q_DECLARE_PRIVATE( FileAttachmentAnnotation ) + Q_DISABLE_COPY( FileAttachmentAnnotation ) +}; + +/** + * \short Sound annotation. + * + * The sound annotation represents a sound to be played when activated. + * + * @since 0.7 (KDE 4.1) + */ +class OKULAR_EXPORT SoundAnnotation : public Annotation +{ + public: + /** + * Creates a new sound annotation. + */ + SoundAnnotation(); + /** + * Creates a new sound annotation from the xml @p description + */ + SoundAnnotation( const QDomNode &description ); + /** + * Destroys the sound annotation. + */ + virtual ~SoundAnnotation(); + + /** + * Gets the name of the icon. + */ + QString soundIconName() const; + + /** + * Sets the @p name of the icon for the sound annotation. + */ + void setSoundIconName( const QString &name ); + + /** + * Gets the sound object. + */ + Sound* sound() const; + + /** + * Sets the @p object representing the sound of the file + * attachment annotation. + */ + void setSound( Sound *object ); + + /** + * Returns the sub type of the sound annotation. + */ + SubType subType() const; + + /** + * Stores the sound annotation as xml in @p document + * under the given parent @p node. + */ + void store( QDomNode &node, QDomDocument &document ) const; + + private: + Q_DECLARE_PRIVATE( SoundAnnotation ) + Q_DISABLE_COPY( SoundAnnotation ) +}; + +/** + * \short Movie annotation. + * + * The movie annotation represents a movie to be played when activated. + * + * @since 0.8 (KDE 4.2) + */ +class OKULAR_EXPORT MovieAnnotation : public Annotation +{ + public: + /** + * Creates a new movie annotation. + */ + MovieAnnotation(); + /** + * Creates a new movie annotation from the xml @p description + */ + MovieAnnotation( const QDomNode &description ); + /** + * Destroys the movie annotation. + */ + virtual ~MovieAnnotation(); + /** + * Gets the movie object. + */ + Movie* movie() const; + /** + * Sets the new @p movie object. + */ + void setMovie( Movie *movie ); + /** + * Returns the sub type of the movie annotation. + */ + SubType subType() const; + /** + * Stores the movie annotation as xml in @p document + * under the given @p parentNode. + */ + void store( QDomNode &parentNode, QDomDocument &document ) const; + + private: + Q_DECLARE_PRIVATE( MovieAnnotation ) + Q_DISABLE_COPY( MovieAnnotation ) +}; + +/** + * \short Screen annotation. + * + * The screen annotation specifies a region of a page upon which media clips + * may be played. It also serves as an object from which actions can be triggered. + * + * @since 0.16 (KDE 4.10) + */ +class OKULAR_EXPORT ScreenAnnotation : public Annotation +{ + public: + /** + * Creates a new screen annotation. + */ + ScreenAnnotation(); + + /** + * Creates a new screen annotation from the xml @p description + */ + ScreenAnnotation( const QDomNode &description ); + + /** + * Destroys the screen annotation. + */ + virtual ~ScreenAnnotation(); + + /** + * Returns the sub type of the screen annotation. + */ + SubType subType() const; + + /** + * Stores the screen annotation as xml in @p document + * under the given @p parentNode. + */ + void store( QDomNode &parentNode, QDomDocument &document ) const; + + /** + * Sets the @p action that is executed when the annotation is triggered. + * + * @since 0.16 (KDE 4.10) + */ + void setAction( Action *action ); + + /** + * Returns the action that is executed when the annotation is triggered or @c 0 if not action has been defined. + * + * @since 0.16 (KDE 4.10) + */ + Action* action() const; + + /** + * Sets the additional @p action of the given @p type. + * + * @since 0.16 (KDE 4.10) + */ + void setAdditionalAction( AdditionalActionType type, Action *action ); + + /** + * Returns the additional action of the given @p type or @c 0 if no action has been defined. + * + * @since 0.16 (KDE 4.10) + */ + Action* additionalAction( AdditionalActionType type ) const; + + private: + Q_DECLARE_PRIVATE( ScreenAnnotation ) + Q_DISABLE_COPY( ScreenAnnotation ) +}; + +/** + * \short Widget annotation. + * + * The widget annotation represents a widget on a page. + * + * @since 0.16 (KDE 4.10) + */ +class OKULAR_EXPORT WidgetAnnotation : public Annotation +{ + public: + /** + * Creates a new widget annotation. + */ + WidgetAnnotation(); + + /** + * Creates a new widget annotation from the xml @p description + */ + WidgetAnnotation( const QDomNode &description ); + + /** + * Destroys the widget annotation. + */ + virtual ~WidgetAnnotation(); + + /** + * Returns the sub type of the widget annotation. + */ + SubType subType() const; + + /** + * Stores the widget annotation as xml in @p document + * under the given @p parentNode. + */ + void store( QDomNode &parentNode, QDomDocument &document ) const; + + /** + * Sets the additional @p action of the given @p type. + * + * @since 0.16 (KDE 4.10) + */ + void setAdditionalAction( AdditionalActionType type, Action *action ); + + /** + * Returns the additional action of the given @p type or @c 0 if no action has been defined. + * + * @since 0.16 (KDE 4.10) + */ + Action* additionalAction( AdditionalActionType type ) const; + + private: + Q_DECLARE_PRIVATE( WidgetAnnotation ) + Q_DISABLE_COPY( WidgetAnnotation ) +}; + +} + +#endif diff --git a/okular/core/annotations_p.h b/okular/core/annotations_p.h new file mode 100644 index 00000000..07b124a4 --- /dev/null +++ b/okular/core/annotations_p.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_ANNOTATIONS_P_H +#define OKULAR_ANNOTATIONS_P_H + +#include "area.h" +#include "annotations.h" + +// qt/kde includes +#include +#include +#include +#include + +class QTransform; + +namespace Okular { + +class PagePrivate; + +class AnnotationPrivate +{ + public: + AnnotationPrivate(); + + virtual ~AnnotationPrivate(); + + /** + * Transforms the annotation coordinates with the transformation + * defined by @p matrix. + */ + void annotationTransform( const QTransform &matrix ); + + virtual void transform( const QTransform &matrix ); + virtual void baseTransform( const QTransform &matrix ); + virtual void resetTransformation(); + virtual void translate( const NormalizedPoint &coord ); + virtual bool openDialogAfterCreation() const; + virtual void setAnnotationProperties( const QDomNode& node ); + virtual AnnotationPrivate* getNewAnnotationPrivate() = 0; + + /** + * Determines the distance of the closest point of the annotation to the + * given point @p x @p y @p xScale @p yScale + * @since 0.17 + */ + virtual double distanceSqr( double x, double y, double xScale, double yScale ); + + PagePrivate * m_page; + + QString m_author; + QString m_contents; + QString m_uniqueName; + QDateTime m_modifyDate; + QDateTime m_creationDate; + + int m_flags; + NormalizedRect m_boundary; + NormalizedRect m_transformedBoundary; + + Okular::Annotation::Style m_style; + Okular::Annotation::Window m_window; + QLinkedList< Okular::Annotation::Revision > m_revisions; + + Annotation::DisposeDataFunction m_disposeFunc; + QVariant m_nativeId; +}; + +} + +#endif diff --git a/okular/core/area.cpp b/okular/core/area.cpp new file mode 100644 index 00000000..bf2eb709 --- /dev/null +++ b/okular/core/area.cpp @@ -0,0 +1,493 @@ +/*************************************************************************** + * Copyright (C) 2004-05 by Enrico Ros * + * Copyright (C) 2005 by Piotr Szymanski * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "area.h" + +#include +#include +#include + +#include + +#include "action.h" +#include "annotations.h" +#include "annotations_p.h" +#include "debug_p.h" +#include "sourcereference.h" + +using namespace Okular; + +/** class NormalizedPoint **/ +NormalizedPoint::NormalizedPoint() + : x( 0.0 ), y( 0.0 ) {} + +NormalizedPoint::NormalizedPoint( double dX, double dY ) + : x( dX ), y( dY ) {} + +NormalizedPoint::NormalizedPoint( int iX, int iY, int xScale, int yScale ) + : x( (double)iX / (double)xScale ), y( (double)iY / (double)yScale ) {} + +NormalizedPoint& NormalizedPoint::operator=( const NormalizedPoint & p ) +{ + x = p.x; + y = p.y; + return *this; +} + +void NormalizedPoint::transform( const QTransform &matrix ) +{ + qreal tmp_x = (qreal)x; + qreal tmp_y = (qreal)y; + matrix.map( tmp_x, tmp_y, &tmp_x, &tmp_y ); + x = tmp_x; + y = tmp_y; +} + +double NormalizedPoint::distanceSqr( double x, double y, double xScale, double yScale ) const +{ + return pow( (this->x - x) * xScale, 2 ) + pow( (this->y - y) * yScale, 2 ); +} + +/** + * Returns a vector from the given points @p a and @p b + * @internal + */ +NormalizedPoint operator-( const NormalizedPoint& a, const NormalizedPoint& b ) +{ + return NormalizedPoint( a.x - b.x, a.y - b.y ); +} + +/** + * @brief Calculates distance of the point @p x @p y @p xScale @p yScale to the line segment from @p start to @p end + */ +double NormalizedPoint::distanceSqr( double x, double y, double xScale, double yScale, const NormalizedPoint& start, const NormalizedPoint& end ) +{ + NormalizedPoint point( x, y ); + double thisDistance; + NormalizedPoint lineSegment( end - start ); + const double lengthSqr = pow( lineSegment.x, 2 ) + pow( lineSegment.y, 2 ); + + //if the length of the current segment is null, we can just + //measure the distance to either end point + if ( lengthSqr == 0.0 ) + { + thisDistance = end.distanceSqr( x, y, xScale, yScale ); + } + else + { + //vector from the start point of the current line segment to the measurement point + NormalizedPoint a = point - start; + //vector from the same start point to the end point of the current line segment + NormalizedPoint b = end - start; + + //we're using a * b (dot product) := |a| * |b| * cos(phi) and the knowledge + //that cos(phi) is adjacent side / hypotenuse (hypotenuse = |b|) + //therefore, t becomes the length of the vector that represents the projection of + //the point p onto the current line segment + //(hint: if this is still unclear, draw it!) + float t = (a.x * b.x + a.y * b.y) / lengthSqr; + + if ( t < 0 ) + { + //projection falls outside the line segment on the side of "start" + thisDistance = point.distanceSqr( start.x, start.y, xScale, yScale ); + } + else if ( t > 1 ) + { + //projection falls outside the line segment on the side of the current point + thisDistance = point.distanceSqr( end.x, end.y, xScale, yScale ); + } + else + { + //projection is within [start, *i]; + //determine the length of the perpendicular distance from the projection to the actual point + NormalizedPoint direction = end - start; + NormalizedPoint projection = start - NormalizedPoint( -t * direction.x, -t * direction.y ); + thisDistance = projection.distanceSqr( x, y, xScale, yScale ); + } + } + return thisDistance; +} + +QDebug operator<<( QDebug str, const Okular::NormalizedPoint& p ) +{ + str.nospace() << "NormPt(" << p.x << "," << p.y << ")"; + return str.space(); +} + +/** class NormalizedRect **/ + +NormalizedRect::NormalizedRect() + : left( 0.0 ), top( 0.0 ), right( 0.0 ), bottom( 0.0 ) {} + +NormalizedRect::NormalizedRect( double l, double t, double r, double b ) + // note: check for swapping coords? + : left( l ), top( t ), right( r ), bottom( b ) {} + +NormalizedRect::NormalizedRect( const QRect & r, double xScale, double yScale ) + : left( (double)r.left() / xScale ), top( (double)r.top() / yScale ), + right( (double)r.right() / xScale ), bottom( (double)r.bottom() / yScale ) {} + +NormalizedRect::NormalizedRect( const NormalizedRect & rect ) + : left( rect.left ), top( rect.top ), right( rect.right ), bottom( rect.bottom ) {} + +NormalizedRect NormalizedRect::fromQRectF( const QRectF &rect ) +{ + QRectF nrect = rect.normalized(); + NormalizedRect ret; + ret.left = nrect.left(); + ret.top = nrect.top(); + ret.right = nrect.right(); + ret.bottom = nrect.bottom(); + return ret; +} + +bool NormalizedRect::isNull() const +{ + return left == 0 && top== 0 && right == 0 && bottom == 0; +} + +bool NormalizedRect::contains( double x, double y ) const +{ + return x >= left && x <= right && y >= top && y <= bottom; +} + +bool NormalizedRect::intersects( const NormalizedRect & r ) const +{ + return (r.left <= right) && (r.right >= left) && (r.top <= bottom) && (r.bottom >= top); +} + +bool NormalizedRect::intersects( const NormalizedRect * r ) const +{ + return (r->left <= right) && (r->right >= left) && (r->top <= bottom) && (r->bottom >= top); +} + +bool NormalizedRect::intersects( double l, double t, double r, double b ) const +{ + return (l <= right) && (r >= left) && (t <= bottom) && (b >= top); +} + +NormalizedRect NormalizedRect::operator| (const NormalizedRect & r) const +{ + NormalizedRect ret; + // todo ! + ret.left=qMin(left,r.left); + ret.top=qMin(top,r.top); + ret.bottom=qMax(bottom,r.bottom); + ret.right=qMax(right,r.right); + return ret; +} + +NormalizedRect& NormalizedRect::operator|= (const NormalizedRect & r) +{ + left = qMin( left, r.left ); + top = qMin( top, r.top ); + bottom = qMax( bottom, r.bottom ); + right = qMax( right, r.right ); + return *this; +} + +NormalizedRect NormalizedRect::operator&( const NormalizedRect & r ) const +{ + if ( isNull() || r.isNull() ) + return NormalizedRect(); + + NormalizedRect ret; + ret.left = qMax( left, r.left ); + ret.top = qMax( top, r.top ); + ret.bottom = qMin( bottom, r.bottom ); + ret.right = qMin( right, r.right ); + return ret; +} + +NormalizedRect & NormalizedRect::operator=( const NormalizedRect & r ) +{ + left = r.left; + right = r.right; + top = r.top; + bottom = r.bottom; + return *this; +} + +bool NormalizedRect::operator==( const NormalizedRect & r ) const +{ + return ( isNull() && r.isNull() ) || + ( fabs( left - r.left ) < 1e-4 && + fabs( right - r.right ) < 1e-4 && + fabs( top - r.top ) < 1e-4 && + fabs( bottom - r.bottom ) < 1e-4 ); +} + +NormalizedPoint NormalizedRect::center() const +{ + return NormalizedPoint((left+right)/2.0, (top+bottom)/2.0); +} + +/* +QDebug operator << (QDebug str , const NormalizedRect &r) +{ + str << "[" <(), d( 0 ) +{ +} + +RegularAreaRect::RegularAreaRect( const RegularAreaRect& rar ) + : RegularArea< NormalizedRect, QRect >( rar ), d( 0 ) +{ +} + +RegularAreaRect::~RegularAreaRect() +{ +} + +RegularAreaRect& RegularAreaRect::operator=( const RegularAreaRect& rar ) +{ + RegularArea< NormalizedRect, QRect >::operator=( rar ); + return *this; +} + + +HighlightAreaRect::HighlightAreaRect( const RegularAreaRect *area ) + : RegularAreaRect(), s_id( -1 ) +{ + if ( area ) + { + RegularAreaRect::ConstIterator it = area->begin(); + RegularAreaRect::ConstIterator itEnd = area->end(); + for ( ; it != itEnd; ++it ) + { + append( NormalizedRect( *it ) ); + } + } +} + +/** class ObjectRect **/ + +ObjectRect::ObjectRect( double l, double t, double r, double b, bool ellipse, ObjectType type, void * pnt ) + : m_objectType( type ), m_object( pnt ) +{ + // assign coordinates swapping them if negative width or height + QRectF rect( r > l ? l : r, b > t ? t : b, fabs( r - l ), fabs( b - t ) ); + if ( ellipse ) + m_path.addEllipse( rect ); + else + m_path.addRect( rect ); + + m_transformedPath = m_path; +} + +ObjectRect::ObjectRect( const NormalizedRect& x, bool ellipse, ObjectType type, void * pnt ) + : m_objectType( type ), m_object( pnt ) +{ + QRectF rect( x.left, x.top, fabs( x.right - x.left ), fabs( x.bottom - x.top ) ); + if ( ellipse ) + m_path.addEllipse( rect ); + else + m_path.addRect( rect ); + + m_transformedPath = m_path; +} + +ObjectRect::ObjectRect( const QPolygonF &poly, ObjectType type, void * pnt ) + : m_objectType( type ), m_object( pnt ) +{ + m_path.addPolygon( poly ); + + m_transformedPath = m_path; +} + +ObjectRect::ObjectType ObjectRect::objectType() const +{ + return m_objectType; +} + +const void * ObjectRect::object() const +{ + return m_object; +} + +const QPainterPath &ObjectRect::region() const +{ + return m_transformedPath; +} + +QRect ObjectRect::boundingRect( double xScale, double yScale ) const +{ + const QRectF &br = m_transformedPath.boundingRect(); + + return QRect( (int)( br.left() * xScale ), (int)( br.top() * yScale ), + (int)( br.width() * xScale ), (int)( br.height() * yScale ) ); +} + +bool ObjectRect::contains( double x, double y, double, double ) const +{ + return m_transformedPath.contains( QPointF( x, y ) ); +} + +void ObjectRect::transform( const QTransform &matrix ) +{ + m_transformedPath = matrix.map( m_path ); +} + +double ObjectRect::distanceSqr( double x, double y, double xScale, double yScale ) const +{ + switch ( m_objectType ) + { + case Action: + case Image: + { + const QRectF& rect( m_transformedPath.boundingRect() ); + return NormalizedRect( rect.x(), rect.y(), rect.right(), rect.bottom() ).distanceSqr( x, y, xScale, yScale ); + } + case OAnnotation: + { + return static_cast(m_object)->d_func()->distanceSqr( x, y, xScale, yScale ); + } + case SourceRef: + { + const SourceRefObjectRect * sr = static_cast< const SourceRefObjectRect * >( this ); + const NormalizedPoint& point = sr->m_point; + if ( point.x == -1.0 ) + { + return pow( ( y - point.y ) * yScale, 2 ); + } + else if ( point.y == -1.0 ) + { + return pow( ( x - point.x ) * xScale, 2 ); + } + else + { + return pow( ( x - point.x ) * xScale, 2 ) + pow( ( y - point.y ) * yScale, 2 ); + } + } + } + return 0.0; +} + +ObjectRect::~ObjectRect() +{ + if ( !m_object ) + return; + + if ( m_objectType == Action ) + delete static_cast( m_object ); + else if ( m_objectType == SourceRef ) + delete static_cast( m_object ); + else + kDebug(OkularDebug).nospace() << "Object deletion not implemented for type '" << m_objectType << "'."; +} + +/** class AnnotationObjectRect **/ + +AnnotationObjectRect::AnnotationObjectRect( Annotation * annotation ) + : ObjectRect( QPolygonF(), OAnnotation, annotation ), m_annotation( annotation ) +{ +} + +Annotation *AnnotationObjectRect::annotation() const +{ + return m_annotation; +} + +QRect AnnotationObjectRect::boundingRect( double xScale, double yScale ) const +{ + const QRect annotRect = AnnotationUtils::annotationGeometry( m_annotation, xScale, yScale ); + const QPoint center = annotRect.center(); + + // Make sure that the rectangle has a minimum size, so that it's possible + // to click on it + const int minSize = 14; + const QRect minRect( center.x()-minSize/2, center.y()-minSize/2, minSize, minSize ); + + return annotRect | minRect; +} + +bool AnnotationObjectRect::contains( double x, double y, double xScale, double yScale ) const +{ + return boundingRect( xScale, yScale ).contains( (int)( x * xScale ), (int)( y * yScale ), false ); +} + +AnnotationObjectRect::~AnnotationObjectRect() +{ + // the annotation pointer is kept elsewehere (in Page, most probably), + // so just release its pointer + m_object = 0; +} + +void AnnotationObjectRect::transform( const QTransform &matrix ) +{ + m_annotation->d_func()->annotationTransform( matrix ); +} + +/** class SourceRefObjectRect **/ + +SourceRefObjectRect::SourceRefObjectRect( const NormalizedPoint& point, void * srcRef ) + : ObjectRect( point.x, point.y, .0, .0, false, SourceRef, srcRef ), m_point( point ) +{ + const double x = m_point.x < 0.0 ? 0.5 : m_point.x; + const double y = m_point.y < 0.0 ? 0.5 : m_point.y; + const QRectF rect( x - 2, y - 2, 5, 5 ); + m_path.addRect( rect ); + + m_transformedPath = m_path; +} + +QRect SourceRefObjectRect::boundingRect( double xScale, double yScale ) const +{ + const double x = m_point.x < 0.0 ? 0.5 : m_point.x; + const double y = m_point.y < 0.0 ? 0.5 : m_point.y; + + return QRect( x * xScale, y * yScale, 1, 1 ); +} + +bool SourceRefObjectRect::contains( double x, double y, double xScale, double yScale ) const +{ + return distanceSqr( x, y, xScale, yScale ) < ( pow( 7.0 / xScale, 2 ) + pow( 7.0 / yScale, 2 ) ); +} diff --git a/okular/core/area.h b/okular/core/area.h new file mode 100644 index 00000000..1f7b10d2 --- /dev/null +++ b/okular/core/area.h @@ -0,0 +1,912 @@ +/*************************************************************************** + * Copyright (C) 2004-05 by Enrico Ros * + * Copyright (C) 2005 by Piotr Szymanski * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_AREA_H_ +#define _OKULAR_AREA_H_ + +#include +#include +#include +#include +#include +#include + +#include "global.h" +#include "okular_export.h" + +class QPolygonF; +class QRect; + +namespace Okular { + +class Annotation; +class Action; +class NormalizedShape; + +/** + * NormalizedPoint is a helper class which stores the coordinates + * of a normalized point. Normalized means that the coordinates are + * between 0 and 1 so that it is page size independent. + * + * Example: + * The normalized point is (0.5, 0.3) + * + * If you want to draw it on a 800x600 page, just multiply the x coordinate (0.5) with + * the page width (800) and the y coordinate (0.3) with the page height (600), so + * the point will be drawn on the page at (400, 180). + * + * That allows you to zoom the page by just multiplying the normalized points with the + * zoomed page size. + */ +class OKULAR_EXPORT NormalizedPoint +{ + public: + /** + * Creates a new empty normalized point. + */ + NormalizedPoint(); + + /** + * Creates a new normalized point with the normalized coordinates (@p x, @p y ). + */ + NormalizedPoint( double x, double y ); + + /** + * Creates a new normalized point with the coordinates (@p x, @p y) which are normalized + * by the scaling factors @p xScale and @p yScale. + */ + NormalizedPoint( int x, int y, int xScale, int yScale ); + + /** + * @internal + */ + NormalizedPoint& operator=( const NormalizedPoint& ); + + /** + * Transforms the normalized point with the operations defined by @p matrix. + */ + void transform( const QTransform &matrix ); + + /** + * Returns squared distance to point @p x @p y @p xScale @p yScale + * @since 0.17 (KDE 4.11) + */ + double distanceSqr( double x, double y, double xScale, double yScale ) const; + + + /** + * @brief Calculates distance of the point @p x @p y @p xScale @p yScale to the line segment from @p start to @p end + * @since 0.17 (KDE 4.11) + */ + static double distanceSqr( double x, double y, double xScale, double yScale, const NormalizedPoint& start, const NormalizedPoint& end ); + + /** + * The normalized x coordinate. + */ + double x; + + /** + * The normalized y coordinate. + */ + double y; +}; + + +/** + * NormalizedRect is a helper class which stores the coordinates + * of a normalized rect, which is a rectangle of @see NormalizedPoints. + */ +class OKULAR_EXPORT NormalizedRect +{ + public: + /** + * Creates a null normalized rectangle. + * @see isNull() + */ + NormalizedRect(); + + /** + * Creates a normalized rectangle with the normalized coordinates + * @p left, @p top, @p right, @p bottom. + * + * If you need the x, y, width and height coordinates use the + * following formulas: + * + * @li x = left + * @li y = top + * @li width = right - left + * @li height = bottom - top + */ + NormalizedRect( double left, double top, double right, double bottom ); + + /** + * Creates a normalized rectangle of the given @p rectangle which is normalized + * by the scaling factors @p xScale and @p yScale. + */ + NormalizedRect( const QRect &rectangle, double xScale, double yScale ); + + /** + * @internal + */ + NormalizedRect( const NormalizedRect& ); + + /** + * @internal + */ + NormalizedRect& operator=( const NormalizedRect &other ); + + /** + * Build a normalized rect from a QRectF. + */ + static NormalizedRect fromQRectF( const QRectF &rect ); + + /** + * Returns whether this normalized rectangle is a null normalized rect. + */ + bool isNull() const; + + /** + * Returns whether the normalized rectangle contains the normalized coordinates + * @p x and @p y. + */ + bool contains( double x, double y ) const; + + /** + * Returns whether the normalized rectangle intersects the @p other normalized + * rectangle. + */ + bool intersects( const NormalizedRect &other ) const; + + /** + * This is an overloaded member function, provided for convenience. It behaves essentially + * like the above function. + */ + bool intersects( const NormalizedRect *other ) const; + + /** + * Returns whether the normalized rectangle intersects an other normalized + * rectangle, which is defined by @p left, @p top, @p right and @p bottom. + */ + bool intersects( double left, double top, double right, double bottom ) const; + + /** + * Returns the rectangle that accrues when the normalized rectangle is multiplyed + * with the scaling @p xScale and @p yScale. + */ + QRect geometry( int xScale, int yScale ) const; + + /** + * Same functionality as geometry, but the output is now rounded before typecasting to int + * @since 0.14 (KDE 4.8) + */ + QRect roundedGeometry( int xScale, int yScale ) const; + + /** + * Returns the normalized bounding rectangle of the normalized rectangle + * combined with the @p other normalized rectangle. + */ + NormalizedRect operator|( const NormalizedRect &other ) const; + + /** + * Sets the normalized rectangle to the normalized bounding rectangle + * of itself combined with the @p other normalized rectangle. + */ + NormalizedRect& operator|=( const NormalizedRect &other ); + + /** + * Returns the intersection of this normalized rectangle with the specified + * @p other. If the rects do not intersect then the result is null. + * + * @since 0.7 (KDE 4.1) + */ + NormalizedRect operator&( const NormalizedRect &other ) const; + + /** + * Returns whether the normalized rectangle is equal to the @p other + * normalized rectangle. + */ + bool operator==( const NormalizedRect &other ) const; + + /** + * Returns the center of the rectangle + * @since 0.10 (KDE 4.4) + */ + NormalizedPoint center() const; + + /** + * Transforms the normalized rectangle with the operations defined by @p matrix. + */ + void transform( const QTransform &matrix ); + + /** + * Returns true if the point pt is located to the bottom of the rectangle + * @since 0.14 (KDE 4.8) + */ + bool isBottom(const NormalizedPoint& pt) const + { + return bottom < pt.y; + } + + /** + * Returns true if the point pt is located on the top of the rectangle + * @since 0.14 (KDE 4.8) + */ + bool isTop(const NormalizedPoint& pt) const + { + return top > pt.y; + } + + /** + * Returns true if the point pt is located under the top of the rectangle + * @since 0.14 (KDE 4.8) + */ + bool isBottomOrLevel(const NormalizedPoint& pt) const + { + return top < pt.y; + } + + /** + * Returns true if the point pt is located above the bottom of the rectangle + * @since 0.14 (KDE 4.8) + */ + bool isTopOrLevel(const NormalizedPoint& pt) const + { + return bottom > pt.y; + } + + /** + * Returns true if the point pt is located to the right of the left arm of rectangle + * @since 0.14 (KDE 4.8) + */ + bool isLeft(const NormalizedPoint& pt) const + { + return left < pt.x; + } + + /** + * Returns true if the point pt is located to the left of the right arm of rectangle + * @since 0.14 (KDE 4.8) + */ + bool isRight(const NormalizedPoint& pt) const + { + return right > pt.x; + } + + /** + * Returns the distance of the point @p x @p y @p xScale @p yScale to the closest + * edge or 0 if the point is within the rectangle + * @since 0.17 (KDE 4.11) + */ + double distanceSqr(double x, double y, double xScale, double yScale) const + { + double distX = 0; + if ( x < left ) + distX = left - x; + else if ( x > right ) + distX = x - right; + + double distY = 0; + if ( top > y ) + distY = top - y; + else if (bottom < y) + distY = y - bottom; + return pow( distX * xScale, 2 ) + pow( distY * yScale, 2 ); + } + + /** + * The normalized left coordinate. + */ + double left; + + /** + * The normalized top coordinate. + */ + double top; + + /** + * The normalized right coordinate. + */ + double right; + + /** + * The normalized bottom coordinate. + */ + double bottom; +}; +KDE_DUMMY_QHASH_FUNCTION(NormalizedRect) + +/** + * @short NormalizedRect that contains a reference to an object. + * + * These rects contains a pointer to a okular object (such as an action or something + * like that). The pointer is read and stored as 'void pointer' so cast is + * performed by accessors based on the value returned by objectType(). Objects + * are reparented to this class. + * + * Type / Class correspondency tab: + * - Action : class Action: description of an action + * - Image : class Image : description of an image (n/a) + * - Annotation: class Annotation: description of an annotation + */ +class OKULAR_EXPORT ObjectRect +{ + public: + /** + * Describes the type of storable object. + */ + enum ObjectType + { + Action, ///< An action + Image, ///< An image + OAnnotation, ///< An annotation + SourceRef ///< A source reference + }; + + /** + * Creates a new object rectangle. + * + * @param left The left coordinate of the rectangle. + * @param top The top coordinate of the rectangle. + * @param right The right coordinate of the rectangle. + * @param bottom The bottom coordinate of the rectangle. + * @param ellipse If true the rectangle describes an ellipse. + * @param type The type of the storable object @see ObjectType. + * @param object The pointer to the storable object. + */ + ObjectRect( double left, double top, double right, double bottom, bool ellipse, ObjectType type, void *object ); + + /** + * This is an overloaded member function, provided for convenience. + */ + ObjectRect( const NormalizedRect &rect, bool ellipse, ObjectType type, void *object ); + + /** + * This is an overloaded member function, provided for convenience. + */ + ObjectRect( const QPolygonF &poly, ObjectType type, void *object ); + + /** + * Destroys the object rectangle. + */ + virtual ~ObjectRect(); + + /** + * Returns the object type of the object rectangle. + * @see ObjectType + */ + ObjectType objectType() const; + + /** + * Returns the storable object of the object rectangle. + */ + const void *object() const; + + /** + * Returns the region that is covered by the object rectangle. + */ + const QPainterPath ®ion() const; + + /** + * Returns the bounding rect of the object rectangle for the + * scaling factor @p xScale and @p yScale. + */ + virtual QRect boundingRect( double xScale, double yScale ) const; + + /** + * Returns whether the object rectangle contains the point @p x, @p y for the + * scaling factor @p xScale and @p yScale. + */ + virtual bool contains( double x, double y, double xScale, double yScale ) const; + + /** + * Transforms the object rectangle with the operations defined by @p matrix. + */ + virtual void transform( const QTransform &matrix ); + + /** + * Returns the square of the distance between the object and the point @p x, @p y + * for the scaling factor @p xScale and @p yScale. + * + * @since 0.8.2 (KDE 4.2.2) + */ + // FIXME this should most probably be a virtual method + double distanceSqr( double x, double y, double xScale, double yScale ) const; + + protected: + ObjectType m_objectType; + void * m_object; + QPainterPath m_path; + QPainterPath m_transformedPath; +}; + +/** + * This class describes the object rectangle for an annotation. + */ +class OKULAR_EXPORT AnnotationObjectRect : public ObjectRect +{ + public: + /** + * Creates a new annotation object rectangle with the + * given @p annotation. + */ + AnnotationObjectRect( Annotation *annotation ); + + /** + * Destroys the annotation object rectangle. + */ + virtual ~AnnotationObjectRect(); + + /** + * Returns the annotation object of the annotation object rectangle. + */ + Annotation *annotation() const; + + /** + * Returns the bounding rect of the annotation object rectangle for the + * scaling factor @p xScale and @p yScale. + */ + virtual QRect boundingRect( double xScale, double yScale ) const; + + /** + * Returns whether the annotation object rectangle contains the point @p x, @p y for the + * scaling factor @p xScale and @p yScale. + */ + virtual bool contains( double x, double y, double xScale, double yScale ) const; + + /** + * Transforms the annotation object rectangle with the operations defined by @p matrix. + */ + virtual void transform( const QTransform &matrix ); + + private: + Annotation * m_annotation; +}; + +/** + * This class describes the object rectangle for a source reference. + */ +class OKULAR_EXPORT SourceRefObjectRect : public ObjectRect +{ + friend class ObjectRect; + + public: + /** + * Creates a new source reference object rectangle. + * + * @param point The point of the source reference. + * @param reference The storable source reference object. + */ + SourceRefObjectRect( const NormalizedPoint& point, void *reference ); + + /** + * Returns the bounding rect of the source reference object rectangle for the + * scaling factor @p xScale and @p yScale. + */ + virtual QRect boundingRect( double xScale, double yScale ) const; + + /** + * Returns whether the source reference object rectangle contains the point @p x, @p y for the + * scaling factor @p xScale and @p yScale. + */ + virtual bool contains( double x, double y, double xScale, double yScale ) const; + + private: + NormalizedPoint m_point; +}; + +/// @cond PRIVATE +/** @internal */ +template +void doDelete( T& t ) +{ + (void)t; +} + +/** @internal */ +template +T* givePtr( T& t ) +{ + return &t; +} + +/** @internal */ +template +T& deref( T& t ) +{ + return t; +} + +/** @internal */ +template +static void doDelete( T* t ) +{ + delete t; +} + +/** @internal */ +template +static T* givePtr( T* t ) +{ + return t; +} + +/** @internal */ +template +static T& deref( T* t ) +{ + return *t; +} +/// @endcond + +/** + * @short A regular area of NormalizedShape which normalizes a Shape + * + * Class NormalizedShape \b must have the following functions/operators defined: + * - bool contains( double, double ) + * - bool intersects( NormalizedShape ) + * - bool isNull() + * - Shape geometry( int, int ) + * - operator|=( NormalizedShape ) which unite two NormalizedShape's + */ +template class RegularArea : public QList +{ + public: + /** + * Destroys a regular area. + */ + ~RegularArea(); + + /** + * Returns whether the regular area contains the + * normalized point @p x, @p y. + */ + bool contains( double x, double y ) const; + + /** + * Returns whether the regular area contains the + * given @p shape. + */ + bool contains( const NormalizedShape& shape ) const; + + /** + * Returns whether the regular area intersects with the given @p area. + */ + bool intersects( const RegularArea *area ) const; + + /** + * Returns whether the regular area intersects with the given @p shape. + */ + bool intersects( const NormalizedShape& shape ) const; + + /** + * Appends the given @p area to the regular area. + */ + void appendArea( const RegularArea *area ); + + /** + * Appends the given @p shape to the regular area. + */ + void appendShape( const NormalizedShape& shape, MergeSide side = MergeAll ); + + /** + * Simplifies the regular area by merging its intersecting subareas. + */ + void simplify(); + + /** + * Returns whether the regular area is a null area. + */ + bool isNull() const; + + /** + * Returns the subareas of the regular areas as shapes for the given scaling factor + * @p xScale and @p yScale, translated by @p dx and @p dy. + */ + QList geometry( int xScale, int yScale, int dx = 0, int dy = 0 ) const; + + /** + * Transforms the regular area with the operations defined by @p matrix. + */ + void transform( const QTransform &matrix ); +}; + +template +RegularArea::~RegularArea() +{ + int size = this->count(); + for ( int i = 0; i < size; ++i ) + doDelete( (*this)[i] ); +} + +template +void RegularArea::simplify() +{ +#ifdef DEBUG_REGULARAREA + int prev_end = this->count(); +#endif + int end = this->count() - 1, x = 0; + for ( int i = 0; i < end; ++i ) + { + if ( givePtr( (*this)[x] )->intersects( deref( (*this)[i+1] ) ) ) + { + deref((*this)[x]) |= deref((*this)[i+1]); + NormalizedShape& tobedeleted = (*this)[i+1]; + this->removeAt( i + 1 ); + doDelete( tobedeleted ); + --end; + --i; + } + else + { + x=i+1; + } + } +#ifdef DEBUG_REGULARAREA + kDebug() << "from" << prev_end << "to" << this->count(); +#endif +} + +template +bool RegularArea::isNull() const +{ + if ( !this ) + return false; + + if ( this->isEmpty() ) + return false; + + typename QList::const_iterator it = this->begin(), itEnd = this->end(); + for ( ; it != itEnd; ++it ) + if ( !givePtr( *it )->isNull() ) + return false; + + return true; +} + +template +bool RegularArea::intersects( const NormalizedShape& rect ) const +{ + if ( !this ) + return false; + + if ( this->isEmpty() ) + return false; + + typename QList::const_iterator it = this->begin(), itEnd = this->end(); + for ( ; it != itEnd; ++it ) + if ( !givePtr( *it )->isNull() && givePtr( *it )->intersects( rect ) ) + return true; + + return false; +} + +template +bool RegularArea::intersects( const RegularArea *area ) const +{ + if ( !this ) + return false; + + if ( this->isEmpty() ) + return false; + + typename QList::const_iterator it = this->begin(), itEnd = this->end(); + for ( ; it != itEnd; ++it ) + { + typename QList::const_iterator areaIt = area->begin(), areaItEnd = area->end(); + for ( ; areaIt != areaItEnd; ++areaIt ) + { + if ( !( *it ).isNull() && ( *it ).intersects( *areaIt ) ) + return true; + } + } + + return false; +} + +template +void RegularArea::appendArea( const RegularArea *area ) +{ + if ( !this ) + return; + + typename QList::const_iterator areaIt = area->begin(), areaItEnd = area->end(); + for ( ; areaIt != areaItEnd; ++areaIt ) + this->append( *areaIt ); +} + + +template +void RegularArea::appendShape( const NormalizedShape& shape, MergeSide side ) +{ + if ( !this ) + return; + + int size = this->count(); + // if the list is empty, adds the shape normally + if ( size == 0 ) + { + this->append( shape ); + } + else + { + bool intersection = false; + NormalizedShape& last = (*this)[size - 1]; +#define O_LAST givePtr( last ) +# define O_LAST_R O_LAST->right +# define O_LAST_L O_LAST->left +# define O_LAST_T O_LAST->top +# define O_LAST_B O_LAST->bottom +#define O_NEW givePtr( shape ) +# define O_NEW_R O_NEW->right +# define O_NEW_L O_NEW->left +# define O_NEW_T O_NEW->top +# define O_NEW_B O_NEW->bottom + switch ( side ) + { + case MergeRight: + intersection = ( O_LAST_R >= O_NEW_L ) && ( O_LAST_L <= O_NEW_R ) + && ( ( O_LAST_T <= O_NEW_T && O_LAST_B >= O_NEW_B ) + || ( O_LAST_T >= O_NEW_T && O_LAST_B <= O_NEW_B ) ); + break; + case MergeBottom: + intersection = ( O_LAST_B >= O_NEW_T ) && ( O_LAST_T <= O_NEW_B ) + && ( ( O_LAST_R <= O_NEW_R && O_LAST_L >= O_NEW_L ) + || ( O_LAST_R >= O_NEW_R && O_LAST_L <= O_NEW_L ) ); + break; + case MergeLeft: + intersection = ( O_LAST_L <= O_NEW_R ) && ( O_LAST_R >= O_NEW_L ) + && ( ( O_LAST_T <= O_NEW_T && O_LAST_B >= O_NEW_B ) + || ( O_LAST_T >= O_NEW_T && O_LAST_B <= O_NEW_B ) ); + break; + case MergeTop: + intersection = ( O_LAST_T <= O_NEW_B ) && ( O_LAST_B >= O_NEW_T ) + && ( ( O_LAST_R <= O_NEW_R && O_LAST_L >= O_NEW_L ) + || ( O_LAST_R >= O_NEW_R && O_LAST_L <= O_NEW_L ) ); + break; + case MergeAll: + intersection = O_LAST->intersects( shape ); + break; + } +#undef O_LAST +# undef O_LAST_R +# undef O_LAST_L +# undef O_LAST_T +# undef O_LAST_B +#undef O_NEW +# undef O_NEW_R +# undef O_NEW_L +# undef O_NEW_T +# undef O_NEW_B + // if the new shape intersects with the last shape in the list, then + // merge it with that and delete the shape + if ( intersection ) + { + deref((*this)[size - 1]) |= deref( shape ); + doDelete( const_cast( shape ) ); + } + else + this->append( shape ); + } +} + + +template +bool RegularArea::contains( double x, double y ) const +{ + if ( !this ) + return false; + + if ( this->isEmpty() ) + return false; + + typename QList::const_iterator it = this->begin(), itEnd = this->end(); + for ( ; it != itEnd; ++it ) + if ( ( *it ).contains( x, y ) ) + return true; + + return false; +} + +template +bool RegularArea::contains( const NormalizedShape& shape ) const +{ + if ( !this ) + return false; + + if ( this->isEmpty() ) + return false; + + return QList::contains( shape ); +} + +template +QList RegularArea::geometry( int xScale, int yScale, int dx, int dy ) const +{ + if ( !this || this->isEmpty() ) + return QList(); + + QList ret; + Shape t; + typename QList::const_iterator it = this->begin(), itEnd = this->end(); + for ( ; it != itEnd; ++it ) + { + t = givePtr( *it )->geometry( xScale, yScale ); + t.translate( dx, dy ); + ret.append( t ); + } + + return ret; +} + +template +void RegularArea::transform( const QTransform &matrix ) +{ + if ( !this ) + return; + + if ( this->isEmpty() ) + return; + + for ( int i = 0; i < this->count(); ++i ) + givePtr( (*this)[i] )->transform( matrix ); +} + +class OKULAR_EXPORT RegularAreaRect : public RegularArea< NormalizedRect, QRect > +{ + public: + RegularAreaRect(); + RegularAreaRect( const RegularAreaRect& rar ); + ~RegularAreaRect(); + + RegularAreaRect& operator=( const RegularAreaRect& rar ); + + private: + class Private; + Private * const d; +}; + +/** + * This class stores the coordinates of a highlighting area + * together with the id of the highlight owner and the color. + */ +class HighlightAreaRect : public RegularAreaRect +{ + public: + /** + * Creates a new highlight area rect with the coordinates of + * the given @p area. + */ + HighlightAreaRect( const RegularAreaRect *area = 0 ); + + /** + * The search ID of the highlight owner. + */ + int s_id; + + /** + * The color of the highlight. + */ + QColor color; +}; + +} + +#ifndef QT_NO_DEBUG_STREAM +/** + * Debug operator for normalized @p point. + */ +OKULAR_EXPORT QDebug operator<<( QDebug str, const Okular::NormalizedPoint &point ); + +/** + * Debug operator for normalized @p rect. + */ +OKULAR_EXPORT QDebug operator<<( QDebug str, const Okular::NormalizedRect &rect ); +#endif + +#endif diff --git a/okular/core/audioplayer.cpp b/okular/core/audioplayer.cpp new file mode 100644 index 00000000..3638040f --- /dev/null +++ b/okular/core/audioplayer.cpp @@ -0,0 +1,259 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "audioplayer.h" +#include "audioplayer_p.h" + +// qt/kde includes +#include +#include +#include +#include +#include +#include +#include +#include + +// local includes +#include "action.h" +#include "debug_p.h" +#include "sound.h" + +using namespace Okular; + +// helper class used to store info about a sound to be played +class SoundInfo +{ +public: + explicit SoundInfo( const Sound * s = 0, const SoundAction * ls = 0 ) + : sound( s ), volume( 0.5 ), synchronous( false ), repeat( false ), + mix( false ) + { + if ( ls ) + { + volume = ls->volume(); + synchronous = ls->synchronous(); + repeat = ls->repeat(); + mix = ls->mix(); + } + } + + const Sound * sound; + double volume; + bool synchronous; + bool repeat; + bool mix; +}; + + +class PlayData +{ +public: + PlayData() + : m_mediaobject( 0 ), m_output( 0 ), m_buffer( 0 ) + { + } + + void play() + { + if ( m_buffer ) + { + m_buffer->open( QIODevice::ReadOnly ); + } + m_mediaobject->play(); + } + + ~PlayData() + { + m_mediaobject->stop(); + delete m_mediaobject; + delete m_output; + delete m_buffer; + } + + Phonon::MediaObject * m_mediaobject; + Phonon::AudioOutput * m_output; + QBuffer * m_buffer; + SoundInfo m_info; +}; + + +AudioPlayerPrivate::AudioPlayerPrivate( AudioPlayer * qq ) + : q( qq ), m_state( AudioPlayer::StoppedState ) +{ + QObject::connect( &m_mapper, SIGNAL(mapped(int)), q, SLOT(finished(int)) ); +} + +AudioPlayerPrivate::~AudioPlayerPrivate() +{ + stopPlayings(); +} + +int AudioPlayerPrivate::newId() const +{ + int newid = 0; + QHash< int, PlayData * >::const_iterator it; + QHash< int, PlayData * >::const_iterator itEnd = m_playing.constEnd(); + do + { + newid = KRandom::random(); + it = m_playing.constFind( newid ); + } while ( it != itEnd ); + return newid; +} + +bool AudioPlayerPrivate::play( const SoundInfo& si ) +{ + kDebug() ; + PlayData * data = new PlayData(); + data->m_output = new Phonon::AudioOutput( Phonon::NotificationCategory ); + data->m_output->setVolume( si.volume ); + data->m_mediaobject = new Phonon::MediaObject(); + Phonon::createPath(data->m_mediaobject, data->m_output); + data->m_info = si; + bool valid = false; + + switch ( si.sound->soundType() ) + { + case Sound::External: + { + QString url = si.sound->url(); + kDebug(OkularDebug) << "External," << url; + if ( !url.isEmpty() ) + { + int newid = newId(); + m_mapper.setMapping( data->m_mediaobject, newid ); + KUrl newurl; + if ( KUrl::isRelativeUrl( url ) ) + { + newurl = m_currentDocument; + newurl.setFileName( url ); + } + else + { + newurl = url; + } + data->m_mediaobject->setCurrentSource( newurl ); + m_playing.insert( newid, data ); + valid = true; + } + break; + } + case Sound::Embedded: + { + QByteArray filedata = si.sound->data(); + kDebug(OkularDebug) << "Embedded," << filedata.length(); + if ( !filedata.isEmpty() ) + { + kDebug(OkularDebug) << "Mediaobject:" << data->m_mediaobject; + int newid = newId(); + m_mapper.setMapping( data->m_mediaobject, newid ); + data->m_buffer = new QBuffer(); + data->m_buffer->setData( filedata ); + data->m_mediaobject->setCurrentSource( Phonon::MediaSource( data->m_buffer ) ); + m_playing.insert( newid, data ); + valid = true; + } + break; + } + } + if ( !valid ) + { + delete data; + data = 0; + } + if ( data ) + { + QObject::connect( data->m_mediaobject, SIGNAL(finished()), &m_mapper, SLOT(map()) ); + kDebug(OkularDebug) << "PLAY"; + data->play(); + m_state = AudioPlayer::PlayingState; + } + return valid; +} + +void AudioPlayerPrivate::stopPlayings() +{ + qDeleteAll( m_playing ); + m_playing.clear(); + m_state = AudioPlayer::StoppedState; +} + +void AudioPlayerPrivate::finished( int id ) +{ + QHash< int, PlayData * >::iterator it = m_playing.find( id ); + if ( it == m_playing.end() ) + return; + + SoundInfo si = it.value()->m_info; + // if the sound must be repeated indefinitely, then start the playback + // again, otherwise destroy the PlayData as it's no more useful + if ( si.repeat ) + { + it.value()->play(); + } + else + { + m_mapper.removeMappings( it.value()->m_mediaobject ); + delete it.value(); + m_playing.erase( it ); + m_state = AudioPlayer::StoppedState; + } + kDebug(OkularDebug) << "finished," << m_playing.count(); +} + + +AudioPlayer::AudioPlayer() + : QObject(), d( new AudioPlayerPrivate( this ) ) +{ +} + +AudioPlayer::~AudioPlayer() +{ + delete d; +} + +AudioPlayer * AudioPlayer::instance() +{ + static AudioPlayer ap; + return ≈ +} + +void AudioPlayer::playSound( const Sound * sound, const SoundAction * linksound ) +{ + // we can't play null pointers ;) + if ( !sound ) + return; + + // we don't play external sounds for remote documents + if ( sound->soundType() == Sound::External && !d->m_currentDocument.isLocalFile() ) + return; + + kDebug() ; + SoundInfo si( sound, linksound ); + + // if the mix flag of the new sound is false, then the currently playing + // sounds must be stopped. + if ( !si.mix ) + d->stopPlayings(); + + d->play( si ); +} + +void AudioPlayer::stopPlaybacks() +{ + d->stopPlayings(); +} + +AudioPlayer::State AudioPlayer::state() const +{ + return d->m_state; +} + +#include "audioplayer.moc" diff --git a/okular/core/audioplayer.h b/okular/core/audioplayer.h new file mode 100644 index 00000000..b335b5ef --- /dev/null +++ b/okular/core/audioplayer.h @@ -0,0 +1,89 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_AUDIOPLAYER_H_ +#define _OKULAR_AUDIOPLAYER_H_ + +#include "okular_export.h" + +#include + +namespace Okular { + +class AudioPlayerPrivate; +class Document; +class Sound; +class SoundAction; + +/** + * @short An audio player. + * + * Singleton utility class to play sounds in documents using the KDE sound + * system. + */ +class OKULAR_EXPORT AudioPlayer : public QObject +{ + Q_OBJECT + + public: + + /** + * The state of AudioPlayer + * @since 0.19 (KDE 4.13) + */ + enum State + { + /** + * The AudioPlayer is playing a audio file. + */ + PlayingState, + /** + * The AudioPlayer isn't playing a audio file. + */ + StoppedState + }; + + ~AudioPlayer(); + + /** + * Gets the instance of the audio player. + */ + static AudioPlayer * instance(); + + /** + * Enqueue the specified @p sound for playing, optionally taking more + * information about the playing from the @p soundlink . + */ + void playSound( const Sound * sound, const SoundAction * linksound = 0 ); + + /** + * Tell the AudioPlayer to stop all the playbacks. + */ + void stopPlaybacks(); + + /** + * Return state of sound (playing/stopped) + * @since 0.19 (KDE 4.13) + */ + State state() const; + + private: + AudioPlayer(); + + friend class AudioPlayerPrivate; + AudioPlayerPrivate * const d; + friend class Document; + + Q_DISABLE_COPY( AudioPlayer ) + Q_PRIVATE_SLOT( d, void finished( int ) ) +}; + +} + +#endif diff --git a/okular/core/audioplayer_p.h b/okular/core/audioplayer_p.h new file mode 100644 index 00000000..32fe4de8 --- /dev/null +++ b/okular/core/audioplayer_p.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_AUDIOPLAYER_P_H_ +#define _OKULAR_AUDIOPLAYER_P_H_ + +// qt/kde includes +#include +#include +#include + +class QBuffer; +class PlayData; +class SoundInfo; + +namespace Okular { + +class AudioPlayer; + +class AudioPlayerPrivate +{ +public: + AudioPlayerPrivate( AudioPlayer * qq ); + + ~AudioPlayerPrivate(); + + int newId() const; + bool play( const SoundInfo& si ); + void stopPlayings(); + + // private slots + void finished( int ); + + AudioPlayer * q; + + QHash< int, PlayData * > m_playing; + QSignalMapper m_mapper; + KUrl m_currentDocument; + AudioPlayer::State m_state; +}; + +} + +#endif diff --git a/okular/core/bookmarkmanager.cpp b/okular/core/bookmarkmanager.cpp new file mode 100644 index 00000000..22951b24 --- /dev/null +++ b/okular/core/bookmarkmanager.cpp @@ -0,0 +1,745 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "bookmarkmanager.h" + +// qt/kde includes +#include +#include +#include +#include +#include +#include +#include + +// local includes +#include "document_p.h" +#include "observer.h" + +using namespace Okular; + +#define foreachObserver( cmd ) {\ + QSet< DocumentObserver * >::const_iterator it = d->document->m_observers.constBegin(), end = d->document->m_observers.constEnd();\ + for ( ; it != end ; ++ it ) { (*it)-> cmd ; } } + +#define foreachObserverD( cmd ) {\ + QSet< DocumentObserver * >::const_iterator it = document->m_observers.constBegin(), end = document->m_observers.constEnd();\ + for ( ; it != end ; ++ it ) { (*it)-> cmd ; } } + +class OkularBookmarkAction : public KBookmarkAction +{ + public: + OkularBookmarkAction( const Okular::DocumentViewport& vp, const KBookmark& bk, KBookmarkOwner* owner, QObject *parent ) + : KBookmarkAction( bk, owner, parent ) + { + if ( vp.isValid() ) + setText( QString::number( vp.pageNumber + 1 ) + " - " + text() ); + setProperty("pageNumber", vp.pageNumber + 1); + setProperty("htmlRef", bk.url().htmlRef()); + } + + inline int pageNumber() const + { + return property("pageNumber").toInt(); + } + + inline QString htmlRef() const + { + return property("htmlRef").toString(); + } +}; + +static inline bool documentViewportFuzzyCompare( const DocumentViewport &vp1, const DocumentViewport &vp2 ) +{ + bool equal = vp1.isValid() && vp2.isValid() && + ( vp1.pageNumber == vp2.pageNumber ) && + ( vp1.rePos.pos == vp2.rePos.pos ); + + if ( !equal ) + return false; + + if ( qAbs(vp1.rePos.normalizedX-vp2.rePos.normalizedX) >= 0.000001 ) + return false; + + if ( qAbs(vp1.rePos.normalizedY-vp2.rePos.normalizedY) >= 0.000001 ) + return false; + + return true; +} + +static inline bool bookmarkLessThan( const KBookmark &b1, const KBookmark &b2 ) +{ + DocumentViewport vp1( b1.url().htmlRef() ); + DocumentViewport vp2( b2.url().htmlRef() ); + + return vp1 < vp2; +} + +static inline bool okularBookmarkActionLessThan( QAction * a1, QAction * a2 ) +{ + DocumentViewport vp1( static_cast< OkularBookmarkAction * >( a1 )->htmlRef() ); + DocumentViewport vp2( static_cast< OkularBookmarkAction * >( a2 )->htmlRef() ); + + return vp1 < vp2; +} + +class BookmarkManager::Private : public KBookmarkOwner +{ + public: + Private( BookmarkManager * qq ) + : KBookmarkOwner(), q( qq ), document( 0 ), manager( 0 ) + { + } + + ~Private() + { + knownFiles.clear(); + // no need to delete the manager, it's automatically done by KBookmarkManager + // delete manager; + } + + virtual QString currentUrl() const; + virtual QString currentTitle() const; + virtual bool enableOption(BookmarkOption option) const; + virtual void openBookmark( const KBookmark & bm, Qt::MouseButtons, Qt::KeyboardModifiers ); + + QHash::iterator bookmarkFind( const KUrl& url, bool doCreate, KBookmarkGroup *result = 0); + + // slots + void _o_changed( const QString & groupAddress, const QString & caller ); + + BookmarkManager * q; + KUrl url; + QHash urlBookmarks; + DocumentPrivate * document; + QString file; + KBookmarkManager * manager; + QHash knownFiles; +}; + +static inline KUrl urlForGroup(const KBookmark &group) +{ + if ( group.url().isValid() ) return group.url(); + else return KUrl( group.fullText() ); +} + +BookmarkManager::BookmarkManager( DocumentPrivate * document ) + : QObject( document->m_parent ), d( new Private( this ) ) +{ + setObjectName( QLatin1String( "Okular::BookmarkManager" ) ); + + d->document = document; + + d->file = KStandardDirs::locateLocal( "data", "okular/bookmarks.xml" ); + + d->manager = KBookmarkManager::managerForFile( d->file, "okular" ); + d->manager->setEditorOptions( KGlobal::caption(), false ); + d->manager->setUpdate( true ); + connect( d->manager, SIGNAL(changed(QString,QString)), + this, SLOT(_o_changed(QString,QString)) ); +} + +BookmarkManager::~BookmarkManager() +{ + delete d; +} + +//BEGIN Reimplementations from KBookmarkOwner +QString BookmarkManager::Private::currentUrl() const +{ + return url.prettyUrl(); +} + +QString BookmarkManager::Private::currentTitle() const +{ + return url.isLocalFile() ? url.toLocalFile() : url.prettyUrl(); +} + +bool BookmarkManager::Private::enableOption(BookmarkOption option) const +{ + Q_UNUSED( option ) + return false; +} + +void BookmarkManager::Private::openBookmark( const KBookmark & bm, Qt::MouseButtons, Qt::KeyboardModifiers ) +{ + emit q->openUrl( bm.url() ); +} +//END Reimplementations from KBookmarkOwner + +void BookmarkManager::Private::_o_changed( const QString & groupAddress, const QString & caller ) +{ + Q_UNUSED( caller ); + if ( groupAddress.isEmpty() ) + return; + + KUrl referurl; + // first, try to find the bookmark group whom change notification was just received + QHash::iterator it = knownFiles.begin(), itEnd = knownFiles.end(); + for ( ; it != itEnd; ++it ) + { + if ( it.value() == groupAddress ) + { + referurl = it.key(); + knownFiles.erase( it ); + break; + } + } + if ( !referurl.isValid() ) + { + const KBookmark bm = manager->findByAddress( groupAddress ); + // better be safe than sorry + if ( bm.isNull() ) + return; + Q_ASSERT( bm.isGroup() ); + referurl = urlForGroup( bm ); + } + Q_ASSERT( referurl.isValid() ); + emit q->bookmarksChanged( referurl ); + // case for the url representing the current document + // (this might happen if the same document is open in another place; + // in such case, make really sure to be in sync) + if ( referurl == url ) + { + // save the old bookmarks for the current url + const QHash oldUrlBookmarks = urlBookmarks; + // set the same url again, so we reload the information we have about it + q->setUrl( referurl ); + // then notify the observers about the changes in the bookmarks + for ( int i = 0; i < qMax( oldUrlBookmarks.size(), urlBookmarks.size() ); i++ ) + { + bool oldContains = oldUrlBookmarks.contains(i) && oldUrlBookmarks[i] > 0; + bool curContains = urlBookmarks.contains(i) && urlBookmarks[i] > 0; + + if ( oldContains != curContains ) + { + foreachObserverD( notifyPageChanged( i, DocumentObserver::Bookmark ) ); + } + else if ( oldContains && oldUrlBookmarks[i] != urlBookmarks[i] ) + { + foreachObserverD( notifyPageChanged( i, DocumentObserver::Bookmark ) ); + } + + } + } + emit q->saved(); +} + +KUrl::List BookmarkManager::files() const +{ + KUrl::List ret; + KBookmarkGroup group = d->manager->root(); + for ( KBookmark bm = group.first(); !bm.isNull(); bm = group.next( bm ) ) + { + if ( bm.isSeparator() || !bm.isGroup() ) + continue; + + ret.append( urlForGroup( bm ) ); + } + return ret; +} + +KBookmark::List BookmarkManager::bookmarks( const KUrl& url ) const +{ + KBookmark::List ret; + KBookmarkGroup group = d->manager->root(); + for ( KBookmark bm = group.first(); !bm.isNull(); bm = group.next( bm ) ) + { + if ( !bm.isGroup() || urlForGroup( bm ) != url ) + continue; + + KBookmarkGroup group = bm.toGroup(); + for ( KBookmark b = group.first(); !b.isNull(); b = group.next( b ) ) + { + if ( b.isSeparator() || b.isGroup() ) + continue; + + ret.append( b ); + } + break; + } + + return ret; +} + +KBookmark::List BookmarkManager::bookmarks() const +{ + return bookmarks( d->url ); +} + +KBookmark::List BookmarkManager::bookmarks( int page ) const +{ + const KBookmark::List bmarks = bookmarks(); + KBookmark::List ret; + foreach( const KBookmark &bm, bmarks ) + { + DocumentViewport vp( bm.url().htmlRef() ); + if ( vp.isValid() && vp.pageNumber == page ) + { + ret.append(bm); + } + } + + return ret; +} + +KBookmark BookmarkManager::bookmark( int page ) const +{ + const KBookmark::List bmarks = bookmarks(); + foreach( const KBookmark &bm, bmarks ) + { + DocumentViewport vp( bm.url().htmlRef() ); + if ( vp.isValid() && vp.pageNumber == page ) + { + return bm; + } + } + return KBookmark(); +} + +KBookmark BookmarkManager::bookmark( const DocumentViewport &viewport ) const +{ + if ( !viewport.isValid() || !isBookmarked( viewport.pageNumber ) ) + return KBookmark(); + + KBookmarkGroup thebg; + QHash::iterator it = d->bookmarkFind( d->url, false, &thebg ); + if ( it == d->knownFiles.end() ) + return KBookmark(); + + for ( KBookmark bm = thebg.first(); !bm.isNull(); bm = thebg.next( bm ) ) + { + if ( bm.isSeparator() || bm.isGroup() ) + continue; + + DocumentViewport vp( bm.url().htmlRef() ); + if ( documentViewportFuzzyCompare( vp, viewport ) ) + { + return bm; + } + } + + return KBookmark(); +} + +void BookmarkManager::save() const +{ + d->manager->emitChanged(); + emit const_cast( this )->saved(); +} + +QHash::iterator BookmarkManager::Private::bookmarkFind( const KUrl& url, bool doCreate, KBookmarkGroup *result ) +{ + QHash::iterator it = knownFiles.find( url ); + if ( it == knownFiles.end() ) + { + // if the url we want to add a new entry for is not in the hash of the + // known files, then first try to find the file among the top-level + // "folder" names + bool found = false; + KBookmarkGroup root = manager->root(); + for ( KBookmark bm = root.first(); !found && !bm.isNull(); bm = root.next( bm ) ) + { + if ( bm.isSeparator() || !bm.isGroup() ) + continue; + + KUrl tmpurl( urlForGroup( bm ) ); + if ( tmpurl == url ) + { + // got it! place it the hash of known files + KBookmarkGroup bg = bm.toGroup(); + it = knownFiles.insert( url, bg.address() ); + found = true; + if ( result ) + *result = bg; + break; + } + } + if ( !found && doCreate ) + { + // folder not found :( + // then, in a single step create a new folder and add it in our cache :) + QString purl = url.isLocalFile() ? url.toLocalFile() : url.prettyUrl(); + KBookmarkGroup newbg = root.createNewFolder( purl ); + newbg.setUrl( url ); + it = knownFiles.insert( url, newbg.address() ); + if ( result ) + *result = newbg; + } + } + else if ( result ) + { + const KBookmark bm = manager->findByAddress( it.value() ); + Q_ASSERT( bm.isGroup() ); + *result = bm.toGroup(); + } + return it; +} + +void BookmarkManager::addBookmark( int n ) +{ + if ( n >= 0 && n < (int)d->document->m_pagesVector.count() ) + { + if ( setPageBookmark( n ) ) + foreachObserver( notifyPageChanged( n, DocumentObserver::Bookmark ) ); + } +} + +void BookmarkManager::addBookmark( const DocumentViewport &vp ) +{ + addBookmark( d->url, vp ); +} + +bool BookmarkManager::addBookmark( const KUrl& referurl, const Okular::DocumentViewport& vp, const QString& title ) +{ + if ( !referurl.isValid() || !vp.isValid() ) + return false; + + if ( vp.pageNumber < 0 || vp.pageNumber >= d->document->m_pagesVector.count() ) + return false; + + KBookmarkGroup thebg; + QHash::iterator it = d->bookmarkFind( referurl, true, &thebg ); + Q_ASSERT( it != d->knownFiles.end() ); + + int count = 0; // Number of bookmarks in the current page + bool found = false; + // Check if the bookmark already exists + for ( KBookmark bm = thebg.first(); !found && !bm.isNull(); bm = thebg.next( bm ) ) + { + if ( bm.isSeparator() || bm.isGroup() ) + continue; + + DocumentViewport bmViewport( bm.url().htmlRef() ); + if ( bmViewport.isValid() && bmViewport.pageNumber == vp.pageNumber ) + { + ++count; + + if ( documentViewportFuzzyCompare( bmViewport, vp ) ) + found = true; + } + } + + if ( found ) + return false; + + QString newtitle; + if ( title.isEmpty() ) + { + // if we have no title specified for the new bookmark, then give it the + // name '#p' where p is the page number where the bookmark is located. + // if there's more than one bookmark per page, give the name '#p-n' + // where n is the index of this bookmark among the ones of its page. + if ( count > 0 ) + newtitle = QString( "#%1-%2" ).arg( vp.pageNumber + 1 ).arg( count ); + else + newtitle = QString( "#%1" ).arg( vp.pageNumber + 1 ); + } + else + newtitle = title; + + KUrl newurl = referurl; + newurl.setHTMLRef( vp.toString() ); + thebg.addBookmark( newtitle, newurl, QString() ); + if ( referurl == d->document->m_url ) + { + d->urlBookmarks[ vp.pageNumber ]++; + foreachObserver( notifyPageChanged( vp.pageNumber, DocumentObserver::Bookmark ) ); + } + d->manager->emitChanged( thebg ); + return true; +} + +void BookmarkManager::removeBookmark( int n ) +{ + if ( n >= 0 && n < (int)d->document->m_pagesVector.count() ) + { + if ( removePageBookmark( n ) ) + foreachObserver( notifyPageChanged( n, DocumentObserver::Bookmark ) ); + } +} + +void BookmarkManager::removeBookmark( const DocumentViewport &vp ) +{ + int page = vp.pageNumber; + if ( page >= 0 && page < d->document->m_pagesVector.count() ) + { + removeBookmark( d->url, bookmark( vp ) ); + } +} + +void BookmarkManager::renameBookmark( KBookmark* bm, const QString& newName) +{ + KBookmarkGroup thebg; + QHash::iterator it = d->bookmarkFind( d->url, false, &thebg ); + Q_ASSERT ( it != d->knownFiles.end() ); + if ( it == d->knownFiles.end() ) + return; + + bm->setFullText( newName ); + d->manager->emitChanged( thebg ); +} + +void BookmarkManager::renameBookmark( const KUrl& referurl, const QString& newName ) +{ + if ( !referurl.isValid() ) + return; + + KBookmarkGroup thebg; + QHash::iterator it = d->bookmarkFind( referurl, false, &thebg ); + Q_ASSERT ( it != d->knownFiles.end() ); + if ( it == d->knownFiles.end() ) + return; + + thebg.setFullText( newName ); + d->manager->emitChanged( thebg ); +} + +QString BookmarkManager::titleForUrl( const KUrl& referurl ) const +{ + KBookmarkGroup thebg; + QHash::iterator it = d->bookmarkFind( referurl, false, &thebg ); + Q_ASSERT( it != d->knownFiles.end() ); + + return thebg.fullText(); +} + +int BookmarkManager::removeBookmark( const KUrl& referurl, const KBookmark& bm ) +{ + if ( !referurl.isValid() || bm.isNull() || bm.isGroup() || bm.isSeparator() ) + return -1; + + DocumentViewport vp( bm.url().htmlRef() ); + if ( !vp.isValid() ) + return -1; + + KBookmarkGroup thebg; + QHash::iterator it = d->bookmarkFind( referurl, false, &thebg ); + if ( it == d->knownFiles.end() ) + return -1; + + thebg.deleteBookmark( bm ); + + if ( referurl == d->document->m_url ) + { + d->urlBookmarks[ vp.pageNumber ]--; + foreachObserver( notifyPageChanged( vp.pageNumber, DocumentObserver::Bookmark ) ); + } + d->manager->emitChanged( thebg ); + + return vp.pageNumber; +} + +void BookmarkManager::removeBookmarks( const KUrl& referurl, const KBookmark::List& list ) +{ + if ( !referurl.isValid() || list.isEmpty() ) + return; + + KBookmarkGroup thebg; + QHash::iterator it = d->bookmarkFind( referurl, false, &thebg ); + if ( it == d->knownFiles.end() ) + return; + + const QHash oldUrlBookmarks = d->urlBookmarks; + bool deletedAny = false; + foreach ( const KBookmark & bm, list ) + { + if ( bm.parentGroup() == thebg ) + { + thebg.deleteBookmark( bm ); + deletedAny = true; + + DocumentViewport vp( bm.url().htmlRef() ); + if ( referurl == d->document->m_url ) + { + d->urlBookmarks[ vp.pageNumber ]--; + } + } + } + + if ( referurl == d->document->m_url ) + { + for ( int i = 0; i < qMax( oldUrlBookmarks.size(), d->urlBookmarks.size() ); i++ ) + { + bool oldContains = oldUrlBookmarks.contains(i) && oldUrlBookmarks[i] > 0; + bool curContains = d->urlBookmarks.contains(i) && d->urlBookmarks[i] > 0; + + if ( oldContains != curContains ) + { + foreachObserver( notifyPageChanged( i, DocumentObserver::Bookmark ) ); + } + else if ( oldContains && oldUrlBookmarks[i] != d->urlBookmarks[i] ) + { + foreachObserver( notifyPageChanged( i, DocumentObserver::Bookmark ) ); + } + } + } + if ( deletedAny ) + d->manager->emitChanged( thebg ); +} + +QList< QAction * > BookmarkManager::actionsForUrl( const KUrl& url ) const +{ + QList< QAction * > ret; + KBookmarkGroup group = d->manager->root(); + for ( KBookmark bm = group.first(); !bm.isNull(); bm = group.next( bm ) ) + { + if ( !bm.isGroup() || urlForGroup( bm ) != url ) + continue; + + KBookmarkGroup group = bm.toGroup(); + for ( KBookmark b = group.first(); !b.isNull(); b = group.next( b ) ) + { + if ( b.isSeparator() || b.isGroup() ) + continue; + + ret.append( new OkularBookmarkAction( DocumentViewport( b.url().htmlRef() ), b, d, 0 ) ); + } + break; + } + qSort( ret.begin(), ret.end(), okularBookmarkActionLessThan ); + return ret; +} + +void BookmarkManager::setUrl( const KUrl& url ) +{ + d->url = url; + d->urlBookmarks.clear(); + KBookmarkGroup thebg; + QHash::iterator it = d->bookmarkFind( url, false, &thebg ); + if ( it != d->knownFiles.end() ) + { + for ( KBookmark bm = thebg.first(); !bm.isNull(); bm = thebg.next( bm ) ) + { + if ( bm.isSeparator() || bm.isGroup() ) + continue; + + DocumentViewport vp( bm.url().htmlRef() ); + if ( !vp.isValid() ) + continue; + + d->urlBookmarks[ vp.pageNumber ]++; + } + } +} + +bool BookmarkManager::setPageBookmark( int page ) +{ + KBookmarkGroup thebg; + QHash::iterator it = d->bookmarkFind( d->url, true, &thebg ); + Q_ASSERT( it != d->knownFiles.end() ); + + bool found = false; + bool added = false; + for ( KBookmark bm = thebg.first(); !found && !bm.isNull(); bm = thebg.next( bm ) ) + { + if ( bm.isSeparator() || bm.isGroup() ) + continue; + + DocumentViewport vp( bm.url().htmlRef() ); + if ( vp.isValid() && vp.pageNumber == page ) + found = true; + + } + if ( !found ) + { + d->urlBookmarks[ page ]++; + DocumentViewport vp; + vp.pageNumber = page; + KUrl newurl = d->url; + newurl.setHTMLRef( vp.toString() ); + thebg.addBookmark( QString::fromLatin1( "#" ) + QString::number( vp.pageNumber + 1 ), newurl, QString() ); + added = true; + d->manager->emitChanged( thebg ); + } + return added; +} + +bool BookmarkManager::removePageBookmark( int page ) +{ + KBookmarkGroup thebg; + QHash::iterator it = d->bookmarkFind( d->url, false, &thebg ); + if ( it == d->knownFiles.end() ) + return false; + + bool found = false; + for ( KBookmark bm = thebg.first(); !found && !bm.isNull(); bm = thebg.next( bm ) ) + { + if ( bm.isSeparator() || bm.isGroup() ) + continue; + + DocumentViewport vp( bm.url().htmlRef() ); + if ( vp.isValid() && vp.pageNumber == page ) + { + found = true; + thebg.deleteBookmark( bm ); + d->urlBookmarks[ page ]--; + d->manager->emitChanged( thebg ); + } + } + return found; +} + +bool BookmarkManager::isBookmarked( int page ) const +{ + return d->urlBookmarks.contains( page ) && d->urlBookmarks[ page ] > 0; +} + +bool BookmarkManager::isBookmarked( const DocumentViewport &viewport ) const +{ + KBookmark bm = bookmark( viewport ); + + return !bm.isNull(); +} + +KBookmark BookmarkManager::nextBookmark( const DocumentViewport &viewport) const +{ + KBookmark::List bmarks = bookmarks(); + qSort( bmarks.begin(), bmarks.end(), bookmarkLessThan); + + KBookmark bookmark; + foreach ( const KBookmark &bm, bmarks ) + { + DocumentViewport vp( bm.url().htmlRef() ); + if ( viewport < vp ) + { + bookmark = bm; + break; + } + } + + return bookmark; +} + +KBookmark BookmarkManager::previousBookmark( const DocumentViewport &viewport ) const +{ + KBookmark::List bmarks = bookmarks(); + qSort( bmarks.begin(), bmarks.end(), bookmarkLessThan ); + + KBookmark bookmark; + for ( KBookmark::List::const_iterator it = bmarks.constEnd(); it != bmarks.constBegin(); --it ) + { + KBookmark bm = *(it-1); + DocumentViewport vp( bm.url().htmlRef() ); + if ( vp < viewport ) + { + bookmark = bm; + break; + } + } + + return bookmark; +} + +#undef foreachObserver +#undef foreachObserverD + +#include "bookmarkmanager.moc" + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/okular/core/bookmarkmanager.h b/okular/core/bookmarkmanager.h new file mode 100644 index 00000000..e24efc66 --- /dev/null +++ b/okular/core/bookmarkmanager.h @@ -0,0 +1,212 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_BOOKMARK_MANAGER_H_ +#define _OKULAR_BOOKMARK_MANAGER_H_ + +#include + +#include "okular_export.h" + +class QAction; +class KUrl; + +namespace Okular { + +class Document; +class DocumentPrivate; +class DocumentViewport; + +/** + * @brief Bookmarks manager utility. + * + * This class is responsible for loading and saving the bookmarks using the + * proper format, and for working with them (eg querying, adding, removing). + */ +class OKULAR_EXPORT BookmarkManager : public QObject +{ + Q_OBJECT + + public: + virtual ~BookmarkManager(); + + /** + * Returns the list of documents with bookmarks. + */ + KUrl::List files() const; + + /** + * Returns the list of bookmarks for the specified @p url. + */ + KBookmark::List bookmarks( const KUrl& url ) const; + + /** + * Returns the list of bookmarks for document + * @since 0.14 (KDE 4.8) + */ + KBookmark::List bookmarks() const; + + /** + * Returns the list of bookmarks for the given page of the document + * @since 0.15 (KDE 4.9) + */ + KBookmark::List bookmarks( int page ) const; + + /** + * Returns the bookmark for the given page of the document + * @since 0.14 (KDE 4.8) + */ + KBookmark bookmark( int page ) const; + + /** + * Returns the bookmark for the given @p viewport of the document + * @since 0.15 (KDE 4.9) + */ + KBookmark bookmark( const DocumentViewport &viewport ) const; + + /** + * Forces to save the list of bookmarks. + */ + void save() const; + + /** + * Adds a bookmark for the given @p page. + */ + void addBookmark( int page ); + + /** + * Adds a bookmark for the given viewport @p vp + * @since 0.15 (KDE 4.9) + */ + void addBookmark( const DocumentViewport &vp ); + + /** + * Adds a new bookmark for the @p referurl at the specified viewport @p vp, + * with an optional @p title. + * + * If no @p title is specified, then \em #n will be used. + */ + bool addBookmark( const KUrl& referurl, const Okular::DocumentViewport& vp, const QString& title = QString() ); + + /** + * Remove a bookmark for the given @p page. + */ + void removeBookmark( int page ); + + /** + * Remove a bookmark for the given viewport @p vp + * @since 0.15 (KDE 4.9) + */ + void removeBookmark( const DocumentViewport &vp ); + + /** + * Removes the bookmark @p bm for the @p referurl specified. + */ + int removeBookmark( const KUrl& referurl, const KBookmark& bm ); + + /** + * Removes the bookmarks in @p list for the @p referurl specified. + * + * @note it will remove only the bookmarks which belong to @p referurl + * + * @since 0.11 (KDE 4.5) + */ + void removeBookmarks( const KUrl& referurl, const KBookmark::List& list ); + + /** + * Returns the bookmark given bookmark of the document + * @since 0.14 (KDE 4.8) + */ + void renameBookmark( KBookmark* bm, const QString& newName ); + + /** + * Renames the top-level bookmark for the @p referurl specified with + * the @p newName specified. + * @since 0.15 (KDE 4.9) + */ + void renameBookmark( const KUrl& referurl, const QString& newName ); + + /** + * Returns title for the @p referurl + * @since 0.15 (KDE 4.9) + */ + QString titleForUrl( const KUrl& referurl ) const; + + /** + * Returns whether the given @p page is bookmarked. + */ + bool isBookmarked( int page ) const; + + /** + * Return whether the given @p viewport is bookmarked. + * @since 0.15 (KDE 4.9) + */ + bool isBookmarked( const DocumentViewport &viewport ) const; + + /** + * Given a @p viewport, returns the next bookmark + * @since 0.15 (KDE 4.9) + */ + KBookmark nextBookmark( const DocumentViewport &viewport ) const; + + /** + * Given a @p viewport, returns the previous bookmark + * @since 0.15 (KDE 4.9) + */ + KBookmark previousBookmark( const DocumentViewport &viewport ) const; + + /** + * Returns a list of actions for the bookmarks of the specified @p url. + * + * @note the actions will have no parents, so you have to delete them + * yourself + */ + QList< QAction* > actionsForUrl( const KUrl& url ) const; + + Q_SIGNALS: + /** + * The bookmark manager is requesting to open the specified @p url. + */ + void openUrl( const KUrl& url ); + + /** + * This signal is emitted whenever bookmarks have been saved. + */ + void saved(); + + /** + * The bookmarks for specified @p url were changed. + * + * @since 0.7 (KDE 4.1) + */ + void bookmarksChanged( const KUrl& url ); + + private: + class Private; + Private * const d; + friend class Private; + + // private interface used by the Document + friend class Document; + friend class DocumentPrivate; + + BookmarkManager( DocumentPrivate * document ); + + void setUrl( const KUrl& url ); + bool setPageBookmark( int page ); + bool removePageBookmark( int page ); + + Q_DISABLE_COPY( BookmarkManager ) + + Q_PRIVATE_SLOT( d, void _o_changed( const QString &, const QString & ) ) +}; + +} + +#endif diff --git a/okular/core/chooseenginedialog.cpp b/okular/core/chooseenginedialog.cpp new file mode 100644 index 00000000..657c95d2 --- /dev/null +++ b/okular/core/chooseenginedialog.cpp @@ -0,0 +1,46 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "chooseenginedialog_p.h" + +#include +#include + +#include + +#include "ui_chooseenginewidget.h" + +ChooseEngineDialog::ChooseEngineDialog( const QStringList &generators, const KMimeType::Ptr &mime, QWidget * parent ) + : KDialog( parent ) +{ + setCaption( i18n( "Backend Selection" ) ); + setButtons( Ok | Cancel ); + setDefaultButton( Ok ); + QWidget *main = new QWidget( this ); + setMainWidget( main ); + m_widget = new Ui_ChooseEngineWidget(); + m_widget->setupUi( main ); + + m_widget->engineList->addItems(generators); + + m_widget->description->setText( + i18n( "More than one backend found for the MIME type:
" + "%1 (%2).

" + "Please select which one to use:
", mime->comment(), mime->name() ) ); +} + +ChooseEngineDialog::~ChooseEngineDialog() +{ + delete m_widget; +} + +int ChooseEngineDialog::selectedGenerator() const +{ + return m_widget->engineList->currentIndex(); +} diff --git a/okular/core/chooseenginedialog_p.h b/okular/core/chooseenginedialog_p.h new file mode 100644 index 00000000..20ae7394 --- /dev/null +++ b/okular/core/chooseenginedialog_p.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _CHOOSEENGINEDIALOG_H +#define _CHOOSEENGINEDIALOG_H + +#include + +#include +#include + +class Ui_ChooseEngineWidget; + +class ChooseEngineDialog : public KDialog +{ + public: + ChooseEngineDialog( const QStringList &generators, const KMimeType::Ptr &mime, QWidget * parent = 0 ); + ~ChooseEngineDialog(); + + int selectedGenerator() const; + + protected: + Ui_ChooseEngineWidget * m_widget; +}; + +#endif diff --git a/okular/core/chooseenginewidget.ui b/okular/core/chooseenginewidget.ui new file mode 100644 index 00000000..541d2841 --- /dev/null +++ b/okular/core/chooseenginewidget.ui @@ -0,0 +1,42 @@ + + ChooseEngineWidget + + + + 0 + 0 + 286 + 96 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::RichText + + + + + + + + + + + diff --git a/okular/core/debug_p.h b/okular/core/debug_p.h new file mode 100644 index 00000000..bb884abf --- /dev/null +++ b/okular/core/debug_p.h @@ -0,0 +1,15 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKUAR_DEBUG_P_H_ +#define _OKUAR_DEBUG_P_H_ + +#define OkularDebug 4700 + +#endif diff --git a/okular/core/document.cpp b/okular/core/document.cpp new file mode 100644 index 00000000..99e05630 --- /dev/null +++ b/okular/core/document.cpp @@ -0,0 +1,4840 @@ +/*************************************************************************** + * Copyright (C) 2004-2005 by Enrico Ros * + * Copyright (C) 2004-2008 by Albert Astals Cid * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "document.h" +#include "document_p.h" +#include "documentcommands_p.h" + +#include +#ifdef Q_OS_WIN +#define _WIN32_WINNT 0x0500 +#include +#elif defined(Q_OS_FREEBSD) +#include +#include +#include +#endif + +// qt/kde/system includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// local includes +#include "action.h" +#include "annotations.h" +#include "annotations_p.h" +#include "audioplayer.h" +#include "audioplayer_p.h" +#include "bookmarkmanager.h" +#include "chooseenginedialog_p.h" +#include "debug_p.h" +#include "generator_p.h" +#include "interfaces/configinterface.h" +#include "interfaces/guiinterface.h" +#include "interfaces/printinterface.h" +#include "interfaces/saveinterface.h" +#include "observer.h" +#include "misc.h" +#include "page.h" +#include "page_p.h" +#include "pagecontroller_p.h" +#include "scripter.h" +#include "settings_core.h" +#include "sourcereference.h" +#include "sourcereference_p.h" +#include "texteditors_p.h" +#include "tile.h" +#include "tilesmanager_p.h" +#include "utils_p.h" +#include "view.h" +#include "view_p.h" +#include "form.h" +#include "utils.h" + +#include + +#include + +using namespace Okular; + +struct AllocatedPixmap +{ + // owner of the page + DocumentObserver *observer; + int page; + qulonglong memory; + // public constructor: initialize data + AllocatedPixmap( DocumentObserver *o, int p, qulonglong m ) : observer( o ), page( p ), memory( m ) {} +}; + +struct ArchiveData +{ + ArchiveData() + { + } + + KTemporaryFile document; + KTemporaryFile metadataFile; +}; + +struct RunningSearch +{ + // store search properties + int continueOnPage; + RegularAreaRect continueOnMatch; + QSet< int > highlightedPages; + + // fields related to previous searches (used for 'continueSearch') + QString cachedString; + Document::SearchType cachedType; + Qt::CaseSensitivity cachedCaseSensitivity; + bool cachedViewportMove : 1; + bool isCurrentlySearching : 1; + QColor cachedColor; + int pagesDone; +}; + +#define foreachObserver( cmd ) {\ + QSet< DocumentObserver * >::const_iterator it=d->m_observers.constBegin(), end=d->m_observers.constEnd();\ + for ( ; it != end ; ++ it ) { (*it)-> cmd ; } } + +#define foreachObserverD( cmd ) {\ + QSet< DocumentObserver * >::const_iterator it = m_observers.constBegin(), end = m_observers.constEnd();\ + for ( ; it != end ; ++ it ) { (*it)-> cmd ; } } + +#define OKULAR_HISTORY_MAXSTEPS 100 +#define OKULAR_HISTORY_SAVEDSTEPS 10 + +/***** Document ******/ + +QString DocumentPrivate::pagesSizeString() const +{ + if (m_generator) + { + if (m_generator->pagesSizeMetric() != Generator::None) + { + QSizeF size = m_parent->allPagesSize(); + if (size.isValid()) return localizedSize(size); + else return QString(); + } + else return QString(); + } + else return QString(); +} + +QString DocumentPrivate::namePaperSize(double inchesWidth, double inchesHeight) const +{ + // Account for small deviations in paper sizes + static const double marginFactor = 0.03; + static const double lowerBoundFactor = 1.0 - marginFactor; + static const double upperBoundFactor = 1.0 + marginFactor; + + const QPrinter::Orientation orientation = inchesWidth > inchesHeight ? QPrinter::Landscape : QPrinter::Portrait; + // enforce portrait mode for further tests + if (inchesWidth > inchesHeight) + qSwap(inchesWidth, inchesHeight); + + // Use QPrinter to find which of the predefined paper sizes + // matches best the given paper width and height + QPrinter dummyPrinter; + QPrinter::PaperSize paperSize = QPrinter::Custom; + for (int i = 0; i < (int)QPrinter::NPaperSize; ++i) + { + const QPrinter::PaperSize ps = (QPrinter::PaperSize)i; + dummyPrinter.setPaperSize(ps); + const QSizeF definedPaperSize = dummyPrinter.paperSize(QPrinter::Inch); + + if (inchesWidth > definedPaperSize.width() * lowerBoundFactor && inchesWidth < definedPaperSize.width() * upperBoundFactor + && inchesHeight > definedPaperSize.height() * lowerBoundFactor && inchesHeight < definedPaperSize.height() * upperBoundFactor) + { + paperSize = ps; + break; + } + } + + // Handle all paper sizes defined in QPrinter, + // return string depending if paper's orientation is landscape or portrait + switch (paperSize) { + case QPrinter::A0: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A0") : i18nc("paper size", "portrait DIN/ISO A0"); + case QPrinter::A1: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A1") : i18nc("paper size", "portrait DIN/ISO A1"); + case QPrinter::A2: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A2") : i18nc("paper size", "portrait DIN/ISO A2"); + case QPrinter::A3: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A3") : i18nc("paper size", "portrait DIN/ISO A3"); + case QPrinter::A4: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A4") : i18nc("paper size", "portrait DIN/ISO A4"); + case QPrinter::A5: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A5") : i18nc("paper size", "portrait DIN/ISO A5"); + case QPrinter::A6: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A6") : i18nc("paper size", "portrait DIN/ISO A6"); + case QPrinter::A7: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A7") : i18nc("paper size", "portrait DIN/ISO A7"); + case QPrinter::A8: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A8") : i18nc("paper size", "portrait DIN/ISO A8"); + case QPrinter::A9: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A9") : i18nc("paper size", "portrait DIN/ISO A9"); + case QPrinter::B0: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B0") : i18nc("paper size", "portrait DIN/ISO B0"); + case QPrinter::B1: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B1") : i18nc("paper size", "portrait DIN/ISO B1"); + case QPrinter::B2: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B2") : i18nc("paper size", "portrait DIN/ISO B2"); + case QPrinter::B3: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B3") : i18nc("paper size", "portrait DIN/ISO B3"); + case QPrinter::B4: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B4") : i18nc("paper size", "portrait DIN/ISO B4"); + case QPrinter::B5: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B5") : i18nc("paper size", "portrait DIN/ISO B5"); + case QPrinter::B6: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B6") : i18nc("paper size", "portrait DIN/ISO B6"); + case QPrinter::B7: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B7") : i18nc("paper size", "portrait DIN/ISO B7"); + case QPrinter::B8: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B8") : i18nc("paper size", "portrait DIN/ISO B8"); + case QPrinter::B9: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B9") : i18nc("paper size", "portrait DIN/ISO B9"); + case QPrinter::B10: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B10") : i18nc("paper size", "portrait DIN/ISO B10"); + case QPrinter::Letter: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape letter") : i18nc("paper size", "portrait letter"); + case QPrinter::Legal: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape legal") : i18nc("paper size", "portrait legal"); + case QPrinter::Executive: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape executive") : i18nc("paper size", "portrait executive"); + case QPrinter::C5E: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape C5E") : i18nc("paper size", "portrait C5E"); + case QPrinter::Comm10E: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape Comm10E") : i18nc("paper size", "portrait Comm10E"); + case QPrinter::DLE: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DLE") : i18nc("paper size", "portrait DLE"); + case QPrinter::Folio: + return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape folio") : i18nc("paper size", "portrait folio"); + case QPrinter::Tabloid: + case QPrinter::Ledger: + /// Ledger and Tabloid are the same, just rotated by 90 degrees + return orientation == QPrinter::Landscape ? i18nc("paper size", "ledger") : i18nc("paper size", "tabloid"); + case QPrinter::Custom: + return orientation == QPrinter::Landscape ? i18nc("paper size", "unknown landscape paper size") : i18nc("paper size", "unknown portrait paper size"); + } + + kWarning() << "PaperSize" << paperSize << "has not been covered"; + return QString(); +} + +QString DocumentPrivate::localizedSize(const QSizeF &size) const +{ + double inchesWidth = 0, inchesHeight = 0; + switch (m_generator->pagesSizeMetric()) + { + case Generator::Points: + inchesWidth = size.width() / 72.0; + inchesHeight = size.height() / 72.0; + break; + + case Generator::Pixels: + { + const QSizeF dpi = m_generator->dpi(); + inchesWidth = size.width() / dpi.width(); + inchesHeight = size.height() / dpi.height(); + } + break; + + case Generator::None: + break; + } + if (KGlobal::locale()->measureSystem() == KLocale::Imperial) + { + return i18nc("%1 is width, %2 is height, %3 is paper size name", "%1 x %2 in (%3)", inchesWidth, inchesHeight, namePaperSize(inchesWidth, inchesHeight)); + } + else + { + return i18nc("%1 is width, %2 is height, %3 is paper size name", "%1 x %2 mm (%3)", QString::number(inchesWidth * 25.4, 'd', 0), QString::number(inchesHeight * 25.4, 'd', 0), namePaperSize(inchesWidth, inchesHeight)); + } +} + +qulonglong DocumentPrivate::calculateMemoryToFree() +{ + // [MEM] choose memory parameters based on configuration profile + qulonglong clipValue = 0; + qulonglong memoryToFree = 0; + + switch ( SettingsCore::memoryLevel() ) + { + case SettingsCore::EnumMemoryLevel::Low: + memoryToFree = m_allocatedPixmapsTotalMemory; + break; + + case SettingsCore::EnumMemoryLevel::Normal: + { + qulonglong thirdTotalMemory = getTotalMemory() / 3; + qulonglong freeMemory = getFreeMemory(); + if (m_allocatedPixmapsTotalMemory > thirdTotalMemory) memoryToFree = m_allocatedPixmapsTotalMemory - thirdTotalMemory; + if (m_allocatedPixmapsTotalMemory > freeMemory) clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2; + } + break; + + case SettingsCore::EnumMemoryLevel::Aggressive: + { + qulonglong freeMemory = getFreeMemory(); + if (m_allocatedPixmapsTotalMemory > freeMemory) clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2; + } + break; + case SettingsCore::EnumMemoryLevel::Greedy: + { + qulonglong freeSwap; + qulonglong freeMemory = getFreeMemory( &freeSwap ); + const qulonglong memoryLimit = qMin( qMax( freeMemory, getTotalMemory()/2 ), freeMemory+freeSwap ); + if (m_allocatedPixmapsTotalMemory > memoryLimit) clipValue = (m_allocatedPixmapsTotalMemory - memoryLimit) / 2; + } + break; + } + + if ( clipValue > memoryToFree ) + memoryToFree = clipValue; + + return memoryToFree; +} + +void DocumentPrivate::cleanupPixmapMemory() +{ + cleanupPixmapMemory( calculateMemoryToFree() ); +} + +void DocumentPrivate::cleanupPixmapMemory( qulonglong memoryToFree ) +{ + if ( memoryToFree < 1 ) + return; + + const int currentViewportPage = (*m_viewportIterator).pageNumber; + + // Create a QMap of visible rects, indexed by page number + QMap< int, VisiblePageRect * > visibleRects; + QVector< Okular::VisiblePageRect * >::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd(); + for ( ; vIt != vEnd; ++vIt ) + visibleRects.insert( (*vIt)->pageNumber, (*vIt) ); + + // Free memory starting from pages that are farthest from the current one + int pagesFreed = 0; + while ( memoryToFree > 0 ) + { + AllocatedPixmap * p = searchLowestPriorityPixmap( true, true ); + if ( !p ) // No pixmap to remove + break; + + kDebug().nospace() << "Evicting cache pixmap observer=" << p->observer << " page=" << p->page; + + // m_allocatedPixmapsTotalMemory can't underflow because we always add or remove + // the memory used by the AllocatedPixmap so at most it can reach zero + m_allocatedPixmapsTotalMemory -= p->memory; + // Make sure memoryToFree does not underflow + if ( p->memory > memoryToFree ) + memoryToFree = 0; + else + memoryToFree -= p->memory; + pagesFreed++; + // delete pixmap + m_pagesVector.at( p->page )->deletePixmap( p->observer ); + // delete allocation descriptor + delete p; + } + + // If we're still on low memory, try to free individual tiles + + // Store pages that weren't completely removed + + QLinkedList< AllocatedPixmap * > pixmapsToKeep; + while (memoryToFree > 0) + { + int clean_hits = 0; + foreach (DocumentObserver *observer, m_observers) + { + AllocatedPixmap * p = searchLowestPriorityPixmap( false, true, observer ); + if ( !p ) // No pixmap to remove + continue; + + clean_hits++; + + TilesManager *tilesManager = m_pagesVector.at( p->page )->d->tilesManager( observer ); + if ( tilesManager && tilesManager->totalMemory() > 0 ) + { + qulonglong memoryDiff = p->memory; + NormalizedRect visibleRect; + if ( visibleRects.contains( p->page ) ) + visibleRect = visibleRects[ p->page ]->rect; + + // Free non visible tiles + tilesManager->cleanupPixmapMemory( memoryToFree, visibleRect, currentViewportPage ); + + p->memory = tilesManager->totalMemory(); + memoryDiff -= p->memory; + memoryToFree = (memoryDiff < memoryToFree) ? (memoryToFree - memoryDiff) : 0; + m_allocatedPixmapsTotalMemory -= memoryDiff; + + if ( p->memory > 0 ) + pixmapsToKeep.append( p ); + else + delete p; + } + else + pixmapsToKeep.append( p ); + } + + if (clean_hits == 0) break; + } + + m_allocatedPixmaps += pixmapsToKeep; + //p--rintf("freeMemory A:[%d -%d = %d] \n", m_allocatedPixmaps.count() + pagesFreed, pagesFreed, m_allocatedPixmaps.count() ); +} + +/* Returns the next pixmap to evict from cache, or NULL if no suitable pixmap + * if found. If unloadableOnly is set, only unloadable pixmaps are returned. If + * thenRemoveIt is set, the pixmap is removed from m_allocatedPixmaps before + * returning it + */ +AllocatedPixmap * DocumentPrivate::searchLowestPriorityPixmap( bool unloadableOnly, bool thenRemoveIt, DocumentObserver *observer ) +{ + QLinkedList< AllocatedPixmap * >::iterator pIt = m_allocatedPixmaps.begin(); + QLinkedList< AllocatedPixmap * >::iterator pEnd = m_allocatedPixmaps.end(); + QLinkedList< AllocatedPixmap * >::iterator farthestPixmap = pEnd; + const int currentViewportPage = (*m_viewportIterator).pageNumber; + + /* Find the pixmap that is farthest from the current viewport */ + int maxDistance = -1; + while ( pIt != pEnd ) + { + const AllocatedPixmap * p = *pIt; + // Filter by observer + if ( observer == 0 || p->observer == observer ) + { + const int distance = qAbs( p->page - currentViewportPage ); + if ( maxDistance < distance && ( !unloadableOnly || p->observer->canUnloadPixmap( p->page ) ) ) + { + maxDistance = distance; + farthestPixmap = pIt; + } + } + ++pIt; + } + + /* No pixmap to remove */ + if ( farthestPixmap == pEnd ) + return 0; + + AllocatedPixmap * selectedPixmap = *farthestPixmap; + if ( thenRemoveIt ) + m_allocatedPixmaps.erase( farthestPixmap ); + return selectedPixmap; +} + +qulonglong DocumentPrivate::getTotalMemory() +{ + static qulonglong cachedValue = 0; + if ( cachedValue ) + return cachedValue; + +#if defined(Q_OS_LINUX) + // if /proc/meminfo doesn't exist, return 128MB + QFile memFile( "/proc/meminfo" ); + if ( !memFile.open( QIODevice::ReadOnly ) ) + return (cachedValue = 134217728); + + QTextStream readStream( &memFile ); + while ( true ) + { + QString entry = readStream.readLine(); + if ( entry.isNull() ) break; + if ( entry.startsWith( "MemTotal:" ) ) + return (cachedValue = (Q_UINT64_C(1024) * entry.section( ' ', -2, -2 ).toULongLong())); + } +#elif defined(Q_OS_FREEBSD) + qulonglong physmem; + int mib[] = {CTL_HW, HW_PHYSMEM}; + size_t len = sizeof( physmem ); + if ( sysctl( mib, 2, &physmem, &len, NULL, 0 ) == 0 ) + return (cachedValue = physmem); +#elif defined(Q_OS_WIN) + MEMORYSTATUSEX stat; + stat.dwLength = sizeof(stat); + GlobalMemoryStatusEx (&stat); + + return ( cachedValue = stat.ullTotalPhys ); +#endif + return (cachedValue = 134217728); +} + +qulonglong DocumentPrivate::getFreeMemory( qulonglong *freeSwap ) +{ + static QTime lastUpdate = QTime::currentTime().addSecs(-3); + static qulonglong cachedValue = 0; + static qulonglong cachedFreeSwap = 0; + + if ( qAbs( lastUpdate.secsTo( QTime::currentTime() ) ) <= 2 ) + { + if (freeSwap) + *freeSwap = cachedFreeSwap; + return cachedValue; + } + + /* Initialize the returned free swap value to 0. It is overwritten if the + * actual value is available */ + if (freeSwap) + *freeSwap = 0; + +#if defined(Q_OS_LINUX) + // if /proc/meminfo doesn't exist, return MEMORY FULL + QFile memFile( "/proc/meminfo" ); + if ( !memFile.open( QIODevice::ReadOnly ) ) + return 0; + + // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers' + // and 'Cached' fields. consider swapped memory as used memory. + qulonglong memoryFree = 0; + QString entry; + QTextStream readStream( &memFile ); + static const int nElems = 5; + QString names[nElems] = { "MemFree:", "Buffers:", "Cached:", "SwapFree:", "SwapTotal:" }; + qulonglong values[nElems] = { 0, 0, 0, 0, 0 }; + bool foundValues[nElems] = { false, false, false, false, false }; + while ( true ) + { + entry = readStream.readLine(); + if ( entry.isNull() ) break; + for ( int i = 0; i < nElems; ++i ) + { + if ( entry.startsWith( names[i] ) ) + { + values[i] = entry.section( ' ', -2, -2 ).toULongLong( &foundValues[i] ); + } + } + } + memFile.close(); + bool found = true; + for ( int i = 0; found && i < nElems; ++i ) + found = found && foundValues[i]; + if ( found ) + { + /* MemFree + Buffers + Cached - SwapUsed = + * = MemFree + Buffers + Cached - (SwapTotal - SwapFree) = + * = MemFree + Buffers + Cached + SwapFree - SwapTotal */ + memoryFree = values[0] + values[1] + values[2] + values[3]; + if ( values[4] > memoryFree ) + memoryFree = 0; + else + memoryFree -= values[4]; + } + else + { + return 0; + } + + lastUpdate = QTime::currentTime(); + + if (freeSwap) + *freeSwap = ( cachedFreeSwap = (Q_UINT64_C(1024) * values[3]) ); + return ( cachedValue = (Q_UINT64_C(1024) * memoryFree) ); +#elif defined(Q_OS_FREEBSD) + qulonglong cache, inact, free, psize; + size_t cachelen, inactlen, freelen, psizelen; + cachelen = sizeof( cache ); + inactlen = sizeof( inact ); + freelen = sizeof( free ); + psizelen = sizeof( psize ); + // sum up inactive, cached and free memory + if ( sysctlbyname( "vm.stats.vm.v_cache_count", &cache, &cachelen, NULL, 0 ) == 0 && + sysctlbyname( "vm.stats.vm.v_inactive_count", &inact, &inactlen, NULL, 0 ) == 0 && + sysctlbyname( "vm.stats.vm.v_free_count", &free, &freelen, NULL, 0 ) == 0 && + sysctlbyname( "vm.stats.vm.v_page_size", &psize, &psizelen, NULL, 0 ) == 0 ) + { + lastUpdate = QTime::currentTime(); + return (cachedValue = (cache + inact + free) * psize); + } + else + { + return 0; + } +#elif defined(Q_OS_WIN) + MEMORYSTATUSEX stat; + stat.dwLength = sizeof(stat); + GlobalMemoryStatusEx (&stat); + + lastUpdate = QTime::currentTime(); + + if (freeSwap) + *freeSwap = ( cachedFreeSwap = stat.ullAvailPageFile ); + return ( cachedValue = stat.ullAvailPhys ); +#else + // tell the memory is full.. will act as in LOW profile + return 0; +#endif +} + +void DocumentPrivate::loadDocumentInfo() +// note: load data and stores it internally (document or pages). observers +// are still uninitialized at this point so don't access them +{ + //kDebug(OkularDebug).nospace() << "Using '" << d->m_xmlFileName << "' as document info file."; + if ( m_xmlFileName.isEmpty() ) + return; + + QFile infoFile( m_xmlFileName ); + loadDocumentInfo( infoFile ); +} + +void DocumentPrivate::loadDocumentInfo( QFile &infoFile ) +{ + if ( !infoFile.exists() || !infoFile.open( QIODevice::ReadOnly ) ) + return; + + // Load DOM from XML file + QDomDocument doc( "documentInfo" ); + if ( !doc.setContent( &infoFile ) ) + { + kDebug(OkularDebug) << "Can't load XML pair! Check for broken xml."; + infoFile.close(); + return; + } + infoFile.close(); + + QDomElement root = doc.documentElement(); + if ( root.tagName() != "documentInfo" ) + return; + + KUrl documentUrl( root.attribute( "url" ) ); + + // Parse the DOM tree + QDomNode topLevelNode = root.firstChild(); + while ( topLevelNode.isElement() ) + { + QString catName = topLevelNode.toElement().tagName(); + + // Restore page attributes (bookmark, annotations, ...) from the DOM + if ( catName == "pageList" ) + { + QDomNode pageNode = topLevelNode.firstChild(); + while ( pageNode.isElement() ) + { + QDomElement pageElement = pageNode.toElement(); + if ( pageElement.hasAttribute( "number" ) ) + { + // get page number (node's attribute) + bool ok; + int pageNumber = pageElement.attribute( "number" ).toInt( &ok ); + + // pass the domElement to the right page, to read config data from + if ( ok && pageNumber >= 0 && pageNumber < (int)m_pagesVector.count() ) + m_pagesVector[ pageNumber ]->d->restoreLocalContents( pageElement ); + } + pageNode = pageNode.nextSibling(); + } + } + + // Restore 'general info' from the DOM + else if ( catName == "generalInfo" ) + { + QDomNode infoNode = topLevelNode.firstChild(); + while ( infoNode.isElement() ) + { + QDomElement infoElement = infoNode.toElement(); + + // restore viewports history + if ( infoElement.tagName() == "history" ) + { + // clear history + m_viewportHistory.clear(); + // append old viewports + QDomNode historyNode = infoNode.firstChild(); + while ( historyNode.isElement() ) + { + QDomElement historyElement = historyNode.toElement(); + if ( historyElement.hasAttribute( "viewport" ) ) + { + QString vpString = historyElement.attribute( "viewport" ); + m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(), + DocumentViewport( vpString ) ); + } + historyNode = historyNode.nextSibling(); + } + // consistancy check + if ( m_viewportHistory.isEmpty() ) + m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(), DocumentViewport() ); + } + else if ( infoElement.tagName() == "rotation" ) + { + QString str = infoElement.text(); + bool ok = true; + int newrotation = !str.isEmpty() ? ( str.toInt( &ok ) % 4 ) : 0; + if ( ok && newrotation != 0 ) + { + setRotationInternal( newrotation, false ); + } + } + else if ( infoElement.tagName() == "views" ) + { + QDomNode viewNode = infoNode.firstChild(); + while ( viewNode.isElement() ) + { + QDomElement viewElement = viewNode.toElement(); + if ( viewElement.tagName() == "view" ) + { + const QString viewName = viewElement.attribute( "name" ); + Q_FOREACH ( View * view, m_views ) + { + if ( view->name() == viewName ) + { + loadViewsInfo( view, viewElement ); + break; + } + } + } + viewNode = viewNode.nextSibling(); + } + } + infoNode = infoNode.nextSibling(); + } + } + + topLevelNode = topLevelNode.nextSibling(); + } // +} + +void DocumentPrivate::loadViewsInfo( View *view, const QDomElement &e ) +{ + QDomNode viewNode = e.firstChild(); + while ( viewNode.isElement() ) + { + QDomElement viewElement = viewNode.toElement(); + + if ( viewElement.tagName() == "zoom" ) + { + const QString valueString = viewElement.attribute( "value" ); + bool newzoom_ok = true; + const double newzoom = !valueString.isEmpty() ? valueString.toDouble( &newzoom_ok ) : 1.0; + if ( newzoom_ok && newzoom != 0 + && view->supportsCapability( View::Zoom ) + && ( view->capabilityFlags( View::Zoom ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) ) + { + view->setCapability( View::Zoom, newzoom ); + } + const QString modeString = viewElement.attribute( "mode" ); + bool newmode_ok = true; + const int newmode = !modeString.isEmpty() ? modeString.toInt( &newmode_ok ) : 2; + if ( newmode_ok + && view->supportsCapability( View::ZoomModality ) + && ( view->capabilityFlags( View::ZoomModality ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) ) + { + view->setCapability( View::ZoomModality, newmode ); + } + } + + viewNode = viewNode.nextSibling(); + } +} + +void DocumentPrivate::saveViewsInfo( View *view, QDomElement &e ) const +{ + if ( view->supportsCapability( View::Zoom ) + && ( view->capabilityFlags( View::Zoom ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) + && view->supportsCapability( View::ZoomModality ) + && ( view->capabilityFlags( View::ZoomModality ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) ) + { + QDomElement zoomEl = e.ownerDocument().createElement( "zoom" ); + e.appendChild( zoomEl ); + bool ok = true; + const double zoom = view->capability( View::Zoom ).toDouble( &ok ); + if ( ok && zoom != 0 ) + { + zoomEl.setAttribute( "value", QString::number(zoom) ); + } + const int mode = view->capability( View::ZoomModality ).toInt( &ok ); + if ( ok ) + { + zoomEl.setAttribute( "mode", mode ); + } + } +} + +QString DocumentPrivate::giveAbsolutePath( const QString & fileName ) const +{ + if ( !QDir::isRelativePath( fileName ) ) + return fileName; + + if ( !m_url.isValid() ) + return QString(); + + return m_url.upUrl().url() + fileName; +} + +bool DocumentPrivate::openRelativeFile( const QString & fileName ) +{ + QString absFileName = giveAbsolutePath( fileName ); + if ( absFileName.isEmpty() ) + return false; + + kDebug(OkularDebug).nospace() << "openDocument: '" << absFileName << "'"; + + emit m_parent->openUrl( absFileName ); + return true; +} + +Generator * DocumentPrivate::loadGeneratorLibrary( const KService::Ptr &service ) +{ + KPluginFactory *factory = KPluginLoader( service->library() ).factory(); + if ( !factory ) + { + kWarning(OkularDebug).nospace() << "Invalid plugin factory for " << service->library() << "!"; + return 0; + } + Generator * generator = factory->create< Okular::Generator >( service->pluginKeyword(), 0 ); + GeneratorInfo info( factory->componentData() ); + info.generator = generator; + if ( info.data.isValid() && info.data.aboutData() ) + info.catalogName = info.data.aboutData()->catalogName(); + m_loadedGenerators.insert( service->name(), info ); + return generator; +} + +void DocumentPrivate::loadAllGeneratorLibraries() +{ + if ( m_generatorsLoaded ) + return; + + m_generatorsLoaded = true; + + QString constraint("([X-KDE-Priority] > 0) and (exist Library)") ; + KService::List offers = KServiceTypeTrader::self()->query( "okular/Generator", constraint ); + loadServiceList( offers ); +} + +void DocumentPrivate::loadServiceList( const KService::List& offers ) +{ + int count = offers.count(); + if ( count <= 0 ) + return; + + for ( int i = 0; i < count; ++i ) + { + QString propName = offers.at(i)->name(); + // don't load already loaded generators + QHash< QString, GeneratorInfo >::const_iterator genIt = m_loadedGenerators.constFind( propName ); + if ( !m_loadedGenerators.isEmpty() && genIt != m_loadedGenerators.constEnd() ) + continue; + + Generator * g = loadGeneratorLibrary( offers.at(i) ); + (void)g; + } +} + +void DocumentPrivate::unloadGenerator( const GeneratorInfo& info ) +{ + delete info.generator; +} + +void DocumentPrivate::cacheExportFormats() +{ + if ( m_exportCached ) + return; + + const ExportFormat::List formats = m_generator->exportFormats(); + for ( int i = 0; i < formats.count(); ++i ) + { + if ( formats.at( i ).mimeType()->name() == QLatin1String( "text/plain" ) ) + m_exportToText = formats.at( i ); + else + m_exportFormats.append( formats.at( i ) ); + } + + m_exportCached = true; +} + +ConfigInterface* DocumentPrivate::generatorConfig( GeneratorInfo& info ) +{ + if ( info.configChecked ) + return info.config; + + info.config = qobject_cast< Okular::ConfigInterface * >( info.generator ); + info.configChecked = true; + return info.config; +} + +SaveInterface* DocumentPrivate::generatorSave( GeneratorInfo& info ) +{ + if ( info.saveChecked ) + return info.save; + + info.save = qobject_cast< Okular::SaveInterface * >( info.generator ); + info.saveChecked = true; + return info.save; +} + +Document::OpenResult DocumentPrivate::openDocumentInternal( const KService::Ptr& offer, bool isstdin, const QString& docFile, const QByteArray& filedata, const QString& password ) +{ + QString propName = offer->name(); + QHash< QString, GeneratorInfo >::const_iterator genIt = m_loadedGenerators.constFind( propName ); + QString catalogName; + if ( genIt != m_loadedGenerators.constEnd() ) + { + m_generator = genIt.value().generator; + catalogName = genIt.value().catalogName; + } + else + { + m_generator = loadGeneratorLibrary( offer ); + if ( !m_generator ) + return Document::OpenError; + genIt = m_loadedGenerators.constFind( propName ); + Q_ASSERT( genIt != m_loadedGenerators.constEnd() ); + catalogName = genIt.value().catalogName; + } + Q_ASSERT_X( m_generator, "Document::load()", "null generator?!" ); + + if ( !catalogName.isEmpty() ) + KGlobal::locale()->insertCatalog( catalogName ); + + m_generator->d_func()->m_document = this; + + // connect error reporting signals + QObject::connect( m_generator, SIGNAL(error(QString,int)), m_parent, SIGNAL(error(QString,int)) ); + QObject::connect( m_generator, SIGNAL(warning(QString,int)), m_parent, SIGNAL(warning(QString,int)) ); + QObject::connect( m_generator, SIGNAL(notice(QString,int)), m_parent, SIGNAL(notice(QString,int)) ); + + QApplication::setOverrideCursor( Qt::WaitCursor ); + + const QSizeF dpi = Utils::realDpi(m_widget); + kDebug() << "Output DPI:" << dpi; + m_generator->setDPI(dpi); + + Document::OpenResult openResult = Document::OpenError; + if ( !isstdin ) + { + openResult = m_generator->loadDocumentWithPassword( docFile, m_pagesVector, password ); + } + else if ( !filedata.isEmpty() ) + { + if ( m_generator->hasFeature( Generator::ReadRawData ) ) + { + openResult = m_generator->loadDocumentFromDataWithPassword( filedata, m_pagesVector, password ); + } + else + { + m_tempFile = new KTemporaryFile(); + if ( !m_tempFile->open() ) + { + delete m_tempFile; + m_tempFile = 0; + } + else + { + m_tempFile->write( filedata ); + QString tmpFileName = m_tempFile->fileName(); + m_tempFile->close(); + openResult = m_generator->loadDocumentWithPassword( tmpFileName, m_pagesVector, password ); + } + } + } + + QApplication::restoreOverrideCursor(); + if ( openResult != Document::OpenSuccess || m_pagesVector.size() <= 0 ) + { + if ( !catalogName.isEmpty() ) + KGlobal::locale()->removeCatalog( catalogName ); + + m_generator->d_func()->m_document = 0; + QObject::disconnect( m_generator, 0, m_parent, 0 ); + m_generator = 0; + + qDeleteAll( m_pagesVector ); + m_pagesVector.clear(); + delete m_tempFile; + m_tempFile = 0; + + // TODO: emit a message telling the document is empty + if ( openResult == Document::OpenSuccess ) + openResult = Document::OpenError; + } + + return openResult; +} + +bool DocumentPrivate::savePageDocumentInfo( KTemporaryFile *infoFile, int what ) const +{ + if ( infoFile->open() ) + { + // 1. Create DOM + QDomDocument doc( "documentInfo" ); + QDomProcessingInstruction xmlPi = doc.createProcessingInstruction( + QString::fromLatin1( "xml" ), QString::fromLatin1( "version=\"1.0\" encoding=\"utf-8\"" ) ); + doc.appendChild( xmlPi ); + QDomElement root = doc.createElement( "documentInfo" ); + doc.appendChild( root ); + + // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM + QDomElement pageList = doc.createElement( "pageList" ); + root.appendChild( pageList ); + // .... save pages that hold data + QVector< Page * >::const_iterator pIt = m_pagesVector.constBegin(), pEnd = m_pagesVector.constEnd(); + for ( ; pIt != pEnd; ++pIt ) + (*pIt)->d->saveLocalContents( pageList, doc, PageItems( what ) ); + + // 3. Save DOM to XML file + QString xml = doc.toString(); + QTextStream os( infoFile ); + os.setCodec( "UTF-8" ); + os << xml; + return true; + } + return false; +} + +DocumentViewport DocumentPrivate::nextDocumentViewport() const +{ + DocumentViewport ret = m_nextDocumentViewport; + if ( !m_nextDocumentDestination.isEmpty() && m_generator ) + { + DocumentViewport vp( m_generator->metaData( "NamedViewport", m_nextDocumentDestination ).toString() ); + if ( vp.isValid() ) + { + ret = vp; + } + } + return ret; +} + +void DocumentPrivate::warnLimitedAnnotSupport() +{ + if ( !m_showWarningLimitedAnnotSupport ) + return; + m_showWarningLimitedAnnotSupport = false; // Show the warning once + + if ( m_annotationsNeedSaveAs ) + { + // Shown if the user is editing annotations in a file whose metadata is + // not stored locally (.okular archives belong to this category) + KMessageBox::information( m_widget, i18n("Your annotation changes will not be saved automatically. Use File -> Save As...\nor your changes will be lost once the document is closed"), QString(), "annotNeedSaveAs" ); + } + else if ( !canAddAnnotationsNatively() ) + { + // If the generator doesn't support native annotations + KMessageBox::information( m_widget, i18n("Your annotations are saved internally by Okular.\nYou can export the annotated document using File -> Export As -> Document Archive"), QString(), "annotExportAsArchive" ); + } +} + +void DocumentPrivate::performAddPageAnnotation( int page, Annotation * annotation ) +{ + Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); + AnnotationProxy *proxy = iface ? iface->annotationProxy() : 0; + + // find out the page to attach annotation + Page * kp = m_pagesVector[ page ]; + if ( !m_generator || !kp ) + return; + + // the annotation belongs already to a page + if ( annotation->d_ptr->m_page ) + return; + + // add annotation to the page + kp->addAnnotation( annotation ); + + // tell the annotation proxy + if ( proxy && proxy->supports(AnnotationProxy::Addition) ) + proxy->notifyAddition( annotation, page ); + + // notify observers about the change + notifyAnnotationChanges( page ); + + if ( annotation->flags() & Annotation::ExternallyDrawn ) + { + // Redraw everything, including ExternallyDrawn annotations + refreshPixmaps( page ); + } + + warnLimitedAnnotSupport(); +} + +void DocumentPrivate::performRemovePageAnnotation( int page, Annotation * annotation ) +{ + Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); + AnnotationProxy *proxy = iface ? iface->annotationProxy() : 0; + bool isExternallyDrawn; + + // find out the page + Page * kp = m_pagesVector[ page ]; + if ( !m_generator || !kp ) + return; + + if ( annotation->flags() & Annotation::ExternallyDrawn ) + isExternallyDrawn = true; + else + isExternallyDrawn = false; + + // try to remove the annotation + if ( m_parent->canRemovePageAnnotation( annotation ) ) + { + // tell the annotation proxy + if ( proxy && proxy->supports(AnnotationProxy::Removal) ) + proxy->notifyRemoval( annotation, page ); + + kp->removeAnnotation( annotation ); // Also destroys the object + + // in case of success, notify observers about the change + notifyAnnotationChanges( page ); + + if ( isExternallyDrawn ) + { + // Redraw everything, including ExternallyDrawn annotations + refreshPixmaps( page ); + } + } + + warnLimitedAnnotSupport(); +} + +void DocumentPrivate::performModifyPageAnnotation( int page, Annotation * annotation, bool appearanceChanged ) +{ + Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); + AnnotationProxy *proxy = iface ? iface->annotationProxy() : 0; + + // find out the page + Page * kp = m_pagesVector[ page ]; + if ( !m_generator || !kp ) + return; + + // tell the annotation proxy + if ( proxy && proxy->supports(AnnotationProxy::Modification) ) + { + proxy->notifyModification( annotation, page, appearanceChanged ); + } + + // notify observers about the change + notifyAnnotationChanges( page ); + if ( appearanceChanged && (annotation->flags() & Annotation::ExternallyDrawn) ) + { + /* When an annotation is being moved, the generator will not render it. + * Therefore there's no need to refresh pixmaps after the first time */ + if ( annotation->flags() & Annotation::BeingMoved ) + { + if ( m_annotationBeingMoved ) + return; + else // First time: take note + m_annotationBeingMoved = true; + } + else + { + m_annotationBeingMoved = false; + } + + // Redraw everything, including ExternallyDrawn annotations + kDebug(OkularDebug) << "Refreshing Pixmaps"; + refreshPixmaps( page ); + } + + // If the user is moving the annotation, don't steal the focus + if ( (annotation->flags() & Annotation::BeingMoved) == 0 ) + warnLimitedAnnotSupport(); +} + +void DocumentPrivate::performSetAnnotationContents( const QString & newContents, Annotation *annot, int pageNumber ) +{ + bool appearanceChanged = false; + + // Check if appearanceChanged should be true + switch ( annot->subType() ) + { + // If it's an in-place TextAnnotation, set the inplace text + case Okular::Annotation::AText: + { + Okular::TextAnnotation * txtann = static_cast< Okular::TextAnnotation * >( annot ); + if ( txtann->textType() == Okular::TextAnnotation::InPlace ) + { + appearanceChanged = true; + } + break; + } + // If it's a LineAnnotation, check if caption text is visible + case Okular::Annotation::ALine: + { + Okular::LineAnnotation * lineann = static_cast< Okular::LineAnnotation * >( annot ); + if ( lineann->showCaption() ) + appearanceChanged = true; + break; + } + default: + break; + } + + // Set contents + annot->setContents( newContents ); + + // Tell the document the annotation has been modified + performModifyPageAnnotation( pageNumber, annot, appearanceChanged ); +} + +void DocumentPrivate::saveDocumentInfo() const +{ + if ( m_xmlFileName.isEmpty() ) + return; + + QFile infoFile( m_xmlFileName ); + if (infoFile.open( QIODevice::WriteOnly | QIODevice::Truncate) ) + { + // 1. Create DOM + QDomDocument doc( "documentInfo" ); + QDomProcessingInstruction xmlPi = doc.createProcessingInstruction( + QString::fromLatin1( "xml" ), QString::fromLatin1( "version=\"1.0\" encoding=\"utf-8\"" ) ); + doc.appendChild( xmlPi ); + QDomElement root = doc.createElement( "documentInfo" ); + root.setAttribute( "url", m_url.pathOrUrl() ); + doc.appendChild( root ); + + // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM + QDomElement pageList = doc.createElement( "pageList" ); + root.appendChild( pageList ); + PageItems saveWhat = AllPageItems; + if ( m_annotationsNeedSaveAs ) + { + /* In this case, if the user makes a modification, he's requested to + * save to a new document. Therefore, if there are existing local + * annotations, we save them back unmodified in the original + * document's metadata, so that it appears that it was not changed */ + saveWhat |= OriginalAnnotationPageItems; + } + // .... save pages that hold data + QVector< Page * >::const_iterator pIt = m_pagesVector.constBegin(), pEnd = m_pagesVector.constEnd(); + for ( ; pIt != pEnd; ++pIt ) + (*pIt)->d->saveLocalContents( pageList, doc, saveWhat ); + + // 2.2. Save document info (current viewport, history, ... ) to DOM + QDomElement generalInfo = doc.createElement( "generalInfo" ); + root.appendChild( generalInfo ); + // create rotation node + if ( m_rotation != Rotation0 ) + { + QDomElement rotationNode = doc.createElement( "rotation" ); + generalInfo.appendChild( rotationNode ); + rotationNode.appendChild( doc.createTextNode( QString::number( (int)m_rotation ) ) ); + } + // ... save history up to OKULAR_HISTORY_SAVEDSTEPS viewports + QLinkedList< DocumentViewport >::const_iterator backIterator = m_viewportIterator; + if ( backIterator != m_viewportHistory.constEnd() ) + { + // go back up to OKULAR_HISTORY_SAVEDSTEPS steps from the current viewportIterator + int backSteps = OKULAR_HISTORY_SAVEDSTEPS; + while ( backSteps-- && backIterator != m_viewportHistory.constBegin() ) + --backIterator; + + // create history root node + QDomElement historyNode = doc.createElement( "history" ); + generalInfo.appendChild( historyNode ); + + // add old[backIterator] and present[viewportIterator] items + QLinkedList< DocumentViewport >::const_iterator endIt = m_viewportIterator; + ++endIt; + while ( backIterator != endIt ) + { + QString name = (backIterator == m_viewportIterator) ? "current" : "oldPage"; + QDomElement historyEntry = doc.createElement( name ); + historyEntry.setAttribute( "viewport", (*backIterator).toString() ); + historyNode.appendChild( historyEntry ); + ++backIterator; + } + } + // create views root node + QDomElement viewsNode = doc.createElement( "views" ); + generalInfo.appendChild( viewsNode ); + Q_FOREACH ( View * view, m_views ) + { + QDomElement viewEntry = doc.createElement( "view" ); + viewEntry.setAttribute( "name", view->name() ); + viewsNode.appendChild( viewEntry ); + saveViewsInfo( view, viewEntry ); + } + + // 3. Save DOM to XML file + QString xml = doc.toString(); + QTextStream os( &infoFile ); + os.setCodec( "UTF-8" ); + os << xml; + } + infoFile.close(); +} + +void DocumentPrivate::slotTimedMemoryCheck() +{ + // [MEM] clean memory (for 'free mem dependant' profiles only) + if ( SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Low && + m_allocatedPixmapsTotalMemory > 1024*1024 ) + cleanupPixmapMemory(); +} + +void DocumentPrivate::sendGeneratorPixmapRequest() +{ + /* If the pixmap cache will have to be cleaned in order to make room for the + * next request, get the distance from the current viewport of the page + * whose pixmap will be removed. We will ignore preload requests for pages + * that are at the same distance or farther */ + const qulonglong memoryToFree = calculateMemoryToFree(); + const int currentViewportPage = (*m_viewportIterator).pageNumber; + int maxDistance = INT_MAX; // Default: No maximum + if ( memoryToFree ) + { + AllocatedPixmap *pixmapToReplace = searchLowestPriorityPixmap( true ); + if ( pixmapToReplace ) + maxDistance = qAbs( pixmapToReplace->page - currentViewportPage ); + } + + // find a request + PixmapRequest * request = 0; + m_pixmapRequestsMutex.lock(); + while ( !m_pixmapRequestsStack.isEmpty() && !request ) + { + PixmapRequest * r = m_pixmapRequestsStack.last(); + if (!r) + { + m_pixmapRequestsStack.pop_back(); + continue; + } + + QRect requestRect = r->isTile() ? r->normalizedRect().geometry( r->width(), r->height() ) : QRect( 0, 0, r->width(), r->height() ); + TilesManager *tilesManager = r->d->tilesManager(); + + // If it's a preload but the generator is not threaded no point in trying to preload + if ( r->preload() && !m_generator->hasFeature( Generator::Threaded ) ) + { + m_pixmapRequestsStack.pop_back(); + delete r; + } + // request only if page isn't already present and request has valid id + // request only if page isn't already present and request has valid id + else if ( ( !r->d->mForce && r->page()->hasPixmap( r->observer(), r->width(), r->height(), r->normalizedRect() ) ) || !m_observers.contains(r->observer()) ) + { + m_pixmapRequestsStack.pop_back(); + delete r; + } + else if ( !r->d->mForce && r->preload() && qAbs( r->pageNumber() - currentViewportPage ) >= maxDistance ) + { + m_pixmapRequestsStack.pop_back(); + //kDebug() << "Ignoring request that doesn't fit in cache"; + delete r; + } + // Ignore requests for pixmaps that are already being generated + else if ( tilesManager && tilesManager->isRequesting( r->normalizedRect(), r->width(), r->height() ) ) + { + m_pixmapRequestsStack.pop_back(); + delete r; + } + // If the requested area is above 8000000 pixels, switch on the tile manager + else if ( !tilesManager && m_generator->hasFeature( Generator::TiledRendering ) && (long)r->width() * (long)r->height() > 8000000L ) + { + // if the image is too big. start using tiles + kDebug(OkularDebug).nospace() << "Start using tiles on page " << r->pageNumber() + << " (" << r->width() << "x" << r->height() << " px);"; + + // fill the tiles manager with the last rendered pixmap + const QPixmap *pixmap = r->page()->_o_nearestPixmap( r->observer(), r->width(), r->height() ); + if ( pixmap ) + { + tilesManager = new TilesManager( r->pageNumber(), pixmap->width(), pixmap->height(), r->page()->rotation() ); + tilesManager->setPixmap( pixmap, NormalizedRect( 0, 0, 1, 1 ) ); + tilesManager->setSize( r->width(), r->height() ); + } + else + { + // create new tiles manager + tilesManager = new TilesManager( r->pageNumber(), r->width(), r->height(), r->page()->rotation() ); + } + tilesManager->setRequest( r->normalizedRect(), r->width(), r->height() ); + r->page()->deletePixmap( r->observer() ); + r->page()->d->setTilesManager( r->observer(), tilesManager ); + r->setTile( true ); + + // Change normalizedRect to the smallest rect that contains all + // visible tiles. + if ( !r->normalizedRect().isNull() ) + { + NormalizedRect tilesRect; + const QList tiles = tilesManager->tilesAt( r->normalizedRect(), TilesManager::TerminalTile ); + QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); + while ( tIt != tEnd ) + { + Tile tile = *tIt; + if ( tilesRect.isNull() ) + tilesRect = tile.rect(); + else + tilesRect |= tile.rect(); + + ++tIt; + } + + r->setNormalizedRect( tilesRect ); + request = r; + } + else + { + // Discard request if normalizedRect is null. This happens in + // preload requests issued by PageView if the requested page is + // not visible and the user has just switched from a non-tiled + // zoom level to a tiled one + m_pixmapRequestsStack.pop_back(); + delete r; + } + } + // If the requested area is below 6000000 pixels, switch off the tile manager + else if ( tilesManager && (long)r->width() * (long)r->height() < 6000000L ) + { + kDebug(OkularDebug).nospace() << "Stop using tiles on page " << r->pageNumber() + << " (" << r->width() << "x" << r->height() << " px);"; + + // page is too small. stop using tiles. + r->page()->deletePixmap( r->observer() ); + r->setTile( false ); + + request = r; + } + else if ( (long)requestRect.width() * (long)requestRect.height() > 20000000L ) + { + m_pixmapRequestsStack.pop_back(); + if ( !m_warnedOutOfMemory ) + { + kWarning(OkularDebug).nospace() << "Running out of memory on page " << r->pageNumber() + << " (" << r->width() << "x" << r->height() << " px);"; + kWarning(OkularDebug) << "this message will be reported only once."; + m_warnedOutOfMemory = true; + } + delete r; + } + else + { + request = r; + } + } + + // if no request found (or already generated), return + if ( !request ) + { + m_pixmapRequestsMutex.unlock(); + return; + } + + // [MEM] preventive memory freeing + qulonglong pixmapBytes = 0; + TilesManager * tm = request->d->tilesManager(); + if ( tm ) + pixmapBytes = tm->totalMemory(); + else + pixmapBytes = 4 * request->width() * request->height(); + + if ( pixmapBytes > (1024 * 1024) ) + cleanupPixmapMemory( memoryToFree /* previously calculated value */ ); + + // submit the request to the generator + if ( m_generator->canGeneratePixmap() ) + { + QRect requestRect = !request->isTile() ? QRect(0, 0, request->width(), request->height() ) : request->normalizedRect().geometry( request->width(), request->height() ); + kDebug(OkularDebug).nospace() << "sending request observer=" << request->observer() << " " <pageNumber() << " async == " << request->asynchronous() << " isTile == " << request->isTile(); + m_pixmapRequestsStack.removeAll ( request ); + + if ( tm ) + tm->setRequest( request->normalizedRect(), request->width(), request->height() ); + + if ( (int)m_rotation % 2 ) + request->d->swap(); + + if ( m_rotation != Rotation0 && !request->normalizedRect().isNull() ) + request->setNormalizedRect( TilesManager::fromRotatedRect( + request->normalizedRect(), m_rotation ) ); + + // we always have to unlock _before_ the generatePixmap() because + // a sync generation would end with requestDone() -> deadlock, and + // we can not really know if the generator can do async requests + m_executingPixmapRequests.push_back( request ); + m_pixmapRequestsMutex.unlock(); + m_generator->generatePixmap( request ); + } + else + { + m_pixmapRequestsMutex.unlock(); + // pino (7/4/2006): set the polling interval from 10 to 30 + QTimer::singleShot( 30, m_parent, SLOT(sendGeneratorPixmapRequest()) ); + } +} + +void DocumentPrivate::rotationFinished( int page, Okular::Page *okularPage ) +{ + Okular::Page *wantedPage = m_pagesVector.value( page, 0 ); + if ( !wantedPage || wantedPage != okularPage ) + return; + + foreach(DocumentObserver *o, m_observers) + o->notifyPageChanged( page, DocumentObserver::Pixmap | DocumentObserver::Annotations ); +} + +void DocumentPrivate::fontReadingProgress( int page ) +{ + emit m_parent->fontReadingProgress( page ); + + if ( page >= (int)m_parent->pages() - 1 ) + { + emit m_parent->fontReadingEnded(); + m_fontThread = 0; + m_fontsCached = true; + } +} + +void DocumentPrivate::fontReadingGotFont( const Okular::FontInfo& font ) +{ + // TODO try to avoid duplicate fonts + m_fontsCache.append( font ); + + emit m_parent->gotFont( font ); +} + +void DocumentPrivate::slotGeneratorConfigChanged( const QString& ) +{ + if ( !m_generator ) + return; + + // reparse generator config and if something changed clear Pages + bool configchanged = false; + QHash< QString, GeneratorInfo >::iterator it = m_loadedGenerators.begin(), itEnd = m_loadedGenerators.end(); + for ( ; it != itEnd; ++it ) + { + Okular::ConfigInterface * iface = generatorConfig( it.value() ); + if ( iface ) + { + bool it_changed = iface->reparseConfig(); + if ( it_changed && ( m_generator == it.value().generator ) ) + configchanged = true; + } + } + if ( configchanged ) + { + // invalidate pixmaps + QVector::const_iterator it = m_pagesVector.constBegin(), end = m_pagesVector.constEnd(); + for ( ; it != end; ++it ) { + (*it)->deletePixmaps(); + } + + // [MEM] remove allocation descriptors + qDeleteAll( m_allocatedPixmaps ); + m_allocatedPixmaps.clear(); + m_allocatedPixmapsTotalMemory = 0; + + // send reload signals to observers + foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap ) ); + } + + // free memory if in 'low' profile + if ( SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && + !m_allocatedPixmaps.isEmpty() && !m_pagesVector.isEmpty() ) + cleanupPixmapMemory(); +} + +void DocumentPrivate::refreshPixmaps( int pageNumber ) +{ + Page* page = m_pagesVector.value( pageNumber, 0 ); + if ( !page ) + return; + + QLinkedList< Okular::PixmapRequest * > requestedPixmaps; + QMap< DocumentObserver*, PagePrivate::PixmapObject >::ConstIterator it = page->d->m_pixmaps.constBegin(), itEnd = page->d->m_pixmaps.constEnd(); + for ( ; it != itEnd; ++it ) + { + QSize size = (*it).m_pixmap->size(); + PixmapRequest * p = new PixmapRequest( it.key(), pageNumber, size.width(), size.height(), 1, PixmapRequest::Asynchronous ); + p->d->mForce = true; + requestedPixmaps.push_back( p ); + } + + foreach (DocumentObserver *observer, m_observers) + { + TilesManager *tilesManager = page->d->tilesManager( observer ); + if ( tilesManager ) + { + tilesManager->markDirty(); + + PixmapRequest * p = new PixmapRequest( observer, pageNumber, tilesManager->width(), tilesManager->height(), 1, PixmapRequest::Asynchronous ); + + NormalizedRect tilesRect; + + // Get the visible page rect + NormalizedRect visibleRect; + QVector< Okular::VisiblePageRect * >::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd(); + for ( ; vIt != vEnd; ++vIt ) + { + if ( (*vIt)->pageNumber == pageNumber ) + { + visibleRect = (*vIt)->rect; + break; + } + } + + if ( !visibleRect.isNull() ) + { + p->setNormalizedRect( visibleRect ); + p->setTile( true ); + p->d->mForce = true; + requestedPixmaps.push_back( p ); + } + else + { + delete p; + } + } + } + + if ( !requestedPixmaps.isEmpty() ) + m_parent->requestPixmaps( requestedPixmaps, Okular::Document::NoOption ); +} + +void DocumentPrivate::_o_configChanged() +{ + // free text pages if needed + calculateMaxTextPages(); + while (m_allocatedTextPagesFifo.count() > m_maxAllocatedTextPages) + { + int pageToKick = m_allocatedTextPagesFifo.takeFirst(); + m_pagesVector.at(pageToKick)->setTextPage( 0 ); // deletes the textpage + } +} + +void DocumentPrivate::doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct) +{ + DoContinueDirectionMatchSearchStruct *searchStruct = static_cast(doContinueDirectionMatchSearchStruct); + RunningSearch *search = m_searches.value(searchStruct->searchID); + + if ((m_searchCancelled && !searchStruct->match) || !search) + { + // if the user cancelled but he just got a match, give him the match! + QApplication::restoreOverrideCursor(); + + if (search) search->isCurrentlySearching = false; + + emit m_parent->searchFinished( searchStruct->searchID, Document::SearchCancelled ); + delete searchStruct->pagesToNotify; + delete searchStruct; + return; + } + + const bool forward = search->cachedType == Document::NextMatch; + bool doContinue = false; + // if no match found, loop through the whole doc, starting from currentPage + if ( !searchStruct->match ) + { + const int pageCount = m_pagesVector.count(); + if (search->pagesDone < pageCount) + { + doContinue = true; + if ( searchStruct->currentPage >= pageCount || searchStruct->currentPage < 0 ) + { + doContinue = false; + search->isCurrentlySearching = false; + search->continueOnPage = forward ? 0 : pageCount - 1; + search->continueOnMatch = RegularAreaRect(); + emit m_parent->searchFinished ( searchStruct->searchID, Document::EndOfDocumentReached ); + } + } + } + + if (doContinue) + { + // get page + Page * page = m_pagesVector[ searchStruct->currentPage ]; + // request search page if needed + if ( !page->hasTextPage() ) + m_parent->requestTextPage( page->number() ); + + // if found a match on the current page, end the loop + searchStruct->match = page->findText( searchStruct->searchID, search->cachedString, forward ? FromTop : FromBottom, search->cachedCaseSensitivity ); + if ( !searchStruct->match ) + { + if (forward) searchStruct->currentPage++; + else searchStruct->currentPage--; + search->pagesDone++; + } + else + { + search->pagesDone = 1; + } + + // Both of the previous if branches need to call doContinueDirectionMatchSearch + QMetaObject::invokeMethod(m_parent, "doContinueDirectionMatchSearch", Qt::QueuedConnection, Q_ARG(void *, searchStruct)); + } + else + { + doProcessSearchMatch( searchStruct->match, search, searchStruct->pagesToNotify, searchStruct->currentPage, searchStruct->searchID, search->cachedViewportMove, search->cachedColor ); + delete searchStruct; + } +} + +void DocumentPrivate::doProcessSearchMatch( RegularAreaRect *match, RunningSearch *search, QSet< int > *pagesToNotify, int currentPage, int searchID, bool moveViewport, const QColor & color ) +{ + // reset cursor to previous shape + QApplication::restoreOverrideCursor(); + + bool foundAMatch = false; + + search->isCurrentlySearching = false; + + // if a match has been found.. + if ( match ) + { + // update the RunningSearch structure adding this match.. + foundAMatch = true; + search->continueOnPage = currentPage; + search->continueOnMatch = *match; + search->highlightedPages.insert( currentPage ); + // ..add highlight to the page.. + m_pagesVector[ currentPage ]->d->setHighlight( searchID, match, color ); + + // ..queue page for notifying changes.. + pagesToNotify->insert( currentPage ); + + // Create a normalized rectangle around the search match that includes a 5% buffer on all sides. + const Okular::NormalizedRect matchRectWithBuffer = Okular::NormalizedRect( match->first().left - 0.05, + match->first().top - 0.05, + match->first().right + 0.05, + match->first().bottom + 0.05 ); + + const bool matchRectFullyVisible = isNormalizedRectangleFullyVisible( matchRectWithBuffer, currentPage ); + + // ..move the viewport to show the first of the searched word sequence centered + if ( moveViewport && !matchRectFullyVisible ) + { + DocumentViewport searchViewport( currentPage ); + searchViewport.rePos.enabled = true; + searchViewport.rePos.normalizedX = (match->first().left + match->first().right) / 2.0; + searchViewport.rePos.normalizedY = (match->first().top + match->first().bottom) / 2.0; + m_parent->setViewport( searchViewport, 0, true ); + } + delete match; + } + + // notify observers about highlights changes + foreach(int pageNumber, *pagesToNotify) + foreach(DocumentObserver *observer, m_observers) + observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights ); + + if (foundAMatch) emit m_parent->searchFinished( searchID, Document::MatchFound ); + else emit m_parent->searchFinished( searchID, Document::NoMatchFound ); + + delete pagesToNotify; +} + +void DocumentPrivate::doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID) +{ + QMap< Page *, QVector > *pageMatches = static_cast< QMap< Page *, QVector > * >(pageMatchesMap); + QSet< int > *pagesToNotify = static_cast< QSet< int > * >( pagesToNotifySet ); + RunningSearch *search = m_searches.value(searchID); + + if (m_searchCancelled || !search) + { + typedef QVector MatchesVector; + + QApplication::restoreOverrideCursor(); + + if (search) search->isCurrentlySearching = false; + + emit m_parent->searchFinished( searchID, Document::SearchCancelled ); + foreach(const MatchesVector &mv, *pageMatches) qDeleteAll(mv); + delete pageMatches; + delete pagesToNotify; + return; + } + + if (currentPage < m_pagesVector.count()) + { + // get page (from the first to the last) + Page *page = m_pagesVector.at(currentPage); + int pageNumber = page->number(); // redundant? is it == currentPage ? + + // request search page if needed + if ( !page->hasTextPage() ) + m_parent->requestTextPage( pageNumber ); + + // loop on a page adding highlights for all found items + RegularAreaRect * lastMatch = 0; + while ( 1 ) + { + if ( lastMatch ) + lastMatch = page->findText( searchID, search->cachedString, NextResult, search->cachedCaseSensitivity, lastMatch ); + else + lastMatch = page->findText( searchID, search->cachedString, FromTop, search->cachedCaseSensitivity ); + + if ( !lastMatch ) + break; + + // add highligh rect to the matches map + (*pageMatches)[page].append(lastMatch); + } + delete lastMatch; + + QMetaObject::invokeMethod(m_parent, "doContinueAllDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotifySet), Q_ARG(void *, pageMatches), Q_ARG(int, currentPage + 1), Q_ARG(int, searchID)); + } + else + { + // reset cursor to previous shape + QApplication::restoreOverrideCursor(); + + search->isCurrentlySearching = false; + bool foundAMatch = pageMatches->count() != 0; + QMap< Page *, QVector >::const_iterator it, itEnd; + it = pageMatches->constBegin(); + itEnd = pageMatches->constEnd(); + for ( ; it != itEnd; ++it) + { + foreach(RegularAreaRect *match, it.value()) + { + it.key()->d->setHighlight( searchID, match, search->cachedColor ); + delete match; + } + search->highlightedPages.insert( it.key()->number() ); + pagesToNotify->insert( it.key()->number() ); + } + + foreach(DocumentObserver *observer, m_observers) + observer->notifySetup( m_pagesVector, 0 ); + + // notify observers about highlights changes + foreach(int pageNumber, *pagesToNotify) + foreach(DocumentObserver *observer, m_observers) + observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights ); + + if (foundAMatch) emit m_parent->searchFinished(searchID, Document::MatchFound ); + else emit m_parent->searchFinished( searchID, Document::NoMatchFound ); + + delete pageMatches; + delete pagesToNotify; + } +} + +void DocumentPrivate::doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList & words) +{ + typedef QPair MatchColor; + QMap< Page *, QVector > *pageMatches = static_cast< QMap< Page *, QVector > * >(pageMatchesMap); + QSet< int > *pagesToNotify = static_cast< QSet< int > * >( pagesToNotifySet ); + RunningSearch *search = m_searches.value(searchID); + + if (m_searchCancelled || !search) + { + typedef QVector MatchesVector; + + QApplication::restoreOverrideCursor(); + + if (search) search->isCurrentlySearching = false; + + emit m_parent->searchFinished( searchID, Document::SearchCancelled ); + + foreach(const MatchesVector &mv, *pageMatches) + { + foreach(const MatchColor &mc, mv) delete mc.first; + } + delete pageMatches; + delete pagesToNotify; + return; + } + + const int wordCount = words.count(); + const int hueStep = (wordCount > 1) ? (60 / (wordCount - 1)) : 60; + int baseHue, baseSat, baseVal; + search->cachedColor.getHsv( &baseHue, &baseSat, &baseVal ); + + if (currentPage < m_pagesVector.count()) + { + // get page (from the first to the last) + Page *page = m_pagesVector.at(currentPage); + int pageNumber = page->number(); // redundant? is it == currentPage ? + + // request search page if needed + if ( !page->hasTextPage() ) + m_parent->requestTextPage( pageNumber ); + + // loop on a page adding highlights for all found items + bool allMatched = wordCount > 0, + anyMatched = false; + for ( int w = 0; w < wordCount; w++ ) + { + const QString &word = words[ w ]; + int newHue = baseHue - w * hueStep; + if ( newHue < 0 ) + newHue += 360; + QColor wordColor = QColor::fromHsv( newHue, baseSat, baseVal ); + RegularAreaRect * lastMatch = 0; + // add all highlights for current word + bool wordMatched = false; + while ( 1 ) + { + if ( lastMatch ) + lastMatch = page->findText( searchID, word, NextResult, search->cachedCaseSensitivity, lastMatch ); + else + lastMatch = page->findText( searchID, word, FromTop, search->cachedCaseSensitivity); + + if ( !lastMatch ) + break; + + // add highligh rect to the matches map + (*pageMatches)[page].append(MatchColor(lastMatch, wordColor)); + wordMatched = true; + } + allMatched = allMatched && wordMatched; + anyMatched = anyMatched || wordMatched; + } + + // if not all words are present in page, remove partial highlights + const bool matchAll = search->cachedType == Document::GoogleAll; + if ( !allMatched && matchAll ) + { + QVector &matches = (*pageMatches)[page]; + foreach(const MatchColor &mc, matches) delete mc.first; + pageMatches->remove(page); + } + + QMetaObject::invokeMethod(m_parent, "doContinueGooglesDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotifySet), Q_ARG(void *, pageMatches), Q_ARG(int, currentPage + 1), Q_ARG(int, searchID), Q_ARG(QStringList, words)); + } + else + { + // reset cursor to previous shape + QApplication::restoreOverrideCursor(); + + search->isCurrentlySearching = false; + bool foundAMatch = pageMatches->count() != 0; + QMap< Page *, QVector >::const_iterator it, itEnd; + it = pageMatches->constBegin(); + itEnd = pageMatches->constEnd(); + for ( ; it != itEnd; ++it) + { + foreach(const MatchColor &mc, it.value()) + { + it.key()->d->setHighlight( searchID, mc.first, mc.second ); + delete mc.first; + } + search->highlightedPages.insert( it.key()->number() ); + pagesToNotify->insert( it.key()->number() ); + } + + // send page lists to update observers (since some filter on bookmarks) + foreach(DocumentObserver *observer, m_observers) + observer->notifySetup( m_pagesVector, 0 ); + + // notify observers about highlights changes + foreach(int pageNumber, *pagesToNotify) + foreach(DocumentObserver *observer, m_observers) + observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights ); + + if (foundAMatch) emit m_parent->searchFinished( searchID, Document::MatchFound ); + else emit m_parent->searchFinished( searchID, Document::NoMatchFound ); + + delete pageMatches; + delete pagesToNotify; + } +} + +QVariant DocumentPrivate::documentMetaData( const QString &key, const QVariant &option ) const +{ + if ( key == QLatin1String( "PaperColor" ) ) + { + bool giveDefault = option.toBool(); + // load paper color from Settings, or use the default color (white) + // if we were told to do so + QColor color; + if ( ( SettingsCore::renderMode() == SettingsCore::EnumRenderMode::Paper ) + && SettingsCore::changeColors() ) + { + color = SettingsCore::paperColor(); + } + else if ( giveDefault ) + { + color = Qt::white; + } + return color; + } + else if ( key == QLatin1String( "TextAntialias" ) ) + { + switch ( SettingsCore::textAntialias() ) + { + case SettingsCore::EnumTextAntialias::Enabled: + return true; + break; +#if 0 + case Settings::EnumTextAntialias::UseKDESettings: + // TODO: read the KDE configuration + return true; + break; +#endif + case SettingsCore::EnumTextAntialias::Disabled: + return false; + break; + } + } + else if ( key == QLatin1String( "GraphicsAntialias" ) ) + { + switch ( SettingsCore::graphicsAntialias() ) + { + case SettingsCore::EnumGraphicsAntialias::Enabled: + return true; + break; + case SettingsCore::EnumGraphicsAntialias::Disabled: + return false; + break; + } + } + else if ( key == QLatin1String( "TextHinting" ) ) + { + switch ( SettingsCore::textHinting() ) + { + case SettingsCore::EnumTextHinting::Enabled: + return true; + break; + case SettingsCore::EnumTextHinting::Disabled: + return false; + break; + } + } + return QVariant(); +} + +bool DocumentPrivate::isNormalizedRectangleFullyVisible( const Okular::NormalizedRect & rectOfInterest, int rectPage ) +{ + bool rectFullyVisible = false; + const QVector & visibleRects = m_parent->visiblePageRects(); + QVector::const_iterator vEnd = visibleRects.end(); + QVector::const_iterator vIt = visibleRects.begin(); + + for ( ; ( vIt != vEnd ) && !rectFullyVisible; ++vIt ) + { + if ( (*vIt)->pageNumber == rectPage && + (*vIt)->rect.contains( rectOfInterest.left, rectOfInterest.top ) && + (*vIt)->rect.contains( rectOfInterest.right, rectOfInterest.bottom ) ) + { + rectFullyVisible = true; + } + } + return rectFullyVisible; +} + +Document::Document( QWidget *widget ) + : QObject( 0 ), d( new DocumentPrivate( this ) ) +{ + d->m_widget = widget; + d->m_bookmarkManager = new BookmarkManager( d ); + d->m_viewportIterator = d->m_viewportHistory.insert( d->m_viewportHistory.end(), DocumentViewport() ); + d->m_undoStack = new QUndoStack(this); + + connect( SettingsCore::self(), SIGNAL(configChanged()), this, SLOT(_o_configChanged()) ); + connect( d->m_undoStack, SIGNAL( canUndoChanged(bool) ), this, SIGNAL( canUndoChanged(bool))); + connect( d->m_undoStack, SIGNAL( canRedoChanged(bool) ), this, SIGNAL( canRedoChanged(bool) ) ); + + qRegisterMetaType(); +} + +Document::~Document() +{ + // delete generator, pages, and related stuff + closeDocument(); + + QSet< View * >::const_iterator viewIt = d->m_views.constBegin(), viewEnd = d->m_views.constEnd(); + for ( ; viewIt != viewEnd; ++viewIt ) + { + View *v = *viewIt; + v->d_func()->document = 0; + } + + // delete the bookmark manager + delete d->m_bookmarkManager; + + // delete the loaded generators + QHash< QString, GeneratorInfo >::const_iterator it = d->m_loadedGenerators.constBegin(), itEnd = d->m_loadedGenerators.constEnd(); + for ( ; it != itEnd; ++it ) + d->unloadGenerator( it.value() ); + d->m_loadedGenerators.clear(); + + // delete the private structure + delete d; +} + +class kMimeTypeMoreThan { +public: + kMimeTypeMoreThan( const KMimeType::Ptr &mime ) : _mime( mime ) {} + bool operator()( const KService::Ptr &s1, const KService::Ptr &s2 ) + { + const QString mimeName = _mime->name(); + if (s1->mimeTypes().contains( mimeName ) && !s2->mimeTypes().contains( mimeName )) + return true; + else if (s2->mimeTypes().contains( mimeName ) && !s1->mimeTypes().contains( mimeName )) + return false; + return s1->property( "X-KDE-Priority" ).toInt() > s2->property( "X-KDE-Priority" ).toInt(); + } + +private: + const KMimeType::Ptr &_mime; +}; + +QString DocumentPrivate::docDataFileName(const KUrl &url, qint64 document_size) +{ + QString fn = url.fileName(); + fn = QString::number( document_size ) + '.' + fn + ".xml"; + QString newokular = "okular/docdata/" + fn; + QString newokularfile = KStandardDirs::locateLocal( "data", newokular ); + if ( !QFile::exists( newokularfile ) ) + { + QString oldkpdf = "kpdf/" + fn; + QString oldkpdffile = KStandardDirs::locateLocal( "data", oldkpdf ); + if ( QFile::exists( oldkpdffile ) ) + { + // ### copy or move? + if ( !QFile::copy( oldkpdffile, newokularfile ) ) + return QString(); + } + } + return newokularfile; +} + +Document::OpenResult Document::openDocument( const QString & docFile, const KUrl& url, const KMimeType::Ptr &_mime, const QString & password ) +{ + KMimeType::Ptr mime = _mime; + QByteArray filedata; + qint64 document_size = -1; + bool isstdin = url.fileName( KUrl::ObeyTrailingSlash ) == QLatin1String( "-" ); + bool triedMimeFromFileContent = false; + if ( !isstdin ) + { + if ( mime.count() <= 0 ) + return OpenError; + + // docFile is always local so we can use QFileInfo on it + QFileInfo fileReadTest( docFile ); + if ( fileReadTest.isFile() && !fileReadTest.isReadable() ) + { + d->m_docFileName.clear(); + return OpenError; + } + // determine the related "xml document-info" filename + d->m_url = url; + d->m_docFileName = docFile; + if ( url.isLocalFile() && !d->m_archiveData ) + { + document_size = fileReadTest.size(); + d->m_xmlFileName = DocumentPrivate::docDataFileName(url, document_size); + } + } + else + { + QFile qstdin; + qstdin.open( stdin, QIODevice::ReadOnly ); + filedata = qstdin.readAll(); + mime = KMimeType::findByContent( filedata ); + if ( !mime || mime->name() == QLatin1String( "application/octet-stream" ) ) + return OpenError; + document_size = filedata.size(); + triedMimeFromFileContent = true; + } + + // 0. load Generator + // request only valid non-disabled plugins suitable for the mimetype + QString constraint("([X-KDE-Priority] > 0) and (exist Library)") ; + KService::List offers = KMimeTypeTrader::self()->query(mime->name(),"okular/Generator",constraint); + if ( offers.isEmpty() && !triedMimeFromFileContent ) + { + KMimeType::Ptr newmime = KMimeType::findByFileContent( docFile ); + triedMimeFromFileContent = true; + if ( newmime->name() != mime->name() ) + { + mime = newmime; + offers = KMimeTypeTrader::self()->query( mime->name(), "okular/Generator", constraint ); + } + if ( offers.isEmpty() ) + { + // There's still no offers, do a final mime search based on the filename + // We need this because sometimes (e.g. when downloading from a webserver) the mimetype we + // use is the one fed by the server, that may be wrong + newmime = KMimeType::findByUrl( docFile ); + if ( newmime->name() != mime->name() ) + { + mime = newmime; + offers = KMimeTypeTrader::self()->query( mime->name(), "okular/Generator", constraint ); + } + } + } + if (offers.isEmpty()) + { + emit error( i18n( "Can not find a plugin which is able to handle the document being passed." ), -1 ); + kWarning(OkularDebug).nospace() << "No plugin for mimetype '" << mime->name() << "'."; + return OpenError; + } + int hRank=0; + // best ranked offer search + int offercount = offers.count(); + if ( offercount > 1 ) + { + // sort the offers: the offers with an higher priority come before + qStableSort( offers.begin(), offers.end(), kMimeTypeMoreThan( mime ) ); + + if ( SettingsCore::chooseGenerators() ) + { + QStringList list; + for ( int i = 0; i < offercount; ++i ) + { + list << offers.at(i)->name(); + } + + ChooseEngineDialog choose( list, mime, d->m_widget ); + + if ( choose.exec() == QDialog::Rejected ) + return OpenError; + + hRank = choose.selectedGenerator(); + } + } + + KService::Ptr offer = offers.at( hRank ); + // 1. load Document + OpenResult openResult = d->openDocumentInternal( offer, isstdin, docFile, filedata, password ); + if ( openResult == OpenError && !triedMimeFromFileContent ) + { + KMimeType::Ptr newmime = KMimeType::findByFileContent( docFile ); + triedMimeFromFileContent = true; + if ( newmime->name() != mime->name() ) + { + mime = newmime; + offers = KMimeTypeTrader::self()->query( mime->name(), "okular/Generator", constraint ); + if ( !offers.isEmpty() ) + { + offer = offers.first(); + openResult = d->openDocumentInternal( offer, isstdin, docFile, filedata, password ); + } + } + } + if ( openResult != OpenSuccess ) + { + return openResult; + } + + d->m_generatorName = offer->name(); + d->m_pageController = new PageController(); + connect( d->m_pageController, SIGNAL(rotationFinished(int,Okular::Page*)), + this, SLOT(rotationFinished(int,Okular::Page*)) ); + + bool containsExternalAnnotations = false; + foreach ( Page * p, d->m_pagesVector ) + { + p->d->m_doc = d; + if ( !p->annotations().empty() ) + containsExternalAnnotations = true; + } + + // Be quiet while restoring local annotations + d->m_showWarningLimitedAnnotSupport = false; + d->m_annotationsNeedSaveAs = false; + + // 2. load Additional Data (bookmarks, local annotations and metadata) about the document + if ( d->m_archiveData ) + { + d->loadDocumentInfo( d->m_archiveData->metadataFile ); + d->m_annotationsNeedSaveAs = true; + } + else + { + d->loadDocumentInfo(); + d->m_annotationsNeedSaveAs = ( d->canAddAnnotationsNatively() && containsExternalAnnotations ); + } + + d->m_showWarningLimitedAnnotSupport = true; + d->m_bookmarkManager->setUrl( d->m_url ); + + // 3. setup observers inernal lists and data + foreachObserver( notifySetup( d->m_pagesVector, DocumentObserver::DocumentChanged ) ); + + // 4. set initial page (restoring the page saved in xml if loaded) + DocumentViewport loadedViewport = (*d->m_viewportIterator); + if ( loadedViewport.isValid() ) + { + (*d->m_viewportIterator) = DocumentViewport(); + if ( loadedViewport.pageNumber >= (int)d->m_pagesVector.size() ) + loadedViewport.pageNumber = d->m_pagesVector.size() - 1; + } + else + loadedViewport.pageNumber = 0; + setViewport( loadedViewport ); + + // start bookmark saver timer + if ( !d->m_saveBookmarksTimer ) + { + d->m_saveBookmarksTimer = new QTimer( this ); + connect( d->m_saveBookmarksTimer, SIGNAL(timeout()), this, SLOT(saveDocumentInfo()) ); + } + d->m_saveBookmarksTimer->start( 5 * 60 * 1000 ); + + // start memory check timer + if ( !d->m_memCheckTimer ) + { + d->m_memCheckTimer = new QTimer( this ); + connect( d->m_memCheckTimer, SIGNAL(timeout()), this, SLOT(slotTimedMemoryCheck()) ); + } + d->m_memCheckTimer->start( 2000 ); + + const DocumentViewport nextViewport = d->nextDocumentViewport(); + if ( nextViewport.isValid() ) + { + setViewport( nextViewport ); + d->m_nextDocumentViewport = DocumentViewport(); + d->m_nextDocumentDestination = QString(); + } + + AudioPlayer::instance()->d->m_currentDocument = isstdin ? KUrl() : d->m_url; + d->m_docSize = document_size; + + const QStringList docScripts = d->m_generator->metaData( "DocumentScripts", "JavaScript" ).toStringList(); + if ( !docScripts.isEmpty() ) + { + d->m_scripter = new Scripter( d ); + Q_FOREACH ( const QString &docscript, docScripts ) + { + d->m_scripter->execute( JavaScript, docscript ); + } + } + + return OpenSuccess; +} + + +KXMLGUIClient* Document::guiClient() +{ + if ( d->m_generator ) + { + Okular::GuiInterface * iface = qobject_cast< Okular::GuiInterface * >( d->m_generator ); + if ( iface ) + return iface->guiClient(); + } + return 0; +} + +void Document::closeDocument() +{ + // check if there's anything to close... + if ( !d->m_generator ) + return; + + delete d->m_pageController; + d->m_pageController = 0; + + delete d->m_scripter; + d->m_scripter = 0; + + // remove requests left in queue + d->m_pixmapRequestsMutex.lock(); + QLinkedList< PixmapRequest * >::const_iterator sIt = d->m_pixmapRequestsStack.constBegin(); + QLinkedList< PixmapRequest * >::const_iterator sEnd = d->m_pixmapRequestsStack.constEnd(); + for ( ; sIt != sEnd; ++sIt ) + delete *sIt; + d->m_pixmapRequestsStack.clear(); + d->m_pixmapRequestsMutex.unlock(); + + QEventLoop loop; + bool startEventLoop = false; + do + { + d->m_pixmapRequestsMutex.lock(); + startEventLoop = !d->m_executingPixmapRequests.isEmpty(); + d->m_pixmapRequestsMutex.unlock(); + if ( startEventLoop ) + { + d->m_closingLoop = &loop; + loop.exec(); + d->m_closingLoop = 0; + } + } + while ( startEventLoop ); + + if ( d->m_fontThread ) + { + disconnect( d->m_fontThread, 0, this, 0 ); + d->m_fontThread->stopExtraction(); + d->m_fontThread->wait(); + d->m_fontThread = 0; + } + + // stop any audio playback + AudioPlayer::instance()->stopPlaybacks(); + + // close the current document and save document info if a document is still opened + if ( d->m_generator && d->m_pagesVector.size() > 0 ) + { + d->saveDocumentInfo(); + d->m_generator->closeDocument(); + } + + // stop timers + if ( d->m_memCheckTimer ) + d->m_memCheckTimer->stop(); + if ( d->m_saveBookmarksTimer ) + d->m_saveBookmarksTimer->stop(); + + if ( d->m_generator ) + { + // disconnect the generator from this document ... + d->m_generator->d_func()->m_document = 0; + // .. and this document from the generator signals + disconnect( d->m_generator, 0, this, 0 ); + + QHash< QString, GeneratorInfo >::const_iterator genIt = d->m_loadedGenerators.constFind( d->m_generatorName ); + Q_ASSERT( genIt != d->m_loadedGenerators.constEnd() ); + if ( !genIt.value().catalogName.isEmpty() && !genIt.value().config ) + KGlobal::locale()->removeCatalog( genIt.value().catalogName ); + } + d->m_generator = 0; + d->m_generatorName = QString(); + d->m_url = KUrl(); + d->m_docFileName = QString(); + d->m_xmlFileName = QString(); + delete d->m_tempFile; + d->m_tempFile = 0; + delete d->m_archiveData; + d->m_archiveData = 0; + d->m_docSize = -1; + d->m_exportCached = false; + d->m_exportFormats.clear(); + d->m_exportToText = ExportFormat(); + d->m_fontsCached = false; + d->m_fontsCache.clear(); + d->m_rotation = Rotation0; + + // send an empty list to observers (to free their data) + foreachObserver( notifySetup( QVector< Page * >(), DocumentObserver::DocumentChanged ) ); + + // delete pages and clear 'd->m_pagesVector' container + QVector< Page * >::const_iterator pIt = d->m_pagesVector.constBegin(); + QVector< Page * >::const_iterator pEnd = d->m_pagesVector.constEnd(); + for ( ; pIt != pEnd; ++pIt ) + delete *pIt; + d->m_pagesVector.clear(); + + // clear 'memory allocation' descriptors + qDeleteAll( d->m_allocatedPixmaps ); + d->m_allocatedPixmaps.clear(); + + // clear 'running searches' descriptors + QMap< int, RunningSearch * >::const_iterator rIt = d->m_searches.constBegin(); + QMap< int, RunningSearch * >::const_iterator rEnd = d->m_searches.constEnd(); + for ( ; rIt != rEnd; ++rIt ) + delete *rIt; + d->m_searches.clear(); + + // clear the visible areas and notify the observers + QVector< VisiblePageRect * >::const_iterator vIt = d->m_pageRects.constBegin(); + QVector< VisiblePageRect * >::const_iterator vEnd = d->m_pageRects.constEnd(); + for ( ; vIt != vEnd; ++vIt ) + delete *vIt; + d->m_pageRects.clear(); + foreachObserver( notifyVisibleRectsChanged() ); + + // reset internal variables + + d->m_viewportHistory.clear(); + d->m_viewportHistory.append( DocumentViewport() ); + d->m_viewportIterator = d->m_viewportHistory.begin(); + d->m_allocatedPixmapsTotalMemory = 0; + d->m_allocatedTextPagesFifo.clear(); + d->m_pageSize = PageSize(); + d->m_pageSizes.clear(); + + delete d->m_documentInfo; + d->m_documentInfo = 0; + + AudioPlayer::instance()->d->m_currentDocument = KUrl(); + + d->m_undoStack->clear(); +} + +void Document::addObserver( DocumentObserver * pObserver ) +{ + Q_ASSERT( !d->m_observers.contains( pObserver ) ); + d->m_observers << pObserver; + + // if the observer is added while a document is already opened, tell it + if ( !d->m_pagesVector.isEmpty() ) + { + pObserver->notifySetup( d->m_pagesVector, DocumentObserver::DocumentChanged ); + pObserver->notifyViewportChanged( false /*disables smoothMove*/ ); + } +} + +void Document::removeObserver( DocumentObserver * pObserver ) +{ + // remove observer from the map. it won't receive notifications anymore + if ( d->m_observers.contains( pObserver ) ) + { + // free observer's pixmap data + QVector::const_iterator it = d->m_pagesVector.constBegin(), end = d->m_pagesVector.constEnd(); + for ( ; it != end; ++it ) + (*it)->deletePixmap( pObserver ); + + // [MEM] free observer's allocation descriptors + QLinkedList< AllocatedPixmap * >::iterator aIt = d->m_allocatedPixmaps.begin(); + QLinkedList< AllocatedPixmap * >::iterator aEnd = d->m_allocatedPixmaps.end(); + while ( aIt != aEnd ) + { + AllocatedPixmap * p = *aIt; + if ( p->observer == pObserver ) + { + aIt = d->m_allocatedPixmaps.erase( aIt ); + delete p; + } + else + ++aIt; + } + + // delete observer entry from the map + d->m_observers.remove( pObserver ); + } +} + +void Document::reparseConfig() +{ + // reparse generator config and if something changed clear Pages + bool configchanged = false; + if ( d->m_generator ) + { + Okular::ConfigInterface * iface = qobject_cast< Okular::ConfigInterface * >( d->m_generator ); + if ( iface ) + configchanged = iface->reparseConfig(); + } + if ( configchanged ) + { + // invalidate pixmaps + QVector::const_iterator it = d->m_pagesVector.constBegin(), end = d->m_pagesVector.constEnd(); + for ( ; it != end; ++it ) { + (*it)->deletePixmaps(); + } + + // [MEM] remove allocation descriptors + qDeleteAll( d->m_allocatedPixmaps ); + d->m_allocatedPixmaps.clear(); + d->m_allocatedPixmapsTotalMemory = 0; + + // send reload signals to observers + foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap ) ); + } + + // free memory if in 'low' profile + if ( SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && + !d->m_allocatedPixmaps.isEmpty() && !d->m_pagesVector.isEmpty() ) + d->cleanupPixmapMemory(); +} + + +bool Document::isOpened() const +{ + return d->m_generator; +} + +bool Document::canConfigurePrinter( ) const +{ + if ( d->m_generator ) + { + Okular::PrintInterface * iface = qobject_cast< Okular::PrintInterface * >( d->m_generator ); + return iface ? true : false; + } + else + return 0; +} + +const DocumentInfo * Document::documentInfo() const +{ + if ( d->m_documentInfo ) + return d->m_documentInfo; + + if ( d->m_generator ) + { + DocumentInfo *info = new DocumentInfo(); + const DocumentInfo *tmp = d->m_generator->generateDocumentInfo(); + if ( tmp ) + *info = *tmp; + + info->set( DocumentInfo::FilePath, currentDocument().prettyUrl() ); + const QString pagesSize = d->pagesSizeString(); + if ( d->m_docSize != -1 ) + { + const QString sizeString = KGlobal::locale()->formatByteSize( d->m_docSize ); + info->set( DocumentInfo::DocumentSize, sizeString ); + } + if (!pagesSize.isEmpty()) + { + info->set( DocumentInfo::PagesSize, pagesSize ); + } + + const DocumentInfo::Key keyPages = DocumentInfo::Pages; + const QString keyString = DocumentInfo::getKeyString( keyPages ); + + if ( info->get( keyString ).isEmpty() ) { + info->set( keyString, QString::number( this->pages() ), + DocumentInfo::getKeyTitle( keyPages ) ); + } + + d->m_documentInfo = info; + return info; + } + else return NULL; +} + +const DocumentSynopsis * Document::documentSynopsis() const +{ + return d->m_generator ? d->m_generator->generateDocumentSynopsis() : NULL; +} + +void Document::startFontReading() +{ + if ( !d->m_generator || !d->m_generator->hasFeature( Generator::FontInfo ) || d->m_fontThread ) + return; + + if ( d->m_fontsCached ) + { + // in case we have cached fonts, simulate a reading + // this way the API is the same, and users no need to care about the + // internal caching + for ( int i = 0; i < d->m_fontsCache.count(); ++i ) + { + emit gotFont( d->m_fontsCache.at( i ) ); + emit fontReadingProgress( i / pages() ); + } + emit fontReadingEnded(); + return; + } + + d->m_fontThread = new FontExtractionThread( d->m_generator, pages() ); + connect( d->m_fontThread, SIGNAL(gotFont(Okular::FontInfo)), this, SLOT(fontReadingGotFont(Okular::FontInfo)) ); + connect( d->m_fontThread, SIGNAL(progress(int)), this, SLOT(fontReadingProgress(int)) ); + + d->m_fontThread->startExtraction( /*d->m_generator->hasFeature( Generator::Threaded )*/true ); +} + +void Document::stopFontReading() +{ + if ( !d->m_fontThread ) + return; + + disconnect( d->m_fontThread, 0, this, 0 ); + d->m_fontThread->stopExtraction(); + d->m_fontThread = 0; + d->m_fontsCache.clear(); +} + +bool Document::canProvideFontInformation() const +{ + return d->m_generator ? d->m_generator->hasFeature( Generator::FontInfo ) : false; +} + +const QList *Document::embeddedFiles() const +{ + return d->m_generator ? d->m_generator->embeddedFiles() : NULL; +} + +const Page * Document::page( int n ) const +{ + return ( n < d->m_pagesVector.count() ) ? d->m_pagesVector.at(n) : 0; +} + +const DocumentViewport & Document::viewport() const +{ + return (*d->m_viewportIterator); +} + +const QVector< VisiblePageRect * > & Document::visiblePageRects() const +{ + return d->m_pageRects; +} + +void Document::setVisiblePageRects( const QVector< VisiblePageRect * > & visiblePageRects, DocumentObserver *excludeObserver ) +{ + QVector< VisiblePageRect * >::const_iterator vIt = d->m_pageRects.constBegin(); + QVector< VisiblePageRect * >::const_iterator vEnd = d->m_pageRects.constEnd(); + for ( ; vIt != vEnd; ++vIt ) + delete *vIt; + d->m_pageRects = visiblePageRects; + // notify change to all other (different from id) observers + foreach(DocumentObserver *o, d->m_observers) + if ( o != excludeObserver ) + o->notifyVisibleRectsChanged(); +} + +uint Document::currentPage() const +{ + return (*d->m_viewportIterator).pageNumber; +} + +uint Document::pages() const +{ + return d->m_pagesVector.size(); +} + +KUrl Document::currentDocument() const +{ + return d->m_url; +} + +bool Document::isAllowed( Permission action ) const +{ + if ( action == Okular::AllowNotes && !d->m_annotationEditingEnabled ) + return false; + +#if !OKULAR_FORCE_DRM + if ( KAuthorized::authorize( "skip_drm" ) && !Okular::SettingsCore::obeyDRM() ) + return true; +#endif + + return d->m_generator ? d->m_generator->isAllowed( action ) : false; +} + +bool Document::supportsSearching() const +{ + return d->m_generator ? d->m_generator->hasFeature( Generator::TextExtraction ) : false; +} + +bool Document::supportsPageSizes() const +{ + return d->m_generator ? d->m_generator->hasFeature( Generator::PageSizes ) : false; +} + +bool Document::supportsTiles() const +{ + return d->m_generator ? d->m_generator->hasFeature( Generator::TiledRendering ) : false; +} + +PageSize::List Document::pageSizes() const +{ + if ( d->m_generator ) + { + if ( d->m_pageSizes.isEmpty() ) + d->m_pageSizes = d->m_generator->pageSizes(); + return d->m_pageSizes; + } + return PageSize::List(); +} + +bool Document::canExportToText() const +{ + if ( !d->m_generator ) + return false; + + d->cacheExportFormats(); + return !d->m_exportToText.isNull(); +} + +bool Document::exportToText( const QString& fileName ) const +{ + if ( !d->m_generator ) + return false; + + d->cacheExportFormats(); + if ( d->m_exportToText.isNull() ) + return false; + + return d->m_generator->exportTo( fileName, d->m_exportToText ); +} + +ExportFormat::List Document::exportFormats() const +{ + if ( !d->m_generator ) + return ExportFormat::List(); + + d->cacheExportFormats(); + return d->m_exportFormats; +} + +bool Document::exportTo( const QString& fileName, const ExportFormat& format ) const +{ + return d->m_generator ? d->m_generator->exportTo( fileName, format ) : false; +} + +bool Document::historyAtBegin() const +{ + return d->m_viewportIterator == d->m_viewportHistory.begin(); +} + +bool Document::historyAtEnd() const +{ + return d->m_viewportIterator == --(d->m_viewportHistory.end()); +} + +QVariant Document::metaData( const QString & key, const QVariant & option ) const +{ + return d->m_generator ? d->m_generator->metaData( key, option ) : QVariant(); +} + +Rotation Document::rotation() const +{ + return d->m_rotation; +} + +QSizeF Document::allPagesSize() const +{ + bool allPagesSameSize = true; + QSizeF size; + for (int i = 0; allPagesSameSize && i < d->m_pagesVector.count(); ++i) + { + const Page *p = d->m_pagesVector.at(i); + if (i == 0) size = QSizeF(p->width(), p->height()); + else + { + allPagesSameSize = (size == QSizeF(p->width(), p->height())); + } + } + if (allPagesSameSize) return size; + else return QSizeF(); +} + +QString Document::pageSizeString(int page) const +{ + if (d->m_generator) + { + if (d->m_generator->pagesSizeMetric() != Generator::None) + { + const Page *p = d->m_pagesVector.at( page ); + return d->localizedSize(QSizeF(p->width(), p->height())); + } + } + return QString(); +} + +void Document::requestPixmaps( const QLinkedList< PixmapRequest * > & requests ) +{ + requestPixmaps( requests, RemoveAllPrevious ); +} + +void Document::requestPixmaps( const QLinkedList< PixmapRequest * > & requests, PixmapRequestFlags reqOptions ) +{ + if ( requests.isEmpty() ) + return; + + if ( !d->m_pageController ) + { + // delete requests.. + QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); + for ( ; rIt != rEnd; ++rIt ) + delete *rIt; + // ..and return + return; + } + + // 1. [CLEAN STACK] remove previous requests of requesterID + // FIXME This assumes all requests come from the same observer, that is true atm but not enforced anywhere + DocumentObserver *requesterObserver = requests.first()->observer(); + QSet< int > requestedPages; + { + QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); + for ( ; rIt != rEnd; ++rIt ) + requestedPages.insert( (*rIt)->pageNumber() ); + } + const bool removeAllPrevious = reqOptions & RemoveAllPrevious; + d->m_pixmapRequestsMutex.lock(); + QLinkedList< PixmapRequest * >::iterator sIt = d->m_pixmapRequestsStack.begin(), sEnd = d->m_pixmapRequestsStack.end(); + while ( sIt != sEnd ) + { + if ( (*sIt)->observer() == requesterObserver + && ( removeAllPrevious || requestedPages.contains( (*sIt)->pageNumber() ) ) ) + { + // delete request and remove it from stack + delete *sIt; + sIt = d->m_pixmapRequestsStack.erase( sIt ); + } + else + ++sIt; + } + + // 2. [ADD TO STACK] add requests to stack + QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); + for ( ; rIt != rEnd; ++rIt ) + { + // set the 'page field' (see PixmapRequest) and check if it is valid + PixmapRequest * request = *rIt; + kDebug(OkularDebug).nospace() << "request observer=" << request->observer() << " " <width() << "x" << request->height() << "@" << request->pageNumber(); + if ( d->m_pagesVector.value( request->pageNumber() ) == 0 ) + { + // skip requests referencing an invalid page (must not happen) + delete request; + continue; + } + + request->d->mPage = d->m_pagesVector.value( request->pageNumber() ); + + if ( request->isTile() ) + { + // Change the current request rect so that only invalid tiles are + // requested. Also make sure the rect is tile-aligned. + NormalizedRect tilesRect; + const QList tiles = request->d->tilesManager()->tilesAt( request->normalizedRect(), TilesManager::TerminalTile ); + QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); + while ( tIt != tEnd ) + { + const Tile &tile = *tIt; + if ( !tile.isValid() ) + { + if ( tilesRect.isNull() ) + tilesRect = tile.rect(); + else + tilesRect |= tile.rect(); + } + + tIt++; + } + + request->setNormalizedRect( tilesRect ); + } + + if ( !request->asynchronous() ) + request->d->mPriority = 0; + + // add request to the 'stack' at the right place + if ( !request->priority() ) + // add priority zero requests to the top of the stack + d->m_pixmapRequestsStack.append( request ); + else + { + // insert in stack sorted by priority + sIt = d->m_pixmapRequestsStack.begin(); + sEnd = d->m_pixmapRequestsStack.end(); + while ( sIt != sEnd && (*sIt)->priority() > request->priority() ) + ++sIt; + d->m_pixmapRequestsStack.insert( sIt, request ); + } + } + d->m_pixmapRequestsMutex.unlock(); + + // 3. [START FIRST GENERATION] if generator is ready, start a new generation, + // or else (if gen is running) it will be started when the new contents will + //come from generator (in requestDone()) + // all handling of requests put into sendGeneratorPixmapRequest + // if ( generator->canRequestPixmap() ) + d->sendGeneratorPixmapRequest(); +} + +void Document::requestTextPage( uint page ) +{ + Page * kp = d->m_pagesVector[ page ]; + if ( !d->m_generator || !kp ) + return; + + // Memory management for TextPages + + d->m_generator->generateTextPage( kp ); +} + +void DocumentPrivate::notifyAnnotationChanges( int page ) +{ + int flags = DocumentObserver::Annotations; + + if ( m_annotationsNeedSaveAs ) + flags |= DocumentObserver::NeedSaveAs; + + foreachObserverD( notifyPageChanged( page, flags ) ); +} + +void Document::addPageAnnotation( int page, Annotation * annotation ) +{ + // Transform annotation's base boundary rectangle into unrotated coordinates + Page *p = d->m_pagesVector[page]; + QTransform t = p->d->rotationMatrix(); + annotation->d_ptr->baseTransform(t.inverted()); + QUndoCommand *uc = new AddAnnotationCommand(this->d, annotation, page); + d->m_undoStack->push(uc); +} + +bool Document::canModifyPageAnnotation( const Annotation * annotation ) const +{ + if ( !annotation || ( annotation->flags() & Annotation::DenyWrite ) ) + return false; + + if ( !isAllowed(Okular::AllowNotes) ) + return false; + + if ( ( annotation->flags() & Annotation::External ) && !d->canModifyExternalAnnotations() ) + return false; + + switch ( annotation->subType() ) + { + case Annotation::AText: + case Annotation::ALine: + case Annotation::AGeom: + case Annotation::AHighlight: + case Annotation::AStamp: + case Annotation::AInk: + return true; + default: + return false; + } +} + +void Document::prepareToModifyAnnotationProperties( Annotation * annotation ) +{ + Q_ASSERT(d->m_prevPropsOfAnnotBeingModified.isNull()); + if (!d->m_prevPropsOfAnnotBeingModified.isNull()) + { + kError(OkularDebug) << "Error: Document::prepareToModifyAnnotationProperties has already been called since last call to Document::modifyPageAnnotationProperties"; + return; + } + d->m_prevPropsOfAnnotBeingModified = annotation->getAnnotationPropertiesDomNode(); +} + +void Document::modifyPageAnnotationProperties( int page, Annotation * annotation ) +{ + Q_ASSERT(!d->m_prevPropsOfAnnotBeingModified.isNull()); + if (d->m_prevPropsOfAnnotBeingModified.isNull()) + { + kError(OkularDebug) << "Error: Document::prepareToModifyAnnotationProperties must be called before Annotation is modified"; + return; + } + QDomNode prevProps = d->m_prevPropsOfAnnotBeingModified; + QUndoCommand *uc = new Okular::ModifyAnnotationPropertiesCommand( d, + annotation, + page, + prevProps, + annotation->getAnnotationPropertiesDomNode() ); + d->m_undoStack->push( uc ); + d->m_prevPropsOfAnnotBeingModified.clear(); +} + +void Document::translatePageAnnotation(int page, Annotation* annotation, const NormalizedPoint & delta ) +{ + int complete = (annotation->flags() & Okular::Annotation::BeingMoved) == 0; + QUndoCommand *uc = new Okular::TranslateAnnotationCommand( d, annotation, page, delta, complete ); + d->m_undoStack->push(uc); +} + +void Document::editPageAnnotationContents( int page, Annotation* annotation, + const QString & newContents, + int newCursorPos, + int prevCursorPos, + int prevAnchorPos + ) +{ + QString prevContents = annotation->contents(); + QUndoCommand *uc = new EditAnnotationContentsCommand( d, annotation, page, newContents, newCursorPos, + prevContents, prevCursorPos, prevAnchorPos ); + d->m_undoStack->push( uc ); +} + +bool Document::canRemovePageAnnotation( const Annotation * annotation ) const +{ + if ( !annotation || ( annotation->flags() & Annotation::DenyDelete ) ) + return false; + + if ( ( annotation->flags() & Annotation::External ) && !d->canRemoveExternalAnnotations() ) + return false; + + switch ( annotation->subType() ) + { + case Annotation::AText: + case Annotation::ALine: + case Annotation::AGeom: + case Annotation::AHighlight: + case Annotation::AStamp: + case Annotation::AInk: + return true; + default: + return false; + } +} + +void Document::removePageAnnotation( int page, Annotation * annotation ) +{ + QUndoCommand *uc = new RemoveAnnotationCommand(this->d, annotation, page); + d->m_undoStack->push(uc); +} + +void Document::removePageAnnotations( int page, const QList &annotations ) +{ + d->m_undoStack->beginMacro(i18nc("remove a collection of annotations from the page", "remove annotations")); + foreach(Annotation* annotation, annotations) + { + QUndoCommand *uc = new RemoveAnnotationCommand(this->d, annotation, page); + d->m_undoStack->push(uc); + } + d->m_undoStack->endMacro(); +} + +bool DocumentPrivate::canAddAnnotationsNatively() const +{ + Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); + + if ( iface && iface->supportsOption(Okular::SaveInterface::SaveChanges) && + iface->annotationProxy() && iface->annotationProxy()->supports(AnnotationProxy::Addition) ) + return true; + + return false; +} + +bool DocumentPrivate::canModifyExternalAnnotations() const +{ + Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); + + if ( iface && iface->supportsOption(Okular::SaveInterface::SaveChanges) && + iface->annotationProxy() && iface->annotationProxy()->supports(AnnotationProxy::Modification) ) + return true; + + return false; +} + +bool DocumentPrivate::canRemoveExternalAnnotations() const +{ + Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); + + if ( iface && iface->supportsOption(Okular::SaveInterface::SaveChanges) && + iface->annotationProxy() && iface->annotationProxy()->supports(AnnotationProxy::Removal) ) + return true; + + return false; +} + +void Document::setPageTextSelection( int page, RegularAreaRect * rect, const QColor & color ) +{ + Page * kp = d->m_pagesVector[ page ]; + if ( !d->m_generator || !kp ) + return; + + // add or remove the selection basing whether rect is null or not + if ( rect ) + kp->d->setTextSelections( rect, color ); + else + kp->d->deleteTextSelections(); + + // notify observers about the change + foreachObserver( notifyPageChanged( page, DocumentObserver::TextSelection ) ); +} + +bool Document::canUndo() const +{ + return d->m_undoStack->canUndo(); +} + +bool Document::canRedo() const +{ + return d->m_undoStack->canRedo(); +} + +/* REFERENCE IMPLEMENTATION: better calling setViewport from other code +void Document::setNextPage() +{ + // advance page and set viewport on observers + if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 ) + setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber + 1 ) ); +} + +void Document::setPrevPage() +{ + // go to previous page and set viewport on observers + if ( (*d->m_viewportIterator).pageNumber > 0 ) + setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber - 1 ) ); +} +*/ +void Document::setViewportPage( int page, DocumentObserver *excludeObserver, bool smoothMove ) +{ + // clamp page in range [0 ... numPages-1] + if ( page < 0 ) + page = 0; + else if ( page > (int)d->m_pagesVector.count() ) + page = d->m_pagesVector.count() - 1; + + // make a viewport from the page and broadcast it + setViewport( DocumentViewport( page ), excludeObserver, smoothMove ); +} + +void Document::setViewport( const DocumentViewport & viewport, DocumentObserver *excludeObserver, bool smoothMove ) +{ + if ( !viewport.isValid() ) + { + kDebug(OkularDebug) << "invalid viewport:" << viewport.toString(); + return; + } + if ( viewport.pageNumber >= int(d->m_pagesVector.count()) ) + { + //kDebug(OkularDebug) << "viewport out of document:" << viewport.toString(); + return; + } + + // if already broadcasted, don't redo it + DocumentViewport & oldViewport = *d->m_viewportIterator; + // disabled by enrico on 2005-03-18 (less debug output) + //if ( viewport == oldViewport ) + // kDebug(OkularDebug) << "setViewport with the same viewport."; + + const int oldPageNumber = oldViewport.pageNumber; + + // set internal viewport taking care of history + if ( oldViewport.pageNumber == viewport.pageNumber || !oldViewport.isValid() ) + { + // if page is unchanged save the viewport at current position in queue + oldViewport = viewport; + } + else + { + // remove elements after viewportIterator in queue + d->m_viewportHistory.erase( ++d->m_viewportIterator, d->m_viewportHistory.end() ); + + // keep the list to a reasonable size by removing head when needed + if ( d->m_viewportHistory.count() >= OKULAR_HISTORY_MAXSTEPS ) + d->m_viewportHistory.pop_front(); + + // add the item at the end of the queue + d->m_viewportIterator = d->m_viewportHistory.insert( d->m_viewportHistory.end(), viewport ); + } + + const int currentViewportPage = (*d->m_viewportIterator).pageNumber; + + const bool currentPageChanged = (oldPageNumber != currentViewportPage); + + // notify change to all other (different from id) observers + foreach(DocumentObserver *o, d->m_observers) + { + if ( o != excludeObserver ) + o->notifyViewportChanged( smoothMove ); + + if ( currentPageChanged ) + o->notifyCurrentPageChanged( oldPageNumber, currentViewportPage ); + } +} + +void Document::setZoom(int factor, DocumentObserver *excludeObserver) +{ + // notify change to all other (different from id) observers + foreach(DocumentObserver *o, d->m_observers) + if (o != excludeObserver) + o->notifyZoom( factor ); +} + +void Document::setPrevViewport() +// restore viewport from the history +{ + if ( d->m_viewportIterator != d->m_viewportHistory.begin() ) + { + const int oldViewportPage = (*d->m_viewportIterator).pageNumber; + + // restore previous viewport and notify it to observers + --d->m_viewportIterator; + foreachObserver( notifyViewportChanged( true ) ); + + const int currentViewportPage = (*d->m_viewportIterator).pageNumber; + if (oldViewportPage != currentViewportPage) + foreachObserver( notifyCurrentPageChanged( oldViewportPage, currentViewportPage ) ); + } +} + +void Document::setNextViewport() +// restore next viewport from the history +{ + QLinkedList< DocumentViewport >::const_iterator nextIterator = d->m_viewportIterator; + ++nextIterator; + if ( nextIterator != d->m_viewportHistory.end() ) + { + const int oldViewportPage = (*d->m_viewportIterator).pageNumber; + + // restore next viewport and notify it to observers + ++d->m_viewportIterator; + foreachObserver( notifyViewportChanged( true ) ); + + const int currentViewportPage = (*d->m_viewportIterator).pageNumber; + if (oldViewportPage != currentViewportPage) + foreachObserver( notifyCurrentPageChanged( oldViewportPage, currentViewportPage ) ); + } +} + +void Document::setNextDocumentViewport( const DocumentViewport & viewport ) +{ + d->m_nextDocumentViewport = viewport; +} + +void Document::setNextDocumentDestination( const QString &namedDestination ) +{ + d->m_nextDocumentDestination = namedDestination; +} + +void Document::searchText( int searchID, const QString & text, bool fromStart, Qt::CaseSensitivity caseSensitivity, + SearchType type, bool moveViewport, const QColor & color ) +{ + d->m_searchCancelled = false; + + // safety checks: don't perform searches on empty or unsearchable docs + if ( !d->m_generator || !d->m_generator->hasFeature( Generator::TextExtraction ) || d->m_pagesVector.isEmpty() ) + { + emit searchFinished( searchID, NoMatchFound ); + return; + } + + // if searchID search not recorded, create new descriptor and init params + QMap< int, RunningSearch * >::iterator searchIt = d->m_searches.find( searchID ); + if ( searchIt == d->m_searches.end() ) + { + RunningSearch * search = new RunningSearch(); + search->continueOnPage = -1; + searchIt = d->m_searches.insert( searchID, search ); + } + RunningSearch * s = *searchIt; + + // update search structure + bool newText = text != s->cachedString; + s->cachedString = text; + s->cachedType = type; + s->cachedCaseSensitivity = caseSensitivity; + s->cachedViewportMove = moveViewport; + s->cachedColor = color; + s->isCurrentlySearching = true; + + // global data for search + QSet< int > *pagesToNotify = new QSet< int >; + + // remove highlights from pages and queue them for notifying changes + *pagesToNotify += s->highlightedPages; + foreach(int pageNumber, s->highlightedPages) + d->m_pagesVector.at(pageNumber)->d->deleteHighlights( searchID ); + s->highlightedPages.clear(); + + // set hourglass cursor + QApplication::setOverrideCursor( Qt::WaitCursor ); + + // 1. ALLDOC - proces all document marking pages + if ( type == AllDocument ) + { + QMap< Page *, QVector > *pageMatches = new QMap< Page *, QVector >; + + // search and highlight 'text' (as a solid phrase) on all pages + QMetaObject::invokeMethod(this, "doContinueAllDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotify), Q_ARG(void *, pageMatches), Q_ARG(int, 0), Q_ARG(int, searchID)); + } + // 2. NEXTMATCH - find next matching item (or start from top) + // 3. PREVMATCH - find previous matching item (or start from bottom) + else if ( type == NextMatch || type == PreviousMatch ) + { + // find out from where to start/resume search from + const bool forward = type == NextMatch; + const int viewportPage = (*d->m_viewportIterator).pageNumber; + const int fromStartSearchPage = forward ? 0 : d->m_pagesVector.count() - 1; + int currentPage = fromStart ? fromStartSearchPage : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage); + Page * lastPage = fromStart ? 0 : d->m_pagesVector[ currentPage ]; + int pagesDone = 0; + + // continue checking last TextPage first (if it is the current page) + RegularAreaRect * match = 0; + if ( lastPage && lastPage->number() == s->continueOnPage ) + { + if ( newText ) + match = lastPage->findText( searchID, text, forward ? FromTop : FromBottom, caseSensitivity ); + else + match = lastPage->findText( searchID, text, forward ? NextResult : PreviousResult, caseSensitivity, &s->continueOnMatch ); + if ( !match ) + { + if (forward) currentPage++; + else currentPage--; + pagesDone++; + } + } + + s->pagesDone = pagesDone; + + DoContinueDirectionMatchSearchStruct *searchStruct = new DoContinueDirectionMatchSearchStruct(); + searchStruct->pagesToNotify = pagesToNotify; + searchStruct->match = match; + searchStruct->currentPage = currentPage; + searchStruct->searchID = searchID; + + QMetaObject::invokeMethod(this, "doContinueDirectionMatchSearch", Qt::QueuedConnection, Q_ARG(void *, searchStruct)); + } + // 4. GOOGLE* - process all document marking pages + else if ( type == GoogleAll || type == GoogleAny ) + { + QMap< Page *, QVector< QPair > > *pageMatches = new QMap< Page *, QVector > >; + const QStringList words = text.split( ' ', QString::SkipEmptyParts ); + + // search and highlight every word in 'text' on all pages + QMetaObject::invokeMethod(this, "doContinueGooglesDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotify), Q_ARG(void *, pageMatches), Q_ARG(int, 0), Q_ARG(int, searchID), Q_ARG(QStringList, words)); + } +} + +void Document::continueSearch( int searchID ) +{ + // check if searchID is present in runningSearches + QMap< int, RunningSearch * >::const_iterator it = d->m_searches.constFind( searchID ); + if ( it == d->m_searches.constEnd() ) + { + emit searchFinished( searchID, NoMatchFound ); + return; + } + + // start search with cached parameters from last search by searchID + RunningSearch * p = *it; + if ( !p->isCurrentlySearching ) + searchText( searchID, p->cachedString, false, p->cachedCaseSensitivity, + p->cachedType, p->cachedViewportMove, p->cachedColor ); +} + +void Document::continueSearch( int searchID, SearchType type ) +{ + // check if searchID is present in runningSearches + QMap< int, RunningSearch * >::const_iterator it = d->m_searches.constFind( searchID ); + if ( it == d->m_searches.constEnd() ) + { + emit searchFinished( searchID, NoMatchFound ); + return; + } + + // start search with cached parameters from last search by searchID + RunningSearch * p = *it; + if ( !p->isCurrentlySearching ) + searchText( searchID, p->cachedString, false, p->cachedCaseSensitivity, + type, p->cachedViewportMove, p->cachedColor ); +} + +void Document::resetSearch( int searchID ) +{ + // if we are closing down, don't bother doing anything + if ( !d->m_generator ) + return; + + // check if searchID is present in runningSearches + QMap< int, RunningSearch * >::iterator searchIt = d->m_searches.find( searchID ); + if ( searchIt == d->m_searches.end() ) + return; + + // get previous parameters for search + RunningSearch * s = *searchIt; + + // unhighlight pages and inform observers about that + foreach(int pageNumber, s->highlightedPages) + { + d->m_pagesVector.at(pageNumber)->d->deleteHighlights( searchID ); + foreachObserver( notifyPageChanged( pageNumber, DocumentObserver::Highlights ) ); + } + + // send the setup signal too (to update views that filter on matches) + foreachObserver( notifySetup( d->m_pagesVector, 0 ) ); + + // remove serch from the runningSearches list and delete it + d->m_searches.erase( searchIt ); + delete s; +} + +void Document::cancelSearch() +{ + d->m_searchCancelled = true; +} + +void Document::undo() +{ + d->m_undoStack->undo(); +} + +void Document::redo() +{ + d->m_undoStack->redo(); +} + +void Document::editFormText( int pageNumber, + Okular::FormFieldText* form, + const QString & newContents, + int newCursorPos, + int prevCursorPos, + int prevAnchorPos ) +{ + QUndoCommand *uc = new EditFormTextCommand( this->d, form, pageNumber, newContents, newCursorPos, form->text(), prevCursorPos, prevAnchorPos ); + d->m_undoStack->push( uc ); +} + +void Document::editFormList( int pageNumber, + FormFieldChoice* form, + const QList< int > & newChoices ) +{ + const QList< int > prevChoices = form->currentChoices(); + QUndoCommand *uc = new EditFormListCommand( this->d, form, pageNumber, newChoices, prevChoices ); + d->m_undoStack->push( uc ); +} + +void Document::editFormCombo( int pageNumber, + FormFieldChoice* form, + const QString & newText, + int newCursorPos, + int prevCursorPos, + int prevAnchorPos ) +{ + + QString prevText; + if ( form->currentChoices().isEmpty() ) + { + prevText = form->editChoice(); + } + else + { + prevText = form->choices()[form->currentChoices()[0]]; + } + + QUndoCommand *uc = new EditFormComboCommand( this->d, form, pageNumber, newText, newCursorPos, prevText, prevCursorPos, prevAnchorPos ); + d->m_undoStack->push( uc ); +} + +void Document::editFormButtons( int pageNumber, const QList< FormFieldButton* >& formButtons, const QList< bool >& newButtonStates ) +{ + QUndoCommand *uc = new EditFormButtonsCommand( this->d, pageNumber, formButtons, newButtonStates ); + d->m_undoStack->push( uc ); +} + +BookmarkManager * Document::bookmarkManager() const +{ + return d->m_bookmarkManager; +} + +QList Document::bookmarkedPageList() const +{ + QList list; + uint docPages = pages(); + + //pages are 0-indexed internally, but 1-indexed externally + for ( uint i = 0; i < docPages; i++ ) + { + if ( bookmarkManager()->isBookmarked( i ) ) + { + list << i + 1; + } + } + return list; +} + +QString Document::bookmarkedPageRange() const +{ + // Code formerly in Part::slotPrint() + // range detecting + QString range; + uint docPages = pages(); + int startId = -1; + int endId = -1; + + for ( uint i = 0; i < docPages; ++i ) + { + if ( bookmarkManager()->isBookmarked( i ) ) + { + if ( startId < 0 ) + startId = i; + if ( endId < 0 ) + endId = startId; + else + ++endId; + } + else if ( startId >= 0 && endId >= 0 ) + { + if ( !range.isEmpty() ) + range += ','; + + if ( endId - startId > 0 ) + range += QString( "%1-%2" ).arg( startId + 1 ).arg( endId + 1 ); + else + range += QString::number( startId + 1 ); + startId = -1; + endId = -1; + } + } + if ( startId >= 0 && endId >= 0 ) + { + if ( !range.isEmpty() ) + range += ','; + + if ( endId - startId > 0 ) + range += QString( "%1-%2" ).arg( startId + 1 ).arg( endId + 1 ); + else + range += QString::number( startId + 1 ); + } + return range; +} + +void Document::processAction( const Action * action ) +{ + if ( !action ) + return; + + switch( action->actionType() ) + { + case Action::Goto: { + const GotoAction * go = static_cast< const GotoAction * >( action ); + d->m_nextDocumentViewport = go->destViewport(); + d->m_nextDocumentDestination = go->destinationName(); + + // Explanation of why d->m_nextDocumentViewport is needed: + // all openRelativeFile does is launch a signal telling we + // want to open another URL, the problem is that when the file is + // non local, the loading is done assynchronously so you can't + // do a setViewport after the if as it was because you are doing the setViewport + // on the old file and when the new arrives there is no setViewport for it and + // it does not show anything + + // first open filename if link is pointing outside this document + if ( go->isExternal() && !d->openRelativeFile( go->fileName() ) ) + { + kWarning(OkularDebug).nospace() << "Action: Error opening '" << go->fileName() << "'."; + return; + } + else + { + const DocumentViewport nextViewport = d->nextDocumentViewport(); + // skip local links that point to nowhere (broken ones) + if ( !nextViewport.isValid() ) + return; + + setViewport( nextViewport, 0, true ); + d->m_nextDocumentViewport = DocumentViewport(); + d->m_nextDocumentDestination = QString(); + } + + } break; + + case Action::Execute: { + const ExecuteAction * exe = static_cast< const ExecuteAction * >( action ); + QString fileName = exe->fileName(); + if ( fileName.endsWith( ".pdf" ) || fileName.endsWith( ".PDF" ) ) + { + d->openRelativeFile( fileName ); + return; + } + + // Albert: the only pdf i have that has that kind of link don't define + // an application and use the fileName as the file to open + fileName = d->giveAbsolutePath( fileName ); + KMimeType::Ptr mime = KMimeType::findByPath( fileName ); + // Check executables + if ( KRun::isExecutableFile( fileName, mime->name() ) ) + { + // Don't have any pdf that uses this code path, just a guess on how it should work + if ( !exe->parameters().isEmpty() ) + { + fileName = d->giveAbsolutePath( exe->parameters() ); + mime = KMimeType::findByPath( fileName ); + if ( KRun::isExecutableFile( fileName, mime->name() ) ) + { + // this case is a link pointing to an executable with a parameter + // that also is an executable, possibly a hand-crafted pdf + KMessageBox::information( d->m_widget, i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that.") ); + return; + } + } + else + { + // this case is a link pointing to an executable with no parameters + // core developers find unacceptable executing it even after asking the user + KMessageBox::information( d->m_widget, i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that.") ); + return; + } + } + + KService::Ptr ptr = KMimeTypeTrader::self()->preferredService( mime->name(), "Application" ); + if ( ptr ) + { + KUrl::List lst; + lst.append( fileName ); + KRun::run( *ptr, lst, 0 ); + } + else + KMessageBox::information( d->m_widget, i18n( "No application found for opening file of mimetype %1.", mime->name() ) ); + } break; + + case Action::DocAction: { + const DocumentAction * docaction = static_cast< const DocumentAction * >( action ); + switch( docaction->documentActionType() ) + { + case DocumentAction::PageFirst: + setViewportPage( 0 ); + break; + case DocumentAction::PagePrev: + if ( (*d->m_viewportIterator).pageNumber > 0 ) + setViewportPage( (*d->m_viewportIterator).pageNumber - 1 ); + break; + case DocumentAction::PageNext: + if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 ) + setViewportPage( (*d->m_viewportIterator).pageNumber + 1 ); + break; + case DocumentAction::PageLast: + setViewportPage( d->m_pagesVector.count() - 1 ); + break; + case DocumentAction::HistoryBack: + setPrevViewport(); + break; + case DocumentAction::HistoryForward: + setNextViewport(); + break; + case DocumentAction::Quit: + emit quit(); + break; + case DocumentAction::Presentation: + emit linkPresentation(); + break; + case DocumentAction::EndPresentation: + emit linkEndPresentation(); + break; + case DocumentAction::Find: + emit linkFind(); + break; + case DocumentAction::GoToPage: + emit linkGoToPage(); + break; + case DocumentAction::Close: + emit close(); + break; + } + } break; + + case Action::Browse: { + const BrowseAction * browse = static_cast< const BrowseAction * >( action ); + QString lilySource; + int lilyRow = 0, lilyCol = 0; + // if the url is a mailto one, invoke mailer + if ( browse->url().startsWith( "mailto:", Qt::CaseInsensitive ) ) + KToolInvocation::invokeMailer( browse->url() ); + else if ( extractLilyPondSourceReference( browse->url(), &lilySource, &lilyRow, &lilyCol ) ) + { + const SourceReference ref( lilySource, lilyRow, lilyCol ); + processSourceReference( &ref ); + } + else + { + QString url = browse->url(); + + // fix for #100366, documents with relative links that are the form of http:foo.pdf + if (url.indexOf("http:") == 0 && url.indexOf("http://") == -1 && url.right(4) == ".pdf") + { + d->openRelativeFile(url.mid(5)); + return; + } + + KUrl realUrl = KUrl( url ); + + // handle documents with relative path + if ( d->m_url.isValid() ) + { + realUrl = KUrl( d->m_url.upUrl(), url ); + } + + // Albert: this is not a leak! + new KRun( realUrl, d->m_widget ); + } + } break; + + case Action::Sound: { + const SoundAction * linksound = static_cast< const SoundAction * >( action ); + AudioPlayer::instance()->playSound( linksound->sound(), linksound ); + } break; + + case Action::Script: { + const ScriptAction * linkscript = static_cast< const ScriptAction * >( action ); + if ( !d->m_scripter ) + d->m_scripter = new Scripter( d ); + d->m_scripter->execute( linkscript->scriptType(), linkscript->script() ); + } break; + + case Action::Movie: + emit processMovieAction( static_cast< const MovieAction * >( action ) ); + break; + case Action::Rendition: { + const RenditionAction * linkrendition = static_cast< const RenditionAction * >( action ); + if ( !linkrendition->script().isEmpty() ) + { + if ( !d->m_scripter ) + d->m_scripter = new Scripter( d ); + d->m_scripter->execute( linkrendition->scriptType(), linkrendition->script() ); + } + else + { + emit processRenditionAction( static_cast< const RenditionAction * >( action ) ); + } + } break; + } +} + +void Document::processSourceReference( const SourceReference * ref ) +{ + if ( !ref ) + return; + + const KUrl url( d->giveAbsolutePath( ref->fileName() ) ); + if ( !url.isLocalFile() ) + { + kDebug(OkularDebug) << url.url() << "is not a local file."; + return; + } + + const QString absFileName = url.toLocalFile(); + if ( !QFile::exists( absFileName ) ) + { + kDebug(OkularDebug) << "No such file:" << absFileName; + return; + } + + bool handled = false; + emit sourceReferenceActivated(absFileName, ref->row(), ref->column(), &handled); + if(handled) { + return; + } + + static QHash< int, QString > editors; + // init the editors table if empty (on first run, usually) + if ( editors.isEmpty() ) + { + editors = buildEditorsMap(); + } + + QHash< int, QString >::const_iterator it = editors.constFind( SettingsCore::externalEditor() ); + QString p; + if ( it != editors.constEnd() ) + p = *it; + else + p = SettingsCore::externalEditorCommand(); + // custom editor not yet configured + if ( p.isEmpty() ) + return; + + // manually append the %f placeholder if not specified + if ( p.indexOf( QLatin1String( "%f" ) ) == -1 ) + p.append( QLatin1String( " %f" ) ); + + // replacing the placeholders + QHash< QChar, QString > map; + map.insert( 'f', absFileName ); + map.insert( 'c', QString::number( ref->column() ) ); + map.insert( 'l', QString::number( ref->row() ) ); + const QString cmd = KMacroExpander::expandMacrosShellQuote( p, map ); + if ( cmd.isEmpty() ) + return; + const QStringList args = KShell::splitArgs( cmd ); + if ( args.isEmpty() ) + return; + + KProcess::startDetached( args ); +} + +const SourceReference * Document::dynamicSourceReference( int pageNr, double absX, double absY ) +{ + const SourceReference * ref = 0; + if ( d->m_generator ) + { + QMetaObject::invokeMethod( d->m_generator, "dynamicSourceReference", Qt::DirectConnection, Q_RETURN_ARG(const Okular::SourceReference*, ref), Q_ARG(int, pageNr), Q_ARG(double, absX), Q_ARG(double, absY) ); + } + return ref; +} + +Document::PrintingType Document::printingSupport() const +{ + if ( d->m_generator ) + { + + if ( d->m_generator->hasFeature( Generator::PrintNative ) ) + { + return NativePrinting; + } + +#ifndef Q_OS_WIN + if ( d->m_generator->hasFeature( Generator::PrintPostscript ) ) + { + return PostscriptPrinting; + } +#endif + + } + + return NoPrinting; +} + +bool Document::supportsPrintToFile() const +{ + return d->m_generator ? d->m_generator->hasFeature( Generator::PrintToFile ) : false; +} + +bool Document::print( QPrinter &printer ) +{ + return d->m_generator ? d->m_generator->print( printer ) : false; +} + +QString Document::printError() const +{ + Okular::Generator::PrintError err = Generator::UnknownPrintError; + if ( d->m_generator ) + { + QMetaObject::invokeMethod( d->m_generator, "printError", Qt::DirectConnection, Q_RETURN_ARG(Okular::Generator::PrintError, err) ); + } + Q_ASSERT( err != Generator::NoPrintError ); + switch ( err ) + { + case Generator::TemporaryFileOpenPrintError: + return i18n( "Could not open a temporary file" ); + case Generator::FileConversionPrintError: + return i18n( "Print conversion failed" ); + case Generator::PrintingProcessCrashPrintError: + return i18n( "Printing process crashed" ); + case Generator::PrintingProcessStartPrintError: + return i18n( "Printing process could not start" ); + case Generator::PrintToFilePrintError: + return i18n( "Printing to file failed" ); + case Generator::InvalidPrinterStatePrintError: + return i18n( "Printer was in invalid state" ); + case Generator::UnableToFindFilePrintError: + return i18n( "Unable to find file to print" ); + case Generator::NoFileToPrintError: + return i18n( "There was no file to print" ); + case Generator::NoBinaryToPrintError: + return i18n( "Could not find a suitable binary for printing. Make sure CUPS lpr binary is available" ); + case Generator::InvalidPageSizePrintError: + return i18n( "The page print size is invalid" ); + case Generator::NoPrintError: + return QString(); + case Generator::UnknownPrintError: + return QString(); + } + + return QString(); +} + +QWidget* Document::printConfigurationWidget() const +{ + if ( d->m_generator ) + { + PrintInterface * iface = qobject_cast< Okular::PrintInterface * >( d->m_generator ); + return iface ? iface->printConfigurationWidget() : 0; + } + else + return 0; +} + +void Document::fillConfigDialog( KConfigDialog * dialog ) +{ + if ( !dialog ) + return; + + // ensure that we have all the generators with settings loaded + QString constraint( "([X-KDE-Priority] > 0) and (exist Library) and ([X-KDE-okularHasInternalSettings])" ); + KService::List offers = KServiceTypeTrader::self()->query( "okular/Generator", constraint ); + d->loadServiceList( offers ); + + bool pagesAdded = false; + QHash< QString, GeneratorInfo >::iterator it = d->m_loadedGenerators.begin(); + QHash< QString, GeneratorInfo >::iterator itEnd = d->m_loadedGenerators.end(); + for ( ; it != itEnd; ++it ) + { + Okular::ConfigInterface * iface = d->generatorConfig( it.value() ); + if ( iface ) + { + iface->addPages( dialog ); + pagesAdded = true; + if ( !it.value().catalogName.isEmpty() ) + KGlobal::locale()->insertCatalog( it.value().catalogName ); + } + } + if ( pagesAdded ) + { + connect( dialog, SIGNAL(settingsChanged(QString)), + this, SLOT(slotGeneratorConfigChanged(QString)) ); + } +} + +int Document::configurableGenerators() const +{ + QString constraint( "([X-KDE-Priority] > 0) and (exist Library) and ([X-KDE-okularHasInternalSettings])" ); + KService::List offers = KServiceTypeTrader::self()->query( "okular/Generator", constraint ); + return offers.count(); +} + +QStringList Document::supportedMimeTypes() const +{ + if ( !d->m_supportedMimeTypes.isEmpty() ) + return d->m_supportedMimeTypes; + + QString constraint( "(Library == 'okularpart')" ); + QLatin1String basePartService( "KParts/ReadOnlyPart" ); + KService::List offers = KServiceTypeTrader::self()->query( basePartService, constraint ); + KService::List::ConstIterator it = offers.constBegin(), itEnd = offers.constEnd(); + for ( ; it != itEnd; ++it ) + { + KService::Ptr service = *it; + QStringList mimeTypes = service->serviceTypes(); + foreach ( const QString& mimeType, mimeTypes ) + if ( mimeType != basePartService ) + d->m_supportedMimeTypes.append( mimeType ); + } + + return d->m_supportedMimeTypes; +} + +const KComponentData* Document::componentData() const +{ + if ( !d->m_generator ) + return 0; + + QHash< QString, GeneratorInfo >::const_iterator genIt = d->m_loadedGenerators.constFind( d->m_generatorName ); + Q_ASSERT( genIt != d->m_loadedGenerators.constEnd() ); + const KComponentData* kcd = &genIt.value().data; + + // empty about data + if ( kcd->isValid() && kcd->aboutData() && kcd->aboutData()->programName().isEmpty() ) + return 0; + + return kcd; +} + +bool Document::canSaveChanges() const +{ + if ( !d->m_generator ) + return false; + Q_ASSERT( !d->m_generatorName.isEmpty() ); + + QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); + Q_ASSERT( genIt != d->m_loadedGenerators.end() ); + SaveInterface* saveIface = d->generatorSave( genIt.value() ); + if ( !saveIface ) + return false; + + return saveIface->supportsOption( SaveInterface::SaveChanges ); +} + +bool Document::canSaveChanges( SaveCapability cap ) const +{ + switch ( cap ) + { + case SaveFormsCapability: + /* Assume that if the generator supports saving, forms can be saved. + * We have no means to actually query the generator at the moment + * TODO: Add some method to query the generator in SaveInterface */ + return canSaveChanges(); + + case SaveAnnotationsCapability: + return d->canAddAnnotationsNatively(); + } + + return false; +} + +bool Document::saveChanges( const QString &fileName ) +{ + QString errorText; + return saveChanges( fileName, &errorText ); +} + +bool Document::saveChanges( const QString &fileName, QString *errorText ) +{ + if ( !d->m_generator || fileName.isEmpty() ) + return false; + Q_ASSERT( !d->m_generatorName.isEmpty() ); + + QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); + Q_ASSERT( genIt != d->m_loadedGenerators.end() ); + SaveInterface* saveIface = d->generatorSave( genIt.value() ); + if ( !saveIface || !saveIface->supportsOption( SaveInterface::SaveChanges ) ) + return false; + + return saveIface->save( fileName, SaveInterface::SaveChanges, errorText ); +} + +void Document::registerView( View *view ) +{ + if ( !view ) + return; + + Document *viewDoc = view->viewDocument(); + if ( viewDoc ) + { + // check if already registered for this document + if ( viewDoc == this ) + return; + + viewDoc->unregisterView( view ); + } + + d->m_views.insert( view ); + view->d_func()->document = d; +} + +void Document::unregisterView( View *view ) +{ + if ( !view ) + return; + + Document *viewDoc = view->viewDocument(); + if ( !viewDoc || viewDoc != this ) + return; + + view->d_func()->document = 0; + d->m_views.remove( view ); +} + +QByteArray Document::fontData(const FontInfo &font) const +{ + QByteArray result; + + if (d->m_generator) + { + QMetaObject::invokeMethod(d->m_generator, "requestFontData", Qt::DirectConnection, Q_ARG(Okular::FontInfo, font), Q_ARG(QByteArray *, &result)); + } + + return result; +} + +Document::OpenResult Document::openDocumentArchive( const QString & docFile, const KUrl & url, const QString & password ) +{ + const KMimeType::Ptr mime = KMimeType::findByPath( docFile, 0, false /* content too */ ); + if ( !mime->is( "application/vnd.kde.okular-archive" ) ) + return OpenError; + + KZip okularArchive( docFile ); + if ( !okularArchive.open( QIODevice::ReadOnly ) ) + return OpenError; + + const KArchiveDirectory * mainDir = okularArchive.directory(); + const KArchiveEntry * mainEntry = mainDir->entry( "content.xml" ); + if ( !mainEntry || !mainEntry->isFile() ) + return OpenError; + + std::auto_ptr< QIODevice > mainEntryDevice( static_cast< const KZipFileEntry * >( mainEntry )->createDevice() ); + QDomDocument doc; + if ( !doc.setContent( mainEntryDevice.get() ) ) + return OpenError; + mainEntryDevice.reset(); + + QDomElement root = doc.documentElement(); + if ( root.tagName() != "OkularArchive" ) + return OpenError; + + QString documentFileName; + QString metadataFileName; + QDomElement el = root.firstChild().toElement(); + for ( ; !el.isNull(); el = el.nextSibling().toElement() ) + { + if ( el.tagName() == "Files" ) + { + QDomElement fileEl = el.firstChild().toElement(); + for ( ; !fileEl.isNull(); fileEl = fileEl.nextSibling().toElement() ) + { + if ( fileEl.tagName() == "DocumentFileName" ) + documentFileName = fileEl.text(); + else if ( fileEl.tagName() == "MetadataFileName" ) + metadataFileName = fileEl.text(); + } + } + } + if ( documentFileName.isEmpty() ) + return OpenError; + + const KArchiveEntry * docEntry = mainDir->entry( documentFileName ); + if ( !docEntry || !docEntry->isFile() ) + return OpenError; + + std::auto_ptr< ArchiveData > archiveData( new ArchiveData() ); + const int dotPos = documentFileName.indexOf( '.' ); + if ( dotPos != -1 ) + archiveData->document.setSuffix( documentFileName.mid( dotPos ) ); + if ( !archiveData->document.open() ) + return OpenError; + + QString tempFileName = archiveData->document.fileName(); + { + std::auto_ptr< QIODevice > docEntryDevice( static_cast< const KZipFileEntry * >( docEntry )->createDevice() ); + copyQIODevice( docEntryDevice.get(), &archiveData->document ); + archiveData->document.close(); + } + + const KArchiveEntry * metadataEntry = mainDir->entry( metadataFileName ); + if ( metadataEntry && metadataEntry->isFile() ) + { + std::auto_ptr< QIODevice > metadataEntryDevice( static_cast< const KZipFileEntry * >( metadataEntry )->createDevice() ); + archiveData->metadataFile.setSuffix( ".xml" ); + if ( archiveData->metadataFile.open() ) + { + copyQIODevice( metadataEntryDevice.get(), &archiveData->metadataFile ); + archiveData->metadataFile.close(); + } + } + + const KMimeType::Ptr docMime = KMimeType::findByPath( tempFileName, 0, true /* local file */ ); + d->m_archiveData = archiveData.get(); + d->m_archivedFileName = documentFileName; + const OpenResult ret = openDocument( tempFileName, url, docMime, password ); + + if ( ret == OpenSuccess ) + { + archiveData.release(); + } + else + { + d->m_archiveData = 0; + } + + return ret; +} + +bool Document::saveDocumentArchive( const QString &fileName ) +{ + if ( !d->m_generator ) + return false; + + /* If we opened an archive, use the name of original file (eg foo.pdf) + * instead of the archive's one (eg foo.okular) */ + QString docFileName = d->m_archiveData ? d->m_archivedFileName : d->m_url.fileName(); + if ( docFileName == QLatin1String( "-" ) ) + return false; + + QString docPath = d->m_docFileName; + const QFileInfo fi( docPath ); + if ( fi.isSymLink() ) + docPath = fi.symLinkTarget(); + + KZip okularArchive( fileName ); + if ( !okularArchive.open( QIODevice::WriteOnly ) ) + return false; + + const KUser user; +#ifndef Q_OS_WIN + const KUserGroup userGroup( user.gid() ); +#else + const KUserGroup userGroup( QString( "" ) ); +#endif + + QDomDocument contentDoc( "OkularArchive" ); + QDomProcessingInstruction xmlPi = contentDoc.createProcessingInstruction( + QString::fromLatin1( "xml" ), QString::fromLatin1( "version=\"1.0\" encoding=\"utf-8\"" ) ); + contentDoc.appendChild( xmlPi ); + QDomElement root = contentDoc.createElement( "OkularArchive" ); + contentDoc.appendChild( root ); + + QDomElement filesNode = contentDoc.createElement( "Files" ); + root.appendChild( filesNode ); + + QDomElement fileNameNode = contentDoc.createElement( "DocumentFileName" ); + filesNode.appendChild( fileNameNode ); + fileNameNode.appendChild( contentDoc.createTextNode( docFileName ) ); + + QDomElement metadataFileNameNode = contentDoc.createElement( "MetadataFileName" ); + filesNode.appendChild( metadataFileNameNode ); + metadataFileNameNode.appendChild( contentDoc.createTextNode( "metadata.xml" ) ); + + // If the generator can save annotations natively, do it + KTemporaryFile modifiedFile; + bool annotationsSavedNatively = false; + if ( d->canAddAnnotationsNatively() ) + { + if ( !modifiedFile.open() ) + return false; + + modifiedFile.close(); // We're only interested in the file name + + QString errorText; + if ( saveChanges( modifiedFile.fileName(), &errorText ) ) + { + docPath = modifiedFile.fileName(); // Save this instead of the original file + annotationsSavedNatively = true; + } + else + { + kWarning(OkularDebug) << "saveChanges failed: " << errorText; + kDebug(OkularDebug) << "Falling back to saving a copy of the original file"; + } + } + + KTemporaryFile metadataFile; + PageItems saveWhat = annotationsSavedNatively ? None : AnnotationPageItems; + if ( !d->savePageDocumentInfo( &metadataFile, saveWhat ) ) + return false; + + const QByteArray contentDocXml = contentDoc.toByteArray(); + okularArchive.writeFile( "content.xml", user.loginName(), userGroup.name(), + contentDocXml.constData(), contentDocXml.length() ); + + okularArchive.addLocalFile( docPath, docFileName ); + okularArchive.addLocalFile( metadataFile.fileName(), "metadata.xml" ); + + if ( !okularArchive.close() ) + return false; + + return true; +} + +QPrinter::Orientation Document::orientation() const +{ + double width, height; + int landscape, portrait; + const Okular::Page *currentPage; + + // if some pages are landscape and others are not, the most common wins, as + // QPrinter does not accept a per-page setting + landscape = 0; + portrait = 0; + for (uint i = 0; i < pages(); i++) + { + currentPage = page(i); + width = currentPage->width(); + height = currentPage->height(); + if (currentPage->orientation() == Okular::Rotation90 || currentPage->orientation() == Okular::Rotation270) qSwap(width, height); + if (width > height) landscape++; + else portrait++; + } + return (landscape > portrait) ? QPrinter::Landscape : QPrinter::Portrait; +} + +void Document::setAnnotationEditingEnabled( bool enable ) +{ + d->m_annotationEditingEnabled = enable; + foreachObserver( notifySetup( d->m_pagesVector, 0 ) ); +} + +void Document::walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const +{ + if (d->m_generator) { + d->m_generator->walletDataForFile( fileName, walletName, walletFolder, walletKey ); + } +} + +void DocumentPrivate::requestDone( PixmapRequest * req ) +{ + if ( !req ) + return; + + if ( !m_generator || m_closingLoop ) + { + m_pixmapRequestsMutex.lock(); + m_executingPixmapRequests.removeAll( req ); + m_pixmapRequestsMutex.unlock(); + delete req; + if ( m_closingLoop ) + m_closingLoop->exit(); + return; + } + +#ifndef NDEBUG + if ( !m_generator->canGeneratePixmap() ) + kDebug(OkularDebug) << "requestDone with generator not in READY state."; +#endif + + // [MEM] 1.1 find and remove a previous entry for the same page and id + QLinkedList< AllocatedPixmap * >::iterator aIt = m_allocatedPixmaps.begin(); + QLinkedList< AllocatedPixmap * >::iterator aEnd = m_allocatedPixmaps.end(); + for ( ; aIt != aEnd; ++aIt ) + if ( (*aIt)->page == req->pageNumber() && (*aIt)->observer == req->observer() ) + { + AllocatedPixmap * p = *aIt; + m_allocatedPixmaps.erase( aIt ); + m_allocatedPixmapsTotalMemory -= p->memory; + delete p; + break; + } + + DocumentObserver *observer = req->observer(); + if ( m_observers.contains(observer) ) + { + // [MEM] 1.2 append memory allocation descriptor to the FIFO + qulonglong memoryBytes = 0; + const TilesManager *tm = req->d->tilesManager(); + if ( tm ) + memoryBytes = tm->totalMemory(); + else + memoryBytes = 4 * req->width() * req->height(); + + AllocatedPixmap * memoryPage = new AllocatedPixmap( req->observer(), req->pageNumber(), memoryBytes ); + m_allocatedPixmaps.append( memoryPage ); + m_allocatedPixmapsTotalMemory += memoryBytes; + + // 2. notify an observer that its pixmap changed + observer->notifyPageChanged( req->pageNumber(), DocumentObserver::Pixmap ); + } +#ifndef NDEBUG + else + kWarning(OkularDebug) << "Receiving a done request for the defunct observer" << observer; +#endif + + // 3. delete request + m_pixmapRequestsMutex.lock(); + m_executingPixmapRequests.removeAll( req ); + m_pixmapRequestsMutex.unlock(); + delete req; + + // 4. start a new generation if some is pending + m_pixmapRequestsMutex.lock(); + bool hasPixmaps = !m_pixmapRequestsStack.isEmpty(); + m_pixmapRequestsMutex.unlock(); + if ( hasPixmaps ) + sendGeneratorPixmapRequest(); +} + +void DocumentPrivate::setPageBoundingBox( int page, const NormalizedRect& boundingBox ) +{ + Page * kp = m_pagesVector[ page ]; + if ( !m_generator || !kp ) + return; + + if ( kp->boundingBox() == boundingBox ) + return; + kp->setBoundingBox( boundingBox ); + + // notify observers about the change + foreachObserverD( notifyPageChanged( page, DocumentObserver::BoundingBox ) ); + + // TODO: For generators that generate the bbox by pixmap scanning, if the first generated pixmap is very small, the bounding box will forever be inaccurate. + // TODO: Crop computation should also consider annotations, actions, etc. to make sure they're not cropped away. + // TODO: Help compute bounding box for generators that create a QPixmap without a QImage, like text and plucker. + // TODO: Don't compute the bounding box if no one needs it (e.g., Trim Borders is off). + +} + +void DocumentPrivate::calculateMaxTextPages() +{ + int multipliers = qMax(1, qRound(getTotalMemory() / 536870912.0)); // 512 MB + switch (SettingsCore::memoryLevel()) + { + case SettingsCore::EnumMemoryLevel::Low: + m_maxAllocatedTextPages = multipliers * 2; + break; + + case SettingsCore::EnumMemoryLevel::Normal: + m_maxAllocatedTextPages = multipliers * 50; + break; + + case SettingsCore::EnumMemoryLevel::Aggressive: + m_maxAllocatedTextPages = multipliers * 250; + break; + + case SettingsCore::EnumMemoryLevel::Greedy: + m_maxAllocatedTextPages = multipliers * 1250; + break; + } +} + +void DocumentPrivate::textGenerationDone( Page *page ) +{ + if ( !m_pageController ) return; + + // 1. If we reached the cache limit, delete the first text page from the fifo + if (m_allocatedTextPagesFifo.size() == m_maxAllocatedTextPages) + { + int pageToKick = m_allocatedTextPagesFifo.takeFirst(); + if (pageToKick != page->number()) // this should never happen but better be safe than sorry + { + m_pagesVector.at(pageToKick)->setTextPage( 0 ); // deletes the textpage + } + } + + // 2. Add the page to the fifo of generated text pages + m_allocatedTextPagesFifo.append( page->number() ); +} + +void Document::setRotation( int r ) +{ + d->setRotationInternal( r, true ); +} + +void DocumentPrivate::setRotationInternal( int r, bool notify ) +{ + Rotation rotation = (Rotation)r; + if ( !m_generator || ( m_rotation == rotation ) ) + return; + + // tell the pages to rotate + QVector< Okular::Page * >::const_iterator pIt = m_pagesVector.constBegin(); + QVector< Okular::Page * >::const_iterator pEnd = m_pagesVector.constEnd(); + for ( ; pIt != pEnd; ++pIt ) + (*pIt)->d->rotateAt( rotation ); + if ( notify ) + { + // notify the generator that the current rotation has changed + m_generator->rotationChanged( rotation, m_rotation ); + } + // set the new rotation + m_rotation = rotation; + + if ( notify ) + { + foreachObserverD( notifySetup( m_pagesVector, DocumentObserver::NewLayoutForPages ) ); + foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap | DocumentObserver::Highlights | DocumentObserver::Annotations ) ); + } + kDebug(OkularDebug) << "Rotated:" << r; +} + +void Document::setPageSize( const PageSize &size ) +{ + if ( !d->m_generator || !d->m_generator->hasFeature( Generator::PageSizes ) ) + return; + + if ( d->m_pageSizes.isEmpty() ) + d->m_pageSizes = d->m_generator->pageSizes(); + int sizeid = d->m_pageSizes.indexOf( size ); + if ( sizeid == -1 ) + return; + + // tell the pages to change size + QVector< Okular::Page * >::const_iterator pIt = d->m_pagesVector.constBegin(); + QVector< Okular::Page * >::const_iterator pEnd = d->m_pagesVector.constEnd(); + for ( ; pIt != pEnd; ++pIt ) + (*pIt)->d->changeSize( size ); + // clear 'memory allocation' descriptors + qDeleteAll( d->m_allocatedPixmaps ); + d->m_allocatedPixmaps.clear(); + d->m_allocatedPixmapsTotalMemory = 0; + // notify the generator that the current page size has changed + d->m_generator->pageSizeChanged( size, d->m_pageSize ); + // set the new page size + d->m_pageSize = size; + + foreachObserver( notifySetup( d->m_pagesVector, DocumentObserver::NewLayoutForPages ) ); + foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap | DocumentObserver::Highlights ) ); + kDebug(OkularDebug) << "New PageSize id:" << sizeid; +} + + +/** DocumentViewport **/ + +DocumentViewport::DocumentViewport( int n ) + : pageNumber( n ) +{ + // default settings + rePos.enabled = false; + rePos.normalizedX = 0.5; + rePos.normalizedY = 0.0; + rePos.pos = Center; + autoFit.enabled = false; + autoFit.width = false; + autoFit.height = false; +} + +DocumentViewport::DocumentViewport( const QString & xmlDesc ) + : pageNumber( -1 ) +{ + // default settings (maybe overridden below) + rePos.enabled = false; + rePos.normalizedX = 0.5; + rePos.normalizedY = 0.0; + rePos.pos = Center; + autoFit.enabled = false; + autoFit.width = false; + autoFit.height = false; + + // check for string presence + if ( xmlDesc.isEmpty() ) + return; + + // decode the string + bool ok; + int field = 0; + QString token = xmlDesc.section( ';', field, field ); + while ( !token.isEmpty() ) + { + // decode the current token + if ( field == 0 ) + { + pageNumber = token.toInt( &ok ); + if ( !ok ) + return; + } + else if ( token.startsWith( "C1" ) ) + { + rePos.enabled = true; + rePos.normalizedX = token.section( ':', 1, 1 ).toDouble(); + rePos.normalizedY = token.section( ':', 2, 2 ).toDouble(); + rePos.pos = Center; + } + else if ( token.startsWith( "C2" ) ) + { + rePos.enabled = true; + rePos.normalizedX = token.section( ':', 1, 1 ).toDouble(); + rePos.normalizedY = token.section( ':', 2, 2 ).toDouble(); + if (token.section( ':', 3, 3 ).toInt() == 1) rePos.pos = Center; + else rePos.pos = TopLeft; + } + else if ( token.startsWith( "AF1" ) ) + { + autoFit.enabled = true; + autoFit.width = token.section( ':', 1, 1 ) == "T"; + autoFit.height = token.section( ':', 2, 2 ) == "T"; + } + // proceed tokenizing string + field++; + token = xmlDesc.section( ';', field, field ); + } +} + +QString DocumentViewport::toString() const +{ + // start string with page number + QString s = QString::number( pageNumber ); + // if has center coordinates, save them on string + if ( rePos.enabled ) + s += QString( ";C2:" ) + QString::number( rePos.normalizedX ) + + ':' + QString::number( rePos.normalizedY ) + + ':' + QString::number( rePos.pos ); + // if has autofit enabled, save its state on string + if ( autoFit.enabled ) + s += QString( ";AF1:" ) + (autoFit.width ? "T" : "F") + + ':' + (autoFit.height ? "T" : "F"); + return s; +} + +bool DocumentViewport::isValid() const +{ + return pageNumber >= 0; +} + +bool DocumentViewport::operator==( const DocumentViewport & vp ) const +{ + bool equal = ( pageNumber == vp.pageNumber ) && + ( rePos.enabled == vp.rePos.enabled ) && + ( autoFit.enabled == vp.autoFit.enabled ); + if ( !equal ) + return false; + if ( rePos.enabled && + (( rePos.normalizedX != vp.rePos.normalizedX) || + ( rePos.normalizedY != vp.rePos.normalizedY ) || rePos.pos != vp.rePos.pos) ) + return false; + if ( autoFit.enabled && + (( autoFit.width != vp.autoFit.width ) || + ( autoFit.height != vp.autoFit.height )) ) + return false; + return true; +} + +bool DocumentViewport::operator<( const DocumentViewport & vp ) const +{ + // TODO: Check autoFit and Position + + if ( pageNumber != vp.pageNumber ) + return pageNumber < vp.pageNumber; + + if ( !rePos.enabled && vp.rePos.enabled ) + return true; + + if ( !vp.rePos.enabled ) + return false; + + if ( rePos.normalizedY != vp.rePos.normalizedY ) + return rePos.normalizedY < vp.rePos.normalizedY; + + return rePos.normalizedX < vp.rePos.normalizedX; +} + + +/** DocumentInfo **/ + +DocumentInfo::DocumentInfo() + : QDomDocument( "DocumentInformation" ) +{ + QDomElement docElement = createElement( "DocumentInfo" ); + appendChild( docElement ); +} + +void DocumentInfo::set( const QString &key, const QString &value, + const QString &title ) +{ + QDomElement docElement = documentElement(); + QDomElement element; + + // check whether key already exists + QDomNodeList list = docElement.elementsByTagName( key ); + if ( list.count() > 0 ) + element = list.item( 0 ).toElement(); + else + element = createElement( key ); + + element.setAttribute( "value", value ); + element.setAttribute( "title", title ); + + if ( list.count() == 0 ) + docElement.appendChild( element ); +} + +void DocumentInfo::set( Key key, const QString &value ) +{ + const QString keyString = getKeyString( key ); + if ( !keyString.isEmpty() ) + set( keyString, value, getKeyTitle( key ) ); + else + kWarning(OkularDebug) << "Invalid key passed"; +} + +QString DocumentInfo::get( const QString &key ) const +{ + const QDomElement docElement = documentElement(); + + // check whether key already exists + const QDomNodeList list = docElement.elementsByTagName( key ); + if ( list.count() > 0 ) + return list.item( 0 ).toElement().attribute( "value" ); + else + return QString(); +} + +QString DocumentInfo::getKeyString( Key key ) //const +{ + switch ( key ) { + case Title: + return "title"; + break; + case Subject: + return "subject"; + break; + case Description: + return "description"; + break; + case Author: + return "author"; + break; + case Creator: + return "creator"; + break; + case Producer: + return "producer"; + break; + case Copyright: + return "copyright"; + break; + case Pages: + return "pages"; + break; + case CreationDate: + return "creationDate"; + break; + case ModificationDate: + return "modificationDate"; + break; + case MimeType: + return "mimeType"; + break; + case Category: + return "category"; + break; + case Keywords: + return "keywords"; + break; + case FilePath: + return "filePath"; + break; + case DocumentSize: + return "documentSize"; + break; + case PagesSize: + return "pageSize"; + break; + default: + return QString(); + break; + } +} + +QString DocumentInfo::getKeyTitle( Key key ) //const +{ + switch ( key ) { + case Title: + return i18n( "Title" ); + break; + case Subject: + return i18n( "Subject" ); + break; + case Description: + return i18n( "Description" ); + break; + case Author: + return i18n( "Author" ); + break; + case Creator: + return i18n( "Creator" ); + break; + case Producer: + return i18n( "Producer" ); + break; + case Copyright: + return i18n( "Copyright" ); + break; + case Pages: + return i18n( "Pages" ); + break; + case CreationDate: + return i18n( "Created" ); + break; + case ModificationDate: + return i18n( "Modified" ); + break; + case MimeType: + return i18n( "Mime Type" ); + break; + case Category: + return i18n( "Category" ); + break; + case Keywords: + return i18n( "Keywords" ); + break; + case FilePath: + return i18n( "File Path" ); + break; + case DocumentSize: + return i18n( "File Size" ); + break; + case PagesSize: + return i18n("Page Size"); + break; + default: + return QString(); + break; + } +} + + +/** DocumentSynopsis **/ + +DocumentSynopsis::DocumentSynopsis() + : QDomDocument( "DocumentSynopsis" ) +{ + // void implementation, only subclassed for naming +} + +DocumentSynopsis::DocumentSynopsis( const QDomDocument &document ) + : QDomDocument( document ) +{ +} + +/** EmbeddedFile **/ + +EmbeddedFile::EmbeddedFile() +{ +} + +EmbeddedFile::~EmbeddedFile() +{ +} + +VisiblePageRect::VisiblePageRect( int page, const NormalizedRect &rectangle ) + : pageNumber( page ), rect( rectangle ) +{ +} + +#undef foreachObserver +#undef foreachObserverD + +#include "document.moc" + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/okular/core/document.h b/okular/core/document.h new file mode 100644 index 00000000..06bac8a0 --- /dev/null +++ b/okular/core/document.h @@ -0,0 +1,1274 @@ +/*************************************************************************** + * Copyright (C) 2004-2005 by Enrico Ros * + * Copyright (C) 2004-2008 by Albert Astals Cid * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_DOCUMENT_H_ +#define _OKULAR_DOCUMENT_H_ + +#include "okular_export.h" +#include "area.h" +#include "global.h" +#include "pagesize.h" + +#include +#include +#include +#include +#include + +#include + +class QPrintDialog; +class KComponentData; +class KBookmark; +class KConfigDialog; +class KXMLGUIClient; +class KUrl; +class DocumentItem; + +namespace Okular { + +class Annotation; +class BookmarkManager; +class DocumentInfo; +class DocumentObserver; +class DocumentPrivate; +class DocumentSynopsis; +class DocumentViewport; +class EmbeddedFile; +class ExportFormat; +class FontInfo; +class FormFieldText; +class FormFieldButton; +class FormFieldChoice; +class Generator; +class Action; +class MovieAction; +class Page; +class PixmapRequest; +class RenditionAction; +class SourceReference; +class View; +class VisiblePageRect; + +/** IDs for seaches. Globally defined here. **/ +#define PART_SEARCH_ID 1 +#define PAGEVIEW_SEARCH_ID 2 +#define SW_SEARCH_ID 3 +#define PRESENTATION_SEARCH_ID 4 + + +/** + * @short The Document. Heart of everything. Actions take place here. + * + * The Document is the main object in Okular. All views query the Document to + * get data/properties or even for accessing pages (in a 'const' way). + * + * It is designed to keep it detached from the document type (pdf, ps, you + * name it..) so whenever you want to get some data, it asks its internals + * generator to do the job and return results in a format-indepedent way. + * + * Apart from the generator (the currently running one) the document stores + * all the Pages ('Page' class) of the current document in a vector and + * notifies all the registered DocumentObservers when some content changes. + * + * For a better understanding of hieracies @see README.internals.png + * @see DocumentObserver, Page + */ +class OKULAR_EXPORT Document : public QObject +{ + Q_OBJECT + + public: + /** + * Creates a new document with the given @p widget as widget to relay GUI things (messageboxes, ...). + */ + explicit Document( QWidget *widget ); + + /** + * Destroys the document. + */ + ~Document(); + + /** + * Describes the result of an open document operation. + * @since 0.20 (KDE 4.14) + */ + enum OpenResult + { + OpenSuccess, //< The document was opened successfully + OpenError, //< The document failed to open + OpenNeedsPassword //< The document needs a password to be opened or the one provided is not the correct + }; + + /** + * Opens the document. + * @since 0.20 (KDE 4.14) + */ + OpenResult openDocument( const QString & docFile, const KUrl & url, const KMimeType::Ptr &mime, const QString &password = QString() ); + + /** + * Closes the document. + */ + void closeDocument(); + + /** + * Registers a new @p observer for the document. + */ + void addObserver( DocumentObserver *observer ); + + /** + * Unregisters the given @p observer for the document. + */ + void removeObserver( DocumentObserver *observer ); + + /** + * Reparses and applies the configuration. + */ + void reparseConfig(); + + /** + * Returns whether the document is currently opened. + */ + bool isOpened() const; + + /** + * Returns the meta data of the document or 0 if no meta data + * are available. + */ + const DocumentInfo * documentInfo() const; + + /** + * Returns the table of content of the document or 0 if no + * table of content is available. + */ + const DocumentSynopsis * documentSynopsis() const; + + /** + * Starts the reading of the information about the fonts in the + * document, if available. + * + * The results as well the end of the reading is notified using the + * signals gotFont(), fontReadingProgress() and fontReadingEnded() + */ + void startFontReading(); + + /** + * Force the termination of the reading of the information about the + * fonts in the document, if running. + */ + void stopFontReading(); + + /** + * Whether the current document can provide information about the + * fonts used in it. + */ + bool canProvideFontInformation() const; + + /** + * Returns the list of embedded files or 0 if no embedded files + * are available. + */ + const QList *embeddedFiles() const; + + /** + * Returns the page object for the given page @p number or 0 + * if the number is out of range. + */ + const Page * page( int number ) const; + + /** + * Returns the current viewport of the document. + */ + const DocumentViewport & viewport() const; + + /** + * Sets the list of visible page rectangles. + * @see VisiblePageRect + */ + void setVisiblePageRects( const QVector< VisiblePageRect * > & visiblePageRects, DocumentObserver *excludeObserver = 0 ); + + /** + * Returns the list of visible page rectangles. + */ + const QVector< VisiblePageRect * > & visiblePageRects() const; + + /** + * Returns the number of the current page. + */ + uint currentPage() const; + + /** + * Returns the number of pages of the document. + */ + uint pages() const; + + /** + * Returns the url of the currently opened document. + */ + KUrl currentDocument() const; + + /** + * Returns whether the given @p action is allowed in the document. + * @see @ref Permission + */ + bool isAllowed( Permission action ) const; + + /** + * Returns whether the document supports searching. + */ + bool supportsSearching() const; + + /** + * Returns whether the document supports the listing of page sizes. + */ + bool supportsPageSizes() const; + + /** + * Returns whether the current document supports tiles + * + * @since 0.16 (KDE 4.10) + */ + bool supportsTiles() const; + + /** + * Returns the list of supported page sizes or an empty list if this + * feature is not available. + * @see supportsPageSizes() + */ + PageSize::List pageSizes() const; + + /** + * Returns whether the document supports the export to ASCII text. + */ + bool canExportToText() const; + + /** + * Exports the document as ASCII text and saves it under @p fileName. + */ + bool exportToText( const QString& fileName ) const; + + /** + * Returns the list of supported export formats. + * @see ExportFormat + */ + QList exportFormats() const; + + /** + * Exports the document in the given @p format and saves it under @p fileName. + */ + bool exportTo( const QString& fileName, const ExportFormat& format ) const; + + /** + * Returns whether the document history is at the begin. + */ + bool historyAtBegin() const; + + /** + * Returns whether the document history is at the end. + */ + bool historyAtEnd() const; + + /** + * Returns the meta data for the given @p key and @p option or an empty variant + * if the key doesn't exists. + */ + QVariant metaData( const QString & key, const QVariant & option = QVariant() ) const; + + /** + * Returns the current rotation of the document. + */ + Rotation rotation() const; + + /** + * If all pages have the same size this method returns it, if the page sizes + * differ an empty size object is returned. + */ + QSizeF allPagesSize() const; + + /** + * Returns the size string for the given @p page or an empty string + * if the page is out of range. + */ + QString pageSizeString( int page ) const; + + /** + * Returns the gui client of the generator, if it provides one. + */ + KXMLGUIClient* guiClient(); + + /** + * Sets the current document viewport to the given @p page. + * + * @param excludeObserver The observer ids which shouldn't be effected by this change. + * @param smoothMove Whether the move shall be animated smoothly. + */ + void setViewportPage( int page, DocumentObserver *excludeObserver = 0, bool smoothMove = false ); + + /** + * Sets the current document viewport to the given @p viewport. + * + * @param excludeObserver The observer which shouldn't be effected by this change. + * @param smoothMove Whether the move shall be animated smoothly. + */ + void setViewport( const DocumentViewport &viewport, DocumentObserver *excludeObserver = 0, bool smoothMove = false ); + + /** + * Sets the current document viewport to the next viewport in the + * viewport history. + */ + void setPrevViewport(); + + /** + * Sets the current document viewport to the previous viewport in the + * viewport history. + */ + void setNextViewport(); + + /** + * Sets the next @p viewport in the viewport history. + */ + void setNextDocumentViewport( const DocumentViewport &viewport ); + + /** + * Sets the next @p namedDestination in the viewport history. + * + * @since 0.9 (KDE 4.3) + */ + void setNextDocumentDestination( const QString &namedDestination ); + + /** + * Sets the zoom for the current document. + */ + void setZoom( int factor, DocumentObserver *excludeObserver = 0 ); + + /** + * Describes the possible options for the pixmap requests. + */ + enum PixmapRequestFlag + { + NoOption = 0, ///< No options + RemoveAllPrevious = 1 ///< Remove all the previous requests, even for non requested page pixmaps + }; + Q_DECLARE_FLAGS( PixmapRequestFlags, PixmapRequestFlag ) + + /** + * Sends @p requests for pixmap generation. + * + * The same as requestPixmaps( requests, RemoveAllPrevious ); + */ + void requestPixmaps( const QLinkedList &requests ); + + /** + * Sends @p requests for pixmap generation. + * + * @param reqOptions the options for the request + * + * @since 0.7 (KDE 4.1) + */ + void requestPixmaps( const QLinkedList &requests, PixmapRequestFlags reqOptions ); + + /** + * Sends a request for text page generation for the given page @p number. + */ + void requestTextPage( uint number ); + + /** + * Adds a new @p annotation to the given @p page. + */ + void addPageAnnotation( int page, Annotation *annotation ); + + /** + * Tests if the @p annotation can be modified + * + * @since 0.15 (KDE 4.9) + */ + bool canModifyPageAnnotation( const Annotation * annotation ) const; + + /** + * Prepares to modify the properties of the given @p annotation. + * Must be called before the annotation's properties are modified + * + * @since 0.17 (KDE 4.11) + */ + void prepareToModifyAnnotationProperties( Annotation * annotation ); + + /** + * Modifies the given @p annotation on the given @p page. + * Must be preceded by a call to prepareToModifyAnnotationProperties before + * the annotation's properties are modified + * + * @since 0.17 (KDE 4.11) + */ + void modifyPageAnnotationProperties( int page, Annotation * annotation ); + + /** + * Translates the position of the given @p annotation on the given @p page by a distance @p delta in normalized coordinates. + * + * Consecutive translations applied to the same @p annotation are merged together on the undo stack if the + * BeingMoved flag is set on the @P annotation + * + * @since 0.17 (KDE 4.11) + */ + void translatePageAnnotation( int page, Annotation *annotation, const Okular::NormalizedPoint & delta ); + + + /** + * Edits the plain text contents of the given @p annotation on the given @p page. + * + * The contents are set to @p newContents with cursor position @p newCursorPos. + * The previous cursor position @p prevCursorPos and previous anchor position @p prevAnchorPos + * must also be supplied so that they can be restored if the edit action is undone. + * + * The Annotation's internal contents should not be modified prior to calling this method. + * + * @since 0.17 (KDE 4.11) + */ + void editPageAnnotationContents( int page, Annotation* annotation, const QString & newContents, + int newCursorPos, int prevCursorPos, int prevAnchorPos ); + + /** + * Tests if the @p annotation can be removed + * + * @since 0.15 (KDE 4.9) + */ + bool canRemovePageAnnotation( const Annotation * annotation ) const; + + /** + * Removes the given @p annotation from the given @p page. + */ + void removePageAnnotation( int page, Annotation *annotation ); + + /** + * Removes the given @p annotations from the given @p page. + */ + void removePageAnnotations( int page, const QList &annotations ); + + /** + * Sets the text selection for the given @p page. + * + * @param rect The rectangle of the selection. + * @param color The color of the selection. + */ + void setPageTextSelection( int page, RegularAreaRect * rect, const QColor & color ); + + /** + * Returns true if there is an undo command available; otherwise returns false. + * @since 0.17 (KDE 4.11) + */ + bool canUndo() const; + + /** + * Returns true if there is a redo command available; otherwise returns false. + * @since 0.17 (KDE 4.11) + */ + bool canRedo() const; + + /** + * Describes the possible search types. + */ + enum SearchType + { + NextMatch, ///< Search next match + PreviousMatch, ///< Search previous match + AllDocument, ///< Search complete document + GoogleAll, ///< Search all words in google style + GoogleAny ///< Search any words in google style + }; + + /** + * Describes how search ended + */ + enum SearchStatus + { + MatchFound, ///< Any match was found + NoMatchFound, ///< No match was found + SearchCancelled, ///< The search was cancelled + EndOfDocumentReached ///< The end of document was reached without any match @since 0.20 (KDE 4.14) + }; + + /** + * Searches the given @p text in the document. + * + * @param searchID The unique id for this search request. + * @param fromStart Whether the search should be started at begin of the document. + * @param caseSensitivity Whether the search is case sensitive. + * @param type The type of the search. @ref SearchType + * @param moveViewport Whether the viewport shall be moved to the position of the matches. + * @param color The highlighting color of the matches. + */ + void searchText( int searchID, const QString & text, bool fromStart, Qt::CaseSensitivity caseSensitivity, + SearchType type, bool moveViewport, const QColor & color ); + + /** + * Continues the search for the given @p searchID. + */ + void continueSearch( int searchID ); + + /** + * Continues the search for the given @p searchID, optionally specifying + * a new type for the search. + * + * @since 0.7 (KDE 4.1) + */ + void continueSearch( int searchID, SearchType type ); + + /** + * Resets the search for the given @p searchID. + */ + void resetSearch( int searchID ); + + /** + * Returns the bookmark manager of the document. + */ + BookmarkManager * bookmarkManager() const; + + /** + * Processes the given @p action. + */ + void processAction( const Action *action ); + + /** + * Returns a list of the bookmarked.pages + */ + QList bookmarkedPageList() const; + + /** + * Returns the range of the bookmarked.pages + */ + QString bookmarkedPageRange() const; + + /** + * Processes/Executes the given source @p reference. + */ + void processSourceReference( const SourceReference *reference ); + + /** + * Returns whether the document can configure the printer itself. + */ + bool canConfigurePrinter() const; + + /** + * What type of printing a document supports + */ + enum PrintingType + { + NoPrinting, ///< Printing Not Supported + NativePrinting, ///< Native Cross-Platform Printing + PostscriptPrinting ///< Postscript file printing + }; + + /** + * Returns what sort of printing the document supports: + * Native, Postscript, None + */ + PrintingType printingSupport() const; + + /** + * Returns whether the document supports printing to both PDF and PS files. + */ + bool supportsPrintToFile() const; + + /** + * Prints the document to the given @p printer. + */ + bool print( QPrinter &printer ); + + /** + * Returns the last print error in case print() failed + * @since 0.11 (KDE 4.5) + */ + QString printError() const; + + /** + * Returns a custom printer configuration page or 0 if no + * custom printer configuration page is available. + */ + QWidget* printConfigurationWidget() const; + + /** + * Fill the KConfigDialog @p dialog with the setting pages of the + * generators. + */ + void fillConfigDialog( KConfigDialog * dialog ); + + /** + * Returns the number of generators that have a configuration widget. + */ + int configurableGenerators() const; + + /** + * Returns the list with the supported MIME types. + */ + QStringList supportedMimeTypes() const; + + /** + * Returns the component data associated with the generator. May be null. + */ + const KComponentData* componentData() const; + + /** + * Saving capabilities. Their availability varies according to the + * underlying generator and/or the document type. + * + * @see canSaveChanges (SaveCapability) + * @since 0.15 (KDE 4.9) + */ + enum SaveCapability + { + SaveFormsCapability = 1, ///< Can save form changes + SaveAnnotationsCapability = 2 ///< Can save annotation changes + }; + + /** + * Returns whether it's possible to save a given category of changes to + * another document. + * + * @since 0.15 (KDE 4.9) + */ + bool canSaveChanges( SaveCapability cap ) const; + + /** + * Returns whether the changes to the document (modified annotations, + * values in form fields, etc) can be saved to another document. + * + * Equivalent to the logical OR of canSaveChanges(SaveCapability) for + * each capability. + * + * @since 0.7 (KDE 4.1) + */ + bool canSaveChanges() const; + + /** + * Save the document and the optional changes to it to the specified + * @p fileName. + * + * @since 0.7 (KDE 4.1) + */ + bool saveChanges( const QString &fileName ); + + /** + * Save the document and the optional changes to it to the specified + * @p fileName and returns a @p errorText if fails. + * + * @since 0.10 (KDE 4.4) + */ + bool saveChanges( const QString &fileName, QString *errorText ); + + /** + * Register the specified @p view for the current document. + * + * It is unregistered from the previous document, if any. + * + * @since 0.7 (KDE 4.1) + */ + void registerView( View *view ); + + /** + * Unregister the specified @p view from the current document. + * + * @since 0.7 (KDE 4.1) + */ + void unregisterView( View *view ); + + /** + * Gets the font data for the given font + * + * @since 0.8 (KDE 4.2) + */ + QByteArray fontData(const FontInfo &font) const; + + /** + * Opens a document archive. + * + * @since 0.20 (KDE 4.14) + */ + OpenResult openDocumentArchive( const QString & docFile, const KUrl & url, const QString &password = QString() ); + + /** + * Saves a document archive. + * + * @since 0.8 (KDE 4.2) + */ + bool saveDocumentArchive( const QString &fileName ); + + /** + * Asks the generator to dynamically generate a SourceReference for a given + * page number and absolute X and Y position on this page. + * + * @attention Ownership of the returned SourceReference is transferred to the caller. + * @note This method does not call processSourceReference( const SourceReference * ) + * + * @since 0.10 (KDE 4.4) + */ + const SourceReference * dynamicSourceReference( int pageNr, double absX, double absY ); + + /** + * Returns the orientation of the document (for printing purposes). This + * is used in the KPart to initialize the print dialog and in the + * generators to check whether the document needs to be rotated or not. + * + * @since 0.14 (KDE 4.8) + */ + QPrinter::Orientation orientation() const; + + /** + * Control annotation editing (creation, modification and removal), + * which is enabled by default. + * + * @since 0.15 (KDE 4.9) + */ + void setAnnotationEditingEnabled( bool enable ); + + /** + * Returns which wallet data to use to read/write the password for the given fileName + * + * @since 0.20 (KDE 4.14) + */ + void walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const; + + public Q_SLOTS: + /** + * This slot is called whenever the user changes the @p rotation of + * the document. + */ + void setRotation( int rotation ); + + /** + * This slot is called whenever the user changes the page @p size + * of the document. + */ + void setPageSize( const PageSize &size ); + + /** + * Cancels the current search + */ + void cancelSearch(); + + /** + * Undo last edit command + * @since 0.17 (KDE 4.11) + */ + void undo(); + + /** + * Redo last undone edit command + * @since 0.17 (KDE 4.11) + */ + void redo(); + + /** + * Edit the text contents of the specified @p form on page @p page to be @p newContents. + * The new text cursor position (@p newCursorPos), previous text cursor position (@p prevCursorPos), + * and previous cursor anchor position will be restored by the undo / redo commands. + * @since 0.17 (KDE 4.11) + */ + void editFormText( int pageNumber, + Okular::FormFieldText* form, + const QString & newContents, + int newCursorPos, + int prevCursorPos, + int prevAnchorPos ); + + /** + * Edit the selected list entries in @p form on page @p page to be @p newChoices. + * @since 0.17 (KDE 4.11) + */ + void editFormList( int pageNumber, + Okular::FormFieldChoice* form, + const QList & newChoices ); + + + /** + * Set the active choice in the combo box @p form on page @p page to @p newText + * The new cursor position (@p newCursorPos), previous cursor position + * (@p prevCursorPos), and previous anchor position (@p prevAnchorPos) + * will be restored by the undo / redo commands. + * + * @since 0.17 (KDE 4.11) + */ + void editFormCombo( int pageNumber, + Okular::FormFieldChoice *form, + const QString & newText, + int newCursorPos, + int prevCursorPos, + int prevAnchorPos ); + + /** + * Set the states of the group of form buttons @p formButtons on page @p page to @p newButtonStates. + * The lists @p formButtons and @p newButtonStates should be the same length and true values + * in @p newButtonStates indicate that the corresponding entry in @p formButtons should be enabled. + */ + void editFormButtons( int pageNumber, + const QList< Okular::FormFieldButton* > & formButtons, + const QList< bool > & newButtonStates ); + + Q_SIGNALS: + /** + * This signal is emitted whenever an action requests a + * document close operation. + */ + void close(); + + /** + * This signal is emitted whenever an action requests an + * application quit operation. + */ + void quit(); + + /** + * This signal is emitted whenever an action requests a + * find operation. + */ + void linkFind(); + + /** + * This signal is emitted whenever an action requests a + * goto operation. + */ + void linkGoToPage(); + + /** + * This signal is emitted whenever an action requests a + * start presentation operation. + */ + void linkPresentation(); + + /** + * This signal is emitted whenever an action requests an + * end presentation operation. + */ + void linkEndPresentation(); + + /** + * This signal is emitted whenever an action requests an + * open url operation for the given document @p url. + */ + void openUrl( const KUrl &url ); + + /** + * This signal is emitted whenever an error occurred. + * + * @param text The description of the error. + * @param duration The time in seconds the message should be shown to the user. + */ + void error( const QString &text, int duration ); + + /** + * This signal is emitted to signal a warning. + * + * @param text The description of the warning. + * @param duration The time in seconds the message should be shown to the user. + */ + void warning( const QString &text, int duration ); + + /** + * This signal is emitted to signal a notice. + * + * @param text The description of the notice. + * @param duration The time in seconds the message should be shown to the user. + */ + void notice( const QString &text, int duration ); + + /** + * Emitted when a new font is found during the reading of the fonts of + * the document. + */ + void gotFont( const Okular::FontInfo& font ); + + /** + * Reports the progress when reading the fonts in the document. + * + * \param page is the page that was just finished to scan for fonts + */ + void fontReadingProgress( int page ); + + /** + * Reports that the reading of the fonts in the document is finished. + */ + void fontReadingEnded(); + + /** + * Reports that the current search finished + */ + void searchFinished( int searchID, Okular::Document::SearchStatus endStatus ); + + /** + * This signal is emitted whenever a source reference with the given parameters has been + * activated. + * + * \param handled should be set to 'true' if a slot handles this source reference; the + * default action to launch the configured editor will then not be performed + * by the document + * + * @since 0.14 (KDE 4.8) + */ + void sourceReferenceActivated(const QString& absFileName, int line, int col, bool *handled); + + /** + * This signal is emitted whenever an movie action is triggered and the UI should process it. + */ + void processMovieAction( const Okular::MovieAction *action ); + + /** + * This signal is emmitted whenever the availability of the undo function changes + * @since 0.17 (KDE 4.11) + */ + void canUndoChanged( bool undoAvailable ); + + /** + * This signal is emmitted whenever the availability of the redo function changes + * @since 0.17 (KDE 4.11) + */ + void canRedoChanged( bool redoAvailable ); + + /** + * This signal is emitted whenever an rendition action is triggered and the UI should process it. + * + * @since 0.16 (KDE 4.10) + */ + void processRenditionAction( const Okular::RenditionAction *action ); + + /** + * This signal is emmitted whenever the contents of the given @p annotation are changed by an undo + * or redo action. + * + * The new contents (@p contents), cursor position (@p cursorPos), and anchor position (@p anchorPos) are + * included + * @since 0.17 (KDE 4.11) + */ + void annotationContentsChangedByUndoRedo( Okular::Annotation* annotation, const QString & contents, int cursorPos, int anchorPos ); + + /** + * This signal is emmitted whenever the text contents of the given text @p form on the given @p page + * are changed by an undo or redo action. + * + * The new text contents (@p contents), cursor position (@p cursorPos), and anchor position (@p anchorPos) are + * included + * @since 0.17 (KDE 4.11) + */ + void formTextChangedByUndoRedo( int page, Okular::FormFieldText* form, const QString & contents, int cursorPos, int anchorPos ); + + /** + * This signal is emmitted whenever the selected @p choices for the given list @p form on the + * given @p page are changed by an undo or redo action. + * @since 0.17 (KDE 4.11) + */ + void formListChangedByUndoRedo( int page, Okular::FormFieldChoice* form, const QList< int > & choices ); + + /** + * This signal is emmitted whenever the active @p text for the given combo @p form on the + * given @p page is changed by an undo or redo action. + * @since 0.17 (KDE 4.11) + */ + void formComboChangedByUndoRedo( int page, Okular::FormFieldChoice* form, const QString & text, int cursorPos, int anchorPos ); + + /** + * This signal is emmitted whenever the state of the specified group of form buttons (@p formButtons) on the + * given @p page is changed by an undo or redo action. + * @since 0.17 (KDE 4.11) + */ + void formButtonsChangedByUndoRedo( int page, const QList< Okular::FormFieldButton* > & formButtons ); + private: + /// @cond PRIVATE + friend class DocumentPrivate; + friend class ::DocumentItem; + friend class EditAnnotationContentsCommand; + friend class EditFormTextCommand; + friend class EditFormListCommand; + friend class EditFormComboCommand; + friend class EditFormButtonsCommand; + /// @endcond + DocumentPrivate *const d; + + Q_DISABLE_COPY( Document ) + + Q_PRIVATE_SLOT( d, void saveDocumentInfo() const ) + Q_PRIVATE_SLOT( d, void slotTimedMemoryCheck() ) + Q_PRIVATE_SLOT( d, void sendGeneratorPixmapRequest() ) + Q_PRIVATE_SLOT( d, void rotationFinished( int page, Okular::Page *okularPage ) ) + Q_PRIVATE_SLOT( d, void fontReadingProgress( int page ) ) + Q_PRIVATE_SLOT( d, void fontReadingGotFont( const Okular::FontInfo& font ) ) + Q_PRIVATE_SLOT( d, void slotGeneratorConfigChanged( const QString& ) ) + Q_PRIVATE_SLOT( d, void refreshPixmaps( int ) ) + Q_PRIVATE_SLOT( d, void _o_configChanged() ) + + // search thread simulators + Q_PRIVATE_SLOT( d, void doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct) ) + Q_PRIVATE_SLOT( d, void doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID) ) + Q_PRIVATE_SLOT( d, void doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList & words) ) +}; + + +/** + * @short A view on the document. + * + * The Viewport structure is the 'current view' over the document. Contained + * data is broadcasted between observers to synchronize their viewports to get + * the 'I scroll one view and others scroll too' views. + */ +class OKULAR_EXPORT DocumentViewport +{ + public: + /** + * Creates a new viewport for the given page @p number. + */ + DocumentViewport( int number = -1 ); + + /** + * Creates a new viewport from the given xml @p description. + */ + DocumentViewport( const QString &description ); + + /** + * Returns the viewport as xml description. + */ + QString toString() const; + + /** + * Returns whether the viewport is valid. + */ + bool isValid() const; + + /** + * @internal + */ + bool operator==( const DocumentViewport &other ) const; + bool operator<( const DocumentViewport &other ) const; + + /** + * The number of the page nearest the center of the viewport. + */ + int pageNumber; + + /** + * Describes the relative position of the viewport. + */ + enum Position + { + Center = 1, ///< Relative to the center of the page. + TopLeft = 2 ///< Relative to the top left corner of the page. + }; + + /** + * If 'rePos.enabled == true' then this structure contains the + * viewport center or top left depending on the value of pos. + */ + struct { + bool enabled; + double normalizedX; + double normalizedY; + Position pos; + } rePos; + + /** + * If 'autoFit.enabled == true' then the page must be autofitted in the viewport. + */ + struct { + bool enabled; + bool width; + bool height; + } autoFit; +}; + +/** + * @short A DOM tree containing information about the document. + * + * The DocumentInfo structure can be filled in by generators to display + * metadata about the currently opened file. + */ +class OKULAR_EXPORT DocumentInfo : public QDomDocument +{ + public: + /** + * The list of predefined keys. + */ + enum Key { + Title, ///< The title of the document + Subject, ///< The subject of the document + Description, ///< The description of the document + Author, ///< The author of the document + Creator, ///< The creator of the document (this can be different from the author) + Producer, ///< The producer of the document (e.g. some software) + Copyright, ///< The copyright of the document + Pages, ///< The number of pages of the document + CreationDate, ///< The date of creation of the document + ModificationDate, ///< The date of last modification of the document + MimeType, ///< The mime type of the document + Category, ///< The category of the document + Keywords, ///< The keywords which describe the content of the document + FilePath, ///< The path of the file @since 0.10 (KDE 4.4) + DocumentSize, ///< The size of the document @since 0.10 (KDE 4.4) + PagesSize ///< The size of the pages (if all pages have the same size) @since 0.10 (KDE 4.4) + }; + + /** + * Creates a new document info. + */ + DocumentInfo(); + + /** + * Sets a value for a special key. The title should be an i18n'ed + * string, since it's used in the document information dialog. + */ + void set( const QString &key, const QString &value, + const QString &title = QString() ); + + /** + * Sets the value for a predefined key. You should use this method + * whenever a predefined key exists for your value. + */ + void set( Key key, const QString &value ); + + /** + * Returns the value for a given key or an empty string when the + * key doesn't exist. + */ + QString get( const QString &key ) const; + + /** + * Returns the internal string for the given key + * @since 0.10 (KDE 4.4) + */ + static QString getKeyString( Key key ); + + /** + * Returns the user visible string for the given key + * @since 0.10 (KDE 4.4) + */ + static QString getKeyTitle( Key key ); + +}; + +/** + * @short A DOM tree that describes the Table of Contents. + * + * The Synopsis (TOC or Table Of Contents for friends) is represented via + * a dom tree where each node has an internal name (displayed in the TOC) + * and one or more attributes. + * + * In the tree the tag name is the 'screen' name of the entry. A tag can have + * attributes. Here follows the list of tag attributes with meaning: + * - Destination: A string description of the referred viewport + * - DestinationName: A 'named reference' to the viewport that must be converted + * using metaData( "NamedViewport", viewport_name ) + * - ExternalFileName: A document to be opened, whose destination is specified + * with Destination or DestinationName + * - Open: a boolean saying whether its TOC branch is open or not (default: false) + * - URL: a URL to be open as destination; if set, no other Destination* or + * ExternalFileName entry is used + */ +class OKULAR_EXPORT DocumentSynopsis : public QDomDocument +{ + public: + /** + * Creates a new document synopsis object. + */ + DocumentSynopsis(); + + /** + * Creates a new document synopsis object with the given + * @p document as parent node. + */ + DocumentSynopsis( const QDomDocument &document ); +}; + +/** + * @short An embedded file into the document. + * + * This class represents a sort of interface of an embedded file in a document. + * + * Generators \b must re-implement its members to give the all the information + * about an embedded file, like its name, its description, the date of creation + * and modification, and the real data of the file. + */ +class OKULAR_EXPORT EmbeddedFile +{ + public: + /** + * Creates a new embedded file. + */ + EmbeddedFile(); + + /** + * Destroys the embedded file. + */ + virtual ~EmbeddedFile(); + + /** + * Returns the name of the file + */ + virtual QString name() const = 0; + + /** + * Returns the description of the file, or an empty string if not + * available + */ + virtual QString description() const = 0; + + /** + * Returns the real data representing the file contents + */ + virtual QByteArray data() const = 0; + + /** + * Returns the size (in bytes) of the file, if available, or -1 otherwise. + * + * @note this method should be a fast way to know the size of the file + * with no need to extract all the data from it + */ + virtual int size() const = 0; + + /** + * Returns the modification date of the file, or an invalid date + * if not available + */ + virtual QDateTime modificationDate() const = 0; + + /** + * Returns the creation date of the file, or an invalid date + * if not available + */ + virtual QDateTime creationDate() const = 0; + +}; + +/** + * @short An area of a specified page + */ +class OKULAR_EXPORT VisiblePageRect +{ + public: + /** + * Creates a new visible page rectangle. + * + * @param pageNumber The page number where the rectangle is located. + * @param rectangle The rectangle in normalized coordinates. + */ + explicit VisiblePageRect( int pageNumber = -1, const NormalizedRect &rectangle = NormalizedRect() ); + + /** + * The page number where the rectangle is located. + */ + int pageNumber; + + /** + * The rectangle in normalized coordinates. + */ + NormalizedRect rect; +}; + +} + +Q_DECLARE_METATYPE( Okular::DocumentInfo::Key ) +Q_DECLARE_OPERATORS_FOR_FLAGS( Okular::Document::PixmapRequestFlags ) + +#endif + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/okular/core/document_p.h b/okular/core/document_p.h new file mode 100644 index 00000000..08775da4 --- /dev/null +++ b/okular/core/document_p.h @@ -0,0 +1,279 @@ +/*************************************************************************** + * Copyright (C) 2004-2005 by Enrico Ros * + * Copyright (C) 2004-2007 by Albert Astals Cid * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_DOCUMENT_P_H_ +#define _OKULAR_DOCUMENT_P_H_ + +#include "document.h" + +// qt/kde/system includes +#include +#include +#include +#include +#include + +#include +#include + +// local includes +#include "fontinfo.h" +#include "generator.h" + +class QUndoStack; +class QEventLoop; +class QFile; +class QTimer; +class KTemporaryFile; + +struct AllocatedPixmap; +struct ArchiveData; +struct RunningSearch; + +namespace Okular { +class ConfigInterface; +class PageController; +class SaveInterface; +class Scripter; +class View; +} + +struct GeneratorInfo +{ + GeneratorInfo( const KComponentData &_data ) + : generator( 0 ), data( _data ), + config( 0 ), save( 0 ), + configChecked( false ), saveChecked( false ) + {} + + Okular::Generator * generator; + KComponentData data; + QString catalogName; + Okular::ConfigInterface * config; + Okular::SaveInterface * save; + bool configChecked : 1; + bool saveChecked : 1; +}; + +namespace Okular { + +class FontExtractionThread; + +struct DoContinueDirectionMatchSearchStruct +{ + QSet< int > *pagesToNotify; + RegularAreaRect *match; + int currentPage; + int searchID; +}; + +class DocumentPrivate +{ + public: + DocumentPrivate( Document *parent ) + : m_parent( parent ), + m_tempFile( 0 ), + m_docSize( -1 ), + m_allocatedPixmapsTotalMemory( 0 ), + m_maxAllocatedTextPages( 0 ), + m_warnedOutOfMemory( false ), + m_rotation( Rotation0 ), + m_exportCached( false ), + m_bookmarkManager( 0 ), + m_memCheckTimer( 0 ), + m_saveBookmarksTimer( 0 ), + m_generator( 0 ), + m_generatorsLoaded( false ), + m_pageController( 0 ), + m_closingLoop( 0 ), + m_scripter( 0 ), + m_archiveData( 0 ), + m_fontsCached( false ), + m_documentInfo( 0 ), + m_annotationEditingEnabled ( true ), + m_annotationBeingMoved( false ) + { + calculateMaxTextPages(); + } + + // private methods + QString pagesSizeString() const; + QString namePaperSize(double inchesWidth, double inchesHeight) const; + QString localizedSize(const QSizeF &size) const; + qulonglong calculateMemoryToFree(); + void cleanupPixmapMemory(); + void cleanupPixmapMemory( qulonglong memoryToFree ); + AllocatedPixmap * searchLowestPriorityPixmap( bool unloadableOnly = false, bool thenRemoveIt = false, DocumentObserver *observer = 0 /* any */ ); + void calculateMaxTextPages(); + qulonglong getTotalMemory(); + qulonglong getFreeMemory( qulonglong *freeSwap = 0 ); + void loadDocumentInfo(); + void loadDocumentInfo( QFile &infoFile ); + void loadViewsInfo( View *view, const QDomElement &e ); + void saveViewsInfo( View *view, QDomElement &e ) const; + QString giveAbsolutePath( const QString & fileName ) const; + bool openRelativeFile( const QString & fileName ); + Generator * loadGeneratorLibrary( const KService::Ptr &service ); + void loadAllGeneratorLibraries(); + void loadServiceList( const KService::List& offers ); + void unloadGenerator( const GeneratorInfo& info ); + void cacheExportFormats(); + void setRotationInternal( int r, bool notify ); + ConfigInterface* generatorConfig( GeneratorInfo& info ); + SaveInterface* generatorSave( GeneratorInfo& info ); + Document::OpenResult openDocumentInternal( const KService::Ptr& offer, bool isstdin, const QString& docFile, const QByteArray& filedata, const QString& password ); + bool savePageDocumentInfo( KTemporaryFile *infoFile, int what ) const; + DocumentViewport nextDocumentViewport() const; + void notifyAnnotationChanges( int page ); + bool canAddAnnotationsNatively() const; + bool canModifyExternalAnnotations() const; + bool canRemoveExternalAnnotations() const; + void warnLimitedAnnotSupport(); + OKULAR_EXPORT static QString docDataFileName(const KUrl &url, qint64 document_size); + + // Methods that implement functionality needed by undo commands + void performAddPageAnnotation( int page, Annotation *annotation ); + void performRemovePageAnnotation( int page, Annotation * annotation ); + void performModifyPageAnnotation( int page, Annotation * annotation, bool appearanceChanged ); + void performSetAnnotationContents( const QString & newContents, Annotation *annot, int pageNumber ); + + // private slots + void saveDocumentInfo() const; + void slotTimedMemoryCheck(); + void sendGeneratorPixmapRequest(); + void rotationFinished( int page, Okular::Page *okularPage ); + void fontReadingProgress( int page ); + void fontReadingGotFont( const Okular::FontInfo& font ); + void slotGeneratorConfigChanged( const QString& ); + void refreshPixmaps( int ); + void _o_configChanged(); + void doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct); + void doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID); + void doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList & words); + + void doProcessSearchMatch( RegularAreaRect *match, RunningSearch *search, QSet< int > *pagesToNotify, int currentPage, int searchID, bool moveViewport, const QColor & color ); + + // generators stuff + /** + * This method is used by the generators to signal the finish of + * the pixmap generation @p request. + */ + void requestDone( PixmapRequest * request ); + void textGenerationDone( Page *page ); + /** + * Sets the bounding box of the given @p page (in terms of upright orientation, i.e., Rotation0). + */ + void setPageBoundingBox( int page, const NormalizedRect& boundingBox ); + /** + * Request a particular metadata of the Document itself (ie, not something + * depending on the document type/backend). + */ + QVariant documentMetaData( const QString &key, const QVariant &option ) const; + + /** + * Return whether the normalized rectangle @p rectOfInterest on page number @p rectPage + * is fully visible. + */ + bool isNormalizedRectangleFullyVisible( const Okular::NormalizedRect & rectOfInterest, int rectPage ); + + // member variables + Document *m_parent; + QPointer m_widget; + + // find descriptors, mapped by ID (we handle multiple searches) + QMap< int, RunningSearch * > m_searches; + bool m_searchCancelled; + + // needed because for remote documents docFileName is a local file and + // we want the remote url when the document refers to relativeNames + KUrl m_url; + + // cached stuff + QString m_docFileName; + QString m_xmlFileName; + KTemporaryFile *m_tempFile; + qint64 m_docSize; + + // viewport stuff + QLinkedList< DocumentViewport > m_viewportHistory; + QLinkedList< DocumentViewport >::iterator m_viewportIterator; + DocumentViewport m_nextDocumentViewport; // see Link::Goto for an explanation + QString m_nextDocumentDestination; + + // observers / requests / allocator stuff + QSet< DocumentObserver * > m_observers; + QLinkedList< PixmapRequest * > m_pixmapRequestsStack; + QLinkedList< PixmapRequest * > m_executingPixmapRequests; + QMutex m_pixmapRequestsMutex; + QLinkedList< AllocatedPixmap * > m_allocatedPixmaps; + qulonglong m_allocatedPixmapsTotalMemory; + QList< int > m_allocatedTextPagesFifo; + int m_maxAllocatedTextPages; + bool m_warnedOutOfMemory; + + // the rotation applied to the document + Rotation m_rotation; + + // the current size of the pages (if available), and the cache of the + // available page sizes + PageSize m_pageSize; + PageSize::List m_pageSizes; + + // cache of the export formats + bool m_exportCached; + ExportFormat::List m_exportFormats; + ExportFormat m_exportToText; + + // our bookmark manager + BookmarkManager *m_bookmarkManager; + + // timers (memory checking / info saver) + QTimer *m_memCheckTimer; + QTimer *m_saveBookmarksTimer; + + QHash m_loadedGenerators; + Generator * m_generator; + QString m_generatorName; + bool m_generatorsLoaded; + QVector< Page * > m_pagesVector; + QVector< VisiblePageRect * > m_pageRects; + + // cache of the mimetype we support + QStringList m_supportedMimeTypes; + + PageController *m_pageController; + QEventLoop *m_closingLoop; + + Scripter *m_scripter; + + ArchiveData *m_archiveData; + QString m_archivedFileName; + + QPointer< FontExtractionThread > m_fontThread; + bool m_fontsCached; + DocumentInfo *m_documentInfo; + FontInfo::List m_fontsCache; + + QSet< View * > m_views; + + bool m_annotationEditingEnabled; + bool m_annotationsNeedSaveAs; + bool m_annotationBeingMoved; // is an annotation currently being moved? + bool m_showWarningLimitedAnnotSupport; + + QUndoStack *m_undoStack; + QDomNode m_prevPropsOfAnnotBeingModified; +}; + +} + +#endif + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/okular/core/documentcommands.cpp b/okular/core/documentcommands.cpp new file mode 100644 index 00000000..95aded51 --- /dev/null +++ b/okular/core/documentcommands.cpp @@ -0,0 +1,567 @@ +/*************************************************************************** + * Copyright (C) 2013 Jon Mease * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "documentcommands_p.h" + +#include "annotations.h" +#include "debug_p.h" +#include "document_p.h" +#include "form.h" +#include "utils_p.h" +#include "page.h" + +#include + +namespace Okular { + +void moveViewportIfBoundingRectNotFullyVisible( Okular::NormalizedRect boundingRect, + DocumentPrivate *docPriv, + int pageNumber ) +{ + const Rotation pageRotation = docPriv->m_parent->page( pageNumber )->rotation(); + const QTransform rotationMatrix = Okular::buildRotationMatrix( pageRotation ); + boundingRect.transform( rotationMatrix ); + if ( !docPriv->isNormalizedRectangleFullyVisible( boundingRect, pageNumber ) ) + { + DocumentViewport searchViewport( pageNumber ); + searchViewport.rePos.enabled = true; + searchViewport.rePos.normalizedX = ( boundingRect.left + boundingRect.right ) / 2.0; + searchViewport.rePos.normalizedY = ( boundingRect.top + boundingRect.bottom ) / 2.0; + docPriv->m_parent->setViewport( searchViewport, 0, true ); + } +} + +Okular::NormalizedRect buildBoundingRectangleForButtons( const QList & formButtons ) +{ + // Initialize coordinates of the bounding rect + double left = 1.0; + double top = 1.0; + double right = 0.0; + double bottom = 0.0; + + foreach( FormFieldButton* formButton, formButtons ) + { + left = qMin( left, formButton->rect().left ); + top = qMin( top, formButton->rect().top ); + right = qMax( right, formButton->rect().right ); + bottom = qMax( bottom, formButton->rect().bottom ); + } + Okular::NormalizedRect boundingRect( left, top, right, bottom ); + return boundingRect; +} + +AddAnnotationCommand::AddAnnotationCommand( Okular::DocumentPrivate * docPriv, Okular::Annotation* annotation, int pageNumber ) + : m_docPriv( docPriv ), + m_annotation( annotation ), + m_pageNumber( pageNumber ), + m_done( false ) +{ + setText( i18nc ("Add an annotation to the page", "add annotation" ) ); +} + +AddAnnotationCommand::~AddAnnotationCommand() +{ + if ( !m_done ) + { + delete m_annotation; + } +} + +void AddAnnotationCommand::undo() +{ + moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber ); + m_docPriv->performRemovePageAnnotation( m_pageNumber, m_annotation ); + m_done = false; +} + +void AddAnnotationCommand::redo() +{ + moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber ); + m_docPriv->performAddPageAnnotation( m_pageNumber, m_annotation ); + m_done = true; +} + + +RemoveAnnotationCommand::RemoveAnnotationCommand(Okular::DocumentPrivate * doc, Okular::Annotation* annotation, int pageNumber) + : m_docPriv( doc ), + m_annotation( annotation ), + m_pageNumber( pageNumber ), + m_done( false ) +{ + setText( i18nc( "Remove an annotation from the page", "remove annotation" ) ); +} + +RemoveAnnotationCommand::~RemoveAnnotationCommand() +{ + if ( m_done ) + { + delete m_annotation; + } +} + +void RemoveAnnotationCommand::undo() +{ + moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber ); + m_docPriv->performAddPageAnnotation( m_pageNumber, m_annotation ); + m_done = false; +} + +void RemoveAnnotationCommand::redo(){ + moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber ); + m_docPriv->performRemovePageAnnotation( m_pageNumber, m_annotation ); + m_done = true; +} + + +ModifyAnnotationPropertiesCommand::ModifyAnnotationPropertiesCommand( DocumentPrivate* docPriv, + Annotation* annotation, + int pageNumber, + QDomNode oldProperties, + QDomNode newProperties ) + : m_docPriv( docPriv ), + m_annotation( annotation ), + m_pageNumber( pageNumber ), + m_prevProperties( oldProperties ), + m_newProperties( newProperties ) +{ + setText(i18nc("Modify an annotation's internal properties (Color, line-width, etc.)", "modify annotation properties")); +} + +void ModifyAnnotationPropertiesCommand::undo() +{ + moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber ); + m_annotation->setAnnotationProperties( m_prevProperties ); + m_docPriv->performModifyPageAnnotation( m_pageNumber, m_annotation, true ); +} + +void ModifyAnnotationPropertiesCommand::redo() +{ + moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber ); + m_annotation->setAnnotationProperties( m_newProperties ); + m_docPriv->performModifyPageAnnotation( m_pageNumber, m_annotation, true ); +} + +TranslateAnnotationCommand::TranslateAnnotationCommand( DocumentPrivate* docPriv, + Annotation* annotation, + int pageNumber, + const Okular::NormalizedPoint & delta, + bool completeDrag ) + : m_docPriv( docPriv ), + m_annotation( annotation ), + m_pageNumber( pageNumber ), + m_delta( delta ), + m_completeDrag( completeDrag ) +{ + setText( i18nc( "Translate an annotation's position on the page", "translate annotation" ) ); +} + +void TranslateAnnotationCommand::undo() +{ + moveViewportIfBoundingRectNotFullyVisible(translateBoundingRectangle( minusDelta() ), m_docPriv, m_pageNumber ); + m_annotation->translate( minusDelta() ); + m_docPriv->performModifyPageAnnotation( m_pageNumber, m_annotation, true ); +} + +void TranslateAnnotationCommand::redo() +{ + moveViewportIfBoundingRectNotFullyVisible(translateBoundingRectangle( m_delta ), m_docPriv, m_pageNumber ); + m_annotation->translate( m_delta ); + m_docPriv->performModifyPageAnnotation( m_pageNumber, m_annotation, true ); +} + +int TranslateAnnotationCommand::id() const +{ + return 1; +} + +bool TranslateAnnotationCommand::mergeWith( const QUndoCommand* uc ) +{ + TranslateAnnotationCommand *tuc = (TranslateAnnotationCommand*)uc; + + if ( tuc->m_annotation != m_annotation ) + return false; + + if ( m_completeDrag ) + { + return false; + } + m_delta = Okular::NormalizedPoint( tuc->m_delta.x + m_delta.x, tuc->m_delta.y + m_delta.y ); + m_completeDrag = tuc->m_completeDrag; + return true; +} + +Okular::NormalizedPoint TranslateAnnotationCommand::minusDelta() +{ + return Okular::NormalizedPoint( -m_delta.x, -m_delta.y ); +} + +Okular::NormalizedRect TranslateAnnotationCommand::translateBoundingRectangle( const Okular::NormalizedPoint & delta ) +{ + Okular::NormalizedRect annotBoundingRect = m_annotation->boundingRectangle(); + double left = qMin( annotBoundingRect.left, annotBoundingRect.left + delta.x ); + double right = qMax( annotBoundingRect.right, annotBoundingRect.right + delta.x ); + double top = qMin( annotBoundingRect.top, annotBoundingRect.top + delta.y ); + double bottom = qMax( annotBoundingRect.bottom, annotBoundingRect.bottom + delta.y ); + Okular::NormalizedRect boundingRect( left, top, right, bottom ); + return boundingRect; +} + +EditTextCommand::EditTextCommand( const QString & newContents, + int newCursorPos, + const QString & prevContents, + int prevCursorPos, + int prevAnchorPos ) + : m_newContents( newContents ), + m_newCursorPos( newCursorPos ), + m_prevContents( prevContents ), + m_prevCursorPos( prevCursorPos ), + m_prevAnchorPos( prevAnchorPos ) +{ + setText( i18nc( "Generic text edit command", "edit text" ) ); + + //// Determine edit type + // If There was a selection then edit was not a simple single character backspace, delete, or insert + if (m_prevCursorPos != m_prevAnchorPos) + { + kDebug(OkularDebug) << "OtherEdit, selection"; + m_editType = OtherEdit; + } + else if ( newContentsRightOfCursor() == oldContentsRightOfCursor() && + newContentsLeftOfCursor() == oldContentsLeftOfCursor().left(oldContentsLeftOfCursor().length() - 1) && + oldContentsLeftOfCursor().right(1) != "\n" ) + { + kDebug(OkularDebug) << "CharBackspace"; + m_editType = CharBackspace; + } + else if ( newContentsLeftOfCursor() == oldContentsLeftOfCursor() && + newContentsRightOfCursor() == oldContentsRightOfCursor().right(oldContentsRightOfCursor().length() - 1) && + oldContentsRightOfCursor().left(1) != "\n" ) + { + kDebug(OkularDebug) << "CharDelete"; + m_editType = CharDelete; + } + else if ( newContentsRightOfCursor() == oldContentsRightOfCursor() && + newContentsLeftOfCursor().left( newContentsLeftOfCursor().length() - 1) == oldContentsLeftOfCursor() && + newContentsLeftOfCursor().right(1) != "\n" ) + { + kDebug(OkularDebug) << "CharInsert"; + m_editType = CharInsert; + } + else + { + kDebug(OkularDebug) << "OtherEdit"; + m_editType = OtherEdit; + } +} + +bool EditTextCommand::mergeWith(const QUndoCommand* uc) +{ + EditTextCommand *euc = (EditTextCommand*)uc; + + // Only attempt merge of euc into this if our new state matches euc's old state and + // the editTypes match and are not type OtherEdit + if ( m_newContents == euc->m_prevContents + && m_newCursorPos == euc->m_prevCursorPos + && m_editType == euc->m_editType + && m_editType != OtherEdit ) + { + m_newContents = euc->m_newContents; + m_newCursorPos = euc->m_newCursorPos; + return true; + } + return false; +} + +QString EditTextCommand::oldContentsLeftOfCursor() +{ + return m_prevContents.left(m_prevCursorPos); +} + +QString EditTextCommand::oldContentsRightOfCursor() +{ + return m_prevContents.right(m_prevContents.length() - m_prevCursorPos); +} + +QString EditTextCommand::newContentsLeftOfCursor() +{ + return m_newContents.left(m_newCursorPos); +} + +QString EditTextCommand::newContentsRightOfCursor() +{ + return m_newContents.right(m_newContents.length() - m_newCursorPos); +} + +EditAnnotationContentsCommand::EditAnnotationContentsCommand( DocumentPrivate* docPriv, + Annotation* annotation, + int pageNumber, + const QString & newContents, + int newCursorPos, + const QString & prevContents, + int prevCursorPos, + int prevAnchorPos ) +: EditTextCommand( newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos ), + m_docPriv( docPriv ), + m_annotation( annotation ), + m_pageNumber( pageNumber ) +{ + setText( i18nc( "Edit an annotation's text contents", "edit annotation contents" ) ); +} + +void EditAnnotationContentsCommand::undo() +{ + moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber ); + m_docPriv->performSetAnnotationContents( m_prevContents, m_annotation, m_pageNumber ); + emit m_docPriv->m_parent->annotationContentsChangedByUndoRedo( m_annotation, m_prevContents, m_prevCursorPos, m_prevAnchorPos ); +} + +void EditAnnotationContentsCommand::redo() +{ + moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber ); + m_docPriv->performSetAnnotationContents( m_newContents, m_annotation, m_pageNumber ); + emit m_docPriv->m_parent->annotationContentsChangedByUndoRedo( m_annotation, m_newContents, m_newCursorPos, m_newCursorPos ); +} + +int EditAnnotationContentsCommand::id() const +{ + return 2; +} + +bool EditAnnotationContentsCommand::mergeWith(const QUndoCommand* uc) +{ + EditAnnotationContentsCommand *euc = (EditAnnotationContentsCommand*)uc; + // Only attempt merge of euc into this if they modify the same annotation + if ( m_annotation == euc->m_annotation ) + { + return EditTextCommand::mergeWith( uc ); + } + else + { + return false; + } +} + +EditFormTextCommand::EditFormTextCommand( Okular::DocumentPrivate* docPriv, + Okular::FormFieldText* form, + int pageNumber, + const QString & newContents, + int newCursorPos, + const QString & prevContents, + int prevCursorPos, + int prevAnchorPos ) +: EditTextCommand( newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos ), + m_docPriv ( docPriv ), + m_form( form ), + m_pageNumber( pageNumber ) +{ + setText( i18nc( "Edit an form's text contents", "edit form contents" ) ); +} + +void EditFormTextCommand::undo() +{ + moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber ); + m_form->setText( m_prevContents ); + emit m_docPriv->m_parent->formTextChangedByUndoRedo( m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos ); +} + +void EditFormTextCommand::redo() +{ + moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber ); + m_form->setText( m_newContents ); + emit m_docPriv->m_parent->formTextChangedByUndoRedo( m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos ); +} + +int EditFormTextCommand::id() const +{ + return 3; +} + +bool EditFormTextCommand::mergeWith(const QUndoCommand* uc) +{ + EditFormTextCommand *euc = (EditFormTextCommand*)uc; + // Only attempt merge of euc into this if they modify the same form + if ( m_form == euc->m_form ) + { + return EditTextCommand::mergeWith( uc ); + } + else + { + return false; + } +} + +EditFormListCommand::EditFormListCommand( Okular::DocumentPrivate* docPriv, + FormFieldChoice* form, + int pageNumber, + const QList< int > & newChoices, + const QList< int > & prevChoices ) +: m_docPriv( docPriv ), + m_form( form ), + m_pageNumber( pageNumber ), + m_newChoices( newChoices ), + m_prevChoices( prevChoices ) +{ + setText( i18nc( "Edit a list form's choices", "edit list form choices" ) ); +} + +void EditFormListCommand::undo() +{ + moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber ); + m_form->setCurrentChoices( m_prevChoices ); + emit m_docPriv->m_parent->formListChangedByUndoRedo( m_pageNumber, m_form, m_prevChoices ); +} + +void EditFormListCommand::redo() +{ + moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber ); + m_form->setCurrentChoices( m_newChoices ); + emit m_docPriv->m_parent->formListChangedByUndoRedo( m_pageNumber, m_form, m_newChoices ); +} + +EditFormComboCommand::EditFormComboCommand( Okular::DocumentPrivate* docPriv, + FormFieldChoice* form, + int pageNumber, + const QString & newContents, + int newCursorPos, + const QString & prevContents, + int prevCursorPos, + int prevAnchorPos ) +: EditTextCommand( newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos ), + m_docPriv( docPriv ), + m_form( form ), + m_pageNumber( pageNumber ), + m_newIndex( -1 ), + m_prevIndex( -1 ) +{ + setText( i18nc( "Edit a combo form's selection", "edit combo form selection" ) ); + + // Determine new and previous choice indices (if any) + for ( int i = 0; i < m_form->choices().size(); i++ ) + { + if ( m_form->choices()[i] == m_prevContents ) + { + m_prevIndex = i; + } + + if ( m_form->choices()[i] == m_newContents ) + { + m_newIndex = i; + } + } +} + +void EditFormComboCommand::undo() +{ + if ( m_prevIndex != -1 ) + { + m_form->setCurrentChoices( QList() << m_prevIndex ); + } + else + { + m_form->setEditChoice( m_prevContents ); + } + moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber ); + emit m_docPriv->m_parent->formComboChangedByUndoRedo( m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos ); +} + +void EditFormComboCommand::redo() +{ + if ( m_newIndex != -1 ) + { + m_form->setCurrentChoices( QList() << m_newIndex ); + } + else + { + m_form->setEditChoice( m_newContents ); + } + moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber ); + emit m_docPriv->m_parent->formComboChangedByUndoRedo( m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos ); +} + +int EditFormComboCommand::id() const +{ + return 4; +} + +bool EditFormComboCommand::mergeWith( const QUndoCommand *uc ) +{ + EditFormComboCommand *euc = (EditFormComboCommand*)uc; + // Only attempt merge of euc into this if they modify the same form + if ( m_form == euc->m_form ) + { + bool shouldMerge = EditTextCommand::mergeWith( uc ); + if( shouldMerge ) + { + m_newIndex = euc->m_newIndex; + } + return shouldMerge; + } + else + { + return false; + } +} + +EditFormButtonsCommand::EditFormButtonsCommand( Okular::DocumentPrivate* docPriv, + int pageNumber, + const QList< FormFieldButton* > & formButtons, + const QList< bool > & newButtonStates ) +: m_docPriv( docPriv ), + m_pageNumber( pageNumber ), + m_formButtons( formButtons ), + m_newButtonStates( newButtonStates ), + m_prevButtonStates( QList< bool >() ) +{ + setText( i18nc( "Edit the state of a group of form buttons", "edit form button states" ) ); + foreach( FormFieldButton* formButton, m_formButtons ) + { + m_prevButtonStates.append( formButton->state() ); + } +} + +void EditFormButtonsCommand::undo() +{ + clearFormButtonStates(); + for( int i = 0; i < m_formButtons.size(); i++ ) + { + bool checked = m_prevButtonStates.at( i ); + if ( checked ) + m_formButtons.at( i )->setState( checked ); + } + + Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons( m_formButtons ); + moveViewportIfBoundingRectNotFullyVisible( boundingRect, m_docPriv, m_pageNumber ); + emit m_docPriv->m_parent->formButtonsChangedByUndoRedo( m_pageNumber, m_formButtons ); +} + +void EditFormButtonsCommand::redo() +{ + clearFormButtonStates(); + for( int i = 0; i < m_formButtons.size(); i++ ) + { + bool checked = m_newButtonStates.at( i ); + if ( checked ) + m_formButtons.at( i )->setState( checked ); + } + + Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons( m_formButtons ); + moveViewportIfBoundingRectNotFullyVisible( boundingRect, m_docPriv, m_pageNumber ); + emit m_docPriv->m_parent->formButtonsChangedByUndoRedo( m_pageNumber, m_formButtons ); +} + +void EditFormButtonsCommand::clearFormButtonStates() +{ + foreach( FormFieldButton* formButton, m_formButtons ) + { + formButton->setState( false ); + } +} + +} + diff --git a/okular/core/documentcommands_p.h b/okular/core/documentcommands_p.h new file mode 100644 index 00000000..17394f2a --- /dev/null +++ b/okular/core/documentcommands_p.h @@ -0,0 +1,259 @@ +/*************************************************************************** + * Copyright (C) 2013 Jon Mease * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_DOCUMENT_COMMANDS_P_H_ +#define _OKULAR_DOCUMENT_COMMANDS_P_H_ + +#include +#include + +#include "area.h" + +namespace Okular { + +class Document; +class Annotation; +class DocumentPrivate; +class FormFieldText; +class FormFieldButton; +class FormFieldChoice; + +class AddAnnotationCommand : public QUndoCommand +{ + public: + AddAnnotationCommand(Okular::DocumentPrivate * docPriv, Okular::Annotation* annotation, int pageNumber); + + virtual ~AddAnnotationCommand(); + + virtual void undo(); + + virtual void redo(); + + private: + Okular::DocumentPrivate * m_docPriv; + Okular::Annotation* m_annotation; + int m_pageNumber; + bool m_done; +}; + +class RemoveAnnotationCommand : public QUndoCommand +{ + public: + RemoveAnnotationCommand(Okular::DocumentPrivate * doc, Okular::Annotation* annotation, int pageNumber); + virtual ~RemoveAnnotationCommand(); + virtual void undo(); + virtual void redo(); + + private: + Okular::DocumentPrivate * m_docPriv; + Okular::Annotation* m_annotation; + int m_pageNumber; + bool m_done; +}; + +class ModifyAnnotationPropertiesCommand : public QUndoCommand +{ + public: + ModifyAnnotationPropertiesCommand( Okular::DocumentPrivate* docPriv, Okular::Annotation* annotation, + int pageNumber, + QDomNode oldProperties, + QDomNode newProperties ); + + virtual void undo(); + virtual void redo(); + + private: + Okular::DocumentPrivate * m_docPriv; + Okular::Annotation* m_annotation; + int m_pageNumber; + QDomNode m_prevProperties; + QDomNode m_newProperties; +}; + +class TranslateAnnotationCommand : public QUndoCommand +{ + public: + TranslateAnnotationCommand(Okular::DocumentPrivate* docPriv, + Okular::Annotation* annotation, + int pageNumber, + const Okular::NormalizedPoint & delta, + bool completeDrag + ); + virtual void undo(); + virtual void redo(); + virtual int id() const; + virtual bool mergeWith(const QUndoCommand *uc); + Okular::NormalizedPoint minusDelta(); + Okular::NormalizedRect translateBoundingRectangle( const Okular::NormalizedPoint & delta ); + + private: + Okular::DocumentPrivate * m_docPriv; + Okular::Annotation* m_annotation; + int m_pageNumber; + Okular::NormalizedPoint m_delta; + bool m_completeDrag; +}; + +class EditTextCommand : public QUndoCommand +{ + public: + EditTextCommand( const QString & newContents, + int newCursorPos, + const QString & prevContents, + int prevCursorPos, + int prevAnchorPos + ); + + virtual void undo() = 0; + virtual void redo() = 0; + virtual int id() const = 0; + virtual bool mergeWith(const QUndoCommand *uc); + + private: + enum EditType { + CharBackspace, ///< Edit made up of one or more single character backspace operations + CharDelete, ///< Edit made up of one or more single character delete operations + CharInsert, ///< Edit made up of one or more single character insertion operations + OtherEdit ///< All other edit operations (these will not be merged together) + }; + + QString oldContentsLeftOfCursor(); + QString newContentsLeftOfCursor(); + QString oldContentsRightOfCursor(); + QString newContentsRightOfCursor(); + + protected: + QString m_newContents; + int m_newCursorPos; + QString m_prevContents; + int m_prevCursorPos; + int m_prevAnchorPos; + EditType m_editType; +}; + + +class EditAnnotationContentsCommand : public EditTextCommand +{ + public: + EditAnnotationContentsCommand(Okular::DocumentPrivate* docPriv, + Okular::Annotation* annotation, + int pageNumber, + const QString & newContents, + int newCursorPos, + const QString & prevContents, + int prevCursorPos, + int prevAnchorPos + ); + + virtual void undo(); + virtual void redo(); + virtual int id() const; + virtual bool mergeWith(const QUndoCommand *uc); + + private: + Okular::DocumentPrivate * m_docPriv; + Okular::Annotation* m_annotation; + int m_pageNumber; +}; + +class EditFormTextCommand : public EditTextCommand +{ + public: + EditFormTextCommand( Okular::DocumentPrivate* docPriv, + Okular::FormFieldText* form, + int pageNumber, + const QString & newContents, + int newCursorPos, + const QString & prevContents, + int prevCursorPos, + int prevAnchorPos ); + virtual void undo(); + virtual void redo(); + virtual int id() const; + virtual bool mergeWith( const QUndoCommand *uc ); + private: + Okular::DocumentPrivate* m_docPriv; + Okular::FormFieldText* m_form; + int m_pageNumber; +}; + +class EditFormListCommand : public QUndoCommand +{ + public: + EditFormListCommand( Okular::DocumentPrivate* docPriv, + FormFieldChoice* form, + int pageNumber, + const QList< int > & newChoices, + const QList< int > & prevChoices + ); + + virtual void undo(); + virtual void redo(); + + private: + Okular::DocumentPrivate* m_docPriv; + FormFieldChoice* m_form; + int m_pageNumber; + QList< int > m_newChoices; + QList< int > m_prevChoices; +}; + +class EditFormComboCommand : public EditTextCommand +{ + public: + EditFormComboCommand( Okular::DocumentPrivate* docPriv, + FormFieldChoice* form, + int pageNumber, + const QString & newText, + int newCursorPos, + const QString & prevText, + int prevCursorPos, + int prevAnchorPos + ); + + virtual void undo(); + virtual void redo(); + virtual int id() const; + virtual bool mergeWith( const QUndoCommand *uc ); + + private: + Okular::DocumentPrivate* m_docPriv; + FormFieldChoice* m_form; + int m_pageNumber; + int m_newIndex; + int m_prevIndex; +}; + +class EditFormButtonsCommand : public QUndoCommand +{ + public: + EditFormButtonsCommand( Okular::DocumentPrivate* docPriv, + int pageNumber, + const QList< FormFieldButton* > & formButtons, + const QList< bool > & newButtonStates + ); + + virtual void undo(); + virtual void redo(); + + private: + void clearFormButtonStates(); + + private: + Okular::DocumentPrivate* m_docPriv; + int m_pageNumber; + QList< FormFieldButton* > m_formButtons; + QList< bool > m_newButtonStates; + QList< bool > m_prevButtonStates; +}; + +} +#endif + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/okular/core/fileprinter.cpp b/okular/core/fileprinter.cpp new file mode 100644 index 00000000..55977a1b --- /dev/null +++ b/okular/core/fileprinter.cpp @@ -0,0 +1,678 @@ +/*************************************************************************** + * Copyright (C) 2007,2010 by John Layt * + * * + * FilePrinterPreview based on KPrintPreview (originally LGPL) * + * Copyright (c) 2007 Alex Merry * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "fileprinter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "debug_p.h" + +using namespace Okular; + +int FilePrinter::printFile( QPrinter &printer, const QString file, + QPrinter::Orientation documentOrientation, FileDeletePolicy fileDeletePolicy, + PageSelectPolicy pageSelectPolicy, const QString &pageRange ) +{ + FilePrinter fp; + return fp.doPrintFiles( printer, QStringList( file ), fileDeletePolicy, pageSelectPolicy, pageRange, + documentOrientation ); +} + +int FilePrinter::doPrintFiles( QPrinter &printer, QStringList fileList, FileDeletePolicy fileDeletePolicy, + PageSelectPolicy pageSelectPolicy, const QString &pageRange, + QPrinter::Orientation documentOrientation ) +{ + + if ( fileList.size() < 1 ) { + return -8; + } + + for (QStringList::ConstIterator it = fileList.constBegin(); it != fileList.constEnd(); ++it) { + if (!QFile::exists(*it)) { + return -7; + } + } + + if ( printer.printerState() == QPrinter::Aborted || printer.printerState() == QPrinter::Error ) { + return -6; + } + + QString exe; + QStringList argList; + int ret; + + // Print to File if a filename set, assumes there must be only 1 file + if ( !printer.outputFileName().isEmpty() ) { + + if ( QFile::exists( printer.outputFileName() ) ) { + QFile::remove( printer.outputFileName() ); + } + + QFileInfo inputFileInfo = QFileInfo( fileList[0] ); + QFileInfo outputFileInfo = QFileInfo( printer.outputFileName() ); + + bool doDeleteFile = (fileDeletePolicy == FilePrinter::SystemDeletesFiles); + if ( inputFileInfo.suffix() == outputFileInfo.suffix() ) { + if ( doDeleteFile ) { + bool res = QFile::rename( fileList[0], printer.outputFileName() ); + if ( res ) { + doDeleteFile = false; + ret = 0; + } else { + ret = -5; + } + } else { + bool res = QFile::copy( fileList[0], printer.outputFileName() ); + if ( res ) { + ret = 0; + } else { + ret = -5; + } + } + } else if ( inputFileInfo.suffix() == "ps" && printer.outputFormat() == QPrinter::PdfFormat && ps2pdfAvailable() ) { + exe = "ps2pdf"; + argList << fileList[0] << printer.outputFileName(); + kDebug(OkularDebug) << "Executing" << exe << "with arguments" << argList; + ret = KProcess::execute( exe, argList ); + } else if ( inputFileInfo.suffix() == "pdf" && printer.outputFormat() == QPrinter::PostScriptFormat && pdf2psAvailable() ) { + exe = "pdf2ps"; + argList << fileList[0] << printer.outputFileName(); + kDebug(OkularDebug) << "Executing" << exe << "with arguments" << argList; + ret = KProcess::execute( exe, argList ); + } else { + ret = -5; + } + + if ( doDeleteFile ) { + QFile::remove( fileList[0] ); + } + + + } else { // Print to a printer via lpr command + + //Decide what executable to use to print with, need the CUPS version of lpr if available + //Some distros name the CUPS version of lpr as lpr-cups or lpr.cups so try those first + //before default to lpr, or failing that to lp + + if ( !KStandardDirs::findExe("lpr-cups").isEmpty() ) { + exe = "lpr-cups"; + } else if ( !KStandardDirs::findExe("lpr.cups").isEmpty() ) { + exe = "lpr.cups"; + } else if ( !KStandardDirs::findExe("lpr").isEmpty() ) { + exe = "lpr"; + } else if ( !KStandardDirs::findExe("lp").isEmpty() ) { + exe = "lp"; + } else { + return -9; + } + + bool useCupsOptions = cupsAvailable(); + argList = printArguments( printer, fileDeletePolicy, pageSelectPolicy, + useCupsOptions, pageRange, exe, documentOrientation ) << fileList; + kDebug(OkularDebug) << "Executing" << exe << "with arguments" << argList; + + ret = KProcess::execute( exe, argList ); + + } + + return ret; +} + +QList FilePrinter::pageList( QPrinter &printer, int lastPage, const QList &selectedPageList ) +{ + return pageList( printer, lastPage, 0, selectedPageList ); +} + +QList FilePrinter::pageList( QPrinter &printer, int lastPage, + int currentPage, const QList &selectedPageList ) +{ + if ( printer.printRange() == QPrinter::Selection) { + return selectedPageList; + } + + int startPage, endPage; + QList list; + + if ( printer.printRange() == QPrinter::PageRange ) { + startPage = printer.fromPage(); + endPage = printer.toPage(); +#if QT_VERSION >= KDE_MAKE_VERSION(4,7,0) + } else if ( printer.printRange() == QPrinter::CurrentPage) { + startPage = currentPage; + endPage = currentPage; +#endif + } else { //AllPages + startPage = 1; + endPage = lastPage; + } + + for (int i = startPage; i <= endPage; i++ ) { + list << i; + } + + return list; +} + +QString FilePrinter::pageRange( QPrinter &printer, int lastPage, const QList &selectedPageList ) +{ + if ( printer.printRange() == QPrinter::Selection) { + return pageListToPageRange( selectedPageList ); + } + + if ( printer.printRange() == QPrinter::PageRange ) { + return QString("%1-%2").arg(printer.fromPage()).arg(printer.toPage()); + } + + return QString("1-%2").arg( lastPage ); +} + +QString FilePrinter::pageListToPageRange( const QList &pageList ) +{ + QString pageRange; + int count = pageList.count(); + int i = 0; + int seqStart = i; + int seqEnd; + + while ( i != count ) { + + if ( i + 1 == count || pageList[i] + 1 != pageList[i+1] ) { + + seqEnd = i; + + if ( !pageRange.isEmpty() ) { + pageRange.append(","); + } + + if ( seqStart == seqEnd ) { + pageRange.append(pageList[i]); + } else { + pageRange.append(QString("%1-%2").arg(seqStart).arg(seqEnd)); + } + + seqStart = i + 1; + } + + i++; + } + + return pageRange; +} + +bool FilePrinter::ps2pdfAvailable() +{ + return ( !KStandardDirs::findExe("ps2pdf").isEmpty() ); +} + +bool FilePrinter::pdf2psAvailable() +{ + return ( !KStandardDirs::findExe("pdf2ps").isEmpty() ); +} + +bool FilePrinter::cupsAvailable() +{ +#ifdef Q_WS_X11 + // Ideally we would have access to the private Qt method + // QCUPSSupport::cupsAvailable() to do this as it is very complex routine. + // However, if CUPS is available then QPrinter::numCopies() will always return 1 + // whereas if CUPS is not available it will return the real number of copies. + // This behaviour is guaranteed never to change, so we can use it as a reliable substitute. + QPrinter testPrinter; + testPrinter.setNumCopies( 2 ); + return ( testPrinter.numCopies() == 1 ); +#else + return false; +#endif +} + +bool FilePrinter::detectCupsService() +{ + QTcpSocket qsock; + qsock.connectToHost("localhost", 631); + bool rtn = qsock.waitForConnected() && qsock.isValid(); + qsock.abort(); + return rtn; +} + +bool FilePrinter::detectCupsConfig() +{ + if ( QFile::exists("/etc/cups/cupsd.conf") ) return true; + if ( QFile::exists("/usr/etc/cups/cupsd.conf") ) return true; + if ( QFile::exists("/usr/local/etc/cups/cupsd.conf") ) return true; + if ( QFile::exists("/opt/etc/cups/cupsd.conf") ) return true; + if ( QFile::exists("/opt/local/etc/cups/cupsd.conf") ) return true; + return false; +} + +QSize FilePrinter::psPaperSize( QPrinter &printer ) +{ + QSize size; + + switch ( printer.pageSize() ) { + case QPrinter::A0: size = QSize( 2384, 3370 ); break; + case QPrinter::A1: size = QSize( 1684, 2384 ); break; + case QPrinter::A2: size = QSize( 1191, 1684 ); break; + case QPrinter::A3: size = QSize( 842, 1191 ); break; + case QPrinter::A4: size = QSize( 595, 842 ); break; + case QPrinter::A5: size = QSize( 420, 595 ); break; + case QPrinter::A6: size = QSize( 298, 420 ); break; + case QPrinter::A7: size = QSize( 210, 298 ); break; + case QPrinter::A8: size = QSize( 147, 210 ); break; + case QPrinter::A9: size = QSize( 105, 147 ); break; + case QPrinter::B0: size = QSize( 2835, 4008 ); break; + case QPrinter::B1: size = QSize( 2004, 2835 ); break; + case QPrinter::B2: size = QSize( 1417, 2004 ); break; + case QPrinter::B3: size = QSize( 1001, 1417 ); break; + case QPrinter::B4: size = QSize( 709, 1001 ); break; + case QPrinter::B5: size = QSize( 499, 709 ); break; + case QPrinter::B6: size = QSize( 354, 499 ); break; + case QPrinter::B7: size = QSize( 249, 354 ); break; + case QPrinter::B8: size = QSize( 176, 249 ); break; + case QPrinter::B9: size = QSize( 125, 176 ); break; + case QPrinter::B10: size = QSize( 88, 125 ); break; + case QPrinter::C5E: size = QSize( 459, 649 ); break; + case QPrinter::Comm10E: size = QSize( 297, 684 ); break; + case QPrinter::DLE: size = QSize( 312, 624 ); break; + case QPrinter::Executive: size = QSize( 522, 756 ); break; + case QPrinter::Folio: size = QSize( 595, 935 ); break; + case QPrinter::Ledger: size = QSize( 1224, 792 ); break; + case QPrinter::Legal: size = QSize( 612, 1008 ); break; + case QPrinter::Letter: size = QSize( 612, 792 ); break; + case QPrinter::Tabloid: size = QSize( 792, 1224 ); break; + case QPrinter::Custom: return QSize( (int) printer.widthMM() * ( 25.4 / 72 ), + (int) printer.heightMM() * ( 25.4 / 72 ) ); + default: return QSize(); + } + + if ( printer.orientation() == QPrinter::Landscape ) { + size.transpose(); + } + + return size; +} + +Generator::PrintError FilePrinter::printError( int c ) +{ + Generator::PrintError pe; + if ( c >= 0 ) + { + pe = Generator::NoPrintError; + } + else { + switch ( c ) + { + case -1: + pe = Generator::PrintingProcessCrashPrintError; + break; + case -2: + pe = Generator::PrintingProcessStartPrintError; + break; + case -5: + pe = Generator::PrintToFilePrintError; + break; + case -6: + pe = Generator::InvalidPrinterStatePrintError; + break; + case -7: + pe = Generator::UnableToFindFilePrintError; + break; + case -8: + pe = Generator::NoFileToPrintError; + break; + case -9: + pe = Generator::NoBinaryToPrintError; + break; + default: + pe = Generator::UnknownPrintError; + } + } + return pe; +} + + + +QStringList FilePrinter::printArguments( QPrinter &printer, FileDeletePolicy fileDeletePolicy, + PageSelectPolicy pageSelectPolicy, bool useCupsOptions, + const QString &pageRange, const QString &version, + QPrinter::Orientation documentOrientation ) +{ + QStringList argList; + + if ( ! destination( printer, version ).isEmpty() ) { + argList << destination( printer, version ); + } + + if ( ! copies( printer, version ).isEmpty() ) { + argList << copies( printer, version ); + } + + if ( ! jobname( printer, version ).isEmpty() ) { + argList << jobname( printer, version ); + } + + if ( ! pages( printer, pageSelectPolicy, pageRange, useCupsOptions, version ).isEmpty() ) { + argList << pages( printer, pageSelectPolicy, pageRange, useCupsOptions, version ); + } + + if ( useCupsOptions && ! cupsOptions( printer, documentOrientation ).isEmpty() ) { + argList << cupsOptions( printer, documentOrientation); + } + + if ( ! deleteFile( printer, fileDeletePolicy, version ).isEmpty() ) { + argList << deleteFile( printer, fileDeletePolicy, version ); + } + + if ( version == "lp" ) { + argList << "--"; + } + + return argList; +} + +QStringList FilePrinter::destination( QPrinter &printer, const QString &version ) +{ + if ( version == "lp" ) { + return QStringList("-d") << printer.printerName(); + } + + if ( version.startsWith( "lpr" ) ) { + return QStringList("-P") << printer.printerName(); + } + + return QStringList(); +} + +QStringList FilePrinter::copies( QPrinter &printer, const QString &version ) +{ + int cp = printer.actualNumCopies(); + + if ( version == "lp" ) { + return QStringList("-n") << QString("%1").arg( cp ); + } + + if ( version.startsWith( "lpr" ) ) { + return QStringList() << QString("-#%1").arg( cp ); + } + + return QStringList(); +} + +QStringList FilePrinter::jobname( QPrinter &printer, const QString &version ) +{ + if ( ! printer.docName().isEmpty() ) { + + if ( version == "lp" ) { + return QStringList("-t") << printer.docName(); + } + + if ( version.startsWith( "lpr" ) ) { + const QString shortenedDocName = QString::fromUtf8(printer.docName().toUtf8().left(255)); + return QStringList("-J") << shortenedDocName; + } + } + + return QStringList(); +} + +QStringList FilePrinter::deleteFile( QPrinter &, FileDeletePolicy fileDeletePolicy, const QString &version ) +{ + if ( fileDeletePolicy == FilePrinter::SystemDeletesFiles && version.startsWith( "lpr" ) ) { + return QStringList("-r"); + } + + return QStringList(); +} + +QStringList FilePrinter::pages( QPrinter &printer, PageSelectPolicy pageSelectPolicy, const QString &pageRange, + bool useCupsOptions, const QString &version ) +{ + if ( pageSelectPolicy == FilePrinter::SystemSelectsPages ) { + + if ( printer.printRange() == QPrinter::Selection && ! pageRange.isEmpty() ) { + + if ( version == "lp" ) { + return QStringList("-P") << pageRange ; + } + + if ( version.startsWith( "lpr" ) && useCupsOptions ) { + return QStringList("-o") << QString("page-ranges=%1").arg( pageRange ); + } + + } + + if ( printer.printRange() == QPrinter::PageRange ) { + + if ( version == "lp" ) { + return QStringList("-P") << QString("%1-%2").arg( printer.fromPage() ) + .arg( printer.toPage() ); + } + + if ( version.startsWith( "lpr" ) && useCupsOptions ) { + return QStringList("-o") << QString("page-ranges=%1-%2").arg( printer.fromPage() ) + .arg( printer.toPage() ); + } + + } + + } + + return QStringList(); // AllPages +} + +QStringList FilePrinter::cupsOptions( QPrinter &printer, QPrinter::Orientation documentOrientation ) +{ + QStringList optionList; + + if ( ! optionMedia( printer ).isEmpty() ) { + optionList << optionMedia( printer ); + } + + if ( ! optionOrientation( printer, documentOrientation ).isEmpty() ) { + optionList << optionOrientation( printer, documentOrientation ); + } + + if ( ! optionDoubleSidedPrinting( printer ).isEmpty() ) { + optionList << optionDoubleSidedPrinting( printer ); + } + + if ( ! optionPageOrder( printer ).isEmpty() ) { + optionList << optionPageOrder( printer ); + } + + if ( ! optionCollateCopies( printer ).isEmpty() ) { + optionList << optionCollateCopies( printer ); + } + + if ( ! optionPageMargins( printer ).isEmpty() ) { + optionList << optionPageMargins( printer ); + } + + optionList << optionCupsProperties( printer ); + + return optionList; +} + +QStringList FilePrinter::optionMedia( QPrinter &printer ) +{ + if ( ! mediaPageSize( printer ).isEmpty() && + ! mediaPaperSource( printer ).isEmpty() ) { + return QStringList("-o") << + QString("media=%1,%2").arg( mediaPageSize( printer ) ) + .arg( mediaPaperSource( printer ) ); + } + + if ( ! mediaPageSize( printer ).isEmpty() ) { + return QStringList("-o") << + QString("media=%1").arg( mediaPageSize( printer ) ); + } + + if ( ! mediaPaperSource( printer ).isEmpty() ) { + return QStringList("-o") << + QString("media=%1").arg( mediaPaperSource( printer ) ); + } + + return QStringList(); +} + +QString FilePrinter::mediaPageSize( QPrinter &printer ) +{ + switch ( printer.pageSize() ) { + case QPrinter::A0: return "A0"; + case QPrinter::A1: return "A1"; + case QPrinter::A2: return "A2"; + case QPrinter::A3: return "A3"; + case QPrinter::A4: return "A4"; + case QPrinter::A5: return "A5"; + case QPrinter::A6: return "A6"; + case QPrinter::A7: return "A7"; + case QPrinter::A8: return "A8"; + case QPrinter::A9: return "A9"; + case QPrinter::B0: return "B0"; + case QPrinter::B1: return "B1"; + case QPrinter::B10: return "B10"; + case QPrinter::B2: return "B2"; + case QPrinter::B3: return "B3"; + case QPrinter::B4: return "B4"; + case QPrinter::B5: return "B5"; + case QPrinter::B6: return "B6"; + case QPrinter::B7: return "B7"; + case QPrinter::B8: return "B8"; + case QPrinter::B9: return "B9"; + case QPrinter::C5E: return "C5"; //Correct Translation? + case QPrinter::Comm10E: return "Comm10"; //Correct Translation? + case QPrinter::DLE: return "DL"; //Correct Translation? + case QPrinter::Executive: return "Executive"; + case QPrinter::Folio: return "Folio"; + case QPrinter::Ledger: return "Ledger"; + case QPrinter::Legal: return "Legal"; + case QPrinter::Letter: return "Letter"; + case QPrinter::Tabloid: return "Tabloid"; + case QPrinter::Custom: return QString("Custom.%1x%2mm") + .arg( printer.heightMM() ) + .arg( printer.widthMM() ); + default: return QString(); + } +} + +// What about Upper and MultiPurpose? And others in PPD??? +QString FilePrinter::mediaPaperSource( QPrinter &printer ) +{ + switch ( printer.paperSource() ) { + case QPrinter::Auto: return QString(); + case QPrinter::Cassette: return "Cassette"; + case QPrinter::Envelope: return "Envelope"; + case QPrinter::EnvelopeManual: return "EnvelopeManual"; + case QPrinter::FormSource: return "FormSource"; + case QPrinter::LargeCapacity: return "LargeCapacity"; + case QPrinter::LargeFormat: return "LargeFormat"; + case QPrinter::Lower: return "Lower"; + case QPrinter::MaxPageSource: return "MaxPageSource"; + case QPrinter::Middle: return "Middle"; + case QPrinter::Manual: return "Manual"; + case QPrinter::OnlyOne: return "OnlyOne"; + case QPrinter::Tractor: return "Tractor"; + case QPrinter::SmallFormat: return "SmallFormat"; + default: return QString(); + } +} + +QStringList FilePrinter::optionOrientation( QPrinter &printer, QPrinter::Orientation documentOrientation ) +{ + // portrait and landscape options rotate the document according to the document orientation + // If we want to print a landscape document as one would expect it, we have to pass the + // portrait option so that the document is not rotated additionally + if ( printer.orientation() == documentOrientation ) { + // the user wants the document printed as is + return QStringList("-o") << "portrait"; + } else { + // the user expects the document being rotated by 90 degrees + return QStringList("-o") << "landscape"; + } +} + +QStringList FilePrinter::optionDoubleSidedPrinting( QPrinter &printer ) +{ + switch ( printer.duplex() ) { + case QPrinter::DuplexNone: return QStringList("-o") << "sides=one-sided"; + case QPrinter::DuplexAuto: if ( printer.orientation() == QPrinter::Landscape ) { + return QStringList("-o") << "sides=two-sided-short-edge"; + } else { + return QStringList("-o") << "sides=two-sided-long-edge"; + } + case QPrinter::DuplexLongSide: return QStringList("-o") << "sides=two-sided-long-edge"; + case QPrinter::DuplexShortSide: return QStringList("-o") << "sides=two-sided-short-edge"; + default: return QStringList(); //Use printer default + } +} + +QStringList FilePrinter::optionPageOrder( QPrinter &printer ) +{ + if ( printer.pageOrder() == QPrinter::LastPageFirst ) { + return QStringList("-o") << "outputorder=reverse"; + } + return QStringList("-o") << "outputorder=normal"; +} + +QStringList FilePrinter::optionCollateCopies( QPrinter &printer ) +{ + if ( printer.collateCopies() ) { + return QStringList("-o") << "Collate=True"; + } + return QStringList("-o") << "Collate=False"; +} + +QStringList FilePrinter::optionPageMargins( QPrinter &printer ) +{ + if (printer.printEngine()->property(QPrintEngine::PPK_PageMargins).isNull()) { + return QStringList(); + } else { + qreal l, t, r, b; + printer.getPageMargins( &l, &t, &r, &b, QPrinter::Point ); + return QStringList("-o") << QString("page-left=%1").arg(l) + << "-o" << QString("page-top=%1").arg(t) + << "-o" << QString("page-right=%1").arg(r) + << "-o" << QString("page-bottom=%1").arg(b) << "-o" << "fit-to-page"; + } +} + +QStringList FilePrinter::optionCupsProperties( QPrinter &printer ) +{ + QStringList dialogOptions = printer.printEngine()->property(QPrintEngine::PrintEnginePropertyKey(0xfe00)).toStringList(); + QStringList cupsOptions; + + for ( int i = 0; i < dialogOptions.count(); i = i + 2 ) { + if ( dialogOptions[i+1].isEmpty() ) { + cupsOptions << "-o" << dialogOptions[i]; + } else { + cupsOptions << "-o" << dialogOptions[i] + '=' + dialogOptions[i+1]; + } + } + + return cupsOptions; +} + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/okular/core/fileprinter.h b/okular/core/fileprinter.h new file mode 100644 index 00000000..c7cd32ae --- /dev/null +++ b/okular/core/fileprinter.h @@ -0,0 +1,187 @@ +/*************************************************************************** + * Copyright (C) 2007, 2010 by John Layt * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +// This Class is a temporary addition to Okular for the duration of KDE 4.0. +// In KDE 4.1 this class will either be moved to kdelibs if still required, +// or replaced with a Qt 4.4 based solution. + +#ifndef FILEPRINTER_H +#define FILEPRINTER_H + +#include +#include +#include + +#include "okular_export.h" +#include "generator.h" + +class QSize; + +namespace Okular { + +class OKULAR_EXPORT FilePrinter +{ +public: + + /** Whether file(s) get deleted by the application or by the print system. + * + * You may need to chose system deletion if your temp file clean-up + * deletes the file before the print system is finished with it. + */ + enum FileDeletePolicy { ApplicationDeletesFiles, SystemDeletesFiles }; + + /** Whether pages to be printed are selected by the application or the print system. + * + * If application side, then the generated file will only contain those pages + * selected by the user, so FilePrinter will print all the pages in the file. + * + * If system side, then the file will contain all the pages in the document, and + * the print system will print the users selected print range from out of the file. + * + * Note system side only works in CUPS, not LPR. + */ + enum PageSelectPolicy { ApplicationSelectsPages, SystemSelectsPages }; + + /** Print a file using the settings in QPrinter + * + * Only supports CUPS and LPR on *NIX. Page Range only supported in CUPS. + * Most settings unsupported by LPR, some settings unsupported by CUPS. + * + * The documentOrientation parameter was added in version 0.14. + * + * @param printer the print settings to use + * @param file the file to print + * @param documentOrientation the orientation stored in the document itself + * @param fileDeletePolicy if the application or system deletes the file + * @param pageSelectPolicy if the application or system selects the pages to print + * @param pageRange page range to print if SystemSlectsPages and user chooses Selection in Print Dialog + * + * @returns Returns exit code: + * -9 if lpr not found + * -8 if empty file name + * -7 if unable to find file + * -6 if invalid printer state + * -5 if print to file copy failed + * -2 if the KProcess could not be started + * -1 if the KProcess crashed + * otherwise the KProcess exit code + * + * @since 0.14 (KDE 4.8) + */ + + static int printFile( QPrinter &printer, const QString file, + QPrinter::Orientation documentOrientation, + FileDeletePolicy fileDeletePolicy = FilePrinter::ApplicationDeletesFiles, + PageSelectPolicy pageSelectPolicy = FilePrinter::ApplicationSelectsPages, + const QString &pageRange = QString() ); + + /** Return the list of pages selected by the user in the Print Dialog + * + * @param printer the print settings to use + * @param lastPage the last page number, needed if AllPages option is selected + * @param currentPage the current page number, needed if CurrentPage option is selected + * @param selectedPageList list of pages to use if Selection option is selected + * @returns Returns list of pages to print + */ + static QList pageList( QPrinter &printer, int lastPage, + int currentPage, const QList &selectedPageList ); + + /** Return the list of pages selected by the user in the Print Dialog + * + * @param printer the print settings to use + * @param lastPage the last page number, needed if AllPages option is selected + * @param selectedPageList list of pages to use if Selection option is selected + * @returns Returns list of pages to print + */ + static QList pageList( QPrinter &printer, int lastPage, const QList &selectedPageList ); + + /** Return the range of pages selected by the user in the Print Dialog + * + * @param printer the print settings to use + * @param lastPage the last page number, needed if AllPages option is selected + * @param selectedPageList list of pages to use if Selection option is selected + * @returns Returns range of pages to print + */ + static QString pageRange( QPrinter &printer, int lastPage, const QList &selectedPageList ); + + /** convert a Page List into a Page Range + * + * @param pageList list of pages to convert + * @returns Returns equivalent page range + */ + static QString pageListToPageRange( const QList &pageList ); + + /** Return if Ghostscript ps2pdf is available on this system + * + * @returns Returns true if Ghostscript ps2pdf available + */ + static bool ps2pdfAvailable(); + + /** Return if Ghostscript pdf2ps is available on this system + * + * @returns Returns true if Ghostscript pdf2ps available + */ + static bool pdf2psAvailable(); + + /** Return if CUPS Print System is available on this system + * + * @returns Returns true if CUPS available + */ + static bool cupsAvailable(); + + /** Returns the postscript standard page size + * + * @returns Returns paper size in ps points + */ + static QSize psPaperSize( QPrinter &printer ); + + /** + * Convert the code returned by printFile* to PrintError + * @since 0.11 (KDE 4.5) + */ + static Generator::PrintError printError( int c ); + +protected: + + bool detectCupsService(); + bool detectCupsConfig(); + + int doPrintFiles( QPrinter &printer, const QStringList fileList, + FileDeletePolicy fileDeletePolicy, PageSelectPolicy pageSelectPolicy, + const QString &pageRange, + QPrinter::Orientation documentOrientation ); + + QStringList printArguments( QPrinter &printer, + FileDeletePolicy fileDeletePolicy, PageSelectPolicy pageSelectPolicy, + bool useCupsOptions, const QString &pageRange, const QString &version, + QPrinter::Orientation documentOrientation ); + + QStringList destination( QPrinter &printer, const QString &version ); + QStringList copies( QPrinter &printer, const QString &version ); + QStringList jobname( QPrinter &printer, const QString &version ); + QStringList deleteFile( QPrinter &printer, FileDeletePolicy fileDeletePolicy, + const QString &version ); + QStringList pages( QPrinter &printer, PageSelectPolicy pageSelectPolicy, + const QString &pageRange, bool useCupsOptions, const QString &version ); + + QStringList cupsOptions( QPrinter &printer, QPrinter::Orientation documentOrientation ); + QStringList optionMedia( QPrinter &printer ); + QString mediaPageSize( QPrinter &printer ); + QString mediaPaperSource( QPrinter &printer ); + QStringList optionOrientation( QPrinter &printer, QPrinter::Orientation documentOrientation ); + QStringList optionDoubleSidedPrinting( QPrinter &printer ); + QStringList optionPageOrder( QPrinter &printer ); + QStringList optionCollateCopies( QPrinter &printer ); + QStringList optionPageMargins( QPrinter &printer ); + QStringList optionCupsProperties( QPrinter &printer ); +}; + +} + +#endif // FILEPRINTER_H diff --git a/okular/core/fontinfo.cpp b/okular/core/fontinfo.cpp new file mode 100644 index 00000000..59ea79f7 --- /dev/null +++ b/okular/core/fontinfo.cpp @@ -0,0 +1,138 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +// local includes +#include "fontinfo.h" + +#include + +using namespace Okular; + +class Okular::FontInfoPrivate + : public QSharedData +{ + public: + FontInfoPrivate() + : type( FontInfo::Unknown ), embedType( FontInfo::NotEmbedded ), + canBeExtracted( false ) + { + } + + bool operator==( const FontInfoPrivate &rhs ) const + { + return name == rhs.name && + type == rhs.type && + embedType == rhs.embedType && + file == rhs.file && + canBeExtracted == rhs.canBeExtracted && + nativeId == rhs.nativeId; + } + + QString name; + FontInfo::FontType type; + FontInfo::EmbedType embedType; + bool canBeExtracted; + QString file; + QVariant nativeId; +}; + + +FontInfo::FontInfo() + : d( new FontInfoPrivate ) +{ +} + +FontInfo::FontInfo( const FontInfo &fi ) + : d( fi.d ) +{ +} + +FontInfo::~FontInfo() +{ +} + +QString FontInfo::name() const +{ + return d->name; +} + +void FontInfo::setName( const QString& name ) +{ + d->name = name; +} + +FontInfo::FontType FontInfo::type() const +{ + return d->type; +} + +void FontInfo::setType( FontInfo::FontType type ) +{ + d->type = type; +} + +FontInfo::EmbedType FontInfo::embedType() const +{ + return d->embedType; +} + +void FontInfo::setEmbedType( FontInfo::EmbedType type ) +{ + d->embedType = type; +} + +QString FontInfo::file() const +{ + return d->file; +} + +void FontInfo::setFile( const QString& file ) +{ + d->file = file; +} + +bool FontInfo::canBeExtracted() const +{ + return d->canBeExtracted; +} + +void FontInfo::setCanBeExtracted(bool extractable) +{ + d->canBeExtracted = extractable; +} + +void FontInfo::setNativeId( const QVariant &id ) +{ + d->nativeId = id; +} + +QVariant FontInfo::nativeId() const +{ + return d->nativeId; +} + +bool FontInfo::operator==( const FontInfo &fi ) const +{ + return *d == *fi.d; +} + +bool FontInfo::operator!=( const FontInfo &fi ) const +{ + return !operator==( fi ); +} + +FontInfo& FontInfo::operator=( const FontInfo &fi ) +{ + if ( this == &fi ) + return *this; + + d = fi.d; + return *this; +} + diff --git a/okular/core/fontinfo.h b/okular/core/fontinfo.h new file mode 100644 index 00000000..d11b3b0a --- /dev/null +++ b/okular/core/fontinfo.h @@ -0,0 +1,162 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_FONTINFO_H_ +#define _OKULAR_FONTINFO_H_ + +#include +#include +#include +#include + +#include "okular_export.h" + +namespace Okular { + +class FontInfoPrivate; + +/** + * @short A small class that represents the information of a font. + */ +class OKULAR_EXPORT FontInfo +{ + public: + typedef QList List; + + /** + * The possible kinds of fonts. + */ + enum FontType + { + Unknown, + Type1, + Type1C, + Type1COT, + Type3, + TrueType, + TrueTypeOT, + CIDType0, + CIDType0C, + CIDType0COT, + CIDTrueType, + CIDTrueTypeOT, + TeXPK, ///< @since 0.10 (KDE 4.4) + TeXVirtual, ///< @since 0.10 (KDE 4.4) + TeXFontMetric, ///< @since 0.10 (KDE 4.4) + TeXFreeTypeHandled ///< @since 0.10 (KDE 4.4) + }; + + /** + * The possible kinds of embed. + */ + enum EmbedType + { + NotEmbedded, + EmbeddedSubset, + FullyEmbedded + }; + + /** + * Construct a new empty font info. + */ + FontInfo(); + /** + * Copy constructor. + */ + FontInfo( const FontInfo &fi ); + /** + * Destructor. + */ + ~FontInfo(); + + /** + * Returns the name of the font. + */ + QString name() const; + /** + * Sets a new name for the font. + */ + void setName( const QString& name ); + + /** + * Returns the type of the font. + */ + FontType type() const; + /** + * Change the type of the font. + */ + void setType( FontType type ); + + /** + * Returns the type of font embedding. + */ + EmbedType embedType() const; + /** + * Sets the type of font embedding. + */ + void setEmbedType( EmbedType type ); + + /** + * In case of not embedded font, returns the path of the font that + * represents this font. + */ + QString file() const; + void setFile( const QString& file ); + + /** + * In case of embedded fonts, returns if the font can be extracted into a QByteArray + * + * @since 0.8 (KDE 4.2) + */ + bool canBeExtracted() const; + + /** + * Sets if a font can be extracted or not. False by default + */ + void setCanBeExtracted( bool extractable ); + + /** + * Sets the "native" @p id of the font info. + * + * This is for use of the Generator, that can optionally store an + * handle (a pointer, an identifier, etc) of the "native" font + * object, if any. + * + * @since 0.8 (KDE 4.2) + */ + void setNativeId( const QVariant &id ); + + /** + * Returns the "native" id of the font info. + * + * @since 0.8 (KDE 4.2) + */ + QVariant nativeId() const; + + FontInfo& operator=( const FontInfo &fi ); + + /** + * Comparison operator. + */ + bool operator==( const FontInfo &fi ) const; + + bool operator!=( const FontInfo &fi ) const; + + private: + /// @cond PRIVATE + friend class FontInfoPrivate; + /// @endcond + QSharedDataPointer d; +}; + +} + +Q_DECLARE_METATYPE(Okular::FontInfo) + +#endif diff --git a/okular/core/form.cpp b/okular/core/form.cpp new file mode 100644 index 00000000..ca8a1326 --- /dev/null +++ b/okular/core/form.cpp @@ -0,0 +1,260 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "form.h" +#include "form_p.h" + +// qt includes +#include + +#include "action.h" + +using namespace Okular; + +FormFieldPrivate::FormFieldPrivate( FormField::FieldType type ) + : m_type( type ), m_activateAction( 0 ) +{ +} + +FormFieldPrivate::~FormFieldPrivate() +{ + delete m_activateAction; +} + +void FormFieldPrivate::setDefault() +{ + m_default = value(); +} + + +FormField::FormField( FormFieldPrivate &dd ) + : d_ptr( &dd ) +{ + d_ptr->q_ptr = this; +} + +FormField::~FormField() +{ + delete d_ptr; +} + +FormField::FieldType FormField::type() const +{ + Q_D( const FormField ); + return d->m_type; +} + +bool FormField::isReadOnly() const +{ + return false; +} + +bool FormField::isVisible() const +{ + return true; +} + +Action* FormField::activationAction() const +{ + Q_D( const FormField ); + return d->m_activateAction; +} + +void FormField::setActivationAction( Action *action ) +{ + Q_D( FormField ); + delete d->m_activateAction; + d->m_activateAction = action; +} + + +class Okular::FormFieldButtonPrivate : public Okular::FormFieldPrivate +{ + public: + FormFieldButtonPrivate() + : FormFieldPrivate( FormField::FormButton ) + { + } + + Q_DECLARE_PUBLIC( FormFieldButton ) + + void setValue( const QString& v ) + { + Q_Q( FormFieldButton ); + q->setState( QVariant( v ).toBool() ); + } + + QString value() const + { + Q_Q( const FormFieldButton ); + return qVariantFromValue( q->state() ).toString(); + } +}; + + +FormFieldButton::FormFieldButton() + : FormField( *new FormFieldButtonPrivate() ) +{ +} + +FormFieldButton::~FormFieldButton() +{ +} + +void FormFieldButton::setState( bool ) +{ +} + + +class Okular::FormFieldTextPrivate : public Okular::FormFieldPrivate +{ + public: + FormFieldTextPrivate() + : FormFieldPrivate( FormField::FormText ) + { + } + + Q_DECLARE_PUBLIC( FormFieldText ) + + void setValue( const QString& v ) + { + Q_Q( FormFieldText ); + q->setText( v ); + } + + QString value() const + { + Q_Q( const FormFieldText ); + return q->text(); + } +}; + + +FormFieldText::FormFieldText() + : FormField( *new FormFieldTextPrivate() ) +{ +} + +FormFieldText::~FormFieldText() +{ +} + +void FormFieldText::setText( const QString& ) +{ +} + +bool FormFieldText::isPassword() const +{ + return false; +} + +bool FormFieldText::isRichText() const +{ + return false; +} + +int FormFieldText::maximumLength() const +{ + return -1; +} + +Qt::Alignment FormFieldText::textAlignment() const +{ + return Qt::AlignVCenter | Qt::AlignLeft; +} + +bool FormFieldText::canBeSpellChecked() const +{ + return false; +} + + +class Okular::FormFieldChoicePrivate : public Okular::FormFieldPrivate +{ + public: + FormFieldChoicePrivate() + : FormFieldPrivate( FormField::FormChoice ) + { + } + + Q_DECLARE_PUBLIC( FormFieldChoice ) + + void setValue( const QString& v ) + { + Q_Q( FormFieldChoice ); + QStringList choices = v.split( ';', QString::SkipEmptyParts ); + QList newchoices; + foreach ( const QString& str, choices ) + { + bool ok = true; + int val = str.toInt( &ok ); + if ( ok ) + newchoices.append( val ); + } + if ( !newchoices.isEmpty() ) + q->setCurrentChoices( newchoices ); + } + + QString value() const + { + Q_Q( const FormFieldChoice ); + QList choices = q->currentChoices(); + qSort( choices ); + QStringList list; + foreach ( int c, choices ) + { + list.append( QString::number( c ) ); + } + return list.join( QLatin1String( ";" ) ); + } +}; + + +FormFieldChoice::FormFieldChoice() + : FormField( *new FormFieldChoicePrivate() ) +{ +} + +FormFieldChoice::~FormFieldChoice() +{ +} + +bool FormFieldChoice::isEditable() const +{ + return false; +} + +bool FormFieldChoice::multiSelect() const +{ + return false; +} + +void FormFieldChoice::setCurrentChoices( const QList< int >& ) +{ +} + +QString FormFieldChoice::editChoice() const +{ + return QString(); +} + +void FormFieldChoice::setEditChoice( const QString& ) +{ +} + +Qt::Alignment FormFieldChoice::textAlignment() const +{ + return Qt::AlignVCenter | Qt::AlignLeft; +} + +bool FormFieldChoice::canBeSpellChecked() const +{ + return false; +} + diff --git a/okular/core/form.h b/okular/core/form.h new file mode 100644 index 00000000..901c6b72 --- /dev/null +++ b/okular/core/form.h @@ -0,0 +1,347 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_FORM_H_ +#define _OKULAR_FORM_H_ + +#include "okular_export.h" +#include "area.h" + +#include + +namespace Okular { + +class Action; +class Page; +class PagePrivate; +class FormFieldPrivate; +class FormFieldButtonPrivate; +class FormFieldTextPrivate; +class FormFieldChoicePrivate; + +/** + * @short The base interface of a form field. + * + * This is the very basic interface to represent a field in a form. + * + * This is not meant to be used as a direct base for the form fields in a + * document, but its abstract subclasses are. + */ +class OKULAR_EXPORT FormField +{ + /// @cond PRIVATE + friend class Page; + friend class PagePrivate; + /// @endcond + + public: + /** + * The types of form field. + */ + enum FieldType + { + FormButton, ///< A "button". See @ref FormFieldButton::ButtonType. + FormText, ///< A field of variable text. See @ref FormFieldText::TextType. + FormChoice, ///< A choice field. See @ref FormFieldChoice::ChoiceType. + FormSignature ///< A signature. + }; + + virtual ~FormField(); + + /** + * The type of the field. + */ + FieldType type() const; + + /** + * The bouding rect of the field, in normalized coordinates. + */ + virtual NormalizedRect rect() const = 0; + + /** + * The ID of the field. + */ + virtual int id() const = 0; + + /** + * The internal name of the field, to be used when referring to the + * field in eg scripts. + */ + virtual QString name() const = 0; + + /** + * The visible name of the field, to be used in the user interface + * (eg in error messages, etc). + */ + virtual QString uiName() const = 0; + + /** + * Whether the field is read-only. + */ + virtual bool isReadOnly() const; + + /** + * Whether this form field is visible. + */ + virtual bool isVisible() const; + + Action* activationAction() const; + + protected: + /// @cond PRIVATE + FormField( FormFieldPrivate &dd ); + Q_DECLARE_PRIVATE( FormField ) + FormFieldPrivate *d_ptr; + /// @endcond + + void setActivationAction( Action *action ); + + private: + Q_DISABLE_COPY( FormField ) +}; + + +/** + * @short Interface of a button form field. + * + * This is the base interface to reimplement to represent a button field, like + * a push button, a check box or a radio button. + * + * @since 0.7 (KDE 4.1) + */ +class OKULAR_EXPORT FormFieldButton : public FormField +{ + public: + /** + * The types of button field. + */ + enum ButtonType + { + Push, ///< A simple push button. + CheckBox, ///< A check box. + Radio ///< A radio button. + }; + + virtual ~FormFieldButton(); + + /** + The particular type of the button field. + */ + virtual ButtonType buttonType() const = 0; + + /** + * The caption to be used for the button. + */ + virtual QString caption() const = 0; + + /** + * The state of the button. + */ + virtual bool state() const = 0; + + /** + * Sets the state of the button to the new \p state . + */ + virtual void setState( bool state ); + + /** + * The list with the IDs of siblings (ie, buttons belonging to the same + * group as the current one. + * + * Valid only for \ref Radio buttons, an empty list otherwise. + */ + virtual QList< int > siblings() const = 0; + + protected: + FormFieldButton(); + + private: + Q_DECLARE_PRIVATE( FormFieldButton ) + Q_DISABLE_COPY( FormFieldButton ) +}; + + +/** + * @short Interface of a text form field. + * + * This is the base interface to reimplement to represent a text field, ie a + * field where the user insert text. + */ +class OKULAR_EXPORT FormFieldText : public FormField +{ + public: + /** + * The types of text field. + */ + enum TextType + { + Normal, ///< A simple singleline text field. + Multiline, ///< A multiline text field. + FileSelect ///< An input field to select the path of a file on disk. + }; + + virtual ~FormFieldText(); + + /** + * The particular type of the text field. + */ + virtual TextType textType() const = 0; + + /** + * The text of text field. + */ + virtual QString text() const = 0; + + /** + * Sets the new @p text in the text field. + * + * The default implementation does nothing. + * + * Reimplemented only if the setting of new text is supported. + */ + virtual void setText( const QString& text ); + + /** + * Whether this text field is a password input, eg its text @b must be + * replaced with asterisks. + * + * Always false for @ref FileSelect text fields. + */ + virtual bool isPassword() const; + + /** + * Whether this text field should allow rich text. + */ + virtual bool isRichText() const; + + /** + * The maximum length allowed for the text of text field, or -1 if + * there is no limitation for the text. + */ + virtual int maximumLength() const; + + /** + * The alignment of the text within the field. + */ + virtual Qt::Alignment textAlignment() const; + + /** + * Whether the text inserted manually in the field (where possible) + * can be spell-checked. + * + * @note meaningful only if the field is editable. + */ + virtual bool canBeSpellChecked() const; + + protected: + FormFieldText(); + + private: + Q_DECLARE_PRIVATE( FormFieldText ) + Q_DISABLE_COPY( FormFieldText ) +}; + + +/** + * @short Interface of a choice form field. + * + * This is the base interface to reimplement to represent a choice field, ie a + * field where the user can select one (of more) element(s) among a set of + * choices. + */ +class OKULAR_EXPORT FormFieldChoice : public FormField +{ + public: + /** + * The types of choice field. + */ + enum ChoiceType + { + ComboBox, ///< A combo box choice field. + ListBox ///< A list box choice field. + }; + + virtual ~FormFieldChoice(); + + /** + * The particular type of the choice field. + */ + virtual ChoiceType choiceType() const = 0; + + /** + * The possible choices of the choice field. + */ + virtual QStringList choices() const = 0; + + /** + * Whether this ComboBox is editable, ie the user can type in a custom + * value. + * + * Always false for the other types of choices. + */ + virtual bool isEditable() const; + + /** + * Whether more than one choice of this ListBox can be selected at the + * same time. + * + * Always false for the other types of choices. + */ + virtual bool multiSelect() const; + + /** + * The currently selected choices. + * + * Always one element in the list in case of single choice elements. + */ + virtual QList< int > currentChoices() const = 0; + + /** + * Sets the selected choices to @p choices . + */ + virtual void setCurrentChoices( const QList< int >& choices ); + + /** + The text entered into an editable combo box choice field + + @since 0.16 (KDE 4.10) + */ + virtual QString editChoice() const; + + /** + Sets the text entered into an editable combo box choice field + + @since 0.16 (KDE 4.10) + */ + virtual void setEditChoice( const QString& text ); + + /** + * The alignment of the text within the field. + */ + virtual Qt::Alignment textAlignment() const; + + /** + * Whether the text inserted manually in the field (where possible) + * can be spell-checked. + * + * @note meaningful only if the field is editable. + */ + virtual bool canBeSpellChecked() const; + + protected: + FormFieldChoice(); + + private: + Q_DECLARE_PRIVATE( FormFieldChoice ) + Q_DISABLE_COPY( FormFieldChoice ) +}; + +} + +#endif diff --git a/okular/core/form_p.h b/okular/core/form_p.h new file mode 100644 index 00000000..33d778a1 --- /dev/null +++ b/okular/core/form_p.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_FORM_P_H +#define OKULAR_FORM_P_H + +#include "form.h" + +#include + +namespace Okular { + +class Action; +class FormField; + +class FormFieldPrivate +{ + public: + FormFieldPrivate( FormField::FieldType type ); + virtual ~FormFieldPrivate(); + + void setDefault(); + + virtual void setValue( const QString& ) = 0; + virtual QString value() const = 0; + + FormField::FieldType m_type; + QString m_default; + Action *m_activateAction; + + Q_DECLARE_PUBLIC( FormField ) + FormField *q_ptr; +}; + +} + +#endif diff --git a/okular/core/generator.cpp b/okular/core/generator.cpp new file mode 100644 index 00000000..ec1d1f76 --- /dev/null +++ b/okular/core/generator.cpp @@ -0,0 +1,672 @@ +/*************************************************************************** + * Copyright (C) 2005 by Piotr Szymanski * + * Copyright (C) 2008 by Albert Astals Cid * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "generator.h" +#include "generator_p.h" +#include "observer.h" + +#include +#include + +#include +#include +#include +#include + +#include "document.h" +#include "document_p.h" +#include "page.h" +#include "page_p.h" +#include "textpage.h" +#include "utils.h" + +using namespace Okular; + +GeneratorPrivate::GeneratorPrivate() + : m_document( 0 ), + mPixmapGenerationThread( 0 ), mTextPageGenerationThread( 0 ), + m_mutex( 0 ), m_threadsMutex( 0 ), mPixmapReady( true ), mTextPageReady( true ), + m_closing( false ), m_closingLoop( 0 ), + m_dpi(72.0, 72.0) +{ +} + +GeneratorPrivate::~GeneratorPrivate() +{ + if ( mPixmapGenerationThread ) + mPixmapGenerationThread->wait(); + + delete mPixmapGenerationThread; + + if ( mTextPageGenerationThread ) + mTextPageGenerationThread->wait(); + + delete mTextPageGenerationThread; + + delete m_mutex; + delete m_threadsMutex; +} + +PixmapGenerationThread* GeneratorPrivate::pixmapGenerationThread() +{ + if ( mPixmapGenerationThread ) + return mPixmapGenerationThread; + + Q_Q( Generator ); + mPixmapGenerationThread = new PixmapGenerationThread( q ); + QObject::connect( mPixmapGenerationThread, SIGNAL(finished()), + q, SLOT(pixmapGenerationFinished()), + Qt::QueuedConnection ); + + return mPixmapGenerationThread; +} + +TextPageGenerationThread* GeneratorPrivate::textPageGenerationThread() +{ + if ( mTextPageGenerationThread ) + return mTextPageGenerationThread; + + Q_Q( Generator ); + mTextPageGenerationThread = new TextPageGenerationThread( q ); + QObject::connect( mTextPageGenerationThread, SIGNAL(finished()), + q, SLOT(textpageGenerationFinished()), + Qt::QueuedConnection ); + + return mTextPageGenerationThread; +} + +void GeneratorPrivate::pixmapGenerationFinished() +{ + Q_Q( Generator ); + PixmapRequest *request = mPixmapGenerationThread->request(); + mPixmapGenerationThread->endGeneration(); + + QMutexLocker locker( threadsLock() ); + mPixmapReady = true; + + if ( m_closing ) + { + delete request; + if ( mTextPageReady ) + { + locker.unlock(); + m_closingLoop->quit(); + } + return; + } + + const QImage& img = mPixmapGenerationThread->image(); + request->page()->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( img ) ), request->normalizedRect() ); + const int pageNumber = request->page()->number(); + + if ( mPixmapGenerationThread->calcBoundingBox() ) + q->updatePageBoundingBox( pageNumber, mPixmapGenerationThread->boundingBox() ); + q->signalPixmapRequestDone( request ); +} + +void GeneratorPrivate::textpageGenerationFinished() +{ + Q_Q( Generator ); + Page *page = mTextPageGenerationThread->page(); + mTextPageGenerationThread->endGeneration(); + + QMutexLocker locker( threadsLock() ); + mTextPageReady = true; + + if ( m_closing ) + { + delete mTextPageGenerationThread->textPage(); + if ( mPixmapReady ) + { + locker.unlock(); + m_closingLoop->quit(); + } + return; + } + + if ( mTextPageGenerationThread->textPage() ) + { + TextPage *tp = mTextPageGenerationThread->textPage(); + page->setTextPage( tp ); + q->signalTextGenerationDone( page, tp ); + } +} + +QMutex* GeneratorPrivate::threadsLock() +{ + if ( !m_threadsMutex ) + m_threadsMutex = new QMutex(); + return m_threadsMutex; +} + +QVariant GeneratorPrivate::metaData( const QString &, const QVariant & ) const +{ + return QVariant(); +} + +QImage GeneratorPrivate::image( PixmapRequest * ) +{ + return QImage(); +} + + +Generator::Generator( QObject *parent, const QVariantList &args ) + : QObject( parent ), d_ptr( new GeneratorPrivate() ) +{ + d_ptr->q_ptr = this; + Q_UNUSED( args ) +} + +Generator::Generator( GeneratorPrivate &dd, QObject *parent, const QVariantList &args ) + : QObject( parent ), d_ptr( &dd ) +{ + d_ptr->q_ptr = this; + Q_UNUSED( args ) +} + +Generator::~Generator() +{ + delete d_ptr; +} + +bool Generator::loadDocument( const QString & fileName, QVector< Page * > & pagesVector ) +{ + return false; +} + +bool Generator::loadDocumentFromData( const QByteArray &, QVector< Page * > & ) +{ + return false; +} + +Document::OpenResult Generator::loadDocumentWithPassword( const QString & fileName, QVector< Page * > & pagesVector, const QString & ) +{ + return loadDocument( fileName, pagesVector ) ? Document::OpenSuccess : Document::OpenError; +} + +Document::OpenResult Generator::loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector< Page * > & pagesVector, const QString & ) +{ + return loadDocumentFromData( fileData, pagesVector ) ? Document::OpenSuccess : Document::OpenError; +} + +bool Generator::closeDocument() +{ + Q_D( Generator ); + + d->m_closing = true; + + d->threadsLock()->lock(); + if ( !( d->mPixmapReady && d->mTextPageReady ) ) + { + QEventLoop loop; + d->m_closingLoop = &loop; + + d->threadsLock()->unlock(); + + loop.exec(); + + d->m_closingLoop = 0; + } + else + { + d->threadsLock()->unlock(); + } + + bool ret = doCloseDocument(); + + d->m_closing = false; + + return ret; +} + +bool Generator::canGeneratePixmap() const +{ + Q_D( const Generator ); + return d->mPixmapReady; +} + +void Generator::generatePixmap( PixmapRequest *request ) +{ + Q_D( Generator ); + d->mPixmapReady = false; + + const bool calcBoundingBox = !request->isTile() && !request->page()->isBoundingBoxKnown(); + + if ( request->asynchronous() && hasFeature( Threaded ) ) + { + d->pixmapGenerationThread()->startGeneration( request, calcBoundingBox ); + + /** + * We create the text page for every page that is visible to the + * user, so he can use the text extraction tools without a delay. + */ + if ( hasFeature( TextExtraction ) && !request->page()->hasTextPage() && canGenerateTextPage() && !d->m_closing ) { + d->mTextPageReady = false; + d->textPageGenerationThread()->startGeneration( request->page() ); + } + + return; + } + + const QImage& img = image( request ); + request->page()->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( img ) ), request->normalizedRect() ); + const int pageNumber = request->page()->number(); + + d->mPixmapReady = true; + + signalPixmapRequestDone( request ); + if ( calcBoundingBox ) + updatePageBoundingBox( pageNumber, Utils::imageBoundingBox( &img ) ); +} + +bool Generator::canGenerateTextPage() const +{ + Q_D( const Generator ); + return d->mTextPageReady; +} + +void Generator::generateTextPage( Page *page ) +{ + TextPage *tp = textPage( page ); + page->setTextPage( tp ); + signalTextGenerationDone( page, tp ); +} + +QImage Generator::image( PixmapRequest *request ) +{ + Q_D( Generator ); + return d->image( request ); +} + +TextPage* Generator::textPage( Page* ) +{ + return 0; +} + +const DocumentInfo * Generator::generateDocumentInfo() +{ + return 0; +} + +const DocumentSynopsis * Generator::generateDocumentSynopsis() +{ + return 0; +} + +FontInfo::List Generator::fontsForPage( int ) +{ + return FontInfo::List(); +} + +const QList * Generator::embeddedFiles() const +{ + return 0; +} + +Generator::PageSizeMetric Generator::pagesSizeMetric() const +{ + return None; +} + +bool Generator::isAllowed( Permission ) const +{ + return true; +} + +void Generator::rotationChanged( Rotation, Rotation ) +{ +} + +PageSize::List Generator::pageSizes() const +{ + return PageSize::List(); +} + +void Generator::pageSizeChanged( const PageSize &, const PageSize & ) +{ +} + +bool Generator::print( QPrinter& ) +{ + return false; +} + +Generator::PrintError Generator::printError() const +{ + return UnknownPrintError; +} + +QVariant Generator::metaData( const QString &key, const QVariant &option ) const +{ + Q_D( const Generator ); + return d->metaData( key, option ); +} + +ExportFormat::List Generator::exportFormats() const +{ + return ExportFormat::List(); +} + +bool Generator::exportTo( const QString&, const ExportFormat& ) +{ + return false; +} + +void Generator::walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const +{ + *walletKey = fileName.section('/', -1, -1); + *walletName = KWallet::Wallet::NetworkWallet(); + *walletFolder = "KPdf"; +} + +bool Generator::hasFeature( GeneratorFeature feature ) const +{ + Q_D( const Generator ); + return d->m_features.contains( feature ); +} + +void Generator::signalPixmapRequestDone( PixmapRequest * request ) +{ + Q_D( Generator ); + if ( d->m_document ) + d->m_document->requestDone( request ); + else + { + delete request; + } +} + +void Generator::signalTextGenerationDone( Page *page, TextPage *textPage ) +{ + Q_D( Generator ); + if ( d->m_document ) + d->m_document->textGenerationDone( page ); + else + delete textPage; +} + +const Document * Generator::document() const +{ + Q_D( const Generator ); + if ( d->m_document ) + { + return d->m_document->m_parent; + } + return 0; +} + +void Generator::setFeature( GeneratorFeature feature, bool on ) +{ + Q_D( Generator ); + if ( on ) + d->m_features.insert( feature ); + else + d->m_features.remove( feature ); +} + +QVariant Generator::documentMetaData( const QString &key, const QVariant &option ) const +{ + Q_D( const Generator ); + if ( !d->m_document ) + return QVariant(); + + return d->m_document->documentMetaData( key, option ); +} + +QMutex* Generator::userMutex() const +{ + Q_D( const Generator ); + if ( !d->m_mutex ) + { + d->m_mutex = new QMutex(); + } + return d->m_mutex; +} + +void Generator::updatePageBoundingBox( int page, const NormalizedRect & boundingBox ) +{ + Q_D( Generator ); + if ( d->m_document ) // still connected to document? + d->m_document->setPageBoundingBox( page, boundingBox ); +} + +void Generator::requestFontData(const Okular::FontInfo & /*font*/, QByteArray * /*data*/) +{ + +} + +const SourceReference * Generator::dynamicSourceReference( int /*pageNr*/, double /*absX*/, double /*absY*/) +{ + return 0; +} + +void Generator::setDPI(const QSizeF & dpi) +{ + Q_D( Generator ); + d->m_dpi = dpi; +} + +QSizeF Generator::dpi() const +{ + Q_D( const Generator ); + return d->m_dpi; +} + +PixmapRequest::PixmapRequest( DocumentObserver *observer, int pageNumber, int width, int height, int priority, PixmapRequestFeatures features ) + : d( new PixmapRequestPrivate ) +{ + d->mObserver = observer; + d->mPageNumber = pageNumber; + d->mWidth = width; + d->mHeight = height; + d->mPriority = priority; + d->mFeatures = features; + d->mForce = false; + d->mTile = false; + d->mNormalizedRect = NormalizedRect(); +} + +PixmapRequest::~PixmapRequest() +{ + delete d; +} + +DocumentObserver *PixmapRequest::observer() const +{ + return d->mObserver; +} + +int PixmapRequest::pageNumber() const +{ + return d->mPageNumber; +} + +int PixmapRequest::width() const +{ + return d->mWidth; +} + +int PixmapRequest::height() const +{ + return d->mHeight; +} + +int PixmapRequest::priority() const +{ + return d->mPriority; +} + +bool PixmapRequest::asynchronous() const +{ + return d->mFeatures & Asynchronous; +} + +bool PixmapRequest::preload() const +{ + return d->mFeatures & Preload; +} + +Page* PixmapRequest::page() const +{ + return d->mPage; +} + +void PixmapRequest::setTile( bool tile ) +{ + d->mTile = tile; +} + +bool PixmapRequest::isTile() const +{ + return d->mTile; +} + +void PixmapRequest::setNormalizedRect( const NormalizedRect &rect ) +{ + if ( d->mNormalizedRect == rect ) + return; + + d->mNormalizedRect = rect; +} + +const NormalizedRect& PixmapRequest::normalizedRect() const +{ + return d->mNormalizedRect; +} + +Okular::TilesManager* PixmapRequestPrivate::tilesManager() const +{ + return mPage->d->tilesManager(mObserver); +} + +void PixmapRequestPrivate::swap() +{ + qSwap( mWidth, mHeight ); +} + +class Okular::ExportFormatPrivate : public QSharedData +{ + public: + ExportFormatPrivate( const QString &description, const KMimeType::Ptr &mimeType, const KIcon &icon = KIcon() ) + : QSharedData(), mDescription( description ), mMimeType( mimeType ), mIcon( icon ) + { + } + ~ExportFormatPrivate() + { + } + + QString mDescription; + KMimeType::Ptr mMimeType; + KIcon mIcon; +}; + +ExportFormat::ExportFormat() + : d( new ExportFormatPrivate( QString(), KMimeType::Ptr() ) ) +{ +} + +ExportFormat::ExportFormat( const QString &description, const KMimeType::Ptr &mimeType ) + : d( new ExportFormatPrivate( description, mimeType ) ) +{ +} + +ExportFormat::ExportFormat( const KIcon &icon, const QString &description, const KMimeType::Ptr &mimeType ) + : d( new ExportFormatPrivate( description, mimeType, icon ) ) +{ +} + +ExportFormat::~ExportFormat() +{ +} + +ExportFormat::ExportFormat( const ExportFormat &other ) + : d( other.d ) +{ +} + +ExportFormat& ExportFormat::operator=( const ExportFormat &other ) +{ + if ( this == &other ) + return *this; + + d = other.d; + + return *this; +} + +QString ExportFormat::description() const +{ + return d->mDescription; +} + +KMimeType::Ptr ExportFormat::mimeType() const +{ + return d->mMimeType; +} + +KIcon ExportFormat::icon() const +{ + return d->mIcon; +} + +bool ExportFormat::isNull() const +{ + return d->mMimeType.isNull() || d->mDescription.isNull(); +} + +ExportFormat ExportFormat::standardFormat( StandardExportFormat type ) +{ + switch ( type ) + { + case PlainText: + return ExportFormat( KIcon( "text-x-generic" ), i18n( "Plain &Text..." ), KMimeType::mimeType( "text/plain" ) ); + break; + case PDF: + return ExportFormat( KIcon( "application-pdf" ), i18n( "PDF" ), KMimeType::mimeType( "application/pdf" ) ); + break; + case OpenDocumentText: + return ExportFormat( + KIcon( "application-vnd.oasis.opendocument.text" ), + i18nc( "This is the document format", "OpenDocument Text" ), + KMimeType::mimeType( "application/vnd.oasis.opendocument.text" ) ); + break; + case HTML: + return ExportFormat( KIcon( "text-html" ), i18nc( "This is the document format", "HTML" ), KMimeType::mimeType( "text/html" ) ); + break; + } + return ExportFormat(); +} + +bool ExportFormat::operator==( const ExportFormat &other ) const +{ + return d == other.d; +} + +bool ExportFormat::operator!=( const ExportFormat &other ) const +{ + return d != other.d; +} + +QDebug operator<<( QDebug str, const Okular::PixmapRequest &req ) +{ + QString s = QString( "PixmapRequest(#%2, %1, %3x%4, page %6, prio %5)" ) + .arg( QString( req.asynchronous() ? "async" : "sync" ) ) + .arg( (qulonglong)req.observer() ) + .arg( req.width() ) + .arg( req.height() ) + .arg( req.priority() ) + .arg( req.pageNumber() ); + str << qPrintable( s ); + return str; +} + +#include "generator.moc" + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/okular/core/generator.h b/okular/core/generator.h new file mode 100644 index 00000000..506f8a82 --- /dev/null +++ b/okular/core/generator.h @@ -0,0 +1,699 @@ +/*************************************************************************** + * Copyright (C) 2004-5 by Enrico Ros * + * Copyright (C) 2005 by Piotr Szymanski * + * Copyright (C) 2008 by Albert Astals Cid * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_GENERATOR_H_ +#define _OKULAR_GENERATOR_H_ + +#include "okular_export.h" +#include "document.h" +#include "fontinfo.h" +#include "global.h" +#include "pagesize.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define OKULAR_EXPORT_PLUGIN( classname, aboutdata ) \ + K_PLUGIN_FACTORY( classname ## Factory, registerPlugin< classname >(); ) \ + K_EXPORT_PLUGIN( classname ## Factory( aboutdata ) ) + +class QByteArray; +class QMutex; +class QPrinter; +class QPrintDialog; +class KIcon; + +namespace Okular { + +class DocumentFonts; +class DocumentInfo; +class DocumentObserver; +class DocumentSynopsis; +class EmbeddedFile; +class ExportFormatPrivate; +class FontInfo; +class GeneratorPrivate; +class Page; +class PixmapRequest; +class PixmapRequestPrivate; +class TextPage; +class NormalizedRect; +class SourceReference; + +/* Note: on contents generation and asynchronous queries. + * Many observers may want to request data syncronously or asynchronously. + * - Sync requests. These should be done in-place. + * - Async request must be done in real background. That usually means a + * thread, such as QThread derived classes. + * Once contents are available, they must be immediately stored in the + * Page they refer to, and a signal is emitted as soon as storing + * (even for sync or async queries) has been done. + */ + +/** + * @short Defines an entry for the export menu + * + * This class encapsulates information about an export format. + * Every Generator can support 0 or more export formats which can be + * queried with @ref Generator::exportFormats(). + */ +class OKULAR_EXPORT ExportFormat +{ + public: + typedef QList List; + + /** + * Creates an empty export format. + * + * @see isNull() + */ + ExportFormat(); + + /** + * Creates a new export format. + * + * @param description The i18n'ed description of the format. + * @param mimeType The supported mime type of the format. + */ + ExportFormat( const QString &description, const KMimeType::Ptr &mimeType ); + + /** + * Creates a new export format. + * + * @param icon The icon used in the GUI for this format. + * @param description The i18n'ed description of the format. + * @param mimeType The supported mime type of the format. + */ + ExportFormat( const KIcon &icon, const QString &description, const KMimeType::Ptr &mimeType ); + + /** + * Destroys the export format. + */ + ~ExportFormat(); + + /** + * @internal + */ + ExportFormat( const ExportFormat &other ); + + /** + * @internal + */ + ExportFormat& operator=( const ExportFormat &other ); + + /** + * Returns the description of the format. + */ + QString description() const; + + /** + * Returns the mime type of the format. + */ + KMimeType::Ptr mimeType() const; + + /** + * Returns the icon for GUI representations of the format. + */ + KIcon icon() const; + + /** + * Returns whether the export format is null/valid. + * + * An ExportFormat is null if the mimetype is not valid or the + * description is empty, or both. + */ + bool isNull() const; + + /** + * Type of standard export format. + */ + enum StandardExportFormat + { + PlainText, ///< Plain text + PDF, ///< PDF, aka Portable Document Format + OpenDocumentText, ///< OpenDocument Text format @since 0.8 (KDE 4.2) + HTML ///< OpenDocument Text format @since 0.8 (KDE 4.2) + }; + + /** + * Builds a standard format for the specified @p type . + */ + static ExportFormat standardFormat( StandardExportFormat type ); + + bool operator==( const ExportFormat &other ) const; + + bool operator!=( const ExportFormat &other ) const; + + private: + /// @cond PRIVATE + friend class ExportFormatPrivate; + /// @endcond + QSharedDataPointer d; +}; + +/** + * @short [Abstract Class] The information generator. + * + * Most of class members are virtuals and some of them pure virtual. The pure + * virtuals provide the minimal functionalities for a Generator, that is being + * able to generate QPixmap for the Page 's of the Document. + * + * Implementing the other functions will make the Generator able to provide + * more contents and/or functionalities (like text extraction). + * + * Generation/query is requested by the Document class only, and that + * class stores the resulting data into Page s. The data will then be + * displayed by the GUI components (PageView, ThumbnailList, etc..). + * + * @see PrintInterface, ConfigInterface, GuiInterface + */ +class OKULAR_EXPORT Generator : public QObject +{ + /// @cond PRIVATE + friend class PixmapGenerationThread; + friend class TextPageGenerationThread; + /// @endcond + + Q_OBJECT + + public: + /** + * Describe the possible optional features that a Generator can + * provide. + */ + enum GeneratorFeature + { + Threaded, + TextExtraction, ///< Whether the Generator can extract text from the document in the form of TextPage's + ReadRawData, ///< Whether the Generator can read a document directly from its raw data. + FontInfo, ///< Whether the Generator can provide information about the fonts used in the document + PageSizes, ///< Whether the Generator can change the size of the document pages. + PrintNative, ///< Whether the Generator supports native cross-platform printing (QPainter-based). + PrintPostscript, ///< Whether the Generator supports postscript-based file printing. + PrintToFile, ///< Whether the Generator supports export to PDF & PS through the Print Dialog + TiledRendering ///< Whether the Generator can render tiles @since 0.16 (KDE 4.10) + }; + + /** + * Creates a new generator. + */ + Generator( QObject *parent, const QVariantList &args ); + + /** + * Destroys the generator. + */ + virtual ~Generator(); + + /** + * Loads the document with the given @p fileName and fills the + * @p pagesVector with the parsed pages. + * + * @note If you implement the WithPassword variants you don't need to implement this one + * + * @returns true on success, false otherwise. + */ + virtual bool loadDocument( const QString & fileName, QVector< Page * > & pagesVector ); + + /** + * Loads the document from the raw data @p fileData and fills the + * @p pagesVector with the parsed pages. + * + * @note If you implement the WithPassword variants you don't need to implement this one + * + * @note the Generator has to have the feature @ref ReadRawData enabled + * + * @returns true on success, false otherwise. + */ + virtual bool loadDocumentFromData( const QByteArray & fileData, QVector< Page * > & pagesVector ); + + /** + * Loads the document with the given @p fileName and @p password and fills the + * @p pagesVector with the parsed pages. + * + * @note Do not implement this if your format doesn't support passwords, it'll cleanly call loadDocument() + * + * @since 0.20 (KDE 4.14) + * + * @returns a LoadResult defining the result of the operation + */ + virtual Document::OpenResult loadDocumentWithPassword( const QString & fileName, QVector< Page * > & pagesVector, const QString &password ); + + /** + * Loads the document from the raw data @p fileData and @p password and fills the + * @p pagesVector with the parsed pages. + * + * @note Do not implement this if your format doesn't support passwords, it'll cleanly call loadDocumentFromData() + * + * @note the Generator has to have the feature @ref ReadRawData enabled + * + * @since 0.20 (KDE 4.14) + * + * @returns a LoadResult defining the result of the operation + */ + virtual Document::OpenResult loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector< Page * > & pagesVector, const QString &password ); + + + /** + * This method is called when the document is closed and not used + * any longer. + * + * @returns true on success, false otherwise. + */ + bool closeDocument(); + + /** + * This method returns whether the generator is ready to + * handle a new pixmap request. + */ + virtual bool canGeneratePixmap() const; + + /** + * This method can be called to trigger the generation of + * a new pixmap as described by @p request. + */ + virtual void generatePixmap( PixmapRequest * request ); + + /** + * This method returns whether the generator is ready to + * handle a new text page request. + */ + virtual bool canGenerateTextPage() const; + + /** + * This method can be called to trigger the generation of + * a text page for the given @p page. + * + * The generation is done synchronous or asynchronous, depending + * on the @p type parameter and the capabilities of the + * generator (e.g. multithreading). + * + * @see TextPage + */ + virtual void generateTextPage( Page * page ); + + /** + * Returns the general information object of the document or 0 if + * no information are available. + */ + virtual const DocumentInfo * generateDocumentInfo(); + + /** + * Returns the 'table of content' object of the document or 0 if + * no table of content is available. + */ + virtual const DocumentSynopsis * generateDocumentSynopsis(); + + /** + * Returns the 'list of embedded fonts' object of the specified \page + * of the document. + * + * \param page a page of the document, starting from 0 - -1 indicates all + * the other fonts + */ + virtual FontInfo::List fontsForPage( int page ); + + /** + * Returns the 'list of embedded files' object of the document or 0 if + * no list of embedded files is available. + */ + virtual const QList * embeddedFiles() const; + + /** + * This enum identifies the metric of the page size. + */ + enum PageSizeMetric + { + None, ///< The page size is not defined in a physical metric. + Points, ///< The page size is given in 1/72 inches. + Pixels ///< The page size is given in screen pixels @since 0.19 (KDE 4.13) + }; + + /** + * This method returns the metric of the page size. Default is @ref None. + */ + virtual PageSizeMetric pagesSizeMetric() const; + + /** + * This method returns whether given @p action (@ref Permission) is + * allowed in this document. + */ + virtual bool isAllowed( Permission action ) const; + + /** + * This method is called when the orientation has been changed by the user. + */ + virtual void rotationChanged( Rotation orientation, Rotation oldOrientation ); + + /** + * Returns the list of supported page sizes. + */ + virtual PageSize::List pageSizes() const; + + /** + * This method is called when the page size has been changed by the user. + */ + virtual void pageSizeChanged( const PageSize &pageSize, const PageSize &oldPageSize ); + + /** + * This method is called to print the document to the given @p printer. + */ + virtual bool print( QPrinter &printer ); + + /** + * Possible print errors + * @since 0.11 (KDE 4.5) + */ + enum PrintError + { + NoPrintError, ///< There was no print error + UnknownPrintError, + TemporaryFileOpenPrintError, + FileConversionPrintError, + PrintingProcessCrashPrintError, + PrintingProcessStartPrintError, + PrintToFilePrintError, + InvalidPrinterStatePrintError, + UnableToFindFilePrintError, + NoFileToPrintError, + NoBinaryToPrintError, + InvalidPageSizePrintError ///< @since 0.18.2 (KDE 4.12.2) + }; + + /** + * This method returns the meta data of the given @p key with the given @p option + * of the document. + */ + virtual QVariant metaData( const QString &key, const QVariant &option ) const; + + /** + * Returns the list of additional supported export formats. + */ + virtual ExportFormat::List exportFormats() const; + + /** + * This method is called to export the document in the given @p format and save it + * under the given @p fileName. The format must be one of the supported export formats. + */ + virtual bool exportTo( const QString &fileName, const ExportFormat &format ); + + /** + * This method is called to know which wallet data should be used for the given file name. + * Unless you have very special requirements to where wallet data should be stored you + * don't need to reimplement this method. + */ + virtual void walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const; + + /** + * Query for the specified @p feature. + */ + bool hasFeature( GeneratorFeature feature ) const; + + /** + * Update DPI of the generator + * + * @since 0.19 (KDE 4.13) + */ + void setDPI(const QSizeF &dpi); + + Q_SIGNALS: + /** + * This signal should be emitted whenever an error occurred in the generator. + * + * @param message The message which should be shown to the user. + * @param duration The time that the message should be shown to the user. + */ + void error( const QString &message, int duration ); + + /** + * This signal should be emitted whenever the user should be warned. + * + * @param message The message which should be shown to the user. + * @param duration The time that the message should be shown to the user. + */ + void warning( const QString &message, int duration ); + + /** + * This signal should be emitted whenever the user should be noticed. + * + * @param message The message which should be shown to the user. + * @param duration The time that the message should be shown to the user. + */ + void notice( const QString &message, int duration ); + + protected: + /** + * This method must be called when the pixmap request triggered by generatePixmap() + * has been finished. + */ + void signalPixmapRequestDone( PixmapRequest * request ); + + /** + * This method must be called when a text generation has been finished. + */ + void signalTextGenerationDone( Page *page, TextPage *textPage ); + + /** + * This method is called when the document is closed and not used + * any longer. + * + * @returns true on success, false otherwise. + */ + virtual bool doCloseDocument() = 0; + + /** + * Returns the image of the page as specified in + * the passed pixmap @p request. + * + * @warning this method may be executed in its own separated thread if the + * @ref Threaded is enabled! + */ + virtual QImage image( PixmapRequest *page ); + + /** + * Returns the text page for the given @p page. + * + * @warning this method may be executed in its own separated thread if the + * @ref Threaded is enabled! + */ + virtual TextPage* textPage( Page *page ); + + /** + * Returns a pointer to the document. + */ + const Document * document() const; + + /** + * Toggle the @p feature . + */ + void setFeature( GeneratorFeature feature, bool on = true ); + + /** + * Request a meta data of the Document, if available, like an internal + * setting. + */ + QVariant documentMetaData( const QString &key, const QVariant &option = QVariant() ) const; + + /** + * Return the pointer to a mutex the generator can use freely. + */ + QMutex* userMutex() const; + + /** + * Set the bounding box of a page after the page has already been handed + * to the Document. Call this instead of Page::setBoundingBox() to ensure + * that all observers are notified. + * + * @since 0.7 (KDE 4.1) + */ + void updatePageBoundingBox( int page, const NormalizedRect & boundingBox ); + + /** + * Returns DPI, previously set via setDPI() + * @since 0.19 (KDE 4.13) + */ + QSizeF dpi() const; + + protected Q_SLOTS: + /** + * Gets the font data for the given font + * + * @since 0.8 (KDE 4.1) + */ + void requestFontData(const Okular::FontInfo &font, QByteArray *data); + + /** + * Asks the generator to dynamically generate a SourceReference for a given + * page number and absolute X and Y position on this page. + * + * @attention Ownership of the returned SourceReference is transferred to the caller. + * @since 0.10 (KDE 4.4) + */ + const SourceReference * dynamicSourceReference( int pageNr, double absX, double absY ); + + /** + * Returns the last print error in case print() failed + * @since 0.11 (KDE 4.5) + */ + Okular::Generator::PrintError printError() const; + + protected: + /// @cond PRIVATE + Generator( GeneratorPrivate &dd, QObject *parent, const QVariantList &args ); + Q_DECLARE_PRIVATE( Generator ) + GeneratorPrivate *d_ptr; + + friend class Document; + friend class DocumentPrivate; + /// @endcond PRIVATE + + private: + Q_DISABLE_COPY( Generator ) + + Q_PRIVATE_SLOT( d_func(), void pixmapGenerationFinished() ) + Q_PRIVATE_SLOT( d_func(), void textpageGenerationFinished() ) +}; + +/** + * @short Describes a pixmap type request. + */ +class OKULAR_EXPORT PixmapRequest +{ + friend class Document; + friend class DocumentPrivate; + + public: + enum PixmapRequestFeature + { + NoFeature = 0, + Asynchronous = 1, + Preload = 2 + }; + Q_DECLARE_FLAGS( PixmapRequestFeatures, PixmapRequestFeature ) + + /** + * Creates a new pixmap request. + * + * @param observer The observer. + * @param pageNumber The page number. + * @param width The width of the page. + * @param height The height of the page. + * @param priority The priority of the request. + * @param features The features of generation. + */ + PixmapRequest( DocumentObserver *observer, int pageNumber, int width, int height, int priority, PixmapRequestFeatures features ); + + /** + * Destroys the pixmap request. + */ + ~PixmapRequest(); + + /** + * Returns the observer of the request. + */ + DocumentObserver *observer() const; + + /** + * Returns the page number of the request. + */ + int pageNumber() const; + + /** + * Returns the page width of the requested pixmap. + */ + int width() const; + + /** + * Returns the page height of the requested pixmap. + */ + int height() const; + + /** + * Returns the priority (less it better, 0 is maximum) of the + * request. + */ + int priority() const; + + /** + * Returns whether the generation should be done synchronous or + * asynchronous. + * + * If asynchronous, the pixmap is created in a thread and the observer + * is notified when the job is done. + */ + bool asynchronous() const; + + /** + * Returns whether the generation request is for a page that is not important + * i.e. it's just for speeding up future rendering + */ + bool preload() const; + + /** + * Returns a pointer to the page where the pixmap shall be generated for. + */ + Page *page() const; + + /** + * Sets whether the generator should render only the given normalized + * rect or the entire page + * + * @since 0.16 (KDE 4.10) + */ + void setTile( bool tile ); + + /** + * Returns whether the generator should render just the region given by + * normalizedRect() or the entire page. + * + * @since 0.16 (KDE 4.10) + */ + bool isTile() const; + + /** + * Sets the region of the page to request. + * + * @since 0.16 (KDE 4.10) + */ + void setNormalizedRect( const NormalizedRect &rect ); + + /** + * Returns the normalized region of the page to request. + * + * @since 0.16 (KDE 4.10) + */ + const NormalizedRect& normalizedRect() const; + + private: + Q_DISABLE_COPY( PixmapRequest ) + + friend class PixmapRequestPrivate; + PixmapRequestPrivate* const d; +}; + +} + +Q_DECLARE_METATYPE(Okular::Generator::PrintError) + +#ifndef QT_NO_DEBUG_STREAM +OKULAR_EXPORT QDebug operator<<( QDebug str, const Okular::PixmapRequest &req ); +#endif + +#endif + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/okular/core/generator_p.cpp b/okular/core/generator_p.cpp new file mode 100644 index 00000000..94c8a09c --- /dev/null +++ b/okular/core/generator_p.cpp @@ -0,0 +1,144 @@ +/*************************************************************************** + * Copyright (C) 2007 Tobias Koenig * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "generator_p.h" + +#include + +#include "fontinfo.h" +#include "generator.h" +#include "utils.h" + +using namespace Okular; + +PixmapGenerationThread::PixmapGenerationThread( Generator *generator ) + : mGenerator( generator ), mRequest( 0 ), mCalcBoundingBox( false ) +{ +} + +void PixmapGenerationThread::startGeneration( PixmapRequest *request, bool calcBoundingBox ) +{ + mRequest = request; + mCalcBoundingBox = calcBoundingBox; + + start( QThread::InheritPriority ); +} + +void PixmapGenerationThread::endGeneration() +{ + mRequest = 0; +} + +PixmapRequest *PixmapGenerationThread::request() const +{ + return mRequest; +} + +QImage PixmapGenerationThread::image() const +{ + return mImage; +} + +bool PixmapGenerationThread::calcBoundingBox() const +{ + return mCalcBoundingBox; +} + +NormalizedRect PixmapGenerationThread::boundingBox() const +{ + return mBoundingBox; +} + +void PixmapGenerationThread::run() +{ + mImage = QImage(); + + if ( mRequest ) + { + mImage = mGenerator->image( mRequest ); + if ( mCalcBoundingBox ) + mBoundingBox = Utils::imageBoundingBox( &mImage ); + } +} + + +TextPageGenerationThread::TextPageGenerationThread( Generator *generator ) + : mGenerator( generator ), mPage( 0 ) +{ +} + +void TextPageGenerationThread::startGeneration( Page *page ) +{ + mPage = page; + + start( QThread::InheritPriority ); +} + +void TextPageGenerationThread::endGeneration() +{ + mPage = 0; +} + +Page *TextPageGenerationThread::page() const +{ + return mPage; +} + +TextPage* TextPageGenerationThread::textPage() const +{ + return mTextPage; +} + +void TextPageGenerationThread::run() +{ + mTextPage = 0; + + if ( mPage ) + mTextPage = mGenerator->textPage( mPage ); +} + + +FontExtractionThread::FontExtractionThread( Generator *generator, int pages ) + : mGenerator( generator ), mNumOfPages( pages ), mGoOn( true ) +{ +} + +void FontExtractionThread::startExtraction( bool async ) +{ + if ( async ) + { + connect( this, SIGNAL(finished()), this, SLOT(deleteLater()) ); + start( QThread::InheritPriority ); + } + else + { + run(); + deleteLater(); + } +} + +void FontExtractionThread::stopExtraction() +{ + mGoOn = false; +} + +void FontExtractionThread::run() +{ + for ( int i = -1; i < mNumOfPages && mGoOn; ++i ) + { + FontInfo::List list = mGenerator->fontsForPage( i ); + foreach ( const FontInfo& fi, list ) + { + emit gotFont( fi ); + } + emit progress( i ); + } +} + +#include "generator_p.moc" diff --git a/okular/core/generator_p.h b/okular/core/generator_p.h new file mode 100644 index 00000000..8a860377 --- /dev/null +++ b/okular/core/generator_p.h @@ -0,0 +1,169 @@ +/*************************************************************************** + * Copyright (C) 2007 Tobias Koenig * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_THREADEDGENERATOR_P_H +#define OKULAR_THREADEDGENERATOR_P_H + +#include "area.h" + +#include +#include +#include + +class QEventLoop; +class QMutex; + +namespace Okular { + +class DocumentObserver; +class DocumentPrivate; +class FontInfo; +class Generator; +class Page; +class PixmapGenerationThread; +class PixmapRequest; +class TextPage; +class TextPageGenerationThread; +class TilesManager; + +class GeneratorPrivate +{ + public: + GeneratorPrivate(); + + virtual ~GeneratorPrivate(); + + Q_DECLARE_PUBLIC( Generator ) + Generator *q_ptr; + + PixmapGenerationThread* pixmapGenerationThread(); + TextPageGenerationThread* textPageGenerationThread(); + + void pixmapGenerationFinished(); + void textpageGenerationFinished(); + + QMutex* threadsLock(); + + virtual QVariant metaData( const QString &key, const QVariant &option ) const; + virtual QImage image( PixmapRequest * ); + + DocumentPrivate *m_document; + // NOTE: the following should be a QSet< GeneratorFeature >, + // but it is not to avoid #include'ing generator.h + QSet< int > m_features; + PixmapGenerationThread *mPixmapGenerationThread; + TextPageGenerationThread *mTextPageGenerationThread; + mutable QMutex *m_mutex; + QMutex *m_threadsMutex; + bool mPixmapReady : 1; + bool mTextPageReady : 1; + bool m_closing : 1; + QEventLoop *m_closingLoop; + QSizeF m_dpi; +}; + + +class PixmapRequestPrivate +{ + public: + void swap(); + TilesManager *tilesManager() const; + + DocumentObserver *mObserver; + int mPageNumber; + int mWidth; + int mHeight; + int mPriority; + int mFeatures; + bool mForce : 1; + bool mTile : 1; + Page *mPage; + NormalizedRect mNormalizedRect; +}; + + +class PixmapGenerationThread : public QThread +{ + Q_OBJECT + + public: + PixmapGenerationThread( Generator *generator ); + + void startGeneration( PixmapRequest *request, bool calcBoundingRect ); + + void endGeneration(); + + PixmapRequest *request() const; + + QImage image() const; + bool calcBoundingBox() const; + NormalizedRect boundingBox() const; + + protected: + virtual void run(); + + private: + Generator *mGenerator; + PixmapRequest *mRequest; + QImage mImage; + NormalizedRect mBoundingBox; + bool mCalcBoundingBox : 1; +}; + + +class TextPageGenerationThread : public QThread +{ + Q_OBJECT + + public: + TextPageGenerationThread( Generator *generator ); + + void startGeneration( Page *page ); + + void endGeneration(); + + Page *page() const; + + TextPage* textPage() const; + + protected: + virtual void run(); + + private: + Generator *mGenerator; + Page *mPage; + TextPage *mTextPage; +}; + +class FontExtractionThread : public QThread +{ + Q_OBJECT + + public: + FontExtractionThread( Generator *generator, int pages ); + + void startExtraction( bool async ); + void stopExtraction(); + + Q_SIGNALS: + void gotFont( const Okular::FontInfo& ); + void progress( int page ); + + protected: + virtual void run(); + + private: + Generator *mGenerator; + int mNumOfPages; + bool mGoOn; +}; + +} + +#endif diff --git a/okular/core/global.h b/okular/core/global.h new file mode 100644 index 00000000..24cef77b --- /dev/null +++ b/okular/core/global.h @@ -0,0 +1,83 @@ +/*************************************************************************** + * Copyright (C) 2007 by Tobias Koenig * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_GLOBAL_H +#define OKULAR_GLOBAL_H + +#include + +namespace Okular { + +/** + * Describes the DRM capabilities. + */ +enum Permission +{ + AllowModify = 1, ///< Allows to modify the document + AllowCopy = 2, ///< Allows to copy the document + AllowPrint = 4, ///< Allows to print the document + AllowNotes = 8, ///< Allows to add annotations to the document + AllowFillForms = 16 ///< Allows to fill the forms in the document +}; +Q_DECLARE_FLAGS( Permissions, Permission ) + +/** + * Describes the direction of searching. + */ +enum SearchDirection +{ + FromTop, ///< Searching from top of the page, next result is to be found, there was no earlier search result. + FromBottom, ///< Searching from bottom of the page, next result is to be found, there was no earlier search result. + NextResult, ///< Searching for the next result on the page, earlier result should be located so we search from the last result not from the beginning of the page. + PreviousResult ///< Searching for the previous result on the page, earlier result should be located so we search from the last result not from the beginning of the page. +}; + +/** + * A rotation. + */ +enum Rotation +{ + Rotation0 = 0, ///< Not rotated. + Rotation90 = 1, ///< Rotated 90 degrees clockwise. + Rotation180 = 2, ///< Rotated 180 degrees clockwise. + Rotation270 = 3 ///< Rotated 2700 degrees clockwise. +}; + +/** + * Describes the type of generation of objects + */ +enum GenerationType +{ + Synchronous, ///< Will create the object in a synchronous way + Asynchronous ///< Will create the object in an asynchronous way +}; + +/** + * The side(s) to be considered when merging areas. + */ +enum MergeSide +{ + MergeRight = 0, ///< Merge only if the right side of the first area intersect. + MergeBottom = 1, ///< Merge only if the bottom side of the first area intersect. + MergeLeft = 2, ///< Merge only if the left side of the first area intersect. + MergeTop = 3, ///< Merge only if the top side of the first area intersect. + MergeAll = 4 ///< Merge if the areas intersects, no matter which side(s). +}; + +/** + * Describes the possible script types. + */ +enum ScriptType +{ + JavaScript = 0 ///< JavaScript code +}; + +} + +#endif diff --git a/okular/core/misc.cpp b/okular/core/misc.cpp new file mode 100644 index 00000000..34812c4f --- /dev/null +++ b/okular/core/misc.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + * Copyright (C) 2005 by Piotr Szymanski * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "core/misc.h" + +#include + +#include "debug_p.h" + +using namespace Okular; + +class TextSelection::Private +{ + public: + int direction; + int it[2]; + NormalizedPoint cur[2]; +}; + +TextSelection::TextSelection( const NormalizedPoint & a, const NormalizedPoint & b ) + : d( new Private ) +{ + if (b.y-a.y<0 || (b.y-a.y==0 && b.x-a.x <0)) + d->direction = 1; + else + d->direction = 0; + + d->cur[0] = a; + d->cur[1] = b; + d->it[d->direction % 2] = -1; + d->it[(d->direction + 1) % 2] = -1; +} + +TextSelection::~TextSelection() +{ + delete d; +} + +void TextSelection::end( const NormalizedPoint & p ) +{ + // changing direction as in 2b , assuming the bool->int conversion is correct + int dir1 = d->direction; + d->direction = (p.y - d->cur[0].y < 0 || (p.y - d->cur[0].y == 0 && p.x - d->cur[0].x < 0)); + if (d->direction != dir1) + kDebug(OkularDebug) << "changing direction in selection"; + + d->cur[1] = p; +} + +void TextSelection::itE( int p ) +{ + d->it[(d->direction + 1) % 2] = p; +} + +void TextSelection::itB( int p ) +{ + d->it[(d->direction) % 2] = p; +} + +int TextSelection::direction() const +{ + return d->direction; +} + +NormalizedPoint TextSelection::start() const +{ + return d->cur[d->direction % 2]; +} + +NormalizedPoint TextSelection::end() const +{ + return d->cur[(d->direction + 1) % 2]; +} + +int TextSelection::itB() const +{ + return d->it[d->direction % 2]; +} + +int TextSelection::itE() const +{ + return d->it[(d->direction + 1) % 2]; +} diff --git a/okular/core/misc.h b/okular/core/misc.h new file mode 100644 index 00000000..2cfba3c1 --- /dev/null +++ b/okular/core/misc.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (C) 2005 by Piotr Szymanski * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_MISC_H_ +#define _OKULAR_MISC_H_ + +#include "okular_export.h" +#include "area.h" + +namespace Okular { + +/** + @short Wrapper around the information needed to generate the selection area + There are two assumptions inside this class: + 1. the start never changes, one instance of this class is used for one selection, + therefore the start of the selection will not change, only end and direction of + the selection will change. + By direction we mean the direction in which the end moves in relation to the start, + forward selection is when end is after the start, backward when its before. + + 2. The following changes might appear during selection: + a. the end moves without changing the direction (it can move up and down but not past the start): + only itE will be updated + b. the end moves with changing the direction then itB becomes itE if the previous direction was forward + or itE becomes itB + + 3. Internally it that is related to the start cursor is always at it[0] while it related to end is it[1], + transition between meanings (itB/itE) is done with dir modifier; +*/ +class OKULAR_EXPORT TextSelection +{ + public: + /** + * Creates a new text selection with the given @p start and @p end point. + */ + TextSelection( const NormalizedPoint &start, const NormalizedPoint &end ); + + /** + * Destroys the text selection. + */ + ~TextSelection(); + + /** + * Changes the end point of the selection to the given @p point. + */ + void end( const NormalizedPoint &point ); + + void itE( int pos ); + void itB( int pos ); + + /** + * Returns the direction of the selection. + */ + int direction() const; + + /** + * Returns the start point of the selection. + */ + NormalizedPoint start() const; + + /** + * Returns the end point of the selection. + */ + NormalizedPoint end() const; + + int itB() const; + int itE() const; + + private: + class Private; + Private* const d; +}; + +} + +#endif diff --git a/okular/core/movie.cpp b/okular/core/movie.cpp new file mode 100644 index 00000000..dd780eba --- /dev/null +++ b/okular/core/movie.cpp @@ -0,0 +1,155 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2012 by Guillermo A. Amaral B. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "movie.h" + +// qt/kde includes +#include +#include +#include +#include + +#include + +#include "debug_p.h" + +using namespace Okular; + +class Movie::Private +{ + public: + Private( const QString &url ) + : m_url( url ), + m_rotation( Rotation0 ), + m_playMode( PlayOnce ), + m_tmp( 0 ), + m_showControls( false ), + m_autoPlay( false ), + m_showPosterImage( false ) + { + } + + QString m_url; + QSize m_aspect; + Rotation m_rotation; + PlayMode m_playMode; + QTemporaryFile *m_tmp; + QImage m_posterImage; + bool m_showControls : 1; + bool m_autoPlay : 1; + bool m_showPosterImage : 1; +}; + +Movie::Movie( const QString& fileName ) + : d( new Private( fileName ) ) +{ +} + +Movie::Movie( const QString& fileName, const QByteArray &data ) + : d( new Private( fileName ) ) +{ + /* Store movie data as temporary file. + * + * Originally loaded movie data directly using a QBuffer, but sadly phonon + * fails to play on a few of my test systems (I think it's the Phonon + * GStreamer backend). Storing the data in a temporary file works fine + * though, not to mention, it releases much needed memory. (gamaral) + */ + d->m_tmp = new QTemporaryFile( QString( "%1/okrXXXXXX" ).arg( QDir::tempPath() ) ); + if ( d->m_tmp->open() ) { + d->m_tmp->write( data ); + d->m_tmp->flush(); + } + else kDebug(OkularDebug) << "Failed to create temporary file for video data."; +} + +Movie::~Movie() +{ + delete d->m_tmp; + delete d; +} + +QString Movie::url() const +{ + if (d->m_tmp) + return d->m_tmp->fileName(); + else + return d->m_url; +} + +void Movie::setSize( const QSize &aspect ) +{ + d->m_aspect = aspect; +} + +QSize Movie::size() const +{ + return d->m_aspect; +} + +void Movie::setRotation( Rotation rotation ) +{ + d->m_rotation = rotation; +} + +Rotation Movie::rotation() const +{ + return d->m_rotation; +} + +void Movie::setShowControls( bool show ) +{ + d->m_showControls = show; +} + +bool Movie::showControls() const +{ + return d->m_showControls; +} + +void Movie::setPlayMode( Movie::PlayMode mode ) +{ + d->m_playMode = mode; +} + +Movie::PlayMode Movie::playMode() const +{ + return d->m_playMode; +} + +void Movie::setAutoPlay( bool autoPlay ) +{ + d->m_autoPlay = autoPlay; +} + +bool Movie::autoPlay() const +{ + return d->m_autoPlay; +} + +void Movie::setShowPosterImage( bool show ) +{ + d->m_showPosterImage = show; +} + +bool Movie::showPosterImage() const +{ + return d->m_showPosterImage; +} + +void Movie::setPosterImage( const QImage &image ) +{ + d->m_posterImage = image; +} + +QImage Movie::posterImage() const +{ + return d->m_posterImage; +} diff --git a/okular/core/movie.h b/okular/core/movie.h new file mode 100644 index 00000000..8d0e2fe7 --- /dev/null +++ b/okular/core/movie.h @@ -0,0 +1,149 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2012 by Guillermo A. Amaral B. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_MOVIE_H_ +#define _OKULAR_MOVIE_H_ + +#include "global.h" +#include "okular_export.h" + +#include + +class QImage; + +namespace Okular { + +/** + * @short Contains information about a movie object. + * + * @since 0.8 (KDE 4.2) + */ +class OKULAR_EXPORT Movie +{ + public: + /** + * The play mode for playing the movie + */ + enum PlayMode + { + PlayOnce, ///< Play the movie once, closing the movie controls at the end + PlayOpen, ///< Like PlayOnce, but leaving the controls open + PlayRepeat, ///< Play continuously until stopped + PlayPalindrome ///< Play forward, then backward, then again forward and so on until stopped + }; + + /** + * Creates a new movie object with the given external @p fileName. + */ + explicit Movie( const QString& fileName ); + + /** + * Creates a new movie object with the given movie data. + */ + explicit Movie( const QString& fileName, const QByteArray &data ); + + /** + * Destroys the movie object. + */ + ~Movie(); + + /** + * Returns the url of the movie. + */ + QString url() const; + + /** + * Sets the size for the movie. + */ + void setSize( const QSize &aspect ); + + /** + * Returns the size of the movie. + */ + QSize size() const; + + /** + * Sets the @p rotation of the movie. + */ + void setRotation( Rotation rotation ); + + /** + * Returns the rotation of the movie. + */ + Rotation rotation() const; + + /** + * Sets whether show a bar with movie controls + */ + void setShowControls( bool show ); + + /** + * Whether show a bar with movie controls + */ + bool showControls() const; + + /** + * Sets the way the movie should be played + */ + void setPlayMode( PlayMode mode ); + + /** + * How to play the movie + */ + PlayMode playMode() const; + + /** + * Sets whether to play the movie automatically + */ + void setAutoPlay( bool autoPlay ); + + /** + * Whether to play the movie automatically + */ + bool autoPlay() const; + + /** + * Sets whether to show a poster image. + * + * @since 4.10 + */ + void setShowPosterImage( bool show ); + + /** + * Whether to show a poster image. + * + * @since 4.10 + */ + bool showPosterImage() const; + + /** + * Sets the poster image. + * + * @since 4.10 + */ + void setPosterImage( const QImage &image ); + + /** + * Returns the poster image. + * + * @since 4.10 + */ + QImage posterImage() const; + + private: + class Private; + Private* const d; + + Q_DISABLE_COPY( Movie ) +}; + +} + +#endif diff --git a/okular/core/observer.cpp b/okular/core/observer.cpp new file mode 100644 index 00000000..caacfefe --- /dev/null +++ b/okular/core/observer.cpp @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (C) 2005 by Enrico Ros * + * Copyright (C) 2005 by Albert Astals Cid * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "observer.h" + +using namespace Okular; + +DocumentObserver::DocumentObserver() +{ +} + +DocumentObserver::~DocumentObserver() +{ +} + +void DocumentObserver::notifySetup( const QVector< Okular::Page * >&, int ) +{ +} + +void DocumentObserver::notifyViewportChanged( bool ) +{ +} + +void DocumentObserver::notifyPageChanged( int, int ) +{ +} + +void DocumentObserver::notifyContentsCleared( int ) +{ +} + +void DocumentObserver::notifyVisibleRectsChanged() +{ +} + +void DocumentObserver::notifyZoom( int ) +{ +} + +bool DocumentObserver::canUnloadPixmap( int ) const +{ + return true; +} + +void DocumentObserver::notifyCurrentPageChanged( int, int ) +{ +} diff --git a/okular/core/observer.h b/okular/core/observer.h new file mode 100644 index 00000000..4d3d9e0e --- /dev/null +++ b/okular/core/observer.h @@ -0,0 +1,121 @@ +/*************************************************************************** + * Copyright (C) 2005 by Enrico Ros * + * Copyright (C) 2005 by Albert Astals Cid * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_DOCUMENTOBSERVER_H_ +#define _OKULAR_DOCUMENTOBSERVER_H_ + +#include + +#include "okular_export.h" + +namespace Okular { + +class Page; + +/** + * @short Base class for objects being notified when something changes. + * + * Inherit this class and call Document->addObserver( yourClass ) to get + * notified of asynchronous events (new pixmap generated, or changed, etc..). + */ +class OKULAR_EXPORT DocumentObserver +{ + public: + DocumentObserver(); + /** + * Destroys the document observer. + */ + virtual ~DocumentObserver(); + + /** + * Flags that can be sent from the document to all observers to + * inform them about the type of object that has been changed. + */ + enum ChangedFlags { + Pixmap = 1, ///< Pixmaps has been changed + Bookmark = 2, ///< Bookmarks has been changed + Highlights = 4, ///< Highlighting information has been changed + TextSelection = 8, ///< Text selection has been changed + Annotations = 16, ///< Annotations have been changed + BoundingBox = 32, ///< Bounding boxes have been changed + NeedSaveAs = 64 ///< Set along with Annotations when Save As is needed or annotation changes will be lost @since 0.15 (KDE 4.9) + }; + + /** + * ... + */ + enum SetupFlags { + DocumentChanged = 1, ///< The document is a new document. + NewLayoutForPages = 2 ///< All the pages have + }; + + /** + * This method is called whenever the document is initialized or reconstructed. + * + * @param pages The vector of pages of the document. + * @param setupFlags the flags with the information about the setup + */ + virtual void notifySetup( const QVector< Okular::Page * > &pages, int setupFlags ); + + /** + * This method is called whenever the viewport has been changed. + * + * @param smoothMove If true, the move shall be animated. + */ + virtual void notifyViewportChanged( bool smoothMove ); + + /** + * This method is called whenever the content on @p page described by the + * passed @p flags has been changed. + */ + virtual void notifyPageChanged( int page, int flags ); + + /** + * This method is called whenever the content described by the passed @p flags + * has been cleared. + */ + virtual void notifyContentsCleared( int flags ); + + /** + * This method is called whenever the visible rects have been changed. + */ + virtual void notifyVisibleRectsChanged(); + + /** + * This method is called whenever the zoom of the document has been changed. + */ + virtual void notifyZoom( int factor ); + + /** + * Returns whether the observer agrees that all pixmaps for the given + * @p page can be unloaded to improve memory usage. + * + * Returns true per default. + */ + virtual bool canUnloadPixmap( int page ) const; + + /** + * This method is called after the current page of the document has been entered. + * + * @param previous The number of the previous page (is @c -1 for the initial page change). + * @param current The number of the current page. + * + * @since 0.16 (KDE 4.10) + */ + virtual void notifyCurrentPageChanged( int previous, int current ); + + private: + class Private; + const Private* d; +}; + +} + +#endif diff --git a/okular/core/okularGenerator.desktop b/okular/core/okularGenerator.desktop new file mode 100644 index 00000000..52154ff0 --- /dev/null +++ b/okular/core/okularGenerator.desktop @@ -0,0 +1,72 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=okular/Generator +Comment=File format backend for Okular +Comment[ar]=المنتهى الخلفي لـنسق الملف لأوكلار +Comment[ast]=Motor de formatos de ficheros pa Okular +Comment[bg]=Ядро на Okular за файлови формати +Comment[bs]=Pozadina formata datoteke za Okular +Comment[ca]=Dorsal de format de fitxer per a l'Okular +Comment[ca@valencia]=Dorsal de format de fitxer per a l'Okular +Comment[cs]=Podpůrná vrstva formátu souboru pro Okular +Comment[da]=Filformatmotor til Okular +Comment[de]=Dateiformat-Modul für Okular +Comment[el]=Σύστημα υποστήριξης μορφής αρχείων για το Okular +Comment[en_GB]=File format backend for Okular +Comment[es]=Motor de formatos de archivos para Okular +Comment[et]=Okulari failivormingu taustaprogramm +Comment[eu]=Okular-en fitxategi-formatuaren motorra +Comment[fi]=Tiedostomuototaustaosa Okularille +Comment[fr]=Moteur de formats de fichiers pour Okular +Comment[ga]=Inneall formáide comhaid le haghaidh Okular +Comment[gl]=Infraestrutura dun formato de ficheiro para Okular +Comment[hne]=ओकुलर बर फाइल प्रारूप बैकएंड +Comment[hr]=Podrška za oblike datoteka za Okular +Comment[hu]=Fájlformátumkezelő az Okularhoz +Comment[ia]=Retro-administration de formato de file pro Okular +Comment[is]=Skráasniðsstuðningur fyrir Okular +Comment[it]=Backend per i formati di file di Okular +Comment[ja]=Okular のファイル形式バックエンド +Comment[kk]=Okular-дың файл пішім тетігі +Comment[km]=ផ្នែក​ខាង​ក្រោយ​ទ្រង់ទ្រាយ​ឯកសារ​សម្រាប់ Okular +Comment[ko]=Okular용 파일 형식 백엔드 +Comment[ku]=Binesazî ya teşeya pelê Okular +Comment[lt]=Okular failų formato programinė sąsaja +Comment[lv]=Failu formātu Okular aizmugure +Comment[mr]=ओक्युलर करिता फाईल प्रकार बॅकएन्ड +Comment[nb]=Filformatmotor for Okular +Comment[nds]=Dateiformaat-Hülpprogramm för Okular +Comment[nl]=Bestandsformaat-backend voor Okular +Comment[nn]=Filformatmotor for Okular +Comment[pa]=ਓਕੁਲਾਰ ਲਈ ਫਾਇਲ ਫਾਰਮੈਟ ਬੈਕਐਂਡ +Comment[pl]=Obsługa formatu pliku dla Okulara +Comment[pt]=Infra-estrutura do formato de ficheiros para o Okular +Comment[pt_BR]=Infraestrutura de formato de arquivo para o Okular +Comment[ro]=Platformă Okular pentru formate de fișiere +Comment[ru]=Модуль поддержки формата для Okular +Comment[sk]=Backend formátu súborov pre Okular +Comment[sl]=Zaledje za vrste datotek za Okular +Comment[sq]=Mbështetës për formatet e skedarëve në Okular +Comment[sr]=Позадина формата фајла за Окулар +Comment[sr@ijekavian]=Позадина формата фајла за Окулар +Comment[sr@ijekavianlatin]=Pozadina formata fajla za Okular +Comment[sr@latin]=Pozadina formata fajla za Okular +Comment[sv]=Filformatgränssnitt för Okular +Comment[th]=แบ็กเอนด์รูปแบบแฟ้มสำหรับ Okular +Comment[tr]=Okular için dosya biçimi arka ucu +Comment[uk]=Сервер типів файлів для okular +Comment[x-test]=xxFile format backend for Okularxx +Comment[zh_CN]=Okular 文件格式后端 +Comment[zh_TW]=Okular 檔案格式後端 +# Priority of the plugin. 0 - disabled +[PropertyDef::X-KDE-Priority] +Type=int + +# Version of the API. +[PropertyDef::X-KDE-okularAPIVersion] +Type=int + +# Has configuration option +[PropertyDef::X-KDE-okularHasInternalSettings] +Type=bool + diff --git a/okular/core/okular_export.h b/okular/core/okular_export.h new file mode 100644 index 00000000..d517915c --- /dev/null +++ b/okular/core/okular_export.h @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_EXPORT_H +#define OKULAR_EXPORT_H + +/* needed for KDE_EXPORT macros */ +#include + + +#if defined _WIN32 || defined _WIN64 +#ifndef OKULAR_EXPORT +# ifdef MAKE_OKULARCORE_LIB +# define OKULAR_EXPORT KDE_EXPORT +# else +# define OKULAR_EXPORT KDE_IMPORT +# endif +#endif + +#else /* UNIX*/ + + +/* export statements for unix */ +#define OKULAR_EXPORT KDE_EXPORT +#endif + +#endif diff --git a/okular/core/page.cpp b/okular/core/page.cpp new file mode 100644 index 00000000..b48f2106 --- /dev/null +++ b/okular/core/page.cpp @@ -0,0 +1,1018 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "page.h" +#include "page_p.h" + +// qt/kde includes +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// local includes +#include "action.h" +#include "annotations.h" +#include "annotations_p.h" +#include "area.h" +#include "debug_p.h" +#include "document.h" +#include "document_p.h" +#include "form.h" +#include "form_p.h" +#include "observer.h" +#include "pagecontroller_p.h" +#include "pagesize.h" +#include "pagetransition.h" +#include "rotationjob_p.h" +#include "textpage.h" +#include "textpage_p.h" +#include "tile.h" +#include "tilesmanager_p.h" +#include "utils_p.h" + +#include + +#ifdef PAGE_PROFILE +#include +#endif + +using namespace Okular; + +static const double distanceConsideredEqual = 25; // 5px + +static void deleteObjectRects( QLinkedList< ObjectRect * >& rects, const QSet& which ) +{ + QLinkedList< ObjectRect * >::iterator it = rects.begin(), end = rects.end(); + for ( ; it != end; ) + if ( which.contains( (*it)->objectType() ) ) + { + delete *it; + it = rects.erase( it ); + } + else + ++it; +} + +PagePrivate::PagePrivate( Page *page, uint n, double w, double h, Rotation o ) + : m_page( page ), m_number( n ), m_orientation( o ), + m_width( w ), m_height( h ), m_doc( 0 ), m_boundingBox( 0, 0, 1, 1 ), + m_rotation( Rotation0 ), + m_text( 0 ), m_transition( 0 ), m_textSelections( 0 ), + m_openingAction( 0 ), m_closingAction( 0 ), m_duration( -1 ), + m_isBoundingBoxKnown( false ) +{ + // avoid Division-By-Zero problems in the program + if ( m_width <= 0 ) + m_width = 1; + + if ( m_height <= 0 ) + m_height = 1; +} + +PagePrivate::~PagePrivate() +{ + qDeleteAll( formfields ); + delete m_openingAction; + delete m_closingAction; + delete m_text; + delete m_transition; +} + + +void PagePrivate::imageRotationDone( RotationJob * job ) +{ + TilesManager *tm = tilesManager( job->observer() ); + if ( tm ) + { + QPixmap *pixmap = new QPixmap( QPixmap::fromImage( job->image() ) ); + tm->setPixmap( pixmap, job->rect() ); + delete pixmap; + return; + } + + QMap< DocumentObserver*, PixmapObject >::iterator it = m_pixmaps.find( job->observer() ); + if ( it != m_pixmaps.end() ) + { + PixmapObject &object = it.value(); + (*object.m_pixmap) = QPixmap::fromImage( job->image() ); + object.m_rotation = job->rotation(); + } else { + PixmapObject object; + object.m_pixmap = new QPixmap( QPixmap::fromImage( job->image() ) ); + object.m_rotation = job->rotation(); + + m_pixmaps.insert( job->observer(), object ); + } +} + +QTransform PagePrivate::rotationMatrix() const +{ + return Okular::buildRotationMatrix( m_rotation ); +} + +/** class Page **/ + +Page::Page( uint page, double w, double h, Rotation o ) + : d( new PagePrivate( this, page, w, h, o ) ) +{ +} + +Page::~Page() +{ + deletePixmaps(); + deleteRects(); + d->deleteHighlights(); + deleteAnnotations(); + d->deleteTextSelections(); + deleteSourceReferences(); + + delete d; +} + +int Page::number() const +{ + return d->m_number; +} + +Rotation Page::orientation() const +{ + return d->m_orientation; +} + +Rotation Page::rotation() const +{ + return d->m_rotation; +} + +Rotation Page::totalOrientation() const +{ + return (Rotation)( ( (int)d->m_orientation + (int)d->m_rotation ) % 4 ); +} + +double Page::width() const +{ + return d->m_width; +} + +double Page::height() const +{ + return d->m_height; +} + +double Page::ratio() const +{ + return d->m_height / d->m_width; +} + +NormalizedRect Page::boundingBox() const +{ + return d->m_boundingBox; +} + +bool Page::isBoundingBoxKnown() const +{ + return d->m_isBoundingBoxKnown; +} + +void Page::setBoundingBox( const NormalizedRect& bbox ) +{ + if ( d->m_isBoundingBoxKnown && d->m_boundingBox == bbox ) + return; + + // Allow tiny rounding errors (happens during rotation) + static const double epsilon = 0.00001; + Q_ASSERT( bbox.left >= -epsilon && bbox.top >= -epsilon && bbox.right <= 1 + epsilon && bbox.bottom <= 1 + epsilon ); + + d->m_boundingBox = bbox & NormalizedRect( 0., 0., 1., 1. ); + d->m_isBoundingBoxKnown = true; +} + +bool Page::hasPixmap( DocumentObserver *observer, int width, int height, const NormalizedRect &rect ) const +{ + TilesManager *tm = d->tilesManager( observer ); + if ( tm ) + { + if ( width != tm->width() || height != tm->height() ) + { + tm->setSize( width, height ); + return false; + } + + return tm->hasPixmap( rect ); + } + + QMap< DocumentObserver*, PagePrivate::PixmapObject >::const_iterator it = d->m_pixmaps.constFind( observer ); + if ( it == d->m_pixmaps.constEnd() ) + return false; + + if ( width == -1 || height == -1 ) + return true; + + const QPixmap *pixmap = it.value().m_pixmap; + + return (pixmap->width() == width && pixmap->height() == height); +} + +bool Page::hasTextPage() const +{ + return d->m_text != 0; +} + +RegularAreaRect * Page::wordAt( const NormalizedPoint &p, QString *word ) const +{ + if ( d->m_text ) + return d->m_text->wordAt( p, word ); + + return 0; +} + +RegularAreaRect * Page::textArea ( TextSelection * selection ) const +{ + if ( d->m_text ) + return d->m_text->textArea( selection ); + + return 0; +} + +bool Page::hasObjectRect( double x, double y, double xScale, double yScale ) const +{ + if ( m_rects.isEmpty() ) + return false; + + QLinkedList< ObjectRect * >::const_iterator it = m_rects.begin(), end = m_rects.end(); + for ( ; it != end; ++it ) + if ( (*it)->distanceSqr( x, y, xScale, yScale ) < distanceConsideredEqual ) + return true; + + return false; +} + +bool Page::hasHighlights( int s_id ) const +{ + // simple case: have no highlights + if ( m_highlights.isEmpty() ) + return false; + // simple case: we have highlights and no id to match + if ( s_id == -1 ) + return true; + // iterate on the highlights list to find an entry by id + QLinkedList< HighlightAreaRect * >::const_iterator it = m_highlights.begin(), end = m_highlights.end(); + for ( ; it != end; ++it ) + if ( (*it)->s_id == s_id ) + return true; + return false; +} + +bool Page::hasTransition() const +{ + return d->m_transition != 0; +} + +bool Page::hasAnnotations() const +{ + return !m_annotations.isEmpty(); +} + +RegularAreaRect * Page::findText( int id, const QString & text, SearchDirection direction, + Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect ) const +{ + RegularAreaRect* rect = 0; + if ( text.isEmpty() || !d->m_text ) + return rect; + + rect = d->m_text->findText( id, text, direction, caseSensitivity, lastRect ); + return rect; +} + +QString Page::text( const RegularAreaRect * area ) const +{ + return text( area, TextPage::AnyPixelTextAreaInclusionBehaviour ); +} + +QString Page::text( const RegularAreaRect * area, TextPage::TextAreaInclusionBehaviour b ) const +{ + QString ret; + + if ( !d->m_text ) + return ret; + + if ( area ) + { + RegularAreaRect rotatedArea = *area; + rotatedArea.transform( d->rotationMatrix().inverted() ); + + ret = d->m_text->text( &rotatedArea, b ); + } + else + ret = d->m_text->text( 0, b ); + + return ret; +} + +TextEntity::List Page::words( const RegularAreaRect * area, TextPage::TextAreaInclusionBehaviour b ) const +{ + TextEntity::List ret; + + if ( !d->m_text ) + return ret; + + if ( area ) + { + RegularAreaRect rotatedArea = *area; + rotatedArea.transform( d->rotationMatrix().inverted() ); + + ret = d->m_text->words( &rotatedArea, b ); + } + else + ret = d->m_text->words( 0, b ); + + for (int i = 0; i < ret.length(); ++i) + { + const TextEntity * orig = ret[i]; + ret[i] = new TextEntity( orig->text(), new Okular::NormalizedRect(orig->transformedArea ( d->rotationMatrix() )) ); + delete orig; + } + + return ret; +} + +void PagePrivate::rotateAt( Rotation orientation ) +{ + if ( orientation == m_rotation ) + return; + + deleteHighlights(); + deleteTextSelections(); + + if ( ( (int)m_orientation + (int)m_rotation ) % 2 != ( (int)m_orientation + (int)orientation ) % 2 ) + qSwap( m_width, m_height ); + + Rotation oldRotation = m_rotation; + m_rotation = orientation; + + /** + * Rotate the images of the page. + */ + QMapIterator< DocumentObserver*, PagePrivate::PixmapObject > it( m_pixmaps ); + while ( it.hasNext() ) { + it.next(); + + const PagePrivate::PixmapObject &object = it.value(); + + RotationJob *job = new RotationJob( object.m_pixmap->toImage(), object.m_rotation, m_rotation, it.key() ); + job->setPage( this ); + m_doc->m_pageController->addRotationJob(job); + } + + /** + * Rotate tiles manager + */ + QMapIterator i(m_tilesManagers); + while (i.hasNext()) { + i.next(); + + TilesManager *tm = i.value(); + if ( tm ) + tm->setRotation( m_rotation ); + } + + /** + * Rotate the object rects on the page. + */ + const QTransform matrix = rotationMatrix(); + QLinkedList< ObjectRect * >::const_iterator objectIt = m_page->m_rects.begin(), end = m_page->m_rects.end(); + for ( ; objectIt != end; ++objectIt ) + (*objectIt)->transform( matrix ); + + QLinkedList< HighlightAreaRect* >::const_iterator hlIt = m_page->m_highlights.begin(), hlItEnd = m_page->m_highlights.end(); + for ( ; hlIt != hlItEnd; ++hlIt ) + { + (*hlIt)->transform( RotationJob::rotationMatrix( oldRotation, m_rotation ) ); + } +} + +void PagePrivate::changeSize( const PageSize &size ) +{ + if ( size.isNull() || ( size.width() == m_width && size.height() == m_height ) ) + return; + + m_page->deletePixmaps(); +// deleteHighlights(); +// deleteTextSelections(); + + m_width = size.width(); + m_height = size.height(); + if ( m_rotation % 2 ) + qSwap( m_width, m_height ); +} + +const ObjectRect * Page::objectRect( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale ) const +{ + // Walk list in reverse order so that annotations in the foreground are preferred + QLinkedListIterator< ObjectRect * > it( m_rects ); + it.toBack(); + while ( it.hasPrevious() ) + { + const ObjectRect *objrect = it.previous(); + if ( ( objrect->objectType() == type ) && objrect->distanceSqr( x, y, xScale, yScale ) < distanceConsideredEqual ) + return objrect; + } + + return 0; +} + +QLinkedList< const ObjectRect * > Page::objectRects( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale ) const +{ + QLinkedList< const ObjectRect * > result; + + QLinkedListIterator< ObjectRect * > it( m_rects ); + it.toBack(); + while ( it.hasPrevious() ) + { + const ObjectRect *objrect = it.previous(); + if ( ( objrect->objectType() == type ) && objrect->distanceSqr( x, y, xScale, yScale ) < distanceConsideredEqual ) + result.append( objrect ); + } + + return result; +} + + +const ObjectRect* Page::nearestObjectRect( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale, double * distance ) const +{ + ObjectRect * res = 0; + double minDistance = std::numeric_limits::max(); + + QLinkedList< ObjectRect * >::const_iterator it = m_rects.constBegin(), end = m_rects.constEnd(); + for ( ; it != end; ++it ) + { + if ( (*it)->objectType() == type ) + { + double d = (*it)->distanceSqr( x, y, xScale, yScale ); + if ( d < minDistance ) + { + res = (*it); + minDistance = d; + } + } + } + + if ( distance ) + *distance = minDistance; + return res; +} + +const PageTransition * Page::transition() const +{ + return d->m_transition; +} + +QLinkedList< Annotation* > Page::annotations() const +{ + return m_annotations; +} + +const Action * Page::pageAction( PageAction action ) const +{ + switch ( action ) + { + case Page::Opening: + return d->m_openingAction; + break; + case Page::Closing: + return d->m_closingAction; + break; + } + + return 0; +} + +QLinkedList< FormField * > Page::formFields() const +{ + return d->formfields; +} + +void Page::setPixmap( DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect ) +{ + if ( d->m_rotation == Rotation0 ) { + TilesManager *tm = d->tilesManager( observer ); + if ( tm ) + { + tm->setPixmap( pixmap, rect ); + delete pixmap; + return; + } + + QMap< DocumentObserver*, PagePrivate::PixmapObject >::iterator it = d->m_pixmaps.find( observer ); + if ( it != d->m_pixmaps.end() ) + { + delete it.value().m_pixmap; + } + else + { + it = d->m_pixmaps.insert( observer, PagePrivate::PixmapObject() ); + } + it.value().m_pixmap = pixmap; + it.value().m_rotation = d->m_rotation; + } else { + RotationJob *job = new RotationJob( pixmap->toImage(), Rotation0, d->m_rotation, observer ); + job->setPage( d ); + job->setRect( TilesManager::toRotatedRect( rect, d->m_rotation ) ); + d->m_doc->m_pageController->addRotationJob(job); + + delete pixmap; + } +} + +void Page::setTextPage( TextPage * textPage ) +{ + delete d->m_text; + + d->m_text = textPage; + if ( d->m_text ) + { + d->m_text->d->m_page = d; + /** + * Correct text order for before text selection + */ + d->m_text->d->correctTextOrder(); + } +} + +void Page::setObjectRects( const QLinkedList< ObjectRect * > & rects ) +{ + QSet which; + which << ObjectRect::Action << ObjectRect::Image; + deleteObjectRects( m_rects, which ); + + /** + * Rotate the object rects of the page. + */ + const QTransform matrix = d->rotationMatrix(); + + QLinkedList< ObjectRect * >::const_iterator objectIt = rects.begin(), end = rects.end(); + for ( ; objectIt != end; ++objectIt ) + (*objectIt)->transform( matrix ); + + m_rects << rects; +} + +void PagePrivate::setHighlight( int s_id, RegularAreaRect *rect, const QColor & color ) +{ + HighlightAreaRect * hr = new HighlightAreaRect(rect); + hr->s_id = s_id; + hr->color = color; + + m_page->m_highlights.append( hr ); +} + +void PagePrivate::setTextSelections( RegularAreaRect *r, const QColor & color ) +{ + deleteTextSelections(); + if ( r ) + { + HighlightAreaRect * hr = new HighlightAreaRect( r ); + hr->s_id = -1; + hr->color = color; + m_textSelections = hr; + delete r; + } +} + +void Page::setSourceReferences( const QLinkedList< SourceRefObjectRect * > & refRects ) +{ + deleteSourceReferences(); + foreach( SourceRefObjectRect * rect, refRects ) + m_rects << rect; +} + +void Page::setDuration( double seconds ) +{ + d->m_duration = seconds; +} + +double Page::duration() const +{ + return d->m_duration; +} + +void Page::setLabel( const QString& label ) +{ + d->m_label = label; +} + +QString Page::label() const +{ + return d->m_label; +} + +const RegularAreaRect * Page::textSelection() const +{ + return d->m_textSelections; +} + +QColor Page::textSelectionColor() const +{ + return d->m_textSelections ? d->m_textSelections->color : QColor(); +} + +void Page::addAnnotation( Annotation * annotation ) +{ + // Generate uniqueName: okular-{UUID} + if(annotation->uniqueName().isEmpty()) + { + QString uniqueName = "okular-" + QUuid::createUuid().toString(); + annotation->setUniqueName( uniqueName ); + } + annotation->d_ptr->m_page = d; + m_annotations.append( annotation ); + + AnnotationObjectRect *rect = new AnnotationObjectRect( annotation ); + + // Rotate the annotation on the page. + const QTransform matrix = d->rotationMatrix(); + annotation->d_ptr->annotationTransform( matrix ); + + m_rects.append( rect ); +} + +bool Page::removeAnnotation( Annotation * annotation ) +{ + if ( !d->m_doc->m_parent->canRemovePageAnnotation(annotation) ) + return false; + + QLinkedList< Annotation * >::iterator aIt = m_annotations.begin(), aEnd = m_annotations.end(); + for ( ; aIt != aEnd; ++aIt ) + { + if((*aIt) && (*aIt)->uniqueName()==annotation->uniqueName()) + { + int rectfound = false; + QLinkedList< ObjectRect * >::iterator it = m_rects.begin(), end = m_rects.end(); + for ( ; it != end && !rectfound; ++it ) + if ( ( (*it)->objectType() == ObjectRect::OAnnotation ) && ( (*it)->object() == (*aIt) ) ) + { + delete *it; + it = m_rects.erase( it ); + rectfound = true; + } + kDebug(OkularDebug) << "removed annotation:" << annotation->uniqueName(); + annotation->d_ptr->m_page = 0; + m_annotations.erase( aIt ); + break; + } + } + + return true; +} + +void Page::setTransition( PageTransition * transition ) +{ + delete d->m_transition; + d->m_transition = transition; +} + +void Page::setPageAction( PageAction action, Action * link ) +{ + switch ( action ) + { + case Page::Opening: + delete d->m_openingAction; + d->m_openingAction = link; + break; + case Page::Closing: + delete d->m_closingAction; + d->m_closingAction = link; + break; + } +} + +void Page::setFormFields( const QLinkedList< FormField * >& fields ) +{ + qDeleteAll( d->formfields ); + d->formfields = fields; + QLinkedList< FormField * >::const_iterator it = d->formfields.begin(), itEnd = d->formfields.end(); + for ( ; it != itEnd; ++it ) + { + (*it)->d_ptr->setDefault(); + } +} + +void Page::deletePixmap( DocumentObserver *observer ) +{ + TilesManager *tm = d->tilesManager( observer ); + if ( tm ) + { + delete tm; + d->m_tilesManagers.remove(observer); + } + else + { + PagePrivate::PixmapObject object = d->m_pixmaps.take( observer ); + delete object.m_pixmap; + } +} + +void Page::deletePixmaps() +{ + QMapIterator< DocumentObserver*, PagePrivate::PixmapObject > it( d->m_pixmaps ); + while ( it.hasNext() ) { + it.next(); + delete it.value().m_pixmap; + } + + d->m_pixmaps.clear(); + + qDeleteAll(d->m_tilesManagers); + d->m_tilesManagers.clear(); +} + +void Page::deleteRects() +{ + // delete ObjectRects of type Link and Image + QSet which; + which << ObjectRect::Action << ObjectRect::Image; + deleteObjectRects( m_rects, which ); +} + +void PagePrivate::deleteHighlights( int s_id ) +{ + // delete highlights by ID + QLinkedList< HighlightAreaRect* >::iterator it = m_page->m_highlights.begin(), end = m_page->m_highlights.end(); + while ( it != end ) + { + HighlightAreaRect* highlight = *it; + if ( s_id == -1 || highlight->s_id == s_id ) + { + it = m_page->m_highlights.erase( it ); + delete highlight; + } + else + ++it; + } +} + +void PagePrivate::deleteTextSelections() +{ + delete m_textSelections; + m_textSelections = 0; +} + +void Page::deleteSourceReferences() +{ + deleteObjectRects( m_rects, QSet() << ObjectRect::SourceRef ); +} + +void Page::deleteAnnotations() +{ + // delete ObjectRects of type Annotation + deleteObjectRects( m_rects, QSet() << ObjectRect::OAnnotation ); + // delete all stored annotations + QLinkedList< Annotation * >::const_iterator aIt = m_annotations.begin(), aEnd = m_annotations.end(); + for ( ; aIt != aEnd; ++aIt ) + delete *aIt; + m_annotations.clear(); +} + +void PagePrivate::restoreLocalContents( const QDomNode & pageNode ) +{ + // iterate over all chilren (annotationList, ...) + QDomNode childNode = pageNode.firstChild(); + while ( childNode.isElement() ) + { + QDomElement childElement = childNode.toElement(); + childNode = childNode.nextSibling(); + + // parse annotationList child element + if ( childElement.tagName() == "annotationList" ) + { +#ifdef PAGE_PROFILE + QTime time; + time.start(); +#endif + // Clone annotationList as root node in restoredLocalAnnotationList + const QDomNode clonedNode = restoredLocalAnnotationList.importNode( childElement, true ); + restoredLocalAnnotationList.appendChild( clonedNode ); + + // iterate over all annotations + QDomNode annotationNode = childElement.firstChild(); + while( annotationNode.isElement() ) + { + // get annotation element and advance to next annot + QDomElement annotElement = annotationNode.toElement(); + annotationNode = annotationNode.nextSibling(); + + // get annotation from the dom element + Annotation * annotation = AnnotationUtils::createAnnotation( annotElement ); + + // append annotation to the list or show warning + if ( annotation ) + { + m_doc->performAddPageAnnotation(m_number, annotation); + kDebug(OkularDebug) << "restored annot:" << annotation->uniqueName(); + } + else + kWarning(OkularDebug).nospace() << "page (" << m_number << "): can't restore an annotation from XML."; + } +#ifdef PAGE_PROFILE + kDebug(OkularDebug).nospace() << "annots: XML Load time: " << time.elapsed() << "ms"; +#endif + } + // parse formList child element + else if ( childElement.tagName() == "forms" ) + { + if ( formfields.isEmpty() ) + continue; + + QHash hashedforms; + QLinkedList< FormField * >::const_iterator fIt = formfields.begin(), fItEnd = formfields.end(); + for ( ; fIt != fItEnd; ++fIt ) + { + hashedforms[(*fIt)->id()] = (*fIt); + } + + // iterate over all forms + QDomNode formsNode = childElement.firstChild(); + while( formsNode.isElement() ) + { + // get annotation element and advance to next annot + QDomElement formElement = formsNode.toElement(); + formsNode = formsNode.nextSibling(); + + if ( formElement.tagName() != "form" ) + continue; + + bool ok = true; + int index = formElement.attribute( "id" ).toInt( &ok ); + if ( !ok ) + continue; + + QHash::const_iterator wantedIt = hashedforms.constFind( index ); + if ( wantedIt == hashedforms.constEnd() ) + continue; + + QString value = formElement.attribute( "value" ); + (*wantedIt)->d_ptr->setValue( value ); + } + } + } +} + +void PagePrivate::saveLocalContents( QDomNode & parentNode, QDomDocument & document, PageItems what ) const +{ + // create the page node and set the 'number' attribute + QDomElement pageElement = document.createElement( "page" ); + pageElement.setAttribute( "number", m_number ); + +#if 0 + // add bookmark info if is bookmarked + if ( d->m_bookmarked ) + { + // create the pageElement's 'bookmark' child + QDomElement bookmarkElement = document.createElement( "bookmark" ); + pageElement.appendChild( bookmarkElement ); + + // add attributes to the element + //bookmarkElement.setAttribute( "name", bookmark name ); + } +#endif + + // add annotations info if has got any + if ( ( what & AnnotationPageItems ) && ( what & OriginalAnnotationPageItems ) ) + { + const QDomElement savedDocRoot = restoredLocalAnnotationList.documentElement(); + if ( !savedDocRoot.isNull() ) + { + // Import and append node in target document + const QDomNode importedNode = document.importNode( savedDocRoot, true ); + pageElement.appendChild( importedNode ); + } + } + else if ( ( what & AnnotationPageItems ) && !m_page->m_annotations.isEmpty() ) + { + // create the annotationList + QDomElement annotListElement = document.createElement( "annotationList" ); + + // add every annotation to the annotationList + QLinkedList< Annotation * >::const_iterator aIt = m_page->m_annotations.constBegin(), aEnd = m_page->m_annotations.constEnd(); + for ( ; aIt != aEnd; ++aIt ) + { + // get annotation + const Annotation * a = *aIt; + // only save okular annotations (not the embedded in file ones) + if ( !(a->flags() & Annotation::External) ) + { + // append an filled-up element called 'annotation' to the list + QDomElement annElement = document.createElement( "annotation" ); + AnnotationUtils::storeAnnotation( a, annElement, document ); + annotListElement.appendChild( annElement ); + kDebug(OkularDebug) << "save annotation:" << a->uniqueName(); + } + } + + // append the annotationList element if annotations have been set + if ( annotListElement.hasChildNodes() ) + pageElement.appendChild( annotListElement ); + } + + // add forms info if has got any + if ( ( what & FormFieldPageItems ) && !formfields.isEmpty() ) + { + // create the formList + QDomElement formListElement = document.createElement( "forms" ); + + // add every form data to the formList + QLinkedList< FormField * >::const_iterator fIt = formfields.constBegin(), fItEnd = formfields.constEnd(); + for ( ; fIt != fItEnd; ++fIt ) + { + // get the form field + const FormField * f = *fIt; + + QString newvalue = f->d_ptr->value(); + if ( f->d_ptr->m_default == newvalue ) + continue; + + // append an filled-up element called 'annotation' to the list + QDomElement formElement = document.createElement( "form" ); + formElement.setAttribute( "id", f->id() ); + formElement.setAttribute( "value", newvalue ); + formListElement.appendChild( formElement ); + } + + // append the annotationList element if annotations have been set + if ( formListElement.hasChildNodes() ) + pageElement.appendChild( formListElement ); + } + + // append the page element only if has children + if ( pageElement.hasChildNodes() ) + parentNode.appendChild( pageElement ); +} + +const QPixmap * Page::_o_nearestPixmap( DocumentObserver *observer, int w, int h ) const +{ + Q_UNUSED( h ) + + const QPixmap * pixmap = 0; + + // if a pixmap is present for given id, use it + QMap< DocumentObserver*, PagePrivate::PixmapObject >::const_iterator itPixmap = d->m_pixmaps.constFind( observer ); + if ( itPixmap != d->m_pixmaps.constEnd() ) + pixmap = itPixmap.value().m_pixmap; + // else find the closest match using pixmaps of other IDs (great optim!) + else if ( !d->m_pixmaps.isEmpty() ) + { + int minDistance = -1; + QMap< DocumentObserver*, PagePrivate::PixmapObject >::const_iterator it = d->m_pixmaps.constBegin(), end = d->m_pixmaps.constEnd(); + for ( ; it != end; ++it ) + { + int pixWidth = (*it).m_pixmap->width(), + distance = pixWidth > w ? pixWidth - w : w - pixWidth; + if ( minDistance == -1 || distance < minDistance ) + { + pixmap = (*it).m_pixmap; + minDistance = distance; + } + } + } + + return pixmap; +} + +bool Page::hasTilesManager( const DocumentObserver *observer ) const +{ + return d->tilesManager( observer ) != 0; +} + +QList Page::tilesAt( const DocumentObserver *observer, const NormalizedRect &rect ) const +{ + TilesManager *tm = d->m_tilesManagers.value( observer ); + if ( tm ) + return tm->tilesAt( rect, TilesManager::PixmapTile ); + else + return QList(); +} + +TilesManager *PagePrivate::tilesManager( const DocumentObserver *observer ) const +{ + return m_tilesManagers.value( observer ); +} + +void PagePrivate::setTilesManager( const DocumentObserver *observer, TilesManager *tm ) +{ + TilesManager *old = m_tilesManagers.value( observer ); + delete old; + + m_tilesManagers.insert(observer, tm); +} diff --git a/okular/core/page.h b/okular/core/page.h new file mode 100644 index 00000000..3d1817a3 --- /dev/null +++ b/okular/core/page.h @@ -0,0 +1,414 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_PAGE_H_ +#define _OKULAR_PAGE_H_ + +#include + +#include "okular_export.h" +#include "area.h" +#include "global.h" +#include "textpage.h" + +class QPixmap; + +class PagePainter; + +namespace Okular { + +class Annotation; +class Document; +class DocumentObserver; +class DocumentPrivate; +class FormField; +class PagePrivate; +class PageTransition; +class SourceReference; +class TextSelection; +class Tile; + +/** + * @short Collector for all the data belonging to a page. + * + * The Page class contains pixmaps (referenced using observers id as key), + * a search page (a class used internally for retrieving text), rect classes + * (that describe links or other active areas in the current page) and more. + * + * All coordinates are normalized to the page, so {x,y} are valid in [0,1] + * range as long as NormalizedRect components. + * + * Note: The class takes ownership of all objects. + */ +class OKULAR_EXPORT Page +{ + public: + /** + * An action to be executed when particular events happen. + */ + enum PageAction + { + Opening, ///< An action to be executed when the page is "opened". + Closing ///< An action to be executed when the page is "closed". + }; + + /** + * Creates a new page. + * + * @param number The number of the page in the document. + * @param width The width of the page. + * @param height The height of the page. + * @param orientation The orientation of the page + */ + Page( uint number, double width, double height, Rotation orientation ); + + /** + * Destroys the page. + */ + ~Page(); + + /** + * Returns the number of the page in the document. + */ + int number() const; + + /** + * Returns the orientation of the page as defined by the document. + */ + Rotation orientation() const; + + /** + * Returns the rotation of the page as defined by the user. + */ + Rotation rotation() const; + + /** + * Returns the total orientation which is the original orientation plus + * the user defined rotation. + */ + Rotation totalOrientation() const; + + /** + * Returns the width of the page. + */ + double width() const; + + /** + * Returns the height of the page. + */ + double height() const; + + /** + * Returns the ration (height / width) of the page. + */ + double ratio() const; + + /** + * Returns the bounding box of the page content in normalized [0,1] coordinates, + * in terms of the upright orientation (Rotation0). + * If it has not been computed yet, returns the full page (i.e., (0, 0, 1, 1)). + * Note that the bounding box may be null if the page is blank. + * + * @since 0.7 (KDE 4.1) + */ + NormalizedRect boundingBox() const; + + /** + * Returns whether the bounding box of the page has been computed. + * Note that even if the bounding box is computed, it may be null if the page is blank. + * + * @since 0.7 (KDE 4.1) + */ + bool isBoundingBoxKnown() const; + + /** + * Sets the bounding box of the page content in normalized [0,1] coordinates, + * in terms of the upright orientation (Rotation0). + * (This does not inform the document's observers, call Document::SetPageBoundingBox + * instead if you want that.) + * + * @since 0.7 (KDE 4.1) + */ + void setBoundingBox( const NormalizedRect& bbox ); + + /** + * Returns whether the page of size @p width x @p height has a @p pixmap + * in the region given by @p rect for the given @p observer + */ + bool hasPixmap( DocumentObserver *observer, int width = -1, int height = -1, const NormalizedRect &rect = NormalizedRect() ) const; + + /** + * Returns whether the page provides a text page (@ref TextPage). + */ + bool hasTextPage() const; + + /** + * Returns whether the page has an object rect which includes the point (@p x, @p y) + * at scale (@p xScale, @p yScale). + */ + bool hasObjectRect( double x, double y, double xScale, double yScale ) const; + + /** + * Returns whether the page provides highlighting for the observer with the + * given @p id. + */ + bool hasHighlights( int id = -1 ) const; + + /** + * Returns whether the page provides a transition effect. + */ + bool hasTransition() const; + + /** + * Returns whether the page provides annotations. + */ + bool hasAnnotations() const; + + /** + * Returns the bounding rect of the text which matches the following criteria + * or 0 if the search is not successful. + * + * @param id An unique id for this search. + * @param text The search text. + * @param direction The direction of the search (@ref SearchDirection) + * @param caseSensitivity If Qt::CaseSensitive, the search is case sensitive; otherwise + * the search is case insensitive. + * @param lastRect If 0 (default) the search starts at the beginning of the page, otherwise + * right/below the coordinates of the given rect. + */ + RegularAreaRect* findText( int id, const QString & text, SearchDirection direction, + Qt::CaseSensitivity caseSensitivity, const RegularAreaRect * lastRect=0) const; + + /** + * Returns the page text (or part of it). + * @see TextPage::text() + */ + QString text( const RegularAreaRect * rect = 0 ) const; + + /** + * Returns the page text (or part of it). + * @see TextPage::text() + * @since 0.10 (KDE 4.4) + */ + QString text( const RegularAreaRect * rect, TextPage::TextAreaInclusionBehaviour b ) const; + + /** + * Returns the page text (or part of it) including the bounding + * rectangles. Note that ownership of the contents of the returned + * list belongs to the caller. + * @see TextPage::words() + * @since 0.14 (KDE 4.8) + */ + TextEntity::List words( const RegularAreaRect * rect, TextPage::TextAreaInclusionBehaviour b ) const; + + /** + * Returns the area and text of the word at the given point + * Note that ownership of the returned area belongs to the caller. + * @see TextPage::wordAt() + * @since 0.15 (KDE 4.9) + */ + RegularAreaRect * wordAt( const NormalizedPoint &p, QString *word = 0 ) const; + + /** + * Returns the rectangular area of the given @p selection. + */ + RegularAreaRect * textArea( TextSelection *selection ) const; + + /** + * Returns the object rect of the given @p type which is at point (@p x, @p y) at scale (@p xScale, @p yScale). + */ + const ObjectRect * objectRect( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale ) const; + + /** + * Returns all object rects of the given @p type which are at point (@p x, @p y) at scale (@p xScale, @p yScale). + * @since 0.16 (KDE 4.10) + */ + QLinkedList< const ObjectRect * > objectRects( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale ) const; + + /** + * Returns the object rect of the given @p type which is nearest to the point (@p x, @p y) at scale (@p xScale, @p yScale). + * + * @since 0.8.2 (KDE 4.2.2) + */ + const ObjectRect * nearestObjectRect( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale, double * distance ) const; + + /** + * Returns the transition effect of the page or 0 if no transition + * effect is set (see hasTransition()). + */ + const PageTransition * transition() const; + + /** + * Returns the list of annotations of the page. + */ + QLinkedList< Annotation* > annotations() const; + + /** + * Returns the @ref Action object which is associated with the given page @p action + * or 0 if no page action is set. + */ + const Action * pageAction( PageAction action ) const; + + /** + * Returns the list of FormField of the page. + */ + QLinkedList< FormField * > formFields() const; + + /** + * Sets the region described by @p rect with @p pixmap for the + * given @p observer. + * If @p rect is not set (default) the @p pixmap is set to the entire + * page. + */ + void setPixmap( DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect = NormalizedRect() ); + + /** + * Sets the @p text page. + */ + void setTextPage( TextPage * text ); + + /** + * Sets the list of object @p rects of the page. + */ + void setObjectRects( const QLinkedList< ObjectRect * > & rects ); + + /** + * Sets the list of source reference objects @p rects. + */ + void setSourceReferences( const QLinkedList< SourceRefObjectRect * > & rects ); + + /** + * Sets the duration of the page to @p seconds when displayed in presentation mode. + * + * Setting a negative number disables the duration. + */ + void setDuration( double seconds ); + + /** + * Returns the duration in seconds of the page when displayed in presentation mode. + * + * A negative number means that no time is set. + */ + double duration() const; + + /** + * Sets the labels for the page to @p label . + */ + void setLabel( const QString& label ); + + /** + * Returns the label of the page, or a null string if not set. + */ + QString label() const; + + /** + * Returns the current text selection. + */ + const RegularAreaRect * textSelection() const; + + /** + * Returns the color of the current text selection, or an invalid color + * if no text selection has been set. + */ + QColor textSelectionColor() const; + + /** + * Adds a new @p annotation to the page. + */ + void addAnnotation( Annotation * annotation ); + + /** + * Removes the @p annotation from the page. + */ + bool removeAnnotation( Annotation * annotation ); + + /** + * Sets the page @p transition effect. + */ + void setTransition( PageTransition * transition ); + + /** + * Sets the @p link object for the given page @p action. + */ + void setPageAction( PageAction action, Action * link ); + + /** + * Sets @p fields as list of FormField of the page. + */ + void setFormFields( const QLinkedList< FormField * >& fields ); + + /** + * Deletes the pixmap for the given @p observer + */ + void deletePixmap( DocumentObserver *observer ); + + /** + * Deletes all pixmaps of the page. + */ + void deletePixmaps(); + + /** + * Deletes all object rects of the page. + */ + void deleteRects(); + + /** + * Deletes all source reference objects of the page. + */ + void deleteSourceReferences(); + + /** + * Deletes all annotations of the page. + */ + void deleteAnnotations(); + + /** + * Returns whether pixmaps for the tiled observer are handled by a + * tile manager. + * + * @since 0.19 (KDE 4.13) + */ + bool hasTilesManager( const DocumentObserver *observer ) const; + + /** + * Returns a list of all tiles intersecting with @p rect. + * + * The list contains only tiles with a pixmap + * + * @since 0.19 (KDE 4.13) + */ + QList tilesAt( const DocumentObserver *observer, const NormalizedRect &rect ) const; + + private: + PagePrivate* const d; + /// @cond PRIVATE + friend class PagePrivate; + friend class Document; + friend class DocumentPrivate; + friend class PixmapRequestPrivate; + + /** + * To improve performance PagePainter accesses the following + * member variables directly. + */ + friend class ::PagePainter; + /// @endcond + + const QPixmap * _o_nearestPixmap( DocumentObserver *, int, int ) const; + + QLinkedList< ObjectRect* > m_rects; + QLinkedList< HighlightAreaRect* > m_highlights; + QLinkedList< Annotation* > m_annotations; + + Q_DISABLE_COPY( Page ) +}; + +} + +#endif diff --git a/okular/core/page_p.h b/okular/core/page_p.h new file mode 100644 index 00000000..69a2c66c --- /dev/null +++ b/okular/core/page_p.h @@ -0,0 +1,151 @@ +/*************************************************************************** + * Copyright (C) 2004 by Enrico Ros * + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_PAGE_PRIVATE_H_ +#define _OKULAR_PAGE_PRIVATE_H_ + +// qt/kde includes +#include +#include +#include +#include +#include + +// local includes +#include "global.h" +#include "area.h" + +class QColor; + +namespace Okular { + +class Action; +class Annotation; +class DocumentObserver; +class DocumentPrivate; +class FormField; +class HighlightAreaRect; +class Page; +class PageSize; +class PageTransition; +class RotationJob; +class TextPage; +class TilesManager; + +enum PageItem +{ + None = 0, + AnnotationPageItems = 0x01, + FormFieldPageItems = 0x02, + AllPageItems = 0xff, + + /* If set along with AnnotationPageItems, tells saveLocalContents to save + * the original annotations (if any) instead of the modified ones */ + OriginalAnnotationPageItems = 0x100 +}; +Q_DECLARE_FLAGS(PageItems, PageItem) + +class PagePrivate +{ + public: + PagePrivate( Page *page, uint n, double w, double h, Rotation o ); + ~PagePrivate(); + + void imageRotationDone( RotationJob * job ); + QTransform rotationMatrix() const; + + /** + * Loads the local contents (e.g. annotations) of the page. + */ + void restoreLocalContents( const QDomNode & pageNode ); + + /** + * Saves the local contents (e.g. annotations) of the page. + */ + void saveLocalContents( QDomNode & parentNode, QDomDocument & document, PageItems what = AllPageItems ) const; + + /** + * Rotates the image and object rects of the page to the given @p orientation. + */ + void rotateAt( Rotation orientation ); + + /** + * Changes the size of the page to the given @p size. + * + * The @p size is meant to be referred to the page not rotated. + */ + void changeSize( const PageSize &size ); + + /** + * Sets the @p color and @p areas of text selections. + */ + void setTextSelections( RegularAreaRect *areas, const QColor & color ); + + /** + * Sets the @p color and @p area of the highlight for the observer with + * the given @p id. + */ + void setHighlight( int id, RegularAreaRect *area, const QColor & color ); + + /** + * Deletes all highlight objects for the observer with the given @p id. + */ + void deleteHighlights( int id = -1 ); + + /** + * Deletes all text selection objects of the page. + */ + void deleteTextSelections(); + + /** + * Get the tiles manager for the tiled @observer + */ + TilesManager *tilesManager( const DocumentObserver *observer ) const; + + /** + * Set the tiles manager for the tiled @observer + */ + void setTilesManager( const DocumentObserver *observer, TilesManager *tm ); + + class PixmapObject + { + public: + QPixmap *m_pixmap; + Rotation m_rotation; + }; + QMap< DocumentObserver*, PixmapObject > m_pixmaps; + QMap< const DocumentObserver*, TilesManager *> m_tilesManagers; + + Page *m_page; + int m_number; + Rotation m_orientation; + double m_width, m_height; + DocumentPrivate *m_doc; + NormalizedRect m_boundingBox; + Rotation m_rotation; + + TextPage * m_text; + PageTransition * m_transition; + HighlightAreaRect *m_textSelections; + QLinkedList< FormField * > formfields; + Action * m_openingAction; + Action * m_closingAction; + double m_duration; + QString m_label; + + bool m_isBoundingBoxKnown : 1; + QDomDocument restoredLocalAnnotationList; // ... +}; + +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(Okular::PageItems) + +#endif diff --git a/okular/core/pagecontroller.cpp b/okular/core/pagecontroller.cpp new file mode 100644 index 00000000..94756f0d --- /dev/null +++ b/okular/core/pagecontroller.cpp @@ -0,0 +1,52 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "pagecontroller_p.h" + +// qt/kde includes +#include +#include + +// local includes +#include "page_p.h" +#include "rotationjob_p.h" + +using namespace Okular; + +PageController::PageController() + : QObject() +{ +} + +PageController::~PageController() +{ +} + +void PageController::addRotationJob(RotationJob *job) +{ + connect( job, SIGNAL(done(ThreadWeaver::Job*)), + this, SLOT(imageRotationDone(ThreadWeaver::Job*)) ); + ThreadWeaver::Weaver::instance()->enqueue(job); +} + +void PageController::imageRotationDone(ThreadWeaver::Job *j) +{ + RotationJob *job = static_cast< RotationJob * >( j ); + + if ( job->page() ) + { + job->page()->imageRotationDone( job ); + + emit rotationFinished( job->page()->m_number, job->page()->m_page ); + } + + job->deleteLater(); +} + +#include "pagecontroller_p.moc" diff --git a/okular/core/pagecontroller_p.h b/okular/core/pagecontroller_p.h new file mode 100644 index 00000000..55fe7bf3 --- /dev/null +++ b/okular/core/pagecontroller_p.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_PAGECONTROLLER_P_H_ +#define _OKULAR_PAGECONTROLLER_P_H_ + +#include + +namespace ThreadWeaver { + class Job; +} + +namespace Okular { + +class Page; +class RotationJob; + +/* There is one PageController per document. It receives notifications of + * completed RotationJobs */ +class PageController : public QObject +{ + Q_OBJECT + + public: + PageController(); + ~PageController(); + + void addRotationJob( RotationJob *job ); + + signals: + void rotationFinished( int page, Okular::Page *okularPage ); + + private slots: + void imageRotationDone(ThreadWeaver::Job*); +}; + +} + +#endif diff --git a/okular/core/pagesize.cpp b/okular/core/pagesize.cpp new file mode 100644 index 00000000..484aba15 --- /dev/null +++ b/okular/core/pagesize.cpp @@ -0,0 +1,116 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +// local includes +#include "pagesize.h" + +using namespace Okular; + +class Okular::PageSizePrivate + : public QSharedData +{ + public: + PageSizePrivate() + : m_width( 0 ), m_height( 0 ) + { + } + + bool operator==( const PageSizePrivate &rhs ) const + { + return m_width == rhs.m_width && + m_height == rhs.m_height && + m_name == rhs.m_name; + } + + double m_width; + double m_height; + QString m_name; +}; + + +PageSize::PageSize() +{ +} + +PageSize::PageSize( double width, double height, const QString &name ) + : d( new PageSizePrivate ) +{ + d->m_width = width; + d->m_height = height; + d->m_name = name; +} + +PageSize::PageSize( const PageSize &pageSize ) + : d( pageSize.d ) +{ +} + +PageSize::~PageSize() +{ +} + +double PageSize::width() const +{ + if ( !d ) + return 0; + + return d->m_width; +} + +double PageSize::height() const +{ + if ( !d ) + return 0; + + return d->m_height; +} + +QString PageSize::name() const +{ + if ( !d ) + return QString(); + + return d->m_name; +} + +bool PageSize::isNull() const +{ + if ( !d ) + return true; + + return d->m_width == 0 && d->m_height == 0 && d->m_name.isEmpty(); +} + +bool PageSize::operator==( const PageSize &pageSize ) const +{ + // 1st: we're null: check if the other is null too + if ( !d ) + return !pageSize.d; + // 2nd: we're not null, return if the other is null + if ( !pageSize.d ) + return false; + + // 3rd: normal == check + return *d == *pageSize.d; +} + +bool PageSize::operator!=( const PageSize &pageSize ) const +{ + return !operator==( pageSize ); +} + +PageSize& PageSize::operator=( const PageSize &pageSize ) +{ + if ( this == &pageSize ) + return *this; + + d = pageSize.d; + return *this; +} + diff --git a/okular/core/pagesize.h b/okular/core/pagesize.h new file mode 100644 index 00000000..0619f06b --- /dev/null +++ b/okular/core/pagesize.h @@ -0,0 +1,83 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_PAGESIZE_H_ +#define _OKULAR_PAGESIZE_H_ + +#include +#include +#include + +#include "okular_export.h" + +namespace Okular { + +class PageSizePrivate; + +/** + * @short A small class that represents the size of a page. + */ +class OKULAR_EXPORT PageSize +{ + public: + typedef QList List; + + /** + * Construct a null page size. + * @see isNull() + */ + PageSize(); + /** + * Construct a page size with the specified @p width and @p height, + * having the ID @p name. + */ + PageSize( double width, double height, const QString &name ); + /** + * Copy constructor. + */ + PageSize( const PageSize &pageSize ); + ~PageSize(); + + /** + * Returns the width of the page size. + */ + double width() const; + /** + * Returns the height of the page size. + */ + double height() const; + /** + * Returns the ID of the page size. + */ + QString name() const; + + /** + * Whether the page size is null. + */ + bool isNull() const; + + PageSize& operator=( const PageSize &pageSize ); + + /** + * Comparison operator. + */ + bool operator==( const PageSize &pageSize ) const; + + bool operator!=( const PageSize &pageSize ) const; + + private: + /// @cond PRIVATE + friend class PageSizePrivate; + /// @endcond + QSharedDataPointer d; +}; + +} + +#endif diff --git a/okular/core/pagetransition.cpp b/okular/core/pagetransition.cpp new file mode 100644 index 00000000..d57106dd --- /dev/null +++ b/okular/core/pagetransition.cpp @@ -0,0 +1,134 @@ +/*************************************************************************** + * Copyright (C) 2005 by Tobias Koenig * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +// local includes +#include "pagetransition.h" + +using namespace Okular; + +/** class Okular::PageTransition **/ + +class PageTransition::Private +{ + public: + Private( Type type ) + : m_type( type ), + m_duration( 1 ), + m_alignment( Horizontal ), + m_direction( Inward ), + m_angle( 0 ), + m_scale( 1.0 ), + m_rectangular( false ) + { + } + + Type m_type; + int m_duration; + Alignment m_alignment; + Direction m_direction; + int m_angle; + double m_scale; + bool m_rectangular; +}; + +PageTransition::PageTransition( Type type ) + : d( new Private( type ) ) +{ +} + +PageTransition::PageTransition( const PageTransition &other ) + : d( new Private( *other.d ) ) +{ +} + +PageTransition& PageTransition::operator=( const PageTransition &other ) +{ + if ( this == &other ) + return *this; + + *d = *other.d; + + return *this; +} + +PageTransition::~PageTransition() +{ + delete d; +} + +PageTransition::Type PageTransition::type() const +{ + return d->m_type; +} + +int PageTransition::duration() const +{ + return d->m_duration; +} + +PageTransition::Alignment PageTransition::alignment() const +{ + return d->m_alignment; +} + +PageTransition::Direction PageTransition::direction() const +{ + return d->m_direction; +} + +int PageTransition::angle() const +{ + return d->m_angle; +} + +double PageTransition::scale() const +{ + return d->m_scale; +} + +bool PageTransition::isRectangular() const +{ + return d->m_rectangular; +} + +void PageTransition::setType( Type type ) +{ + d->m_type = type; +} + +void PageTransition::setDuration( int duration ) +{ + d->m_duration = duration; +} + +void PageTransition::setAlignment( Alignment alignment ) +{ + d->m_alignment = alignment; +} + +void PageTransition::setDirection( Direction direction ) +{ + d->m_direction = direction; +} + +void PageTransition::setAngle( int angle ) +{ + d->m_angle = angle; +} + +void PageTransition::setScale( double scale ) +{ + d->m_scale = scale; +} + +void PageTransition::setIsRectangular( bool rectangular ) +{ + d->m_rectangular = rectangular; +} + diff --git a/okular/core/pagetransition.h b/okular/core/pagetransition.h new file mode 100644 index 00000000..4b6e7f9f --- /dev/null +++ b/okular/core/pagetransition.h @@ -0,0 +1,158 @@ +/*************************************************************************** + * Copyright (C) 2005 by Tobias Koenig * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_PAGETRANSITION_H_ +#define _OKULAR_PAGETRANSITION_H_ + +#include "okular_export.h" + +namespace Okular { + +/** + * @short Information object for the transition effect of a page. + * + * This class encapsulates the information about the effect of + * a page transition. It supports mainly the transition effects as + * defined in PDF specification 1.6. + */ +class OKULAR_EXPORT PageTransition +{ + public: + /** + * Describes the type of transition effect. + */ + enum Type { + Replace, + Split, + Blinds, + Box, + Wipe, + Dissolve, + Glitter, + Fly, + Push, + Cover, + Uncover, + Fade + }; + + /** + * Describes the alignment that is applied to the @ref Type + * of transition effect. + */ + enum Alignment { + Horizontal, + Vertical + }; + + /** + * Describes the direction that is applied to the @ref Type + * of transition effect. + */ + enum Direction { + Inward, + Outward + }; + + /** + * Creates a new page transition of the given @p type. + * + * If no type is given, the normal @ref Replace transition is used. + */ + PageTransition( Type type = Replace ); + + /** + * Creates a new page transition from an @p other. + */ + PageTransition( const PageTransition &other ); + PageTransition& operator=( const PageTransition &other ); + + /** + * Destroys the page transition. + */ + ~PageTransition(); + + /** + * Returns the type of the transition. + */ + Type type() const; + + /** + * Returns the duration of the transition in seconds. + */ + int duration() const; + + /** + * Returns the alignment of the transition. + */ + Alignment alignment() const; + + /** + * Returns the direction of motion of the transition. + */ + Direction direction() const; + + /** + * Returns the angle of rotation of the transition. + */ + int angle() const; + + /** + * Returns the starting or ending scale (Only if type == 'Fly'). + */ + double scale() const; + + /** + * Returns true if the area to be flown is rectangular and opaque (Only if type == 'Fly'). + */ + bool isRectangular() const; + + /** + * Sets the @p type of the transition (@ref Type). + */ + void setType( Type type ); + + /** + * Sets the @p duration in seconds for the transition. + */ + void setDuration( int duration ); + + /** + * Sets the @p alignment of the transition (@ref Alignment). + */ + void setAlignment( Alignment alignment ); + + /** + * Sets the @p direction of the transition (@see Direction). + */ + void setDirection( Direction direction ); + + /** + * Sets the moving @p angle of the transition. + */ + void setAngle( int angle ); + + /** + * Sets the starting or ending scale of the transition (Only if type == 'Fly'). + */ + void setScale( double scale ); + + /** + * Sets whether the area to be flown is rectangular and opaque (Only if type == 'Fly'). + */ + void setIsRectangular( bool rectangular ); + + private: + class Private; + Private* const d; +}; + +} + +#endif diff --git a/okular/core/rotationjob.cpp b/okular/core/rotationjob.cpp new file mode 100644 index 00000000..3447fb48 --- /dev/null +++ b/okular/core/rotationjob.cpp @@ -0,0 +1,106 @@ +/*************************************************************************** + * Copyright (C) 2006 by Tobias Koenig * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "rotationjob_p.h" + +#include + +using namespace Okular; + +RotationJob::RotationJob( const QImage &image, Rotation oldRotation, Rotation newRotation, DocumentObserver *observer ) + : mImage( image ), mOldRotation( oldRotation ), mNewRotation( newRotation ), mObserver( observer ), m_pd( 0 ) + , mRect( NormalizedRect() ) +{ +} + +void RotationJob::setPage( PagePrivate * pd ) +{ + m_pd = pd; +} + +void RotationJob::setRect( const NormalizedRect &rect ) +{ + mRect = rect; +} + +QImage RotationJob::image() const +{ + return mRotatedImage; +} + +Rotation RotationJob::rotation() const +{ + return mNewRotation; +} + +DocumentObserver * RotationJob::observer() const +{ + return mObserver; +} + +PagePrivate * RotationJob::page() const +{ + return m_pd; +} + +NormalizedRect RotationJob::rect() const +{ + return mRect; +} + +void RotationJob::run() +{ + if ( mOldRotation == mNewRotation ) { + mRotatedImage = mImage; + return; + } + + QTransform matrix = rotationMatrix( mOldRotation, mNewRotation ); + + mRotatedImage = mImage.transformed( matrix ); +} + +QTransform RotationJob::rotationMatrix( Rotation from, Rotation to ) +{ + QTransform matrix; + + if ( from == Rotation0 ) { + if ( to == Rotation90 ) + matrix.rotate( 90 ); + else if ( to == Rotation180 ) + matrix.rotate( 180 ); + else if ( to == Rotation270 ) + matrix.rotate( 270 ); + } else if ( from == Rotation90 ) { + if ( to == Rotation180 ) + matrix.rotate( 90 ); + else if ( to == Rotation270 ) + matrix.rotate( 180 ); + else if ( to == Rotation0 ) + matrix.rotate( 270 ); + } else if ( from == Rotation180 ) { + if ( to == Rotation270 ) + matrix.rotate( 90 ); + else if ( to == Rotation0 ) + matrix.rotate( 180 ); + else if ( to == Rotation90 ) + matrix.rotate( 270 ); + } else if ( from == Rotation270 ) { + if ( to == Rotation0 ) + matrix.rotate( 90 ); + else if ( to == Rotation90 ) + matrix.rotate( 180 ); + else if ( to == Rotation180 ) + matrix.rotate( 270 ); + } + + return matrix; +} + +#include "rotationjob_p.moc" diff --git a/okular/core/rotationjob_p.h b/okular/core/rotationjob_p.h new file mode 100644 index 00000000..ee277931 --- /dev/null +++ b/okular/core/rotationjob_p.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2006 by Tobias Koenig * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_ROTATIONJOB_P_H_ +#define _OKULAR_ROTATIONJOB_P_H_ + +#include +#include + +#include + +#include "core/global.h" +#include "core/area.h" + +namespace Okular { + +class DocumentObserver; +class PagePrivate; + +class RotationJob : public ThreadWeaver::Job +{ + Q_OBJECT + + public: + RotationJob( const QImage &image, Rotation oldRotation, Rotation newRotation, DocumentObserver *observer ); + + void setPage( PagePrivate * pd ); + void setRect( const NormalizedRect &rect ); + + QImage image() const; + Rotation rotation() const; + DocumentObserver *observer() const; + PagePrivate * page() const; + NormalizedRect rect() const; + + static QTransform rotationMatrix( Rotation from, Rotation to ); + + protected: + virtual void run(); + + private: + const QImage mImage; + Rotation mOldRotation; + Rotation mNewRotation; + DocumentObserver *mObserver; + QImage mRotatedImage; + PagePrivate * m_pd; + NormalizedRect mRect; +}; + +} + +#endif diff --git a/okular/core/script/executor_kjs.cpp b/okular/core/script/executor_kjs.cpp new file mode 100644 index 00000000..071da7a3 --- /dev/null +++ b/okular/core/script/executor_kjs.cpp @@ -0,0 +1,113 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "executor_kjs_p.h" + +#include +#include +#include +#include + +#include + +#include "../debug_p.h" +#include "../document_p.h" + +#include "kjs_app_p.h" +#include "kjs_console_p.h" +#include "kjs_data_p.h" +#include "kjs_document_p.h" +#include "kjs_field_p.h" +#include "kjs_fullscreen_p.h" +#include "kjs_spell_p.h" +#include "kjs_util_p.h" + +using namespace Okular; + +class Okular::ExecutorKJSPrivate +{ + public: + ExecutorKJSPrivate( DocumentPrivate *doc ) + : m_doc( doc ) + { + initTypes(); + } + ~ExecutorKJSPrivate() + { + JSField::clearCachedFields(); + + delete m_interpreter; + } + + void initTypes(); + + DocumentPrivate *m_doc; + KJSInterpreter *m_interpreter; + KJSGlobalObject m_docObject; +}; + +void ExecutorKJSPrivate::initTypes() +{ + m_docObject = JSDocument::wrapDocument( m_doc ); + m_interpreter = new KJSInterpreter( m_docObject ); + + KJSContext *ctx = m_interpreter->globalContext(); + + JSApp::initType( ctx ); + JSFullscreen::initType( ctx ); + JSConsole::initType( ctx ); + JSData::initType( ctx ); + JSDocument::initType( ctx ); + JSField::initType( ctx ); + JSSpell::initType( ctx ); + JSUtil::initType( ctx ); + + m_docObject.setProperty( ctx, "app", JSApp::object( ctx, m_doc ) ); + m_docObject.setProperty( ctx, "console", JSConsole::object( ctx ) ); + m_docObject.setProperty( ctx, "Doc", m_docObject ); + m_docObject.setProperty( ctx, "spell", JSSpell::object( ctx ) ); + m_docObject.setProperty( ctx, "util", JSUtil::object( ctx ) ); +} + +ExecutorKJS::ExecutorKJS( DocumentPrivate *doc ) + : d( new ExecutorKJSPrivate( doc ) ) +{ +} + +ExecutorKJS::~ExecutorKJS() +{ + delete d; +} + +void ExecutorKJS::execute( const QString &script ) +{ +#if 0 + QString script2; + QString errMsg; + int errLine; + if ( !KJSInterpreter::normalizeCode( script, &script2, &errLine, &errMsg ) ) + { + kWarning(OkularDebug) << "Parse error during normalization!"; + script2 = script; + } +#endif + + KJSResult result = d->m_interpreter->evaluate( "okular.js", 1, + script, &d->m_docObject ); + KJSContext* ctx = d->m_interpreter->globalContext(); + if ( result.isException() || ctx->hasException() ) + { + kDebug(OkularDebug) << "JS exception" << result.errorMessage(); + } + else + { + kDebug(OkularDebug) << "result:" << result.value().toString( ctx ); + } +} diff --git a/okular/core/script/executor_kjs_p.h b/okular/core/script/executor_kjs_p.h new file mode 100644 index 00000000..12a6304d --- /dev/null +++ b/okular/core/script/executor_kjs_p.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_SCRIPT_EXECUTOR_KJS_P_H +#define OKULAR_SCRIPT_EXECUTOR_KJS_P_H + +class QString; + +namespace Okular { + +class DocumentPrivate; +class ExecutorKJSPrivate; + +class ExecutorKJS +{ + public: + ExecutorKJS( DocumentPrivate *doc ); + ~ExecutorKJS(); + + void execute( const QString &script ); + + private: + friend class ExecutorKJSPrivate; + ExecutorKJSPrivate* d; +}; + +} + +#endif diff --git a/okular/core/script/kjs_app.cpp b/okular/core/script/kjs_app.cpp new file mode 100644 index 00000000..ea793938 --- /dev/null +++ b/okular/core/script/kjs_app.cpp @@ -0,0 +1,232 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "kjs_app_p.h" + +#include +#include +#include + +#include + +#include +#include + +#include "../document_p.h" +#include "kjs_fullscreen_p.h" + +using namespace Okular; + +static KJSPrototype *g_appProto; + +// the acrobat version we fake +static const double fake_acroversion = 8.00; + +static const struct FakePluginInfo { + const char *name; + bool certified; + bool loaded; + const char *path; +} s_fake_plugins[] = { + { "Annots", true, true, "" }, + { "EFS", true, true, "" }, + { "EScript", true, true, "" }, + { "Forms", true, true, "" }, + { "ReadOutLoud", true, true, "" }, + { "WebLink", true, true, "" } +}; +static const int s_num_fake_plugins = sizeof( s_fake_plugins ) / sizeof( s_fake_plugins[0] ); + + +static KJSObject appGetFormsVersion( KJSContext *, void * ) +{ + // faking a bit... + return KJSNumber( fake_acroversion ); +} + +static KJSObject appGetLanguage( KJSContext *, void * ) +{ + QString lang; + QString country; + QString dummy; + KLocale::splitLocale( KGlobal::locale()->language(), + lang, country, dummy, dummy ); + QString acroLang = QString::fromLatin1( "ENU" ); + if ( lang == QLatin1String( "da" ) ) + acroLang = QString::fromLatin1( "DAN" ); // Danish + else if ( lang == QLatin1String( "de" ) ) + acroLang = QString::fromLatin1( "DEU" ); // German + else if ( lang == QLatin1String( "en" ) ) + acroLang = QString::fromLatin1( "ENU" ); // English + else if ( lang == QLatin1String( "es" ) ) + acroLang = QString::fromLatin1( "ESP" ); // Spanish + else if ( lang == QLatin1String( "fr" ) ) + acroLang = QString::fromLatin1( "FRA" ); // French + else if ( lang == QLatin1String( "it" ) ) + acroLang = QString::fromLatin1( "ITA" ); // Italian + else if ( lang == QLatin1String( "ko" ) ) + acroLang = QString::fromLatin1( "KOR" ); // Korean + else if ( lang == QLatin1String( "ja" ) ) + acroLang = QString::fromLatin1( "JPN" ); // Japanese + else if ( lang == QLatin1String( "nl" ) ) + acroLang = QString::fromLatin1( "NLD" ); // Dutch + else if ( lang == QLatin1String( "pt" ) && country == QLatin1String( "BR" ) ) + acroLang = QString::fromLatin1( "PTB" ); // Brazilian Portuguese + else if ( lang == QLatin1String( "fi" ) ) + acroLang = QString::fromLatin1( "SUO" ); // Finnish + else if ( lang == QLatin1String( "sv" ) ) + acroLang = QString::fromLatin1( "SVE" ); // Swedish + else if ( lang == QLatin1String( "zh" ) && country == QLatin1String( "CN" ) ) + acroLang = QString::fromLatin1( "CHS" ); // Chinese Simplified + else if ( lang == QLatin1String( "zh" ) && country == QLatin1String( "TW" ) ) + acroLang = QString::fromLatin1( "CHT" ); // Chinese Traditional + return KJSString( acroLang ); +} + +static KJSObject appGetNumPlugins( KJSContext *, void * ) +{ + return KJSNumber( s_num_fake_plugins ); +} + +static KJSObject appGetPlatform( KJSContext *, void * ) +{ +#if defined(Q_OS_WIN) + return KJSString( QString::fromLatin1( "WIN" ) ); +#elif defined(Q_OS_MAC) + return KJSString( QString::fromLatin1( "MAC" ) ); +#else + return KJSString( QString::fromLatin1( "UNIX" ) ); +#endif +} + +static KJSObject appGetPlugIns( KJSContext *context, void * ) +{ + KJSArray plugins( context, s_num_fake_plugins ); + for ( int i = 0; i < s_num_fake_plugins; ++i ) + { + const FakePluginInfo &info = s_fake_plugins[i]; + KJSObject plugin; + plugin.setProperty( context, "certified", info.certified ); + plugin.setProperty( context, "loaded", info.loaded ); + plugin.setProperty( context, "name", info.name ); + plugin.setProperty( context, "path", info.path ); + plugin.setProperty( context, "version", fake_acroversion ); + plugins.setProperty( context, QString::number( i ), plugin ); + } + return plugins; +} + +static KJSObject appGetPrintColorProfiles( KJSContext *context, void * ) +{ + return KJSArray( context, 0 ); +} + +static KJSObject appGetPrinterNames( KJSContext *context, void * ) +{ + return KJSArray( context, 0 ); +} + +static KJSObject appGetViewerType( KJSContext *, void * ) +{ + // faking a bit... + return KJSString( QString::fromLatin1( "Reader" ) ); +} + +static KJSObject appGetViewerVariation( KJSContext *, void * ) +{ + // faking a bit... + return KJSString( QString::fromLatin1( "Reader" ) ); +} + +static KJSObject appGetViewerVersion( KJSContext *, void * ) +{ + // faking a bit... + return KJSNumber( fake_acroversion ); +} + +static KJSObject appBeep( KJSContext *context, void *, + const KJSArguments &arguments ) +{ + if ( arguments.count() < 1 ) + { + return context->throwException( "Missing beep type" ); + } + QApplication::beep(); + return KJSUndefined(); +} + +static KJSObject appGetNthPlugInName( KJSContext *context, void *, + const KJSArguments &arguments ) +{ + if ( arguments.count() < 1 ) + { + return context->throwException( "Missing plugin index" ); + } + const int nIndex = arguments.at( 0 ).toInt32( context ); + + if ( nIndex < 0 || nIndex >= s_num_fake_plugins ) + return context->throwException( "PlugIn index out of bounds" ); + + const FakePluginInfo &info = s_fake_plugins[nIndex]; + return KJSString( info.name ); +} + +static KJSObject appGoBack( KJSContext *, void *object, + const KJSArguments & ) +{ + const DocumentPrivate *doc = reinterpret_cast< DocumentPrivate * >( object ); + if ( doc->m_parent->historyAtBegin() ) + return KJSUndefined(); + + doc->m_parent->setPrevViewport(); + return KJSUndefined(); +} + +static KJSObject appGoForward( KJSContext *, void *object, + const KJSArguments & ) +{ + const DocumentPrivate *doc = reinterpret_cast< DocumentPrivate * >( object ); + if ( doc->m_parent->historyAtEnd() ) + return KJSUndefined(); + + doc->m_parent->setNextViewport(); + return KJSUndefined(); +} + +void JSApp::initType( KJSContext *ctx ) +{ + static bool initialized = false; + if ( initialized ) + return; + initialized = true; + + g_appProto = new KJSPrototype(); + + g_appProto->defineProperty( ctx, "formsVersion", appGetFormsVersion ); + g_appProto->defineProperty( ctx, "language", appGetLanguage ); + g_appProto->defineProperty( ctx, "numPlugIns", appGetNumPlugins ); + g_appProto->defineProperty( ctx, "platform", appGetPlatform ); + g_appProto->defineProperty( ctx, "plugIns", appGetPlugIns ); + g_appProto->defineProperty( ctx, "printColorProfiles", appGetPrintColorProfiles ); + g_appProto->defineProperty( ctx, "printerNames", appGetPrinterNames ); + g_appProto->defineProperty( ctx, "viewerType", appGetViewerType ); + g_appProto->defineProperty( ctx, "viewerVariation", appGetViewerVariation ); + g_appProto->defineProperty( ctx, "viewerVersion", appGetViewerVersion ); + + g_appProto->defineFunction( ctx, "beep", appBeep ); + g_appProto->defineFunction( ctx, "getNthPlugInName", appGetNthPlugInName ); + g_appProto->defineFunction( ctx, "goBack", appGoBack ); + g_appProto->defineFunction( ctx, "goForward", appGoForward ); +} + +KJSObject JSApp::object( KJSContext *ctx, DocumentPrivate *doc ) +{ + return g_appProto->constructObject( ctx, doc ); +} diff --git a/okular/core/script/kjs_app_p.h b/okular/core/script/kjs_app_p.h new file mode 100644 index 00000000..c0649df1 --- /dev/null +++ b/okular/core/script/kjs_app_p.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_SCRIPT_KJS_APP_P_H +#define OKULAR_SCRIPT_KJS_APP_P_H + +class KJSContext; +class KJSObject; + +namespace Okular { + +class DocumentPrivate; + +class JSApp +{ + public: + static void initType( KJSContext *ctx ); + static KJSObject object( KJSContext *ctx, DocumentPrivate *doc ); +}; + +} + +#endif diff --git a/okular/core/script/kjs_console.cpp b/okular/core/script/kjs_console.cpp new file mode 100644 index 00000000..8944d73d --- /dev/null +++ b/okular/core/script/kjs_console.cpp @@ -0,0 +1,150 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "kjs_console_p.h" + +#include +#include +#include + +#include + +#include "../debug_p.h" + +using namespace Okular; + +static KJSPrototype *g_consoleProto; + +#ifdef OKULAR_JS_CONSOLE + +#include +#include + +#include +#include + +K_GLOBAL_STATIC( KDialog, g_jsConsoleWindow ) +static QPlainTextEdit *g_jsConsoleLog = 0; + +static void createConsoleWindow() +{ + if ( g_jsConsoleWindow.exists() ) + return; + + g_jsConsoleWindow->setButtons( KDialog::Close | KDialog::User1 ); + g_jsConsoleWindow->setButtonGuiItem( KDialog::User1, KStandardGuiItem::clear() ); + + QVBoxLayout *mainLay = new QVBoxLayout( g_jsConsoleWindow->mainWidget() ); + mainLay->setMargin( 0 ); + g_jsConsoleLog = new QPlainTextEdit( g_jsConsoleWindow->mainWidget() ); + g_jsConsoleLog->setReadOnly( true ); + mainLay->addWidget( g_jsConsoleLog ); + + QObject::connect( g_jsConsoleWindow, SIGNAL(closeClicked()), + g_jsConsoleWindow, SLOT(close()) ); + QObject::connect( g_jsConsoleWindow, SIGNAL(user1Clicked()), + g_jsConsoleLog, SLOT(clear()) ); +} + +static void showConsole() +{ + createConsoleWindow(); + g_jsConsoleWindow->show(); +} + +static void hideConsole() +{ + if ( !g_jsConsoleWindow.exists() ) + return; + + g_jsConsoleWindow->hide(); +} + +static void clearConsole() +{ + if ( !g_jsConsoleWindow.exists() ) + return; + + g_jsConsoleLog->clear(); +} + +static void outputToConsole( const QString &message ) +{ + showConsole(); + g_jsConsoleLog->appendPlainText( message ); +} + +#else /* OKULAR_JS_CONSOLE */ + +static void showConsole() +{ +} + +static void hideConsole() +{ +} + +static void clearConsole() +{ +} + +static void outputToConsole( const QString &cMessage ) +{ + kDebug(OkularDebug) << "CONSOLE:" << cMessage; +} + +#endif /* OKULAR_JS_CONSOLE */ + +static KJSObject consoleClear( KJSContext *, void *, const KJSArguments & ) +{ + clearConsole(); + return KJSUndefined(); +} + +static KJSObject consoleHide( KJSContext *, void *, const KJSArguments & ) +{ + hideConsole(); + return KJSUndefined(); +} + +static KJSObject consolePrintln( KJSContext *ctx, void *, + const KJSArguments &arguments ) +{ + QString cMessage = arguments.at( 0 ).toString( ctx ); + outputToConsole( cMessage ); + + return KJSUndefined(); +} + +static KJSObject consoleShow( KJSContext *, void *, const KJSArguments & ) +{ + showConsole(); + return KJSUndefined(); +} + +void JSConsole::initType( KJSContext *ctx ) +{ + static bool initialized = false; + if ( initialized ) + return; + initialized = true; + + g_consoleProto = new KJSPrototype(); + + g_consoleProto->defineFunction( ctx, "clear", consoleClear ); + g_consoleProto->defineFunction( ctx, "hide", consoleHide ); + g_consoleProto->defineFunction( ctx, "println", consolePrintln ); + g_consoleProto->defineFunction( ctx, "hide", consoleShow ); +} + +KJSObject JSConsole::object( KJSContext *ctx ) +{ + return g_consoleProto->constructObject( ctx, 0 ); +} diff --git a/okular/core/script/kjs_console_p.h b/okular/core/script/kjs_console_p.h new file mode 100644 index 00000000..0b9096b7 --- /dev/null +++ b/okular/core/script/kjs_console_p.h @@ -0,0 +1,28 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_SCRIPT_KJS_CONSOLE_P_H +#define OKULAR_SCRIPT_KJS_CONSOLE_P_H + +class KJSContext; +class KJSObject; + +namespace Okular { + +class JSConsole +{ + public: + static void initType( KJSContext *ctx ); + static KJSObject object( KJSContext *ctx ); +}; + +} + +#endif diff --git a/okular/core/script/kjs_data.cpp b/okular/core/script/kjs_data.cpp new file mode 100644 index 00000000..30544dcb --- /dev/null +++ b/okular/core/script/kjs_data.cpp @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "kjs_data_p.h" + +#include +#include + +#include + +#include "../document.h" + +using namespace Okular; + +static KJSPrototype *g_dataProto; + +static KJSObject dataGetCreationDate( KJSContext *ctx, void *object ) +{ + const EmbeddedFile *file = reinterpret_cast< EmbeddedFile * >( object ); + + return KJSDate( ctx, file->creationDate() ); +} + +static KJSObject dataGetDescription( KJSContext *, void *object ) +{ + const EmbeddedFile *file = reinterpret_cast< EmbeddedFile * >( object ); + + return KJSString( file->description() ); +} + +static KJSObject dataGetMIMEType( KJSContext *, void * ) +{ + return KJSString( "" ); +} + +static KJSObject dataGetModDate( KJSContext *ctx, void *object ) +{ + const EmbeddedFile *file = reinterpret_cast< EmbeddedFile * >( object ); + + return KJSDate( ctx, file->modificationDate() ); +} + +static KJSObject dataGetName( KJSContext *, void *object ) +{ + const EmbeddedFile *file = reinterpret_cast< EmbeddedFile * >( object ); + + return KJSString( file->name() ); +} + +static KJSObject dataGetPath( KJSContext *, void * ) +{ + return KJSString( "" ); +} + +static KJSObject dataGetSize( KJSContext *, void *object ) +{ + const EmbeddedFile *file = reinterpret_cast< EmbeddedFile * >( object ); + return KJSNumber( file->size() ); +} + +void JSData::initType( KJSContext *ctx ) +{ + static bool initialized = false; + if ( initialized ) + return; + initialized = true; + + if ( !g_dataProto ) + g_dataProto = new KJSPrototype(); + + g_dataProto->defineProperty( ctx, "creationDate", dataGetCreationDate ); + g_dataProto->defineProperty( ctx, "description", dataGetDescription ); + g_dataProto->defineProperty( ctx, "MIMEType", dataGetMIMEType ); + g_dataProto->defineProperty( ctx, "modDate", dataGetModDate ); + g_dataProto->defineProperty( ctx, "name", dataGetName ); + g_dataProto->defineProperty( ctx, "path", dataGetPath ); + g_dataProto->defineProperty( ctx, "size", dataGetSize ); +} + +KJSObject JSData::wrapFile( KJSContext *ctx, EmbeddedFile *f ) +{ + return g_dataProto->constructObject( ctx, f ); +} diff --git a/okular/core/script/kjs_data_p.h b/okular/core/script/kjs_data_p.h new file mode 100644 index 00000000..e8652343 --- /dev/null +++ b/okular/core/script/kjs_data_p.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_SCRIPT_KJS_DATA_P_H +#define OKULAR_SCRIPT_KJS_DATA_P_H + +class KJSContext; +class KJSObject; + +namespace Okular { + +class EmbeddedFile; + +class JSData +{ + public: + static void initType( KJSContext *ctx ); + static KJSObject wrapFile( KJSContext *ctx, EmbeddedFile *f ); +}; + +} + +#endif diff --git a/okular/core/script/kjs_document.cpp b/okular/core/script/kjs_document.cpp new file mode 100644 index 00000000..c3280cdd --- /dev/null +++ b/okular/core/script/kjs_document.cpp @@ -0,0 +1,287 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "kjs_document_p.h" + +#include + +#include +#include +#include + +#include +#include + +#include "../document_p.h" +#include "../page.h" +#include "../form.h" +#include "kjs_data_p.h" +#include "kjs_field_p.h" + +using namespace Okular; + +static KJSPrototype *g_docProto; + +// Document.numPages +static KJSObject docGetNumPages( KJSContext *, void *object ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + + return KJSNumber( doc->m_pagesVector.count() ); +} + +// Document.pageNum (getter) +static KJSObject docGetPageNum( KJSContext *, void *object ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + + return KJSNumber( doc->m_parent->currentPage() ); +} + +// Document.pageNum (setter) +static void docSetPageNum( KJSContext* ctx, void* object, + KJSObject value ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + + int page = value.toInt32( ctx ); + + if ( page == (int)doc->m_parent->currentPage() ) + return; + + doc->m_parent->setViewportPage( page ); +} + +// Document.documentFileName +static KJSObject docGetDocumentFileName( KJSContext *, void *object ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + + return KJSString( doc->m_url.fileName() ); +} + +// Document.filesize +static KJSObject docGetFilesize( KJSContext *, void *object ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + + return KJSNumber( doc->m_docSize ); +} + +// Document.path +static KJSObject docGetPath( KJSContext *, void *object ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + + return KJSString( doc->m_url.pathOrUrl() ); +} + +// Document.URL +static KJSObject docGetURL( KJSContext *, void *object ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + + return KJSString( doc->m_url.prettyUrl() ); +} + +// Document.permStatusReady +static KJSObject docGetPermStatusReady( KJSContext *, void * ) +{ + return KJSBoolean( true ); +} + +// Document.dataObjects +static KJSObject docGetDataObjects( KJSContext *ctx, void *object ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + + const QList< EmbeddedFile * > *files = doc->m_generator->embeddedFiles(); + + KJSArray dataObjects( ctx, files ? files->count() : 0 ); + if ( files ) + { + QList< EmbeddedFile * >::ConstIterator it = files->begin(), itEnd = files->end(); + for ( int i = 0; it != itEnd; ++it, ++i ) + { + KJSObject newdata = JSData::wrapFile( ctx, *it ); + dataObjects.setProperty( ctx, QString::number( i ), newdata ); + } + } + return dataObjects; +} + +// Document.external +static KJSObject docGetExternal( KJSContext *, void *object ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + QWidget *widget = doc->m_widget; + + const bool isShell = ( widget + && widget->parentWidget() + && widget->parentWidget()->objectName() == QLatin1String( "okular::Shell" ) ); + return KJSBoolean( !isShell ); +} + + +static KJSObject docGetInfo( KJSContext *ctx, void *object ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + + KJSObject obj; + const DocumentInfo *docinfo = doc->m_generator->generateDocumentInfo(); + if ( docinfo ) + { +#define KEY_GET( key, property ) \ +do { \ + const QString data = docinfo->get( key ); \ + if ( !data.isEmpty() ) \ + { \ + const KJSString newval( data ); \ + obj.setProperty( ctx, property, newval ); \ + obj.setProperty( ctx, QString( property ).toLower(), newval ); \ + } \ +} while ( 0 ); + KEY_GET( "title", "Title" ); + KEY_GET( "author", "Author" ); + KEY_GET( "subject", "Subject" ); + KEY_GET( "keywords", "Keywords" ); + KEY_GET( "creator", "Creator" ); + KEY_GET( "producer", "Producer" ); +#undef KEY_GET + } + return obj; +} + +#define DOCINFO_GET_METHOD( key, name ) \ +static KJSObject docGet ## name( KJSContext *, void *object ) \ +{ \ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); \ + const DocumentInfo *docinfo = doc->m_generator->generateDocumentInfo(); \ + return KJSString( docinfo->get( key ) ); \ +} + +DOCINFO_GET_METHOD( "author", Author ) +DOCINFO_GET_METHOD( "creator", Creator ) +DOCINFO_GET_METHOD( "keywords", Keywords ) +DOCINFO_GET_METHOD( "producer", Producer ) +DOCINFO_GET_METHOD( "title", Title ) +DOCINFO_GET_METHOD( "subject", Subject ) + +#undef DOCINFO_GET_METHOD + +// Document.getField() +static KJSObject docGetField( KJSContext *context, void *object, + const KJSArguments &arguments ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + + QString cName = arguments.at( 0 ).toString( context ); + + QVector< Page * >::const_iterator pIt = doc->m_pagesVector.constBegin(), pEnd = doc->m_pagesVector.constEnd(); + for ( ; pIt != pEnd; ++pIt ) + { + const QLinkedList< Okular::FormField * > pageFields = (*pIt)->formFields(); + QLinkedList< Okular::FormField * >::const_iterator ffIt = pageFields.constBegin(), ffEnd = pageFields.constEnd(); + for ( ; ffIt != ffEnd; ++ffIt ) + { + if ( (*ffIt)->name() == cName ) + { + return JSField::wrapField( context, *ffIt, *pIt ); + } + } + } + return KJSUndefined(); +} + +// Document.getPageLabel() +static KJSObject docGetPageLabel( KJSContext *ctx,void *object, + const KJSArguments &arguments ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + int nPage = arguments.at( 0 ).toInt32( ctx ); + Page *p = doc->m_pagesVector.value( nPage ); + return KJSString( p ? p->label() : QString() ); +} + +// Document.getPageRotation() +static KJSObject docGetPageRotation( KJSContext *ctx, void *object, + const KJSArguments &arguments ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + int nPage = arguments.at( 0 ).toInt32( ctx ); + Page *p = doc->m_pagesVector.value( nPage ); + return KJSNumber( p ? p->orientation() * 90 : 0 ); +} + +// Document.gotoNamedDest() +static KJSObject docGotoNamedDest( KJSContext *ctx, void *object, + const KJSArguments &arguments ) +{ + DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); + + QString dest = arguments.at( 0 ).toString( ctx ); + + DocumentViewport viewport( doc->m_generator->metaData( "NamedViewport", dest ).toString() ); + if ( !viewport.isValid() ) + return KJSUndefined(); + + doc->m_parent->setViewport( viewport ); + + return KJSUndefined(); +} + +// Document.syncAnnotScan() +static KJSObject docSyncAnnotScan( KJSContext *, void *, + const KJSArguments & ) +{ + return KJSUndefined(); +} + +void JSDocument::initType( KJSContext *ctx ) +{ + assert( g_docProto ); + + static bool initialized = false; + if ( initialized ) + return; + initialized = true; + + g_docProto->defineProperty( ctx, "numPages", docGetNumPages ); + g_docProto->defineProperty( ctx, "pageNum", docGetPageNum, docSetPageNum ); + g_docProto->defineProperty( ctx, "documentFileName", docGetDocumentFileName ); + g_docProto->defineProperty( ctx, "filesize", docGetFilesize ); + g_docProto->defineProperty( ctx, "path", docGetPath ); + g_docProto->defineProperty( ctx, "URL", docGetURL ); + g_docProto->defineProperty( ctx, "permStatusReady", docGetPermStatusReady ); + g_docProto->defineProperty( ctx, "dataObjects", docGetDataObjects ); + g_docProto->defineProperty( ctx, "external", docGetExternal ); + + // info properties + g_docProto->defineProperty( ctx, "info", docGetInfo ); + g_docProto->defineProperty( ctx, "author", docGetAuthor ); + g_docProto->defineProperty( ctx, "creator", docGetCreator ); + g_docProto->defineProperty( ctx, "keywords", docGetKeywords ); + g_docProto->defineProperty( ctx, "producer", docGetProducer ); + g_docProto->defineProperty( ctx, "title", docGetTitle ); + g_docProto->defineProperty( ctx, "subject", docGetSubject ); + + g_docProto->defineFunction( ctx, "getField", docGetField ); + g_docProto->defineFunction( ctx, "getPageLabel", docGetPageLabel ); + g_docProto->defineFunction( ctx, "getPageRotation", docGetPageRotation ); + g_docProto->defineFunction( ctx, "gotoNamedDest", docGotoNamedDest ); + g_docProto->defineFunction( ctx, "syncAnnotScan", docSyncAnnotScan ); +} + +KJSGlobalObject JSDocument::wrapDocument( DocumentPrivate *doc ) +{ + if ( !g_docProto ) + g_docProto = new KJSPrototype(); + return g_docProto->constructGlobalObject( doc ); +} diff --git a/okular/core/script/kjs_document_p.h b/okular/core/script/kjs_document_p.h new file mode 100644 index 00000000..edb9d4b8 --- /dev/null +++ b/okular/core/script/kjs_document_p.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_SCRIPT_KJS_DOCUMENT_P_H +#define OKULAR_SCRIPT_KJS_DOCUMENT_P_H + +class KJSContext; +class KJSGlobalObject; + +namespace Okular { + +class DocumentPrivate; + +class JSDocument +{ + public: + static void initType( KJSContext *ctx ); + static KJSGlobalObject wrapDocument( DocumentPrivate *doc ); +}; + +} + +#endif diff --git a/okular/core/script/kjs_field.cpp b/okular/core/script/kjs_field.cpp new file mode 100644 index 00000000..9bdb73b1 --- /dev/null +++ b/okular/core/script/kjs_field.cpp @@ -0,0 +1,230 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "kjs_field_p.h" + +#include +#include +#include + +#include + +#include +#include + +#include "../debug_p.h" +#include "../document_p.h" +#include "../form.h" +#include "../page.h" + +using namespace Okular; + +static KJSPrototype *g_fieldProto; + +typedef QHash< FormField *, KJSObject > FormCache; +K_GLOBAL_STATIC( FormCache, g_fieldCache ) + +// Field.doc +static KJSObject fieldGetDoc( KJSContext *context, void * ) +{ + return context->interpreter().globalObject(); +} + +// Field.name +static KJSObject fieldGetName( KJSContext *, void *object ) +{ + const FormField *field = reinterpret_cast< FormField * >( object ); + return KJSString( field->name() ); +} + +// Field.readonly (getter) +static KJSObject fieldGetReadOnly( KJSContext *, void *object ) +{ + const FormField *field = reinterpret_cast< FormField * >( object ); + return KJSBoolean( field->isReadOnly() ); +} + +// Field.readonly (setter) +static void fieldSetReadOnly( KJSContext *context, void *object, KJSObject value ) +{ +#if 0 + FormField *field = reinterpret_cast< FormField * >( object ); + bool b = value.toBoolean( context ); + field->setReadOnly( b ); +#else + Q_UNUSED( context ); + Q_UNUSED( object ); + Q_UNUSED( value ); + kDebug(OkularDebug) << "Not implemented: setting readonly property"; +#endif +} + +static QString fieldGetTypeHelper( const FormField *field ) +{ + switch ( field->type() ) + { + case FormField::FormButton: + { + const FormFieldButton *button = static_cast< const FormFieldButton * >( field ); + switch ( button->buttonType() ) + { + case FormFieldButton::Push: + return "button"; + case FormFieldButton::CheckBox: + return "checkbox"; + case FormFieldButton::Radio: + return "radiobutton"; + } + break; + } + case FormField::FormText: + return "text"; + case FormField::FormChoice: + { + const FormFieldChoice *choice = static_cast< const FormFieldChoice * >( field ); + switch ( choice->choiceType() ) + { + case FormFieldChoice::ComboBox: + return "combobox"; + case FormFieldChoice::ListBox: + return "listbox"; + } + break; + } + case FormField::FormSignature: + return "signature"; + } + return QString(); +} + +// Field.type +static KJSObject fieldGetType( KJSContext *, void *object ) +{ + const FormField *field = reinterpret_cast< FormField * >( object ); + + return KJSString( fieldGetTypeHelper( field ) ); +} + +// Field.value (getter) +static KJSObject fieldGetValue( KJSContext *context, void *object ) +{ + FormField *field = reinterpret_cast< FormField * >( object ); + if ( field->isReadOnly() ) + { + KJSObject value = g_fieldCache->value( field ); + if ( g_fieldCache.exists() && g_fieldCache->contains( field ) ) + value = g_fieldCache->value( field ); + else + value = KJSString(""); + kDebug(OkularDebug) << "Getting the value of a readonly field" << field->name() << ":" << value.toString( context ); + return value; + } + + switch ( field->type() ) + { + case FormField::FormButton: + { + const FormFieldButton *button = static_cast< const FormFieldButton * >( field ); + Q_UNUSED( button ); // ### + break; + } + case FormField::FormText: + { + const FormFieldText *text = static_cast< const FormFieldText * >( field ); + return KJSString( text->text() ); + } + case FormField::FormChoice: + { + const FormFieldChoice *choice = static_cast< const FormFieldChoice * >( field ); + Q_UNUSED( choice ); // ### + break; + } + case FormField::FormSignature: + { + break; + } + } + + return KJSUndefined(); +} + +// Field.value (setter) +static void fieldSetValue( KJSContext *context, void *object, KJSObject value ) +{ + FormField *field = reinterpret_cast< FormField * >( object ); + + if ( field->isReadOnly() ) + { + // ### throw exception? + kDebug(OkularDebug) << "Trying to change the readonly field" << field->name() << "to" << value.toString( context ); + g_fieldCache->insert( field, value ); + return; + } + + switch ( field->type() ) + { + case FormField::FormButton: + { + FormFieldButton *button = static_cast< FormFieldButton * >( field ); + Q_UNUSED( button ); // ### + break; + } + case FormField::FormText: + { + FormFieldText *text = static_cast< FormFieldText * >( field ); + text->setText( value.toString( context ) ); + break; + } + case FormField::FormChoice: + { + FormFieldChoice *choice = static_cast< FormFieldChoice * >( field ); + Q_UNUSED( choice ); // ### + break; + } + case FormField::FormSignature: + { + break; + } + } +} + +void JSField::initType( KJSContext *ctx ) +{ + static bool initialized = false; + if ( initialized ) + return; + initialized = true; + + if ( !g_fieldProto ) + g_fieldProto = new KJSPrototype(); + + g_fieldProto->defineProperty( ctx, "doc", fieldGetDoc ); + g_fieldProto->defineProperty( ctx, "name", fieldGetName ); + g_fieldProto->defineProperty( ctx, "readonly", + fieldGetReadOnly, fieldSetReadOnly ); + g_fieldProto->defineProperty( ctx, "type", fieldGetType ); + g_fieldProto->defineProperty( ctx, "value", fieldGetValue, fieldSetValue ); +} + +KJSObject JSField::wrapField( KJSContext *ctx, FormField *field, Page *page ) +{ + // ### cache unique wrapper + KJSObject f = g_fieldProto->constructObject( ctx, field ); + f.setProperty( ctx, "page", page->number() ); + return f; +} + +void JSField::clearCachedFields() +{ + if ( g_fieldCache.exists() ) + { + g_fieldCache->clear(); + } +} diff --git a/okular/core/script/kjs_field_p.h b/okular/core/script/kjs_field_p.h new file mode 100644 index 00000000..bc0a4628 --- /dev/null +++ b/okular/core/script/kjs_field_p.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_SCRIPT_KJS_FIELD_P_H +#define OKULAR_SCRIPT_KJS_FIELD_P_H + +class KJSContext; +class KJSObject; + +namespace Okular { + +class FormField; +class Page; + +class JSField +{ + public: + static void initType( KJSContext *ctx ); + static KJSObject wrapField( KJSContext *ctx, FormField *field, Page *page ); + static void clearCachedFields(); +}; + +} + +#endif diff --git a/okular/core/script/kjs_fullscreen.cpp b/okular/core/script/kjs_fullscreen.cpp new file mode 100644 index 00000000..28086570 --- /dev/null +++ b/okular/core/script/kjs_fullscreen.cpp @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "kjs_fullscreen_p.h" + +#include + +#include +#include + +#include "settings_core.h" + +using namespace Okular; + +static KJSPrototype *g_fsProto; + +static KJSObject fsGetLoop( KJSContext *, void * ) +{ + return KJSBoolean( SettingsCore::slidesLoop() ); +} + +static void fsSetLoop( KJSContext *ctx, void *, KJSObject value ) +{ + bool loop = value.toBoolean( ctx ); + SettingsCore::setSlidesLoop( loop ); +} + +static KJSObject fsGetUseTimer( KJSContext *, void * ) +{ + return KJSBoolean( SettingsCore::slidesAdvance() ); +} + +static void fsSetUseTimer( KJSContext *ctx, void *, KJSObject value ) +{ + bool use = value.toBoolean( ctx ); + SettingsCore::setSlidesAdvance( use ); +} + +static KJSObject fsGetTimeDelay( KJSContext *, void * ) +{ + return KJSNumber( SettingsCore::slidesAdvanceTime() ); +} + +static void fsSetTimeDelay( KJSContext *ctx, void *, KJSObject value ) +{ + int time = static_cast( value.toNumber( ctx ) ); + SettingsCore::setSlidesAdvanceTime( time ); +} + +void JSFullscreen::initType( KJSContext *ctx ) +{ + static bool initialized = false; + if ( initialized ) + return; + initialized = true; + + if ( !g_fsProto ) + g_fsProto = new KJSPrototype(); + + g_fsProto->defineProperty( ctx, "loop", fsGetLoop, fsSetLoop ); + g_fsProto->defineProperty( ctx, "useTimer", + fsGetUseTimer, fsSetUseTimer ); + g_fsProto->defineProperty( ctx, "timeDelay", + fsGetTimeDelay, fsSetTimeDelay ); +} + +KJSObject JSFullscreen::object( KJSContext *ctx ) +{ + assert( g_fsProto ); + return g_fsProto->constructObject( ctx ); +} diff --git a/okular/core/script/kjs_fullscreen_p.h b/okular/core/script/kjs_fullscreen_p.h new file mode 100644 index 00000000..e0f437f9 --- /dev/null +++ b/okular/core/script/kjs_fullscreen_p.h @@ -0,0 +1,28 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_SCRIPT_KJS_FULLSCREEN_P_H +#define OKULAR_SCRIPT_KJS_FULLSCREEN_P_H + +class KJSContext; +class KJSObject; + +namespace Okular { + +class JSFullscreen +{ + public: + static void initType( KJSContext *ctx ); + static KJSObject object( KJSContext *ctx ); +}; + +} + +#endif diff --git a/okular/core/script/kjs_spell.cpp b/okular/core/script/kjs_spell.cpp new file mode 100644 index 00000000..ea21d26a --- /dev/null +++ b/okular/core/script/kjs_spell.cpp @@ -0,0 +1,42 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "kjs_spell_p.h" + +#include +#include + +#include + +using namespace Okular; + +static KJSPrototype *g_spellProto; + +// Spell.available +static KJSObject spellGetAvailable( KJSContext *, void * ) +{ + return KJSBoolean( false ); +} + +void JSSpell::initType( KJSContext *ctx ) +{ + static bool initialized = false; + if ( initialized ) + return; + initialized = true; + + g_spellProto = new KJSPrototype(); + g_spellProto->defineProperty( ctx, QString( "available" ), spellGetAvailable ); +} + +KJSObject JSSpell::object( KJSContext *ctx ) +{ + return g_spellProto->constructObject( ctx, 0 ); +} diff --git a/okular/core/script/kjs_spell_p.h b/okular/core/script/kjs_spell_p.h new file mode 100644 index 00000000..f255fd1f --- /dev/null +++ b/okular/core/script/kjs_spell_p.h @@ -0,0 +1,28 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_SCRIPT_KJS_SPELL_P_H +#define OKULAR_SCRIPT_KJS_SPELL_P_H + +class KJSContext; +class KJSObject; + +namespace Okular { + +class JSSpell +{ + public: + static void initType( KJSContext *ctx ); + static KJSObject object( KJSContext *ctx ); +}; + +} + +#endif diff --git a/okular/core/script/kjs_util.cpp b/okular/core/script/kjs_util.cpp new file mode 100644 index 00000000..99d329bb --- /dev/null +++ b/okular/core/script/kjs_util.cpp @@ -0,0 +1,74 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "kjs_util_p.h" + +#include +#include +#include + +#include + +using namespace Okular; + +static KJSPrototype *g_utilProto; + +static KJSObject crackURL( KJSContext *context, void *, + const KJSArguments &arguments ) +{ + if ( arguments.count() < 1 ) + { + return context->throwException( "Missing URL argument" ); + } + QString cURL = arguments.at( 0 ).toString( context ); + KUrl url( cURL ); + if ( !url.isValid() ) + { + return context->throwException( "Invalid URL" ); + } + if ( url.protocol() != QLatin1String( "file" ) + || url.protocol() != QLatin1String( "http" ) + || url.protocol() != QLatin1String( "https" ) ) + { + return context->throwException( "Protocol not valid: '" + url.protocol() + '\'' ); + } + + KJSObject obj; + obj.setProperty( context, "cScheme", url.protocol() ); + if ( url.hasUser() ) + obj.setProperty( context, "cUser", url.user() ); + if ( url.hasPass() ) + obj.setProperty( context, "cPassword", url.password() ); + obj.setProperty( context, "cHost", url.host() ); + obj.setProperty( context, "nPort", url.port( 80 ) ); + // TODO cPath (Optional) The path portion of the URL. + // TODO cParameters (Optional) The parameter string portion of the URL. + if ( url.hasRef() ) + obj.setProperty( context, "cFragments", url.ref() ); + + return obj; +} + +void JSUtil::initType( KJSContext *ctx ) +{ + static bool initialized = false; + if ( initialized ) + return; + initialized = true; + + g_utilProto = new KJSPrototype(); + g_utilProto->defineFunction( ctx, "crackURL", crackURL ); +} + +KJSObject JSUtil::object( KJSContext *ctx ) +{ + return g_utilProto->constructObject( ctx, 0 ); +} + diff --git a/okular/core/script/kjs_util_p.h b/okular/core/script/kjs_util_p.h new file mode 100644 index 00000000..8acfd7fc --- /dev/null +++ b/okular/core/script/kjs_util_p.h @@ -0,0 +1,28 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * Copyright (C) 2008 by Harri Porten * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_SCRIPT_KJS_UTIL_P_H +#define OKULAR_SCRIPT_KJS_UTIL_P_H + +class KJSContext; +class KJSObject; + +namespace Okular { + +class JSUtil +{ + public: + static void initType( KJSContext *ctx ); + static KJSObject object( KJSContext *ctx ); +}; + +} + +#endif diff --git a/okular/core/scripter.cpp b/okular/core/scripter.cpp new file mode 100644 index 00000000..32850352 --- /dev/null +++ b/okular/core/scripter.cpp @@ -0,0 +1,66 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "scripter.h" + +#include + +#include "debug_p.h" +#include "script/executor_kjs_p.h" + +using namespace Okular; + +class Okular::ScripterPrivate +{ + public: + ScripterPrivate( DocumentPrivate *doc ) + : m_doc( doc ), m_kjs( 0 ) + { + } + + ~ScripterPrivate() + { + delete m_kjs; + } + + DocumentPrivate *m_doc; + ExecutorKJS *m_kjs; +}; + +Scripter::Scripter( DocumentPrivate *doc ) + : d( new ScripterPrivate( doc ) ) +{ +} + +Scripter::~Scripter() +{ + delete d; +} + +QString Scripter::execute( ScriptType type, const QString &script ) +{ + kDebug(OkularDebug) << "executing the script:"; +#if 0 + if ( script.length() < 1000 ) + qDebug() << script; + else + qDebug() << script.left( 1000 ) << "[...]"; +#endif + switch ( type ) + { + case JavaScript: + if ( !d->m_kjs ) + { + d->m_kjs = new ExecutorKJS( d->m_doc ); + } + d->m_kjs->execute( script ); + break; + } + return QString(); +} diff --git a/okular/core/scripter.h b/okular/core/scripter.h new file mode 100644 index 00000000..69cf5d49 --- /dev/null +++ b/okular/core/scripter.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_SCRIPTER_H +#define OKULAR_SCRIPTER_H + +#include "global.h" + +class QString; +class QStringList; + +namespace Okular { + +class Document; +class DocumentPrivate; +class ScripterPrivate; + +class Scripter +{ + friend class Document; + friend class DocumentPrivate; + + public: + ~Scripter(); + + QString execute( ScriptType type, const QString &script ); + + private: + friend class ScripterPrivate; + ScripterPrivate* d; + + Scripter( DocumentPrivate *doc ); +}; + +} + +#endif diff --git a/okular/core/sound.cpp b/okular/core/sound.cpp new file mode 100644 index 00000000..f1e3b7c5 --- /dev/null +++ b/okular/core/sound.cpp @@ -0,0 +1,117 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "sound.h" + +#include + +using namespace Okular; + +class Sound::Private +{ + public: + Private( const QByteArray &data ) + : m_data( QVariant( data ) ), + m_type( Sound::Embedded ) + { + init(); + } + + Private( const QString &url ) + : m_data( QVariant( url ) ), + m_type( Sound::External ) + { + init(); + } + + void init() + { + m_samplingRate = 44100.0; + m_channels = 1; + m_bitsPerSample = 8; + m_soundEncoding = Sound::Raw; + } + + QVariant m_data; + Sound::SoundType m_type; + double m_samplingRate; + int m_channels; + int m_bitsPerSample; + SoundEncoding m_soundEncoding; +}; + +Sound::Sound( const QByteArray& data ) + : d( new Private( data ) ) +{ +} + +Sound::Sound( const QString& url ) + : d( new Private( url ) ) +{ +} + +Sound::~Sound() +{ + delete d; +} + +Sound::SoundType Sound::soundType() const +{ + return d->m_type; +} + +QString Sound::url() const +{ + return d->m_type == Sound::External ? d->m_data.toString() : QString(); +} + +QByteArray Sound::data() const +{ + return d->m_type == Sound::Embedded ? d->m_data.toByteArray() : QByteArray(); +} + +double Sound::samplingRate() const +{ + return d->m_samplingRate; +} + +void Sound::setSamplingRate( double samplingRate ) +{ + d->m_samplingRate = samplingRate; +} + +int Sound::channels() const +{ + return d->m_channels; +} + +void Sound::setChannels( int channels ) +{ + d->m_channels = channels; +} + +int Sound::bitsPerSample() const +{ + return d->m_bitsPerSample; +} + +void Sound::setBitsPerSample( int bitsPerSample ) +{ + d->m_bitsPerSample = bitsPerSample; +} + +Sound::SoundEncoding Sound::soundEncoding() const +{ + return d->m_soundEncoding; +} + +void Sound::setSoundEncoding( Sound::SoundEncoding soundEncoding ) +{ + d->m_soundEncoding = soundEncoding; +} diff --git a/okular/core/sound.h b/okular/core/sound.h new file mode 100644 index 00000000..1019c010 --- /dev/null +++ b/okular/core/sound.h @@ -0,0 +1,127 @@ +/*************************************************************************** + * Copyright (C) 2006 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_SOUND_H_ +#define _OKULAR_SOUND_H_ + +#include "okular_export.h" + +#include +#include + +namespace Okular { + +/** + * @short Contains information about a sound object. + * + * This class encapsulates the information about a sound object + * which is used for links on enter/leave page event. + */ +class OKULAR_EXPORT Sound +{ + public: + /** + * Describes where the sound is stored. + */ + enum SoundType { + External, ///< Is stored at external resource (e.g. url) + Embedded ///< Is stored embedded in the document + }; + + /** + * Describes the encoding of the sound data. + */ + enum SoundEncoding { + Raw, ///< Is not encoded + Signed, ///< Is encoded with twos-complement values + muLaw, ///< Is µ-law encoded + ALaw ///< Is A-law encoded + }; + + /** + * Creates a new sound object with the given embedded + * sound @p data. + */ + explicit Sound( const QByteArray& data ); + + /** + * Creates a new sound object with the given external @p filename. + */ + explicit Sound( const QString& filename ); + + /** + * Destroys the sound object. + */ + ~Sound(); + + /** + * Returns the type of the sound object. + */ + SoundType soundType() const; + + /** + * Returns the external storage url of the sound data. + */ + QString url() const; + + /** + * Returns the embedded sound data. + */ + QByteArray data() const; + + /** + * Sets the sampling @p rate. + */ + void setSamplingRate( double rate ); + + /** + * Returns the sampling rate. + */ + double samplingRate() const; + + /** + * Sets the number of @p channels. + */ + void setChannels( int channels ); + + /** + * Returns the number of channels. + */ + int channels() const; + + /** + * Sets the bits per sample @p rate. + */ + void setBitsPerSample( int rate ); + + /** + * Returns the bits per sample rate. + */ + int bitsPerSample() const; + + /** + * Sets the type of sound @p encoding. + */ + void setSoundEncoding( SoundEncoding encoding ); + + /** + * Returns the sound encoding. + */ + SoundEncoding soundEncoding() const; + + private: + class Private; + Private* const d; + + Q_DISABLE_COPY( Sound ) +}; + +} + +#endif diff --git a/okular/core/sourcereference.cpp b/okular/core/sourcereference.cpp new file mode 100644 index 00000000..e34825ab --- /dev/null +++ b/okular/core/sourcereference.cpp @@ -0,0 +1,93 @@ +/*************************************************************************** + * Copyright (C) 2007,2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "sourcereference.h" +#include "sourcereference_p.h" + +#include +#include +#include + +using namespace Okular; + +class SourceReference::Private +{ + public: + Private() + : row( 0 ), column( 0 ) + { + } + + QString filename; + int row; + int column; +}; + +SourceReference::SourceReference( const QString &fileName, int row, int column ) + : d( new Private ) +{ + d->filename = fileName; + d->row = row; + d->column = column; +} + +SourceReference::~SourceReference() +{ + delete d; +} + +QString SourceReference::fileName() const +{ + return d->filename; +} + +int SourceReference::row() const +{ + return d->row; +} + +int SourceReference::column() const +{ + return d->column; +} + +bool Okular::extractLilyPondSourceReference( const QString &url, QString *file, int *row, int *col ) +{ + if ( !url.startsWith( QLatin1String( "textedit://" ) ) ) + return false; + + *row = 0; + *col = 0; + int lilyChar = 0; + typedef int *IntPtr; + const IntPtr int_data[] = { row, &lilyChar, col }; + int int_index = sizeof( int_data ) / sizeof( int* ) - 1; + int index_last = -1; + int index = url.lastIndexOf( QLatin1Char( ':' ), index_last ); + while ( index != -1 && int_index >= 0 ) + { + // read the current "chunk" + const QStringRef ref = url.midRef( index + 1, index_last - index - 1 ); + *int_data[ int_index ] = QString::fromRawData( ref.data(), ref.count() ).toInt(); + // find the previous "chunk" + index_last = index; + index = url.lastIndexOf( QLatin1Char( ':' ), index_last - 1 ); + --int_index; + } + // NOTE: 11 is the length of "textedit://" + *file = QUrl::fromPercentEncoding( url.mid( 11, index_last != -1 ? index_last - 11 : -1 ).toUtf8() ); + return true; +} + +QString Okular::sourceReferenceToolTip( const QString &source, int row, int col ) +{ + Q_UNUSED( row ); + Q_UNUSED( col ); + return i18nc( "'source' is a source file", "Source: %1", source ); +} diff --git a/okular/core/sourcereference.h b/okular/core/sourcereference.h new file mode 100644 index 00000000..b767b270 --- /dev/null +++ b/okular/core/sourcereference.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_SOURCEREFERENCE_H +#define OKULAR_SOURCEREFERENCE_H + +#include "okular_export.h" + +class QString; + +namespace Okular { + +/** + * @short Defines a source reference + * + * A source reference is a reference to one of the source(s) of the loaded + * document. + */ +class OKULAR_EXPORT SourceReference +{ + public: + /** + * Creates a reference to the row @p row and column @p column of the + * source @p fileName + */ + SourceReference( const QString &fileName, int row, int column = 0 ); + + /** + * Destroys the source reference. + */ + ~SourceReference(); + + /** + * Returns the filename of the source. + */ + QString fileName() const; + + /** + * Returns the row of the position in the source file. + */ + int row() const; + + /** + * Returns the column of the position in the source file. + */ + int column() const; + + private: + class Private; + Private* const d; + + Q_DISABLE_COPY( SourceReference ) +}; + +} + +#endif + diff --git a/okular/core/sourcereference_p.h b/okular/core/sourcereference_p.h new file mode 100644 index 00000000..cf7b3e82 --- /dev/null +++ b/okular/core/sourcereference_p.h @@ -0,0 +1,23 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_SOURCEREFERENCE_P_H +#define OKULAR_SOURCEREFERENCE_P_H + +class QString; + +namespace Okular +{ + +bool extractLilyPondSourceReference( const QString &url, QString *file, int *row, int *col ); +QString sourceReferenceToolTip( const QString &source, int row, int col ); + +} + +#endif diff --git a/okular/core/textdocumentgenerator.cpp b/okular/core/textdocumentgenerator.cpp new file mode 100644 index 00000000..d598ccc8 --- /dev/null +++ b/okular/core/textdocumentgenerator.cpp @@ -0,0 +1,567 @@ +/*************************************************************************** + * Copyright (C) 2007 by Tobias Koenig * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "textdocumentgenerator.h" +#include "textdocumentgenerator_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_VERSION >= 0x040500 +#include +#endif + +#include "action.h" +#include "annotations.h" +#include "page.h" +#include "textpage.h" + +#include "document.h" + +using namespace Okular; + +/** + * Generic Converter Implementation + */ +TextDocumentConverter::TextDocumentConverter() + : QObject( 0 ), d_ptr( new TextDocumentConverterPrivate ) +{ +} + +TextDocumentConverter::~TextDocumentConverter() +{ + delete d_ptr; +} + +QTextDocument *TextDocumentConverter::convert( const QString & ) +{ + return 0; +} + +Document::OpenResult TextDocumentConverter::convertWithPassword( const QString &fileName, const QString & ) +{ + QTextDocument *doc = convert( fileName ); + setDocument( doc ); + return doc != 0 ? Document::OpenSuccess : Document::OpenError; +} + +QTextDocument *TextDocumentConverter::document() +{ + return d_ptr->mDocument; +} + +void TextDocumentConverter::setDocument( QTextDocument *document ) +{ + d_ptr->mDocument = document; +} + +DocumentViewport TextDocumentConverter::calculateViewport( QTextDocument *document, const QTextBlock &block ) +{ + return TextDocumentUtils::calculateViewport( document, block ); +} + +TextDocumentGenerator* TextDocumentConverter::generator() const +{ + return d_ptr->mParent ? d_ptr->mParent->q_func() : 0; +} + +/** + * Generic Generator Implementation + */ +Okular::TextPage* TextDocumentGeneratorPrivate::createTextPage( int pageNumber ) const +{ +#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING + Q_Q( const TextDocumentGenerator ); +#endif + + Okular::TextPage *textPage = new Okular::TextPage; + + int start, end; + +#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING + q->userMutex()->lock(); +#endif + TextDocumentUtils::calculatePositions( mDocument, pageNumber, start, end ); + + { + QTextCursor cursor( mDocument ); + for ( int i = start; i < end - 1; ++i ) { + cursor.setPosition( i ); + cursor.setPosition( i + 1, QTextCursor::KeepAnchor ); + + QString text = cursor.selectedText(); + if ( text.length() == 1 ) { + QRectF rect; + TextDocumentUtils::calculateBoundingRect( mDocument, i, i + 1, rect, pageNumber ); + if ( pageNumber == -1 ) + text = "\n"; + + textPage->append( text, new Okular::NormalizedRect( rect.left(), rect.top(), rect.right(), rect.bottom() ) ); + } + } + } +#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING + q->userMutex()->unlock(); +#endif + + return textPage; +} + +void TextDocumentGeneratorPrivate::addAction( Action *action, int cursorBegin, int cursorEnd ) +{ + if ( !action ) + return; + + LinkPosition position; + position.link = action; + position.startPosition = cursorBegin; + position.endPosition = cursorEnd; + + mLinkPositions.append( position ); +} + +void TextDocumentGeneratorPrivate::addAnnotation( Annotation *annotation, int cursorBegin, int cursorEnd ) +{ + if ( !annotation ) + return; + + annotation->setFlags( annotation->flags() | Okular::Annotation::External ); + + AnnotationPosition position; + position.annotation = annotation; + position.startPosition = cursorBegin; + position.endPosition = cursorEnd; + + mAnnotationPositions.append( position ); +} + +void TextDocumentGeneratorPrivate::addTitle( int level, const QString &title, const QTextBlock &block ) +{ + TitlePosition position; + position.level = level; + position.title = title; + position.block = block; + + mTitlePositions.append( position ); +} + +void TextDocumentGeneratorPrivate::addMetaData( const QString &key, const QString &value, const QString &title ) +{ + mDocumentInfo.set( key, value, title ); +} + +void TextDocumentGeneratorPrivate::addMetaData( DocumentInfo::Key key, const QString &value ) +{ + mDocumentInfo.set( key, value ); +} + +void TextDocumentGeneratorPrivate::generateLinkInfos() +{ + for ( int i = 0; i < mLinkPositions.count(); ++i ) { + const LinkPosition &linkPosition = mLinkPositions[ i ]; + + LinkInfo info; + info.link = linkPosition.link; + + TextDocumentUtils::calculateBoundingRect( mDocument, linkPosition.startPosition, linkPosition.endPosition, + info.boundingRect, info.page ); + + if ( info.page >= 0 ) + mLinkInfos.append( info ); + } +} + +void TextDocumentGeneratorPrivate::generateAnnotationInfos() +{ + for ( int i = 0; i < mAnnotationPositions.count(); ++i ) { + const AnnotationPosition &annotationPosition = mAnnotationPositions[ i ]; + + AnnotationInfo info; + info.annotation = annotationPosition.annotation; + + TextDocumentUtils::calculateBoundingRect( mDocument, annotationPosition.startPosition, annotationPosition.endPosition, + info.boundingRect, info.page ); + + if ( info.page >= 0 ) + mAnnotationInfos.append( info ); + } +} + +void TextDocumentGeneratorPrivate::generateTitleInfos() +{ + QStack< QPair > parentNodeStack; + + QDomNode parentNode = mDocumentSynopsis; + + parentNodeStack.push( qMakePair( 0, parentNode ) ); + + for ( int i = 0; i < mTitlePositions.count(); ++i ) { + const TitlePosition &position = mTitlePositions[ i ]; + + Okular::DocumentViewport viewport = TextDocumentUtils::calculateViewport( mDocument, position.block ); + + QDomElement item = mDocumentSynopsis.createElement( position.title ); + item.setAttribute( "Viewport", viewport.toString() ); + + int headingLevel = position.level; + + // we need a parent, which has to be at a higher heading level than this heading level + // so we just work through the stack + while ( ! parentNodeStack.isEmpty() ) { + int parentLevel = parentNodeStack.top().first; + if ( parentLevel < headingLevel ) { + // this is OK as a parent + parentNode = parentNodeStack.top().second; + break; + } else { + // we'll need to be further into the stack + parentNodeStack.pop(); + } + } + parentNode.appendChild( item ); + parentNodeStack.push( qMakePair( headingLevel, QDomNode(item) ) ); + } +} + +void TextDocumentGeneratorPrivate::initializeGenerator() +{ + Q_Q( TextDocumentGenerator ); + + mConverter->d_ptr->mParent = q->d_func(); + + if ( mGeneralSettings ) { + mFont = mGeneralSettings->font(); + } + + q->setFeature( Generator::TextExtraction ); + q->setFeature( Generator::PrintNative ); + q->setFeature( Generator::PrintToFile ); +#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING + if ( QFontDatabase::supportsThreadedFontRendering() ) + q->setFeature( Generator::Threaded ); +#endif + + QObject::connect( mConverter, SIGNAL(addAction(Action*,int,int)), + q, SLOT(addAction(Action*,int,int)) ); + QObject::connect( mConverter, SIGNAL(addAnnotation(Annotation*,int,int)), + q, SLOT(addAnnotation(Annotation*,int,int)) ); + QObject::connect( mConverter, SIGNAL(addTitle(int,QString,QTextBlock)), + q, SLOT(addTitle(int,QString,QTextBlock)) ); + QObject::connect( mConverter, SIGNAL(addMetaData(QString,QString,QString)), + q, SLOT(addMetaData(QString,QString,QString)) ); + QObject::connect( mConverter, SIGNAL(addMetaData(DocumentInfo::Key,QString)), + q, SLOT(addMetaData(DocumentInfo::Key,QString)) ); + + QObject::connect( mConverter, SIGNAL(error(QString,int)), + q, SIGNAL(error(QString,int)) ); + QObject::connect( mConverter, SIGNAL(warning(QString,int)), + q, SIGNAL(warning(QString,int)) ); + QObject::connect( mConverter, SIGNAL(notice(QString,int)), + q, SIGNAL(notice(QString,int)) ); +} + +TextDocumentGenerator::TextDocumentGenerator( TextDocumentConverter *converter, const QString& configName, QObject *parent, const QVariantList &args ) + : Okular::Generator( *new TextDocumentGeneratorPrivate( converter ), parent, args ) +{ + Q_D( TextDocumentGenerator ); + d->mGeneralSettings = new TextDocumentSettings( configName, this ); + + d->initializeGenerator(); +} + +TextDocumentGenerator::TextDocumentGenerator( TextDocumentConverter *converter, QObject *parent, const QVariantList &args ) + : Okular::Generator( *new TextDocumentGeneratorPrivate( converter ), parent, args ) +{ + Q_D( TextDocumentGenerator ); + + d->initializeGenerator(); +} + +TextDocumentGenerator::~TextDocumentGenerator() +{ +} + +Document::OpenResult TextDocumentGenerator::loadDocumentWithPassword( const QString & fileName, QVector & pagesVector, const QString &password ) +{ + Q_D( TextDocumentGenerator ); + const Document::OpenResult openResult = d->mConverter->convertWithPassword( fileName, password ); + + if ( openResult != Document::OpenSuccess ) + { + d->mDocument = 0; + + // loading failed, cleanup all the stuff eventually gathered from the converter + d->mTitlePositions.clear(); + Q_FOREACH ( const TextDocumentGeneratorPrivate::LinkPosition &linkPos, d->mLinkPositions ) + { + delete linkPos.link; + } + d->mLinkPositions.clear(); + Q_FOREACH ( const TextDocumentGeneratorPrivate::AnnotationPosition &annPos, d->mAnnotationPositions ) + { + delete annPos.annotation; + } + d->mAnnotationPositions.clear(); + + return openResult; + } + d->mDocument = d->mConverter->document(); + + d->generateTitleInfos(); + d->generateLinkInfos(); + d->generateAnnotationInfos(); + + pagesVector.resize( d->mDocument->pageCount() ); + + const QSize size = d->mDocument->pageSize().toSize(); + + QVector< QLinkedList > objects( d->mDocument->pageCount() ); + for ( int i = 0; i < d->mLinkInfos.count(); ++i ) { + const TextDocumentGeneratorPrivate::LinkInfo &info = d->mLinkInfos.at( i ); + + // in case that the converter report bogus link info data, do not assert here + if ( info.page >= objects.count() ) + continue; + + const QRectF rect = info.boundingRect; + objects[ info.page ].append( new Okular::ObjectRect( rect.left(), rect.top(), rect.right(), rect.bottom(), false, + Okular::ObjectRect::Action, info.link ) ); + } + + QVector< QLinkedList > annots( d->mDocument->pageCount() ); + for ( int i = 0; i < d->mAnnotationInfos.count(); ++i ) { + const TextDocumentGeneratorPrivate::AnnotationInfo &info = d->mAnnotationInfos[ i ]; + annots[ info.page ].append( info.annotation ); + } + + for ( int i = 0; i < d->mDocument->pageCount(); ++i ) { + Okular::Page * page = new Okular::Page( i, size.width(), size.height(), Okular::Rotation0 ); + pagesVector[ i ] = page; + + if ( !objects.at( i ).isEmpty() ) { + page->setObjectRects( objects.at( i ) ); + } + QLinkedList::ConstIterator annIt = annots.at( i ).begin(), annEnd = annots.at( i ).end(); + for ( ; annIt != annEnd; ++annIt ) { + page->addAnnotation( *annIt ); + } + } + + return openResult; +} + +bool TextDocumentGenerator::doCloseDocument() +{ + Q_D( TextDocumentGenerator ); + delete d->mDocument; + d->mDocument = 0; + + d->mTitlePositions.clear(); + d->mLinkPositions.clear(); + d->mLinkInfos.clear(); + d->mAnnotationPositions.clear(); + d->mAnnotationInfos.clear(); + // do not use clear() for the following two, otherwise they change type + d->mDocumentInfo = Okular::DocumentInfo(); + d->mDocumentSynopsis = Okular::DocumentSynopsis(); + + return true; +} + +bool TextDocumentGenerator::canGeneratePixmap() const +{ + return Generator::canGeneratePixmap(); +} + +void TextDocumentGenerator::generatePixmap( Okular::PixmapRequest * request ) +{ + Generator::generatePixmap( request ); +} + +QImage TextDocumentGeneratorPrivate::image( PixmapRequest * request ) +{ + if ( !mDocument ) + return QImage(); + +#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING + Q_Q( TextDocumentGenerator ); +#endif + + QImage image( request->width(), request->height(), QImage::Format_ARGB32 ); + image.fill( Qt::white ); + + QPainter p; + p.begin( &image ); + + qreal width = request->width(); + qreal height = request->height(); + + const QSize size = mDocument->pageSize().toSize(); + + p.scale( width / (qreal)size.width(), height / (qreal)size.height() ); + + QRect rect; + rect = QRect( 0, request->pageNumber() * size.height(), size.width(), size.height() ); + p.translate( QPoint( 0, request->pageNumber() * size.height() * -1 ) ); + p.setClipRect( rect ); +#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING + q->userMutex()->lock(); +#endif + QAbstractTextDocumentLayout::PaintContext context; + context.palette.setColor( QPalette::Text, Qt::black ); +// FIXME Fix Qt, this doesn't work, we have horrible hacks +// in the generators that return html, remove that code +// if Qt ever gets fixed +// context.palette.setColor( QPalette::Link, Qt::blue ); + context.clip = rect; + mDocument->setDefaultFont( mFont ); + mDocument->documentLayout()->draw( &p, context ); +#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING + q->userMutex()->unlock(); +#endif + p.end(); + + return image; +} + +Okular::TextPage* TextDocumentGenerator::textPage( Okular::Page * page ) +{ + Q_D( TextDocumentGenerator ); + return d->createTextPage( page->number() ); +} + +bool TextDocumentGenerator::print( QPrinter& printer ) +{ + Q_D( TextDocumentGenerator ); + if ( !d->mDocument ) + return false; + + d->mDocument->print( &printer ); + + return true; +} + +const Okular::DocumentInfo* TextDocumentGenerator::generateDocumentInfo() +{ + Q_D( TextDocumentGenerator ); + return &d->mDocumentInfo; +} + +const Okular::DocumentSynopsis* TextDocumentGenerator::generateDocumentSynopsis() +{ + Q_D( TextDocumentGenerator ); + if ( !d->mDocumentSynopsis.hasChildNodes() ) + return 0; + else + return &d->mDocumentSynopsis; +} + +QVariant TextDocumentGeneratorPrivate::metaData( const QString &key, const QVariant &option ) const +{ + Q_UNUSED( option ) + if ( key == "DocumentTitle" ) + { + return mDocumentInfo.get( "title" ); + } + return QVariant(); +} + +Okular::ExportFormat::List TextDocumentGenerator::exportFormats( ) const +{ + static Okular::ExportFormat::List formats; + if ( formats.isEmpty() ) { + formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ) ); + formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PDF ) ); +#if QT_VERSION >= 0x040500 + if ( QTextDocumentWriter::supportedDocumentFormats().contains( "ODF" ) ) { + formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::OpenDocumentText ) ); + } + if ( QTextDocumentWriter::supportedDocumentFormats().contains( "HTML" ) ) { + formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::HTML ) ); + } +#endif + } + + return formats; +} + +bool TextDocumentGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format ) +{ + Q_D( TextDocumentGenerator ); + if ( !d->mDocument ) + return false; + + if ( format.mimeType()->name() == QLatin1String( "application/pdf" ) ) { + QFile file( fileName ); + if ( !file.open( QIODevice::WriteOnly ) ) + return false; + + QPrinter printer( QPrinter::HighResolution ); + printer.setOutputFormat( QPrinter::PdfFormat ); + printer.setOutputFileName( fileName ); + d->mDocument->print( &printer ); + + return true; + } else if ( format.mimeType()->name() == QLatin1String( "text/plain" ) ) { + QFile file( fileName ); + if ( !file.open( QIODevice::WriteOnly ) ) + return false; + + QTextStream out( &file ); + out << d->mDocument->toPlainText(); + + return true; +#if QT_VERSION >= 0x040500 + } else if ( format.mimeType()->name() == QLatin1String( "application/vnd.oasis.opendocument.text" ) ) { + QTextDocumentWriter odfWriter( fileName, "odf" ); + + return odfWriter.write( d->mDocument ); + } else if ( format.mimeType()->name() == QLatin1String( "text/html" ) ) { + QTextDocumentWriter odfWriter( fileName, "html" ); + + return odfWriter.write( d->mDocument ); +#endif + } + return false; +} + +bool TextDocumentGenerator::reparseConfig() +{ + Q_D( TextDocumentGenerator ); + const QFont newFont = d->mGeneralSettings->font(); + + if ( newFont != d->mFont ) { + d->mFont = newFont; + return true; + } + + return false; +} + +void TextDocumentGenerator::addPages( KConfigDialog* /*dlg*/ ) +{ + kWarning() << "You forgot to reimplement addPages in your TextDocumentGenerator"; + return; +} + +TextDocumentSettings* TextDocumentGenerator::generalSettings() +{ + Q_D( TextDocumentGenerator ); + + return d->mGeneralSettings; +} + +#include "textdocumentgenerator.moc" + diff --git a/okular/core/textdocumentgenerator.h b/okular/core/textdocumentgenerator.h new file mode 100644 index 00000000..ec331d6d --- /dev/null +++ b/okular/core/textdocumentgenerator.h @@ -0,0 +1,234 @@ +/*************************************************************************** + * Copyright (C) 2007 by Tobias Koenig * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_TEXTDOCUMENTGENERATOR_H_ +#define _OKULAR_TEXTDOCUMENTGENERATOR_H_ + +#include "okular_export.h" + +#include "document.h" +#include "generator.h" +#include "textdocumentsettings.h" +#include "../interfaces/configinterface.h" + +class QTextBlock; +class QTextDocument; + +namespace Okular { + +class TextDocumentConverterPrivate; +class TextDocumentGenerator; +class TextDocumentGeneratorPrivate; + +class OKULAR_EXPORT TextDocumentConverter : public QObject +{ + Q_OBJECT + + friend class TextDocumentGenerator; + friend class TextDocumentGeneratorPrivate; + + public: + /** + * Creates a new generic converter. + */ + TextDocumentConverter(); + + /** + * Destroys the generic converter. + */ + ~TextDocumentConverter(); + + /** + * Returns the generated QTextDocument object. + * + * @note there is no need to implement this one if you implement convertWithPassword + */ + virtual QTextDocument *convert( const QString &fileName ); + + /** + * Returns the generated QTextDocument object. + */ + virtual Document::OpenResult convertWithPassword( const QString &fileName, const QString &password ); + + /** + * Returns the generated QTextDocument object. Will be null if convert didn't succeed + */ + QTextDocument *document(); + + Q_SIGNALS: + /** + * Adds a new link object which is located between cursorBegin and + * cursorEnd to the generator. + */ + void addAction( Action *link, int cursorBegin, int cursorEnd ); + + /** + * Adds a new annotation object which is located between cursorBegin and + * cursorEnd to the generator. + */ + void addAnnotation( Annotation *annotation, int cursorBegin, int cursorEnd ); + + /** + * Adds a new title at the given level which is located as position to the generator. + */ + void addTitle( int level, const QString &title, const QTextBlock &position ); + + /** + * Adds a set of meta data to the generator. + */ + void addMetaData( const QString &key, const QString &value, const QString &title ); + + /** + * Adds a set of meta data to the generator. + * + * @since 0.7 (KDE 4.1) + */ + void addMetaData( DocumentInfo::Key key, const QString &value ); + + /** + * This signal should be emitted whenever an error occurred in the converter. + * + * @param message The message which should be shown to the user. + * @param duration The time that the message should be shown to the user. + */ + void error( const QString &message, int duration ); + + /** + * This signal should be emitted whenever the user should be warned. + * + * @param message The message which should be shown to the user. + * @param duration The time that the message should be shown to the user. + */ + void warning( const QString &message, int duration ); + + /** + * This signal should be emitted whenever the user should be noticed. + * + * @param message The message which should be shown to the user. + * @param duration The time that the message should be shown to the user. + */ + void notice( const QString &message, int duration ); + + protected: + /** + * Sets the converted QTextDocument object. + */ + void setDocument( QTextDocument *document ); + + /** + * This method can be used to calculate the viewport for a given text block. + * + * @note This method should be called at the end of the convertion, because it + * triggers QTextDocument to do the layout calculation. + */ + DocumentViewport calculateViewport( QTextDocument *document, const QTextBlock &block ); + + /** + * Returns the generator that owns this converter. + * + * @note May be null if the converter was not created for a generator. + * + * @since 0.7 (KDE 4.1) + */ + TextDocumentGenerator* generator() const; + + private: + TextDocumentConverterPrivate *d_ptr; + Q_DECLARE_PRIVATE( TextDocumentConverter ) + Q_DISABLE_COPY( TextDocumentConverter ) +}; + +/** + * @brief QTextDocument-based Generator + * + * This generator provides a document in the form of a QTextDocument object, + * parsed using a specialized TextDocumentConverter. + */ +class OKULAR_EXPORT TextDocumentGenerator : public Generator, public Okular::ConfigInterface +{ + /// @cond PRIVATE + friend class TextDocumentConverter; + /// @endcond + + Q_OBJECT + Q_INTERFACES( Okular::ConfigInterface ) + + public: + /** + * Creates a new generator that uses the specified @p converter. + * + * @param configName - see Okular::TextDocumentSettings + * + * @note the generator will take ownership of the converter, so you + * don't have to delete it yourself + * @since 0.17 (KDE 4.11) + */ + TextDocumentGenerator( TextDocumentConverter *converter, const QString& configName, QObject *parent, const QVariantList &args ); + /** + * Creates a new generator that uses the specified @p converter. + * + * @deprecated use the one with configName + * + * @note the generator will take ownership of the converter, so you + * don't have to delete it yourself + */ + KDE_DEPRECATED TextDocumentGenerator( TextDocumentConverter *converter, QObject *parent, const QVariantList &args ); + virtual ~TextDocumentGenerator(); + + // [INHERITED] load a document and fill up the pagesVector + Document::OpenResult loadDocumentWithPassword( const QString & fileName, QVector & pagesVector, const QString &password ); + + // [INHERITED] perform actions on document / pages + bool canGeneratePixmap() const; + void generatePixmap( Okular::PixmapRequest * request ); + + // [INHERITED] print document using already configured QPrinter + bool print( QPrinter& printer ); + + // [INHERITED] text exporting + Okular::ExportFormat::List exportFormats() const; + bool exportTo( const QString &fileName, const Okular::ExportFormat &format ); + + // [INHERITED] config interface + /// By default checks if the default font has changed or not + bool reparseConfig(); + /// Does nothing by default. You need to reimplement it in your generator + void addPages( KConfigDialog* dlg ); + + /** + * Config skeleton for TextDocumentSettingsWidget + * + * You must use new construtor to initialize TextDocumentSettings, + * that contain @param configName. + * + * @since 0.17 (KDE 4.11) + */ + TextDocumentSettings* generalSettings(); + + const Okular::DocumentInfo* generateDocumentInfo(); + const Okular::DocumentSynopsis* generateDocumentSynopsis(); + + protected: + bool doCloseDocument(); + Okular::TextPage* textPage( Okular::Page *page ); + + private: + Q_DECLARE_PRIVATE( TextDocumentGenerator ) + Q_DISABLE_COPY( TextDocumentGenerator ) + + Q_PRIVATE_SLOT( d_func(), void addAction( Action*, int, int ) ) + Q_PRIVATE_SLOT( d_func(), void addAnnotation( Annotation*, int, int ) ) + Q_PRIVATE_SLOT( d_func(), void addTitle( int, const QString&, const QTextBlock& ) ) + Q_PRIVATE_SLOT( d_func(), void addMetaData( const QString&, const QString&, const QString& ) ) + Q_PRIVATE_SLOT( d_func(), void addMetaData( DocumentInfo::Key, const QString& ) ) +}; + +} + +#endif diff --git a/okular/core/textdocumentgenerator_p.h b/okular/core/textdocumentgenerator_p.h new file mode 100644 index 00000000..fba73ff6 --- /dev/null +++ b/okular/core/textdocumentgenerator_p.h @@ -0,0 +1,202 @@ +/*************************************************************************** + * Copyright (C) 2007 by Tobias Koenig * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_TEXTDOCUMENTGENERATOR_P_H_ +#define _OKULAR_TEXTDOCUMENTGENERATOR_P_H_ + +#include +#include +#include + +#include "action.h" +#include "document.h" +#include "generator_p.h" +#include "textdocumentgenerator.h" + +namespace Okular { + +namespace TextDocumentUtils { + + static void calculateBoundingRect( QTextDocument *document, int startPosition, int endPosition, + QRectF &rect, int &page ) + { + const QSizeF pageSize = document->pageSize(); + + const QTextBlock startBlock = document->findBlock( startPosition ); + const QRectF startBoundingRect = document->documentLayout()->blockBoundingRect( startBlock ); + + const QTextBlock endBlock = document->findBlock( endPosition ); + const QRectF endBoundingRect = document->documentLayout()->blockBoundingRect( endBlock ); + + QTextLayout *startLayout = startBlock.layout(); + QTextLayout *endLayout = endBlock.layout(); + if (!startLayout || !endLayout) { + kWarning() << "Start or end layout not found" << startLayout << endLayout; + page = -1; + return; + } + + int startPos = startPosition - startBlock.position(); + int endPos = endPosition - endBlock.position(); + const QTextLine startLine = startLayout->lineForTextPosition( startPos ); + const QTextLine endLine = endLayout->lineForTextPosition( endPos ); + + double x = startBoundingRect.x() + startLine.cursorToX( startPos ); + double y = startBoundingRect.y() + startLine.y(); + double r = endBoundingRect.x() + endLine.cursorToX( endPos ); + double b = endBoundingRect.y() + endLine.y() + endLine.height(); + + int offset = qRound( y ) % qRound( pageSize.height() ); + + if ( x > r ) { // line break, so return a pseudo character on the start line + rect = QRectF( x / pageSize.width(), offset / pageSize.height(), + 3 / pageSize.width(), startLine.height() / pageSize.height() ); + page = -1; + return; + } + + page = qRound( y ) / qRound( pageSize.height() ); + rect = QRectF( x / pageSize.width(), offset / pageSize.height(), + (r - x) / pageSize.width(), (b - y) / pageSize.height() ); + } + + static void calculatePositions( QTextDocument *document, int page, int &start, int &end ) + { + const QAbstractTextDocumentLayout *layout = document->documentLayout(); + const QSizeF pageSize = document->pageSize(); + double margin = document->rootFrame()->frameFormat().margin(); + + /** + * Take the upper left and lower left corner including the margin + */ + start = layout->hitTest( QPointF( margin, (page * pageSize.height()) + margin ), Qt::FuzzyHit ); + end = layout->hitTest( QPointF( margin, ((page + 1) * pageSize.height()) - margin ), Qt::FuzzyHit ); + } + + static Okular::DocumentViewport calculateViewport( QTextDocument *document, const QTextBlock &block ) + { + const QSizeF pageSize = document->pageSize(); + const QRectF rect = document->documentLayout()->blockBoundingRect( block ); + + int page = qRound( rect.y() ) / qRound( pageSize.height() ); + int offset = qRound( rect.y() ) % qRound( pageSize.height() ); + + Okular::DocumentViewport viewport( page ); + viewport.rePos.normalizedX = (double)rect.x() / (double)pageSize.width(); + viewport.rePos.normalizedY = (double)offset / (double)pageSize.height(); + viewport.rePos.enabled = true; + viewport.rePos.pos = Okular::DocumentViewport::Center; + + return viewport; + } +} + +class TextDocumentConverterPrivate +{ + public: + TextDocumentConverterPrivate() + : mParent( 0 ) + { + } + + TextDocumentGeneratorPrivate *mParent; + QTextDocument *mDocument; +}; + +class TextDocumentGeneratorPrivate : public GeneratorPrivate +{ + friend class TextDocumentConverter; + + public: + TextDocumentGeneratorPrivate( TextDocumentConverter *converter ) + : mConverter( converter ), mDocument( 0 ), mGeneralSettings( 0 ) + { + } + + virtual ~TextDocumentGeneratorPrivate() + { + delete mConverter; + delete mDocument; + } + + void initializeGenerator(); + + Q_DECLARE_PUBLIC( TextDocumentGenerator ) + + /* reimp */ QVariant metaData( const QString &key, const QVariant &option ) const; + /* reimp */ QImage image( PixmapRequest * ); + + void calculateBoundingRect( int startPosition, int endPosition, QRectF &rect, int &page ) const; + void calculatePositions( int page, int &start, int &end ) const; + Okular::TextPage* createTextPage( int ) const; + + void addAction( Action *action, int cursorBegin, int cursorEnd ); + void addAnnotation( Annotation *annotation, int cursorBegin, int cursorEnd ); + void addTitle( int level, const QString &title, const QTextBlock &position ); + void addMetaData( const QString &key, const QString &value, const QString &title ); + void addMetaData( DocumentInfo::Key, const QString &value ); + + void generateLinkInfos(); + void generateAnnotationInfos(); + void generateTitleInfos(); + + TextDocumentConverter *mConverter; + + QTextDocument *mDocument; + Okular::DocumentInfo mDocumentInfo; + Okular::DocumentSynopsis mDocumentSynopsis; + + struct TitlePosition + { + int level; + QString title; + QTextBlock block; + }; + QList mTitlePositions; + + struct LinkPosition + { + int startPosition; + int endPosition; + Action *link; + }; + QList mLinkPositions; + + struct LinkInfo + { + int page; + QRectF boundingRect; + Action *link; + }; + QList mLinkInfos; + + struct AnnotationPosition + { + int startPosition; + int endPosition; + Annotation *annotation; + }; + QList mAnnotationPositions; + + struct AnnotationInfo + { + int page; + QRectF boundingRect; + Annotation *annotation; + }; + QList mAnnotationInfos; + + TextDocumentSettings *mGeneralSettings; + + QFont mFont; +}; + +} + +#endif diff --git a/okular/core/textdocumentsettings.cpp b/okular/core/textdocumentsettings.cpp new file mode 100644 index 00000000..4187534c --- /dev/null +++ b/okular/core/textdocumentsettings.cpp @@ -0,0 +1,77 @@ +/*************************************************************************** + * Copyright (C) 2013 by Azat Khuzhin * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + + +#include "textdocumentsettings.h" +#include "textdocumentsettings_p.h" +#include "ui_textdocumentsettings.h" + +#include +#include + +#include + + +using namespace Okular; + +/** + * TextDocumentSettingsWidget + */ + +TextDocumentSettingsWidget::TextDocumentSettingsWidget( QWidget *parent ) + : QWidget( parent ) + , d_ptr( new TextDocumentSettingsWidgetPrivate( new Ui_TextDocumentSettings() ) ) +{ + Q_D( TextDocumentSettingsWidget ); + + d->mUi->setupUi( this ); + + // @notice I think this will be useful in future. +#define ADD_WIDGET( property, widget, objectName, labelName ) \ + d->property = new widget( this ); \ + d->property->setObjectName( QString::fromUtf8( objectName ) ); \ + addRow( labelName, d->property ); + + ADD_WIDGET( mFont, KFontRequester, "kcfg_Font", i18n("&Default Font:") ); +#undef ADD_WIDGET +} + +TextDocumentSettingsWidget::~TextDocumentSettingsWidget() +{ + Q_D( TextDocumentSettingsWidget ); + + delete d->mUi; +} + +void TextDocumentSettingsWidget::addRow( const QString& labelText, QWidget *widget ) +{ + Q_D( TextDocumentSettingsWidget ); + + d->mUi->formLayout->addRow( labelText, widget ); +} + + +/** + * TextDocumentSettings + */ + +TextDocumentSettings::TextDocumentSettings( const QString& config, QObject *parent ) + : KConfigSkeleton( config, parent ) + , d_ptr( new TextDocumentSettingsPrivate() ) +{ + Q_D( TextDocumentSettings ); + + addItemFont( "Font", d->mFont ); +} + +QFont TextDocumentSettings::font() const +{ + Q_D( const TextDocumentSettings ); + return d->mFont; +} diff --git a/okular/core/textdocumentsettings.h b/okular/core/textdocumentsettings.h new file mode 100644 index 00000000..1af9ea77 --- /dev/null +++ b/okular/core/textdocumentsettings.h @@ -0,0 +1,123 @@ +/*************************************************************************** + * Copyright (C) 2013 by Azat Khuzhin * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + + +#ifndef _OKULAR_TEXTDOCUMENTSETTINGS_H_ +#define _OKULAR_TEXTDOCUMENTSETTINGS_H_ + + +#include "okular_export.h" + +#include +#include +#include +#include + +namespace Okular { + +class TextDocumentSettingsWidgetPrivate; +class TextDocumentSettingsPrivate; + +/** + * Here is example of how you can add custom settings per-backend: + * + * In .h header: + * {code} + * class KIntSpinBox; + * ... + * + * class YourGenerator + * { + * ... + * public: + * bool reparseConfig(); + * void addPages( KConfigDialog* dlg ); + * ... + * private: + * QString customArgument; + * KIntSpinBox *customArgumentWidget; + * ... + * } + * {/code} + * + * In .cpp module: + * {code} + * #include + * ... + * bool YourGenerator::reparseConfig() + * { + * ... Do something with customArgumentWidget and customArgument ... + * } + * void YourGenerator::addPages( KConfigDialog* dlg ) + * { + * Okular::TextDocumentSettingsWidget *widget = new Okular::TextDocumentSettingsWidget(); + * + * KIntSpinBox *customArgumentWidget = new KIntSpinBox( dlg ); + * customArgumentWidget->setObjectName( QString::fromUtf8( "kcfg_CustomArgument" ) ); + * widget->addRow( "Custom argument", customArgumentWidget ); + * + * Okular::TextDocumentSettings *settings = generalSettings(); + * settings->addItemString( "CustomArgument", customArgument ); + * + * dlg->addPage( widget, settings, ... ); + * } + * {/code} + */ + +/** + * TextDocumentSettingsWidget + * + * Contain default settings for text based documents. + * (all generators that inherited from TextDocumentGenerator) + * Generator can add settings to this object individually. + * + * @since 0.17 (KDE 4.11) + */ +class OKULAR_EXPORT TextDocumentSettingsWidget : public QWidget +{ + public: + TextDocumentSettingsWidget( QWidget *parent = 0 ); + virtual ~TextDocumentSettingsWidget(); + + void addRow( const QString& labelText, QWidget *widget ); + + private: + friend class TextDocumentGenerator; + + TextDocumentSettingsWidgetPrivate *d_ptr; + Q_DECLARE_PRIVATE( TextDocumentSettingsWidget ) + Q_DISABLE_COPY( TextDocumentSettingsWidget ) +}; + +/** + * TextDocumentSettings + * + * Contain default settings/config skeleton + * To save/restore settings. + * + * @since 0.17 (KDE 4.11) + */ +class OKULAR_EXPORT TextDocumentSettings : public KConfigSkeleton +{ + public: + QFont font() const; + + private: + friend class TextDocumentGenerator; + + TextDocumentSettings( const QString& config, QObject *parent ); + + TextDocumentSettingsPrivate *d_ptr; + Q_DECLARE_PRIVATE( TextDocumentSettings ) + Q_DISABLE_COPY( TextDocumentSettings ) +}; + +} + +#endif diff --git a/okular/core/textdocumentsettings_p.h b/okular/core/textdocumentsettings_p.h new file mode 100644 index 00000000..5104de25 --- /dev/null +++ b/okular/core/textdocumentsettings_p.h @@ -0,0 +1,42 @@ +/*************************************************************************** + * Copyright (C) 2013 by Azat Khuzhin * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + + +#ifndef _OKULAR_TEXTDOCUMENTSETTINGS_P_H_ +#define _OKULAR_TEXTDOCUMENTSETTINGS_P_H_ + +class KFontRequester; +class Ui_TextDocumentSettings; + +namespace Okular { + +class TextDocumentSettingsWidgetPrivate +{ + public: + /** + * @note the private class won't take ownership of the ui, so you + * must delete it yourself + */ + TextDocumentSettingsWidgetPrivate(Ui_TextDocumentSettings *ui) + : mUi(ui) + {} + + KFontRequester *mFont; + Ui_TextDocumentSettings *mUi; +}; + +class TextDocumentSettingsPrivate +{ + public: + QFont mFont; +}; + +} + +#endif diff --git a/okular/core/texteditors_p.h b/okular/core/texteditors_p.h new file mode 100644 index 00000000..026d344b --- /dev/null +++ b/okular/core/texteditors_p.h @@ -0,0 +1,39 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_TEXTEDITORS_P_H +#define OKULAR_TEXTEDITORS_P_H + +#include "settings_core.h" + +#include +#include + +namespace Okular +{ + +static inline QHash< int, QString > buildEditorsMap() +{ + QHash< int, QString > editors; + editors.insert( SettingsCore::EnumExternalEditor::Kate, + QString::fromLatin1( "kate --use --line %l --column %c" ) ); + editors.insert( SettingsCore::EnumExternalEditor::Kile, + QString::fromLatin1( "kile --line %l" ) ); + editors.insert( SettingsCore::EnumExternalEditor::Scite, + QString::fromLatin1( "scite %f \"-goto:%l,%c\"" ) ); + editors.insert( SettingsCore::EnumExternalEditor::Emacsclient, + QString::fromLatin1( "emacsclient -a emacs --no-wait +%l %f" ) ); + editors.insert( SettingsCore::EnumExternalEditor::Lyxclient, + QString::fromLatin1( "lyxclient -g %f %l" ) ); + return editors; +} + +} + +#endif diff --git a/okular/core/textpage.cpp b/okular/core/textpage.cpp new file mode 100644 index 00000000..348dea08 --- /dev/null +++ b/okular/core/textpage.cpp @@ -0,0 +1,2036 @@ +/*************************************************************************** + * Copyright (C) 2005 by Piotr Szymanski * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "textpage.h" +#include "textpage_p.h" + +#include + +#include "area.h" +#include "debug_p.h" +#include "misc.h" +#include "page.h" +#include "page_p.h" + +#include + +#include +#include + +using namespace Okular; + +class SearchPoint +{ + public: + SearchPoint() + : offset_begin( -1 ), offset_end( -1 ) + { + } + + /** The TinyTextEntity containing the first character of the match. */ + TextList::ConstIterator it_begin; + + /** The TinyTextEntity containing the last character of the match. */ + TextList::ConstIterator it_end; + + /** The index of the first character of the match in (*it_begin)->text(). + * Satisfies 0 <= offset_begin < (*it_begin)->text().length(). + */ + int offset_begin; + + /** One plus the index of the last character of the match in (*it_end)->text(). + * Satisfies 0 < offset_end <= (*it_end)->text().length(). + */ + int offset_end; +}; + +/* text comparison functions */ + +static bool CaseInsensitiveCmpFn( const QStringRef & from, const QStringRef & to ) +{ + return from.compare( to, Qt::CaseInsensitive ) == 0; +} + +static bool CaseSensitiveCmpFn( const QStringRef & from, const QStringRef & to ) +{ + return from.compare( to, Qt::CaseSensitive ) == 0; +} + + +/** + * Returns true iff segments [@p left1, @p right1] and [@p left2, @p right2] on the real line + * overlap within @p threshold percent, i. e. iff the ratio of the length of the + * intersection of the segments to the length of the shortest of the two input segments + * is not smaller than the threshold. + */ +static bool segmentsOverlap(double left1, double right1, double left2, double right2, int threshold) +{ + // check if one consumes another fully (speed optimization) + + if (left1 <= left2 && right1 >= right2) + return true; + + if (left1 >= left2 && right1 <= right2) + return true; + + // check if there is overlap above threshold + if (right2 >= left1 && right1 >= left2) + { + double overlap = (right2 >= right1) ? right1 - left2 + : right2 - left1; + + double length1 = right1 - left1, + length2 = right2 - left2; + + return overlap * 100 >= threshold * qMin(length1, length2); + } + + return false; +} + +static bool doesConsumeY(const QRect& first, const QRect& second, int threshold) +{ + return segmentsOverlap(first.top(), first.bottom(), second.top(), second.bottom(), threshold); +} + +static bool doesConsumeY(const NormalizedRect& first, const NormalizedRect& second, int threshold) +{ + return segmentsOverlap(first.top, first.bottom, second.top, second.bottom, threshold); +} + + +/* + Rationale behind TinyTextEntity: + + instead of storing directly a QString for the text of an entity, + we store the UTF-16 data and their length. This way, we save about + 4 int's wrt a QString, and we can create a new string from that + raw data (that's the only penalty of that). + Even better, if the string we need to store has at most + MaxStaticChars characters, then we store those in place of the QChar* + that would be used (with new[] + free[]) for the data. + */ +class TinyTextEntity +{ + static const int MaxStaticChars = sizeof( QChar * ) / sizeof( QChar ); + + public: + TinyTextEntity( const QString &text, const NormalizedRect &rect ) + : area( rect ) + { + Q_ASSERT_X( !text.isEmpty(), "TinyTextEntity", "empty string" ); + Q_ASSERT_X( sizeof( d ) == sizeof( QChar * ), "TinyTextEntity", + "internal storage is wider than QChar*, fix it!" ); + length = text.length(); + switch ( length ) + { +#if QT_POINTER_SIZE >= 8 + case 4: + d.qc[3] = text.at( 3 ).unicode(); + // fall through + case 3: + d.qc[2] = text.at( 2 ).unicode(); + // fall through +#endif + case 2: + d.qc[1] = text.at( 1 ).unicode(); + // fall through + case 1: + d.qc[0] = text.at( 0 ).unicode(); + break; + default: + d.data = new QChar[ length ]; + std::memcpy( d.data, text.constData(), length * sizeof( QChar ) ); + } + } + + ~TinyTextEntity() + { + if ( length > MaxStaticChars ) + { + delete [] d.data; + } + } + + inline QString text() const + { + return length <= MaxStaticChars ? QString::fromRawData( ( const QChar * )&d.qc[0], length ) + : QString::fromRawData( d.data, length ); + } + + inline NormalizedRect transformedArea( const QTransform &matrix ) const + { + NormalizedRect transformed_area = area; + transformed_area.transform( matrix ); + return transformed_area; + } + + NormalizedRect area; + + private: + Q_DISABLE_COPY( TinyTextEntity ) + + union + { + QChar *data; + ushort qc[MaxStaticChars]; + } d; + int length; +}; + + +TextEntity::TextEntity( const QString &text, NormalizedRect *area ) + : m_text( text ), m_area( area ), d( 0 ) +{ +} + +TextEntity::~TextEntity() +{ + delete m_area; +} + +QString TextEntity::text() const +{ + return m_text; +} + +NormalizedRect* TextEntity::area() const +{ + return m_area; +} + +NormalizedRect TextEntity::transformedArea(const QTransform &matrix) const +{ + NormalizedRect transformed_area = *m_area; + transformed_area.transform( matrix ); + return transformed_area; +} + + +TextPagePrivate::TextPagePrivate() + : m_page( 0 ) +{ +} + +TextPagePrivate::~TextPagePrivate() +{ + qDeleteAll( m_searchPoints ); + qDeleteAll( m_words ); +} + + +TextPage::TextPage() + : d( new TextPagePrivate() ) +{ +} + +TextPage::TextPage( const TextEntity::List &words ) + : d( new TextPagePrivate() ) +{ + TextEntity::List::ConstIterator it = words.constBegin(), itEnd = words.constEnd(); + for ( ; it != itEnd; ++it ) + { + TextEntity *e = *it; + if ( !e->text().isEmpty() ) + d->m_words.append( new TinyTextEntity( e->text(), *e->area() ) ); + delete e; + } +} + +TextPage::~TextPage() +{ + delete d; +} + +void TextPage::append( const QString &text, NormalizedRect *area ) +{ + if ( !text.isEmpty() ) + d->m_words.append( new TinyTextEntity( text.normalized(QString::NormalizationForm_KC), *area ) ); + delete area; +} + +struct WordWithCharacters +{ + WordWithCharacters(TinyTextEntity *w, const TextList &c) + : word(w), characters(c) + { + } + + inline QString text() const + { + return word->text(); + } + + inline const NormalizedRect &area() const + { + return word->area; + } + + TinyTextEntity *word; + TextList characters; +}; +typedef QList WordsWithCharacters; + +/** + * We will divide the whole page in some regions depending on the horizontal and + * vertical spacing among different regions. Each region will have an area and an + * associated WordsWithCharacters in sorted order. +*/ +class RegionText +{ + +public: + RegionText() + { + }; + + RegionText(const WordsWithCharacters &wordsWithCharacters, const QRect &area) + : m_region_wordWithCharacters(wordsWithCharacters), m_area(area) + { + } + + inline QString string() const + { + QString res; + foreach(const WordWithCharacters &word, m_region_wordWithCharacters) + res += word.text(); + return res; + } + + inline WordsWithCharacters text() const + { + return m_region_wordWithCharacters; + } + + inline QRect area() const + { + return m_area; + } + + inline void setArea(const QRect &area) + { + m_area = area; + } + + inline void setText(const WordsWithCharacters &wordsWithCharacters) + { + m_region_wordWithCharacters = wordsWithCharacters; + } + +private: + WordsWithCharacters m_region_wordWithCharacters; + QRect m_area; +}; + +RegularAreaRect * TextPage::textArea ( TextSelection * sel) const +{ + if ( d->m_words.isEmpty() ) + return new RegularAreaRect(); + +/** + It works like this: + There are two cursors, we need to select all the text between them. The coordinates are normalised, leftTop is (0,0) + rightBottom is (1,1), so for cursors start (sx,sy) and end (ex,ey) we start with finding text rectangles under those + points, if not we search for the first that is to the right to it in the same baseline, if none found, then we search + for the first rectangle with a baseline under the cursor, having two points that are the best rectangles to both + of the cursors: (rx,ry)x(tx,ty) for start and (ux,uy)x(vx,vy) for end, we do a + 1. (rx,ry)x(1,ty) + 2. (0,ty)x(1,uy) + 3. (0,uy)x(vx,vy) + + To find the closest rectangle to cursor (cx,cy) we search for a rectangle that either contains the cursor + or that has a left border >= cx and bottom border >= cy. +*/ + RegularAreaRect * ret= new RegularAreaRect; + + const QTransform matrix = d->m_page ? d->m_page->rotationMatrix() : QTransform(); +#if 0 + int it = -1; + int itB = -1; + int itE = -1; + + // ending cursor is higher than start cursor, we need to find positions in reverse + NormalizedRect tmp; + NormalizedRect start; + NormalizedRect end; + + NormalizedPoint startC = sel->start(); + double startCx = startC.x; + double startCy = startC.y; + + NormalizedPoint endC = sel->end(); + double endCx = endC.x; + double endCy = endC.y; + + if ( sel->direction() == 1 || ( sel->itB() == -1 && sel->direction() == 0 ) ) + { +#ifdef DEBUG_TEXTPAGE + kWarning() << "running first loop"; +#endif + const int count = d->m_words.count(); + for ( it = 0; it < count; it++ ) + { + tmp = *d->m_words[ it ]->area(); + if ( tmp.contains( startCx, startCy ) + || ( tmp.top <= startCy && tmp.bottom >= startCy && tmp.left >= startCx ) + || ( tmp.top >= startCy)) + { + /// we have found the (rx,ry)x(tx,ty) + itB = it; +#ifdef DEBUG_TEXTPAGE + kWarning() << "start is" << itB << "count is" << d->m_words.count(); +#endif + break; + } + } + sel->itB( itB ); + } + itB = sel->itB(); +#ifdef DEBUG_TEXTPAGE + kWarning() << "direction is" << sel->direction(); + kWarning() << "reloaded start is" << itB << "against" << sel->itB(); +#endif + if ( sel->direction() == 0 || ( sel->itE() == -1 && sel->direction() == 1 ) ) + { +#ifdef DEBUG_TEXTPAGE + kWarning() << "running second loop"; +#endif + for ( it = d->m_words.count() - 1; it >= itB; it-- ) + { + tmp = *d->m_words[ it ]->area(); + if ( tmp.contains( endCx, endCy ) + || ( tmp.top <= endCy && tmp.bottom >= endCy && tmp.right <= endCx ) + || ( tmp.bottom <= endCy ) ) + { + /// we have found the (ux,uy)x(vx,vy) + itE = it; +#ifdef DEBUG_TEXTPAGE + kWarning() << "ending is" << itE << "count is" << d->m_words.count(); + kWarning() << "conditions" << tmp.contains( endCx, endCy ) << " " + << ( tmp.top <= endCy && tmp.bottom >= endCy && tmp.right <= endCx ) << " " << + ( tmp.top >= endCy); +#endif + break; + } + } + sel->itE( itE ); + } +#ifdef DEBUG_TEXTPAGE + kWarning() << "reloaded ending is" << itE << "against" << sel->itE(); +#endif + + if ( sel->itB() != -1 && sel->itE() != -1 ) + { + start = *d->m_words[ sel->itB() ]->area(); + end = *d->m_words[ sel->itE() ]->area(); + + NormalizedRect first, second, third; + /// finding out if there is more than one baseline between them is a hard and discussable task + /// we will create a rectangle (rx,0)x(tx,1) and will check how many times does it intersect the + /// areas, if more than one -> we have a three or over line selection + first = start; + second.top = start.bottom; + first.right = second.right = 1; + third = end; + third.left = second.left = 0; + second.bottom = end.top; + int selMax = qMax( sel->itB(), sel->itE() ); + for ( it = qMin( sel->itB(), sel->itE() ); it <= selMax; ++it ) + { + tmp = *d->m_words[ it ]->area(); + if ( tmp.intersects( &first ) || tmp.intersects( &second ) || tmp.intersects( &third ) ) + ret->appendShape( d->m_words.at( it )->transformedArea( matrix ) ); + } + } +#else + const double scaleX = d->m_page->m_page->width(); + const double scaleY = d->m_page->m_page->height(); + + NormalizedPoint startC = sel->start(); + NormalizedPoint endC = sel->end(); + NormalizedPoint temp; + + // if startPoint is right to endPoint swap them + if(startC.x > endC.x) + { + temp = startC; + startC = endC; + endC = temp; + } + + // minX,maxX,minY,maxY gives the bounding rectangle coordinates of the document + const NormalizedRect boundingRect = d->m_page->m_page->boundingBox(); + const QRect content = boundingRect.geometry(scaleX,scaleY); + const double minX = content.left(); + const double maxX = content.right(); + const double minY = content.top(); + const double maxY = content.bottom(); + + /** + * We will now find out the TinyTextEntity for the startRectangle and TinyTextEntity for + * the endRectangle. We have four cases: + * + * Case 1(a): both startpoint and endpoint are out of the bounding Rectangle and at one side, so the rectangle made of start + * and endPoint are outof the bounding rect (do not intersect) + * + * Case 1(b): both startpoint and endpoint are out of bounding rect, but they are in different side, so is their rectangle + * + * Case 2(a): find the rectangle which contains start and endpoint and having some + * TextEntity + * + * Case 2(b): if 2(a) fails (if startPoint and endPoint both are unchanged), then we check whether there is any + * TextEntity within the rect made by startPoint and endPoint + * + * Case 3: Now, we may have two type of selection. + * 1. startpoint is left-top of start_end and endpoint is right-bottom + * 2. startpoint is left-bottom of start_end and endpoint is top-right + * + * Also, as 2(b) is passed, we might have it,itEnd or both unchanged, but the fact is that we have + * text within them. so, we need to search for the best suitable textposition for start and end. + * + * Case 3(a): We search the nearest rectangle consisting of some + * TinyTextEntity right to or bottom of the startPoint for selection 01. + * And, for selection 02, we have to search for right and top + * + * Case 3(b): For endpoint, we have to find the point top of or left to + * endpoint if we have selection 01. + * Otherwise, the search will be left and bottom + */ + + // we know that startC.x > endC.x, we need to decide which is top and which is bottom + const NormalizedRect start_end = (startC.y < endC.y) ? NormalizedRect(startC.x, startC.y, endC.x, endC.y) + : NormalizedRect(startC.x, endC.y, endC.x, startC.y); + + // Case 1(a) + if(!boundingRect.intersects(start_end)) return ret; + + // case 1(b) + /** + note that, after swapping of start and end, we know that, + start is always left to end. but, we cannot say start is + positioned upper than end. + **/ + else + { + // if start is left to content rect take it to content rect boundary + if(startC.x * scaleX < minX) startC.x = minX/scaleX; + if(endC.x * scaleX > maxX) endC.x = maxX/scaleX; + + // if start is top to end (selection type 01) + if(startC.y * scaleY < minY) startC.y = minY/scaleY; + if(endC.y * scaleY > maxY) endC.y = maxY/scaleY; + + // if start is bottom to end (selection type 02) + if(startC.y * scaleY > maxY) startC.y = maxY/scaleY; + if(endC.y * scaleY < minY) endC.y = minY/scaleY; + } + + TextList::ConstIterator it = d->m_words.constBegin(), itEnd = d->m_words.constEnd(); + TextList::ConstIterator start = it, end = itEnd, tmpIt = it; //, tmpItEnd = itEnd; + const MergeSide side = d->m_page ? (MergeSide)d->m_page->m_page->totalOrientation() : MergeRight; + + NormalizedRect tmp; + //case 2(a) + for ( ; it != itEnd; ++it ) + { + tmp = (*it)->area; + if(tmp.contains(startC.x,startC.y)){ + start = it; + } + if(tmp.contains(endC.x,endC.y)){ + end = it; + } + } + + //case 2(b) + it = tmpIt; + if(start == it && end == itEnd) + { + for ( ; it != itEnd; ++it ) + { + // is there any text reactangle within the start_end rect + tmp = (*it)->area; + if(start_end.intersects(tmp)) + break; + } + + // we have searched every text entities, but none is within the rectangle created by start and end + // so, no selection should be done + if(it == itEnd) + { + return ret; + } + } + it = tmpIt; + bool selection_two_start = false; + + //case 3.a + if(start == it) + { + bool flagV = false; + NormalizedRect rect; + + // selection type 01 + if(startC.y <= endC.y) + { + for ( ; it != itEnd; ++it ) + { + rect= (*it)->area; + rect.isBottom(startC) ? flagV = false: flagV = true; + + if(flagV && rect.isRight(startC)) + { + start = it; + break; + } + } + } + + //selection type 02 + else + { + selection_two_start = true; + int distance = scaleX + scaleY + 100; + int count = 0; + + for ( ; it != itEnd; ++it ) + { + rect= (*it)->area; + + if(rect.isBottomOrLevel(startC) && rect.isRight(startC)) + { + count++; + QRect entRect = rect.geometry(scaleX,scaleY); + int xdist, ydist; + xdist = entRect.center().x() - startC.x * scaleX; + ydist = entRect.center().y() - startC.y * scaleY; + + //make them positive + if(xdist < 0) xdist = -xdist; + if(ydist < 0) ydist = -ydist; + + if( (xdist + ydist) < distance) + { + distance = xdist+ ydist; + start = it; + } + } + } + } + } + + //case 3.b + if(end == itEnd) + { + it = tmpIt; + itEnd = itEnd-1; + + bool flagV = false; + NormalizedRect rect; + + if(startC.y <= endC.y) + { + for ( ; itEnd >= it; itEnd-- ) + { + rect= (*itEnd)->area; + rect.isTop(endC) ? flagV = false: flagV = true; + + if(flagV && rect.isLeft(endC)) + { + end = itEnd; + break; + } + } + } + + else + { + int distance = scaleX + scaleY + 100; + for ( ; itEnd >= it; itEnd-- ) + { + rect= (*itEnd)->area; + + if(rect.isTopOrLevel(endC) && rect.isLeft(endC)) + { + QRect entRect = rect.geometry(scaleX,scaleY); + int xdist, ydist; + xdist = entRect.center().x() - endC.x * scaleX; + ydist = entRect.center().y() - endC.y * scaleY; + + //make them positive + if(xdist < 0) xdist = -xdist; + if(ydist < 0) ydist = -ydist; + + if( (xdist + ydist) < distance) + { + distance = xdist+ ydist; + end = itEnd; + } + + } + } + } + } + + /* if start and end in selection 02 are in the same column, and we + start at an empty space we have to remove the selection of last + character + */ + if(selection_two_start) + { + if(start > end) + { + start = start - 1; + } + } + + // if start is less than end swap them + if(start > end) + { + it = start; + start = end; + end = it; + } + + // removes the possibility of crash, in case none of 1 to 3 is true + if(end == d->m_words.constEnd()) end--; + + for( ;start <= end ; start++) + { + ret->appendShape( (*start)->transformedArea( matrix ), side ); + } + +#endif + + return ret; +} + + +RegularAreaRect* TextPage::findText( int searchID, const QString &query, SearchDirection direct, + Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *area ) +{ + SearchDirection dir=direct; + // invalid search request + if ( d->m_words.isEmpty() || query.isEmpty() || ( area && area->isNull() ) ) + return 0; + TextList::ConstIterator start; + int start_offset = 0; + TextList::ConstIterator end; + const QMap< int, SearchPoint* >::const_iterator sIt = d->m_searchPoints.constFind( searchID ); + if ( sIt == d->m_searchPoints.constEnd() ) + { + // if no previous run of this search is found, then set it to start + // from the beginning (respecting the search direction) + if ( dir == NextResult ) + dir = FromTop; + else if ( dir == PreviousResult ) + dir = FromBottom; + } + bool forward = true; + switch ( dir ) + { + case FromTop: + start = d->m_words.constBegin(); + start_offset = 0; + end = d->m_words.constEnd(); + break; + case FromBottom: + start = d->m_words.constEnd(); + start_offset = 0; + end = d->m_words.constBegin(); + forward = false; + break; + case NextResult: + start = (*sIt)->it_end; + start_offset = (*sIt)->offset_end; + end = d->m_words.constEnd(); + break; + case PreviousResult: + start = (*sIt)->it_begin; + start_offset = (*sIt)->offset_begin; + end = d->m_words.constBegin(); + forward = false; + break; + }; + RegularAreaRect* ret = 0; + const TextComparisonFunction cmpFn = caseSensitivity == Qt::CaseSensitive + ? CaseSensitiveCmpFn : CaseInsensitiveCmpFn; + if ( forward ) + { + ret = d->findTextInternalForward( searchID, query, cmpFn, start, start_offset, end ); + } + else + { + ret = d->findTextInternalBackward( searchID, query, cmpFn, start, start_offset, end ); + } + return ret; +} + +// hyphenated '-' must be at the end of a word, so hyphenation means +// we have a '-' just followed by a '\n' character +// check if the string contains a '-' character +// if the '-' is the last entry +static int stringLengthAdaptedWithHyphen(const QString &str, const TextList::ConstIterator &it, const TextList::ConstIterator &textListEnd) +{ + int len = str.length(); + + // hyphenated '-' must be at the end of a word, so hyphenation means + // we have a '-' just followed by a '\n' character + // check if the string contains a '-' character + // if the '-' is the last entry + if ( str.endsWith( '-' ) ) + { + // validity chek of it + 1 + if ( ( it + 1 ) != textListEnd ) + { + // 1. if the next character is '\n' + const QString &lookahedStr = (*(it+1))->text(); + if (lookahedStr.startsWith('\n')) + { + len -= 1; + } + else + { + // 2. if the next word is in a different line or not + const NormalizedRect& hyphenArea = (*it)->area; + const NormalizedRect& lookaheadArea = (*(it + 1))->area; + + // lookahead to check whether both the '-' rect and next character rect overlap + if( !doesConsumeY( hyphenArea, lookaheadArea, 70 ) ) + { + len -= 1; + } + } + } + } + // else if it is the second last entry - for example in pdf format + else if (str.endsWith("-\n")) + { + len -= 2; + } + + return len; +} + +RegularAreaRect* TextPagePrivate::searchPointToArea(const SearchPoint* sp) +{ + const QTransform matrix = m_page ? m_page->rotationMatrix() : QTransform(); + RegularAreaRect* ret=new RegularAreaRect; + + for (TextList::ConstIterator it = sp->it_begin; ; it++) + { + const TinyTextEntity* curEntity = *it; + ret->append( curEntity->transformedArea( matrix ) ); + + if (it == sp->it_end) { + break; + } + } + + ret->simplify(); + return ret; +} + +RegularAreaRect* TextPagePrivate::findTextInternalForward( int searchID, const QString &_query, + TextComparisonFunction comparer, + const TextList::ConstIterator &start, + int start_offset, + const TextList::ConstIterator &end) +{ + // normalize query search all unicode (including glyphs) + const QString query = _query.normalized(QString::NormalizationForm_KC); + + // j is the current position in our query + // len is the length of the string in TextEntity + // queryLeft is the length of the query we have left + int j=0, queryLeft=query.length(); + + TextList::ConstIterator it = start; + int offset = start_offset; + + TextList::ConstIterator it_begin = TextList::ConstIterator(); + int offset_begin = 0; //dummy initial value to suppress compiler warnings + + while ( it != end ) + { + const TinyTextEntity* curEntity = *it; + const QString& str = curEntity->text(); + int len = stringLengthAdaptedWithHyphen(str, it, m_words.constEnd()); + + if (offset >= len) + { + it++; + offset = 0; + continue; + } + + if ( it_begin == TextList::ConstIterator() ) + { + it_begin = it; + offset_begin = offset; + } + + int min=qMin(queryLeft,len-offset); + { +#ifdef DEBUG_TEXTPAGE + kDebug(OkularDebug) << str.midRef(offset, min) << ":" << _query.midRef(j, min); +#endif + // we have equal (or less than) area of the query left as the length of the current + // entity + + if ( !comparer( str.midRef( offset, min ), query.midRef( j, min ) ) ) + { + // we have not matched + // this means we do not have a complete match + // we need to get back to query start + // and continue the search from this place +#ifdef DEBUG_TEXTPAGE + kDebug(OkularDebug) << "\tnot matched"; +#endif + j = 0; + queryLeft=query.length(); + it = it_begin; + offset = offset_begin+1; + it_begin = TextList::ConstIterator(); + } + else + { + // we have a match + // move the current position in the query + // to the position after the length of this string + // we matched + // subtract the length of the current entity from + // the left length of the query +#ifdef DEBUG_TEXTPAGE + kDebug(OkularDebug) << "\tmatched"; +#endif + j += min; + queryLeft -= min; + + if (queryLeft==0) + { + // save or update the search point for the current searchID + QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); + if ( sIt == m_searchPoints.end() ) + { + sIt = m_searchPoints.insert( searchID, new SearchPoint ); + } + SearchPoint* sp = *sIt; + sp->it_begin = it_begin; + sp->it_end = it; + sp->offset_begin = offset_begin; + sp->offset_end = offset + min; + return searchPointToArea(sp); + } + + it++; + offset = 0; + } + } + } + // end of loop - it means that we've ended the textentities + + const QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); + if ( sIt != m_searchPoints.end() ) + { + SearchPoint* sp = *sIt; + m_searchPoints.erase( sIt ); + delete sp; + } + return 0; +} + +RegularAreaRect* TextPagePrivate::findTextInternalBackward( int searchID, const QString &_query, + TextComparisonFunction comparer, + const TextList::ConstIterator &start, + int start_offset, + const TextList::ConstIterator &end) +{ + // normalize query to search all unicode (including glyphs) + const QString query = _query.normalized(QString::NormalizationForm_KC); + + // j is the current position in our query + // len is the length of the string in TextEntity + // queryLeft is the length of the query we have left + int j=query.length(), queryLeft=query.length(); + + TextList::ConstIterator it = start; + int offset = start_offset; + + TextList::ConstIterator it_begin = TextList::ConstIterator(); + int offset_begin = 0; //dummy initial value to suppress compiler warnings + + while ( true ) + { + if (offset <= 0) + { + if ( it == end ) + { + break; + } + it--; + } + + const TinyTextEntity* curEntity = *it; + const QString& str = curEntity->text(); + int len = stringLengthAdaptedWithHyphen(str, it, m_words.constEnd()); + + if (offset <= 0) + { + offset = len; + } + + if ( it_begin == TextList::ConstIterator() ) + { + it_begin = it; + offset_begin = offset; + } + + int min=qMin(queryLeft,offset); + { +#ifdef DEBUG_TEXTPAGE + kDebug(OkularDebug) << str.midRef(offset-min, min) << " : " << _query.midRef(j-min, min); +#endif + // we have equal (or less than) area of the query left as the length of the current + // entity + + // Note len is not str.length() so we can't use rightRef here + if ( !comparer( str.midRef(offset-min, min ), query.midRef( j - min, min ) ) ) + { + // we have not matched + // this means we do not have a complete match + // we need to get back to query start + // and continue the search from this place +#ifdef DEBUG_TEXTPAGE + kDebug(OkularDebug) << "\tnot matched"; +#endif + + j = query.length(); + queryLeft = query.length(); + it = it_begin; + offset = offset_begin-1; + it_begin = TextList::ConstIterator(); + } + else + { + // we have a match + // move the current position in the query + // to the position after the length of this string + // we matched + // subtract the length of the current entity from + // the left length of the query +#ifdef DEBUG_TEXTPAGE + kDebug(OkularDebug) << "\tmatched"; +#endif + j -= min; + queryLeft -= min; + + if ( queryLeft == 0 ) + { + // save or update the search point for the current searchID + QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); + if ( sIt == m_searchPoints.end() ) + { + sIt = m_searchPoints.insert( searchID, new SearchPoint ); + } + SearchPoint* sp = *sIt; + sp->it_begin = it; + sp->it_end = it_begin; + sp->offset_begin = offset - min; + sp->offset_end = offset_begin; + return searchPointToArea(sp); + } + + offset = 0; + } + + } + + } + // end of loop - it means that we've ended the textentities + + const QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); + if ( sIt != m_searchPoints.end() ) + { + SearchPoint* sp = *sIt; + m_searchPoints.erase( sIt ); + delete sp; + } + return 0; +} + +QString TextPage::text(const RegularAreaRect *area) const +{ + return text(area, AnyPixelTextAreaInclusionBehaviour); +} + +QString TextPage::text(const RegularAreaRect *area, TextAreaInclusionBehaviour b) const +{ + if ( area && area->isNull() ) + return QString(); + + TextList::ConstIterator it = d->m_words.constBegin(), itEnd = d->m_words.constEnd(); + QString ret; + if ( area ) + { + for ( ; it != itEnd; ++it ) + { + if (b == AnyPixelTextAreaInclusionBehaviour) + { + if ( area->intersects( (*it)->area ) ) + { + ret += (*it)->text(); + } + } + else + { + NormalizedPoint center = (*it)->area.center(); + if ( area->contains( center.x, center.y ) ) + { + ret += (*it)->text(); + } + } + } + } + else + { + for ( ; it != itEnd; ++it ) + ret += (*it)->text(); + } + return ret; +} + +static bool compareTinyTextEntityX(const WordWithCharacters &first, const WordWithCharacters &second) +{ + QRect firstArea = first.area().roundedGeometry(1000,1000); + QRect secondArea = second.area().roundedGeometry(1000,1000); + + return firstArea.left() < secondArea.left(); +} + +static bool compareTinyTextEntityY(const WordWithCharacters &first, const WordWithCharacters &second) +{ + const QRect firstArea = first.area().roundedGeometry(1000,1000); + const QRect secondArea = second.area().roundedGeometry(1000,1000); + + return firstArea.top() < secondArea.top(); +} + +/** + * Sets a new world list. Deleting the contents of the old one + */ +void TextPagePrivate::setWordList(const TextList &list) +{ + qDeleteAll(m_words); + m_words = list; +} + +/** + * Remove all the spaces in between texts. It will make all the generators + * same, whether they save spaces(like pdf) or not(like djvu). + */ +static void removeSpace(TextList *words) +{ + TextList::Iterator it = words->begin(); + const QString str(' '); + + while ( it != words->end() ) + { + if((*it)->text() == str) + { + it = words->erase(it); + } + else + { + ++it; + } + } +} + +/** + * We will read the TinyTextEntity from characters and try to create words from there. + * Note: characters might be already characters for some generators, but we will keep + * the nomenclature characters for the generator produced data. The resulting + * WordsWithCharacters memory has to be managed by the caller, both the + * WordWithCharacters::word and WordWithCharacters::characters contents + */ +static WordsWithCharacters makeWordFromCharacters(const TextList &characters, int pageWidth, int pageHeight) +{ + /** + * We will traverse characters and try to create words from the TinyTextEntities in it. + * We will search TinyTextEntity blocks and merge them until we get a + * space between two consecutive TinyTextEntities. When we get a space + * we can take it as a end of word. Then we store the word as a TinyTextEntity + * and keep it in newList. + + * We create a RegionText named regionWord that contains the word and the characters associated with it and + * a rectangle area of the element in newList. + + */ + WordsWithCharacters wordsWithCharacters; + + TextList::ConstIterator it = characters.begin(), itEnd = characters.end(), tmpIt; + int newLeft,newRight,newTop,newBottom; + int index = 0; + + for( ; it != itEnd ; it++) + { + QString textString = (*it)->text(); + QString newString; + QRect lineArea = (*it)->area.roundedGeometry(pageWidth,pageHeight),elementArea; + TextList wordCharacters; + tmpIt = it; + int space = 0; + + while (!space) + { + if (textString.length()) + { + newString.append(textString); + + // when textString is the start of the word + if (tmpIt == it) + { + NormalizedRect newRect(lineArea,pageWidth,pageHeight); + wordCharacters.append(new TinyTextEntity(textString.normalized + (QString::NormalizationForm_KC), newRect)); + } + else + { + NormalizedRect newRect(elementArea,pageWidth,pageHeight); + wordCharacters.append(new TinyTextEntity(textString.normalized + (QString::NormalizationForm_KC), newRect)); + } + } + + ++it; + + /* + we must have to put this line before the if condition of it==itEnd + otherwise the last character can be missed + */ + if (it == itEnd) break; + elementArea = (*it)->area.roundedGeometry(pageWidth,pageHeight); + if (!doesConsumeY(elementArea, lineArea, 60)) + { + --it; + break; + } + + const int text_y1 = elementArea.top() , + text_x1 = elementArea.left(), + text_y2 = elementArea.y() + elementArea.height(), + text_x2 = elementArea.x() + elementArea.width(); + const int line_y1 = lineArea.top() ,line_x1 = lineArea.left(), + line_y2 = lineArea.y() + lineArea.height(), + line_x2 = lineArea.x() + lineArea.width(); + + space = elementArea.left() - lineArea.right(); + + if (space != 0) + { + it--; + break; + } + + newLeft = text_x1 < line_x1 ? text_x1 : line_x1; + newRight = line_x2 > text_x2 ? line_x2 : text_x2; + newTop = text_y1 > line_y1 ? line_y1 : text_y1; + newBottom = text_y2 > line_y2 ? text_y2 : line_y2; + + lineArea.setLeft (newLeft); + lineArea.setTop (newTop); + lineArea.setWidth( newRight - newLeft ); + lineArea.setHeight( newBottom - newTop ); + + textString = (*it)->text(); + } + + // if newString is not empty, save it + if (!newString.isEmpty()) + { + const NormalizedRect newRect(lineArea, pageWidth, pageHeight); + TinyTextEntity *word = new TinyTextEntity(newString.normalized(QString::NormalizationForm_KC), newRect); + wordsWithCharacters.append(WordWithCharacters(word, wordCharacters)); + + index++; + } + + if(it == itEnd) break; + } + + return wordsWithCharacters; +} + +/** + * Create Lines from the words and sort them + */ +QList< QPair > makeAndSortLines(const WordsWithCharacters &wordsTmp, int pageWidth, int pageHeight) +{ + /** + * We cannot assume that the generator will give us texts in the right order. + * We can only assume that we will get texts in the page and their bounding + * rectangle. The texts can be character, word, half-word anything. + * So, we need to: + ** + * 1. Sort rectangles/boxes containing texts by y0(top) + * 2. Create textline where there is y overlap between TinyTextEntity 's + * 3. Within each line sort the TinyTextEntity 's by x0(left) + */ + + QList< QPair > lines; + + /* + Make a new copy of the TextList in the words, so that the wordsTmp and lines do + not contain same pointers for all the TinyTextEntity. + */ + QList words = wordsTmp; + + // Step 1 + qSort(words.begin(),words.end(),compareTinyTextEntityY); + + // Step 2 + QList::Iterator it = words.begin(), itEnd = words.end(); + + //for every non-space texts(characters/words) in the textList + for( ; it != itEnd ; it++) + { + const QRect elementArea = (*it).area().roundedGeometry(pageWidth,pageHeight); + bool found = false; + + for( int i = 0 ; i < lines.length() ; i++) + { + /* the line area which will be expanded + line_rects is only necessary to preserve the topmin and bottommax of all + the texts in the line, left and right is not necessary at all + */ + QRect &lineArea = lines[i].second; + const int text_y1 = elementArea.top() , + text_y2 = elementArea.top() + elementArea.height() , + text_x1 = elementArea.left(), + text_x2 = elementArea.left() + elementArea.width(); + const int line_y1 = lineArea.top() , + line_y2 = lineArea.top() + lineArea.height(), + line_x1 = lineArea.left(), + line_x2 = lineArea.left() + lineArea.width(); + + /* + if the new text and the line has y overlapping parts of more than 70%, + the text will be added to this line + */ + if(doesConsumeY(elementArea,lineArea,70)) + { + WordsWithCharacters &line = lines[i].first; + line.append(*it); + + const int newLeft = line_x1 < text_x1 ? line_x1 : text_x1; + const int newRight = line_x2 > text_x2 ? line_x2 : text_x2; + const int newTop = line_y1 < text_y1 ? line_y1 : text_y1; + const int newBottom = text_y2 > line_y2 ? text_y2 : line_y2; + + lineArea = QRect( newLeft,newTop, newRight - newLeft, newBottom - newTop ); + found = true; + } + + if(found) break; + } + + /* when we have found a new line create a new TextList containing + only one element and append it to the lines + */ + if(!found) + { + WordsWithCharacters tmp; + tmp.append((*it)); + lines.append(QPair(tmp, elementArea)); + } + } + + // Step 3 + for(int i = 0 ; i < lines.length() ; i++) + { + WordsWithCharacters &list = lines[i].first; + qSort(list.begin(), list.end(), compareTinyTextEntityX); + } + + return lines; +} + +/** + * Calculate Statistical information from the lines we made previously + */ +static void calculateStatisticalInformation(const QList &words, int pageWidth, int pageHeight, int *word_spacing, int *line_spacing, int *col_spacing) +{ + /** + * For the region, defined by line_rects and lines + * 1. Make line statistical analysis to find the line spacing + * 2. Make character statistical analysis to differentiate between + * word spacing and column spacing. + */ + + /** + * Step 0 + */ + const QList< QPair > sortedLines = makeAndSortLines(words, pageWidth, pageHeight); + + /** + * Step 1 + */ + QMap line_space_stat; + for(int i = 0 ; i < sortedLines.length(); i++) + { + const QRect rectUpper = sortedLines.at(i).second; + + if(i+1 == sortedLines.length()) break; + const QRect rectLower = sortedLines.at(i+1).second; + + int linespace = rectLower.top() - (rectUpper.top() + rectUpper.height()); + if(linespace < 0) linespace =-linespace; + + if(line_space_stat.contains(linespace)) + line_space_stat[linespace]++; + else line_space_stat[linespace] = 1; + } + + *line_spacing = 0; + int weighted_count = 0; + QMapIterator iterate_linespace(line_space_stat); + + while(iterate_linespace.hasNext()) + { + iterate_linespace.next(); + *line_spacing += iterate_linespace.value() * iterate_linespace.key(); + weighted_count += iterate_linespace.value(); + } + if (*line_spacing != 0) + *line_spacing = (int) ( (double)*line_spacing / (double) weighted_count + 0.5); + + /** + * Step 2 + */ + // We would like to use QMap instead of QHash as it will keep the keys sorted + QMap hor_space_stat; + QMap col_space_stat; + QList< QList > space_rects; + QList max_hor_space_rects; + + // Space in every line + for(int i = 0 ; i < sortedLines.length() ; i++) + { + const WordsWithCharacters list = sortedLines.at(i).first; + QList line_space_rects; + int maxSpace = 0, minSpace = pageWidth; + + // for every TinyTextEntity element in the line + WordsWithCharacters::ConstIterator it = list.begin(), itEnd = list.end(); + QRect max_area1,max_area2; + QString before_max, after_max; + + // for every line + for( ; it != itEnd ; it++ ) + { + const QRect area1 = (*it).area().roundedGeometry(pageWidth,pageHeight); + if( it+1 == itEnd ) break; + + const QRect area2 = (*(it+1)).area().roundedGeometry(pageWidth,pageHeight); + int space = area2.left() - area1.right(); + + if(space > maxSpace) + { + max_area1 = area1; + max_area2 = area2; + maxSpace = space; + before_max = (*it).text(); + after_max = (*(it+1)).text(); + } + + if(space < minSpace && space != 0) minSpace = space; + + //if we found a real space, whose length is not zero and also less than the pageWidth + if(space != 0 && space != pageWidth) + { + // increase the count of the space amount + if(hor_space_stat.contains(space)) hor_space_stat[space]++; + else hor_space_stat[space] = 1; + + int left,right,top,bottom; + + left = area1.right(); + right = area2.left(); + + top = area2.top() < area1.top() ? area2.top() : area1.top(); + bottom = area2.bottom() > area1.bottom() ? area2.bottom() : area1.bottom(); + + QRect rect(left,top,right-left,bottom-top); + line_space_rects.append(rect); + } + } + + space_rects.append(line_space_rects); + + if(hor_space_stat.contains(maxSpace)) + { + if(hor_space_stat[maxSpace] != 1) + hor_space_stat[maxSpace]--; + else hor_space_stat.remove(maxSpace); + } + + if(maxSpace != 0) + { + if (col_space_stat.contains(maxSpace)) + col_space_stat[maxSpace]++; + else col_space_stat[maxSpace] = 1; + + //store the max rect of each line + const int left = max_area1.right(); + const int right = max_area2.left(); + const int top = (max_area1.top() > max_area2.top()) ? max_area2.top() : + max_area1.top(); + const int bottom = (max_area1.bottom() < max_area2.bottom()) ? max_area2.bottom() : + max_area1.bottom(); + + const QRect rect(left,top,right-left,bottom-top); + max_hor_space_rects.append(rect); + } + else max_hor_space_rects.append(QRect(0,0,0,0)); + } + + // All the between word space counts are in hor_space_stat + *word_spacing = 0; + weighted_count = 0; + QMapIterator iterate(hor_space_stat); + + while (iterate.hasNext()) + { + iterate.next(); + + if(iterate.key() > 0) + { + *word_spacing += iterate.value() * iterate.key(); + weighted_count += iterate.value(); + } + } + if(weighted_count) + *word_spacing = (int) ((double)*word_spacing / (double)weighted_count + 0.5); + + *col_spacing = 0; + QMapIterator iterate_col(col_space_stat); + + while (iterate_col.hasNext()) + { + iterate_col.next(); + if(iterate_col.value() > *col_spacing) *col_spacing = iterate_col.value(); + } + *col_spacing = col_space_stat.key(*col_spacing); + + // if there is just one line in a region, there is no point in dividing it + if(sortedLines.length() == 1) + *word_spacing = *col_spacing; +} + +/** + * Implements the XY Cut algorithm for textpage segmentation + * The resulting RegionTextList will contain RegionText whose WordsWithCharacters::word and + * WordsWithCharacters::characters are reused from wordsWithCharacters (i.e. no new nor delete happens in this function) + */ +static RegionTextList XYCutForBoundingBoxes(const QList &wordsWithCharacters, const NormalizedRect &boundingBox, int pageWidth, int pageHeight) +{ + RegionTextList tree; + QRect contentRect(boundingBox.geometry(pageWidth,pageHeight)); + const RegionText root(wordsWithCharacters, contentRect); + + // start the tree with the root, it is our only region at the start + tree.push_back(root); + + int i = 0; + + // while traversing the tree has not been ended + while(i < tree.length()) + { + const RegionText node = tree.at(i); + QRect regionRect = node.area(); + + /** + * 1. calculation of projection profiles + */ + // allocate the size of proj profiles and initialize with 0 + int size_proj_y = node.area().height(); + int size_proj_x = node.area().width(); + //dynamic memory allocation + QVarLengthArray proj_on_xaxis(size_proj_x); + QVarLengthArray proj_on_yaxis(size_proj_y); + + for( int j = 0 ; j < size_proj_y ; ++j ) proj_on_yaxis[j] = 0; + for( int j = 0 ; j < size_proj_x ; ++j ) proj_on_xaxis[j] = 0; + + const QList list = node.text(); + + // Calculate tcx and tcy locally for each new region + int word_spacing, line_spacing, column_spacing; + calculateStatisticalInformation(list, pageWidth, pageHeight, &word_spacing, &line_spacing, &column_spacing); + + const int tcx = word_spacing * 2; + const int tcy = line_spacing * 2; + + int maxX = 0 , maxY = 0; + int avgX = 0; + int count; + + // for every text in the region + for(int j = 0 ; j < list.length() ; ++j ) + { + TinyTextEntity *ent = list.at(j).word; + const QRect entRect = ent->area.geometry(pageWidth, pageHeight); + + // calculate vertical projection profile proj_on_xaxis1 + for(int k = entRect.left() ; k <= entRect.left() + entRect.width() ; ++k) + { + if( ( k-regionRect.left() ) < size_proj_x && ( k-regionRect.left() ) >= 0 ) + proj_on_xaxis[k - regionRect.left()] += entRect.height(); + } + + // calculate horizontal projection profile in the same way + for(int k = entRect.top() ; k <= entRect.top() + entRect.height() ; ++k) + { + if( ( k-regionRect.top() ) < size_proj_y && ( k-regionRect.top() ) >= 0 ) + proj_on_yaxis[k - regionRect.top()] += entRect.width(); + } + } + + for( int j = 0 ; j < size_proj_y ; ++j ) + { + if (proj_on_yaxis[j] > maxY) + maxY = proj_on_yaxis[j]; + } + + avgX = count = 0; + for( int j = 0 ; j < size_proj_x ; ++j ) + { + if(proj_on_xaxis[j] > maxX) maxX = proj_on_xaxis[j]; + if(proj_on_xaxis[j]) + { + count++; + avgX+= proj_on_xaxis[j]; + } + } + if(count) avgX /= count; + + + /** + * 2. Cleanup Boundary White Spaces and removal of noise + */ + int xbegin = 0, xend = size_proj_x - 1; + int ybegin = 0, yend = size_proj_y - 1; + while(xbegin < size_proj_x && proj_on_xaxis[xbegin] <= 0) + xbegin++; + while(xend >= 0 && proj_on_xaxis[xend] <= 0) + xend--; + while(ybegin < size_proj_y && proj_on_yaxis[ybegin] <= 0) + ybegin++; + while(yend >= 0 && proj_on_yaxis[yend] <= 0) + yend--; + + //update the regionRect + int old_left = regionRect.left(), old_top = regionRect.top(); + regionRect.setLeft(old_left + xbegin); + regionRect.setRight(old_left + xend); + regionRect.setTop(old_top + ybegin); + regionRect.setBottom(old_top + yend); + + int tnx = (int)((double)avgX * 10.0 / 100.0 + 0.5), tny = 0; + for( int j = 0 ; j < size_proj_x ; ++j ) + proj_on_xaxis[j] -= tnx; + for( int j = 0 ; j < size_proj_y ; ++j ) + proj_on_yaxis[j] -= tny; + + /** + * 3. Find the Widest gap + */ + int gap_hor = -1, pos_hor = -1; + int begin = -1, end = -1; + + // find all hor_gaps and find the maximum between them + for(int j = 1 ; j < size_proj_y ; ++j) + { + //transition from white to black + if(begin >= 0 && proj_on_yaxis[j-1] <= 0 + && proj_on_yaxis[j] > 0) + end = j; + + //transition from black to white + if(proj_on_yaxis[j-1] > 0 && proj_on_yaxis[j] <= 0) + begin = j; + + if(begin > 0 && end > 0 && end-begin > gap_hor) + { + gap_hor = end - begin; + pos_hor = (end + begin) / 2; + begin = -1; + end = -1; + } + } + + + begin = -1, end = -1; + int gap_ver = -1, pos_ver = -1; + + //find all the ver_gaps and find the maximum between them + for(int j = 1 ; j < size_proj_x ; ++j) + { + //transition from white to black + if(begin >= 0 && proj_on_xaxis[j-1] <= 0 + && proj_on_xaxis[j] > 0){ + end = j; + } + + //transition from black to white + if(proj_on_xaxis[j-1] > 0 && proj_on_xaxis[j] <= 0) + begin = j; + + if(begin > 0 && end > 0 && end-begin > gap_ver) + { + gap_ver = end - begin; + pos_ver = (end + begin) / 2; + begin = -1; + end = -1; + } + } + + int cut_pos_x = pos_ver, cut_pos_y = pos_hor; + int gap_x = gap_ver, gap_y = gap_hor; + + /** + * 4. Cut the region and make nodes (left,right) or (up,down) + */ + bool cut_hor = false, cut_ver = false; + + // For horizontal cut + const int topHeight = cut_pos_y - (regionRect.top() - old_top); + const QRect topRect(regionRect.left(), + regionRect.top(), + regionRect.width(), + topHeight); + const QRect bottomRect(regionRect.left(), + regionRect.top() + topHeight, + regionRect.width(), + regionRect.height() - topHeight ); + + // For vertical Cut + const int leftWidth = cut_pos_x - (regionRect.left() - old_left); + const QRect leftRect(regionRect.left(), + regionRect.top(), + leftWidth, + regionRect.height()); + const QRect rightRect(regionRect.left() + leftWidth, + regionRect.top(), + regionRect.width() - leftWidth, + regionRect.height()); + + if(gap_y >= gap_x && gap_y >= tcy) + cut_hor = true; + else if(gap_y >= gap_x && gap_y <= tcy && gap_x >= tcx) + cut_ver = true; + else if(gap_x >= gap_y && gap_x >= tcx) + cut_ver = true; + else if(gap_x >= gap_y && gap_x <= tcx && gap_y >= tcy) + cut_hor = true; + // no cut possible + else + { + // we can now update the node rectangle with the shrinked rectangle + RegionText tmpNode = tree.at(i); + tmpNode.setArea(regionRect); + tree.replace(i,tmpNode); + i++; + continue; + } + + WordsWithCharacters list1,list2; + + // horizontal cut, topRect and bottomRect + if(cut_hor) + { + for( int j = 0 ; j < list.length() ; ++j ) + { + const WordWithCharacters word = list.at(j); + const QRect wordRect = word.area().geometry(pageWidth,pageHeight); + + if(topRect.intersects(wordRect)) + list1.append(word); + else + list2.append(word); + } + + RegionText node1(list1,topRect); + RegionText node2(list2,bottomRect); + + tree.replace(i,node1); + tree.insert(i+1,node2); + } + + //vertical cut, leftRect and rightRect + else if(cut_ver) + { + for( int j = 0 ; j < list.length() ; ++j ) + { + const WordWithCharacters word = list.at(j); + const QRect wordRect = word.area().geometry(pageWidth,pageHeight); + + if(leftRect.intersects(wordRect)) + list1.append(word); + else + list2.append(word); + } + + RegionText node1(list1,leftRect); + RegionText node2(list2,rightRect); + + tree.replace(i,node1); + tree.insert(i+1,node2); + } + } + + return tree; +} + +/** + * Add spaces in between words in a line. It reuses the pointers passed in tree and might add new ones. You will need to take care of deleting them if needed + */ +WordsWithCharacters addNecessarySpace(RegionTextList tree, int pageWidth, int pageHeight) +{ + /** + * 1. Call makeAndSortLines before adding spaces in between words in a line + * 2. Now add spaces between every two words in a line + * 3. Finally, extract all the space separated texts from each region and return it + */ + + // Only change the texts under RegionTexts, not the area + for(int j = 0 ; j < tree.length() ; j++) + { + RegionText &tmpRegion = tree[j]; + + // Step 01 + QList< QPair > sortedLines = makeAndSortLines(tmpRegion.text(), pageWidth, pageHeight); + + // Step 02 + for(int i = 0 ; i < sortedLines.length() ; i++) + { + WordsWithCharacters &list = sortedLines[i].first; + for(int k = 0 ; k < list.length() ; k++ ) + { + const QRect area1 = list.at(k).area().roundedGeometry(pageWidth,pageHeight); + if( k+1 >= list.length() ) break; + + const QRect area2 = list.at(k+1).area().roundedGeometry(pageWidth,pageHeight); + const int space = area2.left() - area1.right(); + + if(space != 0) + { + // Make a TinyTextEntity of string space and push it between it and it+1 + const int left = area1.right(); + const int right = area2.left(); + const int top = area2.top() < area1.top() ? area2.top() : area1.top(); + const int bottom = area2.bottom() > area1.bottom() ? area2.bottom() : area1.bottom(); + + const QString spaceStr(" "); + const QRect rect(QPoint(left,top),QPoint(right,bottom)); + const NormalizedRect entRect(rect,pageWidth,pageHeight); + TinyTextEntity *ent1 = new TinyTextEntity(spaceStr, entRect); + TinyTextEntity *ent2 = new TinyTextEntity(spaceStr, entRect); + WordWithCharacters word(ent1, QList() << ent2); + + list.insert(k+1, word); + + // Skip the space + k++; + } + } + } + + WordsWithCharacters tmpList; + for(int i = 0 ; i < sortedLines.length() ; i++) + { + tmpList += sortedLines.at(i).first; + } + tmpRegion.setText(tmpList); + } + + // Step 03 + WordsWithCharacters tmp; + for(int i = 0 ; i < tree.length() ; i++) + { + tmp += tree.at(i).text(); + } + return tmp; +} + +/** + * Correct the textOrder, all layout recognition works here + */ +void TextPagePrivate::correctTextOrder() +{ + //m_page->m_page->width() and m_page->m_page->height() are in pixels at + //100% zoom level, and thus depend on display DPI. We scale pageWidth and + //pageHeight to remove the dependence. Otherwise bugs would be more difficult + //to reproduce and Okular could fail in extreme cases like a large TV with low DPI. + const double scalingFactor = 2000.0 / (m_page->m_page->width() + m_page->m_page->height()); + const int pageWidth = (int) (scalingFactor * m_page->m_page->width() ); + const int pageHeight = (int) (scalingFactor * m_page->m_page->height()); + + TextList characters = m_words; + + /** + * Remove spaces from the text + */ + removeSpace(&characters); + + /** + * Construct words from characters + */ + const QList wordsWithCharacters = makeWordFromCharacters(characters, pageWidth, pageHeight); + + /** + * Make a XY Cut tree for segmentation of the texts + */ + const RegionTextList tree = XYCutForBoundingBoxes(wordsWithCharacters, m_page->m_page->boundingBox(), pageWidth, pageHeight); + + /** + * Add spaces to the word + */ + const WordsWithCharacters listWithWordsAndSpaces = addNecessarySpace(tree, pageWidth, pageHeight); + + /** + * Break the words into characters + */ + TextList listOfCharacters; + foreach(const WordWithCharacters &word, listWithWordsAndSpaces) + { + delete word.word; + listOfCharacters.append(word.characters); + } + setWordList(listOfCharacters); +} + +TextEntity::List TextPage::words(const RegularAreaRect *area, TextAreaInclusionBehaviour b) const +{ + if ( area && area->isNull() ) + return TextEntity::List(); + + TextEntity::List ret; + if ( area ) + { + foreach (TinyTextEntity *te, d->m_words) + { + if (b == AnyPixelTextAreaInclusionBehaviour) + { + if ( area->intersects( te->area ) ) + { + ret.append( new TextEntity( te->text(), new Okular::NormalizedRect( te->area) ) ); + } + } + else + { + const NormalizedPoint center = te->area.center(); + if ( area->contains( center.x, center.y ) ) + { + ret.append( new TextEntity( te->text(), new Okular::NormalizedRect( te->area) ) ); + } + } + } + } + else + { + foreach (TinyTextEntity *te, d->m_words) + { + ret.append( new TextEntity( te->text(), new Okular::NormalizedRect( te->area) ) ); + } + } + return ret; +} + +RegularAreaRect * TextPage::wordAt( const NormalizedPoint &p, QString *word ) const +{ + TextList::ConstIterator itBegin = d->m_words.constBegin(), itEnd = d->m_words.constEnd(); + TextList::ConstIterator it = itBegin; + TextList::ConstIterator posIt = itEnd; + for ( ; it != itEnd; ++it ) + { + if ( (*it)->area.contains( p.x, p.y ) ) + { + posIt = it; + break; + } + } + QString text; + if ( posIt != itEnd ) + { + if ( (*posIt)->text().simplified().isEmpty() ) + { + return NULL; + } + // Find the first TinyTextEntity of the word + while ( posIt != itBegin ) + { + --posIt; + const QString itText = (*posIt)->text(); + if ( itText.right(1).at(0).isSpace() ) + { + if (itText.endsWith("-\n")) + { + // Is an hyphenated word + // continue searching the start of the word back + continue; + } + + if (itText == "\n" && posIt != itBegin ) + { + --posIt; + if ((*posIt)->text().endsWith("-")) { + // Is an hyphenated word + // continue searching the start of the word back + continue; + } + ++posIt; + } + + ++posIt; + break; + } + } + RegularAreaRect *ret = new RegularAreaRect(); + for ( ; posIt != itEnd; ++posIt ) + { + const QString itText = (*posIt)->text(); + if ( itText.simplified().isEmpty() ) + { + break; + } + + ret->appendShape( (*posIt)->area ); + text += (*posIt)->text(); + if (itText.right(1).at(0).isSpace()) + { + if (!text.endsWith("-\n")) + { + break; + } + } + } + + if (word) + { + *word = text; + } + return ret; + } + else + { + return NULL; + } +} diff --git a/okular/core/textpage.h b/okular/core/textpage.h new file mode 100644 index 00000000..36cbe399 --- /dev/null +++ b/okular/core/textpage.h @@ -0,0 +1,195 @@ +/*************************************************************************** + * Copyright (C) 2005 by Piotr Szymanski * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_TEXTPAGE_H_ +#define _OKULAR_TEXTPAGE_H_ + +#include +#include + +#include "okular_export.h" +#include "global.h" + +class QTransform; + +namespace Okular { + +class NormalizedPoint; +class NormalizedRect; +class Page; +class PagePrivate; +class TextPagePrivate; +class TextSelection; +class RegularAreaRect; + +/*! @class TextEntity + * @short Abstract textentity of Okular + * @par The context + * A document can provide different forms of information about textual representation + * of its contents. It can include information about positions of every character on the + * page, this is the best possibility. + * + * But also it can provide information only about positions of every word on the page (not the character). + * Furthermore it can provide information only about the position of the whole page's text on the page. + * + * Also some document types have glyphes - sets of characters rendered as one, so in search they should + * appear as a text but are only one character when drawn on screen. We need to allow this. + */ +class OKULAR_EXPORT TextEntity +{ + public: + typedef QList List; + + /** + * Creates a new text entity with the given @p text and the + * given @p area. + */ + TextEntity( const QString &text, NormalizedRect *area ); + + /** + * Destroys the text entity. + */ + ~TextEntity(); + + /** + * Returns the text of the text entity. + */ + QString text() const; + + /** + * Returns the bounding area of the text entity. + */ + NormalizedRect* area() const; + + /** + * Returns the transformed area of the text entity. + */ + NormalizedRect transformedArea(const QTransform &matrix) const; + + private: + QString m_text; + NormalizedRect* m_area; + + class Private; + const Private *d; + + Q_DISABLE_COPY( TextEntity ) +}; + +/** + * The TextPage class represents the text of a page by + * providing @see TextEntity items for every word/character of + * the page. + */ +class OKULAR_EXPORT TextPage +{ + /// @cond PRIVATE + friend class Page; + friend class PagePrivate; + /// @endcond + + public: + /** + * Defines the behaviour of adding characters to text() result + * @since 0.10 (KDE 4.4) + */ + enum TextAreaInclusionBehaviour + { + AnyPixelTextAreaInclusionBehaviour, ///< A character is included into text() result if any pixel of his bounding box is in the given area + CentralPixelTextAreaInclusionBehaviour ///< A character is included into text() result if the central pixel of his bounding box is in the given area + }; + + /** + * Creates a new text page. + */ + TextPage(); + + /** + * Creates a new text page with the given @p words. + */ + TextPage( const TextEntity::List &words ); + + /** + * Destroys the text page. + */ + ~TextPage(); + + /** + * Appends the given @p text with the given @p area as new + * @ref TextEntity to the page. + */ + void append( const QString &text, NormalizedRect *area ); + + /** + * Returns the bounding rect of the text which matches the following criteria + * or 0 if the search is not successful. + * + * @param id An unique id for this search. + * @param text The search text. + * @param direction The direction of the search (@ref SearchDirection) + * @param caseSensitivity If Qt::CaseSensitive, the search is case sensitive; otherwise + * the search is case insensitive. + * @param lastRect If 0 the search starts at the beginning of the page, otherwise + * right/below the coordinates of the given rect. + */ + RegularAreaRect* findText( int id, const QString &text, SearchDirection direction, + Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect ); + + /** + * Text extraction function. + * + * Returns: + * - a null string if @p rect is a valid pointer to a null area + * - the whole page text if @p rect is a null pointer + * - the text which is included by rectangular area @p rect otherwise + * Uses AnyPixelTextAreaInclusionBehaviour + */ + QString text( const RegularAreaRect *rect = 0 ) const; + + /** + * Text extraction function. + * + * Returns: + * - a null string if @p rect is a valid pointer to a null area + * - the whole page text if @p rect is a null pointer + * - the text which is included by rectangular area @p rect otherwise + * @since 0.10 (KDE 4.4) + */ + QString text( const RegularAreaRect * rect, TextAreaInclusionBehaviour b ) const; + + /** + * Text entity extraction function. Similar to text() but returns + * the words including their bounding rectangles. Note that + * ownership of the contents of the returned list belongs to the + * caller. + * @since 0.14 (KDE 4.8) + */ + TextEntity::List words( const RegularAreaRect * rect, TextAreaInclusionBehaviour b ) const; + + /** + * Returns the area and text of the word at the given point + * Note that ownership of the returned area belongs to the caller. + * @since 0.15 (KDE 4.9) + */ + RegularAreaRect * wordAt( const NormalizedPoint &p, QString *word = 0 ) const; + + /** + * Returns the rectangular area of the given @p selection. + */ + RegularAreaRect *textArea( TextSelection *selection ) const; + + private: + TextPagePrivate* const d; + + Q_DISABLE_COPY( TextPage ) +}; + +} + +#endif diff --git a/okular/core/textpage_p.h b/okular/core/textpage_p.h new file mode 100644 index 00000000..486d5cb2 --- /dev/null +++ b/okular/core/textpage_p.h @@ -0,0 +1,79 @@ +/*************************************************************************** + * Copyright (C) 2006 by Tobias Koenig * + * Copyright (C) 2007 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_TEXTPAGE_P_H_ +#define _OKULAR_TEXTPAGE_P_H_ + +#include +#include +#include +#include + +class SearchPoint; +class TinyTextEntity; +class RegionText; + +namespace Okular +{ + +class PagePrivate; +typedef QList< TinyTextEntity* > TextList; + +/** + * Returns whether the two strings match. + * Satisfies the condition that if two strings match then their lengths are equal. + */ +typedef bool ( *TextComparisonFunction )( const QStringRef & from, const QStringRef & to ); + +/** + * A list of RegionText. It keeps a bunch of TextList with their bounding rectangles + */ +typedef QList RegionTextList; + +class TextPagePrivate +{ + public: + TextPagePrivate(); + ~TextPagePrivate(); + + RegularAreaRect * findTextInternalForward( int searchID, const QString &query, + TextComparisonFunction comparer, + const TextList::ConstIterator &start, + int start_offset, + const TextList::ConstIterator &end); + RegularAreaRect * findTextInternalBackward( int searchID, const QString &query, + TextComparisonFunction comparer, + const TextList::ConstIterator &start, + int start_offset, + const TextList::ConstIterator &end ); + + /** + * Copy a TextList to m_words, the pointers of list are adopted + */ + void setWordList(const TextList &list); + + /** + * Make necessary modifications in the TextList to make the text order correct, so + * that textselection works fine + */ + void correctTextOrder(); + + // variables those can be accessed directly from TextPage + TextList m_words; + QMap< int, SearchPoint* > m_searchPoints; + PagePrivate *m_page; + + private: + RegularAreaRect * searchPointToArea(const SearchPoint* sp); +}; + +} + +#endif diff --git a/okular/core/tile.h b/okular/core/tile.h new file mode 100644 index 00000000..d9e6ab56 --- /dev/null +++ b/okular/core/tile.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2012 by Fabio D'Urso * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_TILE_H_ +#define _OKULAR_TILE_H_ + +#include "area.h" + +class QPixmap; + +namespace Okular { + +/** + * This class represents a rectangular portion of a page. + * + * It doesn't take ownership of pixmap + * + * @since 0.16 (KDE 4.10) + */ +class OKULAR_EXPORT Tile +{ + public: + Tile( const NormalizedRect &rect, QPixmap *pixmap, bool isValid ); + Tile( const Tile &t ); + ~Tile(); + + /** + * Location of the tile + */ + NormalizedRect rect() const; + + /** + * Pixmap (may also be NULL) + */ + QPixmap * pixmap() const; + + /** + * True if the pixmap is available and updated + */ + bool isValid() const; + + Tile& operator=( const Tile &other ); + + private: + class Private; + Private * d; +}; + +} + +#endif // _OKULAR_TILE_H_ diff --git a/okular/core/tilesmanager.cpp b/okular/core/tilesmanager.cpp new file mode 100644 index 00000000..7600d1a2 --- /dev/null +++ b/okular/core/tilesmanager.cpp @@ -0,0 +1,709 @@ +/*************************************************************************** + * Copyright (C) 2012 by Mailson Menezes * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "tilesmanager_p.h" + +#include +#include +#include +#include + +#include "tile.h" + +#define TILES_MAXSIZE 2000000 + +using namespace Okular; + +static bool rankedTilesLessThan( TileNode *t1, TileNode *t2 ) +{ + // Order tiles by its dirty state and then by distance from the viewport. + if ( t1->dirty == t2->dirty ) + return t1->distance < t2->distance; + + return !t1->dirty; +} + +class TilesManager::Private +{ + public: + Private(); + + bool hasPixmap( const NormalizedRect &rect, const TileNode &tile ) const; + void tilesAt( const NormalizedRect &rect, TileNode &tile, QList &result, TileLeaf tileLeaf ); + void setPixmap( const QPixmap *pixmap, const NormalizedRect &rect, TileNode &tile ); + + /** + * Mark @p tile and all its children as dirty + */ + static void markDirty( TileNode &tile ); + + /** + * Deletes all tiles, recursively + */ + void deleteTiles( const TileNode &tile ); + + void markParentDirty( const TileNode &tile ); + void rankTiles( TileNode &tile, QList &rankedTiles, const NormalizedRect &visibleRect, int visiblePageNumber ); + /** + * Since the tile can be large enough to occupy a significant amount of + * space, they may be split in more tiles. This operation is performed + * when the tiles of a certain region is requested and they are bigger + * than an arbitrary value. Only tiles intersecting the desired region + * are split. There's no need to do this for the entire page. + */ + void split( TileNode &tile, const NormalizedRect &rect ); + + /** + * Checks whether the tile's size is bigger than an arbitrary value and + * performs the split operation returning true. + * Otherwise it just returns false, without performing any operation. + */ + bool splitBigTiles( TileNode &tile, const NormalizedRect &rect ); + + // The page is split in a 4x4 grid of tiles + TileNode tiles[16]; + int width; + int height; + int pageNumber; + qulonglong totalPixels; + Rotation rotation; + NormalizedRect visibleRect; + NormalizedRect requestRect; + int requestWidth; + int requestHeight; +}; + +TilesManager::Private::Private() + : width( 0 ) + , height( 0 ) + , pageNumber( 0 ) + , totalPixels( 0 ) + , rotation( Rotation0 ) + , requestRect( NormalizedRect() ) + , requestWidth( 0 ) + , requestHeight( 0 ) +{ +} + +TilesManager::TilesManager( int pageNumber, int width, int height, Rotation rotation ) + : d( new Private ) +{ + d->pageNumber = pageNumber; + d->width = width; + d->height = height; + d->rotation = rotation; + + // The page is split in a 4x4 grid of tiles + const double dim = 0.25; + for ( int i = 0; i < 16; ++i ) + { + int x = i % 4; + int y = i / 4; + d->tiles[ i ].rect = NormalizedRect( x*dim, y*dim, x*dim+dim, y*dim+dim ); + } +} + +TilesManager::~TilesManager() +{ + for ( int i = 0; i < 16; ++i ) + d->deleteTiles( d->tiles[ i ] ); + + delete d; +} + +void TilesManager::Private::deleteTiles( const TileNode &tile ) +{ + if ( tile.pixmap ) + { + totalPixels -= tile.pixmap->width()*tile.pixmap->height(); + delete tile.pixmap; + } + + if ( tile.nTiles > 0 ) + { + for ( int i = 0; i < tile.nTiles; ++i ) + deleteTiles( tile.tiles[ i ] ); + + delete [] tile.tiles; + } +} + +void TilesManager::setSize( int width, int height ) +{ + if ( width == d->width && height == d->height ) + return; + + d->width = width; + d->height = height; + + markDirty(); +} + +int TilesManager::width() const +{ + return d->width; +} + +int TilesManager::height() const +{ + return d->height; +} + +void TilesManager::setRotation( Rotation rotation ) +{ + if ( rotation == d->rotation ) + return; + + d->rotation = rotation; +} + +Rotation TilesManager::rotation() const +{ + return d->rotation; +} + +void TilesManager::markDirty() +{ + for ( int i = 0; i < 16; ++i ) + { + TilesManager::Private::markDirty( d->tiles[ i ] ); + } +} + +void TilesManager::Private::markDirty( TileNode &tile ) +{ + tile.dirty = true; + + for ( int i = 0; i < tile.nTiles; ++i ) + { + markDirty( tile.tiles[ i ] ); + } +} + +void TilesManager::setPixmap( const QPixmap *pixmap, const NormalizedRect &rect ) +{ + NormalizedRect rotatedRect = TilesManager::fromRotatedRect( rect, d->rotation ); + if ( !d->requestRect.isNull() ) + { + if ( !(d->requestRect == rect) ) + return; + + // Check whether the pixmap has the same absolute size of the expected + // request. + // If the document is rotated, rotate requestRect back to the original + // rotation before comparing to pixmap's size. This is to avoid + // conversion issues. The pixmap request was made using an unrotated + // rect. + QSize pixmapSize = pixmap->size(); + int w = width(); + int h = height(); + if ( d->rotation % 2 ) + { + qSwap(w, h); + pixmapSize.transpose(); + } + + if ( rotatedRect.geometry( w, h ).size() != pixmapSize ) + return; + + d->requestRect = NormalizedRect(); + } + + for ( int i = 0; i < 16; ++i ) + { + d->setPixmap( pixmap, rotatedRect, d->tiles[ i ] ); + } +} + +void TilesManager::Private::setPixmap( const QPixmap *pixmap, const NormalizedRect &rect, TileNode &tile ) +{ + QRect pixmapRect = TilesManager::toRotatedRect( rect, rotation ).geometry( width, height ); + + // Exclude tiles outside the viewport + if ( !tile.rect.intersects( rect ) ) + return; + + // if the tile is not entirely within the viewport (the tile intersects an + // edged of the viewport), attempt to set the pixmap in the children tiles + if ( !((tile.rect & rect) == tile.rect) ) + { + // paint children tiles + if ( tile.nTiles > 0 ) + { + for ( int i = 0; i < tile.nTiles; ++i ) + setPixmap( pixmap, rect, tile.tiles[ i ] ); + + delete tile.pixmap; + tile.pixmap = 0; + } + + return; + } + + // the tile lies entirely within the viewport + if ( tile.nTiles == 0 ) + { + tile.dirty = false; + + // check whether the tile size is big and split it if necessary + if ( !splitBigTiles( tile, rect ) ) + { + if ( tile.pixmap ) + { + totalPixels -= tile.pixmap->width()*tile.pixmap->height(); + delete tile.pixmap; + } + NormalizedRect rotatedRect = TilesManager::toRotatedRect( tile.rect, rotation ); + tile.pixmap = new QPixmap( pixmap->copy( rotatedRect.geometry( width, height ).translated( -pixmapRect.topLeft() ) ) ); + tile.rotation = rotation; + totalPixels += tile.pixmap->width()*tile.pixmap->height(); + } + else + { + if ( tile.pixmap ) + { + totalPixels -= tile.pixmap->width()*tile.pixmap->height(); + delete tile.pixmap; + tile.pixmap = 0; + } + + for ( int i = 0; i < tile.nTiles; ++i ) + setPixmap( pixmap, rect, tile.tiles[ i ] ); + } + } + else + { + QRect tileRect = tile.rect.geometry( width, height ); + // sets the pixmap of the children tiles. if the tile's size is too + // small, discards the children tiles and use the current one + if ( tileRect.width()*tileRect.height() >= TILES_MAXSIZE ) + { + tile.dirty = false; + if ( tile.pixmap ) + { + totalPixels -= tile.pixmap->width()*tile.pixmap->height(); + delete tile.pixmap; + tile.pixmap = 0; + } + + for ( int i = 0; i < tile.nTiles; ++i ) + setPixmap( pixmap, rect, tile.tiles[ i ] ); + } + else + { + // remove children tiles + for ( int i = 0; i < tile.nTiles; ++i ) + { + deleteTiles( tile.tiles[ i ] ); + tile.tiles[ i ].pixmap = 0; + } + + delete [] tile.tiles; + tile.tiles = 0; + tile.nTiles = 0; + + // paint tile + if ( tile.pixmap ) + { + totalPixels -= tile.pixmap->width()*tile.pixmap->height(); + delete tile.pixmap; + } + NormalizedRect rotatedRect = TilesManager::toRotatedRect( tile.rect, rotation ); + tile.pixmap = new QPixmap( pixmap->copy( rotatedRect.geometry( width, height ).translated( -pixmapRect.topLeft() ) ) ); + tile.rotation = rotation; + totalPixels += tile.pixmap->width()*tile.pixmap->height(); + tile.dirty = false; + } + } +} + +bool TilesManager::hasPixmap( const NormalizedRect &rect ) +{ + NormalizedRect rotatedRect = fromRotatedRect( rect, d->rotation ); + for ( int i = 0; i < 16; ++i ) + { + if ( !d->hasPixmap( rotatedRect, d->tiles[ i ] ) ) + return false; + } + + return true; +} + +bool TilesManager::Private::hasPixmap( const NormalizedRect &rect, const TileNode &tile ) const +{ + if ( !tile.rect.intersects( rect ) ) + return true; + + if ( tile.nTiles == 0 ) + return tile.isValid(); + + // all children tiles are clean. doesn't need to go deeper + if ( !tile.dirty ) + return true; + + for ( int i = 0; i < tile.nTiles; ++i ) + { + if ( !hasPixmap( rect, tile.tiles[ i ] ) ) + return false; + } + + return true; +} + +QList TilesManager::tilesAt( const NormalizedRect &rect, TileLeaf tileLeaf ) +{ + QList result; + + NormalizedRect rotatedRect = fromRotatedRect( rect, d->rotation ); + for ( int i = 0; i < 16; ++i ) + { + d->tilesAt( rotatedRect, d->tiles[ i ], result, tileLeaf ); + } + + return result; +} + +void TilesManager::Private::tilesAt( const NormalizedRect &rect, TileNode &tile, QList &result, TileLeaf tileLeaf ) +{ + if ( !tile.rect.intersects( rect ) ) + return; + + // split big tiles before the requests are made, otherwise we would end up + // requesting huge areas unnecessarily + splitBigTiles( tile, rect ); + + if ( ( tileLeaf == TerminalTile && tile.nTiles == 0 ) || ( tileLeaf == PixmapTile && tile.pixmap ) ) + { + NormalizedRect rotatedRect; + if ( rotation != Rotation0 ) + rotatedRect = TilesManager::toRotatedRect( tile.rect, rotation ); + else + rotatedRect = tile.rect; + + if ( tile.pixmap && tileLeaf == PixmapTile && tile.rotation != rotation ) + { + // Lazy tiles rotation + int angleToRotate = (rotation - tile.rotation)*90; + int xOffset = 0, yOffset = 0; + int w = 0, h = 0; + switch( angleToRotate ) + { + case 0: + xOffset = 0; + yOffset = 0; + w = tile.pixmap->width(); + h = tile.pixmap->height(); + break; + case 90: + case -270: + xOffset = 0; + yOffset = -tile.pixmap->height(); + w = tile.pixmap->height(); + h = tile.pixmap->width(); + break; + case 180: + case -180: + xOffset = -tile.pixmap->width(); + yOffset = -tile.pixmap->height(); + w = tile.pixmap->width(); + h = tile.pixmap->height(); + break; + case 270: + case -90: + xOffset = -tile.pixmap->width(); + yOffset = 0; + w = tile.pixmap->height(); + h = tile.pixmap->width(); + break; + } + QPixmap *rotatedPixmap = new QPixmap( w, h ); + QPainter p( rotatedPixmap ); + p.rotate( angleToRotate ); + p.translate( xOffset, yOffset ); + p.drawPixmap( 0, 0, *tile.pixmap ); + p.end(); + + delete tile.pixmap; + tile.pixmap = rotatedPixmap; + tile.rotation = rotation; + } + result.append( Tile( rotatedRect, tile.pixmap, tile.isValid() ) ); + } + else + { + for ( int i = 0; i < tile.nTiles; ++i ) + tilesAt( rect, tile.tiles[ i ], result, tileLeaf ); + } +} + +qulonglong TilesManager::totalMemory() const +{ + return 4*d->totalPixels; +} + +void TilesManager::cleanupPixmapMemory( qulonglong numberOfBytes, const NormalizedRect &visibleRect, int visiblePageNumber ) +{ + QList rankedTiles; + for ( int i = 0; i < 16; ++i ) + { + d->rankTiles( d->tiles[ i ], rankedTiles, visibleRect, visiblePageNumber ); + } + qSort( rankedTiles.begin(), rankedTiles.end(), rankedTilesLessThan ); + + while ( numberOfBytes > 0 && !rankedTiles.isEmpty() ) + { + TileNode *tile = rankedTiles.takeLast(); + if ( !tile->pixmap ) + continue; + + // do not evict visible pixmaps + if ( tile->rect.intersects( visibleRect ) ) + continue; + + qulonglong pixels = tile->pixmap->width()*tile->pixmap->height(); + d->totalPixels -= pixels; + if ( numberOfBytes < 4*pixels ) + numberOfBytes = 0; + else + numberOfBytes -= 4*pixels; + + delete tile->pixmap; + tile->pixmap = 0; + + d->markParentDirty( *tile ); + } +} + +void TilesManager::Private::markParentDirty( const TileNode &tile ) +{ + if ( !tile.parent ) + return; + + if ( !tile.parent->dirty ) + { + tile.parent->dirty = true; + markParentDirty( *tile.parent ); + } +} + +void TilesManager::Private::rankTiles( TileNode &tile, QList &rankedTiles, const NormalizedRect &visibleRect, int visiblePageNumber ) +{ + // If the page is visible, visibleRect is not null. + // Otherwise we use the number of one of the visible pages to calculate the + // distance. + // Note that the current page may be visible and yet its pageNumber is + // different from visiblePageNumber. Since we only use this value on hidden + // pages, any visible page number will fit. + if ( visibleRect.isNull() && visiblePageNumber < 0 ) + return; + + if ( tile.pixmap ) + { + // Update distance + if ( !visibleRect.isNull() ) + { + NormalizedPoint viewportCenter = visibleRect.center(); + NormalizedPoint tileCenter = tile.rect.center(); + // Manhattan distance. It's a good and fast approximation. + tile.distance = qAbs(viewportCenter.x - tileCenter.x) + qAbs(viewportCenter.y - tileCenter.y); + } + else + { + // For non visible pages only the vertical distance is used + if ( pageNumber < visiblePageNumber ) + tile.distance = 1 - tile.rect.bottom; + else + tile.distance = tile.rect.top; + } + rankedTiles.append( &tile ); + } + else + { + for ( int i = 0; i < tile.nTiles; ++i ) + { + rankTiles( tile.tiles[ i ], rankedTiles, visibleRect, visiblePageNumber ); + } + } +} + +bool TilesManager::isRequesting( const NormalizedRect &rect, int pageWidth, int pageHeight ) const +{ + return rect == d->requestRect && pageWidth == d->requestWidth && pageHeight == d->requestHeight; +} + +void TilesManager::setRequest( const NormalizedRect &rect, int pageWidth, int pageHeight ) +{ + d->requestRect = rect; + d->requestWidth = pageWidth; + d->requestHeight = pageHeight; +} + +bool TilesManager::Private::splitBigTiles( TileNode &tile, const NormalizedRect &rect ) +{ + QRect tileRect = tile.rect.geometry( width, height ); + if ( tileRect.width()*tileRect.height() < TILES_MAXSIZE ) + return false; + + split( tile, rect ); + return true; +} + +void TilesManager::Private::split( TileNode &tile, const NormalizedRect &rect ) +{ + if ( tile.nTiles != 0 ) + return; + + if ( rect.isNull() || !tile.rect.intersects( rect ) ) + return; + + tile.nTiles = 4; + tile.tiles = new TileNode[4]; + double hCenter = (tile.rect.left + tile.rect.right)/2; + double vCenter = (tile.rect.top + tile.rect.bottom)/2; + + tile.tiles[0].rect = NormalizedRect( tile.rect.left, tile.rect.top, hCenter, vCenter ); + tile.tiles[1].rect = NormalizedRect( hCenter, tile.rect.top, tile.rect.right, vCenter ); + tile.tiles[2].rect = NormalizedRect( tile.rect.left, vCenter, hCenter, tile.rect.bottom ); + tile.tiles[3].rect = NormalizedRect( hCenter, vCenter, tile.rect.right, tile.rect.bottom ); + + for ( int i = 0; i < tile.nTiles; ++i ) + { + tile.tiles[ i ].parent = &tile; + splitBigTiles( tile.tiles[ i ], rect ); + } +} + +NormalizedRect TilesManager::fromRotatedRect( const NormalizedRect &rect, Rotation rotation ) +{ + if ( rotation == Rotation0 ) + return rect; + + NormalizedRect newRect; + switch ( rotation ) + { + case Rotation90: + newRect = NormalizedRect( rect.top, 1 - rect.right, rect.bottom, 1 - rect.left ); + break; + case Rotation180: + newRect = NormalizedRect( 1 - rect.right, 1 - rect.bottom, 1 - rect.left, 1 - rect.top ); + break; + case Rotation270: + newRect = NormalizedRect( 1 - rect.bottom, rect.left, 1 - rect.top, rect.right ); + break; + default: + newRect = rect; + break; + } + + return newRect; +} + +NormalizedRect TilesManager::toRotatedRect( const NormalizedRect &rect, Rotation rotation ) +{ + if ( rotation == Rotation0 ) + return rect; + + NormalizedRect newRect; + switch ( rotation ) + { + case Rotation90: + newRect = NormalizedRect( 1 - rect.bottom, rect.left, 1 - rect.top, rect.right ); + break; + case Rotation180: + newRect = NormalizedRect( 1 - rect.right, 1 - rect.bottom, 1 - rect.left, 1 - rect.top ); + break; + case Rotation270: + newRect = NormalizedRect( rect.top, 1 - rect.right, rect.bottom, 1 - rect.left ); + break; + default: + newRect = rect; + break; + } + + return newRect; +} + +TileNode::TileNode() + : pixmap( 0 ) + , rotation( Rotation0 ) + , dirty ( true ) + , distance( -1 ) + , tiles( 0 ) + , nTiles( 0 ) + , parent( 0 ) +{ +} + +bool TileNode::isValid() const +{ + return pixmap && !dirty; +} + +class Tile::Private +{ + public: + Private(); + + NormalizedRect rect; + QPixmap *pixmap; + bool isValid; +}; + +Tile::Private::Private() + : pixmap( 0 ) + , isValid( false ) +{ +} + +Tile::Tile( const NormalizedRect &rect, QPixmap *pixmap, bool isValid ) + : d( new Tile::Private ) +{ + d->rect = rect; + d->pixmap = pixmap; + d->isValid = isValid; +} + +Tile::Tile( const Tile &t ) + : d( new Tile::Private ) +{ + d->rect = t.d->rect; + d->pixmap = t.d->pixmap; + d->isValid = t.d->isValid; +} + +Tile& Tile::operator=( const Tile &other ) +{ + if ( this == &other ) + return *this; + + d->rect = other.d->rect; + d->pixmap = other.d->pixmap; + d->isValid = other.d->isValid; + + return *this; +} + +Tile::~Tile() +{ + delete d; +} + +NormalizedRect Tile::rect() const +{ + return d->rect; +} + +QPixmap * Tile::pixmap() const +{ + return d->pixmap; +} + +bool Tile::isValid() const +{ + return d->isValid; +} diff --git a/okular/core/tilesmanager_p.h b/okular/core/tilesmanager_p.h new file mode 100644 index 00000000..e2d4fefc --- /dev/null +++ b/okular/core/tilesmanager_p.h @@ -0,0 +1,207 @@ +/*************************************************************************** + * Copyright (C) 2012 by Mailson Menezes * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_TILES_MANAGER_P_H_ +#define _OKULAR_TILES_MANAGER_P_H_ + +#include "okular_export.h" +#include "area.h" + +class QPixmap; + +namespace Okular { + +class Tile; + +/** + * Node in the quadtree structure used by the tiles manager to store tiles. + * + * Except for the first level, the tiles manager stores tiles in a quadtree + * structure. + * Each node stores the pixmap of a tile and its location on the page. + * There's a limit on the size of the pixmaps (TILES_MAXSIZE, defined in + * tilesmanager.cpp), and tiles that are bigger than that value are split into + * four children tiles, which are stored as children of the original tile. + * If children tiles are still too big, they are recursively split again. + * If the zoom level changes and a big tile goes below the limit, it is merged + * back into a leaf tile. + */ +class TileNode +{ + public: + TileNode(); + + bool isValid() const; + + /** + * Location on the page in normalized coords + */ + NormalizedRect rect; + + /** + * Associated pixmap or NULL if not present + * + * For each node, it is guaranteed that there's no more than one pixmap + * along the path from the root to the node itself. + * In fact, it is very frequent that a leaf node has no pixmap and one + * of its ancestors has. Such a situation shows, for example, when the + * parent tile still has a dirty tile from a previous lower zoom level. + */ + QPixmap *pixmap; + + /** + * Rotation of this individual tile. + * + * A rotation to the page does not immediately rotates the pixmaps in + * cache. This operation happens when pixmaps are going to be used. + */ + Rotation rotation; + + /** + * Whether the tile needs to be repainted (after a zoom or rotation) + * If a tile doesn't have a pixmap but all its children are updated + * (dirty = false), the parent tile is also considered updated. + */ + bool dirty; + + /** + * Distance between the tile and the viewport. + * This is used by the evicting algorithm. + */ + double distance; + + /** + * Children tiles + * When a tile is split into multiple tiles, they're added as children. + * nTiles can be either 0 (in leaf tiles) or 4 (in split tiles). + */ + TileNode *tiles; + int nTiles; + TileNode *parent; +}; + +/** + * @short Tiles management + * + * This class has direct access to all tiles and handles how they should be + * stored, deleted and retrieved. Each tiles manager only handles one page. + * + * The tiles manager is a tree of tiles. At first the page is divided in a 4x4 + * grid of 16 tiles. Then each of these tiles can be recursively split in 4 + * subtiles so that we keep the size of each pixmap inside a safe interval. + */ +class TilesManager +{ + public: + enum TileLeaf + { + TerminalTile, ///< Return tiles without children + PixmapTile ///< Return only tiles with pixmap + }; + + TilesManager( int pageNumber, int width, int height, Rotation rotation = Rotation0 ); + ~TilesManager(); + + /** + * Sets the pixmap of the tiles covered by @p rect (which represents + * the location of @p pixmap on the page). + * @p pixmap may cover an area which contains multiple tiles. So each + * tile we get a cropped part of the @p pixmap. + * + * Also it checks the dimensions of the given parameters against the + * current request as to avoid setting pixmaps of late requests. + */ + void setPixmap( const QPixmap *pixmap, const NormalizedRect &rect ); + + /** + * Checks whether all tiles intersecting with @p rect are available. + * Returns false if at least one tile needs to be repainted (the tile + * is dirty). + */ + bool hasPixmap( const NormalizedRect &rect ); + + /** + * Returns a list of all tiles intersecting with @p rect. + * + * As to avoid requests of big areas, each traversed tile is checked + * for its size and split if necessary. + * + * @param tileLeaf Indicate the type of tile to return + */ + QList tilesAt( const NormalizedRect &rect, TileLeaf tileLeaf ); + + /** + * The total memory consumed by the tiles manager + */ + qulonglong totalMemory() const; + + /** + * Removes at least @p numberOfBytes bytes worth of tiles (least ranked + * tiles are removed first). + * Set @p visibleRect to the visible region of the page. Set a + * @p visiblePageNumber if the current page is not visible. + * Visible tiles are not discarded. + */ + void cleanupPixmapMemory( qulonglong numberOfBytes, const NormalizedRect &visibleRect, int visiblePageNumber ); + + /** + * Checks whether a given region has already been requested + */ + bool isRequesting( const NormalizedRect &rect, int pageWidth, int pageHeight ) const; + + /** + * Sets a region to be requested so the tiles manager knows which + * pixmaps to expect and discard those not useful anymore (late pixmaps) + */ + void setRequest( const NormalizedRect &rect, int pageWidth, int pageHeight ); + + /** + * Inform the new size of the page and mark all tiles to repaint + */ + void setSize( int width, int height ); + + /** + * Gets the width of the page in tiles manager + */ + int width() const; + + /** + * Gets the height of the page in tiles manager + */ + int height() const; + + /** + * Inform the new rotation of the page + */ + void setRotation( Rotation rotation ); + Rotation rotation() const; + + /** + * Mark all tiles as dirty + */ + void markDirty(); + + /** + * Returns a rotated NormalizedRect given a @p rotation + */ + static NormalizedRect toRotatedRect( const NormalizedRect &rect, Rotation rotation ); + + /** + * Returns a non rotated version of @p rect, which is rotated by @p rotation + */ + static NormalizedRect fromRotatedRect( const NormalizedRect &rect, Rotation rotation ); + + private: + class Private; + Private * const d; + friend class Private; +}; + +} + +#endif // _OKULAR_TILES_MANAGER_P_H_ diff --git a/okular/core/utils.cpp b/okular/core/utils.cpp new file mode 100644 index 00000000..a3558b06 --- /dev/null +++ b/okular/core/utils.cpp @@ -0,0 +1,402 @@ +/*************************************************************************** + * Copyright (C) 2006 by Luigi Toscano * + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "utils.h" +#include "utils_p.h" + +#include +#include +#include +#include +#include + +#ifdef Q_WS_X11 + #include "config-okular.h" + #if HAVE_LIBKSCREEN + #include + #include + #endif + #include +#endif + +#ifdef Q_WS_MAC +#include +#include +#endif + + + +using namespace Okular; + +QRect Utils::rotateRect( const QRect & source, int width, int height, int orientation ) +{ + QRect ret; + + // adapt the coordinates of the boxes to the rotation + switch ( orientation ) + { + case 1: + ret = QRect( width - source.y() - source.height(), source.x(), + source.height(), source.width() ); + break; + case 2: + ret = QRect( width - source.x() - source.width(), height - source.y() - source.height(), + source.width(), source.height() ); + break; + case 3: + ret = QRect( source.y(), height - source.x() - source.width(), + source.height(), source.width() ); + break; + case 0: // no modifications + default: // other cases + ret = source; + } + + return ret; +} + +#if defined(Q_WS_X11) + +double Utils::dpiX() +{ + return QX11Info::appDpiX(); +} + +double Utils::dpiY() +{ + return QX11Info::appDpiY(); +} + +double Utils::realDpiX() +{ + const QDesktopWidget* w = QApplication::desktop(); + if (w->width() > 0 && w->widthMM() > 0) { + kDebug() << "Pix:" << w->width() << "MM:" << w->widthMM(); + return (double(w->width()) * 25.4) / double(w->widthMM()); + } else { + return dpiX(); + } +} + +double Utils::realDpiY() +{ + const QDesktopWidget* w = QApplication::desktop(); + if (w->height() > 0 && w->heightMM() > 0) { + kDebug() << "Pix:" << w->height() << "MM:" << w->heightMM(); + return (double(w->height()) * 25.4) / double(w->heightMM()); + } else { + return dpiY(); + } +} + +QSizeF Utils::realDpi(QWidget* widgetOnScreen) +{ + if (widgetOnScreen) + { + // Firstly try to retrieve DPI via LibKScreen +#if HAVE_LIBKSCREEN + KScreen::Config* config = KScreen::Config::current(); + if (config) { + KScreen::OutputList outputs = config->outputs(); + QPoint globalPos = widgetOnScreen->parentWidget() ? + widgetOnScreen->mapToGlobal(widgetOnScreen->pos()): + widgetOnScreen->pos(); + QRect widgetRect(globalPos, widgetOnScreen->size()); + + KScreen::Output* selectedOutput = 0; + int maxArea = 0; + Q_FOREACH(KScreen::Output *output, outputs) + { + if (output->currentMode()) + { + QRect outputRect(output->pos(),output->currentMode()->size()); + QRect intersection = outputRect.intersected(widgetRect); + int area = intersection.width()*intersection.height(); + if (area > maxArea) + { + maxArea = area; + selectedOutput = output; + } + } + } + + if (selectedOutput) + { + kDebug() << "Found widget at output #" << selectedOutput->id(); + QRect outputRect(selectedOutput->pos(),selectedOutput->currentMode()->size()); + QSize szMM = selectedOutput->sizeMm(); + kDebug() << "Output size is (mm) " << szMM; + kDebug() << "Output rect is " << outputRect; + if (selectedOutput->edid()) { + kDebug() << "EDID WxH (cm): " << selectedOutput->edid()->width() << 'x' << selectedOutput->edid()->height(); + } + if (szMM.width() > 0 && szMM.height() > 0 && outputRect.width() > 0 && outputRect.height() > 0 + && selectedOutput->edid() + && qAbs(static_cast(selectedOutput->edid()->width()*10) - szMM.width()) < 10 + && qAbs(static_cast(selectedOutput->edid()->height()*10) - szMM.height()) < 10) + { + // sizes in EDID seem to be consistent + QSizeF res(static_cast(outputRect.width())*25.4/szMM.width(), + static_cast(outputRect.height())*25.4/szMM.height()); + if (!selectedOutput->isHorizontal()) + { + kDebug() << "Output is vertical, transposing DPI rect"; + res.transpose(); + } + if (qAbs(res.width() - res.height()) / qMin(res.height(), res.width()) < 0.05) { + return res; + } else { + kDebug() << "KScreen calculation returned a non square dpi." << res << ". Falling back"; + } + } + } + else + { + kDebug() << "Didn't find a KScreen selectedOutput to calculate DPI. Falling back"; + } + } + else + { + kDebug() << "Didn't find a KScreen config to calculate DPI. Falling back"; + } +#endif + } + // this is also fallback for LibKScreen branch if KScreen::Output + // for particular widget was not found + QSizeF res = QSizeF(realDpiX(), realDpiY()); + if (qAbs(res.width() - res.height()) / qMin(res.height(), res.width()) < 0.05) { + return res; + } else { + kDebug() << "QDesktopWidget calculation returned a non square dpi." << res << ". Falling back"; + } + + res = QSizeF(dpiX(), dpiY()); + if (qAbs(res.width() - res.height()) / qMin(res.height(), res.width()) < 0.05) { + return res; + } else { + kDebug() << "QX11Info returned a non square dpi." << res << ". Falling back"; + } + + res = QSizeF(72, 72); + return res; +} + +#elif defined(Q_WS_MAC) + /* + * Code copied from http://developer.apple.com/qa/qa2001/qa1217.html + */ + // Handy utility function for retrieving an int from a CFDictionaryRef + static int GetIntFromDictionaryForKey( CFDictionaryRef desc, CFStringRef key ) + { + CFNumberRef value; + int num = 0; + if ( (value = (CFNumberRef)CFDictionaryGetValue(desc, key)) == NULL || CFGetTypeID(value) != CFNumberGetTypeID()) + return 0; + CFNumberGetValue(value, kCFNumberIntType, &num); + return num; + } + + static CGDisplayErr GetDisplayDPI( CFDictionaryRef displayModeDict, CGDirectDisplayID displayID, + double *horizontalDPI, double *verticalDPI ) + { + CGDisplayErr err = kCGErrorFailure; + io_connect_t displayPort; + CFDictionaryRef displayDict; + + // Grab a connection to IOKit for the requested display + displayPort = CGDisplayIOServicePort( displayID ); + if ( displayPort != MACH_PORT_NULL ) + { + // Find out what IOKit knows about this display + displayDict = IODisplayCreateInfoDictionary(displayPort, 0); + if ( displayDict != NULL ) + { + const double mmPerInch = 25.4; + double horizontalSizeInInches = + (double)GetIntFromDictionaryForKey(displayDict, + CFSTR(kDisplayHorizontalImageSize)) / mmPerInch; + double verticalSizeInInches = + (double)GetIntFromDictionaryForKey(displayDict, + CFSTR(kDisplayVerticalImageSize)) / mmPerInch; + + // Make sure to release the dictionary we got from IOKit + CFRelease(displayDict); + + // Now we can calculate the actual DPI + // with information from the displayModeDict + *horizontalDPI = + (double)GetIntFromDictionaryForKey( displayModeDict, kCGDisplayWidth ) + / horizontalSizeInInches; + *verticalDPI = (double)GetIntFromDictionaryForKey( displayModeDict, + kCGDisplayHeight ) / verticalSizeInInches; + err = CGDisplayNoErr; + } + } + return err; + } + +double Utils::dpiX() +{ + double x,y; + CGDisplayErr err = GetDisplayDPI( CGDisplayCurrentMode(kCGDirectMainDisplay), + kCGDirectMainDisplay, + &x, &y ); + + return err == CGDisplayNoErr ? x : 72.0; +} + +double Utils::dpiY() +{ + double x,y; + CGDisplayErr err = GetDisplayDPI( CGDisplayCurrentMode(kCGDirectMainDisplay), + kCGDirectMainDisplay, + &x, &y ); + + return err == CGDisplayNoErr ? y : 72.0; +} + +double Utils::realDpiX() +{ + return dpiX(); +} + +double Utils::realDpiY() +{ + return dpiY(); +} + +QSizeF Utils::realDpi(QWidget*) +{ + return QSizeF(realDpiX(), realDpiY()); +} +#else + +double Utils::dpiX() +{ + return QDesktopWidget().physicalDpiX(); +} + +double Utils::dpiY() +{ + return QDesktopWidget().physicalDpiY(); +} + +double Utils::realDpiX() +{ + return dpiX(); +} + +double Utils::realDpiY() +{ + return dpiY(); +} + +QSizeF Utils::realDpi(QWidget*) +{ + return QSizeF(realDpiX(), realDpiY()); +} +#endif + +inline static bool isWhite( QRgb argb ) { + return ( argb & 0xFFFFFF ) == 0xFFFFFF; // ignore alpha +} + +NormalizedRect Utils::imageBoundingBox( const QImage * image ) +{ + if ( !image ) + return NormalizedRect(); + + int width = image->width(); + int height = image->height(); + int left, top, bottom, right, x, y; + +#ifdef BBOX_DEBUG + QTime time; + time.start(); +#endif + + // Scan pixels for top non-white + for ( top = 0; top < height; ++top ) + for ( x = 0; x < width; ++x ) + if ( !isWhite( image->pixel( x, top ) ) ) + goto got_top; + return NormalizedRect( 0, 0, 0, 0 ); // the image is blank +got_top: + left = right = x; + + // Scan pixels for bottom non-white + for ( bottom = height-1; bottom >= top; --bottom ) + for ( x = width-1; x >= 0; --x ) + if ( !isWhite( image->pixel( x, bottom ) ) ) + goto got_bottom; + Q_ASSERT( 0 ); // image changed?! +got_bottom: + if ( x < left ) + left = x; + if ( x > right ) + right = x; + + // Scan for leftmost and rightmost (we already found some bounds on these): + for ( y = top; y <= bottom && ( left > 0 || right < width-1 ); ++y ) + { + for ( x = 0; x < left; ++x ) + if ( !isWhite( image->pixel( x, y ) ) ) + left = x; + for ( x = width-1; x > right+1; --x ) + if ( !isWhite( image->pixel( x, y ) ) ) + right = x; + } + + NormalizedRect bbox( QRect( left, top, ( right - left + 1), ( bottom - top + 1 ) ), + image->width(), image->height() ); + +#ifdef BBOX_DEBUG + kDebug() << "Computed bounding box" << bbox << "in" << time.elapsed() << "ms"; +#endif + + return bbox; +} + +void Okular::copyQIODevice( QIODevice *from, QIODevice *to ) +{ + QByteArray buffer( 65536, '\0' ); + qint64 read = 0; + qint64 written = 0; + while ( ( read = from->read( buffer.data(), buffer.size() ) ) > 0 ) + { + written = to->write( buffer.constData(), read ); + if ( read != written ) + break; + } +} + +QTransform Okular::buildRotationMatrix(Rotation rotation) +{ + QTransform matrix; + matrix.rotate( (int)rotation * 90 ); + + switch ( rotation ) + { + case Rotation90: + matrix.translate( 0, -1 ); + break; + case Rotation180: + matrix.translate( -1, -1 ); + break; + case Rotation270: + matrix.translate( -1, 0 ); + break; + default: ; + } + + return matrix; +} + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/okular/core/utils.h b/okular/core/utils.h new file mode 100644 index 00000000..dc490f7c --- /dev/null +++ b/okular/core/utils.h @@ -0,0 +1,92 @@ +/*************************************************************************** + * Copyright (C) 2006 by Luigi Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_UTILS_H_ +#define _OKULAR_UTILS_H_ + +#include "okular_export.h" +#include "area.h" + +class QRect; +class QImage; +class QWidget; + +namespace Okular +{ + +/** + * @short General utility functions. + * + * This class contains some static functions of general utility. + */ +class OKULAR_EXPORT Utils +{ + public: + /** + * Rotate the rect \p source in the area \p width x \p height with the + * specified \p orientation . + */ + static QRect rotateRect( const QRect & source, int width, int height, int orientation ); + + /** + * Return the horizontal DPI of the main display + */ + static double dpiX(); + + /** + * Return the vertical DPI of the main display + */ + static double dpiY(); + + /** + * Return the real horizontal DPI of the main display. + * + * On X11, it can indicate the real horizontal DPI value without any Xrdb + * setting. Otherwise, returns the same as dpiX(), + * + * @since 0.9 (KDE 4.3) + * @deprecated Can not work with multi-monitor configurations + */ + static double realDpiX(); + + /** + * Return the real vertical DPI of the main display + * + * On X11, it can indicate the real horizontal DPI value without any Xrdb + * setting. Otherwise, returns the same as dpiX(), + * + * @since 0.9 (KDE 4.3) + * @deprecated Can not work with multi-monitor configurations + */ + static double realDpiY(); + + /** + * Return the real DPI of the display containing given widget + * + * On X11, it can indicate the real horizontal DPI value without any Xrdb + * setting. Otherwise, returns the same as realDpiX/Y(), + * + * @since 0.19 (KDE 4.13) + */ + static QSizeF realDpi(QWidget* widgetOnScreen); + + /** + * Compute the smallest rectangle that contains all non-white pixels in image), + * in normalized [0,1] coordinates. + * + * @since 0.7 (KDE 4.1) + */ + static NormalizedRect imageBoundingBox( const QImage* image ); +}; + +} + +#endif + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/okular/core/utils_p.h b/okular/core/utils_p.h new file mode 100644 index 00000000..3b3f5117 --- /dev/null +++ b/okular/core/utils_p.h @@ -0,0 +1,27 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_UTILS_P_H_ +#define _OKULAR_UTILS_P_H_ + +class QIODevice; + +namespace Okular +{ + +void copyQIODevice( QIODevice *from, QIODevice *to ); + +/** + * Return a rotation matrix corresponding to the @p rotation enumeration. + */ +QTransform buildRotationMatrix( Rotation rotation ); + +} + +#endif diff --git a/okular/core/version.h b/okular/core/version.h new file mode 100644 index 00000000..45883775 --- /dev/null +++ b/okular/core/version.h @@ -0,0 +1,24 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_VERSION_H_ +#define _OKULAR_VERSION_H_ + +#define OKULAR_VERSION_STRING "0.20.3" +#define OKULAR_VERSION_MAJOR 0 +#define OKULAR_VERSION_MINOR 20 +#define OKULAR_VERSION_RELEASE 3 +#define OKULAR_MAKE_VERSION( a,b,c ) (((a) << 16) | ((b) << 8) | (c)) + +#define OKULAR_VERSION \ + OKULAR_MAKE_VERSION(OKULAR_VERSION_MAJOR,OKULAR_VERSION_MINOR,OKULAR_VERSION_RELEASE) + +#define OKULAR_IS_VERSION(a,b,c) ( OKULAR_VERSION >= OKULAR_MAKE_VERSION(a,b,c) ) + +#endif diff --git a/okular/core/view.cpp b/okular/core/view.cpp new file mode 100644 index 00000000..2c7c4ba5 --- /dev/null +++ b/okular/core/view.cpp @@ -0,0 +1,77 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "view.h" +#include "view_p.h" + +// local includes +#include "document_p.h" + +using namespace Okular; + +ViewPrivate::ViewPrivate() + : document( 0 ) +{ +} + +ViewPrivate::~ViewPrivate() +{ +} + + +View::View( const QString &name ) + : d_ptr( new ViewPrivate() ) +{ + d_ptr->name = name; +} + +View::~View() +{ + if ( d_ptr->document ) + { + d_ptr->document->m_views.remove( this ); + } + + delete d_ptr; +} + +Document* View::viewDocument() const +{ + return d_ptr->document ? d_ptr->document->m_parent : 0; +} + +QString View::name() const +{ + return d_ptr->name; +} + +bool View::supportsCapability( View::ViewCapability capability ) const +{ + Q_UNUSED( capability ) + return false; +} + +View::CapabilityFlags View::capabilityFlags( View::ViewCapability capability ) const +{ + Q_UNUSED( capability ) + return 0; +} + +QVariant View::capability( View::ViewCapability capability ) const +{ + Q_UNUSED( capability ) + return QVariant(); +} + +void View::setCapability( View::ViewCapability capability, const QVariant &option ) +{ + Q_UNUSED( capability ) + Q_UNUSED( option ) +} + diff --git a/okular/core/view.h b/okular/core/view.h new file mode 100644 index 00000000..a369d246 --- /dev/null +++ b/okular/core/view.h @@ -0,0 +1,113 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_VIEW_H +#define OKULAR_VIEW_H + +#include "okular_export.h" + +class QString; +class QVariant; + +namespace Okular { + +class Document; +class DocumentPrivate; +class ViewPrivate; + +/** + * @short View on the document + * + * The View class represents a "view" on a document. + * A view can be registered with only a document at a time. + * + * @since 0.7 (KDE 4.1) + */ +class OKULAR_EXPORT View +{ + /// @cond PRIVATE + friend class Document; + friend class DocumentPrivate; + /// @endcond + + public: + /** + * The capabilities of a view + */ + enum ViewCapability + { + Zoom, ///< Possibility to get/set the zoom of the view + ZoomModality ///< Possibility to get/set the zoom mode of the view + }; + + /** + * The access type of a capability + */ + enum CapabilityFlag + { + NoFlag = 0, + CapabilityRead = 0x01, ///< Possibility to read a capability + CapabilityWrite = 0x02, ///< Possibility to write a capability + CapabilitySerializable = 0x04 ///< The capability is suitable for being serialized/deserialized + }; + Q_DECLARE_FLAGS( CapabilityFlags, CapabilityFlag ) + + virtual ~View(); + + /** + * Return the document which this view is associated to, + * or null if it is not associated with any document. + */ + Document* viewDocument() const; + + /** + * Return the name of this view. + */ + QString name() const; + + /** + * Query whether the view support the specified @p capability. + */ + virtual bool supportsCapability( ViewCapability capability ) const; + + /** + * Query the flags for the specified @p capability. + */ + virtual CapabilityFlags capabilityFlags( ViewCapability capability ) const; + + /** + * Query the value of the specified @p capability. + */ + virtual QVariant capability( ViewCapability capability ) const; + + /** + * Sets a new value for the specified @p capability. + */ + virtual void setCapability( ViewCapability capability, const QVariant &option ); + + protected: + /** + * Construct a new view with the specified @p name. + */ + View( const QString &name ); + + /// @cond PRIVATE + Q_DECLARE_PRIVATE( View ) + ViewPrivate *d_ptr; + /// @endcond + + private: + Q_DISABLE_COPY( View ) +}; + +} + +Q_DECLARE_OPERATORS_FOR_FLAGS( Okular::View::CapabilityFlags ) + +#endif diff --git a/okular/core/view_p.h b/okular/core/view_p.h new file mode 100644 index 00000000..17a8b699 --- /dev/null +++ b/okular/core/view_p.h @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef OKULAR_VIEW_P_H +#define OKULAR_VIEW_P_H + +#include +#include + +namespace Okular { + +class DocumentPrivate; +class View; + +class ViewPrivate +{ + public: + ViewPrivate(); + virtual ~ViewPrivate(); + + QString name; + DocumentPrivate *document; +}; + +} + +#endif diff --git a/okular/doc/CMakeLists.txt b/okular/doc/CMakeLists.txt new file mode 100644 index 00000000..0e5afe2a --- /dev/null +++ b/okular/doc/CMakeLists.txt @@ -0,0 +1,5 @@ +########### install files ############### +# +# +kde4_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en SUBDIR okular) +kde4_create_manpage(man-okular.1.docbook 1 INSTALL_DESTINATION ${MAN_INSTALL_DIR}) \ No newline at end of file diff --git a/okular/doc/annotation-properties.png b/okular/doc/annotation-properties.png new file mode 100644 index 0000000000000000000000000000000000000000..ee62fd8c2e5b51b2d40fadf0c88f3ede268015a1 GIT binary patch literal 19290 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 KMNUMnLSTY^Ew3d2 literal 0 HcmV?d00001 diff --git a/okular/doc/annotations.png b/okular/doc/annotations.png new file mode 100644 index 0000000000000000000000000000000000000000..f43febac7bdf6afe3c60c611a9639a16aa01b80e GIT binary patch literal 109696 zcmV*TKwQ6xP)6_ao+kg1=+*i|d!eA6ucm!FpN!0==So z`UHBS1*7%yg3-DM>)~}r1VjY`(fp#i{SiJ9zOaCgk;H`g8LtR$1nm<=vnU=j7{wx^ zc}0;tLIxd;;Ngm>XgH=4$-{W48brwBeoB4(nAK>uqasXCJ`y;ftsMd7b|l5xaRlU} z&Vn=^kxCueqp2f-K6Nz-f>Lu<0uR)9AnLzy@VBIXW&MEqCk#PG)QEs=BO1>{ z&80X}Vv&)HMN%#nb@d`8pe+{7UNo82Xd0RAsCViyp7}VE3Q{>Dl{-=)CH2)L@BrwO z&r0REH12^${l6BEiNnWpRcJL-Mc^}h~ zs9u2A!|Rskj|c|j`JzdrAtU4q3jkgbOqf567V^gMc_|Te1j%9sqoNT!uP9Og7exyY z0+Diz zDdnSbH3H~E$VXu{KD>N%R2D>bA6QiF6y~F3LAom0Z9C@TBa-52EJ%PaNbkMsZHs56 zjAgfEZ2H_KJF8l7Mmi!r)oiw&fCf&hP<3GvyEt&(g=z|uEc{|HpsOQ+fP!F%pe9i^ z0WGyC_gZuW=m`^4qc*(}Ju3CbS)T^MupU596!L=FfHdTFEAneeFhjlF2bqDi3|5m;PjD9AdMzGNI&p@u(Zd`s|-%BAUfpIviB4FqBhPhEe>sr0u{ghxhLL)B;n zBSIJ&jZy{U;234&s;G`%Ns>b7<*ofp80m+NqK@omHv?gN& zM1i(IjWCWN9uPktJSrX0h(UcsDMY0$vWCX$U$|EB1!DuNs50gO&U_4Y2DS#p>5fKP% zGD&$td!ogD$<up+$4bCt^Uw5mHUw!KkJRpFS@CKr>YSA zsOKNQ7qfW610pi?V)oV@OXntWUrm_)M)_qZz`rL9zVYL-SxM|)8JGF$`n7op+}vc) zPj=O8EY9M0XYQ)+uVRdbngl@!hpG{dN)R}zBfTBRHx^{`d-U%wufV<@kd^F<<3$O% z8#lZ>Z5;at66UPf*MbLxB^1=mc#Im)J6$2DzX*YV48MvxR0>36jc7a*0p&~_qEZAT zOEE672$->G8WK<72rMqt8I_%4Bj8K+#tqXZ@BcQ1N|t;5Pr(Jj#fY5CrgR(PP*!!M$(KNU|}xUa>nw1ZvsD>GjB z<~D-3?|eOP+>>MHm){D#es1c@lTIC5V*6uR+MM!cx5$^ckWHJIzkT2cBxaV>IS@lf z7_qn1?_4-3Z~G0m)cU2z!`0|+{q3EZ6S7wwXogXQU`hlaDA85<@4UsIAHQyOL#XXW<@+r|r#6VFUw_u6<{9IFkB2vZF(+Zp2R}EoI;of&Zjh@V%}6WQb>=TOIl_}|^zqh~?g|?shp=@IVB6B+qAnXjdhE2c|u;>VRBX4JBzI_Vu2ZE8TWb=^?+@dcp+6mg;_*GF-_Sy@iLKt<&S1_bI-U2!V1M>VT zb^8T;%6wsgFh≤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

d_xJZ-ACIe3nDv5Y**H0G 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

_YN2tG`I=dOG=5TNxKoDARkxNb?nmvN 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 VCoXVG9=M04Agdx%F7-0-{{Wyyr*QxP literal 0 HcmV?d00001 diff --git a/okular/doc/bookmark-management.png b/okular/doc/bookmark-management.png new file mode 100644 index 0000000000000000000000000000000000000000..e958b9de99d3d8c3c897cbddbeefd52e91f064ff GIT binary patch literal 46855 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 CdHLx8 literal 0 HcmV?d00001 diff --git a/okular/doc/configure-backends.png b/okular/doc/configure-backends.png new file mode 100644 index 0000000000000000000000000000000000000000..fed28ad13ff379f26a9ce15d8c088e1dc59862a4 GIT binary patch literal 14549 zcmb8WcT|%>*T4w^p@!Z=2kE^efJm>>tDuwsN;3oyM2d6?2n10O5u{fY>Aly`dkeky z77&moY~J_VZ_n=jvuEf0GEbg+rreol=FZGLXFeDiXaPx>NpNs*fI8ahCO9|`KsY$K zHbi(>NjjySBMuH8j**_32KM#;e;~)fp~S(V{x4KG_!Pt>bR_>5r1T`D^tfbHIAmlv zKp+kU1r}l|mjA;1-xvg><-|fn$4*SgLCC=NKaer<0GT;SSopD!vhV^~g~(V1DcD3P z*n}xK#3(o=sJLbR2Wnmj6`vG?&|?~*#|%P>bRx=hB1(+n>R9L`RGB3;nZdfuat3UV zjM)^7Srp7z6)o6RtT@%I+10JNHKE*EP#%3JUIQ0?V>cml4S-rU|?M{gchXn_Q z_&^2rKzcbKypfYhW`W_ z8X8JU5QqiD^6()i7Y7FiKd+FGpqPk+q_m8joZKUM6%`eAH60yoT|+Z-bIWHo_RtqE z+~J4RF$rLr@(7%>G+#chD73qmy;Xy_SSQUo)NU; zwF3Kn#&-GQ(Y5y@-|2Yw&i@Zs=Mn!yx=8tnv#+m7f0cb>Sx~WaXR`gixV>8934@7k zrRl#YA_A_kEFL&L0Zzoe(rV#55O zo(FT$fXO(nxYqJYQVUTj!xfl8tz>q-JUI2kwz~6y2s(c_agb2d_3n) zZM84B;u#n(pK=Mn16jjx(Clv`oF-%d@j`DvCU;pmlU)F0LdF>bXaYy>sEl zP;DsTPTMNFdP$OYOu3S{S6SS5=HN$lB~3-46J!ln9_nQ5nZ+qKfzZUGLqe9(&4o1u z1-`B6>6Fq*fxfLLyn?LEC?BDn7}AWN|I@W1sEE2NU*vgAMANhm!6*B z$j)alLjO&ew%)5hy`Va-@ z!o5G0nZeh6KN5T5r`dzG1XjSQzh{?qq^hJA7-8>{!c_>UD>mOy@i({flu)P9KP?fOJ!#^b*On(Ti} zZu4EANwqjMHTYFY@2yXF=aae<<&$2S5b5>fRsE27Xh(vTMC&rtiXyN5s{G!Z&`nf1 z-R9drnb>H?40t;$J^puc!am8a=m)ja2d0|!*BHy^YrkCQ+g2pKwp6o4b*znDPFGbR zXa`jL#grMDWSNuAi?g$daNnd*MP#0|(z@e3>CEvg7G3If-VWxXiyzm?X90C@0$|YP z$7XLgaub!VPiF0IW1yCDW5y!Ji*`9Oa~(D6&#iKSt;8TYlnPAtACxz|e^h)9Q~rsA zCeBkoAAFnbk^1juT`ly1+)PKG&(iPImIk?)$%!j|Ra$c}Js-Wk?ZGSq#{A zVqW%g=|=10Kg`*;%#e$;9Hj9NHAKcc@~63R%!*rY4qFy2{(AIIy6V?BH5x3L>n~z8O}=+AVtcLI$+J`n%9SL?V0G!P_t)j1iU3 zSB&Neu`vccEfA#&09l9RULWhrTvXEWvut^WlD`u^;McB{#(;0{ac%lZYYb=JeRZ5>{;JaA0Jb!UdEp3)OK|76opTf|Wu8d~A%^s%x>nRW?P*Oe z3iAC;XuE&>$yWpIf%$<~d$`mv}FJx>^bKtb`cgQA?}N z{KIlP!ZX?85*E%f;YS`?(F{$+~(k1-HD=^3*-6Ly8?NC(sCDM82l`GC#WS%t6U%ACP zb#JM`uGfUpN-ILJ2sJPwui@k>#erM;VAGiG{Yv;B@;`AeS&*BnjFe&>$_Yu%Chup; z_@uarOiQ%HUWcbN>MT8{cHZ*HE{|ZB1w@(Tq%^B4r+$S~5B;f)##Nz>(#|0*L0Nx$ zKAC%tE@iRS5+c88j*-0Ej%wz9_E!QXvyvJ|(9ikk7A z0#b%ai9Rp06+X>Tvcny>t6>1e#D(dW8xK1JQPhy0`NSB*^U-pB9LuX+53r8+(7^@ zN&RyhF1K8UE`d==F_H(j5BNAU06b_K~o0af&8_PP?t#h|jcLJs@a-Oam&gEy1bR-nH$ zyMB^mIzBv~J%b8m3(u^n_(TJ(F+f(yc(2n}B>OUql4Vq8R7CB=A(= zzKYfw31YHLePIUr#*_kqj*f{&2K=m5F|l!_vd0i5aOOQ25}?)7W%HY)ds4m{Aiw#^ z;K!_|dw?P?*1%*h6=+%=)}rHNWc#=AEkEnfb7DJf)r(!Q%KJ>Id+*kks2xa08onNs-+sT4stN zfF2>O9%0EKO@(d}EWu91mg_M@n+VOZsYPkyvc$wKAhy8RdnYAB{wr=Pwij@lbsBnv zkyB~+=P)3`opBL)5(r)U#7;oaIi>IscDW+PVfgZ;$8)6gySw0e-NJjCH?C`P)chy?K~xq{GEhs+34z0PvuOJw&v@As&%yg<)c}Adwf6guW|9i3-+GWPJ2y4 zq-`%PvV6; zjb6GBZa5XXKIlI8AmkxFi@IzKFll?6kKc+{#VX8s85YN=PHE z_T8yo|LmXwz_0oBuUoVkq%1wTtG4fIn{Us8lNC08T|M``iG8aKTzqU_9SWq=+VX9nbB$R^*d1 zj#d~gNJF$-k0xD|!_zx;TnxzHBI^6+qmgc|+cC0rp2dZ&eACQOT}Ki^Tf*chiKHJZ z<}3CqFBx5xZ>`S^M$-9vT%?WAMuEb(sl498jTr90Hia>gDJwbx06&rZXJ5pIhlW^{VF z;%c^}847tZVT8G%r{$)|rHY`4pkkrk_hQmd!k0p7OZ-$0W3!s?Dv-k<%q9VBgu=?j zyKR4z$NZq7%7Py{dLQJx4U4kZ2MCK{qy;@X19ruK;oVF*Dp1nK@`wwI+mVAgJD-iA zpMaRj_vJAPWgf+j?-?tY$o=RpYdcTG_4?W-ikg5E@)*_IXK~7tjDCz^&}mNkaJ#3B zR?2a_)Ap+~;l}o=D2v$@xmaugaf%pa%qyyQm{%kUq`9ODWc!k`!TatjZ!9;$#MoEx z@YJOowEgMhc*TX=2nwJn;r2EFbe7uC<=_K4JGD3?%(E&+TIfSYxfM5!`T>L3SkggRujN)C3CwZ^R{^bmVidFz3PrAD zugI!ZMXHqKv<8s1nNE-9yP~y75NT z{)FK@P{ow@bjBQEBcqZ5h`Kdbj8Jgb^vzDOdexM&2lN5s6;H}81jyYZ^-nawiHO955BxC&Ld{`UlV`bmPn*L_zZ0ehEu!+)bcKNLKsCf zxfr=+A-wGKaVlX~O@sJxhp!wr0EY{yqlcY?0Vn&&6e9;s{FJs_9C~^i7x9`_V1xZH zRSF?eDtJmKW4lB-xvEsy(=hBjFb0AVA4Dou6kco&ZfPX9cX?=6ioTQ|m4>QA&CJjm zhLSd^FGhqa+C3nk-FJ$a-~%UdqiN#Wvg;R%5rcmAM;$1ZIZ2_&ZL#F2{_BL|{rSyo zoG35*9B3Y^L1EgJM~si`_dJ?4?N&jMN>I&`rh?e0s^W0+)|^SUk>xOXIi65tha0@$ zt9`~6X~H%GWAtmrXS%tS?Tity-nY6Un_n5&?Zj!LwS;16yg26v;4JVaaXK=ecGjUr zqLQ?#MED)TP0!zkXDYgd=U9&r`6C)>5mYk?AoNdR2!bW1gfNCCp)F}`Y3TzAEi5!7 zC|b)5{|w~t8`wat`ZVdul_(_h>&d_==N&)5b$TrvF=82Z;CTN#|FZ8uf$vz;XB7Ow zWl`Lfu-owSix{}iC-~<)eyxTn#^CLF%GU}567ApH1c;RrkW+jIZLw3?VP7_FFP5mq z{Zj3kevXB8j(YA648|%hEaAkt9yQsm9Ogjw-1AFCx-)eo?XOP3}@?;en~f%)8)|+5WXDEg{d6tEuvu$f^4LP0qUe zy|wiLrXS(ia*>UE#mc%ET_1&3v`}SRd)q8WpI(f1y+Ca&ulDC+YS4?~z{4(WZ}7?& zim$=R+fTv)nSoUmaWJxS7V)}MX$m@Z7Sn+zz8BqlW|&lQsmBy9(7I&jNP#j5fb{54 zx0NNNjr5(&BML-AOQd{~*r3;G98Uj+WV!G=(gU{Nw8|Z)Ms93uh~TT&xJTqj4bW&= zDqVootm|&Du4ijcujUN3yjTE^f)Y^+tS{23voYbi0LftkkKy>)t;x65Ti}iZJbdDj z8+v*xSkX?PtW(;wlw#tpoZJ&`od9fSJSWG&Pu>(0qkXL$p#dq)C3avzkg|p}RQM5- z_k-y}BTRx&4|a%0cU9VG;4JZ3oa_lUyYAYR+CnqekLunq4GAbTaAzT_RUS7q;0RIj zD@l-wN`vrKq{W19+jsL^YGZkRcA=nZjn|ospDF9I_?d$q(!|5&03*LauFaf!)|)(_ zBKTR3oJP8DUd88dHSxFFi8y2fbd2tdcO83_sWP1L1yMJUxHEeOPH3jIwvJfmoB}KnECE#XMe~T}i1QrPcwP? zCkOyyWbgwGd+;(yw!V=kp3}F81@hDF-E%QtEK`QR=W=LAB+IxL-O!dyYI2fJ+(-)Y zejpqC>wX%rE=WeD$L?}HVlWb8ssR#jnROP50oGMk8P@+ShKPb-ZR_6S0eH!svsd9u zC@K6`3Ymfa&A7i)feNHeNZR9!Tli-_qEqwPEH(QeJ#_!_OF~b{5kGp$(2BKp%LjNB~^j^hybB zSmAAYOl{)s>5?f)0r5+!(b@fngr-#)emHS48dFpI3A9}-Y!j|f$Q=~OHUcg54@W$Z zwN6oeOD1qbYX<4qk^cRpV@nDb%#p~}abB9WzU&&n1=Di@%|Ur4DLYhTS@3u9(Ude` z#H#!3F4o}~z+9AsSdMwWVNHq<{peWBx&th>5CPCn!xECx3g#u3MPB^uM!o+fn=i{s zsjN!r5*NU@#q)_y; zdBxw;zoE6KdEHhIzN{_4x=&?P8V)j8PUM zvvl6zWlo*|8q{fwRsAvn$NhLse7T_i%b(Kqhad=lmA{Q(HExR_J1j3cymJ$N79<8f zU^KCL)A<3W%>3S3>Ds|0>`dmW9s!?6-M<|A6N@9)dgEeftgf!Br}hW%%dyAoc9_Dbs}K ztOsS4?f=Q$K@|k{$o=F=woTeu>7RN*K#-*>1DIo%2kD`sn_h4`6yCzLKojsyPW;2z z4rhU+H20ttSeZK-Jo*=sQx4K|KJ)Ea@vI1YErnaeTmzg3T;BmWL*`@iK-us*CV&eX z3LWVK1RfZu4dC+WSC;08`pv7l!$}EKYF}>A^V{f$}lxa?{|B z`PZ4MPwdzVY;0Q^JL%qn(crs}%{TWt&-d|U@9x3>GEOxL8sp?-o(A!p&?6NbSWUH9 z$f@d-#w zL0rlA19}tQrY4Zdmm$e3slGTfHms&F$pwhHW7HHK-K$qYzsn@{UU`dcGR4?~;ov5!Y9 z>r8|m6;W<@ukAR1`wV;0j{_e%%eX|rN|5Rtlqr~RHC~N*qKCC$e3ekKyP~&JdpMx* zKDH*Hr0pP2wIlpq^^v}QYQ^gBvQ?Qm%5&0yfE-!}2ibd((u|g~NWe)-q6YU~{Q&Rp`_W6`CJ{tXjH($4tlF>#ly@Uv#o=L~}M!t=x zbWH)BVv_ZvPptJ8;^OmHBX%9j72rt}0OCzNBzTnVtUV>f?$fIG4LG;OPxM)heeHbb z)jCoO6KMU#=42r;<73D=cK#fju`6}l;;@9w%gSMya%iU{V_!9tTLf0BaM8LMlew zAe}|NnF_jV_e~V}1uhu9VE-u!>SG^Ko^>39cD|t*0qdccw>z zN-}xhiPFbr5-{ww&s#PRzUfT)lx6XU83vg}@aP1W_XA+@4@sBB#1}l`z8pbN9rM%` zpA?hbD#DH+dAo-@0eY#C&*?gw`HP!r3^W2eXZ4iOJH$eXf9jndKi__BXsz!zI{D@C z!P_VZ+uwy4LpsxUA8ohi#qHL?()@ob0u1^D`)iNEUF;(Q-Kwgs4f|IP1-zggIRd9d zk+YUH+sQ-U&TX~8MQA5*9hK>Nnd8p{p1`2jyrL$y-3)y1c@(?qb?rezwPRZeNIOK) zEpOpX#ylnlHRq;QT)|mWzY{;Sdu8rr`6wIpy(YZ~z1K8U%W2t%DD6O$9)dTRP_uPF ztkZKj4BMG?op~+}D3tVfcV4P5Wys+Q3C?bZNBaEqiM;=L!p3Zhs* zF-x?cC`36}ouLN*zV*7kE<>XtYEQaxIV}Bzy4`=Dzox7iBL<_8->leuE%%)J$5y92 zqn)ix!KiK$(3)(?#k=@|c~5CrL_U}FkTI?9m~=Pq(`}^&MnpHb3|OUjfRN_@VB#{& z%4DrSJaQa_sk8Zydec$)a0`~G`va)sjII>ff9kkJ4c>llOXH_aTu8lN7#J;pUGfRS z^e(-soi#u!p#_Yed5Ox0sM^_ADLHt;U4F0x?XKP4@!#lso{`U5?J6y# z^L6X$q;s-s+7HYF?yaV(JZzaif>`Yx2knNx1H+P!mW$wC+5wa6*XxuTESw;!;f4L2FhLNm^ON2o=kA?)kN(JqZ6dyrZxk!hp`&R)_V`E@o zVB_d^NEy<1-ZNeowZ{&@7i1*UQ1?<p@eqlM>9EpaN9f|_z?F)$d410y5Dpi%#7 z+s7ba*>;g2Ud#3g3Yr*K&~RAT($Z2GE?j=x;{qz$A7u^)aoC7wLqTYi z;dIm{m_J7X)Y%Dj^kbYaD**o4@2qH~9VvqLXw~gF;)dM4?7D$UQs-83Pj8~C@2W*1S@kreA4&oDe>w3tkD;5+4u0D$o6&>Y*o)B3Kb}rB+)6YX3pC3 zKQ@1reN}!3KTQ**<+A> zEmO)SHC5QDVuV0}Q^&n$(r2?iKSOC!+FQa0g0nYUHmcR+Z3pvD#S_{B;_FMl?nT2R z%C@Un8DG4<&#-$~SK)Jcl^*2N_ z7N8QlS=ou!{6i>SbGFBY(&^Jj3dQ|S5u60S2Ww0#+Bwn5j2at}@Hxv4v>CJ0Y}yS{jE^Y-dFo9P=+v|e)x2ERn z0P`UE)nWST&UvhMVm0>mAk-)U?=yC^<w3Os@-uPV&!Mc1Cy~& zrU`z4TSi610UD^A3tS8UEpji`_~P3!%$KDidn?Khb+X3TSQ6(4Jlaub(&&^70LbI3 z*K~#btEc~6H5WjjSRvG2^;k32jX!AdeBRRPS6O*Y_^;;PiGq2*xfCSh$>VVhh*ruK zFF2=_ecGuFyay|v2>|#M5GJ3=sB~gaZ|2 z6!SaEpv5-?V9R8MTtA#2USOKWZ(eKBB0gj4BXoxQB6Ef(^yd{=;^J2&I^)Kn5cIg-4rUHPnWAI~8q zJ%>qUWz6Ie5S@}v0IIA+BM7iVK*V_(V95a~ok&aL(ZD4qBz!bDd)e55TK4dIgWh=+ zhJ1^{x-E=1uFuaERZOINh{M}bD^E)LQID(-t;PU0F+R3->(MV;EPY)Kk1rAu^c2XwBm;w~Yi^A0M*6;G zj>tJqrk4JAT*4_<@+Y(*8vwb^<5%)vi2xPuxyg`$>1;@IyO4@&nr|U3JoSxfbz@pq zvLo%t(qsDdFO9e*l`^Rf-e{u-A|Y2l%U7eRA#kNl)S+Vk>Ff1@&-^7IGE?vLNT#o8 zsc9{4a1Y5KD=tsGWvzRSVc1VYUru4&$ z_s`!!f(7z~ymL#EJ!9TM@3vz*jW*GJm4`-ZA3!sjO^IAftL)bTLd!_zN94>shUxW^ zzIhku4+y8C>|W$uGF9!&{u{g*PhF*aTF9`NE+k%3VIWiKO-@VD0ny|ovm3up?)_;! zie+2$uc_b>!W=u2O8yKocpxc6h#ZW215b zdD*ybMv%!}RP95RX=Fp&*tMU{;)$vAy`sFnn~RWpYcCXu11bmbEE>6R{1O^ge%J`{`Li zgUVvBCe6b7$`m>gwx(qCAbG`{Yc(4#{2R0^tOofU62UNWN!Jn})AjN`D6AqgVSGe``3;XYd{>q#pEC&5&p|WP4G2RU-M95yWg8H8sw$jdVf#%G4HaOaq%t zl!uXo6j+V=``_Yi139qfOwJ=|8!@0?%;@{^xxg3S^|Wqp?z9!p%+nx$`1iMsfZr4g zN4!EWXX}l*x`3-1cYlDZ=9hsFiQu=*ef!Kl{;du6|7p4);R25Pp>1jHzZh)86B;;2 zC-T#d%DrL-XnvvEbxezCOd0{dYk{_A#^?TkpI!YtA;V*H|s4odfrr6 zZ^!KSL5iWlgx~(6&v*-3(`+6ewkSza&@L2pGJN?s^u@35t4`|H9_O-FztU~Gly1!H z!NdwVM3Q@ZIoSw+k+<8$#Ml^`badeU?MLFB=12AM}4(s%xA*Go( z{9Va!5L0!%|AD59EE+d>h~fAphFxAGo#DBLIIh(CD4zGS;Dj^%z20?V`fq|1qA1Pf z?6BYGpT%5n;MsEbYpSj~@#{(|w_{A&C_aV(OqlZbD0bY8Px$X8tpoZe81d8TbwzB47uG3Knr~HjhTut_s&O zKgvHk9+yG#n^;^$w8RzHK9G6dJ+4h)JkA}p} z3fGUg_nPCRepDUXbco|)Xy zA`Z0QQ-6Rqg4vKf;%0TL9N9E|qTejRY%3nMi}95l!!hQPxcKfIoe$HWzPC(zuS5KH z^QXI~8nu}*qyiSAuvR{+VCtpYmLcS(0jYvbzJPY7*TE5!K3{Q7mG1M(S{Mf@JvWG&uXFEqXeU#`nfpFT zjax(oU4JLY_BB;^^q+hlkmGX~iIOk6h<*3Fw)~s29?4A5?~z5IJC$e$dYvuIGL(2D zM*c#AumY^xUof>MzZPI`^8V}G1ICMD1#n^ad$ol19lsVb2qs#H4<9Pu#i9Ga+g?rW zGKwuPU2OMYGTi?o>52ynLBmzLZtn>OpUP7~3#;Hp|g&N-0Ik6U{|XT zafjmR`vEtd8>x2DrsO_i4OLDpBH6ma*WXp?nF@v>?kJ6eww2yXXr;#-KLaV}fft+j zKfhjv4_@_GK%Vj4Vy%ysRXge0YdsplsVnb6pR*pmReQSeVSfYIs>PvD%+ybJeo$pw z*h$d+hK3{sSPYV9<+XYGAFUZ^CZVDdu!U96F6%4F985I9#3zb2sVl8pfEOOHGay^5 zgf+tU&js$-ixkSCc-@$kpj-pzctg+_)wW5q0hsTB9MM8b-p+c3ReVGTBZN1XkL_rD zQI`@j%EkDn7lpcHi6h_FET$B?JB#jq*8yyz=ObvBf3T2%|M)HBnR8vX*eOp(Eq*wg zYHwSecX!%Bm2qiLgS}I(T0rB#PTG-#LPr{vX{lH!c2361d6FPOn9T{r%+B`s?SVQI z%|N0VlIO2Vy(WM3`0P-*IaTti4dyztMe0xtGR(#Ai1=l+QiQUo7|QOC>$c#A_xkh_bm|{XNBjY7H88-n!;=?~M_iuV&~GFUAV*IZ>T{Spz8t~p z#zYnp2m?JAg?~iVy=2-G{f||N?Iz~b*9S^TIABu$yyX3Rg(z>FkLUL9(78y0>k6m) z5xJ(AaWRu7vZ0iDL1ZsHU5>x(szvjr-xcrgGI!MV7(2r;np{!u40zT&J=e!cmS+6A zPH_GTFdBc9W7A)ye?T`W^09ZLx9j51`_RX25qGMyZ0*3g$%HU{z8rzNu+>lBpyC2! zw%$12tPlUIJJ6BpXq7vo96wy8bg`o_7(D#p;pTVa_A=+qZ$t2*^0{2CS8fN~GE0iN zk{dUagJUk!HooN1>UCc0l^i4>Tu}$ZY(%#^ZaJAFvYLW+s-TXLUR&lkQV8l>!0CiPeQP?)xOilL)%4`TIK%YH()Ai!lK5K2 zJ4=8=lzoMEQxDR&EwWisV)997E{KPmC z1fgFU+`c-T-w-im)fju=MQ>86cUTOmH;sDNVRIItV7!9PR zH7s&{<%T`@wy!Yh%LpHP$mN}iFvtVO2XC@AKX({LGFXTV0(;Ay{xwu@h(Bw7Gaql` zaTKz>l1-s_8(ndFg^9o(H)rp+N?7M>EE02f=HBejCtq0xn^e1jpl+T8bt$V?6A|hFS(Uq5(u* zes3e^qM1c0XQn4~6X0Avop^Pyck-EYAn ziJ1yq$cv4(Fj`;A$NWD-qj9A?$TY)$8ZgD#A@0i<&uGQoI->Lp=_=H4&`H*+rC&E4kno6!3Tw+TzXQ^ zY0t|vrE`lkb>KwK@wel+kBMIK3uR8>IN&%SJf~CUfmU+ZZh5IpW+f91`hzZyL93h_ zBO0n#AksP2>D&oC8YW_8>e9WyjY4po?-Sl$m6KAC3EXg-}DJ zn9G57&}av+L3tj^L2Hg9qArnzFoCE)Qdi@5%kopPM>s&#u}xK@Ra*k?5uUGogBxLw z8rZYN>HXn&7#a4?RU1dh{xMs30;n$Rf59aU`cX|{VOBu&yMF&Y&wag^duGneIWu$4oO5ojjn>joB*de^LqkI&R8|6X(9j+LsJ|o} z3{;PDa+)?88U~t{nyx&m`TzSNKtltgp^^LtSVU;JM0j}Q{{cP)1_22g0U;XUKOh33 z5fS|bF)<3bAWB>?-9MngA*ClIV?+U$0)kEP2%GXR@Tpk{X&CY7U?>pKvlG#C5I^Gj z7a%Afm`M=K#E&XT*hIi={FEF|NjM}?B?Y$()f0f6SC)oPfkse;MpWaGgf^XoE`y8# zBVfd&V8*0u!K`k>r)dS#w&&1u;dtT6Y3$Es9?WC;hR-&P-!WFqHBrzlQN%k$ug%Es#7a_#5Om7mM2KYz6T zyj=acJo@w3&*kqwmll66FQUr&?-xsp^UHs+fGWSw&!Vt6H(fn{wlF(2F*83uJy|+) zIy;G+okGq`j3FjYrjR4k$k7?(*wokv3KJv4StCcNa&mOIe0X_hIBR@(0QnEb2K!M+ z8Q34`?;RQF8y)B!?&}`v{Wgq3U-w8~&qQB+TTky`cjrL&*O6~csB*BoYv9}0fvye| z`n$e5f8Fl;+R@k9UeP(Q`t{XBXAY{~)A8jm+FQHZTDsbryT1Izw|~li>;HH2zx`RC z`@eo}NNnhB`c&mvGEh}oELpO=Q%F&mm-{X?x%RF5x3`+|u_M0Tp1fY|?rtV+HX;wq z8iGu;1a;F&)w%rS_(Y^FSS0AO0|SSvCv=IakMy< zjeGmW39QwS#u-~?D0g4<8#p?x2pk!Ty679N>(;JjDCss=dO}6Z^?wgek~=aHH?G0Z z*O(jqJMQDy?s?bt5{J7rpkqz3a`$I8MVXqf@9#gqI4gQxQ_Ghb(LX3_WzASy3wrV5 z1-z0I(5HZM;3uc0{ojOa0%V6_gEbGl|ACb?#e^yaBM0Xi{4_k+a#~@BiW<=E+p?|@ zsz@LS#bGMExD>8v+_i>_@r5dp1L(J{dbCBdhy+(4o)A5sChvz~gE1b9>y#EaL5W@t z0HH0^T36^THdy0JLldUV>%{<10`z>Ym!^IjdKE%@Y0n#-(G@(Y^5LhEUm=W`p^Z)f z-^%wF8VTvZ3TPqhUEo)QW#Kp0NE(GgO8iYf}b0x3;><74X z=+T}%`hF}K@CKRvabK655$+kLBzQWrc(~{1^W*Loo9I#CkMeji-``*FZm*Y@PDU49 z%3`)*vY@hTYKe@;k$(>6u|tXI#co=OD;G+C&_DIWq1#`qd#;T+W;Yla$%Objtj&bq z^Y{2lLPCLJqCtbUVxqSv-BZs`#oK4X%j3>=B2a6PQ-CG*`>3zuA?L(ibho!PYiy_; zt@Q=Ju3sC!#+C-ZkM;giqfRX^0u_Vz+ceUT%9(Au_9a`gY2;pwmsg2-9e39-(MQRk z1~wjkFQS#Ff>Hri{ELJE)Kp^LH|tR)V}VxF`Z=PvD+6nN(%?P2R&zO~0vtskarzfz z;hOt>lvqNJh}YF{pRH7beZ^>S;O1!=X`k3nqtgQuFEV6-U?`3^zpua|BQXL4HI`1w z>#9KW?&6Yit6jo&S}5q~l0oWn`?PFLQH+Q_NfAlpD;;)AAg9}r{WuIqsP1ivl-Tur zGtad_dc|0yevN`<*7FkXV!s-Nx+*=RAWrd%jpnh@V9zUNN4r7T#J(*Rv^Kl5V7~qK z30x!4ck}JoXpWD6fS(ReRY^5RR^XZ6s|@8w>dxxiPNThEuRQGPINS)kdiWl89dgV< zT2tg+W(e7?87+LzlE~tI`$fPs=()hz)U@iF^3%lKo93~nq9-f3RPxv7oftIpVUK3S@A-J&EZgtY=S-&lx>|(fRp4?xgIdene||D` zLRHBu8nSjq^3Tu4So)(wi-&@?IxbEvspLJS-V)36gl=1tgC%}+`27Bjv@9_L1zoZ= zKG&id9FM%%2>(6)=E)N>3RfWM1(2i+BsH5C(n=SDMdP8z(aE$o@k&2qnw&R(ciMGy zNYSkCg1LT_`2!ZOIiU&`w?j$(b5cRL1=~|K1yRK9*qWE@hzvL2)QAxNhvDXD|B=*t z(d)}gsk$#8QW+`pRHa%lP5HhU!dA?wFV@_l(qJSFw2QoXI3D^wSzD1)*3JnVd^$#O z#JjN&;CyTdbSdqp$H5H&bT3ix1Qc(^`GYW6w~6}C*p`%3z$S5DVxX(56a>NM@C_~! zgTt*O_QN(T9gNjSFSNlb+8b7UUZ?*!l;C|rlBL#L!W}Sf^oBzoSy{ex%5n-wkZg(* z`!V84TL|e24Yax$zIxd)R}b%g-1Mz%-BlfTQvuwbzY^oBa8baR5QW!;%NQBwVk5cc zx_`NNK(!Hf(f(P}ty{C_N6+Y>ntq>m_nPS=8=~ynqco zG$}|Tphnk#EV&!tRI7G`JgA_3;ZUJPx)9qM`T9jgpyh^b2ERO?QBE*G3Ce2kKkkYLDVkkYYtaIOIfMEv8yeFJ$-WHf1se}|xLC|C zgGNQ8vX95Sbl7otLWatuUEY#q+NNfAVA9fX5EtVAIbbs--{Su49$VBL zVVm)}55EY%=%b=Jt5-(*feDOka!J(pCVQ86B>$)q61P3h5_;D-1M!+3p{q zjQv3cu^LXwcwQX@dCA>BYf_q1KH%=={L}xl0rQZ>zAjgX_{(JWl$!$(59RRJ+9;lk zj4R_$?{S3~z-0FN~rGkD$Koi^PW14;{l$`!TVs1t&3MrUs81%#hJ6coUg*Ui1}?L~)b$V}aS zHZp-=3?`f)BN1s152f33u&(8-5lQz0AY=>_X zA8^_-EzlYmWU*6PSc$UBj!3Ul=3l3zrqwD7C8u@FfUx-A)?gNXQA=SgQy@or#^~47 ztSr)41N2BUjZ!$BA@Dw~-@tTikO|DFu|Hvl3hw%`L3}(34xhTUq#f^XKYd(hxrQzB; zo8^GzCXY#qS0BQWq+dz+X|})G zQdd2HBM+J|js#PvSg9He$|R0#P>u5&vs9HuFr-X z@Vhv`1he9>VZOrMAw>EY*Bv+T+$09>zdk6zAfnx7km?uDG zv8Zp%N|%$yDD|q_ELGb7Aaf1n;#~$@m)zA#y-3qPVw$^s#Ta6;cm`Jg3Sw8182VPh z9H9rS7N*kYwcr1|JN4DiBJ<0E%Dv3;QCW@reX%b=iKV~FhpwZA^wPciWfcxhAsJxw%;^UR%onQdHd;@2#d{<^3 z&fi6^t@N2WOI!3a16nvfpDd2yWgnRz4NJWav?>DbD!)lh?H;cBK(+YWnU<6dRwi3G ztvxQ3aU7yO+2GKHXB<%q&C;L1%i^B&Od()MIju^j2~&g^(myi95VzM&pb_*WC(++t z5Y&G5K}I$fq%AMPBo>$%_n?6aiTC& zK&m5y&FN**@uK;AyZ#OTD)~#pw-W)+`n!jkwG+SuRDZ++;t+q#3aLvXIL_+@l*z z^@2BQczl4}EjZIQT&#;xTNz$-HYIoa^Np6wCT zSP@a*^8H<<1Q^6Zhw%~NGYrXo3LqCV2z3&`n@`Ogw$^z|MJEI4r^=sUYVp3snk?&* zQbgpjXe$cju=bZ!5Rs02VV|`5HCcxI7ElyVN&F5=(0r@ zz$AqZ+V$U4c@-pP$plk?UkA?O<55>#!gY*ucAq`RyEMSPltvq>r@YCh-@>>YK}UoN z@j<YHF#wZ!=a#ovijE#$gfJ5{vc^l)=^;3oOyzZeVMMw!4--vJ z_93mYQOwEdo;t1X7p)iUlIuGv)mN2=DrSxE+b2F-Gb>3NoL0vDmLbuW{`|p=nI=B` z1~X~tzzjda@%2z+-3K8|@Mhn}AKx5qN=pLp{>-FGvk`YZt`-_7`T;W&;!GCmNA0Uq zl}cHjRzLnyRbi@*;;foZbMk(i{cfs#5c?x!*;ncG+;?d`@4@jSR=rC>asuqL=-nus zJgJ}~-+e_M35SsFWa&_9eoauP_n$Q=wZ@Liz+CeaUvmAJ*c0gYF1pI&kojMA7gaq) zU0oA5W*WJcQeVA$vDcf<&jwvgTOfGHQ!17UuAf1l@n@D_g76LhbJ?J(c2O(LZkrIx6s7T3C)Qku5|8lX{5j(At55ne? zPZIRCt*O=h0x@Y*xi~$N7%n^I@(nQ0=Q~+5@+xy>d`9WHqzO%M&< zzj(Q@xQ6lMhXSSQ#66)7RO!P*udv+hB2w&4PGZm?3dSE(b~uap4;TzAn>gg0yGq~MJY3@Y~d=f0H;=;5RO*A1e^VE{n9 zij79ix#Q;3`Sj;$YoW?~4?Oz;cs*q+X*OqcEN-4$mf)L6JOodg2C#+^+9EDjEv#9A z{(iPxnKQLQHE7C}XF8<|mwv{2)3;5%FQ_yL{cN8df#jo$HfwL_CmKbqIHrI1^WOR8 zdo_fSx~Pbz#aoE`aV_KPV4Ge=9A0;#Y(_v}Lrqutt!>4`Bn?8ED6V^hIHEl!mMmH8 z`n?kKq7iy`cSOj1&oBN1FQ3;^v57I*++4WuPV>wDT3PWK2w=g)}9*!E?bCZWkf2&%-#@A;2ia<^YWd`yRfLW(PS0DY30$9 zZ`qjOwKKo(qt&Ow!&F1#MsbWl{nwa$xS0T7>9a;TiJL5J}3e3E?TXxCbDfK*&~V8*Rv^&ac|D3ve)G#mfXk)E8<4+4lqRveC^ zJikT+#dk6mDpN+$?S{I_B}(Pu!rwO1n9XLOnmsuYRA&D^|Gq8hHI+)_lA0=brE5Gi zyMwu}%i30?ttd#;98^P~sv$3wj!)b>x$|_CR9SsNKem7HW?*4tPI4uA z{o=ec(n%LEqLk@=@}rZ~kmhP5L)WvPA$QdPUwbH4gO!bxz3gGYyn=jpi^yEBbh7%* zMn-$5Zhq2{Ur>JPkZk?Y!l$>eJ<40>CXq+`F)BJT8iU1rqZ;_yix9kshAN8Wk;M&2 zcBZ8)d+Jb+tk4-GEjh;eBB0dGEC=-(3^^VIemsU9d${Uxxc zP6-$&C>jJt*+7q@C@8)W$dbu}zx%(Jdx zARTuC_gp%qcpM$!Dt3Rwpk1fEa6TU=50w>l1HNuyh&H!W(Wwscl5g!f zK+6aLB9w4RtNoj!guUX|u^(>=sAWzomm>^iiIFM?+sZAtik6$sz!Q)jIKuMRlD_<* z6mGD6#q_te1j)@I}PgW=4{7kE<94u>D|vo%Yc zW)@VetmnQb;DkdYlF1X!xHXQ@nZuivjQQt@lYYwLzFzhdwS*@$IO_5Z3xXd-Bc4dp zm-0y*n?@!^C00h8=$tl-l32gK-WLve1hJ>@j2=@L!b#B3Jf)VklE!1}^!4~)0u{5f z)z>ea`BfwI=c+%=$CkZaeA0ZT+$T*(K8*+5sV4lqyq-S;ethA z5pV9=Ci_htpKM(u%-N;?kkm_DeT-?BR$)+eN)agPOcy#Y>tV$Sm0*ek-0G~HyGr;a zNQ!iM{k@p89D!!5z;Q`N_x4QbLRSZe8n!-&A|EHPu)L84hh#AWeuuWCFY28o*QE@d z9XvLbPH!q&mUotNf*v+L&7RtpkO81@lpVt%e_tuIU`aMn+P{6h(x-9jjy{QYlIDl# zV3y%V9BALkW)n%+3@&tH7%E#4>gws~>CasGcP>IfcE}_v=DP zD%^QGw@9z9_1vJK9!%afIp>iKaKxMdgoLa)FVQ^EU24OQ1mqS#-F>k{6UGKEQ&FWOV*MO zk=fa_*IL5=4ATX*zhR{XFY$H0H&N|ndwFUA7dlzSN!4jh>RJhsCgZ`=#!XOAgN^_0 z;47tg5aRxlU{g*;aE%r4lz~ex-xSkpo7IAFDG>6$OoBtFxwp-~eUU&RPLmvS-`bHu zVH8fgi6U7%VFcXzg)|wv0~8+}IwP-+1x<7k8uF*^&8{Lk{!XOL7#geEvWX1G&{Nxu zoLSewuZ%jSb159IQbM02cVSKJrv@bamh_+L2*$&KqmbjpQ*cImIS ziiIL?VC;esQ-0V?e2NGRz0E^(c$Kq{!kmDocJnDDM)o<_NsSM+1RbUB zU^Gy*mpE{7;7C6dJsaSFV5RJFKo~7PAdiSR>jQIfarI1hk?3178m4EJ3>|&{HBm`X zN>%ixL7W_(>SIHJkcN?gIyI#>yx}ccv1_|o`n-xzG%EqDlY_WAP$%By8azEC108wH z2NSCSRefE`6l!XbsKfe$(biTbz3wNhWEJ>Sp+4t#Tzr2G8ZrhS|Jsv^*kv4#*%p1QT#Ay#o+xr^R5HIZY$sbYkkD! zBy5#50*6E@PN|R;4VnDTZ{5Qok#7}A29eFHcbt5YZQi7Yk~E1;A(R9t`YfgxBdY{ue!Kx|dpyN5u3-{V8M zLf5APPoXK#)UlyrfjBYMC3H`V`xMETL>B?3&?KLu@yQmI(hX6gQ)mq8T1OHfIeqZr z9K$t4(-p{M01zRAh3xnIZEZs|r6YKh5gc9iuP4xf+sqYh(VYfHTmOaupk{pS)jx1Z zIEb+GA(3;K9owNXlW4YZ$2VdXH5fF2G}5laV7Rr;!Yu>hUbwV+C+zIh@mUolsvX>y z_&w`2n^X>bcvhc&x9ajPp!A66WF3RiVnn#Ut?+}VM{YGG=kUS_CzI{>I=MfZ!%>unw~XeT^^;BCI61$CB8b>IsLj7i9=LbGIn)A@BsXh@-h zRp|;p*!VTa-8X3MD{AW`nUuYcwpxbfu+VLNcxXAY_hW{TX~2)~yDnEQJv>dVuJ8%_ z*>RvnI4A8aL20q1ZjE~?l%8gV=R5(nG=;N3J)(YDETm~^Eu%w-BVNsA@-;`W)}gNoA+N9VSA|AUNVVZC=vD~WQD}Phxaf4W zMBsh%w`7>fNB4V$)R{NU5lZ&KLrQ};0bsq%pUt;;%mX3*3MsEbx4q$7Z7(A_mBRJZ zcfs;!!naMU7&V-ITAe2eTdwU*pB}RNvYE+QRYh;NXyg2AU@y4~2e2>=nP$GG&2gLVngJC&lh%bVOm>Ipl#f zR?D&;RawROhrR_Zl(^T>P7OuCdX9?E!_SAcQ*jo&@}r1u(T4w;LF85A+mk*w9Yu*i zy+5u;+H2^@pQTYs89Ps*A$mYxd1Anl=4OnPIH8A#E~5hxMw7=IcNO? zwv6X@!QDXa%yYZarkxIQ25mK!2Nq;)`jO!6$Yh-R#t#eH=0ev`5c@_D^ql&Q@XX5? zJ#NOwgl(B=W<_aAIb&s!?%|1~kIS3{MxMocp9mzwn#5HKvP4R8a4Q}mD%mF`flUz) zt}8r*!}CJ&Jj`eBCF0>EHX+~tU@#$emu#x^C*!-O1-0+1E;<#8rrv0mJbhl?37qE( zf)$>FU(I2u2}3PNwO;U3qY-cX2Gabf-U;o}k0n~)lxA-3gp_yIbQQ%!DdXnZ<9@j` zv>UT|@$o_Do*uvx_F>yy{gfW>W+0FX9JCQ*wx+uKC>}z?psgj$H^VISdjDY9pW`W= zm-t{teYnd+?Y5kcZNKU4k#E|J`TZJP`r$GfHkg4<1<$q%m5w~yn#Oc_^m3N2)Z&*> zV|6J1h2=I4^nzvKU{ql2CJJoa`~Z#dW(jH?><_LXj_tp^*~*dp#R}Ns&nS6XF;4tu z#**L^2;vO_%G_T%`22pnTRvkR`q5Noz36*(Q%2pkFnDJ@~>)irP8Hy_)3uCe`>4hZHC zE9itSa^04|2vd9I%Mvtj;9%bN^t#Ljyh&^gyfFj1*%^6sFR2QzbXGX^j_zZrIwU>X zT$*RRM+ZLAWC@j+Kv&JZ9m=PfIV=4tp1aK4_UDA(?7Em7*{HfKMQ$gSe0ke;<@clN zmLPIC_ijZ9=#W}b9&J2sT?E};#j%Ml)|nnsfrnh?1EhhMHj6G$<&--4AC4}BtHy7( z&kR%69eGSz36XcRFPUv${_vqjj4Z`-rPMj?Icu;%J@tpVB6hlaM7M<7)kTD zv|kUNvKZE@c=~y2JT5j?=MjOiE-ci}T~z~yFjXPEeZZb(j{lFO?qGI7?qQW81@*R4 z?Y}ZW_qZ;{*!n90{5r&}u*0PrjeLf_f<-pHUs z3oV%V3=f7bslFtQ+`iviGS*zQdlY#zX1MV?*>O)qu*N8x)dPgdp=Z(fjF6uOnIJ|T z8a}8TvtXQrXHlm&eT%cf0@e6sV}&!N!MS-a;B}u8o9VIFPg5(}yAjqKXim?%J@~{G|A-~@<7%Uqzm1#L zf{1~~KxzjgG(YgT^gxV;v`wSWjS7UW?FhKgDylLhVD^&Dx*3te-@t?r7PsN6_gEv$ z00jm@7i!2s9Gj>l;%LPeFBj4->FM|!6AFomSE`;A+pHOeEqeP<|HDVjDc(6YZmXW_ zA@D=m;WY-rR2b0%=mxfyM|LRG&5wYbK4jJxv*3eSUiECG^;J~#E!U-O=yVFyf5}SQ z1kmAc?8*VJW}_uh5#Rf}+s^BpUp9VDUPZbB0v6184^T4pTt%;0ugKvb5IWKrD7$|9|;77;J}OJrkq;q)7U@R-R8{lki5Zxmkz+W zx5DVnYj!5gK6MLI<(Mp%JdI8xGBvgmaByzU5X^f>$zy_95Zh%2=M`D#n4?4#T-i#Y zYhQsjM#Ff2RWI!vZ3NInNV;t!wR?7#cE^7^j22Al6)W&~-k4;V_%k0X8(gWt{R_9< zgGC^V;trGjPeDZl;EzK;?noPCDS36s9p7TJm1;swCB~4$pT64H8>m-aBwsrwL^qq= zrClJS?cF{V|3L6uiP94SKDgR`ObhD)2yYwX4|Rk*R9uwU@XrtLupj%$Y9@1E9f143 zeGF>XA--D{85`ekf0rD2!D<8H{ zJAv|hhO6fkY(}F(NVOJq;PlSaAl-vPrn@TuF#yjp!9;H7Zs%IJUQ4H{0Gt<|-fmC@ z5biypR8Bk-$xD;-Wj{>qi=9eY)2BaPq9I!2?O4&lpuf;YX$P!AfQYZMYl7S1j}+Dt z0XT5f*`sjBgAn`IR{jpn_jN)8x75Kgl}Cx*QoXHBK?9`8*kpv&C!Y`)&kCw&AE%-L z1HdUH6F!A_K?Q@#`KdfRVMj5DJ4L)YJu8(tKddM(#sGtQwnJV?nA{#kF+7qoq1J6d z8M|zKGyT3loS@B0uzk3nmKW?)keO-#7?$y-S3l5>0qGSOQ)+kqQVgtwa0wyUvy`Ld zbq#8IC&(ZV5F(k`;zHK(wGEu0qKC*ZT1Jv}PL_`|wENzR2YtBn-jCoq;dT`?6b#89 zvQy2eie$@l4I1}TUd>#`=u*Q|=54w_Wmt9n;Ir2grCG%)Y>-8J5EB;4z8CUzchMR2 z+CIW!I5}eM4fS)#tpD1nig3KCP_C3I{#C@eX3r;@zCxvR)NvIn&)y^HhQ=$e?h|O^ z=itBe1l~5r6*Br8&~kiy**E>oKZpqaX1fJWR&`Vd8y(?Q#zve%{HeKEp-v=-+F=eQ zUT_=0|2PD0SW=~ZDy@Cr&?3tb_h*r-j1#G%5raB9 zQpw6!g5Wj<{>QK2g(-t?%PJ*GA?Vt_Kzcri9=Y!Ii9v%QzE)NQy}(f-JJ@z!BBFdVGi+;2dC|Ezjv za&;G~NLJl5BSABL-r9kVT#`LyU0?>j49V}j7pQTa&3$;B7@SJAk9A*2Lc;sLz zkV$$N`w71@{>-(hMPcl>B`d@~%tq&I#R;<7dV#M76)P|fDBD1${R-FM{K7B#9sdBM zOynuKOe!Je>E)d(1zab}?l-zI-trZ)y6rS|^rx`ZFHY#ReKj!w>FzG$>-TGCltt#;U#Pe>Z^Ii zJII*Si`2OcWj}}tAL5WC63+IKrtrJ zlP4}himpApPV8uK@Z|o>B^g{N!tT&`?hIT*DpdKWZ>yKlx$^Czi7a>|9;7Vz(Z>)S zq04x6Fw$xlzHQ?()1d#+BPQ|Ub*+zZ$15WTy1x6F!6Bv`KXeCzUTLQ?>RCX>M>k5N z?AJ9yC!R^Kvc(Lk@;tdyC(Wsi36q=p#1KwTU<>47^sq*!KJj+g^ba#Pt0>xX-X!D0HdbBICd)0Y?X81TT}U2I3B zjXZpFR+>x*VB<+|#X?xbb1_u>5SZTwwMm~(3{ZB;)i=~<%CkR5T=JlfGH;sRd+BKJ z@)5i%aywQWDpp#TokfZ8A^K+3gCo}M1x$}u1pK>HyIG{m0=K)8>J#Sz?o5vnR+WCpc3T+&o zOOu@jM?WOet_p4g32vN7g0R}2Yd(llIuvtUex)g(83G%}@)Wn6jzDB=Rkc3=R=$sp zrk^Cjr&~|_c;fXrdJf)f6D+B+CEoc19l2bJ@W3t4Rxd@{{oKwsu#4L(YY>>XXqleq z^}S>l>e4&M!o^^-7#cEQpi>Xkn59pA_2AL961j`7N8WjxkZpF@ zkN_(1Lq#w3B+jF77O6p8Y_{Gee#Y_#S_f%V&RoSmC*3D$VuxCEj!K)b!#G^{hR3#^ z6K{D?VS9EA3W`26H9FuI)q7o^kFI?ILf#xd%Zkv1e^HK7SU1(|Ogw!xcxzCfi0%39 zHt=^Q$H&2=k2x(sy|Sgmj(&1)zNQ_f<8!7UXWP$M05Lu?BToTXe3n5`AjU{C=J)y) z-c!PY(IblHOeb3-^O!>ZAEWESeyTe|(VL#~M1zO1@y8T>-i`V|rWYiopg-;IyL$St z=SN%3p_;F1?aF|=OF@vMj)8)Ju1K5w#OsI{Q`=S864WN53`>C5GCaTH9YlL zIx56Ljbzr8jTA%$1qj5OEpsJa6r%?(-n=uE=Y+-mcuMczas8H@`S3GRLV8YpE z)?X#tkQJN=d+*^rK{}~%MS-qu08V=)UN=lHH!HwWPF)taM6IqUgfhBcqV<_QYYNbG zc$U6IT}j&`ze$&8FHosMng`L_=ylrZC2CMRgX{oM{MCvq;)T0+L?rl6^y3H;qOtilTt_UkJ9A+ zHsLcafZF52-}GZ6O2w{6I2prd&6$;^nZ$A`Lts9hr`z8Qi$dpr_Gtc|R~nAC!0}%FkJy*RnY?cYx<{88K@oV! zHE}3AM@&{NZIy?faLOjrZpC5y*xZZ*1dkiR79yuRO_uX|NlbWk#)nNRRYht+_t@(Z z$^d%Y=j}r|9LKNE$3ChQrC8G-=MJ7HPAQP1Xd86dbqs9r87u)j?DvXfKLp=CUaYeA zkR~$$MDx-<>(s`Vmcz1&?UC!|Wh*ld8l$$-WHx|xwQDccmJyP~yUZ{f0~NXq+sb=k zJx})C8-A^Okb%&X^NySC`HrjeBvAD+yas#T*OquFqY{j{JVWUV1p$1N^dLAL%?Qj? zQJPEx&|FsZtT*J+6yoeZH>hu?Zw!fzQjC+UEhdi*t<5!VDwujwAL7;7^-#MhZb6SO7^e=bYHF#&QAT^j}-;75M))#&>Wa?S>w%_dl!wMB(+geD_mJxfm0_FIJ% zSTqdf6;R(@NK+CJ7AP6(ziIr_c-V8!>K-j3)OCjnzJyihb1Gy>K>)m$ntu-l6R7(q zt*Z*(X87L>PY$5?>q;0QS7F9t?C79}Xt23yC1{v=?QAdF-2f2YO2C#3kSH^w3daAb zR4LV~qB@lRcK)9fi$-dj4hoFZ+5z)Igkn2LC$)HS6Ej|L}>y6@2A@c>F)h z3;&;Ofd9L{FR6Aa|CRrDkN@4ca(5U|@_$UAytbiS>`seZsc3`YG_-|sglm;yLUHmR zHk3llA|@zw?euR_5jszPS;M3Zr^*q(>{*-#d-UxqJfn?D^rrL+pf74`=Pt|2?Xwk( zJFNrt68rRRM(2+DbKQES+|DH}`$$C?!=h&oyxXy@5;eO5HpNda#Mj_S05e#*{ylKd z9IXURfkXZ&nW6uA{q6d1BO2%b-B5yt{x4+}#RT|=6+n^1v_oDE85Z zir(GDzpl)pJ!IGc-xoK0 z$Dwcw4NA+lN%6#J`}a0qcf$JTo42Awap$Y#7*pnA{Eqs!4l zCc6s}M@o2aeO(}qmWTvoAAS~RfGvBQvQv!WU}{aI`3#^@S6(sN4d!@WGLS^CS}+fn%rj=)TvTpPxU^j zCet}v)pZpkc2YDbv!_fl=3>2=eX?St#1kGL5tGyDqk9z3oye%|in|aHG8k!+^b#Z)ZpN?}&iCEb|yZA~(}XmYq0X<;3r!%k)8J=%!DgfpCx^e~#rUb=@j z(cqhaJ!vQIN3ikcL3Aob4^9SGVHzx$AI>}n z*jZ)&Zer$6Gz0(Ef?wTo+S7w7>uWctWv{u5jHH%SFu#$N=rdy-_!^kMiX2064jbBF!Lx4I}O*rv7E1o2Lv2d_)dPtOM z6F+U=(4))2B)IZo^L^bP13}x-IR2Q1w?-|Gk)iJU*V63Y1leKFS5xG=VX)fay+WNv zr`B$kn3bSSBM+D8)A<)3-uitZwXkFqOg2KWY(){5DaPfl(J15abt58pKH9Uun!GlK zkP8KR9v2m+Jur8Pb{E~e8E7U#kEF|;_VtL%rsf8_$bIKXT_?x)Agg;g_feIZkJdzy zD!nu-29mBCZai93ZV~n)8PGdxbA_y-MXwhFU?ac99pG#8*CI8l2wxSmNiqG`-DNAR;;WCroD&{m zkJ81+cz>7GwP<~vN$JK{(#1K^=*I6Hb;>75G^o&zQr-si`6(7DahTGZws1*hMy6wr zpMMx4kvF0^-u#g3)~`riL9AgW3I*%FLH({!zmGn8v!uAH8IF%KzqK4YZy~WKu6lXX zUUE(ZYbx;rm2_~mwOl*SoH%J{rJrM0Df4(M(@rfDSSqd*H}JW_yFQG3=?v%(}_`kN$-)$p^vl%l>%Nn|)p7|I7>v(R|G=%1cb6T+ycZZ4k2?t>t= zVW^+rib;JeRZZp4!+%!5E#kyD#wXf49THMs4Kxh&y0+YTK*QN_IExi(#0p}p7e&Q+ zW73W5H!tWc{hL!du}K_lyCfdLv0BJt%cQZ2mayw6euJ?~^t&)PX-u1+5Rrw-okz?m zOLY0!Y^jjjy`tdE#f#RmZ+^okVez_3oSC0gvqLC~y!~LSnDy8IT0$D1@XMgKUa#sa z(gY)BX@pRwyl|e5qkb$=Fl+y7 z{aXk?9QRKQX!VR%^?cur{XY{i+1Y{p()zv^fe@SbO;CnKxH{kGY&s zEN5@^Q$MixnlhDmV3^fMn*ViLeLHjV)_}LI;66`jQbEdCupq10cdOymBbV?8;#vki zmOZEl%FJQdNN5+cmpME0H$3l2n;R#PPl~gH%atlBSgZQ?2TyYQX!E}kkkp;nEa7|csyGR`*azgv|wM#4D{{A{57jjcwvenxo+9U)h`5}=PM;gor zt5Rnj>+qu?Y3pKHXsfWD+|*O`WCXL_JRnG6lWKt|oqk@NXKc^O5j&ZQXnI>;aEax2 z?BagBkMEKr+S61g$K`DMGg{Cg*T0q#i61~r_eR zYw|>^`fIi?qTiO_Ft{CeyY(@afX#(T4=N;?-Ou^!5w{8Qiiv0E_YuFrx3_%>Vnq9j zOzuAXF45m_9l!bCy@l2zBIOl7fLGW)j3-CR*#S2tz^*V(>wFB!^h@wT%%}XeelGvZ z-z!YvA4LK$tPjRx=Z0MsbzJ}sW#$9hGyQscTEoLfr%=18*>k=XrmL$DpsVp*6GxlH z-FT(B5zOdr{cu7;3UkH9rhAA4G_XeoR!H+B-pQ{iHB5ygV0;s(vPA0M0xMpQe#^YmNJo;bU{{P6Y+y zGlb3zhRUc#3vwDp*4hP<_|tLLIn}SUCSy%{QqZ;0*|J=a0+;LS$~wsfngMj;=2r z>T`E^T|CR-aZQ`yZcAC4!2bcf= literal 0 HcmV?d00001 diff --git a/okular/doc/configure.png b/okular/doc/configure.png new file mode 100644 index 0000000000000000000000000000000000000000..8e78819d1b4e9bf50e111a907d3af06b651d1b36 GIT binary patch literal 26691 zcmZ^~bx>SS@aP*ta0@KKAp~12xU(z}AUKN$hv4o6hv0z(2ojuKB(S)a%@%y0&`H%=Yx@nyC}6tR#(tNrw6A)hirX8A;VwuURHz2+1>XU?EfFqF$*uK$L+``BNUPm=E=`5 zD5V`jV5eZ%>I)iKr&5_al36<{*@rKv<({bJB3@9;MQG-o$-xh`;Li$$XD_H0{f9;o z;swp}|IjEudqKM%@q%9Kr9u0D=yzS|cU_zIUZX5LAKg8_dj9`-{=c;QySuyF+sliK z^YgPO#PGn$^Xc)?(c!_^;p4&n-u~{+?)Fyi_QT%J*7nxM#@g!U+UnN&YTNqVi@v_P z(ztTFzPh}&vfQ(J^dhU4ZdR8TR+pF7mY4oVStyymT3(p@|I;$NwlFunG&l3Y!tB(4 za%O6NY63oWF*h|aH$Ay9JvlQmx-ii(Gch(ZIW{*rK0Q73%$`n%fuUg&Hc?CRL=^dIbM@9*m9>uPv9F`8 zucP&a+_t{n_SW9E=6^4;z4iZ*t&QESO)qq{HjT7GyIYz%n;W~D8(zq5>gZ^yf1&Gt z%ihMkmu_1_ZCiZ}tgfxUKBcv`>R(NKNaa97MQLVv&1orVMNxi9-e2F`#=P8|j%>%= z45(W2U|RB@;y)3tafMNl5v`HxrtT5m-&{++iA1?*d~~V|bTl?~bP%)6{$(!y+NAdH zS3YX>7$KD)DoALcB9E?=3M1I@lZd#t2&)B1SXB6{vVgP-zrq(jCIw!31#T%8?#}|; z;u0U}*jN~-S=7~-DQK9a1sSM;bd0~~h(1tJ(O_aWOX!@vEC8^ps*>9OUI@r9?e&{C zXlSSa0167uTXggfSXdMkoGdKF3|Maj1?A+##Z62!HJzP<6A}svN|El6UK*kQh|jB6 zrd6_%;_4nqhvS?$uc+U?;ULgtP50Nu7N!1fh+Z5Th2iVd0es;U19pnxvtAFmc>`Vp zfVbSMeB}Z{ApslbYW?$vvqMn}LfD~etQJm=Z{|5$n5i9SV6Fd;$yMdL+@W@!lo%P zI}fj4fJ0KX!S->zC%6bcrkPh;r0P;(%P+aK)!GJ1Zp z^Iq>&s}z;o+@!Smq#!+{t$-;d7@e{Hb)^WBX9saU`mJW9gdzAL*J(73mxk%h9};yj zW#_XRk-q8y&Z^;@s9KEYPOFq^b438=YUF;bi`@@xHiIEGU#K(s#kWsNRf-c$mcuE) zdB!&(40`DGLdtFs9>`!HyacYxKlnF4K5n>3Rb~=2I&{#uNlJ(_nEza|k=04FbW9f%@2 z!gaR43arO1-t$QcdW?Yj>TPaJ#C3M8bP}9Rl>AwXIrDV26Rarnx?tF3f*!iimb6Cv zoju8e;FRO*R(E+b#t5SQU}RoDK@WD%^&K^IDA=q7;rb|DL|Q2j_4n(IG=*)=T*qf` zTdqo1zdX7!CBBawW)UbeG|G+*ZE1j@)UTF7XH-f`fMt%rBZ_xOBwcn7Hl2ghx}if?{^1I zD>hSSO6!zY9y!UCPgM|jhQ9Q;%EWgUI25oJq6Ny`3z3yWgO#dz5=;5n#PQfVloWz# z&%s0k|y>tHda*HX?Sm<=g4LFbX@K&5*&YrL^B>V~9cF2pGo?$`;xwj!^qKpk`3;b1Dezp? ziE-^pHKhHx?Z5+>7?x3R;qj-D_6GiBL z$4Zk`oA975mi=;-|AeHW&$xQMw0AMz3!u`@3f-r5v)+M$A{?0 z28`jgRzgZ$`+%c+(scKmcc}dhTt3&B;3+ynW&PX+H?KWFGKEc&>b;En-C`8ta6;B< z{4;I0!ExNvegXLLsMTM*0^({ntoWicvQpcVKZQ zGE8kkbwaa@`qL9s(hI*vA9aE4mX14M^BHJTD9e5NQwu`L*1Qi9rzKG}d&lF-IqE#*yl~t2BPQh= z^m?a$++-3*tZTH%hzZj`xzj}Is;I9TEXx!#h#4x}KDn7SVBxH~KAdRSdKcA+&YHUB zyI9jt?_ooC&MqF&Uq7c7codSgj*Pz7z2!dxD|s_?*;9fu`A=3JHXi+#-$$y-(o z&Kw9a{-Tt=1PH~n zc*y>w%rbw!{#5O=|3J5kDny{Q{&GbdKK+P>b7QuO+I%JW6s=i{Qt^fO7o%I@xaRSn zd|f67XxJx|vezI^dRSY^MVseK@ZZGSDl@2VPc&|BAVC#S;?zUq4##4MR>5Yxk_jx< zr?vrTM*^RlAPUUY6G2JEWDXxi1MAq`NpldsbOv1C$4l1%TTNJV33hPH3NvvMB^TJ@ z(4k*Heqt>*NxC!}alu_LvQ*Bx+p(IOLvB50H%@3G3e8>~5`U)Bfd{5PABzF)-%^t& z?!qpBDqlt7;g<#^$Uc|Gtt9nCpN)~I1+TKMnA9TodIC%BhM296kJUs{OS*$z{s7}q zYP#>6a%sUY2qvE2nWVos)h2jY27UM0I@=}Msr8hLXb3YHCmp!0V~Pg-{vHUMm+Q~L zlfoVnY!x)M$`|Hb6Voesqu8_K(&v5{UFOP-#PH4#=;RP3VWF5DnID_MH9ai`2wYcR?LgQ&lg{Susfi+%L#isSb}eW##1FlQ+)jWgOVG3=26G+ubO$^D8J zyLObtCySftF8bu@@^}ny?w4_Rzc;i&g_s4!1l5c`-Ea*X=lT4Z@mD>YPY61aR!O3` zXF>ahQ}fq0K&urdmqvMh5#alyqweHe@Y|ShyhK~?+;mkdy53~@vuqcASQecz>gz$b zDhs4OC*luS2?u6v9U%*QM#=lMa$}4rIR;t86u5mdUwRm`nd1T#cftT-+aJ99Z8DOT z#J*VfwxguSjsL@EGVr=603#uJF;Jrny8=4?%_~rT0<);QWivCPkXW~kHV{h0gjOhE z@*$a45b+iP|CV87#5JYE8b^fOl;q+Zqs!$>PbPd%2Pp+VEtQL2_pa8zm9vu6Sh;Lt z3@Jsg&OSAJRmObe%H=9>3GZZMd;Al}eQxpekG?{QBA*9iU-2PC^sM1xAKdqQBmKXNh@GPD9wbAGPJV9LZWeeUiHmXxz)>gk&JYh$eB2VqcAi16P))(m}1@XXhn zq?dvX6`M@dnyj6UWqz$@#4_9t_J_|MGQ^IQc2$h3chR|JsMycG=~ zsHzS5zalhLiZR3&e#F}Lea~+Ms;D#Mj1XqiXnHk}hyGPt|BA`EmvZMlF17cUN3AGA ze{U~JE7m&v#kdH~?VMi)UCw>)vQ{<1@;EB2Q5NPOZNYpMkVytuC(RAVQwXcR6|wq@ zs=(9jFor~wGyC z$ej!)(-dK)u~t39_auA?RaQ<0&zBo%@b^5LxkXjyWX;X#{gH$(kI7h0ufxzYxiYcj z?6p5*Rll8Yto?Q9RXI&=R|0BOSUBgcNRGsubDDdq!DD(fmLZX=o|n*vN6hb)UGP^J zFTaEYG;H(?Kh}87QX)bb{3iA{ftD)g<#pBG+`}&m<+(qtt3jyo*a;<6BLnPLgjCZg zUusVro)Lx@?}?xA}_Hh{9UqVTZmWQ{*aW-{vm*Q*)dErkq4Np z+8$HOH)hoeo-&)z_yW@XOj}N-ktd*mZp5&LXSJA_X@zQ@q?*yazKp1JA*khH{zkFh zc5D5^WT%cCECkl%F*g%&k2Xl!wSh=T#QgiM1~K$2Oj{Nizl_$%jAYrZAj&_pzm^nT zj%AZ|o|xb#p5<%lJIc+0-(J3x3V?IqY?n*`96qwUD=>A0Xz`8502eo5u%F2n&Qg}w zL#zLC+Tza4w|5olYkgc9{BM&L=~hY~Yi(GL9Y4r)l>AxhvM zGU)zQz zg~{sQo>2@upUG&+ETUKa9b8RsSJg|IKj{9TU<@?AVwx*^qsZRYB)PG3uzlYTQ2rG1 z+Pa>VTK-ft_Dz~sM2K$A^8+it=?7vpVHx7PN1Kk6Cxe@F8OC_FkKr~AJpl9w-)~nUxYl{&KL$!ePVU=7%V>z{oK$LA*UkxOTZuc42`iB>A{r$zuDLl%Q$mqA(A7^bOMS(Er`k7^; zz(W0cc%^=o{@Jm7hZ#{4PQv-^E-^XzE{+5@yWvIG1m%KMV36OpR|$|d4C49?>eJw# zpX~p=hUi`YtYKf~Yq2SIJ^(W&hw&?X_iXW^geF<`LW5tecTq!PFCF{^FS-ob{Bp8p zv>)Wonr;6qC!ODuI7$2+C6^nt)ia2bZr%$w(?$hhXaOQLjvF@nR2L17H_jSpU7HP#Rx6QOAstV&MGN>MVRlWR1qsE9+saB0_KsziI;t zHz41*uXvZjg^_y@>odmZtq{Gq9U*@utiaui_2hiq7{P+YV_)o%aZ zGxmENo|r75)K5rdNT3*=xh8S1*KT+f&Gr|B=teX6oaYz~0)}N|4d|6;F#fDpV2IZv zJIMi#ANmuN?Ulnh?<2#=46BqE#|vc`4Cf7SPy-(dc);e|?jkXFlW~OWpZrAmQ%V2D z%PqWpAkD_HI-K^Tfv#iNc+Lede0UuP;BG9}zt@^G7-}9YPO!f8YF3T6^_KfxQd);5 zB0E0%EZLQqBX?wCcP~h;l>|*mG5EIyh5TW@e0?Sfnqv_r3^$ekp=RdGM`EP?JlWAP z5HiU)AamW@8xCCWQ@SQ53i(0ZRG{v+)y0&MWts9-1)Di81iy9xaK>X_dh5wV6K{QB zY;7$hlbPbr_9tDRtaicfS`xH0(xPg8Nz?GpOsin4kA08@aUBlS#fSV>EqsSHB1m+6 zh7GZ0r0Kgx0-TxhugjLq{ORSg{KVP*gIdI1hX(ose$Q7($+FA4nT?E^GhE>zrv&;? zzbH9=4<+oLHIlRUOp)Ot?AuD(46YT?c9~LRmcWVrt!JOq|F_{NoVfUMG0u{2;9C#? zse3pg;bvi-=}-1&8T}vqIrhQu+qg%!JU)FaW4FAK9=YB z#QK9`Bbu@=(sh``X?eW=hh+olArvAm#z2}J9 z-N^N)yi|P1T=vLpPf1B%V*{oaPyOH4#A4}wF0#g)e+v!UZ4*EEy}o&H@H@MlX93HQ z42HL{wM&Or1`xpAq4C=<(ySZzQ)(WNrO8jUasDp}B4ucw%NQ%tAHf)wZjw^kp@)xC(^FlUh_b7-*}R(t~*jR<>e=X3%b^csZgNLR#3VF1(H|6!+w*$ zh@B>FxQLtUdFSHpZlwg0!vvb8unS|-_G;a)<<<07)2c_^KygM;f`|1?VH~%Hfs~w4 zH4iYGT;#CN>y3Ac|AwU>rsoG!jBOhHJ@k-Kd6)Y>pN6bktu=0hz;Ze6=e6=$GZAPN zDzs%qbxVB)kN39x*YWm60W?@({u@oMRn4eU^U)B|!p1W`yi$e1O}ObnWh8`wt5r0!4RSwJUvzQsBCk-m%#Rm2ULLZ`y&H=*Dl5 z$)v@`9>$+=a{RLk3HIy;?`B$j2;F+PM?*7|0I%qBaRND8+VpWUefJ?7t(c$-u$SO* zF7@}&hYxE+H*@in@Zi&5zcWeM2%3TOMG7w!OzjkphH9wlG=&#IL_}8JXA~3-!Za^A z(wGDN>k{{TO{&XHHT5=A49Zh{NE8j!N9x@d#=N?$Xb)$CGj&E23GL9n^=q9N59haM z@xF!YBq`?Viwn0=pzemZefQAa+AVXx2xIl8?3SExU?V;Rg%pae` z#6q$iD%m{cPOI>w5#h6uksi!CZ0?v+6Y=BoGZDug>uTW8Zq63M_l^1Zkd!F^H%FV6 zh}E%2iZSY}CN}rG`>ej!oTQ*t5TZ57f(CjuP)z8WZ&D1s(m(5JJuM%)QC+;w;uIfh z5*)+By0$L@t}~Hb=z{W(J>)hSMcK3_`1AB=P;*wSI-&>O?J>M$%%U0L=iS|mz*4uZ zTK~FDrj-jGy|DvvW(Thh0b>I3%(;C06dlk_Nlo z!ik^lq_h{GoK;SIU>gZVrNgSkQ|^ysmz9KQx*1wI&>nS&b&55`)ijD5Cfe>~si`yJ zH(!|XeoD+;IsVsBTIK;VSwt@{$d}`K(0|P10vCL6M+=ScuTr%N&3rcykYxjv^Ah1r z5}raJ2ZaLlyx^E@*pMN+qgtJo8ZB-~$;6d33gT6c!?h0{OkICk0iHqbwk?LgFs+F+Q z%Y+S83M}A5`og9ud{&IlHq|s>-Enazx>RH$x7H$5B-oReYQCu$87T*6{i+3=qNG0& zmL~dd3V}Ckzr=MB>+>HBL`fyG6K5daDdnctAUn9{w4z?LsOK!R;Oa++a(Od+9k0Zb zOKb}5wI<|x8h7~XW3Wi~_%5!Z1Ul4LeRL$vldC~Zn3d(;});|*mi>L#t9@OO-fBLyImpxdNhp{4GckSXkq zgD$nEccSU+)%_5JQ&`Mh8-Q2fr?~dGIkLJmG%MjIe2%zo4g6) zzHXhYGIZ_004b92ima`J*r<*e*R8Vt5Q`u&^3~Uw;a9g-CmC}Bcc3!0V}Q+UZNHkl z=B2Qzb=DI&rR9J5DJ?o{jvAP&N<(aFm6ZAeuDfTo8bA)EevixHw!627IpqFj9Gn ziAby@4$nce*^UIR;t5ILM-j!HwM2QGAL&oB_dky;-#%6SN%wp>>GvMEQna=0PtQ@V zA~TmfAh!9NV}j3mNN-RiU|u$tA~~%@xU^y9gEmaJ|wFQ~h)LYrjgK*ZYJ@OiaQ0}l$y zidx6ex}#Ufd^$Wg02PC7gfv9?Eq2E0DYJliv&DPd3cpOV~S z+5n_3`CAnH0KR+JwxfX@s&S`PM(oTHE9Lp%Wf1RE`dU5&8~Gx;-4|23ve30V@3iTc z0R=F3lrnKd0##1osfwiOztOR^a2>0Kd-$HqF8^Lw1NETeYC4v>OA^Fyy7f??Mo?G9 z)T_;zRL6BmsfIqTKMhWgTi>m0@kTRaM_@!zNupa|6YAs6jS^O<4o~LFqF)k_Bkh_# z*@?M^`-^S%ojs3yaZY*|8Rkna2^y^h7C3DY1a?hCS>yMK&>)& zRrCSW9Rdh@Mha5`>Imb_XW}v)1+VojCf^TsMA0jloQdghAxl`N5;}xa-TRFQ4e!+jC!-YwB>7Bnlo4WOm2o>I2G=g%b zdgEF805~37r+>eFPjx|k*e24@h~BYcNzr$t+QvXbij4wNh%ZNg#$Ks{uBF7V)@BZ( z&Qv5_s!JP{wq%?O;N?c~sPWpV-?U2Uro2Cgo?lpc2#%WRYm2^Fr z6wsg2(J=_0!bC_W_6d*r$_8G|Q4>4^6|d zpqR8({@U6azSLA*@o)4(drctFk{2mPCt^g!UluC!G^d@o$`88~W}X4>zdqYk=s-m8 zvmrws4ODi9;}V&)I^xuJ_}zv6GH)~Oek^#7IFod~>w(4e`2Ja^qxYn76P8_ylB0jo zWM!ehxaG&l&R)zZiXR_+WzSuwJe=<(r+xtk#$gFhey62oh)ol+SkTl@_ygAKQOXPlaPsj!&ywGc~!`o@{5-tps3&Xl^fcd&KAd)OUH zYyJA+NlRVqMqDp@0VWh3D1EKKmpZS_rQQ_>v#JY*`$dl#(`x^s!ccz)!nyx>uIC7) z7)W*>fDN1C;htki9C?swuwG@c_FKA#pX$a3_FK}W?}03w`Q3Hc`r@k1LDj5g2LAqz zWrS#HxCuBKXM_YhkP_1D^k6Kv6mz{Mq%RLkX%Z6O5n3v=?3ayjC=i;E-hz#QE1R71 zRK(8<7doJm z>5rfm=VUl=$l*8_cY6yL;f8Ef=v{yDDvH)bxXcb0J+dvVW?*PlX`5mPnG%utxeGK% z`(5#F;)XsgFH4qk*vQ@qNWT@5KuqHa_MPO61!)r$rsY1~5Uf@;I^^*}hEkGN^hjU~ zUKw*=tKYICar;Vj8X8iq z_4@Qsqo=B$qR(sT7_%$J$bcxha@Y6nfhO3z>+k2MJR$^80%jU{@ZQ5*;bZh#6NW&o zZ$c*4F5l^M7UL7o_p*n70yAq5+T*Tk^3FZE=qqe3=ZA7Xo7dodTW*&q#e~{(BSAz^ zJih}^Q@zzMQWr-3clJIk1zrYq>c&8+Zbti&j^8{B0oq?>meZB&+@gYtJqV5;bH6d~ zxt6D}W%7;@ZQGxr)8~9j{6g$T^%@$yd}RQF>Tuovm{`8L@^_c6#Y>8xM_lBu@)LV@ z4Cg!r!k(d2B%o0YKMNDHB^LrnM(XNCH$l(O43-Bz)s&qh70?*HBy<;>klqaCWuor# zI%7e}*SK65rDH}OHM{96TLMR<*28Lr1-C=G;$0KPe@Y*HJ8oRnQWqa6%pv?=55~^u z&R+vX&)<2Tf4>%1O#!+SLR>K@A<80a$h86xx&Pc6PttEQQ5PJZ<^M@HQZV&P>^m2{ z^~)lIzE%kHFcxdBsfqwCnI5D<-g0qxtu}0pLRMd>vdghVaLEkJiiYo|qdwqRva~3T5U3{qtC#lxMar5KT zp?-*IzTf&NP%?#!--K?mp643f04vTE{XSDis_Im($t3s_P$efPTY|TcGJ}Zz?r&?k z);iQVadWT8+Asd!`$IPJYxkPU#--}D{Mx*=InTr)bF*iO)?1F!<(WzEF*ok;*51Qr zbn7?JUjf9=JkM+<+(}nla|%7mF3`KyBt=5rdnx}D`h?9r_p^Hcw4@++(7-q5>f-5G z;A>_ML5UP>J?k_&NO2{*nE0$(Wxhg(vKmyT#4lF?0 zr*?31SA5~WmGctHd&f<4^crZ%J0}KOP#N>oyYSAr3GT`RH=pt4T)u@O&2C#RQhLai z+_`Cm9{~j=Y?JIg4(cPDv@msuBxz(Uux~<8ZZrQ&0d6cvb}9y_JLFk`gBvd74h>DZ z7!j^@__68ISPh5^L~ zceROL&W^sr{_Pb{pxcMfHduYu7x#3;I71V7{PA=piT-s@ou?OK<$T0u8dMjs+*8fX zi-ZVuab`@BM^=8Vd0MmW%!*8p_4l96C7XJrizxh7Q&Jc1LmJ-OuUovFQ$DTat_{+W z&y-$kN3Z9d_t0-FyHZbMy#nC^Qmy zHFPC-9q#Uw@9`Qu{!(osA<6Pv{h*%{YVU4%3@HcM)`6-kE|LWko{!0FBEBkewLZ~m zc_f{u#Q>jeW8iETsUvrL4&#san~hcx_hO*%vb41{ZiJ-Wm#;ot>nW4A`iKMDq4Pz^ z4{p89hXm!sd(Pvr%D0`jIy+htSlMb&fTuGtkJe^B^20V|?wq)0G^l>5@AB1g*@qFp zvh3QqP|@DV?iDm?KIQwrK{b##g48Hn-2T*mLof*y+=22eP?MoHNx7}*!ZW;es^bG} zdNKe3*V0)j{q}$ks!Mpct9Ri-Kz^?(%<;_w;HZ4WQH$y>!B4jT;;Z{uRva zBJSwDqAl;eSbq6R^2(}(o|W?-+EdQGBkoyMD%IB08Paq`%9U6{xUl1|4+@0x^eEPL zg#N0GOIhFi{Xn^ku9U?`Pd$O;$G#CX+ON{3J%cAL)~%|RK{!8!jy+!vHDvO}q9I;T z$7CY_giHw#YcVm2KEb{2=FN5Rtzrxoj>$%*1W5> zrY#iC%9m9di}sc2+rw~hsvYa=5i<=BA65uqUbV`DY^Li^5{X_fXdX__a!WM|o|LMX zEY*k0IS&FaVaF0s`vq{xG+Ay;OA-OZvJK7l3}grZrit>lKZKvBrNePgQ}2ae3euBZ z#uT=KYv*CkB=|9E7g1ILz99c1AZ0n~h@6 zz0dUJ+)oZHr>UWmzlV^+sFhbSbd}-fk^vU0;h*-1_MW2F!{=8m)>2)?eh@x2UTgsJ zI6Vp~m3H>7iuNWnkNInTFu8`7b8$}}!S&ojUdCH{)Tfehm?9!+$Yq3&LpjmGX=zu>SG|?Pf^0QLKQ> z3`9wEnWH4@frxo2KYd2BBK94tQWhwwHNwG&Kb>j<+(%DFu^<$Qtl@O{+X-T)N_gIw z=V@x3-r23nh*MxKzwglfL*Pyj`mzX!*kJpHI5Vq+5z-Qd2scwTe##u7)ySoT*o!ZZ zzPFI^$M#%O{Ua9z(KC48cMwyc7Yoe4J0q-;hyG>$y(xPpHzodJDeQ|ID)+82i5(Hn*4NN*#F{<&M*h#Z-T!o43k9{sf{?2#sq~cMp)G`Kv=yl2MuyyF6j>JlHne>_TaXD!N0nC3#2V9> zBzP?p{%x0GIPs;BVdsL>Ty@_PbRTJ)ZPK^%8#I@n>@)@0SnZ;pXiJsf(fXR9+%gM< zf6MqAu6N>|w1t*{6vCaZi?nLW9J&QXc|E)6%CO>(V`)E9SFS~j2f zr#lih)+CT!_E4%y`Mk<=SI2{}bqys5b1)E0 zMD&*laiJcL`7nnt2xwUb4HNI3LAw`cnlHXFn+T>jzwYi6|K3=26-*Sugp*Eho-uiN z&gAti)9K`k(PFAXOT@0j;p`42ifUV!YUk-+)ZQ-%i2#K(xLK+y0E!xSur|29-8J7~ zpYQzu_F)m;Bs$VR>oRG9# zE!R=l$E5dVjm83-e_^VP_<$-aDzFs-zBAoaO@{(^r@AknXSEi+bavlyrHz%(n%M4x zt2GXp@-kW|P1!{JZ|pvpaxm0gFa+bmUuG?yOiqN>c4B4r45UI`W55R0k@HO9)j0Qd zdJr-9*7f#Y01gZs6_80~FmG60u}uWkt+VGjY`EPG^Ow#@y!zC2vLm`w6cRPpGTVa+ z!w?0OdJwNUxtCd?SGAUhsdOeH;4{ zsqvsaKX!ZR->3ZRL?~wE1>xP=ujKM0fQBlkS0TD*`D+LFL^u`!*--Zu4sQ$uPS(j# zug=THL6E%1JhgYL?hYQ)v3Zb~<6bx(osj&XSibM6U!phuMT}E4)IH3h$Pr*0#_SsK zUUmEDh*~9(1pVeKuRm-g$XO=^aF%T8qr&-x7d!=dd4(dV&sEGYY=rN5OZCwcQ=>1g znLAhQ=SNOaL2g4FNNW^J&?O)W;H7Am>n6_rzT9GAD^~EyCbL$8I0o)dGSO!Zk(7o> zX6N>-Vd#4axgOJUj_gpi?FIOdDj z>2SChC0))&em%a2Y6%A<7VdxlNgV|=hj(UL5(xAokA>fz(Oq0=tnXcMVuogj5H@|( z2;U!8bD08ZUx1Pi{!N?)iB-;7+T(&q5**b>;&i;B$B&756`mE^3K3FYxm-wb%)Pfj zXA3t+oS?{Gx`Ub;8pY-ZK`{$(oBgA%wf@Xad;7rof_G+CBv3z=iz}4PzD4_-lY+Mz za;f!7ph$(3;$l5b#Cwc9rmK_B5VU*;$ib20H#T&fSa`#!tGk~19EddjTNw3Svqv{5 zl3TIo&+1nk(Q+MO8O?>bubMw?u|=M)meRm!{FM5#Qd3#1I5hNNri*Ws9;z%m`JXg~ zCvNIUW%n_^ef|10R5zLJ%k_qq6;WW-^U|obJKAn*%uCs zAoe8aG=gyfAn~`@v7j;Q(sx-BVZ3Wr%Tx19OO(O4YH#~BAx=JIJ=en1mn??QG&O?a z2w;jzamQ+46k`OCpso7i=MIyTPnJgvkn?bv%!~obh_H$U;p56VKR4P;ovNqq=rfX4 z3#Shg#SKlq&avYoGrpWZfoWYwmHlZ9t-2OQMHb4^EW7@?IF5M3ZvU1lONSGxMbN2y z>L;Ddgk|FhsOqDsS8Js-cJPYL+Zj9qB0J3#^g=Uv9nbGu-NDr~S## zOzE?W?|Zw~jqJpflp-P`>DEA>nk!a6?P-UQ(Ad*ZO*ED5&&knP&A!(@o^t#j=&gi3 z+;GZ-POAtET2zYKuC$<e3K@Na8-E3m6vt?(}b|CkzsHbNu9}D3wh9sV#1FVW%B1)EkZ^usPB! z;$f<&hSDpu$<$OXec2U9qJj^}WZwwyUU?`wvcMw-oUu!Esz&wz+=P$HI78+4Sat1nnzLZo%@NNa< zb$Hn3cmatz@z$rvY65&}X{M+jSD3+VB*;;U3PI$tpCkp}D(wocz4|CMknY|ar(Yt@ zbY-ud>Pb-QZoW+V<$S`uC(8Y*RalL?UeBIks&CN}9k#kb;Zw3fbpSj0h1r~LSy{L? zv0O_QN_43iRbNk!|6gC1!f_Q$+t*Y@@Z4dg&?f+u^ zVL1OvLXSk2Y<5j;FdQsWJ^LR0z2StpduuC%4Mb=sGTzV({YGK}o;Zjh%13XpzEo0>?jF=X^UTLyaJV8v54PYNTuW}Gv6 ztspsnw1D2K@4UPe;^(u}Mm(4lH+qr4*1NEC4fp#yLo1nSzEOX^dpZAHR3f<`Z=*th zhgS*t{E<%B=eGUEh9W8x`HysXtY4$ep0vD!P!go~mdAE`63ol1A1{FAOExVy4WCu} z!eKhP?nTCZ%X5jVN76gvvaccFJ?NnFMT}4H&Z~O%NgQVMt7tiUiRUe4&LVqZtAXzw zkL~ZeLjl-ZcD*JiMKNjozbU@^1!A5Ip*lDvKexQsz{{FvPG0gMakrQHzAf_ZZi>yc zymyz$%q{!pXP;AgI+1ZV*^B9O&L`nB;{m&jRqq8S-`Cu`)@#CEt!V;;Ix=hEv#W9U zcRgVijN(f3E%Vjv;@WV4pjQC0`#wT!8l6A zCxz+J)BLcB8>PjhKpo?^#Z&TSL*2G37twzca3w#tXK%?e3Qkg9t^nEWbM+-d$%b|Az}Wty!bXQwU)Mc$nGara#y$!&V_~Ige6YPzN%~*8A)DD zooOQkujnj)4;llNn-|?tjs)Kl^q#)Os^%bQA){&^Ne;v4u{xtX+Y4~poA09-fl`EJ@h9C#+KsIk26}Ag1 zib4InDYza-Us-_Bustl6*1Q0bPy2JQ@NC8<&l>jGwa0lT4x1c(tLHN|)%@B7fLr6% zwVaVUVzx1V$XBNs0(tqy+SgDVpICBl<5gu?MQzky_@$tFBZNPUl+Upe*T2AEu-Dc1!KemHpM*9LIrJspU?f*2Fo=nH z!8wMR;nt&Y)U$qp>v!Z-n7a^Fq&-l2&Yf<0vc+ZNkzs%JI7fZIRz6(E=@M+r=3#RdXB zQQoZ_6Ah3PK-+m-5MqLK$bGS(CcpU(eVlN6 zNMz%U(`G`d7Yd{LxPyT{Tc8F{$0YnqYbEdm^sgCCN6W?M z;sV-L9~cGa%Acw)>jfoUPcT>6)y*W`1hxBR3V$`1Z0;pcX8^c6cO_rVL#@Hr?SWS* zyY98J@FGI+Ee?q{QUQs2k#@1w}sR6 zd5oSRy_TlKkWGf$rpzy9Z}N$s^*Rtxgb&RD+{ee7{KPcO-QZ|1c}2Akp+v@vF`>(> z#f1~?ZZ)s6z`iPD`B&T=CUfm1^1A2YD<7_k#o>e_LisdYy)S|4HGi7cP z_vw&AAN{$Fo{hk(y;=TmJ{7qq17K^XY&avz*%y2%{xQaKY1}D}rv1k}xN>@NP#xnh{~^ z`tw^!A1__@QM9A)_0nJ%9m!&e`1_7!+NaNIZSl z5k|=D8#pyKdQl-4bgDoaITktmq3eehl+qYH7ah0VNOgXzt(|ZmxoaxC1Q6Nw z@HX6-HtpCT2NQ%QdlFE@87gFGORxsp*Bf4dTh?P2GPR#2VdnApzA#*DlNtlyRDM~N zCm#JG7DOLXHUzrIm~{eeGrw8i`9r{+MKuK=jcbYTKWm|ftbg}3JCllj9oR4dsAFkZ z62ulD(~f3tX?w90Pal^$*B7pd4*V2!%@i3AN?yUBrJMU3Jo{}+X8OL3{s$+-IlWO! z1%M=XosBznoLY7ekjB5eOjR&2RL{o}Gi~R>_GV%w`Ys3U4&4AcGQPl7_K~V(mkb1X z4uh*ekfFOjXOH)OVjq#~&p5|n2$G@GgaZi|#f#|p4i|7ANn>a*hGW+TIo1**BPx>7 zmQj$2Ta^`hD(NFdrZv{FMcN#=d7;=n&`~_rvA$Nk&v6XQH6_yT} z64f+nxJO_4#G)=21L+}Z^E%PK@4c_83W=-+&uARO9C~5?TE(DzK91GyzB6L%yb8r` zj^OJM1FZ2k&lX<93i3N@u42Wo$gwvs`5aSbJX_S{VC%^~9o{3$r!fU^YT9kTnrMENEB!*tv;TyIR^F|ziabBv zr?KTCN{*zEk0Xrwa%*WKxj}-{52B~s6wb>2>JDj4?tIB+bs8y1LYr7Wnc&lPHZS+c z&;MOFe?_O$XZ8F3iZn~=!Iy;~dgZp!C-GvWav2^HzIKJHXPI92=Zlv^yDk#rf-Mk( zz;IVoT8VLrO}khpticP5?z{{<@xQmZV|psQPZDpkPs16pvb>cg--VtfIbl83{;d0E z=Ph5Q--O{!|0U?;6jlaqX`vwyq^AwX2tz69>Q2qXe@x+yTL8|Wl;@Q#@z)nt(LzS( zD^F!2G#LnD3GsD}^gl2*_%h@wMF4Jfs*p^q{@mu!WHGVxg=8I8(pF|Y84NOmZu#cy zOo($SFLflDa-TLXP-PU41484q&KBtqJNXMZ?psrSz0WU{%LT=wO#a8X zSto|bTiAn^=tus3n5J^j_^-3f?X3^>9vT{kb;gCDTQqK1p>Z>Of*R@_%oX8ij;&q729G>9cmgTyH99GuB= zjPgJi-9AmbDp{C|SOXL>at5t(1#|vK4j3__$C0uePUC=ZMurc?1}Uj1ARRSHe&N!; z{{rQ3*DLRPZX5$#{Ct)Fq6J^O)0#<)H>b|7YKqeya7Xm5;V7(K%Q@+NBg>T-58v1W z6y&X$2yK|W3!fOZZ9yg|(d3zCf}*6~{l?EqS#1ssEqS2;ymB|&xmPWEJkyP@7KUi5 zX_Sw^ZfS(R89Dpqt+jR8>SZkxbZRK^jd<3UU`v!4l?q6DES%lbd2cKBU)G_-{z*{{(TOYoJOn~A3!SCi#f%aXNp58m|0mzjUvfPD9j{G|V2 zZeiJRftf)>_O8EL7T+$y?0wmS?;p2K9*g8(o!r*eTV8d1TNztEFB#iC{=EEs)G4Co}6)Ze$Z=B~)UHdVj{~<_N2^ioh z->agaCz(YmpT#jj^6*Hh;=URn)w%r^WF~!{B+0zbjL~$+X9N6^4THbsigrFc$olg5 zJm*DN6zto3kWkEbsD=l_4`BPX){rKF166;_d5Eg5-y8%@qIFjz9a=v$t3g8WDa^QE zS8dPORMjN#`8MkgpC=t%DPT_W<== zxe;mFkA6^7ZTZ1waHjkU`Pz$L38b)f?e|fnPv`_!t3GQq<8wJwJmeM)*eEZbZ*UH7 znQ%Tl>AxNbUVnRC&Bl8?ElbkqAH5TzJu=r<#vIf=j@Eyp*>*GSL=zWT%D6^XlV4zu`)AqD7 zbAHT^hj!S|)>lCFK%OY&iA&tw*$w6)5P_y+{N>=5bCbO+$#FUDlHtd4=#wtpL-~)5 z?53?P?FsjCj4jo_lOe13@cjb8_Ui)uzmgv7CdK%z;Up zOLK(12NYF-T5RXgd=67E~yl7yPqZF4QWz$C`atWWhf~&$s|)($E=_|xwZIGqz9{h>V8#o z0nf9yit+R3n+dh4F#)N7rX8abc))}ar|K3zrh5S{*6XmW)B-0od^P+Xu@5RBy+~$^G>3KwT~*I zg3?8Awa22r@6mX4C%b;OSF8vhJU1wMUm-ba30(Bg56VbR7u(qrQUo=HQ-9$$si;M> zApex|A(BDgDVv%F?&}eTga|Opk(3g75|2~ z0`1EYo@8IrLd{Z!xFxYVWYyk(s|_8xjNM<=OLj#Kth3e_bR&&H^^<5ohsR zE!;}W26xEeEQLXLmG(TpmJ7{Ds}0F^IhP-lc`n_Z_1-)oyWE5p)K*5fAKoR}!4pdD zQmAB%38xc^-dw2qokNN~`EaxbpZDCJJj@G)&7_NsObs;z*58Bk4Z>6vDUCM!?lpZ` z6YUO?)~#>IZeedd=ypF%!f9#)nl!LNiehtK)h7mP%0hrzg)=h7V96y5 z;9}z%l@tFA7lOEUXW(Or<+xQj)#1^TCgYa1dz$>uc$=d$RmIz??<}3AiYpLvM@IzH zBp!LmVd?KsQj#k+yndoI1B0+Vj}6FH&~;XBzXo89CEN#w!V!FWAm!*~A2YPOO`)a6 zK)*SF}xrQRB2_S>>A}Y%aSVh-Kb$r)D4JZZl;h>DQ3JQMg zK{XuTtz)7t^gz$|aB#w2$VEuf9zB^(FgMLY7@0MNA~ElLvk-SjxbQcnfMx&dkt9K0 zLnVtUH>)k~DVplv4)~aFrW436K%HG}2q7*^o^PB=HztQa0|sS7iVy2@iLMs|Ed`Rm zkCt!zxup?w&)#+#t#{g=G9f3??K>dVe<7X$P;LTFL6w2tGR0=J)bEKt7~j6WY<&?F z1u|&MA)Xyhy5l0$w!0D}2rR_HIvzl9>l&!W^S-yR-2Od^+^c_zZcDGOsw-HVKbN3~ znxVvwh$D3e;+W`brBHqh&O^*!zq%dBPRjOg9X9B6zbqG-Zd6b*;fc=XoAPPev$I-W z6*4>#?s0lyW$-bo%Q>|n?R(yW@YBPnbChg#G%D|d*nnzYD0Zlqo-{O{ezNGd#^Z7bO^Al@k! z#Oe~Oo$jtFlUuWp?KN0(*V#y?n3WOuyTM;GW zlX0QVF=Weu7`?rb_^+gycpaM{GpP4=`|RZxfW%*5@hZ_*(?>b-b7B3ljU|9)O5xcp zA(RPUcDYZ1pdCziUON*~&HuD0_n?VCg7E(f-OB#I5Yk1s+unJfSb>xnYzAZz9v&VO zS+1jU>e&$zJD3zBq1omW$BvXwoLi2obAQM0&O6BH`^H2Z=e#O+m?Xi+ly9oeDJb<_ zdW$mG7jveS@>&?}A`Ew%2Bf+5&!$^VM;mkBP#43xK2*wEs$Y%Z9H(JZM8+Fp`Cef9x4K@e&<s>`)FC*&S*DkP~0VB%k1XCs@s{Jxt#x8u9H4cmr_(RQC9vvCNhfHj? z4$7dM2^V>{%c}nCybg!^h~kCXq^7~G=>lddVJ-h&j7t`j|IPcELiVM9Re|@q?JnnCP#I|9=rUd5?oD>B&CP`=E0OEO;MdpgzDl9* zew^L82SL(=y`DH&UA86-YT?oyi1DBgvTCnRO)2}7vFvK5E4la)1O?BDZ`rcsPoDq#z$XwJZLps!ySU^( z6AHdN;s|FhBO`Z|mwxXLsDeVle#%_2CiO4#hU|X+K|SwyzHgWQ*?RYRq0Ku1*q&Xk z=kko}$E>bvO~IA(JZsg>KNQSF&@9ovn=r!ec>I}xqIqn5zOu9z?seaBuPDewOQ+fPh~%nA+C%S z(oSg54($%>|PO_k*KMs zN<4*S2ro40FMO?`=Xc7=`tDXI#Lf;&7u$1&inh18oz-murb+&?u4K%nk~FbtTj#&= zVXMP0e!kJsG-*v59^i^HbWKHm+k*WV-Yf+_7Q8W16X3Jm3t*{A z>bL%@1VRSOT#m6HN|?6RTs`b8da|rs?-1PrG9pbSqHw&%7t@|gC+BvtK*<$c)QOr| zm2LfAOo)B}jg8?AW!+z(2HgC)Eo6^oZ{VyMym7x6|69}kPUHN_R5M>fk8JmNo1tH0 z5|7!1j`}a(SiQf@taNiTBfGix5>OuS%dWd!N*gVRnc-&Sd>f6)DvR~6mkbL6_T z$ve>9HT--OWcKhq`IuFtz!bZhG!rvHm{xHgX8}a&VqbQ-8RNJhxPp0tx1feG;VL`3*VMOYREQ{9@MF$j(PW-M-ZLF)Jma zS$Dln!jS@)NekqFS7p3H(WqG4X=EXgRK7&?Xiz~Pl>8>++kA}s-$h0seif7&MFsxn z9|>++0?dFY3^Yx$H-PiF)b)P=2rquBR^BkjPXG7bfTRBY4xz;HSx{_79-zXiDo+xu za`w6uM#4~+w4V(Sy9uVL00_|!V|G26nT=MmdEL(QxHq&JJs8AF06J~R;B;(fd03ge zY;U=`m@Gbe>#Vm(w||aQ^EW)|g^Fj2Btj3%56e~&0TBD|m{m`PZWlqQ%W{wXVnP&CKg_yTl{9Q^w7dl*|V zJ^O6;GQm?gmkt=MB*croTBlIjjG8KG@UMLq)KsU7WIxMVckowqb+u$F>fh!v@Eq?H zN#p+DB*Rm)b-n2DdW%VgaVqz9uncB8ui~NUC;4s-tLD~JgE4tKBY(WLJEr;-g!A32 z!`#wBSj{7d*=EJPaQPj|R0bj~S<3^y#hj5aoOyMNSBt#8^&0164<>ef7EMK!%vO++ z`m})DU7czM-u27>Cb*w$bcJ)!9-`qUqh#Y|&$R`M{ge&U^2HfxuJwOC%HP&*~U1!#)f8lMgKB(h;WD2%;VJtaF1JuZKdlifj z<$y(_>>?ivvTaKHAZJA$hiKi2=A`3|b8EKGW#-J!1ZgEK(z<QL2yjsQBB?Qd)w6b)~fxweHQs}S0Hhd^DZRqZ24h)^d!$@)L>%bD|fmAck z1_pM{tWDQE+7QgA^%3aD8QEWgB`LMm%dq+nSL3KN&iw zcG=||KJ@DrBL8xa(w@RkZ^0+4Hb&{)c&LZe$6z47G_RbA*uL0`+W|SAh zsEoX#yiCU_nJtf~xk=-iL1!^(s==@9?s!DmY9HcUEHLp|O~^iX40ZyUp{W%Zbgqr* zd5z|W5Z;i-GdNOI^J2*3)-&l~&(lbo<+MR&kreh-E`5!O>A%+`K=Qw4)pnYy{Yp0- z#YhyaZ*R$j_&(K8AJ51L|GvjTBDU8SM>pfyC%3YUJd`y#G6{a}CEl5V#%hmBt7|I& zNXAt}`rwLU)3FrfV$qHoe(g3ue0?QUnDt0MnkvmZS~nOwwK2jJCd8mJRi^3ua#+xz zZBEVS1L;j|B^?S={{!OQ%Vptw!=RUoXVK^@mjyoWL1mnESV@o|a11MrsCpSttwC2DiXYQxgI1E1sSAJT z&};jr;!AHz0eef{mnc?Y48KpL&4&G&_P@P2*eRo1_iAe6h8SwtllStlbsP1c9E~}Mc2Gs`F#S-IO>{S05fi8E@lU20+>r0o$8)2>I1cL{J~e$~07BZc44IDZ z&>g@slCjb0M72RPlZV7Z)IJZGBK(A0I3h)6=eL6KB@gdAW!#Ug0mT?-p^3jL(luE_ z72G4_(9YogvteT0iF-4vY@bJpAvgTC>QW$2N5yudqcEBhSnr-$bo^MD@#UxAUqQ8U zlvnQKIglIn1t3>sC5{{g9){2-0PKBeuPxT(9di9i8rqoqQr}GqY*f0i-ZmCYg z!^x)--hR@~6>+?d8_}C~9j{z2ltobhnkx43eV0T(GayO z^xx`AuPFu4nKQVPgJy=OH3j{yF*n>rpkI|&NsIi4S5Mq8`t#R}V`uI|n_fffyAZYym1X1JWw2V;s-u1*tqJ3A|GJ)- z7#}_MEfG3Uw>ChO<+zOQ;Bk#$^rySlEr(o_Waqn(N1+RdUiUU|O4G@7SvlJOF3*Um zoH{BnFvCT>OTf9qV`Fd4oNbXAIh{DDZnhwYnrP{*5PuKQ)u zsA&0U*+|8F#c~Tfrjf%>Oza$*9vl_PJyC4?_9{mYe_Jg;laB0p)WiGRir$BrHNSC! zKG%H4?uqG;nR((afvoPXO*RtjOR*JNKkVeYro?{1!6!Q&b#yIguXj|FhC<%(xEez zARO8!GZVT%W8P2!9D|I%cQsIrGn;aVZmH%8TUR$h@sWGK5h;!jpScdl3)CBdaoQ*YZbj|b6RXQ`>17iaU>1PU6 zYmmcO4qOY$lUBfKL;Q2zr4TD;@}v64LPL7WmW*>h20CV9zD-tE=_60mP+@I011_c% zaJMsIld0O9mu?ClUV5_u@^u&iN{Ku5O@s-7>R=$y=VJ&-*Xd=oQEiXfsaQG&W{S5P zag)F&|I}YV&Tc7l3vGxL3;(5f89lQ_TX*a$wBRlMPC!nxsbi;{WDTBz?9+vn{zdH; zGF9nse9_`((!;%S#?x=bs+W{Ef2EDoK@OL0Lmn~a1M8o*e}5bC@{Eaf2Us?vKYQ>h z&H5bilu4`f>Y%gRH3~hlKs$+K0v&aHisvc9>?dTivhjYy&(;KWIy>wpn=p)yt7{z5 z`La)*ucTK$`HJ2ZPwR3y&Db?lyvfowq;ZQiG?hA`dhytlr^|Hjh$xt@& zRp>=g6X#CZ*0VY50dd5T-r{2X27Z5N=g{%Wp9+zFxTtxbc-hdlbT-#6*N$0Xv>`b+Cjsse=tnjzOOd&2eGo|)2 z;(ZBU1-ajrmPQFbf(jZbR$K66aPi&bvv{9nrZeAa{IWgbLkGV^Ds<91^0^ObpE2QW z(rxt>e)`Xf!&BY=U=Do9`?+=2^}r)UR$z958(gTkuH$xcQHGhVrM4}(<%G$%xf7MP zmLVkBP+Kxl)rZ_sU=6D}v~FQ1Ber#VIt&CEqI_i~7FSvM#t$N7*$$d_Ik#=pupkBb zf;fkz>&?UTh-sg=agz7UUFn3TR+_U#Og#QuI^Bmz_OGk_%^%%{6U6bncpvT?2xab$ zKbmFjFnAHKCU|x2^f)sCMLWeWCxB2K6Rq@(^SVRe(FP(xeM!b|SQo=*nE}m*!=JId_uv7j9dkl{dSL$V z&JMjHM1%u+vp%dhO&%~nEq$qz^ zct;RbO{tN-<*)t}Pq6!xAsM#Wlrlvvd>*Xod!fZR(n(@>ql5Iv^1CsTc76111`PO9 z*gpCofNd3Mcu9$JnH6fZnZNPk!48NOlGIs?=6%BN$~iI)Hsd+$I$#8gOH=2 zVRGa*02DxR-T2dc^%-iPA~W`E<|lQ-XX(SWUM3N6E#a@7U2x}7<6g}KaJCFKM&Mk# zc`hc*EW_lo$Bv#v98`Rr2YBcjqR_HY>A;bHClV>Dq5&t!k5qG^26d)#egxfl=w6;v zvm6=OsA7w$kP0e)o}+c9dGO+`cl&cDJ_S6HA}&gnk`L&@PZCmgrK-niAQ6^doqPcp zhU0?nR*`X1KZxjY^VbXUTxLplGKiz817d0z#+}1g{R1h$V3| zIKs)k8evptO^7nFHyBQRVYk{qkfGyk{vnkm;tA`xT?eKrB@7n;E(z?C3)D)-PMWMu z)WB1rgi)c|f`VAS2eEeArN`i;m1HazvqdR&CN9+Wq_U;$LiI@P$0Rl2ROdCuqD)WF z!qDjpKRr0&lS#7d8s{=N3--cjv8q@lHdsFGg46_3F0z*A06tGprAPi%A3+r&`R{ZY Z<9Ij!xaqYp>|X^GC3!Wuav9U0{|6)>Q0o8y literal 0 HcmV?d00001 diff --git a/okular/doc/embedded-files-bar.png b/okular/doc/embedded-files-bar.png new file mode 100644 index 0000000000000000000000000000000000000000..be4cb91f928f875de864f6dfb6e40869dc748590 GIT binary patch literal 4657 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 literal 0 HcmV?d00001 diff --git a/okular/doc/enhance-solid.png b/okular/doc/enhance-solid.png new file mode 100644 index 0000000000000000000000000000000000000000..a5dcf8597d24fbfd26485cc29419d9f56c7872fe GIT binary patch literal 6491 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 + + ATEX"> + + KPDF"> + PDF"> + + +]> + + + + The &okular; Handbook + + + Albert + Astals Cid + +

&Albert.Astals.Cid.mail;
+ + + + Pino + Toscano + +
pino@kde.org
+
+
+ + + &FDLNotice; + 2014-02-25 + 0.19 (&kde; 4.13) + + + &okular; is a &kde; universal document viewer based on &kpdf; code. + + + KDE + okular + pdf + ps + postscript + tiff + djvu + dvi + chm + xps + comicbook + fictionbook + mobipocket + plucker + annotation + + + + Introduction + &okular; is a &kde; universal document viewer based on the code of the &kpdf; application. + Although being based on &kpdf; code, &okular; has some unique features such as overview mode, + improved presentation support and annotation support. + + + &okular; supports a lot of different formats like &PDF;, &PostScript;, Tiff, CHM, DjVU, Images (png, jpg, &etc;) + XPS, Open Document (ODT), Fiction Books, Comic Book, Plucker, EPub and Fax. + For all supported formats and their features see + &okular; Document Format Handlers. + + + &okular;s Main Window + + + + + + &okular;s Main Window + + + + + + Basic Usage + + Opening Files + + To view any supported file in &okular;, select FileOpen... + , choose a supported file in the dialog and click Open. + Your file should now be displayed in the main window. + + + + The new document will be opened in a new tab should the Open new files in tabs option on the General configuration page is checked. + + + + If you have already opened files in &okular; before, you can quickly access them by selecting them in + the FileOpen Recent menu. + + &okular; is the default &kde; application for &PDF; and &PostScript; files, launched when you click with the + &LMB; on such a file type in the filemanager. If you want to open any file whose format is supported by &okular; + use Open with...&okular; from context + menu in the filemanager. + + + After having a file opened you probably want to read it and therefore navigate through it. Go to the + next section to learn more about this. + + + + Navigating + This section describes how you can navigate through a document in &okular;. + + There are multiple ways of scrolling the viewing area. One is to use the + Up Arrow and Down Arrow keys. You may also use + the scrollbar, your mousewheel or the Page Up and Page Down + keys. + + + You can also use vim-like navigation keys, namely H to move to the top of the previous page, L to move to the top of the next page, J to move one line down, and K to move one line up. + + + Another way is to hold the &LMB; down at any place on the document while dragging the mouse in the + opposite direction of where you want to move. This procedure only works if the Browse Tool is + enabled, which you can select by choosing Tools + Browse Tool. + + + + When viewing a document in the Presentation mode use Up Arrow and Down Arrow keys to switch between pages or slides. The number and the position of the current slide will be shown in the overlay at the right upper corner of screen. + + + If you want to read a document with several pages use the automatic scrolling feature of &okular;. + Start automatic scrolling with &Shift;Down Arrow or + &Shift;Up Arrow. Then use these keys to increase and + decrease the scrolling speed. You can start or stop automatic scrolling temporarily by pressing the &Shift; key; + pressing any other key deactivates this feature. + + Another way to navigate through a document with several pages is to use the mouse pointer. Drag the page up or down, continue to drag even while reaching the bottom or top of the screen and behold. + Once you cross the border of a page, the mouse cursor appears on top or bottom of the screen again and you can just continue to drag. + + + The navigation panel on the left side of the screen enables two more ways of navigating + through a document: + + + + + If you click on a page thumbnail the viewing area will be brought to + that page. + + + + + If the document has a table of contents, clicking on a table + of contents item will bring the document to the page linked to that + item. + + + + + If the document has bookmarks, enable the Bookmarks view + and click them to go to the associated page. + If bookmarks are not only shown for the current document, you can quickly + switch to bookmarks in all recently opened files. + + + + + If the document has annotations, enable the Reviews view + and click the annotations or select them with the Up Arrow and Down Arrow keys and press Return to go to the associated page. + + + + + Some documents have links. In this case you can click on them and the view will + change to the page it links to. If the link is to a web page or some + other document the default &kde; handler for that format will be invoked. + For example, clicking on a link pointing to http://www.kde.org + will open the web page in the default &kde;'s web browser. + + + + The document internal links work only when Browse Tool is used. + + + + Additionally, you may use the following functionality to quickly move to specific places + in the document: + + + + + You can go to the beginning of the document using + &Ctrl;Home or + using + + Go + Beginning of the document + . + + + + + You can go to the end of the document using + &Ctrl;End or + using + + Go + End of the document + . + + + + + You can go forward in the document using Space or Page Down. + To go to the next page of the document use the Next Page Toolbar + button or + + Go + Next Page + in the menubar. + + + + + You can go back in the document using &Backspace; or Page Up. + To go to the previous page of the document use Previous Page Toolbar + button or + + Go + Previous Page + in the menubar. + + + + + You can go back to the positions in the document where you came from in a chronological order. + Consider ⪚ reading the phrase As shown in [15], …, and you want + to know quickly lookup reference [15]. So you click on it, and &okular; will jump to the list + of references. Using &Alt;&Shift;Left or + + Go + Back + in the menubar will bring you back to exactly the position where you came from. + + + + + You can go forward in the document after the jumping back as described above using + &Alt;&Shift;Right or + + Go + Forward + in the menubar. + + + + + You can go to the next match when searching using + F3 or &Enter; (when the focus is on Find text field) keys or + + Edit + Find Next + + menu item or move back to the previous match using + &Shift;F3 or &Shift;&Enter; (when the focus is on Find text field) keys or + + Edit + Find Previous + + menu item. + + + + + + Presentation Mode + + The Presentation mode represents another way to view documents in &okular;. It can be + enabled in + ViewPresentation. + It shows the document on a page per page basis. The pages are shown with + zoom to page, that means all the page is visible. + + + + &PDF; documents can even specify that they are always opened in presentation mode. + + + + When in presentation mode, you have an helper bar located on the top of the screen. Just move + the mouse cursor to the top of the screen to make it appear. + + + &okular; in Presentation Mode + + + + + + &okular; in Presentation Mode + + + + + To navigate between + pages you may use the &LMB; (next page) and the &RMB; (previous page), the mouse + wheel, the arrow icons or the line edit in the top bar, + or the keys specified in the Navigating + section. + + + Use Play/Pause button in the top bar to start playing presentation or pause it, correspondingly. + + + You can exit presentation mode at any time by pressing the &Esc; key or clicking + the Quit icon in the top bar. + + + You can also draw on the current page with a pencil. Click on the + Toggle Drawing Mode icon in the top bar to enable or disable the possibility + to draw in the presentation mode. The drawings are cleared automatically when leaving the presentation + mode. You can also click on the Erase Drawings icon to remove the + drawings in the current page. + + + The presentation mode has support for more than one screen in a multi-monitor configuration. + With more than one screen a new button will appear in the top bar, with the icon of a screen: + this is a drop down box that allows you to move the presentation to any of the other available screens. + + + Presentation mode has some configuration options, you can find their + description in the chapter Configuring &okular;. + + + + + Inverse Search between &latex; Editors and &okular; + + Inverse search is a very useful feature when you are writing a &latex; document yourself. If everything is set up properly, you can + click into &okular;'s window with the left mouse button while pressing &Shift;. After that editor loads the &latex; source file and jumps to + the proper paragraph. + + Inverse search cannot work unless: + + + The source file has been compiled successfully. + &okular; knows which editor you would like to use. + The Browse Tool has to be enabled, which you can select by choosing + ToolsBrowse Tool + . + + + With this feature of &okular;, a left mouse click while pressing &Shift; in the &DVI; or &PDF; document will + result in editor opening the corresponding &latex; document and attempt to go to the + corresponding line. Remember to tell &okular; to use proper editor, in &okular;'s + menu item SettingsConfigure Okular... + (on the page Editor). + + For more details on editor configuration please refer to the corresponding section of this manual. + + + Configuring &okular; + + + + + + Configuring editor in &okular; + + Configuring editor in &okular; + + + + + + + &okular; Advanced Features + + Embedded Files + + If the current document has some files embedded in it, when you open it a yellow bar + will appear above the page view to notify you about the embedded files. + + + The embedded files bar + + + + + + The embedded files bar + + + + + In this case, you can either click on the link in the text of the bar or choose + FileEmbedded Files + to open the embedded files dialog. The dialog allows you to view the embedded files and + to extract them. + + + + Forms + + If the current document has forms, when you open it a bar + will appear above the page view where you can enable the forms. + + + The forms bar + + + + + + The forms bar + + + + + In this case, you can either click on Show Forms in the bar or choose + ViewShow Forms + to enter data into the form fields. + + + + Annotations + + &okular; allows you to review and annotate your documents. + Annotations created in &okular; are automatically saved in the internal local data folder + for each user. + &okular; does not implicitly change any document it opens. + + + &okular;'s Annotations + + + + + + &okular;'s Annotations + + + + &okular; has two different kind of annotations: + Text annotations like Yellow Highlighter and Black Underlining + for files with text like ⪚ &PDF;. + Graphic annotations like Pop-up Note, Inline Note, Freehand Line, Highlighter, Straight Line, Polygon, Stamp, Underline, and Ellipse for all formats supported by &okular;. + Using the context menu either in the Reviews view of the navigation panel or in the main window you can open a Pop up Note for any kind of annotation and add or edit comments. + Annotations are not only limited to &PDF; files, they can be used for any format &okular; supports. + + Since &kde; 4.2, &okular; has the "document archiving" feature. This is an &okular;-specific format for carrying the document plus various metadata related to it (currently only annotations). You can save a "document archive" from the open document by choosing FileExport AsDocument Archive. To open an &okular; document archive, just open it with &okular; as it would be ⪚ a &PDF; document. + + + Since &okular; 0.15 you can also save annotations directly into &PDF; files. This feature is only available if &okular; has been built with version 0.20 or later of Poppler rendering library. You can use File Save As... to save the copy of &PDF; file with annotations. + + + + It is not possible to save annotations into &PDF; file if original file was encrypted and &okular; uses Poppler libraries of version which is lower than 0.22. + + + + + If you open a &PDF; with existing annotations, your annotation changes are not automatically saved in the internal local data folder, and you need to save the modified document (using FileSave As...) before closing it. Should you forget to do this &okular; will show confirmation window that allows you to save the document. + + + + + + Due to DRM limitations (typically with &PDF; documents), adding, editing some properties + or removing annotations could not be possible. + + + + + Any action on annotations (creation and removal of annotations, editing arbitrary annotation properties, relocating annotations with &Ctrl;+drag, and editing the text contents of an annotation) can be undone or redone using the corresponding item from the Edit menu. It is also possible to undo the action by pressing &Ctrl;Z and redo the undone action by pressing &Ctrl;&Shift;Z. + + + + Since &okular; 0.17 you can configure the default properties and appearance of each annotating tool. Please refer to the corresponding section in this documentation. + + + Adding annotations + + To add some annotations to the document, you have to activate the annotating toolbar. + This is done by either selecting + ToolsReview or + pressing F6. Once the annotating toolbar is shown, just press one of + its buttons or use keyboard shortcuts (keys from 1 to 9) to start constructing that annotation. + + + The annotating toolbar helps you to make annotations with drawings, shapes, and text messages. You can use the annotating toolbar to mark up a document (⪚ add lines, ellipses, polygons, stamps, highlights, underlines &etc;). The table below describes exactly what the default set of annotating toolbar buttons does. + + + + + + + Button + + + Tool Name + + + Description + + + + + + + + + + + + + + + + + Pop-up Note + + + + + To draw multiline note. The note will can be viewed by double clicking on an icon in the document. + + + Click on the tool button, then click on the place in the document where the pop-up note should be added. Enter the text of pop-up note then click on the Close this note button in the top right corner of the pop-up window. + + + + + + + + + + + + + + + + Inline Note + + + + + To draw inline note. The note will be shown inline as is. + + + Click on the tool button, then click with the &LMB; and hold to place the top-left corner of the note, then drag to place the bottom-right one. Enter the text of the note then click on the OK to save the note, Cancel to cancel note entering or Clear to clear the note. + + + + + + + + + + + + + + + + Freehand Line + + + + + To draw free-form lines. + + + Click on the tool button, then click with the &LMB; and hold to place the start of the line, then drag to draw the line. + + + + + + + + + + + + + + + + Highlighter + + + + + To highlight text in the document with some given background color. + + + Click on the tool button, then click with the &LMB; and hold to place the beginning of the highlighted text snippet, then drag to highlight it. + + + + + + + + + + + + + + + + Straight Line + + + + + To mark with a line. + + + Click on the tool button, then click with the &LMB; to place the starting point of the line, then drag to place of the ending point of the line should be and click once more. + + + + + + + + + + + + + + + + Polygon + + + + + To draw a closed plane figure from three or more segments. The corresponding note can be viewed by double clicking inside the polygon. + + + Click on the tool button, then click with the &LMB; to place the first vertex of the polygon, then drag to place of the second vertex. Proceed until you draw the whole polygon up to the first vertex. Click twice if you want to add some note to the polygon. Enter the text of the note then click on the OK to save the note, Cancel to cancel note entering or Clear to clear the note. + + + + + + + + + + + + + + + + Stamp + + + + + To mark the text or image with some predefined shape. + + + Click on the tool button then click with the &LMB; to place the stamp. + + + A single click just places a square stamp (useful for icons). + To add a rectangular stamp you can click with the &LMB; and hold to place the top-left point, then drag to place the bottom-right one. + + + + + + + + + + + + + + + + Underline + + + + + To underline some text. + + + Click on the tool button, then click with the &LMB; and hold to place the beginning of the underlined text snippet, then drag to underline it. + + + + + + + + + + + + + + + + Ellipse + + + + + To draw an ellipse around some chosen area. + + + Click on the tool button, then click with the &LMB; and hold to place the top-left corner of the circumscribed rectangular for the ellipse, then drag to place the bottom-right one. + + + + + + + + The contents of the annotating toolbar can be configured using the Annotations page of &okular; configuration dialog. This page can be opened with &RMB; clicking on the annotating toolbar then choosing Configure Annotations... from the context menu. + + + With a single &LMB; click on an annotation tool button you can use a tool once. + If you ⪚ want to highlight all important parts of a text, activate that tool + permanently by double clicking on the tool button. + Press the Esc key or click the tool button again to leave the permanent mode. + + + + The annotating toolbar can be docked in any side of the viewing area: just drag it to + move it to another place. + + + + + Activating the annotating toolbar will make you switch to the Browse Tool Mode. + + + + You can stop the construction any time by pressing again on the button of the + annotation you are constructing, or by pressing the &Esc; key. + + + The newly constructed annotation will have as author the author you set in the + Annotations page in &okular;s + configuration dialog. The Annotations page can also be used to configure the content of the annotating toolbar. + + + + Removing annotations + + To remove an annotation, just click on it with the &RMB;, and select + Delete. + + + When removing the annotation, its window will be closed if open. + + + + This option could not be enabled because the document does not allow removing + annotations. + + + + + Editing annotations + + To edit an annotation, click on it with the &RMB; and select + Properties. A dialog will appear with the general + annotation settings (like color and opacity, author, &etc;) and the settings specific + to that annotation type. + + + Annotation Property Dialog + + + + + + Annotation Property Dialog + + + + + To move an annotation, hold down the &Ctrl; key, move the mouse pointer on it and + then start dragging with the &LMB;. + + + + Depending on the document permissions (typically with &PDF; documents), some options + can be disabled. + + + + + + Bookmark Management + + &okular; has a very flexible bookmark system. &okular; saves the position on the page in bookmark and allows you to define more than one bookmark per page. + + + To manage bookmarks in &okular; you can use Bookmarks view from Navigation Panel, Bookmarks menu or context menu of document view (click with &RMB; to open it). + + + Bookmarks view + + To open Bookmarks view click on Bookmarks item on the Navigation Panel. If the Navigation Panel is not shown, use F7 SettingsShow Navigation Panel + main menu item to make it visible. + + + Bookmark view context menu + + + + + + Bookmark view context menu + + + + + The filter bar at the top of Bookmarks view can be used to filter the content of bookmark list pane according to the text in the box. + + + The list pane permits to view the bookmark list in a tree-like fashion: each document in the list can be expanded or collapsed by clicking on the < or v icon next to it. + + + Click on icon below the list to show only the bookmarks from the current document. + + + Right-click menu of document item can be used to open document, rename its item or remove it from the list. Remember that the removal of a document item leads to the removal of all bookmarks in the corresponding document. + + + Right-click menus of individual bookmark items allow you to go to the bookmark, rename or remove it. + + + + + + Command Line Options + + Though &okular; may most often be started from the &kde; program menu, or a desktop icon, it can also be opened at the command line prompt of a terminal window. There are a few useful options that are available when doing this. + + + Specify a File + + By specifying the path and name of a particular file the user can have &okular; open that file immediately upon startup. This option might look something like the following: + + % okular + + + + For &PDF; documents, the name can be given as document_name#named_destination where named_destination is a particular named destination embedded in the document. + + + + + Other Command Line Options + + The following command line help options are available + + + + okular + + This lists the most basic options available at the command line. + + + okular + + This lists the options available for changing the way &okular; interacts with &Qt;. + + + okular + + This lists the options available for changing the way &okular; interacts with &kde;. + + + okular + + This lists all of the command line options. + + + okular + + Lists &okular;'s authors in the terminal window + + + okular + + Lists version information for &Qt;, &kde;, and &okular;. Also available through okular + + + okular + + Shows license information. + + + okular + + Open a page with a given number in the document. Also available through okular + + + okular + + Start the document in presentation mode. + + + okular + + Start with print dialog. + + + okular + + Unique instance control. + + + okular + + Allows to prevent &okular; window raising after the start. + + + okular + + End of options. + + + + + + + + The Menubar + + + The File Menu + + + + + + &Ctrl;O + + File + Open... + + + + + Open a supported file or &okular; archive. If there is already an opened file it will be closed. For more information, see the section about Opening Files. + + + + + + + + File + Open Recent + + + + + Open a file which was used previously from a + submenu. If a file is currently being displayed it + will be closed. For more information, see the section about + Opening Files. + + + + + + + + File + Import PostScript as &PDF;... + + + + + Open a &PostScript; file and convert it to &PDF;. + + + + + + + + File + Save As... + + + + Save the currently open file under a different name using the document backend. With the &PDF; backend (Poppler >= 0.8 required) it is possible to save the document with the changed values of the form fields. It can be possible (provided that the data were not secured using DRM) to save annotations with &PDF; files (Poppler >= 0.22 required). + + + + + + + File + Save Copy As... + + + + Save a copy of the currently open file under a different name + without using the current document backend. + + + + + + + + F5 + File + Reload + + + + Reload the currently open file. + + + + + + + &Ctrl;P + File + Print... + + + + + Print the currently displayed document. + + + + + + + + File + Print Preview... + + + + + Show a preview of how the currently displayed + document would be printed with the default options. + + + + + + + + File + Properties + + + + Display some basic information about the document, such as + title, author, creation date, and details about the fonts used. The available information + depends on the type of document. + + + + + + File + Embedded Files... + + + + Show the files embedded in the document, if the document has any. + For more information, see the section about the + Embedded Files. + + + + + + + File + Export As + + + + This item contains the export formats the current document can be exported to. + The first entry for all kind of documents is always Plain Text... + The second entry is Document Archive, which allows you to save the document with your annotations into an &okular;-specific archive format. Thus it is easily possible to share the original document and your annotations with other &okular; users or work with them collaboratively. + + + + + + + &Ctrl;Q + File + Quit + + + + Close &okular;. + + + + + + + The Edit Menu + + + + + + &Ctrl;Z + Edit + Undo + + + + Undo the last annotation editing command (creation and removal of annotations, editing arbitrary annotation properties, relocating annotations with &Ctrl;+drag, and editing the text contents of an annotation). + + + + + + + &Ctrl;&Shift;Z + Edit + Redo + + + + Redo the last undo step when editing annotations. + + + + + + + + + &Ctrl;C + Edit + Copy + + + + Copy the currently selected text in + Text Selection mode to the clipboard. + + + + + + + + + &Ctrl;A + Edit + Select All + + + + Selects all the text (if the document provides it). This works only in + Text Selection mode. + + + + + + + + + &Ctrl;F + Edit + Find... + + + + + Show the find bar on the bottom of the viewing area that allows you to + search for a string in the document. + + + + + + + + F3 + Edit + Find Next + + + + Try to find the previous searched string again in the document. + + + + + + + &Shift;F3 + Edit + Find Previous + + + + Goes to the previous occurrence of the search string in the document. + + + + + + + The View Menu + + + + + &Ctrl;&Shift;P + View + Presentation + + + + Activates the Presentation Mode. For more information, see the + section about Presentation Mode. + + + + + + &Ctrl;+ + View + Zoom In + + + + Increase the magnification of the document view. + + + + + + + &Ctrl;- + View + Zoom Out + + + + Decrease the magnification of the document view. + + + + + + View + Fit Width + + + + Change the magnification of the document + view to a value that makes the pages' width equal to the document + view's width. + + + + + + View + Fit Page + + + + Change the magnification of the document view + to a value that makes at least one whole page visible. + + + + + + View + Auto Fit + + + + Change the magnification of the document view + to a value that, depending on the size relation between the page and the view area, automatically either makes the pages' width equal to the document view's width (like fit-width), the pages' height equal to the document view's height (like fit-height), or the whole page visible (like fit-page). + + + + + + View + Continuous + + + + Enable the continuous page mode. In continuous mode, + all pages of the document are shown, and you can scroll through + them without having to use the Go + Previous Page and + GoNext Page + options. + + + + + + View + View Mode + + + + This submenu makes you choose the view mode for the pages. The possible + options are: Single Page (only one page per row), + Facing Pages (two pages per row, in a book style), + Facing Pages (Center First Page) and + Overview (the number of columns is the one + specified in the &okular; settings). + + + + + + View + Orientation + + + + + This submenu allows you to changes the orientation + of the pages of the document. + + + + The rotation is applied to the orientation of every page. + + + + You can select Original Orientation to restore + the orientation of the document, discarding all the rotations applied + manually. + + + + + + + View + Page Size + + + + + Changes the size of the pages of the document. + + + + This submenu is active only if the current type of document + supports different page sizes. + + + + + + + + View + Trim Margins + + + + + Remove the white border of pages when viewing pages. + + + + + + + View + Show/Hide Forms + + + + + Show or hides the display of the form fields of the + document. + + + + This menu item is active only if the current document has form + fields. + + + + + + + + + + The Go Menu + + + + + Go + Previous Page + + + + View the previous page of the document. + + + + + + Go + Next Page + + + + View the next page of the document. + + + + + + &Ctrl;Home + Go + Beginning of the document + + + + Go to the beginning of the document. + + + + + + &Ctrl;End + Go + End of the document + + + + Go to the end of the document. + + + + + + &Alt;&Shift;Left + Go + Back + + + + Go back to the previous view of the document. + + + + + + &Alt;&Shift;Right + Go + Forward + + + + Move forward to the next view of the document. This only works if you have already moved back before. + + + + + + &Ctrl;G + Go + Go to Page... + + + + Open a dialog which allows you to go to any page of the document. + + + + + + + The Bookmarks Menu + + + + + &Ctrl;B + Bookmarks + Add/Remove Bookmark + + + + + Add or remove a bookmark for the current position. + + + + + + + Bookmarks + Rename Bookmark + + + + + Rename a bookmark for the current position. + + + + + + + Bookmarks + Previous Bookmark + + + + Go to the previous bookmark, or do nothing if there + are no bookmarks prior to the current one. + + + + + + Bookmarks + Next Bookmark + + + + Go to the next bookmark, or do nothing if there + are no bookmarks after the current one. + + + + + + Bookmarks + No Bookmarks + + + + This is an always disabled action that appears in this menu only if the current document has + no bookmarks. Otherwise a list of all bookmarks is displayed here. Clicking on these bookmarks + allows you to go directly to the associated position. + + + + + + + The Tools Menu + + + + + &Ctrl;1 + Tools + Browse Tool + + + + The mouse will have its normal behavior, &LMB; for dragging the document and following links and &RMB; for adding bookmarks and fit to width. + + + + + + &Ctrl;2 + Tools + Zoom Tool + + + + The mouse will work as a zoom tool. Clicking &LMB; and dragging will zoom the view to the selected area, clicking &RMB; will bring the document back to the previous zoom. + + + + + + &Ctrl;3 + Tools + Selection Tool + + + + The mouse will work as a select tool. In that mode clicking &LMB; and dragging will give the option of copying the text/image of current selected area to the clipboard, speak a text or to save an image to a file. + + + + + + &Ctrl;4 + Tools + Text Selection Tool + + + + The mouse will work as a text selection tool. In that mode clicking &LMB; and + dragging will give the option of selecting the text of the document. Then, just + click with the &RMB; to copy to the clipboard or speak the current selection. + + + + + + &Ctrl;5 + Tools + Table Selection Tool + + + + Draw a rectangle around the text for the table, then use the click with the &LMB; + to divide the text block into rows and columns. A &LMB; click on a existing line removes it and merges the adjacent rows or columns. + + + + + + &Ctrl;6 + Tools + Magnifier + + + + Activates the magnifier mode for the mouse pointer. Press and hold the &LMB; to activate magnifier widget, move the pointer for panning through the document. The magnifier scales each pixel in the document into 10 pixels in the magnifier widget. + + + + + + F6 + Tools + Review + + + + Open the review toolbar. The review toolbar allows you to add + annotations on the document you are reading. For more information, please see the + section about Annotations. + + + + + + Tools + Speak Whole Document + + + + + Tools + Speak Current Page + + + + + Tools + Stop Speaking + + + + These items allow you to speak the whole document or just the current page and stop speaking using the &kde; Text-to-Speech system &jovie;. + The Speak ... actions are enabled only if &jovie; is available in the system. + + + + + + + The Settings Menu + + + + + &Ctrl;M + Settings + Show Menubar + + + + Toggle the Menubar display on and off. Once + hidden it can be made visible using the shortcut + &Ctrl;M again. + If the menubar is hidden, the context menu opened with a right mouse button + click anywhere in the view area has an extra entry Show Menubar. + + + + + + Settings + Show Toolbar + + + + Toggle the Toolbar display on and off. + + + + + + F7 + Settings + Show Navigation Panel + + + + Toggle the navigation panel on and off. + + + + + + Settings + Show Page Bar + + + + Toggle the page bar at the bottom of document area on and off to save vertical place in &okular; window. + + + + + + &Ctrl;&Shift;F + Settings + Full Screen Mode + + + + Enables the full screen mode. Note that + full screen mode is different from presentation mode insofar as the + only peculiarity of full screen mode is that it hides the window + decorations, the menubar and the toolbar. + + + + + + Settings + Configure Shortcuts... + + + + Opens a window that lets you configure the keyboard + shortcuts for many menu commands. + + + + + + Settings + Configure Toolbars... + + + + Opens a window that lets you choose which icons are visible + in the toolbar. + + + + + + Settings + Configure Backends... + + + + Opens the Backend Configuration + window. + + + + + + Settings + Configure &okular;... + + + + Opens the Configure + window. + + + + + + + The Help Menu + &help.menu.documentation; + + + + + Configuring &okular; Backends + + You can configure &okular; backends by choosing + Settings + Configure Backends... + . + Currently, configuration options are provided for EPub, &PostScript;, FictionBook, Txt, OpenDocument Text, and &PDF; backends only. + + + The backends configuration dialog + + + + + + The backends configuration dialog + + + + + Using backend configuration pages for EPub, FictionBook, Txt and OpenDocument Text you can define the font to render documents in the corresponding formats. The Choose... button in these pages opens standard &kde; font configuration window. Please refer to the &kde; Fundamentals documentation for the details. + + + The description of &PostScript; and &PDF; backends configuration pages can be found below. + + + &PostScript; backend configuration + + You can configure &okular; &PostScript; rendering backend based on Ghostscript by choosing Ghostscript from the list on the left part of the configuration dialog. The only configurable option is as follows. + + + + + + Use platform fonts + + + This option determines whether Ghostscript should be allowed to use platform fonts, if unchecked only usage of fonts embedded in the document will be allowed. This option is checked by default. + + + + + + + + &PDF; backend configuration + + You can configure &okular; &PDF; rendering backend based on Poppler by choosing &PDF; from the list on the left part of the configuration dialog. The only configurable option is as follows. + + + + + + Enhance thin lines + + + + Drawing lines in &okular; is implemented in two steps: generation of the clipping path and filling this clipping path. When the line in the original document is less than one pixel this two step implementation could cause problems. For those lines, the clipping path is filled with the filling color that depends on the thickness of the line part inside the clipping area. If the part of the line inside the clipping area gets very small the contrast between the shape and the background color can become too low for the line to be recognizable. The grids of such lines then looks very unpretty. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thin line (red), its clipping path (dashed line) and pixel boundaries (black solid lines) + Thin line shown at a low contrast + + + + + + To enhance the look of the thin lines &okular; implements two options. + + + The first option is Solid. With this option &okular; adjusts clipping path and line position so that clipping path and line are on the same pixel boundary, &ie; &okular; enlarges the thin lines to one pixel on the output device. This mode is similar to the Enhance thin lines in Adobe + Reader. If this option is chosen, the thin lines are always enlarged. + + + + + + + + + Thin line with Solid enhancement + + + Thin line with Solid enhancement + + + + + + The second option is Shape. With this option the clipping path and line are adjusted to pixel boundary as well, but the line intensity is corrected according to its width. + + + + + + + + + Thin line with Shape enhancement + + + Thin line with Shape enhancement + + + + + + The thin lines are not enhanced by default (option No). + + + + + + + + + Configuring &okular; + + General configuration + + You can configure &okular; by choosing Settings + Configure &okular;.... + The configuration dialog is split into six sections. This chapter describes the available + options in detail. + + + + General + + + Accessibility + + + Performance + + + Presentation + + + Annotations + + + Editor + + + + The configuration dialog + + + + + + The configuration dialog + + + + + Depending on the currently installed backends, the Settings + Configure Backends... menu item could be enabled. + This particular configuration dialog holds the configurations of the backends that can + actually be configured. + + + + General + + + Show scrollbars + + Whether to show scrollbars for the document view. + + + + Link the thumbnails with the page + + Whether the thumbnails view should always display the current + page or not. + + + + Show hints and info messages + + Whether to show some informative messages on startup, file + load, &etc; + + + + Display document title in titlebar if available + + Whether to show the current document title in the titlebar of &okular; window. If no metadata for title + found in the document or this item is unchecked &okular; shows filename of the document. + + + + + When not displaying document title + + You can choose any of two options, Display file name only or Display full file path. + + + + + Obey DRM limitations + + Whether &okular; should obey DRM + (Digital Rights Management) restrictions. DRM limitations are used + to make it impossible to perform certain actions with &PDF; documents, + such as copying content to the clipboard. Note that in some configurations + of &okular;, this option is not available. + + + + Open new files in tabs + + Whether to open new documents in tabs. The tabs are disabled by default. + + The default shortcuts to switch between tabs are &Ctrl;. (Next tab) and &Ctrl;, (Previous tab). + + + + + Reload document on file change + + Whether opened files should be automatically checked for + changes and updated, if necessary. + + + + Show backend selection dialog + + Whether &okular; should ask the user which backend to use in case of more + than one backend able to open the current file. If unchecked, &okular; will + use the backend with the highest priority. + + + + Overview columns + + + This option represents the number of columns to use in the overview mode. + + + + + Page Up/Down overlap + + + Here you can define the percentage of the current viewing area that should be visible after pressing Page Up/Page Down keys. + + + + + Default Zoom + + + This options specifies the default zoom mode for file which were never + opened before. For those files which were opened before the previous zoom mode + is applied. + + + + + + + Accessibility + + + Draw border around links + + Whether to draw a border around links. + + + + Change colors + + Enables the color changing options. + + + + Invert Colors + + Inverts colors on the view, &ie; black objects will be shown white. + + + + Change Paper Color + + Changes the paper's color, &ie; the document's background. + + + + Change Dark & Light Colors + + Changes the dark and light color to your preference, that means + black will not be rendered as black but as the selected dark color and white + will not be rendered as white but as the selected light color. + + + + Convert to Black & White + + Converts the document to black and white. You can set the + threshold and the contrast. Setting the threshold to a higher value + by moving it to the right will result in lighter grays used. + + + + + + Performance + + + Enable transparency effects + + Draw selections and other special graphics using + transparency effects. Disable the option to draw them using + outline or opaque fill styles and increase speed on selections. + + + + Memory Usage + + &okular; can achieve best performance by tuning the memory usage, based on your system and your tastes. + The more memory you let it to use, the faster the program will behave. The Default profile is good + for every system, but you can prevent &okular; from using more memory than necessary by selecting the Low + profile, or let it get the most out of your system using Aggressive. Use Greedy profile to preload all + pages without risk of system memory overfull (only 50% of total memory or free memory will be used). + + + + Rendering + + Using this group of options you can improve text and image rendering in &okular;. The result depends on the device to display the document. + Enable Text Antialias and Enable Graphics Antialias items can be used to switch on and off spatial anti-aliasing of text and images in document, correspondingly. + Enable Text Hinting item is meant to be a switcher for font hinting. + Antialiasing and hinting change how the documents are displayed, you may want to tweak them to your preference. + + + + + + Presentation + + + Advance every + + Enables automatic advancing of pages given a time period. + + + + Loop after last page + + When navigating on presentation mode and going past the last page the first page will appear. + + + + Background color + + The color that will fill the part of the screen not covered by the page when on presentation mode. + + + + Pencil color + + The color of the pencil used when drawing on the pages during the presentation mode. + + + + Mouse cursor + + Whether the mouse should be always hidden, always shown or hidden after a small time of inactivity. + + + + Show progress indicator + + Whether to show a progress circle that shows the current page and the total number of pages on the upper + right corner of the presentation screen every time you change the page. + + + + Show summary page + + Whether to show a summary page at the beginning of the presentation with the title, author and number of pages of the document. + + + + Enable transitions + + Use this check box to enable or disable transition effects between pages. + + + + Default transition + + The transition effect between page and page if the document does not specify one. Set this to Random + Transition to make &okular; randomly choose one of the available effects. + + + + Placement + + In this section you can select the Screen used to display the presentation. + Current Screen is same screen of the &okular; window that starts the presentation mode. + Default Screen is the screen marked as default in the xinerama configuration. + Screen 0, Screen 1 &etc; are the available screens. + + + + + + Annotations + + The Annotations page of the configuration dialog + + + + + + The Annotations page of the configuration dialog + + + + + + Author + + The author of the contents added on a document. + Default is the name from the Password & User Account page of the &systemsettings; module Account Details. + + + + Annotation tools + + + This pane is used to configure your annotating toolbar. + + + There are five buttons (Add, Edit, Remove, Move Up, Move Down) and a list box (which lists the contents of the current annotating toolbar) which are used to configure the toolbar. + + + If you need to add some tool button on the toolbar click on the Add button. You can choose the Name, the Type and the Appearance of the created tool. + + + + Please remember that annotation tools in &okular; are highly configurable. For example, you can have two buttons of the same tool but with different color. Do not hesitate to experiment in choosing the button set that is exactly tailored to your workflow. + + + + Click on some item in the list box then click on the corresponding button at the right part of the page to edit, remove, move up or move down the item. The keyboard shortcut of the tool (keys from 1 to 9) depends on its position in the list of annotating toolbar. + + + + + + + Editor + + + Editor + + Choose the editor you want to launch when &okular; wants to open a source file. + This is the case when the document has references to the various points (usually row and column number) of sources it was generated from. The &DVI; format supports natively the addition of the information about the sources the LaTeX document was generated from. A similar system exists for &PDF; documents, called pdfsync, which stores these extra information in an external file named after the &PDF; file itself (for example mydocument.pdfsync for mydocument.pdf). + + + &okular; ships with preconfigured settings for the following editors: &kate;, Kile, SciTE, &Emacs; client, and LyX client. + + + + To use inverse search in Kile, you have to compile your &latex; file with the Modern configuration. + + + + + + Command + + This is the command and its parameters to invoke the selected editor with the source file of the actual document. + This field will be filled automatically if you use one of the preconfigured editors. Otherwise, please choose Custom Text Editor in Editor drop down box and refer to the documentation on your favorite editor to find the proper command. + + You can use the following placeholders: + + + %f - the file name + + + %l - the line of the file to be reached + + + %c - the column of the file to be reached + + + If %f is not specified, then the document name is appended to the specified command. + + + + + + + Credits and License + + + Program Copyright: + &Albert.Astals.Cid; &Albert.Astals.Cid.mail; Current maintainer + Pino Toscano pino@kde.org + Enrico Ros eros.kde@email.it &kpdf; developer + + + + Documentation Copyright: + &Albert.Astals.Cid; &Albert.Astals.Cid.mail; Author + Titus Laska titus.laska@gmx.de Some updates and additions + Pino Toscano pino@kde.org + + + &underFDL; + &underGPL; + + + + Installation + + How to obtain &okular; + &install.intro.documentation; + + + Compilation and Installation + + + If you are reading this help in the &khelpcenter;, &okular; has already been + installed on this system and you do not need install it anymore. + + + &install.compile.documentation; + + + &documentation.index; + + + + diff --git a/okular/doc/mainwindow.png b/okular/doc/mainwindow.png new file mode 100644 index 0000000000000000000000000000000000000000..abddeae55283f00d33eabbde0f4f29ba2a2c7cfc GIT binary patch literal 52736 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 + +]> + + + + +KDE User's Manual +YuriChornoivan +Okular man page. +yurchor@ukr.net +2013-03-09 +K Desktop Environment + + + +okular +1 + + + +okular +a universal document viewer + + + + + okular Qt-options + KDE-options + Okular options + URL + + + + +Description +okular +is aimed to show documents in different formats. + + + +Generic Options + + + + +Show help about options. + + + + + +Show Qt specific options. + + + + + +Show KDE specific options. + + + + + +Show all options. + + + + + +Show author information. + + + + + +Show version information. + + + + + +Show license information. + + + + + +End of options. + + + + + +Okular options + + + + +Open a given page in the document. + + + + + +Start the document in presentation mode. + + + + + +Start with print dialog. + + + + + +Unique instance control. + + + + + +Allows to prevent Okular window raising after the start. + + + + + +Arguments + + + + + An address of the document to be opened. For PDF, can be given as document_name#named_destination where named_destination is a particular named destination embedded in the document. + + + + + +Feedback +On IRC, irc.freenode.net #okular or via email, okular-devel@kde.org or on the web, <http://okular.kde.org/> + + + +See Also +More detailed user documentation is available from help:/okular +(either enter this URL into &konqueror;, or run +khelpcenter +help:/okular). + + +Copyright +Copyright © 2002 Wilco Greven, Christophe Devriese +Copyright © 2004-2005 Enrico Ros +Copyright © 2005 Piotr Szymanski +Copyright © 2004-2012 Albert Astals Cid +Copyright © 2006-2009 Pino Toscano +License: GNU General Public Version 2 <http://www.gnu.org/licenses/gpl-2.0.html> + + + diff --git a/okular/doc/presentation.png b/okular/doc/presentation.png new file mode 100644 index 0000000000000000000000000000000000000000..133f01e59b2412c14d1560f21e92e9888059fe74 GIT binary patch literal 6360 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 literal 0 HcmV?d00001 diff --git a/okular/doc/rating.png b/okular/doc/rating.png new file mode 100644 index 0000000000000000000000000000000000000000..3630d8ff194e8fc8953bcb0d9dc75b85a33113bb GIT binary patch literal 744 zcmVP)@X1W@%1z^^1>U9w;i?(osTkg-1=E}c*qR8~nhDdK2F{cO(U1hxmJ!&W zA=;uP)t3^{j|9-hKDqeelR&^UG)V)Oq*Q zcK6nH_}O;&+IINccK6tJ_ttsz%WLnvRQAbk_Rntj({A_IZur@5_}Xsx*lzdLZuik{ z@yKQIzf0Bm`PWGI z%P{u05b&}R_qr1J#0vS%0Q%AZ`OpCP$N=@d0P(K^_qzc3%mDh@0Q=$q``-Zi(*X9! z0QIZ@_q_o5(E#+^0P^4f{Okbu#{l%F0N9oQ)uRCS#sD%W^8^3@06TP2PE!EJ%8~v3 zmKgc^8xQ8@6h+VG`1tA8RVVlOE&}K21Qhr77eV^^MBd}*?CtOF^6c&O@3QW(92u
Adeb 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 Eg5l6mCjbBd literal 0 HcmV?d00001 diff --git a/okular/doc/tool-line-okular.png b/okular/doc/tool-line-okular.png new file mode 100644 index 0000000000000000000000000000000000000000..e4f2d825126cf7cbead10cd022e56576d7106048 GIT binary patch literal 849 zcmV-X1FrmuP)U36 zv9YMAsI#-P^78Wh{QSJUyw=v%{r&yu=;;6V&;QsH`uh6a-QDHo<^T84{?`-!*&zA( z`OePH@$vDwxw-4>>&?y0*Vos`$jFY4jDESyRx#f z$;rw4`}?e{tj58or?(XjI@9+Nh(DczF^zYXA z`1tnr_W$?M|JW42zrX+Z($2Cc;=?KS@z|oGqQbtV$E_-$kUr$#$-=_I+1c5{!^5<+ zw7zSGXN_|ziQwHEx_F5SgS^w>z)!At(&NAlK4 z;mAqp&q?mnN%-4H|KUgf>V5y;I{)Z)9&`Ms00008bW%=J04^y3$vOi)WLmQ}0003z zNklQ5m7;5F>wiD zVPQ!rX&EF1vU2hYib~2Vs%k3g8mgMI99r7aI>=V&%IoPH7-||Bo0yvE$(mchp z?t!Af)5}}K$I{Z*&%r++FvvPMB-8=b1bz3g@Q6smD9z}Y*tmGj;Dp2^ITQuSx+ylP z9J&^1_URcMnKmjMDOrZt9GQ*cK1>DKIZ$~_1qgW@3M_L|Ec5aU3UQ_fgCfV`lG3vB z3ZF_-+j0|31yNPiHMMp1nyL+rAx-62Ot5KgX>Duo=Fw*E00t9L9O*ns7#&O= bWcdOBU<_1~D-3!700000NkvXXu0mjfe>&%V literal 0 HcmV?d00001 diff --git a/okular/doc/tool-note-inline-okular.png b/okular/doc/tool-note-inline-okular.png new file mode 100644 index 0000000000000000000000000000000000000000..235d0a44606f8d6c1139d24c8ee01fc41bae4515 GIT binary patch literal 803 zcmV+;1Kj+HP)^5v1#$$#I}hUwjm&AEp1=8@OK ziQ?DA&AE~K`ug47-R0%wrKP3X+S>g5{Qdp?`T6;+t*y?^&hhc_xw*OP>+8+U&BevV z*Vos`$jFY4jDESyRx#f$;rw4`}^G7+^npu#>U3N z!oshwug}lVqobqbqN2yg z$Dp8~+1c5{!^5$$vFPaNy1KgX@bIv(u#u6GzP`TG)6>+{)Y#bA;o;%w>FI7-nb-gT z00?waPE!CbDFMkk13hF~vo-(#0W3*GK~#7FVju_@@c<@fpaK>=fRzoXfCCR;rsmsi&i zKvJNo#mlR$qpPRS&o6JFY-nU`B4;XZhGvDig{76XjjfWMmA!+ble4vw1hN7bWVgA( z7;Xp!?jG0xC-DL0$>HS%-J@}Ou z_;CbPl;Kq1Tj`ZqRh^qz6H!}NpP7NFAU(FcAt@L3I44mZGK+G?V3d$ hodqQX9XU7^003wcRNtPPOpyQp002ovPDHLkV1gj&#-0EG literal 0 HcmV?d00001 diff --git a/okular/doc/tool-note-okular.png b/okular/doc/tool-note-okular.png new file mode 100644 index 0000000000000000000000000000000000000000..b0dd4635c679b6d6788f1d0c53ed5ad6eb19d749 GIT binary patch literal 973 zcmV;;12X)HP)U36v9YMAsMW)U(#L-0+KRrka=EZ?vZ-syzj@x% zg~+~mys~l1zh^e4u{QUg9yu8-d*8Tnc+{uj3#C+DvfY{A~&BJ@y&Vu6Bh`+US z(Z+qnyLQvaf5f_W=;-M1+9>y&CS=>*T~4oj*gDBw6w*u zcH!2C(8Yb=)rQW_&bzz2va+(QtgNS}r^3R*$hCOEw{*L)aJQ~*-O`2b?(XmJ@AUNa z+Q*B{x{le$i@(3W;^N|>qN2yg$Dp8~zSGmw)YR1B z;o<4&>5#)dBme*a6m(KfQvfb00m(W8J(qZPHN?ZbsLZ^)bnD0Jo&W#=i%CR5RCocD z$~6*zFc1L1;0{f23GRXa|Bkd5W{zR&C7%?8s)?W@3{wP(u>OZQ0c^+hU`R8W4@3S6 z0SrYc3q`2vhPAZo86Cz+7Lj>bxBWQzb@OKihDNH!5OEWP0#gk$a|;5g$KOe-ROX{{Yp%Acx?fkkGL3h{&kuFo*(?7`P(? zR8?bv5NJgl$o%*OhyoEL1wl>@iJnQxQGp>&DXAWDmHv%@@da)AmI zMe_2^EksOxfQ~RNfH}UfsIU;~i{g?}!!jSA@(M*gxymY4x11c;!0O literal 0 HcmV?d00001 diff --git a/okular/doc/tool-polygon-okular.png b/okular/doc/tool-polygon-okular.png new file mode 100644 index 0000000000000000000000000000000000000000..9df96ca123022193a9127a49cc102e64aaacc389 GIT binary patch literal 664 zcmV;J0%!e+P)>p z-QDHo<@x#f&d$#9@$tF2x$Eoe&CSi%*Vo9%$c~PVw6wIixVWdMr@Onmva+(t$;qs& ztir;=udlDq&(EWyqvYh|?(XjI@9zg(uq<@E_xk+&{r+{)ZyXb>FMcC8{{amH1n#Z;Be3oq|Ndd%KenDKHQ$bcUT0Wv yy=QE^yz*~8L;-ylK9HkmMEv{;gw#*_KfyOLw?b@p?lzbJ0000U36v9YMAsI#-Pyu7^D*4F*~{pjfE@9*!w zzrUB3mbSIDxVX3Z`T6zt_-?e{MxNH(>hs;*-R0%w&d$#F_xJVo_H?z~KAF+dCpX=rNGYyx8iyySuWovdPKGtgNiU!oshwug}lV zqobqb;Ogq?;^N|-o}QADlA)oYlarI3ot?|e%axUt%F4>3qN2yg$Dp8~+1c5{!^4=E zn2?Z=k&%&>mX`4F@UXD3zP`Tc>FMq5?bFlK)YR1B;o;ui-iU~ZhK7cNgM*EYjf#qj zmzS4ALqms$hm@3*dU|>|I5>fUf#ToX;^5um;NIln-{j)o<>TPy>gea|>F4a~=+kOD@b2yK@9y#N@AC2Q^YZcZ^YZlb z^Y!%eF}6C)0005qNkl62q_R05*88VXBQLU=MtB|R3IrOEh8%@ub`-;psb>(Dv6;$O ztBC_0pr*zzZmH^^=;-L^UhIm(-pYnP!oKXLKm~sO0U{6u z8V-R$!CZnNsye=*!eOdD;SrIlKm}3JV0lXh1|hW=BNKi#Q8j)E34ReH1^yT@4M!1? z*f@~1WjuCA0_74CK`u>7#;HIgB{eNQBQpz|0wW(W5s-GrY)4C=H^oh3#Q7a^#9-3l zxq0~og~66Zg?Spq+9eDOnx%H3W#y5f6`qy4#-Td=RT4%LMxuGuH45TtT-mh@3?cfa z@(#Xrs*3eB!VRJP{G~;rjZLAs#<@BsTqY`|3=BriA}!)}64Gj-t&K4v{QPZI?H!$6 zt?aE{o$cK{(wL5H@9pcaV+Vm4?+FtpVfQ9G0uYRDY9xEWCQq3XeM^#A|> literal 0 HcmV?d00001 diff --git a/okular/doc/tool-underline-okular.png b/okular/doc/tool-underline-okular.png new file mode 100644 index 0000000000000000000000000000000000000000..a14282d1f2236f9ebbadfde1387b78e7a2652e09 GIT binary patch literal 629 zcmV-*0*d{KP)s_5wG-QC^g<>meT{rUO%&d$#9 z@$tF2x$Eoe&CSi%*Vo9%$c~PVw6wI=*4DVVxTmM5ySuxxva-p^$*iob!otF@udmO~ z&!eNGSz20JTU%RPTwGXKSc-~@zrVlk?(X8^;-aFW$H&K@prF~=*~7!b z@bK`ku&|Mlk-omZ)6>(`)YReO;pyq=Poz!W00008bW%=J04^y3$vOi)WLmQ}0002| zNklulvhzzQ`b-w*5t*sLP<+oM@v^nOGifwh;{U^I8vX+-sIyj zFjP|JP!KfY;80ND5HyxIF-1{eW^N&8DJW=V%_q%mBWr8NZO?~lf|7}Yqmuxqv%H$U z3%9GhotwLd2#Nwv9WN~}OBrt+9UmVZUoAf!FJFIp$EILFpdg1y5I;@@%E1OMA)y{F zVV2?Q`Yvji3Y={MBu(ri * + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "extensions.h" + +// local includes +#include "part.h" + +namespace Okular +{ + +/* + * BrowserExtension class + */ +BrowserExtension::BrowserExtension(Part* parent) + : KParts::BrowserExtension( parent ), m_part( parent ) +{ + emit enableAction("print", true); + setURLDropHandlingEnabled(true); +} + + +void BrowserExtension::print() +{ + m_part->slotPrint(); +} + + +/* + * OkularLiveConnectExtension class + */ +#define OKULAR_EVAL_RES_OBJ_NAME "__okular_object" +#define OKULAR_EVAL_RES_OBJ "this." OKULAR_EVAL_RES_OBJ_NAME + +OkularLiveConnectExtension::OkularLiveConnectExtension( Part *parent ) + : KParts::LiveConnectExtension( parent ), m_inEval( false ) +{ +} + + +bool OkularLiveConnectExtension::get( const unsigned long objid, const QString &field, + KParts::LiveConnectExtension::Type &type, + unsigned long &retobjid, QString &value ) +{ + Q_UNUSED( value ) + retobjid = objid; + bool result = false; + if ( field == QLatin1String( "postMessage" ) ) + { + type = KParts::LiveConnectExtension::TypeFunction; + result = true; + } + return result; +} + + +bool OkularLiveConnectExtension::put( const unsigned long objid, const QString &field, const QString &value ) +{ + Q_UNUSED( objid ) + if ( m_inEval ) + { + if ( field == QLatin1String( OKULAR_EVAL_RES_OBJ_NAME ) ) + m_evalRes = value; + return true; + } + + return false; +} + + +bool OkularLiveConnectExtension::call( const unsigned long objid, const QString &func, const QStringList &args, + KParts::LiveConnectExtension::Type &type, unsigned long &retobjid, QString &value ) +{ + retobjid = objid; + bool result = false; + if ( func == QLatin1String( "postMessage" ) ) + { + type = KParts::LiveConnectExtension::TypeVoid; + postMessage( args ); + value = QString(); + result = true; + } + return result; +} + + +QString OkularLiveConnectExtension::eval( const QString &script ) +{ + KParts::LiveConnectExtension::ArgList args; + args.append( qMakePair( KParts::LiveConnectExtension::TypeString, script ) ); + m_evalRes.clear(); + m_inEval = true; + emit partEvent( 0, "eval", args ); + m_inEval = false; + return m_evalRes; +} + + +void OkularLiveConnectExtension::postMessage( const QStringList &args ) +{ + QStringList arrayargs; + Q_FOREACH ( const QString &arg, args ) + { + QString newarg = arg; + newarg.replace( '\'', "\\'" ); + arrayargs.append( "\"" + newarg + "\"" ); + } + const QString arrayarg = '[' + arrayargs.join( ", " ) + ']'; + eval( "if (this.messageHandler && typeof this.messageHandler.onMessage == 'function') " + "{ this.messageHandler.onMessage(" + arrayarg + ") }" ); +} + +} + +#include "extensions.moc" + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/okular/extensions.h b/okular/extensions.h new file mode 100644 index 00000000..dcd5c1cb --- /dev/null +++ b/okular/extensions.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (C) 2002 by Wilco Greven * + * Copyright (C) 2008 by Pino Toscano * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _EXTENSIONS_H_ +#define _EXTENSIONS_H_ + +#include + +namespace Okular +{ + +class Part; + +class BrowserExtension : public KParts::BrowserExtension +{ + Q_OBJECT + + public: + BrowserExtension(Part*); + + public slots: + // Automatically detected by the host. + void print(); + + private: + Part *m_part; +}; + +class OkularLiveConnectExtension : public KParts::LiveConnectExtension +{ + Q_OBJECT + + public: + OkularLiveConnectExtension( Part *parent ); + + // from LiveConnectExtension + virtual bool get( const unsigned long objid, const QString &field, Type &type, + unsigned long &retobjid, QString &value ); + virtual bool put( const unsigned long objid, const QString &field, const QString &value ); + virtual bool call( const unsigned long objid, const QString &func, const QStringList &args, + Type &type, unsigned long &retobjid, QString &value ); + + private: + QString eval( const QString &script ); + void postMessage( const QStringList &args ); + + bool m_inEval; + QString m_evalRes; +}; + +} + +#endif + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/okular/generators/CMakeLists.txt b/okular/generators/CMakeLists.txt new file mode 100644 index 00000000..99ff78d3 --- /dev/null +++ b/okular/generators/CMakeLists.txt @@ -0,0 +1,87 @@ +include (MacroLogFeature) + +set(LIBSPECTRE_MINIMUM_VERSION "0.2") + +macro_optional_find_package(Poppler) +macro_log_feature(HAVE_POPPLER_0_12_1 "Poppler-Qt4" "A PDF rendering library" "http://poppler.freedesktop.org" FALSE "0.12.1" "Support for PDF files in okular.") + +macro_optional_find_package(LibSpectre) +macro_log_feature(LIBSPECTRE_FOUND "libspectre" "A PostScript rendering library" "http://libspectre.freedesktop.org/wiki/" FALSE "${LIBSPECTRE_MINIMUM_VERSION}" "Support for PS files in okular.") + +macro_optional_find_package(Kexiv2) +macro_log_feature(KEXIV2_FOUND "LibKExiv2" "Wrapper around Exiv2 library" "http://www.digikam.org/sharedlibs" FALSE "" "Support for image files") + +macro_optional_find_package(CHM) +macro_log_feature(CHM_FOUND "CHM" "A library for dealing with Microsoft ITSS/CHM format files" "http://www.jedrea.com/chmlib" FALSE "" "Support CHM files in okular.") + +macro_optional_find_package(DjVuLibre) +macro_log_feature(DJVULIBRE_FOUND "DjVuLibre" "A library for dealing with DjVu formatted files" "http://djvulibre.djvuzone.org" FALSE "3.5.17" "Support for DjVu files in okular.") + +macro_optional_find_package(TIFF) +macro_log_feature(TIFF_FOUND "libTIFF" "A library for reading and writing TIFF formatted files," "http://www.remotesensing.org/libtiff" FALSE "" "Support for TIFF files in okular.") + +macro_optional_find_package(Freetype) +macro_log_feature(FREETYPE_FOUND "FreeType" "A font rendering engine" "http://www.freetype.org" FALSE "" "Provides freetype font support in the okular DVI generator.") + +macro_optional_find_package(JPEG) +macro_log_feature(JPEG_FOUND "JPEG" "A library for reading and writing JPEG image files." "http://www.ijg.org" FALSE "" "Support fof PalmDB documents in okular.") +macro_optional_find_package(ZLIB) +macro_log_feature(ZLIB_FOUND "ZLib" "The Zlib compression library" "http://www.zlib.net" FALSE "" "Support for Plucker files in Okular.") + +macro_optional_find_package(EPub) +macro_log_feature(EPUB_FOUND "libepub" "A library for reading EPub documents" "http://sourceforge.net/projects/ebook-tools" FALSE "" "Support for EPub documents in Okular.") + +macro_optional_find_package(QMobipocket) +macro_log_feature(QMOBIPOCKET_FOUND "libqmobipocket" "A library for reading Mobipocket documents" "https://projects.kde.org/projects/kde/kdegraphics/kdegraphics-mobipocket" FALSE "" "Support for Mobipocket documents in Okular.") + +# let's enable the generators properly configured + +if(POPPLER_FOUND AND HAVE_POPPLER_0_12_1) + add_subdirectory(poppler) +endif(POPPLER_FOUND AND HAVE_POPPLER_0_12_1) + +if(LIBSPECTRE_FOUND) + add_subdirectory(spectre) +endif(LIBSPECTRE_FOUND) + +if(KEXIV2_FOUND) + add_subdirectory( kimgio ) +endif(KEXIV2_FOUND) + +if(CHM_FOUND) + add_subdirectory( chm ) +endif(CHM_FOUND) + +if(DJVULIBRE_FOUND) + add_subdirectory(djvu) +endif(DJVULIBRE_FOUND) + +add_subdirectory(dvi) + +if(TIFF_FOUND) + add_subdirectory(tiff) +endif(TIFF_FOUND) + +add_subdirectory(xps) + +add_subdirectory(ooo) + +add_subdirectory(fictionbook) + +add_subdirectory(comicbook) + +add_subdirectory(fax) + +if(JPEG_FOUND AND ZLIB_FOUND) + add_subdirectory(plucker) +endif(JPEG_FOUND AND ZLIB_FOUND) + +if(EPUB_FOUND) + add_subdirectory(epub) +endif(EPUB_FOUND) + +add_subdirectory(txt) + +if(QMOBIPOCKET_FOUND) + add_subdirectory(mobipocket) +endif() diff --git a/okular/generators/chm/CMakeLists.txt b/okular/generators/chm/CMakeLists.txt new file mode 100644 index 00000000..e54affca --- /dev/null +++ b/okular/generators/chm/CMakeLists.txt @@ -0,0 +1,33 @@ +add_subdirectory( kio-msits ) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CHM_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/lib +) + + +########### next target ############### + +set(okularGenerator_chmlib_SRCS + lib/lchmurlhandler.cpp + lib/libchmfile.cpp + lib/libchmfileimpl.cpp + lib/libchmfile_search.cpp + lib/libchmtextencoding.cpp + lib/libchmtocimage.cpp + generator_chm.cpp +) + +kde4_add_plugin(okularGenerator_chmlib ${okularGenerator_chmlib_SRCS}) + +target_link_libraries(okularGenerator_chmlib okularcore ${CHM_LIBRARY} ${KDE4_KHTML_LIBS} ) + +install(TARGETS okularGenerator_chmlib DESTINATION ${PLUGIN_INSTALL_DIR}) + + +########### install files ############### + +install( FILES libokularGenerator_chmlib.desktop okularChm.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +install( PROGRAMS okularApplication_chm.desktop active-documentviewer_chm.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) + diff --git a/okular/generators/chm/Messages.sh b/okular/generators/chm/Messages.sh new file mode 100644 index 00000000..f5dfd802 --- /dev/null +++ b/okular/generators/chm/Messages.sh @@ -0,0 +1,2 @@ +#!/bin/sh +$XGETTEXT $(find . -name "*.cpp" -o -name "*.h") -o $podir/okular_chm.pot diff --git a/okular/generators/chm/active-documentviewer_chm.desktop b/okular/generators/chm/active-documentviewer_chm.desktop new file mode 100644 index 00000000..dd42c9f7 --- /dev/null +++ b/okular/generators/chm/active-documentviewer_chm.desktop @@ -0,0 +1,201 @@ +[Desktop Entry] +Name=Reader +Name[ar]=التصيير +Name[bg]=Четец +Name[bs]=Čitač +Name[ca]=Lector +Name[ca@valencia]=Lector +Name[cs]=Čtečka +Name[da]=Læser +Name[de]=Lesegerät +Name[el]=Πρόγραμμα ανάγνωσης +Name[en_GB]=Reader +Name[es]=Lector +Name[et]=Lugeja +Name[fi]=Lukija +Name[fr]=Lecteur +Name[ga]=Léitheoir +Name[gl]=Lector +Name[hu]=Olvasó +Name[ia]=Lector +Name[is]=Lesari +Name[it]=Lettore +Name[kk]=Оқу құралы +Name[ko]=리더 +Name[lt]=Skaitytuvas +Name[mr]=वाचक +Name[nb]=Leser +Name[nds]=Leser +Name[nl]=Lezer +Name[pa]=ਰੀਡਰ +Name[pl]=Czytnik +Name[pt]=Leitor +Name[pt_BR]=Leitor +Name[ro]=Cititor +Name[ru]=Просмотрщик +Name[sk]=Čítačka +Name[sl]=Bralnik +Name[sr]=Читач +Name[sr@ijekavian]=Читач +Name[sr@ijekavianlatin]=Čitač +Name[sr@latin]=Čitač +Name[sv]=Läsprogram +Name[tr]=Okuyucu +Name[ug]=ئوقۇغۇ +Name[uk]=Переглядач +Name[x-test]=xxReaderxx +Name[zh_CN]=阅读器 +Name[zh_TW]=閱讀器 +GenericName=Document viewer +GenericName[ar]=عارض المستندات +GenericName[bg]=Преглед на документи +GenericName[bs]=Prikazivač dokumenata +GenericName[ca]=Visualitzador de documents +GenericName[ca@valencia]=Visualitzador de documents +GenericName[cs]=Prohlížeč dokumentů +GenericName[da]=Dokumentfremviser +GenericName[de]=Dokumentenbetrachter +GenericName[el]=Προβολέας εγγράφων +GenericName[en_GB]=Document Viewer +GenericName[es]=Visor de documentos +GenericName[et]=Dokumendinäitaja +GenericName[fi]=Asiakirjakatselin +GenericName[fr]=Afficheur de document +GenericName[ga]=Amharcán cáipéisí +GenericName[gl]=Visor de documentos +GenericName[hu]=Dokumentummegjelenítő +GenericName[ia]=Visor de documento +GenericName[is]=Skjalaskoðari +GenericName[it]=Visore di documenti +GenericName[ja]=文書ビューア +GenericName[kk]=Құжатты қарау құралы +GenericName[ko]=문서 뷰어 +GenericName[lt]=Dokumentų žiūryklė +GenericName[mr]=दस्तऐवज प्रदर्शक +GenericName[nb]=Dokumentviser +GenericName[nds]=Dokmentkieker +GenericName[nl]=Documentenviewer +GenericName[pa]=ਡੌਕੂਮੈਂਟ ਦਰਸ਼ਕ +GenericName[pl]=Przeglądarka dokumentów +GenericName[pt]=Visualizador de documentos +GenericName[pt_BR]=Visualizador de documentos +GenericName[ro]=Vizualizor de documente +GenericName[ru]=Просмотр документов +GenericName[sk]=Prehliadač dokumentov +GenericName[sl]=Pregledovalnik dokumentov +GenericName[sr]=Приказивач докумената +GenericName[sr@ijekavian]=Приказивач докумената +GenericName[sr@ijekavianlatin]=Prikazivač dokumenata +GenericName[sr@latin]=Prikazivač dokumenata +GenericName[sv]=Dokumentvisare +GenericName[tr]=Belge görüntüleyici +GenericName[uk]=Переглядач документів +GenericName[x-test]=xxDocument viewerxx +GenericName[zh_CN]=文档查看器 +GenericName[zh_TW]=文件檢視器 +Comment=Viewer for various types of documents +Comment[ar]=عارض للعديد من أنواع المستندات +Comment[bg]=Преглед на различни видове документи +Comment[bs]=Pregledač raznih vrsta dokumenata +Comment[ca]=Visualitzador de diversos tipus de documents +Comment[ca@valencia]=Visualitzador de diversos tipus de documents +Comment[cs]=Prohlížeč různých typů dokumentů +Comment[da]=Fremviser af diverse dokumenttyper +Comment[de]=Betrachter für verschiedene Arten von Dokumenten +Comment[el]=Πρόγραμμα προβολής για διάφορους τύπους εγγράφων +Comment[en_GB]=Viewer for various types of documents +Comment[es]=Visor de diversos tipos de documentos +Comment[et]=Eri tüüpi dokumentide näitaja +Comment[fi]=Monenlaisten asiakirjojen katseluohjelma +Comment[fr]=Afficheur pour différents types de documents +Comment[ga]=Amharcán le haghaidh cáipéisí éagsúla +Comment[gl]=Visor de varios tipos de documentos. +Comment[hu]=Megjelenítő különféle típusú dokumentumokhoz +Comment[ia]=Visor pro varie typos de documento +Comment[is]=Skoðari fyrir ýmsar gerðir skjala +Comment[it]=Visore per vari tipi di documenti +Comment[kk]=Түрлі құжаттар қарау құралы +Comment[ko]=여러 형식의 문서 뷰어 +Comment[lt]=Žiūryklė įvairiems dokumentų tipams +Comment[mr]=विविध प्रकारच्या दस्तऐवजांचा प्रदर्शक +Comment[nb]=Framviser for forskjellige dokumenttyper +Comment[nds]=Kieker för en Reeg Dokmenttypen +Comment[nl]=Viewer voor verschillende typen documenten +Comment[pa]=ਕਈ ਕਿਸਮ ਦੇ ਡੌਕੂਮੈਂਟ ਵੇਖਾਉਣ ਲਈ ਦਰਸ਼ਕ +Comment[pl]=Przeglądarka dla różnych typów dokumentów +Comment[pt]=Visualizador de vários tipos de documentos +Comment[pt_BR]=Visualizador para vários tipos de documentos +Comment[ro]=Vizualizor pentru diferite tipuri de documente +Comment[ru]=Программа для просмотра различных типов документов +Comment[sk]=Prehliadač pre rôzne typy dokumentov +Comment[sl]=Pregledovalnik raznih vrst dokumentov +Comment[sr]=Приказивач различитих врста докумената +Comment[sr@ijekavian]=Приказивач различитих врста докумената +Comment[sr@ijekavianlatin]=Prikazivač različitih vrsta dokumenata +Comment[sr@latin]=Prikazivač različitih vrsta dokumenata +Comment[sv]=Visningsprogram för diverse typer av dokument +Comment[tr]=Çeşitli belge türü için görüntüleyici +Comment[ug]=ھەر خىل تىپتىكى پۈتۈكلەرنى كۆرىدىغان پروگرامما +Comment[uk]=Програма для перегляду документів різних типів +Comment[x-test]=xxViewer for various types of documentsxx +Comment[zh_CN]=可以查看多种文档的工具 +Comment[zh_TW]=多種型態文件的檢視器 + +TryExec=active-documentviewer +Exec=active-documentviewer %u +Terminal=false +Icon=okular +Type=Application +Categories=Qt;KDE;Graphics;Office;Viewer; +InitialPreference=2 +NoDisplay=true +MimeType=application/x-chm; +X-KDE-Keywords=chm +X-KDE-Keywords[ar]=chm +X-KDE-Keywords[bg]=chm +X-KDE-Keywords[bs]=chm +X-KDE-Keywords[ca]=chm +X-KDE-Keywords[ca@valencia]=chm +X-KDE-Keywords[cs]=chm +X-KDE-Keywords[da]=chm +X-KDE-Keywords[de]=chm +X-KDE-Keywords[el]=chm +X-KDE-Keywords[en_GB]=chm +X-KDE-Keywords[es]=chm +X-KDE-Keywords[et]=chm +X-KDE-Keywords[fi]=chm +X-KDE-Keywords[fr]=chm +X-KDE-Keywords[ga]=chm +X-KDE-Keywords[gl]=chm +X-KDE-Keywords[hu]=chm +X-KDE-Keywords[ia]=chm +X-KDE-Keywords[is]=chm +X-KDE-Keywords[it]=chm +X-KDE-Keywords[ja]=chm +X-KDE-Keywords[kk]=chm +X-KDE-Keywords[km]=chm +X-KDE-Keywords[ko]=chm +X-KDE-Keywords[lt]=chm +X-KDE-Keywords[lv]=chm +X-KDE-Keywords[mr]=chm +X-KDE-Keywords[nb]=chm +X-KDE-Keywords[nds]=CHM +X-KDE-Keywords[nl]=chm +X-KDE-Keywords[pa]=chm +X-KDE-Keywords[pl]=chm +X-KDE-Keywords[pt]=chm +X-KDE-Keywords[pt_BR]=chm +X-KDE-Keywords[ro]=chm +X-KDE-Keywords[ru]=chm +X-KDE-Keywords[sk]=chm +X-KDE-Keywords[sl]=chm +X-KDE-Keywords[sr]=chm,ЦХМ +X-KDE-Keywords[sr@ijekavian]=chm,ЦХМ +X-KDE-Keywords[sr@ijekavianlatin]=chm,CHM +X-KDE-Keywords[sr@latin]=chm,CHM +X-KDE-Keywords[sv]=chm +X-KDE-Keywords[tr]=chm +X-KDE-Keywords[uk]=chm +X-KDE-Keywords[x-test]=xxchmxx +X-KDE-Keywords[zh_CN]=chm +X-KDE-Keywords[zh_TW]=chm diff --git a/okular/generators/chm/generator_chm.cpp b/okular/generators/chm/generator_chm.cpp new file mode 100644 index 00000000..84b9dde7 --- /dev/null +++ b/okular/generators/chm/generator_chm.cpp @@ -0,0 +1,469 @@ +/*************************************************************************** + * Copyright (C) 2005 by Piotr Szymański * + * Copyright (C) 2008 by Albert Astals Cid * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#include "generator_chm.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static KAboutData createAboutData() +{ + KAboutData aboutData( + "okular_chm", + "okular_chm", + ki18n( "CHM Backend" ), + "0.1.4", + ki18n( "A Microsoft Windows help file renderer" ), + KAboutData::License_GPL, + ki18n( "© 2005-2007 Piotr Szymański\n© 2008 Albert Astals Cid" ) + ); + aboutData.addAuthor( ki18n( "Piotr Szymański" ), KLocalizedString(), "niedakh@gmail.com" ); + aboutData.addAuthor( ki18n( "Albert Astals Cid" ), KLocalizedString(), "aacid@kde.org" ); + return aboutData; +} + +OKULAR_EXPORT_PLUGIN( CHMGenerator, createAboutData() ) + +static QString absolutePath( const QString &baseUrl, const QString &path ) +{ + QString absPath; + if ( path.at( 0 ) == QLatin1Char( '/' ) ) + { + // already absolute + absPath = path; + } + else + { + KUrl url = KUrl::fromPath( baseUrl ); + url.setFileName( path ); + absPath = url.toLocalFile(); + } + return absPath; +} + +CHMGenerator::CHMGenerator( QObject *parent, const QVariantList &args ) + : Okular::Generator( parent, args ) +{ + setFeature( TextExtraction ); + + m_syncGen=0; + m_file=0; + m_docInfo=0; + m_pixmapRequestZoom=1; + m_request = 0; +} + +CHMGenerator::~CHMGenerator() +{ + delete m_syncGen; +} + +bool CHMGenerator::loadDocument( const QString & fileName, QVector< Okular::Page * > & pagesVector ) +{ + m_file = new LCHMFile(); + if (!m_file->loadFile(fileName)) + { + delete m_file; + m_file = 0; + return false; + } + m_fileName=fileName; + QVector< LCHMParsedEntry > topics; + m_file->parseTableOfContents(&topics); + + // fill m_docSyn + QMap lastIndentElement; + foreach(const LCHMParsedEntry &e, topics) + { + QDomElement item = m_docSyn.createElement(e.name); + if (!e.urls.isEmpty()) + { + QString url = e.urls.first(); + if (url.contains(QChar::fromLatin1('%'))) + url = QString::fromUtf8(QByteArray::fromPercentEncoding(url.toUtf8())); + item.setAttribute("ViewportName", url); + } + item.setAttribute("Icon", e.imageid); + if (e.indent == 0) m_docSyn.appendChild(item); + else lastIndentElement[e.indent - 1].appendChild(item); + lastIndentElement[e.indent] = item; + } + + // fill m_urlPage and m_pageUrl + int pageNum = 0; + QStringList pageList; + m_file->enumerateFiles(&pageList); + const QString home = m_file->homeUrl(); + if (home != QLatin1String("/")) + pageList.prepend(home); + foreach (const QString &url, pageList) + { + const QString urlLower = url.toLower(); + if (!urlLower.endsWith(QLatin1String(".html")) && !urlLower.endsWith(QLatin1String(".htm"))) + continue; + + int pos = url.indexOf ('#'); + QString tmpUrl = pos == -1 ? url : url.left(pos); + + // url already there, abort insertion + if (m_urlPage.contains(tmpUrl)) continue; + + // insert the url into the maps, but insert always the variant without the #ref part + m_urlPage.insert(tmpUrl, pageNum); + m_pageUrl.append(tmpUrl); + pageNum++; + } + + pagesVector.resize(m_pageUrl.count()); + m_textpageAddedList.fill(false, pagesVector.count()); + m_rectsGenerated.fill(false, pagesVector.count()); + + if (!m_syncGen) + { + m_syncGen = new KHTMLPart(); + } + disconnect( m_syncGen, 0, this, 0 ); + + for (int i = 0; i < m_pageUrl.count(); ++i) + { + preparePageForSyncOperation(100, m_pageUrl.at(i)); + pagesVector[ i ] = new Okular::Page (i, m_syncGen->view()->contentsWidth(), + m_syncGen->view()->contentsHeight(), Okular::Rotation0 ); + } + + connect( m_syncGen, SIGNAL(completed()), this, SLOT(slotCompleted()) ); + connect( m_syncGen, SIGNAL(canceled(QString)), this, SLOT(slotCompleted()) ); + + return true; +} + +bool CHMGenerator::doCloseDocument() +{ + // delete the document information of the old document + delete m_docInfo; + m_docInfo=0; + delete m_file; + m_file=0; + m_textpageAddedList.clear(); + m_rectsGenerated.clear(); + m_urlPage.clear(); + m_pageUrl.clear(); + m_docSyn.clear(); + if (m_syncGen) + { + m_syncGen->closeUrl(); + } + + return true; +} + +void CHMGenerator::preparePageForSyncOperation( int zoom , const QString & url) +{ + KUrl pAddress= QString("ms-its:" + m_fileName + "::" + url); + m_chmUrl = url; + m_syncGen->setZoomFactor(zoom); + m_syncGen->openUrl(pAddress); + m_syncGen->view()->layout(); + + QEventLoop loop; + connect( m_syncGen, SIGNAL(completed()), &loop, SLOT(quit()) ); + connect( m_syncGen, SIGNAL(canceled(QString)), &loop, SLOT(quit()) ); + // discard any user input, otherwise it breaks the "synchronicity" of this + // function + loop.exec( QEventLoop::ExcludeUserInputEvents ); +} + +void CHMGenerator::slotCompleted() +{ + if ( !m_request ) + return; + + QImage image( m_request->width(), m_request->height(), QImage::Format_ARGB32 ); + image.fill( qRgb( 255, 255, 255 ) ); + + QPainter p( &image ); + QRect r( 0, 0, m_request->width(), m_request->height() ); + + bool moreToPaint; + m_syncGen->paint( &p, r, 0, &moreToPaint ); + + p.end(); + + if ( m_pixmapRequestZoom > 1 ) + m_pixmapRequestZoom = 1; + + if ( !m_textpageAddedList.at( m_request->pageNumber() ) ) { + additionalRequestData(); + m_textpageAddedList[ m_request->pageNumber() ] = true; + } + + m_syncGen->closeUrl(); + m_chmUrl = QString(); + + userMutex()->unlock(); + + Okular::PixmapRequest *req = m_request; + m_request = 0; + + if ( !req->page()->isBoundingBoxKnown() ) + updatePageBoundingBox( req->page()->number(), Okular::Utils::imageBoundingBox( &image ) ); + req->page()->setPixmap( req->observer(), new QPixmap( QPixmap::fromImage( image ) ) ); + signalPixmapRequestDone( req ); +} + +const Okular::DocumentInfo * CHMGenerator::generateDocumentInfo() +{ + if (!m_docInfo) + { + m_docInfo=new Okular::DocumentInfo(); + + m_docInfo->set( Okular::DocumentInfo::MimeType, "application/x-chm" ); + m_docInfo->set( Okular::DocumentInfo::Title, m_file->title() ); + } + return m_docInfo; +} + +const Okular::DocumentSynopsis * CHMGenerator::generateDocumentSynopsis() +{ + return &m_docSyn; +} + +bool CHMGenerator::canGeneratePixmap () const +{ + bool isLocked = true; + if ( userMutex()->tryLock() ) { + userMutex()->unlock(); + isLocked = false; + } + + return !isLocked; +} + +void CHMGenerator::generatePixmap( Okular::PixmapRequest * request ) +{ + int requestWidth = request->width(); + int requestHeight = request->height(); + if (requestWidth<300) + { + m_pixmapRequestZoom=900/requestWidth; + requestWidth*=m_pixmapRequestZoom; + requestHeight*=m_pixmapRequestZoom; + } + + userMutex()->lock(); + QString url= m_pageUrl[request->pageNumber()]; + int zoom = qRound( qMax( static_cast(requestWidth)/static_cast(request->page()->width()) + , static_cast(requestHeight)/static_cast(request->page()->height()) + ) ) * 100; + + KUrl pAddress= QString("ms-its:" + m_fileName + "::" + url); + m_chmUrl = url; + m_syncGen->setZoomFactor(zoom); + m_syncGen->view()->resize(requestWidth,requestHeight); + m_request=request; + // will emit openURL without problems + m_syncGen->openUrl ( pAddress ); +} + + +void CHMGenerator::recursiveExploreNodes(DOM::Node node,Okular::TextPage *tp) +{ + if (node.nodeType() == DOM::Node::TEXT_NODE && !node.getRect().isNull()) + { + QString nodeText=node.nodeValue().string(); + QRect r=node.getRect(); + int vWidth=m_syncGen->view()->width(); + int vHeight=m_syncGen->view()->height(); + Okular::NormalizedRect *nodeNormRect; +#define NOEXP +#ifndef NOEXP + int x,y,height; + int x_next,y_next,height_next; + int nodeTextLength = nodeText.length(); + if (nodeTextLength==1) + { + nodeNormRect=new Okular::NormalizedRect (r,vWidth,vHeight); + tp->append(nodeText,nodeNormRect,nodeNormRect->bottom,0,(nodeText=="\n")); + } + else + { + for (int i=0;iappend(nodeText,nodeNormRect/*,0*/); +#endif + } + DOM::Node child = node.firstChild(); + while ( !child.isNull() ) + { + recursiveExploreNodes(child,tp); + child = child.nextSibling(); + } +} + +void CHMGenerator::additionalRequestData() +{ + Okular::Page * page=m_request->page(); + const bool genObjectRects = !m_rectsGenerated.at( m_request->page()->number() ); + const bool genTextPage = !m_request->page()->hasTextPage() && genObjectRects; + + if (genObjectRects || genTextPage ) + { + DOM::HTMLDocument domDoc=m_syncGen->htmlDocument(); + // only generate object info when generating a full page not a thumbnail + if ( genObjectRects ) + { + QLinkedList< Okular::ObjectRect * > objRects; + int xScale=m_syncGen->view()->width(); + int yScale=m_syncGen->view()->height(); + // getting links + DOM::HTMLCollection coll=domDoc.links(); + DOM::Node n; + QRect r; + if (! coll.isNull() ) + { + int size=coll.length(); + for(int i=0;ipage()->setObjectRects( objRects ); + m_rectsGenerated[ m_request->page()->number() ] = true; + } + + if ( genTextPage ) + { + Okular::TextPage *tp=new Okular::TextPage(); + recursiveExploreNodes(domDoc,tp); + page->setTextPage (tp); + } + } +} + +Okular::TextPage* CHMGenerator::textPage( Okular::Page * page ) +{ + userMutex()->lock(); + const int zoom = 100; + m_syncGen->view()->resize(page->width(), page->height()); + + preparePageForSyncOperation(zoom, m_pageUrl[page->number()]); + Okular::TextPage *tp=new Okular::TextPage(); + recursiveExploreNodes( m_syncGen->htmlDocument(), tp); + userMutex()->unlock(); + return tp; +} + +QVariant CHMGenerator::metaData( const QString &key, const QVariant &option ) const +{ + if ( key == "NamedViewport" && !option.toString().isEmpty() ) + { + const int pos = option.toString().indexOf('#'); + QString tmpUrl = pos == -1 ? option.toString() : option.toString().left(pos); + Okular::DocumentViewport viewport; + QMap::const_iterator it = m_urlPage.find(tmpUrl); + if (it != m_urlPage.end()) + { + viewport.pageNumber = it.value(); + return viewport.toString(); + } + + } + else if ( key == "DocumentTitle" ) + { + return m_file->title(); + } + return QVariant(); +} + +/* kate: replace-tabs on; tab-width 4; */ + +#include "generator_chm.moc" diff --git a/okular/generators/chm/generator_chm.h b/okular/generators/chm/generator_chm.h new file mode 100644 index 00000000..5d970097 --- /dev/null +++ b/okular/generators/chm/generator_chm.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * Copyright (C) 2005 by Piotr Szymański * + * Copyright (C) 2008 by Albert Astals Cid * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ + +#ifndef _OKULAR_CHMGENERATOR_H_ +#define _OKULAR_CHMGENERATOR_H_ + +#include +#include + +#include "lib/libchmfile.h" + +#include + +class KHTMLPart; + +namespace Okular { +class TextPage; +} + +namespace DOM { +class Node; +} + +class CHMGenerator : public Okular::Generator +{ + Q_OBJECT + public: + CHMGenerator( QObject *parent, const QVariantList &args ); + ~CHMGenerator(); + bool loadDocument( const QString & fileName, QVector< Okular::Page * > & pagesVector ); + + const Okular::DocumentInfo * generateDocumentInfo(); + const Okular::DocumentSynopsis * generateDocumentSynopsis(); + + bool canGeneratePixmap() const; + void generatePixmap( Okular::PixmapRequest * request ); + + QVariant metaData( const QString & key, const QVariant & option ) const; + + public slots: + void slotCompleted(); + + protected: + bool doCloseDocument(); + Okular::TextPage* textPage( Okular::Page *page ); + + private: + void additionalRequestData(); + void recursiveExploreNodes( DOM::Node node, Okular::TextPage *tp ); + void preparePageForSyncOperation( int zoom , const QString &url ); + QMap m_urlPage; + QVector m_pageUrl; + Okular::DocumentSynopsis m_docSyn; + LCHMFile* m_file; + KHTMLPart *m_syncGen; + QString m_fileName; + QString m_chmUrl; + Okular::PixmapRequest* m_request; + int m_pixmapRequestZoom; + Okular::DocumentInfo* m_docInfo; + QBitArray m_textpageAddedList; + QBitArray m_rectsGenerated; +}; + +#endif diff --git a/okular/generators/chm/kio-msits/CMakeLists.txt b/okular/generators/chm/kio-msits/CMakeLists.txt new file mode 100644 index 00000000..5b50c878 --- /dev/null +++ b/okular/generators/chm/kio-msits/CMakeLists.txt @@ -0,0 +1,23 @@ +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../lib + ${CHM_INCLUDE_DIR} +) + + +########### next target ############### + +set(kio_msits_PART_SRCS msits.cpp ) + + +kde4_add_plugin(kio_msits ${kio_msits_PART_SRCS}) + +target_link_libraries(kio_msits ${KDE4_KIO_LIBS} ${CHM_LIBRARY} ) + +install(TARGETS kio_msits DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES msits.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + diff --git a/okular/generators/chm/kio-msits/msits.cpp b/okular/generators/chm/kio-msits/msits.cpp new file mode 100644 index 00000000..23a3fce4 --- /dev/null +++ b/okular/generators/chm/kio-msits/msits.cpp @@ -0,0 +1,309 @@ +/* This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "msits.h" +#include "libchmurlfactory.h" + +using namespace KIO; + +extern "C" +{ + int KDE_EXPORT kdemain( int argc, char **argv ) + { + kDebug() << "*** kio_msits Init"; + + KComponentData instance( "kio_msits" ); + + if ( argc != 4 ) + { + kDebug() << "Usage: kio_msits protocol domain-socket1 domain-socket2"; + exit (-1); + } + + ProtocolMSITS slave ( argv[2], argv[3] ); + slave.dispatchLoop(); + + kDebug() << "*** kio_msits Done"; + return 0; + } +} + +ProtocolMSITS::ProtocolMSITS (const QByteArray &pool_socket, const QByteArray &app_socket) + : SlaveBase ("kio_msits", pool_socket, app_socket) +{ + m_chmFile = 0; +} + +ProtocolMSITS::~ProtocolMSITS() +{ + if ( !m_chmFile ) + return; + + chm_close (m_chmFile); + m_chmFile = 0; +} + +// A simple stat() wrapper +static bool isDirectory ( const QString & filename ) +{ + return filename.endsWith( '/' ); +} + + +void ProtocolMSITS::get( const KUrl& url ) +{ + QString htmdata, fileName; + chmUnitInfo ui; + QByteArray buf; + + kDebug() << "kio_msits::get() " << url.path(); + + if ( !parseLoadAndLookup ( url, fileName ) ) + return; // error() has been called by parseLoadAndLookup + + kDebug() << "kio_msits::get: parseLoadAndLookup returned " << fileName; + + if ( LCHMUrlFactory::handleFileType( url.path(), htmdata ) ) + { + buf = htmdata.toUtf8(); + kDebug() << "Using special handling for image pages: " << htmdata; + } + else + { + if ( isDirectory (fileName) ) + { + error( KIO::ERR_IS_DIRECTORY, url.prettyUrl() ); + return; + } + + if ( !ResolveObject ( fileName, &ui) ) + { + kDebug() << "kio_msits::get: could not resolve filename " << fileName; + error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() ); + return; + } + + buf.resize( ui.length ); + + if ( RetrieveObject (&ui, (unsigned char*) buf.data(), 0, ui.length) == 0 ) + { + kDebug() << "kio_msits::get: could not retrieve filename " << fileName; + error( KIO::ERR_NO_CONTENT, url.prettyUrl() ); + return; + } + } + + totalSize( buf.size() ); + KMimeType::Ptr result = KMimeType::findByNameAndContent( fileName, buf ); + kDebug() << "Emitting mimetype " << result->name(); + + mimeType( result->name() ); + data( buf ); + processedSize( buf.size() ); + + finished(); +} + + +bool ProtocolMSITS::parseLoadAndLookup ( const KUrl& url, QString& abspath ) +{ + kDebug() << "ProtocolMSITS::parseLoadAndLookup (const KUrl&) " << url.path(); + + int pos = url.path().indexOf ("::"); + + if ( pos == -1 ) + { + error( KIO::ERR_MALFORMED_URL, url.prettyUrl() ); + return false; + } + + QString filename = url.path().left (pos); + abspath = url.path().mid (pos + 2); // skip :: + + // Some buggy apps add ms-its:/ to the path as well + if ( abspath.startsWith( "ms-its:" ) ) + abspath = abspath.mid( 7 ); + + kDebug() << "ProtocolMSITS::parseLoadAndLookup: filename " << filename << ", path " << abspath; + + if ( filename.isEmpty() ) + { + error( KIO::ERR_MALFORMED_URL, url.prettyUrl() ); + return false; + } + + // If the file has been already loaded, nothing to do. + if ( m_chmFile && filename == m_openedFile ) + return true; + + kDebug() << "Opening a new CHM file " << QFile::encodeName( QDir::toNativeSeparators( filename ) ); + + // First try to open a temporary file + chmFile * tmpchm; + + if( (tmpchm = chm_open ( QFile::encodeName( QDir::toNativeSeparators( filename) ) ) ) == 0 ) + { + error( KIO::ERR_COULD_NOT_READ, url.prettyUrl() ); + return false; + } + + // Replace an existing file by a new one + if ( m_chmFile ) + chm_close (m_chmFile); + + m_chmFile = tmpchm; + m_openedFile = filename; + + kDebug() << "A CHM file " << filename << " has beed opened successfully"; + return true; +} + +/* + * Shamelessly stolen from a KDE KIO tutorial + */ +static void app_entry(UDSEntry& e, unsigned int uds, const QString& str) +{ + e.insert(uds, str); +} + + // appends an int with the UDS-ID uds + static void app_entry(UDSEntry& e, unsigned int uds, long l) + { + e.insert(uds, l); +} + +// internal function +// fills a directory item with its name and size +static void app_dir(UDSEntry& e, const QString & name) +{ + e.clear(); + app_entry(e, KIO::UDSEntry::UDS_NAME, name); + app_entry(e, KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + app_entry(e, KIO::UDSEntry::UDS_SIZE, 1); +} + +// internal function +// fills a file item with its name and size +static void app_file(UDSEntry& e, const QString & name, size_t size) +{ + e.clear(); + app_entry(e, KIO::UDSEntry::UDS_NAME, name); + app_entry(e, KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + app_entry(e, KIO::UDSEntry::UDS_SIZE, size); +} + +void ProtocolMSITS::stat (const KUrl & url) +{ + QString fileName; + chmUnitInfo ui; + + kDebug() << "kio_msits::stat (const KUrl& url) " << url.path(); + + if ( !parseLoadAndLookup ( url, fileName ) ) + return; // error() has been called by parseLoadAndLookup + + if ( !ResolveObject ( fileName, &ui ) ) + { + error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() ); + return; + } + + kDebug() << "kio_msits::stat: adding an entry for " << fileName; + UDSEntry entry; + + if ( isDirectory ( fileName ) ) + app_dir(entry, fileName); + else + app_file(entry, fileName, ui.length); + + statEntry (entry); + + finished(); +} + + +// A local CHMLIB enumerator +static int chmlib_enumerator (struct chmFile *, struct chmUnitInfo *ui, void *context) +{ + ((QVector *) context)->push_back (QString::fromLocal8Bit (ui->path)); + return CHM_ENUMERATOR_CONTINUE; +} + + +void ProtocolMSITS::listDir (const KUrl & url) +{ + QString filepath; + + kDebug() << "kio_msits::listDir (const KUrl& url) " << url.path(); + + if ( !parseLoadAndLookup ( url, filepath ) ) + return; // error() has been called by parseLoadAndLookup + + filepath += '/'; + + if ( !isDirectory (filepath) ) + { + error(KIO::ERR_CANNOT_ENTER_DIRECTORY, url.path()); + return; + } + + kDebug() << "kio_msits::listDir: enumerating directory " << filepath; + + QVector listing; + + if ( chm_enumerate_dir ( m_chmFile, + filepath.toLocal8Bit(), + CHM_ENUMERATE_NORMAL | CHM_ENUMERATE_FILES | CHM_ENUMERATE_DIRS, + chmlib_enumerator, + &listing ) != 1 ) + { + error(KIO::ERR_CANNOT_ENTER_DIRECTORY, url.path()); + return; + } + + UDSEntry entry; + int striplength = filepath.length(); + + for ( int i = 0; i < listing.size(); i++ ) + { + // Strip the direcroty name + QString ename = listing[i].mid (striplength); + + if ( isDirectory ( ename ) ) + app_dir(entry, ename); + else + app_file(entry, ename, 0); + + listEntry(entry, false); + } + + listEntry(entry, true); + finished(); +} diff --git a/okular/generators/chm/kio-msits/msits.h b/okular/generators/chm/kio-msits/msits.h new file mode 100644 index 00000000..4d147cf2 --- /dev/null +++ b/okular/generators/chm/kio-msits/msits.h @@ -0,0 +1,71 @@ +/*************************************************************************** + * Copyright (C) 2005 by Georgy Yunaev * + * tim@krasnogorsk.ru * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef MSITS_H +#define MSITS_H + + +#include +#include + +#include +#include + +#include "chm_lib.h" + + +class ProtocolMSITS : public KIO::SlaveBase +{ +public: + ProtocolMSITS ( const QByteArray&, const QByteArray& ); + virtual ~ProtocolMSITS(); + + virtual void get ( const KUrl& ); + virtual void listDir (const KUrl & url); + virtual void stat (const KUrl & url); + +private: + // This function does next thing: + // - parses the URL to get a file name and URL inside the file; + // - loads a new CHM file, if needed; + // - returns the parsed URL inside the file; + bool parseLoadAndLookup ( const KUrl&, QString& abspath ); + + // Resolve an object inside a CHM file + inline bool ResolveObject (const QString& fileName, chmUnitInfo *ui) + { + return m_chmFile != NULL && ::chm_resolve_object(m_chmFile, fileName.toUtf8().constData(), ui) == CHM_RESOLVE_SUCCESS; + } + + // Retrieve an object from the CHM file + inline size_t RetrieveObject (chmUnitInfo *ui, unsigned char *buffer, LONGUINT64 fileOffset, LONGINT64 bufferSize) + { + return ::chm_retrieve_object(m_chmFile, ui, buffer, fileOffset, bufferSize); + } + + // An opened file name, if presend + QString m_openedFile; + + // a CHM structure file pointer (from chmlib) + chmFile * m_chmFile; +}; + + +#endif /* MSITS_H */ diff --git a/okular/generators/chm/kio-msits/msits.protocol b/okular/generators/chm/kio-msits/msits.protocol new file mode 100644 index 00000000..1b1b2867 --- /dev/null +++ b/okular/generators/chm/kio-msits/msits.protocol @@ -0,0 +1,70 @@ +[Protocol] +exec=kio_msits +protocol=ms-its +input=none +output=filesystem +reading=true +listing=Name,Type,Size +defaultMimetype=text/html +Description=A kioslave for displaying WinHelp files +Description[ar]=خاصية كِييُوسليْف لعرض ملفات WinHelp +Description[ast]=Un «kioslave» p'amosar ficheros WinHelp +Description[bg]=kioslave за показване на помощни файлове за Windows +Description[bs]=U/I alat za prikaz WinHelp datoteke +Description[ca]=Un kioslave per visualitzar els fitxers WinHelp +Description[ca@valencia]=Un kioslave per visualitzar els fitxers WinHelp +Description[cs]=Kioslave pro zobrazení souborů WinHelp +Description[da]=En kioslave til visning af WinHelp-filer +Description[de]=Ein-/Ausgabemodul zur Anzeige von Windows-Hilfe-Dateien +Description[el]=Ένα kioslave για την εμφάνιση αρχείων WinHelp +Description[en_GB]=A kioslave for displaying WinHelp files +Description[eo]=Kio-sklavo por montri la WinHelp dosierojn +Description[es]=Un «kioslave» para mostrar archivos WinHelp +Description[et]=KIO-moodul WinHelp-failide näitamiseks +Description[eu]=WinHelp fitxategiak bistaratzeko kioslave bat +Description[fi]=WinHelp-tiedostoja näyttävä kioslave +Description[fr]=Module d'entrées / sorties pour l'affichage des fichiers d'aide « Win Help » +Description[ga]=Sclábhaí KIO a thaispeánann comhaid WinHelp +Description[gl]=Un kioslave para mostrar ficheiros WinHelp +Description[hi]=विन-हेल्प फ़ाइलों को प्रदर्शित करने के लिए एक केआईओस्लेव +Description[hne]=विन-हेल्प फाइल मन ल प्रदर्सित करे बर एक केआईओस्लेव +Description[hr]=kioslave za prikaz datoteka oblika WinHelp +Description[hu]=KDE-protokoll Windows súgófájlok megtekintéséhez +Description[ia]=Un kioslave pro monstrar files de WinHelp +Description[is]=Kioslave tilað sýna WinHelp skrár +Description[it]=Un kioslave per visualizzare i file della guida di windows +Description[ja]=WinHelp ファイルを表示するための kioslave +Description[kk]=WinHelp файлдарын оқитын kioslave +Description[km]=kioslave សម្រាប់​បង្ហាញ​ឯកសារ WinHelp +Description[ko]=WinHelp 파일을 표시하기 위한 KIO 슬레이브 +Description[ku]=Kioslave ji bo nîşandana pelên WinHelp +Description[lt]=Pagalbinė programa WinHelp failų rodymui +Description[lv]=KIO apstrādātājs WinHelp failu attēlošanai +Description[mr]=WinHelp फाईल्स दर्शविण्याकरिता किओस्लेव्ह +Description[nb]=En kioslave som viser WinHelp-filer +Description[nds]=En In-/Utgaavmoduul för't Wiesen vun "WinHelp"-Dateien +Description[ne]=विनहेल्प फाइल प्रदर्शन गर्नका लागि कियोस्लाभ +Description[nl]=Een kioslave voor het weergeven van WinHelp-bestanden +Description[nn]=Ein kioslave for vising av WinHelp-filer +Description[pa]=WinHelp ਫਾਇਲਾਂ ਵੇਖਾਉਣ ਲਈ kioslave +Description[pl]=Moduł wyświetlający pliki WinHelp +Description[pt]=Um 'kioslave' para mostrar ficheiros do WinHelp +Description[pt_BR]=Um kioslave para mostrar arquivos WinHelp +Description[ro]=Sclav KIO pentru afișarea fișierelor WinHelp +Description[ru]=Библиотека просмотра файлов WinHelp +Description[sk]=Kioslave pre zobrazenie WinHelp súborov +Description[sl]=KIOSlave za prikaz datotek s pomočjo za Windows +Description[sq]=Një kioslave për të shfaqur skedarët WinHelp +Description[sr]=У/И захват за приказ винхелп фајлова +Description[sr@ijekavian]=У/И захват за приказ винхелп фајлова +Description[sr@ijekavianlatin]=U/I zahvat za prikaz WinHelp fajlova +Description[sr@latin]=U/I zahvat za prikaz WinHelp fajlova +Description[sv]=En I/O-slav för att visa WinHelp-filer +Description[th]=ส่วนเสริม kioslave สำหรับแสดงแฟ้ม WinHelp +Description[tr]=WinHelp dosyalarını göstermek için bir kioslave +Description[uk]=Підлеглий В/В для перегляду файлів WinHelp +Description[vi]=Dịch vụ trợ giúp đọc tập tin CHM của Windows +Description[x-test]=xxA kioslave for displaying WinHelp filesxx +Description[zh_CN]=用以显示 WinHelp 文件的 kioslave +Description[zh_TW]=顯示 WinHelp 檔的 kioslave +Icon=help diff --git a/okular/generators/chm/lib/bitfiddle.h b/okular/generators/chm/lib/bitfiddle.h new file mode 100644 index 00000000..eb15b0fa --- /dev/null +++ b/okular/generators/chm/lib/bitfiddle.h @@ -0,0 +1,157 @@ +/* + + Copyright (C) 2003 Razvan Cojocaru + Most of the code in this file is a modified version of code from + Pabs' GPL chmdeco project, credits and thanks go to him. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#include + +#define UINT16ARRAY(x) ((unsigned char)(x)[0] | ((uint16_t)(x)[1] << 8)) +#define UINT32ARRAY(x) (UINT16ARRAY(x) | ((uint32_t)(x)[2] << 16) \ + | ((uint32_t)(x)[3] << 24)) + +inline unsigned int get_int32_le( void *addr) +{ + unsigned char *p = (unsigned char*) addr; + return (unsigned int) ( p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24) ); +} + + +inline uint64_t be_encint(unsigned char* buffer, size_t& length) +{ + uint64_t result = 0; + int shift=0; + length = 0; + + do { + result |= ((*buffer) & 0x7f) << shift; + shift += 7; + ++length; + + } while (*(buffer++) & 0x80); + + return result; +} + + +/* + Finds the first unset bit in memory. Returns the number of set bits found. + Returns -1 if the buffer runs out before we find an unset bit. +*/ +inline int ffus(unsigned char* byte, int* bit, size_t& length) +{ + int bits = 0; + length = 0; + + while(*byte & (1 << *bit)){ + if(*bit) + --(*bit); + else { + ++byte; + ++length; + *bit = 7; + } + ++bits; + } + + if(*bit) + --(*bit); + else { + ++length; + *bit = 7; + } + + return bits; +} + + +inline uint64_t sr_int(unsigned char* byte, int* bit, + unsigned char s, unsigned char r, size_t& length) +{ + uint64_t ret; + unsigned char mask; + int n, n_bits, num_bits, base, count; + length = 0; + size_t fflen; + + if(!bit || *bit > 7 || s != 2) + return ~(uint64_t)0; + ret = 0; + + count = ffus(byte, bit, fflen); + length += fflen; + byte += length; + + n_bits = n = r + (count ? count-1 : 0) ; + + while(n > 0) { + num_bits = n > *bit ? *bit : n-1; + base = n > *bit ? 0 : *bit - (n-1); + + switch(num_bits){ + case 0: + mask = 1; + break; + case 1: + mask = 3; + break; + case 2: + mask = 7; + break; + case 3: + mask = 0xf; + break; + case 4: + mask = 0x1f; + break; + case 5: + mask = 0x3f; + break; + case 6: + mask = 0x7f; + break; + case 7: + mask = 0xff; + break; + default: + mask = 0xff; + break; + } + + mask <<= base; + ret = (ret << (num_bits+1)) | + (uint64_t)((*byte & mask) >> base); + + if( n > *bit ){ + ++byte; + ++length; + n -= *bit+1; + *bit = 7; + } else { + *bit -= n; + n = 0; + } + } + + if(count) + ret |= (uint64_t)1 << n_bits; + + return ret; +} diff --git a/okular/generators/chm/lib/lchmurlhandler.cpp b/okular/generators/chm/lib/lchmurlhandler.cpp new file mode 100644 index 00000000..9d98d87e --- /dev/null +++ b/okular/generators/chm/lib/lchmurlhandler.cpp @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright (C) 2004-2005 by Georgy Yunaev, gyunaev@ulduzsoft.com * + * Please do not use email address above for bug reports; see * + * the README file * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#include "lchmurlhandler.h" + +LCHMUrlHandler::LCHMUrlHandler() +{ +} + + +LCHMUrlHandler::~LCHMUrlHandler() +{ +} + + diff --git a/okular/generators/chm/lib/lchmurlhandler.h b/okular/generators/chm/lib/lchmurlhandler.h new file mode 100644 index 00000000..35133c41 --- /dev/null +++ b/okular/generators/chm/lib/lchmurlhandler.h @@ -0,0 +1,36 @@ +/*************************************************************************** + * Copyright (C) 2004-2007 by Georgy Yunaev, gyunaev@ulduzsoft.com * + * Please do not use email address above for bug reports; see * + * the README file * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef LCHMURLHANDLER_H +#define LCHMURLHANDLER_H + +/** + @author tim +*/ +class LCHMUrlHandler{ +public: + LCHMUrlHandler(); + + ~LCHMUrlHandler(); + +}; + +#endif diff --git a/okular/generators/chm/lib/libchmfile.cpp b/okular/generators/chm/lib/libchmfile.cpp new file mode 100644 index 00000000..0620b308 --- /dev/null +++ b/okular/generators/chm/lib/libchmfile.cpp @@ -0,0 +1,127 @@ +/*************************************************************************** + * Copyright (C) 2004-2007 by Georgy Yunaev, gyunaev@ulduzsoft.com * + * Please do not use email address above for bug reports; see * + * the README file * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "libchmfile.h" +#include "libchmfileimpl.h" + +#include + + +LCHMFile::LCHMFile( ) +{ + m_impl = new LCHMFileImpl(); +} + +LCHMFile::~ LCHMFile( ) +{ + delete m_impl; +} + +bool LCHMFile::loadFile( const QString & archiveName ) +{ + return m_impl->loadFile( archiveName ); +} + +void LCHMFile::closeAll( ) +{ + m_impl->closeAll(); +} + +QString LCHMFile::title( ) const +{ + return m_impl->title(); +} + +QString LCHMFile::homeUrl( ) const +{ + QString url = m_impl->homeUrl(); + return url.isNull() ? "/" : url; +} + +bool LCHMFile::hasTableOfContents( ) const +{ + return !m_impl->m_topicsFile.isNull(); +} + +bool LCHMFile::hasIndexTable( ) const +{ + return !m_impl->m_indexFile.isNull(); +} + +bool LCHMFile::hasSearchTable( ) const +{ + return m_impl->m_searchAvailable; +} + +bool LCHMFile::parseTableOfContents( QVector< LCHMParsedEntry > * topics ) const +{ + return m_impl->parseFileAndFillArray( m_impl->m_topicsFile, topics, false ); +} + +bool LCHMFile::parseIndex( QVector< LCHMParsedEntry > * indexes ) const +{ + return m_impl->parseFileAndFillArray( m_impl->m_indexFile, indexes, true ); +} + +bool LCHMFile::getFileContentAsString( QString * str, const QString & url ) +{ + return m_impl->getFileContentAsString( str, url ); +} + +bool LCHMFile::getFileContentAsBinary( QByteArray * data, const QString & url ) +{ + return m_impl->getFileContentAsBinary( data, url ); +} + +bool LCHMFile::enumerateFiles( QStringList * files ) +{ + return m_impl->enumerateFiles( files ); +} + +QString LCHMFile::getTopicByUrl( const QString & url ) +{ + return m_impl->getTopicByUrl( url ); +} + +const QPixmap * LCHMFile::getBookIconPixmap( unsigned int imagenum ) +{ + return m_impl->getBookIconPixmap( imagenum ); +} + +const LCHMTextEncoding * LCHMFile::currentEncoding( ) const +{ + return m_impl->m_currentEncoding; +} + +bool LCHMFile::setCurrentEncoding( const LCHMTextEncoding * encoding ) +{ + return m_impl->setCurrentEncoding( encoding ); +} + +QString LCHMFile::normalizeUrl( const QString & url ) const +{ + return m_impl->normalizeUrl( url ); +} + +bool LCHMFile::getFileSize(unsigned int * size, const QString & url) +{ + return m_impl->getFileSize( size, url ); +} diff --git a/okular/generators/chm/lib/libchmfile.h b/okular/generators/chm/lib/libchmfile.h new file mode 100644 index 00000000..cb739ac7 --- /dev/null +++ b/okular/generators/chm/lib/libchmfile.h @@ -0,0 +1,280 @@ +/*************************************************************************** + * Copyright (C) 2004-2007 by Georgy Yunaev, gyunaev@ulduzsoft.com * + * Please do not use email address above for bug reports; see * + * the README file * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef INCLUDE_LIBCHMFILE_H +#define INCLUDE_LIBCHMFILE_H + +#include +#include +#include +#include +#include + +#include "libchmtextencoding.h" + + +//! Contains different (non-standard) image types +namespace LCHMBookIcons +{ + const int IMAGE_NONE = -1; + const int IMAGE_AUTO = -2; + const int IMAGE_INDEX = -3; + + const int MAX_BUILTIN_ICONS = 42; +} + + +//! Contains a single index or TOC entry. See LCHMFile::parseTOC() and LCHMFile::parseIndex() +typedef struct +{ + //! Entry name + QString name; + + //! Entry URLs. The TOC entry should have only one URL; the index entry could have several. + QStringList urls; + + //! Associated image number. Used for TOC only; indexes does not have the image. + //! Use LCHMFile::getBookIconPixmap() to get associated pixmap icon + int imageid; + + //! Indentation level for this entry. + int indent; + +} LCHMParsedEntry; + + +// forward declaration +class LCHMFileImpl; + +//! CHM files processor, heavily based on chmlib. Used search code from xchm. +class LCHMFile +{ + public: + //! Default constructor and destructor. + LCHMFile(); + ~LCHMFile(); + + /*! + * \brief Attempts to load a .chm file. + * \param archiveName The .chm filename. + * \return true on success, false on failure. + * + * Loads a CHM file. Could internally load more than one file, if files linked to + * this one are present locally (like MSDN). + * \ingroup init + */ + bool loadFile( const QString& archiveName ); + + /*! + * \brief Closes all the files, and frees the appropriate data. + * \ingroup init + */ + void closeAll(); + + /*! + * \brief Gets the title name of the opened .chm. + * \return The name of the opened document, or an empty string if no .chm has been loaded. + * \ingroup information + */ + QString title() const; + + /*! + * \brief Gets the URL of the default page in the chm archive. + * \return The home page name, with a '/' added in front and relative to + * the root of the archive filesystem. If no .chm has been opened, + * returns "/". + * \ingroup information + */ + QString homeUrl() const; + + /*! + * \brief Checks whether the Table of Contents is present in this file. + * \return true if it is available; false otherwise. + * \ingroup information + */ + bool hasTableOfContents() const; + + /*! + * \brief Checks whether the Index Table is present in this file. + * \return true if it is available; false otherwise. + * \ingroup information + */ + bool hasIndexTable() const; + + /*! + * \brief Checks whether the Search Table is available in this file. + * \return true if it is available; false otherwise. + * \ingroup information + * + * If the search table is not available, the search is not possible. + */ + bool hasSearchTable() const; + + /*! + * \brief Parses the Table of Contents (TOC) + * \param topics A pointer to the container which will store the parsed results. + * Will be cleaned before parsing. + * \return true if the tree is present and parsed successfully, false otherwise. + * The parser is built to be error-prone, however it still can abort with qFatal() + * by really buggy chm file; please report a bug if the file is opened ok under Windows. + * \ingroup fileparsing + */ + bool parseTableOfContents( QVector< LCHMParsedEntry > * topics ) const; + + /*! + * \brief Parses the Index Table + * \param indexes A pointer to the container which will store the parsed results. + * Will be cleaned before parsing. + * \return true if the tree is present and parsed successfully, false otherwise. + * The parser is built to be error-prone, however it still can abort with qFatal() + * by really buggy chm file; so far it never happened on indexes. + * \ingroup fileparsing + */ + bool parseIndex( QVector< LCHMParsedEntry > * indexes ) const; + + /*! + * \brief Retrieves the content from url in current chm file to QString. + * \param str A string where the retrieved content should be stored. + * \param url An URL in chm file to retrieve content from. Must be absolute. + * \return true if the content is successfully received; false otherwise. + * + * This function retrieves the file content (mostly for HTML pages) from the chm archive + * opened by load() function. Because the content in chm file is not stored in Unicode, it + * will be recoded according to current encoding. Do not use for binary data. + * + * \sa setCurrentEncoding() currentEncoding() getFileContentAsBinary() + * \ingroup dataretrieve + */ + bool getFileContentAsString( QString * str, const QString& url ); + + /*! + * \brief Retrieves the content from url in current chm file to QByteArray. + * \param data A data array where the retrieved content should be stored. + * \param url An URL in chm file to retrieve content from. Must be absolute. + * \return true if the content is successfully received; false otherwise. + * + * This function retrieves the file content from the chm archive opened by load() + * function. The content is not encoded. + * + * \sa getFileContentAsString() + * \ingroup dataretrieve + */ + bool getFileContentAsBinary( QByteArray * data, const QString& url ); + + /*! + * \brief Retrieves the content size. + * \param size A pointer where the size will be stored. + * \param url An URL in chm file to retrieve content from. Must be absolute. + * \return true if the content size is successfully stored; false otherwise. + * + * \ingroup dataretrieve + */ + bool getFileSize( unsigned int * size, const QString& url ); + + /*! + * \brief Obtains the list of all the files in current chm file archive. + * \param files An array to store list of URLs (file names) present in chm archive. + * \return true if the enumeration succeed; false otherwise (I could hardly imagine a reason). + * + * \ingroup dataretrieve + */ + bool enumerateFiles( QStringList * files ); + + /*! + * \brief Gets the Title of the HTML page referenced by url. + * \param url An URL in chm file to get title from. Must be absolute. + * \return The title, or QString::null if the URL cannot be found or not a HTML page. + * + * \ingroup dataretrieve + */ + QString getTopicByUrl ( const QString& url ); + + /*! + * \brief Gets the appropriate CHM pixmap icon. + * \param imagenum The image number from TOC. + * \return The pixmap to show in TOC tree. + * + * \ingroup dataretrieve + */ + const QPixmap * getBookIconPixmap( unsigned int imagenum ); + + /*! + * \brief Normalizes the URL, converting relatives, adding "/" in front and removing .. + * \param url The URL to normalize. + * \return The normalized, cleaned up URL. + * + * \ingroup dataretrieve + */ + QString normalizeUrl( const QString& url ) const; + + /*! + * \brief Gets the current CHM archive encoding (set or autodetected) + * \return The current encoding. + * + * \ingroup encoding + */ + const LCHMTextEncoding * currentEncoding() const; + + /*! + * \brief Sets the CHM archive encoding to use + * \param encoding An encoding to use. + * + * \ingroup encoding + */ + bool setCurrentEncoding ( const LCHMTextEncoding * encoding ); + + /*! + * \brief Execute a search query, return the results. + * \param query A search query. + * \param results An array to store URLs where the query was found. + * \return true if search was successful (this does not mean that it returned any results); + * false otherwise. + * + * This function executes a standard search query. The query should consist of one of more + * words separated by a space with a possible prefix. A prefix may be: + * + Plus indicates that the word is required; any page without this word is excluded from the result. + * - Minus indicates that the word is required to be absent; any page with this word is excluded from + * the result. + * "." Quotes indicates a phrase. Anything between quotes is a phrase, which is set of space-separated + * words. Will be in result only if the words in phrase are in page in the same sequence, and + * follow each other. + * + * If there is no prefix, the word considered as required. + * \ingroup search + */ + bool searchQuery ( const QString& query, QStringList * results, unsigned int limit = 100 ); + + //! Access to implementation + LCHMFileImpl * impl() { return m_impl; } + + private: + //! No copy construction allowed. + LCHMFile( const LCHMFile& ); + + //! No assignments allowed. + LCHMFile& operator=( const LCHMFile& ); + + //! Implementation + LCHMFileImpl * m_impl; +}; + + +#endif // INCLUDE_LIBCHMFILE_H diff --git a/okular/generators/chm/lib/libchmfile_search.cpp b/okular/generators/chm/lib/libchmfile_search.cpp new file mode 100644 index 00000000..11723468 --- /dev/null +++ b/okular/generators/chm/lib/libchmfile_search.cpp @@ -0,0 +1,289 @@ +/*************************************************************************** + * Copyright (C) 2004-2007 by Georgy Yunaev, gyunaev@ulduzsoft.com * + * Please do not use email address above for bug reports; see * + * the README file * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include + +#include "libchmfile.h" +#include "libchmfileimpl.h" + +//#define DEBUG_SEARCH(A) qDebug A +#define DEBUG_SEARCH(A) + + +static inline void validateWord ( QString & word, bool & query_valid ) +{ + QRegExp rxvalid ("[^\\d\\w_\\.]+"); + + QString orig = word; + word.remove ( rxvalid ); + + if ( word != orig ) + query_valid = false; +} + +static inline void validateWords ( QStringList & wordlist, bool & query_valid ) +{ + QRegExp rxvalid ("[^\\d\\w_\\.]+"); + + for ( int i = 0; i < wordlist.size(); i++ ) + validateWord ( wordlist[i], query_valid ); +} + + +inline static void mergeResults ( LCHMSearchProgressResults & results, const LCHMSearchProgressResults & src, bool add ) +{ + if ( results.empty() && add ) + { + results = src; + return; + } + + for ( int s1 = 0; s1 < results.size(); s1++ ) + { + bool found = false; + + for ( int s2 = 0; s2 < src.size(); s2++ ) + { + if ( results[s1].urloff == src[s2].urloff ) + { + found = true; + break; + } + } + + // If we're adding, we only add the items found (i.e. any item, which is not found, is removed. + // But if we're removing, we only remove the items found. + if ( (found && !add) || (!found && add) ) + { + results.erase ( results.begin() + s1 ); + s1--; + } + } +} + + +static inline void findNextWords ( QVector & src, const QVector & needle ) +{ + for ( int s1 = 0; s1 < src.size(); s1++ ) + { + bool found = false; + uint64_t target_offset = src[s1] + 1; + + DEBUG_SEARCH (("Offset loop: offset at %u is %u, target %u", (unsigned int) s1, + (unsigned int) src[s1], (unsigned int) target_offset)); + + // Search in the offsets list in attempt to find next word + for ( int s2 = 0; s2 < needle.size(); s2++ ) + { + if ( needle[s2] == target_offset ) + { + found = true; + break; + } + } + + if ( !found ) + { + // Remove this offset, we don't need it anymore + DEBUG_SEARCH (("Offset loop failed: offset %u not found", (unsigned int) target_offset)); + src.erase ( src.begin() + s1 ); + s1--; + } + else + { + DEBUG_SEARCH (("Offset loop succeed: offset %u found", (unsigned int) target_offset)); + src[s1]++; + } + } +} + + +inline bool searchPhrase( LCHMFileImpl * impl, const QStringList & phrase, LCHMSearchProgressResults & results ) +{ + // Accumulate the phrase data here. + LCHMSearchProgressResults phrasekeeper; + + // On the first word, just fill the phrasekeeper with every occupence of the first word + DEBUG_SEARCH (("Search word(0): '%s'", phrase[0].ascii())); + if ( !impl->searchWord ( phrase[0], true, false, phrasekeeper, true ) ) + return false; // the word not found, so the whole phrase is not found either. + + for ( int i = 1; i < phrase.size(); i++ ) + { + LCHMSearchProgressResults srchtmp; + + DEBUG_SEARCH (("Search word(%d): '%s'", i, phrase[i].ascii())); + if ( !impl->searchWord ( phrase[i], true, false, srchtmp, true ) ) + return false; // the ith word not found, so the whole phrase is not found either. + + // Iterate the both arrays, and remove every word in phrasekeeper, which is not found + // in the srchtmp, or is found on a different position. + for ( int p1 = 0; p1 < phrasekeeper.size(); p1++ ) + { + bool found = false; + + DEBUG_SEARCH (("Ext loop (it %d): urloff %d", p1, phrasekeeper[p1].urloff)); + + for ( int p2 = 0; p2 < srchtmp.size(); p2++ ) + { + // look up for words on the same page + if ( srchtmp[p2].urloff != phrasekeeper[p1].urloff ) + continue; + + // Now check every offset to find the one which is 1 bigger than the + findNextWords ( phrasekeeper[p1].offsets, srchtmp[p2].offsets ); + + // If at least one next word is found, we leave the block intact, otherwise remove it. + if ( !phrasekeeper[p1].offsets.empty() ) + found = true; + } + + if ( !found ) + { + DEBUG_SEARCH (("Ext loop: this word not found on %d, remove it", phrasekeeper[p1].urloff)); + phrasekeeper.erase ( phrasekeeper.begin() + p1 ); + p1--; + } + } + } + + for ( int o = 0; o < phrasekeeper.size(); o++ ) + results.push_back ( LCHMSearchProgressResult (phrasekeeper[o].titleoff, phrasekeeper[o].urloff) ); + + return !results.empty(); +} + + + +bool LCHMFile::searchQuery( const QString& inquery, QStringList * searchresults, unsigned int limit ) +{ + QStringList words_must_exist, words_must_not_exist, words_highlight; + QVector phrases_must_exist; + QString query = inquery; + bool query_valid = true; + LCHMSearchProgressResults results; + int pos; + int i; + + /* + * Parse the search query with a simple state machine. + * Query should consist of one of more words separated by a space with a possible prefix. + * A prefix may be: + * + indicates that the word is required; any page without this word is excluded from the result. + * - indicates that the word is required to be absent; any page with this word is excluded from + * the result. + * "." indicates a phrase. Anything between quotes indicates a phrase, which is set of space-separated + * words. Will be in result only if the words in phrase are in page in the same sequence, and + * follow each other. + * If there is no prefix, the word considered as required. + */ + + QRegExp rxphrase( "\"(.*)\"" ); + QRegExp rxword( "([^\\s]+)" ); + rxphrase.setMinimal( TRUE ); + + // First, get the phrase queries + while ( (pos = rxphrase.indexIn (query, 0)) != -1 ) + { + // A phrase query found. Locate its boundaries, and parse it. + QStringList plist = rxphrase.cap ( 1 ).split ( QRegExp ("\\s+") ); + + validateWords ( plist, query_valid ); + + if ( plist.size() > 0 ) + phrases_must_exist.push_back( plist ); + + query.remove (pos, rxphrase.matchedLength()); + } + + // Then, parse the rest query + while ( (pos = rxword.indexIn( query, 0 )) != -1 ) + { + // A phrase query found. Locate its boundaries, and parse it. + QString word = rxword.cap ( 1 ); + QChar type = '+'; + + if ( word[0] == '-' || word[0] == '+' ) + { + type = word[0]; + word.remove (0, 1); + } + + validateWord ( word, query_valid ); + + if ( type == '-' ) + words_must_not_exist.push_back ( word ); + else + words_must_exist.push_back ( word ); + + query.remove (pos, rxword.matchedLength()); + } + +#if defined (DUMP_SEARCH_QUERY) + // Dump the search query + QString qdump; + for ( i = 0; i < phrases_must_exist.size(); i++ ) + qdump += QString(" \"") + phrases_must_exist[i].join (" ") + QString ("\""); + + for ( i = 0; i < words_must_not_exist.size(); i++ ) + qdump += QString (" -") + words_must_not_exist[i]; + + for ( i = 0; i < words_must_exist.size(); i++ ) + qdump += QString (" +") + words_must_exist[i]; + + qDebug ("Search query dump: %s", qdump.ascii()); +#endif + + // First search for phrases + if ( phrases_must_exist.size() > 0 ) + { + LCHMSearchProgressResults tempres; + + for ( i = 0; i < phrases_must_exist.size(); i++ ) + { + if ( !searchPhrase ( impl(), phrases_must_exist[i], tempres ) ) + return false; + + mergeResults ( results, tempres, true ); + } + } + + for ( i = 0; i < words_must_exist.size(); i++ ) + { + LCHMSearchProgressResults tempres; + + if ( !m_impl->searchWord ( words_must_exist[i], true, false, tempres, false ) ) + return false; + + mergeResults ( results, tempres, true ); + } + + for ( i = 0; i < words_must_not_exist.size(); i++ ) + { + LCHMSearchProgressResults tempres; + + m_impl->searchWord ( words_must_not_exist[i], true, false, tempres, false ); + mergeResults ( results, tempres, false ); + } + + m_impl->getSearchResults( results, searchresults, limit ); + return true; +} diff --git a/okular/generators/chm/lib/libchmfileimpl.cpp b/okular/generators/chm/lib/libchmfileimpl.cpp new file mode 100644 index 00000000..929fb19c --- /dev/null +++ b/okular/generators/chm/lib/libchmfileimpl.cpp @@ -0,0 +1,1288 @@ +/*************************************************************************** + * Copyright (C) 2004-2007 by Georgy Yunaev, gyunaev@ulduzsoft.com * + * Portions Copyright (C) 2003 Razvan Cojocaru * + * Please do not use email address above for bug reports; see * + * the README file * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include "chm_lib.h" +#include "bitfiddle.h" +#include "libchmfile.h" +#include "libchmurlfactory.h" +#include "libchmfileimpl.h" + +// Big-enough buffer size for use with various routines. +#define BUF_SIZE 4096 +#define COMMON_BUF_LEN 1025 + +#define TOPICS_ENTRY_LEN 16 +#define URLTBL_ENTRY_LEN 12 + +//#define DEBUGPARSER(A) qDebug A +#define DEBUGPARSER(A) ; + +class KCHMShowWaitCursor +{ + public: + KCHMShowWaitCursor () { QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) ); } + ~KCHMShowWaitCursor () { QApplication::restoreOverrideCursor(); } +}; + + +LCHMFileImpl::LCHMFileImpl( ) +{ + m_chmFile = NULL; + m_filename = m_font = QString::null; + + m_entityDecodeMap.clear(); + m_textCodec = 0; + m_textCodecForSpecialFiles = 0; + m_detectedLCID = 0; + m_currentEncoding = 0; +} + + +LCHMFileImpl::~ LCHMFileImpl( ) +{ + closeAll(); +} + + +bool LCHMFileImpl::loadFile( const QString & archiveName ) +{ + QString filename; + + // If the file has a file:// prefix, remove it + if ( archiveName.startsWith( "file://" ) ) + filename = archiveName.mid( 7 ); // strip it + else + filename = archiveName; + + if( m_chmFile ) + closeAll(); + + m_chmFile = chm_open( QFile::encodeName(filename) ); + + if ( m_chmFile == NULL ) + return false; + + m_filename = filename; + + // Reset encoding + m_textCodec = 0; + m_textCodecForSpecialFiles = 0; + m_currentEncoding = 0; + + // Get information from /#WINDOWS and /#SYSTEM files (encoding, title, context file and so) + // and guess the encoding + getInfoFromWindows(); + getInfoFromSystem(); + guessTextEncoding(); + + // Check whether the search tables are present + if ( ResolveObject("/#TOPICS", &m_chmTOPICS) + && ResolveObject("/#STRINGS", &m_chmSTRINGS) + && ResolveObject("/#URLTBL", &m_chmURLTBL) + && ResolveObject("/#URLSTR", &m_chmURLSTR) ) + { + m_lookupTablesValid = true; + fillTopicsUrlMap(); + } + else + m_lookupTablesValid = false; + + if ( m_lookupTablesValid && ResolveObject ("/$FIftiMain", &m_chmFIftiMain) ) + m_searchAvailable = true; + else + m_searchAvailable = false; + + // Some CHM files have toc and index files, but do not set the name properly. + // Some heuristics here. + chmUnitInfo tui; + + if ( m_topicsFile.isEmpty() && ResolveObject("/toc.hhc", &tui) ) + m_topicsFile = "/toc.hhc"; + + if ( m_indexFile.isEmpty() && ResolveObject("/index.hhk", &tui) ) + m_indexFile = "/index.hhk"; + + return true; +} + + +void LCHMFileImpl::closeAll( ) +{ + if ( m_chmFile == NULL ) + return; + + chm_close( m_chmFile ); + + m_chmFile = NULL; + m_filename = m_font = QString::null; + + m_home.clear(); + m_topicsFile.clear(); + m_indexFile.clear(); + + m_entityDecodeMap.clear(); + m_textCodec = 0; + m_textCodecForSpecialFiles = 0; + m_detectedLCID = 0; + m_currentEncoding = 0; +} + + +QString LCHMFileImpl::decodeEntity( const QString & entity ) +{ + // Set up m_entityDecodeMap characters according to current textCodec + if ( m_entityDecodeMap.isEmpty() ) + { + m_entityDecodeMap["AElig"] = encodeWithCurrentCodec ("\306"); // capital AE diphthong (ligature) + m_entityDecodeMap["Aacute"] = encodeWithCurrentCodec ("\301"); // capital A, acute accent + m_entityDecodeMap["Acirc"] = encodeWithCurrentCodec ("\302"); // capital A, circumflex accent + m_entityDecodeMap["Agrave"] = encodeWithCurrentCodec ("\300"); // capital A, grave accent + m_entityDecodeMap["Aring"] = encodeWithCurrentCodec ("\305"); // capital A, ring + m_entityDecodeMap["Atilde"] = encodeWithCurrentCodec ("\303"); // capital A, tilde + m_entityDecodeMap["Auml"] = encodeWithCurrentCodec ("\304"); // capital A, dieresis or umlaut mark + m_entityDecodeMap["Ccedil"] = encodeWithCurrentCodec ("\307"); // capital C, cedilla + m_entityDecodeMap["Dstrok"] = encodeWithCurrentCodec ("\320"); // whatever + m_entityDecodeMap["ETH"] = encodeWithCurrentCodec ("\320"); // capital Eth, Icelandic + m_entityDecodeMap["Eacute"] = encodeWithCurrentCodec ("\311"); // capital E, acute accent + m_entityDecodeMap["Ecirc"] = encodeWithCurrentCodec ("\312"); // capital E, circumflex accent + m_entityDecodeMap["Egrave"] = encodeWithCurrentCodec ("\310"); // capital E, grave accent + m_entityDecodeMap["Euml"] = encodeWithCurrentCodec ("\313"); // capital E, dieresis or umlaut mark + m_entityDecodeMap["Iacute"] = encodeWithCurrentCodec ("\315"); // capital I, acute accent + m_entityDecodeMap["Icirc"] = encodeWithCurrentCodec ("\316"); // capital I, circumflex accent + m_entityDecodeMap["Igrave"] = encodeWithCurrentCodec ("\314"); // capital I, grave accent + m_entityDecodeMap["Iuml"] = encodeWithCurrentCodec ("\317"); // capital I, dieresis or umlaut mark + m_entityDecodeMap["Ntilde"] = encodeWithCurrentCodec ("\321"); // capital N, tilde + m_entityDecodeMap["Oacute"] = encodeWithCurrentCodec ("\323"); // capital O, acute accent + m_entityDecodeMap["Ocirc"] = encodeWithCurrentCodec ("\324"); // capital O, circumflex accent + m_entityDecodeMap["Ograve"] = encodeWithCurrentCodec ("\322"); // capital O, grave accent + m_entityDecodeMap["Oslash"] = encodeWithCurrentCodec ("\330"); // capital O, slash + m_entityDecodeMap["Otilde"] = encodeWithCurrentCodec ("\325"); // capital O, tilde + m_entityDecodeMap["Ouml"] = encodeWithCurrentCodec ("\326"); // capital O, dieresis or umlaut mark + m_entityDecodeMap["THORN"] = encodeWithCurrentCodec ("\336"); // capital THORN, Icelandic + m_entityDecodeMap["Uacute"] = encodeWithCurrentCodec ("\332"); // capital U, acute accent + m_entityDecodeMap["Ucirc"] = encodeWithCurrentCodec ("\333"); // capital U, circumflex accent + m_entityDecodeMap["Ugrave"] = encodeWithCurrentCodec ("\331"); // capital U, grave accent + m_entityDecodeMap["Uuml"] = encodeWithCurrentCodec ("\334"); // capital U, dieresis or umlaut mark + m_entityDecodeMap["Yacute"] = encodeWithCurrentCodec ("\335"); // capital Y, acute accent + m_entityDecodeMap["OElig"] = encodeWithCurrentCodec ("\338"); // capital Y, acute accent + m_entityDecodeMap["oelig"] = encodeWithCurrentCodec ("\339"); // capital Y, acute accent + + m_entityDecodeMap["aacute"] = encodeWithCurrentCodec ("\341"); // small a, acute accent + m_entityDecodeMap["acirc"] = encodeWithCurrentCodec ("\342"); // small a, circumflex accent + m_entityDecodeMap["aelig"] = encodeWithCurrentCodec ("\346"); // small ae diphthong (ligature) + m_entityDecodeMap["agrave"] = encodeWithCurrentCodec ("\340"); // small a, grave accent + m_entityDecodeMap["aring"] = encodeWithCurrentCodec ("\345"); // small a, ring + m_entityDecodeMap["atilde"] = encodeWithCurrentCodec ("\343"); // small a, tilde + m_entityDecodeMap["auml"] = encodeWithCurrentCodec ("\344"); // small a, dieresis or umlaut mark + m_entityDecodeMap["ccedil"] = encodeWithCurrentCodec ("\347"); // small c, cedilla + m_entityDecodeMap["eacute"] = encodeWithCurrentCodec ("\351"); // small e, acute accent + m_entityDecodeMap["ecirc"] = encodeWithCurrentCodec ("\352"); // small e, circumflex accent + m_entityDecodeMap["Scaron"] = encodeWithCurrentCodec ("\352"); // small e, circumflex accent + m_entityDecodeMap["egrave"] = encodeWithCurrentCodec ("\350"); // small e, grave accent + m_entityDecodeMap["eth"] = encodeWithCurrentCodec ("\360"); // small eth, Icelandic + m_entityDecodeMap["euml"] = encodeWithCurrentCodec ("\353"); // small e, dieresis or umlaut mark + m_entityDecodeMap["iacute"] = encodeWithCurrentCodec ("\355"); // small i, acute accent + m_entityDecodeMap["icirc"] = encodeWithCurrentCodec ("\356"); // small i, circumflex accent + m_entityDecodeMap["igrave"] = encodeWithCurrentCodec ("\354"); // small i, grave accent + m_entityDecodeMap["iuml"] = encodeWithCurrentCodec ("\357"); // small i, dieresis or umlaut mark + m_entityDecodeMap["ntilde"] = encodeWithCurrentCodec ("\361"); // small n, tilde + m_entityDecodeMap["oacute"] = encodeWithCurrentCodec ("\363"); // small o, acute accent + m_entityDecodeMap["ocirc"] = encodeWithCurrentCodec ("\364"); // small o, circumflex accent + m_entityDecodeMap["ograve"] = encodeWithCurrentCodec ("\362"); // small o, grave accent + m_entityDecodeMap["oslash"] = encodeWithCurrentCodec ("\370"); // small o, slash + m_entityDecodeMap["otilde"] = encodeWithCurrentCodec ("\365"); // small o, tilde + m_entityDecodeMap["ouml"] = encodeWithCurrentCodec ("\366"); // small o, dieresis or umlaut mark + m_entityDecodeMap["szlig"] = encodeWithCurrentCodec ("\337"); // small sharp s, German (sz ligature) + m_entityDecodeMap["thorn"] = encodeWithCurrentCodec ("\376"); // small thorn, Icelandic + m_entityDecodeMap["uacute"] = encodeWithCurrentCodec ("\372"); // small u, acute accent + m_entityDecodeMap["ucirc"] = encodeWithCurrentCodec ("\373"); // small u, circumflex accent + m_entityDecodeMap["ugrave"] = encodeWithCurrentCodec ("\371"); // small u, grave accent + m_entityDecodeMap["uuml"] = encodeWithCurrentCodec ("\374"); // small u, dieresis or umlaut mark + m_entityDecodeMap["yacute"] = encodeWithCurrentCodec ("\375"); // small y, acute accent + m_entityDecodeMap["yuml"] = encodeWithCurrentCodec ("\377"); // small y, dieresis or umlaut mark + + m_entityDecodeMap["iexcl"] = encodeWithCurrentCodec ("\241"); + m_entityDecodeMap["cent"] = encodeWithCurrentCodec ("\242"); + m_entityDecodeMap["pound"] = encodeWithCurrentCodec ("\243"); + m_entityDecodeMap["curren"] = encodeWithCurrentCodec ("\244"); + m_entityDecodeMap["yen"] = encodeWithCurrentCodec ("\245"); + m_entityDecodeMap["brvbar"] = encodeWithCurrentCodec ("\246"); + m_entityDecodeMap["sect"] = encodeWithCurrentCodec ("\247"); + m_entityDecodeMap["uml"] = encodeWithCurrentCodec ("\250"); + m_entityDecodeMap["ordf"] = encodeWithCurrentCodec ("\252"); + m_entityDecodeMap["laquo"] = encodeWithCurrentCodec ("\253"); + m_entityDecodeMap["not"] = encodeWithCurrentCodec ("\254"); + m_entityDecodeMap["shy"] = encodeWithCurrentCodec ("\255"); + m_entityDecodeMap["macr"] = encodeWithCurrentCodec ("\257"); + m_entityDecodeMap["deg"] = encodeWithCurrentCodec ("\260"); + m_entityDecodeMap["plusmn"] = encodeWithCurrentCodec ("\261"); + m_entityDecodeMap["sup1"] = encodeWithCurrentCodec ("\271"); + m_entityDecodeMap["sup2"] = encodeWithCurrentCodec ("\262"); + m_entityDecodeMap["sup3"] = encodeWithCurrentCodec ("\263"); + m_entityDecodeMap["acute"] = encodeWithCurrentCodec ("\264"); + m_entityDecodeMap["micro"] = encodeWithCurrentCodec ("\265"); + m_entityDecodeMap["para"] = encodeWithCurrentCodec ("\266"); + m_entityDecodeMap["middot"] = encodeWithCurrentCodec ("\267"); + m_entityDecodeMap["cedil"] = encodeWithCurrentCodec ("\270"); + m_entityDecodeMap["ordm"] = encodeWithCurrentCodec ("\272"); + m_entityDecodeMap["raquo"] = encodeWithCurrentCodec ("\273"); + m_entityDecodeMap["frac14"] = encodeWithCurrentCodec ("\274"); + m_entityDecodeMap["frac12"] = encodeWithCurrentCodec ("\275"); + m_entityDecodeMap["frac34"] = encodeWithCurrentCodec ("\276"); + m_entityDecodeMap["iquest"] = encodeWithCurrentCodec ("\277"); + m_entityDecodeMap["times"] = encodeWithCurrentCodec ("\327"); + m_entityDecodeMap["divide"] = encodeWithCurrentCodec ("\367"); + + m_entityDecodeMap["copy"] = encodeWithCurrentCodec ("\251"); // copyright sign + m_entityDecodeMap["reg"] = encodeWithCurrentCodec ("\256"); // registered sign + m_entityDecodeMap["nbsp"] = encodeWithCurrentCodec ("\240"); // non breaking space + + m_entityDecodeMap["fnof"] = QChar((unsigned short) 402); + + m_entityDecodeMap["Delta"] = QChar((unsigned short) 916); + m_entityDecodeMap["Pi"] = QChar((unsigned short) 928); + m_entityDecodeMap["Sigma"] = QChar((unsigned short) 931); + + m_entityDecodeMap["beta"] = QChar((unsigned short) 946); + m_entityDecodeMap["gamma"] = QChar((unsigned short) 947); + m_entityDecodeMap["delta"] = QChar((unsigned short) 948); + m_entityDecodeMap["eta"] = QChar((unsigned short) 951); + m_entityDecodeMap["theta"] = QChar((unsigned short) 952); + m_entityDecodeMap["lambda"] = QChar((unsigned short) 955); + m_entityDecodeMap["mu"] = QChar((unsigned short) 956); + m_entityDecodeMap["nu"] = QChar((unsigned short) 957); + m_entityDecodeMap["pi"] = QChar((unsigned short) 960); + m_entityDecodeMap["rho"] = QChar((unsigned short) 961); + + m_entityDecodeMap["lsquo"] = QChar((unsigned short) 8216); + m_entityDecodeMap["rsquo"] = QChar((unsigned short) 8217); + m_entityDecodeMap["rdquo"] = QChar((unsigned short) 8221); + m_entityDecodeMap["bdquo"] = QChar((unsigned short) 8222); + m_entityDecodeMap["trade"] = QChar((unsigned short) 8482); + m_entityDecodeMap["ldquo"] = QChar((unsigned short) 8220); + m_entityDecodeMap["ndash"] = QChar((unsigned short) 8211); + m_entityDecodeMap["mdash"] = QChar((unsigned short) 8212); + m_entityDecodeMap["bull"] = QChar((unsigned short) 8226); + m_entityDecodeMap["hellip"] = QChar((unsigned short) 8230); + m_entityDecodeMap["emsp"] = QChar((unsigned short) 8195); + m_entityDecodeMap["rarr"] = QChar((unsigned short) 8594); + m_entityDecodeMap["rArr"] = QChar((unsigned short) 8658); + m_entityDecodeMap["crarr"] = QChar((unsigned short) 8629); + m_entityDecodeMap["le"] = QChar((unsigned short) 8804); + m_entityDecodeMap["ge"] = QChar((unsigned short) 8805); + m_entityDecodeMap["lte"] = QChar((unsigned short) 8804); // wrong, but used somewhere + m_entityDecodeMap["gte"] = QChar((unsigned short) 8805); // wrong, but used somewhere + m_entityDecodeMap["dagger"] = QChar((unsigned short) 8224); + m_entityDecodeMap["Dagger"] = QChar((unsigned short) 8225); + m_entityDecodeMap["euro"] = QChar((unsigned short) 8364); + m_entityDecodeMap["asymp"] = QChar((unsigned short) 8776); + m_entityDecodeMap["isin"] = QChar((unsigned short) 8712); + m_entityDecodeMap["notin"] = QChar((unsigned short) 8713); + m_entityDecodeMap["prod"] = QChar((unsigned short) 8719); + m_entityDecodeMap["ne"] = QChar((unsigned short) 8800); + + m_entityDecodeMap["amp"] = "&"; // ampersand + m_entityDecodeMap["gt"] = ">"; // greater than + m_entityDecodeMap["lt"] = "<"; // less than + m_entityDecodeMap["quot"] = "\""; // double quote + m_entityDecodeMap["apos"] = "'"; // single quote + m_entityDecodeMap["frasl"] = "/"; + m_entityDecodeMap["minus"] = "-"; + m_entityDecodeMap["oplus"] = "+"; + m_entityDecodeMap["Prime"] = "\""; + } + + // If entity is an ASCII code like 〽 - just decode it + if ( entity[0] == '#' ) + { + bool valid; + unsigned int ascode = entity.mid(1).toUInt( &valid ); + + if ( !valid ) + { + qWarning ( "LCHMFileImpl::decodeEntity: could not decode HTML entity '%s'", qPrintable( entity ) ); + return QString::null; + } + + return (QString) (QChar( ascode )); + } + else + { + QMap::const_iterator it = m_entityDecodeMap.constFind( entity ); + + if ( it == m_entityDecodeMap.constEnd() ) + { + qWarning ("LCHMFileImpl::decodeEntity: could not decode HTML entity '%s'", qPrintable( entity )); + return QString::null; + } + + return *it; + } +} + + +inline int LCHMFileImpl::findStringInQuotes (const QString& tag, int offset, QString& value, bool firstquote, bool decodeentities) +{ + int qbegin = tag.indexOf ('"', offset); + + if ( qbegin == -1 ) + qFatal ("LCHMFileImpl::findStringInQuotes: cannot find first quote in tag: '%s'", qPrintable( tag )); + + int qend = firstquote ? tag.indexOf ('"', qbegin + 1) : tag.lastIndexOf ('"'); + + if ( qend == -1 || qend <= qbegin ) + qFatal ("LCHMFileImpl::findStringInQuotes: cannot find last quote in tag: '%s'", qPrintable( tag )); + + // If we do not need to decode HTML entities, just return. + if ( decodeentities ) + { + QString htmlentity = QString::null; + bool fill_entity = false; + + value.reserve (qend - qbegin); // to avoid multiple memory allocations + + for ( int i = qbegin + 1; i < qend; i++ ) + { + if ( !fill_entity ) + { + if ( tag[i] == '&' ) // HTML entity starts + fill_entity = true; + else + value.append (tag[i]); + } + else + { + if ( tag[i] == ';' ) // HTML entity ends + { + // If entity is an ASCII code, just decode it + QString decode = decodeEntity( htmlentity ); + + if ( decode.isNull() ) + break; + + value.append ( decode ); + htmlentity = QString::null; + fill_entity = false; + } + else + htmlentity.append (tag[i]); + } + } + } + else + value = tag.mid (qbegin + 1, qend - qbegin - 1); + + return qend + 1; +} + + +bool LCHMFileImpl::searchWord (const QString& text, + bool wholeWords, + bool titlesOnly, + LCHMSearchProgressResults& results, + bool phrase_search) +{ + bool partial = false; + + if ( text.isEmpty() || !m_searchAvailable ) + return false; + + QString searchword = (QString) convertSearchWord (text); + +#define FTS_HEADER_LEN 0x32 + unsigned char header[FTS_HEADER_LEN]; + + if ( RetrieveObject (&m_chmFIftiMain, header, 0, FTS_HEADER_LEN) == 0 ) + return false; + + unsigned char doc_index_s = header[0x1E], doc_index_r = header[0x1F]; + unsigned char code_count_s = header[0x20], code_count_r = header[0x21]; + unsigned char loc_codes_s = header[0x22], loc_codes_r = header[0x23]; + + if(doc_index_s != 2 || code_count_s != 2 || loc_codes_s != 2) + { + // Don't know how to use values other than 2 yet. Maybe next chmspec. + return false; + } + + unsigned char* cursor32 = header + 0x14; + uint32_t node_offset = UINT32ARRAY(cursor32); + + cursor32 = header + 0x2e; + uint32_t node_len = UINT32ARRAY(cursor32); + + unsigned char* cursor16 = header + 0x18; + uint16_t tree_depth = UINT16ARRAY(cursor16); + + unsigned char word_len, pos; + QString word; + uint32_t i = sizeof(uint16_t); + uint16_t free_space; + + QVector buffer(node_len); + + node_offset = GetLeafNodeOffset (searchword, node_offset, node_len, tree_depth); + + if ( !node_offset ) + return false; + + do + { + // got a leaf node here. + if ( RetrieveObject (&m_chmFIftiMain, buffer.data(), node_offset, node_len) == 0 ) + return false; + + cursor16 = buffer.data() + 6; + free_space = UINT16ARRAY(cursor16); + + i = sizeof(uint32_t) + sizeof(uint16_t) + sizeof(uint16_t); + uint64_t wlc_count, wlc_size; + uint32_t wlc_offset; + + while (i < node_len - free_space) + { + word_len = *(buffer.data() + i); + pos = *(buffer.data() + i + 1); + + char *wrd_buf = new char[word_len]; + memcpy (wrd_buf, buffer.data() + i + 2, word_len - 1); + wrd_buf[word_len - 1] = 0; + + if ( pos == 0 ) + word = wrd_buf; + else + word = word.mid (0, pos) + (const char*) wrd_buf; + + delete[] wrd_buf; + + i += 2 + word_len; + unsigned char title = *(buffer.data() + i - 1); + + size_t encsz; + wlc_count = be_encint (buffer.data() + i, encsz); + i += encsz; + + cursor32 = buffer.data() + i; + wlc_offset = UINT32ARRAY(cursor32); + + i += sizeof(uint32_t) + sizeof(uint16_t); + wlc_size = be_encint (buffer.data() + i, encsz); + i += encsz; + + cursor32 = buffer.data(); + node_offset = UINT32ARRAY(cursor32); + + if ( !title && titlesOnly ) + continue; + + if ( wholeWords && searchword == word ) + return ProcessWLC(wlc_count, wlc_size, + wlc_offset, doc_index_s, + doc_index_r,code_count_s, + code_count_r, loc_codes_s, + loc_codes_r, results, phrase_search); + + if ( !wholeWords ) + { + if ( word.startsWith (searchword)) + { + partial = true; + + ProcessWLC(wlc_count, wlc_size, + wlc_offset, doc_index_s, + doc_index_r,code_count_s, + code_count_r, loc_codes_s, + loc_codes_r, results, phrase_search); + + } + else if ( QString::compare (searchword, word.mid(0, searchword.length())) < -1 ) + break; + } + } + } + while ( !wholeWords && word.startsWith (searchword) && node_offset ); + + return partial; +} + + +bool LCHMFileImpl::ResolveObject(const QString& fileName, chmUnitInfo *ui) const +{ + return m_chmFile != NULL + && ::chm_resolve_object(m_chmFile, qPrintable( fileName ), ui) == + CHM_RESOLVE_SUCCESS; +} + + +size_t LCHMFileImpl::RetrieveObject(const chmUnitInfo *ui, unsigned char *buffer, + LONGUINT64 fileOffset, LONGINT64 bufferSize) const +{ + return ::chm_retrieve_object(m_chmFile, const_cast(ui), + buffer, fileOffset, bufferSize); +} + + +inline uint32_t LCHMFileImpl::GetLeafNodeOffset(const QString& text, + uint32_t initialOffset, + uint32_t buffSize, + uint16_t treeDepth) +{ + uint32_t test_offset = 0; + unsigned char* cursor16, *cursor32; + unsigned char word_len, pos; + uint32_t i = sizeof(uint16_t); + QVector buffer(buffSize); + QString word; + + while(--treeDepth) + { + if ( initialOffset == test_offset ) + return 0; + + test_offset = initialOffset; + if ( RetrieveObject (&m_chmFIftiMain, buffer.data(), initialOffset, buffSize) == 0 ) + return 0; + + cursor16 = buffer.data(); + uint16_t free_space = UINT16ARRAY(cursor16); + + while (i < buffSize - free_space ) + { + word_len = *(buffer.data() + i); + pos = *(buffer.data() + i + 1); + + char *wrd_buf = new char[word_len]; + memcpy ( wrd_buf, buffer.data() + i + 2, word_len - 1 ); + wrd_buf[word_len - 1] = 0; + + if ( pos == 0 ) + word = wrd_buf; + else + word = word.mid(0, pos) + (const char*) wrd_buf; + + delete[] wrd_buf; + + if ( text <= word ) + { + cursor32 = buffer.data() + i + word_len + 1; + initialOffset = UINT32ARRAY(cursor32); + break; + } + + i += word_len + sizeof(unsigned char) + + sizeof(uint32_t) + sizeof(uint16_t); + } + } + + if ( initialOffset == test_offset ) + return 0; + + return initialOffset; +} + + +inline bool LCHMFileImpl::ProcessWLC (uint64_t wlc_count, uint64_t wlc_size, + uint32_t wlc_offset, unsigned char ds, + unsigned char dr, unsigned char cs, + unsigned char cr, unsigned char ls, + unsigned char lr, + LCHMSearchProgressResults& results, + bool phrase_search) +{ + int wlc_bit = 7; + uint64_t index = 0, count; + size_t length, off = 0; + QVector buffer (wlc_size); + unsigned char *cursor32; + + unsigned char entry[TOPICS_ENTRY_LEN]; + unsigned char combuf[13]; + + if ( RetrieveObject (&m_chmFIftiMain, buffer.data(), wlc_offset, wlc_size) == 0 ) + return false; + + for ( uint64_t i = 0; i < wlc_count; ++i ) + { + if ( wlc_bit != 7 ) + { + ++off; + wlc_bit = 7; + } + + index += sr_int (buffer.data() + off, &wlc_bit, ds, dr, length); + off += length; + + if ( RetrieveObject (&m_chmTOPICS, entry, index * 16, TOPICS_ENTRY_LEN) == 0 ) + return false; + + LCHMSearchProgressResult progres; + + cursor32 = entry + 4; + progres.titleoff = UINT32ARRAY(cursor32); + + cursor32 = entry + 8; + progres.urloff = UINT32ARRAY(cursor32); + + if ( RetrieveObject (&m_chmURLTBL, combuf, progres.urloff, 12) == 0 ) + return false; + + cursor32 = combuf + 8; + progres.urloff = UINT32ARRAY (cursor32); + + count = sr_int (buffer.data() + off, &wlc_bit, cs, cr, length); + off += length; + + if ( phrase_search ) + progres.offsets.reserve (count); + + for (uint64_t j = 0; j < count; ++j) + { + uint64_t lcode = sr_int (buffer.data() + off, &wlc_bit, ls, lr, length); + off += length; + + if ( phrase_search ) + progres.offsets.push_back (lcode); + } + + results.push_back (progres); + } + + return true; +} + + +bool LCHMFileImpl::getInfoFromWindows() +{ +#define WIN_HEADER_LEN 0x08 + unsigned char buffer[BUF_SIZE]; + unsigned int factor; + chmUnitInfo ui; + long size = 0; + + if ( ResolveObject("/#WINDOWS", &ui) ) + { + if ( !RetrieveObject(&ui, buffer, 0, WIN_HEADER_LEN) ) + return false; + + uint32_t entries = get_int32_le( (uint32_t *)(buffer) ); + uint32_t entry_size = get_int32_le( (uint32_t *)(buffer + 0x04) ); + + QVector uptr(entries * entry_size); + unsigned char* raw = (unsigned char*) uptr.data(); + + if ( !RetrieveObject (&ui, raw, 8, entries * entry_size) ) + return false; + + if( !ResolveObject ("/#STRINGS", &ui) ) + return false; + + for ( uint32_t i = 0; i < entries; ++i ) + { + uint32_t offset = i * entry_size; + + uint32_t off_title = get_int32_le( (uint32_t *)(raw + offset + 0x14) ); + uint32_t off_home = get_int32_le( (uint32_t *)(raw + offset + 0x68) ); + uint32_t off_hhc = get_int32_le( (uint32_t *)(raw + offset + 0x60) ); + uint32_t off_hhk = get_int32_le( (uint32_t *)(raw + offset + 0x64) ); + + factor = off_title / 4096; + + if ( size == 0 ) + size = RetrieveObject(&ui, buffer, factor * 4096, BUF_SIZE); + + if ( size && off_title ) + m_title = QByteArray( (const char*) (buffer + off_title % 4096) ); + + if ( factor != off_home / 4096) + { + factor = off_home / 4096; + size = RetrieveObject (&ui, buffer, factor * 4096, BUF_SIZE); + } + + if ( size && off_home ) + m_home = QByteArray("/") + QByteArray( (const char*) buffer + off_home % 4096); + + if ( factor != off_hhc / 4096) + { + factor = off_hhc / 4096; + size = RetrieveObject(&ui, buffer, factor * 4096, BUF_SIZE); + } + + if ( size && off_hhc ) + m_topicsFile = QByteArray("/") + QByteArray((const char*) buffer + off_hhc % 4096); + + if ( factor != off_hhk / 4096) + { + factor = off_hhk / 4096; + size = RetrieveObject (&ui, buffer, factor * 4096, BUF_SIZE); + } + + if ( size && off_hhk ) + m_indexFile = QByteArray("/") + QByteArray((const char*) buffer + off_hhk % 4096); + } + } + return true; +} + + + +bool LCHMFileImpl::getInfoFromSystem() +{ + unsigned char buffer[BUF_SIZE]; + chmUnitInfo ui; + + int index = 0; + unsigned char* cursor = NULL, *p; + uint16_t value = 0; + long size = 0; + + // Run the first loop to detect the encoding. We need this, because title could be + // already encoded in user encoding. Same for file names + if ( !ResolveObject ("/#SYSTEM", &ui) ) + return false; + + // Can we pull BUFF_SIZE bytes of the #SYSTEM file? + if ( (size = RetrieveObject (&ui, buffer, 4, BUF_SIZE)) == 0 ) + return false; + + buffer[size - 1] = 0; + + // First loop to detect the encoding + for ( index = 0; index < (size - 1 - (long)sizeof(uint16_t)) ;) + { + cursor = buffer + index; + value = UINT16ARRAY(cursor); + + switch(value) + { + case 0: + index += 2; + cursor = buffer + index; + + if(m_topicsFile.isEmpty()) + m_topicsFile = QByteArray("/") + QByteArray((const char*) buffer + index + 2); + + break; + + case 1: + index += 2; + cursor = buffer + index; + + if(m_indexFile.isEmpty()) + m_indexFile = QByteArray("/") + QByteArray((const char*)buffer + index + 2); + break; + + case 2: + index += 2; + cursor = buffer + index; + + if(m_home.isEmpty() || m_home == "/") + m_home = QByteArray("/") + QByteArray((const char*) buffer + index + 2); + break; + + case 3: + index += 2; + cursor = buffer + index; + m_title = QByteArray( (const char*) (buffer + index + 2) ); + break; + + case 4: + index += 2; + cursor = buffer + index; + + p = buffer + index + 2; + m_detectedLCID = (short) (p[0] | (p[1]<<8)); + + break; + + case 6: + index += 2; + cursor = buffer + index; + + if ( m_topicsFile.isEmpty() ) + { + QString topicAttempt = "/", tmp; + topicAttempt += QString ((const char*) buffer +index +2); + + tmp = topicAttempt + ".hhc"; + + if ( ResolveObject( tmp, &ui) ) + m_topicsFile = qPrintable( tmp ); + + tmp = topicAttempt + ".hhk"; + + if ( ResolveObject( tmp, &ui) ) + m_indexFile = qPrintable( tmp ); + } + break; + + case 16: + index += 2; + cursor = buffer + index; + + m_font = QString ((const char*) buffer + index + 2); + break; + + default: + index += 2; + cursor = buffer + index; + } + + value = UINT16ARRAY(cursor); + index += value + 2; + } + + return true; +} + + +QByteArray LCHMFileImpl::convertSearchWord( const QString & src ) +{ + static const char * searchwordtable[128] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "s", 0, "oe", 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "s", 0, "oe", 0, 0, "y", + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + "a", "a", "a", "a", "a", "a", "ae", "c", "e", "e", "e", "e", "i", "i", "i", "i", + "d", "n", "o", "o", "o", "o", "o", 0, "o", "u", "u", "u", "u", "y", "\xDE", "ss", + "a", "a", "a", "a", "a", "a", "ae", "c", "e", "e", "e", "e", "i", "i", "i", "i", + "o", "n", "o", "o", "o", "o", "o", 0, "o", "u", "u", "u", "u", "y", "\xFE", "y" + }; + + if ( !m_textCodec ) + return (QByteArray) qPrintable( src.toLower() ); + + QByteArray dest = m_textCodec->fromUnicode (src); + + for ( int i = 0; i < dest.size(); i++ ) + { + if ( dest[i] & 0x80 ) + { + int index = dest[i] & 0x7F; + if ( searchwordtable[index] ) + dest.replace (i, 1, searchwordtable[index]); + else + dest.remove (i, 1); + } + } + + return dest.toLower(); +} + + + +void LCHMFileImpl::getSearchResults( const LCHMSearchProgressResults& tempres, + QStringList * results, + unsigned int limit_results ) +{ + unsigned char combuf [COMMON_BUF_LEN]; + QMap urlsmap; // used to prevent duplicated urls + + for ( int i = 0; i < tempres.size(); i++ ) + { + if ( urlsmap.find (tempres[i].urloff) != urlsmap.end() ) + continue; + + urlsmap[tempres[i].urloff] = 1; + + if ( RetrieveObject (&m_chmURLSTR, combuf, tempres[i].urloff + 8, COMMON_BUF_LEN - 1) == 0 ) + continue; + + combuf[COMMON_BUF_LEN - 1] = 0; + results->push_back( LCHMUrlFactory::makeURLabsoluteIfNeeded( (const char*) combuf ) ); + + if ( --limit_results == 0 ) + break; + } +} + + +QString LCHMFileImpl::normalizeUrl( const QString & path ) const +{ + int pos = path.indexOf ('#'); + QString fixedpath = pos == -1 ? path : path.left (pos); + + return LCHMUrlFactory::makeURLabsoluteIfNeeded( fixedpath ); +} + + +/* + * FIXME: + * (from htmlhelp.chm) +*/ +bool LCHMFileImpl::parseFileAndFillArray( const QString & file, QVector< LCHMParsedEntry > * data, bool asIndex ) +{ + QString src; + const int MAX_NEST_DEPTH = 256; + + if ( !getFileContentAsString( &src, file ) || src.isEmpty() ) + return false; + + KCHMShowWaitCursor wc; + +/* + // Save the index for debugging purposes + QFile outfile( "parsed.htm" ); + + if ( outfile.open( IO_WriteOnly ) ) + { + QTextStream textstream( &outfile ); + textstream << src; + outfile.close(); + } +*/ + + unsigned int defaultimagenum = asIndex ? LCHMBookIcons::IMAGE_INDEX : LCHMBookIcons::IMAGE_AUTO; + int pos = 0, indent = 0, root_indent_offset = 0; + bool in_object = false, root_indent_offset_set = false; + + LCHMParsedEntry entry; + entry.imageid = defaultimagenum; + + // Split the HHC file by HTML tags + int stringlen = src.length(); + + while ( pos < stringlen && (pos = src.indexOf ('<', pos)) != -1 ) + { + int i, word_end = 0; + + for ( i = ++pos; i < stringlen; i++ ) + { + // If a " or ' is found, skip to the next one. + if ( (src[i] == '"' || src[i] == '\'') ) + { + // find where quote ends, either by another quote, or by '>' symbol (some people don't know HTML) + int nextpos = src.indexOf (src[i], i+1); + if ( nextpos == -1 && (nextpos = src.indexOf ('>', i+1)) == -1 ) + { + qWarning ("LCHMFileImpl::ParseHhcAndFillTree: corrupted TOC: %s", qPrintable( src.mid(i) )); + return false; + } + + i = nextpos; + } + else if ( src[i] == '>' ) + break; + else if ( !src[i].isLetterOrNumber() && src[i] != '/' && !word_end ) + word_end = i; + } + + QString tagword, tag = src.mid (pos, i - pos); + + if ( word_end ) + tagword = src.mid (pos, word_end - pos).toLower(); + else + tagword = tag.toLower(); + + //qDebug ("tag: '%s', tagword: '%s'\n", qPrintable( tag ), qPrintable( tagword ) ); + + // - a topic entry + if ( tagword == "object" && tag.indexOf ("text/sitemap", 0, Qt::CaseInsensitive ) != -1 ) + in_object = true; + else if ( tagword == "/object" && in_object ) + { + // a topic entry closed. Add a tree item + if ( !entry.name.isEmpty() ) + { + if ( !root_indent_offset_set ) + { + root_indent_offset_set = true; + root_indent_offset = indent; + + if ( root_indent_offset > 1 ) + qWarning("CHM has improper index; root indent offset is %d", root_indent_offset); + } + + // Trim the entry name + entry.name = entry.name.trimmed(); + + int real_indent = indent - root_indent_offset; + + entry.indent = real_indent; + data->push_back( entry ); + } + else + { + if ( !entry.urls.isEmpty() ) + qDebug ("LCHMFileImpl::ParseAndFillTopicsTree: tag with url \"%s\" is parsed, but name is empty.", qPrintable( entry.urls[0] )); + else + qDebug ("LCHMFileImpl::ParseAndFillTopicsTree: tag is parsed, but both name and url are empty."); + } + + entry.name = QString::null; + entry.urls.clear(); + entry.imageid = defaultimagenum; + in_object = false; + } + else if ( tagword == "param" && in_object ) + { + // + int offset; // strlen("param ") + QString name_pattern = "name=", value_pattern = "value="; + QString pname, pvalue; + + if ( (offset = tag.indexOf (name_pattern, 0, Qt::CaseInsensitive )) == -1 ) + qFatal ("LCHMFileImpl::ParseAndFillTopicsTree: bad tag '%s': no name=\n", qPrintable( tag )); + + // offset+5 skips 'name=' + offset = findStringInQuotes (tag, offset + name_pattern.length(), pname, TRUE, FALSE); + pname = pname.toLower(); + + if ( (offset = tag.indexOf(value_pattern, offset, Qt::CaseInsensitive )) == -1 ) + qFatal ("LCHMFileImpl::ParseAndFillTopicsTree: bad tag '%s': no value=\n", qPrintable( tag )); + + // offset+6 skips 'value=' + findStringInQuotes (tag, offset + value_pattern.length(), pvalue, FALSE, TRUE); + + //qDebug (": name '%s', value '%s'", qPrintable( pname ), qPrintable( pvalue )); + + if ( pname == "name" ) + { + // Some help files contain duplicate names, where the second name is empty. Work it around by keeping the first one + if ( !pvalue.isEmpty() ) + entry.name = pvalue; + } + else if ( pname == "local" ) + { + // Check for URL duplication + QString url = LCHMUrlFactory::makeURLabsoluteIfNeeded( pvalue ); + + if ( !entry.urls.contains( url ) ) + entry.urls.push_back( url ); + } + else if ( pname == "see also" && asIndex && entry.name != pvalue ) + entry.urls.push_back (":" + pvalue); + else if ( pname == "imagenumber" ) + { + bool bok; + int imgnum = pvalue.toInt (&bok); + + if ( bok && imgnum >= 0 && imgnum < LCHMBookIcons::MAX_BUILTIN_ICONS ) + entry.imageid = imgnum; + } + } + else if ( tagword == "ul" ) // increase indent level + { + // Fix for buggy help files + if ( ++indent >= MAX_NEST_DEPTH ) + qFatal("LCHMFileImpl::ParseAndFillTopicsTree: max nest depth (%d) is reached, error in help file", MAX_NEST_DEPTH); + + // This intended to fix

chiQ-BeL8%CfLwLq!zrnX*abBQ%dM05DX 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 literal 0 HcmV?d00001 diff --git a/kgpg/icons/hisc-status-key-single.svgz b/kgpg/icons/hisc-status-key-single.svgz new file mode 100644 index 0000000000000000000000000000000000000000..ffaa5cabc71aa729a259e85a7eb524b147b8eb8e GIT binary patch literal 28667 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 literal 0 HcmV?d00001 diff --git a/kgpg/keyexport.cpp b/kgpg/keyexport.cpp new file mode 100644 index 00000000..acd43740 --- /dev/null +++ b/kgpg/keyexport.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2011 Rolf Eike Beer + * Copyright (C) 2011 Luis Ángel Fernández Fernández + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "keyexport.h" +#include + +KeyExport::KeyExport(QWidget *parent, const QStringList &keyservers) + : KDialog(parent), + Ui_KeyExport() +{ + setupUi(this); + setMainWidget(widget); + setCaption(i18n("Public Key Export")); + setButtons(KDialog::Ok | KDialog::Cancel); + setDefaultButton(KDialog::Ok); + newFilename->setWindowTitle(i18n("Save File")); + newFilename->setMode(KFile::File); + + if (!keyservers.isEmpty()) { + checkServer->setEnabled(true); + checkServer->setToolTip(QString()); + destServer->addItems(keyservers); + } +} + +void KeyExport::accept() +{ + if (checkFile->isChecked()) { + if (QFile::exists(newFilename->url().path().simplified())) { + const QString message = i18n("Overwrite existing file %1?", newFilename->url().fileName()); + int result = KMessageBox::warningContinueCancel(this, message, QString(), KStandardGuiItem::overwrite()); + if (KMessageBox::Cancel == result) + return; + } + } + + QDialog::accept(); +} + +#include "keyexport.moc" diff --git a/kgpg/keyexport.h b/kgpg/keyexport.h new file mode 100644 index 00000000..45c9dbc5 --- /dev/null +++ b/kgpg/keyexport.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2002 Jean-Baptiste Mardelle + * Copyright (C) 2007,2008,2009,2010,2011 Rolf Eike Beer + * Copyright (C) 2011 Luis Ángel Fernández Fernández + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#ifndef KEYEXPORT_H +#define KEYEXPORT_H + +#include "ui_keyexport.h" + +#include +#include + +class KeyExport : public KDialog, public Ui_KeyExport +{ + Q_OBJECT + +public: + explicit KeyExport(QWidget *parent = 0, const QStringList &keyservers = QStringList()); + +protected: + virtual void accept(); +}; + +#endif diff --git a/kgpg/keyexport.ui b/kgpg/keyexport.ui new file mode 100644 index 00000000..44195de9 --- /dev/null +++ b/kgpg/keyexport.ui @@ -0,0 +1,193 @@ + + + KeyExport + + + + 0 + 0 + 626 + 411 + + + + + 0 + 0 + + + + + 350 + 0 + + + + + + + + + + + + + + + + Email + + + + + + + Clipboard + + + + + + + false + + + You must define at least one keyserver in settings before you can export to one. + + + Key server: + + + + + + + false + + + + + + + File: + + + true + + + + + + + true + + + + + + + + + + Export Settings + + + + + + Export everything + + + true + + + + + + + Do not export attributes (photo ids) + + + + + + + Clean key + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 602 + 108 + + + + + + + + + + + + + + + KComboBox + QComboBox +

F+13`X3e6}3I-TWdB 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

R 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 literal 0 HcmV?d00001 diff --git a/gwenview/doc/importer-picking-root-folder.png b/gwenview/doc/importer-picking-root-folder.png new file mode 100644 index 0000000000000000000000000000000000000000..fd2edc986b61602f891a0f2ac3fee9f54df5aaad GIT binary patch literal 229186 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 Fx;a%>MvyRc-B< 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